From f4e00f5e6b7824dd9597117d9ed15ac46ae5f17d Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 12 Apr 2023 09:06:42 -0700 Subject: [PATCH 0001/1053] Initial commit --- .github/workflows/ci.yaml | 66 ++ .gitignore | 4 + .gitmodules | 3 + CODEOWNERS | 2 + Cargo.toml | 34 + LICENSE.md | 59 ++ README.md | 66 ++ examples/benchmark.rs | 62 ++ examples/dump.rs | 40 + examples/rev.rs | 45 ++ examples/simple.rs | 65 ++ figures/architecture.svg | 1 + figures/three-layers.svg | 1 + rustfmt.toml | 9 + src/account.rs | 168 ++++ src/db.rs | 1005 +++++++++++++++++++++++ src/file.rs | 112 +++ src/lib.rs | 202 +++++ src/merkle.rs | 1611 +++++++++++++++++++++++++++++++++++++ src/storage.rs | 1206 +++++++++++++++++++++++++++ tests/db.rs | 85 ++ tests/merkle.rs | 239 ++++++ 22 files changed, 5085 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CODEOWNERS create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 examples/benchmark.rs create mode 100644 examples/dump.rs create mode 100644 examples/rev.rs create mode 100644 examples/simple.rs create mode 100644 figures/architecture.svg create mode 100644 figures/three-layers.svg create mode 100644 rustfmt.toml create mode 100644 src/account.rs create mode 100644 src/db.rs create mode 100644 src/file.rs create mode 100644 src/lib.rs create mode 100644 src/merkle.rs create mode 100644 src/storage.rs create mode 100644 tests/db.rs create mode 100644 tests/merkle.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000000..196eab6622e8 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,66 @@ +name: ci + +on: + pull_request: + branches: + - "*" + +env: + CARGO_TERM_COLOR: always + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt, clippy + - name: Lint + run: cargo fmt && git diff --exit-code + - name: Clippy + run: cargo clippy --fix --no-deps && git diff --exit-code + + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Build + run: cargo build --verbose + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Run tests + run: cargo test --verbose + + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Run simple example + run: cargo run --example simple + - name: Run dump example + run: cargo run --example dump + - name: Run benchmark example + run: cargo run --example benchmark -- --nbatch 10000 --batch-size 10 + - name: Run rev example + run: cargo run --example rev diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..2cf520df75c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +shale/target/ +shale/Cargo.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..e5c0fb6d4b44 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "shale"] + path = shale + url = https://github.com/Determinant/shale.git diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000000..6092c7373ede --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# CODEOWNERS +* @Determinant @exdx @haohao-os @gyuho @hexfusion diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..e87402864be3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "firewood" +version = "0.1.0" +edition = "2021" + +[dependencies] +enum-as-inner = "0.5.1" +parking_lot = "0.12.1" +rlp = "0.5.1" +sha3 = "0.10.2" +once_cell = "1.13.1" +hex = "0.4.3" +lru = "0.8.0" +libaio-futures = "0.2.2" +nix = "0.25.0" +typed-builder = "0.10.0" +tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } +shale = "0.1.7" +growth-ring = "0.3.0" +futures = "0.3.24" +primitive-types = { verison = "0.12.0", features = ["impl-rlp"] } + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + +[dev-dependencies] +clap = { version = "4.0.2", features = ["cargo", "derive"] } +criterion = "0.4.0" +keccak-hasher = "0.15.3" +rand = "0.8.5" +triehash = "0.8.4" + +[profile.release] +debug = true diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000000..8f543d3df535 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,59 @@ +© 2023 Ava Labs, Inc. + +Ecosystem License + +Subject to the terms herein, Ava Labs, Inc. (**“Ava Labs”**) hereby grants you a +limited, royalty-free, worldwide, non-sublicensable, +non-transferable,non-exclusive license to use, copy, modify, create derivative +works based on,and redistribute the Software, in source code, binary, or any +other form,including any modifications or derivative works of the Software +(collectively,**“Licensed Software”**), in each case subject to this Ecosystem +License (**“License”**). + +This License applies to all copies, modifications, derivative works, and any +other form or usage of the Licensed Software. You will include and display this +License, without modification, with all uses of the Licensed Software,regardless +of form. + +You will use the Licensed Software solely in connection with the Avalanche +Public Blockchain platform and associated blockchains, comprised exclusively of +the Avalanche X-Chain, C-Chain, P-Chain and any subnets linked to the +P-Chain(“Avalanche Authorized Platform”). This License does not permit use of +the Licensed Software in connection with any forks of the Avalanche Authorized +Platform or in any manner not operationally connected to the Avalanche +Authorized Platform. Ava Labs may publicly announce changes or additions to the +Avalanche Authorized Platform, which may expand or modify usage of the Licensed +Software. Upon such announcement, the Avalanche Authorized Platform will be +deemed to be the then-current iteration of such platform. + +You hereby acknowledge and agree to the terms set forth at +www.avalabs.org/important-notice. + +If you use the Licensed Software in violation of this License, this License will +automatically terminate and Ava Labs reserves all rights to seek any remedy for +such violation. + +Except for uses explicitly permitted in this License, Ava Labs retains all +rights in the Licensed Software, including without limitation the ability to +modify it. + +Except as required or explicitly permitted by this License, you will not use any +Ava Labs names, logos, or trademarks without Ava Labs’ prior written consent. + +You may use this License for software other than the “Licensed Software” +specified above, as long as the only change to this License is the definition of +the term “Licensed Software.” + +The Licensed Software may reference third party components. You acknowledge and +agree that these third party components may be governed by a separate license or +terms and that you will comply with them. + +**TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSED SOFTWARE IS PROVIDED ON +AN “AS IS” BASIS, AND AVA LABS EXPRESSLY DISCLAIMS AND EXCLUDES ALL +REPRESENTATIONS, WARRANTIES AND OTHER TERMS AND CONDITIONS, WHETHER EXPRESS OR +IMPLIED, INCLUDING WITHOUT LIMITATION BY OPERATION OF LAW OR BY CUSTOM, STATUTE +OR OTHERWISE, AND INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTY, TERM, OR +CONDITION OF NON-INFRINGEMENT, MERCHANTABILITY, TITLE, OR FITNESS FOR PARTICULAR +PURPOSE. YOU USE THE LICENSED SOFTWARE AT YOUR OWN RISK. AVA LABS EXPRESSLY +DISCLAIMS ALL LIABILITY (INCLUDING FOR ALL DIRECT, CONSEQUENTIAL OR OTHER +DAMAGES OR LOSSES) RELATED TO ANY USE OF THE LICENSED SOFTWARE.** diff --git a/README.md b/README.md new file mode 100644 index 000000000000..7a89e2911fe0 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. + +Firewood is an embedded key-value store, optimized to store blockchain state. +It prioritizes access to latest state, by providing extremely fast reads, but +also provides a limited view into past state. It does not copy-on-write the +Merkle Patricia Trie (MPT) to generate an ever growing forest of tries like EVM, +but instead keeps one latest version of the MPT index on disk and apply +in-place updates to it. This ensures that the database size is small and stable +during the course of running firewood. Firewood was first conceived to provide +a very fast storage layer for qEVM to enable a fast, complete EVM system with +right design choices made totally from scratch, but it also serves as a drop-in +replacement for any EVM-compatible blockchain storage system, and fits for the +general use of a certified key-value store of arbitrary data. + +Firewood is a robust database implemented from the ground up to directly store +MPT nodes and user data. Unlike most (if not all) of the solutions in the field, +it is not built on top of a generic KV store such as LevelDB/RocksDB. Like a +B+-tree based store, firewood directly uses the tree structure as the index on +disk. Thus, there is no additional “emulation” of the logical MPT to flatten +out the data structure to feed into the underlying DB that is unaware of the +data being stored. + +Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL +guarantees atomicity and durability in the database, but also offers +“reversibility”: some portion of the old WAL can be optionally kept around to +allow a fast in-memory rollback to recover some past versions of the entire +store back in memory. While running the store, new changes will also contribute +to the configured window of changes (at batch granularity) to access any past +versions with no additional cost at all. + +The on-disk footprint of Firewood is more compact than geth. It provides two +isolated storage space which can be both or selectively used the user. The +account model portion of the storage offers something very similar to StateDB +in geth, which captures the address-“state key” style of two-level access for +an account’s (smart contract’s) state. Therefore, it takes minimal effort to +delegate all state storage from an EVM implementation to firewood. The other +portion of the storage supports generic MPT storage for arbitrary keys and +values. When unused, there is no additional cost. + +Firewood is an embedded key-value store, optimized to store blockchain state. +It prioritizes access to latest state, by providing extremely fast reads, but +also provides a limited view into past state. It does not copy-on-write like +the EVM, but instead makes in-place changes to the state tree. This ensures +that the database size is small and stable during the course of running +firewood. Firewood exists to provide a very fast storage layer for [qEVM](https://github.com/ava-labs/qevm) to use in a custom subnet. + +## Build +Firewood currently is Linux-only, as it has a dependency on the asynchronous +I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not +able to successfully emulate the syscalls `libaio` relies on, so Linux or a +Linux VM must be used to run firewood. It is encouraged to enhance the project +with I/O supports for other OSes, such as OSX (where `kqueue` needs to be used +for async I/O) and Windows. Please contact us if you're interested in such contribution. + +Firewood is written in stable Rust, but relies on the Rust nightly toolchain +for code linting/formatting. + +## Run +There are several examples, in the examples directory, that simulate real world +use-cases. Try running them via the command-line, via `cargo run --release +--example simple`. + +## Test +``` +cargo test --release +``` diff --git a/examples/benchmark.rs b/examples/benchmark.rs new file mode 100644 index 000000000000..934ecc958b03 --- /dev/null +++ b/examples/benchmark.rs @@ -0,0 +1,62 @@ +use clap::Parser; +use criterion::Criterion; +use firewood::db::{DBConfig, WALConfig, DB}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg(long)] + nbatch: usize, + #[arg(short, long)] + batch_size: usize, + #[arg(short, long, default_value_t = 0)] + seed: u64, + #[arg(short, long, default_value_t = false)] + no_root_hash: bool, +} + +fn main() { + let args = Args::parse(); + + let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + { + use rand::{Rng, SeedableRng}; + let mut c = Criterion::default(); + let mut group = c.benchmark_group("insert".to_string()); + let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed); + let nbatch = args.nbatch; + let batch_size = args.batch_size; + let total = nbatch * batch_size; + let root_hash = !args.no_root_hash; + let mut workload = Vec::new(); + for _ in 0..nbatch { + let mut batch: Vec<(Vec<_>, Vec<_>)> = Vec::new(); + for _ in 0..batch_size { + batch.push((rng.gen::<[u8; 32]>().into(), rng.gen::<[u8; 32]>().into())); + } + workload.push(batch); + } + println!("workload prepared"); + group.sampling_mode(criterion::SamplingMode::Flat).sample_size(10); + group.throughput(criterion::Throughput::Elements(total as u64)); + group.bench_with_input( + format!("nbatch={nbatch} batch_size={batch_size}"), + &workload, + |b, workload| { + b.iter(|| { + let db = DB::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); + for batch in workload.iter() { + let mut wb = db.new_writebatch(); + for (k, v) in batch { + wb = wb.kv_insert(k, v.clone()).unwrap(); + } + if !root_hash { + wb = wb.no_root_hash(); + } + wb.commit(); + } + }) + }, + ); + } +} diff --git a/examples/dump.rs b/examples/dump.rs new file mode 100644 index 000000000000..39b8324ee348 --- /dev/null +++ b/examples/dump.rs @@ -0,0 +1,40 @@ +use clap::{command, Arg, ArgMatches}; +use firewood::db::{DBConfig, DBError, WALConfig, DB}; + +fn main() { + let matches = command!() + .arg(Arg::new("INPUT").help("db path name").required(false).index(1)) + .get_matches(); + let path = get_db_path(matches); + let db = DB::new(path.unwrap().as_str(), &DBConfig::builder().truncate(false).build()).unwrap(); + let mut stdout = std::io::stdout(); + println!("== Account Model =="); + db.dump(&mut stdout).unwrap(); + println!("== Generic KV =="); + db.kv_dump(&mut stdout).unwrap(); +} + +/// Returns the provided INPUT db path if one is provided. +/// Otherwise, instantiate a DB called simple_db and return the path. +fn get_db_path(matches: ArgMatches) -> Result { + if let Some(m) = matches.get_one::("INPUT") { + return Ok(m.to_string()) + } + + // Build and provide a new db path + let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + let db = DB::new("simple_db", &cfg.truncate(true).build()).unwrap(); + db.new_writebatch() + .set_balance(b"ted", 10.into()) + .unwrap() + .set_code(b"ted", b"smart contract byte code here!") + .unwrap() + .set_nonce(b"ted", 10086) + .unwrap() + .set_state(b"ted", b"x", b"1".to_vec()) + .unwrap() + .set_state(b"ted", b"y", b"2".to_vec()) + .unwrap() + .commit(); + Ok("simple_db".to_string()) +} diff --git a/examples/rev.rs b/examples/rev.rs new file mode 100644 index 000000000000..47f23c1b3069 --- /dev/null +++ b/examples/rev.rs @@ -0,0 +1,45 @@ +use firewood::db::{DBConfig, WALConfig, DB}; + +fn main() { + let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + { + let db = DB::new("rev_db", &cfg.clone().truncate(true).build()).unwrap(); + let items = vec![ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]; + for (k, v) in items.iter() { + db.new_writebatch() + .kv_insert(k, v.as_bytes().to_vec()) + .unwrap() + .commit(); + println!("{}", hex::encode(*db.kv_root_hash().unwrap())); + } + db.kv_dump(&mut std::io::stdout()).unwrap(); + println!( + "{}", + hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) + ); + println!( + "{}", + hex::encode(*db.get_revision(2, None).unwrap().kv_root_hash().unwrap()) + ); + } + { + let db = DB::new("rev_db", &cfg.truncate(false).build()).unwrap(); + { + let rev = db.get_revision(1, None).unwrap(); + print!("{}", hex::encode(*rev.kv_root_hash().unwrap())); + rev.kv_dump(&mut std::io::stdout()).unwrap(); + } + { + let rev = db.get_revision(2, None).unwrap(); + print!("{}", hex::encode(*rev.kv_root_hash().unwrap())); + rev.kv_dump(&mut std::io::stdout()).unwrap(); + } + } +} diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 000000000000..38776135c062 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,65 @@ +use firewood::db::{DBConfig, WALConfig, DB}; + +fn print_states(db: &DB) { + println!("======"); + for account in ["ted", "alice"] { + let addr = account.as_bytes(); + println!("{account}.balance = {}", db.get_balance(addr).unwrap()); + println!("{account}.nonce = {}", db.get_nonce(addr).unwrap()); + println!( + "{}.code = {}", + account, + std::str::from_utf8(&db.get_code(addr).unwrap()).unwrap() + ); + for state_key in ["x", "y", "z"] { + println!( + "{}.state.{} = {}", + account, + state_key, + std::str::from_utf8(&db.get_state(addr, state_key.as_bytes()).unwrap()).unwrap() + ); + } + } +} + +fn main() { + let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + { + let db = DB::new("simple_db", &cfg.clone().truncate(true).build()).unwrap(); + db.new_writebatch() + .set_balance(b"ted", 10.into()) + .unwrap() + .set_code(b"ted", b"smart contract byte code here!") + .unwrap() + .set_nonce(b"ted", 10086) + .unwrap() + .set_state(b"ted", b"x", b"1".to_vec()) + .unwrap() + .set_state(b"ted", b"y", b"2".to_vec()) + .unwrap() + .commit(); + } + { + let db = DB::new("simple_db", &cfg.clone().truncate(false).build()).unwrap(); + print_states(&db); + db.new_writebatch() + .set_state(b"alice", b"z", b"999".to_vec()) + .unwrap() + .commit(); + print_states(&db); + } + { + let db = DB::new("simple_db", &cfg.truncate(false).build()).unwrap(); + print_states(&db); + let mut stdout = std::io::stdout(); + let mut acc = None; + db.dump(&mut stdout).unwrap(); + db.dump_account(b"ted", &mut stdout).unwrap(); + db.new_writebatch().delete_account(b"ted", &mut acc).unwrap(); + assert!(acc.is_some()); + print_states(&db); + db.dump_account(b"ted", &mut stdout).unwrap(); + db.new_writebatch().delete_account(b"nobody", &mut acc).unwrap(); + assert!(acc.is_none()); + } +} diff --git a/figures/architecture.svg b/figures/architecture.svg new file mode 100644 index 000000000000..deeddcfad964 --- /dev/null +++ b/figures/architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/figures/three-layers.svg b/figures/three-layers.svg new file mode 100644 index 000000000000..8681bef2aa4a --- /dev/null +++ b/figures/three-layers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000000..90f1c4e35a03 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,9 @@ +edition = "2018" +unstable_features = true +max_width = 120 +binop_separator = "Back" +inline_attribute_width = 80 +fn_args_layout = "Compressed" +hard_tabs = false +tab_spaces = 4 +trailing_semicolon = false diff --git a/src/account.rs b/src/account.rs new file mode 100644 index 000000000000..abd385de8756 --- /dev/null +++ b/src/account.rs @@ -0,0 +1,168 @@ +use std::fmt; +use std::io::{Cursor, Write}; + +use crate::merkle::{Hash, Node, ValueTransformer}; +use primitive_types::U256; +use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; + +pub struct Account { + pub nonce: u64, + pub balance: U256, + pub root: ObjPtr, + pub code: ObjPtr, + pub root_hash: Hash, + pub code_hash: Hash, +} + +impl Account { + pub fn empty_code() -> &'static Hash { + use once_cell::sync::OnceCell; + static V: OnceCell = OnceCell::new(); + V.get_or_init(|| { + Hash( + hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + .unwrap() + .try_into() + .unwrap(), + ) + }) + } + + pub fn serialize(&self) -> Vec { + let mut buff = Vec::new(); + buff.extend(self.nonce.to_le_bytes()); + buff.resize(40, 0); + self.balance.to_big_endian(&mut buff[8..40]); + buff.extend((self.root.addr()).to_le_bytes()); + buff.extend((self.code.addr()).to_le_bytes()); + buff.extend(self.root_hash.0); + buff.extend(self.code_hash.0); + buff + } + + pub fn deserialize(raw: &[u8]) -> Self { + let nonce = u64::from_le_bytes(raw[..8].try_into().unwrap()); + let balance = U256::from_big_endian(&raw[8..40]); + let root = u64::from_le_bytes(raw[40..48].try_into().unwrap()); + let code = u64::from_le_bytes(raw[48..56].try_into().unwrap()); + let root_hash = Hash(raw[56..88].try_into().unwrap()); + let code_hash = Hash(raw[88..].try_into().unwrap()); + + unsafe { + Self { + nonce, + balance, + root: ObjPtr::new_from_addr(root), + code: ObjPtr::new_from_addr(code), + root_hash, + code_hash, + } + } + } + + pub fn set_code(&mut self, code_hash: Hash, code: ObjPtr) { + self.code_hash = code_hash; + self.code = code; + } +} + +pub struct AccountRLP; + +impl ValueTransformer for AccountRLP { + fn transform(raw: &[u8]) -> Vec { + let acc = Account::deserialize(raw); + let mut stream = rlp::RlpStream::new_list(4); + stream.append(&acc.nonce); + stream.append(&acc.balance); + stream.append(&&acc.root_hash[..]); + stream.append(&&acc.code_hash[..]); + stream.out().into() + } +} + +impl Default for Account { + fn default() -> Self { + Account { + nonce: 0, + balance: U256::zero(), + root: ObjPtr::null(), + code: ObjPtr::null(), + root_hash: crate::merkle::Merkle::empty_root().clone(), + code_hash: Self::empty_code().clone(), + } + } +} + +pub enum Blob { + Code(Vec), +} + +impl MummyItem for Blob { + // currently there is only one variant of Blob: Code + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem.get_view(addr, 4).ok_or(ShaleError::LinearMemStoreError)?; + let len = u32::from_le_bytes(raw[..].try_into().unwrap()) as u64; + let bytes = mem.get_view(addr + 4, len).ok_or(ShaleError::LinearMemStoreError)?; + Ok(Self::Code(bytes.to_vec())) + } + + fn dehydrated_len(&self) -> u64 { + match self { + Self::Code(code) => 4 + code.len() as u64, + } + } + + fn dehydrate(&self, to: &mut [u8]) { + match self { + Self::Code(code) => { + let mut cur = Cursor::new(to); + cur.write_all(&(code.len() as u32).to_le_bytes()).unwrap(); + cur.write_all(code).unwrap(); + } + } + } +} + +#[derive(Debug)] +pub enum BlobError { + Shale(ShaleError), +} + +pub struct BlobStash { + store: Box>, +} + +impl BlobStash { + pub fn new(store: Box>) -> Self { + Self { store } + } + + pub fn get_blob(&self, ptr: ObjPtr) -> Result, BlobError> { + self.store.get_item(ptr).map_err(BlobError::Shale) + } + + pub fn new_blob(&self, item: Blob) -> Result, BlobError> { + self.store.put_item(item, 0).map_err(BlobError::Shale) + } + + pub fn free_blob(&mut self, ptr: ObjPtr) -> Result<(), BlobError> { + self.store.free_item(ptr).map_err(BlobError::Shale) + } + + pub fn flush_dirty(&self) -> Option<()> { + self.store.flush_dirty() + } +} + +impl fmt::Debug for Account { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "", + self.balance, + self.nonce, + hex::encode(*self.code_hash), + hex::encode(*self.root_hash) + ) + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 000000000000..44274d155d19 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,1005 @@ +use std::collections::VecDeque; +use std::io::{Cursor, Write}; +use std::rc::Rc; +use std::thread::JoinHandle; + +use parking_lot::{Mutex, MutexGuard}; +use primitive_types::U256; +use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID}; +use typed_builder::TypedBuilder; + +use crate::account::{Account, AccountRLP, Blob, BlobStash}; +use crate::file; +use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; +use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared}; +pub use crate::storage::{DiskBufferConfig, WALConfig}; + +const MERKLE_META_SPACE: SpaceID = 0x0; +const MERKLE_PAYLOAD_SPACE: SpaceID = 0x1; +const BLOB_META_SPACE: SpaceID = 0x2; +const BLOB_PAYLOAD_SPACE: SpaceID = 0x3; +const SPACE_RESERVED: u64 = 0x1000; + +const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; + +#[derive(Debug)] +pub enum DBError { + InvalidParams, + Merkle(MerkleError), + Blob(crate::account::BlobError), + System(nix::Error), +} + +/// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the +/// correct parameters are used when the DB is opened later (the parameters here will override the +/// parameters in [DBConfig] if the DB already exists). +#[repr(C)] +struct DBParams { + magic: [u8; 16], + meta_file_nbit: u64, + payload_file_nbit: u64, + payload_regn_nbit: u64, + wal_file_nbit: u64, + wal_block_nbit: u64, +} + +/// Config for accessing a version of the DB. +#[derive(TypedBuilder, Clone)] +pub struct DBRevConfig { + /// Maximum cached MPT objects. + #[builder(default = 1 << 20)] + merkle_ncached_objs: usize, + /// Maximum cached Blob (currently just `Account`) objects. + #[builder(default = 4096)] + blob_ncached_objs: usize, +} + +/// Database configuration. +#[derive(TypedBuilder)] +pub struct DBConfig { + /// Maximum cached pages for the free list of the item stash. + #[builder(default = 16384)] // 64M total size by default + meta_ncached_pages: usize, + /// Maximum cached file descriptors for the free list of the item stash. + #[builder(default = 1024)] // 1K fds by default + meta_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + meta_file_nbit: u64, + /// Maximum cached pages for the item stash. This is the low-level cache used by the linear + /// space that holds MPT nodes and account objects. + #[builder(default = 262144)] // 1G total size by default + payload_ncached_pages: usize, + /// Maximum cached file descriptors for the item stash. + #[builder(default = 1024)] // 1K fds by default + payload_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + payload_file_nbit: u64, + /// Maximum steps of walk to recycle a freed item. + #[builder(default = 10)] + payload_max_walk: u64, + /// Region size in bits (should be not greater than `payload_file_nbit`). One file is + /// partitioned into multiple regions. Just use the default value. + #[builder(default = 22)] + payload_regn_nbit: u64, + /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its + /// existing contents will be lost. + #[builder(default = false)] + truncate: bool, + /// Config for accessing a version of the DB. + #[builder(default = DBRevConfig::builder().build())] + rev: DBRevConfig, + /// Config for the disk buffer. + #[builder(default = DiskBufferConfig::builder().build())] + buffer: DiskBufferConfig, + /// Config for WAL. + #[builder(default = WALConfig::builder().build())] + wal: WALConfig, +} + +/// Necessary linear space instances bundled for a `CompactSpace`. +struct SubUniverse { + meta: T, + payload: T, +} + +impl SubUniverse { + fn new(meta: T, payload: T) -> Self { + Self { meta, payload } + } +} + +impl SubUniverse { + fn to_mem_store_r(&self) -> SubUniverse> { + SubUniverse { + meta: self.meta.inner().clone(), + payload: self.payload.inner().clone(), + } + } +} + +impl SubUniverse> { + fn rewind(&self, meta_writes: &[SpaceWrite], payload_writes: &[SpaceWrite]) -> SubUniverse { + SubUniverse::new( + StoreRevShared::from_ash(self.meta.clone(), meta_writes), + StoreRevShared::from_ash(self.payload.clone(), payload_writes), + ) + } +} + +impl SubUniverse> { + fn to_mem_store_r(&self) -> SubUniverse> { + SubUniverse { + meta: self.meta.clone(), + payload: self.payload.clone(), + } + } +} + +/// DB-wide metadata, it keeps track of the roots of the top-level tries. +struct DBHeader { + /// The root node of the account model storage. (Where the values are [Account] objects, which + /// may contain the root for the secondary trie.) + acc_root: ObjPtr, + /// The root node of the generic key-value store. + kv_root: ObjPtr, +} + +impl DBHeader { + pub const MSIZE: u64 = 16; + + pub fn new_empty() -> Self { + Self { + acc_root: ObjPtr::null(), + kv_root: ObjPtr::null(), + } + } +} + +impl MummyItem for DBHeader { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(shale::ShaleError::LinearMemStoreError)?; + let acc_root = u64::from_le_bytes(raw[..8].try_into().unwrap()); + let kv_root = u64::from_le_bytes(raw[8..].try_into().unwrap()); + unsafe { + Ok(Self { + acc_root: ObjPtr::new_from_addr(acc_root), + kv_root: ObjPtr::new_from_addr(kv_root), + }) + } + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = Cursor::new(to); + cur.write_all(&self.acc_root.addr().to_le_bytes()).unwrap(); + cur.write_all(&self.kv_root.addr().to_le_bytes()).unwrap(); + } +} + +/// Necessary linear space instances bundled for the state of the entire DB. +struct Universe { + merkle: SubUniverse, + blob: SubUniverse, +} + +impl Universe { + fn to_mem_store_r(&self) -> Universe> { + Universe { + merkle: self.merkle.to_mem_store_r(), + blob: self.blob.to_mem_store_r(), + } + } +} + +impl Universe> { + fn to_mem_store_r(&self) -> Universe> { + Universe { + merkle: self.merkle.to_mem_store_r(), + blob: self.blob.to_mem_store_r(), + } + } +} + +impl Universe> { + fn rewind( + &self, merkle_meta_writes: &[SpaceWrite], merkle_payload_writes: &[SpaceWrite], + blob_meta_writes: &[SpaceWrite], blob_payload_writes: &[SpaceWrite], + ) -> Universe { + Universe { + merkle: self.merkle.rewind(merkle_meta_writes, merkle_payload_writes), + blob: self.blob.rewind(blob_meta_writes, blob_payload_writes), + } + } +} + +/// Some readable version of the DB. +pub struct DBRev { + header: shale::Obj, + merkle: Merkle, + blob: BlobStash, +} + +impl DBRev { + fn flush_dirty(&mut self) -> Option<()> { + self.header.flush_dirty(); + self.merkle.flush_dirty()?; + self.blob.flush_dirty() + } + + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { + (&mut self.header, &mut self.merkle, &mut self.blob) + } + + /// Get root hash of the generic key-value storage. + pub fn kv_root_hash(&self) -> Result { + self.merkle + .root_hash::(self.header.kv_root) + .map_err(DBError::Merkle) + } + + /// Dump the MPT of the generic key-value storage. + pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + self.merkle.dump(self.header.kv_root, w).map_err(DBError::Merkle) + } + + /// Get root hash of the world state of all accounts. + pub fn root_hash(&self) -> Result { + self.merkle + .root_hash::(self.header.acc_root) + .map_err(DBError::Merkle) + } + + /// Dump the MPT of the entire account model storage. + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + self.merkle.dump(self.header.acc_root, w).map_err(DBError::Merkle) + } + + fn get_account(&self, key: &[u8]) -> Result { + Ok(match self.merkle.get(key, self.header.acc_root) { + Ok(Some(bytes)) => Account::deserialize(&bytes), + Ok(None) => Account::default(), + Err(e) => return Err(DBError::Merkle(e)), + }) + } + + /// Dump the MPT of the state storage under an account. + pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> { + let acc = match self.merkle.get(key, self.header.acc_root) { + Ok(Some(bytes)) => Account::deserialize(&bytes), + Ok(None) => Account::default(), + Err(e) => return Err(DBError::Merkle(e)), + }; + writeln!(w, "{:?}", acc).unwrap(); + if !acc.root.is_null() { + self.merkle.dump(acc.root, w).map_err(DBError::Merkle)?; + } + Ok(()) + } + + /// Get balance of the account. + pub fn get_balance(&self, key: &[u8]) -> Result { + Ok(self.get_account(key)?.balance) + } + + /// Get code of the account. + pub fn get_code(&self, key: &[u8]) -> Result, DBError> { + let code = self.get_account(key)?.code; + if code.is_null() { + return Ok(Vec::new()) + } + let b = self.blob.get_blob(code).map_err(DBError::Blob)?; + Ok(match &**b { + Blob::Code(code) => code.clone(), + }) + } + + /// Get nonce of the account. + pub fn get_nonce(&self, key: &[u8]) -> Result { + Ok(self.get_account(key)?.nonce) + } + + /// Get the state value indexed by `sub_key` in the account indexed by `key`. + pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result, DBError> { + let root = self.get_account(key)?.root; + if root.is_null() { + return Ok(Vec::new()) + } + Ok(match self.merkle.get(sub_key, root) { + Ok(Some(v)) => v.to_vec(), + Ok(None) => Vec::new(), + Err(e) => return Err(DBError::Merkle(e)), + }) + } + + /// Check if the account exists. + pub fn exist(&self, key: &[u8]) -> Result { + Ok(match self.merkle.get(key, self.header.acc_root) { + Ok(r) => r.is_some(), + Err(e) => return Err(DBError::Merkle(e)), + }) + } +} + +struct DBInner { + latest: DBRev, + disk_requester: crate::storage::DiskBufferRequester, + disk_thread: Option>, + staging: Universe>, + cached: Universe>, + revisions: VecDeque>, + max_revisions: usize, +} + +impl Drop for DBInner { + fn drop(&mut self) { + self.disk_requester.shutdown(); + self.disk_thread.take().map(JoinHandle::join); + } +} + +/// Firewood database handle. +pub struct DB { + inner: Mutex, + payload_regn_nbit: u64, + rev_cfg: DBRevConfig, +} + +impl DB { + /// Open a database. + pub fn new(db_path: &str, cfg: &DBConfig) -> Result { + // TODO: make sure all fds are released at the end + if cfg.truncate { + let _ = std::fs::remove_dir_all(db_path); + } + let (db_fd, reset) = file::open_dir(db_path, cfg.truncate).map_err(DBError::System)?; + + let merkle_fd = file::touch_dir("merkle", db_fd).map_err(DBError::System)?; + let merkle_meta_fd = file::touch_dir("meta", merkle_fd).map_err(DBError::System)?; + let merkle_payload_fd = file::touch_dir("compact", merkle_fd).map_err(DBError::System)?; + + let blob_fd = file::touch_dir("blob", db_fd).map_err(DBError::System)?; + let blob_meta_fd = file::touch_dir("meta", blob_fd).map_err(DBError::System)?; + let blob_payload_fd = file::touch_dir("compact", blob_fd).map_err(DBError::System)?; + + let file0 = crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?; + let fd0 = file0.get_fd(); + + if reset { + // initialize DBParams + if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT { + return Err(DBError::InvalidParams) + } + nix::unistd::ftruncate(fd0, 0).map_err(DBError::System)?; + nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DBError::System)?; + let mut magic = [0; 16]; + magic[..MAGIC_STR.len()].copy_from_slice(MAGIC_STR); + let header = DBParams { + magic, + meta_file_nbit: cfg.meta_file_nbit, + payload_file_nbit: cfg.payload_file_nbit, + payload_regn_nbit: cfg.payload_regn_nbit, + wal_file_nbit: cfg.wal.file_nbit, + wal_block_nbit: cfg.wal.block_nbit, + }; + nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0).map_err(DBError::System)?; + } + + // read DBParams + let mut header_bytes = [0; std::mem::size_of::()]; + nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DBError::System)?; + drop(file0); + let mut offset = header_bytes.len() as u64; + let header = unsafe { std::mem::transmute::<_, DBParams>(header_bytes) }; + + // setup disk buffer + let cached = Universe { + merkle: SubUniverse::new( + Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.meta_ncached_pages) + .ncached_files(cfg.meta_ncached_files) + .space_id(MERKLE_META_SPACE) + .file_nbit(header.meta_file_nbit) + .rootfd(merkle_meta_fd) + .build(), + ) + .unwrap(), + ), + Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.payload_ncached_pages) + .ncached_files(cfg.payload_ncached_files) + .space_id(MERKLE_PAYLOAD_SPACE) + .file_nbit(header.payload_file_nbit) + .rootfd(merkle_payload_fd) + .build(), + ) + .unwrap(), + ), + ), + blob: SubUniverse::new( + Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.meta_ncached_pages) + .ncached_files(cfg.meta_ncached_files) + .space_id(BLOB_META_SPACE) + .file_nbit(header.meta_file_nbit) + .rootfd(blob_meta_fd) + .build(), + ) + .unwrap(), + ), + Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.payload_ncached_pages) + .ncached_files(cfg.payload_ncached_files) + .space_id(BLOB_PAYLOAD_SPACE) + .file_nbit(header.payload_file_nbit) + .rootfd(blob_payload_fd) + .build(), + ) + .unwrap(), + ), + ), + }; + + let wal = WALConfig::builder() + .file_nbit(header.wal_file_nbit) + .block_nbit(header.wal_block_nbit) + .max_revisions(cfg.wal.max_revisions) + .build(); + let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); + let disk_requester = crate::storage::DiskBufferRequester::new(sender); + let buffer = cfg.buffer.clone(); + let disk_thread = Some(std::thread::spawn(move || { + let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); + disk_buffer.run() + })); + + disk_requester.reg_cached_space(cached.merkle.meta.as_ref()); + disk_requester.reg_cached_space(cached.merkle.payload.as_ref()); + disk_requester.reg_cached_space(cached.blob.meta.as_ref()); + disk_requester.reg_cached_space(cached.blob.payload.as_ref()); + + let staging = Universe { + merkle: SubUniverse::new( + Rc::new(StoreRevMut::new(cached.merkle.meta.clone() as Rc)), + Rc::new(StoreRevMut::new(cached.merkle.payload.clone() as Rc)), + ), + blob: SubUniverse::new( + Rc::new(StoreRevMut::new(cached.blob.meta.clone() as Rc)), + Rc::new(StoreRevMut::new(cached.blob.payload.clone() as Rc)), + ), + }; + + // recover from WAL + disk_requester.init_wal("wal", db_fd); + + // set up the storage layout + let db_header: ObjPtr; + let merkle_payload_header: ObjPtr; + let blob_payload_header: ObjPtr; + unsafe { + db_header = ObjPtr::new_from_addr(offset); + offset += DBHeader::MSIZE; + merkle_payload_header = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + blob_payload_header = ObjPtr::new_from_addr(0); + } + + if reset { + // initialize space headers + staging.merkle.meta.write( + merkle_payload_header.addr(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)), + ); + staging + .merkle + .meta + .write(db_header.addr(), &shale::to_dehydrated(&DBHeader::new_empty())); + staging.blob.meta.write( + blob_payload_header.addr(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)), + ); + } + + let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe { + let merkle_meta_ref = staging.merkle.meta.as_ref() as &dyn MemStore; + let blob_meta_ref = staging.blob.meta.as_ref() as &dyn MemStore; + + ( + MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + MummyObj::ptr_to_obj( + merkle_meta_ref, + merkle_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), + MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(), + ) + }; + + let merkle_space = shale::compact::CompactSpace::new( + staging.merkle.meta.clone(), + staging.merkle.payload.clone(), + merkle_payload_header_ref, + shale::ObjCache::new(cfg.rev.merkle_ncached_objs), + cfg.payload_max_walk, + header.payload_regn_nbit, + ) + .unwrap(); + + let blob_space = shale::compact::CompactSpace::new( + staging.blob.meta.clone(), + staging.blob.payload.clone(), + blob_payload_header_ref, + shale::ObjCache::new(cfg.rev.blob_ncached_objs), + cfg.payload_max_walk, + header.payload_regn_nbit, + ) + .unwrap(); + + if db_header_ref.acc_root.is_null() { + let mut err = Ok(()); + // create the sentinel node + db_header_ref + .write(|r| { + err = (|| { + Merkle::init_root(&mut r.acc_root, &merkle_space)?; + Merkle::init_root(&mut r.kv_root, &merkle_space) + })(); + }) + .unwrap(); + err.map_err(DBError::Merkle)? + } + + let mut latest = DBRev { + header: db_header_ref, + merkle: Merkle::new(Box::new(merkle_space)), + blob: BlobStash::new(Box::new(blob_space)), + }; + latest.flush_dirty().unwrap(); + + Ok(Self { + inner: Mutex::new(DBInner { + latest, + disk_thread, + disk_requester, + staging, + cached, + revisions: VecDeque::new(), + max_revisions: cfg.wal.max_revisions as usize, + }), + payload_regn_nbit: header.payload_regn_nbit, + rev_cfg: cfg.rev.clone(), + }) + } + + /// Create a write batch. + pub fn new_writebatch(&self) -> WriteBatch { + WriteBatch { + m: self.inner.lock(), + root_hash_recalc: true, + committed: false, + } + } + + /// Dump the MPT of the latest generic key-value storage. + pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + self.inner.lock().latest.kv_dump(w) + } + + /// Dump the MPT of the latest entire account model storage. + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + self.inner.lock().latest.dump(w) + } + + /// Dump the MPT of the latest state storage under an account. + pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> { + self.inner.lock().latest.dump_account(key, w) + } + + /// Get root hash of the latest generic key-value storage. + pub fn kv_root_hash(&self) -> Result { + self.inner.lock().latest.kv_root_hash() + } + + /// Get root hash of the latest world state of all accounts. + pub fn root_hash(&self) -> Result { + self.inner.lock().latest.root_hash() + } + + /// Get the latest balance of the account. + pub fn get_balance(&self, key: &[u8]) -> Result { + self.inner.lock().latest.get_balance(key) + } + + /// Get the latest code of the account. + pub fn get_code(&self, key: &[u8]) -> Result, DBError> { + self.inner.lock().latest.get_code(key) + } + + /// Get the latest nonce of the account. + pub fn get_nonce(&self, key: &[u8]) -> Result { + self.inner.lock().latest.get_nonce(key) + } + + /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. + pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result, DBError> { + self.inner.lock().latest.get_state(key, sub_key) + } + + /// Check if the account exists in the latest world state. + pub fn exist(&self, key: &[u8]) -> Result { + self.inner.lock().latest.exist(key) + } + + /// Get a handle that grants the access to some historical state of the entire DB. + pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { + let mut inner = self.inner.lock(); + + let rlen = inner.revisions.len(); + if nback == 0 || nback > inner.max_revisions { + return None + } + if rlen < nback { + let ashes = inner.disk_requester.collect_ash(nback); + for mut ash in ashes.into_iter().skip(rlen) { + for (_, a) in ash.0.iter_mut() { + a.old.reverse() + } + + let u = match inner.revisions.back() { + Some(u) => u.to_mem_store_r(), + None => inner.cached.to_mem_store_r(), + }; + inner.revisions.push_back(u.rewind( + &ash.0[&MERKLE_META_SPACE].old, + &ash.0[&MERKLE_PAYLOAD_SPACE].old, + &ash.0[&BLOB_META_SPACE].old, + &ash.0[&BLOB_PAYLOAD_SPACE].old, + )); + } + } + if inner.revisions.len() < nback { + return None + } + // set up the storage layout + let db_header: ObjPtr; + let merkle_payload_header: ObjPtr; + let blob_payload_header: ObjPtr; + unsafe { + let mut offset = std::mem::size_of::() as u64; + // DBHeader starts after DBParams in merkle meta space + db_header = ObjPtr::new_from_addr(offset); + offset += DBHeader::MSIZE; + // Merkle CompactHeader starts after DBHeader in merkle meta space + merkle_payload_header = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + // Blob CompactSpaceHeader starts right in blob meta space + blob_payload_header = ObjPtr::new_from_addr(0); + } + + let space = &inner.revisions[nback - 1]; + + let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe { + let merkle_meta_ref = &space.merkle.meta as &dyn MemStore; + let blob_meta_ref = &space.blob.meta as &dyn MemStore; + + ( + MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + MummyObj::ptr_to_obj( + merkle_meta_ref, + merkle_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), + MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(), + ) + }; + + let merkle_space = shale::compact::CompactSpace::new( + Rc::new(space.merkle.meta.clone()), + Rc::new(space.merkle.payload.clone()), + merkle_payload_header_ref, + shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).merkle_ncached_objs), + 0, + self.payload_regn_nbit, + ) + .unwrap(); + + let blob_space = shale::compact::CompactSpace::new( + Rc::new(space.blob.meta.clone()), + Rc::new(space.blob.payload.clone()), + blob_payload_header_ref, + shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).blob_ncached_objs), + 0, + self.payload_regn_nbit, + ) + .unwrap(); + + Some(Revision { + _m: inner, + rev: DBRev { + header: db_header_ref, + merkle: Merkle::new(Box::new(merkle_space)), + blob: BlobStash::new(Box::new(blob_space)), + }, + }) + } +} + +/// Lock protected handle to a readable version of the DB. +pub struct Revision<'a> { + _m: MutexGuard<'a, DBInner>, + rev: DBRev, +} + +impl<'a> std::ops::Deref for Revision<'a> { + type Target = DBRev; + fn deref(&self) -> &DBRev { + &self.rev + } +} + +/// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself +/// because when an error occurs, the write batch will be automaticlaly aborted so that the DB +/// remains clean. +pub struct WriteBatch<'a> { + m: MutexGuard<'a, DBInner>, + root_hash_recalc: bool, + committed: bool, +} + +impl<'a> WriteBatch<'a> { + /// Insert an item to the generic key-value storage. + pub fn kv_insert>(mut self, key: K, val: Vec) -> Result { + let (header, merkle, _) = self.m.latest.borrow_split(); + merkle.insert(key, val, header.kv_root).map_err(DBError::Merkle)?; + Ok(self) + } + + /// Remove an item from the generic key-value storage. `val` will be set to the value that is + /// removed from the storage if it exists. + pub fn kv_remove>(mut self, key: K, val: &mut Option>) -> Result { + let (header, merkle, _) = self.m.latest.borrow_split(); + *val = merkle.remove(key, header.kv_root).map_err(DBError::Merkle)?; + Ok(self) + } + + fn change_account( + &mut self, key: &[u8], modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>, + ) -> Result<(), DBError> { + let (header, merkle, blob) = self.m.latest.borrow_split(); + match merkle.get_mut(key, header.acc_root) { + Ok(Some(mut bytes)) => { + let mut ret = Ok(()); + bytes + .write(|b| { + let mut acc = Account::deserialize(b); + ret = modify(&mut acc, blob); + if ret.is_err() { + return + } + *b = acc.serialize(); + }) + .map_err(DBError::Merkle)?; + ret?; + } + Ok(None) => { + let mut acc = Account::default(); + modify(&mut acc, blob)?; + merkle + .insert(key, acc.serialize(), header.acc_root) + .map_err(DBError::Merkle)?; + } + Err(e) => return Err(DBError::Merkle(e)), + } + Ok(()) + } + + /// Set balance of the account. + pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result { + self.change_account(key, |acc, _| { + acc.balance = balance; + Ok(()) + })?; + Ok(self) + } + + /// Set code of the account. + pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result { + use sha3::Digest; + self.change_account(key, |acc, blob_stash| { + if !acc.code.is_null() { + blob_stash.free_blob(acc.code).map_err(DBError::Blob)?; + } + Ok(acc.set_code( + Hash(sha3::Keccak256::digest(code).into()), + blob_stash + .new_blob(Blob::Code(code.to_vec())) + .map_err(DBError::Blob)? + .as_ptr(), + )) + })?; + Ok(self) + } + + /// Set nonce of the account. + pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result { + self.change_account(key, |acc, _| Ok(acc.nonce = nonce))?; + Ok(self) + } + + /// Set the state value indexed by `sub_key` in the account indexed by `key`. + pub fn set_state(mut self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { + let (header, merkle, _) = self.m.latest.borrow_split(); + let mut acc = match merkle.get(key, header.acc_root) { + Ok(Some(r)) => Account::deserialize(&*r), + Ok(None) => Account::default(), + Err(e) => return Err(DBError::Merkle(e)), + }; + if acc.root.is_null() { + Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DBError::Merkle)?; + } + merkle.insert(sub_key, val, acc.root).map_err(DBError::Merkle)?; + acc.root_hash = merkle.root_hash::(acc.root).map_err(DBError::Merkle)?; + merkle + .insert(key, acc.serialize(), header.acc_root) + .map_err(DBError::Merkle)?; + Ok(self) + } + + /// Create an account. + pub fn create_account(mut self, key: &[u8]) -> Result { + let (header, merkle, _) = self.m.latest.borrow_split(); + let old_balance = match merkle.get_mut(key, header.acc_root) { + Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance, + Ok(None) => U256::zero(), + Err(e) => return Err(DBError::Merkle(e)), + }; + let acc = Account { + balance: old_balance, + ..Default::default() + }; + merkle + .insert(key, acc.serialize(), header.acc_root) + .map_err(DBError::Merkle)?; + Ok(self) + } + + /// Delete an account. + pub fn delete_account(mut self, key: &[u8], acc: &mut Option) -> Result { + let (header, merkle, blob_stash) = self.m.latest.borrow_split(); + let mut a = match merkle.remove(key, header.acc_root) { + Ok(Some(bytes)) => Account::deserialize(&bytes), + Ok(None) => { + *acc = None; + return Ok(self) + } + Err(e) => return Err(DBError::Merkle(e)), + }; + if !a.root.is_null() { + merkle.remove_tree(a.root).map_err(DBError::Merkle)?; + a.root = ObjPtr::null(); + } + if !a.code.is_null() { + blob_stash.free_blob(a.code).map_err(DBError::Blob)?; + a.code = ObjPtr::null(); + } + *acc = Some(a); + Ok(self) + } + + /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root + /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. + pub fn no_root_hash(mut self) -> Self { + self.root_hash_recalc = false; + self + } + + /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are + /// either retained on disk or lost together during a crash. + pub fn commit(mut self) { + use crate::storage::BufferWrite; + let inner = &mut *self.m; + if self.root_hash_recalc { + inner.latest.root_hash().ok(); + inner.latest.kv_root_hash().ok(); + } + // clear the staging layer and apply changes to the CachedSpace + inner.latest.flush_dirty().unwrap(); + let (merkle_payload_pages, merkle_payload_plain) = inner.staging.merkle.payload.take_delta(); + let (merkle_meta_pages, merkle_meta_plain) = inner.staging.merkle.meta.take_delta(); + let (blob_payload_pages, blob_payload_plain) = inner.staging.blob.payload.take_delta(); + let (blob_meta_pages, blob_meta_plain) = inner.staging.blob.meta.take_delta(); + + let old_merkle_meta_delta = inner.cached.merkle.meta.update(&merkle_meta_pages).unwrap(); + let old_merkle_payload_delta = inner.cached.merkle.payload.update(&merkle_payload_pages).unwrap(); + let old_blob_meta_delta = inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); + let old_blob_payload_delta = inner.cached.blob.payload.update(&blob_payload_pages).unwrap(); + + // update the rolling window of past revisions + let new_base = Universe { + merkle: SubUniverse::new( + StoreRevShared::from_delta(inner.cached.merkle.meta.clone(), old_merkle_meta_delta), + StoreRevShared::from_delta(inner.cached.merkle.payload.clone(), old_merkle_payload_delta), + ), + blob: SubUniverse::new( + StoreRevShared::from_delta(inner.cached.blob.meta.clone(), old_blob_meta_delta), + StoreRevShared::from_delta(inner.cached.blob.payload.clone(), old_blob_payload_delta), + ), + }; + + if let Some(rev) = inner.revisions.front_mut() { + rev.merkle.meta.set_prev(new_base.merkle.meta.inner().clone()); + rev.merkle.payload.set_prev(new_base.merkle.payload.inner().clone()); + rev.blob.meta.set_prev(new_base.blob.meta.inner().clone()); + rev.blob.payload.set_prev(new_base.blob.payload.inner().clone()); + } + inner.revisions.push_front(new_base); + while inner.revisions.len() > inner.max_revisions { + inner.revisions.pop_back(); + } + + self.committed = true; + + // schedule writes to the disk + inner.disk_requester.write( + vec![ + BufferWrite { + space_id: inner.staging.merkle.payload.id(), + delta: merkle_payload_pages, + }, + BufferWrite { + space_id: inner.staging.merkle.meta.id(), + delta: merkle_meta_pages, + }, + BufferWrite { + space_id: inner.staging.blob.payload.id(), + delta: blob_payload_pages, + }, + BufferWrite { + space_id: inner.staging.blob.meta.id(), + delta: blob_meta_pages, + }, + ], + crate::storage::AshRecord( + [ + (MERKLE_META_SPACE, merkle_meta_plain), + (MERKLE_PAYLOAD_SPACE, merkle_payload_plain), + (BLOB_META_SPACE, blob_meta_plain), + (BLOB_PAYLOAD_SPACE, blob_payload_plain), + ] + .into(), + ), + ); + } +} + +impl<'a> Drop for WriteBatch<'a> { + fn drop(&mut self) { + if !self.committed { + // drop the staging changes + self.m.staging.merkle.payload.take_delta(); + self.m.staging.merkle.meta.take_delta(); + self.m.staging.blob.payload.take_delta(); + self.m.staging.blob.meta.take_delta(); + } + } +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 000000000000..7d0498171d57 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,112 @@ +// Copied from CedrusDB + +#![allow(dead_code)] + +pub(crate) use std::os::unix::io::RawFd as Fd; + +use nix::errno::Errno; +use nix::fcntl::{open, openat, OFlag}; +use nix::sys::stat::Mode; +use nix::unistd::{close, fsync, mkdir}; + +pub struct File { + fd: Fd, + fid: u64, +} + +impl File { + pub fn open_file(rootfd: Fd, fname: &str, truncate: bool) -> nix::Result { + openat( + rootfd, + fname, + (if truncate { OFlag::O_TRUNC } else { OFlag::empty() }) | OFlag::O_RDWR, + Mode::S_IRUSR | Mode::S_IWUSR, + ) + } + + pub fn create_file(rootfd: Fd, fname: &str) -> Fd { + openat( + rootfd, + fname, + OFlag::O_CREAT | OFlag::O_RDWR, + Mode::S_IRUSR | Mode::S_IWUSR, + ) + .unwrap() + } + + fn _get_fname(fid: u64) -> String { + format!("{:08x}.fw", fid) + } + + pub fn new(fid: u64, flen: u64, rootfd: Fd) -> nix::Result { + let fname = Self::_get_fname(fid); + let fd = match Self::open_file(rootfd, &fname, false) { + Ok(fd) => fd, + Err(e) => match e { + Errno::ENOENT => { + let fd = Self::create_file(rootfd, &fname); + nix::unistd::ftruncate(fd, flen as nix::libc::off_t)?; + fd + } + e => return Err(e), + }, + }; + Ok(File { fd, fid }) + } + + pub fn get_fd(&self) -> Fd { + self.fd + } + pub fn get_fid(&self) -> u64 { + self.fid + } + pub fn get_fname(&self) -> String { + Self::_get_fname(self.fid) + } + + pub fn sync(&self) { + fsync(self.fd).unwrap(); + } +} + +impl Drop for File { + fn drop(&mut self) { + close(self.fd).unwrap(); + } +} + +pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result { + use nix::sys::stat::mkdirat; + if mkdirat(rootfd, dirname, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR).is_err() { + let errno = nix::errno::from_i32(nix::errno::errno()); + if errno != nix::errno::Errno::EEXIST { + return Err(errno) + } + } + openat(rootfd, dirname, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) +} + +pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { + let mut reset_header = truncate; + if truncate { + let _ = std::fs::remove_dir_all(path); + } + match mkdir(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { + Err(e) => { + if truncate { + return Err(e) + } + } + Ok(_) => { + // the DB did not exist + reset_header = true + } + } + Ok(( + match open(path, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) { + Ok(fd) => fd, + Err(e) => return Err(e), + }, + reset_header, + )) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000000..c8761fba29a5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,202 @@ +//! # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. +//! +//! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes +//! access to latest state, by providing extremely fast reads, but also provides a limited view +//! into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever +//! growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk +//! and apply in-place updates to it. This ensures that the database size is small and stable +//! during the course of running firewood. Firewood was first conceived to provide a very fast +//! storage layer for qEVM to enable a fast, complete EVM system with right design choices made +//! totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible +//! blockchain storage system, and fits for the general use of a certified key-value store of +//! arbitrary data. +//! +//! Firewood is a robust database implemented from the ground up to directly store MPT nodes and +//! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a +//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses +//! the tree structure as the index on disk. Thus, there is no additional "emulation" of the +//! logical MPT to flatten out the data structure to feed into the underlying DB that is unaware +//! of the data being stored. +//! +//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees +//! atomicity and durability in the database, but also offers "reversibility": some portion +//! of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover +//! some past versions of the entire store back in memory. While running the store, new changes +//! will also contribute to the configured window of changes (at batch granularity) to access any past +//! versions with no additional cost at all. +//! +//! The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage +//! space which can be both or selectively used the user. The account model portion of the storage +//! offers something very similar to `StateDB` in geth, which captures the address-"state key" +//! style of two-level access for an account's (smart contract's) state. Therefore, it takes +//! minimal effort to delegate all state storage from an EVM implementation to firewood. The other +//! portion of the storage supports generic MPT storage for arbitrary keys and values. When unused, +//! there is no additional cost. +//! +//! # Design Philosophy & Overview +//! +//! With some on-going academic research efforts and increasing demand of faster local storage +//! solutions for the chain state, we realized there are mainly two different regimes of designs. +//! +//! - "Archival" Storage: this style of design emphasizes on the ability to hold all historical +//! data and retrieve a revision of any wold state at a reasonable performance. To economically +//! store all historical certified data, usually copy-on-write merkle tries are used to just +//! capture the changes made by a committed block. The entire storage consists of a forest of these +//! "delta" tries. The total size of the storage will keep growing over the chain length and an ideal, +//! well-executed plan for this is to make sure the performance degradation is reasonable or +//! well-contained with respect to the ever-increasing size of the index. This design is useful +//! for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a +//! query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also +//! need this because the "canonical" branch of the chain could switch (but not necessarily a +//! practical concern nowadays) to a different fork at times. +//! +//! - "Validation" Storage: this regime optimizes for the storage footprint and the performance of +//! operations upon the latest/recent states. With the assumption that the chain's total state +//! size is relatively stable over ever-coming blocks, one can just make the latest state +//! persisted and available to the blockchain system as that's what matters for most of the time. +//! While one can still keep some volatile state versions in memory for mutation and VM +//! execution, the final commit to some state works on a singleton so the indexed merkle tries +//! may be typically updated in place. It is also possible (e.g., firewood) to allow some +//! infrequent access to historical versions with higher cost, and/or allow fast access to +//! versions of the store within certain limited recency. This style of storage is useful for +//! the blockchain systems where only (or mostly) the latest state is required and data footprint +//! should remain constant or grow slowly if possible for sustainability. Validators who +//! directly participate in the consensus and vote for the blocks, for example, can largely +//! benefit from such a design. +//! +//! In firewood, we take a closer look at the second regime and have come up with a simple but +//! robust architecture that fulfills the need for such blockchain storage. +//! +//! ## Storage Model +//! +//! Firewood is built by three layers of abstractions that totally decouple the +//! layout/representation of the data on disk from the actual logical data structure it retains: +//! +//! - Linear, memory-like space: the [shale](https://crates.io/crates/shale) crate from an academic +//! project (CedrusDB) code offers a `MemStore` abstraction for a (64-bit) byte-addressable space +//! that abstracts away the intricate method that actually persists the in-memory data on the +//! secondary storage medium (e.g., hard drive). The implementor of `MemStore` will provide the +//! functions to give the user of `MemStore` an illusion that the user is operating upon a +//! byte-addressable memory space. It is just a "magical" array of bytes one can view and change +//! that is mirrored to the disk. In reality, the linear space will be chunked into files under a +//! directory, but the user does not have to even know about this. +//! +//! - Persistent item storage stash: `ShaleStore` trait from `shale` defines a pool of typed +//! objects that are persisted on disk but also made accessible in memory transparently. It is +//! built on top of `MemStore` by defining how "items" of the given type are laid out, allocated +//! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of +//! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). +//! +//! - Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking +//! `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`). +//! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or +//! persisted on disk. It is as if they're just in memory, which makes it much easier to write +//! and maintain the code. +//! +//! The three layers are depicted as follows: +//! +//!

+//! +//!

+//! +//! Given the abstraction, one can easily realize the fact that the actual data that affect the +//! state of the data structure (MPT) is what the linear space (`MemStore`) keeps track of, that is, +//! a flat but conceptually large byte vector. In other words, given a valid byte vector as the +//! content of the linear space, the higher level data structure can be *uniquely* determined, there +//! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) +//! or less than that, like a way to interpret the bytes. This nice property allows us to completely +//! separate the logical data from its physical representation, greatly simplifies the storage +//! management, and allows reusing the code. It is still a very versatile abstraction, as in theory +//! any persistent data could be stored this way -- sometimes you need to swap in a different +//! `MemShale` or `MemStore` implementation, but without having to touch the code for the persisted +//! data structure. +//! +//! ## Page-based Shadowing and Revisions +//! +//! Following the idea that the MPTs are just a view of a linear byte space, all writes made to the +//! MPTs inside Firewood will eventually be consolidated into some interval writes to the linear +//! space. The writes may overlap and some frequent writes are even done to the same spot in the +//! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit +//! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the +//! dirty pages in some `MemStore` instantiation (see `storage::StoreRevMut`). When a +//! [`db::WriteBatch`] commits, both the recorded interval writes and the aggregated in-memory +//! dirty pages induced by this write batch are taken out from the linear space. Although they are +//! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, +//! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL +//! subsystem (supported by [growthring](https://crates.io/crates/growth-ring)). After the +//! WAL record is written (one record per write batch), the dirty pages are then pushed to the +//! on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See +//! the `BufferCmd::WriteBatch` part of `DiskBuffer::process` for the detailed logic. +//! +//! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: +//! +//! - Traverse the MPT, and that induces the access to some nodes. Suppose the nodes are not already in +//! memory, then: +//! +//! - Bring the necessary pages that contain the accessed nodes into the memory and cache them +//! (`storage::CachedSpace`). +//! +//! - Make changes to the MPT, and that induces the writes to some nodes. The nodes are either +//! already cached in memory (its pages are cached, or its handle `ObjRef` is still in +//! `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the +//! second step for it). +//! +//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` space that +//! overlays atop `CachedSpace`, so all dirty pages during the current write batch will be +//! exactly captured in `StoreRevMut` (see `StoreRevMut::take_delta`). +//! +//! - Finally: +//! +//! - Abort: when the write batch is dropped without invoking `db::WriteBatch::commit`, all in-memory +//! changes will be discarded, the dirty pages from `StoreRevMut` will be dropped and the merkle +//! will "revert" back to its original state without actually having to rollback anything. +//! +//! - Commit: otherwise, the write batch is committed, the interval writes (`storage::Ash`) will be bundled +//! into a single WAL record (`storage::AshRecord`) and sent to WAL subsystem, before dirty pages +//! are scheduled to be written to the space files. Also the dirty pages are applied to the +//! underlying `CachedSpace`. `StoreRevMut` becomes empty again for further write batches. +//! +//! Parts of the following diagram show this normal flow, the "staging" space (implemented by +//! `StoreRevMut`) concept is a bit similar to the staging area in Git, which enables the handling +//! of (resuming from) write errors, clean abortion of an on-going write batch so the entire store +//! state remains intact, and also reduces unnecessary premature disk writes. Essentially, we +//! copy-on-write pages in the space that are touched upon, without directly mutating the +//! underlying "master" space. The staging space is just a collection of these "shadowing" pages +//! and a reference to the its base (master) so any reads could partially hit those dirty pages +//! and/or fall through to the base, whereas all writes are captured. Finally, when things go well, +//! we "push down" these changes to the base and clear up the staging space. +//! +//!

+//! +//!

+//! +//! Thanks to the shadow pages, we can both revive some historical versions of the store and +//! maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram +//! shows previously logged write batch records could be kept even though they are no longer needed +//! for the purpose of crash recovery. The interval writes from a record can be aggregated into +//! pages (see `storage::StoreDelta::new`) and used to reconstruct a "ghost" image of past +//! revision of the linear space (just like how staging space works, except that the ghost space is +//! essentially read-only once constructed). The shadow pages there will function as some +//! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the +//! linear space is very likely untouched by that historical write batch. +//! +//! Then, with the three-layer abstraction we previously talked about, an historical MPT could be +//! derived. In fact, because there is no mandatory traversal or scanning in the process, the +//! only cost to revive a historical state from the log is to just playback the records and create +//! those shadow pages. There is very little additional cost because the ghost space is summoned on an +//! on-demand manner while one accesses the historical MPT. +//! +//! In the other direction, when new write batches are committed, the system moves forward, we can +//! therefore maintain a rolling window of past revisions in memory with *zero* cost. The +//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one +//! step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be +//! created to hold the version of the store before the commit. The backward delta is applied to +//! counteract the change that has been made to the persisted store, which is also a set of shadow pages. +//! No change is required for other historical ghost space instances. Finally, we can phase out +//! some very old ghost space to keep the size of the rolling window invariant. +//! +pub(crate) mod account; +pub mod db; +pub(crate) mod file; +pub mod merkle; +pub(crate) mod storage; diff --git a/src/merkle.rs b/src/merkle.rs new file mode 100644 index 000000000000..a90ea4dc78d6 --- /dev/null +++ b/src/merkle.rs @@ -0,0 +1,1611 @@ +use enum_as_inner::EnumAsInner; +use once_cell::unsync::OnceCell; +use sha3::Digest; +use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; + +use std::cell::Cell; +use std::fmt::{self, Debug}; +use std::io::{Cursor, Read, Write}; + +const NBRANCH: usize = 16; + +#[derive(Debug)] +pub enum MerkleError { + Shale(ShaleError), + ReadOnly, + NotBranchNode, + Format(std::io::Error), +} + +#[derive(PartialEq, Eq, Clone)] +pub struct Hash(pub [u8; 32]); + +impl Hash { + const MSIZE: u64 = 32; +} + +impl std::ops::Deref for Hash { + type Target = [u8; 32]; + fn deref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl MummyItem for Hash { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem.get_view(addr, Self::MSIZE).ok_or(ShaleError::LinearMemStoreError)?; + Ok(Self(raw[..Self::MSIZE as usize].try_into().unwrap())) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + Cursor::new(to).write_all(&self.0).unwrap() + } +} + +/// PartialPath keeps a list of nibbles to represent a path on the MPT. +#[derive(PartialEq, Eq, Clone)] +struct PartialPath(Vec); + +impl Debug for PartialPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for nib in self.0.iter() { + write!(f, "{:x}", *nib & 0xf)?; + } + Ok(()) + } +} + +impl std::ops::Deref for PartialPath { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl PartialPath { + fn into_inner(self) -> Vec { + self.0 + } + + fn encode(&self, term: bool) -> Vec { + let odd_len = (self.0.len() & 1) as u8; + let flags = if term { 2 } else { 0 } + odd_len; + let mut res = if odd_len == 1 { vec![flags] } else { vec![flags, 0x0] }; + res.extend(&self.0); + res + } + + fn decode>(raw: R) -> (Self, bool) { + let raw = raw.as_ref(); + let term = raw[0] > 1; + let odd_len = raw[0] & 1; + ( + Self(if odd_len == 1 { + raw[1..].to_vec() + } else { + raw[2..].to_vec() + }), + term, + ) + } + + fn dehydrated_len(&self) -> u64 { + let len = self.0.len() as u64; + if len & 1 == 1 { + (len + 1) >> 1 + } else { + (len >> 1) + 1 + } + } +} + +#[test] +fn test_partial_path_encoding() { + let check = |steps: &[u8], term| { + let (d, t) = PartialPath::decode(&PartialPath(steps.to_vec()).encode(term)); + assert_eq!(d.0, steps); + assert_eq!(t, term); + }; + for steps in [ + vec![0x1, 0x2, 0x3, 0x4], + vec![0x1, 0x2, 0x3], + vec![0x0, 0x1, 0x2], + vec![0x1, 0x2], + vec![0x1], + ] { + for term in [true, false] { + check(&steps, term) + } + } +} + +#[derive(PartialEq, Eq, Clone)] +struct Data(Vec); + +impl std::ops::Deref for Data { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(PartialEq, Eq, Clone)] +struct BranchNode { + chd: [Option>; NBRANCH], + value: Option, +} + +impl Debug for BranchNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Branch")?; + for (i, c) in self.chd.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({:x} {})", i, c)?; + } + } + write!( + f, + " v={}]", + match &self.value { + Some(v) => hex::encode(&**v), + None => "nil".to_string(), + } + ) + } +} + +impl BranchNode { + fn single_child(&self) -> (Option<(ObjPtr, u8)>, bool) { + let mut has_chd = false; + let mut only_chd = None; + for (i, c) in self.chd.iter().enumerate() { + if c.is_some() { + has_chd = true; + if only_chd.is_some() { + only_chd = None; + break + } + only_chd = (*c).map(|e| (e, i as u8)) + } + } + (only_chd, has_chd) + } + + fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + let mut stream = rlp::RlpStream::new_list(17); + for c in self.chd.iter() { + match c { + Some(c) => { + let mut c_ref = store.get_item(*c).unwrap(); + if c_ref.get_eth_rlp_long::(store) { + let s = stream.append(&&(*c_ref.get_root_hash::(store))[..]); + if c_ref.lazy_dirty.get() { + c_ref.write(|_| {}).unwrap(); + c_ref.lazy_dirty.set(false) + } + s + } else { + let c_rlp = &c_ref.get_eth_rlp::(store); + stream.append_raw(c_rlp, 1) + } + } + None => stream.append_empty_data(), + }; + } + match &self.value { + Some(val) => stream.append(&val.to_vec()), + None => stream.append_empty_data(), + }; + stream.out().into() + } +} + +#[derive(PartialEq, Eq, Clone)] +struct LeafNode(PartialPath, Data); + +impl Debug for LeafNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1)) + } +} + +impl LeafNode { + fn calc_eth_rlp(&self) -> Vec { + rlp::encode_list::, _>(&[from_nibbles(&self.0.encode(true)).collect(), T::transform(&self.1)]).into() + } +} + +#[derive(PartialEq, Eq, Clone)] +struct ExtNode(PartialPath, ObjPtr); + +impl Debug for ExtNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Extension {:?} {}]", self.0, self.1) + } +} + +impl ExtNode { + fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + let mut r = store.get_item(self.1).unwrap(); + let mut stream = rlp::RlpStream::new_list(2); + stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); + if r.get_eth_rlp_long::(store) { + stream.append(&&(*r.get_root_hash::(store))[..]); + if r.lazy_dirty.get() { + r.write(|_| {}).unwrap(); + r.lazy_dirty.set(false) + } + } else { + stream.append_raw(r.get_eth_rlp::(store), 1); + } + stream.out().into() + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct Node { + root_hash: OnceCell, + eth_rlp_long: OnceCell, + eth_rlp: OnceCell>, + lazy_dirty: Cell, + inner: NodeType, +} + +#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] +enum NodeType { + Branch(BranchNode), + Leaf(LeafNode), + Extension(ExtNode), +} + +impl NodeType { + fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + let eth_rlp = match &self { + NodeType::Leaf(n) => n.calc_eth_rlp::(), + NodeType::Extension(n) => n.calc_eth_rlp::(store), + NodeType::Branch(n) => n.calc_eth_rlp::(store), + }; + eth_rlp + } +} + +impl Node { + const BRANCH_NODE: u8 = 0x0; + const EXT_NODE: u8 = 0x1; + const LEAF_NODE: u8 = 0x2; + + fn max_branch_node_size() -> u64 { + const MAX_SIZE: OnceCell = OnceCell::new(); + *MAX_SIZE.get_or_init(|| { + Self { + root_hash: OnceCell::new(), + eth_rlp_long: OnceCell::new(), + eth_rlp: OnceCell::new(), + inner: NodeType::Branch(BranchNode { + chd: [Some(ObjPtr::null()); NBRANCH], + value: Some(Data(Vec::new())), + }), + lazy_dirty: Cell::new(false), + } + .dehydrated_len() + }) + } + + fn get_eth_rlp(&self, store: &dyn ShaleStore) -> &[u8] { + self.eth_rlp.get_or_init(|| self.inner.calc_eth_rlp::(store)) + } + + fn get_root_hash(&self, store: &dyn ShaleStore) -> &Hash { + self.root_hash.get_or_init(|| { + self.lazy_dirty.set(true); + Hash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) + }) + } + + fn get_eth_rlp_long(&self, store: &dyn ShaleStore) -> bool { + *self.eth_rlp_long.get_or_init(|| { + self.lazy_dirty.set(true); + self.get_eth_rlp::(store).len() >= 32 + }) + } + + fn rehash(&mut self) { + self.eth_rlp = OnceCell::new(); + self.eth_rlp_long = OnceCell::new(); + self.root_hash = OnceCell::new(); + } + + fn new(inner: NodeType) -> Self { + let mut s = Self { + root_hash: OnceCell::new(), + eth_rlp_long: OnceCell::new(), + eth_rlp: OnceCell::new(), + inner, + lazy_dirty: Cell::new(false), + }; + s.rehash(); + s + } + + fn new_from_hash(root_hash: Option, eth_rlp_long: Option, inner: NodeType) -> Self { + Self { + root_hash: match root_hash { + Some(h) => OnceCell::with_value(h), + None => OnceCell::new(), + }, + eth_rlp_long: match eth_rlp_long { + Some(b) => OnceCell::with_value(b), + None => OnceCell::new(), + }, + eth_rlp: OnceCell::new(), + inner, + lazy_dirty: Cell::new(false), + } + } + + const ROOT_HASH_VALID_BIT: u8 = 1 << 0; + const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1; + const ETH_RLP_LONG_BIT: u8 = 1 << 2; +} + +impl MummyItem for Node { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let dec_err = |_| ShaleError::DecodeError; + const META_SIZE: u64 = 32 + 1 + 1; + let meta_raw = mem.get_view(addr, META_SIZE).ok_or(ShaleError::LinearMemStoreError)?; + let attrs = meta_raw[32]; + let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { + None + } else { + Some(Hash(meta_raw[0..32].try_into().map_err(dec_err)?)) + }; + let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { + None + } else { + Some(attrs & Node::ETH_RLP_LONG_BIT != 0) + }; + match meta_raw[33] { + Self::BRANCH_NODE => { + let branch_header_size = NBRANCH as u64 * 8 + 4; + let node_raw = mem + .get_view(addr + META_SIZE, branch_header_size) + .ok_or(ShaleError::LinearMemStoreError)?; + let mut cur = Cursor::new(node_raw.deref()); + let mut chd = [None; NBRANCH]; + let mut buff = [0; 8]; + for chd in chd.iter_mut() { + cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + let addr = u64::from_le_bytes(buff); + if addr != 0 { + *chd = Some(unsafe { ObjPtr::new_from_addr(addr) }) + } + } + cur.read_exact(&mut buff[..4]).map_err(|_| ShaleError::DecodeError)?; + let raw_len = u32::from_le_bytes(buff[..4].try_into().map_err(dec_err)?) as u64; + let value = if raw_len == u32::MAX as u64 { + None + } else { + Some(Data( + mem.get_view(addr + META_SIZE + branch_header_size, raw_len) + .ok_or(ShaleError::LinearMemStoreError)? + .to_vec(), + )) + }; + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Branch(BranchNode { chd, value }), + )) + } + Self::EXT_NODE => { + let ext_header_size = 1 + 8; + let node_raw = mem + .get_view(addr + META_SIZE, ext_header_size) + .ok_or(ShaleError::LinearMemStoreError)?; + let mut cur = Cursor::new(node_raw.deref()); + let mut buff = [0; 8]; + cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?; + let len = buff[0] as u64; + cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + let ptr = u64::from_le_bytes(buff); + let nibbles: Vec<_> = to_nibbles( + &*mem + .get_view(addr + META_SIZE + ext_header_size, len) + .ok_or(ShaleError::LinearMemStoreError)?, + ) + .collect(); + let (path, _) = PartialPath::decode(nibbles); + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) })), + )) + } + Self::LEAF_NODE => { + let leaf_header_size = 1 + 4; + let node_raw = mem + .get_view(addr + META_SIZE, leaf_header_size) + .ok_or(ShaleError::LinearMemStoreError)?; + let mut cur = Cursor::new(node_raw.deref()); + let mut buff = [0; 4]; + cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?; + let path_len = buff[0] as u64; + cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + let data_len = u32::from_le_bytes(buff) as u64; + let remainder = mem + .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) + .ok_or(ShaleError::LinearMemStoreError)?; + let nibbles: Vec<_> = to_nibbles(&remainder[..path_len as usize]).collect(); + let (path, _) = PartialPath::decode(nibbles); + let value = Data(remainder[path_len as usize..].to_vec()); + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Leaf(LeafNode(path, value)), + )) + } + _ => Err(ShaleError::DecodeError), + } + } + + fn dehydrated_len(&self) -> u64 { + 32 + 1 + + 1 + + match &self.inner { + NodeType::Branch(n) => { + NBRANCH as u64 * 8 + + 4 + + match &n.value { + Some(val) => val.len() as u64, + None => 0, + } + } + NodeType::Extension(n) => 1 + 8 + n.0.dehydrated_len(), + NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, + } + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = Cursor::new(to); + + let mut attrs = 0; + attrs |= match self.root_hash.get() { + Some(h) => { + cur.write_all(&h.0).unwrap(); + Node::ROOT_HASH_VALID_BIT + } + None => { + cur.write_all(&[0; 32]).unwrap(); + 0 + } + }; + attrs |= match self.eth_rlp_long.get() { + Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT), + None => 0, + }; + cur.write_all(&[attrs]).unwrap(); + + match &self.inner { + NodeType::Branch(n) => { + cur.write_all(&[Self::BRANCH_NODE]).unwrap(); + for c in n.chd.iter() { + cur.write_all(&match c { + Some(p) => p.addr().to_le_bytes(), + None => 0u64.to_le_bytes(), + }) + .unwrap(); + } + match &n.value { + Some(val) => { + cur.write_all(&(val.len() as u32).to_le_bytes()).unwrap(); + cur.write_all(&val).unwrap(); + } + None => { + cur.write_all(&u32::MAX.to_le_bytes()).unwrap(); + } + } + } + NodeType::Extension(n) => { + cur.write_all(&[Self::EXT_NODE]).unwrap(); + let path: Vec = from_nibbles(&n.0.encode(false)).collect(); + cur.write_all(&[path.len() as u8]).unwrap(); + cur.write_all(&n.1.addr().to_le_bytes()).unwrap(); + cur.write_all(&path).unwrap(); + } + NodeType::Leaf(n) => { + cur.write_all(&[Self::LEAF_NODE]).unwrap(); + let path: Vec = from_nibbles(&n.0.encode(true)).collect(); + cur.write_all(&[path.len() as u8]).unwrap(); + cur.write_all(&(n.1.len() as u32).to_le_bytes()).unwrap(); + cur.write_all(&path).unwrap(); + cur.write_all(&n.1).unwrap(); + } + } + } +} + +#[test] +fn test_merkle_node_encoding() { + let check = |node: Node| { + let mut bytes = Vec::new(); + bytes.resize(node.dehydrated_len() as usize, 0); + node.dehydrate(&mut bytes); + + let mem = shale::PlainMem::new(bytes.len() as u64, 0x0); + mem.write(0, &bytes); + println!("{:?}", bytes); + let node_ = Node::hydrate(0, &mem).unwrap(); + assert!(node == node_); + }; + let chd0 = [None; NBRANCH]; + let mut chd1 = chd0; + for i in 0..NBRANCH / 2 { + chd1[i] = Some(unsafe { ObjPtr::new_from_addr(0xa) }); + } + for node in [ + Node::new_from_hash( + None, + None, + NodeType::Leaf(LeafNode(PartialPath(vec![0x1, 0x2, 0x3]), Data(vec![0x4, 0x5]))), + ), + Node::new_from_hash( + None, + None, + NodeType::Extension(ExtNode(PartialPath(vec![0x1, 0x2, 0x3]), unsafe { + ObjPtr::new_from_addr(0x42) + })), + ), + Node::new_from_hash( + None, + None, + NodeType::Branch(BranchNode { + chd: chd0, + value: Some(Data("hello, world!".as_bytes().to_vec())), + }), + ), + Node::new_from_hash(None, None, NodeType::Branch(BranchNode { chd: chd1, value: None })), + ] { + check(node); + } +} + +macro_rules! write_node { + ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { + if let None = $r.write($modify) { + let ptr = $self.new_node($r.clone())?.as_ptr(); + $self.set_parent(ptr, $parents); + $deleted.push($r.as_ptr()); + true + } else { + false + } + }; +} + +pub struct Merkle { + store: Box>, +} + +impl Merkle { + fn get_node(&self, ptr: ObjPtr) -> Result, MerkleError> { + self.store.get_item(ptr).map_err(MerkleError::Shale) + } + fn new_node(&self, item: Node) -> Result, MerkleError> { + self.store.put_item(item, 0).map_err(MerkleError::Shale) + } + fn free_node(&mut self, ptr: ObjPtr) -> Result<(), MerkleError> { + self.store.free_item(ptr).map_err(MerkleError::Shale) + } +} + +impl Merkle { + pub fn new(store: Box>) -> Self { + Self { store } + } + + pub fn init_root(root: &mut ObjPtr, store: &dyn ShaleStore) -> Result<(), MerkleError> { + Ok(*root = store + .put_item( + Node::new(NodeType::Branch(BranchNode { + chd: [None; NBRANCH], + value: None, + })), + Node::max_branch_node_size(), + ) + .map_err(MerkleError::Shale)? + .as_ptr()) + } + + pub fn get_store(&self) -> &dyn ShaleStore { + self.store.as_ref() + } + + pub fn empty_root() -> &'static Hash { + use once_cell::sync::OnceCell; + static V: OnceCell = OnceCell::new(); + V.get_or_init(|| { + Hash( + hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + .unwrap() + .try_into() + .unwrap(), + ) + }) + } + + pub fn root_hash(&self, root: ObjPtr) -> Result { + let root = self + .get_node(root)? + .inner + .as_branch() + .ok_or(MerkleError::NotBranchNode)? + .chd[0]; + Ok(if let Some(root) = root { + let mut node = self.get_node(root)?; + let res = node.get_root_hash::(self.store.as_ref()).clone(); + if node.lazy_dirty.get() { + node.write(|_| {}).unwrap(); + node.lazy_dirty.set(false) + } + res + } else { + Self::empty_root().clone() + }) + } + + fn dump_(&self, u: ObjPtr, w: &mut dyn Write) -> Result<(), MerkleError> { + let u_ref = self.get_node(u)?; + write!( + w, + "{} => {}: ", + u, + match u_ref.root_hash.get() { + Some(h) => hex::encode(&**h), + None => "".to_string(), + } + ) + .map_err(MerkleError::Format)?; + match &u_ref.inner { + NodeType::Branch(n) => { + writeln!(w, "{:?}", n).map_err(MerkleError::Format)?; + for c in n.chd.iter() { + if let Some(c) = c { + self.dump_(*c, w)? + } + } + } + NodeType::Leaf(n) => writeln!(w, "{:?}", n).unwrap(), + NodeType::Extension(n) => { + writeln!(w, "{:?}", n).map_err(MerkleError::Format)?; + self.dump_(n.1, w)? + } + } + Ok(()) + } + + pub fn dump(&self, root: ObjPtr, w: &mut dyn Write) -> Result<(), MerkleError> { + Ok(if root.is_null() { + write!(w, "").map_err(MerkleError::Format)?; + } else { + self.dump_(root, w)?; + }) + } + + fn set_parent<'b>(&self, new_chd: ObjPtr, parents: &mut [(ObjRef<'b, Node>, u8)]) { + let (p_ref, idx) = parents.last_mut().unwrap(); + p_ref + .write(|p| { + match &mut p.inner { + NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd), + NodeType::Extension(pp) => pp.1 = new_chd, + _ => unreachable!(), + } + p.rehash(); + }) + .unwrap(); + } + + fn split<'b>( + &self, mut u_ref: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], rem_path: &[u8], n_path: Vec, + n_value: Option, val: Vec, deleted: &mut Vec>, + ) -> Result>, MerkleError> { + let u_ptr = u_ref.as_ptr(); + let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) { + Some(idx) => { + // _ [u (new path)] + // / + // [parent] (-> [ExtNode (common prefix)]) -> [branch]* + // \_ [leaf (with val)] + u_ref + .write(|u| { + (*match &mut u.inner { + NodeType::Leaf(u) => &mut u.0, + NodeType::Extension(u) => &mut u.0, + _ => unreachable!(), + }) = PartialPath(n_path[idx + 1..].to_vec()); + u.rehash(); + }) + .unwrap(); + let leaf_ptr = self + .new_node(Node::new(NodeType::Leaf(LeafNode( + PartialPath(rem_path[idx + 1..].to_vec()), + Data(val), + ))))? + .as_ptr(); + let mut chd = [None; NBRANCH]; + chd[rem_path[idx] as usize] = Some(leaf_ptr); + chd[n_path[idx] as usize] = Some(match &u_ref.inner { + NodeType::Extension(u) => { + if u.0.len() == 0 { + deleted.push(u_ptr); + u.1 + } else { + u_ptr + } + } + _ => u_ptr, + }); + drop(u_ref); + let t = NodeType::Branch(BranchNode { chd, value: None }); + let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); + if idx > 0 { + self.new_node(Node::new(NodeType::Extension(ExtNode( + PartialPath(rem_path[..idx].to_vec()), + branch_ptr, + ))))? + .as_ptr() + } else { + branch_ptr + } + } + None => { + if rem_path.len() == n_path.len() { + let mut err = None; + write_node!( + self, + u_ref, + |u| { + match &mut u.inner { + NodeType::Leaf(u) => u.1 = Data(val), + NodeType::Extension(u) => { + match (|| { + let mut b_ref = self.get_node(u.1)?; + if let None = b_ref.write(|b| { + b.inner.as_branch_mut().unwrap().value = Some(Data(val)); + b.rehash() + }) { + u.1 = self.new_node(b_ref.clone())?.as_ptr(); + deleted.push(b_ref.as_ptr()); + } + Ok(()) + })() { + Err(e) => err = Some(Err(e)), + _ => (), + } + } + _ => unreachable!(), + } + u.rehash(); + }, + parents, + deleted + ); + return err.unwrap_or(Ok(None)) + } + let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() { + // key path is a prefix of the path to u + u_ref + .write(|u| { + (*match &mut u.inner { + NodeType::Leaf(u) => &mut u.0, + NodeType::Extension(u) => &mut u.0, + _ => unreachable!(), + }) = PartialPath(n_path[rem_path.len() + 1..].to_vec()); + u.rehash(); + }) + .unwrap(); + ( + match &u_ref.inner { + NodeType::Extension(u) => { + if u.0.len() == 0 { + deleted.push(u_ptr); + u.1 + } else { + u_ptr + } + } + _ => u_ptr, + }, + rem_path, + n_path[rem_path.len()], + Some(Data(val)), + ) + } else { + // key path extends the path to u + if n_value.is_none() { + // this case does not apply to an extension node, resume the tree walk + return Ok(Some(val)) + } + let leaf = self.new_node(Node::new(NodeType::Leaf(LeafNode( + PartialPath(rem_path[n_path.len() + 1..].to_vec()), + Data(val), + ))))?; + deleted.push(u_ptr); + (leaf.as_ptr(), &n_path[..], rem_path[n_path.len()], n_value) + }; + drop(u_ref); + // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] + let mut chd = [None; NBRANCH]; + chd[idx as usize] = Some(leaf_ptr); + let branch_ptr = self + .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v })))? + .as_ptr(); + if prefix.len() > 0 { + self.new_node(Node::new(NodeType::Extension(ExtNode( + PartialPath(prefix.to_vec()), + branch_ptr, + ))))? + .as_ptr() + } else { + branch_ptr + } + } + }; + // observation: + // - leaf/extension node can only be the child of a branch node + // - branch node can only be the child of a branch/extension node + self.set_parent(new_chd, parents); + Ok(None) + } + + pub fn insert>(&mut self, key: K, val: Vec, root: ObjPtr) -> Result<(), MerkleError> { + let mut deleted = Vec::new(); + let mut chunks = vec![0]; + chunks.extend(to_nibbles(key.as_ref())); + let mut parents = Vec::new(); + let mut u_ref = Some(self.get_node(root)?); + let mut nskip = 0; + let mut val = Some(val); + for (i, nib) in chunks.iter().enumerate() { + if nskip > 0 { + nskip -= 1; + continue + } + let mut u = u_ref.take().unwrap(); + let u_ptr = u.as_ptr(); + let next_ptr = match &u.inner { + NodeType::Branch(n) => match n.chd[*nib as usize] { + Some(c) => c, + None => { + // insert the leaf to the empty slot + let leaf_ptr = self + .new_node(Node::new(NodeType::Leaf(LeafNode( + PartialPath(chunks[i + 1..].to_vec()), + Data(val.take().unwrap()), + ))))? + .as_ptr(); + u.write(|u| { + let uu = u.inner.as_branch_mut().unwrap(); + uu.chd[*nib as usize] = Some(leaf_ptr); + u.rehash(); + }) + .unwrap(); + break + } + }, + NodeType::Leaf(n) => { + let n_path = n.0.to_vec(); + let n_value = Some(n.1.clone()); + self.split( + u, + &mut parents, + &chunks[i..], + n_path, + n_value, + val.take().unwrap(), + &mut deleted, + )?; + break + } + NodeType::Extension(n) => { + let n_path = n.0.to_vec(); + let n_ptr = n.1; + nskip = n_path.len() - 1; + if let Some(v) = self.split( + u, + &mut parents, + &chunks[i..], + n_path, + None, + val.take().unwrap(), + &mut deleted, + )? { + val = Some(v); + u = self.get_node(u_ptr)?; + n_ptr + } else { + break + } + } + }; + + parents.push((u, *nib)); + u_ref = Some(self.get_node(next_ptr)?); + } + if val.is_some() { + let mut info = None; + let u_ptr = { + let mut u = u_ref.take().unwrap(); + write_node!( + self, + &mut u, + |u| { + info = match &mut u.inner { + NodeType::Branch(n) => { + n.value = Some(Data(val.take().unwrap())); + None + } + NodeType::Leaf(n) => { + if n.0.len() == 0 { + n.1 = Data(val.take().unwrap()); + None + } else { + let idx = n.0[0]; + n.0 = PartialPath(n.0[1..].to_vec()); + u.rehash(); + Some((idx, true, None)) + } + } + NodeType::Extension(n) => { + let idx = n.0[0]; + let more = if n.0.len() > 1 { + n.0 = PartialPath(n.0[1..].to_vec()); + true + } else { + false + }; + Some((idx, more, Some(n.1))) + } + }; + u.rehash() + }, + &mut parents, + &mut deleted + ); + u.as_ptr() + }; + + if let Some((idx, more, ext)) = info { + let mut chd = [None; NBRANCH]; + let c_ptr = if more { + u_ptr + } else { + deleted.push(u_ptr); + ext.unwrap() + }; + chd[idx as usize] = Some(c_ptr); + let branch = self + .new_node(Node::new(NodeType::Branch(BranchNode { + chd, + value: Some(Data(val.take().unwrap())), + })))? + .as_ptr(); + self.set_parent(branch, &mut parents); + } + } + + drop(u_ref); + + for (mut r, _) in parents.into_iter().rev() { + r.write(|u| u.rehash()).unwrap(); + } + + for ptr in deleted.into_iter() { + self.free_node(ptr)? + } + Ok(()) + } + + fn after_remove_leaf<'b>( + &self, parents: &mut Vec<(ObjRef<'b, Node>, u8)>, deleted: &mut Vec>, + ) -> Result<(), MerkleError> { + let (b_chd, val) = { + let (mut b_ref, b_idx) = parents.pop().unwrap(); + // the immediate parent of a leaf must be a branch + b_ref + .write(|b| { + b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = None; + b.rehash() + }) + .unwrap(); + let b_inner = b_ref.inner.as_branch().unwrap(); + let (b_chd, has_chd) = b_inner.single_child(); + if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.len() < 1 { + return Ok(()) + } + deleted.push(b_ref.as_ptr()); + (b_chd, b_inner.value.clone()) + }; + let (mut p_ref, p_idx) = parents.pop().unwrap(); + let p_ptr = p_ref.as_ptr(); + if let Some(val) = val { + match &p_ref.inner { + NodeType::Branch(_) => { + // from: [p: Branch] -> [b (v)]x -> [Leaf]x + // to: [p: Branch] -> [Leaf (v)] + let leaf = self + .new_node(Node::new(NodeType::Leaf(LeafNode(PartialPath(Vec::new()), val))))? + .as_ptr(); + p_ref + .write(|p| { + p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(leaf); + p.rehash() + }) + .unwrap(); + } + NodeType::Extension(n) => { + // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x + // to: P -> [Leaf (v)] + let leaf = self + .new_node(Node::new(NodeType::Leaf(LeafNode( + PartialPath(n.0.clone().into_inner()), + val, + ))))? + .as_ptr(); + deleted.push(p_ptr); + self.set_parent(leaf, parents); + } + _ => unreachable!(), + } + } else { + let (c_ptr, idx) = b_chd.unwrap(); + let mut c_ref = self.get_node(c_ptr)?; + match &c_ref.inner { + NodeType::Branch(_) => { + drop(c_ref); + match &p_ref.inner { + NodeType::Branch(_) => { + // ____[Branch] + // / + // from: [p: Branch] -> [b]x* + // \____[Leaf]x + // to: [p: Branch] -> [Ext] -> [Branch] + let ext = self + .new_node(Node::new(NodeType::Extension(ExtNode(PartialPath(vec![idx]), c_ptr))))? + .as_ptr(); + self.set_parent(ext, &mut [(p_ref, p_idx)]); + } + NodeType::Extension(_) => { + // ____[Branch] + // / + // from: [p: Ext] -> [b]x* + // \____[Leaf]x + // to: [p: Ext] -> [Branch] + write_node!( + self, + p_ref, + |p| { + let mut pp = p.inner.as_extension_mut().unwrap(); + pp.0 .0.push(idx); + pp.1 = c_ptr; + p.rehash(); + }, + parents, + deleted + ); + } + _ => unreachable!(), + } + } + NodeType::Leaf(_) | NodeType::Extension(_) => { + match &p_ref.inner { + NodeType::Branch(_) => { + // ____[Leaf/Ext] + // / + // from: [p: Branch] -> [b]x* + // \____[Leaf]x + // to: [p: Branch] -> [Leaf/Ext] + let c_ptr = if let None = c_ref.write(|c| { + (match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }) + .0 + .insert(0, idx); + c.rehash() + }) { + deleted.push(c_ptr); + self.new_node(c_ref.clone())?.as_ptr() + } else { + c_ptr + }; + drop(c_ref); + p_ref + .write(|p| { + p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(c_ptr); + p.rehash() + }) + .unwrap(); + } + NodeType::Extension(n) => { + // ____[Leaf/Ext] + // / + // from: P -> [p: Ext]x -> [b]x* + // \____[Leaf]x + // to: P -> [p: Leaf/Ext] + deleted.push(p_ptr); + if !write_node!( + self, + c_ref, + |c| { + let mut path = n.0.clone().into_inner(); + path.push(idx); + let path0 = match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }; + path.extend(&**path0); + *path0 = PartialPath(path); + c.rehash() + }, + parents, + deleted + ) { + drop(c_ref); + self.set_parent(c_ptr, parents); + } + } + _ => unreachable!(), + } + } + } + } + Ok(()) + } + + fn after_remove_branch<'b>( + &self, (c_ptr, idx): (ObjPtr, u8), parents: &mut Vec<(ObjRef<'b, Node>, u8)>, + deleted: &mut Vec>, + ) -> Result<(), MerkleError> { + // [b] -> [u] -> [c] + let (mut b_ref, b_idx) = parents.pop().unwrap(); + let mut c_ref = self.get_node(c_ptr).unwrap(); + match &c_ref.inner { + NodeType::Branch(_) => { + drop(c_ref); + let mut err = None; + write_node!( + self, + b_ref, + |b| { + match (|| { + match &mut b.inner { + NodeType::Branch(n) => { + // from: [Branch] -> [Branch]x -> [Branch] + // to: [Branch] -> [Ext] -> [Branch] + n.chd[b_idx as usize] = Some( + self.new_node(Node::new(NodeType::Extension(ExtNode( + PartialPath(vec![idx]), + c_ptr, + ))))? + .as_ptr(), + ); + } + NodeType::Extension(n) => { + // from: [Ext] -> [Branch]x -> [Branch] + // to: [Ext] -> [Branch] + n.0 .0.push(idx); + n.1 = c_ptr + } + _ => unreachable!(), + } + Ok(b.rehash()) + })() { + Err(e) => err = Some(Err(e)), + _ => (), + } + }, + parents, + deleted + ); + if let Some(e) = err { + return e + } + } + NodeType::Leaf(_) | NodeType::Extension(_) => match &b_ref.inner { + NodeType::Branch(_) => { + // from: [Branch] -> [Branch]x -> [Leaf/Ext] + // to: [Branch] -> [Leaf/Ext] + let c_ptr = if let None = c_ref.write(|c| { + match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + } + .0 + .insert(0, idx); + c.rehash() + }) { + deleted.push(c_ptr); + self.new_node(c_ref.clone())?.as_ptr() + } else { + c_ptr + }; + drop(c_ref); + b_ref + .write(|b| { + b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = Some(c_ptr); + b.rehash() + }) + .unwrap(); + } + NodeType::Extension(n) => { + // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] + // to: P -> [Leaf/Ext] + let c_ptr = if let None = c_ref.write(|c| { + let mut path = n.0.clone().into_inner(); + path.push(idx); + let path0 = match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }; + path.extend(&**path0); + *path0 = PartialPath(path); + c.rehash() + }) { + deleted.push(c_ptr); + self.new_node(c_ref.clone())?.as_ptr() + } else { + c_ptr + }; + deleted.push(b_ref.as_ptr()); + drop(c_ref); + self.set_parent(c_ptr, parents); + } + _ => unreachable!(), + }, + } + Ok(()) + } + + pub fn remove>(&mut self, key: K, root: ObjPtr) -> Result>, MerkleError> { + let mut chunks = vec![0]; + chunks.extend(to_nibbles(key.as_ref())); + + if root.is_null() { + return Ok(None) + } + + let mut deleted = Vec::new(); + let mut parents: Vec<(ObjRef, _)> = Vec::new(); + let mut u_ref = self.get_node(root)?; + let mut nskip = 0; + let mut found = None; + + for (i, nib) in chunks.iter().enumerate() { + if nskip > 0 { + nskip -= 1; + continue + } + let next_ptr = match &u_ref.inner { + NodeType::Branch(n) => match n.chd[*nib as usize] { + Some(c) => c, + None => return Ok(None), + }, + NodeType::Leaf(n) => { + if &chunks[i..] != &*n.0 { + return Ok(None) + } + found = Some(n.1.clone()); + deleted.push(u_ref.as_ptr()); + self.after_remove_leaf(&mut parents, &mut deleted)?; + break + } + NodeType::Extension(n) => { + let n_path = &*n.0; + let rem_path = &chunks[i..]; + if rem_path < n_path || &rem_path[..n_path.len()] != n_path { + return Ok(None) + } + nskip = n_path.len() - 1; + n.1 + } + }; + + parents.push((u_ref, *nib)); + u_ref = self.get_node(next_ptr)?; + } + if found.is_none() { + match &u_ref.inner { + NodeType::Branch(n) => { + if n.value.is_none() { + return Ok(None) + } + let (c_chd, _) = n.single_child(); + u_ref + .write(|u| { + found = u.inner.as_branch_mut().unwrap().value.take(); + u.rehash() + }) + .unwrap(); + if let Some((c_ptr, idx)) = c_chd { + deleted.push(u_ref.as_ptr()); + self.after_remove_branch((c_ptr, idx), &mut parents, &mut deleted)? + } + } + NodeType::Leaf(n) => { + if n.0.len() > 0 { + return Ok(None) + } + found = Some(n.1.clone()); + deleted.push(u_ref.as_ptr()); + self.after_remove_leaf(&mut parents, &mut deleted)? + } + _ => (), + } + } + + drop(u_ref); + + for (mut r, _) in parents.into_iter().rev() { + r.write(|u| u.rehash()).unwrap(); + } + + for ptr in deleted.into_iter() { + self.free_node(ptr)?; + } + Ok(found.map(|e| e.0)) + } + + fn remove_tree_(&self, u: ObjPtr, deleted: &mut Vec>) -> Result<(), MerkleError> { + let u_ref = self.get_node(u)?; + match &u_ref.inner { + NodeType::Branch(n) => { + for c in n.chd.iter() { + if let Some(c) = c { + self.remove_tree_(*c, deleted)? + } + } + } + NodeType::Leaf(_) => (), + NodeType::Extension(n) => self.remove_tree_(n.1, deleted)?, + } + deleted.push(u); + Ok(()) + } + + pub fn remove_tree(&mut self, root: ObjPtr) -> Result<(), MerkleError> { + let mut deleted = Vec::new(); + if root.is_null() { + return Ok(()) + } + self.remove_tree_(root, &mut deleted)?; + for ptr in deleted.into_iter() { + self.free_node(ptr)?; + } + Ok(()) + } + + pub fn get_mut>(&mut self, key: K, root: ObjPtr) -> Result, MerkleError> { + let mut chunks = vec![0]; + chunks.extend(to_nibbles(key.as_ref())); + let mut parents = Vec::new(); + + if root.is_null() { + return Ok(None) + } + + let mut u_ref = self.get_node(root)?; + let mut nskip = 0; + + for (i, nib) in chunks.iter().enumerate() { + let u_ptr = u_ref.as_ptr(); + if nskip > 0 { + nskip -= 1; + continue + } + let next_ptr = match &u_ref.inner { + NodeType::Branch(n) => match n.chd[*nib as usize] { + Some(c) => c, + None => return Ok(None), + }, + NodeType::Leaf(n) => { + if &chunks[i..] != &*n.0 { + return Ok(None) + } + drop(u_ref); + return Ok(Some(RefMut::new(u_ptr, parents, self))) + } + NodeType::Extension(n) => { + let n_path = &*n.0; + let rem_path = &chunks[i..]; + if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { + return Ok(None) + } + nskip = n_path.len() - 1; + n.1 + } + }; + parents.push((u_ptr, *nib)); + u_ref = self.get_node(next_ptr)?; + } + + let u_ptr = u_ref.as_ptr(); + match &u_ref.inner { + NodeType::Branch(n) => { + if let Some(_) = n.value.as_ref() { + drop(u_ref); + return Ok(Some(RefMut::new(u_ptr, parents, self))) + } + } + NodeType::Leaf(n) => { + if n.0.len() == 0 { + drop(u_ref); + return Ok(Some(RefMut::new(u_ptr, parents, self))) + } + } + _ => (), + } + + Ok(None) + } + + pub fn get>(&self, key: K, root: ObjPtr) -> Result, MerkleError> { + let mut chunks = vec![0]; + chunks.extend(to_nibbles(key.as_ref())); + + if root.is_null() { + return Ok(None) + } + + let mut u_ref = self.get_node(root)?; + let mut nskip = 0; + + for (i, nib) in chunks.iter().enumerate() { + if nskip > 0 { + nskip -= 1; + continue + } + let next_ptr = match &u_ref.inner { + NodeType::Branch(n) => match n.chd[*nib as usize] { + Some(c) => c, + None => return Ok(None), + }, + NodeType::Leaf(n) => { + if &chunks[i..] != &*n.0 { + return Ok(None) + } + return Ok(Some(Ref(u_ref))) + } + NodeType::Extension(n) => { + let n_path = &*n.0; + let rem_path = &chunks[i..]; + if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { + return Ok(None) + } + nskip = n_path.len() - 1; + n.1 + } + }; + u_ref = self.get_node(next_ptr)?; + } + + match &u_ref.inner { + NodeType::Branch(n) => { + if let Some(_) = n.value.as_ref() { + return Ok(Some(Ref(u_ref))) + } + } + NodeType::Leaf(n) => { + if n.0.len() == 0 { + return Ok(Some(Ref(u_ref))) + } + } + _ => (), + } + + Ok(None) + } + + pub fn flush_dirty(&self) -> Option<()> { + self.store.flush_dirty() + } +} + +pub struct Ref<'a>(ObjRef<'a, Node>); + +pub struct RefMut<'a> { + ptr: ObjPtr, + parents: Vec<(ObjPtr, u8)>, + merkle: &'a mut Merkle, +} + +impl<'a> std::ops::Deref for Ref<'a> { + type Target = [u8]; + fn deref(&self) -> &[u8] { + match &self.0.inner { + NodeType::Branch(n) => n.value.as_ref().unwrap(), + NodeType::Leaf(n) => &n.1, + _ => unreachable!(), + } + } +} + +impl<'a> RefMut<'a> { + fn new(ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, merkle: &'a mut Merkle) -> Self { + Self { ptr, parents, merkle } + } + + pub fn get<'b>(&'b self) -> Ref<'b> { + Ref(self.merkle.get_node(self.ptr).unwrap()) + } + + pub fn write(&mut self, modify: impl FnOnce(&mut Vec)) -> Result<(), MerkleError> { + let mut deleted = Vec::new(); + { + let mut u_ref = self.merkle.get_node(self.ptr).unwrap(); + let mut parents: Vec<_> = self + .parents + .iter() + .map(|(ptr, nib)| (self.merkle.get_node(*ptr).unwrap(), *nib)) + .collect(); + write_node!( + self.merkle, + u_ref, + |u| { + modify(match &mut u.inner { + NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, + NodeType::Leaf(n) => &mut n.1 .0, + _ => unreachable!(), + }); + u.rehash() + }, + &mut parents, + &mut deleted + ); + } + for ptr in deleted.into_iter() { + self.merkle.free_node(ptr)?; + } + Ok(()) + } +} + +pub trait ValueTransformer { + fn transform(bytes: &[u8]) -> Vec; +} + +pub struct IdTrans; + +impl ValueTransformer for IdTrans { + fn transform(bytes: &[u8]) -> Vec { + bytes.to_vec() + } +} + +fn to_nibbles<'a>(bytes: &'a [u8]) -> impl Iterator + 'a { + bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) +} + +fn from_nibbles<'a>(nibbles: &'a [u8]) -> impl Iterator + 'a { + assert!(nibbles.len() & 1 == 0); + nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) +} + +#[test] +fn test_to_nibbles() { + for (bytes, nibbles) in [ + (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), + (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]), + ] { + let n: Vec<_> = to_nibbles(&bytes).collect(); + assert_eq!(n, nibbles); + } +} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 000000000000..2eb6a9e21405 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,1206 @@ +// TODO: try to get rid of the use `RefCell` in this file + +use std::cell::{RefCell, RefMut}; +use std::collections::HashMap; +use std::fmt; +use std::num::NonZeroUsize; +use std::ops::Deref; +use std::rc::Rc; +use std::sync::Arc; + +use aiofut::{AIOBuilder, AIOManager}; +use growthring::{ + wal::{RecoverPolicy, WALLoader, WALWriter}, + WALStoreAIO, +}; +use nix::fcntl::{flock, FlockArg}; +use shale::{MemStore, MemView, SpaceID}; +use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; +use typed_builder::TypedBuilder; + +use crate::file::{Fd, File}; + +pub(crate) const PAGE_SIZE_NBIT: u64 = 12; +pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; +pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; + +pub trait MemStoreR { + fn get_slice(&self, offset: u64, length: u64) -> Option>; + fn id(&self) -> SpaceID; +} + +type Page = [u8; PAGE_SIZE as usize]; + +#[derive(Debug)] +pub struct SpaceWrite { + offset: u64, + data: Box<[u8]>, +} + +#[derive(Debug)] +pub struct Ash { + pub old: Vec, + pub new: Vec>, +} + +impl Ash { + fn new() -> Self { + Self { + old: Vec::new(), + new: Vec::new(), + } + } +} + +#[derive(Debug)] +pub struct AshRecord(pub HashMap); + +impl growthring::wal::Record for AshRecord { + fn serialize(&self) -> growthring::wal::WALBytes { + let mut bytes = Vec::new(); + bytes.extend((self.0.len() as u64).to_le_bytes()); + for (space_id, w) in self.0.iter() { + bytes.extend((*space_id as u8).to_le_bytes()); + bytes.extend((w.old.len() as u32).to_le_bytes()); + for (sw_old, sw_new) in w.old.iter().zip(w.new.iter()) { + bytes.extend(sw_old.offset.to_le_bytes()); + bytes.extend((sw_old.data.len() as u64).to_le_bytes()); + bytes.extend(&*sw_old.data); + bytes.extend(&**sw_new); + } + } + bytes.into() + } +} + +impl AshRecord { + fn deserialize(raw: growthring::wal::WALBytes) -> Self { + let mut r = &raw[..]; + let len = u64::from_le_bytes(r[..8].try_into().unwrap()); + r = &r[8..]; + let writes = (0..len) + .map(|_| { + let space_id = u8::from_le_bytes(r[..1].try_into().unwrap()); + let wlen = u32::from_le_bytes(r[1..5].try_into().unwrap()); + r = &r[5..]; + let mut old = Vec::new(); + let mut new = Vec::new(); + for _ in 0..wlen { + let offset = u64::from_le_bytes(r[..8].try_into().unwrap()); + let data_len = u64::from_le_bytes(r[8..16].try_into().unwrap()); + r = &r[16..]; + let old_write = SpaceWrite { + offset, + data: r[..data_len as usize].into(), + }; + r = &r[data_len as usize..]; + let new_data: Box<[u8]> = r[..data_len as usize].into(); + r = &r[data_len as usize..]; + old.push(old_write); + new.push(new_data); + } + (space_id, Ash { old, new }) + }) + .collect(); + Self(writes) + } +} + +/// Basic copy-on-write item in the linear storage space for multi-versioning. +pub struct DeltaPage(u64, Box); + +impl DeltaPage { + #[inline(always)] + fn offset(&self) -> u64 { + self.0 << PAGE_SIZE_NBIT + } + + #[inline(always)] + fn data(&self) -> &[u8] { + self.1.as_ref() + } + + #[inline(always)] + fn data_mut(&mut self) -> &mut [u8] { + self.1.as_mut() + } +} + +#[derive(Default)] +pub struct StoreDelta(Vec); + +impl fmt::Debug for StoreDelta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "") + } +} + +impl Deref for StoreDelta { + type Target = [DeltaPage]; + fn deref(&self) -> &[DeltaPage] { + &self.0 + } +} + +impl StoreDelta { + pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { + let mut deltas = Vec::new(); + let mut widx: Vec<_> = (0..writes.len()).filter(|i| writes[*i].data.len() > 0).collect(); + if widx.is_empty() { + // the writes are all empty + return Self(deltas) + } + + // sort by the starting point + widx.sort_by_key(|i| writes[*i].offset); + + let mut witer = widx.into_iter(); + let w0 = &writes[witer.next().unwrap()]; + let mut head = w0.offset >> PAGE_SIZE_NBIT; + let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; + + macro_rules! create_dirty_pages { + ($l: expr, $r: expr) => { + for p in $l..=$r { + let off = p << PAGE_SIZE_NBIT; + deltas.push(DeltaPage( + p, + Box::new(src.get_slice(off, PAGE_SIZE).unwrap().try_into().unwrap()), + )); + } + }; + } + + for i in witer { + let w = &writes[i]; + let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; + let wp = w.offset >> PAGE_SIZE_NBIT; + if wp > tail { + // all following writes won't go back past w.offset, so the previous continous + // write area is determined + create_dirty_pages!(head, tail); + head = wp; + } + tail = std::cmp::max(tail, ep) + } + create_dirty_pages!(head, tail); + + let psize = PAGE_SIZE as usize; + for w in writes.into_iter() { + let mut l = 0; + let mut r = deltas.len(); + while r - l > 1 { + let mid = (l + r) >> 1; + (*if w.offset < deltas[mid].offset() { + &mut r + } else { + &mut l + }) = mid; + } + let off = (w.offset - deltas[l].offset()) as usize; + let len = std::cmp::min(psize - off, w.data.len()); + deltas[l].data_mut()[off..off + len].copy_from_slice(&w.data[..len]); + let mut data = &w.data[len..]; + while data.len() >= psize { + l += 1; + deltas[l].data_mut().copy_from_slice(&data[..psize]); + data = &data[psize..]; + } + if data.len() > 0 { + l += 1; + deltas[l].data_mut()[..data.len()].copy_from_slice(&data); + } + } + Self(deltas) + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +pub struct StoreRev { + prev: RefCell>, + delta: StoreDelta, +} + +impl fmt::Debug for StoreRev { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "\n") + } +} + +impl MemStoreR for StoreRev { + fn get_slice(&self, offset: u64, length: u64) -> Option> { + let prev = self.prev.borrow(); + let mut start = offset; + let end = start + length; + let delta = &self.delta; + let mut l = 0; + let mut r = delta.len(); + // no dirty page, before or after all dirty pages + if r == 0 { + return prev.get_slice(start, end - start) + } + // otherwise, some dirty pages are covered by the range + while r - l > 1 { + let mid = (l + r) >> 1; + (*if start < delta[mid].offset() { &mut r } else { &mut l }) = mid; + } + if start >= delta[l].offset() + PAGE_SIZE { + l += 1 + } + if l >= delta.len() || end < delta[l].offset() { + return prev.get_slice(start, end - start) + } + let mut data = Vec::new(); + let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE); + if start < delta[l].offset() { + data.extend(prev.get_slice(start, delta[l].offset() - start)?); + data.extend(&delta[l].data()[..p_off as usize]); + } else { + data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]); + }; + start = delta[l].offset() + p_off; + while start < end { + l += 1; + if l >= delta.len() || end < delta[l].offset() { + data.extend(prev.get_slice(start, end - start)?); + break + } + if delta[l].offset() > start { + data.extend(prev.get_slice(start, delta[l].offset() - start)?); + } + if end < delta[l].offset() + PAGE_SIZE { + data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); + break + } + data.extend(delta[l].data()); + start = delta[l].offset() + PAGE_SIZE; + } + assert!(data.len() == length as usize); + Some(data) + } + + fn id(&self) -> SpaceID { + self.prev.borrow().id() + } +} + +#[derive(Clone, Debug)] +pub struct StoreRevShared(Rc); + +impl StoreRevShared { + pub fn from_ash(prev: Rc, writes: &[SpaceWrite]) -> Self { + let delta = StoreDelta::new(prev.as_ref(), writes); + let prev = RefCell::new(prev); + Self(Rc::new(StoreRev { prev, delta })) + } + + pub fn from_delta(prev: Rc, delta: StoreDelta) -> Self { + let prev = RefCell::new(prev); + Self(Rc::new(StoreRev { prev, delta })) + } + + pub fn set_prev(&mut self, prev: Rc) { + *self.0.prev.borrow_mut() = prev + } + + pub fn inner(&self) -> &Rc { + &self.0 + } +} + +impl MemStore for StoreRevShared { + fn get_view(&self, offset: u64, length: u64) -> Option> { + let data = self.0.get_slice(offset, length)?; + Some(Box::new(StoreRef { data })) + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(StoreShared(self.clone()))) + } + + fn write(&self, _offset: u64, _change: &[u8]) { + // StoreRevShared is a read-only view version of MemStore + // Writes could be induced by lazy hashing and we can just ignore those + } + + fn id(&self) -> SpaceID { + ::id(&self.0) + } +} + +struct StoreRef { + data: Vec, +} + +impl Deref for StoreRef { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.data + } +} + +impl MemView for StoreRef {} + +struct StoreShared(S); + +impl Deref for StoreShared { + type Target = dyn MemStore; + fn deref(&self) -> &(dyn MemStore + 'static) { + &self.0 + } +} + +struct StoreRevMutDelta { + pages: HashMap>, + plain: Ash, +} + +#[derive(Clone)] +pub struct StoreRevMut { + prev: Rc, + deltas: Rc>, +} + +impl StoreRevMut { + pub fn new(prev: Rc) -> Self { + Self { + prev, + deltas: Rc::new(RefCell::new(StoreRevMutDelta { + pages: HashMap::new(), + plain: Ash::new(), + })), + } + } + + fn get_page_mut(&self, pid: u64) -> RefMut<[u8]> { + let mut deltas = self.deltas.borrow_mut(); + if deltas.pages.get(&pid).is_none() { + let page = Box::new( + self.prev + .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) + .unwrap() + .try_into() + .unwrap(), + ); + deltas.pages.insert(pid, page); + } + RefMut::map(deltas, |e| &mut e.pages.get_mut(&pid).unwrap()[..]) + } + + pub fn take_delta(&self) -> (StoreDelta, Ash) { + let mut pages = Vec::new(); + let deltas = std::mem::replace( + &mut *self.deltas.borrow_mut(), + StoreRevMutDelta { + pages: HashMap::new(), + plain: Ash::new(), + }, + ); + for (pid, page) in deltas.pages.into_iter() { + pages.push(DeltaPage(pid, page)); + } + pages.sort_by_key(|p| p.0); + (StoreDelta(pages), deltas.plain) + } +} + +impl MemStore for StoreRevMut { + fn get_view(&self, offset: u64, length: u64) -> Option> { + let data = if length == 0 { + Vec::new() + } else { + let end = offset + length - 1; + let s_pid = offset >> PAGE_SIZE_NBIT; + let s_off = (offset & PAGE_MASK) as usize; + let e_pid = end >> PAGE_SIZE_NBIT; + let e_off = (end & PAGE_MASK) as usize; + let deltas = &self.deltas.borrow().pages; + if s_pid == e_pid { + match deltas.get(&s_pid) { + Some(p) => p[s_off..e_off + 1].to_vec(), + None => self.prev.get_slice(offset, length)?, + } + } else { + let mut data = match deltas.get(&s_pid) { + Some(p) => p[s_off..].to_vec(), + None => self.prev.get_slice(offset, PAGE_SIZE - s_off as u64)?, + }; + for p in s_pid + 1..e_pid { + match deltas.get(&p) { + Some(p) => data.extend(&**p), + None => data.extend(&self.prev.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?), + }; + } + match deltas.get(&e_pid) { + Some(p) => data.extend(&p[..e_off + 1]), + None => data.extend(self.prev.get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?), + } + data + } + }; + Some(Box::new(StoreRef { data })) + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(StoreShared(self.clone()))) + } + + fn write(&self, offset: u64, mut change: &[u8]) { + let length = change.len() as u64; + let end = offset + length - 1; + let s_pid = offset >> PAGE_SIZE_NBIT; + let s_off = (offset & PAGE_MASK) as usize; + let e_pid = end >> PAGE_SIZE_NBIT; + let e_off = (end & PAGE_MASK) as usize; + let mut old: Vec = Vec::new(); + let new: Box<[u8]> = change.into(); + if s_pid == e_pid { + let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1]; + old.extend(&*slice); + slice.copy_from_slice(change) + } else { + let len = PAGE_SIZE as usize - s_off; + { + let slice = &mut self.get_page_mut(s_pid)[s_off..]; + old.extend(&*slice); + slice.copy_from_slice(&change[..len]); + } + change = &change[len..]; + for p in s_pid + 1..e_pid { + let mut slice = self.get_page_mut(p); + old.extend(&*slice); + slice.copy_from_slice(&change[..PAGE_SIZE as usize]); + change = &change[PAGE_SIZE as usize..]; + } + let slice = &mut self.get_page_mut(e_pid)[..e_off + 1]; + old.extend(&*slice); + slice.copy_from_slice(change); + } + let plain = &mut self.deltas.borrow_mut().plain; + assert!(old.len() == new.len()); + plain.old.push(SpaceWrite { + offset, + data: old.into(), + }); + plain.new.push(new); + } + + fn id(&self) -> SpaceID { + self.prev.id() + } +} + +#[cfg(test)] +#[derive(Clone)] +pub struct ZeroStore(Rc<()>); + +#[cfg(test)] +impl ZeroStore { + pub fn new() -> Self { + Self(Rc::new(())) + } +} + +#[cfg(test)] +impl MemStoreR for ZeroStore { + fn get_slice(&self, _: u64, length: u64) -> Option> { + Some(vec![0; length as usize]) + } + + fn id(&self) -> SpaceID { + shale::INVALID_SPACE_ID + } +} + +#[test] +fn test_from_ash() { + use rand::{rngs::StdRng, Rng, SeedableRng}; + let mut rng = StdRng::seed_from_u64(42); + let min = rng.gen_range(0..2 * PAGE_SIZE); + let max = rng.gen_range(min + 1 * PAGE_SIZE..min + 100 * PAGE_SIZE); + for _ in 0..2000 { + let n = 20; + let mut canvas = Vec::new(); + canvas.resize((max - min) as usize, 0); + let mut writes: Vec<_> = Vec::new(); + for _ in 0..n { + let l = rng.gen_range(min..max); + let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max)); + let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect(); + for (idx, byte) in (l..r).zip(data.iter()) { + canvas[(idx - min) as usize] = *byte; + } + println!("[0x{:x}, 0x{:x})", l, r); + writes.push(SpaceWrite { offset: l, data }); + } + let z = Rc::new(ZeroStore::new()); + let rev = StoreRevShared::from_ash(z, &writes); + println!("{:?}", rev); + assert_eq!(&**rev.get_view(min, max - min).unwrap(), &canvas); + for _ in 0..2 * n { + let l = rng.gen_range(min..max); + let r = rng.gen_range(l + 1..max); + assert_eq!( + &**rev.get_view(l, r - l).unwrap(), + &canvas[(l - min) as usize..(r - min) as usize] + ); + } + } +} + +#[derive(TypedBuilder)] +pub struct StoreConfig { + ncached_pages: usize, + ncached_files: usize, + #[builder(default = 22)] // 4MB file by default + file_nbit: u64, + space_id: SpaceID, + rootfd: Fd, +} + +struct CachedSpaceInner { + cached_pages: lru::LruCache>, + pinned_pages: HashMap)>, + files: Arc, + disk_buffer: DiskBufferRequester, +} + +#[derive(Clone)] +pub struct CachedSpace { + inner: Rc>, + space_id: SpaceID, +} + +impl CachedSpace { + pub fn new(cfg: &StoreConfig) -> Result { + let space_id = cfg.space_id; + let files = Arc::new(FilePool::new(cfg)?); + Ok(Self { + inner: Rc::new(RefCell::new(CachedSpaceInner { + cached_pages: lru::LruCache::new(NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size")), + pinned_pages: HashMap::new(), + files, + disk_buffer: DiskBufferRequester::default(), + })), + space_id, + }) + } + + /// Apply `delta` to the store and return the StoreDelta that can undo this change. + pub fn update(&self, delta: &StoreDelta) -> Option { + let mut pages = Vec::new(); + for DeltaPage(pid, page) in &delta.0 { + let data = self.inner.borrow_mut().pin_page(self.space_id, *pid).ok()?; + // save the original data + pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap()))); + // apply the change + data.copy_from_slice(page.as_ref()); + } + Some(StoreDelta(pages)) + } +} + +impl CachedSpaceInner { + fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result, StoreError> { + if let Some(p) = self.disk_buffer.get_page(space_id, pid) { + return Ok(Box::new((*p).clone())) + } + let file_nbit = self.files.get_file_nbit(); + let file_size = 1 << file_nbit; + let poff = pid << PAGE_SIZE_NBIT; + let file = self.files.get_file(poff >> file_nbit)?; + let mut page: Page = [0; PAGE_SIZE as usize]; + nix::sys::uio::pread(file.get_fd(), &mut page, (poff & (file_size - 1)) as nix::libc::off_t) + .map_err(StoreError::System)?; + Ok(Box::new(page)) + } + + fn pin_page(&mut self, space_id: SpaceID, pid: u64) -> Result<&'static mut [u8], StoreError> { + let base = match self.pinned_pages.get_mut(&pid) { + Some(mut e) => { + e.0 += 1; + e.1.as_mut_ptr() + } + None => { + let mut page = match self.cached_pages.pop(&pid) { + Some(p) => p, + None => self.fetch_page(space_id, pid)?, + }; + let ptr = page.as_mut_ptr(); + self.pinned_pages.insert(pid, (1, page)); + ptr + } + }; + Ok(unsafe { std::slice::from_raw_parts_mut(base, PAGE_SIZE as usize) }) + } + + fn unpin_page(&mut self, pid: u64) { + use std::collections::hash_map::Entry::*; + let page = match self.pinned_pages.entry(pid) { + Occupied(mut e) => { + let cnt = &mut e.get_mut().0; + assert!(*cnt > 0); + *cnt -= 1; + if *cnt == 0 { + e.remove().1 + } else { + return + } + } + _ => unreachable!(), + }; + self.cached_pages.put(pid, page); + } +} + +struct PageRef { + pid: u64, + data: &'static mut [u8], + store: CachedSpace, +} + +impl<'a> std::ops::Deref for PageRef { + type Target = [u8]; + fn deref(&self) -> &[u8] { + self.data + } +} + +impl<'a> std::ops::DerefMut for PageRef { + fn deref_mut(&mut self) -> &mut [u8] { + self.data + } +} + +impl PageRef { + fn new(pid: u64, store: &CachedSpace) -> Option { + Some(Self { + pid, + data: store.inner.borrow_mut().pin_page(store.space_id, pid).ok()?, + store: store.clone(), + }) + } +} + +impl Drop for PageRef { + fn drop(&mut self) { + self.store.inner.borrow_mut().unpin_page(self.pid); + } +} + +impl MemStoreR for CachedSpace { + fn get_slice(&self, offset: u64, length: u64) -> Option> { + if length == 0 { + return Some(Default::default()) + } + let end = offset + length - 1; + let s_pid = offset >> PAGE_SIZE_NBIT; + let s_off = (offset & PAGE_MASK) as usize; + let e_pid = end >> PAGE_SIZE_NBIT; + let e_off = (end & PAGE_MASK) as usize; + if s_pid == e_pid { + return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec()) + } + let mut data: Vec = Vec::new(); + { + data.extend(&PageRef::new(s_pid, self)?[s_off..]); + for p in s_pid + 1..e_pid { + data.extend(&PageRef::new(p, self)?[..]); + } + data.extend(&PageRef::new(e_pid, self)?[..e_off + 1]); + } + Some(data) + } + + fn id(&self) -> SpaceID { + self.space_id + } +} + +pub struct FilePool { + files: parking_lot::Mutex>>, + file_nbit: u64, + rootfd: Fd, +} + +impl FilePool { + fn new(cfg: &StoreConfig) -> Result { + let rootfd = cfg.rootfd; + let file_nbit = cfg.file_nbit; + let s = Self { + files: parking_lot::Mutex::new(lru::LruCache::new( + NonZeroUsize::new(cfg.ncached_files).expect("non-zero file num"), + )), + file_nbit, + rootfd, + }; + let f0 = s.get_file(0)?; + if let Err(_) = flock(f0.get_fd(), FlockArg::LockExclusiveNonblock) { + return Err(StoreError::InitError("the store is busy".into())) + } + Ok(s) + } + + fn get_file(&self, fid: u64) -> Result, StoreError> { + let mut files = self.files.lock(); + let file_size = 1 << self.file_nbit; + Ok(match files.get(&fid) { + Some(f) => f.clone(), + None => { + files.put( + fid, + Arc::new(File::new(fid, file_size, self.rootfd).map_err(StoreError::System)?), + ); + files.peek(&fid).unwrap().clone() + } + }) + } + + fn get_file_nbit(&self) -> u64 { + self.file_nbit + } +} + +impl Drop for FilePool { + fn drop(&mut self) { + let f0 = self.get_file(0).unwrap(); + flock(f0.get_fd(), FlockArg::UnlockNonblock).ok(); + nix::unistd::close(self.rootfd).ok(); + } +} + +#[derive(Debug)] +pub struct BufferWrite { + pub space_id: SpaceID, + pub delta: StoreDelta, +} + +pub enum BufferCmd { + InitWAL(Fd, String), + WriteBatch(Vec, AshRecord), + GetPage((SpaceID, u64), oneshot::Sender>>), + CollectAsh(usize, oneshot::Sender>), + RegCachedSpace(SpaceID, Arc), + Shutdown, +} + +#[derive(TypedBuilder, Clone)] +pub struct WALConfig { + #[builder(default = 22)] // 4MB WAL logs + pub(crate) file_nbit: u64, + #[builder(default = 15)] // 32KB + pub(crate) block_nbit: u64, + #[builder(default = 100)] // preserve a rolling window of 100 past commits + pub(crate) max_revisions: u32, +} + +/// Config for the disk buffer. +#[derive(TypedBuilder, Clone)] +pub struct DiskBufferConfig { + /// Maximum buffered disk buffer commands. + #[builder(default = 4096)] + pub(crate) max_buffered: usize, + /// Maximum number of pending pages. + #[builder(default = 65536)] // 256MB total size by default + max_pending: usize, + /// Maximum number of concurrent async I/O requests. + #[builder(default = 1024)] + max_aio_requests: u32, + /// Maximum number of async I/O responses that it polls for at a time. + #[builder(default = 128)] + max_aio_response: u16, + /// Maximum number of async I/O requests per submission. + #[builder(default = 128)] + max_aio_submit: usize, + /// Maximum number of concurrent async I/O requests in WAL. + #[builder(default = 256)] + wal_max_aio_requests: usize, + /// Maximum buffered WAL records. + #[builder(default = 1024)] + wal_max_buffered: usize, + /// Maximum batched WAL records per write. + #[builder(default = 4096)] + wal_max_batch: usize, +} + +struct PendingPage { + staging_data: Arc, + file_nbit: u64, + staging_notifiers: Vec>, + writing_notifiers: Vec>, +} + +pub struct DiskBuffer { + pending: HashMap<(SpaceID, u64), PendingPage>, + inbound: mpsc::Receiver, + fc_notifier: Option>, + fc_blocker: Option>, + file_pools: [Option>; 255], + aiomgr: AIOManager, + local_pool: Rc, + task_id: u64, + tasks: Rc>>>>, + wal: Option>>>, + cfg: DiskBufferConfig, + wal_cfg: WALConfig, +} + +impl DiskBuffer { + pub fn new(inbound: mpsc::Receiver, cfg: &DiskBufferConfig, wal: &WALConfig) -> Option { + const INIT: Option> = None; + let aiomgr = AIOBuilder::default() + .max_events(cfg.max_aio_requests) + .max_nwait(cfg.max_aio_response) + .max_nbatched(cfg.max_aio_submit) + .build() + .ok()?; + + Some(Self { + pending: HashMap::new(), + cfg: cfg.clone(), + inbound, + fc_notifier: None, + fc_blocker: None, + file_pools: [INIT; 255], + aiomgr, + local_pool: Rc::new(tokio::task::LocalSet::new()), + task_id: 0, + tasks: Rc::new(RefCell::new(HashMap::new())), + wal: None, + wal_cfg: wal.clone(), + }) + } + + unsafe fn get_longlive_self(&mut self) -> &'static mut Self { + std::mem::transmute::<&mut Self, &'static mut Self>(self) + } + + fn schedule_write(&mut self, page_key: (SpaceID, u64)) { + let p = self.pending.get(&page_key).unwrap(); + let offset = page_key.1 << PAGE_SIZE_NBIT; + let fid = offset >> p.file_nbit; + let fmask = (1 << p.file_nbit) - 1; + let file = self.file_pools[page_key.0 as usize] + .as_ref() + .unwrap() + .get_file(fid) + .unwrap(); + let fut = self + .aiomgr + .write(file.get_fd(), offset & fmask, Box::new(*p.staging_data), None); + let s = unsafe { self.get_longlive_self() }; + self.start_task(async move { + let (res, _) = fut.await; + res.unwrap(); + s.finish_write(page_key); + }); + } + + fn finish_write(&mut self, page_key: (SpaceID, u64)) { + use std::collections::hash_map::Entry::*; + match self.pending.entry(page_key) { + Occupied(mut e) => { + let slot = e.get_mut(); + for notifier in std::mem::replace(&mut slot.writing_notifiers, Vec::new()) { + notifier.add_permits(1) + } + if slot.staging_notifiers.is_empty() { + e.remove(); + if self.pending.len() < self.cfg.max_pending { + if let Some(notifier) = self.fc_notifier.take() { + notifier.send(()).unwrap(); + } + } + } else { + assert!(slot.writing_notifiers.is_empty()); + std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers); + // write again + self.schedule_write(page_key); + } + } + _ => unreachable!(), + } + } + + async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), ()> { + let mut aiobuilder = AIOBuilder::default(); + aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); + let aiomgr = aiobuilder.build().map_err(|_| ())?; + let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)).map_err(|_| ())?; + let mut loader = WALLoader::new(); + loader + .file_nbit(self.wal_cfg.file_nbit) + .block_nbit(self.wal_cfg.block_nbit) + .recover_policy(RecoverPolicy::Strict); + if self.wal.is_some() { + // already initialized + return Ok(()) + } + let wal = loader + .load( + store, + |raw, _| { + let batch = AshRecord::deserialize(raw); + for (space_id, Ash { old, new }) in batch.0 { + for (old, data) in old.into_iter().zip(new.into_iter()) { + let offset = old.offset; + let file_pool = self.file_pools[space_id as usize].as_ref().unwrap(); + let file_nbit = file_pool.get_file_nbit(); + let file_mask = (1 << file_nbit) - 1; + let fid = offset >> file_nbit; + nix::sys::uio::pwrite( + file_pool.get_file(fid).map_err(|_| ())?.get_fd(), + &*data, + (offset & file_mask) as nix::libc::off_t, + ) + .map_err(|_| ())?; + } + } + Ok(()) + }, + self.wal_cfg.max_revisions, + ) + .await?; + self.wal = Some(Rc::new(Mutex::new(wal))); + Ok(()) + } + + async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec, AshRecord)>) { + use std::collections::hash_map::Entry::*; + loop { + let mut bwrites = Vec::new(); + let mut records = Vec::new(); + + if let Some((bw, ac)) = writes.recv().await { + records.push(ac); + bwrites.extend(bw); + } else { + break + } + while let Ok((bw, ac)) = writes.try_recv() { + records.push(ac); + bwrites.extend(bw); + if records.len() >= self.cfg.wal_max_batch { + break + } + } + // first write to WAL + let ring_ids: Vec<_> = futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) + .await + .into_iter() + .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) + .collect(); + let sem = Rc::new(tokio::sync::Semaphore::new(0)); + let mut npermit = 0; + for BufferWrite { space_id, delta } in bwrites { + for w in delta.0 { + let page_key = (space_id, w.0); + match self.pending.entry(page_key) { + Occupied(mut e) => { + let e = e.get_mut(); + e.staging_data = w.1.into(); + e.staging_notifiers.push(sem.clone()); + npermit += 1; + } + Vacant(e) => { + let file_nbit = self.file_pools[page_key.0 as usize].as_ref().unwrap().file_nbit; + e.insert(PendingPage { + staging_data: w.1.into(), + file_nbit, + staging_notifiers: Vec::new(), + writing_notifiers: vec![sem.clone()], + }); + npermit += 1; + self.schedule_write(page_key); + } + } + } + } + let wal = self.wal.as_ref().unwrap().clone(); + let max_revisions = self.wal_cfg.max_revisions; + self.start_task(async move { + let _ = sem.acquire_many(npermit).await.unwrap(); + wal.lock() + .await + .peel(ring_ids, max_revisions) + .await + .map_err(|_| "WAL errore while pruning") + .unwrap(); + }); + if self.pending.len() >= self.cfg.max_pending { + let (tx, rx) = oneshot::channel(); + self.fc_notifier = Some(tx); + self.fc_blocker = Some(rx); + } + } + } + + async fn process(&mut self, req: BufferCmd, wal_in: &mpsc::Sender<(Vec, AshRecord)>) -> bool { + match req { + BufferCmd::Shutdown => return false, + BufferCmd::InitWAL(rootfd, waldir) => { + if let Err(_) = self.init_wal(rootfd, waldir).await { + panic!("cannot initialize from WAL") + } + } + BufferCmd::GetPage(page_key, tx) => tx + .send(self.pending.get(&page_key).map(|e| e.staging_data.clone())) + .unwrap(), + BufferCmd::WriteBatch(writes, wal_writes) => { + wal_in.send((writes, wal_writes)).await.unwrap(); + } + BufferCmd::CollectAsh(nrecords, tx) => { + // wait to ensure writes are paused for WAL + let ash = self + .wal + .as_ref() + .unwrap() + .clone() + .lock() + .await + .read_recent_records(nrecords, &RecoverPolicy::Strict) + .await + .unwrap() + .into_iter() + .map(AshRecord::deserialize) + .collect(); + tx.send(ash).unwrap(); + } + BufferCmd::RegCachedSpace(space_id, files) => self.file_pools[space_id as usize] = Some(files), + } + true + } + + fn start_task + 'static>(&mut self, fut: F) { + let task_id = self.task_id; + self.task_id += 1; + let tasks = self.tasks.clone(); + self.tasks.borrow_mut().insert( + task_id, + Some(self.local_pool.spawn_local(async move { + fut.await; + tasks.borrow_mut().remove(&task_id); + })), + ); + } + + #[tokio::main(flavor = "current_thread")] + pub async fn run(mut self) { + let wal_in = { + let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered); + let s = unsafe { self.get_longlive_self() }; + self.start_task(s.run_wal_queue(rx)); + tx + }; + self.local_pool + .clone() + .run_until(async { + loop { + if let Some(fc) = self.fc_blocker.take() { + // flow control, wait until ready + fc.await.unwrap(); + } + let req = self.inbound.recv().await.unwrap(); + if !self.process(req, &wal_in).await { + break + } + } + drop(wal_in); + let handles: Vec<_> = self + .tasks + .borrow_mut() + .iter_mut() + .map(|(_, task)| task.take().unwrap()) + .collect(); + for h in handles { + h.await.unwrap(); + } + }) + .await; + } +} + +#[derive(Clone)] +pub struct DiskBufferRequester { + sender: mpsc::Sender, +} + +impl Default for DiskBufferRequester { + fn default() -> Self { + Self { + sender: mpsc::channel(1).0, + } + } +} + +impl DiskBufferRequester { + pub fn new(sender: mpsc::Sender) -> Self { + Self { sender } + } + + pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.sender + .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) + .ok() + .unwrap(); + resp_rx.blocking_recv().unwrap() + } + + pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { + self.sender + .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch)) + .ok() + .unwrap() + } + + pub fn shutdown(&self) { + self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap() + } + + pub fn init_wal(&self, waldir: &str, rootfd: Fd) { + self.sender + .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string())) + .ok() + .unwrap() + } + + pub fn collect_ash(&self, nrecords: usize) -> Vec { + let (resp_tx, resp_rx) = oneshot::channel(); + self.sender + .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx)) + .ok() + .unwrap(); + resp_rx.blocking_recv().unwrap() + } + + pub fn reg_cached_space(&self, space: &CachedSpace) { + let mut inner = space.inner.borrow_mut(); + inner.disk_buffer = self.clone(); + self.sender + .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone())) + .ok() + .unwrap() + } +} + +#[derive(Debug, PartialEq)] +pub enum StoreError { + System(nix::Error), + InitError(String), + // TODO: more error report from the DiskBuffer + //WriterError, +} + +impl From for StoreError { + fn from(e: nix::Error) -> Self { + StoreError::System(e) + } +} diff --git a/tests/db.rs b/tests/db.rs new file mode 100644 index 000000000000..43d90be1f9ed --- /dev/null +++ b/tests/db.rs @@ -0,0 +1,85 @@ +use firewood::db::{DBConfig, WALConfig, DB}; +use std::collections::VecDeque; + +macro_rules! kv_dump { + ($e: ident) => {{ + let mut s = Vec::new(); + $e.kv_dump(&mut s).unwrap(); + String::from_utf8(s).unwrap() + }}; +} + +#[test] +fn test_revisions() { + use rand::{rngs::StdRng, Rng, SeedableRng}; + let cfg = DBConfig::builder() + .meta_ncached_pages(1024) + .meta_ncached_files(128) + .payload_ncached_pages(1024) + .payload_ncached_files(128) + .payload_file_nbit(16) + .payload_regn_nbit(16) + .wal( + WALConfig::builder() + .file_nbit(15) + .block_nbit(8) + .max_revisions(10) + .build(), + ); + + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().gen_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .collect(); + key + }; + for i in 0..10 { + let db = DB::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); + let mut dumped = VecDeque::new(); + for _ in 0..100 { + { + let mut wb = db.new_writebatch(); + let m = rng.borrow_mut().gen_range(1..20); + for _ in 0..m { + let key = keygen(); + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + wb = wb.kv_insert(key, val.to_vec()).unwrap(); + } + wb.commit(); + } + while dumped.len() > 10 { + dumped.pop_back(); + } + dumped.push_front(kv_dump!(db)); + for i in 1..dumped.len() { + let rev = db.get_revision(i, None).unwrap(); + let a = &kv_dump!(rev); + let b = &dumped[i]; + if a != b { + print!("{a}\n{b}"); + panic!("not the same"); + } + } + } + drop(db); + let db = DB::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); + for j in 1..dumped.len() { + let rev = db.get_revision(j, None).unwrap(); + let a = &kv_dump!(rev); + let b = &dumped[j]; + if a != b { + print!("{}\n{}", a, b); + panic!("not the same"); + } + } + println!("i = {i}"); + } +} diff --git a/tests/merkle.rs b/tests/merkle.rs new file mode 100644 index 000000000000..084af2891c25 --- /dev/null +++ b/tests/merkle.rs @@ -0,0 +1,239 @@ +use firewood::merkle::*; +use shale::{MemStore, MummyObj, ObjPtr}; +use std::rc::Rc; + +struct MerkleSetup { + root: ObjPtr, + merkle: Merkle, +} + +impl MerkleSetup { + fn insert>(&mut self, key: K, val: Vec) { + self.merkle.insert(key, val, self.root).unwrap() + } + + fn remove>(&mut self, key: K) { + self.merkle.remove(key, self.root).unwrap(); + } + + fn get>(&self, key: K) -> Option { + self.merkle.get(key, self.root).unwrap() + } + + fn get_mut>(&mut self, key: K) -> Option { + self.merkle.get_mut(key, self.root).unwrap() + } + + fn root_hash(&self) -> Hash { + self.merkle.root_hash::(self.root).unwrap() + } + + fn dump(&self) -> String { + let mut s = Vec::new(); + self.merkle.dump(self.root, &mut s).unwrap(); + String::from_utf8(s).unwrap() + } +} + +fn merkle_setup_test(meta_size: u64, compact_size: u64) -> MerkleSetup { + use shale::{compact::CompactSpaceHeader, PlainMem}; + const RESERVED: u64 = 0x1000; + assert!(meta_size > RESERVED); + assert!(compact_size > RESERVED); + let mem_meta = Rc::new(PlainMem::new(meta_size, 0x0)) as Rc; + let mem_payload = Rc::new(PlainMem::new(compact_size, 0x1)); + let compact_header: ObjPtr = unsafe { ObjPtr::new_from_addr(0x0) }; + + mem_meta.write( + compact_header.addr(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), + ); + + let compact_header = unsafe { + MummyObj::ptr_to_obj(mem_meta.as_ref(), compact_header, shale::compact::CompactHeader::MSIZE).unwrap() + }; + + let cache = shale::ObjCache::new(1); + let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + let mut root = ObjPtr::null(); + Merkle::init_root(&mut root, &space).unwrap(); + MerkleSetup { + root, + merkle: Merkle::new(Box::new(space)), + } +} + +fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( + items: Vec<(K, V)>, meta_size: u64, compact_size: u64, +) -> MerkleSetup { + let mut merkle = merkle_setup_test(meta_size, compact_size); + for (k, v) in items.iter() { + merkle.insert(k, v.as_ref().to_vec()) + } + let merkle_root = &*merkle.root_hash(); + let items_copy = items.clone(); + let reference_root = triehash::trie_root::(items); + println!( + "ours: {}, correct: {}", + hex::encode(merkle_root), + hex::encode(reference_root) + ); + if merkle_root != &reference_root { + for (k, v) in items_copy { + println!("{} => {}", hex::encode(k), hex::encode(v)); + } + println!("{}", merkle.dump()); + panic!(); + } + merkle +} + +#[test] +fn test_root_hash_simple_insertions() { + let items = vec![ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + merkle.dump(); +} + +#[test] +fn test_root_hash_fuzz_insertions() { + use rand::{rngs::StdRng, Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().gen_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .collect(); + key + }; + for _ in 0..10 { + let mut items = Vec::new(); + for _ in 0..10000 { + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + items.push((keygen(), val)); + } + merkle_build_test(items, 0x1000000, 0x1000000); + } +} + +#[test] +fn test_root_hash_reversed_deletions() { + use rand::{rngs::StdRng, Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().gen_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .collect(); + key + }; + for i in 0..1000 { + let mut items = std::collections::HashMap::new(); + for _ in 0..100 { + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + items.insert(keygen(), val); + } + let mut items: Vec<_> = items.into_iter().collect(); + items.sort(); + let mut merkle = merkle_setup_test(0x100000, 0x100000); + let mut hashes = Vec::new(); + let mut dumps = Vec::new(); + for (k, v) in items.iter() { + dumps.push(merkle.dump()); + merkle.insert(k, v.to_vec()); + hashes.push(merkle.root_hash()); + } + hashes.pop(); + println!("----"); + let mut prev_dump = merkle.dump(); + for (((k, _), h), d) in items.iter().rev().zip(hashes.iter().rev()).zip(dumps.iter().rev()) { + merkle.remove(k); + let h0 = merkle.root_hash(); + if *h != h0 { + for (k, _) in items.iter() { + println!("{}", hex::encode(k)); + } + println!("{} != {}", hex::encode(**h), hex::encode(*h0)); + println!("== before {} ===", hex::encode(k)); + print!("{prev_dump}"); + println!("== after {} ===", hex::encode(k)); + print!("{}", merkle.dump()); + println!("== should be ==="); + print!("{d}"); + panic!(); + } + prev_dump = merkle.dump(); + } + println!("i = {i}"); + } +} + +#[test] +fn test_root_hash_random_deletions() { + use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().gen_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .collect(); + key + }; + for i in 0..10 { + let mut items = std::collections::HashMap::new(); + for _ in 0..1000 { + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + items.insert(keygen(), val); + } + let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + items_ordered.sort(); + items_ordered.shuffle(&mut *rng.borrow_mut()); + let mut merkle = merkle_setup_test(0x100000, 0x100000); + for (k, v) in items.iter() { + merkle.insert(k, v.to_vec()); + } + for (k, _) in items_ordered.into_iter() { + assert!(merkle.get(&k).is_some()); + assert!(merkle.get_mut(&k).is_some()); + merkle.remove(&k); + assert!(merkle.get(&k).is_none()); + assert!(merkle.get_mut(&k).is_none()); + items.remove(&k); + for (k, v) in items.iter() { + assert_eq!(&*merkle.get(k).unwrap(), &v[..]); + assert_eq!(&*merkle.get_mut(k).unwrap().get(), &v[..]); + } + let h = triehash::trie_root::, _, _>(items.iter().collect()); + let h0 = merkle.root_hash(); + if &h[..] != &*h0 { + println!("{} != {}", hex::encode(h), hex::encode(*h0)); + } + } + println!("i = {i}"); + } +} From b3455772f75b25ca7c0c2d4c53d114c018737aeb Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 2 Nov 2022 10:41:27 -0400 Subject: [PATCH 0002/1053] lint: fix additional clippy warnings Signed-off-by: Dan Sover --- src/merkle.rs | 25 +++++++++++++------------ src/storage.rs | 14 +++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index a90ea4dc78d6..d430c4d977a5 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -844,7 +844,7 @@ impl Merkle { let branch_ptr = self .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v })))? .as_ptr(); - if prefix.len() > 0 { + if !prefix.is_empty() { self.new_node(Node::new(NodeType::Extension(ExtNode( PartialPath(prefix.to_vec()), branch_ptr, @@ -1024,7 +1024,7 @@ impl Merkle { .unwrap(); let b_inner = b_ref.inner.as_branch().unwrap(); let (b_chd, has_chd) = b_inner.single_child(); - if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.len() < 1 { + if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() { return Ok(()) } deleted.push(b_ref.as_ptr()); @@ -1109,7 +1109,7 @@ impl Merkle { // from: [p: Branch] -> [b]x* // \____[Leaf]x // to: [p: Branch] -> [Leaf/Ext] - let c_ptr = if let None = c_ref.write(|c| { + let c_ptr = if c_ref.write(|c| { (match &mut c.inner { NodeType::Leaf(n) => &mut n.0, NodeType::Extension(n) => &mut n.0, @@ -1118,7 +1118,7 @@ impl Merkle { .0 .insert(0, idx); c.rehash() - }) { + }).is_none() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1205,7 +1205,8 @@ impl Merkle { } _ => unreachable!(), } - Ok(b.rehash()) + b.rehash(); + Ok(()) })() { Err(e) => err = Some(Err(e)), _ => (), @@ -1222,7 +1223,7 @@ impl Merkle { NodeType::Branch(_) => { // from: [Branch] -> [Branch]x -> [Leaf/Ext] // to: [Branch] -> [Leaf/Ext] - let c_ptr = if let None = c_ref.write(|c| { + let c_ptr = if c_ref.write(|c| { match &mut c.inner { NodeType::Leaf(n) => &mut n.0, NodeType::Extension(n) => &mut n.0, @@ -1231,7 +1232,7 @@ impl Merkle { .0 .insert(0, idx); c.rehash() - }) { + }).is_none() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1248,7 +1249,7 @@ impl Merkle { NodeType::Extension(n) => { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] - let c_ptr = if let None = c_ref.write(|c| { + let c_ptr = if c_ref.write(|c| { let mut path = n.0.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { @@ -1259,7 +1260,7 @@ impl Merkle { path.extend(&**path0); *path0 = PartialPath(path); c.rehash() - }) { + }).is_none() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1300,7 +1301,7 @@ impl Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - if &chunks[i..] != &*n.0 { + if chunks[i..] != *n.0 { return Ok(None) } found = Some(n.1.clone()); @@ -1479,7 +1480,7 @@ impl Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - if &chunks[i..] != &*n.0 { + if chunks[i..] != *n.0 { return Ok(None) } return Ok(Some(Ref(u_ref))) @@ -1499,7 +1500,7 @@ impl Merkle { match &u_ref.inner { NodeType::Branch(n) => { - if let Some(_) = n.value.as_ref() { + if n.value.as_ref().is_some() { return Ok(Some(Ref(u_ref))) } } diff --git a/src/storage.rs b/src/storage.rs index 2eb6a9e21405..d6ab0d09ea06 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -60,7 +60,7 @@ impl growthring::wal::Record for AshRecord { let mut bytes = Vec::new(); bytes.extend((self.0.len() as u64).to_le_bytes()); for (space_id, w) in self.0.iter() { - bytes.extend((*space_id as u8).to_le_bytes()); + bytes.extend((*space_id).to_le_bytes()); bytes.extend((w.old.len() as u32).to_le_bytes()); for (sw_old, sw_new) in w.old.iter().zip(w.new.iter()) { bytes.extend(sw_old.offset.to_le_bytes()); @@ -186,7 +186,7 @@ impl StoreDelta { create_dirty_pages!(head, tail); let psize = PAGE_SIZE as usize; - for w in writes.into_iter() { + for w in writes.iter() { let mut l = 0; let mut r = deltas.len(); while r - l > 1 { @@ -206,9 +206,9 @@ impl StoreDelta { deltas[l].data_mut().copy_from_slice(&data[..psize]); data = &data[psize..]; } - if data.len() > 0 { + if !data.is_empty() { l += 1; - deltas[l].data_mut()[..data.len()].copy_from_slice(&data); + deltas[l].data_mut()[..data.len()].copy_from_slice(data); } } Self(deltas) @@ -230,7 +230,7 @@ impl fmt::Debug for StoreRev { for d in self.delta.iter() { write!(f, " 0x{:x}", d.0)?; } - write!(f, ">\n") + writeln!(f, ">") } } @@ -434,7 +434,7 @@ impl MemStore for StoreRevMut { }; for p in s_pid + 1..e_pid { match deltas.get(&p) { - Some(p) => data.extend(&**p), + Some(p) => data.extend(**p), None => data.extend(&self.prev.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?), }; } @@ -610,7 +610,7 @@ impl CachedSpace { impl CachedSpaceInner { fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result, StoreError> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { - return Ok(Box::new((*p).clone())) + return Ok(Box::new((*p))) } let file_nbit = self.files.get_file_nbit(); let file_size = 1 << file_nbit; From e82f8d12313ff7aa18fde211011c8a7a977d4208 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 2 Nov 2022 11:18:27 -0400 Subject: [PATCH 0003/1053] lint: additional clippy fixes Signed-off-by: Dan Sover --- src/db.rs | 12 ++++-- src/merkle.rs | 108 +++++++++++++++++++++++++++---------------------- src/storage.rs | 10 ++--- 3 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/db.rs b/src/db.rs index 44274d155d19..62df34a860f8 100644 --- a/src/db.rs +++ b/src/db.rs @@ -829,20 +829,24 @@ impl<'a> WriteBatch<'a> { if !acc.code.is_null() { blob_stash.free_blob(acc.code).map_err(DBError::Blob)?; } - Ok(acc.set_code( + acc.set_code( Hash(sha3::Keccak256::digest(code).into()), blob_stash .new_blob(Blob::Code(code.to_vec())) .map_err(DBError::Blob)? .as_ptr(), - )) + ); + Ok(()) })?; Ok(self) } /// Set nonce of the account. pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result { - self.change_account(key, |acc, _| Ok(acc.nonce = nonce))?; + self.change_account(key, |acc, _| { + acc.nonce = nonce; + Ok(()) + })?; Ok(self) } @@ -850,7 +854,7 @@ impl<'a> WriteBatch<'a> { pub fn set_state(mut self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { let (header, merkle, _) = self.m.latest.borrow_split(); let mut acc = match merkle.get(key, header.acc_root) { - Ok(Some(r)) => Account::deserialize(&*r), + Ok(Some(r)) => Account::deserialize(&r), Ok(None) => Account::default(), Err(e) => return Err(DBError::Merkle(e)), }; diff --git a/src/merkle.rs b/src/merkle.rs index d430c4d977a5..e63419d5f608 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -106,7 +106,7 @@ impl PartialPath { #[test] fn test_partial_path_encoding() { let check = |steps: &[u8], term| { - let (d, t) = PartialPath::decode(&PartialPath(steps.to_vec()).encode(term)); + let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term)); assert_eq!(d.0, steps); assert_eq!(t, term); }; @@ -264,12 +264,11 @@ enum NodeType { impl NodeType { fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { - let eth_rlp = match &self { + match &self { NodeType::Leaf(n) => n.calc_eth_rlp::(), NodeType::Extension(n) => n.calc_eth_rlp::(store), NodeType::Branch(n) => n.calc_eth_rlp::(store), - }; - eth_rlp + } } } @@ -413,8 +412,7 @@ impl MummyItem for Node { cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( - &*mem - .get_view(addr + META_SIZE + ext_header_size, len) + &mem.get_view(addr + META_SIZE + ext_header_size, len) .ok_or(ShaleError::LinearMemStoreError)?, ) .collect(); @@ -502,7 +500,7 @@ impl MummyItem for Node { match &n.value { Some(val) => { cur.write_all(&(val.len() as u32).to_le_bytes()).unwrap(); - cur.write_all(&val).unwrap(); + cur.write_all(val).unwrap(); } None => { cur.write_all(&u32::MAX.to_le_bytes()).unwrap(); @@ -608,7 +606,7 @@ impl Merkle { } pub fn init_root(root: &mut ObjPtr, store: &dyn ShaleStore) -> Result<(), MerkleError> { - Ok(*root = store + *root = store .put_item( Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], @@ -617,7 +615,8 @@ impl Merkle { Node::max_branch_node_size(), ) .map_err(MerkleError::Shale)? - .as_ptr()) + .as_ptr(); + Ok(()) } pub fn get_store(&self) -> &dyn ShaleStore { @@ -664,7 +663,7 @@ impl Merkle { "{} => {}: ", u, match u_ref.root_hash.get() { - Some(h) => hex::encode(&**h), + Some(h) => hex::encode(**h), None => "".to_string(), } ) @@ -688,11 +687,12 @@ impl Merkle { } pub fn dump(&self, root: ObjPtr, w: &mut dyn Write) -> Result<(), MerkleError> { - Ok(if root.is_null() { + if root.is_null() { write!(w, "").map_err(MerkleError::Format)?; } else { self.dump_(root, w)?; - }) + }; + Ok(()) } fn set_parent<'b>(&self, new_chd: ObjPtr, parents: &mut [(ObjRef<'b, Node>, u8)]) { @@ -774,10 +774,13 @@ impl Merkle { NodeType::Extension(u) => { match (|| { let mut b_ref = self.get_node(u.1)?; - if let None = b_ref.write(|b| { - b.inner.as_branch_mut().unwrap().value = Some(Data(val)); - b.rehash() - }) { + if b_ref + .write(|b| { + b.inner.as_branch_mut().unwrap().value = Some(Data(val)); + b.rehash() + }) + .is_none() + { u.1 = self.new_node(b_ref.clone())?.as_ptr(); deleted.push(b_ref.as_ptr()); } @@ -1109,16 +1112,19 @@ impl Merkle { // from: [p: Branch] -> [b]x* // \____[Leaf]x // to: [p: Branch] -> [Leaf/Ext] - let c_ptr = if c_ref.write(|c| { - (match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), + let c_ptr = if c_ref + .write(|c| { + (match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }) + .0 + .insert(0, idx); + c.rehash() }) - .0 - .insert(0, idx); - c.rehash() - }).is_none() { + .is_none() + { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1223,16 +1229,19 @@ impl Merkle { NodeType::Branch(_) => { // from: [Branch] -> [Branch]x -> [Leaf/Ext] // to: [Branch] -> [Leaf/Ext] - let c_ptr = if c_ref.write(|c| { - match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), - } - .0 - .insert(0, idx); - c.rehash() - }).is_none() { + let c_ptr = if c_ref + .write(|c| { + match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + } + .0 + .insert(0, idx); + c.rehash() + }) + .is_none() + { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1249,18 +1258,21 @@ impl Merkle { NodeType::Extension(n) => { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] - let c_ptr = if c_ref.write(|c| { - let mut path = n.0.clone().into_inner(); - path.push(idx); - let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), - }; - path.extend(&**path0); - *path0 = PartialPath(path); - c.rehash() - }).is_none() { + let c_ptr = if c_ref + .write(|c| { + let mut path = n.0.clone().into_inner(); + path.push(idx); + let path0 = match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }; + path.extend(&**path0); + *path0 = PartialPath(path); + c.rehash() + }) + .is_none() + { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1441,7 +1453,7 @@ impl Merkle { let u_ptr = u_ref.as_ptr(); match &u_ref.inner { NodeType::Branch(n) => { - if let Some(_) = n.value.as_ref() { + if n.value.as_ref().is_some() { drop(u_ref); return Ok(Some(RefMut::new(u_ptr, parents, self))) } diff --git a/src/storage.rs b/src/storage.rs index d6ab0d09ea06..0fbc37bb1d5f 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -524,7 +524,7 @@ fn test_from_ash() { use rand::{rngs::StdRng, Rng, SeedableRng}; let mut rng = StdRng::seed_from_u64(42); let min = rng.gen_range(0..2 * PAGE_SIZE); - let max = rng.gen_range(min + 1 * PAGE_SIZE..min + 100 * PAGE_SIZE); + let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); for _ in 0..2000 { let n = 20; let mut canvas = Vec::new(); @@ -610,7 +610,7 @@ impl CachedSpace { impl CachedSpaceInner { fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result, StoreError> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { - return Ok(Box::new((*p))) + return Ok(Box::new(*p)) } let file_nbit = self.files.get_file_nbit(); let file_size = 1 << file_nbit; @@ -742,7 +742,7 @@ impl FilePool { rootfd, }; let f0 = s.get_file(0)?; - if let Err(_) = flock(f0.get_fd(), FlockArg::LockExclusiveNonblock) { + if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() { return Err(StoreError::InitError("the store is busy".into())) } Ok(s) @@ -908,7 +908,7 @@ impl DiskBuffer { match self.pending.entry(page_key) { Occupied(mut e) => { let slot = e.get_mut(); - for notifier in std::mem::replace(&mut slot.writing_notifiers, Vec::new()) { + for notifier in std::mem::take(&mut slot.writing_notifiers) { notifier.add_permits(1) } if slot.staging_notifiers.is_empty() { @@ -957,7 +957,7 @@ impl DiskBuffer { let fid = offset >> file_nbit; nix::sys::uio::pwrite( file_pool.get_file(fid).map_err(|_| ())?.get_fd(), - &*data, + &data, (offset & file_mask) as nix::libc::off_t, ) .map_err(|_| ())?; From 21530047929b2fd6363c268a11a3a318105ac184 Mon Sep 17 00:00:00 2001 From: exdx Date: Thu, 3 Nov 2022 13:11:04 -0400 Subject: [PATCH 0004/1053] Create dependabot.yml --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..6c707b8e846b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + time: "05:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 10 From 8f64c0020214ebe5ef490ee65afd9c3adab91259 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:13:03 +0000 Subject: [PATCH 0005/1053] Update typed-builder requirement from 0.10.0 to 0.11.0 Updates the requirements on [typed-builder](https://github.com/idanarye/rust-typed-builder) to permit the latest version. - [Release notes](https://github.com/idanarye/rust-typed-builder/releases) - [Changelog](https://github.com/idanarye/rust-typed-builder/blob/master/CHANGELOG.md) - [Commits](https://github.com/idanarye/rust-typed-builder/commits) --- updated-dependencies: - dependency-name: typed-builder dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e87402864be3..bb773ecafab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ hex = "0.4.3" lru = "0.8.0" libaio-futures = "0.2.2" nix = "0.25.0" -typed-builder = "0.10.0" +typed-builder = "0.11.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } shale = "0.1.7" growth-ring = "0.3.0" From c036f6938a75475dfcd344b2190c8dde2c0ebdbb Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Tue, 8 Nov 2022 02:37:22 +0000 Subject: [PATCH 0006/1053] Merkle tree proof generation and verification --- Cargo.toml | 4 +- src/db.rs | 2 +- src/file.rs | 2 +- src/lib.rs | 1 + src/merkle.rs | 104 +++++++++++++++++++++++++++++++++++---- src/proof.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ src/storage.rs | 4 +- tests/db.rs | 2 +- tests/merkle.rs | 104 ++++++++++++++++++++++++++++++++++++++- 9 files changed, 332 insertions(+), 17 deletions(-) create mode 100644 src/proof.rs diff --git a/Cargo.toml b/Cargo.toml index bb773ecafab8..17607d46234f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] enum-as-inner = "0.5.1" parking_lot = "0.12.1" -rlp = "0.5.1" +rlp = "0.5.2" sha3 = "0.10.2" once_cell = "1.13.1" hex = "0.4.3" @@ -19,6 +19,8 @@ shale = "0.1.7" growth-ring = "0.3.0" futures = "0.3.24" primitive-types = { verison = "0.12.0", features = ["impl-rlp"] } +serde = { version = "1.0", features = ["derive"] } +bincode = "1.2.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/db.rs b/src/db.rs index 62df34a860f8..720eb86f579b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -278,7 +278,7 @@ impl DBRev { Ok(None) => Account::default(), Err(e) => return Err(DBError::Merkle(e)), }; - writeln!(w, "{:?}", acc).unwrap(); + writeln!(w, "{acc:?}").unwrap(); if !acc.root.is_null() { self.merkle.dump(acc.root, w).map_err(DBError::Merkle)?; } diff --git a/src/file.rs b/src/file.rs index 7d0498171d57..f4706a8aa26a 100644 --- a/src/file.rs +++ b/src/file.rs @@ -35,7 +35,7 @@ impl File { } fn _get_fname(fid: u64) -> String { - format!("{:08x}.fw", fid) + format!("{fid:08x}.fw") } pub fn new(fid: u64, flen: u64, rootfd: Fd) -> nix::Result { diff --git a/src/lib.rs b/src/lib.rs index c8761fba29a5..4cfdb0f145f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,4 +199,5 @@ pub(crate) mod account; pub mod db; pub(crate) mod file; pub mod merkle; +pub mod proof; pub(crate) mod storage; diff --git a/src/merkle.rs b/src/merkle.rs index e63419d5f608..4235a6c86f1b 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -1,9 +1,12 @@ +use crate::proof::Proof; + use enum_as_inner::EnumAsInner; use once_cell::unsync::OnceCell; use sha3::Digest; use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; use std::cell::Cell; +use std::collections::HashMap; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; @@ -48,7 +51,7 @@ impl MummyItem for Hash { /// PartialPath keeps a list of nibbles to represent a path on the MPT. #[derive(PartialEq, Eq, Clone)] -struct PartialPath(Vec); +pub struct PartialPath(Vec); impl Debug for PartialPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -67,7 +70,7 @@ impl std::ops::Deref for PartialPath { } impl PartialPath { - fn into_inner(self) -> Vec { + pub fn into_inner(self) -> Vec { self.0 } @@ -79,7 +82,7 @@ impl PartialPath { res } - fn decode>(raw: R) -> (Self, bool) { + pub fn decode>(raw: R) -> (Self, bool) { let raw = raw.as_ref(); let term = raw[0] > 1; let odd_len = raw[0] & 1; @@ -144,7 +147,7 @@ impl Debug for BranchNode { write!(f, "[Branch")?; for (i, c) in self.chd.iter().enumerate() { if let Some(c) = c { - write!(f, " ({:x} {})", i, c)?; + write!(f, " ({i:x} {c})")?; } } write!( @@ -535,7 +538,7 @@ fn test_merkle_node_encoding() { let mem = shale::PlainMem::new(bytes.len() as u64, 0x0); mem.write(0, &bytes); - println!("{:?}", bytes); + println!("{bytes:?}"); let node_ = Node::hydrate(0, &mem).unwrap(); assert!(node == node_); }; @@ -670,16 +673,16 @@ impl Merkle { .map_err(MerkleError::Format)?; match &u_ref.inner { NodeType::Branch(n) => { - writeln!(w, "{:?}", n).map_err(MerkleError::Format)?; + writeln!(w, "{n:?}").map_err(MerkleError::Format)?; for c in n.chd.iter() { if let Some(c) = c { self.dump_(*c, w)? } } } - NodeType::Leaf(n) => writeln!(w, "{:?}", n).unwrap(), + NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), NodeType::Extension(n) => { - writeln!(w, "{:?}", n).map_err(MerkleError::Format)?; + writeln!(w, "{n:?}").map_err(MerkleError::Format)?; self.dump_(n.1, w)? } } @@ -1470,6 +1473,87 @@ impl Merkle { Ok(None) } + /// Constructs a merkle proof for key. The result contains all encoded nodes + /// on the path to the value at key. The value itself is also included in the + /// last node and can be retrieved by verifying the proof. + /// + /// If the trie does not contain a value for key, the returned proof contains + /// all nodes of the longest existing prefix of the key, ending with the node + /// that proves the absence of the key (at least the root node). + pub fn prove, T: ValueTransformer>(&self, key: K, root: ObjPtr) -> Result { + let mut chunks = Vec::new(); + chunks.extend(to_nibbles(key.as_ref())); + + let mut proofs: HashMap<[u8; 32], Vec> = HashMap::new(); + if root.is_null() { + return Ok(Proof(proofs)) + } + + // Skip the sentinel root + let root = self + .get_node(root)? + .inner + .as_branch() + .ok_or(MerkleError::NotBranchNode)? + .chd[0]; + let mut u_ref = match root { + Some(root) => self.get_node(root)?, + None => return Ok(Proof(proofs)), + }; + + let mut nskip = 0; + let mut nodes: Vec> = Vec::new(); + for (i, nib) in chunks.iter().enumerate() { + if nskip > 0 { + nskip -= 1; + continue + } + nodes.push(u_ref.as_ptr()); + let next_ptr: ObjPtr = match &u_ref.inner { + NodeType::Branch(n) => match n.chd[*nib as usize] { + Some(c) => c, + None => break, + }, + NodeType::Leaf(_) => break, + NodeType::Extension(n) => { + let n_path = &*n.0; + let remaining_path = &chunks[i..]; + if remaining_path.len() < n_path.len() || &remaining_path[..n_path.len()] != n_path { + break + } else { + nskip = n_path.len() - 1; + n.1 + } + } + }; + u_ref = self.get_node(next_ptr)?; + } + + match &u_ref.inner { + NodeType::Branch(n) => { + if n.value.as_ref().is_some() { + nodes.push(u_ref.as_ptr()); + } + } + NodeType::Leaf(n) => { + if n.0.len() == 0 { + nodes.push(u_ref.as_ptr()); + } + } + _ => (), + } + + drop(u_ref); + // Get the hashes of the nodes. + for node in nodes { + let node = self.get_node(node)?; + let rlp = node.get_eth_rlp::(self.store.as_ref()).clone(); + let hash: [u8; 32] = sha3::Keccak256::digest(rlp).into(); + proofs.insert(hash, rlp.to_vec()); + } + Ok(Proof(proofs)) + } + pub fn get>(&self, key: K, root: ObjPtr) -> Result, MerkleError> { let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); @@ -1603,11 +1687,11 @@ impl ValueTransformer for IdTrans { } } -fn to_nibbles<'a>(bytes: &'a [u8]) -> impl Iterator + 'a { +pub fn to_nibbles<'a>(bytes: &'a [u8]) -> impl Iterator + 'a { bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) } -fn from_nibbles<'a>(nibbles: &'a [u8]) -> impl Iterator + 'a { +pub fn from_nibbles<'a>(nibbles: &'a [u8]) -> impl Iterator + 'a { assert!(nibbles.len() & 1 == 0); nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 000000000000..a955541d3719 --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,126 @@ +use crate::merkle::{to_nibbles, PartialPath}; + +use serde::{Deserialize, Serialize}; +use sha3::Digest; + +use std::collections::HashMap; + +/// Hash -> RLP encoding map +#[derive(Serialize, Deserialize)] +pub struct Proof(pub HashMap<[u8; 32], Vec>); + +#[derive(Debug)] +pub enum ProofError { + DecodeError, + NoSuchNode, + ProofNodeMissing, +} + +/// SubProof contains the RLP encoding and the hash value of a node that maps +/// to a single proof step. If reaches an end step during proof verification, +/// the hash value will be none, and the RLP encoding will be the value of the +/// node. +pub struct SubProof { + rlp: Vec, + hash: Option<[u8; 32]>, +} + +impl Proof { + /// verify_proof checks merkle proofs. The given proof must contain the value for + /// key in a trie with the given root hash. VerifyProof returns an error if the + /// proof contains invalid trie nodes or the wrong value. + pub fn verify_proof>(&self, key: K, root_hash: [u8; 32]) -> Result>, ProofError> { + let mut chunks = Vec::new(); + chunks.extend(to_nibbles(key.as_ref())); + + let mut cur_key: &[u8] = &chunks; + let mut cur_hash = root_hash; + let proofs_map = &self.0; + let mut index = 0; + loop { + let cur_proof = proofs_map.get(&cur_hash).ok_or(ProofError::ProofNodeMissing)?; + let (sub_proof, size) = self.locate_subproof(cur_key, cur_proof)?; + index += size; + match sub_proof { + Some(p) => { + // Return when reaching the end of the key. + if index == chunks.len() { + return Ok(Some(p.rlp)) + } + + // The trie doesn't contain the key. + if p.hash.is_none() { + return Ok(None) + } + cur_hash = p.hash.unwrap(); + cur_key = &chunks[index..]; + } + // The trie doesn't contain the key. + None => return Ok(None), + } + } + } + + fn locate_subproof(&self, key: &[u8], buf: &Vec) -> Result<(Option, usize), ProofError> { + let rlp = rlp::Rlp::new(buf); + let size = rlp.item_count().unwrap(); + match size { + 2 => { + let cur_key_path: Vec<_> = to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let (cur_key_path, term) = PartialPath::decode(cur_key_path); + let cur_key = cur_key_path.into_inner(); + + let rlp = rlp.at(1).unwrap(); + let data = if rlp.is_data() { + rlp.as_val::>().unwrap() + } else { + rlp.as_raw().to_vec() + }; + + // Check if the key of current node match with the given key. + if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { + return Ok((None, 0)) + } + if term { + Ok((Some(SubProof { rlp: data, hash: None }), cur_key.len())) + } else { + self.generate_subproof(data).map(|subproof| (subproof, cur_key.len())) + } + } + 17 => { + if key.is_empty() { + return Err(ProofError::NoSuchNode) + } + let index = key[0]; + let rlp = rlp.at(index as usize).unwrap(); + let data = if rlp.is_data() { + rlp.as_val::>().unwrap() + } else { + rlp.as_raw().to_vec() + }; + self.generate_subproof(data).map(|subproof| (subproof, 1)) + } + _ => Err(ProofError::DecodeError), + } + } + + fn generate_subproof(&self, data: Vec) -> Result, ProofError> { + let data_len = data.len(); + if data_len == 32 { + let sub_hash: &[u8] = &data; + let sub_hash = sub_hash.try_into().unwrap(); + Ok(Some(SubProof { + rlp: data, + hash: Some(sub_hash), + })) + } else if data_len < 32 { + let sub_hash = sha3::Keccak256::digest(&data).into(); + Ok(Some(SubProof { + rlp: data, + hash: Some(sub_hash), + })) + } else { + Err(ProofError::DecodeError) + } + } +} diff --git a/src/storage.rs b/src/storage.rs index 0fbc37bb1d5f..77b04425d476 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -537,12 +537,12 @@ fn test_from_ash() { for (idx, byte) in (l..r).zip(data.iter()) { canvas[(idx - min) as usize] = *byte; } - println!("[0x{:x}, 0x{:x})", l, r); + println!("[0x{l:x}, 0x{r:x})"); writes.push(SpaceWrite { offset: l, data }); } let z = Rc::new(ZeroStore::new()); let rev = StoreRevShared::from_ash(z, &writes); - println!("{:?}", rev); + println!("{rev:?}"); assert_eq!(&**rev.get_view(min, max - min).unwrap(), &canvas); for _ in 0..2 * n { let l = rng.gen_range(min..max); diff --git a/tests/db.rs b/tests/db.rs index 43d90be1f9ed..259523a47cf4 100644 --- a/tests/db.rs +++ b/tests/db.rs @@ -76,7 +76,7 @@ fn test_revisions() { let a = &kv_dump!(rev); let b = &dumped[j]; if a != b { - print!("{}\n{}", a, b); + print!("{a}\n{b}"); panic!("not the same"); } } diff --git a/tests/merkle.rs b/tests/merkle.rs index 084af2891c25..f8ebc821de16 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -1,4 +1,4 @@ -use firewood::merkle::*; +use firewood::{merkle::*, proof::Proof}; use shale::{MemStore, MummyObj, ObjPtr}; use std::rc::Rc; @@ -33,6 +33,15 @@ impl MerkleSetup { self.merkle.dump(self.root, &mut s).unwrap(); String::from_utf8(s).unwrap() } + + fn prove>(&self, key: K) -> Proof { + self.merkle.prove::(key, self.root).unwrap() + } + + fn verify_proof>(&self, key: K, proof: &Proof) -> Option> { + let hash: [u8; 32] = self.root_hash().0; + proof.verify_proof(key, hash).unwrap() + } } fn merkle_setup_test(meta_size: u64, compact_size: u64) -> MerkleSetup { @@ -237,3 +246,96 @@ fn test_root_hash_random_deletions() { println!("i = {i}"); } } + +#[test] +fn test_one_element_proof() { + let items = vec![("k", "v")]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "k"; + + let proof = merkle.prove(key); + assert!(!proof.0.is_empty()); + + let verify_proof = merkle.verify_proof(key, &proof); + assert!(verify_proof.is_some()); +} + +#[test] +/// Verify the proofs that end with leaf node with the given key. +fn test_proof_end_with_leaf() { + let items = vec![ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "doe"; + + let proof = merkle.prove(key); + assert!(!proof.0.is_empty()); + + let verify_proof = merkle.verify_proof(key, &proof); + assert!(verify_proof.is_some()); +} + +#[test] +/// Verify the proofs that end with branch node with the given key. +fn test_proof_end_with_branch() { + let items = vec![("d", "verb"), ("do", "verb"), ("doe", "reindeer"), ("e", "coin")]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "d"; + + let proof = merkle.prove(key); + assert!(!proof.0.is_empty()); + + let verify_proof = merkle.verify_proof(key, &proof); + assert!(verify_proof.is_some()); +} + +#[test] +#[should_panic] +fn test_bad_proof() { + let items = vec![ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "ddd"; + + let mut proof = merkle.prove(key); + assert!(!proof.0.is_empty()); + + // Delete an entry from the generated proofs. + let new_proof = Proof(proof.0.drain().take(1).collect()); + merkle.verify_proof(key, &new_proof); +} + +#[test] +fn test_missing_key_proof() { + let items = vec![("k", "v")]; + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "x"; + + let proof = merkle.prove(key); + assert!(!proof.0.is_empty()); + + let verify_proof = merkle.verify_proof(key, &proof); + assert!(verify_proof.is_none()); +} + +#[test] +fn test_empty_tree_proof() { + let items: Vec<(&str, &str)> = Vec::new(); + let merkle = merkle_build_test(items, 0x10000, 0x10000); + let key = "x"; + + let proof = merkle.prove(key); + assert!(proof.0.is_empty()); +} From 796869791d709c24fbbe53f4f8abc3860137fa3e Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Wed, 9 Nov 2022 16:29:49 -0800 Subject: [PATCH 0007/1053] nits --- src/merkle.rs | 6 +++++- src/proof.rs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index 4235a6c86f1b..1c95e26454e7 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -1480,7 +1480,11 @@ impl Merkle { /// If the trie does not contain a value for key, the returned proof contains /// all nodes of the longest existing prefix of the key, ending with the node /// that proves the absence of the key (at least the root node). - pub fn prove, T: ValueTransformer>(&self, key: K, root: ObjPtr) -> Result { + pub fn prove(&self, key: K, root: ObjPtr) -> Result + where + K: AsRef<[u8]>, + T: ValueTransformer, + { let mut chunks = Vec::new(); chunks.extend(to_nibbles(key.as_ref())); diff --git a/src/proof.rs b/src/proof.rs index a955541d3719..7831cd7b925e 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -16,6 +16,9 @@ pub enum ProofError { ProofNodeMissing, } +const EXT_NODE_SIZE: usize = 2; +const BRANCH_NODE_SIZE: usize = 17; + /// SubProof contains the RLP encoding and the hash value of a node that maps /// to a single proof step. If reaches an end step during proof verification, /// the hash value will be none, and the RLP encoding will be the value of the @@ -65,7 +68,7 @@ impl Proof { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count().unwrap(); match size { - 2 => { + EXT_NODE_SIZE => { let cur_key_path: Vec<_> = to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); @@ -87,7 +90,7 @@ impl Proof { self.generate_subproof(data).map(|subproof| (subproof, cur_key.len())) } } - 17 => { + BRANCH_NODE_SIZE => { if key.is_empty() { return Err(ProofError::NoSuchNode) } From 2c7ec8c4e7a644d9387f3c408f8c30f31ab8aa48 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 14 Nov 2022 14:16:48 -0500 Subject: [PATCH 0008/1053] lint: Fix additional clippy warnings Signed-off-by: Dan Sover --- src/merkle.rs | 28 ++++++++++++---------------- src/proof.rs | 2 +- src/storage.rs | 4 ++-- tests/db.rs | 4 ++-- tests/merkle.rs | 2 +- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index 1c95e26454e7..9c715d3a6abd 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -544,8 +544,8 @@ fn test_merkle_node_encoding() { }; let chd0 = [None; NBRANCH]; let mut chd1 = chd0; - for i in 0..NBRANCH / 2 { - chd1[i] = Some(unsafe { ObjPtr::new_from_addr(0xa) }); + for node in chd1.iter_mut().take(NBRANCH / 2) { + *node = Some(unsafe { ObjPtr::new_from_addr(0xa) }); } for node in [ Node::new_from_hash( @@ -674,10 +674,8 @@ impl Merkle { match &u_ref.inner { NodeType::Branch(n) => { writeln!(w, "{n:?}").map_err(MerkleError::Format)?; - for c in n.chd.iter() { - if let Some(c) = c { - self.dump_(*c, w)? - } + for c in n.chd.iter().flatten() { + self.dump_(*c, w)? } } NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), @@ -800,7 +798,7 @@ impl Merkle { parents, deleted ); - return err.unwrap_or(Ok(None)) + return err.unwrap_or_else(|| Ok(None)) } let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() { // key path is a prefix of the path to u @@ -948,7 +946,7 @@ impl Merkle { let mut u = u_ref.take().unwrap(); write_node!( self, - &mut u, + u, |u| { info = match &mut u.inner { NodeType::Branch(n) => { @@ -1384,10 +1382,8 @@ impl Merkle { let u_ref = self.get_node(u)?; match &u_ref.inner { NodeType::Branch(n) => { - for c in n.chd.iter() { - if let Some(c) = c { - self.remove_tree_(*c, deleted)? - } + for c in n.chd.iter().flatten() { + self.remove_tree_(*c, deleted)? } } NodeType::Leaf(_) => (), @@ -1433,7 +1429,7 @@ impl Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - if &chunks[i..] != &*n.0 { + if chunks[i..] != *n.0 { return Ok(None) } drop(u_ref); @@ -1644,7 +1640,7 @@ impl<'a> RefMut<'a> { Self { ptr, parents, merkle } } - pub fn get<'b>(&'b self) -> Ref<'b> { + pub fn get(&self) -> Ref { Ref(self.merkle.get_node(self.ptr).unwrap()) } @@ -1691,11 +1687,11 @@ impl ValueTransformer for IdTrans { } } -pub fn to_nibbles<'a>(bytes: &'a [u8]) -> impl Iterator + 'a { +pub fn to_nibbles(bytes: &[u8]) -> impl Iterator + '_ { bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) } -pub fn from_nibbles<'a>(nibbles: &'a [u8]) -> impl Iterator + 'a { +pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { assert!(nibbles.len() & 1 == 0); nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } diff --git a/src/proof.rs b/src/proof.rs index 7831cd7b925e..3efdaf8b0931 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -64,7 +64,7 @@ impl Proof { } } - fn locate_subproof(&self, key: &[u8], buf: &Vec) -> Result<(Option, usize), ProofError> { + fn locate_subproof(&self, key: &[u8], buf: &[u8]) -> Result<(Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count().unwrap(); match size { diff --git a/src/storage.rs b/src/storage.rs index 77b04425d476..043701b40af6 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -666,14 +666,14 @@ struct PageRef { store: CachedSpace, } -impl<'a> std::ops::Deref for PageRef { +impl std::ops::Deref for PageRef { type Target = [u8]; fn deref(&self) -> &[u8] { self.data } } -impl<'a> std::ops::DerefMut for PageRef { +impl std::ops::DerefMut for PageRef { fn deref_mut(&mut self) -> &mut [u8] { self.data } diff --git a/tests/db.rs b/tests/db.rs index 259523a47cf4..32a3f01bc7da 100644 --- a/tests/db.rs +++ b/tests/db.rs @@ -59,7 +59,7 @@ fn test_revisions() { dumped.pop_back(); } dumped.push_front(kv_dump!(db)); - for i in 1..dumped.len() { + for (i, _) in dumped.iter().enumerate().skip(1) { let rev = db.get_revision(i, None).unwrap(); let a = &kv_dump!(rev); let b = &dumped[i]; @@ -71,7 +71,7 @@ fn test_revisions() { } drop(db); let db = DB::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); - for j in 1..dumped.len() { + for (j, _) in dumped.iter().enumerate().skip(1) { let rev = db.get_revision(j, None).unwrap(); let a = &kv_dump!(rev); let b = &dumped[j]; diff --git a/tests/merkle.rs b/tests/merkle.rs index f8ebc821de16..ed247c786253 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -239,7 +239,7 @@ fn test_root_hash_random_deletions() { } let h = triehash::trie_root::, _, _>(items.iter().collect()); let h0 = merkle.root_hash(); - if &h[..] != &*h0 { + if h[..] != *h0 { println!("{} != {}", hex::encode(h), hex::encode(*h0)); } } From 06e2fc092dad9bcee7696cf3980c129bc912bc49 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 16 Nov 2022 14:48:33 -0500 Subject: [PATCH 0009/1053] fix: clippy linting Signed-off-by: Dan Sover --- src/merkle.rs | 12 +++++------- src/proof.rs | 32 +++++++++++++++++--------------- src/storage.rs | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index 9c715d3a6abd..e5a90539f636 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -773,7 +773,7 @@ impl Merkle { match &mut u.inner { NodeType::Leaf(u) => u.1 = Data(val), NodeType::Extension(u) => { - match (|| { + if let Err(e) = (|| { let mut b_ref = self.get_node(u.1)?; if b_ref .write(|b| { @@ -787,8 +787,7 @@ impl Merkle { } Ok(()) })() { - Err(e) => err = Some(Err(e)), - _ => (), + err = Some(Err(e)) } } _ => unreachable!(), @@ -1191,7 +1190,7 @@ impl Merkle { self, b_ref, |b| { - match (|| { + if let Err(e) = (|| { match &mut b.inner { NodeType::Branch(n) => { // from: [Branch] -> [Branch]x -> [Branch] @@ -1215,8 +1214,7 @@ impl Merkle { b.rehash(); Ok(()) })() { - Err(e) => err = Some(Err(e)), - _ => (), + err = Some(Err(e)) } }, parents, @@ -1547,7 +1545,7 @@ impl Merkle { // Get the hashes of the nodes. for node in nodes { let node = self.get_node(node)?; - let rlp = node.get_eth_rlp::(self.store.as_ref()).clone(); + let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); let hash: [u8; 32] = sha3::Keccak256::digest(rlp).into(); proofs.insert(hash, rlp.to_vec()); } diff --git a/src/proof.rs b/src/proof.rs index 3efdaf8b0931..c3c264967eaf 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -109,21 +109,23 @@ impl Proof { fn generate_subproof(&self, data: Vec) -> Result, ProofError> { let data_len = data.len(); - if data_len == 32 { - let sub_hash: &[u8] = &data; - let sub_hash = sub_hash.try_into().unwrap(); - Ok(Some(SubProof { - rlp: data, - hash: Some(sub_hash), - })) - } else if data_len < 32 { - let sub_hash = sha3::Keccak256::digest(&data).into(); - Ok(Some(SubProof { - rlp: data, - hash: Some(sub_hash), - })) - } else { - Err(ProofError::DecodeError) + match data_len { + 32 => { + let sub_hash: &[u8] = &data; + let sub_hash = sub_hash.try_into().unwrap(); + Ok(Some(SubProof { + rlp: data, + hash: Some(sub_hash), + })) + } + 0..=31 => { + let sub_hash = sha3::Keccak256::digest(&data).into(); + Ok(Some(SubProof { + rlp: data, + hash: Some(sub_hash), + })) + } + _ => Err(ProofError::DecodeError), } } } diff --git a/src/storage.rs b/src/storage.rs index 043701b40af6..5ba3bf4e9bb4 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1046,7 +1046,7 @@ impl DiskBuffer { match req { BufferCmd::Shutdown => return false, BufferCmd::InitWAL(rootfd, waldir) => { - if let Err(_) = self.init_wal(rootfd, waldir).await { + if (self.init_wal(rootfd, waldir).await).is_err() { panic!("cannot initialize from WAL") } } From efbebeaf5b40caefd0828cdb6bb79d4d3e4ea993 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Thu, 17 Nov 2022 15:18:01 -0500 Subject: [PATCH 0010/1053] lint: Fix outstanding lint issues Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 2 +- src/merkle.rs | 9 +++++++-- src/storage.rs | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 196eab6622e8..c8d57cffec44 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: - name: Lint run: cargo fmt && git diff --exit-code - name: Clippy - run: cargo clippy --fix --no-deps && git diff --exit-code + run: cargo clippy --fix -- -Dwarnings build: runs-on: ubuntu-latest diff --git a/src/merkle.rs b/src/merkle.rs index e5a90539f636..88b86a1d26be 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -281,8 +281,8 @@ impl Node { const LEAF_NODE: u8 = 0x2; fn max_branch_node_size() -> u64 { - const MAX_SIZE: OnceCell = OnceCell::new(); - *MAX_SIZE.get_or_init(|| { + let max_size: OnceCell = OnceCell::new(); + *max_size.get_or_init(|| { Self { root_hash: OnceCell::new(), eth_rlp_long: OnceCell::new(), @@ -710,6 +710,7 @@ impl Merkle { .unwrap(); } + #[allow(clippy::too_many_arguments)] fn split<'b>( &self, mut u_ref: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], rem_path: &[u8], n_path: Vec, n_value: Option, val: Vec, deleted: &mut Vec>, @@ -770,6 +771,7 @@ impl Merkle { self, u_ref, |u| { + #[allow(clippy::blocks_in_if_conditions)] match &mut u.inner { NodeType::Leaf(u) => u.1 = Data(val), NodeType::Extension(u) => { @@ -1105,6 +1107,7 @@ impl Merkle { } } NodeType::Leaf(_) | NodeType::Extension(_) => { + #[allow(clippy::blocks_in_if_conditions)] match &p_ref.inner { NodeType::Branch(_) => { // ____[Leaf/Ext] @@ -1228,6 +1231,7 @@ impl Merkle { NodeType::Branch(_) => { // from: [Branch] -> [Branch]x -> [Leaf/Ext] // to: [Branch] -> [Leaf/Ext] + #[allow(clippy::blocks_in_if_conditions)] let c_ptr = if c_ref .write(|c| { match &mut c.inner { @@ -1257,6 +1261,7 @@ impl Merkle { NodeType::Extension(n) => { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] + #[allow(clippy::blocks_in_if_conditions)] let c_ptr = if c_ref .write(|c| { let mut path = n.0.clone().into_inner(); diff --git a/src/storage.rs b/src/storage.rs index 5ba3bf4e9bb4..265ee1e36de2 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -74,6 +74,7 @@ impl growthring::wal::Record for AshRecord { } impl AshRecord { + #[allow(clippy::boxed_local)] fn deserialize(raw: growthring::wal::WALBytes) -> Self { let mut r = &raw[..]; let len = u64::from_le_bytes(r[..8].try_into().unwrap()); From 50e55abf59e46f81732642e7028420b74a934946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:18:26 +0000 Subject: [PATCH 0011/1053] build(deps): update nix requirement from 0.25.0 to 0.26.1 Updates the requirements on [nix](https://github.com/nix-rust/nix) to permit the latest version. - [Release notes](https://github.com/nix-rust/nix/releases) - [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/compare/v0.25.0...v0.26.1) --- updated-dependencies: - dependency-name: nix dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 17607d46234f..809964edf855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ once_cell = "1.13.1" hex = "0.4.3" lru = "0.8.0" libaio-futures = "0.2.2" -nix = "0.25.0" +nix = "0.26.1" typed-builder = "0.11.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } shale = "0.1.7" From 50656e3fdc1f13a9c2ac39dc6d8af162cbc3ce37 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 28 Nov 2022 15:55:01 -0500 Subject: [PATCH 0012/1053] *: Update version to 0.0.1 The correct version should be reflected in the Cargo.toml file. When a release is published, the crate will automatically have the same version as the version in the Cargo.toml file. Since we are planning to release an alpha 0.0.1 version, this should be the version in the Cargo.toml file. Signed-off-by: Dan Sover --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 809964edf855..208a6846017d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.1.0" +version = "0.0.1" edition = "2021" [dependencies] From 419d87a14a2a094bd81799f32dd387b72cee1064 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 21 Nov 2022 15:30:13 -0500 Subject: [PATCH 0013/1053] ci: Add release and publish GH Actions Signed-off-by: Dan Sover --- .github/workflows/publish.yaml | 23 +++++++++++++++++++++++ .github/workflows/release.yaml | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/publish.yaml create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000000..23aec6491ad3 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,23 @@ +name: publish + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + publish-firewood-crate: + name: firewood-lib + runs-on: ubuntu-latest + if: "startsWith(github.event.release.tag_name, 'v')" + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: publish firewood crate + continue-on-error: true + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000000..e5d9c8f3f7a3 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,18 @@ +name: release + +on: + push: + tags: + - "v*.*.*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Release + uses: softprops/action-gh-release@v1 + with: + draft: true + generate_release_notes: true From 6c3368cd29184eadc956f12ca5b1f5394b12f3e8 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 30 Nov 2022 14:53:53 -0500 Subject: [PATCH 0014/1053] ci: Update batch sizes in ci e2e job Updates the nbatch and batch_size parameters to provide a more realistic setting based on production blockchain database load. Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c8d57cffec44..e4d47602700a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -61,6 +61,6 @@ jobs: - name: Run dump example run: cargo run --example dump - name: Run benchmark example - run: cargo run --example benchmark -- --nbatch 10000 --batch-size 10 + run: cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - name: Run rev example run: cargo run --example rev From 918c869b561945d4eb9f571268b013cbb7043b54 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Wed, 30 Nov 2022 15:50:32 -0500 Subject: [PATCH 0015/1053] examples: add usage examples Signed-off-by: Sam Batschelet --- examples/benchmark.rs | 1 + examples/dump.rs | 1 + examples/rev.rs | 1 + examples/simple.rs | 1 + 4 files changed, 4 insertions(+) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 934ecc958b03..99925177b95f 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -15,6 +15,7 @@ struct Args { no_root_hash: bool, } +/// cargo run --example benchmark -- --nbatch 100 --batch-size 1000 fn main() { let args = Args::parse(); diff --git a/examples/dump.rs b/examples/dump.rs index 39b8324ee348..7e829e7e1968 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -1,6 +1,7 @@ use clap::{command, Arg, ArgMatches}; use firewood::db::{DBConfig, DBError, WALConfig, DB}; +/// cargo run --example dump benchmark_db/ fn main() { let matches = command!() .arg(Arg::new("INPUT").help("db path name").required(false).index(1)) diff --git a/examples/rev.rs b/examples/rev.rs index 47f23c1b3069..010ed1c2f186 100644 --- a/examples/rev.rs +++ b/examples/rev.rs @@ -1,5 +1,6 @@ use firewood::db::{DBConfig, WALConfig, DB}; +/// cargo run --example rev fn main() { let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); { diff --git a/examples/simple.rs b/examples/simple.rs index 38776135c062..e4282bc62db1 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -22,6 +22,7 @@ fn print_states(db: &DB) { } } +/// cargo run --example simple fn main() { let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); { From 13652119a39ddf045315416fbcddd4d02a53703c Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Thu, 1 Dec 2022 15:10:00 -0500 Subject: [PATCH 0016/1053] ci: Add docs linter to strengthen firewood documentation Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e4d47602700a..464e22eb621f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,3 +64,16 @@ jobs: run: cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - name: Run rev example run: cargo run --example rev + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Lint intra docs links + run: cargo rustdoc -p firewood -- -D rustdoc::broken-intra-doc-links + - name: Lint missing crate-level docs + run: cargo rustdoc -p firewood -- -D rustdoc::missing-crate-level-docs From dce50812c6137df6288d119ea62824fa432da44d Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Thu, 8 Dec 2022 11:10:06 -0500 Subject: [PATCH 0017/1053] fix: Specificy --lib in rustdoc linters Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 464e22eb621f..4ec7d35a2780 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -74,6 +74,6 @@ jobs: toolchain: nightly override: true - name: Lint intra docs links - run: cargo rustdoc -p firewood -- -D rustdoc::broken-intra-doc-links + run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links - name: Lint missing crate-level docs - run: cargo rustdoc -p firewood -- -D rustdoc::missing-crate-level-docs + run: cargo rustdoc -p firewood --lib -- -D rustdoc::missing-crate-level-docs From 10bcb99bc789dbfbc73b657931e7eb9b6eab8d60 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 30 Nov 2022 16:11:20 -0500 Subject: [PATCH 0018/1053] cli: Add fwdctl create command Signed-off-by: Dan Sover --- Cargo.toml | 11 +++ src/bin/create.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++++ src/bin/fwdctl.rs | 27 +++++++ src/db.rs | 28 +++---- src/storage.rs | 22 ++--- 5 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 src/bin/create.rs create mode 100644 src/bin/fwdctl.rs diff --git a/Cargo.toml b/Cargo.toml index 208a6846017d..dc86d3ba2a36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,15 @@ name = "firewood" version = "0.0.1" edition = "2021" +autobins = false + +[lib] +name = "firewood" +path = "src/lib.rs" + +[[bin]] +name = "fwdctl" +path = "src/bin/fwdctl.rs" [dependencies] enum-as-inner = "0.5.1" @@ -21,6 +30,8 @@ futures = "0.3.24" primitive-types = { verison = "0.12.0", features = ["impl-rlp"] } serde = { version = "1.0", features = ["derive"] } bincode = "1.2.1" +clap = { version = "4.0.29", features = ["cargo", "derive"] } +anyhow = "1.0.66" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/bin/create.rs b/src/bin/create.rs new file mode 100644 index 000000000000..1964c0c9bf61 --- /dev/null +++ b/src/bin/create.rs @@ -0,0 +1,201 @@ +use anyhow::Result; +use clap::Args; +use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; + +#[derive(Args)] +pub struct Options { + /// DB Options + #[arg(default_value_t = String::from("firewood"), value_name = "NAME", help = "A name for the database")] + pub name: String, + + #[arg( + default_value_t = 16384, + value_name = "META_NCACHED_PAGES", + help = "Maximum cached pages for the free list of the item stash." + )] + pub meta_ncached_pages: usize, + + #[arg( + default_value_t = 1024, + value_name = "META_NCACHED_FILES", + help = "Maximum cached file descriptors for the free list of the item stash." + )] + pub meta_ncached_files: usize, + + #[arg( + default_value_t = 22, + value_name = "META_FILE_NBIT", + help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + the power of 2 for the file size." + )] + pub meta_file_nbit: u64, + + #[arg( + default_value_t = 262144, + value_name = "PAYLOAD_NCACHED_PAGES", + help = "Maximum cached pages for the item stash. This is the low-level cache used by the linear + space that holds MPT nodes and account objects." + )] + pub payload_ncached_pages: usize, + + #[arg( + default_value_t = 1024, + value_name = "PAYLOAD_NCACHED_FILES", + help = "Maximum cached file descriptors for the item stash." + )] + pub payload_ncached_files: usize, + + #[arg( + default_value_t = 22, + value_name = "PAYLOAD_FILE_NBIT", + help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + the power of 2 for the file size." + )] + pub payload_file_nbit: u64, + + #[arg( + default_value_t = 10, + value_name = "PAYLOAD_MAX_WALK", + help = "Maximum steps of walk to recycle a freed item." + )] + pub payload_max_walk: u64, + + #[arg( + default_value_t = 22, + value_name = "PAYLOAD_REGN_NBIT", + help = "Region size in bits (should be not greater than PAYLOAD_FILE_NBIT). One file is + partitioned into multiple regions. Suggested to use the default value." + )] + pub payload_regn_nbit: u64, + + #[arg( + default_value_t = false, + value_name = "TRUNCATE", + help = "Whether to truncate the DB when opening it. If set, the DB will be reset and all its + existing contents will be lost." + )] + pub truncate: bool, + + /// Revision options + #[arg(default_value_t = 1 << 20, value_name = "REV_MERKLE_NCACHED", help = "Config for accessing a version of the DB. Maximum cached MPT objects.")] + merkle_ncached_objs: usize, + + #[arg( + default_value_t = 4096, + value_name = "REV_BLOB_NCACHED", + help = "Maximum cached Blob objects." + )] + blob_ncached_objs: usize, + + /// Disk Buffer options + #[arg( + default_value_t = 4096, + value_name = "DISK_BUFFER_MAX_BUFFERED", + help = "Maximum buffered disk buffer." + )] + max_buffered: usize, + + #[arg( + default_value_t = 65536, + value_name = "DISK_BUFFER_MAX_PENDING", + help = "Maximum number of pending pages." + )] + max_pending: usize, + + #[arg( + default_value_t = 1024, + value_name = "DISK_BUFFER_MAX_AIO_REQUESTS", + help = "Maximum number of concurrent async I/O requests." + )] + max_aio_requests: u32, + + #[arg( + default_value_t = 128, + value_name = "DISK_BUFFER_MAX_AIO_RESPONSE", + help = "Maximum number of async I/O responses that firewood polls for at a time." + )] + max_aio_response: u16, + + #[arg( + default_value_t = 128, + value_name = "DISK_BUFFER_MAX_AIO_SUBMIT", + help = "Maximum number of async I/O requests per submission." + )] + max_aio_submit: usize, + + #[arg( + default_value_t = 256, + value_name = "DISK_BUFFER_WAL_MAX_AIO_REQUESTS", + help = "Maximum number of concurrent async I/O requests in WAL." + )] + wal_max_aio_requests: usize, + + #[arg( + default_value_t = 1024, + value_name = "DISK_BUFFER_WAL_MAX_BUFFERED", + help = "Maximum buffered WAL records." + )] + wal_max_buffered: usize, + + #[arg( + default_value_t = 4096, + value_name = "DISK_BUFFER_WAL_MAX_BATCH", + help = "Maximum batched WAL records per write." + )] + wal_max_batch: usize, + + /// WAL Config + #[arg(default_value_t = 22, value_name = "WAL_FILE_NBIT", help = "Size of WAL file.")] + file_nbit: u64, + + #[arg(default_value_t = 15, value_name = "WAL_BLOCK_NBIT", help = "Size of WAL blocks.")] + block_nbit: u64, + + #[arg( + default_value_t = 100, + value_name = "WAL_MAX_REVISIONS", + help = "Number of revisions to keep from the past. This preserves a rolling window + of the past N commits to the database." + )] + max_revisions: u32, +} + +pub fn initialize_db_config(opts: &Options) -> DBConfig { + DBConfig { + meta_ncached_pages: opts.meta_ncached_pages, + meta_ncached_files: opts.meta_ncached_files, + meta_file_nbit: opts.meta_file_nbit, + payload_ncached_pages: opts.payload_ncached_pages, + payload_ncached_files: opts.payload_ncached_files, + payload_file_nbit: opts.payload_file_nbit, + payload_max_walk: opts.payload_max_walk, + payload_regn_nbit: opts.payload_regn_nbit, + truncate: opts.truncate, + rev: DBRevConfig { + merkle_ncached_objs: opts.merkle_ncached_objs, + blob_ncached_objs: opts.blob_ncached_objs, + }, + buffer: DiskBufferConfig { + max_buffered: opts.max_buffered, + max_pending: opts.max_pending, + max_aio_requests: opts.max_aio_requests, + max_aio_response: opts.max_aio_response, + max_aio_submit: opts.max_aio_submit, + wal_max_aio_requests: opts.wal_max_aio_requests, + wal_max_buffered: opts.wal_max_buffered, + wal_max_batch: opts.wal_max_batch, + }, + wal: WALConfig { + file_nbit: opts.file_nbit, + block_nbit: opts.block_nbit, + max_revisions: opts.max_revisions, + }, + } +} + +pub fn run(opts: &Options) -> Result<()> { + let db_config = initialize_db_config(opts); + let _ = DB::new(opts.name.as_ref(), &db_config); + + Ok(()) +} diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs new file mode 100644 index 000000000000..a7c63e41e466 --- /dev/null +++ b/src/bin/fwdctl.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +pub mod create; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Create a new firewood database + Create(create::Options), +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match &cli.command { + Commands::Create(opts) => create::run(opts), + _ => unreachable!("Subcommand not found. Exhausted list of subcommands"), + } +} diff --git a/src/db.rs b/src/db.rs index 720eb86f579b..dd96fbab66ef 100644 --- a/src/db.rs +++ b/src/db.rs @@ -48,10 +48,10 @@ struct DBParams { pub struct DBRevConfig { /// Maximum cached MPT objects. #[builder(default = 1 << 20)] - merkle_ncached_objs: usize, + pub merkle_ncached_objs: usize, /// Maximum cached Blob (currently just `Account`) objects. #[builder(default = 4096)] - blob_ncached_objs: usize, + pub blob_ncached_objs: usize, } /// Database configuration. @@ -59,45 +59,45 @@ pub struct DBRevConfig { pub struct DBConfig { /// Maximum cached pages for the free list of the item stash. #[builder(default = 16384)] // 64M total size by default - meta_ncached_pages: usize, + pub meta_ncached_pages: usize, /// Maximum cached file descriptors for the free list of the item stash. #[builder(default = 1024)] // 1K fds by default - meta_ncached_files: usize, + pub meta_ncached_files: usize, /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to /// the power of 2 for the file size. #[builder(default = 22)] // 4MB file by default - meta_file_nbit: u64, + pub meta_file_nbit: u64, /// Maximum cached pages for the item stash. This is the low-level cache used by the linear /// space that holds MPT nodes and account objects. #[builder(default = 262144)] // 1G total size by default - payload_ncached_pages: usize, + pub payload_ncached_pages: usize, /// Maximum cached file descriptors for the item stash. #[builder(default = 1024)] // 1K fds by default - payload_ncached_files: usize, + pub payload_ncached_files: usize, /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to /// the power of 2 for the file size. #[builder(default = 22)] // 4MB file by default - payload_file_nbit: u64, + pub payload_file_nbit: u64, /// Maximum steps of walk to recycle a freed item. #[builder(default = 10)] - payload_max_walk: u64, + pub payload_max_walk: u64, /// Region size in bits (should be not greater than `payload_file_nbit`). One file is /// partitioned into multiple regions. Just use the default value. #[builder(default = 22)] - payload_regn_nbit: u64, + pub payload_regn_nbit: u64, /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its /// existing contents will be lost. #[builder(default = false)] - truncate: bool, + pub truncate: bool, /// Config for accessing a version of the DB. #[builder(default = DBRevConfig::builder().build())] - rev: DBRevConfig, + pub rev: DBRevConfig, /// Config for the disk buffer. #[builder(default = DiskBufferConfig::builder().build())] - buffer: DiskBufferConfig, + pub buffer: DiskBufferConfig, /// Config for WAL. #[builder(default = WALConfig::builder().build())] - wal: WALConfig, + pub wal: WALConfig, } /// Necessary linear space instances bundled for a `CompactSpace`. diff --git a/src/storage.rs b/src/storage.rs index 265ee1e36de2..8bb4e3eada23 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -795,11 +795,11 @@ pub enum BufferCmd { #[derive(TypedBuilder, Clone)] pub struct WALConfig { #[builder(default = 22)] // 4MB WAL logs - pub(crate) file_nbit: u64, + pub file_nbit: u64, #[builder(default = 15)] // 32KB - pub(crate) block_nbit: u64, + pub block_nbit: u64, #[builder(default = 100)] // preserve a rolling window of 100 past commits - pub(crate) max_revisions: u32, + pub max_revisions: u32, } /// Config for the disk buffer. @@ -807,28 +807,28 @@ pub struct WALConfig { pub struct DiskBufferConfig { /// Maximum buffered disk buffer commands. #[builder(default = 4096)] - pub(crate) max_buffered: usize, + pub max_buffered: usize, /// Maximum number of pending pages. #[builder(default = 65536)] // 256MB total size by default - max_pending: usize, + pub max_pending: usize, /// Maximum number of concurrent async I/O requests. #[builder(default = 1024)] - max_aio_requests: u32, + pub max_aio_requests: u32, /// Maximum number of async I/O responses that it polls for at a time. #[builder(default = 128)] - max_aio_response: u16, + pub max_aio_response: u16, /// Maximum number of async I/O requests per submission. #[builder(default = 128)] - max_aio_submit: usize, + pub max_aio_submit: usize, /// Maximum number of concurrent async I/O requests in WAL. #[builder(default = 256)] - wal_max_aio_requests: usize, + pub wal_max_aio_requests: usize, /// Maximum buffered WAL records. #[builder(default = 1024)] - wal_max_buffered: usize, + pub wal_max_buffered: usize, /// Maximum batched WAL records per write. #[builder(default = 4096)] - wal_max_batch: usize, + pub wal_max_batch: usize, } struct PendingPage { From b34c6231c5925d91c7bbff540ae7c84819f80c74 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 9 Dec 2022 17:08:08 -0500 Subject: [PATCH 0019/1053] cli: Add fwdctl README and test Signed-off-by: Dan Sover --- Cargo.toml | 2 ++ src/bin/README.md | 6 ++++++ src/bin/fwdctl.rs | 1 - src/bin/tests/cli.rs | 20 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/bin/README.md create mode 100644 src/bin/tests/cli.rs diff --git a/Cargo.toml b/Cargo.toml index dc86d3ba2a36..924c252f4de6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ criterion = "0.4.0" keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" +assert_cmd = "2.0.7" +predicates = "2.1.1" [profile.release] debug = true diff --git a/src/bin/README.md b/src/bin/README.md new file mode 100644 index 000000000000..634c79851ec1 --- /dev/null +++ b/src/bin/README.md @@ -0,0 +1,6 @@ +# fwdctl + +`fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. + +## Supported commands +* `fwdctl create`: Create a new firewood database. Use `fwdctl create -h` to see all the available options. \ No newline at end of file diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index a7c63e41e466..9f3dae1e2fc3 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -22,6 +22,5 @@ fn main() -> Result<()> { match &cli.command { Commands::Create(opts) => create::run(opts), - _ => unreachable!("Subcommand not found. Exhausted list of subcommands"), } } diff --git a/src/bin/tests/cli.rs b/src/bin/tests/cli.rs new file mode 100644 index 000000000000..67cad1480056 --- /dev/null +++ b/src/bin/tests/cli.rs @@ -0,0 +1,20 @@ +use anyhow::Result; +use assert_cmd::Command; +use predicates::prelude::*; + +const PRG: &str = "fwdctl"; +const VERSION: &str = "0.0.1"; + +#[test] +fn prints_version() -> Result<()> { + let expected_version_output: String = format!("{PRG} {VERSION}"); + + // version is defined and succeeds with the desired output + Command::cargo_bin(PRG)? + .args(["-V"]) + .assert() + .success() + .stdout(predicate::str::contains(expected_version_output)); + + Ok(()) +} \ No newline at end of file From d928eb66e95da49c00ef1f1f53f0497c682a7ac2 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 12 Dec 2022 14:21:20 -0500 Subject: [PATCH 0020/1053] cli: Fix flag arguments; add fwdctl documentation Signed-off-by: Dan Sover --- src/bin/README.md | 24 +++++++++++++- src/bin/create.rs | 74 ++++++++++++++++++++++++++++++++++++++++---- src/bin/tests/cli.rs | 4 +-- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/bin/README.md b/src/bin/README.md index 634c79851ec1..a7ac0a8f20e3 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -2,5 +2,27 @@ `fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. +## Building locally +*Note: fwdctl is linux-only* +``` +cargo build --release --bin fwdctl +``` +To use +``` +$ ./target/release/fwdctl -h +``` + ## Supported commands -* `fwdctl create`: Create a new firewood database. Use `fwdctl create -h` to see all the available options. \ No newline at end of file +* `fwdctl create`: Create a new firewood database. + +## Examples +* fwdctl create +``` +# Check available options when creating a database, including the defaults +$ fwdctl create -h +# Create a new, blank instance of firewood with a custom name +$ fwdctl create --name=my-db +# Look inside, there are several folders representing different components of firewood, including the WAL +$ ls my-db +``` + diff --git a/src/bin/create.rs b/src/bin/create.rs index 1964c0c9bf61..2ae23ef1991e 100644 --- a/src/bin/create.rs +++ b/src/bin/create.rs @@ -1,14 +1,21 @@ use anyhow::Result; -use clap::Args; +use clap::{value_parser, Args}; use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; #[derive(Args)] pub struct Options { /// DB Options - #[arg(default_value_t = String::from("firewood"), value_name = "NAME", help = "A name for the database")] + #[arg( + long, + required = false, + default_value_t = String::from("firewood"), + value_name = "NAME", + help = "A name for the database.")] pub name: String, #[arg( + long, + required = false, default_value_t = 16384, value_name = "META_NCACHED_PAGES", help = "Maximum cached pages for the free list of the item stash." @@ -16,6 +23,8 @@ pub struct Options { pub meta_ncached_pages: usize, #[arg( + long, + required = false, default_value_t = 1024, value_name = "META_NCACHED_FILES", help = "Maximum cached file descriptors for the free list of the item stash." @@ -23,6 +32,8 @@ pub struct Options { pub meta_ncached_files: usize, #[arg( + long, + required = false, default_value_t = 22, value_name = "META_FILE_NBIT", help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to @@ -31,6 +42,8 @@ pub struct Options { pub meta_file_nbit: u64, #[arg( + long, + required = false, default_value_t = 262144, value_name = "PAYLOAD_NCACHED_PAGES", help = "Maximum cached pages for the item stash. This is the low-level cache used by the linear @@ -39,6 +52,8 @@ pub struct Options { pub payload_ncached_pages: usize, #[arg( + long, + required = false, default_value_t = 1024, value_name = "PAYLOAD_NCACHED_FILES", help = "Maximum cached file descriptors for the item stash." @@ -46,6 +61,8 @@ pub struct Options { pub payload_ncached_files: usize, #[arg( + long, + required = false, default_value_t = 22, value_name = "PAYLOAD_FILE_NBIT", help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to @@ -54,6 +71,8 @@ pub struct Options { pub payload_file_nbit: u64, #[arg( + long, + required = false, default_value_t = 10, value_name = "PAYLOAD_MAX_WALK", help = "Maximum steps of walk to recycle a freed item." @@ -61,6 +80,8 @@ pub struct Options { pub payload_max_walk: u64, #[arg( + long, + required = false, default_value_t = 22, value_name = "PAYLOAD_REGN_NBIT", help = "Region size in bits (should be not greater than PAYLOAD_FILE_NBIT). One file is @@ -69,18 +90,29 @@ pub struct Options { pub payload_regn_nbit: u64, #[arg( + long, + required = false, + value_parser = value_parser!(bool), + default_missing_value = "false", default_value_t = false, value_name = "TRUNCATE", help = "Whether to truncate the DB when opening it. If set, the DB will be reset and all its - existing contents will be lost." + existing contents will be lost. [default: false]" )] pub truncate: bool, /// Revision options - #[arg(default_value_t = 1 << 20, value_name = "REV_MERKLE_NCACHED", help = "Config for accessing a version of the DB. Maximum cached MPT objects.")] + #[arg( + long, + required = false, + default_value_t = 1 << 20, + value_name = "REV_MERKLE_NCACHED", + help = "Config for accessing a version of the DB. Maximum cached MPT objects.")] merkle_ncached_objs: usize, #[arg( + long, + required = false, default_value_t = 4096, value_name = "REV_BLOB_NCACHED", help = "Maximum cached Blob objects." @@ -89,6 +121,8 @@ pub struct Options { /// Disk Buffer options #[arg( + long, + required = false, default_value_t = 4096, value_name = "DISK_BUFFER_MAX_BUFFERED", help = "Maximum buffered disk buffer." @@ -96,6 +130,8 @@ pub struct Options { max_buffered: usize, #[arg( + long, + required = false, default_value_t = 65536, value_name = "DISK_BUFFER_MAX_PENDING", help = "Maximum number of pending pages." @@ -103,6 +139,8 @@ pub struct Options { max_pending: usize, #[arg( + long, + required = false, default_value_t = 1024, value_name = "DISK_BUFFER_MAX_AIO_REQUESTS", help = "Maximum number of concurrent async I/O requests." @@ -110,6 +148,8 @@ pub struct Options { max_aio_requests: u32, #[arg( + long, + required = false, default_value_t = 128, value_name = "DISK_BUFFER_MAX_AIO_RESPONSE", help = "Maximum number of async I/O responses that firewood polls for at a time." @@ -117,6 +157,8 @@ pub struct Options { max_aio_response: u16, #[arg( + long, + required = false, default_value_t = 128, value_name = "DISK_BUFFER_MAX_AIO_SUBMIT", help = "Maximum number of async I/O requests per submission." @@ -124,6 +166,8 @@ pub struct Options { max_aio_submit: usize, #[arg( + long, + required = false, default_value_t = 256, value_name = "DISK_BUFFER_WAL_MAX_AIO_REQUESTS", help = "Maximum number of concurrent async I/O requests in WAL." @@ -131,6 +175,8 @@ pub struct Options { wal_max_aio_requests: usize, #[arg( + long, + required = false, default_value_t = 1024, value_name = "DISK_BUFFER_WAL_MAX_BUFFERED", help = "Maximum buffered WAL records." @@ -138,6 +184,8 @@ pub struct Options { wal_max_buffered: usize, #[arg( + long, + required = false, default_value_t = 4096, value_name = "DISK_BUFFER_WAL_MAX_BATCH", help = "Maximum batched WAL records per write." @@ -145,13 +193,27 @@ pub struct Options { wal_max_batch: usize, /// WAL Config - #[arg(default_value_t = 22, value_name = "WAL_FILE_NBIT", help = "Size of WAL file.")] + #[arg( + long, + required = false, + default_value_t = 22, + value_name = "WAL_FILE_NBIT", + help = "Size of WAL file." + )] file_nbit: u64, - #[arg(default_value_t = 15, value_name = "WAL_BLOCK_NBIT", help = "Size of WAL blocks.")] + #[arg( + long, + required = false, + default_value_t = 15, + value_name = "WAL_BLOCK_NBIT", + help = "Size of WAL blocks." + )] block_nbit: u64, #[arg( + long, + required = false, default_value_t = 100, value_name = "WAL_MAX_REVISIONS", help = "Number of revisions to keep from the past. This preserves a rolling window diff --git a/src/bin/tests/cli.rs b/src/bin/tests/cli.rs index 67cad1480056..1ad4c16ac25c 100644 --- a/src/bin/tests/cli.rs +++ b/src/bin/tests/cli.rs @@ -3,7 +3,7 @@ use assert_cmd::Command; use predicates::prelude::*; const PRG: &str = "fwdctl"; -const VERSION: &str = "0.0.1"; +const VERSION: &str = env!("CARGO_PKG_VERSION"); #[test] fn prints_version() -> Result<()> { @@ -17,4 +17,4 @@ fn prints_version() -> Result<()> { .stdout(predicate::str::contains(expected_version_output)); Ok(()) -} \ No newline at end of file +} From 1de0bf4d602110b504f9abee607cd12b6c1f4ca1 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 13 Dec 2022 12:50:58 -0500 Subject: [PATCH 0021/1053] docs: Add link to fwdctl README in main README Signed-off-by: Dan Sover --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7a89e2911fe0..9b56520f8df3 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. +## CLI +Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](src/bin/README.md). + ## Test ``` cargo test --release From b54add50a17072bee429002adaa48a773a981a2d Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 13 Dec 2022 15:40:57 -0500 Subject: [PATCH 0022/1053] cli: Add logger Signed-off-by: Dan Sover --- Cargo.toml | 2 ++ src/bin/create.rs | 4 ++++ src/bin/fwdctl.rs | 13 +++++++++++++ src/db.rs | 4 ++-- src/storage.rs | 4 ++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 924c252f4de6..e92a701d0b52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ serde = { version = "1.0", features = ["derive"] } bincode = "1.2.1" clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" +env_logger = "0.10.0" +log = "0.4.17" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/bin/create.rs b/src/bin/create.rs index 2ae23ef1991e..ab1d6f219c29 100644 --- a/src/bin/create.rs +++ b/src/bin/create.rs @@ -1,6 +1,7 @@ use anyhow::Result; use clap::{value_parser, Args}; use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; +use log; #[derive(Args)] pub struct Options { @@ -257,7 +258,10 @@ pub fn initialize_db_config(opts: &Options) -> DBConfig { pub fn run(opts: &Options) -> Result<()> { let db_config = initialize_db_config(opts); + log::debug!("database configuration parameters: \n{:?}\n", db_config); + let _ = DB::new(opts.name.as_ref(), &db_config); + log::info!("created firewood database in {:?}", opts.name); Ok(()) } diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index 9f3dae1e2fc3..238d88a647b1 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -9,6 +9,17 @@ pub mod create; struct Cli { #[command(subcommand)] command: Commands, + #[arg( + long, + short = 'l', + required = false, + help = "Log level. Respects RUST_LOG.", + value_name = "LOG_LEVEL", + num_args = 1, + value_parser = ["debug", "info"], + default_value_t = String::from("info"), + )] + log_level: String, } #[derive(Subcommand)] @@ -20,6 +31,8 @@ enum Commands { fn main() -> Result<()> { let cli = Cli::parse(); + env_logger::init_from_env(env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info".to_string())); + match &cli.command { Commands::Create(opts) => create::run(opts), } diff --git a/src/db.rs b/src/db.rs index dd96fbab66ef..c8611b682b16 100644 --- a/src/db.rs +++ b/src/db.rs @@ -44,7 +44,7 @@ struct DBParams { } /// Config for accessing a version of the DB. -#[derive(TypedBuilder, Clone)] +#[derive(TypedBuilder, Clone, Debug)] pub struct DBRevConfig { /// Maximum cached MPT objects. #[builder(default = 1 << 20)] @@ -55,7 +55,7 @@ pub struct DBRevConfig { } /// Database configuration. -#[derive(TypedBuilder)] +#[derive(TypedBuilder, Debug)] pub struct DBConfig { /// Maximum cached pages for the free list of the item stash. #[builder(default = 16384)] // 64M total size by default diff --git a/src/storage.rs b/src/storage.rs index 8bb4e3eada23..81237c0f1f11 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -792,7 +792,7 @@ pub enum BufferCmd { Shutdown, } -#[derive(TypedBuilder, Clone)] +#[derive(TypedBuilder, Clone, Debug)] pub struct WALConfig { #[builder(default = 22)] // 4MB WAL logs pub file_nbit: u64, @@ -803,7 +803,7 @@ pub struct WALConfig { } /// Config for the disk buffer. -#[derive(TypedBuilder, Clone)] +#[derive(TypedBuilder, Clone, Debug)] pub struct DiskBufferConfig { /// Maximum buffered disk buffer commands. #[builder(default = 4096)] From 28704ee86221f77526607b04f315a68a82580a7c Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 14 Dec 2022 10:22:23 -0500 Subject: [PATCH 0023/1053] cli: Use log-level flag for setting logging level Signed-off-by: Dan Sover --- src/bin/fwdctl.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index 238d88a647b1..08fb2464df86 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -31,7 +31,9 @@ enum Commands { fn main() -> Result<()> { let cli = Cli::parse(); - env_logger::init_from_env(env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info".to_string())); + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.to_string()), + ); match &cli.command { Commands::Create(opts) => create::run(opts), From b4b959335f359f50c4c656342d5c159334491919 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 15 Dec 2022 23:18:56 -0800 Subject: [PATCH 0024/1053] Range proof generation and verification with basic test case --- src/dynamic_mem.rs | 94 +++++++++++++++ src/lib.rs | 2 + src/merkle.rs | 283 ++++++++++++++++++++++++++++++++++++++++----- src/merkle_util.rs | 89 ++++++++++++++ src/proof.rs | 277 +++++++++++++++++++++++++++++++++++++++++++- tests/merkle.rs | 105 +++++------------ 6 files changed, 743 insertions(+), 107 deletions(-) create mode 100644 src/dynamic_mem.rs create mode 100644 src/merkle_util.rs diff --git a/src/dynamic_mem.rs b/src/dynamic_mem.rs new file mode 100644 index 000000000000..acdab127b7fb --- /dev/null +++ b/src/dynamic_mem.rs @@ -0,0 +1,94 @@ +use std::cell::UnsafeCell; +use std::ops::Deref; +use std::rc::Rc; + +use shale::*; + +pub type SpaceID = u8; + +/// Purely volatile, dynamically allocated vector-based implementation for [MemStore]. This is similar to +/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is +/// not enough. +pub struct DynamicMem { + space: Rc>>, + id: SpaceID, +} + +impl DynamicMem { + pub fn new(size: u64, id: SpaceID) -> Self { + let mut space = Vec::new(); + space.resize(size as usize, 0); + let space = Rc::new(UnsafeCell::new(space)); + Self { space, id } + } + + fn get_space_mut(&self) -> &mut Vec { + unsafe { &mut *self.space.get() } + } +} + +impl MemStore for DynamicMem { + fn get_view(&self, offset: u64, length: u64) -> Option> { + let offset = offset as usize; + let length = length as usize; + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size as usize, 0); + } + Some(Box::new(DynamicMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(DynamicMemShared(Self { + space: self.space.clone(), + id: self.id, + }))) + } + + fn write(&self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size as usize, 0); + } + self.get_space_mut()[offset..offset + length].copy_from_slice(change) + } + + fn id(&self) -> SpaceID { + self.id + } +} + +struct DynamicMemView { + offset: usize, + length: usize, + mem: DynamicMem, +} + +struct DynamicMemShared(DynamicMem); + +impl Deref for DynamicMemView { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.mem.get_space_mut()[self.offset..self.offset + self.length] + } +} + +impl Deref for DynamicMemShared { + type Target = dyn MemStore; + fn deref(&self) -> &(dyn MemStore + 'static) { + &self.0 + } +} + +impl MemView for DynamicMemView {} diff --git a/src/lib.rs b/src/lib.rs index 4cfdb0f145f7..4aa75b603d6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,7 +197,9 @@ //! pub(crate) mod account; pub mod db; +pub mod dynamic_mem; pub(crate) mod file; pub mod merkle; +pub mod merkle_util; pub mod proof; pub(crate) mod storage; diff --git a/src/merkle.rs b/src/merkle.rs index 88b86a1d26be..9bc707b8391c 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -6,11 +6,12 @@ use sha3::Digest; use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; use std::cell::Cell; +use std::cmp; use std::collections::HashMap; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; -const NBRANCH: usize = 16; +pub const NBRANCH: usize = 16; #[derive(Debug)] pub enum MerkleError { @@ -137,9 +138,10 @@ impl std::ops::Deref for Data { } #[derive(PartialEq, Eq, Clone)] -struct BranchNode { +pub struct BranchNode { chd: [Option>; NBRANCH], value: Option, + chd_eth_rlp: [Option>; NBRANCH], } impl Debug for BranchNode { @@ -180,7 +182,7 @@ impl BranchNode { fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { let mut stream = rlp::RlpStream::new_list(17); - for c in self.chd.iter() { + for (index, c) in self.chd.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); @@ -196,7 +198,15 @@ impl BranchNode { stream.append_raw(c_rlp, 1) } } - None => stream.append_empty_data(), + None => { + // Check if there is already caclucated rlp for the child, which + // can happen when manually construct a trie from proof. + if self.chd_eth_rlp[index].is_none() { + stream.append_empty_data() + } else { + stream.append_raw(&self.chd_eth_rlp[index].clone().unwrap(), 1) + } + } }; } match &self.value { @@ -205,10 +215,28 @@ impl BranchNode { }; stream.out().into() } + + pub fn new( + chd: [Option>; NBRANCH], value: Option>, chd_eth_rlp: [Option>; NBRANCH], + ) -> Self { + BranchNode { + chd, + value: value.map(|v| Data(v)), + chd_eth_rlp, + } + } + + pub fn chd(&self) -> &[Option>; NBRANCH] { + &self.chd + } + + pub fn chd_mut(&mut self) -> &mut [Option>; NBRANCH] { + &mut self.chd + } } #[derive(PartialEq, Eq, Clone)] -struct LeafNode(PartialPath, Data); +pub struct LeafNode(PartialPath, Data); impl Debug for LeafNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -220,10 +248,14 @@ impl LeafNode { fn calc_eth_rlp(&self) -> Vec { rlp::encode_list::, _>(&[from_nibbles(&self.0.encode(true)).collect(), T::transform(&self.1)]).into() } + + pub fn new(path: Vec, data: Vec) -> Self { + LeafNode(PartialPath(path), Data(data)) + } } #[derive(PartialEq, Eq, Clone)] -struct ExtNode(PartialPath, ObjPtr); +pub struct ExtNode(PartialPath, ObjPtr, Option>); impl Debug for ExtNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -233,20 +265,42 @@ impl Debug for ExtNode { impl ExtNode { fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { - let mut r = store.get_item(self.1).unwrap(); let mut stream = rlp::RlpStream::new_list(2); - stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); - if r.get_eth_rlp_long::(store) { - stream.append(&&(*r.get_root_hash::(store))[..]); - if r.lazy_dirty.get() { - r.write(|_| {}).unwrap(); - r.lazy_dirty.set(false) + if !self.1.is_null() { + let mut r = store.get_item(self.1).unwrap(); + stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); + if r.get_eth_rlp_long::(store) { + stream.append(&&(*r.get_root_hash::(store))[..]); + if r.lazy_dirty.get() { + r.write(|_| {}).unwrap(); + r.lazy_dirty.set(false) + } + } else { + stream.append_raw(r.get_eth_rlp::(store), 1); } } else { - stream.append_raw(r.get_eth_rlp::(store), 1); + // Check if there is already caclucated rlp for the child, which + // can happen when manually construct a trie from proof. + if self.2.is_none() { + stream.append_empty_data(); + } else { + stream.append_raw(&self.2.clone().unwrap(), 1); + } } stream.out().into() } + + pub fn new(path: Vec, chd: ObjPtr, chd_eth_rlp: Option>) -> Self { + ExtNode(PartialPath(path), chd, chd_eth_rlp) + } + + pub fn chd(&self) -> ObjPtr { + self.1 + } + + pub fn chd_mut(&mut self) -> &mut ObjPtr { + &mut self.1 + } } #[derive(PartialEq, Eq, Clone)] @@ -259,7 +313,7 @@ pub struct Node { } #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] -enum NodeType { +pub enum NodeType { Branch(BranchNode), Leaf(LeafNode), Extension(ExtNode), @@ -290,6 +344,7 @@ impl Node { inner: NodeType::Branch(BranchNode { chd: [Some(ObjPtr::null()); NBRANCH], value: Some(Data(Vec::new())), + chd_eth_rlp: Default::default(), }), lazy_dirty: Cell::new(false), } @@ -321,7 +376,7 @@ impl Node { self.root_hash = OnceCell::new(); } - fn new(inner: NodeType) -> Self { + pub fn new(inner: NodeType) -> Self { let mut s = Self { root_hash: OnceCell::new(), eth_rlp_long: OnceCell::new(), @@ -333,6 +388,14 @@ impl Node { s } + pub fn inner(&self) -> &NodeType { + &self.inner + } + + pub fn inner_mut(&mut self) -> &mut NodeType { + &mut self.inner + } + fn new_from_hash(root_hash: Option, eth_rlp_long: Option, inner: NodeType) -> Self { Self { root_hash: match root_hash { @@ -397,10 +460,40 @@ impl MummyItem for Node { .to_vec(), )) }; + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + let offset = if raw_len == u32::MAX as u64 { + addr + META_SIZE + branch_header_size + } else { + addr + META_SIZE + branch_header_size + raw_len + }; + let mut cur_rlp_len = 0; + for chd_rlp in chd_eth_rlp.iter_mut() { + let mut buff = [0 as u8; 1]; + let rlp_len_raw = mem + .get_view(offset + cur_rlp_len, 1) + .ok_or(ShaleError::LinearMemStoreError)?; + cur = Cursor::new(rlp_len_raw.deref()); + cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + let rlp_len = buff[0] as u64; + cur_rlp_len += 1; + if rlp_len != 0 { + let rlp_raw = mem + .get_view(offset + cur_rlp_len, rlp_len) + .ok_or(ShaleError::LinearMemStoreError)?; + let rlp: Vec = rlp_raw[0..].to_vec(); + *chd_rlp = Some(rlp); + cur_rlp_len += rlp_len + } + } + Ok(Self::new_from_hash( root_hash, eth_rlp_long, - NodeType::Branch(BranchNode { chd, value }), + NodeType::Branch(BranchNode { + chd, + value, + chd_eth_rlp, + }), )) } Self::EXT_NODE => { @@ -411,19 +504,36 @@ impl MummyItem for Node { let mut cur = Cursor::new(node_raw.deref()); let mut buff = [0; 8]; cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?; - let len = buff[0] as u64; + let path_len = buff[0] as u64; cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( - &mem.get_view(addr + META_SIZE + ext_header_size, len) + &mem.get_view(addr + META_SIZE + ext_header_size, path_len) .ok_or(ShaleError::LinearMemStoreError)?, ) .collect(); let (path, _) = PartialPath::decode(nibbles); + + let mut buff = [0 as u8; 1]; + let rlp_len_raw = mem + .get_view(addr + META_SIZE + ext_header_size + path_len, 1) + .ok_or(ShaleError::LinearMemStoreError)?; + cur = Cursor::new(rlp_len_raw.deref()); + cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + let rlp_len = buff[0] as u64; + let rlp: Option> = if rlp_len != 0 { + let rlp_raw = mem + .get_view(addr + META_SIZE + ext_header_size + path_len + 1, rlp_len) + .ok_or(ShaleError::LinearMemStoreError)?; + Some(rlp_raw[0..].to_vec()) + } else { + None + }; + Ok(Self::new_from_hash( root_hash, eth_rlp_long, - NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) })), + NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) }, rlp)), )) } Self::LEAF_NODE => { @@ -458,14 +568,29 @@ impl MummyItem for Node { 1 + match &self.inner { NodeType::Branch(n) => { + let mut rlp_len = 0; + for rlp in n.chd_eth_rlp.iter() { + rlp_len += match rlp { + Some(v) => 1 + v.len() as u64, + None => 1, + } + } NBRANCH as u64 * 8 + 4 + match &n.value { Some(val) => val.len() as u64, None => 0, + } + + rlp_len + } + NodeType::Extension(n) => { + 1 + 8 + + n.0.dehydrated_len() + + match &n.2 { + Some(v) => 1 + v.len() as u64, + None => 1, } } - NodeType::Extension(n) => 1 + 8 + n.0.dehydrated_len(), NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, } } @@ -509,6 +634,19 @@ impl MummyItem for Node { cur.write_all(&u32::MAX.to_le_bytes()).unwrap(); } } + // Since child eth rlp is immutable after initialization (only used for range proof), + // it is fine to encode its value adjacent to other fields. Same for extention node. + for rlp in n.chd_eth_rlp.iter() { + match rlp { + Some(v) => { + cur.write_all(&[v.len() as u8]).unwrap(); + cur.write_all(&v).unwrap(); + } + None => { + cur.write_all(&0u8.to_le_bytes()).unwrap(); + } + } + } } NodeType::Extension(n) => { cur.write_all(&[Self::EXT_NODE]).unwrap(); @@ -516,6 +654,11 @@ impl MummyItem for Node { cur.write_all(&[path.len() as u8]).unwrap(); cur.write_all(&n.1.addr().to_le_bytes()).unwrap(); cur.write_all(&path).unwrap(); + if n.2.is_some() { + let rlp = n.2.as_ref().unwrap(); + cur.write_all(&[rlp.len() as u8]).unwrap(); + cur.write_all(&rlp).unwrap(); + } } NodeType::Leaf(n) => { cur.write_all(&[Self::LEAF_NODE]).unwrap(); @@ -547,6 +690,10 @@ fn test_merkle_node_encoding() { for node in chd1.iter_mut().take(NBRANCH / 2) { *node = Some(unsafe { ObjPtr::new_from_addr(0xa) }); } + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { + *rlp = Some(vec![0x1, 0x2, 0x3]); + } for node in [ Node::new_from_hash( None, @@ -556,9 +703,20 @@ fn test_merkle_node_encoding() { Node::new_from_hash( None, None, - NodeType::Extension(ExtNode(PartialPath(vec![0x1, 0x2, 0x3]), unsafe { - ObjPtr::new_from_addr(0x42) - })), + NodeType::Extension(ExtNode( + PartialPath(vec![0x1, 0x2, 0x3]), + unsafe { ObjPtr::new_from_addr(0x42) }, + None, + )), + ), + Node::new_from_hash( + None, + None, + NodeType::Extension(ExtNode( + PartialPath(vec![0x1, 0x2, 0x3]), + { ObjPtr::null() }, + Some(vec![0x1, 0x2, 0x3]), + )), ), Node::new_from_hash( None, @@ -566,9 +724,18 @@ fn test_merkle_node_encoding() { NodeType::Branch(BranchNode { chd: chd0, value: Some(Data("hello, world!".as_bytes().to_vec())), + chd_eth_rlp: Default::default(), + }), + ), + Node::new_from_hash( + None, + None, + NodeType::Branch(BranchNode { + chd: chd1, + value: None, + chd_eth_rlp, }), ), - Node::new_from_hash(None, None, NodeType::Branch(BranchNode { chd: chd1, value: None })), ] { check(node); } @@ -592,10 +759,10 @@ pub struct Merkle { } impl Merkle { - fn get_node(&self, ptr: ObjPtr) -> Result, MerkleError> { + pub fn get_node(&self, ptr: ObjPtr) -> Result, MerkleError> { self.store.get_item(ptr).map_err(MerkleError::Shale) } - fn new_node(&self, item: Node) -> Result, MerkleError> { + pub fn new_node(&self, item: Node) -> Result, MerkleError> { self.store.put_item(item, 0).map_err(MerkleError::Shale) } fn free_node(&mut self, ptr: ObjPtr) -> Result<(), MerkleError> { @@ -614,6 +781,7 @@ impl Merkle { Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], value: None, + chd_eth_rlp: Default::default(), })), Node::max_branch_node_size(), ) @@ -752,12 +920,17 @@ impl Merkle { _ => u_ptr, }); drop(u_ref); - let t = NodeType::Branch(BranchNode { chd, value: None }); + let t = NodeType::Branch(BranchNode { + chd, + value: None, + chd_eth_rlp: Default::default(), + }); let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); if idx > 0 { self.new_node(Node::new(NodeType::Extension(ExtNode( PartialPath(rem_path[..idx].to_vec()), branch_ptr, + None, ))))? .as_ptr() } else { @@ -847,12 +1020,17 @@ impl Merkle { let mut chd = [None; NBRANCH]; chd[idx as usize] = Some(leaf_ptr); let branch_ptr = self - .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v })))? + .new_node(Node::new(NodeType::Branch(BranchNode { + chd, + value: v, + chd_eth_rlp: Default::default(), + })))? .as_ptr(); if !prefix.is_empty() { self.new_node(Node::new(NodeType::Extension(ExtNode( PartialPath(prefix.to_vec()), branch_ptr, + None, ))))? .as_ptr() } else { @@ -997,6 +1175,7 @@ impl Merkle { .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: Some(Data(val.take().unwrap())), + chd_eth_rlp: Default::default(), })))? .as_ptr(); self.set_parent(branch, &mut parents); @@ -1080,7 +1259,11 @@ impl Merkle { // \____[Leaf]x // to: [p: Branch] -> [Ext] -> [Branch] let ext = self - .new_node(Node::new(NodeType::Extension(ExtNode(PartialPath(vec![idx]), c_ptr))))? + .new_node(Node::new(NodeType::Extension(ExtNode( + PartialPath(vec![idx]), + c_ptr, + None, + ))))? .as_ptr(); self.set_parent(ext, &mut [(p_ref, p_idx)]); } @@ -1202,6 +1385,7 @@ impl Merkle { self.new_node(Node::new(NodeType::Extension(ExtNode( PartialPath(vec![idx]), c_ptr, + None, ))))? .as_ptr(), ); @@ -1699,6 +1883,18 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } +pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { + for (ai, bi) in a.iter().zip(b.iter()) { + match ai.cmp(&bi) { + cmp::Ordering::Equal => continue, + ord => return ord, + } + } + + // If every single element was equal, compare length + a.len().cmp(&b.len()) +} + #[test] fn test_to_nibbles() { for (bytes, nibbles) in [ @@ -1709,3 +1905,30 @@ fn test_to_nibbles() { assert_eq!(n, nibbles); } } + +#[test] +fn test_cmp() { + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x56]), + (vec![0xc0, 0xff], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert_eq!(n.is_eq(), true); + } + + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x58]), + (vec![0xc0, 0xee], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert_eq!(n.is_lt(), true); + } + + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x35, 0x56], vec![0x12, 0x34, 0x58]), + (vec![0xc0, 0xff, 0x33], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert_eq!(n.is_gt(), true); + } +} diff --git a/src/merkle_util.rs b/src/merkle_util.rs new file mode 100644 index 000000000000..dad779a7b878 --- /dev/null +++ b/src/merkle_util.rs @@ -0,0 +1,89 @@ +use crate::dynamic_mem::DynamicMem; +use crate::merkle::*; +use crate::proof::Proof; +use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr}; +use std::rc::Rc; + +pub struct MerkleSetup { + root: ObjPtr, + merkle: Merkle, +} + +impl MerkleSetup { + pub fn insert>(&mut self, key: K, val: Vec) { + self.merkle.insert(key, val, self.root).unwrap() + } + + pub fn remove>(&mut self, key: K) { + self.merkle.remove(key, self.root).unwrap(); + } + + pub fn get>(&self, key: K) -> Option { + self.merkle.get(key, self.root).unwrap() + } + + pub fn get_mut>(&mut self, key: K) -> Option { + self.merkle.get_mut(key, self.root).unwrap() + } + + pub fn get_root(&self) -> ObjPtr { + self.root + } + + pub fn get_merkle_mut(&mut self) -> &mut Merkle { + &mut self.merkle + } + + pub fn root_hash(&self) -> Hash { + self.merkle.root_hash::(self.root).unwrap() + } + + pub fn dump(&self) -> String { + let mut s = Vec::new(); + self.merkle.dump(self.root, &mut s).unwrap(); + String::from_utf8(s).unwrap() + } + + pub fn prove>(&self, key: K) -> Proof { + self.merkle.prove::(key, self.root).unwrap() + } + + pub fn verify_proof>(&self, key: K, proof: &Proof) -> Option> { + let hash: [u8; 32] = self.root_hash().0; + proof.verify_proof(key, hash).unwrap() + } + + pub fn verify_range_proof, V: AsRef<[u8]>>( + &self, proof: &Proof, first_key: K, last_key: K, keys: Vec, vals: Vec, + ) -> bool { + let hash: [u8; 32] = self.root_hash().0; + proof.verify_range_proof(hash, first_key, last_key, keys, vals).unwrap() + } +} + +pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { + const RESERVED: u64 = 0x1000; + assert!(meta_size > RESERVED); + assert!(compact_size > RESERVED); + let mem_meta = Rc::new(DynamicMem::new(meta_size, 0x0)) as Rc; + let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); + let compact_header: ObjPtr = unsafe { ObjPtr::new_from_addr(0x0) }; + + mem_meta.write( + compact_header.addr(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), + ); + + let compact_header = unsafe { + MummyObj::ptr_to_obj(mem_meta.as_ref(), compact_header, shale::compact::CompactHeader::MSIZE).unwrap() + }; + + let cache = shale::ObjCache::new(1); + let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + let mut root = ObjPtr::null(); + Merkle::init_root(&mut root, &space).unwrap(); + MerkleSetup { + root, + merkle: Merkle::new(Box::new(space)), + } +} diff --git a/src/proof.rs b/src/proof.rs index c3c264967eaf..94180d977db6 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,7 +1,9 @@ -use crate::merkle::{to_nibbles, PartialPath}; +use crate::merkle::*; +use crate::merkle_util::*; use serde::{Deserialize, Serialize}; use sha3::Digest; +use shale::ObjPtr; use std::collections::HashMap; @@ -14,6 +16,13 @@ pub enum ProofError { DecodeError, NoSuchNode, ProofNodeMissing, + InconsistentProofData, + NonMonotonicIncreaseRange, + RangeHasDeletion, + InvalidProof, + InvalidEdgeKeys, + InconsistentEdgeKeys, + NodesInsertionError, } const EXT_NODE_SIZE: usize = 2; @@ -128,4 +137,270 @@ impl Proof { _ => Err(ProofError::DecodeError), } } + + pub fn concat_proofs(&mut self, other: Proof) { + self.0.extend(other.0) + } + + pub fn verify_range_proof, V: AsRef<[u8]>>( + &self, root_hash: [u8; 32], first_key: K, last_key: K, keys: Vec, vals: Vec, + ) -> Result { + if keys.len() != vals.len() { + return Err(ProofError::InconsistentProofData) + } + + // Ensure the received batch is monotonic increasing and contains no deletions + for n in 0..keys.len() - 1 { + if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { + return Err(ProofError::NonMonotonicIncreaseRange) + } + } + + for v in vals.iter() { + if v.as_ref().len() == 0 { + return Err(ProofError::RangeHasDeletion) + } + } + + // Special case, there is no edge proof at all. The given range is expected + // to be the whole leaf-set in the trie. + if self.0.len() == 0 { + // Use in-memory merkle + let mut merkle = new_merkle(0x10000, 0x10000); + for (index, k) in keys.iter().enumerate() { + merkle.insert(k, vals[index].as_ref().to_vec()) + } + let merkle_root = &*merkle.root_hash(); + if merkle_root != &root_hash { + return Err(ProofError::InvalidProof) + } + return Ok(false) + } + // TODO(Hao): handle special case when there is a provided edge proof but zero key/value pairs. + // TODO(Hao): handle special case when there is only one element and two edge keys are same. + + // Ok, in all other cases, we require two edge paths available. + // First check the validity of edge keys. + if compare(first_key.as_ref(), last_key.as_ref()).is_ge() { + return Err(ProofError::InvalidEdgeKeys) + } + + // TODO(Hao): different length edge keys should be supported + if first_key.as_ref().len() != last_key.as_ref().len() { + return Err(ProofError::InconsistentEdgeKeys) + } + + // Convert the edge proofs to edge trie paths. Then we can + // have the same tree architecture with the original one. + // For the first edge proof, non-existent proof is allowed. + let mut merkle_setup = new_merkle(0x100000, 0x100000); + self.proof_to_path(first_key, root_hash, &mut merkle_setup)?; + + // Pass the root node here, the second path will be merged + // with the first one. For the last edge proof, non-existent + // proof is also allowed. + self.proof_to_path(last_key, root_hash, &mut merkle_setup)?; + + // let merkle = merkle_setup.get_merkle_mut(); + for index in 0..keys.len() { + merkle_setup.insert(keys[index].as_ref(), vals[index].as_ref().to_vec()) + } + + // Calculate the hash + let merkle_root = &*merkle_setup.root_hash(); + if merkle_root != &root_hash { + return Err(ProofError::InvalidProof) + } + + return Ok(true) + } + + /// proofToPath converts a merkle proof to trie node path. The main purpose of + /// this function is recovering a node path from the merkle proof stream. All + /// necessary nodes will be resolved and leave the remaining as hashnode. + /// + /// The given edge proof is allowed to be an existent or non-existent proof. + fn proof_to_path>( + &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, + ) -> Result<(), ProofError> { + let root = merkle_setup.get_root(); + let merkle = merkle_setup.get_merkle_mut(); + let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; + + let mut chunks = Vec::new(); + chunks.extend(to_nibbles(key.as_ref())); + + let mut cur_key: &[u8] = &chunks; + let mut cur_hash = root_hash; + let proofs_map = &self.0; + let mut key_index = 0; + let mut branch_index: u8 = 0; + // let mut root = ObjPtr::null(); + let mut iter = 0; + loop { + let cur_proof = proofs_map.get(&cur_hash).ok_or(ProofError::ProofNodeMissing)?; + // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. + let (mut chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof)?; + // Link the child to the parent based on the node type. + match &u_ref.inner() { + NodeType::Branch(n) => { + match n.chd()[branch_index as usize] { + // If the child already resolved, then use the existing node. + Some(node) => { + chd_ptr = Some(node); + } + None => { + // insert the leaf to the empty slot + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[branch_index as usize] = chd_ptr; + }) + .unwrap(); + } + }; + } + NodeType::Extension(_) => { + // If the child already resolved, then use the existing node. + let node = u_ref.inner().as_extension().unwrap().chd(); + if node.is_null() { + u_ref + .write(|u| { + let uu = u.inner_mut().as_extension_mut().unwrap(); + *uu.chd_mut() = if chd_ptr.is_none() { + ObjPtr::null() + } else { + chd_ptr.unwrap() + }; + }) + .unwrap(); + } else { + chd_ptr = Some(node); + } + } + _ => return Err(ProofError::DecodeError), + }; + + if chd_ptr.is_some() { + u_ref = merkle.get_node(chd_ptr.unwrap()).map_err(|_| ProofError::DecodeError)?; + // If the new parent is a branch node, record the index to correctly link the next child to it. + if u_ref.inner().as_branch().is_some() { + branch_index = chunks[key_index]; + } + } else { + // Root node must be included in the proof. + if iter == 0 { + return Err(ProofError::ProofNodeMissing) + } + } + iter += 1; + + key_index += size; + match sub_proof { + Some(p) => { + // Return when reaching the end of the key. + if key_index == chunks.len() { + drop(u_ref); + return Ok(()) + } + + // The trie doesn't contain the key. + if p.hash.is_none() { + drop(u_ref); + return Ok(()) + } + cur_hash = p.hash.unwrap(); + cur_key = &chunks[key_index..]; + } + // The trie doesn't contain the key. + None => { + drop(u_ref); + return Ok(()) + } + } + } + } + + /// Decode the RLP value to generate the corresponding type of node, and locate the subproof. + fn decode_node( + &self, merkle: &Merkle, key: &[u8], buf: &[u8], + ) -> Result<(Option>, Option, usize), ProofError> { + let rlp = rlp::Rlp::new(buf); + let size = rlp.item_count().unwrap(); + match size { + EXT_NODE_SIZE => { + let cur_key_path: Vec<_> = to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let (cur_key_path, term) = PartialPath::decode(cur_key_path); + let cur_key = cur_key_path.into_inner(); + + let rlp = rlp.at(1).unwrap(); + let data = if rlp.is_data() { + rlp.as_val::>().unwrap() + } else { + rlp.as_raw().to_vec() + }; + + // Check if the key of current node match with the given key. + if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { + return Ok((None, None, 0)) + } + if term { + let leaf = Node::new(NodeType::Leaf(LeafNode::new(cur_key.clone(), data.clone()))); + let leaf_ptr = merkle.new_node(leaf).map_err(|_| ProofError::DecodeError)?; + Ok(( + Some(leaf_ptr.as_ptr()), + Some(SubProof { rlp: data, hash: None }), + cur_key.len(), + )) + } else { + let ext_ptr = merkle + .new_node(Node::new(NodeType::Extension(ExtNode::new( + cur_key.clone(), + ObjPtr::null(), + Some(data.clone()), + )))) + .map_err(|_| ProofError::DecodeError)?; + let subproof = self.generate_subproof(data).map(|subproof| (subproof, cur_key.len()))?; + Ok((Some(ext_ptr.as_ptr()), subproof.0, subproof.1)) + } + } + BRANCH_NODE_SIZE => { + if key.is_empty() { + return Err(ProofError::NoSuchNode) + } + + // Record rlp values of all children. + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + for i in 0..NBRANCH { + let rlp = rlp.at(i).unwrap(); + // Skip if rlp is empty data + if !rlp.is_empty() { + let data = if rlp.is_data() { + rlp.as_val::>().unwrap() + } else { + rlp.as_raw().to_vec() + }; + chd_eth_rlp[i] = Some(data); + } + } + + // Subproof with the given key must exist. + let index = key[0] as usize; + let data: Vec = if chd_eth_rlp[index].is_none() { + return Err(ProofError::DecodeError) + } else { + chd_eth_rlp[index].clone().unwrap() + }; + let subproof = self.generate_subproof(data).map(|subproof| subproof)?; + + let chd = [None; NBRANCH]; + let t = NodeType::Branch(BranchNode::new(chd, None, chd_eth_rlp)); + let branch_ptr = merkle + .new_node(Node::new(t)) + .map_err(|_| ProofError::ProofNodeMissing)?; + Ok((Some(branch_ptr.as_ptr()), subproof, 1)) + } + _ => Err(ProofError::DecodeError), + } + } } diff --git a/tests/merkle.rs b/tests/merkle.rs index ed247c786253..36b7b768d16d 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -1,81 +1,9 @@ -use firewood::{merkle::*, proof::Proof}; -use shale::{MemStore, MummyObj, ObjPtr}; -use std::rc::Rc; - -struct MerkleSetup { - root: ObjPtr, - merkle: Merkle, -} - -impl MerkleSetup { - fn insert>(&mut self, key: K, val: Vec) { - self.merkle.insert(key, val, self.root).unwrap() - } - - fn remove>(&mut self, key: K) { - self.merkle.remove(key, self.root).unwrap(); - } - - fn get>(&self, key: K) -> Option { - self.merkle.get(key, self.root).unwrap() - } - - fn get_mut>(&mut self, key: K) -> Option { - self.merkle.get_mut(key, self.root).unwrap() - } - - fn root_hash(&self) -> Hash { - self.merkle.root_hash::(self.root).unwrap() - } - - fn dump(&self) -> String { - let mut s = Vec::new(); - self.merkle.dump(self.root, &mut s).unwrap(); - String::from_utf8(s).unwrap() - } - - fn prove>(&self, key: K) -> Proof { - self.merkle.prove::(key, self.root).unwrap() - } - - fn verify_proof>(&self, key: K, proof: &Proof) -> Option> { - let hash: [u8; 32] = self.root_hash().0; - proof.verify_proof(key, hash).unwrap() - } -} - -fn merkle_setup_test(meta_size: u64, compact_size: u64) -> MerkleSetup { - use shale::{compact::CompactSpaceHeader, PlainMem}; - const RESERVED: u64 = 0x1000; - assert!(meta_size > RESERVED); - assert!(compact_size > RESERVED); - let mem_meta = Rc::new(PlainMem::new(meta_size, 0x0)) as Rc; - let mem_payload = Rc::new(PlainMem::new(compact_size, 0x1)); - let compact_header: ObjPtr = unsafe { ObjPtr::new_from_addr(0x0) }; - - mem_meta.write( - compact_header.addr(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), - ); - - let compact_header = unsafe { - MummyObj::ptr_to_obj(mem_meta.as_ref(), compact_header, shale::compact::CompactHeader::MSIZE).unwrap() - }; - - let cache = shale::ObjCache::new(1); - let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); - let mut root = ObjPtr::null(); - Merkle::init_root(&mut root, &space).unwrap(); - MerkleSetup { - root, - merkle: Merkle::new(Box::new(space)), - } -} +use firewood::{merkle_util::*, proof::Proof}; fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( items: Vec<(K, V)>, meta_size: u64, compact_size: u64, ) -> MerkleSetup { - let mut merkle = merkle_setup_test(meta_size, compact_size); + let mut merkle = new_merkle(meta_size, compact_size); for (k, v) in items.iter() { merkle.insert(k, v.as_ref().to_vec()) } @@ -163,7 +91,7 @@ fn test_root_hash_reversed_deletions() { } let mut items: Vec<_> = items.into_iter().collect(); items.sort(); - let mut merkle = merkle_setup_test(0x100000, 0x100000); + let mut merkle = new_merkle(0x100000, 0x100000); let mut hashes = Vec::new(); let mut dumps = Vec::new(); for (k, v) in items.iter() { @@ -222,7 +150,7 @@ fn test_root_hash_random_deletions() { let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); items_ordered.sort(); items_ordered.shuffle(&mut *rng.borrow_mut()); - let mut merkle = merkle_setup_test(0x100000, 0x100000); + let mut merkle = new_merkle(0x100000, 0x100000); for (k, v) in items.iter() { merkle.insert(k, v.to_vec()); } @@ -339,3 +267,28 @@ fn test_empty_tree_proof() { let proof = merkle.prove(key); assert!(proof.0.is_empty()); } + +#[test] +fn test_range_proof() { + let mut items = vec![("doa", "verb"), ("doe", "reindeer"), ("dog", "puppy"), ("ddd", "ok")]; + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let start = 0; + let end = &items.len() - 1; + + let mut proof = merkle.prove(&items[start].0); + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[end].0); + assert!(!end_proof.0.is_empty()); + + proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for i in start + 1..end { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); +} From 94b7d0d35291b91d8d54156de24c407af14bc0b5 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Fri, 16 Dec 2022 23:43:08 -0800 Subject: [PATCH 0025/1053] Address review comments --- src/dynamic_mem.rs | 4 +--- src/merkle.rs | 14 +++++++------- src/merkle_util.rs | 3 ++- src/proof.rs | 13 +++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/dynamic_mem.rs b/src/dynamic_mem.rs index acdab127b7fb..014e32aeb5e0 100644 --- a/src/dynamic_mem.rs +++ b/src/dynamic_mem.rs @@ -16,9 +16,7 @@ pub struct DynamicMem { impl DynamicMem { pub fn new(size: u64, id: SpaceID) -> Self { - let mut space = Vec::new(); - space.resize(size as usize, 0); - let space = Rc::new(UnsafeCell::new(space)); + let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); Self { space, id } } diff --git a/src/merkle.rs b/src/merkle.rs index 9bc707b8391c..47d25fce0a32 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -182,7 +182,7 @@ impl BranchNode { fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { let mut stream = rlp::RlpStream::new_list(17); - for (index, c) in self.chd.iter().enumerate() { + for (i, c) in self.chd.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); @@ -199,12 +199,12 @@ impl BranchNode { } } None => { - // Check if there is already caclucated rlp for the child, which - // can happen when manually construct a trie from proof. - if self.chd_eth_rlp[index].is_none() { + // Check if there is already a calculated rlp for the child, which + // can happen when manually constructing a trie from proof. + if self.chd_eth_rlp[i].is_none() { stream.append_empty_data() } else { - stream.append_raw(&self.chd_eth_rlp[index].clone().unwrap(), 1) + stream.append_raw(&self.chd_eth_rlp[i].clone().unwrap(), 1) } } }; @@ -279,8 +279,8 @@ impl ExtNode { stream.append_raw(r.get_eth_rlp::(store), 1); } } else { - // Check if there is already caclucated rlp for the child, which - // can happen when manually construct a trie from proof. + // Check if there is already a caclucated rlp for the child, which + // can happen when manually constructing a trie from proof. if self.2.is_none() { stream.append_empty_data(); } else { diff --git a/src/merkle_util.rs b/src/merkle_util.rs index dad779a7b878..3298f24bb67f 100644 --- a/src/merkle_util.rs +++ b/src/merkle_util.rs @@ -79,7 +79,8 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { }; let cache = shale::ObjCache::new(1); - let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("CompactSpace init fail"); let mut root = ObjPtr::null(); Merkle::init_root(&mut root, &space).unwrap(); MerkleSetup { diff --git a/src/proof.rs b/src/proof.rs index 94180d977db6..847a36f37e6b 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -157,14 +157,14 @@ impl Proof { } for v in vals.iter() { - if v.as_ref().len() == 0 { + if v.as_ref().is_empty() { return Err(ProofError::RangeHasDeletion) } } // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. - if self.0.len() == 0 { + if self.0.is_empty() { // Use in-memory merkle let mut merkle = new_merkle(0x10000, 0x10000); for (index, k) in keys.iter().enumerate() { @@ -201,9 +201,8 @@ impl Proof { // proof is also allowed. self.proof_to_path(last_key, root_hash, &mut merkle_setup)?; - // let merkle = merkle_setup.get_merkle_mut(); - for index in 0..keys.len() { - merkle_setup.insert(keys[index].as_ref(), vals[index].as_ref().to_vec()) + for (i, _) in keys.iter().enumerate() { + merkle_setup.insert(keys[i].as_ref(), vals[i].as_ref().to_vec()) } // Calculate the hash @@ -235,7 +234,6 @@ impl Proof { let proofs_map = &self.0; let mut key_index = 0; let mut branch_index: u8 = 0; - // let mut root = ObjPtr::null(); let mut iter = 0; loop { let cur_proof = proofs_map.get(&cur_hash).ok_or(ProofError::ProofNodeMissing)?; @@ -278,6 +276,7 @@ impl Proof { chd_ptr = Some(node); } } + // We should not hit a leaf node as a parent. _ => return Err(ProofError::DecodeError), }; @@ -300,6 +299,7 @@ impl Proof { Some(p) => { // Return when reaching the end of the key. if key_index == chunks.len() { + // Release the handle to the node. drop(u_ref); return Ok(()) } @@ -400,6 +400,7 @@ impl Proof { .map_err(|_| ProofError::ProofNodeMissing)?; Ok((Some(branch_ptr.as_ptr()), subproof, 1)) } + // RLP length can only be the two cases above. _ => Err(ProofError::DecodeError), } } From 879a73cda956b78e83e59ffa0ffc6bde66c02ff8 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Tue, 20 Dec 2022 21:28:32 -0800 Subject: [PATCH 0026/1053] fix: Unset the pre calculated RLP values of interval nodes --- src/merkle.rs | 14 ++- src/proof.rs | 299 ++++++++++++++++++++++++++++++++++++++++++++++-- tests/merkle.rs | 66 +++++++++++ 3 files changed, 371 insertions(+), 8 deletions(-) diff --git a/src/merkle.rs b/src/merkle.rs index 47d25fce0a32..ac013ae844c0 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -233,6 +233,14 @@ impl BranchNode { pub fn chd_mut(&mut self) -> &mut [Option>; NBRANCH] { &mut self.chd } + + pub fn chd_eth_rlp(&self) -> &[Option>; NBRANCH] { + &self.chd_eth_rlp + } + + pub fn chd_eth_rlp_mut(&mut self) -> &mut [Option>; NBRANCH] { + &mut self.chd_eth_rlp + } } #[derive(PartialEq, Eq, Clone)] @@ -294,6 +302,10 @@ impl ExtNode { ExtNode(PartialPath(path), chd, chd_eth_rlp) } + pub fn path(&self) -> &PartialPath { + &self.0 + } + pub fn chd(&self) -> ObjPtr { self.1 } @@ -634,7 +646,7 @@ impl MummyItem for Node { cur.write_all(&u32::MAX.to_le_bytes()).unwrap(); } } - // Since child eth rlp is immutable after initialization (only used for range proof), + // Since child eth rlp will only be unset after initialization (only used for range proof), // it is fine to encode its value adjacent to other fields. Same for extention node. for rlp in n.chd_eth_rlp.iter() { match rlp { diff --git a/src/proof.rs b/src/proof.rs index 847a36f37e6b..8bd04cc9d2a4 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use sha3::Digest; use shale::ObjPtr; +use std::cmp::Ordering; use std::collections::HashMap; /// Hash -> RLP encoding map @@ -23,6 +24,9 @@ pub enum ProofError { InvalidEdgeKeys, InconsistentEdgeKeys, NodesInsertionError, + NodeNotInTrie, + InvalidNode, + EmptyRange, } const EXT_NODE_SIZE: usize = 2; @@ -194,12 +198,19 @@ impl Proof { // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. let mut merkle_setup = new_merkle(0x100000, 0x100000); - self.proof_to_path(first_key, root_hash, &mut merkle_setup)?; + self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. - self.proof_to_path(last_key, root_hash, &mut merkle_setup)?; + self.proof_to_path(last_key.as_ref(), root_hash, &mut merkle_setup, true)?; + + // Remove all internal calcuated RLP values. All the removed parts should + // be re-filled(or re-constructed) by the given leaves range. + let empty = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; + if empty { + merkle_setup = new_merkle(0x100000, 0x100000); + } for (i, _) in keys.iter().enumerate() { merkle_setup.insert(keys[i].as_ref(), vals[i].as_ref().to_vec()) @@ -220,7 +231,7 @@ impl Proof { /// /// The given edge proof is allowed to be an existent or non-existent proof. fn proof_to_path>( - &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, + &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent: bool, ) -> Result<(), ProofError> { let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); @@ -277,7 +288,7 @@ impl Proof { } } // We should not hit a leaf node as a parent. - _ => return Err(ProofError::DecodeError), + _ => return Err(ProofError::InvalidNode), }; if chd_ptr.is_some() { @@ -307,15 +318,24 @@ impl Proof { // The trie doesn't contain the key. if p.hash.is_none() { drop(u_ref); - return Ok(()) + if allow_non_existent { + return Ok(()) + } + return Err(ProofError::NodeNotInTrie) } cur_hash = p.hash.unwrap(); cur_key = &chunks[key_index..]; } - // The trie doesn't contain the key. + // The trie doesn't contain the key. It's possible + // the proof is a non-existing proof, but at least + // we can prove all resolved nodes are correct, it's + // enough for us to prove range. None => { drop(u_ref); - return Ok(()) + if allow_non_existent { + return Ok(()) + } + return Err(ProofError::NodeNotInTrie) } } } @@ -405,3 +425,268 @@ impl Proof { } } } + +// unsetInternal removes all internal node references. +// It should be called after a trie is constructed with two edge paths. Also +// the given boundary keys must be the one used to construct the edge paths. +// +// It's the key step for range proof. All visited nodes should be marked dirty +// since the node content might be modified. Besides it can happen that some +// fullnodes only have one child which is disallowed. But if the proof is valid, +// the missing children will be filled, otherwise it will be thrown anyway. +// +// Note we have the assumption here the given boundary keys are different +// and right is larger than left. +fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right: K) -> Result { + // Add the sentinel root + let mut left_chunks = vec![0]; + left_chunks.extend(to_nibbles(left.as_ref())); + // Add the sentinel root + let mut right_chunks = vec![0]; + right_chunks.extend(to_nibbles(right.as_ref())); + let mut index = 0; + let root = merkle_setup.get_root(); + let merkle = merkle_setup.get_merkle_mut(); + let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; + let mut parent = ObjPtr::null(); + + let mut fork_left: Ordering = Ordering::Equal; + let mut fork_right: Ordering = Ordering::Equal; + + loop { + match &u_ref.inner() { + NodeType::Branch(n) => { + // If either the node pointed by left proof or right proof is nil, + // stop here and the forkpoint is the fullnode. + let left_node = n.chd()[left_chunks[index] as usize]; + let right_node = n.chd()[right_chunks[index] as usize]; + if left_node.is_none() || right_node.is_none() || left_node.unwrap() != right_node.unwrap() { + break + } + parent = u_ref.as_ptr(); + u_ref = merkle + .get_node(left_node.unwrap()) + .map_err(|_| ProofError::DecodeError)?; + index += 1; + } + NodeType::Extension(n) => { + // If either the key of left proof or right proof doesn't match with + // shortnode, stop here and the forkpoint is the shortnode. + let cur_key = n.path().clone().into_inner(); + if left_chunks.len() - index < cur_key.len() { + fork_left = compare(&left_chunks[index..], &cur_key) + } else { + fork_left = compare(&left_chunks[index..index + cur_key.len()], &cur_key) + } + + if right_chunks.len() - index < cur_key.len() { + fork_right = compare(&right_chunks[index..], &cur_key) + } else { + fork_right = compare(&right_chunks[index..index + cur_key.len()], &cur_key) + } + + if !fork_left.is_eq() || !fork_right.is_eq() { + break + } + parent = u_ref.as_ptr(); + u_ref = merkle.get_node(n.chd()).map_err(|_| ProofError::DecodeError)?; + index += cur_key.len(); + } + _ => return Err(ProofError::InvalidNode), + } + } + + match &u_ref.inner() { + NodeType::Branch(n) => { + let left_node = n.chd()[left_chunks[index] as usize]; + let right_node = n.chd()[right_chunks[index] as usize]; + + // unset all internal nodes calculated RLP value in the forkpoint + for i in left_chunks[index] + 1..right_chunks[index] { + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_eth_rlp_mut()[i as usize] = None; + }) + .unwrap(); + } + let p = u_ref.as_ptr(); + drop(u_ref); + unset(merkle, p, left_node, left_chunks[index..].to_vec(), 1, false)?; + unset(merkle, p, right_node, right_chunks[index..].to_vec(), 1, true)?; + return Ok(false) + } + NodeType::Extension(n) => { + // There can have these five scenarios: + // - both proofs are less than the trie path => no valid range + // - both proofs are greater than the trie path => no valid range + // - left proof is less and right proof is greater => valid range, unset the shortnode entirely + // - left proof points to the shortnode, but right proof is greater + // - right proof points to the shortnode, but left proof is less + let node = n.chd(); + let cur_key = n.path().clone().into_inner(); + if fork_left.is_lt() && fork_right.is_lt() { + drop(u_ref); + return Err(ProofError::EmptyRange) + } + if fork_left.is_gt() && fork_right.is_gt() { + drop(u_ref); + return Err(ProofError::EmptyRange) + } + if !fork_left.is_eq() && !fork_right.is_eq() { + // The fork point is root node, unset the entire trie + if parent.is_null() { + drop(u_ref); + return Ok(true) + } + let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().unwrap(); + pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + }) + .unwrap(); + drop(p_ref); + drop(u_ref); + return Ok(false) + } + let p = u_ref.as_ptr(); + drop(u_ref); + // Only one proof points to non-existent key. + if !fork_right.is_eq() { + unset( + merkle, + p, + Some(node), + left_chunks[index..].to_vec(), + cur_key.len(), + false, + )?; + return Ok(false) + } + if !fork_left.is_eq() { + unset( + merkle, + p, + Some(node), + right_chunks[index..].to_vec(), + cur_key.len(), + true, + )?; + return Ok(false) + } + return Ok(false) + } + _ => return Err(ProofError::InvalidNode), + } +} + +// unset removes all internal node references either the left most or right most. +// It can meet these scenarios: +// +// - The given path is existent in the trie, unset the associated nodes with the +// specific direction +// - The given path is non-existent in the trie +// - the fork point is a fullnode, the corresponding child pointed by path +// is nil, return +// - the fork point is a shortnode, the shortnode is included in the range, +// keep the entire branch and return. +// - the fork point is a shortnode, the shortnode is excluded in the range, +// unset the entire branch. +fn unset>( + merkle: &Merkle, parent: ObjPtr, node: Option>, key: K, index: usize, remove_left: bool, +) -> Result<(), ProofError> { + if node.is_none() { + // If the node is nil, then it's a child of the fork point + // fullnode(it's a non-existent branch). + return Ok(()) + } + // Add the sentinel root + let mut chunks = vec![0]; + chunks.extend(to_nibbles(key.as_ref())); + + let mut u_ref = merkle.get_node(node.unwrap()).map_err(|_| ProofError::NoSuchNode)?; + let p = u_ref.as_ptr(); + + match &u_ref.inner() { + NodeType::Branch(n) => { + let node = n.chd()[chunks[index] as usize]; + if remove_left { + for i in 0..chunks[index] { + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_eth_rlp_mut()[i as usize] = None; + }) + .unwrap(); + } + } else { + for i in chunks[index] + 1..16 { + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_eth_rlp_mut()[i as usize] = None; + }) + .unwrap(); + } + } + + drop(u_ref); + return unset(merkle, p, node, key, index + 1, remove_left) + } + NodeType::Extension(n) => { + let cur_key = n.path().clone().into_inner(); + let node = n.chd(); + if chunks[index..].len() < cur_key.len() || + !compare(&cur_key, &chunks[index..index + cur_key.len()]).is_eq() + { + let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; + // Find the fork point, it's an non-existent branch. + if remove_left { + if compare(&cur_key, &chunks[index..]).is_lt() { + // The key of fork shortnode is less than the path + // (it belongs to the range), unset the entire + // branch. The parent must be a fullnode. + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().unwrap(); + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + //else { + // The key of fork shortnode is greater than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} + } else { + if compare(&cur_key, &chunks[index..]).is_gt() { + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().unwrap(); + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + //else { + // The key of fork shortnode is less than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} + } + drop(u_ref); + drop(p_ref); + return Ok(()) + } + + drop(u_ref); + return unset(merkle, p, Some(node), key, index + cur_key.len(), remove_left) + } + NodeType::Leaf(_) => (), + } + + return Ok(()) +} diff --git a/tests/merkle.rs b/tests/merkle.rs index 36b7b768d16d..216ed84ee426 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -292,3 +292,69 @@ fn test_range_proof() { merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); } + +#[test] +fn test_range_proof_with_non_existent_proof() { + let mut items = vec![ + (std::str::from_utf8(&[0x7]).unwrap(), "verb"), + (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), + (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), + (std::str::from_utf8(&[0x6]).unwrap(), "coin"), + (std::str::from_utf8(&[0x3]).unwrap(), "stallion"), + ]; + + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let start = 0; + let end = &items.len() - 1; + + let mut proof = merkle.prove(std::str::from_utf8(&[0x2]).unwrap()); + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(std::str::from_utf8(&[0x8]).unwrap()); + assert!(!end_proof.0.is_empty()); + + proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for i in start..=end { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); +} + +#[test] +#[should_panic] +fn test_range_proof_with_invalid_non_existent_proof() { + let mut items = vec![ + (std::str::from_utf8(&[0x8]).unwrap(), "verb"), + (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), + (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), + (std::str::from_utf8(&[0x6]).unwrap(), "coin"), + (std::str::from_utf8(&[0x2]).unwrap(), "stallion"), + ]; + + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let start = 0; + let end = &items.len() - 1; + + let mut proof = merkle.prove(std::str::from_utf8(&[0x3]).unwrap()); + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(std::str::from_utf8(&[0x7]).unwrap()); + assert!(!end_proof.0.is_empty()); + + proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + // Create gap + for i in start + 2..end - 1 { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); +} From 72bf05d68714cae8af0d661b8525eacaa23bc193 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Wed, 21 Dec 2022 10:49:05 -0800 Subject: [PATCH 0027/1053] Address review comments. --- src/proof.rs | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/proof.rs b/src/proof.rs index 8bd04cc9d2a4..eb760c85747b 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -207,8 +207,9 @@ impl Proof { // Remove all internal calcuated RLP values. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. - let empty = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; - if empty { + let fork_at_root = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; + // If the fork point is the root, the trie should be empty, start with a new one. + if fork_at_root { merkle_setup = new_merkle(0x100000, 0x100000); } @@ -231,7 +232,7 @@ impl Proof { /// /// The given edge proof is allowed to be an existent or non-existent proof. fn proof_to_path>( - &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent: bool, + &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, ) -> Result<(), ProofError> { let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); @@ -318,7 +319,7 @@ impl Proof { // The trie doesn't contain the key. if p.hash.is_none() { drop(u_ref); - if allow_non_existent { + if allow_non_existent_node { return Ok(()) } return Err(ProofError::NodeNotInTrie) @@ -332,7 +333,7 @@ impl Proof { // enough for us to prove range. None => { drop(u_ref); - if allow_non_existent { + if allow_non_existent_node { return Ok(()) } return Err(ProofError::NodeNotInTrie) @@ -426,17 +427,19 @@ impl Proof { } } -// unsetInternal removes all internal node references. +// unset_internal removes all internal node references. // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. // -// It's the key step for range proof. All visited nodes should be marked dirty -// since the node content might be modified. Besides it can happen that some -// fullnodes only have one child which is disallowed. But if the proof is valid, +// It's the key step for range proof. The precalucated RLP value of all internal +// nodes should be removed. But if the proof is valid, // the missing children will be filled, otherwise it will be thrown anyway. // // Note we have the assumption here the given boundary keys are different // and right is larger than left. +// +// The return value indicates if the fork point is root node. If so, unset the +// entire trie. fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right: K) -> Result { // Add the sentinel root let mut left_chunks = vec![0]; @@ -444,7 +447,6 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right // Add the sentinel root let mut right_chunks = vec![0]; right_chunks.extend(to_nibbles(right.as_ref())); - let mut index = 0; let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; @@ -453,6 +455,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right let mut fork_left: Ordering = Ordering::Equal; let mut fork_right: Ordering = Ordering::Equal; + let mut index = 0; loop { match &u_ref.inner() { NodeType::Branch(n) => { @@ -492,6 +495,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right u_ref = merkle.get_node(n.chd()).map_err(|_| ProofError::DecodeError)?; index += cur_key.len(); } + // The fork point cannot be a leaf since it doesn't have any children. _ => return Err(ProofError::InvalidNode), } } @@ -512,8 +516,8 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right } let p = u_ref.as_ptr(); drop(u_ref); - unset(merkle, p, left_node, left_chunks[index..].to_vec(), 1, false)?; - unset(merkle, p, right_node, right_chunks[index..].to_vec(), 1, true)?; + unset_node_ref(merkle, p, left_node, left_chunks[index..].to_vec(), 1, false)?; + unset_node_ref(merkle, p, right_node, right_chunks[index..].to_vec(), 1, true)?; return Ok(false) } NodeType::Extension(n) => { @@ -533,7 +537,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right drop(u_ref); return Err(ProofError::EmptyRange) } - if !fork_left.is_eq() && !fork_right.is_eq() { + if fork_left.is_ne() && fork_right.is_ne() { // The fork point is root node, unset the entire trie if parent.is_null() { drop(u_ref); @@ -542,7 +546,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; p_ref .write(|p| { - let pp = p.inner_mut().as_branch_mut().unwrap(); + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; }) .unwrap(); @@ -553,8 +557,8 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right let p = u_ref.as_ptr(); drop(u_ref); // Only one proof points to non-existent key. - if !fork_right.is_eq() { - unset( + if fork_right.is_ne() { + unset_node_ref( merkle, p, Some(node), @@ -564,8 +568,8 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right )?; return Ok(false) } - if !fork_left.is_eq() { - unset( + if fork_left.is_ne() { + unset_node_ref( merkle, p, Some(node), @@ -593,7 +597,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right // keep the entire branch and return. // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -fn unset>( +fn unset_node_ref>( merkle: &Merkle, parent: ObjPtr, node: Option>, key: K, index: usize, remove_left: bool, ) -> Result<(), ProofError> { if node.is_none() { @@ -632,13 +636,12 @@ fn unset>( } drop(u_ref); - return unset(merkle, p, node, key, index + 1, remove_left) + return unset_node_ref(merkle, p, node, key, index + 1, remove_left) } NodeType::Extension(n) => { let cur_key = n.path().clone().into_inner(); let node = n.chd(); - if chunks[index..].len() < cur_key.len() || - !compare(&cur_key, &chunks[index..index + cur_key.len()]).is_eq() + if chunks[index..].len() < cur_key.len() || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() { let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; // Find the fork point, it's an non-existent branch. @@ -649,7 +652,7 @@ fn unset>( // branch. The parent must be a fullnode. p_ref .write(|p| { - let pp = p.inner_mut().as_branch_mut().unwrap(); + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; }) .unwrap(); @@ -683,8 +686,9 @@ fn unset>( } drop(u_ref); - return unset(merkle, p, Some(node), key, index + cur_key.len(), remove_left) + return unset_node_ref(merkle, p, Some(node), key, index + cur_key.len(), remove_left) } + // Noop for leaf node as it doesn't have any children and no precalculated RLP value stored. NodeType::Leaf(_) => (), } From a74908c6d6c966f16b2316fc09fc256eb17cdbc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 13:32:50 +0000 Subject: [PATCH 0028/1053] build(deps): update lru requirement from 0.8.0 to 0.9.0 Updates the requirements on [lru](https://github.com/jeromefroe/lru-rs) to permit the latest version. - [Release notes](https://github.com/jeromefroe/lru-rs/releases) - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.8.0...0.9.0) --- updated-dependencies: - dependency-name: lru dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e92a701d0b52..ea9bd317ae7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rlp = "0.5.2" sha3 = "0.10.2" once_cell = "1.13.1" hex = "0.4.3" -lru = "0.8.0" +lru = "0.9.0" libaio-futures = "0.2.2" nix = "0.26.1" typed-builder = "0.11.0" From 38bbeb7341427fe66d1944d92071dafbd8ce507d Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 23 Dec 2022 11:43:28 -0500 Subject: [PATCH 0029/1053] cli: Add generic key value insertion command Signed-off-by: Dan Sover --- src/bin/fwdctl.rs | 4 ++++ src/bin/insert.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ src/bin/tests/cli.rs | 13 +++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/bin/insert.rs diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index 08fb2464df86..e97d8fe07261 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; pub mod create; +pub mod insert; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -26,6 +27,8 @@ struct Cli { enum Commands { /// Create a new firewood database Create(create::Options), + /// Insert a key/value pair into the database + Insert(insert::Options), } fn main() -> Result<()> { @@ -37,5 +40,6 @@ fn main() -> Result<()> { match &cli.command { Commands::Create(opts) => create::run(opts), + Commands::Insert(opts) => insert::run(opts), } } diff --git a/src/bin/insert.rs b/src/bin/insert.rs new file mode 100644 index 000000000000..32fc8301c365 --- /dev/null +++ b/src/bin/insert.rs @@ -0,0 +1,50 @@ +use anyhow::{anyhow, Result}; +use clap::Args; +use firewood::db::{DBConfig, WALConfig, DB}; +use log; + +#[derive(Debug, Args)] +pub struct Options { + /// The key to insert + #[arg(long, short = 'k', required = true, value_name = "KEY", help = "Key to insert")] + pub key: String, + + /// The value to insert + #[arg(long, short = 'v', required = true, value_name = "VALUE", help = "Value to insert")] + pub value: String, + + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + required = true, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db_path: String, +} + +pub fn run(opts: &Options) -> Result<()> { + log::debug!("inserting key value pair {:?}", opts); + let cfg = DBConfig::builder() + .truncate(false) + .wal(WALConfig::builder().max_revisions(10).build()); + + let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + Ok(db) => db, + Err(_) => return Err(anyhow!("error opening database")), + }; + + let x = match db + .new_writebatch() + .kv_insert(opts.key.clone(), opts.value.bytes().collect()) + { + Ok(insertion) => { + insertion.commit(); + log::info!("{}", opts.key); + + Ok(()) + } + Err(_) => return Err(anyhow!("error inserting key/value pair into the database")), + }; + x +} diff --git a/src/bin/tests/cli.rs b/src/bin/tests/cli.rs index 1ad4c16ac25c..470e19736272 100644 --- a/src/bin/tests/cli.rs +++ b/src/bin/tests/cli.rs @@ -6,7 +6,7 @@ const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); #[test] -fn prints_version() -> Result<()> { +fn fwdctl_prints_version() -> Result<()> { let expected_version_output: String = format!("{PRG} {VERSION}"); // version is defined and succeeds with the desired output @@ -18,3 +18,14 @@ fn prints_version() -> Result<()> { Ok(()) } + +#[test] +fn fwdctl_insert_successful() -> Result<()> { + Command::cargo_bin(PRG)? + .args(["insert --key year --value 2023"]) + .assert() + .success() + .stdout(predicate::str::equals("year")); + + Ok(()) +} From 29cbbf26730958a7eadfb7a15fe9e7dbe20a67a3 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 4 Jan 2023 15:27:05 +0000 Subject: [PATCH 0030/1053] cli: Add get command Signed-off-by: Dan Sover --- src/bin/README.md | 3 +++ src/bin/fwdctl.rs | 4 ++++ src/bin/get.rs | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/bin/get.rs diff --git a/src/bin/README.md b/src/bin/README.md index a7ac0a8f20e3..fd2d4d45c720 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -14,6 +14,7 @@ $ ./target/release/fwdctl -h ## Supported commands * `fwdctl create`: Create a new firewood database. +* `fwdctl get`: Get the code associated with a key in the database ## Examples * fwdctl create @@ -25,4 +26,6 @@ $ fwdctl create --name=my-db # Look inside, there are several folders representing different components of firewood, including the WAL $ ls my-db ``` +* fwdctl get + diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index e97d8fe07261..87cce3af4988 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; pub mod create; +pub mod get; pub mod insert; #[derive(Parser)] @@ -29,6 +30,8 @@ enum Commands { Create(create::Options), /// Insert a key/value pair into the database Insert(insert::Options), + /// Get values associated with a key + Get(get::Options), } fn main() -> Result<()> { @@ -41,5 +44,6 @@ fn main() -> Result<()> { match &cli.command { Commands::Create(opts) => create::run(opts), Commands::Insert(opts) => insert::run(opts), + Commands::Get(opts) => get::run(opts), } } diff --git a/src/bin/get.rs b/src/bin/get.rs new file mode 100644 index 000000000000..048b0dfec899 --- /dev/null +++ b/src/bin/get.rs @@ -0,0 +1,39 @@ +use anyhow::{anyhow, Result}; +use clap::Args; +use firewood::db::{DBConfig, WALConfig, DB}; +use log; + +#[derive(Debug, Args)] +pub struct Options { + /// The key to get the value for + #[arg(long, required = true, value_name = "KEY", help = "Key to get")] + pub key: String, + + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + required = true, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db_path: String, +} + +pub fn run(opts: &Options) -> Result<()> { + log::debug!("get key value pair {:?}", opts); + let cfg = DBConfig::builder() + .truncate(false) + .wal(WALConfig::builder().max_revisions(10).build()); + + let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + Ok(db) => db, + Err(_) => return Err(anyhow!("error opening database")), + }; + + match db.get_code(opts.key.as_bytes()) { + Ok(val) => log::info!("{:#?}", val), + Err(_) => return Err(anyhow!("error getting key")), + } + + Ok(()) +} From c04cf2b6a8d98aebdc39ee41c75822942026918e Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 6 Jan 2023 15:30:37 +0000 Subject: [PATCH 0031/1053] cli: Add delete command Signed-off-by: Dan Sover --- src/bin/README.md | 7 +++++-- src/bin/delete.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/bin/fwdctl.rs | 4 ++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/bin/delete.rs diff --git a/src/bin/README.md b/src/bin/README.md index fd2d4d45c720..5c6c66f837de 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -14,7 +14,9 @@ $ ./target/release/fwdctl -h ## Supported commands * `fwdctl create`: Create a new firewood database. -* `fwdctl get`: Get the code associated with a key in the database +* `fwdctl get`: Get the code associated with a key in the database. +* `fwdctl insert`: Insert a key/value pair into the generic key/value store. +* `fwdctl delete`: Delete a key/value pair from the database. ## Examples * fwdctl create @@ -27,5 +29,6 @@ $ fwdctl create --name=my-db $ ls my-db ``` * fwdctl get - +* fwdctl insert --key --value +* fwdctl delete diff --git a/src/bin/delete.rs b/src/bin/delete.rs new file mode 100644 index 000000000000..c1d6d52b1d95 --- /dev/null +++ b/src/bin/delete.rs @@ -0,0 +1,39 @@ +use anyhow::{anyhow, Result}; +use clap::Args; +use firewood::db::{DBConfig, WALConfig, DB}; +use log; + +#[derive(Debug, Args)] +pub struct Options { + /// The key to delete + #[arg(long, required = true, value_name = "KEY", help = "Key to delete")] + pub key: String, + + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + required = true, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db_path: String, +} + +pub fn run(opts: &Options) -> Result<()> { + log::debug!("deleting key {:?}", opts); + let cfg = DBConfig::builder() + .truncate(false) + .wal(WALConfig::builder().max_revisions(10).build()); + + let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + Ok(db) => db, + Err(_) => return Err(anyhow!("error opening database")), + }; + + let mut account = None; + if let Err(_) = db.new_writebatch().delete_account(opts.key.as_bytes(), &mut account) { + return Err(anyhow!("error deleting key")) + } + + Ok(()) +} diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index 87cce3af4988..9db058a813d8 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; pub mod create; +pub mod delete; pub mod get; pub mod insert; @@ -32,6 +33,8 @@ enum Commands { Insert(insert::Options), /// Get values associated with a key Get(get::Options), + /// Delete values associated with a key + Delete(delete::Options), } fn main() -> Result<()> { @@ -45,5 +48,6 @@ fn main() -> Result<()> { Commands::Create(opts) => create::run(opts), Commands::Insert(opts) => insert::run(opts), Commands::Get(opts) => get::run(opts), + Commands::Delete(opts) => delete::run(opts), } } From 638a479a476e3f51cf72eea78bdc863aa0f32741 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 6 Jan 2023 15:44:55 +0000 Subject: [PATCH 0032/1053] cli: Move cli tests under tests/ Signed-off-by: Dan Sover --- src/bin/get.rs | 2 +- src/merkle.rs | 2 +- {src/bin/tests => tests}/cli.rs | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) rename {src/bin/tests => tests}/cli.rs (66%) diff --git a/src/bin/get.rs b/src/bin/get.rs index 048b0dfec899..88b439821e72 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -6,7 +6,7 @@ use log; #[derive(Debug, Args)] pub struct Options { /// The key to get the value for - #[arg(long, required = true, value_name = "KEY", help = "Key to get")] + #[arg(long, short = 'k', required = true, value_name = "KEY", help = "Key to get")] pub key: String, /// The database path (if no path is provided, return an error). Defaults to firewood. diff --git a/src/merkle.rs b/src/merkle.rs index ac013ae844c0..af528b91f56d 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -726,7 +726,7 @@ fn test_merkle_node_encoding() { None, NodeType::Extension(ExtNode( PartialPath(vec![0x1, 0x2, 0x3]), - { ObjPtr::null() }, + ObjPtr::null(), Some(vec![0x1, 0x2, 0x3]), )), ), diff --git a/src/bin/tests/cli.rs b/tests/cli.rs similarity index 66% rename from src/bin/tests/cli.rs rename to tests/cli.rs index 470e19736272..32b5ac3e3f04 100644 --- a/src/bin/tests/cli.rs +++ b/tests/cli.rs @@ -4,10 +4,11 @@ use predicates::prelude::*; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); +const FIREWOOD: &str = "firewood"; #[test] fn fwdctl_prints_version() -> Result<()> { - let expected_version_output: String = format!("{PRG} {VERSION}"); + let expected_version_output: String = format!("{FIREWOOD} {VERSION}\n"); // version is defined and succeeds with the desired output Command::cargo_bin(PRG)? @@ -20,12 +21,13 @@ fn fwdctl_prints_version() -> Result<()> { } #[test] +#[ignore] // TODO fn fwdctl_insert_successful() -> Result<()> { Command::cargo_bin(PRG)? - .args(["insert --key year --value 2023"]) - .assert() - .success() - .stdout(predicate::str::equals("year")); + .args(["insert --key year --value 2023"]) + .assert() + .success() + .stdout(predicate::str::contains("year")); Ok(()) } From 8957acdda788d4d128ca33cccfdcf0fd62cfec55 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 9 Jan 2023 20:09:50 +0000 Subject: [PATCH 0033/1053] cli: Only use kv_ functions in fwdctl Signed-off-by: Dan Sover --- src/bin/delete.rs | 4 ++-- src/bin/get.rs | 2 +- src/db.rs | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/bin/delete.rs b/src/bin/delete.rs index c1d6d52b1d95..1990f58d83b6 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -30,8 +30,8 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - let mut account = None; - if let Err(_) = db.new_writebatch().delete_account(opts.key.as_bytes(), &mut account) { + let mut account: Option> = None; + if let Err(_) = db.new_writebatch().kv_remove(opts.key, &mut account) { return Err(anyhow!("error deleting key")) } diff --git a/src/bin/get.rs b/src/bin/get.rs index 88b439821e72..bc251ed9e16d 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -30,7 +30,7 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - match db.get_code(opts.key.as_bytes()) { + match db.kv_get(opts.key.as_bytes()) { Ok(val) => log::info!("{:#?}", val), Err(_) => return Err(anyhow!("error getting key")), } diff --git a/src/db.rs b/src/db.rs index c8611b682b16..fac1a6c04230 100644 --- a/src/db.rs +++ b/src/db.rs @@ -246,6 +246,15 @@ impl DBRev { .map_err(DBError::Merkle) } + /// Get a value associated with a key + pub fn kv_get_merkle(&self, key: &[u8]) -> Result, DBError> { + let obj_ref = self.merkle.get(key, self.header.kv_root).map_err(DBError::Merkle)?; + match obj_ref { + None => Ok(vec![]), + Some(obj) => Ok(obj.to_vec()), + } + } + /// Dump the MPT of the generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.merkle.dump(self.header.kv_root, w).map_err(DBError::Merkle) @@ -618,6 +627,11 @@ impl DB { self.inner.lock().latest.kv_root_hash() } + /// Get a value in the kv store associated with a particular key. + pub fn kv_get(&self, key: &[u8]) -> Result, DBError> { + self.inner.lock().latest.kv_get_merkle(key) + } + /// Get root hash of the latest world state of all accounts. pub fn root_hash(&self) -> Result { self.inner.lock().latest.root_hash() From be1fcdb0639d62594f6cf56da12623d6dc6df19b Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 9 Jan 2023 20:17:33 +0000 Subject: [PATCH 0034/1053] docs: Update fwdctl README with storage information --- src/bin/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bin/README.md b/src/bin/README.md index 5c6c66f837de..f7a6d047be52 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -2,6 +2,11 @@ `fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. +> Note: firewood has two separate storage areas in the database. One is a generic key-value store. +The other is EVM-based account model storage, based on Merkle-Patricia Tries. The generic key-value +store is being written to by the cli currently. Support for commands that use the account based storage +will be supported in a future release of firewood. + ## Building locally *Note: fwdctl is linux-only* ``` From a3c8bc3dbceff1e856a270d06c3fedaf5d973df0 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 9 Jan 2023 20:26:07 +0000 Subject: [PATCH 0035/1053] fix: Run cargo clippy --fix Signed-off-by: Dan Sover --- src/bin/delete.rs | 2 +- src/db.rs | 2 +- src/dynamic_mem.rs | 4 ++-- src/merkle.rs | 12 ++++++------ src/proof.rs | 41 ++++++++++++++++++----------------------- tests/merkle.rs | 4 ++-- 6 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/bin/delete.rs b/src/bin/delete.rs index 1990f58d83b6..ef7c61f2dde5 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -31,7 +31,7 @@ pub fn run(opts: &Options) -> Result<()> { }; let mut account: Option> = None; - if let Err(_) = db.new_writebatch().kv_remove(opts.key, &mut account) { + if let Err(_) = db.new_writebatch().kv_remove(opts.key.clone(), &mut account) { return Err(anyhow!("error deleting key")) } diff --git a/src/db.rs b/src/db.rs index fac1a6c04230..ed7111a6f92f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -246,7 +246,7 @@ impl DBRev { .map_err(DBError::Merkle) } - /// Get a value associated with a key + /// Get a value associated with a key. pub fn kv_get_merkle(&self, key: &[u8]) -> Result, DBError> { let obj_ref = self.merkle.get(key, self.header.kv_root).map_err(DBError::Merkle)?; match obj_ref { diff --git a/src/dynamic_mem.rs b/src/dynamic_mem.rs index 014e32aeb5e0..999ba923c519 100644 --- a/src/dynamic_mem.rs +++ b/src/dynamic_mem.rs @@ -32,7 +32,7 @@ impl MemStore for DynamicMem { let size = offset + length; // Increase the size if the request range exceeds the current limit. if size > self.get_space_mut().len() { - self.get_space_mut().resize(size as usize, 0); + self.get_space_mut().resize(size, 0); } Some(Box::new(DynamicMemView { offset, @@ -57,7 +57,7 @@ impl MemStore for DynamicMem { let size = offset + length; // Increase the size if the request range exceeds the current limit. if size > self.get_space_mut().len() { - self.get_space_mut().resize(size as usize, 0); + self.get_space_mut().resize(size, 0); } self.get_space_mut()[offset..offset + length].copy_from_slice(change) } diff --git a/src/merkle.rs b/src/merkle.rs index af528b91f56d..708d75690b6a 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -221,7 +221,7 @@ impl BranchNode { ) -> Self { BranchNode { chd, - value: value.map(|v| Data(v)), + value: value.map(Data), chd_eth_rlp, } } @@ -480,7 +480,7 @@ impl MummyItem for Node { }; let mut cur_rlp_len = 0; for chd_rlp in chd_eth_rlp.iter_mut() { - let mut buff = [0 as u8; 1]; + let mut buff = [0_u8; 1]; let rlp_len_raw = mem .get_view(offset + cur_rlp_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; @@ -526,7 +526,7 @@ impl MummyItem for Node { .collect(); let (path, _) = PartialPath::decode(nibbles); - let mut buff = [0 as u8; 1]; + let mut buff = [0_u8; 1]; let rlp_len_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; @@ -652,7 +652,7 @@ impl MummyItem for Node { match rlp { Some(v) => { cur.write_all(&[v.len() as u8]).unwrap(); - cur.write_all(&v).unwrap(); + cur.write_all(v).unwrap(); } None => { cur.write_all(&0u8.to_le_bytes()).unwrap(); @@ -669,7 +669,7 @@ impl MummyItem for Node { if n.2.is_some() { let rlp = n.2.as_ref().unwrap(); cur.write_all(&[rlp.len() as u8]).unwrap(); - cur.write_all(&rlp).unwrap(); + cur.write_all(rlp).unwrap(); } } NodeType::Leaf(n) => { @@ -1897,7 +1897,7 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { for (ai, bi) in a.iter().zip(b.iter()) { - match ai.cmp(&bi) { + match ai.cmp(bi) { cmp::Ordering::Equal => continue, ord => return ord, } diff --git a/src/proof.rs b/src/proof.rs index eb760c85747b..e60499aa75bf 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -223,7 +223,7 @@ impl Proof { return Err(ProofError::InvalidProof) } - return Ok(true) + Ok(true) } /// proofToPath converts a merkle proof to trie node path. The main purpose of @@ -412,7 +412,7 @@ impl Proof { } else { chd_eth_rlp[index].clone().unwrap() }; - let subproof = self.generate_subproof(data).map(|subproof| subproof)?; + let subproof = self.generate_subproof(data)?; let chd = [None; NBRANCH]; let t = NodeType::Branch(BranchNode::new(chd, None, chd_eth_rlp)); @@ -518,7 +518,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right drop(u_ref); unset_node_ref(merkle, p, left_node, left_chunks[index..].to_vec(), 1, false)?; unset_node_ref(merkle, p, right_node, right_chunks[index..].to_vec(), 1, true)?; - return Ok(false) + Ok(false) } NodeType::Extension(n) => { // There can have these five scenarios: @@ -579,9 +579,9 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right )?; return Ok(false) } - return Ok(false) + Ok(false) } - _ => return Err(ProofError::InvalidNode), + _ => Err(ProofError::InvalidNode), } } @@ -662,23 +662,18 @@ fn unset_node_ref>( // path(it doesn't belong to the range), keep // it with the cached hash available. //} - } else { - if compare(&cur_key, &chunks[index..]).is_gt() { - // The key of fork shortnode is greater than the - // path(it belongs to the range), unset the entrie - // branch. The parent must be a fullnode. - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().unwrap(); - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; - }) - .unwrap(); - } - //else { - // The key of fork shortnode is less than the - // path(it doesn't belong to the range), keep - // it with the cached hash available. - //} + } else if compare(&cur_key, &chunks[index..]).is_gt() { + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. Otherwise the + // key is not part of the range and should remain in the + // cached hash. + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().unwrap(); + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); } drop(u_ref); drop(p_ref); @@ -692,5 +687,5 @@ fn unset_node_ref>( NodeType::Leaf(_) => (), } - return Ok(()) + Ok(()) } diff --git a/tests/merkle.rs b/tests/merkle.rs index 216ed84ee426..2a65561b4892 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -276,9 +276,9 @@ fn test_range_proof() { let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(&items[start].0); + let mut proof = merkle.prove(items[start].0); assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0); + let end_proof = merkle.prove(items[end].0); assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); From 16d56842cef3322bb11bbfaa555148eaad47ed19 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Thu, 12 Jan 2023 21:03:21 +0000 Subject: [PATCH 0036/1053] fwdctl: Fix implementation and add tests Signed-off-by: Dan Sover --- src/bin/delete.rs | 15 ++++++++++----- src/bin/get.rs | 16 ++++++++++------ src/bin/insert.rs | 2 +- src/db.rs | 11 ++++++----- tests/cli.rs | 14 +++++++++++++- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/bin/delete.rs b/src/bin/delete.rs index ef7c61f2dde5..cec260b9f15a 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -11,7 +11,7 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( - required = true, + required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" @@ -31,9 +31,14 @@ pub fn run(opts: &Options) -> Result<()> { }; let mut account: Option> = None; - if let Err(_) = db.new_writebatch().kv_remove(opts.key.clone(), &mut account) { - return Err(anyhow!("error deleting key")) - } + let x = match db.new_writebatch().kv_remove(opts.key.clone(), &mut account) { + Ok(wb) => { + wb.commit(); + log::info!("{}", opts.key); - Ok(()) + Ok(()) + } + Err(_) => Err(anyhow!("error deleting key")), + }; + x } diff --git a/src/bin/get.rs b/src/bin/get.rs index bc251ed9e16d..ca38cda2b804 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -11,7 +11,7 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( - required = true, + required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" @@ -27,13 +27,17 @@ pub fn run(opts: &Options) -> Result<()> { let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { Ok(db) => db, - Err(_) => return Err(anyhow!("error opening database")), + Err(_) => return Err(anyhow!("db not available")), }; match db.kv_get(opts.key.as_bytes()) { - Ok(val) => log::info!("{:#?}", val), - Err(_) => return Err(anyhow!("error getting key")), + Ok(val) => { + log::info!("{:#?}", val); + if val.is_empty() { + return Err(anyhow!("no value found for key")) + } + return Ok(()) + } + Err(_) => return Err(anyhow!("key not found")), } - - Ok(()) } diff --git a/src/bin/insert.rs b/src/bin/insert.rs index 32fc8301c365..8acf3f6bdc92 100644 --- a/src/bin/insert.rs +++ b/src/bin/insert.rs @@ -15,7 +15,7 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( - required = true, + required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" diff --git a/src/db.rs b/src/db.rs index ed7111a6f92f..7f1740b2d1b4 100644 --- a/src/db.rs +++ b/src/db.rs @@ -28,6 +28,7 @@ pub enum DBError { Merkle(MerkleError), Blob(crate::account::BlobError), System(nix::Error), + KeyNotFound, } /// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the @@ -247,11 +248,11 @@ impl DBRev { } /// Get a value associated with a key. - pub fn kv_get_merkle(&self, key: &[u8]) -> Result, DBError> { - let obj_ref = self.merkle.get(key, self.header.kv_root).map_err(DBError::Merkle)?; + pub fn kv_get(&self, key: &[u8]) -> Option> { + let obj_ref = self.merkle.get(key, self.header.kv_root); match obj_ref { - None => Ok(vec![]), - Some(obj) => Ok(obj.to_vec()), + Err(_) => None, + Ok(obj) => Some(obj.unwrap().to_vec()), } } @@ -629,7 +630,7 @@ impl DB { /// Get a value in the kv store associated with a particular key. pub fn kv_get(&self, key: &[u8]) -> Result, DBError> { - self.inner.lock().latest.kv_get_merkle(key) + self.inner.lock().latest.kv_get(key).ok_or(DBError::KeyNotFound) } /// Get root hash of the latest world state of all accounts. diff --git a/tests/cli.rs b/tests/cli.rs index 32b5ac3e3f04..a9509f3affd2 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -20,11 +20,23 @@ fn fwdctl_prints_version() -> Result<()> { Ok(()) } +#[test] +fn fwdctl_creates_database() -> Result<()> { + // version is defined and succeeds with the desired output + Command::cargo_bin(PRG)?.arg("create").assert().success(); + + Ok(()) +} + #[test] #[ignore] // TODO fn fwdctl_insert_successful() -> Result<()> { + // Create db + fwdctl_creates_database()?; + + // Insert data Command::cargo_bin(PRG)? - .args(["insert --key year --value 2023"]) + .args(["insert", "--", "--key year", "--value hello"]) .assert() .success() .stdout(predicate::str::contains("year")); From bab171637783a390ac767c1343b6f300ec2523f5 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 13 Jan 2023 16:58:58 -0500 Subject: [PATCH 0037/1053] cli: Add exit codes and stderr error logging Signed-off-by: Dan Sover --- src/bin/create.rs | 13 ++++++++----- src/bin/delete.rs | 2 +- src/bin/fwdctl.rs | 33 +++++++++++++++++++++++++++++---- src/bin/get.rs | 7 ++++++- src/bin/insert.rs | 2 +- src/db.rs | 3 ++- 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/bin/create.rs b/src/bin/create.rs index ab1d6f219c29..933f9e9f8305 100644 --- a/src/bin/create.rs +++ b/src/bin/create.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::{value_parser, Args}; use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; use log; @@ -260,8 +260,11 @@ pub fn run(opts: &Options) -> Result<()> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - let _ = DB::new(opts.name.as_ref(), &db_config); - log::info!("created firewood database in {:?}", opts.name); - - Ok(()) + match DB::new(opts.name.as_ref(), &db_config) { + Ok(_) => { + println!("created firewood database in {:?}", opts.name); + Ok(()) + } + Err(_) => Err(anyhow!("error creating database")), + } } diff --git a/src/bin/delete.rs b/src/bin/delete.rs index cec260b9f15a..68c3b2089f8d 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -34,7 +34,7 @@ pub fn run(opts: &Options) -> Result<()> { let x = match db.new_writebatch().kv_remove(opts.key.clone(), &mut account) { Ok(wb) => { wb.commit(); - log::info!("{}", opts.key); + println!("{}", opts.key); Ok(()) } diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index 9db058a813d8..a59fc6d7a10a 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::{Parser, Subcommand}; +use std::process; pub mod create; pub mod delete; @@ -45,9 +46,33 @@ fn main() -> Result<()> { ); match &cli.command { - Commands::Create(opts) => create::run(opts), - Commands::Insert(opts) => insert::run(opts), - Commands::Get(opts) => get::run(opts), - Commands::Delete(opts) => delete::run(opts), + Commands::Create(opts) => match create::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, + Commands::Insert(opts) => match insert::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, + Commands::Get(opts) => match get::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, + Commands::Delete(opts) => match delete::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, } } diff --git a/src/bin/get.rs b/src/bin/get.rs index ca38cda2b804..a1a5de060b2b 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; +use std::str; #[derive(Debug, Args)] pub struct Options { @@ -32,7 +33,11 @@ pub fn run(opts: &Options) -> Result<()> { match db.kv_get(opts.key.as_bytes()) { Ok(val) => { - log::info!("{:#?}", val); + let s = match str::from_utf8(&val) { + Ok(v) => v, + Err(e) => return Err(anyhow!("Invalid UTF-8 sequence: {}", e)), + }; + println!("{:#?}", s); if val.is_empty() { return Err(anyhow!("no value found for key")) } diff --git a/src/bin/insert.rs b/src/bin/insert.rs index 8acf3f6bdc92..4fd87a3b38c0 100644 --- a/src/bin/insert.rs +++ b/src/bin/insert.rs @@ -40,7 +40,7 @@ pub fn run(opts: &Options) -> Result<()> { { Ok(insertion) => { insertion.commit(); - log::info!("{}", opts.key); + println!("{}", opts.key); Ok(()) } diff --git a/src/db.rs b/src/db.rs index 7f1740b2d1b4..cbdd78259e9b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -29,6 +29,7 @@ pub enum DBError { Blob(crate::account::BlobError), System(nix::Error), KeyNotFound, + CreateError, } /// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the @@ -252,7 +253,7 @@ impl DBRev { let obj_ref = self.merkle.get(key, self.header.kv_root); match obj_ref { Err(_) => None, - Ok(obj) => Some(obj.unwrap().to_vec()), + Ok(obj) => obj.map(|o| o.to_vec()), } } From c363128925a3f4254878eefb9967d1e22bae755f Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 17 Jan 2023 11:14:29 -0500 Subject: [PATCH 0038/1053] docs: Update fwdctl README with more examples Signed-off-by: Dan Sover --- src/bin/README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/bin/README.md b/src/bin/README.md index f7a6d047be52..cf9fcfca5203 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -26,14 +26,26 @@ $ ./target/release/fwdctl -h ## Examples * fwdctl create ``` -# Check available options when creating a database, including the defaults +# Check available options when creating a database, including the defaults. $ fwdctl create -h -# Create a new, blank instance of firewood with a custom name -$ fwdctl create --name=my-db -# Look inside, there are several folders representing different components of firewood, including the WAL -$ ls my-db +# Create a new, blank instance of firewood using the default name "firewood". +$ fwdctl create +# Look inside, there are several folders representing different components of firewood, including the WAL. +$ ls firewood ``` * fwdctl get +``` +Get the value associated with a key in the database, if it exists. +fwdctl get --key +``` * fwdctl insert --key --value +``` +Insert a key/value pair into the database. +fwdctl insert --key --value +``` * fwdctl delete +``` +Delete a key from the database, along with the associated value. +fwdctl delete --key +``` From 2c000d498fa1dad5f473ea7c3efa7f7711e3b046 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 17 Jan 2023 19:57:26 +0000 Subject: [PATCH 0039/1053] cli: Add tests Signed-off-by: Dan Sover --- .gitignore | 3 ++ src/bin/delete.rs | 5 ++- src/bin/get.rs | 7 +-- src/bin/insert.rs | 5 ++- tests/cli.rs | 112 +++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 119 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 2cf520df75c7..a3265aa6f907 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ Cargo.lock shale/target/ shale/Cargo.lock + +# test database +test_firewood diff --git a/src/bin/delete.rs b/src/bin/delete.rs index 68c3b2089f8d..1502c4c3bd99 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -11,12 +11,13 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( + long, required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" )] - pub db_path: String, + pub db: String, } pub fn run(opts: &Options) -> Result<()> { @@ -25,7 +26,7 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + let db = match DB::new(opts.db.as_str(), &cfg.build()) { Ok(db) => db, Err(_) => return Err(anyhow!("error opening database")), }; diff --git a/src/bin/get.rs b/src/bin/get.rs index a1a5de060b2b..701d9e1fe79a 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -12,12 +12,13 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( + long, required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" )] - pub db_path: String, + pub db: String, } pub fn run(opts: &Options) -> Result<()> { @@ -26,7 +27,7 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + let db = match DB::new(opts.db.as_str(), &cfg.build()) { Ok(db) => db, Err(_) => return Err(anyhow!("db not available")), }; @@ -37,7 +38,7 @@ pub fn run(opts: &Options) -> Result<()> { Ok(v) => v, Err(e) => return Err(anyhow!("Invalid UTF-8 sequence: {}", e)), }; - println!("{:#?}", s); + println!("{:?}", s); if val.is_empty() { return Err(anyhow!("no value found for key")) } diff --git a/src/bin/insert.rs b/src/bin/insert.rs index 4fd87a3b38c0..d57daf4db7ca 100644 --- a/src/bin/insert.rs +++ b/src/bin/insert.rs @@ -15,12 +15,13 @@ pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( + long, required = false, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" )] - pub db_path: String, + pub db: String, } pub fn run(opts: &Options) -> Result<()> { @@ -29,7 +30,7 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db_path.as_str(), &cfg.build()) { + let db = match DB::new(opts.db.as_str(), &cfg.build()) { Ok(db) => db, Err(_) => return Err(anyhow!("error opening database")), }; diff --git a/tests/cli.rs b/tests/cli.rs index a9509f3affd2..7427858b4e52 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,10 +1,12 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; +use std::fs::remove_dir_all; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); const FIREWOOD: &str = "firewood"; +const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; #[test] fn fwdctl_prints_version() -> Result<()> { @@ -22,24 +24,122 @@ fn fwdctl_prints_version() -> Result<()> { #[test] fn fwdctl_creates_database() -> Result<()> { - // version is defined and succeeds with the desired output - Command::cargo_bin(PRG)?.arg("create").assert().success(); + Command::cargo_bin(PRG)? + .arg("create") + .arg("--name") + .arg(FIREWOOD_TEST_DB_NAME) + .assert() + .success(); + + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } Ok(()) } #[test] -#[ignore] // TODO fn fwdctl_insert_successful() -> Result<()> { // Create db - fwdctl_creates_database()?; + Command::cargo_bin(PRG)? + .arg("create") + .arg("--name") + .arg(FIREWOOD_TEST_DB_NAME) + .assert() + .success(); // Insert data Command::cargo_bin(PRG)? - .args(["insert", "--", "--key year", "--value hello"]) + .arg("insert") + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--key", "year"]) + .args(["--value", "2023"]) .assert() .success() .stdout(predicate::str::contains("year")); + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } + + Ok(()) +} + +#[test] +fn fwdctl_get_successful() -> Result<()> { + // Create db and insert data + Command::cargo_bin(PRG)? + .arg("create") + .args(["--name", FIREWOOD_TEST_DB_NAME]) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["--key", "year"]) + .args(["--value", "2023"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("year")); + + // Get value back out + Command::cargo_bin(PRG)? + .arg("get") + .args(["--key", "year"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("2023")); + + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } + + Ok(()) +} + +#[test] +fn fwdctl_delete_successful() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg("--name") + .arg(FIREWOOD_TEST_DB_NAME) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["--key", "year"]) + .args(["--value", "2023"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("year")); + + // Delete key + Command::cargo_bin(PRG)? + .arg("delete") + .args(["--key", "year"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("year")); + + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } + + Ok(()) +} + +// Removes the firewood database on disk +fn fwdctl_delete_db() -> Result<()> { + match remove_dir_all(FIREWOOD_TEST_DB_NAME) { + Ok(_) => {} + Err(e) => { + eprintln!("failed to delete testing dir: {e}"); + } + } Ok(()) } From dd2a3ad3d2715e892fa5859381cf2251abf303c5 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 18 Jan 2023 18:00:18 +0000 Subject: [PATCH 0040/1053] cli: Add serial library for testing purposes Signed-off-by: Dan Sover --- Cargo.toml | 1 + tests/cli.rs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea9bd317ae7a..551715cba51b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ rand = "0.8.5" triehash = "0.8.4" assert_cmd = "2.0.7" predicates = "2.1.1" +serial_test = "1.0.0" [profile.release] debug = true diff --git a/tests/cli.rs b/tests/cli.rs index 7427858b4e52..08dea950167e 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; +use serial_test::serial; use std::fs::remove_dir_all; const PRG: &str = "fwdctl"; @@ -9,6 +10,7 @@ const FIREWOOD: &str = "firewood"; const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; #[test] +#[serial] fn fwdctl_prints_version() -> Result<()> { let expected_version_output: String = format!("{FIREWOOD} {VERSION}\n"); @@ -23,6 +25,7 @@ fn fwdctl_prints_version() -> Result<()> { } #[test] +#[serial] fn fwdctl_creates_database() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") @@ -39,6 +42,7 @@ fn fwdctl_creates_database() -> Result<()> { } #[test] +#[serial] fn fwdctl_insert_successful() -> Result<()> { // Create db Command::cargo_bin(PRG)? @@ -66,6 +70,7 @@ fn fwdctl_insert_successful() -> Result<()> { } #[test] +#[serial] fn fwdctl_get_successful() -> Result<()> { // Create db and insert data Command::cargo_bin(PRG)? @@ -100,6 +105,7 @@ fn fwdctl_get_successful() -> Result<()> { } #[test] +#[serial] fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") @@ -135,11 +141,10 @@ fn fwdctl_delete_successful() -> Result<()> { // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { - match remove_dir_all(FIREWOOD_TEST_DB_NAME) { - Ok(_) => {} - Err(e) => { - eprintln!("failed to delete testing dir: {e}"); - } + if let Err(e) = remove_dir_all(FIREWOOD_TEST_DB_NAME) { + eprintln!("failed to delete testing dir: {e}"); + return Err(anyhow!(e)) } + Ok(()) } From 08a7b74fc1f3f0e4ee6ec7ff064cf4e5592f613a Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 23 Jan 2023 18:54:33 +0000 Subject: [PATCH 0041/1053] cli: Add root command The root command can be used to get the root hash of the underlying key/value storage layer. Signed-off-by: Dan Sover --- src/bin/README.md | 1 + src/bin/fwdctl.rs | 10 ++++++++++ src/bin/root.rs | 39 +++++++++++++++++++++++++++++++++++++++ tests/cli.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 src/bin/root.rs diff --git a/src/bin/README.md b/src/bin/README.md index cf9fcfca5203..4c156c3ceacb 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -22,6 +22,7 @@ $ ./target/release/fwdctl -h * `fwdctl get`: Get the code associated with a key in the database. * `fwdctl insert`: Insert a key/value pair into the generic key/value store. * `fwdctl delete`: Delete a key/value pair from the database. +* `fwdctl root`: Get the root hash of the key/value trie ## Examples * fwdctl create diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index a59fc6d7a10a..d57409a6bc9e 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -6,6 +6,7 @@ pub mod create; pub mod delete; pub mod get; pub mod insert; +pub mod root; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -36,6 +37,8 @@ enum Commands { Get(get::Options), /// Delete values associated with a key Delete(delete::Options), + /// Display key/value trie root hash + Root(root::Options), } fn main() -> Result<()> { @@ -74,5 +77,12 @@ fn main() -> Result<()> { } Ok(_) => Ok(()), }, + Commands::Root(opts) => match root::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, } } diff --git a/src/bin/root.rs b/src/bin/root.rs new file mode 100644 index 000000000000..252dc8ef49fc --- /dev/null +++ b/src/bin/root.rs @@ -0,0 +1,39 @@ +use anyhow::{anyhow, Result}; +use clap::Args; +use firewood::db::{DBConfig, WALConfig, DB}; +use log; +use std::str; + +#[derive(Debug, Args)] +pub struct Options { + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + long, + required = false, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db: String, +} + +pub fn run(opts: &Options) -> Result<()> { + log::debug!("root hash {:?}", opts); + let cfg = DBConfig::builder() + .truncate(false) + .wal(WALConfig::builder().max_revisions(10).build()); + + let db = match DB::new(opts.db.as_str(), &cfg.build()) { + Ok(db) => db, + Err(_) => return Err(anyhow!("db not available")), + }; + + match db.kv_root_hash() { + Ok(root) => { + // TODO: collect root into hex encoded string + println!("{:X?}", *root); + Ok(()) + } + Err(_) => return Err(anyhow!("root hash not found")), + } +} diff --git a/tests/cli.rs b/tests/cli.rs index 08dea950167e..0fb8809d07d5 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -148,3 +148,36 @@ fn fwdctl_delete_db() -> Result<()> { Ok(()) } + +#[test] +fn fwdctl_root_hash() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg("--name") + .arg(FIREWOOD_TEST_DB_NAME) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["--key", "year"]) + .args(["--value", "2023"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("year")); + + // Get root + Command::cargo_bin(PRG)? + .arg("root") + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::is_empty().not()); + + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } + + Ok(()) +} From 892abec728dceca19ed59d22f873bf4ad00e515a Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 23 Jan 2023 19:02:11 +0000 Subject: [PATCH 0042/1053] cli: Add dump command The dump command enables dumping the contents of the firewood database to stdout. Signed-off-by: Dan Sover --- src/bin/README.md | 3 ++- src/bin/dump.rs | 35 +++++++++++++++++++++++++++++++++++ src/bin/fwdctl.rs | 10 ++++++++++ tests/cli.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/bin/dump.rs diff --git a/src/bin/README.md b/src/bin/README.md index 4c156c3ceacb..b1e1d36fcc6e 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -22,7 +22,8 @@ $ ./target/release/fwdctl -h * `fwdctl get`: Get the code associated with a key in the database. * `fwdctl insert`: Insert a key/value pair into the generic key/value store. * `fwdctl delete`: Delete a key/value pair from the database. -* `fwdctl root`: Get the root hash of the key/value trie +* `fwdctl root`: Get the root hash of the key/value trie. +* `fwdctl dump`: Dump the contents of the key/value store. ## Examples * fwdctl create diff --git a/src/bin/dump.rs b/src/bin/dump.rs new file mode 100644 index 000000000000..8ec17c0f1bbf --- /dev/null +++ b/src/bin/dump.rs @@ -0,0 +1,35 @@ +use anyhow::{anyhow, Result}; +use clap::Args; +use firewood::db::{DBConfig, WALConfig, DB}; +use log; + +#[derive(Debug, Args)] +pub struct Options { + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + long, + required = false, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db: String, +} + +pub fn run(opts: &Options) -> Result<()> { + log::debug!("dump database {:?}", opts); + let cfg = DBConfig::builder() + .truncate(false) + .wal(WALConfig::builder().max_revisions(10).build()); + + let db = match DB::new(opts.db.as_str(), &cfg.build()) { + Ok(db) => db, + Err(_) => return Err(anyhow!("db not available")), + }; + + let mut stdout = std::io::stdout(); + if let Err(_) = db.kv_dump(&mut stdout) { + return Err(anyhow!("database dump not successful")) + } + Ok(()) +} diff --git a/src/bin/fwdctl.rs b/src/bin/fwdctl.rs index d57409a6bc9e..9d0331530432 100644 --- a/src/bin/fwdctl.rs +++ b/src/bin/fwdctl.rs @@ -4,6 +4,7 @@ use std::process; pub mod create; pub mod delete; +pub mod dump; pub mod get; pub mod insert; pub mod root; @@ -39,6 +40,8 @@ enum Commands { Delete(delete::Options), /// Display key/value trie root hash Root(root::Options), + /// Dump contents of key/value store + Dump(dump::Options), } fn main() -> Result<()> { @@ -84,5 +87,12 @@ fn main() -> Result<()> { } Ok(_) => Ok(()), }, + Commands::Dump(opts) => match dump::run(opts) { + Err(e) => { + eprintln!("{e}"); + process::exit(1) + } + Ok(_) => Ok(()), + }, } } diff --git a/tests/cli.rs b/tests/cli.rs index 0fb8809d07d5..55f5edbf8f27 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -181,3 +181,36 @@ fn fwdctl_root_hash() -> Result<()> { Ok(()) } + +#[test] +fn fwdctl_dump() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg("--name") + .arg(FIREWOOD_TEST_DB_NAME) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["--key", "year"]) + .args(["--value", "2023"]) + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::contains("year")); + + // Get root + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--db", FIREWOOD_TEST_DB_NAME]) + .assert() + .success() + .stdout(predicate::str::is_empty().not()); + + if let Err(e) = fwdctl_delete_db() { + return Err(anyhow!(e)) + } + + Ok(()) +} From 4b493a59742491cd47d3d4ab819ec5c6aa36e2a0 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 23 Jan 2023 19:43:59 +0000 Subject: [PATCH 0043/1053] cli: Fixup root tests to be serial Signed-off-by: Dan Sover --- tests/cli.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/cli.rs b/tests/cli.rs index 55f5edbf8f27..c26681201158 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -9,6 +9,16 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); const FIREWOOD: &str = "firewood"; const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; +// Removes the firewood database on disk +fn fwdctl_delete_db() -> Result<()> { + if let Err(e) = remove_dir_all(FIREWOOD_TEST_DB_NAME) { + eprintln!("failed to delete testing dir: {e}"); + return Err(anyhow!(e)) + } + + Ok(()) +} + #[test] #[serial] fn fwdctl_prints_version() -> Result<()> { @@ -139,17 +149,8 @@ fn fwdctl_delete_successful() -> Result<()> { Ok(()) } -// Removes the firewood database on disk -fn fwdctl_delete_db() -> Result<()> { - if let Err(e) = remove_dir_all(FIREWOOD_TEST_DB_NAME) { - eprintln!("failed to delete testing dir: {e}"); - return Err(anyhow!(e)) - } - - Ok(()) -} - #[test] +#[serial] fn fwdctl_root_hash() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") @@ -183,6 +184,7 @@ fn fwdctl_root_hash() -> Result<()> { } #[test] +#[serial] fn fwdctl_dump() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") From a4043b802a8e873d803c6325cc806b851ec18d2f Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Thu, 22 Dec 2022 10:04:20 -0500 Subject: [PATCH 0044/1053] tests: Add more range proof tests Signed-off-by: Dan Sover --- tests/merkle.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/merkle.rs b/tests/merkle.rs index 2a65561b4892..c3a64c49b01e 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -358,3 +358,57 @@ fn test_range_proof_with_invalid_non_existent_proof() { merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); } + +#[test] +// The start and end nodes are both the same. +fn test_one_element_range_proof() { + let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; + items.sort(); + + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let start = 0; + let end = &items.len() - 1; + + let mut start_proof = merkle.prove(&items[start].0); + assert!(!start_proof.0.is_empty()); + let end_proof = merkle.prove(&items[start].0); // start and end nodes are the same + assert!(!end_proof.0.is_empty()); + + start_proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for i in start..=end { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + assert!(merkle.verify_range_proof(&start_proof, &items[start].0, &items[end].0, keys, vals)); +} + +#[test] +// The range proof starts from 0 (root) to the last one +fn test_all_elements_proof() { + let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; + items.sort(); + + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let start = 0; + let end = &items.len() - 1; + + let mut proof = merkle.prove(&items[start].0); + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[end].0); // start and end nodes are the same + assert!(!end_proof.0.is_empty()); + + proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for i in start..=end { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); +} From b0dbf642434e09f9eafc944d8b3d720942680b3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 13:27:52 +0000 Subject: [PATCH 0045/1053] build(deps): update typed-builder requirement from 0.11.0 to 0.12.0 Updates the requirements on [typed-builder](https://github.com/idanarye/rust-typed-builder) to permit the latest version. - [Release notes](https://github.com/idanarye/rust-typed-builder/releases) - [Changelog](https://github.com/idanarye/rust-typed-builder/blob/master/CHANGELOG.md) - [Commits](https://github.com/idanarye/rust-typed-builder/commits) --- updated-dependencies: - dependency-name: typed-builder dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 551715cba51b..fb446ba5517c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ hex = "0.4.3" lru = "0.9.0" libaio-futures = "0.2.2" nix = "0.26.1" -typed-builder = "0.11.0" +typed-builder = "0.12.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } shale = "0.1.7" growth-ring = "0.3.0" From ff9bd887efe860dccebcade2731f791a7fada232 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 27 Jan 2023 22:01:17 +0000 Subject: [PATCH 0046/1053] gitignore: Add VSCode Signed-off-by: Dan Sover --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a3265aa6f907..16b5654e16e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ shale/Cargo.lock # test database test_firewood + +# vscode +.vscode From 57a8cfd52a53bed9c69930413045d5e9a6b0f201 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 27 Jan 2023 22:01:33 +0000 Subject: [PATCH 0047/1053] lib: Update merkle_utils to return Results Updates the merkle utils to no longer panic on a bad input. A new enum called DataStoreError was added. There is also now a conversion between ProofError and DataStoreError. Errors can be further improved by providing more context information to the caller. Signed-off-by: Dan Sover --- Cargo.toml | 1 + src/merkle_util.rs | 80 ++++++++++++++++++++++++++++++++++------------ src/proof.rs | 18 ++++++++--- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb446ba5517c..e94181a60d37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" log = "0.4.17" +thiserror = "1.0.38" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/merkle_util.rs b/src/merkle_util.rs index 3298f24bb67f..d16c52ff46d8 100644 --- a/src/merkle_util.rs +++ b/src/merkle_util.rs @@ -4,26 +4,54 @@ use crate::proof::Proof; use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr}; use std::rc::Rc; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DataStoreError { + #[error("inserting data")] + InsertionError, + #[error("removing data")] + RemovalError, + #[error("getting data")] + GetError, + #[error("root hash")] + RootHashError, + #[error("dumping data")] + DumpError, + #[error("invalid utf8")] + UTF8Error, + #[error("bad proof")] + ProofError, + #[error("proof verification")] + ProofVerificationError, +} + pub struct MerkleSetup { root: ObjPtr, merkle: Merkle, } impl MerkleSetup { - pub fn insert>(&mut self, key: K, val: Vec) { - self.merkle.insert(key, val, self.root).unwrap() + pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { + self.merkle + .insert(key, val, self.root) + .map_err(|_err| DataStoreError::InsertionError) } - pub fn remove>(&mut self, key: K) { - self.merkle.remove(key, self.root).unwrap(); + pub fn remove>(&mut self, key: K) -> Result>, DataStoreError> { + self.merkle + .remove(key, self.root) + .map_err(|_err| DataStoreError::RemovalError) } - pub fn get>(&self, key: K) -> Option { - self.merkle.get(key, self.root).unwrap() + pub fn get>(&self, key: K) -> Result, DataStoreError> { + self.merkle.get(key, self.root).map_err(|_err| DataStoreError::GetError) } - pub fn get_mut>(&mut self, key: K) -> Option { - self.merkle.get_mut(key, self.root).unwrap() + pub fn get_mut>(&mut self, key: K) -> Result, DataStoreError> { + self.merkle + .get_mut(key, self.root) + .map_err(|_err| DataStoreError::GetError) } pub fn get_root(&self) -> ObjPtr { @@ -34,30 +62,40 @@ impl MerkleSetup { &mut self.merkle } - pub fn root_hash(&self) -> Hash { - self.merkle.root_hash::(self.root).unwrap() + pub fn root_hash(&self) -> Result { + self.merkle + .root_hash::(self.root) + .map_err(|_err| DataStoreError::RootHashError) } - pub fn dump(&self) -> String { + pub fn dump(&self) -> Result { let mut s = Vec::new(); - self.merkle.dump(self.root, &mut s).unwrap(); - String::from_utf8(s).unwrap() + self.merkle + .dump(self.root, &mut s) + .map_err(|_err| DataStoreError::DumpError)?; + String::from_utf8(s).map_err(|_err| DataStoreError::UTF8Error) } - pub fn prove>(&self, key: K) -> Proof { - self.merkle.prove::(key, self.root).unwrap() + pub fn prove>(&self, key: K) -> Result { + self.merkle + .prove::(key, self.root) + .map_err(|_err| DataStoreError::ProofError) } - pub fn verify_proof>(&self, key: K, proof: &Proof) -> Option> { - let hash: [u8; 32] = self.root_hash().0; - proof.verify_proof(key, hash).unwrap() + pub fn verify_proof>(&self, key: K, proof: &Proof) -> Result>, DataStoreError> { + let hash: [u8; 32] = *self.root_hash()?; + proof + .verify_proof(key, hash) + .map_err(|_err| DataStoreError::ProofVerificationError) } pub fn verify_range_proof, V: AsRef<[u8]>>( &self, proof: &Proof, first_key: K, last_key: K, keys: Vec, vals: Vec, - ) -> bool { - let hash: [u8; 32] = self.root_hash().0; - proof.verify_range_proof(hash, first_key, last_key, keys, vals).unwrap() + ) -> Result { + let hash: [u8; 32] = *self.root_hash()?; + proof + .verify_range_proof(hash, first_key, last_key, keys, vals) + .map_err(|_err| DataStoreError::ProofVerificationError) } } diff --git a/src/proof.rs b/src/proof.rs index e60499aa75bf..8102ea0e83c7 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -29,6 +29,16 @@ pub enum ProofError { EmptyRange, } +impl From for ProofError { + fn from(d: DataStoreError) -> ProofError { + match d { + DataStoreError::InsertionError => ProofError::NodesInsertionError, + DataStoreError::RootHashError => ProofError::InvalidNode, + _ => ProofError::InvalidProof, + } + } +} + const EXT_NODE_SIZE: usize = 2; const BRANCH_NODE_SIZE: usize = 17; @@ -172,9 +182,9 @@ impl Proof { // Use in-memory merkle let mut merkle = new_merkle(0x10000, 0x10000); for (index, k) in keys.iter().enumerate() { - merkle.insert(k, vals[index].as_ref().to_vec()) + merkle.insert(k, vals[index].as_ref().to_vec())?; } - let merkle_root = &*merkle.root_hash(); + let merkle_root = &*merkle.root_hash()?; if merkle_root != &root_hash { return Err(ProofError::InvalidProof) } @@ -214,11 +224,11 @@ impl Proof { } for (i, _) in keys.iter().enumerate() { - merkle_setup.insert(keys[i].as_ref(), vals[i].as_ref().to_vec()) + merkle_setup.insert(keys[i].as_ref(), vals[i].as_ref().to_vec())?; } // Calculate the hash - let merkle_root = &*merkle_setup.root_hash(); + let merkle_root = &*merkle_setup.root_hash()?; if merkle_root != &root_hash { return Err(ProofError::InvalidProof) } From 5f1e58678f2098c0da94648d41964c9a9007fac2 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 30 Jan 2023 22:04:19 +0000 Subject: [PATCH 0048/1053] tests: Update tests to use Results Signed-off-by: Dan Sover --- src/merkle_util.rs | 12 +-- tests/merkle.rs | 183 ++++++++++++++++++++++++++------------------- 2 files changed, 110 insertions(+), 85 deletions(-) diff --git a/src/merkle_util.rs b/src/merkle_util.rs index d16c52ff46d8..34349820d3ba 100644 --- a/src/merkle_util.rs +++ b/src/merkle_util.rs @@ -8,21 +8,21 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum DataStoreError { - #[error("inserting data")] + #[error("failed to insert data")] InsertionError, - #[error("removing data")] + #[error("failed to remove data")] RemovalError, - #[error("getting data")] + #[error("failed to get data")] GetError, - #[error("root hash")] + #[error("failed to generate root hash")] RootHashError, - #[error("dumping data")] + #[error("failed to dump data")] DumpError, #[error("invalid utf8")] UTF8Error, #[error("bad proof")] ProofError, - #[error("proof verification")] + #[error("failed to verify proof")] ProofVerificationError, } diff --git a/tests/merkle.rs b/tests/merkle.rs index c3a64c49b01e..7e80213dba6d 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -2,30 +2,31 @@ use firewood::{merkle_util::*, proof::Proof}; fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( items: Vec<(K, V)>, meta_size: u64, compact_size: u64, -) -> MerkleSetup { +) -> Result { let mut merkle = new_merkle(meta_size, compact_size); for (k, v) in items.iter() { - merkle.insert(k, v.as_ref().to_vec()) + merkle.insert(k, v.as_ref().to_vec())?; } - let merkle_root = &*merkle.root_hash(); + let merkle_root = merkle.root_hash().unwrap(); let items_copy = items.clone(); let reference_root = triehash::trie_root::(items); println!( "ours: {}, correct: {}", - hex::encode(merkle_root), + hex::encode(merkle_root.0), hex::encode(reference_root) ); - if merkle_root != &reference_root { + if merkle_root.0 != reference_root { for (k, v) in items_copy { println!("{} => {}", hex::encode(k), hex::encode(v)); } - println!("{}", merkle.dump()); + println!("{:?}", merkle.dump()?); panic!(); } - merkle + Ok(merkle) } #[test] +#[allow(unused_must_use)] fn test_root_hash_simple_insertions() { let items = vec![ ("do", "verb"), @@ -35,11 +36,12 @@ fn test_root_hash_simple_insertions() { ("horse", "stallion"), ("ddd", "ok"), ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000).unwrap(); merkle.dump(); } #[test] +#[allow(unused_must_use)] fn test_root_hash_fuzz_insertions() { use rand::{rngs::StdRng, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -67,7 +69,7 @@ fn test_root_hash_fuzz_insertions() { } #[test] -fn test_root_hash_reversed_deletions() { +fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { use rand::{rngs::StdRng, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); let max_len0 = 8; @@ -96,36 +98,37 @@ fn test_root_hash_reversed_deletions() { let mut dumps = Vec::new(); for (k, v) in items.iter() { dumps.push(merkle.dump()); - merkle.insert(k, v.to_vec()); + merkle.insert(k, v.to_vec())?; hashes.push(merkle.root_hash()); } hashes.pop(); println!("----"); - let mut prev_dump = merkle.dump(); + let mut prev_dump = merkle.dump()?; for (((k, _), h), d) in items.iter().rev().zip(hashes.iter().rev()).zip(dumps.iter().rev()) { - merkle.remove(k); - let h0 = merkle.root_hash(); - if *h != h0 { + merkle.remove(k)?; + let h0 = merkle.root_hash()?.0; + if h.as_ref().unwrap().0 != h0 { for (k, _) in items.iter() { println!("{}", hex::encode(k)); } - println!("{} != {}", hex::encode(**h), hex::encode(*h0)); + println!("{} != {}", hex::encode(**h.as_ref().unwrap()), hex::encode(h0)); println!("== before {} ===", hex::encode(k)); print!("{prev_dump}"); println!("== after {} ===", hex::encode(k)); - print!("{}", merkle.dump()); + print!("{}", merkle.dump()?); println!("== should be ==="); - print!("{d}"); + print!("{:?}", d); panic!(); } - prev_dump = merkle.dump(); + prev_dump = merkle.dump()?; } println!("i = {i}"); } + Ok(()) } #[test] -fn test_root_hash_random_deletions() { +fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); let max_len0 = 8; @@ -152,45 +155,48 @@ fn test_root_hash_random_deletions() { items_ordered.shuffle(&mut *rng.borrow_mut()); let mut merkle = new_merkle(0x100000, 0x100000); for (k, v) in items.iter() { - merkle.insert(k, v.to_vec()); + merkle.insert(k, v.to_vec())?; } for (k, _) in items_ordered.into_iter() { - assert!(merkle.get(&k).is_some()); - assert!(merkle.get_mut(&k).is_some()); - merkle.remove(&k); - assert!(merkle.get(&k).is_none()); - assert!(merkle.get_mut(&k).is_none()); + assert!(merkle.get(&k)?.is_some()); + assert!(merkle.get_mut(&k)?.is_some()); + merkle.remove(&k)?; + assert!(merkle.get(&k)?.is_none()); + assert!(merkle.get_mut(&k)?.is_none()); items.remove(&k); for (k, v) in items.iter() { - assert_eq!(&*merkle.get(k).unwrap(), &v[..]); - assert_eq!(&*merkle.get_mut(k).unwrap().get(), &v[..]); + assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); + assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); } let h = triehash::trie_root::, _, _>(items.iter().collect()); - let h0 = merkle.root_hash(); + let h0 = merkle.root_hash()?; if h[..] != *h0 { println!("{} != {}", hex::encode(h), hex::encode(*h0)); } } println!("i = {i}"); } + Ok(()) } #[test] -fn test_one_element_proof() { +fn test_one_element_proof() -> Result<(), DataStoreError> { let items = vec![("k", "v")]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "k"; - let proof = merkle.prove(key); + let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); - let verify_proof = merkle.verify_proof(key, &proof); + let verify_proof = merkle.verify_proof(key, &proof)?; assert!(verify_proof.is_some()); + + Ok(()) } #[test] /// Verify the proofs that end with leaf node with the given key. -fn test_proof_end_with_leaf() { +fn test_proof_end_with_leaf() -> Result<(), DataStoreError> { let items = vec![ ("do", "verb"), ("doe", "reindeer"), @@ -199,32 +205,36 @@ fn test_proof_end_with_leaf() { ("horse", "stallion"), ("ddd", "ok"), ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "doe"; - let proof = merkle.prove(key); + let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); - let verify_proof = merkle.verify_proof(key, &proof); + let verify_proof = merkle.verify_proof(key, &proof)?; assert!(verify_proof.is_some()); + + Ok(()) } #[test] /// Verify the proofs that end with branch node with the given key. -fn test_proof_end_with_branch() { +fn test_proof_end_with_branch() -> Result<(), DataStoreError> { let items = vec![("d", "verb"), ("do", "verb"), ("doe", "reindeer"), ("e", "coin")]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "d"; - let proof = merkle.prove(key); + let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); - let verify_proof = merkle.verify_proof(key, &proof); + let verify_proof = merkle.verify_proof(key, &proof)?; assert!(verify_proof.is_some()); + + Ok(()) } #[test] -#[should_panic] +#[allow(unused_must_use)] fn test_bad_proof() { let items = vec![ ("do", "verb"), @@ -237,48 +247,52 @@ fn test_bad_proof() { let merkle = merkle_build_test(items, 0x10000, 0x10000); let key = "ddd"; - let mut proof = merkle.prove(key); - assert!(!proof.0.is_empty()); + let proof = merkle.as_ref().unwrap().prove(key); + assert!(!proof.as_ref().unwrap().0.is_empty()); // Delete an entry from the generated proofs. - let new_proof = Proof(proof.0.drain().take(1).collect()); - merkle.verify_proof(key, &new_proof); + let new_proof = Proof(proof.unwrap().0.drain().take(1).collect()); + merkle.unwrap().verify_proof(key, &new_proof).is_err(); } #[test] -fn test_missing_key_proof() { +fn test_missing_key_proof() -> Result<(), DataStoreError> { let items = vec![("k", "v")]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "x"; - let proof = merkle.prove(key); + let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); - let verify_proof = merkle.verify_proof(key, &proof); + let verify_proof = merkle.verify_proof(key, &proof)?; assert!(verify_proof.is_none()); + + Ok(()) } #[test] -fn test_empty_tree_proof() { +fn test_empty_tree_proof() -> Result<(), DataStoreError> { let items: Vec<(&str, &str)> = Vec::new(); - let merkle = merkle_build_test(items, 0x10000, 0x10000); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "x"; - let proof = merkle.prove(key); + let proof = merkle.prove(key)?; assert!(proof.0.is_empty()); + + Ok(()) } #[test] -fn test_range_proof() { +fn test_range_proof() -> Result<(), DataStoreError> { let mut items = vec![("doa", "verb"), ("doe", "reindeer"), ("dog", "puppy"), ("ddd", "ok")]; items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(items[start].0); + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0); + let end_proof = merkle.prove(items[end].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -290,11 +304,13 @@ fn test_range_proof() { vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + + Ok(()) } #[test] -fn test_range_proof_with_non_existent_proof() { +fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { let mut items = vec![ (std::str::from_utf8(&[0x7]).unwrap(), "verb"), (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), @@ -304,13 +320,13 @@ fn test_range_proof_with_non_existent_proof() { ]; items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(std::str::from_utf8(&[0x2]).unwrap()); + let mut proof = merkle.prove(std::str::from_utf8(&[0x2]).unwrap())?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(std::str::from_utf8(&[0x8]).unwrap()); + let end_proof = merkle.prove(std::str::from_utf8(&[0x8]).unwrap())?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -322,11 +338,13 @@ fn test_range_proof_with_non_existent_proof() { vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + + Ok(()) } #[test] -#[should_panic] +#[allow(unused_must_use)] fn test_range_proof_with_invalid_non_existent_proof() { let mut items = vec![ (std::str::from_utf8(&[0x8]).unwrap(), "verb"), @@ -341,12 +359,12 @@ fn test_range_proof_with_invalid_non_existent_proof() { let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(std::str::from_utf8(&[0x3]).unwrap()); - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(std::str::from_utf8(&[0x7]).unwrap()); - assert!(!end_proof.0.is_empty()); + let mut proof = merkle.as_ref().unwrap().prove(std::str::from_utf8(&[0x3]).unwrap()); + assert!(!proof.as_ref().unwrap().0.is_empty()); + let end_proof = merkle.as_ref().unwrap().prove(std::str::from_utf8(&[0x7]).unwrap()); + assert!(!end_proof.as_ref().unwrap().0.is_empty()); - proof.concat_proofs(end_proof); + proof.as_mut().unwrap().concat_proofs(end_proof.unwrap()); let mut keys = Vec::new(); let mut vals = Vec::new(); @@ -356,22 +374,25 @@ fn test_range_proof_with_invalid_non_existent_proof() { vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); + merkle + .unwrap() + .verify_range_proof(proof.as_ref().unwrap(), &items[start].0, &items[end].0, keys, vals) + .is_err(); } #[test] // The start and end nodes are both the same. -fn test_one_element_range_proof() { +fn test_one_element_range_proof() -> Result<(), DataStoreError> { let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; let end = &items.len() - 1; - let mut start_proof = merkle.prove(&items[start].0); + let mut start_proof = merkle.prove(&items[start].0)?; assert!(!start_proof.0.is_empty()); - let end_proof = merkle.prove(&items[start].0); // start and end nodes are the same + let end_proof = merkle.prove(&items[start].0)?; // start and end nodes are the same assert!(!end_proof.0.is_empty()); start_proof.concat_proofs(end_proof); @@ -383,22 +404,24 @@ fn test_one_element_range_proof() { vals.push(&items[i].1); } - assert!(merkle.verify_range_proof(&start_proof, &items[start].0, &items[end].0, keys, vals)); + assert!(merkle.verify_range_proof(&start_proof, &items[start].0, &items[end].0, keys, vals)?); + + Ok(()) } #[test] // The range proof starts from 0 (root) to the last one -fn test_all_elements_proof() { +fn test_all_elements_proof() -> Result<(), DataStoreError> { let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(&items[start].0); + let mut proof = merkle.prove(&items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0); // start and end nodes are the same + let end_proof = merkle.prove(&items[end].0)?; // start and end nodes are the same assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -410,5 +433,7 @@ fn test_all_elements_proof() { vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals); + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + + Ok(()) } From 2d132b01c19c1872921be4d16c3454d5a4a81d0d Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Wed, 1 Feb 2023 18:31:42 +0000 Subject: [PATCH 0049/1053] fix: handle empty key value proof arguments as an error Signed-off-by: Dan Sover --- src/merkle_util.rs | 4 +++- src/proof.rs | 6 ++++++ tests/merkle.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/merkle_util.rs b/src/merkle_util.rs index 34349820d3ba..0ce683d8542c 100644 --- a/src/merkle_util.rs +++ b/src/merkle_util.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum DataStoreError { #[error("failed to insert data")] InsertionError, @@ -24,6 +24,8 @@ pub enum DataStoreError { ProofError, #[error("failed to verify proof")] ProofVerificationError, + #[error("no keys or values found in proof")] + ProofEmptyKeyValuesError, } pub struct MerkleSetup { diff --git a/src/proof.rs b/src/proof.rs index 8102ea0e83c7..b110814b1156 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -27,6 +27,7 @@ pub enum ProofError { NodeNotInTrie, InvalidNode, EmptyRange, + EmptyKeyValues, } impl From for ProofError { @@ -34,6 +35,7 @@ impl From for ProofError { match d { DataStoreError::InsertionError => ProofError::NodesInsertionError, DataStoreError::RootHashError => ProofError::InvalidNode, + DataStoreError::ProofEmptyKeyValuesError => ProofError::EmptyKeyValues, _ => ProofError::InvalidProof, } } @@ -163,6 +165,10 @@ impl Proof { return Err(ProofError::InconsistentProofData) } + if keys.is_empty() { + return Err(ProofError::EmptyKeyValues) + } + // Ensure the received batch is monotonic increasing and contains no deletions for n in 0..keys.len() - 1 { if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { diff --git a/tests/merkle.rs b/tests/merkle.rs index 7e80213dba6d..7c4521d2a62e 100644 --- a/tests/merkle.rs +++ b/tests/merkle.rs @@ -437,3 +437,32 @@ fn test_all_elements_proof() -> Result<(), DataStoreError> { Ok(()) } + +#[test] +// Special case when there is a provided edge proof but zero key/value pairs. +fn test_missing_key_value_pairs() -> Result<(), DataStoreError> { + let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; + items.sort(); + + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + let start = 0; + let end = &items.len() - 1; + + let mut proof = merkle.prove(&items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[end].0)?; // start and end nodes are the same + assert!(!end_proof.0.is_empty()); + + proof.concat_proofs(end_proof); + + // key and value vectors are intentionally empty + let keys: Vec<&&str> = Vec::new(); + let vals: Vec<&&str> = Vec::new(); + + assert_eq!( + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals), + Err(DataStoreError::ProofVerificationError) + ); + + Ok(()) +} From 5e3c41ec837be88fd7c9dc2ec509395fc4dda466 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Fri, 10 Feb 2023 21:25:01 +0000 Subject: [PATCH 0050/1053] cli: Fixup command UX to be positional Signed-off-by: Dan Sover --- src/bin/README.md | 10 +++++----- src/bin/create.rs | 7 +++---- src/bin/delete.rs | 2 +- src/bin/dump.rs | 3 +-- src/bin/get.rs | 2 +- src/bin/insert.rs | 4 ++-- tests/cli.rs | 33 ++++++++++++++------------------- 7 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/bin/README.md b/src/bin/README.md index b1e1d36fcc6e..77934328b68f 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -31,23 +31,23 @@ $ ./target/release/fwdctl -h # Check available options when creating a database, including the defaults. $ fwdctl create -h # Create a new, blank instance of firewood using the default name "firewood". -$ fwdctl create +$ fwdctl create firewood # Look inside, there are several folders representing different components of firewood, including the WAL. $ ls firewood ``` * fwdctl get ``` Get the value associated with a key in the database, if it exists. -fwdctl get --key +fwdctl get ``` -* fwdctl insert --key --value +* fwdctl insert ``` Insert a key/value pair into the database. -fwdctl insert --key --value +fwdctl insert ``` * fwdctl delete ``` Delete a key from the database, along with the associated value. -fwdctl delete --key +fwdctl delete ``` diff --git a/src/bin/create.rs b/src/bin/create.rs index 933f9e9f8305..e0cffc90418c 100644 --- a/src/bin/create.rs +++ b/src/bin/create.rs @@ -7,11 +7,10 @@ use log; pub struct Options { /// DB Options #[arg( - long, - required = false, - default_value_t = String::from("firewood"), + required = true, value_name = "NAME", - help = "A name for the database.")] + help = "A name for the database. A good default name is firewood." + )] pub name: String, #[arg( diff --git a/src/bin/delete.rs b/src/bin/delete.rs index 1502c4c3bd99..a77102996324 100644 --- a/src/bin/delete.rs +++ b/src/bin/delete.rs @@ -6,7 +6,7 @@ use log; #[derive(Debug, Args)] pub struct Options { /// The key to delete - #[arg(long, required = true, value_name = "KEY", help = "Key to delete")] + #[arg(required = true, value_name = "KEY", help = "Key to delete")] pub key: String, /// The database path (if no path is provided, return an error). Defaults to firewood. diff --git a/src/bin/dump.rs b/src/bin/dump.rs index 8ec17c0f1bbf..dabae074410b 100644 --- a/src/bin/dump.rs +++ b/src/bin/dump.rs @@ -7,8 +7,7 @@ use log; pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. #[arg( - long, - required = false, + required = true, value_name = "DB_NAME", default_value_t = String::from("firewood"), help = "Name of the database" diff --git a/src/bin/get.rs b/src/bin/get.rs index 701d9e1fe79a..8b29638a0628 100644 --- a/src/bin/get.rs +++ b/src/bin/get.rs @@ -7,7 +7,7 @@ use std::str; #[derive(Debug, Args)] pub struct Options { /// The key to get the value for - #[arg(long, short = 'k', required = true, value_name = "KEY", help = "Key to get")] + #[arg(required = true, value_name = "KEY", help = "Key to get")] pub key: String, /// The database path (if no path is provided, return an error). Defaults to firewood. diff --git a/src/bin/insert.rs b/src/bin/insert.rs index d57daf4db7ca..4833c9953467 100644 --- a/src/bin/insert.rs +++ b/src/bin/insert.rs @@ -6,11 +6,11 @@ use log; #[derive(Debug, Args)] pub struct Options { /// The key to insert - #[arg(long, short = 'k', required = true, value_name = "KEY", help = "Key to insert")] + #[arg(required = true, value_name = "KEY", help = "Key to insert")] pub key: String, /// The value to insert - #[arg(long, short = 'v', required = true, value_name = "VALUE", help = "Value to insert")] + #[arg(required = true, value_name = "VALUE", help = "Value to insert")] pub value: String, /// The database path (if no path is provided, return an error). Defaults to firewood. diff --git a/tests/cli.rs b/tests/cli.rs index c26681201158..8eccae304f22 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -39,7 +39,6 @@ fn fwdctl_prints_version() -> Result<()> { fn fwdctl_creates_database() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg("--name") .arg(FIREWOOD_TEST_DB_NAME) .assert() .success(); @@ -57,7 +56,6 @@ fn fwdctl_insert_successful() -> Result<()> { // Create db Command::cargo_bin(PRG)? .arg("create") - .arg("--name") .arg(FIREWOOD_TEST_DB_NAME) .assert() .success(); @@ -65,9 +63,9 @@ fn fwdctl_insert_successful() -> Result<()> { // Insert data Command::cargo_bin(PRG)? .arg("insert") + .args(["year"]) + .args(["2023"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) - .args(["--key", "year"]) - .args(["--value", "2023"]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -85,14 +83,14 @@ fn fwdctl_get_successful() -> Result<()> { // Create db and insert data Command::cargo_bin(PRG)? .arg("create") - .args(["--name", FIREWOOD_TEST_DB_NAME]) + .args([FIREWOOD_TEST_DB_NAME]) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") - .args(["--key", "year"]) - .args(["--value", "2023"]) + .args(["year"]) + .args(["2023"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -101,7 +99,7 @@ fn fwdctl_get_successful() -> Result<()> { // Get value back out Command::cargo_bin(PRG)? .arg("get") - .args(["--key", "year"]) + .args(["year"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -119,15 +117,14 @@ fn fwdctl_get_successful() -> Result<()> { fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg("--name") .arg(FIREWOOD_TEST_DB_NAME) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") - .args(["--key", "year"]) - .args(["--value", "2023"]) + .args(["year"]) + .args(["2023"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -136,7 +133,7 @@ fn fwdctl_delete_successful() -> Result<()> { // Delete key Command::cargo_bin(PRG)? .arg("delete") - .args(["--key", "year"]) + .args(["year"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -154,15 +151,14 @@ fn fwdctl_delete_successful() -> Result<()> { fn fwdctl_root_hash() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg("--name") .arg(FIREWOOD_TEST_DB_NAME) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") - .args(["--key", "year"]) - .args(["--value", "2023"]) + .args(["year"]) + .args(["2023"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -188,15 +184,14 @@ fn fwdctl_root_hash() -> Result<()> { fn fwdctl_dump() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg("--name") .arg(FIREWOOD_TEST_DB_NAME) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") - .args(["--key", "year"]) - .args(["--value", "2023"]) + .args(["year"]) + .args(["2023"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() @@ -205,7 +200,7 @@ fn fwdctl_dump() -> Result<()> { // Get root Command::cargo_bin(PRG)? .arg("dump") - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args([FIREWOOD_TEST_DB_NAME]) .assert() .success() .stdout(predicate::str::is_empty().not()); From 565604c59bc5682d48a16decbc040a983a695740 Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 1 Feb 2023 17:04:56 -0500 Subject: [PATCH 0051/1053] Create LICENSE.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9b56520f8df3..38dbd1917e6e 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ the EVM, but instead makes in-place changes to the state tree. This ensures that the database size is small and stable during the course of running firewood. Firewood exists to provide a very fast storage layer for [qEVM](https://github.com/ava-labs/qevm) to use in a custom subnet. +## License +firewood is licensed by the Ecosystem License. For more information, see the +[LICENSE file](./LICENSE.md). + ## Build Firewood currently is Linux-only, as it has a dependency on the asynchronous I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not From 3c5e07d4c96b2e2de1474cc2a55fecbb3e030817 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 27 Feb 2023 13:36:26 -0800 Subject: [PATCH 0052/1053] Fix typo in Cargo.toml verison -> version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e94181a60d37..4297abdea5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } shale = "0.1.7" growth-ring = "0.3.0" futures = "0.3.24" -primitive-types = { verison = "0.12.0", features = ["impl-rlp"] } +primitive-types = { version = "0.12.0", features = ["impl-rlp"] } serde = { version = "1.0", features = ["derive"] } bincode = "1.2.1" clap = { version = "4.0.29", features = ["cargo", "derive"] } From 90d1695818fc28f5154004803e757141905aac24 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Mon, 27 Feb 2023 20:16:04 +0000 Subject: [PATCH 0053/1053] transfervm: Update firewood to match needed functionality This change vendors in changes made in the transfervm copy of firewood that were made to ensure the VM can use firewood as the backing data store. Signed-off-by: Dan Sover --- src/db.rs | 31 +++++++++++++++++++++++++++++++ src/merkle.rs | 10 ++++++++++ src/proof.rs | 9 +++++++++ 3 files changed, 50 insertions(+) diff --git a/src/db.rs b/src/db.rs index cbdd78259e9b..8c60c21dbf65 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,6 @@ use std::collections::VecDeque; +use std::error::Error; +use std::fmt; use std::io::{Cursor, Write}; use std::rc::Rc; use std::thread::JoinHandle; @@ -11,6 +13,7 @@ use typed_builder::TypedBuilder; use crate::account::{Account, AccountRLP, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; +use crate::proof::Proof; use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared}; pub use crate::storage::{DiskBufferConfig, WALConfig}; @@ -30,8 +33,18 @@ pub enum DBError { System(nix::Error), KeyNotFound, CreateError, + InvalidRangeProof, } +impl fmt::Display for DBError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: fix this + write!(f, "{:?}", &self) + } +} + +impl Error for DBError {} + /// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the /// correct parameters are used when the DB is opened later (the parameters here will override the /// parameters in [DBConfig] if the DB already exists). @@ -313,6 +326,24 @@ impl DBRev { }) } + /// Provides a proof that a key is in the MPT. + pub fn prove>(&self, key: K) -> Result { + self.merkle + .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) + .map_err(DBError::Merkle) + } + + /// Verifies a range proof is valid for a set of keys. + pub fn verify_range_proof, V: AsRef<[u8]>>( + &self, proof: Proof, first_key: K, last_key: K, keys: Vec, values: Vec, + ) -> Result { + let hash: [u8; 32] = *self.kv_root_hash()?; + let valid = proof + .verify_range_proof(hash, first_key, last_key, keys, values) + .map_err(|_e| DBError::InvalidRangeProof)?; + Ok(valid) + } + /// Get nonce of the account. pub fn get_nonce(&self, key: &[u8]) -> Result { Ok(self.get_account(key)?.nonce) diff --git a/src/merkle.rs b/src/merkle.rs index 708d75690b6a..8def49237331 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -8,6 +8,7 @@ use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; use std::cell::Cell; use std::cmp; use std::collections::HashMap; +use std::error::Error; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; @@ -21,6 +22,15 @@ pub enum MerkleError { Format(std::io::Error), } +impl fmt::Display for MerkleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: fix this + write!(f, "{:?}", &self) + } +} + +impl Error for MerkleError {} + #[derive(PartialEq, Eq, Clone)] pub struct Hash(pub [u8; 32]); diff --git a/src/proof.rs b/src/proof.rs index b110814b1156..6f20ecf92953 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,3 +1,4 @@ +use crate::db::DBError; use crate::merkle::*; use crate::merkle_util::*; @@ -41,6 +42,14 @@ impl From for ProofError { } } +impl From for ProofError { + fn from(d: DBError) -> ProofError { + match d { + _ => ProofError::InvalidProof, + } + } +} + const EXT_NODE_SIZE: usize = 2; const BRANCH_NODE_SIZE: usize = 17; From ced14ff42ecf707cfee1e167e3282917915e406a Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 28 Feb 2023 13:55:39 +0000 Subject: [PATCH 0054/1053] lib: Update DB and Merkle errors to implement the Error trait Signed-off-by: Dan Sover --- src/db.rs | 23 ++++++++++++++++++----- src/merkle.rs | 8 ++++++-- src/proof.rs | 30 +++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/db.rs b/src/db.rs index 8c60c21dbf65..786c1fd326d6 100644 --- a/src/db.rs +++ b/src/db.rs @@ -13,7 +13,7 @@ use typed_builder::TypedBuilder; use crate::account::{Account, AccountRLP, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; -use crate::proof::Proof; +use crate::proof::{Proof, ProofError}; use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared}; pub use crate::storage::{DiskBufferConfig, WALConfig}; @@ -33,13 +33,20 @@ pub enum DBError { System(nix::Error), KeyNotFound, CreateError, - InvalidRangeProof, + InvalidRangeProof(ProofError), } impl fmt::Display for DBError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO: fix this - write!(f, "{:?}", &self) + match self { + DBError::InvalidParams => write!(f, "invalid parameters provided"), + DBError::Merkle(e) => write!(f, "merkle error: {e:?}"), + DBError::Blob(e) => write!(f, "storage error: {e:?}"), + DBError::System(e) => write!(f, "system error: {e:?}"), + DBError::KeyNotFound => write!(f, "not found"), + DBError::CreateError => write!(f, "database create error"), + DBError::InvalidRangeProof(e) => write!(f, "invalid range proof: {e:?}"), + } } } @@ -340,7 +347,7 @@ impl DBRev { let hash: [u8; 32] = *self.kv_root_hash()?; let valid = proof .verify_range_proof(hash, first_key, last_key, keys, values) - .map_err(|_e| DBError::InvalidRangeProof)?; + .map_err(|e| DBError::InvalidRangeProof(e))?; Ok(valid) } @@ -1054,3 +1061,9 @@ impl<'a> Drop for WriteBatch<'a> { } } } + +#[test] +fn test_proof_error() { + let e = DBError::InvalidRangeProof(ProofError::InvalidNode).to_string(); + assert_eq!(e, "invalid range proof: InvalidNode"); +} diff --git a/src/merkle.rs b/src/merkle.rs index 8def49237331..894bee31cee1 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -24,8 +24,12 @@ pub enum MerkleError { impl fmt::Display for MerkleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO: fix this - write!(f, "{:?}", &self) + match self { + MerkleError::Shale(e) => write!(f, "merkle datastore error: {e:?}"), + MerkleError::ReadOnly => write!(f, "error: read only"), + MerkleError::NotBranchNode => write!(f, "error: node is not a branch node"), + MerkleError::Format(e) => write!(f, "format error: {e:?}"), + } } } diff --git a/src/proof.rs b/src/proof.rs index 6f20ecf92953..8338a6b42360 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -8,6 +8,7 @@ use shale::ObjPtr; use std::cmp::Ordering; use std::collections::HashMap; +use std::fmt; /// Hash -> RLP encoding map #[derive(Serialize, Deserialize)] @@ -45,7 +46,34 @@ impl From for ProofError { impl From for ProofError { fn from(d: DBError) -> ProofError { match d { - _ => ProofError::InvalidProof, + DBError::InvalidParams => ProofError::InvalidProof, + DBError::Merkle(_e) => ProofError::InvalidNode, + DBError::Blob(_e) => ProofError::InvalidNode, + DBError::System(_e) => ProofError::InvalidNode, + DBError::KeyNotFound => ProofError::InvalidEdgeKeys, + DBError::CreateError => ProofError::NoSuchNode, + DBError::InvalidRangeProof(_e) => ProofError::InvalidProof, + } + } +} + +impl fmt::Display for ProofError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProofError::DecodeError => write!(f, "decoding"), + ProofError::NoSuchNode => write!(f, "no such node"), + ProofError::ProofNodeMissing => write!(f, "proof node missing"), + ProofError::InconsistentProofData => write!(f, "inconsistent proof data"), + ProofError::NonMonotonicIncreaseRange => write!(f, "nonmonotonic range increase"), + ProofError::RangeHasDeletion => write!(f, "range has deletion"), + ProofError::InvalidProof => write!(f, "invalid proof"), + ProofError::InvalidEdgeKeys => write!(f, "invalid edge keys"), + ProofError::InconsistentEdgeKeys => write!(f, "inconsistent edge keys"), + ProofError::NodesInsertionError => write!(f, "node insertion error"), + ProofError::NodeNotInTrie => write!(f, "node not in trie"), + ProofError::InvalidNode => write!(f, "invalid node"), + ProofError::EmptyRange => write!(f, "empty range"), + ProofError::EmptyKeyValues => write!(f, "empty keys or values provided"), } } } From 8bd2420bcc1e47d98dad74c034c089de597efd4c Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 28 Feb 2023 16:28:39 +0000 Subject: [PATCH 0055/1053] lib: Update proof errors Signed-off-by: Dan Sover --- src/db.rs | 20 ++++---------------- src/merkle.rs | 4 ++++ src/proof.rs | 27 +++++++++++++++++---------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/db.rs b/src/db.rs index 786c1fd326d6..565bc8fa9ad1 100644 --- a/src/db.rs +++ b/src/db.rs @@ -33,7 +33,6 @@ pub enum DBError { System(nix::Error), KeyNotFound, CreateError, - InvalidRangeProof(ProofError), } impl fmt::Display for DBError { @@ -45,7 +44,6 @@ impl fmt::Display for DBError { DBError::System(e) => write!(f, "system error: {e:?}"), DBError::KeyNotFound => write!(f, "not found"), DBError::CreateError => write!(f, "database create error"), - DBError::InvalidRangeProof(e) => write!(f, "invalid range proof: {e:?}"), } } } @@ -334,20 +332,16 @@ impl DBRev { } /// Provides a proof that a key is in the MPT. - pub fn prove>(&self, key: K) -> Result { - self.merkle - .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) - .map_err(DBError::Merkle) + pub fn prove>(&self, key: K) -> Result { + self.merkle.prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) } /// Verifies a range proof is valid for a set of keys. pub fn verify_range_proof, V: AsRef<[u8]>>( &self, proof: Proof, first_key: K, last_key: K, keys: Vec, values: Vec, - ) -> Result { + ) -> Result { let hash: [u8; 32] = *self.kv_root_hash()?; - let valid = proof - .verify_range_proof(hash, first_key, last_key, keys, values) - .map_err(|e| DBError::InvalidRangeProof(e))?; + let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; Ok(valid) } @@ -1061,9 +1055,3 @@ impl<'a> Drop for WriteBatch<'a> { } } } - -#[test] -fn test_proof_error() { - let e = DBError::InvalidRangeProof(ProofError::InvalidNode).to_string(); - assert_eq!(e, "invalid range proof: InvalidNode"); -} diff --git a/src/merkle.rs b/src/merkle.rs index 894bee31cee1..46edff2ba88f 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -20,6 +20,8 @@ pub enum MerkleError { ReadOnly, NotBranchNode, Format(std::io::Error), + ParentLeafBranch, + UnsetInternal, } impl fmt::Display for MerkleError { @@ -29,6 +31,8 @@ impl fmt::Display for MerkleError { MerkleError::ReadOnly => write!(f, "error: read only"), MerkleError::NotBranchNode => write!(f, "error: node is not a branch node"), MerkleError::Format(e) => write!(f, "format error: {e:?}"), + MerkleError::ParentLeafBranch => write!(f, "parent should not be a leaf branch"), + MerkleError::UnsetInternal => write!(f, "removing internal node references failed"), } } } diff --git a/src/proof.rs b/src/proof.rs index 8338a6b42360..b17d2152253b 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,7 +1,9 @@ +use crate::account::BlobError; use crate::db::DBError; use crate::merkle::*; use crate::merkle_util::*; +use nix::errno::Errno; use serde::{Deserialize, Serialize}; use sha3::Digest; use shale::ObjPtr; @@ -27,16 +29,19 @@ pub enum ProofError { InconsistentEdgeKeys, NodesInsertionError, NodeNotInTrie, - InvalidNode, + InvalidNode(MerkleError), EmptyRange, EmptyKeyValues, + BlobStoreError(BlobError), + SystemError(Errno), + InvalidRootHash, } impl From for ProofError { fn from(d: DataStoreError) -> ProofError { match d { DataStoreError::InsertionError => ProofError::NodesInsertionError, - DataStoreError::RootHashError => ProofError::InvalidNode, + DataStoreError::RootHashError => ProofError::InvalidRootHash, DataStoreError::ProofEmptyKeyValuesError => ProofError::EmptyKeyValues, _ => ProofError::InvalidProof, } @@ -47,12 +52,11 @@ impl From for ProofError { fn from(d: DBError) -> ProofError { match d { DBError::InvalidParams => ProofError::InvalidProof, - DBError::Merkle(_e) => ProofError::InvalidNode, - DBError::Blob(_e) => ProofError::InvalidNode, - DBError::System(_e) => ProofError::InvalidNode, + DBError::Merkle(e) => ProofError::InvalidNode(e), + DBError::Blob(e) => ProofError::BlobStoreError(e), + DBError::System(e) => ProofError::SystemError(e), DBError::KeyNotFound => ProofError::InvalidEdgeKeys, DBError::CreateError => ProofError::NoSuchNode, - DBError::InvalidRangeProof(_e) => ProofError::InvalidProof, } } } @@ -71,9 +75,12 @@ impl fmt::Display for ProofError { ProofError::InconsistentEdgeKeys => write!(f, "inconsistent edge keys"), ProofError::NodesInsertionError => write!(f, "node insertion error"), ProofError::NodeNotInTrie => write!(f, "node not in trie"), - ProofError::InvalidNode => write!(f, "invalid node"), + ProofError::InvalidNode(e) => write!(f, "invalid node: {e:?}"), ProofError::EmptyRange => write!(f, "empty range"), ProofError::EmptyKeyValues => write!(f, "empty keys or values provided"), + ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), + ProofError::SystemError(e) => write!(f, "system error: {e:?}"), + ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), } } } @@ -342,7 +349,7 @@ impl Proof { } } // We should not hit a leaf node as a parent. - _ => return Err(ProofError::InvalidNode), + _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; if chd_ptr.is_some() { @@ -549,7 +556,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right index += cur_key.len(); } // The fork point cannot be a leaf since it doesn't have any children. - _ => return Err(ProofError::InvalidNode), + _ => return Err(ProofError::InvalidNode(MerkleError::UnsetInternal)), } } @@ -634,7 +641,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right } Ok(false) } - _ => Err(ProofError::InvalidNode), + _ => Err(ProofError::InvalidNode(MerkleError::UnsetInternal)), } } From aa2ed6e357c31a6503f693952633257345338a18 Mon Sep 17 00:00:00 2001 From: Dan Sover Date: Tue, 28 Feb 2023 21:28:14 +0000 Subject: [PATCH 0056/1053] proof: Add StdError trait to ProofError Signed-off-by: Dan Sover --- src/proof.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/proof.rs b/src/proof.rs index b17d2152253b..ac7a66a01a3f 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -10,6 +10,7 @@ use shale::ObjPtr; use std::cmp::Ordering; use std::collections::HashMap; +use std::error::Error; use std::fmt; /// Hash -> RLP encoding map @@ -85,6 +86,8 @@ impl fmt::Display for ProofError { } } +impl Error for ProofError {} + const EXT_NODE_SIZE: usize = 2; const BRANCH_NODE_SIZE: usize = 17; From 0f79cb9c4df08e44e2e37e784c1552e09e55294c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 1 Mar 2023 14:25:54 -0800 Subject: [PATCH 0057/1053] Switch to cargo workspaces (#121) * Switch to cargo workspaces This improves build time for building the library only, and will make moving downstream dependencies closely tied to this repo here easier (examples: libaio-futures, growth-ring). Also cleaned up gitignore, using templates from gitignore.io. --- .gitignore | 66 +++++++++++++++++--- Cargo.toml | 55 ++-------------- firewood/Cargo.toml | 33 ++++++++++ {examples => firewood/examples}/benchmark.rs | 0 {examples => firewood/examples}/dump.rs | 0 {examples => firewood/examples}/rev.rs | 0 {examples => firewood/examples}/simple.rs | 0 {src => firewood/src}/account.rs | 0 {src => firewood/src}/db.rs | 0 {src => firewood/src}/dynamic_mem.rs | 0 {src => firewood/src}/file.rs | 0 {src => firewood/src}/lib.rs | 0 {src => firewood/src}/merkle.rs | 0 {src => firewood/src}/merkle_util.rs | 0 {src => firewood/src}/proof.rs | 0 {src => firewood/src}/storage.rs | 0 fwdctl/Cargo.toml | 11 ++++ {src/bin => fwdctl/src}/README.md | 0 {src/bin => fwdctl/src}/create.rs | 0 {src/bin => fwdctl/src}/delete.rs | 0 {src/bin => fwdctl/src}/dump.rs | 0 {src/bin => fwdctl/src}/get.rs | 0 {src/bin => fwdctl/src}/insert.rs | 0 src/bin/fwdctl.rs => fwdctl/src/main.rs | 0 {src/bin => fwdctl/src}/root.rs | 0 25 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 firewood/Cargo.toml rename {examples => firewood/examples}/benchmark.rs (100%) rename {examples => firewood/examples}/dump.rs (100%) rename {examples => firewood/examples}/rev.rs (100%) rename {examples => firewood/examples}/simple.rs (100%) rename {src => firewood/src}/account.rs (100%) rename {src => firewood/src}/db.rs (100%) rename {src => firewood/src}/dynamic_mem.rs (100%) rename {src => firewood/src}/file.rs (100%) rename {src => firewood/src}/lib.rs (100%) rename {src => firewood/src}/merkle.rs (100%) rename {src => firewood/src}/merkle_util.rs (100%) rename {src => firewood/src}/proof.rs (100%) rename {src => firewood/src}/storage.rs (100%) create mode 100644 fwdctl/Cargo.toml rename {src/bin => fwdctl/src}/README.md (100%) rename {src/bin => fwdctl/src}/create.rs (100%) rename {src/bin => fwdctl/src}/delete.rs (100%) rename {src/bin => fwdctl/src}/dump.rs (100%) rename {src/bin => fwdctl/src}/get.rs (100%) rename {src/bin => fwdctl/src}/insert.rs (100%) rename src/bin/fwdctl.rs => fwdctl/src/main.rs (100%) rename {src/bin => fwdctl/src}/root.rs (100%) diff --git a/.gitignore b/.gitignore index 16b5654e16e5..32ed236d9318 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,62 @@ -/target +# ignore test databases +*_db +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,vim + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock -shale/target/ -shale/Cargo.lock -# test database -test_firewood +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide -# vscode -.vscode +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim diff --git a/Cargo.toml b/Cargo.toml index 4297abdea5ba..350fcc1e9ebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,53 +1,8 @@ -[package] -name = "firewood" -version = "0.0.1" -edition = "2021" -autobins = false - -[lib] -name = "firewood" -path = "src/lib.rs" - -[[bin]] -name = "fwdctl" -path = "src/bin/fwdctl.rs" - -[dependencies] -enum-as-inner = "0.5.1" -parking_lot = "0.12.1" -rlp = "0.5.2" -sha3 = "0.10.2" -once_cell = "1.13.1" -hex = "0.4.3" -lru = "0.9.0" -libaio-futures = "0.2.2" -nix = "0.26.1" -typed-builder = "0.12.0" -tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } -shale = "0.1.7" -growth-ring = "0.3.0" -futures = "0.3.24" -primitive-types = { version = "0.12.0", features = ["impl-rlp"] } -serde = { version = "1.0", features = ["derive"] } -bincode = "1.2.1" -clap = { version = "4.0.29", features = ["cargo", "derive"] } -anyhow = "1.0.66" -env_logger = "0.10.0" -log = "0.4.17" -thiserror = "1.0.38" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - - -[dev-dependencies] -clap = { version = "4.0.2", features = ["cargo", "derive"] } -criterion = "0.4.0" -keccak-hasher = "0.15.3" -rand = "0.8.5" -triehash = "0.8.4" -assert_cmd = "2.0.7" -predicates = "2.1.1" -serial_test = "1.0.0" +[workspace] +members = [ + "firewood", + "fwdctl", +] [profile.release] debug = true diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml new file mode 100644 index 000000000000..d3070eb0285d --- /dev/null +++ b/firewood/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "firewood" +version = "0.0.1" +edition = "2021" + +[dependencies] +enum-as-inner = "0.5.1" +parking_lot = "0.12.1" +rlp = "0.5.2" +sha3 = "0.10.2" +once_cell = "1.13.1" +hex = "0.4.3" +lru = "0.9.0" +libaio-futures = "0.2.2" +nix = "0.26.1" +typed-builder = "0.12.0" +tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } +shale = "0.1.7" +growth-ring = "0.3.0" +futures = "0.3.24" +primitive-types = { version = "0.12.0", features = ["impl-rlp"] } +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.38" + +[dev-dependencies] +criterion = "0.4.0" +keccak-hasher = "0.15.3" +rand = "0.8.5" +triehash = "0.8.4" +assert_cmd = "2.0.7" +predicates = "2.1.1" +serial_test = "1.0.0" +clap = { version = "4.0.29" } diff --git a/examples/benchmark.rs b/firewood/examples/benchmark.rs similarity index 100% rename from examples/benchmark.rs rename to firewood/examples/benchmark.rs diff --git a/examples/dump.rs b/firewood/examples/dump.rs similarity index 100% rename from examples/dump.rs rename to firewood/examples/dump.rs diff --git a/examples/rev.rs b/firewood/examples/rev.rs similarity index 100% rename from examples/rev.rs rename to firewood/examples/rev.rs diff --git a/examples/simple.rs b/firewood/examples/simple.rs similarity index 100% rename from examples/simple.rs rename to firewood/examples/simple.rs diff --git a/src/account.rs b/firewood/src/account.rs similarity index 100% rename from src/account.rs rename to firewood/src/account.rs diff --git a/src/db.rs b/firewood/src/db.rs similarity index 100% rename from src/db.rs rename to firewood/src/db.rs diff --git a/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs similarity index 100% rename from src/dynamic_mem.rs rename to firewood/src/dynamic_mem.rs diff --git a/src/file.rs b/firewood/src/file.rs similarity index 100% rename from src/file.rs rename to firewood/src/file.rs diff --git a/src/lib.rs b/firewood/src/lib.rs similarity index 100% rename from src/lib.rs rename to firewood/src/lib.rs diff --git a/src/merkle.rs b/firewood/src/merkle.rs similarity index 100% rename from src/merkle.rs rename to firewood/src/merkle.rs diff --git a/src/merkle_util.rs b/firewood/src/merkle_util.rs similarity index 100% rename from src/merkle_util.rs rename to firewood/src/merkle_util.rs diff --git a/src/proof.rs b/firewood/src/proof.rs similarity index 100% rename from src/proof.rs rename to firewood/src/proof.rs diff --git a/src/storage.rs b/firewood/src/storage.rs similarity index 100% rename from src/storage.rs rename to firewood/src/storage.rs diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml new file mode 100644 index 000000000000..b0b3e7bda0d1 --- /dev/null +++ b/fwdctl/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fwdctl" +version = "0.0.1" +edition = "2021" + +[dependencies] +firewood = { version = "0.0.1", path = "../firewood" } +clap = { version = "4.0.29", features = ["cargo", "derive"] } +anyhow = "1.0.66" +env_logger = "0.10.0" +log = "0.4.17" diff --git a/src/bin/README.md b/fwdctl/src/README.md similarity index 100% rename from src/bin/README.md rename to fwdctl/src/README.md diff --git a/src/bin/create.rs b/fwdctl/src/create.rs similarity index 100% rename from src/bin/create.rs rename to fwdctl/src/create.rs diff --git a/src/bin/delete.rs b/fwdctl/src/delete.rs similarity index 100% rename from src/bin/delete.rs rename to fwdctl/src/delete.rs diff --git a/src/bin/dump.rs b/fwdctl/src/dump.rs similarity index 100% rename from src/bin/dump.rs rename to fwdctl/src/dump.rs diff --git a/src/bin/get.rs b/fwdctl/src/get.rs similarity index 100% rename from src/bin/get.rs rename to fwdctl/src/get.rs diff --git a/src/bin/insert.rs b/fwdctl/src/insert.rs similarity index 100% rename from src/bin/insert.rs rename to fwdctl/src/insert.rs diff --git a/src/bin/fwdctl.rs b/fwdctl/src/main.rs similarity index 100% rename from src/bin/fwdctl.rs rename to fwdctl/src/main.rs diff --git a/src/bin/root.rs b/fwdctl/src/root.rs similarity index 100% rename from src/bin/root.rs rename to fwdctl/src/root.rs From fc3a89f8333920dafa256562788b6c02b8819865 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 1 Mar 2023 16:01:15 -0800 Subject: [PATCH 0058/1053] Remove rustfmt.toml and reformat (#122) Based on discussions online. Just removed rustfmt.toml and ran 'cargo fmt', and improved the cargo fmt check --- .github/workflows/ci.yaml | 2 +- firewood/examples/benchmark.rs | 4 +- firewood/examples/dump.rs | 15 ++- firewood/examples/simple.rs | 8 +- firewood/src/account.rs | 8 +- firewood/src/db.rs | 188 +++++++++++++++++++------- firewood/src/file.rs | 25 +++- firewood/src/merkle.rs | 240 +++++++++++++++++++++------------ firewood/src/merkle_util.rs | 29 +++- firewood/src/proof.rs | 204 +++++++++++++++++++--------- firewood/src/storage.rs | 102 +++++++++----- fwdctl/src/delete.rs | 5 +- fwdctl/src/dump.rs | 2 +- fwdctl/src/get.rs | 4 +- fwdctl/src/main.rs | 3 +- rustfmt.toml | 9 -- 16 files changed, 590 insertions(+), 258 deletions(-) delete mode 100644 rustfmt.toml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4ec7d35a2780..67a6077afa09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,7 @@ jobs: override: true components: rustfmt, clippy - name: Lint - run: cargo fmt && git diff --exit-code + run: cargo fmt -- --check - name: Clippy run: cargo clippy --fix -- -Dwarnings diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 99925177b95f..e094ac1befa4 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -38,7 +38,9 @@ fn main() { workload.push(batch); } println!("workload prepared"); - group.sampling_mode(criterion::SamplingMode::Flat).sample_size(10); + group + .sampling_mode(criterion::SamplingMode::Flat) + .sample_size(10); group.throughput(criterion::Throughput::Elements(total as u64)); group.bench_with_input( format!("nbatch={nbatch} batch_size={batch_size}"), diff --git a/firewood/examples/dump.rs b/firewood/examples/dump.rs index 7e829e7e1968..ccd539fce555 100644 --- a/firewood/examples/dump.rs +++ b/firewood/examples/dump.rs @@ -4,10 +4,19 @@ use firewood::db::{DBConfig, DBError, WALConfig, DB}; /// cargo run --example dump benchmark_db/ fn main() { let matches = command!() - .arg(Arg::new("INPUT").help("db path name").required(false).index(1)) + .arg( + Arg::new("INPUT") + .help("db path name") + .required(false) + .index(1), + ) .get_matches(); let path = get_db_path(matches); - let db = DB::new(path.unwrap().as_str(), &DBConfig::builder().truncate(false).build()).unwrap(); + let db = DB::new( + path.unwrap().as_str(), + &DBConfig::builder().truncate(false).build(), + ) + .unwrap(); let mut stdout = std::io::stdout(); println!("== Account Model =="); db.dump(&mut stdout).unwrap(); @@ -19,7 +28,7 @@ fn main() { /// Otherwise, instantiate a DB called simple_db and return the path. fn get_db_path(matches: ArgMatches) -> Result { if let Some(m) = matches.get_one::("INPUT") { - return Ok(m.to_string()) + return Ok(m.to_string()); } // Build and provide a new db path diff --git a/firewood/examples/simple.rs b/firewood/examples/simple.rs index e4282bc62db1..360b2968e339 100644 --- a/firewood/examples/simple.rs +++ b/firewood/examples/simple.rs @@ -56,11 +56,15 @@ fn main() { let mut acc = None; db.dump(&mut stdout).unwrap(); db.dump_account(b"ted", &mut stdout).unwrap(); - db.new_writebatch().delete_account(b"ted", &mut acc).unwrap(); + db.new_writebatch() + .delete_account(b"ted", &mut acc) + .unwrap(); assert!(acc.is_some()); print_states(&db); db.dump_account(b"ted", &mut stdout).unwrap(); - db.new_writebatch().delete_account(b"nobody", &mut acc).unwrap(); + db.new_writebatch() + .delete_account(b"nobody", &mut acc) + .unwrap(); assert!(acc.is_none()); } } diff --git a/firewood/src/account.rs b/firewood/src/account.rs index abd385de8756..c3385753a985 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -100,9 +100,13 @@ pub enum Blob { impl MummyItem for Blob { // currently there is only one variant of Blob: Code fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { - let raw = mem.get_view(addr, 4).ok_or(ShaleError::LinearMemStoreError)?; + let raw = mem + .get_view(addr, 4) + .ok_or(ShaleError::LinearMemStoreError)?; let len = u32::from_le_bytes(raw[..].try_into().unwrap()) as u64; - let bytes = mem.get_view(addr + 4, len).ok_or(ShaleError::LinearMemStoreError)?; + let bytes = mem + .get_view(addr + 4, len) + .ok_or(ShaleError::LinearMemStoreError)?; Ok(Self::Code(bytes.to_vec())) } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 565bc8fa9ad1..6591358f3385 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,7 +14,9 @@ use crate::account::{Account, AccountRLP, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; use crate::proof::{Proof, ProofError}; -use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared}; +use crate::storage::{ + CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared, +}; pub use crate::storage::{DiskBufferConfig, WALConfig}; const MERKLE_META_SPACE: SpaceID = 0x0; @@ -142,7 +144,11 @@ impl SubUniverse { } impl SubUniverse> { - fn rewind(&self, meta_writes: &[SpaceWrite], payload_writes: &[SpaceWrite]) -> SubUniverse { + fn rewind( + &self, + meta_writes: &[SpaceWrite], + payload_writes: &[SpaceWrite], + ) -> SubUniverse { SubUniverse::new( StoreRevShared::from_ash(self.meta.clone(), meta_writes), StoreRevShared::from_ash(self.payload.clone(), payload_writes), @@ -231,11 +237,16 @@ impl Universe> { impl Universe> { fn rewind( - &self, merkle_meta_writes: &[SpaceWrite], merkle_payload_writes: &[SpaceWrite], - blob_meta_writes: &[SpaceWrite], blob_payload_writes: &[SpaceWrite], + &self, + merkle_meta_writes: &[SpaceWrite], + merkle_payload_writes: &[SpaceWrite], + blob_meta_writes: &[SpaceWrite], + blob_payload_writes: &[SpaceWrite], ) -> Universe { Universe { - merkle: self.merkle.rewind(merkle_meta_writes, merkle_payload_writes), + merkle: self + .merkle + .rewind(merkle_meta_writes, merkle_payload_writes), blob: self.blob.rewind(blob_meta_writes, blob_payload_writes), } } @@ -277,7 +288,9 @@ impl DBRev { /// Dump the MPT of the generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { - self.merkle.dump(self.header.kv_root, w).map_err(DBError::Merkle) + self.merkle + .dump(self.header.kv_root, w) + .map_err(DBError::Merkle) } /// Get root hash of the world state of all accounts. @@ -289,7 +302,9 @@ impl DBRev { /// Dump the MPT of the entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { - self.merkle.dump(self.header.acc_root, w).map_err(DBError::Merkle) + self.merkle + .dump(self.header.acc_root, w) + .map_err(DBError::Merkle) } fn get_account(&self, key: &[u8]) -> Result { @@ -323,7 +338,7 @@ impl DBRev { pub fn get_code(&self, key: &[u8]) -> Result, DBError> { let code = self.get_account(key)?.code; if code.is_null() { - return Ok(Vec::new()) + return Ok(Vec::new()); } let b = self.blob.get_blob(code).map_err(DBError::Blob)?; Ok(match &**b { @@ -333,12 +348,18 @@ impl DBRev { /// Provides a proof that a key is in the MPT. pub fn prove>(&self, key: K) -> Result { - self.merkle.prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) + self.merkle + .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) } /// Verifies a range proof is valid for a set of keys. pub fn verify_range_proof, V: AsRef<[u8]>>( - &self, proof: Proof, first_key: K, last_key: K, keys: Vec, values: Vec, + &self, + proof: Proof, + first_key: K, + last_key: K, + keys: Vec, + values: Vec, ) -> Result { let hash: [u8; 32] = *self.kv_root_hash()?; let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; @@ -354,7 +375,7 @@ impl DBRev { pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result, DBError> { let root = self.get_account(key)?.root; if root.is_null() { - return Ok(Vec::new()) + return Ok(Vec::new()); } Ok(match self.merkle.get(sub_key, root) { Ok(Some(v)) => v.to_vec(), @@ -413,13 +434,16 @@ impl DB { let blob_meta_fd = file::touch_dir("meta", blob_fd).map_err(DBError::System)?; let blob_payload_fd = file::touch_dir("compact", blob_fd).map_err(DBError::System)?; - let file0 = crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?; + let file0 = + crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?; let fd0 = file0.get_fd(); if reset { // initialize DBParams - if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT { - return Err(DBError::InvalidParams) + if cfg.payload_file_nbit < cfg.payload_regn_nbit + || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT + { + return Err(DBError::InvalidParams); } nix::unistd::ftruncate(fd0, 0).map_err(DBError::System)?; nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DBError::System)?; @@ -433,7 +457,8 @@ impl DB { wal_file_nbit: cfg.wal.file_nbit, wal_block_nbit: cfg.wal.block_nbit, }; - nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0).map_err(DBError::System)?; + nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0) + .map_err(DBError::System)?; } // read DBParams @@ -519,12 +544,20 @@ impl DB { let staging = Universe { merkle: SubUniverse::new( - Rc::new(StoreRevMut::new(cached.merkle.meta.clone() as Rc)), - Rc::new(StoreRevMut::new(cached.merkle.payload.clone() as Rc)), + Rc::new(StoreRevMut::new( + cached.merkle.meta.clone() as Rc + )), + Rc::new(StoreRevMut::new( + cached.merkle.payload.clone() as Rc + )), ), blob: SubUniverse::new( - Rc::new(StoreRevMut::new(cached.blob.meta.clone() as Rc)), - Rc::new(StoreRevMut::new(cached.blob.payload.clone() as Rc)), + Rc::new(StoreRevMut::new( + cached.blob.meta.clone() as Rc + )), + Rc::new(StoreRevMut::new( + cached.blob.payload.clone() as Rc + )), ), }; @@ -548,15 +581,21 @@ impl DB { // initialize space headers staging.merkle.meta.write( merkle_payload_header.addr(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + SPACE_RESERVED, + SPACE_RESERVED, + )), + ); + staging.merkle.meta.write( + db_header.addr(), + &shale::to_dehydrated(&DBHeader::new_empty()), ); - staging - .merkle - .meta - .write(db_header.addr(), &shale::to_dehydrated(&DBHeader::new_empty())); staging.blob.meta.write( blob_payload_header.addr(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + SPACE_RESERVED, + SPACE_RESERVED, + )), ); } @@ -572,7 +611,12 @@ impl DB { shale::compact::CompactHeader::MSIZE, ) .unwrap(), - MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(), + MummyObj::ptr_to_obj( + blob_meta_ref, + blob_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), ) }; @@ -663,7 +707,11 @@ impl DB { /// Get a value in the kv store associated with a particular key. pub fn kv_get(&self, key: &[u8]) -> Result, DBError> { - self.inner.lock().latest.kv_get(key).ok_or(DBError::KeyNotFound) + self.inner + .lock() + .latest + .kv_get(key) + .ok_or(DBError::KeyNotFound) } /// Get root hash of the latest world state of all accounts. @@ -702,7 +750,7 @@ impl DB { let rlen = inner.revisions.len(); if nback == 0 || nback > inner.max_revisions { - return None + return None; } if rlen < nback { let ashes = inner.disk_requester.collect_ash(nback); @@ -724,7 +772,7 @@ impl DB { } } if inner.revisions.len() < nback { - return None + return None; } // set up the storage layout let db_header: ObjPtr; @@ -757,7 +805,12 @@ impl DB { shale::compact::CompactHeader::MSIZE, ) .unwrap(), - MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(), + MummyObj::ptr_to_obj( + blob_meta_ref, + blob_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), ) }; @@ -818,20 +871,30 @@ impl<'a> WriteBatch<'a> { /// Insert an item to the generic key-value storage. pub fn kv_insert>(mut self, key: K, val: Vec) -> Result { let (header, merkle, _) = self.m.latest.borrow_split(); - merkle.insert(key, val, header.kv_root).map_err(DBError::Merkle)?; + merkle + .insert(key, val, header.kv_root) + .map_err(DBError::Merkle)?; Ok(self) } /// Remove an item from the generic key-value storage. `val` will be set to the value that is /// removed from the storage if it exists. - pub fn kv_remove>(mut self, key: K, val: &mut Option>) -> Result { + pub fn kv_remove>( + mut self, + key: K, + val: &mut Option>, + ) -> Result { let (header, merkle, _) = self.m.latest.borrow_split(); - *val = merkle.remove(key, header.kv_root).map_err(DBError::Merkle)?; + *val = merkle + .remove(key, header.kv_root) + .map_err(DBError::Merkle)?; Ok(self) } fn change_account( - &mut self, key: &[u8], modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>, + &mut self, + key: &[u8], + modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>, ) -> Result<(), DBError> { let (header, merkle, blob) = self.m.latest.borrow_split(); match merkle.get_mut(key, header.acc_root) { @@ -842,7 +905,7 @@ impl<'a> WriteBatch<'a> { let mut acc = Account::deserialize(b); ret = modify(&mut acc, blob); if ret.is_err() { - return + return; } *b = acc.serialize(); }) @@ -909,8 +972,12 @@ impl<'a> WriteBatch<'a> { if acc.root.is_null() { Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DBError::Merkle)?; } - merkle.insert(sub_key, val, acc.root).map_err(DBError::Merkle)?; - acc.root_hash = merkle.root_hash::(acc.root).map_err(DBError::Merkle)?; + merkle + .insert(sub_key, val, acc.root) + .map_err(DBError::Merkle)?; + acc.root_hash = merkle + .root_hash::(acc.root) + .map_err(DBError::Merkle)?; merkle .insert(key, acc.serialize(), header.acc_root) .map_err(DBError::Merkle)?; @@ -936,13 +1003,17 @@ impl<'a> WriteBatch<'a> { } /// Delete an account. - pub fn delete_account(mut self, key: &[u8], acc: &mut Option) -> Result { + pub fn delete_account( + mut self, + key: &[u8], + acc: &mut Option, + ) -> Result { let (header, merkle, blob_stash) = self.m.latest.borrow_split(); let mut a = match merkle.remove(key, header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => { *acc = None; - return Ok(self) + return Ok(self); } Err(e) => return Err(DBError::Merkle(e)), }; @@ -976,33 +1047,56 @@ impl<'a> WriteBatch<'a> { } // clear the staging layer and apply changes to the CachedSpace inner.latest.flush_dirty().unwrap(); - let (merkle_payload_pages, merkle_payload_plain) = inner.staging.merkle.payload.take_delta(); + let (merkle_payload_pages, merkle_payload_plain) = + inner.staging.merkle.payload.take_delta(); let (merkle_meta_pages, merkle_meta_plain) = inner.staging.merkle.meta.take_delta(); let (blob_payload_pages, blob_payload_plain) = inner.staging.blob.payload.take_delta(); let (blob_meta_pages, blob_meta_plain) = inner.staging.blob.meta.take_delta(); let old_merkle_meta_delta = inner.cached.merkle.meta.update(&merkle_meta_pages).unwrap(); - let old_merkle_payload_delta = inner.cached.merkle.payload.update(&merkle_payload_pages).unwrap(); + let old_merkle_payload_delta = inner + .cached + .merkle + .payload + .update(&merkle_payload_pages) + .unwrap(); let old_blob_meta_delta = inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); - let old_blob_payload_delta = inner.cached.blob.payload.update(&blob_payload_pages).unwrap(); + let old_blob_payload_delta = inner + .cached + .blob + .payload + .update(&blob_payload_pages) + .unwrap(); // update the rolling window of past revisions let new_base = Universe { merkle: SubUniverse::new( StoreRevShared::from_delta(inner.cached.merkle.meta.clone(), old_merkle_meta_delta), - StoreRevShared::from_delta(inner.cached.merkle.payload.clone(), old_merkle_payload_delta), + StoreRevShared::from_delta( + inner.cached.merkle.payload.clone(), + old_merkle_payload_delta, + ), ), blob: SubUniverse::new( StoreRevShared::from_delta(inner.cached.blob.meta.clone(), old_blob_meta_delta), - StoreRevShared::from_delta(inner.cached.blob.payload.clone(), old_blob_payload_delta), + StoreRevShared::from_delta( + inner.cached.blob.payload.clone(), + old_blob_payload_delta, + ), ), }; if let Some(rev) = inner.revisions.front_mut() { - rev.merkle.meta.set_prev(new_base.merkle.meta.inner().clone()); - rev.merkle.payload.set_prev(new_base.merkle.payload.inner().clone()); + rev.merkle + .meta + .set_prev(new_base.merkle.meta.inner().clone()); + rev.merkle + .payload + .set_prev(new_base.merkle.payload.inner().clone()); rev.blob.meta.set_prev(new_base.blob.meta.inner().clone()); - rev.blob.payload.set_prev(new_base.blob.payload.inner().clone()); + rev.blob + .payload + .set_prev(new_base.blob.payload.inner().clone()); } inner.revisions.push_front(new_base); while inner.revisions.len() > inner.max_revisions { diff --git a/firewood/src/file.rs b/firewood/src/file.rs index f4706a8aa26a..fd1d5fd522e8 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -19,7 +19,11 @@ impl File { openat( rootfd, fname, - (if truncate { OFlag::O_TRUNC } else { OFlag::empty() }) | OFlag::O_RDWR, + (if truncate { + OFlag::O_TRUNC + } else { + OFlag::empty() + }) | OFlag::O_RDWR, Mode::S_IRUSR | Mode::S_IWUSR, ) } @@ -77,13 +81,24 @@ impl Drop for File { pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result { use nix::sys::stat::mkdirat; - if mkdirat(rootfd, dirname, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR).is_err() { + if mkdirat( + rootfd, + dirname, + Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR, + ) + .is_err() + { let errno = nix::errno::from_i32(nix::errno::errno()); if errno != nix::errno::Errno::EEXIST { - return Err(errno) + return Err(errno); } } - openat(rootfd, dirname, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) + openat( + rootfd, + dirname, + OFlag::O_DIRECTORY | OFlag::O_PATH, + Mode::empty(), + ) } pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { @@ -94,7 +109,7 @@ pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { match mkdir(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { Err(e) => { if truncate { - return Err(e) + return Err(e); } } Ok(_) => { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 46edff2ba88f..d9009bba0b1f 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -55,7 +55,9 @@ impl std::ops::Deref for Hash { impl MummyItem for Hash { fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { - let raw = mem.get_view(addr, Self::MSIZE).ok_or(ShaleError::LinearMemStoreError)?; + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; Ok(Self(raw[..Self::MSIZE as usize].try_into().unwrap())) } @@ -96,7 +98,11 @@ impl PartialPath { fn encode(&self, term: bool) -> Vec { let odd_len = (self.0.len() & 1) as u8; let flags = if term { 2 } else { 0 } + odd_len; - let mut res = if odd_len == 1 { vec![flags] } else { vec![flags, 0x0] }; + let mut res = if odd_len == 1 { + vec![flags] + } else { + vec![flags, 0x0] + }; res.extend(&self.0); res } @@ -190,7 +196,7 @@ impl BranchNode { has_chd = true; if only_chd.is_some() { only_chd = None; - break + break; } only_chd = (*c).map(|e| (e, i as u8)) } @@ -235,7 +241,9 @@ impl BranchNode { } pub fn new( - chd: [Option>; NBRANCH], value: Option>, chd_eth_rlp: [Option>; NBRANCH], + chd: [Option>; NBRANCH], + value: Option>, + chd_eth_rlp: [Option>; NBRANCH], ) -> Self { BranchNode { chd, @@ -272,7 +280,11 @@ impl Debug for LeafNode { impl LeafNode { fn calc_eth_rlp(&self) -> Vec { - rlp::encode_list::, _>(&[from_nibbles(&self.0.encode(true)).collect(), T::transform(&self.1)]).into() + rlp::encode_list::, _>(&[ + from_nibbles(&self.0.encode(true)).collect(), + T::transform(&self.1), + ]) + .into() } pub fn new(path: Vec, data: Vec) -> Self { @@ -383,7 +395,8 @@ impl Node { } fn get_eth_rlp(&self, store: &dyn ShaleStore) -> &[u8] { - self.eth_rlp.get_or_init(|| self.inner.calc_eth_rlp::(store)) + self.eth_rlp + .get_or_init(|| self.inner.calc_eth_rlp::(store)) } fn get_root_hash(&self, store: &dyn ShaleStore) -> &Hash { @@ -451,7 +464,9 @@ impl MummyItem for Node { fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { let dec_err = |_| ShaleError::DecodeError; const META_SIZE: u64 = 32 + 1 + 1; - let meta_raw = mem.get_view(addr, META_SIZE).ok_or(ShaleError::LinearMemStoreError)?; + let meta_raw = mem + .get_view(addr, META_SIZE) + .ok_or(ShaleError::LinearMemStoreError)?; let attrs = meta_raw[32]; let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None @@ -473,13 +488,15 @@ impl MummyItem for Node { let mut chd = [None; NBRANCH]; let mut buff = [0; 8]; for chd in chd.iter_mut() { - cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff) + .map_err(|_| ShaleError::DecodeError)?; let addr = u64::from_le_bytes(buff); if addr != 0 { *chd = Some(unsafe { ObjPtr::new_from_addr(addr) }) } } - cur.read_exact(&mut buff[..4]).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff[..4]) + .map_err(|_| ShaleError::DecodeError)?; let raw_len = u32::from_le_bytes(buff[..4].try_into().map_err(dec_err)?) as u64; let value = if raw_len == u32::MAX as u64 { None @@ -503,7 +520,8 @@ impl MummyItem for Node { .get_view(offset + cur_rlp_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; cur = Cursor::new(rlp_len_raw.deref()); - cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff) + .map_err(|_| ShaleError::DecodeError)?; let rlp_len = buff[0] as u64; cur_rlp_len += 1; if rlp_len != 0 { @@ -533,9 +551,11 @@ impl MummyItem for Node { .ok_or(ShaleError::LinearMemStoreError)?; let mut cur = Cursor::new(node_raw.deref()); let mut buff = [0; 8]; - cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff[..1]) + .map_err(|_| ShaleError::DecodeError)?; let path_len = buff[0] as u64; - cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff) + .map_err(|_| ShaleError::DecodeError)?; let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( &mem.get_view(addr + META_SIZE + ext_header_size, path_len) @@ -549,7 +569,8 @@ impl MummyItem for Node { .get_view(addr + META_SIZE + ext_header_size + path_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; cur = Cursor::new(rlp_len_raw.deref()); - cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff) + .map_err(|_| ShaleError::DecodeError)?; let rlp_len = buff[0] as u64; let rlp: Option> = if rlp_len != 0 { let rlp_raw = mem @@ -573,9 +594,11 @@ impl MummyItem for Node { .ok_or(ShaleError::LinearMemStoreError)?; let mut cur = Cursor::new(node_raw.deref()); let mut buff = [0; 4]; - cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff[..1]) + .map_err(|_| ShaleError::DecodeError)?; let path_len = buff[0] as u64; - cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff) + .map_err(|_| ShaleError::DecodeError)?; let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) @@ -594,9 +617,9 @@ impl MummyItem for Node { } fn dehydrated_len(&self) -> u64 { - 32 + 1 + - 1 + - match &self.inner { + 32 + 1 + + 1 + + match &self.inner { NodeType::Branch(n) => { let mut rlp_len = 0; for rlp in n.chd_eth_rlp.iter() { @@ -605,18 +628,18 @@ impl MummyItem for Node { None => 1, } } - NBRANCH as u64 * 8 + - 4 + - match &n.value { + NBRANCH as u64 * 8 + + 4 + + match &n.value { Some(val) => val.len() as u64, None => 0, - } + - rlp_len + } + + rlp_len } NodeType::Extension(n) => { - 1 + 8 + - n.0.dehydrated_len() + - match &n.2 { + 1 + 8 + + n.0.dehydrated_len() + + match &n.2 { Some(v) => 1 + v.len() as u64, None => 1, } @@ -728,7 +751,10 @@ fn test_merkle_node_encoding() { Node::new_from_hash( None, None, - NodeType::Leaf(LeafNode(PartialPath(vec![0x1, 0x2, 0x3]), Data(vec![0x4, 0x5]))), + NodeType::Leaf(LeafNode( + PartialPath(vec![0x1, 0x2, 0x3]), + Data(vec![0x4, 0x5]), + )), ), Node::new_from_hash( None, @@ -805,7 +831,10 @@ impl Merkle { Self { store } } - pub fn init_root(root: &mut ObjPtr, store: &dyn ShaleStore) -> Result<(), MerkleError> { + pub fn init_root( + root: &mut ObjPtr, + store: &dyn ShaleStore, + ) -> Result<(), MerkleError> { *root = store .put_item( Node::new(NodeType::Branch(BranchNode { @@ -910,8 +939,14 @@ impl Merkle { #[allow(clippy::too_many_arguments)] fn split<'b>( - &self, mut u_ref: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], rem_path: &[u8], n_path: Vec, - n_value: Option, val: Vec, deleted: &mut Vec>, + &self, + mut u_ref: ObjRef<'b, Node>, + parents: &mut [(ObjRef<'b, Node>, u8)], + rem_path: &[u8], + n_path: Vec, + n_value: Option, + val: Vec, + deleted: &mut Vec>, ) -> Result>, MerkleError> { let u_ptr = u_ref.as_ptr(); let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) { @@ -982,7 +1017,8 @@ impl Merkle { let mut b_ref = self.get_node(u.1)?; if b_ref .write(|b| { - b.inner.as_branch_mut().unwrap().value = Some(Data(val)); + b.inner.as_branch_mut().unwrap().value = + Some(Data(val)); b.rehash() }) .is_none() @@ -1002,7 +1038,7 @@ impl Merkle { parents, deleted ); - return err.unwrap_or_else(|| Ok(None)) + return err.unwrap_or_else(|| Ok(None)); } let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() { // key path is a prefix of the path to u @@ -1036,7 +1072,7 @@ impl Merkle { // key path extends the path to u if n_value.is_none() { // this case does not apply to an extension node, resume the tree walk - return Ok(Some(val)) + return Ok(Some(val)); } let leaf = self.new_node(Node::new(NodeType::Leaf(LeafNode( PartialPath(rem_path[n_path.len() + 1..].to_vec()), @@ -1075,7 +1111,12 @@ impl Merkle { Ok(None) } - pub fn insert>(&mut self, key: K, val: Vec, root: ObjPtr) -> Result<(), MerkleError> { + pub fn insert>( + &mut self, + key: K, + val: Vec, + root: ObjPtr, + ) -> Result<(), MerkleError> { let mut deleted = Vec::new(); let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); @@ -1086,7 +1127,7 @@ impl Merkle { for (i, nib) in chunks.iter().enumerate() { if nskip > 0 { nskip -= 1; - continue + continue; } let mut u = u_ref.take().unwrap(); let u_ptr = u.as_ptr(); @@ -1107,7 +1148,7 @@ impl Merkle { u.rehash(); }) .unwrap(); - break + break; } }, NodeType::Leaf(n) => { @@ -1122,7 +1163,7 @@ impl Merkle { val.take().unwrap(), &mut deleted, )?; - break + break; } NodeType::Extension(n) => { let n_path = n.0.to_vec(); @@ -1141,7 +1182,7 @@ impl Merkle { u = self.get_node(u_ptr)?; n_ptr } else { - break + break; } } }; @@ -1225,7 +1266,9 @@ impl Merkle { } fn after_remove_leaf<'b>( - &self, parents: &mut Vec<(ObjRef<'b, Node>, u8)>, deleted: &mut Vec>, + &self, + parents: &mut Vec<(ObjRef<'b, Node>, u8)>, + deleted: &mut Vec>, ) -> Result<(), MerkleError> { let (b_chd, val) = { let (mut b_ref, b_idx) = parents.pop().unwrap(); @@ -1239,7 +1282,7 @@ impl Merkle { let b_inner = b_ref.inner.as_branch().unwrap(); let (b_chd, has_chd) = b_inner.single_child(); if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() { - return Ok(()) + return Ok(()); } deleted.push(b_ref.as_ptr()); (b_chd, b_inner.value.clone()) @@ -1252,7 +1295,10 @@ impl Merkle { // from: [p: Branch] -> [b (v)]x -> [Leaf]x // to: [p: Branch] -> [Leaf (v)] let leaf = self - .new_node(Node::new(NodeType::Leaf(LeafNode(PartialPath(Vec::new()), val))))? + .new_node(Node::new(NodeType::Leaf(LeafNode( + PartialPath(Vec::new()), + val, + ))))? .as_ptr(); p_ref .write(|p| { @@ -1349,7 +1395,8 @@ impl Merkle { drop(c_ref); p_ref .write(|p| { - p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(c_ptr); + p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = + Some(c_ptr); p.rehash() }) .unwrap(); @@ -1392,7 +1439,9 @@ impl Merkle { } fn after_remove_branch<'b>( - &self, (c_ptr, idx): (ObjPtr, u8), parents: &mut Vec<(ObjRef<'b, Node>, u8)>, + &self, + (c_ptr, idx): (ObjPtr, u8), + parents: &mut Vec<(ObjRef<'b, Node>, u8)>, deleted: &mut Vec>, ) -> Result<(), MerkleError> { // [b] -> [u] -> [c] @@ -1411,14 +1460,13 @@ impl Merkle { NodeType::Branch(n) => { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] - n.chd[b_idx as usize] = Some( - self.new_node(Node::new(NodeType::Extension(ExtNode( - PartialPath(vec![idx]), - c_ptr, - None, - ))))? - .as_ptr(), - ); + n.chd[b_idx as usize] = + Some( + self.new_node(Node::new(NodeType::Extension( + ExtNode(PartialPath(vec![idx]), c_ptr, None), + )))? + .as_ptr(), + ); } NodeType::Extension(n) => { // from: [Ext] -> [Branch]x -> [Branch] @@ -1438,7 +1486,7 @@ impl Merkle { deleted ); if let Some(e) = err { - return e + return e; } } NodeType::Leaf(_) | NodeType::Extension(_) => match &b_ref.inner { @@ -1506,12 +1554,16 @@ impl Merkle { Ok(()) } - pub fn remove>(&mut self, key: K, root: ObjPtr) -> Result>, MerkleError> { + pub fn remove>( + &mut self, + key: K, + root: ObjPtr, + ) -> Result>, MerkleError> { let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); if root.is_null() { - return Ok(None) + return Ok(None); } let mut deleted = Vec::new(); @@ -1523,7 +1575,7 @@ impl Merkle { for (i, nib) in chunks.iter().enumerate() { if nskip > 0 { nskip -= 1; - continue + continue; } let next_ptr = match &u_ref.inner { NodeType::Branch(n) => match n.chd[*nib as usize] { @@ -1532,18 +1584,18 @@ impl Merkle { }, NodeType::Leaf(n) => { if chunks[i..] != *n.0 { - return Ok(None) + return Ok(None); } found = Some(n.1.clone()); deleted.push(u_ref.as_ptr()); self.after_remove_leaf(&mut parents, &mut deleted)?; - break + break; } NodeType::Extension(n) => { let n_path = &*n.0; let rem_path = &chunks[i..]; if rem_path < n_path || &rem_path[..n_path.len()] != n_path { - return Ok(None) + return Ok(None); } nskip = n_path.len() - 1; n.1 @@ -1557,7 +1609,7 @@ impl Merkle { match &u_ref.inner { NodeType::Branch(n) => { if n.value.is_none() { - return Ok(None) + return Ok(None); } let (c_chd, _) = n.single_child(); u_ref @@ -1573,7 +1625,7 @@ impl Merkle { } NodeType::Leaf(n) => { if n.0.len() > 0 { - return Ok(None) + return Ok(None); } found = Some(n.1.clone()); deleted.push(u_ref.as_ptr()); @@ -1595,7 +1647,11 @@ impl Merkle { Ok(found.map(|e| e.0)) } - fn remove_tree_(&self, u: ObjPtr, deleted: &mut Vec>) -> Result<(), MerkleError> { + fn remove_tree_( + &self, + u: ObjPtr, + deleted: &mut Vec>, + ) -> Result<(), MerkleError> { let u_ref = self.get_node(u)?; match &u_ref.inner { NodeType::Branch(n) => { @@ -1613,7 +1669,7 @@ impl Merkle { pub fn remove_tree(&mut self, root: ObjPtr) -> Result<(), MerkleError> { let mut deleted = Vec::new(); if root.is_null() { - return Ok(()) + return Ok(()); } self.remove_tree_(root, &mut deleted)?; for ptr in deleted.into_iter() { @@ -1622,13 +1678,17 @@ impl Merkle { Ok(()) } - pub fn get_mut>(&mut self, key: K, root: ObjPtr) -> Result, MerkleError> { + pub fn get_mut>( + &mut self, + key: K, + root: ObjPtr, + ) -> Result, MerkleError> { let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); let mut parents = Vec::new(); if root.is_null() { - return Ok(None) + return Ok(None); } let mut u_ref = self.get_node(root)?; @@ -1638,7 +1698,7 @@ impl Merkle { let u_ptr = u_ref.as_ptr(); if nskip > 0 { nskip -= 1; - continue + continue; } let next_ptr = match &u_ref.inner { NodeType::Branch(n) => match n.chd[*nib as usize] { @@ -1647,16 +1707,16 @@ impl Merkle { }, NodeType::Leaf(n) => { if chunks[i..] != *n.0 { - return Ok(None) + return Ok(None); } drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))) + return Ok(Some(RefMut::new(u_ptr, parents, self))); } NodeType::Extension(n) => { let n_path = &*n.0; let rem_path = &chunks[i..]; if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { - return Ok(None) + return Ok(None); } nskip = n_path.len() - 1; n.1 @@ -1671,13 +1731,13 @@ impl Merkle { NodeType::Branch(n) => { if n.value.as_ref().is_some() { drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))) + return Ok(Some(RefMut::new(u_ptr, parents, self))); } } NodeType::Leaf(n) => { if n.0.len() == 0 { drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))) + return Ok(Some(RefMut::new(u_ptr, parents, self))); } } _ => (), @@ -1703,7 +1763,7 @@ impl Merkle { let mut proofs: HashMap<[u8; 32], Vec> = HashMap::new(); if root.is_null() { - return Ok(Proof(proofs)) + return Ok(Proof(proofs)); } // Skip the sentinel root @@ -1723,7 +1783,7 @@ impl Merkle { for (i, nib) in chunks.iter().enumerate() { if nskip > 0 { nskip -= 1; - continue + continue; } nodes.push(u_ref.as_ptr()); let next_ptr: ObjPtr = match &u_ref.inner { @@ -1735,8 +1795,10 @@ impl Merkle { NodeType::Extension(n) => { let n_path = &*n.0; let remaining_path = &chunks[i..]; - if remaining_path.len() < n_path.len() || &remaining_path[..n_path.len()] != n_path { - break + if remaining_path.len() < n_path.len() + || &remaining_path[..n_path.len()] != n_path + { + break; } else { nskip = n_path.len() - 1; n.1 @@ -1771,12 +1833,16 @@ impl Merkle { Ok(Proof(proofs)) } - pub fn get>(&self, key: K, root: ObjPtr) -> Result, MerkleError> { + pub fn get>( + &self, + key: K, + root: ObjPtr, + ) -> Result, MerkleError> { let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); if root.is_null() { - return Ok(None) + return Ok(None); } let mut u_ref = self.get_node(root)?; @@ -1785,7 +1851,7 @@ impl Merkle { for (i, nib) in chunks.iter().enumerate() { if nskip > 0 { nskip -= 1; - continue + continue; } let next_ptr = match &u_ref.inner { NodeType::Branch(n) => match n.chd[*nib as usize] { @@ -1794,15 +1860,15 @@ impl Merkle { }, NodeType::Leaf(n) => { if chunks[i..] != *n.0 { - return Ok(None) + return Ok(None); } - return Ok(Some(Ref(u_ref))) + return Ok(Some(Ref(u_ref))); } NodeType::Extension(n) => { let n_path = &*n.0; let rem_path = &chunks[i..]; if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { - return Ok(None) + return Ok(None); } nskip = n_path.len() - 1; n.1 @@ -1814,12 +1880,12 @@ impl Merkle { match &u_ref.inner { NodeType::Branch(n) => { if n.value.as_ref().is_some() { - return Ok(Some(Ref(u_ref))) + return Ok(Some(Ref(u_ref))); } } NodeType::Leaf(n) => { if n.0.len() == 0 { - return Ok(Some(Ref(u_ref))) + return Ok(Some(Ref(u_ref))); } } _ => (), @@ -1854,7 +1920,11 @@ impl<'a> std::ops::Deref for Ref<'a> { impl<'a> RefMut<'a> { fn new(ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, merkle: &'a mut Merkle) -> Self { - Self { ptr, parents, merkle } + Self { + ptr, + parents, + merkle, + } } pub fn get(&self) -> Ref { @@ -1905,7 +1975,9 @@ impl ValueTransformer for IdTrans { } pub fn to_nibbles(bytes: &[u8]) -> impl Iterator + '_ { - bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) + bytes + .iter() + .flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) } pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 0ce683d8542c..e5944829cb2c 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -47,7 +47,9 @@ impl MerkleSetup { } pub fn get>(&self, key: K) -> Result, DataStoreError> { - self.merkle.get(key, self.root).map_err(|_err| DataStoreError::GetError) + self.merkle + .get(key, self.root) + .map_err(|_err| DataStoreError::GetError) } pub fn get_mut>(&mut self, key: K) -> Result, DataStoreError> { @@ -84,7 +86,11 @@ impl MerkleSetup { .map_err(|_err| DataStoreError::ProofError) } - pub fn verify_proof>(&self, key: K, proof: &Proof) -> Result>, DataStoreError> { + pub fn verify_proof>( + &self, + key: K, + proof: &Proof, + ) -> Result>, DataStoreError> { let hash: [u8; 32] = *self.root_hash()?; proof .verify_proof(key, hash) @@ -92,7 +98,12 @@ impl MerkleSetup { } pub fn verify_range_proof, V: AsRef<[u8]>>( - &self, proof: &Proof, first_key: K, last_key: K, keys: Vec, vals: Vec, + &self, + proof: &Proof, + first_key: K, + last_key: K, + keys: Vec, + vals: Vec, ) -> Result { let hash: [u8; 32] = *self.root_hash()?; proof @@ -115,12 +126,18 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { ); let compact_header = unsafe { - MummyObj::ptr_to_obj(mem_meta.as_ref(), compact_header, shale::compact::CompactHeader::MSIZE).unwrap() + MummyObj::ptr_to_obj( + mem_meta.as_ref(), + compact_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap() }; let cache = shale::ObjCache::new(1); - let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("CompactSpace init fail"); + let space = + shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("CompactSpace init fail"); let mut root = ObjPtr::null(); Merkle::init_root(&mut root, &space).unwrap(); MerkleSetup { diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index ac7a66a01a3f..21887f5e2ce7 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -104,7 +104,11 @@ impl Proof { /// verify_proof checks merkle proofs. The given proof must contain the value for /// key in a trie with the given root hash. VerifyProof returns an error if the /// proof contains invalid trie nodes or the wrong value. - pub fn verify_proof>(&self, key: K, root_hash: [u8; 32]) -> Result>, ProofError> { + pub fn verify_proof>( + &self, + key: K, + root_hash: [u8; 32], + ) -> Result>, ProofError> { let mut chunks = Vec::new(); chunks.extend(to_nibbles(key.as_ref())); @@ -113,19 +117,21 @@ impl Proof { let proofs_map = &self.0; let mut index = 0; loop { - let cur_proof = proofs_map.get(&cur_hash).ok_or(ProofError::ProofNodeMissing)?; + let cur_proof = proofs_map + .get(&cur_hash) + .ok_or(ProofError::ProofNodeMissing)?; let (sub_proof, size) = self.locate_subproof(cur_key, cur_proof)?; index += size; match sub_proof { Some(p) => { // Return when reaching the end of the key. if index == chunks.len() { - return Ok(Some(p.rlp)) + return Ok(Some(p.rlp)); } // The trie doesn't contain the key. if p.hash.is_none() { - return Ok(None) + return Ok(None); } cur_hash = p.hash.unwrap(); cur_key = &chunks[index..]; @@ -136,12 +142,17 @@ impl Proof { } } - fn locate_subproof(&self, key: &[u8], buf: &[u8]) -> Result<(Option, usize), ProofError> { + fn locate_subproof( + &self, + key: &[u8], + buf: &[u8], + ) -> Result<(Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count().unwrap(); match size { EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let cur_key_path: Vec<_> = + to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); @@ -154,17 +165,24 @@ impl Proof { // Check if the key of current node match with the given key. if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { - return Ok((None, 0)) + return Ok((None, 0)); } if term { - Ok((Some(SubProof { rlp: data, hash: None }), cur_key.len())) + Ok(( + Some(SubProof { + rlp: data, + hash: None, + }), + cur_key.len(), + )) } else { - self.generate_subproof(data).map(|subproof| (subproof, cur_key.len())) + self.generate_subproof(data) + .map(|subproof| (subproof, cur_key.len())) } } BRANCH_NODE_SIZE => { if key.is_empty() { - return Err(ProofError::NoSuchNode) + return Err(ProofError::NoSuchNode); } let index = key[0]; let rlp = rlp.at(index as usize).unwrap(); @@ -206,26 +224,31 @@ impl Proof { } pub fn verify_range_proof, V: AsRef<[u8]>>( - &self, root_hash: [u8; 32], first_key: K, last_key: K, keys: Vec, vals: Vec, + &self, + root_hash: [u8; 32], + first_key: K, + last_key: K, + keys: Vec, + vals: Vec, ) -> Result { if keys.len() != vals.len() { - return Err(ProofError::InconsistentProofData) + return Err(ProofError::InconsistentProofData); } if keys.is_empty() { - return Err(ProofError::EmptyKeyValues) + return Err(ProofError::EmptyKeyValues); } // Ensure the received batch is monotonic increasing and contains no deletions for n in 0..keys.len() - 1 { if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { - return Err(ProofError::NonMonotonicIncreaseRange) + return Err(ProofError::NonMonotonicIncreaseRange); } } for v in vals.iter() { if v.as_ref().is_empty() { - return Err(ProofError::RangeHasDeletion) + return Err(ProofError::RangeHasDeletion); } } @@ -239,9 +262,9 @@ impl Proof { } let merkle_root = &*merkle.root_hash()?; if merkle_root != &root_hash { - return Err(ProofError::InvalidProof) + return Err(ProofError::InvalidProof); } - return Ok(false) + return Ok(false); } // TODO(Hao): handle special case when there is a provided edge proof but zero key/value pairs. // TODO(Hao): handle special case when there is only one element and two edge keys are same. @@ -249,12 +272,12 @@ impl Proof { // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if compare(first_key.as_ref(), last_key.as_ref()).is_ge() { - return Err(ProofError::InvalidEdgeKeys) + return Err(ProofError::InvalidEdgeKeys); } // TODO(Hao): different length edge keys should be supported if first_key.as_ref().len() != last_key.as_ref().len() { - return Err(ProofError::InconsistentEdgeKeys) + return Err(ProofError::InconsistentEdgeKeys); } // Convert the edge proofs to edge trie paths. Then we can @@ -270,7 +293,8 @@ impl Proof { // Remove all internal calcuated RLP values. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. - let fork_at_root = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; + let fork_at_root = + unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; // If the fork point is the root, the trie should be empty, start with a new one. if fork_at_root { merkle_setup = new_merkle(0x100000, 0x100000); @@ -283,7 +307,7 @@ impl Proof { // Calculate the hash let merkle_root = &*merkle_setup.root_hash()?; if merkle_root != &root_hash { - return Err(ProofError::InvalidProof) + return Err(ProofError::InvalidProof); } Ok(true) @@ -295,7 +319,11 @@ impl Proof { /// /// The given edge proof is allowed to be an existent or non-existent proof. fn proof_to_path>( - &self, key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, + &self, + key: K, + root_hash: [u8; 32], + merkle_setup: &mut MerkleSetup, + allow_non_existent_node: bool, ) -> Result<(), ProofError> { let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); @@ -311,7 +339,9 @@ impl Proof { let mut branch_index: u8 = 0; let mut iter = 0; loop { - let cur_proof = proofs_map.get(&cur_hash).ok_or(ProofError::ProofNodeMissing)?; + let cur_proof = proofs_map + .get(&cur_hash) + .ok_or(ProofError::ProofNodeMissing)?; // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. let (mut chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof)?; // Link the child to the parent based on the node type. @@ -356,7 +386,9 @@ impl Proof { }; if chd_ptr.is_some() { - u_ref = merkle.get_node(chd_ptr.unwrap()).map_err(|_| ProofError::DecodeError)?; + u_ref = merkle + .get_node(chd_ptr.unwrap()) + .map_err(|_| ProofError::DecodeError)?; // If the new parent is a branch node, record the index to correctly link the next child to it. if u_ref.inner().as_branch().is_some() { branch_index = chunks[key_index]; @@ -364,7 +396,7 @@ impl Proof { } else { // Root node must be included in the proof. if iter == 0 { - return Err(ProofError::ProofNodeMissing) + return Err(ProofError::ProofNodeMissing); } } iter += 1; @@ -376,16 +408,16 @@ impl Proof { if key_index == chunks.len() { // Release the handle to the node. drop(u_ref); - return Ok(()) + return Ok(()); } // The trie doesn't contain the key. if p.hash.is_none() { drop(u_ref); if allow_non_existent_node { - return Ok(()) + return Ok(()); } - return Err(ProofError::NodeNotInTrie) + return Err(ProofError::NodeNotInTrie); } cur_hash = p.hash.unwrap(); cur_key = &chunks[key_index..]; @@ -397,9 +429,9 @@ impl Proof { None => { drop(u_ref); if allow_non_existent_node { - return Ok(()) + return Ok(()); } - return Err(ProofError::NodeNotInTrie) + return Err(ProofError::NodeNotInTrie); } } } @@ -407,13 +439,17 @@ impl Proof { /// Decode the RLP value to generate the corresponding type of node, and locate the subproof. fn decode_node( - &self, merkle: &Merkle, key: &[u8], buf: &[u8], + &self, + merkle: &Merkle, + key: &[u8], + buf: &[u8], ) -> Result<(Option>, Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count().unwrap(); match size { EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let cur_key_path: Vec<_> = + to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); @@ -426,14 +462,18 @@ impl Proof { // Check if the key of current node match with the given key. if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { - return Ok((None, None, 0)) + return Ok((None, None, 0)); } if term { - let leaf = Node::new(NodeType::Leaf(LeafNode::new(cur_key.clone(), data.clone()))); + let leaf = + Node::new(NodeType::Leaf(LeafNode::new(cur_key.clone(), data.clone()))); let leaf_ptr = merkle.new_node(leaf).map_err(|_| ProofError::DecodeError)?; Ok(( Some(leaf_ptr.as_ptr()), - Some(SubProof { rlp: data, hash: None }), + Some(SubProof { + rlp: data, + hash: None, + }), cur_key.len(), )) } else { @@ -444,13 +484,15 @@ impl Proof { Some(data.clone()), )))) .map_err(|_| ProofError::DecodeError)?; - let subproof = self.generate_subproof(data).map(|subproof| (subproof, cur_key.len()))?; + let subproof = self + .generate_subproof(data) + .map(|subproof| (subproof, cur_key.len()))?; Ok((Some(ext_ptr.as_ptr()), subproof.0, subproof.1)) } } BRANCH_NODE_SIZE => { if key.is_empty() { - return Err(ProofError::NoSuchNode) + return Err(ProofError::NoSuchNode); } // Record rlp values of all children. @@ -471,7 +513,7 @@ impl Proof { // Subproof with the given key must exist. let index = key[0] as usize; let data: Vec = if chd_eth_rlp[index].is_none() { - return Err(ProofError::DecodeError) + return Err(ProofError::DecodeError); } else { chd_eth_rlp[index].clone().unwrap() }; @@ -503,7 +545,11 @@ impl Proof { // // The return value indicates if the fork point is root node. If so, unset the // entire trie. -fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right: K) -> Result { +fn unset_internal>( + merkle_setup: &mut MerkleSetup, + left: K, + right: K, +) -> Result { // Add the sentinel root let mut left_chunks = vec![0]; left_chunks.extend(to_nibbles(left.as_ref())); @@ -526,8 +572,11 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right // stop here and the forkpoint is the fullnode. let left_node = n.chd()[left_chunks[index] as usize]; let right_node = n.chd()[right_chunks[index] as usize]; - if left_node.is_none() || right_node.is_none() || left_node.unwrap() != right_node.unwrap() { - break + if left_node.is_none() + || right_node.is_none() + || left_node.unwrap() != right_node.unwrap() + { + break; } parent = u_ref.as_ptr(); u_ref = merkle @@ -552,10 +601,12 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right } if !fork_left.is_eq() || !fork_right.is_eq() { - break + break; } parent = u_ref.as_ptr(); - u_ref = merkle.get_node(n.chd()).map_err(|_| ProofError::DecodeError)?; + u_ref = merkle + .get_node(n.chd()) + .map_err(|_| ProofError::DecodeError)?; index += cur_key.len(); } // The fork point cannot be a leaf since it doesn't have any children. @@ -579,8 +630,22 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right } let p = u_ref.as_ptr(); drop(u_ref); - unset_node_ref(merkle, p, left_node, left_chunks[index..].to_vec(), 1, false)?; - unset_node_ref(merkle, p, right_node, right_chunks[index..].to_vec(), 1, true)?; + unset_node_ref( + merkle, + p, + left_node, + left_chunks[index..].to_vec(), + 1, + false, + )?; + unset_node_ref( + merkle, + p, + right_node, + right_chunks[index..].to_vec(), + 1, + true, + )?; Ok(false) } NodeType::Extension(n) => { @@ -594,19 +659,21 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right let cur_key = n.path().clone().into_inner(); if fork_left.is_lt() && fork_right.is_lt() { drop(u_ref); - return Err(ProofError::EmptyRange) + return Err(ProofError::EmptyRange); } if fork_left.is_gt() && fork_right.is_gt() { drop(u_ref); - return Err(ProofError::EmptyRange) + return Err(ProofError::EmptyRange); } if fork_left.is_ne() && fork_right.is_ne() { // The fork point is root node, unset the entire trie if parent.is_null() { drop(u_ref); - return Ok(true) + return Ok(true); } - let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); @@ -615,7 +682,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right .unwrap(); drop(p_ref); drop(u_ref); - return Ok(false) + return Ok(false); } let p = u_ref.as_ptr(); drop(u_ref); @@ -629,7 +696,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right cur_key.len(), false, )?; - return Ok(false) + return Ok(false); } if fork_left.is_ne() { unset_node_ref( @@ -640,7 +707,7 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right cur_key.len(), true, )?; - return Ok(false) + return Ok(false); } Ok(false) } @@ -661,18 +728,25 @@ fn unset_internal>(merkle_setup: &mut MerkleSetup, left: K, right // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. fn unset_node_ref>( - merkle: &Merkle, parent: ObjPtr, node: Option>, key: K, index: usize, remove_left: bool, + merkle: &Merkle, + parent: ObjPtr, + node: Option>, + key: K, + index: usize, + remove_left: bool, ) -> Result<(), ProofError> { if node.is_none() { // If the node is nil, then it's a child of the fork point // fullnode(it's a non-existent branch). - return Ok(()) + return Ok(()); } // Add the sentinel root let mut chunks = vec![0]; chunks.extend(to_nibbles(key.as_ref())); - let mut u_ref = merkle.get_node(node.unwrap()).map_err(|_| ProofError::NoSuchNode)?; + let mut u_ref = merkle + .get_node(node.unwrap()) + .map_err(|_| ProofError::NoSuchNode)?; let p = u_ref.as_ptr(); match &u_ref.inner() { @@ -699,14 +773,17 @@ fn unset_node_ref>( } drop(u_ref); - return unset_node_ref(merkle, p, node, key, index + 1, remove_left) + return unset_node_ref(merkle, p, node, key, index + 1, remove_left); } NodeType::Extension(n) => { let cur_key = n.path().clone().into_inner(); let node = n.chd(); - if chunks[index..].len() < cur_key.len() || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() + if chunks[index..].len() < cur_key.len() + || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() { - let mut p_ref = merkle.get_node(parent).map_err(|_| ProofError::NoSuchNode)?; + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; // Find the fork point, it's an non-existent branch. if remove_left { if compare(&cur_key, &chunks[index..]).is_lt() { @@ -740,11 +817,18 @@ fn unset_node_ref>( } drop(u_ref); drop(p_ref); - return Ok(()) + return Ok(()); } drop(u_ref); - return unset_node_ref(merkle, p, Some(node), key, index + cur_key.len(), remove_left) + return unset_node_ref( + merkle, + p, + Some(node), + key, + index + cur_key.len(), + remove_left, + ); } // Noop for leaf node as it doesn't have any children and no precalculated RLP value stored. NodeType::Leaf(_) => (), diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index 81237c0f1f11..3365fbca06b5 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -146,10 +146,12 @@ impl Deref for StoreDelta { impl StoreDelta { pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { let mut deltas = Vec::new(); - let mut widx: Vec<_> = (0..writes.len()).filter(|i| writes[*i].data.len() > 0).collect(); + let mut widx: Vec<_> = (0..writes.len()) + .filter(|i| writes[*i].data.len() > 0) + .collect(); if widx.is_empty() { // the writes are all empty - return Self(deltas) + return Self(deltas); } // sort by the starting point @@ -245,18 +247,22 @@ impl MemStoreR for StoreRev { let mut r = delta.len(); // no dirty page, before or after all dirty pages if r == 0 { - return prev.get_slice(start, end - start) + return prev.get_slice(start, end - start); } // otherwise, some dirty pages are covered by the range while r - l > 1 { let mid = (l + r) >> 1; - (*if start < delta[mid].offset() { &mut r } else { &mut l }) = mid; + (*if start < delta[mid].offset() { + &mut r + } else { + &mut l + }) = mid; } if start >= delta[l].offset() + PAGE_SIZE { l += 1 } if l >= delta.len() || end < delta[l].offset() { - return prev.get_slice(start, end - start) + return prev.get_slice(start, end - start); } let mut data = Vec::new(); let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE); @@ -271,14 +277,14 @@ impl MemStoreR for StoreRev { l += 1; if l >= delta.len() || end < delta[l].offset() { data.extend(prev.get_slice(start, end - start)?); - break + break; } if delta[l].offset() > start { data.extend(prev.get_slice(start, delta[l].offset() - start)?); } if end < delta[l].offset() + PAGE_SIZE { data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); - break + break; } data.extend(delta[l].data()); start = delta[l].offset() + PAGE_SIZE; @@ -441,7 +447,10 @@ impl MemStore for StoreRevMut { } match deltas.get(&e_pid) { Some(p) => data.extend(&p[..e_off + 1]), - None => data.extend(self.prev.get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?), + None => data.extend( + self.prev + .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, + ), } data } @@ -585,7 +594,9 @@ impl CachedSpace { let files = Arc::new(FilePool::new(cfg)?); Ok(Self { inner: Rc::new(RefCell::new(CachedSpaceInner { - cached_pages: lru::LruCache::new(NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size")), + cached_pages: lru::LruCache::new( + NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size"), + ), pinned_pages: HashMap::new(), files, disk_buffer: DiskBufferRequester::default(), @@ -611,15 +622,19 @@ impl CachedSpace { impl CachedSpaceInner { fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result, StoreError> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { - return Ok(Box::new(*p)) + return Ok(Box::new(*p)); } let file_nbit = self.files.get_file_nbit(); let file_size = 1 << file_nbit; let poff = pid << PAGE_SIZE_NBIT; let file = self.files.get_file(poff >> file_nbit)?; let mut page: Page = [0; PAGE_SIZE as usize]; - nix::sys::uio::pread(file.get_fd(), &mut page, (poff & (file_size - 1)) as nix::libc::off_t) - .map_err(StoreError::System)?; + nix::sys::uio::pread( + file.get_fd(), + &mut page, + (poff & (file_size - 1)) as nix::libc::off_t, + ) + .map_err(StoreError::System)?; Ok(Box::new(page)) } @@ -652,7 +667,7 @@ impl CachedSpaceInner { if *cnt == 0 { e.remove().1 } else { - return + return; } } _ => unreachable!(), @@ -684,7 +699,11 @@ impl PageRef { fn new(pid: u64, store: &CachedSpace) -> Option { Some(Self { pid, - data: store.inner.borrow_mut().pin_page(store.space_id, pid).ok()?, + data: store + .inner + .borrow_mut() + .pin_page(store.space_id, pid) + .ok()?, store: store.clone(), }) } @@ -699,7 +718,7 @@ impl Drop for PageRef { impl MemStoreR for CachedSpace { fn get_slice(&self, offset: u64, length: u64) -> Option> { if length == 0 { - return Some(Default::default()) + return Some(Default::default()); } let end = offset + length - 1; let s_pid = offset >> PAGE_SIZE_NBIT; @@ -707,7 +726,7 @@ impl MemStoreR for CachedSpace { let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; if s_pid == e_pid { - return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec()) + return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec()); } let mut data: Vec = Vec::new(); { @@ -744,7 +763,7 @@ impl FilePool { }; let f0 = s.get_file(0)?; if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() { - return Err(StoreError::InitError("the store is busy".into())) + return Err(StoreError::InitError("the store is busy".into())); } Ok(s) } @@ -854,7 +873,11 @@ pub struct DiskBuffer { } impl DiskBuffer { - pub fn new(inbound: mpsc::Receiver, cfg: &DiskBufferConfig, wal: &WALConfig) -> Option { + pub fn new( + inbound: mpsc::Receiver, + cfg: &DiskBufferConfig, + wal: &WALConfig, + ) -> Option { const INIT: Option> = None; let aiomgr = AIOBuilder::default() .max_events(cfg.max_aio_requests) @@ -893,9 +916,12 @@ impl DiskBuffer { .unwrap() .get_file(fid) .unwrap(); - let fut = self - .aiomgr - .write(file.get_fd(), offset & fmask, Box::new(*p.staging_data), None); + let fut = self.aiomgr.write( + file.get_fd(), + offset & fmask, + Box::new(*p.staging_data), + None, + ); let s = unsafe { self.get_longlive_self() }; self.start_task(async move { let (res, _) = fut.await; @@ -942,7 +968,7 @@ impl DiskBuffer { .recover_policy(RecoverPolicy::Strict); if self.wal.is_some() { // already initialized - return Ok(()) + return Ok(()); } let wal = loader .load( @@ -983,21 +1009,22 @@ impl DiskBuffer { records.push(ac); bwrites.extend(bw); } else { - break + break; } while let Ok((bw, ac)) = writes.try_recv() { records.push(ac); bwrites.extend(bw); if records.len() >= self.cfg.wal_max_batch { - break + break; } } // first write to WAL - let ring_ids: Vec<_> = futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) - .await - .into_iter() - .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) - .collect(); + let ring_ids: Vec<_> = + futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) + .await + .into_iter() + .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) + .collect(); let sem = Rc::new(tokio::sync::Semaphore::new(0)); let mut npermit = 0; for BufferWrite { space_id, delta } in bwrites { @@ -1011,7 +1038,10 @@ impl DiskBuffer { npermit += 1; } Vacant(e) => { - let file_nbit = self.file_pools[page_key.0 as usize].as_ref().unwrap().file_nbit; + let file_nbit = self.file_pools[page_key.0 as usize] + .as_ref() + .unwrap() + .file_nbit; e.insert(PendingPage { staging_data: w.1.into(), file_nbit, @@ -1043,7 +1073,11 @@ impl DiskBuffer { } } - async fn process(&mut self, req: BufferCmd, wal_in: &mpsc::Sender<(Vec, AshRecord)>) -> bool { + async fn process( + &mut self, + req: BufferCmd, + wal_in: &mpsc::Sender<(Vec, AshRecord)>, + ) -> bool { match req { BufferCmd::Shutdown => return false, BufferCmd::InitWAL(rootfd, waldir) => { @@ -1074,7 +1108,9 @@ impl DiskBuffer { .collect(); tx.send(ash).unwrap(); } - BufferCmd::RegCachedSpace(space_id, files) => self.file_pools[space_id as usize] = Some(files), + BufferCmd::RegCachedSpace(space_id, files) => { + self.file_pools[space_id as usize] = Some(files) + } } true } @@ -1110,7 +1146,7 @@ impl DiskBuffer { } let req = self.inbound.recv().await.unwrap(); if !self.process(req, &wal_in).await { - break + break; } } drop(wal_in); diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index a77102996324..29b65a08f466 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -32,7 +32,10 @@ pub fn run(opts: &Options) -> Result<()> { }; let mut account: Option> = None; - let x = match db.new_writebatch().kv_remove(opts.key.clone(), &mut account) { + let x = match db + .new_writebatch() + .kv_remove(opts.key.clone(), &mut account) + { Ok(wb) => { wb.commit(); println!("{}", opts.key); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index dabae074410b..c0a910825a74 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -28,7 +28,7 @@ pub fn run(opts: &Options) -> Result<()> { let mut stdout = std::io::stdout(); if let Err(_) = db.kv_dump(&mut stdout) { - return Err(anyhow!("database dump not successful")) + return Err(anyhow!("database dump not successful")); } Ok(()) } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 8b29638a0628..b6a8a47153e7 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -40,9 +40,9 @@ pub fn run(opts: &Options) -> Result<()> { }; println!("{:?}", s); if val.is_empty() { - return Err(anyhow!("no value found for key")) + return Err(anyhow!("no value found for key")); } - return Ok(()) + return Ok(()); } Err(_) => return Err(anyhow!("key not found")), } diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 9d0331530432..ce0c35155c0c 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -48,7 +48,8 @@ fn main() -> Result<()> { let cli = Cli::parse(); env_logger::init_from_env( - env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.to_string()), + env_logger::Env::default() + .filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.to_string()), ); match &cli.command { diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 90f1c4e35a03..000000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,9 +0,0 @@ -edition = "2018" -unstable_features = true -max_width = 120 -binop_separator = "Back" -inline_attribute_width = 80 -fn_args_layout = "Compressed" -hard_tabs = false -tab_spaces = 4 -trailing_semicolon = false From 67496efc359c8ffcef25211d680070e30955786a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 2 Mar 2023 05:54:53 -0800 Subject: [PATCH 0059/1053] Pull in dependent repositories (#123) Maintenance on multiple repos is harder than keeping everything in one place. Now that we have workspaces, just grab the latest copy of each dependency and check it in here. There is some loss of history for the original repos, but it didn't look like there was much. Removed all rustfmt.toml files, and ran 'cargo fmt' on these before committing. --- Cargo.toml | 3 + firewood/Cargo.toml | 6 +- growth-ring/Cargo.toml | 32 + growth-ring/README.rst | 11 + growth-ring/examples/.gitignore | 2 + growth-ring/examples/demo1.rs | 99 ++ growth-ring/src/lib.rs | 228 ++++ growth-ring/src/wal.rs | 1230 ++++++++++++++++++++++ growth-ring/tests/common/mod.rs | 666 ++++++++++++ growth-ring/tests/rand_fail.rs | 99 ++ libaio-futures/Cargo.toml | 25 + libaio-futures/build.rs | 15 + libaio-futures/libaio/.gitignore | 10 + libaio-futures/libaio/COPYING | 515 +++++++++ libaio-futures/libaio/Makefile | 73 ++ libaio-futures/libaio/aio_ring.h | 49 + libaio-futures/libaio/compat-0_1.c | 62 ++ libaio-futures/libaio/io_cancel.c | 23 + libaio-futures/libaio/io_destroy.c | 23 + libaio-futures/libaio/io_getevents.c | 35 + libaio-futures/libaio/io_pgetevents.c | 56 + libaio-futures/libaio/io_queue_init.c | 33 + libaio-futures/libaio/io_queue_release.c | 27 + libaio-futures/libaio/io_queue_run.c | 39 + libaio-futures/libaio/io_queue_wait.c | 31 + libaio-futures/libaio/io_setup.c | 23 + libaio-futures/libaio/io_submit.c | 23 + libaio-futures/libaio/libaio.h | 300 ++++++ libaio-futures/libaio/libaio.map | 27 + libaio-futures/libaio/raw_syscall.c | 19 + libaio-futures/libaio/syscall-alpha.h | 5 + libaio-futures/libaio/syscall-arm.h | 26 + libaio-futures/libaio/syscall-generic.h | 11 + libaio-futures/libaio/syscall-i386.h | 6 + libaio-futures/libaio/syscall-ia64.h | 5 + libaio-futures/libaio/syscall-ppc.h | 5 + libaio-futures/libaio/syscall-s390.h | 5 + libaio-futures/libaio/syscall-sparc.h | 5 + libaio-futures/libaio/syscall-x86_64.h | 6 + libaio-futures/libaio/syscall.h | 73 ++ libaio-futures/libaio/vsys_def.h | 24 + libaio-futures/src/abi.rs | 111 ++ libaio-futures/src/lib.rs | 596 +++++++++++ libaio-futures/tests/simple_test.rs | 63 ++ shale/Cargo.toml | 12 + shale/LICENSE | 22 + shale/src/block.rs | 1 + shale/src/compact.rs | 594 +++++++++++ shale/src/lib.rs | 603 +++++++++++ shale/src/util.rs | 6 + 50 files changed, 5960 insertions(+), 3 deletions(-) create mode 100644 growth-ring/Cargo.toml create mode 100644 growth-ring/README.rst create mode 100644 growth-ring/examples/.gitignore create mode 100644 growth-ring/examples/demo1.rs create mode 100644 growth-ring/src/lib.rs create mode 100644 growth-ring/src/wal.rs create mode 100644 growth-ring/tests/common/mod.rs create mode 100644 growth-ring/tests/rand_fail.rs create mode 100644 libaio-futures/Cargo.toml create mode 100644 libaio-futures/build.rs create mode 100644 libaio-futures/libaio/.gitignore create mode 100644 libaio-futures/libaio/COPYING create mode 100644 libaio-futures/libaio/Makefile create mode 100644 libaio-futures/libaio/aio_ring.h create mode 100644 libaio-futures/libaio/compat-0_1.c create mode 100644 libaio-futures/libaio/io_cancel.c create mode 100644 libaio-futures/libaio/io_destroy.c create mode 100644 libaio-futures/libaio/io_getevents.c create mode 100644 libaio-futures/libaio/io_pgetevents.c create mode 100644 libaio-futures/libaio/io_queue_init.c create mode 100644 libaio-futures/libaio/io_queue_release.c create mode 100644 libaio-futures/libaio/io_queue_run.c create mode 100644 libaio-futures/libaio/io_queue_wait.c create mode 100644 libaio-futures/libaio/io_setup.c create mode 100644 libaio-futures/libaio/io_submit.c create mode 100644 libaio-futures/libaio/libaio.h create mode 100644 libaio-futures/libaio/libaio.map create mode 100644 libaio-futures/libaio/raw_syscall.c create mode 100644 libaio-futures/libaio/syscall-alpha.h create mode 100644 libaio-futures/libaio/syscall-arm.h create mode 100644 libaio-futures/libaio/syscall-generic.h create mode 100644 libaio-futures/libaio/syscall-i386.h create mode 100644 libaio-futures/libaio/syscall-ia64.h create mode 100644 libaio-futures/libaio/syscall-ppc.h create mode 100644 libaio-futures/libaio/syscall-s390.h create mode 100644 libaio-futures/libaio/syscall-sparc.h create mode 100644 libaio-futures/libaio/syscall-x86_64.h create mode 100644 libaio-futures/libaio/syscall.h create mode 100644 libaio-futures/libaio/vsys_def.h create mode 100644 libaio-futures/src/abi.rs create mode 100644 libaio-futures/src/lib.rs create mode 100644 libaio-futures/tests/simple_test.rs create mode 100644 shale/Cargo.toml create mode 100644 shale/LICENSE create mode 100644 shale/src/block.rs create mode 100644 shale/src/compact.rs create mode 100644 shale/src/lib.rs create mode 100644 shale/src/util.rs diff --git a/Cargo.toml b/Cargo.toml index 350fcc1e9ebd..5e168130918e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,8 @@ [workspace] members = [ + "growth-ring", + "libaio-futures", + "shale", "firewood", "fwdctl", ] diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d3070eb0285d..8f6ad3edbc12 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.1" edition = "2021" [dependencies] +growth-ring = { version = "0.3.0", path = "../growth-ring" } +libaio-futures = {version = "0.2.2", path = "../libaio-futures" } +shale = { version = "0.1.7", path = "../shale" } enum-as-inner = "0.5.1" parking_lot = "0.12.1" rlp = "0.5.2" @@ -11,12 +14,9 @@ sha3 = "0.10.2" once_cell = "1.13.1" hex = "0.4.3" lru = "0.9.0" -libaio-futures = "0.2.2" nix = "0.26.1" typed-builder = "0.12.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } -shale = "0.1.7" -growth-ring = "0.3.0" futures = "0.3.24" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } serde = { version = "1.0", features = ["derive"] } diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml new file mode 100644 index 000000000000..7a699ecd66be --- /dev/null +++ b/growth-ring/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "growth-ring" +version = "0.3.0" +authors = ["Determinant "] +edition = "2018" +homepage = "https://github.com/Determinant/growth-ring" +keywords = ["wal", "db", "futures"] +license = "MIT" +description = "Simple and modular write-ahead-logging implementation." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libaio-futures = { version = "0.2.3", path = "../libaio-futures" } +crc = "3.0.0" +lru = "0.8.0" +scan_fmt = "0.2.6" +regex = "1.6.0" +async-trait = "0.1.57" +futures = "0.3.24" +nix = "0.25.0" +libc = "0.2.133" + +[dev-dependencies] +hex = "0.4.3" +rand = "0.8.5" +indexmap = "1.9.1" + +[lib] +name = "growthring" +path = "src/lib.rs" +crate-type = ["dylib", "rlib", "staticlib"] diff --git a/growth-ring/README.rst b/growth-ring/README.rst new file mode 100644 index 000000000000..ff61f443f439 --- /dev/null +++ b/growth-ring/README.rst @@ -0,0 +1,11 @@ +growth-ring +=========== + +.. image:: https://travis-ci.com/ava-labs/growth-ring.svg?token=EbLxqxy3qxjHrZKkyoP4&branch=master + :target: https://travis-ci.com/Determinant/growth-ring + +Documentation +------------- +- Latest_ + +.. _Latest: https://docs.rs/growth-ring diff --git a/growth-ring/examples/.gitignore b/growth-ring/examples/.gitignore new file mode 100644 index 000000000000..7d977f4aac48 --- /dev/null +++ b/growth-ring/examples/.gitignore @@ -0,0 +1,2 @@ +demo +testdb diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs new file mode 100644 index 000000000000..fe2a96c28c86 --- /dev/null +++ b/growth-ring/examples/demo1.rs @@ -0,0 +1,99 @@ +use futures::executor::block_on; +use growthring::{ + wal::{WALBytes, WALLoader, WALRingId, WALWriter}, + WALStoreAIO, +}; +use rand::{seq::SliceRandom, Rng, SeedableRng}; + +fn test(records: Vec, wal: &mut WALWriter) -> Vec { + let mut res = Vec::new(); + for r in wal.grow(records).into_iter() { + let ring_id = futures::executor::block_on(r).unwrap().1; + println!("got ring id: {:?}", ring_id); + res.push(ring_id); + } + res +} + +fn recover(payload: WALBytes, ringid: WALRingId) -> Result<(), ()> { + println!( + "recover(payload={}, ringid={:?}", + std::str::from_utf8(&payload).unwrap(), + ringid + ); + Ok(()) +} + +fn main() { + let wal_dir = "./wal_demo1"; + let mut rng = rand::rngs::StdRng::seed_from_u64(0); + let mut loader = WALLoader::new(); + loader.file_nbit(9).block_nbit(8); + + let store = WALStoreAIO::new(&wal_dir, true, None, None).unwrap(); + let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); + for _ in 0..3 { + test( + ["hi", "hello", "lol"] + .iter() + .map(|s| s.to_string()) + .collect::>(), + &mut wal, + ); + } + for _ in 0..3 { + test( + vec!["a".repeat(10), "b".repeat(100), "c".repeat(1000)], + &mut wal, + ); + } + + let store = WALStoreAIO::new(&wal_dir, false, None, None).unwrap(); + let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); + for _ in 0..3 { + test( + vec![ + "a".repeat(10), + "b".repeat(100), + "c".repeat(300), + "d".repeat(400), + ], + &mut wal, + ); + } + + let store = WALStoreAIO::new(&wal_dir, false, None, None).unwrap(); + let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); + let mut history = std::collections::VecDeque::new(); + for _ in 0..3 { + let mut ids = Vec::new(); + for _ in 0..3 { + let mut records = Vec::new(); + for _ in 0..100 { + let rec = "a".repeat(rng.gen_range(1..1000)); + history.push_back(rec.clone()); + if history.len() > 100 { + history.pop_front(); + } + records.push(rec) + } + for id in test(records, &mut wal).iter() { + ids.push(*id) + } + } + ids.shuffle(&mut rng); + for e in ids.chunks(20) { + println!("peel(20)"); + futures::executor::block_on(wal.peel(e, 100)).unwrap(); + } + } + for (rec, ans) in + block_on(wal.read_recent_records(100, &growthring::wal::RecoverPolicy::Strict)) + .unwrap() + .into_iter() + .zip(history.into_iter().rev()) + { + assert_eq!(std::str::from_utf8(&rec).unwrap(), &ans); + println!("{}", std::str::from_utf8(&rec).unwrap()); + } +} diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs new file mode 100644 index 000000000000..1f42ee58108f --- /dev/null +++ b/growth-ring/src/lib.rs @@ -0,0 +1,228 @@ +//! Simple and modular write-ahead-logging implementation. +//! +//! # Examples +//! +//! ``` +//! use growthring::{WALStoreAIO, wal::WALLoader}; +//! use futures::executor::block_on; +//! let mut loader = WALLoader::new(); +//! loader.file_nbit(9).block_nbit(8); +//! +//! +//! // Start with empty WAL (truncate = true). +//! let store = WALStoreAIO::new("./walfiles", true, None, None).unwrap(); +//! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); +//! // Write a vector of records to WAL. +//! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { +//! let ring_id = block_on(f).unwrap().1; +//! println!("WAL recorded record to {:?}", ring_id); +//! } +//! +//! +//! // Load from WAL (truncate = false). +//! let store = WALStoreAIO::new("./walfiles", false, None, None).unwrap(); +//! let mut wal = block_on(loader.load(store, |payload, ringid| { +//! // redo the operations in your application +//! println!("recover(payload={}, ringid={:?})", +//! std::str::from_utf8(&payload).unwrap(), +//! ringid); +//! Ok(()) +//! }, 0)).unwrap(); +//! // We saw some log playback, even there is no failure. +//! // Let's try to grow the WAL to create many files. +//! let ring_ids = wal.grow((1..100).into_iter().map(|i| "a".repeat(i)).collect::>()) +//! .into_iter().map(|f| block_on(f).unwrap().1).collect::>(); +//! // Then assume all these records are not longer needed. We can tell WALWriter by the `peel` +//! // method. +//! block_on(wal.peel(ring_ids, 0)).unwrap(); +//! // There will only be one remaining file in ./walfiles. +//! +//! let store = WALStoreAIO::new("./walfiles", false, None, None).unwrap(); +//! let wal = block_on(loader.load(store, |payload, _| { +//! println!("payload.len() = {}", payload.len()); +//! Ok(()) +//! }, 0)).unwrap(); +//! // After each recovery, the ./walfiles is empty. +//! ``` + +#[macro_use] +extern crate scan_fmt; +pub mod wal; + +use aiofut::{AIOBuilder, AIOManager}; +use async_trait::async_trait; +use libc::off_t; +use nix::fcntl::{fallocate, open, openat, FallocateFlags, OFlag}; +use nix::sys::stat::Mode; +use nix::unistd::{close, ftruncate, mkdir, unlinkat, UnlinkatFlags}; +use std::os::unix::io::RawFd; +use std::sync::Arc; +use wal::{WALBytes, WALFile, WALPos, WALStore}; + +pub struct WALFileAIO { + fd: RawFd, + aiomgr: Arc, +} + +impl WALFileAIO { + pub fn new(rootfd: RawFd, filename: &str, aiomgr: Arc) -> Result { + openat( + rootfd, + filename, + OFlag::O_CREAT | OFlag::O_RDWR, + Mode::S_IRUSR | Mode::S_IWUSR, + ) + .and_then(|fd| Ok(WALFileAIO { fd, aiomgr })) + .or_else(|_| Err(())) + } +} + +impl Drop for WALFileAIO { + fn drop(&mut self) { + close(self.fd).unwrap(); + } +} + +#[async_trait(?Send)] +impl WALFile for WALFileAIO { + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()> { + // TODO: is there any async version of fallocate? + fallocate( + self.fd, + FallocateFlags::FALLOC_FL_ZERO_RANGE, + offset as off_t, + length as off_t, + ) + .and_then(|_| Ok(())) + .or_else(|_| Err(())) + } + + fn truncate(&self, length: usize) -> Result<(), ()> { + ftruncate(self.fd, length as off_t).or_else(|_| Err(())) + } + + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()> { + let (res, data) = self.aiomgr.write(self.fd, offset, data, None).await; + res.or_else(|_| Err(())).and_then(|nwrote| { + if nwrote == data.len() { + Ok(()) + } else { + Err(()) + } + }) + } + + async fn read(&self, offset: WALPos, length: usize) -> Result, ()> { + let (res, data) = self.aiomgr.read(self.fd, offset, length, None).await; + res.or_else(|_| Err(())) + .and_then(|nread| Ok(if nread == length { Some(data) } else { None })) + } +} + +pub struct WALStoreAIO { + rootfd: RawFd, + aiomgr: Arc, +} + +unsafe impl Send for WALStoreAIO {} + +impl WALStoreAIO { + pub fn new( + wal_dir: &str, + truncate: bool, + rootfd: Option, + aiomgr: Option, + ) -> Result { + let aiomgr = Arc::new( + aiomgr + .ok_or(Err(())) + .or_else(|_: Result| AIOBuilder::default().build().or(Err(())))?, + ); + + if truncate { + let _ = std::fs::remove_dir_all(wal_dir); + } + let walfd; + match rootfd { + None => { + match mkdir(wal_dir, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { + Err(e) => { + if truncate { + panic!("error while creating directory: {}", e) + } + } + Ok(_) => (), + } + walfd = match open(wal_dir, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) { + Ok(fd) => fd, + Err(_) => panic!("error while opening the WAL directory"), + } + } + Some(fd) => { + let dirstr = std::ffi::CString::new(wal_dir).unwrap(); + let ret = unsafe { + libc::mkdirat( + fd, + dirstr.as_ptr(), + libc::S_IRUSR | libc::S_IWUSR | libc::S_IXUSR, + ) + }; + if ret != 0 { + if truncate { + panic!("error while creating directory") + } + } + walfd = match nix::fcntl::openat( + fd, + wal_dir, + OFlag::O_DIRECTORY | OFlag::O_PATH, + Mode::empty(), + ) { + Ok(fd) => fd, + Err(_) => panic!("error while opening the WAL directory"), + } + } + } + Ok(WALStoreAIO { + rootfd: walfd, + aiomgr, + }) + } +} + +#[async_trait(?Send)] +impl WALStore for WALStoreAIO { + type FileNameIter = std::vec::IntoIter; + + async fn open_file(&self, filename: &str, _touch: bool) -> Result, ()> { + let filename = filename.to_string(); + WALFileAIO::new(self.rootfd, &filename, self.aiomgr.clone()) + .and_then(|f| Ok(Box::new(f) as Box)) + } + + async fn remove_file(&self, filename: String) -> Result<(), ()> { + unlinkat( + Some(self.rootfd), + filename.as_str(), + UnlinkatFlags::NoRemoveDir, + ) + .or_else(|_| Err(())) + } + + fn enumerate_files(&self) -> Result { + let mut logfiles = Vec::new(); + for ent in nix::dir::Dir::openat(self.rootfd, "./", OFlag::empty(), Mode::empty()) + .unwrap() + .iter() + { + logfiles.push(ent.unwrap().file_name().to_str().unwrap().to_string()) + } + Ok(logfiles.into_iter()) + } +} + +impl Drop for WALStoreAIO { + fn drop(&mut self) { + nix::unistd::close(self.rootfd).ok(); + } +} diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs new file mode 100644 index 000000000000..6f2f816dd943 --- /dev/null +++ b/growth-ring/src/wal.rs @@ -0,0 +1,1230 @@ +use async_trait::async_trait; +use futures::{ + future::{self, FutureExt, TryFutureExt}, + stream::StreamExt, + Future, +}; + +use std::cell::{RefCell, UnsafeCell}; +use std::collections::{hash_map, BinaryHeap, HashMap, VecDeque}; +use std::convert::{TryFrom, TryInto}; +use std::mem::MaybeUninit; +use std::num::NonZeroUsize; +use std::pin::Pin; + +const FILENAME_FMT: &str = r"[0-9a-f]+\.log"; + +enum WALRingType { + #[allow(dead_code)] + Null = 0x0, + Full, + First, + Middle, + Last, +} + +#[repr(packed)] +struct WALRingBlob { + counter: u32, + crc32: u32, + rsize: u32, + rtype: u8, + // payload follows +} + +impl TryFrom for WALRingType { + type Error = (); + fn try_from(v: u8) -> Result { + match v { + x if x == WALRingType::Null as u8 => Ok(WALRingType::Null), + x if x == WALRingType::Full as u8 => Ok(WALRingType::Full), + x if x == WALRingType::First as u8 => Ok(WALRingType::First), + x if x == WALRingType::Middle as u8 => Ok(WALRingType::Middle), + x if x == WALRingType::Last as u8 => Ok(WALRingType::Last), + _ => Err(()), + } + } +} + +type WALFileId = u64; +pub type WALBytes = Box<[u8]>; +pub type WALPos = u64; + +fn get_fid(fname: &str) -> WALFileId { + scan_fmt!(fname, "{x}.log", [hex WALFileId]).unwrap() +} + +fn get_fname(fid: WALFileId) -> String { + format!("{:08x}.log", fid) +} + +fn sort_fids(file_nbit: u64, mut fids: Vec) -> Vec<(u8, u64)> { + let (min, max) = fids.iter().fold((u64::MAX, u64::MIN), |acc, fid| { + ((*fid).min(acc.0), (*fid).max(acc.1)) + }); + let fid_half = u64::MAX >> (file_nbit + 1); + if max > min && max - min > fid_half { + // we treat this as u64 overflow has happened, take proper care here + let mut aux: Vec<_> = fids + .into_iter() + .map(|fid| (if fid < fid_half { 1 } else { 0 }, fid)) + .collect(); + aux.sort(); + aux + } else { + fids.sort(); + fids.into_iter().map(|fid| (0, fid)).collect() + } +} + +fn counter_lt(a: u32, b: u32) -> bool { + if u32::abs_diff(a, b) > u32::MAX / 2 { + b < a + } else { + a < b + } +} + +#[repr(C)] +struct Header { + /// all preceding files ((); + +#[repr(C)] +#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] +pub struct WALRingId { + start: WALPos, + end: WALPos, + counter: u32, +} + +impl WALRingId { + pub fn empty_id() -> Self { + WALRingId { + start: 0, + end: 0, + counter: 0, + } + } + pub fn get_start(&self) -> WALPos { + self.start + } + pub fn get_end(&self) -> WALPos { + self.end + } +} + +impl Ord for WALRingId { + fn cmp(&self, other: &WALRingId) -> std::cmp::Ordering { + other + .start + .cmp(&self.start) + .then_with(|| other.end.cmp(&self.end)) + } +} + +impl PartialOrd for WALRingId { + fn partial_cmp(&self, other: &WALRingId) -> Option { + Some(self.cmp(other)) + } +} + +pub trait Record { + fn serialize(&self) -> WALBytes; +} + +impl Record for WALBytes { + fn serialize(&self) -> WALBytes { + self[..].into() + } +} + +impl Record for String { + fn serialize(&self) -> WALBytes { + self.as_bytes().into() + } +} + +impl Record for &str { + fn serialize(&self) -> WALBytes { + self.as_bytes().into() + } +} + +/// the state for a WAL writer +struct WALState { + /// the next position for a record, addressed in the entire WAL space + next: WALPos, + /// number of bits for a file + file_nbit: u64, + next_complete: WALRingId, + counter: u32, + io_complete: BinaryHeap, + pending_removal: VecDeque<(WALFileId, u32)>, +} + +#[async_trait(?Send)] +pub trait WALFile { + /// Initialize the file space in [offset, offset + length) to zero. + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()>; + /// Write data with offset. We assume all previous `allocate`/`truncate` invocations are visible + /// if ordered earlier (should be guaranteed by most OS). Additionally, the write caused + /// by each invocation of this function should be _atomic_ (the entire single write should be + /// all or nothing). + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()>; + /// Read data with offset. Return `Ok(None)` when it reaches EOF. + async fn read(&self, offset: WALPos, length: usize) -> Result, ()>; + /// Truncate a file to a specified length. + fn truncate(&self, length: usize) -> Result<(), ()>; +} + +#[async_trait(?Send)] +pub trait WALStore { + type FileNameIter: Iterator; + + /// Open a file given the filename, create the file if not exists when `touch` is `true`. + async fn open_file(&self, filename: &str, touch: bool) -> Result, ()>; + /// Unlink a file given the filename. + async fn remove_file(&self, filename: String) -> Result<(), ()>; + /// Enumerate all WAL filenames. It should include all WAL files that are previously opened + /// (created) but not removed. The list could be unordered. + fn enumerate_files(&self) -> Result; +} + +struct WALFileHandle<'a, F: WALStore> { + fid: WALFileId, + handle: &'a dyn WALFile, + pool: *const WALFilePool, +} + +impl<'a, F: WALStore> std::ops::Deref for WALFileHandle<'a, F> { + type Target = dyn WALFile + 'a; + fn deref(&self) -> &Self::Target { + self.handle + } +} + +impl<'a, F: WALStore> Drop for WALFileHandle<'a, F> { + fn drop(&mut self) { + unsafe { + (&*self.pool).release_file(self.fid); + } + } +} + +/// The middle layer that manages WAL file handles and invokes public trait functions to actually +/// manipulate files and their contents. +struct WALFilePool { + store: F, + header_file: Box, + handle_cache: RefCell>>, + handle_used: RefCell, usize)>>>, + last_write: UnsafeCell>>>>>, + last_peel: UnsafeCell>>>>>, + file_nbit: u64, + file_size: u64, + block_nbit: u64, +} + +impl WALFilePool { + async fn new( + store: F, + file_nbit: u64, + block_nbit: u64, + cache_size: NonZeroUsize, + ) -> Result { + let file_nbit = file_nbit as u64; + let block_nbit = block_nbit as u64; + let header_file = store.open_file("HEAD", true).await?; + header_file.truncate(HEADER_SIZE)?; + Ok(WALFilePool { + store, + header_file, + handle_cache: RefCell::new(lru::LruCache::new(cache_size)), + handle_used: RefCell::new(HashMap::new()), + last_write: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), + last_peel: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), + file_nbit, + file_size: 1 << (file_nbit as u64), + block_nbit, + }) + } + + async fn read_header(&self) -> Result { + let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); + let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); + let header = unsafe { std::mem::transmute::<_, Header>(bytes) }; + Ok(header) + } + + async fn write_header(&self, header: &Header) -> Result<(), ()> { + let base = header as *const Header as usize as *const u8; + let bytes = unsafe { std::slice::from_raw_parts(base, HEADER_SIZE) }; + self.header_file.write(0, bytes.into()).await?; + Ok(()) + } + + fn get_file<'a>( + &'a self, + fid: u64, + touch: bool, + ) -> impl Future, ()>> { + async move { + let pool = self as *const WALFilePool; + if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { + let handle = match self.handle_used.borrow_mut().entry(fid) { + hash_map::Entry::Vacant(e) => unsafe { + &*(*e.insert(UnsafeCell::new((h, 1))).get()).0 + }, + _ => unreachable!(), + }; + Ok(WALFileHandle { fid, handle, pool }) + } else { + let v = unsafe { + &mut *match self.handle_used.borrow_mut().entry(fid) { + hash_map::Entry::Occupied(e) => e.into_mut(), + hash_map::Entry::Vacant(e) => e.insert(UnsafeCell::new(( + self.store.open_file(&get_fname(fid), touch).await?, + 0, + ))), + } + .get() + }; + v.1 += 1; + Ok(WALFileHandle { + fid, + handle: &*v.0, + pool, + }) + } + } + } + + fn release_file(&self, fid: WALFileId) { + match self.handle_used.borrow_mut().entry(fid) { + hash_map::Entry::Occupied(e) => { + let v = unsafe { &mut *e.get().get() }; + v.1 -= 1; + if v.1 == 0 { + self.handle_cache + .borrow_mut() + .put(fid, e.remove().into_inner().0); + } + } + _ => unreachable!(), + } + } + + fn write<'a>( + &'a mut self, + writes: Vec<(WALPos, WALBytes)>, + ) -> Vec> + 'a>>> { + if writes.is_empty() { + return Vec::new(); + } + let file_size = self.file_size; + let file_nbit = self.file_nbit; + let meta: Vec<(u64, u64)> = writes + .iter() + .map(|(off, w)| ((*off) >> file_nbit, w.len() as u64)) + .collect(); + let mut files: Vec + 'a>>> = Vec::new(); + for &(fid, _) in meta.iter() { + files.push(Box::pin(self.get_file(fid, true)) as Pin + 'a>>) + } + let mut fid = writes[0].0 >> file_nbit; + let mut alloc_start = writes[0].0 & (self.file_size - 1); + let mut alloc_end = alloc_start + writes[0].1.len() as u64; + let last_write = unsafe { + std::mem::replace(&mut *self.last_write.get(), std::mem::MaybeUninit::uninit()) + .assume_init() + }; + // pre-allocate the file space + let alloc = async move { + last_write.await?; + let mut last_h: Option< + Pin, ()>> + 'a>>, + > = None; + for ((next_fid, wl), h) in meta.into_iter().zip(files.into_iter()) { + if let Some(lh) = last_h.take() { + if next_fid != fid { + lh.await? + .allocate(alloc_start, (alloc_end - alloc_start) as usize) + .await?; + last_h = Some(h); + alloc_start = 0; + alloc_end = alloc_start + wl; + fid = next_fid; + } else { + last_h = Some(lh); + alloc_end += wl; + } + } else { + last_h = Some(h); + } + } + if let Some(lh) = last_h { + lh.await? + .allocate(alloc_start, (alloc_end - alloc_start) as usize) + .await? + } + Ok(()) + }; + let mut res = Vec::new(); + let mut prev = Box::pin(alloc) as Pin + 'a>>; + for (off, w) in writes.into_iter() { + let f = self.get_file(off >> file_nbit, true); + let w = (async move { + prev.await?; + f.await?.write(off & (file_size - 1), w).await + }) + .shared(); + prev = Box::pin(w.clone()); + res.push(Box::pin(w) as Pin + 'a>>) + } + unsafe { + (*self.last_write.get()) = MaybeUninit::new(std::mem::transmute::< + Pin + 'a>>, + Pin + 'static>>, + >(prev)) + } + res + } + + fn remove_files<'a>( + &'a mut self, + state: &mut WALState, + keep_nrecords: u32, + ) -> impl Future> + 'a { + let last_peel = unsafe { + std::mem::replace(&mut *self.last_peel.get(), std::mem::MaybeUninit::uninit()) + .assume_init() + }; + + let mut removes: Vec>>>> = Vec::new(); + while state.pending_removal.len() > 1 { + let (fid, counter) = state.pending_removal.front().unwrap(); + if counter_lt(counter + keep_nrecords, state.counter) { + removes.push(self.store.remove_file(get_fname(*fid)) + as Pin + 'a>>); + state.pending_removal.pop_front(); + } else { + break; + } + } + let p = async move { + last_peel.await.ok(); + for r in removes.into_iter() { + r.await.ok(); + } + Ok(()) + } + .shared(); + unsafe { + (*self.last_peel.get()) = MaybeUninit::new(std::mem::transmute( + Box::pin(p.clone()) as Pin + 'a>> + )) + } + p + } + + fn in_use_len(&self) -> usize { + self.handle_used.borrow().len() + } + + fn reset(&mut self) { + self.handle_cache.borrow_mut().clear(); + self.handle_used.borrow_mut().clear() + } +} + +pub struct WALWriter { + state: WALState, + file_pool: WALFilePool, + block_buffer: WALBytes, + block_size: u32, + msize: usize, +} + +unsafe impl Send for WALWriter where F: WALStore + Send {} + +impl WALWriter { + fn new(state: WALState, file_pool: WALFilePool) -> Self { + let mut b = Vec::new(); + let block_size = 1 << file_pool.block_nbit as u32; + let msize = std::mem::size_of::(); + b.resize(block_size as usize, 0); + WALWriter { + state, + file_pool, + block_buffer: b.into_boxed_slice(), + block_size, + msize, + } + } + + /// Submit a sequence of records to WAL. It returns a vector of futures, each of which + /// corresponds to one record. When a future resolves to `WALRingId`, it is guaranteed the + /// record is already logged. Then, after finalizing the changes encoded by that record to + /// the persistent storage, the caller can recycle the WAL files by invoking the given + /// `peel` with the given `WALRingId`s. Note: each serialized record should contain at least 1 + /// byte (empty record payload will result in assertion failure). + pub fn grow<'a, R: Record + 'a>( + &'a mut self, + records: Vec, + ) -> Vec> + 'a> { + let mut res = Vec::new(); + let mut writes = Vec::new(); + let msize = self.msize as u32; + // the global offest of the begining of the block + // the start of the unwritten data + let mut bbuff_start = self.state.next as u32 & (self.block_size - 1); + // the end of the unwritten data + let mut bbuff_cur = bbuff_start; + + for rec in records.iter() { + let bytes = rec.serialize(); + let mut rec = &bytes[..]; + let mut rsize = rec.len() as u32; + let mut ring_start = None; + assert!(rsize > 0); + while rsize > 0 { + let remain = self.block_size - bbuff_cur; + if remain > msize { + let d = remain - msize; + let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; + let blob = unsafe { + std::mem::transmute::<*mut u8, &mut WALRingBlob>( + (&mut self.block_buffer[bbuff_cur as usize..]).as_mut_ptr(), + ) + }; + bbuff_cur += msize; + if d >= rsize { + // the remaining rec fits in the block + let payload = rec; + blob.counter = self.state.counter; + blob.crc32 = CRC32.checksum(payload); + blob.rsize = rsize; + let (rs, rt) = if let Some(rs) = ring_start.take() { + self.state.counter += 1; + (rs, WALRingType::Last) + } else { + self.state.counter += 1; + (rs0, WALRingType::Full) + }; + blob.rtype = rt as u8; + self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] + .copy_from_slice(payload); + bbuff_cur += rsize; + rsize = 0; + let end = self.state.next + (bbuff_cur - bbuff_start) as u64; + res.push(( + WALRingId { + start: rs, + end, + counter: blob.counter, + }, + Vec::new(), + )); + } else { + // the remaining block can only accommodate partial rec + let payload = &rec[..d as usize]; + blob.counter = self.state.counter; + blob.crc32 = CRC32.checksum(payload); + blob.rsize = d; + blob.rtype = if ring_start.is_some() { + WALRingType::Middle + } else { + ring_start = Some(rs0); + WALRingType::First + } as u8; + self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] + .copy_from_slice(payload); + bbuff_cur += d; + rsize -= d; + rec = &rec[d as usize..]; + } + } else { + // add padding space by moving the point to the end of the block + bbuff_cur = self.block_size; + } + if bbuff_cur == self.block_size { + writes.push(( + self.state.next, + self.block_buffer[bbuff_start as usize..] + .to_vec() + .into_boxed_slice(), + )); + self.state.next += (self.block_size - bbuff_start) as u64; + bbuff_start = 0; + bbuff_cur = 0; + } + } + } + if bbuff_cur > bbuff_start { + writes.push(( + self.state.next, + self.block_buffer[bbuff_start as usize..bbuff_cur as usize] + .to_vec() + .into_boxed_slice(), + )); + self.state.next += (bbuff_cur - bbuff_start) as u64; + } + + // mark the block info for each record + let mut i = 0; + 'outer: for (j, (off, w)) in writes.iter().enumerate() { + let blk_s = *off; + let blk_e = blk_s + w.len() as u64; + while res[i].0.end <= blk_s { + i += 1; + if i >= res.len() { + break 'outer; + } + } + while res[i].0.start < blk_e { + res[i].1.push(j); + if res[i].0.end >= blk_e { + break; + } + i += 1; + if i >= res.len() { + break 'outer; + } + } + } + + let writes: Vec> = self + .file_pool + .write(writes) + .into_iter() + .map(move |f| async move { f.await }.shared()) + .collect(); + let res = res + .into_iter() + .zip(records.into_iter()) + .map(|((ringid, blks), rec)| { + future::try_join_all(blks.into_iter().map(|idx| writes[idx].clone())) + .or_else(|_| future::ready(Err(()))) + .and_then(move |_| future::ready(Ok((rec, ringid)))) + }) + .collect(); + res + } + + /// Inform the `WALWriter` that some data writes are complete so that it could automatically + /// remove obsolete WAL files. The given list of `WALRingId` does not need to be ordered and + /// could be of arbitrary length. Use `0` for `keep_nrecords` if all obsolete WAL files + /// need to removed (the obsolete files do not affect the speed of recovery or correctness). + pub async fn peel<'a, T: AsRef<[WALRingId]>>( + &'a mut self, + records: T, + keep_nrecords: u32, + ) -> Result<(), ()> { + let msize = self.msize as u64; + let block_size = self.block_size as u64; + let state = &mut self.state; + for rec in records.as_ref() { + state.io_complete.push(*rec); + } + while let Some(s) = state.io_complete.peek().and_then(|&e| Some(e.start)) { + if s != state.next_complete.end { + break; + } + let mut m = state.io_complete.pop().unwrap(); + let block_remain = block_size - (m.end & (block_size - 1)); + if block_remain <= msize as u64 { + m.end += block_remain + } + let fid = m.start >> state.file_nbit; + match state.pending_removal.back_mut() { + Some(l) => { + if l.0 == fid { + l.1 = m.counter + } else { + for i in l.0 + 1..fid + 1 { + state.pending_removal.push_back((i, m.counter)) + } + } + } + None => state.pending_removal.push_back((fid, m.counter)), + } + state.next_complete = m; + } + self.file_pool.remove_files(state, keep_nrecords).await.ok(); + Ok(()) + } + + pub fn file_pool_in_use(&self) -> usize { + self.file_pool.in_use_len() + } + + pub async fn read_recent_records<'a>( + &'a self, + nrecords: usize, + recover_policy: &RecoverPolicy, + ) -> Result, ()> { + let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); + let file_pool = &self.file_pool; + let file_nbit = file_pool.file_nbit; + let block_size = 1 << file_pool.block_nbit; + let msize = std::mem::size_of::(); + + let logfiles = sort_fids( + file_nbit, + file_pool + .store + .enumerate_files()? + .filter(|f| filename_fmt.is_match(f)) + .map(|s| get_fid(&s)) + .collect(), + ); + + let mut chunks: Option> = None; + let mut records = Vec::new(); + 'outer: for (_, fid) in logfiles.into_iter().rev() { + let f = file_pool.get_file(fid, false).await?; + let ring_stream = WALLoader::read_rings(&f, true, file_pool.block_nbit, recover_policy); + let mut off = fid << file_nbit; + let mut rings = Vec::new(); + futures::pin_mut!(ring_stream); + while let Some(ring) = ring_stream.next().await { + rings.push(ring); + } + for ring in rings.into_iter().rev() { + let ring = ring.map_err(|_| ())?; + let (header, payload) = ring; + let payload = payload.unwrap(); + match header.rtype.try_into() { + Ok(WALRingType::Full) => { + assert!(chunks.is_none()); + if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { + return Err(()); + } + off += header.rsize as u64; + records.push(payload); + } + Ok(WALRingType::First) => { + if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { + return Err(()); + } + if let Some(mut chunks) = chunks.take() { + chunks.push(payload); + let mut acc = Vec::new(); + chunks.into_iter().rev().fold(&mut acc, |acc, v| { + acc.extend(v.iter()); + acc + }); + records.push(acc.into()); + } else { + unreachable!() + } + off += header.rsize as u64; + } + Ok(WALRingType::Middle) => { + if let Some(chunks) = &mut chunks { + chunks.push(payload); + } else { + unreachable!() + } + off += header.rsize as u64; + } + Ok(WALRingType::Last) => { + assert!(chunks.is_none()); + chunks = Some(vec![payload]); + off += header.rsize as u64; + } + Ok(WALRingType::Null) => break, + Err(_) => match recover_policy { + RecoverPolicy::Strict => return Err(()), + RecoverPolicy::BestEffort => break 'outer, + }, + } + let block_remain = block_size - (off & (block_size - 1)); + if block_remain <= msize as u64 { + off += block_remain; + } + if records.len() >= nrecords { + break 'outer; + } + } + } + Ok(records) + } +} + +#[derive(Copy, Clone)] +pub enum RecoverPolicy { + /// all checksums must be correct, otherwise recovery fails + Strict, + /// stop recovering when hitting the first corrupted record + BestEffort, +} + +pub struct WALLoader { + file_nbit: u64, + block_nbit: u64, + cache_size: NonZeroUsize, + recover_policy: RecoverPolicy, +} + +impl Default for WALLoader { + fn default() -> Self { + WALLoader { + file_nbit: 22, // 4MB + block_nbit: 15, // 32KB, + cache_size: NonZeroUsize::new(16).unwrap(), + recover_policy: RecoverPolicy::Strict, + } + } +} + +impl WALLoader { + pub fn new() -> Self { + Default::default() + } + + pub fn file_nbit(&mut self, v: u64) -> &mut Self { + self.file_nbit = v; + self + } + + pub fn block_nbit(&mut self, v: u64) -> &mut Self { + self.block_nbit = v; + self + } + + pub fn cache_size(&mut self, v: NonZeroUsize) -> &mut Self { + self.cache_size = v; + self + } + + pub fn recover_policy(&mut self, p: RecoverPolicy) -> &mut Self { + self.recover_policy = p; + self + } + + fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { + if checksum == CRC32.checksum(data) { + Ok(true) + } else { + match p { + RecoverPolicy::Strict => Err(()), + RecoverPolicy::BestEffort => Ok(false), + } + } + } + + fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { + Self::verify_checksum_(data, checksum, &self.recover_policy) + } + + fn read_rings<'a, F: WALStore + 'a>( + file: &'a WALFileHandle<'a, F>, + read_payload: bool, + block_nbit: u64, + recover_policy: &'a RecoverPolicy, + ) -> impl futures::Stream), bool>> + 'a { + let block_size = 1 << block_nbit; + let msize = std::mem::size_of::(); + + struct Vars<'a, F: WALStore> { + done: bool, + off: u64, + file: &'a WALFileHandle<'a, F>, + } + + let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { + done: false, + off: 0, + file, + })); + + futures::stream::unfold((), move |_| { + let v = vars.clone(); + async move { + let mut v = v.borrow_mut(); + + macro_rules! check { + ($res: expr) => { + match $res { + Ok(t) => t, + Err(_) => die!(), + } + }; + } + + macro_rules! die { + () => {{ + v.done = true; + return Some((Err(true), ())); + }}; + } + + macro_rules! _yield { + () => {{ + v.done = true; + return None; + }}; + ($v: expr) => {{ + let v = $v; + catch_up!(); + return Some((Ok(v), ())); + }}; + } + + macro_rules! catch_up { + () => {{ + let block_remain = block_size - (v.off & (block_size - 1)); + if block_remain <= msize as u64 { + v.off += block_remain; + } + }}; + } + + if v.done { + return None; + } + let header_raw = match check!(v.file.read(v.off, msize as usize).await) { + Some(h) => h, + None => _yield!(), + }; + v.off += msize as u64; + let header = + unsafe { std::mem::transmute::<*const u8, &WALRingBlob>(header_raw.as_ptr()) }; + let header = WALRingBlob { + counter: header.counter, + crc32: header.crc32, + rsize: header.rsize, + rtype: header.rtype, + }; + let payload; + match header.rtype.try_into() { + Ok(WALRingType::Full) + | Ok(WALRingType::First) + | Ok(WALRingType::Middle) + | Ok(WALRingType::Last) => { + payload = if read_payload { + Some(check!(check!( + v.file.read(v.off, header.rsize as usize).await + ) + .ok_or(()))) + } else { + None + }; + v.off += header.rsize as u64; + } + Ok(WALRingType::Null) => _yield!(), + Err(_) => match recover_policy { + RecoverPolicy::Strict => die!(), + RecoverPolicy::BestEffort => { + v.done = true; + return Some((Err(false), ())); + } + }, + } + _yield!((header, payload)) + } + }) + } + + fn read_records<'a, F: WALStore + 'a>( + &'a self, + file: &'a WALFileHandle<'a, F>, + chunks: &'a mut Option<(Vec, WALPos)>, + ) -> impl futures::Stream> + 'a { + let fid = file.fid; + let file_nbit = self.file_nbit; + let block_size = 1 << self.block_nbit; + let msize = std::mem::size_of::(); + + struct Vars<'a, F: WALStore> { + done: bool, + chunks: &'a mut Option<(Vec, WALPos)>, + off: u64, + file: &'a WALFileHandle<'a, F>, + } + + let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { + done: false, + off: 0, + chunks, + file, + })); + + futures::stream::unfold((), move |_| { + let v = vars.clone(); + async move { + let mut v = v.borrow_mut(); + + macro_rules! check { + ($res: expr) => { + match $res { + Ok(t) => t, + Err(_) => die!(), + } + }; + } + + macro_rules! die { + () => {{ + v.done = true; + return Some((Err(true), ())); + }}; + } + + macro_rules! _yield { + () => {{ + v.done = true; + return None; + }}; + ($v: expr) => {{ + let v = $v; + catch_up!(); + return Some((Ok(v), ())); + }}; + } + + macro_rules! catch_up { + () => {{ + let block_remain = block_size - (v.off & (block_size - 1)); + if block_remain <= msize as u64 { + v.off += block_remain; + } + }}; + } + + if v.done { + return None; + } + loop { + let header_raw = match check!(v.file.read(v.off, msize as usize).await) { + Some(h) => h, + None => _yield!(), + }; + let ringid_start = (fid << file_nbit) + v.off; + v.off += msize as u64; + let header = unsafe { + std::mem::transmute::<*const u8, &WALRingBlob>(header_raw.as_ptr()) + }; + let rsize = header.rsize; + match header.rtype.try_into() { + Ok(WALRingType::Full) => { + assert!(v.chunks.is_none()); + let payload = + check!(check!(v.file.read(v.off, rsize as usize).await).ok_or(())); + // TODO: improve the behavior when CRC32 fails + if !check!(self.verify_checksum(&payload, header.crc32)) { + die!() + } + v.off += rsize as u64; + _yield!(( + payload, + WALRingId { + start: ringid_start, + end: (fid << file_nbit) + v.off, + counter: header.counter + }, + header.counter + )) + } + Ok(WALRingType::First) => { + assert!(v.chunks.is_none()); + let chunk = + check!(check!(v.file.read(v.off, rsize as usize).await).ok_or(())); + if !check!(self.verify_checksum(&chunk, header.crc32)) { + die!() + } + *v.chunks = Some((vec![chunk], ringid_start)); + v.off += rsize as u64; + } + Ok(WALRingType::Middle) => { + let Vars { + chunks, off, file, .. + } = &mut *v; + if let Some((chunks, _)) = chunks { + let chunk = + check!(check!(file.read(*off, rsize as usize).await).ok_or(())); + if !check!(self.verify_checksum(&chunk, header.crc32)) { + die!() + } + chunks.push(chunk); + } // otherwise ignore the leftover + *off += rsize as u64; + } + Ok(WALRingType::Last) => { + let v_off = v.off; + v.off += rsize as u64; + if let Some((mut chunks, ringid_start)) = v.chunks.take() { + let chunk = check!(check!( + v.file.read(v_off, rsize as usize).await + ) + .ok_or(())); + if !check!(self.verify_checksum(&chunk, header.crc32)) { + die!() + } + chunks.push(chunk); + let mut payload = Vec::new(); + payload.resize(chunks.iter().fold(0, |acc, v| acc + v.len()), 0); + let mut ps = &mut payload[..]; + for c in chunks { + ps[..c.len()].copy_from_slice(&*c); + ps = &mut ps[c.len()..]; + } + _yield!(( + payload.into_boxed_slice(), + WALRingId { + start: ringid_start, + end: (fid << file_nbit) + v.off, + counter: header.counter, + }, + header.counter + )) + } + } + Ok(WALRingType::Null) => _yield!(), + Err(_) => match self.recover_policy { + RecoverPolicy::Strict => die!(), + RecoverPolicy::BestEffort => { + v.done = true; + return Some((Err(false), ())); + } + }, + } + catch_up!() + } + } + }) + } + + /// Recover by reading the WAL files. + pub async fn load Result<(), ()>>( + &self, + store: S, + mut recover_func: F, + keep_nrecords: u32, + ) -> Result, ()> { + let msize = std::mem::size_of::(); + assert!(self.file_nbit > self.block_nbit); + assert!(msize < 1 << self.block_nbit); + let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); + let mut file_pool = + WALFilePool::new(store, self.file_nbit, self.block_nbit, self.cache_size).await?; + let logfiles = sort_fids( + self.file_nbit, + file_pool + .store + .enumerate_files()? + .filter(|f| filename_fmt.is_match(f)) + .map(|s| get_fid(&s)) + .collect(), + ); + + let header = file_pool.read_header().await?; + + let mut chunks = None; + let mut pre_skip = true; + let mut scanned: Vec<(String, WALFileHandle)> = Vec::new(); + let mut counter = 0; + + // TODO: check for missing logfiles + 'outer: for (_, fid) in logfiles.into_iter() { + let fname = get_fname(fid); + let f = file_pool.get_file(fid, false).await?; + if header.recover_fid == fid { + pre_skip = false; + } + if pre_skip { + scanned.push((fname, f)); + continue; + } + { + let stream = self.read_records(&f, &mut chunks); + futures::pin_mut!(stream); + while let Some(res) = stream.next().await { + let (bytes, ring_id, _) = match res { + Err(e) => { + if e { + return Err(()); + } else { + break 'outer; + } + } + Ok(t) => t, + }; + recover_func(bytes, ring_id)?; + } + } + scanned.push((fname, f)); + } + + 'outer: for (_, f) in scanned.iter().rev() { + let records: Vec<_> = Self::read_rings(f, false, self.block_nbit, &self.recover_policy) + .collect() + .await; + for e in records.into_iter().rev() { + let (rec, _) = e.map_err(|_| ())?; + if rec.rtype == WALRingType::Full as u8 || rec.rtype == WALRingType::Last as u8 { + counter = rec.counter + 1; + break 'outer; + } + } + } + + let fid_mask = (!0) >> self.file_nbit; + let mut pending_removal = VecDeque::new(); + let recover_fid = match scanned.last() { + Some((_, f)) => (f.fid + 1) & fid_mask, + None => 0, + }; + + file_pool.write_header(&Header { recover_fid }).await?; + + let mut skip_remove = false; + for (fname, f) in scanned.into_iter() { + let mut last = None; + let stream = Self::read_rings(&f, false, self.block_nbit, &self.recover_policy); + futures::pin_mut!(stream); + while let Some(r) = stream.next().await { + last = Some(r.map_err(|_| ())?); + } + if let Some((last_rec, _)) = last { + if !counter_lt(last_rec.counter + keep_nrecords, counter) { + skip_remove = true; + } + if skip_remove { + pending_removal.push_back((f.fid, last_rec.counter)); + } + } + if !skip_remove { + f.truncate(0)?; + file_pool.store.remove_file(fname).await?; + } + } + + file_pool.reset(); + + let next = recover_fid << self.file_nbit; + let next_complete = WALRingId { + start: 0, + end: next, + counter, + }; + Ok(WALWriter::new( + WALState { + counter, + next_complete, + next, + file_nbit: self.file_nbit, + io_complete: BinaryHeap::new(), + pending_removal, + }, + file_pool, + )) + } +} + +pub const CRC32: crc::Crc = crc::Crc::::new(&crc::CRC_32_CKSUM); diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs new file mode 100644 index 000000000000..4d651ae9ca6f --- /dev/null +++ b/growth-ring/tests/common/mod.rs @@ -0,0 +1,666 @@ +#[cfg(test)] +#[allow(dead_code)] +use async_trait::async_trait; +use futures::executor::block_on; +use growthring::wal::{WALBytes, WALFile, WALLoader, WALPos, WALRingId, WALStore}; +use indexmap::{map::Entry, IndexMap}; +use rand::Rng; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::collections::{hash_map, HashMap}; +use std::convert::TryInto; +use std::rc::Rc; + +pub trait FailGen { + fn next_fail(&self) -> bool; +} + +struct FileContentEmul(RefCell>); + +impl FileContentEmul { + pub fn new() -> Self { + FileContentEmul(RefCell::new(Vec::new())) + } +} + +impl std::ops::Deref for FileContentEmul { + type Target = RefCell>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Emulate the a virtual file handle. +pub struct WALFileEmul { + file: Rc, + fgen: Rc, +} + +#[async_trait(?Send)] +impl WALFile for WALFileEmul { + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()> { + if self.fgen.next_fail() { + return Err(()); + } + let offset = offset as usize; + if offset + length > self.file.borrow().len() { + self.file.borrow_mut().resize(offset + length, 0) + } + for v in &mut self.file.borrow_mut()[offset..offset + length] { + *v = 0 + } + Ok(()) + } + + fn truncate(&self, length: usize) -> Result<(), ()> { + if self.fgen.next_fail() { + return Err(()); + } + self.file.borrow_mut().resize(length, 0); + Ok(()) + } + + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()> { + if self.fgen.next_fail() { + return Err(()); + } + let offset = offset as usize; + self.file.borrow_mut()[offset..offset + data.len()].copy_from_slice(&data); + Ok(()) + } + + async fn read(&self, offset: WALPos, length: usize) -> Result, ()> { + if self.fgen.next_fail() { + return Err(()); + } + + let offset = offset as usize; + let file = self.file.borrow(); + if offset + length > file.len() { + Ok(None) + } else { + Ok(Some( + (&file[offset..offset + length]).to_vec().into_boxed_slice(), + )) + } + } +} + +pub struct WALStoreEmulState { + files: HashMap>, +} + +impl WALStoreEmulState { + pub fn new() -> Self { + WALStoreEmulState { + files: HashMap::new(), + } + } + pub fn clone(&self) -> Self { + WALStoreEmulState { + files: self.files.clone(), + } + } +} + +/// Emulate the persistent storage state. +pub struct WALStoreEmul<'a, G> +where + G: FailGen, +{ + state: RefCell<&'a mut WALStoreEmulState>, + fgen: Rc, +} + +impl<'a, G: FailGen> WALStoreEmul<'a, G> { + pub fn new(state: &'a mut WALStoreEmulState, fgen: Rc) -> Self { + let state = RefCell::new(state); + WALStoreEmul { state, fgen } + } +} + +#[async_trait(?Send)] +impl<'a, G> WALStore for WALStoreEmul<'a, G> +where + G: 'static + FailGen, +{ + type FileNameIter = std::vec::IntoIter; + + async fn open_file(&self, filename: &str, touch: bool) -> Result, ()> { + if self.fgen.next_fail() { + return Err(()); + } + match self.state.borrow_mut().files.entry(filename.to_string()) { + hash_map::Entry::Occupied(e) => Ok(Box::new(WALFileEmul { + file: e.get().clone(), + fgen: self.fgen.clone(), + })), + hash_map::Entry::Vacant(e) => { + if touch { + Ok(Box::new(WALFileEmul { + file: e.insert(Rc::new(FileContentEmul::new())).clone(), + fgen: self.fgen.clone(), + })) + } else { + Err(()) + } + } + } + } + + async fn remove_file(&self, filename: String) -> Result<(), ()> { + //println!("remove_file(filename={})", filename); + if self.fgen.next_fail() { + return Err(()); + } + self.state + .borrow_mut() + .files + .remove(&filename) + .ok_or(()) + .and_then(|_| Ok(())) + } + + fn enumerate_files(&self) -> Result { + if self.fgen.next_fail() { + return Err(()); + } + let mut logfiles = Vec::new(); + for (fname, _) in self.state.borrow().files.iter() { + logfiles.push(fname.clone()) + } + Ok(logfiles.into_iter()) + } +} + +pub struct SingleFailGen { + cnt: std::cell::Cell, + fail_point: usize, +} + +impl SingleFailGen { + pub fn new(fail_point: usize) -> Self { + SingleFailGen { + cnt: std::cell::Cell::new(0), + fail_point, + } + } +} + +impl FailGen for SingleFailGen { + fn next_fail(&self) -> bool { + let c = self.cnt.get(); + self.cnt.set(c + 1); + c == self.fail_point + } +} + +pub struct ZeroFailGen; + +impl FailGen for ZeroFailGen { + fn next_fail(&self) -> bool { + false + } +} + +pub struct CountFailGen(std::cell::Cell); + +impl CountFailGen { + pub fn new() -> Self { + CountFailGen(std::cell::Cell::new(0)) + } + pub fn get_count(&self) -> usize { + self.0.get() + } +} + +impl FailGen for CountFailGen { + fn next_fail(&self) -> bool { + self.0.set(self.0.get() + 1); + false + } +} + +/// An ordered list of intervals: `(begin, end, color)*`. +#[derive(Clone)] +pub struct PaintStrokes(Vec<(u32, u32, u32)>); + +impl PaintStrokes { + pub fn new() -> Self { + PaintStrokes(Vec::new()) + } + + pub fn to_bytes(&self) -> WALBytes { + let mut res: Vec = Vec::new(); + let is = std::mem::size_of::(); + let len = self.0.len() as u32; + res.resize(is * (1 + 3 * self.0.len()), 0); + let mut rs = &mut res[..]; + rs[..is].copy_from_slice(&len.to_le_bytes()); + rs = &mut rs[is..]; + for (s, e, c) in self.0.iter() { + rs[..is].copy_from_slice(&s.to_le_bytes()); + rs[is..is * 2].copy_from_slice(&e.to_le_bytes()); + rs[is * 2..is * 3].copy_from_slice(&c.to_le_bytes()); + rs = &mut rs[is * 3..]; + } + res.into_boxed_slice() + } + + pub fn from_bytes(raw: &[u8]) -> Self { + assert!(raw.len() > 4); + assert!(raw.len() & 3 == 0); + let is = std::mem::size_of::(); + let (len_raw, mut rest) = raw.split_at(is); + let len = u32::from_le_bytes(len_raw.try_into().unwrap()); + let mut res = Vec::new(); + for _ in 0..len { + let (s_raw, rest1) = rest.split_at(is); + let (e_raw, rest2) = rest1.split_at(is); + let (c_raw, rest3) = rest2.split_at(is); + res.push(( + u32::from_le_bytes(s_raw.try_into().unwrap()), + u32::from_le_bytes(e_raw.try_into().unwrap()), + u32::from_le_bytes(c_raw.try_into().unwrap()), + )); + rest = rest3 + } + PaintStrokes(res) + } + + pub fn gen_rand( + max_pos: u32, + max_len: u32, + max_col: u32, + n: usize, + rng: &mut R, + ) -> PaintStrokes { + assert!(max_pos > 0); + let mut strokes = Self::new(); + for _ in 0..n { + let pos = rng.gen_range(0..max_pos); + let len = rng.gen_range(1..std::cmp::min(max_len, max_pos - pos + 1)); + strokes.stroke(pos, pos + len, rng.gen_range(0..max_col)) + } + strokes + } + + pub fn stroke(&mut self, start: u32, end: u32, color: u32) { + self.0.push((start, end, color)) + } + + pub fn into_vec(self) -> Vec<(u32, u32, u32)> { + self.0 + } +} + +impl growthring::wal::Record for PaintStrokes { + fn serialize(&self) -> WALBytes { + self.to_bytes() + } +} + +#[test] +fn test_paint_strokes() { + let mut p = PaintStrokes::new(); + for i in 0..3 { + p.stroke(i, i + 3, i + 10) + } + let pr = p.to_bytes(); + for ((s, e, c), i) in PaintStrokes::from_bytes(&pr) + .into_vec() + .into_iter() + .zip(0..) + { + assert_eq!(s, i); + assert_eq!(e, i + 3); + assert_eq!(c, i + 10); + } +} + +pub struct Canvas { + waiting: HashMap, + queue: IndexMap>, + canvas: Box<[u32]>, +} + +impl Canvas { + pub fn new(size: usize) -> Self { + let mut canvas = Vec::new(); + // fill the backgroudn color 0 + canvas.resize(size, 0); + let canvas = canvas.into_boxed_slice(); + Canvas { + waiting: HashMap::new(), + queue: IndexMap::new(), + canvas, + } + } + + pub fn new_reference(&self, ops: &[PaintStrokes]) -> Self { + let mut res = Self::new(self.canvas.len()); + for op in ops { + for (s, e, c) in op.0.iter() { + for i in *s..*e { + res.canvas[i as usize] = *c + } + } + } + res + } + + fn get_waiting(&mut self, rid: WALRingId) -> &mut usize { + match self.waiting.entry(rid) { + hash_map::Entry::Occupied(e) => e.into_mut(), + hash_map::Entry::Vacant(e) => e.insert(0), + } + } + + fn get_queued(&mut self, pos: u32) -> &mut VecDeque<(u32, WALRingId)> { + match self.queue.entry(pos) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => e.insert(VecDeque::new()), + } + } + + pub fn prepaint(&mut self, strokes: &PaintStrokes, rid: &WALRingId) { + let rid = *rid; + let mut nwait = 0; + for (s, e, c) in strokes.0.iter() { + for i in *s..*e { + nwait += 1; + self.get_queued(i).push_back((*c, rid)) + } + } + *self.get_waiting(rid) = nwait + } + + // TODO: allow customized scheduler + /// Schedule to paint one position, randomly. It optionally returns a finished batch write + /// identified by its start position of WALRingId. + pub fn rand_paint(&mut self, rng: &mut R) -> Option<(Option, u32)> { + if self.is_empty() { + return None; + } + let idx = rng.gen_range(0..self.queue.len()); + let (pos, _) = self.queue.get_index(idx).unwrap(); + let pos = *pos; + Some((self.paint(pos), pos)) + } + + pub fn clear_queued(&mut self) { + self.queue.clear(); + self.waiting.clear(); + } + + pub fn paint_all(&mut self) { + for (pos, q) in self.queue.iter() { + self.canvas[*pos as usize] = q.back().unwrap().0; + } + self.clear_queued() + } + + pub fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + pub fn paint(&mut self, pos: u32) -> Option { + let q = self.queue.get_mut(&pos).unwrap(); + let (c, rid) = q.pop_front().unwrap(); + if q.is_empty() { + self.queue.remove(&pos); + } + self.canvas[pos as usize] = c; + if let Some(cnt) = self.waiting.get_mut(&rid) { + *cnt -= 1; + if *cnt == 0 { + self.waiting.remove(&rid); + Some(rid) + } else { + None + } + } else { + None + } + } + + pub fn is_same(&self, other: &Canvas) -> bool { + self.canvas.cmp(&other.canvas) == std::cmp::Ordering::Equal + } + + pub fn print(&self, max_col: usize) { + println!("# begin canvas"); + for r in self.canvas.chunks(max_col) { + for c in r.iter() { + print!("{:02x} ", c & 0xff); + } + println!(""); + } + println!("# end canvas"); + } +} + +#[test] +fn test_canvas() { + let mut rng = ::seed_from_u64(42); + let mut canvas1 = Canvas::new(100); + let mut canvas2 = Canvas::new(100); + let canvas3 = Canvas::new(101); + let dummy = WALRingId::empty_id(); + let s1 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); + let s2 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); + assert!(canvas1.is_same(&canvas2)); + assert!(!canvas2.is_same(&canvas3)); + canvas1.prepaint(&s1, &dummy); + canvas1.prepaint(&s2, &dummy); + canvas2.prepaint(&s1, &dummy); + canvas2.prepaint(&s2, &dummy); + assert!(canvas1.is_same(&canvas2)); + canvas1.rand_paint(&mut rng); + assert!(!canvas1.is_same(&canvas2)); + while let Some(_) = canvas1.rand_paint(&mut rng) {} + while let Some(_) = canvas2.rand_paint(&mut rng) {} + assert!(canvas1.is_same(&canvas2)); + canvas1.print(10); +} + +pub struct PaintingSim { + pub block_nbit: u64, + pub file_nbit: u64, + pub file_cache: usize, + /// number of PaintStrokes (WriteBatch) + pub n: usize, + /// number of strokes per PaintStrokes + pub m: usize, + /// number of scheduled ticks per PaintStroke submission + pub k: usize, + /// the size of canvas + pub csize: usize, + /// max length of a single stroke + pub stroke_max_len: u32, + /// max color value + pub stroke_max_col: u32, + /// max number of strokes per PaintStroke + pub stroke_max_n: usize, + /// random seed + pub seed: u64, +} + +impl PaintingSim { + pub fn run( + &self, + state: &mut WALStoreEmulState, + canvas: &mut Canvas, + loader: WALLoader, + ops: &mut Vec, + ringid_map: &mut HashMap, + fgen: Rc, + ) -> Result<(), ()> { + let mut rng = ::seed_from_u64(self.seed); + let mut wal = block_on(loader.load( + WALStoreEmul::new(state, fgen.clone()), + |_, _| { + if fgen.next_fail() { + Err(()) + } else { + Ok(()) + } + }, + 0, + ))?; + for _ in 0..self.n { + let pss = (0..self.m) + .map(|_| { + PaintStrokes::gen_rand( + self.csize as u32, + self.stroke_max_len, + self.stroke_max_col, + rng.gen_range(1..self.stroke_max_n + 1), + &mut rng, + ) + }) + .collect::>(); + let pss_ = pss.clone(); + // write ahead + let rids = wal.grow(pss); + assert_eq!(rids.len(), self.m); + let recs = rids + .into_iter() + .zip(pss_.into_iter()) + .map(|(r, ps)| -> Result<_, _> { + ops.push(ps); + let (rec, rid) = futures::executor::block_on(r)?; + ringid_map.insert(rid, ops.len() - 1); + Ok((rec, rid)) + }) + .collect::, ()>>()?; + // finish appending to WAL + /* + for rid in rids.iter() { + println!("got ringid: {:?}", rid); + } + */ + // prepare data writes + for (ps, rid) in recs.into_iter() { + canvas.prepaint(&ps, &rid); + } + // run k ticks of the fine-grained scheduler + for _ in 0..rng.gen_range(1..self.k) { + // storage I/O could fail + if fgen.next_fail() { + return Err(()); + } + if let Some((fin_rid, _)) = canvas.rand_paint(&mut rng) { + if let Some(rid) = fin_rid { + futures::executor::block_on(wal.peel(&[rid], 0))? + } + } else { + break; + } + } + } + //canvas.print(40); + assert_eq!(wal.file_pool_in_use(), 0); + Ok(()) + } + + pub fn get_walloader(&self) -> WALLoader { + let mut loader = WALLoader::new(); + loader + .file_nbit(self.file_nbit) + .block_nbit(self.block_nbit) + .cache_size(std::num::NonZeroUsize::new(self.file_cache).unwrap()); + loader + } + + pub fn get_nticks(&self, state: &mut WALStoreEmulState) -> usize { + let mut canvas = Canvas::new(self.csize); + let mut ops: Vec = Vec::new(); + let mut ringid_map = HashMap::new(); + let fgen = Rc::new(CountFailGen::new()); + self.run( + state, + &mut canvas, + self.get_walloader(), + &mut ops, + &mut ringid_map, + fgen.clone(), + ) + .unwrap(); + fgen.get_count() + } + + pub fn check( + &self, + state: &mut WALStoreEmulState, + canvas: &mut Canvas, + wal: WALLoader, + ops: &Vec, + ringid_map: &HashMap, + ) -> bool { + if ops.is_empty() { + return true; + } + let mut last_idx = 0; + let mut napplied = 0; + canvas.clear_queued(); + block_on(wal.load( + WALStoreEmul::new(state, Rc::new(ZeroFailGen)), + |payload, ringid| { + let s = PaintStrokes::from_bytes(&payload); + canvas.prepaint(&s, &ringid); + last_idx = *ringid_map.get(&ringid).unwrap() + 1; + napplied += 1; + Ok(()) + }, + 0, + )) + .unwrap(); + println!("last = {}/{}, applied = {}", last_idx, ops.len(), napplied); + canvas.paint_all(); + // recover complete + let canvas0 = if last_idx > 0 { + let canvas0 = canvas.new_reference(&ops[..last_idx]); + if canvas.is_same(&canvas0) { + None + } else { + Some(canvas0) + } + } else { + let canvas0 = canvas.new_reference(&[]); + if canvas.is_same(&canvas0) { + None + } else { + let i0 = ops.len() - self.m; + let mut canvas0 = canvas0.new_reference(&ops[..i0]); + let mut res = None; + 'outer: loop { + if canvas.is_same(&canvas0) { + break; + } + for i in i0..ops.len() { + canvas0.prepaint(&ops[i], &WALRingId::empty_id()); + canvas0.paint_all(); + if canvas.is_same(&canvas0) { + break 'outer; + } + } + res = Some(canvas0); + break; + } + res + } + }; + if let Some(canvas0) = canvas0 { + canvas.print(40); + canvas0.print(40); + false + } else { + true + } + } + + pub fn new_canvas(&self) -> Canvas { + Canvas::new(self.csize) + } +} diff --git a/growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs new file mode 100644 index 000000000000..2e6b1faadbbb --- /dev/null +++ b/growth-ring/tests/rand_fail.rs @@ -0,0 +1,99 @@ +#[cfg(test)] +mod common; + +use std::collections::HashMap; +use std::rc::Rc; + +fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WALStoreEmulState, f: usize) { + let sim = &sims[0]; + // save the current state and start from there + let mut state = state.clone(); + let mut state0 = state.clone(); + let nticks = sim.get_nticks(&mut state0); + println!("fail = {}, nticks = {}", f, nticks); + for i in 0..nticks { + println!("fail = {}, pos = {}", f, i); + let mut canvas = sim.new_canvas(); + let mut ops: Vec = Vec::new(); + let mut ringid_map = HashMap::new(); + let fgen = common::SingleFailGen::new(i); + if sim + .run( + &mut state, + &mut canvas, + sim.get_walloader(), + &mut ops, + &mut ringid_map, + Rc::new(fgen), + ) + .is_err() + { + if sims.len() > 1 { + _multi_point_failure(&sims[1..], &state, f + 1) + } else { + assert!(sim.check( + &mut state, + &mut canvas, + sim.get_walloader(), + &ops, + &ringid_map, + )) + } + } + } +} + +fn multi_point_failure(sims: &[common::PaintingSim]) { + _multi_point_failure(sims, &common::WALStoreEmulState::new(), 1); +} + +#[test] +fn single_point_failure1() { + let sim = common::PaintingSim { + block_nbit: 5, + file_nbit: 6, + file_cache: 1000, + n: 100, + m: 10, + k: 1000, + csize: 1000, + stroke_max_len: 10, + stroke_max_col: 256, + stroke_max_n: 5, + seed: 0, + }; + multi_point_failure(&[sim]); +} + +#[test] +fn two_failures() { + let sims = [ + common::PaintingSim { + block_nbit: 5, + file_nbit: 6, + file_cache: 1000, + n: 10, + m: 5, + k: 100, + csize: 1000, + stroke_max_len: 10, + stroke_max_col: 256, + stroke_max_n: 3, + seed: 0, + }, + common::PaintingSim { + block_nbit: 5, + file_nbit: 6, + file_cache: 1000, + n: 10, + m: 5, + k: 100, + csize: 1000, + stroke_max_len: 10, + stroke_max_col: 256, + stroke_max_n: 3, + seed: 0, + }, + ]; + multi_point_failure(&sims); +} diff --git a/libaio-futures/Cargo.toml b/libaio-futures/Cargo.toml new file mode 100644 index 000000000000..f33434995077 --- /dev/null +++ b/libaio-futures/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "libaio-futures" +version = "0.2.3" +authors = ["Determinant "] +edition = "2018" +homepage = "https://github.com/Determinant/libaio-futures" +keywords = ["libaio", "aio", "async", "futures"] +license = "MIT" +description = "Straightforward Linux AIO using Futures/async/await." + +[features] +emulated-failure = [] + +[dependencies] +libc = "0.2.133" +parking_lot = "0.12.1" +crossbeam-channel = "0.5.6" + +[dev-dependencies] +futures = "0.3.24" + +[lib] +name = "aiofut" +path = "src/lib.rs" +crate-type = ["dylib", "rlib", "staticlib"] diff --git a/libaio-futures/build.rs b/libaio-futures/build.rs new file mode 100644 index 000000000000..fd58353f4749 --- /dev/null +++ b/libaio-futures/build.rs @@ -0,0 +1,15 @@ +use std::env; + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + std::process::Command::new("make") + .args(&[format!("{}/libaio.a", out_dir)]) + .env("OUT_DIR", &out_dir) + .current_dir("./libaio") + .status() + .expect("failed to make libaio"); + eprintln!("{}", out_dir); + // the current source version of libaio is 0.3.112 + println!("cargo:rerun-if-changed={}/libaio.a", out_dir); + println!("cargo:rustc-link-search=native={}", out_dir); +} diff --git a/libaio-futures/libaio/.gitignore b/libaio-futures/libaio/.gitignore new file mode 100644 index 000000000000..d4a430941e88 --- /dev/null +++ b/libaio-futures/libaio/.gitignore @@ -0,0 +1,10 @@ +*.rej +*.orig +*~ +/*.patch + +*.o +*.o[ls] + +/src/libaio.a +/src/libaio.so* diff --git a/libaio-futures/libaio/COPYING b/libaio-futures/libaio/COPYING new file mode 100644 index 000000000000..c4792dd27a32 --- /dev/null +++ b/libaio-futures/libaio/COPYING @@ -0,0 +1,515 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +^L + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +^L + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +^L + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +^L + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +^L + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. +^L + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +^L + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS +^L + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper +mail. + +You should also get your employer (if you work as a programmer) or +your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James +Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/libaio-futures/libaio/Makefile b/libaio-futures/libaio/Makefile new file mode 100644 index 000000000000..1865bbe6622b --- /dev/null +++ b/libaio-futures/libaio/Makefile @@ -0,0 +1,73 @@ +prefix=/usr +includedir=$(prefix)/include +libdir=$(prefix)/lib + +CFLAGS ?= -g -fomit-frame-pointer -O2 +CFLAGS += -Wall -I. -fPIC +SO_CFLAGS=-shared $(CFLAGS) +L_CFLAGS=$(CFLAGS) +LINK_FLAGS= +LINK_FLAGS+=$(LDFLAGS) +ENABLE_SHARED ?= 1 + +soname=libaio.so.1 +minor=0 +micro=1 +libname=$(soname).$(minor).$(micro) +all_targets += $(OUT_DIR)/libaio.a + +ifeq ($(ENABLE_SHARED),1) +all_targets += $(libname) +endif + +all: $(all_targets) + +# libaio provided functions +libaio_srcs := io_queue_init.c io_queue_release.c +libaio_srcs += io_queue_wait.c io_queue_run.c + +# real syscalls +libaio_srcs += io_getevents.c io_submit.c io_cancel.c +libaio_srcs += io_setup.c io_destroy.c io_pgetevents.c + +# internal functions +libaio_srcs += raw_syscall.c + +# old symbols +libaio_srcs += compat-0_1.c + +libaio_objs := $(patsubst %.c,$(OUT_DIR)/%.ol,$(libaio_srcs)) +libaio_sobjs := $(patsubst %.c,$(OUT_DIR)/%.os,$(libaio_srcs)) + +$(libaio_objs) $(libaio_sobjs): libaio.h vsys_def.h + +$(OUT_DIR)/%.os: %.c + $(CC) $(SO_CFLAGS) -c -o $@ $< + +$(OUT_DIR)/%.ol: %.c + $(CC) $(L_CFLAGS) -c -o $@ $< + +AR ?= ar +RANLIB ?= ranlib +$(OUT_DIR)/libaio.a: $(libaio_objs) + rm -f $(OUT_DIR)/libaio.a + $(AR) r $(OUT_DIR)/libaio.a $^ + $(RANLIB) $(OUT_DIR)/libaio.a + +$(libname): $(libaio_sobjs) libaio.map + $(CC) $(SO_CFLAGS) -Wl,--version-script=libaio.map -Wl,-soname=$(soname) -o $@ $(libaio_sobjs) $(LINK_FLAGS) + +install: $(all_targets) + install -D -m 644 libaio.h $(includedir)/libaio.h + install -D -m 644 libaio.a $(libdir)/libaio.a +ifeq ($(ENABLE_SHARED),1) + install -D -m 755 $(libname) $(libdir)/$(libname) + ln -sf $(libname) $(libdir)/$(soname) + ln -sf $(libname) $(libdir)/libaio.so +endif + +$(libaio_objs): libaio.h + +clean: + rm -f $(all_targets) $(libaio_objs) $(libaio_sobjs) $(soname).new + rm -f *.so* *.a *.o diff --git a/libaio-futures/libaio/aio_ring.h b/libaio-futures/libaio/aio_ring.h new file mode 100644 index 000000000000..3842c4b3c127 --- /dev/null +++ b/libaio-futures/libaio/aio_ring.h @@ -0,0 +1,49 @@ +/* + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _AIO_RING_H +#define _AIO_RING_H + +#define AIO_RING_MAGIC 0xa10a10a1 + +struct aio_ring { + unsigned id; /* kernel internal index number */ + unsigned nr; /* number of io_events */ + unsigned head; + unsigned tail; + + unsigned magic; + unsigned compat_features; + unsigned incompat_features; + unsigned header_length; /* size of aio_ring */ +}; + +static inline int aio_ring_is_empty(io_context_t ctx, struct timespec *timeout) +{ + struct aio_ring *ring = (struct aio_ring *)ctx; + + if (!ring || ring->magic != AIO_RING_MAGIC) + return 0; + if (!timeout || timeout->tv_sec || timeout->tv_nsec) + return 0; + if (ring->head != ring->tail) + return 0; + return 1; +} + +#endif /* _AIO_RING_H */ diff --git a/libaio-futures/libaio/compat-0_1.c b/libaio-futures/libaio/compat-0_1.c new file mode 100644 index 000000000000..136396f99638 --- /dev/null +++ b/libaio-futures/libaio/compat-0_1.c @@ -0,0 +1,62 @@ +/* libaio Linux async I/O interface + + compat-0_1.c : compatibility symbols for libaio 0.1.x-0.3.x + + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#include "libaio.h" +#include "vsys_def.h" + +#include "syscall.h" + + +/* ABI change. Provide partial compatibility on this one for now. */ +SYMVER(compat0_1_io_cancel, io_cancel, 0.1); +int compat0_1_io_cancel(io_context_t ctx, struct iocb *iocb) +{ + struct io_event event; + + /* FIXME: the old ABI would return the event on the completion queue */ + return io_cancel(ctx, iocb, &event); +} + +SYMVER(compat0_1_io_queue_wait, io_queue_wait, 0.1); +int compat0_1_io_queue_wait(io_context_t ctx, struct timespec *when) +{ + struct timespec timeout; + if (when) + timeout = *when; + return io_getevents(ctx, 0, 0, NULL, when ? &timeout : NULL); +} + + +/* ABI change. Provide backwards compatibility for this one. */ +SYMVER(compat0_1_io_getevents, io_getevents, 0.1); +int compat0_1_io_getevents(io_context_t ctx_id, long nr, + struct io_event *events, + const struct timespec *const_timeout) +{ + struct timespec timeout; + if (const_timeout) + timeout = *const_timeout; + return io_getevents(ctx_id, 1, nr, events, + const_timeout ? &timeout : NULL); +} + diff --git a/libaio-futures/libaio/io_cancel.c b/libaio-futures/libaio/io_cancel.c new file mode 100644 index 000000000000..2f0f5f4aa0d4 --- /dev/null +++ b/libaio-futures/libaio/io_cancel.c @@ -0,0 +1,23 @@ +/* io_cancel.c + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include "syscall.h" + +io_syscall3(int, io_cancel_0_4, io_cancel, io_context_t, ctx, struct iocb *, iocb, struct io_event *, event) +DEFSYMVER(io_cancel_0_4, io_cancel, 0.4) diff --git a/libaio-futures/libaio/io_destroy.c b/libaio-futures/libaio/io_destroy.c new file mode 100644 index 000000000000..0ab6bd17433d --- /dev/null +++ b/libaio-futures/libaio/io_destroy.c @@ -0,0 +1,23 @@ +/* io_destroy + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include "syscall.h" + +io_syscall1(int, io_destroy, io_destroy, io_context_t, ctx) diff --git a/libaio-futures/libaio/io_getevents.c b/libaio-futures/libaio/io_getevents.c new file mode 100644 index 000000000000..c471fbcd70bd --- /dev/null +++ b/libaio-futures/libaio/io_getevents.c @@ -0,0 +1,35 @@ +/* io_getevents.c + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include "syscall.h" +#include "aio_ring.h" + +io_syscall5(int, __io_getevents_0_4, io_getevents, io_context_t, ctx, long, min_nr, long, nr, struct io_event *, events, struct timespec *, timeout) + +int io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event * events, struct timespec * timeout) +{ + if (aio_ring_is_empty(ctx, timeout)) + return 0; + return __io_getevents_0_4(ctx, min_nr, nr, events, timeout); +} + +//DEFSYMVER(io_getevents_0_4, io_getevents, 0.4) diff --git a/libaio-futures/libaio/io_pgetevents.c b/libaio-futures/libaio/io_pgetevents.c new file mode 100644 index 000000000000..e6b061468897 --- /dev/null +++ b/libaio-futures/libaio/io_pgetevents.c @@ -0,0 +1,56 @@ +/* + libaio Linux async I/O interface + Copyright 2018 Christoph Hellwig. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include "syscall.h" +#include "aio_ring.h" + +#ifdef __NR_io_pgetevents +io_syscall6(int, __io_pgetevents, io_pgetevents, io_context_t, ctx, long, + min_nr, long, nr, struct io_event *, events, + struct timespec *, timeout, void *, sigmask); + +int io_pgetevents(io_context_t ctx, long min_nr, long nr, + struct io_event *events, struct timespec *timeout, + sigset_t *sigmask) +{ + struct { + unsigned long ss; + unsigned long ss_len; + } data; + + if (aio_ring_is_empty(ctx, timeout)) + return 0; + + data.ss = (unsigned long)sigmask; + data.ss_len = _NSIG / 8; + return __io_pgetevents(ctx, min_nr, nr, events, timeout, &data); +} +#else +int io_pgetevents(io_context_t ctx, long min_nr, long nr, + struct io_event *events, struct timespec *timeout, + sigset_t *sigmask) + +{ + return -ENOSYS; +} +#endif /* __NR_io_pgetevents */ diff --git a/libaio-futures/libaio/io_queue_init.c b/libaio-futures/libaio/io_queue_init.c new file mode 100644 index 000000000000..563d1375a425 --- /dev/null +++ b/libaio-futures/libaio/io_queue_init.c @@ -0,0 +1,33 @@ +/* io_queue_init.c + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#include "syscall.h" + +int io_queue_init(int maxevents, io_context_t *ctxp) +{ + if (maxevents > 0) { + *ctxp = NULL; + return io_setup(maxevents, ctxp); + } + return -EINVAL; +} diff --git a/libaio-futures/libaio/io_queue_release.c b/libaio-futures/libaio/io_queue_release.c new file mode 100644 index 000000000000..94bbb867a085 --- /dev/null +++ b/libaio-futures/libaio/io_queue_release.c @@ -0,0 +1,27 @@ +/* io_queue_release.c + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +int io_queue_release(io_context_t ctx) +{ + return io_destroy(ctx); +} diff --git a/libaio-futures/libaio/io_queue_run.c b/libaio-futures/libaio/io_queue_run.c new file mode 100644 index 000000000000..e0132f4009e8 --- /dev/null +++ b/libaio-futures/libaio/io_queue_run.c @@ -0,0 +1,39 @@ +/* io_submit + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +int io_queue_run(io_context_t ctx) +{ + static struct timespec timeout = { 0, 0 }; + struct io_event event; + int ret; + + /* FIXME: batch requests? */ + while (1 == (ret = io_getevents(ctx, 0, 1, &event, &timeout))) { + io_callback_t cb = (io_callback_t)event.data; + struct iocb *iocb = event.obj; + + cb(ctx, iocb, event.res, event.res2); + } + + return ret; +} diff --git a/libaio-futures/libaio/io_queue_wait.c b/libaio-futures/libaio/io_queue_wait.c new file mode 100644 index 000000000000..538d2f3b7bfd --- /dev/null +++ b/libaio-futures/libaio/io_queue_wait.c @@ -0,0 +1,31 @@ +/* io_submit + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define NO_SYSCALL_ERRNO +#include +#include +#include +#include "syscall.h" + +struct timespec; + +int io_queue_wait_0_4(io_context_t ctx, struct timespec *timeout) +{ + return io_getevents(ctx, 0, 0, NULL, timeout); +} +DEFSYMVER(io_queue_wait_0_4, io_queue_wait, 0.4) diff --git a/libaio-futures/libaio/io_setup.c b/libaio-futures/libaio/io_setup.c new file mode 100644 index 000000000000..4ba1afc993a5 --- /dev/null +++ b/libaio-futures/libaio/io_setup.c @@ -0,0 +1,23 @@ +/* io_setup + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include "syscall.h" + +io_syscall2(int, io_setup, io_setup, int, maxevents, io_context_t *, ctxp) diff --git a/libaio-futures/libaio/io_submit.c b/libaio-futures/libaio/io_submit.c new file mode 100644 index 000000000000..e22ba549601c --- /dev/null +++ b/libaio-futures/libaio/io_submit.c @@ -0,0 +1,23 @@ +/* io_submit + libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include "syscall.h" + +io_syscall3(int, io_submit, io_submit, io_context_t, ctx, long, nr, struct iocb **, iocbs) diff --git a/libaio-futures/libaio/libaio.h b/libaio-futures/libaio/libaio.h new file mode 100644 index 000000000000..2bc24e089ecf --- /dev/null +++ b/libaio-futures/libaio/libaio.h @@ -0,0 +1,300 @@ +/* /usr/include/libaio.h + * + * Copyright 2000,2001,2002 Red Hat, Inc. + * + * Written by Benjamin LaHaise + * + * libaio Linux async I/O interface + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __LIBAIO_H +#define __LIBAIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +struct timespec; +struct sockaddr; +struct iovec; + +typedef struct io_context *io_context_t; + +typedef enum io_iocb_cmd { + IO_CMD_PREAD = 0, + IO_CMD_PWRITE = 1, + + IO_CMD_FSYNC = 2, + IO_CMD_FDSYNC = 3, + + IO_CMD_POLL = 5, + IO_CMD_NOOP = 6, + IO_CMD_PREADV = 7, + IO_CMD_PWRITEV = 8, +} io_iocb_cmd_t; + +/* little endian, 32 bits */ +#if defined(__i386__) || (defined(__arm__) && !defined(__ARMEB__)) || \ + defined(__sh__) || defined(__bfin__) || defined(__MIPSEL__) || \ + defined(__cris__) || (defined(__riscv) && __riscv_xlen == 32) || \ + (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 4) +#define PADDED(x, y) x; unsigned y +#define PADDEDptr(x, y) x; unsigned y +#define PADDEDul(x, y) unsigned long x; unsigned y + +/* little endian, 64 bits */ +#elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__) || \ + (defined(__aarch64__) && defined(__AARCH64EL__)) || \ + (defined(__riscv) && __riscv_xlen == 64) || \ + (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 8) +#define PADDED(x, y) x, y +#define PADDEDptr(x, y) x +#define PADDEDul(x, y) unsigned long x + +/* big endian, 64 bits */ +#elif defined(__powerpc64__) || defined(__s390x__) || \ + (defined(__sparc__) && defined(__arch64__)) || \ + (defined(__aarch64__) && defined(__AARCH64EB__)) || \ + (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ && __SIZEOF_LONG__ == 8) +#define PADDED(x, y) unsigned y; x +#define PADDEDptr(x,y) x +#define PADDEDul(x, y) unsigned long x + +/* big endian, 32 bits */ +#elif defined(__PPC__) || defined(__s390__) || \ + (defined(__arm__) && defined(__ARMEB__)) || \ + defined(__sparc__) || defined(__MIPSEB__) || defined(__m68k__) || \ + defined(__hppa__) || defined(__frv__) || defined(__avr32__) || \ + (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ && __SIZEOF_LONG__ == 4) +#define PADDED(x, y) unsigned y; x +#define PADDEDptr(x, y) unsigned y; x +#define PADDEDul(x, y) unsigned y; unsigned long x + +#else +#error endian? +#endif + +struct io_iocb_poll { + PADDED(int events, __pad1); +}; /* result code is the set of result flags or -'ve errno */ + +struct io_iocb_sockaddr { + struct sockaddr *addr; + int len; +}; /* result code is the length of the sockaddr, or -'ve errno */ + +struct io_iocb_common { + PADDEDptr(void *buf, __pad1); + PADDEDul(nbytes, __pad2); + long long offset; + long long __pad3; + unsigned flags; + unsigned resfd; +}; /* result code is the amount read or -'ve errno */ + +struct io_iocb_vector { + const struct iovec *vec; + int nr; + long long offset; +}; /* result code is the amount read or -'ve errno */ + +struct iocb { + PADDEDptr(void *data, __pad1); /* Return in the io completion event */ + /* key: For use in identifying io requests */ + /* aio_rw_flags: RWF_* flags (such as RWF_NOWAIT) */ + PADDED(unsigned key, aio_rw_flags); + + short aio_lio_opcode; + short aio_reqprio; + int aio_fildes; + + union { + struct io_iocb_common c; + struct io_iocb_vector v; + struct io_iocb_poll poll; + struct io_iocb_sockaddr saddr; + } u; +}; + +struct io_event { + PADDEDptr(void *data, __pad1); + PADDEDptr(struct iocb *obj, __pad2); + PADDEDul(res, __pad3); + PADDEDul(res2, __pad4); +}; + +#undef PADDED +#undef PADDEDptr +#undef PADDEDul + +typedef void (*io_callback_t)(io_context_t ctx, struct iocb *iocb, long res, long res2); + +/* library wrappers */ +extern int io_queue_init(int maxevents, io_context_t *ctxp); +/*extern int io_queue_grow(io_context_t ctx, int new_maxevents);*/ +extern int io_queue_release(io_context_t ctx); +/*extern int io_queue_wait(io_context_t ctx, struct timespec *timeout);*/ +extern int io_queue_run(io_context_t ctx); + +/* Actual syscalls */ +extern int io_setup(int maxevents, io_context_t *ctxp); +extern int io_destroy(io_context_t ctx); +extern int io_submit(io_context_t ctx, long nr, struct iocb *ios[]); +extern int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); +extern int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout); +extern int io_pgetevents(io_context_t ctx_id, long min_nr, long nr, + struct io_event *events, struct timespec *timeout, + sigset_t *sigmask); + + +static inline void io_set_callback(struct iocb *iocb, io_callback_t cb) +{ + iocb->data = (void *)cb; +} + +static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PREAD; + iocb->aio_reqprio = 0; + iocb->u.c.buf = buf; + iocb->u.c.nbytes = count; + iocb->u.c.offset = offset; +} + +static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PWRITE; + iocb->aio_reqprio = 0; + iocb->u.c.buf = buf; + iocb->u.c.nbytes = count; + iocb->u.c.offset = offset; +} + +static inline void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PREADV; + iocb->aio_reqprio = 0; + iocb->u.c.buf = (void *)iov; + iocb->u.c.nbytes = iovcnt; + iocb->u.c.offset = offset; +} + +static inline void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PWRITEV; + iocb->aio_reqprio = 0; + iocb->u.c.buf = (void *)iov; + iocb->u.c.nbytes = iovcnt; + iocb->u.c.offset = offset; +} + +static inline void io_prep_preadv2(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset, int flags) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PREADV; + iocb->aio_reqprio = 0; + iocb->aio_rw_flags = flags; + iocb->u.c.buf = (void *)iov; + iocb->u.c.nbytes = iovcnt; + iocb->u.c.offset = offset; +} + +static inline void io_prep_pwritev2(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset, int flags) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_PWRITEV; + iocb->aio_reqprio = 0; + iocb->aio_rw_flags = flags; + iocb->u.c.buf = (void *)iov; + iocb->u.c.nbytes = iovcnt; + iocb->u.c.offset = offset; +} + +static inline void io_prep_poll(struct iocb *iocb, int fd, int events) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_POLL; + iocb->aio_reqprio = 0; + iocb->u.poll.events = events; +} + +static inline int io_poll(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd, int events) +{ + io_prep_poll(iocb, fd, events); + io_set_callback(iocb, cb); + return io_submit(ctx, 1, &iocb); +} + +static inline void io_prep_fsync(struct iocb *iocb, int fd) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_FSYNC; + iocb->aio_reqprio = 0; +} + +static inline int io_fsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) +{ + io_prep_fsync(iocb, fd); + io_set_callback(iocb, cb); + return io_submit(ctx, 1, &iocb); +} + +static inline void io_prep_fdsync(struct iocb *iocb, int fd) +{ + memset(iocb, 0, sizeof(*iocb)); + iocb->aio_fildes = fd; + iocb->aio_lio_opcode = IO_CMD_FDSYNC; + iocb->aio_reqprio = 0; +} + +static inline int io_fdsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) +{ + io_prep_fdsync(iocb, fd); + io_set_callback(iocb, cb); + return io_submit(ctx, 1, &iocb); +} + +static inline void io_set_eventfd(struct iocb *iocb, int eventfd) +{ + iocb->u.c.flags |= (1 << 0) /* IOCB_FLAG_RESFD */; + iocb->u.c.resfd = eventfd; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBAIO_H */ diff --git a/libaio-futures/libaio/libaio.map b/libaio-futures/libaio/libaio.map new file mode 100644 index 000000000000..ec9d13b5f2c8 --- /dev/null +++ b/libaio-futures/libaio/libaio.map @@ -0,0 +1,27 @@ +LIBAIO_0.1 { + global: + io_queue_init; + io_queue_run; + io_queue_wait; + io_queue_release; + io_cancel; + io_submit; + io_getevents; + local: + *; + +}; + +LIBAIO_0.4 { + global: + io_setup; + io_destroy; + io_cancel; + io_getevents; + io_queue_wait; +} LIBAIO_0.1; + +LIBAIO_0.5 { + global: + io_pgetevents; +} LIBAIO_0.4; diff --git a/libaio-futures/libaio/raw_syscall.c b/libaio-futures/libaio/raw_syscall.c new file mode 100644 index 000000000000..c3fe4b8deb93 --- /dev/null +++ b/libaio-futures/libaio/raw_syscall.c @@ -0,0 +1,19 @@ +#include "syscall.h" + +#if defined(__ia64__) +/* based on code from glibc by Jes Sorensen */ +__asm__(".text\n" + ".globl __ia64_aio_raw_syscall\n" + ".proc __ia64_aio_raw_syscall\n" + "__ia64_aio_raw_syscall:\n" + "alloc r2=ar.pfs,1,0,8,0\n" + "mov r15=r32\n" + "break 0x100000\n" + ";;" + "br.ret.sptk.few b0\n" + ".size __ia64_aio_raw_syscall, . - __ia64_aio_raw_syscall\n" + ".endp __ia64_aio_raw_syscall" +); +#endif + +; diff --git a/libaio-futures/libaio/syscall-alpha.h b/libaio-futures/libaio/syscall-alpha.h new file mode 100644 index 000000000000..0aa4d3d51b67 --- /dev/null +++ b/libaio-futures/libaio/syscall-alpha.h @@ -0,0 +1,5 @@ +#define __NR_io_setup 398 +#define __NR_io_destroy 399 +#define __NR_io_getevents 400 +#define __NR_io_submit 401 +#define __NR_io_cancel 402 diff --git a/libaio-futures/libaio/syscall-arm.h b/libaio-futures/libaio/syscall-arm.h new file mode 100644 index 000000000000..556852bbbf36 --- /dev/null +++ b/libaio-futures/libaio/syscall-arm.h @@ -0,0 +1,26 @@ +/* + * linux/include/asm-arm/unistd.h + * + * Copyright (C) 2001-2005 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Please forward _all_ changes to this file to rmk@arm.linux.org.uk, + * no matter what the change is. Thanks! + */ + +#define __NR_OABI_SYSCALL_BASE 0x900000 + +#if defined(__thumb__) || defined(__ARM_EABI__) +#define __NR_SYSCALL_BASE 0 +#else +#define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE +#endif + +#define __NR_io_setup (__NR_SYSCALL_BASE+243) +#define __NR_io_destroy (__NR_SYSCALL_BASE+244) +#define __NR_io_getevents (__NR_SYSCALL_BASE+245) +#define __NR_io_submit (__NR_SYSCALL_BASE+246) +#define __NR_io_cancel (__NR_SYSCALL_BASE+247) diff --git a/libaio-futures/libaio/syscall-generic.h b/libaio-futures/libaio/syscall-generic.h new file mode 100644 index 000000000000..b217b531be16 --- /dev/null +++ b/libaio-futures/libaio/syscall-generic.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * This is based on the include/uapi/asm-generic/unistd.h header file + * in the kernel, which is a generic syscall schema for new architectures. + */ + +#define __NR_io_setup 0 +#define __NR_io_destroy 1 +#define __NR_io_submit 2 +#define __NR_io_cancel 3 +#define __NR_io_getevents 4 diff --git a/libaio-futures/libaio/syscall-i386.h b/libaio-futures/libaio/syscall-i386.h new file mode 100644 index 000000000000..bc66bb176f88 --- /dev/null +++ b/libaio-futures/libaio/syscall-i386.h @@ -0,0 +1,6 @@ +#define __NR_io_setup 245 +#define __NR_io_destroy 246 +#define __NR_io_getevents 247 +#define __NR_io_submit 248 +#define __NR_io_cancel 249 +#define __NR_io_pgetevents 385 diff --git a/libaio-futures/libaio/syscall-ia64.h b/libaio-futures/libaio/syscall-ia64.h new file mode 100644 index 000000000000..a21e93b9883f --- /dev/null +++ b/libaio-futures/libaio/syscall-ia64.h @@ -0,0 +1,5 @@ +#define __NR_io_setup 1238 +#define __NR_io_destroy 1239 +#define __NR_io_getevents 1240 +#define __NR_io_submit 1241 +#define __NR_io_cancel 1242 diff --git a/libaio-futures/libaio/syscall-ppc.h b/libaio-futures/libaio/syscall-ppc.h new file mode 100644 index 000000000000..dcfb11866392 --- /dev/null +++ b/libaio-futures/libaio/syscall-ppc.h @@ -0,0 +1,5 @@ +#define __NR_io_setup 227 +#define __NR_io_destroy 228 +#define __NR_io_getevents 229 +#define __NR_io_submit 230 +#define __NR_io_cancel 231 diff --git a/libaio-futures/libaio/syscall-s390.h b/libaio-futures/libaio/syscall-s390.h new file mode 100644 index 000000000000..f0805f5fb03b --- /dev/null +++ b/libaio-futures/libaio/syscall-s390.h @@ -0,0 +1,5 @@ +#define __NR_io_setup 243 +#define __NR_io_destroy 244 +#define __NR_io_getevents 245 +#define __NR_io_submit 246 +#define __NR_io_cancel 247 diff --git a/libaio-futures/libaio/syscall-sparc.h b/libaio-futures/libaio/syscall-sparc.h new file mode 100644 index 000000000000..3e63e92577f4 --- /dev/null +++ b/libaio-futures/libaio/syscall-sparc.h @@ -0,0 +1,5 @@ +#define __NR_io_setup 268 +#define __NR_io_destroy 269 +#define __NR_io_submit 270 +#define __NR_io_cancel 271 +#define __NR_io_getevents 272 diff --git a/libaio-futures/libaio/syscall-x86_64.h b/libaio-futures/libaio/syscall-x86_64.h new file mode 100644 index 000000000000..0eccef342e2a --- /dev/null +++ b/libaio-futures/libaio/syscall-x86_64.h @@ -0,0 +1,6 @@ +#define __NR_io_setup 206 +#define __NR_io_destroy 207 +#define __NR_io_getevents 208 +#define __NR_io_submit 209 +#define __NR_io_cancel 210 +#define __NR_io_pgetevents 333 diff --git a/libaio-futures/libaio/syscall.h b/libaio-futures/libaio/syscall.h new file mode 100644 index 000000000000..74bd6fb8f16b --- /dev/null +++ b/libaio-futures/libaio/syscall.h @@ -0,0 +1,73 @@ +#include +#include +#include + +#define _SYMSTR(str) #str +#define SYMSTR(str) _SYMSTR(str) + +#define SYMVER(compat_sym, orig_sym, ver_sym) \ + //__asm__(".symver " SYMSTR(compat_sym) "," SYMSTR(orig_sym) "@LIBAIO_" SYMSTR(ver_sym)); + +#define DEFSYMVER(compat_sym, orig_sym, ver_sym) \ + //__asm__(".symver " SYMSTR(compat_sym) "," SYMSTR(orig_sym) "@@LIBAIO_" SYMSTR(ver_sym)); + +#if defined(__i386__) +#include "syscall-i386.h" +#elif defined(__x86_64__) +#include "syscall-x86_64.h" +#elif defined(__ia64__) +#include "syscall-ia64.h" +#elif defined(__PPC__) +#include "syscall-ppc.h" +#elif defined(__s390__) +#include "syscall-s390.h" +#elif defined(__alpha__) +#include "syscall-alpha.h" +#elif defined(__arm__) +#include "syscall-arm.h" +#elif defined(__sparc__) +#include "syscall-sparc.h" +#elif defined(__aarch64__) || defined(__riscv) +#include "syscall-generic.h" +#else +#warning "using system call numbers from sys/syscall.h" +#endif + +#define _body_io_syscall(sname, args...) \ +{ \ + int ret, saved_errno; \ + saved_errno = errno; \ + ret= syscall(__NR_##sname, ## args); \ + if (ret < 0) { \ + ret = -errno; \ + errno = saved_errno; \ + } \ + return ret; \ +} + +#define io_syscall1(type,fname,sname,type1,arg1) \ +type fname(type1 arg1) \ +_body_io_syscall(sname, (long)arg1) + +#define io_syscall2(type,fname,sname,type1,arg1,type2,arg2) \ +type fname(type1 arg1,type2 arg2) \ +_body_io_syscall(sname, (long)arg1, (long)arg2) + +#define io_syscall3(type,fname,sname,type1,arg1,type2,arg2,type3,arg3) \ +type fname(type1 arg1,type2 arg2,type3 arg3) \ +_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3) + +#define io_syscall4(type,fname,sname,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ +type fname (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \ +_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4) + +#define io_syscall5(type,fname,sname,type1,arg1,type2,arg2,type3,arg3,type4,arg4, type5,arg5) \ +type fname (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \ +_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4, (long)arg5) + +#define io_syscall6(type,fname,sname,type1,arg1,type2,arg2,type3,arg3, \ + type4,arg4,type5,arg5,type6,arg6) \ +type fname (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5, \ + type6 arg6) \ +_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4, \ + (long)arg5, (long)arg6) diff --git a/libaio-futures/libaio/vsys_def.h b/libaio-futures/libaio/vsys_def.h new file mode 100644 index 000000000000..13d032e330b2 --- /dev/null +++ b/libaio-futures/libaio/vsys_def.h @@ -0,0 +1,24 @@ +/* libaio Linux async I/O interface + Copyright 2002 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +extern int vsys_io_setup(unsigned nr_reqs, io_context_t *ctxp); +extern int vsys_io_destroy(io_context_t ctx); +extern int vsys_io_submit(io_context_t ctx, long nr, struct iocb *iocbs[]); +extern int vsys_io_cancel(io_context_t ctx, struct iocb *iocb); +extern int vsys_io_wait(io_context_t ctx, struct iocb *iocb, const struct timespec *when); +extern int vsys_io_getevents(io_context_t ctx_id, long nr, struct io_event *events, const struct timespec *timeout); + diff --git a/libaio-futures/src/abi.rs b/libaio-futures/src/abi.rs new file mode 100644 index 000000000000..347a6065d350 --- /dev/null +++ b/libaio-futures/src/abi.rs @@ -0,0 +1,111 @@ +// libaio ABI adapted from: +// https://raw.githubusercontent.com/jsgf/libaio-rust/master/src/aioabi.rs +#![allow(dead_code)] + +pub use libc::timespec; +use libc::{c_int, c_long, size_t}; +use std::default::Default; +use std::mem::zeroed; + +#[repr(C)] +pub enum IOCmd { + PRead = 0, + PWrite = 1, + FSync = 2, + FdSync = 3, + // 4 was the experimental IOCB_CMD_PREADX, + Poll = 5, + Noop = 6, + PReadV = 7, + PWriteV = 8, +} + +pub const IOCB_FLAG_RESFD: u32 = 1 << 0; +pub const IOCB_FLAG_IOPRIO: u32 = 1 << 1; + +// Taken from linux/include/linux/aio_abi.h +// This is a kernel ABI, so there should be no need to worry about it changing. +#[repr(C)] +pub struct IOCb { + pub aio_data: u64, // ends up in io_event.data + // NOTE: the order of aio_key and aio_rw_flags could be byte-order depedent + pub aio_key: u32, + pub aio_rw_flags: u32, + + pub aio_lio_opcode: u16, + pub aio_reqprio: u16, + pub aio_fildes: u32, + + pub aio_buf: u64, + pub aio_nbytes: u64, + pub aio_offset: u64, + + pub aio_reserved2: u64, + pub aio_flags: u32, + pub aio_resfd: u32, +} + +impl Default for IOCb { + fn default() -> IOCb { + IOCb { + aio_lio_opcode: IOCmd::Noop as u16, + aio_fildes: (-1_i32) as u32, + ..unsafe { zeroed() } + } + } +} + +#[derive(Clone)] +#[repr(C)] +pub struct IOEvent { + pub data: u64, + pub obj: u64, + pub res: i64, + pub res2: i64, +} + +impl Default for IOEvent { + fn default() -> IOEvent { + unsafe { zeroed() } + } +} + +pub enum IOContext {} +pub type IOContextPtr = *mut IOContext; + +#[repr(C)] +pub struct IOVector { + pub iov_base: *mut u8, + pub iov_len: size_t, +} + +#[link(name = "aio", kind = "static")] +extern "C" { + pub fn io_queue_init(maxevents: c_int, ctxp: *mut IOContextPtr) -> c_int; + pub fn io_queue_release(ctx: IOContextPtr) -> c_int; + pub fn io_queue_run(ctx: IOContextPtr) -> c_int; + pub fn io_setup(maxevents: c_int, ctxp: *mut IOContextPtr) -> c_int; + pub fn io_destroy(ctx: IOContextPtr) -> c_int; + pub fn io_submit(ctx: IOContextPtr, nr: c_long, ios: *mut *mut IOCb) -> c_int; + pub fn io_cancel(ctx: IOContextPtr, iocb: *mut IOCb, evt: *mut IOEvent) -> c_int; + pub fn io_getevents( + ctx_id: IOContextPtr, + min_nr: c_long, + nr: c_long, + events: *mut IOEvent, + timeout: *mut timespec, + ) -> c_int; + pub fn io_set_eventfd(iocb: *mut IOCb, eventfd: c_int); +} + +#[cfg(test)] +mod test { + use std::mem::size_of; + + #[test] + fn test_sizes() { + // Check against kernel ABI + assert!(size_of::() == 32); + assert!(size_of::() == 64); + } +} diff --git a/libaio-futures/src/lib.rs b/libaio-futures/src/lib.rs new file mode 100644 index 000000000000..3cafed3faeaf --- /dev/null +++ b/libaio-futures/src/lib.rs @@ -0,0 +1,596 @@ +//! Straightforward Linux AIO using Futures/async/await. +//! +//! # Example +//! +//! Use aiofut to schedule writes to a file: +//! +//! ```rust +//! use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; +//! use aiofut::AIOBuilder; +//! use std::os::unix::io::AsRawFd; +//! let mut aiomgr = AIOBuilder::default().build().unwrap(); +//! let file = std::fs::OpenOptions::new() +//! .read(true) +//! .write(true) +//! .create(true) +//! .truncate(true) +//! .open("test") +//! .unwrap(); +//! let fd = file.as_raw_fd(); +//! // keep all returned futures in a vector +//! let ws = vec![(0, "hello"), (5, "world"), (2, "xxxx")] +//! .into_iter() +//! .map(|(off, s)| aiomgr.write(fd, off, s.as_bytes().into(), None)) +//! .collect::>(); +//! // here we use futures::executor::LocalPool to poll all futures +//! let mut pool = LocalPool::new(); +//! let spawner = pool.spawner(); +//! for w in ws.into_iter() { +//! let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { +//! println!("wrote {} bytes", r.0.unwrap()); +//! }); +//! spawner.spawn_local(h).unwrap(); +//! } +//! pool.run(); +//! ``` + +mod abi; +use libc::time_t; +use parking_lot::Mutex; +use std::collections::{hash_map, HashMap}; +use std::os::raw::c_long; +use std::os::unix::io::RawFd; +use std::pin::Pin; +use std::sync::{ + atomic::{AtomicPtr, AtomicUsize, Ordering}, + Arc, +}; + +const LIBAIO_EAGAIN: libc::c_int = -libc::EAGAIN; +const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; +const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; + +#[derive(Debug)] +pub enum Error { + MaxEventsTooLarge, + LowKernelRes, + NotSupported, + OtherError, +} + +// NOTE: I assume it io_context_t is thread-safe, no? +struct AIOContext(abi::IOContextPtr); +unsafe impl Sync for AIOContext {} +unsafe impl Send for AIOContext {} + +impl std::ops::Deref for AIOContext { + type Target = abi::IOContextPtr; + fn deref(&self) -> &abi::IOContextPtr { + &self.0 + } +} + +impl AIOContext { + fn new(maxevents: u32) -> Result { + let mut ctx = std::ptr::null_mut(); + unsafe { + match abi::io_setup(maxevents as libc::c_int, &mut ctx) { + 0 => Ok(()), + LIBAIO_EAGAIN => Err(Error::MaxEventsTooLarge), + LIBAIO_ENOMEM => Err(Error::LowKernelRes), + LIBAIO_ENOSYS => Err(Error::NotSupported), + _ => Err(Error::OtherError), + } + .and_then(|_| Ok(AIOContext(ctx))) + } + } +} + +impl Drop for AIOContext { + fn drop(&mut self) { + unsafe { + assert_eq!(abi::io_destroy(self.0), 0); + } + } +} + +/// Represent the necessary data for an AIO operation. Memory-safe when moved. +pub struct AIO { + // hold the buffer used by iocb + data: Option>, + iocb: AtomicPtr, + id: u64, +} + +impl AIO { + fn new( + id: u64, + fd: RawFd, + off: u64, + data: Box<[u8]>, + priority: u16, + flags: u32, + opcode: abi::IOCmd, + ) -> Self { + let mut iocb = Box::new(abi::IOCb::default()); + iocb.aio_fildes = fd as u32; + iocb.aio_lio_opcode = opcode as u16; + iocb.aio_reqprio = priority; + iocb.aio_buf = data.as_ptr() as u64; + iocb.aio_nbytes = data.len() as u64; + iocb.aio_offset = off; + iocb.aio_flags = flags; + iocb.aio_data = id; + let iocb = AtomicPtr::new(Box::into_raw(iocb)); + let data = Some(data); + AIO { iocb, id, data } + } +} + +impl Drop for AIO { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.iocb.load(Ordering::Acquire))); + } + } +} + +/// The result of an AIO operation: the number of bytes written on success, +/// or the errno on failure. +pub type AIOResult = (Result, Box<[u8]>); + +/// Represents a scheduled (future) asynchronous I/O operation, which gets executed (resolved) +/// automatically. +pub struct AIOFuture { + notifier: Arc, + aio_id: u64, +} + +impl AIOFuture { + pub fn get_id(&self) -> u64 { + self.aio_id + } +} + +impl std::future::Future for AIOFuture { + type Output = AIOResult; + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> std::task::Poll { + if let Some(ret) = self.notifier.poll(self.aio_id, cx.waker()) { + std::task::Poll::Ready(ret) + } else { + std::task::Poll::Pending + } + } +} + +impl Drop for AIOFuture { + fn drop(&mut self) { + self.notifier.dropped(self.aio_id) + } +} + +enum AIOState { + FutureInit(AIO, bool), + FuturePending(AIO, std::task::Waker, bool), + FutureDone(AIOResult), +} + +/// The state machine for finished AIO operations and wakes up the futures. +pub struct AIONotifier { + waiting: Mutex>, + npending: AtomicUsize, + io_ctx: AIOContext, + #[cfg(feature = "emulated-failure")] + emul_fail: Option, +} + +impl AIONotifier { + fn register_notify(&self, id: u64, state: AIOState) { + let mut waiting = self.waiting.lock(); + assert!(waiting.insert(id, state).is_none()); + } + + fn dropped(&self, id: u64) { + let mut waiting = self.waiting.lock(); + match waiting.entry(id) { + hash_map::Entry::Occupied(mut e) => match e.get_mut() { + AIOState::FutureInit(_, dropped) => *dropped = true, + AIOState::FuturePending(_, _, dropped) => *dropped = true, + AIOState::FutureDone(_) => { + e.remove(); + } + }, + _ => (), + } + } + + fn poll(&self, id: u64, waker: &std::task::Waker) -> Option { + let mut waiting = self.waiting.lock(); + match waiting.entry(id) { + hash_map::Entry::Occupied(e) => { + let v = e.remove(); + match v { + AIOState::FutureInit(aio, _) => { + waiting.insert(id, AIOState::FuturePending(aio, waker.clone(), false)); + None + } + AIOState::FuturePending(aio, waker, dropped) => { + waiting.insert(id, AIOState::FuturePending(aio, waker, dropped)); + None + } + AIOState::FutureDone(res) => Some(res), + } + } + _ => unreachable!(), + } + } + + fn finish(&self, id: u64, res: i64) { + let mut w = self.waiting.lock(); + self.npending.fetch_sub(1, Ordering::Relaxed); + match w.entry(id) { + hash_map::Entry::Occupied(e) => match e.remove() { + AIOState::FutureInit(mut aio, dropped) => { + if !dropped { + let data = aio.data.take().unwrap(); + w.insert( + id, + AIOState::FutureDone(if res >= 0 { + (Ok(res as usize), data) + } else { + (Err(-res as i32), data) + }), + ); + } + } + AIOState::FuturePending(mut aio, waker, dropped) => { + if !dropped { + let data = aio.data.take().unwrap(); + w.insert( + id, + AIOState::FutureDone(if res >= 0 { + (Ok(res as usize), data) + } else { + (Err(-res as i32), data) + }), + ); + waker.wake(); + } + } + AIOState::FutureDone(ret) => { + w.insert(id, AIOState::FutureDone(ret)); + } + }, + _ => unreachable!(), + } + } +} + +pub struct AIOBuilder { + max_events: u32, + max_nwait: u16, + max_nbatched: usize, + timeout: Option, + #[cfg(feature = "emulated-failure")] + emul_fail: Option, +} + +impl Default for AIOBuilder { + fn default() -> Self { + AIOBuilder { + max_events: 128, + max_nwait: 128, + max_nbatched: 128, + timeout: None, + #[cfg(feature = "emulated-failure")] + emul_fail: None, + } + } +} + +impl AIOBuilder { + /// Maximum concurrent async IO operations. + pub fn max_events(&mut self, v: u32) -> &mut Self { + self.max_events = v; + self + } + + /// Maximum complete IOs per poll. + pub fn max_nwait(&mut self, v: u16) -> &mut Self { + self.max_nwait = v; + self + } + + /// Maximum number of IOs per submission. + pub fn max_nbatched(&mut self, v: usize) -> &mut Self { + self.max_nbatched = v; + self + } + + /// Timeout for a polling iteration (default is None). + pub fn timeout(&mut self, sec: u32) -> &mut Self { + self.timeout = Some(sec); + self + } + + #[cfg(feature = "emulated-failure")] + pub fn emulated_failure(&mut self, ef: EmulatedFailureShared) -> &mut Self { + self.emul_fail = Some(ef); + self + } + + /// Build an AIOManager object based on the configuration (and auto-start the background IO + /// scheduling thread). + pub fn build(&mut self) -> Result { + let (scheduler_in, scheduler_out) = new_batch_scheduler(self.max_nbatched); + let (exit_s, exit_r) = crossbeam_channel::bounded(0); + + let notifier = Arc::new(AIONotifier { + io_ctx: AIOContext::new(self.max_events)?, + waiting: Mutex::new(HashMap::new()), + npending: AtomicUsize::new(0), + #[cfg(feature = "emulated-failure")] + emul_fail: self.emul_fail.as_ref().map(|ef| ef.clone()), + }); + let mut aiomgr = AIOManager { + notifier, + listener: None, + scheduler_in, + exit_s, + }; + aiomgr.start(scheduler_out, exit_r, self.max_nwait, self.timeout)?; + Ok(aiomgr) + } +} + +pub trait EmulatedFailure: Send { + fn tick(&mut self) -> Option; +} + +pub type EmulatedFailureShared = Arc>; + +/// Manager all AIOs. +pub struct AIOManager { + notifier: Arc, + scheduler_in: AIOBatchSchedulerIn, + listener: Option>, + exit_s: crossbeam_channel::Sender<()>, +} + +impl AIOManager { + fn start( + &mut self, + mut scheduler_out: AIOBatchSchedulerOut, + exit_r: crossbeam_channel::Receiver<()>, + max_nwait: u16, + timeout: Option, + ) -> Result<(), Error> { + let n = self.notifier.clone(); + self.listener = Some(std::thread::spawn(move || { + let mut timespec = timeout.and_then(|sec: u32| { + Some(libc::timespec { + tv_sec: sec as time_t, + tv_nsec: 0, + }) + }); + let mut ongoing = 0; + loop { + // try to quiesce + if ongoing == 0 && scheduler_out.is_empty() { + let mut sel = crossbeam_channel::Select::new(); + sel.recv(&exit_r); + sel.recv(&scheduler_out.get_receiver()); + if sel.ready() == 0 { + exit_r.recv().unwrap(); + break; + } + } + // submit as many aios as possible + loop { + let nacc = scheduler_out.submit(&n); + ongoing += nacc; + if nacc == 0 { + break; + } + } + // no need to wait if there is no progress + if ongoing == 0 { + continue; + } + // then block on any finishing aios + let mut events = vec![abi::IOEvent::default(); max_nwait as usize]; + let ret = unsafe { + abi::io_getevents( + *n.io_ctx, + 1, + max_nwait as c_long, + events.as_mut_ptr(), + timespec + .as_mut() + .and_then(|t| Some(t as *mut libc::timespec)) + .unwrap_or(std::ptr::null_mut()), + ) + }; + // TODO: AIO fatal error handling + // avoid empty slice + if ret == 0 { + continue; + } + assert!(ret > 0); + ongoing -= ret as usize; + for ev in events[..ret as usize].iter() { + #[cfg(not(feature = "emulated-failure"))] + n.finish(ev.data as u64, ev.res); + #[cfg(feature = "emulated-failure")] + { + let mut res = ev.res; + if let Some(emul_fail) = n.emul_fail.as_ref() { + let mut ef = emul_fail.lock(); + if let Some(e) = ef.tick() { + res = e + } + } + n.finish(ev.data as u64, res); + } + } + } + })); + Ok(()) + } + + pub fn read(&self, fd: RawFd, offset: u64, length: usize, priority: Option) -> AIOFuture { + let priority = priority.unwrap_or(0); + let mut data = Vec::new(); + data.resize(length, 0); + let data = data.into_boxed_slice(); + let aio = AIO::new( + self.scheduler_in.next_id(), + fd, + offset, + data, + priority, + 0, + abi::IOCmd::PRead, + ); + self.scheduler_in.schedule(aio, &self.notifier) + } + + pub fn write( + &self, + fd: RawFd, + offset: u64, + data: Box<[u8]>, + priority: Option, + ) -> AIOFuture { + let priority = priority.unwrap_or(0); + let aio = AIO::new( + self.scheduler_in.next_id(), + fd, + offset, + data, + priority, + 0, + abi::IOCmd::PWrite, + ); + self.scheduler_in.schedule(aio, &self.notifier) + } + + /// Get a copy of the current data in the buffer. + pub fn copy_data(&self, aio_id: u64) -> Option> { + let w = self.notifier.waiting.lock(); + w.get(&aio_id).and_then(|state| { + Some( + match state { + AIOState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), + AIOState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), + AIOState::FutureDone(res) => &res.1, + } + .to_vec(), + ) + }) + } + + /// Get the number of pending AIOs (approximation). + pub fn get_npending(&self) -> usize { + self.notifier.npending.load(Ordering::Relaxed) + } +} + +impl Drop for AIOManager { + fn drop(&mut self) { + self.exit_s.send(()).unwrap(); + self.listener.take().unwrap().join().unwrap(); + } +} + +pub struct AIOBatchSchedulerIn { + queue_in: crossbeam_channel::Sender>, + last_id: std::cell::Cell, +} + +pub struct AIOBatchSchedulerOut { + queue_out: crossbeam_channel::Receiver>, + max_nbatched: usize, + leftover: Vec>, +} + +impl AIOBatchSchedulerIn { + fn schedule(&self, aio: AIO, notifier: &Arc) -> AIOFuture { + let fut = AIOFuture { + notifier: notifier.clone(), + aio_id: aio.id, + }; + let iocb = aio.iocb.load(Ordering::Acquire); + notifier.register_notify(aio.id, AIOState::FutureInit(aio, false)); + self.queue_in.send(AtomicPtr::new(iocb)).unwrap(); + notifier.npending.fetch_add(1, Ordering::Relaxed); + fut + } + + fn next_id(&self) -> u64 { + let id = self.last_id.get(); + self.last_id.set(id.wrapping_add(1)); + id + } +} + +impl AIOBatchSchedulerOut { + fn get_receiver(&self) -> &crossbeam_channel::Receiver> { + &self.queue_out + } + fn is_empty(&self) -> bool { + self.leftover.len() == 0 + } + fn submit(&mut self, notifier: &AIONotifier) -> usize { + let mut quota = self.max_nbatched; + let mut pending = self + .leftover + .iter() + .map(|p| p.load(Ordering::Acquire)) + .collect::>(); + if pending.len() < quota { + quota -= pending.len(); + while let Ok(iocb) = self.queue_out.try_recv() { + pending.push(iocb.load(Ordering::Acquire)); + quota -= 1; + if quota == 0 { + break; + } + } + } + if pending.len() == 0 { + return 0; + } + let mut ret = unsafe { + abi::io_submit( + *notifier.io_ctx, + pending.len() as c_long, + (&mut pending).as_mut_ptr(), + ) + }; + if ret < 0 && ret == LIBAIO_EAGAIN { + ret = 0 + } + let nacc = ret as usize; + self.leftover = (&pending[nacc..]) + .iter() + .map(|p| AtomicPtr::new(*p)) + .collect::>(); + nacc + } +} + +/// Create the scheduler that submits AIOs in batches. +fn new_batch_scheduler(max_nbatched: usize) -> (AIOBatchSchedulerIn, AIOBatchSchedulerOut) { + let (queue_in, queue_out) = crossbeam_channel::unbounded(); + let bin = AIOBatchSchedulerIn { + queue_in, + last_id: std::cell::Cell::new(0), + }; + let bout = AIOBatchSchedulerOut { + queue_out, + max_nbatched, + leftover: Vec::new(), + }; + (bin, bout) +} diff --git a/libaio-futures/tests/simple_test.rs b/libaio-futures/tests/simple_test.rs new file mode 100644 index 000000000000..e241b2c0fabc --- /dev/null +++ b/libaio-futures/tests/simple_test.rs @@ -0,0 +1,63 @@ +use aiofut::AIOBuilder; +use futures::executor::LocalPool; +use futures::future::FutureExt; +use futures::task::LocalSpawnExt; +use std::os::unix::io::AsRawFd; + +#[test] +fn simple1() { + let aiomgr = AIOBuilder::default().max_events(100).build().unwrap(); + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("test") + .unwrap(); + let fd = file.as_raw_fd(); + let ws = vec![(0, "hello"), (5, "world"), (2, "xxxx")] + .into_iter() + .map(|(off, s)| aiomgr.write(fd, off, s.as_bytes().into(), None)) + .collect::>(); + let mut pool = LocalPool::new(); + let spawner = pool.spawner(); + for w in ws.into_iter() { + let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { + println!("wrote {} bytes", r.0.unwrap()); + }); + spawner.spawn_local(h).unwrap(); + } + pool.run(); +} + +#[test] +fn simple2() { + let aiomgr = AIOBuilder::default().build().unwrap(); + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("test2") + .unwrap(); + let fd = file.as_raw_fd(); + let ws = (0..4000) + .into_iter() + .map(|i| { + let off = i * 128; + let s = char::from((97 + i % 26) as u8) + .to_string() + .repeat((i + 1) as usize); + aiomgr.write(fd, off as u64, s.as_bytes().into(), None) + }) + .collect::>(); + let mut pool = LocalPool::new(); + let spawner = pool.spawner(); + for w in ws.into_iter() { + let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { + println!("wrote {} bytes", r.0.unwrap()); + }); + spawner.spawn_local(h).unwrap(); + } + pool.run(); +} diff --git a/shale/Cargo.toml b/shale/Cargo.toml new file mode 100644 index 000000000000..f9a991b899c3 --- /dev/null +++ b/shale/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "shale" +version = "0.1.8" +edition = "2021" +description = "Useful abstraction and light-weight implemenation for a key-value store." +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex = "0.4.3" +lru = "0.8.1" diff --git a/shale/LICENSE b/shale/LICENSE new file mode 100644 index 000000000000..81a1d4cf1dfb --- /dev/null +++ b/shale/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019 Maofan (Ted) Yin +Copyright (c) 2019 Cornell University + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shale/src/block.rs b/shale/src/block.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/shale/src/block.rs @@ -0,0 +1 @@ + diff --git a/shale/src/compact.rs b/shale/src/compact.rs new file mode 100644 index 000000000000..187655a0baf3 --- /dev/null +++ b/shale/src/compact.rs @@ -0,0 +1,594 @@ +use super::{MemStore, MummyItem, MummyObj, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore}; +use std::cell::UnsafeCell; +use std::io::{Cursor, Write}; +use std::rc::Rc; + +pub struct CompactHeader { + payload_size: u64, + is_freed: bool, + desc_addr: ObjPtr, +} + +impl CompactHeader { + #![allow(dead_code)] + + pub const MSIZE: u64 = 17; + pub fn is_freed(&self) -> bool { + self.is_freed + } + + pub fn payload_size(&self) -> u64 { + self.payload_size + } +} + +impl MummyItem for CompactHeader { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + let payload_size = u64::from_le_bytes(raw[..8].try_into().unwrap()); + let is_freed = if raw[8] == 0 { false } else { true }; + let desc_addr = u64::from_le_bytes(raw[9..17].try_into().unwrap()); + Ok(Self { + payload_size, + is_freed, + desc_addr: ObjPtr::new(desc_addr), + }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = Cursor::new(to); + cur.write_all(&self.payload_size.to_le_bytes()).unwrap(); + cur.write_all(&[if self.is_freed { 1 } else { 0 }]).unwrap(); + cur.write_all(&self.desc_addr.addr().to_le_bytes()).unwrap(); + } +} + +struct CompactFooter { + payload_size: u64, +} + +impl CompactFooter { + const MSIZE: u64 = 8; +} + +impl MummyItem for CompactFooter { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + let payload_size = u64::from_le_bytes(raw.deref().try_into().unwrap()); + Ok(Self { payload_size }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + Cursor::new(to) + .write_all(&self.payload_size.to_le_bytes()) + .unwrap(); + } +} + +#[derive(Clone, Copy)] +struct CompactDescriptor { + payload_size: u64, + haddr: u64, // pointer to the payload of freed space +} + +impl CompactDescriptor { + const MSIZE: u64 = 16; +} + +impl MummyItem for CompactDescriptor { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + let payload_size = u64::from_le_bytes(raw[..8].try_into().unwrap()); + let haddr = u64::from_le_bytes(raw[8..].try_into().unwrap()); + Ok(Self { + payload_size, + haddr, + }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = Cursor::new(to); + cur.write_all(&self.payload_size.to_le_bytes()).unwrap(); + cur.write_all(&self.haddr.to_le_bytes()).unwrap(); + } +} + +pub struct CompactSpaceHeader { + meta_space_tail: u64, + compact_space_tail: u64, + base_addr: ObjPtr, + alloc_addr: ObjPtr, +} + +struct CompactSpaceHeaderSliced { + meta_space_tail: Obj, + compact_space_tail: Obj, + base_addr: Obj>, + alloc_addr: Obj>, +} + +impl CompactSpaceHeaderSliced { + fn flush_dirty(&mut self) { + self.meta_space_tail.flush_dirty(); + self.compact_space_tail.flush_dirty(); + self.base_addr.flush_dirty(); + self.alloc_addr.flush_dirty(); + } +} + +impl CompactSpaceHeader { + pub const MSIZE: u64 = 32; + + pub fn new(meta_base: u64, compact_base: u64) -> Self { + unsafe { + Self { + meta_space_tail: meta_base, + compact_space_tail: compact_base, + base_addr: ObjPtr::new_from_addr(meta_base), + alloc_addr: ObjPtr::new_from_addr(meta_base), + } + } + } + + fn into_fields(r: Obj) -> Result { + unsafe { + Ok(CompactSpaceHeaderSliced { + meta_space_tail: MummyObj::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, + compact_space_tail: MummyObj::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, + base_addr: MummyObj::slice(&r, 16, 8, r.base_addr)?, + alloc_addr: MummyObj::slice(&r, 24, 8, r.alloc_addr)?, + }) + } + } +} + +impl MummyItem for CompactSpaceHeader { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + let meta_space_tail = u64::from_le_bytes(raw[..8].try_into().unwrap()); + let compact_space_tail = u64::from_le_bytes(raw[8..16].try_into().unwrap()); + let base_addr = u64::from_le_bytes(raw[16..24].try_into().unwrap()); + let alloc_addr = u64::from_le_bytes(raw[24..].try_into().unwrap()); + unsafe { + Ok(Self { + meta_space_tail, + compact_space_tail, + base_addr: ObjPtr::new_from_addr(base_addr), + alloc_addr: ObjPtr::new_from_addr(alloc_addr), + }) + } + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = Cursor::new(to); + cur.write_all(&self.meta_space_tail.to_le_bytes()).unwrap(); + cur.write_all(&self.compact_space_tail.to_le_bytes()) + .unwrap(); + cur.write_all(&self.base_addr.addr().to_le_bytes()).unwrap(); + cur.write_all(&self.alloc_addr.addr().to_le_bytes()) + .unwrap(); + } +} + +struct ObjPtrField(ObjPtr); + +impl ObjPtrField { + const MSIZE: u64 = 8; +} + +impl MummyItem for ObjPtrField { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + unsafe { + Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( + raw.deref().try_into().unwrap(), + )))) + } + } + + fn dehydrate(&self, to: &mut [u8]) { + Cursor::new(to) + .write_all(&self.0.addr().to_le_bytes()) + .unwrap() + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } +} + +impl std::ops::Deref for ObjPtrField { + type Target = ObjPtr; + fn deref(&self) -> &ObjPtr { + &self.0 + } +} + +impl std::ops::DerefMut for ObjPtrField { + fn deref_mut(&mut self) -> &mut ObjPtr { + &mut self.0 + } +} + +struct U64Field(u64); + +impl U64Field { + const MSIZE: u64 = 8; +} + +impl MummyItem for U64Field { + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + Ok(Self(u64::from_le_bytes(raw.deref().try_into().unwrap()))) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + Cursor::new(to).write_all(&self.0.to_le_bytes()).unwrap() + } +} + +impl std::ops::Deref for U64Field { + type Target = u64; + fn deref(&self) -> &u64 { + &self.0 + } +} + +impl std::ops::DerefMut for U64Field { + fn deref_mut(&mut self) -> &mut u64 { + &mut self.0 + } +} + +struct CompactSpaceInner { + meta_space: Rc, + compact_space: Rc, + header: CompactSpaceHeaderSliced, + obj_cache: super::ObjCache, + alloc_max_walk: u64, + regn_nbit: u64, +} + +impl CompactSpaceInner { + fn get_descriptor( + &self, + ptr: ObjPtr, + ) -> Result, ShaleError> { + unsafe { MummyObj::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } + } + + fn get_data_ref( + &self, + ptr: ObjPtr, + len_limit: u64, + ) -> Result, ShaleError> { + unsafe { MummyObj::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) } + } + + fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { + self.get_data_ref::(ptr, CompactHeader::MSIZE) + } + + fn get_footer(&self, ptr: ObjPtr) -> Result, ShaleError> { + self.get_data_ref::(ptr, CompactFooter::MSIZE) + } + + fn del_desc(&mut self, desc_addr: ObjPtr) -> Result<(), ShaleError> { + let desc_size = CompactDescriptor::MSIZE; + debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); + self.header.meta_space_tail.write(|r| **r -= desc_size); + if desc_addr.addr != **self.header.meta_space_tail { + let desc_last = self + .get_descriptor(unsafe { ObjPtr::new_from_addr(**self.header.meta_space_tail) })?; + let mut desc = self.get_descriptor(unsafe { ObjPtr::new_from_addr(desc_addr.addr) })?; + desc.write(|r| *r = *desc_last); + let mut header = self.get_header(ObjPtr::new(desc.haddr))?; + header.write(|h| h.desc_addr = desc_addr); + } + Ok(()) + } + + fn new_desc(&mut self) -> Result, ShaleError> { + let addr = **self.header.meta_space_tail; + self.header + .meta_space_tail + .write(|r| **r += CompactDescriptor::MSIZE); + Ok(unsafe { ObjPtr::new_from_addr(addr) }) + } + + fn free(&mut self, addr: u64) -> Result<(), ShaleError> { + let hsize = CompactHeader::MSIZE; + let fsize = CompactFooter::MSIZE; + let regn_size = 1 << self.regn_nbit; + + let mut offset = addr - hsize; + let header_payload_size = { + let header = self.get_header(ObjPtr::new(offset))?; + assert!(!header.is_freed); + header.payload_size + }; + let mut h = offset; + let mut payload_size = header_payload_size; + + if offset & (regn_size - 1) > 0 { + // merge with lower data segment + offset -= fsize; + let (pheader_is_freed, pheader_payload_size, pheader_desc_addr) = { + let pfooter = self.get_footer(ObjPtr::new(offset))?; + offset -= pfooter.payload_size + hsize; + let pheader = self.get_header(ObjPtr::new(offset))?; + (pheader.is_freed, pheader.payload_size, pheader.desc_addr) + }; + if pheader_is_freed { + h = offset; + payload_size += hsize + fsize + pheader_payload_size; + self.del_desc(pheader_desc_addr)?; + } + } + + offset = addr + header_payload_size; + let mut f = offset; + + if offset + fsize < **self.header.compact_space_tail + && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize + { + // merge with higher data segment + offset += fsize; + let (nheader_is_freed, nheader_payload_size, nheader_desc_addr) = { + let nheader = self.get_header(ObjPtr::new(offset))?; + (nheader.is_freed, nheader.payload_size, nheader.desc_addr) + }; + if nheader_is_freed { + offset += hsize + nheader_payload_size; + f = offset; + { + let nfooter = self.get_footer(ObjPtr::new(offset))?; + assert!(nheader_payload_size == nfooter.payload_size); + } + payload_size += hsize + fsize + nheader_payload_size; + self.del_desc(nheader_desc_addr)?; + } + } + + let desc_addr = self.new_desc()?; + { + let mut desc = self.get_descriptor(desc_addr)?; + desc.write(|d| { + d.payload_size = payload_size; + d.haddr = h; + }); + } + let mut h = self.get_header(ObjPtr::new(h))?; + let mut f = self.get_footer(ObjPtr::new(f))?; + h.write(|h| { + h.payload_size = payload_size; + h.is_freed = true; + h.desc_addr = desc_addr; + }); + f.write(|f| f.payload_size = payload_size); + Ok(()) + } + + fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { + let tail = **self.header.meta_space_tail; + if tail == self.header.base_addr.addr { + return Ok(None); + } + + let hsize = CompactHeader::MSIZE; + let fsize = CompactFooter::MSIZE; + let dsize = CompactDescriptor::MSIZE; + + let mut old_alloc_addr = *self.header.alloc_addr; + + if old_alloc_addr.addr >= tail { + old_alloc_addr = *self.header.base_addr; + } + + let mut ptr = old_alloc_addr; + let mut res: Option = None; + for _ in 0..self.alloc_max_walk { + assert!(ptr.addr < tail); + let (desc_payload_size, desc_haddr) = { + let desc = self.get_descriptor(ptr)?; + (desc.payload_size, desc.haddr) + }; + let exit = if desc_payload_size == length { + // perfect match + { + let mut header = self.get_header(ObjPtr::new(desc_haddr))?; + assert_eq!(header.payload_size, desc_payload_size); + assert!(header.is_freed); + header.write(|h| h.is_freed = false); + } + self.del_desc(ptr)?; + true + } else if desc_payload_size > length + hsize + fsize { + // able to split + { + let mut lheader = self.get_header(ObjPtr::new(desc_haddr))?; + assert_eq!(lheader.payload_size, desc_payload_size); + assert!(lheader.is_freed); + lheader.write(|h| { + h.is_freed = false; + h.payload_size = length; + }); + } + { + let mut lfooter = self.get_footer(ObjPtr::new(desc_haddr + hsize + length))?; + //assert!(lfooter.payload_size == desc_payload_size); + lfooter.write(|f| f.payload_size = length); + } + + let offset = desc_haddr + hsize + length + fsize; + let rpayload_size = desc_payload_size - length - fsize - hsize; + let rdesc_addr = self.new_desc()?; + { + let mut rdesc = self.get_descriptor(rdesc_addr)?; + rdesc.write(|rd| { + rd.payload_size = rpayload_size; + rd.haddr = offset; + }); + } + { + let mut rheader = self.get_header(ObjPtr::new(offset))?; + rheader.write(|rh| { + rh.is_freed = true; + rh.payload_size = rpayload_size; + rh.desc_addr = rdesc_addr; + }); + } + { + let mut rfooter = + self.get_footer(ObjPtr::new(offset + hsize + rpayload_size))?; + rfooter.write(|f| f.payload_size = rpayload_size); + } + self.del_desc(ptr)?; + true + } else { + false + }; + if exit { + self.header.alloc_addr.write(|r| *r = ptr); + res = Some(desc_haddr + hsize); + break; + } + ptr.addr += dsize; + if ptr.addr >= tail { + ptr = *self.header.base_addr; + } + if ptr == old_alloc_addr { + break; + } + } + Ok(res) + } + + fn alloc_new(&mut self, length: u64) -> Result { + let regn_size = 1 << self.regn_nbit; + let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; + let mut offset = **self.header.compact_space_tail; + self.header.compact_space_tail.write(|r| { + // an item is always fully in one region + let rem = regn_size - (offset & (regn_size - 1)); + if rem < total_length { + offset += rem; + **r += rem; + } + **r += total_length + }); + let mut h = self.get_header(ObjPtr::new(offset))?; + let mut f = self.get_footer(ObjPtr::new(offset + CompactHeader::MSIZE + length))?; + h.write(|h| { + h.payload_size = length; + h.is_freed = false; + h.desc_addr = ObjPtr::new(0); + }); + f.write(|f| f.payload_size = length); + Ok(offset + CompactHeader::MSIZE) + } +} + +pub struct CompactSpace { + inner: UnsafeCell>, +} + +impl CompactSpace { + pub fn new( + meta_space: Rc, + compact_space: Rc, + header: Obj, + obj_cache: super::ObjCache, + alloc_max_walk: u64, + regn_nbit: u64, + ) -> Result { + let cs = CompactSpace { + inner: UnsafeCell::new(CompactSpaceInner { + meta_space, + compact_space, + header: CompactSpaceHeader::into_fields(header)?, + obj_cache, + alloc_max_walk, + regn_nbit, + }), + }; + Ok(cs) + } +} + +impl ShaleStore for CompactSpace { + fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError> { + let size = item.dehydrated_len() + extra; + let inner = unsafe { &mut *self.inner.get() }; + let ptr: ObjPtr = unsafe { + ObjPtr::new_from_addr(if let Some(addr) = inner.alloc_from_freed(size)? { + addr + } else { + inner.alloc_new(size)? + }) + }; + let mut u = inner.obj_cache.put(unsafe { + MummyObj::item_to_obj(inner.compact_space.as_ref(), ptr.addr(), size, item)? + }); + u.write(|_| {}).unwrap(); + Ok(u) + } + + fn free_item(&mut self, ptr: ObjPtr) -> Result<(), ShaleError> { + let inner = self.inner.get_mut(); + inner.obj_cache.pop(ptr); + inner.free(ptr.addr()) + } + + fn get_item<'a>(&'a self, ptr: ObjPtr) -> Result, ShaleError> { + let inner = unsafe { &*self.inner.get() }; + if let Some(r) = inner.obj_cache.get(ptr)? { + return Ok(r); + } + if ptr.addr() < CompactSpaceHeader::MSIZE { + return Err(ShaleError::ObjPtrInvalid); + } + let h = inner.get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))?; + Ok(inner + .obj_cache + .put(inner.get_data_ref(ptr, h.payload_size)?)) + } + + fn flush_dirty(&self) -> Option<()> { + let inner = unsafe { &mut *self.inner.get() }; + inner.header.flush_dirty(); + inner.obj_cache.flush_dirty() + } +} diff --git a/shale/src/lib.rs b/shale/src/lib.rs new file mode 100644 index 000000000000..37fb6c88f943 --- /dev/null +++ b/shale/src/lib.rs @@ -0,0 +1,603 @@ +use std::cell::UnsafeCell; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::marker::PhantomData; +use std::num::NonZeroUsize; +use std::ops::Deref; +use std::rc::Rc; + +pub mod block; +pub mod compact; +pub mod util; + +#[derive(Debug)] +pub enum ShaleError { + LinearMemStoreError, + DecodeError, + ObjRefAlreadyInUse, + ObjPtrInvalid, + SliceError, +} + +pub type SpaceID = u8; +pub const INVALID_SPACE_ID: SpaceID = 0xff; + +pub struct DiskWrite { + pub space_id: SpaceID, + pub space_off: u64, + pub data: Box<[u8]>, +} + +impl std::fmt::Debug for DiskWrite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "[Disk space=0x{:02x} offset=0x{:04x} data=0x{}", + self.space_id, + self.space_off, + hex::encode(&self.data) + ) + } +} + +/// A handle that pins and provides a readable access to a portion of the linear memory image. +pub trait MemView: Deref {} + +/// In-memory store that offers access to intervals from a linear byte space, which is usually +/// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear +/// persistent store. Reads could trigger disk reads to bring data into memory, but writes will +/// *only* be visible in memory (it does not write back to the disk). +pub trait MemStore { + /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them + /// directly accessible. + fn get_view(&self, offset: u64, length: u64) -> Option>; + /// Returns a handle that allows shared access to the store. + fn get_shared(&self) -> Option>>; + /// Write the `change` to the portion of the linear space starting at `offset`. The change + /// should be immediately visible to all `MemView` associated to this linear space. + fn write(&self, offset: u64, change: &[u8]); + /// Returns the identifier of this storage space. + fn id(&self) -> SpaceID; +} + +/// Opaque typed pointer in the 64-bit virtual addressable space. +#[repr(C)] +pub struct ObjPtr { + pub(crate) addr: u64, + phantom: PhantomData, +} + +impl std::cmp::PartialEq for ObjPtr { + fn eq(&self, other: &ObjPtr) -> bool { + self.addr == other.addr + } +} + +impl Eq for ObjPtr {} +impl Copy for ObjPtr {} +impl Clone for ObjPtr { + fn clone(&self) -> Self { + *self + } +} + +impl std::hash::Hash for ObjPtr { + fn hash(&self, state: &mut H) { + self.addr.hash(state) + } +} + +impl fmt::Display for ObjPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[ObjPtr addr={:08x}]", self.addr) + } +} + +impl ObjPtr { + pub fn null() -> Self { + Self::new(0) + } + pub fn is_null(&self) -> bool { + self.addr == 0 + } + pub fn addr(&self) -> u64 { + self.addr + } + + #[inline(always)] + pub(crate) fn new(addr: u64) -> Self { + ObjPtr { + addr, + phantom: PhantomData, + } + } + + #[inline(always)] + pub unsafe fn new_from_addr(addr: u64) -> Self { + Self::new(addr) + } +} + +/// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object +/// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to +/// turn a `TypedView` into an [ObjRef]. +pub trait TypedView: Deref { + /// Get the offset of the initial byte in the linear space. + fn get_offset(&self) -> u64; + /// Access it as a [MemStore] object. + fn get_mem_store(&self) -> &dyn MemStore; + /// Estimate the serialized length of the current type content. It should not be smaller than + /// the actually length. + fn estimate_mem_image(&self) -> Option; + /// Serialize the type content to the memory image. It defines how the current in-memory object + /// of `T` should be represented in the linear storage space. + fn write_mem_image(&self, mem_image: &mut [u8]); + /// Gain mutable access to the typed content. By changing it, its serialized bytes (and length) + /// could change. + fn write(&mut self) -> &mut T; + /// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto + /// reflected in the underlying [MemStore]). + fn is_mem_mapped(&self) -> bool; +} + +/// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] +/// or [MummyObj::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. +/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] +/// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. +pub struct Obj { + value: Box>, + dirty: Option, +} + +impl Obj { + #[inline(always)] + pub fn as_ptr(&self) -> ObjPtr { + ObjPtr::::new(self.value.get_offset()) + } +} + +impl Obj { + /// Write to the underlying object. Returns `Some(())` on success. + #[inline] + pub fn write(&mut self, modify: impl FnOnce(&mut T) -> ()) -> Option<()> { + modify(self.value.write()); + // if `estimate_mem_image` gives overflow, the object will not be written + self.dirty = None; + Some(self.dirty = Some(self.value.estimate_mem_image()?)) + } + + #[inline(always)] + pub fn get_space_id(&self) -> SpaceID { + self.value.get_mem_store().id() + } + + #[inline(always)] + pub fn from_typed_view(value: Box>) -> Self { + Obj { value, dirty: None } + } + + pub fn flush_dirty(&mut self) { + if !self.value.is_mem_mapped() { + if let Some(new_value_len) = self.dirty.take() { + let mut new_value = vec![0; new_value_len as usize]; + self.value.write_mem_image(&mut new_value); + self.value + .get_mem_store() + .write(self.value.get_offset(), &new_value); + } + } + } +} + +impl Drop for Obj { + fn drop(&mut self) { + self.flush_dirty() + } +} + +impl Deref for Obj { + type Target = T; + fn deref(&self) -> &T { + &*self.value + } +} + +/// User handle that offers read & write access to the stored [ShaleStore] item. +pub struct ObjRef<'a, T> { + inner: Option>, + cache: ObjCache, + _life: PhantomData<&'a mut ()>, +} + +impl<'a, T> ObjRef<'a, T> { + pub unsafe fn to_longlive(mut self) -> ObjRef<'static, T> { + ObjRef { + inner: self.inner.take(), + cache: ObjCache(self.cache.0.clone()), + _life: PhantomData, + } + } + + #[inline] + pub fn write(&mut self, modify: impl FnOnce(&mut T) -> ()) -> Option<()> { + let inner = self.inner.as_mut().unwrap(); + inner.write(modify)?; + self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); + Some(()) + } +} + +impl<'a, T> Deref for ObjRef<'a, T> { + type Target = Obj; + fn deref(&self) -> &Obj { + self.inner.as_ref().unwrap() + } +} + +impl<'a, T> Drop for ObjRef<'a, T> { + fn drop(&mut self) { + let mut inner = self.inner.take().unwrap(); + let ptr = inner.as_ptr(); + let cache = self.cache.get_inner_mut(); + if cache.pinned.remove(&ptr).unwrap() { + inner.dirty = None; + } else { + cache.cached.put(ptr, inner); + } + } +} + +/// A persistent item storage backed by linear logical space. New items can be created and old +/// items could be retrieved or dropped. +pub trait ShaleStore { + /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. + fn get_item<'a>(&'a self, ptr: ObjPtr) -> Result, ShaleError>; + /// Allocate a new item. + fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError>; + /// Free an item and recycle its space when applicable. + fn free_item(&mut self, item: ObjPtr) -> Result<(), ShaleError>; + /// Flush all dirty writes. + fn flush_dirty(&self) -> Option<()>; +} + +/// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient +/// implementation could be directly transmuting to/from a POD struct. But sometimes necessary +/// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. +pub trait MummyItem { + fn dehydrated_len(&self) -> u64; + fn dehydrate(&self, to: &mut [u8]); + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result + where + Self: Sized; + fn is_mem_mapped(&self) -> bool { + false + } +} + +pub fn to_dehydrated(item: &dyn MummyItem) -> Vec { + let mut buff = vec![0; item.dehydrated_len() as usize]; + item.dehydrate(&mut buff); + buff +} + +/// Reference implementation of [TypedView]. It takes any type that implements [MummyItem] and +/// should be useful for most applications. +pub struct MummyObj { + decoded: T, + mem: Box>, + offset: u64, + len_limit: u64, +} + +impl Deref for MummyObj { + type Target = T; + fn deref(&self) -> &T { + &self.decoded + } +} + +impl TypedView for MummyObj { + fn get_offset(&self) -> u64 { + self.offset + } + + fn get_mem_store(&self) -> &dyn MemStore { + &**self.mem + } + + fn estimate_mem_image(&self) -> Option { + let len = self.decoded.dehydrated_len(); + if len as u64 > self.len_limit { + None + } else { + Some(len) + } + } + + fn write_mem_image(&self, mem_image: &mut [u8]) { + self.decoded.dehydrate(mem_image) + } + + fn write(&mut self) -> &mut T { + &mut self.decoded + } + fn is_mem_mapped(&self) -> bool { + self.decoded.is_mem_mapped() + } +} + +impl MummyObj { + #[inline(always)] + fn new(offset: u64, len_limit: u64, space: &dyn MemStore) -> Result { + let decoded = T::hydrate(offset, space)?; + Ok(Self { + offset, + decoded, + mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + len_limit, + }) + } + + #[inline(always)] + fn from_hydrated( + offset: u64, + len_limit: u64, + decoded: T, + space: &dyn MemStore, + ) -> Result { + Ok(Self { + offset, + decoded, + mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + len_limit, + }) + } + + #[inline(always)] + pub unsafe fn ptr_to_obj( + store: &dyn MemStore, + ptr: ObjPtr, + len_limit: u64, + ) -> Result, ShaleError> { + Ok(Obj::from_typed_view(Box::new(Self::new( + ptr.addr(), + len_limit, + store, + )?))) + } + + #[inline(always)] + pub unsafe fn item_to_obj( + store: &dyn MemStore, + addr: u64, + len_limit: u64, + decoded: T, + ) -> Result, ShaleError> { + Ok(Obj::from_typed_view(Box::new(Self::from_hydrated( + addr, len_limit, decoded, store, + )?))) + } +} + +impl MummyObj { + unsafe fn new_from_slice( + offset: u64, + len_limit: u64, + decoded: T, + space: &dyn MemStore, + ) -> Result { + Ok(Self { + offset, + decoded, + mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + len_limit, + }) + } + + pub unsafe fn slice( + s: &Obj, + offset: u64, + length: u64, + decoded: U, + ) -> Result, ShaleError> { + let addr_ = s.value.get_offset() + offset; + if s.dirty.is_some() { + return Err(ShaleError::SliceError); + } + let r = Box::new(MummyObj::new_from_slice( + addr_, + length, + decoded, + s.value.get_mem_store(), + )?); + Ok(Obj { + value: r, + dirty: None, + }) + } +} + +impl ObjPtr { + const MSIZE: u64 = 8; +} + +impl MummyItem for ObjPtr { + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + use std::io::{Cursor, Write}; + Cursor::new(to) + .write_all(&self.addr().to_le_bytes()) + .unwrap(); + } + + fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearMemStoreError)?; + unsafe { + Ok(Self::new_from_addr(u64::from_le_bytes( + (**raw).try_into().unwrap(), + ))) + } + } +} + +/// Purely volatile, vector-based implementation for [MemStore]. This is good for testing or trying +/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write +/// your own [MemStore] implementation. +pub struct PlainMem { + space: Rc>>, + id: SpaceID, +} + +impl PlainMem { + pub fn new(size: u64, id: SpaceID) -> Self { + let mut space = Vec::new(); + space.resize(size as usize, 0); + let space = Rc::new(UnsafeCell::new(space)); + Self { space, id } + } + + fn get_space_mut(&self) -> &mut Vec { + unsafe { &mut *self.space.get() } + } +} + +impl MemStore for PlainMem { + fn get_view(&self, offset: u64, length: u64) -> Option> { + let offset = offset as usize; + let length = length as usize; + if offset + length > self.get_space_mut().len() { + None + } else { + Some(Box::new(PlainMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(PlainMemShared(Self { + space: self.space.clone(), + id: self.id, + }))) + } + + fn write(&self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + self.get_space_mut()[offset..offset + length].copy_from_slice(change) + } + + fn id(&self) -> SpaceID { + self.id + } +} + +struct PlainMemView { + offset: usize, + length: usize, + mem: PlainMem, +} + +struct PlainMemShared(PlainMem); + +impl Deref for PlainMemView { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.mem.get_space_mut()[self.offset..self.offset + self.length] + } +} + +impl Deref for PlainMemShared { + type Target = dyn MemStore; + fn deref(&self) -> &(dyn MemStore + 'static) { + &self.0 + } +} + +impl MemView for PlainMemView {} + +struct ObjCacheInner { + cached: lru::LruCache, Obj>, + pinned: HashMap, bool>, + dirty: HashSet>, +} + +/// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. +pub struct ObjCache(Rc>>); + +impl ObjCache { + pub fn new(capacity: usize) -> Self { + Self(Rc::new(UnsafeCell::new(ObjCacheInner { + cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), + pinned: HashMap::new(), + dirty: HashSet::new(), + }))) + } + + #[inline(always)] + fn get_inner_mut(&self) -> &mut ObjCacheInner { + unsafe { &mut *self.0.get() } + } + + #[inline(always)] + pub fn get<'a>(&'a self, ptr: ObjPtr) -> Result>, ShaleError> { + let inner = &mut self.get_inner_mut(); + if let Some(r) = inner.cached.pop(&ptr) { + if inner.pinned.insert(ptr, false).is_some() { + return Err(ShaleError::ObjRefAlreadyInUse); + } + return Ok(Some(ObjRef { + inner: Some(r), + cache: Self(self.0.clone()), + _life: PhantomData, + })); + } + Ok(None) + } + + #[inline(always)] + pub fn put<'a>(&'a self, inner: Obj) -> ObjRef<'a, T> { + let ptr = inner.as_ptr(); + self.get_inner_mut().pinned.insert(ptr, false); + ObjRef { + inner: Some(inner), + cache: Self(self.0.clone()), + _life: PhantomData, + } + } + + #[inline(always)] + pub fn pop(&self, ptr: ObjPtr) { + let inner = self.get_inner_mut(); + if let Some(f) = inner.pinned.get_mut(&ptr) { + *f = true + } + if let Some(mut r) = inner.cached.pop(&ptr) { + r.dirty = None + } + inner.dirty.remove(&ptr); + } + + pub fn flush_dirty(&self) -> Option<()> { + let inner = self.get_inner_mut(); + if !inner.pinned.is_empty() { + return None; + } + for ptr in std::mem::replace(&mut inner.dirty, HashSet::new()) { + if let Some(r) = inner.cached.peek_mut(&ptr) { + r.flush_dirty() + } + } + Some(()) + } +} diff --git a/shale/src/util.rs b/shale/src/util.rs new file mode 100644 index 000000000000..e532ddebf679 --- /dev/null +++ b/shale/src/util.rs @@ -0,0 +1,6 @@ +#[allow(dead_code)] +pub fn get_raw_bytes(data: &T) -> Vec { + unsafe { + std::slice::from_raw_parts(data as *const T as *const u8, std::mem::size_of::()).to_vec() + } +} From 4f34a44e525d714931feee724255108e597ce81f Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 3 Mar 2023 10:29:13 -0500 Subject: [PATCH 0060/1053] Update CODEOWNERS Add rkuris to CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6092c7373ede..d681448a04fe 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # CODEOWNERS -* @Determinant @exdx @haohao-os @gyuho @hexfusion +* @Determinant @exdx @haohao-os @gyuho @hexfusion @rkuris From c093a5bb59cb4955cb29bf997e029fb3a1bbd84c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:01:56 +0000 Subject: [PATCH 0061/1053] build(deps): update nix requirement from 0.25.0 to 0.26.2 Updates the requirements on [nix](https://github.com/nix-rust/nix) to permit the latest version. - [Release notes](https://github.com/nix-rust/nix/releases) - [Changelog](https://github.com/nix-rust/nix/blob/v0.26.2/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/commits/v0.26.2) --- updated-dependencies: - dependency-name: nix dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- growth-ring/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 7a699ecd66be..2edd3e47ad08 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -18,7 +18,7 @@ scan_fmt = "0.2.6" regex = "1.6.0" async-trait = "0.1.57" futures = "0.3.24" -nix = "0.25.0" +nix = "0.26.2" libc = "0.2.133" [dev-dependencies] From 0616eb36b7cb9f71ffdb8d8fe333a01807292305 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Fri, 3 Mar 2023 21:41:01 +0000 Subject: [PATCH 0062/1053] tests: re-enable integration tests after introduce cargo workspaces --- {tests => firewood/tests}/db.rs | 5 ++- {tests => firewood/tests}/merkle.rs | 68 ++++++++++++++++++++++++----- fwdctl/Cargo.toml | 5 +++ {tests => fwdctl/tests}/cli.rs | 29 ++++-------- 4 files changed, 73 insertions(+), 34 deletions(-) rename {tests => firewood/tests}/db.rs (95%) rename {tests => firewood/tests}/merkle.rs (90%) rename {tests => fwdctl/tests}/cli.rs (87%) diff --git a/tests/db.rs b/firewood/tests/db.rs similarity index 95% rename from tests/db.rs rename to firewood/tests/db.rs index 32a3f01bc7da..d40e6597a9b9 100644 --- a/tests/db.rs +++ b/firewood/tests/db.rs @@ -33,7 +33,10 @@ fn test_revisions() { let keygen = || { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); - (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + ( + rng.gen_range(1..max_len0 + 1), + rng.gen_range(1..max_len1 + 1), + ) }; let key: Vec = (0..len0) .map(|_| rng.borrow_mut().gen_range(0..2)) diff --git a/tests/merkle.rs b/firewood/tests/merkle.rs similarity index 90% rename from tests/merkle.rs rename to firewood/tests/merkle.rs index 7c4521d2a62e..6dc5a1f4371b 100644 --- a/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,7 +1,9 @@ use firewood::{merkle_util::*, proof::Proof}; fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( - items: Vec<(K, V)>, meta_size: u64, compact_size: u64, + items: Vec<(K, V)>, + meta_size: u64, + compact_size: u64, ) -> Result { let mut merkle = new_merkle(meta_size, compact_size); for (k, v) in items.iter() { @@ -50,7 +52,10 @@ fn test_root_hash_fuzz_insertions() { let keygen = || { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); - (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + ( + rng.gen_range(1..max_len0 + 1), + rng.gen_range(1..max_len1 + 1), + ) }; let key: Vec = (0..len0) .map(|_| rng.borrow_mut().gen_range(0..2)) @@ -77,7 +82,10 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { let keygen = || { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); - (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + ( + rng.gen_range(1..max_len0 + 1), + rng.gen_range(1..max_len1 + 1), + ) }; let key: Vec = (0..len0) .map(|_| rng.borrow_mut().gen_range(0..2)) @@ -104,14 +112,23 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { hashes.pop(); println!("----"); let mut prev_dump = merkle.dump()?; - for (((k, _), h), d) in items.iter().rev().zip(hashes.iter().rev()).zip(dumps.iter().rev()) { + for (((k, _), h), d) in items + .iter() + .rev() + .zip(hashes.iter().rev()) + .zip(dumps.iter().rev()) + { merkle.remove(k)?; let h0 = merkle.root_hash()?.0; if h.as_ref().unwrap().0 != h0 { for (k, _) in items.iter() { println!("{}", hex::encode(k)); } - println!("{} != {}", hex::encode(**h.as_ref().unwrap()), hex::encode(h0)); + println!( + "{} != {}", + hex::encode(**h.as_ref().unwrap()), + hex::encode(h0) + ); println!("== before {} ===", hex::encode(k)); print!("{prev_dump}"); println!("== after {} ===", hex::encode(k)); @@ -136,7 +153,10 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { let keygen = || { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); - (rng.gen_range(1..max_len0 + 1), rng.gen_range(1..max_len1 + 1)) + ( + rng.gen_range(1..max_len0 + 1), + rng.gen_range(1..max_len1 + 1), + ) }; let key: Vec = (0..len0) .map(|_| rng.borrow_mut().gen_range(0..2)) @@ -168,7 +188,9 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); } - let h = triehash::trie_root::, _, _>(items.iter().collect()); + let h = triehash::trie_root::, _, _>( + items.iter().collect(), + ); let h0 = merkle.root_hash()?; if h[..] != *h0 { println!("{} != {}", hex::encode(h), hex::encode(*h0)); @@ -220,7 +242,12 @@ fn test_proof_end_with_leaf() -> Result<(), DataStoreError> { #[test] /// Verify the proofs that end with branch node with the given key. fn test_proof_end_with_branch() -> Result<(), DataStoreError> { - let items = vec![("d", "verb"), ("do", "verb"), ("doe", "reindeer"), ("e", "coin")]; + let items = vec![ + ("d", "verb"), + ("do", "verb"), + ("doe", "reindeer"), + ("e", "coin"), + ]; let merkle = merkle_build_test(items, 0x10000, 0x10000)?; let key = "d"; @@ -284,7 +311,12 @@ fn test_empty_tree_proof() -> Result<(), DataStoreError> { #[test] fn test_range_proof() -> Result<(), DataStoreError> { - let mut items = vec![("doa", "verb"), ("doe", "reindeer"), ("dog", "puppy"), ("ddd", "ok")]; + let mut items = vec![ + ("doa", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("ddd", "ok"), + ]; items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; @@ -359,9 +391,15 @@ fn test_range_proof_with_invalid_non_existent_proof() { let start = 0; let end = &items.len() - 1; - let mut proof = merkle.as_ref().unwrap().prove(std::str::from_utf8(&[0x3]).unwrap()); + let mut proof = merkle + .as_ref() + .unwrap() + .prove(std::str::from_utf8(&[0x3]).unwrap()); assert!(!proof.as_ref().unwrap().0.is_empty()); - let end_proof = merkle.as_ref().unwrap().prove(std::str::from_utf8(&[0x7]).unwrap()); + let end_proof = merkle + .as_ref() + .unwrap() + .prove(std::str::from_utf8(&[0x7]).unwrap()); assert!(!end_proof.as_ref().unwrap().0.is_empty()); proof.as_mut().unwrap().concat_proofs(end_proof.unwrap()); @@ -376,7 +414,13 @@ fn test_range_proof_with_invalid_non_existent_proof() { merkle .unwrap() - .verify_range_proof(proof.as_ref().unwrap(), &items[start].0, &items[end].0, keys, vals) + .verify_range_proof( + proof.as_ref().unwrap(), + &items[start].0, + &items[end].0, + keys, + vals, + ) .is_err(); } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index b0b3e7bda0d1..1f33af7f2293 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -9,3 +9,8 @@ clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" log = "0.4.17" + +[dev-dependencies] +assert_cmd = "2.0.7" +predicates = "2.1.1" +serial_test = "1.0.0" diff --git a/tests/cli.rs b/fwdctl/tests/cli.rs similarity index 87% rename from tests/cli.rs rename to fwdctl/tests/cli.rs index 8eccae304f22..6055eb03566a 100644 --- a/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -6,14 +6,13 @@ use std::fs::remove_dir_all; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); -const FIREWOOD: &str = "firewood"; const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { if let Err(e) = remove_dir_all(FIREWOOD_TEST_DB_NAME) { eprintln!("failed to delete testing dir: {e}"); - return Err(anyhow!(e)) + return Err(anyhow!(e)); } Ok(()) @@ -22,7 +21,7 @@ fn fwdctl_delete_db() -> Result<()> { #[test] #[serial] fn fwdctl_prints_version() -> Result<()> { - let expected_version_output: String = format!("{FIREWOOD} {VERSION}\n"); + let expected_version_output: String = format!("{PRG} {VERSION}\n"); // version is defined and succeeds with the desired output Command::cargo_bin(PRG)? @@ -43,9 +42,7 @@ fn fwdctl_creates_database() -> Result<()> { .assert() .success(); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } @@ -70,9 +67,7 @@ fn fwdctl_insert_successful() -> Result<()> { .success() .stdout(predicate::str::contains("year")); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } @@ -105,9 +100,7 @@ fn fwdctl_get_successful() -> Result<()> { .success() .stdout(predicate::str::contains("2023")); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } @@ -139,9 +132,7 @@ fn fwdctl_delete_successful() -> Result<()> { .success() .stdout(predicate::str::contains("year")); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } @@ -172,9 +163,7 @@ fn fwdctl_root_hash() -> Result<()> { .success() .stdout(predicate::str::is_empty().not()); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } @@ -205,9 +194,7 @@ fn fwdctl_dump() -> Result<()> { .success() .stdout(predicate::str::is_empty().not()); - if let Err(e) = fwdctl_delete_db() { - return Err(anyhow!(e)) - } + fwdctl_delete_db().map_err(|e| anyhow!(e))?; Ok(()) } From 24be0affceff09a03807ad03db73a5cae5552c68 Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 6 Mar 2023 09:56:34 -0500 Subject: [PATCH 0063/1053] fix: Tweak repo organization (#130) * fix: rename 'figures' -> 'docs' This change makes it more clear what the folder is. It also enables it to be used for other purposes in the future. Signed-off-by: Dan Sover * fix: Move fwdctl README to module root level Signed-off-by: Dan Sover * fix: Add auto-generated comment to gitignore Signed-off-by: Dan Sover --------- Signed-off-by: Dan Sover Co-authored-by: Sam Batschelet --- .gitignore | 5 +++++ {figures => docs}/architecture.svg | 0 {figures => docs}/three-layers.svg | 0 fwdctl/{src => }/README.md | 0 4 files changed, 5 insertions(+) rename {figures => docs}/architecture.svg (100%) rename {figures => docs}/three-layers.svg (100%) rename fwdctl/{src => }/README.md (100%) diff --git a/.gitignore b/.gitignore index 32ed236d9318..08fa7a521f7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Ignore VSCode directory +.vscode + +#### Below sections are auto-generated #### + # ignore test databases *_db # Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim diff --git a/figures/architecture.svg b/docs/architecture.svg similarity index 100% rename from figures/architecture.svg rename to docs/architecture.svg diff --git a/figures/three-layers.svg b/docs/three-layers.svg similarity index 100% rename from figures/three-layers.svg rename to docs/three-layers.svg diff --git a/fwdctl/src/README.md b/fwdctl/README.md similarity index 100% rename from fwdctl/src/README.md rename to fwdctl/README.md From 974d9415461f60bf5761ce55c7092f3aa22903ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:33:42 +0000 Subject: [PATCH 0064/1053] build(deps): update lru requirement from 0.8.0 to 0.10.0 Updates the requirements on [lru](https://github.com/jeromefroe/lru-rs) to permit the latest version. - [Release notes](https://github.com/jeromefroe/lru-rs/releases) - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/commits/0.10.0) --- updated-dependencies: - dependency-name: lru dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- firewood/Cargo.toml | 2 +- growth-ring/Cargo.toml | 2 +- shale/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8f6ad3edbc12..cc06ef526d88 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -13,7 +13,7 @@ rlp = "0.5.2" sha3 = "0.10.2" once_cell = "1.13.1" hex = "0.4.3" -lru = "0.9.0" +lru = "0.10.0" nix = "0.26.1" typed-builder = "0.12.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 2edd3e47ad08..ee3ec49231d2 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -13,7 +13,7 @@ description = "Simple and modular write-ahead-logging implementation." [dependencies] libaio-futures = { version = "0.2.3", path = "../libaio-futures" } crc = "3.0.0" -lru = "0.8.0" +lru = "0.10.0" scan_fmt = "0.2.6" regex = "1.6.0" async-trait = "0.1.57" diff --git a/shale/Cargo.toml b/shale/Cargo.toml index f9a991b899c3..4ed4980a2fe8 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -9,4 +9,4 @@ license = "MIT" [dependencies] hex = "0.4.3" -lru = "0.8.1" +lru = "0.10.0" From b16209b88c51f9d7ca7a5d231d9844c5e631a67c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 17:40:14 +0000 Subject: [PATCH 0065/1053] build(deps): update typed-builder requirement from 0.12.0 to 0.13.0 Updates the requirements on [typed-builder](https://github.com/idanarye/rust-typed-builder) to permit the latest version. - [Release notes](https://github.com/idanarye/rust-typed-builder/releases) - [Changelog](https://github.com/idanarye/rust-typed-builder/blob/master/CHANGELOG.md) - [Commits](https://github.com/idanarye/rust-typed-builder/commits) --- updated-dependencies: - dependency-name: typed-builder dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index cc06ef526d88..a62e63855582 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -15,7 +15,7 @@ once_cell = "1.13.1" hex = "0.4.3" lru = "0.10.0" nix = "0.26.1" -typed-builder = "0.12.0" +typed-builder = "0.13.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } futures = "0.3.24" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } From 7d7d21f41fb122ad86e7fb1d30d793c075e83489 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 6 Mar 2023 12:18:23 -0600 Subject: [PATCH 0066/1053] MacOS fixes (part 1) This only gets MacOS to compile. The tests fail because the system call interface on MacOS is different, and it's not clear we want to use POSIX AIO on MacOS. But local dev on a mac is now possible, especially for large refactors. --- firewood/src/file.rs | 27 +++++-------------------- growth-ring/src/lib.rs | 32 +++++++++++++++++++++--------- libaio-futures/libaio/compat-0_1.c | 2 +- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/firewood/src/file.rs b/firewood/src/file.rs index fd1d5fd522e8..306d3f0fa12b 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -1,17 +1,15 @@ // Copied from CedrusDB -#![allow(dead_code)] - pub(crate) use std::os::unix::io::RawFd as Fd; +use growthring::oflags; use nix::errno::Errno; use nix::fcntl::{open, openat, OFlag}; use nix::sys::stat::Mode; -use nix::unistd::{close, fsync, mkdir}; +use nix::unistd::{close, mkdir}; pub struct File { fd: Fd, - fid: u64, } impl File { @@ -55,22 +53,12 @@ impl File { e => return Err(e), }, }; - Ok(File { fd, fid }) + Ok(File { fd }) } pub fn get_fd(&self) -> Fd { self.fd } - pub fn get_fid(&self) -> u64 { - self.fid - } - pub fn get_fname(&self) -> String { - Self::_get_fname(self.fid) - } - - pub fn sync(&self) { - fsync(self.fd).unwrap(); - } } impl Drop for File { @@ -93,12 +81,7 @@ pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result { return Err(errno); } } - openat( - rootfd, - dirname, - OFlag::O_DIRECTORY | OFlag::O_PATH, - Mode::empty(), - ) + openat(rootfd, dirname, oflags(), Mode::empty()) } pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { @@ -118,7 +101,7 @@ pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { } } Ok(( - match open(path, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) { + match open(path, oflags(), Mode::empty()) { Ok(fd) => fd, Err(e) => return Err(e), }, diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 1f42ee58108f..618bd24b1058 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -52,7 +52,10 @@ pub mod wal; use aiofut::{AIOBuilder, AIOManager}; use async_trait::async_trait; use libc::off_t; +#[cfg(target_os = "linux")] use nix::fcntl::{fallocate, open, openat, FallocateFlags, OFlag}; +#[cfg(not(target_os = "linux"))] +use nix::fcntl::{open, openat, OFlag}; use nix::sys::stat::Mode; use nix::unistd::{close, ftruncate, mkdir, unlinkat, UnlinkatFlags}; use std::os::unix::io::RawFd; @@ -85,16 +88,22 @@ impl Drop for WALFileAIO { #[async_trait(?Send)] impl WALFile for WALFileAIO { + #[cfg(target_os = "linux")] async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()> { // TODO: is there any async version of fallocate? - fallocate( + return fallocate( self.fd, FallocateFlags::FALLOC_FL_ZERO_RANGE, offset as off_t, length as off_t, ) .and_then(|_| Ok(())) - .or_else(|_| Err(())) + .or_else(|_| Err(())); + } + #[cfg(not(target_os = "linux"))] + // TODO: macos support is possible here, but possibly unnecessary + async fn allocate(&self, _offset: WALPos, _length: usize) -> Result<(), ()> { + Ok(()) } fn truncate(&self, length: usize) -> Result<(), ()> { @@ -153,7 +162,7 @@ impl WALStoreAIO { } Ok(_) => (), } - walfd = match open(wal_dir, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) { + walfd = match open(wal_dir, oflags(), Mode::empty()) { Ok(fd) => fd, Err(_) => panic!("error while opening the WAL directory"), } @@ -172,12 +181,7 @@ impl WALStoreAIO { panic!("error while creating directory") } } - walfd = match nix::fcntl::openat( - fd, - wal_dir, - OFlag::O_DIRECTORY | OFlag::O_PATH, - Mode::empty(), - ) { + walfd = match nix::fcntl::openat(fd, wal_dir, oflags(), Mode::empty()) { Ok(fd) => fd, Err(_) => panic!("error while opening the WAL directory"), } @@ -190,6 +194,16 @@ impl WALStoreAIO { } } +/// Return OS specific open flags for opening files +/// TODO: Switch to a rust idiomatic directory scanning approach +/// TODO: This shouldn't need to escape growth-ring (no pub) +pub fn oflags() -> OFlag { + #[cfg(target_os = "linux")] + return OFlag::O_DIRECTORY | OFlag::O_PATH; + #[cfg(not(target_os = "linux"))] + return OFlag::O_DIRECTORY; +} + #[async_trait(?Send)] impl WALStore for WALStoreAIO { type FileNameIter = std::vec::IntoIter; diff --git a/libaio-futures/libaio/compat-0_1.c b/libaio-futures/libaio/compat-0_1.c index 136396f99638..966020ae8c8a 100644 --- a/libaio-futures/libaio/compat-0_1.c +++ b/libaio-futures/libaio/compat-0_1.c @@ -19,7 +19,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include +#include #include "libaio.h" #include "vsys_def.h" From a12107f8ddfb82f7baf75a2590d163feef8b7219 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 6 Mar 2023 17:29:19 -0600 Subject: [PATCH 0067/1053] Clippy fixes (#135) Not all clippy fixes are included here since some have to do with safety, but all non-safety issues are resolved with this commit. --- shale/src/compact.rs | 6 +++--- shale/src/lib.rs | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 187655a0baf3..0fa9e327fbd4 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -28,7 +28,7 @@ impl MummyItem for CompactHeader { .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; let payload_size = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let is_freed = if raw[8] == 0 { false } else { true }; + let is_freed = raw[8] != 0; let desc_addr = u64::from_le_bytes(raw[9..17].try_into().unwrap()); Ok(Self { payload_size, @@ -549,7 +549,7 @@ impl CompactSpace { } impl ShaleStore for CompactSpace { - fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError> { + fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let inner = unsafe { &mut *self.inner.get() }; let ptr: ObjPtr = unsafe { @@ -572,7 +572,7 @@ impl ShaleStore for CompactSpace { inner.free(ptr.addr()) } - fn get_item<'a>(&'a self, ptr: ObjPtr) -> Result, ShaleError> { + fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError> { let inner = unsafe { &*self.inner.get() }; if let Some(r) = inner.obj_cache.get(ptr)? { return Ok(r); diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 37fb6c88f943..74726cd719d3 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -159,11 +159,11 @@ impl Obj { impl Obj { /// Write to the underlying object. Returns `Some(())` on success. #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T) -> ()) -> Option<()> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Option<()> { modify(self.value.write()); // if `estimate_mem_image` gives overflow, the object will not be written - self.dirty = None; - Some(self.dirty = Some(self.value.estimate_mem_image()?)) + self.dirty = Some(self.value.estimate_mem_image()?); + Some(()) } #[inline(always)] @@ -198,7 +198,7 @@ impl Drop for Obj { impl Deref for Obj { type Target = T; fn deref(&self) -> &T { - &*self.value + &self.value } } @@ -219,7 +219,7 @@ impl<'a, T> ObjRef<'a, T> { } #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T) -> ()) -> Option<()> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Option<()> { let inner = self.inner.as_mut().unwrap(); inner.write(modify)?; self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); @@ -251,9 +251,9 @@ impl<'a, T> Drop for ObjRef<'a, T> { /// items could be retrieved or dropped. pub trait ShaleStore { /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. - fn get_item<'a>(&'a self, ptr: ObjPtr) -> Result, ShaleError>; + fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; /// Allocate a new item. - fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError>; + fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; /// Free an item and recycle its space when applicable. fn free_item(&mut self, item: ObjPtr) -> Result<(), ShaleError>; /// Flush all dirty writes. @@ -307,7 +307,7 @@ impl TypedView for MummyObj { fn estimate_mem_image(&self) -> Option { let len = self.decoded.dehydrated_len(); - if len as u64 > self.len_limit { + if len > self.len_limit { None } else { Some(len) @@ -550,7 +550,7 @@ impl ObjCache { } #[inline(always)] - pub fn get<'a>(&'a self, ptr: ObjPtr) -> Result>, ShaleError> { + pub fn get(&'_ self, ptr: ObjPtr) -> Result>, ShaleError> { let inner = &mut self.get_inner_mut(); if let Some(r) = inner.cached.pop(&ptr) { if inner.pinned.insert(ptr, false).is_some() { @@ -566,7 +566,7 @@ impl ObjCache { } #[inline(always)] - pub fn put<'a>(&'a self, inner: Obj) -> ObjRef<'a, T> { + pub fn put(&'_ self, inner: Obj) -> ObjRef<'_, T> { let ptr = inner.as_ptr(); self.get_inner_mut().pinned.insert(ptr, false); ObjRef { @@ -593,7 +593,7 @@ impl ObjCache { if !inner.pinned.is_empty() { return None; } - for ptr in std::mem::replace(&mut inner.dirty, HashSet::new()) { + for ptr in std::mem::take(&mut inner.dirty) { if let Some(r) = inner.cached.peek_mut(&ptr) { r.flush_dirty() } From a43140910e617eb807146f520c1e44c3a5662f82 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 6 Mar 2023 21:06:28 -0600 Subject: [PATCH 0068/1053] Shorten tests (#137) Tests were taking about 2 hours due to introduction of the growth-ring subproject. This growth-ring test is quite exhaustive: it sets a failure point at each tick of the async subsystem, and runs some very large trees. Great test, but it takes a long time, so added [ignore] blocks. These tests will now only run when specifically requested, rather than on each commit. Added a short sanity test version which can still run on each commit. --- growth-ring/tests/rand_fail.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs index 2e6b1faadbbb..ad4470dff9e0 100644 --- a/growth-ring/tests/rand_fail.rs +++ b/growth-ring/tests/rand_fail.rs @@ -48,6 +48,25 @@ fn multi_point_failure(sims: &[common::PaintingSim]) { } #[test] +fn short_single_point_failure() { + let sim = common::PaintingSim { + block_nbit: 5, + file_nbit: 6, + file_cache: 1000, + n: 100, + m: 10, + k: 10, + csize: 1000, + stroke_max_len: 10, + stroke_max_col: 256, + stroke_max_n: 5, + seed: 0, + }; + multi_point_failure(&[sim]); +} + +#[test] +#[ignore] fn single_point_failure1() { let sim = common::PaintingSim { block_nbit: 5, @@ -66,6 +85,7 @@ fn single_point_failure1() { } #[test] +#[ignore] fn two_failures() { let sims = [ common::PaintingSim { From 774c649c8850d86a81eccd063cbc77ebe22edde2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 6 Mar 2023 21:46:52 -0600 Subject: [PATCH 0069/1053] Remove unused block.rs (#136) Block.rs was empty; removed from source --- shale/src/block.rs | 1 - shale/src/lib.rs | 1 - 2 files changed, 2 deletions(-) delete mode 100644 shale/src/block.rs diff --git a/shale/src/block.rs b/shale/src/block.rs deleted file mode 100644 index 8b137891791f..000000000000 --- a/shale/src/block.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 74726cd719d3..f536cb16a426 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -6,7 +6,6 @@ use std::num::NonZeroUsize; use std::ops::Deref; use std::rc::Rc; -pub mod block; pub mod compact; pub mod util; From 5f81d21513c8008d743af9431fbca05df6f9f884 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 7 Mar 2023 06:49:14 -0600 Subject: [PATCH 0070/1053] Switch to stable toolchain (#129) * Switch to stable toolchain Per style guide, all builds are now on stable toolchain * Fix readme Squash on merge * tests: re-enable integration tests after introduce cargo workspaces * fix: Tweak repo organization (#130) * fix: rename 'figures' -> 'docs' This change makes it more clear what the folder is. It also enables it to be used for other purposes in the future. Signed-off-by: Dan Sover * fix: Move fwdctl README to module root level Signed-off-by: Dan Sover * fix: Add auto-generated comment to gitignore Signed-off-by: Dan Sover --------- Signed-off-by: Dan Sover Co-authored-by: Sam Batschelet --------- Signed-off-by: Dan Sover Co-authored-by: Hao Hao Co-authored-by: exdx Co-authored-by: Sam Batschelet --- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/publish.yaml | 2 +- README.md | 3 --- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 67a6077afa09..f609cad2626d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true components: rustfmt, clippy - name: Lint @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true - name: Build run: cargo build --verbose @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true - name: Run tests run: cargo test --verbose @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true - name: Run simple example run: cargo run --example simple @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true - name: Lint intra docs links run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 23aec6491ad3..605fae1cd1b4 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable override: true - name: publish firewood crate continue-on-error: true diff --git a/README.md b/README.md index 38dbd1917e6e..cc37d077861a 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,6 @@ Linux VM must be used to run firewood. It is encouraged to enhance the project with I/O supports for other OSes, such as OSX (where `kqueue` needs to be used for async I/O) and Windows. Please contact us if you're interested in such contribution. -Firewood is written in stable Rust, but relies on the Rust nightly toolchain -for code linting/formatting. - ## Run There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release From ec46870a4cf6c89ff8793b6678a062e35a7852b0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 7 Mar 2023 16:44:24 -0600 Subject: [PATCH 0071/1053] Remove unnecessary unsafes (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove unnecessary unsafes It's only unsafe to dereference a raw pointer, not create one. This cleans up all unsafe blocks around creating pointers. From https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html : We can create raw pointers in safe code; we just can’t dereference raw pointers outside an unsafe block, as you’ll see in a bit. Unsafe count was:68 -> now:38 --- firewood/src/account.rs | 16 ++++---- firewood/src/db.rs | 50 +++++++++++------------- firewood/src/merkle.rs | 8 ++-- firewood/src/merkle_util.rs | 17 ++++----- shale/src/compact.rs | 76 +++++++++++++++++-------------------- shale/src/lib.rs | 20 +++++----- 6 files changed, 83 insertions(+), 104 deletions(-) diff --git a/firewood/src/account.rs b/firewood/src/account.rs index c3385753a985..17ceb1262e1f 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -48,15 +48,13 @@ impl Account { let root_hash = Hash(raw[56..88].try_into().unwrap()); let code_hash = Hash(raw[88..].try_into().unwrap()); - unsafe { - Self { - nonce, - balance, - root: ObjPtr::new_from_addr(root), - code: ObjPtr::new_from_addr(code), - root_hash, - code_hash, - } + Self { + nonce, + balance, + root: ObjPtr::new_from_addr(root), + code: ObjPtr::new_from_addr(code), + root_hash, + code_hash, } } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6591358f3385..a8a2ea9f8134 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -192,12 +192,10 @@ impl MummyItem for DBHeader { .ok_or(shale::ShaleError::LinearMemStoreError)?; let acc_root = u64::from_le_bytes(raw[..8].try_into().unwrap()); let kv_root = u64::from_le_bytes(raw[8..].try_into().unwrap()); - unsafe { - Ok(Self { - acc_root: ObjPtr::new_from_addr(acc_root), - kv_root: ObjPtr::new_from_addr(kv_root), - }) - } + Ok(Self { + acc_root: ObjPtr::new_from_addr(acc_root), + kv_root: ObjPtr::new_from_addr(kv_root), + }) } fn dehydrated_len(&self) -> u64 { @@ -568,14 +566,12 @@ impl DB { let db_header: ObjPtr; let merkle_payload_header: ObjPtr; let blob_payload_header: ObjPtr; - unsafe { - db_header = ObjPtr::new_from_addr(offset); - offset += DBHeader::MSIZE; - merkle_payload_header = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); - blob_payload_header = ObjPtr::new_from_addr(0); - } + db_header = ObjPtr::new_from_addr(offset); + offset += DBHeader::MSIZE; + merkle_payload_header = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + blob_payload_header = ObjPtr::new_from_addr(0); if reset { // initialize space headers @@ -599,7 +595,7 @@ impl DB { ); } - let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe { + let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { let merkle_meta_ref = staging.merkle.meta.as_ref() as &dyn MemStore; let blob_meta_ref = staging.blob.meta.as_ref() as &dyn MemStore; @@ -778,22 +774,20 @@ impl DB { let db_header: ObjPtr; let merkle_payload_header: ObjPtr; let blob_payload_header: ObjPtr; - unsafe { - let mut offset = std::mem::size_of::() as u64; - // DBHeader starts after DBParams in merkle meta space - db_header = ObjPtr::new_from_addr(offset); - offset += DBHeader::MSIZE; - // Merkle CompactHeader starts after DBHeader in merkle meta space - merkle_payload_header = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); - // Blob CompactSpaceHeader starts right in blob meta space - blob_payload_header = ObjPtr::new_from_addr(0); - } + let mut offset = std::mem::size_of::() as u64; + // DBHeader starts after DBParams in merkle meta space + db_header = ObjPtr::new_from_addr(offset); + offset += DBHeader::MSIZE; + // Merkle CompactHeader starts after DBHeader in merkle meta space + merkle_payload_header = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + // Blob CompactSpaceHeader starts right in blob meta space + blob_payload_header = ObjPtr::new_from_addr(0); let space = &inner.revisions[nback - 1]; - let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe { + let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { let merkle_meta_ref = &space.merkle.meta as &dyn MemStore; let blob_meta_ref = &space.blob.meta as &dyn MemStore; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d9009bba0b1f..7c987eeeb76a 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -492,7 +492,7 @@ impl MummyItem for Node { .map_err(|_| ShaleError::DecodeError)?; let addr = u64::from_le_bytes(buff); if addr != 0 { - *chd = Some(unsafe { ObjPtr::new_from_addr(addr) }) + *chd = Some(ObjPtr::new_from_addr(addr)) } } cur.read_exact(&mut buff[..4]) @@ -584,7 +584,7 @@ impl MummyItem for Node { Ok(Self::new_from_hash( root_hash, eth_rlp_long, - NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) }, rlp)), + NodeType::Extension(ExtNode(path, ObjPtr::new_from_addr(ptr), rlp)), )) } Self::LEAF_NODE => { @@ -741,7 +741,7 @@ fn test_merkle_node_encoding() { let chd0 = [None; NBRANCH]; let mut chd1 = chd0; for node in chd1.iter_mut().take(NBRANCH / 2) { - *node = Some(unsafe { ObjPtr::new_from_addr(0xa) }); + *node = Some(ObjPtr::new_from_addr(0xa)); } let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { @@ -761,7 +761,7 @@ fn test_merkle_node_encoding() { None, NodeType::Extension(ExtNode( PartialPath(vec![0x1, 0x2, 0x3]), - unsafe { ObjPtr::new_from_addr(0x42) }, + ObjPtr::new_from_addr(0x42), None, )), ), diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index e5944829cb2c..0f7a323730f5 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -118,22 +118,19 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { assert!(compact_size > RESERVED); let mem_meta = Rc::new(DynamicMem::new(meta_size, 0x0)) as Rc; let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); - let compact_header: ObjPtr = unsafe { ObjPtr::new_from_addr(0x0) }; + let compact_header: ObjPtr = ObjPtr::new_from_addr(0x0); mem_meta.write( compact_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), ); - let compact_header = unsafe { - MummyObj::ptr_to_obj( - mem_meta.as_ref(), - compact_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap() - }; - + let compact_header = MummyObj::ptr_to_obj( + mem_meta.as_ref(), + compact_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(); let cache = shale::ObjCache::new(1); let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 0fa9e327fbd4..9af85f1b91a6 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -10,8 +10,6 @@ pub struct CompactHeader { } impl CompactHeader { - #![allow(dead_code)] - pub const MSIZE: u64 = 17; pub fn is_freed(&self) -> bool { self.is_freed @@ -138,25 +136,21 @@ impl CompactSpaceHeader { pub const MSIZE: u64 = 32; pub fn new(meta_base: u64, compact_base: u64) -> Self { - unsafe { - Self { - meta_space_tail: meta_base, - compact_space_tail: compact_base, - base_addr: ObjPtr::new_from_addr(meta_base), - alloc_addr: ObjPtr::new_from_addr(meta_base), - } + Self { + meta_space_tail: meta_base, + compact_space_tail: compact_base, + base_addr: ObjPtr::new_from_addr(meta_base), + alloc_addr: ObjPtr::new_from_addr(meta_base), } } fn into_fields(r: Obj) -> Result { - unsafe { - Ok(CompactSpaceHeaderSliced { - meta_space_tail: MummyObj::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, - compact_space_tail: MummyObj::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, - base_addr: MummyObj::slice(&r, 16, 8, r.base_addr)?, - alloc_addr: MummyObj::slice(&r, 24, 8, r.alloc_addr)?, - }) - } + Ok(CompactSpaceHeaderSliced { + meta_space_tail: MummyObj::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, + compact_space_tail: MummyObj::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, + base_addr: MummyObj::slice(&r, 16, 8, r.base_addr)?, + alloc_addr: MummyObj::slice(&r, 24, 8, r.alloc_addr)?, + }) } } @@ -169,14 +163,12 @@ impl MummyItem for CompactSpaceHeader { let compact_space_tail = u64::from_le_bytes(raw[8..16].try_into().unwrap()); let base_addr = u64::from_le_bytes(raw[16..24].try_into().unwrap()); let alloc_addr = u64::from_le_bytes(raw[24..].try_into().unwrap()); - unsafe { - Ok(Self { - meta_space_tail, - compact_space_tail, - base_addr: ObjPtr::new_from_addr(base_addr), - alloc_addr: ObjPtr::new_from_addr(alloc_addr), - }) - } + Ok(Self { + meta_space_tail, + compact_space_tail, + base_addr: ObjPtr::new_from_addr(base_addr), + alloc_addr: ObjPtr::new_from_addr(alloc_addr), + }) } fn dehydrated_len(&self) -> u64 { @@ -205,11 +197,9 @@ impl MummyItem for ObjPtrField { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - unsafe { - Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( - raw.deref().try_into().unwrap(), - )))) - } + Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( + raw.deref().try_into().unwrap(), + )))) } fn dehydrate(&self, to: &mut [u8]) { @@ -286,7 +276,7 @@ impl CompactSpaceInner { &self, ptr: ObjPtr, ) -> Result, ShaleError> { - unsafe { MummyObj::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } + MummyObj::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } fn get_data_ref( @@ -294,7 +284,7 @@ impl CompactSpaceInner { ptr: ObjPtr, len_limit: u64, ) -> Result, ShaleError> { - unsafe { MummyObj::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) } + MummyObj::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) } fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { @@ -310,9 +300,9 @@ impl CompactSpaceInner { debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); self.header.meta_space_tail.write(|r| **r -= desc_size); if desc_addr.addr != **self.header.meta_space_tail { - let desc_last = self - .get_descriptor(unsafe { ObjPtr::new_from_addr(**self.header.meta_space_tail) })?; - let mut desc = self.get_descriptor(unsafe { ObjPtr::new_from_addr(desc_addr.addr) })?; + let desc_last = + self.get_descriptor(ObjPtr::new_from_addr(**self.header.meta_space_tail))?; + let mut desc = self.get_descriptor(ObjPtr::new_from_addr(desc_addr.addr))?; desc.write(|r| *r = *desc_last); let mut header = self.get_header(ObjPtr::new(desc.haddr))?; header.write(|h| h.desc_addr = desc_addr); @@ -325,7 +315,7 @@ impl CompactSpaceInner { self.header .meta_space_tail .write(|r| **r += CompactDescriptor::MSIZE); - Ok(unsafe { ObjPtr::new_from_addr(addr) }) + Ok(ObjPtr::new_from_addr(addr)) } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { @@ -552,16 +542,18 @@ impl ShaleStore for CompactSpace { fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let inner = unsafe { &mut *self.inner.get() }; - let ptr: ObjPtr = unsafe { + let ptr: ObjPtr = ObjPtr::new_from_addr(if let Some(addr) = inner.alloc_from_freed(size)? { addr } else { inner.alloc_new(size)? - }) - }; - let mut u = inner.obj_cache.put(unsafe { - MummyObj::item_to_obj(inner.compact_space.as_ref(), ptr.addr(), size, item)? - }); + }); + let mut u = inner.obj_cache.put(MummyObj::item_to_obj( + inner.compact_space.as_ref(), + ptr.addr(), + size, + item, + )?); u.write(|_| {}).unwrap(); Ok(u) } diff --git a/shale/src/lib.rs b/shale/src/lib.rs index f536cb16a426..fae87e592a99 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -112,7 +112,7 @@ impl ObjPtr { } #[inline(always)] - pub unsafe fn new_from_addr(addr: u64) -> Self { + pub fn new_from_addr(addr: u64) -> Self { Self::new(addr) } } @@ -209,7 +209,7 @@ pub struct ObjRef<'a, T> { } impl<'a, T> ObjRef<'a, T> { - pub unsafe fn to_longlive(mut self) -> ObjRef<'static, T> { + pub fn to_longlive(mut self) -> ObjRef<'static, T> { ObjRef { inner: self.inner.take(), cache: ObjCache(self.cache.0.clone()), @@ -353,7 +353,7 @@ impl MummyObj { } #[inline(always)] - pub unsafe fn ptr_to_obj( + pub fn ptr_to_obj( store: &dyn MemStore, ptr: ObjPtr, len_limit: u64, @@ -366,7 +366,7 @@ impl MummyObj { } #[inline(always)] - pub unsafe fn item_to_obj( + pub fn item_to_obj( store: &dyn MemStore, addr: u64, len_limit: u64, @@ -379,7 +379,7 @@ impl MummyObj { } impl MummyObj { - unsafe fn new_from_slice( + fn new_from_slice( offset: u64, len_limit: u64, decoded: T, @@ -393,7 +393,7 @@ impl MummyObj { }) } - pub unsafe fn slice( + pub fn slice( s: &Obj, offset: u64, length: u64, @@ -436,11 +436,9 @@ impl MummyItem for ObjPtr { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - unsafe { - Ok(Self::new_from_addr(u64::from_le_bytes( - (**raw).try_into().unwrap(), - ))) - } + Ok(Self::new_from_addr(u64::from_le_bytes( + (**raw).try_into().unwrap(), + ))) } } From 53b18566968d052ea3b192ac2fde7999567b6581 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 7 Mar 2023 17:40:33 -0600 Subject: [PATCH 0072/1053] Add authors to firewood/Cargo.toml (#128) Still to do: update other Cargo.toml files based on actual contributions --- firewood/Cargo.toml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index a62e63855582..deaa1d806b72 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -2,7 +2,17 @@ name = "firewood" version = "0.0.1" edition = "2021" - +authors = [ + "Ted Yin (@Determinant) ", + "Dan Sover (@exdx) ", + "Hao Hao (@haohao-os) ", + "Gyuho Lee (@gyuho) ", + "Sam Batschelet (@hexfusion) ", + "Ron Kuris (@rkuris) ", +] +description = "Firewood is an embedded key-value store, optimized to store blockchain state." +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" [dependencies] growth-ring = { version = "0.3.0", path = "../growth-ring" } libaio-futures = {version = "0.2.2", path = "../libaio-futures" } From 55a017acc5f3faa59f9abd7733e08199755a3cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 09:39:20 -0500 Subject: [PATCH 0073/1053] build(deps): update typed-builder requirement from 0.13.0 to 0.14.0 (#144) Updates the requirements on [typed-builder](https://github.com/idanarye/rust-typed-builder) to permit the latest version. - [Release notes](https://github.com/idanarye/rust-typed-builder/releases) - [Changelog](https://github.com/idanarye/rust-typed-builder/blob/master/CHANGELOG.md) - [Commits](https://github.com/idanarye/rust-typed-builder/commits) --- updated-dependencies: - dependency-name: typed-builder dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index deaa1d806b72..4a596aadc84c 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -25,7 +25,7 @@ once_cell = "1.13.1" hex = "0.4.3" lru = "0.10.0" nix = "0.26.1" -typed-builder = "0.13.0" +typed-builder = "0.14.0" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } futures = "0.3.24" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } From 4c8f07491c5bf0d439d0735f2eeb165b75e81617 Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 10 Mar 2023 09:39:59 -0500 Subject: [PATCH 0074/1053] Update README.md (#141) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc37d077861a..30737c81b234 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ use-cases. Try running them via the command-line, via `cargo run --release --example simple`. ## CLI -Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](src/bin/README.md). +Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](fwdctl/README.md). ## Test ``` From 0e6e812d6b9c367e4f2933c425b990a3f22c3c46 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 7 Mar 2023 15:57:30 -0600 Subject: [PATCH 0075/1053] Refactor for safety This is a large change that removes a significant amount of unsafe code. The first step was to change PlainMem from `Rc>>` to `Rc>>` and properly use interior mutability. This had some serious consequences: Deref, as implemented in the MemView trait, is not possible to implement. There is a discussion of this problem on [this stack overflow post](https://stackoverflow.com/questions/57856047/how-can-i-implement-deref-for-a-struct-that-holds-an-rcrefcelltrait) To work around this, an `as_deref()` method that uses a type of `DerefReturn` was added to the `MemView` trait, and implemented. The largest source of diff noise was a change to use the new `as_deref()` method instead of either implicilty dereferencing things or using a hard dereference operator. On the flip side, ugly syntax like `&**rev.get_view(...)` turned into `rev.get_view(...).as_deref()` which seemed a little more palatable. Methods that mutate the state of the memory must use a `&mut self` rather than `&self`. This affects methods like `MemStore::write` and all the types that implement `MemStore`. There was also a need to impelement `DerefMut` on the child classes so that a mutable reference could be obtained; otherwise it's not possible to call methods that mutate the base object. A final small change was necessary to initialize the database by getting unique ownership during a reset; see `firewood/src/db.rs` for those changes. --- firewood/src/account.rs | 4 +- firewood/src/db.rs | 14 ++++--- firewood/src/dynamic_mem.rs | 26 ++++++++++--- firewood/src/merkle.rs | 38 +++++++++++-------- firewood/src/merkle_util.rs | 16 +++----- firewood/src/storage.rs | 45 ++++++++++++++++------ shale/src/compact.rs | 24 ++++++------ shale/src/lib.rs | 75 +++++++++++++++++++++++-------------- 8 files changed, 152 insertions(+), 90 deletions(-) diff --git a/firewood/src/account.rs b/firewood/src/account.rs index 17ceb1262e1f..a74684bacfa5 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -101,11 +101,11 @@ impl MummyItem for Blob { let raw = mem .get_view(addr, 4) .ok_or(ShaleError::LinearMemStoreError)?; - let len = u32::from_le_bytes(raw[..].try_into().unwrap()) as u64; + let len = u32::from_le_bytes(raw.as_deref()[..].try_into().unwrap()) as u64; let bytes = mem .get_view(addr + 4, len) .ok_or(ShaleError::LinearMemStoreError)?; - Ok(Self::Code(bytes.to_vec())) + Ok(Self::Code(bytes.as_deref().into())) } fn dehydrated_len(&self) -> u64 { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index a8a2ea9f8134..b646fdeb3f92 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -190,8 +190,8 @@ impl MummyItem for DBHeader { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(shale::ShaleError::LinearMemStoreError)?; - let acc_root = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let kv_root = u64::from_le_bytes(raw[8..].try_into().unwrap()); + let acc_root = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); + let kv_root = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); Ok(Self { acc_root: ObjPtr::new_from_addr(acc_root), kv_root: ObjPtr::new_from_addr(kv_root), @@ -540,7 +540,7 @@ impl DB { disk_requester.reg_cached_space(cached.blob.meta.as_ref()); disk_requester.reg_cached_space(cached.blob.payload.as_ref()); - let staging = Universe { + let mut staging = Universe { merkle: SubUniverse::new( Rc::new(StoreRevMut::new( cached.merkle.meta.clone() as Rc @@ -575,18 +575,20 @@ impl DB { if reset { // initialize space headers - staging.merkle.meta.write( + let initializer = Rc::::make_mut(&mut staging.merkle.meta); + initializer.write( merkle_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, SPACE_RESERVED, )), ); - staging.merkle.meta.write( + initializer.write( db_header.addr(), &shale::to_dehydrated(&DBHeader::new_empty()), ); - staging.blob.meta.write( + let initializer = Rc::::make_mut(&mut staging.blob.meta); + initializer.write( blob_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index 999ba923c519..e3ad4bbaf4fd 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -1,5 +1,5 @@ use std::cell::UnsafeCell; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use shale::*; @@ -26,7 +26,11 @@ impl DynamicMem { } impl MemStore for DynamicMem { - fn get_view(&self, offset: u64, length: u64) -> Option> { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { let offset = offset as usize; let length = length as usize; let size = offset + length; @@ -44,14 +48,14 @@ impl MemStore for DynamicMem { })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(DynamicMemShared(Self { space: self.space.clone(), id: self.id, }))) } - fn write(&self, offset: u64, change: &[u8]) { + fn write(&mut self, offset: u64, change: &[u8]) { let offset = offset as usize; let length = change.len(); let size = offset + length; @@ -89,4 +93,16 @@ impl Deref for DynamicMemShared { } } -impl MemView for DynamicMemView {} +impl DerefMut for DynamicMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl MemView for DynamicMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() + } +} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7c987eeeb76a..5ee637342dd9 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -58,7 +58,9 @@ impl MummyItem for Hash { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - Ok(Self(raw[..Self::MSIZE as usize].try_into().unwrap())) + Ok(Self( + raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), + )) } fn dehydrated_len(&self) -> u64 { @@ -467,24 +469,26 @@ impl MummyItem for Node { let meta_raw = mem .get_view(addr, META_SIZE) .ok_or(ShaleError::LinearMemStoreError)?; - let attrs = meta_raw[32]; + let attrs = meta_raw.as_deref()[32]; let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None } else { - Some(Hash(meta_raw[0..32].try_into().map_err(dec_err)?)) + Some(Hash( + meta_raw.as_deref()[0..32].try_into().map_err(dec_err)?, + )) }; let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { None } else { Some(attrs & Node::ETH_RLP_LONG_BIT != 0) }; - match meta_raw[33] { + match meta_raw.as_deref()[33] { Self::BRANCH_NODE => { let branch_header_size = NBRANCH as u64 * 8 + 4; let node_raw = mem .get_view(addr + META_SIZE, branch_header_size) .ok_or(ShaleError::LinearMemStoreError)?; - let mut cur = Cursor::new(node_raw.deref()); + let mut cur = Cursor::new(node_raw.as_deref()); let mut chd = [None; NBRANCH]; let mut buff = [0; 8]; for chd in chd.iter_mut() { @@ -504,7 +508,7 @@ impl MummyItem for Node { Some(Data( mem.get_view(addr + META_SIZE + branch_header_size, raw_len) .ok_or(ShaleError::LinearMemStoreError)? - .to_vec(), + .as_deref(), )) }; let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); @@ -519,7 +523,7 @@ impl MummyItem for Node { let rlp_len_raw = mem .get_view(offset + cur_rlp_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; - cur = Cursor::new(rlp_len_raw.deref()); + cur = Cursor::new(rlp_len_raw.as_deref()); cur.read_exact(&mut buff) .map_err(|_| ShaleError::DecodeError)?; let rlp_len = buff[0] as u64; @@ -528,7 +532,7 @@ impl MummyItem for Node { let rlp_raw = mem .get_view(offset + cur_rlp_len, rlp_len) .ok_or(ShaleError::LinearMemStoreError)?; - let rlp: Vec = rlp_raw[0..].to_vec(); + let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); *chd_rlp = Some(rlp); cur_rlp_len += rlp_len } @@ -549,7 +553,7 @@ impl MummyItem for Node { let node_raw = mem .get_view(addr + META_SIZE, ext_header_size) .ok_or(ShaleError::LinearMemStoreError)?; - let mut cur = Cursor::new(node_raw.deref()); + let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 8]; cur.read_exact(&mut buff[..1]) .map_err(|_| ShaleError::DecodeError)?; @@ -559,7 +563,8 @@ impl MummyItem for Node { let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( &mem.get_view(addr + META_SIZE + ext_header_size, path_len) - .ok_or(ShaleError::LinearMemStoreError)?, + .ok_or(ShaleError::LinearMemStoreError)? + .as_deref(), ) .collect(); let (path, _) = PartialPath::decode(nibbles); @@ -568,7 +573,7 @@ impl MummyItem for Node { let rlp_len_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len, 1) .ok_or(ShaleError::LinearMemStoreError)?; - cur = Cursor::new(rlp_len_raw.deref()); + cur = Cursor::new(rlp_len_raw.as_deref()); cur.read_exact(&mut buff) .map_err(|_| ShaleError::DecodeError)?; let rlp_len = buff[0] as u64; @@ -576,7 +581,7 @@ impl MummyItem for Node { let rlp_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len + 1, rlp_len) .ok_or(ShaleError::LinearMemStoreError)?; - Some(rlp_raw[0..].to_vec()) + Some(rlp_raw.as_deref()[0..].to_vec()) } else { None }; @@ -592,7 +597,7 @@ impl MummyItem for Node { let node_raw = mem .get_view(addr + META_SIZE, leaf_header_size) .ok_or(ShaleError::LinearMemStoreError)?; - let mut cur = Cursor::new(node_raw.deref()); + let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 4]; cur.read_exact(&mut buff[..1]) .map_err(|_| ShaleError::DecodeError)?; @@ -603,9 +608,10 @@ impl MummyItem for Node { let remainder = mem .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) .ok_or(ShaleError::LinearMemStoreError)?; - let nibbles: Vec<_> = to_nibbles(&remainder[..path_len as usize]).collect(); + let nibbles: Vec<_> = + to_nibbles(&remainder.as_deref()[..path_len as usize]).collect(); let (path, _) = PartialPath::decode(nibbles); - let value = Data(remainder[path_len as usize..].to_vec()); + let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); Ok(Self::new_from_hash( root_hash, eth_rlp_long, @@ -732,7 +738,7 @@ fn test_merkle_node_encoding() { bytes.resize(node.dehydrated_len() as usize, 0); node.dehydrate(&mut bytes); - let mem = shale::PlainMem::new(bytes.len() as u64, 0x0); + let mut mem = shale::PlainMem::new(bytes.len() as u64, 0x0); mem.write(0, &bytes); println!("{bytes:?}"); let node_ = Node::hydrate(0, &mem).unwrap(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 0f7a323730f5..c21f2c32bf42 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -116,21 +116,17 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { const RESERVED: u64 = 0x1000; assert!(meta_size > RESERVED); assert!(compact_size > RESERVED); - let mem_meta = Rc::new(DynamicMem::new(meta_size, 0x0)) as Rc; - let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); + let mut dm = DynamicMem::new(meta_size, 0); let compact_header: ObjPtr = ObjPtr::new_from_addr(0x0); - - mem_meta.write( + dm.write( compact_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), ); + let compact_header = + MummyObj::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); + let mem_meta = Rc::new(dm) as Rc; + let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); - let compact_header = MummyObj::ptr_to_obj( - mem_meta.as_ref(), - compact_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(); let cache = shale::ObjCache::new(1); let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index 3365fbca06b5..67c861f152b7 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -4,7 +4,7 @@ use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::fmt; use std::num::NonZeroUsize; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; @@ -323,16 +323,20 @@ impl StoreRevShared { } impl MemStore for StoreRevShared { - fn get_view(&self, offset: u64, length: u64) -> Option> { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { let data = self.0.get_slice(offset, length)?; Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(StoreShared(self.clone()))) } - fn write(&self, _offset: u64, _change: &[u8]) { + fn write(&mut self, _offset: u64, _change: &[u8]) { // StoreRevShared is a read-only view version of MemStore // Writes could be induced by lazy hashing and we can just ignore those } @@ -353,7 +357,13 @@ impl Deref for StoreRef { } } -impl MemView for StoreRef {} +impl MemView for StoreRef { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.deref().to_vec() + } +} struct StoreShared(S); @@ -364,6 +374,12 @@ impl Deref for StoreShared { } } +impl DerefMut for StoreShared { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + struct StoreRevMutDelta { pages: HashMap>, plain: Ash, @@ -419,7 +435,11 @@ impl StoreRevMut { } impl MemStore for StoreRevMut { - fn get_view(&self, offset: u64, length: u64) -> Option> { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { let data = if length == 0 { Vec::new() } else { @@ -458,11 +478,11 @@ impl MemStore for StoreRevMut { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(StoreShared(self.clone()))) } - fn write(&self, offset: u64, mut change: &[u8]) { + fn write(&mut self, offset: u64, mut change: &[u8]) { let length = change.len() as u64; let end = offset + length - 1; let s_pid = offset >> PAGE_SIZE_NBIT; @@ -553,13 +573,16 @@ fn test_from_ash() { let z = Rc::new(ZeroStore::new()); let rev = StoreRevShared::from_ash(z, &writes); println!("{rev:?}"); - assert_eq!(&**rev.get_view(min, max - min).unwrap(), &canvas); + assert_eq!( + rev.get_view(min, max - min).as_deref().unwrap().as_deref(), + canvas + ); for _ in 0..2 * n { let l = rng.gen_range(min..max); let r = rng.gen_range(l + 1..max); assert_eq!( - &**rev.get_view(l, r - l).unwrap(), - &canvas[(l - min) as usize..(r - min) as usize] + rev.get_view(l, r - l).as_deref().unwrap().as_deref(), + canvas[(l - min) as usize..(r - min) as usize] ); } } diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 9af85f1b91a6..cccddb2f3484 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -25,9 +25,9 @@ impl MummyItem for CompactHeader { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - let payload_size = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let is_freed = raw[8] != 0; - let desc_addr = u64::from_le_bytes(raw[9..17].try_into().unwrap()); + let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); + let is_freed = raw.as_deref()[8] != 0; + let desc_addr = u64::from_le_bytes(raw.as_deref()[9..17].try_into().unwrap()); Ok(Self { payload_size, is_freed, @@ -60,7 +60,7 @@ impl MummyItem for CompactFooter { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - let payload_size = u64::from_le_bytes(raw.deref().try_into().unwrap()); + let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); Ok(Self { payload_size }) } @@ -90,8 +90,8 @@ impl MummyItem for CompactDescriptor { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - let payload_size = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let haddr = u64::from_le_bytes(raw[8..].try_into().unwrap()); + let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); + let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); Ok(Self { payload_size, haddr, @@ -159,10 +159,10 @@ impl MummyItem for CompactSpaceHeader { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - let meta_space_tail = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let compact_space_tail = u64::from_le_bytes(raw[8..16].try_into().unwrap()); - let base_addr = u64::from_le_bytes(raw[16..24].try_into().unwrap()); - let alloc_addr = u64::from_le_bytes(raw[24..].try_into().unwrap()); + let meta_space_tail = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); + let compact_space_tail = u64::from_le_bytes(raw.as_deref()[8..16].try_into().unwrap()); + let base_addr = u64::from_le_bytes(raw.as_deref()[16..24].try_into().unwrap()); + let alloc_addr = u64::from_le_bytes(raw.as_deref()[24..].try_into().unwrap()); Ok(Self { meta_space_tail, compact_space_tail, @@ -198,7 +198,7 @@ impl MummyItem for ObjPtrField { .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( - raw.deref().try_into().unwrap(), + raw.as_deref().try_into().unwrap(), )))) } @@ -237,7 +237,7 @@ impl MummyItem for U64Field { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; - Ok(Self(u64::from_le_bytes(raw.deref().try_into().unwrap()))) + Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) } fn dehydrated_len(&self) -> u64 { diff --git a/shale/src/lib.rs b/shale/src/lib.rs index fae87e592a99..8d330991f987 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -1,9 +1,10 @@ -use std::cell::UnsafeCell; +use std::borrow::BorrowMut; +use std::cell::{RefCell, UnsafeCell}; use std::collections::{HashMap, HashSet}; use std::fmt; use std::marker::PhantomData; use std::num::NonZeroUsize; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; pub mod compact; @@ -40,7 +41,10 @@ impl std::fmt::Debug for DiskWrite { } /// A handle that pins and provides a readable access to a portion of the linear memory image. -pub trait MemView: Deref {} +pub trait MemView { + type DerefReturn: Deref; + fn as_deref(&self) -> Self::DerefReturn; +} /// In-memory store that offers access to intervals from a linear byte space, which is usually /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear @@ -49,12 +53,13 @@ pub trait MemView: Deref {} pub trait MemStore { /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them /// directly accessible. - fn get_view(&self, offset: u64, length: u64) -> Option>; + fn get_view(&self, offset: u64, length: u64) + -> Option>>>; /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Option>>; + fn get_shared(&self) -> Option>>; /// Write the `change` to the portion of the linear space starting at `offset`. The change /// should be immediately visible to all `MemView` associated to this linear space. - fn write(&self, offset: u64, change: &[u8]); + fn write(&mut self, offset: u64, change: &[u8]); /// Returns the identifier of this storage space. fn id(&self) -> SpaceID; } @@ -125,6 +130,8 @@ pub trait TypedView: Deref { fn get_offset(&self) -> u64; /// Access it as a [MemStore] object. fn get_mem_store(&self) -> &dyn MemStore; + /// Access it as a mutable MemStore object + fn get_mut_mem_store(&mut self) -> &mut dyn MemStore; /// Estimate the serialized length of the current type content. It should not be smaller than /// the actually length. fn estimate_mem_image(&self) -> Option; @@ -180,9 +187,9 @@ impl Obj { if let Some(new_value_len) = self.dirty.take() { let mut new_value = vec![0; new_value_len as usize]; self.value.write_mem_image(&mut new_value); - self.value - .get_mem_store() - .write(self.value.get_offset(), &new_value); + let offset = self.value.get_offset(); + let bx: &mut dyn MemStore = self.value.get_mut_mem_store(); + bx.write(offset, &new_value); } } } @@ -283,7 +290,7 @@ pub fn to_dehydrated(item: &dyn MummyItem) -> Vec { /// should be useful for most applications. pub struct MummyObj { decoded: T, - mem: Box>, + mem: Box>, offset: u64, len_limit: u64, } @@ -304,6 +311,10 @@ impl TypedView for MummyObj { &**self.mem } + fn get_mut_mem_store(&mut self) -> &mut dyn MemStore { + &mut **self.mem + } + fn estimate_mem_image(&self) -> Option { let len = self.decoded.dehydrated_len(); if len > self.len_limit { @@ -436,8 +447,10 @@ impl MummyItem for ObjPtr { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; + let addrdyn = raw.deref(); + let addrvec = addrdyn.as_deref(); Ok(Self::new_from_addr(u64::from_le_bytes( - (**raw).try_into().unwrap(), + addrvec.try_into().unwrap(), ))) } } @@ -446,28 +459,28 @@ impl MummyItem for ObjPtr { /// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write /// your own [MemStore] implementation. pub struct PlainMem { - space: Rc>>, + space: Rc>>, id: SpaceID, } impl PlainMem { pub fn new(size: u64, id: SpaceID) -> Self { - let mut space = Vec::new(); + let mut space: Vec = Vec::new(); space.resize(size as usize, 0); - let space = Rc::new(UnsafeCell::new(space)); + let space = Rc::new(RefCell::new(space)); Self { space, id } } - - fn get_space_mut(&self) -> &mut Vec { - unsafe { &mut *self.space.get() } - } } impl MemStore for PlainMem { - fn get_view(&self, offset: u64, length: u64) -> Option> { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { let offset = offset as usize; let length = length as usize; - if offset + length > self.get_space_mut().len() { + if offset + length > self.space.borrow().len() { None } else { Some(Box::new(PlainMemView { @@ -481,17 +494,18 @@ impl MemStore for PlainMem { } } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(PlainMemShared(Self { space: self.space.clone(), id: self.id, }))) } - fn write(&self, offset: u64, change: &[u8]) { + fn write(&mut self, offset: u64, change: &[u8]) { let offset = offset as usize; let length = change.len(); - self.get_space_mut()[offset..offset + length].copy_from_slice(change) + let mut vect = self.space.deref().borrow_mut(); + vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); } fn id(&self) -> SpaceID { @@ -507,10 +521,9 @@ struct PlainMemView { struct PlainMemShared(PlainMem); -impl Deref for PlainMemView { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.mem.get_space_mut()[self.offset..self.offset + self.length] +impl DerefMut for PlainMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.borrow_mut() } } @@ -521,7 +534,13 @@ impl Deref for PlainMemShared { } } -impl MemView for PlainMemView {} +impl MemView for PlainMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() + } +} struct ObjCacheInner { cached: lru::LruCache, Obj>, From c8d355a6fc162f92e3d6378e500e7e7cd7da3688 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 8 Mar 2023 11:50:14 -0600 Subject: [PATCH 0076/1053] [perf]Use zero cost abstractions Current implementation uses dynamic dispatch (vtables). This causes a bunch of vtables and special cases, when in reality probably only one concrete type will ever get used. The biggest advantage here is that now MemStore requires Debug, so I can actually print a MemStore in the debugger or with print statements. --- firewood/src/account.rs | 2 +- firewood/src/db.rs | 10 +++++----- firewood/src/dynamic_mem.rs | 1 + firewood/src/merkle.rs | 4 ++-- firewood/src/merkle_util.rs | 2 +- firewood/src/storage.rs | 15 +++++++++------ shale/src/compact.rs | 32 ++++++++++++++++---------------- shale/src/lib.rs | 14 ++++++++------ 8 files changed, 43 insertions(+), 37 deletions(-) diff --git a/firewood/src/account.rs b/firewood/src/account.rs index a74684bacfa5..0c1b1c000b8e 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -97,7 +97,7 @@ pub enum Blob { impl MummyItem for Blob { // currently there is only one variant of Blob: Code - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, 4) .ok_or(ShaleError::LinearMemStoreError)?; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b646fdeb3f92..7c007497008f 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -186,7 +186,7 @@ impl DBHeader { } impl MummyItem for DBHeader { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(shale::ShaleError::LinearMemStoreError)?; @@ -598,8 +598,8 @@ impl DB { } let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { - let merkle_meta_ref = staging.merkle.meta.as_ref() as &dyn MemStore; - let blob_meta_ref = staging.blob.meta.as_ref() as &dyn MemStore; + let merkle_meta_ref = staging.merkle.meta.as_ref(); + let blob_meta_ref = staging.blob.meta.as_ref(); ( MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), @@ -790,8 +790,8 @@ impl DB { let space = &inner.revisions[nback - 1]; let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { - let merkle_meta_ref = &space.merkle.meta as &dyn MemStore; - let blob_meta_ref = &space.blob.meta as &dyn MemStore; + let merkle_meta_ref = &space.merkle.meta; + let blob_meta_ref = &space.blob.meta; ( MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index e3ad4bbaf4fd..22ac07401bd0 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -9,6 +9,7 @@ pub type SpaceID = u8; /// Purely volatile, dynamically allocated vector-based implementation for [MemStore]. This is similar to /// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is /// not enough. +#[derive(Debug)] pub struct DynamicMem { space: Rc>>, id: SpaceID, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5ee637342dd9..1c4d528a7a69 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -54,7 +54,7 @@ impl std::ops::Deref for Hash { } impl MummyItem for Hash { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -463,7 +463,7 @@ impl Node { } impl MummyItem for Node { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let dec_err = |_| ShaleError::DecodeError; const META_SIZE: u64 = 32 + 1 + 1; let meta_raw = mem diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index c21f2c32bf42..e6d3e94cb74d 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -124,7 +124,7 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { ); let compact_header = MummyObj::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); - let mem_meta = Rc::new(dm) as Rc; + let mem_meta = Rc::new(dm); let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); let cache = shale::ObjCache::new(1); diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index 67c861f152b7..b30f80488760 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -2,7 +2,7 @@ use std::cell::{RefCell, RefMut}; use std::collections::HashMap; -use std::fmt; +use std::fmt::{self, Debug}; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::rc::Rc; @@ -24,7 +24,7 @@ pub(crate) const PAGE_SIZE_NBIT: u64 = 12; pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; -pub trait MemStoreR { +pub trait MemStoreR: Debug { fn get_slice(&self, offset: u64, length: u64) -> Option>; fn id(&self) -> SpaceID; } @@ -380,12 +380,13 @@ impl DerefMut for StoreShared { } } +#[derive(Debug)] struct StoreRevMutDelta { pages: HashMap>, plain: Ash, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct StoreRevMut { prev: Rc, deltas: Rc>, @@ -528,7 +529,7 @@ impl MemStore for StoreRevMut { } #[cfg(test)] -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ZeroStore(Rc<()>); #[cfg(test)] @@ -598,6 +599,7 @@ pub struct StoreConfig { rootfd: Fd, } +#[derive(Debug)] struct CachedSpaceInner { cached_pages: lru::LruCache>, pinned_pages: HashMap)>, @@ -605,7 +607,7 @@ struct CachedSpaceInner { disk_buffer: DiskBufferRequester, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct CachedSpace { inner: Rc>, space_id: SpaceID, @@ -767,6 +769,7 @@ impl MemStoreR for CachedSpace { } } +#[derive(Debug)] pub struct FilePool { files: parking_lot::Mutex>>, file_nbit: u64, @@ -1187,7 +1190,7 @@ impl DiskBuffer { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DiskBufferRequester { sender: mpsc::Sender, } diff --git a/shale/src/compact.rs b/shale/src/compact.rs index cccddb2f3484..bd7ee3de5dc0 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -21,7 +21,7 @@ impl CompactHeader { } impl MummyItem for CompactHeader { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -56,7 +56,7 @@ impl CompactFooter { } impl MummyItem for CompactFooter { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -86,7 +86,7 @@ impl CompactDescriptor { } impl MummyItem for CompactDescriptor { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -155,7 +155,7 @@ impl CompactSpaceHeader { } impl MummyItem for CompactSpaceHeader { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -193,7 +193,7 @@ impl ObjPtrField { } impl MummyItem for ObjPtrField { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -233,7 +233,7 @@ impl U64Field { } impl MummyItem for U64Field { - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -262,16 +262,16 @@ impl std::ops::DerefMut for U64Field { } } -struct CompactSpaceInner { - meta_space: Rc, - compact_space: Rc, +struct CompactSpaceInner { + meta_space: Rc, + compact_space: Rc, header: CompactSpaceHeaderSliced, obj_cache: super::ObjCache, alloc_max_walk: u64, regn_nbit: u64, } -impl CompactSpaceInner { +impl CompactSpaceInner { fn get_descriptor( &self, ptr: ObjPtr, @@ -511,14 +511,14 @@ impl CompactSpaceInner { } } -pub struct CompactSpace { - inner: UnsafeCell>, +pub struct CompactSpace { + inner: UnsafeCell>, } -impl CompactSpace { +impl CompactSpace { pub fn new( - meta_space: Rc, - compact_space: Rc, + meta_space: Rc, + compact_space: Rc, header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, @@ -538,7 +538,7 @@ impl CompactSpace { } } -impl ShaleStore for CompactSpace { +impl ShaleStore for CompactSpace { fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let inner = unsafe { &mut *self.inner.get() }; diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 8d330991f987..1d1234bfc677 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -2,6 +2,7 @@ use std::borrow::BorrowMut; use std::cell::{RefCell, UnsafeCell}; use std::collections::{HashMap, HashSet}; use std::fmt; +use std::fmt::Debug; use std::marker::PhantomData; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; @@ -50,7 +51,7 @@ pub trait MemView { /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear /// persistent store. Reads could trigger disk reads to bring data into memory, but writes will /// *only* be visible in memory (it does not write back to the disk). -pub trait MemStore { +pub trait MemStore: Debug { /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them /// directly accessible. fn get_view(&self, offset: u64, length: u64) @@ -272,7 +273,7 @@ pub trait ShaleStore { pub trait MummyItem { fn dehydrated_len(&self) -> u64; fn dehydrate(&self, to: &mut [u8]); - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result + fn hydrate(addr: u64, mem: &T) -> Result where Self: Sized; fn is_mem_mapped(&self) -> bool { @@ -338,7 +339,7 @@ impl TypedView for MummyObj { impl MummyObj { #[inline(always)] - fn new(offset: u64, len_limit: u64, space: &dyn MemStore) -> Result { + fn new(offset: u64, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; Ok(Self { offset, @@ -364,8 +365,8 @@ impl MummyObj { } #[inline(always)] - pub fn ptr_to_obj( - store: &dyn MemStore, + pub fn ptr_to_obj( + store: &U, ptr: ObjPtr, len_limit: u64, ) -> Result, ShaleError> { @@ -443,7 +444,7 @@ impl MummyItem for ObjPtr { .unwrap(); } - fn hydrate(addr: u64, mem: &dyn MemStore) -> Result { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::LinearMemStoreError)?; @@ -458,6 +459,7 @@ impl MummyItem for ObjPtr { /// Purely volatile, vector-based implementation for [MemStore]. This is good for testing or trying /// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write /// your own [MemStore] implementation. +#[derive(Debug)] pub struct PlainMem { space: Rc>>, id: SpaceID, From b44d2bc7a809ea416c5a699fe298ac31336c1352 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 9 Mar 2023 06:23:37 +0000 Subject: [PATCH 0077/1053] range proof: handle special cases --- firewood/src/merkle.rs | 20 +++++- firewood/src/proof.rs | 132 +++++++++++++++++++++++++++++++-------- firewood/tests/merkle.rs | 58 ++++++++--------- 3 files changed, 154 insertions(+), 56 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1c4d528a7a69..acfde2bfcfa0 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -154,7 +154,13 @@ fn test_partial_path_encoding() { } #[derive(PartialEq, Eq, Clone)] -struct Data(Vec); +pub struct Data(Vec); + +impl Data { + pub fn value(&self) -> &Vec { + &self.0 + } +} impl std::ops::Deref for Data { type Target = [u8]; @@ -254,6 +260,10 @@ impl BranchNode { } } + pub fn value(&self) -> &Option { + &self.value + } + pub fn chd(&self) -> &[Option>; NBRANCH] { &self.chd } @@ -292,6 +302,14 @@ impl LeafNode { pub fn new(path: Vec, data: Vec) -> Self { LeafNode(PartialPath(path), Data(data)) } + + pub fn path(&self) -> &PartialPath { + &self.0 + } + + pub fn data(&self) -> &Data { + &self.1 + } } #[derive(PartialEq, Eq, Clone)] diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 21887f5e2ce7..c0d7ac607898 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -25,6 +25,7 @@ pub enum ProofError { InconsistentProofData, NonMonotonicIncreaseRange, RangeHasDeletion, + InvalidData, InvalidProof, InvalidEdgeKeys, InconsistentEdgeKeys, @@ -32,7 +33,6 @@ pub enum ProofError { NodeNotInTrie, InvalidNode(MerkleError), EmptyRange, - EmptyKeyValues, BlobStoreError(BlobError), SystemError(Errno), InvalidRootHash, @@ -43,7 +43,6 @@ impl From for ProofError { match d { DataStoreError::InsertionError => ProofError::NodesInsertionError, DataStoreError::RootHashError => ProofError::InvalidRootHash, - DataStoreError::ProofEmptyKeyValuesError => ProofError::EmptyKeyValues, _ => ProofError::InvalidProof, } } @@ -71,6 +70,7 @@ impl fmt::Display for ProofError { ProofError::InconsistentProofData => write!(f, "inconsistent proof data"), ProofError::NonMonotonicIncreaseRange => write!(f, "nonmonotonic range increase"), ProofError::RangeHasDeletion => write!(f, "range has deletion"), + ProofError::InvalidData => write!(f, "invalid data"), ProofError::InvalidProof => write!(f, "invalid proof"), ProofError::InvalidEdgeKeys => write!(f, "invalid edge keys"), ProofError::InconsistentEdgeKeys => write!(f, "inconsistent edge keys"), @@ -78,7 +78,6 @@ impl fmt::Display for ProofError { ProofError::NodeNotInTrie => write!(f, "node not in trie"), ProofError::InvalidNode(e) => write!(f, "invalid node: {e:?}"), ProofError::EmptyRange => write!(f, "empty range"), - ProofError::EmptyKeyValues => write!(f, "empty keys or values provided"), ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), ProofError::SystemError(e) => write!(f, "system error: {e:?}"), ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), @@ -235,14 +234,12 @@ impl Proof { return Err(ProofError::InconsistentProofData); } - if keys.is_empty() { - return Err(ProofError::EmptyKeyValues); - } - // Ensure the received batch is monotonic increasing and contains no deletions - for n in 0..keys.len() - 1 { - if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { - return Err(ProofError::NonMonotonicIncreaseRange); + if keys.len() > 0 { + for n in 0..keys.len() - 1 { + if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { + return Err(ProofError::NonMonotonicIncreaseRange); + } } } @@ -252,23 +249,50 @@ impl Proof { } } + // Use in-memory merkle + let mut merkle_setup = new_merkle(0x10000, 0x10000); // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if self.0.is_empty() { - // Use in-memory merkle - let mut merkle = new_merkle(0x10000, 0x10000); for (index, k) in keys.iter().enumerate() { - merkle.insert(k, vals[index].as_ref().to_vec())?; + merkle_setup.insert(k, vals[index].as_ref().to_vec())?; } - let merkle_root = &*merkle.root_hash()?; + let merkle_root = &*merkle_setup.root_hash()?; if merkle_root != &root_hash { return Err(ProofError::InvalidProof); } return Ok(false); } - // TODO(Hao): handle special case when there is a provided edge proof but zero key/value pairs. - // TODO(Hao): handle special case when there is only one element and two edge keys are same. + // Special case when there is a provided edge proof but zero key/value pairs, + // ensure there are no more accounts / slots in the trie. + if keys.len() == 0 { + let data = + self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; + + if data.is_some() { + // No more entries should be available + return Err(ProofError::InvalidData); + } + return Ok(false); + } + // Special case, there is only one element and two edge keys are same. + // In this case, we can't construct two edge paths. So handle it here. + if keys.len() == 1 && compare(first_key.as_ref(), last_key.as_ref()).is_eq() { + let data = + self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, false)?; + + if compare(first_key.as_ref(), keys[0].as_ref()).is_ne() { + // correct proof but invalid key + return Err(ProofError::InvalidEdgeKeys); + } + + if data.is_none() || compare(&data.unwrap(), vals[0].as_ref()).is_ne() { + // correct proof but invalid data + return Err(ProofError::InvalidData); + } + return Ok(true); + } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if compare(first_key.as_ref(), last_key.as_ref()).is_ge() { @@ -283,7 +307,6 @@ impl Proof { // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - let mut merkle_setup = new_merkle(0x100000, 0x100000); self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; // Pass the root node here, the second path will be merged @@ -324,7 +347,8 @@ impl Proof { root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, - ) -> Result<(), ProofError> { + ) -> Result>, ProofError> { + // Start with the sentinel root let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; @@ -343,7 +367,8 @@ impl Proof { .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. - let (mut chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof)?; + let (mut chd_ptr, sub_proof, size) = + self.decode_node(merkle, cur_key, cur_proof, false)?; // Link the child to the parent based on the node type. match &u_ref.inner() { NodeType::Branch(n) => { @@ -406,16 +431,43 @@ impl Proof { Some(p) => { // Return when reaching the end of the key. if key_index == chunks.len() { + let mut data: Vec = Vec::new(); + // Decode the last subproof to get the value. + if p.hash.is_some() { + let proof = proofs_map + .get(&p.hash.unwrap()) + .ok_or(ProofError::ProofNodeMissing)?; + let (chd_ptr, _, _) = self.decode_node(merkle, cur_key, proof, true)?; + + if chd_ptr.is_some() { + u_ref = merkle + .get_node(chd_ptr.unwrap()) + .map_err(|_| ProofError::DecodeError)?; + match &u_ref.inner() { + NodeType::Branch(n) => { + if let Some(v) = n.value() { + data = v.value().clone(); + } + } + NodeType::Leaf(n) => { + if n.path().len() == 0 { + data = n.data().value().clone(); + } + } + _ => (), + } + } + } // Release the handle to the node. drop(u_ref); - return Ok(()); + return Ok(Some(data)); } // The trie doesn't contain the key. if p.hash.is_none() { drop(u_ref); if allow_non_existent_node { - return Ok(()); + return Ok(None); } return Err(ProofError::NodeNotInTrie); } @@ -429,7 +481,7 @@ impl Proof { None => { drop(u_ref); if allow_non_existent_node { - return Ok(()); + return Ok(None); } return Err(ProofError::NodeNotInTrie); } @@ -438,11 +490,17 @@ impl Proof { } /// Decode the RLP value to generate the corresponding type of node, and locate the subproof. + /// + /// # Arguments + /// + /// * `end_node` - A boolean indicates whether this is the ebd node to decode, thus no `key` + /// to be present. fn decode_node( &self, merkle: &Merkle, key: &[u8], buf: &[u8], + end_node: bool, ) -> Result<(Option>, Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count().unwrap(); @@ -491,8 +549,17 @@ impl Proof { } } BRANCH_NODE_SIZE => { - if key.is_empty() { - return Err(ProofError::NoSuchNode); + // Extract the value of the branch node. + let mut value: Option> = None; + let data_rlp = rlp.at(NBRANCH).unwrap(); + // Skip if rlp is empty data + if !data_rlp.is_empty() { + let data = if data_rlp.is_data() { + data_rlp.as_val::>().unwrap() + } else { + data_rlp.as_raw().to_vec() + }; + value = Some(data); } // Record rlp values of all children. @@ -509,18 +576,29 @@ impl Proof { chd_eth_rlp[i] = Some(data); } } + // If the node is the last one to be decoded, then no subproof to be extracted. + if end_node { + let chd = [None; NBRANCH]; + let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp)); + let branch_ptr = merkle + .new_node(Node::new(t)) + .map_err(|_| ProofError::ProofNodeMissing)?; + return Ok((Some(branch_ptr.as_ptr()), None, 1)); + } else if key.is_empty() { + return Err(ProofError::NoSuchNode); + } - // Subproof with the given key must exist. + // Check if the subproof with the given key exist. let index = key[0] as usize; let data: Vec = if chd_eth_rlp[index].is_none() { - return Err(ProofError::DecodeError); + return Ok((None, None, 0)); } else { chd_eth_rlp[index].clone().unwrap() }; let subproof = self.generate_subproof(data)?; let chd = [None; NBRANCH]; - let t = NodeType::Branch(BranchNode::new(chd, None, chd_eth_rlp)); + let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp)); let branch_ptr = merkle .new_node(Node::new(t)) .map_err(|_| ProofError::ProofNodeMissing)?; diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 6dc5a1f4371b..07bd5acd916d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -363,14 +363,20 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { proof.concat_proofs(end_proof); - let mut keys = Vec::new(); - let mut vals = Vec::new(); + let mut keys: Vec<&str> = Vec::new(); + let mut vals: Vec<&str> = Vec::new(); for i in start..=end { keys.push(&items[i].0); vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + merkle.verify_range_proof( + &proof, + std::str::from_utf8(&[0x2]).unwrap(), + std::str::from_utf8(&[0x8]).unwrap(), + keys, + vals, + )?; Ok(()) } @@ -432,14 +438,11 @@ fn test_one_element_range_proof() -> Result<(), DataStoreError> { let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; - let end = &items.len() - 1; + // start and end nodes are the same + let end = 0; - let mut start_proof = merkle.prove(&items[start].0)?; + let start_proof = merkle.prove(&items[start].0)?; assert!(!start_proof.0.is_empty()); - let end_proof = merkle.prove(&items[start].0)?; // start and end nodes are the same - assert!(!end_proof.0.is_empty()); - - start_proof.concat_proofs(end_proof); let mut keys = Vec::new(); let mut vals = Vec::new(); @@ -465,7 +468,7 @@ fn test_all_elements_proof() -> Result<(), DataStoreError> { let mut proof = merkle.prove(&items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0)?; // start and end nodes are the same + let end_proof = merkle.prove(&items[end].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -483,30 +486,29 @@ fn test_all_elements_proof() -> Result<(), DataStoreError> { } #[test] -// Special case when there is a provided edge proof but zero key/value pairs. -fn test_missing_key_value_pairs() -> Result<(), DataStoreError> { - let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; - items.sort(); +// Tests the range proof with "no" element. The first edge proof must +// be a non-existent proof. +fn test_empty_range_proof() -> Result<(), DataStoreError> { + let mut items = vec![ + (std::str::from_utf8(&[0x7]).unwrap(), "verb"), + (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), + (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), + (std::str::from_utf8(&[0x6]).unwrap(), "coin"), + (std::str::from_utf8(&[0x3]).unwrap(), "stallion"), + ]; + items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let start = 0; - let end = &items.len() - 1; - let mut proof = merkle.prove(&items[start].0)?; + let first = std::str::from_utf8(&[0x8]).unwrap(); + let proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0)?; // start and end nodes are the same - assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + // key and value vectors are intentionally empty. + let keys: Vec<&str> = Vec::new(); + let vals: Vec<&str> = Vec::new(); - // key and value vectors are intentionally empty - let keys: Vec<&&str> = Vec::new(); - let vals: Vec<&&str> = Vec::new(); - - assert_eq!( - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals), - Err(DataStoreError::ProofVerificationError) - ); + merkle.verify_range_proof(&proof, first, first, keys, vals)?; Ok(()) } From df4a238a10133febc7554ed7fe69f59b1b24f10e Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 9 Mar 2023 23:38:46 +0000 Subject: [PATCH 0078/1053] Fix the test and address review comment. --- firewood/src/merkle.rs | 8 +---- firewood/src/proof.rs | 66 ++++++++++++++++++---------------------- firewood/tests/merkle.rs | 4 +-- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index acfde2bfcfa0..fbdb0a674e62 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -153,15 +153,9 @@ fn test_partial_path_encoding() { } } -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Data(Vec); -impl Data { - pub fn value(&self) -> &Vec { - &self.0 - } -} - impl std::ops::Deref for Data { type Target = [u8]; fn deref(&self) -> &[u8] { diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index c0d7ac607898..cd51e9d6288e 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -12,6 +12,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::error::Error; use std::fmt; +use std::ops::Deref; /// Hash -> RLP encoding map #[derive(Serialize, Deserialize)] @@ -235,11 +236,9 @@ impl Proof { } // Ensure the received batch is monotonic increasing and contains no deletions - if keys.len() > 0 { - for n in 0..keys.len() - 1 { - if compare(keys[n].as_ref(), keys[n + 1].as_ref()).is_ge() { - return Err(ProofError::NonMonotonicIncreaseRange); - } + for n in 0..(keys.len() as i32 - 1) { + if compare(keys[n as usize].as_ref(), keys[(n + 1) as usize].as_ref()).is_ge() { + return Err(ProofError::NonMonotonicIncreaseRange); } } @@ -266,11 +265,10 @@ impl Proof { // Special case when there is a provided edge proof but zero key/value pairs, // ensure there are no more accounts / slots in the trie. if keys.len() == 0 { - let data = - self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; - - if data.is_some() { - // No more entries should be available + if let Some(_) = + self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)? + { + // No more entries should be available. return Err(ProofError::InvalidData); } return Ok(false); @@ -287,11 +285,16 @@ impl Proof { return Err(ProofError::InvalidEdgeKeys); } - if data.is_none() || compare(&data.unwrap(), vals[0].as_ref()).is_ne() { - // correct proof but invalid data - return Err(ProofError::InvalidData); - } - return Ok(true); + return data.map_or_else( + || Err(ProofError::InvalidData), + |d| { + if compare(&d, vals[0].as_ref()).is_ne() { + Err(ProofError::InvalidData) + } else { + Ok(true) + } + }, + ); } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. @@ -446,26 +449,23 @@ impl Proof { match &u_ref.inner() { NodeType::Branch(n) => { if let Some(v) = n.value() { - data = v.value().clone(); + data = v.deref().to_vec(); } } NodeType::Leaf(n) => { if n.path().len() == 0 { - data = n.data().value().clone(); + data = n.data().deref().to_vec().clone(); } } _ => (), } } } - // Release the handle to the node. - drop(u_ref); return Ok(Some(data)); } // The trie doesn't contain the key. if p.hash.is_none() { - drop(u_ref); if allow_non_existent_node { return Ok(None); } @@ -479,11 +479,9 @@ impl Proof { // we can prove all resolved nodes are correct, it's // enough for us to prove range. None => { - drop(u_ref); - if allow_non_existent_node { - return Ok(None); - } - return Err(ProofError::NodeNotInTrie); + return allow_non_existent_node + .then_some(None) + .ok_or(ProofError::NodeNotInTrie); } } } @@ -576,13 +574,13 @@ impl Proof { chd_eth_rlp[i] = Some(data); } } + let chd = [None; NBRANCH]; + let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp.clone())); + let branch_ptr = merkle + .new_node(Node::new(t)) + .map_err(|_| ProofError::ProofNodeMissing)?; // If the node is the last one to be decoded, then no subproof to be extracted. if end_node { - let chd = [None; NBRANCH]; - let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp)); - let branch_ptr = merkle - .new_node(Node::new(t)) - .map_err(|_| ProofError::ProofNodeMissing)?; return Ok((Some(branch_ptr.as_ptr()), None, 1)); } else if key.is_empty() { return Err(ProofError::NoSuchNode); @@ -591,17 +589,11 @@ impl Proof { // Check if the subproof with the given key exist. let index = key[0] as usize; let data: Vec = if chd_eth_rlp[index].is_none() { - return Ok((None, None, 0)); + return Ok((Some(branch_ptr.as_ptr()), None, 1)); } else { chd_eth_rlp[index].clone().unwrap() }; let subproof = self.generate_subproof(data)?; - - let chd = [None; NBRANCH]; - let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp)); - let branch_ptr = merkle - .new_node(Node::new(t)) - .map_err(|_| ProofError::ProofNodeMissing)?; Ok((Some(branch_ptr.as_ptr()), subproof, 1)) } // RLP length can only be the two cases above. diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 07bd5acd916d..b3d6ce56a950 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -354,7 +354,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = 0; - let end = &items.len() - 1; + let end = items.len(); let mut proof = merkle.prove(std::str::from_utf8(&[0x2]).unwrap())?; assert!(!proof.0.is_empty()); @@ -365,7 +365,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { let mut keys: Vec<&str> = Vec::new(); let mut vals: Vec<&str> = Vec::new(); - for i in start..=end { + for i in start..end { keys.push(&items[i].0); vals.push(&items[i].1); } From d451502d7e44d43a0edd402c1140e37160887105 Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 14 Mar 2023 15:12:15 -0400 Subject: [PATCH 0079/1053] fix: run clippy --fix across all workspaces (#149) Signed-off-by: Dan Sover --- firewood/src/account.rs | 2 +- firewood/src/db.rs | 20 +++--- firewood/src/merkle.rs | 16 ++--- firewood/src/proof.rs | 26 ++------ firewood/tests/merkle.rs | 10 +-- fwdctl/src/get.rs | 4 +- fwdctl/src/root.rs | 2 +- growth-ring/examples/demo1.rs | 6 +- growth-ring/src/lib.rs | 26 ++++---- growth-ring/src/wal.rs | 98 +++++++++++++---------------- growth-ring/tests/common/mod.rs | 10 +-- libaio-futures/tests/simple_test.rs | 1 - 12 files changed, 95 insertions(+), 126 deletions(-) diff --git a/firewood/src/account.rs b/firewood/src/account.rs index 0c1b1c000b8e..0052f5272a19 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -105,7 +105,7 @@ impl MummyItem for Blob { let bytes = mem .get_view(addr + 4, len) .ok_or(ShaleError::LinearMemStoreError)?; - Ok(Self::Code(bytes.as_deref().into())) + Ok(Self::Code(bytes.as_deref())) } fn dehydrated_len(&self) -> u64 { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7c007497008f..366d6c43e873 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -563,15 +563,13 @@ impl DB { disk_requester.init_wal("wal", db_fd); // set up the storage layout - let db_header: ObjPtr; - let merkle_payload_header: ObjPtr; - let blob_payload_header: ObjPtr; - db_header = ObjPtr::new_from_addr(offset); + + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += DBHeader::MSIZE; - merkle_payload_header = ObjPtr::new_from_addr(offset); + let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += CompactSpaceHeader::MSIZE; assert!(offset <= SPACE_RESERVED); - blob_payload_header = ObjPtr::new_from_addr(0); + let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); if reset { // initialize space headers @@ -773,19 +771,17 @@ impl DB { return None; } // set up the storage layout - let db_header: ObjPtr; - let merkle_payload_header: ObjPtr; - let blob_payload_header: ObjPtr; + let mut offset = std::mem::size_of::() as u64; // DBHeader starts after DBParams in merkle meta space - db_header = ObjPtr::new_from_addr(offset); + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += DBHeader::MSIZE; // Merkle CompactHeader starts after DBHeader in merkle meta space - merkle_payload_header = ObjPtr::new_from_addr(offset); + let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += CompactSpaceHeader::MSIZE; assert!(offset <= SPACE_RESERVED); // Blob CompactSpaceHeader starts right in blob meta space - blob_payload_header = ObjPtr::new_from_addr(0); + let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); let space = &inner.revisions[nback - 1]; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fbdb0a674e62..25f3ce5faa69 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -941,7 +941,7 @@ impl Merkle { Ok(()) } - fn set_parent<'b>(&self, new_chd: ObjPtr, parents: &mut [(ObjRef<'b, Node>, u8)]) { + fn set_parent(&self, new_chd: ObjPtr, parents: &mut [(ObjRef<'_, Node>, u8)]) { let (p_ref, idx) = parents.last_mut().unwrap(); p_ref .write(|p| { @@ -1283,9 +1283,9 @@ impl Merkle { Ok(()) } - fn after_remove_leaf<'b>( + fn after_remove_leaf( &self, - parents: &mut Vec<(ObjRef<'b, Node>, u8)>, + parents: &mut Vec<(ObjRef<'_, Node>, u8)>, deleted: &mut Vec>, ) -> Result<(), MerkleError> { let (b_chd, val) = { @@ -1456,10 +1456,10 @@ impl Merkle { Ok(()) } - fn after_remove_branch<'b>( + fn after_remove_branch( &self, (c_ptr, idx): (ObjPtr, u8), - parents: &mut Vec<(ObjRef<'b, Node>, u8)>, + parents: &mut Vec<(ObjRef<'_, Node>, u8)>, deleted: &mut Vec>, ) -> Result<(), MerkleError> { // [b] -> [u] -> [c] @@ -2033,7 +2033,7 @@ fn test_cmp() { (vec![0xc0, 0xff], vec![0xc0, 0xff]), ] { let n = compare(&bytes_a, &bytes_b); - assert_eq!(n.is_eq(), true); + assert!(n.is_eq()); } for (bytes_a, bytes_b) in [ @@ -2041,7 +2041,7 @@ fn test_cmp() { (vec![0xc0, 0xee], vec![0xc0, 0xff]), ] { let n = compare(&bytes_a, &bytes_b); - assert_eq!(n.is_lt(), true); + assert!(n.is_lt()); } for (bytes_a, bytes_b) in [ @@ -2049,6 +2049,6 @@ fn test_cmp() { (vec![0xc0, 0xff, 0x33], vec![0xc0, 0xff]), ] { let n = compare(&bytes_a, &bytes_b); - assert_eq!(n.is_gt(), true); + assert!(n.is_gt()); } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index cd51e9d6288e..62b5ebea7019 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -264,7 +264,7 @@ impl Proof { } // Special case when there is a provided edge proof but zero key/value pairs, // ensure there are no more accounts / slots in the trie. - if keys.len() == 0 { + if keys.is_empty() { if let Some(_) = self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)? { @@ -454,7 +454,7 @@ impl Proof { } NodeType::Leaf(n) => { if n.path().len() == 0 { - data = n.data().deref().to_vec().clone(); + data = n.data().deref().to_vec(); } } _ => (), @@ -700,22 +700,8 @@ fn unset_internal>( } let p = u_ref.as_ptr(); drop(u_ref); - unset_node_ref( - merkle, - p, - left_node, - left_chunks[index..].to_vec(), - 1, - false, - )?; - unset_node_ref( - merkle, - p, - right_node, - right_chunks[index..].to_vec(), - 1, - true, - )?; + unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; + unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; Ok(false) } NodeType::Extension(n) => { @@ -762,7 +748,7 @@ fn unset_internal>( merkle, p, Some(node), - left_chunks[index..].to_vec(), + &left_chunks[index..], cur_key.len(), false, )?; @@ -773,7 +759,7 @@ fn unset_internal>( merkle, p, Some(node), - right_chunks[index..].to_vec(), + &right_chunks[index..], cur_key.len(), true, )?; diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index b3d6ce56a950..1fc39d22c04a 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -366,8 +366,8 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { let mut keys: Vec<&str> = Vec::new(); let mut vals: Vec<&str> = Vec::new(); for i in start..end { - keys.push(&items[i].0); - vals.push(&items[i].1); + keys.push(items[i].0); + vals.push(items[i].1); } merkle.verify_range_proof( @@ -441,7 +441,7 @@ fn test_one_element_range_proof() -> Result<(), DataStoreError> { // start and end nodes are the same let end = 0; - let start_proof = merkle.prove(&items[start].0)?; + let start_proof = merkle.prove(items[start].0)?; assert!(!start_proof.0.is_empty()); let mut keys = Vec::new(); @@ -466,9 +466,9 @@ fn test_all_elements_proof() -> Result<(), DataStoreError> { let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(&items[start].0)?; + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0)?; + let end_proof = merkle.prove(items[end].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index b6a8a47153e7..1f848873279f 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -42,8 +42,8 @@ pub fn run(opts: &Options) -> Result<()> { if val.is_empty() { return Err(anyhow!("no value found for key")); } - return Ok(()); + Ok(()) } - Err(_) => return Err(anyhow!("key not found")), + Err(_) => Err(anyhow!("key not found")), } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 252dc8ef49fc..ee45af0d9f79 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -34,6 +34,6 @@ pub fn run(opts: &Options) -> Result<()> { println!("{:X?}", *root); Ok(()) } - Err(_) => return Err(anyhow!("root hash not found")), + Err(_) => Err(anyhow!("root hash not found")), } } diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs index fe2a96c28c86..2cc4f259e600 100644 --- a/growth-ring/examples/demo1.rs +++ b/growth-ring/examples/demo1.rs @@ -30,7 +30,7 @@ fn main() { let mut loader = WALLoader::new(); loader.file_nbit(9).block_nbit(8); - let store = WALStoreAIO::new(&wal_dir, true, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, true, None, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -48,7 +48,7 @@ fn main() { ); } - let store = WALStoreAIO::new(&wal_dir, false, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, false, None, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -62,7 +62,7 @@ fn main() { ); } - let store = WALStoreAIO::new(&wal_dir, false, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, false, None, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); let mut history = std::collections::VecDeque::new(); for _ in 0..3 { diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 618bd24b1058..fe17682dc12d 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -75,8 +75,8 @@ impl WALFileAIO { OFlag::O_CREAT | OFlag::O_RDWR, Mode::S_IRUSR | Mode::S_IWUSR, ) - .and_then(|fd| Ok(WALFileAIO { fd, aiomgr })) - .or_else(|_| Err(())) + .map(|fd| WALFileAIO { fd, aiomgr }) + .map_err(|_| ()) } } @@ -97,8 +97,8 @@ impl WALFile for WALFileAIO { offset as off_t, length as off_t, ) - .and_then(|_| Ok(())) - .or_else(|_| Err(())); + .map(|_| ()) + .map_err(|_| ()); } #[cfg(not(target_os = "linux"))] // TODO: macos support is possible here, but possibly unnecessary @@ -107,12 +107,12 @@ impl WALFile for WALFileAIO { } fn truncate(&self, length: usize) -> Result<(), ()> { - ftruncate(self.fd, length as off_t).or_else(|_| Err(())) + ftruncate(self.fd, length as off_t).map_err(|_| ()) } async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()> { let (res, data) = self.aiomgr.write(self.fd, offset, data, None).await; - res.or_else(|_| Err(())).and_then(|nwrote| { + res.map_err(|_| ()).and_then(|nwrote| { if nwrote == data.len() { Ok(()) } else { @@ -123,8 +123,8 @@ impl WALFile for WALFileAIO { async fn read(&self, offset: WALPos, length: usize) -> Result, ()> { let (res, data) = self.aiomgr.read(self.fd, offset, length, None).await; - res.or_else(|_| Err(())) - .and_then(|nread| Ok(if nread == length { Some(data) } else { None })) + res.map_err(|_| ()) + .map(|nread| if nread == length { Some(data) } else { None }) } } @@ -176,10 +176,8 @@ impl WALStoreAIO { libc::S_IRUSR | libc::S_IWUSR | libc::S_IXUSR, ) }; - if ret != 0 { - if truncate { - panic!("error while creating directory") - } + if ret != 0 && truncate { + panic!("error while creating directory") } walfd = match nix::fcntl::openat(fd, wal_dir, oflags(), Mode::empty()) { Ok(fd) => fd, @@ -211,7 +209,7 @@ impl WALStore for WALStoreAIO { async fn open_file(&self, filename: &str, _touch: bool) -> Result, ()> { let filename = filename.to_string(); WALFileAIO::new(self.rootfd, &filename, self.aiomgr.clone()) - .and_then(|f| Ok(Box::new(f) as Box)) + .map(|f| Box::new(f) as Box) } async fn remove_file(&self, filename: String) -> Result<(), ()> { @@ -220,7 +218,7 @@ impl WALStore for WALStoreAIO { filename.as_str(), UnlinkatFlags::NoRemoveDir, ) - .or_else(|_| Err(())) + .map_err(|_| ()) } fn enumerate_files(&self) -> Result { diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 6f2f816dd943..76a0dc948deb 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -210,7 +210,7 @@ impl<'a, F: WALStore> std::ops::Deref for WALFileHandle<'a, F> { impl<'a, F: WALStore> Drop for WALFileHandle<'a, F> { fn drop(&mut self) { unsafe { - (&*self.pool).release_file(self.fid); + (*self.pool).release_file(self.fid); } } } @@ -236,8 +236,8 @@ impl WALFilePool { block_nbit: u64, cache_size: NonZeroUsize, ) -> Result { - let file_nbit = file_nbit as u64; - let block_nbit = block_nbit as u64; + let file_nbit = file_nbit; + let block_nbit = block_nbit; let header_file = store.open_file("HEAD", true).await?; header_file.truncate(HEADER_SIZE)?; Ok(WALFilePool { @@ -248,7 +248,7 @@ impl WALFilePool { last_write: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), last_peel: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), file_nbit, - file_size: 1 << (file_nbit as u64), + file_size: 1 << file_nbit, block_nbit, }) } @@ -267,39 +267,33 @@ impl WALFilePool { Ok(()) } - fn get_file<'a>( - &'a self, - fid: u64, - touch: bool, - ) -> impl Future, ()>> { - async move { - let pool = self as *const WALFilePool; - if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { - let handle = match self.handle_used.borrow_mut().entry(fid) { - hash_map::Entry::Vacant(e) => unsafe { - &*(*e.insert(UnsafeCell::new((h, 1))).get()).0 - }, - _ => unreachable!(), - }; - Ok(WALFileHandle { fid, handle, pool }) - } else { - let v = unsafe { - &mut *match self.handle_used.borrow_mut().entry(fid) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => e.insert(UnsafeCell::new(( - self.store.open_file(&get_fname(fid), touch).await?, - 0, - ))), - } - .get() - }; - v.1 += 1; - Ok(WALFileHandle { - fid, - handle: &*v.0, - pool, - }) - } + async fn get_file<'a>(&'a self, fid: u64, touch: bool) -> Result, ()> { + let pool = self as *const WALFilePool; + if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { + let handle = match self.handle_used.borrow_mut().entry(fid) { + hash_map::Entry::Vacant(e) => unsafe { + &*(*e.insert(UnsafeCell::new((h, 1))).get()).0 + }, + _ => unreachable!(), + }; + Ok(WALFileHandle { fid, handle, pool }) + } else { + let v = unsafe { + &mut *match self.handle_used.borrow_mut().entry(fid) { + hash_map::Entry::Occupied(e) => e.into_mut(), + hash_map::Entry::Vacant(e) => e.insert(UnsafeCell::new(( + self.store.open_file(&get_fname(fid), touch).await?, + 0, + ))), + } + .get() + }; + v.1 += 1; + Ok(WALFileHandle { + fid, + handle: &*v.0, + pool, + }) } } @@ -497,9 +491,9 @@ impl WALWriter { let d = remain - msize; let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; let blob = unsafe { - std::mem::transmute::<*mut u8, &mut WALRingBlob>( - (&mut self.block_buffer[bbuff_cur as usize..]).as_mut_ptr(), - ) + &mut *self.block_buffer[bbuff_cur as usize..] + .as_mut_ptr() + .cast::() }; bbuff_cur += msize; if d >= rsize { @@ -603,16 +597,15 @@ impl WALWriter { .into_iter() .map(move |f| async move { f.await }.shared()) .collect(); - let res = res - .into_iter() + + res.into_iter() .zip(records.into_iter()) .map(|((ringid, blks), rec)| { future::try_join_all(blks.into_iter().map(|idx| writes[idx].clone())) .or_else(|_| future::ready(Err(()))) .and_then(move |_| future::ready(Ok((rec, ringid)))) }) - .collect(); - res + .collect() } /// Inform the `WALWriter` that some data writes are complete so that it could automatically @@ -630,13 +623,13 @@ impl WALWriter { for rec in records.as_ref() { state.io_complete.push(*rec); } - while let Some(s) = state.io_complete.peek().and_then(|&e| Some(e.start)) { + while let Some(s) = state.io_complete.peek().map(|&e| e.start) { if s != state.next_complete.end { break; } let mut m = state.io_complete.pop().unwrap(); let block_remain = block_size - (m.end & (block_size - 1)); - if block_remain <= msize as u64 { + if block_remain <= msize { m.end += block_remain } let fid = m.start >> state.file_nbit; @@ -888,13 +881,12 @@ impl WALLoader { if v.done { return None; } - let header_raw = match check!(v.file.read(v.off, msize as usize).await) { + let header_raw = match check!(v.file.read(v.off, msize).await) { Some(h) => h, None => _yield!(), }; v.off += msize as u64; - let header = - unsafe { std::mem::transmute::<*const u8, &WALRingBlob>(header_raw.as_ptr()) }; + let header = unsafe { &*header_raw.as_ptr().cast::() }; let header = WALRingBlob { counter: header.counter, crc32: header.crc32, @@ -1001,15 +993,13 @@ impl WALLoader { return None; } loop { - let header_raw = match check!(v.file.read(v.off, msize as usize).await) { + let header_raw = match check!(v.file.read(v.off, msize).await) { Some(h) => h, None => _yield!(), }; let ringid_start = (fid << file_nbit) + v.off; v.off += msize as u64; - let header = unsafe { - std::mem::transmute::<*const u8, &WALRingBlob>(header_raw.as_ptr()) - }; + let header = unsafe { &*header_raw.as_ptr().cast::() }; let rsize = header.rsize; match header.rtype.try_into() { Ok(WALRingType::Full) => { @@ -1071,7 +1061,7 @@ impl WALLoader { payload.resize(chunks.iter().fold(0, |acc, v| acc + v.len()), 0); let mut ps = &mut payload[..]; for c in chunks { - ps[..c.len()].copy_from_slice(&*c); + ps[..c.len()].copy_from_slice(&c); ps = &mut ps[c.len()..]; } _yield!(( diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 4d651ae9ca6f..2ccef66a856e 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -80,7 +80,7 @@ impl WALFile for WALFileEmul { Ok(None) } else { Ok(Some( - (&file[offset..offset + length]).to_vec().into_boxed_slice(), + file[offset..offset + length].to_vec().into_boxed_slice(), )) } } @@ -158,7 +158,7 @@ where .files .remove(&filename) .ok_or(()) - .and_then(|_| Ok(())) + .map(|_| ()) } fn enumerate_files(&self) -> Result { @@ -434,7 +434,7 @@ impl Canvas { for c in r.iter() { print!("{:02x} ", c & 0xff); } - println!(""); + println!(); } println!("# end canvas"); } @@ -458,8 +458,8 @@ fn test_canvas() { assert!(canvas1.is_same(&canvas2)); canvas1.rand_paint(&mut rng); assert!(!canvas1.is_same(&canvas2)); - while let Some(_) = canvas1.rand_paint(&mut rng) {} - while let Some(_) = canvas2.rand_paint(&mut rng) {} + while canvas1.rand_paint(&mut rng).is_some() {} + while canvas2.rand_paint(&mut rng).is_some() {} assert!(canvas1.is_same(&canvas2)); canvas1.print(10); } diff --git a/libaio-futures/tests/simple_test.rs b/libaio-futures/tests/simple_test.rs index e241b2c0fabc..d6504533119a 100644 --- a/libaio-futures/tests/simple_test.rs +++ b/libaio-futures/tests/simple_test.rs @@ -42,7 +42,6 @@ fn simple2() { .unwrap(); let fd = file.as_raw_fd(); let ws = (0..4000) - .into_iter() .map(|i| { let off = i * 128; let s = char::from((97 + i % 26) as u8) From 3c139389bb73f3e64bcf373e04616a21101c516b Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 14 Mar 2023 17:02:44 -0400 Subject: [PATCH 0080/1053] file: Update create_file to return a Result (#150) Fixes file::create_file to return a Result in order to avoid a potential panic when there is an issue creating a file on the filesystem. Signed-off-by: Dan Sover --- firewood/src/file.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 306d3f0fa12b..ab17d8f39e80 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -26,14 +26,13 @@ impl File { ) } - pub fn create_file(rootfd: Fd, fname: &str) -> Fd { + pub fn create_file(rootfd: Fd, fname: &str) -> nix::Result { openat( rootfd, fname, OFlag::O_CREAT | OFlag::O_RDWR, Mode::S_IRUSR | Mode::S_IWUSR, ) - .unwrap() } fn _get_fname(fid: u64) -> String { @@ -46,7 +45,7 @@ impl File { Ok(fd) => fd, Err(e) => match e { Errno::ENOENT => { - let fd = Self::create_file(rootfd, &fname); + let fd = Self::create_file(rootfd, &fname)?; nix::unistd::ftruncate(fd, flen as nix::libc::off_t)?; fd } @@ -108,3 +107,12 @@ pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { reset_header, )) } + +#[test] +/// This test simulates a filesystem error: for example the specified path +/// does not exist when creating a file. +fn test_create_file() { + if let Err(e) = File::create_file(0, "/badpath/baddir") { + assert_eq!(e.desc(), "No such file or directory") + } +} From e064273721f203d384e6cdb2f9d2bfd5aebda3d8 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 16 Mar 2023 10:57:21 -0400 Subject: [PATCH 0081/1053] Standardize AsRef for user facing methods Signed-off-by: Sam Batschelet --- firewood/src/db.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 366d6c43e873..4a1f032f393a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -276,7 +276,7 @@ impl DBRev { } /// Get a value associated with a key. - pub fn kv_get(&self, key: &[u8]) -> Option> { + pub fn kv_get>(&self, key: K) -> Option> { let obj_ref = self.merkle.get(key, self.header.kv_root); match obj_ref { Err(_) => None, @@ -305,7 +305,7 @@ impl DBRev { .map_err(DBError::Merkle) } - fn get_account(&self, key: &[u8]) -> Result { + fn get_account>(&self, key: K) -> Result { Ok(match self.merkle.get(key, self.header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => Account::default(), @@ -314,7 +314,7 @@ impl DBRev { } /// Dump the MPT of the state storage under an account. - pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { let acc = match self.merkle.get(key, self.header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => Account::default(), @@ -328,12 +328,12 @@ impl DBRev { } /// Get balance of the account. - pub fn get_balance(&self, key: &[u8]) -> Result { + pub fn get_balance>(&self, key: K) -> Result { Ok(self.get_account(key)?.balance) } /// Get code of the account. - pub fn get_code(&self, key: &[u8]) -> Result, DBError> { + pub fn get_code>(&self, key: K) -> Result, DBError> { let code = self.get_account(key)?.code; if code.is_null() { return Ok(Vec::new()); @@ -365,12 +365,12 @@ impl DBRev { } /// Get nonce of the account. - pub fn get_nonce(&self, key: &[u8]) -> Result { + pub fn get_nonce>(&self, key: K) -> Result { Ok(self.get_account(key)?.nonce) } /// Get the state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result, DBError> { + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { let root = self.get_account(key)?.root; if root.is_null() { return Ok(Vec::new()); @@ -383,7 +383,7 @@ impl DBRev { } /// Check if the account exists. - pub fn exist(&self, key: &[u8]) -> Result { + pub fn exist>(&self, key: K) -> Result { Ok(match self.merkle.get(key, self.header.acc_root) { Ok(r) => r.is_some(), Err(e) => return Err(DBError::Merkle(e)), @@ -692,7 +692,7 @@ impl DB { } /// Dump the MPT of the latest state storage under an account. - pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { self.inner.lock().latest.dump_account(key, w) } @@ -702,7 +702,7 @@ impl DB { } /// Get a value in the kv store associated with a particular key. - pub fn kv_get(&self, key: &[u8]) -> Result, DBError> { + pub fn kv_get>(&self, key: K) -> Result, DBError> { self.inner .lock() .latest @@ -716,27 +716,27 @@ impl DB { } /// Get the latest balance of the account. - pub fn get_balance(&self, key: &[u8]) -> Result { + pub fn get_balance>(&self, key: K) -> Result { self.inner.lock().latest.get_balance(key) } /// Get the latest code of the account. - pub fn get_code(&self, key: &[u8]) -> Result, DBError> { + pub fn get_code>(&self, key: K) -> Result, DBError> { self.inner.lock().latest.get_code(key) } /// Get the latest nonce of the account. - pub fn get_nonce(&self, key: &[u8]) -> Result { + pub fn get_nonce>(&self, key: K) -> Result { self.inner.lock().latest.get_nonce(key) } /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result, DBError> { + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { self.inner.lock().latest.get_state(key, sub_key) } /// Check if the account exists in the latest world state. - pub fn exist(&self, key: &[u8]) -> Result { + pub fn exist>(&self, key: K) -> Result { self.inner.lock().latest.exist(key) } From b04283fd0145dcdc540d57471bd31882830e152b Mon Sep 17 00:00:00 2001 From: exdx Date: Thu, 16 Mar 2023 14:03:51 -0400 Subject: [PATCH 0082/1053] ci: Clippy should fail in case of warnings (#151) * ci: Clippy should fail in case of warnings Signed-off-by: Dan Sover * libaio: Fix clippy lint issues Signed-off-by: Dan Sover * *: Fix remaining clippy lint errors Signed-off-by: Dan Sover --------- Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 2 +- firewood/src/dynamic_mem.rs | 2 ++ firewood/src/proof.rs | 14 ++++++----- fwdctl/src/dump.rs | 2 +- growth-ring/src/lib.rs | 21 ++++++++-------- growth-ring/src/wal.rs | 18 ++++++++++--- libaio-futures/src/lib.rs | 50 ++++++++++++++++++------------------- shale/src/lib.rs | 2 ++ 8 files changed, 63 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f609cad2626d..fc7432ec5a98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: - name: Lint run: cargo fmt -- --check - name: Clippy - run: cargo clippy --fix -- -Dwarnings + run: cargo clippy -- -D warnings build: runs-on: ubuntu-latest diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index 22ac07401bd0..730454cd2f07 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -21,6 +21,8 @@ impl DynamicMem { Self { space, id } } + #[allow(clippy::mut_from_ref)] + // TODO: Refactor this usage. fn get_space_mut(&self) -> &mut Vec { unsafe { &mut *self.space.get() } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 62b5ebea7019..f96a5be1f756 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -265,8 +265,8 @@ impl Proof { // Special case when there is a provided edge proof but zero key/value pairs, // ensure there are no more accounts / slots in the trie. if keys.is_empty() { - if let Some(_) = - self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)? + if (self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?) + .is_some() { // No more entries should be available. return Err(ProofError::InvalidData); @@ -398,11 +398,11 @@ impl Proof { u_ref .write(|u| { let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = if chd_ptr.is_none() { - ObjPtr::null() + *uu.chd_mut() = if let Some(chd_p) = chd_ptr { + chd_p } else { - chd_ptr.unwrap() - }; + ObjPtr::null() + } }) .unwrap(); } else { @@ -493,6 +493,7 @@ impl Proof { /// /// * `end_node` - A boolean indicates whether this is the ebd node to decode, thus no `key` /// to be present. + #[allow(clippy::type_complexity)] fn decode_node( &self, merkle: &Merkle, @@ -562,6 +563,7 @@ impl Proof { // Record rlp values of all children. let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + #[allow(clippy::needless_range_loop)] for i in 0..NBRANCH { let rlp = rlp.at(i).unwrap(); // Skip if rlp is empty data diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index c0a910825a74..0a4306a0cbcc 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -27,7 +27,7 @@ pub fn run(opts: &Options) -> Result<()> { }; let mut stdout = std::io::stdout(); - if let Err(_) = db.kv_dump(&mut stdout) { + if db.kv_dump(&mut stdout).is_err() { return Err(anyhow!("database dump not successful")); } Ok(()) diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index fe17682dc12d..9f44432bffce 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -67,6 +67,8 @@ pub struct WALFileAIO { aiomgr: Arc, } +#[allow(clippy::result_unit_err)] +// TODO: Refactor to return a meaningful error. impl WALFileAIO { pub fn new(rootfd: RawFd, filename: &str, aiomgr: Arc) -> Result { openat( @@ -136,6 +138,7 @@ pub struct WALStoreAIO { unsafe impl Send for WALStoreAIO {} impl WALStoreAIO { + #[allow(clippy::result_unit_err)] pub fn new( wal_dir: &str, truncate: bool, @@ -151,18 +154,14 @@ impl WALStoreAIO { if truncate { let _ = std::fs::remove_dir_all(wal_dir); } - let walfd; - match rootfd { + let walfd = match rootfd { None => { - match mkdir(wal_dir, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { - Err(e) => { - if truncate { - panic!("error while creating directory: {}", e) - } + if let Err(e) = mkdir(wal_dir, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { + if truncate { + panic!("error while creating directory: {}", e) } - Ok(_) => (), } - walfd = match open(wal_dir, oflags(), Mode::empty()) { + match open(wal_dir, oflags(), Mode::empty()) { Ok(fd) => fd, Err(_) => panic!("error while opening the WAL directory"), } @@ -179,12 +178,12 @@ impl WALStoreAIO { if ret != 0 && truncate { panic!("error while creating directory") } - walfd = match nix::fcntl::openat(fd, wal_dir, oflags(), Mode::empty()) { + match nix::fcntl::openat(fd, wal_dir, oflags(), Mode::empty()) { Ok(fd) => fd, Err(_) => panic!("error while opening the WAL directory"), } } - } + }; Ok(WALStoreAIO { rootfd: walfd, aiomgr, diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 76a0dc948deb..fb977dad663f 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -178,6 +178,7 @@ pub trait WALFile { /// Read data with offset. Return `Ok(None)` when it reaches EOF. async fn read(&self, offset: WALPos, length: usize) -> Result, ()>; /// Truncate a file to a specified length. + #[allow(clippy::result_unit_err)] fn truncate(&self, length: usize) -> Result<(), ()>; } @@ -191,6 +192,7 @@ pub trait WALStore { async fn remove_file(&self, filename: String) -> Result<(), ()>; /// Enumerate all WAL filenames. It should include all WAL files that are previously opened /// (created) but not removed. The list could be unordered. + #[allow(clippy::result_unit_err)] fn enumerate_files(&self) -> Result; } @@ -221,8 +223,11 @@ struct WALFilePool { store: F, header_file: Box, handle_cache: RefCell>>, + #[allow(clippy::type_complexity)] handle_used: RefCell, usize)>>>, + #[allow(clippy::type_complexity)] last_write: UnsafeCell>>>>>, + #[allow(clippy::type_complexity)] last_peel: UnsafeCell>>>>>, file_nbit: u64, file_size: u64, @@ -267,7 +272,9 @@ impl WALFilePool { Ok(()) } - async fn get_file<'a>(&'a self, fid: u64, touch: bool) -> Result, ()> { + #[allow(clippy::await_holding_refcell_ref)] + // TODO: Refactor to remove mutable reference from being awaited. + async fn get_file(&self, fid: u64, touch: bool) -> Result, ()> { let pool = self as *const WALFilePool; if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { @@ -312,6 +319,7 @@ impl WALFilePool { } } + #[allow(clippy::type_complexity)] fn write<'a>( &'a mut self, writes: Vec<(WALPos, WALBytes)>, @@ -388,6 +396,7 @@ impl WALFilePool { res } + #[allow(clippy::type_complexity)] fn remove_files<'a>( &'a mut self, state: &mut WALState, @@ -612,8 +621,8 @@ impl WALWriter { /// remove obsolete WAL files. The given list of `WALRingId` does not need to be ordered and /// could be of arbitrary length. Use `0` for `keep_nrecords` if all obsolete WAL files /// need to removed (the obsolete files do not affect the speed of recovery or correctness). - pub async fn peel<'a, T: AsRef<[WALRingId]>>( - &'a mut self, + pub async fn peel>( + &mut self, records: T, keep_nrecords: u32, ) -> Result<(), ()> { @@ -815,6 +824,8 @@ impl WALLoader { Self::verify_checksum_(data, checksum, &self.recover_policy) } + #[allow(clippy::await_holding_refcell_ref)] + // TODO: Refactor to a more safe solution. fn read_rings<'a, F: WALStore + 'a>( file: &'a WALFileHandle<'a, F>, read_payload: bool, @@ -923,6 +934,7 @@ impl WALLoader { }) } + #[allow(clippy::await_holding_refcell_ref)] fn read_records<'a, F: WALStore + 'a>( &'a self, file: &'a WALFileHandle<'a, F>, diff --git a/libaio-futures/src/lib.rs b/libaio-futures/src/lib.rs index 3cafed3faeaf..7c85dba1394a 100644 --- a/libaio-futures/src/lib.rs +++ b/libaio-futures/src/lib.rs @@ -35,6 +35,7 @@ //! ``` mod abi; +use abi::IOCb; use libc::time_t; use parking_lot::Mutex; use std::collections::{hash_map, HashMap}; @@ -81,7 +82,7 @@ impl AIOContext { LIBAIO_ENOSYS => Err(Error::NotSupported), _ => Err(Error::OtherError), } - .and_then(|_| Ok(AIOContext(ctx))) + .map(|_| AIOContext(ctx)) } } } @@ -112,7 +113,7 @@ impl AIO { flags: u32, opcode: abi::IOCmd, ) -> Self { - let mut iocb = Box::new(abi::IOCb::default()); + let mut iocb = Box::::default(); iocb.aio_fildes = fd as u32; iocb.aio_lio_opcode = opcode as u16; iocb.aio_reqprio = priority; @@ -169,6 +170,7 @@ impl Drop for AIOFuture { } } +#[allow(clippy::enum_variant_names)] enum AIOState { FutureInit(AIO, bool), FuturePending(AIO, std::task::Waker, bool), @@ -192,15 +194,14 @@ impl AIONotifier { fn dropped(&self, id: u64) { let mut waiting = self.waiting.lock(); - match waiting.entry(id) { - hash_map::Entry::Occupied(mut e) => match e.get_mut() { + if let hash_map::Entry::Occupied(mut e) = waiting.entry(id) { + match e.get_mut() { AIOState::FutureInit(_, dropped) => *dropped = true, AIOState::FuturePending(_, _, dropped) => *dropped = true, AIOState::FutureDone(_) => { e.remove(); } - }, - _ => (), + } } } @@ -367,19 +368,18 @@ impl AIOManager { ) -> Result<(), Error> { let n = self.notifier.clone(); self.listener = Some(std::thread::spawn(move || { - let mut timespec = timeout.and_then(|sec: u32| { - Some(libc::timespec { - tv_sec: sec as time_t, - tv_nsec: 0, - }) + let mut timespec = timeout.map(|sec: u32| libc::timespec { + tv_sec: sec as time_t, + tv_nsec: 0, }); + let mut ongoing = 0; loop { // try to quiesce if ongoing == 0 && scheduler_out.is_empty() { let mut sel = crossbeam_channel::Select::new(); sel.recv(&exit_r); - sel.recv(&scheduler_out.get_receiver()); + sel.recv(scheduler_out.get_receiver()); if sel.ready() == 0 { exit_r.recv().unwrap(); break; @@ -407,7 +407,7 @@ impl AIOManager { events.as_mut_ptr(), timespec .as_mut() - .and_then(|t| Some(t as *mut libc::timespec)) + .map(|t| t as *mut libc::timespec) .unwrap_or(std::ptr::null_mut()), ) }; @@ -420,7 +420,7 @@ impl AIOManager { ongoing -= ret as usize; for ev in events[..ret as usize].iter() { #[cfg(not(feature = "emulated-failure"))] - n.finish(ev.data as u64, ev.res); + n.finish(ev.data, ev.res); #[cfg(feature = "emulated-failure")] { let mut res = ev.res; @@ -478,15 +478,13 @@ impl AIOManager { /// Get a copy of the current data in the buffer. pub fn copy_data(&self, aio_id: u64) -> Option> { let w = self.notifier.waiting.lock(); - w.get(&aio_id).and_then(|state| { - Some( - match state { - AIOState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), - AIOState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), - AIOState::FutureDone(res) => &res.1, - } - .to_vec(), - ) + w.get(&aio_id).map(|state| { + match state { + AIOState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), + AIOState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), + AIOState::FutureDone(res) => &res.1, + } + .to_vec() }) } @@ -558,21 +556,21 @@ impl AIOBatchSchedulerOut { } } } - if pending.len() == 0 { + if pending.is_empty() { return 0; } let mut ret = unsafe { abi::io_submit( *notifier.io_ctx, pending.len() as c_long, - (&mut pending).as_mut_ptr(), + pending.as_mut_ptr(), ) }; if ret < 0 && ret == LIBAIO_EAGAIN { ret = 0 } let nacc = ret as usize; - self.leftover = (&pending[nacc..]) + self.leftover = pending[nacc..] .iter() .map(|p| AtomicPtr::new(*p)) .collect::>(); diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 1d1234bfc677..27d859a4c835 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -563,6 +563,8 @@ impl ObjCache { } #[inline(always)] + #[allow(clippy::mut_from_ref)] + // TODO: Refactor this function fn get_inner_mut(&self) -> &mut ObjCacheInner { unsafe { &mut *self.0.get() } } From d544366f7b1c6a0bab97b9967453ce0d8fa443e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:24:52 -0400 Subject: [PATCH 0083/1053] build(deps): update predicates requirement from 2.1.1 to 3.0.1 (#154) Updates the requirements on [predicates](https://github.com/assert-rs/predicates-rs) to permit the latest version. - [Release notes](https://github.com/assert-rs/predicates-rs/releases) - [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/assert-rs/predicates-rs/compare/v2.1.1...v3.0.1) --- updated-dependencies: - dependency-name: predicates dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- fwdctl/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 4a596aadc84c..d13ff3313e3c 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -38,6 +38,6 @@ keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" assert_cmd = "2.0.7" -predicates = "2.1.1" +predicates = "3.0.1" serial_test = "1.0.0" clap = { version = "4.0.29" } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 1f33af7f2293..30b8216399c9 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -12,5 +12,5 @@ log = "0.4.17" [dev-dependencies] assert_cmd = "2.0.7" -predicates = "2.1.1" +predicates = "3.0.1" serial_test = "1.0.0" From 96107d2e8c77e5013178a39c8ac49d5649bc08da Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 21 Mar 2023 18:08:14 -0400 Subject: [PATCH 0084/1053] firewood-connection: Add new library crate (#158) --- Cargo.toml | 1 + README.md | 2 + firewood-connection/Cargo.toml | 16 ++++ firewood-connection/README.md | 28 ++++++ firewood-connection/src/lib.rs | 83 ++++++++++++++++ firewood-connection/tests/manager.rs | 136 +++++++++++++++++++++++++++ 6 files changed, 266 insertions(+) create mode 100644 firewood-connection/Cargo.toml create mode 100644 firewood-connection/README.md create mode 100644 firewood-connection/src/lib.rs create mode 100644 firewood-connection/tests/manager.rs diff --git a/Cargo.toml b/Cargo.toml index 5e168130918e..4def532994f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "shale", "firewood", "fwdctl", + "firewood-connection" ] [profile.release] diff --git a/README.md b/README.md index 30737c81b234..ea58e90f6ae6 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. +To integrate firewood into a custom VM or other project, see the [firewood-connection](./firewood-connection/README.md) for a straightforward way to use firewood via custom message-passing. + ## CLI Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](fwdctl/README.md). diff --git a/firewood-connection/Cargo.toml b/firewood-connection/Cargo.toml new file mode 100644 index 000000000000..6889977e77bf --- /dev/null +++ b/firewood-connection/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "firewood-connection" +version = "0.0.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.21.1", features = ["rt-multi-thread", "sync", "macros"] } +crossbeam-channel = "0.5.7" +firewood = { version = "0.0.1", path = "../firewood" } +log = "0.4.17" +env_logger = "0.10.0" + +[dev-dependencies] +serial_test = "1.0.0" diff --git a/firewood-connection/README.md b/firewood-connection/README.md new file mode 100644 index 000000000000..9569d679069f --- /dev/null +++ b/firewood-connection/README.md @@ -0,0 +1,28 @@ +# firewood-connection + +The firewood-connection library provides primitives to run firewood inside of an +async environment, for example a custom VM. firewood-connection provides a +series of channels that can be used to send `Actions` or closures from the async +environment to firewood in order to be executed. Firewood runs asynchronously on +its own thread, and messages are passed between the application and the firewood +library. + +This solution is required because firewood currently depends on its own async +runtime in order to run its storage layer. Since nesting async runtimes is not +possible, firewood needs to run on its own dedicated thread. Ideally firewood +would support async directly, and this crate would not be necessary, and we are +working to add this support directly in the near future. + +## Usage + +Using the firewood-connection is straightforward. A call to `initialize()` +sets up the communication channel between async environments and returns a +`Manager`. Once a `Manager` is established, `Manger.call()` can pass +functions along the channel that will be executed on the database. Messages will +also be returned along the channel indicating the result of the database +operation. + +## Example + +See the [transfervm](https://github.com/ava-labs/transfervm-rs) for +an example of a custom VM that integrates with firewood-connection. diff --git a/firewood-connection/src/lib.rs b/firewood-connection/src/lib.rs new file mode 100644 index 000000000000..e7595c0a555d --- /dev/null +++ b/firewood-connection/src/lib.rs @@ -0,0 +1,83 @@ +use std::{path::Path, thread}; + +use crossbeam_channel::Sender; +use firewood::db::{DBConfig, DBError, DB}; +use tokio::sync::oneshot; + +type Action = Box; + +/// Firewood connection manager communicates with the firewood database in a +/// separate thread through channels. +#[derive(Clone, Debug)] +pub struct Manager { + // Channel used by the caller to communicate with firewood. + action_tx: Sender, +} + +impl Manager { + pub async fn new>(db_path: P, cfg: DBConfig) -> Result { + let owned_path = db_path.as_ref().to_path_buf(); + initialize(move || DB::new(&owned_path.to_string_lossy(), &cfg)).await + } + + /// Calls a db action over channel. + // TODO: Remove static lifetime bounds + pub async fn call(&self, func: F) -> T + where + F: FnOnce(&DB) -> T + Send + 'static, + T: Send + 'static, + { + // communicate result of action. + let (result_tx, result_rx) = oneshot::channel(); + + // sends an action function to the db via channel and returns the response. + self.action_tx + .send(Box::new(move |db| { + let resp = func(db); + let _ = result_tx.send(resp); + })) + .expect("action tx channel failed"); + + result_rx.await.expect("action rx channel failed") + } +} + +/// Creates a new work thread which listens for actions over channel. +async fn initialize(new_db: I) -> Result +where + I: FnOnce() -> Result + Send + 'static, +{ + // provides communication from the caller to the db in the work thread. + let (action_tx, action_rx) = crossbeam_channel::unbounded::(); + // communicates the result of the db initialize action. + let (init_tx, init_rx) = oneshot::channel(); + + log::debug!("work thread spawned"); + thread::spawn(move || { + let db = match new_db() { + Ok(db) => db, + Err(e) => { + // return error + let _ = init_tx.send(Err(e)); + return; + } + }; + + if init_tx.send(Ok(())).is_err() { + log::error!("failed to send db action result: ok"); + return; + } + + // listen for actions + while let Ok(func) = action_rx.recv() { + log::debug!("action received"); + func(&db); + log::debug!("action complete"); + } + }); + + init_rx + .await + .expect("result rx channel failed") + .map(|_| Manager { action_tx }) +} diff --git a/firewood-connection/tests/manager.rs b/firewood-connection/tests/manager.rs new file mode 100644 index 000000000000..1e7e6bc26d99 --- /dev/null +++ b/firewood-connection/tests/manager.rs @@ -0,0 +1,136 @@ +use std::{sync::Arc, vec}; + +use firewood::db::DBConfig; +use firewood_connection::Manager; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn test_manager() { + let cfg = setup_db(); + + let manager = Manager::new("/tmp/simple_db", cfg).await.unwrap(); + + manager + .call(|db| { + db.new_writebatch() + .kv_insert("foo", "bar".as_bytes().to_vec()) + .unwrap() + .commit(); + }) + .await; + + let resp = manager.call(|db| db.kv_get(b"foo").unwrap()).await; + + assert_eq!(resp, "bar".as_bytes().to_vec()); + cleanup_db() +} + +#[tokio::test] +#[serial] +async fn concurrent_reads_writes() { + let cfg = setup_db(); + + let manager = Manager::new("/tmp/simple_db", cfg).await.unwrap(); + + manager + .call(|db| { + let items = vec![ + ("d", "verb"), + ("do", "verb"), + ("doe", "reindeer"), + ("e", "coin"), + ]; + + for (k, v) in items.iter() { + db.new_writebatch() + .kv_insert(k, v.as_bytes().to_vec()) + .unwrap() + .commit(); + } + }) + .await; + + let handle = tokio::spawn(async move { + manager.call(|db| db.kv_get(b"doe").unwrap()).await; + }) + .await; + + match handle { + Ok(_) => cleanup_db(), + Err(e) => log::error!("{e}"), + } +} + +#[tokio::test(flavor = "multi_thread")] +#[serial] +async fn multi_thread_read_writes() { + let cfg = setup_db(); + + let manager = Arc::new(Manager::new("/tmp/simple_db", cfg).await.unwrap()); + + let keys = vec!["a", "b", "c", "d", "e"]; + let m1 = manager.clone(); + + tokio::spawn(async move { + for k in keys { + m1.call(move |db| { + db.new_writebatch() + .kv_insert(k, "z".as_bytes().to_vec()) + .unwrap() + .commit() + }) + .await; + } + }); + + let m2 = manager.clone(); + let keys2 = vec!["a", "b", "c", "d", "e"]; + + tokio::spawn(async move { + for k in keys2 { + m2.call(move |db| { + db.new_writebatch() + .kv_insert(k, "x".as_bytes().to_vec()) + .unwrap() + .commit() + }) + .await + } + }); + + let m3 = manager; + let keys3 = vec!["a", "b", "c", "d", "e"]; + + tokio::spawn(async move { + for k in keys3 { + m3.call(move |db| { + println!("{:?}", db.kv_get(k)); + }) + .await + } + }); +} + +#[cfg(test)] +fn setup_db() -> DBConfig { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::Info) + .is_test(true) + .try_init(); + + DBConfig::builder() + .wal(firewood::db::WALConfig::builder().max_revisions(10).build()) + .truncate(true) + .build() +} + +// Removes the firewood database on disk +#[cfg(test)] +fn cleanup_db() { + use std::fs::remove_dir_all; + + if let Err(e) = remove_dir_all("/tmp/simple_db") { + eprintln!("failed to delete testing dir: {e}"); + } +} From 92cdcb136598a61fcad1ee8050a9d48677fee1dd Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Tue, 21 Mar 2023 23:01:18 +0000 Subject: [PATCH 0085/1053] range proof test coverage with geth and bug fixes --- firewood/src/merkle.rs | 25 +- firewood/src/merkle_util.rs | 8 +- firewood/src/proof.rs | 248 +++++++++---- firewood/tests/merkle.rs | 681 +++++++++++++++++++++++++++++------- 4 files changed, 766 insertions(+), 196 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 25f3ce5faa69..016101719cf1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -178,6 +178,11 @@ impl Debug for BranchNode { write!(f, " ({i:x} {c})")?; } } + for (i, c) in self.chd_eth_rlp.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {:?})", c)?; + } + } write!( f, " v={}]", @@ -230,7 +235,12 @@ impl BranchNode { if self.chd_eth_rlp[i].is_none() { stream.append_empty_data() } else { - stream.append_raw(&self.chd_eth_rlp[i].clone().unwrap(), 1) + let v = self.chd_eth_rlp[i].clone().unwrap(); + if v.len() == 32 { + stream.append(&v) + } else { + stream.append_raw(&v, 1) + } } } }; @@ -311,7 +321,7 @@ pub struct ExtNode(PartialPath, ObjPtr, Option>); impl Debug for ExtNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Extension {:?} {}]", self.0, self.1) + write!(f, "[Extension {:?} {} {:?}]", self.0, self.1, self.2) } } @@ -336,7 +346,12 @@ impl ExtNode { if self.2.is_none() { stream.append_empty_data(); } else { - stream.append_raw(&self.2.clone().unwrap(), 1); + let v = self.2.clone().unwrap(); + if v.len() == 32 { + stream.append(&v); + } else { + stream.append_raw(&v, 1); + } } } stream.out().into() @@ -357,6 +372,10 @@ impl ExtNode { pub fn chd_mut(&mut self) -> &mut ObjPtr { &mut self.1 } + + pub fn chd_eth_rlp_mut(&mut self) -> &mut Option> { + &mut self.2 + } } #[derive(PartialEq, Eq, Clone)] diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index e6d3e94cb74d..8c8299298ed0 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,6 +1,6 @@ -use crate::dynamic_mem::DynamicMem; use crate::merkle::*; use crate::proof::Proof; +use crate::{dynamic_mem::DynamicMem, proof::ProofError}; use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr}; use std::rc::Rc; @@ -104,11 +104,9 @@ impl MerkleSetup { last_key: K, keys: Vec, vals: Vec, - ) -> Result { + ) -> Result { let hash: [u8; 32] = *self.root_hash()?; - proof - .verify_range_proof(hash, first_key, last_key, keys, vals) - .map_err(|_err| DataStoreError::ProofVerificationError) + proof.verify_range_proof(hash, first_key, last_key, keys, vals) } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index f96a5be1f756..55cb60c6b474 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -15,7 +15,7 @@ use std::fmt; use std::ops::Deref; /// Hash -> RLP encoding map -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Proof(pub HashMap<[u8; 32], Vec>); #[derive(Debug)] @@ -34,6 +34,8 @@ pub enum ProofError { NodeNotInTrie, InvalidNode(MerkleError), EmptyRange, + ForkLeft, + ForkRight, BlobStoreError(BlobError), SystemError(Errno), InvalidRootHash, @@ -79,6 +81,8 @@ impl fmt::Display for ProofError { ProofError::NodeNotInTrie => write!(f, "node not in trie"), ProofError::InvalidNode(e) => write!(f, "invalid node: {e:?}"), ProofError::EmptyRange => write!(f, "empty range"), + ProofError::ForkLeft => write!(f, "fork left"), + ProofError::ForkRight => write!(f, "fork right"), ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), ProofError::SystemError(e) => write!(f, "system error: {e:?}"), ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), @@ -434,34 +438,79 @@ impl Proof { Some(p) => { // Return when reaching the end of the key. if key_index == chunks.len() { - let mut data: Vec = Vec::new(); + cur_key = &chunks[key_index..]; + let mut data = None; // Decode the last subproof to get the value. if p.hash.is_some() { let proof = proofs_map .get(&p.hash.unwrap()) .ok_or(ProofError::ProofNodeMissing)?; - let (chd_ptr, _, _) = self.decode_node(merkle, cur_key, proof, true)?; - - if chd_ptr.is_some() { - u_ref = merkle - .get_node(chd_ptr.unwrap()) - .map_err(|_| ProofError::DecodeError)?; - match &u_ref.inner() { - NodeType::Branch(n) => { - if let Some(v) = n.value() { - data = v.deref().to_vec(); + (chd_ptr, _, _) = self.decode_node(merkle, cur_key, proof, true)?; + + // Link the child to the parent based on the node type. + match &u_ref.inner() { + NodeType::Branch(n) => { + match n.chd()[branch_index as usize] { + // If the child already resolved, then use the existing node. + Some(_) => {} + None => { + // insert the leaf to the empty slot + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[branch_index as usize] = chd_ptr; + }) + .unwrap(); } + }; + } + NodeType::Extension(_) => { + // If the child already resolved, then use the existing node. + let node = u_ref.inner().as_extension().unwrap().chd(); + if node.is_null() { + u_ref + .write(|u| { + let uu = u.inner_mut().as_extension_mut().unwrap(); + *uu.chd_mut() = if let Some(chd_p) = chd_ptr { + chd_p + } else { + ObjPtr::null() + } + }) + .unwrap(); } - NodeType::Leaf(n) => { - if n.path().len() == 0 { - data = n.data().deref().to_vec(); - } + } + // We should not hit a leaf node as a parent. + _ => { + return Err(ProofError::InvalidNode( + MerkleError::ParentLeafBranch, + )) + } + }; + } + drop(u_ref); + if chd_ptr.is_some() { + let c_ref = merkle + .get_node(chd_ptr.unwrap()) + .map_err(|_| ProofError::DecodeError)?; + match &c_ref.inner() { + NodeType::Branch(n) => { + if let Some(v) = n.value() { + data = Some(v.deref().to_vec()); } - _ => (), } + NodeType::Leaf(n) => { + // Return the value on the node only when the key matches exactly + // (e.g. the length path of subproof node is 0). + if p.hash.is_none() || (p.hash.is_some() && n.path().len() == 0) + { + data = Some(n.data().deref().to_vec()); + } + } + _ => (), } } - return Ok(Some(data)); + return Ok(data); } // The trie doesn't contain the key. @@ -491,7 +540,7 @@ impl Proof { /// /// # Arguments /// - /// * `end_node` - A boolean indicates whether this is the ebd node to decode, thus no `key` + /// * `end_node` - A boolean indicates whether this is the end node to decode, thus no `key` /// to be present. #[allow(clippy::type_complexity)] fn decode_node( @@ -517,35 +566,41 @@ impl Proof { rlp.as_raw().to_vec() }; - // Check if the key of current node match with the given key. - if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { - return Ok((None, None, 0)); - } + let ext_ptr: Option>; + let subproof: Option; if term { - let leaf = - Node::new(NodeType::Leaf(LeafNode::new(cur_key.clone(), data.clone()))); - let leaf_ptr = merkle.new_node(leaf).map_err(|_| ProofError::DecodeError)?; - Ok(( - Some(leaf_ptr.as_ptr()), - Some(SubProof { - rlp: data, - hash: None, - }), - cur_key.len(), - )) + ext_ptr = Some( + merkle + .new_node(Node::new(NodeType::Leaf(LeafNode::new( + cur_key.clone(), + data.clone(), + )))) + .map_err(|_| ProofError::DecodeError)? + .as_ptr(), + ); + subproof = Some(SubProof { + rlp: data, + hash: None, + }); } else { - let ext_ptr = merkle - .new_node(Node::new(NodeType::Extension(ExtNode::new( - cur_key.clone(), - ObjPtr::null(), - Some(data.clone()), - )))) - .map_err(|_| ProofError::DecodeError)?; - let subproof = self - .generate_subproof(data) - .map(|subproof| (subproof, cur_key.len()))?; - Ok((Some(ext_ptr.as_ptr()), subproof.0, subproof.1)) + ext_ptr = Some( + merkle + .new_node(Node::new(NodeType::Extension(ExtNode::new( + cur_key.clone(), + ObjPtr::null(), + Some(data.clone()), + )))) + .map_err(|_| ProofError::DecodeError)? + .as_ptr(), + ); + subproof = self.generate_subproof(data)?; } + + // Check if the key of current node match with the given key. + if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { + return Ok((ext_ptr, None, 0)); + } + Ok((ext_ptr, subproof, cur_key.len())) } BRANCH_NODE_SIZE => { // Extract the value of the branch node. @@ -681,8 +736,21 @@ fn unset_internal>( .map_err(|_| ProofError::DecodeError)?; index += cur_key.len(); } - // The fork point cannot be a leaf since it doesn't have any children. - _ => return Err(ProofError::InvalidNode(MerkleError::UnsetInternal)), + NodeType::Leaf(n) => { + let cur_key = n.path().clone().into_inner(); + if left_chunks.len() - index < cur_key.len() { + fork_left = compare(&left_chunks[index..], &cur_key) + } else { + fork_left = compare(&left_chunks[index..index + cur_key.len()], &cur_key) + } + + if right_chunks.len() - index < cur_key.len() { + fork_right = compare(&right_chunks[index..], &cur_key) + } else { + fork_right = compare(&right_chunks[index..index + cur_key.len()], &cur_key) + } + break; + } } } @@ -696,6 +764,7 @@ fn unset_internal>( u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[i as usize] = None; uu.chd_eth_rlp_mut()[i as usize] = None; }) .unwrap(); @@ -716,17 +785,14 @@ fn unset_internal>( let node = n.chd(); let cur_key = n.path().clone().into_inner(); if fork_left.is_lt() && fork_right.is_lt() { - drop(u_ref); return Err(ProofError::EmptyRange); } if fork_left.is_gt() && fork_right.is_gt() { - drop(u_ref); return Err(ProofError::EmptyRange); } if fork_left.is_ne() && fork_right.is_ne() { // The fork point is root node, unset the entire trie if parent.is_null() { - drop(u_ref); return Ok(true); } let mut p_ref = merkle @@ -735,11 +801,10 @@ fn unset_internal>( p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[left_chunks[index - 1] as usize] = None; pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; }) .unwrap(); - drop(p_ref); - drop(u_ref); return Ok(false); } let p = u_ref.as_ptr(); @@ -769,7 +834,49 @@ fn unset_internal>( } Ok(false) } - _ => Err(ProofError::InvalidNode(MerkleError::UnsetInternal)), + NodeType::Leaf(_) => { + if fork_left.is_lt() && fork_right.is_lt() { + return Err(ProofError::EmptyRange); + } + if fork_left.is_gt() && fork_right.is_gt() { + return Err(ProofError::EmptyRange); + } + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; + if fork_left.is_ne() && fork_right.is_ne() { + p_ref + .write(|p| match p.inner_mut() { + NodeType::Extension(n) => { + *n.chd_mut() = ObjPtr::null(); + *n.chd_eth_rlp_mut() = None; + } + NodeType::Branch(n) => { + n.chd_mut()[left_chunks[index - 1] as usize] = None; + n.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + } + _ => {} + }) + .unwrap(); + } else if fork_right.is_ne() { + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[left_chunks[index - 1] as usize] = None; + pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + }) + .unwrap(); + } else if fork_left.is_ne() { + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[right_chunks[index - 1] as usize] = None; + pp.chd_eth_rlp_mut()[right_chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + Ok(false) + } } } @@ -798,9 +905,9 @@ fn unset_node_ref>( // fullnode(it's a non-existent branch). return Ok(()); } - // Add the sentinel root - let mut chunks = vec![0]; - chunks.extend(to_nibbles(key.as_ref())); + + let mut chunks = Vec::new(); + chunks.extend(key.as_ref()); let mut u_ref = merkle .get_node(node.unwrap()) @@ -815,6 +922,7 @@ fn unset_node_ref>( u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[i as usize] = None; uu.chd_eth_rlp_mut()[i as usize] = None; }) .unwrap(); @@ -824,6 +932,7 @@ fn unset_node_ref>( u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[i as usize] = None; uu.chd_eth_rlp_mut()[i as usize] = None; }) .unwrap(); @@ -851,6 +960,7 @@ fn unset_node_ref>( p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[chunks[index - 1] as usize] = None; pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; }) .unwrap(); @@ -868,17 +978,15 @@ fn unset_node_ref>( // cached hash. p_ref .write(|p| { - let pp = p.inner_mut().as_branch_mut().unwrap(); + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[chunks[index - 1] as usize] = None; pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; }) .unwrap(); } - drop(u_ref); - drop(p_ref); return Ok(()); } - drop(u_ref); return unset_node_ref( merkle, p, @@ -888,8 +996,24 @@ fn unset_node_ref>( remove_left, ); } - // Noop for leaf node as it doesn't have any children and no precalculated RLP value stored. - NodeType::Leaf(_) => (), + NodeType::Leaf(_) => { + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; + p_ref + .write(|p| match p.inner_mut() { + NodeType::Extension(n) => { + *n.chd_mut() = ObjPtr::null(); + *n.chd_eth_rlp_mut() = None; + } + NodeType::Branch(n) => { + n.chd_mut()[chunks[index - 1] as usize] = None; + n.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + } + _ => {} + }) + .unwrap(); + } } Ok(()) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 1fc39d22c04a..4608794e0e49 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,4 +1,12 @@ -use firewood::{merkle_util::*, proof::Proof}; +use firewood::{ + merkle::compare, + merkle_util::*, + proof::{Proof, ProofError}, +}; + +use std::collections::HashMap; + +use rand::Rng; fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( items: Vec<(K, V)>, @@ -65,7 +73,7 @@ fn test_root_hash_fuzz_insertions() { }; for _ in 0..10 { let mut items = Vec::new(); - for _ in 0..10000 { + for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.push((keygen(), val)); } @@ -93,9 +101,9 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { .collect(); key }; - for i in 0..1000 { + for i in 0..10 { let mut items = std::collections::HashMap::new(); - for _ in 0..100 { + for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.insert(keygen(), val); } @@ -166,7 +174,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { }; for i in 0..10 { let mut items = std::collections::HashMap::new(); - for _ in 0..1000 { + for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.insert(keygen(), val); } @@ -310,205 +318,626 @@ fn test_empty_tree_proof() -> Result<(), DataStoreError> { } #[test] -fn test_range_proof() -> Result<(), DataStoreError> { - let mut items = vec![ - ("doa", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("ddd", "ok"), - ]; +// Tests normal range proof with both edge proofs as the existent proof. +// The test cases are generated randomly. +fn test_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let start = 0; - let end = &items.len() - 1; - let mut proof = merkle.prove(items[start].0)?; + for _ in 0..10 { + let start = rand::thread_rng().gen_range(0..items.len()); + let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + let mut proof = merkle.prove(items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(items[end].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for i in start..=end { + keys.push(&items[i].0); + vals.push(&items[i].1); + } + + merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + } + Ok(()) +} + +#[test] +// Tests normal range proof with two non-existent proofs. +// The test cases are generated randomly. +// TODO: flaky test +fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + for _ in 0..10 { + let start = rand::thread_rng().gen_range(0..items.len()); + let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + // Short circuit if the decreased key is same with the previous key + let first = decrease_key(&items[start].0); + if start != 0 && compare(first.as_ref(), items[start - 1].0.as_ref()).is_eq() { + continue; + } + // Short circuit if the decreased key is underflow + if compare(first.as_ref(), items[start].0.as_ref()).is_gt() { + continue; + } + // Short circuit if the increased key is same with the next key + let last = increase_key(&items[end - 1].0); + if end != items.len() && compare(last.as_ref(), items[end].0.as_ref()).is_eq() { + continue; + } + // Short circuit if the increased key is overflow + if compare(last.as_ref(), items[end - 1].0.as_ref()).is_lt() { + continue; + } + + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let mut keys: Vec<[u8; 32]> = Vec::new(); + let mut vals: Vec<[u8; 20]> = Vec::new(); + for i in start..end { + keys.push(*items[i].0); + vals.push(*items[i].1); + } + + merkle.verify_range_proof(&proof, first, last, keys, vals)?; + } + + Ok(()) +} + +#[test] +// Tests such scenarios: +// - There exists a gap between the first element and the left edge proof +// - There exists a gap between the last element and the right edge proof +fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + // Case 1 + let mut start = 100; + let mut end = 200; + let first = decrease_key(&items[start].0); + + let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0)?; + let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); - let mut keys = Vec::new(); - let mut vals = Vec::new(); - for i in start + 1..end { - keys.push(&items[i].0); - vals.push(&items[i].1); + start = 105; // Gap created + let mut keys: Vec<[u8; 32]> = Vec::new(); + let mut vals: Vec<[u8; 20]> = Vec::new(); + // Create gap + for i in start..end { + keys.push(*items[i].0); + vals.push(*items[i].1); } + assert!(merkle + .verify_range_proof(&proof, first, *items[end - 1].0, keys, vals) + .is_err()); + + // Case 2 + start = 100; + end = 200; + let last = increase_key(&items[end - 1].0); - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + let mut proof = merkle.prove(items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + end = 195; // Capped slice + let mut keys: Vec<[u8; 32]> = Vec::new(); + let mut vals: Vec<[u8; 20]> = Vec::new(); + // Create gap + for i in start..end { + keys.push(*items[i].0); + vals.push(*items[i].1); + } + assert!(merkle + .verify_range_proof(&proof, *items[start].0, last, keys, vals) + .is_err()); Ok(()) } #[test] -fn test_range_proof_with_non_existent_proof() -> Result<(), DataStoreError> { - let mut items = vec![ - (std::str::from_utf8(&[0x7]).unwrap(), "verb"), - (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), - (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), - (std::str::from_utf8(&[0x6]).unwrap(), "coin"), - (std::str::from_utf8(&[0x3]).unwrap(), "stallion"), - ]; - +// Tests the proof with only one element. The first edge proof can be existent one or +// non-existent one. +fn test_one_element_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let start = 0; - let end = items.len(); - let mut proof = merkle.prove(std::str::from_utf8(&[0x2]).unwrap())?; + // One element with existent edge proof, both edge proofs + // point to the SAME key. + let start = 1000; + let start_proof = merkle.prove(&items[start].0)?; + assert!(!start_proof.0.is_empty()); + + merkle.verify_range_proof( + &start_proof, + &items[start].0, + &items[start].0, + vec![&items[start].0], + vec![&items[start].1], + )?; + + // One element with left non-existent edge proof + let first = decrease_key(&items[start].0); + let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(std::str::from_utf8(&[0x8]).unwrap())?; + let end_proof = merkle.prove(&items[start].0)?; assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + merkle.verify_range_proof( + &proof, + first, + *items[start].0, + vec![*items[start].0], + vec![*items[start].1], + )?; + // One element with right non-existent edge proof + let last = increase_key(&items[start].0); + let mut proof = merkle.prove(&items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); - let mut keys: Vec<&str> = Vec::new(); - let mut vals: Vec<&str> = Vec::new(); - for i in start..end { - keys.push(items[i].0); - vals.push(items[i].1); - } + merkle.verify_range_proof( + &proof, + *items[start].0, + last, + vec![*items[start].0], + vec![*items[start].1], + )?; + + // One element with two non-existent edge proofs + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); merkle.verify_range_proof( &proof, - std::str::from_utf8(&[0x2]).unwrap(), - std::str::from_utf8(&[0x8]).unwrap(), - keys, - vals, + first, + last, + vec![*items[start].0], + vec![*items[start].1], )?; + // Test the mini trie with only a single element. + let key = rand::thread_rng().gen::<[u8; 32]>(); + let val = rand::thread_rng().gen::<[u8; 20]>(); + let merkle = merkle_build_test(vec![(key, val)], 0x10000, 0x10000)?; + + let first: [u8; 32] = [0; 32]; + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&key)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + merkle.verify_range_proof(&proof, first, key, vec![key], vec![val])?; + Ok(()) } #[test] -#[allow(unused_must_use)] -fn test_range_proof_with_invalid_non_existent_proof() { - let mut items = vec![ - (std::str::from_utf8(&[0x8]).unwrap(), "verb"), - (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), - (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), - (std::str::from_utf8(&[0x6]).unwrap(), "coin"), - (std::str::from_utf8(&[0x2]).unwrap(), "stallion"), - ]; - +// Tests the range proof with all elements. +// The edge proofs can be nil. +fn test_all_elements_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + let item_iter = items.clone().into_iter(); + let keys: Vec<&[u8; 32]> = item_iter.clone().map(|item| item.0).collect(); + let vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); + + let empty_proof = Proof(HashMap::new()); + let empty_key: [u8; 32] = [0; 32]; + merkle.verify_range_proof( + &empty_proof, + &empty_key, + &empty_key, + keys.clone(), + vals.clone(), + )?; + + // With edge proofs, it should still work. let start = 0; let end = &items.len() - 1; - let mut proof = merkle - .as_ref() - .unwrap() - .prove(std::str::from_utf8(&[0x3]).unwrap()); - assert!(!proof.as_ref().unwrap().0.is_empty()); - let end_proof = merkle - .as_ref() - .unwrap() - .prove(std::str::from_utf8(&[0x7]).unwrap()); - assert!(!end_proof.as_ref().unwrap().0.is_empty()); + let mut proof = merkle.prove(&items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[end].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); - proof.as_mut().unwrap().concat_proofs(end_proof.unwrap()); + merkle.verify_range_proof( + &proof, + items[start].0, + items[end].0, + keys.clone(), + vals.clone(), + )?; - let mut keys = Vec::new(); - let mut vals = Vec::new(); - // Create gap - for i in start + 2..end - 1 { - keys.push(&items[i].0); - vals.push(&items[i].1); - } + // Even with non-existent edge proofs, it should still work. + let first: [u8; 32] = [0; 32]; + let last: [u8; 32] = [255; 32]; + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); - merkle - .unwrap() - .verify_range_proof( - proof.as_ref().unwrap(), - &items[start].0, - &items[end].0, - keys, - vals, - ) - .is_err(); + merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; + + Ok(()) } #[test] -// The start and end nodes are both the same. -fn test_one_element_range_proof() -> Result<(), DataStoreError> { - let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; +// Tests the range proof with "no" element. The first edge proof must +// be a non-existent proof. +fn test_empty_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + let cases = vec![(items.len() - 1, false)]; + for (_, c) in cases.iter().enumerate() { + let first = increase_key(&items[c.0].0); + let proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + + // key and value vectors are intentionally empty. + let keys: Vec<[u8; 32]> = Vec::new(); + let vals: Vec<[u8; 20]> = Vec::new(); + + if c.1 { + assert!(merkle + .verify_range_proof(&proof, first, first, keys, vals) + .is_err()); + } else { + merkle.verify_range_proof(&proof, first, first, keys, vals)?; + } + } + + Ok(()) +} + +#[test] +// Focuses on the small trie with embedded nodes. If the gapped +// node is embedded in the trie, it should be detected too. +fn test_gapped_range_proof() -> Result<(), ProofError> { + let mut items = Vec::new(); + // Sorted entries + for i in 0..10 as u32 { + let mut key: [u8; 32] = [0; 32]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + } + items.push((key, i.to_be_bytes())); + } let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let start = 0; - // start and end nodes are the same - let end = 0; - let start_proof = merkle.prove(items[start].0)?; - assert!(!start_proof.0.is_empty()); + let first = 2; + let last = 8; + + let mut proof = merkle.prove(&items[first].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[last - 1].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); let mut keys = Vec::new(); let mut vals = Vec::new(); - for i in start..=end { + for i in first..last { + if i == (first + last) / 2 { + continue; + } keys.push(&items[i].0); vals.push(&items[i].1); } - assert!(merkle.verify_range_proof(&start_proof, &items[start].0, &items[end].0, keys, vals)?); + assert!(merkle + .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) + .is_err()); Ok(()) } #[test] -// The range proof starts from 0 (root) to the last one -fn test_all_elements_proof() -> Result<(), DataStoreError> { - let mut items = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")]; +// Tests the element is not in the range covered by proofs. +fn test_same_side_proof() -> Result<(), DataStoreError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let start = 0; - let end = &items.len() - 1; - let mut proof = merkle.prove(items[start].0)?; + let pos = 1000; + let mut last = decrease_key(&items[pos].0); + let mut first = last; + first = decrease_key(&first); + + let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0)?; + let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + assert!(merkle + .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) + .is_err()); + + first = increase_key(&items[pos].0); + last = first; + last = increase_key(&last); + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); - let mut keys = Vec::new(); - let mut vals = Vec::new(); - for i in start..=end { - keys.push(&items[i].0); - vals.push(&items[i].1); - } + assert!(merkle + .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) + .is_err()); - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + Ok(()) +} +#[test] +// Tests the range starts from zero. +fn test_single_side_range_proof() -> Result<(), ProofError> { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..4096 as u32 { + let key = rand::thread_rng().gen::<[u8; 32]>(); + let val = rand::thread_rng().gen::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + let cases = vec![0, 1, 100, 1000, items.len() - 1]; + for case in cases { + let start: [u8; 32] = [0; 32]; + let mut proof = merkle.prove(start)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(items[case].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let item_iter = items.clone().into_iter().take(case + 1); + let keys = item_iter.clone().map(|item| *item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); + + merkle.verify_range_proof(&proof, start, *items[case].0, keys, vals)?; + } + } Ok(()) } #[test] -// Tests the range proof with "no" element. The first edge proof must -// be a non-existent proof. -fn test_empty_range_proof() -> Result<(), DataStoreError> { - let mut items = vec![ - (std::str::from_utf8(&[0x7]).unwrap(), "verb"), - (std::str::from_utf8(&[0x4]).unwrap(), "reindeer"), - (std::str::from_utf8(&[0x5]).unwrap(), "puppy"), - (std::str::from_utf8(&[0x6]).unwrap(), "coin"), - (std::str::from_utf8(&[0x3]).unwrap(), "stallion"), - ]; +// Tests the range ends with 0xffff...fff. +fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..4096 as u32 { + let key = rand::thread_rng().gen::<[u8; 32]>(); + let val = rand::thread_rng().gen::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + let cases = vec![0, 1, 100, 1000, items.len() - 1]; + for case in cases { + let end: [u8; 32] = [255; 32]; + let mut proof = merkle.prove(items[case].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(end)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let item_iter = items.clone().into_iter().skip(case); + let keys = item_iter.clone().map(|item| item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); + + merkle.verify_range_proof(&proof, items[case].0, &end, keys, vals)?; + } + } + Ok(()) +} +#[test] +// Tests normal range proof with both edge proofs +// as the existent proof, but with an extra empty value included, which is a +// noop technically, but practically should be rejected. +fn test_empty_value_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(512); + let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let first = std::str::from_utf8(&[0x8]).unwrap(); - let proof = merkle.prove(first)?; + // Create a new entry with a slightly modified key + let mid_index = items.len() / 2; + let key = increase_key(&items[mid_index - 1].0); + let empty_data: [u8; 20] = [0; 20]; + items.splice( + mid_index..mid_index, + vec![(&key, &empty_data)].iter().cloned(), + ); + + let start = 1; + let end = items.len() - 1; + + let mut proof = merkle.prove(&items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&items[end - 1].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let item_iter = items.clone().into_iter().skip(start).take(end - start); + let keys = item_iter.clone().map(|item| item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); + assert!(merkle + .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals) + .is_err()); + + Ok(()) +} + +#[test] +// Tests the range proof with all elements, +// but with an extra empty value included, which is a noop technically, but +// practically should be rejected. +fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(512); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + // Create a new entry with a slightly modified key + let mid_index = items.len() / 2; + let key = increase_key(&items[mid_index - 1].0); + let empty_data: [u8; 20] = [0; 20]; + items.splice( + mid_index..mid_index, + vec![(&key, &empty_data)].iter().cloned(), + ); + + let start = 0; + let end = items.len() - 1; + + let mut proof = merkle.prove(items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(items[end].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let item_iter = items.clone().into_iter(); + let keys = item_iter.clone().map(|item| item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); + assert!(merkle + .verify_range_proof(&proof, items[start].0, items[end].0, keys, vals) + .is_err()); + + Ok(()) +} + +#[test] +fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { + let mut items = Vec::new(); + items.push(( + hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("02").expect("Decoding failed"), + )); + items.push(( + hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("03").expect("Decoding failed"), + )); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"); + let end = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .expect("Decoding failed"); + + let mut proof = merkle.prove(&start)?; assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(&end)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); - // key and value vectors are intentionally empty. - let keys: Vec<&str> = Vec::new(); - let vals: Vec<&str> = Vec::new(); + let item_iter = items.clone().into_iter(); + let keys = item_iter.clone().map(|item| item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); - merkle.verify_range_proof(&proof, first, first, keys, vals)?; + merkle.verify_range_proof(&proof, start, end, keys, vals)?; Ok(()) } + +fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { + let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); + for i in 0..100 as u32 { + let mut key: [u8; 32] = [0; 32]; + let mut data: [u8; 20] = [0; 20]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + data[index] = *d; + } + items.insert(key, data); + + let mut more_key: [u8; 32] = [0; 32]; + for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { + more_key[index] = *d; + } + items.insert(more_key, data); + } + + for _ in 0..n { + let key = rand::thread_rng().gen::<[u8; 32]>(); + let val = rand::thread_rng().gen::<[u8; 20]>(); + items.insert(key, val); + } + return items; +} + +fn increase_key(key: &[u8; 32]) -> [u8; 32] { + let mut new_key = key.clone(); + for i in (0..key.len()).rev() { + if new_key[i] == 0xff { + new_key[i] = 0x00; + } else { + new_key[i] += 1; + break; + } + } + new_key +} + +fn decrease_key(key: &[u8; 32]) -> [u8; 32] { + let mut new_key = key.clone(); + for i in (0..key.len()).rev() { + if new_key[i] == 0x00 { + new_key[i] = 0xff; + } else { + new_key[i] -= 1; + break; + } + } + new_key +} From 60ab949c3ef9bf3d1363e87bd31ed212fee44dc7 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 23 Mar 2023 00:56:14 +0000 Subject: [PATCH 0086/1053] [range proof] add test_bloated_range_proof and test_bad_range_proof --- firewood/src/merkle.rs | 17 +++--- firewood/tests/merkle.rs | 122 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 11 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 016101719cf1..01878f680245 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -13,6 +13,7 @@ use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; pub const NBRANCH: usize = 16; +pub const HASH_SIZE: usize = 32; #[derive(Debug)] pub enum MerkleError { @@ -40,15 +41,15 @@ impl fmt::Display for MerkleError { impl Error for MerkleError {} #[derive(PartialEq, Eq, Clone)] -pub struct Hash(pub [u8; 32]); +pub struct Hash(pub [u8; HASH_SIZE]); impl Hash { const MSIZE: u64 = 32; } impl std::ops::Deref for Hash { - type Target = [u8; 32]; - fn deref(&self) -> &[u8; 32] { + type Target = [u8; HASH_SIZE]; + fn deref(&self) -> &[u8; HASH_SIZE] { &self.0 } } @@ -236,7 +237,7 @@ impl BranchNode { stream.append_empty_data() } else { let v = self.chd_eth_rlp[i].clone().unwrap(); - if v.len() == 32 { + if v.len() == HASH_SIZE { stream.append(&v) } else { stream.append_raw(&v, 1) @@ -347,7 +348,7 @@ impl ExtNode { stream.append_empty_data(); } else { let v = self.2.clone().unwrap(); - if v.len() == 32 { + if v.len() == HASH_SIZE { stream.append(&v); } else { stream.append_raw(&v, 1); @@ -442,7 +443,7 @@ impl Node { fn get_eth_rlp_long(&self, store: &dyn ShaleStore) -> bool { *self.eth_rlp_long.get_or_init(|| { self.lazy_dirty.set(true); - self.get_eth_rlp::(store).len() >= 32 + self.get_eth_rlp::(store).len() >= HASH_SIZE }) } @@ -1798,7 +1799,7 @@ impl Merkle { let mut chunks = Vec::new(); chunks.extend(to_nibbles(key.as_ref())); - let mut proofs: HashMap<[u8; 32], Vec> = HashMap::new(); + let mut proofs: HashMap<[u8; HASH_SIZE], Vec> = HashMap::new(); if root.is_null() { return Ok(Proof(proofs)); } @@ -1864,7 +1865,7 @@ impl Merkle { for node in nodes { let node = self.get_node(node)?; let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); - let hash: [u8; 32] = sha3::Keccak256::digest(rlp).into(); + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(rlp).into(); proofs.insert(hash, rlp.to_vec()); } Ok(Proof(proofs)) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 4608794e0e49..037ee591c0fa 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -332,22 +332,101 @@ fn test_range_proof() -> Result<(), ProofError> { let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0)?; + let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); let mut keys = Vec::new(); let mut vals = Vec::new(); - for i in start..=end { + for i in start..end { keys.push(&items[i].0); vals.push(&items[i].1); } - merkle.verify_range_proof(&proof, &items[start].0, &items[end].0, keys, vals)?; + merkle.verify_range_proof(&proof, &items[start].0, &items[end - 1].0, keys, vals)?; } Ok(()) } +#[test] +// Tests a few cases which the proof is wrong. +// The prover is expected to detect the error. +fn test_bad_range_proof() -> Result<(), ProofError> { + let set = generate_random_data(4096); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + for _ in 0..10 { + let start = rand::thread_rng().gen_range(0..items.len()); + let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + let mut proof = merkle.prove(items[start].0)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(items[end - 1].0)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let mut keys: Vec<[u8; 32]> = Vec::new(); + let mut vals: Vec<[u8; 20]> = Vec::new(); + for i in start..end { + keys.push(items[i].0.clone()); + vals.push(items[i].1.clone()); + } + + let test_case: u32 = rand::thread_rng().gen_range(0..6); + let index = rand::thread_rng().gen_range(0..end - start); + match test_case { + 0 => { + // Modified key + keys[index] = rand::thread_rng().gen::<[u8; 32]>(); // In theory it can't be same + } + 1 => { + // Modified val + vals[index] = rand::thread_rng().gen::<[u8; 20]>(); // In theory it can't be same + } + 2 => { + // Gapped entry slice + if index == 0 || index == end - start - 1 { + continue; + } + keys.remove(index); + vals.remove(index); + } + 3 => { + // Out of order + let index_1 = rand::thread_rng().gen_range(0..end - start); + let index_2 = rand::thread_rng().gen_range(0..end - start); + if index_1 == index_2 { + continue; + } + keys.swap(index_1, index_2); + vals.swap(index_1, index_2); + } + 4 => { + // Set random key to empty, do nothing + keys[index] = [0; 32]; + } + 5 => { + // Set random value to nil + vals[index] = [0; 20]; + } + _ => unreachable!(), + } + assert!(merkle + .verify_range_proof( + &proof, + items[start].0.clone(), + items[end - 1].0.clone(), + keys, + vals + ) + .is_err()); + } + + Ok(()) +} + #[test] // Tests normal range proof with two non-existent proofs. // The test cases are generated randomly. @@ -890,6 +969,43 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { Ok(()) } +#[test] +// Tests a malicious proof, where the proof is more or less the +// whole trie. This is to match correpsonding test in geth. +fn test_bloadted_range_proof() -> Result<(), ProofError> { + // Use a small trie + let mut items = Vec::new(); + for i in 0..100 as u32 { + let mut key: [u8; 32] = [0; 32]; + let mut data: [u8; 20] = [0; 20]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + data[index] = *d; + } + items.push((key, data)); + } + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + // In the 'malicious' case, we add proofs for every single item + // (but only one key/value pair used as leaf) + let mut proof = Proof(HashMap::new()); + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for (i, item) in items.iter().enumerate() { + let cur_proof = merkle.prove(&item.0)?; + assert!(!cur_proof.0.is_empty()); + proof.concat_proofs(cur_proof); + if i == 50 { + keys.push(item.0); + vals.push(item.1); + } + } + + merkle.verify_range_proof(&proof, keys[0], keys[keys.len() - 1], keys, vals)?; + + Ok(()) +} + fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); for i in 0..100 as u32 { From 623ab5a8139dfc9a4280cbb724080f4df57a6e8d Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 23 Mar 2023 20:22:56 +0000 Subject: [PATCH 0087/1053] [range proof] fix test_range_proof_with_non_existent_proof flaky test --- firewood/src/proof.rs | 29 ++++++++++++++++++++++++++++- firewood/tests/merkle.rs | 16 +++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 55cb60c6b474..6d037140b26c 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -996,10 +996,37 @@ fn unset_node_ref>( remove_left, ); } - NodeType::Leaf(_) => { + NodeType::Leaf(n) => { let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; + let cur_key = n.path().clone().into_inner(); + // Similar to branch node, we need to compare the path to see if the node + // needs to be unset. + if chunks[index..].len() < cur_key.len() + || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() + { + if remove_left { + if compare(&cur_key, &chunks[index..]).is_lt() { + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[chunks[index - 1] as usize] = None; + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + } else if compare(&cur_key, &chunks[index..]).is_gt() { + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[chunks[index - 1] as usize] = None; + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + return Ok(()); + } p_ref .write(|p| match p.inner_mut() { NodeType::Extension(n) => { diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 037ee591c0fa..c856d76110f3 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -430,7 +430,6 @@ fn test_bad_range_proof() -> Result<(), ProofError> { #[test] // Tests normal range proof with two non-existent proofs. // The test cases are generated randomly. -// TODO: flaky test fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -476,6 +475,21 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { merkle.verify_range_proof(&proof, first, last, keys, vals)?; } + // Special case, two edge proofs for two edge key. + let first: [u8; 32] = [0; 32]; + let last: [u8; 32] = [255; 32]; + let mut proof = merkle.prove(first)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(last)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let item_iter = items.clone().into_iter(); + let keys = item_iter.clone().map(|item| item.0).collect(); + let vals = item_iter.map(|item| item.1).collect(); + + merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; + Ok(()) } From d4131ff5e788dec5c312ef74501ee6958a4ce320 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Fri, 24 Mar 2023 05:27:58 +0000 Subject: [PATCH 0088/1053] [proof] trivial refactor to address review comment --- firewood/src/proof.rs | 43 +++++++++++++++------------------------- firewood/tests/merkle.rs | 5 +---- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 6d037140b26c..edd5669895e1 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -737,17 +737,17 @@ fn unset_internal>( index += cur_key.len(); } NodeType::Leaf(n) => { - let cur_key = n.path().clone().into_inner(); + let cur_key = n.path(); if left_chunks.len() - index < cur_key.len() { - fork_left = compare(&left_chunks[index..], &cur_key) + fork_left = compare(&left_chunks[index..], cur_key) } else { - fork_left = compare(&left_chunks[index..index + cur_key.len()], &cur_key) + fork_left = compare(&left_chunks[index..index + cur_key.len()], cur_key) } if right_chunks.len() - index < cur_key.len() { - fork_right = compare(&right_chunks[index..], &cur_key) + fork_right = compare(&right_chunks[index..], cur_key) } else { - fork_right = compare(&right_chunks[index..index + cur_key.len()], &cur_key) + fork_right = compare(&right_chunks[index..index + cur_key.len()], cur_key) } break; } @@ -943,17 +943,15 @@ fn unset_node_ref>( return unset_node_ref(merkle, p, node, key, index + 1, remove_left); } NodeType::Extension(n) => { - let cur_key = n.path().clone().into_inner(); + let cur_key = n.path(); let node = n.chd(); - if chunks[index..].len() < cur_key.len() - || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() - { + if !(chunks[index..]).starts_with(cur_key) { let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; // Find the fork point, it's an non-existent branch. if remove_left { - if compare(&cur_key, &chunks[index..]).is_lt() { + if compare(cur_key, &chunks[index..]).is_lt() { // The key of fork shortnode is less than the path // (it belongs to the range), unset the entire // branch. The parent must be a fullnode. @@ -970,7 +968,7 @@ fn unset_node_ref>( // path(it doesn't belong to the range), keep // it with the cached hash available. //} - } else if compare(&cur_key, &chunks[index..]).is_gt() { + } else if compare(cur_key, &chunks[index..]).is_gt() { // The key of fork shortnode is greater than the // path(it belongs to the range), unset the entrie // branch. The parent must be a fullnode. Otherwise the @@ -1000,30 +998,21 @@ fn unset_node_ref>( let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; - let cur_key = n.path().clone().into_inner(); + let cur_key = n.path(); // Similar to branch node, we need to compare the path to see if the node // needs to be unset. - if chunks[index..].len() < cur_key.len() - || compare(&cur_key, &chunks[index..index + cur_key.len()]).is_ne() - { - if remove_left { - if compare(&cur_key, &chunks[index..]).is_lt() { + if !(chunks[index..]).starts_with(cur_key) { + match (cur_key.cmp(&chunks[index..]), remove_left) { + (Ordering::Greater, false) | (Ordering::Less, true) => { p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_mut()[chunks[index - 1] as usize] = None; pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; }) - .unwrap(); + .expect("node write failure"); } - } else if compare(&cur_key, &chunks[index..]).is_gt() { - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; - }) - .unwrap(); + _ => (), } return Ok(()); } @@ -1039,7 +1028,7 @@ fn unset_node_ref>( } _ => {} }) - .unwrap(); + .expect("node write failure"); } } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index c856d76110f3..795d4871812d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -484,10 +484,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); - let item_iter = items.clone().into_iter(); - let keys = item_iter.clone().map(|item| item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - + let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; Ok(()) From ca1bfbf231a56af6c7d4b2e70437212ddf985818 Mon Sep 17 00:00:00 2001 From: exdx Date: Sat, 25 Mar 2023 18:05:04 -0400 Subject: [PATCH 0089/1053] fix: Update StoreError to use thiserror (#156) Signed-off-by: Dan Sover --- firewood/src/db.rs | 3 +- firewood/src/storage.rs | 75 +++++++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4a1f032f393a..e99e9badc368 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -749,7 +749,8 @@ impl DB { return None; } if rlen < nback { - let ashes = inner.disk_requester.collect_ash(nback); + // TODO: Remove unwrap + let ashes = inner.disk_requester.collect_ash(nback).ok().unwrap(); for mut ash in ashes.into_iter().skip(rlen) { for (_, a) in ash.0.iter_mut() { a.old.reverse() diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index b30f80488760..59263fe66771 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -15,6 +15,9 @@ use growthring::{ }; use nix::fcntl::{flock, FlockArg}; use shale::{MemStore, MemView, SpaceID}; +use thiserror::Error; +use tokio::sync::mpsc::error::SendError; +use tokio::sync::oneshot::error::RecvError; use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; use typed_builder::TypedBuilder; @@ -24,6 +27,20 @@ pub(crate) const PAGE_SIZE_NBIT: u64 = 12; pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; +#[derive(Debug, Error)] +pub enum StoreError { + #[error("system error: `{0}`")] + System(#[from] nix::Error), + #[error("init error: `{0}`")] + Init(String), + // TODO: more error report from the DiskBuffer + //WriterError, + #[error("error sending data: `{0}`")] + Send(#[from] SendError), + #[error("error receiving data: `{0}")] + Receive(#[from] RecvError), +} + pub trait MemStoreR: Debug { fn get_slice(&self, offset: u64, length: u64) -> Option>; fn id(&self) -> SpaceID; @@ -614,7 +631,7 @@ pub struct CachedSpace { } impl CachedSpace { - pub fn new(cfg: &StoreConfig) -> Result { + pub fn new(cfg: &StoreConfig) -> Result> { let space_id = cfg.space_id; let files = Arc::new(FilePool::new(cfg)?); Ok(Self { @@ -645,7 +662,11 @@ impl CachedSpace { } impl CachedSpaceInner { - fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result, StoreError> { + fn fetch_page( + &mut self, + space_id: SpaceID, + pid: u64, + ) -> Result, StoreError> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { return Ok(Box::new(*p)); } @@ -663,7 +684,11 @@ impl CachedSpaceInner { Ok(Box::new(page)) } - fn pin_page(&mut self, space_id: SpaceID, pid: u64) -> Result<&'static mut [u8], StoreError> { + fn pin_page( + &mut self, + space_id: SpaceID, + pid: u64, + ) -> Result<&'static mut [u8], StoreError> { let base = match self.pinned_pages.get_mut(&pid) { Some(mut e) => { e.0 += 1; @@ -777,7 +802,7 @@ pub struct FilePool { } impl FilePool { - fn new(cfg: &StoreConfig) -> Result { + fn new(cfg: &StoreConfig) -> Result> { let rootfd = cfg.rootfd; let file_nbit = cfg.file_nbit; let s = Self { @@ -789,12 +814,12 @@ impl FilePool { }; let f0 = s.get_file(0)?; if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() { - return Err(StoreError::InitError("the store is busy".into())); + return Err(StoreError::Init("the store is busy".into())); } Ok(s) } - fn get_file(&self, fid: u64) -> Result, StoreError> { + fn get_file(&self, fid: u64) -> Result, StoreError> { let mut files = self.files.lock(); let file_size = 1 << self.file_nbit; Ok(match files.get(&fid) { @@ -1212,16 +1237,16 @@ impl DiskBufferRequester { let (resp_tx, resp_rx) = oneshot::channel(); self.sender .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) - .ok() - .unwrap(); + .map_err(StoreError::Send) + .ok(); resp_rx.blocking_recv().unwrap() } pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { self.sender .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch)) - .ok() - .unwrap() + .map_err(StoreError::Send) + .ok(); } pub fn shutdown(&self) { @@ -1231,17 +1256,17 @@ impl DiskBufferRequester { pub fn init_wal(&self, waldir: &str, rootfd: Fd) { self.sender .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string())) - .ok() - .unwrap() + .map_err(StoreError::Send) + .ok(); } - pub fn collect_ash(&self, nrecords: usize) -> Vec { + pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { let (resp_tx, resp_rx) = oneshot::channel(); self.sender .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx)) - .ok() - .unwrap(); - resp_rx.blocking_recv().unwrap() + .map_err(StoreError::Send) + .ok(); + resp_rx.blocking_recv().map_err(StoreError::Receive) } pub fn reg_cached_space(&self, space: &CachedSpace) { @@ -1249,21 +1274,7 @@ impl DiskBufferRequester { inner.disk_buffer = self.clone(); self.sender .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone())) - .ok() - .unwrap() - } -} - -#[derive(Debug, PartialEq)] -pub enum StoreError { - System(nix::Error), - InitError(String), - // TODO: more error report from the DiskBuffer - //WriterError, -} - -impl From for StoreError { - fn from(e: nix::Error) -> Self { - StoreError::System(e) + .map_err(StoreError::Send) + .ok(); } } From ae2f8405725bd81017b2fc5463290b0b061ae9c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Mar 2023 10:12:11 -0400 Subject: [PATCH 0090/1053] build(deps): update serial_test requirement from 1.0.0 to 2.0.0 (#173) Updates the requirements on [serial_test](https://github.com/palfrey/serial_test) to permit the latest version. - [Release notes](https://github.com/palfrey/serial_test/releases) - [Commits](https://github.com/palfrey/serial_test/compare/v1.0.0...v2.0.0) --- updated-dependencies: - dependency-name: serial_test dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood-connection/Cargo.toml | 2 +- firewood/Cargo.toml | 2 +- fwdctl/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood-connection/Cargo.toml b/firewood-connection/Cargo.toml index 6889977e77bf..ef3dece3fc06 100644 --- a/firewood-connection/Cargo.toml +++ b/firewood-connection/Cargo.toml @@ -13,4 +13,4 @@ log = "0.4.17" env_logger = "0.10.0" [dev-dependencies] -serial_test = "1.0.0" +serial_test = "2.0.0" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d13ff3313e3c..81dbd657f954 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -39,5 +39,5 @@ rand = "0.8.5" triehash = "0.8.4" assert_cmd = "2.0.7" predicates = "3.0.1" -serial_test = "1.0.0" +serial_test = "2.0.0" clap = { version = "4.0.29" } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 30b8216399c9..fdaeb2e2550f 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -13,4 +13,4 @@ log = "0.4.17" [dev-dependencies] assert_cmd = "2.0.7" predicates = "3.0.1" -serial_test = "1.0.0" +serial_test = "2.0.0" From 8c933859bb859de9765d02a2443f63d6adfcabc6 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 30 Mar 2023 05:10:52 +0000 Subject: [PATCH 0091/1053] [proof] add more test coverage for single key proof verification --- firewood/tests/merkle.rs | 73 ++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 795d4871812d..d1ececc6c038 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -217,9 +217,30 @@ fn test_one_element_proof() -> Result<(), DataStoreError> { let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); + assert!(proof.0.len() == 1); - let verify_proof = merkle.verify_proof(key, &proof)?; - assert!(verify_proof.is_some()); + let val = merkle.verify_proof(key, &proof)?; + assert!(val.is_some()); + assert!(compare(val.unwrap().as_ref(), "v".as_ref()).is_eq()); + + Ok(()) +} + +#[test] +fn test_proof() -> Result<(), DataStoreError> { + let set = generate_random_data(500); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); + + for (i, key) in keys.iter().enumerate() { + let proof = merkle.prove(key)?; + assert!(!proof.0.is_empty()); + let val = merkle.verify_proof(key, &proof)?; + assert!(val.is_some()); + assert!(compare(val.unwrap().as_ref(), vals[i].as_ref()).is_eq()); + } Ok(()) } @@ -269,38 +290,40 @@ fn test_proof_end_with_branch() -> Result<(), DataStoreError> { } #[test] -#[allow(unused_must_use)] -fn test_bad_proof() { - let items = vec![ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000); - let key = "ddd"; +fn test_bad_proof() -> Result<(), DataStoreError> { + let set = generate_random_data(800); + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + let (keys, _): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); + + for (_, key) in keys.iter().enumerate() { + let mut proof = merkle.prove(key)?; + assert!(!proof.0.is_empty()); - let proof = merkle.as_ref().unwrap().prove(key); - assert!(!proof.as_ref().unwrap().0.is_empty()); + // Delete an entry from the generated proofs. + let len = proof.0.len(); + let new_proof = Proof(proof.0.drain().take(len - 1).collect()); + assert!(merkle.verify_proof(key, &new_proof).is_err()); + } - // Delete an entry from the generated proofs. - let new_proof = Proof(proof.unwrap().0.drain().take(1).collect()); - merkle.unwrap().verify_proof(key, &new_proof).is_err(); + Ok(()) } #[test] +// Tests that missing keys can also be proven. The test explicitly uses a single +// entry trie and checks for missing keys both before and after the single entry. fn test_missing_key_proof() -> Result<(), DataStoreError> { let items = vec![("k", "v")]; let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - let key = "x"; - - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); + for key in vec!["a", "j", "l", "z"] { + let proof = merkle.prove(key)?; + assert!(!proof.0.is_empty()); + assert!(proof.0.len() == 1); - let verify_proof = merkle.verify_proof(key, &proof)?; - assert!(verify_proof.is_none()); + let val = merkle.verify_proof(key, &proof)?; + assert!(val.is_none()); + } Ok(()) } From 32e76e6ac7641d449b0d4dfee214def055d97832 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Thu, 30 Mar 2023 17:22:31 +0000 Subject: [PATCH 0092/1053] [proof] Fix overflow issues with randomization in test --- firewood/tests/merkle.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index d1ececc6c038..d274ff7ecefe 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -353,6 +353,10 @@ fn test_range_proof() -> Result<(), ProofError> { let start = rand::thread_rng().gen_range(0..items.len()); let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + if end <= start { + continue; + } + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; @@ -384,6 +388,10 @@ fn test_bad_range_proof() -> Result<(), ProofError> { let start = rand::thread_rng().gen_range(0..items.len()); let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + if end <= start { + continue; + } + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; @@ -463,6 +471,10 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { let start = rand::thread_rng().gen_range(0..items.len()); let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + if end <= start { + continue; + } + // Short circuit if the decreased key is same with the previous key let first = decrease_key(&items[start].0); if start != 0 && compare(first.as_ref(), items[start - 1].0.as_ref()).is_eq() { From d1146d3ed2f21b4cf963a2ccdc9dbe5afc535cb9 Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 3 Apr 2023 11:14:42 -0400 Subject: [PATCH 0093/1053] db: Refactor kv_remove to be more ergonomic (#168) * db: Refactor kv_remove to not take in a vector that is then mutated. This change makes kv_remove more ergonomic and easier to use for callers. Signed-off-by: Dan Sover * cli: Fix up delete command This commit changes the behavior to have the delete command print the value associated with the removed key instead of the key itself. Printing the key was incorrect behavior, since the key is supplied to by user ahead of time. Signed-off-by: Dan Sover --------- Signed-off-by: Dan Sover --- firewood/src/db.rs | 10 +++------- fwdctl/src/delete.rs | 21 ++++++++------------- fwdctl/tests/cli.rs | 4 ++-- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e99e9badc368..8ef1b8205a5e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -872,16 +872,12 @@ impl<'a> WriteBatch<'a> { /// Remove an item from the generic key-value storage. `val` will be set to the value that is /// removed from the storage if it exists. - pub fn kv_remove>( - mut self, - key: K, - val: &mut Option>, - ) -> Result { + pub fn kv_remove>(mut self, key: K) -> Result<(Self, Option>), DBError> { let (header, merkle, _) = self.m.latest.borrow_split(); - *val = merkle + let old_value = merkle .remove(key, header.kv_root) .map_err(DBError::Merkle)?; - Ok(self) + Ok((self, old_value)) } fn change_account( diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 29b65a08f466..8ef8d4bb1869 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; @@ -31,18 +31,13 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - let mut account: Option> = None; - let x = match db + if let Ok((wb, _)) = db .new_writebatch() - .kv_remove(opts.key.clone(), &mut account) + .kv_remove(opts.key.clone()) + .map_err(Error::msg) { - Ok(wb) => { - wb.commit(); - println!("{}", opts.key); - - Ok(()) - } - Err(_) => Err(anyhow!("error deleting key")), - }; - x + wb.commit() + } + println!("key {} deleted successfully", opts.key); + Ok(()) } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 6055eb03566a..36e59a1f4ed0 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -123,14 +123,14 @@ fn fwdctl_delete_successful() -> Result<()> { .success() .stdout(predicate::str::contains("year")); - // Delete key + // Delete key -- prints raw data of deleted value Command::cargo_bin(PRG)? .arg("delete") .args(["year"]) .args(["--db", FIREWOOD_TEST_DB_NAME]) .assert() .success() - .stdout(predicate::str::contains("year")); + .stdout(predicate::str::contains("key year deleted successfully")); fwdctl_delete_db().map_err(|e| anyhow!(e))?; From 526c2a9999bcc1381aa55c8443fb07fd5114c86f Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 3 Apr 2023 14:26:07 -0400 Subject: [PATCH 0094/1053] Update CODEOWNERS (#178) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index d681448a04fe..7523ae731747 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # CODEOWNERS -* @Determinant @exdx @haohao-os @gyuho @hexfusion @rkuris +* @Determinant @exdx @xinifinity @gyuho @hexfusion @rkuris From c023ece0aa4724c497ebfc0ddfacca379c2aa29d Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 3 Apr 2023 15:15:18 -0400 Subject: [PATCH 0095/1053] db: Add e2e test (#167) Signed-off-by: Dan Sover --- firewood/src/merkle.rs | 2 +- firewood/tests/db.rs | 75 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 01878f680245..5039a66a7cbc 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -40,7 +40,7 @@ impl fmt::Display for MerkleError { impl Error for MerkleError {} -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Hash(pub [u8; HASH_SIZE]); impl Hash { diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index d40e6597a9b9..59a528fe1aff 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,5 +1,5 @@ use firewood::db::{DBConfig, WALConfig, DB}; -use std::collections::VecDeque; +use std::{collections::VecDeque, fs::remove_dir_all, path::Path}; macro_rules! kv_dump { ($e: ident) => {{ @@ -86,3 +86,76 @@ fn test_revisions() { println!("i = {i}"); } } + +#[test] +fn create_db_issue_proof() { + let cfg = DBConfig::builder() + .meta_ncached_pages(1024) + .meta_ncached_files(128) + .payload_ncached_pages(1024) + .payload_ncached_files(128) + .payload_file_nbit(16) + .payload_regn_nbit(16) + .wal( + WALConfig::builder() + .file_nbit(15) + .block_nbit(8) + .max_revisions(10) + .build(), + ); + + let db = DB::new("test_db_proof", &cfg.truncate(true).build()).unwrap(); + + let mut wb = db.new_writebatch(); + + let items = vec![ + ("d", "verb"), + ("do", "verb"), + ("doe", "reindeer"), + ("e", "coin"), + ]; + + for (k, v) in items { + wb = wb.kv_insert(k.as_bytes(), v.as_bytes().to_vec()).unwrap(); + } + wb.commit(); + + // Add second commit due to API restrictions + let mut wb = db.new_writebatch(); + for (k, v) in Vec::from([("x", "two")]).iter() { + wb = wb + .kv_insert(k.to_string().as_bytes(), v.as_bytes().to_vec()) + .unwrap(); + } + wb.commit(); + + let rev = db.get_revision(1, None).unwrap(); + let key = "doe".as_bytes(); + let root_hash = rev.kv_root_hash(); + + match rev.prove(key) { + Ok(proof) => { + let verification = proof.verify_proof(key, *root_hash.unwrap()).unwrap(); + assert!(verification.is_some()); + } + Err(e) => { + panic!("Error: {}", e); + } + } + + let missing_key = "dog".as_bytes(); + // The proof for the missing key will return the path to the missing key + if let Err(e) = rev.prove(missing_key) { + println!("Error: {}", e); + // TODO do type assertion on error + } + + fwdctl_delete_db("test_db_proof"); +} + +// Removes the firewood database on disk +fn fwdctl_delete_db>(path: P) { + if let Err(e) = remove_dir_all(path) { + eprintln!("failed to delete testing dir: {e}"); + } +} From 39c541cc82407069abda86b29ee3d6442521aca6 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Mon, 3 Apr 2023 19:01:11 +0000 Subject: [PATCH 0096/1053] Demostrate how to use range proof along with revision. --- firewood/examples/rev.rs | 45 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 010ed1c2f186..85071ee4b08c 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -5,14 +5,7 @@ fn main() { let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); { let db = DB::new("rev_db", &cfg.clone().truncate(true).build()).unwrap(); - let items = vec![ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]; + let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; for (k, v) in items.iter() { db.new_writebatch() .kv_insert(k, v.as_bytes().to_vec()) @@ -34,13 +27,47 @@ fn main() { let db = DB::new("rev_db", &cfg.truncate(false).build()).unwrap(); { let rev = db.get_revision(1, None).unwrap(); - print!("{}", hex::encode(*rev.kv_root_hash().unwrap())); + println!("{}", hex::encode(*rev.kv_root_hash().unwrap())); rev.kv_dump(&mut std::io::stdout()).unwrap(); + + let mut items_rev_1 = vec![("dof", "verb"), ("doe", "reindeer")]; + items_rev_1.sort(); + let (keys, vals) = items_rev_1.clone().into_iter().unzip(); + + let mut proof = rev.prove(items_rev_1[0].0).unwrap(); + let end_proof = rev.prove(items_rev_1[items_rev_1.len() - 1].0).unwrap(); + proof.concat_proofs(end_proof); + + rev.verify_range_proof( + proof, + items_rev_1[0].0, + items_rev_1[items_rev_1.len() - 1].0, + keys, + vals, + ) + .unwrap(); } { let rev = db.get_revision(2, None).unwrap(); print!("{}", hex::encode(*rev.kv_root_hash().unwrap())); rev.kv_dump(&mut std::io::stdout()).unwrap(); + + let mut items_rev_2 = vec![("dof", "verb")]; + items_rev_2.sort(); + let (keys, vals) = items_rev_2.clone().into_iter().unzip(); + + let mut proof = rev.prove(items_rev_2[0].0).unwrap(); + let end_proof = rev.prove(items_rev_2[items_rev_2.len() - 1].0).unwrap(); + proof.concat_proofs(end_proof); + + rev.verify_range_proof( + proof, + items_rev_2[0].0, + items_rev_2[items_rev_2.len() - 1].0, + keys, + vals, + ) + .unwrap(); } } } From a9a669fc44ad8a6ba402fc7c8a6bc67b745a98d1 Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 4 Apr 2023 11:46:37 -0400 Subject: [PATCH 0097/1053] docs: Document get_revisions function with additional information. (#177) --- firewood/src/db.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8ef1b8205a5e..67d4d31f24f0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -741,6 +741,13 @@ impl DB { } /// Get a handle that grants the access to some historical state of the entire DB. + /// + /// Note: There must be at least two committed batches in order for this function to return an older 'Revision', + /// other than the latest state. + /// + /// The latest revision (nback) starts from 1, which is one behind the current state. + /// If nback equals 0, or is above the configured maximum number of revisions, this function returns None. + /// It also returns None in the case where the nback is larger than the number of revisions available. pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { let mut inner = self.inner.lock(); From 1b86c2166a4160f0485adce1511781080e294d5e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 23 Mar 2023 00:16:06 +0000 Subject: [PATCH 0098/1053] Replace firewood-connection with client/server implementation This diff adds support for an async DB trait, shown in api.rs. The general idea is to create a channel to the thread running the DB, rather than passing the DB object around. For batches, each batch gets an ID, but the batch itself stays server side. The following API calls are fully implemented: * set_code * set_nonce * set_state * create_account * no_root_hash * root_hash Also fixed up the shutdown code, by implementing Drop on Connection. --- Cargo.toml | 1 - firewood-connection/Cargo.toml | 16 -- firewood-connection/README.md | 28 -- firewood-connection/src/lib.rs | 83 ------ firewood-connection/tests/manager.rs | 136 --------- firewood/Cargo.toml | 1 + firewood/src/api.rs | 107 +++++++ firewood/src/lib.rs | 3 + firewood/src/merkle.rs | 2 +- firewood/src/sender.rs | 70 +++++ firewood/src/service/client.rs | 401 +++++++++++++++++++++++++++ firewood/src/service/mod.rs | 86 ++++++ firewood/src/service/server.rs | 185 ++++++++++++ firewood/src/storage.rs | 1 + firewood/tests/merkle.rs | 56 ++-- 15 files changed, 883 insertions(+), 293 deletions(-) delete mode 100644 firewood-connection/Cargo.toml delete mode 100644 firewood-connection/README.md delete mode 100644 firewood-connection/src/lib.rs delete mode 100644 firewood-connection/tests/manager.rs create mode 100644 firewood/src/api.rs create mode 100644 firewood/src/sender.rs create mode 100644 firewood/src/service/client.rs create mode 100644 firewood/src/service/mod.rs create mode 100644 firewood/src/service/server.rs diff --git a/Cargo.toml b/Cargo.toml index 4def532994f0..5e168130918e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "shale", "firewood", "fwdctl", - "firewood-connection" ] [profile.release] diff --git a/firewood-connection/Cargo.toml b/firewood-connection/Cargo.toml deleted file mode 100644 index ef3dece3fc06..000000000000 --- a/firewood-connection/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "firewood-connection" -version = "0.0.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tokio = { version = "1.21.1", features = ["rt-multi-thread", "sync", "macros"] } -crossbeam-channel = "0.5.7" -firewood = { version = "0.0.1", path = "../firewood" } -log = "0.4.17" -env_logger = "0.10.0" - -[dev-dependencies] -serial_test = "2.0.0" diff --git a/firewood-connection/README.md b/firewood-connection/README.md deleted file mode 100644 index 9569d679069f..000000000000 --- a/firewood-connection/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# firewood-connection - -The firewood-connection library provides primitives to run firewood inside of an -async environment, for example a custom VM. firewood-connection provides a -series of channels that can be used to send `Actions` or closures from the async -environment to firewood in order to be executed. Firewood runs asynchronously on -its own thread, and messages are passed between the application and the firewood -library. - -This solution is required because firewood currently depends on its own async -runtime in order to run its storage layer. Since nesting async runtimes is not -possible, firewood needs to run on its own dedicated thread. Ideally firewood -would support async directly, and this crate would not be necessary, and we are -working to add this support directly in the near future. - -## Usage - -Using the firewood-connection is straightforward. A call to `initialize()` -sets up the communication channel between async environments and returns a -`Manager`. Once a `Manager` is established, `Manger.call()` can pass -functions along the channel that will be executed on the database. Messages will -also be returned along the channel indicating the result of the database -operation. - -## Example - -See the [transfervm](https://github.com/ava-labs/transfervm-rs) for -an example of a custom VM that integrates with firewood-connection. diff --git a/firewood-connection/src/lib.rs b/firewood-connection/src/lib.rs deleted file mode 100644 index e7595c0a555d..000000000000 --- a/firewood-connection/src/lib.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{path::Path, thread}; - -use crossbeam_channel::Sender; -use firewood::db::{DBConfig, DBError, DB}; -use tokio::sync::oneshot; - -type Action = Box; - -/// Firewood connection manager communicates with the firewood database in a -/// separate thread through channels. -#[derive(Clone, Debug)] -pub struct Manager { - // Channel used by the caller to communicate with firewood. - action_tx: Sender, -} - -impl Manager { - pub async fn new>(db_path: P, cfg: DBConfig) -> Result { - let owned_path = db_path.as_ref().to_path_buf(); - initialize(move || DB::new(&owned_path.to_string_lossy(), &cfg)).await - } - - /// Calls a db action over channel. - // TODO: Remove static lifetime bounds - pub async fn call(&self, func: F) -> T - where - F: FnOnce(&DB) -> T + Send + 'static, - T: Send + 'static, - { - // communicate result of action. - let (result_tx, result_rx) = oneshot::channel(); - - // sends an action function to the db via channel and returns the response. - self.action_tx - .send(Box::new(move |db| { - let resp = func(db); - let _ = result_tx.send(resp); - })) - .expect("action tx channel failed"); - - result_rx.await.expect("action rx channel failed") - } -} - -/// Creates a new work thread which listens for actions over channel. -async fn initialize(new_db: I) -> Result -where - I: FnOnce() -> Result + Send + 'static, -{ - // provides communication from the caller to the db in the work thread. - let (action_tx, action_rx) = crossbeam_channel::unbounded::(); - // communicates the result of the db initialize action. - let (init_tx, init_rx) = oneshot::channel(); - - log::debug!("work thread spawned"); - thread::spawn(move || { - let db = match new_db() { - Ok(db) => db, - Err(e) => { - // return error - let _ = init_tx.send(Err(e)); - return; - } - }; - - if init_tx.send(Ok(())).is_err() { - log::error!("failed to send db action result: ok"); - return; - } - - // listen for actions - while let Ok(func) = action_rx.recv() { - log::debug!("action received"); - func(&db); - log::debug!("action complete"); - } - }); - - init_rx - .await - .expect("result rx channel failed") - .map(|_| Manager { action_tx }) -} diff --git a/firewood-connection/tests/manager.rs b/firewood-connection/tests/manager.rs deleted file mode 100644 index 1e7e6bc26d99..000000000000 --- a/firewood-connection/tests/manager.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::{sync::Arc, vec}; - -use firewood::db::DBConfig; -use firewood_connection::Manager; -use serial_test::serial; - -#[tokio::test] -#[serial] -async fn test_manager() { - let cfg = setup_db(); - - let manager = Manager::new("/tmp/simple_db", cfg).await.unwrap(); - - manager - .call(|db| { - db.new_writebatch() - .kv_insert("foo", "bar".as_bytes().to_vec()) - .unwrap() - .commit(); - }) - .await; - - let resp = manager.call(|db| db.kv_get(b"foo").unwrap()).await; - - assert_eq!(resp, "bar".as_bytes().to_vec()); - cleanup_db() -} - -#[tokio::test] -#[serial] -async fn concurrent_reads_writes() { - let cfg = setup_db(); - - let manager = Manager::new("/tmp/simple_db", cfg).await.unwrap(); - - manager - .call(|db| { - let items = vec![ - ("d", "verb"), - ("do", "verb"), - ("doe", "reindeer"), - ("e", "coin"), - ]; - - for (k, v) in items.iter() { - db.new_writebatch() - .kv_insert(k, v.as_bytes().to_vec()) - .unwrap() - .commit(); - } - }) - .await; - - let handle = tokio::spawn(async move { - manager.call(|db| db.kv_get(b"doe").unwrap()).await; - }) - .await; - - match handle { - Ok(_) => cleanup_db(), - Err(e) => log::error!("{e}"), - } -} - -#[tokio::test(flavor = "multi_thread")] -#[serial] -async fn multi_thread_read_writes() { - let cfg = setup_db(); - - let manager = Arc::new(Manager::new("/tmp/simple_db", cfg).await.unwrap()); - - let keys = vec!["a", "b", "c", "d", "e"]; - let m1 = manager.clone(); - - tokio::spawn(async move { - for k in keys { - m1.call(move |db| { - db.new_writebatch() - .kv_insert(k, "z".as_bytes().to_vec()) - .unwrap() - .commit() - }) - .await; - } - }); - - let m2 = manager.clone(); - let keys2 = vec!["a", "b", "c", "d", "e"]; - - tokio::spawn(async move { - for k in keys2 { - m2.call(move |db| { - db.new_writebatch() - .kv_insert(k, "x".as_bytes().to_vec()) - .unwrap() - .commit() - }) - .await - } - }); - - let m3 = manager; - let keys3 = vec!["a", "b", "c", "d", "e"]; - - tokio::spawn(async move { - for k in keys3 { - m3.call(move |db| { - println!("{:?}", db.kv_get(k)); - }) - .await - } - }); -} - -#[cfg(test)] -fn setup_db() -> DBConfig { - let _ = env_logger::builder() - .filter_level(log::LevelFilter::Info) - .is_test(true) - .try_init(); - - DBConfig::builder() - .wal(firewood::db::WALConfig::builder().max_revisions(10).build()) - .truncate(true) - .build() -} - -// Removes the firewood database on disk -#[cfg(test)] -fn cleanup_db() { - use std::fs::remove_dir_all; - - if let Err(e) = remove_dir_all("/tmp/simple_db") { - eprintln!("failed to delete testing dir: {e}"); - } -} diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 81dbd657f954..602dd198e704 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -31,6 +31,7 @@ futures = "0.3.24" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.38" +async-trait = "0.1.57" [dev-dependencies] criterion = "0.4.0" diff --git a/firewood/src/api.rs b/firewood/src/api.rs new file mode 100644 index 000000000000..6be9cfcb7a4a --- /dev/null +++ b/firewood/src/api.rs @@ -0,0 +1,107 @@ +use std::io::Write; + +use primitive_types::U256; + +use crate::account::Account; +use crate::db::DBError; +use crate::merkle::{Hash, MerkleError}; +use crate::proof::Proof; + +use async_trait::async_trait; + +pub type Nonce = u64; + +#[async_trait] +pub trait DB { + async fn kv_root_hash(&self) -> Result; + async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError>; + async fn new_writebatch(&self) -> B; + async fn kv_dump(&self, writer: W) -> Result<(), DBError>; + async fn root_hash(&self) -> Result; + async fn dump(&self, writer: W) -> Result<(), DBError>; + async fn dump_account + Send + Sync>( + &self, + key: K, + writer: W, + ) -> Result<(), DBError>; + async fn get_balance + Send + Sync>(&self, key: K) -> Result; + async fn get_code + Send + Sync>(&self, key: K) -> Result, DBError>; + async fn prove + Send + Sync>(&self, key: K) -> Result; + async fn verify_range_proof + Send + Sync>( + &self, + proof: Proof, + first_key: K, + last_key: K, + keys: Vec, + values: Vec, + ); + async fn get_nonce + Send + Sync>(&self, key: K) -> Result; + async fn get_state + Send + Sync>( + &self, + key: K, + sub_key: K, + ) -> Result, DBError>; + async fn exist + Send + Sync>(&self, key: K) -> Result; +} + +#[async_trait] +pub trait WriteBatch +where + Self: Sized, +{ + async fn kv_insert + Send + Sync, V: AsRef<[u8]> + Send + Sync>( + self, + key: K, + val: V, + ) -> Result; + /// Remove an item from the generic key-value storage. `val` will be set to the value that is + /// removed from the storage if it exists. + async fn kv_remove + Send + Sync>( + self, + key: K, + ) -> Result<(Self, Option>), DBError>; + /// Set balance of the account + async fn set_balance + Send + Sync>( + self, + key: K, + balance: U256, + ) -> Result; + /// Set code of the account + async fn set_code + Send + Sync, V: AsRef<[u8]> + Send + Sync>( + self, + key: K, + code: V, + ) -> Result; + /// Set nonce of the account. + async fn set_nonce + Send + Sync>( + self, + key: K, + nonce: u64, + ) -> Result; + /// Set the state value indexed by `sub_key` in the account indexed by `key`. + async fn set_state< + K: AsRef<[u8]> + Send + Sync, + SK: AsRef<[u8]> + Send + Sync, + V: AsRef<[u8]> + Send + Sync, + >( + self, + key: K, + sub_key: SK, + val: V, + ) -> Result; + /// Create an account. + async fn create_account + Send + Sync>(self, key: K) -> Result; + /// Delete an account. + async fn delete_account + Send + Sync>( + self, + key: K, + acc: &mut Option, + ) -> Result; + /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root + /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. + async fn no_root_hash(self) -> Self; + + /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are + /// either retained on disk or lost together during a crash. + async fn commit(self); +} diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 4aa75b603d6b..17d41912a379 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -203,3 +203,6 @@ pub mod merkle; pub mod merkle_util; pub mod proof; pub(crate) mod storage; + +pub mod api; +pub mod service; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5039a66a7cbc..4f3fc690f674 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -40,7 +40,7 @@ impl fmt::Display for MerkleError { impl Error for MerkleError {} -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct Hash(pub [u8; HASH_SIZE]); impl Hash { diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs new file mode 100644 index 000000000000..4227a16046e3 --- /dev/null +++ b/firewood/src/sender.rs @@ -0,0 +1,70 @@ +use crate::api::DB; + +pub struct Sender { + +} + +impl, V: AsRef<[u8]>> DB for Sender { + fn kv_root_hash(&self) -> Result { + todo!() + } + + fn kv_get(&self, key: K) -> Option> { + todo!() + } + + fn kv_dump(&self, writer: W) -> Result<(), crate::db::DBError> { + todo!() + } + + fn root_hash(&self) -> Result { + todo!() + } + + fn dump(&self, writer: W) -> Result<(), crate::db::DBError> { + todo!() + } + + fn get_account(&self, key: K) -> Result { + todo!() + } + + fn dump_account(&self, key: K, writer: W) -> Result<(), crate::db::DBError> { + todo!() + } + + fn get_balance(&self, key: K) -> Result { + todo!() + } + + fn get_code(&self, key: K) -> Result, crate::db::DBError> { + todo!() + } + + fn prove(&self, key: K) -> Result { + todo!() + } + + fn verify_range_proof( + &self, + proof: crate::proof::Proof, + first_key: K, + last_key: K, + keys: Vec, + values: Vec, + ) { + todo!() + } + + fn get_nonce(&self, key: K) -> Result { + todo!() + } + + fn get_state(&self, key: K, sub_key: K) -> Result, crate::db::DBError> { + todo!() + } + + fn exist(&self, key: K) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs new file mode 100644 index 000000000000..e299ada639e9 --- /dev/null +++ b/firewood/src/service/client.rs @@ -0,0 +1,401 @@ +/// Client side connection structure +/// +/// A connection is used to send messages to the firewood thread. +use std::fmt::Debug; +use std::mem::take; +use std::thread::JoinHandle; +use std::{path::Path, thread}; + +use tokio::sync::{mpsc, oneshot}; + +use crate::{ + api, + db::{DBConfig, DBError}, + merkle, +}; +use async_trait::async_trait; + +use super::server::FirewoodService; +use super::{BatchHandle, BatchRequest, Request}; + +/// A `Connection` represents a connection to the thread running firewood +/// The type specified is how you want to refer to your key values; this is +/// something like `Vec` or `&[u8]` +#[derive(Debug)] +pub struct Connection { + sender: Option>, + handle: Option>, +} + +impl Drop for Connection { + fn drop(&mut self) { + drop(take(&mut self.sender)); + take(&mut self.handle) + .unwrap() + .join() + .expect("Couldn't join with the firewood thread"); + } +} + +impl Connection { + #[allow(dead_code)] + fn new>(path: P, cfg: DBConfig) -> Self { + let (sender, receiver) = mpsc::channel(1_000) + as ( + tokio::sync::mpsc::Sender, + tokio::sync::mpsc::Receiver, + ); + let owned_path = path.as_ref().to_path_buf(); + let handle = thread::Builder::new() + .name("firewood-receiver".to_owned()) + .spawn(move || FirewoodService::new(receiver, owned_path, cfg)) + .expect("thread creation failed"); + Self { + sender: Some(sender), + handle: Some(handle), + } + } +} + +#[async_trait] +impl api::WriteBatch for BatchHandle { + async fn kv_insert + Send + Sync, V: AsRef<[u8]> + Send + Sync>( + self, + key: K, + val: V, + ) -> Result { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::KvInsert { + handle: self.id, + key: key.as_ref().to_vec(), + val: val.as_ref().to_vec(), + respond_to: send, + })) + .await; + return match recv.await { + Ok(_) => Ok(self), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn kv_remove + Send + Sync>( + self, + key: K, + ) -> Result<(Self, Option>), DBError> { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::KvRemove { + handle: self.id, + key: key.as_ref().to_vec(), + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(v)) => Ok((self, v)), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn set_balance + Send + Sync>( + self, + key: K, + balance: primitive_types::U256, + ) -> Result { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::SetBalance { + handle: self.id, + key: key.as_ref().to_vec(), + balance, + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(_)) => Ok(self), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn set_code(self, key: K, code: V) -> Result + where + K: AsRef<[u8]> + Send + Sync, + V: AsRef<[u8]> + Send + Sync, + { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::SetCode { + handle: self.id, + key: key.as_ref().to_vec(), + code: code.as_ref().to_vec(), + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(_)) => Ok(self), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn set_nonce + Send + Sync>( + self, + key: K, + nonce: u64, + ) -> Result { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::SetNonce { + handle: self.id, + key: key.as_ref().to_vec(), + nonce, + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(_)) => Ok(self), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn set_state(self, key: K, sub_key: SK, state: S) -> Result + where + K: AsRef<[u8]> + Send + Sync, + SK: AsRef<[u8]> + Send + Sync, + S: AsRef<[u8]> + Send + Sync, + { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::SetState { + handle: self.id, + key: key.as_ref().to_vec(), + sub_key: sub_key.as_ref().to_vec(), + state: state.as_ref().to_vec(), + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(_)) => Ok(self), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn create_account + Send + Sync>(self, key: K) -> Result { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::CreateAccount { + handle: self.id, + key: key.as_ref().to_vec(), + respond_to: send, + })) + .await; + return match recv.await { + Ok(Ok(_)) => Ok(self), + Ok(Err(e)) => Err(e), + Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } + + async fn delete_account + Send + Sync>( + self, + _key: K, + _acc: &mut Option, + ) -> Result { + todo!() + } + + async fn no_root_hash(self) -> Self { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::NoRootHash { + handle: self.id, + respond_to: send, + })) + .await; + let _ = recv.await; + self + } + + async fn commit(self) { + let (send, recv) = oneshot::channel(); + let _ = self + .sender + .send(Request::BatchRequest(BatchRequest::Commit { + handle: self.id, + respond_to: send, + })) + .await; + return match recv.await { + Ok(_) => (), + Err(_e) => (), // Err(DBError::InvalidParams), // TODO: need a special error for comm failures + }; + } +} + +#[async_trait] +impl crate::api::DB for Connection +where + tokio::sync::mpsc::Sender: From>, +{ + async fn kv_root_hash(&self) -> Result { + let (send, recv) = oneshot::channel(); + let msg = Request::RootHash { respond_to: send }; + let _ = self.sender.as_ref().unwrap().send(msg).await; + recv.await.expect("Actor task has been killed") + } + + async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError> { + let (send, recv) = oneshot::channel(); + let _ = Request::Get { + key: key.as_ref().to_vec(), + respond_to: send, + }; + recv.await.expect("Actor task has been killed") + } + + async fn kv_dump(&self, _writer: W) -> Result<(), DBError> { + unimplemented!(); + } + + async fn new_writebatch(&self) -> BatchHandle { + let (send, recv) = oneshot::channel(); + let msg = Request::NewBatch { respond_to: send }; + self.sender + .as_ref() + .unwrap() + .send(msg) + .await + .expect("channel failed"); + let id = recv.await; + let id = id.unwrap(); + BatchHandle { + sender: self.sender.as_ref().unwrap().clone(), + id, + } + } + + async fn root_hash(&self) -> Result { + let (send, recv) = oneshot::channel(); + let msg = Request::RootHash { respond_to: send }; + self.sender + .as_ref() + .unwrap() + .send(msg) + .await + .expect("channel failed"); + recv.await.expect("channel failed") + } + + async fn dump(&self, _writer: W) -> Result<(), DBError> { + todo!() + } + + async fn dump_account + Send + Sync>( + &self, + _key: K, + _writer: W, + ) -> Result<(), DBError> { + todo!() + } + + async fn get_balance + Send + Sync>( + &self, + _key: K, + ) -> Result { + todo!() + } + + async fn get_code + Send + Sync>(&self, _key: K) -> Result, DBError> { + todo!() + } + + async fn prove + Send + Sync>( + &self, + _key: K, + ) -> Result { + todo!() + } + + async fn verify_range_proof + Send + Sync>( + &self, + _proof: crate::proof::Proof, + _first_key: K, + _last_key: K, + _keys: Vec, + _values: Vec, + ) { + todo!() + } + + async fn get_nonce + Send + Sync>( + &self, + _key: K, + ) -> Result { + todo!() + } + + async fn get_state + Send + Sync>( + &self, + _key: K, + _sub_key: K, + ) -> Result, DBError> { + todo!() + } + + async fn exist + Send + Sync>(&self, _key: K) -> Result { + todo!() + } +} + +#[cfg(test)] +mod test { + use crate::{api::WriteBatch, api::DB, db::WALConfig}; + use std::path::PathBuf; + + use super::*; + #[tokio::test] + async fn sender_api() { + let key = "key".as_bytes(); + // test using a subdirectory of CARGO_TARGET_DIR which is + // cleaned up on `cargo clean` + let tmpdb = [ + &std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".to_string()), + "sender_api_test_db", + ]; + let tmpdb = tmpdb.into_iter().collect::(); + let conn = Connection::new(tmpdb, db_config()); + let batch = conn.new_writebatch().await; + let batch = batch.kv_insert(key, "val".as_bytes()).await.unwrap(); + let batch = batch.set_code(key, "code".as_bytes()).await.unwrap(); + let batch = batch.set_nonce(key, 42).await.unwrap(); + let batch = batch + .set_state(key, "subkey".as_bytes(), "state".as_bytes()) + .await + .unwrap(); + let batch = batch.create_account(key).await.unwrap(); + let batch = batch.no_root_hash().await; + let (batch, oldvalue) = batch.kv_remove(key).await.unwrap(); + assert_eq!(oldvalue, Some("val".as_bytes().to_vec())); + batch.commit().await; + assert_ne!(conn.root_hash().await.unwrap(), merkle::Hash([0; 32])); + } + + fn db_config() -> DBConfig { + DBConfig::builder() + .wal(WALConfig::builder().max_revisions(10).build()) + .truncate(true) + .build() + } +} diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs new file mode 100644 index 000000000000..b3d210083aa7 --- /dev/null +++ b/firewood/src/service/mod.rs @@ -0,0 +1,86 @@ +use tokio::sync::{mpsc, oneshot}; + +use crate::{db::DBError, merkle}; + +mod client; +mod server; + +pub type BatchId = u32; + +#[derive(Debug)] +pub struct BatchHandle { + sender: mpsc::Sender, + id: u32, +} + +/// Client side request object +#[derive(Debug)] +pub enum Request { + NewBatch { + respond_to: oneshot::Sender, + }, + RootHash { + respond_to: oneshot::Sender>, + }, + Get { + key: Vec, + respond_to: oneshot::Sender, DBError>>, + }, + BatchRequest(BatchRequest), +} + +type OwnedKey = Vec; +type OwnedVal = Vec; + +#[derive(Debug)] +pub enum BatchRequest { + KvRemove { + handle: BatchId, + key: OwnedKey, + respond_to: oneshot::Sender>, DBError>>, + }, + KvInsert { + handle: BatchId, + key: OwnedKey, + val: OwnedKey, + respond_to: oneshot::Sender>, + }, + Commit { + handle: BatchId, + respond_to: oneshot::Sender>, + }, + SetBalance { + handle: BatchId, + key: OwnedKey, + balance: primitive_types::U256, + respond_to: oneshot::Sender>, + }, + SetCode { + handle: BatchId, + key: OwnedKey, + code: OwnedVal, + respond_to: oneshot::Sender>, + }, + SetNonce { + handle: BatchId, + key: OwnedKey, + nonce: u64, + respond_to: oneshot::Sender>, + }, + SetState { + handle: BatchId, + key: OwnedKey, + sub_key: OwnedVal, + state: OwnedVal, + respond_to: oneshot::Sender>, + }, + CreateAccount { + handle: BatchId, + key: OwnedKey, + respond_to: oneshot::Sender>, + }, + NoRootHash { + handle: BatchId, + respond_to: oneshot::Sender<()>, + }, +} diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs new file mode 100644 index 000000000000..7be4ba78ba39 --- /dev/null +++ b/firewood/src/service/server.rs @@ -0,0 +1,185 @@ +use std::{ + collections::HashMap, + path::PathBuf, + sync::atomic::{AtomicU32, Ordering}, +}; +use tokio::sync::mpsc::Receiver; + +use crate::db::{DBConfig, DBError, DB}; + +use super::{BatchId, BatchRequest, Request}; + +macro_rules! handle_req { + ($in: expr, $out: expr) => { + let _ = $in.send($out); + }; +} +macro_rules! get_batch { + ($batches: ident, $handle: ident, $out: expr) => { + match $batches.remove(&$handle) { + None => { + let _ = $out.send(Err(DBError::InvalidParams)); + continue; + } + Some(x) => x, + } + }; +} + +#[derive(Copy, Debug, Clone)] +pub struct FirewoodService {} + +impl FirewoodService { + pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DBConfig) -> Self { + let db = DB::new(&owned_path.to_string_lossy(), &cfg).unwrap(); + let mut batches = HashMap::::new(); + let lastid = AtomicU32::new(0); + loop { + let msg = match receiver.blocking_recv() { + Some(msg) => msg, + None => break, + }; + match msg { + Request::NewBatch { respond_to } => { + let id: BatchId = lastid.fetch_add(1, Ordering::Relaxed); + batches.insert(id, db.new_writebatch()); + let _ = respond_to.send(id); + } + Request::RootHash { respond_to } => { + handle_req!(respond_to, db.root_hash()); + } + Request::Get { key, respond_to } => { + handle_req!(respond_to, db.kv_get(key)); + } + Request::BatchRequest(req) => match req { + BatchRequest::Commit { handle, respond_to } => { + let batch = get_batch!(batches, handle, respond_to); + batch.commit(); + let _ = respond_to.send(Ok(())); + } + BatchRequest::KvInsert { + handle, + key, + val, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.kv_insert(key, val) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::KvRemove { + handle, + key, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.kv_remove(key) { + Ok(v) => { + batches.insert(handle, v.0); + Ok(v.1) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::SetBalance { + handle, + key, + balance, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.set_balance(key.as_ref(), balance) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::SetCode { + handle, + key, + code, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.set_code(key.as_ref(), code.as_ref()) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::SetNonce { + handle, + key, + nonce, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.set_nonce(key.as_ref(), nonce) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::SetState { + handle, + key, + sub_key, + state, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.set_state(key.as_ref(), sub_key.as_ref(), state) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::CreateAccount { + handle, + key, + respond_to, + } => { + let batch = get_batch!(batches, handle, respond_to); + let resp = match batch.create_account(key.as_ref()) { + Ok(v) => { + batches.insert(handle, v); + Ok(()) + } + Err(e) => Err(e), + }; + respond_to.send(resp).unwrap(); + } + BatchRequest::NoRootHash { handle, respond_to } => { + // TODO: there's no way to report an error back to the caller here + if let Some(batch) = batches.remove(&handle) { + batches.insert(handle, batch.no_root_hash()); + } else { + // log!("Invalid handle {handle} passed to no_root_hash"); + } + respond_to.send(()).unwrap(); + } + }, + } + } + FirewoodService {} + } +} diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index 59263fe66771..cb0c35e6bad4 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -853,6 +853,7 @@ pub struct BufferWrite { pub delta: StoreDelta, } +#[derive(Debug)] pub enum BufferCmd { InitWAL(Fd, String), WriteBatch(Vec, AshRecord), diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index d274ff7ecefe..9db4c43c625b 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -476,7 +476,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { } // Short circuit if the decreased key is same with the previous key - let first = decrease_key(&items[start].0); + let first = decrease_key(items[start].0); if start != 0 && compare(first.as_ref(), items[start - 1].0.as_ref()).is_eq() { continue; } @@ -485,7 +485,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { continue; } // Short circuit if the increased key is same with the next key - let last = increase_key(&items[end - 1].0); + let last = increase_key(items[end - 1].0); if end != items.len() && compare(last.as_ref(), items[end].0.as_ref()).is_eq() { continue; } @@ -538,7 +538,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> // Case 1 let mut start = 100; let mut end = 200; - let first = decrease_key(&items[start].0); + let first = decrease_key(items[start].0); let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); @@ -561,7 +561,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> // Case 2 start = 100; end = 200; - let last = increase_key(&items[end - 1].0); + let last = increase_key(items[end - 1].0); let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); @@ -596,7 +596,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { // One element with existent edge proof, both edge proofs // point to the SAME key. let start = 1000; - let start_proof = merkle.prove(&items[start].0)?; + let start_proof = merkle.prove(items[start].0)?; assert!(!start_proof.0.is_empty()); merkle.verify_range_proof( @@ -608,10 +608,10 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { )?; // One element with left non-existent edge proof - let first = decrease_key(&items[start].0); + let first = decrease_key(items[start].0); let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[start].0)?; + let end_proof = merkle.prove(items[start].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -624,8 +624,8 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { )?; // One element with right non-existent edge proof - let last = increase_key(&items[start].0); - let mut proof = merkle.prove(&items[start].0)?; + let last = increase_key(items[start].0); + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); @@ -662,7 +662,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { let first: [u8; 32] = [0; 32]; let mut proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&key)?; + let end_proof = merkle.prove(key)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -698,9 +698,9 @@ fn test_all_elements_proof() -> Result<(), ProofError> { let start = 0; let end = &items.len() - 1; - let mut proof = merkle.prove(&items[start].0)?; + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end].0)?; + let end_proof = merkle.prove(items[end].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -737,7 +737,7 @@ fn test_empty_range_proof() -> Result<(), ProofError> { let cases = vec![(items.len() - 1, false)]; for (_, c) in cases.iter().enumerate() { - let first = increase_key(&items[c.0].0); + let first = increase_key(items[c.0].0); let proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); @@ -763,7 +763,7 @@ fn test_empty_range_proof() -> Result<(), ProofError> { fn test_gapped_range_proof() -> Result<(), ProofError> { let mut items = Vec::new(); // Sorted entries - for i in 0..10 as u32 { + for i in 0..10_u32 { let mut key: [u8; 32] = [0; 32]; for (index, d) in i.to_be_bytes().iter().enumerate() { key[index] = *d; @@ -775,9 +775,9 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { let first = 2; let last = 8; - let mut proof = merkle.prove(&items[first].0)?; + let mut proof = merkle.prove(items[first].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[last - 1].0)?; + let end_proof = merkle.prove(items[last - 1].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -807,7 +807,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let pos = 1000; - let mut last = decrease_key(&items[pos].0); + let mut last = decrease_key(items[pos].0); let mut first = last; first = decrease_key(&first); @@ -821,7 +821,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) .is_err()); - first = increase_key(&items[pos].0); + first = increase_key(items[pos].0); last = first; last = increase_key(&last); @@ -843,7 +843,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { fn test_single_side_range_proof() -> Result<(), ProofError> { for _ in 0..10 { let mut set = HashMap::new(); - for _ in 0..4096 as u32 { + for _ in 0..4096_u32 { let key = rand::thread_rng().gen::<[u8; 32]>(); let val = rand::thread_rng().gen::<[u8; 20]>(); set.insert(key, val); @@ -876,7 +876,7 @@ fn test_single_side_range_proof() -> Result<(), ProofError> { fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { for _ in 0..10 { let mut set = HashMap::new(); - for _ in 0..4096 as u32 { + for _ in 0..4096_u32 { let key = rand::thread_rng().gen::<[u8; 32]>(); let val = rand::thread_rng().gen::<[u8; 20]>(); set.insert(key, val); @@ -916,7 +916,7 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { // Create a new entry with a slightly modified key let mid_index = items.len() / 2; - let key = increase_key(&items[mid_index - 1].0); + let key = increase_key(items[mid_index - 1].0); let empty_data: [u8; 20] = [0; 20]; items.splice( mid_index..mid_index, @@ -926,9 +926,9 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { let start = 1; let end = items.len() - 1; - let mut proof = merkle.prove(&items[start].0)?; + let mut proof = merkle.prove(items[start].0)?; assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&items[end - 1].0)?; + let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); @@ -954,7 +954,7 @@ fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { // Create a new entry with a slightly modified key let mid_index = items.len() / 2; - let key = increase_key(&items[mid_index - 1].0); + let key = increase_key(items[mid_index - 1].0); let empty_data: [u8; 20] = [0; 20]; items.splice( mid_index..mid_index, @@ -1054,7 +1054,7 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); - for i in 0..100 as u32 { + for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; let mut data: [u8; 20] = [0; 20]; for (index, d) in i.to_be_bytes().iter().enumerate() { @@ -1075,11 +1075,11 @@ fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { let val = rand::thread_rng().gen::<[u8; 20]>(); items.insert(key, val); } - return items; + items } fn increase_key(key: &[u8; 32]) -> [u8; 32] { - let mut new_key = key.clone(); + let mut new_key = *key; for i in (0..key.len()).rev() { if new_key[i] == 0xff { new_key[i] = 0x00; @@ -1092,7 +1092,7 @@ fn increase_key(key: &[u8; 32]) -> [u8; 32] { } fn decrease_key(key: &[u8; 32]) -> [u8; 32] { - let mut new_key = key.clone(); + let mut new_key = *key; for i in (0..key.len()).rev() { if new_key[i] == 0x00 { new_key[i] = 0xff; From 0a6977f330bbe3c58e12efa53454e27ee6ceb5aa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 4 Apr 2023 19:38:13 +0000 Subject: [PATCH 0099/1053] Complete revision implementation --- firewood/src/api.rs | 73 +++++++------ firewood/src/service/client.rs | 181 +++++++++++++++++++++------------ firewood/src/service/mod.rs | 46 +++++++-- firewood/src/service/server.rs | 119 +++++++++++++++------- 4 files changed, 278 insertions(+), 141 deletions(-) diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 6be9cfcb7a4a..d2d9e8f2824d 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -3,45 +3,18 @@ use std::io::Write; use primitive_types::U256; use crate::account::Account; -use crate::db::DBError; -use crate::merkle::{Hash, MerkleError}; -use crate::proof::Proof; +use crate::db::{DBError, DBRevConfig}; +use crate::merkle::Hash; +use crate::{merkle::MerkleError, proof::Proof}; use async_trait::async_trait; pub type Nonce = u64; #[async_trait] -pub trait DB { - async fn kv_root_hash(&self) -> Result; - async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError>; +pub trait DB { async fn new_writebatch(&self) -> B; - async fn kv_dump(&self, writer: W) -> Result<(), DBError>; - async fn root_hash(&self) -> Result; - async fn dump(&self, writer: W) -> Result<(), DBError>; - async fn dump_account + Send + Sync>( - &self, - key: K, - writer: W, - ) -> Result<(), DBError>; - async fn get_balance + Send + Sync>(&self, key: K) -> Result; - async fn get_code + Send + Sync>(&self, key: K) -> Result, DBError>; - async fn prove + Send + Sync>(&self, key: K) -> Result; - async fn verify_range_proof + Send + Sync>( - &self, - proof: Proof, - first_key: K, - last_key: K, - keys: Vec, - values: Vec, - ); - async fn get_nonce + Send + Sync>(&self, key: K) -> Result; - async fn get_state + Send + Sync>( - &self, - key: K, - sub_key: K, - ) -> Result, DBError>; - async fn exist + Send + Sync>(&self, key: K) -> Result; + async fn get_revision(&self, nback: usize, cfg: Option) -> Option; } #[async_trait] @@ -105,3 +78,39 @@ where /// either retained on disk or lost together during a crash. async fn commit(self); } + +#[async_trait] +pub trait Revision +where + Self: Sized, +{ + async fn kv_root_hash(&self) -> Result; + async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError>; + + async fn kv_dump(&self, writer: W) -> Result<(), DBError>; + async fn root_hash(&self) -> Result; + async fn dump(&self, writer: W) -> Result<(), DBError>; + + async fn prove + Send + Sync>(&self, key: K) -> Result; + async fn verify_range_proof + Send + Sync>( + &self, + proof: Proof, + first_key: K, + last_key: K, + keys: Vec, + values: Vec, + ); + async fn get_balance + Send + Sync>(&self, key: K) -> Result; + async fn get_code + Send + Sync>(&self, key: K) -> Result, DBError>; + async fn get_nonce + Send + Sync>(&self, key: K) -> Result; + async fn get_state + Send + Sync>( + &self, + key: K, + sub_key: K, + ) -> Result, DBError>; + async fn dump_account + Send + Sync>( + &self, + key: K, + writer: W, + ) -> Result<(), DBError>; +} diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index e299ada639e9..1bc4a4449bc2 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -8,6 +8,8 @@ use std::{path::Path, thread}; use tokio::sync::{mpsc, oneshot}; +use crate::api::Revision; +use crate::db::DBRevConfig; use crate::{ api, db::{DBConfig, DBError}, @@ -16,7 +18,7 @@ use crate::{ use async_trait::async_trait; use super::server::FirewoodService; -use super::{BatchHandle, BatchRequest, Request}; +use super::{BatchHandle, BatchRequest, Request, RevRequest, RevisionHandle}; /// A `Connection` represents a connection to the thread running firewood /// The type specified is how you want to refer to your key values; this is @@ -244,57 +246,68 @@ impl api::WriteBatch for BatchHandle { } } +impl super::RevisionHandle { + pub async fn close(self) { + let _ = self + .sender + .send(Request::RevRequest(RevRequest::Drop { handle: self.id })) + .await; + } +} + #[async_trait] -impl crate::api::DB for Connection -where - tokio::sync::mpsc::Sender: From>, -{ +impl Revision for super::RevisionHandle { async fn kv_root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); - let msg = Request::RootHash { respond_to: send }; - let _ = self.sender.as_ref().unwrap().send(msg).await; + let msg = Request::RevRequest(RevRequest::RootHash { + handle: self.id, + respond_to: send, + }); + let _ = self.sender.send(msg).await; recv.await.expect("Actor task has been killed") } async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError> { let (send, recv) = oneshot::channel(); - let _ = Request::Get { + let _ = Request::RevRequest(RevRequest::Get { + handle: self.id, key: key.as_ref().to_vec(), respond_to: send, - }; + }); recv.await.expect("Actor task has been killed") } - async fn kv_dump(&self, _writer: W) -> Result<(), DBError> { - unimplemented!(); - } - - async fn new_writebatch(&self) -> BatchHandle { + async fn prove + Send + Sync>( + &self, + key: K, + ) -> Result { let (send, recv) = oneshot::channel(); - let msg = Request::NewBatch { respond_to: send }; - self.sender - .as_ref() - .unwrap() - .send(msg) - .await - .expect("channel failed"); - let id = recv.await; - let id = id.unwrap(); - BatchHandle { - sender: self.sender.as_ref().unwrap().clone(), - id, - } + let msg = Request::RevRequest(RevRequest::Prove { + handle: self.id, + key: key.as_ref().to_vec(), + respond_to: send, + }); + self.sender.send(msg).await.expect("channel failed"); + recv.await.expect("channel failed") } + async fn verify_range_proof + Send + Sync>( + &self, + _proof: crate::proof::Proof, + _first_key: K, + _last_key: K, + _keys: Vec, + _values: Vec, + ) { + todo!() + } async fn root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); - let msg = Request::RootHash { respond_to: send }; - self.sender - .as_ref() - .unwrap() - .send(msg) - .await - .expect("channel failed"); + let msg = Request::RevRequest(RevRequest::RootHash { + handle: self.id, + respond_to: send, + }); + self.sender.send(msg).await.expect("channel failed"); recv.await.expect("channel failed") } @@ -310,6 +323,10 @@ where todo!() } + async fn kv_dump(&self, _writer: W) -> Result<(), DBError> { + unimplemented!(); + } + async fn get_balance + Send + Sync>( &self, _key: K, @@ -321,24 +338,6 @@ where todo!() } - async fn prove + Send + Sync>( - &self, - _key: K, - ) -> Result { - todo!() - } - - async fn verify_range_proof + Send + Sync>( - &self, - _proof: crate::proof::Proof, - _first_key: K, - _last_key: K, - _keys: Vec, - _values: Vec, - ) { - todo!() - } - async fn get_nonce + Send + Sync>( &self, _key: K, @@ -353,9 +352,48 @@ where ) -> Result, DBError> { todo!() } +} - async fn exist + Send + Sync>(&self, _key: K) -> Result { - todo!() +#[async_trait] +impl crate::api::DB for Connection +where + tokio::sync::mpsc::Sender: From>, +{ + async fn new_writebatch(&self) -> BatchHandle { + let (send, recv) = oneshot::channel(); + let msg = Request::NewBatch { respond_to: send }; + self.sender + .as_ref() + .unwrap() + .send(msg) + .await + .expect("channel failed"); + let id = recv.await; + let id = id.unwrap(); + BatchHandle { + sender: self.sender.as_ref().unwrap().clone(), + id, + } + } + + async fn get_revision(&self, nback: usize, cfg: Option) -> Option { + let (send, recv) = oneshot::channel(); + let msg = Request::NewRevision { + nback, + cfg, + respond_to: send, + }; + self.sender + .as_ref() + .unwrap() + .send(msg) + .await + .expect("channel failed"); + let id = recv.await.unwrap(); + id.map(|id| RevisionHandle { + sender: self.sender.as_ref().unwrap().clone(), + id, + }) } } @@ -367,7 +405,7 @@ mod test { use super::*; #[tokio::test] async fn sender_api() { - let key = "key".as_bytes(); + let key = b"key"; // test using a subdirectory of CARGO_TARGET_DIR which is // cleaned up on `cargo clean` let tmpdb = [ @@ -377,19 +415,30 @@ mod test { let tmpdb = tmpdb.into_iter().collect::(); let conn = Connection::new(tmpdb, db_config()); let batch = conn.new_writebatch().await; - let batch = batch.kv_insert(key, "val".as_bytes()).await.unwrap(); - let batch = batch.set_code(key, "code".as_bytes()).await.unwrap(); - let batch = batch.set_nonce(key, 42).await.unwrap(); - let batch = batch - .set_state(key, "subkey".as_bytes(), "state".as_bytes()) - .await - .unwrap(); - let batch = batch.create_account(key).await.unwrap(); - let batch = batch.no_root_hash().await; + let batch = batch.kv_insert(key, b"val").await.unwrap(); + { + let batch = batch.set_code(key, b"code").await.unwrap(); + let batch = batch.set_nonce(key, 42).await.unwrap(); + let batch = batch.set_state(key, b"subkey", b"state").await.unwrap(); + let batch = batch.create_account(key).await.unwrap(); + let batch = batch.no_root_hash().await; + } let (batch, oldvalue) = batch.kv_remove(key).await.unwrap(); - assert_eq!(oldvalue, Some("val".as_bytes().to_vec())); + assert_eq!(oldvalue, Some(b"val".to_vec())); batch.commit().await; - assert_ne!(conn.root_hash().await.unwrap(), merkle::Hash([0; 32])); + let batch = conn.new_writebatch().await; + let batch = batch.kv_insert(b"k2", b"val").await.unwrap(); + batch.commit().await; + + assert_ne!( + conn.get_revision(1, None) + .await + .unwrap() + .root_hash() + .await + .unwrap(), + merkle::Hash([0; 32]) + ); } fn db_config() -> DBConfig { diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index b3d210083aa7..deb1fa127df7 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -1,11 +1,16 @@ use tokio::sync::{mpsc, oneshot}; -use crate::{db::DBError, merkle}; +use crate::{ + db::{DBError, DBRevConfig}, + merkle::{self, MerkleError}, + proof::Proof, +}; mod client; mod server; pub type BatchId = u32; +pub type RevId = u32; #[derive(Debug)] pub struct BatchHandle { @@ -13,20 +18,26 @@ pub struct BatchHandle { id: u32, } +#[derive(Debug)] +pub struct RevisionHandle { + sender: mpsc::Sender, + id: u32, +} + /// Client side request object #[derive(Debug)] pub enum Request { NewBatch { respond_to: oneshot::Sender, }, - RootHash { - respond_to: oneshot::Sender>, - }, - Get { - key: Vec, - respond_to: oneshot::Sender, DBError>>, + NewRevision { + nback: usize, + cfg: Option, + respond_to: oneshot::Sender>, }, + BatchRequest(BatchRequest), + RevRequest(RevRequest), } type OwnedKey = Vec; @@ -84,3 +95,24 @@ pub enum BatchRequest { respond_to: oneshot::Sender<()>, }, } + +#[derive(Debug)] +pub enum RevRequest { + Get { + handle: RevId, + key: OwnedKey, + respond_to: oneshot::Sender, DBError>>, + }, + Prove { + handle: RevId, + key: OwnedKey, + respond_to: oneshot::Sender>, + }, + RootHash { + handle: RevId, + respond_to: oneshot::Sender>, + }, + Drop { + handle: RevId, + }, +} diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 7be4ba78ba39..69592f8c1751 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -5,18 +5,26 @@ use std::{ }; use tokio::sync::mpsc::Receiver; -use crate::db::{DBConfig, DBError, DB}; +use crate::{ + db::{DBConfig, DBError, DB}, + merkle::MerkleError, +}; -use super::{BatchId, BatchRequest, Request}; +use super::{BatchId, BatchRequest, Request, RevId}; -macro_rules! handle_req { - ($in: expr, $out: expr) => { - let _ = $in.send($out); - }; -} macro_rules! get_batch { - ($batches: ident, $handle: ident, $out: expr) => { - match $batches.remove(&$handle) { + ($active_batch: expr, $handle: ident, $lastid: ident, $respond_to: expr) => {{ + if $handle != $lastid.load(Ordering::Relaxed) - 1 || $active_batch.is_none() { + let _ = $respond_to.send(Err(DBError::InvalidParams)); + continue; + } + $active_batch.take().unwrap() + }}; +} + +macro_rules! get_rev { + ($rev: ident, $handle: ident, $out: expr) => { + match $rev.get(&$handle) { None => { let _ = $out.send(Err(DBError::InvalidParams)); continue; @@ -25,14 +33,14 @@ macro_rules! get_batch { } }; } - #[derive(Copy, Debug, Clone)] pub struct FirewoodService {} impl FirewoodService { pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DBConfig) -> Self { let db = DB::new(&owned_path.to_string_lossy(), &cfg).unwrap(); - let mut batches = HashMap::::new(); + let mut active_batch: Option = None; + let mut revs = HashMap::::new(); let lastid = AtomicU32::new(0); loop { let msg = match receiver.blocking_recv() { @@ -42,18 +50,57 @@ impl FirewoodService { match msg { Request::NewBatch { respond_to } => { let id: BatchId = lastid.fetch_add(1, Ordering::Relaxed); - batches.insert(id, db.new_writebatch()); + active_batch = Some(db.new_writebatch()); let _ = respond_to.send(id); } - Request::RootHash { respond_to } => { - handle_req!(respond_to, db.root_hash()); - } - Request::Get { key, respond_to } => { - handle_req!(respond_to, db.kv_get(key)); + Request::NewRevision { + nback, + cfg, + respond_to, + } => { + let id: RevId = lastid.fetch_add(1, Ordering::Relaxed); + let msg = match db.get_revision(nback, cfg) { + Some(rev) => { + revs.insert(id, rev); + Some(id) + } + None => None, + }; + let _ = respond_to.send(msg); } + + Request::RevRequest(req) => match req { + super::RevRequest::Get { + handle, + key, + respond_to, + } => { + let rev = get_rev!(revs, handle, respond_to); + let msg = rev.kv_get(key); + let _ = respond_to.send(msg.map_or(Err(DBError::KeyNotFound), Ok)); + } + super::RevRequest::Prove { + handle, + key, + respond_to, + } => { + let msg = revs + .get(&handle) + .map_or(Err(MerkleError::UnsetInternal), |r| r.prove(key)); + let _ = respond_to.send(msg); + } + super::RevRequest::RootHash { handle, respond_to } => { + let rev = get_rev!(revs, handle, respond_to); + let msg = rev.kv_root_hash(); + let _ = respond_to.send(msg); + } + super::RevRequest::Drop { handle } => { + revs.remove(&handle); + } + }, Request::BatchRequest(req) => match req { BatchRequest::Commit { handle, respond_to } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); batch.commit(); let _ = respond_to.send(Ok(())); } @@ -63,10 +110,10 @@ impl FirewoodService { val, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.kv_insert(key, val) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -78,10 +125,10 @@ impl FirewoodService { key, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.kv_remove(key) { Ok(v) => { - batches.insert(handle, v.0); + active_batch = Some(v.0); Ok(v.1) } Err(e) => Err(e), @@ -94,10 +141,10 @@ impl FirewoodService { balance, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.set_balance(key.as_ref(), balance) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -110,10 +157,10 @@ impl FirewoodService { code, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.set_code(key.as_ref(), code.as_ref()) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -126,10 +173,10 @@ impl FirewoodService { nonce, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.set_nonce(key.as_ref(), nonce) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -143,10 +190,10 @@ impl FirewoodService { state, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.set_state(key.as_ref(), sub_key.as_ref(), state) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -158,10 +205,10 @@ impl FirewoodService { key, respond_to, } => { - let batch = get_batch!(batches, handle, respond_to); + let batch = get_batch!(active_batch, handle, lastid, respond_to); let resp = match batch.create_account(key.as_ref()) { Ok(v) => { - batches.insert(handle, v); + active_batch = Some(v); Ok(()) } Err(e) => Err(e), @@ -170,10 +217,10 @@ impl FirewoodService { } BatchRequest::NoRootHash { handle, respond_to } => { // TODO: there's no way to report an error back to the caller here - if let Some(batch) = batches.remove(&handle) { - batches.insert(handle, batch.no_root_hash()); - } else { - // log!("Invalid handle {handle} passed to no_root_hash"); + if handle == lastid.load(Ordering::Relaxed) - 1 { + if let Some(batch) = active_batch { + active_batch = Some(batch.no_root_hash()); + } } respond_to.send(()).unwrap(); } From 9af1587d2d4c12bb56d93e6cce95152fa74a986e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 4 Apr 2023 19:38:39 +0000 Subject: [PATCH 0100/1053] Mark some APIs behind feature flags Two feature flags added are 'proof' and 'eth' --- firewood/Cargo.toml | 6 ++++++ firewood/src/api.rs | 18 +++++++++++++++++- firewood/src/service/client.rs | 14 +++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 602dd198e704..ecd6edd909d5 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -42,3 +42,9 @@ assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.0.29" } + +[features] +# proof API +proof = [] +# eth API +eth = [] \ No newline at end of file diff --git a/firewood/src/api.rs b/firewood/src/api.rs index d2d9e8f2824d..34c64bbb2e93 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -1,10 +1,13 @@ use std::io::Write; +#[cfg(feature = "eth")] +use crate::account::Account; +#[cfg(feature = "eth")] use primitive_types::U256; -use crate::account::Account; use crate::db::{DBError, DBRevConfig}; use crate::merkle::Hash; +#[cfg(feature = "proof")] use crate::{merkle::MerkleError, proof::Proof}; use async_trait::async_trait; @@ -33,19 +36,23 @@ where self, key: K, ) -> Result<(Self, Option>), DBError>; + /// Set balance of the account + #[cfg(feature = "eth")] async fn set_balance + Send + Sync>( self, key: K, balance: U256, ) -> Result; /// Set code of the account + #[cfg(feature = "eth")] async fn set_code + Send + Sync, V: AsRef<[u8]> + Send + Sync>( self, key: K, code: V, ) -> Result; /// Set nonce of the account. + #[cfg(feature = "eth")] async fn set_nonce + Send + Sync>( self, key: K, @@ -63,8 +70,10 @@ where val: V, ) -> Result; /// Create an account. + #[cfg(feature = "eth")] async fn create_account + Send + Sync>(self, key: K) -> Result; /// Delete an account. + #[cfg(feature = "eth")] async fn delete_account + Send + Sync>( self, key: K, @@ -91,7 +100,9 @@ where async fn root_hash(&self) -> Result; async fn dump(&self, writer: W) -> Result<(), DBError>; + #[cfg(feature = "proof")] async fn prove + Send + Sync>(&self, key: K) -> Result; + #[cfg(feature = "proof")] async fn verify_range_proof + Send + Sync>( &self, proof: Proof, @@ -100,14 +111,19 @@ where keys: Vec, values: Vec, ); + #[cfg(feature = "eth")] async fn get_balance + Send + Sync>(&self, key: K) -> Result; + #[cfg(feature = "eth")] async fn get_code + Send + Sync>(&self, key: K) -> Result, DBError>; + #[cfg(feature = "eth")] async fn get_nonce + Send + Sync>(&self, key: K) -> Result; + #[cfg(feature = "eth")] async fn get_state + Send + Sync>( &self, key: K, sub_key: K, ) -> Result, DBError>; + #[cfg(feature = "eth")] async fn dump_account + Send + Sync>( &self, key: K, diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index 1bc4a4449bc2..0e1bac215693 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -102,6 +102,7 @@ impl api::WriteBatch for BatchHandle { }; } + #[cfg(feature = "eth")] async fn set_balance + Send + Sync>( self, key: K, @@ -124,6 +125,7 @@ impl api::WriteBatch for BatchHandle { }; } + #[cfg(feature = "eth")] async fn set_code(self, key: K, code: V) -> Result where K: AsRef<[u8]> + Send + Sync, @@ -146,6 +148,7 @@ impl api::WriteBatch for BatchHandle { }; } + #[cfg(feature = "eth")] async fn set_nonce + Send + Sync>( self, key: K, @@ -191,7 +194,7 @@ impl api::WriteBatch for BatchHandle { Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures }; } - + #[cfg(feature = "eth")] async fn create_account + Send + Sync>(self, key: K) -> Result { let (send, recv) = oneshot::channel(); let _ = self @@ -209,6 +212,7 @@ impl api::WriteBatch for BatchHandle { }; } + #[cfg(feature = "eth")] async fn delete_account + Send + Sync>( self, _key: K, @@ -277,6 +281,7 @@ impl Revision for super::RevisionHandle { recv.await.expect("Actor task has been killed") } + #[cfg(feature = "proof")] async fn prove + Send + Sync>( &self, key: K, @@ -291,6 +296,7 @@ impl Revision for super::RevisionHandle { recv.await.expect("channel failed") } + #[cfg(feature = "proof")] async fn verify_range_proof + Send + Sync>( &self, _proof: crate::proof::Proof, @@ -315,6 +321,7 @@ impl Revision for super::RevisionHandle { todo!() } + #[cfg(feature = "eth")] async fn dump_account + Send + Sync>( &self, _key: K, @@ -327,6 +334,7 @@ impl Revision for super::RevisionHandle { unimplemented!(); } + #[cfg(feature = "eth")] async fn get_balance + Send + Sync>( &self, _key: K, @@ -334,10 +342,12 @@ impl Revision for super::RevisionHandle { todo!() } + #[cfg(feature = "eth")] async fn get_code + Send + Sync>(&self, _key: K) -> Result, DBError> { todo!() } + #[cfg(feature = "eth")] async fn get_nonce + Send + Sync>( &self, _key: K, @@ -345,6 +355,7 @@ impl Revision for super::RevisionHandle { todo!() } + #[cfg(feature = "eth")] async fn get_state + Send + Sync>( &self, _key: K, @@ -416,6 +427,7 @@ mod test { let conn = Connection::new(tmpdb, db_config()); let batch = conn.new_writebatch().await; let batch = batch.kv_insert(key, b"val").await.unwrap(); + #[cfg(feature = "eth")] { let batch = batch.set_code(key, b"code").await.unwrap(); let batch = batch.set_nonce(key, 42).await.unwrap(); From b907fb05e7b84d81ed0645d3ba96b6b517354a7b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 14 Mar 2023 20:50:02 +0000 Subject: [PATCH 0101/1053] Add some basic benchmarks I think the serialization code is quite slow, so adding some baseline benchmarks for future testing. I think we can do better with bytesmuck or something. --- shale/Cargo.toml | 6 ++++++ shale/benches/shale-bench.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 shale/benches/shale-bench.rs diff --git a/shale/Cargo.toml b/shale/Cargo.toml index 4ed4980a2fe8..33d2404cb2c5 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -8,5 +8,11 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bencher = "0.1.5" hex = "0.4.3" lru = "0.10.0" +rand = "0.8.5" + +[[bench]] +name = "shale-bench" +harness = false diff --git a/shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs new file mode 100644 index 000000000000..51080bf729c5 --- /dev/null +++ b/shale/benches/shale-bench.rs @@ -0,0 +1,36 @@ +use bencher::{benchmark_group, benchmark_main, Bencher}; + +use rand::Rng; +use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr, PlainMem}; + +fn get_view(b: &mut Bencher) { + const SIZE: u64 = 2_000_000; + + let mut m = PlainMem::new(SIZE, 0); + let mut rng = rand::thread_rng(); + + b.iter(|| { + let len = rng.gen_range(0..26); + let rdata = &"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]; + + let offset = rng.gen_range(0..SIZE - len as u64); + + m.write(offset, &rdata); + let view = m.get_view(offset, rdata.len().try_into().unwrap()).unwrap(); + assert_eq!(view.as_deref(), rdata); + }); +} +fn serialize(b: &mut Bencher) { + const SIZE: u64 = 2_000_000; + + let m = PlainMem::new(SIZE, 0); + + b.iter(|| { + let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); + let _compact_header = + MummyObj::ptr_to_obj(&m, compact_header_obj, shale::compact::CompactHeader::MSIZE) + .unwrap(); + }); +} +benchmark_group!(benches, get_view, serialize); +benchmark_main!(benches); From 4972d4d14f4bbc0f4d6bd59d42abd2df1df9ec18 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 14 Mar 2023 22:43:00 +0000 Subject: [PATCH 0102/1053] Minor fix: move dependencies to dev-dependencies Will squash on merge --- shale/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shale/Cargo.toml b/shale/Cargo.toml index 33d2404cb2c5..ea4077fab38f 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -8,9 +8,11 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bencher = "0.1.5" hex = "0.4.3" lru = "0.10.0" + +[dev-dependencies] +bencher = "0.1.5" rand = "0.8.5" [[bench]] From 90cd3e22b022cbf2c214e25882548002d0107269 Mon Sep 17 00:00:00 2001 From: "Ron Kuris (Work)" Date: Tue, 4 Apr 2023 22:44:19 +0000 Subject: [PATCH 0103/1053] Remove unsafe with bytemuck --- firewood/Cargo.toml | 3 ++- firewood/src/db.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index ecd6edd909d5..ff5bc1799d54 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -32,6 +32,7 @@ primitive-types = { version = "0.12.0", features = ["impl-rlp"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.38" async-trait = "0.1.57" +bytemuck = { version = "1.13.1", features = ["derive"] } [dev-dependencies] criterion = "0.4.0" @@ -47,4 +48,4 @@ clap = { version = "4.0.29" } # proof API proof = [] # eth API -eth = [] \ No newline at end of file +eth = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 67d4d31f24f0..4b152d486936 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -5,6 +5,7 @@ use std::io::{Cursor, Write}; use std::rc::Rc; use std::thread::JoinHandle; +use bytemuck::{cast_slice, AnyBitPattern}; use parking_lot::{Mutex, MutexGuard}; use primitive_types::U256; use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID}; @@ -56,6 +57,7 @@ impl Error for DBError {} /// correct parameters are used when the DB is opened later (the parameters here will override the /// parameters in [DBConfig] if the DB already exists). #[repr(C)] +#[derive(Debug, Clone, Copy, AnyBitPattern)] struct DBParams { magic: [u8; 16], meta_file_nbit: u64, @@ -464,7 +466,7 @@ impl DB { nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DBError::System)?; drop(file0); let mut offset = header_bytes.len() as u64; - let header = unsafe { std::mem::transmute::<_, DBParams>(header_bytes) }; + let header: DBParams = cast_slice(&header_bytes)[0]; // setup disk buffer let cached = Universe { From 67dc5f7ae07c70016d60552dd65d6191b10f2e48 Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 5 Apr 2023 13:45:20 -0400 Subject: [PATCH 0104/1053] api: Use eth and proof feature gates across all API surfaces. (#181) Signed-off-by: Dan Sover --- firewood/src/api.rs | 1 + firewood/src/sender.rs | 8 ++++++++ firewood/src/service/client.rs | 1 + firewood/src/service/mod.rs | 10 ++++++++-- firewood/src/service/server.rs | 11 +++++++---- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 34c64bbb2e93..52d9792228ed 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -59,6 +59,7 @@ where nonce: u64, ) -> Result; /// Set the state value indexed by `sub_key` in the account indexed by `key`. + #[cfg(feature = "eth")] async fn set_state< K: AsRef<[u8]> + Send + Sync, SK: AsRef<[u8]> + Send + Sync, diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs index 4227a16046e3..6769c2a1baf7 100644 --- a/firewood/src/sender.rs +++ b/firewood/src/sender.rs @@ -25,26 +25,32 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } + #[cfg(feature = "eth")] fn get_account(&self, key: K) -> Result { todo!() } + #[cfg(feature = "eth")] fn dump_account(&self, key: K, writer: W) -> Result<(), crate::db::DBError> { todo!() } + #[cfg(feature = "eth")] fn get_balance(&self, key: K) -> Result { todo!() } + #[cfg(feature = "eth")] fn get_code(&self, key: K) -> Result, crate::db::DBError> { todo!() } + #[cfg(feature = "proof")] fn prove(&self, key: K) -> Result { todo!() } + #[cfg(feature = "proof")] fn verify_range_proof( &self, proof: crate::proof::Proof, @@ -56,10 +62,12 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } + #[cfg(feature = "eth")] fn get_nonce(&self, key: K) -> Result { todo!() } + #[cfg(feature = "eth")] fn get_state(&self, key: K, sub_key: K) -> Result, crate::db::DBError> { todo!() } diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index 0e1bac215693..0aba1862362d 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -171,6 +171,7 @@ impl api::WriteBatch for BatchHandle { }; } + #[cfg(feature = "eth")] async fn set_state(self, key: K, sub_key: SK, state: S) -> Result where K: AsRef<[u8]> + Send + Sync, diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index deb1fa127df7..1db9f96569dc 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -2,8 +2,7 @@ use tokio::sync::{mpsc, oneshot}; use crate::{ db::{DBError, DBRevConfig}, - merkle::{self, MerkleError}, - proof::Proof, + merkle, }; mod client; @@ -41,6 +40,7 @@ pub enum Request { } type OwnedKey = Vec; +#[allow(dead_code)] type OwnedVal = Vec; #[derive(Debug)] @@ -60,24 +60,28 @@ pub enum BatchRequest { handle: BatchId, respond_to: oneshot::Sender>, }, + #[cfg(feature = "eth")] SetBalance { handle: BatchId, key: OwnedKey, balance: primitive_types::U256, respond_to: oneshot::Sender>, }, + #[cfg(feature = "eth")] SetCode { handle: BatchId, key: OwnedKey, code: OwnedVal, respond_to: oneshot::Sender>, }, + #[cfg(feature = "eth")] SetNonce { handle: BatchId, key: OwnedKey, nonce: u64, respond_to: oneshot::Sender>, }, + #[cfg(feature = "eth")] SetState { handle: BatchId, key: OwnedKey, @@ -85,6 +89,7 @@ pub enum BatchRequest { state: OwnedVal, respond_to: oneshot::Sender>, }, + #[cfg(feature = "eth")] CreateAccount { handle: BatchId, key: OwnedKey, @@ -103,6 +108,7 @@ pub enum RevRequest { key: OwnedKey, respond_to: oneshot::Sender, DBError>>, }, + #[cfg(feature = "proof")] Prove { handle: RevId, key: OwnedKey, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 69592f8c1751..fcffdfd254a9 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -5,10 +5,7 @@ use std::{ }; use tokio::sync::mpsc::Receiver; -use crate::{ - db::{DBConfig, DBError, DB}, - merkle::MerkleError, -}; +use crate::db::{DBConfig, DBError, DB}; use super::{BatchId, BatchRequest, Request, RevId}; @@ -79,6 +76,7 @@ impl FirewoodService { let msg = rev.kv_get(key); let _ = respond_to.send(msg.map_or(Err(DBError::KeyNotFound), Ok)); } + #[cfg(feature = "proof")] super::RevRequest::Prove { handle, key, @@ -135,6 +133,7 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } + #[cfg(feature = "eth")] BatchRequest::SetBalance { handle, key, @@ -151,6 +150,7 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } + #[cfg(feature = "eth")] BatchRequest::SetCode { handle, key, @@ -167,6 +167,7 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } + #[cfg(feature = "eth")] BatchRequest::SetNonce { handle, key, @@ -183,6 +184,7 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } + #[cfg(feature = "eth")] BatchRequest::SetState { handle, key, @@ -200,6 +202,7 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } + #[cfg(feature = "eth")] BatchRequest::CreateAccount { handle, key, From 53b470d2dc9b6fbe3a381e66bc59a2c67b334730 Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 5 Apr 2023 17:48:55 -0400 Subject: [PATCH 0105/1053] fix: Update db::new() to accept a Path (#187) Signed-off-by: Dan Sover --- firewood/src/db.rs | 5 +++-- firewood/src/file.rs | 9 +++++---- firewood/src/service/server.rs | 2 +- fwdctl/src/create.rs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4b152d486936..7a500ec8c979 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::error::Error; use std::fmt; use std::io::{Cursor, Write}; +use std::path::Path; use std::rc::Rc; use std::thread::JoinHandle; @@ -419,10 +420,10 @@ pub struct DB { impl DB { /// Open a database. - pub fn new(db_path: &str, cfg: &DBConfig) -> Result { + pub fn new>(db_path: P, cfg: &DBConfig) -> Result { // TODO: make sure all fds are released at the end if cfg.truncate { - let _ = std::fs::remove_dir_all(db_path); + let _ = std::fs::remove_dir_all(db_path.as_ref()); } let (db_fd, reset) = file::open_dir(db_path, cfg.truncate).map_err(DBError::System)?; diff --git a/firewood/src/file.rs b/firewood/src/file.rs index ab17d8f39e80..f4fd3719f7e4 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -1,6 +1,7 @@ // Copied from CedrusDB pub(crate) use std::os::unix::io::RawFd as Fd; +use std::path::Path; use growthring::oflags; use nix::errno::Errno; @@ -83,12 +84,12 @@ pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result { openat(rootfd, dirname, oflags(), Mode::empty()) } -pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { +pub fn open_dir>(path: P, truncate: bool) -> Result<(Fd, bool), nix::Error> { let mut reset_header = truncate; if truncate { - let _ = std::fs::remove_dir_all(path); + let _ = std::fs::remove_dir_all(path.as_ref()); } - match mkdir(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { + match mkdir(path.as_ref(), Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { Err(e) => { if truncate { return Err(e); @@ -100,7 +101,7 @@ pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> { } } Ok(( - match open(path, oflags(), Mode::empty()) { + match open(path.as_ref(), oflags(), Mode::empty()) { Ok(fd) => fd, Err(e) => return Err(e), }, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index fcffdfd254a9..7f5af6b73562 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -35,7 +35,7 @@ pub struct FirewoodService {} impl FirewoodService { pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DBConfig) -> Self { - let db = DB::new(&owned_path.to_string_lossy(), &cfg).unwrap(); + let db = DB::new(owned_path, &cfg).unwrap(); let mut active_batch: Option = None; let mut revs = HashMap::::new(); let lastid = AtomicU32::new(0); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index e0cffc90418c..7dfd7bd8a05c 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -259,7 +259,7 @@ pub fn run(opts: &Options) -> Result<()> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - match DB::new(opts.name.as_ref(), &db_config) { + match DB::new::<&str>(opts.name.as_ref(), &db_config) { Ok(_) => { println!("created firewood database in {:?}", opts.name); Ok(()) From e084c94d1ffcee16af69846db7e105eec3e2f211 Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 5 Apr 2023 18:30:26 -0400 Subject: [PATCH 0106/1053] fix: Use bytemuck instead of unsafe in growth-ring (#185) --- growth-ring/Cargo.toml | 1 + growth-ring/src/wal.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index ee3ec49231d2..4d41fe4a2fee 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -20,6 +20,7 @@ async-trait = "0.1.57" futures = "0.3.24" nix = "0.26.2" libc = "0.2.133" +bytemuck = "1.13.1" [dev-dependencies] hex = "0.4.3" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index fb977dad663f..e62625a41aba 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use bytemuck::{cast_slice, AnyBitPattern}; use futures::{ future::{self, FutureExt, TryFutureExt}, stream::StreamExt, @@ -86,6 +87,7 @@ fn counter_lt(a: u32, b: u32) -> bool { } #[repr(C)] +#[derive(Debug, Clone, Copy, AnyBitPattern)] struct Header { /// all preceding files ( WALFilePool { async fn read_header(&self) -> Result { let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); - let header = unsafe { std::mem::transmute::<_, Header>(bytes) }; + let header: Header = cast_slice(&bytes)[0]; Ok(header) } From 97fbafebb8e5a605972c54ac5779977b77dd82a9 Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 7 Apr 2023 09:44:52 -0400 Subject: [PATCH 0107/1053] *: Add license header to firewood source code (#189) Signed-off-by: Dan Sover --- firewood/examples/benchmark.rs | 3 +++ firewood/examples/dump.rs | 3 +++ firewood/examples/rev.rs | 3 +++ firewood/examples/simple.rs | 3 +++ firewood/src/account.rs | 3 +++ firewood/src/api.rs | 3 +++ firewood/src/db.rs | 3 +++ firewood/src/dynamic_mem.rs | 3 +++ firewood/src/file.rs | 3 +++ firewood/src/lib.rs | 3 +++ firewood/src/merkle.rs | 3 +++ firewood/src/merkle_util.rs | 3 +++ firewood/src/proof.rs | 3 +++ firewood/src/sender.rs | 3 +++ firewood/src/service/client.rs | 3 +++ firewood/src/service/mod.rs | 3 +++ firewood/src/service/server.rs | 3 +++ firewood/src/storage.rs | 3 +++ fwdctl/src/create.rs | 3 +++ fwdctl/src/delete.rs | 3 +++ fwdctl/src/dump.rs | 3 +++ fwdctl/src/get.rs | 3 +++ fwdctl/src/insert.rs | 3 +++ fwdctl/src/main.rs | 3 +++ fwdctl/src/root.rs | 3 +++ 25 files changed, 75 insertions(+) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index e094ac1befa4..641f143d7dc1 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use clap::Parser; use criterion::Criterion; use firewood::db::{DBConfig, WALConfig, DB}; diff --git a/firewood/examples/dump.rs b/firewood/examples/dump.rs index ccd539fce555..1b716b8bee08 100644 --- a/firewood/examples/dump.rs +++ b/firewood/examples/dump.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use clap::{command, Arg, ArgMatches}; use firewood::db::{DBConfig, DBError, WALConfig, DB}; diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 85071ee4b08c..628989c3597b 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use firewood::db::{DBConfig, WALConfig, DB}; /// cargo run --example rev diff --git a/firewood/examples/simple.rs b/firewood/examples/simple.rs index 360b2968e339..2630ed6bdc6a 100644 --- a/firewood/examples/simple.rs +++ b/firewood/examples/simple.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use firewood::db::{DBConfig, WALConfig, DB}; fn print_states(db: &DB) { diff --git a/firewood/src/account.rs b/firewood/src/account.rs index 0052f5272a19..3ed151c0062e 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::fmt; use std::io::{Cursor, Write}; diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 52d9792228ed..ce87c2695cb1 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::io::Write; #[cfg(feature = "eth")] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7a500ec8c979..22e8c5f3e3f1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::collections::VecDeque; use std::error::Error; use std::fmt; diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index 730454cd2f07..6d0dc03895cb 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::cell::UnsafeCell; use std::ops::{Deref, DerefMut}; use std::rc::Rc; diff --git a/firewood/src/file.rs b/firewood/src/file.rs index f4fd3719f7e4..c81f53988567 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // Copied from CedrusDB pub(crate) use std::os::unix::io::RawFd as Fd; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 17d41912a379..c77226a0cc2e 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + //! # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. //! //! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4f3fc690f674..fb89117f5689 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::proof::Proof; use enum_as_inner::EnumAsInner; diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 8c8299298ed0..181479bf499e 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::merkle::*; use crate::proof::Proof; use crate::{dynamic_mem::DynamicMem, proof::ProofError}; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index edd5669895e1..74a9b98cd3ac 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::account::BlobError; use crate::db::DBError; use crate::merkle::*; diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs index 6769c2a1baf7..5ac923646a11 100644 --- a/firewood/src/sender.rs +++ b/firewood/src/sender.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::api::DB; pub struct Sender { diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index 0aba1862362d..68b950ac701a 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + /// Client side connection structure /// /// A connection is used to send messages to the firewood thread. diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index 1db9f96569dc..e2fdd71ec855 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use tokio::sync::{mpsc, oneshot}; use crate::{ diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 7f5af6b73562..6a6f07abf544 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::{ collections::HashMap, path::PathBuf, diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index cb0c35e6bad4..bbe181c2dbbd 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // TODO: try to get rid of the use `RefCell` in this file use std::cell::{RefCell, RefMut}; diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 7dfd7bd8a05c..a441ec0aaf03 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use clap::{value_parser, Args}; use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 8ef8d4bb1869..f481502fde93 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 0a4306a0cbcc..0467243e45d8 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 1f848873279f..39478e19cc25 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 4833c9953467..dad6c865367c 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index ce0c35155c0c..d4c8948438fc 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::Result; use clap::{Parser, Subcommand}; use std::process; diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index ee45af0d9f79..93c608930848 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; From dfc09fa1b03d1cfe8168dd3fc1e02c272fd2b5c3 Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 7 Apr 2023 16:34:30 -0400 Subject: [PATCH 0108/1053] docs: Add alpha warning to firewood README (#191) Signed-off-by: Dan Sover --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ea58e90f6ae6..b2b899f56cc5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. +> :warning: firewood is alpha-level software and is not ready for production +> use. Do not use firewood to store production data. See the +> [license](./LICENSE.md) for more information regarding firewood usage. + Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes access to latest state, by providing extremely fast reads, but also provides a limited view into past state. It does not copy-on-write the From b113065c24509ca5b89cc551e0caba51232e2277 Mon Sep 17 00:00:00 2001 From: Hao Hao Date: Sat, 8 Apr 2023 04:25:56 +0000 Subject: [PATCH 0109/1053] Add range proof test for smallest and largest keys --- firewood/tests/merkle.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 9db4c43c625b..70961f4d2889 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -904,6 +904,35 @@ fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { Ok(()) } +#[test] +// Tests the range starts with zero and ends with 0xffff...fff. +fn test_both_sides_range_proof() -> Result<(), ProofError> { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..4096 as u32 { + let key = rand::thread_rng().gen::<[u8; 32]>(); + let val = rand::thread_rng().gen::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = Vec::from_iter(set.iter()); + items.sort(); + let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; + + let start: [u8; 32] = [0; 32]; + let end: [u8; 32] = [255; 32]; + + let mut proof = merkle.prove(start)?; + assert!(!proof.0.is_empty()); + let end_proof = merkle.prove(end)?; + assert!(!end_proof.0.is_empty()); + proof.concat_proofs(end_proof); + + let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); + merkle.verify_range_proof(&proof, &start, &end, keys, vals)?; + } + Ok(()) +} + #[test] // Tests normal range proof with both edge proofs // as the existent proof, but with an extra empty value included, which is a From b8f74fcdab2474ca9429bf94104e521fcf5aa0be Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 11 Apr 2023 13:46:24 -0700 Subject: [PATCH 0110/1053] Add the future roadmap to README (#196) * Add future roadmap and milestones to README --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index b2b899f56cc5..a4b61ed81e22 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,36 @@ firewood. Firewood exists to provide a very fast storage layer for [qEVM](https: firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). +## Roadmap +### Green Milestone +This milestone will focus on additional code cleanup, including supporting +concurrent access to a specific revision, as well as cleaning up the basic +reader and writer interfaces to have consistent read/write semantics. +- [ ] Concurrent readers of pinned revisions while allowing additional batches +to commit, to support parallel reads for the past consistent states. The revisions +are uniquely identified by root hashes. +- [ ] Pin a reader to a specific revision, so that future commits or other +operations do not see any changes. +- [ ] Be able to read-your-write in a batch that is not committed. Uncommitted +changes will not be shown to any other concurrent readers. +### Seasoned milestone +This milestone will add support for proposals, including proposed future +branches, with a cache to make committing these branches efficiently. +- [ ] Be able to propose a batch against the existing committed revision, or +propose a batch against any existing proposed revision. +- [ ] Be able to quickly commit a batch that has been proposed. Note that this +invalidates all other proposals that are not children of the committed proposed batch. +### Dried milestone +The focus of this milestone will be to support synchronization to other +instances to replicate the state. A synchronization library should also +be developed for this milestone. +- [ ] Support replicating the full state with corresponding range proofs that +verify the correctness of the data. +- [ ] Support replicating the delta state from the last sync point with +corresponding range proofs that verify the correctness of the data. +- [ ] Enforce limits on the size of the range proof as well as keys to make + synchronization easier for clients. + ## Build Firewood currently is Linux-only, as it has a dependency on the asynchronous I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not From 5e942ed6ab6616a80d543f6172359962d52fd632 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 09:54:13 -0700 Subject: [PATCH 0111/1053] remove unused file (#3) --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e5c0fb6d4b44..000000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "shale"] - path = shale - url = https://github.com/Determinant/shale.git From c317e17bef03809dc8d9a2ea4de489e9ed6f42c1 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 09:57:48 -0700 Subject: [PATCH 0112/1053] Add website (#4) * add website files * cleanup website --- {docs => assets}/architecture.svg | 0 {docs => assets}/three-layers.svg | 0 docs/COPYRIGHT.txt | 46 + docs/FiraSans-LICENSE.txt | 94 + docs/FiraSans-Medium.woff2 | Bin 0 -> 132780 bytes docs/FiraSans-Regular.woff2 | Bin 0 -> 129188 bytes docs/LICENSE-APACHE.txt | 201 + docs/LICENSE-MIT.txt | 23 + docs/NanumBarunGothic-LICENSE.txt | 99 + docs/NanumBarunGothic.ttf.woff2 | Bin 0 -> 399468 bytes docs/SourceCodePro-It.ttf.woff2 | Bin 0 -> 44896 bytes docs/SourceCodePro-LICENSE.txt | 93 + docs/SourceCodePro-Regular.ttf.woff2 | Bin 0 -> 52228 bytes docs/SourceCodePro-Semibold.ttf.woff2 | Bin 0 -> 52348 bytes docs/SourceSerif4-Bold.ttf.woff2 | Bin 0 -> 81320 bytes docs/SourceSerif4-It.ttf.woff2 | Bin 0 -> 59860 bytes docs/SourceSerif4-LICENSE.md | 93 + docs/SourceSerif4-Regular.ttf.woff2 | Bin 0 -> 76180 bytes docs/ayu.css | 1 + docs/clipboard.svg | 1 + docs/crates.js | 1 + docs/dark.css | 1 + docs/down-arrow.svg | 1 + docs/favicon-16x16.png | Bin 0 -> 715 bytes docs/favicon-32x32.png | Bin 0 -> 1125 bytes docs/favicon.svg | 24 + docs/firewood/all.html | 1 + docs/firewood/db/enum.DBError.html | 10 + docs/firewood/db/index.html | 3 + docs/firewood/db/sidebar-items.js | 1 + docs/firewood/db/struct.DB.html | 19 + docs/firewood/db/struct.DBConfig.html | 9 + docs/firewood/db/struct.DBRev.html | 16 + docs/firewood/db/struct.DBRevConfig.html | 9 + docs/firewood/db/struct.DiskBufferConfig.html | 9 + docs/firewood/db/struct.Revision.html | 16 + docs/firewood/db/struct.WALConfig.html | 8 + docs/firewood/db/struct.WriteBatch.html | 21 + docs/firewood/index.html | 197 + docs/firewood/merkle/enum.MerkleError.html | 10 + docs/firewood/merkle/index.html | 1 + docs/firewood/merkle/sidebar-items.js | 1 + docs/firewood/merkle/struct.Hash.html | 84 + docs/firewood/merkle/struct.IdTrans.html | 5 + docs/firewood/merkle/struct.Merkle.html | 11 + docs/firewood/merkle/struct.Node.html | 7 + docs/firewood/merkle/struct.Ref.html | 934 +++++ docs/firewood/merkle/struct.RefMut.html | 5 + .../merkle/trait.ValueTransformer.html | 3 + docs/firewood/sidebar-items.js | 1 + .../storage/struct.DiskBufferConfig.html | 11 + docs/firewood/storage/struct.WALConfig.html | 11 + docs/implementors/core/clone/trait.Clone.js | 48 + docs/implementors/core/cmp/trait.Eq.js | 36 + docs/implementors/core/cmp/trait.PartialEq.js | 39 + docs/implementors/core/fmt/trait.Debug.js | 51 + docs/implementors/core/marker/trait.Freeze.js | 55 + docs/implementors/core/marker/trait.Send.js | 55 + .../core/marker/trait.StructuralEq.js | 29 + .../core/marker/trait.StructuralPartialEq.js | 32 + docs/implementors/core/marker/trait.Sync.js | 55 + docs/implementors/core/marker/trait.Unpin.js | 55 + .../core/ops/deref/trait.Deref.js | 17 + docs/implementors/core/ops/drop/trait.Drop.js | 24 + .../panic/unwind_safe/trait.RefUnwindSafe.js | 55 + .../panic/unwind_safe/trait.UnwindSafe.js | 55 + .../firewood/merkle/trait.ValueTransformer.js | 3 + docs/implementors/shale/trait.MummyItem.js | 4 + docs/index.html | 1 + docs/light.css | 1 + docs/main.js | 8 + docs/normalize.css | 2 + docs/noscript.css | 1 + docs/rust-logo.svg | 61 + docs/rustdoc.css | 1 + docs/search-index.js | 80 + docs/search.js | 1 + docs/settings.css | 1 + docs/settings.html | 1 + docs/settings.js | 11 + docs/source-files.js | 79 + docs/source-script.js | 1 + docs/src/firewood/account.rs.html | 338 ++ docs/src/firewood/db.rs.html | 2020 ++++++++++ docs/src/firewood/file.rs.html | 226 ++ docs/src/firewood/lib.rs.html | 408 ++ docs/src/firewood/merkle.rs.html | 3424 +++++++++++++++++ docs/src/firewood/storage.rs.html | 2416 ++++++++++++ docs/storage.js | 1 + docs/toggle-minus.svg | 1 + docs/toggle-plus.svg | 1 + docs/wheel.svg | 1 + 92 files changed, 11779 insertions(+) rename {docs => assets}/architecture.svg (100%) rename {docs => assets}/three-layers.svg (100%) create mode 100644 docs/COPYRIGHT.txt create mode 100644 docs/FiraSans-LICENSE.txt create mode 100644 docs/FiraSans-Medium.woff2 create mode 100644 docs/FiraSans-Regular.woff2 create mode 100644 docs/LICENSE-APACHE.txt create mode 100644 docs/LICENSE-MIT.txt create mode 100644 docs/NanumBarunGothic-LICENSE.txt create mode 100644 docs/NanumBarunGothic.ttf.woff2 create mode 100644 docs/SourceCodePro-It.ttf.woff2 create mode 100644 docs/SourceCodePro-LICENSE.txt create mode 100644 docs/SourceCodePro-Regular.ttf.woff2 create mode 100644 docs/SourceCodePro-Semibold.ttf.woff2 create mode 100644 docs/SourceSerif4-Bold.ttf.woff2 create mode 100644 docs/SourceSerif4-It.ttf.woff2 create mode 100644 docs/SourceSerif4-LICENSE.md create mode 100644 docs/SourceSerif4-Regular.ttf.woff2 create mode 100644 docs/ayu.css create mode 100644 docs/clipboard.svg create mode 100644 docs/crates.js create mode 100644 docs/dark.css create mode 100644 docs/down-arrow.svg create mode 100644 docs/favicon-16x16.png create mode 100644 docs/favicon-32x32.png create mode 100644 docs/favicon.svg create mode 100644 docs/firewood/all.html create mode 100644 docs/firewood/db/enum.DBError.html create mode 100644 docs/firewood/db/index.html create mode 100644 docs/firewood/db/sidebar-items.js create mode 100644 docs/firewood/db/struct.DB.html create mode 100644 docs/firewood/db/struct.DBConfig.html create mode 100644 docs/firewood/db/struct.DBRev.html create mode 100644 docs/firewood/db/struct.DBRevConfig.html create mode 100644 docs/firewood/db/struct.DiskBufferConfig.html create mode 100644 docs/firewood/db/struct.Revision.html create mode 100644 docs/firewood/db/struct.WALConfig.html create mode 100644 docs/firewood/db/struct.WriteBatch.html create mode 100644 docs/firewood/index.html create mode 100644 docs/firewood/merkle/enum.MerkleError.html create mode 100644 docs/firewood/merkle/index.html create mode 100644 docs/firewood/merkle/sidebar-items.js create mode 100644 docs/firewood/merkle/struct.Hash.html create mode 100644 docs/firewood/merkle/struct.IdTrans.html create mode 100644 docs/firewood/merkle/struct.Merkle.html create mode 100644 docs/firewood/merkle/struct.Node.html create mode 100644 docs/firewood/merkle/struct.Ref.html create mode 100644 docs/firewood/merkle/struct.RefMut.html create mode 100644 docs/firewood/merkle/trait.ValueTransformer.html create mode 100644 docs/firewood/sidebar-items.js create mode 100644 docs/firewood/storage/struct.DiskBufferConfig.html create mode 100644 docs/firewood/storage/struct.WALConfig.html create mode 100644 docs/implementors/core/clone/trait.Clone.js create mode 100644 docs/implementors/core/cmp/trait.Eq.js create mode 100644 docs/implementors/core/cmp/trait.PartialEq.js create mode 100644 docs/implementors/core/fmt/trait.Debug.js create mode 100644 docs/implementors/core/marker/trait.Freeze.js create mode 100644 docs/implementors/core/marker/trait.Send.js create mode 100644 docs/implementors/core/marker/trait.StructuralEq.js create mode 100644 docs/implementors/core/marker/trait.StructuralPartialEq.js create mode 100644 docs/implementors/core/marker/trait.Sync.js create mode 100644 docs/implementors/core/marker/trait.Unpin.js create mode 100644 docs/implementors/core/ops/deref/trait.Deref.js create mode 100644 docs/implementors/core/ops/drop/trait.Drop.js create mode 100644 docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js create mode 100644 docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js create mode 100644 docs/implementors/firewood/merkle/trait.ValueTransformer.js create mode 100644 docs/implementors/shale/trait.MummyItem.js create mode 100644 docs/index.html create mode 100644 docs/light.css create mode 100644 docs/main.js create mode 100644 docs/normalize.css create mode 100644 docs/noscript.css create mode 100644 docs/rust-logo.svg create mode 100644 docs/rustdoc.css create mode 100644 docs/search-index.js create mode 100644 docs/search.js create mode 100644 docs/settings.css create mode 100644 docs/settings.html create mode 100644 docs/settings.js create mode 100644 docs/source-files.js create mode 100644 docs/source-script.js create mode 100644 docs/src/firewood/account.rs.html create mode 100644 docs/src/firewood/db.rs.html create mode 100644 docs/src/firewood/file.rs.html create mode 100644 docs/src/firewood/lib.rs.html create mode 100644 docs/src/firewood/merkle.rs.html create mode 100644 docs/src/firewood/storage.rs.html create mode 100644 docs/storage.js create mode 100644 docs/toggle-minus.svg create mode 100644 docs/toggle-plus.svg create mode 100644 docs/wheel.svg diff --git a/docs/architecture.svg b/assets/architecture.svg similarity index 100% rename from docs/architecture.svg rename to assets/architecture.svg diff --git a/docs/three-layers.svg b/assets/three-layers.svg similarity index 100% rename from docs/three-layers.svg rename to assets/three-layers.svg diff --git a/docs/COPYRIGHT.txt b/docs/COPYRIGHT.txt new file mode 100644 index 000000000000..34e48134cc34 --- /dev/null +++ b/docs/COPYRIGHT.txt @@ -0,0 +1,46 @@ +These documentation pages include resources by third parties. This copyright +file applies only to those resources. The following third party resources are +included, and carry their own copyright notices and license terms: + +* Fira Sans (FiraSans-Regular.woff2, FiraSans-Medium.woff2): + + Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ + with Reserved Font Name Fira Sans. + + Copyright (c) 2014, Telefonica S.A. + + Licensed under the SIL Open Font License, Version 1.1. + See FiraSans-LICENSE.txt. + +* rustdoc.css, main.js, and playpen.js: + + Copyright 2015 The Rust Developers. + Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE.txt) or + the MIT license (LICENSE-MIT.txt) at your option. + +* normalize.css: + + Copyright (c) Nicolas Gallagher and Jonathan Neal. + Licensed under the MIT license (see LICENSE-MIT.txt). + +* Source Code Pro (SourceCodePro-Regular.ttf.woff2, + SourceCodePro-Semibold.ttf.woff2, SourceCodePro-It.ttf.woff2): + + Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), + with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark + of Adobe Systems Incorporated in the United States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceCodePro-LICENSE.txt. + +* Source Serif 4 (SourceSerif4-Regular.ttf.woff2, SourceSerif4-Bold.ttf.woff2, + SourceSerif4-It.ttf.woff2): + + Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name + 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United + States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceSerif4-LICENSE.md. + +This copyright file is intended to be distributed with rustdoc output. diff --git a/docs/FiraSans-LICENSE.txt b/docs/FiraSans-LICENSE.txt new file mode 100644 index 000000000000..ff9afab064a2 --- /dev/null +++ b/docs/FiraSans-LICENSE.txt @@ -0,0 +1,94 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/FiraSans-Medium.woff2 b/docs/FiraSans-Medium.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..7a1e5fc548ef28137a32150b6aa50a568cd53d02 GIT binary patch literal 132780 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�|o|A<_G9#bJ&K$mXTSM@kZfZ^-ZYS)6EG~kHJ{! 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

TIWHPC<_9>em(Cvlhuf-BD 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 literal 0 HcmV?d00001 diff --git a/docs/FiraSans-Regular.woff2 b/docs/FiraSans-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e766e06ccb0d457fcdc8d4428efb796c7772a497 GIT binary patch literal 129188 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

)Thpg9J)x+@B(n?maf9&#`PQNHtKX<@|CQ`> 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 N#!G)s;2JO>~QPMdMZ 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

xz5_8aF@(pQs@zgto+ItjS_s2^}<|M=lFQ2 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,qWUc(1+qXDfx7@=bp~~)(TqS z_5BF?f8WUf{W$C@srj@O_I!NmB#!7Q>m6xX4+Kp^uaau+*B^8k-RnraR#b zBF9Oqo~}9c})B7HgVPuHcNUPgcGCCXknwYipUhLC7QNo2D`P?r22X;qyxpg zIebJ#b?Wa7)KrZ4IOq&0N|rP%kAvK)(~KLqGS@+!zsgk#__P{DJk$eg3km9YqlAEm z`x&-5RH}}E%kGw7JF@gy(|=l3b-HTM(TZD|Z8HP!9!ViE2T{ErA4~2lZV6ftt z(6U6w+^CywChK4Ou&BSthY2RLnJXF$)i<0PB}cH6ipV`^TdM_nGf!LtIcmNodH^nB zH5?5j{2#&Ebz$Bx5?)|Dl2{=a9B)Swb8bo&!MX8*>HS0j-*!fvi_RR#PSk7<3~LhM z*jj%yL3}I~cfJE|a51%*R4~p!F%3e&dx+?JF3vtPdn3Mc&K!(&n@N;X-_mu9qleSV zWlmAV64=lX(F{WaB86_SxHeIN%eVc57luaPI-9c!7H}r$`5YC&C_GaMmOv4Kb+7s zjwf0T2F%=^5Wg2VDO=sp#d(DAC23heV9S8a$PxZq z3CW;Tt|6CYPTxcjP+}xdg=B!Jkv|f&&~bxAtRV635T|MrXn@m%fry65I3@4 z8{i|@=N+Shf@lZG`9zLO#SNpuO3VD^2{PcPafNo9F4hRrg{w2=BR2kfi5*$P?xw)t z_)SL^*Gv#OUCMAnwqle4F`ac7)+zkHI=rCuCklZT0T!^S`1O$Y4u|hQCx{h6eIJ4# zH0Z=#7;Su+ItaJt%8fmZhQ8c7q_>%H=_+_C9DmUS1)Xn;)P ze?dWcG4{;Sp~E)=^r(oflRf{-P3uoIXjMuJ!x%pG6&Bay9bOn`RoQPSnYfU6NJdL2 zZk#yG$>GgfZ{6AsDe{L+-ko`Ok&3gZu@6~A1NL2nABvOjX6_A1o|4dZ{f?E_tD|{a zI#{Hc4Ib@@4xK%INAU!tGaAQ+pkWmbdgT&94wVv8kZJBh-1!D?Q4e_q57UKAKE<<4 z^fUWs49l4x+xM_JYt@$>bCkn$FnAwA&S|+;^pH3R>_{5jdeP6UnvB3S4G73F&|K&u zf5J$O$)VAl!UVmgAgB&9?#<=S1_s#}gheGF3(v&m_V1~}5qgzhWM zgj1=D#jIET13>g&}L4L^U? zZ`miWtR>X?s9m$m`Uta#J1%{MQv71iEtunQB9oS5@jO4_7NTW()Dwe3d+j@|KsV{7tGc7F{Ekd`g z6z=WnCfw+30%IKgxjp`*BDch-qz~4)=^lHzSY-|rL!{YUmEydV8x>D8pw}^Jls+K| zC%70QczSM-7S8fHWS}699?eQVm^~J{Rhu*So0<>}t3&exOr%<(f2RGc>(jeWDM6a# z@#$49fG4crPn;uhhi0$-EV{~@0cysG3&*C2g~*P@zgU;nj-4iU2nMnp=f*=*>(Ob# zHx^%ov|ZHOq?mey@m3V#<21owRbNa$ft-GqT)zCr#H$Pq0dv3gSq!5CsMowBcJve- zZ>46}U|3-7nN2bAKk!xd+DE+;fYfSfPcS5`hW@!-;te+OPHN`#AKMtgdt1bwdF5Sv z5Cm)2D*vc6P04rTqEUSH8^Z8Ocf7O#^H^{f1o|Gs`8*eQ?}e0p%s0i{-g&GaX^_PU z{kY|>I{fYYd6`%54SKEw34HemnG=CeFuHUmLIa8C) zb-ElL1#F+kLzJefeRttn**KT+4#UJ_a0)&_KIUYQLP?*>f)FC>=|iTO8Q)JJ*Euvk zJQLBCF~IRNn~&@c0u1!G;as5RUmlwXkOQ&94(?;DW2;kq;EsD3tTS_|-x0ak$gxu{ z0b>@Q2S{&_1N}J%XULN(3cL4ieCoktj~D6aV5fi+|BwWDI`V~IuDnzSPOqt|io|d$ zdCsM5JMls*;e1i?vqjWE*#n?%*CQFeiWf5#giI!fPF=95qu~~FK%FtMHoKEf7k?=( zx<#)IxyXm!fl=fm^A9r_%3QYDk0B|a_Ja>=90X`Qp7yq7-REf~HJ3{qryds|8MN|@0-sfDJ2iFTM4swU_fL+V*zLiwNA-G#ToPgR zsu?c3+VrBR27KSpmmZSY;rOaFWmu}W$|aTNxa6xueTt_0D}-5ZS(3x(dY4S70?wBU zXcIv(+&zm(Fvo_3=jK)@S#g;FJeJlM0d)A`YUjn#Y9h(({aW55+zFy3xEU@e2K{>AK?zFo;yLJ`fY-e)s`t69+y#=8pm- zumy6V6F7K@4A;4v<|DNxYt36*qS$hZNIt{9_Xb+#4)?g&6&~OPRsBRJ9yU6Qjmp;-g>9&|LbS3j`4} zrm)qi`u4R|aH-esA91+ioD`0QZ+Pmz-rAao#MrVLV}rgXZl1PZrMu!`M8HN8cd~N# z&#Wi0tlalA8zSPu;2)_9hp9S39;~8T72Wm!Cxo3Luf|c@(P*MAMN)|zUoRWOY*>2Z zg9XJLa*rVo$bAk+)V@m$wbZ+obV)H3F zkwJptU(0OQ&zYw7?yNYys;IGb1dmS}_n}hSwqS=pDr+43R+c^1VoTb zpqZ=sX1wOkHEv|tv3X+Hkc74z2d`c;(g{)L`Rc`itanYNnsh-sRufx)j~w{7hH1f9EHu-OL!yr| z$uNogsP&>DD#*>D8YR>3ni~*khHz*|_*)nIAN;XaqiZ7J(%QPuw;EmdoSR_%fg=9p zbZscahGAM6w<&Tcec%2iqNAQPj6Xufb4;6uci4=PkFQ6pKP33EGmJTd>zp>oW~iDU z6&oMvut6d!Gpjyc=FNch1WP=P@`}Xxe6=dIYND>c3c}VE82aM9w6koy8lb}uU)TNB zJ>p~m(z+H3V_c`Wi{P8Uruj0drO z^3O@(Ofe5O$|v*K@XilJf|iBWyeS0n5$57aPcC16W4A9`g;ZnK3r8AaTK-{eJdq~T z@kuxNAYt{ueHLABlP$eG2$a^ohIuCJ{@}{%LXYi<0cdmf9lFkmUFJu`Y-Vo*2`Cwu z?k>Ns?+Ea?ew>DHXH{D}Ogx`492#vH1m{zzlhm2&pgRNECZGrzZKTF7c4b?5a+dCS zMCw}AuE5%%_I?Ac-l*Wr*u{9JKPS(dqIa^}%qqHkbP{?4sT5PEh&e#5-G&B41iyeQ z+br}vLPzZtdKk7Z`Q`vWinL1~%dHiRPm8fLo|*sG)il0btamzt1y?(hkLNe$U_`95 zcO9c@8HtxEe5xI!V7i-c*$Dik*v_C$Fzg$Yi}OJt(5Y(d^xoMfw_fB(K5(az zj%75C%1x3PQ$bj{(ErG}e8kNpE(n4qk}y#WJD0y*?>`^VRD$?vS7|OxHQ;Ov+jg?F>abXY#yG>Z1QcrVqtjL#hy7fz zc_AX-FE;lbfuR+_J>4f_)B%Hm8BMMdQ!ziIeLzy4viQV<&Dq@RUkO`vtqA zzqa0*6}0K0j=(Zf80RuBcXkgWXw=ydw=+O)W)jX;J6Ww>UH%GG-1H!XJp76vP_rp{ zay3KhTOW$O(Bu$zMik*bE;itd?$IX0^}a7DgceU*EnAe1J?9kfN2-x?!_480KQa)z z0u^1a(P0@VB8gg_%MoXtsvFf*jQ;+#k?WGhEFKPO(jlX79LwnYLXYjZMjb&~X+Oi7 zh)AY8T4!-gT38I4ITrs$Ds^jkLQ}XQ0l`gV_clh)0ub-)-j4*f@KTQ@h<|Jj_K#<5 zifgZEd$hXR$NT6ertfmm#A+1V0GaM?84$rc+2MNItC3Tpvb&zpYQX7-lsg^B4IBR( zHwf0Vq0oTnmCI6^bRA^)dZ0<$k#4?S`el?q)5UpLOKD%U|-!i*%XKEAtP4^UD z+}K_*2dAP;^_+~8JBjm5@Ltru3v<2Fh;Mo$xH4YEfSZrtx5|(ig$}Xfv|F7cLPIVg z=lXNR_MVcRNA9N!%j+#kgn2wLD@9;eMY{Q0^>{8^ zJ65cV^a&|g_;u~9SDZ1`o_e(ake)D}|_M zV6pB|n!L|Sbc@>V-88hH!J}3sf0N64o6*1dUKh6G`0#ZW$k`tma@2*x2AJ&CF^6jV z(RWHtG?y1_05*Ad{@QJDBjfX30)%#bt1*gp@JKt{{uj?v;ui`<+tGX)mpmub&8BLu zQh|Pje*bs}H|_fy1+lUF1a%h{2dCmo7C4?9*S%nN^7?#|hH;Jrn!!{4eK6UwU;x(R z@mYl}E2oC8rXZT(xNgG0&$L@;{n%lKH@a+~83DHBY1rIz0$LI7wM89Kd!YQ*hBsnH zx6CpDU&Lz>z~@tYYW(+?L;_FvcgNG-Z(DoJ%B+tbeujf#G?5LOE(+)y{?0Ms2I~)5 z8#^kQ7WJ>An8BQcgElU8FcI}ER%?S43aa)z!5H;U6^9&BkC z*8`C|aq~!e5GPAnbr2z)2ACD_T+dWRJ!F|%=4IPv==GfU|K{8}PK^K44trQ&J9E2p z7qy>#3TeGWa1?c-eLS$tuQU<5&Z-4CKNe}zsP!G>I|*|&fWihQiKR4>i3xT4xik6H zl_7G~z*JpMIP{*^#fI;;K({Y&$Q4IxU2hvXfIBpu;9MJaqqIXDC5mP&b z2u}yCe=b$9_-jL%L%7;M-B``ZYVN@S)GTt2Z*`)$;?f1tBC(s^Y-lZN&ETEAnKLfy zz^7(lPgzJTX`d}+4+j-SuvcM~J|||#S{#90Mp9pABL9B33T~DFB_DvVxkhKqt-BR#mDy1Kf*!b6}Y(DiR=ly7^&b%elX=omNf)a)=tm~ZW9(TL z8iqRiQ*Alt3%M^srx?y0lJsK_JneA0&G813F5@kwF@bY;{=NI9` z9S6O#){_$|wtW#A*l@CUy;_4rxB0iX;pI5CH4kuGYI^Y}t$oysoU3D@xlQ7i&31+YHLv(qhp$ETmslTsp;D$iO27J4{a7SrAQ4W32#j! z&KN%J&3$Gx;-wKg&WNRITqXsh#>BW;)q3!oK|K`@SB-k#4z-@(4$nD>Q#p!BV%5}p z4_1Z4j1X9?*Q@SGVu5iYEH@fZsu~ZB&{};Dm(4{DTXDUrW$(3Wr;|LLF>)_&{Cq_! zm-QrAueZHVJC4{ z^$4Ls=p`fg7!QqTl#n%%8gvj@4&%?`Y;JIdY|rEhgaj=IVil=DZND++HrBsIzR;1D zqzFxU!#LiBZ?o&1RYd9Qb7FcSL2%M-4dh>17h=p!GA(D&IOgEs;96;RHO&hlET*`| zL8Ii~#=A@pbOG)1eLVA4%X2if*cIkyNU?d++%b)d6s@J{dW#fM2n3r8g>OR&c?~Jb z?HE4DRr$%6)7Y_R|B-`dP z50H-Jhk;PHyDbG7Y{!1T9T!`b5~T$y*5SHF!QDnKa0Uo(HVAmEV9z1^&|1y1C0QSt zV;N>iXa!y}z?ebG8p4Oz%yjFlHg@L8@Avwhdeerhv377%72yIKEbpTrK(%puG~HQj z^@}Y`x579Zyo=*2H8iW;J!tMOtI#P62Mv5)u$VPHbxPPrMcP>n0W;hM3YA%R+L5Vv zbIW5A*^H~u0R`+xn+*}hwcSpsD$<;8^P5VuXYbmKdHd2_Z%-H` zADR)9v8|!uLx>P?-qqH1BSj~RyY^oHYEduP@oiHtUtALNG+$m|xmctUTcF_TNKh0E zmg9?2K`5lLaN!Rxg5XYLTHroK)d;JCA_PYDO(p)$so1wbH3>WWr*$DmByUvTiq1-m zT7;>63}}WtC782`_&@fxP6YN!pVA%PsDWjyY&q`?bqU}_x6Rr>cdB_u-_z$zT18~g z=0uCI?d3)@Qg=jFgzpN|W&u-s62~DO_bw;AUfbkFha_4>idju^_cDLv%?HJRID-{wfHfrno3&LIA89Y-H)B|w+52)dNW}C1Xsb$C6^|< z^7jTfBkSPr{_svoKRCFS=vLkN`D81XtwmLjepeSW#pBM*A){$O#s}Xe;72BrjP2lh zfwlVAaSikO#;prub5XFk?pW>g&t2SfoLMHvt_-#05azvjGrZTNLd1HYADt5xv>_O} z(-NSmvsi5@wf$4G+AXRe^bE7Tmg4QQOp0g^UIkKHFHcoEU}XMpj;t)#EmFN7ruQ7NIKR_cjc!Q{i!^h-8Tcb?^&z zEs$px*VgZNn1sE1Xn1QLe|M8{+Y!h?p4G+=RNc;*JV5kNy}xV&(sK`=(bW+W!agk5 zT~^#JM~Y1hhy5BFr<2otq0IbJ+f{<`L9I5?3y}#V?cCnyCyiM+U39nIZ^r~FRBtpd z=~TN^iCurFdklHe5>T+01#+}vtTYC35Xt8HWv|z+FuJ-i!L?t~gx4^q`7?SKu9~>c zU9({nVk_w6JiX3lrSE*x+6E68JJghkM!mwmO?>Yi&*Wo+HdxZe)+4}1cvOcZew%y1 z?p6os=RR{gTc{5B7EzFQT^dmLk=J_z=UV1+&wgJ-oPw49jYs<{U6{)>0vc_R# zHIfe^nV(eRygB!3gTyi?t5ZecT%W$%1)K^B4?cq^%s-ojo`zydV+r_2Xr^#)Mj?b) z7+F+>i5aS0xhSw;dp4=yPcFu27r|8M#rM1vw~z7ozl3gW0ZRzxu)D9F}d>_OO5p3H!f=eTHIHDt8?nJtIOOTFlJ?t==;^WA8(=`Qyo1 zY23@R(=o*l-t3N!!wyAl%IVXUPUI1u)A@X~vtg1Dc1MEokCC4Ei`bfrZ<9}9x6&jA zpm{{gWey9ZY~4oI)Eb;OA)~e=GiCMBKLQ@t9N(e0NcPfCYd2yEr06IjHC#gEu~w+2 zbr!|C*Do1#*1SLJgOztC^|7Zv&RS6VTHm&Ca-=K_!@5>j{h&87)r^^As^`Yh- z+w2Vh8MKBo;1wf=iupLMua$<7xW+@TTVP9KeQMkjzsSKs$P^Faw3W$7Nbdspf?Geg zjeD7zR3}*2@N+E6tLA0*&)OZ;_8p1!05427I~#LBOStstB8XNYWoYOqN_Vn4nxvKF z$?a0~N7IjQjy-MEq7TxZCr-0^q>RND+=7yb7AT!{D3@r;)ur25huq;9%@7Q9U}kch zLvJzv5&!VGp4RGrLe#GcZ{{4PYCm;1HY>PfFXE$y4Qsu@x>4W6#)f<51Ku<9IcmS` zzz3;yKpG`P(l03-hlq4kUg({aJAt*CgHfx%c0IX&F8^f@i7^HpMna=>p6hLyjJcDMziM%Kc?>dy9 z)j4`3y?$*@K`cKOTXB68$wp)G*Aw3PH zffwP=aCh!CU%Yzr%q_VszyuJtoTd9A21Xn^Yd;^@wx3kC(V8~OTEE&_0lwNQM;wR) zhJ0Q;TG;ojT90n2`Si*S<$NZ3|JG}RdSV}pOBIR;nNioGdtUr{JRT=ch6$xi7>KT3 zif4GFHitt|WPVFwE{?u~`(EqFv65#vPa zv%&S5(dy~o>RGf{kJ5oe8`++@_XiGhUm)(c=(i9;lH{v$UR4nH0sa zpQFH2*jr|F!I%X8TS~pSp!t2>OZ%d6jhVNG=I%b$Uv*$R=4A&hu-19bd7Tl} z8R2%w8>W*?UW|#UKl{uJ7iEGv8eq`u;F`K8Vg+kt$1+-rFvpigV?ovi&M()bS!5jG zpy6OMV9ch6JN6Li#vb#l19ROHD}j5Xy+JZIDQo%^uw=++fNIPYe~ zhRd6ZovlQrJr<=Rb&4~|0^4#N80-ZxrD}*IV8ovlaZVU7R?Q@LsQt`MycR$4*ZPSX72G{|3t`WFdg(8v#7K}bC--$Eu@QiXqsriKVriO04hqOLzU$Yv4#vtdH z-0UjX8P1vQFsx!@>=!s;(J>FF>yMpJM3wkGCXW6tXi)ukL$HcPu5oHJ0Jlb;(*@Iz z#R>UQDDi??F&b*4;{6x}uf@VOh_8D}Y)SZp!Re>X0u%leBPeI9SVLFEiNkdVtW6+s z+_+rQxxg3ooP(g}*z9i+NVUefK6zpSf9euAD3qf&a{fH>{lS||Mv|whPSSWCJJ9YB zO8LV5Ph$C$7OUmPBxkHEJ~&^C)%Tp|#8#h_X-M-8+?%%05@gCwIAez!yvv|ODvoIV zdkcz^2Rk+`>gR`X095&YZz#v0GIe?07n&ezih0SI0ya5?Frfeo>|^X_D7YFYD_f9m2g&q zjJTpzi%;~35MNTY8UQ07XQ$S5S+#qU6@%IK%$R$`U2eZ3$~o2?cd@E6&KFg%H-Jz zQr5;?&OfriM$EX8C(4BU!L=K2dl6gnf$ihBeY3dB8F0 zpe2Wj0nyD7cnc=K;$7~9P#&iS{*~E~P z(pL_Yj3c&ln~_!OVdr<_qAM~wmDA6g2mCSWulF4~kVpsvc8M-hliE-NtMQsU+@A;o zgOtflH)j;}%0rFd@U%_uH@mUsBVOYH|l76hi|bMR3B%(I<@Mz z>#Eer;<_*WvL*%es6y#=YHP}+AA$~ALA-b;4)59P47Y$g2#+n#6+}JOLUgTX{~K{m0l9z2s+uMCTdKdjDytKK8fuoR9icMkcO{4#yL$p`k^KaHEIC~< z0z7|Sk0wtTnK{M!J@als7K+Q}wx(kt-Rkuf<|zLSNbl1%5;q2(8o7| zyT#{@}N5}%%r~?l8Zo@-#dU_gL(uTcVj%C zF62v;*js;kASr!!-M%EnbX^te^riZ1A*yEmV{6dlID{F|-V!}~GqN_psHm%Iw)2tE zJZkE>!5Q7@Y=hEJjqu}V`{KV|XUp$%Q)4vNSOJoUXP^L48)s1_J}ZM*-LNA-d{%}g zEQw-N&i+N~EVoX*QddZi`}U}1Cak+eKv#hs`m?xlrsKNlq3`97d8t^gp=#^0ub1C! zqq^^zTsdPMqbQpO<8ZlJdq0@kV7jbVgB%-X=CLmHVsn4tG`@7X`a(3Oecb0ZH20)l zf?{JI7uN-1TouFxM&V!TzD654qyF7AdWI$^azx2~^`g8E5J6uhT`h_>FHA5ceRsh( zJA`Ar_U38Qj}{DgHXop49JzD zpxX&t88;E+O767ztWU+x6!PhvE~M>r7(avF^0$BYF5eX_VwH~KwW9`W3^a&-Ouvkl zS(*9mkF=FSqE$dx`K=71s8gm1U&8&=1AgNR#cO6XlKIFaD(|iGm{;F)7P)5;s~i@4 z!ksX+$h2cO4L1!X7Qb66{&};2YYI@M(YDRFzFU))nhdoB2PzV(-f0Z9hBLtG`*Rv< zzntSI?Eb+qPbc}l{3!$<|G#cYkf;bUEqBWs7+$_v(abLrrYJilH4xUtHM$$o>un{=SFO-2q-g0MIfYPlEs81 zpfjb&TJ-3wd4kz11o}F3@#lXxacbDlCvVaT37%yCTN=`_rQ4>V?muJ&aAsVn&L*lU zSm)LEiZ8JW6w^1Plu}C62s=MZgdpGL>JxpQ6Rt%^2H^D;du#<}udp5)ytT+b?$DH^ z&Jk4qBQasYU8V~{Ks^FOEHRi}7!EyL&Aw==kI;v6f9hdKwS#PU?aI)HnkgPn2n$EP zV;U{teIqQ}&C%aR9_f}tW@}KEn+#uDQ79}N=p-!b zBKNmmcGKEUOky0I%=`DN_19f7m%H2&8o>U2HrCHC0O~u|9lG)@ORNc7m@RbIpExQB z4$C?!Zv5tUgVm&rxNYe&+ra7j21Pg2#K-sc?{ZmBTOQlALp5)nU3dNL?>A_(HLP|K ze~(W%bO0pG-j(#OB4`05o_(T*XJWm~V{B+W?Xat4J==b(e{JV&k6jMjrussmIqvw9 z4-4Zso8JnZMLrWX_J^raz2lWk)au8vxJVcFH-McOXn~$p2SA;<>^7;!{0S&OoaxDX zoth!3dt8*QkvS}}^;|bKe`UtS#$bQNIFU3sJ!jwCmC&=|s!3?MW%J<*yn}TTi{fIG zboPj3vj|N;m&zA8H9OR`yi>EXt3e`Avpf*ovfqXV&e(V7b`y&Tjy(C8)}zV-SyleG z@4W4=H%=gd&stza`yjVJiPV#UnnUxxMif3jg4-P`{!7S0-#5YwvzeGCJ-t6Ecl$;8 zPtTz7Dp2W+8(LJ`>BQqpKs$}!H^f9}52JkkT`8A?;7sgwF>ln+7PTJQ0;}Kg8NYj$ zQ;YqgmKXN3unq2?0eyHM=|Z$POwijpE%I%m#7)%MSrpjYC8bE(Tj4WuL-xN1$x*+~ zcrWdNsOBI%Os?*D4|>E#L~E z9JY&75j$168yb5sg!SK5Mj07pB$LcfQ|!rrY5O?elkE#RIFcQ@@0Ak-GFZ^iGBx+l zhoyiTnAx^>%3$a@Q3R9I<29Ax!)`$5WakHizM}Dnr=3;=3A%W8>CfsE)1E_wOD~x~ zkPnXIt{kZtvT*&0oxCV=YCU2t=6o|(vv-hZW(etPq^siQu3c-Tu4v71MdmkLxJ^>n zt2{`e$ZHM2a$~a>Qr5@OG zUERQpxLF*RRs~_CC?hT-g04|_EXBYO445JcjUbB>S;La9xJhFs_RN{b2rNtx78e*6 z%PYrxzg1miXLue~KF13Vkc9!jz3*Gi7{&ZlHn8G!BQO9(f)V#_CrE{K+|u88Fb5h^ zupbME?dc@8W#}U7(R){|))3HQT*MqxA4%5hL(-F9>W1X2WRT_ zaV-o8CFp9SccQBVcH6H-5uPYoL22)VnfIN}Gat6^m)P6b@WlaX@naSk7kK&%N{uFk zjdhkz!(gtfo{8X?2pqlllNO8q3GgCO4X6dF&QN zm|v7b$RgwxTdwB`!0kIR01IN@55MB$7KW7&54v^8%QFWw4E*z)Vi8`CS)tTRJpdEs zH*atnEaIbp;W@bI@>Ox%agevwT6jr3Lz^zw@1l0W&^wx}rhF;gpk3bA_RPUJVezL} zz+n6kJStwnO0%oT^>;0@_4XoE`JVp6&ikX^_@?NjdO9{5O216N!G&)^9*VK=ZV02SRK6H4bi z%Z2Y*2Gwn)@S*i+j;5a~1-MZ1QT{GOB^4zO~uMYp>jzUu59T z`|FGPMjQqht@ZyO%ifEQ-gW*}jB|=1mXCSeU}-<~0fBCiYUuot>pDwOaAvcM^!w4# zT90K*($F=Vv=lL;MxK0Hv!wHvviZodGiP^7Iw(%BdEKbwCR!XMuZ<0DG-%i#?~9f? zclgr(4*L2=9+Lr7OhhMT;CO;}S@@kCzG}G7X z1jrx9*Xbg-{$^x56Dd8%pRI_^n$+qAqSKCm9O^N-==rX}2^{{|VP5Os;XKw{RtfZ) zz6oT-wb7nw&UW?VymoJT@wFS^>Wd+U_Y__4;;~qN&dtR-rX`=UI-@teJ}_U6UyH#` z4an|Rb@jx=^fbNIcaf8iQ$s(Tn`Su~pS=xAS}@sI{nHjju=9}DvN zA<^$Mu(?zTz*Nn>tks7p2B!Ph*ur$55yWFeVGrrBcE2B8pZu!{WZtr)`;3zryQc3M zVXXyS_gzuMwQ6p z!0)S0V5=@F*Xgxu6xXFQ!c37R;)y%#*>Vl4bBO{B#$VoeVKwDby=c!U+tC?admj6( zd7oc`*1RKk*h`zU`zXEnzRsD6??Aza9?)~rFB$%bfDz!3fQuQiwY@@e&!^>{h`n%t zN`H>+mvNY5ksYxu5Wg6LwpM#?x;?>@$0GC3ALPVVu*OC_#I%QojAoaj_K{6u(u^$_ zj$~4h7y$jpa+%oFK1DR;NH4I;1l5-es)M@l&&2by@BdwT62Wxp{w8QI^-v`1O?~o|GUFn?S?8;fUf-sr>iP^E8q4965Nk(}>{L zi%6C?t|Y@7HHU5ZK^Vd1Ut?xzay0zZQH!}CW&@It4?C{~eveL;!;Mg{R%BCd3pNP+qnlE9RZ~-UmCo;bxyppCpXx7SYyjA};4^P5jH35&N144TzHyvCO63+3OimRV0NVn1C-LEJeS`KrfukyW zkk5$tzI(gfv@Rwr45fl(J-%FuLXbWME8lz(-iC_rcU(el{|hnvlNwf9wwxz=zx?7$ z4g4{<7W=1w{Ya*k3e;B{1B`>0UvdZ(AB!d1`GpEl$Tu;!Nl=4}Of)6GY6HB8F(AO0 zM0Ve)HtO6BHk%|is~Vjp2k_)i-j9i#6L(-W*B)COgM&{&LrlIq#D&t^Bk$f`u0TFy z6s=MY#Tq9nKcv^AkOyEE6kMA-%W!Y58CYjkm7@&abig0v<)YMAa*u25ZLZOPs!FF{ z^z;F+JLUo>+COFp`y5a7Tidli+1sMbh{PV9M;|UPY)z12b{(qDB>vv77z$2@1BeW0 z{?6_>bkTWxCgU`-DpSf9gU;en&b!E!-?lvoZBrs2uK9RWq8UToqrDD2`Tj9dDlR!$ zYbU5bT9+WP8pMiR&mcl}XBv#TaI0+wK`7^wl^0omk?5*;9uTIS`i9L;7|6;C(Yrb(+83~JHQTlH%Sju-vH#E2 z0AWC$zhIC8=M?eBs{Y|_bcP{8iD5Cmh&32Bh(zo*bj_Fj7bRskf90I2IceEl5|iEW zmBH8j|D&R^8%HO;U;hlu_sX|C+7DkZJub-zoc!ti@q%3ciDQeYTglubo%`aP-o7_L zVLh%*sG}n+$0+{cymaJ0T|G$iHSESHV$4eg1<{?^;*b#qIOkc{Yi%A1i)|a6`dKB)D5Fy~j<^ z4*wIdzg>Jky&r%&;=vCi=$3*9-&bL$JY8?vK$!QDH0f}}8qC^tJntn&lNgc*XWMH} zv^(?iz%_%5uW_vs?EL3LGh&f)Cs{TFibgG%S;K{;-77Ua$QgtdMT}~{)~&1g$Jh3- zX+1cVdK8VfJ7QVCHx#A(`rkujSaoFoTHU>I7lmfdTAbI@m3m^mNmgGS8Gd}aJ}fCV zElSRptUoWzKX`cMwRQG`Gy{K@&D*eXy}{zHg^KR z0kUGaUD-zFc@WB>lMGduQKgPkp+2#aF2-nB8-}hw>h`5a&TxDfwyNl{fJ?Pome8M^ zK=nAe70wVtkkH=AFJ+X7G}{D7@mOXEBj1;8$gG?#SE+mfn{c-zps396ES<52VbGt@ znjY)C1Uf0DAIn;qU6kAam)5hqb&Rqc;U8t3%;fKRC~O9Fy}6-ou;8<@$AHFck^}XK z6T>XI5=RA}fr4bKJlE}gsHB2N^T#6w_9+NDVwU)1OAgA2SWX>C3^LTcZubSEU9ca$ zt`VTo?q*%Wkb0ogSfcj^rOTt^WAgPb_-+iH-98H>oG~Hk+O=SZePKs@H?I2G8uY?O zf1-|xZg?k{D-}YaUv1=Ii$4Whgc?kRV!{gUD7uofi}yGby|PF4-ri+hydIO0SgG8M zaT~a`p>HyM4Uo39eY-k3CCV8R^K`WwcNsQejff}pO?qU zNA~Yjr3mG4CfvPCq@P!oMgFosX{DRLx8xs{2QGiZ+7P$pXZd-Fh$~J`Omz8by7)ZS zwrEF7ZD1)u$}chOJ>=F%j!!-weom(rW#4?E$#$m%8zY<2WP|q z<4%6&F>FkCf2@5O;B5;yUh0Teg7|2 z35VxQHsTA<-9Ee3DyWU;fmipkk{rF^CwM{f%kYU6GZBrsV0N_#o9N!}q~C=%ZY{&n z1HV$MJoditcisKnfHBbyg50DBOGmuFvu3geTTS|%cA|dWgF&Y&d@Kh=>7Xw{tQ7UO zRQ2d(T30DDp;x%*CBypde&58+tiSh(_Q_D(tdl3*yyJVjF(Iif|MG!tYx2fJ*BYSX zZjX)(z_RalkRvB-o)pkt1D)wCt1_D9?%pk|pqm_?-^q4ys2%IXytH1-C-Vj&cRR)2 z)X>ZBZKaqG>z^{0i?aRYIgQdm`<##2as7rx+(%TO(0wQQtBoTgv?MfE!oew@mrc%c z1hDhKi0syElzcWBy}<78bP{!EeZa9HLHnv7xR-K^u(9iLYlDv;twi15(fzHy6+9M) zIMQ#K5s*mh9_6E=eartet^Lan(8;@|y!^BKRwKotyQk?!(U)(-C;ue>1}f3ENPhXz zXrihjYh$deiFtR9W(f{b)*Q$Oem}}+gRXws-d&?I5FWQIMV{XSZbeIrTlq2E@?(5x z_g>PhXi8rn72`g=ah#;JvTtt?A8#I@r}4dZ(B7}yd+s}D*V6`@qC)2MCkb<@I(T=} z`&XGV(%lB}Rp)sW7Ko5dBQ*dI@9iKY+z_BAiMhNcf26~)(b8-iN8Ajg9ERv7x*HGj ze10WU{exIs9qq{;yw-F8?_!-xv1X4P>E1yRZmBnS5Of?#darSjWS{mhn(vq3X#L}B z;SdAd$f(+OZT;QoNyOIX+cmO9rI{{ws@Xx*+!<)NS_?>$Oo z^oA{UO}))-M#j{1H z0&wSqcXN1T(knX)1!rCxk2_lpqYK0LwB163?u&2Y>rcs8khJhhdng;1i^-#FMYgN( zu#fvY%G%?$Z?*HU8%-W=-Onh>OPcnUd!Nq2(Xb*MfmqrNuGp~s{pPpO_yaxGLg#o4 z3KD)#9kZLkfKNi}1rNoB_1={Xq1STW5waGWNKD_NNps0^Y%-2+o+QqO%&_u1=W#UL zyS%zD#A|3qYS=v)eb~|0SuMOn*g=~1FPHi)eqyPUVrX`@tf*KPo7_aYBX&=sZZLQ1 z41%M#&QWdT zj$OePo6rw=XfvlzA4Tgy83OIODa4|J@%yLJt^Oa;Xr%C4-J}w=nt96WIKcVG^39+_ z@``#lPfh*~=(Nz|m9CEX#wJjpneOeGKFNEnhd(okm;!C~%0wtK%tQuZSp`*G{)c$t zJ$bwF;@<1|2bA|4A&p={ce8P3JcwDKpq-|tSLc5tqKJ!UP7!`WWG?f)$T@&l5^;9~ z6Q;Tr%M|n*Q*(`q($-+(GDDEXiOZQHLa)~uMb*tm?Ji#Rt7` z7~dM!iWKFst?!wb#)|!0!K_EhOp=M9KtF-J(4jfSUd{EM>WT*I3Y=s)L_FZtti_8QTnqaox5)F@%(t ztwM=b;Ar5efq{nipoJ`~by3=AXL54S@}Lnc>O)`ZPYOGeF2img7UD>SeQlIwGmcpI zKS=B1s%Y(f2AXWyH4vlAq8apOwf_6KNV0(*(iMCi!uRgrGXL~!h#CbM-H6~{TWzr2 z(>?>qv3oMMv_wQtjgywn{Z)E2x3BWO$&cMOrJ}zu`<he3Aut{zHkJ+Ix$RW{~a;QtLwqZQ3X#Gh3 zW*V2na&K$@@BFhKA?Iwl;#=`axLUSocap3TDNc-oesVy1pgfk~ZQyTP~ zxf6WaktVO76i&_KruKntMZsrW`wfs?{y?j2ky&vqw^DTuGl=KMnxoRiV*I1mcYd&o zthD{Rs&V)Z%~h0lTVs5QHfU&L)NoWno-t1%8wcCsL&=9L12AlO#QXFExQsn`=$7V% z=6Qdn*8?;E96mGJxY|>QX3PV*Q+TEqFcC#+I|nVHqJtSmRH_fK>ze*t)-OLOLY@h#^`hQ;TGbIZwOJeuilhoO+f z<8n+O(RcvNtPk|PGMHpj`vHY!>-1m#4#9tXA$$wH#ARpZ7NnQA={3Q&98q_IhdRFw z}M>~?>O5^T$)m7wbu}#t|L1~GDD%zbgWC>xG|@_RUD(-4Z)!J zZI?3YIXhxuPINvDklQ(lFzZreYHE! zrNay9&ks(bJs>52DBa%Ut8b>}vw<19SMw8DlpqWdz%r-Gz)7?K(z=%wmDesZKqAmM zfbM>a$!EJh5_CCA2FETk{2?$-LJ@5c3J>$S8$kpm#v{e?ej*(?%C99?aG4}Hgol`t zmV^Rv*9S0d=eIt6pfeRs!%TuWmO*oENr-!hXPH_3#ND56SR#p4@J(Lo+2^M3Sm(FYl;tFE&W?u0#EqRd@wTQKS<& zeYpMh+xhX4Vv&2tW*avKKuqC9DB5Cgo}MFl!69?v`lx`!_S{TrhApQ75)7|g$1hw= zPS9wEZ=mul!bM%9ssiLK6j*sZ{TtevR<~V~FviqVjb^VOUM=mwOGA=et`@Xy4VsuK zdh{$ylLe9cLXG%2b>Usg_lN^}ySX!LC1M)Ot)}tr^Uoz-ke$IPS)>Udd~pFg{SZ!h zEa)sRDo+^OIbXT=Ih>ukFBDwa7}~;S!of-m@B9wI9+sr73WSsWvS|g_4Bp49Y)Wae zEA`!&jCqGn+3D@l>SLh|&o!2co*Q08+x*ZT^v3CL*F54AxpN=N78DgmY?EjD-C(tv zaUnu2rIxg5AiY^2u^6xEZ~D-9>`cnr6LIh=0=hDc1kJ4Qv)lZss~2uP29T+;E!kM<1d zf2=h7tY`oD(`2uuu>7qcOPnTFwk{JE!t#{Ct;YfTaPm8MUx`cXiq57(A~(M*F6~I$ zxaU&ySXblGX}9|!1*~l_(ONp^GYuJq1h%QV7C&tjcdtZvqwuDIIEC7ek`}7XELR&; zv&mfSf5tVI}kJ-Kq6cAQM;Ej#q1`cci)`2fF6GfZ}eXKt_`XE<;eH2VgxfIFB zx;Oc8Dg_mw&z1QrV4`}FfHdkd8H2SZ-C&Xy)>0_%*|nuY>}x2>p2c%G(PHgfd1y#D z^Sl!ytFp5bZP_}0GeNy-YuV+|(E6)}p^(%9J)x;@{r8}^ypXse-Y%%EkEVo(^(rdY zSR6VfMnZCNBqe5rXspFsp493I%kV-mX_`!A@encj*zlndQPF41;cz^32dT-M-w z@d?=bx#{~m%jB|q@G|tx(9WIn8*mGfM`ccpoVY623>me#-kmgQWTuwo^7#V30mt=U z2>zkG|9a(gvGloBO5Q_Mv@BQ@hLWqni!YKr1ep~vmv`JV5sbl`gB6VxtA~q>fV-S+ zWOGv=tK9$TayBllz5IoX`4xadOK0Z4s>m>ax5Fs6#A9(kJg8_nbAo)gkoNCa=9cwU z?LA=7cBK1D9GnexTMT<^OEQ{)Sco#c0{k$ySQ!C&azX!QB!7oY=(RCvuerDE^_quk zOHOSvmu`);#@^R7omGq_n*M>kA!AieWQ>gmA%&XA^D3o;XkHhB3OIoc=N-GazrhA+O35x)>!80A(7yaWKrx$B+b#{t=C=LnS2rZvt8oWI zZcW+Bm3z?V^iF!c+^iY*dpxuBIQ}hbF<(#?gIsUtWdI3pBS^4QWUda@K_D;6$gd${ zn~Gev_Co1^-JU>>;{VgoK5)84Fch>&MdmC>$T?6Z8j0UlvXPh%^JV^Wtt-Vd8d5YN zN6joK6b2vr~TAZ|%lD zTI)aCxJoqIc43q&Rc7m6bb;4*ul)-oKDtwF@$4YOD9&oTd52kisR#*HP>D>0BBN;O zL>3C{wxo5MRlg4)m!0S)`3l6Kc}gM@`t)saad9u-iyktEok^Px((s! zI3dfr_XS+Q=VAYOh`pZ3+OLuRda3rdJ3o)>vk?5oXa1I$FQV;-8&0R^d80?Ufe^s> zZj|{#Zp+pFm-nr?<>s3p5P6}_xj7vqGLv8CKLh2;v1GujOVN)hZ0_n6lsxvmz{rrtQIXuj{=)=WT{er?$_^_PjcQ zj~7qEP4k}HWjQfX{&r(V3j|Rjb!6EYaVx^pENV6`O~p;>em(9=uZRUBHIdx1K;N>jn%MP?|4<*#?uoya?bt<>Y!dmsxT7HW7_=A7V z?w)O#W+Jg|ecl;j8fLeeNdIAmzw+9sACb@KCvdHEpiRqkzSD%fH=79{SK!?Ztrm^{Zm+a!sQ8TP-nW`=vr; zU>0zoh3;Q9WPKo{L!JtqJ5+GxiTF)SvR5|Wr_(L9^up_3+{d}y$BtppAd&O?op9>a zxXn}wq~33wL@^gcGy@gtly5ZY1g4#8UY|IHLXotbxW%M=FP{}_(Z=2p@iq&l%znR@ z@?o2n1gIjYu#~-fWr&-lv=YsZBig&76yF=`CX&YP4F)KKUFc4IlfDsX;la^`Ejzpc zUc1FQHUk8VR>*Mzq5Li_Z_*MaLyc7Cook19N0;i7Ee;0gSon(5viLS*qO~_zrd~te zyl(5~AgOOz>bL8xYh|nc?GI_WVPJXdy3OO`9o?@J;I>}z;jlysXxo0FA=<1BW6F(Q z*oym5w51Q6y4SwAjEksmx7`e43SSh6QRdiQazIwQ5tRKIKA+qiUlDEYwA|PTTd{;H zuQx}ct_w`3|CMFo#8od?B#r~CU)c3>o<3!x;{#0{3C!4d=Ud@-4eT1wwJGSZ#2_lr zn!%-XiB#F{NM56}Fe`|MHsnYhQ^wW+3CmPzkhSs8`#yZ>``Vei%jjKv(k}Td6GDi? zVLtSVX=h<5Z5*Gy{bhn=f-+X29GpT?@^XDQxmh56_7J>V^I;5f_dKnSFyPwBTlIn62C z1>#3vy=wxwd>=A0vVYy$3~pVGQ0+pE$AN{R-dfL(H%a>h$rq(YEKbDo>kP?FNhce& zzhT8zUe~=~8xSUQVm9pITaPC)fxqZ2YffxOOyZC*k- z0|pFcLr{lQ^F3CCez&rM-(%)8-Aok`!5-ZB`6P-6yO4X*sLlisTaoEVBf-LkN8Pa3 zz*a-Pf`e_t{^n~N3f+V-FWS{61K%-h66Nkwx!%*mEe(vQD>w<;(?iXF^oon%xN2>rNeDVUx9_nmDgQfu-J45G6!}+M%XnrkSSH8Go8Rp zb5v!AEDhE|*2YIA66wFXn+&ymo1I`aSr`H%k>)y-yxJiz`&68Q#%cdih*?fo#NCKQ z2%b?-o37@VZ!G`ehMYoAv$lfEs18gk>J(=q9$(BlxB`k~Oa`%Y>t#r6{tn{w{aHaJ z+^@#8=pv3#og-Csg39Z=)+N>{OjhrR3B z&e+>Wjjr%y8C`#9*DcS`?SFb3@77#+^jdrTN^WUAk|BJ9toC(&#K^m$i-7@MU`&ew zq02Chzz9s9iaPVDW}N_*y%C2~Yg;18fggR!ADdXO{|3`eT3R=l2#r1Dy4D z<{WR#L~P^yI%CGLfk#$dewjgWT(Awn*teY-;!Erd%x&j-CBkSf?R7NG=vstfUwn%i zy(Mn0I&}<-;|s-}Nfrw~rKMf8e_Bv)9H;Z?h~;6~_8AnDKodtU@$~&n3p*I)_nS2( zzBL1F&xu#&C13<4(OtiB7+6OwhOTQ{c)Tpw5R?hJF1;18&1e{MNh03X)mfyDD8-nk z3BQ&~01yuH+C45$d`0lJSz$`)GJocQ@`QSa<>>+kj{bh|O}Fpm1U@T&CcEGYB-4bB*mhk0VoHK!B5gF+DPiI}Ww~$$nbL6S3SNDgC zrpGUupR8oku&7+G34pR;EqK;<#w7q7MmB`3jp#23d@re;Z6UvX8Qkcy<#kG%?%Z0Z zaj&iBmc5vPJ&$kf)mUp1_C5zN&`yLz@$SnM-2RFEzC^fQpm1)?25ymGlzZk5S5))k z_YkyERlo+3dnWF4brS3o+fi+FeGPNLx$@?-ixq&$J{lOd8hZ$2PR%P) zdBw^I>=Kgq@=PI8M4LtBQ`kkk5QC`vPgaaRnh`kvkt4{r%y9STf`2Pt{duWCg}1;k z+Ge5xgq{Y$1N26{m&a5)F$a%8Fzid93E|JT^!ysn$AbrtP=~xn{knj&UtaZDnfn>w z%nRJkEum~(h0jgaGWPBEyU!p;HgH>jhU$2z1L3rYoz6c_>~98+Dmh&u^KtgeQ+(x_ zby1_$L2)yJu_!R)Dp*-xwM%hd`wEw2-}cW6#_ zlTgkKU2$TLKC=B0KkoVLJ^N7u77zL3^phiHhx3%p zML})bF4c~Dpm4Axms32oQ7d(ib+Xh^9)59FYk^#}xg-VuOraApmZIu?Tu9q-XI+?ry?yW>1+wQ^XX=op@UbKOL{vQSH^EU(||g86vT0E+9##w2=4suvtFN zmuBOA(qiB(YczulNt3Qy5s)YtUebb>zt06oX<7;_eCasm+A|LR={9%Z&{B(^|A*xt z2VVY~R!{fqhQ{Uscj^Dsg9BoZvvr}^B{4-?Er>BEMQKB*;rQBU@10IC>2&*N53U#hjDV7@O!*y-lbLCtS- zd^B?i08&in@Lg_T?+hf*D`oAacur3r=wmHmPUn-3#Qopf-!vwzBf4A5kf$x?cFeo% zk$>ll3k3K13Lj1c{6Z z6jkIva1^v=s!~}fae9)v#5M@eE zygOdRzj=x&3FCXZ`X{YeYG3Z?b%}?DW25VEOhL^Vt+c0ee=sQbmD_PP`iQVPO*QHD zSE=u6KCdD%@etE;)V9F7vChveDisvaM7pVL<@cwgT!@G{QjkFIx8u074oE{iwb8e5 zh=D}ZE4*O+ojg0Wj~mOc73B7mg>npTJSEL=x?H~73$0y0gq&CyRsp>Ux?0cUO1G?? zPU@6S3EJH8O*n7=vud6p!#SbLzOV8=e5Us)=xz4aQ!WlZip8mY3$6EBcsbhTJfvYM zusq7!BV{c4{N2XFZuq29lCV^lXi~%06+=pMQ*@y$pnhB|f9@R`N8SaH+f#IcU7nZL z@C^8OI;@Rocb!=;^+#sJ6j?kA(zi~d9E#FHln0UFr!&SpySxOhRid}FWfkl;jon_{ z4F&=-vJ>4{5UXe{v7lIO-NhBWU5bNu@i|I4_f=#5CymJ!C(+Oj_|yDPBlr z&ybw3A8CWb{}w@R4pKIsIvPuy{e?o2dM4o~&N~IDMzl}UJ5mq2_x4JVtV)}yvam6{ z3+~bluCH0;7`sjR=6yj>E_<;<0edaIcd;*Wd_MltW) zV$=x#`2xO^5-$1^(4m@8_a>@qM7cO7->iFg69LP9fb&>o=2G(EI|Yzue&lO;hK?KR z--dGE<9<7_f8)+^KZ=_^*tIAH18L_`wgU2_^PV;PmJU|vhblL{R%mieH8zIy(XnGD zv{n8;U`UacCH@qX%)(4Tg=!M;vuJLB2{c(6m$e&V?&#F?vc;gr3I#}2mpKqvkL2Y2c+x&y ziiu^7Nz1qM5%52-EbOSU;Q0mvsl5eBZ!%$jRU;|ISc_s-$K+hIpFjH+6vw`m5!#sJ9?HzCu{Zz zCW4qn^=t;eeviyg`Wql`XTtVqaxhQ$94>VBlIIv-XE~6H{6;Pn{8lpOH^1M60UrKq zlSe}?78y2t#BMhO0ZpA^QV=33tO6v!{#~2M20Ryp=&m2+HLPW-Y9>xK;d#|0-Vd z8%cu=NlayJpM~!2shz9vuY%c=T4|UUM3)}Y#4QXA%sAS-mAJ<28UOLZ>4g_j%n##B zZ`$?9p^dvUfHxh4A0+oeb^)_aXgI6xlvvv{%bV$ySk7`+sGatex2&;pY}*>GO{HSx zR;w)ryC5icU%{5L&D$&x(qhnI*+tEnFW#beaDV$cr9<>qMYL`5U!y8RLr>%Ec)U-WMnwCcd-<8_ zK$3T+N%sS{hnc9U_iEnVN~ssI^Hi~XT>cs(M6Q|@d!qCg(6$D?1n1sKL*S>|ZE3gR z-nW)@dZ^u&3v$zMcT&3{+~h!`p=dHOeMMNYvE)t^4!zsDH8dS{tp|sPx9PSy^}c0a zw%g?G1tGVNg)pB*g9^x@8~XLp%WA9B!AW3gM$g%T`5=vkQ#S3T1k^hJcI*$bW2d5L zYeQ0}iO4r9+-VwZB`XI~H1fanMcLyYB&N5uVqJyoFIBOhR!l@<3H@rycls%a0+Z>D z-cmrz^3RAdNOS$ge8YDyI~bTR5d|YM;5KA@cJv{N^XXq7U-KPvFSPlx1EF#{zay=- z60Tln#COf^*ZnBUq2zpc^^4I^26mW6HmO2OMpkyEU0pvg&FJE>v9&;>zDo`d8{0>g zwn+I)M~$|ufYOz)w5;y6U${JLJww47S+?&wA z4Dp~?;%GHffo&}ZiqKYID%J@>iL15->+(Yur*|Nx#Xw*tMJB#-spw3AnV1|ItU0ot zzINM2%J0Dpu!#uGbO;oIQ55OoQUJHEt&D(Hj9uf1`L0Ed(BY9>; zJ(`T>H-E+0KR%eH#@@K=I%7)9LLgn+LCV8$=<3F5&22Ps=9$tV9=(0VjQYOev!SLP zygV@dfNVJI8&{=NDPkz#Cl(J`<-wD3LHaNs4^$=R?|iYG&r83LpPrFJ9``~iDiC7H z|2)l$-A4(t&7BWS`+*OflZug>P9B+z>>4pQ$KlNrG?FtPu{^vRakTGmegPYpdj#Zy z{6Gwxmy}IiS7c$wt@?v!i)x{N4{fJOpETzVO31JGj80-F_C9kO&6Ezj+jG2 zHVs2_WbfmLP@#44b4!Zc5%b{lN?I0s^18xMsFP9unK;M0tFexDDju z2+kqUE-zbKtnBa%*a)vP&Y+Kvbo%7)SpSo(76*kE|8#9RQBgo*_`q*Dl%bb!|{lI}A$4?1zZQq-7t?q2? zR1%B1b~4R((4lcuY!WC`zD#TK*dfX|bQjtZ#>sFj!!b4_cg|Uv9EYQr{ymM?-mUeN z`Jqk6!3EuLWQR#C`SI;K4G$0#@06)!Gj2>7E@muD0mP&I5r&eAgxc`Fm%l#R{8c_G zM+cpK-Jp16(7~u1Z^0w`af*D+xkc^n#(sFa<-JH*#Fy03_CXxp5SnrMX#ut1SZ&qY z+Kt4akNZyvz>g^nE|RL-8U0W5PjvtULH$?t)OB9*ZVB>c|GCjWx@c4I4C;wf`805W z6z#w2)S}h4WOZU~v!*3vt#;U9yHpw?vt>aRBs8mtW(s+MCNW8>iIe4P2Ce=g~umQp|~(ZU~%Ot4o|Qk|Ne$tRc8 z1ykFJ35U$W79$YX!li;q?x)7o(gO4LqXXGZC#-bH1ep=uO6YhOJv1@dsVs@M*~iA2 zSfMY?)&s~0ToZo_#uBD)I{+jR)ZGNdxd}>vL^aha5TT%4Dy8g5jBX|nR3L71c5Cba zix4BD5+KkbK3~&ZhpY<9Ibtw5 zc<%2VSiubDuIf{Vrvc=+oI*7X8!(u3B1owP*=s-?t!R*hX*+hHXA}am7%>7~n4ws@ zGqs8~L<)iumCO`vi8F-N1#3uQvx)lKI;2*y&n(&AFa{`{0O*K#!ApPuu=(Ul;%n@-!52(Ha>v4y-EMN(5|>=! z@>-YRL(CRgbl_f31H4dFP#~>}z_k>Du!~Dh8Uut>a@fM6!&or^FsjJ6{~H-h%|$8% zikujdQkekN;tDWgO4(GW0kMf4Rx?ReyZXZY-a>7jdiGm(JTQsll|5bp(UWB|UPTo`+wHEkKB=WK&s##LSpnl3GR3T1rM@ z@*g>qu+B_-@da-fPp^oq$=GzK!zMala>*ucQ_VUZqYnc^O~k6n*Lu4ej4 ztO3`a>*SnXkpn$d!0b~Z3vSfAkr}1}Pxz9Hgs*PxCr(|?{keKkz92W2O?Mh_HCN9Z zS2SN*1#|I)9@1UzV4fAe8TSQK4tV&U@eiV;kNW^}i0kHBm@ZM9Dwj+CAWA92V7hQ} zJ5TQj?|$&?Om^@9#22X)6g7zPHMG<*#wj1kmRQgl=D)ZyxmjW@We0DYR@AEL@b@k% zpc^ZQ#3C9a78Dvnnyxah(LY&U5ae0}1!9Q+-Hp(l8--mYvF+sHDT+oDp+^uwl33$^K(#>w zkwCdsRJ1L85Ol^R)M@mwakc0TdG}6FlwWeg3+?t0748MOL2QEbNFi%@umB6CuqYwa zm+z9t>BPh)f+knwrD#A%4?O||q{Vy|F$zHRIfNg+U)qZ;+r9Ws3h3p|=-UU&CEdCG zRtF4F5<)PV#O)Xz+zXO8M5ri|klKWI3zN}=vNsVu!lNx#vCp}rKtu^SXDRKdk_B+a zCRXH7=?s^>6XAplI+-1IG?>IPb?C7RCOJT;*`@*;R-nCmju6h9rsBY*JrE*UA#0RB zs6K+D=MEZ7SFdu_OM$HYA38}2xP=GkO#AMO43HhR$Dp{-LOXu1 z!=^e7=z6;8Il!7w#afI&z*1uejt;qjv%^j9rDFdg7d>7I)qk*Z_J!%(2|Bc52a6AG ze(QTh(AKX!4JjIhQm2#IGO@y`SUU$6P@bYdY04}@VUFuKO68bBjLn&tnpD@tRxq-2 zZL+;1b*~(K%dbjnC*g@)7-k;n{rHwg9^X6~Q zM*H^LIPBtjY<0Fdk(`j_v9}a1Ufle5JWkRF%l@wB(kwrboWX}yd;!voZVJnCx6!(u zl#MD@T9Hb5S{B{PFiS;OLwnIUS4Zl$ze>EYBMUd!SYq3W-ULci(nIF zotG}c>c3^$mG$y_Mo;odSjeqicO={3-?}|=zCLw%LOCb7pgChb^wfX#h_}dCmugia z|99thIp?s9Sm!R`(YmF9LtE(xVg?w-fHi(Ynpo#m@k2i7*;rjjxSqM5muEG(u5>*q zi$>SBdf-yCaw+eWwX3JJuo>4-S{mB_9OyS*C)TDLT161V3^0t5_dAF5%T~oP#(t-8 zz4KmLICZFM1~-UL>4)vZ!Kh~7kf!bjdZL%qM@}f0L*BAKl@Pj_yAA$#SwrW3=5E6Z z*j~^#ne>2aW?JgzR876_+;(UjWRLZ!vAQG7JstnB_D^8&#H6y*dFVPlGtxA3GNu2Y zjIjX;4)*b{cF6Bj9V$88wqbYFu*1VxyE8{mj-uyA!=y5P>^FXU`Q*#s>9PT|cJ|vg zS-n}6&NV$**aA!3bjY3BPVFDD6Wa?bM;ZK$<>O>5(xvW%LBtftkFtJ%FJ@@k?jbT6KmHwiyl?QNPq2l`=3A zFB*@eC&<$#DZw)4H1}X>)CP)<1t+^^Y{$6SvlVpnfGFYA942grGn|#2d|5kcj++l* zrjiNU`mXEi4V)Ct6D8|r1u`}f^t|nnsq|CM|CO26r5YP#11UMx zQX$r90pKQ!RupjBV>+xyYnw@F$_viCqJYx0iV-%IoSr1u5YB}DC>Z-uWXUw;G-3EZ z(F;+Kw}lQpG#C^qSO$kteW8}0NsAJC1}vG`BR5mOmNexKw1ry@bP}r-MhqTWeKF31 zu>;5i-Bd8r2Gx5AN$3K3k)4g%2T<>Lv-q| zGeA{g_{bb5`e9s`%>2-}XKC%HmRF_Fm#qKXFdoAAyQNl6!+5%q<4dQkGLNU{;+YGp zXP5Di=7#0dWPpB@uSyIEx*1>?W67|9=JFM^i$RUhI~J{>IIv_~Lc1E9Mx0pEuS(k+ zSI{GP>1_ZVRc+Xfn2$Zpt)G?_;`c zdg=6))HdBTy{*w?>}F&XpEm#7OY~Vy(YEMj^f-y9i_)SC#exSfRvviU_1usWd1ea0%|m9VWaemQ&vHB91kIUsu&@Bl@eC zz_Wwb3G-GHYD$iYRnnO|>X+Mfj^*2!2YlBtWIz$X8T^J+=l$bn5Y-kfT ziMyaVGDJ*>2{9pkAU32O@|E;86*h%IL0WT*t|{)arHY$Zxni6w@qt&Q1x{1b&O?Vr zC}2{nCJCf+kVH$<^!+7vD7wBTRCI2s^;4>(=vl{9)JIY4qPdScK$QpZn)Jw9ZDnSKsrkwi8l#`@!KWN~wUs%_%0#oLO>@PMRdjBWPI`gkSNJ1agvk9_6uFWQ_9pgC ziC2Rn@#}R_Cq?879*XET_oF4kvCu*^2$tM`R{2uwmeA?qL6jyx%@qJ58?biH-Cfdv zgGnhfo{Ji2-rn~iOoNH{suT_AEsGxB!BWAHhM*~}1xyLL>30r>12?5Mfl1o4^w$}A z%Mva}G(AB#Z9p?}a2h>Dm~J<>5EL0f89X>e%@mO@SXDp?qu2h?lJ^g2Q@?F>*VX7x z4wu}ULR_+Hz~q`CZP3JvjY+eGW>DKSZ8d8(H>9T6BsS3|aUWuvxKuo*yNK%!TH=}B z)Uid!ptg0Cb}aClcZ}@+#(^!~He&Se`l+&-mWr?n(IxdG+*D(#@1x#U->hE}4{=+) zsUATk#xZWvnv8uIZN|1no6%t8$&eT_553M*B_N)y{X<*Z>sO3evA4ieJtrxUvXm<>_!P7=n%9p!U|pP4>#?qT#EI1tGbZ$rTw_a9TH4LFR~`GEvG@@Fo8+^YOo_IQ*C7vn#i#B{CjSc{9=REU$Q= zCtw#2bi!$hE_fyl9-wHBBk~2Saws-3SDy=n=%(n04Y-7+Fp7xaKV=mJ?zx=AW63jt zeDtyp8*mlZT_yz?ANQ|M^54(G z|5$qQXQwk^ug{`){=e73?H`Td9P#G=Yhz*#SUR9KW|HW2lW}c)x3((ic5L5hpPZO! zc5px~Ja*ujTDpmy&LS7)CyVoq-EOjMc7U#Yqp?wj<~?@en1R2Mb>L>9`YxE~_^#hifV1*bcMSM0HDauGPKInrGLJ%b=ExyWS#flg1o!SrS+Q=% zeQ^ojTQhIXnq2JrU}1*RFb3&Wnir&u=~05y`xdg8c<{v+xA6CiC9Kq1zjl_aQ4~@E zxPmu#N3H0>+Qt0iIe5K4)GldqVW$tEn}`sx?8zR@drY~dml2lSlDb4I}R;abg zX-lYeX5cUZKTezJk@IE_s%Nss+hGN1(8u)t;g-kA8xwfICoHl$RauNksntW zx{Df&E)%pTB0N{4SLbA z+Xs3?MXXA8H&7ny%=w`A<7ieyCoLC6y6;xGvMw} zDYKqd?=CCqxqhPFohoNe!Qm^RX*1Nh`u1C^eVT3Y@%^`}x>9@O0ta1*`lgmsZoVce z)-Ta-y=#3R^KDe2ARBs2&HQ}&SyIp@-9$IhP4qtKHhLR63cJvoDqFlsvsU*-u#P|X zB>4)$mcHTsq_#asw$Yh1CbSRK25q!9y$Raz#^RJsq?-eVF-m#2SR2p8E7g4OGwHg5 zAo=NsNE`b9f@~5t<4y4nIm9fFH63^+JQP#}T>u1p|u%HV2W zjB58rR43gvr|9p;F%KPhz*o?19P^2Rp;+l;s+t%_RGi-@AyUVIw;hh;ZU5izd@bid zbSd&byc?9@Yb^Qi=f~5o$R~{dr<;s~%^%GA|I2dD=BM*gZPVhpL zCu`HVjvz}SSDFK{1GL%O%touu)Fe=>`ADMgR+F)9XqT|RGj#I8F*@+T!eOy1J5MpW zcY^DzDKe=Z@`t1WKT6kxf#Boc4hqJqtun z&H#cJ2lY{9(p=Uh$?;JWnL6ax9nR|(EUum0PeZ+;da2~h&6Uhn>P`3T&+Qvclt&=y zt-!c~yw@Gbu0))D%PWcGvV3fJNj1!+<9Hzq*hd0E(Jei1#|MQ)(`k?=?78?hhqnPv z==nwfnUX0PRVr?`0+h*B20M0`=onbA)Sh~6(DkO% z1@X_t4OAeGIff-b5CbM&sne7sOqv?)#il)o2rJiY!Y1$-^bO?bP5o5A@!Ci4J!2S< zVIs%W0!)ok1PV3g39gcZBJU+KH3D(2_v1U>Yf|rwYMEwJ8+|VTJlF@#rS+!4w3)gA zyRrhQ$@1z!7V87#=#h9q2y~qBUpM!+Cet*jS|OAgUy`CRkH>v@!Mp&b>MZ`fm@16l Psah$GwaNdR#Q^{S={DW} literal 0 HcmV?d00001 diff --git a/docs/SourceCodePro-It.ttf.woff2 b/docs/SourceCodePro-It.ttf.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..462c34efcd9d6b70b42359ca1a1d9476efe43eeb GIT binary patch literal 44896 zcmV({K+?Z=Pew8T0RR910Iy&G4gdfE0nTUu0Ivi90S?&!00000000000000000000 z0000PMjC_$8>N06tv&`|0ND--gv@Y*#diynZ~y@|0we>Ia0DO)nHC4y7Fz;g^QUR-U>{{R2~-;pfF znEyxK^MD~{S_^ZvZJW@jqFZvwvE=cL>SS%!7k9J}@oIWizRXQF&f>}$LhRiq^@4?xgspR-qywNkz(HVnPb>Cyk2C#LU_3C23!ia>e(7`W3z0I|O2>5tqNMeQ)fBmVn&FUMUQBxb>kfOni-uQobvy1C2KD-x9HyL~T z8AHa?kKud4gk&GcLtt<$z=u@YSn1q8;|oH`{#&y5!HYU*(NV&<%3a3h|H}?a%t_7TuH9a|{+nv~A5fYblX2}a<@wDCQU|on0cs-nkLNe-%$)}$gr;i0 z0;!0kT@kg4h;5jE|6WsLt0!n!+nl{-Ks@;ix-a%x08>3Yzs;WV)#(>O5Ku&-3~Ve= z0Ra(&7NfCp;moBylaPD&)|h3b!5E ze+68s=)&q*e$@~~#E+_4K(Z_R^O6UnZH>fEB^nEJKiVGq0d#(kZAgl!Ura_f=Y~ko5_xy;?y9 zjpzTj{CO#*3Nw@kUDz$OYj>%_aLz(cGCBvFI^XO|X=4#43c8P_jtXGfWqP7R{>-@A zbNzv9nXTyqsirkG4<|UV4fHvxRu`GJpYGjn-2!S%urx|2lLee&y5yYawVzCx;7F0G#(=l_S5r@{CEqv_@wJePn=7GzYMn6&-LI{x}&Z1pDj4RGml4s0={UJ2wG8KtQPA(v*3?fV5(bVAIG-1-%D+z;x>1AKbMu8+43{`GMU# z{nE^uOPX}R9dMu2;Qn8oy#!lltXIV^@3qS#5opSZrVc1_WrmyDKcYzZUq5Qb$$};>$ct89XP;bx zyOw~i{O8Ze($}|QLARBMwEGoDr3{y9dLebTA`spf*Ly&Q;759YU&h zJEzt~nLFohyUwTIs_-#UW5MbUxZzTNd)I!CoL~w1|1ICyd3*Q$wrRtrN?LQ^b5IzG zk^5)=&(G#EX>Bu?5!SX(+u zr9q39Dn>U(AN~nIA);>Me@o2?1i~NHgi%ozTJZ0NRK6cJS-1)}$RI_ec)3u&_SWm) z)7Vm$k+-QS)dUlYkT&Vy4xunnczUw#QvP}?;=!(bg@B07VS`PLPEActA&-PK$!$wZ zO9*h?0_V~x5}wQBlV>Kw+DvL$Zbni9+Vd7y`Mkd|?K`uYEp2A<>t&l88!;*fC=YlP zjdwiX+*sK!TV66GlUXiYXb(i+uPxt)YG~fx5L>muccyl@CeY?UfSLc?J%!z%X#Ugb zdO93I&GP$g;`pHUy2tThHtleHG~ACkt_T1G1zWKbWJ3%A5MXuyoI?RG3Me3;#x4-- zMU;nQ=<;wD0}ki#<>4BoJUpP4hbQ#%@SI5=Xj$cf0RsYFya=R8BakD9K$R*2GiE4o z%Y5)hKKB!m>RVh~b0 zj(k!tkv!5Qg?!REyL{3$1ch8v1clsC93i)tMIrapfsp5#LdZ+aM#wvLA>_BTD5PIL zgbXc;kool^WKjdC$>N4k$kHYevbGt7Y-kQ4Tl)x+BYl9#ss4pXk+>-^F<1#E6^@@t zSKUmBHO!P8X(nT|nM^Te8XtS6%y=_Bo^~dCx&=+mJZVPunPvr#=wRrA-vF10>!%vFIt`Z}xFhB1bQUMiE!VflL@v5lR>bWTNFf}dOS0R)E3 z51c`va;onu^#6$x78RWTKpAQ?Zvv>|f+{GZgp?e?{k{&t^1E%%$`~MAmRO}!xgDz2 z_fB`j$wIo zVJV(E1GB@j_r1h}77TapaL=Q`-n>lZ+dS;B+MOCV8f4H`YQFJb5Gz{tf3Lgx8DUD+ za4`hI*&-GK6%KYGBHHNTfbqhG5RtFZ=d2z7Dlm~ubE^X=jyRMMLyQ3gsvUEz&{n7& z;`Hgo7o1;ti7Q-dndNtLtK8$$)>y5*#+lx+kL(Nk&VDg=f-24#ph=>cfSRZ?;*kq_ z<4^k=DAMl?tNE{4IFj|-urOv&+0dXRLtm+7nq`?eAxrnlX4bj>+Vs@UKn24!ipMfB ziN9*BADc&|Us%6(l(P4YXYChX;$D?hFWEW0q4)HOzS0k>vv{X0To*7~?9i9rIlhwU zUFF{KxtDDBJe_A%Df3U8$9mBR0wu_Gma~bbZa!Uv`RpdVYIk-B)m#_hOoJsjf|YS;PAni)vBrZNU>8P42^MjB9L14imbqBq zkRthuWtR2Czqr`Sr@@`Zc16KrmsG+VEWcDj{^OQXxhupYgnytgnm3$t?Ch+0h$#!! zycOrO0V5itCdEI!-Ac&AM}TDULzJGwf<+Bl05p6bLVNNM0zPmOp)z_#r4qbBf5e@T zyKo`96Fd<|3w8tg;J4`E1e?_(#t?nkpg+!LB6MQw^uKYdWWd^8w=1MVNL0+=@S#Ju z7(hfcSBx=g!etsgSLLOR&v~T;+JnU#OBXgL#{sTP@Xa5 zuZZPhtO=*KG*t#~6I9KVzC;XWu%_$O%r>^u7_vRC0`2d$%Y(=H!JsFOS6e&r$14Tp10S*RJAqGDAKlaAzDxD7!^D z34N>wCmITbe#F?pg>j{8eh?J?OGPdX@w}1&QI>>C{j|v@4NO4`%ZP0e>bV|Db;*2& zR+48hqRK#o16XOCB#^}Bw1_xhrSa5*X(%Z9km1uCGBY0I7KA4YO&NJ^rLmDX@Bm(bd53Uhy^l-rV;6jMV z*XVQBj(-)HNJinvbc=+F0rG-27?)}vChU6}KXIPxp}M+xyC^jCdwW%_>V6LlmSVwp zgBe@{oSdtp25O-;(#W6yjS2Mx*KxbgeAD-Hz@bMRdGeWOopas=2Zu-3Zb*(5Sc&7X zzJ>@9BSo&UrYJLNp(fI(j|ON#&fo6=N1Sx>DW{!r)_Lbkye@y?;-za%GAxs`#8+4S zo@tsep8*~)6fz#orhQ)x)dmOCad7Z0a z=!VrkHCnas>eOpMKt$H42?Z4`SuE>RVFoVs$oLyw6xiSsp3$N8r{!PP*V056r`Yv2t0sOs;H5CY1zk0pUpmPawo zvBQ`HE}+trZ}6rM*(k#r#WzZ?bQ+*78o9g{4#3vn6cx=PnbAq@|3C5rmriZyoXOp%gTY5C|T(}WFkr!fhX4^0WGGOfL5G1W3BuLs&&ui9 zx$qMwPmv}=mK=Es6e&@rLX{eI8nkrxa(<=JCe{X12IM#i!yv^~W+Z&DaF}hGQdRx4 z_Z?et^@_KA;4|O&$+0+7w5oU6P4_h8bw;Rc6%}iPsel|OVKSUPM#^XcGb_71&X`@I z=38QgwaS#Mian+3X*FuS<{cmTBEBnAzc}HX23L7_!(Xo2iNYlPZ8$aVnbIAfs8DF= zr83E6VcR}I9Ex+eEVEvveGc|-JifD`T=7Z&)ssgRRf-?fIpu;z*WI~odi%2~0w4&A z=rhnpHW>QEuBDX>ODjpbak6BOoR?6OahPwtU21*eSby~+R+cdVf}n^#18rnONw@E2 zMaxQ%Y8*2gM>vbDOR;5ES#Pt-*imfvdDcNMdBb}?iLXlS59*w9L8I&LL{pg!5Clc^ z8E7N3qvejJl_cFbS+ZZ|{Kvl~>o8N1xfXFNnM$W8ZMDPG4tv8#z8Q7@vQ_6axXQ!J zul?+a03-e}28JAle!24`whBbTwu}^WDKK3wTR2L$<;a$0oODUFVJL`|k`ixZIOCWD zaia7s2@|#R^YTPfCH5WHHM-!GIzPm*O6s3{<1-(4%PS7YYn9x!YCLU^D&@+evJAc1 zdaEo`%+(5JU^(V89FhN{%Dy&$mQ*aMU5O!M`lM%qJOD$2*QqG^qRf)%^K*+_0d1JAM@gl@m+=+T z%0Z(s-vM6P{*JhQ!`iL$M)6o+z&NsSGLtvBOfyY*xyPN(Nj>OGZgOX0c&wUYoEMZl zJ$h6?M#BVpdWU`zo>~hnOfbPLW)fKwZS8TdgLy_v9h*^U#bO)?7L~WZl>r%ACf&ZV ziVThfBs841$aim><-4z~@;%TF`7U&}d=IgZ0~>P1J`uV+2&qag?I!>>bu2xOD-+75 z9+Mzr%inrpnq>Czoi*!{Hy2XRk?VSm7*R@kRw8C$CT7C)r<0~0Sw<(T}?KVk@62Ul!HA4ipS zy{0x35|?PDLn2Pf3aZ~RmEr2bGT-jY-Yy{7BR(LWzvXiGnahNm!yH98nVui6dII@aoiSKtM!7)~E>u z6%Bn-=QO%nw_g1Qef8Z>zx@RT2Luj#-hxlS#lxR;Z24*m4GWKmjEatlO-PK3A9BM| z$kSJkCNTNdBT{C>Wb=!Q1eORPqx)VV>BVa7PU2zL{tW}!Yz*DHqz*D2MhFg zl@)0=T3Mx?n|xy)A%3uj0oNGJ#0n6lb*h%Cw2qU~wQdEj!h(YyX>i;1eww8)OrK&g zNhO!rl2A0tW+=@cyfoq79feJF@t9}xvcl}=>CPBc&=OeP6Ri?aoMKhTXU8ssTt9P0 z7(v@w+_IolqJcoEN8VrWx^U*oohNU;`~?aYE>g5u&BaTUtff@xUaUt@1gd;yh&f7` zX~aAORo;xCB7{?OhFJSUMqE(}xmaD)NUh2;pJ}OT`m%=%ll>}UY*kUwawAmekWr(E zc`v%CA*rtz`g?G>Z_8q!Pw$fNjXBfFXe`O?K4A~#$&`c=C8nnFQh_#9008Lnn28^R zXybp;PY(F1TmGxacLQukpMwYv!~h4?9t22D83LdI91CSSNnUOPd@d*DcMrFb%2e4@ zFWZEKHQ7~XdsbH)Y;gk}Xzs;7)Uceaw#IvL!e5?R=6s63M3q-z-c`z;%<1H`Q{M|# z4Mp?l>dbk~W^2m6Oc^k|@IbE(8WV8Ej~Wt@#VlcQOIr0r2@@yHmp6aG1{!ayiKd$D zfh-AZJ4ZI9q(mJG%X#WR=|uhdUdO9)mFU#2gV?wU^DPK5*?%UmO1HYI#`KvX(`!}> zThz#N?lIb;o3SA_%%3HzZ+6qoNPFrA_WyM!X?_dd&F2>ht+BaK9($_I6CGsPbKt~I zl4Rgw!$vsjwR^AV8Tj(++n*l*)S6Il{}Yr5U?xbo!w4cpffc)!M8+k}T&TfjT5i2J zed~*Xl$JiUY<=}N+N|ccs1-?)r_PuqTh2m7ODERXK;MCJDdva{gNTHZhLuB`4ln=7 z98UrXCz_UIvNxsx*3f>XGg!HJiP1$LW2{7pfuHj-ei0`ZH+K(DZy!BQIM>Cll~tx$ zn@-*O3>YzP#v+`hSuyLFL?(mDV)B?WW{$a*HbC1&yGI9f1gnTu&BC)9SdA%XV3lY$9Mnj&inRkcVm6>Pe=bo}>^*&$dW?FaN|3N@ zl-sbB+95KFmbSCHKSbdI);Mbq>j>*ilQ-oPsi~Rv!FznaO5_! z0|S7eq3Fvz;kyUFMR8WOBRda`O8_0$5SP<&NMWV%Eqj9`g69kwRDR?w+lmC82=D+C zjNcm?3fsp3$8Tq2ThQLN>APxM8iH+wOjvMHj;-yN9ZODj)}uFC`1M3s2iy72AcJ23 z1I>7E)5wsp`HyH)8tHWY2=3@OlSWgX&WQ8%!R{=KD`;q7)47Gu|C+GzQvZy1-t`L% zO!~;b?q3U5iS^HT+#X}X`gSv~ZT7m~`KEWSbk5CayYa&rGWTH55BBC@A09oxFXrVR z{BnbajT$#;$pC+Mfd4qGihoOHrFqxBq+yX6KFQCYaR;;fKIiHPW9tSG1V*7&f8eNw zPUy_Gv;zp20uWBcv`_j1Acia7a$6F)N|QZI7}9D?p(L3>D2P>?S-qR9&LmR?xNEP5 zC`T-_nKuW$m?y}ee|^?VA5M7 zXBdwf?dou-xw?k*CX9o#iJY0o^bCx<9t{MQ4e}DI+iuUO`0FEy`-XLj)Vr=y+C9>Z z`QTpzt*~G*i@!`?SSk9EPud|fz4_?5pkra0Xfo^4t#(=G|9GsgnHs%FCD5c&7^*}# zhUSM>&p*@N5EGDv=%w{UPIiYOxaO^ba&hqRMx6X6va)1o* z>I(0!mgd(@0o~@`EulRS(tVNTi>+96g^k*+nQx(Y z-_%q*QkOe?zjI&J;z$A_vZs#%3ZNHi#s2+ySxK0OTF2!4)1)B1C6!S*RUB~BtEq-G z!QYC%-sMr`pT)~nL`}4(?mtsdqewI@ai!u($2XeEOfah)EW=pmVw0Os*Z6i_V0Q#{ zS8(@)l_R`d5#@=iM0}+ZDpOIXx)=@lY0O_!0XhrURfM)e^cQEKc(1DTx+-s~_O4o= zyXaTf{BGW#uE*BB7^{M*YM85mRm%~cvpTNoxohBwz#EY-5`Sd9sr04RpTa@lv9)3n&s6Zzg7jcDXd*_-AaO#1}h6u9%`{dOBGqE#A>D1Dzjd> zmsNOQoiAPTwadPBMcVa#G%QDQrI<^b)4YRX+vavKd)RyLyd(Kjrpl8qZ=BUFOE(CO0YjiLRpy$nMaGOFgSxf$SfRp2 z)R=BQ06^Ub(RD|~o*L-M zftu*Kfm&$NfZBN7K^pHmkU{4U6ttcl>Qr6y%b*_mZ%`k*(*n8|cr$oA?6*e-kbo_y;{T@GstQ@E=}p&<(FM z=#JMP^g#a(dZHeKUij<*z0v4FA5=7;FM4C3AO6rlfAscX02(_Oh|d`?2!DKF@b!Di zkQzFgb%p_-G+;RX^uP$zb1)KpI2eU5955Qq954n47~6Vt7*`YU^9LqgJwqm~X_QH; zPS>$BT+x}XHOm}#bgp~Mv%s5O=-n=Iu8aL{i533&Soz6s8CYes)$dx~yAD=#y@TE0 ze=N_&ZCfjwYTKBf?SMYxcEIO&9N>%V8S-U)JzhKC;ChRETi^f7mFtg?5BL##fS+2; z;ph4t^?^T_CI$R?byxnXf5xQqFE9=G59@*du@{((jcQ4R`2h)ArGy0lsn{lUSQy;I zE;onOz%870YgirJ#yPi#HNhQRaA#Np+{Jl!hqb^xTy$?(58TI9_lHfv1Kjpt*bF?x z9S?`i!6V%BXxIWg#$AtxjlmP#^kmo>JOz!X!!F<%XgwQt1DK!y&Db(?;*PP!9HNXhv8W85n?_L z$AM1}-=|3VESvy7N7@(RB#;JKX~T&i9Wv5~(?JH5WDKW)Oeo46&IVaflQo!{2D@-7A(C6}g~C0cFrJEpdqGjW6blc4;&>|& z?gJ(9Q7SwFN)xC|codW+NV)I?C{L&g;W1E=V3opipfWM4gl9ojqE!nof$Aix5ncl| zNmVPn0cw-3PIwd4B}2XN7N}3A2H|bckSvYDyPz>SnuKpbQ)I}ZUY7%>;)!-Tfjsd%@3b|1r*zK zj1z#pVIjaicG@3~00$6oFkB4|Vdijn2OJ^W(eNHPMy})GeQ<)jnY?@bp91+#hY!IS z3S0^+fy@S1j}!h6cY_-c z)ZiaU0z5ED<{xPe`9TptxvP!yP3p<1Z7gXE@*(yY+BaLtp^hDwejF_Kq&JM2; z2fjb?c=;75(?3A}s@QJI=%J^w=$=7%4xk};63|e*)X+%0{Lm=8;?U^!5u>qn3@&VW ztDgGTHg^7vE!I`av-Q_K*8N4A3;g_UY0=`2fB=tHt?uFRu4~igws!662?Qn`I&eC5 z(&^I0up-8>1>PdQV*V0=Qha6FD<@PzEF)N@P+5_xM9YyH=Z|WI8}DHYfbmzc{^5-3 zKa8+1Kyc^ImIn_oPhOC`c|-9P04+!mCp?jGuL$lp%A>PZoInJ?CH$*YfItJlqX{Gc zJd@xCfM*fN0Qh+%2B4~>Eaw)85yO(40z^&ChL#pgM+eEk03|^Jv_wgqq)LT1Y#740 zan_vMY2d{4IG`L@1ZK^d=69&_JGPG zBUB0Yg(@Q}R0Z~dsv-+i4fcboBRfAyLQPNsY6@pS%}^O?4rf9wPz7oUXF;t{6>1F^Ky6SPY6}-a?NA454;Mil zP#5Y5cR`)d0qPF-Lp{(J>In}*z0eQp4G%$m&>!jx4@3Ph0O}7Fa#P6FF<234jKzDLgO$V8V_GV6R;AR2wy{!unL+C-#}Ba8k!2*@16l@) zLCbM3&2a`YTOUB29|`@;sKy_uoSc&4+3p~rJ;>@2xt>518v5`KwDs0 zXe%B8+6K!(+wmyS4p<)AiN}C;!Isc&ybrVowu1KJ1E77dHMAce0v&*Dpo91b=n!lR z9mdB%M_@bXC_Vu?2HQi&@hQ*=*a13;&wx(BQP61=g3iFv&{-6Q&cU_Nd9;8oz;)0? zw1h6f_0VOsg08>~&{edCuECAab+m!<;3nt>+Cn$sX6P2$LAT*n=ngtTci}eZ9y&w! z;dbZ&xqf@cIR81>;0p2p0{}Ui8k0 z_J8#Lhz^{C=-m+=LLZFiFzPj;BUeAZ=;%3yo*$y)=;a|garNqpPX4DB-FVSyr@i8= zbGkY2ypLRV*%NyQkFaMD-90}K_dW20M;>{~vpgTYh6at^XwmAA4!xh21rP5$BKA`s zaNy7nCr*7CFfd~cah?m4SdQkfWoOaa<2)Y@@fXpD$-JkQms{}&_SgtZMPN=?>LH7!|Hquu&~xH9w!M12u`7* zI*X3(%(8J_4PU<2(b3V;GcbyiB1WnVI&In{*l3IOw(8NT*MKIw?9%PFdp{eVOZ-9& zOO}>HK&WNSS``$O)f_om1p^}q4vvTm7paJdDv^uaRDBeLR`HwGaPiWDd3WpUm@mf%h318hq%; zFyMnnh6x`sG75O_kx|Bbj*RN{9z-o_=@qSLSGQlm0pS0Q0wMgnQJ~V^Zwl6G9St3g zj~ECu0kh-3 zmGMEd)4%^FeBgj^asasp2C)j`l25?B4*=!kiV_h85Cbl!A_Nl$h0@!C`J3a{Z%O0XMDe`2K5ZMh#ipFJppDB8*c3RlReUI3m~C5{ zQs%#(iAwLaQS!d4FnUA*6StXO;g%+kx{*_d(K)}F$+~e)psFg|E%4Axs?UV9mHDQ`aHANW+1eG&Gqc-+SD8T{1PWX1y*%l~_Qkn6TaG91(B% zv(zFs9bVYGH8VmdKLZ%!#HTP?xfU@L2{R7;jA3ytkjdY^B*N@{5(U6)>t{Xl_` zY20e|wza4C1P8fjQP;UhOLcn|5j)}$)UeT7coF*?ie%Xop@&oBOBc^l#P&pMcu&ji zB6F5Y1XixO1_hX1Ewk+TYLQ87r`8U1O%HcX?(gN;@#2TeGqa~lb1}D1pXarW=vv8$ zDP0y7Ys2q&E@g06s~kricsRVvIsLRv*(X(=XQ#8{XZIpuo!oFf3j?pL%6_~dcL-CM zdPU^bieH$H=MXe~50dOZPF8VlAF15+3v0>Q4K)EbjaxoU+s zn`;zT^2&&Mb%%E9NooQn6jO z0v>5WzpAVbI`EYtgG1a502KhUW7mo#w}%r9*XD|-qP$YPkH1BLoZ{LXIwMN-5z%-- zWDrqJHDD_UP`CnN2TJ_bI2Ld-<{aV&Za`3mtT9Ir7&@gc(OPmGQG!L37x8P#9#{@X z4H`1I1d(XyuLJ{APp26wKmu# z@GwL4VGIx>=7f(wJIP1ng;s4ppMVcgtU>c_X+`%95SPTDlnK7iW>A2(4tJ?;!k2Py zr-uyJGC1f;O5_>}W1s>qGRA>n0uVzIKCbRRr!Fb002^)r{xcW_j|kWyiZFx_*hE$GBl1bvoltXuJSkgqTr21&B~rakvDeQu*`r_oBhLTVHqtVHb?EW%v(^4!3nIE?1CgW)_Z2Hp{@x0n=2 zdGTg0EX=!08QmzzBTE}jTFVI8SdXJpJVR>E_#h=IV5I|Wmj3(-0MoHDWbf$&2+>NL>se%B*2#>jBur>6I38o3j& z%XqA&rv|d+Ox|D*sK8;T+I2d3)*>5iX!Ck|@2T1&x&2TiRA-;3N#-DD7iCGybU{s$ zLycW7cEApbf+RY_zuP+%I2g+^gbv+_Iyg~CctL61FKp-;#>jiT-lQ!!66t!XaQ$cK zAw(9 zn;+`=aPqIzi4}TeHCHxoM>#R18JqhESxRjtH%`KkE2EmaeI5UahT0gK_SSe9JxtE1 z_zypL5ejIf0*+x08g|-G^x>J{FddVK%vFnxbjBPFJ98* z1#ZAo3`h0Kr@(b+r%?2qq(cSXweM&~1;+G*>8}1cQbu~PGM{Ki;&gWP&LzZX^6(Iie3vTw*7e+0u;%Vn z94MfKztyLJpq&o6$~dtnjp+%2^Lx(U^S19%hVV<74tog?x&jt^8>#Bt!otJ{BjMcnhT1*^P)UjmavO6Ff z)1*eg=ek$`O(ejm&(=uOcdx_XSmh&ujpraL)O5f>vNwWsR1ljR_bt0-rYZ%jNWtX} zAgxo*H?xGMd6Jv0o`2+_(nd5#2?XrHs7h+3E<+>lYL+D@bh%#HYhq=$oGb=?AoOBt z^Az31gGiBzbZAZ=t|x;&(w#loWZt8+d}NTU_?W|VxPK!*NpPd?3PzC2xV^L|UwLIj z^&%KrXzLpT;_{0iY0J;$MOsXIBi%1QJze0@v*+haiZT1Gc&wQ}fNEWOe1w%=Rzw)n zbs0-=iVl6I&g+)_p878_;ysNcu}$6q77gpos%aK*U-&BrPh8a3o59so&-Xs)o&TKc z=;3RDzVA=ZE=YiYur>w&3c*>LYkH6QN=$=egOijkiTeu03>-NS+Hn(st4z}0=MVN! z(cNFH^I)duw?ur9AUOa32zLvwQoV6S8AwmTXaq*5W%saFERbLHVFdtKUT-ui5G%}M zh1c@9(G0tiJ2KYEi7)oG)~oZuPv)az*F@|6@ujU7+=|l*FCW6WdSR(BM{1wr4^CPM zCe(?wNIbd$2#g?ovemAx6KZ7*6-2+)!}@%KJ_uq+IS5|%T{v)L2TtNo)VK)XIYKcK z&q1P~t9P~-My}FhBqcdbOlKKcjGg4nQCYK7Ex~P5Qs-qU>{X2DW`;DGS)9uGPhD{G zcV7RKKI|^EEZrKoaw^<)&xAUn{f9`u_qMtlP(Y&Sm|%YavdDw)=u>H6gbs~9b`S62GP2UmDj*!l5AocciHQQF!{-g=NG+f4 zfVeXy*S5<7874iSY)qkvsc0OlqgeI-4@hy;4ErAnvv4*FXd}P~!?2pL(l(jP|Grm( z^TS}di9l{P%czI5?qfb?W8L~J=*XQ7d_F>m1%Fb4bR%&`kAxOVY(0utX1RFi)|J&Y zG=B77Aaaw4>e6%G-_97A6w**??t7n!rW><2AWv*8iQ(+PPd;5|dP3j30QVb?8EL>m1$2PMh5XUj`+(sEYMMIKK_7sMTklPkh zin9zgPpqsGhlCe8yx5frnA8C6bZ>P$b3Nxmm))@o0W z*hSr5!Y(eUg69Om-eZS7W;BilipQ1oW;>P-T;F|)B)jewz-Vd4v=>cUaFBCN6}0HB zsuu{NFco*k)^wng?ls37Q1&%Bbm4XmXq4goOIGOMpnW5+**|{pQinySZio;!mN-LM zdT|#FW9}wBH`RP!EfocC0++#AsB|Iqk5g@U7!)$!jVa=flX!;0PhVt6KPK)a0aLMy z#Og@hgX>)6h||;=w;~{Em0!d(kDmTi|vovG_CwkVQhjF1;kRo)IKr+;{9xpuddArW!5?UgRMpV-f zWp!x}`R+3iLy1wbtn86kn{)mT8sE)4L)F&rZuYL;<#`GcWY@`8+E{n!mh0@ketO9; zg2&*TxWrrMC|fF;NAlMw&ZIg=M%q4bqY2(|MszicF~H+HEBI+iSC5&vz!?QLpS%1y zDasrQOYWW5HZDJNvPa||)pKWtC4RxNyH24}<*;GH6nCJ~fVIHGf&+DdBwddi{ac(X z0*;QY3%sBfZ?;caGaPQ~8WsK$OWMIws@G@CdGXb?e@)3jTMgZPJAJGV%f!O~x9qrF zw$eI(Sr-~`Ii>5Azq)pUgPrpvb$Dv_oLz7ZqmhX^ONxXS@2CGDcBm^-@tyVzR!MA$-_y~nMk zp|TgcW`O|~SV30A1w6z7HtgLAa{KQgtej!(hlG(d@80qwR--09wxUNx-ukJ#JtMp#VDNqPGlk*sk|ns z8Lm#m2@0a@$()m|vX4|(Hu1qKZi|J-l}W`&E>)4zC;JowE3U3BNLRT$Nq(ugT^Nd) zsFHIZ*!r;Uq&D$YD-1y(x{)d9J!wr?&^)(r%Ng@BNKB@*dqgP^?%>iFVCFh?z%)9?PIWUukA5y_Za6Ly8^?KSU9s#F&h-{jc+OV&}eP& z7ThOi<1ux0G=%PmIjbCY*v~kv_Cv2BgwH)IH-=M;)bw_ZYmT4Idb9z z$a`(k0SnEQxK8Bv_LNRUT$|h^dS1-7PE?vvi4en# z1JN_PiF_L)~@nkqx_-0CY2G-ubz9QzjT%#LBEg)-&3vvd&7DUvbZ0cx5NfGwyY zeV2X23&ooGdJ^nErE{K7@P>BeCOW2Dp@!nnP1oCo6DK+kC+##+dkrKOkJr=FQ@mtq z`|*X>1ZytDEkh!ZW!p&2T#=ri4R+M^$=n1hQ@9bO=c-R?t1(`1l=VVbs%D=CkMHuQIALAdx}KS^Jk9sw2fE2G}ViKA8 zbPLTlM)Fis=*L5|P4-v^dAloiZ*b05P=j31{7rU=B zi5eCDuzT8i5qZBW*opm+I_a&LD`Zb0H~E3F8Fij-QPfVx${Z{U z_MtaCrpswNM~64fArcSk51ZFwN3iHHSUf9aAqrqr*&)M`_*RD%<7+r7+QMb_$5&WZ zo)&O*0j>j->Ux=@M(P@#c`|*8LmeHmVng{80oPPX6J|Rz`|CRk%3`n#lhFKLb_$Z3 zck=rnhf4_QqtJ?+54x_WST5Q|QD0tN&zQxofw@YI)TkEfXce(w%vNeP+Ndg}RgwUt zO5-b&Ep1*c_s(7`MD|b$%hHo@avuG~n{FspJKoA4$u#yB%pT?Ca|Tn1rdg$-jQ(_i z_2y8fO`&9HQ;|Hql0}-Ug(i&DRrd+tKaypMF;+9a>;RuoI|k7crq4+AtU5>w&A4S8 zY8+t`(5jIA;=ORh9Hx;?BRj1V+vQOY{2<7S9ekPTSi#S5u>Vx>n(I$Q?33|pSAzzm zu$vVftmXFPrc4`__bs$xq|A!9YX(6)4tzsk=7Mx3l6JrrCsk-H&UcNWd!uL@8D=2c zQXaT@zn$H8K=4|Ej3cPqIqqCoQMZRi@*a$<1iD8z)HY(S;LSHsc28McNpBmOO(FDe zllD*5*vMFmADXoI7P%H?9%jS~-~09>ydIk)HQjfhkAROqLf+8p>ID=A?K^cHhdTpz zs5?=0p2_ylZKt4h(Itu#${3es4`meEiXLF8QEoxv;tP@po{B}YUM361X@L4Hh9g~5 zQh_vy{DTy3QfU%@!`8EXy&ky{ZkK}Z4GXOH6sdFr%+L)@H_0;0bB4}&n*vRsx%;Rl z?q~$>o<9lw`|_)L_ingGwu{L|u6mF3_zGh@ldk%&=PI{vZ%%3n` zh3_IRpE9-0*ow`OigBP3p4WA zXq`vuDnsEnlsM0J+mL_-;x}kA|WvAdU ziI7a0$#m-ICnV&Ze_?H(mS8=p59kRE?-{*CqzUoPrcc1EOQ|O`@+QqHg;5PrWR5x> zn*fU%KwE%PzVfuj5v^=BZSv7BYhT||fTSMin&bU|b;CO4ATIDV;$kc^w4{%FU{pg+ z99H*L;T6~E@IWT5x$G(HJTZ9xsPCcnVIF*zkz7-5YNTcuWd|uyP#oO?TFh>ZL7^}e z@2;I0wlN8otKK>&i)AZzGoytD0Z(GtQq^p9anri`h*WfoW9>O%2(SDFz)LnA=|F+4 zANIr7d5QAXukou~Tf(Dij_TR3m1`Pt{4ZE({LUOOylY0! z4tug?|9uels+ezd5U-F11~H9*4GtU@VnSCQ)4zSx)eKF1X?y`+2EI^!@W_w#WnzLQCAq|MF-3FGzUp7SCh{^LtKZ;_cq7`f|^& zzu5KXUw`?lza1BfiXC@edQS`fHm}vw{}9#rR)v5#n9>xsmOJ9cN;pTbz&gIfk@|3U z7cFa!FBG>Hy}M2plV4o2Ko{>z8VUF;SI}PxhaN_d%4oh6LUL}Hyt#exiQQ?!UX^;7 z|FoL4IepIg`?41y0SELH?$?qRH|rD`(>;rx7K@}8z_Mny6yfN(Pio~YZG%^}ntDCW zojvDV_`N2eSJT_cuH4N$>+i%GGkS{6PQMF^`u6s`PbrDL|(izmwS``q#xx`f|t<8 zpmVZ&MS0xTXJX+VZo0Q>*y638Y!dB$cQEp0Cz9GR41W<1G#J>n|icg;7@`v)-)=EIt!Z;Gr3}o|7V_$ZnWLbTnnRzW{c#6vkN@A%`fP0) zZHJzgJ>!NkDBKQRMjVyMrYRh?4q<20m`$o6gcH{o)AP!*UP>-UtNqcMT|dqyF$s=s($Q2LBvx@22fp*>@ zy!ic!vO^$Ok9RQ$voXYs6tMFW3a3e>qd@hPzOX4gGZICw_DY*qJ6wk6 z>rf>s>{iCnxy3?%9!BhHHAtTwNce8v9J!;KS+MZNA#7mH*^x=YLELOq13N1v9vvT#HaoPEj z$qrMK)q8q6yZ2P>>JIe?pRzE^sM#8ZHMFBj&TsGgu}lT3N(eezdb-k#y2l`gzZSDC z)&6LX<)JH;e7~;dNDg`@EZ>Ol!u-cIM_Es@#_!WINyG7PRl?tndQ8@A?iQHY47<>K zn_5}xZ`ncq`I=_zaKmYZc?84Xps;DiTd74P|t7BFK9A*)ZL2SqSk#;0Co-kj_gboo6J{*-A@ zPEvqKhI_cxe1x}Jp&~r~=N<2*p$@{M-C3*UU=GC>D9dp^e5IP;l%T5ST z4aRI0%`85EK=0v6qT5%=O}4CS{<6`?%wLFTXEvUjvVM9mT#>@37j|)Hl)Fez0B&zS zGv+S4J80rAzj-Szocg9RSTFglcp@jCa%%k&IrwQ8Iz_C`wyTAaeY4B9Wg(|B8!MBy z!@UnH!qzD)p-K#&SZ3STTz6ZU*A8D^fs74L@e(7zgNz~&(zlf{dQFWw1>FjgwB+dS zhWSOcU*WM2_m}f{4XGaXikz>I85)*Tb?1uQ67$Puu_i? z?@eJHR)L30_qiSp5PS3SG1EwjS!`A)SYN$Al8Y}9`c#df?NMt-piHVtSv&hnjFv^Q^kju-FT8&^9ac=ufD6KbsF z->Q&)T7(*`^igoFZz4l38XDj1|EGkak$yf$NF`fO%pZZr-C%^I(~@_1=m?a@_q0vQ(w7!E_vgfwaog-!_L+%Uvq$h(D(mzNyyM~4ZxU-Kp@w(lZv~*a4I}w<#Jf34t)@{~d&;8MV4hS2~d$Nl> zE{O<5#4cQu12TA=*?Hoic5b>gh3KV_B$yZ|A59;&`&E9uXB-5>z5+x$I~b5}Pp(?v z^c%w3b%oS8&y=&kUF_KwGb`)Sn0OfduYh1y0`Lvy#+LTxy6Wa9$8wh^?8)T(FtQ^r zv#Yn=4k8-OTkN42;fIbDFk1DSt^x+y2D2FnMM=K4uY~Q)0>FW1YPZyA6s2112${pW zwS|7YLTj4^v078*m;5~rR zU_P9-!r5$5wIua})0D(e!gnP|aQ?gqIu2%a0XOCD`B2QQP!Dl!vdoUx} z5?d4W&T}T~P@CIoXh}4Tgtn{ndoUTU@h&+y6J^$shp^(|VBB(E<-+Gm;&YpU&V5TP z?My^TzzxU0=5w1ps~o|6fyW^bOeiAEdZZWMd@lC-Mw4SHrNB)n>~nUVTV4tvD7n_q zj2^4R_uzgn-ru+{;a}-$rO|c5lsV;f24465X2V`x#G5nDiIX_HA^l9pHG|?w z?{C1*qDtsq*C|>;1vEQ)iVB{>{*+t>7EF6m?P|To&sX3iw)U&SVh@x^lPwk`;q*EwE}7c3ChD zw?&x|f7!MHo#rqI>bk2H(5=RbQ%OmiQviXwn^yaYI1b81cDKhX672!hS9zQL(B3j#*QO0qJb?6MMVMi9_Fr)vKi zvgEb?aN%x-Zt?A{RW6Hx6gGGuh&O9g?jKgMsX2{7c>yLN4YgnNXPJ+E)P z_W$0yzFbxzB+7T>*bU6{^){Ri~}!_L+h9tHO#-==ov^xKegiWoewO zaA)K4g|6m9E*jodV1lpu^J%Q=eB7%9=*rT9plt!3&c?kz`f3Hu`__w&!aprxF77qM z*F}EXqTRq$B?|3>tX#f#i}zQFs>nRuH( zYU5S>1m3Q4&0Qzf7hZz~!^gYLVfN5DuX}^`(4hZc*X&^w@61HSWgwy-)811S#P{vt zr%*Feizn-?sY|(Fv&YrCNE#c4M|%34A#F_I8pNmWe$?Z6FR{DXVwL)N&L}WfCzG5e zZbWReEf4TOl`3wVCom*+E?nMJTif#dj^avtiN895?I}nO^)}~&@-HD*-PQ1<%He=+25Txp*Jyy@HM z33p+`SdcJA8L|WtTo~l9l_7~Q^y>MoC!QZ7qc4s zX^UWey38$(9^-v2X8E2mDK7V^u3n^BUD#29Y>^?4U-=HFQMeZ%PF=Ij92h@P;T~rbdhHD!fTRtD`2~039W$-6a{)RwrrpH zf14h414)BfgBzch8u2E=p5CSY#tF(!ulxBJ=>+6axhBBxaIt`g3Q31JejeN6D*YX*QRd zk&*7n{j6DLRyw$(-7=vs+c!JSd_b@+?ggBP*fe*?1i`apT~8>QQoXyWSJSu^iAj?H z67QwTzytO$OrmgUJU5q0pm7a#q^Ikupn@Y95YWCF0UJs^Qo36;EGZ1154AS5&$$v! z)P_9-moWul9T-_;bqiSh!FO!jg-0hRba8cjSEF{2i%3ml`IYb8X9 zy>npb5IWvp6L-#d3MYc?MRd*>bHkO(F& zNP$gmtA)7GhIp#Bb*v?`ST7?ny#u;c~;St%Q{#U!MIz^{eAMrpN5)) zM(ykQqH{J9=t?Cl%R&#Cgf@#B*7Mi!t(`Wjpc)@0O_+WbA|J1OVIo%c;*a*k_efkk zi*d}yFu53>&x&RN^{K&hd2y~hV+%DIw9M%W>^nJl5bHt^u)-@9b#{?$pVPh8Wk+2D zn-2Zd>@OKQR6V_woE9(vvg|aWlC3}1XbO8SEm>{8OFkRLfFllM$BxC%^VT<8q&Z7bA80Wa68#_ z$rmi@pWnaxk3v7yQZ-9uq{VuFtlFP_OWjw9Z=D)zfs+u*eQ-nLHfQ`Vw;%n+7K^d4 z0BbiYA)&C1>sT|>ilD5w#qenremwmm#~$lf1Oh($4%=&tVC!t|j_+#K4la%1dsf`* zqpAla#J;oR6b~_WIQf!A_mOn@tVrRtKv`Hbh4Y?JF-1_?AZuChPiR`IP1=I4nK`gm z8xkzEaNB?Dm2GMj^b`|3teG9s`W8WjWT~#yv$r`OKnvwlJ)Y7iv63%9@HD}*!@Z^d1=ugi%74{SdWi>bxnXZ}(F#6krD|~(NpiUVckSArM+LC@4L@kq=bp@( zx0C2g1?NKyUEGQz?Dvu2w`a=}NRK5W6wuj84#uskv!IT5_pJL@CdLDte*^>uOtjIug`> zh$r7NmEWRWlz9bkszPq7c4QF2ge%@IcV}}#s$m$jB3X(d;rky2Zo;;-@|FKa#59Gq z(`fF)+>sC}^RmmnmbOb^YlqyB!LpIn?Fz7RfTgXwEFo)r?Y&!#}B^=5{iZ5k6T{*PK*2mu66aJ>_MW@E<4Ng=uc?+joL}Qz1R)K)8%7T z|3D!;#)%auu|XBiFL$pwAD$CDGP+wzR=7v9HIZ=!UdW2U&Ni_nIWpAQ=3TR14#b&P zufR!(R%3upTK@U7hifo5I%AyUflXq@lzw7GNz6eO@n+yCcn2pGcD67c12NHOng^UMNFr+b1+mgxUxflJoAHQ9aU}#i2 zlI}7jo-y8E+fd$8`PNr*0_FeKe;@^n3QRW9iyk-(o#eVha%p6X)B=2hLo>{*Kr|bs zHy11xG9;TasTO;mpmsY|$(g%q9m*YSi|-a?t|NP z2`mlfHwoAXAM+nmNWx++7iFngN#d$zQXxsHijWKVD#&mSk&*M|Mt83OpkXXeYiP5_ zgDYFqaf&ec*Cf482KSq2k6@ozZsxO5K1w)-k%mNE&OuIOyUoyVcP8WWdUtqVne91e zMZ0_PTe0Y)k<%mI-%s&q!xa#QNhz9g$=r)XrEWV%{K$%TOUO4jQZcU7+F>>HBN_pQGKK6bk)(x-R$TG?=n0qF^=`NC`jxIZ- zj=1v{64?TEyTkH(zwqIoxcrI&IU;)UTXgEO8t7S;hR6qh!bmJ?a%SgahVU&?XloFRu$|vjttHn z8$Xc*c6+5GDhe2;t7_isUmR60gT$W#5rhit;Za%EfyTKJoD&M%yEPzuA?+Q=l8E!V|$00)!*4_3wPGyoGZU+6%rHcBs}0wedL<=N0T@Hbf-3b-AQW0I&rAeVR8r} zwXSJP`ghFd1Lm<~pBD;20bKW-N^+%X^~+C*L8b_m>iO-cD=APo6N-KN^2J=8z^jnQ zblN9^aTndww-M~-!(hO4RTVqzeFLB`qD|p{uvrdLBSsIpmh%wmYchVH(Joz}-Wzji z6XL@HjuD?Z%4V_F+Eoyl)yr#(OK>$!zxQS2T`{Mc`fYa&;$L?JbWY=4zTHrXt0wLr zi(MJa8WVp2>>Am>xXK$A0nBOa^!C{$HI$RN1tmeROEi|Qatr<7(zM=KBc{M!Bik}8 zF0bct@W0t^Apma1Rn-)JWl9T_vrkv?-c+7vs+`8}R5n9FWI6Z~7H05bUBNU?Jp2== zTU`0F$jdWemnA?Zi!561tC1K&_sJ2Zg&tW9oPeV_ZMlqdOI=)$e75J)*#+12JNNFQ zOguPi6Ysf&CKOTXf!@#5JLiOOEs(SbsHXiVJdkE7X;Hz?F)DT4`cg7%N#fZqC1-Z(VRz(Fx#fhJ z`kEi+%)c*!>(dG;%4^*Eek{*K{fpeg&E%oCW6KNfh&bgwG{4|uS)^6BCQwqc0#!#(@0v<=0|f}I{ceIOx~ zU3K&uwKF(5nNNLL;9Vk-gQlO%s)+39A9t6Tlj(y zw_UB(c?|jpA{j8LX`WqEN6!>ky6Aqjj@#u7x68m=I50|AI1H6GnStcj)yTx9qW`c> z5|36Fr*=sdavuIS;fF`i;fT`@2^M9Xw!y)z(}JW=@;a0gad%x(}*sWj?3yxq@Ht z+Nb-MsIbUwnA7F5Yh03+hcO}+7gF+@HQk`k>rl^%H#>=*D7ydIw@Bvm<#27N>j zsJlPU$E!_pG)_%!?(!<0o(R4RBfY37aH5vZ0X^#OFkgD|nn$8`#o~6WJ?)X%j9`Dj zKjs;;Rf65)7loVAjdsuOQ11Trbl^YWJHM@FW`?YKd07*&N)ViZC40Z!%Ifr?1!eEU zz55~J*rR<15m>YjbsRdq6TkGK)4OBy!QZeUZ{bwC;JBuJsoovyS}?s|f$H{Udf2Xv zxGeLS_vlmYNzFexy|9I#2aQCV7_b1wT)uYZ^@M~KLTe=;GiNLe!{+Na{OF-o*#nR{+vCB-ws0jXWx z^dcRy&Q|Y=FjY+zx?Bu0fk+ACI08}Omk0ZX5w!KPjrV?Y{Pcta8j?Dp+n3=9Y!2nO ztDziu!7jqCvkIEbis=L@H;h2bvDO8ti;JI$yKmS0RLFsz2I#Jx(a=i^Nw2z$)OS`m z?HRN(+~kI-Fem<8F1|8|>m?80JB!Uzedapg#nA^emoXhcX}2uG0SX(U$JmRj?t?ZX zdy(00w*idFQMQe!f)Ao_TlLvz#@zZ7yhRa^^k!}j)(Ztih9zDbr2h&psdsQOi8(=G zJj}O$%f2n(n`gq439pja#P&w9$t&Y|Qemf77Vubi2|^9BL_nd?Wo24TFlIf+=?_Ly z=C0W;8seGr-to1GH%>+|{;JPrZy5J$@}W5?zb7L-vn~i-;D0zRZ`IOR?lyVNu*bRg zS-nh=Y{>l&H(=NHHqAArY<+NL*7Y^bnSi~o`+dSAMCvGc@_#tyfcvwV%x}9kMH>lU z@~5oF&H--OqJf8cjWrfIUoVNsxEqIxcu2S<;l?71@9HW{VYSmfhGFm#Ru$VQq$LKf z1eF_={13DA?1-8#Sfl?2%EL5NCX%2LGFf^D>)D<8oM4^MM@j(fwef7@cozyoA z3wWDkZtu~Lt2b}yH)uBI-pFMg2fx6|ax0a*Ek4j94Rcjqy%n*VB~U|`$g9nj!>-EL zuOIhNb%#;`*T|F+Y4KsEz z#3*!-AMjv(9V=Sd8LiOrV%Na~lR}wM)*J3FtSF3X6BdO_!4Js)$8VOe@K}D59n@p? zEiCVJ@6#D20jAt*Ml8dF5NgXBG`+4t&3!2))4H~fhfi`|S$%TFJ@R6!jklf1KHYt~ z;s@GtfhA2`E#*o}g2OupBJn}_AIwXsHGKg$cbP1|--NY{%o+7_SH$G@8TRv&g6dWA zacBmzrelgV`Zz~R@`->jdX7rmOQH^E<2Q_Sm#lZgd;k4A(X@^V-F=P56ZDi2CZDmV9Ac<0EPN%BhS*Ap-_0m)7l4 zwB%9ycfW#-ZgnX=Rd1ce|B9F*LL6iMak{^|Pd< zwwR-(DyylRTN04(+#88^!{7FT1SFmgc)8mZt4F+Ud)&9jJe(bhRUaxUzIzn#TOdLseP2{}p-dGI3XPzDE(KB9Bymugel}#<}E9Y$3 zTi8^_?}{uPFqfp^$E^eAJsewNFDEV42O3=&6f!2Uk-S~i1!1e=yr06uYE;~x=WbGk z=syN}5~*k{4nm=7Q^h5Rr@s30JJ0vi-pHnX&3Z-kk_;NOTXGk2c&|*Ir}7(M$Yv7z zt=W*P`n`o2D!ZbYkZWwpl=Y1<)RI!k4@pqWEK}uF6UCLQUy-;2?osWbw58K}$e5@m zYT0RaaG%Y$%)ZBCKxdTtSTeu8GK~zt8uEI>^_oW$48{aq8k^SOYDH(zc&oS?uhyGv zb zAz3*hQ|blr@4$mcBod2E)vBB*sw!^QwpffV4~oCz$5Gt3*}5L9Z>fEc^MJOpuw{N5 z3gB7=8HomuS2ra1WAj|u8P}!ZI_^$i4@kIluKX!deMCdrd1mR4TM|Cq91}FY3BNJi zfuL}HpA?&j-Q{n>ky%uZio>u{Voh9=|vLPi^05U zTmkt%85n#-B;7Z-DuK!n1BM-a6U8g6BV1u%XVgL|%#7qXwcIdI2o<ug7LJVHF1zK`e~umK zYZ!2R4!kQFM|};`i#i%lPN-y$PN9fjxN-BrFCQs&-^wbO>g&{tZqg}~7=`%_qv9#c z;sRf;8u)Q%9XU5Md8c=0{5EhR$oA~7yoLblQzU%Cd4VIsq+EZRB`diuNUu@&!a|wG zF>i5D2y6=Y9=A!aiN<9rquXWCsiV!ZM``0qPeC}_y3?+{?_M?eCwM_-%|A4R}N*s{0ENC93sTnfn* z;H3pR`1SzpyYw6INFFY;guzLD?L@q@EeAcEmt|d{+hDWLXEJ$<+2r?`PN?8YS<47; z-Cz$d+QWcYe`@j6-_<7A-HQin`|Li*Rxfy9 z`+((#3xGpk=L)tTh#Sy$r?ViqFH9tsHP)c@uB!T4>7?nnAd2{8;Fw)zGg=)tK)oB{ zI5)=Xkm)sBte=7EpCLL|Av_%jPrK|H*$Ybga@pRWjkVjq_ zYK?3e?{K6z%QiEKsssagj1t%D_8PF{pI>DE2gz_?d%h5eKX^iRBHFL(POzzxv|^$& zCh?vipQn5Y&+G{~xi40AxG!TEoYdCX*FqI=3)FZ(%s8;Y*)yX;a866#-wKt(tyZ)X z9>?3PQ~mS<5y%=?;qDeCc|W+$lP84S10zu^4u62nyV2_^LiJk6`N5hkPLT;OEcJ?C ze&VTNG2dsfy}O>TatlpHsPIz;+8W@9hL1W_46uY#33J~+pRO}G=Yj6~h;F_)XoRIX zzqOO5(Iy^VqT2RMwe=Z@|BPZ|I+lP-b=GW!+}E0Qung z-^ot-BtPHt7RdcH&F8xF+&MMMXU|jFoUP>Q!;$k^QUFG}FQFDZXzT@fO}+O!(Os9o zHg4PD$oi&3C}4L!ShpfV`#_@0^ZErxsy?*(UvE+A>3qqV4|gjMb@CE?LL!-HF}ROQ zJc3&AK)D1MaQ-FtJJIovfJJ>jMUmZxE_vopPPfyoU-8CLap zFsQmK(9k*QIX(fD(`Me0Q955FsQDATW@kp(0?XR|n4$^k z-@#frdgy~_UoO+jg;35Ts2qLqzQI#U0w;@iQx>>DC>aD2bXGy%pg<1Oh%m@;1 zaigaGUh|xG{@co?eug&8^ouCYLPn)i_%Ipib|pWJL*7APxmANSOqGQpaTu9GEgE&x z24VqjWY&}RB2?Mux`Ax{$h(n@pdzih2=tUOa=7=tOf?Zd#MaJ!^+Ot(S_9T6^Q z-q$Ji8Sf?-pXKtN?G8J^4SUw`eVlw>;@xE1Ij-%TS-1esM3W~suZbkA_x3p8?Q~y{ z_tinMXj{H+HJEvdL=+VW`1&brZ?AaYCNV6##+!aRe5m|Ds6)Z)-+RxD#LBE{HPk}a zRzG<$c>NXL&d)zz`0izi5q*4xNYpPfT3lUBbLHgtxgI*_g#5H=(Z=SNf){7_qBtTa zW@LU;Dn=$5G5cB&X*=b_Hz*HN_79VC_ILsVrb7*NTwH(t!S(C?B+;pDpyyDY-q77F zcX6mcQ3>lkf!Kd>2meubc2df3-;Y zfj9S${UmfFzvmxNC=0)0y=LbR)%AV4hCbu&Fx-IFm4iRd1V}~ypml-ESk7VgF$n+q z%xF&Dni?VHyndxZaXDBjL{Ki6rtw~Oqkip0$5ZRCg<|&wE5go%VLD@0TjGS1o_^j? zna@H19Ot8ct6FnkC{8vR#`79dw8E;yjHP@!R-2Nj{J1I@g#IrOWgMB9}Cy71C)xHxLN zH_5Xi`~GMcu0xB`rr;6q`~+HBOZ*&PtEnY@1|DKe{n^B~szx1&&pKfjkkvL1cnt?2 zh{YsJhcpTmj984aOi;6ka+{2b#X=gTh=z(qBJiWn|Mj!#A4~POD8QPmRyY?Ci>2fv z7C#OkEP+rc)HKIj$~|M?FPIZ)co&*YqAqA6-|d$pt!d;d>XuT>#((L24u|=0(t|*{ z%%cLU6xo9hqSqfSLWP3BDqu~3gD2?E@8ARMwUzz$bsCfTK22k+votMtAG$EU$w~K$ z2Tv%~2W^0`6<05Hj#FsUbmCE;@gT7cu`64mauxmJ+1G>L;l&PQwPOfF_@9T~X9kzC zet0%yet7InmemqvCQpE$;2;W<{rn%670P-#e>>Gj$7UX_v-C|6ef$dC)7O|HCD}== z|I?wLy2sh++u6=%_}U}j+ydbk$!Tf4Ge)KNR{usXx^epCmt%s$bL)5vQ8kaFewZF= zhcSyh5tIW5^+je@`{>}x=y#(Rc!f=&1K^}4|KbI1tM*ZF)20TkB~=k> z>uX6jz_F60qz}Vuit;N%7!swveomv(834qF0J9)UlWSHfDBJ0lkNOKC6*s?gkBM-* zkvv8Aq_1R1WP$)tHjsX_$bZo<{ey7QqU&3BQ$jST0aLfd6biK`=8hudZ;n$YUkiY? zfggjRkiI1)S8>X}QeIdjavr2diN*Wb#HUavDx;c!TSE{ZhV1HWOr@mm#1s+dT3SEw zFUXs$SweWwM41FZC&o&;SSAR9nr$qJ^79jGX$ht=fTEBtm5|Ggk8<%-q8|{&IJKKY zC$`O*^fUoRc=e`Tsswz?=qmo}dQh9IPBU;QNqMMmmUhc=Pt}$!n>t=msqA2EMLy{52JP z`5&G0!>*@V0{7dlP~zs%uSCuYhZ0%qP>a?OtB82CmOuk@?dkU-Z2G*j2PIoq=bx5_ z_Znkrn!(zJJp4Fp32tmxm5-(CYi8Zv|8WnuV4-{SF3 zb8F=SzESd781FQZQLGek2gIL&=h}rL6;$wyh$@utKkI%yg7<2zi%k)};d2G(8$uNh z2-2mJ3YEd_gzef?Qhpz^kd6q1?QHg1sze}M1jZ1lxd2@L);hXQgP0I?E~Zj8-cM_F zDw%qWUd43ExqmasXD`$5cZ>u@2*UWqWoyVmCfE#Xa&fhsx`J-`n19Oa<&XK=(hs85 z_4JkWF7;2U{MjxN$xHpvYfdSjzE6KYw-yG%6!y~qYG6xPE%jUro$)jt%Nq>{_;RmY zFL{J+d8af?>S?Is1+rX;BBB5#4MLXd1yWC3TO5KlEN>Xz+O^584;2y-Or_+eiJiT;b-W1?1^d)Q-M%PL-c@O{Y`amM@Pgng8Wn2Lv_Y z88sDxyS{l!2SE+|SuSC@=dVyHPSV7qa@zWqE1bFJpv!L8rW5iP*dopO=`tob(=N-p z5M|Pn{clvaHL*yKVAOBCW(|`U^3H^FUZ{&vP^lBk!PODADqd zJ(uzhhR?We=*M!3^)+Gwek@ebI8j9g?(NL!b$+NEZV5Y-2sa=X{>6}?LjGEc#|2Q` zz`UuX)bX>!^gnuUbA;Rg4YHtLCqgxCyB9o?#)Y7LwW|;(OLJ(gYMhq>l-j zZo+?td&HF5W8{R=+KCNi4utEV%xVANDEOE#yLdGiCaiwa3ZS_4*@gm{j1fE)We}r@ zE`%CU_QZ2N&5LIdrS$N5Ns{cy9JouTK#DOU-ysxx6l$}C?+{763S|?REpyv3s1MzU z+^-LYjV_s#kILrXnJcwwm1vBvAuSCfuKq`%6aG0C%aJR-^<&-K|TA4b^ zWkGxB()a+ISFAf^!77PZydiATUqf zKhu94>6q!Cs*wdukB#G^w<98cuJ$Dd4gbm>+;9TvIMqKx9tBD`Q@E_cGC$AXB86&I zc6gN-K@4Rk=2{$PzwRDFcCW}W(??6zuXh$AwKs-dBw`(~G$_Q{PZo2*PmG3Pv?QJr zZpP+My3oRgetCWW@`voVEsxxhW#Gpe7U3A{7u-;vN-sx_$PzCq%)%BRG0|BUk3Uu| z(Zx47BERXKmaQ{=NiLLh+7HH*R^`w(i>G%#--TX| zXFcapzt6ASKk0wGu~c71@8;4vaP$PDpUa(jxag79eH~A>Sx!rZSLz3{xbKrYIP}g} z_b`?fXOcVa{%(1^Q?UV1MpU#wS9;N`Q~{-$LGPNCC=d}H+SXRN#Tcto^9pu&pwr5V z$}X%sPpPSTba00(IC6(tDk1PZwCQm%_3>??{T1}^B$7F=YtnzR+q=&v~;&=bs`_9BA&OWV+iRGS2{Gag1E$? zKp)(*a`IKTxBg%pNH{`cL43YjDScviYN)aUV9NRac7MT(DqR3aoV{6-rz?dNJy70) zh-ridFBcl}`Nx4u>|T$QY?z@~7s>+vTlrNd{TcXdX?=IBG}%;K5qP4lG^Mz{(@@`a z_IZ>KVM1M63%79=xZ`N&yCw%g&V0D`>}(KrI8OZl+YKCHMTqvF9}{;KS|fpIQ{yk5 zuf+<^Hk@Elzvl?P_#fpT6`LFhubF!^-?*>o$d?9bzkA&MRQKJ%oB&_^U`Do6~brczk@og%68wb+Gc=pqb0emwi{4uLHr<$(oT-X{gv>6Ago;PJ0w}Y(3 zxo~#E`Y1ZjQ}~K4AoOLm~kOv#-UBX+dwbj0S(Kl;# z+>>g`eP{IVA7)f}&l=u8N|*nt%$xB-qm@OpLI|$sHx(>@*Z|&gf2$$<{jl7@+Z6GB zRKY%T{N~K9qqIGZ5${J7X#OEICil4t-b(-Uc!wG>ICeDSNtbHl%tkPua(`=o3six& z4YVR`rh~>p%sseeSrv#!q89rAeNre6#(N?W4JyV0AkKMlc%vRl&#$e30|e(lh3JdH z-Zwe!vJR}~Upm`)aJb{vycZm9;t-??o(tg%CAWNC;q)3tgdS3`b6SY9Yxu>PkMA#X zh>|1+3qTv?!`2J#f_%bQmVY3rft(#*FPFsq?_eJ zvk&w`%5imIUwC<%KYdxS*98f0(@{RyxOQl0^ve$pj*}A@(gN`{{3U1NB|NT`K)QtM zTl#)`i=*rh|EYf>oLQwf2Mqv&{zs|`XhCB5(eQ!Y12VLd+>c%54f*dYKwx*jbd>|% zSTq?2*sne|&7VFmczINgy<0tmfn}t7)=OQsagQRq^vQ5Ex|7Yn}06ECn;qNO%FpfaFD!@m8pqxg* zposPjSuiA|{sw3#Y-S3AlDs>9RN!Z)zTHqe)<<7rwV0>9K?5VAVtbNv??M;GB2mAKu1@N*sxQE>Fdx z9%yU|@@fK-CC@}Li&M((*>PTGNGNWAgz~Y?geemfVj$R_z`QsoB9$GAnT*1< zpE-ftv~zznMYNQ+vNg2GhLZ*DPkedSw!H^5bQ*IK?Ohx8igG1t@`upGeL$IAjo9o_ z?heY=CzZ^$xZ#vdHsWeguhQl$L(G}I)py6xp;?m}OpU$lt4V0>S>_zn!yWiyCm1#V zaMR}({5B^$^o`ArOp?-JU}X>M<-a*r!f5e_vI=NRqZwGMRX@wG$yHB_6LFuYu7uzg zhyl0y_HQ56PiT*TIU?FRp08Hg;@8U9|9liIb9W>D$W|4TIb>}cYb~QsFYcS_=jO*D zsjc{u!PBufN+P1DF;SZZi3)2z)|@VxmGu6L42MMYm$>}r#_@&qp0 zZicGxauJ|9RF~jf$oz%bv!b(B2b4RXnq8P#QjG)WGfUZ7pm7+A1ZG9Eik7ej08i(* zJkcTnxI;6UN{LS(=F{il8si}*BUnp4HDXL9nvY}Q=5Mwkg0~^_aiVDNsc>G%{m^_+ z6au01HrbF&rWc%u4g)kR2D;R{r9vDAc5`zMML_j17v7>3L5om|vtuMbIH7i{_b{3E zaC7bXBH*seJJq`aySUj0l0R+EC+Nt%sr8$topWlbF-`=eVYm;D^Ea(KxM@BA-jNDC z0oI>G*6~4jOT=Qt4Rwl^Qw;SOjolFB@mvJK-3WsY;AA=mES3QjRTzQ}$DqZmFk(!M z$+QuLET+@Rnj>=_SjS6Lu=Fm)bjTc?mf3-dK3!Y5|gGDs5vE;_AUNWk4zpsWsvtnw2WQ z)T~j9Lpp$yf=*b_@nF!4P=Jji5i|--N^VF)u)?5d)M>@R5C$C~7h>}viG&YV0WR-X z61No4ub{2^u%^r`77+9VRK;Se`jlU9X>RCONHW$oevPp>T-xNv)*O^5tcz_p`smPS z;`YzM7Az^^+3G7sHb^$$#W?n?$?N7@8b2-Q*?P6|CTk@)j(0!7B6mCKvU);ylRSHR zTfNv%k}>e`*dbE2yGahko)`-8a|Jn|0590dB4Y?$QqSuyo)u2f$=qI!Snwc=f}#vb zJ)wKFOt?10*X88^9~hT1P3(xQAq?s|V_jCIM^lgB>JhI-VT*Qk>Ij3@ITJg;rtt_x zGvYN+qP}n=AFI!sK6v5TVfB8Czl?$_qioD2@vUqkL1y)iG7%R8KD?_^;&~+FO-UFOTABiy}vR zKlrGlm}7Rr%ogPSy*kb)>Sa&8@+L-u`PdmNx#PodgU73lvB>3fKN#TXO=2N1$L-9N zk#QPk4BnjR=(`g7vb9L9VV3FgkzjNKUAZ#My|zU0jr-tZ@57_o{B`$|5pHY#;gR+( zQutHmPd%RYCtgHl?Z0ff+D0L8hryvgxaZpj^@D$GFvxfWh-6sVWRW1ZbRxFOAY)K9 zz)~;h&vX+*6MTxph-zg*1L7p(U{)mhHeuFNZyPhl;PO=jtb(*rjN>YZtUwT(f0_9= zNfH?jcsaVuo(Lx1^go>2L2e+B6vtHPxFK@4-L_{=^xBX8TwKyplXuPzUz?5VOSC;W z^w1Y4@Sx)|uyeE1@!#gy_Ed3h_Q%l08jNQyw41Q4w^$_XwVOedE4&#Cv^Z)(T}qA@ zANvg7>DN=!u_j%lZhbWi>?LI}?3&FyIOn5(7kxiF_B`TmYxGaE zM;2b=zI!Y>5^8F=dETeG;h22=7WuztzdRLZPg#pCDC-DcRt=+p@#v@i-BwwH^_-`l zGlWKuEj`8opA)CZ0c6*dvos!41-vgLPBsrYxXP7By?icR==sT{A-nQm=~~s#n4Hnl z>TtC-*V9>NVKU|yrp7!hpR+{VeKINhCVQV;=}X; zbpsNy1HW zn{gWX9M)bn1c++zy-1Zp@SHJVA=kBPkwa26t9&32heK9SD zSUA0saFa;RfaYvMxb>SrzEPmOY*)LZv?I2X5%~1Dj4?F61ca8hMoOg?WJaf2QdAvm zj0ZN`(PMcsGJ09bMnsq2cwja*_$)SSQ@FX!G!W+cl(z_N$e7$)wiF7edN9Lug|83u zkEO4)j#Z9uZpg5IT{qk~*n|ijr`RWHiipm*Gcl~#fGec~p1(2@Z7 zp<&XTH!Ii>l9I7q>p%rKIJVKd3FfNs5p~aD%ii{>r*=~2{7#@Xc5b_4+Yz1rnzLAf zlI?@-v2h_$gJ?>$xUc9G&bgvSgS2l}%ucgL3FDhti(m#Z_J;1<0&svsIN*H^%cuqm zBP&bU-k7k*uh)F4<8sUlZeONRJM6pIX>sypTz zP0kqcQg<}rWKcFZYen#TsKef6_55@!LJrSxhTC-oamOVn;WIR~#VB_E!*mM)obPZr zY4It5;f4yT`1fh8qTBzFq&7tQF6MZtfM>kOJXan<#`|VSPDzfD^9~?r0j%z)V?rJ) zSs|5cW#8?IFJ*l9_hCyv>1Q#*0CKk_EmP%su?) zg?Z*tzt|?{UfI4?j=`y1BJCJM^0Fv%>Fz+oLmqq>z-smJ8ai4faFdwd9`-1{TmdE zNc-#RvQUWxo)M)Qx*G}p{knNRvkN!E4=LQcf5G3}kJxyUE#*f8)Ikpf*slxdJJZPv zeI5N^z0>uYq5+vB&J#s7*f83Ss0hOzl6c>(>PyZ86inuo?z~$2$VENguZ?@tQs80| zC#!#;uVA|4LZ+=64IB>hDEVa;AZUoQ5LYMvdH&0u;eGqZoqYEJrW1Qmww=mLB|kFR zJ^45-f!pExqzmG_1V-U~`_ny4_HUizt831%>G~Cog}TW2-k3f>63Gre-aW;O_}i<# z(nGjYLdtt4;-|iovrJ#j@e09ZRsYwv%M<4(GHJ`F>ARx1R9p|0nAj_?QiDWigpj-9hFMY*Z{)1jn z+%{l$-yS7Q1dih4#mhkY+&>S7T$RB1a$U#}Yt*VtE7N%(K#1-hww#Sb){iTn#MClL z`#0*7nb%?gZfYcbkm#}YQ0Y)WbljeHw%`tjFD0vYqR?o)^impiS26gYu&{dYK(18H zouLkR+Eul-9DTsF1D`l{Llwo9(jLIvfX06-msk7JwJ3XU7i{lkCv~(KRXq{+qb0;# zeh{mBdOB~@L;lpwJ69)z1$a-tn)GOv(mcgU-~5q;UW!^=$~v|l@Diq#FY(-5Pznkp-DxA9J^b`fb{00)^@FLlXP zMB6$>BB8VHs7+!~h5y!ko)jY+8d#@cFV6%-%|id_xQE`uxX?cM&i9_h&Y94Ecu`pl zbC#ggWp7w@W!8F^ZqUxUH^}DOSz^v?!Q)cHa_~y79mp_Sm*2J5f1t|@PJQRaq?5<7 zLgtlg)d9cxk=Y}s1lRC<=0a22?RqmO%cqnV+`4F3{A|l`*1f!M{yUqQEYYf)`<1^@ zm)k54t8={05)+k0PL=~b9-nw{{7fc_3%HIq5)bL4oGEtIS@&Myr)&T4*t7x)S4**s zO|bOSQP;JerEE#}UeO0bv6Gp!^Mq<~Mm>~BFHi1~>IIe0_9?r0}g$V7mKQ+Bj?vaN( zTEE%;4po8ySCBFeY?pnBYOnfW(fd8}Z5`V39LU;`y+BRCkyhf`h?hv#Av#)CReKFq zUxuaFYQD8!&X7rq-?FMYCj<6{Lf3`{bw&F>5$x8d#)B<%eV2RTwb9u13n2t8`^P-` zKjbAYg9`lnIQ1#LM}D?Hna|sEbDW;@$g)-H2okH$eD5anhH@=DgagfvolD3kK>h6* zGt_tC;Cxxl9nC>8VsLJnae6FHSGBA35IDK-%}j9l?)(0dd6PN-qCv!m7&gWYC^oHy zvzG4(H=RORWtI8JTFQi6E3)NPNAM-($`OIEnu?b#GYUY+_qqn`UIdMW96}eAYLpt( zBsr%b>2{Sk;mVawS)e19<|GdSyuBO14RUgX4lahi=(Q{CT}V;wiVQa-b7-9m_)gs@ z#|NtcPXwb;B4~EF`7t0)y;QlHGQ3WprJ2nXeeo1sMP>zx(j7p`mQac+9;6g{8J-Q3 zRLTHQPrT0Lcp{3Df3ZjEXYl>e?g^-xBSTDv1664V2;teL2qXRJaq?qh{!z9=FJQbe zAGjdHT9rk;>>n%Pc(0 za{~Lk!tAXsJA981W={3y=3H1{+`iwA8g@&k+qe^J7oX9+Ei z=W3hO| z!6mh4ocQR2g^xR8*&lV}Iy-AGYI>h|Qz5SCU9B1478x5?)av)Lcj?~B?Z5b9Th!!u zzj^+K;{Py0Wz8Z20wLg5_Wn|FA2md+u zO~1JG{cP?;y_ab-wdGIN_H;9G;O+j%*2n8_lGDZ!+Z>Y}(~XsoP?jJSnv=qe!#u)h z7mi8+)6yaP2QHVmV8%2rC1C5>lhQrEIE2Ui{I zFXQN$m@^H5(?9mI#Rc0;*5hCxAg{T5S4I*yptFrJ_FWcvE&*Mds?`Zy!vR| z-qcr>l|NR4)~#itzg|H>hK9CN-2?sK3x$b5-m-=d#XG>L`wW7zVi~ko!+PNcoU+y* zl}>Xd0n#abQW+H<;SeYC%)qI#RKHPH2HBd)yzLmiL_gd!%(eque#=vRbq~Pw&`yv$TY138QQb5gd;zurq}=py0H@9q-u8Rf=6)BqnAw!D z3B!14I1ZD%CPD)}4mYCp)0ah`{P;=~WwNavcX1QRo865!Qx^5a%}*n4DD11PlseLZ zB8bwjQohra-$A`&t4s79WMYpAQvMQ!4ikW7`;Z}x2UtDXXgx4$MS9qOC_C`IB$7(2RWmCZ=`Mt^*jDlk)i{P| zZy_C9Tg9vnjM*PK$jRp64ZI%U+EX?&zNJmGj^Rc!%YIlx-8xgxBC4P~^C>1-ld?xK zqaSlG8Nm`o@X~SaCn%2x55hLwh9b8tD@ordNsI+c3y-{1?e4>)&Yf4~rC4wch5+R% zE2{|H0O)>(d&|;W8A-%v_nNEqWswAS@#JdQlTLQBWJpTK-_cY!StCeZJN}#F_7lW} zWh&!ohBsiFY|zRH1XXR(nxs2ZCgIF1L1MFtRjF?{3EcoP6qZ9x?jnvLVbCdx(Ufk` zt>nJjfK_gc@Ok^j7=r0>57g(mxxe5e3I?QQh-^3^J2LIGugOp1LW?*BJlpB~ZN~&c zZIV{&2;4U+74OOp%8eK&T!{6XAU|n@`E%o1{%wU*Ipd}Q`MfwkDcFQEM8olf1(z^l z`SL$OaMmLQN#{-RXVrE@)|M0k(?CgGB6GF)5_49eQ3Zs~F~&B)Ivo7td+sKFO~s3>e0IG{23h;mb&#nesag4` z@_kFs2^qCMA+wVAJ8q84!5WP9NU`2&g7hU2DjG)NM)Q$YnG&=9e4m&G@jONGqX9q# zjtoI)W??OMfj>ehq1{_N>iwhhQ}a#zZJA_|!fVChVI_sHmJo~UBk#nYTutvWZP5~I zCWUONz7ihkxs*v_kt}p0xUiDGOK&_WEYt#}4hf;P&S#DenI3A9ce}X%OaTm&Sk@0w z6#p1M!8m=`j?Pls?7r@@?USEYfF6L49Z@<=U|-qXl?V2Du6{2v#0&hqFt<CA9bxDcrer!V$z`hL$-a6_+Y}r#CO(e@AiTW)HmZe`6I( z=2N1FZo_D$pXm6-X89MfP;!sCGKzm~_|jE|u2^NaKaKMJ z38J4LE|CeZUA=0;_s-)@U95UU&_59rh$&}WFK&N4HGgOMiqVhqSLO=Vg#VX)lniw4C&(!X5IZ{1P6bN(?scMAl1y_HFauG98RRzGJ4gI4fm}q)@0Civ}hdCf=?e)zradr#O&-%ID` zlYzl+EW+o$``Pn+tZ5SyznYtsxA`ryD||PwUdUZH2dt*}s)EoU49)6O1dstr`PBq6 zIeN**@qbhSuYw4gAo$J!2J>zr>I^I<>n^JJUE$$jM84b%W~#X5T+!>7``ps=ywjph z*ziXkuz>K$IDx=m7gL%qmkjk_R{QH)332B}c7vykP=4!n+s?9s8fkVG}81*3h$1Kho;4@eSj zZ%{ze;Ab#%bDp;iXr)#!D-Qh_u2fosM(yQ2*--cWXuq*YIZHrq8+=5_%48C&0ABS; zwN8Nahn;uqaMG-#(##?bwy-zPhty&1=mk*9Lz~aqCod4nPP&d+8 z8lDqa8@aVp$%AbS24IwH97uQkp)Frj@QY$bvxA6cV5N;XoUaWm&UI?uN2W#-Jl?AqR-{LLDOj?w zbJp^D*$!B|8XNOX@4;sjKIq!nT&fUiWw<4q#oG%NNz!pEbnyBGznUvewy3C>H#*8% z>AN0~VB0ah^|64uhQNm=*MzxeS!Wt&YtdszCEHf0z(XcDIVF2$btH{*JY7oOj@PD7 z(`+Fam1GNNs_g?UUtVb^JsHgzB}pu-z@tPdLqu)W>+y$;3Bh?M1gm2_J|+;m=D)#@ zV;@>o2SZv)`%~6|?X*_iF)w;ieC$kdGA1a0Rl^gUFl@`usJ^CGicuDWQ#gTo2PNHR zC&P-Xq}7D@ zZY;ai8C`0+b6w2F`YOk-htSl^DgMF+M%&54@uvS-5;chL*5vF41Z*h6OssejM=W(i z$xBe1d`S!%B+uATuCyyow?Z9cgUeZ~axMLGtxL${@1)+dKVZ*lVsp@c z;e6lhTK22%u@V5(kCu}=;^arKLEcCz7I&%p=)3PMhG;cSxx08Ye+ymbW~27@o|;cU z=K7&N(dsjMlNRg-r0-#S$kyTZN>BXRqo_?Sw9GLXH6#+%v_m$tTFZ+Q*@gxQu52*v zw0uT?{L2pHDRL4v^*)A~D0pLdOu`pj1j_p$kUXOhMO=x8%oOhkv?9VAim%dI%I{-u z$Yim^^Z+N&4l25oG-||&yDUH0j*hGYo}{vWHMrd(IMPh%x7Scxb6ORCw;SkW z{m!sjQ_#Mft?X#Tt@?DWY;w$(*3Fk7>t1V@$oAB|VwN!des_X;TRm*(y7}}{VrTpd z#6jx6Q~4u*qs-$@_;upv0ajIaXgw`FP>=5X9Q@%hJ<1HU1=5mn@; z_+|rgN*YDT$;ETGmedeE?KUIlm;hkTV@pCC!nc#~*F zc)*A21pw7?u1#XOfpuuq$dwb_by@_Ir0L>(l2KNPWJ1ZkSPNQXKd(8qZzp_J4Q@ZIdA=2T7g77l_lOv^$=_#c_QJ2 zEa8uW#c-q$6MCfD)e=tNj<|+vFghlx90y?*oI9-k{4Q7(sdAx^lORino{*1;gvKX# zyD9rMWGSh{Bg-;2Iq2nW30Y>UTLNK2Cn-mzE0W+FjE`PH^udruXf~n{HMRhpWP4=T z^T2&oIPxm6$97H9vD^lyx>3xSh;&AY#F+T=K0<|b9azkZp_*KXv*c`-xg#4RLV!5E z>&6z71eHRmTA@;hkEPl+WqxzWYa6*-zF0h|#zhQ$cUq;Ey)^3k_-HVc+vmMo{SrA( zs#@YVYo%DA)yBS=(iWS*)>N*L;}jD4GO>1E*0zjgR#kMgsY02SKkpS|mXd)+mK3EE z6S|5Hf8n^d4E?~E?${a2M4(`Jkts1P$z$hsZm`anKyX#cHVRi54ACcR{e16s5tZm$&loStQ62UaYvC{>BAc)>+v}hn9@06mF2D zyh~PWnu+BbL&EO9B-fZ(fyG$p+2D!5(+N(~fVp)+Ho=`+ZVXfY$zpEDFe>Z~VYMqy z1J~W*Z_FV({>O#Z@ZxUsvgw{{1&{ZNcD-HiOlm#Zr)EdyBhB3OVn@0&DR|Id2VO4U z{UX95Gg_;GKB30WYxC9Be50y;>R+hWaL*e(7hDH=h~i#7Bw0cIgzRl>ormc#BOVMUvk1%sLdwY z8mAa9ZGV}$t48w2oFD=;lu_w-I0Ol7f&m&)q6Wb%aZ!jjuB^|`H*5eBp6!H88ATKfsj&^* zl-1=b3C%X1X+{u;IKm8%UIT^E8Kg&Gz!eP$JP`rK)R0#bgxe@<2?Vk!QwITal80@W z+ zdMQ!T+(So%)%pw1GVNhz@AIDiaTFKCyDf4w})_~)>Rde zSP1M?`U*f%xf_nmkw{m*?WA_!`!dcH1sBLMyNjYy{%a3---<{VbnywtFIXA-lteP` ziRdZ4UnfM*l;qFBfZQA3ZTsr)8OUWt;qKthY8_$B6bGEdv|~#~Y_ii9 z(S|9=K@2Cg-Nk4xX-;SFL6-sI0^zu%3x_z~C$vb+V!1-sVJ;w5OsR~*#+s`Fh$MOE z0>XgxDLsFVBTIUL!R8uG?Y}YMj}iV{jz>td-@d@S*lw3?l79{F3Z5>=M$b^l`!F@V znU=$$Ddd@yUMlgTP1l*ZI=|+tYt90VL!82!)+>fL&FXV0(d)L@m^iPMq!=Di0Pq3} zH!5JqN9Prvz(f(#l2EejJ~HZ{JkXpIly5syO^`p3PNmD0x-go=ei4&8dr=GSrW6o& zlm!+qtrz5)gan{ws#6HM+00v~zb#x*GR<3^Ke2+)fmR75+XC}J6VIgz&oJlF*KDoTUeQWVU`nbd zo)ML3nB~sb=PXol$fn|_5w~l$d(q#ZL!L?$rK^O&Tj~`^v%h8xnN%rgkQ}jND8nG` zpdwX*=w(|EZ>+>M9yZ#0ZGl7Rz=IrJ)w|pjEfeFr+Uo@^4Xs$?UsWi1ou~EmkE;vO zNzNOa<$Mg99qFe;qkrFEzfN-1At*g7`}Hd&b!r9dWQ&#rUJ9tvoe)@;yE_V!8M&!!4vK^$5z7UVP%fH`Pp(wx;*~tkD6+lcd^ypqC8nBFQk#9KZA+*rM*;s=pwn`(R638) znC$L+VGA0)@IgIcqY)f5EI9OJJcHT|uR3lG3F|(AOr>2y(Q3qsPH(UtFzB%&t-3_z zg2~jHR$cqXWm!{w=Ve)2{|6SLT0@W&mVJ@p^3247G|4P;<@%JR&11%15L$cL$XG1; zRG_HD#6`qJ$A(8o$VntKX*?p6E4nF_Tr!(47R~F`yi2DwyB}kLL~i7G<+NBV zmr9m|J1$&U?0dgKq0p#wiY*!rNu^Q~6*^@I5_*4gXrq=_)cs;uMHx)`%a@|Lm91@C z))dWkUDk>yOn^a_LwzNoHdL~k2zz3y(+!lEKO8Wag(snjIC-aQ}0+8)>kk>D%Pg5DzC1SYUV?MW0|0xSQIaeQjula zL@o;p@H)e-@fanO#8ql+Cw3uV%Pr*l$e_(x?nUSolK+p&k7B>KWaTwq{_P2p{i>6`{^S4&=jRr8eh7RNG`DK% z2q{G+H%C{iaoTRWoG-WB+$?lyLGs-nQKe5&u^7z98xo7f0M@>CNh+7lq?^9_w%23L z?5LUDE6DtMX4(gYplM#iIh6->f{WAS1xd`(I3ccls*1yU-+}L44DRtRx*S2YgzU8K zYUAVzH0J&E7Moo49{)?+vn$-C_tnMH;_?I&BQrx&V{?U*qr1KK#qaaakFO6ePj3${ zR#}V3LwGq~Dbv>#ZmPg&EUGGt>m989e(woFUf*Z|_%{;<4xk7vKK-h52dtoxgSA2G z)p;rGzM-YqeKj6F2@|LkF>?k@8@RMFbbxS%15m|8RMn-JU)I3ieLqwr(d2dxT|mI* z$S#$$(wk4i?X(Gk34HmRE}E;AmzkTLpP{3rrzvYeB|bjbkDsS6^;!vD;wO1qic^Qq zZM?cUe2CD&1dD3UT#44=#S9zk+M1rCrAiF+2$gg*j{?~bgB*C(o#vgoJ-@$yzxGyO z2OuI3KqMDnrXB)CsFJ9YsanLUmawDAnntXexU=cn#;%^g0}C8Tu%gHlE1bc?d3$_* zUc{PKZk@e;2G-u@?(hMG7y^wr1eI8ZoVp358VjvD4YgX2JbeKqX!wScrEPs|TFH)_ zN4dv|q08Ge>+yEZiAv1D7X*FqhJ)~Iu1CB|vp{9nHga-aGc@-Pk&%)Tr7xyNNAh6t zdlqtvWRGYdz1RkT-3%aA08aIgmZ&gYiN$ z;_2U$t>nQ`Gv9ZXVe^w{TTPeuX|7OnCAO+C$V;7(+87&|`{-y>>JnQP4hDsExvvaw z?Z=P%VJ=~_kkQV8FLS)8N10^7kqiP9nkxCE7fW9^^P(iH?fmWT@3H)mVg2-;IxP$3u%|0r@)VU~90jIm zFN~ z6?rLfvs8FW^Bc$7yiAUY_@x0gp2~q<_xpk1iPt7D)MXsrVtpn~q+|TXa~qi_uK_$Z zaz|LZDm^Ez$-rt4^Qw$2p@(TltNEEuR$tQkrK6?36EGenb|}_~rJ6KoSCdkqjPlKn zOW$@j)cbwK+)}=5IlcsXGD}{Pl@-=bYl6 z_5=E_E*$%qA+mBf0fr!BHf1{&V;zZT(}_DULLedva@)B-rDX%bmi7Y8&c28{`2T{& zNF*bPH{|{Z(fcxi#TIDVvG4#P-cY4*AfUK6M#@S>9;oyLA4g}@I6vS1aB_?;+TeB% zs9M~#g@V`p)37~*w?k7KOH@YS%Rx6ApM&s2a>y@o`_G_aJqrdbSh8YG3xkyqhZ;q! zS{aA>TsG+*DQirw&QIzdK?U7G%jn=yv;)m>(M;>1&+4v5gt>*^XP5hSOlVWy%{n63 z$Z6`|A9POdzA`Wk`eVj3 z#g|us41@Rs!uSLD0~`VX0{|cyK!d)E;TV7wS2-JJrS=XHO7V;Tq+-Cv>5-|YQ>YeJ zWORgR8D9jcm9cARZmr9KV^$pgi8oINEO@1k1O2X2MnX12qqh$SViW*yYlBSwEr8*s z!*2e@QR0X2VAC33J0D_b(}n-+#2@uI?*zB`t;1n=f2WbgE?Fn$(T`xCh3Jg)h(r}b z2t-P(>hO_mt3|jxyNg?mgKN;tg62v|J4Oi8<{MRrVaS$@ciU&m?A$G1R=aHTPdAk{@W!+hwb$&yAdMJPqKYrFVC#- z$yt$y&Nr4A`@oSl$cv?^NQk7?`$oqzDt}*M>^U2Qzz^-39Rwz!nLva2Xi>^h7?*pWs(83NeUdP>1Bdb!MC$`1*#q+n8~)mpY_a!pyrU>$Sc+F{vd zx8iez2o2FOh0}OkQn|^(iC8N>KXG=PJlVZ5Ns)EU4`*ybfP418QZTX0EreABqN8aI!_>}FBHGLkA&l{R+JCk9~KFW z<@m53?1%mP;aBU6N9UVaif`)&t_MIZlCCf+OPJDnolPVV3~9QJqGUNWO9}X2nCZW8 z1;Bq{s{aT7zusK`g)czD849Aags5ycIAQ%EkSE(Iix*RQXMvv8OjU8^@#%gJn%0iT V7)~)yDZnpu49h_gZvJhn{{a>V-DCg& literal 0 HcmV?d00001 diff --git a/docs/SourceCodePro-LICENSE.txt b/docs/SourceCodePro-LICENSE.txt new file mode 100644 index 000000000000..07542572e33b --- /dev/null +++ b/docs/SourceCodePro-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/SourceCodePro-Regular.ttf.woff2 b/docs/SourceCodePro-Regular.ttf.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..10b558e0b69a74b8329766fffbd5f64356c7230d GIT binary patch literal 52228 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 z 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 D0=ucW literal 0 HcmV?d00001 diff --git a/docs/SourceSerif4-Bold.ttf.woff2 b/docs/SourceSerif4-Bold.ttf.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..db57d21455c94e80aa7cca25cac803a08e1b01ec GIT binary patch literal 81320 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 N>ZPvCW|kqQ1ONsnDE9yW literal 0 HcmV?d00001 diff --git a/docs/SourceSerif4-It.ttf.woff2 b/docs/SourceSerif4-It.ttf.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1cbc021a3aa22e1469542d531e9115fb78d497ec GIT binary patch literal 59860 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 literal 0 HcmV?d00001 diff --git a/docs/SourceSerif4-LICENSE.md b/docs/SourceSerif4-LICENSE.md new file mode 100644 index 000000000000..68ea1892406c --- /dev/null +++ b/docs/SourceSerif4-LICENSE.md @@ -0,0 +1,93 @@ +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/SourceSerif4-Regular.ttf.woff2 b/docs/SourceSerif4-Regular.ttf.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2db73fe2b49e8cfcaaf1d5311a610e730b8ed502 GIT binary patch literal 76180 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 literal 0 HcmV?d00001 diff --git a/docs/ayu.css b/docs/ayu.css new file mode 100644 index 000000000000..55d0e436b71d --- /dev/null +++ b/docs/ayu.css @@ -0,0 +1 @@ + :root{--main-background-color:#0f1419;--main-color:#c5c5c5;--settings-input-color:#ffb454;--sidebar-background-color:#14191f;--sidebar-background-color-hover:rgba(70,70,70,0.33);--code-block-background-color:#191f26;--scrollbar-track-background-color:transparent;--scrollbar-thumb-background-color:#5c6773;--scrollbar-color:#5c6773 #24292f;--headings-border-bottom-color:#5c6773;--border-color:#5c6773;--button-background-color:#141920;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#5c6773;--copy-path-button-color:#fff;--copy-path-img-filter:invert(70%);--copy-path-img-hover-filter:invert(100%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ffa0a5;--trait-link-color:#39afd7;--assoc-item-link-color:#39afd7;--function-link-color:#fdd687;--macro-link-color:#a37acc;--keyword-link-color:#39afd7;--mod-link-color:#39afd7;--link-color:#39afd7;--sidebar-link-color:#53b1db;--sidebar-current-link-background-color:transparent;--search-result-link-focus-background-color:#3c3c3c;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--search-color:#fff;--code-highlight-kw-color:#ff7733;--code-highlight-kw-2-color:#ff7733;--code-highlight-lifetime-color:#ff7733;--code-highlight-prelude-color:#69f2df;--code-highlight-prelude-val-color:#ff7733;--code-highlight-number-color:#b8cc52;--code-highlight-string-color:#b8cc52;--code-highlight-literal-color:#ff7733;--code-highlight-attribute-color:#e6e1cf;--code-highlight-self-color:#36a3d9;--code-highlight-macro-color:#a37acc;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#788797;--code-highlight-doc-comment-color:#a1ac88;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}h1,h2,h3,h4{color:white;}h1 a{color:#fff;}h4{border:none;}.docblock code{color:#ffb454;}.code-header{color:#e6e1cf;}.docblock pre>code,pre>code{color:#e6e1cf;}.item-info code{color:#e6e1cf;}.docblock a>code{color:#39AFD7 !important;}pre,.rustdoc.source .example-wrap{color:#e6e1cf;}.rust-logo{filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);}.sidebar .current,.sidebar a:hover{color:#ffb44c;}.sidebar-elems .location{color:#ff7733;}.src-line-numbers span{color:#5c6773;}.src-line-numbers .line-highlighted{color:#708090;background-color:rgba(255,236,164,0.06);padding-right:4px;border-right:1px solid #ffb44c;}.search-results a:hover{color:#fff !important;background-color:#3c3c3c;}.search-results a:focus{color:#fff !important;background-color:#3c3c3c;}.search-results a{color:#0096cf;}.search-results a div.desc{color:#c5c5c5;}.content .item-info::before{color:#ccc;}.sidebar h2 a,.sidebar h3 a{color:white;}body.source .example-wrap pre.rust a{background:#333;}details.rustdoc-toggle>summary::before{filter:invert(100%);}#crate-search-div::after{filter:invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%);}#crate-search:hover,#crate-search:focus{border-color:#e0e0e0 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%);}.module-item .stab,.import-item .stab{color:#000;}.result-name .primitive>i,.result-name .keyword>i{color:#788797;}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{color:#5c67736e;border:none;}a.test-arrow{font-size:100%;color:#788797;border-radius:4px;background-color:rgba(57,175,215,0.09);}a.test-arrow:hover{background-color:rgba(57,175,215,0.368);color:#c5c5c5;}:target{background:rgba(255,236,164,0.06);border-right:3px solid rgba(255,180,76,0.85);}.search-failed a{color:#39AFD7;}.tooltip::after{background-color:#314559;color:#c5c5c5;}.tooltip::before{border-color:transparent #314559 transparent transparent;}.notable-traits-tooltiptext{background-color:#314559;}#titles>button.selected{background-color:#141920 !important;border-bottom:1px solid #ffb44c !important;border-top:none;}#titles>button:not(.selected){background-color:transparent !important;border:none;}#titles>button:hover{border-bottom:1px solid rgba(242,151,24,0.3);}#titles>button>div.count{color:#888;}pre.rust .lifetime{}pre.rust .kw{}#titles>button:hover,#titles>button.selected{}pre.rust .self,pre.rust .bool-val,pre.rust .prelude-val,pre.rust .attribute{}pre.rust .kw-2,pre.rust .prelude-ty{}kbd{color:#c5c5c5;background-color:#314559;box-shadow:inset 0 -1px 0 #5c6773;}#settings-menu>a,#help-button>a{color:#fff;}#settings-menu>a img{filter:invert(100);}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#e0e0e0;}.search-results .result-name span.alias{color:#c5c5c5;}.search-results .result-name span.grey{color:#999;}#source-sidebar>.title{color:#fff;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#14191f;color:#ffb44c;}#source-sidebar div.files>a.selected{background-color:#14191f;color:#ffb44c;}.scraped-example-list .scrape-help{border-color:#aaa;color:#eee;}.scraped-example-list .scrape-help:hover{border-color:white;color:white;}.scraped-example .example-wrap .rust span.highlight{background:rgb(91,59,1);}.scraped-example .example-wrap .rust span.highlight.focus{background:rgb(124,75,15);}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(15,20,25,1),rgba(15,20,25,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(15,20,25,1),rgba(15,20,25,0));}.toggle-line-inner{background:#999;}.toggle-line:hover .toggle-line-inner{background:#c5c5c5;} \ No newline at end of file diff --git a/docs/clipboard.svg b/docs/clipboard.svg new file mode 100644 index 000000000000..8adbd9963048 --- /dev/null +++ b/docs/clipboard.svg @@ -0,0 +1 @@ + diff --git a/docs/crates.js b/docs/crates.js new file mode 100644 index 000000000000..2fa17f411555 --- /dev/null +++ b/docs/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["ahash","aho_corasick","aiofut","async_trait","bincode","bitflags","block_buffer","byteorder","bytes","cfg_if","cpufeatures","crc","crc_catalog","crossbeam_channel","crossbeam_utils","crunchy","crypto_common","digest","enum_as_inner","firewood","fixed_hash","futures","futures_channel","futures_core","futures_executor","futures_io","futures_macro","futures_sink","futures_task","futures_util","generic_array","getrandom","growthring","hashbrown","heck","hex","impl_rlp","keccak","libc","lock_api","lru","memchr","memoffset","nix","once_cell","parking_lot","parking_lot_core","pin_project_lite","pin_utils","ppv_lite86","primitive_types","proc_macro2","quote","rand","rand_chacha","rand_core","regex","regex_syntax","rlp","rustc_hex","scan_fmt","scopeguard","serde","serde_derive","sha3","shale","slab","smallvec","static_assertions","syn","tokio","tokio_macros","typed_builder","typenum","uint","unicode_ident"]; \ No newline at end of file diff --git a/docs/dark.css b/docs/dark.css new file mode 100644 index 000000000000..7f314acf1230 --- /dev/null +++ b/docs/dark.css @@ -0,0 +1 @@ +:root{--main-background-color:#353535;--main-color:#ddd;--settings-input-color:#2196f3;--sidebar-background-color:#505050;--sidebar-background-color-hover:#676767;--code-block-background-color:#2A2A2A;--scrollbar-track-background-color:#717171;--scrollbar-thumb-background-color:rgba(32,34,37,.6);--scrollbar-color:rgba(32,34,37,.6) #5a5a5a;--headings-border-bottom-color:#d2d2d2;--border-color:#e0e0e0;--button-background-color:#f0f0f0;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#008dfd;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(65%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#2dbfb8;--trait-link-color:#b78cf2;--assoc-item-link-color:#d2991d;--function-link-color:#2bab63;--macro-link-color:#09bd00;--keyword-link-color:#d2991d;--mod-link-color:#d2991d;--link-color:#d2991d;--sidebar-link-color:#fdbf35;--sidebar-current-link-background-color:#444;--search-result-link-focus-background-color:#616161;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--search-color:#111;--code-highlight-kw-color:#ab8ac1;--code-highlight-kw-2-color:#769acb;--code-highlight-lifetime-color:#d97f26;--code-highlight-prelude-color:#769acb;--code-highlight-prelude-val-color:#ee6868;--code-highlight-number-color:#83a300;--code-highlight-string-color:#83a300;--code-highlight-literal-color:#ee6868;--code-highlight-attribute-color:#ee6868;--code-highlight-self-color:#ee6868;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8d8d8b;--code-highlight-doc-comment-color:#8ca375;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}.rust-logo{filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff)}.src-line-numbers span{color:#3B91E2;}.src-line-numbers .line-highlighted{background-color:#0a042f !important;}.content .item-info::before{color:#ccc;}body.source .example-wrap pre.rust a{background:#333;}details.rustdoc-toggle>summary::before{filter:invert(100%);}#crate-search-div::after{filter:invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);}#crate-search:hover,#crate-search:focus{border-color:#2196f3 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{border-color:#4a4949;}a.test-arrow{color:#dedede;background-color:rgba(78,139,202,0.2);}a.test-arrow:hover{background-color:#4e8bca;}:target{background-color:#494a3d;border-right:3px solid #bb7410;}.search-failed a{color:#0089ff;}.tooltip::after{background-color:#000;color:#fff;border-color:#000;}.tooltip::before{border-color:transparent black transparent transparent;}.notable-traits-tooltiptext{background-color:#111;}#titles>button:not(.selected){background-color:#252525;border-top-color:#252525;}#titles>button:hover,#titles>button.selected{border-top-color:#0089ff;background-color:#353535;}#titles>button>div.count{color:#888;}kbd{color:#000;background-color:#fafbfc;box-shadow:inset 0 -1px 0 #c6cbd1;}#settings-menu>a,#help-button>a{color:#000;}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#ffb900;}.search-results .result-name span.alias{color:#fff;}.search-results .result-name span.grey{color:#ccc;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#444;}#source-sidebar div.files>a.selected{background-color:#333;}.scraped-example-list .scrape-help{border-color:#aaa;color:#eee;}.scraped-example-list .scrape-help:hover{border-color:white;color:white;}.scraped-example .example-wrap .rust span.highlight{background:rgb(91,59,1);}.scraped-example .example-wrap .rust span.highlight.focus{background:rgb(124,75,15);}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(53,53,53,1),rgba(53,53,53,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(53,53,53,1),rgba(53,53,53,0));}.toggle-line-inner{background:#999;}.toggle-line:hover .toggle-line-inner{background:#c5c5c5;} \ No newline at end of file diff --git a/docs/down-arrow.svg b/docs/down-arrow.svg new file mode 100644 index 000000000000..5d76a64e92c7 --- /dev/null +++ b/docs/down-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/favicon-16x16.png b/docs/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ea4b45cae1618e6e20e6d61897da953f34b66b30 GIT binary patch literal 715 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L9P|ym58ULAS1_A}yOFVsD*`G1;@(QxEEoEaloaX$3oUhmn3B1b>^ zXS8u>Ub4$r*u2F1FEiJ>rQJ&x%nC}Q!>*kacg*^JAVaGgCxj?;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UT zt3o15f)dLW3X1a6GILTDN-7Id6*3D-k{K8(<~;ty!%-Nfp>fLp^cl~mK@7~w+k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z!DH1*13q>1OJmp-&A)3@J_A;A0V7u0bkfJ)-`Krrb zGey1(YybBo_r#8#3kPrfo#tA4xb3R$F4j$a9H~9huUPE$JQDstTM~O>^@Ps(;>UH<3Fx0=LBZUre@8723HjVToWun1;~KVtGY7SF1E7liM5< zHa%KaZ1y+v;MF5PX0dXM#bh1)zI}?P`JCclPp)GktoyD%Uv%1HzQp-VwViVJr}FN4 zBVAi3pdsabJ2zzio=sD>mtWX++u%m3k>>5t|1&=?+*B*EnLW)#$^O=9J{D1Fvz#4w zCmkrSML-}_v8Imc2?OP1;|%KWmLM+u&^dKy+fI{C57UY0UhRg-3U_ zKl;3k)jRBCi*uZh#-8L8Gwj!FXV37syULEeYD%&1+S-jgUC&wB|>?y4oO5hW>!C8<`)MX5lF!N|bKNY}tn*U&h` z(Adh*+{(a0+rYrez#!Wq{4a`z-29Zxv`X9>q*C7l^C^QQ$cEtjw370~qEv?R@^Zb* zyzJuS#DY}4{G#;P?`))iio&ZxB1(c1%M}WW^3yVNQWZ)n3sMy_3rdn17%JvG{=~yk z7^b0d%K!8k&!<5Q%*xz)$=t%q!rqfbn1vNw8cYtSFe`5kQ8<0$%84Uqj>sHgKi%N5 cz)O$emAGKZCnwXXKr0wLUHx3vIVCg!0EmFw6951J literal 0 HcmV?d00001 diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 000000000000..8b34b511989e --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/docs/firewood/all.html b/docs/firewood/all.html new file mode 100644 index 000000000000..0e910a2644aa --- /dev/null +++ b/docs/firewood/all.html @@ -0,0 +1 @@ +List of all items in this crate

\ No newline at end of file diff --git a/docs/firewood/db/enum.DBError.html b/docs/firewood/db/enum.DBError.html new file mode 100644 index 000000000000..40de0cb06195 --- /dev/null +++ b/docs/firewood/db/enum.DBError.html @@ -0,0 +1,10 @@ +DBError in firewood::db - Rust
pub enum DBError {
+    InvalidParams,
+    Merkle(MerkleError),
+    Blob(BlobError),
+    System(Error),
+}

Variants

InvalidParams

Merkle(MerkleError)

Blob(BlobError)

System(Error)

Trait Implementations

Formats the value using the given formatter. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/index.html b/docs/firewood/db/index.html new file mode 100644 index 000000000000..7a941c876afd --- /dev/null +++ b/docs/firewood/db/index.html @@ -0,0 +1,3 @@ +firewood::db - Rust

Structs

Firewood database handle.
Database configuration.
Some readable version of the DB.
Config for accessing a version of the DB.
Config for the disk buffer.
Lock protected handle to a readable version of the DB.
An atomic batch of changes made to the DB. Each operation on a WriteBatch will move itself +because when an error occurs, the write batch will be automaticlaly aborted so that the DB +remains clean.

Enums

\ No newline at end of file diff --git a/docs/firewood/db/sidebar-items.js b/docs/firewood/db/sidebar-items.js new file mode 100644 index 000000000000..402fc96865f1 --- /dev/null +++ b/docs/firewood/db/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":[["DBError",""]],"struct":[["DB","Firewood database handle."],["DBConfig","Database configuration."],["DBRev","Some readable version of the DB."],["DBRevConfig","Config for accessing a version of the DB."],["DiskBufferConfig","Config for the disk buffer."],["Revision","Lock protected handle to a readable version of the DB."],["WALConfig",""],["WriteBatch","An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself because when an error occurs, the write batch will be automaticlaly aborted so that the DB remains clean."]]}; \ No newline at end of file diff --git a/docs/firewood/db/struct.DB.html b/docs/firewood/db/struct.DB.html new file mode 100644 index 000000000000..e65067945351 --- /dev/null +++ b/docs/firewood/db/struct.DB.html @@ -0,0 +1,19 @@ +DB in firewood::db - Rust
pub struct DB { /* private fields */ }
Expand description

Firewood database handle.

+

Implementations

Open a database.

+

Create a write batch.

+

Dump the MPT of the latest generic key-value storage.

+

Dump the MPT of the latest entire account model storage.

+

Dump the MPT of the latest state storage under an account.

+

Get root hash of the latest generic key-value storage.

+

Get root hash of the latest world state of all accounts.

+

Get the latest balance of the account.

+

Get the latest code of the account.

+

Get the latest nonce of the account.

+

Get the latest state value indexed by sub_key in the account indexed by key.

+

Check if the account exists in the latest world state.

+

Get a handle that grants the access to some historical state of the entire DB.

+

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.DBConfig.html b/docs/firewood/db/struct.DBConfig.html new file mode 100644 index 000000000000..871c8fb767aa --- /dev/null +++ b/docs/firewood/db/struct.DBConfig.html @@ -0,0 +1,9 @@ +DBConfig in firewood::db - Rust
pub struct DBConfig { /* private fields */ }
Expand description

Database configuration.

+

Implementations

Create a builder for building DBConfig. +On the builder, call .meta_ncached_pages(...)(optional), .meta_ncached_files(...)(optional), .meta_file_nbit(...)(optional), .payload_ncached_pages(...)(optional), .payload_ncached_files(...)(optional), .payload_file_nbit(...)(optional), .payload_max_walk(...)(optional), .payload_regn_nbit(...)(optional), .truncate(...)(optional), .rev(...)(optional), .buffer(...)(optional), .wal(...)(optional) to set the values of the fields. +Finally, call .build() to create the instance of DBConfig.

+

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.DBRev.html b/docs/firewood/db/struct.DBRev.html new file mode 100644 index 000000000000..e7e85037a4eb --- /dev/null +++ b/docs/firewood/db/struct.DBRev.html @@ -0,0 +1,16 @@ +DBRev in firewood::db - Rust
pub struct DBRev { /* private fields */ }
Expand description

Some readable version of the DB.

+

Implementations

Get root hash of the generic key-value storage.

+

Dump the MPT of the generic key-value storage.

+

Get root hash of the world state of all accounts.

+

Dump the MPT of the entire account model storage.

+

Dump the MPT of the state storage under an account.

+

Get balance of the account.

+

Get code of the account.

+

Get nonce of the account.

+

Get the state value indexed by sub_key in the account indexed by key.

+

Check if the account exists.

+

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.DBRevConfig.html b/docs/firewood/db/struct.DBRevConfig.html new file mode 100644 index 000000000000..836ef38eb498 --- /dev/null +++ b/docs/firewood/db/struct.DBRevConfig.html @@ -0,0 +1,9 @@ +DBRevConfig in firewood::db - Rust
pub struct DBRevConfig { /* private fields */ }
Expand description

Config for accessing a version of the DB.

+

Implementations

Create a builder for building DBRevConfig. +On the builder, call .merkle_ncached_objs(...)(optional), .blob_ncached_objs(...)(optional) to set the values of the fields. +Finally, call .build() to create the instance of DBRevConfig.

+

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.DiskBufferConfig.html b/docs/firewood/db/struct.DiskBufferConfig.html new file mode 100644 index 000000000000..d7b42c10261b --- /dev/null +++ b/docs/firewood/db/struct.DiskBufferConfig.html @@ -0,0 +1,9 @@ +DiskBufferConfig in firewood::db - Rust
pub struct DiskBufferConfig { /* private fields */ }
Expand description

Config for the disk buffer.

+

Implementations

Create a builder for building DiskBufferConfig. +On the builder, call .max_buffered(...)(optional), .max_pending(...)(optional), .max_aio_requests(...)(optional), .max_aio_response(...)(optional), .max_aio_submit(...)(optional), .wal_max_aio_requests(...)(optional), .wal_max_buffered(...)(optional), .wal_max_batch(...)(optional) to set the values of the fields. +Finally, call .build() to create the instance of DiskBufferConfig.

+

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.Revision.html b/docs/firewood/db/struct.Revision.html new file mode 100644 index 000000000000..b7a7003ce3a7 --- /dev/null +++ b/docs/firewood/db/struct.Revision.html @@ -0,0 +1,16 @@ +Revision in firewood::db - Rust
pub struct Revision<'a> { /* private fields */ }
Expand description

Lock protected handle to a readable version of the DB.

+

Methods from Deref<Target = DBRev>

Get root hash of the generic key-value storage.

+

Dump the MPT of the generic key-value storage.

+

Get root hash of the world state of all accounts.

+

Dump the MPT of the entire account model storage.

+

Dump the MPT of the state storage under an account.

+

Get balance of the account.

+

Get code of the account.

+

Get nonce of the account.

+

Get the state value indexed by sub_key in the account indexed by key.

+

Check if the account exists.

+

Trait Implementations

The resulting type after dereferencing.
Dereferences the value.

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.WALConfig.html b/docs/firewood/db/struct.WALConfig.html new file mode 100644 index 000000000000..df3384529383 --- /dev/null +++ b/docs/firewood/db/struct.WALConfig.html @@ -0,0 +1,8 @@ +WALConfig in firewood::db - Rust
pub struct WALConfig { /* private fields */ }

Implementations

Create a builder for building WALConfig. +On the builder, call .file_nbit(...)(optional), .block_nbit(...)(optional), .max_revisions(...)(optional) to set the values of the fields. +Finally, call .build() to create the instance of WALConfig.

+

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/db/struct.WriteBatch.html b/docs/firewood/db/struct.WriteBatch.html new file mode 100644 index 000000000000..10010a981e5d --- /dev/null +++ b/docs/firewood/db/struct.WriteBatch.html @@ -0,0 +1,21 @@ +WriteBatch in firewood::db - Rust
pub struct WriteBatch<'a> { /* private fields */ }
Expand description

An atomic batch of changes made to the DB. Each operation on a WriteBatch will move itself +because when an error occurs, the write batch will be automaticlaly aborted so that the DB +remains clean.

+

Implementations

Insert an item to the generic key-value storage.

+

Remove an item from the generic key-value storage. val will be set to the value that is +removed from the storage if it exists.

+

Set balance of the account.

+

Set code of the account.

+

Set nonce of the account.

+

Set the state value indexed by sub_key in the account indexed by key.

+

Create an account.

+

Delete an account.

+

Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root +hashes to future invocation of root_hash, kv_root_hash or batch commits.

+

Persist all changes to the DB. The atomicity of the WriteBatch guarantees all changes are +either retained on disk or lost together during a crash.

+

Trait Implementations

Executes the destructor for this type. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/index.html b/docs/firewood/index.html new file mode 100644 index 000000000000..6fee912c7079 --- /dev/null +++ b/docs/firewood/index.html @@ -0,0 +1,197 @@ +firewood - Rust
Expand description

Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval.

+

Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes +access to latest state, by providing extremely fast reads, but also provides a limited view +into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever +growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk +and apply in-place updates to it. This ensures that the database size is small and stable +during the course of running firewood. Firewood was first conceived to provide a very fast +storage layer for qEVM to enable a fast, complete EVM system with right design choices made +totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible +blockchain storage system, and fits for the general use of a certified key-value store of +arbitrary data.

+

Firewood is a robust database implemented from the ground up to directly store MPT nodes and +user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a +generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses +the tree structure as the index on disk. Thus, there is no additional “emulation” of the +logical MPT to flatten out the data structure to feed into the underlying DB that is unaware +of the data being stored.

+

Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees +atomicity and durability in the database, but also offers “reversibility”: some portion +of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover +some past versions of the entire store back in memory. While running the store, new changes +will also contribute to the configured window of changes (at batch granularity) to access any past +versions with no additional cost at all.

+

The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage +space which can be both or selectively used the user. The account model portion of the storage +offers something very similar to StateDB in geth, which captures the address-“state key” +style of two-level access for an account’s (smart contract’s) state. Therefore, it takes +minimal effort to delegate all state storage from an EVM implementation to firewood. The other +portion of the storage supports generic MPT storage for arbitrary keys and values. When unused, +there is no additional cost.

+

Design Philosophy & Overview

+

With some on-going academic research efforts and increasing demand of faster local storage +solutions for the chain state, we realized there are mainly two different regimes of designs.

+
    +
  • +

    “Archival” Storage: this style of design emphasizes on the ability to hold all historical +data and retrieve a revision of any wold state at a reasonable performance. To economically +store all historical certified data, usually copy-on-write merkle tries are used to just +capture the changes made by a committed block. The entire storage consists of a forest of these +“delta” tries. The total size of the storage will keep growing over the chain length and an ideal, +well-executed plan for this is to make sure the performance degradation is reasonable or +well-contained with respect to the ever-increasing size of the index. This design is useful +for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a +query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also +need this because the “canonical” branch of the chain could switch (but not necessarily a +practical concern nowadays) to a different fork at times.

    +
  • +
  • +

    “Validation” Storage: this regime optimizes for the storage footprint and the performance of +operations upon the latest/recent states. With the assumption that the chain’s total state +size is relatively stable over ever-coming blocks, one can just make the latest state +persisted and available to the blockchain system as that’s what matters for most of the time. +While one can still keep some volatile state versions in memory for mutation and VM +execution, the final commit to some state works on a singleton so the indexed merkle tries +may be typically updated in place. It is also possible (e.g., firewood) to allow some +infrequent access to historical versions with higher cost, and/or allow fast access to +versions of the store within certain limited recency. This style of storage is useful for +the blockchain systems where only (or mostly) the latest state is required and data footprint +should remain constant or grow slowly if possible for sustainability. Validators who +directly participate in the consensus and vote for the blocks, for example, can largely +benefit from such a design.

    +
  • +
+

In firewood, we take a closer look at the second regime and have come up with a simple but +robust architecture that fulfills the need for such blockchain storage.

+

Storage Model

+

Firewood is built by three layers of abstractions that totally decouple the +layout/representation of the data on disk from the actual logical data structure it retains:

+
    +
  • +

    Linear, memory-like space: the shale crate from an academic +project (CedrusDB) code offers a MemStore abstraction for a (64-bit) byte-addressable space +that abstracts away the intricate method that actually persists the in-memory data on the +secondary storage medium (e.g., hard drive). The implementor of MemStore will provide the +functions to give the user of MemStore an illusion that the user is operating upon a +byte-addressable memory space. It is just a “magical” array of bytes one can view and change +that is mirrored to the disk. In reality, the linear space will be chunked into files under a +directory, but the user does not have to even know about this.

    +
  • +
  • +

    Persistent item storage stash: ShaleStore trait from shale defines a pool of typed +objects that are persisted on disk but also made accessible in memory transparently. It is +built on top of MemStore by defining how “items” of the given type are laid out, allocated +and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of +basic implementation in shale crate, but one can always define his/her own ShaleStore).

    +
  • +
  • +

    Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking +ShaleStore (see src/merkle.rs; another stash for code objects is in src/account.rs). +The data structure code is totally unaware of how its objects (i.e., nodes) are organized or +persisted on disk. It is as if they’re just in memory, which makes it much easier to write +and maintain the code.

    +
  • +
+

The three layers are depicted as follows:

+

+ +

+

Given the abstraction, one can easily realize the fact that the actual data that affect the +state of the data structure (MPT) is what the linear space (MemStore) keeps track of, that is, +a flat but conceptually large byte vector. In other words, given a valid byte vector as the +content of the linear space, the higher level data structure can be uniquely determined, there +is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) +or less than that, like a way to interpret the bytes. This nice property allows us to completely +separate the logical data from its physical representation, greatly simplifies the storage +management, and allows reusing the code. It is still a very versatile abstraction, as in theory +any persistent data could be stored this way – sometimes you need to swap in a different +MemShale or MemStore implementation, but without having to touch the code for the persisted +data structure.

+

Page-based Shadowing and Revisions

+

Following the idea that the MPTs are just a view of a linear byte space, all writes made to the +MPTs inside Firewood will eventually be consolidated into some interval writes to the linear +space. The writes may overlap and some frequent writes are even done to the same spot in the +space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit +virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the +dirty pages in some MemStore instantiation (see storage::StoreRevMut). When a +db::WriteBatch commits, both the recorded interval writes and the aggregated in-memory +dirty pages induced by this write batch are taken out from the linear space. Although they are +mathematically equivalent, interval writes are more compact than pages (which are 4K in size, +become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL +subsystem (supported by growthring). After the +WAL record is written (one record per write batch), the dirty pages are then pushed to the +on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See +the BufferCmd::WriteBatch part of DiskBuffer::process for the detailed logic.

+

In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood:

+
    +
  • +

    Traverse the MPT, and that induces the access to some nodes. Suppose the nodes are not already in +memory, then:

    +
  • +
  • +

    Bring the necessary pages that contain the accessed nodes into the memory and cache them +(storage::CachedSpace).

    +
  • +
  • +

    Make changes to the MPT, and that induces the writes to some nodes. The nodes are either +already cached in memory (its pages are cached, or its handle ObjRef<Node> is still in +shale::ObjCache) or need to be brought into the memory (if that’s the case, go back to the +second step for it).

    +
  • +
  • +

    Writes to nodes are converted into interval writes to the stagging StoreRevMut space that +overlays atop CachedSpace, so all dirty pages during the current write batch will be +exactly captured in StoreRevMut (see StoreRevMut::take_delta).

    +
  • +
  • +

    Finally:

    +
      +
    • +

      Abort: when the write batch is dropped without invoking db::WriteBatch::commit, all in-memory +changes will be discarded, the dirty pages from StoreRevMut will be dropped and the merkle +will “revert” back to its original state without actually having to rollback anything.

      +
    • +
    • +

      Commit: otherwise, the write batch is committed, the interval writes (storage::Ash) will be bundled +into a single WAL record (storage::AshRecord) and sent to WAL subsystem, before dirty pages +are scheduled to be written to the space files. Also the dirty pages are applied to the +underlying CachedSpace. StoreRevMut becomes empty again for further write batches.

      +
    • +
    +
  • +
+

Parts of the following diagram show this normal flow, the “staging” space (implemented by +StoreRevMut) concept is a bit similar to the staging area in Git, which enables the handling +of (resuming from) write errors, clean abortion of an on-going write batch so the entire store +state remains intact, and also reduces unnecessary premature disk writes. Essentially, we +copy-on-write pages in the space that are touched upon, without directly mutating the +underlying “master” space. The staging space is just a collection of these “shadowing” pages +and a reference to the its base (master) so any reads could partially hit those dirty pages +and/or fall through to the base, whereas all writes are captured. Finally, when things go well, +we “push down” these changes to the base and clear up the staging space.

+

+ +

+

Thanks to the shadow pages, we can both revive some historical versions of the store and +maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram +shows previously logged write batch records could be kept even though they are no longer needed +for the purpose of crash recovery. The interval writes from a record can be aggregated into +pages (see storage::StoreDelta::new) and used to reconstruct a “ghost” image of past +revision of the linear space (just like how staging space works, except that the ghost space is +essentially read-only once constructed). The shadow pages there will function as some +“rewinding” changes to patch the necessary locations in the linear space, while the rest of the +linear space is very likely untouched by that historical write batch.

+

Then, with the three-layer abstraction we previously talked about, an historical MPT could be +derived. In fact, because there is no mandatory traversal or scanning in the process, the +only cost to revive a historical state from the log is to just playback the records and create +those shadow pages. There is very little additional cost because the ghost space is summoned on an +on-demand manner while one accesses the historical MPT.

+

In the other direction, when new write batches are committed, the system moves forward, we can +therefore maintain a rolling window of past revisions in memory with zero cost. The +mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one +step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be +created to hold the version of the store before the commit. The backward delta is applied to +counteract the change that has been made to the persisted store, which is also a set of shadow pages. +No change is required for other historical ghost space instances. Finally, we can phase out +some very old ghost space to keep the size of the rolling window invariant.

+

Modules

\ No newline at end of file diff --git a/docs/firewood/merkle/enum.MerkleError.html b/docs/firewood/merkle/enum.MerkleError.html new file mode 100644 index 000000000000..67bf18d7275f --- /dev/null +++ b/docs/firewood/merkle/enum.MerkleError.html @@ -0,0 +1,10 @@ +MerkleError in firewood::merkle - Rust
pub enum MerkleError {
+    Shale(ShaleError),
+    ReadOnly,
+    NotBranchNode,
+    Format(Error),
+}

Variants

Shale(ShaleError)

ReadOnly

NotBranchNode

Format(Error)

Trait Implementations

Formats the value using the given formatter. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/index.html b/docs/firewood/merkle/index.html new file mode 100644 index 000000000000..b0ea654f14e6 --- /dev/null +++ b/docs/firewood/merkle/index.html @@ -0,0 +1 @@ +firewood::merkle - Rust
\ No newline at end of file diff --git a/docs/firewood/merkle/sidebar-items.js b/docs/firewood/merkle/sidebar-items.js new file mode 100644 index 000000000000..6cafd793bb93 --- /dev/null +++ b/docs/firewood/merkle/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":[["MerkleError",""]],"fn":[["from_nibbles",""],["to_nibbles",""]],"struct":[["Hash",""],["IdTrans",""],["Merkle",""],["Node",""],["PartialPath","PartialPath keeps a list of nibbles to represent a path on the MPT."],["Ref",""],["RefMut",""]],"trait":[["ValueTransformer",""]]}; \ No newline at end of file diff --git a/docs/firewood/merkle/struct.Hash.html b/docs/firewood/merkle/struct.Hash.html new file mode 100644 index 000000000000..e4df59470165 --- /dev/null +++ b/docs/firewood/merkle/struct.Hash.html @@ -0,0 +1,84 @@ +Hash in firewood::merkle - Rust
pub struct Hash(pub [u8; 32]);

Tuple Fields

0: [u8; 32]

Methods from Deref<Target = [u8; 32]>

Returns a slice containing the entire array. Equivalent to &s[..].

+
🔬This is a nightly-only experimental API. (array_methods)

Borrows each element and returns an array of references with the same +size as self.

+
Example
+
#![feature(array_methods)]
+
+let floats = [3.1, 2.7, -1.0];
+let float_refs: [&f64; 3] = floats.each_ref();
+assert_eq!(float_refs, [&3.1, &2.7, &-1.0]);
+

This method is particularly useful if combined with other methods, like +map. This way, you can avoid moving the original +array if its elements are not Copy.

+ +
#![feature(array_methods)]
+
+let strings = ["Ferris".to_string(), "♥".to_string(), "Rust".to_string()];
+let is_ascii = strings.each_ref().map(|s| s.is_ascii());
+assert_eq!(is_ascii, [true, false, true]);
+
+// We can still access the original array: it has not been moved.
+assert_eq!(strings.len(), 3);
+
🔬This is a nightly-only experimental API. (split_array)

Divides one array reference into two at an index.

+

The first will contain all indices from [0, M) (excluding +the index M itself) and the second will contain all +indices from [M, N) (excluding the index N itself).

+
Panics
+

Panics if M > N.

+
Examples
+
#![feature(split_array)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+{
+   let (left, right) = v.split_array_ref::<0>();
+   assert_eq!(left, &[]);
+   assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_array_ref::<2>();
+    assert_eq!(left, &[1, 2]);
+    assert_eq!(right, &[3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_array_ref::<6>();
+    assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+    assert_eq!(right, &[]);
+}
+
🔬This is a nightly-only experimental API. (split_array)

Divides one array reference into two at an index from the end.

+

The first will contain all indices from [0, N - M) (excluding +the index N - M itself) and the second will contain all +indices from [N - M, N) (excluding the index N itself).

+
Panics
+

Panics if M > N.

+
Examples
+
#![feature(split_array)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+{
+   let (left, right) = v.rsplit_array_ref::<0>();
+   assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+   assert_eq!(right, &[]);
+}
+
+{
+    let (left, right) = v.rsplit_array_ref::<2>();
+    assert_eq!(left, &[1, 2, 3, 4]);
+    assert_eq!(right, &[5, 6]);
+}
+
+{
+    let (left, right) = v.rsplit_array_ref::<6>();
+    assert_eq!(left, &[]);
+    assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
+

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more
The resulting type after dereferencing.
Dereferences the value.
This method tests for self and other values to be equal, and is used +by ==. Read more
This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/struct.IdTrans.html b/docs/firewood/merkle/struct.IdTrans.html new file mode 100644 index 000000000000..6701f80ede67 --- /dev/null +++ b/docs/firewood/merkle/struct.IdTrans.html @@ -0,0 +1,5 @@ +IdTrans in firewood::merkle - Rust
pub struct IdTrans;

Trait Implementations

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/struct.Merkle.html b/docs/firewood/merkle/struct.Merkle.html new file mode 100644 index 000000000000..17fbe7060cb9 --- /dev/null +++ b/docs/firewood/merkle/struct.Merkle.html @@ -0,0 +1,11 @@ +Merkle in firewood::merkle - Rust
pub struct Merkle { /* private fields */ }

Implementations

Constructs a merkle proof for key. The result contains all encoded nodes +on the path to the value at key. The value itself is also included in the +last node and can be retrieved by verifying the proof.

+

If the trie does not contain a value for key, the returned proof contains +all nodes of the longest existing prefix of the key, ending with the node +that proves the absence of the key (at least the root node).

+

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/struct.Node.html b/docs/firewood/merkle/struct.Node.html new file mode 100644 index 000000000000..a5377eb6f224 --- /dev/null +++ b/docs/firewood/merkle/struct.Node.html @@ -0,0 +1,7 @@ +Node in firewood::merkle - Rust
pub struct Node { /* private fields */ }

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more
This method tests for self and other values to be equal, and is used +by ==. Read more
This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/struct.Ref.html b/docs/firewood/merkle/struct.Ref.html new file mode 100644 index 000000000000..8fbb6bd66eff --- /dev/null +++ b/docs/firewood/merkle/struct.Ref.html @@ -0,0 +1,934 @@ +Ref in firewood::merkle - Rust
pub struct Ref<'a>(_);

Methods from Deref<Target = [u8]>

Returns the number of elements in the slice.

+
Examples
+
let a = [1, 2, 3];
+assert_eq!(a.len(), 3);
+

Returns true if the slice has a length of 0.

+
Examples
+
let a = [1, 2, 3];
+assert!(!a.is_empty());
+

Returns the first element of the slice, or None if it is empty.

+
Examples
+
let v = [10, 40, 30];
+assert_eq!(Some(&10), v.first());
+
+let w: &[i32] = &[];
+assert_eq!(None, w.first());
+

Returns the first and all the rest of the elements of the slice, or None if it is empty.

+
Examples
+
let x = &[0, 1, 2];
+
+if let Some((first, elements)) = x.split_first() {
+    assert_eq!(first, &0);
+    assert_eq!(elements, &[1, 2]);
+}
+

Returns the last and all the rest of the elements of the slice, or None if it is empty.

+
Examples
+
let x = &[0, 1, 2];
+
+if let Some((last, elements)) = x.split_last() {
+    assert_eq!(last, &2);
+    assert_eq!(elements, &[0, 1]);
+}
+

Returns the last element of the slice, or None if it is empty.

+
Examples
+
let v = [10, 40, 30];
+assert_eq!(Some(&30), v.last());
+
+let w: &[i32] = &[];
+assert_eq!(None, w.last());
+

Returns a reference to an element or subslice depending on the type of +index.

+
    +
  • If given a position, returns a reference to the element at that +position or None if out of bounds.
  • +
  • If given a range, returns the subslice corresponding to that range, +or None if out of bounds.
  • +
+
Examples
+
let v = [10, 40, 30];
+assert_eq!(Some(&40), v.get(1));
+assert_eq!(Some(&[10, 40][..]), v.get(0..2));
+assert_eq!(None, v.get(3));
+assert_eq!(None, v.get(0..4));
+

Returns a reference to an element or subslice, without doing bounds +checking.

+

For a safe alternative see get.

+
Safety
+

Calling this method with an out-of-bounds index is undefined behavior +even if the resulting reference is not used.

+
Examples
+
let x = &[1, 2, 4];
+
+unsafe {
+    assert_eq!(x.get_unchecked(1), &2);
+}
+

Returns a raw pointer to the slice’s buffer.

+

The caller must ensure that the slice outlives the pointer this +function returns, or else it will end up pointing to garbage.

+

The caller must also ensure that the memory the pointer (non-transitively) points to +is never written to (except inside an UnsafeCell) using this pointer or any pointer +derived from it. If you need to mutate the contents of the slice, use as_mut_ptr.

+

Modifying the container referenced by this slice may cause its buffer +to be reallocated, which would also make any pointers to it invalid.

+
Examples
+
let x = &[1, 2, 4];
+let x_ptr = x.as_ptr();
+
+unsafe {
+    for i in 0..x.len() {
+        assert_eq!(x.get_unchecked(i), &*x_ptr.add(i));
+    }
+}
+

Returns the two raw pointers spanning the slice.

+

The returned range is half-open, which means that the end pointer +points one past the last element of the slice. This way, an empty +slice is represented by two equal pointers, and the difference between +the two pointers represents the size of the slice.

+

See as_ptr for warnings on using these pointers. The end pointer +requires extra caution, as it does not point to a valid element in the +slice.

+

This function is useful for interacting with foreign interfaces which +use two pointers to refer to a range of elements in memory, as is +common in C++.

+

It can also be useful to check if a pointer to an element refers to an +element of this slice:

+ +
let a = [1, 2, 3];
+let x = &a[1] as *const _;
+let y = &5 as *const _;
+
+assert!(a.as_ptr_range().contains(&x));
+assert!(!a.as_ptr_range().contains(&y));
+

Returns an iterator over the slice.

+

The iterator yields all items from start to end.

+
Examples
+
let x = &[1, 2, 4];
+let mut iterator = x.iter();
+
+assert_eq!(iterator.next(), Some(&1));
+assert_eq!(iterator.next(), Some(&2));
+assert_eq!(iterator.next(), Some(&4));
+assert_eq!(iterator.next(), None);
+

Returns an iterator over all contiguous windows of length +size. The windows overlap. If the slice is shorter than +size, the iterator returns no values.

+
Panics
+

Panics if size is 0.

+
Examples
+
let slice = ['r', 'u', 's', 't'];
+let mut iter = slice.windows(2);
+assert_eq!(iter.next().unwrap(), &['r', 'u']);
+assert_eq!(iter.next().unwrap(), &['u', 's']);
+assert_eq!(iter.next().unwrap(), &['s', 't']);
+assert!(iter.next().is_none());
+

If the slice is shorter than size:

+ +
let slice = ['f', 'o', 'o'];
+let mut iter = slice.windows(4);
+assert!(iter.next().is_none());
+

Returns an iterator over chunk_size elements of the slice at a time, starting at the +beginning of the slice.

+

The chunks are slices and do not overlap. If chunk_size does not divide the length of the +slice, then the last chunk will not have length chunk_size.

+

See chunks_exact for a variant of this iterator that returns chunks of always exactly +chunk_size elements, and rchunks for the same iterator but starting at the end of the +slice.

+
Panics
+

Panics if chunk_size is 0.

+
Examples
+
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.chunks(2);
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert_eq!(iter.next().unwrap(), &['m']);
+assert!(iter.next().is_none());
+

Returns an iterator over chunk_size elements of the slice at a time, starting at the +beginning of the slice.

+

The chunks are slices and do not overlap. If chunk_size does not divide the length of the +slice, then the last up to chunk_size-1 elements will be omitted and can be retrieved +from the remainder function of the iterator.

+

Due to each chunk having exactly chunk_size elements, the compiler can often optimize the +resulting code better than in the case of chunks.

+

See chunks for a variant of this iterator that also returns the remainder as a smaller +chunk, and rchunks_exact for the same iterator but starting at the end of the slice.

+
Panics
+

Panics if chunk_size is 0.

+
Examples
+
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.chunks_exact(2);
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['m']);
+
🔬This is a nightly-only experimental API. (slice_as_chunks)

Splits the slice into a slice of N-element arrays, +assuming that there’s no remainder.

+
Safety
+

This may only be called when

+
    +
  • The slice splits exactly into N-element chunks (aka self.len() % N == 0).
  • +
  • N != 0.
  • +
+
Examples
+
#![feature(slice_as_chunks)]
+let slice: &[char] = &['l', 'o', 'r', 'e', 'm', '!'];
+let chunks: &[[char; 1]] =
+    // SAFETY: 1-element chunks never have remainder
+    unsafe { slice.as_chunks_unchecked() };
+assert_eq!(chunks, &[['l'], ['o'], ['r'], ['e'], ['m'], ['!']]);
+let chunks: &[[char; 3]] =
+    // SAFETY: The slice length (6) is a multiple of 3
+    unsafe { slice.as_chunks_unchecked() };
+assert_eq!(chunks, &[['l', 'o', 'r'], ['e', 'm', '!']]);
+
+// These would be unsound:
+// let chunks: &[[_; 5]] = slice.as_chunks_unchecked() // The slice length is not a multiple of 5
+// let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // Zero-length chunks are never allowed
+
🔬This is a nightly-only experimental API. (slice_as_chunks)

Splits the slice into a slice of N-element arrays, +starting at the beginning of the slice, +and a remainder slice with length strictly less than N.

+
Panics
+

Panics if N is 0. This check will most probably get changed to a compile time +error before this method gets stabilized.

+
Examples
+
#![feature(slice_as_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let (chunks, remainder) = slice.as_chunks();
+assert_eq!(chunks, &[['l', 'o'], ['r', 'e']]);
+assert_eq!(remainder, &['m']);
+
🔬This is a nightly-only experimental API. (slice_as_chunks)

Splits the slice into a slice of N-element arrays, +starting at the end of the slice, +and a remainder slice with length strictly less than N.

+
Panics
+

Panics if N is 0. This check will most probably get changed to a compile time +error before this method gets stabilized.

+
Examples
+
#![feature(slice_as_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let (remainder, chunks) = slice.as_rchunks();
+assert_eq!(remainder, &['l']);
+assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);
+
🔬This is a nightly-only experimental API. (array_chunks)

Returns an iterator over N elements of the slice at a time, starting at the +beginning of the slice.

+

The chunks are array references and do not overlap. If N does not divide the +length of the slice, then the last up to N-1 elements will be omitted and can be +retrieved from the remainder function of the iterator.

+

This method is the const generic equivalent of chunks_exact.

+
Panics
+

Panics if N is 0. This check will most probably get changed to a compile time +error before this method gets stabilized.

+
Examples
+
#![feature(array_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.array_chunks();
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['m']);
+
🔬This is a nightly-only experimental API. (array_windows)

Returns an iterator over overlapping windows of N elements of a slice, +starting at the beginning of the slice.

+

This is the const generic equivalent of windows.

+

If N is greater than the size of the slice, it will return no windows.

+
Panics
+

Panics if N is 0. This check will most probably get changed to a compile time +error before this method gets stabilized.

+
Examples
+
#![feature(array_windows)]
+let slice = [0, 1, 2, 3];
+let mut iter = slice.array_windows();
+assert_eq!(iter.next().unwrap(), &[0, 1]);
+assert_eq!(iter.next().unwrap(), &[1, 2]);
+assert_eq!(iter.next().unwrap(), &[2, 3]);
+assert!(iter.next().is_none());
+

Returns an iterator over chunk_size elements of the slice at a time, starting at the end +of the slice.

+

The chunks are slices and do not overlap. If chunk_size does not divide the length of the +slice, then the last chunk will not have length chunk_size.

+

See rchunks_exact for a variant of this iterator that returns chunks of always exactly +chunk_size elements, and chunks for the same iterator but starting at the beginning +of the slice.

+
Panics
+

Panics if chunk_size is 0.

+
Examples
+
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.rchunks(2);
+assert_eq!(iter.next().unwrap(), &['e', 'm']);
+assert_eq!(iter.next().unwrap(), &['o', 'r']);
+assert_eq!(iter.next().unwrap(), &['l']);
+assert!(iter.next().is_none());
+

Returns an iterator over chunk_size elements of the slice at a time, starting at the +end of the slice.

+

The chunks are slices and do not overlap. If chunk_size does not divide the length of the +slice, then the last up to chunk_size-1 elements will be omitted and can be retrieved +from the remainder function of the iterator.

+

Due to each chunk having exactly chunk_size elements, the compiler can often optimize the +resulting code better than in the case of rchunks.

+

See rchunks for a variant of this iterator that also returns the remainder as a smaller +chunk, and chunks_exact for the same iterator but starting at the beginning of the +slice.

+
Panics
+

Panics if chunk_size is 0.

+
Examples
+
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.rchunks_exact(2);
+assert_eq!(iter.next().unwrap(), &['e', 'm']);
+assert_eq!(iter.next().unwrap(), &['o', 'r']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['l']);
+
🔬This is a nightly-only experimental API. (slice_group_by)

Returns an iterator over the slice producing non-overlapping runs +of elements using the predicate to separate them.

+

The predicate is called on two elements following themselves, +it means the predicate is called on slice[0] and slice[1] +then on slice[1] and slice[2] and so on.

+
Examples
+
#![feature(slice_group_by)]
+
+let slice = &[1, 1, 1, 3, 3, 2, 2, 2];
+
+let mut iter = slice.group_by(|a, b| a == b);
+
+assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
+assert_eq!(iter.next(), Some(&[3, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
+assert_eq!(iter.next(), None);
+

This method can be used to extract the sorted subslices:

+ +
#![feature(slice_group_by)]
+
+let slice = &[1, 1, 2, 3, 2, 3, 2, 3, 4];
+
+let mut iter = slice.group_by(|a, b| a <= b);
+
+assert_eq!(iter.next(), Some(&[1, 1, 2, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 3, 4][..]));
+assert_eq!(iter.next(), None);
+

Divides one slice into two at an index.

+

The first will contain all indices from [0, mid) (excluding +the index mid itself) and the second will contain all +indices from [mid, len) (excluding the index len itself).

+
Panics
+

Panics if mid > len.

+
Examples
+
let v = [1, 2, 3, 4, 5, 6];
+
+{
+   let (left, right) = v.split_at(0);
+   assert_eq!(left, []);
+   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_at(2);
+    assert_eq!(left, [1, 2]);
+    assert_eq!(right, [3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_at(6);
+    assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+    assert_eq!(right, []);
+}
+
🔬This is a nightly-only experimental API. (slice_split_at_unchecked)

Divides one slice into two at an index, without doing bounds checking.

+

The first will contain all indices from [0, mid) (excluding +the index mid itself) and the second will contain all +indices from [mid, len) (excluding the index len itself).

+

For a safe alternative see split_at.

+
Safety
+

Calling this method with an out-of-bounds index is undefined behavior +even if the resulting reference is not used. The caller has to ensure that +0 <= mid <= self.len().

+
Examples
+
#![feature(slice_split_at_unchecked)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+unsafe {
+   let (left, right) = v.split_at_unchecked(0);
+   assert_eq!(left, []);
+   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+unsafe {
+    let (left, right) = v.split_at_unchecked(2);
+    assert_eq!(left, [1, 2]);
+    assert_eq!(right, [3, 4, 5, 6]);
+}
+
+unsafe {
+    let (left, right) = v.split_at_unchecked(6);
+    assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+    assert_eq!(right, []);
+}
+
🔬This is a nightly-only experimental API. (split_array)

Divides one slice into an array and a remainder slice at an index.

+

The array will contain all indices from [0, N) (excluding +the index N itself) and the slice will contain all +indices from [N, len) (excluding the index len itself).

+
Panics
+

Panics if N > len.

+
Examples
+
#![feature(split_array)]
+
+let v = &[1, 2, 3, 4, 5, 6][..];
+
+{
+   let (left, right) = v.split_array_ref::<0>();
+   assert_eq!(left, &[]);
+   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_array_ref::<2>();
+    assert_eq!(left, &[1, 2]);
+    assert_eq!(right, [3, 4, 5, 6]);
+}
+
+{
+    let (left, right) = v.split_array_ref::<6>();
+    assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+    assert_eq!(right, []);
+}
+
🔬This is a nightly-only experimental API. (split_array)

Divides one slice into an array and a remainder slice at an index from +the end.

+

The slice will contain all indices from [0, len - N) (excluding +the index len - N itself) and the array will contain all +indices from [len - N, len) (excluding the index len itself).

+
Panics
+

Panics if N > len.

+
Examples
+
#![feature(split_array)]
+
+let v = &[1, 2, 3, 4, 5, 6][..];
+
+{
+   let (left, right) = v.rsplit_array_ref::<0>();
+   assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+   assert_eq!(right, &[]);
+}
+
+{
+    let (left, right) = v.rsplit_array_ref::<2>();
+    assert_eq!(left, [1, 2, 3, 4]);
+    assert_eq!(right, &[5, 6]);
+}
+
+{
+    let (left, right) = v.rsplit_array_ref::<6>();
+    assert_eq!(left, []);
+    assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
+

Returns an iterator over subslices separated by elements that match +pred. The matched element is not contained in the subslices.

+
Examples
+
let slice = [10, 40, 33, 20];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
+

If the first element is matched, an empty slice will be the first item +returned by the iterator. Similarly, if the last element in the slice +is matched, an empty slice will be the last item returned by the +iterator:

+ +
let slice = [10, 40, 33];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40]);
+assert_eq!(iter.next().unwrap(), &[]);
+assert!(iter.next().is_none());
+

If two matched elements are directly adjacent, an empty slice will be +present between them:

+ +
let slice = [10, 6, 33, 20];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10]);
+assert_eq!(iter.next().unwrap(), &[]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
+

Returns an iterator over subslices separated by elements that match +pred. The matched element is contained in the end of the previous +subslice as a terminator.

+
Examples
+
let slice = [10, 40, 33, 20];
+let mut iter = slice.split_inclusive(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
+

If the last element of the slice is matched, +that element will be considered the terminator of the preceding slice. +That slice will be the last item returned by the iterator.

+ +
let slice = [3, 10, 40, 33];
+let mut iter = slice.split_inclusive(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[3]);
+assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
+assert!(iter.next().is_none());
+

Returns an iterator over subslices separated by elements that match +pred, starting at the end of the slice and working backwards. +The matched element is not contained in the subslices.

+
Examples
+
let slice = [11, 22, 33, 0, 44, 55];
+let mut iter = slice.rsplit(|num| *num == 0);
+
+assert_eq!(iter.next().unwrap(), &[44, 55]);
+assert_eq!(iter.next().unwrap(), &[11, 22, 33]);
+assert_eq!(iter.next(), None);
+

As with split(), if the first or last element is matched, an empty +slice will be the first (or last) item returned by the iterator.

+ +
let v = &[0, 1, 1, 2, 3, 5, 8];
+let mut it = v.rsplit(|n| *n % 2 == 0);
+assert_eq!(it.next().unwrap(), &[]);
+assert_eq!(it.next().unwrap(), &[3, 5]);
+assert_eq!(it.next().unwrap(), &[1, 1]);
+assert_eq!(it.next().unwrap(), &[]);
+assert_eq!(it.next(), None);
+

Returns an iterator over subslices separated by elements that match +pred, limited to returning at most n items. The matched element is +not contained in the subslices.

+

The last element returned, if any, will contain the remainder of the +slice.

+
Examples
+

Print the slice split once by numbers divisible by 3 (i.e., [10, 40], +[20, 60, 50]):

+ +
let v = [10, 40, 30, 20, 60, 50];
+
+for group in v.splitn(2, |num| *num % 3 == 0) {
+    println!("{group:?}");
+}
+

Returns an iterator over subslices separated by elements that match +pred limited to returning at most n items. This starts at the end of +the slice and works backwards. The matched element is not contained in +the subslices.

+

The last element returned, if any, will contain the remainder of the +slice.

+
Examples
+

Print the slice split once, starting from the end, by numbers divisible +by 3 (i.e., [50], [10, 40, 30, 20]):

+ +
let v = [10, 40, 30, 20, 60, 50];
+
+for group in v.rsplitn(2, |num| *num % 3 == 0) {
+    println!("{group:?}");
+}
+

Returns true if the slice contains an element with the given value.

+

This operation is O(n).

+

Note that if you have a sorted slice, binary_search may be faster.

+
Examples
+
let v = [10, 40, 30];
+assert!(v.contains(&30));
+assert!(!v.contains(&50));
+

If you do not have a &T, but some other value that you can compare +with one (for example, String implements PartialEq<str>), you can +use iter().any:

+ +
let v = [String::from("hello"), String::from("world")]; // slice of `String`
+assert!(v.iter().any(|e| e == "hello")); // search with `&str`
+assert!(!v.iter().any(|e| e == "hi"));
+

Returns true if needle is a prefix of the slice.

+
Examples
+
let v = [10, 40, 30];
+assert!(v.starts_with(&[10]));
+assert!(v.starts_with(&[10, 40]));
+assert!(!v.starts_with(&[50]));
+assert!(!v.starts_with(&[10, 50]));
+

Always returns true if needle is an empty slice:

+ +
let v = &[10, 40, 30];
+assert!(v.starts_with(&[]));
+let v: &[u8] = &[];
+assert!(v.starts_with(&[]));
+

Returns true if needle is a suffix of the slice.

+
Examples
+
let v = [10, 40, 30];
+assert!(v.ends_with(&[30]));
+assert!(v.ends_with(&[40, 30]));
+assert!(!v.ends_with(&[50]));
+assert!(!v.ends_with(&[50, 30]));
+

Always returns true if needle is an empty slice:

+ +
let v = &[10, 40, 30];
+assert!(v.ends_with(&[]));
+let v: &[u8] = &[];
+assert!(v.ends_with(&[]));
+

Returns a subslice with the prefix removed.

+

If the slice starts with prefix, returns the subslice after the prefix, wrapped in Some. +If prefix is empty, simply returns the original slice.

+

If the slice does not start with prefix, returns None.

+
Examples
+
let v = &[10, 40, 30];
+assert_eq!(v.strip_prefix(&[10]), Some(&[40, 30][..]));
+assert_eq!(v.strip_prefix(&[10, 40]), Some(&[30][..]));
+assert_eq!(v.strip_prefix(&[50]), None);
+assert_eq!(v.strip_prefix(&[10, 50]), None);
+
+let prefix : &str = "he";
+assert_eq!(b"hello".strip_prefix(prefix.as_bytes()),
+           Some(b"llo".as_ref()));
+

Returns a subslice with the suffix removed.

+

If the slice ends with suffix, returns the subslice before the suffix, wrapped in Some. +If suffix is empty, simply returns the original slice.

+

If the slice does not end with suffix, returns None.

+
Examples
+
let v = &[10, 40, 30];
+assert_eq!(v.strip_suffix(&[30]), Some(&[10, 40][..]));
+assert_eq!(v.strip_suffix(&[40, 30]), Some(&[10][..]));
+assert_eq!(v.strip_suffix(&[50]), None);
+assert_eq!(v.strip_suffix(&[50, 30]), None);
+

Binary searches this slice for a given element. +This behaves similarly to contains if this slice is sorted.

+

If the value is found then Result::Ok is returned, containing the +index of the matching element. If there are multiple matches, then any +one of the matches could be returned. The index is chosen +deterministically, but is subject to change in future versions of Rust. +If the value is not found then Result::Err is returned, containing +the index where a matching element could be inserted while maintaining +sorted order.

+

See also binary_search_by, binary_search_by_key, and partition_point.

+
Examples
+

Looks up a series of four elements. The first is found, with a +uniquely determined position; the second and third are not +found; the fourth could match any position in [1, 4].

+ +
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+assert_eq!(s.binary_search(&13),  Ok(9));
+assert_eq!(s.binary_search(&4),   Err(7));
+assert_eq!(s.binary_search(&100), Err(13));
+let r = s.binary_search(&1);
+assert!(match r { Ok(1..=4) => true, _ => false, });
+

If you want to find that whole range of matching items, rather than +an arbitrary matching one, that can be done using partition_point:

+ +
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+let low = s.partition_point(|x| x < &1);
+assert_eq!(low, 1);
+let high = s.partition_point(|x| x <= &1);
+assert_eq!(high, 5);
+let r = s.binary_search(&1);
+assert!((low..high).contains(&r.unwrap()));
+
+assert!(s[..low].iter().all(|&x| x < 1));
+assert!(s[low..high].iter().all(|&x| x == 1));
+assert!(s[high..].iter().all(|&x| x > 1));
+
+// For something not found, the "range" of equal items is empty
+assert_eq!(s.partition_point(|x| x < &11), 9);
+assert_eq!(s.partition_point(|x| x <= &11), 9);
+assert_eq!(s.binary_search(&11), Err(9));
+

If you want to insert an item to a sorted vector, while maintaining +sort order, consider using partition_point:

+ +
let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+let num = 42;
+let idx = s.partition_point(|&x| x < num);
+// The above is equivalent to `let idx = s.binary_search(&num).unwrap_or_else(|x| x);`
+s.insert(idx, num);
+assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
+

Binary searches this slice with a comparator function. +This behaves similarly to contains if this slice is sorted.

+

The comparator function should implement an order consistent +with the sort order of the underlying slice, returning an +order code that indicates whether its argument is Less, +Equal or Greater the desired target.

+

If the value is found then Result::Ok is returned, containing the +index of the matching element. If there are multiple matches, then any +one of the matches could be returned. The index is chosen +deterministically, but is subject to change in future versions of Rust. +If the value is not found then Result::Err is returned, containing +the index where a matching element could be inserted while maintaining +sorted order.

+

See also binary_search, binary_search_by_key, and partition_point.

+
Examples
+

Looks up a series of four elements. The first is found, with a +uniquely determined position; the second and third are not +found; the fourth could match any position in [1, 4].

+ +
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+let seek = 13;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Ok(9));
+let seek = 4;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(7));
+let seek = 100;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(13));
+let seek = 1;
+let r = s.binary_search_by(|probe| probe.cmp(&seek));
+assert!(match r { Ok(1..=4) => true, _ => false, });
+

Binary searches this slice with a key extraction function. +This behaves similarly to contains if this slice is sorted.

+

Assumes that the slice is sorted by the key, for instance with +sort_by_key using the same key extraction function.

+

If the value is found then Result::Ok is returned, containing the +index of the matching element. If there are multiple matches, then any +one of the matches could be returned. The index is chosen +deterministically, but is subject to change in future versions of Rust. +If the value is not found then Result::Err is returned, containing +the index where a matching element could be inserted while maintaining +sorted order.

+

See also binary_search, binary_search_by, and partition_point.

+
Examples
+

Looks up a series of four elements in a slice of pairs sorted by +their second elements. The first is found, with a uniquely +determined position; the second and third are not found; the +fourth could match any position in [1, 4].

+ +
let s = [(0, 0), (2, 1), (4, 1), (5, 1), (3, 1),
+         (1, 2), (2, 3), (4, 5), (5, 8), (3, 13),
+         (1, 21), (2, 34), (4, 55)];
+
+assert_eq!(s.binary_search_by_key(&13, |&(a, b)| b),  Ok(9));
+assert_eq!(s.binary_search_by_key(&4, |&(a, b)| b),   Err(7));
+assert_eq!(s.binary_search_by_key(&100, |&(a, b)| b), Err(13));
+let r = s.binary_search_by_key(&1, |&(a, b)| b);
+assert!(match r { Ok(1..=4) => true, _ => false, });
+

Transmute the slice to a slice of another type, ensuring alignment of the types is +maintained.

+

This method splits the slice into three distinct slices: prefix, correctly aligned middle +slice of a new type, and the suffix slice. The method may make the middle slice the greatest +length possible for a given type and input slice, but only your algorithm’s performance +should depend on that, not its correctness. It is permissible for all of the input data to +be returned as the prefix or suffix slice.

+

This method has no purpose when either input element T or output element U are +zero-sized and will return the original slice without splitting anything.

+
Safety
+

This method is essentially a transmute with respect to the elements in the returned +middle slice, so all the usual caveats pertaining to transmute::<T, U> also apply here.

+
Examples
+

Basic usage:

+ +
unsafe {
+    let bytes: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
+    let (prefix, shorts, suffix) = bytes.align_to::<u16>();
+    // less_efficient_algorithm_for_bytes(prefix);
+    // more_efficient_algorithm_for_aligned_shorts(shorts);
+    // less_efficient_algorithm_for_bytes(suffix);
+}
+
🔬This is a nightly-only experimental API. (portable_simd)

Split a slice into a prefix, a middle of aligned SIMD types, and a suffix.

+

This is a safe wrapper around slice::align_to, so has the same weak +postconditions as that method. You’re only assured that +self.len() == prefix.len() + middle.len() * LANES + suffix.len().

+

Notably, all of the following are possible:

+
    +
  • prefix.len() >= LANES.
  • +
  • middle.is_empty() despite self.len() >= 3 * LANES.
  • +
  • suffix.len() >= LANES.
  • +
+

That said, this is a safe method, so if you’re only writing safe code, +then this can at most cause incorrect logic, not unsoundness.

+
Panics
+

This will panic if the size of the SIMD type is different from +LANES times that of the scalar.

+

At the time of writing, the trait restrictions on Simd<T, LANES> keeps +that from ever happening, as only power-of-two numbers of lanes are +supported. It’s possible that, in the future, those restrictions might +be lifted in a way that would make it possible to see panics from this +method for something like LANES == 3.

+
Examples
+
#![feature(portable_simd)]
+use core::simd::SimdFloat;
+
+let short = &[1, 2, 3];
+let (prefix, middle, suffix) = short.as_simd::<4>();
+assert_eq!(middle, []); // Not enough elements for anything in the middle
+
+// They might be split in any possible way between prefix and suffix
+let it = prefix.iter().chain(suffix).copied();
+assert_eq!(it.collect::<Vec<_>>(), vec![1, 2, 3]);
+
+fn basic_simd_sum(x: &[f32]) -> f32 {
+    use std::ops::Add;
+    use std::simd::f32x4;
+    let (prefix, middle, suffix) = x.as_simd();
+    let sums = f32x4::from_array([
+        prefix.iter().copied().sum(),
+        0.0,
+        0.0,
+        suffix.iter().copied().sum(),
+    ]);
+    let sums = middle.iter().copied().fold(sums, f32x4::add);
+    sums.reduce_sum()
+}
+
+let numbers: Vec<f32> = (1..101).map(|x| x as _).collect();
+assert_eq!(basic_simd_sum(&numbers[1..99]), 4949.0);
+
🔬This is a nightly-only experimental API. (is_sorted)

Checks if the elements of this slice are sorted.

+

That is, for each element a and its following element b, a <= b must hold. If the +slice yields exactly zero or one element, true is returned.

+

Note that if Self::Item is only PartialOrd, but not Ord, the above definition +implies that this function returns false if any two consecutive items are not +comparable.

+
Examples
+
#![feature(is_sorted)]
+let empty: [i32; 0] = [];
+
+assert!([1, 2, 2, 9].is_sorted());
+assert!(![1, 3, 2, 4].is_sorted());
+assert!([0].is_sorted());
+assert!(empty.is_sorted());
+assert!(![0.0, 1.0, f32::NAN].is_sorted());
+
🔬This is a nightly-only experimental API. (is_sorted)

Checks if the elements of this slice are sorted using the given comparator function.

+

Instead of using PartialOrd::partial_cmp, this function uses the given compare +function to determine the ordering of two elements. Apart from that, it’s equivalent to +is_sorted; see its documentation for more information.

+
🔬This is a nightly-only experimental API. (is_sorted)

Checks if the elements of this slice are sorted using the given key extraction function.

+

Instead of comparing the slice’s elements directly, this function compares the keys of the +elements, as determined by f. Apart from that, it’s equivalent to is_sorted; see its +documentation for more information.

+
Examples
+
#![feature(is_sorted)]
+
+assert!(["c", "bb", "aaa"].is_sorted_by_key(|s| s.len()));
+assert!(![-2i32, -1, 0, 3].is_sorted_by_key(|n| n.abs()));
+

Returns the index of the partition point according to the given predicate +(the index of the first element of the second partition).

+

The slice is assumed to be partitioned according to the given predicate. +This means that all elements for which the predicate returns true are at the start of the slice +and all elements for which the predicate returns false are at the end. +For example, [7, 15, 3, 5, 4, 12, 6] is a partitioned under the predicate x % 2 != 0 +(all odd numbers are at the start, all even at the end).

+

If this slice is not partitioned, the returned result is unspecified and meaningless, +as this method performs a kind of binary search.

+

See also binary_search, binary_search_by, and binary_search_by_key.

+
Examples
+
let v = [1, 2, 3, 3, 5, 6, 7];
+let i = v.partition_point(|&x| x < 5);
+
+assert_eq!(i, 4);
+assert!(v[..i].iter().all(|&x| x < 5));
+assert!(v[i..].iter().all(|&x| !(x < 5)));
+

If all elements of the slice match the predicate, including if the slice +is empty, then the length of the slice will be returned:

+ +
let a = [2, 4, 8];
+assert_eq!(a.partition_point(|x| x < &100), a.len());
+let a: [i32; 0] = [];
+assert_eq!(a.partition_point(|x| x < &100), 0);
+

If you want to insert an item to a sorted vector, while maintaining +sort order:

+ +
let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+let num = 42;
+let idx = s.partition_point(|&x| x < num);
+s.insert(idx, num);
+assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
+
🔬This is a nightly-only experimental API. (slice_flatten)

Takes a &[[T; N]], and flattens it to a &[T].

+
Panics
+

This panics if the length of the resulting slice would overflow a usize.

+

This is only possible when flattening a slice of arrays of zero-sized +types, and thus tends to be irrelevant in practice. If +size_of::<T>() > 0, this will never panic.

+
Examples
+
#![feature(slice_flatten)]
+
+assert_eq!([[1, 2, 3], [4, 5, 6]].flatten(), &[1, 2, 3, 4, 5, 6]);
+
+assert_eq!(
+    [[1, 2, 3], [4, 5, 6]].flatten(),
+    [[1, 2], [3, 4], [5, 6]].flatten(),
+);
+
+let slice_of_empty_arrays: &[[i32; 0]] = &[[], [], [], [], []];
+assert!(slice_of_empty_arrays.flatten().is_empty());
+
+let empty_slice_of_arrays: &[[u32; 10]] = &[];
+assert!(empty_slice_of_arrays.flatten().is_empty());
+

Checks if all bytes in this slice are within the ASCII range.

+

Checks that two slices are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+

Returns an iterator that produces an escaped version of this slice, +treating it as an ASCII string.

+
Examples
+

+let s = b"0\t\r\n'\"\\\x9d";
+let escaped = s.escape_ascii().to_string();
+assert_eq!(escaped, "0\\t\\r\\n\\'\\\"\\\\\\x9d");
+
🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

Returns a byte slice with leading ASCII whitespace bytes removed.

+

‘Whitespace’ refers to the definition used by +u8::is_ascii_whitespace.

+
Examples
+
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b" \t hello world\n".trim_ascii_start(), b"hello world\n");
+assert_eq!(b"  ".trim_ascii_start(), b"");
+assert_eq!(b"".trim_ascii_start(), b"");
+
🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

Returns a byte slice with trailing ASCII whitespace bytes removed.

+

‘Whitespace’ refers to the definition used by +u8::is_ascii_whitespace.

+
Examples
+
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b"\r hello world\n ".trim_ascii_end(), b"\r hello world");
+assert_eq!(b"  ".trim_ascii_end(), b"");
+assert_eq!(b"".trim_ascii_end(), b"");
+
🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

Returns a byte slice with leading and trailing ASCII whitespace bytes +removed.

+

‘Whitespace’ refers to the definition used by +u8::is_ascii_whitespace.

+
Examples
+
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b"\r hello world\n ".trim_ascii(), b"hello world");
+assert_eq!(b"  ".trim_ascii(), b"");
+assert_eq!(b"".trim_ascii(), b"");
+

Returns a vector containing a copy of this slice where each byte +is mapped to its ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

Returns a vector containing a copy of this slice where each byte +is mapped to its ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

Copies self into a new Vec.

+
Examples
+
let s = [10, 40, 30];
+let x = s.to_vec();
+// Here, `s` and `x` can be modified independently.
+
🔬This is a nightly-only experimental API. (allocator_api)

Copies self into a new Vec with an allocator.

+
Examples
+
#![feature(allocator_api)]
+
+use std::alloc::System;
+
+let s = [10, 40, 30];
+let x = s.to_vec_in(System);
+// Here, `s` and `x` can be modified independently.
+

Creates a vector by repeating a slice n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!([1, 2].repeat(3), vec![1, 2, 1, 2, 1, 2]);
+

A panic upon overflow:

+ +
// this will panic at runtime
+b"0123456789abcdef".repeat(usize::MAX);
+

Flattens a slice of T into a single value Self::Output.

+
Examples
+
assert_eq!(["hello", "world"].concat(), "helloworld");
+assert_eq!([[1, 2], [3, 4]].concat(), [1, 2, 3, 4]);
+

Flattens a slice of T into a single value Self::Output, placing a +given separator between each.

+
Examples
+
assert_eq!(["hello", "world"].join(" "), "hello world");
+assert_eq!([[1, 2], [3, 4]].join(&0), [1, 2, 0, 3, 4]);
+assert_eq!([[1, 2], [3, 4]].join(&[0, 0][..]), [1, 2, 0, 0, 3, 4]);
+
👎Deprecated since 1.3.0: renamed to join

Flattens a slice of T into a single value Self::Output, placing a +given separator between each.

+
Examples
+
assert_eq!(["hello", "world"].connect(" "), "hello world");
+assert_eq!([[1, 2], [3, 4]].connect(&0), [1, 2, 0, 3, 4]);
+

Trait Implementations

The resulting type after dereferencing.
Dereferences the value.

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/struct.RefMut.html b/docs/firewood/merkle/struct.RefMut.html new file mode 100644 index 000000000000..8bb946c0c5b4 --- /dev/null +++ b/docs/firewood/merkle/struct.RefMut.html @@ -0,0 +1,5 @@ +RefMut in firewood::merkle - Rust
pub struct RefMut<'a> { /* private fields */ }

Implementations

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

+

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Should always be Self
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
\ No newline at end of file diff --git a/docs/firewood/merkle/trait.ValueTransformer.html b/docs/firewood/merkle/trait.ValueTransformer.html new file mode 100644 index 000000000000..690841a320e6 --- /dev/null +++ b/docs/firewood/merkle/trait.ValueTransformer.html @@ -0,0 +1,3 @@ +ValueTransformer in firewood::merkle - Rust
pub trait ValueTransformer {
+    fn transform(bytes: &[u8]) -> Vec<u8>Notable traits for Vec<u8, A>impl<A> Write for Vec<u8, A>where
    A: Allocator,
; +}

Required Methods

Implementors

\ No newline at end of file diff --git a/docs/firewood/sidebar-items.js b/docs/firewood/sidebar-items.js new file mode 100644 index 000000000000..e49d00d449bc --- /dev/null +++ b/docs/firewood/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":[["db",""],["merkle",""],["proof",""]]}; \ No newline at end of file diff --git a/docs/firewood/storage/struct.DiskBufferConfig.html b/docs/firewood/storage/struct.DiskBufferConfig.html new file mode 100644 index 000000000000..af27f85453ed --- /dev/null +++ b/docs/firewood/storage/struct.DiskBufferConfig.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../firewood/db/struct.DiskBufferConfig.html...

+ + + \ No newline at end of file diff --git a/docs/firewood/storage/struct.WALConfig.html b/docs/firewood/storage/struct.WALConfig.html new file mode 100644 index 000000000000..ccfe6aa53864 --- /dev/null +++ b/docs/firewood/storage/struct.WALConfig.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../firewood/db/struct.WALConfig.html...

+ + + \ No newline at end of file diff --git a/docs/implementors/core/clone/trait.Clone.js b/docs/implementors/core/clone/trait.Clone.js new file mode 100644 index 000000000000..4ec68431b861 --- /dev/null +++ b/docs/implementors/core/clone/trait.Clone.js @@ -0,0 +1,48 @@ +(function() {var implementors = { +"ahash":[["impl Clone for AHasher"],["impl Clone for RandomState"]], +"aho_corasick":[["impl<S: Clone + StateID> Clone for AhoCorasick<S>"],["impl Clone for AhoCorasickBuilder"],["impl Clone for MatchKind"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for MatchKind"],["impl Clone for Config"],["impl Clone for Builder"],["impl Clone for Searcher"],["impl Clone for Match"]], +"bincode":[["impl Clone for LittleEndian"],["impl Clone for BigEndian"],["impl Clone for NativeEndian"],["impl Clone for FixintEncoding"],["impl Clone for VarintEncoding"],["impl Clone for Config"],["impl Clone for Bounded"],["impl Clone for Infinite"],["impl Clone for AllowTrailing"],["impl Clone for RejectTrailing"],["impl Clone for DefaultOptions"],["impl<O: Clone + Options, L: Clone + SizeLimit> Clone for WithOtherLimit<O, L>"],["impl<O: Clone + Options, E: Clone + BincodeByteOrder> Clone for WithOtherEndian<O, E>"],["impl<O: Clone + Options, I: Clone + IntEncoding> Clone for WithOtherIntEncoding<O, I>"],["impl<O: Clone + Options, T: Clone + TrailingBytes> Clone for WithOtherTrailing<O, T>"]], +"block_buffer":[["impl Clone for Eager"],["impl Clone for Lazy"],["impl Clone for Error"],["impl<BlockSize, Kind> Clone for BlockBuffer<BlockSize, Kind>where
    BlockSize: ArrayLength<u8> + IsLess<U256>,
    Le<BlockSize, U256>: NonZero,
    Kind: BufferKind,
"]], +"byteorder":[["impl Clone for BigEndian"],["impl Clone for LittleEndian"]], +"bytes":[["impl Clone for Bytes"],["impl Clone for BytesMut"]], +"crc":[["impl<'a, W: Clone + Width> Clone for Digest<'a, W>"]], +"crossbeam_channel":[["impl<T> Clone for Sender<T>"],["impl<T> Clone for Receiver<T>"],["impl<T: Clone> Clone for SendError<T>"],["impl<T: Clone> Clone for TrySendError<T>"],["impl<T: Clone> Clone for SendTimeoutError<T>"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl Clone for RecvTimeoutError"],["impl Clone for TrySelectError"],["impl Clone for SelectTimeoutError"],["impl Clone for TryReadyError"],["impl Clone for ReadyTimeoutError"],["impl<'a> Clone for Select<'a>"]], +"crossbeam_utils":[["impl<T: Clone> Clone for CachePadded<T>"],["impl Clone for Unparker"],["impl Clone for WaitGroup"]], +"crypto_common":[["impl Clone for InvalidLength"]], +"digest":[["impl<T: Clone, OutSize: Clone, O: Clone> Clone for CtVariableCoreWrapper<T, OutSize, O>where
    T: VariableOutputCore,
    OutSize: ArrayLength<u8> + IsLessOrEqual<T::OutputSize>,
    LeEq<OutSize, T::OutputSize>: NonZero,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
"],["impl<T: Clone> Clone for RtVariableCoreWrapper<T>where
    T: VariableOutputCore + UpdateCore,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
    T::BlockSize: Clone,
    T::BufferKind: Clone,
"],["impl<T: Clone> Clone for CoreWrapper<T>where
    T: BufferKindUser,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
    T::BlockSize: Clone,
    T::BufferKind: Clone,
"],["impl<T: Clone> Clone for XofReaderCoreWrapper<T>where
    T: XofReaderCore,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
    T::BlockSize: Clone,
"],["impl Clone for TruncSide"],["impl Clone for Box<dyn DynDigest>"],["impl Clone for InvalidOutputSize"],["impl Clone for InvalidBufferSize"]], +"firewood":[["impl Clone for DBRevConfig"],["impl Clone for Hash"],["impl Clone for PartialPath"],["impl Clone for Node"],["impl Clone for WALConfig"],["impl Clone for DiskBufferConfig"]], +"futures_channel":[["impl Clone for SendError"],["impl<T: Clone> Clone for TrySendError<T>"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for UnboundedSender<T>"],["impl Clone for Canceled"]], +"futures_executor":[["impl Clone for LocalSpawner"]], +"futures_util":[["impl<Fut: Future> Clone for WeakShared<Fut>"],["impl<Fut> Clone for Shared<Fut>where
    Fut: Future,
"],["impl<T> Clone for Pending<T>"],["impl<F: Clone> Clone for OptionFuture<F>"],["impl<T: Clone> Clone for PollImmediate<T>"],["impl<T: Clone> Clone for Ready<T>"],["impl<A: Clone, B: Clone> Clone for Either<A, B>"],["impl<I: Clone> Clone for Iter<I>"],["impl<T: Clone> Clone for Repeat<T>"],["impl<F: Clone> Clone for RepeatWith<F>"],["impl<T> Clone for Empty<T>"],["impl<T> Clone for Pending<T>"],["impl<S: Clone> Clone for PollImmediate<S>"],["impl Clone for PollNext"],["impl<T> Clone for Drain<T>"],["impl<Si: Clone, F: Clone> Clone for SinkMapErr<Si, F>"],["impl<Si, Item, U, Fut, F> Clone for With<Si, Item, U, Fut, F>where
    Si: Clone,
    F: Clone,
    Fut: Clone,
"],["impl<T: Clone> Clone for AllowStdIo<T>"],["impl<T: Clone> Clone for Cursor<T>"],["impl<T: Clone> Clone for Abortable<T>"],["impl Clone for AbortHandle"],["impl Clone for Aborted"]], +"generic_array":[["impl<T: Clone, N> Clone for GenericArray<T, N>where
    N: ArrayLength<T>,
"],["impl<T: Clone, N> Clone for GenericArrayIter<T, N>where
    N: ArrayLength<T>,
"]], +"getrandom":[["impl Clone for Error"]], +"growthring":[["impl Clone for WALRingId"],["impl Clone for RecoverPolicy"]], +"hashbrown":[["impl<K: Clone, V: Clone, S: Clone, A: Allocator + Clone> Clone for HashMap<K, V, S, A>"],["impl<K, V> Clone for Iter<'_, K, V>"],["impl<K, V> Clone for Keys<'_, K, V>"],["impl<K, V> Clone for Values<'_, K, V>"],["impl<T: Clone, S: Clone, A: Allocator + Clone> Clone for HashSet<T, S, A>"],["impl<K> Clone for Iter<'_, K>"],["impl<T, S, A: Allocator + Clone> Clone for Intersection<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for Difference<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for SymmetricDifference<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for Union<'_, T, S, A>"],["impl Clone for TryReserveError"]], +"hex":[["impl Clone for FromHexError"]], +"libc":[["impl Clone for DIR"],["impl Clone for group"],["impl Clone for utimbuf"],["impl Clone for timeval"],["impl Clone for timespec"],["impl Clone for rlimit"],["impl Clone for rusage"],["impl Clone for ipv6_mreq"],["impl Clone for hostent"],["impl Clone for iovec"],["impl Clone for pollfd"],["impl Clone for winsize"],["impl Clone for linger"],["impl Clone for sigval"],["impl Clone for itimerval"],["impl Clone for tms"],["impl Clone for servent"],["impl Clone for protoent"],["impl Clone for FILE"],["impl Clone for fpos_t"],["impl Clone for timezone"],["impl Clone for in_addr"],["impl Clone for ip_mreq"],["impl Clone for ip_mreqn"],["impl Clone for ip_mreq_source"],["impl Clone for sockaddr"],["impl Clone for sockaddr_in"],["impl Clone for sockaddr_in6"],["impl Clone for addrinfo"],["impl Clone for sockaddr_ll"],["impl Clone for fd_set"],["impl Clone for tm"],["impl Clone for sched_param"],["impl Clone for Dl_info"],["impl Clone for lconv"],["impl Clone for in_pktinfo"],["impl Clone for ifaddrs"],["impl Clone for in6_rtmsg"],["impl Clone for arpreq"],["impl Clone for arpreq_old"],["impl Clone for arphdr"],["impl Clone for mmsghdr"],["impl Clone for epoll_event"],["impl Clone for sockaddr_un"],["impl Clone for sockaddr_storage"],["impl Clone for utsname"],["impl Clone for sigevent"],["impl Clone for fpos64_t"],["impl Clone for rlimit64"],["impl Clone for glob_t"],["impl Clone for passwd"],["impl Clone for spwd"],["impl Clone for dqblk"],["impl Clone for signalfd_siginfo"],["impl Clone for itimerspec"],["impl Clone for fsid_t"],["impl Clone for packet_mreq"],["impl Clone for cpu_set_t"],["impl Clone for if_nameindex"],["impl Clone for msginfo"],["impl Clone for sembuf"],["impl Clone for input_event"],["impl Clone for input_id"],["impl Clone for input_absinfo"],["impl Clone for input_keymap_entry"],["impl Clone for input_mask"],["impl Clone for ff_replay"],["impl Clone for ff_trigger"],["impl Clone for ff_envelope"],["impl Clone for ff_constant_effect"],["impl Clone for ff_ramp_effect"],["impl Clone for ff_condition_effect"],["impl Clone for ff_periodic_effect"],["impl Clone for ff_rumble_effect"],["impl Clone for ff_effect"],["impl Clone for uinput_ff_upload"],["impl Clone for uinput_ff_erase"],["impl Clone for uinput_abs_setup"],["impl Clone for dl_phdr_info"],["impl Clone for Elf32_Ehdr"],["impl Clone for Elf64_Ehdr"],["impl Clone for Elf32_Sym"],["impl Clone for Elf64_Sym"],["impl Clone for Elf32_Phdr"],["impl Clone for Elf64_Phdr"],["impl Clone for Elf32_Shdr"],["impl Clone for Elf64_Shdr"],["impl Clone for ucred"],["impl Clone for mntent"],["impl Clone for posix_spawn_file_actions_t"],["impl Clone for posix_spawnattr_t"],["impl Clone for genlmsghdr"],["impl Clone for in6_pktinfo"],["impl Clone for arpd_request"],["impl Clone for inotify_event"],["impl Clone for fanotify_response"],["impl Clone for sockaddr_vm"],["impl Clone for regmatch_t"],["impl Clone for sock_extended_err"],["impl Clone for __c_anonymous_sockaddr_can_tp"],["impl Clone for __c_anonymous_sockaddr_can_j1939"],["impl Clone for can_filter"],["impl Clone for j1939_filter"],["impl Clone for sock_filter"],["impl Clone for sock_fprog"],["impl Clone for seccomp_data"],["impl Clone for nlmsghdr"],["impl Clone for nlmsgerr"],["impl Clone for nlattr"],["impl Clone for file_clone_range"],["impl Clone for __c_anonymous_ifru_map"],["impl Clone for in6_ifreq"],["impl Clone for sockaddr_nl"],["impl Clone for dirent"],["impl Clone for dirent64"],["impl Clone for sockaddr_alg"],["impl Clone for uinput_setup"],["impl Clone for uinput_user_dev"],["impl Clone for af_alg_iv"],["impl Clone for mq_attr"],["impl Clone for __c_anonymous_ifr_ifru"],["impl Clone for ifreq"],["impl Clone for sock_txtime"],["impl Clone for __c_anonymous_sockaddr_can_can_addr"],["impl Clone for sockaddr_can"],["impl Clone for statx"],["impl Clone for statx_timestamp"],["impl Clone for aiocb"],["impl Clone for __exit_status"],["impl Clone for __timeval"],["impl Clone for glob64_t"],["impl Clone for msghdr"],["impl Clone for cmsghdr"],["impl Clone for termios"],["impl Clone for mallinfo"],["impl Clone for mallinfo2"],["impl Clone for nl_pktinfo"],["impl Clone for nl_mmap_req"],["impl Clone for nl_mmap_hdr"],["impl Clone for rtentry"],["impl Clone for timex"],["impl Clone for ntptimeval"],["impl Clone for regex_t"],["impl Clone for Elf64_Chdr"],["impl Clone for Elf32_Chdr"],["impl Clone for seminfo"],["impl Clone for ptrace_peeksiginfo_args"],["impl Clone for __c_anonymous_ptrace_syscall_info_entry"],["impl Clone for __c_anonymous_ptrace_syscall_info_exit"],["impl Clone for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Clone for ptrace_syscall_info"],["impl Clone for __c_anonymous_ptrace_syscall_info_data"],["impl Clone for utmpx"],["impl Clone for sigset_t"],["impl Clone for sysinfo"],["impl Clone for msqid_ds"],["impl Clone for semid_ds"],["impl Clone for sigaction"],["impl Clone for statfs"],["impl Clone for flock"],["impl Clone for flock64"],["impl Clone for siginfo_t"],["impl Clone for stack_t"],["impl Clone for stat"],["impl Clone for stat64"],["impl Clone for statfs64"],["impl Clone for statvfs64"],["impl Clone for pthread_attr_t"],["impl Clone for _libc_fpxreg"],["impl Clone for _libc_xmmreg"],["impl Clone for _libc_fpstate"],["impl Clone for user_regs_struct"],["impl Clone for user"],["impl Clone for mcontext_t"],["impl Clone for ipc_perm"],["impl Clone for shmid_ds"],["impl Clone for seccomp_notif_sizes"],["impl Clone for ptrace_rseq_configuration"],["impl Clone for user_fpregs_struct"],["impl Clone for ucontext_t"],["impl Clone for statvfs"],["impl Clone for max_align_t"],["impl Clone for clone_args"],["impl Clone for sem_t"],["impl Clone for termios2"],["impl Clone for pthread_mutexattr_t"],["impl Clone for pthread_rwlockattr_t"],["impl Clone for pthread_condattr_t"],["impl Clone for fanotify_event_metadata"],["impl Clone for pthread_cond_t"],["impl Clone for pthread_mutex_t"],["impl Clone for pthread_rwlock_t"],["impl Clone for can_frame"],["impl Clone for canfd_frame"],["impl Clone for open_how"],["impl Clone for in6_addr"]], +"lru":[["impl<'a, K, V> Clone for Iter<'a, K, V>"]], +"memchr":[["impl Clone for Prefilter"],["impl<'n> Clone for Finder<'n>"],["impl<'n> Clone for FinderRev<'n>"],["impl Clone for FinderBuilder"]], +"nix":[["impl Clone for Entry"],["impl Clone for Type"],["impl Clone for ClearEnvError"],["impl Clone for Errno"],["impl Clone for AtFlags"],["impl Clone for OFlag"],["impl Clone for RenameFlags"],["impl Clone for SealFlag"],["impl Clone for FdFlag"],["impl Clone for FlockArg"],["impl Clone for SpliceFFlags"],["impl Clone for FallocateFlags"],["impl Clone for PosixFadviseAdvice"],["impl Clone for InterfaceAddress"],["impl Clone for InterfaceFlags"],["impl Clone for ModuleInitFlags"],["impl Clone for DeleteModuleFlags"],["impl Clone for MsFlags"],["impl Clone for MntFlags"],["impl Clone for MQ_OFlag"],["impl Clone for MqAttr"],["impl Clone for PollFd"],["impl Clone for PollFlags"],["impl Clone for OpenptyResult"],["impl Clone for ForkptyResult"],["impl Clone for CloneFlags"],["impl Clone for CpuSet"],["impl Clone for AioFsyncMode"],["impl Clone for LioMode"],["impl Clone for AioCancelStat"],["impl Clone for EpollFlags"],["impl Clone for EpollOp"],["impl Clone for EpollCreateFlags"],["impl Clone for EpollEvent"],["impl Clone for EfdFlags"],["impl Clone for MemFdCreateFlag"],["impl Clone for ProtFlags"],["impl Clone for MapFlags"],["impl Clone for MRemapFlags"],["impl Clone for MmapAdvise"],["impl Clone for MsFlags"],["impl Clone for MlockAllFlags"],["impl Clone for Persona"],["impl Clone for Request"],["impl Clone for Event"],["impl Clone for Options"],["impl Clone for QuotaType"],["impl Clone for QuotaFmt"],["impl Clone for QuotaValidFlags"],["impl Clone for Dqblk"],["impl Clone for RebootMode"],["impl Clone for Resource"],["impl Clone for UsageWho"],["impl Clone for Usage"],["impl Clone for FdSet"],["impl Clone for Signal"],["impl Clone for SignalIterator"],["impl Clone for SaFlags"],["impl Clone for SigmaskHow"],["impl Clone for SigSet"],["impl<'a> Clone for SigSetIter<'a>"],["impl Clone for SigHandler"],["impl Clone for SigAction"],["impl Clone for SigevNotify"],["impl Clone for SigEvent"],["impl Clone for SfdFlags"],["impl Clone for AddressFamily"],["impl Clone for InetAddr"],["impl Clone for IpAddr"],["impl Clone for Ipv4Addr"],["impl Clone for Ipv6Addr"],["impl Clone for UnixAddr"],["impl Clone for SockaddrIn"],["impl Clone for SockaddrIn6"],["impl Clone for SockaddrStorage"],["impl Clone for SockAddr"],["impl Clone for NetlinkAddr"],["impl Clone for AlgAddr"],["impl Clone for LinkAddr"],["impl Clone for VsockAddr"],["impl Clone for ReuseAddr"],["impl Clone for ReusePort"],["impl Clone for TcpNoDelay"],["impl Clone for Linger"],["impl Clone for IpAddMembership"],["impl Clone for IpDropMembership"],["impl Clone for Ipv6AddMembership"],["impl Clone for Ipv6DropMembership"],["impl Clone for IpMulticastTtl"],["impl Clone for IpMulticastLoop"],["impl Clone for Priority"],["impl Clone for IpTos"],["impl Clone for Ipv6TClass"],["impl Clone for IpFreebind"],["impl Clone for ReceiveTimeout"],["impl Clone for SendTimeout"],["impl Clone for Broadcast"],["impl Clone for OobInline"],["impl Clone for SocketError"],["impl Clone for DontRoute"],["impl Clone for KeepAlive"],["impl Clone for PeerCredentials"],["impl Clone for TcpKeepIdle"],["impl Clone for TcpMaxSeg"],["impl Clone for TcpKeepCount"],["impl Clone for TcpRepair"],["impl Clone for TcpKeepInterval"],["impl Clone for TcpUserTimeout"],["impl Clone for RcvBuf"],["impl Clone for SndBuf"],["impl Clone for RcvBufForce"],["impl Clone for SndBufForce"],["impl Clone for SockType"],["impl Clone for AcceptConn"],["impl Clone for BindToDevice"],["impl Clone for OriginalDst"],["impl Clone for Ip6tOriginalDst"],["impl Clone for Timestamping"],["impl Clone for ReceiveTimestamp"],["impl Clone for ReceiveTimestampns"],["impl Clone for IpTransparent"],["impl Clone for Mark"],["impl Clone for PassCred"],["impl Clone for TcpCongestion"],["impl Clone for Ipv4PacketInfo"],["impl Clone for Ipv6RecvPacketInfo"],["impl Clone for Ipv4OrigDstAddr"],["impl Clone for UdpGsoSegment"],["impl Clone for UdpGroSegment"],["impl Clone for TxTime"],["impl Clone for RxqOvfl"],["impl Clone for Ipv6V6Only"],["impl Clone for Ipv4RecvErr"],["impl Clone for Ipv6RecvErr"],["impl Clone for IpMtu"],["impl Clone for Ipv4Ttl"],["impl Clone for Ipv6Ttl"],["impl Clone for Ipv6OrigDstAddr"],["impl Clone for Ipv6DontFrag"],["impl Clone for AlgSetAeadAuthSize"],["impl<T: Clone> Clone for AlgSetKey<T>"],["impl Clone for SockType"],["impl Clone for SockProtocol"],["impl Clone for TimestampingFlag"],["impl Clone for SockFlag"],["impl Clone for MsgFlags"],["impl Clone for UnixCredentials"],["impl Clone for IpMembershipRequest"],["impl Clone for Ipv6MembershipRequest"],["impl<'a, 's, S: Clone> Clone for RecvMsg<'a, 's, S>"],["impl<'a> Clone for CmsgIterator<'a>"],["impl Clone for ControlMessageOwned"],["impl Clone for Timestamps"],["impl<'a> Clone for ControlMessage<'a>"],["impl Clone for Shutdown"],["impl Clone for SFlag"],["impl Clone for Mode"],["impl Clone for FchmodatFlags"],["impl Clone for UtimensatFlags"],["impl Clone for Statfs"],["impl Clone for FsType"],["impl Clone for FsFlags"],["impl Clone for Statvfs"],["impl Clone for SysInfo"],["impl Clone for Termios"],["impl Clone for BaudRate"],["impl Clone for SetArg"],["impl Clone for FlushArg"],["impl Clone for FlowArg"],["impl Clone for SpecialCharacterIndices"],["impl Clone for InputFlags"],["impl Clone for OutputFlags"],["impl Clone for ControlFlags"],["impl Clone for LocalFlags"],["impl Clone for Expiration"],["impl Clone for TimerSetTimeFlags"],["impl Clone for TimeSpec"],["impl Clone for TimeVal"],["impl Clone for RemoteIoVec"],["impl<T: Clone> Clone for IoVec<T>"],["impl Clone for UtsName"],["impl Clone for WaitPidFlag"],["impl Clone for WaitStatus"],["impl Clone for Id"],["impl Clone for AddWatchFlags"],["impl Clone for InitFlags"],["impl Clone for Inotify"],["impl Clone for WatchDescriptor"],["impl Clone for ClockId"],["impl Clone for TimerFlags"],["impl Clone for ClockId"],["impl Clone for UContext"],["impl Clone for Uid"],["impl Clone for Gid"],["impl Clone for Pid"],["impl Clone for ForkResult"],["impl Clone for FchownatFlags"],["impl Clone for Whence"],["impl Clone for LinkatFlags"],["impl Clone for UnlinkatFlags"],["impl Clone for PathconfVar"],["impl Clone for SysconfVar"],["impl Clone for ResUid"],["impl Clone for ResGid"],["impl Clone for AccessFlags"],["impl Clone for User"],["impl Clone for Group"]], +"once_cell":[["impl<T: Clone> Clone for OnceCell<T>"],["impl<T: Clone> Clone for OnceCell<T>"]], +"parking_lot":[["impl Clone for WaitTimeoutResult"],["impl Clone for OnceState"]], +"parking_lot_core":[["impl Clone for ParkResult"],["impl Clone for UnparkResult"],["impl Clone for RequeueOp"],["impl Clone for FilterOp"],["impl Clone for UnparkToken"],["impl Clone for ParkToken"]], +"ppv_lite86":[["impl Clone for YesS3"],["impl Clone for NoS3"],["impl Clone for YesS4"],["impl Clone for NoS4"],["impl Clone for YesA1"],["impl Clone for NoA1"],["impl Clone for YesA2"],["impl Clone for NoA2"],["impl Clone for YesNI"],["impl Clone for NoNI"],["impl<S3: Clone, S4: Clone, NI: Clone> Clone for SseMachine<S3, S4, NI>"],["impl<NI: Clone> Clone for Avx2Machine<NI>"],["impl Clone for vec128_storage"],["impl Clone for vec256_storage"],["impl Clone for vec512_storage"]], +"primitive_types":[["impl Clone for U128"],["impl Clone for U256"],["impl Clone for U512"],["impl Clone for H128"],["impl Clone for H160"],["impl Clone for H256"],["impl Clone for H384"],["impl Clone for H512"],["impl Clone for H768"]], +"proc_macro2":[["impl Clone for TokenStream"],["impl Clone for Span"],["impl Clone for TokenTree"],["impl Clone for Group"],["impl Clone for Delimiter"],["impl Clone for Punct"],["impl Clone for Spacing"],["impl Clone for Ident"],["impl Clone for Literal"],["impl Clone for IntoIter"]], +"rand":[["impl Clone for Bernoulli"],["impl Clone for BernoulliError"],["impl Clone for OpenClosed01"],["impl Clone for Open01"],["impl Clone for Alphanumeric"],["impl<'a, T: Clone> Clone for Slice<'a, T>"],["impl<X: Clone + SampleUniform + PartialOrd> Clone for WeightedIndex<X>where
    X::Sampler: Clone,
"],["impl Clone for WeightedError"],["impl<X: Clone + SampleUniform> Clone for Uniform<X>where
    X::Sampler: Clone,
"],["impl<X: Clone> Clone for UniformInt<X>"],["impl Clone for UniformChar"],["impl<X: Clone> Clone for UniformFloat<X>"],["impl Clone for UniformDuration"],["impl Clone for Standard"],["impl<R, Rsdr> Clone for ReseedingRng<R, Rsdr>where
    R: BlockRngCore + SeedableRng + Clone,
    Rsdr: RngCore + Clone,
"],["impl Clone for StepRng"],["impl Clone for IndexVec"],["impl Clone for IndexVecIntoIter"]], +"rand_chacha":[["impl Clone for ChaCha20Core"],["impl Clone for ChaCha20Rng"],["impl Clone for ChaCha12Core"],["impl Clone for ChaCha12Rng"],["impl Clone for ChaCha8Core"],["impl Clone for ChaCha8Rng"]], +"rand_core":[["impl<R: Clone + BlockRngCore + ?Sized> Clone for BlockRng<R>where
    R::Results: Clone,
"],["impl<R: Clone + BlockRngCore + ?Sized> Clone for BlockRng64<R>where
    R::Results: Clone,
"],["impl Clone for OsRng"]], +"regex":[["impl Clone for Error"],["impl<'t> Clone for Match<'t>"],["impl Clone for Regex"],["impl<'r> Clone for CaptureNames<'r>"],["impl Clone for CaptureLocations"],["impl<'c, 't> Clone for SubCaptureMatches<'c, 't>"],["impl<'t> Clone for NoExpand<'t>"],["impl Clone for RegexSet"],["impl Clone for SetMatches"],["impl<'a> Clone for SetMatchesIter<'a>"],["impl Clone for RegexSet"],["impl Clone for SetMatches"],["impl<'a> Clone for SetMatchesIter<'a>"],["impl<'t> Clone for Match<'t>"],["impl Clone for Regex"],["impl<'r> Clone for CaptureNames<'r>"],["impl Clone for CaptureLocations"],["impl<'c, 't> Clone for SubCaptureMatches<'c, 't>"],["impl<'t> Clone for NoExpand<'t>"]], +"regex_syntax":[["impl Clone for ParserBuilder"],["impl Clone for Parser"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for Span"],["impl Clone for Position"],["impl Clone for WithComments"],["impl Clone for Comment"],["impl Clone for Ast"],["impl Clone for Alternation"],["impl Clone for Concat"],["impl Clone for Literal"],["impl Clone for LiteralKind"],["impl Clone for SpecialLiteralKind"],["impl Clone for HexLiteralKind"],["impl Clone for Class"],["impl Clone for ClassPerl"],["impl Clone for ClassPerlKind"],["impl Clone for ClassAscii"],["impl Clone for ClassAsciiKind"],["impl Clone for ClassUnicode"],["impl Clone for ClassUnicodeKind"],["impl Clone for ClassUnicodeOpKind"],["impl Clone for ClassBracketed"],["impl Clone for ClassSet"],["impl Clone for ClassSetItem"],["impl Clone for ClassSetRange"],["impl Clone for ClassSetUnion"],["impl Clone for ClassSetBinaryOp"],["impl Clone for ClassSetBinaryOpKind"],["impl Clone for Assertion"],["impl Clone for AssertionKind"],["impl Clone for Repetition"],["impl Clone for RepetitionOp"],["impl Clone for RepetitionKind"],["impl Clone for RepetitionRange"],["impl Clone for Group"],["impl Clone for GroupKind"],["impl Clone for CaptureName"],["impl Clone for SetFlags"],["impl Clone for Flags"],["impl Clone for FlagsItem"],["impl Clone for FlagsItemKind"],["impl Clone for Flag"],["impl Clone for Error"],["impl Clone for Literals"],["impl Clone for Literal"],["impl Clone for TranslatorBuilder"],["impl Clone for Translator"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for Hir"],["impl Clone for HirKind"],["impl Clone for Literal"],["impl Clone for Class"],["impl Clone for ClassUnicode"],["impl Clone for ClassUnicodeRange"],["impl Clone for ClassBytes"],["impl Clone for ClassBytesRange"],["impl Clone for Anchor"],["impl Clone for WordBoundary"],["impl Clone for Group"],["impl Clone for GroupKind"],["impl Clone for Repetition"],["impl Clone for RepetitionKind"],["impl Clone for RepetitionRange"],["impl Clone for ParserBuilder"],["impl Clone for Parser"],["impl Clone for Utf8Sequence"],["impl Clone for Utf8Range"]], +"rlp":[["impl Clone for DecoderError"],["impl<'a> Clone for Rlp<'a>"]], +"rustc_hex":[["impl Clone for FromHexError"]], +"serde":[["impl Clone for Error"],["impl<E> Clone for UnitDeserializer<E>"],["impl<E> Clone for BoolDeserializer<E>"],["impl<E> Clone for I8Deserializer<E>"],["impl<E> Clone for I16Deserializer<E>"],["impl<E> Clone for I32Deserializer<E>"],["impl<E> Clone for I64Deserializer<E>"],["impl<E> Clone for IsizeDeserializer<E>"],["impl<E> Clone for U8Deserializer<E>"],["impl<E> Clone for U16Deserializer<E>"],["impl<E> Clone for U64Deserializer<E>"],["impl<E> Clone for UsizeDeserializer<E>"],["impl<E> Clone for F32Deserializer<E>"],["impl<E> Clone for F64Deserializer<E>"],["impl<E> Clone for CharDeserializer<E>"],["impl<E> Clone for I128Deserializer<E>"],["impl<E> Clone for U128Deserializer<E>"],["impl<E> Clone for U32Deserializer<E>"],["impl<'de, E> Clone for StrDeserializer<'de, E>"],["impl<'de, E> Clone for BorrowedStrDeserializer<'de, E>"],["impl<E> Clone for StringDeserializer<E>"],["impl<'a, E> Clone for CowStrDeserializer<'a, E>"],["impl<'a, E> Clone for BytesDeserializer<'a, E>"],["impl<'de, E> Clone for BorrowedBytesDeserializer<'de, E>"],["impl<I: Clone, E: Clone> Clone for SeqDeserializer<I, E>"],["impl<A: Clone> Clone for SeqAccessDeserializer<A>"],["impl<'de, I, E> Clone for MapDeserializer<'de, I, E>where
    I: Iterator + Clone,
    I::Item: Pair,
    <I::Item as Pair>::Second: Clone,
"],["impl<A: Clone> Clone for MapAccessDeserializer<A>"],["impl<A: Clone> Clone for EnumAccessDeserializer<A>"],["impl Clone for IgnoredAny"],["impl<'a> Clone for Unexpected<'a>"]], +"sha3":[["impl Clone for Keccak224Core"],["impl Clone for Keccak256Core"],["impl Clone for Keccak384Core"],["impl Clone for Keccak512Core"],["impl Clone for Keccak256FullCore"],["impl Clone for Sha3_224Core"],["impl Clone for Sha3_256Core"],["impl Clone for Sha3_384Core"],["impl Clone for Sha3_512Core"],["impl Clone for Shake128Core"],["impl Clone for Shake128ReaderCore"],["impl Clone for Shake256Core"],["impl Clone for Shake256ReaderCore"],["impl Clone for CShake128Core"],["impl Clone for CShake128ReaderCore"],["impl Clone for CShake256Core"],["impl Clone for CShake256ReaderCore"]], +"shale":[["impl<T> Clone for ObjPtr<T>"]], +"slab":[["impl<T: Clone> Clone for Slab<T>"],["impl<'a, T> Clone for Iter<'a, T>"]], +"smallvec":[["impl<A: Array> Clone for SmallVec<A>where
    A::Item: Clone,
"],["impl<A: Array + Clone> Clone for IntoIter<A>where
    A::Item: Clone,
"]], +"syn":[["impl Clone for Underscore"],["impl Clone for Abstract"],["impl Clone for As"],["impl Clone for Async"],["impl Clone for Auto"],["impl Clone for Await"],["impl Clone for Become"],["impl Clone for Box"],["impl Clone for Break"],["impl Clone for Const"],["impl Clone for Continue"],["impl Clone for Crate"],["impl Clone for Default"],["impl Clone for Do"],["impl Clone for Dyn"],["impl Clone for Else"],["impl Clone for Enum"],["impl Clone for Extern"],["impl Clone for Final"],["impl Clone for Fn"],["impl Clone for For"],["impl Clone for If"],["impl Clone for Impl"],["impl Clone for In"],["impl Clone for Let"],["impl Clone for Loop"],["impl Clone for Macro"],["impl Clone for Match"],["impl Clone for Mod"],["impl Clone for Move"],["impl Clone for Mut"],["impl Clone for Override"],["impl Clone for Priv"],["impl Clone for Pub"],["impl Clone for Ref"],["impl Clone for Return"],["impl Clone for SelfType"],["impl Clone for SelfValue"],["impl Clone for Static"],["impl Clone for Struct"],["impl Clone for Super"],["impl Clone for Trait"],["impl Clone for Try"],["impl Clone for Type"],["impl Clone for Typeof"],["impl Clone for Union"],["impl Clone for Unsafe"],["impl Clone for Unsized"],["impl Clone for Use"],["impl Clone for Virtual"],["impl Clone for Where"],["impl Clone for While"],["impl Clone for Yield"],["impl Clone for Add"],["impl Clone for AddEq"],["impl Clone for And"],["impl Clone for AndAnd"],["impl Clone for AndEq"],["impl Clone for At"],["impl Clone for Bang"],["impl Clone for Caret"],["impl Clone for CaretEq"],["impl Clone for Colon"],["impl Clone for Colon2"],["impl Clone for Comma"],["impl Clone for Div"],["impl Clone for DivEq"],["impl Clone for Dollar"],["impl Clone for Dot"],["impl Clone for Dot2"],["impl Clone for Dot3"],["impl Clone for DotDotEq"],["impl Clone for Eq"],["impl Clone for EqEq"],["impl Clone for Ge"],["impl Clone for Gt"],["impl Clone for Le"],["impl Clone for Lt"],["impl Clone for MulEq"],["impl Clone for Ne"],["impl Clone for Or"],["impl Clone for OrEq"],["impl Clone for OrOr"],["impl Clone for Pound"],["impl Clone for Question"],["impl Clone for RArrow"],["impl Clone for LArrow"],["impl Clone for Rem"],["impl Clone for RemEq"],["impl Clone for FatArrow"],["impl Clone for Semi"],["impl Clone for Shl"],["impl Clone for ShlEq"],["impl Clone for Shr"],["impl Clone for ShrEq"],["impl Clone for Star"],["impl Clone for Sub"],["impl Clone for SubEq"],["impl Clone for Tilde"],["impl Clone for Brace"],["impl Clone for Bracket"],["impl Clone for Paren"],["impl Clone for Group"],["impl<'a> Clone for ImplGenerics<'a>"],["impl<'a> Clone for TypeGenerics<'a>"],["impl<'a> Clone for Turbofish<'a>"],["impl Clone for Lifetime"],["impl Clone for LitStr"],["impl Clone for LitByteStr"],["impl Clone for LitByte"],["impl Clone for LitChar"],["impl Clone for LitInt"],["impl Clone for LitFloat"],["impl<'a> Clone for Cursor<'a>"],["impl<T, P> Clone for Punctuated<T, P>where
    T: Clone,
    P: Clone,
"],["impl<'a, T, P> Clone for Pairs<'a, T, P>"],["impl<T, P> Clone for IntoPairs<T, P>where
    T: Clone,
    P: Clone,
"],["impl<T> Clone for IntoIter<T>where
    T: Clone,
"],["impl<'a, T> Clone for Iter<'a, T>"],["impl<T, P> Clone for Pair<T, P>where
    T: Clone,
    P: Clone,
"],["impl Clone for Abi"],["impl Clone for AngleBracketedGenericArguments"],["impl Clone for Arm"],["impl Clone for AttrStyle"],["impl Clone for Attribute"],["impl Clone for BareFnArg"],["impl Clone for BinOp"],["impl Clone for Binding"],["impl Clone for Block"],["impl Clone for BoundLifetimes"],["impl Clone for ConstParam"],["impl Clone for Constraint"],["impl Clone for Data"],["impl Clone for DataEnum"],["impl Clone for DataStruct"],["impl Clone for DataUnion"],["impl Clone for DeriveInput"],["impl Clone for Expr"],["impl Clone for ExprArray"],["impl Clone for ExprAssign"],["impl Clone for ExprAssignOp"],["impl Clone for ExprAsync"],["impl Clone for ExprAwait"],["impl Clone for ExprBinary"],["impl Clone for ExprBlock"],["impl Clone for ExprBox"],["impl Clone for ExprBreak"],["impl Clone for ExprCall"],["impl Clone for ExprCast"],["impl Clone for ExprClosure"],["impl Clone for ExprContinue"],["impl Clone for ExprField"],["impl Clone for ExprForLoop"],["impl Clone for ExprGroup"],["impl Clone for ExprIf"],["impl Clone for ExprIndex"],["impl Clone for ExprLet"],["impl Clone for ExprLit"],["impl Clone for ExprLoop"],["impl Clone for ExprMacro"],["impl Clone for ExprMatch"],["impl Clone for ExprMethodCall"],["impl Clone for ExprParen"],["impl Clone for ExprPath"],["impl Clone for ExprRange"],["impl Clone for ExprReference"],["impl Clone for ExprRepeat"],["impl Clone for ExprReturn"],["impl Clone for ExprStruct"],["impl Clone for ExprTry"],["impl Clone for ExprTryBlock"],["impl Clone for ExprTuple"],["impl Clone for ExprType"],["impl Clone for ExprUnary"],["impl Clone for ExprUnsafe"],["impl Clone for ExprWhile"],["impl Clone for ExprYield"],["impl Clone for Field"],["impl Clone for FieldPat"],["impl Clone for FieldValue"],["impl Clone for Fields"],["impl Clone for FieldsNamed"],["impl Clone for FieldsUnnamed"],["impl Clone for File"],["impl Clone for FnArg"],["impl Clone for ForeignItem"],["impl Clone for ForeignItemFn"],["impl Clone for ForeignItemMacro"],["impl Clone for ForeignItemStatic"],["impl Clone for ForeignItemType"],["impl Clone for GenericArgument"],["impl Clone for GenericMethodArgument"],["impl Clone for GenericParam"],["impl Clone for Generics"],["impl Clone for ImplItem"],["impl Clone for ImplItemConst"],["impl Clone for ImplItemMacro"],["impl Clone for ImplItemMethod"],["impl Clone for ImplItemType"],["impl Clone for Index"],["impl Clone for Item"],["impl Clone for ItemConst"],["impl Clone for ItemEnum"],["impl Clone for ItemExternCrate"],["impl Clone for ItemFn"],["impl Clone for ItemForeignMod"],["impl Clone for ItemImpl"],["impl Clone for ItemMacro"],["impl Clone for ItemMacro2"],["impl Clone for ItemMod"],["impl Clone for ItemStatic"],["impl Clone for ItemStruct"],["impl Clone for ItemTrait"],["impl Clone for ItemTraitAlias"],["impl Clone for ItemType"],["impl Clone for ItemUnion"],["impl Clone for ItemUse"],["impl Clone for Label"],["impl Clone for LifetimeDef"],["impl Clone for Lit"],["impl Clone for LitBool"],["impl Clone for Local"],["impl Clone for Macro"],["impl Clone for MacroDelimiter"],["impl Clone for Member"],["impl Clone for Meta"],["impl Clone for MetaList"],["impl Clone for MetaNameValue"],["impl Clone for MethodTurbofish"],["impl Clone for NestedMeta"],["impl Clone for ParenthesizedGenericArguments"],["impl Clone for Pat"],["impl Clone for PatBox"],["impl Clone for PatIdent"],["impl Clone for PatLit"],["impl Clone for PatMacro"],["impl Clone for PatOr"],["impl Clone for PatPath"],["impl Clone for PatRange"],["impl Clone for PatReference"],["impl Clone for PatRest"],["impl Clone for PatSlice"],["impl Clone for PatStruct"],["impl Clone for PatTuple"],["impl Clone for PatTupleStruct"],["impl Clone for PatType"],["impl Clone for PatWild"],["impl Clone for Path"],["impl Clone for PathArguments"],["impl Clone for PathSegment"],["impl Clone for PredicateEq"],["impl Clone for PredicateLifetime"],["impl Clone for PredicateType"],["impl Clone for QSelf"],["impl Clone for RangeLimits"],["impl Clone for Receiver"],["impl Clone for ReturnType"],["impl Clone for Signature"],["impl Clone for Stmt"],["impl Clone for TraitBound"],["impl Clone for TraitBoundModifier"],["impl Clone for TraitItem"],["impl Clone for TraitItemConst"],["impl Clone for TraitItemMacro"],["impl Clone for TraitItemMethod"],["impl Clone for TraitItemType"],["impl Clone for Type"],["impl Clone for TypeArray"],["impl Clone for TypeBareFn"],["impl Clone for TypeGroup"],["impl Clone for TypeImplTrait"],["impl Clone for TypeInfer"],["impl Clone for TypeMacro"],["impl Clone for TypeNever"],["impl Clone for TypeParam"],["impl Clone for TypeParamBound"],["impl Clone for TypeParen"],["impl Clone for TypePath"],["impl Clone for TypePtr"],["impl Clone for TypeReference"],["impl Clone for TypeSlice"],["impl Clone for TypeTraitObject"],["impl Clone for TypeTuple"],["impl Clone for UnOp"],["impl Clone for UseGlob"],["impl Clone for UseGroup"],["impl Clone for UseName"],["impl Clone for UsePath"],["impl Clone for UseRename"],["impl Clone for UseTree"],["impl Clone for Variadic"],["impl Clone for Variant"],["impl Clone for VisCrate"],["impl Clone for VisPublic"],["impl Clone for VisRestricted"],["impl Clone for Visibility"],["impl Clone for WhereClause"],["impl Clone for WherePredicate"],["impl<'c, 'a> Clone for StepCursor<'c, 'a>"],["impl Clone for Error"]], +"tokio":[["impl Clone for Handle"],["impl Clone for BarrierWaitResult"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for WeakSender<T>"],["impl<T> Clone for UnboundedSender<T>"],["impl<T> Clone for WeakUnboundedSender<T>"],["impl Clone for TryRecvError"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl<T: Clone> Clone for OnceCell<T>"],["impl Clone for RecvError"],["impl<T> Clone for Receiver<T>"]], +"typenum":[["impl Clone for B0"],["impl Clone for B1"],["impl<U: Clone + Unsigned + NonZero> Clone for PInt<U>"],["impl<U: Clone + Unsigned + NonZero> Clone for NInt<U>"],["impl Clone for Z0"],["impl Clone for UTerm"],["impl<U: Clone, B: Clone> Clone for UInt<U, B>"],["impl Clone for ATerm"],["impl<V: Clone, A: Clone> Clone for TArr<V, A>"],["impl Clone for Greater"],["impl Clone for Less"],["impl Clone for Equal"]], +"uint":[["impl Clone for FromStrRadixErrKind"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/cmp/trait.Eq.js b/docs/implementors/core/cmp/trait.Eq.js new file mode 100644 index 000000000000..4727063fccf9 --- /dev/null +++ b/docs/implementors/core/cmp/trait.Eq.js @@ -0,0 +1,36 @@ +(function() {var implementors = { +"aho_corasick":[["impl Eq for MatchKind"],["impl Eq for MatchKind"],["impl Eq for Match"]], +"block_buffer":[["impl Eq for Error"]], +"byteorder":[["impl Eq for BigEndian"],["impl Eq for LittleEndian"]], +"bytes":[["impl Eq for Bytes"],["impl Eq for BytesMut"]], +"crossbeam_channel":[["impl<T: Eq> Eq for SendError<T>"],["impl<T: Eq> Eq for TrySendError<T>"],["impl<T: Eq> Eq for SendTimeoutError<T>"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl Eq for RecvTimeoutError"],["impl Eq for TrySelectError"],["impl Eq for SelectTimeoutError"],["impl Eq for TryReadyError"],["impl Eq for ReadyTimeoutError"]], +"crossbeam_utils":[["impl<T: Eq> Eq for CachePadded<T>"]], +"crypto_common":[["impl Eq for InvalidLength"]], +"digest":[["impl Eq for InvalidBufferSize"]], +"firewood":[["impl Eq for Hash"],["impl Eq for PartialPath"],["impl Eq for Node"]], +"futures_channel":[["impl Eq for SendError"],["impl<T: Eq> Eq for TrySendError<T>"],["impl Eq for Canceled"]], +"futures_util":[["impl<T: Eq, E: Eq> Eq for TryChunksError<T, E>"],["impl Eq for PollNext"],["impl<T: Eq> Eq for AllowStdIo<T>"],["impl Eq for Aborted"]], +"generic_array":[["impl<T: Eq, N> Eq for GenericArray<T, N>where
    N: ArrayLength<T>,
"]], +"getrandom":[["impl Eq for Error"]], +"growthring":[["impl Eq for WALRingId"]], +"hashbrown":[["impl<K, V, S, A> Eq for HashMap<K, V, S, A>where
    K: Eq + Hash,
    V: Eq,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T, S, A> Eq for HashSet<T, S, A>where
    T: Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl Eq for TryReserveError"]], +"libc":[["impl Eq for group"],["impl Eq for utimbuf"],["impl Eq for timeval"],["impl Eq for timespec"],["impl Eq for rlimit"],["impl Eq for rusage"],["impl Eq for ipv6_mreq"],["impl Eq for hostent"],["impl Eq for iovec"],["impl Eq for pollfd"],["impl Eq for winsize"],["impl Eq for linger"],["impl Eq for sigval"],["impl Eq for itimerval"],["impl Eq for tms"],["impl Eq for servent"],["impl Eq for protoent"],["impl Eq for in_addr"],["impl Eq for ip_mreq"],["impl Eq for ip_mreqn"],["impl Eq for ip_mreq_source"],["impl Eq for sockaddr"],["impl Eq for sockaddr_in"],["impl Eq for sockaddr_in6"],["impl Eq for addrinfo"],["impl Eq for sockaddr_ll"],["impl Eq for fd_set"],["impl Eq for tm"],["impl Eq for sched_param"],["impl Eq for Dl_info"],["impl Eq for lconv"],["impl Eq for in_pktinfo"],["impl Eq for ifaddrs"],["impl Eq for in6_rtmsg"],["impl Eq for arpreq"],["impl Eq for arpreq_old"],["impl Eq for arphdr"],["impl Eq for mmsghdr"],["impl Eq for epoll_event"],["impl Eq for sockaddr_un"],["impl Eq for sockaddr_storage"],["impl Eq for utsname"],["impl Eq for sigevent"],["impl Eq for rlimit64"],["impl Eq for glob_t"],["impl Eq for passwd"],["impl Eq for spwd"],["impl Eq for dqblk"],["impl Eq for signalfd_siginfo"],["impl Eq for itimerspec"],["impl Eq for fsid_t"],["impl Eq for packet_mreq"],["impl Eq for cpu_set_t"],["impl Eq for if_nameindex"],["impl Eq for msginfo"],["impl Eq for sembuf"],["impl Eq for input_event"],["impl Eq for input_id"],["impl Eq for input_absinfo"],["impl Eq for input_keymap_entry"],["impl Eq for input_mask"],["impl Eq for ff_replay"],["impl Eq for ff_trigger"],["impl Eq for ff_envelope"],["impl Eq for ff_constant_effect"],["impl Eq for ff_ramp_effect"],["impl Eq for ff_condition_effect"],["impl Eq for ff_periodic_effect"],["impl Eq for ff_rumble_effect"],["impl Eq for ff_effect"],["impl Eq for uinput_ff_upload"],["impl Eq for uinput_ff_erase"],["impl Eq for uinput_abs_setup"],["impl Eq for dl_phdr_info"],["impl Eq for Elf32_Ehdr"],["impl Eq for Elf64_Ehdr"],["impl Eq for Elf32_Sym"],["impl Eq for Elf64_Sym"],["impl Eq for Elf32_Phdr"],["impl Eq for Elf64_Phdr"],["impl Eq for Elf32_Shdr"],["impl Eq for Elf64_Shdr"],["impl Eq for ucred"],["impl Eq for mntent"],["impl Eq for posix_spawn_file_actions_t"],["impl Eq for posix_spawnattr_t"],["impl Eq for genlmsghdr"],["impl Eq for in6_pktinfo"],["impl Eq for arpd_request"],["impl Eq for inotify_event"],["impl Eq for fanotify_response"],["impl Eq for sockaddr_vm"],["impl Eq for regmatch_t"],["impl Eq for sock_extended_err"],["impl Eq for __c_anonymous_sockaddr_can_tp"],["impl Eq for __c_anonymous_sockaddr_can_j1939"],["impl Eq for can_filter"],["impl Eq for j1939_filter"],["impl Eq for sock_filter"],["impl Eq for sock_fprog"],["impl Eq for seccomp_data"],["impl Eq for nlmsghdr"],["impl Eq for nlmsgerr"],["impl Eq for nlattr"],["impl Eq for file_clone_range"],["impl Eq for __c_anonymous_ifru_map"],["impl Eq for in6_ifreq"],["impl Eq for sockaddr_nl"],["impl Eq for dirent"],["impl Eq for dirent64"],["impl Eq for pthread_cond_t"],["impl Eq for pthread_mutex_t"],["impl Eq for pthread_rwlock_t"],["impl Eq for sockaddr_alg"],["impl Eq for uinput_setup"],["impl Eq for uinput_user_dev"],["impl Eq for af_alg_iv"],["impl Eq for mq_attr"],["impl Eq for statx"],["impl Eq for statx_timestamp"],["impl Eq for aiocb"],["impl Eq for __exit_status"],["impl Eq for __timeval"],["impl Eq for glob64_t"],["impl Eq for msghdr"],["impl Eq for cmsghdr"],["impl Eq for termios"],["impl Eq for mallinfo"],["impl Eq for mallinfo2"],["impl Eq for nl_pktinfo"],["impl Eq for nl_mmap_req"],["impl Eq for nl_mmap_hdr"],["impl Eq for rtentry"],["impl Eq for timex"],["impl Eq for ntptimeval"],["impl Eq for regex_t"],["impl Eq for Elf64_Chdr"],["impl Eq for Elf32_Chdr"],["impl Eq for seminfo"],["impl Eq for ptrace_peeksiginfo_args"],["impl Eq for __c_anonymous_ptrace_syscall_info_entry"],["impl Eq for __c_anonymous_ptrace_syscall_info_exit"],["impl Eq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Eq for ptrace_syscall_info"],["impl Eq for utmpx"],["impl Eq for __c_anonymous_ptrace_syscall_info_data"],["impl Eq for sigset_t"],["impl Eq for sysinfo"],["impl Eq for msqid_ds"],["impl Eq for semid_ds"],["impl Eq for sigaction"],["impl Eq for statfs"],["impl Eq for flock"],["impl Eq for flock64"],["impl Eq for siginfo_t"],["impl Eq for stack_t"],["impl Eq for stat"],["impl Eq for stat64"],["impl Eq for statfs64"],["impl Eq for statvfs64"],["impl Eq for pthread_attr_t"],["impl Eq for _libc_fpxreg"],["impl Eq for _libc_xmmreg"],["impl Eq for _libc_fpstate"],["impl Eq for user_regs_struct"],["impl Eq for user"],["impl Eq for mcontext_t"],["impl Eq for ipc_perm"],["impl Eq for shmid_ds"],["impl Eq for seccomp_notif_sizes"],["impl Eq for ptrace_rseq_configuration"],["impl Eq for user_fpregs_struct"],["impl Eq for ucontext_t"],["impl Eq for statvfs"],["impl Eq for clone_args"],["impl Eq for sem_t"],["impl Eq for termios2"],["impl Eq for pthread_mutexattr_t"],["impl Eq for pthread_rwlockattr_t"],["impl Eq for pthread_condattr_t"],["impl Eq for fanotify_event_metadata"],["impl Eq for open_how"],["impl Eq for in6_addr"]], +"nix":[["impl Eq for Dir"],["impl<'d> Eq for Iter<'d>"],["impl Eq for OwningIter"],["impl Eq for Entry"],["impl Eq for Type"],["impl Eq for Errno"],["impl Eq for AtFlags"],["impl Eq for OFlag"],["impl Eq for RenameFlags"],["impl Eq for SealFlag"],["impl Eq for FdFlag"],["impl<'a> Eq for FcntlArg<'a>"],["impl Eq for FlockArg"],["impl Eq for SpliceFFlags"],["impl Eq for FallocateFlags"],["impl Eq for PosixFadviseAdvice"],["impl Eq for InterfaceAddress"],["impl Eq for InterfaceAddressIterator"],["impl Eq for InterfaceFlags"],["impl Eq for ModuleInitFlags"],["impl Eq for DeleteModuleFlags"],["impl Eq for MsFlags"],["impl Eq for MntFlags"],["impl Eq for MQ_OFlag"],["impl Eq for MqAttr"],["impl Eq for PollFd"],["impl Eq for PollFlags"],["impl Eq for OpenptyResult"],["impl Eq for PtyMaster"],["impl Eq for CloneFlags"],["impl Eq for CpuSet"],["impl Eq for AioFsyncMode"],["impl Eq for LioMode"],["impl Eq for AioCancelStat"],["impl Eq for EpollFlags"],["impl Eq for EpollOp"],["impl Eq for EpollCreateFlags"],["impl Eq for EpollEvent"],["impl Eq for EfdFlags"],["impl Eq for MemFdCreateFlag"],["impl Eq for ProtFlags"],["impl Eq for MapFlags"],["impl Eq for MRemapFlags"],["impl Eq for MmapAdvise"],["impl Eq for MsFlags"],["impl Eq for MlockAllFlags"],["impl Eq for Persona"],["impl Eq for Request"],["impl Eq for Event"],["impl Eq for Options"],["impl Eq for QuotaType"],["impl Eq for QuotaFmt"],["impl Eq for QuotaValidFlags"],["impl Eq for Dqblk"],["impl Eq for RebootMode"],["impl Eq for Resource"],["impl Eq for UsageWho"],["impl Eq for Usage"],["impl Eq for FdSet"],["impl Eq for Signal"],["impl Eq for SignalIterator"],["impl Eq for SaFlags"],["impl Eq for SigmaskHow"],["impl Eq for SigSet"],["impl Eq for SigHandler"],["impl Eq for SigAction"],["impl Eq for SigevNotify"],["impl Eq for SigEvent"],["impl Eq for SfdFlags"],["impl Eq for SignalFd"],["impl Eq for AddressFamily"],["impl Eq for InetAddr"],["impl Eq for IpAddr"],["impl Eq for Ipv4Addr"],["impl Eq for Ipv6Addr"],["impl Eq for UnixAddr"],["impl Eq for SockaddrIn"],["impl Eq for SockaddrIn6"],["impl Eq for SockaddrStorage"],["impl Eq for SockAddr"],["impl Eq for NetlinkAddr"],["impl Eq for AlgAddr"],["impl Eq for LinkAddr"],["impl Eq for VsockAddr"],["impl Eq for ReuseAddr"],["impl Eq for ReusePort"],["impl Eq for TcpNoDelay"],["impl Eq for Linger"],["impl Eq for IpAddMembership"],["impl Eq for IpDropMembership"],["impl Eq for Ipv6AddMembership"],["impl Eq for Ipv6DropMembership"],["impl Eq for IpMulticastTtl"],["impl Eq for IpMulticastLoop"],["impl Eq for Priority"],["impl Eq for IpTos"],["impl Eq for Ipv6TClass"],["impl Eq for IpFreebind"],["impl Eq for ReceiveTimeout"],["impl Eq for SendTimeout"],["impl Eq for Broadcast"],["impl Eq for OobInline"],["impl Eq for SocketError"],["impl Eq for DontRoute"],["impl Eq for KeepAlive"],["impl Eq for PeerCredentials"],["impl Eq for TcpKeepIdle"],["impl Eq for TcpMaxSeg"],["impl Eq for TcpKeepCount"],["impl Eq for TcpRepair"],["impl Eq for TcpKeepInterval"],["impl Eq for TcpUserTimeout"],["impl Eq for RcvBuf"],["impl Eq for SndBuf"],["impl Eq for RcvBufForce"],["impl Eq for SndBufForce"],["impl Eq for SockType"],["impl Eq for AcceptConn"],["impl Eq for BindToDevice"],["impl Eq for OriginalDst"],["impl Eq for Ip6tOriginalDst"],["impl Eq for Timestamping"],["impl Eq for ReceiveTimestamp"],["impl Eq for ReceiveTimestampns"],["impl Eq for IpTransparent"],["impl Eq for Mark"],["impl Eq for PassCred"],["impl Eq for TcpCongestion"],["impl Eq for Ipv4PacketInfo"],["impl Eq for Ipv6RecvPacketInfo"],["impl Eq for Ipv4OrigDstAddr"],["impl Eq for UdpGsoSegment"],["impl Eq for UdpGroSegment"],["impl Eq for TxTime"],["impl Eq for RxqOvfl"],["impl Eq for Ipv6V6Only"],["impl Eq for Ipv4RecvErr"],["impl Eq for Ipv6RecvErr"],["impl Eq for IpMtu"],["impl Eq for Ipv4Ttl"],["impl Eq for Ipv6Ttl"],["impl Eq for Ipv6OrigDstAddr"],["impl Eq for Ipv6DontFrag"],["impl Eq for SockType"],["impl Eq for SockProtocol"],["impl Eq for TimestampingFlag"],["impl Eq for SockFlag"],["impl Eq for MsgFlags"],["impl Eq for UnixCredentials"],["impl Eq for IpMembershipRequest"],["impl Eq for Ipv6MembershipRequest"],["impl<'a, 's, S: Eq> Eq for RecvMsg<'a, 's, S>"],["impl<'a> Eq for CmsgIterator<'a>"],["impl Eq for ControlMessageOwned"],["impl Eq for Timestamps"],["impl<'a> Eq for ControlMessage<'a>"],["impl Eq for Shutdown"],["impl Eq for SFlag"],["impl Eq for Mode"],["impl Eq for FsType"],["impl Eq for FsFlags"],["impl Eq for Statvfs"],["impl Eq for SysInfo"],["impl Eq for Termios"],["impl Eq for BaudRate"],["impl Eq for SetArg"],["impl Eq for FlushArg"],["impl Eq for FlowArg"],["impl Eq for SpecialCharacterIndices"],["impl Eq for InputFlags"],["impl Eq for OutputFlags"],["impl Eq for ControlFlags"],["impl Eq for LocalFlags"],["impl Eq for Expiration"],["impl Eq for TimerSetTimeFlags"],["impl Eq for TimeSpec"],["impl Eq for TimeVal"],["impl Eq for RemoteIoVec"],["impl<T: Eq> Eq for IoVec<T>"],["impl Eq for UtsName"],["impl Eq for WaitPidFlag"],["impl Eq for WaitStatus"],["impl Eq for Id"],["impl Eq for AddWatchFlags"],["impl Eq for InitFlags"],["impl Eq for WatchDescriptor"],["impl Eq for ClockId"],["impl Eq for TimerFlags"],["impl Eq for ClockId"],["impl Eq for UContext"],["impl Eq for Uid"],["impl Eq for Gid"],["impl Eq for Pid"],["impl Eq for PathconfVar"],["impl Eq for SysconfVar"],["impl Eq for ResUid"],["impl Eq for ResGid"],["impl Eq for AccessFlags"],["impl Eq for User"],["impl Eq for Group"]], +"once_cell":[["impl<T: Eq> Eq for OnceCell<T>"],["impl<T: Eq> Eq for OnceCell<T>"]], +"parking_lot":[["impl Eq for WaitTimeoutResult"],["impl Eq for OnceState"]], +"parking_lot_core":[["impl Eq for ParkResult"],["impl Eq for UnparkResult"],["impl Eq for RequeueOp"],["impl Eq for FilterOp"],["impl Eq for UnparkToken"],["impl Eq for ParkToken"]], +"ppv_lite86":[["impl Eq for vec128_storage"],["impl Eq for vec256_storage"],["impl Eq for vec512_storage"]], +"primitive_types":[["impl Eq for Error"],["impl Eq for U128"],["impl Eq for U256"],["impl Eq for U512"],["impl Eq for H128"],["impl Eq for H160"],["impl Eq for H256"],["impl Eq for H384"],["impl Eq for H512"],["impl Eq for H768"]], +"proc_macro2":[["impl Eq for Delimiter"],["impl Eq for Spacing"],["impl Eq for Ident"]], +"rand":[["impl Eq for BernoulliError"],["impl Eq for WeightedError"],["impl Eq for StepRng"]], +"rand_chacha":[["impl Eq for ChaCha20Core"],["impl Eq for ChaCha20Rng"],["impl Eq for ChaCha12Core"],["impl Eq for ChaCha12Rng"],["impl Eq for ChaCha8Core"],["impl Eq for ChaCha8Rng"]], +"regex":[["impl<'t> Eq for Match<'t>"],["impl<'t> Eq for Match<'t>"]], +"regex_syntax":[["impl Eq for Error"],["impl Eq for ErrorKind"],["impl Eq for Span"],["impl Eq for Position"],["impl Eq for WithComments"],["impl Eq for Comment"],["impl Eq for Ast"],["impl Eq for Alternation"],["impl Eq for Concat"],["impl Eq for Literal"],["impl Eq for LiteralKind"],["impl Eq for SpecialLiteralKind"],["impl Eq for HexLiteralKind"],["impl Eq for Class"],["impl Eq for ClassPerl"],["impl Eq for ClassPerlKind"],["impl Eq for ClassAscii"],["impl Eq for ClassAsciiKind"],["impl Eq for ClassUnicode"],["impl Eq for ClassUnicodeKind"],["impl Eq for ClassUnicodeOpKind"],["impl Eq for ClassBracketed"],["impl Eq for ClassSet"],["impl Eq for ClassSetItem"],["impl Eq for ClassSetRange"],["impl Eq for ClassSetUnion"],["impl Eq for ClassSetBinaryOp"],["impl Eq for ClassSetBinaryOpKind"],["impl Eq for Assertion"],["impl Eq for AssertionKind"],["impl Eq for Repetition"],["impl Eq for RepetitionOp"],["impl Eq for RepetitionKind"],["impl Eq for RepetitionRange"],["impl Eq for Group"],["impl Eq for GroupKind"],["impl Eq for CaptureName"],["impl Eq for SetFlags"],["impl Eq for Flags"],["impl Eq for FlagsItem"],["impl Eq for FlagsItemKind"],["impl Eq for Flag"],["impl Eq for Error"],["impl Eq for Literals"],["impl Eq for Literal"],["impl Eq for Error"],["impl Eq for ErrorKind"],["impl Eq for Hir"],["impl Eq for HirKind"],["impl Eq for Literal"],["impl Eq for Class"],["impl Eq for ClassUnicode"],["impl Eq for ClassUnicodeRange"],["impl Eq for ClassBytes"],["impl Eq for ClassBytesRange"],["impl Eq for Anchor"],["impl Eq for WordBoundary"],["impl Eq for Group"],["impl Eq for GroupKind"],["impl Eq for Repetition"],["impl Eq for RepetitionKind"],["impl Eq for RepetitionRange"],["impl Eq for Utf8Sequence"],["impl Eq for Utf8Range"]], +"rlp":[["impl Eq for DecoderError"]], +"shale":[["impl<T> Eq for ObjPtr<T>"]], +"smallvec":[["impl<A: Array> Eq for SmallVec<A>where
    A::Item: Eq,
"]], +"syn":[["impl Eq for Underscore"],["impl Eq for Abstract"],["impl Eq for As"],["impl Eq for Async"],["impl Eq for Auto"],["impl Eq for Await"],["impl Eq for Become"],["impl Eq for Box"],["impl Eq for Break"],["impl Eq for Const"],["impl Eq for Continue"],["impl Eq for Crate"],["impl Eq for Default"],["impl Eq for Do"],["impl Eq for Dyn"],["impl Eq for Else"],["impl Eq for Enum"],["impl Eq for Extern"],["impl Eq for Final"],["impl Eq for Fn"],["impl Eq for For"],["impl Eq for If"],["impl Eq for Impl"],["impl Eq for In"],["impl Eq for Let"],["impl Eq for Loop"],["impl Eq for Macro"],["impl Eq for Match"],["impl Eq for Mod"],["impl Eq for Move"],["impl Eq for Mut"],["impl Eq for Override"],["impl Eq for Priv"],["impl Eq for Pub"],["impl Eq for Ref"],["impl Eq for Return"],["impl Eq for SelfType"],["impl Eq for SelfValue"],["impl Eq for Static"],["impl Eq for Struct"],["impl Eq for Super"],["impl Eq for Trait"],["impl Eq for Try"],["impl Eq for Type"],["impl Eq for Typeof"],["impl Eq for Union"],["impl Eq for Unsafe"],["impl Eq for Unsized"],["impl Eq for Use"],["impl Eq for Virtual"],["impl Eq for Where"],["impl Eq for While"],["impl Eq for Yield"],["impl Eq for Add"],["impl Eq for AddEq"],["impl Eq for And"],["impl Eq for AndAnd"],["impl Eq for AndEq"],["impl Eq for At"],["impl Eq for Bang"],["impl Eq for Caret"],["impl Eq for CaretEq"],["impl Eq for Colon"],["impl Eq for Colon2"],["impl Eq for Comma"],["impl Eq for Div"],["impl Eq for DivEq"],["impl Eq for Dollar"],["impl Eq for Dot"],["impl Eq for Dot2"],["impl Eq for Dot3"],["impl Eq for DotDotEq"],["impl Eq for Eq"],["impl Eq for EqEq"],["impl Eq for Ge"],["impl Eq for Gt"],["impl Eq for Le"],["impl Eq for Lt"],["impl Eq for MulEq"],["impl Eq for Ne"],["impl Eq for Or"],["impl Eq for OrEq"],["impl Eq for OrOr"],["impl Eq for Pound"],["impl Eq for Question"],["impl Eq for RArrow"],["impl Eq for LArrow"],["impl Eq for Rem"],["impl Eq for RemEq"],["impl Eq for FatArrow"],["impl Eq for Semi"],["impl Eq for Shl"],["impl Eq for ShlEq"],["impl Eq for Shr"],["impl Eq for ShrEq"],["impl Eq for Star"],["impl Eq for Sub"],["impl Eq for SubEq"],["impl Eq for Tilde"],["impl Eq for Brace"],["impl Eq for Bracket"],["impl Eq for Paren"],["impl Eq for Group"],["impl Eq for Member"],["impl Eq for Index"],["impl<'a> Eq for ImplGenerics<'a>"],["impl<'a> Eq for TypeGenerics<'a>"],["impl<'a> Eq for Turbofish<'a>"],["impl Eq for Lifetime"],["impl<'a> Eq for Cursor<'a>"],["impl<T, P> Eq for Punctuated<T, P>where
    T: Eq,
    P: Eq,
"],["impl Eq for Abi"],["impl Eq for AngleBracketedGenericArguments"],["impl Eq for Arm"],["impl Eq for AttrStyle"],["impl Eq for Attribute"],["impl Eq for BareFnArg"],["impl Eq for BinOp"],["impl Eq for Binding"],["impl Eq for Block"],["impl Eq for BoundLifetimes"],["impl Eq for ConstParam"],["impl Eq for Constraint"],["impl Eq for Data"],["impl Eq for DataEnum"],["impl Eq for DataStruct"],["impl Eq for DataUnion"],["impl Eq for DeriveInput"],["impl Eq for Expr"],["impl Eq for ExprArray"],["impl Eq for ExprAssign"],["impl Eq for ExprAssignOp"],["impl Eq for ExprAsync"],["impl Eq for ExprAwait"],["impl Eq for ExprBinary"],["impl Eq for ExprBlock"],["impl Eq for ExprBox"],["impl Eq for ExprBreak"],["impl Eq for ExprCall"],["impl Eq for ExprCast"],["impl Eq for ExprClosure"],["impl Eq for ExprContinue"],["impl Eq for ExprField"],["impl Eq for ExprForLoop"],["impl Eq for ExprGroup"],["impl Eq for ExprIf"],["impl Eq for ExprIndex"],["impl Eq for ExprLet"],["impl Eq for ExprLit"],["impl Eq for ExprLoop"],["impl Eq for ExprMacro"],["impl Eq for ExprMatch"],["impl Eq for ExprMethodCall"],["impl Eq for ExprParen"],["impl Eq for ExprPath"],["impl Eq for ExprRange"],["impl Eq for ExprReference"],["impl Eq for ExprRepeat"],["impl Eq for ExprReturn"],["impl Eq for ExprStruct"],["impl Eq for ExprTry"],["impl Eq for ExprTryBlock"],["impl Eq for ExprTuple"],["impl Eq for ExprType"],["impl Eq for ExprUnary"],["impl Eq for ExprUnsafe"],["impl Eq for ExprWhile"],["impl Eq for ExprYield"],["impl Eq for Field"],["impl Eq for FieldPat"],["impl Eq for FieldValue"],["impl Eq for Fields"],["impl Eq for FieldsNamed"],["impl Eq for FieldsUnnamed"],["impl Eq for File"],["impl Eq for FnArg"],["impl Eq for ForeignItem"],["impl Eq for ForeignItemFn"],["impl Eq for ForeignItemMacro"],["impl Eq for ForeignItemStatic"],["impl Eq for ForeignItemType"],["impl Eq for GenericArgument"],["impl Eq for GenericMethodArgument"],["impl Eq for GenericParam"],["impl Eq for Generics"],["impl Eq for ImplItem"],["impl Eq for ImplItemConst"],["impl Eq for ImplItemMacro"],["impl Eq for ImplItemMethod"],["impl Eq for ImplItemType"],["impl Eq for Item"],["impl Eq for ItemConst"],["impl Eq for ItemEnum"],["impl Eq for ItemExternCrate"],["impl Eq for ItemFn"],["impl Eq for ItemForeignMod"],["impl Eq for ItemImpl"],["impl Eq for ItemMacro"],["impl Eq for ItemMacro2"],["impl Eq for ItemMod"],["impl Eq for ItemStatic"],["impl Eq for ItemStruct"],["impl Eq for ItemTrait"],["impl Eq for ItemTraitAlias"],["impl Eq for ItemType"],["impl Eq for ItemUnion"],["impl Eq for ItemUse"],["impl Eq for Label"],["impl Eq for LifetimeDef"],["impl Eq for Lit"],["impl Eq for LitBool"],["impl Eq for LitByte"],["impl Eq for LitByteStr"],["impl Eq for LitChar"],["impl Eq for LitFloat"],["impl Eq for LitInt"],["impl Eq for LitStr"],["impl Eq for Local"],["impl Eq for Macro"],["impl Eq for MacroDelimiter"],["impl Eq for Meta"],["impl Eq for MetaList"],["impl Eq for MetaNameValue"],["impl Eq for MethodTurbofish"],["impl Eq for NestedMeta"],["impl Eq for ParenthesizedGenericArguments"],["impl Eq for Pat"],["impl Eq for PatBox"],["impl Eq for PatIdent"],["impl Eq for PatLit"],["impl Eq for PatMacro"],["impl Eq for PatOr"],["impl Eq for PatPath"],["impl Eq for PatRange"],["impl Eq for PatReference"],["impl Eq for PatRest"],["impl Eq for PatSlice"],["impl Eq for PatStruct"],["impl Eq for PatTuple"],["impl Eq for PatTupleStruct"],["impl Eq for PatType"],["impl Eq for PatWild"],["impl Eq for Path"],["impl Eq for PathArguments"],["impl Eq for PathSegment"],["impl Eq for PredicateEq"],["impl Eq for PredicateLifetime"],["impl Eq for PredicateType"],["impl Eq for QSelf"],["impl Eq for RangeLimits"],["impl Eq for Receiver"],["impl Eq for ReturnType"],["impl Eq for Signature"],["impl Eq for Stmt"],["impl Eq for TraitBound"],["impl Eq for TraitBoundModifier"],["impl Eq for TraitItem"],["impl Eq for TraitItemConst"],["impl Eq for TraitItemMacro"],["impl Eq for TraitItemMethod"],["impl Eq for TraitItemType"],["impl Eq for Type"],["impl Eq for TypeArray"],["impl Eq for TypeBareFn"],["impl Eq for TypeGroup"],["impl Eq for TypeImplTrait"],["impl Eq for TypeInfer"],["impl Eq for TypeMacro"],["impl Eq for TypeNever"],["impl Eq for TypeParam"],["impl Eq for TypeParamBound"],["impl Eq for TypeParen"],["impl Eq for TypePath"],["impl Eq for TypePtr"],["impl Eq for TypeReference"],["impl Eq for TypeSlice"],["impl Eq for TypeTraitObject"],["impl Eq for TypeTuple"],["impl Eq for UnOp"],["impl Eq for UseGlob"],["impl Eq for UseGroup"],["impl Eq for UseName"],["impl Eq for UsePath"],["impl Eq for UseRename"],["impl Eq for UseTree"],["impl Eq for Variadic"],["impl Eq for Variant"],["impl Eq for VisCrate"],["impl Eq for VisPublic"],["impl Eq for VisRestricted"],["impl Eq for Visibility"],["impl Eq for WhereClause"],["impl Eq for WherePredicate"],["impl Eq for Nothing"]], +"tokio":[["impl Eq for RuntimeFlavor"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl<T: Eq> Eq for TrySendError<T>"],["impl Eq for TryRecvError"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl Eq for TryAcquireError"],["impl<T: Eq> Eq for OnceCell<T>"],["impl<T: Eq> Eq for SetError<T>"]], +"typenum":[["impl Eq for B0"],["impl Eq for B1"],["impl<U: Eq + Unsigned + NonZero> Eq for PInt<U>"],["impl<U: Eq + Unsigned + NonZero> Eq for NInt<U>"],["impl Eq for Z0"],["impl Eq for UTerm"],["impl<U: Eq, B: Eq> Eq for UInt<U, B>"],["impl Eq for ATerm"],["impl<V: Eq, A: Eq> Eq for TArr<V, A>"],["impl Eq for Greater"],["impl Eq for Less"],["impl Eq for Equal"]], +"uint":[["impl Eq for FromStrRadixErrKind"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/cmp/trait.PartialEq.js b/docs/implementors/core/cmp/trait.PartialEq.js new file mode 100644 index 000000000000..b1fe8eb3036f --- /dev/null +++ b/docs/implementors/core/cmp/trait.PartialEq.js @@ -0,0 +1,39 @@ +(function() {var implementors = { +"aho_corasick":[["impl PartialEq<MatchKind> for MatchKind"],["impl PartialEq<MatchKind> for MatchKind"],["impl PartialEq<Match> for Match"]], +"block_buffer":[["impl PartialEq<Error> for Error"]], +"byteorder":[["impl PartialEq<BigEndian> for BigEndian"],["impl PartialEq<LittleEndian> for LittleEndian"]], +"bytes":[["impl PartialEq<Bytes> for Bytes"],["impl PartialEq<[u8]> for Bytes"],["impl PartialEq<Bytes> for [u8]"],["impl PartialEq<str> for Bytes"],["impl PartialEq<Bytes> for str"],["impl PartialEq<Vec<u8, Global>> for Bytes"],["impl PartialEq<Bytes> for Vec<u8>"],["impl PartialEq<String> for Bytes"],["impl PartialEq<Bytes> for String"],["impl PartialEq<Bytes> for &[u8]"],["impl PartialEq<Bytes> for &str"],["impl<'a, T: ?Sized> PartialEq<&'a T> for Byteswhere
    Bytes: PartialEq<T>,
"],["impl PartialEq<BytesMut> for BytesMut"],["impl PartialEq<[u8]> for BytesMut"],["impl PartialEq<BytesMut> for [u8]"],["impl PartialEq<str> for BytesMut"],["impl PartialEq<BytesMut> for str"],["impl PartialEq<Vec<u8, Global>> for BytesMut"],["impl PartialEq<BytesMut> for Vec<u8>"],["impl PartialEq<String> for BytesMut"],["impl PartialEq<BytesMut> for String"],["impl<'a, T: ?Sized> PartialEq<&'a T> for BytesMutwhere
    BytesMut: PartialEq<T>,
"],["impl PartialEq<BytesMut> for &[u8]"],["impl PartialEq<BytesMut> for &str"],["impl PartialEq<BytesMut> for Bytes"],["impl PartialEq<Bytes> for BytesMut"]], +"crossbeam_channel":[["impl<T: PartialEq> PartialEq<SendError<T>> for SendError<T>"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl<T: PartialEq> PartialEq<SendTimeoutError<T>> for SendTimeoutError<T>"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<RecvTimeoutError> for RecvTimeoutError"],["impl PartialEq<TrySelectError> for TrySelectError"],["impl PartialEq<SelectTimeoutError> for SelectTimeoutError"],["impl PartialEq<TryReadyError> for TryReadyError"],["impl PartialEq<ReadyTimeoutError> for ReadyTimeoutError"]], +"crossbeam_utils":[["impl<T: PartialEq> PartialEq<CachePadded<T>> for CachePadded<T>"]], +"crypto_common":[["impl PartialEq<InvalidLength> for InvalidLength"]], +"digest":[["impl PartialEq<InvalidBufferSize> for InvalidBufferSize"]], +"firewood":[["impl PartialEq<Hash> for Hash"],["impl PartialEq<PartialPath> for PartialPath"],["impl PartialEq<Node> for Node"]], +"futures_channel":[["impl PartialEq<SendError> for SendError"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl PartialEq<Canceled> for Canceled"]], +"futures_util":[["impl<T: PartialEq, E: PartialEq> PartialEq<TryChunksError<T, E>> for TryChunksError<T, E>"],["impl PartialEq<PollNext> for PollNext"],["impl<T: PartialEq> PartialEq<AllowStdIo<T>> for AllowStdIo<T>"],["impl PartialEq<Aborted> for Aborted"]], +"generic_array":[["impl<T: PartialEq, N> PartialEq<GenericArray<T, N>> for GenericArray<T, N>where
    N: ArrayLength<T>,
"]], +"getrandom":[["impl PartialEq<Error> for Error"]], +"growthring":[["impl PartialEq<WALRingId> for WALRingId"]], +"hashbrown":[["impl<K, V, S, A> PartialEq<HashMap<K, V, S, A>> for HashMap<K, V, S, A>where
    K: Eq + Hash,
    V: PartialEq,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T, S, A> PartialEq<HashSet<T, S, A>> for HashSet<T, S, A>where
    T: Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl PartialEq<TryReserveError> for TryReserveError"]], +"hex":[["impl PartialEq<FromHexError> for FromHexError"]], +"libc":[["impl PartialEq<group> for group"],["impl PartialEq<utimbuf> for utimbuf"],["impl PartialEq<timeval> for timeval"],["impl PartialEq<timespec> for timespec"],["impl PartialEq<rlimit> for rlimit"],["impl PartialEq<rusage> for rusage"],["impl PartialEq<ipv6_mreq> for ipv6_mreq"],["impl PartialEq<hostent> for hostent"],["impl PartialEq<iovec> for iovec"],["impl PartialEq<pollfd> for pollfd"],["impl PartialEq<winsize> for winsize"],["impl PartialEq<linger> for linger"],["impl PartialEq<sigval> for sigval"],["impl PartialEq<itimerval> for itimerval"],["impl PartialEq<tms> for tms"],["impl PartialEq<servent> for servent"],["impl PartialEq<protoent> for protoent"],["impl PartialEq<in_addr> for in_addr"],["impl PartialEq<ip_mreq> for ip_mreq"],["impl PartialEq<ip_mreqn> for ip_mreqn"],["impl PartialEq<ip_mreq_source> for ip_mreq_source"],["impl PartialEq<sockaddr> for sockaddr"],["impl PartialEq<sockaddr_in> for sockaddr_in"],["impl PartialEq<sockaddr_in6> for sockaddr_in6"],["impl PartialEq<addrinfo> for addrinfo"],["impl PartialEq<sockaddr_ll> for sockaddr_ll"],["impl PartialEq<fd_set> for fd_set"],["impl PartialEq<tm> for tm"],["impl PartialEq<sched_param> for sched_param"],["impl PartialEq<Dl_info> for Dl_info"],["impl PartialEq<lconv> for lconv"],["impl PartialEq<in_pktinfo> for in_pktinfo"],["impl PartialEq<ifaddrs> for ifaddrs"],["impl PartialEq<in6_rtmsg> for in6_rtmsg"],["impl PartialEq<arpreq> for arpreq"],["impl PartialEq<arpreq_old> for arpreq_old"],["impl PartialEq<arphdr> for arphdr"],["impl PartialEq<mmsghdr> for mmsghdr"],["impl PartialEq<epoll_event> for epoll_event"],["impl PartialEq<sockaddr_un> for sockaddr_un"],["impl PartialEq<sockaddr_storage> for sockaddr_storage"],["impl PartialEq<utsname> for utsname"],["impl PartialEq<sigevent> for sigevent"],["impl PartialEq<rlimit64> for rlimit64"],["impl PartialEq<glob_t> for glob_t"],["impl PartialEq<passwd> for passwd"],["impl PartialEq<spwd> for spwd"],["impl PartialEq<dqblk> for dqblk"],["impl PartialEq<signalfd_siginfo> for signalfd_siginfo"],["impl PartialEq<itimerspec> for itimerspec"],["impl PartialEq<fsid_t> for fsid_t"],["impl PartialEq<packet_mreq> for packet_mreq"],["impl PartialEq<cpu_set_t> for cpu_set_t"],["impl PartialEq<if_nameindex> for if_nameindex"],["impl PartialEq<msginfo> for msginfo"],["impl PartialEq<sembuf> for sembuf"],["impl PartialEq<input_event> for input_event"],["impl PartialEq<input_id> for input_id"],["impl PartialEq<input_absinfo> for input_absinfo"],["impl PartialEq<input_keymap_entry> for input_keymap_entry"],["impl PartialEq<input_mask> for input_mask"],["impl PartialEq<ff_replay> for ff_replay"],["impl PartialEq<ff_trigger> for ff_trigger"],["impl PartialEq<ff_envelope> for ff_envelope"],["impl PartialEq<ff_constant_effect> for ff_constant_effect"],["impl PartialEq<ff_ramp_effect> for ff_ramp_effect"],["impl PartialEq<ff_condition_effect> for ff_condition_effect"],["impl PartialEq<ff_periodic_effect> for ff_periodic_effect"],["impl PartialEq<ff_rumble_effect> for ff_rumble_effect"],["impl PartialEq<ff_effect> for ff_effect"],["impl PartialEq<uinput_ff_upload> for uinput_ff_upload"],["impl PartialEq<uinput_ff_erase> for uinput_ff_erase"],["impl PartialEq<uinput_abs_setup> for uinput_abs_setup"],["impl PartialEq<dl_phdr_info> for dl_phdr_info"],["impl PartialEq<Elf32_Ehdr> for Elf32_Ehdr"],["impl PartialEq<Elf64_Ehdr> for Elf64_Ehdr"],["impl PartialEq<Elf32_Sym> for Elf32_Sym"],["impl PartialEq<Elf64_Sym> for Elf64_Sym"],["impl PartialEq<Elf32_Phdr> for Elf32_Phdr"],["impl PartialEq<Elf64_Phdr> for Elf64_Phdr"],["impl PartialEq<Elf32_Shdr> for Elf32_Shdr"],["impl PartialEq<Elf64_Shdr> for Elf64_Shdr"],["impl PartialEq<ucred> for ucred"],["impl PartialEq<mntent> for mntent"],["impl PartialEq<posix_spawn_file_actions_t> for posix_spawn_file_actions_t"],["impl PartialEq<posix_spawnattr_t> for posix_spawnattr_t"],["impl PartialEq<genlmsghdr> for genlmsghdr"],["impl PartialEq<in6_pktinfo> for in6_pktinfo"],["impl PartialEq<arpd_request> for arpd_request"],["impl PartialEq<inotify_event> for inotify_event"],["impl PartialEq<fanotify_response> for fanotify_response"],["impl PartialEq<sockaddr_vm> for sockaddr_vm"],["impl PartialEq<regmatch_t> for regmatch_t"],["impl PartialEq<sock_extended_err> for sock_extended_err"],["impl PartialEq<__c_anonymous_sockaddr_can_tp> for __c_anonymous_sockaddr_can_tp"],["impl PartialEq<__c_anonymous_sockaddr_can_j1939> for __c_anonymous_sockaddr_can_j1939"],["impl PartialEq<can_filter> for can_filter"],["impl PartialEq<j1939_filter> for j1939_filter"],["impl PartialEq<sock_filter> for sock_filter"],["impl PartialEq<sock_fprog> for sock_fprog"],["impl PartialEq<seccomp_data> for seccomp_data"],["impl PartialEq<nlmsghdr> for nlmsghdr"],["impl PartialEq<nlmsgerr> for nlmsgerr"],["impl PartialEq<nlattr> for nlattr"],["impl PartialEq<file_clone_range> for file_clone_range"],["impl PartialEq<__c_anonymous_ifru_map> for __c_anonymous_ifru_map"],["impl PartialEq<in6_ifreq> for in6_ifreq"],["impl PartialEq<sockaddr_nl> for sockaddr_nl"],["impl PartialEq<dirent> for dirent"],["impl PartialEq<dirent64> for dirent64"],["impl PartialEq<pthread_cond_t> for pthread_cond_t"],["impl PartialEq<pthread_mutex_t> for pthread_mutex_t"],["impl PartialEq<pthread_rwlock_t> for pthread_rwlock_t"],["impl PartialEq<sockaddr_alg> for sockaddr_alg"],["impl PartialEq<uinput_setup> for uinput_setup"],["impl PartialEq<uinput_user_dev> for uinput_user_dev"],["impl PartialEq<af_alg_iv> for af_alg_iv"],["impl PartialEq<mq_attr> for mq_attr"],["impl PartialEq<statx> for statx"],["impl PartialEq<statx_timestamp> for statx_timestamp"],["impl PartialEq<aiocb> for aiocb"],["impl PartialEq<__exit_status> for __exit_status"],["impl PartialEq<__timeval> for __timeval"],["impl PartialEq<glob64_t> for glob64_t"],["impl PartialEq<msghdr> for msghdr"],["impl PartialEq<cmsghdr> for cmsghdr"],["impl PartialEq<termios> for termios"],["impl PartialEq<mallinfo> for mallinfo"],["impl PartialEq<mallinfo2> for mallinfo2"],["impl PartialEq<nl_pktinfo> for nl_pktinfo"],["impl PartialEq<nl_mmap_req> for nl_mmap_req"],["impl PartialEq<nl_mmap_hdr> for nl_mmap_hdr"],["impl PartialEq<rtentry> for rtentry"],["impl PartialEq<timex> for timex"],["impl PartialEq<ntptimeval> for ntptimeval"],["impl PartialEq<regex_t> for regex_t"],["impl PartialEq<Elf64_Chdr> for Elf64_Chdr"],["impl PartialEq<Elf32_Chdr> for Elf32_Chdr"],["impl PartialEq<seminfo> for seminfo"],["impl PartialEq<ptrace_peeksiginfo_args> for ptrace_peeksiginfo_args"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_entry> for __c_anonymous_ptrace_syscall_info_entry"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_exit> for __c_anonymous_ptrace_syscall_info_exit"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_seccomp> for __c_anonymous_ptrace_syscall_info_seccomp"],["impl PartialEq<ptrace_syscall_info> for ptrace_syscall_info"],["impl PartialEq<utmpx> for utmpx"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_data> for __c_anonymous_ptrace_syscall_info_data"],["impl PartialEq<sigset_t> for sigset_t"],["impl PartialEq<sysinfo> for sysinfo"],["impl PartialEq<msqid_ds> for msqid_ds"],["impl PartialEq<semid_ds> for semid_ds"],["impl PartialEq<sigaction> for sigaction"],["impl PartialEq<statfs> for statfs"],["impl PartialEq<flock> for flock"],["impl PartialEq<flock64> for flock64"],["impl PartialEq<siginfo_t> for siginfo_t"],["impl PartialEq<stack_t> for stack_t"],["impl PartialEq<stat> for stat"],["impl PartialEq<stat64> for stat64"],["impl PartialEq<statfs64> for statfs64"],["impl PartialEq<statvfs64> for statvfs64"],["impl PartialEq<pthread_attr_t> for pthread_attr_t"],["impl PartialEq<_libc_fpxreg> for _libc_fpxreg"],["impl PartialEq<_libc_xmmreg> for _libc_xmmreg"],["impl PartialEq<_libc_fpstate> for _libc_fpstate"],["impl PartialEq<user_regs_struct> for user_regs_struct"],["impl PartialEq<user> for user"],["impl PartialEq<mcontext_t> for mcontext_t"],["impl PartialEq<ipc_perm> for ipc_perm"],["impl PartialEq<shmid_ds> for shmid_ds"],["impl PartialEq<seccomp_notif_sizes> for seccomp_notif_sizes"],["impl PartialEq<ptrace_rseq_configuration> for ptrace_rseq_configuration"],["impl PartialEq<user_fpregs_struct> for user_fpregs_struct"],["impl PartialEq<ucontext_t> for ucontext_t"],["impl PartialEq<statvfs> for statvfs"],["impl PartialEq<clone_args> for clone_args"],["impl PartialEq<sem_t> for sem_t"],["impl PartialEq<termios2> for termios2"],["impl PartialEq<pthread_mutexattr_t> for pthread_mutexattr_t"],["impl PartialEq<pthread_rwlockattr_t> for pthread_rwlockattr_t"],["impl PartialEq<pthread_condattr_t> for pthread_condattr_t"],["impl PartialEq<fanotify_event_metadata> for fanotify_event_metadata"],["impl PartialEq<open_how> for open_how"],["impl PartialEq<in6_addr> for in6_addr"]], +"nix":[["impl PartialEq<Dir> for Dir"],["impl<'d> PartialEq<Iter<'d>> for Iter<'d>"],["impl PartialEq<OwningIter> for OwningIter"],["impl PartialEq<Entry> for Entry"],["impl PartialEq<Type> for Type"],["impl PartialEq<Errno> for Errno"],["impl PartialEq<AtFlags> for AtFlags"],["impl PartialEq<OFlag> for OFlag"],["impl PartialEq<RenameFlags> for RenameFlags"],["impl PartialEq<SealFlag> for SealFlag"],["impl PartialEq<FdFlag> for FdFlag"],["impl<'a> PartialEq<FcntlArg<'a>> for FcntlArg<'a>"],["impl PartialEq<FlockArg> for FlockArg"],["impl PartialEq<SpliceFFlags> for SpliceFFlags"],["impl PartialEq<FallocateFlags> for FallocateFlags"],["impl PartialEq<PosixFadviseAdvice> for PosixFadviseAdvice"],["impl PartialEq<InterfaceAddress> for InterfaceAddress"],["impl PartialEq<InterfaceAddressIterator> for InterfaceAddressIterator"],["impl PartialEq<InterfaceFlags> for InterfaceFlags"],["impl PartialEq<ModuleInitFlags> for ModuleInitFlags"],["impl PartialEq<DeleteModuleFlags> for DeleteModuleFlags"],["impl PartialEq<MsFlags> for MsFlags"],["impl PartialEq<MntFlags> for MntFlags"],["impl PartialEq<MQ_OFlag> for MQ_OFlag"],["impl PartialEq<MqAttr> for MqAttr"],["impl PartialEq<PollFd> for PollFd"],["impl PartialEq<PollFlags> for PollFlags"],["impl PartialEq<OpenptyResult> for OpenptyResult"],["impl PartialEq<PtyMaster> for PtyMaster"],["impl PartialEq<CloneFlags> for CloneFlags"],["impl PartialEq<CpuSet> for CpuSet"],["impl PartialEq<AioFsyncMode> for AioFsyncMode"],["impl PartialEq<LioMode> for LioMode"],["impl PartialEq<AioCancelStat> for AioCancelStat"],["impl PartialEq<EpollFlags> for EpollFlags"],["impl PartialEq<EpollOp> for EpollOp"],["impl PartialEq<EpollCreateFlags> for EpollCreateFlags"],["impl PartialEq<EpollEvent> for EpollEvent"],["impl PartialEq<EfdFlags> for EfdFlags"],["impl PartialEq<MemFdCreateFlag> for MemFdCreateFlag"],["impl PartialEq<ProtFlags> for ProtFlags"],["impl PartialEq<MapFlags> for MapFlags"],["impl PartialEq<MRemapFlags> for MRemapFlags"],["impl PartialEq<MmapAdvise> for MmapAdvise"],["impl PartialEq<MsFlags> for MsFlags"],["impl PartialEq<MlockAllFlags> for MlockAllFlags"],["impl PartialEq<Persona> for Persona"],["impl PartialEq<Request> for Request"],["impl PartialEq<Event> for Event"],["impl PartialEq<Options> for Options"],["impl PartialEq<QuotaType> for QuotaType"],["impl PartialEq<QuotaFmt> for QuotaFmt"],["impl PartialEq<QuotaValidFlags> for QuotaValidFlags"],["impl PartialEq<Dqblk> for Dqblk"],["impl PartialEq<RebootMode> for RebootMode"],["impl PartialEq<Resource> for Resource"],["impl PartialEq<UsageWho> for UsageWho"],["impl PartialEq<Usage> for Usage"],["impl PartialEq<FdSet> for FdSet"],["impl PartialEq<Signal> for Signal"],["impl PartialEq<SignalIterator> for SignalIterator"],["impl PartialEq<SaFlags> for SaFlags"],["impl PartialEq<SigmaskHow> for SigmaskHow"],["impl PartialEq<SigSet> for SigSet"],["impl PartialEq<SigHandler> for SigHandler"],["impl PartialEq<SigAction> for SigAction"],["impl PartialEq<SigevNotify> for SigevNotify"],["impl PartialEq<SigEvent> for SigEvent"],["impl PartialEq<SfdFlags> for SfdFlags"],["impl PartialEq<SignalFd> for SignalFd"],["impl PartialEq<AddressFamily> for AddressFamily"],["impl PartialEq<InetAddr> for InetAddr"],["impl PartialEq<IpAddr> for IpAddr"],["impl PartialEq<Ipv4Addr> for Ipv4Addr"],["impl PartialEq<Ipv6Addr> for Ipv6Addr"],["impl PartialEq<UnixAddr> for UnixAddr"],["impl PartialEq<SockaddrIn> for SockaddrIn"],["impl PartialEq<SockaddrIn6> for SockaddrIn6"],["impl PartialEq<SockaddrStorage> for SockaddrStorage"],["impl PartialEq<SockAddr> for SockAddr"],["impl PartialEq<NetlinkAddr> for NetlinkAddr"],["impl PartialEq<AlgAddr> for AlgAddr"],["impl PartialEq<LinkAddr> for LinkAddr"],["impl PartialEq<VsockAddr> for VsockAddr"],["impl PartialEq<ReuseAddr> for ReuseAddr"],["impl PartialEq<ReusePort> for ReusePort"],["impl PartialEq<TcpNoDelay> for TcpNoDelay"],["impl PartialEq<Linger> for Linger"],["impl PartialEq<IpAddMembership> for IpAddMembership"],["impl PartialEq<IpDropMembership> for IpDropMembership"],["impl PartialEq<Ipv6AddMembership> for Ipv6AddMembership"],["impl PartialEq<Ipv6DropMembership> for Ipv6DropMembership"],["impl PartialEq<IpMulticastTtl> for IpMulticastTtl"],["impl PartialEq<IpMulticastLoop> for IpMulticastLoop"],["impl PartialEq<Priority> for Priority"],["impl PartialEq<IpTos> for IpTos"],["impl PartialEq<Ipv6TClass> for Ipv6TClass"],["impl PartialEq<IpFreebind> for IpFreebind"],["impl PartialEq<ReceiveTimeout> for ReceiveTimeout"],["impl PartialEq<SendTimeout> for SendTimeout"],["impl PartialEq<Broadcast> for Broadcast"],["impl PartialEq<OobInline> for OobInline"],["impl PartialEq<SocketError> for SocketError"],["impl PartialEq<DontRoute> for DontRoute"],["impl PartialEq<KeepAlive> for KeepAlive"],["impl PartialEq<PeerCredentials> for PeerCredentials"],["impl PartialEq<TcpKeepIdle> for TcpKeepIdle"],["impl PartialEq<TcpMaxSeg> for TcpMaxSeg"],["impl PartialEq<TcpKeepCount> for TcpKeepCount"],["impl PartialEq<TcpRepair> for TcpRepair"],["impl PartialEq<TcpKeepInterval> for TcpKeepInterval"],["impl PartialEq<TcpUserTimeout> for TcpUserTimeout"],["impl PartialEq<RcvBuf> for RcvBuf"],["impl PartialEq<SndBuf> for SndBuf"],["impl PartialEq<RcvBufForce> for RcvBufForce"],["impl PartialEq<SndBufForce> for SndBufForce"],["impl PartialEq<SockType> for SockType"],["impl PartialEq<AcceptConn> for AcceptConn"],["impl PartialEq<BindToDevice> for BindToDevice"],["impl PartialEq<OriginalDst> for OriginalDst"],["impl PartialEq<Ip6tOriginalDst> for Ip6tOriginalDst"],["impl PartialEq<Timestamping> for Timestamping"],["impl PartialEq<ReceiveTimestamp> for ReceiveTimestamp"],["impl PartialEq<ReceiveTimestampns> for ReceiveTimestampns"],["impl PartialEq<IpTransparent> for IpTransparent"],["impl PartialEq<Mark> for Mark"],["impl PartialEq<PassCred> for PassCred"],["impl PartialEq<TcpCongestion> for TcpCongestion"],["impl PartialEq<Ipv4PacketInfo> for Ipv4PacketInfo"],["impl PartialEq<Ipv6RecvPacketInfo> for Ipv6RecvPacketInfo"],["impl PartialEq<Ipv4OrigDstAddr> for Ipv4OrigDstAddr"],["impl PartialEq<UdpGsoSegment> for UdpGsoSegment"],["impl PartialEq<UdpGroSegment> for UdpGroSegment"],["impl PartialEq<TxTime> for TxTime"],["impl PartialEq<RxqOvfl> for RxqOvfl"],["impl PartialEq<Ipv6V6Only> for Ipv6V6Only"],["impl PartialEq<Ipv4RecvErr> for Ipv4RecvErr"],["impl PartialEq<Ipv6RecvErr> for Ipv6RecvErr"],["impl PartialEq<IpMtu> for IpMtu"],["impl PartialEq<Ipv4Ttl> for Ipv4Ttl"],["impl PartialEq<Ipv6Ttl> for Ipv6Ttl"],["impl PartialEq<Ipv6OrigDstAddr> for Ipv6OrigDstAddr"],["impl PartialEq<Ipv6DontFrag> for Ipv6DontFrag"],["impl PartialEq<SockType> for SockType"],["impl PartialEq<SockProtocol> for SockProtocol"],["impl PartialEq<TimestampingFlag> for TimestampingFlag"],["impl PartialEq<SockFlag> for SockFlag"],["impl PartialEq<MsgFlags> for MsgFlags"],["impl PartialEq<UnixCredentials> for UnixCredentials"],["impl PartialEq<IpMembershipRequest> for IpMembershipRequest"],["impl PartialEq<Ipv6MembershipRequest> for Ipv6MembershipRequest"],["impl<'a, 's, S: PartialEq> PartialEq<RecvMsg<'a, 's, S>> for RecvMsg<'a, 's, S>"],["impl<'a> PartialEq<CmsgIterator<'a>> for CmsgIterator<'a>"],["impl PartialEq<ControlMessageOwned> for ControlMessageOwned"],["impl PartialEq<Timestamps> for Timestamps"],["impl<'a> PartialEq<ControlMessage<'a>> for ControlMessage<'a>"],["impl PartialEq<Shutdown> for Shutdown"],["impl PartialEq<SFlag> for SFlag"],["impl PartialEq<Mode> for Mode"],["impl PartialEq<FsType> for FsType"],["impl PartialEq<FsFlags> for FsFlags"],["impl PartialEq<Statvfs> for Statvfs"],["impl PartialEq<SysInfo> for SysInfo"],["impl PartialEq<Termios> for Termios"],["impl PartialEq<BaudRate> for BaudRate"],["impl PartialEq<SetArg> for SetArg"],["impl PartialEq<FlushArg> for FlushArg"],["impl PartialEq<FlowArg> for FlowArg"],["impl PartialEq<SpecialCharacterIndices> for SpecialCharacterIndices"],["impl PartialEq<InputFlags> for InputFlags"],["impl PartialEq<OutputFlags> for OutputFlags"],["impl PartialEq<ControlFlags> for ControlFlags"],["impl PartialEq<LocalFlags> for LocalFlags"],["impl PartialEq<Expiration> for Expiration"],["impl PartialEq<TimerSetTimeFlags> for TimerSetTimeFlags"],["impl PartialEq<TimeSpec> for TimeSpec"],["impl PartialEq<TimeVal> for TimeVal"],["impl PartialEq<RemoteIoVec> for RemoteIoVec"],["impl<T: PartialEq> PartialEq<IoVec<T>> for IoVec<T>"],["impl PartialEq<UtsName> for UtsName"],["impl PartialEq<WaitPidFlag> for WaitPidFlag"],["impl PartialEq<WaitStatus> for WaitStatus"],["impl PartialEq<Id> for Id"],["impl PartialEq<AddWatchFlags> for AddWatchFlags"],["impl PartialEq<InitFlags> for InitFlags"],["impl PartialEq<WatchDescriptor> for WatchDescriptor"],["impl PartialEq<ClockId> for ClockId"],["impl PartialEq<TimerFlags> for TimerFlags"],["impl PartialEq<ClockId> for ClockId"],["impl PartialEq<UContext> for UContext"],["impl PartialEq<Uid> for Uid"],["impl PartialEq<Gid> for Gid"],["impl PartialEq<Pid> for Pid"],["impl PartialEq<PathconfVar> for PathconfVar"],["impl PartialEq<SysconfVar> for SysconfVar"],["impl PartialEq<ResUid> for ResUid"],["impl PartialEq<ResGid> for ResGid"],["impl PartialEq<AccessFlags> for AccessFlags"],["impl PartialEq<User> for User"],["impl PartialEq<Group> for Group"]], +"once_cell":[["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"],["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"]], +"parking_lot":[["impl PartialEq<WaitTimeoutResult> for WaitTimeoutResult"],["impl PartialEq<OnceState> for OnceState"]], +"parking_lot_core":[["impl PartialEq<ParkResult> for ParkResult"],["impl PartialEq<UnparkResult> for UnparkResult"],["impl PartialEq<RequeueOp> for RequeueOp"],["impl PartialEq<FilterOp> for FilterOp"],["impl PartialEq<UnparkToken> for UnparkToken"],["impl PartialEq<ParkToken> for ParkToken"]], +"ppv_lite86":[["impl PartialEq<vec128_storage> for vec128_storage"],["impl PartialEq<vec256_storage> for vec256_storage"],["impl PartialEq<vec512_storage> for vec512_storage"]], +"primitive_types":[["impl PartialEq<Error> for Error"],["impl PartialEq<U128> for U128"],["impl PartialEq<U256> for U256"],["impl PartialEq<U512> for U512"],["impl PartialEq<H128> for H128"],["impl PartialEq<H160> for H160"],["impl PartialEq<H256> for H256"],["impl PartialEq<H384> for H384"],["impl PartialEq<H512> for H512"],["impl PartialEq<H768> for H768"]], +"proc_macro2":[["impl PartialEq<Delimiter> for Delimiter"],["impl PartialEq<Spacing> for Spacing"],["impl PartialEq<Ident> for Ident"],["impl<T> PartialEq<T> for Identwhere
    T: ?Sized + AsRef<str>,
"]], +"rand":[["impl PartialEq<Bernoulli> for Bernoulli"],["impl PartialEq<BernoulliError> for BernoulliError"],["impl<X: PartialEq + SampleUniform + PartialOrd> PartialEq<WeightedIndex<X>> for WeightedIndex<X>where
    X::Sampler: PartialEq,
"],["impl PartialEq<WeightedError> for WeightedError"],["impl<X: PartialEq + SampleUniform> PartialEq<Uniform<X>> for Uniform<X>where
    X::Sampler: PartialEq,
"],["impl<X: PartialEq> PartialEq<UniformInt<X>> for UniformInt<X>"],["impl<X: PartialEq> PartialEq<UniformFloat<X>> for UniformFloat<X>"],["impl PartialEq<StepRng> for StepRng"],["impl PartialEq<IndexVec> for IndexVec"]], +"rand_chacha":[["impl PartialEq<ChaCha20Core> for ChaCha20Core"],["impl PartialEq<ChaCha20Rng> for ChaCha20Rng"],["impl PartialEq<ChaCha12Core> for ChaCha12Core"],["impl PartialEq<ChaCha12Rng> for ChaCha12Rng"],["impl PartialEq<ChaCha8Core> for ChaCha8Core"],["impl PartialEq<ChaCha8Rng> for ChaCha8Rng"]], +"regex":[["impl PartialEq<Error> for Error"],["impl<'t> PartialEq<Match<'t>> for Match<'t>"],["impl<'t> PartialEq<Match<'t>> for Match<'t>"]], +"regex_syntax":[["impl PartialEq<Error> for Error"],["impl PartialEq<ErrorKind> for ErrorKind"],["impl PartialEq<Span> for Span"],["impl PartialEq<Position> for Position"],["impl PartialEq<WithComments> for WithComments"],["impl PartialEq<Comment> for Comment"],["impl PartialEq<Ast> for Ast"],["impl PartialEq<Alternation> for Alternation"],["impl PartialEq<Concat> for Concat"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<LiteralKind> for LiteralKind"],["impl PartialEq<SpecialLiteralKind> for SpecialLiteralKind"],["impl PartialEq<HexLiteralKind> for HexLiteralKind"],["impl PartialEq<Class> for Class"],["impl PartialEq<ClassPerl> for ClassPerl"],["impl PartialEq<ClassPerlKind> for ClassPerlKind"],["impl PartialEq<ClassAscii> for ClassAscii"],["impl PartialEq<ClassAsciiKind> for ClassAsciiKind"],["impl PartialEq<ClassUnicode> for ClassUnicode"],["impl PartialEq<ClassUnicodeKind> for ClassUnicodeKind"],["impl PartialEq<ClassUnicodeOpKind> for ClassUnicodeOpKind"],["impl PartialEq<ClassBracketed> for ClassBracketed"],["impl PartialEq<ClassSet> for ClassSet"],["impl PartialEq<ClassSetItem> for ClassSetItem"],["impl PartialEq<ClassSetRange> for ClassSetRange"],["impl PartialEq<ClassSetUnion> for ClassSetUnion"],["impl PartialEq<ClassSetBinaryOp> for ClassSetBinaryOp"],["impl PartialEq<ClassSetBinaryOpKind> for ClassSetBinaryOpKind"],["impl PartialEq<Assertion> for Assertion"],["impl PartialEq<AssertionKind> for AssertionKind"],["impl PartialEq<Repetition> for Repetition"],["impl PartialEq<RepetitionOp> for RepetitionOp"],["impl PartialEq<RepetitionKind> for RepetitionKind"],["impl PartialEq<RepetitionRange> for RepetitionRange"],["impl PartialEq<Group> for Group"],["impl PartialEq<GroupKind> for GroupKind"],["impl PartialEq<CaptureName> for CaptureName"],["impl PartialEq<SetFlags> for SetFlags"],["impl PartialEq<Flags> for Flags"],["impl PartialEq<FlagsItem> for FlagsItem"],["impl PartialEq<FlagsItemKind> for FlagsItemKind"],["impl PartialEq<Flag> for Flag"],["impl PartialEq<Error> for Error"],["impl PartialEq<Literals> for Literals"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<Error> for Error"],["impl PartialEq<ErrorKind> for ErrorKind"],["impl PartialEq<Hir> for Hir"],["impl PartialEq<HirKind> for HirKind"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<Class> for Class"],["impl PartialEq<ClassUnicode> for ClassUnicode"],["impl PartialEq<ClassUnicodeRange> for ClassUnicodeRange"],["impl PartialEq<ClassBytes> for ClassBytes"],["impl PartialEq<ClassBytesRange> for ClassBytesRange"],["impl PartialEq<Anchor> for Anchor"],["impl PartialEq<WordBoundary> for WordBoundary"],["impl PartialEq<Group> for Group"],["impl PartialEq<GroupKind> for GroupKind"],["impl PartialEq<Repetition> for Repetition"],["impl PartialEq<RepetitionKind> for RepetitionKind"],["impl PartialEq<RepetitionRange> for RepetitionRange"],["impl PartialEq<Utf8Sequence> for Utf8Sequence"],["impl PartialEq<Utf8Range> for Utf8Range"]], +"rlp":[["impl PartialEq<DecoderError> for DecoderError"]], +"scan_fmt":[["impl PartialEq<ScanError> for ScanError"]], +"serde":[["impl PartialEq<Error> for Error"],["impl<'a> PartialEq<Unexpected<'a>> for Unexpected<'a>"]], +"shale":[["impl<T> PartialEq<ObjPtr<T>> for ObjPtr<T>"]], +"smallvec":[["impl<A: Array, B: Array> PartialEq<SmallVec<B>> for SmallVec<A>where
    A::Item: PartialEq<B::Item>,
"]], +"syn":[["impl PartialEq<Underscore> for Underscore"],["impl PartialEq<Abstract> for Abstract"],["impl PartialEq<As> for As"],["impl PartialEq<Async> for Async"],["impl PartialEq<Auto> for Auto"],["impl PartialEq<Await> for Await"],["impl PartialEq<Become> for Become"],["impl PartialEq<Box> for Box"],["impl PartialEq<Break> for Break"],["impl PartialEq<Const> for Const"],["impl PartialEq<Continue> for Continue"],["impl PartialEq<Crate> for Crate"],["impl PartialEq<Default> for Default"],["impl PartialEq<Do> for Do"],["impl PartialEq<Dyn> for Dyn"],["impl PartialEq<Else> for Else"],["impl PartialEq<Enum> for Enum"],["impl PartialEq<Extern> for Extern"],["impl PartialEq<Final> for Final"],["impl PartialEq<Fn> for Fn"],["impl PartialEq<For> for For"],["impl PartialEq<If> for If"],["impl PartialEq<Impl> for Impl"],["impl PartialEq<In> for In"],["impl PartialEq<Let> for Let"],["impl PartialEq<Loop> for Loop"],["impl PartialEq<Macro> for Macro"],["impl PartialEq<Match> for Match"],["impl PartialEq<Mod> for Mod"],["impl PartialEq<Move> for Move"],["impl PartialEq<Mut> for Mut"],["impl PartialEq<Override> for Override"],["impl PartialEq<Priv> for Priv"],["impl PartialEq<Pub> for Pub"],["impl PartialEq<Ref> for Ref"],["impl PartialEq<Return> for Return"],["impl PartialEq<SelfType> for SelfType"],["impl PartialEq<SelfValue> for SelfValue"],["impl PartialEq<Static> for Static"],["impl PartialEq<Struct> for Struct"],["impl PartialEq<Super> for Super"],["impl PartialEq<Trait> for Trait"],["impl PartialEq<Try> for Try"],["impl PartialEq<Type> for Type"],["impl PartialEq<Typeof> for Typeof"],["impl PartialEq<Union> for Union"],["impl PartialEq<Unsafe> for Unsafe"],["impl PartialEq<Unsized> for Unsized"],["impl PartialEq<Use> for Use"],["impl PartialEq<Virtual> for Virtual"],["impl PartialEq<Where> for Where"],["impl PartialEq<While> for While"],["impl PartialEq<Yield> for Yield"],["impl PartialEq<Add> for Add"],["impl PartialEq<AddEq> for AddEq"],["impl PartialEq<And> for And"],["impl PartialEq<AndAnd> for AndAnd"],["impl PartialEq<AndEq> for AndEq"],["impl PartialEq<At> for At"],["impl PartialEq<Bang> for Bang"],["impl PartialEq<Caret> for Caret"],["impl PartialEq<CaretEq> for CaretEq"],["impl PartialEq<Colon> for Colon"],["impl PartialEq<Colon2> for Colon2"],["impl PartialEq<Comma> for Comma"],["impl PartialEq<Div> for Div"],["impl PartialEq<DivEq> for DivEq"],["impl PartialEq<Dollar> for Dollar"],["impl PartialEq<Dot> for Dot"],["impl PartialEq<Dot2> for Dot2"],["impl PartialEq<Dot3> for Dot3"],["impl PartialEq<DotDotEq> for DotDotEq"],["impl PartialEq<Eq> for Eq"],["impl PartialEq<EqEq> for EqEq"],["impl PartialEq<Ge> for Ge"],["impl PartialEq<Gt> for Gt"],["impl PartialEq<Le> for Le"],["impl PartialEq<Lt> for Lt"],["impl PartialEq<MulEq> for MulEq"],["impl PartialEq<Ne> for Ne"],["impl PartialEq<Or> for Or"],["impl PartialEq<OrEq> for OrEq"],["impl PartialEq<OrOr> for OrOr"],["impl PartialEq<Pound> for Pound"],["impl PartialEq<Question> for Question"],["impl PartialEq<RArrow> for RArrow"],["impl PartialEq<LArrow> for LArrow"],["impl PartialEq<Rem> for Rem"],["impl PartialEq<RemEq> for RemEq"],["impl PartialEq<FatArrow> for FatArrow"],["impl PartialEq<Semi> for Semi"],["impl PartialEq<Shl> for Shl"],["impl PartialEq<ShlEq> for ShlEq"],["impl PartialEq<Shr> for Shr"],["impl PartialEq<ShrEq> for ShrEq"],["impl PartialEq<Star> for Star"],["impl PartialEq<Sub> for Sub"],["impl PartialEq<SubEq> for SubEq"],["impl PartialEq<Tilde> for Tilde"],["impl PartialEq<Brace> for Brace"],["impl PartialEq<Bracket> for Bracket"],["impl PartialEq<Paren> for Paren"],["impl PartialEq<Group> for Group"],["impl PartialEq<Member> for Member"],["impl PartialEq<Index> for Index"],["impl<'a> PartialEq<ImplGenerics<'a>> for ImplGenerics<'a>"],["impl<'a> PartialEq<TypeGenerics<'a>> for TypeGenerics<'a>"],["impl<'a> PartialEq<Turbofish<'a>> for Turbofish<'a>"],["impl PartialEq<Lifetime> for Lifetime"],["impl PartialEq<LitStr> for LitStr"],["impl PartialEq<LitByteStr> for LitByteStr"],["impl PartialEq<LitByte> for LitByte"],["impl PartialEq<LitChar> for LitChar"],["impl PartialEq<LitInt> for LitInt"],["impl PartialEq<LitFloat> for LitFloat"],["impl<'a> PartialEq<Cursor<'a>> for Cursor<'a>"],["impl<T, P> PartialEq<Punctuated<T, P>> for Punctuated<T, P>where
    T: PartialEq,
    P: PartialEq,
"],["impl PartialEq<Abi> for Abi"],["impl PartialEq<AngleBracketedGenericArguments> for AngleBracketedGenericArguments"],["impl PartialEq<Arm> for Arm"],["impl PartialEq<AttrStyle> for AttrStyle"],["impl PartialEq<Attribute> for Attribute"],["impl PartialEq<BareFnArg> for BareFnArg"],["impl PartialEq<BinOp> for BinOp"],["impl PartialEq<Binding> for Binding"],["impl PartialEq<Block> for Block"],["impl PartialEq<BoundLifetimes> for BoundLifetimes"],["impl PartialEq<ConstParam> for ConstParam"],["impl PartialEq<Constraint> for Constraint"],["impl PartialEq<Data> for Data"],["impl PartialEq<DataEnum> for DataEnum"],["impl PartialEq<DataStruct> for DataStruct"],["impl PartialEq<DataUnion> for DataUnion"],["impl PartialEq<DeriveInput> for DeriveInput"],["impl PartialEq<Expr> for Expr"],["impl PartialEq<ExprArray> for ExprArray"],["impl PartialEq<ExprAssign> for ExprAssign"],["impl PartialEq<ExprAssignOp> for ExprAssignOp"],["impl PartialEq<ExprAsync> for ExprAsync"],["impl PartialEq<ExprAwait> for ExprAwait"],["impl PartialEq<ExprBinary> for ExprBinary"],["impl PartialEq<ExprBlock> for ExprBlock"],["impl PartialEq<ExprBox> for ExprBox"],["impl PartialEq<ExprBreak> for ExprBreak"],["impl PartialEq<ExprCall> for ExprCall"],["impl PartialEq<ExprCast> for ExprCast"],["impl PartialEq<ExprClosure> for ExprClosure"],["impl PartialEq<ExprContinue> for ExprContinue"],["impl PartialEq<ExprField> for ExprField"],["impl PartialEq<ExprForLoop> for ExprForLoop"],["impl PartialEq<ExprGroup> for ExprGroup"],["impl PartialEq<ExprIf> for ExprIf"],["impl PartialEq<ExprIndex> for ExprIndex"],["impl PartialEq<ExprLet> for ExprLet"],["impl PartialEq<ExprLit> for ExprLit"],["impl PartialEq<ExprLoop> for ExprLoop"],["impl PartialEq<ExprMacro> for ExprMacro"],["impl PartialEq<ExprMatch> for ExprMatch"],["impl PartialEq<ExprMethodCall> for ExprMethodCall"],["impl PartialEq<ExprParen> for ExprParen"],["impl PartialEq<ExprPath> for ExprPath"],["impl PartialEq<ExprRange> for ExprRange"],["impl PartialEq<ExprReference> for ExprReference"],["impl PartialEq<ExprRepeat> for ExprRepeat"],["impl PartialEq<ExprReturn> for ExprReturn"],["impl PartialEq<ExprStruct> for ExprStruct"],["impl PartialEq<ExprTry> for ExprTry"],["impl PartialEq<ExprTryBlock> for ExprTryBlock"],["impl PartialEq<ExprTuple> for ExprTuple"],["impl PartialEq<ExprType> for ExprType"],["impl PartialEq<ExprUnary> for ExprUnary"],["impl PartialEq<ExprUnsafe> for ExprUnsafe"],["impl PartialEq<ExprWhile> for ExprWhile"],["impl PartialEq<ExprYield> for ExprYield"],["impl PartialEq<Field> for Field"],["impl PartialEq<FieldPat> for FieldPat"],["impl PartialEq<FieldValue> for FieldValue"],["impl PartialEq<Fields> for Fields"],["impl PartialEq<FieldsNamed> for FieldsNamed"],["impl PartialEq<FieldsUnnamed> for FieldsUnnamed"],["impl PartialEq<File> for File"],["impl PartialEq<FnArg> for FnArg"],["impl PartialEq<ForeignItem> for ForeignItem"],["impl PartialEq<ForeignItemFn> for ForeignItemFn"],["impl PartialEq<ForeignItemMacro> for ForeignItemMacro"],["impl PartialEq<ForeignItemStatic> for ForeignItemStatic"],["impl PartialEq<ForeignItemType> for ForeignItemType"],["impl PartialEq<GenericArgument> for GenericArgument"],["impl PartialEq<GenericMethodArgument> for GenericMethodArgument"],["impl PartialEq<GenericParam> for GenericParam"],["impl PartialEq<Generics> for Generics"],["impl PartialEq<ImplItem> for ImplItem"],["impl PartialEq<ImplItemConst> for ImplItemConst"],["impl PartialEq<ImplItemMacro> for ImplItemMacro"],["impl PartialEq<ImplItemMethod> for ImplItemMethod"],["impl PartialEq<ImplItemType> for ImplItemType"],["impl PartialEq<Item> for Item"],["impl PartialEq<ItemConst> for ItemConst"],["impl PartialEq<ItemEnum> for ItemEnum"],["impl PartialEq<ItemExternCrate> for ItemExternCrate"],["impl PartialEq<ItemFn> for ItemFn"],["impl PartialEq<ItemForeignMod> for ItemForeignMod"],["impl PartialEq<ItemImpl> for ItemImpl"],["impl PartialEq<ItemMacro> for ItemMacro"],["impl PartialEq<ItemMacro2> for ItemMacro2"],["impl PartialEq<ItemMod> for ItemMod"],["impl PartialEq<ItemStatic> for ItemStatic"],["impl PartialEq<ItemStruct> for ItemStruct"],["impl PartialEq<ItemTrait> for ItemTrait"],["impl PartialEq<ItemTraitAlias> for ItemTraitAlias"],["impl PartialEq<ItemType> for ItemType"],["impl PartialEq<ItemUnion> for ItemUnion"],["impl PartialEq<ItemUse> for ItemUse"],["impl PartialEq<Label> for Label"],["impl PartialEq<LifetimeDef> for LifetimeDef"],["impl PartialEq<Lit> for Lit"],["impl PartialEq<LitBool> for LitBool"],["impl PartialEq<Local> for Local"],["impl PartialEq<Macro> for Macro"],["impl PartialEq<MacroDelimiter> for MacroDelimiter"],["impl PartialEq<Meta> for Meta"],["impl PartialEq<MetaList> for MetaList"],["impl PartialEq<MetaNameValue> for MetaNameValue"],["impl PartialEq<MethodTurbofish> for MethodTurbofish"],["impl PartialEq<NestedMeta> for NestedMeta"],["impl PartialEq<ParenthesizedGenericArguments> for ParenthesizedGenericArguments"],["impl PartialEq<Pat> for Pat"],["impl PartialEq<PatBox> for PatBox"],["impl PartialEq<PatIdent> for PatIdent"],["impl PartialEq<PatLit> for PatLit"],["impl PartialEq<PatMacro> for PatMacro"],["impl PartialEq<PatOr> for PatOr"],["impl PartialEq<PatPath> for PatPath"],["impl PartialEq<PatRange> for PatRange"],["impl PartialEq<PatReference> for PatReference"],["impl PartialEq<PatRest> for PatRest"],["impl PartialEq<PatSlice> for PatSlice"],["impl PartialEq<PatStruct> for PatStruct"],["impl PartialEq<PatTuple> for PatTuple"],["impl PartialEq<PatTupleStruct> for PatTupleStruct"],["impl PartialEq<PatType> for PatType"],["impl PartialEq<PatWild> for PatWild"],["impl PartialEq<Path> for Path"],["impl PartialEq<PathArguments> for PathArguments"],["impl PartialEq<PathSegment> for PathSegment"],["impl PartialEq<PredicateEq> for PredicateEq"],["impl PartialEq<PredicateLifetime> for PredicateLifetime"],["impl PartialEq<PredicateType> for PredicateType"],["impl PartialEq<QSelf> for QSelf"],["impl PartialEq<RangeLimits> for RangeLimits"],["impl PartialEq<Receiver> for Receiver"],["impl PartialEq<ReturnType> for ReturnType"],["impl PartialEq<Signature> for Signature"],["impl PartialEq<Stmt> for Stmt"],["impl PartialEq<TraitBound> for TraitBound"],["impl PartialEq<TraitBoundModifier> for TraitBoundModifier"],["impl PartialEq<TraitItem> for TraitItem"],["impl PartialEq<TraitItemConst> for TraitItemConst"],["impl PartialEq<TraitItemMacro> for TraitItemMacro"],["impl PartialEq<TraitItemMethod> for TraitItemMethod"],["impl PartialEq<TraitItemType> for TraitItemType"],["impl PartialEq<Type> for Type"],["impl PartialEq<TypeArray> for TypeArray"],["impl PartialEq<TypeBareFn> for TypeBareFn"],["impl PartialEq<TypeGroup> for TypeGroup"],["impl PartialEq<TypeImplTrait> for TypeImplTrait"],["impl PartialEq<TypeInfer> for TypeInfer"],["impl PartialEq<TypeMacro> for TypeMacro"],["impl PartialEq<TypeNever> for TypeNever"],["impl PartialEq<TypeParam> for TypeParam"],["impl PartialEq<TypeParamBound> for TypeParamBound"],["impl PartialEq<TypeParen> for TypeParen"],["impl PartialEq<TypePath> for TypePath"],["impl PartialEq<TypePtr> for TypePtr"],["impl PartialEq<TypeReference> for TypeReference"],["impl PartialEq<TypeSlice> for TypeSlice"],["impl PartialEq<TypeTraitObject> for TypeTraitObject"],["impl PartialEq<TypeTuple> for TypeTuple"],["impl PartialEq<UnOp> for UnOp"],["impl PartialEq<UseGlob> for UseGlob"],["impl PartialEq<UseGroup> for UseGroup"],["impl PartialEq<UseName> for UseName"],["impl PartialEq<UsePath> for UsePath"],["impl PartialEq<UseRename> for UseRename"],["impl PartialEq<UseTree> for UseTree"],["impl PartialEq<Variadic> for Variadic"],["impl PartialEq<Variant> for Variant"],["impl PartialEq<VisCrate> for VisCrate"],["impl PartialEq<VisPublic> for VisPublic"],["impl PartialEq<VisRestricted> for VisRestricted"],["impl PartialEq<Visibility> for Visibility"],["impl PartialEq<WhereClause> for WhereClause"],["impl PartialEq<WherePredicate> for WherePredicate"],["impl PartialEq<Nothing> for Nothing"]], +"tokio":[["impl PartialEq<RuntimeFlavor> for RuntimeFlavor"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<TryAcquireError> for TryAcquireError"],["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"],["impl<T: PartialEq> PartialEq<SetError<T>> for SetError<T>"]], +"typenum":[["impl PartialEq<B0> for B0"],["impl PartialEq<B1> for B1"],["impl<U: PartialEq + Unsigned + NonZero> PartialEq<PInt<U>> for PInt<U>"],["impl<U: PartialEq + Unsigned + NonZero> PartialEq<NInt<U>> for NInt<U>"],["impl PartialEq<Z0> for Z0"],["impl PartialEq<UTerm> for UTerm"],["impl<U: PartialEq, B: PartialEq> PartialEq<UInt<U, B>> for UInt<U, B>"],["impl PartialEq<ATerm> for ATerm"],["impl<V: PartialEq, A: PartialEq> PartialEq<TArr<V, A>> for TArr<V, A>"],["impl PartialEq<Greater> for Greater"],["impl PartialEq<Less> for Less"],["impl PartialEq<Equal> for Equal"]], +"uint":[["impl PartialEq<FromStrRadixErrKind> for FromStrRadixErrKind"],["impl PartialEq<FromDecStrErr> for FromDecStrErr"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/fmt/trait.Debug.js b/docs/implementors/core/fmt/trait.Debug.js new file mode 100644 index 000000000000..0463240d1f35 --- /dev/null +++ b/docs/implementors/core/fmt/trait.Debug.js @@ -0,0 +1,51 @@ +(function() {var implementors = { +"ahash":[["impl Debug for AHasher"],["impl Debug for RandomState"]], +"aho_corasick":[["impl<S: Debug + StateID> Debug for AhoCorasick<S>"],["impl<'a, 'b, S: Debug + StateID> Debug for FindIter<'a, 'b, S>"],["impl<'a, 'b, S: Debug + StateID> Debug for FindOverlappingIter<'a, 'b, S>"],["impl<'a, R: Debug, S: Debug + StateID> Debug for StreamFindIter<'a, R, S>"],["impl Debug for AhoCorasickBuilder"],["impl Debug for MatchKind"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for MatchKind"],["impl Debug for Config"],["impl Debug for Builder"],["impl Debug for Searcher"],["impl<'s, 'h> Debug for FindIter<'s, 'h>"],["impl Debug for Match"]], +"aiofut":[["impl Debug for Error"]], +"bincode":[["impl Debug for Config"],["impl Debug for ErrorKind"]], +"block_buffer":[["impl Debug for Eager"],["impl Debug for Lazy"],["impl Debug for Error"],["impl<BlockSize: Debug, Kind: Debug> Debug for BlockBuffer<BlockSize, Kind>where
    BlockSize: ArrayLength<u8> + IsLess<U256>,
    Le<BlockSize, U256>: NonZero,
    Kind: BufferKind,
"]], +"byteorder":[["impl Debug for BigEndian"],["impl Debug for LittleEndian"]], +"bytes":[["impl<T: Debug, U: Debug> Debug for Chain<T, U>"],["impl<T: Debug> Debug for IntoIter<T>"],["impl<T: Debug> Debug for Limit<T>"],["impl<B: Debug> Debug for Reader<B>"],["impl<T: Debug> Debug for Take<T>"],["impl Debug for UninitSlice"],["impl<B: Debug> Debug for Writer<B>"],["impl Debug for Bytes"],["impl Debug for BytesMut"]], +"crossbeam_channel":[["impl<T> Debug for Sender<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Iter<'_, T>"],["impl<T> Debug for TryIter<'_, T>"],["impl<T> Debug for IntoIter<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl Debug for RecvTimeoutError"],["impl Debug for TrySelectError"],["impl Debug for SelectTimeoutError"],["impl Debug for TryReadyError"],["impl Debug for ReadyTimeoutError"],["impl<T> Debug for SendError<T>"],["impl<T> Debug for TrySendError<T>"],["impl<T> Debug for SendTimeoutError<T>"],["impl Debug for Select<'_>"],["impl Debug for SelectedOperation<'_>"]], +"crossbeam_utils":[["impl<T: Copy + Debug> Debug for AtomicCell<T>"],["impl<T: Debug> Debug for CachePadded<T>"],["impl Debug for Backoff"],["impl Debug for Parker"],["impl Debug for Unparker"],["impl<T: ?Sized + Debug> Debug for ShardedLock<T>"],["impl<T: Debug> Debug for ShardedLockReadGuard<'_, T>"],["impl<T: Debug> Debug for ShardedLockWriteGuard<'_, T>"],["impl Debug for WaitGroup"],["impl Debug for Scope<'_>"],["impl<'scope, 'env> Debug for ScopedThreadBuilder<'scope, 'env>"],["impl<T> Debug for ScopedJoinHandle<'_, T>"]], +"crypto_common":[["impl Debug for InvalidLength"]], +"digest":[["impl<T> Debug for RtVariableCoreWrapper<T>where
    T: VariableOutputCore + UpdateCore + AlgorithmName,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
"],["impl<T> Debug for CoreWrapper<T>where
    T: BufferKindUser + AlgorithmName,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
"],["impl<T> Debug for XofReaderCoreWrapper<T>where
    T: XofReaderCore + AlgorithmName,
    T::BlockSize: IsLess<U256>,
    Le<T::BlockSize, U256>: NonZero,
"],["impl Debug for TruncSide"],["impl Debug for InvalidOutputSize"],["impl Debug for InvalidBufferSize"]], +"firewood":[["impl Debug for DBError"],["impl Debug for MerkleError"],["impl Debug for PartialPath"],["impl Debug for ProofError"]], +"futures_channel":[["impl<T: Debug> Debug for Sender<T>"],["impl<T: Debug> Debug for UnboundedSender<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl<T: Debug> Debug for UnboundedReceiver<T>"],["impl Debug for SendError"],["impl<T> Debug for TrySendError<T>"],["impl Debug for TryRecvError"],["impl<T: Debug> Debug for Sender<T>"],["impl<'a, T: Debug> Debug for Cancellation<'a, T>"],["impl Debug for Canceled"],["impl<T: Debug> Debug for Receiver<T>"]], +"futures_executor":[["impl Debug for LocalPool"],["impl Debug for LocalSpawner"],["impl<S: Debug + Stream + Unpin> Debug for BlockingStream<S>"],["impl Debug for EnterError"],["impl Debug for Enter"]], +"futures_task":[["impl Debug for SpawnError"],["impl<'a> Debug for WakerRef<'a>"],["impl<T> Debug for LocalFutureObj<'_, T>"],["impl<T> Debug for FutureObj<'_, T>"]], +"futures_util":[["impl<Fut: Debug> Debug for Fuse<Fut>"],["impl<F> Debug for Flatten<F>where
    Flatten<F, <F as Future>::Output>: Debug,
    F: Future,
"],["impl<F> Debug for FlattenStream<F>where
    Flatten<F, <F as Future>::Output>: Debug,
    F: Future,
"],["impl<Fut, F> Debug for Map<Fut, F>where
    Map<Fut, F>: Debug,
"],["impl<F> Debug for IntoStream<F>where
    Once<F>: Debug,
"],["impl<Fut, T> Debug for MapInto<Fut, T>where
    Map<Fut, IntoFn<T>>: Debug,
"],["impl<Fut1, Fut2, F> Debug for Then<Fut1, Fut2, F>where
    Flatten<Map<Fut1, F>, Fut2>: Debug,
"],["impl<Fut, F> Debug for Inspect<Fut, F>where
    Map<Fut, InspectFn<F>>: Debug,
"],["impl<Fut> Debug for NeverError<Fut>where
    Map<Fut, OkFn<Never>>: Debug,
"],["impl<Fut> Debug for UnitError<Fut>where
    Map<Fut, OkFn<()>>: Debug,
"],["impl<Fut: Debug> Debug for CatchUnwind<Fut>"],["impl<T: Debug> Debug for RemoteHandle<T>"],["impl<Fut: Future + Debug> Debug for Remote<Fut>"],["impl<Fut: Future> Debug for Shared<Fut>"],["impl<Fut: Future> Debug for WeakShared<Fut>"],["impl<Fut: Debug> Debug for IntoFuture<Fut>"],["impl<Fut1, Fut2> Debug for TryFlatten<Fut1, Fut2>where
    TryFlatten<Fut1, Fut2>: Debug,
"],["impl<Fut> Debug for TryFlattenStream<Fut>where
    TryFlatten<Fut, Fut::Ok>: Debug,
    Fut: TryFuture,
"],["impl<Fut, Si> Debug for FlattenSink<Fut, Si>where
    TryFlatten<Fut, Si>: Debug,
"],["impl<Fut1, Fut2, F> Debug for AndThen<Fut1, Fut2, F>where
    TryFlatten<MapOk<Fut1, F>, Fut2>: Debug,
"],["impl<Fut1, Fut2, F> Debug for OrElse<Fut1, Fut2, F>where
    TryFlattenErr<MapErr<Fut1, F>, Fut2>: Debug,
"],["impl<Fut, E> Debug for ErrInto<Fut, E>where
    MapErr<Fut, IntoFn<E>>: Debug,
"],["impl<Fut, E> Debug for OkInto<Fut, E>where
    MapOk<Fut, IntoFn<E>>: Debug,
"],["impl<Fut, F> Debug for InspectOk<Fut, F>where
    Inspect<IntoFuture<Fut>, InspectOkFn<F>>: Debug,
"],["impl<Fut, F> Debug for InspectErr<Fut, F>where
    Inspect<IntoFuture<Fut>, InspectErrFn<F>>: Debug,
"],["impl<Fut, F> Debug for MapOk<Fut, F>where
    Map<IntoFuture<Fut>, MapOkFn<F>>: Debug,
"],["impl<Fut, F> Debug for MapErr<Fut, F>where
    Map<IntoFuture<Fut>, MapErrFn<F>>: Debug,
"],["impl<Fut, F, G> Debug for MapOkOrElse<Fut, F, G>where
    Map<IntoFuture<Fut>, ChainFn<MapOkFn<F>, ChainFn<MapErrFn<G>, MergeResultFn>>>: Debug,
"],["impl<Fut, F> Debug for UnwrapOrElse<Fut, F>where
    Map<IntoFuture<Fut>, UnwrapOrElseFn<F>>: Debug,
"],["impl<F: Debug> Debug for Lazy<F>"],["impl<T: Debug> Debug for Pending<T>"],["impl<Fut: Debug + Future> Debug for MaybeDone<Fut>where
    Fut::Output: Debug,
"],["impl<Fut: Debug + TryFuture> Debug for TryMaybeDone<Fut>where
    Fut::Ok: Debug,
"],["impl<F: Debug> Debug for OptionFuture<F>"],["impl<F> Debug for PollFn<F>"],["impl<T: Debug> Debug for PollImmediate<T>"],["impl<T: Debug> Debug for Ready<T>"],["impl<Fut1, Fut2> Debug for Join<Fut1, Fut2>where
    Fut1: Future + Debug,
    Fut1::Output: Debug,
    Fut2: Future + Debug,
    Fut2::Output: Debug,
"],["impl<Fut1, Fut2, Fut3> Debug for Join3<Fut1, Fut2, Fut3>where
    Fut1: Future + Debug,
    Fut1::Output: Debug,
    Fut2: Future + Debug,
    Fut2::Output: Debug,
    Fut3: Future + Debug,
    Fut3::Output: Debug,
"],["impl<Fut1, Fut2, Fut3, Fut4> Debug for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Future + Debug,
    Fut1::Output: Debug,
    Fut2: Future + Debug,
    Fut2::Output: Debug,
    Fut3: Future + Debug,
    Fut3::Output: Debug,
    Fut4: Future + Debug,
    Fut4::Output: Debug,
"],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Debug for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Future + Debug,
    Fut1::Output: Debug,
    Fut2: Future + Debug,
    Fut2::Output: Debug,
    Fut3: Future + Debug,
    Fut3::Output: Debug,
    Fut4: Future + Debug,
    Fut4::Output: Debug,
    Fut5: Future + Debug,
    Fut5::Output: Debug,
"],["impl<F> Debug for JoinAll<F>where
    F: Future + Debug,
    F::Output: Debug,
"],["impl<A: Debug, B: Debug> Debug for Select<A, B>"],["impl<Fut: Debug> Debug for SelectAll<Fut>"],["impl<Fut1, Fut2> Debug for TryJoin<Fut1, Fut2>where
    Fut1: TryFuture + Debug,
    Fut1::Ok: Debug,
    Fut1::Error: Debug,
    Fut2: TryFuture + Debug,
    Fut2::Ok: Debug,
    Fut2::Error: Debug,
"],["impl<Fut1, Fut2, Fut3> Debug for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: TryFuture + Debug,
    Fut1::Ok: Debug,
    Fut1::Error: Debug,
    Fut2: TryFuture + Debug,
    Fut2::Ok: Debug,
    Fut2::Error: Debug,
    Fut3: TryFuture + Debug,
    Fut3::Ok: Debug,
    Fut3::Error: Debug,
"],["impl<Fut1, Fut2, Fut3, Fut4> Debug for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: TryFuture + Debug,
    Fut1::Ok: Debug,
    Fut1::Error: Debug,
    Fut2: TryFuture + Debug,
    Fut2::Ok: Debug,
    Fut2::Error: Debug,
    Fut3: TryFuture + Debug,
    Fut3::Ok: Debug,
    Fut3::Error: Debug,
    Fut4: TryFuture + Debug,
    Fut4::Ok: Debug,
    Fut4::Error: Debug,
"],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Debug for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: TryFuture + Debug,
    Fut1::Ok: Debug,
    Fut1::Error: Debug,
    Fut2: TryFuture + Debug,
    Fut2::Ok: Debug,
    Fut2::Error: Debug,
    Fut3: TryFuture + Debug,
    Fut3::Ok: Debug,
    Fut3::Error: Debug,
    Fut4: TryFuture + Debug,
    Fut4::Ok: Debug,
    Fut4::Error: Debug,
    Fut5: TryFuture + Debug,
    Fut5::Ok: Debug,
    Fut5::Error: Debug,
"],["impl<F> Debug for TryJoinAll<F>where
    F: TryFuture + Debug,
    F::Ok: Debug,
    F::Error: Debug,
    F::Output: Debug,
"],["impl<A: Debug, B: Debug> Debug for TrySelect<A, B>"],["impl<Fut: Debug> Debug for SelectOk<Fut>"],["impl<A: Debug, B: Debug> Debug for Either<A, B>"],["impl<St1: Debug, St2: Debug> Debug for Chain<St1, St2>"],["impl<St: Debug, C: Debug> Debug for Collect<St, C>"],["impl<St: Debug, FromA: Debug, FromB: Debug> Debug for Unzip<St, FromA, FromB>"],["impl<St: Debug + Stream> Debug for Concat<St>where
    St::Item: Debug,
"],["impl<St: Debug> Debug for Cycle<St>"],["impl<St: Debug> Debug for Enumerate<St>"],["impl<St, Fut, F> Debug for Filter<St, Fut, F>where
    St: Stream + Debug,
    St::Item: Debug,
    Fut: Debug,
"],["impl<St, Fut, F> Debug for FilterMap<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St> Debug for Flatten<St>where
    Flatten<St, St::Item>: Debug,
    St: Stream,
"],["impl<St, Fut, T, F> Debug for Fold<St, Fut, T, F>where
    St: Debug,
    Fut: Debug,
    T: Debug,
"],["impl<St, Si> Debug for Forward<St, Si>where
    Forward<St, Si, St::Ok>: Debug,
    St: TryStream,
"],["impl<St, Fut, F> Debug for ForEach<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St: Debug> Debug for Fuse<St>"],["impl<St: Debug> Debug for StreamFuture<St>"],["impl<St, F> Debug for Inspect<St, F>where
    Map<St, InspectFn<F>>: Debug,
"],["impl<St, F> Debug for Map<St, F>where
    St: Debug,
"],["impl<St, U, F> Debug for FlatMap<St, U, F>where
    Flatten<Map<St, F>, U>: Debug,
"],["impl<'a, St: Debug + ?Sized> Debug for Next<'a, St>"],["impl<'a, St: Debug + ?Sized> Debug for SelectNextSome<'a, St>"],["impl<St: Debug + Stream> Debug for Peekable<St>where
    St::Item: Debug,
"],["impl<St> Debug for Peek<'_, St>where
    St: Stream + Debug,
    St::Item: Debug,
"],["impl<St> Debug for PeekMut<'_, St>where
    St: Stream + Debug,
    St::Item: Debug,
"],["impl<St, F> Debug for NextIf<'_, St, F>where
    St: Stream + Debug,
    St::Item: Debug,
"],["impl<St, T> Debug for NextIfEq<'_, St, T>where
    St: Stream + Debug,
    St::Item: Debug,
    T: ?Sized,
"],["impl<St: Debug> Debug for Skip<St>"],["impl<St, Fut, F> Debug for SkipWhile<St, Fut, F>where
    St: Stream + Debug,
    St::Item: Debug,
    Fut: Debug,
"],["impl<St: Debug> Debug for Take<St>"],["impl<St, Fut, F> Debug for TakeWhile<St, Fut, F>where
    St: Stream + Debug,
    St::Item: Debug,
    Fut: Debug,
"],["impl<St, Fut> Debug for TakeUntil<St, Fut>where
    St: Stream + Debug,
    St::Item: Debug,
    Fut: Future + Debug,
"],["impl<St, Fut, F> Debug for Then<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St1: Debug + Stream, St2: Debug + Stream> Debug for Zip<St1, St2>where
    St1::Item: Debug,
    St2::Item: Debug,
"],["impl<St: Debug + Stream> Debug for Chunks<St>where
    St::Item: Debug,
"],["impl<St: Debug + Stream> Debug for ReadyChunks<St>where
    St::Item: Debug,
"],["impl<St, S, Fut, F> Debug for Scan<St, S, Fut, F>where
    St: Stream + Debug,
    St::Item: Debug,
    S: Debug,
    Fut: Debug,
"],["impl<St> Debug for BufferUnordered<St>where
    St: Stream + Debug,
"],["impl<St> Debug for Buffered<St>where
    St: Stream + Debug,
    St::Item: Future,
"],["impl<St, Fut, F> Debug for ForEachConcurrent<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<S: Debug> Debug for SplitStream<S>"],["impl<S: Debug, Item: Debug> Debug for SplitSink<S, Item>"],["impl<T, Item> Debug for ReuniteError<T, Item>"],["impl<St: Debug> Debug for CatchUnwind<St>"],["impl<St, Fut, F> Debug for AndThen<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St, E> Debug for ErrInto<St, E>where
    MapErr<St, IntoFn<E>>: Debug,
"],["impl<St, F> Debug for InspectOk<St, F>where
    Inspect<IntoStream<St>, InspectOkFn<F>>: Debug,
"],["impl<St, F> Debug for InspectErr<St, F>where
    Inspect<IntoStream<St>, InspectErrFn<F>>: Debug,
"],["impl<St: Debug> Debug for IntoStream<St>"],["impl<St, F> Debug for MapOk<St, F>where
    Map<IntoStream<St>, MapOkFn<F>>: Debug,
"],["impl<St, F> Debug for MapErr<St, F>where
    Map<IntoStream<St>, MapErrFn<F>>: Debug,
"],["impl<St, Fut, F> Debug for OrElse<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<'a, St: Debug + ?Sized> Debug for TryNext<'a, St>"],["impl<St, Fut, F> Debug for TryForEach<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St, Fut, F> Debug for TryFilter<St, Fut, F>where
    St: TryStream + Debug,
    St::Ok: Debug,
    Fut: Debug,
"],["impl<St, Fut, F> Debug for TryFilterMap<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St: Debug> Debug for TryFlatten<St>where
    St: TryStream,
    St::Ok: Debug,
"],["impl<St: Debug, C: Debug> Debug for TryCollect<St, C>"],["impl<St: Debug + TryStream> Debug for TryConcat<St>where
    St::Ok: Debug,
"],["impl<St: Debug + TryStream> Debug for TryChunks<St>where
    St::Ok: Debug,
"],["impl<T, E: Debug> Debug for TryChunksError<T, E>"],["impl<St, Fut, T, F> Debug for TryFold<St, Fut, T, F>where
    St: Debug,
    Fut: Debug,
    T: Debug,
"],["impl<T, F, Fut> Debug for TryUnfold<T, F, Fut>where
    T: Debug,
    Fut: Debug,
"],["impl<St, Fut, F> Debug for TrySkipWhile<St, Fut, F>where
    St: TryStream + Debug,
    St::Ok: Debug,
    Fut: Debug,
"],["impl<St, Fut, F> Debug for TryTakeWhile<St, Fut, F>where
    St: TryStream + Debug,
    St::Ok: Debug,
    Fut: Debug,
"],["impl<St: Debug> Debug for TryBufferUnordered<St>where
    St: TryStream,
    St::Ok: Debug,
"],["impl<St: Debug> Debug for TryBuffered<St>where
    St: TryStream,
    St::Ok: TryFuture,
    St::Ok: Debug,
"],["impl<St, Fut, F> Debug for TryForEachConcurrent<St, Fut, F>where
    St: Debug,
    Fut: Debug,
"],["impl<St: Debug> Debug for IntoAsyncRead<St>where
    St: TryStream<Error = Error>,
    St::Ok: AsRef<[u8]>,
    St::Ok: Debug,
"],["impl<I: Debug> Debug for Iter<I>"],["impl<T: Debug> Debug for Repeat<T>"],["impl<F: Debug> Debug for RepeatWith<F>"],["impl<T: Debug> Debug for Empty<T>"],["impl<Fut: Debug> Debug for Once<Fut>"],["impl<T: Debug> Debug for Pending<T>"],["impl<F> Debug for PollFn<F>"],["impl<S: Debug> Debug for PollImmediate<S>"],["impl<St1: Debug, St2: Debug> Debug for Select<St1, St2>"],["impl Debug for PollNext"],["impl<St1, St2, Clos, State> Debug for SelectWithStrategy<St1, St2, Clos, State>where
    St1: Debug,
    St2: Debug,
    State: Debug,
"],["impl<T, F, Fut> Debug for Unfold<T, F, Fut>where
    T: Debug,
    Fut: Debug,
"],["impl<Fut: Future> Debug for FuturesOrdered<Fut>"],["impl<'a, Fut: Debug> Debug for IterPinMut<'a, Fut>"],["impl<'a, Fut: Debug + Unpin> Debug for IterMut<'a, Fut>"],["impl<'a, Fut: Debug> Debug for IterPinRef<'a, Fut>"],["impl<'a, Fut: Debug + Unpin> Debug for Iter<'a, Fut>"],["impl<Fut: Debug + Unpin> Debug for IntoIter<Fut>"],["impl<Fut> Debug for FuturesUnordered<Fut>"],["impl<St: Debug> Debug for SelectAll<St>"],["impl<'a, St: Debug + Unpin> Debug for Iter<'a, St>"],["impl<'a, St: Debug + Unpin> Debug for IterMut<'a, St>"],["impl<St: Debug + Unpin> Debug for IntoIter<St>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Close<'a, Si, Item>"],["impl<T: Debug> Debug for Drain<T>"],["impl<Si1: Debug, Si2: Debug> Debug for Fanout<Si1, Si2>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Feed<'a, Si, Item>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Flush<'a, Si, Item>"],["impl<Si: Debug + Sink<Item>, Item: Debug, E: Debug> Debug for SinkErrInto<Si, Item, E>where
    Si::Error: Debug,
"],["impl<Si: Debug, F: Debug> Debug for SinkMapErr<Si, F>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Send<'a, Si, Item>"],["impl<Si, St> Debug for SendAll<'_, Si, St>where
    Si: Debug + ?Sized,
    St: Debug + ?Sized + TryStream,
    St::Ok: Debug,
"],["impl<T: Debug, F: Debug, R: Debug> Debug for Unfold<T, F, R>"],["impl<Si, Item, U, Fut, F> Debug for With<Si, Item, U, Fut, F>where
    Si: Debug,
    Fut: Debug,
"],["impl<Si, Item, U, St, F> Debug for WithFlatMap<Si, Item, U, St, F>where
    Si: Debug,
    St: Debug,
    Item: Debug,
"],["impl<Si: Debug, Item: Debug> Debug for Buffer<Si, Item>"],["impl<T: Debug> Debug for AllowStdIo<T>"],["impl<R: Debug> Debug for BufReader<R>"],["impl<'a, R: Debug> Debug for SeeKRelative<'a, R>"],["impl<W: Debug> Debug for BufWriter<W>"],["impl<W: Debug + AsyncWrite> Debug for LineWriter<W>"],["impl<T, U> Debug for Chain<T, U>where
    T: Debug,
    U: Debug,
"],["impl<'a, W: Debug + ?Sized> Debug for Close<'a, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for Copy<'a, R, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for CopyBuf<'a, R, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for CopyBufAbortable<'a, R, W>"],["impl<T: Debug> Debug for Cursor<T>"],["impl Debug for Empty"],["impl<'a, R: Debug + ?Sized> Debug for FillBuf<'a, R>"],["impl<'a, W: Debug + ?Sized> Debug for Flush<'a, W>"],["impl<W: Debug, Item: Debug> Debug for IntoSink<W, Item>"],["impl<R: Debug> Debug for Lines<R>"],["impl<'a, R: Debug + ?Sized> Debug for Read<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadVectored<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadExact<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadLine<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadToEnd<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadToString<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadUntil<'a, R>"],["impl Debug for Repeat"],["impl<'a, S: Debug + ?Sized> Debug for Seek<'a, S>"],["impl Debug for Sink"],["impl<T: Debug> Debug for ReadHalf<T>"],["impl<T: Debug> Debug for WriteHalf<T>"],["impl<T> Debug for ReuniteError<T>"],["impl<R: Debug> Debug for Take<R>"],["impl<T: Debug> Debug for Window<T>"],["impl<'a, W: Debug + ?Sized> Debug for Write<'a, W>"],["impl<'a, W: Debug + ?Sized> Debug for WriteVectored<'a, W>"],["impl<'a, W: Debug + ?Sized> Debug for WriteAll<'a, W>"],["impl<T: ?Sized> Debug for Mutex<T>"],["impl<T: ?Sized> Debug for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Debug> Debug for OwnedMutexGuard<T>"],["impl<T: ?Sized> Debug for MutexLockFuture<'_, T>"],["impl<T: ?Sized + Debug> Debug for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized + Debug> Debug for MappedMutexGuard<'_, T, U>"],["impl<T: Debug> Debug for Abortable<T>"],["impl Debug for AbortRegistration"],["impl Debug for AbortHandle"],["impl Debug for Aborted"]], +"generic_array":[["impl<T: Debug, N> Debug for GenericArray<T, N>where
    N: ArrayLength<T>,
"],["impl<T: Debug, N> Debug for GenericArrayIter<T, N>where
    N: ArrayLength<T>,
"]], +"getrandom":[["impl Debug for Error"]], +"growthring":[["impl Debug for WALRingId"]], +"hashbrown":[["impl<K, V, S, A> Debug for HashMap<K, V, S, A>where
    K: Debug,
    V: Debug,
    A: Allocator + Clone,
"],["impl<K: Debug, V: Debug> Debug for Iter<'_, K, V>"],["impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for IntoKeys<K, V, A>"],["impl<K, V: Debug, A: Allocator + Clone> Debug for IntoValues<K, V, A>"],["impl<K: Debug, V> Debug for Keys<'_, K, V>"],["impl<K, V: Debug> Debug for Values<'_, K, V>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawEntryBuilderMut<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for RawEntryMut<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for RawOccupiedEntryMut<'_, K, V, S, A>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawVacantEntryMut<'_, K, V, S, A>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawEntryBuilder<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for Entry<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedEntry<'_, K, V, S, A>"],["impl<K: Debug, V, S, A: Allocator + Clone> Debug for VacantEntry<'_, K, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V: Debug, S, A: Allocator + Clone> Debug for EntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedEntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V, S, A: Allocator + Clone> Debug for VacantEntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedError<'_, K, V, S, A>"],["impl<K, V> Debug for IterMut<'_, K, V>where
    K: Debug,
    V: Debug,
"],["impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for IntoIter<K, V, A>"],["impl<K, V: Debug> Debug for ValuesMut<'_, K, V>"],["impl<K, V, A> Debug for Drain<'_, K, V, A>where
    K: Debug,
    V: Debug,
    A: Allocator + Clone,
"],["impl<T, S, A> Debug for HashSet<T, S, A>where
    T: Debug,
    A: Allocator + Clone,
"],["impl<K: Debug> Debug for Iter<'_, K>"],["impl<K: Debug, A: Allocator + Clone> Debug for IntoIter<K, A>"],["impl<K: Debug, A: Allocator + Clone> Debug for Drain<'_, K, A>"],["impl<T, S, A> Debug for Intersection<'_, T, S, A>where
    T: Debug + Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T, S, A> Debug for Difference<'_, T, S, A>where
    T: Debug + Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T, S, A> Debug for SymmetricDifference<'_, T, S, A>where
    T: Debug + Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T, S, A> Debug for Union<'_, T, S, A>where
    T: Debug + Eq + Hash,
    S: BuildHasher,
    A: Allocator + Clone,
"],["impl<T: Debug, S, A: Allocator + Clone> Debug for Entry<'_, T, S, A>"],["impl<T: Debug, S, A: Allocator + Clone> Debug for OccupiedEntry<'_, T, S, A>"],["impl<T: Debug, S, A: Allocator + Clone> Debug for VacantEntry<'_, T, S, A>"],["impl Debug for TryReserveError"]], +"hex":[["impl Debug for FromHexError"]], +"libc":[["impl Debug for DIR"],["impl Debug for group"],["impl Debug for utimbuf"],["impl Debug for timeval"],["impl Debug for timespec"],["impl Debug for rlimit"],["impl Debug for rusage"],["impl Debug for ipv6_mreq"],["impl Debug for hostent"],["impl Debug for iovec"],["impl Debug for pollfd"],["impl Debug for winsize"],["impl Debug for linger"],["impl Debug for sigval"],["impl Debug for itimerval"],["impl Debug for tms"],["impl Debug for servent"],["impl Debug for protoent"],["impl Debug for FILE"],["impl Debug for fpos_t"],["impl Debug for timezone"],["impl Debug for in_addr"],["impl Debug for ip_mreq"],["impl Debug for ip_mreqn"],["impl Debug for ip_mreq_source"],["impl Debug for sockaddr"],["impl Debug for sockaddr_in"],["impl Debug for sockaddr_in6"],["impl Debug for addrinfo"],["impl Debug for sockaddr_ll"],["impl Debug for fd_set"],["impl Debug for tm"],["impl Debug for sched_param"],["impl Debug for Dl_info"],["impl Debug for lconv"],["impl Debug for in_pktinfo"],["impl Debug for ifaddrs"],["impl Debug for in6_rtmsg"],["impl Debug for arpreq"],["impl Debug for arpreq_old"],["impl Debug for arphdr"],["impl Debug for mmsghdr"],["impl Debug for epoll_event"],["impl Debug for sockaddr_un"],["impl Debug for sockaddr_storage"],["impl Debug for utsname"],["impl Debug for sigevent"],["impl Debug for fpos64_t"],["impl Debug for rlimit64"],["impl Debug for glob_t"],["impl Debug for passwd"],["impl Debug for spwd"],["impl Debug for dqblk"],["impl Debug for signalfd_siginfo"],["impl Debug for itimerspec"],["impl Debug for fsid_t"],["impl Debug for packet_mreq"],["impl Debug for cpu_set_t"],["impl Debug for if_nameindex"],["impl Debug for msginfo"],["impl Debug for sembuf"],["impl Debug for input_event"],["impl Debug for input_id"],["impl Debug for input_absinfo"],["impl Debug for input_keymap_entry"],["impl Debug for input_mask"],["impl Debug for ff_replay"],["impl Debug for ff_trigger"],["impl Debug for ff_envelope"],["impl Debug for ff_constant_effect"],["impl Debug for ff_ramp_effect"],["impl Debug for ff_condition_effect"],["impl Debug for ff_periodic_effect"],["impl Debug for ff_rumble_effect"],["impl Debug for ff_effect"],["impl Debug for uinput_ff_upload"],["impl Debug for uinput_ff_erase"],["impl Debug for uinput_abs_setup"],["impl Debug for dl_phdr_info"],["impl Debug for Elf32_Ehdr"],["impl Debug for Elf64_Ehdr"],["impl Debug for Elf32_Sym"],["impl Debug for Elf64_Sym"],["impl Debug for Elf32_Phdr"],["impl Debug for Elf64_Phdr"],["impl Debug for Elf32_Shdr"],["impl Debug for Elf64_Shdr"],["impl Debug for ucred"],["impl Debug for mntent"],["impl Debug for posix_spawn_file_actions_t"],["impl Debug for posix_spawnattr_t"],["impl Debug for genlmsghdr"],["impl Debug for in6_pktinfo"],["impl Debug for arpd_request"],["impl Debug for inotify_event"],["impl Debug for fanotify_response"],["impl Debug for sockaddr_vm"],["impl Debug for regmatch_t"],["impl Debug for sock_extended_err"],["impl Debug for __c_anonymous_sockaddr_can_tp"],["impl Debug for __c_anonymous_sockaddr_can_j1939"],["impl Debug for can_filter"],["impl Debug for j1939_filter"],["impl Debug for sock_filter"],["impl Debug for sock_fprog"],["impl Debug for seccomp_data"],["impl Debug for nlmsghdr"],["impl Debug for nlmsgerr"],["impl Debug for nlattr"],["impl Debug for file_clone_range"],["impl Debug for __c_anonymous_ifru_map"],["impl Debug for in6_ifreq"],["impl Debug for sockaddr_nl"],["impl Debug for dirent"],["impl Debug for dirent64"],["impl Debug for pthread_cond_t"],["impl Debug for pthread_mutex_t"],["impl Debug for pthread_rwlock_t"],["impl Debug for sockaddr_alg"],["impl Debug for uinput_setup"],["impl Debug for uinput_user_dev"],["impl Debug for af_alg_iv"],["impl Debug for mq_attr"],["impl Debug for __c_anonymous_ifr_ifru"],["impl Debug for ifreq"],["impl Debug for statx"],["impl Debug for statx_timestamp"],["impl Debug for aiocb"],["impl Debug for __exit_status"],["impl Debug for __timeval"],["impl Debug for glob64_t"],["impl Debug for msghdr"],["impl Debug for cmsghdr"],["impl Debug for termios"],["impl Debug for mallinfo"],["impl Debug for mallinfo2"],["impl Debug for nl_pktinfo"],["impl Debug for nl_mmap_req"],["impl Debug for nl_mmap_hdr"],["impl Debug for rtentry"],["impl Debug for timex"],["impl Debug for ntptimeval"],["impl Debug for regex_t"],["impl Debug for Elf64_Chdr"],["impl Debug for Elf32_Chdr"],["impl Debug for seminfo"],["impl Debug for ptrace_peeksiginfo_args"],["impl Debug for __c_anonymous_ptrace_syscall_info_entry"],["impl Debug for __c_anonymous_ptrace_syscall_info_exit"],["impl Debug for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Debug for ptrace_syscall_info"],["impl Debug for utmpx"],["impl Debug for __c_anonymous_ptrace_syscall_info_data"],["impl Debug for sigset_t"],["impl Debug for sysinfo"],["impl Debug for msqid_ds"],["impl Debug for semid_ds"],["impl Debug for sigaction"],["impl Debug for statfs"],["impl Debug for flock"],["impl Debug for flock64"],["impl Debug for siginfo_t"],["impl Debug for stack_t"],["impl Debug for stat"],["impl Debug for stat64"],["impl Debug for statfs64"],["impl Debug for statvfs64"],["impl Debug for pthread_attr_t"],["impl Debug for _libc_fpxreg"],["impl Debug for _libc_xmmreg"],["impl Debug for _libc_fpstate"],["impl Debug for user_regs_struct"],["impl Debug for user"],["impl Debug for mcontext_t"],["impl Debug for ipc_perm"],["impl Debug for shmid_ds"],["impl Debug for seccomp_notif_sizes"],["impl Debug for ptrace_rseq_configuration"],["impl Debug for user_fpregs_struct"],["impl Debug for ucontext_t"],["impl Debug for statvfs"],["impl Debug for clone_args"],["impl Debug for sem_t"],["impl Debug for termios2"],["impl Debug for pthread_mutexattr_t"],["impl Debug for pthread_rwlockattr_t"],["impl Debug for pthread_condattr_t"],["impl Debug for fanotify_event_metadata"],["impl Debug for open_how"],["impl Debug for in6_addr"]], +"lock_api":[["impl<R: RawMutex, T: ?Sized + Debug> Debug for Mutex<R, T>"],["impl<'a, R: RawMutex + 'a, T: Debug + ?Sized + 'a> Debug for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: Debug + ?Sized + 'a> Debug for MappedMutexGuard<'a, R, T>"],["impl<R: RawMutex, G: GetThreadId, T: ?Sized + Debug> Debug for ReentrantMutex<R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: Debug + ?Sized + 'a> Debug for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: Debug + ?Sized + 'a> Debug for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<R: RawRwLock, T: ?Sized + Debug> Debug for RwLock<R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: Debug + ?Sized + 'a> Debug for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for MappedRwLockWriteGuard<'a, R, T>"]], +"lru":[["impl<K: Hash + Eq, V> Debug for LruCache<K, V>"]], +"memchr":[["impl Debug for Prefilter"],["impl<'h, 'n> Debug for FindIter<'h, 'n>"],["impl<'h, 'n> Debug for FindRevIter<'h, 'n>"],["impl<'n> Debug for Finder<'n>"],["impl<'n> Debug for FinderRev<'n>"],["impl Debug for FinderBuilder"]], +"nix":[["impl Debug for Dir"],["impl<'d> Debug for Iter<'d>"],["impl Debug for OwningIter"],["impl Debug for Entry"],["impl Debug for Type"],["impl Debug for ClearEnvError"],["impl Debug for Errno"],["impl Debug for AtFlags"],["impl Debug for OFlag"],["impl Debug for RenameFlags"],["impl Debug for SealFlag"],["impl Debug for FdFlag"],["impl<'a> Debug for FcntlArg<'a>"],["impl Debug for FlockArg"],["impl Debug for SpliceFFlags"],["impl Debug for FallocateFlags"],["impl Debug for PosixFadviseAdvice"],["impl Debug for InterfaceAddress"],["impl Debug for InterfaceAddressIterator"],["impl Debug for InterfaceFlags"],["impl Debug for Interface"],["impl Debug for Interfaces"],["impl<'a> Debug for InterfacesIter<'a>"],["impl Debug for ModuleInitFlags"],["impl Debug for DeleteModuleFlags"],["impl Debug for MsFlags"],["impl Debug for MntFlags"],["impl Debug for MQ_OFlag"],["impl Debug for MqAttr"],["impl Debug for MqdT"],["impl Debug for PollFd"],["impl Debug for PollFlags"],["impl Debug for OpenptyResult"],["impl Debug for ForkptyResult"],["impl Debug for PtyMaster"],["impl Debug for CloneFlags"],["impl Debug for CpuSet"],["impl Debug for AioFsyncMode"],["impl Debug for LioMode"],["impl Debug for AioCancelStat"],["impl Debug for AioFsync"],["impl<'a> Debug for AioRead<'a>"],["impl<'a> Debug for AioWrite<'a>"],["impl Debug for EpollFlags"],["impl Debug for EpollOp"],["impl Debug for EpollCreateFlags"],["impl Debug for EpollEvent"],["impl Debug for EfdFlags"],["impl Debug for MemFdCreateFlag"],["impl Debug for ProtFlags"],["impl Debug for MapFlags"],["impl Debug for MRemapFlags"],["impl Debug for MmapAdvise"],["impl Debug for MsFlags"],["impl Debug for MlockAllFlags"],["impl Debug for Persona"],["impl Debug for Request"],["impl Debug for Event"],["impl Debug for Options"],["impl Debug for QuotaType"],["impl Debug for QuotaFmt"],["impl Debug for QuotaValidFlags"],["impl Debug for Dqblk"],["impl Debug for RebootMode"],["impl Debug for Resource"],["impl Debug for UsageWho"],["impl Debug for Usage"],["impl Debug for FdSet"],["impl<'a> Debug for Fds<'a>"],["impl Debug for Signal"],["impl Debug for SignalIterator"],["impl Debug for SaFlags"],["impl Debug for SigmaskHow"],["impl Debug for SigSet"],["impl<'a> Debug for SigSetIter<'a>"],["impl Debug for SigHandler"],["impl Debug for SigAction"],["impl Debug for SigevNotify"],["impl Debug for SigEvent"],["impl Debug for SfdFlags"],["impl Debug for SignalFd"],["impl Debug for AddressFamily"],["impl Debug for InetAddr"],["impl Debug for IpAddr"],["impl Debug for Ipv4Addr"],["impl Debug for Ipv6Addr"],["impl Debug for UnixAddr"],["impl Debug for SockaddrIn"],["impl Debug for SockaddrIn6"],["impl Debug for SockaddrStorage"],["impl Debug for SockAddr"],["impl Debug for NetlinkAddr"],["impl Debug for AlgAddr"],["impl Debug for LinkAddr"],["impl Debug for VsockAddr"],["impl Debug for ReuseAddr"],["impl Debug for ReusePort"],["impl Debug for TcpNoDelay"],["impl Debug for Linger"],["impl Debug for IpAddMembership"],["impl Debug for IpDropMembership"],["impl Debug for Ipv6AddMembership"],["impl Debug for Ipv6DropMembership"],["impl Debug for IpMulticastTtl"],["impl Debug for IpMulticastLoop"],["impl Debug for Priority"],["impl Debug for IpTos"],["impl Debug for Ipv6TClass"],["impl Debug for IpFreebind"],["impl Debug for ReceiveTimeout"],["impl Debug for SendTimeout"],["impl Debug for Broadcast"],["impl Debug for OobInline"],["impl Debug for SocketError"],["impl Debug for DontRoute"],["impl Debug for KeepAlive"],["impl Debug for PeerCredentials"],["impl Debug for TcpKeepIdle"],["impl Debug for TcpMaxSeg"],["impl Debug for TcpKeepCount"],["impl Debug for TcpRepair"],["impl Debug for TcpKeepInterval"],["impl Debug for TcpUserTimeout"],["impl Debug for RcvBuf"],["impl Debug for SndBuf"],["impl Debug for RcvBufForce"],["impl Debug for SndBufForce"],["impl Debug for SockType"],["impl Debug for AcceptConn"],["impl Debug for BindToDevice"],["impl Debug for OriginalDst"],["impl Debug for Ip6tOriginalDst"],["impl Debug for Timestamping"],["impl Debug for ReceiveTimestamp"],["impl Debug for ReceiveTimestampns"],["impl Debug for IpTransparent"],["impl Debug for Mark"],["impl Debug for PassCred"],["impl Debug for TcpCongestion"],["impl Debug for Ipv4PacketInfo"],["impl Debug for Ipv6RecvPacketInfo"],["impl Debug for Ipv4OrigDstAddr"],["impl Debug for UdpGsoSegment"],["impl Debug for UdpGroSegment"],["impl Debug for TxTime"],["impl Debug for RxqOvfl"],["impl Debug for Ipv6V6Only"],["impl Debug for Ipv4RecvErr"],["impl Debug for Ipv6RecvErr"],["impl Debug for IpMtu"],["impl Debug for Ipv4Ttl"],["impl Debug for Ipv6Ttl"],["impl Debug for Ipv6OrigDstAddr"],["impl Debug for Ipv6DontFrag"],["impl Debug for AlgSetAeadAuthSize"],["impl<T: Debug> Debug for AlgSetKey<T>"],["impl Debug for SockType"],["impl Debug for SockProtocol"],["impl Debug for TimestampingFlag"],["impl Debug for SockFlag"],["impl Debug for MsgFlags"],["impl Debug for UnixCredentials"],["impl Debug for IpMembershipRequest"],["impl Debug for Ipv6MembershipRequest"],["impl<'a, 's, S: Debug> Debug for RecvMsg<'a, 's, S>"],["impl<'a> Debug for CmsgIterator<'a>"],["impl Debug for ControlMessageOwned"],["impl Debug for Timestamps"],["impl<'a> Debug for ControlMessage<'a>"],["impl<S: Debug> Debug for MultiHeaders<S>"],["impl<'a, S: Debug> Debug for MultiResults<'a, S>"],["impl<'a> Debug for IoSliceIterator<'a>"],["impl Debug for Shutdown"],["impl Debug for SFlag"],["impl Debug for Mode"],["impl Debug for FchmodatFlags"],["impl Debug for UtimensatFlags"],["impl Debug for FsType"],["impl Debug for Statfs"],["impl Debug for FsFlags"],["impl Debug for Statvfs"],["impl Debug for SysInfo"],["impl Debug for Termios"],["impl Debug for BaudRate"],["impl Debug for SetArg"],["impl Debug for FlushArg"],["impl Debug for FlowArg"],["impl Debug for SpecialCharacterIndices"],["impl Debug for InputFlags"],["impl Debug for OutputFlags"],["impl Debug for ControlFlags"],["impl Debug for LocalFlags"],["impl Debug for Expiration"],["impl Debug for TimerSetTimeFlags"],["impl Debug for TimeSpec"],["impl Debug for TimeVal"],["impl Debug for RemoteIoVec"],["impl<T: Debug> Debug for IoVec<T>"],["impl Debug for UtsName"],["impl Debug for WaitPidFlag"],["impl Debug for WaitStatus"],["impl Debug for Id"],["impl Debug for AddWatchFlags"],["impl Debug for InitFlags"],["impl Debug for Inotify"],["impl Debug for WatchDescriptor"],["impl Debug for InotifyEvent"],["impl Debug for TimerFd"],["impl Debug for ClockId"],["impl Debug for TimerFlags"],["impl Debug for Timer"],["impl Debug for ClockId"],["impl Debug for UContext"],["impl Debug for Uid"],["impl Debug for Gid"],["impl Debug for Pid"],["impl Debug for ForkResult"],["impl Debug for FchownatFlags"],["impl Debug for Whence"],["impl Debug for LinkatFlags"],["impl Debug for UnlinkatFlags"],["impl Debug for PathconfVar"],["impl Debug for SysconfVar"],["impl Debug for ResUid"],["impl Debug for ResGid"],["impl Debug for AccessFlags"],["impl Debug for User"],["impl Debug for Group"]], +"once_cell":[["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug, F> Debug for Lazy<T, F>"],["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug, F> Debug for Lazy<T, F>"],["impl Debug for OnceNonZeroUsize"],["impl Debug for OnceBool"],["impl<T> Debug for OnceBox<T>"]], +"parking_lot":[["impl Debug for WaitTimeoutResult"],["impl Debug for Condvar"],["impl Debug for OnceState"],["impl Debug for Once"]], +"parking_lot_core":[["impl Debug for ParkResult"],["impl Debug for UnparkResult"],["impl Debug for RequeueOp"],["impl Debug for FilterOp"],["impl Debug for UnparkToken"],["impl Debug for ParkToken"]], +"primitive_types":[["impl Debug for Error"],["impl Debug for U128"],["impl Debug for U256"],["impl Debug for U512"],["impl Debug for H128"],["impl Debug for H160"],["impl Debug for H256"],["impl Debug for H384"],["impl Debug for H512"],["impl Debug for H768"]], +"proc_macro2":[["impl Debug for TokenStream"],["impl Debug for LexError"],["impl Debug for Span"],["impl Debug for TokenTree"],["impl Debug for Delimiter"],["impl Debug for Group"],["impl Debug for Spacing"],["impl Debug for Punct"],["impl Debug for Ident"],["impl Debug for Literal"],["impl Debug for IntoIter"]], +"rand":[["impl Debug for Bernoulli"],["impl Debug for BernoulliError"],["impl<D: Debug, R: Debug, T: Debug> Debug for DistIter<D, R, T>"],["impl<D: Debug, F: Debug, T: Debug, S: Debug> Debug for DistMap<D, F, T, S>"],["impl Debug for OpenClosed01"],["impl Debug for Open01"],["impl Debug for Alphanumeric"],["impl<'a, T: Debug> Debug for Slice<'a, T>"],["impl<X: Debug + SampleUniform + PartialOrd> Debug for WeightedIndex<X>where
    X::Sampler: Debug,
"],["impl Debug for WeightedError"],["impl<X: Debug + SampleUniform> Debug for Uniform<X>where
    X::Sampler: Debug,
"],["impl<X: Debug> Debug for UniformInt<X>"],["impl Debug for UniformChar"],["impl<X: Debug> Debug for UniformFloat<X>"],["impl Debug for UniformDuration"],["impl<W: Debug + Weight> Debug for WeightedIndex<W>"],["impl Debug for Standard"],["impl<R: Debug> Debug for ReadRng<R>"],["impl Debug for ReadError"],["impl<R: Debug, Rsdr: Debug> Debug for ReseedingRng<R, Rsdr>where
    R: BlockRngCore + SeedableRng,
    Rsdr: RngCore,
"],["impl Debug for StepRng"],["impl Debug for IndexVec"],["impl<'a> Debug for IndexVecIter<'a>"],["impl Debug for IndexVecIntoIter"],["impl<'a, S: Debug + ?Sized + 'a, T: Debug + 'a> Debug for SliceChooseIter<'a, S, T>"]], +"rand_chacha":[["impl Debug for ChaCha20Core"],["impl Debug for ChaCha20Rng"],["impl Debug for ChaCha12Core"],["impl Debug for ChaCha12Rng"],["impl Debug for ChaCha8Core"],["impl Debug for ChaCha8Rng"]], +"rand_core":[["impl<R: BlockRngCore + Debug> Debug for BlockRng<R>"],["impl<R: BlockRngCore + Debug> Debug for BlockRng64<R>"],["impl Debug for Error"],["impl Debug for OsRng"]], +"regex":[["impl Debug for Error"],["impl Debug for RegexBuilder"],["impl Debug for RegexBuilder"],["impl Debug for RegexSetBuilder"],["impl Debug for RegexSetBuilder"],["impl<'t> Debug for Match<'t>"],["impl Debug for Regex"],["impl<'r, 't> Debug for Matches<'r, 't>"],["impl<'r, 't> Debug for CaptureMatches<'r, 't>"],["impl<'r, 't> Debug for Split<'r, 't>"],["impl<'r, 't> Debug for SplitN<'r, 't>"],["impl<'r> Debug for CaptureNames<'r>"],["impl Debug for CaptureLocations"],["impl<'t> Debug for Captures<'t>"],["impl<'c, 't> Debug for SubCaptureMatches<'c, 't>"],["impl<'a, R: Debug + ?Sized> Debug for ReplacerRef<'a, R>"],["impl<'t> Debug for NoExpand<'t>"],["impl Debug for SetMatches"],["impl Debug for SetMatchesIntoIter"],["impl<'a> Debug for SetMatchesIter<'a>"],["impl Debug for RegexSet"],["impl Debug for SetMatches"],["impl Debug for SetMatchesIntoIter"],["impl<'a> Debug for SetMatchesIter<'a>"],["impl Debug for RegexSet"],["impl<'t> Debug for Match<'t>"],["impl Debug for Regex"],["impl<'r> Debug for CaptureNames<'r>"],["impl<'r, 't> Debug for Split<'r, 't>"],["impl<'r, 't> Debug for SplitN<'r, 't>"],["impl Debug for CaptureLocations"],["impl<'t> Debug for Captures<'t>"],["impl<'c, 't> Debug for SubCaptureMatches<'c, 't>"],["impl<'r, 't> Debug for CaptureMatches<'r, 't>"],["impl<'r, 't> Debug for Matches<'r, 't>"],["impl<'a, R: Debug + ?Sized> Debug for ReplacerRef<'a, R>"],["impl<'t> Debug for NoExpand<'t>"]], +"regex_syntax":[["impl Debug for ParserBuilder"],["impl Debug for Parser"],["impl Debug for Printer"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for Span"],["impl Debug for Position"],["impl Debug for WithComments"],["impl Debug for Comment"],["impl Debug for Ast"],["impl Debug for Alternation"],["impl Debug for Concat"],["impl Debug for Literal"],["impl Debug for LiteralKind"],["impl Debug for SpecialLiteralKind"],["impl Debug for HexLiteralKind"],["impl Debug for Class"],["impl Debug for ClassPerl"],["impl Debug for ClassPerlKind"],["impl Debug for ClassAscii"],["impl Debug for ClassAsciiKind"],["impl Debug for ClassUnicode"],["impl Debug for ClassUnicodeKind"],["impl Debug for ClassUnicodeOpKind"],["impl Debug for ClassBracketed"],["impl Debug for ClassSet"],["impl Debug for ClassSetItem"],["impl Debug for ClassSetRange"],["impl Debug for ClassSetUnion"],["impl Debug for ClassSetBinaryOp"],["impl Debug for ClassSetBinaryOpKind"],["impl Debug for Assertion"],["impl Debug for AssertionKind"],["impl Debug for Repetition"],["impl Debug for RepetitionOp"],["impl Debug for RepetitionKind"],["impl Debug for RepetitionRange"],["impl Debug for Group"],["impl Debug for GroupKind"],["impl Debug for CaptureName"],["impl Debug for SetFlags"],["impl Debug for Flags"],["impl Debug for FlagsItem"],["impl Debug for FlagsItemKind"],["impl Debug for Flag"],["impl Debug for Error"],["impl Debug for Literals"],["impl Debug for Literal"],["impl Debug for Printer"],["impl Debug for TranslatorBuilder"],["impl Debug for Translator"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for Hir"],["impl Debug for HirKind"],["impl Debug for Literal"],["impl Debug for Class"],["impl Debug for ClassUnicode"],["impl<'a> Debug for ClassUnicodeIter<'a>"],["impl Debug for ClassUnicodeRange"],["impl Debug for ClassBytes"],["impl<'a> Debug for ClassBytesIter<'a>"],["impl Debug for ClassBytesRange"],["impl Debug for Anchor"],["impl Debug for WordBoundary"],["impl Debug for Group"],["impl Debug for GroupKind"],["impl Debug for Repetition"],["impl Debug for RepetitionKind"],["impl Debug for RepetitionRange"],["impl Debug for ParserBuilder"],["impl Debug for Parser"],["impl Debug for CaseFoldError"],["impl Debug for UnicodeWordError"],["impl Debug for Utf8Sequence"],["impl Debug for Utf8Range"],["impl Debug for Utf8Sequences"]], +"rlp":[["impl Debug for DecoderError"],["impl Debug for Prototype"],["impl Debug for PayloadInfo"],["impl<'a> Debug for Rlp<'a>"]], +"rustc_hex":[["impl Debug for FromHexError"]], +"scan_fmt":[["impl Debug for ScanError"]], +"scopeguard":[["impl Debug for Always"],["impl<T, F, S> Debug for ScopeGuard<T, F, S>where
    T: Debug,
    F: FnOnce(T),
    S: Strategy,
"]], +"serde":[["impl Debug for Error"],["impl<E> Debug for UnitDeserializer<E>"],["impl<E> Debug for BoolDeserializer<E>"],["impl<E> Debug for I8Deserializer<E>"],["impl<E> Debug for I16Deserializer<E>"],["impl<E> Debug for I32Deserializer<E>"],["impl<E> Debug for I64Deserializer<E>"],["impl<E> Debug for IsizeDeserializer<E>"],["impl<E> Debug for U8Deserializer<E>"],["impl<E> Debug for U16Deserializer<E>"],["impl<E> Debug for U64Deserializer<E>"],["impl<E> Debug for UsizeDeserializer<E>"],["impl<E> Debug for F32Deserializer<E>"],["impl<E> Debug for F64Deserializer<E>"],["impl<E> Debug for CharDeserializer<E>"],["impl<E> Debug for I128Deserializer<E>"],["impl<E> Debug for U128Deserializer<E>"],["impl<E> Debug for U32Deserializer<E>"],["impl<'a, E> Debug for StrDeserializer<'a, E>"],["impl<'de, E> Debug for BorrowedStrDeserializer<'de, E>"],["impl<E> Debug for StringDeserializer<E>"],["impl<'a, E> Debug for CowStrDeserializer<'a, E>"],["impl<'a, E> Debug for BytesDeserializer<'a, E>"],["impl<'de, E> Debug for BorrowedBytesDeserializer<'de, E>"],["impl<I, E> Debug for SeqDeserializer<I, E>where
    I: Debug,
"],["impl<A: Debug> Debug for SeqAccessDeserializer<A>"],["impl<'de, I, E> Debug for MapDeserializer<'de, I, E>where
    I: Iterator + Debug,
    I::Item: Pair,
    <I::Item as Pair>::Second: Debug,
"],["impl<A: Debug> Debug for MapAccessDeserializer<A>"],["impl<A: Debug> Debug for EnumAccessDeserializer<A>"],["impl Debug for IgnoredAny"],["impl<'a> Debug for Unexpected<'a>"]], +"sha3":[["impl Debug for Keccak224Core"],["impl Debug for Keccak256Core"],["impl Debug for Keccak384Core"],["impl Debug for Keccak512Core"],["impl Debug for Keccak256FullCore"],["impl Debug for Sha3_224Core"],["impl Debug for Sha3_256Core"],["impl Debug for Sha3_384Core"],["impl Debug for Sha3_512Core"],["impl Debug for Shake128Core"],["impl Debug for Shake256Core"],["impl Debug for CShake128Core"],["impl Debug for CShake256Core"]], +"shale":[["impl Debug for ShaleError"],["impl Debug for DiskWrite"]], +"slab":[["impl<'a, T: Debug> Debug for VacantEntry<'a, T>"],["impl<T> Debug for Slab<T>where
    T: Debug,
"],["impl<T> Debug for IntoIter<T>where
    T: Debug,
"],["impl<T> Debug for Iter<'_, T>where
    T: Debug,
"],["impl<T> Debug for IterMut<'_, T>where
    T: Debug,
"],["impl<T> Debug for Drain<'_, T>"]], +"smallvec":[["impl Debug for CollectionAllocErr"],["impl<'a, T: 'a + Array> Debug for Drain<'a, T>where
    T::Item: Debug,
"],["impl<A: Array> Debug for SmallVec<A>where
    A::Item: Debug,
"],["impl<A: Array> Debug for IntoIter<A>where
    A::Item: Debug,
"]], +"syn":[["impl Debug for Underscore"],["impl Debug for Abstract"],["impl Debug for As"],["impl Debug for Async"],["impl Debug for Auto"],["impl Debug for Await"],["impl Debug for Become"],["impl Debug for Box"],["impl Debug for Break"],["impl Debug for Const"],["impl Debug for Continue"],["impl Debug for Crate"],["impl Debug for Default"],["impl Debug for Do"],["impl Debug for Dyn"],["impl Debug for Else"],["impl Debug for Enum"],["impl Debug for Extern"],["impl Debug for Final"],["impl Debug for Fn"],["impl Debug for For"],["impl Debug for If"],["impl Debug for Impl"],["impl Debug for In"],["impl Debug for Let"],["impl Debug for Loop"],["impl Debug for Macro"],["impl Debug for Match"],["impl Debug for Mod"],["impl Debug for Move"],["impl Debug for Mut"],["impl Debug for Override"],["impl Debug for Priv"],["impl Debug for Pub"],["impl Debug for Ref"],["impl Debug for Return"],["impl Debug for SelfType"],["impl Debug for SelfValue"],["impl Debug for Static"],["impl Debug for Struct"],["impl Debug for Super"],["impl Debug for Trait"],["impl Debug for Try"],["impl Debug for Type"],["impl Debug for Typeof"],["impl Debug for Union"],["impl Debug for Unsafe"],["impl Debug for Unsized"],["impl Debug for Use"],["impl Debug for Virtual"],["impl Debug for Where"],["impl Debug for While"],["impl Debug for Yield"],["impl Debug for Add"],["impl Debug for AddEq"],["impl Debug for And"],["impl Debug for AndAnd"],["impl Debug for AndEq"],["impl Debug for At"],["impl Debug for Bang"],["impl Debug for Caret"],["impl Debug for CaretEq"],["impl Debug for Colon"],["impl Debug for Colon2"],["impl Debug for Comma"],["impl Debug for Div"],["impl Debug for DivEq"],["impl Debug for Dollar"],["impl Debug for Dot"],["impl Debug for Dot2"],["impl Debug for Dot3"],["impl Debug for DotDotEq"],["impl Debug for Eq"],["impl Debug for EqEq"],["impl Debug for Ge"],["impl Debug for Gt"],["impl Debug for Le"],["impl Debug for Lt"],["impl Debug for MulEq"],["impl Debug for Ne"],["impl Debug for Or"],["impl Debug for OrEq"],["impl Debug for OrOr"],["impl Debug for Pound"],["impl Debug for Question"],["impl Debug for RArrow"],["impl Debug for LArrow"],["impl Debug for Rem"],["impl Debug for RemEq"],["impl Debug for FatArrow"],["impl Debug for Semi"],["impl Debug for Shl"],["impl Debug for ShlEq"],["impl Debug for Shr"],["impl Debug for ShrEq"],["impl Debug for Star"],["impl Debug for Sub"],["impl Debug for SubEq"],["impl Debug for Tilde"],["impl Debug for Brace"],["impl Debug for Bracket"],["impl Debug for Paren"],["impl Debug for Group"],["impl<'a> Debug for ImplGenerics<'a>"],["impl<'a> Debug for TypeGenerics<'a>"],["impl<'a> Debug for Turbofish<'a>"],["impl Debug for LitStr"],["impl Debug for LitByteStr"],["impl Debug for LitByte"],["impl Debug for LitChar"],["impl Debug for LitInt"],["impl Debug for LitFloat"],["impl Debug for LitBool"],["impl<T: Debug, P: Debug> Debug for Punctuated<T, P>"],["impl Debug for Abi"],["impl Debug for AngleBracketedGenericArguments"],["impl Debug for Arm"],["impl Debug for AttrStyle"],["impl Debug for Attribute"],["impl Debug for BareFnArg"],["impl Debug for BinOp"],["impl Debug for Binding"],["impl Debug for Block"],["impl Debug for BoundLifetimes"],["impl Debug for ConstParam"],["impl Debug for Constraint"],["impl Debug for Data"],["impl Debug for DataEnum"],["impl Debug for DataStruct"],["impl Debug for DataUnion"],["impl Debug for DeriveInput"],["impl Debug for Expr"],["impl Debug for ExprArray"],["impl Debug for ExprAssign"],["impl Debug for ExprAssignOp"],["impl Debug for ExprAsync"],["impl Debug for ExprAwait"],["impl Debug for ExprBinary"],["impl Debug for ExprBlock"],["impl Debug for ExprBox"],["impl Debug for ExprBreak"],["impl Debug for ExprCall"],["impl Debug for ExprCast"],["impl Debug for ExprClosure"],["impl Debug for ExprContinue"],["impl Debug for ExprField"],["impl Debug for ExprForLoop"],["impl Debug for ExprGroup"],["impl Debug for ExprIf"],["impl Debug for ExprIndex"],["impl Debug for ExprLet"],["impl Debug for ExprLit"],["impl Debug for ExprLoop"],["impl Debug for ExprMacro"],["impl Debug for ExprMatch"],["impl Debug for ExprMethodCall"],["impl Debug for ExprParen"],["impl Debug for ExprPath"],["impl Debug for ExprRange"],["impl Debug for ExprReference"],["impl Debug for ExprRepeat"],["impl Debug for ExprReturn"],["impl Debug for ExprStruct"],["impl Debug for ExprTry"],["impl Debug for ExprTryBlock"],["impl Debug for ExprTuple"],["impl Debug for ExprType"],["impl Debug for ExprUnary"],["impl Debug for ExprUnsafe"],["impl Debug for ExprWhile"],["impl Debug for ExprYield"],["impl Debug for Field"],["impl Debug for FieldPat"],["impl Debug for FieldValue"],["impl Debug for Fields"],["impl Debug for FieldsNamed"],["impl Debug for FieldsUnnamed"],["impl Debug for File"],["impl Debug for FnArg"],["impl Debug for ForeignItem"],["impl Debug for ForeignItemFn"],["impl Debug for ForeignItemMacro"],["impl Debug for ForeignItemStatic"],["impl Debug for ForeignItemType"],["impl Debug for GenericArgument"],["impl Debug for GenericMethodArgument"],["impl Debug for GenericParam"],["impl Debug for Generics"],["impl Debug for ImplItem"],["impl Debug for ImplItemConst"],["impl Debug for ImplItemMacro"],["impl Debug for ImplItemMethod"],["impl Debug for ImplItemType"],["impl Debug for Index"],["impl Debug for Item"],["impl Debug for ItemConst"],["impl Debug for ItemEnum"],["impl Debug for ItemExternCrate"],["impl Debug for ItemFn"],["impl Debug for ItemForeignMod"],["impl Debug for ItemImpl"],["impl Debug for ItemMacro"],["impl Debug for ItemMacro2"],["impl Debug for ItemMod"],["impl Debug for ItemStatic"],["impl Debug for ItemStruct"],["impl Debug for ItemTrait"],["impl Debug for ItemTraitAlias"],["impl Debug for ItemType"],["impl Debug for ItemUnion"],["impl Debug for ItemUse"],["impl Debug for Label"],["impl Debug for Lifetime"],["impl Debug for LifetimeDef"],["impl Debug for Lit"],["impl Debug for Local"],["impl Debug for Macro"],["impl Debug for MacroDelimiter"],["impl Debug for Member"],["impl Debug for Meta"],["impl Debug for MetaList"],["impl Debug for MetaNameValue"],["impl Debug for MethodTurbofish"],["impl Debug for NestedMeta"],["impl Debug for ParenthesizedGenericArguments"],["impl Debug for Pat"],["impl Debug for PatBox"],["impl Debug for PatIdent"],["impl Debug for PatLit"],["impl Debug for PatMacro"],["impl Debug for PatOr"],["impl Debug for PatPath"],["impl Debug for PatRange"],["impl Debug for PatReference"],["impl Debug for PatRest"],["impl Debug for PatSlice"],["impl Debug for PatStruct"],["impl Debug for PatTuple"],["impl Debug for PatTupleStruct"],["impl Debug for PatType"],["impl Debug for PatWild"],["impl Debug for Path"],["impl Debug for PathArguments"],["impl Debug for PathSegment"],["impl Debug for PredicateEq"],["impl Debug for PredicateLifetime"],["impl Debug for PredicateType"],["impl Debug for QSelf"],["impl Debug for RangeLimits"],["impl Debug for Receiver"],["impl Debug for ReturnType"],["impl Debug for Signature"],["impl Debug for Stmt"],["impl Debug for TraitBound"],["impl Debug for TraitBoundModifier"],["impl Debug for TraitItem"],["impl Debug for TraitItemConst"],["impl Debug for TraitItemMacro"],["impl Debug for TraitItemMethod"],["impl Debug for TraitItemType"],["impl Debug for Type"],["impl Debug for TypeArray"],["impl Debug for TypeBareFn"],["impl Debug for TypeGroup"],["impl Debug for TypeImplTrait"],["impl Debug for TypeInfer"],["impl Debug for TypeMacro"],["impl Debug for TypeNever"],["impl Debug for TypeParam"],["impl Debug for TypeParamBound"],["impl Debug for TypeParen"],["impl Debug for TypePath"],["impl Debug for TypePtr"],["impl Debug for TypeReference"],["impl Debug for TypeSlice"],["impl Debug for TypeTraitObject"],["impl Debug for TypeTuple"],["impl Debug for UnOp"],["impl Debug for UseGlob"],["impl Debug for UseGroup"],["impl Debug for UseName"],["impl Debug for UsePath"],["impl Debug for UseRename"],["impl Debug for UseTree"],["impl Debug for Variadic"],["impl Debug for Variant"],["impl Debug for VisCrate"],["impl Debug for VisPublic"],["impl Debug for VisRestricted"],["impl Debug for Visibility"],["impl Debug for WhereClause"],["impl Debug for WherePredicate"],["impl<'a> Debug for ParseBuffer<'a>"],["impl Debug for Nothing"],["impl Debug for Error"]], +"tokio":[["impl Debug for ReadBuf<'_>"],["impl Debug for JoinError"],["impl Debug for AbortHandle"],["impl<T> Debug for JoinHandle<T>where
    T: Debug,
"],["impl Debug for Builder"],["impl Debug for Handle"],["impl<'a> Debug for EnterGuard<'a>"],["impl Debug for TryCurrentError"],["impl Debug for Runtime"],["impl Debug for RuntimeFlavor"],["impl Debug for Barrier"],["impl Debug for BarrierWaitResult"],["impl<T: Debug> Debug for SendError<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl<T> Debug for Sender<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Sender<T>"],["impl<T> Debug for WeakSender<T>"],["impl<T> Debug for Permit<'_, T>"],["impl<T> Debug for OwnedPermit<T>"],["impl<T> Debug for UnboundedSender<T>"],["impl<T> Debug for UnboundedReceiver<T>"],["impl<T> Debug for WeakUnboundedSender<T>"],["impl<T: Debug> Debug for SendError<T>"],["impl<T: Debug> Debug for TrySendError<T>"],["impl Debug for TryRecvError"],["impl Debug for TryLockError"],["impl<T: ?Sized> Debug for Mutex<T>where
    T: Debug,
"],["impl<T: ?Sized + Debug> Debug for MutexGuard<'_, T>"],["impl<T: ?Sized + Debug> Debug for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized + Debug> Debug for MappedMutexGuard<'a, T>"],["impl Debug for Notify"],["impl<'a> Debug for Notified<'a>"],["impl<T: Debug> Debug for Sender<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl Debug for TryAcquireError"],["impl Debug for AcquireError"],["impl Debug for Semaphore"],["impl<'a> Debug for SemaphorePermit<'a>"],["impl Debug for OwnedSemaphorePermit"],["impl<T: ?Sized, U: ?Sized> Debug for OwnedRwLockReadGuard<T, U>where
    U: Debug,
"],["impl<T: ?Sized> Debug for OwnedRwLockWriteGuard<T>where
    T: Debug,
"],["impl<T: ?Sized, U: ?Sized> Debug for OwnedRwLockMappedWriteGuard<T, U>where
    U: Debug,
"],["impl<'a, T: ?Sized> Debug for RwLockReadGuard<'a, T>where
    T: Debug,
"],["impl<'a, T: ?Sized> Debug for RwLockWriteGuard<'a, T>where
    T: Debug,
"],["impl<'a, T: ?Sized> Debug for RwLockMappedWriteGuard<'a, T>where
    T: Debug,
"],["impl<T: Debug + ?Sized> Debug for RwLock<T>"],["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug> Debug for SetError<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl<T: Debug> Debug for Sender<T>"],["impl<'a, T: Debug> Debug for Ref<'a, T>"],["impl<T: Debug> Debug for SendError<T>"],["impl Debug for RecvError"],["impl Debug for LocalEnterGuard"],["impl Debug for LocalSet"],["impl<T: 'static> Debug for LocalKey<T>"],["impl<T: 'static, F> Debug for TaskLocalFuture<T, F>where
    T: Debug,
"],["impl<T> Debug for JoinSet<T>"]], +"typenum":[["impl Debug for B0"],["impl Debug for B1"],["impl<U: Debug + Unsigned + NonZero> Debug for PInt<U>"],["impl<U: Debug + Unsigned + NonZero> Debug for NInt<U>"],["impl Debug for Z0"],["impl Debug for UTerm"],["impl<U: Debug, B: Debug> Debug for UInt<U, B>"],["impl Debug for ATerm"],["impl<V: Debug, A: Debug> Debug for TArr<V, A>"],["impl Debug for Greater"],["impl Debug for Less"],["impl Debug for Equal"]], +"uint":[["impl Debug for FromStrRadixErrKind"],["impl Debug for FromStrRadixErr"],["impl Debug for FromDecStrErr"],["impl Debug for FromHexError"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Freeze.js b/docs/implementors/core/marker/trait.Freeze.js new file mode 100644 index 000000000000..2c93a4acd3dc --- /dev/null +++ b/docs/implementors/core/marker/trait.Freeze.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl Freeze for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Freeze for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> Freeze for AhoCorasick<S>where
    S: Freeze,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Freeze for FindIter<'a, 'b, S>",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Freeze for FindOverlappingIter<'a, 'b, S>where
    S: Freeze,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Freeze for StreamFindIter<'a, R, S>where
    R: Freeze,
    S: Freeze,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Freeze for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Freeze for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Freeze for Error",1,["aho_corasick::error::Error"]],["impl Freeze for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Freeze for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Freeze for Config",1,["aho_corasick::packed::api::Config"]],["impl Freeze for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Freeze for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Freeze for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Freeze for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl Freeze for Error",1,["aiofut::Error"]],["impl !Freeze for AIO",1,["aiofut::AIO"]],["impl Freeze for AIOFuture",1,["aiofut::AIOFuture"]],["impl !Freeze for AIONotifier",1,["aiofut::AIONotifier"]],["impl Freeze for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !Freeze for AIOManager",1,["aiofut::AIOManager"]],["impl !Freeze for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Freeze for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl Freeze for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Freeze for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Freeze for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Freeze for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Freeze for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Freeze for Config",1,["bincode::config::legacy::Config"]],["impl Freeze for Bounded",1,["bincode::config::limit::Bounded"]],["impl Freeze for Infinite",1,["bincode::config::limit::Infinite"]],["impl Freeze for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Freeze for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Freeze for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Freeze for WithOtherLimit<O, L>where
    L: Freeze,
    O: Freeze,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Freeze for WithOtherEndian<O, E>where
    O: Freeze,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Freeze for WithOtherIntEncoding<O, I>where
    O: Freeze,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Freeze for WithOtherTrailing<O, T>where
    O: Freeze,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Freeze for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Freeze for IoReader<R>where
    R: Freeze,
",1,["bincode::de::read::IoReader"]],["impl<R, O> Freeze for Deserializer<R, O>where
    O: Freeze,
    R: Freeze,
",1,["bincode::de::Deserializer"]],["impl Freeze for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Freeze for Serializer<W, O>where
    O: Freeze,
    W: Freeze,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl Freeze for Eager",1,["block_buffer::Eager"]],["impl Freeze for Lazy",1,["block_buffer::Lazy"]],["impl Freeze for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Freeze for BlockBuffer<BlockSize, Kind>where
    <BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl Freeze for BigEndian",1,["byteorder::BigEndian"]],["impl Freeze for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> Freeze for Chain<T, U>where
    T: Freeze,
    U: Freeze,
",1,["bytes::buf::chain::Chain"]],["impl<T> Freeze for IntoIter<T>where
    T: Freeze,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> Freeze for Limit<T>where
    T: Freeze,
",1,["bytes::buf::limit::Limit"]],["impl<B> Freeze for Reader<B>where
    B: Freeze,
",1,["bytes::buf::reader::Reader"]],["impl<T> Freeze for Take<T>where
    T: Freeze,
",1,["bytes::buf::take::Take"]],["impl Freeze for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Freeze for Writer<B>where
    B: Freeze,
",1,["bytes::buf::writer::Writer"]],["impl !Freeze for Bytes",1,["bytes::bytes::Bytes"]],["impl Freeze for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], +"crc":[["impl<W> Freeze for Crc<W>where
    W: Freeze,
",1,["crc::Crc"]],["impl<'a, W> Freeze for Digest<'a, W>where
    W: Freeze,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> Freeze for Algorithm<W>where
    W: Freeze,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<T> Freeze for Sender<T>",1,["crossbeam_channel::channel::Sender"]],["impl<T> Freeze for Receiver<T>",1,["crossbeam_channel::channel::Receiver"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Freeze for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Freeze for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Freeze for SendError<T>where
    T: Freeze,
",1,["crossbeam_channel::err::SendError"]],["impl<T> Freeze for TrySendError<T>where
    T: Freeze,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Freeze for SendTimeoutError<T>where
    T: Freeze,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Freeze for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Freeze for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Freeze for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Freeze for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Freeze for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Freeze for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Freeze for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> Freeze for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> Freeze for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]]], +"crossbeam_utils":[["impl<T> !Freeze for AtomicCell<T>",1,["crossbeam_utils::atomic::atomic_cell::AtomicCell"]],["impl<T> Freeze for CachePadded<T>where
    T: Freeze,
",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl !Freeze for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl Freeze for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Freeze for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<T> !Freeze for ShardedLock<T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLock"]],["impl<'a, T: ?Sized> Freeze for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> Freeze for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Freeze for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Freeze for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Freeze for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> Freeze for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]]], +"crypto_common":[["impl Freeze for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> Freeze for CtVariableCoreWrapper<T, OutSize, O>where
    T: Freeze,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Freeze for RtVariableCoreWrapper<T>where
    T: Freeze,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Freeze for CoreWrapper<T>where
    T: Freeze,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Freeze for XofReaderCoreWrapper<T>where
    T: Freeze,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Freeze for TruncSide",1,["digest::core_api::TruncSide"]],["impl Freeze for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Freeze for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl Freeze for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Freeze for WALConfig",1,["firewood::storage::WALConfig"]],["impl Freeze for DBError",1,["firewood::db::DBError"]],["impl Freeze for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Freeze for DBConfig",1,["firewood::db::DBConfig"]],["impl Freeze for DBRev",1,["firewood::db::DBRev"]],["impl !Freeze for DB",1,["firewood::db::DB"]],["impl<'a> Freeze for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> Freeze for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Freeze for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Freeze for Hash",1,["firewood::merkle::Hash"]],["impl Freeze for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !Freeze for Node",1,["firewood::merkle::Node"]],["impl Freeze for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> Freeze for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> Freeze for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Freeze for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Freeze for Proof",1,["firewood::proof::Proof"]],["impl Freeze for ProofError",1,["firewood::proof::ProofError"]],["impl Freeze for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> Freeze for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> Freeze for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Freeze for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> Freeze for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Freeze for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Freeze for TrySendError<T>where
    T: Freeze,
",1,["futures_channel::mpsc::TrySendError"]],["impl Freeze for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Freeze for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> Freeze for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Freeze for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl Freeze for Canceled",1,["futures_channel::oneshot::Canceled"]]], +"futures_executor":[["impl !Freeze for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl Freeze for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Freeze for BlockingStream<S>where
    S: Freeze,
",1,["futures_executor::local_pool::BlockingStream"]],["impl Freeze for Enter",1,["futures_executor::enter::Enter"]],["impl Freeze for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl Freeze for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Freeze for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> Freeze for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> Freeze for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], +"futures_util":[["impl<Fut> Freeze for Fuse<Fut>where
    Fut: Freeze,
",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Freeze for CatchUnwind<Fut>where
    Fut: Freeze,
",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Freeze for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Freeze for Remote<Fut>where
    Fut: Freeze,
",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Freeze for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Freeze for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Freeze for Flatten<F>where
    F: Freeze,
    <F as Future>::Output: Freeze,
",1,["futures_util::future::future::Flatten"]],["impl<F> Freeze for FlattenStream<F>where
    F: Freeze,
    <F as Future>::Output: Freeze,
",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Freeze for Map<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::future::Map"]],["impl<F> Freeze for IntoStream<F>where
    F: Freeze,
",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Freeze for MapInto<Fut, T>where
    Fut: Freeze,
",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Freeze for Then<Fut1, Fut2, F>where
    F: Freeze,
    Fut1: Freeze,
    Fut2: Freeze,
",1,["futures_util::future::future::Then"]],["impl<Fut, F> Freeze for Inspect<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::future::Inspect"]],["impl<Fut> Freeze for NeverError<Fut>where
    Fut: Freeze,
",1,["futures_util::future::future::NeverError"]],["impl<Fut> Freeze for UnitError<Fut>where
    Fut: Freeze,
",1,["futures_util::future::future::UnitError"]],["impl<Fut> Freeze for IntoFuture<Fut>where
    Fut: Freeze,
",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Freeze for TryFlatten<Fut1, Fut2>where
    Fut1: Freeze,
    Fut2: Freeze,
",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Freeze for TryFlattenStream<Fut>where
    Fut: Freeze,
    <Fut as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Freeze for FlattenSink<Fut, Si>where
    Fut: Freeze,
    Si: Freeze,
",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Freeze for AndThen<Fut1, Fut2, F>where
    F: Freeze,
    Fut1: Freeze,
    Fut2: Freeze,
",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Freeze for OrElse<Fut1, Fut2, F>where
    F: Freeze,
    Fut1: Freeze,
    Fut2: Freeze,
",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Freeze for ErrInto<Fut, E>where
    Fut: Freeze,
",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Freeze for OkInto<Fut, E>where
    Fut: Freeze,
",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Freeze for InspectOk<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Freeze for InspectErr<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Freeze for MapOk<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Freeze for MapErr<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Freeze for MapOkOrElse<Fut, F, G>where
    F: Freeze,
    Fut: Freeze,
    G: Freeze,
",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Freeze for UnwrapOrElse<Fut, F>where
    F: Freeze,
    Fut: Freeze,
",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Freeze for Lazy<F>where
    F: Freeze,
",1,["futures_util::future::lazy::Lazy"]],["impl<T> Freeze for Pending<T>",1,["futures_util::future::pending::Pending"]],["impl<Fut> Freeze for MaybeDone<Fut>where
    Fut: Freeze,
    <Fut as Future>::Output: Freeze,
",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Freeze for TryMaybeDone<Fut>where
    Fut: Freeze,
    <Fut as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Freeze for OptionFuture<F>where
    F: Freeze,
",1,["futures_util::future::option::OptionFuture"]],["impl<F> Freeze for PollFn<F>where
    F: Freeze,
",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Freeze for PollImmediate<T>where
    T: Freeze,
",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Freeze for Ready<T>where
    T: Freeze,
",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Freeze for Join<Fut1, Fut2>where
    Fut1: Freeze,
    Fut2: Freeze,
    <Fut1 as Future>::Output: Freeze,
    <Fut2 as Future>::Output: Freeze,
",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Freeze for Join3<Fut1, Fut2, Fut3>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    <Fut1 as Future>::Output: Freeze,
    <Fut2 as Future>::Output: Freeze,
    <Fut3 as Future>::Output: Freeze,
",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Freeze for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    Fut4: Freeze,
    <Fut1 as Future>::Output: Freeze,
    <Fut2 as Future>::Output: Freeze,
    <Fut3 as Future>::Output: Freeze,
    <Fut4 as Future>::Output: Freeze,
",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Freeze for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    Fut4: Freeze,
    Fut5: Freeze,
    <Fut1 as Future>::Output: Freeze,
    <Fut2 as Future>::Output: Freeze,
    <Fut3 as Future>::Output: Freeze,
    <Fut4 as Future>::Output: Freeze,
    <Fut5 as Future>::Output: Freeze,
",1,["futures_util::future::join::Join5"]],["impl<F> !Freeze for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Freeze for Select<A, B>where
    A: Freeze,
    B: Freeze,
",1,["futures_util::future::select::Select"]],["impl<Fut> Freeze for SelectAll<Fut>",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Freeze for TryJoin<Fut1, Fut2>where
    Fut1: Freeze,
    Fut2: Freeze,
    <Fut1 as TryFuture>::Ok: Freeze,
    <Fut2 as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Freeze for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    <Fut1 as TryFuture>::Ok: Freeze,
    <Fut2 as TryFuture>::Ok: Freeze,
    <Fut3 as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Freeze for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    Fut4: Freeze,
    <Fut1 as TryFuture>::Ok: Freeze,
    <Fut2 as TryFuture>::Ok: Freeze,
    <Fut3 as TryFuture>::Ok: Freeze,
    <Fut4 as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Freeze for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Freeze,
    Fut2: Freeze,
    Fut3: Freeze,
    Fut4: Freeze,
    Fut5: Freeze,
    <Fut1 as TryFuture>::Ok: Freeze,
    <Fut2 as TryFuture>::Ok: Freeze,
    <Fut3 as TryFuture>::Ok: Freeze,
    <Fut4 as TryFuture>::Ok: Freeze,
    <Fut5 as TryFuture>::Ok: Freeze,
",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !Freeze for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Freeze for TrySelect<A, B>where
    A: Freeze,
    B: Freeze,
",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Freeze for SelectOk<Fut>",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Freeze for Either<A, B>where
    A: Freeze,
    B: Freeze,
",1,["futures_util::future::either::Either"]],["impl Freeze for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Freeze for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Freeze for Abortable<T>where
    T: Freeze,
",1,["futures_util::abortable::Abortable"]],["impl Freeze for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Freeze for Chain<St1, St2>where
    St1: Freeze,
    St2: Freeze,
",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Freeze for Collect<St, C>where
    C: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Freeze for Unzip<St, FromA, FromB>where
    FromA: Freeze,
    FromB: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Freeze for Concat<St>where
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Freeze for Cycle<St>where
    St: Freeze,
",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Freeze for Enumerate<St>where
    St: Freeze,
",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Freeze for Filter<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Freeze for FilterMap<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Freeze for Fold<St, Fut, T, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    T: Freeze,
",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Freeze for ForEach<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Freeze for Fuse<St>where
    St: Freeze,
",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Freeze for StreamFuture<St>where
    St: Freeze,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Freeze for Map<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Freeze for Next<'a, St>",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Freeze for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Freeze for Peekable<St>where
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Freeze for Peek<'a, St>",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Freeze for PeekMut<'a, St>",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Freeze for NextIf<'a, St, F>where
    F: Freeze,
",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Freeze for NextIfEq<'a, St, T>",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Freeze for Skip<St>where
    St: Freeze,
",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Freeze for SkipWhile<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Freeze for Take<St>where
    St: Freeze,
",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Freeze for TakeWhile<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Freeze for TakeUntil<St, Fut>where
    Fut: Freeze,
    St: Freeze,
    <Fut as Future>::Output: Freeze,
",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Freeze for Then<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Freeze for Zip<St1, St2>where
    St1: Freeze,
    St2: Freeze,
    <St1 as Stream>::Item: Freeze,
    <St2 as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Freeze for Chunks<St>where
    St: Freeze,
",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Freeze for ReadyChunks<St>where
    St: Freeze,
",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Freeze for Scan<St, S, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    S: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !Freeze for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !Freeze for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !Freeze for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Freeze for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Freeze for SplitSink<S, Item>where
    Item: Freeze,
",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Freeze for ReuniteError<T, Item>where
    Item: Freeze,
",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Freeze for CatchUnwind<St>where
    St: Freeze,
",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Freeze for Flatten<St>where
    St: Freeze,
    <St as Stream>::Item: Freeze,
",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Freeze for Forward<St, Si>where
    Si: Freeze,
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Freeze for Inspect<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Freeze for FlatMap<St, U, F>where
    F: Freeze,
    St: Freeze,
    U: Freeze,
",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Freeze for AndThen<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Freeze for IntoStream<St>where
    St: Freeze,
",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Freeze for OrElse<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Freeze for TryNext<'a, St>",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Freeze for TryForEach<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Freeze for TryFilter<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Freeze for TryFilterMap<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Freeze for TryFlatten<St>where
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Freeze for TryCollect<St, C>where
    C: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Freeze for TryConcat<St>where
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Freeze for TryChunks<St>where
    St: Freeze,
",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Freeze for TryChunksError<T, E>where
    E: Freeze,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Freeze for TryFold<St, Fut, T, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    T: Freeze,
",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Freeze for TryUnfold<T, F, Fut>where
    F: Freeze,
    Fut: Freeze,
    T: Freeze,
",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Freeze for TrySkipWhile<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Freeze for TryTakeWhile<St, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !Freeze for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !Freeze for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !Freeze for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Freeze for IntoAsyncRead<St>where
    St: Freeze,
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Freeze for ErrInto<St, E>where
    St: Freeze,
",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Freeze for InspectOk<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Freeze for InspectErr<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Freeze for MapOk<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Freeze for MapErr<St, F>where
    F: Freeze,
    St: Freeze,
",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Freeze for Iter<I>where
    I: Freeze,
",1,["futures_util::stream::iter::Iter"]],["impl<T> Freeze for Repeat<T>where
    T: Freeze,
",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Freeze for RepeatWith<F>where
    F: Freeze,
",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Freeze for Empty<T>",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Freeze for Once<Fut>where
    Fut: Freeze,
",1,["futures_util::stream::once::Once"]],["impl<T> Freeze for Pending<T>",1,["futures_util::stream::pending::Pending"]],["impl<F> Freeze for PollFn<F>where
    F: Freeze,
",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Freeze for PollImmediate<S>where
    S: Freeze,
",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Freeze for Select<St1, St2>where
    St1: Freeze,
    St2: Freeze,
",1,["futures_util::stream::select::Select"]],["impl Freeze for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Freeze for SelectWithStrategy<St1, St2, Clos, State>where
    Clos: Freeze,
    St1: Freeze,
    St2: Freeze,
    State: Freeze,
",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Freeze for Unfold<T, F, Fut>where
    F: Freeze,
    Fut: Freeze,
    T: Freeze,
",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !Freeze for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Freeze for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> Freeze for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Freeze for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> Freeze for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !Freeze for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !Freeze for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !Freeze for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Freeze for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Freeze for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !Freeze for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Freeze for Close<'a, Si, Item>",1,["futures_util::sink::close::Close"]],["impl<T> Freeze for Drain<T>",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Freeze for Fanout<Si1, Si2>where
    Si1: Freeze,
    Si2: Freeze,
",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Freeze for Feed<'a, Si, Item>where
    Item: Freeze,
",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Freeze for Flush<'a, Si, Item>",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Freeze for SinkErrInto<Si, Item, E>where
    Si: Freeze,
",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Freeze for SinkMapErr<Si, F>where
    F: Freeze,
    Si: Freeze,
",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Freeze for Send<'a, Si, Item>where
    Item: Freeze,
",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Freeze for SendAll<'a, Si, St>where
    <St as TryStream>::Ok: Freeze,
",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Freeze for Unfold<T, F, R>where
    F: Freeze,
    R: Freeze,
    T: Freeze,
",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Freeze for With<Si, Item, U, Fut, F>where
    F: Freeze,
    Fut: Freeze,
    Si: Freeze,
",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Freeze for WithFlatMap<Si, Item, U, St, F>where
    F: Freeze,
    Item: Freeze,
    Si: Freeze,
    St: Freeze,
",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Freeze for Buffer<Si, Item>where
    Si: Freeze,
",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Freeze for AllowStdIo<T>where
    T: Freeze,
",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Freeze for BufReader<R>where
    R: Freeze,
",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Freeze for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Freeze for BufWriter<W>where
    W: Freeze,
",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Freeze for LineWriter<W>where
    W: Freeze,
",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Freeze for Chain<T, U>where
    T: Freeze,
    U: Freeze,
",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Freeze for Close<'a, W>",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Freeze for Copy<'a, R, W>where
    R: Freeze,
",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Freeze for CopyBuf<'a, R, W>where
    R: Freeze,
",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Freeze for CopyBufAbortable<'a, R, W>where
    R: Freeze,
",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Freeze for Cursor<T>where
    T: Freeze,
",1,["futures_util::io::cursor::Cursor"]],["impl Freeze for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Freeze for FillBuf<'a, R>",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Freeze for Flush<'a, W>",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Freeze for IntoSink<W, Item>where
    Item: Freeze,
    W: Freeze,
",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Freeze for Lines<R>where
    R: Freeze,
",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Freeze for Read<'a, R>",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Freeze for ReadVectored<'a, R>",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Freeze for ReadExact<'a, R>",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Freeze for ReadLine<'a, R>",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Freeze for ReadToEnd<'a, R>",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Freeze for ReadToString<'a, R>",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Freeze for ReadUntil<'a, R>",1,["futures_util::io::read_until::ReadUntil"]],["impl Freeze for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Freeze for Seek<'a, S>",1,["futures_util::io::seek::Seek"]],["impl Freeze for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Freeze for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> Freeze for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> Freeze for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> Freeze for Take<R>where
    R: Freeze,
",1,["futures_util::io::take::Take"]],["impl<T> Freeze for Window<T>where
    T: Freeze,
",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Freeze for Write<'a, W>",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Freeze for WriteVectored<'a, W>",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Freeze for WriteAll<'a, W>",1,["futures_util::io::write_all::WriteAll"]],["impl<T> !Freeze for Mutex<T>",1,["futures_util::lock::mutex::Mutex"]],["impl<T: ?Sized> Freeze for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T: ?Sized> Freeze for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Freeze for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T: ?Sized> Freeze for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T: ?Sized, U: ?Sized> Freeze for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], +"generic_array":[["impl<T, N> Freeze for GenericArrayIter<T, N>where
    <N as ArrayLength<T>>::ArrayType: Freeze,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> Freeze for GenericArray<T, U>where
    <U as ArrayLength<T>>::ArrayType: Freeze,
",1,["generic_array::GenericArray"]]], +"getrandom":[["impl Freeze for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl Freeze for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !Freeze for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl Freeze for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Freeze for WALLoader",1,["growthring::wal::WALLoader"]],["impl Freeze for WALFileAIO",1,["growthring::WALFileAIO"]],["impl Freeze for WALStoreAIO",1,["growthring::WALStoreAIO"]]], +"hashbrown":[["impl<K, V, S, A> Freeze for HashMap<K, V, S, A>where
    A: Freeze,
    S: Freeze,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Freeze for Iter<'a, K, V>",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Freeze for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Freeze for IntoIter<K, V, A>where
    A: Freeze,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Freeze for IntoKeys<K, V, A>where
    A: Freeze,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Freeze for IntoValues<K, V, A>where
    A: Freeze,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Freeze for Keys<'a, K, V>",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Freeze for Values<'a, K, V>",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Freeze for Drain<'a, K, V, A>where
    A: Freeze,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Freeze for DrainFilter<'a, K, V, F, A>where
    F: Freeze,
",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Freeze for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryBuilder<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Freeze for Entry<'a, K, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Freeze for OccupiedEntry<'a, K, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> Freeze for VacantEntry<'a, K, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for EntryRef<'a, 'b, K, Q, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Freeze,
",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Freeze for OccupiedError<'a, K, V, S, A>where
    K: Freeze,
    V: Freeze,
",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Freeze for HashSet<T, S, A>where
    A: Freeze,
    S: Freeze,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> Freeze for Iter<'a, K>",1,["hashbrown::set::Iter"]],["impl<K, A> Freeze for IntoIter<K, A>where
    A: Freeze,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Freeze for Drain<'a, K, A>where
    A: Freeze,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Freeze for DrainFilter<'a, K, F, A>where
    F: Freeze,
",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Freeze for Intersection<'a, T, S, A>",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Freeze for Difference<'a, T, S, A>",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Freeze for SymmetricDifference<'a, T, S, A>",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Freeze for Union<'a, T, S, A>",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Freeze for Entry<'a, T, S, A>where
    T: Freeze,
",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Freeze for OccupiedEntry<'a, T, S, A>where
    T: Freeze,
",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Freeze for VacantEntry<'a, T, S, A>where
    T: Freeze,
",1,["hashbrown::set::VacantEntry"]],["impl Freeze for TryReserveError",1,["hashbrown::TryReserveError"]]], +"heck":[["impl<T> Freeze for AsKebabCase<T>where
    T: Freeze,
",1,["heck::kebab::AsKebabCase"]],["impl<T> Freeze for AsLowerCamelCase<T>where
    T: Freeze,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Freeze for AsShoutyKebabCase<T>where
    T: Freeze,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Freeze for AsShoutySnakeCase<T>where
    T: Freeze,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Freeze for AsSnakeCase<T>where
    T: Freeze,
",1,["heck::snake::AsSnakeCase"]],["impl<T> Freeze for AsTitleCase<T>where
    T: Freeze,
",1,["heck::title::AsTitleCase"]],["impl<T> Freeze for AsUpperCamelCase<T>where
    T: Freeze,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl Freeze for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl Freeze for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Freeze for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Freeze for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Freeze for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Freeze for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Freeze for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Freeze for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Freeze for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl Freeze for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Freeze for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Freeze for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Freeze for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Freeze for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Freeze for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Freeze for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Freeze for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Freeze for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Freeze for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl Freeze for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl Freeze for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Freeze for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Freeze for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Freeze for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Freeze for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Freeze for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl Freeze for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Freeze for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Freeze for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Freeze for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Freeze for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Freeze for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Freeze for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Freeze for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl Freeze for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Freeze for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Freeze for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl Freeze for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl Freeze for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Freeze for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Freeze for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Freeze for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Freeze for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Freeze for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Freeze for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Freeze for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl Freeze for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Freeze for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Freeze for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl Freeze for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Freeze for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Freeze for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Freeze for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Freeze for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Freeze for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Freeze for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Freeze for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Freeze for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Freeze for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Freeze for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl Freeze for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl Freeze for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl Freeze for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Freeze for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Freeze for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Freeze for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Freeze for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Freeze for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Freeze for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl Freeze for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Freeze for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Freeze for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Freeze for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Freeze for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Freeze for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Freeze for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Freeze for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Freeze for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Freeze for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Freeze for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Freeze for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Freeze for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Freeze for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl Freeze for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Freeze for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Freeze for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Freeze for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Freeze for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Freeze for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl Freeze for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Freeze for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Freeze for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Freeze for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Freeze for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Freeze for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Freeze for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Freeze for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Freeze for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Freeze for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl Freeze for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl Freeze for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Freeze for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Freeze for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Freeze for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Freeze for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Freeze for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Freeze for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Freeze for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Freeze for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Freeze for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Freeze for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Freeze for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Freeze for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Freeze for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Freeze for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl Freeze for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Freeze for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Freeze for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Freeze for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Freeze for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Freeze for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Freeze for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Freeze for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Freeze for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Freeze for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Freeze for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Freeze for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Freeze for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Freeze for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Freeze for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Freeze for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl Freeze for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl Freeze for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Freeze for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Freeze for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Freeze for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Freeze for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Freeze for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Freeze for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Freeze for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Freeze for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Freeze for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Freeze for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Freeze for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Freeze for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Freeze for timezone",1,["libc::unix::linux_like::timezone"]],["impl Freeze for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Freeze for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Freeze for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Freeze for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Freeze for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Freeze for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Freeze for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl Freeze for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Freeze for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Freeze for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl Freeze for tm",1,["libc::unix::linux_like::tm"]],["impl Freeze for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl Freeze for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl Freeze for lconv",1,["libc::unix::linux_like::lconv"]],["impl Freeze for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl Freeze for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Freeze for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Freeze for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Freeze for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Freeze for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl Freeze for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Freeze for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Freeze for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Freeze for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Freeze for utsname",1,["libc::unix::linux_like::utsname"]],["impl Freeze for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Freeze for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Freeze for DIR",1,["libc::unix::DIR"]],["impl Freeze for group",1,["libc::unix::group"]],["impl Freeze for utimbuf",1,["libc::unix::utimbuf"]],["impl Freeze for timeval",1,["libc::unix::timeval"]],["impl Freeze for timespec",1,["libc::unix::timespec"]],["impl Freeze for rlimit",1,["libc::unix::rlimit"]],["impl Freeze for rusage",1,["libc::unix::rusage"]],["impl Freeze for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl Freeze for hostent",1,["libc::unix::hostent"]],["impl Freeze for iovec",1,["libc::unix::iovec"]],["impl Freeze for pollfd",1,["libc::unix::pollfd"]],["impl Freeze for winsize",1,["libc::unix::winsize"]],["impl Freeze for linger",1,["libc::unix::linger"]],["impl Freeze for sigval",1,["libc::unix::sigval"]],["impl Freeze for itimerval",1,["libc::unix::itimerval"]],["impl Freeze for tms",1,["libc::unix::tms"]],["impl Freeze for servent",1,["libc::unix::servent"]],["impl Freeze for protoent",1,["libc::unix::protoent"]],["impl Freeze for FILE",1,["libc::unix::FILE"]],["impl Freeze for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<R, T> !Freeze for Mutex<R, T>",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T: ?Sized> Freeze for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> !Freeze for RawReentrantMutex<R, G>",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T> !Freeze for ReentrantMutex<R, G, T>",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T: ?Sized> Freeze for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T: ?Sized> Freeze for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T> !Freeze for RwLock<R, T>",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T: ?Sized> Freeze for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Freeze for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedRwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl Freeze for GuardSend",1,["lock_api::GuardSend"]],["impl Freeze for GuardNoSend",1,["lock_api::GuardNoSend"]]], +"lru":[["impl<K, V, S> Freeze for LruCache<K, V, S>where
    S: Freeze,
",1,["lru::LruCache"]],["impl<'a, K, V> Freeze for Iter<'a, K, V>",1,["lru::Iter"]],["impl<'a, K, V> Freeze for IterMut<'a, K, V>",1,["lru::IterMut"]],["impl<K, V> Freeze for IntoIter<K, V>",1,["lru::IntoIter"]]], +"memchr":[["impl<'a> Freeze for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Freeze for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Freeze for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Freeze for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Freeze for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Freeze for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Freeze for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Freeze for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Freeze for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl Freeze for Dir",1,["nix::dir::Dir"]],["impl<'d> Freeze for Iter<'d>",1,["nix::dir::Iter"]],["impl Freeze for OwningIter",1,["nix::dir::OwningIter"]],["impl Freeze for Entry",1,["nix::dir::Entry"]],["impl Freeze for Type",1,["nix::dir::Type"]],["impl Freeze for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Freeze for Errno",1,["nix::errno::consts::Errno"]],["impl Freeze for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Freeze for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Freeze for OFlag",1,["nix::fcntl::OFlag"]],["impl Freeze for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Freeze for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Freeze for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Freeze for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Freeze for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Freeze for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Freeze for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Freeze for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl Freeze for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl Freeze for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl Freeze for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> Freeze for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Freeze for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Freeze for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Freeze for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Freeze for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Freeze for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Freeze for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Freeze for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Freeze for MqdT",1,["nix::mqueue::MqdT"]],["impl Freeze for PollFd",1,["nix::poll::PollFd"]],["impl Freeze for PollFlags",1,["nix::poll::PollFlags"]],["impl Freeze for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Freeze for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Freeze for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Freeze for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Freeze for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Freeze for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Freeze for LioMode",1,["nix::sys::aio::LioMode"]],["impl Freeze for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Freeze for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Freeze for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Freeze for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Freeze for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Freeze for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Freeze for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Freeze for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Freeze for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Freeze for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Freeze for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Freeze for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Freeze for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Freeze for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Freeze for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Freeze for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Freeze for Persona",1,["nix::sys::personality::Persona"]],["impl Freeze for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Freeze for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Freeze for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Freeze for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Freeze for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Freeze for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Freeze for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Freeze for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Freeze for Resource",1,["nix::sys::resource::Resource"]],["impl Freeze for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Freeze for Usage",1,["nix::sys::resource::Usage"]],["impl Freeze for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Freeze for Fds<'a>",1,["nix::sys::select::Fds"]],["impl Freeze for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Freeze for Signal",1,["nix::sys::signal::Signal"]],["impl Freeze for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Freeze for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Freeze for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Freeze for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Freeze for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Freeze for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Freeze for SigAction",1,["nix::sys::signal::SigAction"]],["impl Freeze for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Freeze for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Freeze for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Freeze for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Freeze for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Freeze for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Freeze for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Freeze for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Freeze for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Freeze for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Freeze for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Freeze for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Freeze for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Freeze for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Freeze for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Freeze for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Freeze for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Freeze for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Freeze for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Freeze for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Freeze for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Freeze for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Freeze for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Freeze for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Freeze for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Freeze for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Freeze for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Freeze for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Freeze for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Freeze for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Freeze for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Freeze for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Freeze for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Freeze for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Freeze for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Freeze for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Freeze for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Freeze for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Freeze for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Freeze for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Freeze for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Freeze for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Freeze for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Freeze for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Freeze for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Freeze for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Freeze for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Freeze for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Freeze for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Freeze for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Freeze for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Freeze for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Freeze for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Freeze for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Freeze for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Freeze for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Freeze for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Freeze for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Freeze for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Freeze for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Freeze for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Freeze for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Freeze for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Freeze for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Freeze for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Freeze for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Freeze for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Freeze for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Freeze for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Freeze for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Freeze for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Freeze for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Freeze for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Freeze for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Freeze for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Freeze for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Freeze for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Freeze for AlgSetKey<T>",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Freeze for SockType",1,["nix::sys::socket::SockType"]],["impl Freeze for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Freeze for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Freeze for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Freeze for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Freeze for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Freeze for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Freeze for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> Freeze for RecvMsg<'a, 's, S>where
    S: Freeze,
",1,["nix::sys::socket::RecvMsg"]],["impl<'a> Freeze for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Freeze for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Freeze for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Freeze for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> Freeze for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> Freeze for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Freeze for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Freeze for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Freeze for SFlag",1,["nix::sys::stat::SFlag"]],["impl Freeze for Mode",1,["nix::sys::stat::Mode"]],["impl Freeze for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Freeze for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Freeze for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Freeze for FsType",1,["nix::sys::statfs::FsType"]],["impl Freeze for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Freeze for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Freeze for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !Freeze for Termios",1,["nix::sys::termios::Termios"]],["impl Freeze for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Freeze for SetArg",1,["nix::sys::termios::SetArg"]],["impl Freeze for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Freeze for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Freeze for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Freeze for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Freeze for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Freeze for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Freeze for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Freeze for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Freeze for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Freeze for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Freeze for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Freeze for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> Freeze for IoVec<T>",1,["nix::sys::uio::IoVec"]],["impl Freeze for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Freeze for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Freeze for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Freeze for Id",1,["nix::sys::wait::Id"]],["impl Freeze for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Freeze for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Freeze for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Freeze for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Freeze for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Freeze for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Freeze for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Freeze for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl Freeze for Timer",1,["nix::sys::timer::Timer"]],["impl Freeze for ClockId",1,["nix::time::ClockId"]],["impl Freeze for UContext",1,["nix::ucontext::UContext"]],["impl Freeze for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Freeze for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Freeze for Uid",1,["nix::unistd::Uid"]],["impl Freeze for Gid",1,["nix::unistd::Gid"]],["impl Freeze for Pid",1,["nix::unistd::Pid"]],["impl Freeze for ForkResult",1,["nix::unistd::ForkResult"]],["impl Freeze for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Freeze for Whence",1,["nix::unistd::Whence"]],["impl Freeze for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Freeze for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Freeze for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Freeze for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Freeze for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Freeze for User",1,["nix::unistd::User"]],["impl Freeze for Group",1,["nix::unistd::Group"]]], +"once_cell":[["impl<T> !Freeze for OnceCell<T>",1,["once_cell::unsync::OnceCell"]],["impl<T, F = fn() -> T> !Freeze for Lazy<T, F>",1,["once_cell::unsync::Lazy"]],["impl<T> !Freeze for OnceCell<T>",1,["once_cell::sync::OnceCell"]],["impl<T, F = fn() -> T> !Freeze for Lazy<T, F>",1,["once_cell::sync::Lazy"]],["impl<T> !Freeze for OnceBox<T>",1,["once_cell::race::once_box::OnceBox"]],["impl !Freeze for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl !Freeze for OnceBool",1,["once_cell::race::OnceBool"]]], +"parking_lot":[["impl Freeze for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl !Freeze for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Freeze for OnceState",1,["parking_lot::once::OnceState"]],["impl !Freeze for Once",1,["parking_lot::once::Once"]],["impl !Freeze for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl !Freeze for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl !Freeze for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Freeze for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl Freeze for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Freeze for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Freeze for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Freeze for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Freeze for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Freeze for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Freeze for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl Freeze for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Freeze for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Freeze for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Freeze for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Freeze for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Freeze for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Freeze for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Freeze for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Freeze for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Freeze for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Freeze for SseMachine<S3, S4, NI>",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Freeze for Avx2Machine<NI>",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Freeze for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Freeze for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Freeze for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl Freeze for Error",1,["primitive_types::Error"]],["impl Freeze for U128",1,["primitive_types::U128"]],["impl Freeze for U256",1,["primitive_types::U256"]],["impl Freeze for U512",1,["primitive_types::U512"]],["impl Freeze for H128",1,["primitive_types::H128"]],["impl Freeze for H160",1,["primitive_types::H160"]],["impl Freeze for H256",1,["primitive_types::H256"]],["impl Freeze for H384",1,["primitive_types::H384"]],["impl Freeze for H512",1,["primitive_types::H512"]],["impl Freeze for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl Freeze for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl Freeze for TokenStream",1,["proc_macro2::TokenStream"]],["impl Freeze for LexError",1,["proc_macro2::LexError"]],["impl Freeze for Span",1,["proc_macro2::Span"]],["impl Freeze for TokenTree",1,["proc_macro2::TokenTree"]],["impl Freeze for Group",1,["proc_macro2::Group"]],["impl Freeze for Delimiter",1,["proc_macro2::Delimiter"]],["impl Freeze for Punct",1,["proc_macro2::Punct"]],["impl Freeze for Spacing",1,["proc_macro2::Spacing"]],["impl Freeze for Ident",1,["proc_macro2::Ident"]],["impl Freeze for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl Freeze for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Freeze for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Freeze for DistIter<D, R, T>where
    D: Freeze,
    R: Freeze,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Freeze for DistMap<D, F, T, S>where
    D: Freeze,
    F: Freeze,
",1,["rand::distributions::distribution::DistMap"]],["impl Freeze for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Freeze for Open01",1,["rand::distributions::float::Open01"]],["impl Freeze for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Freeze for Slice<'a, T>",1,["rand::distributions::slice::Slice"]],["impl<X> Freeze for WeightedIndex<X>where
    X: Freeze,
    <X as SampleUniform>::Sampler: Freeze,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Freeze for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Freeze for Uniform<X>where
    <X as SampleUniform>::Sampler: Freeze,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> Freeze for UniformInt<X>where
    X: Freeze,
",1,["rand::distributions::uniform::UniformInt"]],["impl Freeze for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Freeze for UniformFloat<X>where
    X: Freeze,
",1,["rand::distributions::uniform::UniformFloat"]],["impl Freeze for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Freeze for WeightedIndex<W>",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Freeze for Standard",1,["rand::distributions::Standard"]],["impl<R> Freeze for ReadRng<R>where
    R: Freeze,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl Freeze for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Freeze for ReseedingRng<R, Rsdr>where
    R: Freeze,
    Rsdr: Freeze,
    <R as BlockRngCore>::Results: Freeze,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Freeze for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Freeze for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Freeze for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Freeze for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Freeze for SliceChooseIter<'a, S, T>",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl Freeze for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Freeze for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Freeze for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Freeze for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Freeze for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Freeze for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> Freeze for BlockRng<R>where
    R: Freeze,
    <R as BlockRngCore>::Results: Freeze,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Freeze for BlockRng64<R>where
    R: Freeze,
    <R as BlockRngCore>::Results: Freeze,
",1,["rand_core::block::BlockRng64"]],["impl Freeze for Error",1,["rand_core::error::Error"]],["impl Freeze for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl Freeze for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Freeze for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Freeze for Match<'t>",1,["regex::re_bytes::Match"]],["impl Freeze for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Freeze for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Freeze for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Freeze for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Freeze for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Freeze for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Freeze for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Freeze for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Freeze for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Freeze for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Freeze for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Freeze for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Freeze for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Freeze for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Freeze for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Freeze for Error",1,["regex::error::Error"]],["impl Freeze for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Freeze for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Freeze for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Freeze for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Freeze for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Freeze for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Freeze for Match<'t>",1,["regex::re_unicode::Match"]],["impl Freeze for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Freeze for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Freeze for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Freeze for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Freeze for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Freeze for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Freeze for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Freeze for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Freeze for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Freeze for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Freeze for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl Freeze for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !Freeze for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Freeze for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Freeze for Error",1,["regex_syntax::ast::Error"]],["impl Freeze for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Freeze for Span",1,["regex_syntax::ast::Span"]],["impl Freeze for Position",1,["regex_syntax::ast::Position"]],["impl Freeze for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Freeze for Comment",1,["regex_syntax::ast::Comment"]],["impl Freeze for Ast",1,["regex_syntax::ast::Ast"]],["impl Freeze for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Freeze for Concat",1,["regex_syntax::ast::Concat"]],["impl Freeze for Literal",1,["regex_syntax::ast::Literal"]],["impl Freeze for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Freeze for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Freeze for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Freeze for Class",1,["regex_syntax::ast::Class"]],["impl Freeze for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Freeze for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Freeze for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Freeze for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Freeze for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Freeze for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Freeze for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Freeze for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Freeze for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Freeze for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Freeze for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Freeze for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Freeze for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Freeze for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Freeze for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Freeze for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Freeze for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Freeze for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Freeze for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Freeze for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Freeze for Group",1,["regex_syntax::ast::Group"]],["impl Freeze for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Freeze for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Freeze for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Freeze for Flags",1,["regex_syntax::ast::Flags"]],["impl Freeze for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Freeze for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Freeze for Flag",1,["regex_syntax::ast::Flag"]],["impl Freeze for Error",1,["regex_syntax::error::Error"]],["impl Freeze for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Freeze for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Freeze for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Freeze for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !Freeze for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Freeze for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Freeze for Error",1,["regex_syntax::hir::Error"]],["impl Freeze for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Freeze for Hir",1,["regex_syntax::hir::Hir"]],["impl Freeze for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Freeze for Literal",1,["regex_syntax::hir::Literal"]],["impl Freeze for Class",1,["regex_syntax::hir::Class"]],["impl Freeze for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Freeze for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Freeze for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Freeze for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Freeze for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Freeze for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Freeze for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Freeze for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Freeze for Group",1,["regex_syntax::hir::Group"]],["impl Freeze for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Freeze for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Freeze for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Freeze for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Freeze for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !Freeze for Parser",1,["regex_syntax::parser::Parser"]],["impl Freeze for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Freeze for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Freeze for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Freeze for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl Freeze for DecoderError",1,["rlp::error::DecoderError"]],["impl Freeze for Prototype",1,["rlp::rlpin::Prototype"]],["impl Freeze for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !Freeze for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> Freeze for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Freeze for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> Freeze for ToHexIter<T>where
    T: Freeze,
",1,["rustc_hex::ToHexIter"]],["impl Freeze for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Freeze for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl Freeze for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl Freeze for Always",1,["scopeguard::Always"]],["impl<T, F, S> Freeze for ScopeGuard<T, F, S>where
    F: Freeze,
    T: Freeze,
",1,["scopeguard::ScopeGuard"]]], +"serde":[["impl Freeze for Error",1,["serde::de::value::Error"]],["impl<E> Freeze for UnitDeserializer<E>",1,["serde::de::value::UnitDeserializer"]],["impl<E> Freeze for BoolDeserializer<E>",1,["serde::de::value::BoolDeserializer"]],["impl<E> Freeze for I8Deserializer<E>",1,["serde::de::value::I8Deserializer"]],["impl<E> Freeze for I16Deserializer<E>",1,["serde::de::value::I16Deserializer"]],["impl<E> Freeze for I32Deserializer<E>",1,["serde::de::value::I32Deserializer"]],["impl<E> Freeze for I64Deserializer<E>",1,["serde::de::value::I64Deserializer"]],["impl<E> Freeze for IsizeDeserializer<E>",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Freeze for U8Deserializer<E>",1,["serde::de::value::U8Deserializer"]],["impl<E> Freeze for U16Deserializer<E>",1,["serde::de::value::U16Deserializer"]],["impl<E> Freeze for U64Deserializer<E>",1,["serde::de::value::U64Deserializer"]],["impl<E> Freeze for UsizeDeserializer<E>",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Freeze for F32Deserializer<E>",1,["serde::de::value::F32Deserializer"]],["impl<E> Freeze for F64Deserializer<E>",1,["serde::de::value::F64Deserializer"]],["impl<E> Freeze for CharDeserializer<E>",1,["serde::de::value::CharDeserializer"]],["impl<E> Freeze for I128Deserializer<E>",1,["serde::de::value::I128Deserializer"]],["impl<E> Freeze for U128Deserializer<E>",1,["serde::de::value::U128Deserializer"]],["impl<E> Freeze for U32Deserializer<E>",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Freeze for StrDeserializer<'a, E>",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Freeze for BorrowedStrDeserializer<'de, E>",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Freeze for StringDeserializer<E>",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Freeze for CowStrDeserializer<'a, E>",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Freeze for BytesDeserializer<'a, E>",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Freeze for BorrowedBytesDeserializer<'de, E>",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Freeze for SeqDeserializer<I, E>where
    I: Freeze,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> Freeze for SeqAccessDeserializer<A>where
    A: Freeze,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Freeze for MapDeserializer<'de, I, E>where
    I: Freeze,
    <<I as Iterator>::Item as Pair>::Second: Freeze,
",1,["serde::de::value::MapDeserializer"]],["impl<A> Freeze for MapAccessDeserializer<A>where
    A: Freeze,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Freeze for EnumAccessDeserializer<A>where
    A: Freeze,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl Freeze for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Freeze for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Freeze for Impossible<Ok, Error>",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl Freeze for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Freeze for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Freeze for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Freeze for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Freeze for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Freeze for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Freeze for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Freeze for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Freeze for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Freeze for Shake128Core",1,["sha3::Shake128Core"]],["impl Freeze for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Freeze for Shake256Core",1,["sha3::Shake256Core"]],["impl Freeze for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Freeze for CShake128Core",1,["sha3::CShake128Core"]],["impl Freeze for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Freeze for CShake256Core",1,["sha3::CShake256Core"]],["impl Freeze for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl Freeze for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Freeze for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Freeze for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Freeze for ShaleError",1,["shale::ShaleError"]],["impl Freeze for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Freeze for ObjPtr<T>",1,["shale::ObjPtr"]],["impl<T: ?Sized> Freeze for Obj<T>",1,["shale::Obj"]],["impl<'a, T> Freeze for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> Freeze for MummyObj<T>where
    T: Freeze,
",1,["shale::MummyObj"]],["impl Freeze for PlainMem",1,["shale::PlainMem"]],["impl<T: ?Sized> Freeze for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> Freeze for Slab<T>",1,["slab::Slab"]],["impl<'a, T> Freeze for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> Freeze for IntoIter<T>",1,["slab::IntoIter"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["slab::Iter"]],["impl<'a, T> Freeze for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> Freeze for Drain<'a, T>",1,["slab::Drain"]]], +"smallvec":[["impl Freeze for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> Freeze for Drain<'a, T>",1,["smallvec::Drain"]],["impl<A> Freeze for SmallVec<A>where
    A: Freeze,
",1,["smallvec::SmallVec"]],["impl<A> Freeze for IntoIter<A>where
    A: Freeze,
",1,["smallvec::IntoIter"]]], +"syn":[["impl Freeze for Underscore",1,["syn::token::Underscore"]],["impl Freeze for Abstract",1,["syn::token::Abstract"]],["impl Freeze for As",1,["syn::token::As"]],["impl Freeze for Async",1,["syn::token::Async"]],["impl Freeze for Auto",1,["syn::token::Auto"]],["impl Freeze for Await",1,["syn::token::Await"]],["impl Freeze for Become",1,["syn::token::Become"]],["impl Freeze for Box",1,["syn::token::Box"]],["impl Freeze for Break",1,["syn::token::Break"]],["impl Freeze for Const",1,["syn::token::Const"]],["impl Freeze for Continue",1,["syn::token::Continue"]],["impl Freeze for Crate",1,["syn::token::Crate"]],["impl Freeze for Default",1,["syn::token::Default"]],["impl Freeze for Do",1,["syn::token::Do"]],["impl Freeze for Dyn",1,["syn::token::Dyn"]],["impl Freeze for Else",1,["syn::token::Else"]],["impl Freeze for Enum",1,["syn::token::Enum"]],["impl Freeze for Extern",1,["syn::token::Extern"]],["impl Freeze for Final",1,["syn::token::Final"]],["impl Freeze for Fn",1,["syn::token::Fn"]],["impl Freeze for For",1,["syn::token::For"]],["impl Freeze for If",1,["syn::token::If"]],["impl Freeze for Impl",1,["syn::token::Impl"]],["impl Freeze for In",1,["syn::token::In"]],["impl Freeze for Let",1,["syn::token::Let"]],["impl Freeze for Loop",1,["syn::token::Loop"]],["impl Freeze for Macro",1,["syn::token::Macro"]],["impl Freeze for Match",1,["syn::token::Match"]],["impl Freeze for Mod",1,["syn::token::Mod"]],["impl Freeze for Move",1,["syn::token::Move"]],["impl Freeze for Mut",1,["syn::token::Mut"]],["impl Freeze for Override",1,["syn::token::Override"]],["impl Freeze for Priv",1,["syn::token::Priv"]],["impl Freeze for Pub",1,["syn::token::Pub"]],["impl Freeze for Ref",1,["syn::token::Ref"]],["impl Freeze for Return",1,["syn::token::Return"]],["impl Freeze for SelfType",1,["syn::token::SelfType"]],["impl Freeze for SelfValue",1,["syn::token::SelfValue"]],["impl Freeze for Static",1,["syn::token::Static"]],["impl Freeze for Struct",1,["syn::token::Struct"]],["impl Freeze for Super",1,["syn::token::Super"]],["impl Freeze for Trait",1,["syn::token::Trait"]],["impl Freeze for Try",1,["syn::token::Try"]],["impl Freeze for Type",1,["syn::token::Type"]],["impl Freeze for Typeof",1,["syn::token::Typeof"]],["impl Freeze for Union",1,["syn::token::Union"]],["impl Freeze for Unsafe",1,["syn::token::Unsafe"]],["impl Freeze for Unsized",1,["syn::token::Unsized"]],["impl Freeze for Use",1,["syn::token::Use"]],["impl Freeze for Virtual",1,["syn::token::Virtual"]],["impl Freeze for Where",1,["syn::token::Where"]],["impl Freeze for While",1,["syn::token::While"]],["impl Freeze for Yield",1,["syn::token::Yield"]],["impl Freeze for Add",1,["syn::token::Add"]],["impl Freeze for AddEq",1,["syn::token::AddEq"]],["impl Freeze for And",1,["syn::token::And"]],["impl Freeze for AndAnd",1,["syn::token::AndAnd"]],["impl Freeze for AndEq",1,["syn::token::AndEq"]],["impl Freeze for At",1,["syn::token::At"]],["impl Freeze for Bang",1,["syn::token::Bang"]],["impl Freeze for Caret",1,["syn::token::Caret"]],["impl Freeze for CaretEq",1,["syn::token::CaretEq"]],["impl Freeze for Colon",1,["syn::token::Colon"]],["impl Freeze for Colon2",1,["syn::token::Colon2"]],["impl Freeze for Comma",1,["syn::token::Comma"]],["impl Freeze for Div",1,["syn::token::Div"]],["impl Freeze for DivEq",1,["syn::token::DivEq"]],["impl Freeze for Dollar",1,["syn::token::Dollar"]],["impl Freeze for Dot",1,["syn::token::Dot"]],["impl Freeze for Dot2",1,["syn::token::Dot2"]],["impl Freeze for Dot3",1,["syn::token::Dot3"]],["impl Freeze for DotDotEq",1,["syn::token::DotDotEq"]],["impl Freeze for Eq",1,["syn::token::Eq"]],["impl Freeze for EqEq",1,["syn::token::EqEq"]],["impl Freeze for Ge",1,["syn::token::Ge"]],["impl Freeze for Gt",1,["syn::token::Gt"]],["impl Freeze for Le",1,["syn::token::Le"]],["impl Freeze for Lt",1,["syn::token::Lt"]],["impl Freeze for MulEq",1,["syn::token::MulEq"]],["impl Freeze for Ne",1,["syn::token::Ne"]],["impl Freeze for Or",1,["syn::token::Or"]],["impl Freeze for OrEq",1,["syn::token::OrEq"]],["impl Freeze for OrOr",1,["syn::token::OrOr"]],["impl Freeze for Pound",1,["syn::token::Pound"]],["impl Freeze for Question",1,["syn::token::Question"]],["impl Freeze for RArrow",1,["syn::token::RArrow"]],["impl Freeze for LArrow",1,["syn::token::LArrow"]],["impl Freeze for Rem",1,["syn::token::Rem"]],["impl Freeze for RemEq",1,["syn::token::RemEq"]],["impl Freeze for FatArrow",1,["syn::token::FatArrow"]],["impl Freeze for Semi",1,["syn::token::Semi"]],["impl Freeze for Shl",1,["syn::token::Shl"]],["impl Freeze for ShlEq",1,["syn::token::ShlEq"]],["impl Freeze for Shr",1,["syn::token::Shr"]],["impl Freeze for ShrEq",1,["syn::token::ShrEq"]],["impl Freeze for Star",1,["syn::token::Star"]],["impl Freeze for Sub",1,["syn::token::Sub"]],["impl Freeze for SubEq",1,["syn::token::SubEq"]],["impl Freeze for Tilde",1,["syn::token::Tilde"]],["impl Freeze for Brace",1,["syn::token::Brace"]],["impl Freeze for Bracket",1,["syn::token::Bracket"]],["impl Freeze for Paren",1,["syn::token::Paren"]],["impl Freeze for Group",1,["syn::token::Group"]],["impl Freeze for Attribute",1,["syn::attr::Attribute"]],["impl Freeze for AttrStyle",1,["syn::attr::AttrStyle"]],["impl Freeze for Meta",1,["syn::attr::Meta"]],["impl Freeze for MetaList",1,["syn::attr::MetaList"]],["impl Freeze for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl Freeze for NestedMeta",1,["syn::attr::NestedMeta"]],["impl Freeze for Variant",1,["syn::data::Variant"]],["impl Freeze for Fields",1,["syn::data::Fields"]],["impl Freeze for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl Freeze for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl Freeze for Field",1,["syn::data::Field"]],["impl Freeze for Visibility",1,["syn::data::Visibility"]],["impl Freeze for VisPublic",1,["syn::data::VisPublic"]],["impl Freeze for VisCrate",1,["syn::data::VisCrate"]],["impl Freeze for VisRestricted",1,["syn::data::VisRestricted"]],["impl Freeze for Expr",1,["syn::expr::Expr"]],["impl Freeze for ExprArray",1,["syn::expr::ExprArray"]],["impl Freeze for ExprAssign",1,["syn::expr::ExprAssign"]],["impl Freeze for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl Freeze for ExprAsync",1,["syn::expr::ExprAsync"]],["impl Freeze for ExprAwait",1,["syn::expr::ExprAwait"]],["impl Freeze for ExprBinary",1,["syn::expr::ExprBinary"]],["impl Freeze for ExprBlock",1,["syn::expr::ExprBlock"]],["impl Freeze for ExprBox",1,["syn::expr::ExprBox"]],["impl Freeze for ExprBreak",1,["syn::expr::ExprBreak"]],["impl Freeze for ExprCall",1,["syn::expr::ExprCall"]],["impl Freeze for ExprCast",1,["syn::expr::ExprCast"]],["impl Freeze for ExprClosure",1,["syn::expr::ExprClosure"]],["impl Freeze for ExprContinue",1,["syn::expr::ExprContinue"]],["impl Freeze for ExprField",1,["syn::expr::ExprField"]],["impl Freeze for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl Freeze for ExprGroup",1,["syn::expr::ExprGroup"]],["impl Freeze for ExprIf",1,["syn::expr::ExprIf"]],["impl Freeze for ExprIndex",1,["syn::expr::ExprIndex"]],["impl Freeze for ExprLet",1,["syn::expr::ExprLet"]],["impl Freeze for ExprLit",1,["syn::expr::ExprLit"]],["impl Freeze for ExprLoop",1,["syn::expr::ExprLoop"]],["impl Freeze for ExprMacro",1,["syn::expr::ExprMacro"]],["impl Freeze for ExprMatch",1,["syn::expr::ExprMatch"]],["impl Freeze for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl Freeze for ExprParen",1,["syn::expr::ExprParen"]],["impl Freeze for ExprPath",1,["syn::expr::ExprPath"]],["impl Freeze for ExprRange",1,["syn::expr::ExprRange"]],["impl Freeze for ExprReference",1,["syn::expr::ExprReference"]],["impl Freeze for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl Freeze for ExprReturn",1,["syn::expr::ExprReturn"]],["impl Freeze for ExprStruct",1,["syn::expr::ExprStruct"]],["impl Freeze for ExprTry",1,["syn::expr::ExprTry"]],["impl Freeze for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl Freeze for ExprTuple",1,["syn::expr::ExprTuple"]],["impl Freeze for ExprType",1,["syn::expr::ExprType"]],["impl Freeze for ExprUnary",1,["syn::expr::ExprUnary"]],["impl Freeze for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl Freeze for ExprWhile",1,["syn::expr::ExprWhile"]],["impl Freeze for ExprYield",1,["syn::expr::ExprYield"]],["impl Freeze for Member",1,["syn::expr::Member"]],["impl Freeze for Index",1,["syn::expr::Index"]],["impl Freeze for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl Freeze for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl Freeze for FieldValue",1,["syn::expr::FieldValue"]],["impl Freeze for Label",1,["syn::expr::Label"]],["impl Freeze for Arm",1,["syn::expr::Arm"]],["impl Freeze for RangeLimits",1,["syn::expr::RangeLimits"]],["impl Freeze for Generics",1,["syn::generics::Generics"]],["impl Freeze for GenericParam",1,["syn::generics::GenericParam"]],["impl Freeze for TypeParam",1,["syn::generics::TypeParam"]],["impl Freeze for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl Freeze for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> Freeze for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> Freeze for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> Freeze for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl Freeze for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl Freeze for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl Freeze for TraitBound",1,["syn::generics::TraitBound"]],["impl Freeze for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl Freeze for WhereClause",1,["syn::generics::WhereClause"]],["impl Freeze for WherePredicate",1,["syn::generics::WherePredicate"]],["impl Freeze for PredicateType",1,["syn::generics::PredicateType"]],["impl Freeze for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl Freeze for PredicateEq",1,["syn::generics::PredicateEq"]],["impl Freeze for Item",1,["syn::item::Item"]],["impl Freeze for ItemConst",1,["syn::item::ItemConst"]],["impl Freeze for ItemEnum",1,["syn::item::ItemEnum"]],["impl Freeze for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl Freeze for ItemFn",1,["syn::item::ItemFn"]],["impl Freeze for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl Freeze for ItemImpl",1,["syn::item::ItemImpl"]],["impl Freeze for ItemMacro",1,["syn::item::ItemMacro"]],["impl Freeze for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl Freeze for ItemMod",1,["syn::item::ItemMod"]],["impl Freeze for ItemStatic",1,["syn::item::ItemStatic"]],["impl Freeze for ItemStruct",1,["syn::item::ItemStruct"]],["impl Freeze for ItemTrait",1,["syn::item::ItemTrait"]],["impl Freeze for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl Freeze for ItemType",1,["syn::item::ItemType"]],["impl Freeze for ItemUnion",1,["syn::item::ItemUnion"]],["impl Freeze for ItemUse",1,["syn::item::ItemUse"]],["impl Freeze for UseTree",1,["syn::item::UseTree"]],["impl Freeze for UsePath",1,["syn::item::UsePath"]],["impl Freeze for UseName",1,["syn::item::UseName"]],["impl Freeze for UseRename",1,["syn::item::UseRename"]],["impl Freeze for UseGlob",1,["syn::item::UseGlob"]],["impl Freeze for UseGroup",1,["syn::item::UseGroup"]],["impl Freeze for ForeignItem",1,["syn::item::ForeignItem"]],["impl Freeze for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl Freeze for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl Freeze for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl Freeze for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl Freeze for TraitItem",1,["syn::item::TraitItem"]],["impl Freeze for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl Freeze for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl Freeze for TraitItemType",1,["syn::item::TraitItemType"]],["impl Freeze for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl Freeze for ImplItem",1,["syn::item::ImplItem"]],["impl Freeze for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl Freeze for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl Freeze for ImplItemType",1,["syn::item::ImplItemType"]],["impl Freeze for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl Freeze for Signature",1,["syn::item::Signature"]],["impl Freeze for FnArg",1,["syn::item::FnArg"]],["impl Freeze for Receiver",1,["syn::item::Receiver"]],["impl Freeze for File",1,["syn::file::File"]],["impl Freeze for Lifetime",1,["syn::lifetime::Lifetime"]],["impl Freeze for Lit",1,["syn::lit::Lit"]],["impl Freeze for LitStr",1,["syn::lit::LitStr"]],["impl Freeze for LitByteStr",1,["syn::lit::LitByteStr"]],["impl Freeze for LitByte",1,["syn::lit::LitByte"]],["impl Freeze for LitChar",1,["syn::lit::LitChar"]],["impl Freeze for LitInt",1,["syn::lit::LitInt"]],["impl Freeze for LitFloat",1,["syn::lit::LitFloat"]],["impl Freeze for LitBool",1,["syn::lit::LitBool"]],["impl Freeze for StrStyle",1,["syn::lit::StrStyle"]],["impl Freeze for Macro",1,["syn::mac::Macro"]],["impl Freeze for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl Freeze for DeriveInput",1,["syn::derive::DeriveInput"]],["impl Freeze for Data",1,["syn::derive::Data"]],["impl Freeze for DataStruct",1,["syn::derive::DataStruct"]],["impl Freeze for DataEnum",1,["syn::derive::DataEnum"]],["impl Freeze for DataUnion",1,["syn::derive::DataUnion"]],["impl Freeze for BinOp",1,["syn::op::BinOp"]],["impl Freeze for UnOp",1,["syn::op::UnOp"]],["impl Freeze for Block",1,["syn::stmt::Block"]],["impl Freeze for Stmt",1,["syn::stmt::Stmt"]],["impl Freeze for Local",1,["syn::stmt::Local"]],["impl Freeze for Type",1,["syn::ty::Type"]],["impl Freeze for TypeArray",1,["syn::ty::TypeArray"]],["impl Freeze for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl Freeze for TypeGroup",1,["syn::ty::TypeGroup"]],["impl Freeze for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl Freeze for TypeInfer",1,["syn::ty::TypeInfer"]],["impl Freeze for TypeMacro",1,["syn::ty::TypeMacro"]],["impl Freeze for TypeNever",1,["syn::ty::TypeNever"]],["impl Freeze for TypeParen",1,["syn::ty::TypeParen"]],["impl Freeze for TypePath",1,["syn::ty::TypePath"]],["impl Freeze for TypePtr",1,["syn::ty::TypePtr"]],["impl Freeze for TypeReference",1,["syn::ty::TypeReference"]],["impl Freeze for TypeSlice",1,["syn::ty::TypeSlice"]],["impl Freeze for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl Freeze for TypeTuple",1,["syn::ty::TypeTuple"]],["impl Freeze for Abi",1,["syn::ty::Abi"]],["impl Freeze for BareFnArg",1,["syn::ty::BareFnArg"]],["impl Freeze for Variadic",1,["syn::ty::Variadic"]],["impl Freeze for ReturnType",1,["syn::ty::ReturnType"]],["impl Freeze for Pat",1,["syn::pat::Pat"]],["impl Freeze for PatBox",1,["syn::pat::PatBox"]],["impl Freeze for PatIdent",1,["syn::pat::PatIdent"]],["impl Freeze for PatLit",1,["syn::pat::PatLit"]],["impl Freeze for PatMacro",1,["syn::pat::PatMacro"]],["impl Freeze for PatOr",1,["syn::pat::PatOr"]],["impl Freeze for PatPath",1,["syn::pat::PatPath"]],["impl Freeze for PatRange",1,["syn::pat::PatRange"]],["impl Freeze for PatReference",1,["syn::pat::PatReference"]],["impl Freeze for PatRest",1,["syn::pat::PatRest"]],["impl Freeze for PatSlice",1,["syn::pat::PatSlice"]],["impl Freeze for PatStruct",1,["syn::pat::PatStruct"]],["impl Freeze for PatTuple",1,["syn::pat::PatTuple"]],["impl Freeze for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl Freeze for PatType",1,["syn::pat::PatType"]],["impl Freeze for PatWild",1,["syn::pat::PatWild"]],["impl Freeze for FieldPat",1,["syn::pat::FieldPat"]],["impl Freeze for Path",1,["syn::path::Path"]],["impl Freeze for PathSegment",1,["syn::path::PathSegment"]],["impl Freeze for PathArguments",1,["syn::path::PathArguments"]],["impl Freeze for GenericArgument",1,["syn::path::GenericArgument"]],["impl Freeze for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl Freeze for Binding",1,["syn::path::Binding"]],["impl Freeze for Constraint",1,["syn::path::Constraint"]],["impl Freeze for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl Freeze for QSelf",1,["syn::path::QSelf"]],["impl Freeze for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> Freeze for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Freeze for Punctuated<T, P>",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Freeze for Pairs<'a, T, P>",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Freeze for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> Freeze for IntoPairs<T, P>where
    T: Freeze,
",1,["syn::punctuated::IntoPairs"]],["impl<T> Freeze for IntoIter<T>",1,["syn::punctuated::IntoIter"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> Freeze for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Freeze for Pair<T, P>where
    P: Freeze,
    T: Freeze,
",1,["syn::punctuated::Pair"]],["impl<'a> !Freeze for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Freeze for Error",1,["syn::error::Error"]],["impl<'a> !Freeze for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> Freeze for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Freeze for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> Freeze for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Freeze for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Freeze for AbortHandle",1,["tokio::runtime::task::abort::AbortHandle"]],["impl<T> Freeze for JoinHandle<T>",1,["tokio::runtime::task::join::JoinHandle"]],["impl !Freeze for Builder",1,["tokio::runtime::builder::Builder"]],["impl Freeze for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Freeze for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Freeze for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !Freeze for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Freeze for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !Freeze for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !Freeze for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Freeze for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Freeze for SendError<T>where
    T: Freeze,
",1,["tokio::sync::broadcast::error::SendError"]],["impl Freeze for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Freeze for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Freeze for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Freeze for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Freeze for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Freeze for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Freeze for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Freeze for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Freeze for SendError<T>where
    T: Freeze,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Freeze for TrySendError<T>where
    T: Freeze,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Freeze for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T> !Freeze for Mutex<T>",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T: ?Sized> Freeze for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Freeze for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Freeze for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl Freeze for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl !Freeze for Notify",1,["tokio::sync::notify::Notify"]],["impl Freeze for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Freeze for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl Freeze for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Freeze for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl !Freeze for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Freeze for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Freeze for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T: ?Sized, U: ?Sized> Freeze for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T: ?Sized> Freeze for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T: ?Sized, U: ?Sized> Freeze for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T> !Freeze for RwLock<T>",1,["tokio::sync::rwlock::RwLock"]],["impl<T> !Freeze for OnceCell<T>",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> Freeze for SetError<T>where
    T: Freeze,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> Freeze for SendError<T>where
    T: Freeze,
",1,["tokio::sync::watch::error::SendError"]],["impl Freeze for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Freeze for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl !Freeze for LocalSet",1,["tokio::task::local::LocalSet"]],["impl Freeze for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Freeze for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Freeze for TaskLocalFuture<T, F>where
    F: Freeze,
    T: Freeze,
",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Freeze for Unconstrained<F>where
    F: Freeze,
",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Freeze for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]]], +"typenum":[["impl Freeze for B0",1,["typenum::bit::B0"]],["impl Freeze for B1",1,["typenum::bit::B1"]],["impl<U> Freeze for PInt<U>where
    U: Freeze,
",1,["typenum::int::PInt"]],["impl<U> Freeze for NInt<U>where
    U: Freeze,
",1,["typenum::int::NInt"]],["impl Freeze for Z0",1,["typenum::int::Z0"]],["impl Freeze for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Freeze for UInt<U, B>where
    B: Freeze,
    U: Freeze,
",1,["typenum::uint::UInt"]],["impl Freeze for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Freeze for TArr<V, A>where
    A: Freeze,
    V: Freeze,
",1,["typenum::array::TArr"]],["impl Freeze for Greater",1,["typenum::Greater"]],["impl Freeze for Less",1,["typenum::Less"]],["impl Freeze for Equal",1,["typenum::Equal"]]], +"uint":[["impl Freeze for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Freeze for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Freeze for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Freeze for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Send.js b/docs/implementors/core/marker/trait.Send.js new file mode 100644 index 000000000000..222916262f8b --- /dev/null +++ b/docs/implementors/core/marker/trait.Send.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl Send for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Send for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> Send for AhoCorasick<S>where
    S: Send,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Send for FindIter<'a, 'b, S>where
    S: Sync,
",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Send for FindOverlappingIter<'a, 'b, S>where
    S: Send + Sync,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Send for StreamFindIter<'a, R, S>where
    R: Send,
    S: Send + Sync,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Send for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Send for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Send for Error",1,["aho_corasick::error::Error"]],["impl Send for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Send for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Send for Config",1,["aho_corasick::packed::api::Config"]],["impl Send for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Send for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Send for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Send for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl Send for Error",1,["aiofut::Error"]],["impl Send for AIO",1,["aiofut::AIO"]],["impl Send for AIOFuture",1,["aiofut::AIOFuture"]],["impl Send for AIONotifier",1,["aiofut::AIONotifier"]],["impl Send for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl Send for AIOManager",1,["aiofut::AIOManager"]],["impl Send for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Send for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl Send for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Send for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Send for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Send for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Send for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Send for Config",1,["bincode::config::legacy::Config"]],["impl Send for Bounded",1,["bincode::config::limit::Bounded"]],["impl Send for Infinite",1,["bincode::config::limit::Infinite"]],["impl Send for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Send for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Send for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Send for WithOtherLimit<O, L>where
    L: Send,
    O: Send,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Send for WithOtherEndian<O, E>where
    E: Send,
    O: Send,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Send for WithOtherIntEncoding<O, I>where
    I: Send,
    O: Send,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Send for WithOtherTrailing<O, T>where
    O: Send,
    T: Send,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Send for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Send for IoReader<R>where
    R: Send,
",1,["bincode::de::read::IoReader"]],["impl<R, O> Send for Deserializer<R, O>where
    O: Send,
    R: Send,
",1,["bincode::de::Deserializer"]],["impl Send for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Send for Serializer<W, O>where
    O: Send,
    W: Send,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl Send for Eager",1,["block_buffer::Eager"]],["impl Send for Lazy",1,["block_buffer::Lazy"]],["impl Send for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Send for BlockBuffer<BlockSize, Kind>where
    Kind: Send,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl Send for BigEndian",1,["byteorder::BigEndian"]],["impl Send for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> Send for Chain<T, U>where
    T: Send,
    U: Send,
",1,["bytes::buf::chain::Chain"]],["impl<T> Send for IntoIter<T>where
    T: Send,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> Send for Limit<T>where
    T: Send,
",1,["bytes::buf::limit::Limit"]],["impl<B> Send for Reader<B>where
    B: Send,
",1,["bytes::buf::reader::Reader"]],["impl<T> Send for Take<T>where
    T: Send,
",1,["bytes::buf::take::Take"]],["impl Send for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Send for Writer<B>where
    B: Send,
",1,["bytes::buf::writer::Writer"]],["impl Send for Bytes"],["impl Send for BytesMut"]], +"crc":[["impl<W> Send for Crc<W>where
    W: Send + Sync,
",1,["crc::Crc"]],["impl<'a, W> Send for Digest<'a, W>where
    W: Send + Sync,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> Send for Algorithm<W>where
    W: Send,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<'a, T> Send for Iter<'a, T>where
    T: Send,
",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Send for TryIter<'a, T>where
    T: Send,
",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Send for IntoIter<T>where
    T: Send,
",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Send for SendError<T>where
    T: Send,
",1,["crossbeam_channel::err::SendError"]],["impl<T> Send for TrySendError<T>where
    T: Send,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Send for SendTimeoutError<T>where
    T: Send,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Send for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Send for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Send for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Send for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Send for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Send for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Send for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !Send for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T: Send> Send for Sender<T>"],["impl<T: Send> Send for Receiver<T>"],["impl Send for Select<'_>"]], +"crossbeam_utils":[["impl Send for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl<'a, T> !Send for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T> !Send for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Send for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Send for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Send for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<T: Send> Send for AtomicCell<T>"],["impl<T: Send> Send for CachePadded<T>"],["impl Send for Parker"],["impl Send for Unparker"],["impl<T: ?Sized + Send> Send for ShardedLock<T>"],["impl<T> Send for ScopedJoinHandle<'_, T>"]], +"crypto_common":[["impl Send for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> Send for CtVariableCoreWrapper<T, OutSize, O>where
    O: Send,
    OutSize: Send,
    T: Send,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Send for RtVariableCoreWrapper<T>where
    T: Send,
    <T as BufferKindUser>::BufferKind: Send,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Send for CoreWrapper<T>where
    T: Send,
    <T as BufferKindUser>::BufferKind: Send,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Send for XofReaderCoreWrapper<T>where
    T: Send,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Send for TruncSide",1,["digest::core_api::TruncSide"]],["impl Send for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Send for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl Send for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Send for WALConfig",1,["firewood::storage::WALConfig"]],["impl Send for DBError",1,["firewood::db::DBError"]],["impl Send for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Send for DBConfig",1,["firewood::db::DBConfig"]],["impl !Send for DBRev",1,["firewood::db::DBRev"]],["impl !Send for DB",1,["firewood::db::DB"]],["impl<'a> !Send for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !Send for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Send for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Send for Hash",1,["firewood::merkle::Hash"]],["impl Send for PartialPath",1,["firewood::merkle::PartialPath"]],["impl Send for Node",1,["firewood::merkle::Node"]],["impl !Send for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !Send for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !Send for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Send for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Send for Proof",1,["firewood::proof::Proof"]],["impl Send for ProofError",1,["firewood::proof::ProofError"]],["impl Send for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> Send for Sender<T>where
    T: Send,
",1,["futures_channel::mpsc::Sender"]],["impl<T> Send for UnboundedSender<T>where
    T: Send,
",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Send for Receiver<T>where
    T: Send,
",1,["futures_channel::mpsc::Receiver"]],["impl<T> Send for UnboundedReceiver<T>where
    T: Send,
",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Send for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Send for TrySendError<T>where
    T: Send,
",1,["futures_channel::mpsc::TrySendError"]],["impl Send for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Send for Receiver<T>where
    T: Send,
",1,["futures_channel::oneshot::Receiver"]],["impl<T> Send for Sender<T>where
    T: Send,
",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Send for Cancellation<'a, T>where
    T: Send,
",1,["futures_channel::oneshot::Cancellation"]],["impl Send for Canceled",1,["futures_channel::oneshot::Canceled"]]], +"futures_executor":[["impl !Send for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !Send for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Send for BlockingStream<S>where
    S: Send,
",1,["futures_executor::local_pool::BlockingStream"]],["impl Send for Enter",1,["futures_executor::enter::Enter"]],["impl Send for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl Send for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Send for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !Send for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<T> Send for FutureObj<'_, T>"]], +"futures_util":[["impl<Fut> Send for Fuse<Fut>where
    Fut: Send,
",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Send for CatchUnwind<Fut>where
    Fut: Send,
",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Send for RemoteHandle<T>where
    T: Send,
",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Send for Remote<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send,
",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Send for Shared<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send + Sync,
",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Send for WeakShared<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send + Sync,
",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Send for Flatten<F>where
    F: Send,
    <F as Future>::Output: Send,
",1,["futures_util::future::future::Flatten"]],["impl<F> Send for FlattenStream<F>where
    F: Send,
    <F as Future>::Output: Send,
",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Send for Map<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::future::Map"]],["impl<F> Send for IntoStream<F>where
    F: Send,
",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Send for MapInto<Fut, T>where
    Fut: Send,
",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Send for Then<Fut1, Fut2, F>where
    F: Send,
    Fut1: Send,
    Fut2: Send,
",1,["futures_util::future::future::Then"]],["impl<Fut, F> Send for Inspect<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::future::Inspect"]],["impl<Fut> Send for NeverError<Fut>where
    Fut: Send,
",1,["futures_util::future::future::NeverError"]],["impl<Fut> Send for UnitError<Fut>where
    Fut: Send,
",1,["futures_util::future::future::UnitError"]],["impl<Fut> Send for IntoFuture<Fut>where
    Fut: Send,
",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Send for TryFlatten<Fut1, Fut2>where
    Fut1: Send,
    Fut2: Send,
",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Send for TryFlattenStream<Fut>where
    Fut: Send,
    <Fut as TryFuture>::Ok: Send,
",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Send for FlattenSink<Fut, Si>where
    Fut: Send,
    Si: Send,
",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Send for AndThen<Fut1, Fut2, F>where
    F: Send,
    Fut1: Send,
    Fut2: Send,
",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Send for OrElse<Fut1, Fut2, F>where
    F: Send,
    Fut1: Send,
    Fut2: Send,
",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Send for ErrInto<Fut, E>where
    Fut: Send,
",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Send for OkInto<Fut, E>where
    Fut: Send,
",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Send for InspectOk<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Send for InspectErr<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Send for MapOk<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Send for MapErr<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Send for MapOkOrElse<Fut, F, G>where
    F: Send,
    Fut: Send,
    G: Send,
",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Send for UnwrapOrElse<Fut, F>where
    F: Send,
    Fut: Send,
",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Send for Lazy<F>where
    F: Send,
",1,["futures_util::future::lazy::Lazy"]],["impl<T> Send for Pending<T>where
    T: Send,
",1,["futures_util::future::pending::Pending"]],["impl<Fut> Send for MaybeDone<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send,
",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Send for TryMaybeDone<Fut>where
    Fut: Send,
    <Fut as TryFuture>::Ok: Send,
",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Send for OptionFuture<F>where
    F: Send,
",1,["futures_util::future::option::OptionFuture"]],["impl<F> Send for PollFn<F>where
    F: Send,
",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Send for PollImmediate<T>where
    T: Send,
",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Send for Ready<T>where
    T: Send,
",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Send for Join<Fut1, Fut2>where
    Fut1: Send,
    Fut2: Send,
    <Fut1 as Future>::Output: Send,
    <Fut2 as Future>::Output: Send,
",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Send for Join3<Fut1, Fut2, Fut3>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    <Fut1 as Future>::Output: Send,
    <Fut2 as Future>::Output: Send,
    <Fut3 as Future>::Output: Send,
",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Send for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    Fut4: Send,
    <Fut1 as Future>::Output: Send,
    <Fut2 as Future>::Output: Send,
    <Fut3 as Future>::Output: Send,
    <Fut4 as Future>::Output: Send,
",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Send for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    Fut4: Send,
    Fut5: Send,
    <Fut1 as Future>::Output: Send,
    <Fut2 as Future>::Output: Send,
    <Fut3 as Future>::Output: Send,
    <Fut4 as Future>::Output: Send,
    <Fut5 as Future>::Output: Send,
",1,["futures_util::future::join::Join5"]],["impl<F> Send for JoinAll<F>where
    F: Send,
    <F as Future>::Output: Send,
",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Send for Select<A, B>where
    A: Send,
    B: Send,
",1,["futures_util::future::select::Select"]],["impl<Fut> Send for SelectAll<Fut>where
    Fut: Send,
",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Send for TryJoin<Fut1, Fut2>where
    Fut1: Send,
    Fut2: Send,
    <Fut1 as TryFuture>::Ok: Send,
    <Fut2 as TryFuture>::Ok: Send,
",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Send for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    <Fut1 as TryFuture>::Ok: Send,
    <Fut2 as TryFuture>::Ok: Send,
    <Fut3 as TryFuture>::Ok: Send,
",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Send for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    Fut4: Send,
    <Fut1 as TryFuture>::Ok: Send,
    <Fut2 as TryFuture>::Ok: Send,
    <Fut3 as TryFuture>::Ok: Send,
    <Fut4 as TryFuture>::Ok: Send,
",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Send for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Send,
    Fut2: Send,
    Fut3: Send,
    Fut4: Send,
    Fut5: Send,
    <Fut1 as TryFuture>::Ok: Send,
    <Fut2 as TryFuture>::Ok: Send,
    <Fut3 as TryFuture>::Ok: Send,
    <Fut4 as TryFuture>::Ok: Send,
    <Fut5 as TryFuture>::Ok: Send,
",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> Send for TryJoinAll<F>where
    F: Send,
    <F as TryFuture>::Error: Send,
    <F as TryFuture>::Ok: Send,
",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Send for TrySelect<A, B>where
    A: Send,
    B: Send,
",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Send for SelectOk<Fut>where
    Fut: Send,
",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Send for Either<A, B>where
    A: Send,
    B: Send,
",1,["futures_util::future::either::Either"]],["impl Send for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Send for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Send for Abortable<T>where
    T: Send,
",1,["futures_util::abortable::Abortable"]],["impl Send for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Send for Chain<St1, St2>where
    St1: Send,
    St2: Send,
",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Send for Collect<St, C>where
    C: Send,
    St: Send,
",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Send for Unzip<St, FromA, FromB>where
    FromA: Send,
    FromB: Send,
    St: Send,
",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Send for Concat<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Send for Cycle<St>where
    St: Send,
",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Send for Enumerate<St>where
    St: Send,
",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Send for Filter<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Send for FilterMap<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Send for Fold<St, Fut, T, F>where
    F: Send,
    Fut: Send,
    St: Send,
    T: Send,
",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Send for ForEach<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Send for Fuse<St>where
    St: Send,
",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Send for StreamFuture<St>where
    St: Send,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Send for Map<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Send for Next<'a, St>where
    St: Send,
",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Send for SelectNextSome<'a, St>where
    St: Send,
",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Send for Peekable<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Send for Peek<'a, St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Send for PeekMut<'a, St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Send for NextIf<'a, St, F>where
    F: Send,
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Send for NextIfEq<'a, St, T>where
    St: Send,
    T: Sync,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Send for Skip<St>where
    St: Send,
",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Send for SkipWhile<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Send for Take<St>where
    St: Send,
",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Send for TakeWhile<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Send for TakeUntil<St, Fut>where
    Fut: Send,
    St: Send,
    <Fut as Future>::Output: Send,
",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Send for Then<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Send for Zip<St1, St2>where
    St1: Send,
    St2: Send,
    <St1 as Stream>::Item: Send,
    <St2 as Stream>::Item: Send,
",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Send for Chunks<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Send for ReadyChunks<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Send for Scan<St, S, Fut, F>where
    F: Send,
    Fut: Send,
    S: Send,
    St: Send,
",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> Send for BufferUnordered<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> Send for Buffered<St>where
    St: Send,
    <St as Stream>::Item: Send,
    <<St as Stream>::Item as Future>::Output: Send,
",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> Send for ForEachConcurrent<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Send for SplitStream<S>where
    S: Send,
",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Send for SplitSink<S, Item>where
    Item: Send,
    S: Send,
",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Send for ReuniteError<T, Item>where
    Item: Send,
    T: Send,
",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Send for CatchUnwind<St>where
    St: Send,
",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Send for Flatten<St>where
    St: Send,
    <St as Stream>::Item: Send,
",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Send for Forward<St, Si>where
    Si: Send,
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Send for Inspect<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Send for FlatMap<St, U, F>where
    F: Send,
    St: Send,
    U: Send,
",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Send for AndThen<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Send for IntoStream<St>where
    St: Send,
",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Send for OrElse<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Send for TryNext<'a, St>where
    St: Send,
",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Send for TryForEach<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Send for TryFilter<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Send for TryFilterMap<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Send for TryFlatten<St>where
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Send for TryCollect<St, C>where
    C: Send,
    St: Send,
",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Send for TryConcat<St>where
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Send for TryChunks<St>where
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Send for TryChunksError<T, E>where
    E: Send,
    T: Send,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Send for TryFold<St, Fut, T, F>where
    F: Send,
    Fut: Send,
    St: Send,
    T: Send,
",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Send for TryUnfold<T, F, Fut>where
    F: Send,
    Fut: Send,
    T: Send,
",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Send for TrySkipWhile<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Send for TryTakeWhile<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> Send for TryBufferUnordered<St>where
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> Send for TryBuffered<St>where
    St: Send,
    <<St as TryStream>::Ok as TryFuture>::Error: Send,
    <St as TryStream>::Ok: Send,
    <<St as TryStream>::Ok as TryFuture>::Ok: Send,
",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> Send for TryForEachConcurrent<St, Fut, F>where
    F: Send,
    Fut: Send,
    St: Send,
",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Send for IntoAsyncRead<St>where
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Send for ErrInto<St, E>where
    St: Send,
",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Send for InspectOk<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Send for InspectErr<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Send for MapOk<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Send for MapErr<St, F>where
    F: Send,
    St: Send,
",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Send for Iter<I>where
    I: Send,
",1,["futures_util::stream::iter::Iter"]],["impl<T> Send for Repeat<T>where
    T: Send,
",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Send for RepeatWith<F>where
    F: Send,
",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Send for Empty<T>where
    T: Send,
",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Send for Once<Fut>where
    Fut: Send,
",1,["futures_util::stream::once::Once"]],["impl<T> Send for Pending<T>where
    T: Send,
",1,["futures_util::stream::pending::Pending"]],["impl<F> Send for PollFn<F>where
    F: Send,
",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Send for PollImmediate<S>where
    S: Send,
",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Send for Select<St1, St2>where
    St1: Send,
    St2: Send,
",1,["futures_util::stream::select::Select"]],["impl Send for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Send for SelectWithStrategy<St1, St2, Clos, State>where
    Clos: Send,
    St1: Send,
    St2: Send,
    State: Send,
",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Send for Unfold<T, F, Fut>where
    F: Send,
    Fut: Send,
    T: Send,
",1,["futures_util::stream::unfold::Unfold"]],["impl<T> Send for FuturesOrdered<T>where
    T: Send,
    <T as Future>::Output: Send,
",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Send for IterMut<'a, Fut>where
    Fut: Send,
",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Send for Iter<'a, Fut>where
    Fut: Send,
",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<St> Send for SelectAll<St>where
    St: Send,
",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Send for Iter<'a, St>where
    St: Send,
",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Send for IterMut<'a, St>where
    St: Send,
",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Send for IntoIter<St>where
    St: Send,
",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Send for Close<'a, Si, Item>where
    Si: Send,
",1,["futures_util::sink::close::Close"]],["impl<T> Send for Drain<T>where
    T: Send,
",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Send for Fanout<Si1, Si2>where
    Si1: Send,
    Si2: Send,
",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Send for Feed<'a, Si, Item>where
    Item: Send,
    Si: Send,
",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Send for Flush<'a, Si, Item>where
    Si: Send,
",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Send for SinkErrInto<Si, Item, E>where
    Si: Send,
",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Send for SinkMapErr<Si, F>where
    F: Send,
    Si: Send,
",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Send for Send<'a, Si, Item>where
    Item: Send,
    Si: Send,
",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Send for SendAll<'a, Si, St>where
    Si: Send,
    St: Send,
    <St as TryStream>::Ok: Send,
",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Send for Unfold<T, F, R>where
    F: Send,
    R: Send,
    T: Send,
",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Send for With<Si, Item, U, Fut, F>where
    F: Send,
    Fut: Send,
    Si: Send,
",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Send for WithFlatMap<Si, Item, U, St, F>where
    F: Send,
    Item: Send,
    Si: Send,
    St: Send,
",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Send for Buffer<Si, Item>where
    Item: Send,
    Si: Send,
",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Send for AllowStdIo<T>where
    T: Send,
",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Send for BufReader<R>where
    R: Send,
",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Send for SeeKRelative<'a, R>where
    R: Send,
",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Send for BufWriter<W>where
    W: Send,
",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Send for LineWriter<W>where
    W: Send,
",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Send for Chain<T, U>where
    T: Send,
    U: Send,
",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Send for Close<'a, W>where
    W: Send,
",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Send for Copy<'a, R, W>where
    R: Send,
    W: Send,
",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Send for CopyBuf<'a, R, W>where
    R: Send,
    W: Send,
",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Send for CopyBufAbortable<'a, R, W>where
    R: Send,
    W: Send,
",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Send for Cursor<T>where
    T: Send,
",1,["futures_util::io::cursor::Cursor"]],["impl Send for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Send for FillBuf<'a, R>where
    R: Send,
",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Send for Flush<'a, W>where
    W: Send,
",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Send for IntoSink<W, Item>where
    Item: Send,
    W: Send,
",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Send for Lines<R>where
    R: Send,
",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Send for Read<'a, R>where
    R: Send,
",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Send for ReadVectored<'a, R>where
    R: Send,
",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Send for ReadExact<'a, R>where
    R: Send,
",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Send for ReadLine<'a, R>where
    R: Send,
",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Send for ReadToEnd<'a, R>where
    R: Send,
",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Send for ReadToString<'a, R>where
    R: Send,
",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Send for ReadUntil<'a, R>where
    R: Send,
",1,["futures_util::io::read_until::ReadUntil"]],["impl Send for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Send for Seek<'a, S>where
    S: Send,
",1,["futures_util::io::seek::Seek"]],["impl Send for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Send for ReadHalf<T>where
    T: Send,
",1,["futures_util::io::split::ReadHalf"]],["impl<T> Send for WriteHalf<T>where
    T: Send,
",1,["futures_util::io::split::WriteHalf"]],["impl<T> Send for ReuniteError<T>where
    T: Send,
",1,["futures_util::io::split::ReuniteError"]],["impl<R> Send for Take<R>where
    R: Send,
",1,["futures_util::io::take::Take"]],["impl<T> Send for Window<T>where
    T: Send,
",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Send for Write<'a, W>where
    W: Send,
",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Send for WriteVectored<'a, W>where
    W: Send,
",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Send for WriteAll<'a, W>where
    W: Send,
",1,["futures_util::io::write_all::WriteAll"]],["impl<Fut: Send> Send for IterPinRef<'_, Fut>"],["impl<Fut: Send> Send for IterPinMut<'_, Fut>"],["impl<Fut: Send + Unpin> Send for IntoIter<Fut>"],["impl<Fut: Send> Send for FuturesUnordered<Fut>"],["impl<T: ?Sized + Send> Send for Mutex<T>"],["impl<T: ?Sized + Send> Send for MutexLockFuture<'_, T>"],["impl<T: ?Sized + Send> Send for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Send> Send for MutexGuard<'_, T>"],["impl<T: ?Sized + Send> Send for OwnedMutexGuard<T>"],["impl<T: ?Sized + Send, U: ?Sized + Send> Send for MappedMutexGuard<'_, T, U>"]], +"generic_array":[["impl<T, N> Send for GenericArrayIter<T, N>where
    T: Send,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T: Send, N: ArrayLength<T>> Send for GenericArray<T, N>"]], +"getrandom":[["impl Send for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl Send for WALRingId",1,["growthring::wal::WALRingId"]],["impl Send for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Send for WALLoader",1,["growthring::wal::WALLoader"]],["impl !Send for WALFileAIO",1,["growthring::WALFileAIO"]],["impl<F> Send for WALWriter<F>where
    F: WALStore + Send,
"],["impl Send for WALStoreAIO"]], +"hashbrown":[["impl<K, V, S, A> Send for HashMap<K, V, S, A>where
    A: Send,
    K: Send,
    S: Send,
    V: Send,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Send for Iter<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Iter"]],["impl<K, V, A> Send for IntoIter<K, V, A>where
    A: Send,
    K: Send,
    V: Send,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Send for IntoKeys<K, V, A>where
    A: Send,
    K: Send,
    V: Send,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Send for IntoValues<K, V, A>where
    A: Send,
    K: Send,
    V: Send,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Send for Keys<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Send for Values<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Send for Drain<'a, K, V, A>where
    A: Send + Copy,
    K: Send,
    V: Send,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Send for DrainFilter<'a, K, V, F, A>where
    A: Send,
    F: Send,
    K: Send,
    V: Send,
",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Send for ValuesMut<'a, K, V>where
    K: Send,
    V: Send,
",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Send for RawEntryBuilderMut<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Send,
    V: Send,
",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Send for RawEntryMut<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Send + Sync,
    V: Send,
",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Send for RawVacantEntryMut<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Sync,
    V: Send,
",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Send for RawEntryBuilder<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Send for Entry<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Send,
    V: Send,
",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Send for VacantEntry<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Send,
    V: Send,
",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Send for EntryRef<'a, 'b, K, Q, V, S, A>where
    A: Send,
    K: Send,
    Q: Sync,
    S: Send,
    V: Send,
",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Send for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
    A: Send,
    K: Send,
    Q: Sync,
    S: Send,
    V: Send,
",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Send for OccupiedError<'a, K, V, S, A>where
    A: Send,
    K: Send,
    S: Send,
    V: Send,
",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Send for HashSet<T, S, A>where
    A: Send,
    S: Send,
    T: Send,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> Send for Iter<'a, K>where
    K: Sync,
",1,["hashbrown::set::Iter"]],["impl<K, A> Send for IntoIter<K, A>where
    A: Send,
    K: Send,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Send for Drain<'a, K, A>where
    A: Send + Copy,
    K: Send,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Send for DrainFilter<'a, K, F, A>where
    A: Send,
    F: Send,
    K: Send,
",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Send for Intersection<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Send for Difference<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Send for SymmetricDifference<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Send for Union<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Send for Entry<'a, T, S, A>where
    A: Send,
    S: Send,
    T: Send,
",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Send for OccupiedEntry<'a, T, S, A>where
    A: Send,
    S: Send,
    T: Send,
",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Send for VacantEntry<'a, T, S, A>where
    A: Send,
    S: Send,
    T: Send,
",1,["hashbrown::set::VacantEntry"]],["impl Send for TryReserveError",1,["hashbrown::TryReserveError"]],["impl<K: Send, V: Send> Send for IterMut<'_, K, V>"],["impl<K, V, S, A> Send for RawOccupiedEntryMut<'_, K, V, S, A>where
    K: Send,
    V: Send,
    S: Send,
    A: Send + Allocator + Clone,
"],["impl<K, V, S, A> Send for OccupiedEntry<'_, K, V, S, A>where
    K: Send,
    V: Send,
    S: Send,
    A: Send + Allocator + Clone,
"],["impl<'a, 'b, K, Q, V, S, A> Send for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Send,
    Q: Sync + ?Sized,
    V: Send,
    S: Send,
    A: Send + Allocator + Clone,
"]], +"heck":[["impl<T> Send for AsKebabCase<T>where
    T: Send,
",1,["heck::kebab::AsKebabCase"]],["impl<T> Send for AsLowerCamelCase<T>where
    T: Send,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Send for AsShoutyKebabCase<T>where
    T: Send,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Send for AsShoutySnakeCase<T>where
    T: Send,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Send for AsSnakeCase<T>where
    T: Send,
",1,["heck::snake::AsSnakeCase"]],["impl<T> Send for AsTitleCase<T>where
    T: Send,
",1,["heck::title::AsTitleCase"]],["impl<T> Send for AsUpperCamelCase<T>where
    T: Send,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl Send for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl Send for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Send for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Send for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Send for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Send for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Send for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Send for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Send for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl !Send for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Send for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Send for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Send for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Send for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Send for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Send for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Send for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Send for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Send for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl !Send for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl !Send for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Send for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Send for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Send for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Send for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Send for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl !Send for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Send for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Send for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Send for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Send for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Send for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Send for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Send for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl !Send for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Send for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Send for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl !Send for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl !Send for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Send for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Send for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Send for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Send for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Send for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Send for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Send for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl !Send for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Send for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Send for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl !Send for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Send for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Send for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Send for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Send for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Send for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Send for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Send for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Send for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Send for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Send for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Send for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Send for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Send for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Send for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl !Send for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl !Send for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl !Send for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Send for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Send for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Send for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Send for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Send for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Send for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl !Send for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Send for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Send for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Send for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Send for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Send for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Send for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Send for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Send for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Send for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Send for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Send for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Send for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Send for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl !Send for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Send for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Send for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Send for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Send for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Send for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl !Send for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Send for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Send for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Send for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Send for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Send for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Send for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Send for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Send for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Send for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl !Send for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl !Send for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Send for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Send for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Send for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Send for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Send for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Send for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Send for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Send for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Send for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Send for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Send for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Send for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Send for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Send for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl !Send for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Send for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Send for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Send for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Send for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Send for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Send for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Send for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Send for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Send for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Send for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Send for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Send for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Send for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Send for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Send for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl !Send for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl !Send for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Send for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Send for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Send for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Send for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Send for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Send for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Send for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Send for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Send for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Send for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Send for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Send for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Send for timezone",1,["libc::unix::linux_like::timezone"]],["impl Send for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Send for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Send for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Send for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Send for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Send for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Send for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl !Send for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Send for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Send for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl !Send for tm",1,["libc::unix::linux_like::tm"]],["impl Send for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl !Send for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl !Send for lconv",1,["libc::unix::linux_like::lconv"]],["impl Send for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl !Send for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Send for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Send for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Send for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Send for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl !Send for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Send for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Send for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Send for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Send for utsname",1,["libc::unix::linux_like::utsname"]],["impl !Send for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Send for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Send for DIR",1,["libc::unix::DIR"]],["impl !Send for group",1,["libc::unix::group"]],["impl Send for utimbuf",1,["libc::unix::utimbuf"]],["impl Send for timeval",1,["libc::unix::timeval"]],["impl Send for timespec",1,["libc::unix::timespec"]],["impl Send for rlimit",1,["libc::unix::rlimit"]],["impl Send for rusage",1,["libc::unix::rusage"]],["impl Send for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl !Send for hostent",1,["libc::unix::hostent"]],["impl !Send for iovec",1,["libc::unix::iovec"]],["impl Send for pollfd",1,["libc::unix::pollfd"]],["impl Send for winsize",1,["libc::unix::winsize"]],["impl Send for linger",1,["libc::unix::linger"]],["impl !Send for sigval",1,["libc::unix::sigval"]],["impl Send for itimerval",1,["libc::unix::itimerval"]],["impl Send for tms",1,["libc::unix::tms"]],["impl !Send for servent",1,["libc::unix::servent"]],["impl !Send for protoent",1,["libc::unix::protoent"]],["impl Send for FILE",1,["libc::unix::FILE"]],["impl Send for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<'a, R, T: ?Sized> Send for MutexGuard<'a, R, T>where
    R: Sync,
    T: Send,
    <R as RawMutex>::GuardMarker: Send,
",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, G, T> !Send for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !Send for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockReadGuard<'a, R, T>where
    R: Sync,
    T: Send + Sync,
    <R as RawRwLock>::GuardMarker: Send,
",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockWriteGuard<'a, R, T>where
    R: Sync,
    T: Send + Sync,
    <R as RawRwLock>::GuardMarker: Send,
",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockUpgradableReadGuard<'a, R, T>where
    R: Sync,
    T: Send + Sync,
    <R as RawRwLock>::GuardMarker: Send,
",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl Send for GuardSend",1,["lock_api::GuardSend"]],["impl !Send for GuardNoSend",1,["lock_api::GuardNoSend"]],["impl<R: RawMutex + Send, T: ?Sized + Send> Send for Mutex<R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + Send + 'a> Send for MappedMutexGuard<'a, R, T>where
    R::GuardMarker: Send,
"],["impl<R: RawMutex + Send, G: GetThreadId + Send> Send for RawReentrantMutex<R, G>"],["impl<R: RawMutex + Send, G: GetThreadId + Send, T: ?Sized + Send> Send for ReentrantMutex<R, G, T>"],["impl<R: RawRwLock + Send, T: ?Sized + Send> Send for RwLock<R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Send for MappedRwLockReadGuard<'a, R, T>where
    R::GuardMarker: Send,
"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Send + 'a> Send for MappedRwLockWriteGuard<'a, R, T>where
    R::GuardMarker: Send,
"]], +"lru":[["impl<K, V> Send for IntoIter<K, V>where
    K: Send,
    V: Send,
",1,["lru::IntoIter"]],["impl<K: Send, V: Send, S: Send> Send for LruCache<K, V, S>"],["impl<'a, K: Send, V: Send> Send for Iter<'a, K, V>"],["impl<'a, K: Send, V: Send> Send for IterMut<'a, K, V>"]], +"memchr":[["impl<'a> Send for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Send for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Send for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Send for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Send for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Send for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Send for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Send for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Send for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl<'d> Send for Iter<'d>",1,["nix::dir::Iter"]],["impl Send for OwningIter",1,["nix::dir::OwningIter"]],["impl Send for Entry",1,["nix::dir::Entry"]],["impl Send for Type",1,["nix::dir::Type"]],["impl Send for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Send for Errno",1,["nix::errno::consts::Errno"]],["impl Send for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Send for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Send for OFlag",1,["nix::fcntl::OFlag"]],["impl Send for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Send for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Send for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Send for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Send for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Send for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Send for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Send for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl !Send for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl !Send for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl !Send for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> !Send for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Send for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Send for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Send for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Send for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Send for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Send for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Send for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Send for MqdT",1,["nix::mqueue::MqdT"]],["impl Send for PollFd",1,["nix::poll::PollFd"]],["impl Send for PollFlags",1,["nix::poll::PollFlags"]],["impl Send for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Send for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Send for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Send for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Send for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Send for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Send for LioMode",1,["nix::sys::aio::LioMode"]],["impl Send for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Send for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Send for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Send for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Send for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Send for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Send for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Send for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Send for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Send for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Send for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Send for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Send for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Send for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Send for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Send for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Send for Persona",1,["nix::sys::personality::Persona"]],["impl Send for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Send for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Send for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Send for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Send for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Send for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Send for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Send for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Send for Resource",1,["nix::sys::resource::Resource"]],["impl Send for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Send for Usage",1,["nix::sys::resource::Usage"]],["impl Send for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Send for Fds<'a>",1,["nix::sys::select::Fds"]],["impl !Send for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Send for Signal",1,["nix::sys::signal::Signal"]],["impl Send for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Send for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Send for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Send for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Send for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Send for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Send for SigAction",1,["nix::sys::signal::SigAction"]],["impl Send for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Send for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Send for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Send for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Send for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Send for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Send for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Send for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Send for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Send for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Send for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Send for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Send for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Send for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Send for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Send for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Send for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Send for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Send for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Send for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Send for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Send for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Send for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Send for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Send for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Send for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Send for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Send for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Send for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Send for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Send for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Send for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Send for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Send for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Send for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Send for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Send for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Send for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Send for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Send for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Send for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Send for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Send for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Send for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Send for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Send for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Send for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Send for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Send for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Send for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Send for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Send for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Send for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Send for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Send for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Send for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Send for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Send for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Send for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Send for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Send for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Send for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Send for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Send for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Send for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Send for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Send for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Send for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Send for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Send for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Send for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Send for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Send for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Send for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Send for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Send for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Send for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Send for AlgSetKey<T>where
    T: Send,
",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Send for SockType",1,["nix::sys::socket::SockType"]],["impl Send for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Send for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Send for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Send for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Send for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Send for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Send for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> !Send for RecvMsg<'a, 's, S>",1,["nix::sys::socket::RecvMsg"]],["impl<'a> !Send for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Send for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Send for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Send for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> !Send for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> !Send for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Send for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Send for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Send for SFlag",1,["nix::sys::stat::SFlag"]],["impl Send for Mode",1,["nix::sys::stat::Mode"]],["impl Send for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Send for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Send for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Send for FsType",1,["nix::sys::statfs::FsType"]],["impl Send for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Send for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Send for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl Send for Termios",1,["nix::sys::termios::Termios"]],["impl Send for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Send for SetArg",1,["nix::sys::termios::SetArg"]],["impl Send for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Send for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Send for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Send for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Send for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Send for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Send for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Send for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Send for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Send for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Send for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Send for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl Send for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Send for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Send for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Send for Id",1,["nix::sys::wait::Id"]],["impl Send for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Send for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Send for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Send for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Send for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Send for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Send for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Send for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl !Send for Timer",1,["nix::sys::timer::Timer"]],["impl Send for ClockId",1,["nix::time::ClockId"]],["impl !Send for UContext",1,["nix::ucontext::UContext"]],["impl Send for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Send for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Send for Uid",1,["nix::unistd::Uid"]],["impl Send for Gid",1,["nix::unistd::Gid"]],["impl Send for Pid",1,["nix::unistd::Pid"]],["impl Send for ForkResult",1,["nix::unistd::ForkResult"]],["impl Send for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Send for Whence",1,["nix::unistd::Whence"]],["impl Send for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Send for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Send for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Send for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Send for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Send for User",1,["nix::unistd::User"]],["impl Send for Group",1,["nix::unistd::Group"]],["impl Send for Dir"],["impl<T> Send for IoVec<T>where
    T: Send,
"]], +"once_cell":[["impl<T> Send for OnceCell<T>where
    T: Send,
",1,["once_cell::unsync::OnceCell"]],["impl<T, F> Send for Lazy<T, F>where
    F: Send,
    T: Send,
",1,["once_cell::unsync::Lazy"]],["impl<T> Send for OnceCell<T>where
    T: Send,
",1,["once_cell::sync::OnceCell"]],["impl<T, F> Send for Lazy<T, F>where
    F: Send,
    T: Send,
",1,["once_cell::sync::Lazy"]],["impl<T> Send for OnceBox<T>where
    T: Send,
",1,["once_cell::race::once_box::OnceBox"]],["impl Send for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Send for OnceBool",1,["once_cell::race::OnceBool"]]], +"parking_lot":[["impl Send for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Send for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Send for OnceState",1,["parking_lot::once::OnceState"]],["impl Send for Once",1,["parking_lot::once::Once"]],["impl Send for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Send for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Send for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Send for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl Send for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Send for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Send for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Send for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Send for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Send for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Send for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl Send for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Send for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Send for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Send for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Send for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Send for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Send for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Send for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Send for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Send for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Send for SseMachine<S3, S4, NI>where
    NI: Send,
    S3: Send,
    S4: Send,
",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Send for Avx2Machine<NI>where
    NI: Send,
",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Send for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Send for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Send for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl Send for Error",1,["primitive_types::Error"]],["impl Send for U128",1,["primitive_types::U128"]],["impl Send for U256",1,["primitive_types::U256"]],["impl Send for U512",1,["primitive_types::U512"]],["impl Send for H128",1,["primitive_types::H128"]],["impl Send for H160",1,["primitive_types::H160"]],["impl Send for H256",1,["primitive_types::H256"]],["impl Send for H384",1,["primitive_types::H384"]],["impl Send for H512",1,["primitive_types::H512"]],["impl Send for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl !Send for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl !Send for TokenStream",1,["proc_macro2::TokenStream"]],["impl !Send for LexError",1,["proc_macro2::LexError"]],["impl !Send for Span",1,["proc_macro2::Span"]],["impl !Send for TokenTree",1,["proc_macro2::TokenTree"]],["impl !Send for Group",1,["proc_macro2::Group"]],["impl Send for Delimiter",1,["proc_macro2::Delimiter"]],["impl !Send for Punct",1,["proc_macro2::Punct"]],["impl Send for Spacing",1,["proc_macro2::Spacing"]],["impl !Send for Ident",1,["proc_macro2::Ident"]],["impl !Send for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl Send for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Send for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Send for DistIter<D, R, T>where
    D: Send,
    R: Send,
    T: Send,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Send for DistMap<D, F, T, S>where
    D: Send,
    F: Send,
",1,["rand::distributions::distribution::DistMap"]],["impl Send for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Send for Open01",1,["rand::distributions::float::Open01"]],["impl Send for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Send for Slice<'a, T>where
    T: Sync,
",1,["rand::distributions::slice::Slice"]],["impl<X> Send for WeightedIndex<X>where
    X: Send,
    <X as SampleUniform>::Sampler: Send,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Send for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Send for Uniform<X>where
    <X as SampleUniform>::Sampler: Send,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> Send for UniformInt<X>where
    X: Send,
",1,["rand::distributions::uniform::UniformInt"]],["impl Send for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Send for UniformFloat<X>where
    X: Send,
",1,["rand::distributions::uniform::UniformFloat"]],["impl Send for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Send for WeightedIndex<W>where
    W: Send,
",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Send for Standard",1,["rand::distributions::Standard"]],["impl<R> Send for ReadRng<R>where
    R: Send,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl Send for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Send for ReseedingRng<R, Rsdr>where
    R: Send,
    Rsdr: Send,
    <R as BlockRngCore>::Results: Send,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Send for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Send for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Send for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Send for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Send for SliceChooseIter<'a, S, T>where
    S: Sync,
    T: Send,
",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl Send for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Send for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Send for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Send for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Send for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Send for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> Send for BlockRng<R>where
    R: Send,
    <R as BlockRngCore>::Results: Send,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Send for BlockRng64<R>where
    R: Send,
    <R as BlockRngCore>::Results: Send,
",1,["rand_core::block::BlockRng64"]],["impl Send for Error",1,["rand_core::error::Error"]],["impl Send for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl Send for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Send for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Send for Match<'t>",1,["regex::re_bytes::Match"]],["impl Send for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Send for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Send for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Send for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Send for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Send for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Send for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Send for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Send for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Send for ReplacerRef<'a, R>where
    R: Send,
",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Send for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Send for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Send for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Send for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Send for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Send for Error",1,["regex::error::Error"]],["impl Send for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Send for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Send for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Send for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Send for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Send for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Send for Match<'t>",1,["regex::re_unicode::Match"]],["impl Send for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Send for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Send for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Send for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Send for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Send for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Send for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Send for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Send for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Send for ReplacerRef<'a, R>where
    R: Send,
",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Send for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl Send for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl Send for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Send for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Send for Error",1,["regex_syntax::ast::Error"]],["impl Send for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Send for Span",1,["regex_syntax::ast::Span"]],["impl Send for Position",1,["regex_syntax::ast::Position"]],["impl Send for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Send for Comment",1,["regex_syntax::ast::Comment"]],["impl Send for Ast",1,["regex_syntax::ast::Ast"]],["impl Send for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Send for Concat",1,["regex_syntax::ast::Concat"]],["impl Send for Literal",1,["regex_syntax::ast::Literal"]],["impl Send for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Send for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Send for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Send for Class",1,["regex_syntax::ast::Class"]],["impl Send for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Send for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Send for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Send for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Send for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Send for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Send for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Send for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Send for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Send for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Send for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Send for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Send for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Send for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Send for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Send for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Send for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Send for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Send for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Send for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Send for Group",1,["regex_syntax::ast::Group"]],["impl Send for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Send for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Send for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Send for Flags",1,["regex_syntax::ast::Flags"]],["impl Send for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Send for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Send for Flag",1,["regex_syntax::ast::Flag"]],["impl Send for Error",1,["regex_syntax::error::Error"]],["impl Send for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Send for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Send for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Send for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl Send for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Send for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Send for Error",1,["regex_syntax::hir::Error"]],["impl Send for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Send for Hir",1,["regex_syntax::hir::Hir"]],["impl Send for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Send for Literal",1,["regex_syntax::hir::Literal"]],["impl Send for Class",1,["regex_syntax::hir::Class"]],["impl Send for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Send for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Send for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Send for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Send for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Send for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Send for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Send for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Send for Group",1,["regex_syntax::hir::Group"]],["impl Send for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Send for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Send for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Send for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Send for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl Send for Parser",1,["regex_syntax::parser::Parser"]],["impl Send for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Send for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Send for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Send for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl Send for DecoderError",1,["rlp::error::DecoderError"]],["impl Send for Prototype",1,["rlp::rlpin::Prototype"]],["impl Send for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> Send for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !Send for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Send for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> Send for ToHexIter<T>where
    T: Send,
",1,["rustc_hex::ToHexIter"]],["impl Send for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Send for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl Send for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl Send for Always",1,["scopeguard::Always"]],["impl<T, F, S> Send for ScopeGuard<T, F, S>where
    F: Send,
    T: Send,
",1,["scopeguard::ScopeGuard"]]], +"serde":[["impl Send for Error",1,["serde::de::value::Error"]],["impl<E> Send for UnitDeserializer<E>where
    E: Send,
",1,["serde::de::value::UnitDeserializer"]],["impl<E> Send for BoolDeserializer<E>where
    E: Send,
",1,["serde::de::value::BoolDeserializer"]],["impl<E> Send for I8Deserializer<E>where
    E: Send,
",1,["serde::de::value::I8Deserializer"]],["impl<E> Send for I16Deserializer<E>where
    E: Send,
",1,["serde::de::value::I16Deserializer"]],["impl<E> Send for I32Deserializer<E>where
    E: Send,
",1,["serde::de::value::I32Deserializer"]],["impl<E> Send for I64Deserializer<E>where
    E: Send,
",1,["serde::de::value::I64Deserializer"]],["impl<E> Send for IsizeDeserializer<E>where
    E: Send,
",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Send for U8Deserializer<E>where
    E: Send,
",1,["serde::de::value::U8Deserializer"]],["impl<E> Send for U16Deserializer<E>where
    E: Send,
",1,["serde::de::value::U16Deserializer"]],["impl<E> Send for U64Deserializer<E>where
    E: Send,
",1,["serde::de::value::U64Deserializer"]],["impl<E> Send for UsizeDeserializer<E>where
    E: Send,
",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Send for F32Deserializer<E>where
    E: Send,
",1,["serde::de::value::F32Deserializer"]],["impl<E> Send for F64Deserializer<E>where
    E: Send,
",1,["serde::de::value::F64Deserializer"]],["impl<E> Send for CharDeserializer<E>where
    E: Send,
",1,["serde::de::value::CharDeserializer"]],["impl<E> Send for I128Deserializer<E>where
    E: Send,
",1,["serde::de::value::I128Deserializer"]],["impl<E> Send for U128Deserializer<E>where
    E: Send,
",1,["serde::de::value::U128Deserializer"]],["impl<E> Send for U32Deserializer<E>where
    E: Send,
",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Send for StrDeserializer<'a, E>where
    E: Send,
",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Send for BorrowedStrDeserializer<'de, E>where
    E: Send,
",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Send for StringDeserializer<E>where
    E: Send,
",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Send for CowStrDeserializer<'a, E>where
    E: Send,
",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Send for BytesDeserializer<'a, E>where
    E: Send,
",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Send for BorrowedBytesDeserializer<'de, E>where
    E: Send,
",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Send for SeqDeserializer<I, E>where
    E: Send,
    I: Send,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> Send for SeqAccessDeserializer<A>where
    A: Send,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Send for MapDeserializer<'de, I, E>where
    E: Send,
    I: Send,
    <<I as Iterator>::Item as Pair>::Second: Send,
",1,["serde::de::value::MapDeserializer"]],["impl<A> Send for MapAccessDeserializer<A>where
    A: Send,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Send for EnumAccessDeserializer<A>where
    A: Send,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl Send for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Send for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Send for Impossible<Ok, Error>where
    Error: Send,
    Ok: Send,
",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl Send for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Send for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Send for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Send for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Send for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Send for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Send for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Send for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Send for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Send for Shake128Core",1,["sha3::Shake128Core"]],["impl Send for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Send for Shake256Core",1,["sha3::Shake256Core"]],["impl Send for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Send for CShake128Core",1,["sha3::CShake128Core"]],["impl Send for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Send for CShake256Core",1,["sha3::CShake256Core"]],["impl Send for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl Send for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Send for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Send for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Send for ShaleError",1,["shale::ShaleError"]],["impl Send for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Send for ObjPtr<T>where
    T: Send,
",1,["shale::ObjPtr"]],["impl<T> !Send for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !Send for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !Send for MummyObj<T>",1,["shale::MummyObj"]],["impl !Send for PlainMem",1,["shale::PlainMem"]],["impl<T> !Send for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> Send for Slab<T>where
    T: Send,
",1,["slab::Slab"]],["impl<'a, T> Send for VacantEntry<'a, T>where
    T: Send,
",1,["slab::VacantEntry"]],["impl<T> Send for IntoIter<T>where
    T: Send,
",1,["slab::IntoIter"]],["impl<'a, T> Send for Iter<'a, T>where
    T: Sync,
",1,["slab::Iter"]],["impl<'a, T> Send for IterMut<'a, T>where
    T: Send,
",1,["slab::IterMut"]],["impl<'a, T> Send for Drain<'a, T>where
    T: Send,
",1,["slab::Drain"]]], +"smallvec":[["impl Send for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<A> Send for IntoIter<A>where
    <A as Array>::Item: Send,
",1,["smallvec::IntoIter"]],["impl<'a, T: Send + Array> Send for Drain<'a, T>"],["impl<A: Array> Send for SmallVec<A>where
    A::Item: Send,
"]], +"syn":[["impl !Send for Underscore",1,["syn::token::Underscore"]],["impl !Send for Abstract",1,["syn::token::Abstract"]],["impl !Send for As",1,["syn::token::As"]],["impl !Send for Async",1,["syn::token::Async"]],["impl !Send for Auto",1,["syn::token::Auto"]],["impl !Send for Await",1,["syn::token::Await"]],["impl !Send for Become",1,["syn::token::Become"]],["impl !Send for Box",1,["syn::token::Box"]],["impl !Send for Break",1,["syn::token::Break"]],["impl !Send for Const",1,["syn::token::Const"]],["impl !Send for Continue",1,["syn::token::Continue"]],["impl !Send for Crate",1,["syn::token::Crate"]],["impl !Send for Default",1,["syn::token::Default"]],["impl !Send for Do",1,["syn::token::Do"]],["impl !Send for Dyn",1,["syn::token::Dyn"]],["impl !Send for Else",1,["syn::token::Else"]],["impl !Send for Enum",1,["syn::token::Enum"]],["impl !Send for Extern",1,["syn::token::Extern"]],["impl !Send for Final",1,["syn::token::Final"]],["impl !Send for Fn",1,["syn::token::Fn"]],["impl !Send for For",1,["syn::token::For"]],["impl !Send for If",1,["syn::token::If"]],["impl !Send for Impl",1,["syn::token::Impl"]],["impl !Send for In",1,["syn::token::In"]],["impl !Send for Let",1,["syn::token::Let"]],["impl !Send for Loop",1,["syn::token::Loop"]],["impl !Send for Macro",1,["syn::token::Macro"]],["impl !Send for Match",1,["syn::token::Match"]],["impl !Send for Mod",1,["syn::token::Mod"]],["impl !Send for Move",1,["syn::token::Move"]],["impl !Send for Mut",1,["syn::token::Mut"]],["impl !Send for Override",1,["syn::token::Override"]],["impl !Send for Priv",1,["syn::token::Priv"]],["impl !Send for Pub",1,["syn::token::Pub"]],["impl !Send for Ref",1,["syn::token::Ref"]],["impl !Send for Return",1,["syn::token::Return"]],["impl !Send for SelfType",1,["syn::token::SelfType"]],["impl !Send for SelfValue",1,["syn::token::SelfValue"]],["impl !Send for Static",1,["syn::token::Static"]],["impl !Send for Struct",1,["syn::token::Struct"]],["impl !Send for Super",1,["syn::token::Super"]],["impl !Send for Trait",1,["syn::token::Trait"]],["impl !Send for Try",1,["syn::token::Try"]],["impl !Send for Type",1,["syn::token::Type"]],["impl !Send for Typeof",1,["syn::token::Typeof"]],["impl !Send for Union",1,["syn::token::Union"]],["impl !Send for Unsafe",1,["syn::token::Unsafe"]],["impl !Send for Unsized",1,["syn::token::Unsized"]],["impl !Send for Use",1,["syn::token::Use"]],["impl !Send for Virtual",1,["syn::token::Virtual"]],["impl !Send for Where",1,["syn::token::Where"]],["impl !Send for While",1,["syn::token::While"]],["impl !Send for Yield",1,["syn::token::Yield"]],["impl !Send for Add",1,["syn::token::Add"]],["impl !Send for AddEq",1,["syn::token::AddEq"]],["impl !Send for And",1,["syn::token::And"]],["impl !Send for AndAnd",1,["syn::token::AndAnd"]],["impl !Send for AndEq",1,["syn::token::AndEq"]],["impl !Send for At",1,["syn::token::At"]],["impl !Send for Bang",1,["syn::token::Bang"]],["impl !Send for Caret",1,["syn::token::Caret"]],["impl !Send for CaretEq",1,["syn::token::CaretEq"]],["impl !Send for Colon",1,["syn::token::Colon"]],["impl !Send for Colon2",1,["syn::token::Colon2"]],["impl !Send for Comma",1,["syn::token::Comma"]],["impl !Send for Div",1,["syn::token::Div"]],["impl !Send for DivEq",1,["syn::token::DivEq"]],["impl !Send for Dollar",1,["syn::token::Dollar"]],["impl !Send for Dot",1,["syn::token::Dot"]],["impl !Send for Dot2",1,["syn::token::Dot2"]],["impl !Send for Dot3",1,["syn::token::Dot3"]],["impl !Send for DotDotEq",1,["syn::token::DotDotEq"]],["impl !Send for Eq",1,["syn::token::Eq"]],["impl !Send for EqEq",1,["syn::token::EqEq"]],["impl !Send for Ge",1,["syn::token::Ge"]],["impl !Send for Gt",1,["syn::token::Gt"]],["impl !Send for Le",1,["syn::token::Le"]],["impl !Send for Lt",1,["syn::token::Lt"]],["impl !Send for MulEq",1,["syn::token::MulEq"]],["impl !Send for Ne",1,["syn::token::Ne"]],["impl !Send for Or",1,["syn::token::Or"]],["impl !Send for OrEq",1,["syn::token::OrEq"]],["impl !Send for OrOr",1,["syn::token::OrOr"]],["impl !Send for Pound",1,["syn::token::Pound"]],["impl !Send for Question",1,["syn::token::Question"]],["impl !Send for RArrow",1,["syn::token::RArrow"]],["impl !Send for LArrow",1,["syn::token::LArrow"]],["impl !Send for Rem",1,["syn::token::Rem"]],["impl !Send for RemEq",1,["syn::token::RemEq"]],["impl !Send for FatArrow",1,["syn::token::FatArrow"]],["impl !Send for Semi",1,["syn::token::Semi"]],["impl !Send for Shl",1,["syn::token::Shl"]],["impl !Send for ShlEq",1,["syn::token::ShlEq"]],["impl !Send for Shr",1,["syn::token::Shr"]],["impl !Send for ShrEq",1,["syn::token::ShrEq"]],["impl !Send for Star",1,["syn::token::Star"]],["impl !Send for Sub",1,["syn::token::Sub"]],["impl !Send for SubEq",1,["syn::token::SubEq"]],["impl !Send for Tilde",1,["syn::token::Tilde"]],["impl !Send for Brace",1,["syn::token::Brace"]],["impl !Send for Bracket",1,["syn::token::Bracket"]],["impl !Send for Paren",1,["syn::token::Paren"]],["impl !Send for Group",1,["syn::token::Group"]],["impl !Send for Attribute",1,["syn::attr::Attribute"]],["impl !Send for AttrStyle",1,["syn::attr::AttrStyle"]],["impl !Send for Meta",1,["syn::attr::Meta"]],["impl !Send for MetaList",1,["syn::attr::MetaList"]],["impl !Send for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl !Send for NestedMeta",1,["syn::attr::NestedMeta"]],["impl !Send for Variant",1,["syn::data::Variant"]],["impl !Send for Fields",1,["syn::data::Fields"]],["impl !Send for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl !Send for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl !Send for Field",1,["syn::data::Field"]],["impl !Send for Visibility",1,["syn::data::Visibility"]],["impl !Send for VisPublic",1,["syn::data::VisPublic"]],["impl !Send for VisCrate",1,["syn::data::VisCrate"]],["impl !Send for VisRestricted",1,["syn::data::VisRestricted"]],["impl !Send for Expr",1,["syn::expr::Expr"]],["impl !Send for ExprArray",1,["syn::expr::ExprArray"]],["impl !Send for ExprAssign",1,["syn::expr::ExprAssign"]],["impl !Send for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl !Send for ExprAsync",1,["syn::expr::ExprAsync"]],["impl !Send for ExprAwait",1,["syn::expr::ExprAwait"]],["impl !Send for ExprBinary",1,["syn::expr::ExprBinary"]],["impl !Send for ExprBlock",1,["syn::expr::ExprBlock"]],["impl !Send for ExprBox",1,["syn::expr::ExprBox"]],["impl !Send for ExprBreak",1,["syn::expr::ExprBreak"]],["impl !Send for ExprCall",1,["syn::expr::ExprCall"]],["impl !Send for ExprCast",1,["syn::expr::ExprCast"]],["impl !Send for ExprClosure",1,["syn::expr::ExprClosure"]],["impl !Send for ExprContinue",1,["syn::expr::ExprContinue"]],["impl !Send for ExprField",1,["syn::expr::ExprField"]],["impl !Send for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl !Send for ExprGroup",1,["syn::expr::ExprGroup"]],["impl !Send for ExprIf",1,["syn::expr::ExprIf"]],["impl !Send for ExprIndex",1,["syn::expr::ExprIndex"]],["impl !Send for ExprLet",1,["syn::expr::ExprLet"]],["impl !Send for ExprLit",1,["syn::expr::ExprLit"]],["impl !Send for ExprLoop",1,["syn::expr::ExprLoop"]],["impl !Send for ExprMacro",1,["syn::expr::ExprMacro"]],["impl !Send for ExprMatch",1,["syn::expr::ExprMatch"]],["impl !Send for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl !Send for ExprParen",1,["syn::expr::ExprParen"]],["impl !Send for ExprPath",1,["syn::expr::ExprPath"]],["impl !Send for ExprRange",1,["syn::expr::ExprRange"]],["impl !Send for ExprReference",1,["syn::expr::ExprReference"]],["impl !Send for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl !Send for ExprReturn",1,["syn::expr::ExprReturn"]],["impl !Send for ExprStruct",1,["syn::expr::ExprStruct"]],["impl !Send for ExprTry",1,["syn::expr::ExprTry"]],["impl !Send for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl !Send for ExprTuple",1,["syn::expr::ExprTuple"]],["impl !Send for ExprType",1,["syn::expr::ExprType"]],["impl !Send for ExprUnary",1,["syn::expr::ExprUnary"]],["impl !Send for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl !Send for ExprWhile",1,["syn::expr::ExprWhile"]],["impl !Send for ExprYield",1,["syn::expr::ExprYield"]],["impl !Send for Member",1,["syn::expr::Member"]],["impl !Send for Index",1,["syn::expr::Index"]],["impl !Send for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl !Send for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl !Send for FieldValue",1,["syn::expr::FieldValue"]],["impl !Send for Label",1,["syn::expr::Label"]],["impl !Send for Arm",1,["syn::expr::Arm"]],["impl !Send for RangeLimits",1,["syn::expr::RangeLimits"]],["impl !Send for Generics",1,["syn::generics::Generics"]],["impl !Send for GenericParam",1,["syn::generics::GenericParam"]],["impl !Send for TypeParam",1,["syn::generics::TypeParam"]],["impl !Send for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl !Send for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> !Send for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> !Send for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> !Send for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl !Send for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl !Send for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl !Send for TraitBound",1,["syn::generics::TraitBound"]],["impl !Send for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl !Send for WhereClause",1,["syn::generics::WhereClause"]],["impl !Send for WherePredicate",1,["syn::generics::WherePredicate"]],["impl !Send for PredicateType",1,["syn::generics::PredicateType"]],["impl !Send for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl !Send for PredicateEq",1,["syn::generics::PredicateEq"]],["impl !Send for Item",1,["syn::item::Item"]],["impl !Send for ItemConst",1,["syn::item::ItemConst"]],["impl !Send for ItemEnum",1,["syn::item::ItemEnum"]],["impl !Send for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl !Send for ItemFn",1,["syn::item::ItemFn"]],["impl !Send for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl !Send for ItemImpl",1,["syn::item::ItemImpl"]],["impl !Send for ItemMacro",1,["syn::item::ItemMacro"]],["impl !Send for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl !Send for ItemMod",1,["syn::item::ItemMod"]],["impl !Send for ItemStatic",1,["syn::item::ItemStatic"]],["impl !Send for ItemStruct",1,["syn::item::ItemStruct"]],["impl !Send for ItemTrait",1,["syn::item::ItemTrait"]],["impl !Send for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl !Send for ItemType",1,["syn::item::ItemType"]],["impl !Send for ItemUnion",1,["syn::item::ItemUnion"]],["impl !Send for ItemUse",1,["syn::item::ItemUse"]],["impl !Send for UseTree",1,["syn::item::UseTree"]],["impl !Send for UsePath",1,["syn::item::UsePath"]],["impl !Send for UseName",1,["syn::item::UseName"]],["impl !Send for UseRename",1,["syn::item::UseRename"]],["impl !Send for UseGlob",1,["syn::item::UseGlob"]],["impl !Send for UseGroup",1,["syn::item::UseGroup"]],["impl !Send for ForeignItem",1,["syn::item::ForeignItem"]],["impl !Send for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl !Send for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl !Send for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl !Send for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl !Send for TraitItem",1,["syn::item::TraitItem"]],["impl !Send for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl !Send for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl !Send for TraitItemType",1,["syn::item::TraitItemType"]],["impl !Send for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl !Send for ImplItem",1,["syn::item::ImplItem"]],["impl !Send for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl !Send for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl !Send for ImplItemType",1,["syn::item::ImplItemType"]],["impl !Send for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl !Send for Signature",1,["syn::item::Signature"]],["impl !Send for FnArg",1,["syn::item::FnArg"]],["impl !Send for Receiver",1,["syn::item::Receiver"]],["impl !Send for File",1,["syn::file::File"]],["impl !Send for Lifetime",1,["syn::lifetime::Lifetime"]],["impl !Send for Lit",1,["syn::lit::Lit"]],["impl !Send for LitStr",1,["syn::lit::LitStr"]],["impl !Send for LitByteStr",1,["syn::lit::LitByteStr"]],["impl !Send for LitByte",1,["syn::lit::LitByte"]],["impl !Send for LitChar",1,["syn::lit::LitChar"]],["impl !Send for LitInt",1,["syn::lit::LitInt"]],["impl !Send for LitFloat",1,["syn::lit::LitFloat"]],["impl !Send for LitBool",1,["syn::lit::LitBool"]],["impl Send for StrStyle",1,["syn::lit::StrStyle"]],["impl !Send for Macro",1,["syn::mac::Macro"]],["impl !Send for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl !Send for DeriveInput",1,["syn::derive::DeriveInput"]],["impl !Send for Data",1,["syn::derive::Data"]],["impl !Send for DataStruct",1,["syn::derive::DataStruct"]],["impl !Send for DataEnum",1,["syn::derive::DataEnum"]],["impl !Send for DataUnion",1,["syn::derive::DataUnion"]],["impl !Send for BinOp",1,["syn::op::BinOp"]],["impl !Send for UnOp",1,["syn::op::UnOp"]],["impl !Send for Block",1,["syn::stmt::Block"]],["impl !Send for Stmt",1,["syn::stmt::Stmt"]],["impl !Send for Local",1,["syn::stmt::Local"]],["impl !Send for Type",1,["syn::ty::Type"]],["impl !Send for TypeArray",1,["syn::ty::TypeArray"]],["impl !Send for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl !Send for TypeGroup",1,["syn::ty::TypeGroup"]],["impl !Send for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl !Send for TypeInfer",1,["syn::ty::TypeInfer"]],["impl !Send for TypeMacro",1,["syn::ty::TypeMacro"]],["impl !Send for TypeNever",1,["syn::ty::TypeNever"]],["impl !Send for TypeParen",1,["syn::ty::TypeParen"]],["impl !Send for TypePath",1,["syn::ty::TypePath"]],["impl !Send for TypePtr",1,["syn::ty::TypePtr"]],["impl !Send for TypeReference",1,["syn::ty::TypeReference"]],["impl !Send for TypeSlice",1,["syn::ty::TypeSlice"]],["impl !Send for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl !Send for TypeTuple",1,["syn::ty::TypeTuple"]],["impl !Send for Abi",1,["syn::ty::Abi"]],["impl !Send for BareFnArg",1,["syn::ty::BareFnArg"]],["impl !Send for Variadic",1,["syn::ty::Variadic"]],["impl !Send for ReturnType",1,["syn::ty::ReturnType"]],["impl !Send for Pat",1,["syn::pat::Pat"]],["impl !Send for PatBox",1,["syn::pat::PatBox"]],["impl !Send for PatIdent",1,["syn::pat::PatIdent"]],["impl !Send for PatLit",1,["syn::pat::PatLit"]],["impl !Send for PatMacro",1,["syn::pat::PatMacro"]],["impl !Send for PatOr",1,["syn::pat::PatOr"]],["impl !Send for PatPath",1,["syn::pat::PatPath"]],["impl !Send for PatRange",1,["syn::pat::PatRange"]],["impl !Send for PatReference",1,["syn::pat::PatReference"]],["impl !Send for PatRest",1,["syn::pat::PatRest"]],["impl !Send for PatSlice",1,["syn::pat::PatSlice"]],["impl !Send for PatStruct",1,["syn::pat::PatStruct"]],["impl !Send for PatTuple",1,["syn::pat::PatTuple"]],["impl !Send for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl !Send for PatType",1,["syn::pat::PatType"]],["impl !Send for PatWild",1,["syn::pat::PatWild"]],["impl !Send for FieldPat",1,["syn::pat::FieldPat"]],["impl !Send for Path",1,["syn::path::Path"]],["impl !Send for PathSegment",1,["syn::path::PathSegment"]],["impl !Send for PathArguments",1,["syn::path::PathArguments"]],["impl !Send for GenericArgument",1,["syn::path::GenericArgument"]],["impl !Send for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl !Send for Binding",1,["syn::path::Binding"]],["impl !Send for Constraint",1,["syn::path::Constraint"]],["impl !Send for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl !Send for QSelf",1,["syn::path::QSelf"]],["impl !Send for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> !Send for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Send for Punctuated<T, P>where
    P: Send,
    T: Send,
",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Send for Pairs<'a, T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Send for PairsMut<'a, T, P>where
    P: Send,
    T: Send,
",1,["syn::punctuated::PairsMut"]],["impl<T, P> Send for IntoPairs<T, P>where
    P: Send,
    T: Send,
",1,["syn::punctuated::IntoPairs"]],["impl<T> Send for IntoIter<T>where
    T: Send,
",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !Send for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !Send for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Send for Pair<T, P>where
    P: Send,
    T: Send,
",1,["syn::punctuated::Pair"]],["impl<'a> !Send for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Send for Error",1,["syn::error::Error"]],["impl<'a> !Send for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> !Send for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Send for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> Send for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Send for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Send for Builder",1,["tokio::runtime::builder::Builder"]],["impl Send for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Send for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Send for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Send for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Send for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl Send for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Send for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Send for SendError<T>where
    T: Send,
",1,["tokio::sync::broadcast::error::SendError"]],["impl Send for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Send for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Send for Sender<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Send for WeakSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Send for Permit<'a, T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Send for OwnedPermit<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Send for Receiver<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Send for UnboundedSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Send for WeakUnboundedSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Send for UnboundedReceiver<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Send for SendError<T>where
    T: Send,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Send for TrySendError<T>where
    T: Send,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Send for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<'a, T: ?Sized> Send for MutexGuard<'a, T>where
    T: Send,
",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Send for OwnedMutexGuard<T>where
    T: Send,
",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl Send for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Send for Notify",1,["tokio::sync::notify::Notify"]],["impl Send for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Send for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Send for Sender<T>where
    T: Send,
",1,["tokio::sync::oneshot::Sender"]],["impl<T> Send for Receiver<T>where
    T: Send,
",1,["tokio::sync::oneshot::Receiver"]],["impl Send for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Send for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Send for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Send for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Send for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T> Send for SetError<T>where
    T: Send,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> Send for SendError<T>where
    T: Send,
",1,["tokio::sync::watch::error::SendError"]],["impl Send for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Send for Receiver<T>where
    T: Send + Sync,
",1,["tokio::sync::watch::Receiver"]],["impl<T> Send for Sender<T>where
    T: Send + Sync,
",1,["tokio::sync::watch::Sender"]],["impl<'a, T> !Send for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl !Send for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !Send for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Send for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Send for TaskLocalFuture<T, F>where
    F: Send,
    T: Send,
",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Send for Unconstrained<F>where
    F: Send,
",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Send for JoinSet<T>where
    T: Send,
",1,["tokio::task::join_set::JoinSet"]],["impl Send for AbortHandle"],["impl<T: Send> Send for JoinHandle<T>"],["impl<T: Send> Send for Sender<T>"],["impl<T: Send> Send for Receiver<T>"],["impl<T> Send for Mutex<T>where
    T: ?Sized + Send,
"],["impl<'a, T> Send for MappedMutexGuard<'a, T>where
    T: ?Sized + Send + 'a,
"],["impl<'a> Send for Notified<'a>"],["impl<T> Send for RwLock<T>where
    T: ?Sized + Send,
"],["impl<T> Send for RwLockReadGuard<'_, T>where
    T: ?Sized + Sync,
"],["impl<T, U> Send for OwnedRwLockReadGuard<T, U>where
    T: ?Sized + Send + Sync,
    U: ?Sized + Sync,
"],["impl<T> Send for RwLockWriteGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Send for OwnedRwLockWriteGuard<T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Send for RwLockMappedWriteGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T, U> Send for OwnedRwLockMappedWriteGuard<T, U>where
    T: ?Sized + Send + Sync,
    U: ?Sized + Send + Sync,
"],["impl<T: Send> Send for OnceCell<T>"]], +"typenum":[["impl Send for B0",1,["typenum::bit::B0"]],["impl Send for B1",1,["typenum::bit::B1"]],["impl<U> Send for PInt<U>where
    U: Send,
",1,["typenum::int::PInt"]],["impl<U> Send for NInt<U>where
    U: Send,
",1,["typenum::int::NInt"]],["impl Send for Z0",1,["typenum::int::Z0"]],["impl Send for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Send for UInt<U, B>where
    B: Send,
    U: Send,
",1,["typenum::uint::UInt"]],["impl Send for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Send for TArr<V, A>where
    A: Send,
    V: Send,
",1,["typenum::array::TArr"]],["impl Send for Greater",1,["typenum::Greater"]],["impl Send for Less",1,["typenum::Less"]],["impl Send for Equal",1,["typenum::Equal"]]], +"uint":[["impl Send for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Send for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Send for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Send for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.StructuralEq.js b/docs/implementors/core/marker/trait.StructuralEq.js new file mode 100644 index 000000000000..3a20a20ed347 --- /dev/null +++ b/docs/implementors/core/marker/trait.StructuralEq.js @@ -0,0 +1,29 @@ +(function() {var implementors = { +"aho_corasick":[["impl StructuralEq for MatchKind"],["impl StructuralEq for MatchKind"],["impl StructuralEq for Match"]], +"block_buffer":[["impl StructuralEq for Error"]], +"byteorder":[["impl StructuralEq for BigEndian"],["impl StructuralEq for LittleEndian"]], +"crossbeam_channel":[["impl<T> StructuralEq for SendError<T>"],["impl<T> StructuralEq for TrySendError<T>"],["impl<T> StructuralEq for SendTimeoutError<T>"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for RecvTimeoutError"],["impl StructuralEq for TrySelectError"],["impl StructuralEq for SelectTimeoutError"],["impl StructuralEq for TryReadyError"],["impl StructuralEq for ReadyTimeoutError"]], +"crossbeam_utils":[["impl<T> StructuralEq for CachePadded<T>"]], +"crypto_common":[["impl StructuralEq for InvalidLength"]], +"digest":[["impl StructuralEq for InvalidBufferSize"]], +"firewood":[["impl StructuralEq for Hash"],["impl StructuralEq for PartialPath"],["impl StructuralEq for Node"]], +"futures_channel":[["impl StructuralEq for SendError"],["impl<T> StructuralEq for TrySendError<T>"],["impl StructuralEq for Canceled"]], +"futures_util":[["impl<T, E> StructuralEq for TryChunksError<T, E>"],["impl StructuralEq for PollNext"],["impl<T> StructuralEq for AllowStdIo<T>"],["impl StructuralEq for Aborted"]], +"getrandom":[["impl StructuralEq for Error"]], +"growthring":[["impl StructuralEq for WALRingId"]], +"hashbrown":[["impl StructuralEq for TryReserveError"]], +"libc":[["impl StructuralEq for group"],["impl StructuralEq for utimbuf"],["impl StructuralEq for timeval"],["impl StructuralEq for timespec"],["impl StructuralEq for rlimit"],["impl StructuralEq for rusage"],["impl StructuralEq for ipv6_mreq"],["impl StructuralEq for hostent"],["impl StructuralEq for iovec"],["impl StructuralEq for pollfd"],["impl StructuralEq for winsize"],["impl StructuralEq for linger"],["impl StructuralEq for sigval"],["impl StructuralEq for itimerval"],["impl StructuralEq for tms"],["impl StructuralEq for servent"],["impl StructuralEq for protoent"],["impl StructuralEq for in_addr"],["impl StructuralEq for ip_mreq"],["impl StructuralEq for ip_mreqn"],["impl StructuralEq for ip_mreq_source"],["impl StructuralEq for sockaddr"],["impl StructuralEq for sockaddr_in"],["impl StructuralEq for sockaddr_in6"],["impl StructuralEq for addrinfo"],["impl StructuralEq for sockaddr_ll"],["impl StructuralEq for fd_set"],["impl StructuralEq for tm"],["impl StructuralEq for sched_param"],["impl StructuralEq for Dl_info"],["impl StructuralEq for lconv"],["impl StructuralEq for in_pktinfo"],["impl StructuralEq for ifaddrs"],["impl StructuralEq for in6_rtmsg"],["impl StructuralEq for arpreq"],["impl StructuralEq for arpreq_old"],["impl StructuralEq for arphdr"],["impl StructuralEq for mmsghdr"],["impl StructuralEq for rlimit64"],["impl StructuralEq for glob_t"],["impl StructuralEq for passwd"],["impl StructuralEq for spwd"],["impl StructuralEq for dqblk"],["impl StructuralEq for signalfd_siginfo"],["impl StructuralEq for itimerspec"],["impl StructuralEq for fsid_t"],["impl StructuralEq for packet_mreq"],["impl StructuralEq for cpu_set_t"],["impl StructuralEq for if_nameindex"],["impl StructuralEq for msginfo"],["impl StructuralEq for sembuf"],["impl StructuralEq for input_event"],["impl StructuralEq for input_id"],["impl StructuralEq for input_absinfo"],["impl StructuralEq for input_keymap_entry"],["impl StructuralEq for input_mask"],["impl StructuralEq for ff_replay"],["impl StructuralEq for ff_trigger"],["impl StructuralEq for ff_envelope"],["impl StructuralEq for ff_constant_effect"],["impl StructuralEq for ff_ramp_effect"],["impl StructuralEq for ff_condition_effect"],["impl StructuralEq for ff_periodic_effect"],["impl StructuralEq for ff_rumble_effect"],["impl StructuralEq for ff_effect"],["impl StructuralEq for uinput_ff_upload"],["impl StructuralEq for uinput_ff_erase"],["impl StructuralEq for uinput_abs_setup"],["impl StructuralEq for dl_phdr_info"],["impl StructuralEq for Elf32_Ehdr"],["impl StructuralEq for Elf64_Ehdr"],["impl StructuralEq for Elf32_Sym"],["impl StructuralEq for Elf64_Sym"],["impl StructuralEq for Elf32_Phdr"],["impl StructuralEq for Elf64_Phdr"],["impl StructuralEq for Elf32_Shdr"],["impl StructuralEq for Elf64_Shdr"],["impl StructuralEq for ucred"],["impl StructuralEq for mntent"],["impl StructuralEq for posix_spawn_file_actions_t"],["impl StructuralEq for posix_spawnattr_t"],["impl StructuralEq for genlmsghdr"],["impl StructuralEq for in6_pktinfo"],["impl StructuralEq for arpd_request"],["impl StructuralEq for inotify_event"],["impl StructuralEq for fanotify_response"],["impl StructuralEq for sockaddr_vm"],["impl StructuralEq for regmatch_t"],["impl StructuralEq for sock_extended_err"],["impl StructuralEq for __c_anonymous_sockaddr_can_tp"],["impl StructuralEq for __c_anonymous_sockaddr_can_j1939"],["impl StructuralEq for can_filter"],["impl StructuralEq for j1939_filter"],["impl StructuralEq for sock_filter"],["impl StructuralEq for sock_fprog"],["impl StructuralEq for seccomp_data"],["impl StructuralEq for nlmsghdr"],["impl StructuralEq for nlmsgerr"],["impl StructuralEq for nlattr"],["impl StructuralEq for file_clone_range"],["impl StructuralEq for __c_anonymous_ifru_map"],["impl StructuralEq for in6_ifreq"],["impl StructuralEq for statx"],["impl StructuralEq for statx_timestamp"],["impl StructuralEq for aiocb"],["impl StructuralEq for __exit_status"],["impl StructuralEq for __timeval"],["impl StructuralEq for glob64_t"],["impl StructuralEq for msghdr"],["impl StructuralEq for cmsghdr"],["impl StructuralEq for termios"],["impl StructuralEq for mallinfo"],["impl StructuralEq for mallinfo2"],["impl StructuralEq for nl_pktinfo"],["impl StructuralEq for nl_mmap_req"],["impl StructuralEq for nl_mmap_hdr"],["impl StructuralEq for rtentry"],["impl StructuralEq for timex"],["impl StructuralEq for ntptimeval"],["impl StructuralEq for regex_t"],["impl StructuralEq for Elf64_Chdr"],["impl StructuralEq for Elf32_Chdr"],["impl StructuralEq for seminfo"],["impl StructuralEq for ptrace_peeksiginfo_args"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_entry"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_exit"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl StructuralEq for ptrace_syscall_info"],["impl StructuralEq for sigset_t"],["impl StructuralEq for sysinfo"],["impl StructuralEq for msqid_ds"],["impl StructuralEq for semid_ds"],["impl StructuralEq for sigaction"],["impl StructuralEq for statfs"],["impl StructuralEq for flock"],["impl StructuralEq for flock64"],["impl StructuralEq for siginfo_t"],["impl StructuralEq for stack_t"],["impl StructuralEq for stat"],["impl StructuralEq for stat64"],["impl StructuralEq for statfs64"],["impl StructuralEq for statvfs64"],["impl StructuralEq for pthread_attr_t"],["impl StructuralEq for _libc_fpxreg"],["impl StructuralEq for _libc_xmmreg"],["impl StructuralEq for _libc_fpstate"],["impl StructuralEq for user_regs_struct"],["impl StructuralEq for user"],["impl StructuralEq for mcontext_t"],["impl StructuralEq for ipc_perm"],["impl StructuralEq for shmid_ds"],["impl StructuralEq for seccomp_notif_sizes"],["impl StructuralEq for ptrace_rseq_configuration"],["impl StructuralEq for statvfs"],["impl StructuralEq for clone_args"],["impl StructuralEq for sem_t"],["impl StructuralEq for termios2"],["impl StructuralEq for pthread_mutexattr_t"],["impl StructuralEq for pthread_rwlockattr_t"],["impl StructuralEq for pthread_condattr_t"],["impl StructuralEq for fanotify_event_metadata"],["impl StructuralEq for open_how"],["impl StructuralEq for in6_addr"]], +"nix":[["impl StructuralEq for Dir"],["impl<'d> StructuralEq for Iter<'d>"],["impl StructuralEq for OwningIter"],["impl StructuralEq for Entry"],["impl StructuralEq for Type"],["impl StructuralEq for Errno"],["impl StructuralEq for AtFlags"],["impl StructuralEq for OFlag"],["impl StructuralEq for RenameFlags"],["impl StructuralEq for SealFlag"],["impl StructuralEq for FdFlag"],["impl<'a> StructuralEq for FcntlArg<'a>"],["impl StructuralEq for FlockArg"],["impl StructuralEq for SpliceFFlags"],["impl StructuralEq for FallocateFlags"],["impl StructuralEq for PosixFadviseAdvice"],["impl StructuralEq for InterfaceAddress"],["impl StructuralEq for InterfaceAddressIterator"],["impl StructuralEq for InterfaceFlags"],["impl StructuralEq for ModuleInitFlags"],["impl StructuralEq for DeleteModuleFlags"],["impl StructuralEq for MsFlags"],["impl StructuralEq for MntFlags"],["impl StructuralEq for MQ_OFlag"],["impl StructuralEq for MqAttr"],["impl StructuralEq for PollFd"],["impl StructuralEq for PollFlags"],["impl StructuralEq for OpenptyResult"],["impl StructuralEq for PtyMaster"],["impl StructuralEq for CloneFlags"],["impl StructuralEq for CpuSet"],["impl StructuralEq for AioFsyncMode"],["impl StructuralEq for LioMode"],["impl StructuralEq for AioCancelStat"],["impl StructuralEq for EpollFlags"],["impl StructuralEq for EpollOp"],["impl StructuralEq for EpollCreateFlags"],["impl StructuralEq for EpollEvent"],["impl StructuralEq for EfdFlags"],["impl StructuralEq for MemFdCreateFlag"],["impl StructuralEq for ProtFlags"],["impl StructuralEq for MapFlags"],["impl StructuralEq for MRemapFlags"],["impl StructuralEq for MmapAdvise"],["impl StructuralEq for MsFlags"],["impl StructuralEq for MlockAllFlags"],["impl StructuralEq for Persona"],["impl StructuralEq for Request"],["impl StructuralEq for Event"],["impl StructuralEq for Options"],["impl StructuralEq for QuotaType"],["impl StructuralEq for QuotaFmt"],["impl StructuralEq for QuotaValidFlags"],["impl StructuralEq for Dqblk"],["impl StructuralEq for RebootMode"],["impl StructuralEq for Resource"],["impl StructuralEq for UsageWho"],["impl StructuralEq for Usage"],["impl StructuralEq for FdSet"],["impl StructuralEq for Signal"],["impl StructuralEq for SignalIterator"],["impl StructuralEq for SaFlags"],["impl StructuralEq for SigmaskHow"],["impl StructuralEq for SigSet"],["impl StructuralEq for SigHandler"],["impl StructuralEq for SigAction"],["impl StructuralEq for SigevNotify"],["impl StructuralEq for SigEvent"],["impl StructuralEq for SfdFlags"],["impl StructuralEq for SignalFd"],["impl StructuralEq for AddressFamily"],["impl StructuralEq for InetAddr"],["impl StructuralEq for IpAddr"],["impl StructuralEq for Ipv4Addr"],["impl StructuralEq for Ipv6Addr"],["impl StructuralEq for SockaddrIn"],["impl StructuralEq for SockaddrIn6"],["impl StructuralEq for SockAddr"],["impl StructuralEq for NetlinkAddr"],["impl StructuralEq for LinkAddr"],["impl StructuralEq for ReuseAddr"],["impl StructuralEq for ReusePort"],["impl StructuralEq for TcpNoDelay"],["impl StructuralEq for Linger"],["impl StructuralEq for IpAddMembership"],["impl StructuralEq for IpDropMembership"],["impl StructuralEq for Ipv6AddMembership"],["impl StructuralEq for Ipv6DropMembership"],["impl StructuralEq for IpMulticastTtl"],["impl StructuralEq for IpMulticastLoop"],["impl StructuralEq for Priority"],["impl StructuralEq for IpTos"],["impl StructuralEq for Ipv6TClass"],["impl StructuralEq for IpFreebind"],["impl StructuralEq for ReceiveTimeout"],["impl StructuralEq for SendTimeout"],["impl StructuralEq for Broadcast"],["impl StructuralEq for OobInline"],["impl StructuralEq for SocketError"],["impl StructuralEq for DontRoute"],["impl StructuralEq for KeepAlive"],["impl StructuralEq for PeerCredentials"],["impl StructuralEq for TcpKeepIdle"],["impl StructuralEq for TcpMaxSeg"],["impl StructuralEq for TcpKeepCount"],["impl StructuralEq for TcpRepair"],["impl StructuralEq for TcpKeepInterval"],["impl StructuralEq for TcpUserTimeout"],["impl StructuralEq for RcvBuf"],["impl StructuralEq for SndBuf"],["impl StructuralEq for RcvBufForce"],["impl StructuralEq for SndBufForce"],["impl StructuralEq for SockType"],["impl StructuralEq for AcceptConn"],["impl StructuralEq for BindToDevice"],["impl StructuralEq for OriginalDst"],["impl StructuralEq for Ip6tOriginalDst"],["impl StructuralEq for Timestamping"],["impl StructuralEq for ReceiveTimestamp"],["impl StructuralEq for ReceiveTimestampns"],["impl StructuralEq for IpTransparent"],["impl StructuralEq for Mark"],["impl StructuralEq for PassCred"],["impl StructuralEq for TcpCongestion"],["impl StructuralEq for Ipv4PacketInfo"],["impl StructuralEq for Ipv6RecvPacketInfo"],["impl StructuralEq for Ipv4OrigDstAddr"],["impl StructuralEq for UdpGsoSegment"],["impl StructuralEq for UdpGroSegment"],["impl StructuralEq for TxTime"],["impl StructuralEq for RxqOvfl"],["impl StructuralEq for Ipv6V6Only"],["impl StructuralEq for Ipv4RecvErr"],["impl StructuralEq for Ipv6RecvErr"],["impl StructuralEq for IpMtu"],["impl StructuralEq for Ipv4Ttl"],["impl StructuralEq for Ipv6Ttl"],["impl StructuralEq for Ipv6OrigDstAddr"],["impl StructuralEq for Ipv6DontFrag"],["impl StructuralEq for SockType"],["impl StructuralEq for SockProtocol"],["impl StructuralEq for TimestampingFlag"],["impl StructuralEq for SockFlag"],["impl StructuralEq for MsgFlags"],["impl StructuralEq for UnixCredentials"],["impl StructuralEq for IpMembershipRequest"],["impl StructuralEq for Ipv6MembershipRequest"],["impl<'a, 's, S> StructuralEq for RecvMsg<'a, 's, S>"],["impl<'a> StructuralEq for CmsgIterator<'a>"],["impl StructuralEq for ControlMessageOwned"],["impl StructuralEq for Timestamps"],["impl<'a> StructuralEq for ControlMessage<'a>"],["impl StructuralEq for Shutdown"],["impl StructuralEq for SFlag"],["impl StructuralEq for Mode"],["impl StructuralEq for FsType"],["impl StructuralEq for FsFlags"],["impl StructuralEq for Statvfs"],["impl StructuralEq for SysInfo"],["impl StructuralEq for Termios"],["impl StructuralEq for BaudRate"],["impl StructuralEq for SetArg"],["impl StructuralEq for FlushArg"],["impl StructuralEq for FlowArg"],["impl StructuralEq for SpecialCharacterIndices"],["impl StructuralEq for InputFlags"],["impl StructuralEq for OutputFlags"],["impl StructuralEq for ControlFlags"],["impl StructuralEq for LocalFlags"],["impl StructuralEq for Expiration"],["impl StructuralEq for TimerSetTimeFlags"],["impl StructuralEq for TimeSpec"],["impl StructuralEq for TimeVal"],["impl StructuralEq for RemoteIoVec"],["impl<T> StructuralEq for IoVec<T>"],["impl StructuralEq for UtsName"],["impl StructuralEq for WaitPidFlag"],["impl StructuralEq for WaitStatus"],["impl StructuralEq for Id"],["impl StructuralEq for AddWatchFlags"],["impl StructuralEq for InitFlags"],["impl StructuralEq for WatchDescriptor"],["impl StructuralEq for ClockId"],["impl StructuralEq for TimerFlags"],["impl StructuralEq for ClockId"],["impl StructuralEq for UContext"],["impl StructuralEq for Uid"],["impl StructuralEq for Gid"],["impl StructuralEq for Pid"],["impl StructuralEq for PathconfVar"],["impl StructuralEq for SysconfVar"],["impl StructuralEq for ResUid"],["impl StructuralEq for ResGid"],["impl StructuralEq for AccessFlags"],["impl StructuralEq for User"],["impl StructuralEq for Group"]], +"parking_lot":[["impl StructuralEq for WaitTimeoutResult"],["impl StructuralEq for OnceState"]], +"parking_lot_core":[["impl StructuralEq for ParkResult"],["impl StructuralEq for UnparkResult"],["impl StructuralEq for RequeueOp"],["impl StructuralEq for FilterOp"],["impl StructuralEq for UnparkToken"],["impl StructuralEq for ParkToken"]], +"primitive_types":[["impl StructuralEq for Error"],["impl StructuralEq for U128"],["impl StructuralEq for U256"],["impl StructuralEq for U512"]], +"proc_macro2":[["impl StructuralEq for Delimiter"],["impl StructuralEq for Spacing"]], +"rand":[["impl StructuralEq for BernoulliError"],["impl StructuralEq for WeightedError"],["impl StructuralEq for StepRng"]], +"rand_chacha":[["impl StructuralEq for ChaCha20Core"],["impl StructuralEq for ChaCha12Core"],["impl StructuralEq for ChaCha8Core"]], +"regex":[["impl<'t> StructuralEq for Match<'t>"],["impl<'t> StructuralEq for Match<'t>"]], +"regex_syntax":[["impl StructuralEq for Error"],["impl StructuralEq for ErrorKind"],["impl StructuralEq for Span"],["impl StructuralEq for Position"],["impl StructuralEq for WithComments"],["impl StructuralEq for Comment"],["impl StructuralEq for Ast"],["impl StructuralEq for Alternation"],["impl StructuralEq for Concat"],["impl StructuralEq for Literal"],["impl StructuralEq for LiteralKind"],["impl StructuralEq for SpecialLiteralKind"],["impl StructuralEq for HexLiteralKind"],["impl StructuralEq for Class"],["impl StructuralEq for ClassPerl"],["impl StructuralEq for ClassPerlKind"],["impl StructuralEq for ClassAscii"],["impl StructuralEq for ClassAsciiKind"],["impl StructuralEq for ClassUnicode"],["impl StructuralEq for ClassUnicodeKind"],["impl StructuralEq for ClassUnicodeOpKind"],["impl StructuralEq for ClassBracketed"],["impl StructuralEq for ClassSet"],["impl StructuralEq for ClassSetItem"],["impl StructuralEq for ClassSetRange"],["impl StructuralEq for ClassSetUnion"],["impl StructuralEq for ClassSetBinaryOp"],["impl StructuralEq for ClassSetBinaryOpKind"],["impl StructuralEq for Assertion"],["impl StructuralEq for AssertionKind"],["impl StructuralEq for Repetition"],["impl StructuralEq for RepetitionOp"],["impl StructuralEq for RepetitionKind"],["impl StructuralEq for RepetitionRange"],["impl StructuralEq for Group"],["impl StructuralEq for GroupKind"],["impl StructuralEq for CaptureName"],["impl StructuralEq for SetFlags"],["impl StructuralEq for Flags"],["impl StructuralEq for FlagsItem"],["impl StructuralEq for FlagsItemKind"],["impl StructuralEq for Flag"],["impl StructuralEq for Error"],["impl StructuralEq for Literals"],["impl StructuralEq for Literal"],["impl StructuralEq for Error"],["impl StructuralEq for ErrorKind"],["impl StructuralEq for Hir"],["impl StructuralEq for HirKind"],["impl StructuralEq for Literal"],["impl StructuralEq for Class"],["impl StructuralEq for ClassUnicode"],["impl StructuralEq for ClassUnicodeRange"],["impl StructuralEq for ClassBytes"],["impl StructuralEq for ClassBytesRange"],["impl StructuralEq for Anchor"],["impl StructuralEq for WordBoundary"],["impl StructuralEq for Group"],["impl StructuralEq for GroupKind"],["impl StructuralEq for Repetition"],["impl StructuralEq for RepetitionKind"],["impl StructuralEq for RepetitionRange"],["impl StructuralEq for Utf8Sequence"],["impl StructuralEq for Utf8Range"]], +"rlp":[["impl StructuralEq for DecoderError"]], +"tokio":[["impl StructuralEq for RuntimeFlavor"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl<T> StructuralEq for TrySendError<T>"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for TryAcquireError"],["impl<T> StructuralEq for SetError<T>"]], +"typenum":[["impl StructuralEq for B0"],["impl StructuralEq for B1"],["impl<U: Unsigned + NonZero> StructuralEq for PInt<U>"],["impl<U: Unsigned + NonZero> StructuralEq for NInt<U>"],["impl StructuralEq for Z0"],["impl StructuralEq for UTerm"],["impl<U, B> StructuralEq for UInt<U, B>"],["impl StructuralEq for ATerm"],["impl<V, A> StructuralEq for TArr<V, A>"],["impl StructuralEq for Greater"],["impl StructuralEq for Less"],["impl StructuralEq for Equal"]], +"uint":[["impl StructuralEq for FromStrRadixErrKind"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.StructuralPartialEq.js b/docs/implementors/core/marker/trait.StructuralPartialEq.js new file mode 100644 index 000000000000..1ccfa885910d --- /dev/null +++ b/docs/implementors/core/marker/trait.StructuralPartialEq.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"aho_corasick":[["impl StructuralPartialEq for MatchKind"],["impl StructuralPartialEq for MatchKind"],["impl StructuralPartialEq for Match"]], +"block_buffer":[["impl StructuralPartialEq for Error"]], +"byteorder":[["impl StructuralPartialEq for BigEndian"],["impl StructuralPartialEq for LittleEndian"]], +"crossbeam_channel":[["impl<T> StructuralPartialEq for SendError<T>"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl<T> StructuralPartialEq for SendTimeoutError<T>"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for RecvTimeoutError"],["impl StructuralPartialEq for TrySelectError"],["impl StructuralPartialEq for SelectTimeoutError"],["impl StructuralPartialEq for TryReadyError"],["impl StructuralPartialEq for ReadyTimeoutError"]], +"crossbeam_utils":[["impl<T> StructuralPartialEq for CachePadded<T>"]], +"crypto_common":[["impl StructuralPartialEq for InvalidLength"]], +"digest":[["impl StructuralPartialEq for InvalidBufferSize"]], +"firewood":[["impl StructuralPartialEq for Hash"],["impl StructuralPartialEq for PartialPath"],["impl StructuralPartialEq for Node"]], +"futures_channel":[["impl StructuralPartialEq for SendError"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl StructuralPartialEq for Canceled"]], +"futures_util":[["impl<T, E> StructuralPartialEq for TryChunksError<T, E>"],["impl StructuralPartialEq for PollNext"],["impl<T> StructuralPartialEq for AllowStdIo<T>"],["impl StructuralPartialEq for Aborted"]], +"getrandom":[["impl StructuralPartialEq for Error"]], +"growthring":[["impl StructuralPartialEq for WALRingId"]], +"hashbrown":[["impl StructuralPartialEq for TryReserveError"]], +"hex":[["impl StructuralPartialEq for FromHexError"]], +"libc":[["impl StructuralPartialEq for group"],["impl StructuralPartialEq for utimbuf"],["impl StructuralPartialEq for timeval"],["impl StructuralPartialEq for timespec"],["impl StructuralPartialEq for rlimit"],["impl StructuralPartialEq for rusage"],["impl StructuralPartialEq for ipv6_mreq"],["impl StructuralPartialEq for hostent"],["impl StructuralPartialEq for iovec"],["impl StructuralPartialEq for pollfd"],["impl StructuralPartialEq for winsize"],["impl StructuralPartialEq for linger"],["impl StructuralPartialEq for sigval"],["impl StructuralPartialEq for itimerval"],["impl StructuralPartialEq for tms"],["impl StructuralPartialEq for servent"],["impl StructuralPartialEq for protoent"],["impl StructuralPartialEq for in_addr"],["impl StructuralPartialEq for ip_mreq"],["impl StructuralPartialEq for ip_mreqn"],["impl StructuralPartialEq for ip_mreq_source"],["impl StructuralPartialEq for sockaddr"],["impl StructuralPartialEq for sockaddr_in"],["impl StructuralPartialEq for sockaddr_in6"],["impl StructuralPartialEq for addrinfo"],["impl StructuralPartialEq for sockaddr_ll"],["impl StructuralPartialEq for fd_set"],["impl StructuralPartialEq for tm"],["impl StructuralPartialEq for sched_param"],["impl StructuralPartialEq for Dl_info"],["impl StructuralPartialEq for lconv"],["impl StructuralPartialEq for in_pktinfo"],["impl StructuralPartialEq for ifaddrs"],["impl StructuralPartialEq for in6_rtmsg"],["impl StructuralPartialEq for arpreq"],["impl StructuralPartialEq for arpreq_old"],["impl StructuralPartialEq for arphdr"],["impl StructuralPartialEq for mmsghdr"],["impl StructuralPartialEq for rlimit64"],["impl StructuralPartialEq for glob_t"],["impl StructuralPartialEq for passwd"],["impl StructuralPartialEq for spwd"],["impl StructuralPartialEq for dqblk"],["impl StructuralPartialEq for signalfd_siginfo"],["impl StructuralPartialEq for itimerspec"],["impl StructuralPartialEq for fsid_t"],["impl StructuralPartialEq for packet_mreq"],["impl StructuralPartialEq for cpu_set_t"],["impl StructuralPartialEq for if_nameindex"],["impl StructuralPartialEq for msginfo"],["impl StructuralPartialEq for sembuf"],["impl StructuralPartialEq for input_event"],["impl StructuralPartialEq for input_id"],["impl StructuralPartialEq for input_absinfo"],["impl StructuralPartialEq for input_keymap_entry"],["impl StructuralPartialEq for input_mask"],["impl StructuralPartialEq for ff_replay"],["impl StructuralPartialEq for ff_trigger"],["impl StructuralPartialEq for ff_envelope"],["impl StructuralPartialEq for ff_constant_effect"],["impl StructuralPartialEq for ff_ramp_effect"],["impl StructuralPartialEq for ff_condition_effect"],["impl StructuralPartialEq for ff_periodic_effect"],["impl StructuralPartialEq for ff_rumble_effect"],["impl StructuralPartialEq for ff_effect"],["impl StructuralPartialEq for uinput_ff_upload"],["impl StructuralPartialEq for uinput_ff_erase"],["impl StructuralPartialEq for uinput_abs_setup"],["impl StructuralPartialEq for dl_phdr_info"],["impl StructuralPartialEq for Elf32_Ehdr"],["impl StructuralPartialEq for Elf64_Ehdr"],["impl StructuralPartialEq for Elf32_Sym"],["impl StructuralPartialEq for Elf64_Sym"],["impl StructuralPartialEq for Elf32_Phdr"],["impl StructuralPartialEq for Elf64_Phdr"],["impl StructuralPartialEq for Elf32_Shdr"],["impl StructuralPartialEq for Elf64_Shdr"],["impl StructuralPartialEq for ucred"],["impl StructuralPartialEq for mntent"],["impl StructuralPartialEq for posix_spawn_file_actions_t"],["impl StructuralPartialEq for posix_spawnattr_t"],["impl StructuralPartialEq for genlmsghdr"],["impl StructuralPartialEq for in6_pktinfo"],["impl StructuralPartialEq for arpd_request"],["impl StructuralPartialEq for inotify_event"],["impl StructuralPartialEq for fanotify_response"],["impl StructuralPartialEq for sockaddr_vm"],["impl StructuralPartialEq for regmatch_t"],["impl StructuralPartialEq for sock_extended_err"],["impl StructuralPartialEq for __c_anonymous_sockaddr_can_tp"],["impl StructuralPartialEq for __c_anonymous_sockaddr_can_j1939"],["impl StructuralPartialEq for can_filter"],["impl StructuralPartialEq for j1939_filter"],["impl StructuralPartialEq for sock_filter"],["impl StructuralPartialEq for sock_fprog"],["impl StructuralPartialEq for seccomp_data"],["impl StructuralPartialEq for nlmsghdr"],["impl StructuralPartialEq for nlmsgerr"],["impl StructuralPartialEq for nlattr"],["impl StructuralPartialEq for file_clone_range"],["impl StructuralPartialEq for __c_anonymous_ifru_map"],["impl StructuralPartialEq for in6_ifreq"],["impl StructuralPartialEq for statx"],["impl StructuralPartialEq for statx_timestamp"],["impl StructuralPartialEq for aiocb"],["impl StructuralPartialEq for __exit_status"],["impl StructuralPartialEq for __timeval"],["impl StructuralPartialEq for glob64_t"],["impl StructuralPartialEq for msghdr"],["impl StructuralPartialEq for cmsghdr"],["impl StructuralPartialEq for termios"],["impl StructuralPartialEq for mallinfo"],["impl StructuralPartialEq for mallinfo2"],["impl StructuralPartialEq for nl_pktinfo"],["impl StructuralPartialEq for nl_mmap_req"],["impl StructuralPartialEq for nl_mmap_hdr"],["impl StructuralPartialEq for rtentry"],["impl StructuralPartialEq for timex"],["impl StructuralPartialEq for ntptimeval"],["impl StructuralPartialEq for regex_t"],["impl StructuralPartialEq for Elf64_Chdr"],["impl StructuralPartialEq for Elf32_Chdr"],["impl StructuralPartialEq for seminfo"],["impl StructuralPartialEq for ptrace_peeksiginfo_args"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_entry"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_exit"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl StructuralPartialEq for ptrace_syscall_info"],["impl StructuralPartialEq for sigset_t"],["impl StructuralPartialEq for sysinfo"],["impl StructuralPartialEq for msqid_ds"],["impl StructuralPartialEq for semid_ds"],["impl StructuralPartialEq for sigaction"],["impl StructuralPartialEq for statfs"],["impl StructuralPartialEq for flock"],["impl StructuralPartialEq for flock64"],["impl StructuralPartialEq for siginfo_t"],["impl StructuralPartialEq for stack_t"],["impl StructuralPartialEq for stat"],["impl StructuralPartialEq for stat64"],["impl StructuralPartialEq for statfs64"],["impl StructuralPartialEq for statvfs64"],["impl StructuralPartialEq for pthread_attr_t"],["impl StructuralPartialEq for _libc_fpxreg"],["impl StructuralPartialEq for _libc_xmmreg"],["impl StructuralPartialEq for _libc_fpstate"],["impl StructuralPartialEq for user_regs_struct"],["impl StructuralPartialEq for user"],["impl StructuralPartialEq for mcontext_t"],["impl StructuralPartialEq for ipc_perm"],["impl StructuralPartialEq for shmid_ds"],["impl StructuralPartialEq for seccomp_notif_sizes"],["impl StructuralPartialEq for ptrace_rseq_configuration"],["impl StructuralPartialEq for statvfs"],["impl StructuralPartialEq for clone_args"],["impl StructuralPartialEq for sem_t"],["impl StructuralPartialEq for termios2"],["impl StructuralPartialEq for pthread_mutexattr_t"],["impl StructuralPartialEq for pthread_rwlockattr_t"],["impl StructuralPartialEq for pthread_condattr_t"],["impl StructuralPartialEq for fanotify_event_metadata"],["impl StructuralPartialEq for open_how"],["impl StructuralPartialEq for in6_addr"]], +"nix":[["impl StructuralPartialEq for Dir"],["impl<'d> StructuralPartialEq for Iter<'d>"],["impl StructuralPartialEq for OwningIter"],["impl StructuralPartialEq for Entry"],["impl StructuralPartialEq for Type"],["impl StructuralPartialEq for Errno"],["impl StructuralPartialEq for AtFlags"],["impl StructuralPartialEq for OFlag"],["impl StructuralPartialEq for RenameFlags"],["impl StructuralPartialEq for SealFlag"],["impl StructuralPartialEq for FdFlag"],["impl<'a> StructuralPartialEq for FcntlArg<'a>"],["impl StructuralPartialEq for FlockArg"],["impl StructuralPartialEq for SpliceFFlags"],["impl StructuralPartialEq for FallocateFlags"],["impl StructuralPartialEq for PosixFadviseAdvice"],["impl StructuralPartialEq for InterfaceAddress"],["impl StructuralPartialEq for InterfaceAddressIterator"],["impl StructuralPartialEq for InterfaceFlags"],["impl StructuralPartialEq for ModuleInitFlags"],["impl StructuralPartialEq for DeleteModuleFlags"],["impl StructuralPartialEq for MsFlags"],["impl StructuralPartialEq for MntFlags"],["impl StructuralPartialEq for MQ_OFlag"],["impl StructuralPartialEq for MqAttr"],["impl StructuralPartialEq for PollFd"],["impl StructuralPartialEq for PollFlags"],["impl StructuralPartialEq for OpenptyResult"],["impl StructuralPartialEq for PtyMaster"],["impl StructuralPartialEq for CloneFlags"],["impl StructuralPartialEq for CpuSet"],["impl StructuralPartialEq for AioFsyncMode"],["impl StructuralPartialEq for LioMode"],["impl StructuralPartialEq for AioCancelStat"],["impl StructuralPartialEq for EpollFlags"],["impl StructuralPartialEq for EpollOp"],["impl StructuralPartialEq for EpollCreateFlags"],["impl StructuralPartialEq for EpollEvent"],["impl StructuralPartialEq for EfdFlags"],["impl StructuralPartialEq for MemFdCreateFlag"],["impl StructuralPartialEq for ProtFlags"],["impl StructuralPartialEq for MapFlags"],["impl StructuralPartialEq for MRemapFlags"],["impl StructuralPartialEq for MmapAdvise"],["impl StructuralPartialEq for MsFlags"],["impl StructuralPartialEq for MlockAllFlags"],["impl StructuralPartialEq for Persona"],["impl StructuralPartialEq for Request"],["impl StructuralPartialEq for Event"],["impl StructuralPartialEq for Options"],["impl StructuralPartialEq for QuotaType"],["impl StructuralPartialEq for QuotaFmt"],["impl StructuralPartialEq for QuotaValidFlags"],["impl StructuralPartialEq for Dqblk"],["impl StructuralPartialEq for RebootMode"],["impl StructuralPartialEq for Resource"],["impl StructuralPartialEq for UsageWho"],["impl StructuralPartialEq for Usage"],["impl StructuralPartialEq for FdSet"],["impl StructuralPartialEq for Signal"],["impl StructuralPartialEq for SignalIterator"],["impl StructuralPartialEq for SaFlags"],["impl StructuralPartialEq for SigmaskHow"],["impl StructuralPartialEq for SigSet"],["impl StructuralPartialEq for SigHandler"],["impl StructuralPartialEq for SigAction"],["impl StructuralPartialEq for SigevNotify"],["impl StructuralPartialEq for SigEvent"],["impl StructuralPartialEq for SfdFlags"],["impl StructuralPartialEq for SignalFd"],["impl StructuralPartialEq for AddressFamily"],["impl StructuralPartialEq for InetAddr"],["impl StructuralPartialEq for IpAddr"],["impl StructuralPartialEq for Ipv4Addr"],["impl StructuralPartialEq for Ipv6Addr"],["impl StructuralPartialEq for SockaddrIn"],["impl StructuralPartialEq for SockaddrIn6"],["impl StructuralPartialEq for SockAddr"],["impl StructuralPartialEq for NetlinkAddr"],["impl StructuralPartialEq for LinkAddr"],["impl StructuralPartialEq for ReuseAddr"],["impl StructuralPartialEq for ReusePort"],["impl StructuralPartialEq for TcpNoDelay"],["impl StructuralPartialEq for Linger"],["impl StructuralPartialEq for IpAddMembership"],["impl StructuralPartialEq for IpDropMembership"],["impl StructuralPartialEq for Ipv6AddMembership"],["impl StructuralPartialEq for Ipv6DropMembership"],["impl StructuralPartialEq for IpMulticastTtl"],["impl StructuralPartialEq for IpMulticastLoop"],["impl StructuralPartialEq for Priority"],["impl StructuralPartialEq for IpTos"],["impl StructuralPartialEq for Ipv6TClass"],["impl StructuralPartialEq for IpFreebind"],["impl StructuralPartialEq for ReceiveTimeout"],["impl StructuralPartialEq for SendTimeout"],["impl StructuralPartialEq for Broadcast"],["impl StructuralPartialEq for OobInline"],["impl StructuralPartialEq for SocketError"],["impl StructuralPartialEq for DontRoute"],["impl StructuralPartialEq for KeepAlive"],["impl StructuralPartialEq for PeerCredentials"],["impl StructuralPartialEq for TcpKeepIdle"],["impl StructuralPartialEq for TcpMaxSeg"],["impl StructuralPartialEq for TcpKeepCount"],["impl StructuralPartialEq for TcpRepair"],["impl StructuralPartialEq for TcpKeepInterval"],["impl StructuralPartialEq for TcpUserTimeout"],["impl StructuralPartialEq for RcvBuf"],["impl StructuralPartialEq for SndBuf"],["impl StructuralPartialEq for RcvBufForce"],["impl StructuralPartialEq for SndBufForce"],["impl StructuralPartialEq for SockType"],["impl StructuralPartialEq for AcceptConn"],["impl StructuralPartialEq for BindToDevice"],["impl StructuralPartialEq for OriginalDst"],["impl StructuralPartialEq for Ip6tOriginalDst"],["impl StructuralPartialEq for Timestamping"],["impl StructuralPartialEq for ReceiveTimestamp"],["impl StructuralPartialEq for ReceiveTimestampns"],["impl StructuralPartialEq for IpTransparent"],["impl StructuralPartialEq for Mark"],["impl StructuralPartialEq for PassCred"],["impl StructuralPartialEq for TcpCongestion"],["impl StructuralPartialEq for Ipv4PacketInfo"],["impl StructuralPartialEq for Ipv6RecvPacketInfo"],["impl StructuralPartialEq for Ipv4OrigDstAddr"],["impl StructuralPartialEq for UdpGsoSegment"],["impl StructuralPartialEq for UdpGroSegment"],["impl StructuralPartialEq for TxTime"],["impl StructuralPartialEq for RxqOvfl"],["impl StructuralPartialEq for Ipv6V6Only"],["impl StructuralPartialEq for Ipv4RecvErr"],["impl StructuralPartialEq for Ipv6RecvErr"],["impl StructuralPartialEq for IpMtu"],["impl StructuralPartialEq for Ipv4Ttl"],["impl StructuralPartialEq for Ipv6Ttl"],["impl StructuralPartialEq for Ipv6OrigDstAddr"],["impl StructuralPartialEq for Ipv6DontFrag"],["impl StructuralPartialEq for SockType"],["impl StructuralPartialEq for SockProtocol"],["impl StructuralPartialEq for TimestampingFlag"],["impl StructuralPartialEq for SockFlag"],["impl StructuralPartialEq for MsgFlags"],["impl StructuralPartialEq for UnixCredentials"],["impl StructuralPartialEq for IpMembershipRequest"],["impl StructuralPartialEq for Ipv6MembershipRequest"],["impl<'a, 's, S> StructuralPartialEq for RecvMsg<'a, 's, S>"],["impl<'a> StructuralPartialEq for CmsgIterator<'a>"],["impl StructuralPartialEq for ControlMessageOwned"],["impl StructuralPartialEq for Timestamps"],["impl<'a> StructuralPartialEq for ControlMessage<'a>"],["impl StructuralPartialEq for Shutdown"],["impl StructuralPartialEq for SFlag"],["impl StructuralPartialEq for Mode"],["impl StructuralPartialEq for FsType"],["impl StructuralPartialEq for FsFlags"],["impl StructuralPartialEq for Statvfs"],["impl StructuralPartialEq for SysInfo"],["impl StructuralPartialEq for Termios"],["impl StructuralPartialEq for BaudRate"],["impl StructuralPartialEq for SetArg"],["impl StructuralPartialEq for FlushArg"],["impl StructuralPartialEq for FlowArg"],["impl StructuralPartialEq for SpecialCharacterIndices"],["impl StructuralPartialEq for InputFlags"],["impl StructuralPartialEq for OutputFlags"],["impl StructuralPartialEq for ControlFlags"],["impl StructuralPartialEq for LocalFlags"],["impl StructuralPartialEq for Expiration"],["impl StructuralPartialEq for TimerSetTimeFlags"],["impl StructuralPartialEq for TimeSpec"],["impl StructuralPartialEq for TimeVal"],["impl StructuralPartialEq for RemoteIoVec"],["impl<T> StructuralPartialEq for IoVec<T>"],["impl StructuralPartialEq for UtsName"],["impl StructuralPartialEq for WaitPidFlag"],["impl StructuralPartialEq for WaitStatus"],["impl StructuralPartialEq for Id"],["impl StructuralPartialEq for AddWatchFlags"],["impl StructuralPartialEq for InitFlags"],["impl StructuralPartialEq for WatchDescriptor"],["impl StructuralPartialEq for ClockId"],["impl StructuralPartialEq for TimerFlags"],["impl StructuralPartialEq for ClockId"],["impl StructuralPartialEq for UContext"],["impl StructuralPartialEq for Uid"],["impl StructuralPartialEq for Gid"],["impl StructuralPartialEq for Pid"],["impl StructuralPartialEq for PathconfVar"],["impl StructuralPartialEq for SysconfVar"],["impl StructuralPartialEq for ResUid"],["impl StructuralPartialEq for ResGid"],["impl StructuralPartialEq for AccessFlags"],["impl StructuralPartialEq for User"],["impl StructuralPartialEq for Group"]], +"parking_lot":[["impl StructuralPartialEq for WaitTimeoutResult"],["impl StructuralPartialEq for OnceState"]], +"parking_lot_core":[["impl StructuralPartialEq for ParkResult"],["impl StructuralPartialEq for UnparkResult"],["impl StructuralPartialEq for RequeueOp"],["impl StructuralPartialEq for FilterOp"],["impl StructuralPartialEq for UnparkToken"],["impl StructuralPartialEq for ParkToken"]], +"primitive_types":[["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for U128"],["impl StructuralPartialEq for U256"],["impl StructuralPartialEq for U512"]], +"proc_macro2":[["impl StructuralPartialEq for Delimiter"],["impl StructuralPartialEq for Spacing"]], +"rand":[["impl StructuralPartialEq for Bernoulli"],["impl StructuralPartialEq for BernoulliError"],["impl<X: SampleUniform + PartialOrd> StructuralPartialEq for WeightedIndex<X>"],["impl StructuralPartialEq for WeightedError"],["impl<X: SampleUniform> StructuralPartialEq for Uniform<X>"],["impl<X> StructuralPartialEq for UniformInt<X>"],["impl<X> StructuralPartialEq for UniformFloat<X>"],["impl StructuralPartialEq for StepRng"]], +"rand_chacha":[["impl StructuralPartialEq for ChaCha20Core"],["impl StructuralPartialEq for ChaCha12Core"],["impl StructuralPartialEq for ChaCha8Core"]], +"regex":[["impl StructuralPartialEq for Error"],["impl<'t> StructuralPartialEq for Match<'t>"],["impl<'t> StructuralPartialEq for Match<'t>"]], +"regex_syntax":[["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for ErrorKind"],["impl StructuralPartialEq for Span"],["impl StructuralPartialEq for Position"],["impl StructuralPartialEq for WithComments"],["impl StructuralPartialEq for Comment"],["impl StructuralPartialEq for Ast"],["impl StructuralPartialEq for Alternation"],["impl StructuralPartialEq for Concat"],["impl StructuralPartialEq for Literal"],["impl StructuralPartialEq for LiteralKind"],["impl StructuralPartialEq for SpecialLiteralKind"],["impl StructuralPartialEq for HexLiteralKind"],["impl StructuralPartialEq for Class"],["impl StructuralPartialEq for ClassPerl"],["impl StructuralPartialEq for ClassPerlKind"],["impl StructuralPartialEq for ClassAscii"],["impl StructuralPartialEq for ClassAsciiKind"],["impl StructuralPartialEq for ClassUnicode"],["impl StructuralPartialEq for ClassUnicodeKind"],["impl StructuralPartialEq for ClassUnicodeOpKind"],["impl StructuralPartialEq for ClassBracketed"],["impl StructuralPartialEq for ClassSet"],["impl StructuralPartialEq for ClassSetItem"],["impl StructuralPartialEq for ClassSetRange"],["impl StructuralPartialEq for ClassSetUnion"],["impl StructuralPartialEq for ClassSetBinaryOp"],["impl StructuralPartialEq for ClassSetBinaryOpKind"],["impl StructuralPartialEq for Assertion"],["impl StructuralPartialEq for AssertionKind"],["impl StructuralPartialEq for Repetition"],["impl StructuralPartialEq for RepetitionOp"],["impl StructuralPartialEq for RepetitionKind"],["impl StructuralPartialEq for RepetitionRange"],["impl StructuralPartialEq for Group"],["impl StructuralPartialEq for GroupKind"],["impl StructuralPartialEq for CaptureName"],["impl StructuralPartialEq for SetFlags"],["impl StructuralPartialEq for Flags"],["impl StructuralPartialEq for FlagsItem"],["impl StructuralPartialEq for FlagsItemKind"],["impl StructuralPartialEq for Flag"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for Literals"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for ErrorKind"],["impl StructuralPartialEq for Hir"],["impl StructuralPartialEq for HirKind"],["impl StructuralPartialEq for Literal"],["impl StructuralPartialEq for Class"],["impl StructuralPartialEq for ClassUnicode"],["impl StructuralPartialEq for ClassUnicodeRange"],["impl StructuralPartialEq for ClassBytes"],["impl StructuralPartialEq for ClassBytesRange"],["impl StructuralPartialEq for Anchor"],["impl StructuralPartialEq for WordBoundary"],["impl StructuralPartialEq for Group"],["impl StructuralPartialEq for GroupKind"],["impl StructuralPartialEq for Repetition"],["impl StructuralPartialEq for RepetitionKind"],["impl StructuralPartialEq for RepetitionRange"],["impl StructuralPartialEq for Utf8Sequence"],["impl StructuralPartialEq for Utf8Range"]], +"rlp":[["impl StructuralPartialEq for DecoderError"]], +"scan_fmt":[["impl StructuralPartialEq for ScanError"]], +"serde":[["impl StructuralPartialEq for Error"],["impl<'a> StructuralPartialEq for Unexpected<'a>"]], +"tokio":[["impl StructuralPartialEq for RuntimeFlavor"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for TryAcquireError"],["impl<T> StructuralPartialEq for SetError<T>"]], +"typenum":[["impl StructuralPartialEq for B0"],["impl StructuralPartialEq for B1"],["impl<U: Unsigned + NonZero> StructuralPartialEq for PInt<U>"],["impl<U: Unsigned + NonZero> StructuralPartialEq for NInt<U>"],["impl StructuralPartialEq for Z0"],["impl StructuralPartialEq for UTerm"],["impl<U, B> StructuralPartialEq for UInt<U, B>"],["impl StructuralPartialEq for ATerm"],["impl<V, A> StructuralPartialEq for TArr<V, A>"],["impl StructuralPartialEq for Greater"],["impl StructuralPartialEq for Less"],["impl StructuralPartialEq for Equal"]], +"uint":[["impl StructuralPartialEq for FromStrRadixErrKind"],["impl StructuralPartialEq for FromDecStrErr"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Sync.js b/docs/implementors/core/marker/trait.Sync.js new file mode 100644 index 000000000000..2162895de83e --- /dev/null +++ b/docs/implementors/core/marker/trait.Sync.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl Sync for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Sync for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> Sync for AhoCorasick<S>where
    S: Sync,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Sync for FindIter<'a, 'b, S>where
    S: Sync,
",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Sync for FindOverlappingIter<'a, 'b, S>where
    S: Sync,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Sync for StreamFindIter<'a, R, S>where
    R: Sync,
    S: Sync,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Sync for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Sync for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Sync for Error",1,["aho_corasick::error::Error"]],["impl Sync for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Sync for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Sync for Config",1,["aho_corasick::packed::api::Config"]],["impl Sync for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Sync for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Sync for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Sync for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl Sync for Error",1,["aiofut::Error"]],["impl Sync for AIO",1,["aiofut::AIO"]],["impl Sync for AIOFuture",1,["aiofut::AIOFuture"]],["impl Sync for AIONotifier",1,["aiofut::AIONotifier"]],["impl Sync for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !Sync for AIOManager",1,["aiofut::AIOManager"]],["impl !Sync for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Sync for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl Sync for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Sync for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Sync for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Sync for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Sync for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Sync for Config",1,["bincode::config::legacy::Config"]],["impl Sync for Bounded",1,["bincode::config::limit::Bounded"]],["impl Sync for Infinite",1,["bincode::config::limit::Infinite"]],["impl Sync for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Sync for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Sync for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Sync for WithOtherLimit<O, L>where
    L: Sync,
    O: Sync,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Sync for WithOtherEndian<O, E>where
    E: Sync,
    O: Sync,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Sync for WithOtherIntEncoding<O, I>where
    I: Sync,
    O: Sync,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Sync for WithOtherTrailing<O, T>where
    O: Sync,
    T: Sync,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Sync for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Sync for IoReader<R>where
    R: Sync,
",1,["bincode::de::read::IoReader"]],["impl<R, O> Sync for Deserializer<R, O>where
    O: Sync,
    R: Sync,
",1,["bincode::de::Deserializer"]],["impl Sync for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Sync for Serializer<W, O>where
    O: Sync,
    W: Sync,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl Sync for Eager",1,["block_buffer::Eager"]],["impl Sync for Lazy",1,["block_buffer::Lazy"]],["impl Sync for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Sync for BlockBuffer<BlockSize, Kind>where
    Kind: Sync,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl Sync for BigEndian",1,["byteorder::BigEndian"]],["impl Sync for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> Sync for Chain<T, U>where
    T: Sync,
    U: Sync,
",1,["bytes::buf::chain::Chain"]],["impl<T> Sync for IntoIter<T>where
    T: Sync,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> Sync for Limit<T>where
    T: Sync,
",1,["bytes::buf::limit::Limit"]],["impl<B> Sync for Reader<B>where
    B: Sync,
",1,["bytes::buf::reader::Reader"]],["impl<T> Sync for Take<T>where
    T: Sync,
",1,["bytes::buf::take::Take"]],["impl Sync for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Sync for Writer<B>where
    B: Sync,
",1,["bytes::buf::writer::Writer"]],["impl Sync for Bytes"],["impl Sync for BytesMut"]], +"crc":[["impl<W> Sync for Crc<W>where
    W: Sync,
",1,["crc::Crc"]],["impl<'a, W> Sync for Digest<'a, W>where
    W: Sync,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> Sync for Algorithm<W>where
    W: Sync,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<'a, T> Sync for Iter<'a, T>where
    T: Send,
",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Sync for TryIter<'a, T>where
    T: Send,
",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Sync for IntoIter<T>where
    T: Send,
",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Sync for SendError<T>where
    T: Sync,
",1,["crossbeam_channel::err::SendError"]],["impl<T> Sync for TrySendError<T>where
    T: Sync,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Sync for SendTimeoutError<T>where
    T: Sync,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Sync for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Sync for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Sync for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Sync for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Sync for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Sync for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Sync for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !Sync for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T: Send> Sync for Sender<T>"],["impl<T: Send> Sync for Receiver<T>"],["impl Sync for Select<'_>"]], +"crossbeam_utils":[["impl !Sync for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl !Sync for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Sync for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'scope, 'env> Sync for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<T: Send> Sync for AtomicCell<T>"],["impl<T: Sync> Sync for CachePadded<T>"],["impl Sync for Unparker"],["impl<T: ?Sized + Send + Sync> Sync for ShardedLock<T>"],["impl<T: ?Sized + Sync> Sync for ShardedLockReadGuard<'_, T>"],["impl<T: ?Sized + Sync> Sync for ShardedLockWriteGuard<'_, T>"],["impl Sync for Scope<'_>"],["impl<T> Sync for ScopedJoinHandle<'_, T>"]], +"crypto_common":[["impl Sync for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> Sync for CtVariableCoreWrapper<T, OutSize, O>where
    O: Sync,
    OutSize: Sync,
    T: Sync,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Sync for RtVariableCoreWrapper<T>where
    T: Sync,
    <T as BufferKindUser>::BufferKind: Sync,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Sync for CoreWrapper<T>where
    T: Sync,
    <T as BufferKindUser>::BufferKind: Sync,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Sync for XofReaderCoreWrapper<T>where
    T: Sync,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Sync for TruncSide",1,["digest::core_api::TruncSide"]],["impl Sync for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Sync for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl Sync for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Sync for WALConfig",1,["firewood::storage::WALConfig"]],["impl Sync for DBError",1,["firewood::db::DBError"]],["impl Sync for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Sync for DBConfig",1,["firewood::db::DBConfig"]],["impl !Sync for DBRev",1,["firewood::db::DBRev"]],["impl !Sync for DB",1,["firewood::db::DB"]],["impl<'a> !Sync for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !Sync for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Sync for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Sync for Hash",1,["firewood::merkle::Hash"]],["impl Sync for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !Sync for Node",1,["firewood::merkle::Node"]],["impl !Sync for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !Sync for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !Sync for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Sync for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Sync for Proof",1,["firewood::proof::Proof"]],["impl Sync for ProofError",1,["firewood::proof::ProofError"]],["impl Sync for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> Sync for Sender<T>where
    T: Send,
",1,["futures_channel::mpsc::Sender"]],["impl<T> Sync for UnboundedSender<T>where
    T: Send,
",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Sync for Receiver<T>where
    T: Send,
",1,["futures_channel::mpsc::Receiver"]],["impl<T> Sync for UnboundedReceiver<T>where
    T: Send,
",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Sync for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Sync for TrySendError<T>where
    T: Sync,
",1,["futures_channel::mpsc::TrySendError"]],["impl Sync for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Sync for Receiver<T>where
    T: Send,
",1,["futures_channel::oneshot::Receiver"]],["impl<T> Sync for Sender<T>where
    T: Send,
",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Sync for Cancellation<'a, T>where
    T: Send,
",1,["futures_channel::oneshot::Cancellation"]],["impl Sync for Canceled",1,["futures_channel::oneshot::Canceled"]]], +"futures_executor":[["impl !Sync for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !Sync for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Sync for BlockingStream<S>where
    S: Sync,
",1,["futures_executor::local_pool::BlockingStream"]],["impl Sync for Enter",1,["futures_executor::enter::Enter"]],["impl Sync for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl Sync for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Sync for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !Sync for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !Sync for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], +"futures_util":[["impl<Fut> Sync for Fuse<Fut>where
    Fut: Sync,
",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Sync for CatchUnwind<Fut>where
    Fut: Sync,
",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Sync for RemoteHandle<T>where
    T: Send,
",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Sync for Remote<Fut>where
    Fut: Sync,
    <Fut as Future>::Output: Send,
",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Sync for Shared<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send + Sync,
",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Sync for WeakShared<Fut>where
    Fut: Send,
    <Fut as Future>::Output: Send + Sync,
",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Sync for Flatten<F>where
    F: Sync,
    <F as Future>::Output: Sync,
",1,["futures_util::future::future::Flatten"]],["impl<F> Sync for FlattenStream<F>where
    F: Sync,
    <F as Future>::Output: Sync,
",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Sync for Map<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::future::Map"]],["impl<F> Sync for IntoStream<F>where
    F: Sync,
",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Sync for MapInto<Fut, T>where
    Fut: Sync,
",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Sync for Then<Fut1, Fut2, F>where
    F: Sync,
    Fut1: Sync,
    Fut2: Sync,
",1,["futures_util::future::future::Then"]],["impl<Fut, F> Sync for Inspect<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::future::Inspect"]],["impl<Fut> Sync for NeverError<Fut>where
    Fut: Sync,
",1,["futures_util::future::future::NeverError"]],["impl<Fut> Sync for UnitError<Fut>where
    Fut: Sync,
",1,["futures_util::future::future::UnitError"]],["impl<Fut> Sync for IntoFuture<Fut>where
    Fut: Sync,
",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Sync for TryFlatten<Fut1, Fut2>where
    Fut1: Sync,
    Fut2: Sync,
",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Sync for TryFlattenStream<Fut>where
    Fut: Sync,
    <Fut as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Sync for FlattenSink<Fut, Si>where
    Fut: Sync,
    Si: Sync,
",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Sync for AndThen<Fut1, Fut2, F>where
    F: Sync,
    Fut1: Sync,
    Fut2: Sync,
",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Sync for OrElse<Fut1, Fut2, F>where
    F: Sync,
    Fut1: Sync,
    Fut2: Sync,
",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Sync for ErrInto<Fut, E>where
    Fut: Sync,
",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Sync for OkInto<Fut, E>where
    Fut: Sync,
",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Sync for InspectOk<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Sync for InspectErr<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Sync for MapOk<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Sync for MapErr<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Sync for MapOkOrElse<Fut, F, G>where
    F: Sync,
    Fut: Sync,
    G: Sync,
",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Sync for UnwrapOrElse<Fut, F>where
    F: Sync,
    Fut: Sync,
",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Sync for Lazy<F>where
    F: Sync,
",1,["futures_util::future::lazy::Lazy"]],["impl<T> Sync for Pending<T>where
    T: Sync,
",1,["futures_util::future::pending::Pending"]],["impl<Fut> Sync for MaybeDone<Fut>where
    Fut: Sync,
    <Fut as Future>::Output: Sync,
",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Sync for TryMaybeDone<Fut>where
    Fut: Sync,
    <Fut as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Sync for OptionFuture<F>where
    F: Sync,
",1,["futures_util::future::option::OptionFuture"]],["impl<F> Sync for PollFn<F>where
    F: Sync,
",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Sync for PollImmediate<T>where
    T: Sync,
",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Sync for Ready<T>where
    T: Sync,
",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Sync for Join<Fut1, Fut2>where
    Fut1: Sync,
    Fut2: Sync,
    <Fut1 as Future>::Output: Sync,
    <Fut2 as Future>::Output: Sync,
",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Sync for Join3<Fut1, Fut2, Fut3>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    <Fut1 as Future>::Output: Sync,
    <Fut2 as Future>::Output: Sync,
    <Fut3 as Future>::Output: Sync,
",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Sync for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    Fut4: Sync,
    <Fut1 as Future>::Output: Sync,
    <Fut2 as Future>::Output: Sync,
    <Fut3 as Future>::Output: Sync,
    <Fut4 as Future>::Output: Sync,
",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Sync for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    Fut4: Sync,
    Fut5: Sync,
    <Fut1 as Future>::Output: Sync,
    <Fut2 as Future>::Output: Sync,
    <Fut3 as Future>::Output: Sync,
    <Fut4 as Future>::Output: Sync,
    <Fut5 as Future>::Output: Sync,
",1,["futures_util::future::join::Join5"]],["impl<F> Sync for JoinAll<F>where
    F: Sync,
    <F as Future>::Output: Sync,
",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Sync for Select<A, B>where
    A: Sync,
    B: Sync,
",1,["futures_util::future::select::Select"]],["impl<Fut> Sync for SelectAll<Fut>where
    Fut: Sync,
",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Sync for TryJoin<Fut1, Fut2>where
    Fut1: Sync,
    Fut2: Sync,
    <Fut1 as TryFuture>::Ok: Sync,
    <Fut2 as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Sync for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    <Fut1 as TryFuture>::Ok: Sync,
    <Fut2 as TryFuture>::Ok: Sync,
    <Fut3 as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Sync for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    Fut4: Sync,
    <Fut1 as TryFuture>::Ok: Sync,
    <Fut2 as TryFuture>::Ok: Sync,
    <Fut3 as TryFuture>::Ok: Sync,
    <Fut4 as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Sync for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: Sync,
    Fut2: Sync,
    Fut3: Sync,
    Fut4: Sync,
    Fut5: Sync,
    <Fut1 as TryFuture>::Ok: Sync,
    <Fut2 as TryFuture>::Ok: Sync,
    <Fut3 as TryFuture>::Ok: Sync,
    <Fut4 as TryFuture>::Ok: Sync,
    <Fut5 as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> Sync for TryJoinAll<F>where
    F: Sync,
    <F as TryFuture>::Error: Sync,
    <F as TryFuture>::Ok: Sync,
",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Sync for TrySelect<A, B>where
    A: Sync,
    B: Sync,
",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Sync for SelectOk<Fut>where
    Fut: Sync,
",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Sync for Either<A, B>where
    A: Sync,
    B: Sync,
",1,["futures_util::future::either::Either"]],["impl Sync for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Sync for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Sync for Abortable<T>where
    T: Sync,
",1,["futures_util::abortable::Abortable"]],["impl Sync for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Sync for Chain<St1, St2>where
    St1: Sync,
    St2: Sync,
",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Sync for Collect<St, C>where
    C: Sync,
    St: Sync,
",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Sync for Unzip<St, FromA, FromB>where
    FromA: Sync,
    FromB: Sync,
    St: Sync,
",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Sync for Concat<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Sync for Cycle<St>where
    St: Sync,
",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Sync for Enumerate<St>where
    St: Sync,
",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Sync for Filter<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Sync for FilterMap<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Sync for Fold<St, Fut, T, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    T: Sync,
",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Sync for ForEach<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Sync for Fuse<St>where
    St: Sync,
",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Sync for StreamFuture<St>where
    St: Sync,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Sync for Map<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Sync for Next<'a, St>where
    St: Sync,
",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Sync for SelectNextSome<'a, St>where
    St: Sync,
",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Sync for Peekable<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Sync for Peek<'a, St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Sync for PeekMut<'a, St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Sync for NextIf<'a, St, F>where
    F: Sync,
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Sync for NextIfEq<'a, St, T>where
    St: Sync,
    T: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Sync for Skip<St>where
    St: Sync,
",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Sync for SkipWhile<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Sync for Take<St>where
    St: Sync,
",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Sync for TakeWhile<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Sync for TakeUntil<St, Fut>where
    Fut: Sync,
    St: Sync,
    <Fut as Future>::Output: Sync,
",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Sync for Then<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Sync for Zip<St1, St2>where
    St1: Sync,
    St2: Sync,
    <St1 as Stream>::Item: Sync,
    <St2 as Stream>::Item: Sync,
",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Sync for Chunks<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Sync for ReadyChunks<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Sync for Scan<St, S, Fut, F>where
    F: Sync,
    Fut: Sync,
    S: Sync,
    St: Sync,
",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> Sync for BufferUnordered<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> Sync for Buffered<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
    <<St as Stream>::Item as Future>::Output: Sync,
",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> Sync for ForEachConcurrent<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Sync for SplitStream<S>where
    S: Send,
",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Sync for SplitSink<S, Item>where
    Item: Sync,
    S: Send,
",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Sync for ReuniteError<T, Item>where
    Item: Sync,
    T: Send,
",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Sync for CatchUnwind<St>where
    St: Sync,
",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Sync for Flatten<St>where
    St: Sync,
    <St as Stream>::Item: Sync,
",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Sync for Forward<St, Si>where
    Si: Sync,
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Sync for Inspect<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Sync for FlatMap<St, U, F>where
    F: Sync,
    St: Sync,
    U: Sync,
",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Sync for AndThen<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Sync for IntoStream<St>where
    St: Sync,
",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Sync for OrElse<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Sync for TryNext<'a, St>where
    St: Sync,
",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Sync for TryForEach<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Sync for TryFilter<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Sync for TryFilterMap<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Sync for TryFlatten<St>where
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Sync for TryCollect<St, C>where
    C: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Sync for TryConcat<St>where
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Sync for TryChunks<St>where
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Sync for TryChunksError<T, E>where
    E: Sync,
    T: Sync,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Sync for TryFold<St, Fut, T, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    T: Sync,
",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Sync for TryUnfold<T, F, Fut>where
    F: Sync,
    Fut: Sync,
    T: Sync,
",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Sync for TrySkipWhile<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Sync for TryTakeWhile<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> Sync for TryBufferUnordered<St>where
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> Sync for TryBuffered<St>where
    St: Sync,
    <<St as TryStream>::Ok as TryFuture>::Error: Sync,
    <St as TryStream>::Ok: Sync,
    <<St as TryStream>::Ok as TryFuture>::Ok: Sync,
",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> Sync for TryForEachConcurrent<St, Fut, F>where
    F: Sync,
    Fut: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Sync for IntoAsyncRead<St>where
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Sync for ErrInto<St, E>where
    St: Sync,
",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Sync for InspectOk<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Sync for InspectErr<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Sync for MapOk<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Sync for MapErr<St, F>where
    F: Sync,
    St: Sync,
",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Sync for Iter<I>where
    I: Sync,
",1,["futures_util::stream::iter::Iter"]],["impl<T> Sync for Repeat<T>where
    T: Sync,
",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Sync for RepeatWith<F>where
    F: Sync,
",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Sync for Empty<T>where
    T: Sync,
",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Sync for Once<Fut>where
    Fut: Sync,
",1,["futures_util::stream::once::Once"]],["impl<T> Sync for Pending<T>where
    T: Sync,
",1,["futures_util::stream::pending::Pending"]],["impl<F> Sync for PollFn<F>where
    F: Sync,
",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Sync for PollImmediate<S>where
    S: Sync,
",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Sync for Select<St1, St2>where
    St1: Sync,
    St2: Sync,
",1,["futures_util::stream::select::Select"]],["impl Sync for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Sync for SelectWithStrategy<St1, St2, Clos, State>where
    Clos: Sync,
    St1: Sync,
    St2: Sync,
    State: Sync,
",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Sync for Unfold<T, F, Fut>where
    F: Sync,
    Fut: Sync,
    T: Sync,
",1,["futures_util::stream::unfold::Unfold"]],["impl<T> Sync for FuturesOrdered<T>where
    T: Sync,
    <T as Future>::Output: Sync,
",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Sync for IterMut<'a, Fut>where
    Fut: Sync,
",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Sync for Iter<'a, Fut>where
    Fut: Sync,
",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<St> Sync for SelectAll<St>where
    St: Sync,
",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Sync for Iter<'a, St>where
    St: Sync,
",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Sync for IterMut<'a, St>where
    St: Sync,
",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Sync for IntoIter<St>where
    St: Sync,
",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Sync for Close<'a, Si, Item>where
    Si: Sync,
",1,["futures_util::sink::close::Close"]],["impl<T> Sync for Drain<T>where
    T: Sync,
",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Sync for Fanout<Si1, Si2>where
    Si1: Sync,
    Si2: Sync,
",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Sync for Feed<'a, Si, Item>where
    Item: Sync,
    Si: Sync,
",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Sync for Flush<'a, Si, Item>where
    Si: Sync,
",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Sync for SinkErrInto<Si, Item, E>where
    Si: Sync,
",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Sync for SinkMapErr<Si, F>where
    F: Sync,
    Si: Sync,
",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Sync for Send<'a, Si, Item>where
    Item: Sync,
    Si: Sync,
",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Sync for SendAll<'a, Si, St>where
    Si: Sync,
    St: Sync,
    <St as TryStream>::Ok: Sync,
",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Sync for Unfold<T, F, R>where
    F: Sync,
    R: Sync,
    T: Sync,
",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Sync for With<Si, Item, U, Fut, F>where
    F: Sync,
    Fut: Sync,
    Si: Sync,
",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Sync for WithFlatMap<Si, Item, U, St, F>where
    F: Sync,
    Item: Sync,
    Si: Sync,
    St: Sync,
",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Sync for Buffer<Si, Item>where
    Item: Sync,
    Si: Sync,
",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Sync for AllowStdIo<T>where
    T: Sync,
",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Sync for BufReader<R>where
    R: Sync,
",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Sync for SeeKRelative<'a, R>where
    R: Sync,
",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Sync for BufWriter<W>where
    W: Sync,
",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Sync for LineWriter<W>where
    W: Sync,
",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Sync for Chain<T, U>where
    T: Sync,
    U: Sync,
",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Sync for Close<'a, W>where
    W: Sync,
",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Sync for Copy<'a, R, W>where
    R: Sync,
    W: Sync,
",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Sync for CopyBuf<'a, R, W>where
    R: Sync,
    W: Sync,
",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Sync for CopyBufAbortable<'a, R, W>where
    R: Sync,
    W: Sync,
",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Sync for Cursor<T>where
    T: Sync,
",1,["futures_util::io::cursor::Cursor"]],["impl Sync for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Sync for FillBuf<'a, R>where
    R: Sync,
",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Sync for Flush<'a, W>where
    W: Sync,
",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Sync for IntoSink<W, Item>where
    Item: Sync,
    W: Sync,
",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Sync for Lines<R>where
    R: Sync,
",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Sync for Read<'a, R>where
    R: Sync,
",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Sync for ReadVectored<'a, R>where
    R: Sync,
",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Sync for ReadExact<'a, R>where
    R: Sync,
",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Sync for ReadLine<'a, R>where
    R: Sync,
",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Sync for ReadToEnd<'a, R>where
    R: Sync,
",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Sync for ReadToString<'a, R>where
    R: Sync,
",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Sync for ReadUntil<'a, R>where
    R: Sync,
",1,["futures_util::io::read_until::ReadUntil"]],["impl Sync for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Sync for Seek<'a, S>where
    S: Sync,
",1,["futures_util::io::seek::Seek"]],["impl Sync for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Sync for ReadHalf<T>where
    T: Send,
",1,["futures_util::io::split::ReadHalf"]],["impl<T> Sync for WriteHalf<T>where
    T: Send,
",1,["futures_util::io::split::WriteHalf"]],["impl<T> Sync for ReuniteError<T>where
    T: Send,
",1,["futures_util::io::split::ReuniteError"]],["impl<R> Sync for Take<R>where
    R: Sync,
",1,["futures_util::io::take::Take"]],["impl<T> Sync for Window<T>where
    T: Sync,
",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Sync for Write<'a, W>where
    W: Sync,
",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Sync for WriteVectored<'a, W>where
    W: Sync,
",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Sync for WriteAll<'a, W>where
    W: Sync,
",1,["futures_util::io::write_all::WriteAll"]],["impl<Fut: Sync> Sync for IterPinRef<'_, Fut>"],["impl<Fut: Sync> Sync for IterPinMut<'_, Fut>"],["impl<Fut: Sync + Unpin> Sync for IntoIter<Fut>"],["impl<Fut: Sync> Sync for FuturesUnordered<Fut>"],["impl<T: ?Sized + Send> Sync for Mutex<T>"],["impl<T: ?Sized> Sync for MutexLockFuture<'_, T>"],["impl<T: ?Sized> Sync for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T>"],["impl<T: ?Sized + Sync> Sync for OwnedMutexGuard<T>"],["impl<T: ?Sized + Sync, U: ?Sized + Sync> Sync for MappedMutexGuard<'_, T, U>"]], +"generic_array":[["impl<T, N> Sync for GenericArrayIter<T, N>where
    T: Sync,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T: Sync, N: ArrayLength<T>> Sync for GenericArray<T, N>"]], +"getrandom":[["impl Sync for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl Sync for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !Sync for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl Sync for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Sync for WALLoader",1,["growthring::wal::WALLoader"]],["impl !Sync for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !Sync for WALStoreAIO",1,["growthring::WALStoreAIO"]]], +"hashbrown":[["impl<K, V, S, A> Sync for HashMap<K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Sync for Iter<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Sync for IterMut<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Sync for IntoIter<K, V, A>where
    A: Sync,
    K: Sync,
    V: Sync,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Sync for IntoKeys<K, V, A>where
    A: Sync,
    K: Sync,
    V: Sync,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Sync for IntoValues<K, V, A>where
    A: Sync,
    K: Sync,
    V: Sync,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Sync for Keys<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Sync for Values<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Sync for Drain<'a, K, V, A>where
    A: Copy + Sync,
    K: Sync,
    V: Sync,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Sync for DrainFilter<'a, K, V, F, A>where
    A: Sync,
    F: Sync,
    K: Sync,
    V: Sync,
",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Sync for ValuesMut<'a, K, V>where
    K: Sync,
    V: Sync,
",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Sync for RawEntryBuilderMut<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Sync for RawEntryMut<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Sync for RawVacantEntryMut<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Sync for RawEntryBuilder<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Sync for Entry<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Sync for VacantEntry<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Sync for EntryRef<'a, 'b, K, Q, V, S, A>where
    A: Sync,
    K: Sync,
    Q: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Sync for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
    A: Sync,
    K: Sync,
    Q: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Sync for OccupiedError<'a, K, V, S, A>where
    A: Sync,
    K: Sync,
    S: Sync,
    V: Sync,
",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Sync for HashSet<T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> Sync for Iter<'a, K>where
    K: Sync,
",1,["hashbrown::set::Iter"]],["impl<K, A> Sync for IntoIter<K, A>where
    A: Sync,
    K: Sync,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Sync for Drain<'a, K, A>where
    A: Copy + Sync,
    K: Sync,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Sync for DrainFilter<'a, K, F, A>where
    A: Sync,
    F: Sync,
    K: Sync,
",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Sync for Intersection<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Sync for Difference<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Sync for SymmetricDifference<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Sync for Union<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Sync for Entry<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Sync for OccupiedEntry<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Sync for VacantEntry<'a, T, S, A>where
    A: Sync,
    S: Sync,
    T: Sync,
",1,["hashbrown::set::VacantEntry"]],["impl Sync for TryReserveError",1,["hashbrown::TryReserveError"]],["impl<K, V, S, A> Sync for RawOccupiedEntryMut<'_, K, V, S, A>where
    K: Sync,
    V: Sync,
    S: Sync,
    A: Sync + Allocator + Clone,
"],["impl<K, V, S, A> Sync for OccupiedEntry<'_, K, V, S, A>where
    K: Sync,
    V: Sync,
    S: Sync,
    A: Sync + Allocator + Clone,
"],["impl<'a, 'b, K, Q, V, S, A> Sync for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Sync,
    Q: Sync + ?Sized,
    V: Sync,
    S: Sync,
    A: Sync + Allocator + Clone,
"]], +"heck":[["impl<T> Sync for AsKebabCase<T>where
    T: Sync,
",1,["heck::kebab::AsKebabCase"]],["impl<T> Sync for AsLowerCamelCase<T>where
    T: Sync,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Sync for AsShoutyKebabCase<T>where
    T: Sync,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Sync for AsShoutySnakeCase<T>where
    T: Sync,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Sync for AsSnakeCase<T>where
    T: Sync,
",1,["heck::snake::AsSnakeCase"]],["impl<T> Sync for AsTitleCase<T>where
    T: Sync,
",1,["heck::title::AsTitleCase"]],["impl<T> Sync for AsUpperCamelCase<T>where
    T: Sync,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl Sync for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl Sync for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Sync for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Sync for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Sync for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Sync for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Sync for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Sync for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Sync for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl !Sync for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Sync for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Sync for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Sync for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Sync for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Sync for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Sync for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Sync for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Sync for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Sync for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl !Sync for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl !Sync for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Sync for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Sync for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Sync for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Sync for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Sync for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl !Sync for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Sync for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Sync for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Sync for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Sync for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Sync for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Sync for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Sync for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl !Sync for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Sync for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Sync for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl !Sync for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl !Sync for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Sync for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Sync for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Sync for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Sync for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Sync for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Sync for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Sync for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl !Sync for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Sync for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Sync for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl !Sync for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Sync for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Sync for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Sync for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Sync for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Sync for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Sync for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Sync for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Sync for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Sync for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Sync for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Sync for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Sync for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Sync for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Sync for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl !Sync for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl !Sync for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl !Sync for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Sync for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Sync for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Sync for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Sync for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Sync for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Sync for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl !Sync for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Sync for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Sync for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Sync for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Sync for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Sync for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Sync for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Sync for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Sync for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Sync for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Sync for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Sync for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Sync for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Sync for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl !Sync for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Sync for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Sync for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Sync for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Sync for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Sync for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl !Sync for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Sync for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Sync for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Sync for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Sync for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Sync for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Sync for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Sync for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Sync for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Sync for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl !Sync for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl !Sync for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Sync for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Sync for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Sync for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Sync for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Sync for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Sync for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Sync for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Sync for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Sync for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Sync for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Sync for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Sync for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Sync for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Sync for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl !Sync for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Sync for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Sync for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Sync for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Sync for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Sync for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Sync for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Sync for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Sync for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Sync for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Sync for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Sync for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Sync for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Sync for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Sync for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Sync for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl !Sync for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl !Sync for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Sync for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Sync for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Sync for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Sync for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Sync for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Sync for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Sync for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Sync for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Sync for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Sync for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Sync for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Sync for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Sync for timezone",1,["libc::unix::linux_like::timezone"]],["impl Sync for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Sync for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Sync for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Sync for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Sync for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Sync for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Sync for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl !Sync for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Sync for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Sync for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl !Sync for tm",1,["libc::unix::linux_like::tm"]],["impl Sync for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl !Sync for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl !Sync for lconv",1,["libc::unix::linux_like::lconv"]],["impl Sync for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl !Sync for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Sync for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Sync for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Sync for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Sync for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl !Sync for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Sync for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Sync for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Sync for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Sync for utsname",1,["libc::unix::linux_like::utsname"]],["impl !Sync for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Sync for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Sync for DIR",1,["libc::unix::DIR"]],["impl !Sync for group",1,["libc::unix::group"]],["impl Sync for utimbuf",1,["libc::unix::utimbuf"]],["impl Sync for timeval",1,["libc::unix::timeval"]],["impl Sync for timespec",1,["libc::unix::timespec"]],["impl Sync for rlimit",1,["libc::unix::rlimit"]],["impl Sync for rusage",1,["libc::unix::rusage"]],["impl Sync for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl !Sync for hostent",1,["libc::unix::hostent"]],["impl !Sync for iovec",1,["libc::unix::iovec"]],["impl Sync for pollfd",1,["libc::unix::pollfd"]],["impl Sync for winsize",1,["libc::unix::winsize"]],["impl Sync for linger",1,["libc::unix::linger"]],["impl !Sync for sigval",1,["libc::unix::sigval"]],["impl Sync for itimerval",1,["libc::unix::itimerval"]],["impl Sync for tms",1,["libc::unix::tms"]],["impl !Sync for servent",1,["libc::unix::servent"]],["impl !Sync for protoent",1,["libc::unix::protoent"]],["impl Sync for FILE",1,["libc::unix::FILE"]],["impl Sync for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<'a, R, T: ?Sized> Sync for RwLockReadGuard<'a, R, T>where
    R: Sync,
    T: Send + Sync,
    <R as RawRwLock>::GuardMarker: Sync,
",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Sync for RwLockWriteGuard<'a, R, T>where
    R: Sync,
    T: Send + Sync,
    <R as RawRwLock>::GuardMarker: Sync,
",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl Sync for GuardSend",1,["lock_api::GuardSend"]],["impl Sync for GuardNoSend"],["impl<R: RawMutex + Sync, T: ?Sized + Send> Sync for Mutex<R, T>"],["impl<'a, R: RawMutex + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MappedMutexGuard<'a, R, T>"],["impl<R: RawMutex + Sync, G: GetThreadId + Sync> Sync for RawReentrantMutex<R, G>"],["impl<R: RawMutex + Sync, G: GetThreadId + Sync, T: ?Sized + Send> Sync for ReentrantMutex<R, G, T>"],["impl<'a, R: RawMutex + Sync + 'a, G: GetThreadId + Sync + 'a, T: ?Sized + Sync + 'a> Sync for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + Sync + 'a, G: GetThreadId + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<R: RawRwLock + Sync, T: ?Sized + Send + Sync> Sync for RwLock<R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + Sync + 'a> Sync for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Sync for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Sync for MappedRwLockWriteGuard<'a, R, T>"]], +"lru":[["impl<K, V> Sync for IntoIter<K, V>where
    K: Sync,
    V: Sync,
",1,["lru::IntoIter"]],["impl<K: Sync, V: Sync, S: Sync> Sync for LruCache<K, V, S>"],["impl<'a, K: Sync, V: Sync> Sync for Iter<'a, K, V>"],["impl<'a, K: Sync, V: Sync> Sync for IterMut<'a, K, V>"]], +"memchr":[["impl<'a> Sync for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Sync for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Sync for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Sync for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Sync for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Sync for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Sync for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Sync for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Sync for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl !Sync for Dir",1,["nix::dir::Dir"]],["impl<'d> !Sync for Iter<'d>",1,["nix::dir::Iter"]],["impl !Sync for OwningIter",1,["nix::dir::OwningIter"]],["impl Sync for Entry",1,["nix::dir::Entry"]],["impl Sync for Type",1,["nix::dir::Type"]],["impl Sync for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Sync for Errno",1,["nix::errno::consts::Errno"]],["impl Sync for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Sync for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Sync for OFlag",1,["nix::fcntl::OFlag"]],["impl Sync for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Sync for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Sync for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Sync for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Sync for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Sync for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Sync for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Sync for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl !Sync for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl !Sync for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl !Sync for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> !Sync for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Sync for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Sync for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Sync for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Sync for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Sync for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Sync for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Sync for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Sync for MqdT",1,["nix::mqueue::MqdT"]],["impl Sync for PollFd",1,["nix::poll::PollFd"]],["impl Sync for PollFlags",1,["nix::poll::PollFlags"]],["impl Sync for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Sync for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Sync for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Sync for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Sync for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Sync for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Sync for LioMode",1,["nix::sys::aio::LioMode"]],["impl Sync for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Sync for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Sync for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Sync for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Sync for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Sync for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Sync for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Sync for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Sync for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Sync for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Sync for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Sync for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Sync for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Sync for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Sync for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Sync for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Sync for Persona",1,["nix::sys::personality::Persona"]],["impl Sync for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Sync for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Sync for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Sync for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Sync for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Sync for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Sync for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Sync for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Sync for Resource",1,["nix::sys::resource::Resource"]],["impl Sync for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Sync for Usage",1,["nix::sys::resource::Usage"]],["impl Sync for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Sync for Fds<'a>",1,["nix::sys::select::Fds"]],["impl !Sync for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Sync for Signal",1,["nix::sys::signal::Signal"]],["impl Sync for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Sync for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Sync for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Sync for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Sync for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Sync for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Sync for SigAction",1,["nix::sys::signal::SigAction"]],["impl Sync for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Sync for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Sync for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Sync for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Sync for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Sync for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Sync for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Sync for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Sync for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Sync for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Sync for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Sync for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Sync for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Sync for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Sync for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Sync for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Sync for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Sync for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Sync for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Sync for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Sync for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Sync for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Sync for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Sync for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Sync for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Sync for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Sync for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Sync for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Sync for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Sync for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Sync for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Sync for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Sync for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Sync for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Sync for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Sync for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Sync for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Sync for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Sync for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Sync for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Sync for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Sync for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Sync for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Sync for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Sync for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Sync for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Sync for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Sync for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Sync for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Sync for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Sync for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Sync for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Sync for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Sync for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Sync for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Sync for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Sync for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Sync for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Sync for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Sync for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Sync for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Sync for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Sync for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Sync for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Sync for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Sync for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Sync for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Sync for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Sync for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Sync for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Sync for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Sync for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Sync for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Sync for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Sync for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Sync for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Sync for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Sync for AlgSetKey<T>where
    T: Sync,
",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Sync for SockType",1,["nix::sys::socket::SockType"]],["impl Sync for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Sync for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Sync for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Sync for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Sync for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Sync for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Sync for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> !Sync for RecvMsg<'a, 's, S>",1,["nix::sys::socket::RecvMsg"]],["impl<'a> !Sync for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Sync for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Sync for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Sync for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> !Sync for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> !Sync for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Sync for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Sync for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Sync for SFlag",1,["nix::sys::stat::SFlag"]],["impl Sync for Mode",1,["nix::sys::stat::Mode"]],["impl Sync for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Sync for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Sync for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Sync for FsType",1,["nix::sys::statfs::FsType"]],["impl Sync for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Sync for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Sync for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !Sync for Termios",1,["nix::sys::termios::Termios"]],["impl Sync for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Sync for SetArg",1,["nix::sys::termios::SetArg"]],["impl Sync for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Sync for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Sync for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Sync for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Sync for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Sync for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Sync for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Sync for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Sync for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Sync for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Sync for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Sync for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl Sync for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Sync for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Sync for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Sync for Id",1,["nix::sys::wait::Id"]],["impl Sync for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Sync for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Sync for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Sync for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Sync for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Sync for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Sync for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Sync for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl !Sync for Timer",1,["nix::sys::timer::Timer"]],["impl Sync for ClockId",1,["nix::time::ClockId"]],["impl !Sync for UContext",1,["nix::ucontext::UContext"]],["impl Sync for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Sync for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Sync for Uid",1,["nix::unistd::Uid"]],["impl Sync for Gid",1,["nix::unistd::Gid"]],["impl Sync for Pid",1,["nix::unistd::Pid"]],["impl Sync for ForkResult",1,["nix::unistd::ForkResult"]],["impl Sync for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Sync for Whence",1,["nix::unistd::Whence"]],["impl Sync for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Sync for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Sync for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Sync for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Sync for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Sync for User",1,["nix::unistd::User"]],["impl Sync for Group",1,["nix::unistd::Group"]],["impl<T> Sync for IoVec<T>where
    T: Sync,
"]], +"once_cell":[["impl<T> !Sync for OnceCell<T>",1,["once_cell::unsync::OnceCell"]],["impl<T, F = fn() -> T> !Sync for Lazy<T, F>",1,["once_cell::unsync::Lazy"]],["impl<T> Sync for OnceCell<T>where
    T: Send + Sync,
",1,["once_cell::sync::OnceCell"]],["impl Sync for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Sync for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T, F: Send> Sync for Lazy<T, F>where
    OnceCell<T>: Sync,
"],["impl<T: Sync + Send> Sync for OnceBox<T>"]], +"parking_lot":[["impl Sync for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Sync for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Sync for OnceState",1,["parking_lot::once::OnceState"]],["impl Sync for Once",1,["parking_lot::once::Once"]],["impl Sync for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Sync for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Sync for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Sync for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl Sync for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Sync for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Sync for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Sync for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Sync for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Sync for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Sync for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl Sync for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Sync for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Sync for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Sync for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Sync for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Sync for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Sync for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Sync for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Sync for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Sync for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Sync for SseMachine<S3, S4, NI>where
    NI: Sync,
    S3: Sync,
    S4: Sync,
",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Sync for Avx2Machine<NI>where
    NI: Sync,
",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Sync for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Sync for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Sync for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl Sync for Error",1,["primitive_types::Error"]],["impl Sync for U128",1,["primitive_types::U128"]],["impl Sync for U256",1,["primitive_types::U256"]],["impl Sync for U512",1,["primitive_types::U512"]],["impl Sync for H128",1,["primitive_types::H128"]],["impl Sync for H160",1,["primitive_types::H160"]],["impl Sync for H256",1,["primitive_types::H256"]],["impl Sync for H384",1,["primitive_types::H384"]],["impl Sync for H512",1,["primitive_types::H512"]],["impl Sync for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl !Sync for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl !Sync for TokenStream",1,["proc_macro2::TokenStream"]],["impl !Sync for LexError",1,["proc_macro2::LexError"]],["impl !Sync for Span",1,["proc_macro2::Span"]],["impl !Sync for TokenTree",1,["proc_macro2::TokenTree"]],["impl !Sync for Group",1,["proc_macro2::Group"]],["impl Sync for Delimiter",1,["proc_macro2::Delimiter"]],["impl !Sync for Punct",1,["proc_macro2::Punct"]],["impl Sync for Spacing",1,["proc_macro2::Spacing"]],["impl !Sync for Ident",1,["proc_macro2::Ident"]],["impl !Sync for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl Sync for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Sync for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Sync for DistIter<D, R, T>where
    D: Sync,
    R: Sync,
    T: Sync,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Sync for DistMap<D, F, T, S>where
    D: Sync,
    F: Sync,
",1,["rand::distributions::distribution::DistMap"]],["impl Sync for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Sync for Open01",1,["rand::distributions::float::Open01"]],["impl Sync for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Sync for Slice<'a, T>where
    T: Sync,
",1,["rand::distributions::slice::Slice"]],["impl<X> Sync for WeightedIndex<X>where
    X: Sync,
    <X as SampleUniform>::Sampler: Sync,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Sync for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Sync for Uniform<X>where
    <X as SampleUniform>::Sampler: Sync,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> Sync for UniformInt<X>where
    X: Sync,
",1,["rand::distributions::uniform::UniformInt"]],["impl Sync for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Sync for UniformFloat<X>where
    X: Sync,
",1,["rand::distributions::uniform::UniformFloat"]],["impl Sync for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Sync for WeightedIndex<W>where
    W: Sync,
",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Sync for Standard",1,["rand::distributions::Standard"]],["impl<R> Sync for ReadRng<R>where
    R: Sync,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl Sync for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Sync for ReseedingRng<R, Rsdr>where
    R: Sync,
    Rsdr: Sync,
    <R as BlockRngCore>::Results: Sync,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Sync for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Sync for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Sync for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Sync for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Sync for SliceChooseIter<'a, S, T>where
    S: Sync,
    T: Sync,
",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl Sync for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Sync for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Sync for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Sync for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Sync for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Sync for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> Sync for BlockRng<R>where
    R: Sync,
    <R as BlockRngCore>::Results: Sync,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Sync for BlockRng64<R>where
    R: Sync,
    <R as BlockRngCore>::Results: Sync,
",1,["rand_core::block::BlockRng64"]],["impl Sync for Error",1,["rand_core::error::Error"]],["impl Sync for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl Sync for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Sync for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Sync for Match<'t>",1,["regex::re_bytes::Match"]],["impl Sync for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> !Sync for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> !Sync for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> !Sync for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> !Sync for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Sync for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Sync for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Sync for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Sync for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Sync for ReplacerRef<'a, R>where
    R: Sync,
",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Sync for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Sync for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Sync for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Sync for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Sync for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Sync for Error",1,["regex::error::Error"]],["impl Sync for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Sync for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Sync for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Sync for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Sync for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Sync for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Sync for Match<'t>",1,["regex::re_unicode::Match"]],["impl Sync for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Sync for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> !Sync for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> !Sync for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Sync for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Sync for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Sync for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> !Sync for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> !Sync for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Sync for ReplacerRef<'a, R>where
    R: Sync,
",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Sync for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl Sync for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !Sync for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Sync for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Sync for Error",1,["regex_syntax::ast::Error"]],["impl Sync for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Sync for Span",1,["regex_syntax::ast::Span"]],["impl Sync for Position",1,["regex_syntax::ast::Position"]],["impl Sync for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Sync for Comment",1,["regex_syntax::ast::Comment"]],["impl Sync for Ast",1,["regex_syntax::ast::Ast"]],["impl Sync for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Sync for Concat",1,["regex_syntax::ast::Concat"]],["impl Sync for Literal",1,["regex_syntax::ast::Literal"]],["impl Sync for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Sync for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Sync for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Sync for Class",1,["regex_syntax::ast::Class"]],["impl Sync for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Sync for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Sync for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Sync for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Sync for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Sync for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Sync for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Sync for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Sync for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Sync for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Sync for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Sync for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Sync for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Sync for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Sync for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Sync for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Sync for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Sync for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Sync for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Sync for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Sync for Group",1,["regex_syntax::ast::Group"]],["impl Sync for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Sync for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Sync for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Sync for Flags",1,["regex_syntax::ast::Flags"]],["impl Sync for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Sync for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Sync for Flag",1,["regex_syntax::ast::Flag"]],["impl Sync for Error",1,["regex_syntax::error::Error"]],["impl Sync for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Sync for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Sync for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Sync for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !Sync for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Sync for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Sync for Error",1,["regex_syntax::hir::Error"]],["impl Sync for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Sync for Hir",1,["regex_syntax::hir::Hir"]],["impl Sync for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Sync for Literal",1,["regex_syntax::hir::Literal"]],["impl Sync for Class",1,["regex_syntax::hir::Class"]],["impl Sync for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Sync for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Sync for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Sync for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Sync for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Sync for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Sync for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Sync for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Sync for Group",1,["regex_syntax::hir::Group"]],["impl Sync for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Sync for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Sync for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Sync for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Sync for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !Sync for Parser",1,["regex_syntax::parser::Parser"]],["impl Sync for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Sync for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Sync for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Sync for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl Sync for DecoderError",1,["rlp::error::DecoderError"]],["impl Sync for Prototype",1,["rlp::rlpin::Prototype"]],["impl Sync for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !Sync for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !Sync for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Sync for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> Sync for ToHexIter<T>where
    T: Sync,
",1,["rustc_hex::ToHexIter"]],["impl Sync for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Sync for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl Sync for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl Sync for Always",1,["scopeguard::Always"]],["impl<T, F, S> Sync for ScopeGuard<T, F, S>where
    T: Sync,
    F: FnOnce(T),
    S: Strategy,
"]], +"serde":[["impl Sync for Error",1,["serde::de::value::Error"]],["impl<E> Sync for UnitDeserializer<E>where
    E: Sync,
",1,["serde::de::value::UnitDeserializer"]],["impl<E> Sync for BoolDeserializer<E>where
    E: Sync,
",1,["serde::de::value::BoolDeserializer"]],["impl<E> Sync for I8Deserializer<E>where
    E: Sync,
",1,["serde::de::value::I8Deserializer"]],["impl<E> Sync for I16Deserializer<E>where
    E: Sync,
",1,["serde::de::value::I16Deserializer"]],["impl<E> Sync for I32Deserializer<E>where
    E: Sync,
",1,["serde::de::value::I32Deserializer"]],["impl<E> Sync for I64Deserializer<E>where
    E: Sync,
",1,["serde::de::value::I64Deserializer"]],["impl<E> Sync for IsizeDeserializer<E>where
    E: Sync,
",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Sync for U8Deserializer<E>where
    E: Sync,
",1,["serde::de::value::U8Deserializer"]],["impl<E> Sync for U16Deserializer<E>where
    E: Sync,
",1,["serde::de::value::U16Deserializer"]],["impl<E> Sync for U64Deserializer<E>where
    E: Sync,
",1,["serde::de::value::U64Deserializer"]],["impl<E> Sync for UsizeDeserializer<E>where
    E: Sync,
",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Sync for F32Deserializer<E>where
    E: Sync,
",1,["serde::de::value::F32Deserializer"]],["impl<E> Sync for F64Deserializer<E>where
    E: Sync,
",1,["serde::de::value::F64Deserializer"]],["impl<E> Sync for CharDeserializer<E>where
    E: Sync,
",1,["serde::de::value::CharDeserializer"]],["impl<E> Sync for I128Deserializer<E>where
    E: Sync,
",1,["serde::de::value::I128Deserializer"]],["impl<E> Sync for U128Deserializer<E>where
    E: Sync,
",1,["serde::de::value::U128Deserializer"]],["impl<E> Sync for U32Deserializer<E>where
    E: Sync,
",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Sync for StrDeserializer<'a, E>where
    E: Sync,
",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Sync for BorrowedStrDeserializer<'de, E>where
    E: Sync,
",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Sync for StringDeserializer<E>where
    E: Sync,
",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Sync for CowStrDeserializer<'a, E>where
    E: Sync,
",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Sync for BytesDeserializer<'a, E>where
    E: Sync,
",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Sync for BorrowedBytesDeserializer<'de, E>where
    E: Sync,
",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Sync for SeqDeserializer<I, E>where
    E: Sync,
    I: Sync,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> Sync for SeqAccessDeserializer<A>where
    A: Sync,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Sync for MapDeserializer<'de, I, E>where
    E: Sync,
    I: Sync,
    <<I as Iterator>::Item as Pair>::Second: Sync,
",1,["serde::de::value::MapDeserializer"]],["impl<A> Sync for MapAccessDeserializer<A>where
    A: Sync,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Sync for EnumAccessDeserializer<A>where
    A: Sync,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl Sync for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Sync for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Sync for Impossible<Ok, Error>where
    Error: Sync,
    Ok: Sync,
",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl Sync for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Sync for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Sync for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Sync for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Sync for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Sync for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Sync for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Sync for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Sync for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Sync for Shake128Core",1,["sha3::Shake128Core"]],["impl Sync for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Sync for Shake256Core",1,["sha3::Shake256Core"]],["impl Sync for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Sync for CShake128Core",1,["sha3::CShake128Core"]],["impl Sync for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Sync for CShake256Core",1,["sha3::CShake256Core"]],["impl Sync for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl Sync for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Sync for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Sync for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Sync for ShaleError",1,["shale::ShaleError"]],["impl Sync for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Sync for ObjPtr<T>where
    T: Sync,
",1,["shale::ObjPtr"]],["impl<T> !Sync for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !Sync for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !Sync for MummyObj<T>",1,["shale::MummyObj"]],["impl !Sync for PlainMem",1,["shale::PlainMem"]],["impl<T> !Sync for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> Sync for Slab<T>where
    T: Sync,
",1,["slab::Slab"]],["impl<'a, T> Sync for VacantEntry<'a, T>where
    T: Sync,
",1,["slab::VacantEntry"]],["impl<T> Sync for IntoIter<T>where
    T: Sync,
",1,["slab::IntoIter"]],["impl<'a, T> Sync for Iter<'a, T>where
    T: Sync,
",1,["slab::Iter"]],["impl<'a, T> Sync for IterMut<'a, T>where
    T: Sync,
",1,["slab::IterMut"]],["impl<'a, T> Sync for Drain<'a, T>where
    T: Sync,
",1,["slab::Drain"]]], +"smallvec":[["impl Sync for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<A> Sync for SmallVec<A>where
    A: Sync,
",1,["smallvec::SmallVec"]],["impl<A> Sync for IntoIter<A>where
    A: Sync,
",1,["smallvec::IntoIter"]],["impl<'a, T: Sync + Array> Sync for Drain<'a, T>"]], +"syn":[["impl !Sync for Underscore",1,["syn::token::Underscore"]],["impl !Sync for Abstract",1,["syn::token::Abstract"]],["impl !Sync for As",1,["syn::token::As"]],["impl !Sync for Async",1,["syn::token::Async"]],["impl !Sync for Auto",1,["syn::token::Auto"]],["impl !Sync for Await",1,["syn::token::Await"]],["impl !Sync for Become",1,["syn::token::Become"]],["impl !Sync for Box",1,["syn::token::Box"]],["impl !Sync for Break",1,["syn::token::Break"]],["impl !Sync for Const",1,["syn::token::Const"]],["impl !Sync for Continue",1,["syn::token::Continue"]],["impl !Sync for Crate",1,["syn::token::Crate"]],["impl !Sync for Default",1,["syn::token::Default"]],["impl !Sync for Do",1,["syn::token::Do"]],["impl !Sync for Dyn",1,["syn::token::Dyn"]],["impl !Sync for Else",1,["syn::token::Else"]],["impl !Sync for Enum",1,["syn::token::Enum"]],["impl !Sync for Extern",1,["syn::token::Extern"]],["impl !Sync for Final",1,["syn::token::Final"]],["impl !Sync for Fn",1,["syn::token::Fn"]],["impl !Sync for For",1,["syn::token::For"]],["impl !Sync for If",1,["syn::token::If"]],["impl !Sync for Impl",1,["syn::token::Impl"]],["impl !Sync for In",1,["syn::token::In"]],["impl !Sync for Let",1,["syn::token::Let"]],["impl !Sync for Loop",1,["syn::token::Loop"]],["impl !Sync for Macro",1,["syn::token::Macro"]],["impl !Sync for Match",1,["syn::token::Match"]],["impl !Sync for Mod",1,["syn::token::Mod"]],["impl !Sync for Move",1,["syn::token::Move"]],["impl !Sync for Mut",1,["syn::token::Mut"]],["impl !Sync for Override",1,["syn::token::Override"]],["impl !Sync for Priv",1,["syn::token::Priv"]],["impl !Sync for Pub",1,["syn::token::Pub"]],["impl !Sync for Ref",1,["syn::token::Ref"]],["impl !Sync for Return",1,["syn::token::Return"]],["impl !Sync for SelfType",1,["syn::token::SelfType"]],["impl !Sync for SelfValue",1,["syn::token::SelfValue"]],["impl !Sync for Static",1,["syn::token::Static"]],["impl !Sync for Struct",1,["syn::token::Struct"]],["impl !Sync for Super",1,["syn::token::Super"]],["impl !Sync for Trait",1,["syn::token::Trait"]],["impl !Sync for Try",1,["syn::token::Try"]],["impl !Sync for Type",1,["syn::token::Type"]],["impl !Sync for Typeof",1,["syn::token::Typeof"]],["impl !Sync for Union",1,["syn::token::Union"]],["impl !Sync for Unsafe",1,["syn::token::Unsafe"]],["impl !Sync for Unsized",1,["syn::token::Unsized"]],["impl !Sync for Use",1,["syn::token::Use"]],["impl !Sync for Virtual",1,["syn::token::Virtual"]],["impl !Sync for Where",1,["syn::token::Where"]],["impl !Sync for While",1,["syn::token::While"]],["impl !Sync for Yield",1,["syn::token::Yield"]],["impl !Sync for Add",1,["syn::token::Add"]],["impl !Sync for AddEq",1,["syn::token::AddEq"]],["impl !Sync for And",1,["syn::token::And"]],["impl !Sync for AndAnd",1,["syn::token::AndAnd"]],["impl !Sync for AndEq",1,["syn::token::AndEq"]],["impl !Sync for At",1,["syn::token::At"]],["impl !Sync for Bang",1,["syn::token::Bang"]],["impl !Sync for Caret",1,["syn::token::Caret"]],["impl !Sync for CaretEq",1,["syn::token::CaretEq"]],["impl !Sync for Colon",1,["syn::token::Colon"]],["impl !Sync for Colon2",1,["syn::token::Colon2"]],["impl !Sync for Comma",1,["syn::token::Comma"]],["impl !Sync for Div",1,["syn::token::Div"]],["impl !Sync for DivEq",1,["syn::token::DivEq"]],["impl !Sync for Dollar",1,["syn::token::Dollar"]],["impl !Sync for Dot",1,["syn::token::Dot"]],["impl !Sync for Dot2",1,["syn::token::Dot2"]],["impl !Sync for Dot3",1,["syn::token::Dot3"]],["impl !Sync for DotDotEq",1,["syn::token::DotDotEq"]],["impl !Sync for Eq",1,["syn::token::Eq"]],["impl !Sync for EqEq",1,["syn::token::EqEq"]],["impl !Sync for Ge",1,["syn::token::Ge"]],["impl !Sync for Gt",1,["syn::token::Gt"]],["impl !Sync for Le",1,["syn::token::Le"]],["impl !Sync for Lt",1,["syn::token::Lt"]],["impl !Sync for MulEq",1,["syn::token::MulEq"]],["impl !Sync for Ne",1,["syn::token::Ne"]],["impl !Sync for Or",1,["syn::token::Or"]],["impl !Sync for OrEq",1,["syn::token::OrEq"]],["impl !Sync for OrOr",1,["syn::token::OrOr"]],["impl !Sync for Pound",1,["syn::token::Pound"]],["impl !Sync for Question",1,["syn::token::Question"]],["impl !Sync for RArrow",1,["syn::token::RArrow"]],["impl !Sync for LArrow",1,["syn::token::LArrow"]],["impl !Sync for Rem",1,["syn::token::Rem"]],["impl !Sync for RemEq",1,["syn::token::RemEq"]],["impl !Sync for FatArrow",1,["syn::token::FatArrow"]],["impl !Sync for Semi",1,["syn::token::Semi"]],["impl !Sync for Shl",1,["syn::token::Shl"]],["impl !Sync for ShlEq",1,["syn::token::ShlEq"]],["impl !Sync for Shr",1,["syn::token::Shr"]],["impl !Sync for ShrEq",1,["syn::token::ShrEq"]],["impl !Sync for Star",1,["syn::token::Star"]],["impl !Sync for Sub",1,["syn::token::Sub"]],["impl !Sync for SubEq",1,["syn::token::SubEq"]],["impl !Sync for Tilde",1,["syn::token::Tilde"]],["impl !Sync for Brace",1,["syn::token::Brace"]],["impl !Sync for Bracket",1,["syn::token::Bracket"]],["impl !Sync for Paren",1,["syn::token::Paren"]],["impl !Sync for Group",1,["syn::token::Group"]],["impl !Sync for Attribute",1,["syn::attr::Attribute"]],["impl !Sync for AttrStyle",1,["syn::attr::AttrStyle"]],["impl !Sync for Meta",1,["syn::attr::Meta"]],["impl !Sync for MetaList",1,["syn::attr::MetaList"]],["impl !Sync for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl !Sync for NestedMeta",1,["syn::attr::NestedMeta"]],["impl !Sync for Variant",1,["syn::data::Variant"]],["impl !Sync for Fields",1,["syn::data::Fields"]],["impl !Sync for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl !Sync for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl !Sync for Field",1,["syn::data::Field"]],["impl !Sync for Visibility",1,["syn::data::Visibility"]],["impl !Sync for VisPublic",1,["syn::data::VisPublic"]],["impl !Sync for VisCrate",1,["syn::data::VisCrate"]],["impl !Sync for VisRestricted",1,["syn::data::VisRestricted"]],["impl !Sync for Expr",1,["syn::expr::Expr"]],["impl !Sync for ExprArray",1,["syn::expr::ExprArray"]],["impl !Sync for ExprAssign",1,["syn::expr::ExprAssign"]],["impl !Sync for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl !Sync for ExprAsync",1,["syn::expr::ExprAsync"]],["impl !Sync for ExprAwait",1,["syn::expr::ExprAwait"]],["impl !Sync for ExprBinary",1,["syn::expr::ExprBinary"]],["impl !Sync for ExprBlock",1,["syn::expr::ExprBlock"]],["impl !Sync for ExprBox",1,["syn::expr::ExprBox"]],["impl !Sync for ExprBreak",1,["syn::expr::ExprBreak"]],["impl !Sync for ExprCall",1,["syn::expr::ExprCall"]],["impl !Sync for ExprCast",1,["syn::expr::ExprCast"]],["impl !Sync for ExprClosure",1,["syn::expr::ExprClosure"]],["impl !Sync for ExprContinue",1,["syn::expr::ExprContinue"]],["impl !Sync for ExprField",1,["syn::expr::ExprField"]],["impl !Sync for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl !Sync for ExprGroup",1,["syn::expr::ExprGroup"]],["impl !Sync for ExprIf",1,["syn::expr::ExprIf"]],["impl !Sync for ExprIndex",1,["syn::expr::ExprIndex"]],["impl !Sync for ExprLet",1,["syn::expr::ExprLet"]],["impl !Sync for ExprLit",1,["syn::expr::ExprLit"]],["impl !Sync for ExprLoop",1,["syn::expr::ExprLoop"]],["impl !Sync for ExprMacro",1,["syn::expr::ExprMacro"]],["impl !Sync for ExprMatch",1,["syn::expr::ExprMatch"]],["impl !Sync for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl !Sync for ExprParen",1,["syn::expr::ExprParen"]],["impl !Sync for ExprPath",1,["syn::expr::ExprPath"]],["impl !Sync for ExprRange",1,["syn::expr::ExprRange"]],["impl !Sync for ExprReference",1,["syn::expr::ExprReference"]],["impl !Sync for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl !Sync for ExprReturn",1,["syn::expr::ExprReturn"]],["impl !Sync for ExprStruct",1,["syn::expr::ExprStruct"]],["impl !Sync for ExprTry",1,["syn::expr::ExprTry"]],["impl !Sync for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl !Sync for ExprTuple",1,["syn::expr::ExprTuple"]],["impl !Sync for ExprType",1,["syn::expr::ExprType"]],["impl !Sync for ExprUnary",1,["syn::expr::ExprUnary"]],["impl !Sync for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl !Sync for ExprWhile",1,["syn::expr::ExprWhile"]],["impl !Sync for ExprYield",1,["syn::expr::ExprYield"]],["impl !Sync for Member",1,["syn::expr::Member"]],["impl !Sync for Index",1,["syn::expr::Index"]],["impl !Sync for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl !Sync for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl !Sync for FieldValue",1,["syn::expr::FieldValue"]],["impl !Sync for Label",1,["syn::expr::Label"]],["impl !Sync for Arm",1,["syn::expr::Arm"]],["impl !Sync for RangeLimits",1,["syn::expr::RangeLimits"]],["impl !Sync for Generics",1,["syn::generics::Generics"]],["impl !Sync for GenericParam",1,["syn::generics::GenericParam"]],["impl !Sync for TypeParam",1,["syn::generics::TypeParam"]],["impl !Sync for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl !Sync for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> !Sync for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> !Sync for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> !Sync for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl !Sync for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl !Sync for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl !Sync for TraitBound",1,["syn::generics::TraitBound"]],["impl !Sync for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl !Sync for WhereClause",1,["syn::generics::WhereClause"]],["impl !Sync for WherePredicate",1,["syn::generics::WherePredicate"]],["impl !Sync for PredicateType",1,["syn::generics::PredicateType"]],["impl !Sync for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl !Sync for PredicateEq",1,["syn::generics::PredicateEq"]],["impl !Sync for Item",1,["syn::item::Item"]],["impl !Sync for ItemConst",1,["syn::item::ItemConst"]],["impl !Sync for ItemEnum",1,["syn::item::ItemEnum"]],["impl !Sync for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl !Sync for ItemFn",1,["syn::item::ItemFn"]],["impl !Sync for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl !Sync for ItemImpl",1,["syn::item::ItemImpl"]],["impl !Sync for ItemMacro",1,["syn::item::ItemMacro"]],["impl !Sync for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl !Sync for ItemMod",1,["syn::item::ItemMod"]],["impl !Sync for ItemStatic",1,["syn::item::ItemStatic"]],["impl !Sync for ItemStruct",1,["syn::item::ItemStruct"]],["impl !Sync for ItemTrait",1,["syn::item::ItemTrait"]],["impl !Sync for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl !Sync for ItemType",1,["syn::item::ItemType"]],["impl !Sync for ItemUnion",1,["syn::item::ItemUnion"]],["impl !Sync for ItemUse",1,["syn::item::ItemUse"]],["impl !Sync for UseTree",1,["syn::item::UseTree"]],["impl !Sync for UsePath",1,["syn::item::UsePath"]],["impl !Sync for UseName",1,["syn::item::UseName"]],["impl !Sync for UseRename",1,["syn::item::UseRename"]],["impl !Sync for UseGlob",1,["syn::item::UseGlob"]],["impl !Sync for UseGroup",1,["syn::item::UseGroup"]],["impl !Sync for ForeignItem",1,["syn::item::ForeignItem"]],["impl !Sync for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl !Sync for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl !Sync for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl !Sync for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl !Sync for TraitItem",1,["syn::item::TraitItem"]],["impl !Sync for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl !Sync for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl !Sync for TraitItemType",1,["syn::item::TraitItemType"]],["impl !Sync for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl !Sync for ImplItem",1,["syn::item::ImplItem"]],["impl !Sync for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl !Sync for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl !Sync for ImplItemType",1,["syn::item::ImplItemType"]],["impl !Sync for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl !Sync for Signature",1,["syn::item::Signature"]],["impl !Sync for FnArg",1,["syn::item::FnArg"]],["impl !Sync for Receiver",1,["syn::item::Receiver"]],["impl !Sync for File",1,["syn::file::File"]],["impl !Sync for Lifetime",1,["syn::lifetime::Lifetime"]],["impl !Sync for Lit",1,["syn::lit::Lit"]],["impl !Sync for LitStr",1,["syn::lit::LitStr"]],["impl !Sync for LitByteStr",1,["syn::lit::LitByteStr"]],["impl !Sync for LitByte",1,["syn::lit::LitByte"]],["impl !Sync for LitChar",1,["syn::lit::LitChar"]],["impl !Sync for LitInt",1,["syn::lit::LitInt"]],["impl !Sync for LitFloat",1,["syn::lit::LitFloat"]],["impl !Sync for LitBool",1,["syn::lit::LitBool"]],["impl Sync for StrStyle",1,["syn::lit::StrStyle"]],["impl !Sync for Macro",1,["syn::mac::Macro"]],["impl !Sync for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl !Sync for DeriveInput",1,["syn::derive::DeriveInput"]],["impl !Sync for Data",1,["syn::derive::Data"]],["impl !Sync for DataStruct",1,["syn::derive::DataStruct"]],["impl !Sync for DataEnum",1,["syn::derive::DataEnum"]],["impl !Sync for DataUnion",1,["syn::derive::DataUnion"]],["impl !Sync for BinOp",1,["syn::op::BinOp"]],["impl !Sync for UnOp",1,["syn::op::UnOp"]],["impl !Sync for Block",1,["syn::stmt::Block"]],["impl !Sync for Stmt",1,["syn::stmt::Stmt"]],["impl !Sync for Local",1,["syn::stmt::Local"]],["impl !Sync for Type",1,["syn::ty::Type"]],["impl !Sync for TypeArray",1,["syn::ty::TypeArray"]],["impl !Sync for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl !Sync for TypeGroup",1,["syn::ty::TypeGroup"]],["impl !Sync for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl !Sync for TypeInfer",1,["syn::ty::TypeInfer"]],["impl !Sync for TypeMacro",1,["syn::ty::TypeMacro"]],["impl !Sync for TypeNever",1,["syn::ty::TypeNever"]],["impl !Sync for TypeParen",1,["syn::ty::TypeParen"]],["impl !Sync for TypePath",1,["syn::ty::TypePath"]],["impl !Sync for TypePtr",1,["syn::ty::TypePtr"]],["impl !Sync for TypeReference",1,["syn::ty::TypeReference"]],["impl !Sync for TypeSlice",1,["syn::ty::TypeSlice"]],["impl !Sync for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl !Sync for TypeTuple",1,["syn::ty::TypeTuple"]],["impl !Sync for Abi",1,["syn::ty::Abi"]],["impl !Sync for BareFnArg",1,["syn::ty::BareFnArg"]],["impl !Sync for Variadic",1,["syn::ty::Variadic"]],["impl !Sync for ReturnType",1,["syn::ty::ReturnType"]],["impl !Sync for Pat",1,["syn::pat::Pat"]],["impl !Sync for PatBox",1,["syn::pat::PatBox"]],["impl !Sync for PatIdent",1,["syn::pat::PatIdent"]],["impl !Sync for PatLit",1,["syn::pat::PatLit"]],["impl !Sync for PatMacro",1,["syn::pat::PatMacro"]],["impl !Sync for PatOr",1,["syn::pat::PatOr"]],["impl !Sync for PatPath",1,["syn::pat::PatPath"]],["impl !Sync for PatRange",1,["syn::pat::PatRange"]],["impl !Sync for PatReference",1,["syn::pat::PatReference"]],["impl !Sync for PatRest",1,["syn::pat::PatRest"]],["impl !Sync for PatSlice",1,["syn::pat::PatSlice"]],["impl !Sync for PatStruct",1,["syn::pat::PatStruct"]],["impl !Sync for PatTuple",1,["syn::pat::PatTuple"]],["impl !Sync for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl !Sync for PatType",1,["syn::pat::PatType"]],["impl !Sync for PatWild",1,["syn::pat::PatWild"]],["impl !Sync for FieldPat",1,["syn::pat::FieldPat"]],["impl !Sync for Path",1,["syn::path::Path"]],["impl !Sync for PathSegment",1,["syn::path::PathSegment"]],["impl !Sync for PathArguments",1,["syn::path::PathArguments"]],["impl !Sync for GenericArgument",1,["syn::path::GenericArgument"]],["impl !Sync for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl !Sync for Binding",1,["syn::path::Binding"]],["impl !Sync for Constraint",1,["syn::path::Constraint"]],["impl !Sync for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl !Sync for QSelf",1,["syn::path::QSelf"]],["impl !Sync for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> !Sync for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Sync for Punctuated<T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Sync for Pairs<'a, T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Sync for PairsMut<'a, T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::PairsMut"]],["impl<T, P> Sync for IntoPairs<T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::IntoPairs"]],["impl<T> Sync for IntoIter<T>where
    T: Sync,
",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !Sync for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !Sync for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Sync for Pair<T, P>where
    P: Sync,
    T: Sync,
",1,["syn::punctuated::Pair"]],["impl<'a> !Sync for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Sync for Error",1,["syn::error::Error"]],["impl<'a> !Sync for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> !Sync for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Sync for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> Sync for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Sync for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Sync for Builder",1,["tokio::runtime::builder::Builder"]],["impl Sync for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Sync for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Sync for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Sync for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Sync for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl Sync for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Sync for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Sync for SendError<T>where
    T: Sync,
",1,["tokio::sync::broadcast::error::SendError"]],["impl Sync for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Sync for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Sync for Sender<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Sync for WeakSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Sync for Permit<'a, T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Sync for OwnedPermit<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Sync for Receiver<T>where
    T: Send,
",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Sync for UnboundedSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Sync for WeakUnboundedSender<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Sync for UnboundedReceiver<T>where
    T: Send,
",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Sync for SendError<T>where
    T: Sync,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Sync for TrySendError<T>where
    T: Sync,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Sync for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl Sync for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Sync for Notify",1,["tokio::sync::notify::Notify"]],["impl Sync for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Sync for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Sync for Sender<T>where
    T: Send,
",1,["tokio::sync::oneshot::Sender"]],["impl<T> Sync for Receiver<T>where
    T: Send,
",1,["tokio::sync::oneshot::Receiver"]],["impl Sync for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Sync for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Sync for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Sync for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Sync for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T> Sync for SetError<T>where
    T: Sync,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> Sync for SendError<T>where
    T: Sync,
",1,["tokio::sync::watch::error::SendError"]],["impl Sync for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Sync for Receiver<T>where
    T: Send + Sync,
",1,["tokio::sync::watch::Receiver"]],["impl<T> Sync for Sender<T>where
    T: Send + Sync,
",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Sync for Ref<'a, T>where
    T: Sync,
",1,["tokio::sync::watch::Ref"]],["impl !Sync for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !Sync for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Sync for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Sync for TaskLocalFuture<T, F>where
    F: Sync,
    T: Sync,
",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Sync for Unconstrained<F>where
    F: Sync,
",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Sync for JoinSet<T>where
    T: Send,
",1,["tokio::task::join_set::JoinSet"]],["impl Sync for AbortHandle"],["impl<T: Send> Sync for JoinHandle<T>"],["impl<T: Send> Sync for Sender<T>"],["impl<T: Send> Sync for Receiver<T>"],["impl<T> Sync for Mutex<T>where
    T: ?Sized + Send,
"],["impl<T> Sync for MutexGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Sync for OwnedMutexGuard<T>where
    T: ?Sized + Send + Sync,
"],["impl<'a, T> Sync for MappedMutexGuard<'a, T>where
    T: ?Sized + Sync + 'a,
"],["impl<'a> Sync for Notified<'a>"],["impl<T> Sync for RwLock<T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Sync for RwLockReadGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T, U> Sync for OwnedRwLockReadGuard<T, U>where
    T: ?Sized + Send + Sync,
    U: ?Sized + Send + Sync,
"],["impl<T> Sync for RwLockWriteGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Sync for OwnedRwLockWriteGuard<T>where
    T: ?Sized + Send + Sync,
"],["impl<T> Sync for RwLockMappedWriteGuard<'_, T>where
    T: ?Sized + Send + Sync,
"],["impl<T, U> Sync for OwnedRwLockMappedWriteGuard<T, U>where
    T: ?Sized + Send + Sync,
    U: ?Sized + Send + Sync,
"],["impl<T: Sync + Send> Sync for OnceCell<T>"]], +"typenum":[["impl Sync for B0",1,["typenum::bit::B0"]],["impl Sync for B1",1,["typenum::bit::B1"]],["impl<U> Sync for PInt<U>where
    U: Sync,
",1,["typenum::int::PInt"]],["impl<U> Sync for NInt<U>where
    U: Sync,
",1,["typenum::int::NInt"]],["impl Sync for Z0",1,["typenum::int::Z0"]],["impl Sync for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Sync for UInt<U, B>where
    B: Sync,
    U: Sync,
",1,["typenum::uint::UInt"]],["impl Sync for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Sync for TArr<V, A>where
    A: Sync,
    V: Sync,
",1,["typenum::array::TArr"]],["impl Sync for Greater",1,["typenum::Greater"]],["impl Sync for Less",1,["typenum::Less"]],["impl Sync for Equal",1,["typenum::Equal"]]], +"uint":[["impl Sync for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Sync for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Sync for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Sync for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Unpin.js b/docs/implementors/core/marker/trait.Unpin.js new file mode 100644 index 000000000000..175a0c40ec5d --- /dev/null +++ b/docs/implementors/core/marker/trait.Unpin.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl Unpin for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Unpin for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> Unpin for AhoCorasick<S>where
    S: Unpin,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Unpin for FindIter<'a, 'b, S>",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Unpin for FindOverlappingIter<'a, 'b, S>where
    S: Unpin,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Unpin for StreamFindIter<'a, R, S>where
    R: Unpin,
    S: Unpin,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Unpin for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Unpin for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Unpin for Error",1,["aho_corasick::error::Error"]],["impl Unpin for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Unpin for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Unpin for Config",1,["aho_corasick::packed::api::Config"]],["impl Unpin for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Unpin for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Unpin for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Unpin for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl Unpin for Error",1,["aiofut::Error"]],["impl Unpin for AIO",1,["aiofut::AIO"]],["impl Unpin for AIOFuture",1,["aiofut::AIOFuture"]],["impl Unpin for AIONotifier",1,["aiofut::AIONotifier"]],["impl Unpin for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl Unpin for AIOManager",1,["aiofut::AIOManager"]],["impl Unpin for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Unpin for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl Unpin for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Unpin for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Unpin for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Unpin for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Unpin for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Unpin for Config",1,["bincode::config::legacy::Config"]],["impl Unpin for Bounded",1,["bincode::config::limit::Bounded"]],["impl Unpin for Infinite",1,["bincode::config::limit::Infinite"]],["impl Unpin for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Unpin for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Unpin for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Unpin for WithOtherLimit<O, L>where
    L: Unpin,
    O: Unpin,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Unpin for WithOtherEndian<O, E>where
    E: Unpin,
    O: Unpin,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Unpin for WithOtherIntEncoding<O, I>where
    I: Unpin,
    O: Unpin,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Unpin for WithOtherTrailing<O, T>where
    O: Unpin,
    T: Unpin,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Unpin for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Unpin for IoReader<R>where
    R: Unpin,
",1,["bincode::de::read::IoReader"]],["impl<R, O> Unpin for Deserializer<R, O>where
    O: Unpin,
    R: Unpin,
",1,["bincode::de::Deserializer"]],["impl Unpin for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Unpin for Serializer<W, O>where
    O: Unpin,
    W: Unpin,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl Unpin for Eager",1,["block_buffer::Eager"]],["impl Unpin for Lazy",1,["block_buffer::Lazy"]],["impl Unpin for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Unpin for BlockBuffer<BlockSize, Kind>where
    Kind: Unpin,
    <BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl Unpin for BigEndian",1,["byteorder::BigEndian"]],["impl Unpin for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> Unpin for Chain<T, U>where
    T: Unpin,
    U: Unpin,
",1,["bytes::buf::chain::Chain"]],["impl<T> Unpin for IntoIter<T>where
    T: Unpin,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> Unpin for Limit<T>where
    T: Unpin,
",1,["bytes::buf::limit::Limit"]],["impl<B> Unpin for Reader<B>where
    B: Unpin,
",1,["bytes::buf::reader::Reader"]],["impl<T> Unpin for Take<T>where
    T: Unpin,
",1,["bytes::buf::take::Take"]],["impl Unpin for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Unpin for Writer<B>where
    B: Unpin,
",1,["bytes::buf::writer::Writer"]],["impl Unpin for Bytes",1,["bytes::bytes::Bytes"]],["impl Unpin for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], +"crc":[["impl<W> Unpin for Crc<W>where
    W: Unpin,
",1,["crc::Crc"]],["impl<'a, W> Unpin for Digest<'a, W>where
    W: Unpin,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> Unpin for Algorithm<W>where
    W: Unpin,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<T> Unpin for Sender<T>",1,["crossbeam_channel::channel::Sender"]],["impl<T> Unpin for Receiver<T>where
    T: Unpin,
",1,["crossbeam_channel::channel::Receiver"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Unpin for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Unpin for IntoIter<T>where
    T: Unpin,
",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Unpin for SendError<T>where
    T: Unpin,
",1,["crossbeam_channel::err::SendError"]],["impl<T> Unpin for TrySendError<T>where
    T: Unpin,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Unpin for SendTimeoutError<T>where
    T: Unpin,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Unpin for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Unpin for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Unpin for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Unpin for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Unpin for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Unpin for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Unpin for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> Unpin for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> Unpin for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]]], +"crossbeam_utils":[["impl<T> Unpin for AtomicCell<T>where
    T: Unpin,
",1,["crossbeam_utils::atomic::atomic_cell::AtomicCell"]],["impl<T> Unpin for CachePadded<T>where
    T: Unpin,
",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl Unpin for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl Unpin for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Unpin for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<T: ?Sized> Unpin for ShardedLock<T>where
    T: Unpin,
",1,["crossbeam_utils::sync::sharded_lock::ShardedLock"]],["impl<'a, T: ?Sized> Unpin for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> Unpin for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Unpin for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Unpin for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Unpin for ScopedThreadBuilder<'scope, 'env>where
    'env: 'scope,
",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> Unpin for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]]], +"crypto_common":[["impl Unpin for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> Unpin for CtVariableCoreWrapper<T, OutSize, O>where
    O: Unpin,
    OutSize: Unpin,
    T: Unpin,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Unpin for RtVariableCoreWrapper<T>where
    T: Unpin,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
    <T as BufferKindUser>::BufferKind: Unpin,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Unpin for CoreWrapper<T>where
    T: Unpin,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
    <T as BufferKindUser>::BufferKind: Unpin,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Unpin for XofReaderCoreWrapper<T>where
    T: Unpin,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Unpin for TruncSide",1,["digest::core_api::TruncSide"]],["impl Unpin for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Unpin for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl Unpin for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Unpin for WALConfig",1,["firewood::storage::WALConfig"]],["impl Unpin for DBError",1,["firewood::db::DBError"]],["impl Unpin for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Unpin for DBConfig",1,["firewood::db::DBConfig"]],["impl Unpin for DBRev",1,["firewood::db::DBRev"]],["impl Unpin for DB",1,["firewood::db::DB"]],["impl<'a> Unpin for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> Unpin for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Unpin for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Unpin for Hash",1,["firewood::merkle::Hash"]],["impl Unpin for PartialPath",1,["firewood::merkle::PartialPath"]],["impl Unpin for Node",1,["firewood::merkle::Node"]],["impl Unpin for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> Unpin for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> Unpin for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Unpin for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Unpin for Proof",1,["firewood::proof::Proof"]],["impl Unpin for ProofError",1,["firewood::proof::ProofError"]],["impl Unpin for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> Unpin for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> Unpin for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl Unpin for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Unpin for TrySendError<T>where
    T: Unpin,
",1,["futures_channel::mpsc::TrySendError"]],["impl Unpin for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<'a, T> Unpin for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl Unpin for Canceled",1,["futures_channel::oneshot::Canceled"]],["impl<T> Unpin for UnboundedReceiver<T>"],["impl<T> Unpin for Receiver<T>"],["impl<T> Unpin for Receiver<T>"],["impl<T> Unpin for Sender<T>"]], +"futures_executor":[["impl Unpin for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl Unpin for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Unpin for BlockingStream<S>",1,["futures_executor::local_pool::BlockingStream"]],["impl Unpin for Enter",1,["futures_executor::enter::Enter"]],["impl Unpin for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl Unpin for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Unpin for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<T> Unpin for LocalFutureObj<'_, T>"],["impl<T> Unpin for FutureObj<'_, T>"]], +"futures_util":[["impl<T> Unpin for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Unpin for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Unpin for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<F> Unpin for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Unpin for Either<A, B>where
    A: Unpin,
    B: Unpin,
",1,["futures_util::future::either::Either"]],["impl Unpin for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Unpin for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl Unpin for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St> Unpin for StreamFuture<St>where
    St: Unpin,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<'a, St: ?Sized> Unpin for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<T, Item> Unpin for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<T, E> Unpin for TryChunksError<T, E>where
    E: Unpin,
    T: Unpin,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl Unpin for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<'a, Fut> Unpin for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> Unpin for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Unpin for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> Unpin for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> Unpin for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<'a, St> Unpin for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Unpin for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Unpin for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, R> Unpin for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<T> Unpin for Cursor<T>where
    T: Unpin,
",1,["futures_util::io::cursor::Cursor"]],["impl Unpin for Empty",1,["futures_util::io::empty::Empty"]],["impl Unpin for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl Unpin for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Unpin for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> Unpin for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> Unpin for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<T> Unpin for Window<T>where
    T: Unpin,
",1,["futures_util::io::window::Window"]],["impl<T: ?Sized> Unpin for Mutex<T>where
    T: Unpin,
",1,["futures_util::lock::mutex::Mutex"]],["impl<T: ?Sized> Unpin for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T: ?Sized> Unpin for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Unpin for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T: ?Sized> Unpin for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T: ?Sized, U: ?Sized> Unpin for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]],["impl<'__pin, Fut> Unpin for Fuse<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<'__pin, F> Unpin for Flatten<F>where
    __Origin<'__pin, F>: Unpin,
    F: Future,
"],["impl<'__pin, F> Unpin for FlattenStream<F>where
    __Origin<'__pin, F>: Unpin,
    F: Future,
"],["impl<'__pin, Fut, F> Unpin for Map<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, F> Unpin for IntoStream<F>where
    __Origin<'__pin, F>: Unpin,
"],["impl<'__pin, Fut, T> Unpin for MapInto<Fut, T>where
    __Origin<'__pin, Fut, T>: Unpin,
"],["impl<'__pin, Fut1, Fut2, F> Unpin for Then<Fut1, Fut2, F>where
    __Origin<'__pin, Fut1, Fut2, F>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for Inspect<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, Fut> Unpin for NeverError<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<'__pin, Fut> Unpin for UnitError<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<'__pin, Fut> Unpin for CatchUnwind<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<'__pin, Fut: Future> Unpin for Remote<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<Fut: Future> Unpin for Shared<Fut>"],["impl<'__pin, Fut> Unpin for IntoFuture<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<'__pin, Fut1, Fut2> Unpin for TryFlatten<Fut1, Fut2>where
    __Origin<'__pin, Fut1, Fut2>: Unpin,
"],["impl<'__pin, Fut> Unpin for TryFlattenStream<Fut>where
    __Origin<'__pin, Fut>: Unpin,
    Fut: TryFuture,
"],["impl<'__pin, Fut, Si> Unpin for FlattenSink<Fut, Si>where
    __Origin<'__pin, Fut, Si>: Unpin,
"],["impl<'__pin, Fut1, Fut2, F> Unpin for AndThen<Fut1, Fut2, F>where
    __Origin<'__pin, Fut1, Fut2, F>: Unpin,
"],["impl<'__pin, Fut1, Fut2, F> Unpin for OrElse<Fut1, Fut2, F>where
    __Origin<'__pin, Fut1, Fut2, F>: Unpin,
"],["impl<'__pin, Fut, E> Unpin for ErrInto<Fut, E>where
    __Origin<'__pin, Fut, E>: Unpin,
"],["impl<'__pin, Fut, E> Unpin for OkInto<Fut, E>where
    __Origin<'__pin, Fut, E>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for InspectOk<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for InspectErr<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for MapOk<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for MapErr<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<'__pin, Fut, F, G> Unpin for MapOkOrElse<Fut, F, G>where
    __Origin<'__pin, Fut, F, G>: Unpin,
"],["impl<'__pin, Fut, F> Unpin for UnwrapOrElse<Fut, F>where
    __Origin<'__pin, Fut, F>: Unpin,
"],["impl<F> Unpin for Lazy<F>"],["impl<T> Unpin for Pending<T>"],["impl<Fut: Future + Unpin> Unpin for MaybeDone<Fut>"],["impl<Fut: TryFuture + Unpin> Unpin for TryMaybeDone<Fut>"],["impl<'__pin, F> Unpin for OptionFuture<F>where
    __Origin<'__pin, F>: Unpin,
"],["impl<F> Unpin for PollFn<F>"],["impl<'__pin, T> Unpin for PollImmediate<T>where
    __Origin<'__pin, T>: Unpin,
"],["impl<T> Unpin for Ready<T>"],["impl<'__pin, Fut1: Future, Fut2: Future> Unpin for Join<Fut1, Fut2>where
    __Origin<'__pin, Fut1, Fut2>: Unpin,
"],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future> Unpin for Join3<Fut1, Fut2, Fut3>where
    __Origin<'__pin, Fut1, Fut2, Fut3>: Unpin,
"],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future, Fut4: Future> Unpin for Join4<Fut1, Fut2, Fut3, Fut4>where
    __Origin<'__pin, Fut1, Fut2, Fut3, Fut4>: Unpin,
"],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future, Fut4: Future, Fut5: Future> Unpin for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    __Origin<'__pin, Fut1, Fut2, Fut3, Fut4, Fut5>: Unpin,
"],["impl<A: Unpin, B: Unpin> Unpin for Select<A, B>"],["impl<Fut: Unpin> Unpin for SelectAll<Fut>"],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture> Unpin for TryJoin<Fut1, Fut2>where
    __Origin<'__pin, Fut1, Fut2>: Unpin,
"],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture> Unpin for TryJoin3<Fut1, Fut2, Fut3>where
    __Origin<'__pin, Fut1, Fut2, Fut3>: Unpin,
"],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture, Fut4: TryFuture> Unpin for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    __Origin<'__pin, Fut1, Fut2, Fut3, Fut4>: Unpin,
"],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture, Fut4: TryFuture, Fut5: TryFuture> Unpin for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    __Origin<'__pin, Fut1, Fut2, Fut3, Fut4, Fut5>: Unpin,
"],["impl<A: Unpin, B: Unpin> Unpin for TrySelect<A, B>"],["impl<Fut: Unpin> Unpin for SelectOk<Fut>"],["impl<'__pin, St1, St2> Unpin for Chain<St1, St2>where
    __Origin<'__pin, St1, St2>: Unpin,
"],["impl<'__pin, St, C> Unpin for Collect<St, C>where
    __Origin<'__pin, St, C>: Unpin,
"],["impl<'__pin, St, FromA, FromB> Unpin for Unzip<St, FromA, FromB>where
    __Origin<'__pin, St, FromA, FromB>: Unpin,
"],["impl<'__pin, St: Stream> Unpin for Concat<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St> Unpin for Cycle<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St> Unpin for Enumerate<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for Filter<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
    St: Stream,
"],["impl<'__pin, St, Fut, F> Unpin for FilterMap<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St> Unpin for Flatten<St>where
    __Origin<'__pin, St>: Unpin,
    St: Stream,
"],["impl<'__pin, St, Fut, T, F> Unpin for Fold<St, Fut, T, F>where
    __Origin<'__pin, St, Fut, T, F>: Unpin,
"],["impl<'__pin, St, Si> Unpin for Forward<St, Si>where
    __Origin<'__pin, St, Si>: Unpin,
    St: TryStream,
"],["impl<'__pin, St, Fut, F> Unpin for ForEach<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St> Unpin for Fuse<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, F> Unpin for Inspect<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St, F> Unpin for Map<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St, U, F> Unpin for FlatMap<St, U, F>where
    __Origin<'__pin, St, U, F>: Unpin,
"],["impl<St: ?Sized + Unpin> Unpin for Next<'_, St>"],["impl<'__pin, St: Stream> Unpin for Peekable<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, 'a, St: Stream> Unpin for Peek<'a, St>where
    __Origin<'__pin, 'a, St>: Unpin,
"],["impl<'__pin, 'a, St: Stream> Unpin for PeekMut<'a, St>where
    __Origin<'__pin, 'a, St>: Unpin,
"],["impl<'__pin, 'a, St: Stream, F> Unpin for NextIf<'a, St, F>where
    __Origin<'__pin, 'a, St, F>: Unpin,
"],["impl<'__pin, 'a, St: Stream, T: ?Sized> Unpin for NextIfEq<'a, St, T>where
    __Origin<'__pin, 'a, St, T>: Unpin,
"],["impl<'__pin, St> Unpin for Skip<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for SkipWhile<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
    St: Stream,
"],["impl<'__pin, St> Unpin for Take<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St: Stream, Fut, F> Unpin for TakeWhile<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St: Stream, Fut: Future> Unpin for TakeUntil<St, Fut>where
    __Origin<'__pin, St, Fut>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for Then<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St1: Stream, St2: Stream> Unpin for Zip<St1, St2>where
    __Origin<'__pin, St1, St2>: Unpin,
"],["impl<'__pin, St: Stream> Unpin for Chunks<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St: Stream> Unpin for ReadyChunks<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St: Stream, S, Fut, F> Unpin for Scan<St, S, Fut, F>where
    __Origin<'__pin, St, S, Fut, F>: Unpin,
"],["impl<'__pin, St> Unpin for BufferUnordered<St>where
    __Origin<'__pin, St>: Unpin,
    St: Stream,
"],["impl<'__pin, St> Unpin for Buffered<St>where
    __Origin<'__pin, St>: Unpin,
    St: Stream,
    St::Item: Future,
"],["impl<'__pin, St, Fut, F> Unpin for ForEachConcurrent<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<S> Unpin for SplitStream<S>"],["impl<S, Item> Unpin for SplitSink<S, Item>"],["impl<'__pin, St> Unpin for CatchUnwind<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for AndThen<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St, E> Unpin for ErrInto<St, E>where
    __Origin<'__pin, St, E>: Unpin,
"],["impl<'__pin, St, F> Unpin for InspectOk<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St, F> Unpin for InspectErr<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St> Unpin for IntoStream<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, F> Unpin for MapOk<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St, F> Unpin for MapErr<St, F>where
    __Origin<'__pin, St, F>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for OrElse<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<St: ?Sized + Unpin> Unpin for TryNext<'_, St>"],["impl<'__pin, St, Fut, F> Unpin for TryForEach<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for TryFilter<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
    St: TryStream,
"],["impl<'__pin, St, Fut, F> Unpin for TryFilterMap<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St> Unpin for TryFlatten<St>where
    __Origin<'__pin, St>: Unpin,
    St: TryStream,
"],["impl<'__pin, St, C> Unpin for TryCollect<St, C>where
    __Origin<'__pin, St, C>: Unpin,
"],["impl<'__pin, St: TryStream> Unpin for TryConcat<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St: TryStream> Unpin for TryChunks<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<'__pin, St, Fut, T, F> Unpin for TryFold<St, Fut, T, F>where
    __Origin<'__pin, St, Fut, T, F>: Unpin,
"],["impl<'__pin, T, F, Fut> Unpin for TryUnfold<T, F, Fut>where
    __Origin<'__pin, T, F, Fut>: Unpin,
"],["impl<'__pin, St, Fut, F> Unpin for TrySkipWhile<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
    St: TryStream,
"],["impl<'__pin, St, Fut, F> Unpin for TryTakeWhile<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
    St: TryStream,
"],["impl<'__pin, St> Unpin for TryBufferUnordered<St>where
    __Origin<'__pin, St>: Unpin,
    St: TryStream,
"],["impl<'__pin, St> Unpin for TryBuffered<St>where
    __Origin<'__pin, St>: Unpin,
    St: TryStream,
    St::Ok: TryFuture,
"],["impl<'__pin, St, Fut, F> Unpin for TryForEachConcurrent<St, Fut, F>where
    __Origin<'__pin, St, Fut, F>: Unpin,
"],["impl<'__pin, St> Unpin for IntoAsyncRead<St>where
    __Origin<'__pin, St>: Unpin,
    St: TryStream<Error = Error>,
    St::Ok: AsRef<[u8]>,
"],["impl<I> Unpin for Iter<I>"],["impl<T> Unpin for Repeat<T>"],["impl<A, F: FnMut() -> A> Unpin for RepeatWith<F>"],["impl<T> Unpin for Empty<T>"],["impl<'__pin, Fut> Unpin for Once<Fut>where
    __Origin<'__pin, Fut>: Unpin,
"],["impl<T> Unpin for Pending<T>"],["impl<F> Unpin for PollFn<F>"],["impl<'__pin, S> Unpin for PollImmediate<S>where
    __Origin<'__pin, S>: Unpin,
"],["impl<'__pin, St1, St2> Unpin for Select<St1, St2>where
    __Origin<'__pin, St1, St2>: Unpin,
"],["impl<'__pin, St1, St2, Clos, State> Unpin for SelectWithStrategy<St1, St2, Clos, State>where
    __Origin<'__pin, St1, St2, Clos, State>: Unpin,
"],["impl<'__pin, T, F, Fut> Unpin for Unfold<T, F, Fut>where
    __Origin<'__pin, T, F, Fut>: Unpin,
"],["impl<T: Future> Unpin for FuturesOrdered<T>"],["impl<Fut> Unpin for FuturesUnordered<Fut>"],["impl<'__pin, St> Unpin for SelectAll<St>where
    __Origin<'__pin, St>: Unpin,
"],["impl<Si: Unpin + ?Sized, Item> Unpin for Close<'_, Si, Item>"],["impl<T> Unpin for Drain<T>"],["impl<'__pin, Si1, Si2> Unpin for Fanout<Si1, Si2>where
    __Origin<'__pin, Si1, Si2>: Unpin,
"],["impl<Si: Unpin + ?Sized, Item> Unpin for Feed<'_, Si, Item>"],["impl<Si: Unpin + ?Sized, Item> Unpin for Flush<'_, Si, Item>"],["impl<'__pin, Si: Sink<Item>, Item, E> Unpin for SinkErrInto<Si, Item, E>where
    __Origin<'__pin, Si, Item, E>: Unpin,
"],["impl<'__pin, Si, F> Unpin for SinkMapErr<Si, F>where
    __Origin<'__pin, Si, F>: Unpin,
"],["impl<Si: Unpin + ?Sized, Item> Unpin for Send<'_, Si, Item>"],["impl<Si, St> Unpin for SendAll<'_, Si, St>where
    Si: Unpin + ?Sized,
    St: TryStream + Unpin + ?Sized,
"],["impl<'__pin, T, F, R> Unpin for Unfold<T, F, R>where
    __Origin<'__pin, T, F, R>: Unpin,
"],["impl<'__pin, Si, Item, U, Fut, F> Unpin for With<Si, Item, U, Fut, F>where
    __Origin<'__pin, Si, Item, U, Fut, F>: Unpin,
"],["impl<'__pin, Si, Item, U, St, F> Unpin for WithFlatMap<Si, Item, U, St, F>where
    __Origin<'__pin, Si, Item, U, St, F>: Unpin,
"],["impl<'__pin, Si, Item> Unpin for Buffer<Si, Item>where
    __Origin<'__pin, Si, Item>: Unpin,
"],["impl<T> Unpin for AllowStdIo<T>"],["impl<'__pin, R> Unpin for BufReader<R>where
    __Origin<'__pin, R>: Unpin,
"],["impl<'__pin, W> Unpin for BufWriter<W>where
    __Origin<'__pin, W>: Unpin,
"],["impl<'__pin, W: AsyncWrite> Unpin for LineWriter<W>where
    __Origin<'__pin, W>: Unpin,
"],["impl<'__pin, T, U> Unpin for Chain<T, U>where
    __Origin<'__pin, T, U>: Unpin,
"],["impl<W: ?Sized + Unpin> Unpin for Close<'_, W>"],["impl<'__pin, 'a, R, W: ?Sized> Unpin for Copy<'a, R, W>where
    __Origin<'__pin, 'a, R, W>: Unpin,
"],["impl<'__pin, 'a, R, W: ?Sized> Unpin for CopyBuf<'a, R, W>where
    __Origin<'__pin, 'a, R, W>: Unpin,
"],["impl<'__pin, 'a, R, W: ?Sized> Unpin for CopyBufAbortable<'a, R, W>where
    __Origin<'__pin, 'a, R, W>: Unpin,
"],["impl<R: ?Sized> Unpin for FillBuf<'_, R>"],["impl<W: ?Sized + Unpin> Unpin for Flush<'_, W>"],["impl<'__pin, W, Item> Unpin for IntoSink<W, Item>where
    __Origin<'__pin, W, Item>: Unpin,
"],["impl<'__pin, R> Unpin for Lines<R>where
    __Origin<'__pin, R>: Unpin,
"],["impl<R: ?Sized + Unpin> Unpin for Read<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadVectored<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadExact<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadLine<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadToEnd<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadToString<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadUntil<'_, R>"],["impl<S: ?Sized + Unpin> Unpin for Seek<'_, S>"],["impl<'__pin, R> Unpin for Take<R>where
    __Origin<'__pin, R>: Unpin,
"],["impl<W: ?Sized + Unpin> Unpin for Write<'_, W>"],["impl<W: ?Sized + Unpin> Unpin for WriteVectored<'_, W>"],["impl<W: ?Sized + Unpin> Unpin for WriteAll<'_, W>"],["impl<'__pin, T> Unpin for Abortable<T>where
    __Origin<'__pin, T>: Unpin,
"]], +"generic_array":[["impl<T, N> Unpin for GenericArrayIter<T, N>where
    <N as ArrayLength<T>>::ArrayType: Unpin,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> Unpin for GenericArray<T, U>where
    <U as ArrayLength<T>>::ArrayType: Unpin,
",1,["generic_array::GenericArray"]]], +"getrandom":[["impl Unpin for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl Unpin for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> Unpin for WALWriter<F>where
    F: Unpin,
",1,["growthring::wal::WALWriter"]],["impl Unpin for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Unpin for WALLoader",1,["growthring::wal::WALLoader"]],["impl Unpin for WALFileAIO",1,["growthring::WALFileAIO"]],["impl Unpin for WALStoreAIO",1,["growthring::WALStoreAIO"]]], +"hashbrown":[["impl<K, V, S, A> Unpin for HashMap<K, V, S, A>where
    A: Unpin,
    K: Unpin,
    S: Unpin,
    V: Unpin,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Unpin for Iter<'a, K, V>",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Unpin for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Unpin for IntoIter<K, V, A>where
    A: Unpin,
    K: Unpin,
    V: Unpin,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Unpin for IntoKeys<K, V, A>where
    A: Unpin,
    K: Unpin,
    V: Unpin,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Unpin for IntoValues<K, V, A>where
    A: Unpin,
    K: Unpin,
    V: Unpin,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Unpin for Keys<'a, K, V>",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Unpin for Values<'a, K, V>",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Unpin for Drain<'a, K, V, A>where
    A: Unpin,
    K: Unpin,
    V: Unpin,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Unpin for DrainFilter<'a, K, V, F, A>where
    F: Unpin,
",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Unpin for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryBuilder<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Unpin for Entry<'a, K, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Unpin for OccupiedEntry<'a, K, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> Unpin for VacantEntry<'a, K, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for EntryRef<'a, 'b, K, Q, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
    K: Unpin,
",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Unpin for OccupiedError<'a, K, V, S, A>where
    K: Unpin,
    V: Unpin,
",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Unpin for HashSet<T, S, A>where
    A: Unpin,
    S: Unpin,
    T: Unpin,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> Unpin for Iter<'a, K>",1,["hashbrown::set::Iter"]],["impl<K, A> Unpin for IntoIter<K, A>where
    A: Unpin,
    K: Unpin,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Unpin for Drain<'a, K, A>where
    A: Unpin,
    K: Unpin,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Unpin for DrainFilter<'a, K, F, A>where
    F: Unpin,
",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Unpin for Intersection<'a, T, S, A>",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Unpin for Difference<'a, T, S, A>",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Unpin for SymmetricDifference<'a, T, S, A>",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Unpin for Union<'a, T, S, A>",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Unpin for Entry<'a, T, S, A>where
    T: Unpin,
",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Unpin for OccupiedEntry<'a, T, S, A>where
    T: Unpin,
",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Unpin for VacantEntry<'a, T, S, A>where
    T: Unpin,
",1,["hashbrown::set::VacantEntry"]],["impl Unpin for TryReserveError",1,["hashbrown::TryReserveError"]]], +"heck":[["impl<T> Unpin for AsKebabCase<T>where
    T: Unpin,
",1,["heck::kebab::AsKebabCase"]],["impl<T> Unpin for AsLowerCamelCase<T>where
    T: Unpin,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Unpin for AsShoutyKebabCase<T>where
    T: Unpin,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Unpin for AsShoutySnakeCase<T>where
    T: Unpin,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Unpin for AsSnakeCase<T>where
    T: Unpin,
",1,["heck::snake::AsSnakeCase"]],["impl<T> Unpin for AsTitleCase<T>where
    T: Unpin,
",1,["heck::title::AsTitleCase"]],["impl<T> Unpin for AsUpperCamelCase<T>where
    T: Unpin,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl Unpin for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl Unpin for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Unpin for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Unpin for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Unpin for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Unpin for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Unpin for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Unpin for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Unpin for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl Unpin for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Unpin for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Unpin for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Unpin for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Unpin for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Unpin for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Unpin for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Unpin for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Unpin for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Unpin for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl Unpin for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl Unpin for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Unpin for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Unpin for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Unpin for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Unpin for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Unpin for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl Unpin for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Unpin for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Unpin for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Unpin for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Unpin for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Unpin for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Unpin for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Unpin for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl Unpin for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Unpin for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Unpin for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl Unpin for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl Unpin for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Unpin for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Unpin for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Unpin for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Unpin for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Unpin for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Unpin for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Unpin for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl Unpin for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Unpin for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Unpin for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl Unpin for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Unpin for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Unpin for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Unpin for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Unpin for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Unpin for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Unpin for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Unpin for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Unpin for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Unpin for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Unpin for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl Unpin for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl Unpin for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl Unpin for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Unpin for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Unpin for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Unpin for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Unpin for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Unpin for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Unpin for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl Unpin for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Unpin for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Unpin for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Unpin for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Unpin for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Unpin for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Unpin for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Unpin for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Unpin for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Unpin for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Unpin for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Unpin for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Unpin for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Unpin for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl Unpin for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Unpin for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Unpin for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Unpin for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Unpin for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Unpin for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl Unpin for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Unpin for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Unpin for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Unpin for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Unpin for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Unpin for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Unpin for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Unpin for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Unpin for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Unpin for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl Unpin for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl Unpin for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Unpin for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Unpin for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Unpin for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Unpin for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Unpin for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Unpin for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Unpin for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Unpin for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Unpin for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Unpin for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Unpin for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Unpin for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Unpin for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Unpin for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl Unpin for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Unpin for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Unpin for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Unpin for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Unpin for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Unpin for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Unpin for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Unpin for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Unpin for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Unpin for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Unpin for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Unpin for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Unpin for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Unpin for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Unpin for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Unpin for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl Unpin for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl Unpin for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Unpin for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Unpin for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Unpin for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Unpin for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Unpin for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Unpin for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Unpin for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Unpin for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Unpin for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Unpin for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Unpin for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Unpin for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Unpin for timezone",1,["libc::unix::linux_like::timezone"]],["impl Unpin for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Unpin for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Unpin for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Unpin for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Unpin for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Unpin for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Unpin for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl Unpin for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Unpin for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Unpin for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl Unpin for tm",1,["libc::unix::linux_like::tm"]],["impl Unpin for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl Unpin for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl Unpin for lconv",1,["libc::unix::linux_like::lconv"]],["impl Unpin for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl Unpin for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Unpin for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Unpin for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Unpin for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Unpin for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl Unpin for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Unpin for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Unpin for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Unpin for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Unpin for utsname",1,["libc::unix::linux_like::utsname"]],["impl Unpin for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Unpin for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Unpin for DIR",1,["libc::unix::DIR"]],["impl Unpin for group",1,["libc::unix::group"]],["impl Unpin for utimbuf",1,["libc::unix::utimbuf"]],["impl Unpin for timeval",1,["libc::unix::timeval"]],["impl Unpin for timespec",1,["libc::unix::timespec"]],["impl Unpin for rlimit",1,["libc::unix::rlimit"]],["impl Unpin for rusage",1,["libc::unix::rusage"]],["impl Unpin for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl Unpin for hostent",1,["libc::unix::hostent"]],["impl Unpin for iovec",1,["libc::unix::iovec"]],["impl Unpin for pollfd",1,["libc::unix::pollfd"]],["impl Unpin for winsize",1,["libc::unix::winsize"]],["impl Unpin for linger",1,["libc::unix::linger"]],["impl Unpin for sigval",1,["libc::unix::sigval"]],["impl Unpin for itimerval",1,["libc::unix::itimerval"]],["impl Unpin for tms",1,["libc::unix::tms"]],["impl Unpin for servent",1,["libc::unix::servent"]],["impl Unpin for protoent",1,["libc::unix::protoent"]],["impl Unpin for FILE",1,["libc::unix::FILE"]],["impl Unpin for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<R, T: ?Sized> Unpin for Mutex<R, T>where
    R: Unpin,
    T: Unpin,
",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T: ?Sized> Unpin for MutexGuard<'a, R, T>where
    <R as RawMutex>::GuardMarker: Unpin,
",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> Unpin for RawReentrantMutex<R, G>where
    G: Unpin,
    R: Unpin,
",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T: ?Sized> Unpin for ReentrantMutex<R, G, T>where
    G: Unpin,
    R: Unpin,
    T: Unpin,
",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T: ?Sized> Unpin for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T: ?Sized> Unpin for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T: ?Sized> Unpin for RwLock<R, T>where
    R: Unpin,
    T: Unpin,
",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T: ?Sized> Unpin for RwLockReadGuard<'a, R, T>where
    <R as RawRwLock>::GuardMarker: Unpin,
",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for RwLockWriteGuard<'a, R, T>where
    <R as RawRwLock>::GuardMarker: Unpin,
",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Unpin for RwLockUpgradableReadGuard<'a, R, T>where
    <R as RawRwLock>::GuardMarker: Unpin,
",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedRwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl Unpin for GuardSend",1,["lock_api::GuardSend"]],["impl Unpin for GuardNoSend",1,["lock_api::GuardNoSend"]]], +"lru":[["impl<K, V, S> Unpin for LruCache<K, V, S>where
    S: Unpin,
",1,["lru::LruCache"]],["impl<'a, K, V> Unpin for Iter<'a, K, V>",1,["lru::Iter"]],["impl<'a, K, V> Unpin for IterMut<'a, K, V>",1,["lru::IterMut"]],["impl<K, V> Unpin for IntoIter<K, V>",1,["lru::IntoIter"]]], +"memchr":[["impl<'a> Unpin for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Unpin for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Unpin for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Unpin for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Unpin for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Unpin for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Unpin for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Unpin for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Unpin for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl Unpin for Dir",1,["nix::dir::Dir"]],["impl<'d> Unpin for Iter<'d>",1,["nix::dir::Iter"]],["impl Unpin for OwningIter",1,["nix::dir::OwningIter"]],["impl Unpin for Entry",1,["nix::dir::Entry"]],["impl Unpin for Type",1,["nix::dir::Type"]],["impl Unpin for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Unpin for Errno",1,["nix::errno::consts::Errno"]],["impl Unpin for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Unpin for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Unpin for OFlag",1,["nix::fcntl::OFlag"]],["impl Unpin for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Unpin for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Unpin for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Unpin for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Unpin for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Unpin for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Unpin for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Unpin for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl Unpin for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl Unpin for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl Unpin for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> Unpin for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Unpin for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Unpin for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Unpin for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Unpin for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Unpin for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Unpin for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Unpin for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Unpin for MqdT",1,["nix::mqueue::MqdT"]],["impl Unpin for PollFd",1,["nix::poll::PollFd"]],["impl Unpin for PollFlags",1,["nix::poll::PollFlags"]],["impl Unpin for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Unpin for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Unpin for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Unpin for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Unpin for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Unpin for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Unpin for LioMode",1,["nix::sys::aio::LioMode"]],["impl Unpin for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl !Unpin for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> !Unpin for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> !Unpin for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Unpin for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Unpin for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Unpin for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Unpin for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Unpin for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Unpin for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Unpin for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Unpin for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Unpin for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Unpin for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Unpin for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Unpin for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Unpin for Persona",1,["nix::sys::personality::Persona"]],["impl Unpin for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Unpin for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Unpin for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Unpin for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Unpin for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Unpin for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Unpin for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Unpin for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Unpin for Resource",1,["nix::sys::resource::Resource"]],["impl Unpin for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Unpin for Usage",1,["nix::sys::resource::Usage"]],["impl Unpin for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Unpin for Fds<'a>",1,["nix::sys::select::Fds"]],["impl Unpin for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Unpin for Signal",1,["nix::sys::signal::Signal"]],["impl Unpin for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Unpin for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Unpin for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Unpin for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Unpin for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Unpin for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Unpin for SigAction",1,["nix::sys::signal::SigAction"]],["impl Unpin for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Unpin for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Unpin for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Unpin for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Unpin for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Unpin for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Unpin for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Unpin for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Unpin for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Unpin for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Unpin for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Unpin for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Unpin for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Unpin for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Unpin for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Unpin for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Unpin for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Unpin for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Unpin for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Unpin for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Unpin for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Unpin for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Unpin for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Unpin for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Unpin for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Unpin for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Unpin for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Unpin for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Unpin for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Unpin for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Unpin for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Unpin for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Unpin for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Unpin for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Unpin for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Unpin for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Unpin for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Unpin for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Unpin for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Unpin for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Unpin for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Unpin for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Unpin for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Unpin for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Unpin for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Unpin for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Unpin for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Unpin for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Unpin for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Unpin for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Unpin for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Unpin for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Unpin for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Unpin for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Unpin for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Unpin for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Unpin for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Unpin for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Unpin for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Unpin for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Unpin for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Unpin for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Unpin for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Unpin for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Unpin for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Unpin for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Unpin for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Unpin for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Unpin for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Unpin for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Unpin for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Unpin for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Unpin for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Unpin for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Unpin for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Unpin for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Unpin for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Unpin for AlgSetKey<T>where
    T: Unpin,
",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Unpin for SockType",1,["nix::sys::socket::SockType"]],["impl Unpin for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Unpin for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Unpin for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Unpin for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Unpin for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Unpin for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Unpin for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> Unpin for RecvMsg<'a, 's, S>where
    S: Unpin,
",1,["nix::sys::socket::RecvMsg"]],["impl<'a> Unpin for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Unpin for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Unpin for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Unpin for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> Unpin for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> Unpin for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Unpin for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Unpin for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Unpin for SFlag",1,["nix::sys::stat::SFlag"]],["impl Unpin for Mode",1,["nix::sys::stat::Mode"]],["impl Unpin for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Unpin for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Unpin for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Unpin for FsType",1,["nix::sys::statfs::FsType"]],["impl Unpin for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Unpin for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Unpin for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl Unpin for Termios",1,["nix::sys::termios::Termios"]],["impl Unpin for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Unpin for SetArg",1,["nix::sys::termios::SetArg"]],["impl Unpin for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Unpin for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Unpin for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Unpin for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Unpin for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Unpin for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Unpin for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Unpin for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Unpin for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Unpin for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Unpin for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Unpin for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> Unpin for IoVec<T>where
    T: Unpin,
",1,["nix::sys::uio::IoVec"]],["impl Unpin for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Unpin for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Unpin for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Unpin for Id",1,["nix::sys::wait::Id"]],["impl Unpin for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Unpin for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Unpin for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Unpin for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Unpin for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Unpin for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Unpin for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Unpin for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl Unpin for Timer",1,["nix::sys::timer::Timer"]],["impl Unpin for ClockId",1,["nix::time::ClockId"]],["impl Unpin for UContext",1,["nix::ucontext::UContext"]],["impl Unpin for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Unpin for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Unpin for Uid",1,["nix::unistd::Uid"]],["impl Unpin for Gid",1,["nix::unistd::Gid"]],["impl Unpin for Pid",1,["nix::unistd::Pid"]],["impl Unpin for ForkResult",1,["nix::unistd::ForkResult"]],["impl Unpin for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Unpin for Whence",1,["nix::unistd::Whence"]],["impl Unpin for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Unpin for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Unpin for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Unpin for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Unpin for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Unpin for User",1,["nix::unistd::User"]],["impl Unpin for Group",1,["nix::unistd::Group"]]], +"once_cell":[["impl<T> Unpin for OnceCell<T>where
    T: Unpin,
",1,["once_cell::unsync::OnceCell"]],["impl<T, F> Unpin for Lazy<T, F>where
    F: Unpin,
    T: Unpin,
",1,["once_cell::unsync::Lazy"]],["impl<T> Unpin for OnceCell<T>where
    T: Unpin,
",1,["once_cell::sync::OnceCell"]],["impl<T, F> Unpin for Lazy<T, F>where
    F: Unpin,
    T: Unpin,
",1,["once_cell::sync::Lazy"]],["impl<T> Unpin for OnceBox<T>",1,["once_cell::race::once_box::OnceBox"]],["impl Unpin for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Unpin for OnceBool",1,["once_cell::race::OnceBool"]]], +"parking_lot":[["impl Unpin for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Unpin for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Unpin for OnceState",1,["parking_lot::once::OnceState"]],["impl Unpin for Once",1,["parking_lot::once::Once"]],["impl Unpin for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Unpin for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Unpin for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Unpin for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl Unpin for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Unpin for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Unpin for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Unpin for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Unpin for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Unpin for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Unpin for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl Unpin for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Unpin for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Unpin for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Unpin for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Unpin for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Unpin for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Unpin for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Unpin for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Unpin for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Unpin for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Unpin for SseMachine<S3, S4, NI>where
    NI: Unpin,
    S3: Unpin,
    S4: Unpin,
",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Unpin for Avx2Machine<NI>where
    NI: Unpin,
",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Unpin for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Unpin for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Unpin for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl Unpin for Error",1,["primitive_types::Error"]],["impl Unpin for U128",1,["primitive_types::U128"]],["impl Unpin for U256",1,["primitive_types::U256"]],["impl Unpin for U512",1,["primitive_types::U512"]],["impl Unpin for H128",1,["primitive_types::H128"]],["impl Unpin for H160",1,["primitive_types::H160"]],["impl Unpin for H256",1,["primitive_types::H256"]],["impl Unpin for H384",1,["primitive_types::H384"]],["impl Unpin for H512",1,["primitive_types::H512"]],["impl Unpin for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl Unpin for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl Unpin for TokenStream",1,["proc_macro2::TokenStream"]],["impl Unpin for LexError",1,["proc_macro2::LexError"]],["impl Unpin for Span",1,["proc_macro2::Span"]],["impl Unpin for TokenTree",1,["proc_macro2::TokenTree"]],["impl Unpin for Group",1,["proc_macro2::Group"]],["impl Unpin for Delimiter",1,["proc_macro2::Delimiter"]],["impl Unpin for Punct",1,["proc_macro2::Punct"]],["impl Unpin for Spacing",1,["proc_macro2::Spacing"]],["impl Unpin for Ident",1,["proc_macro2::Ident"]],["impl Unpin for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl Unpin for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Unpin for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Unpin for DistIter<D, R, T>where
    D: Unpin,
    R: Unpin,
    T: Unpin,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Unpin for DistMap<D, F, T, S>where
    D: Unpin,
    F: Unpin,
",1,["rand::distributions::distribution::DistMap"]],["impl Unpin for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Unpin for Open01",1,["rand::distributions::float::Open01"]],["impl Unpin for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Unpin for Slice<'a, T>",1,["rand::distributions::slice::Slice"]],["impl<X> Unpin for WeightedIndex<X>where
    X: Unpin,
    <X as SampleUniform>::Sampler: Unpin,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Unpin for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Unpin for Uniform<X>where
    <X as SampleUniform>::Sampler: Unpin,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> Unpin for UniformInt<X>where
    X: Unpin,
",1,["rand::distributions::uniform::UniformInt"]],["impl Unpin for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Unpin for UniformFloat<X>where
    X: Unpin,
",1,["rand::distributions::uniform::UniformFloat"]],["impl Unpin for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Unpin for WeightedIndex<W>where
    W: Unpin,
",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Unpin for Standard",1,["rand::distributions::Standard"]],["impl<R> Unpin for ReadRng<R>where
    R: Unpin,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl Unpin for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Unpin for ReseedingRng<R, Rsdr>where
    R: Unpin,
    Rsdr: Unpin,
    <R as BlockRngCore>::Results: Unpin,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Unpin for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Unpin for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Unpin for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Unpin for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Unpin for SliceChooseIter<'a, S, T>where
    T: Unpin,
",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl Unpin for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Unpin for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Unpin for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Unpin for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Unpin for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Unpin for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> Unpin for BlockRng<R>where
    R: Unpin,
    <R as BlockRngCore>::Results: Unpin,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Unpin for BlockRng64<R>where
    R: Unpin,
    <R as BlockRngCore>::Results: Unpin,
",1,["rand_core::block::BlockRng64"]],["impl Unpin for Error",1,["rand_core::error::Error"]],["impl Unpin for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl Unpin for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Unpin for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Unpin for Match<'t>",1,["regex::re_bytes::Match"]],["impl Unpin for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Unpin for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Unpin for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Unpin for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Unpin for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Unpin for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Unpin for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Unpin for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Unpin for SubCaptureMatches<'c, 't>where
    't: 'c,
",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Unpin for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Unpin for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Unpin for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Unpin for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Unpin for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Unpin for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Unpin for Error",1,["regex::error::Error"]],["impl Unpin for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Unpin for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Unpin for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Unpin for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Unpin for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Unpin for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Unpin for Match<'t>",1,["regex::re_unicode::Match"]],["impl Unpin for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Unpin for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Unpin for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Unpin for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Unpin for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Unpin for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Unpin for SubCaptureMatches<'c, 't>where
    't: 'c,
",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Unpin for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Unpin for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Unpin for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Unpin for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl Unpin for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl Unpin for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Unpin for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Unpin for Error",1,["regex_syntax::ast::Error"]],["impl Unpin for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Unpin for Span",1,["regex_syntax::ast::Span"]],["impl Unpin for Position",1,["regex_syntax::ast::Position"]],["impl Unpin for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Unpin for Comment",1,["regex_syntax::ast::Comment"]],["impl Unpin for Ast",1,["regex_syntax::ast::Ast"]],["impl Unpin for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Unpin for Concat",1,["regex_syntax::ast::Concat"]],["impl Unpin for Literal",1,["regex_syntax::ast::Literal"]],["impl Unpin for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Unpin for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Unpin for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Unpin for Class",1,["regex_syntax::ast::Class"]],["impl Unpin for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Unpin for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Unpin for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Unpin for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Unpin for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Unpin for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Unpin for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Unpin for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Unpin for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Unpin for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Unpin for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Unpin for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Unpin for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Unpin for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Unpin for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Unpin for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Unpin for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Unpin for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Unpin for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Unpin for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Unpin for Group",1,["regex_syntax::ast::Group"]],["impl Unpin for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Unpin for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Unpin for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Unpin for Flags",1,["regex_syntax::ast::Flags"]],["impl Unpin for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Unpin for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Unpin for Flag",1,["regex_syntax::ast::Flag"]],["impl Unpin for Error",1,["regex_syntax::error::Error"]],["impl Unpin for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Unpin for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Unpin for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Unpin for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl Unpin for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Unpin for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Unpin for Error",1,["regex_syntax::hir::Error"]],["impl Unpin for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Unpin for Hir",1,["regex_syntax::hir::Hir"]],["impl Unpin for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Unpin for Literal",1,["regex_syntax::hir::Literal"]],["impl Unpin for Class",1,["regex_syntax::hir::Class"]],["impl Unpin for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Unpin for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Unpin for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Unpin for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Unpin for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Unpin for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Unpin for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Unpin for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Unpin for Group",1,["regex_syntax::hir::Group"]],["impl Unpin for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Unpin for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Unpin for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Unpin for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Unpin for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl Unpin for Parser",1,["regex_syntax::parser::Parser"]],["impl Unpin for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Unpin for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Unpin for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Unpin for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl Unpin for DecoderError",1,["rlp::error::DecoderError"]],["impl Unpin for Prototype",1,["rlp::rlpin::Prototype"]],["impl Unpin for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> Unpin for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> Unpin for RlpIterator<'a, 'view>where
    'a: 'view,
",1,["rlp::rlpin::RlpIterator"]],["impl Unpin for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> Unpin for ToHexIter<T>where
    T: Unpin,
",1,["rustc_hex::ToHexIter"]],["impl Unpin for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Unpin for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl Unpin for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl Unpin for Always",1,["scopeguard::Always"]],["impl<T, F, S> Unpin for ScopeGuard<T, F, S>where
    F: Unpin,
    T: Unpin,
",1,["scopeguard::ScopeGuard"]]], +"serde":[["impl Unpin for Error",1,["serde::de::value::Error"]],["impl<E> Unpin for UnitDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::UnitDeserializer"]],["impl<E> Unpin for BoolDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::BoolDeserializer"]],["impl<E> Unpin for I8Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::I8Deserializer"]],["impl<E> Unpin for I16Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::I16Deserializer"]],["impl<E> Unpin for I32Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::I32Deserializer"]],["impl<E> Unpin for I64Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::I64Deserializer"]],["impl<E> Unpin for IsizeDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Unpin for U8Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::U8Deserializer"]],["impl<E> Unpin for U16Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::U16Deserializer"]],["impl<E> Unpin for U64Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::U64Deserializer"]],["impl<E> Unpin for UsizeDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Unpin for F32Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::F32Deserializer"]],["impl<E> Unpin for F64Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::F64Deserializer"]],["impl<E> Unpin for CharDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::CharDeserializer"]],["impl<E> Unpin for I128Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::I128Deserializer"]],["impl<E> Unpin for U128Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::U128Deserializer"]],["impl<E> Unpin for U32Deserializer<E>where
    E: Unpin,
",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Unpin for StrDeserializer<'a, E>where
    E: Unpin,
",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Unpin for BorrowedStrDeserializer<'de, E>where
    E: Unpin,
",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Unpin for StringDeserializer<E>where
    E: Unpin,
",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Unpin for CowStrDeserializer<'a, E>where
    E: Unpin,
",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Unpin for BytesDeserializer<'a, E>where
    E: Unpin,
",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Unpin for BorrowedBytesDeserializer<'de, E>where
    E: Unpin,
",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Unpin for SeqDeserializer<I, E>where
    E: Unpin,
    I: Unpin,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> Unpin for SeqAccessDeserializer<A>where
    A: Unpin,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Unpin for MapDeserializer<'de, I, E>where
    E: Unpin,
    I: Unpin,
    <<I as Iterator>::Item as Pair>::Second: Unpin,
",1,["serde::de::value::MapDeserializer"]],["impl<A> Unpin for MapAccessDeserializer<A>where
    A: Unpin,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Unpin for EnumAccessDeserializer<A>where
    A: Unpin,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl Unpin for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Unpin for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Unpin for Impossible<Ok, Error>where
    Error: Unpin,
    Ok: Unpin,
",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl Unpin for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Unpin for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Unpin for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Unpin for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Unpin for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Unpin for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Unpin for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Unpin for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Unpin for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Unpin for Shake128Core",1,["sha3::Shake128Core"]],["impl Unpin for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Unpin for Shake256Core",1,["sha3::Shake256Core"]],["impl Unpin for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Unpin for CShake128Core",1,["sha3::CShake128Core"]],["impl Unpin for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Unpin for CShake256Core",1,["sha3::CShake256Core"]],["impl Unpin for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl Unpin for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Unpin for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> Unpin for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Unpin for ShaleError",1,["shale::ShaleError"]],["impl Unpin for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Unpin for ObjPtr<T>where
    T: Unpin,
",1,["shale::ObjPtr"]],["impl<T: ?Sized> Unpin for Obj<T>",1,["shale::Obj"]],["impl<'a, T> Unpin for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> Unpin for MummyObj<T>where
    T: Unpin,
",1,["shale::MummyObj"]],["impl Unpin for PlainMem",1,["shale::PlainMem"]],["impl<T: ?Sized> Unpin for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> Unpin for Slab<T>where
    T: Unpin,
",1,["slab::Slab"]],["impl<'a, T> Unpin for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> Unpin for IntoIter<T>where
    T: Unpin,
",1,["slab::IntoIter"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["slab::Iter"]],["impl<'a, T> Unpin for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> Unpin for Drain<'a, T>",1,["slab::Drain"]]], +"smallvec":[["impl Unpin for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> Unpin for Drain<'a, T>",1,["smallvec::Drain"]],["impl<A> Unpin for SmallVec<A>where
    A: Unpin,
",1,["smallvec::SmallVec"]],["impl<A> Unpin for IntoIter<A>where
    A: Unpin,
",1,["smallvec::IntoIter"]]], +"syn":[["impl Unpin for Underscore",1,["syn::token::Underscore"]],["impl Unpin for Abstract",1,["syn::token::Abstract"]],["impl Unpin for As",1,["syn::token::As"]],["impl Unpin for Async",1,["syn::token::Async"]],["impl Unpin for Auto",1,["syn::token::Auto"]],["impl Unpin for Await",1,["syn::token::Await"]],["impl Unpin for Become",1,["syn::token::Become"]],["impl Unpin for Box",1,["syn::token::Box"]],["impl Unpin for Break",1,["syn::token::Break"]],["impl Unpin for Const",1,["syn::token::Const"]],["impl Unpin for Continue",1,["syn::token::Continue"]],["impl Unpin for Crate",1,["syn::token::Crate"]],["impl Unpin for Default",1,["syn::token::Default"]],["impl Unpin for Do",1,["syn::token::Do"]],["impl Unpin for Dyn",1,["syn::token::Dyn"]],["impl Unpin for Else",1,["syn::token::Else"]],["impl Unpin for Enum",1,["syn::token::Enum"]],["impl Unpin for Extern",1,["syn::token::Extern"]],["impl Unpin for Final",1,["syn::token::Final"]],["impl Unpin for Fn",1,["syn::token::Fn"]],["impl Unpin for For",1,["syn::token::For"]],["impl Unpin for If",1,["syn::token::If"]],["impl Unpin for Impl",1,["syn::token::Impl"]],["impl Unpin for In",1,["syn::token::In"]],["impl Unpin for Let",1,["syn::token::Let"]],["impl Unpin for Loop",1,["syn::token::Loop"]],["impl Unpin for Macro",1,["syn::token::Macro"]],["impl Unpin for Match",1,["syn::token::Match"]],["impl Unpin for Mod",1,["syn::token::Mod"]],["impl Unpin for Move",1,["syn::token::Move"]],["impl Unpin for Mut",1,["syn::token::Mut"]],["impl Unpin for Override",1,["syn::token::Override"]],["impl Unpin for Priv",1,["syn::token::Priv"]],["impl Unpin for Pub",1,["syn::token::Pub"]],["impl Unpin for Ref",1,["syn::token::Ref"]],["impl Unpin for Return",1,["syn::token::Return"]],["impl Unpin for SelfType",1,["syn::token::SelfType"]],["impl Unpin for SelfValue",1,["syn::token::SelfValue"]],["impl Unpin for Static",1,["syn::token::Static"]],["impl Unpin for Struct",1,["syn::token::Struct"]],["impl Unpin for Super",1,["syn::token::Super"]],["impl Unpin for Trait",1,["syn::token::Trait"]],["impl Unpin for Try",1,["syn::token::Try"]],["impl Unpin for Type",1,["syn::token::Type"]],["impl Unpin for Typeof",1,["syn::token::Typeof"]],["impl Unpin for Union",1,["syn::token::Union"]],["impl Unpin for Unsafe",1,["syn::token::Unsafe"]],["impl Unpin for Unsized",1,["syn::token::Unsized"]],["impl Unpin for Use",1,["syn::token::Use"]],["impl Unpin for Virtual",1,["syn::token::Virtual"]],["impl Unpin for Where",1,["syn::token::Where"]],["impl Unpin for While",1,["syn::token::While"]],["impl Unpin for Yield",1,["syn::token::Yield"]],["impl Unpin for Add",1,["syn::token::Add"]],["impl Unpin for AddEq",1,["syn::token::AddEq"]],["impl Unpin for And",1,["syn::token::And"]],["impl Unpin for AndAnd",1,["syn::token::AndAnd"]],["impl Unpin for AndEq",1,["syn::token::AndEq"]],["impl Unpin for At",1,["syn::token::At"]],["impl Unpin for Bang",1,["syn::token::Bang"]],["impl Unpin for Caret",1,["syn::token::Caret"]],["impl Unpin for CaretEq",1,["syn::token::CaretEq"]],["impl Unpin for Colon",1,["syn::token::Colon"]],["impl Unpin for Colon2",1,["syn::token::Colon2"]],["impl Unpin for Comma",1,["syn::token::Comma"]],["impl Unpin for Div",1,["syn::token::Div"]],["impl Unpin for DivEq",1,["syn::token::DivEq"]],["impl Unpin for Dollar",1,["syn::token::Dollar"]],["impl Unpin for Dot",1,["syn::token::Dot"]],["impl Unpin for Dot2",1,["syn::token::Dot2"]],["impl Unpin for Dot3",1,["syn::token::Dot3"]],["impl Unpin for DotDotEq",1,["syn::token::DotDotEq"]],["impl Unpin for Eq",1,["syn::token::Eq"]],["impl Unpin for EqEq",1,["syn::token::EqEq"]],["impl Unpin for Ge",1,["syn::token::Ge"]],["impl Unpin for Gt",1,["syn::token::Gt"]],["impl Unpin for Le",1,["syn::token::Le"]],["impl Unpin for Lt",1,["syn::token::Lt"]],["impl Unpin for MulEq",1,["syn::token::MulEq"]],["impl Unpin for Ne",1,["syn::token::Ne"]],["impl Unpin for Or",1,["syn::token::Or"]],["impl Unpin for OrEq",1,["syn::token::OrEq"]],["impl Unpin for OrOr",1,["syn::token::OrOr"]],["impl Unpin for Pound",1,["syn::token::Pound"]],["impl Unpin for Question",1,["syn::token::Question"]],["impl Unpin for RArrow",1,["syn::token::RArrow"]],["impl Unpin for LArrow",1,["syn::token::LArrow"]],["impl Unpin for Rem",1,["syn::token::Rem"]],["impl Unpin for RemEq",1,["syn::token::RemEq"]],["impl Unpin for FatArrow",1,["syn::token::FatArrow"]],["impl Unpin for Semi",1,["syn::token::Semi"]],["impl Unpin for Shl",1,["syn::token::Shl"]],["impl Unpin for ShlEq",1,["syn::token::ShlEq"]],["impl Unpin for Shr",1,["syn::token::Shr"]],["impl Unpin for ShrEq",1,["syn::token::ShrEq"]],["impl Unpin for Star",1,["syn::token::Star"]],["impl Unpin for Sub",1,["syn::token::Sub"]],["impl Unpin for SubEq",1,["syn::token::SubEq"]],["impl Unpin for Tilde",1,["syn::token::Tilde"]],["impl Unpin for Brace",1,["syn::token::Brace"]],["impl Unpin for Bracket",1,["syn::token::Bracket"]],["impl Unpin for Paren",1,["syn::token::Paren"]],["impl Unpin for Group",1,["syn::token::Group"]],["impl Unpin for Attribute",1,["syn::attr::Attribute"]],["impl Unpin for AttrStyle",1,["syn::attr::AttrStyle"]],["impl Unpin for Meta",1,["syn::attr::Meta"]],["impl Unpin for MetaList",1,["syn::attr::MetaList"]],["impl Unpin for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl Unpin for NestedMeta",1,["syn::attr::NestedMeta"]],["impl Unpin for Variant",1,["syn::data::Variant"]],["impl Unpin for Fields",1,["syn::data::Fields"]],["impl Unpin for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl Unpin for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl Unpin for Field",1,["syn::data::Field"]],["impl Unpin for Visibility",1,["syn::data::Visibility"]],["impl Unpin for VisPublic",1,["syn::data::VisPublic"]],["impl Unpin for VisCrate",1,["syn::data::VisCrate"]],["impl Unpin for VisRestricted",1,["syn::data::VisRestricted"]],["impl Unpin for Expr",1,["syn::expr::Expr"]],["impl Unpin for ExprArray",1,["syn::expr::ExprArray"]],["impl Unpin for ExprAssign",1,["syn::expr::ExprAssign"]],["impl Unpin for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl Unpin for ExprAsync",1,["syn::expr::ExprAsync"]],["impl Unpin for ExprAwait",1,["syn::expr::ExprAwait"]],["impl Unpin for ExprBinary",1,["syn::expr::ExprBinary"]],["impl Unpin for ExprBlock",1,["syn::expr::ExprBlock"]],["impl Unpin for ExprBox",1,["syn::expr::ExprBox"]],["impl Unpin for ExprBreak",1,["syn::expr::ExprBreak"]],["impl Unpin for ExprCall",1,["syn::expr::ExprCall"]],["impl Unpin for ExprCast",1,["syn::expr::ExprCast"]],["impl Unpin for ExprClosure",1,["syn::expr::ExprClosure"]],["impl Unpin for ExprContinue",1,["syn::expr::ExprContinue"]],["impl Unpin for ExprField",1,["syn::expr::ExprField"]],["impl Unpin for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl Unpin for ExprGroup",1,["syn::expr::ExprGroup"]],["impl Unpin for ExprIf",1,["syn::expr::ExprIf"]],["impl Unpin for ExprIndex",1,["syn::expr::ExprIndex"]],["impl Unpin for ExprLet",1,["syn::expr::ExprLet"]],["impl Unpin for ExprLit",1,["syn::expr::ExprLit"]],["impl Unpin for ExprLoop",1,["syn::expr::ExprLoop"]],["impl Unpin for ExprMacro",1,["syn::expr::ExprMacro"]],["impl Unpin for ExprMatch",1,["syn::expr::ExprMatch"]],["impl Unpin for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl Unpin for ExprParen",1,["syn::expr::ExprParen"]],["impl Unpin for ExprPath",1,["syn::expr::ExprPath"]],["impl Unpin for ExprRange",1,["syn::expr::ExprRange"]],["impl Unpin for ExprReference",1,["syn::expr::ExprReference"]],["impl Unpin for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl Unpin for ExprReturn",1,["syn::expr::ExprReturn"]],["impl Unpin for ExprStruct",1,["syn::expr::ExprStruct"]],["impl Unpin for ExprTry",1,["syn::expr::ExprTry"]],["impl Unpin for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl Unpin for ExprTuple",1,["syn::expr::ExprTuple"]],["impl Unpin for ExprType",1,["syn::expr::ExprType"]],["impl Unpin for ExprUnary",1,["syn::expr::ExprUnary"]],["impl Unpin for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl Unpin for ExprWhile",1,["syn::expr::ExprWhile"]],["impl Unpin for ExprYield",1,["syn::expr::ExprYield"]],["impl Unpin for Member",1,["syn::expr::Member"]],["impl Unpin for Index",1,["syn::expr::Index"]],["impl Unpin for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl Unpin for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl Unpin for FieldValue",1,["syn::expr::FieldValue"]],["impl Unpin for Label",1,["syn::expr::Label"]],["impl Unpin for Arm",1,["syn::expr::Arm"]],["impl Unpin for RangeLimits",1,["syn::expr::RangeLimits"]],["impl Unpin for Generics",1,["syn::generics::Generics"]],["impl Unpin for GenericParam",1,["syn::generics::GenericParam"]],["impl Unpin for TypeParam",1,["syn::generics::TypeParam"]],["impl Unpin for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl Unpin for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> Unpin for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> Unpin for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> Unpin for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl Unpin for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl Unpin for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl Unpin for TraitBound",1,["syn::generics::TraitBound"]],["impl Unpin for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl Unpin for WhereClause",1,["syn::generics::WhereClause"]],["impl Unpin for WherePredicate",1,["syn::generics::WherePredicate"]],["impl Unpin for PredicateType",1,["syn::generics::PredicateType"]],["impl Unpin for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl Unpin for PredicateEq",1,["syn::generics::PredicateEq"]],["impl Unpin for Item",1,["syn::item::Item"]],["impl Unpin for ItemConst",1,["syn::item::ItemConst"]],["impl Unpin for ItemEnum",1,["syn::item::ItemEnum"]],["impl Unpin for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl Unpin for ItemFn",1,["syn::item::ItemFn"]],["impl Unpin for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl Unpin for ItemImpl",1,["syn::item::ItemImpl"]],["impl Unpin for ItemMacro",1,["syn::item::ItemMacro"]],["impl Unpin for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl Unpin for ItemMod",1,["syn::item::ItemMod"]],["impl Unpin for ItemStatic",1,["syn::item::ItemStatic"]],["impl Unpin for ItemStruct",1,["syn::item::ItemStruct"]],["impl Unpin for ItemTrait",1,["syn::item::ItemTrait"]],["impl Unpin for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl Unpin for ItemType",1,["syn::item::ItemType"]],["impl Unpin for ItemUnion",1,["syn::item::ItemUnion"]],["impl Unpin for ItemUse",1,["syn::item::ItemUse"]],["impl Unpin for UseTree",1,["syn::item::UseTree"]],["impl Unpin for UsePath",1,["syn::item::UsePath"]],["impl Unpin for UseName",1,["syn::item::UseName"]],["impl Unpin for UseRename",1,["syn::item::UseRename"]],["impl Unpin for UseGlob",1,["syn::item::UseGlob"]],["impl Unpin for UseGroup",1,["syn::item::UseGroup"]],["impl Unpin for ForeignItem",1,["syn::item::ForeignItem"]],["impl Unpin for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl Unpin for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl Unpin for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl Unpin for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl Unpin for TraitItem",1,["syn::item::TraitItem"]],["impl Unpin for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl Unpin for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl Unpin for TraitItemType",1,["syn::item::TraitItemType"]],["impl Unpin for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl Unpin for ImplItem",1,["syn::item::ImplItem"]],["impl Unpin for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl Unpin for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl Unpin for ImplItemType",1,["syn::item::ImplItemType"]],["impl Unpin for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl Unpin for Signature",1,["syn::item::Signature"]],["impl Unpin for FnArg",1,["syn::item::FnArg"]],["impl Unpin for Receiver",1,["syn::item::Receiver"]],["impl Unpin for File",1,["syn::file::File"]],["impl Unpin for Lifetime",1,["syn::lifetime::Lifetime"]],["impl Unpin for Lit",1,["syn::lit::Lit"]],["impl Unpin for LitStr",1,["syn::lit::LitStr"]],["impl Unpin for LitByteStr",1,["syn::lit::LitByteStr"]],["impl Unpin for LitByte",1,["syn::lit::LitByte"]],["impl Unpin for LitChar",1,["syn::lit::LitChar"]],["impl Unpin for LitInt",1,["syn::lit::LitInt"]],["impl Unpin for LitFloat",1,["syn::lit::LitFloat"]],["impl Unpin for LitBool",1,["syn::lit::LitBool"]],["impl Unpin for StrStyle",1,["syn::lit::StrStyle"]],["impl Unpin for Macro",1,["syn::mac::Macro"]],["impl Unpin for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl Unpin for DeriveInput",1,["syn::derive::DeriveInput"]],["impl Unpin for Data",1,["syn::derive::Data"]],["impl Unpin for DataStruct",1,["syn::derive::DataStruct"]],["impl Unpin for DataEnum",1,["syn::derive::DataEnum"]],["impl Unpin for DataUnion",1,["syn::derive::DataUnion"]],["impl Unpin for BinOp",1,["syn::op::BinOp"]],["impl Unpin for UnOp",1,["syn::op::UnOp"]],["impl Unpin for Block",1,["syn::stmt::Block"]],["impl Unpin for Stmt",1,["syn::stmt::Stmt"]],["impl Unpin for Local",1,["syn::stmt::Local"]],["impl Unpin for Type",1,["syn::ty::Type"]],["impl Unpin for TypeArray",1,["syn::ty::TypeArray"]],["impl Unpin for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl Unpin for TypeGroup",1,["syn::ty::TypeGroup"]],["impl Unpin for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl Unpin for TypeInfer",1,["syn::ty::TypeInfer"]],["impl Unpin for TypeMacro",1,["syn::ty::TypeMacro"]],["impl Unpin for TypeNever",1,["syn::ty::TypeNever"]],["impl Unpin for TypeParen",1,["syn::ty::TypeParen"]],["impl Unpin for TypePath",1,["syn::ty::TypePath"]],["impl Unpin for TypePtr",1,["syn::ty::TypePtr"]],["impl Unpin for TypeReference",1,["syn::ty::TypeReference"]],["impl Unpin for TypeSlice",1,["syn::ty::TypeSlice"]],["impl Unpin for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl Unpin for TypeTuple",1,["syn::ty::TypeTuple"]],["impl Unpin for Abi",1,["syn::ty::Abi"]],["impl Unpin for BareFnArg",1,["syn::ty::BareFnArg"]],["impl Unpin for Variadic",1,["syn::ty::Variadic"]],["impl Unpin for ReturnType",1,["syn::ty::ReturnType"]],["impl Unpin for Pat",1,["syn::pat::Pat"]],["impl Unpin for PatBox",1,["syn::pat::PatBox"]],["impl Unpin for PatIdent",1,["syn::pat::PatIdent"]],["impl Unpin for PatLit",1,["syn::pat::PatLit"]],["impl Unpin for PatMacro",1,["syn::pat::PatMacro"]],["impl Unpin for PatOr",1,["syn::pat::PatOr"]],["impl Unpin for PatPath",1,["syn::pat::PatPath"]],["impl Unpin for PatRange",1,["syn::pat::PatRange"]],["impl Unpin for PatReference",1,["syn::pat::PatReference"]],["impl Unpin for PatRest",1,["syn::pat::PatRest"]],["impl Unpin for PatSlice",1,["syn::pat::PatSlice"]],["impl Unpin for PatStruct",1,["syn::pat::PatStruct"]],["impl Unpin for PatTuple",1,["syn::pat::PatTuple"]],["impl Unpin for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl Unpin for PatType",1,["syn::pat::PatType"]],["impl Unpin for PatWild",1,["syn::pat::PatWild"]],["impl Unpin for FieldPat",1,["syn::pat::FieldPat"]],["impl Unpin for Path",1,["syn::path::Path"]],["impl Unpin for PathSegment",1,["syn::path::PathSegment"]],["impl Unpin for PathArguments",1,["syn::path::PathArguments"]],["impl Unpin for GenericArgument",1,["syn::path::GenericArgument"]],["impl Unpin for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl Unpin for Binding",1,["syn::path::Binding"]],["impl Unpin for Constraint",1,["syn::path::Constraint"]],["impl Unpin for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl Unpin for QSelf",1,["syn::path::QSelf"]],["impl Unpin for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> Unpin for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Unpin for Punctuated<T, P>where
    P: Unpin,
    T: Unpin,
",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Unpin for Pairs<'a, T, P>",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Unpin for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> Unpin for IntoPairs<T, P>where
    P: Unpin,
    T: Unpin,
",1,["syn::punctuated::IntoPairs"]],["impl<T> Unpin for IntoIter<T>where
    T: Unpin,
",1,["syn::punctuated::IntoIter"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> Unpin for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Unpin for Pair<T, P>where
    P: Unpin,
    T: Unpin,
",1,["syn::punctuated::Pair"]],["impl<'a> Unpin for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Unpin for Error",1,["syn::error::Error"]],["impl<'a> Unpin for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> Unpin for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Unpin for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> Unpin for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Unpin for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Unpin for AbortHandle",1,["tokio::runtime::task::abort::AbortHandle"]],["impl Unpin for Builder",1,["tokio::runtime::builder::Builder"]],["impl Unpin for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Unpin for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Unpin for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Unpin for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Unpin for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !Unpin for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl Unpin for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Unpin for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Unpin for SendError<T>where
    T: Unpin,
",1,["tokio::sync::broadcast::error::SendError"]],["impl Unpin for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Unpin for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Unpin for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Unpin for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Unpin for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Unpin for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Unpin for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Unpin for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Unpin for SendError<T>where
    T: Unpin,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Unpin for TrySendError<T>where
    T: Unpin,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Unpin for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T: ?Sized> Unpin for Mutex<T>where
    T: Unpin,
",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T: ?Sized> Unpin for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Unpin for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Unpin for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl Unpin for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Unpin for Notify",1,["tokio::sync::notify::Notify"]],["impl Unpin for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Unpin for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl Unpin for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Unpin for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Unpin for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Unpin for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Unpin for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T: ?Sized, U: ?Sized> Unpin for OwnedRwLockReadGuard<T, U>where
    T: Unpin,
",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T: ?Sized> Unpin for OwnedRwLockWriteGuard<T>where
    T: Unpin,
",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T: ?Sized, U: ?Sized> Unpin for OwnedRwLockMappedWriteGuard<T, U>where
    T: Unpin,
",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T: ?Sized> Unpin for RwLock<T>where
    T: Unpin,
",1,["tokio::sync::rwlock::RwLock"]],["impl<T> Unpin for OnceCell<T>where
    T: Unpin,
",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> Unpin for SetError<T>where
    T: Unpin,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> Unpin for SendError<T>where
    T: Unpin,
",1,["tokio::sync::watch::error::SendError"]],["impl Unpin for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Unpin for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl Unpin for LocalSet",1,["tokio::task::local::LocalSet"]],["impl Unpin for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Unpin for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> !Unpin for TaskLocalFuture<T, F>",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<T> Unpin for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl<T> Unpin for JoinHandle<T>"],["impl<T> Unpin for Receiver<T>"],["impl<'__pin, F> Unpin for Unconstrained<F>where
    __Origin<'__pin, F>: Unpin,
"]], +"typenum":[["impl Unpin for B0",1,["typenum::bit::B0"]],["impl Unpin for B1",1,["typenum::bit::B1"]],["impl<U> Unpin for PInt<U>where
    U: Unpin,
",1,["typenum::int::PInt"]],["impl<U> Unpin for NInt<U>where
    U: Unpin,
",1,["typenum::int::NInt"]],["impl Unpin for Z0",1,["typenum::int::Z0"]],["impl Unpin for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Unpin for UInt<U, B>where
    B: Unpin,
    U: Unpin,
",1,["typenum::uint::UInt"]],["impl Unpin for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Unpin for TArr<V, A>where
    A: Unpin,
    V: Unpin,
",1,["typenum::array::TArr"]],["impl Unpin for Greater",1,["typenum::Greater"]],["impl Unpin for Less",1,["typenum::Less"]],["impl Unpin for Equal",1,["typenum::Equal"]]], +"uint":[["impl Unpin for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Unpin for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Unpin for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Unpin for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/ops/deref/trait.Deref.js b/docs/implementors/core/ops/deref/trait.Deref.js new file mode 100644 index 000000000000..511eb982b7ca --- /dev/null +++ b/docs/implementors/core/ops/deref/trait.Deref.js @@ -0,0 +1,17 @@ +(function() {var implementors = { +"bytes":[["impl Deref for Bytes"],["impl Deref for BytesMut"]], +"crossbeam_utils":[["impl<T> Deref for CachePadded<T>"],["impl<T: ?Sized> Deref for ShardedLockReadGuard<'_, T>"],["impl<T: ?Sized> Deref for ShardedLockWriteGuard<'_, T>"]], +"firewood":[["impl<'a> Deref for Revision<'a>"],["impl Deref for Hash"],["impl Deref for PartialPath"],["impl<'a> Deref for Ref<'a>"]], +"futures_executor":[["impl<S: Stream + Unpin> Deref for BlockingStream<S>"]], +"futures_task":[["impl Deref for WakerRef<'_>"]], +"futures_util":[["impl<T: ?Sized> Deref for OwnedMutexGuard<T>"],["impl<T: ?Sized> Deref for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized> Deref for MappedMutexGuard<'_, T, U>"]], +"generic_array":[["impl<T, N> Deref for GenericArray<T, N>where
    N: ArrayLength<T>,
"]], +"lock_api":[["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Deref for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Deref for MappedMutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Deref for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Deref for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + 'a> Deref for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for MappedRwLockWriteGuard<'a, R, T>"]], +"once_cell":[["impl<T, F: FnOnce() -> T> Deref for Lazy<T, F>"],["impl<T, F: FnOnce() -> T> Deref for Lazy<T, F>"]], +"regex_syntax":[["impl Deref for Literal"]], +"scopeguard":[["impl<T, F, S> Deref for ScopeGuard<T, F, S>where
    F: FnOnce(T),
    S: Strategy,
"]], +"shale":[["impl<T> Deref for Obj<T>"],["impl<'a, T> Deref for ObjRef<'a, T>"],["impl<T> Deref for MummyObj<T>"]], +"smallvec":[["impl<A: Array> Deref for SmallVec<A>"]], +"syn":[["impl Deref for Underscore"],["impl Deref for Add"],["impl Deref for And"],["impl Deref for At"],["impl Deref for Bang"],["impl Deref for Caret"],["impl Deref for Colon"],["impl Deref for Comma"],["impl Deref for Div"],["impl Deref for Dollar"],["impl Deref for Dot"],["impl Deref for Eq"],["impl Deref for Gt"],["impl Deref for Lt"],["impl Deref for Or"],["impl Deref for Pound"],["impl Deref for Question"],["impl Deref for Rem"],["impl Deref for Semi"],["impl Deref for Star"],["impl Deref for Sub"],["impl Deref for Tilde"],["impl<'c, 'a> Deref for StepCursor<'c, 'a>"]], +"tokio":[["impl<T: ?Sized> Deref for MutexGuard<'_, T>"],["impl<T: ?Sized> Deref for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized> Deref for MappedMutexGuard<'a, T>"],["impl<T: ?Sized, U: ?Sized> Deref for OwnedRwLockReadGuard<T, U>"],["impl<T: ?Sized> Deref for OwnedRwLockWriteGuard<T>"],["impl<T: ?Sized, U: ?Sized> Deref for OwnedRwLockMappedWriteGuard<T, U>"],["impl<T: ?Sized> Deref for RwLockReadGuard<'_, T>"],["impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T>"],["impl<T: ?Sized> Deref for RwLockMappedWriteGuard<'_, T>"],["impl<T> Deref for Ref<'_, T>"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/ops/drop/trait.Drop.js b/docs/implementors/core/ops/drop/trait.Drop.js new file mode 100644 index 000000000000..8f3097fe6835 --- /dev/null +++ b/docs/implementors/core/ops/drop/trait.Drop.js @@ -0,0 +1,24 @@ +(function() {var implementors = { +"aiofut":[["impl Drop for AIO"],["impl Drop for AIOFuture"],["impl Drop for AIOManager"]], +"bytes":[["impl Drop for Bytes"],["impl Drop for BytesMut"]], +"crossbeam_channel":[["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl Drop for SelectedOperation<'_>"]], +"crossbeam_utils":[["impl<T> Drop for AtomicCell<T>"],["impl<T: ?Sized> Drop for ShardedLockWriteGuard<'_, T>"],["impl Drop for WaitGroup"]], +"firewood":[["impl<'a> Drop for WriteBatch<'a>"]], +"futures_channel":[["impl<T> Drop for Receiver<T>"],["impl<T> Drop for UnboundedReceiver<T>"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"]], +"futures_executor":[["impl Drop for Enter"]], +"futures_task":[["impl<T> Drop for LocalFutureObj<'_, T>"]], +"futures_util":[["impl<Fut> Drop for Shared<Fut>where
    Fut: Future,
"],["impl<Fut> Drop for FuturesUnordered<Fut>"],["impl<T: ?Sized> Drop for OwnedMutexLockFuture<T>"],["impl<T: ?Sized> Drop for OwnedMutexGuard<T>"],["impl<T: ?Sized> Drop for MutexLockFuture<'_, T>"],["impl<T: ?Sized> Drop for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized> Drop for MappedMutexGuard<'_, T, U>"]], +"generic_array":[["impl<T, N> Drop for GenericArrayIter<T, N>where
    N: ArrayLength<T>,
"]], +"growthring":[["impl Drop for WALFileAIO"],["impl Drop for WALStoreAIO"]], +"hashbrown":[["impl<'a, K, V, F, A> Drop for DrainFilter<'a, K, V, F, A>where
    F: FnMut(&K, &mut V) -> bool,
    A: Allocator + Clone,
"],["impl<'a, K, F, A: Allocator + Clone> Drop for DrainFilter<'a, K, F, A>where
    F: FnMut(&K) -> bool,
"]], +"lock_api":[["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Drop for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Drop for MappedMutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Drop for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Drop for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + 'a> Drop for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for MappedRwLockWriteGuard<'a, R, T>"]], +"lru":[["impl<K, V, S> Drop for LruCache<K, V, S>"]], +"nix":[["impl Drop for Dir"],["impl<'d> Drop for Iter<'d>"],["impl Drop for InterfaceAddressIterator"],["impl Drop for Interfaces"],["impl Drop for PtyMaster"],["impl Drop for SignalFd"],["impl Drop for TimerFd"],["impl Drop for Timer"]], +"once_cell":[["impl<T> Drop for OnceBox<T>"]], +"regex_syntax":[["impl Drop for Ast"],["impl Drop for ClassSet"],["impl Drop for Hir"]], +"scopeguard":[["impl<T, F, S> Drop for ScopeGuard<T, F, S>where
    F: FnOnce(T),
    S: Strategy,
"]], +"shale":[["impl<T: ?Sized> Drop for Obj<T>"],["impl<'a, T> Drop for ObjRef<'a, T>"]], +"smallvec":[["impl<'a, T: 'a + Array> Drop for Drain<'a, T>"],["impl<A: Array> Drop for SmallVec<A>"],["impl<A: Array> Drop for IntoIter<A>"]], +"syn":[["impl<'a> Drop for ParseBuffer<'a>"]], +"tokio":[["impl Drop for AbortHandle"],["impl<T> Drop for JoinHandle<T>"],["impl Drop for Runtime"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl<T> Drop for Permit<'_, T>"],["impl<T> Drop for OwnedPermit<T>"],["impl<T: ?Sized> Drop for MutexGuard<'_, T>"],["impl<T: ?Sized> Drop for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized> Drop for MappedMutexGuard<'a, T>"],["impl Drop for Notified<'_>"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl Drop for SemaphorePermit<'_>"],["impl Drop for OwnedSemaphorePermit"],["impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockReadGuard<T, U>"],["impl<T: ?Sized> Drop for OwnedRwLockWriteGuard<T>"],["impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockMappedWriteGuard<T, U>"],["impl<'a, T: ?Sized> Drop for RwLockReadGuard<'a, T>"],["impl<'a, T: ?Sized> Drop for RwLockWriteGuard<'a, T>"],["impl<'a, T: ?Sized> Drop for RwLockMappedWriteGuard<'a, T>"],["impl<T> Drop for OnceCell<T>"],["impl<T> Drop for Receiver<T>"],["impl<T> Drop for Sender<T>"],["impl Drop for LocalEnterGuard"],["impl Drop for LocalSet"],["impl<T: 'static, F> Drop for TaskLocalFuture<T, F>"],["impl<T> Drop for JoinSet<T>"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js b/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js new file mode 100644 index 000000000000..c9d2d9a6b51b --- /dev/null +++ b/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl RefUnwindSafe for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl RefUnwindSafe for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> RefUnwindSafe for AhoCorasick<S>where
    S: RefUnwindSafe,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> RefUnwindSafe for FindIter<'a, 'b, S>where
    S: RefUnwindSafe,
",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> RefUnwindSafe for FindOverlappingIter<'a, 'b, S>where
    S: RefUnwindSafe,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> RefUnwindSafe for StreamFindIter<'a, R, S>where
    R: RefUnwindSafe,
    S: RefUnwindSafe,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl RefUnwindSafe for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl RefUnwindSafe for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl RefUnwindSafe for Error",1,["aho_corasick::error::Error"]],["impl RefUnwindSafe for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl RefUnwindSafe for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl RefUnwindSafe for Config",1,["aho_corasick::packed::api::Config"]],["impl RefUnwindSafe for Builder",1,["aho_corasick::packed::api::Builder"]],["impl RefUnwindSafe for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> RefUnwindSafe for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl RefUnwindSafe for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl RefUnwindSafe for Error",1,["aiofut::Error"]],["impl RefUnwindSafe for AIO",1,["aiofut::AIO"]],["impl !RefUnwindSafe for AIOFuture",1,["aiofut::AIOFuture"]],["impl !RefUnwindSafe for AIONotifier",1,["aiofut::AIONotifier"]],["impl RefUnwindSafe for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !RefUnwindSafe for AIOManager",1,["aiofut::AIOManager"]],["impl !RefUnwindSafe for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl RefUnwindSafe for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl RefUnwindSafe for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl RefUnwindSafe for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl RefUnwindSafe for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl RefUnwindSafe for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl RefUnwindSafe for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl RefUnwindSafe for Config",1,["bincode::config::legacy::Config"]],["impl RefUnwindSafe for Bounded",1,["bincode::config::limit::Bounded"]],["impl RefUnwindSafe for Infinite",1,["bincode::config::limit::Infinite"]],["impl RefUnwindSafe for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl RefUnwindSafe for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl RefUnwindSafe for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> RefUnwindSafe for WithOtherLimit<O, L>where
    L: RefUnwindSafe,
    O: RefUnwindSafe,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> RefUnwindSafe for WithOtherEndian<O, E>where
    E: RefUnwindSafe,
    O: RefUnwindSafe,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> RefUnwindSafe for WithOtherIntEncoding<O, I>where
    I: RefUnwindSafe,
    O: RefUnwindSafe,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> RefUnwindSafe for WithOtherTrailing<O, T>where
    O: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> RefUnwindSafe for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> RefUnwindSafe for IoReader<R>where
    R: RefUnwindSafe,
",1,["bincode::de::read::IoReader"]],["impl<R, O> RefUnwindSafe for Deserializer<R, O>where
    O: RefUnwindSafe,
    R: RefUnwindSafe,
",1,["bincode::de::Deserializer"]],["impl !RefUnwindSafe for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> RefUnwindSafe for Serializer<W, O>where
    O: RefUnwindSafe,
    W: RefUnwindSafe,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl RefUnwindSafe for Eager",1,["block_buffer::Eager"]],["impl RefUnwindSafe for Lazy",1,["block_buffer::Lazy"]],["impl RefUnwindSafe for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> RefUnwindSafe for BlockBuffer<BlockSize, Kind>where
    Kind: RefUnwindSafe,
    <BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl RefUnwindSafe for BigEndian",1,["byteorder::BigEndian"]],["impl RefUnwindSafe for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> RefUnwindSafe for Chain<T, U>where
    T: RefUnwindSafe,
    U: RefUnwindSafe,
",1,["bytes::buf::chain::Chain"]],["impl<T> RefUnwindSafe for IntoIter<T>where
    T: RefUnwindSafe,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> RefUnwindSafe for Limit<T>where
    T: RefUnwindSafe,
",1,["bytes::buf::limit::Limit"]],["impl<B> RefUnwindSafe for Reader<B>where
    B: RefUnwindSafe,
",1,["bytes::buf::reader::Reader"]],["impl<T> RefUnwindSafe for Take<T>where
    T: RefUnwindSafe,
",1,["bytes::buf::take::Take"]],["impl RefUnwindSafe for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> RefUnwindSafe for Writer<B>where
    B: RefUnwindSafe,
",1,["bytes::buf::writer::Writer"]],["impl RefUnwindSafe for Bytes",1,["bytes::bytes::Bytes"]],["impl RefUnwindSafe for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], +"crc":[["impl<W> RefUnwindSafe for Crc<W>where
    W: RefUnwindSafe,
",1,["crc::Crc"]],["impl<'a, W> RefUnwindSafe for Digest<'a, W>where
    W: RefUnwindSafe,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> RefUnwindSafe for Algorithm<W>where
    W: RefUnwindSafe,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<'a, T> RefUnwindSafe for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> RefUnwindSafe for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> RefUnwindSafe for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> RefUnwindSafe for SendError<T>where
    T: RefUnwindSafe,
",1,["crossbeam_channel::err::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
    T: RefUnwindSafe,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> RefUnwindSafe for SendTimeoutError<T>where
    T: RefUnwindSafe,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl RefUnwindSafe for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl RefUnwindSafe for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl RefUnwindSafe for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl RefUnwindSafe for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl RefUnwindSafe for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl RefUnwindSafe for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !RefUnwindSafe for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> RefUnwindSafe for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T> RefUnwindSafe for Sender<T>"],["impl<T> RefUnwindSafe for Receiver<T>"]], +"crossbeam_utils":[["impl<T> RefUnwindSafe for CachePadded<T>where
    T: RefUnwindSafe,
",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl !RefUnwindSafe for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl RefUnwindSafe for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl RefUnwindSafe for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<'a, T: ?Sized> RefUnwindSafe for ShardedLockReadGuard<'a, T>where
    T: RefUnwindSafe,
",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> RefUnwindSafe for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl RefUnwindSafe for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> RefUnwindSafe for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> RefUnwindSafe for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> RefUnwindSafe for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]],["impl<T> RefUnwindSafe for AtomicCell<T>"],["impl<T: ?Sized> RefUnwindSafe for ShardedLock<T>"]], +"crypto_common":[["impl RefUnwindSafe for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> RefUnwindSafe for CtVariableCoreWrapper<T, OutSize, O>where
    O: RefUnwindSafe,
    OutSize: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> RefUnwindSafe for RtVariableCoreWrapper<T>where
    T: RefUnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
    <T as BufferKindUser>::BufferKind: RefUnwindSafe,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> RefUnwindSafe for CoreWrapper<T>where
    T: RefUnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
    <T as BufferKindUser>::BufferKind: RefUnwindSafe,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> RefUnwindSafe for XofReaderCoreWrapper<T>where
    T: RefUnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl RefUnwindSafe for TruncSide",1,["digest::core_api::TruncSide"]],["impl RefUnwindSafe for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl RefUnwindSafe for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl RefUnwindSafe for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl RefUnwindSafe for WALConfig",1,["firewood::storage::WALConfig"]],["impl !RefUnwindSafe for DBError",1,["firewood::db::DBError"]],["impl RefUnwindSafe for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl RefUnwindSafe for DBConfig",1,["firewood::db::DBConfig"]],["impl !RefUnwindSafe for DBRev",1,["firewood::db::DBRev"]],["impl !RefUnwindSafe for DB",1,["firewood::db::DB"]],["impl<'a> !RefUnwindSafe for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !RefUnwindSafe for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl !RefUnwindSafe for MerkleError",1,["firewood::merkle::MerkleError"]],["impl RefUnwindSafe for Hash",1,["firewood::merkle::Hash"]],["impl RefUnwindSafe for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !RefUnwindSafe for Node",1,["firewood::merkle::Node"]],["impl !RefUnwindSafe for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !RefUnwindSafe for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !RefUnwindSafe for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl RefUnwindSafe for IdTrans",1,["firewood::merkle::IdTrans"]],["impl RefUnwindSafe for Proof",1,["firewood::proof::Proof"]],["impl RefUnwindSafe for ProofError",1,["firewood::proof::ProofError"]],["impl RefUnwindSafe for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> !RefUnwindSafe for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> !RefUnwindSafe for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> !RefUnwindSafe for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl RefUnwindSafe for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
    T: RefUnwindSafe,
",1,["futures_channel::mpsc::TrySendError"]],["impl RefUnwindSafe for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> !RefUnwindSafe for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl RefUnwindSafe for Canceled",1,["futures_channel::oneshot::Canceled"]]], +"futures_executor":[["impl !RefUnwindSafe for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !RefUnwindSafe for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> RefUnwindSafe for BlockingStream<S>where
    S: RefUnwindSafe,
",1,["futures_executor::local_pool::BlockingStream"]],["impl RefUnwindSafe for Enter",1,["futures_executor::enter::Enter"]],["impl RefUnwindSafe for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl RefUnwindSafe for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> RefUnwindSafe for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !RefUnwindSafe for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !RefUnwindSafe for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], +"futures_util":[["impl<Fut> RefUnwindSafe for Fuse<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> RefUnwindSafe for CatchUnwind<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> !RefUnwindSafe for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> !RefUnwindSafe for Remote<Fut>",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> !RefUnwindSafe for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> !RefUnwindSafe for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> RefUnwindSafe for Flatten<F>where
    F: RefUnwindSafe,
    <F as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::future::Flatten"]],["impl<F> RefUnwindSafe for FlattenStream<F>where
    F: RefUnwindSafe,
    <F as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> RefUnwindSafe for Map<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::Map"]],["impl<F> RefUnwindSafe for IntoStream<F>where
    F: RefUnwindSafe,
",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> RefUnwindSafe for MapInto<Fut, T>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> RefUnwindSafe for Then<Fut1, Fut2, F>where
    F: RefUnwindSafe,
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
",1,["futures_util::future::future::Then"]],["impl<Fut, F> RefUnwindSafe for Inspect<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::Inspect"]],["impl<Fut> RefUnwindSafe for NeverError<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::NeverError"]],["impl<Fut> RefUnwindSafe for UnitError<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::future::UnitError"]],["impl<Fut> RefUnwindSafe for IntoFuture<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> RefUnwindSafe for TryFlatten<Fut1, Fut2>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> RefUnwindSafe for TryFlattenStream<Fut>where
    Fut: RefUnwindSafe,
    <Fut as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> RefUnwindSafe for FlattenSink<Fut, Si>where
    Fut: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> RefUnwindSafe for AndThen<Fut1, Fut2, F>where
    F: RefUnwindSafe,
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> RefUnwindSafe for OrElse<Fut1, Fut2, F>where
    F: RefUnwindSafe,
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> RefUnwindSafe for ErrInto<Fut, E>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> RefUnwindSafe for OkInto<Fut, E>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> RefUnwindSafe for InspectOk<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> RefUnwindSafe for InspectErr<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> RefUnwindSafe for MapOk<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> RefUnwindSafe for MapErr<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> RefUnwindSafe for MapOkOrElse<Fut, F, G>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    G: RefUnwindSafe,
",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> RefUnwindSafe for UnwrapOrElse<Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> RefUnwindSafe for Lazy<F>where
    F: RefUnwindSafe,
",1,["futures_util::future::lazy::Lazy"]],["impl<T> RefUnwindSafe for Pending<T>where
    T: RefUnwindSafe,
",1,["futures_util::future::pending::Pending"]],["impl<Fut> RefUnwindSafe for MaybeDone<Fut>where
    Fut: RefUnwindSafe,
    <Fut as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> RefUnwindSafe for TryMaybeDone<Fut>where
    Fut: RefUnwindSafe,
    <Fut as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> RefUnwindSafe for OptionFuture<F>where
    F: RefUnwindSafe,
",1,["futures_util::future::option::OptionFuture"]],["impl<F> RefUnwindSafe for PollFn<F>where
    F: RefUnwindSafe,
",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> RefUnwindSafe for PollImmediate<T>where
    T: RefUnwindSafe,
",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> RefUnwindSafe for Ready<T>where
    T: RefUnwindSafe,
",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> RefUnwindSafe for Join<Fut1, Fut2>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    <Fut1 as Future>::Output: RefUnwindSafe,
    <Fut2 as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> RefUnwindSafe for Join3<Fut1, Fut2, Fut3>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    <Fut1 as Future>::Output: RefUnwindSafe,
    <Fut2 as Future>::Output: RefUnwindSafe,
    <Fut3 as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> RefUnwindSafe for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    Fut4: RefUnwindSafe,
    <Fut1 as Future>::Output: RefUnwindSafe,
    <Fut2 as Future>::Output: RefUnwindSafe,
    <Fut3 as Future>::Output: RefUnwindSafe,
    <Fut4 as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> RefUnwindSafe for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    Fut4: RefUnwindSafe,
    Fut5: RefUnwindSafe,
    <Fut1 as Future>::Output: RefUnwindSafe,
    <Fut2 as Future>::Output: RefUnwindSafe,
    <Fut3 as Future>::Output: RefUnwindSafe,
    <Fut4 as Future>::Output: RefUnwindSafe,
    <Fut5 as Future>::Output: RefUnwindSafe,
",1,["futures_util::future::join::Join5"]],["impl<F> !RefUnwindSafe for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> RefUnwindSafe for Select<A, B>where
    A: RefUnwindSafe,
    B: RefUnwindSafe,
",1,["futures_util::future::select::Select"]],["impl<Fut> RefUnwindSafe for SelectAll<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> RefUnwindSafe for TryJoin<Fut1, Fut2>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    <Fut1 as TryFuture>::Ok: RefUnwindSafe,
    <Fut2 as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> RefUnwindSafe for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    <Fut1 as TryFuture>::Ok: RefUnwindSafe,
    <Fut2 as TryFuture>::Ok: RefUnwindSafe,
    <Fut3 as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> RefUnwindSafe for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    Fut4: RefUnwindSafe,
    <Fut1 as TryFuture>::Ok: RefUnwindSafe,
    <Fut2 as TryFuture>::Ok: RefUnwindSafe,
    <Fut3 as TryFuture>::Ok: RefUnwindSafe,
    <Fut4 as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> RefUnwindSafe for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: RefUnwindSafe,
    Fut2: RefUnwindSafe,
    Fut3: RefUnwindSafe,
    Fut4: RefUnwindSafe,
    Fut5: RefUnwindSafe,
    <Fut1 as TryFuture>::Ok: RefUnwindSafe,
    <Fut2 as TryFuture>::Ok: RefUnwindSafe,
    <Fut3 as TryFuture>::Ok: RefUnwindSafe,
    <Fut4 as TryFuture>::Ok: RefUnwindSafe,
    <Fut5 as TryFuture>::Ok: RefUnwindSafe,
",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !RefUnwindSafe for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> RefUnwindSafe for TrySelect<A, B>where
    A: RefUnwindSafe,
    B: RefUnwindSafe,
",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> RefUnwindSafe for SelectOk<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> RefUnwindSafe for Either<A, B>where
    A: RefUnwindSafe,
    B: RefUnwindSafe,
",1,["futures_util::future::either::Either"]],["impl !RefUnwindSafe for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl !RefUnwindSafe for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> !RefUnwindSafe for Abortable<T>",1,["futures_util::abortable::Abortable"]],["impl RefUnwindSafe for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> RefUnwindSafe for Chain<St1, St2>where
    St1: RefUnwindSafe,
    St2: RefUnwindSafe,
",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> RefUnwindSafe for Collect<St, C>where
    C: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> RefUnwindSafe for Unzip<St, FromA, FromB>where
    FromA: RefUnwindSafe,
    FromB: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> RefUnwindSafe for Concat<St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> RefUnwindSafe for Cycle<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> RefUnwindSafe for Enumerate<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> RefUnwindSafe for Filter<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> RefUnwindSafe for FilterMap<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> RefUnwindSafe for Fold<St, Fut, T, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> RefUnwindSafe for ForEach<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> RefUnwindSafe for Fuse<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> RefUnwindSafe for StreamFuture<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> RefUnwindSafe for Map<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> RefUnwindSafe for Next<'a, St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> RefUnwindSafe for SelectNextSome<'a, St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> RefUnwindSafe for Peekable<St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> RefUnwindSafe for Peek<'a, St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> RefUnwindSafe for PeekMut<'a, St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> RefUnwindSafe for NextIf<'a, St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> RefUnwindSafe for NextIfEq<'a, St, T>where
    St: RefUnwindSafe,
    T: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> RefUnwindSafe for Skip<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> RefUnwindSafe for SkipWhile<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> RefUnwindSafe for Take<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> RefUnwindSafe for TakeWhile<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> RefUnwindSafe for TakeUntil<St, Fut>where
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <Fut as Future>::Output: RefUnwindSafe,
",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> RefUnwindSafe for Then<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> RefUnwindSafe for Zip<St1, St2>where
    St1: RefUnwindSafe,
    St2: RefUnwindSafe,
    <St1 as Stream>::Item: RefUnwindSafe,
    <St2 as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> RefUnwindSafe for Chunks<St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> RefUnwindSafe for ReadyChunks<St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> RefUnwindSafe for Scan<St, S, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    S: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !RefUnwindSafe for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !RefUnwindSafe for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !RefUnwindSafe for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> !RefUnwindSafe for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> !RefUnwindSafe for SplitSink<S, Item>",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> !RefUnwindSafe for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> RefUnwindSafe for CatchUnwind<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> RefUnwindSafe for Flatten<St>where
    St: RefUnwindSafe,
    <St as Stream>::Item: RefUnwindSafe,
",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> RefUnwindSafe for Forward<St, Si>where
    Si: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::stream::Forward"]],["impl<St, F> RefUnwindSafe for Inspect<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> RefUnwindSafe for FlatMap<St, U, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
    U: RefUnwindSafe,
",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> RefUnwindSafe for AndThen<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> RefUnwindSafe for IntoStream<St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> RefUnwindSafe for OrElse<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> RefUnwindSafe for TryNext<'a, St>where
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> RefUnwindSafe for TryForEach<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> RefUnwindSafe for TryFilter<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> RefUnwindSafe for TryFilterMap<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> RefUnwindSafe for TryFlatten<St>where
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> RefUnwindSafe for TryCollect<St, C>where
    C: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> RefUnwindSafe for TryConcat<St>where
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> RefUnwindSafe for TryChunks<St>where
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> RefUnwindSafe for TryChunksError<T, E>where
    E: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> RefUnwindSafe for TryFold<St, Fut, T, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> RefUnwindSafe for TryUnfold<T, F, Fut>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> RefUnwindSafe for TrySkipWhile<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> RefUnwindSafe for TryTakeWhile<St, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !RefUnwindSafe for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !RefUnwindSafe for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !RefUnwindSafe for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> RefUnwindSafe for IntoAsyncRead<St>where
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> RefUnwindSafe for ErrInto<St, E>where
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> RefUnwindSafe for InspectOk<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> RefUnwindSafe for InspectErr<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> RefUnwindSafe for MapOk<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> RefUnwindSafe for MapErr<St, F>where
    F: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> RefUnwindSafe for Iter<I>where
    I: RefUnwindSafe,
",1,["futures_util::stream::iter::Iter"]],["impl<T> RefUnwindSafe for Repeat<T>where
    T: RefUnwindSafe,
",1,["futures_util::stream::repeat::Repeat"]],["impl<F> RefUnwindSafe for RepeatWith<F>where
    F: RefUnwindSafe,
",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> RefUnwindSafe for Empty<T>where
    T: RefUnwindSafe,
",1,["futures_util::stream::empty::Empty"]],["impl<Fut> RefUnwindSafe for Once<Fut>where
    Fut: RefUnwindSafe,
",1,["futures_util::stream::once::Once"]],["impl<T> RefUnwindSafe for Pending<T>where
    T: RefUnwindSafe,
",1,["futures_util::stream::pending::Pending"]],["impl<F> RefUnwindSafe for PollFn<F>where
    F: RefUnwindSafe,
",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> RefUnwindSafe for PollImmediate<S>where
    S: RefUnwindSafe,
",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> RefUnwindSafe for Select<St1, St2>where
    St1: RefUnwindSafe,
    St2: RefUnwindSafe,
",1,["futures_util::stream::select::Select"]],["impl RefUnwindSafe for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> RefUnwindSafe for SelectWithStrategy<St1, St2, Clos, State>where
    Clos: RefUnwindSafe,
    St1: RefUnwindSafe,
    St2: RefUnwindSafe,
    State: RefUnwindSafe,
",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> RefUnwindSafe for Unfold<T, F, Fut>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !RefUnwindSafe for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> !RefUnwindSafe for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> !RefUnwindSafe for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> !RefUnwindSafe for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> !RefUnwindSafe for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !RefUnwindSafe for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !RefUnwindSafe for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !RefUnwindSafe for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> !RefUnwindSafe for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> !RefUnwindSafe for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !RefUnwindSafe for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Close<'a, Si, Item>where
    Si: RefUnwindSafe,
",1,["futures_util::sink::close::Close"]],["impl<T> RefUnwindSafe for Drain<T>where
    T: RefUnwindSafe,
",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> RefUnwindSafe for Fanout<Si1, Si2>where
    Si1: RefUnwindSafe,
    Si2: RefUnwindSafe,
",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Feed<'a, Si, Item>where
    Item: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Flush<'a, Si, Item>where
    Si: RefUnwindSafe,
",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> RefUnwindSafe for SinkErrInto<Si, Item, E>where
    Si: RefUnwindSafe,
",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> RefUnwindSafe for SinkMapErr<Si, F>where
    F: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Send<'a, Si, Item>where
    Item: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> RefUnwindSafe for SendAll<'a, Si, St>where
    Si: RefUnwindSafe,
    St: RefUnwindSafe,
    <St as TryStream>::Ok: RefUnwindSafe,
",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> RefUnwindSafe for Unfold<T, F, R>where
    F: RefUnwindSafe,
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> RefUnwindSafe for With<Si, Item, U, Fut, F>where
    F: RefUnwindSafe,
    Fut: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> RefUnwindSafe for WithFlatMap<Si, Item, U, St, F>where
    F: RefUnwindSafe,
    Item: RefUnwindSafe,
    Si: RefUnwindSafe,
    St: RefUnwindSafe,
",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> RefUnwindSafe for Buffer<Si, Item>where
    Item: RefUnwindSafe,
    Si: RefUnwindSafe,
",1,["futures_util::sink::buffer::Buffer"]],["impl<T> RefUnwindSafe for AllowStdIo<T>where
    T: RefUnwindSafe,
",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> RefUnwindSafe for BufReader<R>where
    R: RefUnwindSafe,
",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> RefUnwindSafe for SeeKRelative<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> RefUnwindSafe for BufWriter<W>where
    W: RefUnwindSafe,
",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> RefUnwindSafe for LineWriter<W>where
    W: RefUnwindSafe,
",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> RefUnwindSafe for Chain<T, U>where
    T: RefUnwindSafe,
    U: RefUnwindSafe,
",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> RefUnwindSafe for Close<'a, W>where
    W: RefUnwindSafe,
",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> RefUnwindSafe for Copy<'a, R, W>where
    R: RefUnwindSafe,
    W: RefUnwindSafe,
",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> RefUnwindSafe for CopyBuf<'a, R, W>where
    R: RefUnwindSafe,
    W: RefUnwindSafe,
",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W> !RefUnwindSafe for CopyBufAbortable<'a, R, W>",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> RefUnwindSafe for Cursor<T>where
    T: RefUnwindSafe,
",1,["futures_util::io::cursor::Cursor"]],["impl RefUnwindSafe for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> RefUnwindSafe for FillBuf<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> RefUnwindSafe for Flush<'a, W>where
    W: RefUnwindSafe,
",1,["futures_util::io::flush::Flush"]],["impl<W, Item> RefUnwindSafe for IntoSink<W, Item>where
    Item: RefUnwindSafe,
    W: RefUnwindSafe,
",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> RefUnwindSafe for Lines<R>where
    R: RefUnwindSafe,
",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> RefUnwindSafe for Read<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadVectored<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadExact<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadLine<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadToEnd<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadToString<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadUntil<'a, R>where
    R: RefUnwindSafe,
",1,["futures_util::io::read_until::ReadUntil"]],["impl RefUnwindSafe for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> RefUnwindSafe for Seek<'a, S>where
    S: RefUnwindSafe,
",1,["futures_util::io::seek::Seek"]],["impl RefUnwindSafe for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> !RefUnwindSafe for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> !RefUnwindSafe for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> !RefUnwindSafe for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> RefUnwindSafe for Take<R>where
    R: RefUnwindSafe,
",1,["futures_util::io::take::Take"]],["impl<T> RefUnwindSafe for Window<T>where
    T: RefUnwindSafe,
",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> RefUnwindSafe for Write<'a, W>where
    W: RefUnwindSafe,
",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> RefUnwindSafe for WriteVectored<'a, W>where
    W: RefUnwindSafe,
",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> RefUnwindSafe for WriteAll<'a, W>where
    W: RefUnwindSafe,
",1,["futures_util::io::write_all::WriteAll"]],["impl<T> !RefUnwindSafe for Mutex<T>",1,["futures_util::lock::mutex::Mutex"]],["impl<T> !RefUnwindSafe for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T> !RefUnwindSafe for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T> !RefUnwindSafe for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T> !RefUnwindSafe for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T, U> !RefUnwindSafe for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], +"generic_array":[["impl<T, N> RefUnwindSafe for GenericArrayIter<T, N>where
    <N as ArrayLength<T>>::ArrayType: RefUnwindSafe,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> RefUnwindSafe for GenericArray<T, U>where
    <U as ArrayLength<T>>::ArrayType: RefUnwindSafe,
",1,["generic_array::GenericArray"]]], +"getrandom":[["impl RefUnwindSafe for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl RefUnwindSafe for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !RefUnwindSafe for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl RefUnwindSafe for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl RefUnwindSafe for WALLoader",1,["growthring::wal::WALLoader"]],["impl !RefUnwindSafe for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !RefUnwindSafe for WALStoreAIO",1,["growthring::WALStoreAIO"]]], +"hashbrown":[["impl<K, V, S, A> RefUnwindSafe for HashMap<K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> RefUnwindSafe for Iter<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Iter"]],["impl<'a, K, V> RefUnwindSafe for IterMut<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::IterMut"]],["impl<K, V, A> RefUnwindSafe for IntoIter<K, V, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> RefUnwindSafe for IntoKeys<K, V, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> RefUnwindSafe for IntoValues<K, V, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> RefUnwindSafe for Keys<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Keys"]],["impl<'a, K, V> RefUnwindSafe for Values<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> RefUnwindSafe for Drain<'a, K, V, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> RefUnwindSafe for DrainFilter<'a, K, V, F, A>where
    A: RefUnwindSafe,
    F: RefUnwindSafe,
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> RefUnwindSafe for ValuesMut<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryBuilderMut<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryMut<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawOccupiedEntryMut<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawVacantEntryMut<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryBuilder<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> RefUnwindSafe for Entry<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> RefUnwindSafe for OccupiedEntry<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> RefUnwindSafe for VacantEntry<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for EntryRef<'a, 'b, K, Q, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    Q: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    Q: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    Q: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> RefUnwindSafe for OccupiedError<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> RefUnwindSafe for HashSet<T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> RefUnwindSafe for Iter<'a, K>where
    K: RefUnwindSafe,
",1,["hashbrown::set::Iter"]],["impl<K, A> RefUnwindSafe for IntoIter<K, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> RefUnwindSafe for Drain<'a, K, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> RefUnwindSafe for DrainFilter<'a, K, F, A>where
    A: RefUnwindSafe,
    F: RefUnwindSafe,
    K: RefUnwindSafe,
",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> RefUnwindSafe for Intersection<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> RefUnwindSafe for Difference<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> RefUnwindSafe for SymmetricDifference<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> RefUnwindSafe for Union<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> RefUnwindSafe for Entry<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> RefUnwindSafe for OccupiedEntry<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> RefUnwindSafe for VacantEntry<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::VacantEntry"]],["impl RefUnwindSafe for TryReserveError",1,["hashbrown::TryReserveError"]]], +"heck":[["impl<T> RefUnwindSafe for AsKebabCase<T>where
    T: RefUnwindSafe,
",1,["heck::kebab::AsKebabCase"]],["impl<T> RefUnwindSafe for AsLowerCamelCase<T>where
    T: RefUnwindSafe,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> RefUnwindSafe for AsShoutyKebabCase<T>where
    T: RefUnwindSafe,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> RefUnwindSafe for AsShoutySnakeCase<T>where
    T: RefUnwindSafe,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> RefUnwindSafe for AsSnakeCase<T>where
    T: RefUnwindSafe,
",1,["heck::snake::AsSnakeCase"]],["impl<T> RefUnwindSafe for AsTitleCase<T>where
    T: RefUnwindSafe,
",1,["heck::title::AsTitleCase"]],["impl<T> RefUnwindSafe for AsUpperCamelCase<T>where
    T: RefUnwindSafe,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl RefUnwindSafe for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl RefUnwindSafe for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl RefUnwindSafe for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl RefUnwindSafe for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl RefUnwindSafe for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl RefUnwindSafe for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl RefUnwindSafe for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl RefUnwindSafe for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl RefUnwindSafe for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl RefUnwindSafe for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl RefUnwindSafe for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl RefUnwindSafe for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl RefUnwindSafe for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl RefUnwindSafe for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl RefUnwindSafe for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl RefUnwindSafe for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl RefUnwindSafe for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl RefUnwindSafe for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl RefUnwindSafe for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl RefUnwindSafe for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl RefUnwindSafe for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl RefUnwindSafe for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl RefUnwindSafe for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl RefUnwindSafe for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl RefUnwindSafe for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl RefUnwindSafe for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl RefUnwindSafe for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl RefUnwindSafe for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl RefUnwindSafe for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl RefUnwindSafe for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl RefUnwindSafe for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl RefUnwindSafe for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl RefUnwindSafe for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl RefUnwindSafe for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl RefUnwindSafe for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl RefUnwindSafe for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl RefUnwindSafe for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl RefUnwindSafe for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl RefUnwindSafe for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl RefUnwindSafe for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl RefUnwindSafe for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl RefUnwindSafe for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl RefUnwindSafe for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl RefUnwindSafe for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl RefUnwindSafe for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl RefUnwindSafe for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl RefUnwindSafe for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl RefUnwindSafe for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl RefUnwindSafe for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl RefUnwindSafe for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl RefUnwindSafe for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl RefUnwindSafe for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl RefUnwindSafe for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl RefUnwindSafe for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl RefUnwindSafe for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl RefUnwindSafe for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl RefUnwindSafe for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl RefUnwindSafe for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl RefUnwindSafe for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl RefUnwindSafe for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl RefUnwindSafe for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl RefUnwindSafe for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl RefUnwindSafe for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl RefUnwindSafe for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl RefUnwindSafe for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl RefUnwindSafe for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl RefUnwindSafe for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl RefUnwindSafe for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl RefUnwindSafe for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl RefUnwindSafe for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl RefUnwindSafe for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl RefUnwindSafe for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl RefUnwindSafe for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl RefUnwindSafe for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl RefUnwindSafe for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl RefUnwindSafe for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl RefUnwindSafe for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl RefUnwindSafe for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl RefUnwindSafe for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl RefUnwindSafe for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl RefUnwindSafe for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl RefUnwindSafe for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl RefUnwindSafe for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl RefUnwindSafe for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl RefUnwindSafe for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl RefUnwindSafe for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl RefUnwindSafe for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl RefUnwindSafe for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl RefUnwindSafe for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl RefUnwindSafe for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl RefUnwindSafe for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl RefUnwindSafe for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl RefUnwindSafe for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl RefUnwindSafe for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl RefUnwindSafe for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl RefUnwindSafe for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl RefUnwindSafe for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl RefUnwindSafe for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl RefUnwindSafe for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl RefUnwindSafe for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl RefUnwindSafe for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl RefUnwindSafe for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl RefUnwindSafe for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl RefUnwindSafe for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl RefUnwindSafe for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl RefUnwindSafe for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl RefUnwindSafe for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl RefUnwindSafe for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl RefUnwindSafe for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl RefUnwindSafe for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl RefUnwindSafe for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl RefUnwindSafe for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl RefUnwindSafe for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl RefUnwindSafe for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl RefUnwindSafe for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl RefUnwindSafe for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl RefUnwindSafe for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl RefUnwindSafe for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl RefUnwindSafe for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl RefUnwindSafe for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl RefUnwindSafe for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl RefUnwindSafe for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl RefUnwindSafe for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl RefUnwindSafe for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl RefUnwindSafe for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl RefUnwindSafe for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl RefUnwindSafe for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl RefUnwindSafe for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl RefUnwindSafe for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl RefUnwindSafe for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl RefUnwindSafe for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl RefUnwindSafe for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl RefUnwindSafe for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl RefUnwindSafe for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl RefUnwindSafe for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl RefUnwindSafe for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl RefUnwindSafe for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl RefUnwindSafe for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl RefUnwindSafe for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl RefUnwindSafe for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl RefUnwindSafe for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl RefUnwindSafe for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl RefUnwindSafe for timezone",1,["libc::unix::linux_like::timezone"]],["impl RefUnwindSafe for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl RefUnwindSafe for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl RefUnwindSafe for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl RefUnwindSafe for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl RefUnwindSafe for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl RefUnwindSafe for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl RefUnwindSafe for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl RefUnwindSafe for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl RefUnwindSafe for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl RefUnwindSafe for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl RefUnwindSafe for tm",1,["libc::unix::linux_like::tm"]],["impl RefUnwindSafe for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl RefUnwindSafe for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl RefUnwindSafe for lconv",1,["libc::unix::linux_like::lconv"]],["impl RefUnwindSafe for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl RefUnwindSafe for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl RefUnwindSafe for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl RefUnwindSafe for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl RefUnwindSafe for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl RefUnwindSafe for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl RefUnwindSafe for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl RefUnwindSafe for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl RefUnwindSafe for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl RefUnwindSafe for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl RefUnwindSafe for utsname",1,["libc::unix::linux_like::utsname"]],["impl RefUnwindSafe for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl RefUnwindSafe for in6_addr",1,["libc::unix::align::in6_addr"]],["impl RefUnwindSafe for DIR",1,["libc::unix::DIR"]],["impl RefUnwindSafe for group",1,["libc::unix::group"]],["impl RefUnwindSafe for utimbuf",1,["libc::unix::utimbuf"]],["impl RefUnwindSafe for timeval",1,["libc::unix::timeval"]],["impl RefUnwindSafe for timespec",1,["libc::unix::timespec"]],["impl RefUnwindSafe for rlimit",1,["libc::unix::rlimit"]],["impl RefUnwindSafe for rusage",1,["libc::unix::rusage"]],["impl RefUnwindSafe for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl RefUnwindSafe for hostent",1,["libc::unix::hostent"]],["impl RefUnwindSafe for iovec",1,["libc::unix::iovec"]],["impl RefUnwindSafe for pollfd",1,["libc::unix::pollfd"]],["impl RefUnwindSafe for winsize",1,["libc::unix::winsize"]],["impl RefUnwindSafe for linger",1,["libc::unix::linger"]],["impl RefUnwindSafe for sigval",1,["libc::unix::sigval"]],["impl RefUnwindSafe for itimerval",1,["libc::unix::itimerval"]],["impl RefUnwindSafe for tms",1,["libc::unix::tms"]],["impl RefUnwindSafe for servent",1,["libc::unix::servent"]],["impl RefUnwindSafe for protoent",1,["libc::unix::protoent"]],["impl RefUnwindSafe for FILE",1,["libc::unix::FILE"]],["impl RefUnwindSafe for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<R, T> !RefUnwindSafe for Mutex<R, T>",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T> !RefUnwindSafe for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedMutexGuard<'a, R, T>where
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> !RefUnwindSafe for RawReentrantMutex<R, G>",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T> !RefUnwindSafe for ReentrantMutex<R, G, T>",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T> !RefUnwindSafe for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !RefUnwindSafe for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T> !RefUnwindSafe for RwLock<R, T>",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T> !RefUnwindSafe for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T> !RefUnwindSafe for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T> !RefUnwindSafe for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedRwLockReadGuard<'a, R, T>where
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedRwLockWriteGuard<'a, R, T>where
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl RefUnwindSafe for GuardSend",1,["lock_api::GuardSend"]],["impl RefUnwindSafe for GuardNoSend",1,["lock_api::GuardNoSend"]]], +"lru":[["impl<K, V, S> RefUnwindSafe for LruCache<K, V, S>where
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::LruCache"]],["impl<'a, K, V> RefUnwindSafe for Iter<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::Iter"]],["impl<'a, K, V> RefUnwindSafe for IterMut<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::IterMut"]],["impl<K, V> RefUnwindSafe for IntoIter<K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::IntoIter"]]], +"memchr":[["impl<'a> RefUnwindSafe for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> RefUnwindSafe for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> RefUnwindSafe for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl RefUnwindSafe for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> RefUnwindSafe for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> RefUnwindSafe for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> RefUnwindSafe for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> RefUnwindSafe for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl RefUnwindSafe for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl RefUnwindSafe for Dir",1,["nix::dir::Dir"]],["impl<'d> RefUnwindSafe for Iter<'d>",1,["nix::dir::Iter"]],["impl RefUnwindSafe for OwningIter",1,["nix::dir::OwningIter"]],["impl RefUnwindSafe for Entry",1,["nix::dir::Entry"]],["impl RefUnwindSafe for Type",1,["nix::dir::Type"]],["impl RefUnwindSafe for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl RefUnwindSafe for Errno",1,["nix::errno::consts::Errno"]],["impl RefUnwindSafe for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl RefUnwindSafe for AtFlags",1,["nix::fcntl::AtFlags"]],["impl RefUnwindSafe for OFlag",1,["nix::fcntl::OFlag"]],["impl RefUnwindSafe for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl RefUnwindSafe for SealFlag",1,["nix::fcntl::SealFlag"]],["impl RefUnwindSafe for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> RefUnwindSafe for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl RefUnwindSafe for FlockArg",1,["nix::fcntl::FlockArg"]],["impl RefUnwindSafe for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl RefUnwindSafe for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl RefUnwindSafe for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl RefUnwindSafe for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl RefUnwindSafe for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl RefUnwindSafe for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> RefUnwindSafe for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl RefUnwindSafe for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl RefUnwindSafe for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl RefUnwindSafe for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl RefUnwindSafe for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl RefUnwindSafe for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl RefUnwindSafe for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl RefUnwindSafe for MqAttr",1,["nix::mqueue::MqAttr"]],["impl RefUnwindSafe for MqdT",1,["nix::mqueue::MqdT"]],["impl RefUnwindSafe for PollFd",1,["nix::poll::PollFd"]],["impl RefUnwindSafe for PollFlags",1,["nix::poll::PollFlags"]],["impl RefUnwindSafe for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl RefUnwindSafe for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl RefUnwindSafe for PtyMaster",1,["nix::pty::PtyMaster"]],["impl RefUnwindSafe for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl RefUnwindSafe for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl RefUnwindSafe for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl RefUnwindSafe for LioMode",1,["nix::sys::aio::LioMode"]],["impl RefUnwindSafe for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl RefUnwindSafe for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> RefUnwindSafe for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> RefUnwindSafe for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl RefUnwindSafe for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl RefUnwindSafe for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl RefUnwindSafe for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl RefUnwindSafe for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl RefUnwindSafe for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl RefUnwindSafe for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl RefUnwindSafe for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl RefUnwindSafe for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl RefUnwindSafe for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl RefUnwindSafe for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl RefUnwindSafe for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl RefUnwindSafe for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl RefUnwindSafe for Persona",1,["nix::sys::personality::Persona"]],["impl RefUnwindSafe for Request",1,["nix::sys::ptrace::linux::Request"]],["impl RefUnwindSafe for Event",1,["nix::sys::ptrace::linux::Event"]],["impl RefUnwindSafe for Options",1,["nix::sys::ptrace::linux::Options"]],["impl RefUnwindSafe for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl RefUnwindSafe for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl RefUnwindSafe for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl RefUnwindSafe for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl RefUnwindSafe for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl RefUnwindSafe for Resource",1,["nix::sys::resource::Resource"]],["impl RefUnwindSafe for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl RefUnwindSafe for Usage",1,["nix::sys::resource::Usage"]],["impl RefUnwindSafe for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> RefUnwindSafe for Fds<'a>",1,["nix::sys::select::Fds"]],["impl RefUnwindSafe for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl RefUnwindSafe for Signal",1,["nix::sys::signal::Signal"]],["impl RefUnwindSafe for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl RefUnwindSafe for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl RefUnwindSafe for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl RefUnwindSafe for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> RefUnwindSafe for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl RefUnwindSafe for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl RefUnwindSafe for SigAction",1,["nix::sys::signal::SigAction"]],["impl RefUnwindSafe for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl RefUnwindSafe for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl RefUnwindSafe for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl RefUnwindSafe for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl RefUnwindSafe for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl RefUnwindSafe for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl RefUnwindSafe for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl RefUnwindSafe for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl RefUnwindSafe for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl RefUnwindSafe for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl RefUnwindSafe for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl RefUnwindSafe for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl RefUnwindSafe for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl RefUnwindSafe for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl RefUnwindSafe for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl RefUnwindSafe for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl RefUnwindSafe for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl RefUnwindSafe for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl RefUnwindSafe for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl RefUnwindSafe for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl RefUnwindSafe for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl RefUnwindSafe for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl RefUnwindSafe for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl RefUnwindSafe for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl RefUnwindSafe for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl RefUnwindSafe for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl RefUnwindSafe for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl RefUnwindSafe for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl RefUnwindSafe for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl RefUnwindSafe for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl RefUnwindSafe for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl RefUnwindSafe for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl RefUnwindSafe for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl RefUnwindSafe for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl RefUnwindSafe for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl RefUnwindSafe for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl RefUnwindSafe for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl RefUnwindSafe for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl RefUnwindSafe for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl RefUnwindSafe for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl RefUnwindSafe for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl RefUnwindSafe for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl RefUnwindSafe for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl RefUnwindSafe for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl RefUnwindSafe for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl RefUnwindSafe for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl RefUnwindSafe for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl RefUnwindSafe for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl RefUnwindSafe for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl RefUnwindSafe for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl RefUnwindSafe for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl RefUnwindSafe for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl RefUnwindSafe for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl RefUnwindSafe for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl RefUnwindSafe for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl RefUnwindSafe for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl RefUnwindSafe for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl RefUnwindSafe for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl RefUnwindSafe for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl RefUnwindSafe for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl RefUnwindSafe for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl RefUnwindSafe for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl RefUnwindSafe for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl RefUnwindSafe for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl RefUnwindSafe for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl RefUnwindSafe for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl RefUnwindSafe for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl RefUnwindSafe for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl RefUnwindSafe for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl RefUnwindSafe for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl RefUnwindSafe for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl RefUnwindSafe for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl RefUnwindSafe for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl RefUnwindSafe for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl RefUnwindSafe for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl RefUnwindSafe for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl RefUnwindSafe for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> RefUnwindSafe for AlgSetKey<T>where
    T: RefUnwindSafe,
",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl RefUnwindSafe for SockType",1,["nix::sys::socket::SockType"]],["impl RefUnwindSafe for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl RefUnwindSafe for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl RefUnwindSafe for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl RefUnwindSafe for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl RefUnwindSafe for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl RefUnwindSafe for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl RefUnwindSafe for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> RefUnwindSafe for RecvMsg<'a, 's, S>where
    S: RefUnwindSafe,
",1,["nix::sys::socket::RecvMsg"]],["impl<'a> RefUnwindSafe for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl RefUnwindSafe for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl RefUnwindSafe for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> RefUnwindSafe for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> RefUnwindSafe for MultiHeaders<S>where
    S: RefUnwindSafe,
",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> RefUnwindSafe for MultiResults<'a, S>where
    S: RefUnwindSafe,
",1,["nix::sys::socket::MultiResults"]],["impl<'a> RefUnwindSafe for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl RefUnwindSafe for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl RefUnwindSafe for SFlag",1,["nix::sys::stat::SFlag"]],["impl RefUnwindSafe for Mode",1,["nix::sys::stat::Mode"]],["impl RefUnwindSafe for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl RefUnwindSafe for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl RefUnwindSafe for Statfs",1,["nix::sys::statfs::Statfs"]],["impl RefUnwindSafe for FsType",1,["nix::sys::statfs::FsType"]],["impl RefUnwindSafe for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl RefUnwindSafe for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl RefUnwindSafe for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !RefUnwindSafe for Termios",1,["nix::sys::termios::Termios"]],["impl RefUnwindSafe for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl RefUnwindSafe for SetArg",1,["nix::sys::termios::SetArg"]],["impl RefUnwindSafe for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl RefUnwindSafe for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl RefUnwindSafe for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl RefUnwindSafe for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl RefUnwindSafe for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl RefUnwindSafe for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl RefUnwindSafe for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl RefUnwindSafe for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl RefUnwindSafe for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl RefUnwindSafe for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl RefUnwindSafe for TimeVal",1,["nix::sys::time::TimeVal"]],["impl RefUnwindSafe for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> RefUnwindSafe for IoVec<T>where
    T: RefUnwindSafe,
",1,["nix::sys::uio::IoVec"]],["impl RefUnwindSafe for UtsName",1,["nix::sys::utsname::UtsName"]],["impl RefUnwindSafe for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl RefUnwindSafe for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl RefUnwindSafe for Id",1,["nix::sys::wait::Id"]],["impl RefUnwindSafe for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl RefUnwindSafe for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl RefUnwindSafe for Inotify",1,["nix::sys::inotify::Inotify"]],["impl RefUnwindSafe for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl RefUnwindSafe for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl RefUnwindSafe for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl RefUnwindSafe for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl RefUnwindSafe for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl RefUnwindSafe for Timer",1,["nix::sys::timer::Timer"]],["impl RefUnwindSafe for ClockId",1,["nix::time::ClockId"]],["impl RefUnwindSafe for UContext",1,["nix::ucontext::UContext"]],["impl RefUnwindSafe for ResUid",1,["nix::unistd::getres::ResUid"]],["impl RefUnwindSafe for ResGid",1,["nix::unistd::getres::ResGid"]],["impl RefUnwindSafe for Uid",1,["nix::unistd::Uid"]],["impl RefUnwindSafe for Gid",1,["nix::unistd::Gid"]],["impl RefUnwindSafe for Pid",1,["nix::unistd::Pid"]],["impl RefUnwindSafe for ForkResult",1,["nix::unistd::ForkResult"]],["impl RefUnwindSafe for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl RefUnwindSafe for Whence",1,["nix::unistd::Whence"]],["impl RefUnwindSafe for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl RefUnwindSafe for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl RefUnwindSafe for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl RefUnwindSafe for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl RefUnwindSafe for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl RefUnwindSafe for User",1,["nix::unistd::User"]],["impl RefUnwindSafe for Group",1,["nix::unistd::Group"]]], +"once_cell":[["impl<T> RefUnwindSafe for OnceCell<T>where
    T: UnwindSafe + RefUnwindSafe,
",1,["once_cell::sync::OnceCell"]],["impl<T> RefUnwindSafe for OnceBox<T>where
    T: RefUnwindSafe,
",1,["once_cell::race::once_box::OnceBox"]],["impl RefUnwindSafe for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl RefUnwindSafe for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceCell<T>"],["impl<T, F: RefUnwindSafe> RefUnwindSafe for Lazy<T, F>where
    OnceCell<T>: RefUnwindSafe,
"],["impl<T, F: RefUnwindSafe> RefUnwindSafe for Lazy<T, F>where
    OnceCell<T>: RefUnwindSafe,
"]], +"parking_lot":[["impl RefUnwindSafe for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl RefUnwindSafe for Condvar",1,["parking_lot::condvar::Condvar"]],["impl RefUnwindSafe for OnceState",1,["parking_lot::once::OnceState"]],["impl RefUnwindSafe for Once",1,["parking_lot::once::Once"]],["impl RefUnwindSafe for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl RefUnwindSafe for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl RefUnwindSafe for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl RefUnwindSafe for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl RefUnwindSafe for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl RefUnwindSafe for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl RefUnwindSafe for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl RefUnwindSafe for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl RefUnwindSafe for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl RefUnwindSafe for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl RefUnwindSafe for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl RefUnwindSafe for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl RefUnwindSafe for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl RefUnwindSafe for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl RefUnwindSafe for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl RefUnwindSafe for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl RefUnwindSafe for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl RefUnwindSafe for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl RefUnwindSafe for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl RefUnwindSafe for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl RefUnwindSafe for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> RefUnwindSafe for SseMachine<S3, S4, NI>where
    NI: RefUnwindSafe,
    S3: RefUnwindSafe,
    S4: RefUnwindSafe,
",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> RefUnwindSafe for Avx2Machine<NI>where
    NI: RefUnwindSafe,
",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl RefUnwindSafe for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl RefUnwindSafe for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl RefUnwindSafe for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl RefUnwindSafe for Error",1,["primitive_types::Error"]],["impl RefUnwindSafe for U128",1,["primitive_types::U128"]],["impl RefUnwindSafe for U256",1,["primitive_types::U256"]],["impl RefUnwindSafe for U512",1,["primitive_types::U512"]],["impl RefUnwindSafe for H128",1,["primitive_types::H128"]],["impl RefUnwindSafe for H160",1,["primitive_types::H160"]],["impl RefUnwindSafe for H256",1,["primitive_types::H256"]],["impl RefUnwindSafe for H384",1,["primitive_types::H384"]],["impl RefUnwindSafe for H512",1,["primitive_types::H512"]],["impl RefUnwindSafe for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl RefUnwindSafe for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl RefUnwindSafe for TokenStream",1,["proc_macro2::TokenStream"]],["impl RefUnwindSafe for LexError",1,["proc_macro2::LexError"]],["impl RefUnwindSafe for Span",1,["proc_macro2::Span"]],["impl RefUnwindSafe for TokenTree",1,["proc_macro2::TokenTree"]],["impl RefUnwindSafe for Group",1,["proc_macro2::Group"]],["impl RefUnwindSafe for Delimiter",1,["proc_macro2::Delimiter"]],["impl RefUnwindSafe for Punct",1,["proc_macro2::Punct"]],["impl RefUnwindSafe for Spacing",1,["proc_macro2::Spacing"]],["impl RefUnwindSafe for Ident",1,["proc_macro2::Ident"]],["impl RefUnwindSafe for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl RefUnwindSafe for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl RefUnwindSafe for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> RefUnwindSafe for DistIter<D, R, T>where
    D: RefUnwindSafe,
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> RefUnwindSafe for DistMap<D, F, T, S>where
    D: RefUnwindSafe,
    F: RefUnwindSafe,
",1,["rand::distributions::distribution::DistMap"]],["impl RefUnwindSafe for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl RefUnwindSafe for Open01",1,["rand::distributions::float::Open01"]],["impl RefUnwindSafe for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> RefUnwindSafe for Slice<'a, T>where
    T: RefUnwindSafe,
",1,["rand::distributions::slice::Slice"]],["impl<X> RefUnwindSafe for WeightedIndex<X>where
    X: RefUnwindSafe,
    <X as SampleUniform>::Sampler: RefUnwindSafe,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl RefUnwindSafe for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> RefUnwindSafe for Uniform<X>where
    <X as SampleUniform>::Sampler: RefUnwindSafe,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> RefUnwindSafe for UniformInt<X>where
    X: RefUnwindSafe,
",1,["rand::distributions::uniform::UniformInt"]],["impl RefUnwindSafe for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> RefUnwindSafe for UniformFloat<X>where
    X: RefUnwindSafe,
",1,["rand::distributions::uniform::UniformFloat"]],["impl RefUnwindSafe for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> RefUnwindSafe for WeightedIndex<W>where
    W: RefUnwindSafe,
",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl RefUnwindSafe for Standard",1,["rand::distributions::Standard"]],["impl<R> RefUnwindSafe for ReadRng<R>where
    R: RefUnwindSafe,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl !RefUnwindSafe for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> RefUnwindSafe for ReseedingRng<R, Rsdr>where
    R: RefUnwindSafe,
    Rsdr: RefUnwindSafe,
    <R as BlockRngCore>::Results: RefUnwindSafe,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl RefUnwindSafe for StepRng",1,["rand::rngs::mock::StepRng"]],["impl RefUnwindSafe for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> RefUnwindSafe for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl RefUnwindSafe for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> RefUnwindSafe for SliceChooseIter<'a, S, T>where
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl RefUnwindSafe for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl RefUnwindSafe for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl RefUnwindSafe for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl RefUnwindSafe for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl RefUnwindSafe for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl RefUnwindSafe for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> RefUnwindSafe for BlockRng<R>where
    R: RefUnwindSafe,
    <R as BlockRngCore>::Results: RefUnwindSafe,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> RefUnwindSafe for BlockRng64<R>where
    R: RefUnwindSafe,
    <R as BlockRngCore>::Results: RefUnwindSafe,
",1,["rand_core::block::BlockRng64"]],["impl !RefUnwindSafe for Error",1,["rand_core::error::Error"]],["impl RefUnwindSafe for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl RefUnwindSafe for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl RefUnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> RefUnwindSafe for Match<'t>",1,["regex::re_bytes::Match"]],["impl RefUnwindSafe for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> RefUnwindSafe for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> RefUnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> RefUnwindSafe for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> RefUnwindSafe for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> RefUnwindSafe for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl RefUnwindSafe for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> RefUnwindSafe for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> RefUnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReplacerRef<'a, R>where
    R: RefUnwindSafe,
",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> RefUnwindSafe for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl RefUnwindSafe for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl RefUnwindSafe for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl RefUnwindSafe for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> RefUnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl RefUnwindSafe for Error",1,["regex::error::Error"]],["impl RefUnwindSafe for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl RefUnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl RefUnwindSafe for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl RefUnwindSafe for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl RefUnwindSafe for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> RefUnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> RefUnwindSafe for Match<'t>",1,["regex::re_unicode::Match"]],["impl RefUnwindSafe for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> RefUnwindSafe for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> RefUnwindSafe for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> RefUnwindSafe for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl RefUnwindSafe for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> RefUnwindSafe for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> RefUnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> RefUnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> RefUnwindSafe for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReplacerRef<'a, R>where
    R: RefUnwindSafe,
",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> RefUnwindSafe for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl RefUnwindSafe for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !RefUnwindSafe for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl RefUnwindSafe for Printer",1,["regex_syntax::ast::print::Printer"]],["impl RefUnwindSafe for Error",1,["regex_syntax::ast::Error"]],["impl RefUnwindSafe for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl RefUnwindSafe for Span",1,["regex_syntax::ast::Span"]],["impl RefUnwindSafe for Position",1,["regex_syntax::ast::Position"]],["impl RefUnwindSafe for WithComments",1,["regex_syntax::ast::WithComments"]],["impl RefUnwindSafe for Comment",1,["regex_syntax::ast::Comment"]],["impl RefUnwindSafe for Ast",1,["regex_syntax::ast::Ast"]],["impl RefUnwindSafe for Alternation",1,["regex_syntax::ast::Alternation"]],["impl RefUnwindSafe for Concat",1,["regex_syntax::ast::Concat"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::ast::Literal"]],["impl RefUnwindSafe for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl RefUnwindSafe for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl RefUnwindSafe for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl RefUnwindSafe for Class",1,["regex_syntax::ast::Class"]],["impl RefUnwindSafe for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl RefUnwindSafe for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl RefUnwindSafe for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl RefUnwindSafe for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl RefUnwindSafe for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl RefUnwindSafe for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl RefUnwindSafe for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl RefUnwindSafe for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl RefUnwindSafe for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl RefUnwindSafe for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl RefUnwindSafe for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl RefUnwindSafe for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl RefUnwindSafe for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl RefUnwindSafe for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl RefUnwindSafe for Assertion",1,["regex_syntax::ast::Assertion"]],["impl RefUnwindSafe for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl RefUnwindSafe for Repetition",1,["regex_syntax::ast::Repetition"]],["impl RefUnwindSafe for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl RefUnwindSafe for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl RefUnwindSafe for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl RefUnwindSafe for Group",1,["regex_syntax::ast::Group"]],["impl RefUnwindSafe for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl RefUnwindSafe for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl RefUnwindSafe for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl RefUnwindSafe for Flags",1,["regex_syntax::ast::Flags"]],["impl RefUnwindSafe for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl RefUnwindSafe for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl RefUnwindSafe for Flag",1,["regex_syntax::ast::Flag"]],["impl RefUnwindSafe for Error",1,["regex_syntax::error::Error"]],["impl RefUnwindSafe for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl RefUnwindSafe for Printer",1,["regex_syntax::hir::print::Printer"]],["impl RefUnwindSafe for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !RefUnwindSafe for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl RefUnwindSafe for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl RefUnwindSafe for Error",1,["regex_syntax::hir::Error"]],["impl RefUnwindSafe for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl RefUnwindSafe for Hir",1,["regex_syntax::hir::Hir"]],["impl RefUnwindSafe for HirKind",1,["regex_syntax::hir::HirKind"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::hir::Literal"]],["impl RefUnwindSafe for Class",1,["regex_syntax::hir::Class"]],["impl RefUnwindSafe for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> RefUnwindSafe for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl RefUnwindSafe for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl RefUnwindSafe for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> RefUnwindSafe for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl RefUnwindSafe for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl RefUnwindSafe for Anchor",1,["regex_syntax::hir::Anchor"]],["impl RefUnwindSafe for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl RefUnwindSafe for Group",1,["regex_syntax::hir::Group"]],["impl RefUnwindSafe for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl RefUnwindSafe for Repetition",1,["regex_syntax::hir::Repetition"]],["impl RefUnwindSafe for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl RefUnwindSafe for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl RefUnwindSafe for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !RefUnwindSafe for Parser",1,["regex_syntax::parser::Parser"]],["impl RefUnwindSafe for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl RefUnwindSafe for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl RefUnwindSafe for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl RefUnwindSafe for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl RefUnwindSafe for DecoderError",1,["rlp::error::DecoderError"]],["impl RefUnwindSafe for Prototype",1,["rlp::rlpin::Prototype"]],["impl RefUnwindSafe for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !RefUnwindSafe for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !RefUnwindSafe for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl RefUnwindSafe for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> RefUnwindSafe for ToHexIter<T>where
    T: RefUnwindSafe,
",1,["rustc_hex::ToHexIter"]],["impl RefUnwindSafe for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> RefUnwindSafe for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl RefUnwindSafe for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl RefUnwindSafe for Always",1,["scopeguard::Always"]],["impl<T, F, S> RefUnwindSafe for ScopeGuard<T, F, S>where
    F: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["scopeguard::ScopeGuard"]]], +"serde":[["impl RefUnwindSafe for Error",1,["serde::de::value::Error"]],["impl<E> RefUnwindSafe for UnitDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::UnitDeserializer"]],["impl<E> RefUnwindSafe for BoolDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::BoolDeserializer"]],["impl<E> RefUnwindSafe for I8Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::I8Deserializer"]],["impl<E> RefUnwindSafe for I16Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::I16Deserializer"]],["impl<E> RefUnwindSafe for I32Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::I32Deserializer"]],["impl<E> RefUnwindSafe for I64Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::I64Deserializer"]],["impl<E> RefUnwindSafe for IsizeDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::IsizeDeserializer"]],["impl<E> RefUnwindSafe for U8Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::U8Deserializer"]],["impl<E> RefUnwindSafe for U16Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::U16Deserializer"]],["impl<E> RefUnwindSafe for U64Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::U64Deserializer"]],["impl<E> RefUnwindSafe for UsizeDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::UsizeDeserializer"]],["impl<E> RefUnwindSafe for F32Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::F32Deserializer"]],["impl<E> RefUnwindSafe for F64Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::F64Deserializer"]],["impl<E> RefUnwindSafe for CharDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::CharDeserializer"]],["impl<E> RefUnwindSafe for I128Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::I128Deserializer"]],["impl<E> RefUnwindSafe for U128Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::U128Deserializer"]],["impl<E> RefUnwindSafe for U32Deserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> RefUnwindSafe for StrDeserializer<'a, E>where
    E: RefUnwindSafe,
",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> RefUnwindSafe for BorrowedStrDeserializer<'de, E>where
    E: RefUnwindSafe,
",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> RefUnwindSafe for StringDeserializer<E>where
    E: RefUnwindSafe,
",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> RefUnwindSafe for CowStrDeserializer<'a, E>where
    E: RefUnwindSafe,
",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> RefUnwindSafe for BytesDeserializer<'a, E>where
    E: RefUnwindSafe,
",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> RefUnwindSafe for BorrowedBytesDeserializer<'de, E>where
    E: RefUnwindSafe,
",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> RefUnwindSafe for SeqDeserializer<I, E>where
    E: RefUnwindSafe,
    I: RefUnwindSafe,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> RefUnwindSafe for SeqAccessDeserializer<A>where
    A: RefUnwindSafe,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> RefUnwindSafe for MapDeserializer<'de, I, E>where
    E: RefUnwindSafe,
    I: RefUnwindSafe,
    <<I as Iterator>::Item as Pair>::Second: RefUnwindSafe,
",1,["serde::de::value::MapDeserializer"]],["impl<A> RefUnwindSafe for MapAccessDeserializer<A>where
    A: RefUnwindSafe,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> RefUnwindSafe for EnumAccessDeserializer<A>where
    A: RefUnwindSafe,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl RefUnwindSafe for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> RefUnwindSafe for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> RefUnwindSafe for Impossible<Ok, Error>where
    Error: RefUnwindSafe,
    Ok: RefUnwindSafe,
",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl RefUnwindSafe for Keccak224Core",1,["sha3::Keccak224Core"]],["impl RefUnwindSafe for Keccak256Core",1,["sha3::Keccak256Core"]],["impl RefUnwindSafe for Keccak384Core",1,["sha3::Keccak384Core"]],["impl RefUnwindSafe for Keccak512Core",1,["sha3::Keccak512Core"]],["impl RefUnwindSafe for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl RefUnwindSafe for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl RefUnwindSafe for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl RefUnwindSafe for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl RefUnwindSafe for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl RefUnwindSafe for Shake128Core",1,["sha3::Shake128Core"]],["impl RefUnwindSafe for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl RefUnwindSafe for Shake256Core",1,["sha3::Shake256Core"]],["impl RefUnwindSafe for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl RefUnwindSafe for CShake128Core",1,["sha3::CShake128Core"]],["impl RefUnwindSafe for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl RefUnwindSafe for CShake256Core",1,["sha3::CShake256Core"]],["impl RefUnwindSafe for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl RefUnwindSafe for CompactHeader",1,["shale::compact::CompactHeader"]],["impl RefUnwindSafe for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !RefUnwindSafe for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl RefUnwindSafe for ShaleError",1,["shale::ShaleError"]],["impl RefUnwindSafe for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> RefUnwindSafe for ObjPtr<T>where
    T: RefUnwindSafe,
",1,["shale::ObjPtr"]],["impl<T> !RefUnwindSafe for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !RefUnwindSafe for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !RefUnwindSafe for MummyObj<T>",1,["shale::MummyObj"]],["impl !RefUnwindSafe for PlainMem",1,["shale::PlainMem"]],["impl<T> !RefUnwindSafe for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> RefUnwindSafe for Slab<T>where
    T: RefUnwindSafe,
",1,["slab::Slab"]],["impl<'a, T> RefUnwindSafe for VacantEntry<'a, T>where
    T: RefUnwindSafe,
",1,["slab::VacantEntry"]],["impl<T> RefUnwindSafe for IntoIter<T>where
    T: RefUnwindSafe,
",1,["slab::IntoIter"]],["impl<'a, T> RefUnwindSafe for Iter<'a, T>where
    T: RefUnwindSafe,
",1,["slab::Iter"]],["impl<'a, T> RefUnwindSafe for IterMut<'a, T>where
    T: RefUnwindSafe,
",1,["slab::IterMut"]],["impl<'a, T> RefUnwindSafe for Drain<'a, T>where
    T: RefUnwindSafe,
",1,["slab::Drain"]]], +"smallvec":[["impl RefUnwindSafe for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> RefUnwindSafe for Drain<'a, T>where
    T: RefUnwindSafe,
    <T as Array>::Item: RefUnwindSafe,
",1,["smallvec::Drain"]],["impl<A> RefUnwindSafe for SmallVec<A>where
    A: RefUnwindSafe,
    <A as Array>::Item: RefUnwindSafe,
",1,["smallvec::SmallVec"]],["impl<A> RefUnwindSafe for IntoIter<A>where
    A: RefUnwindSafe,
    <A as Array>::Item: RefUnwindSafe,
",1,["smallvec::IntoIter"]]], +"syn":[["impl RefUnwindSafe for Underscore",1,["syn::token::Underscore"]],["impl RefUnwindSafe for Abstract",1,["syn::token::Abstract"]],["impl RefUnwindSafe for As",1,["syn::token::As"]],["impl RefUnwindSafe for Async",1,["syn::token::Async"]],["impl RefUnwindSafe for Auto",1,["syn::token::Auto"]],["impl RefUnwindSafe for Await",1,["syn::token::Await"]],["impl RefUnwindSafe for Become",1,["syn::token::Become"]],["impl RefUnwindSafe for Box",1,["syn::token::Box"]],["impl RefUnwindSafe for Break",1,["syn::token::Break"]],["impl RefUnwindSafe for Const",1,["syn::token::Const"]],["impl RefUnwindSafe for Continue",1,["syn::token::Continue"]],["impl RefUnwindSafe for Crate",1,["syn::token::Crate"]],["impl RefUnwindSafe for Default",1,["syn::token::Default"]],["impl RefUnwindSafe for Do",1,["syn::token::Do"]],["impl RefUnwindSafe for Dyn",1,["syn::token::Dyn"]],["impl RefUnwindSafe for Else",1,["syn::token::Else"]],["impl RefUnwindSafe for Enum",1,["syn::token::Enum"]],["impl RefUnwindSafe for Extern",1,["syn::token::Extern"]],["impl RefUnwindSafe for Final",1,["syn::token::Final"]],["impl RefUnwindSafe for Fn",1,["syn::token::Fn"]],["impl RefUnwindSafe for For",1,["syn::token::For"]],["impl RefUnwindSafe for If",1,["syn::token::If"]],["impl RefUnwindSafe for Impl",1,["syn::token::Impl"]],["impl RefUnwindSafe for In",1,["syn::token::In"]],["impl RefUnwindSafe for Let",1,["syn::token::Let"]],["impl RefUnwindSafe for Loop",1,["syn::token::Loop"]],["impl RefUnwindSafe for Macro",1,["syn::token::Macro"]],["impl RefUnwindSafe for Match",1,["syn::token::Match"]],["impl RefUnwindSafe for Mod",1,["syn::token::Mod"]],["impl RefUnwindSafe for Move",1,["syn::token::Move"]],["impl RefUnwindSafe for Mut",1,["syn::token::Mut"]],["impl RefUnwindSafe for Override",1,["syn::token::Override"]],["impl RefUnwindSafe for Priv",1,["syn::token::Priv"]],["impl RefUnwindSafe for Pub",1,["syn::token::Pub"]],["impl RefUnwindSafe for Ref",1,["syn::token::Ref"]],["impl RefUnwindSafe for Return",1,["syn::token::Return"]],["impl RefUnwindSafe for SelfType",1,["syn::token::SelfType"]],["impl RefUnwindSafe for SelfValue",1,["syn::token::SelfValue"]],["impl RefUnwindSafe for Static",1,["syn::token::Static"]],["impl RefUnwindSafe for Struct",1,["syn::token::Struct"]],["impl RefUnwindSafe for Super",1,["syn::token::Super"]],["impl RefUnwindSafe for Trait",1,["syn::token::Trait"]],["impl RefUnwindSafe for Try",1,["syn::token::Try"]],["impl RefUnwindSafe for Type",1,["syn::token::Type"]],["impl RefUnwindSafe for Typeof",1,["syn::token::Typeof"]],["impl RefUnwindSafe for Union",1,["syn::token::Union"]],["impl RefUnwindSafe for Unsafe",1,["syn::token::Unsafe"]],["impl RefUnwindSafe for Unsized",1,["syn::token::Unsized"]],["impl RefUnwindSafe for Use",1,["syn::token::Use"]],["impl RefUnwindSafe for Virtual",1,["syn::token::Virtual"]],["impl RefUnwindSafe for Where",1,["syn::token::Where"]],["impl RefUnwindSafe for While",1,["syn::token::While"]],["impl RefUnwindSafe for Yield",1,["syn::token::Yield"]],["impl RefUnwindSafe for Add",1,["syn::token::Add"]],["impl RefUnwindSafe for AddEq",1,["syn::token::AddEq"]],["impl RefUnwindSafe for And",1,["syn::token::And"]],["impl RefUnwindSafe for AndAnd",1,["syn::token::AndAnd"]],["impl RefUnwindSafe for AndEq",1,["syn::token::AndEq"]],["impl RefUnwindSafe for At",1,["syn::token::At"]],["impl RefUnwindSafe for Bang",1,["syn::token::Bang"]],["impl RefUnwindSafe for Caret",1,["syn::token::Caret"]],["impl RefUnwindSafe for CaretEq",1,["syn::token::CaretEq"]],["impl RefUnwindSafe for Colon",1,["syn::token::Colon"]],["impl RefUnwindSafe for Colon2",1,["syn::token::Colon2"]],["impl RefUnwindSafe for Comma",1,["syn::token::Comma"]],["impl RefUnwindSafe for Div",1,["syn::token::Div"]],["impl RefUnwindSafe for DivEq",1,["syn::token::DivEq"]],["impl RefUnwindSafe for Dollar",1,["syn::token::Dollar"]],["impl RefUnwindSafe for Dot",1,["syn::token::Dot"]],["impl RefUnwindSafe for Dot2",1,["syn::token::Dot2"]],["impl RefUnwindSafe for Dot3",1,["syn::token::Dot3"]],["impl RefUnwindSafe for DotDotEq",1,["syn::token::DotDotEq"]],["impl RefUnwindSafe for Eq",1,["syn::token::Eq"]],["impl RefUnwindSafe for EqEq",1,["syn::token::EqEq"]],["impl RefUnwindSafe for Ge",1,["syn::token::Ge"]],["impl RefUnwindSafe for Gt",1,["syn::token::Gt"]],["impl RefUnwindSafe for Le",1,["syn::token::Le"]],["impl RefUnwindSafe for Lt",1,["syn::token::Lt"]],["impl RefUnwindSafe for MulEq",1,["syn::token::MulEq"]],["impl RefUnwindSafe for Ne",1,["syn::token::Ne"]],["impl RefUnwindSafe for Or",1,["syn::token::Or"]],["impl RefUnwindSafe for OrEq",1,["syn::token::OrEq"]],["impl RefUnwindSafe for OrOr",1,["syn::token::OrOr"]],["impl RefUnwindSafe for Pound",1,["syn::token::Pound"]],["impl RefUnwindSafe for Question",1,["syn::token::Question"]],["impl RefUnwindSafe for RArrow",1,["syn::token::RArrow"]],["impl RefUnwindSafe for LArrow",1,["syn::token::LArrow"]],["impl RefUnwindSafe for Rem",1,["syn::token::Rem"]],["impl RefUnwindSafe for RemEq",1,["syn::token::RemEq"]],["impl RefUnwindSafe for FatArrow",1,["syn::token::FatArrow"]],["impl RefUnwindSafe for Semi",1,["syn::token::Semi"]],["impl RefUnwindSafe for Shl",1,["syn::token::Shl"]],["impl RefUnwindSafe for ShlEq",1,["syn::token::ShlEq"]],["impl RefUnwindSafe for Shr",1,["syn::token::Shr"]],["impl RefUnwindSafe for ShrEq",1,["syn::token::ShrEq"]],["impl RefUnwindSafe for Star",1,["syn::token::Star"]],["impl RefUnwindSafe for Sub",1,["syn::token::Sub"]],["impl RefUnwindSafe for SubEq",1,["syn::token::SubEq"]],["impl RefUnwindSafe for Tilde",1,["syn::token::Tilde"]],["impl RefUnwindSafe for Brace",1,["syn::token::Brace"]],["impl RefUnwindSafe for Bracket",1,["syn::token::Bracket"]],["impl RefUnwindSafe for Paren",1,["syn::token::Paren"]],["impl RefUnwindSafe for Group",1,["syn::token::Group"]],["impl RefUnwindSafe for Attribute",1,["syn::attr::Attribute"]],["impl RefUnwindSafe for AttrStyle",1,["syn::attr::AttrStyle"]],["impl RefUnwindSafe for Meta",1,["syn::attr::Meta"]],["impl RefUnwindSafe for MetaList",1,["syn::attr::MetaList"]],["impl RefUnwindSafe for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl RefUnwindSafe for NestedMeta",1,["syn::attr::NestedMeta"]],["impl RefUnwindSafe for Variant",1,["syn::data::Variant"]],["impl RefUnwindSafe for Fields",1,["syn::data::Fields"]],["impl RefUnwindSafe for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl RefUnwindSafe for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl RefUnwindSafe for Field",1,["syn::data::Field"]],["impl RefUnwindSafe for Visibility",1,["syn::data::Visibility"]],["impl RefUnwindSafe for VisPublic",1,["syn::data::VisPublic"]],["impl RefUnwindSafe for VisCrate",1,["syn::data::VisCrate"]],["impl RefUnwindSafe for VisRestricted",1,["syn::data::VisRestricted"]],["impl RefUnwindSafe for Expr",1,["syn::expr::Expr"]],["impl RefUnwindSafe for ExprArray",1,["syn::expr::ExprArray"]],["impl RefUnwindSafe for ExprAssign",1,["syn::expr::ExprAssign"]],["impl RefUnwindSafe for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl RefUnwindSafe for ExprAsync",1,["syn::expr::ExprAsync"]],["impl RefUnwindSafe for ExprAwait",1,["syn::expr::ExprAwait"]],["impl RefUnwindSafe for ExprBinary",1,["syn::expr::ExprBinary"]],["impl RefUnwindSafe for ExprBlock",1,["syn::expr::ExprBlock"]],["impl RefUnwindSafe for ExprBox",1,["syn::expr::ExprBox"]],["impl RefUnwindSafe for ExprBreak",1,["syn::expr::ExprBreak"]],["impl RefUnwindSafe for ExprCall",1,["syn::expr::ExprCall"]],["impl RefUnwindSafe for ExprCast",1,["syn::expr::ExprCast"]],["impl RefUnwindSafe for ExprClosure",1,["syn::expr::ExprClosure"]],["impl RefUnwindSafe for ExprContinue",1,["syn::expr::ExprContinue"]],["impl RefUnwindSafe for ExprField",1,["syn::expr::ExprField"]],["impl RefUnwindSafe for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl RefUnwindSafe for ExprGroup",1,["syn::expr::ExprGroup"]],["impl RefUnwindSafe for ExprIf",1,["syn::expr::ExprIf"]],["impl RefUnwindSafe for ExprIndex",1,["syn::expr::ExprIndex"]],["impl RefUnwindSafe for ExprLet",1,["syn::expr::ExprLet"]],["impl RefUnwindSafe for ExprLit",1,["syn::expr::ExprLit"]],["impl RefUnwindSafe for ExprLoop",1,["syn::expr::ExprLoop"]],["impl RefUnwindSafe for ExprMacro",1,["syn::expr::ExprMacro"]],["impl RefUnwindSafe for ExprMatch",1,["syn::expr::ExprMatch"]],["impl RefUnwindSafe for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl RefUnwindSafe for ExprParen",1,["syn::expr::ExprParen"]],["impl RefUnwindSafe for ExprPath",1,["syn::expr::ExprPath"]],["impl RefUnwindSafe for ExprRange",1,["syn::expr::ExprRange"]],["impl RefUnwindSafe for ExprReference",1,["syn::expr::ExprReference"]],["impl RefUnwindSafe for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl RefUnwindSafe for ExprReturn",1,["syn::expr::ExprReturn"]],["impl RefUnwindSafe for ExprStruct",1,["syn::expr::ExprStruct"]],["impl RefUnwindSafe for ExprTry",1,["syn::expr::ExprTry"]],["impl RefUnwindSafe for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl RefUnwindSafe for ExprTuple",1,["syn::expr::ExprTuple"]],["impl RefUnwindSafe for ExprType",1,["syn::expr::ExprType"]],["impl RefUnwindSafe for ExprUnary",1,["syn::expr::ExprUnary"]],["impl RefUnwindSafe for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl RefUnwindSafe for ExprWhile",1,["syn::expr::ExprWhile"]],["impl RefUnwindSafe for ExprYield",1,["syn::expr::ExprYield"]],["impl RefUnwindSafe for Member",1,["syn::expr::Member"]],["impl RefUnwindSafe for Index",1,["syn::expr::Index"]],["impl RefUnwindSafe for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl RefUnwindSafe for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl RefUnwindSafe for FieldValue",1,["syn::expr::FieldValue"]],["impl RefUnwindSafe for Label",1,["syn::expr::Label"]],["impl RefUnwindSafe for Arm",1,["syn::expr::Arm"]],["impl RefUnwindSafe for RangeLimits",1,["syn::expr::RangeLimits"]],["impl RefUnwindSafe for Generics",1,["syn::generics::Generics"]],["impl RefUnwindSafe for GenericParam",1,["syn::generics::GenericParam"]],["impl RefUnwindSafe for TypeParam",1,["syn::generics::TypeParam"]],["impl RefUnwindSafe for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl RefUnwindSafe for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> RefUnwindSafe for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> RefUnwindSafe for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> RefUnwindSafe for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl RefUnwindSafe for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl RefUnwindSafe for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl RefUnwindSafe for TraitBound",1,["syn::generics::TraitBound"]],["impl RefUnwindSafe for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl RefUnwindSafe for WhereClause",1,["syn::generics::WhereClause"]],["impl RefUnwindSafe for WherePredicate",1,["syn::generics::WherePredicate"]],["impl RefUnwindSafe for PredicateType",1,["syn::generics::PredicateType"]],["impl RefUnwindSafe for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl RefUnwindSafe for PredicateEq",1,["syn::generics::PredicateEq"]],["impl RefUnwindSafe for Item",1,["syn::item::Item"]],["impl RefUnwindSafe for ItemConst",1,["syn::item::ItemConst"]],["impl RefUnwindSafe for ItemEnum",1,["syn::item::ItemEnum"]],["impl RefUnwindSafe for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl RefUnwindSafe for ItemFn",1,["syn::item::ItemFn"]],["impl RefUnwindSafe for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl RefUnwindSafe for ItemImpl",1,["syn::item::ItemImpl"]],["impl RefUnwindSafe for ItemMacro",1,["syn::item::ItemMacro"]],["impl RefUnwindSafe for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl RefUnwindSafe for ItemMod",1,["syn::item::ItemMod"]],["impl RefUnwindSafe for ItemStatic",1,["syn::item::ItemStatic"]],["impl RefUnwindSafe for ItemStruct",1,["syn::item::ItemStruct"]],["impl RefUnwindSafe for ItemTrait",1,["syn::item::ItemTrait"]],["impl RefUnwindSafe for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl RefUnwindSafe for ItemType",1,["syn::item::ItemType"]],["impl RefUnwindSafe for ItemUnion",1,["syn::item::ItemUnion"]],["impl RefUnwindSafe for ItemUse",1,["syn::item::ItemUse"]],["impl RefUnwindSafe for UseTree",1,["syn::item::UseTree"]],["impl RefUnwindSafe for UsePath",1,["syn::item::UsePath"]],["impl RefUnwindSafe for UseName",1,["syn::item::UseName"]],["impl RefUnwindSafe for UseRename",1,["syn::item::UseRename"]],["impl RefUnwindSafe for UseGlob",1,["syn::item::UseGlob"]],["impl RefUnwindSafe for UseGroup",1,["syn::item::UseGroup"]],["impl RefUnwindSafe for ForeignItem",1,["syn::item::ForeignItem"]],["impl RefUnwindSafe for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl RefUnwindSafe for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl RefUnwindSafe for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl RefUnwindSafe for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl RefUnwindSafe for TraitItem",1,["syn::item::TraitItem"]],["impl RefUnwindSafe for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl RefUnwindSafe for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl RefUnwindSafe for TraitItemType",1,["syn::item::TraitItemType"]],["impl RefUnwindSafe for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl RefUnwindSafe for ImplItem",1,["syn::item::ImplItem"]],["impl RefUnwindSafe for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl RefUnwindSafe for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl RefUnwindSafe for ImplItemType",1,["syn::item::ImplItemType"]],["impl RefUnwindSafe for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl RefUnwindSafe for Signature",1,["syn::item::Signature"]],["impl RefUnwindSafe for FnArg",1,["syn::item::FnArg"]],["impl RefUnwindSafe for Receiver",1,["syn::item::Receiver"]],["impl RefUnwindSafe for File",1,["syn::file::File"]],["impl RefUnwindSafe for Lifetime",1,["syn::lifetime::Lifetime"]],["impl RefUnwindSafe for Lit",1,["syn::lit::Lit"]],["impl RefUnwindSafe for LitStr",1,["syn::lit::LitStr"]],["impl RefUnwindSafe for LitByteStr",1,["syn::lit::LitByteStr"]],["impl RefUnwindSafe for LitByte",1,["syn::lit::LitByte"]],["impl RefUnwindSafe for LitChar",1,["syn::lit::LitChar"]],["impl RefUnwindSafe for LitInt",1,["syn::lit::LitInt"]],["impl RefUnwindSafe for LitFloat",1,["syn::lit::LitFloat"]],["impl RefUnwindSafe for LitBool",1,["syn::lit::LitBool"]],["impl RefUnwindSafe for StrStyle",1,["syn::lit::StrStyle"]],["impl RefUnwindSafe for Macro",1,["syn::mac::Macro"]],["impl RefUnwindSafe for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl RefUnwindSafe for DeriveInput",1,["syn::derive::DeriveInput"]],["impl RefUnwindSafe for Data",1,["syn::derive::Data"]],["impl RefUnwindSafe for DataStruct",1,["syn::derive::DataStruct"]],["impl RefUnwindSafe for DataEnum",1,["syn::derive::DataEnum"]],["impl RefUnwindSafe for DataUnion",1,["syn::derive::DataUnion"]],["impl RefUnwindSafe for BinOp",1,["syn::op::BinOp"]],["impl RefUnwindSafe for UnOp",1,["syn::op::UnOp"]],["impl RefUnwindSafe for Block",1,["syn::stmt::Block"]],["impl RefUnwindSafe for Stmt",1,["syn::stmt::Stmt"]],["impl RefUnwindSafe for Local",1,["syn::stmt::Local"]],["impl RefUnwindSafe for Type",1,["syn::ty::Type"]],["impl RefUnwindSafe for TypeArray",1,["syn::ty::TypeArray"]],["impl RefUnwindSafe for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl RefUnwindSafe for TypeGroup",1,["syn::ty::TypeGroup"]],["impl RefUnwindSafe for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl RefUnwindSafe for TypeInfer",1,["syn::ty::TypeInfer"]],["impl RefUnwindSafe for TypeMacro",1,["syn::ty::TypeMacro"]],["impl RefUnwindSafe for TypeNever",1,["syn::ty::TypeNever"]],["impl RefUnwindSafe for TypeParen",1,["syn::ty::TypeParen"]],["impl RefUnwindSafe for TypePath",1,["syn::ty::TypePath"]],["impl RefUnwindSafe for TypePtr",1,["syn::ty::TypePtr"]],["impl RefUnwindSafe for TypeReference",1,["syn::ty::TypeReference"]],["impl RefUnwindSafe for TypeSlice",1,["syn::ty::TypeSlice"]],["impl RefUnwindSafe for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl RefUnwindSafe for TypeTuple",1,["syn::ty::TypeTuple"]],["impl RefUnwindSafe for Abi",1,["syn::ty::Abi"]],["impl RefUnwindSafe for BareFnArg",1,["syn::ty::BareFnArg"]],["impl RefUnwindSafe for Variadic",1,["syn::ty::Variadic"]],["impl RefUnwindSafe for ReturnType",1,["syn::ty::ReturnType"]],["impl RefUnwindSafe for Pat",1,["syn::pat::Pat"]],["impl RefUnwindSafe for PatBox",1,["syn::pat::PatBox"]],["impl RefUnwindSafe for PatIdent",1,["syn::pat::PatIdent"]],["impl RefUnwindSafe for PatLit",1,["syn::pat::PatLit"]],["impl RefUnwindSafe for PatMacro",1,["syn::pat::PatMacro"]],["impl RefUnwindSafe for PatOr",1,["syn::pat::PatOr"]],["impl RefUnwindSafe for PatPath",1,["syn::pat::PatPath"]],["impl RefUnwindSafe for PatRange",1,["syn::pat::PatRange"]],["impl RefUnwindSafe for PatReference",1,["syn::pat::PatReference"]],["impl RefUnwindSafe for PatRest",1,["syn::pat::PatRest"]],["impl RefUnwindSafe for PatSlice",1,["syn::pat::PatSlice"]],["impl RefUnwindSafe for PatStruct",1,["syn::pat::PatStruct"]],["impl RefUnwindSafe for PatTuple",1,["syn::pat::PatTuple"]],["impl RefUnwindSafe for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl RefUnwindSafe for PatType",1,["syn::pat::PatType"]],["impl RefUnwindSafe for PatWild",1,["syn::pat::PatWild"]],["impl RefUnwindSafe for FieldPat",1,["syn::pat::FieldPat"]],["impl RefUnwindSafe for Path",1,["syn::path::Path"]],["impl RefUnwindSafe for PathSegment",1,["syn::path::PathSegment"]],["impl RefUnwindSafe for PathArguments",1,["syn::path::PathArguments"]],["impl RefUnwindSafe for GenericArgument",1,["syn::path::GenericArgument"]],["impl RefUnwindSafe for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl RefUnwindSafe for Binding",1,["syn::path::Binding"]],["impl RefUnwindSafe for Constraint",1,["syn::path::Constraint"]],["impl RefUnwindSafe for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl RefUnwindSafe for QSelf",1,["syn::path::QSelf"]],["impl RefUnwindSafe for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> RefUnwindSafe for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> RefUnwindSafe for Punctuated<T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> RefUnwindSafe for Pairs<'a, T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> RefUnwindSafe for PairsMut<'a, T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::PairsMut"]],["impl<T, P> RefUnwindSafe for IntoPairs<T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::IntoPairs"]],["impl<T> RefUnwindSafe for IntoIter<T>where
    T: RefUnwindSafe,
",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !RefUnwindSafe for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !RefUnwindSafe for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> RefUnwindSafe for Pair<T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::Pair"]],["impl<'a> !RefUnwindSafe for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl RefUnwindSafe for Error",1,["syn::error::Error"]],["impl<'a> !RefUnwindSafe for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> RefUnwindSafe for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl RefUnwindSafe for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> RefUnwindSafe for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl !RefUnwindSafe for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl !RefUnwindSafe for Builder",1,["tokio::runtime::builder::Builder"]],["impl !RefUnwindSafe for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> !RefUnwindSafe for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl RefUnwindSafe for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !RefUnwindSafe for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl RefUnwindSafe for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !RefUnwindSafe for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !RefUnwindSafe for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl RefUnwindSafe for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> RefUnwindSafe for SendError<T>where
    T: RefUnwindSafe,
",1,["tokio::sync::broadcast::error::SendError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> !RefUnwindSafe for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> !RefUnwindSafe for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> !RefUnwindSafe for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> !RefUnwindSafe for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> !RefUnwindSafe for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> !RefUnwindSafe for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> RefUnwindSafe for SendError<T>where
    T: RefUnwindSafe,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
    T: RefUnwindSafe,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Mutex<T>",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T> !RefUnwindSafe for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T> !RefUnwindSafe for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T> !RefUnwindSafe for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl RefUnwindSafe for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl RefUnwindSafe for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl RefUnwindSafe for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl !RefUnwindSafe for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> !RefUnwindSafe for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl !RefUnwindSafe for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T, U = T> !RefUnwindSafe for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T> !RefUnwindSafe for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T, U = T> !RefUnwindSafe for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T> !RefUnwindSafe for RwLock<T>",1,["tokio::sync::rwlock::RwLock"]],["impl<T> !RefUnwindSafe for OnceCell<T>",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> RefUnwindSafe for SetError<T>where
    T: RefUnwindSafe,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> RefUnwindSafe for SendError<T>where
    T: RefUnwindSafe,
",1,["tokio::sync::watch::error::SendError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> RefUnwindSafe for Ref<'a, T>where
    T: RefUnwindSafe,
",1,["tokio::sync::watch::Ref"]],["impl !RefUnwindSafe for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !RefUnwindSafe for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> RefUnwindSafe for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> RefUnwindSafe for TaskLocalFuture<T, F>where
    F: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> RefUnwindSafe for Unconstrained<F>where
    F: RefUnwindSafe,
",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> RefUnwindSafe for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl RefUnwindSafe for AbortHandle"],["impl<T> RefUnwindSafe for JoinHandle<T>"],["impl RefUnwindSafe for Notify"]], +"typenum":[["impl RefUnwindSafe for B0",1,["typenum::bit::B0"]],["impl RefUnwindSafe for B1",1,["typenum::bit::B1"]],["impl<U> RefUnwindSafe for PInt<U>where
    U: RefUnwindSafe,
",1,["typenum::int::PInt"]],["impl<U> RefUnwindSafe for NInt<U>where
    U: RefUnwindSafe,
",1,["typenum::int::NInt"]],["impl RefUnwindSafe for Z0",1,["typenum::int::Z0"]],["impl RefUnwindSafe for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> RefUnwindSafe for UInt<U, B>where
    B: RefUnwindSafe,
    U: RefUnwindSafe,
",1,["typenum::uint::UInt"]],["impl RefUnwindSafe for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> RefUnwindSafe for TArr<V, A>where
    A: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["typenum::array::TArr"]],["impl RefUnwindSafe for Greater",1,["typenum::Greater"]],["impl RefUnwindSafe for Less",1,["typenum::Less"]],["impl RefUnwindSafe for Equal",1,["typenum::Equal"]]], +"uint":[["impl RefUnwindSafe for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl RefUnwindSafe for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl RefUnwindSafe for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl RefUnwindSafe for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js b/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js new file mode 100644 index 000000000000..bd3cf161c3dd --- /dev/null +++ b/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js @@ -0,0 +1,55 @@ +(function() {var implementors = { +"ahash":[["impl UnwindSafe for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl UnwindSafe for RandomState",1,["ahash::random_state::RandomState"]]], +"aho_corasick":[["impl<S> UnwindSafe for AhoCorasick<S>where
    S: UnwindSafe,
",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> UnwindSafe for FindIter<'a, 'b, S>where
    S: RefUnwindSafe,
",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> UnwindSafe for FindOverlappingIter<'a, 'b, S>where
    S: UnwindSafe + RefUnwindSafe,
",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> UnwindSafe for StreamFindIter<'a, R, S>where
    R: UnwindSafe,
    S: UnwindSafe + RefUnwindSafe,
",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl UnwindSafe for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl UnwindSafe for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl UnwindSafe for Error",1,["aho_corasick::error::Error"]],["impl UnwindSafe for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl UnwindSafe for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl UnwindSafe for Config",1,["aho_corasick::packed::api::Config"]],["impl UnwindSafe for Builder",1,["aho_corasick::packed::api::Builder"]],["impl UnwindSafe for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> UnwindSafe for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl UnwindSafe for Match",1,["aho_corasick::Match"]]], +"aiofut":[["impl UnwindSafe for Error",1,["aiofut::Error"]],["impl UnwindSafe for AIO",1,["aiofut::AIO"]],["impl !UnwindSafe for AIOFuture",1,["aiofut::AIOFuture"]],["impl UnwindSafe for AIONotifier",1,["aiofut::AIONotifier"]],["impl UnwindSafe for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !UnwindSafe for AIOManager",1,["aiofut::AIOManager"]],["impl UnwindSafe for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl UnwindSafe for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], +"bincode":[["impl UnwindSafe for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl UnwindSafe for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl UnwindSafe for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl UnwindSafe for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl UnwindSafe for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl UnwindSafe for Config",1,["bincode::config::legacy::Config"]],["impl UnwindSafe for Bounded",1,["bincode::config::limit::Bounded"]],["impl UnwindSafe for Infinite",1,["bincode::config::limit::Infinite"]],["impl UnwindSafe for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl UnwindSafe for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl UnwindSafe for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> UnwindSafe for WithOtherLimit<O, L>where
    L: UnwindSafe,
    O: UnwindSafe,
",1,["bincode::config::WithOtherLimit"]],["impl<O, E> UnwindSafe for WithOtherEndian<O, E>where
    E: UnwindSafe,
    O: UnwindSafe,
",1,["bincode::config::WithOtherEndian"]],["impl<O, I> UnwindSafe for WithOtherIntEncoding<O, I>where
    I: UnwindSafe,
    O: UnwindSafe,
",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> UnwindSafe for WithOtherTrailing<O, T>where
    O: UnwindSafe,
    T: UnwindSafe,
",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> UnwindSafe for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> UnwindSafe for IoReader<R>where
    R: UnwindSafe,
",1,["bincode::de::read::IoReader"]],["impl<R, O> UnwindSafe for Deserializer<R, O>where
    O: UnwindSafe,
    R: UnwindSafe,
",1,["bincode::de::Deserializer"]],["impl !UnwindSafe for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> UnwindSafe for Serializer<W, O>where
    O: UnwindSafe,
    W: UnwindSafe,
",1,["bincode::ser::Serializer"]]], +"block_buffer":[["impl UnwindSafe for Eager",1,["block_buffer::Eager"]],["impl UnwindSafe for Lazy",1,["block_buffer::Lazy"]],["impl UnwindSafe for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> UnwindSafe for BlockBuffer<BlockSize, Kind>where
    Kind: UnwindSafe,
    <BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
",1,["block_buffer::BlockBuffer"]]], +"byteorder":[["impl UnwindSafe for BigEndian",1,["byteorder::BigEndian"]],["impl UnwindSafe for LittleEndian",1,["byteorder::LittleEndian"]]], +"bytes":[["impl<T, U> UnwindSafe for Chain<T, U>where
    T: UnwindSafe,
    U: UnwindSafe,
",1,["bytes::buf::chain::Chain"]],["impl<T> UnwindSafe for IntoIter<T>where
    T: UnwindSafe,
",1,["bytes::buf::iter::IntoIter"]],["impl<T> UnwindSafe for Limit<T>where
    T: UnwindSafe,
",1,["bytes::buf::limit::Limit"]],["impl<B> UnwindSafe for Reader<B>where
    B: UnwindSafe,
",1,["bytes::buf::reader::Reader"]],["impl<T> UnwindSafe for Take<T>where
    T: UnwindSafe,
",1,["bytes::buf::take::Take"]],["impl UnwindSafe for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> UnwindSafe for Writer<B>where
    B: UnwindSafe,
",1,["bytes::buf::writer::Writer"]],["impl UnwindSafe for Bytes",1,["bytes::bytes::Bytes"]],["impl UnwindSafe for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], +"crc":[["impl<W> UnwindSafe for Crc<W>where
    W: UnwindSafe + RefUnwindSafe,
",1,["crc::Crc"]],["impl<'a, W> UnwindSafe for Digest<'a, W>where
    W: UnwindSafe + RefUnwindSafe,
",1,["crc::Digest"]]], +"crc_catalog":[["impl<W> UnwindSafe for Algorithm<W>where
    W: UnwindSafe,
",1,["crc_catalog::Algorithm"]]], +"crossbeam_channel":[["impl<'a, T> UnwindSafe for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> UnwindSafe for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> UnwindSafe for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> UnwindSafe for SendError<T>where
    T: UnwindSafe,
",1,["crossbeam_channel::err::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
    T: UnwindSafe,
",1,["crossbeam_channel::err::TrySendError"]],["impl<T> UnwindSafe for SendTimeoutError<T>where
    T: UnwindSafe,
",1,["crossbeam_channel::err::SendTimeoutError"]],["impl UnwindSafe for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl UnwindSafe for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl UnwindSafe for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl UnwindSafe for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl UnwindSafe for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl UnwindSafe for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl UnwindSafe for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !UnwindSafe for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> UnwindSafe for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T> UnwindSafe for Sender<T>"],["impl<T> UnwindSafe for Receiver<T>"]], +"crossbeam_utils":[["impl<T> UnwindSafe for CachePadded<T>where
    T: UnwindSafe,
",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl UnwindSafe for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl UnwindSafe for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl UnwindSafe for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<'a, T: ?Sized> UnwindSafe for ShardedLockReadGuard<'a, T>where
    T: RefUnwindSafe,
",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> UnwindSafe for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl UnwindSafe for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> !UnwindSafe for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> UnwindSafe for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> UnwindSafe for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]],["impl<T> UnwindSafe for AtomicCell<T>"],["impl<T: ?Sized> UnwindSafe for ShardedLock<T>"]], +"crypto_common":[["impl UnwindSafe for InvalidLength",1,["crypto_common::InvalidLength"]]], +"digest":[["impl<T, OutSize, O> UnwindSafe for CtVariableCoreWrapper<T, OutSize, O>where
    O: UnwindSafe,
    OutSize: UnwindSafe,
    T: UnwindSafe,
",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> UnwindSafe for RtVariableCoreWrapper<T>where
    T: UnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
    <T as BufferKindUser>::BufferKind: UnwindSafe,
",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> UnwindSafe for CoreWrapper<T>where
    T: UnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
    <T as BufferKindUser>::BufferKind: UnwindSafe,
",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> UnwindSafe for XofReaderCoreWrapper<T>where
    T: UnwindSafe,
    <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl UnwindSafe for TruncSide",1,["digest::core_api::TruncSide"]],["impl UnwindSafe for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl UnwindSafe for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], +"firewood":[["impl UnwindSafe for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl UnwindSafe for WALConfig",1,["firewood::storage::WALConfig"]],["impl !UnwindSafe for DBError",1,["firewood::db::DBError"]],["impl UnwindSafe for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl UnwindSafe for DBConfig",1,["firewood::db::DBConfig"]],["impl !UnwindSafe for DBRev",1,["firewood::db::DBRev"]],["impl !UnwindSafe for DB",1,["firewood::db::DB"]],["impl<'a> !UnwindSafe for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !UnwindSafe for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl !UnwindSafe for MerkleError",1,["firewood::merkle::MerkleError"]],["impl UnwindSafe for Hash",1,["firewood::merkle::Hash"]],["impl UnwindSafe for PartialPath",1,["firewood::merkle::PartialPath"]],["impl UnwindSafe for Node",1,["firewood::merkle::Node"]],["impl !UnwindSafe for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !UnwindSafe for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !UnwindSafe for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl UnwindSafe for IdTrans",1,["firewood::merkle::IdTrans"]],["impl UnwindSafe for Proof",1,["firewood::proof::Proof"]],["impl UnwindSafe for ProofError",1,["firewood::proof::ProofError"]],["impl UnwindSafe for SubProof",1,["firewood::proof::SubProof"]]], +"futures_channel":[["impl<T> !UnwindSafe for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> !UnwindSafe for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> !UnwindSafe for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl UnwindSafe for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
    T: UnwindSafe,
",1,["futures_channel::mpsc::TrySendError"]],["impl UnwindSafe for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> !UnwindSafe for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> !UnwindSafe for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl UnwindSafe for Canceled",1,["futures_channel::oneshot::Canceled"]]], +"futures_executor":[["impl !UnwindSafe for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !UnwindSafe for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> UnwindSafe for BlockingStream<S>where
    S: UnwindSafe,
",1,["futures_executor::local_pool::BlockingStream"]],["impl UnwindSafe for Enter",1,["futures_executor::enter::Enter"]],["impl UnwindSafe for EnterError",1,["futures_executor::enter::EnterError"]]], +"futures_task":[["impl UnwindSafe for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> UnwindSafe for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !UnwindSafe for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !UnwindSafe for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], +"futures_util":[["impl<Fut> UnwindSafe for Fuse<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> UnwindSafe for CatchUnwind<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> !UnwindSafe for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> !UnwindSafe for Remote<Fut>",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> !UnwindSafe for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> !UnwindSafe for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> UnwindSafe for Flatten<F>where
    F: UnwindSafe,
    <F as Future>::Output: UnwindSafe,
",1,["futures_util::future::future::Flatten"]],["impl<F> UnwindSafe for FlattenStream<F>where
    F: UnwindSafe,
    <F as Future>::Output: UnwindSafe,
",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> UnwindSafe for Map<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::future::Map"]],["impl<F> UnwindSafe for IntoStream<F>where
    F: UnwindSafe,
",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> UnwindSafe for MapInto<Fut, T>where
    Fut: UnwindSafe,
",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> UnwindSafe for Then<Fut1, Fut2, F>where
    F: UnwindSafe,
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
",1,["futures_util::future::future::Then"]],["impl<Fut, F> UnwindSafe for Inspect<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::future::Inspect"]],["impl<Fut> UnwindSafe for NeverError<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::future::NeverError"]],["impl<Fut> UnwindSafe for UnitError<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::future::UnitError"]],["impl<Fut> UnwindSafe for IntoFuture<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> UnwindSafe for TryFlatten<Fut1, Fut2>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> UnwindSafe for TryFlattenStream<Fut>where
    Fut: UnwindSafe,
    <Fut as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> UnwindSafe for FlattenSink<Fut, Si>where
    Fut: UnwindSafe,
    Si: UnwindSafe,
",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> UnwindSafe for AndThen<Fut1, Fut2, F>where
    F: UnwindSafe,
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> UnwindSafe for OrElse<Fut1, Fut2, F>where
    F: UnwindSafe,
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> UnwindSafe for ErrInto<Fut, E>where
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> UnwindSafe for OkInto<Fut, E>where
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> UnwindSafe for InspectOk<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> UnwindSafe for InspectErr<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> UnwindSafe for MapOk<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> UnwindSafe for MapErr<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> UnwindSafe for MapOkOrElse<Fut, F, G>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    G: UnwindSafe,
",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> UnwindSafe for UnwrapOrElse<Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> UnwindSafe for Lazy<F>where
    F: UnwindSafe,
",1,["futures_util::future::lazy::Lazy"]],["impl<T> UnwindSafe for Pending<T>where
    T: UnwindSafe,
",1,["futures_util::future::pending::Pending"]],["impl<Fut> UnwindSafe for MaybeDone<Fut>where
    Fut: UnwindSafe,
    <Fut as Future>::Output: UnwindSafe,
",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> UnwindSafe for TryMaybeDone<Fut>where
    Fut: UnwindSafe,
    <Fut as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> UnwindSafe for OptionFuture<F>where
    F: UnwindSafe,
",1,["futures_util::future::option::OptionFuture"]],["impl<F> UnwindSafe for PollFn<F>where
    F: UnwindSafe,
",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> UnwindSafe for PollImmediate<T>where
    T: UnwindSafe,
",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> UnwindSafe for Ready<T>where
    T: UnwindSafe,
",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> UnwindSafe for Join<Fut1, Fut2>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    <Fut1 as Future>::Output: UnwindSafe,
    <Fut2 as Future>::Output: UnwindSafe,
",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> UnwindSafe for Join3<Fut1, Fut2, Fut3>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    <Fut1 as Future>::Output: UnwindSafe,
    <Fut2 as Future>::Output: UnwindSafe,
    <Fut3 as Future>::Output: UnwindSafe,
",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> UnwindSafe for Join4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    Fut4: UnwindSafe,
    <Fut1 as Future>::Output: UnwindSafe,
    <Fut2 as Future>::Output: UnwindSafe,
    <Fut3 as Future>::Output: UnwindSafe,
    <Fut4 as Future>::Output: UnwindSafe,
",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> UnwindSafe for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    Fut4: UnwindSafe,
    Fut5: UnwindSafe,
    <Fut1 as Future>::Output: UnwindSafe,
    <Fut2 as Future>::Output: UnwindSafe,
    <Fut3 as Future>::Output: UnwindSafe,
    <Fut4 as Future>::Output: UnwindSafe,
    <Fut5 as Future>::Output: UnwindSafe,
",1,["futures_util::future::join::Join5"]],["impl<F> !UnwindSafe for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> UnwindSafe for Select<A, B>where
    A: UnwindSafe,
    B: UnwindSafe,
",1,["futures_util::future::select::Select"]],["impl<Fut> UnwindSafe for SelectAll<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> UnwindSafe for TryJoin<Fut1, Fut2>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    <Fut1 as TryFuture>::Ok: UnwindSafe,
    <Fut2 as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> UnwindSafe for TryJoin3<Fut1, Fut2, Fut3>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    <Fut1 as TryFuture>::Ok: UnwindSafe,
    <Fut2 as TryFuture>::Ok: UnwindSafe,
    <Fut3 as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> UnwindSafe for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    Fut4: UnwindSafe,
    <Fut1 as TryFuture>::Ok: UnwindSafe,
    <Fut2 as TryFuture>::Ok: UnwindSafe,
    <Fut3 as TryFuture>::Ok: UnwindSafe,
    <Fut4 as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> UnwindSafe for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
    Fut1: UnwindSafe,
    Fut2: UnwindSafe,
    Fut3: UnwindSafe,
    Fut4: UnwindSafe,
    Fut5: UnwindSafe,
    <Fut1 as TryFuture>::Ok: UnwindSafe,
    <Fut2 as TryFuture>::Ok: UnwindSafe,
    <Fut3 as TryFuture>::Ok: UnwindSafe,
    <Fut4 as TryFuture>::Ok: UnwindSafe,
    <Fut5 as TryFuture>::Ok: UnwindSafe,
",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !UnwindSafe for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> UnwindSafe for TrySelect<A, B>where
    A: UnwindSafe,
    B: UnwindSafe,
",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> UnwindSafe for SelectOk<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> UnwindSafe for Either<A, B>where
    A: UnwindSafe,
    B: UnwindSafe,
",1,["futures_util::future::either::Either"]],["impl !UnwindSafe for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl !UnwindSafe for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> !UnwindSafe for Abortable<T>",1,["futures_util::abortable::Abortable"]],["impl UnwindSafe for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> UnwindSafe for Chain<St1, St2>where
    St1: UnwindSafe,
    St2: UnwindSafe,
",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> UnwindSafe for Collect<St, C>where
    C: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> UnwindSafe for Unzip<St, FromA, FromB>where
    FromA: UnwindSafe,
    FromB: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> UnwindSafe for Concat<St>where
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> UnwindSafe for Cycle<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> UnwindSafe for Enumerate<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> UnwindSafe for Filter<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> UnwindSafe for FilterMap<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> UnwindSafe for Fold<St, Fut, T, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> UnwindSafe for ForEach<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> UnwindSafe for Fuse<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> UnwindSafe for StreamFuture<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> UnwindSafe for Map<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St> !UnwindSafe for Next<'a, St>",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St> !UnwindSafe for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> UnwindSafe for Peekable<St>where
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> !UnwindSafe for Peek<'a, St>",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> !UnwindSafe for PeekMut<'a, St>",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> !UnwindSafe for NextIf<'a, St, F>",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T> !UnwindSafe for NextIfEq<'a, St, T>",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> UnwindSafe for Skip<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> UnwindSafe for SkipWhile<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> UnwindSafe for Take<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> UnwindSafe for TakeWhile<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> UnwindSafe for TakeUntil<St, Fut>where
    Fut: UnwindSafe,
    St: UnwindSafe,
    <Fut as Future>::Output: UnwindSafe,
",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> UnwindSafe for Then<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> UnwindSafe for Zip<St1, St2>where
    St1: UnwindSafe,
    St2: UnwindSafe,
    <St1 as Stream>::Item: UnwindSafe,
    <St2 as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> UnwindSafe for Chunks<St>where
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> UnwindSafe for ReadyChunks<St>where
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> UnwindSafe for Scan<St, S, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    S: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !UnwindSafe for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !UnwindSafe for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !UnwindSafe for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> !UnwindSafe for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> !UnwindSafe for SplitSink<S, Item>",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> !UnwindSafe for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> UnwindSafe for CatchUnwind<St>where
    St: UnwindSafe,
",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> UnwindSafe for Flatten<St>where
    St: UnwindSafe,
    <St as Stream>::Item: UnwindSafe,
",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> UnwindSafe for Forward<St, Si>where
    Si: UnwindSafe,
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::stream::Forward"]],["impl<St, F> UnwindSafe for Inspect<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> UnwindSafe for FlatMap<St, U, F>where
    F: UnwindSafe,
    St: UnwindSafe,
    U: UnwindSafe,
",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> UnwindSafe for AndThen<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> UnwindSafe for IntoStream<St>where
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> UnwindSafe for OrElse<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St> !UnwindSafe for TryNext<'a, St>",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> UnwindSafe for TryForEach<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> UnwindSafe for TryFilter<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> UnwindSafe for TryFilterMap<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> UnwindSafe for TryFlatten<St>where
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> UnwindSafe for TryCollect<St, C>where
    C: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> UnwindSafe for TryConcat<St>where
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> UnwindSafe for TryChunks<St>where
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> UnwindSafe for TryChunksError<T, E>where
    E: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> UnwindSafe for TryFold<St, Fut, T, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> UnwindSafe for TryUnfold<T, F, Fut>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> UnwindSafe for TrySkipWhile<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> UnwindSafe for TryTakeWhile<St, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !UnwindSafe for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !UnwindSafe for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !UnwindSafe for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> UnwindSafe for IntoAsyncRead<St>where
    St: UnwindSafe,
    <St as TryStream>::Ok: UnwindSafe,
",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> UnwindSafe for ErrInto<St, E>where
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> UnwindSafe for InspectOk<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> UnwindSafe for InspectErr<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> UnwindSafe for MapOk<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> UnwindSafe for MapErr<St, F>where
    F: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> UnwindSafe for Iter<I>where
    I: UnwindSafe,
",1,["futures_util::stream::iter::Iter"]],["impl<T> UnwindSafe for Repeat<T>where
    T: UnwindSafe,
",1,["futures_util::stream::repeat::Repeat"]],["impl<F> UnwindSafe for RepeatWith<F>where
    F: UnwindSafe,
",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> UnwindSafe for Empty<T>where
    T: UnwindSafe,
",1,["futures_util::stream::empty::Empty"]],["impl<Fut> UnwindSafe for Once<Fut>where
    Fut: UnwindSafe,
",1,["futures_util::stream::once::Once"]],["impl<T> UnwindSafe for Pending<T>where
    T: UnwindSafe,
",1,["futures_util::stream::pending::Pending"]],["impl<F> UnwindSafe for PollFn<F>where
    F: UnwindSafe,
",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> UnwindSafe for PollImmediate<S>where
    S: UnwindSafe,
",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> UnwindSafe for Select<St1, St2>where
    St1: UnwindSafe,
    St2: UnwindSafe,
",1,["futures_util::stream::select::Select"]],["impl UnwindSafe for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> UnwindSafe for SelectWithStrategy<St1, St2, Clos, State>where
    Clos: UnwindSafe,
    St1: UnwindSafe,
    St2: UnwindSafe,
    State: UnwindSafe,
",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> UnwindSafe for Unfold<T, F, Fut>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !UnwindSafe for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> !UnwindSafe for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> !UnwindSafe for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> !UnwindSafe for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> !UnwindSafe for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !UnwindSafe for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !UnwindSafe for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !UnwindSafe for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> !UnwindSafe for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> !UnwindSafe for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !UnwindSafe for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si, Item> !UnwindSafe for Close<'a, Si, Item>",1,["futures_util::sink::close::Close"]],["impl<T> UnwindSafe for Drain<T>where
    T: UnwindSafe,
",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> UnwindSafe for Fanout<Si1, Si2>where
    Si1: UnwindSafe,
    Si2: UnwindSafe,
",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si, Item> !UnwindSafe for Feed<'a, Si, Item>",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si, Item> !UnwindSafe for Flush<'a, Si, Item>",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> UnwindSafe for SinkErrInto<Si, Item, E>where
    Si: UnwindSafe,
",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> UnwindSafe for SinkMapErr<Si, F>where
    F: UnwindSafe,
    Si: UnwindSafe,
",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si, Item> !UnwindSafe for Send<'a, Si, Item>",1,["futures_util::sink::send::Send"]],["impl<'a, Si, St> !UnwindSafe for SendAll<'a, Si, St>",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> UnwindSafe for Unfold<T, F, R>where
    F: UnwindSafe,
    R: UnwindSafe,
    T: UnwindSafe,
",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> UnwindSafe for With<Si, Item, U, Fut, F>where
    F: UnwindSafe,
    Fut: UnwindSafe,
    Si: UnwindSafe,
",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> UnwindSafe for WithFlatMap<Si, Item, U, St, F>where
    F: UnwindSafe,
    Item: UnwindSafe,
    Si: UnwindSafe,
    St: UnwindSafe,
",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> UnwindSafe for Buffer<Si, Item>where
    Item: UnwindSafe,
    Si: UnwindSafe,
",1,["futures_util::sink::buffer::Buffer"]],["impl<T> UnwindSafe for AllowStdIo<T>where
    T: UnwindSafe,
",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> UnwindSafe for BufReader<R>where
    R: UnwindSafe,
",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> !UnwindSafe for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> UnwindSafe for BufWriter<W>where
    W: UnwindSafe,
",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> UnwindSafe for LineWriter<W>where
    W: UnwindSafe,
",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> UnwindSafe for Chain<T, U>where
    T: UnwindSafe,
    U: UnwindSafe,
",1,["futures_util::io::chain::Chain"]],["impl<'a, W> !UnwindSafe for Close<'a, W>",1,["futures_util::io::close::Close"]],["impl<'a, R, W> !UnwindSafe for Copy<'a, R, W>",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W> !UnwindSafe for CopyBuf<'a, R, W>",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W> !UnwindSafe for CopyBufAbortable<'a, R, W>",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> UnwindSafe for Cursor<T>where
    T: UnwindSafe,
",1,["futures_util::io::cursor::Cursor"]],["impl UnwindSafe for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R> !UnwindSafe for FillBuf<'a, R>",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W> !UnwindSafe for Flush<'a, W>",1,["futures_util::io::flush::Flush"]],["impl<W, Item> UnwindSafe for IntoSink<W, Item>where
    Item: UnwindSafe,
    W: UnwindSafe,
",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> UnwindSafe for Lines<R>where
    R: UnwindSafe,
",1,["futures_util::io::lines::Lines"]],["impl<'a, R> !UnwindSafe for Read<'a, R>",1,["futures_util::io::read::Read"]],["impl<'a, R> !UnwindSafe for ReadVectored<'a, R>",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R> !UnwindSafe for ReadExact<'a, R>",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R> !UnwindSafe for ReadLine<'a, R>",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R> !UnwindSafe for ReadToEnd<'a, R>",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R> !UnwindSafe for ReadToString<'a, R>",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R> !UnwindSafe for ReadUntil<'a, R>",1,["futures_util::io::read_until::ReadUntil"]],["impl UnwindSafe for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S> !UnwindSafe for Seek<'a, S>",1,["futures_util::io::seek::Seek"]],["impl UnwindSafe for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> !UnwindSafe for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> !UnwindSafe for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> !UnwindSafe for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> UnwindSafe for Take<R>where
    R: UnwindSafe,
",1,["futures_util::io::take::Take"]],["impl<T> UnwindSafe for Window<T>where
    T: UnwindSafe,
",1,["futures_util::io::window::Window"]],["impl<'a, W> !UnwindSafe for Write<'a, W>",1,["futures_util::io::write::Write"]],["impl<'a, W> !UnwindSafe for WriteVectored<'a, W>",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W> !UnwindSafe for WriteAll<'a, W>",1,["futures_util::io::write_all::WriteAll"]],["impl<T: ?Sized> UnwindSafe for Mutex<T>where
    T: UnwindSafe,
",1,["futures_util::lock::mutex::Mutex"]],["impl<T> !UnwindSafe for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T> !UnwindSafe for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T> !UnwindSafe for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T> !UnwindSafe for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T, U> !UnwindSafe for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], +"generic_array":[["impl<T, N> UnwindSafe for GenericArrayIter<T, N>where
    <N as ArrayLength<T>>::ArrayType: UnwindSafe,
",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> UnwindSafe for GenericArray<T, U>where
    <U as ArrayLength<T>>::ArrayType: UnwindSafe,
",1,["generic_array::GenericArray"]]], +"getrandom":[["impl UnwindSafe for Error",1,["getrandom::error::Error"]]], +"growthring":[["impl UnwindSafe for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !UnwindSafe for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl UnwindSafe for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl UnwindSafe for WALLoader",1,["growthring::wal::WALLoader"]],["impl !UnwindSafe for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !UnwindSafe for WALStoreAIO",1,["growthring::WALStoreAIO"]]], +"hashbrown":[["impl<K, V, S, A> UnwindSafe for HashMap<K, V, S, A>where
    A: UnwindSafe,
    K: UnwindSafe,
    S: UnwindSafe,
    V: UnwindSafe,
",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> UnwindSafe for Iter<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Iter"]],["impl<'a, K, V> !UnwindSafe for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> UnwindSafe for IntoIter<K, V, A>where
    A: UnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> UnwindSafe for IntoKeys<K, V, A>where
    A: UnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> UnwindSafe for IntoValues<K, V, A>where
    A: UnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> UnwindSafe for Keys<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Keys"]],["impl<'a, K, V> UnwindSafe for Values<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> UnwindSafe for Drain<'a, K, V, A>where
    A: UnwindSafe + RefUnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A = Global> !UnwindSafe for DrainFilter<'a, K, V, F, A>",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> !UnwindSafe for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> UnwindSafe for RawEntryBuilder<'a, K, V, S, A>where
    A: RefUnwindSafe,
    K: RefUnwindSafe,
    S: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for Entry<'a, K, V, S, A>",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for OccupiedEntry<'a, K, V, S, A>",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for VacantEntry<'a, K, V, S, A>",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for EntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for VacantEntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for OccupiedError<'a, K, V, S, A>",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> UnwindSafe for HashSet<T, S, A>where
    A: UnwindSafe,
    S: UnwindSafe,
    T: UnwindSafe,
",1,["hashbrown::set::HashSet"]],["impl<'a, K> UnwindSafe for Iter<'a, K>where
    K: RefUnwindSafe,
",1,["hashbrown::set::Iter"]],["impl<K, A> UnwindSafe for IntoIter<K, A>where
    A: UnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> UnwindSafe for Drain<'a, K, A>where
    A: UnwindSafe + RefUnwindSafe,
    K: UnwindSafe + RefUnwindSafe,
",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A = Global> !UnwindSafe for DrainFilter<'a, K, F, A>",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> UnwindSafe for Intersection<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> UnwindSafe for Difference<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> UnwindSafe for SymmetricDifference<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> UnwindSafe for Union<'a, T, S, A>where
    A: RefUnwindSafe,
    S: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["hashbrown::set::Union"]],["impl<'a, T, S, A = Global> !UnwindSafe for Entry<'a, T, S, A>",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A = Global> !UnwindSafe for OccupiedEntry<'a, T, S, A>",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A = Global> !UnwindSafe for VacantEntry<'a, T, S, A>",1,["hashbrown::set::VacantEntry"]],["impl UnwindSafe for TryReserveError",1,["hashbrown::TryReserveError"]]], +"heck":[["impl<T> UnwindSafe for AsKebabCase<T>where
    T: UnwindSafe,
",1,["heck::kebab::AsKebabCase"]],["impl<T> UnwindSafe for AsLowerCamelCase<T>where
    T: UnwindSafe,
",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> UnwindSafe for AsShoutyKebabCase<T>where
    T: UnwindSafe,
",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> UnwindSafe for AsShoutySnakeCase<T>where
    T: UnwindSafe,
",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> UnwindSafe for AsSnakeCase<T>where
    T: UnwindSafe,
",1,["heck::snake::AsSnakeCase"]],["impl<T> UnwindSafe for AsTitleCase<T>where
    T: UnwindSafe,
",1,["heck::title::AsTitleCase"]],["impl<T> UnwindSafe for AsUpperCamelCase<T>where
    T: UnwindSafe,
",1,["heck::upper_camel::AsUpperCamelCase"]]], +"hex":[["impl UnwindSafe for FromHexError",1,["hex::error::FromHexError"]]], +"libc":[["impl UnwindSafe for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl UnwindSafe for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl UnwindSafe for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl UnwindSafe for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl UnwindSafe for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl UnwindSafe for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl UnwindSafe for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl UnwindSafe for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl UnwindSafe for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl UnwindSafe for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl UnwindSafe for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl UnwindSafe for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl UnwindSafe for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl UnwindSafe for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl UnwindSafe for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl UnwindSafe for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl UnwindSafe for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl UnwindSafe for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl UnwindSafe for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl UnwindSafe for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl UnwindSafe for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl UnwindSafe for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl UnwindSafe for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl UnwindSafe for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl UnwindSafe for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl UnwindSafe for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl UnwindSafe for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl UnwindSafe for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl UnwindSafe for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl UnwindSafe for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl UnwindSafe for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl UnwindSafe for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl UnwindSafe for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl UnwindSafe for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl UnwindSafe for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl UnwindSafe for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl UnwindSafe for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl UnwindSafe for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl UnwindSafe for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl UnwindSafe for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl UnwindSafe for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl UnwindSafe for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl UnwindSafe for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl UnwindSafe for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl UnwindSafe for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl UnwindSafe for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl UnwindSafe for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl UnwindSafe for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl UnwindSafe for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl UnwindSafe for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl UnwindSafe for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl UnwindSafe for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl UnwindSafe for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl UnwindSafe for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl UnwindSafe for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl UnwindSafe for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl UnwindSafe for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl UnwindSafe for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl UnwindSafe for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl UnwindSafe for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl UnwindSafe for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl UnwindSafe for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl UnwindSafe for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl UnwindSafe for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl UnwindSafe for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl UnwindSafe for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl UnwindSafe for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl UnwindSafe for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl UnwindSafe for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl UnwindSafe for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl UnwindSafe for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl UnwindSafe for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl UnwindSafe for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl UnwindSafe for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl UnwindSafe for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl UnwindSafe for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl UnwindSafe for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl UnwindSafe for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl UnwindSafe for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl UnwindSafe for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl UnwindSafe for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl UnwindSafe for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl UnwindSafe for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl UnwindSafe for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl UnwindSafe for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl UnwindSafe for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl UnwindSafe for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl UnwindSafe for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl UnwindSafe for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl UnwindSafe for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl UnwindSafe for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl UnwindSafe for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl UnwindSafe for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl UnwindSafe for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl UnwindSafe for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl UnwindSafe for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl UnwindSafe for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl UnwindSafe for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl UnwindSafe for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl UnwindSafe for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl UnwindSafe for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl UnwindSafe for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl UnwindSafe for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl UnwindSafe for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl UnwindSafe for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl UnwindSafe for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl UnwindSafe for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl UnwindSafe for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl UnwindSafe for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl UnwindSafe for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl UnwindSafe for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl UnwindSafe for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl UnwindSafe for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl UnwindSafe for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl UnwindSafe for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl UnwindSafe for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl UnwindSafe for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl UnwindSafe for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl UnwindSafe for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl UnwindSafe for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl UnwindSafe for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl UnwindSafe for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl UnwindSafe for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl UnwindSafe for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl UnwindSafe for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl UnwindSafe for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl UnwindSafe for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl UnwindSafe for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl UnwindSafe for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl UnwindSafe for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl UnwindSafe for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl UnwindSafe for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl UnwindSafe for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl UnwindSafe for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl UnwindSafe for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl UnwindSafe for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl UnwindSafe for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl UnwindSafe for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl UnwindSafe for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl UnwindSafe for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl UnwindSafe for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl UnwindSafe for timezone",1,["libc::unix::linux_like::timezone"]],["impl UnwindSafe for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl UnwindSafe for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl UnwindSafe for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl UnwindSafe for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl UnwindSafe for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl UnwindSafe for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl UnwindSafe for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl UnwindSafe for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl UnwindSafe for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl UnwindSafe for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl UnwindSafe for tm",1,["libc::unix::linux_like::tm"]],["impl UnwindSafe for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl UnwindSafe for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl UnwindSafe for lconv",1,["libc::unix::linux_like::lconv"]],["impl UnwindSafe for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl UnwindSafe for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl UnwindSafe for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl UnwindSafe for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl UnwindSafe for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl UnwindSafe for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl UnwindSafe for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl UnwindSafe for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl UnwindSafe for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl UnwindSafe for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl UnwindSafe for utsname",1,["libc::unix::linux_like::utsname"]],["impl UnwindSafe for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl UnwindSafe for in6_addr",1,["libc::unix::align::in6_addr"]],["impl UnwindSafe for DIR",1,["libc::unix::DIR"]],["impl UnwindSafe for group",1,["libc::unix::group"]],["impl UnwindSafe for utimbuf",1,["libc::unix::utimbuf"]],["impl UnwindSafe for timeval",1,["libc::unix::timeval"]],["impl UnwindSafe for timespec",1,["libc::unix::timespec"]],["impl UnwindSafe for rlimit",1,["libc::unix::rlimit"]],["impl UnwindSafe for rusage",1,["libc::unix::rusage"]],["impl UnwindSafe for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl UnwindSafe for hostent",1,["libc::unix::hostent"]],["impl UnwindSafe for iovec",1,["libc::unix::iovec"]],["impl UnwindSafe for pollfd",1,["libc::unix::pollfd"]],["impl UnwindSafe for winsize",1,["libc::unix::winsize"]],["impl UnwindSafe for linger",1,["libc::unix::linger"]],["impl UnwindSafe for sigval",1,["libc::unix::sigval"]],["impl UnwindSafe for itimerval",1,["libc::unix::itimerval"]],["impl UnwindSafe for tms",1,["libc::unix::tms"]],["impl UnwindSafe for servent",1,["libc::unix::servent"]],["impl UnwindSafe for protoent",1,["libc::unix::protoent"]],["impl UnwindSafe for FILE",1,["libc::unix::FILE"]],["impl UnwindSafe for fpos_t",1,["libc::unix::fpos_t"]]], +"lock_api":[["impl<R, T: ?Sized> UnwindSafe for Mutex<R, T>where
    R: UnwindSafe,
    T: UnwindSafe,
",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T> !UnwindSafe for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T> !UnwindSafe for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> UnwindSafe for RawReentrantMutex<R, G>where
    G: UnwindSafe,
    R: UnwindSafe,
",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T: ?Sized> UnwindSafe for ReentrantMutex<R, G, T>where
    G: UnwindSafe,
    R: UnwindSafe,
    T: UnwindSafe,
",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T> !UnwindSafe for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !UnwindSafe for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T: ?Sized> UnwindSafe for RwLock<R, T>where
    R: UnwindSafe,
    T: UnwindSafe,
",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T> !UnwindSafe for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T> !UnwindSafe for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T> !UnwindSafe for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> UnwindSafe for MappedRwLockReadGuard<'a, R, T>where
    R: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T> !UnwindSafe for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl UnwindSafe for GuardSend",1,["lock_api::GuardSend"]],["impl UnwindSafe for GuardNoSend",1,["lock_api::GuardNoSend"]]], +"lru":[["impl<K, V, S> UnwindSafe for LruCache<K, V, S>where
    K: UnwindSafe + RefUnwindSafe,
    S: UnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["lru::LruCache"]],["impl<'a, K, V> UnwindSafe for Iter<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::Iter"]],["impl<'a, K, V> UnwindSafe for IterMut<'a, K, V>where
    K: RefUnwindSafe,
    V: RefUnwindSafe,
",1,["lru::IterMut"]],["impl<K, V> UnwindSafe for IntoIter<K, V>where
    K: UnwindSafe + RefUnwindSafe,
    V: UnwindSafe + RefUnwindSafe,
",1,["lru::IntoIter"]]], +"memchr":[["impl<'a> UnwindSafe for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> UnwindSafe for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> UnwindSafe for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl UnwindSafe for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> UnwindSafe for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> UnwindSafe for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> UnwindSafe for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> UnwindSafe for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl UnwindSafe for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], +"nix":[["impl UnwindSafe for Dir",1,["nix::dir::Dir"]],["impl<'d> !UnwindSafe for Iter<'d>",1,["nix::dir::Iter"]],["impl UnwindSafe for OwningIter",1,["nix::dir::OwningIter"]],["impl UnwindSafe for Entry",1,["nix::dir::Entry"]],["impl UnwindSafe for Type",1,["nix::dir::Type"]],["impl UnwindSafe for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl UnwindSafe for Errno",1,["nix::errno::consts::Errno"]],["impl UnwindSafe for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl UnwindSafe for AtFlags",1,["nix::fcntl::AtFlags"]],["impl UnwindSafe for OFlag",1,["nix::fcntl::OFlag"]],["impl UnwindSafe for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl UnwindSafe for SealFlag",1,["nix::fcntl::SealFlag"]],["impl UnwindSafe for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> !UnwindSafe for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl UnwindSafe for FlockArg",1,["nix::fcntl::FlockArg"]],["impl UnwindSafe for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl UnwindSafe for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl UnwindSafe for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl UnwindSafe for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl UnwindSafe for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl UnwindSafe for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> UnwindSafe for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl UnwindSafe for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl UnwindSafe for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl UnwindSafe for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl UnwindSafe for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl UnwindSafe for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl UnwindSafe for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl UnwindSafe for MqAttr",1,["nix::mqueue::MqAttr"]],["impl UnwindSafe for MqdT",1,["nix::mqueue::MqdT"]],["impl UnwindSafe for PollFd",1,["nix::poll::PollFd"]],["impl UnwindSafe for PollFlags",1,["nix::poll::PollFlags"]],["impl UnwindSafe for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl UnwindSafe for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl UnwindSafe for PtyMaster",1,["nix::pty::PtyMaster"]],["impl UnwindSafe for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl UnwindSafe for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl UnwindSafe for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl UnwindSafe for LioMode",1,["nix::sys::aio::LioMode"]],["impl UnwindSafe for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl UnwindSafe for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> UnwindSafe for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> UnwindSafe for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl UnwindSafe for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl UnwindSafe for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl UnwindSafe for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl UnwindSafe for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl UnwindSafe for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl UnwindSafe for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl UnwindSafe for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl UnwindSafe for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl UnwindSafe for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl UnwindSafe for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl UnwindSafe for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl UnwindSafe for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl UnwindSafe for Persona",1,["nix::sys::personality::Persona"]],["impl UnwindSafe for Request",1,["nix::sys::ptrace::linux::Request"]],["impl UnwindSafe for Event",1,["nix::sys::ptrace::linux::Event"]],["impl UnwindSafe for Options",1,["nix::sys::ptrace::linux::Options"]],["impl UnwindSafe for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl UnwindSafe for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl UnwindSafe for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl UnwindSafe for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl UnwindSafe for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl UnwindSafe for Resource",1,["nix::sys::resource::Resource"]],["impl UnwindSafe for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl UnwindSafe for Usage",1,["nix::sys::resource::Usage"]],["impl UnwindSafe for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> UnwindSafe for Fds<'a>",1,["nix::sys::select::Fds"]],["impl UnwindSafe for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl UnwindSafe for Signal",1,["nix::sys::signal::Signal"]],["impl UnwindSafe for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl UnwindSafe for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl UnwindSafe for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl UnwindSafe for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> UnwindSafe for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl UnwindSafe for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl UnwindSafe for SigAction",1,["nix::sys::signal::SigAction"]],["impl UnwindSafe for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl UnwindSafe for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl UnwindSafe for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl UnwindSafe for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl UnwindSafe for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl UnwindSafe for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl UnwindSafe for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl UnwindSafe for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl UnwindSafe for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl UnwindSafe for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl UnwindSafe for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl UnwindSafe for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl UnwindSafe for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl UnwindSafe for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl UnwindSafe for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl UnwindSafe for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl UnwindSafe for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl UnwindSafe for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl UnwindSafe for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl UnwindSafe for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl UnwindSafe for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl UnwindSafe for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl UnwindSafe for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl UnwindSafe for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl UnwindSafe for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl UnwindSafe for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl UnwindSafe for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl UnwindSafe for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl UnwindSafe for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl UnwindSafe for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl UnwindSafe for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl UnwindSafe for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl UnwindSafe for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl UnwindSafe for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl UnwindSafe for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl UnwindSafe for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl UnwindSafe for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl UnwindSafe for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl UnwindSafe for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl UnwindSafe for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl UnwindSafe for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl UnwindSafe for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl UnwindSafe for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl UnwindSafe for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl UnwindSafe for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl UnwindSafe for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl UnwindSafe for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl UnwindSafe for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl UnwindSafe for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl UnwindSafe for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl UnwindSafe for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl UnwindSafe for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl UnwindSafe for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl UnwindSafe for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl UnwindSafe for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl UnwindSafe for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl UnwindSafe for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl UnwindSafe for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl UnwindSafe for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl UnwindSafe for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl UnwindSafe for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl UnwindSafe for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl UnwindSafe for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl UnwindSafe for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl UnwindSafe for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl UnwindSafe for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl UnwindSafe for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl UnwindSafe for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl UnwindSafe for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl UnwindSafe for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl UnwindSafe for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl UnwindSafe for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl UnwindSafe for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl UnwindSafe for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl UnwindSafe for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl UnwindSafe for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl UnwindSafe for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> UnwindSafe for AlgSetKey<T>where
    T: UnwindSafe,
",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl UnwindSafe for SockType",1,["nix::sys::socket::SockType"]],["impl UnwindSafe for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl UnwindSafe for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl UnwindSafe for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl UnwindSafe for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl UnwindSafe for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl UnwindSafe for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl UnwindSafe for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> UnwindSafe for RecvMsg<'a, 's, S>where
    S: UnwindSafe,
",1,["nix::sys::socket::RecvMsg"]],["impl<'a> UnwindSafe for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl UnwindSafe for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl UnwindSafe for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> UnwindSafe for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> UnwindSafe for MultiHeaders<S>where
    S: UnwindSafe,
",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> UnwindSafe for MultiResults<'a, S>where
    S: RefUnwindSafe,
",1,["nix::sys::socket::MultiResults"]],["impl<'a> UnwindSafe for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl UnwindSafe for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl UnwindSafe for SFlag",1,["nix::sys::stat::SFlag"]],["impl UnwindSafe for Mode",1,["nix::sys::stat::Mode"]],["impl UnwindSafe for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl UnwindSafe for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl UnwindSafe for Statfs",1,["nix::sys::statfs::Statfs"]],["impl UnwindSafe for FsType",1,["nix::sys::statfs::FsType"]],["impl UnwindSafe for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl UnwindSafe for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl UnwindSafe for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl UnwindSafe for Termios",1,["nix::sys::termios::Termios"]],["impl UnwindSafe for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl UnwindSafe for SetArg",1,["nix::sys::termios::SetArg"]],["impl UnwindSafe for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl UnwindSafe for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl UnwindSafe for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl UnwindSafe for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl UnwindSafe for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl UnwindSafe for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl UnwindSafe for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl UnwindSafe for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl UnwindSafe for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl UnwindSafe for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl UnwindSafe for TimeVal",1,["nix::sys::time::TimeVal"]],["impl UnwindSafe for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> UnwindSafe for IoVec<T>where
    T: UnwindSafe,
",1,["nix::sys::uio::IoVec"]],["impl UnwindSafe for UtsName",1,["nix::sys::utsname::UtsName"]],["impl UnwindSafe for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl UnwindSafe for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl UnwindSafe for Id",1,["nix::sys::wait::Id"]],["impl UnwindSafe for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl UnwindSafe for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl UnwindSafe for Inotify",1,["nix::sys::inotify::Inotify"]],["impl UnwindSafe for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl UnwindSafe for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl UnwindSafe for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl UnwindSafe for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl UnwindSafe for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl UnwindSafe for Timer",1,["nix::sys::timer::Timer"]],["impl UnwindSafe for ClockId",1,["nix::time::ClockId"]],["impl UnwindSafe for UContext",1,["nix::ucontext::UContext"]],["impl UnwindSafe for ResUid",1,["nix::unistd::getres::ResUid"]],["impl UnwindSafe for ResGid",1,["nix::unistd::getres::ResGid"]],["impl UnwindSafe for Uid",1,["nix::unistd::Uid"]],["impl UnwindSafe for Gid",1,["nix::unistd::Gid"]],["impl UnwindSafe for Pid",1,["nix::unistd::Pid"]],["impl UnwindSafe for ForkResult",1,["nix::unistd::ForkResult"]],["impl UnwindSafe for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl UnwindSafe for Whence",1,["nix::unistd::Whence"]],["impl UnwindSafe for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl UnwindSafe for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl UnwindSafe for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl UnwindSafe for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl UnwindSafe for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl UnwindSafe for User",1,["nix::unistd::User"]],["impl UnwindSafe for Group",1,["nix::unistd::Group"]]], +"once_cell":[["impl<T, F> UnwindSafe for Lazy<T, F>where
    F: UnwindSafe,
    T: UnwindSafe,
",1,["once_cell::unsync::Lazy"]],["impl<T> UnwindSafe for OnceCell<T>where
    T: UnwindSafe,
",1,["once_cell::sync::OnceCell"]],["impl<T, F> UnwindSafe for Lazy<T, F>where
    F: UnwindSafe,
    T: UnwindSafe,
",1,["once_cell::sync::Lazy"]],["impl<T> UnwindSafe for OnceBox<T>where
    T: UnwindSafe + RefUnwindSafe,
",1,["once_cell::race::once_box::OnceBox"]],["impl UnwindSafe for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl UnwindSafe for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T: UnwindSafe> UnwindSafe for OnceCell<T>"]], +"parking_lot":[["impl UnwindSafe for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl UnwindSafe for Condvar",1,["parking_lot::condvar::Condvar"]],["impl UnwindSafe for OnceState",1,["parking_lot::once::OnceState"]],["impl UnwindSafe for Once",1,["parking_lot::once::Once"]],["impl UnwindSafe for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl UnwindSafe for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl UnwindSafe for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl UnwindSafe for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], +"parking_lot_core":[["impl UnwindSafe for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl UnwindSafe for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl UnwindSafe for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl UnwindSafe for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl UnwindSafe for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl UnwindSafe for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl UnwindSafe for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], +"ppv_lite86":[["impl UnwindSafe for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl UnwindSafe for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl UnwindSafe for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl UnwindSafe for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl UnwindSafe for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl UnwindSafe for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl UnwindSafe for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl UnwindSafe for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl UnwindSafe for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl UnwindSafe for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> UnwindSafe for SseMachine<S3, S4, NI>where
    NI: UnwindSafe,
    S3: UnwindSafe,
    S4: UnwindSafe,
",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> UnwindSafe for Avx2Machine<NI>where
    NI: UnwindSafe,
",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl UnwindSafe for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl UnwindSafe for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl UnwindSafe for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], +"primitive_types":[["impl UnwindSafe for Error",1,["primitive_types::Error"]],["impl UnwindSafe for U128",1,["primitive_types::U128"]],["impl UnwindSafe for U256",1,["primitive_types::U256"]],["impl UnwindSafe for U512",1,["primitive_types::U512"]],["impl UnwindSafe for H128",1,["primitive_types::H128"]],["impl UnwindSafe for H160",1,["primitive_types::H160"]],["impl UnwindSafe for H256",1,["primitive_types::H256"]],["impl UnwindSafe for H384",1,["primitive_types::H384"]],["impl UnwindSafe for H512",1,["primitive_types::H512"]],["impl UnwindSafe for H768",1,["primitive_types::H768"]]], +"proc_macro2":[["impl UnwindSafe for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl UnwindSafe for TokenStream",1,["proc_macro2::TokenStream"]],["impl UnwindSafe for LexError",1,["proc_macro2::LexError"]],["impl UnwindSafe for Span",1,["proc_macro2::Span"]],["impl UnwindSafe for TokenTree",1,["proc_macro2::TokenTree"]],["impl UnwindSafe for Group",1,["proc_macro2::Group"]],["impl UnwindSafe for Delimiter",1,["proc_macro2::Delimiter"]],["impl UnwindSafe for Punct",1,["proc_macro2::Punct"]],["impl UnwindSafe for Spacing",1,["proc_macro2::Spacing"]],["impl UnwindSafe for Ident",1,["proc_macro2::Ident"]],["impl UnwindSafe for Literal",1,["proc_macro2::Literal"]]], +"rand":[["impl UnwindSafe for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl UnwindSafe for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> UnwindSafe for DistIter<D, R, T>where
    D: UnwindSafe,
    R: UnwindSafe,
    T: UnwindSafe,
",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> UnwindSafe for DistMap<D, F, T, S>where
    D: UnwindSafe,
    F: UnwindSafe,
",1,["rand::distributions::distribution::DistMap"]],["impl UnwindSafe for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl UnwindSafe for Open01",1,["rand::distributions::float::Open01"]],["impl UnwindSafe for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> UnwindSafe for Slice<'a, T>where
    T: RefUnwindSafe,
",1,["rand::distributions::slice::Slice"]],["impl<X> UnwindSafe for WeightedIndex<X>where
    X: UnwindSafe,
    <X as SampleUniform>::Sampler: UnwindSafe,
",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl UnwindSafe for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> UnwindSafe for Uniform<X>where
    <X as SampleUniform>::Sampler: UnwindSafe,
",1,["rand::distributions::uniform::Uniform"]],["impl<X> UnwindSafe for UniformInt<X>where
    X: UnwindSafe,
",1,["rand::distributions::uniform::UniformInt"]],["impl UnwindSafe for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> UnwindSafe for UniformFloat<X>where
    X: UnwindSafe,
",1,["rand::distributions::uniform::UniformFloat"]],["impl UnwindSafe for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> UnwindSafe for WeightedIndex<W>where
    W: UnwindSafe,
",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl UnwindSafe for Standard",1,["rand::distributions::Standard"]],["impl<R> UnwindSafe for ReadRng<R>where
    R: UnwindSafe,
",1,["rand::rngs::adapter::read::ReadRng"]],["impl !UnwindSafe for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> UnwindSafe for ReseedingRng<R, Rsdr>where
    R: UnwindSafe,
    Rsdr: UnwindSafe,
    <R as BlockRngCore>::Results: UnwindSafe,
",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl UnwindSafe for StepRng",1,["rand::rngs::mock::StepRng"]],["impl UnwindSafe for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> UnwindSafe for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl UnwindSafe for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> UnwindSafe for SliceChooseIter<'a, S, T>where
    S: RefUnwindSafe,
    T: UnwindSafe,
",1,["rand::seq::SliceChooseIter"]]], +"rand_chacha":[["impl UnwindSafe for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl UnwindSafe for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl UnwindSafe for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl UnwindSafe for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl UnwindSafe for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl UnwindSafe for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], +"rand_core":[["impl<R: ?Sized> UnwindSafe for BlockRng<R>where
    R: UnwindSafe,
    <R as BlockRngCore>::Results: UnwindSafe,
",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> UnwindSafe for BlockRng64<R>where
    R: UnwindSafe,
    <R as BlockRngCore>::Results: UnwindSafe,
",1,["rand_core::block::BlockRng64"]],["impl !UnwindSafe for Error",1,["rand_core::error::Error"]],["impl UnwindSafe for OsRng",1,["rand_core::os::OsRng"]]], +"regex":[["impl UnwindSafe for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl UnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> UnwindSafe for Match<'t>",1,["regex::re_bytes::Match"]],["impl UnwindSafe for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> UnwindSafe for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> UnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> UnwindSafe for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> UnwindSafe for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> UnwindSafe for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl UnwindSafe for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> UnwindSafe for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> UnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R> !UnwindSafe for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> UnwindSafe for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl UnwindSafe for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl UnwindSafe for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl UnwindSafe for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> UnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl UnwindSafe for Error",1,["regex::error::Error"]],["impl UnwindSafe for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl UnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl UnwindSafe for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl UnwindSafe for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl UnwindSafe for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> UnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> UnwindSafe for Match<'t>",1,["regex::re_unicode::Match"]],["impl UnwindSafe for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> UnwindSafe for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> UnwindSafe for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> UnwindSafe for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl UnwindSafe for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> UnwindSafe for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> UnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> UnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> UnwindSafe for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R> !UnwindSafe for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> UnwindSafe for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], +"regex_syntax":[["impl UnwindSafe for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl UnwindSafe for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl UnwindSafe for Printer",1,["regex_syntax::ast::print::Printer"]],["impl UnwindSafe for Error",1,["regex_syntax::ast::Error"]],["impl UnwindSafe for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl UnwindSafe for Span",1,["regex_syntax::ast::Span"]],["impl UnwindSafe for Position",1,["regex_syntax::ast::Position"]],["impl UnwindSafe for WithComments",1,["regex_syntax::ast::WithComments"]],["impl UnwindSafe for Comment",1,["regex_syntax::ast::Comment"]],["impl UnwindSafe for Ast",1,["regex_syntax::ast::Ast"]],["impl UnwindSafe for Alternation",1,["regex_syntax::ast::Alternation"]],["impl UnwindSafe for Concat",1,["regex_syntax::ast::Concat"]],["impl UnwindSafe for Literal",1,["regex_syntax::ast::Literal"]],["impl UnwindSafe for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl UnwindSafe for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl UnwindSafe for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl UnwindSafe for Class",1,["regex_syntax::ast::Class"]],["impl UnwindSafe for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl UnwindSafe for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl UnwindSafe for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl UnwindSafe for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl UnwindSafe for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl UnwindSafe for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl UnwindSafe for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl UnwindSafe for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl UnwindSafe for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl UnwindSafe for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl UnwindSafe for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl UnwindSafe for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl UnwindSafe for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl UnwindSafe for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl UnwindSafe for Assertion",1,["regex_syntax::ast::Assertion"]],["impl UnwindSafe for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl UnwindSafe for Repetition",1,["regex_syntax::ast::Repetition"]],["impl UnwindSafe for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl UnwindSafe for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl UnwindSafe for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl UnwindSafe for Group",1,["regex_syntax::ast::Group"]],["impl UnwindSafe for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl UnwindSafe for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl UnwindSafe for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl UnwindSafe for Flags",1,["regex_syntax::ast::Flags"]],["impl UnwindSafe for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl UnwindSafe for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl UnwindSafe for Flag",1,["regex_syntax::ast::Flag"]],["impl UnwindSafe for Error",1,["regex_syntax::error::Error"]],["impl UnwindSafe for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl UnwindSafe for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl UnwindSafe for Printer",1,["regex_syntax::hir::print::Printer"]],["impl UnwindSafe for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl UnwindSafe for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl UnwindSafe for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl UnwindSafe for Error",1,["regex_syntax::hir::Error"]],["impl UnwindSafe for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl UnwindSafe for Hir",1,["regex_syntax::hir::Hir"]],["impl UnwindSafe for HirKind",1,["regex_syntax::hir::HirKind"]],["impl UnwindSafe for Literal",1,["regex_syntax::hir::Literal"]],["impl UnwindSafe for Class",1,["regex_syntax::hir::Class"]],["impl UnwindSafe for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> UnwindSafe for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl UnwindSafe for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl UnwindSafe for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> UnwindSafe for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl UnwindSafe for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl UnwindSafe for Anchor",1,["regex_syntax::hir::Anchor"]],["impl UnwindSafe for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl UnwindSafe for Group",1,["regex_syntax::hir::Group"]],["impl UnwindSafe for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl UnwindSafe for Repetition",1,["regex_syntax::hir::Repetition"]],["impl UnwindSafe for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl UnwindSafe for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl UnwindSafe for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl UnwindSafe for Parser",1,["regex_syntax::parser::Parser"]],["impl UnwindSafe for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl UnwindSafe for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl UnwindSafe for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl UnwindSafe for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], +"rlp":[["impl UnwindSafe for DecoderError",1,["rlp::error::DecoderError"]],["impl UnwindSafe for Prototype",1,["rlp::rlpin::Prototype"]],["impl UnwindSafe for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> UnwindSafe for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !UnwindSafe for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl UnwindSafe for RlpStream",1,["rlp::stream::RlpStream"]]], +"rustc_hex":[["impl<T> UnwindSafe for ToHexIter<T>where
    T: UnwindSafe,
",1,["rustc_hex::ToHexIter"]],["impl UnwindSafe for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> UnwindSafe for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], +"scan_fmt":[["impl UnwindSafe for ScanError",1,["scan_fmt::parse::ScanError"]]], +"scopeguard":[["impl UnwindSafe for Always",1,["scopeguard::Always"]],["impl<T, F, S> UnwindSafe for ScopeGuard<T, F, S>where
    F: UnwindSafe,
    T: UnwindSafe,
",1,["scopeguard::ScopeGuard"]]], +"serde":[["impl UnwindSafe for Error",1,["serde::de::value::Error"]],["impl<E> UnwindSafe for UnitDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::UnitDeserializer"]],["impl<E> UnwindSafe for BoolDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::BoolDeserializer"]],["impl<E> UnwindSafe for I8Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::I8Deserializer"]],["impl<E> UnwindSafe for I16Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::I16Deserializer"]],["impl<E> UnwindSafe for I32Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::I32Deserializer"]],["impl<E> UnwindSafe for I64Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::I64Deserializer"]],["impl<E> UnwindSafe for IsizeDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::IsizeDeserializer"]],["impl<E> UnwindSafe for U8Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::U8Deserializer"]],["impl<E> UnwindSafe for U16Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::U16Deserializer"]],["impl<E> UnwindSafe for U64Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::U64Deserializer"]],["impl<E> UnwindSafe for UsizeDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::UsizeDeserializer"]],["impl<E> UnwindSafe for F32Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::F32Deserializer"]],["impl<E> UnwindSafe for F64Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::F64Deserializer"]],["impl<E> UnwindSafe for CharDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::CharDeserializer"]],["impl<E> UnwindSafe for I128Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::I128Deserializer"]],["impl<E> UnwindSafe for U128Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::U128Deserializer"]],["impl<E> UnwindSafe for U32Deserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> UnwindSafe for StrDeserializer<'a, E>where
    E: UnwindSafe,
",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> UnwindSafe for BorrowedStrDeserializer<'de, E>where
    E: UnwindSafe,
",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> UnwindSafe for StringDeserializer<E>where
    E: UnwindSafe,
",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> UnwindSafe for CowStrDeserializer<'a, E>where
    E: UnwindSafe,
",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> UnwindSafe for BytesDeserializer<'a, E>where
    E: UnwindSafe,
",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> UnwindSafe for BorrowedBytesDeserializer<'de, E>where
    E: UnwindSafe,
",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> UnwindSafe for SeqDeserializer<I, E>where
    E: UnwindSafe,
    I: UnwindSafe,
",1,["serde::de::value::SeqDeserializer"]],["impl<A> UnwindSafe for SeqAccessDeserializer<A>where
    A: UnwindSafe,
",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> UnwindSafe for MapDeserializer<'de, I, E>where
    E: UnwindSafe,
    I: UnwindSafe,
    <<I as Iterator>::Item as Pair>::Second: UnwindSafe,
",1,["serde::de::value::MapDeserializer"]],["impl<A> UnwindSafe for MapAccessDeserializer<A>where
    A: UnwindSafe,
",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> UnwindSafe for EnumAccessDeserializer<A>where
    A: UnwindSafe,
",1,["serde::de::value::EnumAccessDeserializer"]],["impl UnwindSafe for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> UnwindSafe for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> UnwindSafe for Impossible<Ok, Error>where
    Error: UnwindSafe,
    Ok: UnwindSafe,
",1,["serde::ser::impossible::Impossible"]]], +"sha3":[["impl UnwindSafe for Keccak224Core",1,["sha3::Keccak224Core"]],["impl UnwindSafe for Keccak256Core",1,["sha3::Keccak256Core"]],["impl UnwindSafe for Keccak384Core",1,["sha3::Keccak384Core"]],["impl UnwindSafe for Keccak512Core",1,["sha3::Keccak512Core"]],["impl UnwindSafe for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl UnwindSafe for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl UnwindSafe for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl UnwindSafe for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl UnwindSafe for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl UnwindSafe for Shake128Core",1,["sha3::Shake128Core"]],["impl UnwindSafe for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl UnwindSafe for Shake256Core",1,["sha3::Shake256Core"]],["impl UnwindSafe for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl UnwindSafe for CShake128Core",1,["sha3::CShake128Core"]],["impl UnwindSafe for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl UnwindSafe for CShake256Core",1,["sha3::CShake256Core"]],["impl UnwindSafe for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], +"shale":[["impl UnwindSafe for CompactHeader",1,["shale::compact::CompactHeader"]],["impl UnwindSafe for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !UnwindSafe for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl UnwindSafe for ShaleError",1,["shale::ShaleError"]],["impl UnwindSafe for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> UnwindSafe for ObjPtr<T>where
    T: UnwindSafe,
",1,["shale::ObjPtr"]],["impl<T> !UnwindSafe for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !UnwindSafe for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !UnwindSafe for MummyObj<T>",1,["shale::MummyObj"]],["impl !UnwindSafe for PlainMem",1,["shale::PlainMem"]],["impl<T> !UnwindSafe for ObjCache<T>",1,["shale::ObjCache"]]], +"slab":[["impl<T> UnwindSafe for Slab<T>where
    T: UnwindSafe,
",1,["slab::Slab"]],["impl<'a, T> !UnwindSafe for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> UnwindSafe for IntoIter<T>where
    T: UnwindSafe + RefUnwindSafe,
",1,["slab::IntoIter"]],["impl<'a, T> UnwindSafe for Iter<'a, T>where
    T: RefUnwindSafe,
",1,["slab::Iter"]],["impl<'a, T> !UnwindSafe for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> UnwindSafe for Drain<'a, T>where
    T: RefUnwindSafe,
",1,["slab::Drain"]]], +"smallvec":[["impl UnwindSafe for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> UnwindSafe for Drain<'a, T>where
    T: RefUnwindSafe,
    <T as Array>::Item: RefUnwindSafe,
",1,["smallvec::Drain"]],["impl<A> UnwindSafe for SmallVec<A>where
    A: UnwindSafe,
    <A as Array>::Item: RefUnwindSafe,
",1,["smallvec::SmallVec"]],["impl<A> UnwindSafe for IntoIter<A>where
    A: UnwindSafe,
    <A as Array>::Item: RefUnwindSafe,
",1,["smallvec::IntoIter"]]], +"syn":[["impl UnwindSafe for Underscore",1,["syn::token::Underscore"]],["impl UnwindSafe for Abstract",1,["syn::token::Abstract"]],["impl UnwindSafe for As",1,["syn::token::As"]],["impl UnwindSafe for Async",1,["syn::token::Async"]],["impl UnwindSafe for Auto",1,["syn::token::Auto"]],["impl UnwindSafe for Await",1,["syn::token::Await"]],["impl UnwindSafe for Become",1,["syn::token::Become"]],["impl UnwindSafe for Box",1,["syn::token::Box"]],["impl UnwindSafe for Break",1,["syn::token::Break"]],["impl UnwindSafe for Const",1,["syn::token::Const"]],["impl UnwindSafe for Continue",1,["syn::token::Continue"]],["impl UnwindSafe for Crate",1,["syn::token::Crate"]],["impl UnwindSafe for Default",1,["syn::token::Default"]],["impl UnwindSafe for Do",1,["syn::token::Do"]],["impl UnwindSafe for Dyn",1,["syn::token::Dyn"]],["impl UnwindSafe for Else",1,["syn::token::Else"]],["impl UnwindSafe for Enum",1,["syn::token::Enum"]],["impl UnwindSafe for Extern",1,["syn::token::Extern"]],["impl UnwindSafe for Final",1,["syn::token::Final"]],["impl UnwindSafe for Fn",1,["syn::token::Fn"]],["impl UnwindSafe for For",1,["syn::token::For"]],["impl UnwindSafe for If",1,["syn::token::If"]],["impl UnwindSafe for Impl",1,["syn::token::Impl"]],["impl UnwindSafe for In",1,["syn::token::In"]],["impl UnwindSafe for Let",1,["syn::token::Let"]],["impl UnwindSafe for Loop",1,["syn::token::Loop"]],["impl UnwindSafe for Macro",1,["syn::token::Macro"]],["impl UnwindSafe for Match",1,["syn::token::Match"]],["impl UnwindSafe for Mod",1,["syn::token::Mod"]],["impl UnwindSafe for Move",1,["syn::token::Move"]],["impl UnwindSafe for Mut",1,["syn::token::Mut"]],["impl UnwindSafe for Override",1,["syn::token::Override"]],["impl UnwindSafe for Priv",1,["syn::token::Priv"]],["impl UnwindSafe for Pub",1,["syn::token::Pub"]],["impl UnwindSafe for Ref",1,["syn::token::Ref"]],["impl UnwindSafe for Return",1,["syn::token::Return"]],["impl UnwindSafe for SelfType",1,["syn::token::SelfType"]],["impl UnwindSafe for SelfValue",1,["syn::token::SelfValue"]],["impl UnwindSafe for Static",1,["syn::token::Static"]],["impl UnwindSafe for Struct",1,["syn::token::Struct"]],["impl UnwindSafe for Super",1,["syn::token::Super"]],["impl UnwindSafe for Trait",1,["syn::token::Trait"]],["impl UnwindSafe for Try",1,["syn::token::Try"]],["impl UnwindSafe for Type",1,["syn::token::Type"]],["impl UnwindSafe for Typeof",1,["syn::token::Typeof"]],["impl UnwindSafe for Union",1,["syn::token::Union"]],["impl UnwindSafe for Unsafe",1,["syn::token::Unsafe"]],["impl UnwindSafe for Unsized",1,["syn::token::Unsized"]],["impl UnwindSafe for Use",1,["syn::token::Use"]],["impl UnwindSafe for Virtual",1,["syn::token::Virtual"]],["impl UnwindSafe for Where",1,["syn::token::Where"]],["impl UnwindSafe for While",1,["syn::token::While"]],["impl UnwindSafe for Yield",1,["syn::token::Yield"]],["impl UnwindSafe for Add",1,["syn::token::Add"]],["impl UnwindSafe for AddEq",1,["syn::token::AddEq"]],["impl UnwindSafe for And",1,["syn::token::And"]],["impl UnwindSafe for AndAnd",1,["syn::token::AndAnd"]],["impl UnwindSafe for AndEq",1,["syn::token::AndEq"]],["impl UnwindSafe for At",1,["syn::token::At"]],["impl UnwindSafe for Bang",1,["syn::token::Bang"]],["impl UnwindSafe for Caret",1,["syn::token::Caret"]],["impl UnwindSafe for CaretEq",1,["syn::token::CaretEq"]],["impl UnwindSafe for Colon",1,["syn::token::Colon"]],["impl UnwindSafe for Colon2",1,["syn::token::Colon2"]],["impl UnwindSafe for Comma",1,["syn::token::Comma"]],["impl UnwindSafe for Div",1,["syn::token::Div"]],["impl UnwindSafe for DivEq",1,["syn::token::DivEq"]],["impl UnwindSafe for Dollar",1,["syn::token::Dollar"]],["impl UnwindSafe for Dot",1,["syn::token::Dot"]],["impl UnwindSafe for Dot2",1,["syn::token::Dot2"]],["impl UnwindSafe for Dot3",1,["syn::token::Dot3"]],["impl UnwindSafe for DotDotEq",1,["syn::token::DotDotEq"]],["impl UnwindSafe for Eq",1,["syn::token::Eq"]],["impl UnwindSafe for EqEq",1,["syn::token::EqEq"]],["impl UnwindSafe for Ge",1,["syn::token::Ge"]],["impl UnwindSafe for Gt",1,["syn::token::Gt"]],["impl UnwindSafe for Le",1,["syn::token::Le"]],["impl UnwindSafe for Lt",1,["syn::token::Lt"]],["impl UnwindSafe for MulEq",1,["syn::token::MulEq"]],["impl UnwindSafe for Ne",1,["syn::token::Ne"]],["impl UnwindSafe for Or",1,["syn::token::Or"]],["impl UnwindSafe for OrEq",1,["syn::token::OrEq"]],["impl UnwindSafe for OrOr",1,["syn::token::OrOr"]],["impl UnwindSafe for Pound",1,["syn::token::Pound"]],["impl UnwindSafe for Question",1,["syn::token::Question"]],["impl UnwindSafe for RArrow",1,["syn::token::RArrow"]],["impl UnwindSafe for LArrow",1,["syn::token::LArrow"]],["impl UnwindSafe for Rem",1,["syn::token::Rem"]],["impl UnwindSafe for RemEq",1,["syn::token::RemEq"]],["impl UnwindSafe for FatArrow",1,["syn::token::FatArrow"]],["impl UnwindSafe for Semi",1,["syn::token::Semi"]],["impl UnwindSafe for Shl",1,["syn::token::Shl"]],["impl UnwindSafe for ShlEq",1,["syn::token::ShlEq"]],["impl UnwindSafe for Shr",1,["syn::token::Shr"]],["impl UnwindSafe for ShrEq",1,["syn::token::ShrEq"]],["impl UnwindSafe for Star",1,["syn::token::Star"]],["impl UnwindSafe for Sub",1,["syn::token::Sub"]],["impl UnwindSafe for SubEq",1,["syn::token::SubEq"]],["impl UnwindSafe for Tilde",1,["syn::token::Tilde"]],["impl UnwindSafe for Brace",1,["syn::token::Brace"]],["impl UnwindSafe for Bracket",1,["syn::token::Bracket"]],["impl UnwindSafe for Paren",1,["syn::token::Paren"]],["impl UnwindSafe for Group",1,["syn::token::Group"]],["impl UnwindSafe for Attribute",1,["syn::attr::Attribute"]],["impl UnwindSafe for AttrStyle",1,["syn::attr::AttrStyle"]],["impl UnwindSafe for Meta",1,["syn::attr::Meta"]],["impl UnwindSafe for MetaList",1,["syn::attr::MetaList"]],["impl UnwindSafe for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl UnwindSafe for NestedMeta",1,["syn::attr::NestedMeta"]],["impl UnwindSafe for Variant",1,["syn::data::Variant"]],["impl UnwindSafe for Fields",1,["syn::data::Fields"]],["impl UnwindSafe for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl UnwindSafe for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl UnwindSafe for Field",1,["syn::data::Field"]],["impl UnwindSafe for Visibility",1,["syn::data::Visibility"]],["impl UnwindSafe for VisPublic",1,["syn::data::VisPublic"]],["impl UnwindSafe for VisCrate",1,["syn::data::VisCrate"]],["impl UnwindSafe for VisRestricted",1,["syn::data::VisRestricted"]],["impl UnwindSafe for Expr",1,["syn::expr::Expr"]],["impl UnwindSafe for ExprArray",1,["syn::expr::ExprArray"]],["impl UnwindSafe for ExprAssign",1,["syn::expr::ExprAssign"]],["impl UnwindSafe for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl UnwindSafe for ExprAsync",1,["syn::expr::ExprAsync"]],["impl UnwindSafe for ExprAwait",1,["syn::expr::ExprAwait"]],["impl UnwindSafe for ExprBinary",1,["syn::expr::ExprBinary"]],["impl UnwindSafe for ExprBlock",1,["syn::expr::ExprBlock"]],["impl UnwindSafe for ExprBox",1,["syn::expr::ExprBox"]],["impl UnwindSafe for ExprBreak",1,["syn::expr::ExprBreak"]],["impl UnwindSafe for ExprCall",1,["syn::expr::ExprCall"]],["impl UnwindSafe for ExprCast",1,["syn::expr::ExprCast"]],["impl UnwindSafe for ExprClosure",1,["syn::expr::ExprClosure"]],["impl UnwindSafe for ExprContinue",1,["syn::expr::ExprContinue"]],["impl UnwindSafe for ExprField",1,["syn::expr::ExprField"]],["impl UnwindSafe for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl UnwindSafe for ExprGroup",1,["syn::expr::ExprGroup"]],["impl UnwindSafe for ExprIf",1,["syn::expr::ExprIf"]],["impl UnwindSafe for ExprIndex",1,["syn::expr::ExprIndex"]],["impl UnwindSafe for ExprLet",1,["syn::expr::ExprLet"]],["impl UnwindSafe for ExprLit",1,["syn::expr::ExprLit"]],["impl UnwindSafe for ExprLoop",1,["syn::expr::ExprLoop"]],["impl UnwindSafe for ExprMacro",1,["syn::expr::ExprMacro"]],["impl UnwindSafe for ExprMatch",1,["syn::expr::ExprMatch"]],["impl UnwindSafe for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl UnwindSafe for ExprParen",1,["syn::expr::ExprParen"]],["impl UnwindSafe for ExprPath",1,["syn::expr::ExprPath"]],["impl UnwindSafe for ExprRange",1,["syn::expr::ExprRange"]],["impl UnwindSafe for ExprReference",1,["syn::expr::ExprReference"]],["impl UnwindSafe for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl UnwindSafe for ExprReturn",1,["syn::expr::ExprReturn"]],["impl UnwindSafe for ExprStruct",1,["syn::expr::ExprStruct"]],["impl UnwindSafe for ExprTry",1,["syn::expr::ExprTry"]],["impl UnwindSafe for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl UnwindSafe for ExprTuple",1,["syn::expr::ExprTuple"]],["impl UnwindSafe for ExprType",1,["syn::expr::ExprType"]],["impl UnwindSafe for ExprUnary",1,["syn::expr::ExprUnary"]],["impl UnwindSafe for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl UnwindSafe for ExprWhile",1,["syn::expr::ExprWhile"]],["impl UnwindSafe for ExprYield",1,["syn::expr::ExprYield"]],["impl UnwindSafe for Member",1,["syn::expr::Member"]],["impl UnwindSafe for Index",1,["syn::expr::Index"]],["impl UnwindSafe for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl UnwindSafe for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl UnwindSafe for FieldValue",1,["syn::expr::FieldValue"]],["impl UnwindSafe for Label",1,["syn::expr::Label"]],["impl UnwindSafe for Arm",1,["syn::expr::Arm"]],["impl UnwindSafe for RangeLimits",1,["syn::expr::RangeLimits"]],["impl UnwindSafe for Generics",1,["syn::generics::Generics"]],["impl UnwindSafe for GenericParam",1,["syn::generics::GenericParam"]],["impl UnwindSafe for TypeParam",1,["syn::generics::TypeParam"]],["impl UnwindSafe for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl UnwindSafe for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> UnwindSafe for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> UnwindSafe for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> UnwindSafe for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl UnwindSafe for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl UnwindSafe for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl UnwindSafe for TraitBound",1,["syn::generics::TraitBound"]],["impl UnwindSafe for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl UnwindSafe for WhereClause",1,["syn::generics::WhereClause"]],["impl UnwindSafe for WherePredicate",1,["syn::generics::WherePredicate"]],["impl UnwindSafe for PredicateType",1,["syn::generics::PredicateType"]],["impl UnwindSafe for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl UnwindSafe for PredicateEq",1,["syn::generics::PredicateEq"]],["impl UnwindSafe for Item",1,["syn::item::Item"]],["impl UnwindSafe for ItemConst",1,["syn::item::ItemConst"]],["impl UnwindSafe for ItemEnum",1,["syn::item::ItemEnum"]],["impl UnwindSafe for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl UnwindSafe for ItemFn",1,["syn::item::ItemFn"]],["impl UnwindSafe for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl UnwindSafe for ItemImpl",1,["syn::item::ItemImpl"]],["impl UnwindSafe for ItemMacro",1,["syn::item::ItemMacro"]],["impl UnwindSafe for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl UnwindSafe for ItemMod",1,["syn::item::ItemMod"]],["impl UnwindSafe for ItemStatic",1,["syn::item::ItemStatic"]],["impl UnwindSafe for ItemStruct",1,["syn::item::ItemStruct"]],["impl UnwindSafe for ItemTrait",1,["syn::item::ItemTrait"]],["impl UnwindSafe for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl UnwindSafe for ItemType",1,["syn::item::ItemType"]],["impl UnwindSafe for ItemUnion",1,["syn::item::ItemUnion"]],["impl UnwindSafe for ItemUse",1,["syn::item::ItemUse"]],["impl UnwindSafe for UseTree",1,["syn::item::UseTree"]],["impl UnwindSafe for UsePath",1,["syn::item::UsePath"]],["impl UnwindSafe for UseName",1,["syn::item::UseName"]],["impl UnwindSafe for UseRename",1,["syn::item::UseRename"]],["impl UnwindSafe for UseGlob",1,["syn::item::UseGlob"]],["impl UnwindSafe for UseGroup",1,["syn::item::UseGroup"]],["impl UnwindSafe for ForeignItem",1,["syn::item::ForeignItem"]],["impl UnwindSafe for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl UnwindSafe for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl UnwindSafe for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl UnwindSafe for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl UnwindSafe for TraitItem",1,["syn::item::TraitItem"]],["impl UnwindSafe for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl UnwindSafe for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl UnwindSafe for TraitItemType",1,["syn::item::TraitItemType"]],["impl UnwindSafe for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl UnwindSafe for ImplItem",1,["syn::item::ImplItem"]],["impl UnwindSafe for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl UnwindSafe for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl UnwindSafe for ImplItemType",1,["syn::item::ImplItemType"]],["impl UnwindSafe for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl UnwindSafe for Signature",1,["syn::item::Signature"]],["impl UnwindSafe for FnArg",1,["syn::item::FnArg"]],["impl UnwindSafe for Receiver",1,["syn::item::Receiver"]],["impl UnwindSafe for File",1,["syn::file::File"]],["impl UnwindSafe for Lifetime",1,["syn::lifetime::Lifetime"]],["impl UnwindSafe for Lit",1,["syn::lit::Lit"]],["impl UnwindSafe for LitStr",1,["syn::lit::LitStr"]],["impl UnwindSafe for LitByteStr",1,["syn::lit::LitByteStr"]],["impl UnwindSafe for LitByte",1,["syn::lit::LitByte"]],["impl UnwindSafe for LitChar",1,["syn::lit::LitChar"]],["impl UnwindSafe for LitInt",1,["syn::lit::LitInt"]],["impl UnwindSafe for LitFloat",1,["syn::lit::LitFloat"]],["impl UnwindSafe for LitBool",1,["syn::lit::LitBool"]],["impl UnwindSafe for StrStyle",1,["syn::lit::StrStyle"]],["impl UnwindSafe for Macro",1,["syn::mac::Macro"]],["impl UnwindSafe for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl UnwindSafe for DeriveInput",1,["syn::derive::DeriveInput"]],["impl UnwindSafe for Data",1,["syn::derive::Data"]],["impl UnwindSafe for DataStruct",1,["syn::derive::DataStruct"]],["impl UnwindSafe for DataEnum",1,["syn::derive::DataEnum"]],["impl UnwindSafe for DataUnion",1,["syn::derive::DataUnion"]],["impl UnwindSafe for BinOp",1,["syn::op::BinOp"]],["impl UnwindSafe for UnOp",1,["syn::op::UnOp"]],["impl UnwindSafe for Block",1,["syn::stmt::Block"]],["impl UnwindSafe for Stmt",1,["syn::stmt::Stmt"]],["impl UnwindSafe for Local",1,["syn::stmt::Local"]],["impl UnwindSafe for Type",1,["syn::ty::Type"]],["impl UnwindSafe for TypeArray",1,["syn::ty::TypeArray"]],["impl UnwindSafe for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl UnwindSafe for TypeGroup",1,["syn::ty::TypeGroup"]],["impl UnwindSafe for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl UnwindSafe for TypeInfer",1,["syn::ty::TypeInfer"]],["impl UnwindSafe for TypeMacro",1,["syn::ty::TypeMacro"]],["impl UnwindSafe for TypeNever",1,["syn::ty::TypeNever"]],["impl UnwindSafe for TypeParen",1,["syn::ty::TypeParen"]],["impl UnwindSafe for TypePath",1,["syn::ty::TypePath"]],["impl UnwindSafe for TypePtr",1,["syn::ty::TypePtr"]],["impl UnwindSafe for TypeReference",1,["syn::ty::TypeReference"]],["impl UnwindSafe for TypeSlice",1,["syn::ty::TypeSlice"]],["impl UnwindSafe for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl UnwindSafe for TypeTuple",1,["syn::ty::TypeTuple"]],["impl UnwindSafe for Abi",1,["syn::ty::Abi"]],["impl UnwindSafe for BareFnArg",1,["syn::ty::BareFnArg"]],["impl UnwindSafe for Variadic",1,["syn::ty::Variadic"]],["impl UnwindSafe for ReturnType",1,["syn::ty::ReturnType"]],["impl UnwindSafe for Pat",1,["syn::pat::Pat"]],["impl UnwindSafe for PatBox",1,["syn::pat::PatBox"]],["impl UnwindSafe for PatIdent",1,["syn::pat::PatIdent"]],["impl UnwindSafe for PatLit",1,["syn::pat::PatLit"]],["impl UnwindSafe for PatMacro",1,["syn::pat::PatMacro"]],["impl UnwindSafe for PatOr",1,["syn::pat::PatOr"]],["impl UnwindSafe for PatPath",1,["syn::pat::PatPath"]],["impl UnwindSafe for PatRange",1,["syn::pat::PatRange"]],["impl UnwindSafe for PatReference",1,["syn::pat::PatReference"]],["impl UnwindSafe for PatRest",1,["syn::pat::PatRest"]],["impl UnwindSafe for PatSlice",1,["syn::pat::PatSlice"]],["impl UnwindSafe for PatStruct",1,["syn::pat::PatStruct"]],["impl UnwindSafe for PatTuple",1,["syn::pat::PatTuple"]],["impl UnwindSafe for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl UnwindSafe for PatType",1,["syn::pat::PatType"]],["impl UnwindSafe for PatWild",1,["syn::pat::PatWild"]],["impl UnwindSafe for FieldPat",1,["syn::pat::FieldPat"]],["impl UnwindSafe for Path",1,["syn::path::Path"]],["impl UnwindSafe for PathSegment",1,["syn::path::PathSegment"]],["impl UnwindSafe for PathArguments",1,["syn::path::PathArguments"]],["impl UnwindSafe for GenericArgument",1,["syn::path::GenericArgument"]],["impl UnwindSafe for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl UnwindSafe for Binding",1,["syn::path::Binding"]],["impl UnwindSafe for Constraint",1,["syn::path::Constraint"]],["impl UnwindSafe for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl UnwindSafe for QSelf",1,["syn::path::QSelf"]],["impl UnwindSafe for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> UnwindSafe for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> UnwindSafe for Punctuated<T, P>where
    P: UnwindSafe,
    T: UnwindSafe,
",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> UnwindSafe for Pairs<'a, T, P>where
    P: RefUnwindSafe,
    T: RefUnwindSafe,
",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> !UnwindSafe for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> UnwindSafe for IntoPairs<T, P>where
    P: UnwindSafe + RefUnwindSafe,
    T: UnwindSafe + RefUnwindSafe,
",1,["syn::punctuated::IntoPairs"]],["impl<T> UnwindSafe for IntoIter<T>where
    T: UnwindSafe + RefUnwindSafe,
",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !UnwindSafe for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !UnwindSafe for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> UnwindSafe for Pair<T, P>where
    P: UnwindSafe,
    T: UnwindSafe,
",1,["syn::punctuated::Pair"]],["impl<'a> UnwindSafe for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl UnwindSafe for Error",1,["syn::error::Error"]],["impl<'a> !UnwindSafe for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> UnwindSafe for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl UnwindSafe for Nothing",1,["syn::parse::Nothing"]]], +"tokio":[["impl<'a> !UnwindSafe for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl !UnwindSafe for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl !UnwindSafe for Builder",1,["tokio::runtime::builder::Builder"]],["impl !UnwindSafe for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> !UnwindSafe for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl UnwindSafe for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !UnwindSafe for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl UnwindSafe for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !UnwindSafe for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !UnwindSafe for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl UnwindSafe for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> UnwindSafe for SendError<T>where
    T: UnwindSafe,
",1,["tokio::sync::broadcast::error::SendError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> !UnwindSafe for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> !UnwindSafe for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> !UnwindSafe for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> !UnwindSafe for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> !UnwindSafe for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> !UnwindSafe for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> UnwindSafe for SendError<T>where
    T: UnwindSafe,
",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
    T: UnwindSafe,
",1,["tokio::sync::mpsc::error::TrySendError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T: ?Sized> UnwindSafe for Mutex<T>where
    T: UnwindSafe,
",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T> !UnwindSafe for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T> !UnwindSafe for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T> !UnwindSafe for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl UnwindSafe for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl UnwindSafe for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl UnwindSafe for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl UnwindSafe for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> !UnwindSafe for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl !UnwindSafe for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T, U = T> !UnwindSafe for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T> !UnwindSafe for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T, U = T> !UnwindSafe for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T> !UnwindSafe for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T> !UnwindSafe for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T> !UnwindSafe for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T: ?Sized> UnwindSafe for RwLock<T>where
    T: UnwindSafe,
",1,["tokio::sync::rwlock::RwLock"]],["impl<T> UnwindSafe for OnceCell<T>where
    T: UnwindSafe,
",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> UnwindSafe for SetError<T>where
    T: UnwindSafe,
",1,["tokio::sync::once_cell::SetError"]],["impl<T> UnwindSafe for SendError<T>where
    T: UnwindSafe,
",1,["tokio::sync::watch::error::SendError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> UnwindSafe for Ref<'a, T>where
    T: RefUnwindSafe,
",1,["tokio::sync::watch::Ref"]],["impl !UnwindSafe for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !UnwindSafe for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> UnwindSafe for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> UnwindSafe for TaskLocalFuture<T, F>where
    F: UnwindSafe,
    T: UnwindSafe,
",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> UnwindSafe for Unconstrained<F>where
    F: UnwindSafe,
",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> UnwindSafe for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl UnwindSafe for AbortHandle"],["impl<T> UnwindSafe for JoinHandle<T>"],["impl UnwindSafe for Notify"]], +"typenum":[["impl UnwindSafe for B0",1,["typenum::bit::B0"]],["impl UnwindSafe for B1",1,["typenum::bit::B1"]],["impl<U> UnwindSafe for PInt<U>where
    U: UnwindSafe,
",1,["typenum::int::PInt"]],["impl<U> UnwindSafe for NInt<U>where
    U: UnwindSafe,
",1,["typenum::int::NInt"]],["impl UnwindSafe for Z0",1,["typenum::int::Z0"]],["impl UnwindSafe for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> UnwindSafe for UInt<U, B>where
    B: UnwindSafe,
    U: UnwindSafe,
",1,["typenum::uint::UInt"]],["impl UnwindSafe for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> UnwindSafe for TArr<V, A>where
    A: UnwindSafe,
    V: UnwindSafe,
",1,["typenum::array::TArr"]],["impl UnwindSafe for Greater",1,["typenum::Greater"]],["impl UnwindSafe for Less",1,["typenum::Less"]],["impl UnwindSafe for Equal",1,["typenum::Equal"]]], +"uint":[["impl UnwindSafe for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl UnwindSafe for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl UnwindSafe for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl UnwindSafe for FromHexError",1,["uint::uint::FromHexError"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/firewood/merkle/trait.ValueTransformer.js b/docs/implementors/firewood/merkle/trait.ValueTransformer.js new file mode 100644 index 000000000000..1961d5cb23d9 --- /dev/null +++ b/docs/implementors/firewood/merkle/trait.ValueTransformer.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"firewood":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/shale/trait.MummyItem.js b/docs/implementors/shale/trait.MummyItem.js new file mode 100644 index 000000000000..cb5ab49451d1 --- /dev/null +++ b/docs/implementors/shale/trait.MummyItem.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"firewood":[["impl MummyItem for Hash"],["impl MummyItem for Node"]], +"shale":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000000..1f05c1c90ff3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ + diff --git a/docs/light.css b/docs/light.css new file mode 100644 index 000000000000..1aa2dceef964 --- /dev/null +++ b/docs/light.css @@ -0,0 +1 @@ +:root{--main-background-color:white;--main-color:black;--settings-input-color:#2196f3;--sidebar-background-color:#F5F5F5;--sidebar-background-color-hover:#E0E0E0;--code-block-background-color:#F5F5F5;--scrollbar-track-background-color:#dcdcdc;--scrollbar-thumb-background-color:rgba(36,37,39,0.6);--scrollbar-color:rgba(36,37,39,0.6) #d9d9d9;--headings-border-bottom-color:#ddd;--border-color:#e0e0e0;--button-background-color:#fff;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#66afe9;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(35%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ad378a;--trait-link-color:#6e4fc9;--assoc-item-link-color:#3873ad;--function-link-color:#ad7c37;--macro-link-color:#068000;--keyword-link-color:#3873ad;--mod-link-color:#3873ad;--link-color:#3873ad;--sidebar-link-color:#356da4;--sidebar-current-link-background-color:#fff;--search-result-link-focus-background-color:#ccc;--stab-background-color:#fff5d6;--stab-code-color:#000;--search-color:#000;--code-highlight-kw-color:#8959a8;--code-highlight-kw-2-color:#4271ae;--code-highlight-lifetime-color:#b76514;--code-highlight-prelude-color:#4271ae;--code-highlight-prelude-val-color:#c82829;--code-highlight-number-color:#718c00;--code-highlight-string-color:#718c00;--code-highlight-literal-color:#c82829;--code-highlight-attribute-color:#c82829;--code-highlight-self-color:#c82829;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8e908c;--code-highlight-doc-comment-color:#4d4d4c;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}.rust-logo{}.src-line-numbers span{color:#c67e2d;}.src-line-numbers .line-highlighted{background-color:#FDFFD3 !important;}.content .item-info::before{color:#ccc;}body.source .example-wrap pre.rust a{background:#eee;}#crate-search-div::after{filter:invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);}#crate-search:hover,#crate-search:focus{border-color:#717171 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{border-color:#c7c7c7;}a.test-arrow{color:#f5f5f5;background-color:rgba(78,139,202,0.2);}a.test-arrow:hover{background-color:#4e8bca;}:target{background:#FDFFD3;border-right:3px solid #AD7C37;}.search-failed a{color:#3873AD;}.tooltip::after{background-color:#000;color:#fff;}.tooltip::before{border-color:transparent black transparent transparent;}.notable-traits-tooltiptext{background-color:#eee;}#titles>button:not(.selected){background-color:#e6e6e6;border-top-color:#e6e6e6;}#titles>button:hover,#titles>button.selected{background-color:#ffffff;border-top-color:#0089ff;}#titles>button>div.count{color:#888;}kbd{color:#000;background-color:#fafbfc;box-shadow:inset 0 -1px 0 #c6cbd1;}#settings-menu>a,#help-button>a{color:#000;}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#717171;}.search-results .result-name span.alias{color:#000;}.search-results .result-name span.grey{color:#999;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#E0E0E0;}#source-sidebar div.files>a.selected{background-color:#fff;}.scraped-example-list .scrape-help{border-color:#555;color:#333;}.scraped-example-list .scrape-help:hover{border-color:black;color:black;}.scraped-example .example-wrap .rust span.highlight{background:#fcffd6;}.scraped-example .example-wrap .rust span.highlight.focus{background:#f6fdb0;}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(255,255,255,1),rgba(255,255,255,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(255,255,255,1),rgba(255,255,255,0));}.toggle-line-inner{background:#ccc;}.toggle-line:hover .toggle-line-inner{background:#999;} \ No newline at end of file diff --git a/docs/main.js b/docs/main.js new file mode 100644 index 000000000000..63d1a20b5583 --- /dev/null +++ b/docs/main.js @@ -0,0 +1,8 @@ +"use strict";function getVar(name){const el=document.getElementById("rustdoc-vars");if(el){return el.attributes["data-"+name].value}else{return null}}function resourcePath(basename,extension){return getVar("root-path")+basename+getVar("resource-suffix")+extension}function hideMain(){addClass(document.getElementById(MAIN_ID),"hidden")}function showMain(){removeClass(document.getElementById(MAIN_ID),"hidden")}function elemIsInParent(elem,parent){while(elem&&elem!==document.body){if(elem===parent){return true}elem=elem.parentElement}return false}function blurHandler(event,parentElem,hideCallback){if(!elemIsInParent(document.activeElement,parentElem)&&!elemIsInParent(event.relatedTarget,parentElem)){hideCallback()}}(function(){window.rootPath=getVar("root-path");window.currentCrate=getVar("current-crate")}());function setMobileTopbar(){const mobileLocationTitle=document.querySelector(".mobile-topbar h2");const locationTitle=document.querySelector(".sidebar h2.location");if(mobileLocationTitle&&locationTitle){mobileLocationTitle.innerHTML=locationTitle.innerHTML}}function getVirtualKey(ev){if("key"in ev&&typeof ev.key!=="undefined"){return ev.key}const c=ev.charCode||ev.keyCode;if(c===27){return"Escape"}return String.fromCharCode(c)}const MAIN_ID="main-content";const SETTINGS_BUTTON_ID="settings-menu";const ALTERNATIVE_DISPLAY_ID="alternative-display";const NOT_DISPLAYED_ID="not-displayed";const HELP_BUTTON_ID="help-button";function getSettingsButton(){return document.getElementById(SETTINGS_BUTTON_ID)}function getHelpButton(){return document.getElementById(HELP_BUTTON_ID)}function getNakedUrl(){return window.location.href.split("?")[0].split("#")[0]}function insertAfter(newNode,referenceNode){referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling)}function getOrCreateSection(id,classes){let el=document.getElementById(id);if(!el){el=document.createElement("section");el.id=id;el.className=classes;insertAfter(el,document.getElementById(MAIN_ID))}return el}function getAlternativeDisplayElem(){return getOrCreateSection(ALTERNATIVE_DISPLAY_ID,"content hidden")}function getNotDisplayedElem(){return getOrCreateSection(NOT_DISPLAYED_ID,"hidden")}function switchDisplayedElement(elemToDisplay){const el=getAlternativeDisplayElem();if(el.children.length>0){getNotDisplayedElem().appendChild(el.firstElementChild)}if(elemToDisplay===null){addClass(el,"hidden");showMain();return}el.appendChild(elemToDisplay);hideMain();removeClass(el,"hidden")}function browserSupportsHistoryApi(){return window.history&&typeof window.history.pushState==="function"}function loadCss(cssFileName){const link=document.createElement("link");link.href=resourcePath(cssFileName,".css");link.type="text/css";link.rel="stylesheet";document.getElementsByTagName("head")[0].appendChild(link)}(function(){const isHelpPage=window.location.pathname.endsWith("/help.html");function loadScript(url){const script=document.createElement("script");script.src=url;document.head.append(script)}getSettingsButton().onclick=event=>{if(event.ctrlKey||event.altKey||event.metaKey){return}addClass(getSettingsButton(),"rotate");event.preventDefault();loadCss("settings");loadScript(resourcePath("settings",".js"))};window.searchState={loadingText:"Loading search results...",input:document.getElementsByClassName("search-input")[0],outputElement:()=>{let el=document.getElementById("search");if(!el){el=document.createElement("section");el.id="search";getNotDisplayedElem().appendChild(el)}return el},title:document.title,titleBeforeSearch:document.title,timeout:null,currentTab:0,focusedByTab:[null,null,null],clearInputTimeout:()=>{if(searchState.timeout!==null){clearTimeout(searchState.timeout);searchState.timeout=null}},isDisplayed:()=>searchState.outputElement().parentElement.id===ALTERNATIVE_DISPLAY_ID,focus:()=>{searchState.input.focus()},defocus:()=>{searchState.input.blur()},showResults:search=>{if(search===null||typeof search==="undefined"){search=searchState.outputElement()}switchDisplayedElement(search);searchState.mouseMovedAfterSearch=false;document.title=searchState.title},hideResults:()=>{switchDisplayedElement(null);document.title=searchState.titleBeforeSearch;if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}},getQueryStringParams:()=>{const params={};window.location.search.substring(1).split("&").map(s=>{const pair=s.split("=");params[decodeURIComponent(pair[0])]=typeof pair[1]==="undefined"?null:decodeURIComponent(pair[1])});return params},setup:()=>{const search_input=searchState.input;if(!searchState.input){return}let searchLoaded=false;function loadSearch(){if(!searchLoaded){searchLoaded=true;loadScript(resourcePath("search",".js"));loadScript(resourcePath("search-index",".js"))}}search_input.addEventListener("focus",()=>{search_input.origPlaceholder=search_input.placeholder;search_input.placeholder="Type your search here.";loadSearch()});if(search_input.value!==""){loadSearch()}const params=searchState.getQueryStringParams();if(params.search!==undefined){const search=searchState.outputElement();search.innerHTML="

"+searchState.loadingText+"

";searchState.showResults(search);loadSearch()}},};function getPageId(){if(window.location.hash){const tmp=window.location.hash.replace(/^#/,"");if(tmp.length>0){return tmp}}return null}const toggleAllDocsId="toggle-all-docs";let savedHash="";function handleHashes(ev){if(ev!==null&&searchState.isDisplayed()&&ev.newURL){switchDisplayedElement(null);const hash=ev.newURL.slice(ev.newURL.indexOf("#")+1);if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.search+"#"+hash)}const elem=document.getElementById(hash);if(elem){elem.scrollIntoView()}}if(savedHash!==window.location.hash){savedHash=window.location.hash;if(savedHash.length===0){return}expandSection(savedHash.slice(1))}}function onHashChange(ev){hideSidebar();handleHashes(ev)}function openParentDetails(elem){while(elem){if(elem.tagName==="DETAILS"){elem.open=true}elem=elem.parentNode}}function expandSection(id){openParentDetails(document.getElementById(id))}function handleEscape(ev){searchState.clearInputTimeout();switchDisplayedElement(null);if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}ev.preventDefault();searchState.defocus();window.hidePopoverMenus()}function handleShortcut(ev){const disableShortcuts=getSettingValue("disable-shortcuts")==="true";if(ev.ctrlKey||ev.altKey||ev.metaKey||disableShortcuts){return}if(document.activeElement.tagName==="INPUT"&&document.activeElement.type!=="checkbox"){switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break}}else{switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break;case"s":case"S":ev.preventDefault();searchState.focus();break;case"+":ev.preventDefault();expandAllDocs();break;case"-":ev.preventDefault();collapseAllDocs();break;case"?":showHelp();break;default:break}}}document.addEventListener("keypress",handleShortcut);document.addEventListener("keydown",handleShortcut);function addSidebarItems(){if(!window.SIDEBAR_ITEMS){return}const sidebar=document.getElementsByClassName("sidebar-elems")[0];function block(shortty,id,longty){const filtered=window.SIDEBAR_ITEMS[shortty];if(!filtered){return}const h3=document.createElement("h3");h3.innerHTML=`${longty}`;const ul=document.createElement("ul");ul.className="block "+shortty;for(const item of filtered){const name=item[0];const desc=item[1];let path;if(shortty==="mod"){path=name+"/index.html"}else{path=shortty+"."+name+".html"}const current_page=document.location.href.split("/").pop();const link=document.createElement("a");link.href=path;link.title=desc;if(path===current_page){link.className="current"}link.textContent=name;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebar.appendChild(h3);sidebar.appendChild(ul)}if(sidebar){block("primitive","primitives","Primitive Types");block("mod","modules","Modules");block("macro","macros","Macros");block("struct","structs","Structs");block("enum","enums","Enums");block("union","unions","Unions");block("constant","constants","Constants");block("static","static","Statics");block("trait","traits","Traits");block("fn","functions","Functions");block("type","types","Type Definitions");block("foreigntype","foreign-types","Foreign Types");block("keyword","keywords","Keywords");block("traitalias","trait-aliases","Trait Aliases")}}window.register_implementors=imp=>{const implementors=document.getElementById("implementors-list");const synthetic_implementors=document.getElementById("synthetic-implementors-list");const inlined_types=new Set();const TEXT_IDX=0;const SYNTHETIC_IDX=1;const TYPES_IDX=2;if(synthetic_implementors){onEachLazy(synthetic_implementors.getElementsByClassName("impl"),el=>{const aliases=el.getAttribute("data-aliases");if(!aliases){return}aliases.split(",").forEach(alias=>{inlined_types.add(alias)})})}let currentNbImpls=implementors.getElementsByClassName("impl").length;const traitName=document.querySelector("h1.fqn > .trait").textContent;const baseIdName="impl-"+traitName+"-";const libs=Object.getOwnPropertyNames(imp);const script=document.querySelector("script[data-ignore-extern-crates]");const ignoreExternCrates=script?script.getAttribute("data-ignore-extern-crates"):"";for(const lib of libs){if(lib===window.currentCrate||ignoreExternCrates.indexOf(lib)!==-1){continue}const structs=imp[lib];struct_loop:for(const struct of structs){const list=struct[SYNTHETIC_IDX]?synthetic_implementors:implementors;if(struct[SYNTHETIC_IDX]){for(const struct_type of struct[TYPES_IDX]){if(inlined_types.has(struct_type)){continue struct_loop}inlined_types.add(struct_type)}}const code=document.createElement("h3");code.innerHTML=struct[TEXT_IDX];addClass(code,"code-header");onEachLazy(code.getElementsByTagName("a"),elem=>{const href=elem.getAttribute("href");if(href&&href.indexOf("http")!==0){elem.setAttribute("href",window.rootPath+href)}});const currentId=baseIdName+currentNbImpls;const anchor=document.createElement("a");anchor.href="#"+currentId;addClass(anchor,"anchor");const display=document.createElement("div");display.id=currentId;addClass(display,"impl");display.appendChild(anchor);display.appendChild(code);list.appendChild(display);currentNbImpls+=1}}};if(window.pending_implementors){window.register_implementors(window.pending_implementors)}function addSidebarCrates(){if(!window.ALL_CRATES){return}const sidebarElems=document.getElementsByClassName("sidebar-elems")[0];if(!sidebarElems){return}const h3=document.createElement("h3");h3.innerHTML="Crates";const ul=document.createElement("ul");ul.className="block crate";for(const crate of window.ALL_CRATES){const link=document.createElement("a");link.href=window.rootPath+crate+"/index.html";if(window.rootPath!=="./"&&crate===window.currentCrate){link.className="current"}link.textContent=crate;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebarElems.appendChild(h3);sidebarElems.appendChild(ul)}function expandAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);removeClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(!hasClass(e,"type-contents-toggle")){e.open=true}});innerToggle.title="collapse all docs";innerToggle.children[0].innerText="\u2212"}function collapseAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);addClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(e.parentNode.id!=="implementations-list"||(!hasClass(e,"implementors-toggle")&&!hasClass(e,"type-contents-toggle"))){e.open=false}});innerToggle.title="expand all docs";innerToggle.children[0].innerText="+"}function toggleAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);if(!innerToggle){return}if(hasClass(innerToggle,"will-expand")){expandAllDocs()}else{collapseAllDocs()}}(function(){const toggles=document.getElementById(toggleAllDocsId);if(toggles){toggles.onclick=toggleAllDocs}const hideMethodDocs=getSettingValue("auto-hide-method-docs")==="true";const hideImplementations=getSettingValue("auto-hide-trait-implementations")==="true";const hideLargeItemContents=getSettingValue("auto-hide-large-items")!=="false";function setImplementorsTogglesOpen(id,open){const list=document.getElementById(id);if(list!==null){onEachLazy(list.getElementsByClassName("implementors-toggle"),e=>{e.open=open})}}if(hideImplementations){setImplementorsTogglesOpen("trait-implementations-list",false);setImplementorsTogglesOpen("blanket-implementations-list",false)}onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(!hideLargeItemContents&&hasClass(e,"type-contents-toggle")){e.open=true}if(hideMethodDocs&&hasClass(e,"method-toggle")){e.open=false}});const pageId=getPageId();if(pageId!==null){expandSection(pageId)}}());window.rustdoc_add_line_numbers_to_examples=()=>{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");if(line_numbers.length>0){return}const count=x.textContent.split("\n").length;const elems=[];for(let i=0;i{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");for(const node of line_numbers){parent.removeChild(node)}})};(function(){if(getSettingValue("line-numbers")==="true"){window.rustdoc_add_line_numbers_to_examples()}}());let oldSidebarScrollPosition=null;window.rustdocMobileScrollLock=function(){const mobile_topbar=document.querySelector(".mobile-topbar");if(window.innerWidth<=window.RUSTDOC_MOBILE_BREAKPOINT){oldSidebarScrollPosition=window.scrollY;document.body.style.width=`${document.body.offsetWidth}px`;document.body.style.position="fixed";document.body.style.top=`-${oldSidebarScrollPosition}px`;if(mobile_topbar){mobile_topbar.style.top=`${oldSidebarScrollPosition}px`;mobile_topbar.style.position="relative"}}else{oldSidebarScrollPosition=null}};window.rustdocMobileScrollUnlock=function(){const mobile_topbar=document.querySelector(".mobile-topbar");if(oldSidebarScrollPosition!==null){document.body.style.width="";document.body.style.position="";document.body.style.top="";if(mobile_topbar){mobile_topbar.style.top="";mobile_topbar.style.position=""}window.scrollTo(0,oldSidebarScrollPosition);oldSidebarScrollPosition=null}};function showSidebar(){window.rustdocMobileScrollLock();const sidebar=document.getElementsByClassName("sidebar")[0];addClass(sidebar,"shown")}function hideSidebar(){window.rustdocMobileScrollUnlock();const sidebar=document.getElementsByClassName("sidebar")[0];removeClass(sidebar,"shown")}window.addEventListener("resize",()=>{if(window.innerWidth>window.RUSTDOC_MOBILE_BREAKPOINT&&oldSidebarScrollPosition!==null){hideSidebar()}});function handleClick(id,f){const elem=document.getElementById(id);if(elem){elem.addEventListener("click",f)}}handleClick(MAIN_ID,()=>{hideSidebar()});onEachLazy(document.getElementsByTagName("a"),el=>{if(el.hash){el.addEventListener("click",()=>{expandSection(el.hash.slice(1));hideSidebar()})}});onEachLazy(document.querySelectorAll(".rustdoc-toggle > summary:not(.hideme)"),el=>{el.addEventListener("click",e=>{if(e.target.tagName!=="SUMMARY"&&e.target.tagName!=="A"){e.preventDefault()}})});onEachLazy(document.getElementsByClassName("notable-traits"),e=>{e.onclick=function(){this.getElementsByClassName("notable-traits-tooltiptext")[0].classList.toggle("force-tooltip")}});const sidebar_menu_toggle=document.getElementsByClassName("sidebar-menu-toggle")[0];if(sidebar_menu_toggle){sidebar_menu_toggle.addEventListener("click",()=>{const sidebar=document.getElementsByClassName("sidebar")[0];if(!hasClass(sidebar,"shown")){showSidebar()}else{hideSidebar()}})}function helpBlurHandler(event){blurHandler(event,getHelpButton(),window.hidePopoverMenus)}function buildHelpMenu(){const book_info=document.createElement("span");book_info.className="top";book_info.innerHTML="You can find more information in \ + the rustdoc book.";const shortcuts=[["?","Show this help dialog"],["S","Focus the search field"],["↑","Move up in search results"],["↓","Move down in search results"],["← / →","Switch result tab (when results focused)"],["⏎","Go to active search result"],["+","Expand all sections"],["-","Collapse all sections"],].map(x=>"
"+x[0].split(" ").map((y,index)=>((index&1)===0?""+y+"":" "+y+" ")).join("")+"
"+x[1]+"
").join("");const div_shortcuts=document.createElement("div");addClass(div_shortcuts,"shortcuts");div_shortcuts.innerHTML="

Keyboard Shortcuts

"+shortcuts+"
";const infos=["Prefix searches with a type followed by a colon (e.g., fn:) to \ + restrict the search to a given item kind.","Accepted kinds are: fn, mod, struct, \ + enum, trait, type, macro, \ + and const.","Search functions by type signature (e.g., vec -> usize or \ + -> vec)","Search multiple things at once by splitting your query with comma (e.g., \ + str,u8 or String,struct:Vec,test)","You can look for items with an exact name by putting double quotes around \ + your request: \"string\"","Look for items inside another one by searching for a path: vec::Vec",].map(x=>"

"+x+"

").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="

Search Tricks

"+infos;const rustdoc_version=document.createElement("span");rustdoc_version.className="bottom";const rustdoc_version_code=document.createElement("code");rustdoc_version_code.innerText="rustdoc "+getVar("rustdoc-version");rustdoc_version.appendChild(rustdoc_version_code);const container=document.createElement("div");if(!isHelpPage){container.className="popover"}container.id="help";container.style.display="none";const side_by_side=document.createElement("div");side_by_side.className="side-by-side";side_by_side.appendChild(div_shortcuts);side_by_side.appendChild(div_infos);container.appendChild(book_info);container.appendChild(side_by_side);container.appendChild(rustdoc_version);if(isHelpPage){const help_section=document.createElement("section");help_section.appendChild(container);document.getElementById("main-content").appendChild(help_section);container.style.display="block"}else{const help_button=getHelpButton();help_button.appendChild(container);container.onblur=helpBlurHandler;container.onclick=event=>{event.preventDefault()};help_button.onblur=helpBlurHandler;help_button.children[0].onblur=helpBlurHandler}return container}window.hidePopoverMenus=function(){onEachLazy(document.querySelectorAll(".search-container .popover"),elem=>{elem.style.display="none"})};function getHelpMenu(buildNeeded){let menu=getHelpButton().querySelector(".popover");if(!menu&&buildNeeded){menu=buildHelpMenu()}return menu}function showHelp(){const menu=getHelpMenu(true);if(menu.style.display==="none"){window.hidePopoverMenus();menu.style.display=""}}if(isHelpPage){showHelp();document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault()})}else{document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault();const menu=getHelpMenu(true);const shouldShowHelp=menu.style.display==="none";if(shouldShowHelp){showHelp()}else{window.hidePopoverMenus()}})}setMobileTopbar();addSidebarItems();addSidebarCrates();onHashChange(null);window.addEventListener("hashchange",onHashChange);searchState.setup()}());(function(){let reset_button_timeout=null;window.copy_path=but=>{const parent=but.parentElement;const path=[];onEach(parent.childNodes,child=>{if(child.tagName==="A"){path.push(child.textContent)}});const el=document.createElement("textarea");el.value=path.join("::");el.setAttribute("readonly","");el.style.position="absolute";el.style.left="-9999px";document.body.appendChild(el);el.select();document.execCommand("copy");document.body.removeChild(el);but.children[0].style.display="none";let tmp;if(but.childNodes.length<2){tmp=document.createTextNode("✓");but.appendChild(tmp)}else{onEachLazy(but.childNodes,e=>{if(e.nodeType===Node.TEXT_NODE){tmp=e;return true}});tmp.textContent="✓"}if(reset_button_timeout!==null){window.clearTimeout(reset_button_timeout)}function reset_button(){tmp.textContent="";reset_button_timeout=null;but.children[0].style.display=""}reset_button_timeout=window.setTimeout(reset_button,1000)}}()) \ No newline at end of file diff --git a/docs/normalize.css b/docs/normalize.css new file mode 100644 index 000000000000..469959f13729 --- /dev/null +++ b/docs/normalize.css @@ -0,0 +1,2 @@ + /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} \ No newline at end of file diff --git a/docs/noscript.css b/docs/noscript.css new file mode 100644 index 000000000000..e816793aa128 --- /dev/null +++ b/docs/noscript.css @@ -0,0 +1 @@ + #main-content .attributes{margin-left:0 !important;}#copy-path{display:none;}nav.sub{display:none;} \ No newline at end of file diff --git a/docs/rust-logo.svg b/docs/rust-logo.svg new file mode 100644 index 000000000000..62424d8ffd76 --- /dev/null +++ b/docs/rust-logo.svg @@ -0,0 +1,61 @@ + + + diff --git a/docs/rustdoc.css b/docs/rustdoc.css new file mode 100644 index 000000000000..0110592b81f3 --- /dev/null +++ b/docs/rustdoc.css @@ -0,0 +1 @@ + @font-face {font-family:'Fira Sans';font-style:normal;font-weight:400;src:local('Fira Sans'),url("FiraSans-Regular.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:500;src:local('Fira Sans Medium'),url("FiraSans-Medium.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:400;src:local('Source Serif 4'),url("SourceSerif4-Regular.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:italic;font-weight:400;src:local('Source Serif 4 Italic'),url("SourceSerif4-It.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:700;src:local('Source Serif 4 Bold'),url("SourceSerif4-Bold.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:400;src:url("SourceCodePro-Regular.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:italic;font-weight:400;src:url("SourceCodePro-It.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:600;src:url("SourceCodePro-Semibold.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'NanumBarunGothic';src:url("NanumBarunGothic.ttf.woff2") format("woff2");font-display:swap;unicode-range:U+AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF;}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}html{content:"";}@media (prefers-color-scheme:light){html{content:"light";}}@media (prefers-color-scheme:dark){html{content:"dark";}}body{font:1rem/1.5 "Source Serif 4",NanumBarunGothic,serif;margin:0;position:relative;overflow-wrap:break-word;overflow-wrap:anywhere;-webkit-font-feature-settings:"kern","liga";-moz-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";background-color:var(--main-background-color);color:var(--main-color);}h1{font-size:1.5rem;}h2{font-size:1.375rem;}h3{font-size:1.25rem;}h1,h2,h3,h4,h5,h6{font-weight:500;}h1,h2,h3,h4{margin:25px 0 15px 0;padding-bottom:6px;}.docblock h3,.docblock h4,h5,h6{margin:15px 0 5px 0;}.docblock>h2:first-child,.docblock>h3:first-child,.docblock>h4:first-child,.docblock>h5:first-child,.docblock>h6:first-child{margin-top:0;}h1.fqn{margin:0;padding:0;flex-grow:1;overflow-wrap:break-word;overflow-wrap:anywhere;}.main-heading{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:6px;margin-bottom:15px;}#toggle-all-docs{text-decoration:none;}.content h2,.top-doc .docblock>h3,.top-doc .docblock>h4{border-bottom:1px solid var(--headings-border-bottom-color);}h3.code-header{font-size:1.125rem;}h4.code-header{font-size:1rem;}.code-header{font-weight:600;border-bottom-style:none;margin:0;padding:0;}#crate-search,h1,h2,h3,h4,h5,h6,.sidebar,.mobile-topbar,.search-input,.search-results .result-name,.item-left>a,.out-of-band,span.since,a.srclink,#help-button>a,details.rustdoc-toggle.top-doc>summary,details.rustdoc-toggle.non-exhaustive>summary,.scraped-example-title,.more-examples-toggle summary,.more-examples-toggle .hide-more,.example-links a,ul.all-items{font-family:"Fira Sans",Arial,NanumBarunGothic,sans-serif;}a#toggle-all-docs,a.anchor,.small-section-header a,#source-sidebar a,pre.rust a,.sidebar h2 a,.sidebar h3 a,.mobile-topbar h2 a,h1 a,.search-results a,.module-item .stab,.import-item .stab,.result-name .primitive>i,.result-name .keyword>i,.method .where,.fn .where,.where.fmt-newline{color:var(--main-color);}.content span.enum,.content a.enum,.content span.struct,.content a.struct,.content span.union,.content a.union,.content span.primitive,.content a.primitive,.content span.type,.content a.type,.content span.foreigntype,.content a.foreigntype{color:var(--type-link-color);}.content span.trait,.content a.trait,.content span.traitalias,.content a.traitalias{color:var(--trait-link-color);}.content span.associatedtype,.content a.associatedtype,.content span.constant,.content a.constant,.content span.static,.content a.static{color:var(--assoc-item-link-color);}.content span.fn,.content a.fn,.content .fnname,.content span.method,.content a.method,.content span.tymethod,.content a.tymethod{color:var(--function-link-color);}.content span.attr,.content a.attr,.content span.derive,.content a.derive,.content span.macro,.content a.macro{color:var(--macro-link-color);}.content span.mod,.content a.mod{color:var(--mod-link-color);}.content span.keyword,.content a.keyword{color:var(--keyword-link-color);}a{color:var(--link-color);}ol,ul{padding-left:24px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:.625em;}p{margin:0 0 .75em 0;}p:last-child{margin:0;}summary{outline:none;}button{padding:1px 6px;}.rustdoc{display:flex;flex-direction:row;flex-wrap:nowrap;}main{position:relative;flex-grow:1;padding:10px 15px 40px 45px;min-width:0;}.source main{padding:15px;}.width-limiter{max-width:960px;margin-right:auto;}.source .width-limiter{max-width:unset;}details:not(.rustdoc-toggle) summary{margin-bottom:.6em;}code,pre,a.test-arrow,.code-header{font-family:"Source Code Pro",monospace;}.docblock code,.docblock-short code{border-radius:3px;padding:0 0.125em;}.docblock pre code,.docblock-short pre code{padding:0;}pre{padding:14px;}.item-decl pre{overflow-x:auto;}.source .content pre{padding:20px;}img{max-width:100%;}.source .content{overflow:visible;}.sub-logo-container{line-height:0;}.sub-logo-container>img{height:60px;width:60px;object-fit:contain;}.sidebar,.mobile-topbar,.sidebar-menu-toggle{background-color:var(--sidebar-background-color);}.sidebar{font-size:0.875rem;width:200px;min-width:200px;overflow-y:scroll;position:sticky;height:100vh;top:0;left:0;}.rustdoc.source .sidebar{width:50px;min-width:0px;max-width:300px;flex-grow:0;flex-shrink:0;flex-basis:auto;border-right:1px solid;overflow-x:hidden;overflow-y:hidden;}.rustdoc.source .sidebar .sidebar-logo{display:none;}.source .sidebar,#sidebar-toggle,#source-sidebar{background-color:var(--sidebar-background-color);}#sidebar-toggle>button:hover,#sidebar-toggle>button:focus{background-color:var(--sidebar-background-color-hover);}.source .sidebar>*:not(#sidebar-toggle){visibility:hidden;}.source-sidebar-expanded .source .sidebar{overflow-y:auto;width:300px;}.source-sidebar-expanded .source .sidebar>*:not(#sidebar-toggle){visibility:visible;}#all-types{margin-top:1em;}*{scrollbar-width:initial;scrollbar-color:var(--scrollbar-color);}.sidebar{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color);}::-webkit-scrollbar{width:12px;}.sidebar::-webkit-scrollbar{width:8px;}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0;background-color:var(--scrollbar-track-background-color);}.sidebar::-webkit-scrollbar-track{background-color:var(--scrollbar-track-background-color);}::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-background-color);}.hidden{display:none !important;}.sidebar .logo-container{display:flex;margin-top:10px;margin-bottom:10px;justify-content:center;}.version{overflow-wrap:break-word;}.logo-container>img{height:100px;width:100px;}ul.block,.block li{padding:0;margin:0;list-style:none;}.block a,.sidebar h2 a,.sidebar h3 a{display:block;padding:0.25rem;margin-left:-0.25rem;text-overflow:ellipsis;overflow:hidden;}.sidebar h2{overflow-wrap:anywhere;padding:0;margin:0;margin-top:0.7rem;margin-bottom:0.7rem;}.sidebar h3{font-size:1.125rem;padding:0;margin:0;}.sidebar-elems,.sidebar>h2{padding-left:24px;}.sidebar a,.sidebar .current{color:var(--sidebar-link-color);}.sidebar .current,.sidebar a:hover{background-color:var(--sidebar-current-link-background-color);}.sidebar-elems .block{margin-bottom:2em;}.sidebar-elems .block li a{white-space:nowrap;}.mobile-topbar{display:none;}.source .content pre.rust{white-space:pre;overflow:auto;padding-left:0;}.rustdoc .example-wrap{display:flex;position:relative;margin-bottom:10px;}.rustdoc .example-wrap:last-child{margin-bottom:0px;}pre.example-line-numbers{overflow:initial;border:1px solid;padding:13px 8px;text-align:right;border-top-left-radius:5px;border-bottom-left-radius:5px;}.src-line-numbers{text-align:right;}.rustdoc:not(.source) .example-wrap>pre:not(.example-line-numbers){width:100%;overflow-x:auto;}.rustdoc:not(.source) .example-wrap>pre.src-line-numbers{width:auto;overflow-x:visible;}.rustdoc .example-wrap>pre{margin:0;}.search-loading{text-align:center;}.content>.example-wrap pre.src-line-numbers{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}.src-line-numbers span{cursor:pointer;}.docblock-short{overflow-wrap:break-word;overflow-wrap:anywhere;overflow:hidden;text-overflow:ellipsis;}.docblock>:not(pre)>code,.docblock-short>code{white-space:pre-wrap;}.top-doc .docblock h2{font-size:1.375rem;}.top-doc .docblock h3{font-size:1.25rem;}.top-doc .docblock h4,.top-doc .docblock h5{font-size:1.125rem;}.top-doc .docblock h6{font-size:1rem;}.docblock h5{font-size:1rem;}.docblock h6{font-size:0.875rem;}.docblock{margin-left:24px;position:relative;}.docblock>:not(.more-examples-toggle):not(.example-wrap){max-width:100%;overflow-x:auto;}.out-of-band{flex-grow:0;font-size:1.125rem;}.docblock code,.docblock-short code,pre,.rustdoc.source .example-wrap{background-color:var(--code-block-background-color);}#main-content{position:relative;}.docblock table{margin:.5em 0;width:calc(100% - 2px);overflow-x:auto;display:block;border-collapse:collapse;}.docblock table td{padding:.5em;border:1px dashed var(--border-color);vertical-align:top;}.docblock table th{padding:.5em;text-align:left;border:1px solid var(--border-color);}.method .where,.fn .where,.where.fmt-newline{display:block;font-size:0.875rem;}.item-info{display:block;margin-left:24px;}.item-info code{font-size:0.875rem;}#main-content>.item-info{margin-top:0;margin-left:0;}nav.sub{flex-grow:1;flex-flow:row nowrap;margin:4px 0 25px 0;display:flex;align-items:center;}nav.sub form{flex-grow:1;}.source nav.sub{margin:0 0 15px 0;}.source nav.sub form{margin-left:32px;}a{text-decoration:none;}.small-section-header{display:flex;justify-content:space-between;position:relative;}.small-section-header:hover>.anchor{display:initial;}.impl:hover>.anchor,.trait-impl:hover>.anchor{display:inline-block;position:absolute;}.anchor{display:none;position:absolute;left:-0.5em;background:none !important;}.anchor.field{left:-5px;}.small-section-header>.anchor{left:-15px;padding-right:8px;}h2.small-section-header>.anchor{padding-right:6px;}.anchor::before{content:'§';}.main-heading a:hover,.example-wrap>pre.rust a:hover,.all-items a:hover,.docblock a:not(.test-arrow):not(.scrape-help):hover,.docblock-short a:not(.test-arrow):not(.scrape-help):hover,.item-info a{text-decoration:underline;}.crate.block a.current{font-weight:500;}table,.item-table{overflow-wrap:break-word;}.item-table{display:table;}.item-row{display:table-row;}.item-left,.item-right{display:table-cell;}.item-left{padding-right:1.25rem;}.search-container{position:relative;display:flex;height:34px;}.search-results-title{margin-top:0;white-space:nowrap;display:inline-flex;max-width:100%;align-items:baseline;}#crate-search-div{display:inline-block;position:relative;min-width:5em;}#crate-search{min-width:115px;padding:0;padding-left:4px;padding-right:23px;max-width:100%;text-overflow:ellipsis;border:1px solid var(--border-color);border-radius:4px;outline:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;text-indent:0.01px;background-color:var(--main-background-color);color:inherit;line-height:1.5;font-weight:500;}@-moz-document url-prefix(){#crate-search{padding-left:0px;padding-right:19px;}}#crate-search-div::after{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0;content:"";background-repeat:no-repeat;background-size:20px;background-position:calc(100% - 2px) 56%;background-image:url("down-arrow.svg");}#crate-search>option{font-size:1rem;}.search-input{-webkit-appearance:none;-moz-box-sizing:border-box !important;box-sizing:border-box !important;outline:none;border:1px solid var(--border-color);border-radius:2px;padding:8px;font-size:1rem;width:100%;background-color:var(--button-background-color);color:var(--search-color);}.search-input:focus{border-color:var(--search-input-focused-border-color);}.search-results{display:none;padding-bottom:2em;}.search-results.active{display:block;clear:both;}.search-results .desc>span{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:block;}.search-results>a{display:block;margin-left:2px;margin-right:2px;border-bottom:1px solid #aaa3;}.search-results>a>div{display:flex;flex-flow:row wrap;}.search-results .result-name,.search-results div.desc{width:50%;}.search-results .result-name{padding-right:1em;}.search-results a:hover,.search-results a:focus{background-color:var(--search-result-link-focus-background-color);}.popover{font-size:1rem;position:absolute;right:0;z-index:2;display:block;margin-top:7px;border-radius:3px;border:1px solid var(--border-color);font-size:1rem;}.popover::before{content:'';position:absolute;right:11px;border:solid var(--border-color);border-width:1px 1px 0 0;display:inline-block;padding:4px;transform:rotate(-45deg);top:-5px;}.popover,.popover::before{background-color:var(--main-background-color);color:var(--main-color);}#help.popover{max-width:600px;}#help.popover::before{right:48px;}#help dt{float:left;clear:left;display:block;margin-right:0.5rem;}#help span.top,#help span.bottom{text-align:center;display:block;font-size:1.125rem;}#help span.top{margin:10px 0;border-bottom:1px solid var(--border-color);padding-bottom:4px;margin-bottom:6px;}#help span.bottom{clear:both;border-top:1px solid var(--border-color);}.side-by-side>div{width:50%;float:left;padding:0 20px 20px 17px;}.item-info .stab{width:fit-content;min-height:36px;display:flex;align-items:center;white-space:pre-wrap;}.stab{padding:3px;margin-bottom:5px;font-size:0.875rem;font-weight:normal;color:var(--main-color);background-color:var(--stab-background-color);}.stab.portability>code{background:none;color:var(--stab-code-color);}.stab .emoji{font-size:1.25rem;margin-right:0.3rem;}.docblock .stab{padding:0 0.125em;margin-bottom:0;}.emoji{text-shadow:1px 0 0 black,-1px 0 0 black,0 1px 0 black,0 -1px 0 black;}.module-item .stab,.import-item .stab{border-radius:3px;display:inline-block;font-size:0.875rem;line-height:1.2;margin-bottom:0;margin-left:0.3125em;padding:2px;vertical-align:text-bottom;}.module-item.unstable,.import-item.unstable{opacity:0.65;}.since{font-weight:normal;font-size:initial;}.rightside{padding-left:12px;padding-right:2px;float:right;}.rightside:not(a),.out-of-band{color:var(--right-side-color);}pre.rust{tab-size:4;-moz-tab-size:4;}pre.rust .kw{color:var(--code-highlight-kw-color);}pre.rust .kw-2{color:var(--code-highlight-kw-2-color);}pre.rust .lifetime{color:var(--code-highlight-lifetime-color);}pre.rust .prelude-ty{color:var(--code-highlight-prelude-color);}pre.rust .prelude-val{color:var(--code-highlight-prelude-val-color);}pre.rust .string{color:var(--code-highlight-string-color);}pre.rust .number{color:var(--code-highlight-number-color);}pre.rust .bool-val{color:var(--code-highlight-literal-color);}pre.rust .self{color:var(--code-highlight-self-color);}pre.rust .attribute{color:var(--code-highlight-attribute-color);}pre.rust .macro,pre.rust .macro-nonterminal{color:var(--code-highlight-macro-color);}pre.rust .question-mark{font-weight:bold;color:var(--code-highlight-question-mark-color);}pre.rust .comment{color:var(--code-highlight-comment-color);}pre.rust .doccomment{color:var(--code-highlight-doc-comment-color);}.example-wrap.compile_fail,.example-wrap.should_panic{border-left:2px solid var(--codeblock-error-color);}.ignore.example-wrap{border-left:2px solid var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover,.example-wrap.should_panic:hover{border-left:2px solid var(--codeblock-error-hover-color);}.example-wrap.ignore:hover{border-left:2px solid var(--codeblock-ignore-hover-color);}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip{color:var(--codeblock-error-color);}.example-wrap.ignore .tooltip{color:var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover .tooltip,.example-wrap.should_panic:hover .tooltip{color:var(--codeblock-error-hover-color);}.example-wrap.ignore:hover .tooltip{color:var(--codeblock-ignore-hover-color);}.example-wrap .tooltip{position:absolute;display:block;cursor:pointer;left:-25px;top:5px;}.example-wrap .tooltip::after{display:none;text-align:center;padding:5px 3px 3px 3px;border-radius:6px;margin-left:5px;font-size:1rem;border:1px solid var(--border-color);position:absolute;width:max-content;top:-2px;z-index:1;}.example-wrap .tooltip::before{content:" ";position:absolute;top:50%;left:16px;margin-top:-5px;border-width:5px;border-style:solid;display:none;z-index:1;}.example-wrap.ignore .tooltip::after{content:"This example is not tested";}.example-wrap.compile_fail .tooltip::after{content:"This example deliberately fails to compile";}.example-wrap.should_panic .tooltip::after{content:"This example panics";}.example-wrap.edition .tooltip::after{content:"This code runs with edition " attr(data-edition);}.example-wrap .tooltip:hover::before,.example-wrap .tooltip:hover::after{display:inline;}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip,.example-wrap.ignore .tooltip{font-weight:bold;font-size:1.25rem;}a.test-arrow{display:inline-block;visibility:hidden;position:absolute;padding:5px 10px 5px 10px;border-radius:5px;font-size:1.375rem;top:5px;right:5px;z-index:1;}.example-wrap:hover .test-arrow{visibility:visible;}a.test-arrow:hover{text-decoration:none;}.code-attribute{font-weight:300;color:var(--code-attribute-color);}.item-spacer{width:100%;height:12px;}.out-of-band>span.since{font-size:1.25rem;}h3.variant{font-weight:600;font-size:1.125rem;margin-bottom:10px;}.sub-variant h4{font-size:1rem;font-weight:400;margin-top:0;margin-bottom:0;}.sub-variant{margin-left:24px;margin-bottom:40px;}.sub-variant>.sub-variant-field{margin-left:24px;}:target>code,:target>.code-header{opacity:1;}:target{padding-right:3px;}.notable-traits-tooltip{display:inline-block;cursor:pointer;}.notable-traits:hover .notable-traits-tooltiptext,.notable-traits .notable-traits-tooltiptext.force-tooltip{display:inline-block;}.notable-traits .notable-traits-tooltiptext{display:none;padding:5px 3px 3px 3px;border-radius:6px;margin-left:5px;z-index:10;font-size:1rem;cursor:default;position:absolute;border:1px solid;}.notable-traits-tooltip::after{content:"\00a0\00a0\00a0";}.notable-traits .notable,.notable-traits .docblock{margin:0;}.notable-traits .notable{margin:0;margin-bottom:13px;font-size:1.1875rem;font-weight:600;display:block;}.notable-traits .docblock code.content{margin:0;padding:0;font-size:1.25rem;}.search-failed{text-align:center;margin-top:20px;display:none;}.search-failed.active{display:block;}.search-failed>ul{text-align:left;max-width:570px;margin-left:auto;margin-right:auto;}#titles{display:flex;flex-direction:row;gap:1px;margin-bottom:4px;}#titles>button{text-align:center;font-size:1.125rem;cursor:pointer;border:0;border-top:2px solid;flex:1;line-height:1.5;color:inherit;}#titles>button>div.count{display:inline-block;font-size:1rem;}.notable-traits{cursor:pointer;z-index:2;margin-left:5px;}#sidebar-toggle{position:sticky;top:0;left:0;font-size:1.25rem;border-bottom:1px solid;display:flex;height:40px;justify-content:center;align-items:center;z-index:10;}#source-sidebar{width:100%;overflow:auto;}#source-sidebar>.title{font-size:1.5rem;text-align:center;border-bottom:1px solid var(--border-color);margin-bottom:6px;}#sidebar-toggle>button{font-size:inherit;font-weight:bold;background:none;color:inherit;cursor:pointer;text-align:center;border:none;outline:none;position:absolute;top:0;bottom:0;left:0;right:0;width:100%;-webkit-appearance:none;opacity:1;}#settings-menu,#help-button{margin-left:4px;outline:none;}#settings-menu>a,#help-button>a,#copy-path{width:33px;cursor:pointer;line-height:1.5;}#settings-menu>a,#help-button>a{padding:5px;height:100%;display:block;background-color:var(--button-background-color);border:1px solid var(--border-color);border-radius:2px;}#copy-path{color:var(--copy-path-button-color);background:var(--main-background-color);height:34px;margin-left:10px;padding:0;padding-left:2px;border:0;}#copy-path>img{filter:var(--copy-path-img-filter);}#copy-path:hover>img{filter:var(--copy-path-img-hover-filter);}@keyframes rotating{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}#settings-menu.rotate>a img{animation:rotating 2s linear infinite;}#help-button>a{text-align:center;font-size:20px;padding-top:2px;}kbd{display:inline-block;padding:3px 5px;font:15px monospace;line-height:10px;vertical-align:middle;border:solid 1px var(--border-color);border-radius:3px;cursor:default;}ul.all-items>li{list-style:none;}details.dir-entry{padding-left:4px;}details.dir-entry>summary::after{content:" ►";position:absolute;left:-15px;top:0px;font-size:80%;padding:2px 0px;width:25px;}details[open].dir-entry>summary::after{content:" ▼";}details.dir-entry>summary::-webkit-details-marker,details.dir-entry>summary::marker{display:none;}details.dir-entry>summary{margin:0 0 0 13px;list-style:none;cursor:pointer;position:relative;}details.dir-entry div.folders,details.dir-entry div.files{padding-left:23px;}details.dir-entry a{display:block;}details.rustdoc-toggle{contain:layout;position:relative;}details.rustdoc-toggle>summary.hideme{cursor:pointer;}details.rustdoc-toggle>summary{list-style:none;}details.rustdoc-toggle>summary::-webkit-details-marker,details.rustdoc-toggle>summary::marker{display:none;}details.rustdoc-toggle>summary.hideme>span{margin-left:9px;}details.rustdoc-toggle>summary::before{content:"";cursor:pointer;width:16px;height:16px;background-repeat:no-repeat;background-position:top left;display:inline-block;vertical-align:middle;opacity:.5;}details.rustdoc-toggle>summary.hideme>span,.more-examples-toggle summary,.more-examples-toggle .hide-more{color:var(--toggles-color);}details.rustdoc-toggle>summary::after{content:"Expand";overflow:hidden;width:0;height:0;position:absolute;}details.rustdoc-toggle>summary.hideme::after{content:"";}details.rustdoc-toggle>summary:focus::before,details.rustdoc-toggle>summary:hover::before{opacity:1;}details.rustdoc-toggle.top-doc>summary,details.rustdoc-toggle.top-doc>summary::before,details.rustdoc-toggle.non-exhaustive>summary,details.rustdoc-toggle.non-exhaustive>summary::before{font-size:1rem;}details.non-exhaustive{margin-bottom:8px;}details.rustdoc-toggle>summary.hideme::before{position:relative;}details.rustdoc-toggle>summary:not(.hideme)::before{position:absolute;left:-24px;top:4px;}.impl-items>details.rustdoc-toggle>summary:not(.hideme)::before{position:absolute;left:-24px;}details.rustdoc-toggle[open] >summary.hideme{position:absolute;}details.rustdoc-toggle[open] >summary.hideme>span{display:none;}details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle[open] >summary.hideme::before{background-image:url("toggle-minus.svg");}details.rustdoc-toggle>summary::before{background-image:url("toggle-plus.svg");}details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle[open] >summary.hideme::before{width:16px;height:16px;background-repeat:no-repeat;background-position:top left;display:inline-block;content:"";}details.rustdoc-toggle[open] >summary::after,details.rustdoc-toggle[open] >summary.hideme::after{content:"Collapse";}.docblock summary>*{display:inline-block;}.docblock>.example-wrap:first-child .tooltip{margin-top:16px;}@media (max-width:700px){*[id]{scroll-margin-top:45px;}.rustdoc{padding-top:0px;display:block;}main{padding-left:15px;padding-top:0px;}.main-heading{flex-direction:column;}.out-of-band{text-align:left;margin-left:initial;padding:initial;}.out-of-band .since::before{content:"Since ";}#copy-path{display:none;}.sidebar .sidebar-logo,.sidebar .location{display:none;}.sidebar{position:fixed;top:45px;left:-1000px;margin-left:0;margin:0;padding:0;z-index:11;height:calc(100vh - 45px);}.source main,.rustdoc.source .sidebar{top:0;padding:0;height:100vh;border:0;}.sidebar.shown,.source-sidebar-expanded .source .sidebar,.sidebar:focus-within{left:0;}.rustdoc.source>.sidebar{width:0;}.mobile-topbar h2{padding-bottom:0;margin:auto 0.5em auto auto;overflow:hidden;font-size:24px;}.mobile-topbar h2 a{display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}.mobile-topbar .logo-container{max-height:45px;}.mobile-topbar .logo-container>img{max-width:35px;max-height:35px;margin-left:20px;margin-top:5px;margin-bottom:5px;}.mobile-topbar{display:flex;flex-direction:row;position:sticky;z-index:10;font-size:2rem;height:45px;width:100%;left:0;top:0;}.sidebar-menu-toggle{width:45px;font-size:32px;border:none;color:var(--main-color);}.sidebar-elems{margin-top:1em;background-color:var(--sidebar-background-color);}.content{margin-left:0px;}.anchor{display:none !important;}#titles>button>div.count{display:block;}#sidebar-filler{position:fixed;left:45px;width:calc(100% - 45px);top:0;height:45px;z-index:-1;border-bottom:1px solid;}#main-content>details.rustdoc-toggle>summary::before,#main-content>div>details.rustdoc-toggle>summary::before{left:-11px;}#sidebar-toggle{position:fixed;left:1px;top:100px;width:30px;font-size:1.5rem;text-align:center;padding:0;z-index:10;border-top-right-radius:3px;border-bottom-right-radius:3px;cursor:pointer;border:1px solid;border-left:0;}.source-sidebar-expanded #sidebar-toggle{left:unset;top:unset;width:unset;border-top-right-radius:unset;border-bottom-right-radius:unset;position:sticky;border:0;border-bottom:1px solid;}.notable-traits .notable-traits-tooltiptext{left:0;top:100%;}#help-button{display:none;}.item-table{display:block;}.item-row{display:flex;flex-flow:column wrap;}.item-left,.item-right{width:100%;}.search-results>a{border-bottom:1px solid #aaa9;padding:5px 0px;}.search-results .result-name,.search-results div.desc{width:100%;}.search-results div.desc,.item-right{padding-left:2em;}.source-sidebar-expanded .source .sidebar{max-width:100vw;width:100vw;}details.rustdoc-toggle:not(.top-doc)>summary{margin-left:10px;}.impl-items>details.rustdoc-toggle>summary:not(.hideme)::before,#main-content>details.rustdoc-toggle:not(.top-doc)>summary::before,#main-content>div>details.rustdoc-toggle>summary::before{left:-11px;}.impl-items>.item-info{margin-left:34px;}.source nav.sub{margin:0;padding:8px;}}@media print{nav.sidebar,nav.sub,.out-of-band,a.srclink,#copy-path,details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle>summary::before,details.rustdoc-toggle.top-doc>summary{display:none;}.docblock{margin-left:0;}main{padding:10px;}}@media (max-width:464px){.docblock{margin-left:12px;}.docblock code{overflow-wrap:break-word;overflow-wrap:anywhere;}nav.sub{flex-direction:column;}nav.sub form{align-self:stretch;}.sub-logo-container>img{height:35px;width:35px;}#sidebar-toggle{top:10px;}.source-sidebar-expanded #sidebar-toggle{top:unset;}}.method-toggle>summary,.implementors-toggle>summary,.impl,#implementors-list>.docblock,.impl-items>section,.methods>section{margin-bottom:0.75em;}.method-toggle[open]:not(:last-child),.implementors-toggle[open]:not(:last-child){margin-bottom:2em;}#trait-implementations-list .method-toggle:not(:last-child),#synthetic-implementations-list .method-toggle:not(:last-child),#blanket-implementations-list .method-toggle:not(:last-child){margin-bottom:1em;}.scraped-example-list .scrape-help{margin-left:10px;padding:0 4px;font-weight:normal;font-size:12px;position:relative;bottom:1px;background:transparent;border-width:1px;border-style:solid;border-radius:50px;}.scraped-example .code-wrapper{position:relative;display:flex;flex-direction:row;flex-wrap:wrap;width:100%;}.scraped-example:not(.expanded) .code-wrapper{max-height:240px;}.scraped-example:not(.expanded) .code-wrapper pre{overflow-y:hidden;max-height:240px;padding-bottom:0;}.scraped-example:not(.expanded) .code-wrapper pre.src-line-numbers{overflow-x:hidden;}.scraped-example .code-wrapper .prev{position:absolute;top:0.25em;right:2.25em;z-index:100;cursor:pointer;}.scraped-example .code-wrapper .next{position:absolute;top:0.25em;right:1.25em;z-index:100;cursor:pointer;}.scraped-example .code-wrapper .expand{position:absolute;top:0.25em;right:0.25em;z-index:100;cursor:pointer;}.scraped-example:not(.expanded) .code-wrapper:before{content:" ";width:100%;height:5px;position:absolute;z-index:100;top:0;}.scraped-example:not(.expanded) .code-wrapper:after{content:" ";width:100%;height:5px;position:absolute;z-index:100;bottom:0;}.scraped-example .code-wrapper .src-line-numbers{margin:0;padding:14px 0;}.scraped-example .code-wrapper .src-line-numbers span{padding:0 14px;}.scraped-example .code-wrapper .example-wrap{flex:1;overflow-x:auto;overflow-y:hidden;margin-bottom:0;}.scraped-example:not(.expanded) .code-wrapper .example-wrap{overflow-x:hidden;}.scraped-example .code-wrapper .example-wrap pre.rust{overflow-x:inherit;width:inherit;overflow-y:hidden;}.more-examples-toggle{max-width:calc(100% + 25px);margin-top:10px;margin-left:-25px;}.more-examples-toggle .hide-more{margin-left:25px;margin-bottom:5px;cursor:pointer;}.more-scraped-examples{margin-left:5px;display:flex;flex-direction:row;}.more-scraped-examples-inner{width:calc(100% - 20px);}.toggle-line{align-self:stretch;margin-right:10px;margin-top:5px;padding:0 4px;cursor:pointer;}.toggle-line-inner{min-width:2px;height:100%;}.more-scraped-examples .scraped-example{margin-bottom:20px;}.more-scraped-examples .scraped-example:last-child{margin-bottom:0;}.example-links a{margin-top:20px;}.example-links ul{margin-bottom:0;} \ No newline at end of file diff --git a/docs/search-index.js b/docs/search-index.js new file mode 100644 index 000000000000..8a6424369d84 --- /dev/null +++ b/docs/search-index.js @@ -0,0 +1,80 @@ +var searchIndex = JSON.parse('{\ +"ahash":{"doc":"AHash is a hashing algorithm is intended to be a high …","t":[3,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AHasher","CallHasher","RandomState","borrow","borrow","borrow_mut","borrow_mut","build_hasher","clone","clone","clone_into","clone_into","default","default","finish","fmt","fmt","from","from","generate_with","get_hash","into","into","new","new_with_keys","set_random_source","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","with_seed","with_seeds","write","write_u128","write_u16","write_u32","write_u64","write_u8","write_usize"],"q":["ahash","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A Hasher for hashing an arbitrary stream of bytes.","Provides a way to get an optimized hasher for a given data …","Provides a Hasher factory. This is typically used (e.g. by …","","","","","Constructs a new AHasher with keys based on this …","","","","","Constructs a new AHasher with fixed keys. If std is …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Allows for supplying seeds, but each time it is called the …","","Calls U::from(self).","Calls U::from(self).","Use randomly generated keys","Creates a new hasher keyed to the provided key.","Provides an optional way to manually supply a source of …","","","","","","","","","Allows for explicitly setting a seed to used.","Allows for explicitly setting the seeds to used.","","","","","","",""],"i":[0,0,0,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,1,16,2,1,1,2,1,2,1,2,1,2,1,2,1,1,1,2,2,2,2,2,2,2],"f":[0,0,0,[[]],[[]],[[]],[[]],[1,2],[2,2],[1,1],[[]],[[]],[[],2],[[],1],[2,3],[[2,4],5],[[1,4],5],[[]],[[]],[[3,3,3,3],1],[[],3],[[]],[[]],[[],1],[[6,6],2],[[[0,[0,7,8]]],[[10,[9]]]],[[]],[[]],[[],10],[[],10],[[],10],[[],10],[[],11],[[],11],[12,1],[[3,3,3,3],1],[2],[[2,6]],[[2,13]],[[2,14]],[[2,3]],[[2,15]],[[2,12]]],"p":[[3,"RandomState"],[3,"AHasher"],[15,"u64"],[3,"Formatter"],[6,"Result"],[15,"u128"],[8,"Send"],[8,"Sync"],[15,"bool"],[4,"Result"],[3,"TypeId"],[15,"usize"],[15,"u16"],[15,"u32"],[15,"u8"],[8,"CallHasher"]]},\ +"aho_corasick":{"doc":"A library for finding occurrences of many patterns at …","t":[3,3,3,4,3,3,13,13,3,4,13,13,8,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,13,13,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AhoCorasick","AhoCorasickBuilder","Error","ErrorKind","FindIter","FindOverlappingIter","LeftmostFirst","LeftmostLongest","Match","MatchKind","PremultiplyOverflow","Standard","StateID","StateIDOverflow","StreamFindIter","anchored","ascii_case_insensitive","auto_configure","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build_with_size","byte_classes","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","dense_depth","description","dfa","earliest_find","end","eq","eq","find","find_iter","find_overlapping_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_usize","hash","heap_bytes","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_empty","is_match","kind","len","match_kind","match_kind","max_id","max_pattern_len","new","new","new_auto_configured","next","next","next","packed","pattern","pattern_count","prefilter","premultiply","provide","replace_all","replace_all_bytes","replace_all_with","replace_all_with_bytes","start","stream_find_iter","stream_replace_all","stream_replace_all_with","supports_overlapping","supports_stream","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_usize","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","max","max","requested_max","Builder","Config","FindIter","LeftmostFirst","LeftmostLongest","MatchKind","Searcher","add","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","builder","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","default","default","default","eq","extend","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","heap_bytes","into","into","into","into","into","into_iter","match_kind","match_kind","minimum_len","new","new","new","next","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id"],"q":["aho_corasick","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","aho_corasick::ErrorKind","","","aho_corasick::packed","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An automaton for searching multiple strings in linear time.","A builder for configuring an Aho-Corasick automaton.","An error that occurred during the construction of an …","The kind of error that occurred.","An iterator of non-overlapping matches in a particular …","An iterator of overlapping matches in a particular …","Use leftmost-first match semantics, which reports leftmost …","Use leftmost-longest match semantics, which reports …","A representation of a match reported by an Aho-Corasick …","A knob for controlling the match semantics of an …","An error that occurs when premultiplication of state IDs …","Use standard match semantics, which support overlapping …","A trait describing the representation of an automaton’s …","An error that occurs when constructing an automaton would …","An iterator that reports Aho-Corasick matches in a stream.","Enable anchored mode, which requires all matches to start …","Enable ASCII-aware case insensitive matching.","Automatically configure the settings on this builder …","","","","","","","","","","","","","","","","","","","Build an Aho-Corasick automaton using the configuration …","Build an Aho-Corasick automaton using the configuration …","Shrink the size of the transition alphabet by mapping …","","","","","","","","","","","","","","","Set the limit on how many NFA states use a dense …","","Compile the standard Aho-Corasick automaton into a …","Returns the location of the first detected match in …","The ending position of the match.","","","Returns the location of the first match according to the …","Returns an iterator of non-overlapping matches, using the …","Returns an iterator of overlapping matches in the given …","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from a usize to this implementation’s …","","Returns the approximate total amount of heap used by this …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if and only if this match is empty. That is, …","Returns true if and only if this automaton matches the …","Return the kind of this error.","The length, in bytes, of the match.","Returns the match kind used by this automaton.","Set the desired match semantics.","Return the maximum state identifier supported by this …","Returns the length of the longest pattern matched by this …","Create a new Aho-Corasick automaton using the default …","Create a new builder for configuring an Aho-Corasick …","Build an Aho-Corasick automaton with an automatically …","","","","A lower level API for packed multiple substring search, …","Returns the identifier of the pattern that matched.","Return the total number of patterns matched by this …","Enable heuristic prefilter optimizations.","Premultiply state identifiers in the transition table. …","","Replace all matches with a corresponding value in the …","Replace all matches using raw bytes with a corresponding …","Replace all matches using a closure called on each match. …","Replace all matches using raw bytes with a closure called …","The starting position of the match.","Returns an iterator of non-overlapping matches in the given","Search for and replace all matches of this automaton in …","Search the given reader and replace all matches of this …","Returns true if and only if this automaton supports …","Returns true if and only if this automaton supports stream …","","","","","","","","Convert this implementation’s representation to a usize.","","","","","","","","","","","","","","","","","","","","","","","","","","","","The maximum possible state ID.","The maximum possible state id.","The maximum ID required by premultiplication.","A builder for constructing a packed searcher from a …","The configuration for a packed multiple pattern searcher.","An iterator over non-overlapping matches from a packed …","Use leftmost-first match semantics, which reports leftmost …","Use leftmost-longest match semantics, which reports …","A knob for controlling the match semantics of a packed …","A packed searcher for quickly finding occurrences of …","Add the given pattern to this set to match.","","","","","","","","","","","Build a searcher from the patterns added to this builder …","Create a packed builder from this configuration. The …","","","","","","","","","","","","","Add the given iterator of patterns to this set to match.","Return the first occurrence of any of the patterns in this …","Return the first occurrence of any of the patterns in this …","Return an iterator of non-overlapping occurrences of the …","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the approximate total amount of heap used by this …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Set the match semantics for this configuration.","Returns the match kind used by this packed searcher.","Returns the minimum length of a haystack that is required …","Create a new default configuration. A default …","Create a new builder for constructing a multi-pattern …","A convenience function for constructing a searcher from an …","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,8,8,0,0,9,8,0,9,0,1,1,1,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,1,1,1,3,1,8,4,9,10,3,1,8,4,9,10,1,8,1,4,1,3,10,8,10,3,3,3,3,15,16,20,1,8,4,4,9,10,3,15,16,20,1,8,4,9,10,7,10,3,3,15,16,20,1,8,4,9,10,15,16,20,10,3,4,10,3,1,7,3,3,1,3,15,16,20,0,10,3,1,1,4,3,3,3,3,10,3,3,3,3,3,3,1,8,4,9,10,4,7,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,33,34,34,0,0,0,31,31,0,0,28,31,30,28,29,32,31,30,28,29,32,28,30,31,30,28,29,31,30,28,29,31,30,28,31,28,29,29,29,31,30,28,29,32,31,30,28,29,32,29,31,30,28,29,32,32,30,29,29,30,28,29,32,31,30,28,29,31,30,28,29,32,31,30,28,29,32,31,30,28,29,32],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2],1],[[1,2],1],[1,1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,3],[1,[[5,[3,4]]]],[[1,2],1],[[[3,[[0,[6,7]]]]],[[3,[[0,[6,7]]]]]],[1,1],[8,8],[4,4],[9,9],[10,10],[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[],8],[[1,11],1],[4,12],[[1,2],1],[[[3,[7]],13],[[14,[10]]]],[10,11],[[8,8],2],[[10,10],2],[[[3,[7]],13],[[14,[10]]]],[[[3,[7]]],[[15,[7]]]],[[[3,[7]]],[[16,[7]]]],[[[3,[[0,[17,7]]]],18],19],[[[15,[[0,[17,7]]]],18],19],[[[16,[[0,[17,7]]]],18],19],[[[20,[17,[0,[17,7]]]],18],19],[[1,18],19],[[8,18],19],[[4,18],19],[[4,18],19],[[9,18],19],[[10,18],19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[11],[10],[[[3,[7]]],11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[10,2],[[[3,[7]],13],2],[4,9],[10,11],[[[3,[7]]],8],[[1,8],1],[[],11],[[[3,[7]]],11],[[],3],[[],1],[[],3],[[[15,[7]]],[[14,[10]]]],[[[16,[7]]],[[14,[10]]]],[[[20,[21,7]]],[[14,[[22,[10]]]]]],0,[10,11],[[[3,[7]]],11],[[1,2],1],[[1,2],1],[23],[[[3,[7]],12],24],[[[3,[7]]],[[26,[25]]]],[[[3,[7]],12,24]],[[[3,[7]],26]],[10,11],[[[3,[7]],21],[[20,[21,7]]]],[[[3,[7]]],22],[[[3,[7]]],22],[[[3,[7]]],2],[[[3,[7]]],2],[[]],[[]],[[]],[[]],[[]],[[]],[[],24],[[],11],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],0,0,0,0,0,0,0,0,0,0,[[28,13],28],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[28,[[14,[29]]]],[30,28],[31,31],[30,30],[28,28],[29,29],[[]],[[]],[[]],[[]],[[],31],[[],30],[[],28],[[31,31],2],[28,28],[[29,13],[[14,[10]]]],[[29,13,11],[[14,[10]]]],[29,32],[[31,18],19],[[30,18],19],[[28,18],19],[[29,18],19],[[32,18],19],[[]],[[]],[[]],[[]],[[]],[29,11],[[]],[[]],[[]],[[]],[[]],[[]],[[30,31],30],[29,31],[29,11],[[],30],[[],28],[[],[[14,[29]]]],[32,[[14,[10]]]],[[]],[[]],[[]],[[]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],27],[[],27],[[],27],[[],27],[[],27]],"p":[[3,"AhoCorasickBuilder"],[15,"bool"],[3,"AhoCorasick"],[3,"Error"],[4,"Result"],[8,"Clone"],[8,"StateID"],[4,"MatchKind"],[4,"ErrorKind"],[3,"Match"],[15,"usize"],[15,"str"],[8,"AsRef"],[4,"Option"],[3,"FindIter"],[3,"FindOverlappingIter"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[3,"StreamFindIter"],[8,"Read"],[6,"Result"],[3,"Demand"],[3,"String"],[15,"u8"],[3,"Vec"],[3,"TypeId"],[3,"Builder"],[3,"Searcher"],[3,"Config"],[4,"MatchKind"],[3,"FindIter"],[13,"StateIDOverflow"],[13,"PremultiplyOverflow"]]},\ +"aiofut":{"doc":"Straightforward Linux AIO using Futures/async/await.","t":[3,3,3,3,3,3,3,6,8,6,4,13,13,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AIO","AIOBatchSchedulerIn","AIOBatchSchedulerOut","AIOBuilder","AIOFuture","AIOManager","AIONotifier","AIOResult","EmulatedFailure","EmulatedFailureShared","Error","LowKernelRes","MaxEventsTooLarge","NotSupported","OtherError","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","copy_data","default","drop","drop","drop","fmt","from","from","from","from","from","from","from","from","get_id","get_npending","into","into","into","into","into","into","into","into","into_future","max_events","max_nbatched","max_nwait","poll","read","tick","timeout","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write"],"q":["aiofut","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Represent the necessary data for an AIO operation. …","","","","Represents a scheduled (future) asynchronous I/O …","Manager all AIOs.","The state machine for finished AIO operations and wakes up …","The result of an AIO operation: the number of bytes …","","","","","","","","","","","","","","","","","","","","","","","","Build an AIOManager object based on the configuration (and …","Get a copy of the current data in the buffer.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Get the number of pending AIOs (approximation).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Maximum concurrent async IO operations.","Maximum number of IOs per submission.","Maximum complete IOs per poll.","","","","Timeout for a polling iteration (default is None).","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,1,2,1,9,10,2,3,9,10,23,1,2,24,25,3,10,2,9,10,23,1,2,24,25,3,10,1,1,1,10,2,26,1,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,2],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,[[4,[2,3]]]],[[2,5],[[8,[[7,[6]]]]]],[[],1],[9],[10],[2],[[3,11],12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[10,5],[2,13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,14],1],[[1,13],1],[[1,15],1],[[[16,[10]],17],18],[[2,19,5,13,[8,[15]]],10],[[],[[8,[20]]]],[[1,14],1],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[2,19,5,22,[8,[15]]],10]],"p":[[3,"AIOBuilder"],[3,"AIOManager"],[4,"Error"],[4,"Result"],[15,"u64"],[15,"u8"],[3,"Vec"],[4,"Option"],[3,"AIO"],[3,"AIOFuture"],[3,"Formatter"],[6,"Result"],[15,"usize"],[15,"u32"],[15,"u16"],[3,"Pin"],[3,"Context"],[4,"Poll"],[6,"RawFd"],[15,"i64"],[3,"TypeId"],[3,"Box"],[3,"AIONotifier"],[3,"AIOBatchSchedulerIn"],[3,"AIOBatchSchedulerOut"],[8,"EmulatedFailure"]]},\ +"async_trait":{"doc":"github crates-io docs-rs","t":[23],"n":["async_trait"],"q":["async_trait"],"d":[""],"i":[0],"f":[0],"p":[]},\ +"bincode":{"doc":"Bincode is a crate for encoding and decoding using a tiny …","t":[2,3,13,2,13,2,6,4,13,13,13,13,13,2,6,13,3,13,11,11,11,11,11,0,5,11,11,0,11,5,5,5,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,8,3,3,11,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["BincodeRead","Config","Custom","DefaultOptions","DeserializeAnyNotSupported","Deserializer","Error","ErrorKind","InvalidBoolEncoding","InvalidCharEncoding","InvalidTagEncoding","InvalidUtf8Encoding","Io","Options","Result","SequenceMustHaveLength","Serializer","SizeLimit","borrow","borrow","borrow_mut","borrow_mut","cause","config","config","custom","custom","de","description","deserialize","deserialize_from","deserialize_from_custom","fmt","fmt","from","from","from","into","into","is_human_readable","new","options","provide","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_f32","serialize_f64","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_into","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","serialized_size","to_string","try_from","try_from","try_into","try_into","type_id","type_id","0","0","0","0","0","0","AllowTrailing","BigEndian","Bounded","Config","DefaultOptions","FixintEncoding","Infinite","LittleEndian","NativeEndian","Options","RejectTrailing","VarintEncoding","WithOtherEndian","WithOtherIntEncoding","WithOtherLimit","WithOtherTrailing","allow_trailing_bytes","big_endian","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","deserialize","deserialize","deserialize_from","deserialize_from","deserialize_from_custom","deserialize_from_custom","deserialize_from_custom_seed","deserialize_from_custom_seed","deserialize_from_seed","deserialize_from_seed","deserialize_seed","deserialize_seed","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","limit","little_endian","native_endian","new","no_limit","reject_trailing_bytes","serialize","serialize","serialize_into","serialize_into","serialized_size","serialized_size","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_big_endian","with_fixint_encoding","with_limit","with_little_endian","with_native_endian","with_no_limit","with_varint_encoding","Deserializer","borrow","borrow_mut","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","from","from_slice","into","is_human_readable","newtype_variant_seed","read","struct_variant","try_from","try_into","tuple_variant","type_id","unit_variant","variant_seed","with_bincode_read","with_reader","BincodeRead","IoReader","SliceReader","borrow","borrow","borrow_mut","borrow_mut","forward_read_bytes","forward_read_bytes","forward_read_bytes","forward_read_str","forward_read_str","forward_read_str","from","from","get_byte_buffer","get_byte_buffer","get_byte_buffer","into","into","read","read","read_exact","read_exact","try_from","try_from","try_into","try_into","type_id","type_id"],"q":["bincode","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::ErrorKind","","","","","bincode::config","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::de","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::de::read","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","A configuration builder whose options Bincode will use …","A custom error message from Serde.","","Serde has a deserialize_any method that lets the format …","","An error that can be produced during (de)serializing.","The kind of error that can be produced during a …","Returned if the deserializer attempts to deserialize a …","Returned if the deserializer attempts to deserialize a …","Returned if the deserializer attempts to deserialize the …","Returned if the deserializer attempts to deserialize a …","If the error stems from the reader/writer that is being …","","The result of a serialization or deserialization operation.","Bincode can not encode sequences of unknown length (like …","An Serializer that encodes values directly into a Writer.","If (de)serializing a message takes more than the provided …","","","","","","bincode uses a Builder-pattern to configure the …","Get a default configuration object.","","","Deserialize bincode data to a Rust data structure.","","Deserializes a slice of bytes into an instance of T using …","Deserializes an object directly from a Reader using the …","Deserializes an object from a custom BincodeReader using …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","Creates a new Serializer with the given Writer.","Get a default configuration object.","","Serializes a serializable object into a Vec of bytes using …","","","","","","","","","","","Serializes an object directly into a Writer using the …","","","","","","","","","","","","","","","","","","","","","Returns the size that an object would be if serialized …","","","","","","","","","","","","","","A TrailingBytes config that will allow trailing bytes in …","Big-endian byte ordering.","A SizeLimit that restricts serialized or deserialized …","A configuration builder whose options Bincode will use …","The default options for bincode …","Fixed-size integer encoding.","A SizeLimit without a limit! Use this if you don’t care …","Little-endian byte ordering.","The native byte ordering of the current system.","A configuration builder trait whose options Bincode will …","A TrailingBytes config that will cause bincode to produce …","Variable-size integer encoding (excepting [ui]8).","A configuration struct with a user-specified endian order","A configuration struct with a user-specified length …","A configuration struct with a user-specified byte limit","A configuration struct with a user-specified trailing …","Sets the deserializer to allow trailing bytes","Sets the endianness to big-endian","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Deserializes a slice of bytes into an instance of T using …","Deserializes a slice of bytes into an instance of T using …","Deserializes an object directly from a Reader using this …","Deserializes an object directly from a Reader using this …","Deserializes an object from a custom BincodeReader using …","Deserializes an object from a custom BincodeReader using …","Deserializes an object from a custom BincodeReader with …","Deserializes an object from a custom BincodeReader with …","Deserializes an object directly from a Reader with state …","Deserializes an object directly from a Reader with state …","Deserializes a slice of bytes with state seed using this …","Deserializes a slice of bytes with state seed using this …","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Sets the byte limit to limit.","Sets the endianness to little-endian This is the default.","Sets the endianness to the the machine-native endianness","Get a default configuration object.","Sets the byte limit to be unlimited. This is the default.","Sets the deserializer to reject trailing bytes","Serializes a serializable object into a Vec of bytes using …","Serializes a serializable object into a Vec of bytes using …","Serializes an object directly into a Writer using this …","Serializes an object directly into a Writer using this …","Returns the size that an object would be if serialized …","Returns the size that an object would be if serialized …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Sets the endianness to big-endian","Sets the length encoding to be fixed","Sets the byte limit to limit.","Sets the endianness to little-endian This is the default.","Sets the endianness to the the machine-native endianness","Sets the byte limit to be unlimited. This is the default.","Sets the length encoding to varint","A Deserializer that reads bytes from a buffer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Creates a new Deserializer that will read from the given …","Calls U::from(self).","","","Specialized ways to read data into bincode.","","","","","","","","Creates a new Deserializer with the given BincodeReader","Creates a new Deserializer with a given Reader and options.","An optional Read trait for advanced Bincode usage.","A BincodeRead implementation for io::Readers","A BincodeRead implementation for byte slices","","","","","Pass a slice of the next length bytes on to the serde …","","","Check that the next length bytes are a valid string and …","","","Returns the argument unchanged.","Returns the argument unchanged.","Transfer ownership of the next length bytes to the caller.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","",""],"i":[0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,1,0,1,12,1,12,1,1,0,0,6,6,0,1,0,0,0,1,1,6,12,1,12,1,12,12,0,1,0,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,1,12,1,12,1,12,1,59,60,61,62,63,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,16,15,4,15,4,15,4,15,4,15,4,15,4,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,4,4,4,16,4,15,15,4,15,4,15,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,15,15,15,15,15,15,15,0,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,0,55,55,55,55,55,55,55,55,55,0,0,0,56,57,56,57,53,56,57,53,56,57,56,57,53,56,57,56,57,56,57,56,57,56,57,56,57,56,57],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[1,[[3,[2]]]],0,[[],4],[5,6],[5,6],0,[1,7],[[],8],[[],8],[[],8],[[1,9],10],[[1,9],10],[11,6],[[]],[[]],[[]],[[]],[12,13],[[14,15],[[12,[14,15]]]],[[],16],[17],[[],[[8,[[19,[18]]]]]],[[12,13],8],[12,8],[[12,20],8],[[12,21],8],[[12,22],8],[[12,23],8],[[12,24],8],[[12,25],8],[[12,26],8],[[12,27],8],[[],8],[[12,[3,[28]]],8],[[12,7],8],[[12,7,29,7],8],[12,8],[[12,[3,[28]]],8],[12,8],[[12,7],8],[[12,7,28],8],[[12,7,29,7,28],8],[[12,28],8],[[12,7,28],8],[[12,7,29,7,28],8],[[12,30],8],[[12,31],8],[[12,29],8],[[12,32],8],[[12,18],8],[12,8],[[12,7],8],[[12,7,29,7],8],[[],[[8,[32]]]],[[],33],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[37,[36]]]],[4,4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[38,38],[39,39],[40,40],[41,41],[42,42],[4,4],[43,43],[44,44],[36,36],[45,45],[16,16],[[[47,[[0,[46,15]],[0,[46,0]]]]],[[47,[[0,[46,15]],[0,[46,0]]]]]],[[[48,[[0,[46,15]],[0,[46,0]]]]],[[48,[[0,[46,15]],[0,[46,0]]]]]],[[[49,[[0,[46,15]],[0,[46,0]]]]],[[49,[[0,[46,15]],[0,[46,0]]]]]],[[[37,[[0,[46,15]],[0,[46,0]]]]],[[37,[[0,[46,15]],[0,[46,0]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],16],[[],[[8,[50]]]],[4,[[8,[50]]]],[51,[[8,[52]]]],[[4,51],[[8,[52]]]],[53,[[8,[52]]]],[[4,53],[[8,[52]]]],[[54,53],8],[[4,54,53],8],[[54,51],8],[[4,54,51],8],[54,8],[[4,54],8],[[4,9],10],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[4,32],4],[4,4],[4,4],[[],16],[4,4],[[],[[37,[45]]]],[[],[[8,[[19,[18]]]]]],[4,[[8,[[19,[18]]]]]],[14,8],[[4,14],8],[[],[[8,[32]]]],[4,[[8,[32]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],[[48,[39]]]],[[],[[49,[41]]]],[32,[[47,[43]]]],[[],[[48,[38]]]],[[],[[48,[40]]]],[[],[[47,[44]]]],[[],[[49,[42]]]],0,[[]],[[]],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[[55,28],8],[[55,7,28],8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[[]],[15,[[55,[56,15]]]],[[]],[55,13],[55,8],0,[55,8],[[],34],[[],34],[[55,28],8],[[],35],[55,8],[55,8],[[53,15],[[55,[53,15]]]],[[51,15],[[55,[[57,[51]],15]]]],0,0,0,[[]],[[]],[[]],[[]],[28,8],[[56,28],8],[[57,28],8],[28,8],[[56,28],8],[[57,28],8],[[]],[[]],[28,[[8,[[19,[18]]]]]],[[56,28],[[8,[[19,[18]]]]]],[[57,28],[[8,[[19,[18]]]]]],[[]],[[]],[56,[[58,[28]]]],[[[57,[51]]],[[58,[28]]]],[56,58],[[[57,[51]]],58],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35]],"p":[[4,"ErrorKind"],[8,"Error"],[4,"Option"],[3,"Config"],[8,"Display"],[6,"Error"],[15,"str"],[6,"Result"],[3,"Formatter"],[6,"Result"],[3,"Error"],[3,"Serializer"],[15,"bool"],[8,"Write"],[8,"Options"],[3,"DefaultOptions"],[3,"Demand"],[15,"u8"],[3,"Vec"],[15,"char"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"usize"],[15,"u32"],[15,"u128"],[15,"u16"],[15,"u64"],[3,"String"],[4,"Result"],[3,"TypeId"],[3,"AllowTrailing"],[3,"WithOtherTrailing"],[3,"LittleEndian"],[3,"BigEndian"],[3,"NativeEndian"],[3,"FixintEncoding"],[3,"VarintEncoding"],[3,"Bounded"],[3,"Infinite"],[3,"RejectTrailing"],[8,"Clone"],[3,"WithOtherLimit"],[3,"WithOtherEndian"],[3,"WithOtherIntEncoding"],[8,"Deserialize"],[8,"Read"],[8,"DeserializeOwned"],[8,"BincodeRead"],[8,"DeserializeSeed"],[3,"Deserializer"],[3,"SliceReader"],[3,"IoReader"],[6,"Result"],[13,"Io"],[13,"InvalidUtf8Encoding"],[13,"InvalidBoolEncoding"],[13,"InvalidTagEncoding"],[13,"Custom"]]},\ +"bitflags":{"doc":"A typesafe bitmask flag generator useful for sets of …","t":[14],"n":["bitflags"],"q":["bitflags"],"d":["The macro used to generate the flag structures."],"i":[0],"f":[0],"p":[]},\ +"block_buffer":{"doc":"Fixed size buffer for block processing of data.","t":[6,3,8,3,6,3,3,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Block","BlockBuffer","BufferKind","Eager","EagerBuffer","Error","Lazy","LazyBuffer","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","default","default","default","digest_blocks","digest_pad","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","generic_array","get_data","get_pos","into","into","into","into","len128_padding_be","len64_padding_be","len64_padding_le","new","pad_with_zeros","remaining","reset","set","set_data","size","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_new","type_id","type_id","type_id","type_id"],"q":["block_buffer","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Block on which BlockBuffer operates.","Buffer for block processing of data.","Trait for buffer kinds.","Eager block buffer kind, which guarantees that buffer …","Eager block buffer.","Block buffer error.","Lazy block buffer kind, which guarantees that buffer …","Lazy block buffer.","","","","","","","","","","","","","","","","Digest data in input in blocks of size BlockSize using the …","Compress remaining data after padding it with delim, zeros …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Return slice of data stored inside the buffer.","Return current cursor position.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Pad message with 0x80, zeros and 128-bit message length …","Pad message with 0x80, zeros and 64-bit message length …","Pad message with 0x80, zeros and 64-bit message length …","Create new buffer from slice.","Pad remaining data with zeros and return resulting block.","Return number of remaining bytes in the internall buffer.","Reset buffer by setting cursor position to zero.","Set buffer content and cursor position.","Set data to generated blocks.","Return size of the internall buffer in bytes.","","","","","","","","","Create new buffer from slice.","","","",""],"i":[0,0,0,0,0,0,0,0,1,2,3,4,1,2,3,4,1,2,3,4,1,2,4,4,4,3,1,2,3,3,4,1,2,3,4,0,4,4,1,2,3,4,4,4,4,4,4,4,4,4,4,4,1,2,3,4,1,2,3,4,4,1,2,3,4],"f":[0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[4,4],[[],1],[[],2],[[],4],[[4,5]],[[[4,[1]],6,5]],[[3,3],7],[[1,8],9],[[2,8],9],[[3,8],[[11,[10]]]],[[3,8],9],[[[4,[12,12]],8],9],[[]],[[]],[[]],[[]],0,[4],[4,13],[[]],[[]],[[]],[[]],[[[4,[1]],14,5]],[[[4,[1]],15,5]],[[[4,[1]],15,5]],[[],4],[4,16],[4,13],[4],[[4,16,13]],[[[4,[1]],5]],[4,13],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],[[11,[4,3]]]],[[],17],[[],17],[[],17],[[],17]],"p":[[3,"Eager"],[3,"Lazy"],[3,"Error"],[3,"BlockBuffer"],[8,"FnMut"],[15,"u8"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Error"],[4,"Result"],[8,"Debug"],[15,"usize"],[15,"u128"],[15,"u64"],[6,"Block"],[3,"TypeId"]]},\ +"byteorder":{"doc":"This crate provides convenience methods for encoding and …","t":[6,4,8,6,4,6,6,8,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,10,11,11,10,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11],"n":["BE","BigEndian","ByteOrder","LE","LittleEndian","NativeEndian","NetworkEndian","ReadBytesExt","WriteBytesExt","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","default","default","eq","eq","fmt","fmt","from","from","from_slice_f32","from_slice_f32","from_slice_f32","from_slice_f64","from_slice_f64","from_slice_f64","from_slice_i128","from_slice_i16","from_slice_i32","from_slice_i64","from_slice_u128","from_slice_u128","from_slice_u128","from_slice_u16","from_slice_u16","from_slice_u16","from_slice_u32","from_slice_u32","from_slice_u32","from_slice_u64","from_slice_u64","from_slice_u64","hash","hash","into","into","partial_cmp","partial_cmp","read_f32","read_f32","read_f32","read_f32_into","read_f32_into","read_f32_into","read_f32_into_unchecked","read_f32_into_unchecked","read_f32_into_unchecked","read_f64","read_f64","read_f64","read_f64_into","read_f64_into","read_f64_into","read_f64_into_unchecked","read_f64_into_unchecked","read_f64_into_unchecked","read_i128","read_i128","read_i128","read_i128_into","read_i128_into","read_i128_into","read_i16","read_i16","read_i16","read_i16_into","read_i16_into","read_i16_into","read_i24","read_i24","read_i24","read_i32","read_i32","read_i32","read_i32_into","read_i32_into","read_i32_into","read_i48","read_i48","read_i48","read_i64","read_i64","read_i64","read_i64_into","read_i64_into","read_i64_into","read_i8","read_i8","read_i8_into","read_i8_into","read_int","read_int","read_int","read_int128","read_int128","read_int128","read_u128","read_u128","read_u128","read_u128","read_u128","read_u128_into","read_u128_into","read_u128_into","read_u128_into","read_u128_into","read_u16","read_u16","read_u16","read_u16","read_u16","read_u16_into","read_u16_into","read_u16_into","read_u16_into","read_u16_into","read_u24","read_u24","read_u24","read_u32","read_u32","read_u32","read_u32","read_u32","read_u32_into","read_u32_into","read_u32_into","read_u32_into","read_u32_into","read_u48","read_u48","read_u48","read_u64","read_u64","read_u64","read_u64","read_u64","read_u64_into","read_u64_into","read_u64_into","read_u64_into","read_u64_into","read_u8","read_u8","read_uint","read_uint","read_uint","read_uint","read_uint","read_uint128","read_uint128","read_uint128","read_uint128","read_uint128","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","write_f32","write_f32","write_f32","write_f32_into","write_f64","write_f64","write_f64","write_f64_into","write_i128","write_i128","write_i128","write_i128_into","write_i16","write_i16","write_i16","write_i16_into","write_i24","write_i24","write_i24","write_i32","write_i32","write_i32","write_i32_into","write_i48","write_i48","write_i48","write_i64","write_i64","write_i64","write_i64_into","write_i8","write_i8","write_i8_into","write_int","write_int","write_int","write_int128","write_int128","write_int128","write_u128","write_u128","write_u128","write_u128","write_u128","write_u128_into","write_u128_into","write_u128_into","write_u16","write_u16","write_u16","write_u16","write_u16","write_u16_into","write_u16_into","write_u16_into","write_u24","write_u24","write_u24","write_u32","write_u32","write_u32","write_u32","write_u32","write_u32_into","write_u32_into","write_u32_into","write_u48","write_u48","write_u48","write_u64","write_u64","write_u64","write_u64","write_u64","write_u64_into","write_u64_into","write_u64_into","write_u8","write_u8","write_uint","write_uint","write_uint","write_uint","write_uint","write_uint128","write_uint128","write_uint128","write_uint128","write_uint128"],"q":["byteorder","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A type alias for BigEndian.","Defines big-endian serialization.","ByteOrder describes types that can serialize integers as …","A type alias for LittleEndian.","Defines little-endian serialization.","Defines system native-endian serialization.","Defines network byte order serialization.","Extends Read with methods for reading numbers. (For std::io…","Extends Write with methods for writing numbers. (For …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Converts the given slice of IEEE754 single-precision (4 …","","","Converts the given slice of IEEE754 double-precision (8 …","","","Converts the given slice of signed 128 bit integers to a …","Converts the given slice of signed 16 bit integers to a …","Converts the given slice of signed 32 bit integers to a …","Converts the given slice of signed 64 bit integers to a …","Converts the given slice of unsigned 128 bit integers to a …","","","Converts the given slice of unsigned 16 bit integers to a …","","","Converts the given slice of unsigned 32 bit integers to a …","","","Converts the given slice of unsigned 64 bit integers to a …","","","","","Calls U::from(self).","Calls U::from(self).","","","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a sequence of IEEE754 single-precision (4 bytes) …","Reads a sequence of IEEE754 single-precision (4 bytes) …","Reads IEEE754 single-precision (4 bytes) floating point …","DEPRECATED.","DEPRECATED.","DEPRECATED.","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a sequence of IEEE754 double-precision (8 bytes) …","Reads a sequence of IEEE754 double-precision (8 bytes) …","Reads IEEE754 single-precision (4 bytes) floating point …","DEPRECATED.","DEPRECATED.","DEPRECATED.","Reads a signed 128 bit integer from the underlying reader.","Reads a signed 128 bit integer from the underlying reader.","Reads a signed 128 bit integer from buf.","Reads a sequence of signed 128 bit integers from the …","Reads a sequence of signed 128 bit integers from the …","Reads signed 128 bit integers from src into dst.","Reads a signed 16 bit integer from the underlying reader.","Reads a signed 16 bit integer from the underlying reader.","Reads a signed 16 bit integer from buf.","Reads a sequence of signed 16 bit integers from the …","Reads a sequence of signed 16 bit integers from the …","Reads signed 16 bit integers from src to dst.","Reads a signed 24 bit integer from the underlying reader.","Reads a signed 24 bit integer from the underlying reader.","Reads a signed 24 bit integer from buf, stored in i32.","Reads a signed 32 bit integer from the underlying reader.","Reads a signed 32 bit integer from the underlying reader.","Reads a signed 32 bit integer from buf.","Reads a sequence of signed 32 bit integers from the …","Reads a sequence of signed 32 bit integers from the …","Reads signed 32 bit integers from src into dst.","Reads a signed 48 bit integer from the underlying reader.","Reads a signed 48 bit integer from the underlying reader.","Reads a signed 48 bit integer from buf, stored in i64.","Reads a signed 64 bit integer from the underlying reader.","Reads a signed 64 bit integer from the underlying reader.","Reads a signed 64 bit integer from buf.","Reads a sequence of signed 64 bit integers from the …","Reads a sequence of signed 64 bit integers from the …","Reads signed 64 bit integers from src into dst.","Reads a signed 8 bit integer from the underlying reader.","Reads a signed 8 bit integer from the underlying reader.","Reads a sequence of signed 8 bit integers from the …","Reads a sequence of signed 8 bit integers from the …","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from buf.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from buf.","Reads an unsigned 128 bit integer from buf.","Reads an unsigned 128 bit integer from the underlying …","Reads an unsigned 128 bit integer from the underlying …","","","Reads unsigned 128 bit integers from src into dst.","Reads a sequence of unsigned 128 bit integers from the …","Reads a sequence of unsigned 128 bit integers from the …","","","Reads an unsigned 16 bit integer from buf.","Reads an unsigned 16 bit integer from the underlying …","Reads an unsigned 16 bit integer from the underlying …","","","Reads unsigned 16 bit integers from src into dst.","Reads a sequence of unsigned 16 bit integers from the …","Reads a sequence of unsigned 16 bit integers from the …","","","Reads an unsigned 24 bit integer from the underlying …","Reads an unsigned 24 bit integer from the underlying …","Reads an unsigned 24 bit integer from buf, stored in u32.","Reads an unsigned 32 bit integer from buf.","Reads an unsigned 32 bit integer from the underlying …","Reads an unsigned 32 bit integer from the underlying …","","","Reads unsigned 32 bit integers from src into dst.","Reads a sequence of unsigned 32 bit integers from the …","Reads a sequence of unsigned 32 bit integers from the …","","","Reads an unsigned 48 bit integer from the underlying …","Reads an unsigned 48 bit integer from the underlying …","Reads an unsigned 48 bit integer from buf, stored in u64.","Reads an unsigned 64 bit integer from buf.","Reads an unsigned 64 bit integer from the underlying …","Reads an unsigned 64 bit integer from the underlying …","","","Reads unsigned 64 bit integers from src into dst.","Reads a sequence of unsigned 64 bit integers from the …","Reads a sequence of unsigned 64 bit integers from the …","","","Reads an unsigned 8 bit integer from the underlying reader.","Reads an unsigned 8 bit integer from the underlying reader.","Reads an unsigned n-bytes integer from buf.","Reads an unsigned n-bytes integer from the underlying …","Reads an unsigned n-bytes integer from the underlying …","","","Reads an unsigned n-bytes integer from buf.","Reads an unsigned n-bytes integer from the underlying …","Reads an unsigned n-bytes integer from the underlying …","","","","","","","","","","","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes IEEE754 double-precision (8 bytes) floating point …","Writes a signed 128 bit integer to the underlying writer.","Writes a signed 128 bit integer to the underlying writer.","Writes a signed 128 bit integer n to buf.","Writes signed 128 bit integers from src into dst.","Writes a signed 16 bit integer to the underlying writer.","Writes a signed 16 bit integer to the underlying writer.","Writes a signed 16 bit integer n to buf.","Writes signed 16 bit integers from src into dst.","Writes a signed 24 bit integer to the underlying writer.","Writes a signed 24 bit integer to the underlying writer.","Writes a signed 24 bit integer n to buf, stored in i32.","Writes a signed 32 bit integer to the underlying writer.","Writes a signed 32 bit integer to the underlying writer.","Writes a signed 32 bit integer n to buf.","Writes signed 32 bit integers from src into dst.","Writes a signed 48 bit integer to the underlying writer.","Writes a signed 48 bit integer to the underlying writer.","Writes a signed 48 bit integer n to buf, stored in i64.","Writes a signed 64 bit integer to the underlying writer.","Writes a signed 64 bit integer to the underlying writer.","Writes a signed 64 bit integer n to buf.","Writes signed 64 bit integers from src into dst.","Writes a signed 8 bit integer to the underlying writer.","Writes a signed 8 bit integer to the underlying writer.","Writes signed 8 bit integers from src into dst.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed integer n to buf using only nbytes.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed integer n to buf using only nbytes.","Writes an unsigned 128 bit integer n to buf.","Writes an unsigned 128 bit integer to the underlying …","Writes an unsigned 128 bit integer to the underlying …","","","Writes unsigned 128 bit integers from src into dst.","","","Writes an unsigned 16 bit integer n to buf.","Writes an unsigned 16 bit integer to the underlying writer.","Writes an unsigned 16 bit integer to the underlying writer.","","","Writes unsigned 16 bit integers from src into dst.","","","Writes an unsigned 24 bit integer to the underlying writer.","Writes an unsigned 24 bit integer to the underlying writer.","Writes an unsigned 24 bit integer n to buf, stored in u32.","Writes an unsigned 32 bit integer n to buf.","Writes an unsigned 32 bit integer to the underlying writer.","Writes an unsigned 32 bit integer to the underlying writer.","","","Writes unsigned 32 bit integers from src into dst.","","","Writes an unsigned 48 bit integer to the underlying writer.","Writes an unsigned 48 bit integer to the underlying writer.","Writes an unsigned 48 bit integer n to buf, stored in u64.","Writes an unsigned 64 bit integer n to buf.","Writes an unsigned 64 bit integer to the underlying writer.","Writes an unsigned 64 bit integer to the underlying writer.","","","Writes unsigned 64 bit integers from src into dst.","","","Writes an unsigned 8 bit integer to the underlying writer.","Writes an unsigned 8 bit integer to the underlying writer.","Writes an unsigned integer n to buf using only nbytes.","Writes an unsigned n-bytes integer to the underlying …","Writes an unsigned n-bytes integer to the underlying …","","","Writes an unsigned integer n to buf using only nbytes.","Writes an unsigned n-bytes integer to the underlying …","Writes an unsigned n-bytes integer to the underlying …","",""],"i":[0,0,0,0,0,0,0,0,0,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,24,1,2,24,1,2,24,24,24,24,24,1,2,24,1,2,24,1,2,24,1,2,1,2,1,2,1,2,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,25,25,25,25,24,25,25,24,24,25,25,1,2,24,25,25,1,2,24,25,25,1,2,24,25,25,1,2,25,25,24,24,25,25,1,2,24,25,25,1,2,25,25,24,24,25,25,1,2,24,25,25,1,2,25,25,24,25,25,1,2,24,25,25,1,2,1,2,1,2,1,2,1,2,26,26,24,24,26,26,24,24,26,26,24,24,26,26,24,24,26,26,24,26,26,24,24,26,26,24,26,26,24,24,26,26,24,26,26,24,26,26,24,24,26,26,1,2,24,1,2,24,26,26,1,2,24,1,2,26,26,24,24,26,26,1,2,24,1,2,26,26,24,24,26,26,1,2,24,1,2,26,26,24,26,26,1,2,24,26,26,1,2],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[1,1],[2,2],[[]],[[]],[[1,1],3],[[2,2],3],[[],1],[[],2],[[1,1],4],[[2,2],4],[[1,5],6],[[2,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2],[[]],[[]],[[1,1],[[7,[3]]]],[[2,2],[[7,[3]]]],[[],[[9,[8]]]],[[],[[9,[8]]]],[[],8],[[],9],[[],9],[[]],[[],9],[[],9],[[]],[[],[[9,[10]]]],[[],[[9,[10]]]],[[],10],[[],9],[[],9],[[]],[[],9],[[],9],[[]],[[],[[9,[11]]]],[[],[[9,[11]]]],[[],11],[[],9],[[],9],[[]],[[],[[9,[12]]]],[[],[[9,[12]]]],[[],12],[[],9],[[],9],[[]],[[],[[9,[13]]]],[[],[[9,[13]]]],[[],13],[[],[[9,[13]]]],[[],[[9,[13]]]],[[],13],[[],9],[[],9],[[]],[[],[[9,[14]]]],[[],[[9,[14]]]],[[],14],[[],[[9,[14]]]],[[],[[9,[14]]]],[[],14],[[],9],[[],9],[[]],[[],[[9,[15]]]],[[],[[9,[15]]]],[[],9],[[],9],[16,[[9,[14]]]],[16,[[9,[14]]]],[16,14],[16,[[9,[11]]]],[16,[[9,[11]]]],[16,11],[[],17],[[],[[9,[17]]]],[[],[[9,[17]]]],[[],17],[[],17],[[]],[[],9],[[],9],[[]],[[]],[[],18],[[],[[9,[18]]]],[[],[[9,[18]]]],[[],18],[[],18],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[19]]]],[[],[[9,[19]]]],[[],19],[[],19],[[],[[9,[19]]]],[[],[[9,[19]]]],[[],19],[[],19],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[20]]]],[[],[[9,[20]]]],[[],20],[[],20],[[],[[9,[20]]]],[[],[[9,[20]]]],[[],20],[[],20],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[21]]]],[[],[[9,[21]]]],[16,20],[16,[[9,[20]]]],[16,[[9,[20]]]],[16,20],[16,20],[16,17],[16,[[9,[17]]]],[16,[[9,[17]]]],[16,17],[16,17],[[]],[[]],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],[8,9],[8,9],[8],[[]],[10,9],[10,9],[10],[[]],[11,9],[11,9],[11],[[]],[12,9],[12,9],[12],[[]],[13,9],[13,9],[13],[13,9],[13,9],[13],[[]],[14,9],[14,9],[14],[14,9],[14,9],[14],[[]],[15,9],[15,9],[[]],[[14,16],9],[[14,16],9],[[14,16]],[[11,16],9],[[11,16],9],[[11,16]],[17],[17,9],[17,9],[17],[17],[[]],[[]],[[]],[18],[18,9],[18,9],[18],[18],[[]],[[]],[[]],[19,9],[19,9],[19],[19],[19,9],[19,9],[19],[19],[[]],[[]],[[]],[20,9],[20,9],[20],[20],[20,9],[20,9],[20],[20],[[]],[[]],[[]],[21,9],[21,9],[[20,16]],[[20,16],9],[[20,16],9],[[20,16]],[[20,16]],[[17,16]],[[17,16],9],[[17,16],9],[[17,16]],[[17,16]]],"p":[[4,"BigEndian"],[4,"LittleEndian"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[15,"f32"],[6,"Result"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"usize"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"u8"],[4,"Result"],[3,"TypeId"],[8,"ByteOrder"],[8,"ReadBytesExt"],[8,"WriteBytesExt"]]},\ +"bytes":{"doc":"Provides abstractions for working with bytes.","t":[8,8,3,3,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,0,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,8,3,3,3,3,3,3,3,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Buf","BufMut","Bytes","BytesMut","advance","advance","advance","advance_mut","advance_mut","as_mut","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","buf","capacity","chunk","chunk","chunk","chunk_mut","chunk_mut","clear","clear","clone","clone","clone_into","clone_into","cmp","cmp","copy_from_slice","copy_to_bytes","copy_to_bytes","default","default","deref","deref","deref_mut","drop","drop","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend_from_slice","fmt","fmt","fmt","fmt","fmt","fmt","freeze","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_static","hash","hash","into","into","into_iter","into_iter","into_iter","into_iter","is_empty","is_empty","len","len","new","new","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","put","put_bytes","put_slice","remaining","remaining","remaining","remaining_mut","remaining_mut","reserve","resize","set_len","slice","slice_ref","spare_capacity_mut","split","split_off","split_off","split_to","split_to","to_owned","to_owned","truncate","truncate","try_from","try_from","try_into","try_into","type_id","type_id","unsplit","with_capacity","write_fmt","write_str","zeroed","Buf","BufMut","Chain","IntoIter","Limit","Reader","Take","UninitSlice","Writer","advance","advance","advance","advance_mut","advance_mut","advance_mut","as_mut_ptr","as_uninit_slice_mut","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chain","chain","chain","chain_mut","chain_mut","chain_mut","chunk","chunk","chunk","chunk_mut","chunk_mut","chunk_mut","chunks_vectored","chunks_vectored","chunks_vectored","chunks_vectored","consume","copy_from_slice","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_slice","copy_to_slice","copy_to_slice","fill_buf","first_mut","first_ref","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_raw_parts_mut","get_f32","get_f32","get_f32","get_f32_le","get_f32_le","get_f32_le","get_f32_ne","get_f32_ne","get_f32_ne","get_f64","get_f64","get_f64","get_f64_le","get_f64_le","get_f64_le","get_f64_ne","get_f64_ne","get_f64_ne","get_i128","get_i128","get_i128","get_i128_le","get_i128_le","get_i128_le","get_i128_ne","get_i128_ne","get_i128_ne","get_i16","get_i16","get_i16","get_i16_le","get_i16_le","get_i16_le","get_i16_ne","get_i16_ne","get_i16_ne","get_i32","get_i32","get_i32","get_i32_le","get_i32_le","get_i32_le","get_i32_ne","get_i32_ne","get_i32_ne","get_i64","get_i64","get_i64","get_i64_le","get_i64_le","get_i64_le","get_i64_ne","get_i64_ne","get_i64_ne","get_i8","get_i8","get_i8","get_int","get_int","get_int","get_int_le","get_int_le","get_int_le","get_int_ne","get_int_ne","get_int_ne","get_mut","get_mut","get_mut","get_mut","get_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_u128","get_u128","get_u128","get_u128_le","get_u128_le","get_u128_le","get_u128_ne","get_u128_ne","get_u128_ne","get_u16","get_u16","get_u16","get_u16_le","get_u16_le","get_u16_le","get_u16_ne","get_u16_ne","get_u16_ne","get_u32","get_u32","get_u32","get_u32_le","get_u32_le","get_u32_le","get_u32_ne","get_u32_ne","get_u32_ne","get_u64","get_u64","get_u64","get_u64_le","get_u64_le","get_u64_le","get_u64_ne","get_u64_ne","get_u64_ne","get_u8","get_u8","get_u8","get_uint","get_uint","get_uint","get_uint_le","get_uint_le","get_uint_le","get_uint_ne","get_uint_ne","get_uint_ne","has_remaining","has_remaining","has_remaining","has_remaining_mut","has_remaining_mut","has_remaining_mut","index","index","index","index","index","index","index_mut","index_mut","index_mut","index_mut","index_mut","index_mut","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","last_mut","last_ref","len","limit","limit","limit","limit","limit","next","put","put","put","put_bytes","put_bytes","put_bytes","put_f32","put_f32","put_f32","put_f32_le","put_f32_le","put_f32_le","put_f32_ne","put_f32_ne","put_f32_ne","put_f64","put_f64","put_f64","put_f64_le","put_f64_le","put_f64_le","put_f64_ne","put_f64_ne","put_f64_ne","put_i128","put_i128","put_i128","put_i128_le","put_i128_le","put_i128_le","put_i128_ne","put_i128_ne","put_i128_ne","put_i16","put_i16","put_i16","put_i16_le","put_i16_le","put_i16_le","put_i16_ne","put_i16_ne","put_i16_ne","put_i32","put_i32","put_i32","put_i32_le","put_i32_le","put_i32_le","put_i32_ne","put_i32_ne","put_i32_ne","put_i64","put_i64","put_i64","put_i64_le","put_i64_le","put_i64_le","put_i64_ne","put_i64_ne","put_i64_ne","put_i8","put_i8","put_i8","put_int","put_int","put_int","put_int_le","put_int_le","put_int_le","put_int_ne","put_int_ne","put_int_ne","put_slice","put_slice","put_slice","put_u128","put_u128","put_u128","put_u128_le","put_u128_le","put_u128_le","put_u128_ne","put_u128_ne","put_u128_ne","put_u16","put_u16","put_u16","put_u16_le","put_u16_le","put_u16_le","put_u16_ne","put_u16_ne","put_u16_ne","put_u32","put_u32","put_u32","put_u32_le","put_u32_le","put_u32_le","put_u32_ne","put_u32_ne","put_u32_ne","put_u64","put_u64","put_u64","put_u64_le","put_u64_le","put_u64_le","put_u64_ne","put_u64_ne","put_u64_ne","put_u8","put_u8","put_u8","put_uint","put_uint","put_uint","put_uint_le","put_uint_le","put_uint_le","put_uint_ne","put_uint_ne","put_uint_ne","read","reader","reader","reader","remaining","remaining","remaining","remaining_mut","remaining_mut","remaining_mut","set_limit","set_limit","size_hint","take","take","take","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write","write_byte","writer","writer","writer"],"q":["bytes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bytes::buf","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Read bytes from a buffer.","A trait for values that provide sequential write access to …","A cheaply cloneable and sliceable chunk of contiguous …","A unique reference to a contiguous slice of memory.","Advance the internal cursor of the Buf","","","Advance the internal cursor of the BufMut","","","","","","","","","","","","Utilities for working with buffers.","Returns the number of bytes the BytesMut can hold without …","Returns a slice starting at the current position and of …","","","Returns a mutable slice starting at the current BufMut …","","Clears the buffer, removing all data.","Clears the buffer, removing all data. Existing capacity is …","","","","","","","Creates Bytes instance from slice, by copying it.","","","","","","","","","","","","","","","","","","","","","","","","","","","Appends given bytes to this BytesMut.","","","","","","","Converts self into an immutable Bytes.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Creates a new Bytes from a static slice.","","","Calls U::from(self).","Calls U::from(self).","","","","","Returns true if the Bytes has a length of 0.","Returns true if the BytesMut has a length of 0.","Returns the number of bytes contained in this Bytes.","Returns the number of bytes contained in this BytesMut.","Creates a new empty Bytes.","Creates a new BytesMut with default capacity.","","","","","","","","","","","","","","","","Returns the number of bytes between the current position …","","","Returns the number of bytes that can be written from the …","","Reserves capacity for at least additional more bytes to be …","Resizes the buffer so that len is equal to new_len.","Sets the length of the buffer.","Returns a slice of self for the provided range.","Returns a slice of self that is equivalent to the given …","Returns the remaining spare capacity of the buffer as a …","Removes the bytes from the current view, returning them in …","Splits the bytes into two at the given index.","Splits the bytes into two at the given index.","Splits the bytes into two at the given index.","Splits the buffer into two at the given index.","","","Shortens the buffer, keeping the first len bytes and …","Shortens the buffer, keeping the first len bytes and …","","","","","","","Absorbs a BytesMut that was previously split off.","Creates a new BytesMut with the specified capacity.","","","Creates a new BytesMut, which is initialized with zero.","Read bytes from a buffer.","A trait for values that provide sequential write access to …","A Chain sequences two buffers.","Iterator over the bytes contained by the buffer.","A BufMut adapter which limits the amount of bytes that can …","A Buf adapter which implements io::Read for the inner …","A Buf adapter which limits the bytes read from an …","Uninitialized byte slice.","A BufMut adapter which implements io::Write for the inner …","Advance the internal cursor of the Buf","","","Advance the internal cursor of the BufMut","","","Return a raw pointer to the slice’s buffer.","Return a &mut [MaybeUninit<u8>] to this slice’s buffer.","","","","","","","","","","","","","","","Creates an adaptor which will chain this buffer with …","Creates an adaptor which will chain this buffer with …","Creates an adaptor which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Returns a slice starting at the current position and of …","","","Returns a mutable slice starting at the current BufMut …","","","Fills dst with potentially multiple slices starting at self…","Fills dst with potentially multiple slices starting at self…","Fills dst with potentially multiple slices starting at self…","","","Copies bytes from src into self.","Consumes len bytes inside self and returns new instance of …","Consumes len bytes inside self and returns new instance of …","Consumes len bytes inside self and returns new instance of …","","","Copies bytes from self into dst.","Copies bytes from self into dst.","Copies bytes from self into dst.","","Gets a mutable reference to the first underlying Buf.","Gets a reference to the first underlying Buf.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create a &mut UninitSlice from a pointer and a length.","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 8 bit integer from self.","Gets a signed 8 bit integer from self.","Gets a signed 8 bit integer from self.","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying BufMut.","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying BufMut.","Gets a reference to the underlying Buf.","Gets a reference to the underlying BufMut.","Gets a reference to the underlying Buf.","Gets a reference to the underlying Buf.","Gets a reference to the underlying BufMut.","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 8 bit integer from self.","Gets an unsigned 8 bit integer from self.","Gets an unsigned 8 bit integer from self.","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in native-endian …","Gets an unsigned n-byte integer from self in native-endian …","Gets an unsigned n-byte integer from self in native-endian …","Returns true if there are any more bytes to consume","Returns true if there are any more bytes to consume","Returns true if there are any more bytes to consume","Returns true if there is space in self for more bytes.","Returns true if there is space in self for more bytes.","Returns true if there is space in self for more bytes.","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this Chain, returning the underlying values.","Consumes this IntoIter, returning the underlying value.","Consumes this Limit, returning the underlying value.","Consumes this Reader, returning the underlying value.","Consumes this Take, returning the underlying value.","Consumes this Writer, returning the underlying value.","","","Gets a mutable reference to the last underlying Buf.","Gets a reference to the last underlying Buf.","Returns the number of bytes in the slice.","Creates an adaptor which can write at most limit bytes to …","Creates an adaptor which can write at most limit bytes to …","Creates an adaptor which can write at most limit bytes to …","Returns the maximum number of bytes that can be written","Returns the maximum number of bytes that can be read.","","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Put cnt bytes val into self.","Put cnt bytes val into self.","Put cnt bytes val into self.","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 8 bit integer to self.","Writes a signed 8 bit integer to self.","Writes a signed 8 bit integer to self.","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 8 bit integer to self.","Writes an unsigned 8 bit integer to self.","Writes an unsigned 8 bit integer to self.","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","","Creates an adaptor which implements the Read trait for self…","Creates an adaptor which implements the Read trait for self…","Creates an adaptor which implements the Read trait for self…","Returns the number of bytes between the current position …","","","Returns the number of bytes that can be written from the …","","","Sets the maximum number of bytes that can be written.","Sets the maximum number of bytes that can be read.","","Creates an adaptor which will read at most limit bytes …","Creates an adaptor which will read at most limit bytes …","Creates an adaptor which will read at most limit bytes …","","","","","","","","","","","","","","","","","","","","","Write a single byte at the specified offset.","Creates an adaptor which implements the Write trait for …","Creates an adaptor which implements the Write trait for …","Creates an adaptor which implements the Write trait for …"],"i":[0,0,0,0,16,2,3,23,3,3,2,3,2,2,3,3,2,3,3,0,3,16,2,3,23,3,2,3,2,3,2,3,2,3,2,2,3,2,3,2,3,3,2,3,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,3,3,3,2,2,2,2,2,2,2,3,3,3,2,3,3,2,2,3,2,3,2,2,3,3,2,3,2,3,2,3,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,16,2,3,23,3,3,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,16,21,22,23,21,24,4,4,4,21,30,24,26,22,28,4,21,30,24,26,22,28,16,16,16,23,23,23,16,21,22,23,21,24,16,16,16,21,26,4,16,16,16,21,22,16,16,16,26,21,21,28,4,21,30,24,26,22,28,21,30,24,26,22,28,4,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,30,24,26,22,28,30,24,26,22,28,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,23,23,23,4,4,4,4,4,4,4,4,4,4,4,4,21,30,24,26,22,28,21,30,24,26,22,28,21,30,21,21,4,23,23,23,24,22,30,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,26,16,16,16,16,21,22,23,21,24,24,22,30,16,16,16,21,30,24,26,22,28,21,30,24,26,22,28,4,21,30,24,26,22,28,28,4,23,23,23],"f":[0,0,0,0,[1],[[2,1]],[[3,1]],[1],[[3,1]],[3],[2],[3],[[]],[2],[3],[[]],[[]],[3],[[]],0,[3,1],[[]],[2],[3],[[],4],[3,4],[2],[3],[2,2],[3,3],[[]],[[]],[[2,2],5],[[3,3],5],[[],2],[[2,1],2],[[3,1],2],[[],2],[[],3],[2],[3],[3],[2],[3],[[2,6],7],[[2,3],7],[2,7],[[2,8],7],[[2,2],7],[[2,9],7],[2,7],[[3,3],7],[[3,6],7],[3,7],[[3,2],7],[3,7],[[3,8],7],[[3,9],7],[3],[3],[3],[3],[[2,10],11],[[2,10],11],[[2,10],11],[[3,10],11],[[3,10],11],[[3,10],11],[3,2],[12,2],[3,2],[[[9,[13]]],2],[6,2],[8,2],[[],2],[[]],[[]],[8,3],[[],3],[14,2],[14,3],[14,3],[[],2],[2],[3],[[]],[[]],[2],[2],[3],[3],[2,7],[3,7],[2,1],[3,1],[[],2],[[],3],[2,[[15,[5]]]],[2,[[15,[5]]]],[[2,8],[[15,[5]]]],[[2,9],[[15,[5]]]],[[2,6],[[15,[5]]]],[[2,2],[[15,[5]]]],[[3,3],[[15,[5]]]],[3,[[15,[5]]]],[[3,6],[[15,[5]]]],[[3,9],[[15,[5]]]],[[3,8],[[15,[5]]]],[3,[[15,[5]]]],[[3,16]],[[3,13,1]],[3],[[],1],[2,1],[3,1],[[],1],[3,1],[[3,1]],[[3,1,13]],[[3,1]],[[2,[17,[1]]],2],[2,2],[3],[3,3],[[2,1],2],[[3,1],3],[[2,1],2],[[3,1],3],[[]],[[]],[[2,1]],[[3,1]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[3,3]],[1,3],[[3,20],11],[[3,8],11],[1,3],0,0,0,0,0,0,0,0,0,[1],[[21,1]],[[[22,[16]],1]],[1],[[21,1]],[[[24,[23]],1]],[4,13],[4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,[[21,[16]]]],[16,[[21,[16]]]],[16,[[21,[16]]]],[23,[[21,[23]]]],[23,[[21,[23]]]],[23,[[21,[23]]]],[[]],[21],[[[22,[16]]]],[[],4],[21,4],[[[24,[23]]],4],[[],1],[[],1],[[],1],[21,1],[[[26,[[0,[16,25]]]],1]],[4],[1,2],[1,2],[1,2],[[21,1],2],[[[22,[16]],1],2],[[]],[[]],[[]],[[[26,[[0,[16,25]]]]],27],[21],[21],[[[28,[[0,[23,25]]]]],27],[[4,10],11],[[[21,[29,29]],10],11],[[[30,[29]],10],11],[[[24,[29]],10],11],[[[26,[29]],10],11],[[[22,[29]],10],11],[[[28,[29]],10],11],[[]],[[]],[[]],[[]],[[]],[[]],[[13,1],4],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],37],[[],37],[[],37],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[30],[24],[[[26,[16]]]],[22],[[[28,[23]]]],[30],[24],[[[26,[16]]]],[22],[[[28,[23]]]],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],13],[[],13],[[],13],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[4,[42,[1]]],4],[[4,[43,[1]]],4],[[4,44],4],[[4,[45,[1]]],4],[[4,[46,[1]]],4],[[4,[47,[1]]],4],[[4,[43,[1]]],4],[[4,[46,[1]]],4],[[4,[47,[1]]],4],[[4,44],4],[[4,[42,[1]]],4],[[4,[45,[1]]],4],[[]],[[]],[[]],[[]],[[]],[[]],[21],[30],[24],[[[26,[16]]],16],[22],[[[28,[23]]],23],[21],[[]],[21],[21],[4,1],[1,24],[1,24],[1,24],[24,1],[22,1],[[[30,[16]]],[[15,[13]]]],[16],[16],[16],[[13,1]],[[13,1]],[[13,1]],[31],[31],[31],[31],[31],[31],[31],[31],[31],[32],[32],[32],[32],[32],[32],[32],[32],[32],[33],[33],[33],[33],[33],[33],[33],[33],[33],[34],[34],[34],[34],[34],[34],[34],[34],[34],[35],[35],[35],[35],[35],[35],[35],[35],[35],[36],[36],[36],[36],[36],[36],[36],[36],[36],[37],[37],[37],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[]],[[]],[[]],[38],[38],[38],[38],[38],[38],[38],[38],[38],[39],[39],[39],[39],[39],[39],[39],[39],[39],[40],[40],[40],[40],[40],[40],[40],[40],[40],[41],[41],[41],[41],[41],[41],[41],[41],[41],[13],[13],[13],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[[26,[[0,[16,25]]]]],[[27,[1]]]],[[],26],[[],26],[[],26],[[],1],[21,1],[[[22,[16]]],1],[[],1],[21,1],[[[24,[23]]],1],[[24,1]],[[22,1]],[[[30,[16]]]],[1,22],[1,22],[1,22],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[[28,[[0,[23,25]]]]],[[27,[1]]]],[[4,1,13]],[[],28],[[],28],[[],28]],"p":[[15,"usize"],[3,"Bytes"],[3,"BytesMut"],[3,"UninitSlice"],[4,"Ordering"],[3,"String"],[15,"bool"],[15,"str"],[3,"Vec"],[3,"Formatter"],[6,"Result"],[3,"Box"],[15,"u8"],[8,"IntoIterator"],[4,"Option"],[8,"Buf"],[8,"RangeBounds"],[4,"Result"],[3,"TypeId"],[3,"Arguments"],[3,"Chain"],[3,"Take"],[8,"BufMut"],[3,"Limit"],[8,"Sized"],[3,"Reader"],[6,"Result"],[3,"Writer"],[8,"Debug"],[3,"IntoIter"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[3,"RangeFrom"],[3,"RangeInclusive"],[3,"RangeFull"],[3,"Range"],[3,"RangeToInclusive"],[3,"RangeTo"]]},\ +"cfg_if":{"doc":"A macro for defining #[cfg] if-else statements.","t":[14],"n":["cfg_if"],"q":["cfg_if"],"d":["The main macro provided by this crate. See crate …"],"i":[0],"f":[0],"p":[]},\ +"cpufeatures":{"doc":"This crate provides macros for runtime CPU feature …","t":[14],"n":["new"],"q":["cpufeatures"],"d":["Create module with CPU feature detection code."],"i":[0],"f":[0],"p":[]},\ +"crc":{"doc":"crc","t":[3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,3,8,12,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["Algorithm","CRC_10_ATM","CRC_10_CDMA2000","CRC_10_GSM","CRC_11_FLEXRAY","CRC_11_UMTS","CRC_12_CDMA2000","CRC_12_DECT","CRC_12_GSM","CRC_12_UMTS","CRC_13_BBC","CRC_14_DARC","CRC_14_GSM","CRC_15_CAN","CRC_15_MPT1327","CRC_16_ARC","CRC_16_CDMA2000","CRC_16_CMS","CRC_16_DDS_110","CRC_16_DECT_R","CRC_16_DECT_X","CRC_16_DNP","CRC_16_EN_13757","CRC_16_GENIBUS","CRC_16_GSM","CRC_16_IBM_3740","CRC_16_IBM_SDLC","CRC_16_ISO_IEC_14443_3_A","CRC_16_KERMIT","CRC_16_LJ1200","CRC_16_MAXIM_DOW","CRC_16_MCRF4XX","CRC_16_MODBUS","CRC_16_NRSC_5","CRC_16_OPENSAFETY_A","CRC_16_OPENSAFETY_B","CRC_16_PROFIBUS","CRC_16_RIELLO","CRC_16_SPI_FUJITSU","CRC_16_T10_DIF","CRC_16_TELEDISK","CRC_16_TMS37157","CRC_16_UMTS","CRC_16_USB","CRC_16_XMODEM","CRC_17_CAN_FD","CRC_21_CAN_FD","CRC_24_BLE","CRC_24_FLEXRAY_A","CRC_24_FLEXRAY_B","CRC_24_INTERLAKEN","CRC_24_LTE_A","CRC_24_LTE_B","CRC_24_OPENPGP","CRC_24_OS_9","CRC_30_CDMA","CRC_31_PHILIPS","CRC_32_AIXM","CRC_32_AUTOSAR","CRC_32_BASE91_D","CRC_32_BZIP2","CRC_32_CD_ROM_EDC","CRC_32_CKSUM","CRC_32_ISCSI","CRC_32_ISO_HDLC","CRC_32_JAMCRC","CRC_32_MEF","CRC_32_MPEG_2","CRC_32_XFER","CRC_3_GSM","CRC_3_ROHC","CRC_40_GSM","CRC_4_G_704","CRC_4_INTERLAKEN","CRC_5_EPC_C1G2","CRC_5_G_704","CRC_5_USB","CRC_64_ECMA_182","CRC_64_GO_ISO","CRC_64_MS","CRC_64_WE","CRC_64_XZ","CRC_6_CDMA2000_A","CRC_6_CDMA2000_B","CRC_6_DARC","CRC_6_GSM","CRC_6_G_704","CRC_7_MMC","CRC_7_ROHC","CRC_7_UMTS","CRC_82_DARC","CRC_8_AUTOSAR","CRC_8_BLUETOOTH","CRC_8_CDMA2000","CRC_8_DARC","CRC_8_DVB_S2","CRC_8_GSM_A","CRC_8_GSM_B","CRC_8_HITAG","CRC_8_I_432_1","CRC_8_I_CODE","CRC_8_LTE","CRC_8_MAXIM_DOW","CRC_8_MIFARE_MAD","CRC_8_NRSC_5","CRC_8_OPENSAFETY","CRC_8_ROHC","CRC_8_SAE_J1850","CRC_8_SMBUS","CRC_8_TECH_3250","CRC_8_WCDMA","Crc","Digest","Width","algorithm","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","check","checksum","checksum","checksum","checksum","checksum","clone","digest","digest","digest","digest","digest","digest_with_initial","digest_with_initial","digest_with_initial","digest_with_initial","digest_with_initial","finalize","finalize","finalize","finalize","finalize","from","from","from","init","into","into","into","new","new","new","new","new","poly","refin","refout","residue","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","update","update","update","update","update","width","xorout"],"q":["crc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["This struct describes a CRC algorithm using the fields …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The contents of the register after initialising, reading …","","","","","","","","","","","","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","The settings of the bit cells at the start of each …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","The generator polynomial that sets the feedback tap …","If equal to false, specifies that the characters of the …","If equal to false, specifies that the contents of the …","The contents of the register after initialising, reading …","","","","","","","","","","","","","","","The number of bit cells in the linear feedback shift …","The XOR value applied to the contents of the register …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,10,2,9,10,2,9,10,2,2,2,2,2,9,2,2,2,2,2,2,2,2,2,2,9,9,9,9,9,10,2,9,10,10,2,9,2,2,2,2,2,10,10,10,10,10,2,9,10,2,9,10,2,9,9,9,9,9,9,10,10],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],0,[[[2,[1]]],1],[[[2,[3]]],3],[[[2,[4]]],4],[[[2,[5]]],5],[[[2,[6]]],6],[[[9,[[0,[7,8]]]]],[[9,[[0,[7,8]]]]]],[[[2,[3]]],[[9,[3]]]],[[[2,[6]]],[[9,[6]]]],[[[2,[4]]],[[9,[4]]]],[[[2,[5]]],[[9,[5]]]],[[[2,[1]]],[[9,[1]]]],[[[2,[6]],6],[[9,[6]]]],[[[2,[4]],4],[[9,[4]]]],[[[2,[5]],5],[[9,[5]]]],[[[2,[1]],1],[[9,[1]]]],[[[2,[3]],3],[[9,[3]]]],[[[9,[3]]],3],[[[9,[4]]],4],[[[9,[6]]],6],[[[9,[1]]],1],[[[9,[5]]],5],[[]],[[]],[[]],0,[[]],[[]],[[]],[10,[[2,[4]]]],[10,[[2,[3]]]],[10,[[2,[6]]]],[10,[[2,[1]]]],[10,[[2,[5]]]],0,0,0,0,[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],12],[[],12],[[],12],[[[9,[6]]]],[[[9,[5]]]],[[[9,[1]]]],[[[9,[4]]]],[[[9,[3]]]],0,0],"p":[[15,"u128"],[3,"Crc"],[15,"u8"],[15,"u16"],[15,"u64"],[15,"u32"],[8,"Clone"],[8,"Width"],[3,"Digest"],[3,"Algorithm"],[4,"Result"],[3,"TypeId"]]},\ +"crc_catalog":{"doc":"","t":[3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,8,11,11,12,11,12,11,12,12,12,12,11,11,11,12,12],"n":["Algorithm","CRC_10_ATM","CRC_10_CDMA2000","CRC_10_GSM","CRC_11_FLEXRAY","CRC_11_UMTS","CRC_12_CDMA2000","CRC_12_DECT","CRC_12_GSM","CRC_12_UMTS","CRC_13_BBC","CRC_14_DARC","CRC_14_GSM","CRC_15_CAN","CRC_15_MPT1327","CRC_16_ARC","CRC_16_CDMA2000","CRC_16_CMS","CRC_16_DDS_110","CRC_16_DECT_R","CRC_16_DECT_X","CRC_16_DNP","CRC_16_EN_13757","CRC_16_GENIBUS","CRC_16_GSM","CRC_16_IBM_3740","CRC_16_IBM_SDLC","CRC_16_ISO_IEC_14443_3_A","CRC_16_KERMIT","CRC_16_LJ1200","CRC_16_MAXIM_DOW","CRC_16_MCRF4XX","CRC_16_MODBUS","CRC_16_NRSC_5","CRC_16_OPENSAFETY_A","CRC_16_OPENSAFETY_B","CRC_16_PROFIBUS","CRC_16_RIELLO","CRC_16_SPI_FUJITSU","CRC_16_T10_DIF","CRC_16_TELEDISK","CRC_16_TMS37157","CRC_16_UMTS","CRC_16_USB","CRC_16_XMODEM","CRC_17_CAN_FD","CRC_21_CAN_FD","CRC_24_BLE","CRC_24_FLEXRAY_A","CRC_24_FLEXRAY_B","CRC_24_INTERLAKEN","CRC_24_LTE_A","CRC_24_LTE_B","CRC_24_OPENPGP","CRC_24_OS_9","CRC_30_CDMA","CRC_31_PHILIPS","CRC_32_AIXM","CRC_32_AUTOSAR","CRC_32_BASE91_D","CRC_32_BZIP2","CRC_32_CD_ROM_EDC","CRC_32_CKSUM","CRC_32_ISCSI","CRC_32_ISO_HDLC","CRC_32_JAMCRC","CRC_32_MEF","CRC_32_MPEG_2","CRC_32_XFER","CRC_3_GSM","CRC_3_ROHC","CRC_40_GSM","CRC_4_G_704","CRC_4_INTERLAKEN","CRC_5_EPC_C1G2","CRC_5_G_704","CRC_5_USB","CRC_64_ECMA_182","CRC_64_GO_ISO","CRC_64_MS","CRC_64_WE","CRC_64_XZ","CRC_6_CDMA2000_A","CRC_6_CDMA2000_B","CRC_6_DARC","CRC_6_GSM","CRC_6_G_704","CRC_7_MMC","CRC_7_ROHC","CRC_7_UMTS","CRC_82_DARC","CRC_8_AUTOSAR","CRC_8_BLUETOOTH","CRC_8_CDMA2000","CRC_8_DARC","CRC_8_DVB_S2","CRC_8_GSM_A","CRC_8_GSM_B","CRC_8_HITAG","CRC_8_I_432_1","CRC_8_I_CODE","CRC_8_LTE","CRC_8_MAXIM_DOW","CRC_8_MIFARE_MAD","CRC_8_NRSC_5","CRC_8_OPENSAFETY","CRC_8_ROHC","CRC_8_SAE_J1850","CRC_8_SMBUS","CRC_8_TECH_3250","CRC_8_WCDMA","Width","borrow","borrow_mut","check","from","init","into","poly","refin","refout","residue","try_from","try_into","type_id","width","xorout"],"q":["crc_catalog","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["This struct describes a CRC algorithm using the fields …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The contents of the register after initialising, reading …","Returns the argument unchanged.","The settings of the bit cells at the start of each …","Calls U::from(self).","The generator polynomial that sets the feedback tap …","If equal to false, specifies that the characters of the …","If equal to false, specifies that the contents of the …","The contents of the register after initialising, reading …","","","","The number of bit cells in the linear feedback shift …","The XOR value applied to the contents of the register …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,[[]],0,[[]],0,0,0,0,[[],1],[[],1],[[],2],0,0],"p":[[4,"Result"],[3,"TypeId"],[3,"Algorithm"]]},\ +"crossbeam_channel":{"doc":"Multi-producer multi-consumer channels for message passing.","t":[12,13,13,13,13,13,13,3,3,3,3,3,4,3,3,3,3,4,3,13,13,3,3,4,3,4,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12],"n":["0","Disconnected","Disconnected","Disconnected","Disconnected","Empty","Full","IntoIter","Iter","ReadyTimeoutError","Receiver","RecvError","RecvTimeoutError","Select","SelectTimeoutError","SelectedOperation","SendError","SendTimeoutError","Sender","Timeout","Timeout","TryIter","TryReadyError","TryRecvError","TrySelectError","TrySendError","after","at","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bounded","capacity","capacity","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","drop","drop","drop","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","is_disconnected","is_disconnected","is_disconnected","is_disconnected","is_empty","is_empty","is_empty","is_full","is_full","is_full","is_timeout","is_timeout","iter","len","len","never","new","next","next","next","provide","provide","provide","provide","provide","provide","provide","provide","ready","ready_deadline","ready_timeout","recv","recv","recv","recv_deadline","recv_timeout","remove","same_channel","same_channel","select","select","select_deadline","select_timeout","send","send","send","send_deadline","send_timeout","tick","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_iter","try_ready","try_recv","try_select","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","0","0","0","0"],"q":["crossbeam_channel","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_channel::SendTimeoutError","","crossbeam_channel::TrySendError",""],"d":["","The message could not be sent because the channel is …","The message could not be sent because the channel is …","The message could not be received because the channel is …","The message could not be received because the channel is …","A message could not be received because the channel is …","The message could not be sent because the channel is full.","A blocking iterator over messages in a channel.","A blocking iterator over messages in a channel.","An error returned from the ready_timeout method.","The receiving side of a channel.","An error returned from the recv method.","An error returned from the recv_timeout method.","Selects from a set of channel operations.","An error returned from the select_timeout method.","A selected operation that needs to be completed.","An error returned from the send method.","An error returned from the send_timeout method.","The sending side of a channel.","The message could not be sent because the channel is full …","A message could not be received because the channel is …","A non-blocking iterator over messages in a channel.","An error returned from the try_ready method.","An error returned from the try_recv method.","An error returned from the try_select method.","An error returned from the try_send method.","Creates a receiver that delivers a message after a certain …","Creates a receiver that delivers a message at a certain …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a channel of bounded capacity.","If the channel is bounded, returns its capacity.","If the channel is bounded, returns its capacity.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the index of the selected operation.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Unwraps the message.","Unwraps the message.","Unwraps the message.","","","","","","Returns true if the send operation failed because the …","Returns true if the send operation failed because the …","Returns true if the receive operation failed because the …","Returns true if the receive operation failed because the …","Returns true if the channel is empty.","Returns true if the channel is empty.","Returns true if the receive operation failed because the …","Returns true if the channel is full.","Returns true if the channel is full.","Returns true if the send operation failed because the …","Returns true if the send operation timed out.","Returns true if the receive operation timed out.","A blocking iterator over messages in the channel.","Returns the number of messages in the channel.","Returns the number of messages in the channel.","Creates a receiver that never delivers messages.","Creates an empty list of channel operations for selection.","","","","","","","","","","","","Blocks until one of the operations becomes ready.","Blocks until a given deadline, or until one of the …","Blocks for a limited time until one of the operations …","Blocks the current thread until a message is received or …","Adds a receive operation.","Completes the receive operation.","Waits for a message to be received from the channel, but …","Waits for a message to be received from the channel, but …","Removes a previously added operation.","Returns true if senders belong to the same channel.","Returns true if receivers belong to the same channel.","Blocks until one of the operations becomes ready and …","Selects from a set of channel operations.","Blocks until a given deadline, or until one of the …","Blocks for a limited time until one of the operations …","Blocks the current thread until a message is sent or the …","Adds a send operation.","Completes the send operation.","Waits for a message to be sent into the channel, but only …","Waits for a message to be sent into the channel, but only …","Creates a receiver that delivers messages periodically.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A non-blocking iterator over messages in the channel.","Attempts to find a ready operation without blocking.","Attempts to receive a message from the channel without …","Attempts to select one of the operations without blocking.","Attempts to send a message into the channel without …","","","","","","","","","","","","","","","","","","Creates a channel of unbounded capacity.","","","",""],"i":[9,10,11,13,14,13,10,0,0,0,0,0,0,0,0,0,0,0,0,11,14,0,0,0,0,0,0,0,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,0,5,3,5,3,7,9,10,11,12,13,14,15,16,17,18,5,3,7,9,10,11,12,13,14,15,16,17,18,7,5,3,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,18,5,3,24,25,26,7,19,9,10,10,11,11,12,13,13,14,14,15,16,17,18,19,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,9,10,11,3,3,24,25,26,10,11,13,14,5,3,13,5,3,10,11,14,3,5,3,0,7,24,25,26,9,10,11,12,13,14,15,16,7,7,7,3,7,19,3,3,7,5,3,7,0,7,7,5,7,19,5,5,0,5,3,7,9,10,11,12,13,14,15,16,17,18,9,10,11,12,13,14,15,16,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,3,7,3,7,5,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,0,31,32,33,34],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,[[3,[2]]]],[2,[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4],[5,[[6,[4]]]],[3,[[6,[4]]]],[5,5],[3,3],[7,7],[[[9,[8]]],[[9,[8]]]],[[[10,[8]]],[[10,[8]]]],[[[11,[8]]],[[11,[8]]]],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],7],[5],[3],[19],[[[9,[20]],9],21],[[[10,[20]],10],21],[[[11,[20]],11],21],[[12,12],21],[[13,13],21],[[14,14],21],[[15,15],21],[[16,16],21],[[17,17],21],[[18,18],21],[[5,22],23],[[3,22],23],[[24,22],23],[[25,22],23],[[26,22],23],[[7,22],23],[[19,22],23],[[9,22],23],[[9,22],23],[[10,22],23],[[10,22],23],[[11,22],23],[[11,22],23],[[12,22],23],[[12,22],23],[[13,22],23],[[13,22],23],[[14,22],23],[[14,22],23],[[15,22],23],[[15,22],23],[[16,22],23],[[16,22],23],[[17,22],23],[[18,22],23],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9,10],[[]],[9,11],[[]],[[]],[[]],[12,13],[[]],[12,14],[[]],[[]],[[]],[[]],[19,4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9],[10],[11],[3],[3],[[]],[[]],[[]],[10,21],[11,21],[13,21],[14,21],[5,21],[3,21],[13,21],[5,21],[3,21],[10,21],[11,21],[14,21],[3,24],[5,4],[3,4],[[],3],[[],7],[24,6],[25,6],[26,6],[27],[27],[27],[27],[27],[27],[27],[27],[7,4],[[7,2],[[28,[4,18]]]],[[7,1],[[28,[4,18]]]],[3,[[28,[12]]]],[[7,3],4],[[19,3],[[28,[12]]]],[[3,2],[[28,[14]]]],[[3,1],[[28,[14]]]],[[7,4]],[[5,5],21],[[3,3],21],[7,19],0,[[7,2],[[28,[19,16]]]],[[7,1],[[28,[19,16]]]],[5,[[28,[9]]]],[[7,5],4],[[19,5],[[28,[9]]]],[[5,2],[[28,[11]]]],[[5,1],[[28,[11]]]],[1,[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[3,25],[7,[[28,[4,17]]]],[3,[[28,[13]]]],[7,[[28,[19,15]]]],[5,[[28,[10]]]],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[]],0,0,0,0],"p":[[3,"Duration"],[3,"Instant"],[3,"Receiver"],[15,"usize"],[3,"Sender"],[4,"Option"],[3,"Select"],[8,"Clone"],[3,"SendError"],[4,"TrySendError"],[4,"SendTimeoutError"],[3,"RecvError"],[4,"TryRecvError"],[4,"RecvTimeoutError"],[3,"TrySelectError"],[3,"SelectTimeoutError"],[3,"TryReadyError"],[3,"ReadyTimeoutError"],[3,"SelectedOperation"],[8,"PartialEq"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Iter"],[3,"TryIter"],[3,"IntoIter"],[3,"Demand"],[4,"Result"],[3,"String"],[3,"TypeId"],[13,"Timeout"],[13,"Disconnected"],[13,"Full"],[13,"Disconnected"]]},\ +"crossbeam_utils":{"doc":"Miscellaneous tools for concurrent programming.","t":[3,3,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,0,11,11,11,11,11,11,11,3,8,16,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Backoff","CachePadded","atomic","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","from","from","from","from","hash","into","into","into_inner","is_completed","new","new","reset","snooze","spin","sync","thread","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","AtomicCell","AtomicConsume","Val","as_ptr","borrow","borrow_mut","compare_and_swap","compare_exchange","default","drop","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_update","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fmt","from","from","from","into","into_inner","is_lock_free","load","load_consume","new","store","swap","take","try_from","try_into","type_id","Parker","ShardedLock","ShardedLockReadGuard","ShardedLockWriteGuard","Unparker","WaitGroup","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","default","default","default","deref","deref","deref_mut","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from_raw","from_raw","get_mut","into","into","into","into","into","into","into_inner","into_raw","into_raw","is_poisoned","new","new","new","park","park_deadline","park_timeout","read","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_read","try_write","type_id","type_id","type_id","type_id","type_id","type_id","unpark","unparker","wait","write","Scope","ScopedJoinHandle","ScopedThreadBuilder","as_pthread_t","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","builder","fmt","fmt","fmt","from","from","from","into","into","into","into_pthread_t","join","name","scope","spawn","spawn","stack_size","thread","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id"],"q":["crossbeam_utils","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::atomic","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::thread","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Performs exponential backoff in spin loops.","Pads and aligns a value to the length of a cache line.","Atomic types.","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Returns the inner value.","Returns true if exponential backoff has completed and …","Creates a new Backoff.","Pads and aligns a value to the length of a cache line.","Resets the Backoff.","Backs off in a blocking loop.","Backs off in a lock-free loop.","Thread synchronization primitives.","Threads that can borrow variables from the stack.","","","","","","","","A thread-safe mutable memory location.","Trait which allows reading from primitive atomic types …","Type returned by load_consume.","Returns a raw pointer to the underlying data in this …","","","If the current value equals current, stores new into the …","If the current value equals current, stores new into the …","","","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies logical “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies logical “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies logical “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Fetches the value, and applies a function to it that …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies logical “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","","","Returns the argument unchanged.","","Calls U::from(self).","Consumes the atomic and returns the contained value.","Returns true if operations on values of this type are …","Loads a value from the atomic cell.","Loads a value from the atomic using a “consume” memory …","Creates a new atomic cell initialized with val.","Stores val into the atomic cell.","Stores val into the atomic cell and returns the previous …","Takes the value of the atomic cell, leaving …","","","","A thread parking primitive.","A sharded reader-writer lock.","A guard used to release the shared read access of a …","A guard used to release the exclusive write access of a …","Unparks a thread parked by the associated Parker.","Enables threads to synchronize the beginning or end of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts a raw pointer into a Parker.","Converts a raw pointer into an Unparker.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this lock, returning the underlying data.","Converts a Parker into a raw pointer.","Converts an Unparker into a raw pointer.","Returns true if the lock is poisoned.","Creates a new Parker.","Creates a new sharded reader-writer lock.","Creates a new wait group and returns the single reference …","Blocks the current thread until the token is made …","Blocks the current thread until the token is made …","Blocks the current thread until the token is made …","Locks with shared read access, blocking the current thread …","","","","","","","","","","","","","","","","","Attempts to acquire this lock with shared read access.","Attempts to acquire this lock with exclusive write access.","","","","","","","Atomically makes the token available if it is not already.","Returns a reference to an associated Unparker.","Drops this reference and waits until all other references …","Locks with exclusive write access, blocking the current …","A scope for spawning threads.","A handle that can be used to join its scoped thread.","Configures the properties of a new thread.","","","","","","","","Creates a builder that can configure a thread before …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Waits for the thread to finish and returns its result.","Sets the name for the new thread.","Creates a new scope for spawning threads.","Spawns a scoped thread.","Spawns a scoped thread with this configuration.","Sets the size of the stack for the new thread.","Returns a handle to the underlying thread.","","","","","","","","",""],"i":[0,0,0,3,2,3,2,2,2,3,2,2,2,2,3,2,3,2,2,2,2,3,2,2,3,3,2,3,3,3,0,0,2,3,2,3,2,3,2,0,0,49,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,49,14,14,14,14,14,14,14,0,0,0,0,0,0,31,29,32,34,35,30,31,29,32,34,35,30,29,30,29,30,31,32,30,34,35,35,35,30,31,29,32,34,34,35,35,30,31,29,32,32,32,34,35,30,31,29,32,31,29,32,34,35,30,32,31,29,32,31,32,30,31,31,31,32,29,30,34,35,31,29,32,34,35,30,31,29,32,34,35,30,32,32,31,29,32,34,35,30,29,31,30,32,0,0,0,42,44,42,45,44,42,45,44,44,42,45,44,42,45,44,42,45,42,42,45,0,44,45,45,42,44,42,45,44,42,45,44,42,45],"f":[0,0,0,[[]],[[]],[[]],[[]],[[[2,[1]]],[[2,[1]]]],[[]],[[],3],[[],[[2,[4]]]],[2],[2],[[[2,[5]],2],6],[[3,7],8],[[[2,[9]],7],8],[[]],[[],2],[[]],[10],[[[2,[11]]]],[[]],[[]],[2],[3,6],[[],3],[[],2],[3],[3],[3],0,0,[[]],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],0,0,0,[14],[[]],[[]],[[[14,[[0,[15,16]]]],[0,[15,16]],[0,[15,16]]],[[0,[15,16]]]],[[[14,[[0,[15,16]]]],[0,[15,16]],[0,[15,16]]],[[12,[[0,[15,16]],[0,[15,16]]]]]],[[],[[14,[4]]]],[14],[[[14,[17]],17],17],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[24]],24],24],[[[14,[25]],25],25],[[[14,[26]],26],26],[[[14,[27]],27],27],[[[14,[28]],28],28],[[[14,[27]],27],27],[[[14,[25]],25],25],[[[14,[26]],26],26],[[[14,[28]],28],28],[[[14,[18]],18],18],[[[14,[6]],6],6],[[[14,[19]],19],19],[[[14,[17]],17],17],[[[14,[22]],22],22],[[[14,[24]],24],24],[[[14,[23]],23],23],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[21]],21],21],[[[14,[26]],26],26],[[[14,[23]],23],23],[[[14,[20]],20],20],[[[14,[25]],25],25],[[[14,[28]],28],28],[[[14,[17]],17],17],[[[14,[27]],27],27],[[[14,[22]],22],22],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[18]],18],18],[[[14,[26]],26],26],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[23]],23],23],[[[14,[17]],17],17],[[[14,[28]],28],28],[[[14,[22]],22],22],[[[14,[25]],25],25],[[[14,[27]],27],27],[[[14,[27]],27],27],[[[14,[28]],28],28],[[[14,[25]],25],25],[[[14,[20]],20],20],[[[14,[18]],18],18],[[[14,[6]],6],6],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[26]],26],26],[[[14,[21]],21],21],[[[14,[17]],17],17],[[[14,[6]],6],6],[[[14,[20]],20],20],[[[14,[18]],18],18],[[[14,[24]],24],24],[[[14,[21]],21],21],[[[14,[23]],23],23],[[[14,[27]],27],27],[[[14,[26]],26],26],[[[14,[22]],22],22],[[[14,[28]],28],28],[[[14,[17]],17],17],[[[14,[19]],19],19],[[[14,[25]],25],25],[[[14,[27]],27],27],[[[14,[25]],25],25],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[19]],19],19],[[[14,[21]],21],21],[[[14,[26]],26],26],[[[14,[20]],20],20],[[[14,[28]],28],28],[[[14,[18]],18],18],[[[14,[17]],17],17],[[[14,[24]],24],24],[[[14,[[0,[15,16]]]]],[[12,[[0,[15,16]],[0,[15,16]]]]]],[[[14,[21]],21],21],[[[14,[28]],28],28],[[[14,[26]],26],26],[[[14,[25]],25],25],[[[14,[6]],6],6],[[[14,[22]],22],22],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[20]],20],20],[[[14,[27]],27],27],[[[14,[23]],23],23],[[[14,[17]],17],17],[[[14,[[0,[15,9]]]],7],8],[10],[[]],[[],14],[[]],[14],[[],6],[[[14,[15]]],15],[[]],[[],14],[14],[14],[[[14,[4]]],4],[[],12],[[],12],[[],13],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,29],[30,30],[[]],[[]],[[],31],[[],[[32,[4]]]],[[],30],[[[34,[33]]]],[[[35,[33]]]],[[[35,[33]]]],[[[35,[33]]]],[30],[[31,7],8],[[29,7],8],[[[32,[[0,[33,9]]]],7],8],[[[34,[[0,[33,36]]]],7],8],[[[34,[9]],7],8],[[[35,[[0,[33,36]]]],7],8],[[[35,[9]],7],8],[[30,7],8],[[]],[[]],[[],32],[[]],[10],[[]],[[]],[[]],[[],31],[[],29],[[[32,[33]]],37],[[]],[[]],[[]],[[]],[[]],[[]],[32,37],[31],[29],[[[32,[33]]],6],[[],31],[[],32],[[],30],[31],[[31,38]],[[31,39]],[[[32,[33]]],[[37,[[34,[33]]]]]],[[]],[[]],[[],40],[[],40],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[[32,[33]]],[[41,[[34,[33]]]]]],[[[32,[33]]],[[41,[[35,[33]]]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[29],[31,29],[30],[[[32,[33]]],[[37,[[35,[33]]]]]],0,0,0,[42,43],[[]],[[]],[[]],[[]],[[]],[[]],[44,45],[[44,7],8],[[42,7],8],[[45,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[42,43],[42,46],[[45,40],45],[[],46],[44,42],[45,[[47,[42]]]],[[45,22],45],[42,48],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],[[],13]],"p":[[8,"Clone"],[3,"CachePadded"],[3,"Backoff"],[8,"Default"],[8,"PartialEq"],[15,"bool"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[15,"never"],[8,"Hash"],[4,"Result"],[3,"TypeId"],[3,"AtomicCell"],[8,"Copy"],[8,"Eq"],[15,"isize"],[15,"u128"],[15,"i128"],[15,"i64"],[15,"u64"],[15,"usize"],[15,"i32"],[15,"u16"],[15,"i8"],[15,"u32"],[15,"u8"],[15,"i16"],[3,"Unparker"],[3,"WaitGroup"],[3,"Parker"],[3,"ShardedLock"],[8,"Sized"],[3,"ShardedLockReadGuard"],[3,"ShardedLockWriteGuard"],[8,"Display"],[6,"LockResult"],[3,"Instant"],[3,"Duration"],[3,"String"],[6,"TryLockResult"],[3,"ScopedJoinHandle"],[6,"RawPthread"],[3,"Scope"],[3,"ScopedThreadBuilder"],[6,"Result"],[6,"Result"],[3,"Thread"],[8,"AtomicConsume"]]},\ +"crunchy":{"doc":"The crunchy unroller - deterministically unroll constant …","t":[14],"n":["unroll"],"q":["crunchy"],"d":["Unroll the given for loop"],"i":[0],"f":[0],"p":[]},\ +"crypto_common":{"doc":"Common cryptographic traits.","t":[8,6,16,8,16,8,8,8,3,6,16,8,6,8,8,16,8,6,16,8,6,16,8,8,11,11,11,11,11,11,11,11,11,2,10,10,11,11,11,11,10,10,11,11,11,11,10,11,11,11,11,11,2,10],"n":["AlgorithmName","Block","BlockSize","BlockSizeUser","Inner","InnerInit","InnerIvInit","InnerUser","InvalidLength","Iv","IvSize","IvSizeUser","Key","KeyInit","KeyIvInit","KeySize","KeySizeUser","Output","OutputSize","OutputSizeUser","ParBlocks","ParBlocksSize","ParBlocksSizeUser","Reset","block_size","borrow","borrow_mut","clone","clone_into","eq","fmt","fmt","from","generic_array","inner_init","inner_iv_init","inner_iv_slice_init","into","iv_size","key_size","new","new","new_from_slice","new_from_slices","output_size","provide","reset","to_owned","to_string","try_from","try_into","type_id","typenum","write_alg_name"],"q":["crypto_common","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Trait which stores algorithm name constant, used in Debug …","Block on which BlockSizeUser implementors operate.","Size of the block in bytes.","Types which process data in blocks.","Inner type.","Types which can be initialized from another type (usually …","Types which can be initialized from another type and …","Types which use another type for initialization.","The error type returned when key and/or IV used in the …","Initialization vector (nonce) used by IvSizeUser …","Initialization vector size in bytes.","Types which use initialization vector (nonce) for …","Key used by KeySizeUser implementors.","Types which can be initialized from key.","Types which can be initialized from key and initialization …","Key size in bytes.","Types which use key for initialization.","Output array of OutputSizeUser implementors.","Size of the output in bytes.","Types which return data with the given size.","Parallel blocks on which ParBlocksSizeUser implementors …","Number of blocks which can be processed in parallel.","Types which can process blocks in parallel.","Resettable types.","Return block size in bytes.","","","","","","","","Returns the argument unchanged.","","Initialize value from the inner.","Initialize value using inner and iv array.","Initialize value using inner and iv slice.","Calls U::from(self).","Return IV size in bytes.","Return key size in bytes.","Create new value from fixed size key.","Create new value from fixed length key and nonce.","Create new value from variable size key.","Create new value from variable length key and nonce.","Return output size in bytes.","","Reset state to its initial value.","","","","","","","Write algorithm name into f."],"i":[0,0,13,0,14,0,0,0,0,0,15,0,0,0,0,16,0,0,17,0,0,18,0,0,13,2,2,2,2,2,2,2,2,0,19,20,20,2,15,16,21,22,21,22,17,2,23,2,2,2,2,2,0,24],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[]],[[]],[2,2],[[]],[[2,2],3],[[2,4],[[6,[5]]]],[[2,4],7],[[]],0,[[]],[8],[[],[[6,[2]]]],[[]],[[],1],[[],1],[9],[[9,8]],[[],[[6,[2]]]],[[],[[6,[2]]]],[[],1],[10],[[]],[[]],[[],11],[[],6],[[],6],[[],12],0,[4,7]],"p":[[15,"usize"],[3,"InvalidLength"],[15,"bool"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[6,"Iv"],[6,"Key"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"BlockSizeUser"],[8,"InnerUser"],[8,"IvSizeUser"],[8,"KeySizeUser"],[8,"OutputSizeUser"],[8,"ParBlocksSizeUser"],[8,"InnerInit"],[8,"InnerIvInit"],[8,"KeyInit"],[8,"KeyIvInit"],[8,"Reset"],[8,"AlgorithmName"]]},\ +"digest":{"doc":"This crate provides traits which describe functionality of …","t":[8,8,8,8,8,8,8,3,3,18,6,16,8,16,8,8,8,8,8,2,11,11,11,11,10,11,10,11,11,11,11,0,0,2,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,11,11,10,10,10,11,10,11,11,11,11,11,11,11,2,14,11,11,10,10,10,10,10,10,11,11,11,10,11,10,10,10,11,11,11,11,11,11,11,11,11,11,2,10,10,10,3,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,6,16,8,6,16,8,16,8,3,3,8,8,13,16,8,16,8,13,3,18,4,8,8,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11],"n":["Digest","DynDigest","ExtendableOutput","ExtendableOutputReset","FixedOutput","FixedOutputReset","HashMarker","InvalidBufferSize","InvalidOutputSize","MAX_OUTPUT_SIZE","Output","OutputSize","OutputSizeUser","Reader","Reset","Update","VariableOutput","VariableOutputReset","XofReader","block_buffer","borrow","borrow","borrow_mut","borrow_mut","box_clone","chain","chain_update","clone","clone","clone_into","clone_into","consts","core_api","crypto_common","default","default","digest","digest_variable","digest_xof","eq","finalize","finalize","finalize","finalize_boxed","finalize_boxed","finalize_boxed_reset","finalize_boxed_reset","finalize_fixed","finalize_fixed_reset","finalize_into","finalize_into","finalize_into","finalize_into_reset","finalize_into_reset","finalize_into_reset","finalize_reset","finalize_reset","finalize_reset","finalize_variable","finalize_variable_reset","finalize_xof","finalize_xof_into","finalize_xof_reset","finalize_xof_reset_into","fmt","fmt","fmt","fmt","from","from","generic_array","impl_oid_carrier","into","into","new","new","new_with_prefix","output_size","output_size","output_size","output_size","provide","provide","read","read_boxed","reset","reset","reset","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","typenum","update","update","update","B0","B1","False","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","Z0","add","bitand","bitand","bitand","bitor","bitor","bitor","bitxor","bitxor","bitxor","bitxor","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","cmp","default","default","default","div","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","into","into","into","max","max","max","max","max","max","max","min","min","min","min","min","min","min","mul","mul","mul","neg","new","new","new","new","new","not","not","partial_cmp","partial_cmp","partial_cmp","partial_div","powi","powi","powi","rem","sub","sub","sub","to_bool","to_bool","to_i16","to_i32","to_i64","to_i8","to_int","to_int","to_int","to_int","to_isize","to_owned","to_owned","to_owned","to_u8","to_u8","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","AlgorithmName","Block","BlockSize","BlockSizeUser","Buffer","BufferKind","BufferKindUser","Core","CoreProxy","CoreWrapper","CtVariableCoreWrapper","ExtendableOutputCore","FixedOutputCore","Left","OutputSize","OutputSizeUser","ReaderCore","Reset","Right","RtVariableCoreWrapper","TRUNC_SIDE","TruncSide","UpdateCore","VariableOutputCore","XofReaderCore","XofReaderCoreWrapper","block_size","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","decompose","default","default","default","finalize_fixed_core","finalize_fixed_core","finalize_into","finalize_into_reset","finalize_variable","finalize_variable_core","finalize_variable_reset","finalize_xof","finalize_xof_core","finalize_xof_reset","flush","flush","fmt","fmt","fmt","fmt","from","from","from","from","from","from_core","into","into","into","into","into","new","new","new","new_from_slice","output_size","output_size","read","read","read_block","reset","reset","reset","reset","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","update","update","update_blocks","update_blocks","write","write","write_alg_name","write_alg_name"],"q":["digest","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","digest::consts","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","digest::core_api","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Convenience wrapper trait covering functionality of …","Modification of the Digest trait suitable for trait …","Trait for hash functions with extendable-output (XOF).","Trait for hash functions with extendable-output (XOF) able …","Trait for hash functions with fixed-size output.","Trait for hash functions with fixed-size output able to …","Marker trait for cryptographic hash functions.","Buffer length is not equal to hash output size.","The error type used in variable hash traits.","Maximum size of output hash.","Output array of OutputSizeUser implementors.","Size of the output in bytes.","Types which return data with the given size.","Reader","Resettable types.","Types which consume data with byte granularity.","Trait for hash functions with variable-size output.","Trait for hash functions with variable-size output able to …","Trait for reader types which are used to extract …","","","","","","Clone hasher state into a boxed trait object","Digest input data in a chained manner.","Process input data in a chained manner.","","","","","Type aliases for many constants.","Low-level traits operating on blocks and wrappers around …","","","","Compute hash of data.","Compute hash of data and write it to output.","Compute hash of data and write it into output.","","Retrieve result and consume hasher instance.","Retrieve result and consume boxed hasher instance","Retrieve result and consume boxed hasher instance","Retrieve result into a boxed slice of the specified size …","Retrieve result into a boxed slice and consume hasher.","Retrieve result into a boxed slice of the specified size …","Retrieve result into a boxed slice and reset the hasher …","Retrieve result and consume the hasher instance.","Retrieve result and reset the hasher state.","Write result into provided array and consume the hasher …","Write result into provided array and consume the hasher …","Consume value and write result into provided array.","Write result into provided array and reset the hasher …","Write result into provided array and reset the hasher …","Write result into provided array and reset the hasher …","Retrieve result and reset hasher instance.","Retrieve result and reset hasher instance","Retrieve result and reset hasher instance","Write result into the output buffer.","Write result into the output buffer and reset the hasher …","Retrieve XOF reader and consume hasher instance.","Finalize XOF and write result into out.","Retrieve XOF reader and reset hasher instance state.","Finalize XOF, write result into out, and reset the hasher …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Implement dummy type with hidden docs which is used to “…","Calls U::from(self).","Calls U::from(self).","Create new hasher instance.","Create new hasher instance with the given output size.","Create new hasher instance which has processed the …","Get output size of the hasher","Get output size of the hasher","Get output size of the hasher instance provided to the new …","Return output size in bytes.","","","Read output into the buffer. Can be called an unlimited …","Read output into a boxed slice of the specified size.","Reset state to its initial value.","Reset hasher instance to its initial state.","Reset hasher instance to its initial state.","","","","","","","","","","","","Process data, updating the internal state.","Digest input data.","Update state using the provided data.","The type-level bit 0.","The type-level bit 1.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The type-level signed integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","Instantiates a singleton representing this bit.","Instantiates a singleton representing this bit.","","Instantiates a singleton representing the integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Trait which stores algorithm name constant, used in Debug …","Block on which BlockSizeUser implementors operate.","Size of the block in bytes.","Types which process data in blocks.","Buffer type used by type which implements BufferKindUser.","Block buffer kind over which type operates.","Types which use BlockBuffer functionality.","Type wrapped by CoreWrapper.","A proxy trait to a core type implemented by CoreWrapper","Wrapper around BufferKindUser.","Wrapper around VariableOutputCore which selects output size","Core trait for hash functions with extendable (XOF) output …","Core trait for hash functions with fixed output size.","Truncate left side, i.e. &out[..n].","Size of the output in bytes.","Types which return data with the given size.","XOF reader core state.","Resettable types.","Truncate right side, i.e. &out[m..].","Wrapper around VariableOutputCore which selects output size","Side which should be used in a truncated result.","Type which used for defining truncation side in the …","Types which consume data in blocks.","Core trait for hash functions with variable output size.","Core reader trait for extendable-output function (XOF) …","Wrapper around XofReaderCore implementations.","Return block size in bytes.","","","","","","","","","","","","","","","","","","","","","Decompose wrapper into inner parts.","","","","Finalize state using remaining data stored in the provided …","","","","","Finalize hasher and write full hashing result into the out …","","","Retrieve XOF reader using remaining data stored in the …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create new wrapper from core.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Initialize hasher state for given output size.","","","","Return output size in bytes.","","","","Read next XOF block.","Reset state to its initial value.","","","","","","","","","","","","","","","","","","","","","","","","","","Update state using the provided data blocks.","","","","Write algorithm name into f.",""],"i":[0,0,0,0,0,0,0,0,0,44,0,45,0,46,0,0,0,0,0,0,4,5,4,5,1,47,48,4,5,4,5,0,0,0,4,5,48,44,46,5,48,1,1,46,44,49,50,51,52,48,1,51,48,1,52,48,1,1,44,50,46,46,49,49,4,4,5,5,4,5,0,0,4,5,48,44,48,48,1,44,45,4,5,53,53,54,48,1,4,5,4,5,4,5,4,5,4,5,0,48,1,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,16,17,17,16,16,17,16,16,17,17,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,15,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,16,16,17,17,15,15,15,16,16,17,17,15,15,15,15,15,15,15,16,16,17,17,15,16,17,16,17,15,15,15,15,15,15,15,15,15,16,17,15,15,15,15,15,15,15,15,15,16,17,15,16,17,16,17,15,16,17,15,16,17,15,0,0,55,0,0,56,0,57,0,0,0,0,0,36,45,0,58,0,36,0,59,0,0,0,0,0,55,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,34,32,34,35,60,32,34,34,33,59,33,34,58,34,33,34,33,34,35,36,32,33,34,35,36,34,32,33,34,35,36,59,33,34,34,45,33,35,35,61,54,32,33,34,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,33,34,62,32,33,34,63,32],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[],[[2,[1]]]],[3],[3],[4,4],[5,5],[[]],[[]],0,0,0,[[],4],[[],5],[3,6],[3,[[7,[4]]]],[3],[[5,5],8],[[],6],[2,2],[2,2],[9,2],[[],2],[9,2],[[],2],[[],6],[[],6],[6],[[],[[7,[5]]]],[6],[6],[[],[[7,[5]]]],[6],[[],6],[[],2],[[],2],[[],[[7,[5]]]],[[],[[7,[5]]]],[[]],[[]],[[]],[[]],[[4,10],11],[[4,10],11],[[5,10],11],[[5,10],11],[[]],[[]],0,0,[[]],[[]],[[]],[9,[[7,[4]]]],[3],[[],9],[[],9],[[],9],[[],9],[12],[12],[[]],[9,2],[[]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],0,[3],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[15],[16],[[17,16]],[[17,17]],[[16,17]],[[16,16]],[17],[[16,17]],[[16,16]],[[17,17]],[[17,16]],[[]],[[]],[[]],[[]],[[]],[[]],[16,16],[17,17],[15,15],[[]],[[]],[[]],[[16,16],18],[[17,17],18],[[15,15],18],[[],16],[[],17],[[],15],[15],[[16,16],8],[[17,17],8],[[15,15],8],[[16,10],[[7,[19]]]],[[17,10],[[7,[19]]]],[[15,10],[[7,[19]]]],[[]],[[]],[[]],[16],[17],[15],[[]],[[]],[[]],[[16,17],17],[[16,16],16],[[17,16],17],[[17,17],17],[[15,20]],[[15,21]],[[15,15]],[[16,17],16],[[16,16],16],[[17,16],16],[[17,17],17],[[15,21]],[[15,15]],[[15,20]],[[15,22]],[[15,23]],[15],[15],[[],16],[[],16],[[],17],[[],17],[[],15],[16],[17],[[16,16],[[24,[18]]]],[[17,17],[[24,[18]]]],[[15,15],[[24,[18]]]],[[]],[[15,15]],[[15,21]],[[15,20]],[15],[[15,20]],[[15,21]],[[15,15]],[[],8],[[],8],[[],25],[[],26],[[],27],[[],28],[[],26],[[],27],[[],28],[[],25],[[],29],[[]],[[]],[[]],[[],30],[[],30],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],[[],14],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],9],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[32,[31,31,31]]],[[32,[31,31,31]]]],[[[33,[31]]],[[33,[31]]]],[[[34,[31]]],[[34,[31]]]],[[[35,[31]]],[[35,[31]]]],[36,36],[[]],[[]],[[]],[[]],[[]],[34],[[],32],[[],[[34,[37]]]],[[],[[35,[37]]]],[[38,6]],[[32,38,39]],[[34,6]],[[34,6]],[33,[[7,[5]]]],[[38,6]],[33,[[7,[5]]]],[34],[38],[34],[33,40],[34,40],[[33,10],[[7,[19]]]],[[34,10],[[7,[19]]]],[[35,10],[[7,[19]]]],[[36,10],11],[[]],[[]],[[]],[[]],[[]],[[],34],[[]],[[]],[[]],[[]],[[]],[9,[[7,[4]]]],[9,[[7,[33,4]]]],[41,34],[[],[[7,[34,42]]]],[[],9],[33,9],[35,[[40,[9]]]],[35],[[],43],[[]],[32],[33],[34],[[]],[[]],[[]],[[]],[[]],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],[[],14],[[],14],[[],14],[33],[34],[[]],[32],[33,[[40,[9]]]],[34,[[40,[9]]]],[10,[[7,[19]]]],[10,11]],"p":[[8,"DynDigest"],[3,"Box"],[8,"AsRef"],[3,"InvalidOutputSize"],[3,"InvalidBufferSize"],[6,"Output"],[4,"Result"],[15,"bool"],[15,"usize"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Z0"],[3,"B0"],[3,"B1"],[4,"Ordering"],[3,"Error"],[3,"PInt"],[3,"NInt"],[3,"TArr"],[3,"ATerm"],[4,"Option"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"u8"],[8,"Clone"],[3,"CtVariableCoreWrapper"],[3,"RtVariableCoreWrapper"],[3,"CoreWrapper"],[3,"XofReaderCoreWrapper"],[4,"TruncSide"],[8,"Default"],[6,"Buffer"],[3,"GenericArray"],[6,"Result"],[6,"Key"],[3,"InvalidLength"],[6,"Block"],[8,"VariableOutput"],[8,"OutputSizeUser"],[8,"ExtendableOutput"],[8,"Update"],[8,"Digest"],[8,"ExtendableOutputReset"],[8,"VariableOutputReset"],[8,"FixedOutput"],[8,"FixedOutputReset"],[8,"XofReader"],[8,"Reset"],[8,"BlockSizeUser"],[8,"BufferKindUser"],[8,"CoreProxy"],[8,"ExtendableOutputCore"],[8,"VariableOutputCore"],[8,"FixedOutputCore"],[8,"XofReaderCore"],[8,"UpdateCore"],[8,"AlgorithmName"]]},\ +"enum_as_inner":{"doc":"enum-as-inner","t":[24],"n":["EnumAsInner"],"q":["enum_as_inner"],"d":["Derive functions on an Enum for easily accessing …"],"i":[0],"f":[0],"p":[]},\ +"firewood":{"doc":"Firewood: non-archival blockchain key-value store with …","t":[0,0,0,13,3,3,4,3,3,3,13,13,3,13,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,13,3,3,3,4,3,13,3,13,3,3,13,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,13,13,3,4,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["db","merkle","proof","Blob","DB","DBConfig","DBError","DBRev","DBRevConfig","DiskBufferConfig","InvalidParams","Merkle","Revision","System","WALConfig","WriteBatch","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","builder","builder","builder","builder","clone","clone","clone","clone_into","clone_into","clone_into","commit","create_account","delete_account","deref","drop","dump","dump","dump_account","dump_account","exist","exist","fmt","from","from","from","from","from","from","from","from","from","get_balance","get_balance","get_code","get_code","get_nonce","get_nonce","get_revision","get_state","get_state","into","into","into","into","into","into","into","into","into","kv_dump","kv_dump","kv_insert","kv_remove","kv_root_hash","kv_root_hash","new","new_writebatch","no_root_hash","root_hash","root_hash","set_balance","set_code","set_nonce","set_state","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","0","0","0","0","Format","Hash","IdTrans","Merkle","MerkleError","Node","NotBranchNode","PartialPath","ReadOnly","Ref","RefMut","Shale","ValueTransformer","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","decode","dehydrate","dehydrate","dehydrated_len","dehydrated_len","deref","deref","deref","dump","empty_root","eq","eq","eq","flush_dirty","fmt","fmt","from","from","from","from","from","from","from","from","from_nibbles","get","get","get_mut","get_store","hydrate","hydrate","init_root","insert","into","into","into","into","into","into","into","into","into_inner","new","prove","remove","remove_tree","root_hash","to_nibbles","to_owned","to_owned","to_owned","transform","transform","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write","0","0","0","DecodeError","NoSuchNode","Proof","ProofError","ProofNodeMissing","SubProof","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","deserialize","fmt","from","from","from","into","into","into","serialize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","verify_proof"],"q":["firewood","","","firewood::db","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","firewood::db::DBError","","","firewood::merkle","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","firewood::merkle::MerkleError","","firewood::proof","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","Firewood database handle.","Database configuration.","","Some readable version of the DB.","Config for accessing a version of the DB.","Config for the disk buffer.","","","Lock protected handle to a readable version of the DB.","","","An atomic batch of changes made to the DB. Each operation …","","","","","","","","","","","","","","","","","","","Create a builder for building DBRevConfig. On the builder, …","Create a builder for building DBConfig. On the builder, …","Create a builder for building WALConfig. On the builder, …","Create a builder for building DiskBufferConfig. On the …","","","","","","","Persist all changes to the DB. The atomicity of the …","Create an account.","Delete an account.","","","Dump the MPT of the entire account model storage.","Dump the MPT of the latest entire account model storage.","Dump the MPT of the state storage under an account.","Dump the MPT of the latest state storage under an account.","Check if the account exists.","Check if the account exists in the latest world state.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get balance of the account.","Get the latest balance of the account.","Get code of the account.","Get the latest code of the account.","Get nonce of the account.","Get the latest nonce of the account.","Get a handle that grants the access to some historical …","Get the state value indexed by sub_key in the account …","Get the latest state value indexed by sub_key in the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Dump the MPT of the generic key-value storage.","Dump the MPT of the latest generic key-value storage.","Insert an item to the generic key-value storage.","Remove an item from the generic key-value storage. val …","Get root hash of the generic key-value storage.","Get root hash of the latest generic key-value storage.","Open a database.","Create a write batch.","Do not rehash merkle roots upon commit. This will leave …","Get root hash of the world state of all accounts.","Get root hash of the latest world state of all accounts.","Set balance of the account.","Set code of the account.","Set nonce of the account.","Set the state value indexed by sub_key in the account …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","PartialPath keeps a list of nibbles to represent a path on …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Constructs a merkle proof for key. The result contains all …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Hash -> RLP encoding map","","","SubProof contains the RLP encoding and the hash value of a …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","verify_proof checks merkle proofs. The given proof must …"],"i":[0,0,0,5,0,0,0,0,0,0,5,5,0,5,0,0,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,1,23,2,3,1,2,3,1,2,3,4,4,4,8,4,9,11,9,11,9,11,5,9,11,8,4,5,1,23,2,3,9,11,9,11,9,11,11,9,11,9,11,8,4,5,1,23,2,3,9,11,4,4,9,11,11,11,4,9,11,4,4,4,4,1,2,3,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,41,42,43,21,30,0,0,0,0,0,30,0,30,0,0,30,0,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,21,25,26,21,25,26,25,21,26,21,26,27,21,25,28,28,21,25,26,28,30,25,28,27,33,44,30,21,25,26,0,28,33,28,28,21,26,28,28,28,27,33,44,30,21,25,26,25,28,28,28,28,28,0,21,25,26,45,44,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,33,46,47,38,40,40,0,0,40,0,48,38,40,48,38,40,38,40,48,38,40,48,38,40,38,48,38,40,48,38,40,48,38,40,38],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[1,1],[2,2],[3,3],[[]],[[]],[[]],[4],[4,[[6,[4,5]]]],[[4,7],[[6,[4,5]]]],[8,9],[4],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[9,[[6,[12,5]]]],[11,[[6,[12,5]]]],[[5,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9,[[6,[15,5]]]],[11,[[6,[15,5]]]],[9,[[6,[[17,[16]],5]]]],[11,[[6,[[17,[16]],5]]]],[9,[[6,[18,5]]]],[11,[[6,[18,5]]]],[[11,19,[7,[1]]],[[7,[8]]]],[9,[[6,[[17,[16]],5]]]],[11,[[6,[[17,[16]],5]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[[4,20,[17,[16]]],[[6,[4,5]]]],[[4,20,7],[[6,[4,5]]]],[9,[[6,[21,5]]]],[11,[[6,[21,5]]]],[[22,23],[[6,[11,5]]]],[11,4],[4,4],[9,[[6,[21,5]]]],[11,[[6,[21,5]]]],[[4,15],[[6,[4,5]]]],[4,[[6,[4,5]]]],[[4,18],[[6,[4,5]]]],[[4,[17,[16]]],[[6,[4,5]]]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[21,21],[25,25],[26,26],[[]],[[]],[[]],[20],[21],[26],[21,18],[26,18],[27],[21],[25],[[28,[29,[26]],10],[[6,[30]]]],[[],21],[[21,21],12],[[25,25],12],[[26,26],12],[28,7],[[30,13],14],[[25,13],[[6,[31]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],32],[[28,20,[29,[26]]],[[6,[[7,[27]],30]]]],[33,27],[[28,20,[29,[26]]],[[6,[[7,[33]],30]]]],[28,34],[[18,35],[[6,[21,36]]]],[[18,35],[[6,[26,36]]]],[[29,34],[[6,[30]]]],[[28,20,[17,[16]],[29,[26]]],[[6,[30]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,[[17,[16]]]],[[[37,[34]]],28],[[28,[29,[26]]],[[6,[38,30]]]],[[28,20,[29,[26]]],[[6,[[7,[[17,[16]]]],30]]]],[[28,[29,[26]]],[[6,[30]]]],[[28,[29,[26]]],[[6,[21,30]]]],[[],32],[[]],[[]],[[]],[[],[[17,[16]]]],[[],[[17,[16]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[33,39],[[6,[30]]]],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[],[[6,[38]]]],[[40,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[38,6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[38,20],[[6,[[7,[[17,[16]]]],40]]]]],"p":[[3,"DBRevConfig"],[3,"WALConfig"],[3,"DiskBufferConfig"],[3,"WriteBatch"],[4,"DBError"],[4,"Result"],[4,"Option"],[3,"Revision"],[3,"DBRev"],[8,"Write"],[3,"DB"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"U256"],[15,"u8"],[3,"Vec"],[15,"u64"],[15,"usize"],[8,"AsRef"],[3,"Hash"],[15,"str"],[3,"DBConfig"],[3,"TypeId"],[3,"PartialPath"],[3,"Node"],[3,"Ref"],[3,"Merkle"],[3,"ObjPtr"],[4,"MerkleError"],[3,"Error"],[8,"Iterator"],[3,"RefMut"],[8,"ShaleStore"],[8,"MemStore"],[4,"ShaleError"],[3,"Box"],[3,"Proof"],[8,"FnOnce"],[4,"ProofError"],[13,"Merkle"],[13,"Blob"],[13,"System"],[3,"IdTrans"],[8,"ValueTransformer"],[13,"Shale"],[13,"Format"],[3,"SubProof"]]},\ +"fixed_hash":{"doc":"","t":[14,14],"n":["construct_fixed_hash","impl_fixed_hash_conversions"],"q":["fixed_hash",""],"d":["Construct a fixed-size hash type.","Implements lossy conversions between the given types."],"i":[0,0],"f":[0,0],"p":[]},\ +"futures":{"doc":"Abstractions for asynchronous programming.","t":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,14,0,0,14,14,14,0,14,14,14,0,0,14,0,14,0,0,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,3,3,3,3,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,8,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,16,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,5,11,11,5,11,11,11,11,11,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,13,13,3,13,13,8,8,8,8,8,8,8,8,13,3,3,3,3,13,13,13,3,3,3,13,13,3,13,13,3,13,3,4,13,13,13,13,3,3,13,13,3,13,13,13,3,3,13,3,3,13,13,13,13,13,13,13,13,13,3,3,3,3,13,3,3,3,3,3,13,6,3,3,3,4,3,13,13,13,3,13,13,13,13,3,13,3,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,8,8,8,8,16,16,16,8,16,16,16,16,8,8,8,8,2,2,2,2,2,2,2,2,2,10,0,10,10,10,10,10,10,10,10,11,10,10,10,11,0,11,10,0,10,10,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,8,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,16,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,5,11,11,11,11,10,5,11,11,11,11,11,11,11,11,11,10,11,10,5,5,5,5,5,5,11,11,11,11,11,11,5,11,11,5,11,11,5,10,5,5,11,5,11,11,5,5,5,11,11,11,11,5,5,5,5,5,5,10,11,5,11,11,12,12,12,12,12,12,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,5,11,11,11,11,10,11,10,11,10,11,11,11,11,11,11,10,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,10,5,11,11,11,11,11,5,11,11,5,5,5,10,11,11,5,5,11,11,5,0,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,5,5,11,11,3,3,3,3,3,3,3,3,3,3,5,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,5,0,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,8,3,3,3,3,8,8,13,4,3,3,13,8,3,8,8,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,5,11,5,11,12],"n":["AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","Future","FutureExt","Sink","SinkExt","Stream","StreamExt","TryFuture","TryFutureExt","TryStream","TryStreamExt","executor","future","io","join","lock","never","pending","pin_mut","poll","prelude","ready","select","select_biased","sink","stream","stream_select","task","try_join","mpsc","oneshot","Receiver","SendError","Sender","TryRecvError","TrySendError","UnboundedReceiver","UnboundedSender","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","close_channel","close_channel","disconnect","disconnect","drop","drop","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","hash_receiver","hash_receiver","into","into","into","into","into","into","into","into_inner","into_send_error","is_closed","is_closed","is_connected_to","is_connected_to","is_disconnected","is_disconnected","is_full","is_full","is_terminated","is_terminated","poll_close","poll_close","poll_close","poll_flush","poll_flush","poll_flush","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","provide","provide","provide","same_receiver","same_receiver","start_send","start_send","start_send","start_send","start_send","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll_next","try_poll_next","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","unbounded_send","Canceled","Cancellation","Receiver","Sender","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancellation","channel","clone","clone_into","close","drop","drop","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","into_future","into_future","is_canceled","is_connected_to","is_terminated","poll","poll","poll_canceled","provide","send","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_poll","try_recv","type_id","type_id","type_id","type_id","BlockingStream","Enter","EnterError","LocalPool","LocalSpawner","block_on","block_on_stream","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","default","deref","deref_mut","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_inner","into_iter","new","next","provide","run","run_until","run_until_stalled","size_hint","spawn_local_obj","spawn_obj","spawner","status","status_local","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_run_one","type_id","type_id","type_id","type_id","type_id","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Output","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abortable","and_then","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed_local","catch_unwind","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","consume","default","downgrade","drop","drop","drop","err","err_into","factor_first","factor_second","flatten","flatten_sink","flatten_stream","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_iter","fuse","inspect","inspect_err","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_raw","into_raw","into_stream","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","map","map_err","map_into","map_ok","map_ok_or_else","maybe_done","never_error","now_or_never","ok","ok_into","or_else","output_mut","output_mut","peek","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_read","poll_read_vectored","poll_ready","poll_ready","poll_ready","poll_ready","poll_seek","poll_unpin","poll_write","poll_write_vectored","ready","remote_handle","right_future","select","select_all","select_ok","shared","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","strong_count","take_output","take_output","terminated","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_flatten","try_flatten_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_unpin","try_select","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unit_error","unwrap_or_else","upgrade","weak_count","0","0","0","0","0","0","0","1","AddrInUse","AddrNotAvailable","AllowStdIo","AlreadyExists","ArgumentListTooLong","AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","BrokenPipe","BufReader","BufWriter","Chain","Close","ConnectionAborted","ConnectionRefused","ConnectionReset","Copy","CopyBuf","CopyBufAbortable","CrossesDevices","Current","Cursor","Deadlock","DirectoryNotEmpty","Empty","End","Error","ErrorKind","ExecutableFileBusy","FileTooLarge","FilesystemLoop","FilesystemQuotaExceeded","FillBuf","Flush","HostUnreachable","Interrupted","IntoSink","InvalidData","InvalidFilename","InvalidInput","IoSlice","IoSliceMut","IsADirectory","LineWriter","Lines","NetworkDown","NetworkUnreachable","NotADirectory","NotConnected","NotFound","NotSeekable","Other","OutOfMemory","PermissionDenied","Read","ReadExact","ReadHalf","ReadLine","ReadOnlyFilesystem","ReadToEnd","ReadToString","ReadUntil","ReadVectored","Repeat","ResourceBusy","Result","ReuniteError","SeeKRelative","Seek","SeekFrom","Sink","StaleNetworkFileHandle","Start","StorageFull","Take","TimedOut","TooManyLinks","UnexpectedEof","Unsupported","Window","WouldBlock","Write","WriteAll","WriteHalf","WriteVectored","WriteZero","advance","advance","advance_slices","advance_slices","as_mut","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","buffer","buffer","cause","chain","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","close","cmp","cmp","consume","consume","consume","consume","consume","consume","consume","consume","consume","consume_unpin","copy","copy_buf","copy_buf_abortable","default","deref","deref","deref_mut","description","downcast","empty","end","eq","eq","eq","fill_buf","fill_buf","flush","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_raw_os_error","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_sink","kind","last_os_error","limit","lines","new","new","new","new","new","new","new","new","new","other","partial_cmp","partial_cmp","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_next","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_ready","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek_relative","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","position","provide","provide","raw_os_error","read","read","read_exact","read_exact","read_line","read_to_end","read_to_end","read_to_string","read_to_string","read_until","read_vectored","read_vectored","repeat","reunite","reunite","seek","seek","seek_relative","set","set_limit","set_position","sink","source","split","start","start_send","stream_position","take","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_capacity","with_capacity","with_capacity","write","write","write_all","write_all","write_fmt","write_vectored","write_vectored","0","0","0","MappedMutexGuard","Mutex","MutexGuard","MutexLockFuture","OwnedMutexGuard","OwnedMutexLockFuture","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","deref","deref","deref_mut","deref_mut","deref_mut","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","get_mut","into","into","into","into","into","into","into_future","into_future","into_inner","is_terminated","is_terminated","lock","lock_owned","map","map","new","poll","poll","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","type_id","type_id","type_id","type_id","type_id","type_id","Never","AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","Error","Error","Future","Item","Ok","Ok","Output","Sink","Stream","TryFuture","TryStream","_","_","_","_","_","_","_","_","_","consume","future","poll","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_next","poll_read","poll_read_vectored","poll_ready","poll_seek","poll_write","poll_write_vectored","sink","size_hint","start_send","stream","try_poll","try_poll_next","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Output","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abortable","and_then","boxed","boxed_local","catch_unwind","drop","err","err_into","flatten","flatten_sink","flatten_stream","fuse","inspect","inspect_err","inspect_ok","into_future","into_raw","into_stream","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","map","map_err","map_into","map_ok","map_ok_or_else","maybe_done","never_error","now_or_never","ok","ok_into","or_else","pending","poll","poll_fn","poll_immediate","poll_unpin","ready","remote_handle","right_future","select","select_all","select_ok","shared","then","try_flatten","try_flatten_stream","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll_unpin","try_select","unit_error","unwrap_or_else","0","0","0","0","0","0","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","buffer","close","drain","fanout","feed","flush","left_sink","poll_close","poll_close_unpin","poll_flush","poll_flush_unpin","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","start_send","start_send_unpin","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abortable","all","and_then","any","boxed","boxed_local","buffer_unordered","buffered","by_ref","catch_unwind","chain","chunks","collect","concat","count","cycle","empty","enumerate","err_into","filter","filter_map","flat_map","flat_map_unordered","flatten","flatten_unordered","fold","for_each","for_each_concurrent","forward","fuse","futures_unordered","inspect","inspect_err","inspect_ok","into_async_read","into_future","into_stream","is_terminated","iter","left_stream","map","map_err","map_ok","next","once","or_else","peekable","pending","poll_fn","poll_immediate","poll_next","poll_next_unpin","ready_chunks","repeat","repeat_with","right_stream","scan","select","select_all","select_all","select_next_some","select_with_strategy","size_hint","skip","skip_while","split","take","take_until","take_while","then","try_buffer_unordered","try_buffered","try_chunks","try_collect","try_concat","try_filter","try_filter_map","try_flatten","try_fold","try_for_each","try_for_each_concurrent","try_next","try_poll_next","try_poll_next_unpin","try_skip_while","try_take_while","try_unfold","unfold","unzip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","IntoIter","Iter","IterMut","SelectAll","select_all","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","clone","clone","clone","clone_into","clone_into","clone_into","close","drain","fanout","feed","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","is_terminated","is_terminated","is_terminated","is_terminated","left_sink","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close_unpin","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush_unpin","poll_next","poll_next","poll_next","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send_unpin","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abort","abortable","all","and_then","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed_local","buffer_unordered","buffered","by_ref","catch_unwind","chain","chunks","clear","clear","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","collect","concat","consume","count","cycle","default","default","default","default","drop","empty","enumerate","eq","eq","eq","err_into","extend","extend","extend","filter","filter_map","flat_map","flat_map_unordered","flatten","flatten_unordered","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fold","for_each","for_each_concurrent","forward","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","fuse","futures_unordered","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","inspect","inspect_err","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_async_read","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_stream","is_aborted","is_done","is_empty","is_empty","is_empty","is_stopped","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","iter","iter","iter","iter_mut","iter_mut","iter_pin_mut","iter_pin_ref","left_stream","len","len","len","map","map_err","map_ok","new","new","new","new","new_pair","next","next_if","next_if_eq","once","or_else","peek","peek_mut","peekable","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next_unpin","poll_peek","poll_peek_mut","poll_read","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_write","provide","provide","provide","push","push","push","push_back","push_front","ready_chunks","repeat","repeat_with","reunite","reunite","right_stream","scan","select","select_all","select_all","select_next_some","select_with_strategy","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","skip","skip_while","spawn_local_obj","spawn_obj","split","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","take","take_future","take_result","take_until","take_while","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","toggle","try_buffer_unordered","try_buffered","try_chunks","try_collect","try_concat","try_filter","try_filter_map","try_flatten","try_fold","try_for_each","try_for_each_concurrent","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next_unpin","try_skip_while","try_take_while","try_unfold","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","unzip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","next","next","next","next","next","size_hint","size_hint","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","IntoIter","Iter","IterMut","SelectAll","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","into_iter","next","next","next","select_all","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","ArcWake","AtomicWaker","Context","FutureObj","LocalFutureObj","LocalSpawn","LocalSpawnExt","Pending","Poll","RawWaker","RawWakerVTable","Ready","Spawn","SpawnError","SpawnExt","UnsafeFutureObj","Waker","WakerRef","as_raw","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","branch","branch","clone","clone","clone","clone_into","clone_into","clone_into","cmp","data","default","deref","drop","drop","drop","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_output","from_output","from_raw","from_residual","from_residual","from_residual","from_waker","hash","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_pending","is_ready","is_shutdown","map","map_err","map_err","map_ok","map_ok","new","new","new","new","new","new","new_unowned","noop_waker","noop_waker_ref","partial_cmp","poll","poll","provide","ready","register","shutdown","spawn","spawn_local","spawn_local_obj","spawn_local_with_handle","spawn_obj","spawn_with_handle","status","status_local","take","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vtable","wake","wake","wake","wake_by_ref","wake_by_ref","waker","waker","waker_ref","will_wake","0"],"q":["futures","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::channel","","futures::channel::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::channel::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::executor","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::future::Either","","futures::future::MaybeDone","","futures::future::TryMaybeDone","","futures::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::io::SeekFrom","","","futures::lock","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::never","futures::prelude","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::future::Either","","futures::prelude::future::MaybeDone","","futures::prelude::future::TryMaybeDone","","futures::prelude::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::stream::futures_unordered","","","","","","futures::prelude::stream::select_all","","","","","futures::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream::futures_unordered","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream::select_all","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::task::Poll"],"d":["","","","","","","","","","","","","","","","","","","Built-in executors and related tools.","Asynchronous values.","Asynchronous I/O.","Polls multiple futures simultaneously, returning a tuple …","Futures-powered synchronization primitives.","This module contains the Never type.","A macro which yields to the event loop once.","Pins a value on the stack.","A macro which returns the result of polling a future once …","A “prelude” for crates using the futures crate.","Extracts the successful type of a Poll<T>.","Polls multiple futures and streams simultaneously, …","Polls multiple futures and streams simultaneously, …","Asynchronous sinks.","Asynchronous streams.","Combines several streams, all producing the same Item …","Tools for working with tasks.","Polls multiple futures simultaneously, resolving to a …","A multi-producer, single-consumer queue for sending values …","A channel for sending a single message between …","The receiving end of a bounded mpsc channel.","The error type for Senders used as Sinks.","The transmission end of a bounded mpsc channel.","The error type returned from try_next.","The error type returned from try_send.","The receiving end of an unbounded mpsc channel.","The transmission end of an unbounded mpsc channel.","","","","","","","","","","","","","","","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel, without dropping …","Closes the receiving half of a channel, without dropping …","Closes this channel from the sender side, preventing any …","Closes this channel from the sender side, preventing any …","Disconnects this sender from the channel, closing it if …","Disconnects this sender from the channel, closing it if …","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Hashes the receiver into the provided hasher","Hashes the receiver into the provided hasher","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the message that was attempted to be sent but …","Drops the message and converts into a SendError.","Returns whether this channel is closed without needing a …","Returns whether this channel is closed without needing a …","Returns whether the sender send to this receiver.","Returns whether the sender send to this receiver.","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the channel …","Returns true if this error is a result of the channel …","","","","","","","","","","","","Polls the channel to determine if there is guaranteed …","","","Check if the channel is ready to receive a message.","","","","Returns whether the senders send to the same receiver.","Returns whether the senders send to the same receiver.","","Send a message on the channel.","","Send a message on the channel.","","","","","","","","","","","","","","","","","","","","","","","Tries to receive the next message without notifying a …","Tries to receive the next message without notifying a …","","","Attempts to send a message on this Sender, returning the …","","","","","","","","Creates an unbounded mpsc channel for communicating …","Sends a message along this channel.","Error returned from a Receiver when the corresponding …","A future that resolves when the receiving end of a channel …","A future for a value that will be provided by another …","A means of transmitting a single value to another task.","","","","","","","","","Creates a future that resolves when this Sender’s …","Creates a new one-shot channel for sending a single value …","","","Gracefully close this receiver, preventing any subsequent …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Tests to see whether this Sender’s corresponding Receiver","Tests to see whether this Sender is connected to the given …","","","","Polls this Sender half to detect whether its associated …","","Completes this oneshot with a successful result.","","","","","","","","","","","","Attempts to receive a message outside of the context of a …","","","","","An iterator which blocks on values from a stream until …","Represents an executor context.","An error returned by enter if an execution scope has …","A single-threaded task pool for polling futures to …","A handle to a LocalPool that implements Spawn.","Run a future to completion on the current thread.","Turn a stream into a blocking iterator.","","","","","","","","","","","","","","","","","Marks the current thread as being within the dynamic …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this BlockingStream into the inner Stream type.","","Create a new, empty pool of tasks.","","","Run all tasks in the pool to completion.","Runs all the tasks in the pool until the given future …","Runs all tasks in the pool and returns if no more progress …","","","","Get a clonable handle to the pool as a Spawn.","","","","","","","","","","","","","","","Runs all tasks and returns after completing one future or …","","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","A future represents an asynchronous computation obtained …","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","The type of value produced on completion.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","","","","","","","","","","","","","","","","","Creates a new WeakShared for this Shared.","Drops the future represented by the given fat pointer.","","","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Factor out a homogeneous type from an either of pairs.","Factor out a homogeneous type from an either of pairs.","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Drops this handle without canceling the underlying future.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the success value of a future before …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Wraps a TryFuture into a type that implements Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unwraps the value from this immediately ready future.","Consumes this combinator, returning the underlying futures.","Extract the value of an either over two equivalent types.","Convert an owned instance into a (conceptually owned) fat …","","Convert this future into a single element stream.","Returns true if the underlying future should no longer be …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Returns an Option containing a mutable reference to the …","Returns an Option containing a mutable reference to the …","Returns Some containing a reference to this Shared’s …","Creates a future which never resolves, representing a …","Attempt to resolve the future to a final value, registering","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","","","","","","","","","","","","","","A convenience for calling Future::poll on Unpin future …","","","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","","","","","","","","","","Gets the number of strong pointers to this allocation.","Attempt to take the output of a MaybeDone without driving …","Attempt to take the output of a TryMaybeDone without …","Creates a new Fuse-wrapped future which is already …","Chain on a computation for when a future finished, passing …","","","","","","","","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","Attempts to upgrade this WeakShared into a Shared.","Gets the number of weak pointers to this allocation.","","","","","","","","","A socket address could not be bound because the address is …","A nonexistent interface was requested or the requested …","A simple wrapper type which allows types which implement …","An entity already exists, often a file.","Program argument list too long.","Read bytes asynchronously.","An extension trait which adds utility methods to …","Read bytes asynchronously.","An extension trait which adds utility methods to AsyncRead …","Seek bytes asynchronously.","An extension trait which adds utility methods to AsyncSeek …","Write bytes asynchronously.","An extension trait which adds utility methods to AsyncWrite…","The operation failed because a pipe was closed.","The BufReader struct adds buffering to any reader.","Wraps a writer and buffers its output.","Reader for the chain method.","Future for the close method.","The connection was aborted (terminated) by the remote …","The connection was refused by the remote server.","The connection was reset by the remote server.","Future for the copy() function.","Future for the copy_buf() function.","Future for the [copy_buf()] function.","Cross-device or cross-filesystem (hard) link or rename.","Sets the offset to the current position plus the specified …","A Cursor wraps an in-memory buffer and provides it with a …","Deadlock (avoided).","A non-empty directory was specified where an empty …","Reader for the empty() function.","Sets the offset to the size of this object plus the …","The error type for I/O operations of the Read, Write, Seek…","A list specifying general categories of I/O error.","Executable file is busy.","File larger than allowed or supported.","Loop in the filesystem or IO subsystem; often, too many …","Filesystem quota was exceeded.","Future for the fill_buf method.","Future for the flush method.","The remote host is not reachable.","This operation was interrupted.","Sink for the into_sink method.","Data not valid for the operation were encountered.","A filename was invalid.","A parameter was incorrect.","A buffer type used with Write::write_vectored.","A buffer type used with Read::read_vectored.","The filesystem object is, unexpectedly, a directory.","Wrap a writer, like BufWriter does, but prioritizes …","Stream for the lines method.","The system’s networking is down.","The network containing the remote host is not reachable.","A filesystem object is, unexpectedly, not a directory.","The network operation failed because it was not connected …","An entity was not found, often a file.","Seek on unseekable file.","A custom error that does not fall under any other I/O …","An operation could not be completed, because it failed to …","The operation lacked the necessary privileges to complete.","Future for the read method.","Future for the read_exact method.","The readable half of an object returned from …","Future for the read_line method.","The filesystem or storage medium is read-only, but a write …","Future for the read_to_end method.","Future for the read_to_string method.","Future for the read_until method.","Future for the read_vectored method.","Reader for the repeat() function.","Resource is busy.","A specialized Result type for I/O operations.","Error indicating a ReadHalf<T> and WriteHalf<T> were not …","Future for the BufReader::seek_relative method.","Future for the seek method.","Enumeration of possible methods to seek within an I/O …","Writer for the sink() function.","Stale network file handle.","Sets the offset to the provided number of bytes.","The underlying storage (typically, a filesystem) is full.","Reader for the take method.","The I/O operation’s timeout expired, causing it to be …","Too many (hard) links to the same filesystem object.","An error returned when an operation could not be completed …","This operation is unsupported on this platform.","A owned window around an underlying buffer.","The operation needs to block to complete, but the blocking …","Future for the write method.","Future for the write_all method.","The writable half of an object returned from …","Future for the write_vectored method.","An error returned when an operation could not be completed …","Advance the internal cursor of the slice.","Advance the internal cursor of the slice.","Advance a slice of slices.","Advance a slice of slices.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a reference to the internally buffered data.","Returns a reference to the internally buffered data.","Returns a reference to buf_writer’s internally buffered …","","Creates an adaptor which will chain this stream with …","","","","","","","","","","","Creates a future which will entirely close this AsyncWrite.","","","Tells this buffer that amt bytes have been consumed from …","","","","","","","","","A convenience for calling AsyncBufRead::consume on Unpin …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","","","","","","Attempt to downgrade the inner error to E if any.","Constructs a new handle to an empty reader.","Returns the end index of this window into the underlying …","","","","Creates a future which will wait for a non-empty buffer to …","","Creates a future which will entirely flush this AsyncWrite.","","","","","","","","","","Shows a human-readable description of the ErrorKind.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts a [alloc::ffi::NulError] into a Error.","","Returns the argument unchanged.","Converts an ErrorKind into an Error.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Creates a new instance of an Error from a particular OS …","Returns a mutable reference to the inner error wrapped by …","Returns a mutable reference to the contained IO object.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Gets mutable references to the underlying readers in this …","Gets a mutable reference to the underlying value in this …","Acquires a mutable reference to the underlying sink or …","Gets a mutable reference to the underlying buffer inside …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Gets pinned mutable references to the underlying readers …","Acquires a pinned mutable reference to the underlying sink …","Returns a reference to the inner error wrapped by this …","Returns a reference to the contained IO object.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Gets references to the underlying readers in this Chain.","Gets a reference to the underlying value in this cursor.","Acquires a reference to the underlying sink or stream that …","Gets a shared reference to the underlying buffer inside of …","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Consumes the Error, returning its inner error (if any).","Consumes self and returns the contained IO object.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes the Chain, returning the wrapped readers.","Consumes this cursor, returning the underlying value.","Consumes this combinator, returning the underlying sink or …","Consumes this Window, returning the underlying buffer.","Allow using an AsyncWrite as a Sink<Item: AsRef<[u8]>>.","Returns the corresponding ErrorKind for this error.","Returns an error representing the last OS error which …","Returns the remaining number of bytes that can be read …","Returns a stream over the lines of this reader. This …","Creates a new I/O error from a known kind of error as well …","Creates a new IoSliceMut wrapping a byte slice.","Creates a new IoSlice wrapping a byte slice.","Creates a new AllowStdIo from an existing IO object.","Creates a new BufReader with a default buffer capacity. …","Creates a new BufWriter with a default buffer capacity. …","Create a new LineWriter with default buffer capacity. The …","Creates a new cursor wrapping the provided underlying …","Creates a new window around the buffer t defaulting to the …","Creates a new I/O error from an arbitrary error payload.","","","","","","","","","","","","","","","","","","","","","Attempt to close the object.","","","","","Forward to buf_writer ’s BufWriter::poll_close()","","","","","","","Attempt to return the contents of the internal buffer, …","","","","","","","","Attempt to flush the object, ensuring that any buffered …","","","","","Forward to buf_writer ’s BufWriter::poll_flush()","","","","","","","","Attempt to read from the AsyncRead into buf.","","","","","","","","","","Attempt to read from the AsyncRead into bufs using vectored","","","","","","","","","Attempt to seek to an offset, in bytes, in a stream.","","Seek to an offset, in bytes, in the underlying reader.","Seek to the offset, in bytes, in the underlying writer.","","Attempts to seek relative to the current position. If the …","Attempt to write bytes from buf into the object.","","","","","","","","","","","Attempt to write bytes from bufs into the object using …","","","","","","","","","","","Returns the current position of this cursor.","","","Returns the OS error that this error represents (if any).","Tries to read some bytes directly into the given buf in …","","Creates a future which will read exactly enough bytes to …","","Creates a future which will read all the bytes associated …","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes associated …","Creates a future which will read from the AsyncRead into …","","Creates an instance of a reader that infinitely repeats …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Creates a future which will seek an IO object, and then …","","Seeks relative to the current position. If the new …","Changes the range of this window to the range specified.","Sets the number of bytes that can be read before this …","Sets the position of this cursor.","Creates an instance of a writer which will successfully …","","Helper method for splitting this read/write object into …","Returns the starting index of this window into the …","","Creates a future which will return the current seek …","Creates an AsyncRead adapter which will read at most limit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new BufReader with the specified buffer capacity.","Creates a new BufWriter with the specified buffer capacity.","Creates a new LineWriter with the specified buffer …","Creates a future which will write bytes from buf into the …","","Write data into this object.","","","Creates a future which will write bytes from bufs into the …","","","","","An RAII guard returned by the MutexGuard::map and …","A futures-aware mutex.","An RAII guard returned by the lock and try_lock methods. …","A future which resolves when the target mutex has been …","An RAII guard returned by the lock_owned and try_lock_owned…","A future which resolves when the target mutex has been …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes this mutex, returning the underlying data.","","","Acquire the lock asynchronously.","Acquire the lock asynchronously.","Returns a locked view over a portion of the locked data.","Returns a locked view over a portion of the locked data.","Creates a new futures-aware mutex.","","","","","","","","","","","","","","","Attempt to acquire the lock immediately.","Attempt to acquire the lock immediately.","","","","","","","A type with no possible values.","Read bytes asynchronously.","Read bytes asynchronously.","Seek bytes asynchronously.","Write bytes asynchronously.","The type of value produced by the sink when an error …","The type of failures yielded by this future","The type of failures yielded by this future","A future represents an asynchronous computation obtained …","Values yielded by the stream.","The type of successful values yielded by this future","The type of successful values yielded by this future","The type of value produced on completion.","A Sink is a value into which other values can be sent, …","A stream of values produced asynchronously.","A convenience for futures that return Result values that …","A convenience for streams that return Result values that …","","","","","","","","","","Tells this buffer that amt bytes have been consumed from …","Asynchronous values.","Attempt to resolve the future to a final value, registering","Flush any remaining output and close this sink, if …","Attempt to close the object.","Attempt to return the contents of the internal buffer, …","Flush any remaining output from this sink.","Attempt to flush the object, ensuring that any buffered …","Attempt to pull out the next value of this stream, …","Attempt to read from the AsyncRead into buf.","Attempt to read from the AsyncRead into bufs using vectored","Attempts to prepare the Sink to receive a value.","Attempt to seek to an offset, in bytes, in a stream.","Attempt to write bytes from buf into the object.","Attempt to write bytes from bufs into the object using …","Asynchronous sinks.","Returns the bounds on the remaining length of the stream.","Begin the process of sending a value to the sink. Each …","Asynchronous streams.","Poll this TryFuture as if it were a Future.","Poll this TryStream as if it were a Stream.","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","A future represents an asynchronous computation obtained …","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","The type of value produced on completion.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","Drops the future represented by the given fat pointer.","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the success value of a future before …","Wraps a TryFuture into a type that implements Future.","Convert an owned instance into a (conceptually owned) fat …","Convert this future into a single element stream.","Returns true if the underlying future should no longer be …","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Creates a future which never resolves, representing a …","Attempt to resolve the future to a final value, registering","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","A convenience for calling Future::poll on Unpin future …","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","Chain on a computation for when a future finished, passing …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","","","","","","","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","Adds a fixed-size buffer to the current sink.","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","Wrap this sink in an Either sink, making it the left-hand …","Flush any remaining output and close this sink, if …","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","A convenience method for calling Sink::poll_flush on Unpin …","Attempts to prepare the Sink to receive a value.","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","Begin the process of sending a value to the sink. Each …","A convenience method for calling Sink::start_send on Unpin …","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","Wraps the current stream in a new stream which converts …","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","Wraps a TryStream into a type that implements Stream","Returns true if the stream should no longer be polled.","Converts an Iterator into a Stream which is always ready …","Wrap this stream in an Either stream, making it the …","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Creates a future that resolves to the next item in the …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","A convenience method for calling Stream::poll_next on Unpin","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","Splits this Stream + Sink object into separate Sink and …","Creates a new stream of at most n items of the underlying …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Creates a future that attempts to resolve the next item in …","Poll this TryStream as if it were a Stream.","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","","","","","","","","","","","","","","","","","","","","","","","","","","","Adds a fixed-size buffer to the current sink.","","","","","","","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get a mutable reference to the inner sinks.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Get a pinned mutable reference to the inner sinks.","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Get a shared reference to the inner sinks.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Consumes this combinator, returning the underlying sinks.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","","","","","Wrap this sink in an Either sink, making it the left-hand …","","","","","","Flush any remaining output and close this sink, if …","","","","","","","","","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","","","","","","","","","A convenience method for calling Sink::poll_flush on Unpin …","","","","","","Attempts to prepare the Sink to receive a value.","","","","","","","","","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","","","","","","Begin the process of sending a value to the sink. Each …","","","","","","","","","A convenience method for calling Sink::start_send on Unpin …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Abort the Abortable stream/future associated with this …","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","Clears the set, removing all futures.","Clears the set, removing all streams.","","","","","","","","","","","","","","","","","","","","","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","","","","","","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","","","","Wraps the current stream in a new stream which converts …","","","","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying stream that …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying streams …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying stream that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying streams that this …","","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","","","","","","","","","","","","","","","","","","","","","","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying stream.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying streams.","","","","","","","Wraps a TryStream into a type that implements Stream","Checks whether the task has been aborted. Note that all …","Returns whether the underlying stream has finished or not.","Returns true if the queue contains no futures","Returns true if the set contains no futures.","Returns true if the set contains no streams","Whether the stream was stopped yet by the stopping future …","Returns true if the stream should no longer be polled.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts an Iterator into a Stream which is always ready …","Returns an iterator that allows inspecting each future in …","Returns an iterator that allows inspecting each stream in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows modifying each stream in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows inspecting each future in …","Wrap this stream in an Either stream, making it the …","Returns the number of futures contained in the queue.","Returns the number of futures contained in the set.","Returns the number of streams contained in the set.","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Constructs a new, empty FuturesOrdered","Constructs a new, empty FuturesUnordered.","Constructs a new, empty SelectAll","Creates a new Abortable future/stream using an existing …","Creates an (AbortHandle, AbortRegistration) pair which can …","Creates a future that resolves to the next item in the …","Creates a future which will consume and return the next …","Creates a future which will consume and return the next …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Produces a future which retrieves a reference to the next …","Produces a future which retrieves a mutable reference to …","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling Stream::poll_next on Unpin","Peek retrieves a reference to the next item in the stream.","Peek retrieves a mutable reference to the next item in the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Push a future into the queue.","Push a future into the set.","Push a stream into the set.","Pushes a future to the back of the queue.","Pushes a future to the front of the queue.","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","","","Splits this Stream + Sink object into separate Sink and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream of at most n items of the underlying …","Extract the stopping future out of the combinator. The …","Once the stopping future is resolved, this method can be …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","","","","","","","","","","","","","","Toggle the value and return the old one.","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a future that attempts to resolve the next item in …","","","","","","","","","","","","Poll this TryStream as if it were a Stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","Convert a list of streams into a Stream of results from …","","","","","","","","","","","","","A way of waking up a specific task.","A synchronization primitive for task wakeup.","The context of an asynchronous task.","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","Extension trait for LocalSpawn.","Represents that a value is not ready yet.","Indicates whether a value is available or if the current …","A RawWaker allows the implementor of a task executor to …","A virtual function pointer table (vtable) that specifies …","Represents that a value is immediately ready.","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","Extension trait for Spawn.","A custom implementation of a future trait object for …","A Waker is a handle for waking up a task by notifying its …","A Waker that is only valid for a given lifetime.","Get a reference to the underlying RawWaker.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the data pointer used to create this RawWaker.","","","Drops the future represented by the given fat pointer.","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Use a Wake-able type as a Waker.","Moves the value into a Poll::Ready to make a Poll<T>.","Returns the argument unchanged.","","Returns the argument unchanged.","Use a Wake-able type as a RawWaker.","Returns the argument unchanged.","","","Creates a new Waker from RawWaker.","","","","Create a new Context from a &Waker.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Returns true if the poll is a Pending value.","Returns true if the poll is a Poll::Ready value.","Check whether spawning failed to the executor being shut …","Maps a Poll<T> to Poll<U> by applying a function to a …","Maps a Poll::Ready<Option<Result<T, E>>> to …","Maps a Poll::Ready<Result<T, E>> to …","Maps a Poll<Result<T, E>> to Poll<Result<U, E>> by …","Maps a Poll<Option<Result<T, E>>> to …","Create an AtomicWaker.","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Creates a new RawWaker from the provided data pointer and …","Creates a new RawWakerVTable from the provided clone, wake,","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","","Extracts the successful type of a Poll<T>.","Registers the waker to be notified on calls to wake.","Spawning failed because the executor has been shut down.","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Returns the last Waker passed to register, so that the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the vtable pointer used to create this RawWaker.","Indicates that the associated task is ready to make …","Calls wake on the last Waker passed to register.","Wake up the task associated with this Waker.","Indicates that the associated task is ready to make …","Wake up the task associated with this Waker without …","Creates a Waker from an Arc<impl ArcWake>.","Returns a reference to the Waker for the current task.","Creates a reference to a Waker from a reference to …","Returns true if this Waker and another Waker would awake …",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,2,3,6,7,4,5,9,2,3,6,7,4,5,0,2,3,4,5,2,3,4,5,6,7,2,3,2,3,6,7,4,5,9,9,2,3,6,7,4,4,5,5,9,2,3,6,7,4,5,2,3,9,2,3,6,7,4,5,5,5,2,3,2,3,4,5,4,5,6,7,2,3,3,2,3,3,6,7,2,2,3,3,3,9,4,5,2,3,2,2,3,3,3,2,3,4,5,9,4,5,9,2,3,6,7,4,5,9,2,3,6,7,4,5,6,7,6,7,2,9,2,3,6,7,4,5,0,3,0,0,0,0,23,20,21,22,23,20,21,22,20,0,22,22,23,23,20,22,23,20,21,22,22,23,20,21,22,23,20,21,22,23,21,20,20,23,23,21,20,22,20,22,22,23,20,21,22,23,20,21,22,23,23,23,20,21,22,0,0,0,0,0,0,0,27,28,26,25,24,27,28,26,25,24,25,25,26,24,24,27,0,27,28,28,26,25,24,27,28,26,25,24,27,28,26,25,24,24,24,26,24,28,26,26,26,24,25,25,26,25,25,25,28,27,28,26,25,24,27,28,26,25,24,26,27,28,26,25,24,0,0,0,0,0,0,0,73,74,0,0,267,0,0,0,0,0,0,73,74,0,0,73,74,0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,267,0,0,0,33,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,269,269,269,37,38,39,40,41,42,43,37,38,39,40,41,42,43,43,40,37,270,37,33,0,268,43,43,269,268,269,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,59,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,49,50,80,86,269,269,268,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,268,48,49,50,51,45,52,54,55,56,57,58,36,59,60,61,62,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,42,80,43,270,33,269,271,37,51,45,47,52,53,54,55,56,57,58,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,43,43,0,0,0,0,0,0,269,269,268,269,268,268,0,269,269,0,268,268,73,74,37,0,33,37,48,49,50,51,45,52,54,55,56,57,58,36,59,60,61,62,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,47,63,46,43,43,43,47,63,46,43,43,0,0,47,53,63,46,41,43,43,43,47,63,46,43,43,269,43,43,0,269,269,0,0,0,269,47,53,63,46,43,47,63,46,43,37,73,74,51,269,37,38,39,40,41,42,43,268,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,0,0,0,0,0,0,267,37,48,50,51,45,52,54,55,56,57,58,36,59,61,62,32,64,44,65,66,67,68,69,70,71,72,39,74,42,81,82,83,84,85,86,43,47,53,63,46,43,268,0,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,269,268,38,37,272,273,274,275,276,277,113,113,99,99,0,99,99,0,0,0,0,0,0,0,0,99,0,0,0,0,99,99,99,0,0,0,99,89,0,99,99,0,89,0,0,99,99,99,99,0,0,99,99,0,99,99,99,0,0,99,0,0,99,99,99,99,99,99,99,99,99,0,0,0,0,99,0,0,0,0,0,99,0,0,0,0,0,0,99,89,99,0,99,99,99,99,0,99,0,0,0,0,99,91,92,91,92,93,93,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,94,95,96,88,278,99,92,89,100,101,99,92,89,100,101,279,99,100,280,104,100,100,94,95,98,101,105,281,0,0,0,101,91,92,91,88,88,0,93,99,89,100,281,100,279,100,88,88,91,104,111,112,113,113,99,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,88,88,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,88,100,94,95,98,101,105,93,94,95,98,105,88,100,94,95,96,98,101,105,93,99,100,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,88,100,94,95,98,101,105,93,279,88,88,105,281,88,91,92,100,94,95,96,101,93,88,99,100,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,282,112,100,94,95,96,101,101,101,101,116,127,280,104,100,94,95,98,101,105,282,112,100,94,95,96,101,101,101,101,116,127,117,283,104,111,100,94,95,98,101,126,105,283,111,100,94,95,98,101,126,116,284,100,94,95,101,94,282,112,100,94,95,96,101,101,101,101,127,282,112,100,94,95,96,101,101,101,101,127,101,88,113,88,278,100,278,100,281,278,100,278,100,281,278,100,0,126,127,285,100,94,93,105,101,0,88,278,93,116,285,278,99,92,89,100,101,88,113,99,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,117,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,94,95,96,279,100,279,100,100,279,100,286,287,288,0,0,0,0,0,0,137,141,138,142,139,140,137,141,138,142,139,140,137,138,139,140,138,139,140,141,138,142,139,140,137,141,138,142,139,140,137,137,137,141,138,142,139,140,137,137,141,138,142,139,140,141,142,137,141,142,137,137,139,140,137,141,142,137,141,138,142,139,140,137,141,138,142,139,140,137,137,137,141,138,142,139,140,0,0,0,0,0,289,267,290,0,161,267,290,33,0,0,0,0,0,0,0,0,0,0,0,0,0,280,0,33,289,282,280,289,282,161,283,283,289,284,282,282,0,161,289,0,267,290,0,0,0,0,0,0,0,73,74,0,0,267,0,0,0,0,0,0,73,74,0,0,73,74,0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,267,0,0,0,33,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,268,269,269,269,270,0,268,269,268,269,269,269,268,268,268,270,269,271,0,0,0,0,0,0,269,269,268,269,268,268,0,269,269,0,268,268,0,33,0,0,269,0,269,269,0,0,0,269,269,268,268,0,0,0,0,0,0,267,268,0,269,268,272,273,274,275,276,277,0,0,0,289,0,0,0,0,0,0,0,0,0,0,0,0,291,291,0,291,291,291,291,289,291,289,291,289,291,291,291,291,291,291,289,291,0,291,291,242,241,242,241,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,0,237,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,292,293,292,292,292,292,292,292,292,292,292,292,292,292,292,0,292,293,292,292,292,292,292,292,292,292,292,292,292,0,292,293,293,293,292,293,294,0,292,292,293,293,292,0,293,292,0,0,0,161,292,292,0,0,292,292,0,0,0,292,0,161,292,292,292,292,292,292,292,293,293,293,293,293,293,293,293,293,293,293,293,290,293,293,293,0,0,292,292,0,0,0,0,0,0,0,0,0,0,0,0,0,0,289,0,0,0,0,0,0,0,0,0,0,0,0,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,291,147,154,156,147,154,156,291,0,291,291,291,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,148,153,154,156,157,145,148,153,154,156,157,145,148,153,154,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,149,150,151,148,153,154,156,157,145,153,154,157,145,291,152,146,149,150,151,289,147,148,153,154,155,156,157,145,291,289,147,148,153,154,155,156,157,145,291,153,154,156,157,145,289,147,148,153,154,155,156,157,145,291,291,291,291,291,291,153,154,156,157,145,289,147,148,153,154,155,156,157,145,291,147,154,156,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,149,150,151,153,154,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,0,291,291,242,241,242,241,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,0,237,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,235,0,292,293,292,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,292,292,292,292,292,292,292,292,236,208,192,204,205,171,200,202,237,238,235,239,192,204,205,171,200,202,237,238,235,239,292,292,189,292,292,240,236,237,208,236,0,292,241,237,239,293,240,236,208,292,292,292,292,292,292,242,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,239,292,292,292,292,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,240,236,208,292,0,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,237,292,293,293,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,293,292,167,233,168,181,184,182,190,196,209,243,244,245,246,183,228,226,220,221,225,227,238,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,236,236,236,208,208,208,293,238,185,240,236,208,214,294,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,183,164,159,173,188,187,191,195,194,198,228,222,223,224,220,219,225,229,230,227,204,205,171,197,200,202,207,210,232,208,0,236,208,236,208,236,236,292,240,236,208,292,293,293,240,236,208,238,235,292,199,199,0,293,199,199,292,0,167,233,168,181,184,182,190,196,209,243,244,245,246,183,228,226,220,221,225,227,238,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,189,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,0,0,161,201,240,236,165,170,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,247,164,159,173,188,187,191,195,194,198,222,223,224,219,231,229,230,217,218,192,204,205,171,197,200,202,207,210,232,208,238,292,199,199,189,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,242,241,239,240,236,208,240,240,292,0,0,247,248,292,292,0,0,0,292,0,161,240,236,165,170,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,219,229,230,192,204,205,171,197,200,202,292,292,236,236,292,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,292,214,214,292,292,292,192,204,205,171,200,202,237,238,235,239,242,241,239,237,293,293,293,293,293,293,293,293,293,293,293,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,293,168,181,184,209,228,226,220,221,225,227,238,290,201,240,236,165,170,174,175,179,185,186,193,176,199,211,212,213,215,214,216,206,162,163,247,164,159,173,188,187,191,195,194,198,222,223,224,219,231,229,230,217,218,192,204,205,171,197,200,207,210,232,208,238,293,293,293,0,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,0,292,292,0,0,0,0,0,0,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,0,0,0,0,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,0,251,253,257,251,253,257,251,253,257,251,253,257,0,0,0,0,0,0,0,15,0,0,0,15,0,0,0,0,0,0,258,30,262,29,31,263,14,258,15,259,261,30,262,29,31,263,14,258,15,259,261,15,15,258,15,261,258,15,261,15,259,262,263,270,29,258,15,259,261,30,30,262,29,31,263,14,258,15,259,261,30,262,29,29,29,29,29,29,31,31,31,31,31,263,14,258,258,15,15,15,259,259,261,15,15,258,15,15,15,14,15,30,262,29,31,263,14,258,15,259,261,29,31,29,270,15,15,30,15,15,15,15,15,262,29,31,263,259,261,263,0,0,15,29,31,30,15,262,30,295,296,297,296,298,295,298,297,262,258,15,261,30,30,262,29,31,263,14,258,15,259,261,30,262,29,31,263,14,258,15,259,261,29,31,30,262,29,31,263,14,258,15,259,261,259,299,262,258,299,258,0,14,0,258,300],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[5,5],[[]],[[]],[[]],[[]],[6],[7],[2],[3],[2],[3],[6],[7],[[4,4],8],[[5,5],8],[[9,10],[[12,[11]]]],[[9,10],[[12,[11]]]],[[2,10],[[12,[11]]]],[[3,10],[[12,[11]]]],[[6,10],[[12,[11]]]],[[7,10],[[12,[11]]]],[[4,10],[[12,[11]]]],[[4,10],[[12,[11]]]],[[5,10],[[12,[11]]]],[[5,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2],[3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5],[5,4],[2,8],[3,8],[[2,6],8],[[3,7],8],[4,8],[5,8],[4,8],[5,8],[6,8],[7,8],[[[13,[2]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[2]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[6]],14],[[15,[16]]]],[[[13,[7]],14],[[15,[16]]]],[[[13,[2]],14],[[15,[12]]]],[[2,14],[[15,[[12,[4]]]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[3,14],[[15,[[12,[4]]]]]],[17],[17],[17],[[2,2],8],[[3,3],8],[[[13,[2]]],12],[2,[[12,[4]]]],[[[13,[3]]],12],[3,[[12,[4]]]],[[[13,[3]]],12],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[6,[[12,[16,9]]]],[7,[[12,[16,9]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[2,[[12,[5]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[]],[3,[[12,[5]]]],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[20,21],[[]],[22,22],[[]],[23],[23],[20],[[22,22],8],[[23,10],[[12,[11]]]],[[20,10],[[12,[11]]]],[[21,10],[[12,[11]]]],[[22,10],[[12,[11]]]],[[22,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[20,8],[[20,23],8],[23,8],[[[13,[23]],14],[[15,[[12,[22]]]]]],[[[13,[21]],14],15],[[20,14],15],[17],[20,12],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[23,[[12,[16,22]]]],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,[[]],[[],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,25],[[]],[[],26],[24],[24],[27],[[],[[12,[27,28]]]],[[27,10],[[12,[11]]]],[[28,10],[[12,[11]]]],[[28,10],[[12,[11]]]],[[26,10],[[12,[11]]]],[[25,10],[[12,[11]]]],[[24,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[24],[[]],[[],26],[24,16],[17],[26],[26],[26],[24],[[25,29],[[12,[30]]]],[[25,31],[[12,[30]]]],[26,25],[25,[[12,[30]]]],[25,[[12,[30]]]],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[26,8],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],32],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[13,[[35,[33,34]]]]]],[[],[[13,[[35,[33,34]]]]]],[[],36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[13,[43]],1]],[[],40],[37,[[16,[38]]]],[33],[37],[33],[[],[[42,[12]]]],[[],44],[43],[43],[[],45],[[],46],[[],47],[[37,10],[[12,[11]]]],[[38,10],[[12,[11]]]],[[48,10],[[12,[11]]]],[[49,10],[[12,[11]]]],[[50,10],[[12,[11]]]],[[51,10],[[12,[11]]]],[[45,10],[[12,[11]]]],[[47,10],[[12,[11]]]],[[52,10],[[12,[11]]]],[[53,10],[[12,[11]]]],[[54,10],[[12,[11]]]],[[55,10],[[12,[11]]]],[[56,10],[[12,[11]]]],[[57,10],[[12,[11]]]],[[58,10],[[12,[11]]]],[[36,10],[[12,[11]]]],[[59,10],[[12,[11]]]],[[60,10],[[12,[11]]]],[[61,10],[[12,[11]]]],[[62,10],[[12,[11]]]],[[63,10],[[12,[11]]]],[[46,10],[[12,[11]]]],[[32,10],[[12,[11]]]],[[64,10],[[12,[11]]]],[[44,10],[[12,[11]]]],[[65,10],[[12,[11]]]],[[66,10],[[12,[11]]]],[[67,10],[[12,[11]]]],[[68,10],[[12,[11]]]],[[69,10],[[12,[11]]]],[[70,10],[[12,[11]]]],[[71,10],[[12,[11]]]],[[72,10],[[12,[11]]]],[[39,10],[[12,[11]]]],[[73,10],[[12,[11]]]],[[74,10],[[12,[11]]]],[[40,10],[[12,[11]]]],[[41,10],[[12,[11]]]],[[42,10],[[12,[11]]]],[[75,10],[[12,[11]]]],[[76,10],[[12,[11]]]],[[77,10],[[12,[11]]]],[[78,10],[[12,[11]]]],[[79,10],[[12,[11]]]],[[80,10],[[12,[11]]]],[[81,10],[[12,[11]]]],[[82,10],[[12,[11]]]],[[83,10],[[12,[11]]]],[[84,10],[[12,[11]]]],[[85,10],[[12,[11]]]],[[86,10],[[12,[11]]]],[[43,10],[[12,[11]]]],[59],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,40],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],49],[[],50],[[],80],[[],86],[[],51],[[],56],[[],67],[[],66],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],61],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[42],[80,[[87,[34]]]],[43],[[],33],[33,33],[[],53],[[],8],[37,8],[51,8],[45,8],[47,8],[52,8],[53,8],[54,8],[55,8],[56,8],[57,8],[58,8],[61,8],[62,8],[63,8],[46,8],[32,8],[64,8],[44,8],[65,8],[66,8],[67,8],[68,8],[69,8],[70,8],[71,8],[72,8],[39,8],[73,8],[74,8],[40,8],[41,8],[42,8],[75,8],[76,8],[77,8],[78,8],[79,8],[43,8],[43,8],[[],75],[[],76],[[],77],[[],78],[[],49],[[],72],[[],43],[[],52],[[],69],[[],54],[[],68],[[],70],[[],73],[[],57],[[],16],[[],[[42,[12]]]],[[],65],[[],64],[[[13,[73]]],16],[[[13,[74]]],16],[37,16],[[],39],[[13,14],15],[[[13,[37]],14],15],[[[13,[48]],14],15],[[[13,[49]],14],15],[[[13,[50]],14],15],[[[13,[51]],14],15],[[[13,[45]],14],15],[[[13,[52]],14],15],[[[13,[54]],14],15],[[[13,[55]],14],15],[[[13,[56]],14],15],[[[13,[57]],14],15],[[[13,[58]],14],15],[[[13,[36]],14],15],[[[13,[59]],14],15],[[[13,[60]],14],15],[[[13,[61]],14],15],[[[13,[62]],14],15],[[[13,[32]],14],15],[[[13,[64]],14],15],[[[13,[44]],14],15],[[[13,[65]],14],15],[[[13,[66]],14],15],[[[13,[67]],14],15],[[[13,[68]],14],15],[[[13,[69]],14],15],[[[13,[70]],14],15],[[[13,[71]],14],15],[[[13,[72]],14],15],[[[13,[39]],14],15],[[[13,[73]],14],15],[[[13,[74]],14],15],[[[13,[40]],14],15],[[[13,[41]],14],[[15,[16]]]],[[[13,[42]],14],15],[[[13,[75]],14],15],[[[13,[76]],14],15],[[[13,[77]],14],15],[[[13,[78]],14],15],[[[13,[79]],14],15],[[[13,[80]],14],15],[[[13,[81]],14],15],[[[13,[82]],14],15],[[[13,[83]],14],15],[[[13,[84]],14],15],[[[13,[85]],14],15],[[[13,[86]],14],15],[[[13,[43]],14],15],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[43]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[43]],14],[[15,[12]]]],[[],48],[[],41],[[[13,[47]],14],[[15,[16]]]],[[[13,[53]],14],[[15,[16]]]],[[[13,[63]],14],[[15,[16]]]],[[[13,[46]],14],[[15,[16]]]],[[[13,[41]],14],[[15,[16]]]],[[[13,[43]],14],[[15,[16]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[12]]]],[[[13,[43]],14,89],[[15,[[12,[90,88]]]]]],[14,15],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[],42],[[]],[[],43],[[],79],[[],80],[[],86],[[],37],[47],[53],[63],[46],[43],[[[13,[47]]],12],[[[13,[63]]],12],[[[13,[46]]],12],[[[13,[43]]],12],[37,[[16,[1]]]],[[[13,[73]]],16],[[[13,[74]]],16],[[],51],[[],55],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],62],[[],63],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],81],[[],82],[[],83],[[],84],[[],50],[[],74],[[13,14],[[15,[12]]]],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[12]]]],[[],85],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],58],[[],71],[38,[[16,[37]]]],[37,[[16,[1]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[91,1]],[[92,1]],[1],[1],[93],[93],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[94],[95],[96],[88,[[16,[97]]]],[[],98],[99,99],[92,92],[89,89],[100,100],[101,101],[[]],[[]],[[]],[[]],[[]],[[],102],[[99,99],103],[[100,100],103],[[13,1]],[[[13,[104]],1]],[[100,1]],[[[13,[100]],1]],[[[13,[94]],1]],[[[13,[95]],1]],[[[13,[98]],1]],[[[13,[101]],1]],[[[13,[105]],1]],[1],[[],106],[[],107],[[]],[[],101],[91],[92],[91],[88,108],[88,[[12,[[35,[34]],88]]]],[[],104],[93,1],[[99,99],8],[[89,89],8],[[100,100],8],[[],109],[100,[[12,[88]]]],[[],110],[100,[[12,[88]]]],[[88,10],[[12,[11]]]],[[88,10],[[12,[11]]]],[[91,10],[[12,[11]]]],[[104,10],[[12,[11]]]],[[111,10],[[12,[11]]]],[[112,10],[[12,[11]]]],[[113,10],[[12,[11]]]],[[113,10],[[12,[11]]]],[[99,10],[[12,[11]]]],[[99,10],[[12,[11]]]],[[92,10],[[12,[11]]]],[[89,10],[[12,[11]]]],[[100,10],[[12,[11]]]],[[94,10],[[12,[11]]]],[[114,10],[[12,[11]]]],[[95,10],[[12,[11]]]],[[96,10],[[12,[11]]]],[[98,10],[[12,[11]]]],[[102,10],[[12,[11]]]],[[106,10],[[12,[11]]]],[[107,10],[[12,[11]]]],[[115,10],[[12,[11]]]],[[101,10],[[12,[11]]]],[[109,10],[[12,[11]]]],[[110,10],[[12,[11]]]],[[116,10],[[12,[11]]]],[[117,10],[[12,[11]]]],[[118,10],[[12,[11]]]],[[119,10],[[12,[11]]]],[[120,10],[[12,[11]]]],[[121,10],[[12,[11]]]],[[122,10],[[12,[11]]]],[[123,10],[[12,[11]]]],[[124,10],[[12,[11]]]],[[125,10],[[12,[11]]]],[[126,10],[[12,[11]]]],[[127,10],[[12,[11]]]],[[105,10],[[12,[11]]]],[[93,10],[[12,[11]]]],[[128,10],[[12,[11]]]],[[129,10],[[12,[11]]]],[[130,10],[[12,[11]]]],[131,88],[132,88],[[]],[99,88],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[133,88],[88,[[16,[97]]]],[100],[94],[95],[98],[101],[105],[93],[[[13,[94]]],13],[[[13,[95]]],13],[[[13,[98]]]],[[[13,[105]]],13],[88,[[16,[97]]]],[100],[94],[95],[96],[98],[101],[105],[93],[99],[100],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[88,[[16,[[35,[97,34]]]]]],[100],[94],[95],[98],[101],[105],[93],[[],116],[88,99],[[],88],[105,90],[[],117],[99,88],[[],91],[[],92],[[],100],[[],94],[[],95],[[],96],[[],101],[[],93],[[],88],[[99,99],[[16,[103]]]],[[100,100],[[16,[103]]]],[[[13,[114]],14],15],[[[13,[102]],14],15],[[[13,[106]],14],15],[[[13,[107]],14],15],[[[13,[115]],14],15],[[[13,[109]],14],15],[[[13,[110]],14],15],[[[13,[118]],14],15],[[[13,[119]],14],15],[[[13,[120]],14],15],[[[13,[121]],14],15],[[[13,[122]],14],15],[[[13,[123]],14],15],[[[13,[124]],14],15],[[[13,[125]],14],15],[[[13,[128]],14],15],[[[13,[129]],14],15],[[[13,[130]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[112]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[96]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[[13,[127]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[104]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[98]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[105]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[112]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[96]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[[13,[127]],14],[[15,[[12,[88]]]]]],[[[13,[117]],14],[[15,[16]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[104]],14],[[15,[[12,[1,88]]]]]],[[[13,[111]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[98]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[126]],14],[[15,[[12,[1,88]]]]]],[[[13,[105]],14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[111]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[98]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[126]],14],[[15,[[12,[1,88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[13,14,89],[[15,[[12,[90,88]]]]]],[[[13,[100]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[94]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[95]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[101]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[94]],14,134],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[112]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[96]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[127]],14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[112]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[96]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[127]],14],[[15,[[12,[1,88]]]]]],[101,90],[17],[17],[88,[[16,[133]]]],[[],118],[100,[[12,[1,88]]]],[[],120],[100,[[12,[88]]]],[18,121],[87,122],[[100,87],[[12,[1,88]]]],[18,123],[[100,18],[[12,[1,88]]]],[[135,87],124],[[],119],[100,[[12,[1,88]]]],[135,111],[[126,127],[[12,[113]]]],[[127,126],[[12,[113]]]],[89,125],[[100,89],[[12,[90,88]]]],[[[13,[94]],134],114],[93],[[105,90]],[[101,90]],[[],112],[88,[[16,[97]]]],[[]],[93,1],[[[13,[116]]],12],[[],125],[90,105],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[1,94],[1,95],[1,96],[[],128],[100,[[12,[1,88]]]],[[],130],[100,[[12,[88]]]],[[100,136],[[12,[88]]]],[[],129],[100,[[12,[1,88]]]],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],137],[138],[139],[140],[138],[139],[140],[141],[138],[142],[139],[140],[[137,10],[[12,[11]]]],[[141,10],[[12,[11]]]],[[138,10],[[12,[11]]]],[[142,10],[[12,[11]]]],[[139,10],[[12,[11]]]],[[140,10],[[12,[11]]]],[143],[[]],[[],137],[[]],[[]],[[]],[[]],[[]],[137],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[137],[141,8],[142,8],[137,142],[[[144,[137]]],141],[139,140],[140,140],[[],137],[[[13,[141]],14],15],[[[13,[142]],14],15],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[137,[[16,[139]]]],[144,[[16,[138]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[13,1]],0,[[13,14],15],[[13,14],[[15,[12]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[12]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[16]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[12]]]],[[13,14,89],[[15,[[12,[90,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],0,[[]],[13,12],0,[[13,14],[[15,[12]]]],[[13,14],[[15,[[16,[12]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],32],[[],[[13,[[35,[33,34]]]]]],[[],[[13,[[35,[33,34]]]]]],[[],36],[33],[[],[[42,[12]]]],[[],44],[[],45],[[],46],[[],47],[[],51],[[],56],[[],67],[[],66],[[],61],[[],33],[[],53],[[],8],[[],75],[[],76],[[],77],[[],78],[[],49],[[],72],[[],43],[[],52],[[],69],[[],54],[[],68],[[],70],[[],73],[[],57],[[],16],[[],[[42,[12]]]],[[],65],[[],64],[[],39],[[13,14],15],[[],48],[[],41],[14,15],[[],42],[[]],[[],43],[[],79],[[],80],[[],86],[[],37],[[],55],[[],62],[[],63],[[],81],[[],82],[[],83],[[],84],[[],50],[[],74],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[],85],[[],58],[[],71],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,145],[[],146],[[],147],[[],148],[[],149],[[],150],[[],43],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[],43],[[],151],[[],152],[[],153],[[],154],[13,12],[[],12],[[],155],[[],156],[[],157],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],158],[[],159],[[],160],[[],[[13,[[35,[161,34]]]]]],[[],[[13,[[35,[161,34]]]]]],[1,162],[1,163],[[]],[[],164],[[],165],[1,166],[[],167],[[],168],[[],169],[[],170],[[],171],[[],172],[[],173],[[],174],[[],175],[[],176],[[[177,[[16,[1]]]]],178],[[],179],[[[177,[[16,[1]]]]],180],[[],181],[[],182],[[[177,[[16,[1]]]]],183],[[],184],[[],185],0,[[],186],[[],187],[[],188],[[],189],[[],190],[[],191],[[],8],[[],192],[[],43],[[],193],[[],194],[[],195],[[],196],[[],197],[[],198],[[],199],[[],200],[[],201],[[],202],[[13,14],[[15,[16]]]],[14,[[15,[16]]]],[1,203],[[],204],[[],205],[[],43],[[],206],[[],207],0,[[],208],[[],209],[[],210],[[]],[1,211],[[],212],[[]],[1,213],[[],214],[[],215],[[],216],[1,217],[1,218],[1,219],[[],220],[[],221],[[],222],[[],223],[[],224],[[],225],[[],226],[[[177,[[16,[1]]]]],227],[[],228],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[[16,[12]]]]]],[[],229],[[],230],[[],231],[[],232],[[],233],[[],234],0,0,0,0,0,0,0,0,0,0,[[],208],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,145],[147,147],[154,154],[156,156],[[]],[[]],[[]],[[],146],[[],147],[[],148],[[],149],[[],150],[[152,10],[[12,[11]]]],[[146,10],[[12,[11]]]],[[147,10],[[12,[11]]]],[[148,10],[[12,[11]]]],[[149,10],[[12,[11]]]],[[150,10],[[12,[11]]]],[[153,10],[[12,[11]]]],[[154,10],[[12,[11]]]],[[151,10],[[12,[11]]]],[[155,10],[[12,[11]]]],[[156,10],[[12,[11]]]],[[157,10],[[12,[11]]]],[[145,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[154],[156],[157],[145],[[[13,[148]]]],[[[13,[153]]],13],[[[13,[154]]],13],[[[13,[156]]],13],[[[13,[157]]],13],[[[13,[145]]],13],[148],[153],[154],[156],[157],[145],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[154],[156],[157],[145],[153,8],[154,8],[157,8],[145,8],[[],43],[[[13,[152]],14],15],[[[13,[146]],14],15],[[[13,[149]],14],15],[[[13,[150]],14],15],[[[13,[151]],14],15],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[[13,[153]],14],[[15,[16]]]],[[[13,[154]],14],[[15,[16]]]],[[[13,[156]],14],[[15,[16]]]],[[[13,[157]],14],[[15,[16]]]],[[[13,[145]],14],[[15,[16]]]],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[],43],[[],151],[[],152],[[],153],[[],154],[153],[154],[156],[157],[145],[13,12],[[[13,[147]]],12],[[[13,[148]]],12],[[[13,[153]]],12],[[[13,[154]]],12],[[[13,[155]]],12],[[[13,[156]]],12],[[[13,[157]]],12],[[[13,[145]]],12],[[],12],[[]],[[]],[[]],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],155],[[],156],[[],157],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[235],[[]],[[],158],[[],159],[[],160],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[13,[[35,[161,34]]]]]],[[],[[13,[[35,[161,34]]]]]],[1,162],[1,163],[[]],[[],164],[[],165],[1,166],[236],[208],[192,192],[204,204],[205,205],[171,171],[200,200],[202,202],[237,237],[238,238],[235,235],[239,239],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],167],[[],168],[[[13,[189]],1]],[[],169],[[],170],[[],240],[[],236],[[],237],[[],208],[236],[[],171],[[],172],[[241,241],8],[[237,237],8],[[239,239],8],[[],173],[240],[236],[208],[[],174],[[],175],[[],176],[[[177,[[16,[1]]]]],178],[[],179],[[[177,[[16,[1]]]]],180],[[242,10],[[12,[11]]]],[[242,10],[[12,[11]]]],[[201,10],[[12,[11]]]],[[240,10],[[12,[11]]]],[[236,10],[[12,[11]]]],[[165,10],[[12,[11]]]],[[167,10],[[12,[11]]]],[[233,10],[[12,[11]]]],[[168,10],[[12,[11]]]],[[170,10],[[12,[11]]]],[[172,10],[[12,[11]]]],[[174,10],[[12,[11]]]],[[175,10],[[12,[11]]]],[[179,10],[[12,[11]]]],[[181,10],[[12,[11]]]],[[184,10],[[12,[11]]]],[[182,10],[[12,[11]]]],[[185,10],[[12,[11]]]],[[190,10],[[12,[11]]]],[[186,10],[[12,[11]]]],[[193,10],[[12,[11]]]],[[176,10],[[12,[11]]]],[[196,10],[[12,[11]]]],[[209,10],[[12,[11]]]],[[199,10],[[12,[11]]]],[[243,10],[[12,[11]]]],[[244,10],[[12,[11]]]],[[245,10],[[12,[11]]]],[[246,10],[[12,[11]]]],[[211,10],[[12,[11]]]],[[212,10],[[12,[11]]]],[[213,10],[[12,[11]]]],[[215,10],[[12,[11]]]],[[214,10],[[12,[11]]]],[[216,10],[[12,[11]]]],[[234,10],[[12,[11]]]],[[166,10],[[12,[11]]]],[[203,10],[[12,[11]]]],[[206,10],[[12,[11]]]],[[162,10],[[12,[11]]]],[[163,10],[[12,[11]]]],[[183,10],[[12,[11]]]],[[247,10],[[12,[11]]]],[[248,10],[[12,[11]]]],[[164,10],[[12,[11]]]],[[159,10],[[12,[11]]]],[[173,10],[[12,[11]]]],[[188,10],[[12,[11]]]],[[187,10],[[12,[11]]]],[[191,10],[[12,[11]]]],[[195,10],[[12,[11]]]],[[194,10],[[12,[11]]]],[[198,10],[[12,[11]]]],[[228,10],[[12,[11]]]],[[226,10],[[12,[11]]]],[[222,10],[[12,[11]]]],[[223,10],[[12,[11]]]],[[224,10],[[12,[11]]]],[[220,10],[[12,[11]]]],[[221,10],[[12,[11]]]],[[219,10],[[12,[11]]]],[[241,10],[[12,[11]]]],[[241,10],[[12,[11]]]],[[225,10],[[12,[11]]]],[[231,10],[[12,[11]]]],[[229,10],[[12,[11]]]],[[230,10],[[12,[11]]]],[[217,10],[[12,[11]]]],[[218,10],[[12,[11]]]],[[227,10],[[12,[11]]]],[[189,10],[[12,[11]]]],[[192,10],[[12,[11]]]],[[204,10],[[12,[11]]]],[[205,10],[[12,[11]]]],[[171,10],[[12,[11]]]],[[197,10],[[12,[11]]]],[[200,10],[[12,[11]]]],[[202,10],[[12,[11]]]],[[207,10],[[12,[11]]]],[[237,10],[[12,[11]]]],[[210,10],[[12,[11]]]],[[232,10],[[12,[11]]]],[[208,10],[[12,[11]]]],[[238,10],[[12,[11]]]],[[249,10],[[12,[11]]]],[[235,10],[[12,[11]]]],[[239,10],[[12,[11]]]],[[239,10],[[12,[11]]]],[[],181],[[],182],[[[177,[[16,[1]]]]],183],[[],184],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],240],[[],236],[[],208],[[],185],0,[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[[[13,[172]]],13],[[[13,[174]]],13],[[[13,[175]]],13],[[[13,[179]]],13],[[[13,[185]]],13],[[[13,[190]]],[[16,[13]]]],[[[13,[186]]],13],[[[13,[193]]],13],[[[13,[176]]],13],[[[13,[199]]],13],[[[13,[211]]],13],[[[13,[212]]],13],[[[13,[213]]],13],[[[13,[215]]],13],[[[13,[214]]],13],[[[13,[216]]],13],[[[13,[234]]]],[[[13,[166]]],13],[[[13,[203]]],13],[[[13,[206]]],13],[[[13,[162]]],13],[[[13,[163]]],13],[[[13,[164]]],13],[[[13,[159]]],13],[[[13,[173]]],13],[[[13,[188]]],13],[[[13,[187]]],13],[[[13,[191]]],13],[[[13,[195]]],13],[[[13,[194]]],13],[[[13,[198]]],13],[[[13,[222]]],13],[[[13,[223]]],13],[[[13,[224]]],13],[[[13,[219]]],13],[[[13,[229]]],13],[[[13,[230]]],13],[[[13,[217]]],13],[[[13,[218]]],13],[[[13,[207]]]],[[[13,[210]]]],[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[237],[[],186],[[],187],[[],188],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],189],[[],190],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[236],[236],[236],[208],[208],[208],[[],191],[238,8],[185,8],[240,8],[236,8],[208,8],[214,8],[[],8],[240,8],[236,8],[165,8],[167,8],[233,8],[168,8],[170,8],[172,8],[174,8],[175,8],[179,8],[181,8],[184,8],[182,8],[185,8],[190,8],[186,8],[193,8],[176,8],[196,8],[209,8],[199,8],[243,8],[244,8],[245,8],[246,8],[211,8],[212,8],[213,8],[215,8],[214,8],[216,8],[234,8],[166,8],[203,8],[206,8],[162,8],[183,8],[164,8],[159,8],[173,8],[188,8],[187,8],[191,8],[195,8],[194,8],[198,8],[228,8],[222,8],[223,8],[224,8],[220,8],[219,8],[225,8],[229,8],[230,8],[227,8],[204,8],[205,8],[171,8],[197,8],[200,8],[202,8],[207,8],[210,8],[232,8],[208,8],[[],192],[236,250],[208,251],[236,252],[208,253],[[[13,[236]]],254],[[[13,[236]]],255],[[],43],[240,1],[236,1],[208,1],[[],193],[[],194],[[],195],[[],240],[[],236],[[],208],[249,238],[[]],[[],196],[[[13,[199]]],245],[[[13,[199]]],246],[[],197],[[],198],[[[13,[199]]],243],[[[13,[199]]],244],[[],199],[[],200],[[[13,[167]],14],15],[[[13,[233]],14],15],[[[13,[168]],14],15],[[[13,[181]],14],15],[[[13,[184]],14],15],[[[13,[182]],14],15],[[[13,[190]],14],15],[[[13,[196]],14],15],[[[13,[209]],14],15],[[[13,[243]],14],15],[[[13,[244]],14],15],[[[13,[245]],14],15],[[[13,[246]],14],15],[[[13,[183]],14],15],[[[13,[228]],14],15],[[[13,[226]],14],15],[[[13,[220]],14],15],[[[13,[221]],14],15],[[[13,[225]],14],15],[[[13,[227]],14],15],[[[13,[238]],14],15],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[],201],[[],202],[[13,14],[[15,[16]]]],[[[13,[201]],14],[[15,[16]]]],[[[13,[240]],14],[[15,[16]]]],[[[13,[236]],14],[[15,[16]]]],[[[13,[165]],14],[[15,[16]]]],[[[13,[170]],14],[[15,[16]]]],[[[13,[172]],14],[[15,[16]]]],[[[13,[174]],14],[[15,[16]]]],[[[13,[175]],14],[[15,[16]]]],[[[13,[179]],14],[[15,[16]]]],[[[13,[185]],14],[[15,[16]]]],[[[13,[186]],14],[[15,[16]]]],[[[13,[193]],14],[[15,[16]]]],[[[13,[176]],14],[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[211]],14],[[15,[16]]]],[[[13,[212]],14],[[15,[16]]]],[[[13,[213]],14],[[15,[16]]]],[[[13,[215]],14],[[15,[16]]]],[[[13,[214]],14],[[15,[16]]]],[[[13,[216]],14],[[15,[16]]]],[[[13,[234]],14],[[15,[16]]]],[[[13,[166]],14],[[15,[16]]]],[[[13,[203]],14],[[15,[16]]]],[[[13,[206]],14],[[15,[16]]]],[[[13,[162]],14],[[15,[16]]]],[[[13,[163]],14],[[15,[16]]]],[[[13,[247]],14],[[15,[16]]]],[[[13,[164]],14],[[15,[16]]]],[[[13,[159]],14],[[15,[16]]]],[[[13,[173]],14],[[15,[16]]]],[[[13,[188]],14],[[15,[16]]]],[[[13,[187]],14],[[15,[16]]]],[[[13,[191]],14],[[15,[16]]]],[[[13,[195]],14],[[15,[16]]]],[[[13,[194]],14],[[15,[16]]]],[[[13,[198]],14],[[15,[16]]]],[[[13,[222]],14],[[15,[16]]]],[[[13,[223]],14],[[15,[16]]]],[[[13,[224]],14],[[15,[16]]]],[[[13,[219]],14],[[15,[16]]]],[[[13,[231]],14],[[15,[16]]]],[[[13,[229]],14],[[15,[16]]]],[[[13,[230]],14],[[15,[16]]]],[[[13,[217]],14],[[15,[16]]]],[[[13,[218]],14],[[15,[16]]]],[[[13,[192]],14],[[15,[16]]]],[[[13,[204]],14],[[15,[16]]]],[[[13,[205]],14],[[15,[16]]]],[[[13,[171]],14],[[15,[16]]]],[[[13,[197]],14],[[15,[16]]]],[[[13,[200]],14],[[15,[16]]]],[[[13,[202]],14],[[15,[16]]]],[[[13,[207]],14],[[15,[16]]]],[[[13,[210]],14],[[15,[16]]]],[[[13,[232]],14],[[15,[16]]]],[[[13,[208]],14],[[15,[16]]]],[[[13,[238]],14],[[15,[16]]]],[14,[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[189]],14],[[15,[[12,[1,88]]]]]],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[1,88]]]]]],[17],[17],[17],[240],[236],[208],[240],[240],[1,203],[[],204],[[],205],[[247,248],[[12,[242]]]],[[248,247],[[12,[242]]]],[[],43],[[],206],[[],207],0,[[],208],[[],209],[[],210],[[]],[240],[236],[165],[170],[172],[174],[175],[179],[185],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[219],[229],[230],[192],[204],[205],[171],[197],[200],[202],[1,211],[[],212],[[[236,[29]],29],[[12,[30]]]],[[[236,[31]],31],[[12,[30]]]],[[]],[[[13,[172]]],12],[[[13,[174]]],12],[[[13,[175]]],12],[[[13,[179]]],12],[[[13,[185]]],12],[[[13,[186]]],12],[[[13,[193]]],12],[[[13,[176]]],12],[[[13,[199]]],12],[[[13,[211]]],12],[[[13,[212]]],12],[[[13,[213]]],12],[[[13,[215]]],12],[[[13,[214]]],12],[[[13,[216]]],12],[[[13,[166]]],12],[[[13,[203]]],12],[[[13,[206]]],12],[[[13,[162]]],12],[[[13,[163]]],12],[[[13,[248]]],12],[[[13,[159]]],12],[[[13,[173]]],12],[[[13,[188]]],12],[[[13,[187]]],12],[[[13,[191]]],12],[[[13,[195]]],12],[[[13,[194]]],12],[[[13,[198]]],12],[[[13,[222]]],12],[[[13,[223]]],12],[[[13,[224]]],12],[[[13,[219]]],12],[[[13,[229]]],12],[[[13,[230]]],12],[[[13,[217]]],12],[[[13,[218]]],12],[1,213],[214,16],[214,16],[[],214],[[],215],[[],216],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[237,237],[1,217],[1,218],[1,219],[[],220],[[],221],[[],222],[[],223],[[],224],[[],225],[[],226],[[[177,[[16,[1]]]]],227],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],228],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[[16,[12]]]]]],[[],229],[[],230],[[],231],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],232],[[],233],[[],234],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[254,10],[[12,[11]]]],[[252,10],[[12,[11]]]],[[255,10],[[12,[11]]]],[[250,10],[[12,[11]]]],[[256,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[254,16],[252,16],[255,16],[250,16],[256,16],[254],[252],[255],[250],[256],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[251,10],[[12,[11]]]],[[253,10],[[12,[11]]]],[[257,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[251,16],[253,16],[257,16],[[],208],[251],[253],[257],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[258,259],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[15,[[16,[12]]]]],260],[[[15,[12]]],260],[258,258],[15,15],[261,261],[[]],[[]],[[]],[[15,15],103],[259],[[],262],[263,258],[33],[29],[258],[[15,15],8],[[259,259],8],[[261,261],8],[[30,10],[[12,[11]]]],[[30,10],[[12,[11]]]],[[262,10],[[12,[11]]]],[[29,10],[[12,[11]]]],[[31,10],[[12,[11]]]],[[263,10],[[12,[11]]]],[[14,10],[[12,[11]]]],[[258,10],[[12,[11]]]],[[15,10],[[12,[11]]]],[[259,10],[[12,[11]]]],[[261,10],[[12,[11]]]],[[]],[[]],[[[35,[34]]],29],[[[13,[[35,[34]]]]],29],[31,29],[[[35,[33,34]]],29],[[]],[[[13,[[35,[33,34]]]]],29],[[[13,[[35,[34]]]]],31],[[[35,[33,34]]],31],[[[35,[34]]],31],[[]],[[[13,[[35,[33,34]]]]],31],[[]],[[]],[[]],[144,258],[[],15],[[]],[143],[[]],[144,259],[[]],[[],[[15,[12]]]],[[],[[15,[[16,[12]]]]]],[259,258],[[[265,[264]]],15],[[[12,[264]]],[[15,[12]]]],[[[12,[264]]],[[15,[[16,[12]]]]]],[258,14],[15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,31],[[],33],[15,8],[15,8],[30,8],[15,15],[[[15,[[16,[12]]]]],[[15,[[16,[12]]]]]],[[[15,[12]]],[[15,[12]]]],[[[15,[12]]],[[15,[12]]]],[[[15,[[16,[12]]]]],[[15,[[16,[12]]]]]],[[],262],[[],29],[[],31],[258,263],[261,259],[[],261],[[[266,[258]]],263],[[],258],[[],258],[[15,15],[[16,[103]]]],[[[13,[29]],14],15],[[[13,[31]],14],15],[17],[15,265],[[262,258]],[[],30],[[],[[12,[30]]]],[[],[[12,[30]]]],[29,[[12,[30]]]],[[],[[12,[59,30]]]],[31,[[12,[30]]]],[[],[[12,[59,30]]]],[[],[[12,[30]]]],[[],[[12,[30]]]],[262,[[16,[258]]]],[[]],[[]],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[259,261],[144],[262],[258],[144],[258],[144,258],[14,258],[144,263],[[258,258],8],0],"p":[[15,"usize"],[3,"Sender"],[3,"UnboundedSender"],[3,"SendError"],[3,"TrySendError"],[3,"Receiver"],[3,"UnboundedReceiver"],[15,"bool"],[3,"TryRecvError"],[3,"Formatter"],[3,"Error"],[4,"Result"],[3,"Pin"],[3,"Context"],[4,"Poll"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Sender"],[3,"Cancellation"],[3,"Canceled"],[3,"Receiver"],[3,"BlockingStream"],[3,"LocalSpawner"],[3,"LocalPool"],[3,"Enter"],[3,"EnterError"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"AndThen"],[8,"Future"],[3,"Global"],[3,"Box"],[3,"CatchUnwind"],[3,"Shared"],[3,"WeakShared"],[3,"Pending"],[3,"OptionFuture"],[3,"PollImmediate"],[3,"Ready"],[4,"Either"],[3,"ErrInto"],[3,"Flatten"],[3,"FlattenSink"],[3,"FlattenStream"],[3,"PollFn"],[3,"JoinAll"],[3,"TryJoinAll"],[3,"Fuse"],[3,"Map"],[3,"IntoStream"],[3,"MapInto"],[3,"Then"],[3,"Inspect"],[3,"NeverError"],[3,"UnitError"],[3,"RemoteHandle"],[3,"Remote"],[3,"IntoFuture"],[3,"TryFlatten"],[3,"TryFlattenStream"],[3,"OrElse"],[3,"OkInto"],[3,"InspectOk"],[3,"InspectErr"],[3,"MapOk"],[3,"MapErr"],[3,"MapOkOrElse"],[3,"UnwrapOrElse"],[3,"Lazy"],[4,"MaybeDone"],[4,"TryMaybeDone"],[3,"Join"],[3,"Join3"],[3,"Join4"],[3,"Join5"],[3,"Select"],[3,"SelectAll"],[3,"TryJoin"],[3,"TryJoin3"],[3,"TryJoin4"],[3,"TryJoin5"],[3,"TrySelect"],[3,"SelectOk"],[3,"Vec"],[3,"Error"],[4,"SeekFrom"],[15,"u64"],[3,"IoSliceMut"],[3,"IoSlice"],[3,"Window"],[3,"BufReader"],[3,"BufWriter"],[3,"LineWriter"],[8,"Error"],[3,"Chain"],[4,"ErrorKind"],[3,"AllowStdIo"],[3,"Cursor"],[3,"Close"],[4,"Ordering"],[3,"Empty"],[3,"Take"],[3,"Copy"],[3,"CopyBuf"],[15,"str"],[3,"FillBuf"],[3,"Flush"],[3,"Repeat"],[3,"Sink"],[3,"ReuniteError"],[3,"SeeKRelative"],[3,"CopyBufAbortable"],[3,"IntoSink"],[3,"Lines"],[3,"Read"],[3,"ReadVectored"],[3,"ReadExact"],[3,"ReadLine"],[3,"ReadToEnd"],[3,"ReadToString"],[3,"ReadUntil"],[3,"Seek"],[3,"ReadHalf"],[3,"WriteHalf"],[3,"Write"],[3,"WriteVectored"],[3,"WriteAll"],[3,"NulError"],[3,"IntoInnerError"],[15,"i32"],[15,"i64"],[15,"u8"],[3,"Arguments"],[3,"Mutex"],[3,"OwnedMutexGuard"],[3,"MutexGuard"],[3,"MappedMutexGuard"],[3,"OwnedMutexLockFuture"],[3,"MutexLockFuture"],[15,"never"],[3,"Arc"],[3,"Buffer"],[3,"Close"],[3,"Drain"],[3,"Fanout"],[3,"Feed"],[3,"Flush"],[3,"Send"],[3,"SendAll"],[3,"SinkErrInto"],[3,"SinkMapErr"],[3,"Unfold"],[3,"With"],[3,"WithFlatMap"],[3,"All"],[3,"AndThen"],[3,"Any"],[8,"Stream"],[3,"BufferUnordered"],[3,"Buffered"],[3,"CatchUnwind"],[3,"Chain"],[3,"Chunks"],[3,"Collect"],[3,"Concat"],[3,"Count"],[3,"Cycle"],[3,"Empty"],[3,"Enumerate"],[3,"ErrInto"],[3,"Filter"],[3,"FilterMap"],[3,"FlatMap"],[8,"Into"],[3,"FlatMapUnordered"],[3,"Flatten"],[3,"FlattenUnordered"],[3,"Fold"],[3,"ForEach"],[3,"ForEachConcurrent"],[3,"Forward"],[3,"Fuse"],[3,"Inspect"],[3,"InspectErr"],[3,"InspectOk"],[3,"IntoAsyncRead"],[3,"StreamFuture"],[3,"IntoStream"],[3,"Iter"],[3,"Map"],[3,"MapErr"],[3,"MapOk"],[3,"Next"],[3,"Once"],[3,"OrElse"],[3,"Peekable"],[3,"Pending"],[3,"PollFn"],[3,"PollImmediate"],[3,"ReadyChunks"],[3,"Repeat"],[3,"RepeatWith"],[3,"Scan"],[3,"Select"],[3,"SelectAll"],[3,"SelectNextSome"],[3,"SelectWithStrategy"],[3,"Skip"],[3,"SkipWhile"],[3,"Take"],[3,"TakeUntil"],[3,"TakeWhile"],[3,"Then"],[3,"TryBufferUnordered"],[3,"TryBuffered"],[3,"TryChunks"],[3,"TryCollect"],[3,"TryConcat"],[3,"TryFilter"],[3,"TryFilterMap"],[3,"TryFlatten"],[3,"TryFold"],[3,"TryForEach"],[3,"TryForEachConcurrent"],[3,"TryNext"],[3,"TrySkipWhile"],[3,"TryTakeWhile"],[3,"TryUnfold"],[3,"Unfold"],[3,"Unzip"],[3,"Zip"],[3,"AbortHandle"],[3,"FuturesUnordered"],[4,"PollNext"],[3,"Abortable"],[3,"Aborted"],[3,"FuturesOrdered"],[3,"TryChunksError"],[3,"ReuniteError"],[3,"Peek"],[3,"PeekMut"],[3,"NextIf"],[3,"NextIfEq"],[3,"SplitStream"],[3,"SplitSink"],[3,"AbortRegistration"],[3,"Iter"],[3,"Iter"],[3,"IterMut"],[3,"IterMut"],[3,"IterPinMut"],[3,"IterPinRef"],[3,"IntoIter"],[3,"IntoIter"],[3,"Waker"],[3,"RawWaker"],[4,"ControlFlow"],[3,"RawWakerVTable"],[3,"AtomicWaker"],[3,"WakerRef"],[4,"Infallible"],[3,"Ready"],[3,"ManuallyDrop"],[8,"TryFuture"],[8,"TryFutureExt"],[8,"FutureExt"],[8,"UnsafeFutureObj"],[8,"FusedFuture"],[13,"Left"],[13,"Right"],[13,"Future"],[13,"Done"],[13,"Future"],[13,"Done"],[8,"AsyncReadExt"],[8,"AsyncWriteExt"],[8,"AsyncBufRead"],[8,"AsyncBufReadExt"],[8,"AsyncWrite"],[8,"AsyncRead"],[8,"AsyncSeek"],[8,"AsyncSeekExt"],[13,"Start"],[13,"End"],[13,"Current"],[8,"Sink"],[8,"TryStream"],[8,"SinkExt"],[8,"StreamExt"],[8,"TryStreamExt"],[8,"FusedStream"],[8,"SpawnExt"],[8,"LocalSpawnExt"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"],[13,"Ready"]],"a":{"errno":[1521],"getlasterror":[1521]}},\ +"futures_channel":{"doc":"Asynchronous channels.","t":[0,0,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,3,3,3,3,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["mpsc","oneshot","Receiver","SendError","Sender","TryRecvError","TrySendError","UnboundedReceiver","UnboundedSender","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","close_channel","close_channel","disconnect","disconnect","drop","drop","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","hash_receiver","hash_receiver","into","into","into","into","into","into","into","into_inner","into_send_error","is_closed","is_closed","is_connected_to","is_connected_to","is_disconnected","is_disconnected","is_full","is_full","is_terminated","is_terminated","poll_close","poll_close","poll_close","poll_flush","poll_flush","poll_flush","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","provide","provide","provide","same_receiver","same_receiver","start_send","start_send","start_send","start_send","start_send","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll_next","try_poll_next","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","unbounded_send","Canceled","Cancellation","Receiver","Sender","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancellation","channel","clone","clone_into","close","drop","drop","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","into_future","into_future","is_canceled","is_connected_to","is_terminated","poll","poll","poll_canceled","provide","send","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_poll","try_recv","type_id","type_id","type_id","type_id"],"q":["futures_channel","","futures_channel::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_channel::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A multi-producer, single-consumer queue for sending values …","A channel for sending a single message between …","The receiving end of a bounded mpsc channel.","The error type for Senders used as Sinks.","The transmission end of a bounded mpsc channel.","The error type returned from try_next.","The error type returned from try_send.","The receiving end of an unbounded mpsc channel.","The transmission end of an unbounded mpsc channel.","","","","","","","","","","","","","","","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel, without dropping …","Closes the receiving half of a channel, without dropping …","Closes this channel from the sender side, preventing any …","Closes this channel from the sender side, preventing any …","Disconnects this sender from the channel, closing it if …","Disconnects this sender from the channel, closing it if …","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Hashes the receiver into the provided hasher","Hashes the receiver into the provided hasher","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the message that was attempted to be sent but …","Drops the message and converts into a SendError.","Returns whether this channel is closed without needing a …","Returns whether this channel is closed without needing a …","Returns whether the sender send to this receiver.","Returns whether the sender send to this receiver.","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the channel …","Returns true if this error is a result of the channel …","","","","","","","","","","","","Polls the channel to determine if there is guaranteed …","","","Check if the channel is ready to receive a message.","","","","Returns whether the senders send to the same receiver.","Returns whether the senders send to the same receiver.","Send a message on the channel.","","Send a message on the channel.","","","","","","","","","","","","","","","","","","","","","","","","Tries to receive the next message without notifying a …","Tries to receive the next message without notifying a …","","","Attempts to send a message on this Sender, returning the …","","","","","","","","Creates an unbounded mpsc channel for communicating …","Sends a message along this channel.","Error returned from a Receiver when the corresponding …","A future that resolves when the receiving end of a channel …","A future for a value that will be provided by another …","A means of transmitting a single value to another task.","","","","","","","","","Creates a future that resolves when this Sender’s …","Creates a new one-shot channel for sending a single value …","","","Gracefully close this receiver, preventing any subsequent …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Tests to see whether this Sender’s corresponding Receiver","Tests to see whether this Sender is connected to the given …","","","","Polls this Sender half to detect whether its associated …","","Completes this oneshot with a successful result.","","","","","","","","","","","","Attempts to receive a message outside of the context of a …","","","",""],"i":[0,0,0,0,0,0,0,0,0,11,2,3,7,8,4,6,11,2,3,7,8,4,6,0,2,3,4,6,2,3,4,6,7,8,2,3,2,3,7,8,4,6,11,11,2,3,7,8,4,4,6,6,11,2,3,7,8,4,6,2,3,11,2,3,7,8,4,6,6,6,2,3,2,3,4,6,4,6,7,8,2,3,3,2,3,3,7,8,2,2,3,3,3,11,4,6,2,3,2,2,3,3,3,2,3,4,6,11,4,6,11,2,3,7,8,4,6,11,2,3,7,8,4,6,7,8,7,8,2,11,2,3,7,8,4,6,0,3,0,0,0,0,26,23,24,25,26,23,24,25,23,0,25,25,26,26,23,25,26,23,24,25,25,26,23,24,25,26,23,24,25,26,24,23,23,26,26,24,23,25,23,25,25,26,23,24,25,26,23,24,25,26,26,26,23,24,25],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[[[6,[5]]],[[6,[5]]]],[[]],[[]],[[]],[[]],[7],[8],[2],[3],[2],[3],[7],[8],[[4,4],9],[[[6,[10]],6],9],[[11,12],13],[[11,12],13],[[[2,[14]],12],13],[[[3,[14]],12],13],[[[7,[14]],12],13],[[[8,[14]],12],13],[[4,12],13],[[4,12],13],[[6,12],13],[[6,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2],[3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[6],[6,4],[2,9],[3,9],[[2,7],9],[[3,8],9],[4,9],[6,9],[4,9],[6,9],[7,9],[8,9],[[[15,[2]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[2]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[7]],16],[[18,[19]]]],[[[15,[8]],16],[[18,[19]]]],[[[15,[2]],16],[[18,[17]]]],[[2,16],[[18,[[17,[4]]]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[3,16],[[18,[[17,[4]]]]]],[20],[20],[20],[[2,2],9],[[3,3],9],[2,[[17,[4]]]],[[[15,[2]]],17],[3,[[17,[4]]]],[[[15,[3]]],17],[[[15,[3]]],17],[[]],[[]],[[]],[[]],[[],21],[[],21],[[],21],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[7,[[17,[19,11]]]],[8,[[17,[19,11]]]],[[15,16],[[18,[[19,[17]]]]]],[[15,16],[[18,[[19,[17]]]]]],[2,[[17,[6]]]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[]],[3,[[17,[6]]]],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[23,24],[[]],[25,25],[[]],[26],[26],[23],[[25,25],9],[[[26,[14]],12],13],[[[23,[14]],12],13],[[[24,[14]],12],13],[[25,12],13],[[25,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[23,9],[[23,26],9],[26,9],[[[15,[26]],16],[[18,[[17,[25]]]]]],[[[15,[24]],16],18],[[23,16],18],[20],[23,17],[[]],[[],21],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[15,16],18],[26,[[17,[19,25]]]],[[],22],[[],22],[[],22],[[],22]],"p":[[15,"usize"],[3,"Sender"],[3,"UnboundedSender"],[3,"SendError"],[8,"Clone"],[3,"TrySendError"],[3,"Receiver"],[3,"UnboundedReceiver"],[15,"bool"],[8,"PartialEq"],[3,"TryRecvError"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Sender"],[3,"Cancellation"],[3,"Canceled"],[3,"Receiver"]]},\ +"futures_core":{"doc":"Core traits and types for asynchronous operations in Rust.","t":[2,2,2,2,2,2,0,14,0,0,6,16,8,2,6,16,8,10,10,6,16,8,16,6,16,8,8,10,10,11,10,2,2,2,2,2],"n":["FusedFuture","FusedStream","Future","Stream","TryFuture","TryStream","future","ready","stream","task","BoxFuture","Error","FusedFuture","Future","LocalBoxFuture","Ok","TryFuture","is_terminated","try_poll","BoxStream","Error","FusedStream","Item","LocalBoxStream","Ok","Stream","TryStream","is_terminated","poll_next","size_hint","try_poll_next","Context","Poll","RawWaker","RawWakerVTable","Waker"],"q":["futures_core","","","","","","","","","","futures_core::future","","","","","","","","","futures_core::stream","","","","","","","","","","","","futures_core::task","","","",""],"d":["","","","","","","Futures.","Extracts the successful type of a Poll<T>.","Asynchronous streams.","Task notification.","An owned dynamically typed Future for use in cases where …","The type of failures yielded by this future","A future which tracks whether or not the underlying future …","","BoxFuture, but without the Send requirement.","The type of successful values yielded by this future","A convenience for futures that return Result values that …","Returns true if the underlying future should no longer be …","Poll this TryFuture as if it were a Future.","An owned dynamically typed Stream for use in cases where …","The type of failures yielded by this future","A stream which tracks whether or not the underlying stream …","Values yielded by the stream.","BoxStream, but without the Send requirement.","The type of successful values yielded by this future","A stream of values produced asynchronously.","A convenience for streams that return Result values that …","Returns true if the stream should no longer be polled.","Attempt to pull out the next value of this stream, …","Returns the bounds on the remaining length of the stream.","Poll this TryStream as if it were a Stream.","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,7,0,8,7,0,9,0,10,0,9,0,0,11,10,10,9,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[2,3],[[5,[4]]]],0,0,0,0,0,0,0,0,[[],1],[[2,3],[[5,[6]]]],[[]],[[2,3],[[5,[[6,[4]]]]]],0,0,0,0,0],"p":[[15,"bool"],[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[4,"Option"],[8,"TryFuture"],[8,"FusedFuture"],[8,"TryStream"],[8,"Stream"],[8,"FusedStream"]]},\ +"futures_executor":{"doc":"Built-in executors and related tools.","t":[3,3,3,3,3,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["BlockingStream","Enter","EnterError","LocalPool","LocalSpawner","block_on","block_on_stream","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","default","deref","deref_mut","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_inner","into_iter","new","next","provide","run","run_until","run_until_stalled","size_hint","spawn_local_obj","spawn_obj","spawner","status","status_local","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_run_one","type_id","type_id","type_id","type_id","type_id"],"q":["futures_executor","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An iterator which blocks on values from a stream until …","Represents an executor context.","An error returned by enter if an execution scope has …","A single-threaded task pool for polling futures to …","A handle to a LocalPool that implements Spawn.","Run a future to completion on the current thread.","Turn a stream into a blocking iterator.","","","","","","","","","","","","","","","","","Marks the current thread as being within the dynamic …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this BlockingStream into the inner Stream type.","","Create a new, empty pool of tasks.","","","Run all tasks in the pool to completion.","Runs all the tasks in the pool until the given future …","Runs all tasks in the pool and returns if no more progress …","","","","Get a clonable handle to the pool as a Spawn.","","","","","","","","","","","","","","","Runs all tasks and returns after completing one future or …","","","","",""],"i":[0,0,0,0,0,0,0,7,8,6,5,4,7,8,6,5,4,5,5,6,4,4,7,0,7,8,8,6,5,4,7,8,6,5,4,7,8,6,5,4,4,4,6,4,8,6,6,6,4,5,5,6,5,5,5,8,7,8,6,5,4,7,8,6,5,4,6,7,8,6,5,4],"f":[0,0,0,0,0,[1],[[[0,[2,3]]],[[4,[[0,[2,3]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,5],[[]],[[],6],[[[4,[[0,[2,3]]]]]],[[[4,[[0,[2,3]]]]]],[7],[[],[[9,[7,8]]]],[[7,10],11],[[8,10],11],[[8,10],11],[[6,10],11],[[5,10],11],[[[4,[[0,[12,2,3]]]],10],11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[4,[[0,[2,3]]]]],[[0,[2,3]]]],[[]],[[],6],[[[4,[[0,[2,3]]]]],13],[14],[6],[[6,1]],[6],[[[4,[[0,[2,3]]]]]],[[5,15],[[9,[16]]]],[[5,17],[[9,[16]]]],[6,5],[5,[[9,[16]]]],[5,[[9,[16]]]],[[]],[[],18],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[6,19],[[],20],[[],20],[[],20],[[],20],[[],20]],"p":[[8,"Future"],[8,"Stream"],[8,"Unpin"],[3,"BlockingStream"],[3,"LocalSpawner"],[3,"LocalPool"],[3,"Enter"],[3,"EnterError"],[4,"Result"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[4,"Option"],[3,"Demand"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"String"],[15,"bool"],[3,"TypeId"]]},\ +"futures_io":{"doc":"Asynchronous I/O","t":[8,8,8,8,2,2,2,2,2,2,10,10,10,10,10,11,11,10,10,11,11],"n":["AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","ErrorKind","IoSlice","IoSliceMut","Result","SeekFrom","consume","poll_close","poll_fill_buf","poll_flush","poll_read","poll_read_vectored","poll_read_vectored","poll_seek","poll_write","poll_write_vectored","poll_write_vectored"],"q":["futures_io","","","","","","","","","","","","","","","","","","","",""],"d":["Read bytes asynchronously.","Read bytes asynchronously.","Seek bytes asynchronously.","Write bytes asynchronously.","","","","","","","Tells this buffer that amt bytes have been consumed from …","Attempt to close the object.","Attempt to return the contents of the internal buffer, …","Attempt to flush the object, ensuring that any buffered …","Attempt to read from the AsyncRead into buf.","Attempt to read from the AsyncRead into bufs using vectored","Attempt to read from the AsyncRead into bufs using vectored","Attempt to seek to an offset, in bytes, in a stream.","Attempt to write bytes from buf into the object.","Attempt to write bytes from bufs into the object using …","Attempt to write bytes from bufs into the object using …"],"i":[0,0,0,0,0,0,0,0,0,0,8,9,8,9,10,10,10,11,9,9,9],"f":[0,0,0,0,0,0,0,0,0,0,[[1,2]],[[1,3],[[5,[4]]]],[[1,3],[[5,[4]]]],[[1,3],[[5,[4]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3,6],[[5,[[4,[7]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]]],"p":[[3,"Pin"],[15,"usize"],[3,"Context"],[6,"Result"],[4,"Poll"],[4,"SeekFrom"],[15,"u64"],[8,"AsyncBufRead"],[8,"AsyncWrite"],[8,"AsyncRead"],[8,"AsyncSeek"]]},\ +"futures_macro":{"doc":"The futures-rs procedural macro implementations.","t":[14,14,14,14,23,14],"n":["join_internal","select_biased_internal","select_internal","stream_select_internal","test_internal","try_join_internal"],"q":["futures_macro","","","","",""],"d":["The join! macro.","The select_biased! macro.","The select! macro.","The stream_select! macro.","","The try_join! macro."],"i":[0,0,0,0,0,0],"f":[0,0,0,0,0,0],"p":[]},\ +"futures_sink":{"doc":"Asynchronous sinks","t":[16,8,10,10,10,10],"n":["Error","Sink","poll_close","poll_flush","poll_ready","start_send"],"q":["futures_sink","","","","",""],"d":["The type of value produced by the sink when an error …","A Sink is a value into which other values can be sent, …","Flush any remaining output and close this sink, if …","Flush any remaining output from this sink.","Attempts to prepare the Sink to receive a value.","Begin the process of sending a value to the sink. Each …"],"i":[5,0,5,5,5,5],"f":[0,0,[[1,2],[[4,[3]]]],[[1,2],[[4,[3]]]],[[1,2],[[4,[3]]]],[1,3]],"p":[[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[8,"Sink"]]},\ +"futures_task":{"doc":"Tools for working with tasks.","t":[8,2,3,3,8,2,2,2,8,3,8,2,3,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,5,5,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5],"n":["ArcWake","Context","FutureObj","LocalFutureObj","LocalSpawn","Poll","RawWaker","RawWakerVTable","Spawn","SpawnError","UnsafeFutureObj","Waker","WakerRef","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","deref","drop","drop","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_shutdown","new","new","new","new_unowned","noop_waker","noop_waker_ref","poll","poll","provide","shutdown","spawn_local_obj","spawn_obj","status","status","status_local","status_local","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","wake","wake","wake_by_ref","waker","waker_ref"],"q":["futures_task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A way of waking up a specific task.","","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","","","","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","A custom implementation of a future trait object for …","","A Waker that is only valid for a given lifetime.","","","","","","","","","","Drops the future represented by the given fat pointer.","","","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Check whether spawning failed to the executor being shut …","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","Spawning failed because the executor has been shut down.","Spawns a future that will be run to completion.","Spawns a future that will be run to completion.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","","","","","","","","","","","","","","Indicates that the associated task is ready to make …","Indicates that the associated task is ready to make …","Indicates that the associated task is ready to make …","Creates a Waker from an Arc<impl ArcWake>.","Creates a reference to a Waker from a reference to …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,5,4,8,1,5,4,8,1,1,13,4,5,5,4,8,1,5,4,4,4,4,4,4,8,8,8,8,8,1,5,4,8,1,4,8,4,13,5,4,8,1,1,0,0,4,8,5,5,22,23,23,23,22,22,5,5,4,8,1,5,4,8,1,5,4,8,1,24,24,24,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,2],[3],[4],[[5,6],7],[[5,6],7],[[4,6],7],[[8,6],7],[[1,6],7],[[]],[8,4],[[[10,[[9,[3]]]]],4],[[]],[[[9,[3]]],4],[[[9,[3]]],4],[[[10,[[9,[3]]]]],4],[[[10,[[9,[[0,[3,11]]]]]]],8],[[[9,[3]]],8],[[[9,[[0,[3,11]]]]],8],[[[10,[[9,[3]]]]],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,8],[[],3],[5,12],[13,4],[[[0,[13,11]]],8],[2,1],[[[14,[2]]],1],[[],2],[[],2],[[[10,[4]],15],16],[[[10,[8]],15],16],[17],[[],5],[4,[[18,[5]]]],[8,[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],19],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],20],[[],20],[[],20],[[],20],[21],[21],[21],[21,2],[21,1]],"p":[[3,"WakerRef"],[3,"Waker"],[8,"Future"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"Formatter"],[6,"Result"],[3,"FutureObj"],[3,"Box"],[3,"Pin"],[8,"Send"],[15,"bool"],[8,"UnsafeFutureObj"],[3,"ManuallyDrop"],[3,"Context"],[4,"Poll"],[3,"Demand"],[4,"Result"],[3,"String"],[3,"TypeId"],[3,"Arc"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"]]},\ +"futures_util":{"doc":"Combinators and utilities for working with Futures, Stream…","t":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,14,0,0,14,14,14,14,14,14,0,0,14,0,14,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,2,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,3,8,8,8,8,8,8,8,8,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,2,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,5,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,5,0,5,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,8,3,2,3,3,8,8,2,2,2,8,3,8,8,2,3,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5],"n":["AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","Future","FutureExt","Sink","SinkExt","Stream","StreamExt","TryFuture","TryFutureExt","TryStream","TryStreamExt","future","io","join","lock","never","pending","pin_mut","poll","ready","select","select_biased","sink","stream","stream_select","task","try_join","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abort","abortable","and_then","and_then","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed","boxed_local","boxed_local","catch_unwind","catch_unwind","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","consume","default","downgrade","drop","drop","eq","err","err_into","err_into","factor_first","factor_second","flatten","flatten","flatten_sink","flatten_sink","flatten_stream","flatten_stream","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_iter","fuse","fuse","inspect","inspect","inspect_err","inspect_err","inspect_ok","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_raw","into_stream","into_stream","is_aborted","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","left_future","map","map","map_err","map_err","map_into","map_into","map_ok","map_ok","map_ok_or_else","map_ok_or_else","maybe_done","never_error","never_error","new","new_pair","now_or_never","now_or_never","ok","ok_into","ok_into","or_else","or_else","output_mut","output_mut","peek","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_read","poll_read_vectored","poll_ready","poll_ready","poll_ready","poll_ready","poll_seek","poll_unpin","poll_unpin","poll_write","poll_write_vectored","provide","ready","remote_handle","remote_handle","right_future","right_future","select","select_all","select_ok","shared","shared","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","strong_count","take_output","take_output","terminated","then","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_flatten","try_flatten","try_flatten_stream","try_flatten_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_unpin","try_poll_unpin","try_select","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unit_error","unit_error","unwrap_or_else","unwrap_or_else","upgrade","weak_count","0","0","0","0","0","0","0","1","AllowStdIo","AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","BufReader","BufWriter","Chain","Close","Copy","CopyBuf","CopyBufAbortable","Cursor","Empty","Error","ErrorKind","FillBuf","Flush","IntoSink","IoSlice","IoSliceMut","LineWriter","Lines","Read","ReadExact","ReadHalf","ReadLine","ReadToEnd","ReadToString","ReadUntil","ReadVectored","Repeat","Result","ReuniteError","SeeKRelative","Seek","SeekFrom","Sink","Take","Window","Write","WriteAll","WriteHalf","WriteVectored","as_mut","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","buffer","buffer","chain","clone","clone","clone_into","clone_into","close","cmp","consume","consume","consume","consume","consume","consume","consume","consume","consume","consume_unpin","copy","copy_buf","copy_buf_abortable","default","empty","end","eq","fill_buf","fill_buf","flush","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_sink","limit","lines","new","new","new","new","new","new","partial_cmp","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_next","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_ready","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek_relative","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","position","provide","read","read","read_exact","read_exact","read_line","read_to_end","read_to_end","read_to_string","read_to_string","read_until","read_vectored","read_vectored","repeat","reunite","reunite","seek","seek","seek_relative","set","set_limit","set_position","sink","split","start","start_send","stream_position","take","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_capacity","with_capacity","with_capacity","write","write","write_all","write_all","write_fmt","write_vectored","write_vectored","MappedMutexGuard","Mutex","MutexGuard","MutexLockFuture","OwnedMutexGuard","OwnedMutexLockFuture","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","deref","deref","deref_mut","deref_mut","deref_mut","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","get_mut","into","into","into","into","into","into","into_future","into_future","into_inner","is_terminated","is_terminated","lock","lock_owned","map","map","new","poll","poll","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","type_id","type_id","type_id","type_id","type_id","type_id","Never","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","clone","clone","clone","clone_into","clone_into","clone_into","close","drain","fanout","feed","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","is_terminated","is_terminated","is_terminated","is_terminated","left_sink","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close_unpin","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush_unpin","poll_next","poll_next","poll_next","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send_unpin","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abortable","all","all","and_then","and_then","any","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed","boxed_local","boxed_local","buffer_unordered","buffer_unordered","buffered","buffered","by_ref","by_ref","catch_unwind","catch_unwind","chain","chain","chunks","chunks","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","collect","collect","concat","concat","consume","count","count","cycle","cycle","default","default","default","default","drop","empty","enumerate","enumerate","eq","eq","err_into","err_into","extend","extend","extend","filter","filter","filter_map","filter_map","flat_map","flat_map","flat_map_unordered","flat_map_unordered","flatten","flatten","flatten_unordered","flatten_unordered","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fold","fold","for_each","for_each","for_each_concurrent","for_each_concurrent","forward","forward","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","fuse","fuse","futures_unordered","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","inspect","inspect","inspect_err","inspect_err","inspect_ok","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_async_read","into_async_read","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_stream","into_stream","is_done","is_empty","is_stopped","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","iter","left_stream","left_stream","len","map","map","map_err","map_err","map_ok","map_ok","new","next","next","next_if","next_if_eq","once","or_else","or_else","peek","peek_mut","peekable","peekable","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next_unpin","poll_next_unpin","poll_peek","poll_peek_mut","poll_read","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_write","provide","provide","push","push_back","push_front","ready_chunks","ready_chunks","repeat","repeat_with","reunite","reunite","right_stream","right_stream","scan","scan","select","select_all","select_all","select_next_some","select_next_some","select_with_strategy","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","skip","skip","skip_while","skip_while","spawn_local_obj","spawn_obj","split","split","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","take","take","take_future","take_result","take_until","take_until","take_while","take_while","then","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","toggle","try_buffer_unordered","try_buffer_unordered","try_buffered","try_buffered","try_chunks","try_chunks","try_collect","try_collect","try_concat","try_concat","try_filter","try_filter","try_filter_map","try_filter_map","try_flatten","try_flatten","try_fold","try_fold","try_for_each","try_for_each","try_for_each_concurrent","try_for_each_concurrent","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next_unpin","try_poll_next_unpin","try_skip_while","try_skip_while","try_take_while","try_take_while","try_unfold","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","unzip","unzip","zip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","iter_pin_mut","iter_pin_ref","len","new","next","next","next","next","next","push","size_hint","size_hint","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","IntoIter","Iter","IterMut","SelectAll","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clear","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","len","new","next","next","next","push","select_all","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","ArcWake","AtomicWaker","Context","FutureObj","LocalFutureObj","LocalSpawn","LocalSpawnExt","Poll","RawWaker","RawWakerVTable","Spawn","SpawnError","SpawnExt","UnsafeFutureObj","Waker","WakerRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_shutdown","new","new","new","new","new_unowned","noop_waker","noop_waker_ref","poll","poll","provide","register","shutdown","spawn","spawn","spawn_local","spawn_local","spawn_local_obj","spawn_local_with_handle","spawn_local_with_handle","spawn_obj","spawn_with_handle","spawn_with_handle","status","status_local","take","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","type_id","type_id","type_id","type_id","type_id","wake","wake","wake_by_ref","waker","waker_ref"],"q":["futures_util","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::future::Either","","futures_util::future::MaybeDone","","futures_util::future::TryMaybeDone","","futures_util::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::lock","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::never","futures_util::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream::futures_unordered","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream::select_all","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","","","","","","","","","Asynchronous values.","Asynchronous I/O.","Polls multiple futures simultaneously, returning a tuple …","Futures-powered synchronization primitives.","This module contains the Never type.","A macro which yields to the event loop once.","Pins a value on the stack.","A macro which returns the result of polling a future once …","Extracts the successful type of a Poll<T>.","Polls multiple futures and streams simultaneously, …","Polls multiple futures and streams simultaneously, …","Asynchronous sinks.","Asynchronous streams.","Combines several streams, all producing the same Item …","Tools for working with tasks.","Polls multiple futures simultaneously, resolving to a …","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Abort the Abortable stream/future associated with this …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","Executes another future after this one resolves …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","Catches unwinding panics while polling the future.","","","","","","","","","","","","","","","","","","","","","","","Creates a new WeakShared for this Shared.","Drops the future represented by the given fat pointer.","","","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Maps this future’s Error to a new error type using the …","Factor out a homogeneous type from an either of pairs.","Factor out a homogeneous type from an either of pairs.","Flatten the execution of this future when the output of …","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Drops this handle without canceling the underlying future.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Fuse a future such that poll will never again be called …","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the error value of a future before …","Do something with the success value of a future before …","Do something with the success value of a future before …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Wraps a TryFuture into a type that implements Future.","Wraps a TryFuture into a type that implements Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unwraps the value from this immediately ready future.","Consumes this combinator, returning the underlying futures.","Extract the value of an either over two equivalent types.","Convert an owned instance into a (conceptually owned) fat …","Convert this future into a single element stream.","Convert this future into a single element stream.","Checks whether the task has been aborted. Note that all …","Returns true if the underlying future should no longer be …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Turns a Future<Output = T> into a …","Creates a new Abortable future/stream using an existing …","Creates an (AbortHandle, AbortRegistration) pair which can …","Evaluates and consumes the future, returning the resulting …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Executes another future if this one resolves to an error. …","Returns an Option containing a mutable reference to the …","Returns an Option containing a mutable reference to the …","Returns Some containing a reference to this Shared’s …","Creates a future which never resolves, representing a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","","","","","","","","","","","","","","","A convenience for calling Future::poll on Unpin future …","A convenience for calling Future::poll on Unpin future …","","","","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","Create a cloneable handle to this future where all handles …","","","","","","","","","","Gets the number of strong pointers to this allocation.","Attempt to take the output of a MaybeDone without driving …","Attempt to take the output of a TryMaybeDone without …","Creates a new Fuse-wrapped future which is already …","Chain on a computation for when a future finished, passing …","Chain on a computation for when a future finished, passing …","","","","","","","","","","","","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryFuture::try_poll on …","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Turns a Future<Output = T> into a …","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","Unwraps this future’s output, producing a future with …","Attempts to upgrade this WeakShared into a Shared.","Gets the number of weak pointers to this allocation.","","","","","","","","","A simple wrapper type which allows types which implement …","Read bytes asynchronously.","An extension trait which adds utility methods to …","Read bytes asynchronously.","An extension trait which adds utility methods to AsyncRead …","Seek bytes asynchronously.","An extension trait which adds utility methods to AsyncSeek …","Write bytes asynchronously.","An extension trait which adds utility methods to AsyncWrite…","The BufReader struct adds buffering to any reader.","Wraps a writer and buffers its output.","Reader for the chain method.","Future for the close method.","Future for the copy() function.","Future for the copy_buf() function.","Future for the [copy_buf()] function.","A Cursor wraps an in-memory buffer and provides it with a …","Reader for the empty() function.","","","Future for the fill_buf method.","Future for the flush method.","Sink for the into_sink method.","","","Wrap a writer, like BufWriter does, but prioritizes …","Stream for the lines method.","Future for the read method.","Future for the read_exact method.","The readable half of an object returned from …","Future for the read_line method.","Future for the read_to_end method.","Future for the read_to_string method.","Future for the read_until method.","Future for the read_vectored method.","Reader for the repeat() function.","","Error indicating a ReadHalf<T> and WriteHalf<T> were not …","Future for the BufReader::seek_relative method.","Future for the seek method.","","Writer for the sink() function.","Reader for the take method.","A owned window around an underlying buffer.","Future for the write method.","Future for the write_all method.","The writable half of an object returned from …","Future for the write_vectored method.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a reference to the internally buffered data.","Returns a reference to the internally buffered data.","Returns a reference to buf_writer’s internally buffered …","Creates an adaptor which will chain this stream with …","","","","","Creates a future which will entirely close this AsyncWrite.","","Tells this buffer that amt bytes have been consumed from …","","","","","","","","","A convenience for calling AsyncBufRead::consume on Unpin …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","","Constructs a new handle to an empty reader.","Returns the end index of this window into the underlying …","","Creates a future which will wait for a non-empty buffer to …","","Creates a future which will entirely flush this AsyncWrite.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the contained IO object.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Gets mutable references to the underlying readers in this …","Gets a mutable reference to the underlying value in this …","Acquires a mutable reference to the underlying sink or …","Gets a mutable reference to the underlying buffer inside …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Gets pinned mutable references to the underlying readers …","Acquires a pinned mutable reference to the underlying sink …","Returns a reference to the contained IO object.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Gets references to the underlying readers in this Chain.","Gets a reference to the underlying value in this cursor.","Acquires a reference to the underlying sink or stream that …","Gets a shared reference to the underlying buffer inside of …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Consumes self and returns the contained IO object.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes the Chain, returning the wrapped readers.","Consumes this cursor, returning the underlying value.","Consumes this combinator, returning the underlying sink or …","Consumes this Window, returning the underlying buffer.","Allow using an AsyncWrite as a Sink<Item: AsRef<[u8]>>.","Returns the remaining number of bytes that can be read …","Returns a stream over the lines of this reader. This …","Creates a new AllowStdIo from an existing IO object.","Creates a new BufReader with a default buffer capacity. …","Creates a new BufWriter with a default buffer capacity. …","Create a new LineWriter with default buffer capacity. The …","Creates a new cursor wrapping the provided underlying …","Creates a new window around the buffer t defaulting to the …","","","","","","","","","","","","","","","","","","","","Attempt to close the object.","","","","","Forward to buf_writer ’s BufWriter::poll_close()","","","","","","","Attempt to return the contents of the internal buffer, …","","","","","","","","Attempt to flush the object, ensuring that any buffered …","","","","","Forward to buf_writer ’s BufWriter::poll_flush()","","","","","","","","Attempt to read from the AsyncRead into buf.","","","","","","","","","","Attempt to read from the AsyncRead into bufs using vectored","","","","","","","","","Attempt to seek to an offset, in bytes, in a stream.","","Seek to an offset, in bytes, in the underlying reader.","Seek to the offset, in bytes, in the underlying writer.","","Attempts to seek relative to the current position. If the …","Attempt to write bytes from buf into the object.","","","","","","","","","","","Attempt to write bytes from bufs into the object using …","","","","","","","","","","","Returns the current position of this cursor.","","Tries to read some bytes directly into the given buf in …","","Creates a future which will read exactly enough bytes to …","","Creates a future which will read all the bytes associated …","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes associated …","Creates a future which will read from the AsyncRead into …","","Creates an instance of a reader that infinitely repeats …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Creates a future which will seek an IO object, and then …","","Seeks relative to the current position. If the new …","Changes the range of this window to the range specified.","Sets the number of bytes that can be read before this …","Sets the position of this cursor.","Creates an instance of a writer which will successfully …","Helper method for splitting this read/write object into …","Returns the starting index of this window into the …","","Creates a future which will return the current seek …","Creates an AsyncRead adapter which will read at most limit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new BufReader with the specified buffer capacity.","Creates a new BufWriter with the specified buffer capacity.","Creates a new LineWriter with the specified buffer …","Creates a future which will write bytes from buf into the …","","Write data into this object.","","","Creates a future which will write bytes from bufs into the …","","An RAII guard returned by the MutexGuard::map and …","A futures-aware mutex.","An RAII guard returned by the lock and try_lock methods. …","A future which resolves when the target mutex has been …","An RAII guard returned by the lock_owned and try_lock_owned…","A future which resolves when the target mutex has been …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes this mutex, returning the underlying data.","","","Acquire the lock asynchronously.","Acquire the lock asynchronously.","Returns a locked view over a portion of the locked data.","Returns a locked view over a portion of the locked data.","Creates a new futures-aware mutex.","","","","","","","","","","","","","","","Attempt to acquire the lock immediately.","Attempt to acquire the lock immediately.","","","","","","","A type with no possible values.","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","","","","","","","","","","","","","","","","","","","","","","","","","","","Adds a fixed-size buffer to the current sink.","","","","","","","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get a mutable reference to the inner sinks.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Get a pinned mutable reference to the inner sinks.","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Get a shared reference to the inner sinks.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Consumes this combinator, returning the underlying sinks.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","","","","","Wrap this sink in an Either sink, making it the left-hand …","","","","","","Flush any remaining output and close this sink, if …","","","","","","","","","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","","","","","","","","","A convenience method for calling Sink::poll_flush on Unpin …","","","","","","Attempts to prepare the Sink to receive a value.","","","","","","","","","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","","","","","","Begin the process of sending a value to the sink. Each …","","","","","","","","","A convenience method for calling Sink::start_send on Unpin …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","Execute predicate over asynchronous stream, and return true…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","An adaptor for chunking up items of the stream inside a …","","","","","","","","","","","","","","","Transforms a stream into a collection, returning a future …","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","Concatenate all items of a stream into a single extendable …","","Drives the stream to completion, counting the number of …","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","Repeats a stream endlessly.","","","","","","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","Creates a stream which gives the current iteration count …","","","Wraps the current stream in a new stream which converts …","Wraps the current stream in a new stream which converts …","","","","Filters the values produced by this stream according to …","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Execute an accumulating asynchronous computation over a …","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","A future that completes after the given stream has been …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Fuse a stream such that poll_next will never again be …","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying stream that …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying streams …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying stream that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying streams that this …","","Do something with each item of this stream, afterwards …","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Do something with the success value of this stream, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Adapter that converts this stream into an AsyncBufRead.","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","Converts this stream into a future of …","","","","","","","","","","","","","","","","","","","","","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying stream.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying streams.","","","","","","","Wraps a TryStream into a type that implements Stream","Wraps a TryStream into a type that implements Stream","Returns whether the underlying stream has finished or not.","Returns true if the queue contains no futures","Whether the stream was stopped yet by the stopping future …","Returns true if the stream should no longer be polled.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts an Iterator into a Stream which is always ready …","Wrap this stream in an Either stream, making it the …","Wrap this stream in an Either stream, making it the …","Returns the number of futures contained in the queue.","Maps this stream’s items to a different type, returning …","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Constructs a new, empty FuturesOrdered","Creates a future that resolves to the next item in the …","Creates a future that resolves to the next item in the …","Creates a future which will consume and return the next …","Creates a future which will consume and return the next …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Chain on a computation for when an error happens, passing …","Produces a future which retrieves a reference to the next …","Produces a future which retrieves a mutable reference to …","Creates a new stream which exposes a peek method.","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling Stream::poll_next on Unpin","A convenience method for calling Stream::poll_next on Unpin","Peek retrieves a reference to the next item in the stream.","Peek retrieves a mutable reference to the next item in the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Push a future into the queue.","Pushes a future to the back of the queue.","Pushes a future to the front of the queue.","An adaptor for chunking up ready items of the stream …","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Wrap this stream in an Either stream, making it the …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream which skips n items of the underlying …","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","Skip elements on this stream while the provided …","","","Splits this Stream + Sink object into separate Sink and …","Splits this Stream + Sink object into separate Sink and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream of at most n items of the underlying …","Creates a new stream of at most n items of the underlying …","Extract the stopping future out of the combinator. The …","Once the stopping future is resolved, this method can be …","Take elements from this stream until the provided future …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","Computes from this stream’s items new items of a …","","","","","","","","","","Toggle the value and return the old one.","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a future that attempts to resolve the next item in …","Creates a future that attempts to resolve the next item in …","","","","","","","","","","","Poll this TryStream as if it were a Stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryStream::try_poll_next …","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","","","","","","","","","","","Clears the set, removing all futures.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Returns true if the set contains no futures.","Returns an iterator that allows inspecting each future in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows inspecting each future in …","Returns the number of futures contained in the set.","Constructs a new, empty FuturesUnordered.","","","","","","Push a future into the set.","","","","","","","","","","","","","","","","","","","","","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","","","","","","","Clears the set, removing all streams.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if the set contains no streams","Returns an iterator that allows inspecting each stream in …","Returns an iterator that allows modifying each stream in …","Returns the number of streams contained in the set.","Constructs a new, empty SelectAll","","","","Push a stream into the set.","Convert a list of streams into a Stream of results from …","","","","","","","","","","","","","A way of waking up a specific task.","A synchronization primitive for task wakeup.","","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","Extension trait for LocalSpawn.","","","","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","Extension trait for Spawn.","A custom implementation of a future trait object for …","","A Waker that is only valid for a given lifetime.","","","","","","","","","","","","","Drops the future represented by the given fat pointer.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Check whether spawning failed to the executor being shut …","Create an AtomicWaker.","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","Registers the waker to be notified on calls to wake.","Spawning failed because the executor has been shut down.","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a task that polls the given future to completion …","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a task that polls the given future to completion …","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Returns the last Waker passed to register, so that the …","","","","","","","","","","","","","","","","","","","Indicates that the associated task is ready to make …","Calls wake on the last Waker passed to register.","Indicates that the associated task is ready to make …","Creates a Waker from an Arc<impl ArcWake>.","Creates a reference to a Waker from a reference to …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,56,0,0,55,0,0,0,0,0,0,54,56,0,0,54,56,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,55,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,266,266,266,266,266,266,6,8,9,11,12,13,14,15,1,16,6,8,9,11,12,13,14,15,1,16,14,11,6,267,6,16,0,265,265,14,14,266,266,265,265,266,266,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,16,40,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,29,30,62,68,266,266,266,266,265,265,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,265,265,28,29,30,32,23,33,35,36,37,38,39,5,40,41,42,43,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,13,62,14,267,266,266,15,73,6,32,23,25,33,34,35,36,37,38,39,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,14,14,0,0,0,0,0,0,266,266,266,266,265,265,266,266,265,265,265,265,0,266,266,15,1,266,266,0,265,265,265,265,54,56,6,0,6,28,29,30,32,23,33,35,36,37,38,39,5,40,41,42,43,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,25,44,24,14,14,14,25,44,24,14,14,0,0,25,34,44,24,12,14,15,14,14,25,44,24,14,14,266,266,14,14,16,0,266,266,266,266,0,0,0,266,266,25,34,44,24,14,25,44,24,14,6,54,56,32,266,266,6,8,9,11,12,13,14,15,1,16,16,265,265,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,0,0,0,0,0,0,55,6,28,30,32,23,33,35,36,37,38,39,5,40,42,43,2,45,22,46,47,48,49,50,51,52,53,9,56,13,63,64,65,66,67,68,14,15,25,34,44,24,14,15,265,265,0,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,266,266,265,265,8,6,268,269,270,271,272,273,107,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,83,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,86,88,89,274,91,92,91,92,275,91,97,96,91,91,86,88,90,92,98,276,0,0,0,92,0,83,91,276,91,275,91,96,105,106,107,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,91,86,88,90,92,98,83,86,88,90,98,91,86,88,89,90,92,98,83,91,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,91,86,88,90,92,98,83,275,98,276,91,86,88,89,92,83,91,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,87,106,91,86,88,89,92,92,92,92,111,122,97,96,91,86,88,90,92,98,87,106,91,86,88,89,92,92,92,92,111,122,112,85,96,105,91,86,88,90,92,121,98,85,105,91,86,88,90,92,121,111,128,91,86,88,92,86,87,106,91,86,88,89,92,92,92,92,122,87,106,91,86,88,89,92,92,92,92,122,92,107,274,91,274,91,276,274,91,274,91,276,274,91,0,121,122,277,91,86,83,98,92,0,274,83,111,277,274,91,92,107,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,112,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,86,88,89,275,91,275,91,91,275,91,0,0,0,0,0,0,135,139,136,140,137,138,135,139,136,140,137,138,135,136,137,138,136,137,138,139,136,140,137,138,135,139,136,140,137,138,135,135,135,139,136,140,137,138,135,135,139,136,140,137,138,139,140,135,139,140,135,135,137,138,135,139,140,135,139,136,140,137,138,135,139,136,140,137,138,135,135,135,139,136,140,137,138,0,0,0,0,152,0,0,0,0,0,0,0,0,0,0,0,0,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,278,144,145,146,144,145,146,278,0,278,278,278,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,148,153,145,146,156,143,148,153,145,146,156,143,148,153,145,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,149,150,154,148,153,145,146,156,143,153,145,156,143,278,151,147,149,150,154,152,144,148,153,145,155,146,156,143,278,152,144,148,153,145,155,146,156,143,278,153,145,146,156,143,152,144,148,153,145,155,146,156,143,278,278,278,278,278,278,153,145,146,156,143,152,144,148,153,145,155,146,156,143,278,144,145,146,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,149,150,154,153,145,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,0,278,278,190,183,190,183,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,0,173,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,279,279,280,280,279,279,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,167,168,169,170,171,172,173,167,168,169,170,171,172,173,279,279,279,279,177,279,279,279,279,179,180,173,181,180,0,279,279,183,173,280,280,179,180,181,279,279,279,279,279,279,279,279,279,279,279,279,190,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,279,279,279,279,279,279,279,279,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,179,180,181,279,279,0,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,173,279,279,280,280,280,280,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,280,280,279,279,175,192,176,193,194,195,197,200,201,203,204,205,206,216,225,226,230,232,235,241,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,180,180,180,181,181,181,280,280,196,179,211,157,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,216,164,159,184,219,220,221,222,223,224,225,227,228,229,230,233,235,237,238,241,168,169,170,242,171,172,243,244,245,181,0,279,279,179,279,279,280,280,280,280,179,279,279,202,202,0,280,280,202,202,279,279,0,175,192,176,193,194,195,197,200,201,203,204,205,206,216,225,226,230,232,235,241,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,177,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,0,0,158,191,179,180,165,178,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,217,164,159,184,219,220,221,222,223,224,227,228,229,233,236,237,238,239,240,167,168,169,170,242,171,172,243,244,245,181,279,279,202,202,177,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,190,183,179,179,179,279,279,0,0,217,218,279,279,279,279,0,0,0,279,279,0,158,179,180,165,178,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,233,237,238,167,168,169,170,242,171,172,279,279,279,279,180,180,279,279,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,279,279,211,211,279,279,279,279,279,279,167,168,169,170,171,172,173,190,183,173,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,280,280,176,193,194,201,225,226,230,232,235,241,231,191,179,180,165,178,185,186,189,196,198,199,187,202,207,208,209,210,211,212,215,162,163,217,164,159,184,219,220,221,222,223,224,227,228,229,233,236,237,238,239,240,167,168,169,170,242,171,243,244,245,181,280,280,280,280,280,280,0,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,0,279,279,279,279,0,0,0,0,0,0,251,252,253,254,255,251,252,253,254,255,180,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,180,180,180,180,180,180,180,251,252,253,254,255,180,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,0,0,0,0,256,257,258,256,257,258,181,256,257,258,256,257,258,256,257,258,256,257,258,181,181,181,181,181,256,257,258,181,0,256,257,258,256,257,258,256,257,258,256,257,258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,259,248,250,260,249,259,248,250,260,259,260,267,248,249,249,259,248,250,260,249,259,248,248,248,248,248,248,250,250,250,250,250,260,249,259,248,250,260,248,250,248,267,249,259,248,250,260,260,0,0,248,250,249,259,249,281,281,282,282,283,282,282,284,281,281,284,283,259,249,249,259,248,250,260,249,259,248,250,260,248,250,249,259,248,250,260,285,259,285,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1],[[]],[[],2],[[],2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[],3],[[],4],[[],4],[[],5],[[],5],[6,6],[[[8,[7]]],[[8,[7]]]],[9,9],[[[11,[10]]],[[11,[10]]]],[[[12,[10]]],[[12,[10]]]],[[[13,[10]]],[[13,[10]]]],[[[14,[10,10]]],[[14,[10,10]]]],[[[15,[10]]],[[15,[10]]]],[1,1],[16,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[17,[14]],18]],[[],11],[6,[[19,[8]]]],[7],[6],[[16,16],20],[[],[[13,[21]]]],[[],22],[[],22],[14],[14],[[],23],[[],23],[[],24],[[],24],[[],25],[[],25],[[[6,[7]],26],27],[[[8,[7]],26],27],[[28,26],27],[[29,26],27],[[30,26],27],[[[32,[31]],26],27],[[23,26],27],[[25,26],27],[[33,26],27],[[34,26],27],[[35,26],27],[[36,26],27],[[37,26],27],[[38,26],27],[[39,26],27],[[[5,[31]],26],27],[[[40,[31]],26],27],[[[41,[[0,[7,31]]]],26],27],[[[42,[31]],26],27],[[43,26],27],[[44,26],27],[[24,26],27],[[2,26],27],[[45,26],27],[[22,26],27],[[46,26],27],[[47,26],27],[[48,26],27],[[49,26],27],[[50,26],27],[[51,26],27],[[52,26],27],[[[53,[31]],26],27],[[[9,[31]],26],27],[[[54,[[0,[31,7]]]],26],27],[[[56,[[0,[31,55]]]],26],27],[[[11,[31]],26],27],[[[12,[31]],26],27],[[[13,[31]],26],27],[[57,26],27],[[58,26],27],[[59,26],27],[[60,26],27],[[[61,[31,31]],26],27],[[[62,[31]],26],27],[[63,26],27],[[64,26],27],[[65,26],27],[[66,26],27],[[[67,[31,31]],26],27],[[[68,[31]],26],27],[[[14,[31,31]],26],27],[[[15,[31]],26],27],[[69,26],27],[[1,26],27],[[16,26],27],[[16,26],27],[40],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[70,[[29,[7]]]],[70,30],[70,[[62,[[0,[7,71]]]]]],[70,[[68,[[0,[55,71]]]]]],[[],32],[[],32],[[],37],[[],37],[[],48],[[],48],[[],47],[[],47],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],42],[[],42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[13],[62,72],[14],[[],7],[[],34],[[],34],[15,20],[[],20],[6,20],[[[32,[7]]],20],[23,20],[25,20],[33,20],[34,20],[35,20],[36,20],[37,20],[38,20],[39,20],[[[42,[[0,[55,73]]]]],20],[43,20],[44,20],[24,20],[2,20],[45,20],[22,20],[46,20],[47,20],[48,20],[49,20],[50,20],[51,20],[52,20],[53,20],[9,20],[[[54,[7]]],20],[[[56,[55]]],20],[[[11,[73]]],20],[[[12,[7]]],20],[13,20],[[[57,[73,73]]],20],[[[58,[73,73,73]]],20],[[[59,[73,73,73,73]]],20],[[[60,[73,73,73,73,73]]],20],[61,20],[14,20],[14,20],[[],57],[[],58],[[],59],[[],60],[[],29],[[],53],[[],14],[[],14],[[],33],[[],33],[[],50],[[],50],[[],35],[[],35],[[],49],[[],49],[[],51],[[],51],[7,[[54,[7]]]],[[],38],[[],38],[69,15],[[]],[[],19],[[],19],[[],[[13,[21]]]],[[],46],[[],46],[[],45],[[],45],[[[17,[[54,[7]]]]],19],[[[17,[[56,[55]]]]],19],[6,19],[[],9],[[[17,[6]],74],75],[[[17,[28]],74],75],[[[17,[29]],74],75],[[[17,[30]],74],75],[[[17,[[32,[7]]]],74],75],[[[17,[23]],74],75],[[[17,[33]],74],75],[[[17,[35]],74],75],[[[17,[36]],74],75],[[[17,[37]],74],75],[[[17,[38]],74],75],[[[17,[39]],74],75],[[[17,[5]],74],75],[[[17,[40]],74],75],[[[17,[[41,[7]]]],74],75],[[[17,[[42,[55]]]],74],75],[[[17,[43]],74],75],[[[17,[2]],74],75],[[[17,[45]],74],75],[[[17,[22]],74],75],[[[17,[46]],74],75],[[[17,[47]],74],75],[[[17,[48]],74],75],[[[17,[49]],74],75],[[[17,[50]],74],75],[[[17,[51]],74],75],[[[17,[52]],74],75],[[[17,[53]],74],75],[[[17,[9]],74],75],[[[17,[[54,[7]]]],74],75],[[[17,[[56,[55]]]],74],75],[[[17,[[11,[7]]]],74],75],[[[17,[12]],74],[[75,[19]]]],[[[17,[13]],74],75],[[[17,[[57,[7,7]]]],74],75],[[[17,[[58,[7,7,7]]]],74],75],[[[17,[[59,[7,7,7,7]]]],74],75],[[[17,[[60,[7,7,7,7,7]]]],74],75],[[[17,[61]],74],75],[[[17,[[62,[[0,[7,71]]]]]],74],75],[[[17,[63]],74],75],[[[17,[64]],74],75],[[[17,[65]],74],75],[[[17,[66]],74],75],[[[17,[[67,[71,71]]]],74],75],[[[17,[[68,[[0,[55,71]]]]]],74],75],[[[17,[14]],74],75],[[[17,[15]],74],75],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[14]],74],[[75,[21]]]],[[],28],[7,[[12,[7]]]],[[[17,[25]],74],[[75,[19]]]],[[[17,[34]],74],[[75,[19]]]],[[[17,[44]],74],[[75,[19]]]],[[[17,[24]],74],[[75,[19]]]],[[[17,[12]],74],[[75,[19]]]],[[[17,[14]],74],[[75,[19]]]],[[[17,[15]],74],[[75,[19]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[21]]]],[[[17,[14]],74,77],[[75,[[76,[78]]]]]],[74,75],[74,75],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[79],[[],13],[[]],[[]],[[],14],[[],14],[[],61],[[],62],[[],68],[[],6],[[],6],[25],[34],[44],[24],[14],[[[17,[25]]],21],[[[17,[44]]],21],[[[17,[24]]],21],[[[17,[14]]],21],[6,[[19,[18]]]],[[[17,[[54,[7]]]]],19],[[[17,[[56,[55]]]]],19],[[],[[32,[7]]]],[[],36],[[],36],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],80],[[],43],[[],43],[[],44],[[],44],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],63],[[],64],[[],65],[[],66],[[],30],[55,[[56,[55]]]],[[17,74],[[75,[21]]]],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[74,[[75,[21]]]],[74,[[75,[21]]]],[[],67],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],39],[[],39],[[],52],[[],52],[[[8,[7]]],[[19,[[6,[7]]]]]],[6,[[19,[18]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[83,[82]]]],[[[83,[84]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[86,[85]]]],[[[88,[87]]]],[[[89,[87]]]],[[],90],[[[91,[10]]],[[91,[10]]]],[[[92,[10]]],[[92,[10]]]],[[]],[[]],[[],93],[[[91,[94]],91],95],[[17,18]],[[[17,[96]],18]],[[91,18]],[[[17,[91]],18]],[[[17,[[86,[85]]]],18]],[[[17,[[88,[97]]]],18]],[[[17,[90]],18]],[[[17,[92]],18]],[[[17,[[98,[97]]]],18]],[18],[[],99],[[],100],[[]],[[],[[92,[101]]]],[[],96],[[[83,[84]]],18],[[[91,[102]],91],20],[[],103],[91,76],[[],104],[91,76],[[96,26],27],[[105,26],27],[[106,26],27],[[107,26],27],[[107,26],27],[[[91,[31]],26],27],[[[86,[31]],26],27],[[[108,[31]],26],27],[[[88,[31]],26],27],[[[89,[[0,[31,87]]]],26],27],[[90,26],27],[[[93,[[0,[31,109]]]],26],27],[[[99,[31,[0,[31,109]]]],26],27],[[[100,[31,[0,[31,109]]]],26],27],[[[110,[31,[0,[31,109]]]],26],27],[[[92,[31]],26],27],[[[103,[[0,[31,109]]]],26],27],[[[104,[[0,[31,109]]]],26],27],[[[111,[31,31]],26],27],[[[112,[31]],26],27],[[[113,[[0,[31,109]]]],26],27],[[[114,[[0,[31,109]]]],26],27],[[[115,[[0,[31,109]]]],26],27],[[[116,[[0,[31,109]]]],26],27],[[[117,[[0,[31,109]]]],26],27],[[[118,[[0,[31,109]]]],26],27],[[[119,[[0,[31,109]]]],26],27],[[[120,[[0,[31,109]]]],26],27],[[[121,[31]],26],27],[[[122,[31]],26],27],[[[98,[31]],26],27],[[[83,[31]],26],27],[[[123,[[0,[31,109]]]],26],27],[[[124,[[0,[31,109]]]],26],27],[[[125,[[0,[31,109]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[91],[[[86,[85]]]],[[[88,[87]]]],[90],[92],[[[98,[85]]]],[[[83,[84]]]],[[[17,[[86,[85]]]]],17],[[[17,[[88,[87]]]]],17],[[[17,[90]]]],[[[17,[[98,[85]]]]],17],[91],[[[86,[85]]]],[[[88,[87]]]],[[[89,[87]]]],[90],[92],[[[98,[85]]]],[[[83,[84]]]],[[[91,[126]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[91],[[[86,[85]]],85],[[[88,[87]]],87],[90],[92],[[[98,[85]]],85],[[[83,[84]]],84],[[],[[111,[84]]]],[[[98,[85]]],78],[[],112],[[],91],[85,[[86,[85]]]],[87,[[88,[87]]]],[87,[[89,[87]]]],[[],92],[84,[[83,[84]]]],[[[91,[127]],91],[[19,[95]]]],[[[17,[108]],74],75],[[[17,[[93,[[0,[87,109,71]]]]]],74],75],[[[17,[[99,[85,[0,[87,71,109]]]]]],74],75],[[[17,[100]],74],75],[[[17,[110]],74],75],[[[17,[103]],74],75],[[[17,[104]],74],75],[[[17,[[113,[[0,[85,109,71]]]]]],74],75],[[[17,[[114,[[0,[85,109,71]]]]]],74],75],[[[17,[[115,[[0,[85,109,71]]]]]],74],75],[[[17,[[116,[[0,[97,109,71]]]]]],74],75],[[[17,[117]],74],75],[[[17,[118]],74],75],[[[17,[[119,[[0,[97,109,71]]]]]],74],75],[[[17,[[120,[[0,[128,109,71]]]]]],74],75],[[[17,[[123,[[0,[87,109,71]]]]]],74],75],[[[17,[[124,[[0,[87,109,71]]]]]],74],75],[[[17,[[125,[[0,[87,109,71]]]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[106]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[87]]]],74],[[75,[76]]]],[[[17,[[88,[87]]]],74],[[75,[76]]]],[[[17,[[89,[87]]]],74],[[75,[76]]]],[[[17,[[92,[130]]]],74],[[75,[76]]]],[[[17,[[92,[72]]]],74],[[75,[76]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[[17,[[122,[87]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[96]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[85]]]],74],[[75,[76]]]],[[[17,[[88,[97]]]],74],[[75,[76]]]],[[[17,[90]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[98,[97]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[106]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[87]]]],74],[[75,[76]]]],[[[17,[[88,[87]]]],74],[[75,[76]]]],[[[17,[[89,[87]]]],74],[[75,[76]]]],[[[17,[[92,[130]]]],74],[[75,[76]]]],[[[17,[[92,[72]]]],74],[[75,[76]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[[17,[[122,[87]]]],74],[[75,[76]]]],[[[17,[[112,[97]]]],74],[[75,[19]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[96]],74],[[75,[[76,[18]]]]]],[[[17,[105]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[90]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[0,[84,71]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[121,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[98,[85]]]],74],[[75,[[21,[18,129]]]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[105]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[90]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[0,[84,71]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[121,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[17,74,77],[[75,[[21,[78,129]]]]]],[[[17,[91]],74,77],[[75,[[76,[78]]]]]],[[[17,[[86,[[0,[85,128]]]]]],74,77],[[75,[[76,[78]]]]]],[[[17,[[88,[[0,[87,128]]]]]],74,77],[[75,[[76,[78]]]]]],[[[17,[92]],74,77],[[75,[[76,[78]]]]]],[[[17,[[86,[[0,[85,128]]]]]],74,132],[[75,[76]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[106]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[89,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[130]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[72]]]],74],[[75,[[76,[18]]]]]],[[[17,[92]],74],[[75,[[76,[18]]]]]],[[[17,[[122,[87]]]],74],[[75,[[76,[18]]]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[106]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[89,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[72]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[130]]]],74],[[75,[[76,[18]]]]]],[[[17,[92]],74],[[75,[[76,[18]]]]]],[[[17,[[122,[87]]]],74],[[75,[[76,[18]]]]]],[92,78],[79],[[],113],[91,[[76,[18]]]],[[],115],[91,76],[80,116],[72,117],[[91,72],[[76,[18]]]],[80,118],[[91,80],[[76,[18]]]],[[131,72],119],[[],114],[91,[[76,[18]]]],[131,105],[[[121,[71]],[122,[71]]],[[21,[71,[107,[71]]]]]],[[[122,[71]],[121,[71]]],[[21,[71,[107,[71]]]]]],[77,120],[[91,77],[[76,[78]]]],[[[17,[[86,[[0,[85,128]]]]]],132],[[108,[[0,[85,128]]]]]],[[[83,[84]],[133,[18]]]],[[[98,[85]],78]],[[92,78]],[[],106],[[]],[[[83,[84]]],18],[[[17,[[111,[87,84]]]],84],21],[[],120],[78,98],[[]],[[]],[[],80],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[18,85],[[86,[85]]]],[[18,87],[[88,[87]]]],[[18,87],[[89,[87]]]],[[],123],[91,[[76,[18]]]],[[],125],[91,76],[[91,134],76],[[],124],[91,[[76,[18]]]],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[135,[101]]]],[[[136,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[136,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[139,[109]]]],[[[136,[109]]]],[[[140,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[135,[109]],26],27],[[[139,[109]],26],27],[[[136,[[0,[109,31]]]],26],27],[[[140,[109]],26],27],[[[137,[[0,[109,31]]]],26],27],[[[138,[109,[0,[109,31]]]],26],27],[141],[[]],[[],135],[[]],[[]],[[]],[[]],[[]],[[[135,[109]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[135],[[[139,[109]]],20],[[[140,[109]]],20],[[[135,[109]]],[[140,[109]]]],[[[142,[[135,[109]]]]],[[139,[109]]]],[[[137,[109]]],[[138,[109,109]]]],[[[138,[109,109]]],[[138,[109,109]]]],[[],135],[[[17,[[139,[109]]]],74],75],[[[17,[[140,[109]]]],74],75],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[[135,[109]]],[[19,[[137,[109]]]]]],[142,[[19,[[136,[109]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[18,143],[144,144],[[[145,[10,10]]],[[145,[10,10]]]],[146,146],[[]],[[]],[[]],[[],147],[[],144],[[],148],[[],149],[[],150],[[151,26],27],[[[147,[[0,[31,109]],31]],26],27],[[[144,[31]],26],27],[[[148,[31,31]],26],27],[[[149,[[0,[31,109]],31]],26],27],[[[150,[[0,[31,109]],31]],26],27],[[[153,[[0,[31,[152,[31]]]],31,31]],26],27],[[[145,[31,31]],26],27],[[[154,[[0,[31,109]],31]],26],27],[[[155,[31,31,31]],26],27],[[146,26],27],[[156,26],27],[[[143,[31,31]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[145],[146],[156],[[[143,[152]]]],[[[17,[148]]]],[[[17,[153]]],17],[[[17,[145]]],17],[[[17,[146]]],17],[[[17,[156]]],17],[[[17,[[143,[152]]]]],17],[148],[153],[145],[146],[156],[[[143,[152]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[145],[146],[156],[[[143,[152]]],152],[153,20],[[[145,[157]]],20],[156,20],[143,20],[[],14],[[[17,[151]],74],75],[[[17,[[147,[[0,[152,71,109]]]]]],74],75],[[[17,[[149,[[0,[152,71,109]]]]]],74],75],[[[17,[[150,[[0,[152,71,109]]]]]],74],75],[[[17,[[154,[[0,[152,71,109]]]]]],74],75],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[[17,[153]],74],[[75,[19]]]],[[[17,[[145,[158]]]],74],[[75,[19]]]],[[[17,[146]],74],[[75,[19]]]],[[[17,[156]],74],[[75,[19]]]],[[[17,[143]],74],[[75,[19]]]],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[],14],[[],154],[[],151],[[],153],[[],145],[153],[[[145,[158]]]],[146],[156],[143],[17,21],[[[17,[144]]],21],[[[17,[148]]],21],[[[17,[153]]],21],[[[17,[145]]],21],[[[17,[155]]],21],[[[17,[146]]],21],[[[17,[156]]],21],[[[17,[[143,[152]]]]],21],[[],21],[[]],[[]],[[]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],155],[[],146],[[],156],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,[[],159],[[],159],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],160],[[],160],[[],161],[[],161],[18,162],[18,162],[18,163],[18,163],[[]],[[]],[[],164],[[],164],[[],165],[[],165],[18,166],[18,166],[[[167,[10]]],[[167,[10]]]],[[[168,[10]]],[[168,[10]]]],[[[169,[10]]],[[169,[10]]]],[170,170],[171,171],[[[172,[10]]],[[172,[10]]]],[173,173],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[175,[[0,[101,174]]]]]],[[],[[175,[[0,[101,174]]]]]],[[],176],[[],176],[[[17,[177]],18]],0,0,[[],178],[[],178],[[],[[179,[7]]]],[[],180],[[],173],[[],[[181,[[0,[158,71]]]]]],[180],[[],170],[[],182],[[],182],[[[183,[102,102]],183],20],[[173,173],20],[[],184],[[],184],[[[179,[7]]]],[180],[[[181,[[0,[158,71]]]],70]],[[],185],[[],185],[[],186],[[],186],[[],187],[[],187],0,0,[[],189],[[],189],0,0,[[190,26],27],[[190,26],27],[[191,26],27],[[[179,[7]],26],27],[[180,26],27],[[[165,[31,31]],26],27],[[[175,[31,31]],26],27],[[[192,[31,31,31]],26],27],[[[176,[[0,[31,158]]]],26],27],[[[178,[31]],26],27],[[[182,[31]],26],27],[[185,26],27],[[186,26],27],[[189,26],27],[[193,26],27],[[194,26],27],[[195,26],27],[[[196,[31]],26],27],[[[197,[31]],26],27],[[198,26],27],[[199,26],27],[[187,26],27],[[[200,[[0,[31,109]]]],26],27],[[[201,[[0,[31,109]]]],26],27],[[[202,[[0,[31,158]]]],26],27],[[203,26],27],[[204,26],27],[[205,26],27],[[206,26],27],[[[207,[31]],26],27],[[208,26],27],[[[209,[31]],26],27],[[210,26],27],[[211,26],27],[[212,26],27],[[[213,[[0,[31,158]],[0,[31,158]]]],26],27],[[[166,[[0,[31,158]]]],26],27],[[[214,[[0,[31,158]]]],26],27],[[215,26],27],[[162,26],27],[[163,26],27],[[216,26],27],[[[217,[31]],26],27],[[[218,[31,31]],26],27],[[[164,[31]],26],27],[[159,26],27],[[184,26],27],[[219,26],27],[[220,26],27],[[[221,[31]],26],27],[[222,26],27],[[223,26],27],[[224,26],27],[[[225,[[0,[31,109]]]],26],27],[[226,26],27],[[227,26],27],[[228,26],27],[[[229,[31]],26],27],[[[230,[31,31]],26],27],[[[232,[[0,[31,231]]]],26],27],[[[233,[[0,[31,231]]]],26],27],[[[183,[234]],26],27],[[[183,[31]],26],27],[[235,26],27],[[236,26],27],[[237,26],27],[[238,26],27],[[[239,[31]],26],27],[[[240,[31]],26],27],[[241,26],27],[[[177,[31]],26],27],[[[167,[31]],26],27],[[[168,[31]],26],27],[[[169,[31]],26],27],[[[170,[31]],26],27],[[[242,[31]],26],27],[[[171,[31]],26],27],[[[172,[31]],26],27],[[[243,[31,31]],26],27],[[173,26],27],[[244,26],27],[[245,26],27],[[[181,[31]],26],27],[[],193],[[],193],[[],195],[[],195],[[[188,[[19,[18]]]]],216],[[[188,[[19,[18]]]]],216],[[],194],[[],194],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[179,[7]]]],[[],180],[70,[[181,[[0,[158,71]]]]]],[[],196],[[],196],0,[[[182,[158]]]],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],19],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[[[209,[158]]]],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]]],[237],[238],[239],[240],[243],[244],[[[17,[[182,[158]]]]],17],[[[17,[185]]],17],[[[17,[186]]],17],[[[17,[189]]],17],[[[17,[196]]],17],[[[17,[[197,[[0,[158,71]]]]]]],[[19,[17]]]],[[[17,[198]]],17],[[[17,[199]]],17],[[[17,[187]]],17],[[[17,[[202,[158]]]]],17],[[[17,[[207,[158]]]]],17],[[[17,[208]]],17],[[[17,[[209,[158]]]]],17],[[[17,[210]]],17],[[[17,[211]]],17],[[[17,[212]]],17],[[[17,[[213,[158,158]]]]]],[[[17,[[166,[158]]]]],17],[[[17,[[214,[158]]]]],17],[[[17,[215]]],17],[[[17,[162]]],17],[[[17,[163]]],17],[[[17,[[164,[[0,[158,246]]]]]]],17],[[[17,[159]]],17],[[[17,[184]]],17],[[[17,[219]]],17],[[[17,[220]]],17],[[[17,[221]]],17],[[[17,[222]]],17],[[[17,[223]]],17],[[[17,[224]]],17],[[[17,[227]]],17],[[[17,[228]]],17],[[[17,[229]]],17],[[[17,[[233,[231]]]]],17],[[[17,[237]]],17],[[[17,[238]]],17],[[[17,[239]]],17],[[[17,[240]]],17],[[[17,[243]]]],[[[17,[244]]]],[[[182,[158]]]],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],19],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[[[209,[158]]]],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]]],[237],[238],[239],[240],[243],[244],[173],[[],198],[[],198],[[],220],[[],220],[[],219],[[],219],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],177],[[],177],[[],197],[[],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[182,[158]]],158],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],[[19,[[0,[158,71]]]]]],[198],[199],[187],[[[202,[158]]],158],[[[207,[158]]],158],[208],[[[209,[158]]],158],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]],158],[[[214,[158]]],158],[215],[162],[163],[[[164,[[0,[158,246]]]]],[[0,[158,246]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]],231],[237],[238],[239],[240],[243],[244],[180],[[[180,[71]]]],[180],[181],[[[181,[[0,[158,71]]]]]],[181],[[],221],[[],221],[196,20],[[[179,[7]]],20],[211,20],[[],20],[[[179,[7]]],20],[[[180,[7]]],20],[165,20],[175,20],[192,20],[176,20],[178,20],[[[182,[[0,[158,157]]]]],20],[185,20],[186,20],[189,20],[193,20],[194,20],[195,20],[[[196,[158]]],20],[[[197,[[0,[158,71]]]]],20],[198,20],[199,20],[187,20],[[[200,[[0,[109,157,71]]]]],20],[[[201,[[0,[109,157,71]]]]],20],[[[202,[158]]],20],[[[203,[158]]],20],[[[204,[158]]],20],[205,20],[206,20],[[[207,[157]]],20],[208,20],[209,20],[210,20],[211,20],[212,20],[213,20],[[[166,[157]]],20],[[[214,[157]]],20],[215,20],[162,20],[216,20],[[[164,[[0,[157,246]]]]],20],[159,20],[184,20],[219,20],[220,20],[[[221,[[0,[231,157]]]]],20],[222,20],[223,20],[224,20],[[[225,[[0,[109,231,71,157]]]]],20],[227,20],[228,20],[229,20],[230,20],[[[233,[[0,[231,157]]]]],20],[235,20],[237,20],[238,20],[241,20],[168,20],[[[169,[247]]],20],[170,20],[[[242,[7]]],20],[171,20],[[[172,[158]]],20],[243,20],[244,20],[245,20],[[[181,[[0,[158,71]]]]],20],[[],167],[[],14],[[],14],[[[179,[7]]],18],[[],199],[[],199],[[],223],[[],223],[[],222],[[],222],[[],[[179,[7]]]],[[],200],[[],200],[[[17,[[202,[158]]]]],[[205,[158]]]],[[[17,[[202,[158]]]]],[[206,[158]]]],[7,[[242,[7]]]],[[],224],[[],224],[[[17,[[202,[158]]]]],[[203,[158]]]],[[[17,[[202,[158]]]]],[[204,[158]]]],[[],202],[[],202],[[],171],[[[17,[175]],74],75],[[[17,[192]],74],75],[[[17,[176]],74],75],[[[17,[193]],74],75],[[[17,[194]],74],75],[[[17,[195]],74],75],[[[17,[[197,[[0,[158,71]]]]]],74],75],[[[17,[[200,[[0,[109,158,71]]]]]],74],75],[[[17,[[201,[[0,[109,157,71]]]]]],74],75],[[[17,[203]],74],75],[[[17,[204]],74],75],[[[17,[205]],74],75],[[[17,[206]],74],75],[[[17,[216]],74],75],[[[17,[[225,[[0,[109,231,71]]]]]],74],75],[[[17,[226]],74],75],[[[17,[230]],74],75],[[[17,[232]],74],75],[[[17,[235]],74],75],[[[17,[241]],74],75],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[76]]]],[[[17,[177]],74],[[75,[76]]]],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[76]]]],[[],191],[158,[[172,[158]]]],[[17,74],[[75,[19]]]],[[[17,[191]],74],[[75,[19]]]],[[[17,[[179,[7]]]],74],[[75,[19]]]],[[[17,[[180,[7]]]],74],[[75,[19]]]],[[[17,[165]],74],[[75,[19]]]],[[[17,[178]],74],[[75,[19]]]],[[[17,[[182,[158]]]],74],[[75,[19]]]],[[[17,[185]],74],[[75,[19]]]],[[[17,[186]],74],[[75,[19]]]],[[[17,[189]],74],[[75,[19]]]],[[[17,[[196,[158]]]],74],[[75,[19]]]],[[[17,[198]],74],[[75,[19]]]],[[[17,[199]],74],[[75,[19]]]],[[[17,[187]],74],[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[[207,[158]]]],74],[[75,[19]]]],[[[17,[208]],74],[[75,[19]]]],[[[17,[209]],74],[[75,[19]]]],[[[17,[210]],74],[[75,[19]]]],[[[17,[211]],74],[[75,[19]]]],[[[17,[212]],74],[[75,[19]]]],[[[17,[213]],74],[[75,[19]]]],[[[17,[[166,[158]]]],74],[[75,[19]]]],[[[17,[[214,[158]]]],74],[[75,[19]]]],[[[17,[215]],74],[[75,[19]]]],[[[17,[162]],74],[[75,[19]]]],[[[17,[163]],74],[[75,[19]]]],[[[17,[[217,[158]]]],74],[[75,[19]]]],[[[17,[[164,[[0,[158,246]]]]]],74],[[75,[19]]]],[[[17,[159]],74],[[75,[19]]]],[[[17,[184]],74],[[75,[19]]]],[[[17,[219]],74],[[75,[19]]]],[[[17,[220]],74],[[75,[19]]]],[[[17,[[221,[231]]]],74],[[75,[19]]]],[[[17,[222]],74],[[75,[19]]]],[[[17,[223]],74],[[75,[19]]]],[[[17,[224]],74],[[75,[19]]]],[[[17,[227]],74],[[75,[19]]]],[[[17,[228]],74],[[75,[19]]]],[[[17,[229]],74],[[75,[19]]]],[[[17,[[233,[231]]]],74],[[75,[19]]]],[[[17,[236]],74],[[75,[19]]]],[[[17,[237]],74],[[75,[19]]]],[[[17,[238]],74],[[75,[19]]]],[[[17,[239]],74],[[75,[19]]]],[[[17,[240]],74],[[75,[19]]]],[[[17,[167]],74],[[75,[19]]]],[[[17,[168]],74],[[75,[19]]]],[[[17,[[169,[247]]]],74],[[75,[19]]]],[[[17,[170]],74],[[75,[19]]]],[[[17,[[242,[7]]]],74],[[75,[19]]]],[[[17,[171]],74],[[75,[19]]]],[[[17,[172]],74],[[75,[19]]]],[[[17,[243]],74],[[75,[19]]]],[[[17,[244]],74],[[75,[19]]]],[[[17,[245]],74],[[75,[19]]]],[[[17,[[181,[[0,[158,71]]]]]],74],[[75,[19]]]],[74,[[75,[19]]]],[74,[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[177]],74],[[75,[[76,[18]]]]]],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[[76,[18]]]]]],[79],[79],[[[179,[7]],7]],[[[179,[7]],7]],[[[179,[7]],7]],[18,214],[18,214],[[],168],[247,[[169,[247]]]],[[[217,[71]],[218,[71]]],[[21,[71,[190,[71]]]]]],[[[218,[[0,[152,71]]]],[217,[[0,[152,71]]]]],[[21,[[0,[152,71]],[190,[[0,[152,71]]]]]]]],[[],14],[[],14],[[],215],[[],215],[[],243],0,[[],181],[[],201],[[],201],[[],244],[[]],[[[179,[7]]]],[[[180,[7]]]],[165],[178],[[[182,[158]]]],[185],[186],[189],[[[196,[158]]]],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[209],[210],[211],[212],[213],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[[[221,[231]]]],[222],[223],[224],[227],[228],[[[233,[231]]]],[237],[238],[167],[168],[[[169,[247]]]],[170],[[[242,[7]]]],[171],[172],[18,207],[18,207],[[],208],[[],208],[[[180,[248]],248],[[21,[249]]]],[[[180,[250]],250],[[21,[249]]]],[[]],[[]],[[[17,[182]]],21],[[[17,[185]]],21],[[[17,[186]]],21],[[[17,[189]]],21],[[[17,[[196,[[0,[158,152]]]]]]],21],[[[17,[198]]],21],[[[17,[199]]],21],[[[17,[187]]],21],[[[17,[202]]],21],[[[17,[207]]],21],[[[17,[208]]],21],[[[17,[209]]],21],[[[17,[210]]],21],[[[17,[211]]],21],[[[17,[212]]],21],[[[17,[166]]],21],[[[17,[214]]],21],[[[17,[215]]],21],[[[17,[162]]],21],[[[17,[163]]],21],[[[17,[[218,[152]]]]],21],[[[17,[159]]],21],[[[17,[184]]],21],[[[17,[219]]],21],[[[17,[220]]],21],[[[17,[[221,[152]]]]],21],[[[17,[222]]],21],[[[17,[223]]],21],[[[17,[224]]],21],[[[17,[227]]],21],[[[17,[228]]],21],[[[17,[229]]],21],[[[17,[233]]],21],[[[17,[237]]],21],[[[17,[238]]],21],[[[17,[239]]],21],[[[17,[240]]],21],[18,209],[18,209],[211,19],[211,19],[[],211],[[],211],[[],210],[[],210],[[],212],[[],212],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],80],[[],80],[173,173],[18,239],[18,239],[18,240],[18,240],[18,233],[18,233],[[],[[230,[[0,[101,174]]]]]],[[],[[230,[[0,[101,174]]]]]],[[],232],[[],232],[[],227],[[],227],[[],228],[[],228],[[],229],[[],229],[[],235],[[],235],[[],226],[[],226],[[[188,[[19,[18]]]]],241],[[[188,[[19,[18]]]]],241],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],225],[[],225],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[74,[[75,[[19,[21]]]]]],[74,[[75,[[19,[21]]]]]],[[],237],[[],237],[[],238],[[],238],[[],236],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],245],[[],192],[[],192],[[],213],[[],213],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[180],[[[251,[31]],26],27],[[[252,[[0,[31,71]]]],26],27],[[[253,[31]],26],27],[[[254,[[0,[31,71]]]],26],27],[[[255,[[0,[31,71]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[180,20],[180,254],[180,252],[[[17,[180]]],251],[[[17,[180]]],253],[180,18],[[],180],[251,19],[[[252,[71]]],19],[253,19],[[[254,[71]]],19],[[[255,[71]]],19],[180],[251],[[[252,[71]]]],[253],[[[254,[71]]]],[[[255,[71]]]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],81],[[],81],[[],81],[[],81],[[],81],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[[181,[[0,[158,71]]]]]],[[[256,[[0,[31,71]]]],26],27],[[[257,[[0,[31,71]]]],26],27],[[[258,[[0,[31,71]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[181,[[0,[158,71]]]]],20],[[[181,[[0,[158,71]]]]],[[256,[[0,[158,71]]]]]],[[[181,[[0,[158,71]]]]],[[257,[[0,[158,71]]]]]],[[[181,[[0,[158,71]]]]],18],[[],[[181,[[0,[158,71]]]]]],[[[256,[[0,[158,71]]]]],19],[[[257,[[0,[158,71]]]]],19],[[[258,[[0,[158,71]]]]],19],[[[181,[[0,[158,71]]]],[0,[158,71]]]],[[],181],[[[256,[[0,[158,71]]]]]],[[[257,[[0,[158,71]]]]]],[[[258,[[0,[158,71]]]]]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],81],[[],81],[[],81],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],259],[260,261],[7],[248],[[249,26],[[21,[262]]]],[[249,26],[[21,[262]]]],[[259,26],[[21,[262]]]],[[248,26],[[21,[262]]]],[[250,26],[[21,[262]]]],[[260,26],[[21,[262]]]],[[]],[[]],[[[130,[263]]],248],[[[17,[[130,[263]]]]],248],[[[130,[7,263]]],248],[[[17,[[130,[7,263]]]]],248],[[]],[250,248],[[]],[[[17,[[130,[263]]]]],250],[[[130,[7,263]]],250],[[[17,[[130,[7,263]]]]],250],[[[130,[263]]],250],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[248,250],[[],7],[249,20],[[],259],[[],248],[[],250],[261,260],[[[264,[261]]],260],[[],261],[[],261],[[[17,[248]],74],75],[[[17,[250]],74],75],[79],[[259,261]],[[],249],[[],[[21,[249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[248,[[21,[249]]]],[[],[[21,[40,249]]]],[[],[[21,[40,249]]]],[250,[[21,[249]]]],[[],[[21,[40,249]]]],[[],[[21,[40,249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[259,[[19,[261]]]],[[],80],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[],81],[[],81],[[],81],[[],81],[[],81],[142],[259],[142],[142,261],[142,260]],"p":[[3,"AbortHandle"],[3,"AndThen"],[6,"BoxFuture"],[6,"LocalBoxFuture"],[3,"CatchUnwind"],[3,"Shared"],[8,"Future"],[3,"WeakShared"],[3,"Pending"],[8,"Clone"],[3,"OptionFuture"],[3,"PollImmediate"],[3,"Ready"],[4,"Either"],[3,"Abortable"],[3,"Aborted"],[3,"Pin"],[15,"usize"],[4,"Option"],[15,"bool"],[4,"Result"],[3,"ErrInto"],[3,"Flatten"],[3,"FlattenSink"],[3,"FlattenStream"],[3,"Formatter"],[6,"Result"],[3,"PollFn"],[3,"JoinAll"],[3,"TryJoinAll"],[8,"Debug"],[3,"Fuse"],[3,"Map"],[3,"IntoStream"],[3,"MapInto"],[3,"Then"],[3,"Inspect"],[3,"NeverError"],[3,"UnitError"],[3,"RemoteHandle"],[3,"Remote"],[3,"IntoFuture"],[3,"TryFlatten"],[3,"TryFlattenStream"],[3,"OrElse"],[3,"OkInto"],[3,"InspectOk"],[3,"InspectErr"],[3,"MapOk"],[3,"MapErr"],[3,"MapOkOrElse"],[3,"UnwrapOrElse"],[3,"Lazy"],[4,"MaybeDone"],[8,"TryFuture"],[4,"TryMaybeDone"],[3,"Join"],[3,"Join3"],[3,"Join4"],[3,"Join5"],[3,"Select"],[3,"SelectAll"],[3,"TryJoin"],[3,"TryJoin3"],[3,"TryJoin4"],[3,"TryJoin5"],[3,"TrySelect"],[3,"SelectOk"],[3,"AbortRegistration"],[8,"IntoIterator"],[8,"Unpin"],[3,"Vec"],[8,"FusedFuture"],[3,"Context"],[4,"Poll"],[6,"Result"],[4,"SeekFrom"],[15,"u64"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"AsMut"],[3,"Window"],[8,"AsRef"],[8,"AsyncRead"],[3,"BufReader"],[8,"AsyncWrite"],[3,"BufWriter"],[3,"LineWriter"],[3,"Chain"],[3,"AllowStdIo"],[3,"Cursor"],[3,"Close"],[8,"Ord"],[4,"Ordering"],[3,"Empty"],[8,"AsyncBufRead"],[3,"Take"],[3,"Copy"],[3,"CopyBuf"],[8,"Default"],[8,"PartialEq"],[3,"FillBuf"],[3,"Flush"],[3,"Repeat"],[3,"Sink"],[3,"ReuniteError"],[3,"SeeKRelative"],[8,"Sized"],[3,"CopyBufAbortable"],[3,"IntoSink"],[3,"Lines"],[3,"Read"],[3,"ReadVectored"],[3,"ReadExact"],[3,"ReadLine"],[3,"ReadToEnd"],[3,"ReadToString"],[3,"ReadUntil"],[3,"Seek"],[3,"ReadHalf"],[3,"WriteHalf"],[3,"Write"],[3,"WriteVectored"],[3,"WriteAll"],[8,"Hash"],[8,"PartialOrd"],[8,"AsyncSeek"],[3,"Error"],[3,"Box"],[15,"u8"],[15,"i64"],[8,"RangeBounds"],[3,"Arguments"],[3,"Mutex"],[3,"OwnedMutexGuard"],[3,"MutexGuard"],[3,"MappedMutexGuard"],[3,"OwnedMutexLockFuture"],[3,"MutexLockFuture"],[15,"never"],[3,"Arc"],[3,"Buffer"],[3,"Drain"],[3,"SinkMapErr"],[3,"With"],[3,"Close"],[3,"Fanout"],[3,"Feed"],[3,"Flush"],[3,"SendAll"],[8,"Sink"],[3,"SinkErrInto"],[3,"Send"],[3,"Unfold"],[3,"WithFlatMap"],[8,"FusedStream"],[8,"Stream"],[3,"AndThen"],[6,"BoxStream"],[6,"LocalBoxStream"],[3,"BufferUnordered"],[3,"Buffered"],[3,"CatchUnwind"],[3,"Chain"],[3,"Chunks"],[3,"Iter"],[3,"Repeat"],[3,"RepeatWith"],[3,"Empty"],[3,"Pending"],[3,"PollImmediate"],[4,"PollNext"],[8,"Extend"],[3,"Collect"],[3,"Concat"],[3,"IntoAsyncRead"],[3,"Cycle"],[3,"FuturesOrdered"],[3,"FuturesUnordered"],[3,"SelectAll"],[3,"Enumerate"],[3,"TryChunksError"],[3,"ErrInto"],[3,"Filter"],[3,"FilterMap"],[3,"FlatMap"],[8,"Into"],[3,"Flatten"],[3,"ReuniteError"],[3,"PollFn"],[3,"Unzip"],[3,"Fold"],[3,"Forward"],[3,"ForEach"],[3,"Fuse"],[3,"StreamFuture"],[3,"Inspect"],[3,"Map"],[3,"Next"],[3,"SelectNextSome"],[3,"Peekable"],[3,"Peek"],[3,"PeekMut"],[3,"NextIf"],[3,"NextIfEq"],[3,"Skip"],[3,"SkipWhile"],[3,"Take"],[3,"TakeWhile"],[3,"TakeUntil"],[3,"Then"],[3,"Zip"],[3,"ReadyChunks"],[3,"Scan"],[3,"ForEachConcurrent"],[3,"SplitStream"],[3,"SplitSink"],[3,"InspectOk"],[3,"InspectErr"],[3,"IntoStream"],[3,"MapOk"],[3,"MapErr"],[3,"OrElse"],[3,"TryNext"],[3,"TryForEach"],[3,"TryFilter"],[3,"TryFilterMap"],[3,"TryFlatten"],[3,"TryCollect"],[8,"TryStream"],[3,"TryConcat"],[3,"TryChunks"],[8,"Display"],[3,"TryFold"],[3,"TryUnfold"],[3,"TrySkipWhile"],[3,"TryTakeWhile"],[3,"TryBufferUnordered"],[3,"TryBuffered"],[3,"TryForEachConcurrent"],[3,"Once"],[3,"Select"],[3,"SelectWithStrategy"],[3,"Unfold"],[8,"UnwindSafe"],[8,"FnMut"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"IterPinMut"],[3,"IterMut"],[3,"IterPinRef"],[3,"Iter"],[3,"IntoIter"],[3,"Iter"],[3,"IterMut"],[3,"IntoIter"],[3,"AtomicWaker"],[3,"WakerRef"],[3,"Waker"],[3,"Error"],[3,"Global"],[3,"ManuallyDrop"],[8,"TryFutureExt"],[8,"FutureExt"],[8,"UnsafeFutureObj"],[13,"Left"],[13,"Right"],[13,"Future"],[13,"Done"],[13,"Future"],[13,"Done"],[8,"AsyncReadExt"],[8,"AsyncWriteExt"],[8,"AsyncBufReadExt"],[8,"AsyncSeekExt"],[8,"SinkExt"],[8,"StreamExt"],[8,"TryStreamExt"],[8,"SpawnExt"],[8,"LocalSpawnExt"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"]]},\ +"generic_array":{"doc":"This crate implements a structure that can be used as a …","t":[8,16,3,2,11,0,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,0,11,11,11,11,11,0,11,11,11,11,11,11,11,8,6,16,8,16,8,6,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,16,8,16,8,16,16,16,16,16,6,8,16,8,10,10,10,10,10,10,10],"n":["ArrayLength","ArrayType","GenericArray","GenericArrayIter","append","arr","arr","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut_slice","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_slice","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from_slice","cmp","concat","default","deref","deref_mut","eq","fmt","fmt","fmt","fold","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_exact_iter","from_iter","from_mut_slice","from_slice","functional","generate","hash","into","into_iter","into_iter","into_iter","iter","map","partial_cmp","pop_back","pop_front","prepend","sequence","split","split","split","try_from","try_into","type_id","zip","AddLength","Inc","Output","FunctionalSequence","Mapped","MappedGenericSequence","MappedSequence","fold","map","zip","GenericArrayIter","as_mut_slice","as_slice","borrow","borrow_mut","clone","count","drop","fmt","fold","from","into","into_iter","last","len","next","next_back","nth","rfold","size_hint","try_from","try_into","type_id","Concat","First","GenericSequence","Length","Lengthen","Longer","Output","Rest","Second","Sequence","SequenceItem","Shorten","Shorter","Split","append","concat","generate","pop_back","pop_front","prepend","split"],"q":["generic_array","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","generic_array::arr","","","generic_array::functional","","","","","","","generic_array::iter","","","","","","","","","","","","","","","","","","","","","","","generic_array::sequence","","","","","","","","","","","","","","","","","","","",""],"d":["Trait making GenericArray work, marking types to be used …","Associated type representing the array type for the number","Struct representing a generic array - GenericArray<T, N> …","","","Implementation for arr! macro.","Macro allowing for easy generation of Generic Arrays. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Extracts a mutable slice containing the entire array.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Extracts a slice containing the entire array.","","","","","","Construct a GenericArray from a slice by cloning its …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts mutable slice to a mutable generic array reference","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts slice to a generic array reference with inferred …","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new GenericArray instance from an iterator with …","","Converts mutable slice to a mutable generic array reference","Converts slice to a generic array reference with inferred …","Functional programming with generic sequences","","","Calls U::from(self).","","","","GenericArray iterator implementation.","","","","","","Useful traits for manipulating sequences of data stored in …","","","","","","","","Helper trait for arr! macro","Helper type for arr! macro","Resulting length","Defines functional programming methods for generic …","Mapped sequence type","Defines the relationship between one generic sequence and …","Accessor type for a mapped generic sequence","Folds (or reduces) a sequence of data into a single value.","Maps a GenericSequence to another GenericSequence.","Combines two GenericSequence instances and iterates …","An iterator that moves out of a GenericArray","Returns the remaining items of this iterator as a mutable …","Returns the remaining items of this iterator as a slice","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","Defines GenericSequences which can be joined together, …","First part of the resulting split array","Defines some sequence with an associated length and …","GenericArray associated length","Defines any GenericSequence which can be lengthened or …","GenericSequence that has one more element than Self","Resulting sequence formed by the concatenation.","Sequence to be concatenated with self","Second part of the resulting split array","Concrete sequence type used in conjuction with reference …","Accessor for GenericSequence item type, which is really …","Defines a GenericSequence which can be shortened by …","GenericSequence that has one less element than Self","Defines a GenericSequence that can be split into two parts …","Returns a new array with the given element appended to the …","Concatenate, or join, two sequences.","Initializes a new sequence instance using the given …","Returns a new array without the last element, and the last …","Returns a new array without the first element, and the …","Returns a new array with the given element prepended to …","Splits an array at the given index, returning the separate …"],"i":[0,1,0,0,2,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,0,2,2,2,2,2,0,2,2,2,2,2,2,2,0,0,98,0,99,0,0,100,100,100,0,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,0,101,0,102,0,103,104,104,101,102,0,0,105,0,103,104,102,105,105,103,101],"f":[0,0,0,0,[[[2,[1]]]],0,0,[[[2,[3]]]],[[[2,[4]]]],[[[2,[5]]]],[[[2,[6]]]],[[[2,[7]]]],[[[2,[8]]]],[[[2,[9]]]],[[[2,[10]]]],[[[2,[11]]]],[[[2,[12]]]],[[[2,[13]]]],[[[2,[14]]]],[[[2,[15]]]],[[[2,[16]]]],[[[2,[17]]]],[[[2,[18]]]],[[[2,[19]]]],[[[2,[20]]]],[[[2,[21]]]],[[[2,[22]]]],[[[2,[23]]]],[[[2,[24]]]],[[[2,[25]]]],[[[2,[26]]]],[[[2,[27]]]],[[[2,[28]]]],[[[2,[29]]]],[[[2,[30]]]],[[[2,[31]]]],[[[2,[32]]]],[[[2,[33]]]],[[[2,[34]]]],[[[2,[35]]]],[[[2,[36]]]],[[[2,[37]]]],[[[2,[38]]]],[[[2,[39]]]],[[[2,[40]]]],[[[2,[41]]]],[[[2,[42]]]],[[[2,[43]]]],[[[2,[44]]]],[[[2,[45]]]],[[[2,[46]]]],[[[2,[47]]]],[[[2,[48]]]],[[[2,[49]]]],[[[2,[50]]]],[[[2,[51]]]],[[[2,[52]]]],[[[2,[53]]]],[[[2,[54]]]],[[[2,[55]]]],[[[2,[56]]]],[[[2,[57]]]],[[[2,[58]]]],[[[2,[59]]]],[[[2,[60]]]],[[[2,[61]]]],[[[2,[62]]]],[[[2,[63]]]],[[[2,[64]]]],[[[2,[65]]]],[[[2,[66]]]],[[[2,[67]]]],[[[2,[68]]]],[[[2,[69]]]],[[[2,[70]]]],[2],[[[2,[71]]]],[[[2,[72]]]],[[[2,[73]]]],[[[2,[74]]]],[[[2,[75]]]],[[[2,[76]]]],[[[2,[77]]]],[[[2,[78]]]],[[[2,[79]]]],[2],[[[2,[24]]]],[[[2,[25]]]],[[[2,[77]]]],[[[2,[76]]]],[[[2,[73]]]],[[[2,[72]]]],[[[2,[40]]]],[[[2,[32]]]],[[[2,[65]]]],[[[2,[41]]]],[2],[[[2,[5]]]],[[[2,[69]]]],[[[2,[6]]]],[[[2,[67]]]],[[[2,[7]]]],[[[2,[68]]]],[[[2,[8]]]],[[[2,[4]]]],[[[2,[64]]]],[[[2,[43]]]],[[[2,[9]]]],[[[2,[63]]]],[[[2,[11]]]],[[[2,[61]]]],[[[2,[12]]]],[[[2,[62]]]],[[[2,[13]]]],[[[2,[60]]]],[[[2,[59]]]],[[[2,[58]]]],[[[2,[56]]]],[[[2,[57]]]],[[[2,[14]]]],[[[2,[53]]]],[[[2,[52]]]],[[[2,[54]]]],[[[2,[15]]]],[[[2,[50]]]],[[[2,[49]]]],[[[2,[48]]]],[[[2,[51]]]],[[[2,[46]]]],[[[2,[47]]]],[[[2,[45]]]],[[[2,[44]]]],[[[2,[66]]]],[[[2,[75]]]],[[[2,[17]]]],[[[2,[3]]]],[[[2,[78]]]],[[[2,[42]]]],[[[2,[18]]]],[[[2,[70]]]],[[[2,[39]]]],[[[2,[16]]]],[[[2,[37]]]],[[[2,[38]]]],[[[2,[35]]]],[[[2,[34]]]],[[[2,[33]]]],[[[2,[31]]]],[[[2,[36]]]],[[[2,[27]]]],[[[2,[30]]]],[[[2,[29]]]],[[[2,[28]]]],[[[2,[71]]]],[[[2,[55]]]],[[[2,[26]]]],[[[2,[74]]]],[[[2,[79]]]],[[[2,[23]]]],[[[2,[20]]]],[[[2,[22]]]],[[[2,[21]]]],[[[2,[19]]]],[[[2,[10]]]],[2],[2],[[]],[[]],[2],[[[2,[80]]],[[2,[80]]]],[[],[[2,[80]]]],[[[2,[81]],2],82],[2],[[],[[2,[83]]]],[2],[2],[[[2,[84]],[2,[84]]],85],[[[2,[86,[1,[86]]]],87],88],[[[2,[86,[1,[86]]]],87],88],[[[2,[89]],87],88],[2],[[],2],[[],2],[[],2],[[],2],[[],[[2,[26]]]],[[],2],[[],2],[[],[[2,[22]]]],[[],2],[[],2],[[],[[2,[18]]]],[[],2],[[],2],[[],[[2,[12]]]],[[],2],[[],2],[[],[[2,[6]]]],[[],2],[[],2],[[],[[2,[40]]]],[[],2],[[],2],[[],[[2,[28]]]],[[],2],[[],2],[[],[[2,[20]]]],[[],2],[[],2],[[],[[2,[14]]]],[[],2],[[],2],[[],[[2,[5]]]],[[],2],[[],2],[[],[[2,[27]]]],[[],2],[[],2],[[],[[2,[16]]]],[[],2],[[],2],[[],[[2,[32]]]],[[],2],[[],2],[[],[[2,[10]]]],[[],2],[[],2],[[],[[2,[24]]]],[[],2],[[],2],[[],[[2,[8]]]],[[],2],[[],2],[[],[[2,[36]]]],[[],2],[[],2],[[],2],[[],[[2,[38]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[4]]]],[[],2],[[],2],[[],[[2,[34]]]],[[],2],[[],[[2,[42]]]],[[],2],[[],2],[[],2],[[],[[2,[39]]]],[[],[[2,[43]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[47]]]],[[],2],[[],2],[[],[[2,[3]]]],[[],2],[[],[[2,[51]]]],[[],2],[[],2],[[],2],[[],[[2,[45]]]],[[],[[2,[54]]]],[[],2],[[],2],[[],2],[[],[[2,[49]]]],[[],[[2,[57]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[60]]]],[[],2],[[],2],[[],[[2,[53]]]],[[],2],[[],[[2,[62]]]],[[],2],[[],2],[[],2],[[],[[2,[59]]]],[[],[[2,[63]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[66]]]],[[],2],[[],2],[[],[[2,[61]]]],[[],2],[[],[[2,[68]]]],[[],2],[[],2],[[],2],[[],[[2,[64]]]],[[],[[2,[69]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[55]]]],[[],2],[[],2],[[],[[2,[67]]]],[[],2],[[],[[2,[75]]]],[[],2],[[],2],[[],2],[[],[[2,[70]]]],[[],[[2,[77]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[78]]]],[[],2],[[],2],[[],[[2,[73]]]],[[],2],[[],[[2,[74]]]],[[],2],[[],2],[[],2],[[],[[2,[79]]]],[[],[[2,[72]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[65]]]],[[],[[2,[30]]]],[[],2],[[],[[2,[76]]]],[[],2],[[],[[2,[58]]]],[[],2],[[],2],[[],2],[[]],[[],[[2,[56]]]],[[],2],[[],2],[[],2],[[],[[2,[7]]]],[[],[[2,[52]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[50]]]],[[],2],[[],2],[[],[[2,[9]]]],[[],2],[[],[[2,[48]]]],[[],2],[[],2],[[],2],[[],[[2,[11]]]],[[],[[2,[46]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[44]]]],[[],2],[[],2],[[],[[2,[13]]]],[[],2],[[],[[2,[41]]]],[[],2],[[],2],[[],2],[[],[[2,[15]]]],[[],[[2,[37]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[35]]]],[[],2],[[],2],[[],[[2,[17]]]],[[],2],[[],[[2,[33]]]],[[],2],[[],2],[[],2],[[],[[2,[19]]]],[[],[[2,[31]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[29]]]],[[],2],[[],2],[[],[[2,[21]]]],[[],2],[[],[[2,[71]]]],[[],2],[[],2],[[],2],[[],[[2,[23]]]],[[],[[2,[25]]]],[[],2],[[],2],[[],[[90,[2]]]],[[],2],[[],2],[[],2],0,[[],2],[[[2,[91]]]],[[]],[2],[2],[2],0,[2,[[92,[2]]]],[[[2,[93]],2],[[90,[82]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],0,[2],[2],[2],[[],94],[[],94],[[],95],[2,[[92,[2]]]],0,0,0,0,0,0,0,[[]],[[],92],[[],92],0,[96],[96],[[]],[[]],[[[96,[80]]],[[96,[80]]]],[96,97],[96],[[[96,[89]],87],88],[96],[[]],[[]],[[]],[96,90],[96,97],[96,90],[96,90],[[96,97],90],[96],[96],[[],94],[[],94],[[],95],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]]],"p":[[8,"ArrayLength"],[3,"GenericArray"],[6,"U12"],[6,"U34"],[6,"U25"],[6,"U20"],[6,"U1024"],[6,"U31"],[6,"U1000"],[6,"U29"],[6,"U512"],[6,"U19"],[6,"U256"],[6,"U24"],[6,"U128"],[6,"U27"],[6,"U500"],[6,"U18"],[6,"U400"],[6,"U23"],[6,"U300"],[6,"U17"],[6,"U200"],[6,"U30"],[6,"U100"],[6,"U16"],[6,"U26"],[6,"U22"],[6,"U80"],[6,"U15"],[6,"U70"],[6,"U28"],[6,"U64"],[6,"U14"],[6,"U63"],[6,"U32"],[6,"U62"],[6,"U33"],[6,"U13"],[6,"U21"],[6,"U61"],[6,"U35"],[6,"U36"],[6,"U60"],[6,"U11"],[6,"U59"],[6,"U37"],[6,"U58"],[6,"U10"],[6,"U57"],[6,"U38"],[6,"U56"],[6,"U9"],[6,"U39"],[6,"U47"],[6,"U55"],[6,"U40"],[6,"U54"],[6,"U8"],[6,"U41"],[6,"U7"],[6,"U42"],[6,"U43"],[6,"U6"],[6,"U53"],[6,"U44"],[6,"U5"],[6,"U45"],[6,"U46"],[6,"U4"],[6,"U90"],[6,"U52"],[6,"U3"],[6,"U51"],[6,"U48"],[6,"U1"],[6,"U49"],[6,"U50"],[6,"U2"],[8,"Clone"],[8,"Ord"],[4,"Ordering"],[8,"Default"],[8,"PartialEq"],[15,"bool"],[15,"u8"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[4,"Option"],[8,"Hash"],[6,"MappedSequence"],[8,"PartialOrd"],[4,"Result"],[3,"TypeId"],[3,"GenericArrayIter"],[15,"usize"],[8,"AddLength"],[8,"MappedGenericSequence"],[8,"FunctionalSequence"],[8,"Split"],[8,"GenericSequence"],[8,"Lengthen"],[8,"Concat"],[8,"Shorten"]]},\ +"getrandom":{"doc":"Interface to the operating system’s random number …","t":[18,18,3,18,18,18,18,18,18,18,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11],"n":["CUSTOM_START","ERRNO_NOT_POSITIVE","Error","FAILED_RDRAND","INTERNAL_START","IOS_SEC_RANDOM","NODE_CRYPTO","NODE_ES_MODULE","NODE_RANDOM_FILL_SYNC","NO_RDRAND","UNSUPPORTED","VXWORKS_RAND_SECURE","WEB_CRYPTO","WEB_GET_RANDOM_VALUES","WINDOWS_RTL_GEN_RANDOM","borrow","borrow_mut","clone","clone_into","code","eq","fmt","fmt","from","from","getrandom","into","provide","raw_os_error","to_owned","to_string","try_from","try_into","type_id"],"q":["getrandom","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Codes at or above this point can be used by users to …","The platform-specific errno returned a non-positive value.","A small and no_std compatible error type","RDRAND instruction failed due to a hardware issue.","Codes below this point represent OS Errors (i.e. positive …","Call to iOS SecRandomCopyBytes failed.","Node.js does not have the crypto CommonJS module.","Called from an ES module on Node.js. This is unsupported, …","Calling Node.js function crypto.randomFillSync failed.","RDRAND instruction unsupported on this target.","This target/platform is not supported by getrandom.","On VxWorks, call to randSecure failed (random number …","The environment does not support the Web Crypto API.","Calling Web Crypto API crypto.getRandomValues failed.","Call to Windows RtlGenRandom failed.","","","","","Extract the bare error code.","","","","","Returns the argument unchanged.","Fill dest with random bytes from the system’s preferred …","Calls U::from(self).","","Extract the raw OS error code (if this error came from the …","","","","",""],"i":[1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[1,1],[[]],[1,2],[[1,1],3],[[1,4],5],[[1,4],5],[2,1],[[]],[[],[[6,[1]]]],[[]],[7],[1,[[9,[8]]]],[[]],[[],10],[[],6],[[],6],[[],11]],"p":[[3,"Error"],[3,"NonZeroU32"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"Demand"],[15,"i32"],[4,"Option"],[3,"String"],[3,"TypeId"]]},\ +"growthring":{"doc":"Simple and modular write-ahead-logging implementation.","t":[3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,13,17,16,8,4,13,6,8,3,6,3,8,3,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,10,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10],"n":["WALFileAIO","WALStoreAIO","allocate","borrow","borrow","borrow_mut","borrow_mut","drop","drop","enumerate_files","from","from","into","into","new","new","open_file","read","remove_file","truncate","try_from","try_from","try_into","try_into","type_id","type_id","wal","write","BestEffort","CRC32","FileNameIter","Record","RecoverPolicy","Strict","WALBytes","WALFile","WALLoader","WALPos","WALRingId","WALStore","WALWriter","allocate","block_nbit","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cache_size","clone","clone","clone_into","clone_into","cmp","default","empty_id","enumerate_files","eq","file_nbit","file_pool_in_use","fmt","from","from","from","from","get_end","get_hash","get_start","grow","hash","into","into","into","into","load","new","open_file","partial_cmp","peel","read","read_recent_records","recover_policy","remove_file","serialize","serialize","to_owned","to_owned","truncate","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","write"],"q":["growthring","","","","","","","","","","","","","","","","","","","","","","","","","","","","growthring::wal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","stop recovering when hitting the first corrupted record","","","","","all checksums must be correct, otherwise recovery fails","","","","","","","","Initialize the file space in [offset, offset + length) to …","","","","","","","","","","","","","","","","","","Enumerate all WAL filenames. It should include all WAL …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Submit a sequence of records to WAL. It returns a vector …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Recover by reading the WAL files.","","Open a file given the filename, create the file if not …","","Inform the WALWriter that some data writes are complete so …","Read data with offset. Return Ok(None) when it reaches EOF.","","","Unlink a file given the filename.","","","","","Truncate a file to a specified length.","","","","","","","","","","","","","Write data with offset. We assume all previous allocate/…"],"i":[0,0,1,1,7,1,7,1,7,7,1,7,1,7,1,7,7,1,7,1,1,7,1,7,1,7,0,1,22,0,24,0,0,22,0,0,0,0,0,0,0,33,18,25,18,21,22,25,18,21,22,18,21,22,21,22,21,18,21,24,21,18,25,21,25,18,21,22,21,21,21,25,21,25,18,21,22,18,18,24,21,25,33,25,18,24,28,17,21,22,33,25,18,21,22,25,18,21,22,25,18,21,22,33],"f":[0,0,[[1,2,3],[[6,[[5,[4]]]]]],[[]],[[]],[[]],[[]],[1],[7],[7,8],[[]],[[]],[[]],[[]],[[9,10,[12,[11]]],[[8,[1]]]],[[10,13,[14,[9]],[14,[11]]],[[8,[7]]]],[[7,10,13],[[6,[[5,[4]]]]]],[[1,2,3],[[6,[[5,[4]]]]]],[[7,15],[[6,[[5,[4]]]]]],[[1,3],8],[[],8],[[],8],[[],8],[[],8],[[],16],[[],16],0,[[1,2,17],[[6,[[5,[4]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,3],[[6,[[5,[4]]]]]],[[18,19],18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[18,20],18],[21,21],[22,22],[[]],[[]],[[21,21],23],[[],18],[[],21],[[],8],[[21,21],13],[[18,19],18],[[[25,[24]]],3],[[21,26],27],[[]],[[]],[[]],[[]],[21,2],[[],19],[21,2],[[[25,[24]],[29,[28]]],[[29,[4]]]],[21],[[]],[[]],[[]],[[]],[[18,24,30,31],[[8,[[25,[24]]]]]],[[],18],[[10,13],[[6,[[5,[4]]]]]],[[21,21],[[14,[23]]]],[[[25,[24]],32,31],8],[[2,3],[[6,[[5,[4]]]]]],[[[25,[24]],3,22],[[8,[[29,[17]]]]]],[[18,22],18],[15,[[6,[[5,[4]]]]]],[[],17],[17,17],[[]],[[]],[3,8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],16],[[],16],[[],16],[[],16],[[2,17],[[6,[[5,[4]]]]]]],"p":[[3,"WALFileAIO"],[6,"WALPos"],[15,"usize"],[8,"Future"],[3,"Box"],[3,"Pin"],[3,"WALStoreAIO"],[4,"Result"],[6,"RawFd"],[15,"str"],[3,"AIOManager"],[3,"Arc"],[15,"bool"],[4,"Option"],[3,"String"],[3,"TypeId"],[6,"WALBytes"],[3,"WALLoader"],[15,"u64"],[3,"NonZeroUsize"],[3,"WALRingId"],[4,"RecoverPolicy"],[4,"Ordering"],[8,"WALStore"],[3,"WALWriter"],[3,"Formatter"],[6,"Result"],[8,"Record"],[3,"Vec"],[8,"FnMut"],[15,"u32"],[8,"AsRef"],[8,"WALFile"]]},\ +"hashbrown":{"doc":"This crate is a Rust port of Google’s high-performance …","t":[13,13,3,3,4,11,11,11,11,11,11,11,0,0,11,11,11,11,11,12,6,3,3,4,4,3,3,3,3,3,3,3,13,13,13,3,3,3,3,3,4,3,3,13,13,13,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,12,12,12,12,12,12,3,3,3,4,3,3,3,3,13,3,3,3,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["AllocError","CapacityOverflow","HashMap","HashSet","TryReserveError","borrow","borrow_mut","clone","clone_into","eq","fmt","from","hash_map","hash_set","into","to_owned","try_from","try_into","type_id","layout","DefaultHashBuilder","Drain","DrainFilter","Entry","EntryRef","HashMap","IntoIter","IntoKeys","IntoValues","Iter","IterMut","Keys","Occupied","Occupied","Occupied","OccupiedEntry","OccupiedEntryRef","OccupiedError","RawEntryBuilder","RawEntryBuilderMut","RawEntryMut","RawOccupiedEntryMut","RawVacantEntryMut","Vacant","Vacant","Vacant","VacantEntry","VacantEntryRef","Values","ValuesMut","allocator","and_modify","and_modify","and_modify","and_replace_entry_with","and_replace_entry_with","and_replace_entry_with","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone","clone","clone_from","clone_into","clone_into","clone_into","clone_into","contains_key","default","drain","drain_filter","drop","entry","entry","entry_ref","eq","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_hash","from_hash","from_iter","from_key","from_key","from_key_hashed_nocheck","from_key_hashed_nocheck","get","get","get","get","get_key_value","get_key_value","get_key_value_mut","get_key_value_mut","get_many_key_value_mut","get_many_key_value_unchecked_mut","get_many_mut","get_many_unchecked_mut","get_mut","get_mut","get_mut","get_mut","hasher","index","insert","insert","insert","insert","insert","insert","insert","insert","insert","insert","insert_hashed_nocheck","insert_key","insert_unique_unchecked","insert_with_hasher","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_key","into_key","into_key","into_key_value","into_keys","into_mut","into_mut","into_mut","into_values","is_empty","iter","iter_mut","key","key","key","key","key","key","key","key_mut","keys","len","len","len","len","len","len","len","len","len","len","new","new_in","next","next","next","next","next","next","next","next","next","next","or_default","or_default","or_insert","or_insert","or_insert","or_insert_with","or_insert_with","or_insert_with","or_insert_with_key","or_insert_with_key","raw_entry","raw_entry_mut","remove","remove","remove","remove","remove_entry","remove_entry","remove_entry","remove_entry","replace_entry","replace_entry","replace_entry_with","replace_entry_with","replace_entry_with","replace_key","replace_key","reserve","retain","shrink_to","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_insert","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_reserve","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","value","values","values_mut","with_capacity","with_capacity_and_hasher","with_capacity_and_hasher_in","with_capacity_in","with_hasher","with_hasher_in","0","0","0","0","0","0","Difference","Drain","DrainFilter","Entry","HashSet","Intersection","IntoIter","Iter","Occupied","OccupiedEntry","SymmetricDifference","Union","Vacant","VacantEntry","allocator","bitand","bitor","bitxor","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone","clone","clone","clone","clone_from","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","contains","default","difference","drain","drain_filter","drop","entry","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","get","get","get","get","get_or_insert","get_or_insert_owned","get_or_insert_with","hasher","insert","insert","insert","insert_unique_unchecked","intersection","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_value","is_disjoint","is_empty","is_subset","is_superset","iter","len","len","len","len","new","new_in","next","next","next","next","next","next","next","next","or_insert","remove","remove","replace","replace","reserve","retain","shrink_to","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","sub","symmetric_difference","take","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_reserve","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","with_capacity","with_capacity_and_hasher","with_capacity_and_hasher_in","with_capacity_in","with_hasher","with_hasher_in","0","0"],"q":["hashbrown","","","","","","","","","","","","","","","","","","","hashbrown::TryReserveError","hashbrown::hash_map","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hashbrown::hash_map::Entry","","hashbrown::hash_map::EntryRef","","hashbrown::hash_map::RawEntryMut","","hashbrown::hash_set","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hashbrown::hash_set::Entry",""],"d":["The memory allocator returned an error","Error due to the computed capacity exceeding the collection…","A hash map implemented with quadratic probing and SIMD …","A hash set implemented as a HashMap where the value is ().","The error type for try_reserve methods.","","","","","","","Returns the argument unchanged.","A hash map implemented with quadratic probing and SIMD …","A hash set implemented as a HashMap where the value is ().","Calls U::from(self).","","","","","The layout of the allocation request that failed.","Default hasher for HashMap.","A draining iterator over the entries of a HashMap in …","A draining iterator over entries of a HashMap which don’…","A view into a single entry in a map, which may either be …","A view into a single entry in a map, which may either be …","A hash map implemented with quadratic probing and SIMD …","An owning iterator over the entries of a HashMap in …","An owning iterator over the keys of a HashMap in arbitrary …","An owning iterator over the values of a HashMap in …","An iterator over the entries of a HashMap in arbitrary …","A mutable iterator over the entries of a HashMap in …","An iterator over the keys of a HashMap in arbitrary order. …","An occupied entry.","An occupied entry.","An occupied entry.","A view into an occupied entry in a HashMap. It is part of …","A view into an occupied entry in a HashMap. It is part of …","The error returned by try_insert when the key already …","A builder for computing where in a HashMap a key-value …","A builder for computing where in a HashMap a key-value …","A view into a single entry in a map, which may either be …","A view into an occupied entry in a HashMap. It is part of …","A view into a vacant entry in a HashMap. It is part of the …","A vacant entry.","A vacant entry.","A vacant entry.","A view into a vacant entry in a HashMap. It is part of the …","A view into a vacant entry in a HashMap. It is part of the …","An iterator over the values of a HashMap in arbitrary …","A mutable iterator over the values of a HashMap in …","Returns a reference to the underlying allocator.","Provides in-place mutable access to an occupied entry …","Provides in-place mutable access to an occupied entry …","Provides in-place mutable access to an occupied entry …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the number of elements the map can hold without …","Clears the map, removing all key-value pairs. Keeps the …","","","","","","","","","","Returns true if the map contains a value for the specified …","Creates an empty HashMap<K, V, S, A>, with the Default …","Clears the map, returning all key-value pairs as an …","Drains elements which are true under the given predicate, …","","Gets the given key’s corresponding entry in the map for …","The entry in the map that was already occupied.","Gets the given key’s corresponding entry by reference in …","","Inserts all new key-values from the iterator to existing …","Inserts all new key-values from the iterator to existing …","Inserts all new key-values from the iterator to existing …","","","","","","","","","","","","","","","","","","","","","","","","Examples","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Creates a RawEntryMut from the given hash and matching …","Access an immutable entry by hash and matching function.","","Creates a RawEntryMut from the given key.","Access an immutable entry by key.","Creates a RawEntryMut from the given key and its hash.","Access an immutable entry by a key and its hash.","Returns a reference to the value corresponding to the key.","Gets a reference to the value in the entry.","Gets a reference to the value in the entry.","Gets a reference to the value in the entry.","Returns the key-value pair corresponding to the supplied …","Gets a reference to the key and value in the entry.","Returns the key-value pair corresponding to the supplied …","Gets a mutable reference to the key and value in the entry.","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Returns a mutable reference to the value corresponding to …","Gets a mutable reference to the value in the entry.","Gets a mutable reference to the value in the entry.","Gets a mutable reference to the value in the entry.","Returns a reference to the map’s BuildHasher.","Returns a reference to the value corresponding to the …","Inserts a key-value pair into the map.","Sets the value of the entry, and returns a …","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns an OccupiedEntry.","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns an …","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntryRef’s …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns the entry’s old …","Insert a key-value pair into the map without checking if …","Set the value of an entry with a custom hasher function.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a consuming iterator, that is, one that moves each …","Creates an iterator over the entries of a HashMap in …","Creates an iterator over the entries of a HashMap in …","","","","","","","","","","","Converts the entry into a mutable reference to the key in …","Take ownership of the key.","Take ownership of the key.","Converts the OccupiedEntry into a mutable reference to the …","Creates a consuming iterator visiting all the keys in …","Converts the OccupiedEntry into a mutable reference to the …","Converts the OccupiedEntry into a mutable reference to the …","Converts the OccupiedEntryRef into a mutable reference to …","Creates a consuming iterator visiting all the values in …","Returns true if the map contains no elements.","An iterator visiting all key-value pairs in arbitrary …","An iterator visiting all key-value pairs in arbitrary …","Gets a reference to the key in the entry.","Returns a reference to this entry’s key.","Gets a reference to the key in the entry.","Gets a reference to the key that would be used when …","Returns a reference to this entry’s key.","Gets a reference to the key in the entry.","Gets a reference to the key that would be used when …","Gets a mutable reference to the key in the entry.","An iterator visiting all keys in arbitrary order. The …","Returns the number of elements in the map.","","","","","","","","","","Creates an empty HashMap.","Creates an empty HashMap using the given allocator.","","","","","","","","","","","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting, if empty, …","Ensures a value is in the entry by inserting, if empty, …","Creates a raw immutable entry builder for the HashMap.","Creates a raw entry builder for the HashMap.","Removes a key from the map, returning the value at the key …","Takes the value out of the entry, and returns it.","Takes the value out of the entry, and returns it. Keeps …","Takes the value out of the entry, and returns it. Keeps …","Removes a key from the map, returning the stored key and …","Take the ownership of the key and value from the map.","Take the ownership of the key and value from the map. …","Take the ownership of the key and value from the map. …","Replaces the entry, returning the old key and value. The …","Replaces the entry, returning the old key and value. The …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Replaces the key in the hash map with the key used to …","Replaces the key in the hash map with the key used to …","Reserves capacity for at least additional more elements to …","Retains only the elements specified by the predicate. …","Shrinks the capacity of the map with a lower limit. It …","Shrinks the capacity of the map as much as possible. It …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tries to insert a key-value pair into the map, and returns …","","","","","","","","","","","","","","","","","","","","","","","","Tries to reserve capacity for at least additional more …","","","","","","","","","","","","","","","","","","","","","","","","The value which was not inserted, because the entry was …","An iterator visiting all values in arbitrary order. The …","An iterator visiting all values mutably in arbitrary order.","Creates an empty HashMap with the specified capacity.","Creates an empty HashMap with the specified capacity, …","Creates an empty HashMap with the specified capacity, …","Creates an empty HashMap with the specified capacity using …","Creates an empty HashMap which will use the given hash …","Creates an empty HashMap which will use the given hash …","","","","","","","A lazy iterator producing elements in the difference of …","A draining iterator over the items of a HashSet.","A draining iterator over entries of a HashSet which don’…","A view into a single entry in a set, which may either be …","A hash set implemented as a HashMap where the value is ().","A lazy iterator producing elements in the intersection of …","An owning iterator over the items of a HashSet.","An iterator over the items of a HashSet.","An occupied entry.","A view into an occupied entry in a HashSet. It is part of …","A lazy iterator producing elements in the symmetric …","A lazy iterator producing elements in the union of HashSet…","A vacant entry.","A view into a vacant entry in a HashSet. It is part of the …","Returns a reference to the underlying allocator.","Returns the intersection of self and rhs as a new …","Returns the union of self and rhs as a new HashSet<T, S>.","Returns the symmetric difference of self and rhs as a new …","","","","","","","","","","","","","","","","","","","","","","","","","Returns the number of elements the set can hold without …","Clears the set, removing all values.","","","","","","","","","","","","","","Returns true if the set contains a value.","Creates an empty HashSet<T, S> with the Default value for …","Visits the values representing the difference, i.e., the …","Clears the set, returning all elements in an iterator.","Drains elements which are true under the given predicate, …","","Gets the given value’s corresponding entry in the set …","","","","","","","","","","","","","","","Examples","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns a reference to the value in the set, if any, that …","Returns a reference to this entry’s value.","Gets a reference to the value in the entry.","Gets a reference to the value that would be used when …","Inserts the given value into the set if it is not present, …","Inserts an owned copy of the given value into the set if …","Inserts a value computed from f into the set if the given …","Returns a reference to the set’s BuildHasher.","Adds a value to the set.","Sets the value of the entry, and returns an OccupiedEntry.","Sets the value of the entry with the VacantEntry’s value.","Insert a value the set without checking if the value …","Visits the values representing the intersection, i.e., the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Creates a consuming iterator, that is, one that moves each …","","","","","","","","","Take ownership of the value.","Returns true if self has no elements in common with other. …","Returns true if the set contains no elements.","Returns true if the set is a subset of another, i.e., other…","Returns true if the set is a superset of another, i.e., …","An iterator visiting all elements in arbitrary order. The …","Returns the number of elements in the set.","","","","Creates an empty HashSet.","Creates an empty HashSet.","","","","","","","","","Ensures a value is in the entry by inserting if it was …","Removes a value from the set. Returns whether the value was","Takes the value out of the entry, and returns it. Keeps …","Adds a value to the set, replacing the existing value, if …","Replaces the entry, returning the old value. The new value …","Reserves capacity for at least additional more elements to …","Retains only the elements specified by the predicate.","Shrinks the capacity of the set with a lower limit. It …","Shrinks the capacity of the set as much as possible. It …","","","","","","","","","Returns the difference of self and rhs as a new …","Visits the values representing the symmetric difference, …","Removes and returns the value in the set, if any, that is …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tries to reserve capacity for at least additional more …","","","","","","","","","","","","","Visits the values representing the union, i.e., all the …","Creates an empty HashSet with the specified capacity.","Creates an empty HashSet with the specified capacity, using","Creates an empty HashSet with the specified capacity, using","Creates an empty HashSet with the specified capacity.","Creates a new empty hash set which will use the given …","Creates a new empty hash set which will use the given …","",""],"i":[1,1,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,56,0,0,0,0,0,0,0,0,0,0,0,0,9,10,12,0,0,0,0,0,0,0,0,9,10,12,0,0,0,0,8,9,10,12,9,10,12,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,8,14,15,16,8,8,14,15,16,8,8,8,8,18,8,35,8,8,8,8,8,8,14,21,22,23,24,15,16,17,25,26,9,27,28,29,10,30,31,12,33,34,35,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,26,29,8,26,29,26,29,8,27,30,33,8,27,8,27,8,8,8,8,8,27,30,33,8,8,8,9,27,28,10,30,31,12,33,34,28,27,8,28,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,8,14,21,22,23,24,15,16,17,18,25,27,31,34,27,8,27,30,33,8,8,8,8,27,10,30,31,12,33,34,27,8,8,14,21,22,23,24,15,16,17,25,8,8,14,21,22,23,24,15,16,17,18,25,10,12,9,10,12,9,10,12,10,12,8,8,8,27,30,33,8,27,30,33,30,33,27,30,33,30,33,8,8,8,8,14,21,22,23,24,15,16,17,18,25,8,14,15,16,35,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,35,8,8,8,8,8,8,8,8,57,58,59,60,61,62,0,0,0,0,0,0,0,0,50,0,0,0,50,0,42,42,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,43,51,48,49,44,45,46,47,50,52,53,42,42,42,43,44,45,46,47,42,42,43,44,45,46,47,42,42,42,42,42,49,42,42,42,42,42,43,51,48,44,45,46,47,50,52,53,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,50,52,53,42,42,42,42,42,50,53,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,43,51,48,49,44,45,46,47,53,42,42,42,42,42,42,43,51,48,42,42,43,51,48,49,44,45,46,47,50,42,52,42,52,42,42,42,42,43,51,48,49,44,45,46,47,42,42,42,42,43,44,45,46,47,42,43,51,48,49,44,45,46,47,50,52,53,42,43,51,48,49,44,45,46,47,50,52,53,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,42,42,42,42,42,63,64],"f":[0,0,0,0,0,[[]],[[]],[1,1],[[]],[[1,1],2],[[1,3],4],[[]],0,0,[[]],[[]],[[],5],[[],5],[[],6],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[8,[[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[8,[[0,[0,7]]]]],13],[[[8,[[0,[0,7]]]]]],[[[8,[7,7,7,[0,[0,7]]]]],[[8,[7,7,7,[0,[0,7]]]]]],[14,14],[15,15],[16,16],[[[8,[7,7,7,[0,[0,7]]]],[8,[7,7,7,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[8,2],[[],8],[[[8,[[0,[0,7]]]]],[[17,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[18,[[0,[0,7]]]]]],[18],[8,10],0,[8,[[12,[11]]]],[[8,8],2],[[8,19]],[[8,19]],[[8,19]],[[8,3],4],[[[14,[20,20]],3],4],[[21,3],4],[[[22,[20,20,[0,[0,7]]]],3],4],[[[23,[20,20,[0,[0,7]]]],3],4],[[[24,[20,[0,[0,7]]]],3],4],[[[15,[20]],3],4],[[[16,[20]],3],4],[[17,3],4],[[[25,[20]],3],4],[[[26,[[0,[0,7]]]],3],4],[[[9,[20,20,[0,[0,7]]]],3],4],[[[27,[20,20,[0,[0,7]]]],3],4],[[[28,[[0,[0,7]]]],3],4],[[[29,[[0,[0,7]]]],3],4],[[[10,[20,20,[0,[0,7]]]],3],4],[[[30,[20,20,[0,[0,7]]]],3],4],[[[31,[20,[0,[0,7]]]],3],4],[[[12,[[32,[[0,[11,20]]]],[0,[11,20]],20,[0,[0,7]]]],3],4],[[[33,[[32,[[0,[11,20]]]],[0,[11,20]],20,[0,[0,7]]]],3],4],[[[34,[[32,[[0,[11,20]]]],[0,[11,20]],[0,[0,7]]]],3],4],[[[35,[20,20,[0,[0,7]]]],3],4],[[[35,[20,20,[0,[0,7]]]],3],4],[[],[[8,[36]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[26,[[0,[0,7]]]],37],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]],37],38],[19,8],[[[26,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]]],38],[[[26,[[0,[0,7]]]],37],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]],37],38],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[8,38],[8,38],[8,38],[8,38],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]]],[8],[8,38],[[[9,[[0,[0,7]]]]],[[27,[[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[28,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[30,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[33,[11,[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[28,[[0,[0,7]]]],37]],[[[27,[[0,[0,7]]]]]],[8],[[[28,[[0,[0,7]]]],37]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[8,[[0,[0,7]]]]],[[22,[[0,[0,7]]]]]],[8,21],[8,14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[27,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[23,[[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[24,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],2],[[[8,[[0,[0,7]]]]],14],[[[8,[[0,[0,7]]]]],21],[[[27,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],15],[[[8,[[0,[0,7]]]]],13],[14,13],[21,13],[[[22,[[0,[0,7]]]]],13],[[[23,[[0,[0,7]]]]],13],[[[24,[[0,[0,7]]]]],13],[15,13],[16,13],[[[17,[[0,[0,7]]]]],13],[25,13],[[],[[8,[36]]]],[[[0,[0,7]]],[[8,[36,[0,[0,7]]]]]],[14,38],[21,38],[[[22,[[0,[0,7]]]]],38],[[[23,[[0,[0,7]]]]],38],[[[24,[[0,[0,7]]]]],38],[15,38],[16,38],[[[17,[[0,[0,7]]]]],38],[18,38],[25,38],[[[10,[39,[0,[0,7]]]]]],[[[12,[11,39,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]],40]],[[[12,[11,[0,[0,7]]]],40]],[[[10,[[0,[0,7]]]],40]],[[[12,[11,[0,[0,7]]]],40]],[[[8,[[0,[0,7]]]]],[[29,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[26,[[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[8,13]],[[[8,[[0,[0,7]]]]]],[[8,13]],[8],[14],[21],[[[22,[[0,[0,7]]]]]],[[[23,[[0,[0,7]]]]]],[[[24,[[0,[0,7]]]]]],[15],[16],[[[17,[[0,[0,7]]]]]],[18],[25],[[]],[[]],[[]],[[]],[[],41],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[8,[[5,[35]]]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[8,13],[[5,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],0,[[[8,[[0,[0,7]]]]],16],[[[8,[[0,[0,7]]]]],25],[13,[[8,[36]]]],[13,8],[[13,[0,[0,7]]],[[8,[[0,[0,7]]]]]],[[13,[0,[0,7]]],[[8,[36,[0,[0,7]]]]]],[[],8],[[[0,[0,7]]],[[8,[[0,[0,7]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[42],[[42,42],42],[[42,42],42],[[42,42],42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[42,[[0,[0,7]]]]],13],[[[42,[[0,[0,7]]]]]],[[[42,[7,7,[0,[0,7]]]]],[[42,[7,7,[0,[0,7]]]]]],[43,43],[[[44,[[0,[0,7]]]]],[[44,[[0,[0,7]]]]]],[[[45,[[0,[0,7]]]]],[[45,[[0,[0,7]]]]]],[[[46,[[0,[0,7]]]]],[[46,[[0,[0,7]]]]]],[[[47,[[0,[0,7]]]]],[[47,[[0,[0,7]]]]]],[[[42,[7,7,[0,[0,7]]]],[42,[7,7,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[42,2],[[],42],[[42,42],45],[[[42,[[0,[0,7]]]]],[[48,[[0,[0,7]]]]]],[[[42,[[0,[0,7]]]]],[[49,[[0,[0,7]]]]]],[[[49,[[0,[0,7]]]]]],[42,50],[[42,42],2],[[42,19]],[[42,19]],[[42,3],4],[[[43,[20]],3],4],[[[51,[20,[0,[0,7]]]],3],4],[[[48,[20,[0,[0,7]]]],3],4],[[44,3],4],[[45,3],4],[[46,3],4],[[47,3],4],[[[50,[20,[0,[0,7]]]],3],4],[[[52,[20,[0,[0,7]]]],3],4],[[[53,[20,[0,[0,7]]]],3],4],[[],[[42,[36]]]],[8,42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,42],[42,38],[[[50,[[0,[0,7]]]]]],[[[52,[[0,[0,7]]]]]],[[[53,[[0,[0,7]]]]]],[42],[42],[42],[42],[42,2],[[[50,[[0,[0,7]]]]],[[52,[[0,[0,7]]]]]],[[[53,[[0,[0,7]]]]]],[42],[[42,42],44],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[42,43],[[[42,[[0,[0,7]]]]],[[51,[[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[53,[[0,[0,7]]]]]],[[42,42],2],[[[42,[[0,[0,7]]]]],2],[[42,42],2],[[42,42],2],[[[42,[[0,[0,7]]]]],43],[[[42,[[0,[0,7]]]]],13],[43,13],[[[51,[[0,[0,7]]]]],13],[[[48,[[0,[0,7]]]]],13],[[],[[42,[36]]]],[[[0,[0,7]]],[[42,[[0,[54,55]],36,[0,[0,7]]]]]],[43,38],[[[51,[[0,[0,7]]]]],38],[[[48,[[0,[0,7]]]]],38],[[[49,[[0,[0,7]]]]],38],[44,38],[45,38],[46,38],[47,38],[[[50,[[0,[0,7]]]]]],[42,2],[[[52,[[0,[0,7]]]]]],[42,38],[[[52,[[0,[0,7]]]]]],[[42,13]],[[[42,[[0,[0,7]]]]]],[[42,13]],[42],[43],[[[51,[[0,[0,7]]]]]],[[[48,[[0,[0,7]]]]]],[[[49,[[0,[0,7]]]]]],[44],[45],[46],[47],[[42,42],42],[[42,42],46],[42,38],[[]],[[]],[[]],[[]],[[]],[[]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[42,13],[[5,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[42,42],47],[13,[[42,[36]]]],[13,[[42,[0]]]],[13,42],[[13,[0,[0,7]]],[[42,[[0,[54,55]],36,[0,[0,7]]]]]],[[],[[42,[0]]]],[[],42],0,0],"p":[[4,"TryReserveError"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"TypeId"],[8,"Clone"],[3,"HashMap"],[4,"RawEntryMut"],[4,"Entry"],[8,"Sized"],[4,"EntryRef"],[15,"usize"],[3,"Iter"],[3,"Keys"],[3,"Values"],[3,"Drain"],[3,"DrainFilter"],[8,"IntoIterator"],[8,"Debug"],[3,"IterMut"],[3,"IntoIter"],[3,"IntoKeys"],[3,"IntoValues"],[3,"ValuesMut"],[3,"RawEntryBuilderMut"],[3,"RawOccupiedEntryMut"],[3,"RawVacantEntryMut"],[3,"RawEntryBuilder"],[3,"OccupiedEntry"],[3,"VacantEntry"],[8,"Borrow"],[3,"OccupiedEntryRef"],[3,"VacantEntryRef"],[3,"OccupiedError"],[6,"DefaultHashBuilder"],[15,"u64"],[4,"Option"],[8,"Default"],[8,"FnOnce"],[3,"String"],[3,"HashSet"],[3,"Iter"],[3,"Intersection"],[3,"Difference"],[3,"SymmetricDifference"],[3,"Union"],[3,"Drain"],[3,"DrainFilter"],[4,"Entry"],[3,"IntoIter"],[3,"OccupiedEntry"],[3,"VacantEntry"],[8,"Hash"],[8,"Eq"],[13,"AllocError"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"]]},\ +"heck":{"doc":"heck is a case conversion library.","t":[12,12,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,10,8,8,8,8,8,8,8,8,8,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","0","0","0","0","0","0","0","0","0","AsKebabCase","AsLowerCamelCase","AsPascalCase","AsShoutyKebabCase","AsShoutySnakeCase","AsShoutySnekCase","AsSnakeCase","AsSnekCase","AsTitleCase","AsUpperCamelCase","TO_SHOUTY_SNEK_CASE","ToKebabCase","ToLowerCamelCase","ToPascalCase","ToShoutyKebabCase","ToShoutySnakeCase","ToShoutySnekCase","ToSnakeCase","ToSnekCase","ToTitleCase","ToUpperCamelCase","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","into","into","into","into","into","into","into","to_kebab_case","to_lower_camel_case","to_pascal_case","to_shouty_kebab_case","to_shouty_snake_case","to_snake_case","to_snek_case","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_title_case","to_upper_camel_case","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id"],"q":["heck","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","This wrapper performs a kebab case conversion in …","This wrapper performs a lower camel case conversion in …","This wrapper performs a upper camel case conversion in …","This wrapper performs a kebab case conversion in …","This wrapper performs a shouty snake case conversion in …","This wrapper performs a shouty snake case conversion in …","This wrapper performs a snake case conversion in …","This wrapper performs a snake case conversion in …","This wrapper performs a title case conversion in …","This wrapper performs a upper camel case conversion in …","CONVERT THIS TYPE TO SNEK CASE.","This trait defines a kebab case conversion.","This trait defines a lower camel case conversion.","ToPascalCase is an alias for ToUpperCamelCase. See …","This trait defines a shouty kebab case conversion.","This trait defines a shouty snake case conversion.","Oh heck, ToShoutySnekCase is an alias for …","This trait defines a snake case conversion.","Oh heck, SnekCase is an alias for ToSnakeCase. See …","This trait defines a title case conversion.","This trait defines an upper camel case conversion.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this type to kebab case.","Convert this type to lower camel case.","Convert this type to upper camel case.","Convert this type to shouty kebab case.","Convert this type to shouty snake case.","Convert this type to snake case.","Convert this type to snek case.","","","","","","","","Convert this type to title case.","Convert this type to upper camel case.","","","","","","","","","","","","","","","","","","","","",""],"i":[3,6,7,8,8,9,9,10,11,11,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,16,17,18,19,20,21,22,3,6,7,8,9,10,11,23,24,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[3,[[2,[1]]]],4],5],[[[6,[[2,[1]]]],4],5],[[[7,[[2,[1]]]],4],5],[[[8,[[2,[1]]]],4],5],[[[9,[[2,[1]]]],4],5],[[[10,[[2,[1]]]],4],5],[[[11,[[2,[1]]]],4],5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14]],"p":[[15,"str"],[8,"AsRef"],[3,"AsKebabCase"],[3,"Formatter"],[6,"Result"],[3,"AsLowerCamelCase"],[3,"AsShoutyKebabCase"],[3,"AsShoutySnakeCase"],[3,"AsSnakeCase"],[3,"AsTitleCase"],[3,"AsUpperCamelCase"],[3,"String"],[4,"Result"],[3,"TypeId"],[8,"ToShoutySnekCase"],[8,"ToKebabCase"],[8,"ToLowerCamelCase"],[8,"ToPascalCase"],[8,"ToShoutyKebabCase"],[8,"ToShoutySnakeCase"],[8,"ToSnakeCase"],[8,"ToSnekCase"],[8,"ToTitleCase"],[8,"ToUpperCamelCase"]]},\ +"hex":{"doc":"Encoding and decoding hex strings.","t":[16,8,4,13,13,13,8,11,11,11,11,5,5,5,10,10,5,5,11,11,11,11,10,11,11,11,11,11,11,11,12,12],"n":["Error","FromHex","FromHexError","InvalidHexCharacter","InvalidStringLength","OddLength","ToHex","borrow","borrow_mut","clone","clone_into","decode","decode_to_slice","encode","encode_hex","encode_hex_upper","encode_to_slice","encode_upper","eq","fmt","fmt","from","from_hex","into","provide","to_owned","to_string","try_from","try_into","type_id","c","index"],"q":["hex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hex::FromHexError",""],"d":["","Types that can be decoded from a hex string.","The error type for decoding a hex string into Vec<u8> or …","An invalid character was found. Valid ones are: 0...9, …","If the hex string is decoded into a fixed sized container, …","A hex string’s length needs to be even, as two digits …","Encoding values as hex string.","","","","","Decodes a hex string into raw bytes.","Decode a hex string into a mutable bytes slice.","Encodes data as hex string using lowercase characters.","Encode the hex strict representing self into the result. …","Encode the hex strict representing self into the result. …","Encodes some bytes into a mutable slice of bytes.","Encodes data as hex string using uppercase characters.","","","","Returns the argument unchanged.","Creates an instance of type Self from the given hex …","Calls U::from(self).","","","","","","","",""],"i":[14,0,0,1,1,1,0,1,1,1,1,0,0,0,15,15,0,0,1,1,1,1,14,1,1,1,1,1,1,1,16,16],"f":[0,0,0,0,0,0,0,[[]],[[]],[1,1],[[]],[2,[[5,[[4,[3]],1]]]],[2,[[5,[1]]]],[2,6],[[],[[8,[7]]]],[[],[[8,[7]]]],[2,[[5,[1]]]],[2,6],[[1,1],9],[[1,10],11],[[1,10],11],[[]],[2,5],[[]],[12],[[]],[[],6],[[],5],[[],5],[[],13],0,0],"p":[[4,"FromHexError"],[8,"AsRef"],[15,"u8"],[3,"Vec"],[4,"Result"],[3,"String"],[15,"char"],[8,"FromIterator"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"TypeId"],[8,"FromHex"],[8,"ToHex"],[13,"InvalidHexCharacter"]]},\ +"impl_rlp":{"doc":"RLP serialization support for uint and fixed hash.","t":[14,14],"n":["impl_fixed_hash_rlp","impl_uint_rlp"],"q":["impl_rlp",""],"d":["Add RLP serialization support to a fixed-sized hash type …","Add RLP serialization support to an integer created by …"],"i":[0,0],"f":[0,0],"p":[]},\ +"keccak":{"doc":"Keccak sponge function.","t":[18,8,5,5,5,5,5,10,10,14,14],"n":["KECCAK_F_ROUND_COUNT","LaneSize","f1600","f200","f400","f800","keccak_p","rotate_left","truncate_rc","unroll24","unroll5"],"q":["keccak","","","","","","","","","",""],"d":["","","Keccak-f sponge function","Keccak-f sponge function","Keccak-f sponge function","Keccak-f sponge function","Generic Keccak-p sponge function","","","",""],"i":[4,0,0,0,0,0,0,4,4,0,0],"f":[0,0,[[]],[[]],[[]],[[]],[1],[2],[3],0,0],"p":[[15,"usize"],[15,"u32"],[15,"u64"],[8,"LaneSize"]]},\ +"libc":{"doc":"libc - Raw FFI bindings to platforms’ system libraries","t":[17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,6,6,5,6,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,5,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,4,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,5,17,5,17,17,17,4,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,13,13,13,13,13,13,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,5,5,5,5,5,17,17,17,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,17,17,17,17,17,12,3,3,3,3,5,12,12,12,12,12,12,12,6,6,12,12,12,12,12,12,6,6,12,12,5,5,5,5,5,12,12,5,5,5,5,5,12,12,12,12,3,5,12,12,12,12,12,12,12,12,12,12,5,5,12,5,12,12,12,5,12,5,12,5,5,3,5,12,12,12,12,12,5,5,5,3,12,5,5,5,12,12,6,3,3,3,5,5,5,5,5,5,5,12,12,12,12,5,12,6,6,12,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,5,5,12,12,12,12,12,12,6,6,6,12,6,12,12,6,6,12,12,6,6,6,6,6,6,6,4,12,5,12,6,5,5,5,5,5,5,12,5,5,5,5,5,12,12,5,5,5,5,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,12,12,12,12,12,12,12,12,12,3,12,12,12,12,12,5,5,5,12,12,5,6,6,12,12,12,6,12,12,6,12,12,12,12,12,12,12,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,5,12,12,3,5,5,5,12,5,5,5,12,12,12,12,5,5,12,12,12,12,12,12,12,12,12,12,3,5,5,5,5,12,12,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,5,5,5,5,5,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,12,12,5,5,5,5,5,5,5,5,5,5,12,3,5,5,5,5,12,12,5,5,5,5,5,5,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,3,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,12,5,5,4,5,5,5,12,5,5,12,12,5,5,5,5,5,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,5,5,5,5,5,6,3,12,12,12,12,12,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,6,12,12,12,5,3,5,5,5,12,12,12,12,5,3,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,6,6,6,5,5,5,6,3,5,5,5,5,6,6,12,12,6,3,3,5,12,5,3,3,12,3,5,5,12,12,12,12,12,12,12,3,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,12,12,12,12,12,12,12,12,12,3,3,3,6,3,6,12,12,5,12,12,6,6,6,6,6,12,12,12,12,12,12,12,12,6,12,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,12,12,3,3,3,3,3,12,12,12,12,12,3,12,12,12,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,3,12,12,5,17,17,17,17,17,17,17,17,17,17,17,17,17,6,3,5,5,3,6,5,5,5,12,12,12,12,12,12,12,12,12,12,5,5,5,3,6,6,3,5,5,5,5,5,12,3,6,5,5,5,5,5,3,5,12,12,5,5,5,5,5,12,3,3,5,6,6,3,6,6,5,7,5,3,5,6,6,5,6,6,12,5,12,12,5,5,5,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,3,5,3,12,12,12,12,12,6,6,5,5,5,5,5,6,6,5,5,5,5,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,12,6,12,12,12,12,12,5,5,5,12,12,12,12,12,12,12,3,3,5,5,5,5,5,12,12,12,5,12,6,12,12,12,5,6,5,6,5,6,12,12,5,12,12,5,5,3,12,6,12,12,5,5,5,5,5,5,5,5,6,5,6,5,5,5,6,6,6,5,5,5,3,6,5,5,12,12,12,12,12,12,12,12,12,12,3,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,12,5,6,5,12,12,6,5,3,5,12,5,5,5,5,5,5,5,5,5,6,5,5,5,5,5,5,5,5,5,5,5,5,6,5,12,5,5,12,12,5,12,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,6,6,12,3,6,6,6,6,6,3,6,6,3,6,6,3,5,17,17,17,17,17,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,5,5,5,5,5,5,5,5,3,5,5,5,5,5,3,5,5,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,5,6,5,5,5,5,5,5,5,6,5,5,5,5,5,3,5,5,5,5,5,5,5,3,5,5,5,3,5,5,5,5,5,5,5,5,3,5,5,5,5,5,5,6,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,6,5,12,12,12,12,5,5,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,5,5,4,5,5,5,12,12,3,5,5,12,12,5,5,5,5,5,5,5,5,5,5,5,5,5,3,5,5,3,6,12,5,5,5,5,5,5,5,12,12,12,12,12,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,3,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,6,3,3,3,3,3,12,12,12,12,12,12,12,3,12,6,12,12,12,12,6,6,12,12,12,12,12,5,12,12,12,12,12,5,5,5,3,12,5,12,12,12,12,12,12,12,12,12,5,5,3,3,12,12,5,12,5,12,12,12,12,12,5,12,12,12,12,12,5,6,5,5,5,3,5,5,3,5,19,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,12,12,12,12,12,12,12,12,5,12,12,5,5,6,5,5,5,3,5,11,12,12,12,11,12,12,11,12,11,12,11,3,5,5,5,5,5,12,12,12,12,3,5,6,3,5,5,5,5,6,3,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,12,12,5,12,12,12,12,12,5,3,3,3,3,3,3,3,3,3,5,5,6,12,12,6,5,5,5,12,12,12,12,12,12,5,6,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,5,3,5,12,3,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,12,6,12,12,12,12,5,5,5,5,5,5,5,5,5,4,4,6,5,5,12,5,12,12,12,12,12,5,6,5,5,6,6,5,6,5,3,6,6,5,5,6,5,5,5,5,5,5,5,5,5,3,12,6,6,3,6,6,3,6,6,3,6,6,12,3,6,6,3,6,6,6,12,12,3,6,6,5,6,6,3,6,6,6,12,6,5,5,6,3,6,6,3,6,6,6,3,6,6,6,12,3,6,6,12,5,12,12,6,3,5,5,12,3,3,3,3,4,3,12,12,12,12,12,12,12,12,12,12,12,5,5,3,12,12,12,12,12,5,12,5,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,3,12,12,12,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,6,5,12,12,12,5,12,12,12,12,12,12,12,3,5,5,5,3,5,3,12,6,12,12,12,12,12,12,12,12,3,12,12,12,6,5,6,6,6,7,6,3,6,3,3,6,6,6,6,12,12,12,12,3,3,3,3,6,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,5,5,5,6,5,5,3,12,12,5,5,5,12,12,12,12,12,12,12,12,12,3,3,12,12],"n":["ABDAY_1","ABDAY_2","ABDAY_3","ABDAY_4","ABDAY_5","ABDAY_6","ABDAY_7","ABMON_1","ABMON_10","ABMON_11","ABMON_12","ABMON_2","ABMON_3","ABMON_4","ABMON_5","ABMON_6","ABMON_7","ABMON_8","ABMON_9","ACCOUNTING","AF_APPLETALK","AF_CCITT","AF_CHAOS","AF_CNT","AF_COIP","AF_DATAKIT","AF_DECnet","AF_DLI","AF_E164","AF_ECMA","AF_HYLINK","AF_IMPLINK","AF_INET","AF_INET6","AF_IPX","AF_ISDN","AF_ISO","AF_LAT","AF_LINK","AF_LOCAL","AF_NATM","AF_NDRV","AF_NETBIOS","AF_NS","AF_OSI","AF_PPP","AF_PUP","AF_ROUTE","AF_SIP","AF_SNA","AF_SYSTEM","AF_SYS_CONTROL","AF_UNIX","AF_UNSPEC","AIO_ALLDONE","AIO_CANCELED","AIO_LISTIO_MAX","AIO_NOTCANCELED","AI_ADDRCONFIG","AI_ALL","AI_CANONNAME","AI_DEFAULT","AI_MASK","AI_NUMERICHOST","AI_NUMERICSERV","AI_PASSIVE","AI_UNUSABLE","AI_V4MAPPED","AI_V4MAPPED_CFG","ALTWERASE","ALT_DIGITS","AM_STR","ARPOP_REPLY","ARPOP_REQUEST","ATF_COM","ATF_PERM","ATF_PUBL","ATF_USETRAILERS","ATTR_BIT_MAP_COUNT","ATTR_CMNEXT_CLONEID","ATTR_CMNEXT_EXT_FLAGS","ATTR_CMNEXT_LINKID","ATTR_CMNEXT_NOFIRMLINKPATH","ATTR_CMNEXT_PRIVATESIZE","ATTR_CMNEXT_REALDEVID","ATTR_CMNEXT_REALFSID","ATTR_CMNEXT_RECURSIVE_GENCOUNT","ATTR_CMNEXT_RELPATH","ATTR_CMN_ACCESSMASK","ATTR_CMN_ACCTIME","ATTR_CMN_ADDEDTIME","ATTR_CMN_BKUPTIME","ATTR_CMN_CHGTIME","ATTR_CMN_CRTIME","ATTR_CMN_DATA_PROTECT_FLAGS","ATTR_CMN_DEVID","ATTR_CMN_DOCUMENT_ID","ATTR_CMN_EXTENDED_SECURITY","ATTR_CMN_FILEID","ATTR_CMN_FLAGS","ATTR_CMN_FNDRINFO","ATTR_CMN_FSID","ATTR_CMN_FULLPATH","ATTR_CMN_GEN_COUNT","ATTR_CMN_GRPID","ATTR_CMN_GRPUUID","ATTR_CMN_MODTIME","ATTR_CMN_NAME","ATTR_CMN_OBJID","ATTR_CMN_OBJPERMANENTID","ATTR_CMN_OBJTAG","ATTR_CMN_OBJTYPE","ATTR_CMN_OWNERID","ATTR_CMN_PARENTID","ATTR_CMN_PAROBJID","ATTR_CMN_RETURNED_ATTRS","ATTR_CMN_SCRIPT","ATTR_CMN_USERACCESS","ATTR_CMN_UUID","ATTR_DIR_ALLOCSIZE","ATTR_DIR_DATALENGTH","ATTR_DIR_ENTRYCOUNT","ATTR_DIR_IOBLOCKSIZE","ATTR_DIR_LINKCOUNT","ATTR_DIR_MOUNTSTATUS","ATTR_FILE_ALLOCSIZE","ATTR_FILE_DATAALLOCSIZE","ATTR_FILE_DATALENGTH","ATTR_FILE_DEVTYPE","ATTR_FILE_FORKCOUNT","ATTR_FILE_FORKLIST","ATTR_FILE_IOBLOCKSIZE","ATTR_FILE_LINKCOUNT","ATTR_FILE_RSRCALLOCSIZE","ATTR_FILE_RSRCLENGTH","ATTR_FILE_TOTALSIZE","ATTR_VOL_ALLOCATIONCLUMP","ATTR_VOL_ATTRIBUTES","ATTR_VOL_CAPABILITIES","ATTR_VOL_DIRCOUNT","ATTR_VOL_ENCODINGSUSED","ATTR_VOL_FILECOUNT","ATTR_VOL_FSTYPE","ATTR_VOL_INFO","ATTR_VOL_IOBLOCKSIZE","ATTR_VOL_MAXOBJCOUNT","ATTR_VOL_MINALLOCATION","ATTR_VOL_MOUNTEDDEVICE","ATTR_VOL_MOUNTFLAGS","ATTR_VOL_MOUNTPOINT","ATTR_VOL_NAME","ATTR_VOL_OBJCOUNT","ATTR_VOL_QUOTA_SIZE","ATTR_VOL_RESERVED_SIZE","ATTR_VOL_SIGNATURE","ATTR_VOL_SIZE","ATTR_VOL_SPACEAVAIL","ATTR_VOL_SPACEFREE","ATTR_VOL_SPACEUSED","ATTR_VOL_UUID","AT_EACCESS","AT_FDCWD","AT_REMOVEDIR","AT_SYMLINK_FOLLOW","AT_SYMLINK_NOFOLLOW","B0","B110","B115200","B1200","B134","B14400","B150","B1800","B19200","B200","B230400","B2400","B28800","B300","B38400","B4800","B50","B57600","B600","B7200","B75","B76800","B9600","BIOCFLUSH","BIOCGBLEN","BIOCGDLT","BIOCGDLTLIST","BIOCGETIF","BIOCGHDRCMPLT","BIOCGRSIG","BIOCGRTIMEOUT","BIOCGSEESENT","BIOCGSTATS","BIOCIMMEDIATE","BIOCPROMISC","BIOCSBLEN","BIOCSDLT","BIOCSETF","BIOCSETFNR","BIOCSETIF","BIOCSHDRCMPLT","BIOCSRSIG","BIOCSRTIMEOUT","BIOCSSEESENT","BIOCVERSION","BOOT_TIME","BPF_ALIGNMENT","BRKINT","BS0","BS1","BSDLY","BUFSIZ","BUS_ADRALN","BUS_ADRERR","BUS_OBJERR","CCCryptorStatus","CCRNGStatus","CCRandomGenerateBytes","CCStatus","CIGNORE","CLD_CONTINUED","CLD_DUMPED","CLD_EXITED","CLD_KILLED","CLD_STOPPED","CLD_TRAPPED","CLOCAL","CLOCK_MONOTONIC","CLOCK_MONOTONIC_RAW","CLOCK_MONOTONIC_RAW_APPROX","CLOCK_PROCESS_CPUTIME_ID","CLOCK_REALTIME","CLOCK_THREAD_CPUTIME_ID","CLOCK_UPTIME_RAW","CLOCK_UPTIME_RAW_APPROX","CMSG_DATA","CMSG_FIRSTHDR","CMSG_LEN","CMSG_NXTHDR","CMSG_SPACE","CODESET","CONNECT_DATA_AUTHENTICATED","CONNECT_DATA_IDEMPOTENT","CONNECT_RESUME_ON_READ_WRITE","COPYFILE_ACL","COPYFILE_CHECK","COPYFILE_CLONE","COPYFILE_CLONE_FORCE","COPYFILE_CONTINUE","COPYFILE_COPY_DATA","COPYFILE_COPY_XATTR","COPYFILE_DATA","COPYFILE_DATA_SPARSE","COPYFILE_ERR","COPYFILE_EXCL","COPYFILE_FINISH","COPYFILE_METADATA","COPYFILE_MOVE","COPYFILE_NOFOLLOW","COPYFILE_NOFOLLOW_DST","COPYFILE_NOFOLLOW_SRC","COPYFILE_PACK","COPYFILE_PRESERVE_DST_TRACKED","COPYFILE_PROGRESS","COPYFILE_QUIT","COPYFILE_RECURSE_DIR","COPYFILE_RECURSE_DIR_CLEANUP","COPYFILE_RECURSE_ERROR","COPYFILE_RECURSE_FILE","COPYFILE_RECURSIVE","COPYFILE_RUN_IN_PLACE","COPYFILE_SECURITY","COPYFILE_SKIP","COPYFILE_START","COPYFILE_STAT","COPYFILE_UNLINK","COPYFILE_UNPACK","COPYFILE_VERBOSE","COPYFILE_XATTR","CPU_STATE_IDLE","CPU_STATE_MAX","CPU_STATE_NICE","CPU_STATE_SYSTEM","CPU_STATE_USER","CR0","CR1","CR2","CR3","CRDLY","CREAD","CRNCYSTR","CRTSCTS","CS5","CS6","CS7","CS8","CSIZE","CSTOPB","CTLFLAG_ANYBODY","CTLFLAG_KERN","CTLFLAG_LOCKED","CTLFLAG_MASKED","CTLFLAG_NOAUTO","CTLFLAG_NOLOCK","CTLFLAG_OID2","CTLFLAG_RD","CTLFLAG_RW","CTLFLAG_SECURE","CTLFLAG_WR","CTLTYPE","CTLTYPE_INT","CTLTYPE_NODE","CTLTYPE_OPAQUE","CTLTYPE_QUAD","CTLTYPE_STRING","CTLTYPE_STRUCT","CTL_DEBUG","CTL_DEBUG_MAXID","CTL_DEBUG_NAME","CTL_DEBUG_VALUE","CTL_HW","CTL_KERN","CTL_MACHDEP","CTL_MAXID","CTL_NET","CTL_UNSPEC","CTL_USER","CTL_VFS","CTL_VM","DAY_1","DAY_2","DAY_3","DAY_4","DAY_5","DAY_6","DAY_7","DEAD_PROCESS","DIR","DIR_MNTSTATUS_MNTPOINT","DLT_ARCNET","DLT_ATM_RFC1483","DLT_AX25","DLT_CHAOS","DLT_EN10MB","DLT_EN3MB","DLT_FDDI","DLT_IEEE802","DLT_LOOP","DLT_NULL","DLT_PPP","DLT_PRONET","DLT_RAW","DLT_SLIP","DT_BLK","DT_CHR","DT_DIR","DT_FIFO","DT_LNK","DT_REG","DT_SOCK","DT_UNKNOWN","D_FMT","D_MD_ORDER","D_T_FMT","Dl_info","E2BIG","EACCES","EADDRINUSE","EADDRNOTAVAIL","EAFNOSUPPORT","EAGAIN","EAI_AGAIN","EAI_BADFLAGS","EAI_FAIL","EAI_FAMILY","EAI_MEMORY","EAI_NODATA","EAI_NONAME","EAI_OVERFLOW","EAI_SERVICE","EAI_SOCKTYPE","EAI_SYSTEM","EALREADY","EAUTH","EBADARCH","EBADEXEC","EBADF","EBADMACHO","EBADMSG","EBADRPC","EBUSY","ECANCELED","ECHILD","ECHO","ECHOCTL","ECHOE","ECHOK","ECHOKE","ECHONL","ECHOPRT","ECONNABORTED","ECONNREFUSED","ECONNRESET","EDEADLK","EDESTADDRREQ","EDEVERR","EDOM","EDQUOT","EEXIST","EFAULT","EFBIG","EFTYPE","EHOSTDOWN","EHOSTUNREACH","EIDRM","EILSEQ","EINPROGRESS","EINTR","EINVAL","EIO","EISCONN","EISDIR","ELAST","ELOOP","EMFILE","EMLINK","EMPTY","EMSGSIZE","EMULTIHOP","ENAMETOOLONG","ENEEDAUTH","ENETDOWN","ENETRESET","ENETUNREACH","ENFILE","ENOATTR","ENOBUFS","ENODATA","ENODEV","ENOENT","ENOEXEC","ENOLCK","ENOLINK","ENOMEM","ENOMSG","ENOPOLICY","ENOPROTOOPT","ENOSPC","ENOSR","ENOSTR","ENOSYS","ENOTBLK","ENOTCONN","ENOTDIR","ENOTEMPTY","ENOTRECOVERABLE","ENOTSOCK","ENOTSUP","ENOTTY","ENXIO","EOF","EOPNOTSUPP","EOVERFLOW","EOWNERDEAD","EPERM","EPFNOSUPPORT","EPIPE","EPROCLIM","EPROCUNAVAIL","EPROGMISMATCH","EPROGUNAVAIL","EPROTO","EPROTONOSUPPORT","EPROTOTYPE","EPWROFF","EQFULL","ERA","ERANGE","ERA_D_FMT","ERA_D_T_FMT","ERA_T_FMT","EREMOTE","EROFS","ERPCMISMATCH","ESHLIBVERS","ESHUTDOWN","ESOCKTNOSUPPORT","ESPIPE","ESRCH","ESTALE","ETIME","ETIMEDOUT","ETOOMANYREFS","ETXTBSY","EUSERS","EVFILT_AIO","EVFILT_FS","EVFILT_MACHPORT","EVFILT_PROC","EVFILT_READ","EVFILT_SIGNAL","EVFILT_TIMER","EVFILT_USER","EVFILT_VM","EVFILT_VNODE","EVFILT_WRITE","EV_ADD","EV_CLEAR","EV_DELETE","EV_DISABLE","EV_DISPATCH","EV_ENABLE","EV_EOF","EV_ERROR","EV_FLAG0","EV_FLAG1","EV_ONESHOT","EV_OOBAND","EV_POLL","EV_RECEIPT","EV_SYSFLAGS","EWOULDBLOCK","EXDEV","EXIT_FAILURE","EXIT_SUCCESS","EXTA","EXTB","EXTPROC","FD_CLOEXEC","FD_CLR","FD_ISSET","FD_SET","FD_SETSIZE","FD_ZERO","FF0","FF1","FFDLY","FILE","FILENAME_MAX","FIOASYNC","FIOCLEX","FIODTYPE","FIOGETOWN","FIONBIO","FIONCLEX","FIONREAD","FIOSETOWN","FLUSHO","FOPEN_MAX","FSOPT_ATTR_CMN_EXTENDED","FSOPT_NOFOLLOW","FSOPT_NOFOLLOW_ANY","FSOPT_PACK_INVAL_ATTRS","FSOPT_REPORT_FULLSIZE","FSOPT_RETURN_REALDEV","F_ALLOCATEALL","F_ALLOCATECONTIG","F_BARRIERFSYNC","F_DUPFD","F_DUPFD_CLOEXEC","F_FREEZE_FS","F_FULLFSYNC","F_GETFD","F_GETFL","F_GETLK","F_GETOWN","F_GETPATH","F_GETPATH_NOFIRMLINK","F_GLOBAL_NOCACHE","F_LOCK","F_LOG2PHYS","F_LOG2PHYS_EXT","F_NOCACHE","F_NODIRECT","F_OK","F_PEOFPOSMODE","F_PREALLOCATE","F_RDADVISE","F_RDAHEAD","F_RDLCK","F_SETFD","F_SETFL","F_SETLK","F_SETLKW","F_SETOWN","F_TEST","F_THAW_FS","F_TLOCK","F_ULOCK","F_UNLCK","F_VOLPOSMODE","F_WRLCK","GETALL","GETNCNT","GETPID","GETVAL","GETZCNT","GLOB_ABORTED","GLOB_APPEND","GLOB_DOOFFS","GLOB_ERR","GLOB_MARK","GLOB_NOCHECK","GLOB_NOESCAPE","GLOB_NOMATCH","GLOB_NOSORT","GLOB_NOSPACE","GRPQUOTA","HOST_CPU_LOAD_INFO","HOST_CPU_LOAD_INFO_COUNT","HOST_EXPIRED_TASK_INFO","HOST_EXTMOD_INFO64","HOST_LOAD_INFO","HOST_VM_INFO","HOST_VM_INFO64","HOST_VM_INFO64_COUNT","HUPCL","HW_AVAILCPU","HW_BUS_FREQ","HW_BYTEORDER","HW_CACHELINE","HW_CPU_FREQ","HW_DISKNAMES","HW_DISKSTATS","HW_EPOCH","HW_FLOATINGPT","HW_L1DCACHESIZE","HW_L1ICACHESIZE","HW_L2CACHESIZE","HW_L2SETTINGS","HW_L3CACHESIZE","HW_L3SETTINGS","HW_MACHINE","HW_MACHINE_ARCH","HW_MAXID","HW_MEMSIZE","HW_MODEL","HW_NCPU","HW_PAGESIZE","HW_PHYSMEM","HW_PRODUCT","HW_TARGET","HW_TB_FREQ","HW_USERMEM","HW_VECTORUNIT","ICANON","ICRNL","IEXTEN","IFF_ALLMULTI","IFF_ALTPHYS","IFF_BROADCAST","IFF_DEBUG","IFF_LINK0","IFF_LINK1","IFF_LINK2","IFF_LOOPBACK","IFF_MULTICAST","IFF_NOARP","IFF_NOTRAILERS","IFF_OACTIVE","IFF_POINTOPOINT","IFF_PROMISC","IFF_RUNNING","IFF_SIMPLEX","IFF_UP","IFNAMSIZ","IF_NAMESIZE","IGNBRK","IGNCR","IGNPAR","IMAXBEL","INADDR_ANY","INADDR_BROADCAST","INADDR_LOOPBACK","INADDR_NONE","INIT_PROCESS","INLCR","INPCK","INT_MAX","INT_MIN","IOV_MAX","IPC_CREAT","IPC_EXCL","IPC_M","IPC_NOWAIT","IPC_PRIVATE","IPC_R","IPC_RMID","IPC_SET","IPC_STAT","IPC_W","IPPROTO_3PC","IPPROTO_ADFS","IPPROTO_AH","IPPROTO_AHIP","IPPROTO_APES","IPPROTO_ARGUS","IPPROTO_AX25","IPPROTO_BHA","IPPROTO_BLT","IPPROTO_BRSATMON","IPPROTO_CFTP","IPPROTO_CHAOS","IPPROTO_CMTP","IPPROTO_CPHB","IPPROTO_CPNX","IPPROTO_DDP","IPPROTO_DGP","IPPROTO_DIVERT","IPPROTO_DONE","IPPROTO_DSTOPTS","IPPROTO_EGP","IPPROTO_EMCON","IPPROTO_ENCAP","IPPROTO_EON","IPPROTO_ESP","IPPROTO_ETHERIP","IPPROTO_FRAGMENT","IPPROTO_GGP","IPPROTO_GMTP","IPPROTO_GRE","IPPROTO_HELLO","IPPROTO_HMP","IPPROTO_HOPOPTS","IPPROTO_ICMP","IPPROTO_ICMPV6","IPPROTO_IDP","IPPROTO_IDPR","IPPROTO_IDRP","IPPROTO_IGMP","IPPROTO_IGP","IPPROTO_IGRP","IPPROTO_IL","IPPROTO_INLSP","IPPROTO_INP","IPPROTO_IP","IPPROTO_IPCOMP","IPPROTO_IPCV","IPPROTO_IPEIP","IPPROTO_IPIP","IPPROTO_IPPC","IPPROTO_IPV6","IPPROTO_IRTP","IPPROTO_KRYPTOLAN","IPPROTO_LARP","IPPROTO_LEAF1","IPPROTO_LEAF2","IPPROTO_MAX","IPPROTO_MEAS","IPPROTO_MHRP","IPPROTO_MICP","IPPROTO_MTP","IPPROTO_MUX","IPPROTO_ND","IPPROTO_NHRP","IPPROTO_NONE","IPPROTO_NSP","IPPROTO_NVPII","IPPROTO_OSPFIGP","IPPROTO_PGM","IPPROTO_PIGP","IPPROTO_PIM","IPPROTO_PRM","IPPROTO_PUP","IPPROTO_PVP","IPPROTO_RAW","IPPROTO_RCCMON","IPPROTO_RDP","IPPROTO_ROUTING","IPPROTO_RSVP","IPPROTO_RVD","IPPROTO_SATEXPAK","IPPROTO_SATMON","IPPROTO_SCCSP","IPPROTO_SCTP","IPPROTO_SDRP","IPPROTO_SEP","IPPROTO_SRPC","IPPROTO_ST","IPPROTO_SVMTP","IPPROTO_SWIPE","IPPROTO_TCF","IPPROTO_TCP","IPPROTO_TP","IPPROTO_TPXX","IPPROTO_TRUNK1","IPPROTO_TRUNK2","IPPROTO_TTP","IPPROTO_UDP","IPPROTO_VINES","IPPROTO_VISA","IPPROTO_VMTP","IPPROTO_WBEXPAK","IPPROTO_WBMON","IPPROTO_WSN","IPPROTO_XNET","IPPROTO_XTP","IPTOS_ECN_CE","IPTOS_ECN_ECT0","IPTOS_ECN_ECT1","IPTOS_ECN_MASK","IPTOS_ECN_NOTECT","IPV6_BOUND_IF","IPV6_CHECKSUM","IPV6_DONTFRAG","IPV6_HOPLIMIT","IPV6_JOIN_GROUP","IPV6_LEAVE_GROUP","IPV6_MULTICAST_HOPS","IPV6_MULTICAST_IF","IPV6_MULTICAST_LOOP","IPV6_PKTINFO","IPV6_RECVPKTINFO","IPV6_RECVTCLASS","IPV6_TCLASS","IPV6_UNICAST_HOPS","IPV6_V6ONLY","IP_ADD_MEMBERSHIP","IP_ADD_SOURCE_MEMBERSHIP","IP_BLOCK_SOURCE","IP_BOUND_IF","IP_DONTFRAG","IP_DROP_MEMBERSHIP","IP_DROP_SOURCE_MEMBERSHIP","IP_HDRINCL","IP_MULTICAST_IF","IP_MULTICAST_LOOP","IP_MULTICAST_TTL","IP_PKTINFO","IP_RECVDSTADDR","IP_RECVIF","IP_RECVTOS","IP_TOS","IP_TTL","IP_UNBLOCK_SOURCE","ISIG","ISTRIP","ITIMER_PROF","ITIMER_REAL","ITIMER_VIRTUAL","IUTF8","IXANY","IXOFF","IXON","KERN_ABORTED","KERN_AFFINITY","KERN_AIOMAX","KERN_AIOPROCMAX","KERN_AIOTHREADS","KERN_ALREADY_IN_SET","KERN_ALREADY_WAITING","KERN_ARGMAX","KERN_BOOTFILE","KERN_BOOTTIME","KERN_CHECKOPENEVT","KERN_CLASSIC","KERN_CLASSICHANDLER","KERN_CLOCKRATE","KERN_CODESIGN_ERROR","KERN_COREDUMP","KERN_COREFILE","KERN_DEFAULT_SET","KERN_DOMAINNAME","KERN_DUMMY","KERN_DUMPDEV","KERN_EXCEPTION_PROTECTED","KERN_EXEC","KERN_FAILURE","KERN_FILE","KERN_HOSTID","KERN_HOSTNAME","KERN_INSUFFICIENT_BUFFER_SIZE","KERN_INVALID_ADDRESS","KERN_INVALID_ARGUMENT","KERN_INVALID_CAPABILITY","KERN_INVALID_HOST","KERN_INVALID_LEDGER","KERN_INVALID_MEMORY_CONTROL","KERN_INVALID_NAME","KERN_INVALID_OBJECT","KERN_INVALID_POLICY","KERN_INVALID_PROCESSOR_SET","KERN_INVALID_RIGHT","KERN_INVALID_SECURITY","KERN_INVALID_TASK","KERN_INVALID_VALUE","KERN_IPC","KERN_JOB_CONTROL","KERN_KDBUFWAIT","KERN_KDCPUMAP","KERN_KDDFLAGS","KERN_KDEBUG","KERN_KDEFLAGS","KERN_KDENABLE","KERN_KDGETBUF","KERN_KDGETENTROPY","KERN_KDGETREG","KERN_KDPIDEX","KERN_KDPIDTR","KERN_KDREADCURTHRMAP","KERN_KDREADTR","KERN_KDREMOVE","KERN_KDSETBUF","KERN_KDSETREG","KERN_KDSETRTCDEC","KERN_KDSETUP","KERN_KDSET_TYPEFILTER","KERN_KDTHRMAP","KERN_KDWRITEMAP","KERN_KDWRITETR","KERN_LOCK_OWNED","KERN_LOCK_OWNED_SELF","KERN_LOCK_SET_DESTROYED","KERN_LOCK_UNSTABLE","KERN_LOGSIGEXIT","KERN_LOW_PRI_DELAY","KERN_LOW_PRI_WINDOW","KERN_MAXFILES","KERN_MAXFILESPERPROC","KERN_MAXID","KERN_MAXPARTITIONS","KERN_MAXPROC","KERN_MAXPROCPERUID","KERN_MAXVNODES","KERN_MEMORY_DATA_MOVED","KERN_MEMORY_ERROR","KERN_MEMORY_FAILURE","KERN_MEMORY_PRESENT","KERN_MEMORY_RESTART_COPY","KERN_NAME_EXISTS","KERN_NETBOOT","KERN_NGROUPS","KERN_NISDOMAINNAME","KERN_NODE_DOWN","KERN_NOT_DEPRESSED","KERN_NOT_IN_SET","KERN_NOT_RECEIVER","KERN_NOT_SUPPORTED","KERN_NOT_WAITING","KERN_NO_ACCESS","KERN_NO_SPACE","KERN_NTP_PLL","KERN_NX_PROTECTION","KERN_OPENEVT_PROC","KERN_OPERATION_TIMED_OUT","KERN_OSRELDATE","KERN_OSRELEASE","KERN_OSREV","KERN_OSTYPE","KERN_OSVERSION","KERN_POLICY_LIMIT","KERN_POLICY_STATIC","KERN_POSIX","KERN_POSIX1","KERN_PROC","KERN_PROCARGS","KERN_PROCARGS2","KERN_PROCDELAYTERM","KERN_PROCNAME","KERN_PROC_ALL","KERN_PROC_LCID","KERN_PROC_PGRP","KERN_PROC_PID","KERN_PROC_RUID","KERN_PROC_SESSION","KERN_PROC_TTY","KERN_PROC_UID","KERN_PROF","KERN_PROTECTION_FAILURE","KERN_PS_STRINGS","KERN_RAGEVNODE","KERN_RAGE_PROC","KERN_RAGE_THREAD","KERN_RESOURCE_SHORTAGE","KERN_RIGHT_EXISTS","KERN_RPC_CONTINUE_ORPHAN","KERN_RPC_SERVER_TERMINATED","KERN_RPC_TERMINATE_ORPHAN","KERN_SAFEBOOT","KERN_SAVED_IDS","KERN_SECURELVL","KERN_SEMAPHORE_DESTROYED","KERN_SHREG_PRIVATIZABLE","KERN_SPECULATIVE_READS","KERN_SUCCESS","KERN_SUGID_COREDUMP","KERN_SYMFILE","KERN_SYSV","KERN_TERMINATED","KERN_TFP","KERN_TFP_POLICY","KERN_TFP_POLICY_DEFAULT","KERN_TFP_POLICY_DENY","KERN_THALTSTACK","KERN_THREADNAME","KERN_TRANSLATE","KERN_TTY","KERN_UNOPENEVT_PROC","KERN_UNRAGE_PROC","KERN_UNRAGE_THREAD","KERN_UPDATEINTERVAL","KERN_UREFS_OVERFLOW","KERN_USRSTACK32","KERN_USRSTACK64","KERN_VERSION","KERN_VNODE","KIPC_MAXSOCKBUF","KIPC_MAX_DATALEN","KIPC_MAX_HDR","KIPC_MAX_LINKHDR","KIPC_MAX_PROTOHDR","KIPC_MBSTAT","KIPC_NMBCLUSTERS","KIPC_SOCKBUF_WASTE","KIPC_SOMAXCONN","KIPC_SOQLIMITCOMPAT","LC_ALL","LC_ALL_MASK","LC_COLLATE","LC_COLLATE_MASK","LC_CTYPE","LC_CTYPE_MASK","LC_MESSAGES","LC_MESSAGES_MASK","LC_MONETARY","LC_MONETARY_MASK","LC_NUMERIC","LC_NUMERIC_MASK","LC_SEGMENT","LC_SEGMENT_64","LC_TIME","LC_TIME_MASK","LIO_NOP","LIO_NOWAIT","LIO_READ","LIO_WAIT","LIO_WRITE","LOCAL_PEERCRED","LOCAL_PEEREPID","LOCAL_PEEREUUID","LOCAL_PEERPID","LOCAL_PEERUUID","LOCK_EX","LOCK_NB","LOCK_SH","LOCK_UN","LOGIN_PROCESS","LOG_ALERT","LOG_AUTH","LOG_AUTHPRIV","LOG_CONS","LOG_CRIT","LOG_CRON","LOG_DAEMON","LOG_DEBUG","LOG_EMERG","LOG_ERR","LOG_FACMASK","LOG_FTP","LOG_INFO","LOG_INSTALL","LOG_KERN","LOG_LAUNCHD","LOG_LOCAL0","LOG_LOCAL1","LOG_LOCAL2","LOG_LOCAL3","LOG_LOCAL4","LOG_LOCAL5","LOG_LOCAL6","LOG_LOCAL7","LOG_LPR","LOG_MAIL","LOG_NDELAY","LOG_NETINFO","LOG_NEWS","LOG_NFACILITIES","LOG_NOTICE","LOG_NOWAIT","LOG_ODELAY","LOG_PERROR","LOG_PID","LOG_PRIMASK","LOG_RAS","LOG_REMOTEAUTH","LOG_SYSLOG","LOG_USER","LOG_UUCP","LOG_WARNING","L_tmpnam","MACH_PORT_NULL","MACH_TASK_BASIC_INFO","MACH_TASK_BASIC_INFO_COUNT","MADV_CAN_REUSE","MADV_DONTNEED","MADV_FREE","MADV_FREE_REUSABLE","MADV_FREE_REUSE","MADV_NORMAL","MADV_RANDOM","MADV_SEQUENTIAL","MADV_WILLNEED","MADV_ZERO_WIRED_PAGES","MAP_ANON","MAP_ANONYMOUS","MAP_COPY","MAP_FAILED","MAP_FILE","MAP_FIXED","MAP_HASSEMAPHORE","MAP_JIT","MAP_NOCACHE","MAP_NOEXTEND","MAP_NORESERVE","MAP_PRIVATE","MAP_RENAME","MAP_SHARED","MAXCOMLEN","MAXFREQ","MAXPATHLEN","MAXPHASE","MAXSEC","MAXTC","MAXTHREADNAMESIZE","MCL_CURRENT","MCL_FUTURE","MDMBUF","MEMORY_OBJECT_NULL","MH_MAGIC","MH_MAGIC_64","MINCORE_INCORE","MINCORE_MODIFIED","MINCORE_MODIFIED_OTHER","MINCORE_REFERENCED","MINCORE_REFERENCED_OTHER","MINSEC","MINSIGSTKSZ","MNT_ASYNC","MNT_AUTOMOUNTED","MNT_CPROTECT","MNT_DEFWRITE","MNT_DONTBROWSE","MNT_DOVOLFS","MNT_EXPORTED","MNT_FORCE","MNT_IGNORE_OWNERSHIP","MNT_JOURNALED","MNT_LOCAL","MNT_MULTILABEL","MNT_NOATIME","MNT_NOBLOCK","MNT_NODEV","MNT_NOEXEC","MNT_NOSUID","MNT_NOUSERXATTR","MNT_NOWAIT","MNT_QUARANTINE","MNT_QUOTA","MNT_RDONLY","MNT_RELOAD","MNT_ROOTFS","MNT_SNAPSHOT","MNT_SYNCHRONOUS","MNT_UNION","MNT_UPDATE","MNT_WAIT","MOD_CLKA","MOD_CLKB","MOD_ESTERROR","MOD_FREQUENCY","MOD_MAXERROR","MOD_MICRO","MOD_NANO","MOD_OFFSET","MOD_PPSMAX","MOD_STATUS","MOD_TAI","MOD_TIMECONST","MON_1","MON_10","MON_11","MON_12","MON_2","MON_3","MON_4","MON_5","MON_6","MON_7","MON_8","MON_9","MSG_CTRUNC","MSG_DONTROUTE","MSG_DONTWAIT","MSG_EOF","MSG_EOR","MSG_FLUSH","MSG_HAVEMORE","MSG_HOLD","MSG_OOB","MSG_PEEK","MSG_RCVMORE","MSG_SEND","MSG_TRUNC","MSG_WAITALL","MS_ASYNC","MS_DEACTIVATE","MS_INVALIDATE","MS_KILLPAGES","MS_SYNC","NANOSECOND","NCCS","NET_RT_DUMP","NET_RT_FLAGS","NET_RT_IFLIST","NET_RT_IFLIST2","NEW_TIME","NI_DGRAM","NI_MAXHOST","NI_MAXSERV","NI_NAMEREQD","NI_NOFQDN","NI_NUMERICHOST","NI_NUMERICSCOPE","NI_NUMERICSERV","NL0","NL1","NLDLY","NOEXPR","NOFLSH","NOKERNINFO","NOSTR","NOTE_ABSOLUTE","NOTE_ATTRIB","NOTE_BACKGROUND","NOTE_CHILD","NOTE_CRITICAL","NOTE_DELETE","NOTE_EXEC","NOTE_EXIT","NOTE_EXITSTATUS","NOTE_EXIT_CSERROR","NOTE_EXIT_DECRYPTFAIL","NOTE_EXIT_DETAIL","NOTE_EXIT_DETAIL_MASK","NOTE_EXIT_MEMORY","NOTE_EXTEND","NOTE_FFAND","NOTE_FFCOPY","NOTE_FFCTRLMASK","NOTE_FFLAGSMASK","NOTE_FFNOP","NOTE_FFOR","NOTE_FORK","NOTE_LEEWAY","NOTE_LINK","NOTE_LOWAT","NOTE_NONE","NOTE_NSECONDS","NOTE_PCTRLMASK","NOTE_PDATAMASK","NOTE_RENAME","NOTE_REVOKE","NOTE_SECONDS","NOTE_SIGNAL","NOTE_TRACK","NOTE_TRACKERR","NOTE_TRIGGER","NOTE_USECONDS","NOTE_VM_ERROR","NOTE_VM_PRESSURE","NOTE_VM_PRESSURE_SUDDEN_TERMINATE","NOTE_VM_PRESSURE_TERMINATE","NOTE_WRITE","NTP_API","OCRNL","OFDEL","OFILL","OLD_TIME","ONLCR","ONLRET","ONOCR","ONOEOT","OPOST","OS_LOG_TYPE_DEBUG","OS_LOG_TYPE_DEFAULT","OS_LOG_TYPE_ERROR","OS_LOG_TYPE_FAULT","OS_LOG_TYPE_INFO","OS_SIGNPOST_EVENT","OS_SIGNPOST_INTERVAL_BEGIN","OS_SIGNPOST_INTERVAL_END","OS_UNFAIR_LOCK_INIT","OXTABS","O_ACCMODE","O_APPEND","O_ASYNC","O_CLOEXEC","O_CREAT","O_DIRECTORY","O_DSYNC","O_EVTONLY","O_EXCL","O_EXLOCK","O_FSYNC","O_NDELAY","O_NOCTTY","O_NOFOLLOW","O_NOFOLLOW_ANY","O_NONBLOCK","O_RDONLY","O_RDWR","O_SHLOCK","O_SYMLINK","O_SYNC","O_TRUNC","O_WRONLY","PARENB","PARMRK","PARODD","PATH_MAX","PENDIN","PF_APPLETALK","PF_CCITT","PF_CHAOS","PF_CNT","PF_COIP","PF_DATAKIT","PF_DECnet","PF_DLI","PF_ECMA","PF_HYLINK","PF_IMPLINK","PF_INET","PF_INET6","PF_IPX","PF_ISDN","PF_ISO","PF_KEY","PF_LAT","PF_LINK","PF_LOCAL","PF_NATM","PF_NDRV","PF_NETBIOS","PF_NS","PF_OSI","PF_PIP","PF_PPP","PF_PUP","PF_ROUTE","PF_RTIP","PF_SIP","PF_SNA","PF_SYSTEM","PF_UNIX","PF_UNSPEC","PF_XTP","PIPE_BUF","PM_STR","POLLERR","POLLHUP","POLLIN","POLLNVAL","POLLOUT","POLLPRI","POLLRDBAND","POLLRDNORM","POLLWRBAND","POLLWRNORM","POSIX_MADV_DONTNEED","POSIX_MADV_NORMAL","POSIX_MADV_RANDOM","POSIX_MADV_SEQUENTIAL","POSIX_MADV_WILLNEED","POSIX_SPAWN_CLOEXEC_DEFAULT","POSIX_SPAWN_RESETIDS","POSIX_SPAWN_SETEXEC","POSIX_SPAWN_SETPGROUP","POSIX_SPAWN_SETSIGDEF","POSIX_SPAWN_SETSIGMASK","POSIX_SPAWN_START_SUSPENDED","PRIO_DARWIN_BG","PRIO_DARWIN_NONUI","PRIO_DARWIN_PROCESS","PRIO_DARWIN_THREAD","PRIO_MAX","PRIO_MIN","PRIO_PGRP","PRIO_PROCESS","PRIO_USER","PROCESSOR_BASIC_INFO","PROCESSOR_CPU_LOAD_INFO","PROCESSOR_PM_REGS_INFO","PROCESSOR_SET_BASIC_INFO","PROCESSOR_SET_LOAD_INFO","PROCESSOR_TEMPERATURE","PROC_CSM_ALL","PROC_CSM_NOSMT","PROC_CSM_TECS","PROC_PIDPATHINFO_MAXSIZE","PROC_PIDTASKALLINFO","PROC_PIDTASKINFO","PROC_PIDTBSDINFO","PROC_PIDTHREADINFO","PROC_PIDVNODEPATHINFO","PROT_EXEC","PROT_NONE","PROT_READ","PROT_WRITE","PTHREAD_COND_INITIALIZER","PTHREAD_CREATE_DETACHED","PTHREAD_CREATE_JOINABLE","PTHREAD_INTROSPECTION_THREAD_CREATE","PTHREAD_INTROSPECTION_THREAD_DESTROY","PTHREAD_INTROSPECTION_THREAD_START","PTHREAD_INTROSPECTION_THREAD_TERMINATE","PTHREAD_MUTEX_DEFAULT","PTHREAD_MUTEX_ERRORCHECK","PTHREAD_MUTEX_INITIALIZER","PTHREAD_MUTEX_NORMAL","PTHREAD_MUTEX_RECURSIVE","PTHREAD_PROCESS_PRIVATE","PTHREAD_PROCESS_SHARED","PTHREAD_RWLOCK_INITIALIZER","PTHREAD_STACK_MIN","PT_ATTACH","PT_ATTACHEXC","PT_CONTINUE","PT_DENY_ATTACH","PT_DETACH","PT_FIRSTMACH","PT_FORCEQUOTA","PT_KILL","PT_READ_D","PT_READ_I","PT_READ_U","PT_SIGEXC","PT_STEP","PT_THUPDATE","PT_TRACE_ME","PT_WRITE_D","PT_WRITE_I","PT_WRITE_U","P_ALL","P_PGID","P_PID","QCMD","QOS_CLASS_BACKGROUND","QOS_CLASS_DEFAULT","QOS_CLASS_UNSPECIFIED","QOS_CLASS_USER_INITIATED","QOS_CLASS_USER_INTERACTIVE","QOS_CLASS_UTILITY","Q_GETQUOTA","Q_QUOTAOFF","Q_QUOTAON","Q_SETQUOTA","Q_SYNC","RADIXCHAR","RAND_MAX","REG_ASSERT","REG_ATOI","REG_BACKR","REG_BADBR","REG_BADPAT","REG_BADRPT","REG_BASIC","REG_DUMP","REG_EBRACE","REG_EBRACK","REG_ECOLLATE","REG_ECTYPE","REG_EESCAPE","REG_EMPTY","REG_EPAREN","REG_ERANGE","REG_ESPACE","REG_ESUBREG","REG_EXTENDED","REG_ICASE","REG_INVARG","REG_ITOA","REG_LARGE","REG_NEWLINE","REG_NOMATCH","REG_NOSPEC","REG_NOSUB","REG_NOTBOL","REG_NOTEOL","REG_PEND","REG_STARTEND","REG_TRACE","RENAME_EXCL","RENAME_SWAP","RLIMIT_AS","RLIMIT_CORE","RLIMIT_CPU","RLIMIT_DATA","RLIMIT_FSIZE","RLIMIT_MEMLOCK","RLIMIT_NOFILE","RLIMIT_NPROC","RLIMIT_RSS","RLIMIT_STACK","RLIM_INFINITY","RLIM_NLIMITS","RTAX_AUTHOR","RTAX_BRD","RTAX_DST","RTAX_GATEWAY","RTAX_GENMASK","RTAX_IFA","RTAX_IFP","RTAX_MAX","RTAX_NETMASK","RTA_AUTHOR","RTA_BRD","RTA_DST","RTA_GATEWAY","RTA_GENMASK","RTA_IFA","RTA_IFP","RTA_NETMASK","RTF_BLACKHOLE","RTF_BROADCAST","RTF_CLONING","RTF_CONDEMNED","RTF_DEAD","RTF_DELCLONE","RTF_DONE","RTF_DYNAMIC","RTF_GATEWAY","RTF_GLOBAL","RTF_HOST","RTF_IFREF","RTF_IFSCOPE","RTF_LLINFO","RTF_LOCAL","RTF_MODIFIED","RTF_MULTICAST","RTF_NOIFREF","RTF_PINNED","RTF_PRCLONING","RTF_PROTO1","RTF_PROTO2","RTF_PROTO3","RTF_PROXY","RTF_REJECT","RTF_ROUTER","RTF_STATIC","RTF_UP","RTF_WASCLONED","RTF_XRESOLVE","RTLD_DEFAULT","RTLD_FIRST","RTLD_GLOBAL","RTLD_LAZY","RTLD_LOCAL","RTLD_NEXT","RTLD_NODELETE","RTLD_NOLOAD","RTLD_NOW","RTLD_SELF","RTM_ADD","RTM_CHANGE","RTM_DELADDR","RTM_DELETE","RTM_DELMADDR","RTM_GET","RTM_GET2","RTM_IFINFO","RTM_IFINFO2","RTM_LOCK","RTM_LOSING","RTM_MISS","RTM_NEWADDR","RTM_NEWMADDR","RTM_NEWMADDR2","RTM_OLDADD","RTM_OLDDEL","RTM_REDIRECT","RTM_RESOLVE","RTM_VERSION","RTV_EXPIRE","RTV_HOPCOUNT","RTV_MTU","RTV_RPIPE","RTV_RTT","RTV_RTTVAR","RTV_SPIPE","RTV_SSTHRESH","RUN_LVL","RUSAGE_CHILDREN","RUSAGE_INFO_V0","RUSAGE_INFO_V1","RUSAGE_INFO_V2","RUSAGE_INFO_V3","RUSAGE_INFO_V4","RUSAGE_SELF","R_OK","SAE_ASSOCID_ALL","SAE_ASSOCID_ANY","SAE_CONNID_ALL","SAE_CONNID_ANY","SA_NOCLDSTOP","SA_NOCLDWAIT","SA_NODEFER","SA_ONSTACK","SA_RESETHAND","SA_RESTART","SA_SIGINFO","SCALE_PPM","SCHED_FIFO","SCHED_OTHER","SCHED_RR","SCM_CREDS","SCM_RIGHTS","SCM_TIMESTAMP","SEEK_CUR","SEEK_DATA","SEEK_END","SEEK_HOLE","SEEK_SET","SEM_FAILED","SEM_UNDO","SETALL","SETVAL","SF_APPEND","SF_ARCHIVED","SF_IMMUTABLE","SF_SETTABLE","SHMLBA","SHM_R","SHM_RDONLY","SHM_RND","SHM_W","SHUTDOWN_TIME","SHUT_RD","SHUT_RDWR","SHUT_WR","SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGEMT","SIGEV_NONE","SIGEV_SIGNAL","SIGEV_THREAD","SIGFPE","SIGHUP","SIGILL","SIGINFO","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGNATURE","SIGPIPE","SIGPROF","SIGQUIT","SIGSEGV","SIGSTKSZ","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIG_BLOCK","SIG_DFL","SIG_ERR","SIG_IGN","SIG_SETMASK","SIG_UNBLOCK","SIOCGIFADDR","SOCK_DGRAM","SOCK_MAXADDRLEN","SOCK_RAW","SOCK_RDM","SOCK_SEQPACKET","SOCK_STREAM","SOL_LOCAL","SOL_SOCKET","SOMAXCONN","SO_ACCEPTCONN","SO_BROADCAST","SO_DEBUG","SO_DONTROUTE","SO_DONTTRUNC","SO_ERROR","SO_KEEPALIVE","SO_LABEL","SO_LINGER","SO_LINGER_SEC","SO_NKE","SO_NOADDRERR","SO_NOSIGPIPE","SO_NOTIFYCONFLICT","SO_NP_EXTENSIONS","SO_NREAD","SO_NWRITE","SO_OOBINLINE","SO_PEERLABEL","SO_RANDOMPORT","SO_RCVBUF","SO_RCVLOWAT","SO_RCVTIMEO","SO_REUSEADDR","SO_REUSEPORT","SO_REUSESHAREUID","SO_SNDBUF","SO_SNDLOWAT","SO_SNDTIMEO","SO_TIMESTAMP","SO_TIMESTAMP_MONOTONIC","SO_TYPE","SO_USELOOPBACK","SO_WANTMORE","SO_WANTOOBFLAG","SS_DISABLE","SS_ONSTACK","STA_CLK","STA_CLOCKERR","STA_DEL","STA_FLL","STA_FREQHOLD","STA_INS","STA_MODE","STA_NANO","STA_PLL","STA_PPSERROR","STA_PPSFREQ","STA_PPSJITTER","STA_PPSSIGNAL","STA_PPSTIME","STA_PPSWANDER","STA_RONLY","STA_UNSYNC","STDERR_FILENO","STDIN_FILENO","STDOUT_FILENO","ST_NOSUID","ST_RDONLY","SUPERPAGE_NONE","SUPERPAGE_SIZE_2MB","SUPERPAGE_SIZE_ANY","SYSDIR_DIRECTORY_ADMIN_APPLICATION","SYSDIR_DIRECTORY_ALL_APPLICATIONS","SYSDIR_DIRECTORY_ALL_LIBRARIES","SYSDIR_DIRECTORY_APPLICATION","SYSDIR_DIRECTORY_APPLICATION_SUPPORT","SYSDIR_DIRECTORY_AUTOSAVED_INFORMATION","SYSDIR_DIRECTORY_CACHES","SYSDIR_DIRECTORY_CORESERVICE","SYSDIR_DIRECTORY_DEMO_APPLICATION","SYSDIR_DIRECTORY_DESKTOP","SYSDIR_DIRECTORY_DEVELOPER","SYSDIR_DIRECTORY_DEVELOPER_APPLICATION","SYSDIR_DIRECTORY_DOCUMENT","SYSDIR_DIRECTORY_DOCUMENTATION","SYSDIR_DIRECTORY_DOWNLOADS","SYSDIR_DIRECTORY_INPUT_METHODS","SYSDIR_DIRECTORY_LIBRARY","SYSDIR_DIRECTORY_MOVIES","SYSDIR_DIRECTORY_MUSIC","SYSDIR_DIRECTORY_PICTURES","SYSDIR_DIRECTORY_PREFERENCE_PANES","SYSDIR_DIRECTORY_PRINTER_DESCRIPTION","SYSDIR_DIRECTORY_SHARED_PUBLIC","SYSDIR_DIRECTORY_USER","SYSDIR_DOMAIN_MASK_ALL","SYSDIR_DOMAIN_MASK_LOCAL","SYSDIR_DOMAIN_MASK_NETWORK","SYSDIR_DOMAIN_MASK_SYSTEM","SYSDIR_DOMAIN_MASK_USER","SYSPROTO_CONTROL","SYSPROTO_EVENT","S_IEXEC","S_IFBLK","S_IFCHR","S_IFDIR","S_IFIFO","S_IFLNK","S_IFMT","S_IFREG","S_IFSOCK","S_IREAD","S_IRGRP","S_IROTH","S_IRUSR","S_IRWXG","S_IRWXO","S_IRWXU","S_ISGID","S_ISUID","S_ISVTX","S_IWGRP","S_IWOTH","S_IWRITE","S_IWUSR","S_IXGRP","S_IXOTH","S_IXUSR","TAB0","TAB1","TAB2","TAB3","TABDLY","TASK_THREAD_TIMES_INFO","TASK_THREAD_TIMES_INFO_COUNT","TCIFLUSH","TCIOFF","TCIOFLUSH","TCION","TCOFLUSH","TCOOFF","TCOON","TCP_FASTOPEN","TCP_KEEPALIVE","TCP_KEEPCNT","TCP_KEEPINTVL","TCP_MAXSEG","TCP_NODELAY","TCP_NOOPT","TCP_NOPUSH","TCSADRAIN","TCSAFLUSH","TCSANOW","THOUSEP","THREAD_AFFINITY_POLICY","THREAD_AFFINITY_POLICY_COUNT","THREAD_AFFINITY_TAG_NULL","THREAD_BACKGROUND_POLICY","THREAD_BACKGROUND_POLICY_COUNT","THREAD_BACKGROUND_POLICY_DARWIN_BG","THREAD_BASIC_INFO","THREAD_BASIC_INFO_COUNT","THREAD_EXTENDED_INFO","THREAD_EXTENDED_INFO_COUNT","THREAD_EXTENDED_POLICY","THREAD_EXTENDED_POLICY_COUNT","THREAD_IDENTIFIER_INFO","THREAD_IDENTIFIER_INFO_COUNT","THREAD_LATENCY_QOS_POLICY","THREAD_LATENCY_QOS_POLICY_COUNT","THREAD_PRECEDENCE_POLICY","THREAD_PRECEDENCE_POLICY_COUNT","THREAD_STANDARD_POLICY","THREAD_STANDARD_POLICY_COUNT","THREAD_THROUGHPUT_QOS_POLICY","THREAD_THROUGHPUT_QOS_POLICY_COUNT","THREAD_TIME_CONSTRAINT_POLICY","THREAD_TIME_CONSTRAINT_POLICY_COUNT","TH_FLAGS_GLOBAL_FORCED_IDLE","TH_FLAGS_IDLE","TH_FLAGS_SWAPPED","TH_STATE_HALTED","TH_STATE_RUNNING","TH_STATE_STOPPED","TH_STATE_UNINTERRUPTIBLE","TH_STATE_WAITING","TIME_DEL","TIME_ERROR","TIME_INS","TIME_OK","TIME_OOP","TIME_WAIT","TIOCCBRK","TIOCCDTR","TIOCCONS","TIOCDCDTIMESTAMP","TIOCDRAIN","TIOCDSIMICROCODE","TIOCEXCL","TIOCEXT","TIOCFLUSH","TIOCGDRAINWAIT","TIOCGETD","TIOCGPGRP","TIOCGWINSZ","TIOCIXOFF","TIOCIXON","TIOCMBIC","TIOCMBIS","TIOCMGDTRWAIT","TIOCMGET","TIOCMODG","TIOCMODS","TIOCMSDTRWAIT","TIOCMSET","TIOCM_CAR","TIOCM_CD","TIOCM_CTS","TIOCM_DSR","TIOCM_DTR","TIOCM_LE","TIOCM_RI","TIOCM_RNG","TIOCM_RTS","TIOCM_SR","TIOCM_ST","TIOCNOTTY","TIOCNXCL","TIOCOUTQ","TIOCPKT","TIOCPKT_DATA","TIOCPKT_DOSTOP","TIOCPKT_FLUSHREAD","TIOCPKT_FLUSHWRITE","TIOCPKT_IOCTL","TIOCPKT_NOSTOP","TIOCPKT_START","TIOCPKT_STOP","TIOCPTYGNAME","TIOCPTYGRANT","TIOCPTYUNLK","TIOCREMOTE","TIOCSBRK","TIOCSCONS","TIOCSCTTY","TIOCSDRAINWAIT","TIOCSDTR","TIOCSETD","TIOCSIG","TIOCSPGRP","TIOCSTART","TIOCSTAT","TIOCSTI","TIOCSTOP","TIOCSWINSZ","TIOCTIMESTAMP","TIOCUCNTL","TMP_MAX","TOSTOP","T_FMT","T_FMT_AMPM","UF_APPEND","UF_COMPRESSED","UF_HIDDEN","UF_IMMUTABLE","UF_NODUMP","UF_OPAQUE","UF_SETTABLE","UF_TRACKED","USER_BC_BASE_MAX","USER_BC_DIM_MAX","USER_BC_SCALE_MAX","USER_BC_STRING_MAX","USER_COLL_WEIGHTS_MAX","USER_CS_PATH","USER_EXPR_NEST_MAX","USER_LINE_MAX","USER_MAXID","USER_POSIX2_CHAR_TERM","USER_POSIX2_C_BIND","USER_POSIX2_C_DEV","USER_POSIX2_FORT_DEV","USER_POSIX2_FORT_RUN","USER_POSIX2_LOCALEDEF","USER_POSIX2_SW_DEV","USER_POSIX2_UPE","USER_POSIX2_VERSION","USER_PROCESS","USER_RE_DUP_MAX","USER_STREAM_MAX","USER_TZNAME_MAX","USRQUOTA","UTIME_NOW","UTIME_OMIT","UTUN_OPT_FLAGS","UTUN_OPT_IFNAME","VDISCARD","VDSUSP","VEOF","VEOL","VEOL2","VERASE","VINTR","VKILL","VLNEXT","VMIN","VM_FLAGS_ALIAS_MASK","VM_FLAGS_ANYWHERE","VM_FLAGS_FIXED","VM_FLAGS_NO_CACHE","VM_FLAGS_OVERWRITE","VM_FLAGS_PURGABLE","VM_FLAGS_RANDOM_ADDR","VM_FLAGS_RESILIENT_CODESIGN","VM_FLAGS_RESILIENT_MEDIA","VM_FLAGS_RETURN_4K_DATA_ADDR","VM_FLAGS_RETURN_DATA_ADDR","VM_FLAGS_SUPERPAGE_MASK","VM_FLAGS_SUPERPAGE_NONE","VM_FLAGS_SUPERPAGE_SHIFT","VM_FLAGS_SUPERPAGE_SIZE_2MB","VM_FLAGS_SUPERPAGE_SIZE_ANY","VM_FLAGS_USER_ALLOCATE","VM_FLAGS_USER_MAP","VM_FLAGS_USER_REMAP","VM_LOADAVG","VM_MACHFACTOR","VM_MAKE_TAG","VM_MAXID","VM_MEMORY_ACCELERATE","VM_MEMORY_ANALYSIS_TOOL","VM_MEMORY_APPKIT","VM_MEMORY_APPLICATION_SPECIFIC_1","VM_MEMORY_APPLICATION_SPECIFIC_16","VM_MEMORY_ASL","VM_MEMORY_ASSETSD","VM_MEMORY_ATS","VM_MEMORY_CARBON","VM_MEMORY_CGIMAGE","VM_MEMORY_COREDATA","VM_MEMORY_COREDATA_OBJECTIDS","VM_MEMORY_COREGRAPHICS","VM_MEMORY_COREGRAPHICS_BACKINGSTORES","VM_MEMORY_COREGRAPHICS_DATA","VM_MEMORY_COREGRAPHICS_FRAMEBUFFERS","VM_MEMORY_COREGRAPHICS_MISC","VM_MEMORY_COREGRAPHICS_SHARED","VM_MEMORY_COREGRAPHICS_XALLOC","VM_MEMORY_COREIMAGE","VM_MEMORY_COREPROFILE","VM_MEMORY_CORESERVICES","VM_MEMORY_COREUI","VM_MEMORY_COREUIFILE","VM_MEMORY_CORPSEINFO","VM_MEMORY_DHMM","VM_MEMORY_DYLD","VM_MEMORY_DYLD_MALLOC","VM_MEMORY_DYLIB","VM_MEMORY_FOUNDATION","VM_MEMORY_GENEALOGY","VM_MEMORY_GLSL","VM_MEMORY_GUARD","VM_MEMORY_IMAGEIO","VM_MEMORY_IOKIT","VM_MEMORY_JAVA","VM_MEMORY_JAVASCRIPT_CORE","VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR","VM_MEMORY_JAVASCRIPT_JIT_REGISTER_FILE","VM_MEMORY_LAYERKIT","VM_MEMORY_LIBDISPATCH","VM_MEMORY_MACH_MSG","VM_MEMORY_MALLOC","VM_MEMORY_MALLOC_HUGE","VM_MEMORY_MALLOC_LARGE","VM_MEMORY_MALLOC_LARGE_REUSABLE","VM_MEMORY_MALLOC_LARGE_REUSED","VM_MEMORY_MALLOC_NANO","VM_MEMORY_MALLOC_SMALL","VM_MEMORY_MALLOC_TINY","VM_MEMORY_OBJC_DISPATCHERS","VM_MEMORY_OPENCL","VM_MEMORY_OS_ALLOC_ONCE","VM_MEMORY_RAWCAMERA","VM_MEMORY_REALLOC","VM_MEMORY_SBRK","VM_MEMORY_SCENEKIT","VM_MEMORY_SHARED_PMAP","VM_MEMORY_SKYWALK","VM_MEMORY_SQLITE","VM_MEMORY_STACK","VM_MEMORY_SWIFT_METADATA","VM_MEMORY_SWIFT_RUNTIME","VM_MEMORY_TCMALLOC","VM_MEMORY_UNSHARED_PMAP","VM_MEMORY_WEBCORE_PURGEABLE_BUFFERS","VM_METER","VM_PAGE_QUERY_PAGE_COPIED","VM_PAGE_QUERY_PAGE_CS_NX","VM_PAGE_QUERY_PAGE_CS_TAINTED","VM_PAGE_QUERY_PAGE_CS_VALIDATED","VM_PAGE_QUERY_PAGE_DIRTY","VM_PAGE_QUERY_PAGE_EXTERNAL","VM_PAGE_QUERY_PAGE_FICTITIOUS","VM_PAGE_QUERY_PAGE_PAGED_OUT","VM_PAGE_QUERY_PAGE_PRESENT","VM_PAGE_QUERY_PAGE_REF","VM_PAGE_QUERY_PAGE_SPECULATIVE","VM_PROT_EXECUTE","VM_PROT_NONE","VM_PROT_READ","VM_PROT_WRITE","VM_SWAPUSAGE","VOL_CAPABILITIES_FORMAT","VOL_CAPABILITIES_INTERFACES","VOL_CAP_FMT_2TB_FILESIZE","VOL_CAP_FMT_64BIT_OBJECT_IDS","VOL_CAP_FMT_CASE_PRESERVING","VOL_CAP_FMT_CASE_SENSITIVE","VOL_CAP_FMT_DECMPFS_COMPRESSION","VOL_CAP_FMT_DIR_HARDLINKS","VOL_CAP_FMT_DOCUMENT_ID","VOL_CAP_FMT_FAST_STATFS","VOL_CAP_FMT_HARDLINKS","VOL_CAP_FMT_HIDDEN_FILES","VOL_CAP_FMT_JOURNAL","VOL_CAP_FMT_JOURNAL_ACTIVE","VOL_CAP_FMT_NO_IMMUTABLE_FILES","VOL_CAP_FMT_NO_PERMISSIONS","VOL_CAP_FMT_NO_ROOT_TIMES","VOL_CAP_FMT_NO_VOLUME_SIZES","VOL_CAP_FMT_OPENDENYMODES","VOL_CAP_FMT_PATH_FROM_ID","VOL_CAP_FMT_PERSISTENTOBJECTIDS","VOL_CAP_FMT_SEALED","VOL_CAP_FMT_SHARED_SPACE","VOL_CAP_FMT_SPARSE_FILES","VOL_CAP_FMT_SYMBOLICLINKS","VOL_CAP_FMT_VOL_GROUPS","VOL_CAP_FMT_WRITE_GENERATION_COUNT","VOL_CAP_FMT_ZERO_RUNS","VOL_CAP_INT_ADVLOCK","VOL_CAP_INT_ALLOCATE","VOL_CAP_INT_ATTRLIST","VOL_CAP_INT_CLONE","VOL_CAP_INT_COPYFILE","VOL_CAP_INT_EXCHANGEDATA","VOL_CAP_INT_EXTENDED_ATTR","VOL_CAP_INT_EXTENDED_SECURITY","VOL_CAP_INT_FLOCK","VOL_CAP_INT_MANLOCK","VOL_CAP_INT_NAMEDSTREAMS","VOL_CAP_INT_NFSEXPORT","VOL_CAP_INT_READDIRATTR","VOL_CAP_INT_RENAME_EXCL","VOL_CAP_INT_RENAME_OPENFAIL","VOL_CAP_INT_RENAME_SWAP","VOL_CAP_INT_SEARCHFS","VOL_CAP_INT_SNAPSHOT","VOL_CAP_INT_USERACCESS","VOL_CAP_INT_VOL_RENAME","VQUIT","VREPRINT","VSTART","VSTATUS","VSTOP","VSUSP","VT0","VT1","VTDLY","VTIME","VWERASE","WCONTINUED","WCOREDUMP","WEXITED","WEXITSTATUS","WIFCONTINUED","WIFEXITED","WIFSIGNALED","WIFSTOPPED","WNOHANG","WNOWAIT","WSTOPPED","WSTOPSIG","WTERMSIG","WUNTRACED","W_OK","XATTR_CREATE","XATTR_NODEFAULT","XATTR_NOFOLLOW","XATTR_NOSECURITY","XATTR_REPLACE","XATTR_SHOWCOMPRESSION","XUCRED_VERSION","X_OK","YESEXPR","YESSTR","_CS_DARWIN_USER_CACHE_DIR","_CS_DARWIN_USER_DIR","_CS_DARWIN_USER_TEMP_DIR","_CS_PATH","_IOFBF","_IOLBF","_IONBF","_NSGetEnviron","_NSGetExecutablePath","_PC_CHOWN_RESTRICTED","_PC_LINK_MAX","_PC_MAX_CANON","_PC_MAX_INPUT","_PC_NAME_MAX","_PC_NO_TRUNC","_PC_PATH_MAX","_PC_PIPE_BUF","_PC_VDISABLE","_POSIX_VDISABLE","_PTHREAD_COND_SIG_init","_PTHREAD_MUTEX_SIG_init","_PTHREAD_RWLOCK_SIG_init","_RLIMIT_POSIX_FLAG","_SC_2_CHAR_TERM","_SC_2_C_BIND","_SC_2_C_DEV","_SC_2_FORT_DEV","_SC_2_FORT_RUN","_SC_2_LOCALEDEF","_SC_2_PBS","_SC_2_PBS_ACCOUNTING","_SC_2_PBS_CHECKPOINT","_SC_2_PBS_LOCATE","_SC_2_PBS_MESSAGE","_SC_2_PBS_TRACK","_SC_2_SW_DEV","_SC_2_UPE","_SC_2_VERSION","_SC_ADVISORY_INFO","_SC_AIO_LISTIO_MAX","_SC_AIO_MAX","_SC_AIO_PRIO_DELTA_MAX","_SC_ARG_MAX","_SC_ASYNCHRONOUS_IO","_SC_ATEXIT_MAX","_SC_BARRIERS","_SC_BC_BASE_MAX","_SC_BC_DIM_MAX","_SC_BC_SCALE_MAX","_SC_BC_STRING_MAX","_SC_CHILD_MAX","_SC_CLK_TCK","_SC_CLOCK_SELECTION","_SC_COLL_WEIGHTS_MAX","_SC_CPUTIME","_SC_DELAYTIMER_MAX","_SC_EXPR_NEST_MAX","_SC_FILE_LOCKING","_SC_FSYNC","_SC_GETGR_R_SIZE_MAX","_SC_GETPW_R_SIZE_MAX","_SC_HOST_NAME_MAX","_SC_IOV_MAX","_SC_IPV6","_SC_JOB_CONTROL","_SC_LINE_MAX","_SC_LOGIN_NAME_MAX","_SC_MAPPED_FILES","_SC_MEMLOCK","_SC_MEMLOCK_RANGE","_SC_MEMORY_PROTECTION","_SC_MESSAGE_PASSING","_SC_MONOTONIC_CLOCK","_SC_MQ_OPEN_MAX","_SC_MQ_PRIO_MAX","_SC_NGROUPS_MAX","_SC_NPROCESSORS_CONF","_SC_NPROCESSORS_ONLN","_SC_OPEN_MAX","_SC_PAGESIZE","_SC_PAGE_SIZE","_SC_PASS_MAX","_SC_PHYS_PAGES","_SC_PRIORITIZED_IO","_SC_PRIORITY_SCHEDULING","_SC_RAW_SOCKETS","_SC_READER_WRITER_LOCKS","_SC_REALTIME_SIGNALS","_SC_REGEXP","_SC_RE_DUP_MAX","_SC_RTSIG_MAX","_SC_SAVED_IDS","_SC_SEMAPHORES","_SC_SEM_NSEMS_MAX","_SC_SEM_VALUE_MAX","_SC_SHARED_MEMORY_OBJECTS","_SC_SHELL","_SC_SIGQUEUE_MAX","_SC_SPAWN","_SC_SPIN_LOCKS","_SC_SPORADIC_SERVER","_SC_SS_REPL_MAX","_SC_STREAM_MAX","_SC_SYMLOOP_MAX","_SC_SYNCHRONIZED_IO","_SC_THREADS","_SC_THREAD_ATTR_STACKADDR","_SC_THREAD_ATTR_STACKSIZE","_SC_THREAD_CPUTIME","_SC_THREAD_DESTRUCTOR_ITERATIONS","_SC_THREAD_KEYS_MAX","_SC_THREAD_PRIORITY_SCHEDULING","_SC_THREAD_PRIO_INHERIT","_SC_THREAD_PRIO_PROTECT","_SC_THREAD_PROCESS_SHARED","_SC_THREAD_SAFE_FUNCTIONS","_SC_THREAD_SPORADIC_SERVER","_SC_THREAD_STACK_MIN","_SC_THREAD_THREADS_MAX","_SC_TIMEOUTS","_SC_TIMERS","_SC_TIMER_MAX","_SC_TRACE","_SC_TRACE_EVENT_FILTER","_SC_TRACE_EVENT_NAME_MAX","_SC_TRACE_INHERIT","_SC_TRACE_LOG","_SC_TRACE_NAME_MAX","_SC_TRACE_SYS_MAX","_SC_TRACE_USER_EVENT_MAX","_SC_TTY_NAME_MAX","_SC_TYPED_MEMORY_OBJECTS","_SC_TZNAME_MAX","_SC_V6_ILP32_OFF32","_SC_V6_ILP32_OFFBIG","_SC_V6_LP64_OFF64","_SC_V6_LPBIG_OFFBIG","_SC_VERSION","_SC_XBS5_ILP32_OFF32","_SC_XBS5_ILP32_OFFBIG","_SC_XBS5_LP64_OFF64","_SC_XBS5_LPBIG_OFFBIG","_SC_XOPEN_CRYPT","_SC_XOPEN_ENH_I18N","_SC_XOPEN_LEGACY","_SC_XOPEN_REALTIME","_SC_XOPEN_REALTIME_THREADS","_SC_XOPEN_SHM","_SC_XOPEN_STREAMS","_SC_XOPEN_UNIX","_SC_XOPEN_VERSION","_SC_XOPEN_XCU_VERSION","_UTX_HOSTSIZE","_UTX_IDSIZE","_UTX_LINESIZE","_UTX_USERSIZE","_WSTATUS","_WSTOPPED","__PTHREAD_CONDATTR_SIZE__","__PTHREAD_COND_SIZE__","__PTHREAD_MUTEX_SIZE__","__PTHREAD_RWLOCKATTR_SIZE__","__PTHREAD_RWLOCK_SIZE__","__cpsr","__darwin_arm_exception_state64","__darwin_arm_neon_state64","__darwin_arm_thread_state64","__darwin_mcontext64","__error","__es","__esr","__exception","__far","__fp","__fpcr","__fpsr","__int128","__int128_t","__lr","__ns","__pad","__pc","__sp","__ss","__uint128","__uint128_t","__v","__x","_dyld_get_image_header","_dyld_get_image_name","_dyld_get_image_vmaddr_slide","_dyld_image_count","_exit","_key","_seq","abort","abs","accept","access","acct","actime","active_count","active_count","address","addrinfo","adjtime","affinity_tag","ai_addr","ai_addrlen","ai_canonname","ai_family","ai_flags","ai_next","ai_protocol","ai_socktype","aio_buf","aio_cancel","aio_error","aio_fildes","aio_fsync","aio_lio_opcode","aio_nbytes","aio_offset","aio_read","aio_reqprio","aio_return","aio_sigevent","aio_suspend","aio_write","aiocb","alarm","ar_hln","ar_hrd","ar_op","ar_pln","ar_pro","arc4random","arc4random_buf","arc4random_uniform","arphdr","array","atexit","atof","atoi","attr_dataoffset","attr_length","attrgroup_t","attribute_set_t","attrlist","attrreference_t","backtrace","backtrace_async","backtrace_from_fp","backtrace_image_offsets","backtrace_symbols","backtrace_symbols_fd","basename","bh_caplen","bh_datalen","bh_hdrlen","bh_tstamp","bind","bitmapcount","blkcnt_t","blksize_t","blocks_in_use","boolean_t","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bpf_hdr","brk","bsearch","buf","bytes_free","bytes_total","bytes_used","c_cc","c_cflag","c_char","c_double","c_float","c_iflag","c_int","c_ispeed","c_lflag","c_long","c_longlong","c_oflag","c_ospeed","c_schar","c_short","c_uchar","c_uint","c_ulong","c_ulonglong","c_ushort","c_void","calcnt","calloc","capabilities","cc_t","cfgetispeed","cfgetospeed","cfmakeraw","cfsetispeed","cfsetospeed","cfsetspeed","cgid","chdir","chflags","chmod","chown","chroot","chunks_free","chunks_used","clearerr","clock_getres","clock_gettime","clock_settime","clock_t","clockid_t","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clonefile","clonefileat","close","closedir","closelog","cmd","cmd","cmd","cmdsize","cmdsize","cmdsize","cmsg_len","cmsg_level","cmsg_type","cmsghdr","commonattr","commonattr","compressions","compressor_page_count","computation","confstr","connect","connectx","constant","constraint","copyfile","copyfile_flags_t","copyfile_state_t","cow_faults","cow_faults","cpu_subtype","cpu_subtype_t","cpu_ticks","cpu_type","cpu_type_t","cpu_usage","cpusubtype","cpusubtype","cputype","cputype","cr_groups","cr_ngroups","cr_uid","cr_version","creat","cuid","currency_symbol","d_ino","d_name","d_namlen","d_reclen","d_seekoff","d_type","data","data","decimal_point","decompressions","default_policy","denom","dev_t","difftime","dirattr","dirattr","dirent","dirfd","dirname","disconnectx","dispatch_qaddr","dladdr","dlclose","dlerror","dli_fbase","dli_fname","dli_saddr","dli_sname","dlopen","dlsym","dqb_bhardlimit","dqb_bsoftlimit","dqb_btime","dqb_curbytes","dqb_curinodes","dqb_id","dqb_ihardlimit","dqb_isoftlimit","dqb_itime","dqb_spare","dqblk","drand48","dup","dup2","duplocale","e_tdev","e_tpgid","endgrent","endpwent","endservent","endutxent","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","erand48","errcnt","esterror","esterror","events","exchangedata","execl","execle","execlp","execv","execve","execvp","exit","ext","external_page_count","f_bavail","f_bavail","f_bfree","f_bfree","f_blocks","f_blocks","f_bsize","f_bsize","f_favail","f_ffree","f_ffree","f_files","f_files","f_flag","f_flags","f_flags_ext","f_frsize","f_fsid","f_fsid","f_fssubtype","f_fstypename","f_iosize","f_mntfromname","f_mntonname","f_namemax","f_owner","f_reserved","f_type","faccessat","faults","faults","fchdir","fchflags","fchmod","fchmodat","fchown","fchownat","fclonefileat","fclose","fcntl","fcopyfile","fd","fd_set","fdopen","fdopendir","feof","ferror","fflags","fflags","fflush","fgetattrlist","fgetc","fgetpos","fgets","fgetxattr","fileattr","fileattr","fileno","fileoff","fileoff","filesize","filesize","filetype","filetype","filter","filter","flags","flags","flags","flags","flags","flags","flags","flistxattr","flock","flock","fmemopen","fmount","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fopen","fork","forkattr","forkattr","forkpty","fpathconf","fpos_t","fprintf","fputc","fputs","frac_digits","fread","free","free_count","free_count","freeaddrinfo","freeifaddrs","freelocale","fremovexattr","freopen","freq","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","fsblkcnt_t","fscanf","fseek","fseeko","fsetattrlist","fsetpos","fsetxattr","fsfilcnt_t","fsid_t","fst_bytesalloc","fst_flags","fst_length","fst_offset","fst_posmode","fstat","fstatat","fstatfs","fstatvfs","fstore_t","fsync","ftell","ftello","ftok","ftruncate","futimens","futimes","fwrite","gai_strerror","getaddrinfo","getattrlist","getattrlistat","getattrlistbulk","getchar","getchar_unlocked","getcwd","getdomainname","getdtablesize","getegid","getenv","geteuid","getfsstat","getgid","getgrent","getgrgid","getgrgid_r","getgrnam","getgrnam_r","getgrouplist","getgroups","gethostid","gethostname","gethostuuid","getifaddrs","getitimer","getline","getloadavg","getlogin","getmntinfo","getnameinfo","getopt","getpeereid","getpeername","getpgid","getpgrp","getpid","getppid","getpriority","getprogname","getprotobyname","getprotobynumber","getpwent","getpwnam","getpwnam_r","getpwuid","getpwuid_r","getrlimit","getrusage","getservbyname","getservbyport","getservent","getsid","getsockname","getsockopt","gettimeofday","getuid","getutxent","getutxid","getutxline","getxattr","gid","gid_t","gl_offs","gl_pathc","gl_pathv","glob","glob_t","globfree","gmtime","gmtime_r","gr_gid","gr_mem","gr_name","gr_passwd","grantpt","group","grouping","h_addr_list","h_addrtype","h_aliases","h_length","h_name","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hdr_cnt","headers","hits","hits","host_flavor_t","host_info64_t","host_info_t","host_processor_info","host_statistics","host_statistics64","host_t","hostent","hstrerror","iconv","iconv_close","iconv_open","iconv_t","id_t","ident","ident","idtype_t","if_data","if_data64","if_freenameindex","if_index","if_indextoname","if_msghdr","if_msghdr2","if_name","if_nameindex","if_nameindex","if_nametoindex","ifa_addr","ifa_data","ifa_dstaddr","ifa_flags","ifa_name","ifa_netmask","ifa_next","ifaddrs","ifi_addrlen","ifi_addrlen","ifi_baudrate","ifi_baudrate","ifi_collisions","ifi_collisions","ifi_hdrlen","ifi_hdrlen","ifi_hwassist","ifi_ibytes","ifi_ibytes","ifi_ierrors","ifi_ierrors","ifi_imcasts","ifi_imcasts","ifi_ipackets","ifi_ipackets","ifi_iqdrops","ifi_iqdrops","ifi_lastchange","ifi_lastchange","ifi_metric","ifi_metric","ifi_mtu","ifi_mtu","ifi_noproto","ifi_noproto","ifi_obytes","ifi_obytes","ifi_oerrors","ifi_oerrors","ifi_omcasts","ifi_omcasts","ifi_opackets","ifi_opackets","ifi_physical","ifi_physical","ifi_recvquota","ifi_recvquota","ifi_recvtiming","ifi_recvtiming","ifi_reserved1","ifi_reserved2","ifi_type","ifi_type","ifi_typelen","ifi_typelen","ifi_unused1","ifi_unused1","ifi_unused2","ifi_xmitquota","ifi_xmitquota","ifi_xmittiming","ifi_xmittiming","ifm_addrs","ifm_addrs","ifm_data","ifm_data","ifm_flags","ifm_flags","ifm_index","ifm_index","ifm_msglen","ifm_msglen","ifm_snd_drops","ifm_snd_len","ifm_snd_maxlen","ifm_timer","ifm_type","ifm_type","ifm_version","ifm_version","image_offset","importance","imr_address","imr_ifindex","imr_interface","imr_interface","imr_multiaddr","imr_multiaddr","imr_multiaddr","imr_sourceaddr","in6_addr","in6_pktinfo","in_addr","in_addr_t","in_pktinfo","in_port_t","inactive_count","inactive_count","initgroups","initprot","initprot","ino_t","int16_t","int32_t","int64_t","int8_t","int_curr_symbol","int_frac_digits","int_n_cs_precedes","int_n_sep_by_space","int_n_sign_posn","int_p_cs_precedes","int_p_sep_by_space","int_p_sign_posn","integer_t","internal_page_count","intmax_t","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","intptr_t","ioctl","iov_base","iov_len","iovec","ip_mreq","ip_mreq_source","ip_mreqn","ipc_perm","ipi6_addr","ipi6_ifindex","ipi_addr","ipi_ifindex","ipi_spec_dst","ipv6_mreq","ipv6mr_interface","ipv6mr_multiaddr","is_master","isalnum","isalpha","isatty","isblank","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","it_interval","it_value","itimerval","jitcnt","jitter","jrand48","kCCAlignmentError","kCCBufferTooSmall","kCCCallSequenceError","kCCDecodeError","kCCInvalidKey","kCCKeySizeError","kCCMemoryFailure","kCCOverflow","kCCParamError","kCCRNGFailure","kCCSuccess","kCCUnimplemented","kCCUnspecifiedError","kern_return_t","kevent","kevent","kevent64","kevent64_s","key_t","kill","killpg","kqueue","l2p_contigbytes","l2p_devoffset","l2p_flags","l_len","l_linger","l_onoff","l_pid","l_start","l_type","l_whence","labs","lchown","lcong48","lconv","ledger_array_t","ledger_t","linger","link","linkat","lio_listio","listen","listxattr","load_average","load_command","locale_t","localeconv","localeconv_l","localtime","localtime_r","lockf","log2phys","login_tty","lookups","lookups","lrand48","lseek","lstat","lutimes","mach_absolute_time","mach_factor","mach_header","mach_header_64","mach_host_self","mach_msg_type_number_t","mach_port_t","mach_task_basic_info","mach_task_basic_info_data_t","mach_task_basic_info_t","mach_task_self","mach_task_self_","mach_thread_self","mach_timebase_info","mach_timebase_info","mach_timebase_info_data_t","mach_vm_address_t","mach_vm_map","mach_vm_offset_t","mach_vm_size_t","machine","madvise","magic","magic","major","makedev","malloc","malloc_default_zone","malloc_good_size","malloc_printf","malloc_size","malloc_statistics_t","malloc_zone_calloc","malloc_zone_check","malloc_zone_free","malloc_zone_from_ptr","malloc_zone_log","malloc_zone_malloc","malloc_zone_print","malloc_zone_print_ptr_info","malloc_zone_realloc","malloc_zone_statistics","malloc_zone_t","malloc_zone_valloc","max_align_t","max_size_in_use","maxerror","maxerror","maxprot","maxprot","mcontext_t","mem_entry_name_port_t","memchr","memcmp","memcpy","memmem","memmove","memory_object_offset_t","memory_object_t","memset","memset_pattern16","memset_pattern4","memset_pattern8","memset_s","microseconds","mincore","minor","mkdir","mkdirat","mkdtemp","mkfifo","mknod","mkstemp","mkstemps","mktime","mlock","mlockall","mmap","mode","mode_t","modes","modtime","mon_decimal_point","mon_grouping","mon_thousands_sep","mount","mprotect","mrand48","msg_control","msg_controllen","msg_flags","msg_iov","msg_iovlen","msg_name","msg_namelen","msghdr","mstats","mstats","msync","munlock","munlockall","munmap","n_cs_precedes","n_sep_by_space","n_sign_posn","nanosleep","nativeattr","natural_t","ncmds","ncmds","negative_sign","newlocale","nfds_t","nice","nl_item","nl_langinfo","nlink_t","no_data","nodename","nrand48","nsects","nsects","ntp_adjtime","ntp_gettime","ntptimeval","numer","off_t","offset","offset","open","open_memstream","open_wmemstream","openat","opendir","openlog","openpty","os_log_create","os_log_t","os_log_type_enabled","os_log_type_t","os_signpost_enabled","os_signpost_id_generate","os_signpost_id_make_with_pointer","os_signpost_id_t","os_signpost_type_t","os_unfair_lock","os_unfair_lock_assert_not_owner","os_unfair_lock_assert_owner","os_unfair_lock_lock","os_unfair_lock_s","os_unfair_lock_t","os_unfair_lock_trylock","os_unfair_lock_unlock","p_aliases","p_cs_precedes","p_name","p_proto","p_sep_by_space","p_sign_posn","pageins","pageins","pageouts","pageouts","passwd","pathconf","pause","pbi_comm","pbi_flags","pbi_gid","pbi_name","pbi_nfiles","pbi_nice","pbi_pgid","pbi_pid","pbi_pjobc","pbi_ppid","pbi_rgid","pbi_ruid","pbi_start_tvsec","pbi_start_tvusec","pbi_status","pbi_svgid","pbi_svuid","pbi_uid","pbi_xstatus","pbsd","pclose","period","perror","pid_t","pipe","policy","policy","policy_t","poll","pollfd","popen","positive_sign","posix_madvise","posix_memalign","posix_openpt","posix_spawn","posix_spawn_file_actions_addclose","posix_spawn_file_actions_adddup2","posix_spawn_file_actions_addopen","posix_spawn_file_actions_destroy","posix_spawn_file_actions_init","posix_spawn_file_actions_t","posix_spawnattr_destroy","posix_spawnattr_getarchpref_np","posix_spawnattr_getflags","posix_spawnattr_getpgroup","posix_spawnattr_getsigdefault","posix_spawnattr_getsigmask","posix_spawnattr_init","posix_spawnattr_setarchpref_np","posix_spawnattr_setflags","posix_spawnattr_setpgroup","posix_spawnattr_setsigdefault","posix_spawnattr_setsigmask","posix_spawnattr_t","posix_spawnp","ppsfreq","pread","preadv","precision","preemptible","printf","priority","proc_bsdinfo","proc_kmsgbuf","proc_libversion","proc_listallpids","proc_listchildpids","proc_listpgrppids","proc_listpids","proc_name","proc_pid_rusage","proc_pidfdinfo","proc_pidfileportinfo","proc_pidinfo","proc_pidpath","proc_regionfilename","proc_set_csm","proc_set_no_smt","proc_setthread_csm","proc_setthread_no_smt","proc_taskallinfo","proc_taskinfo","proc_threadinfo","proc_vnodepathinfo","processor_basic_info","processor_basic_info_data_t","processor_basic_info_t","processor_count","processor_cpu_load_info","processor_cpu_load_info_data_t","processor_cpu_load_info_t","processor_flavor_t","processor_info_array_t","processor_info_t","processor_set_basic_info","processor_set_basic_info_data_t","processor_set_basic_info_t","processor_set_load_info","processor_set_load_info_data_t","processor_set_load_info_t","protoent","pselect","pseudo_AF_HDRCMPLT","pseudo_AF_KEY","pseudo_AF_PIP","pseudo_AF_RTIP","pseudo_AF_XTP","pth_cpu_usage","pth_cpu_usage","pth_curpri","pth_curpri","pth_flags","pth_flags","pth_maxpriority","pth_maxpriority","pth_name","pth_name","pth_policy","pth_policy","pth_priority","pth_priority","pth_run_state","pth_run_state","pth_sleep_time","pth_sleep_time","pth_system_time","pth_system_time","pth_user_time","pth_user_time","pthread_atfork","pthread_attr_destroy","pthread_attr_get_qos_class_np","pthread_attr_getschedparam","pthread_attr_init","pthread_attr_set_qos_class_np","pthread_attr_setdetachstate","pthread_attr_setschedparam","pthread_attr_setstacksize","pthread_attr_t","pthread_cancel","pthread_cond_broadcast","pthread_cond_destroy","pthread_cond_init","pthread_cond_signal","pthread_cond_t","pthread_cond_timedwait","pthread_cond_wait","pthread_condattr_destroy","pthread_condattr_getpshared","pthread_condattr_init","pthread_condattr_setpshared","pthread_condattr_t","pthread_cpu_number_np","pthread_create","pthread_create_from_mach_thread","pthread_detach","pthread_exit","pthread_from_mach_thread_np","pthread_get_qos_class_np","pthread_get_stackaddr_np","pthread_get_stacksize_np","pthread_getname_np","pthread_getschedparam","pthread_getspecific","pthread_introspection_getspecific_np","pthread_introspection_hook_install","pthread_introspection_hook_t","pthread_introspection_setspecific_np","pthread_jit_write_callback_t","pthread_jit_write_freeze_callbacks_np","pthread_jit_write_protect_np","pthread_jit_write_protect_supported_np","pthread_jit_write_with_callback_np","pthread_join","pthread_key_create","pthread_key_delete","pthread_key_t","pthread_kill","pthread_mach_thread_np","pthread_mutex_destroy","pthread_mutex_init","pthread_mutex_lock","pthread_mutex_t","pthread_mutex_trylock","pthread_mutex_unlock","pthread_mutexattr_destroy","pthread_mutexattr_getpshared","pthread_mutexattr_init","pthread_mutexattr_setpshared","pthread_mutexattr_settype","pthread_mutexattr_t","pthread_rwlock_destroy","pthread_rwlock_init","pthread_rwlock_rdlock","pthread_rwlock_t","pthread_rwlock_tryrdlock","pthread_rwlock_trywrlock","pthread_rwlock_unlock","pthread_rwlock_wrlock","pthread_rwlockattr_destroy","pthread_rwlockattr_getpshared","pthread_rwlockattr_init","pthread_rwlockattr_setpshared","pthread_rwlockattr_t","pthread_self","pthread_set_qos_class_self_np","pthread_setname_np","pthread_setschedparam","pthread_setspecific","pthread_sigmask","pthread_t","pthread_threadid_np","pti_cow_faults","pti_csw","pti_faults","pti_messages_received","pti_messages_sent","pti_numrunning","pti_pageins","pti_policy","pti_priority","pti_resident_size","pti_syscalls_mach","pti_syscalls_unix","pti_threadnum","pti_threads_system","pti_threads_user","pti_total_system","pti_total_user","pti_virtual_size","ptinfo","ptrace","ptrdiff_t","ptsname","purgeable_count","purgeable_count","purges","purges","putchar","putchar_unlocked","putenv","puts","pututxline","pvi_cdir","pvi_rdir","pw_change","pw_class","pw_dir","pw_expire","pw_gecos","pw_gid","pw_name","pw_passwd","pw_shell","pw_uid","pwrite","pwritev","qos_class_t","qsort","querylocale","quotactl","ra_count","ra_offset","radvisory","raise","rand","reactivations","reactivations","read","readdir","readdir_r","readlink","readlinkat","readv","realloc","realpath","recv","recvfrom","recvmsg","regcomp","regerror","regex_t","regexec","regfree","regmatch_t","regoff_t","release","remove","removexattr","rename","renameat","renameatx_np","renamex_np","res_init","reserved","reserved","resident_size","resident_size_max","revents","rewind","rewinddir","rfu_1","ri_billed_energy","ri_billed_system_time","ri_billed_system_time","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_pageins","ri_child_pageins","ri_child_pageins","ri_child_pageins","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_system_time","ri_child_system_time","ri_child_system_time","ri_child_system_time","ri_child_user_time","ri_child_user_time","ri_child_user_time","ri_child_user_time","ri_cpu_time_qos_background","ri_cpu_time_qos_background","ri_cpu_time_qos_default","ri_cpu_time_qos_default","ri_cpu_time_qos_legacy","ri_cpu_time_qos_legacy","ri_cpu_time_qos_maintenance","ri_cpu_time_qos_maintenance","ri_cpu_time_qos_user_initiated","ri_cpu_time_qos_user_initiated","ri_cpu_time_qos_user_interactive","ri_cpu_time_qos_user_interactive","ri_cpu_time_qos_utility","ri_cpu_time_qos_utility","ri_cycles","ri_diskio_bytesread","ri_diskio_bytesread","ri_diskio_bytesread","ri_diskio_byteswritten","ri_diskio_byteswritten","ri_diskio_byteswritten","ri_instructions","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interval_max_phys_footprint","ri_lifetime_max_phys_footprint","ri_logical_writes","ri_pageins","ri_pageins","ri_pageins","ri_pageins","ri_pageins","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_resident_size","ri_resident_size","ri_resident_size","ri_resident_size","ri_resident_size","ri_runnable_time","ri_serviced_energy","ri_serviced_system_time","ri_serviced_system_time","ri_system_time","ri_system_time","ri_system_time","ri_system_time","ri_system_time","ri_user_time","ri_user_time","ri_user_time","ri_user_time","ri_user_time","ri_uuid","ri_uuid","ri_uuid","ri_uuid","ri_uuid","ri_wired_size","ri_wired_size","ri_wired_size","ri_wired_size","ri_wired_size","rlim_cur","rlim_max","rlim_t","rlimit","rm_eo","rm_so","rmdir","ru_idrss","ru_inblock","ru_isrss","ru_ixrss","ru_majflt","ru_maxrss","ru_minflt","ru_msgrcv","ru_msgsnd","ru_nivcsw","ru_nsignals","ru_nswap","ru_nvcsw","ru_oublock","ru_stime","ru_utime","run_state","running","rusage","rusage_info_t","rusage_info_v0","rusage_info_v1","rusage_info_v2","rusage_info_v3","rusage_info_v4","s6_addr","s_addr","s_aliases","s_name","s_port","s_proto","sa_data","sa_endpoints_t","sa_family","sa_family_t","sa_flags","sa_len","sa_mask","sa_sigaction","sae_associd_t","sae_connid_t","sae_dstaddr","sae_dstaddrlen","sae_srcaddr","sae_srcaddrlen","sae_srcif","sbrk","sc_family","sc_id","sc_len","sc_reserved","sc_unit","scanf","sched_get_priority_max","sched_get_priority_min","sched_param","sched_priority","sched_yield","sdl_alen","sdl_data","sdl_family","sdl_index","sdl_len","sdl_nlen","sdl_slen","sdl_type","seconds","seed48","seekdir","segment_command","segment_command_64","segname","segname","select","sem_base","sem_close","sem_ctime","sem_flg","sem_nsems","sem_num","sem_op","sem_open","sem_otime","sem_pad1","sem_pad2","sem_pad3","sem_perm","sem_post","sem_t","sem_trywait","sem_unlink","sem_wait","sembuf","semctl","semget","semid_ds","semop","semun","send","sendfile","sendmsg","sendto","servent","setattrlist","setattrlistat","setbuf","setdomainname","setegid","setenv","seteuid","setgid","setgrent","setgroups","sethostid","sethostname","setitimer","setlocale","setlogmask","setpgid","setpriority","setprogname","setpwent","setregid","setreuid","setrlimit","setservent","setsid","setsockopt","settimeofday","setuid","setutxent","setvbuf","setxattr","sf_hdtr","shift","shm_atime","shm_cpid","shm_ctime","shm_dtime","shm_internal","shm_lpid","shm_nattch","shm_open","shm_perm","shm_segsz","shm_unlink","shmat","shmatt_t","shmctl","shmdt","shmget","shmid_ds","shutdown","si_addr","si_addr","si_code","si_errno","si_pid","si_pid","si_signo","si_status","si_status","si_uid","si_uid","si_value","sigaction","sigaction","sigaddset","sigaltstack","sigdelset","sigemptyset","sigev_notify","sigev_notify_attributes","sigev_signo","sigev_value","sigevent","sigfillset","sighandler_t","siginfo_t","sigismember","signal","sigpending","sigprocmask","sigset_t","sigval","sigwait","sin6_addr","sin6_family","sin6_flowinfo","sin6_len","sin6_port","sin6_scope_id","sin_addr","sin_addr","sin_family","sin_family","sin_len","sin_len","sin_other","sin_port","sin_port","sin_srcaddr","sin_tos","sin_zero","sival_ptr","size","size_allocated","size_in_use","size_t","sizeofcmds","sizeofcmds","sleep","sleep_time","slot_num","snd_family","snd_len","snd_name","snprintf","sockaddr","sockaddr_ctl","sockaddr_dl","sockaddr_in","sockaddr_in6","sockaddr_inarp","sockaddr_ndrv","sockaddr_storage","sockaddr_un","socket","socketpair","socklen_t","speculative_count","speculative_count","speed_t","sprintf","srand","srand48","ss_family","ss_flags","ss_len","ss_size","ss_sp","ss_sysaddr","sscanf","ssize_t","st_atime","st_atime_nsec","st_birthtime","st_birthtime_nsec","st_blksize","st_blocks","st_ctime","st_ctime_nsec","st_dev","st_flags","st_gen","st_gid","st_ino","st_lspare","st_mode","st_mtime","st_mtime_nsec","st_nlink","st_qspare","st_rdev","st_size","st_uid","stabil","stack_t","stat","stat","statfs","statfs","status","statvfs","statvfs","stbcnt","stpcpy","stpncpy","strcasecmp","strcasestr","strcat","strchr","strcmp","strcoll","strcpy","strcspn","strdup","strerror","strerror_r","strlen","strncasecmp","strncat","strncmp","strncpy","strndup","strnlen","strpbrk","strrchr","strsignal","strspn","strstr","strtod","strtof","strtok","strtok_r","strtol","strtonum","strtoul","strxfrm","sun_family","sun_len","sun_path","suseconds_t","suspend_count","suspend_count","swapins","swapouts","symlink","symlinkat","sync","syscall","sysconf","sysctl","sysctlbyname","sysctlnametomib","sysdir_get_next_search_path_enumeration","sysdir_search_path_directory_t","sysdir_search_path_domain_mask_t","sysdir_search_path_enumeration_state","sysdir_start_search_path_enumeration","syslog","sysname","system","system_time","system_time","system_time","tai","task_count","task_create","task_flavor_t","task_for_pid","task_info","task_info_t","task_inspect_t","task_set_info","task_t","task_terminate","task_thread_times_info","task_thread_times_info_data_t","task_thread_times_info_t","task_threads","tcdrain","tcflag_t","tcflow","tcflush","tcgetattr","tcgetpgrp","tcgetsid","tcsendbreak","tcsetattr","tcsetpgrp","telldir","termios","thousands_sep","thread_act_array_t","thread_act_t","thread_affinity_policy","thread_affinity_policy_data_t","thread_affinity_policy_t","thread_background_policy","thread_background_policy_data_t","thread_background_policy_t","thread_basic_info","thread_basic_info_data_t","thread_basic_info_t","thread_count","thread_extended_info","thread_extended_info_data_t","thread_extended_info_t","thread_extended_policy","thread_extended_policy_data_t","thread_extended_policy_t","thread_flavor_t","thread_handle","thread_id","thread_identifier_info","thread_identifier_info_data_t","thread_identifier_info_t","thread_info","thread_info_t","thread_inspect_t","thread_latency_qos_policy","thread_latency_qos_policy_data_t","thread_latency_qos_policy_t","thread_latency_qos_t","thread_latency_qos_tier","thread_policy_flavor_t","thread_policy_get","thread_policy_set","thread_policy_t","thread_precedence_policy","thread_precedence_policy_data_t","thread_precedence_policy_t","thread_standard_policy","thread_standard_policy_data_t","thread_standard_policy_t","thread_t","thread_throughput_qos_policy","thread_throughput_qos_policy_data_t","thread_throughput_qos_policy_t","thread_throughput_qos_t","thread_throughput_qos_tier","thread_time_constraint_policy","thread_time_constraint_policy_data_t","thread_time_constraint_policy_t","throttled_count","time","time","time_state","time_t","time_value_t","timegm","times","timeshare","timespec","timeval","timeval32","timex","timezone","tm","tm_gmtoff","tm_hour","tm_isdst","tm_mday","tm_min","tm_mon","tm_sec","tm_wday","tm_yday","tm_year","tm_zone","tmpfile","tmpnam","tms","tms_cstime","tms_cutime","tms_stime","tms_utime","tolerance","tolower","total_uncompressed_pages_in_compressor","toupper","trailers","trl_cnt","truncate","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","ttyname","ttyname_r","tv_nsec","tv_sec","tv_sec","tv_sec","tv_usec","tv_usec","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uc_link","uc_mcontext","uc_mcsize","uc_onstack","uc_sigmask","uc_stack","ucontext_t","udata","udata","uid","uid_t","uint16_t","uint32_t","uint64_t","uint8_t","uintmax_t","uintptr_t","umask","uname","ungetc","unlink","unlinkat","unlockpt","unmount","unsetenv","useconds_t","uselocale","user_time","user_time","user_time","usleep","ut_host","ut_id","ut_line","ut_pid","ut_tv","ut_type","ut_user","utimbuf","utime","utimensat","utimes","utmpx","utmpxname","utsname","uuid","uuid_t","val","valid","validattr","version","vi_fsid","vi_pad","vi_stat","vi_type","vinfo_stat","vip_path","vip_vi","virtual_size","vm_address_t","vm_deallocate","vm_inherit_t","vm_map_t","vm_offset_t","vm_page_size","vm_prot_t","vm_range_t","vm_size_t","vm_statistics","vm_statistics64","vm_statistics64_data_t","vm_statistics64_t","vm_statistics_data_t","vm_statistics_t","vmaddr","vmaddr","vmsize","vmsize","vnode_info","vnode_info_path","vol_attributes_attr_t","vol_capabilities_attr_t","vol_capabilities_set_t","volattr","volattr","vst_atime","vst_atimensec","vst_birthtime","vst_birthtimensec","vst_blksize","vst_blocks","vst_ctime","vst_ctimensec","vst_dev","vst_flags","vst_gen","vst_gid","vst_ino","vst_mode","vst_mtime","vst_mtimensec","vst_nlink","vst_qspare","vst_rdev","vst_size","vst_uid","wait","wait4","waitid","waitpid","wchar_t","wcslen","wcstombs","winsize","wire_count","wire_count","wmemchr","write","writev","ws_col","ws_row","ws_xpixel","ws_ypixel","xsu_avail","xsu_encrypted","xsu_pagesize","xsu_total","xsu_used","xsw_usage","xucred","zero_fill_count","zero_fill_count"],"q":["libc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","data includes security that replaces the TFO-cookie","data is idempotent","resume connect() on read/write","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Third Party Connect","Any distributed FS","IP6 Auth Header","any host internal protocol","any private encr. scheme","Argus","AX.25 Frames","BHA","Bulk Data Transfer","BackRoom SATNET Monitoring","CFTP","Chaos","Control Message Transport","Comp. Prot. HeartBeat","Comp. Prot. Net. Executive","Datagram Delivery","dissimilar gateway prot.","divert pseudo-protocol","last return value of *_input(), meaning “all job for …","IP6 destination option","exterior gateway protocol","EMCON","encapsulation header","ISO cnlp","IP6 Encap Sec. Payload","Ethernet IP encapsulation","IP6 fragmentation header","gateway2 (deprecated)","GMTP","General Routing Encap.","“hello” routing protocol","Host Monitoring","IP6 hop-by-hop options","","","xns idp","InterDomain Policy Routing","InterDomain Routing","group mgmt protocol","NSFNET-IGP","Cisco/GXS IGRP","IL transport protocol","Integ. Net Layer Security","Merit Internodal","","payload compression (IPComp)","Packet Core Utility","IP encapsulated in IP","for compatibility","Pluribus Packet Core","","Reliable Transaction","Kryptolan","Locus Address Resoloution","Leaf-1","Leaf-2","","DCN Measurement Subsystems","Mobile Host Routing","Mobile Int.ing control","Multicast Transport","Multiplexing","Sun net disk proto (temp.)","Next Hop Resolution","IP6 no next header","Network Services","network voice protocol","OSPFIGP","PGM","private interior gateway","Protocol Independent Mcast","Packet Radio Measurement","pup","Packet Video Protocol","raw IP packet","BBN RCC Monitoring","Reliable Data","IP6 routing header","resource reservation","Remote Virtual Disk","SATNET/Backroom EXPAK","Satnet Monitoring","Semaphore Comm. security","SCTP","Source Demand Routing","Sequential Exchange","Strite RPC protocol","Stream protocol II.","Secure VMTP","IP with encryption","TCF","","tp-4 w/ class negotiation","TP++ Transport","Trunk-1","Trunk-2","TTP","","Banyon VINES","VISA Protocol","VMTP","WIDEBAND EXPAK","WIDEBAND Monitoring","Wang Span Network","Cross Net Debugger","XTP","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","((sae_associd_t)(-1ULL))","","((sae_connid_t)(-1ULL))","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Enable/Disable TCP Fastopen on this socket","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","C __int128 (a GCC extension that’s part of many ABIs)","C __int128_t (alternate name for __int128)","","","","","","","C unsigned __int128 (a GCC extension that’s part of many …","C __uint128_t (alternate name for __uint128)","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Equivalent to C’s void type when used as a pointer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Notes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The 64-bit libc on Solaris and illumos only has readdir_r. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,42,42,42,42,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,0,0,0,0,0,157,158,158,158,159,160,160,0,0,159,157,159,159,159,157,0,0,160,159,0,0,0,0,0,86,86,0,0,0,0,0,12,110,145,104,0,0,98,50,50,50,50,50,50,50,50,48,0,0,48,0,48,48,48,0,48,0,48,0,0,0,0,88,88,88,88,88,0,0,0,0,149,0,0,0,119,119,0,0,0,0,0,0,0,0,0,0,0,152,152,152,152,0,118,0,0,102,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,149,103,103,103,67,67,0,0,0,67,0,67,67,0,0,67,67,0,0,0,0,0,0,0,0,92,0,120,0,0,0,0,0,0,0,86,0,0,0,0,0,103,103,0,0,0,0,0,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,0,0,78,79,80,78,79,80,1,1,1,0,118,121,145,145,96,0,0,0,92,96,0,0,0,110,145,136,0,135,136,0,140,76,77,76,77,75,75,75,75,0,86,70,128,128,128,128,128,128,64,123,70,145,137,51,0,0,118,121,0,0,0,0,141,0,0,0,62,62,62,62,0,0,65,65,65,65,65,65,65,65,65,65,0,0,0,0,0,72,72,0,0,0,0,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,0,92,92,93,20,0,0,0,0,0,0,0,0,64,145,61,127,61,127,61,127,61,127,61,61,127,61,127,61,127,127,61,61,127,127,127,127,127,127,61,127,127,127,0,110,145,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,64,123,0,0,0,0,0,0,118,121,0,78,79,78,79,76,77,64,123,64,76,77,78,79,123,140,0,0,0,0,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,0,0,118,121,0,0,0,0,0,0,70,0,0,110,145,0,0,0,0,0,92,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,0,0,0,0,0,0,59,59,59,59,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,86,0,49,49,49,0,0,0,0,0,11,11,11,11,0,0,70,18,18,18,18,18,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,69,69,110,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,123,0,0,0,0,36,0,0,0,36,0,0,0,33,33,33,33,33,33,33,0,143,151,143,151,143,151,143,151,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,151,151,143,151,143,151,143,151,151,143,151,143,151,66,144,66,144,66,144,66,144,66,144,144,144,144,144,66,144,66,144,0,97,46,46,45,47,45,46,47,47,0,0,0,0,0,0,110,145,0,78,79,0,0,0,0,0,70,70,70,70,70,70,70,70,0,145,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,19,19,0,0,0,0,0,85,85,84,84,84,0,17,17,136,0,0,0,0,0,0,0,0,0,0,0,0,0,24,24,0,92,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,147,147,68,22,22,68,68,68,68,0,0,0,0,0,0,0,0,0,0,0,0,138,0,0,0,0,0,0,0,0,0,110,145,0,0,0,0,0,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,76,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,92,93,78,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,139,0,0,0,0,0,0,0,0,0,0,0,0,0,86,0,92,12,70,70,70,0,0,0,3,3,3,3,3,3,3,0,0,0,0,0,0,0,70,70,70,0,122,0,76,77,70,0,0,0,0,0,0,94,40,0,78,79,0,0,0,51,0,92,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,70,27,27,70,70,110,145,110,145,0,0,0,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,73,0,96,0,0,0,140,146,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,0,0,92,96,0,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,73,0,0,0,110,145,110,145,0,0,0,0,0,109,109,32,32,32,32,32,32,32,32,32,32,0,0,0,0,0,0,60,60,0,0,0,110,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,77,118,146,146,20,0,0,72,116,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,115,116,115,116,115,116,115,116,115,116,115,116,115,116,116,114,115,116,114,115,116,116,112,113,114,115,116,116,116,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,116,116,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,15,15,0,0,38,38,0,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,140,136,0,0,0,0,0,0,0,161,89,26,26,26,26,30,0,30,0,57,30,57,57,0,0,91,91,91,91,91,0,83,83,83,83,83,0,0,0,0,105,0,81,81,81,81,81,81,81,81,139,0,0,0,0,78,79,0,124,0,124,87,124,87,87,0,124,124,124,124,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,125,125,125,125,125,125,125,0,125,125,0,0,0,0,0,0,0,0,56,56,56,56,56,56,56,56,56,56,56,56,0,0,0,0,0,0,134,134,134,134,0,0,0,0,0,0,0,0,0,0,0,31,31,31,31,31,31,63,82,63,82,63,82,82,63,82,82,82,63,23,104,102,102,0,76,77,0,140,136,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,110,145,0,0,0,0,132,58,132,58,58,83,0,0,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,92,0,0,0,0,0,92,0,0,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,39,39,0,140,146,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,111,140,146,93,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,138,0,0,0,0,0,0,0,141,141,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,145,0,93,93,0,0,0,0,95,0,0,0,0,0,0,34,34,34,34,34,34,34,34,34,34,34,0,0,0,25,25,25,25,92,0,145,0,69,69,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,14,13,14,150,13,150,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,156,156,156,156,156,156,0,64,123,86,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,111,140,146,0,133,133,133,133,133,133,133,0,0,0,0,0,0,0,117,0,149,120,122,40,107,107,107,107,0,108,108,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,79,78,79,0,0,0,0,0,118,121,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,0,0,0,0,0,0,0,0,110,145,0,0,0,21,21,21,21,74,74,74,74,74,0,0,110,145],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[3,1],[4,4],[[3,1],1],[4,4],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[5,6]],[[5,6],7],[[5,6]],0,[6],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[5,5],5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[8,9],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,7],0,[5,5],[5,7],[5,7],[5,7],[5,7],0,0,0,[5,5],[5,5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[6,6],[34,34],[3,3],[1,1],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[61,61],[62,62],[63,63],[64,64],[65,65],[66,66],[67,67],[68,68],[69,69],[70,70],[71,71],[72,72],[73,73],[74,74],[75,75],[76,76],[77,77],[78,78],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[85,85],[86,86],[87,87],[88,88],[89,89],[90,90],[91,91],[92,92],[93,93],[94,94],[95,95],[96,96],[97,97],[98,98],[99,99],[100,100],[101,101],[102,102],[103,103],[104,104],[105,105],[106,106],[107,107],[108,108],[109,109],[110,110],[111,111],[112,112],[113,113],[114,114],[115,115],[116,116],[117,117],[118,118],[119,119],[120,120],[121,121],[122,122],[123,123],[124,124],[125,125],[126,126],[127,127],[128,128],[129,129],[130,130],[131,131],[132,132],[133,133],[134,134],[135,135],[136,136],[137,137],[138,138],[139,139],[140,140],[141,141],[142,142],[143,143],[144,144],[145,145],[146,146],[147,147],[148,148],[149,149],[150,150],[151,151],[152,152],[153,153],[154,154],[155,155],[156,156],[157,157],[158,158],[159,159],[160,160],[161,161],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[11,11],7],[[12,12],7],[[13,13],7],[[14,14],7],[[15,15],7],[[16,16],7],[[17,17],7],[[18,18],7],[[19,19],7],[[20,20],7],[[21,21],7],[[22,22],7],[[23,23],7],[[24,24],7],[[25,25],7],[[26,26],7],[[27,27],7],[[30,30],7],[[31,31],7],[[32,32],7],[[33,33],7],[[6,6],7],[[34,34],7],[[3,3],7],[[1,1],7],[[35,35],7],[[36,36],7],[[37,37],7],[[38,38],7],[[39,39],7],[[40,40],7],[[162,162],7],[[45,45],7],[[46,46],7],[[47,47],7],[[48,48],7],[[49,49],7],[[50,50],7],[[51,51],7],[[52,52],7],[[53,53],7],[[54,54],7],[[55,55],7],[[56,56],7],[[57,57],7],[[58,58],7],[[59,59],7],[[60,60],7],[[61,61],7],[[62,62],7],[[63,63],7],[[64,64],7],[[65,65],7],[[66,66],7],[[67,67],7],[[68,68],7],[[69,69],7],[[70,70],7],[[71,71],7],[[72,72],7],[[73,73],7],[[74,74],7],[[75,75],7],[[76,76],7],[[77,77],7],[[78,78],7],[[79,79],7],[[80,80],7],[[81,81],7],[[82,82],7],[[83,83],7],[[84,84],7],[[85,85],7],[[86,86],7],[[87,87],7],[[88,88],7],[[89,89],7],[[90,90],7],[[91,91],7],[[92,92],7],[[93,93],7],[[94,94],7],[[95,95],7],[[96,96],7],[[97,97],7],[[98,98],7],[[99,99],7],[[100,100],7],[[101,101],7],[[102,102],7],[[103,103],7],[[104,104],7],[[105,105],7],[[106,106],7],[[107,107],7],[[108,108],7],[[109,109],7],[[110,110],7],[[111,111],7],[[112,112],7],[[113,113],7],[[114,114],7],[[115,115],7],[[116,116],7],[[117,117],7],[[118,118],7],[[119,119],7],[[120,120],7],[[121,121],7],[[122,122],7],[[123,123],7],[[124,124],7],[[125,125],7],[[126,126],7],[[127,127],7],[[128,128],7],[[129,129],7],[[130,130],7],[[131,131],7],[[132,132],7],[[133,133],7],[[134,134],7],[[135,135],7],[[136,136],7],[[137,137],7],[[138,138],7],[[139,139],7],[[140,140],7],[[141,141],7],[[142,142],7],[[143,143],7],[[144,144],7],[[145,145],7],[[146,146],7],[[147,147],7],[[149,149],7],[[150,150],7],[[151,151],7],[[152,152],7],[[153,153],7],[[154,154],7],[[156,156],7],[[157,157],7],[[158,158],7],[[159,159],7],[[160,160],7],[[161,161],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[10,163],164],[[11,163],164],[[12,163],164],[[13,163],164],[[14,163],164],[[15,163],164],[[16,163],164],[[17,163],164],[[18,163],164],[[19,163],164],[[20,163],164],[[21,163],164],[[22,163],164],[[23,163],164],[[24,163],164],[[25,163],164],[[26,163],164],[[27,163],164],[[28,163],164],[[29,163],164],[[30,163],164],[[31,163],164],[[32,163],164],[[33,163],164],[[6,163],164],[[34,163],164],[[3,163],164],[[1,163],164],[[35,163],164],[[36,163],164],[[37,163],164],[[38,163],164],[[39,163],164],[[40,163],164],[[162,163],164],[[41,163],164],[[42,163],164],[[43,163],164],[[44,163],164],[[45,163],164],[[46,163],164],[[47,163],164],[[48,163],164],[[49,163],164],[[50,163],164],[[51,163],164],[[52,163],164],[[53,163],164],[[54,163],164],[[55,163],164],[[56,163],164],[[57,163],164],[[58,163],164],[[59,163],164],[[60,163],164],[[61,163],164],[[62,163],164],[[63,163],164],[[64,163],164],[[65,163],164],[[66,163],164],[[67,163],164],[[68,163],164],[[69,163],164],[[70,163],164],[[71,163],164],[[72,163],164],[[73,163],164],[[74,163],164],[[75,163],164],[[76,163],164],[[77,163],164],[[78,163],164],[[79,163],164],[[80,163],164],[[81,163],164],[[82,163],164],[[83,163],164],[[84,163],164],[[85,163],164],[[86,163],164],[[87,163],164],[[88,163],164],[[89,163],164],[[90,163],164],[[91,163],164],[[92,163],164],[[93,163],164],[[94,163],164],[[95,163],164],[[96,163],164],[[97,163],164],[[98,163],164],[[99,163],164],[[100,163],164],[[101,163],164],[[102,163],164],[[103,163],164],[[104,163],164],[[105,163],164],[[106,163],164],[[107,163],164],[[108,163],164],[[109,163],164],[[110,163],164],[[111,163],164],[[112,163],164],[[113,163],164],[[114,163],164],[[115,163],164],[[116,163],164],[[117,163],164],[[118,163],164],[[119,163],164],[[120,163],164],[[121,163],164],[[122,163],164],[[123,163],164],[[124,163],164],[[125,163],164],[[126,163],164],[[127,163],164],[[128,163],164],[[129,163],164],[[130,163],164],[[131,163],164],[[132,163],164],[[133,163],164],[[165,163],[[167,[166]]]],[[134,163],164],[[135,163],164],[[136,163],164],[[137,163],164],[[138,163],164],[[139,163],164],[[140,163],164],[[141,163],164],[[142,163],164],[[143,163],164],[[144,163],164],[[145,163],164],[[146,163],164],[[147,163],164],[[149,163],164],[[150,163],164],[[151,163],164],[[152,163],164],[[153,163],164],[[154,163],164],[[156,163],164],[[157,163],164],[[158,163],164],[[159,163],164],[[160,163],164],[[161,163],164],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[30],[31],[32],[33],[6],[34],[3],[1],[35],[36],[37],[38],[39],[40],[162],[45],[46],[47],[48],[49],[50],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60],[61],[62],[63],[64],[65],[66],[67],[68],[69],[70],[71],[72],[73],[74],[75],[76],[77],[78],[79],[80],[81],[82],[83],[84],[85],[86],[87],[88],[89],[90],[91],[92],[93],[94],[95],[96],[97],[98],[99],[100],[101],[102],[103],[104],[105],[106],[107],[108],[109],[110],[111],[112],[113],[114],[115],[116],[117],[118],[119],[120],[121],[122],[123],[124],[125],[126],[127],[128],[129],[130],[131],[132],[133],[134],[135],[136],[137],[138],[139],[140],[141],[142],[143],[144],[145],[146],[147],[149],[150],[151],[152],[153],[154],[156],[157],[158],[159],[160],[161],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],168],0,0,0,0,0,0,0,0,0,0,0,0,0,[169,170],[[170,170],169],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[169,170],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[56,165],0,0,0,[56,171],0,0,[56,5],0,[56,172],0,[56,23],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],0,0,0,0,0,0,0,0,[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"p":[[3,"cmsghdr"],[6,"c_uchar"],[3,"msghdr"],[6,"c_uint"],[6,"c_int"],[3,"fd_set"],[15,"bool"],[15,"u8"],[15,"u32"],[4,"DIR"],[3,"group"],[3,"utimbuf"],[3,"timeval"],[3,"timespec"],[3,"rlimit"],[3,"rusage"],[3,"ipv6_mreq"],[3,"hostent"],[3,"iovec"],[3,"pollfd"],[3,"winsize"],[3,"linger"],[3,"sigval"],[3,"itimerval"],[3,"tms"],[3,"servent"],[3,"protoent"],[4,"FILE"],[4,"fpos_t"],[3,"sockaddr"],[3,"sockaddr_in6"],[3,"passwd"],[3,"ifaddrs"],[3,"tm"],[3,"fsid_t"],[3,"if_nameindex"],[3,"regex_t"],[3,"regmatch_t"],[3,"sockaddr_un"],[3,"utsname"],[4,"timezone"],[4,"qos_class_t"],[4,"sysdir_search_path_directory_t"],[4,"sysdir_search_path_domain_mask_t"],[3,"ip_mreq"],[3,"ip_mreqn"],[3,"ip_mreq_source"],[3,"aiocb"],[3,"glob_t"],[3,"addrinfo"],[3,"mach_timebase_info"],[3,"stat"],[3,"pthread_mutexattr_t"],[3,"pthread_condattr_t"],[3,"pthread_rwlockattr_t"],[3,"siginfo_t"],[3,"sigaction"],[3,"stack_t"],[3,"fstore_t"],[3,"radvisory"],[3,"statvfs"],[3,"Dl_info"],[3,"sockaddr_in"],[3,"kevent64_s"],[3,"dqblk"],[3,"if_msghdr"],[3,"termios"],[3,"flock"],[3,"sf_hdtr"],[3,"lconv"],[3,"proc_taskinfo"],[3,"proc_bsdinfo"],[3,"proc_taskallinfo"],[3,"xsw_usage"],[3,"xucred"],[3,"mach_header"],[3,"mach_header_64"],[3,"segment_command"],[3,"segment_command_64"],[3,"load_command"],[3,"sockaddr_dl"],[3,"sockaddr_inarp"],[3,"sockaddr_ctl"],[3,"in_pktinfo"],[3,"in6_pktinfo"],[3,"ipc_perm"],[3,"sembuf"],[3,"arphdr"],[3,"in_addr"],[3,"sockaddr_ndrv"],[3,"sa_endpoints_t"],[3,"timex"],[3,"ntptimeval"],[3,"thread_standard_policy"],[3,"thread_extended_policy"],[3,"thread_time_constraint_policy"],[3,"thread_precedence_policy"],[3,"thread_affinity_policy"],[3,"thread_background_policy"],[3,"thread_latency_qos_policy"],[3,"thread_throughput_qos_policy"],[3,"malloc_statistics_t"],[3,"mstats"],[3,"vm_range_t"],[3,"sched_param"],[3,"vinfo_stat"],[3,"vnode_info"],[3,"vnode_info_path"],[3,"proc_vnodepathinfo"],[3,"vm_statistics"],[3,"task_thread_times_info"],[3,"rusage_info_v0"],[3,"rusage_info_v1"],[3,"rusage_info_v2"],[3,"rusage_info_v3"],[3,"rusage_info_v4"],[3,"image_offset"],[3,"attrlist"],[3,"attrreference_t"],[3,"vol_capabilities_attr_t"],[3,"attribute_set_t"],[3,"vol_attributes_attr_t"],[3,"kevent"],[3,"semid_ds"],[3,"shmid_ds"],[3,"proc_threadinfo"],[3,"statfs"],[3,"dirent"],[3,"pthread_rwlock_t"],[3,"pthread_mutex_t"],[3,"pthread_cond_t"],[3,"sockaddr_storage"],[3,"utmpx"],[3,"sigevent"],[3,"processor_cpu_load_info"],[3,"processor_basic_info"],[3,"processor_set_basic_info"],[3,"processor_set_load_info"],[3,"time_value_t"],[3,"thread_basic_info"],[3,"thread_identifier_info"],[3,"thread_extended_info"],[3,"if_data64"],[3,"if_msghdr2"],[3,"vm_statistics64"],[3,"mach_task_basic_info"],[3,"log2phys"],[3,"os_unfair_lock_s"],[19,"semun"],[3,"timeval32"],[3,"if_data"],[3,"bpf_hdr"],[3,"pthread_attr_t"],[3,"malloc_zone_t"],[3,"max_align_t"],[3,"ucontext_t"],[3,"__darwin_mcontext64"],[3,"__darwin_arm_exception_state64"],[3,"__darwin_arm_thread_state64"],[3,"__darwin_arm_neon_state64"],[3,"in6_addr"],[6,"os_unfair_lock"],[3,"Formatter"],[6,"Result"],[4,"c_void"],[3,"Error"],[4,"Result"],[6,"mach_port_t"],[6,"dev_t"],[15,"i32"],[6,"pid_t"],[6,"uid_t"],[3,"TypeId"]],"a":{"__errno_location":[2353],"errno":[2353]}},\ +"lock_api":{"doc":"This library provides type-safe and fully-featured Mutex …","t":[16,16,8,16,16,3,3,18,18,18,18,16,16,3,3,3,3,3,3,8,8,8,3,8,8,8,8,8,8,8,8,8,8,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,10,10,10,11,11,11,10,10,10,10,10,10,10,11,11,11,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,10,10,10,11,11,11,11,11,11,11,11,11,11,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11],"n":["Duration","Duration","GetThreadId","GuardMarker","GuardMarker","GuardNoSend","GuardSend","INIT","INIT","INIT","INIT","Instant","Instant","MappedMutexGuard","MappedReentrantMutexGuard","MappedRwLockReadGuard","MappedRwLockWriteGuard","Mutex","MutexGuard","RawMutex","RawMutexFair","RawMutexTimed","RawReentrantMutex","RawRwLock","RawRwLockDowngrade","RawRwLockFair","RawRwLockRecursive","RawRwLockRecursiveTimed","RawRwLockTimed","RawRwLockUpgrade","RawRwLockUpgradeDowngrade","RawRwLockUpgradeFair","RawRwLockUpgradeTimed","ReentrantMutex","ReentrantMutexGuard","RwLock","RwLockReadGuard","RwLockUpgradableReadGuard","RwLockWriteGuard","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bump","bump","bump","bump","bump","bump","bump","bump","bump_exclusive","bump_exclusive","bump_shared","bump_shared","bump_upgradable","bump_upgradable","const_new","const_new","const_new","data_ptr","data_ptr","data_ptr","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","downgrade","downgrade","downgrade","downgrade_to_upgradable","downgrade_to_upgradable","downgrade_upgradable","drop","drop","drop","drop","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","force_unlock","force_unlock","force_unlock_fair","force_unlock_fair","force_unlock_read","force_unlock_read_fair","force_unlock_write","force_unlock_write_fair","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked_exclusive","is_locked_exclusive","is_locked_exclusive","is_owned_by_current_thread","is_owned_by_current_thread","leak","lock","lock","lock","lock","lock_exclusive","lock_shared","lock_shared_recursive","lock_upgradable","map","map","map","map","map","map","map","map","mutex","new","new","new","nonzero_thread_id","raw","raw","raw","read","read_recursive","remutex","rwlock","rwlock","rwlock","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock","try_lock","try_lock","try_lock_exclusive","try_lock_exclusive_for","try_lock_exclusive_until","try_lock_for","try_lock_for","try_lock_for","try_lock_for","try_lock_shared","try_lock_shared_for","try_lock_shared_recursive","try_lock_shared_recursive_for","try_lock_shared_recursive_until","try_lock_shared_until","try_lock_until","try_lock_until","try_lock_until","try_lock_until","try_lock_upgradable","try_lock_upgradable_for","try_lock_upgradable_until","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_read","try_read_for","try_read_recursive","try_read_recursive_for","try_read_recursive_until","try_read_until","try_upgradable_read","try_upgradable_read_for","try_upgradable_read_until","try_upgrade","try_upgrade","try_upgrade_for","try_upgrade_for","try_upgrade_until","try_upgrade_until","try_write","try_write_for","try_write_until","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unlock","unlock","unlock_exclusive","unlock_exclusive_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_shared","unlock_shared_fair","unlock_upgradable","unlock_upgradable_fair","unlocked","unlocked","unlocked","unlocked","unlocked","unlocked_fair","unlocked_fair","unlocked_fair","unlocked_fair","unlocked_fair","upgradable_read","upgrade","upgrade","write"],"q":["lock_api","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Duration type used for try_lock_for.","Duration type used for try_lock_for.","Helper trait which returns a non-zero thread ID.","Marker type which determines whether a lock guard should …","Marker type which determines whether a lock guard should …","Marker type which indicates that the Guard type for a lock …","Marker type which indicates that the Guard type for a lock …","Initial value for an unlocked mutex.","Initial value.","Initial value for an unlocked mutex.","Initial value for an unlocked RwLock.","Instant type used for try_lock_until.","Instant type used for try_lock_until.","An RAII mutex guard returned by MutexGuard::map, which can …","An RAII mutex guard returned by ReentrantMutexGuard::map, …","An RAII read lock guard returned by RwLockReadGuard::map, …","An RAII write lock guard returned by RwLockWriteGuard::map…","A mutual exclusion primitive useful for protecting shared …","An RAII implementation of a “scoped lock” of a mutex. …","Basic operations for a mutex.","Additional methods for mutexes which support fair …","Additional methods for mutexes which support locking with …","A raw mutex type that wraps another raw mutex to provide …","Basic operations for a reader-writer lock.","Additional methods for RwLocks which support atomically …","Additional methods for RwLocks which support fair …","Additional methods for RwLocks which support recursive …","Additional methods for RwLocks which support recursive …","Additional methods for RwLocks which support locking with …","Additional methods for RwLocks which support atomically …","Additional methods for RwLocks which support upgradable …","Additional methods for RwLocks which support upgradable …","Additional methods for RwLocks which support upgradable …","A mutex which can be recursively locked by a single thread.","An RAII implementation of a “scoped lock” of a …","A reader-writer lock","RAII structure used to release the shared read access of a …","RAII structure used to release the upgradable read access …","RAII structure used to release the exclusive write access …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields an exclusive lock to a waiting thread …","Temporarily yields an exclusive lock to a waiting thread …","Temporarily yields a shared lock to a waiting thread if …","Temporarily yields a shared lock to a waiting thread if …","Temporarily yields an upgradable lock to a waiting thread …","Temporarily yields an upgradable lock to a waiting thread …","Creates a new mutex based on a pre-existing raw mutex.","Creates a new reentrant mutex based on a pre-existing raw …","Creates a new new instance of an RwLock<T> based on a …","Returns a raw pointer to the underlying data.","Returns a raw pointer to the underlying data.","Returns a raw pointer to the underlying data.","","","","","","","","","","","","","","","","","Atomically downgrades an exclusive lock into a shared lock …","Atomically downgrades a write lock into a read lock …","Atomically downgrades an upgradable read lock lock into a …","Downgrades an exclusive lock to an upgradable lock.","Atomically downgrades a write lock into an upgradable read …","Downgrades an upgradable lock to a shared lock.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Forcibly unlocks the mutex.","Forcibly unlocks the mutex.","Forcibly unlocks the mutex using a fair unlock procotol.","Forcibly unlocks the mutex using a fair unlock protocol.","Forcibly unlocks a read lock.","Forcibly unlocks a read lock using a fair unlock procotol.","Forcibly unlocks a write lock.","Forcibly unlocks a write lock using a fair unlock procotol.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Returns a mutable reference to the underlying data.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this mutex, returning the underlying data.","Consumes this mutex, returning the underlying data.","Consumes this RwLock, returning the underlying data.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks if this RwLock is currently locked in any way.","Checks if this RwLock is currently locked in any way.","Checks whether this RwLock is currently locked in any way.","Check if this RwLock is currently exclusively locked.","Check if this RwLock is currently exclusively locked.","Check if this RwLock is currently exclusively locked.","Checks whether the mutex is currently held by the current …","Checks whether the mutex is currently held by the current …","Leaks the mutex guard and returns a mutable reference to …","Acquires this mutex, blocking the current thread until it …","Acquires a mutex, blocking the current thread until it is …","Acquires this mutex, blocking if it’s held by another …","Acquires a reentrant mutex, blocking the current thread …","Acquires an exclusive lock, blocking the current thread …","Acquires a shared lock, blocking the current thread until …","Acquires a shared lock without deadlocking in case of a …","Acquires an upgradable lock, blocking the current thread …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedReentrantMutexGuard for a component of …","Makes a new MappedReentrantMutexGuard for a component of …","Make a new MappedRwLockReadGuard for a component of the …","Make a new MappedRwLockWriteGuard for a component of the …","Make a new MappedRwLockReadGuard for a component of the …","Make a new MappedRwLockWriteGuard for a component of the …","Returns a reference to the original Mutex object.","Creates a new mutex in an unlocked state ready for use.","Creates a new reentrant mutex in an unlocked state ready …","Creates a new instance of an RwLock<T> which is unlocked.","Returns a non-zero thread ID which identifies the current …","Returns the underlying raw mutex object.","Returns the underlying raw mutex object.","Returns the underlying raw reader-writer lock object.","Locks this RwLock with shared read access, blocking the …","Locks this RwLock with shared read access, blocking the …","Returns a reference to the original ReentrantMutex object.","Returns a reference to the original reader-writer lock …","Returns a reference to the original reader-writer lock …","Returns a reference to the original reader-writer lock …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attempts to acquire this mutex without blocking. Returns …","Attempts to acquire this lock.","Attempts to acquire this mutex without blocking. Returns …","Attempts to acquire this lock.","Attempts to acquire an exclusive lock without blocking.","Attempts to acquire an exclusive lock until a timeout is …","Attempts to acquire an exclusive lock until a timeout is …","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire a shared lock without blocking.","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock without deadlocking in …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire an upgradable lock without blocking.","Attempts to acquire an upgradable lock until a timeout is …","Attempts to acquire an upgradable lock until a timeout is …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedReentrantMutexGuard for a …","Attempts to make a new MappedReentrantMutexGuard for a …","Attempts to make a new MappedRwLockReadGuard for a …","Attempts to make a new MappedRwLockWriteGuard for a …","Attempts to make a new MappedRwLockReadGuard for a …","Attempts to make a new MappedRwLockWriteGuard for a …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with upgradable read …","Attempts to acquire this RwLock with upgradable read …","Attempts to acquire this RwLock with upgradable read …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to lock this RwLock with exclusive write access.","Attempts to acquire this RwLock with exclusive write …","Attempts to acquire this RwLock with exclusive write …","","","","","","","","","","","","","","","","Unlocks this mutex.","Unlocks this mutex. The inner mutex may not be unlocked if …","Releases an exclusive lock.","Releases an exclusive lock using a fair unlock protocol.","Unlocks this mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks this mutex using a fair unlock protocol. The inner …","Unlocks the mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Releases a shared lock.","Releases a shared lock using a fair unlock protocol.","Releases an upgradable lock.","Releases an upgradable lock using a fair unlock protocol.","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Locks this RwLock with upgradable read access, blocking …","Upgrades an upgradable lock to an exclusive lock.","Atomically upgrades an upgradable read lock lock into a …","Locks this RwLock with exclusive write access, blocking …"],"i":[35,36,0,15,16,0,0,15,4,5,16,35,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,1,1,3,5,6,8,9,11,7,7,7,7,10,10,12,13,14,12,13,14,12,13,14,3,18,6,19,8,9,11,21,22,3,18,9,22,23,9,11,24,9,24,3,18,6,19,8,9,11,21,22,12,3,3,18,18,13,6,6,19,19,14,8,8,9,9,11,11,21,21,22,22,12,13,12,13,14,14,14,14,40,41,12,12,12,3,18,5,13,13,13,6,19,14,14,14,8,9,11,21,22,12,13,14,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,12,13,14,15,15,12,5,13,16,16,14,16,16,14,5,13,3,15,12,5,13,16,16,32,20,3,18,6,19,8,9,21,22,3,12,13,14,4,12,13,14,14,14,6,8,9,11,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,15,12,5,13,16,36,36,35,12,5,13,16,36,32,37,37,36,35,12,5,13,20,38,38,3,18,6,19,8,9,21,22,14,14,14,14,14,14,14,14,14,20,11,38,11,38,11,14,14,14,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,15,5,16,7,1,3,18,5,6,19,8,9,11,21,22,16,7,20,10,3,6,8,9,11,3,6,8,9,11,14,20,11,14],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[3,[1,2]]]],[[[5,[1,4]]]],[[[6,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[],12],[[],13],[[],14],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[],[[12,[15,[0,[2,17]]]]]],[[],[[13,[15,4,[0,[2,17]]]]]],[[],[[14,[16,[0,[2,17]]]]]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[6,[15,4,2]]]],[[[19,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[21,[16,2]]]],[[[22,[16,2]]]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[9,[16,2]]]],[[[22,[16,2]]]],[[]],[[[9,[23,2]]],[[8,[23,2]]]],[[[11,[24,2]]],[[8,[24,2]]]],[[]],[[[9,[24,2]]],[[11,[24,2]]]],[[]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[6,[15,4,2]]]],[[[19,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[21,[16,2]]]],[[[22,[16,2]]]],[[[12,[15,[0,[2,25]]]],26],27],[[[3,[15,[0,[25,2]]]],26],27],[[[3,[15,[0,[28,2]]]],26],27],[[[18,[15,[0,[28,2]]]],26],27],[[[18,[15,[0,[25,2]]]],26],27],[[[13,[15,4,[0,[2,25]]]],26],27],[[[6,[15,4,[0,[28,2]]]],26],27],[[[6,[15,4,[0,[25,2]]]],26],27],[[[19,[15,4,[0,[28,2]]]],26],27],[[[19,[15,4,[0,[25,2]]]],26],27],[[[14,[16,[0,[2,25]]]],26],27],[[[8,[16,[0,[25,2]]]],26],27],[[[8,[16,[0,[28,2]]]],26],27],[[[9,[16,[0,[28,2]]]],26],27],[[[9,[16,[0,[25,2]]]],26],27],[[[11,[20,[0,[25,2]]]],26],27],[[[11,[20,[0,[28,2]]]],26],27],[[[21,[16,[0,[25,2]]]],26],27],[[[21,[16,[0,[28,2]]]],26],27],[[[22,[16,[0,[28,2]]]],26],27],[[[22,[16,[0,[25,2]]]],26],27],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[12,[1,2]]]],[[[13,[1,4,2]]]],[[[14,[16,2]]]],[[[14,[7,2]]]],[[[14,[16,2]]]],[[[14,[7,2]]]],[[]],[[]],[[]],[29],[[],[[12,[15]]]],[[]],[[]],[[]],[[],[[13,[15,4]]]],[29],[[]],[[]],[[]],[29],[[],[[14,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[15]]]],[[[13,[15,4]]]],[[[14,[16]]]],[[],30],[[],30],[[[12,[15,2]]],30],[[[5,[15,4]]],30],[[[13,[15,4,2]]],30],[[],30],[[],30],[[[14,[16,2]]],30],[[],30],[[],30],[[[14,[16,2]]],30],[[[5,[15,4]]],30],[[[13,[15,4,2]]],30],[[[3,[15,2]]]],[[]],[[[12,[15,2]]],[[3,[15,2]]]],[[[5,[15,4]]]],[[[13,[15,4,2]]],[[6,[15,4,2]]]],[[]],[[]],[[]],[[]],[[[3,[15,2]]],[[18,[15,2]]]],[[[18,[15,2]]],[[18,[15,2]]]],[[[6,[15,4,2]]],[[19,[15,4,2]]]],[[[19,[15,4,2]]],[[19,[15,4,2]]]],[[[8,[16,2]]],[[21,[16,2]]]],[[[9,[16,2]]],[[22,[16,2]]]],[[[21,[16,2]]],[[21,[16,2]]]],[[[22,[16,2]]],[[22,[16,2]]]],[[[3,[15,2]]],12],[[],[[12,[15]]]],[[],[[13,[15,4]]]],[[],[[14,[16]]]],[[],31],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[[14,[16,2]]],[[8,[16,2]]]],[[[14,[32,2]]],[[8,[32,2]]]],[[[6,[15,4,2]]],13],[[[8,[16,2]]],14],[[[9,[16,2]]],14],[[[11,[20,2]]],14],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],30],[[[12,[15,2]]],[[34,[[3,[15,2]]]]]],[[[5,[15,4]]],30],[[[13,[15,4,2]]],[[34,[[6,[15,4,2]]]]]],[[],30],[[],30],[[],30],[[],30],[[[12,[35,2]]],[[34,[[3,[35,2]]]]]],[[[5,[35,4]]],30],[[[13,[35,4,2]]],[[34,[[6,[35,4,2]]]]]],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[[12,[35,2]]],[[34,[[3,[35,2]]]]]],[[[5,[35,4]]],30],[[[13,[35,4,2]]],[[34,[[6,[35,4,2]]]]]],[[],30],[[],30],[[],30],[[[3,[15,2]]],[[33,[[18,[15,2]],[3,[15,2]]]]]],[[[18,[15,2]]],[[33,[[18,[15,2]],[18,[15,2]]]]]],[[[6,[15,4,2]]],[[33,[[19,[15,4,2]],[6,[15,4,2]]]]]],[[[19,[15,4,2]]],[[33,[[19,[15,4,2]],[19,[15,4,2]]]]]],[[[8,[16,2]]],[[33,[[21,[16,2]],[8,[16,2]]]]]],[[[9,[16,2]]],[[33,[[22,[16,2]],[9,[16,2]]]]]],[[[21,[16,2]]],[[33,[[21,[16,2]],[21,[16,2]]]]]],[[[22,[16,2]]],[[33,[[22,[16,2]],[22,[16,2]]]]]],[[[14,[16,2]]],[[34,[[8,[16,2]]]]]],[[[14,[36,2]]],[[34,[[8,[36,2]]]]]],[[[14,[32,2]]],[[34,[[8,[32,2]]]]]],[[[14,[37,2]]],[[34,[[8,[37,2]]]]]],[[[14,[37,2]]],[[34,[[8,[37,2]]]]]],[[[14,[36,2]]],[[34,[[8,[36,2]]]]]],[[[14,[20,2]]],[[34,[[11,[20,2]]]]]],[[[14,[38,2]]],[[34,[[11,[38,2]]]]]],[[[14,[38,2]]],[[34,[[11,[38,2]]]]]],[[],30],[[[11,[20,2]]],[[33,[[9,[20,2]],[11,[20,2]]]]]],[[],30],[[[11,[38,2]]],[[33,[[9,[38,2]],[11,[38,2]]]]]],[[],30],[[[11,[38,2]]],[[33,[[9,[38,2]],[11,[38,2]]]]]],[[[14,[16,2]]],[[34,[[9,[16,2]]]]]],[[[14,[36,2]]],[[34,[[9,[36,2]]]]]],[[[14,[36,2]]],[[34,[[9,[36,2]]]]]],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[]],[[[5,[15,4]]]],[[]],[[]],[[]],[[[3,[1,2]]]],[[[18,[1,2]]]],[[[5,[1,4]]]],[[[6,[1,4,2]]]],[[[19,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[[21,[7,2]]]],[[[22,[7,2]]]],[[]],[[]],[[]],[[]],[[[3,[15,2]]]],[[[6,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[3,[1,2]]]],[[[6,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[[14,[20,2]]],[[11,[20,2]]]],[[]],[[[11,[20,2]]],[[9,[20,2]]]],[[[14,[16,2]]],[[9,[16,2]]]]],"p":[[8,"RawMutexFair"],[8,"Sized"],[3,"MutexGuard"],[8,"GetThreadId"],[3,"RawReentrantMutex"],[3,"ReentrantMutexGuard"],[8,"RawRwLockFair"],[3,"RwLockReadGuard"],[3,"RwLockWriteGuard"],[8,"RawRwLockUpgradeFair"],[3,"RwLockUpgradableReadGuard"],[3,"Mutex"],[3,"ReentrantMutex"],[3,"RwLock"],[8,"RawMutex"],[8,"RawRwLock"],[8,"Default"],[3,"MappedMutexGuard"],[3,"MappedReentrantMutexGuard"],[8,"RawRwLockUpgrade"],[3,"MappedRwLockReadGuard"],[3,"MappedRwLockWriteGuard"],[8,"RawRwLockDowngrade"],[8,"RawRwLockUpgradeDowngrade"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[8,"Display"],[15,"never"],[15,"bool"],[3,"NonZeroUsize"],[8,"RawRwLockRecursive"],[4,"Result"],[4,"Option"],[8,"RawMutexTimed"],[8,"RawRwLockTimed"],[8,"RawRwLockRecursiveTimed"],[8,"RawRwLockUpgradeTimed"],[3,"TypeId"],[3,"GuardSend"],[3,"GuardNoSend"]]},\ +"lru":{"doc":"An implementation of a LRU cache. The cache supports get, …","t":[6,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["DefaultHasher","IntoIter","Iter","IterMut","LruCache","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cap","clear","clone","clone_into","contains","count","count","count","demote","drop","fmt","from","from","from","from","get","get_mut","get_or_insert","get_or_insert_mut","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","len","new","next","next","next","next_back","next_back","peek","peek_lru","peek_mut","pop","pop_entry","pop_lru","promote","push","put","resize","size_hint","size_hint","size_hint","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unbounded","unbounded_with_hasher","with_hasher"],"q":["lru","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","An iterator that moves out of a LruCache.","An iterator over the entries of a LruCache.","An iterator over mutables entries of a LruCache.","An LRU Cache","","","","","","","","","Returns the maximum number of key-value pairs the cache …","Clears the contents of the cache.","","","Returns a bool indicating whether the given key is in the …","","","","Marks the key as the least recently used one.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a reference to the value of the key in the cache …","Returns a mutable reference to the value of the key in the …","Returns a reference to the value of the key in the cache …","Returns a mutable reference to the value of the key in the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","Returns a bool indicating whether the cache is empty or …","An iterator visiting all entries in most-recently used …","An iterator visiting all entries in most-recently-used …","Returns the number of key-value pairs that are currently …","Creates a new LRU Cache that holds at most cap items.","","","","","","Returns a reference to the value corresponding to the key …","Returns the value corresponding to the least recently used …","Returns a mutable reference to the value corresponding to …","Removes and returns the value corresponding to the key …","Removes and returns the key and the value corresponding to …","Removes and returns the key and value corresponding to the …","Marks the key as the most recently used one.","Pushes a key-value pair into the cache. If an entry with …","Puts a key-value pair into cache. If the key already …","Resizes the cache. If the new capacity is smaller than the …","","","","","","","","","","","","","","","","","Creates a new LRU Cache that never automatically evicts …","Creates a new LRU Cache that never automatically evicts …","Creates a new LRU Cache that holds at most cap items and …"],"i":[0,0,0,0,0,4,6,9,10,4,6,9,10,4,4,6,6,4,6,9,10,4,4,4,4,6,9,10,4,4,4,4,4,6,9,10,4,4,4,6,9,10,4,4,4,4,4,6,9,10,6,9,4,4,4,4,4,4,4,4,4,4,6,9,10,6,4,6,9,10,4,6,9,10,4,6,9,10,4,4,4],"f":[0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],5],[[[4,[[0,[1,2]],3]]]],[6,6],[[]],[[[4,[[0,[1,2]],3]]],7],[6,8],[9,8],[10,8],[[[4,[[0,[1,2]],3]]]],[4],[[[4,[[0,[1,2]]]],11],12],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]],[0,[1,2]]]],[[[4,[[0,[1,2]],3]],[0,[1,2]]]],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]]]]],[[10,[[0,[1,2]]]]]],[4,[[6,[[0,[1,2]]]]]],[4,[[9,[[0,[1,2]]]]]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],7],[[[4,[[0,[1,2]],3]]],[[6,[[0,[1,2]]]]]],[[[4,[[0,[1,2]],3]]],[[9,[[0,[1,2]]]]]],[[[4,[[0,[1,2]],3]]],8],[5,[[4,[[0,[1,2]]]]]],[6,13],[9,13],[10,13],[6,13],[9,13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]]],[[[4,[[0,[1,2]],3]],[0,[1,2]]],13],[[[4,[[0,[1,2]],3]],[0,[1,2]]],13],[[[4,[[0,[1,2]],3]],5]],[6],[9],[10],[[]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],15],[[],15],[[],15],[[],15],[[],[[4,[[0,[1,2]]]]]],[3,[[4,[[0,[1,2]],3]]]],[[5,3],[[4,[[0,[1,2]],3]]]]],"p":[[8,"Hash"],[8,"Eq"],[8,"BuildHasher"],[3,"LruCache"],[3,"NonZeroUsize"],[3,"Iter"],[15,"bool"],[15,"usize"],[3,"IterMut"],[3,"IntoIter"],[3,"Formatter"],[6,"Result"],[4,"Option"],[4,"Result"],[3,"TypeId"]]},\ +"memchr":{"doc":"This library provides heavily optimized routines for …","t":[3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,0,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,3,3,3,3,3,13,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Memchr","Memchr2","Memchr3","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","from","from","from","into","into","into","into_iter","into_iter","into_iter","memchr","memchr2","memchr2_iter","memchr3","memchr3_iter","memchr_iter","memmem","memrchr","memrchr2","memrchr2_iter","memrchr3","memrchr3_iter","memrchr_iter","new","new","new","next","next","next","next_back","next_back","next_back","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","Auto","FindIter","FindRevIter","Finder","FinderBuilder","FinderRev","None","Prefilter","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build_forward","build_reverse","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","default","default","find","find","find_iter","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","into","into","into","into","into","into","into_iter","into_iter","into_owned","into_owned","into_owned","into_owned","needle","needle","new","new","new","next","next","prefilter","rfind","rfind","rfind_iter","rfind_iter","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id"],"q":["memchr","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","memchr::memmem","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An iterator for memchr.","An iterator for memchr2.","An iterator for memchr3.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Search for the first occurrence of a byte in a slice.","Like memchr, but searches for either of two bytes instead …","An iterator over all occurrences of the needles in a …","Like memchr, but searches for any of three bytes instead …","An iterator over all occurrences of the needles in a …","An iterator over all occurrences of the needle in a …","This module provides forward and reverse substring search …","Search for the last occurrence of a byte in a slice.","Like memrchr, but searches for either of two bytes instead …","An iterator over all occurrences of the needles in a …","Like memrchr, but searches for any of three bytes instead …","An iterator over all occurrences of the needles in a …","An iterator over all occurrences of the needle in a …","Creates a new iterator that yields all positions of needle …","Creates a new iterator that yields all positions of needle …","Create a new Memchr3 that’s initialized to zero with a …","","","","","","","","","","","","","","","","","","","Automatically detect whether a heuristic prefilter should …","An iterator over non-overlapping substring matches.","An iterator over non-overlapping substring matches in …","A single substring searcher fixed to a particular needle.","A builder for constructing non-default forward or reverse …","A single substring reverse searcher fixed to a particular …","Never used a prefilter in substring search.","Prefilter controls whether heuristics are used to …","Convert this finder into its borrowed variant.","Convert this finder into its borrowed variant.","","","","","","","","","","","","","Build a forward finder using the given needle from the …","Build a reverse finder using the given needle from the …","","","","","","","","","","","Returns the index of the first occurrence of the given …","Returns the index of the first occurrence of this needle …","Returns an iterator over all non-overlapping occurrences …","Returns an iterator over all occurrences of a substring in …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Convert this iterator into its owned variant, such that it …","Convert this iterator into its owned variant, such that it …","Convert this finder into its owned variant, such that it …","Convert this finder into its owned variant, such that it …","Returns the needle that this finder searches for.","Returns the needle that this finder searches for.","Create a new finder for the given needle.","Create a new reverse finder for the given needle.","Create a new finder builder with default settings.","","","Configure the prefilter setting for the finder.","Returns the index of the last occurrence of the given …","Returns the index of the last occurrence of this needle in …","Returns a reverse iterator over all non-overlapping …","Returns a reverse iterator over all occurrences of a …","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,0,0,0,0,0,0,0,0,0,0,0,0,0,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,13,0,0,0,0,0,13,0,10,11,13,14,17,10,11,12,13,14,17,10,11,12,12,12,13,10,11,12,13,10,11,12,13,12,0,10,0,10,13,14,17,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12,14,17,14,17,10,11,10,11,10,11,12,14,17,12,0,11,0,11,13,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12],"f":[0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,[[3,[2]]]],[[1,1],[[3,[2]]]],[[1,1],4],[[1,1,1],[[3,[2]]]],[[1,1,1],5],[1,6],0,[1,[[3,[2]]]],[[1,1],[[3,[2]]]],[[1,1],[[7,[4]]]],[[1,1,1],[[3,[2]]]],[[1,1,1],[[7,[5]]]],[1,[[7,[6]]]],[1,6],[[1,1],4],[[1,1,1],5],[6,[[3,[2]]]],[4,[[3,[2]]]],[5,[[3,[2]]]],[6,3],[4,3],[5,3],[6],[4],[5],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],9],[[],9],[[],9],0,0,0,0,0,0,0,0,[10,10],[11,11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[12,10],[12,11],[13,13],[10,10],[11,11],[12,12],[[]],[[]],[[]],[[]],[[],13],[[],12],[[],[[3,[2]]]],[10,[[3,[2]]]],[[],14],[10,14],[[13,15],16],[[14,15],16],[[17,15],16],[[10,15],16],[[11,15],16],[[12,15],16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[14,14],[17,17],[10,10],[11,11],[10],[11],[[],10],[[],11],[[],12],[14,[[3,[2]]]],[17,[[3,[2]]]],[[12,13],12],[[],[[3,[2]]]],[[11,18],[[3,[2]]]],[[],17],[11,17],[[]],[[]],[[]],[[]],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9]],"p":[[15,"u8"],[15,"usize"],[4,"Option"],[3,"Memchr2"],[3,"Memchr3"],[3,"Memchr"],[3,"Rev"],[4,"Result"],[3,"TypeId"],[3,"Finder"],[3,"FinderRev"],[3,"FinderBuilder"],[4,"Prefilter"],[3,"FindIter"],[3,"Formatter"],[6,"Result"],[3,"FindRevIter"],[8,"AsRef"]]},\ +"memoffset":{"doc":"A crate used for calculating offsets of struct members and …","t":[14,14,14,14,14,14,14],"n":["offset_of","offset_of_tuple","offset_of_union","raw_field","raw_field_tuple","raw_field_union","span_of"],"q":["memoffset","","","","","",""],"d":["Calculates the offset of the specified field from the …","Calculates the offset of the specified field from the …","Calculates the offset of the specified union member from …","Computes a const raw pointer to the given field of the …","Computes a const raw pointer to the given field of the …","Computes a const raw pointer to the given field of the …","Produces a range instance representing the sub-slice …"],"i":[0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0],"p":[]},\ +"nix":{"doc":"Rust friendly bindings to the various *nix system …","t":[6,8,6,14,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,10,0,10,2,0,0,0,0,0,14,14,14,14,0,0,0,0,0,10,13,13,3,13,3,13,13,3,3,13,13,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,4,8,13,11,11,11,11,11,11,11,5,11,11,11,5,11,11,11,11,11,10,11,11,11,11,11,11,18,18,18,18,18,3,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,13,13,13,13,13,3,4,3,4,13,13,13,13,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,13,13,13,13,13,13,4,18,18,18,3,18,18,18,18,3,3,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,12,12,12,12,12,12,12,12,5,3,3,12,11,11,11,11,12,11,11,12,11,11,11,12,11,11,11,11,5,11,11,12,11,11,11,12,11,11,11,11,11,11,11,11,3,18,18,3,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,3,3,3,18,18,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,5,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,5,11,11,11,11,11,5,11,11,11,11,11,11,11,11,12,12,5,5,5,5,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,6,3,3,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,13,4,13,3,4,13,3,3,13,13,4,13,13,16,5,10,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,13,13,13,3,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,14,14,14,14,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,3,3,4,3,18,18,18,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,3,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,6,5,5,6,4,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,4,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,5,11,11,11,11,11,11,11,5,11,11,11,11,5,5,11,5,11,5,5,5,5,11,11,11,5,5,5,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,5,5,3,13,13,13,13,18,18,18,18,18,18,18,18,18,18,4,4,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,4,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,17,13,13,13,4,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,17,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,5,5,13,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,17,13,13,17,13,13,13,13,13,13,13,13,13,13,13,13,17,13,13,13,13,13,13,13,13,13,13,3,3,13,13,3,4,13,3,3,13,4,13,13,4,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,5,5,11,11,11,5,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,11,11,12,12,12,12,12,12,12,18,18,17,17,3,2,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,2,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,4,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,3,4,4,13,13,13,13,8,13,13,13,13,13,4,3,4,3,3,13,13,13,13,3,3,13,13,13,13,13,13,13,13,13,13,3,13,18,18,18,18,18,18,18,18,18,18,13,3,3,3,13,13,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,3,13,13,13,13,18,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,8,4,13,4,3,4,4,3,3,8,19,13,13,3,3,13,13,13,13,13,13,13,3,3,13,13,13,13,13,16,16,13,13,3,13,13,13,5,5,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,12,12,12,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,12,12,6,11,11,5,5,5,5,10,11,11,11,5,5,12,12,12,12,12,12,12,12,12,11,11,11,3,3,3,3,5,3,5,5,0,12,11,11,11,11,11,11,12,12,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,4,3,5,13,13,3,13,13,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5,6,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,5,5,12,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,11,11,11,11,5,11,11,11,11,11,11,11,11,3,18,18,18,18,18,18,18,18,18,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,4,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,18,18,18,18,18,18,18,18,18,4,4,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,17,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,18,4,4,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,18,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,13,13,18,17,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,10,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,4,13,13,13,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,13,13,13,13,13,4,4,13,13,13,18,18,18,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,3,3,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,5,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,13,13,13,4,13,13,13,13,13,13,13,13,18,18,18,18,18,18,3,4,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,18,18,18,18,18,18,18,18,18,18,18,3,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,3,13,13,13,13,13,13,13,13,13,13,13,18,4,13,4,13,13,3,3,13,13,13,13,13,4,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,4,3,13,17,13,18,13,3,3,13,13,13,13,13,13,13,13,13,13,13,13,4,13,13,13,3,4,3,18,4,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,5,0,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,12,5,5,5,5,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,12,5,5,5,5,12,12,11,11,11,11,12,12,5,5,5,5,5,5,12,12,11,12,12,11,5,5,5,5,5,5,5,5,5,5,5,5,12,5,11,11,5,11,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,5,5,5,12,5,5,5,5],"n":["Error","NixPath","Result","cmsg_space","dir","env","errno","fcntl","features","ifaddrs","ioctl_none","ioctl_none_bad","ioctl_read","ioctl_read_bad","ioctl_read_buf","ioctl_readwrite","ioctl_readwrite_bad","ioctl_readwrite_buf","ioctl_write_buf","ioctl_write_int","ioctl_write_int_bad","ioctl_write_ptr","ioctl_write_ptr_bad","is_empty","kmod","len","libc","mount","mqueue","net","poll","pty","request_code_none","request_code_read","request_code_readwrite","request_code_write","sched","sys","time","ucontext","unistd","with_nix_path","BlockDevice","CharacterDevice","Dir","Directory","Entry","Fifo","File","Iter","OwningIter","Socket","Symlink","Type","as_raw_fd","as_raw_fd","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","drop","drop","eq","eq","eq","eq","eq","file_name","file_type","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_fd","hash","hash","hash","hash","hash","ino","into","into","into","into","into","into_iter","into_iter","into_iter","iter","next","next","open","openat","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","ClearEnvError","borrow","borrow_mut","clearenv","clone","clone_into","fmt","fmt","from","into","provide","to_owned","to_string","try_from","try_into","type_id","E2BIG","EACCES","EADDRINUSE","EADDRNOTAVAIL","EADV","EAFNOSUPPORT","EAGAIN","EALREADY","EBADE","EBADF","EBADFD","EBADMSG","EBADR","EBADRQC","EBADSLT","EBFONT","EBUSY","ECANCELED","ECHILD","ECHRNG","ECOMM","ECONNABORTED","ECONNREFUSED","ECONNRESET","EDEADLK","EDEADLOCK","EDESTADDRREQ","EDOM","EDOTDOT","EDQUOT","EEXIST","EFAULT","EFBIG","EHOSTDOWN","EHOSTUNREACH","EHWPOISON","EIDRM","EILSEQ","EINPROGRESS","EINTR","EINVAL","EIO","EISCONN","EISDIR","EISNAM","EKEYEXPIRED","EKEYREJECTED","EKEYREVOKED","EL2HLT","EL2NSYNC","EL3HLT","EL3RST","ELIBACC","ELIBBAD","ELIBEXEC","ELIBMAX","ELIBSCN","ELNRNG","ELOOP","EMEDIUMTYPE","EMFILE","EMLINK","EMSGSIZE","EMULTIHOP","ENAMETOOLONG","ENAVAIL","ENETDOWN","ENETRESET","ENETUNREACH","ENFILE","ENOANO","ENOBUFS","ENOCSI","ENODATA","ENODEV","ENOENT","ENOEXEC","ENOKEY","ENOLCK","ENOLINK","ENOMEDIUM","ENOMEM","ENOMSG","ENONET","ENOPKG","ENOPROTOOPT","ENOSPC","ENOSR","ENOSTR","ENOSYS","ENOTBLK","ENOTCONN","ENOTDIR","ENOTEMPTY","ENOTNAM","ENOTRECOVERABLE","ENOTSOCK","ENOTSUP","ENOTTY","ENOTUNIQ","ENXIO","EOPNOTSUPP","EOVERFLOW","EOWNERDEAD","EPERM","EPFNOSUPPORT","EPIPE","EPROTO","EPROTONOSUPPORT","EPROTOTYPE","ERANGE","EREMCHG","EREMOTE","EREMOTEIO","ERESTART","ERFKILL","EROFS","ESHUTDOWN","ESOCKTNOSUPPORT","ESPIPE","ESRCH","ESRMNT","ESTALE","ESTRPIPE","ETIME","ETIMEDOUT","ETOOMANYREFS","ETXTBSY","EUCLEAN","EUNATCH","EUSERS","EWOULDBLOCK","EXDEV","EXFULL","Errno","ErrnoSentinel","UnknownErrno","borrow","borrow_mut","clear","clone","clone_into","desc","eq","errno","fmt","fmt","from","from_i32","from_i32","into","last","provide","result","sentinel","to_owned","to_string","try_from","try_from","try_into","type_id","AT_EMPTY_PATH","AT_NO_AUTOMOUNT","AT_REMOVEDIR","AT_SYMLINK_FOLLOW","AT_SYMLINK_NOFOLLOW","AtFlags","FALLOC_FL_COLLAPSE_RANGE","FALLOC_FL_INSERT_RANGE","FALLOC_FL_KEEP_SIZE","FALLOC_FL_PUNCH_HOLE","FALLOC_FL_UNSHARE_RANGE","FALLOC_FL_ZERO_RANGE","FD_CLOEXEC","F_ADD_SEALS","F_DUPFD","F_DUPFD_CLOEXEC","F_GETFD","F_GETFL","F_GETLK","F_GETPIPE_SZ","F_GET_SEALS","F_OFD_GETLK","F_OFD_SETLK","F_OFD_SETLKW","F_SEAL_GROW","F_SEAL_SEAL","F_SEAL_SHRINK","F_SEAL_WRITE","F_SETFD","F_SETFL","F_SETLK","F_SETLKW","F_SETPIPE_SZ","FallocateFlags","FcntlArg","FdFlag","FlockArg","LockExclusive","LockExclusiveNonblock","LockShared","LockSharedNonblock","OFlag","O_ACCMODE","O_APPEND","O_ASYNC","O_CLOEXEC","O_CREAT","O_DIRECT","O_DIRECTORY","O_DSYNC","O_EXCL","O_FSYNC","O_LARGEFILE","O_NDELAY","O_NOATIME","O_NOCTTY","O_NOFOLLOW","O_NONBLOCK","O_PATH","O_RDONLY","O_RDWR","O_RSYNC","O_SYNC","O_TMPFILE","O_TRUNC","O_WRONLY","POSIX_FADV_DONTNEED","POSIX_FADV_NOREUSE","POSIX_FADV_NORMAL","POSIX_FADV_RANDOM","POSIX_FADV_SEQUENTIAL","POSIX_FADV_WILLNEED","PosixFadviseAdvice","RENAME_EXCHANGE","RENAME_NOREPLACE","RENAME_WHITEOUT","RenameFlags","SPLICE_F_GIFT","SPLICE_F_MORE","SPLICE_F_MOVE","SPLICE_F_NONBLOCK","SealFlag","SpliceFFlags","Unlock","UnlockNonblock","all","all","all","all","all","all","all","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","complement","complement","complement","contains","contains","contains","contains","contains","contains","contains","copy_file_range","difference","difference","difference","difference","difference","difference","difference","empty","empty","empty","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","extend","extend","extend","fallocate","fcntl","flock","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","insert","insert","insert","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","is_empty","is_empty","is_empty","not","not","not","not","not","not","not","open","openat","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","posix_fadvise","posix_fallocate","readlink","readlinkat","remove","remove","remove","remove","remove","remove","remove","renameat","renameat2","set","set","set","set","set","set","set","splice","sub","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","tee","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","union","union","union","vmsplice","0","0","0","0","0","0","0","0","0","0","0","0","socket_atomic_cloexec","InterfaceAddress","InterfaceAddressIterator","address","borrow","borrow","borrow_mut","borrow_mut","broadcast","clone","clone_into","destination","drop","eq","eq","flags","fmt","fmt","from","from","getifaddrs","hash","hash","interface_name","into","into","into_iter","netmask","next","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","DeleteModuleFlags","MODULE_INIT_IGNORE_MODVERSIONS","MODULE_INIT_IGNORE_VERMAGIC","ModuleInitFlags","O_NONBLOCK","O_TRUNC","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","delete_module","difference","difference","empty","empty","eq","eq","extend","extend","finit_module","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","init_module","insert","insert","intersection","intersection","intersects","intersects","into","into","is_all","is_all","is_empty","is_empty","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","toggle","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","union","MNT_DETACH","MNT_EXPIRE","MNT_FORCE","MS_ACTIVE","MS_BIND","MS_DIRSYNC","MS_I_VERSION","MS_KERNMOUNT","MS_LAZYTIME","MS_MANDLOCK","MS_MGC_MSK","MS_MGC_VAL","MS_MOVE","MS_NOATIME","MS_NODEV","MS_NODIRATIME","MS_NOEXEC","MS_NOSUID","MS_NOUSER","MS_POSIXACL","MS_PRIVATE","MS_RDONLY","MS_REC","MS_RELATIME","MS_REMOUNT","MS_RMT_MASK","MS_SHARED","MS_SILENT","MS_SLAVE","MS_STRICTATIME","MS_SYNCHRONOUS","MS_UNBINDABLE","MntFlags","MsFlags","UMOUNT_NOFOLLOW","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","difference","difference","empty","empty","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","is_all","is_all","is_empty","is_empty","mount","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","toggle","toggle","try_from","try_from","try_into","try_into","type_id","type_id","umount","umount2","union","union","MQ_OFlag","MqAttr","MqdT","O_CLOEXEC","O_CREAT","O_EXCL","O_NONBLOCK","O_RDONLY","O_RDWR","O_WRONLY","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","curmsgs","difference","empty","eq","eq","extend","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","into","is_all","is_empty","maxmsg","mq_attr_member_t","mq_close","mq_getattr","mq_open","mq_receive","mq_remove_nonblock","mq_send","mq_set_nonblock","mq_setattr","mq_unlink","msgsize","new","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","if_","IFF_ALLMULTI","IFF_AUTOMEDIA","IFF_BROADCAST","IFF_DEBUG","IFF_DORMANT","IFF_DYNAMIC","IFF_ECHO","IFF_LOOPBACK","IFF_LOWER_UP","IFF_MASTER","IFF_MULTICAST","IFF_NOARP","IFF_NOTRAILERS","IFF_NO_PI","IFF_POINTOPOINT","IFF_PORTSEL","IFF_PROMISC","IFF_RUNNING","IFF_SLAVE","IFF_TAP","IFF_TUN","IFF_UP","Interface","InterfaceFlags","Interfaces","InterfacesIter","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","cmp","complement","contains","difference","drop","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","if_nameindex","if_nametoindex","index","insert","intersection","intersects","into","into","into","into","into_iter","into_iter","is_all","is_empty","iter","name","next","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_slice","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","POLLERR","POLLHUP","POLLIN","POLLNVAL","POLLOUT","POLLPRI","POLLRDBAND","POLLRDNORM","POLLWRBAND","POLLWRNORM","PollFd","PollFlags","all","all","any","as_raw_fd","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","difference","empty","eq","eq","events","extend","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","new","not","partial_cmp","poll","ppoll","remove","revents","set","set_events","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","ForkptyResult","OpenptyResult","PtyMaster","SessionId","Winsize","as_raw_fd","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","drop","eq","eq","eq","flush","flush","fmt","fmt","fmt","fmt","fork_result","forkpty","from","from","from","from","from","grantpt","hash","hash","hash","into","into","into","into","into_raw_fd","master","master","openpty","posix_openpt","ptsname","ptsname_r","read","read","slave","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unlockpt","write","write","ws_col","ws_row","ws_xpixel","ws_ypixel","CLONE_DETACHED","CLONE_FILES","CLONE_FS","CLONE_IO","CLONE_NEWCGROUP","CLONE_NEWIPC","CLONE_NEWNET","CLONE_NEWNS","CLONE_NEWPID","CLONE_NEWUSER","CLONE_NEWUTS","CLONE_PARENT","CLONE_PTRACE","CLONE_SIGHAND","CLONE_SYSVSEM","CLONE_THREAD","CLONE_UNTRACED","CLONE_VFORK","CLONE_VM","CloneCb","CloneFlags","CpuSet","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","cmp","complement","contains","count","default","difference","empty","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","is_set","new","not","partial_cmp","remove","sched_getaffinity","sched_getcpu","sched_setaffinity","sched_yield","set","set","setns","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","unset","unshare","aio","epoll","eventfd","inotify","ioctl","memfd","mman","personality","pthread","ptrace","quota","reboot","resource","select","sendfile","signal","signalfd","socket","stat","statfs","statvfs","sysinfo","termios","time","timer","timerfd","uio","utsname","wait","Aio","AioAllDone","AioCancelStat","AioCanceled","AioFsync","AioFsyncMode","AioNotCanceled","AioRead","AioWrite","LIO_NOWAIT","LIO_WAIT","LioMode","O_DSYNC","O_SYNC","Output","aio_cancel_all","aio_return","aio_return","aio_return","aio_return","aio_suspend","as_mut","as_mut","as_ref","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancel","cancel","cancel","cancel","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","eq","eq","eq","error","error","error","error","fd","fd","fd","fd","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hash","hash","hash","in_progress","in_progress","in_progress","in_progress","into","into","into","into","into","into","lio_listio","mode","nbytes","nbytes","new","new","new","offset","offset","partial_cmp","partial_cmp","priority","priority","priority","priority","set_sigev_notify","set_sigev_notify","set_sigev_notify","set_sigev_notify","sigevent","sigevent","sigevent","sigevent","submit","submit","submit","submit","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","EPOLLERR","EPOLLET","EPOLLEXCLUSIVE","EPOLLHUP","EPOLLIN","EPOLLMSG","EPOLLONESHOT","EPOLLOUT","EPOLLPRI","EPOLLRDBAND","EPOLLRDHUP","EPOLLRDNORM","EPOLLWAKEUP","EPOLLWRBAND","EPOLLWRNORM","EPOLL_CLOEXEC","EpollCreateFlags","EpollCtlAdd","EpollCtlDel","EpollCtlMod","EpollEvent","EpollFlags","EpollOp","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","data","difference","difference","empty","empty","empty","epoll_create","epoll_create1","epoll_ctl","epoll_wait","eq","eq","eq","eq","events","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","is_all","is_all","is_empty","is_empty","new","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","union","EFD_CLOEXEC","EFD_NONBLOCK","EFD_SEMAPHORE","EfdFlags","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","eventfd","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","insert","intersection","intersects","into","is_all","is_empty","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","AddWatchFlags","IN_ACCESS","IN_ALL_EVENTS","IN_ATTRIB","IN_CLOEXEC","IN_CLOSE","IN_CLOSE_NOWRITE","IN_CLOSE_WRITE","IN_CREATE","IN_DELETE","IN_DELETE_SELF","IN_DONT_FOLLOW","IN_IGNORED","IN_ISDIR","IN_MODIFY","IN_MOVE","IN_MOVED_FROM","IN_MOVED_TO","IN_MOVE_SELF","IN_NONBLOCK","IN_ONESHOT","IN_ONLYDIR","IN_OPEN","IN_Q_OVERFLOW","IN_UNMOUNT","InitFlags","Inotify","InotifyEvent","WatchDescriptor","add_watch","all","all","as_raw_fd","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","complement","contains","contains","cookie","difference","difference","empty","empty","eq","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_raw_fd","hash","hash","hash","init","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","mask","name","not","not","partial_cmp","partial_cmp","partial_cmp","read_events","remove","remove","rm_watch","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","union","union","wd","request_code_none","request_code_read","request_code_readwrite","request_code_write","MFD_ALLOW_SEALING","MFD_CLOEXEC","MemFdCreateFlag","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","insert","intersection","intersects","into","is_all","is_empty","memfd_create","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","MADV_DODUMP","MADV_DOFORK","MADV_DONTDUMP","MADV_DONTFORK","MADV_DONTNEED","MADV_FREE","MADV_HUGEPAGE","MADV_HWPOISON","MADV_MERGEABLE","MADV_NOHUGEPAGE","MADV_NORMAL","MADV_RANDOM","MADV_REMOVE","MADV_SEQUENTIAL","MADV_SOFT_OFFLINE","MADV_UNMERGEABLE","MADV_WILLNEED","MAP_32BIT","MAP_ANON","MAP_ANONYMOUS","MAP_DENYWRITE","MAP_EXECUTABLE","MAP_FILE","MAP_FIXED","MAP_FIXED_NOREPLACE","MAP_GROWSDOWN","MAP_HUGETLB","MAP_HUGE_16GB","MAP_HUGE_16MB","MAP_HUGE_1GB","MAP_HUGE_1MB","MAP_HUGE_256MB","MAP_HUGE_2GB","MAP_HUGE_2MB","MAP_HUGE_32MB","MAP_HUGE_512KB","MAP_HUGE_512MB","MAP_HUGE_64KB","MAP_HUGE_8MB","MAP_LOCKED","MAP_NONBLOCK","MAP_NORESERVE","MAP_POPULATE","MAP_PRIVATE","MAP_SHARED","MAP_STACK","MCL_CURRENT","MCL_FUTURE","MREMAP_FIXED","MREMAP_MAYMOVE","MRemapFlags","MS_ASYNC","MS_INVALIDATE","MS_SYNC","MapFlags","MlockAllFlags","MmapAdvise","MsFlags","PROT_EXEC","PROT_GROWSDOWN","PROT_GROWSUP","PROT_NONE","PROT_READ","PROT_WRITE","ProtFlags","all","all","all","all","all","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","complement","contains","contains","contains","contains","contains","difference","difference","difference","difference","difference","empty","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","insert","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","intersects","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","is_empty","madvise","mlock","mlockall","mmap","mprotect","mremap","msync","munlock","munlockall","munmap","not","not","not","not","not","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","remove","remove","remove","remove","remove","set","set","set","set","set","shm_open","shm_unlink","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","union","ADDR_COMPAT_LAYOUT","ADDR_LIMIT_32BIT","ADDR_LIMIT_3GB","ADDR_NO_RANDOMIZE","FDPIC_FUNCPTRS","MMAP_PAGE_ZERO","Persona","READ_IMPLIES_EXEC","SHORT_INODE","STICKY_TIMEOUTS","UNAME26","WHOLE_SECONDS","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","get","hash","insert","intersection","intersects","into","is_all","is_empty","not","partial_cmp","remove","set","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","Pthread","pthread_kill","pthread_self","AddressType","Event","Options","PTRACE_ATTACH","PTRACE_CONT","PTRACE_DETACH","PTRACE_EVENT_CLONE","PTRACE_EVENT_EXEC","PTRACE_EVENT_EXIT","PTRACE_EVENT_FORK","PTRACE_EVENT_SECCOMP","PTRACE_EVENT_STOP","PTRACE_EVENT_VFORK","PTRACE_EVENT_VFORK_DONE","PTRACE_GETEVENTMSG","PTRACE_GETFPREGS","PTRACE_GETFPXREGS","PTRACE_GETREGS","PTRACE_GETREGSET","PTRACE_GETSIGINFO","PTRACE_INTERRUPT","PTRACE_KILL","PTRACE_LISTEN","PTRACE_O_EXITKILL","PTRACE_O_TRACECLONE","PTRACE_O_TRACEEXEC","PTRACE_O_TRACEEXIT","PTRACE_O_TRACEFORK","PTRACE_O_TRACESECCOMP","PTRACE_O_TRACESYSGOOD","PTRACE_O_TRACEVFORK","PTRACE_O_TRACEVFORKDONE","PTRACE_PEEKDATA","PTRACE_PEEKSIGINFO","PTRACE_PEEKTEXT","PTRACE_PEEKUSER","PTRACE_POKEDATA","PTRACE_POKETEXT","PTRACE_POKEUSER","PTRACE_SEIZE","PTRACE_SETFPREGS","PTRACE_SETFPXREGS","PTRACE_SETOPTIONS","PTRACE_SETREGS","PTRACE_SETREGSET","PTRACE_SETSIGINFO","PTRACE_SINGLESTEP","PTRACE_SYSCALL","PTRACE_SYSEMU","PTRACE_SYSEMU_SINGLESTEP","PTRACE_TRACEME","Request","all","attach","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","cont","contains","detach","difference","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","getevent","getregs","getsiginfo","hash","hash","hash","insert","interrupt","intersection","intersects","into","into","into","is_all","is_empty","kill","not","partial_cmp","partial_cmp","partial_cmp","read","read_user","remove","seize","set","setoptions","setregs","setsiginfo","step","sub","sub_assign","symmetric_difference","syscall","sysemu","sysemu_step","to_owned","to_owned","to_owned","toggle","traceme","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","write","write_user","Dqblk","GRPQUOTA","QFMT_VFS_OLD","QFMT_VFS_V0","QFMT_VFS_V1","QIF_ALL","QIF_BLIMITS","QIF_BTIME","QIF_ILIMITS","QIF_INODES","QIF_ITIME","QIF_LIMITS","QIF_SPACE","QIF_TIMES","QIF_USAGE","QuotaFmt","QuotaType","QuotaValidFlags","USRQUOTA","all","allocated_inodes","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","block_time_limit","blocks_hard_limit","blocks_soft_limit","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","contains","default","default","difference","empty","eq","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","hash","hash","inode_time_limit","inodes_hard_limit","inodes_soft_limit","insert","intersection","intersects","into","into","into","into","is_all","is_empty","not","occupied_space","partial_cmp","partial_cmp","partial_cmp","quotactl_get","quotactl_off","quotactl_on","quotactl_set","quotactl_sync","remove","set","set_block_time_limit","set_blocks_hard_limit","set_blocks_soft_limit","set_inode_time_limit","set_inodes_hard_limit","set_inodes_soft_limit","sub","sub_assign","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","RB_AUTOBOOT","RB_HALT_SYSTEM","RB_KEXEC","RB_POWER_OFF","RB_SW_SUSPEND","RebootMode","borrow","borrow_mut","clone","clone_into","cmp","eq","fmt","from","hash","into","partial_cmp","reboot","set_cad_enabled","to_owned","try_from","try_into","type_id","RLIMIT_AS","RLIMIT_CORE","RLIMIT_CPU","RLIMIT_DATA","RLIMIT_FSIZE","RLIMIT_LOCKS","RLIMIT_MEMLOCK","RLIMIT_MSGQUEUE","RLIMIT_NICE","RLIMIT_NOFILE","RLIMIT_NPROC","RLIMIT_RSS","RLIMIT_RTPRIO","RLIMIT_RTTIME","RLIMIT_SIGPENDING","RLIMIT_STACK","RLIM_INFINITY","RUSAGE_CHILDREN","RUSAGE_SELF","RUSAGE_THREAD","Resource","Usage","UsageWho","as_mut","as_ref","block_reads","block_writes","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","eq","eq","eq","fmt","fmt","fmt","from","from","from","full_swaps","getrlimit","getrusage","hash","hash","hash","into","into","into","involuntary_context_switches","ipc_receives","ipc_sends","major_page_faults","max_rss","minor_page_faults","partial_cmp","partial_cmp","rlim_t","setrlimit","shared_integral","signals","system_time","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","unshared_data_integral","unshared_stack_integral","user_time","voluntary_context_switches","FD_SETSIZE","FdSet","Fds","borrow","borrow","borrow_mut","borrow_mut","clear","clone","clone_into","contains","default","eq","fds","fmt","fmt","from","from","hash","highest","insert","into","into","into_iter","new","next","next_back","pselect","remove","select","size_hint","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","sendfile","sendfile64","Handler","SA_NOCLDSTOP","SA_NOCLDWAIT","SA_NODEFER","SA_ONSTACK","SA_RESETHAND","SA_RESTART","SA_SIGINFO","SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGFPE","SIGHUP","SIGILL","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGPIPE","SIGPOLL","SIGPROF","SIGPWR","SIGQUIT","SIGSEGV","SIGSTKFLT","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGUNUSED","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIG_BLOCK","SIG_SETMASK","SIG_UNBLOCK","SaFlags","SigAction","SigAction","SigDfl","SigEvent","SigHandler","SigIgn","SigSet","SigSetIter","SigevNone","SigevNotify","SigevSignal","SigevThreadId","SigmaskHow","Signal","SignalIterator","add","all","all","as_mut_ptr","as_ref","as_ref","as_str","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","contains","contains","difference","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","from_iter","from_sigset_t_unchecked","from_str","handler","hash","hash","hash","hash","hash","hash","hash","hash","hash","insert","intersection","intersects","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_all","is_empty","iter","iterator","kill","killpg","mask","new","new","next","next","not","partial_cmp","partial_cmp","partial_cmp","pthread_sigmask","raise","remove","remove","set","sigaction","sigevent","signal","sigprocmask","sub","sub_assign","symmetric_difference","thread_block","thread_get_mask","thread_set_mask","thread_swap_mask","thread_unblock","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_of_thread_id","union","wait","0","0","si_value","si_value","signal","signal","thread_id","SFD_CLOEXEC","SFD_NONBLOCK","SIGNALFD_NEW","SIGNALFD_SIGINFO_SIZE","SfdFlags","SigSet","SignalFd","all","as_raw_fd","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","difference","drop","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","hash","insert","intersection","intersects","into","into","into","into_iter","is_all","is_empty","new","next","not","partial_cmp","read_signal","remove","set","set_mask","siginfo","signal","signalfd","ssi_addr","ssi_addr_lsb","ssi_arch","ssi_band","ssi_call_addr","ssi_code","ssi_errno","ssi_fd","ssi_int","ssi_overrun","ssi_pid","ssi_ptr","ssi_signo","ssi_status","ssi_stime","ssi_syscall","ssi_tid","ssi_trapno","ssi_uid","ssi_utime","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","with_flags","0","0","AddressFamily","Alg","Alg","AlgAddr","AlgSetAeadAssoclen","AlgSetIv","AlgSetOp","AppleTalk","Ash","AtmPvc","AtmSvc","Ax25","Bluetooth","Both","Bridge","Caif","Can","CmsgIterator","ControlMessage","ControlMessageOwned","Datagram","Decnet","Econet","EthAll","GetSockOpt","Ib","Ieee802154","Inet","Inet","Inet6","InetAddr","IoSliceIterator","IpAddr","IpMembershipRequest","Ipv4Addr","Ipv4OrigDstAddr","Ipv4PacketInfo","Ipv4PacketInfo","Ipv4RecvErr","Ipv6Addr","Ipv6MembershipRequest","Ipv6OrigDstAddr","Ipv6PacketInfo","Ipv6PacketInfo","Ipv6RecvErr","Ipx","Irda","Isdn","Iucv","Key","Link","LinkAddr","Llc","MSG_CMSG_CLOEXEC","MSG_CTRUNC","MSG_DONTWAIT","MSG_EOR","MSG_ERRQUEUE","MSG_NOSIGNAL","MSG_OOB","MSG_PEEK","MSG_TRUNC","MSG_WAITALL","Mpls","MsgFlags","MultiHeaders","MultiResults","NetBeui","NetRom","Netlink","Netlink","NetlinkAddr","NetlinkAudit","NetlinkCrypto","NetlinkDECNetRoutingMessage","NetlinkFIBLookup","NetlinkIPv6Firewall","NetlinkISCSI","NetlinkKObjectUEvent","NetlinkNetFilter","NetlinkRDMA","NetlinkRoute","NetlinkSCSITransport","NetlinkSELinux","NetlinkSockDiag","NetlinkUserSock","Nfc","Packet","Phonet","Pppox","Raw","Raw","Rdm","Rds","Read","RecvMsg","Rose","RxRpc","RxqOvfl","RxqOvfl","SOCK_CLOEXEC","SOCK_NONBLOCK","SOF_TIMESTAMPING_RAW_HARDWARE","SOF_TIMESTAMPING_RX_HARDWARE","SOF_TIMESTAMPING_RX_SOFTWARE","SOF_TIMESTAMPING_SOFTWARE","SOF_TIMESTAMPING_TX_HARDWARE","SOF_TIMESTAMPING_TX_SOFTWARE","ScmCredentials","ScmCredentials","ScmRights","ScmRights","ScmTimestamp","ScmTimestampns","ScmTimestampsns","Security","SeqPacket","SetSockOpt","Shutdown","Sna","SockAddr","SockFlag","SockProtocol","SockType","SockaddrIn","SockaddrIn6","SockaddrLike","SockaddrStorage","Stream","Tcp","TimestampingFlag","Timestamps","Tipc","TxTime","Udp","UdpGroSegments","UdpGsoSegments","Unix","Unix","UnixAddr","UnixCredentials","Unspec","V4","V4","V6","V6","Val","Val","Vsock","Vsock","VsockAddr","Wanpipe","Write","X25","accept","accept4","addr","address","alg_name","alg_type","all","all","all","any","as_abstract","as_alg_addr","as_alg_addr_mut","as_ffi_pair","as_link_addr","as_link_addr_mut","as_mut_ptr","as_netlink_addr","as_netlink_addr_mut","as_ptr","as_ptr","as_ptr","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_sockaddr_in","as_sockaddr_in6","as_sockaddr_in6_mut","as_sockaddr_in_mut","as_unix_addr","as_unix_addr_mut","as_vsock_addr","as_vsock_addr_mut","bind","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bytes","cid","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmsg_len","cmsg_level","cmsg_type","cmsghdr","cmsgs","complement","complement","complement","connect","contains","contains","contains","default","difference","difference","difference","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","family","family","family","flags","flowinfo","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_i32","from_iter","from_iter","from_iter","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_std","from_std","from_std","from_std","from_str","from_str","get","getpeername","getsockname","getsockopt","gid","groups","halen","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hatype","hw_raw","hw_trans","ifindex","insert","insert","insert","intersection","intersection","intersection","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","iovs","ip","ip","ip","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_unnamed","len","len","len","len","listen","msg_control","msg_controllen","msg_flags","msg_iov","msg_iovlen","msg_name","msg_namelen","msghdr","new","new","new","new","new","new","new","new","new","new","new","new_abstract","new_alg","new_inet","new_netlink","new_unix","new_unnamed","new_v4","new_v6","new_vsock","next","next","next","not","not","not","octets","partial_cmp","partial_cmp","partial_cmp","path","path_len","pid","pid","pkttype","port","port","port","port","preallocate","protocol","recv","recvfrom","recvmmsg","recvmsg","remove","remove","remove","sa_data","sa_family","sa_family_t","scope_id","segments","send","sendmmsg","sendmsg","sendto","set","set","set","set","setsockopt","shutdown","sin6_addr","sin6_family","sin6_flowinfo","sin6_port","sin6_scope_id","sin_addr","sin_family","sin_port","sin_zero","size","size","size","sockaddr","sockaddr_in","sockaddr_in6","sockaddr_storage","sockaddr_storage_to_addr","sockaddr_un","socket","socketpair","sockopt","ss_family","sub","sub","sub","sub_assign","sub_assign","sub_assign","sun_family","sun_path","symmetric_difference","symmetric_difference","symmetric_difference","system","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_std","to_std","to_std","to_std","to_str","to_str","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","union","union","union","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","1","0","0","0","0","0","0","0","0","0","0","AcceptConn","AlgSetAeadAuthSize","AlgSetKey","BindToDevice","Broadcast","DontRoute","Ip6tOriginalDst","IpAddMembership","IpDropMembership","IpFreebind","IpMtu","IpMulticastLoop","IpMulticastTtl","IpTos","IpTransparent","Ipv4OrigDstAddr","Ipv4PacketInfo","Ipv4RecvErr","Ipv4Ttl","Ipv6AddMembership","Ipv6DontFrag","Ipv6DropMembership","Ipv6OrigDstAddr","Ipv6RecvErr","Ipv6RecvPacketInfo","Ipv6TClass","Ipv6Ttl","Ipv6V6Only","KeepAlive","Linger","Mark","OobInline","OriginalDst","PassCred","PeerCredentials","Priority","RcvBuf","RcvBufForce","ReceiveTimeout","ReceiveTimestamp","ReceiveTimestampns","ReuseAddr","ReusePort","RxqOvfl","SendTimeout","SndBuf","SndBufForce","SockType","SocketError","TcpCongestion","TcpKeepCount","TcpKeepIdle","TcpKeepInterval","TcpMaxSeg","TcpNoDelay","TcpRepair","TcpUserTimeout","Timestamping","TxTime","UdpGroSegment","UdpGsoSegment","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","FchmodatFlags","FileStat","FileStat","FollowSymlink","FollowSymlink","Mode","NoFollowSymlink","NoFollowSymlink","SFlag","S_IFBLK","S_IFCHR","S_IFDIR","S_IFIFO","S_IFLNK","S_IFMT","S_IFREG","S_IFSOCK","S_IRGRP","S_IROTH","S_IRUSR","S_IRWXG","S_IRWXO","S_IRWXU","S_ISGID","S_ISUID","S_ISVTX","S_IWGRP","S_IWOTH","S_IWUSR","S_IXGRP","S_IXOTH","S_IXUSR","UtimensatFlags","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","dev_t","difference","difference","empty","empty","eq","eq","eq","extend","extend","fchmod","fchmodat","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","fstat","fstatat","futimens","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","lstat","lutimes","major","makedev","minor","mkdirat","mknod","mknodat","mode_t","not","not","partial_cmp","partial_cmp","remove","remove","set","set","st_atime","st_atime_nsec","st_blksize","st_blocks","st_ctime","st_ctime_nsec","st_dev","st_gid","st_ino","st_mode","st_mtime","st_mtime_nsec","st_nlink","st_rdev","st_size","st_uid","stat","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","umask","union","union","utimensat","utimes","0","ADFS_SUPER_MAGIC","AFFS_SUPER_MAGIC","AFS_SUPER_MAGIC","AUTOFS_SUPER_MAGIC","BPF_FS_MAGIC","BTRFS_SUPER_MAGIC","CGROUP2_SUPER_MAGIC","CGROUP_SUPER_MAGIC","CODA_SUPER_MAGIC","CRAMFS_MAGIC","DEBUGFS_MAGIC","DEVPTS_SUPER_MAGIC","ECRYPTFS_SUPER_MAGIC","EFS_SUPER_MAGIC","EXT2_SUPER_MAGIC","EXT3_SUPER_MAGIC","EXT4_SUPER_MAGIC","F2FS_SUPER_MAGIC","FUSE_SUPER_MAGIC","FUTEXFS_SUPER_MAGIC","FsType","HOSTFS_SUPER_MAGIC","HPFS_SUPER_MAGIC","HUGETLBFS_MAGIC","ISOFS_SUPER_MAGIC","JFFS2_SUPER_MAGIC","MINIX2_SUPER_MAGIC","MINIX2_SUPER_MAGIC2","MINIX3_SUPER_MAGIC","MINIX_SUPER_MAGIC","MINIX_SUPER_MAGIC2","MSDOS_SUPER_MAGIC","NCP_SUPER_MAGIC","NFS_SUPER_MAGIC","NILFS_SUPER_MAGIC","NSFS_MAGIC","OCFS2_SUPER_MAGIC","OPENPROM_SUPER_MAGIC","OVERLAYFS_SUPER_MAGIC","PROC_SUPER_MAGIC","QNX4_SUPER_MAGIC","QNX6_SUPER_MAGIC","RDTGROUP_SUPER_MAGIC","REISERFS_SUPER_MAGIC","SECURITYFS_MAGIC","SELINUX_MAGIC","SMACK_MAGIC","SMB_SUPER_MAGIC","SYSFS_MAGIC","Statfs","TMPFS_MAGIC","TRACEFS_MAGIC","UDF_SUPER_MAGIC","USBDEVICE_SUPER_MAGIC","XENFS_SUPER_MAGIC","XFS_SUPER_MAGIC","block_size","blocks","blocks_available","blocks_free","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","files","files_free","filesystem_id","filesystem_type","flags","fmt","fmt","from","from","fsid_t","fstatfs","into","into","maximum_name_length","optimal_transfer_size","statfs","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","FsFlags","ST_APPEND","ST_IMMUTABLE","ST_MANDLOCK","ST_NOATIME","ST_NODEV","ST_NODIRATIME","ST_NOEXEC","ST_NOSUID","ST_RDONLY","ST_RELATIME","ST_SYNCHRONOUS","ST_WRITE","Statvfs","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","block_size","blocks","blocks_available","blocks_free","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","default","difference","empty","eq","eq","extend","files","files_available","files_free","filesystem_id","flags","fmt","fmt","fmt","fmt","fmt","fmt","fragment_size","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","fstatvfs","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","name_max","not","partial_cmp","remove","set","statvfs","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","SysInfo","borrow","borrow_mut","clone","clone_into","eq","fmt","from","hash","into","load_average","process_count","ram_total","ram_unused","swap_free","swap_total","sysinfo","to_owned","try_from","try_into","type_id","uptime","B0","B1000000","B110","B115200","B1152000","B1200","B134","B150","B1500000","B1800","B19200","B200","B2000000","B230400","B2400","B2500000","B300","B3000000","B3500000","B38400","B4000000","B460800","B4800","B50","B500000","B57600","B576000","B600","B75","B921600","B9600","BRKINT","BS0","BS1","BSDLY","BaudRate","CBAUD","CBAUDEX","CIBAUD","CLOCAL","CMSPAR","CR0","CR1","CR2","CR3","CRDLY","CREAD","CRTSCTS","CS5","CS6","CS7","CS8","CSIZE","CSTOPB","ControlFlags","ECHO","ECHOCTL","ECHOE","ECHOK","ECHOKE","ECHONL","ECHOPRT","EXTPROC","FF0","FF1","FFDLY","FLUSHO","FlowArg","FlushArg","HUPCL","ICANON","ICRNL","IEXTEN","IGNBRK","IGNCR","IGNPAR","IMAXBEL","INLCR","INPCK","ISIG","ISTRIP","IUTF8","IXANY","IXOFF","IXON","InputFlags","LocalFlags","NCCS","NL0","NL1","NLDLY","NOFLSH","OCRNL","OFDEL","OFILL","OLCUC","ONLCR","ONLRET","ONOCR","OPOST","OutputFlags","PARENB","PARMRK","PARODD","PENDIN","SetArg","SpecialCharacterIndices","TAB0","TAB1","TAB2","TAB3","TABDLY","TCIFLUSH","TCIOFF","TCIOFLUSH","TCION","TCOFLUSH","TCOOFF","TCOON","TCSADRAIN","TCSAFLUSH","TCSANOW","TOSTOP","Termios","VDISCARD","VEOF","VEOL","VEOL2","VERASE","VINTR","VKILL","VLNEXT","VMIN","VQUIT","VREPRINT","VSTART","VSTOP","VSUSP","VSWTC","VT0","VT1","VTDLY","VTIME","VWERASE","XTABS","_POSIX_VDISABLE","all","all","all","all","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cfgetispeed","cfgetospeed","cfmakeraw","cfsetispeed","cfsetospeed","cfsetspeed","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","contains","contains","contains","contains","control_chars","control_flags","difference","difference","difference","difference","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","hash","hash","hash","input_flags","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","line_discipline","local_flags","not","not","not","not","output_flags","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","remove","remove","remove","remove","set","set","set","set","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","tcdrain","tcflow","tcflush","tcgetattr","tcgetsid","tcsendbreak","tcsetattr","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","TimeSpec","TimeVal","TimeValLike","add","add","as_mut","as_mut","as_ref","as_ref","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","div","div","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from_duration","from_timespec","hash","hash","hours","into","into","microseconds","microseconds","microseconds","milliseconds","milliseconds","milliseconds","minutes","mul","mul","nanoseconds","nanoseconds","nanoseconds","neg","neg","new","new","num_hours","num_microseconds","num_microseconds","num_microseconds","num_milliseconds","num_milliseconds","num_milliseconds","num_minutes","num_nanoseconds","num_nanoseconds","num_nanoseconds","num_seconds","num_seconds","num_seconds","partial_cmp","partial_cmp","seconds","seconds","seconds","sub","sub","suseconds_t","time_t","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","tv_nsec","tv_sec","tv_sec","tv_usec","type_id","type_id","zero","Expiration","Interval","IntervalDelayed","OneShot","Timer","TimerSetTimeFlags","borrow","borrow_mut","drop","fmt","from","get","into","new","overruns","set","try_from","try_into","type_id","0","0","0","1","CLOCK_BOOTTIME","CLOCK_BOOTTIME_ALARM","CLOCK_MONOTONIC","CLOCK_REALTIME","CLOCK_REALTIME_ALARM","ClockId","Expiration","Interval","IntervalDelayed","OneShot","TFD_CLOEXEC","TFD_NONBLOCK","TFD_TIMER_ABSTIME","TimerFd","TimerFlags","TimerSetTimeFlags","all","all","as_raw_fd","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","complement","contains","contains","difference","difference","drop","empty","empty","eq","eq","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_raw_fd","get","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","new","not","not","partial_cmp","partial_cmp","partial_cmp","remove","remove","set","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","union","union","unset","wait","0","0","0","1","IoVec","RemoteIoVec","as_slice","base","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","from","from","from_mut_slice","from_slice","hash","hash","into","into","len","pread","preadv","process_vm_readv","process_vm_writev","pwrite","pwritev","readv","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","writev","UtsName","borrow","borrow_mut","clone","clone_into","domainname","eq","fmt","from","hash","into","machine","nodename","release","sysname","to_owned","try_from","try_into","type_id","uname","version","All","Continued","Exited","Id","PGid","PIDFd","Pid","PtraceEvent","PtraceSyscall","Signaled","StillAlive","Stopped","WCONTINUED","WEXITED","WNOHANG","WNOWAIT","WSTOPPED","WUNTRACED","WaitPidFlag","WaitStatus","__WALL","__WCLONE","__WNOTHREAD","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","complement","contains","difference","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","from_raw","hash","hash","hash","insert","intersection","intersects","into","into","into","is_all","is_empty","not","partial_cmp","partial_cmp","pid","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","wait","waitid","waitpid","0","0","0","0","0","0","0","0","0","1","1","1","1","2","2","CLOCK_BOOTTIME","CLOCK_BOOTTIME_ALARM","CLOCK_MONOTONIC","CLOCK_MONOTONIC_COARSE","CLOCK_MONOTONIC_RAW","CLOCK_PROCESS_CPUTIME_ID","CLOCK_REALTIME","CLOCK_REALTIME_ALARM","CLOCK_REALTIME_COARSE","CLOCK_TAI","CLOCK_THREAD_CPUTIME_ID","ClockId","as_raw","borrow","borrow_mut","clock_getcpuclockid","clock_getres","clock_gettime","clock_settime","clone","clone_into","cmp","eq","fmt","fmt","from","from","from_raw","hash","into","now","partial_cmp","pid_cpu_clock_id","res","set_time","to_owned","to_string","try_from","try_into","type_id","UContext","borrow","borrow_mut","clone","clone_into","eq","fmt","from","get","hash","into","set","sigmask","sigmask_mut","to_owned","try_from","try_into","type_id","AIO_LISTIO_MAX","AIO_MAX","AIO_PRIO_DELTA_MAX","ARG_MAX","ATEXIT_MAX","AccessFlags","BC_BASE_MAX","BC_DIM_MAX","BC_SCALE_MAX","BC_STRING_MAX","CHILD_MAX","CLK_TCK","COLL_WEIGHTS_MAX","Child","DELAYTIMER_MAX","EXPR_NEST_MAX","FILESIZEBITS","F_OK","FchownatFlags","FollowSymlink","ForkResult","GETGR_R_SIZE_MAX","GETPW_R_SIZE_MAX","Gid","Group","HOST_NAME_MAX","IOV_MAX","LINE_MAX","LINK_MAX","LOGIN_NAME_MAX","LinkatFlags","MAX_CANON","MAX_INPUT","MQ_OPEN_MAX","MQ_PRIO_MAX","NAME_MAX","NGROUPS_MAX","NoFollowSymlink","NoRemoveDir","NoSymlinkFollow","OPEN_MAX","PAGE_SIZE","PATH_MAX","PIPE_BUF","POSIX2_SYMLINKS","POSIX_ALLOC_SIZE_MIN","POSIX_REC_INCR_XFER_SIZE","POSIX_REC_MAX_XFER_SIZE","POSIX_REC_MIN_XFER_SIZE","POSIX_REC_XFER_ALIGN","PTHREAD_DESTRUCTOR_ITERATIONS","PTHREAD_KEYS_MAX","PTHREAD_STACK_MIN","PTHREAD_THREADS_MAX","Parent","PathconfVar","Pid","RE_DUP_MAX","ROOT","RTSIG_MAX","R_OK","RemoveDir","ResGid","ResUid","SEM_NSEMS_MAX","SEM_VALUE_MAX","SIGQUEUE_MAX","STREAM_MAX","SYMLINK_MAX","SYMLOOP_MAX","SeekCur","SeekData","SeekEnd","SeekHole","SeekSet","SymlinkFollow","SysconfVar","TIMER_MAX","TTY_NAME_MAX","TZNAME_MAX","Uid","UnlinkatFlags","User","W_OK","Whence","X_OK","_AVPHYS_PAGES","_NPROCESSORS_CONF","_NPROCESSORS_ONLN","_PHYS_PAGES","_POSIX2_CHAR_TERM","_POSIX2_C_BIND","_POSIX2_C_DEV","_POSIX2_FORT_DEV","_POSIX2_FORT_RUN","_POSIX2_LOCALEDEF","_POSIX2_PBS","_POSIX2_PBS_ACCOUNTING","_POSIX2_PBS_CHECKPOINT","_POSIX2_PBS_LOCATE","_POSIX2_PBS_MESSAGE","_POSIX2_PBS_TRACK","_POSIX2_SW_DEV","_POSIX2_UPE","_POSIX2_VERSION","_POSIX_ADVISORY_INFO","_POSIX_ASYNCHRONOUS_IO","_POSIX_ASYNC_IO","_POSIX_BARRIERS","_POSIX_CHOWN_RESTRICTED","_POSIX_CLOCK_SELECTION","_POSIX_CPUTIME","_POSIX_FSYNC","_POSIX_IPV6","_POSIX_JOB_CONTROL","_POSIX_MAPPED_FILES","_POSIX_MEMLOCK","_POSIX_MEMLOCK_RANGE","_POSIX_MEMORY_PROTECTION","_POSIX_MESSAGE_PASSING","_POSIX_MONOTONIC_CLOCK","_POSIX_NO_TRUNC","_POSIX_PRIORITIZED_IO","_POSIX_PRIORITY_SCHEDULING","_POSIX_PRIO_IO","_POSIX_RAW_SOCKETS","_POSIX_READER_WRITER_LOCKS","_POSIX_REALTIME_SIGNALS","_POSIX_REGEXP","_POSIX_SAVED_IDS","_POSIX_SEMAPHORES","_POSIX_SHARED_MEMORY_OBJECTS","_POSIX_SHELL","_POSIX_SPAWN","_POSIX_SPIN_LOCKS","_POSIX_SPORADIC_SERVER","_POSIX_SS_REPL_MAX","_POSIX_SYNCHRONIZED_IO","_POSIX_SYNC_IO","_POSIX_THREADS","_POSIX_THREAD_ATTR_STACKADDR","_POSIX_THREAD_ATTR_STACKSIZE","_POSIX_THREAD_CPUTIME","_POSIX_THREAD_PRIORITY_SCHEDULING","_POSIX_THREAD_PRIO_INHERIT","_POSIX_THREAD_PRIO_PROTECT","_POSIX_THREAD_PROCESS_SHARED","_POSIX_THREAD_ROBUST_PRIO_INHERIT","_POSIX_THREAD_ROBUST_PRIO_PROTECT","_POSIX_THREAD_SAFE_FUNCTIONS","_POSIX_THREAD_SPORADIC_SERVER","_POSIX_TIMEOUTS","_POSIX_TIMERS","_POSIX_TRACE","_POSIX_TRACE_EVENT_FILTER","_POSIX_TRACE_EVENT_NAME_MAX","_POSIX_TRACE_INHERIT","_POSIX_TRACE_LOG","_POSIX_TRACE_NAME_MAX","_POSIX_TRACE_SYS_MAX","_POSIX_TRACE_USER_EVENT_MAX","_POSIX_TYPED_MEMORY_OBJECTS","_POSIX_V6_ILP32_OFF32","_POSIX_V6_ILP32_OFFBIG","_POSIX_V6_LP64_OFF64","_POSIX_V6_LPBIG_OFFBIG","_POSIX_VDISABLE","_POSIX_VERSION","_XOPEN_CRYPT","_XOPEN_ENH_I18N","_XOPEN_LEGACY","_XOPEN_REALTIME","_XOPEN_REALTIME_THREADS","_XOPEN_SHM","_XOPEN_STREAMS","_XOPEN_UNIX","_XOPEN_VERSION","access","acct","alarm","all","as_raw","as_raw","as_raw","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chdir","chown","chroot","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","close","cmp","cmp","complement","contains","current","current","daemon","difference","dir","dup","dup2","dup3","eaccess","effective","effective","effective","effective","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","execv","execve","execveat","execvp","execvpe","extend","faccessat","fchdir","fchown","fchownat","fdatasync","fexecve","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fork","fpathconf","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_gid","from_iter","from_name","from_name","from_raw","from_raw","from_raw","from_uid","fsync","ftruncate","gecos","getcwd","getegid","geteuid","getgid","getgrouplist","getgroups","gethostname","getpgid","getpgrp","getpid","getppid","getresgid","getresuid","getsid","gettid","getuid","gid","gid","hash","hash","hash","hash","hash","hash","initgroups","insert","intersection","intersects","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","is_all","is_child","is_empty","is_parent","is_root","isatty","linkat","lseek","lseek64","mem","mkdir","mkfifo","mkfifoat","mkstemp","name","name","not","parent","partial_cmp","partial_cmp","passwd","passwd","pathconf","pause","pipe","pipe2","pivot_root","read","real","real","remove","saved","saved","set","setegid","seteuid","setfsgid","setfsuid","setgid","setgroups","sethostname","setpgid","setresgid","setresuid","setsid","setuid","shell","sleep","sub","sub_assign","symlinkat","symmetric_difference","sync","syncfs","sysconf","tcgetpgrp","tcsetpgrp","this","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","toggle","truncate","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","ttyname","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","union","unlink","unlinkat","write","child","disable","enable","cancel","set"],"q":["nix","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::dir","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::env","","","","","","","","","","","","","","","","nix::errno","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::fcntl","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::fcntl::FcntlArg","","","","","","","","","","","","nix::features","nix::ifaddrs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::kmod","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::mount","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::mqueue","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::net","nix::net::if_","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::poll","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::pty","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sched","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::aio","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::epoll","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::eventfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::inotify","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::ioctl","","","","nix::sys::memfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::mman","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::personality","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::pthread","","","nix::sys::ptrace","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::quota","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::reboot","","","","","","","","","","","","","","","","","","","","","","","nix::sys::resource","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::select","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::sendfile","","nix::sys::signal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::signal::SigHandler","","nix::sys::signal::SigevNotify","","","","","nix::sys::signalfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::socket","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::socket::ControlMessage","","","","","","","","","","nix::sys::socket::ControlMessageOwned","","","","","","","","","","","","","","","nix::sys::socket::InetAddr","","nix::sys::socket::IpAddr","","nix::sys::socket::SockAddr","","","","","","nix::sys::socket::sockopt","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::stat","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::statfs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::statvfs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::sysinfo","","","","","","","","","","","","","","","","","","","","","","nix::sys::termios","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::time","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::timer","","","","","","","","","","","","","","","","","","","nix::sys::timer::Expiration","","","","nix::sys::timerfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::timerfd::Expiration","","","","nix::sys::uio","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::utsname","","","","","","","","","","","","","","","","","","","","","nix::sys::wait","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::wait::Id","","","nix::sys::wait::WaitStatus","","","","","","","","","","","","nix::time","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::ucontext","","","","","","","","","","","","","","","","","","nix::unistd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::unistd::ForkResult","nix::unistd::acct","","nix::unistd::alarm",""],"d":["Nix’s main error type.","Common trait used to represent file system paths by many …","Nix Result Type","Create a buffer large enough for storing some control …","List directory contents","Environment variables","","","Feature tests for OS functionality","Query network interface addresses","Generates a wrapper function for an ioctl that passes no …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads data …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads an …","Generates a wrapper function for an ioctl that reads and …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads and …","Generates a wrapper function for an ioctl that writes an …","Generates a wrapper function for a ioctl that writes an …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that writes data …","Generates a wrapper function for a “bad” ioctl that …","Is the path empty?","Load and unload kernel modules.","Length of the path in bytes","","Mount file systems","Posix Message Queue functions","Functionality involving network interfaces","Wait for events to trigger on specific file descriptors","Create master and slave virtual pseudo-terminals (PTYs)","Generate an ioctl request code for a command that passes …","Generate an ioctl request code for a command that reads.","Generate an ioctl request code for a command that reads …","Generate an ioctl request code for a command that writes.","Execution scheduling","Mostly platform-specific functionality","","","Safe wrappers around functions found in libc “unistd.h”…","Execute a function with this path as a CStr.","Block device","Character device","An open directory.","Directory","A directory entry, similar to std::fs::DirEntry.","FIFO (Named pipe)","Regular file","Return type of Dir::iter.","The return type of Dir::into_iter","Unix-domain socket","Symbolic link","Type of file referenced by a directory entry","","","","","","","","","","","","","","","","","","","","","","","","Returns the bare file name of this directory entry without …","Returns the type of this directory entry, if known.","","","","","","Returns the argument unchanged.","Converts from a descriptor-based object, closing the …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts from a file descriptor, closing it on success or …","","","","","","Returns the inode number (d_ino) of the underlying dirent.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a owning iterator, that is, one that takes …","","","Returns an iterator of Result<Entry> which rewinds when …","","","Opens the given path as with fcntl::open.","Opens the given path as with fcntl::openat.","","","","","","","","","","","","","","","","","","Indicates that clearenv failed for some unknown reason","","","Clear the environment of all name-value pairs.","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The sentinel value indicates that a function failed and …","","","","","","","","","Returns the platform-specific value of errno","","","Returns the argument unchanged.","","","Calls U::from(self).","","","Returns Ok(value) if it does not contain the sentinel …","","","","","","","","","","","","","","Removes byte range from a file without leaving a hole.","Increases file space by inserting a hole within the file …","File size is not changed.","Deallocates space by creating a hole.","Shared file data extants are made private to the file.","Zeroes space in specified byte range.","The file descriptor will automatically be closed during a …","","","","","","","","","","","","The size of the file cannot be increased.","Prevents further calls to fcntl() with F_ADD_SEALS.","The file cannot be reduced in size.","The file contents cannot be modified.","","","","","","Mode argument flags for fallocate determining operation …","","Additional configuration flags for fcntl’s F_SETFD.","","","","","","Configuration options for opened files.","Mask for the access mode of the file.","Open the file in append-only mode.","Generate a signal when input or output becomes possible.","Closes the file descriptor once an execve call is made.","Create the file if it does not exist.","Try to minimize cache effects of the I/O for this file.","If the specified path isn’t a directory, fail.","Implicitly follow each write() with an fdatasync().","Error out if a file was not created.","Same as O_SYNC.","Allow files whose sizes can’t be represented in an off_t …","Same as O_NONBLOCK.","Do not update the file last access time during read(2)s.","Don’t attach the device as the process’ controlling …","open() will fail if the given path is a symbolic link.","When possible, open the file in nonblocking mode.","Obtain a file descriptor for low-level access.","Only allow reading.","Allow both reading and writing.","Similar to O_DSYNC but applies to reads instead.","Implicitly follow each write() with an fsync().","Create an unnamed temporary file.","Truncate an existing regular file to 0 length if it allows …","Only allow writing.","","","","","","","","","","","","Gift the user pages to the kernel.","Hint that more data will be coming in a subsequent splice.","Request that pages be moved instead of copied.","Do not block on I/O.","Additional flags for file sealing, which allows for …","Additional flags to splice and friends.","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Copy a range of data from one file to another","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","Manipulates file space.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","","","","","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","","","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","Check if the OS supports atomic close-on-exec for sockets","Describes a single address for an interface as returned by …","Holds the results of getifaddrs.","Network address of this interface","","","","","Broadcast address of this interface, if applicable","","","Point-to-point destination address","","","","Flags as from SIOCGIFFLAGS ioctl","","","Returns the argument unchanged.","Returns the argument unchanged.","Get interface addresses using libc’s getifaddrs","","","Name of the network interface","Calls U::from(self).","Calls U::from(self).","","Netmask of this interface","","","","","","","","","Flags used by delete_module.","Ignore symbol version hashes.","Ignore kernel version magic.","Flags used by the finit_module function.","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Unloads the kernel module with the given name.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","Loads a kernel module from a given file descriptor.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Loads a kernel module from a buffer.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","Linux 2.4.0 - Bind directory at different place","Directory modifications are synchronous","","","","Allow mandatory locks on a FS","","","","Do not update access times","Disallow access to device special files","Do not update directory access times","Disallow program execution","Ignore suid and sgid bits","","","","Mount read-only","","","Alter flags of a mounted FS","","","","","","Writes are synced at once","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Used with mq_open.","A message-queue attribute, optionally used with mq_setattr …","Identifies an open POSIX Message Queue","Set the close-on-exec flag for the message queue …","Create a message queue.","If set along with O_CREAT, mq_open will fail if the message","mq_send and mq_receive should fail with EAGAIN rather than …","Open the message queue for receiving messages.","Open the queue for both receiving and sending messages","Open the queue for sending messages.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","The number of messages currently held in the queue","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","The current flags, either 0 or O_NONBLOCK.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","The max number of messages that can be held by the queue","Size of a message queue attribute member","Close a message queue","Get message queue attributes","Open a message queue","Receive a message from a message queue","Convenience function. Removes O_NONBLOCK attribute for a …","Send a message to a message queue","Convenience function. Sets the O_NONBLOCK attribute for a …","Set the attributes of the message queue. Only O_NONBLOCK …","Remove a message queue","The maximum size of each message (in bytes)","Create a new message queue attribute","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","Network interface name resolution.","Receive all multicast packets. (see netdevice(7))","Auto media selection active. (see netdevice(7))","Valid broadcast address set. (see netdevice(7))","Internal debugging flag. (see netdevice(7))","Driver signals dormant. Volatile.","The addresses are lost when the interface goes down. (see …","Echo sent packets. Volatile.","Interface is a loopback interface. (see netdevice(7))","Driver signals L1 up. Volatile.","Master of a load balancing bundle. (see netdevice(7))","Supports multicast. (see netdevice(7))","No arp protocol, L2 destination address not set. (see …","Avoid use of trailers. (see netdevice(7))","Do not provide packet information","Interface is a point-to-point link. (see netdevice(7))","Is able to select media type via ifmap. (see netdevice(7))","Interface is in promiscuous mode. (see netdevice(7))","Resources allocated. (see netdevice(7))","Slave of a load balancing bundle. (see netdevice(7))","TAP device","TUN device (no Ethernet headers)","Interface is running. (see netdevice(7))","A network interface. Has a name like “eth0” or “…","Standard interface flags, used by getifaddrs","A list of the network interfaces available on this system. …","An iterator over the interfaces in an Interfaces.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Retrieve a list of the network interfaces available on the …","Resolve an interface into a interface number.","Obtain the index of this interface.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Iterate over the interfaces in this list.","Obtain the name of this interface.","","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Convert this to a slice of interfaces. Note that the …","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Error condition (only returned in PollFd::revents; ignored …","Hang up (only returned in PollFd::revents; ignored in …","There is data to read.","Invalid request: fd not open (only returned in …","Writing is now possible, though a write larger that the …","There is some exceptional condition on the file descriptor.","Priority band data can be read (generally unused on Linux).","Equivalent to POLLIN","Priority data may be written.","Equivalent to POLLOUT","This is a wrapper around libc::pollfd.","These flags define the different events that can be …","Returns if all the events of interest occured in the last …","Returns the set containing all flags.","Returns if any of the events of interest occured in the …","","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","The events of interest for this PollFd.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Creates a new PollFd specifying the events of interest for …","Returns the complement of this set of flags.","","poll waits for one of a set of file descriptors to become …","ppoll() allows an application to safely wait until either …","Removes the specified flags in-place.","Returns the events that occurred in the last call to poll …","Inserts or removes the specified flags depending on the …","Modify the events of interest for this PollFd.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Representation of a master with a forked pty","Representation of a master/slave pty pair","Representation of the Master device in a master/slave pty …","","","","","","","","","","","","","","","","","","","","","","","","","","","","Metadata about forked process","Create a new pseudoterminal, returning the master file …","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Grant access to a slave pseudoterminal (see grantpt(3))","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","The master port in a virtual pty pair","The master port in a virtual pty pair","Create a new pseudoterminal, returning the slave and …","Open a pseudoterminal device (see posix_openpt(3))","Get the name of the slave pseudoterminal (see ptsname(3))","Get the name of the slave pseudoterminal (see ptsname(3))","","","The slave port in a virtual pty pair","","","","","","","","","","","","","","","","Unlock a pseudoterminal master/slave pseudoterminal pair …","","","","","","","Unused since Linux 2.6.2","The calling process and the child process share the same …","The caller and the child process share the same filesystem","The new process shares an I/O context with the calling …","Create the process in a new cgroup namespace.","Create the process in a new IPC namespace.","Create the process in a new network namespace.","The cloned child is started in a new mount namespace.","Create the process in a new PID namespace.","Create the process in a new user namespace.","Create the process in a new UTS namespace.","The parent of the new child (as returned by getppid(2)) …","If the calling process is being traced, then trace the …","The calling process and the child process share the same …","The child and the calling process share a single list of …","The child is placed in the same thread group as the calling","A tracing process cannot force CLONE_PTRACE on this child …","The execution of the calling process is suspended until the","The calling process and the child process run in the same …","Type for the function executed by clone.","Options for use with clone","CpuSet represent a bit-mask of CPUs. CpuSets are used by …","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","clone create a child process (clone(2))","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Return the maximum number of CPU in CpuSet","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Test to see if a CPU is in the CpuSet. field is the CPU id …","Create a new and empty CpuSet.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","sched_getaffinity get a thread’s CPU affinity mask (…","Determines the CPU on which the calling thread is running.","sched_setaffinity set a thread’s CPU affinity mask (…","Explicitly yield the processor to other threads.","Inserts or removes the specified flags depending on the …","Add a CPU to CpuSet. field is the CPU id to add","reassociate thread with a namespace","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Remove a CPU from CpuSet. field is the CPU id to remove","disassociate parts of the process execution context","POSIX Asynchronous I/O","","","Monitoring API for filesystem events.","Provide helpers for making ioctl system calls.","Interfaces for managing memory-backed files.","Memory management declarations.","Process execution domains","Low level threading primitives","","Set and configure disk quotas for users, groups, or …","Reboot/shutdown or enable/disable Ctrl-Alt-Delete.","Configure the process resource limits.","Portably monitor a group of file descriptors for readiness.","Send data from a file to a socket, bypassing userland.","Operating system signals.","Interface for the signalfd syscall.","Socket interface functions","","Get filesystem statistics, non-portably","Get filesystem statistics","","An interface for controlling asynchronous communication …","","Timer API via signals.","Timer API via file descriptors.","Vectored I/O","Get system identification","Wait for a process to change status","Methods common to all AIO operations","All of the requests have already finished","Return values for AioCb::cancel and aio_cancel_all","All outstanding requests were canceled","An asynchronous version of fsync(2).","Mode for AioCb::fsync. Controls whether only data or both …","Some requests were not canceled. Their status should be …","Asynchronously reads from a file descriptor into a buffer","Asynchronously writes from a buffer to a file descriptor","Requests that lio_listio return immediately","Requests that lio_listio block until all requested …","Mode for lio_listio","on supported operating systems only, do it like fdatasync","do it like fsync","The return type of Aio::aio_return.","Cancels outstanding AIO requests for a given file …","Retrieve return status of an asynchronous operation.","","","","Suspends the calling process until at least one of the …","","","","","","","","","","","","","","","","","","Cancels an outstanding AIO request.","","","","","","","","","","","","","","","Retrieve error status of an asynchronous operation.","","","","Returns the underlying file descriptor associated with the …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Does this operation currently have any in-kernel state?","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Submits multiple asynchronous I/O requests with a single …","Returns the operation’s fsync mode: data and metadata or …","Returns the requested length of the aio operation in bytes","Returns the requested length of the aio operation in bytes","Construct a new AioWrite.","Create a new AioRead, placing the data in a mutable slice.","Create a new AioFsync.","Returns the file offset of the operation.","Returns the file offset of the operation.","","","Returns the priority of the AioCb","","","","Update the notification settings for an existing AIO …","","","","Returns the SigEvent that will be used for notification.","","","","Actually start the I/O operation.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Configuration options for inotify_add_watch.","File was accessed.","All of the events.","Metadata changed.","Set the FD_CLOEXEC flag on the file descriptor.","Combination of IN_CLOSE_WRITE and IN_CLOSE_NOWRITE.","Nonwritable file was closed.","Writable file was closed.","Subfile was created.","Subfile was deleted.","Self was deleted.","Don’t follow symlinks.","File was ignored.","Event occurred against directory.","File was modified.","Combination of IN_MOVED_FROM and IN_MOVED_TO.","File was moved from X.","File was moved to Y.","Self was moved.","Set the O_NONBLOCK flag on the open file description …","Only send event once.","Only watch the path if it is a directory.","File was opened.","Event queue overflowed.","Backing filesystem was unmounted.","Configuration options for inotify_init1.","An inotify instance. This is also a file descriptor, you …","A single inotify event.","This object is returned when you create a new watch on an …","Adds a new watch on the target file or directory.","Returns the set containing all flags.","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","This cookie is a number that allows you to connect related …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","Initialize a new inotify instance.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Event mask. This field is a bitfield describing the exact …","Filename. This field exists only if the event was …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","Reads a collection of events from the inotify file …","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes an existing watch using the watch descriptor …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Watch descriptor. This field corresponds to the watch …","Generate an ioctl request code for a command that passes …","Generate an ioctl request code for a command that reads.","Generate an ioctl request code for a command that reads …","Generate an ioctl request code for a command that writes.","Allow sealing operations on this file.","Set the close-on-exec (FD_CLOEXEC) flag on the new file …","Options that change the behavior of memfd_create.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Creates an anonymous file that lives in memory, and return …","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Undo the effect of an earlier MADV_DONTDUMP.","Undo the effect of MADV_DONTFORK.","Exclude the given range from a core dump.","Do not make pages in this range available to the child …","Do not expect access in the near future.","Specify that the application no longer needs the pages in …","Enable Transparent Huge Pages (THP) for pages in the given …","Poison the given pages.","Enable Kernel Samepage Merging (KSM) for the given pages.","Undo the effect of MADV_HUGEPAGE.","No further special treatment. This is the default.","Expect random page references.","Free up a given range of pages and its associated backing …","Expect sequential page references.","Preserve the memory of each page but offline the original …","Undo the effect of MADV_MERGEABLE","Expect access in the near future.","Put the mapping into the first 2GB of the process address …","Synonym for MAP_ANONYMOUS.","The mapping is not backed by any file.","Compatibility flag. Ignored.","Compatibility flag. Ignored.","Compatibility flag. Ignored.","Place the mapping at exactly the address specified in addr.","Place the mapping at exactly the address specified in addr…","Used for stacks; indicates to the kernel that the mapping …","Allocate the mapping using “huge pages.”","Make use of 16GB huge page (must be supported by the …","Make use of 16MB huge page (must be supported by the …","Make use of 1GB huge page (must be supported by the system)","Make use of 1MB huge page (must be supported by the system)","Make use of 256MB huge page (must be supported by the …","Make use of 2GB huge page (must be supported by the system)","Make use of 2MB huge page (must be supported by the system)","Make use of 32MB huge page (must be supported by the …","Make use of 512KB huge page (must be supported by the …","Make use of 512MB huge page (must be supported by the …","Make use of 64KB huge page (must be supported by the …","Make use of 8MB huge page (must be supported by the system)","Mark the mmaped region to be locked in the same way as …","Only meaningful when used with MAP_POPULATE. Don’t …","Do not reserve swap space for this mapping.","Populate page tables for a mapping.","Create a private copy-on-write mapping. Mutually exclusive …","Share this mapping. Mutually exclusive with MAP_PRIVATE.","Region grows down, like a stack.","Lock pages that are currently mapped into the address …","Lock pages which will become mapped into the address space …","Place the mapping at exactly the address specified in …","Permit the kernel to relocate the mapping to a new virtual …","Options for mremap.","Schedule an update but return immediately.","Invalidate all cached data.","Perform an update and wait for it to complete.","Additional parameters for mmap.","Flags for mlockall.","Usage information for a range of memory to allow for …","Configuration flags for msync.","Pages can be executed","Apply protection up to the end of a mapping that grows …","Apply protection down to the beginning of a mapping that …","Pages cannot be accessed.","Pages can be read.","Pages can be written.","Desired memory protection of a memory mapping.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","give advice about use of memory","Locks all memory pages that contain part of the address …","Locks all memory pages mapped into this process’ address …","allocate memory, or map files or devices into memory","Set protection of memory mapping.","Expands (or shrinks) an existing memory mapping, …","synchronize a mapped region","Unlocks all memory pages that contain part of the address …","Unlocks all memory pages mapped into this process’ …","remove a mapping","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Creates and opens a new, or opens an existing, POSIX …","Performs the converse of shm_open, removing an object …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Provide the legacy virtual address space layout.","Limit the address space to 32 bits.","Use 0xc0000000 as the offset at which to search a virtual …","Disable address-space-layout randomization.","User-space function pointers to signal handlers point to …","Map page 0 as read-only.","Flags used and returned by get() and set().","PROT_READ implies PROT_EXEC for mmap(2).","No effects.","select(2), pselect(2), and ppoll(2) do not modify the …","Have uname(2) report a 2.6.40+ version number rather than …","No effects.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Retrieve the current process personality.","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Set the current process personality.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Identifies an individual thread.","Send a signal to a thread (see pthread_kill(3)).","Obtain ID of the calling thread (see pthread_self(3)","","Using the ptrace options the tracer can configure the …","Ptrace options used in conjunction with the …","","","","Event that stops before a return from clone.","Event that stops before a return from execve.","Event for a stop before an exit. Unlike the waitpid Exit …","Event that stops before a return from fork or clone.","Stop triggered by a seccomp rule on a tracee.","Stop triggered by the INTERRUPT syscall, or a group stop, …","Event that stops before a return from vfork or clone.","Event for a return from vfork.","","","","","","","","","","Send a SIGKILL to the tracee if the tracer exits. This is …","Stop tracee at next clone call and trace the cloned …","Stop tracee at next execve call.","Stop tracee at next exit call. Stops before exit commences …","Stop tracee at next fork and start tracing the forked …","Stop tracee when a SECCOMP_RET_TRACE rule is triggered. …","When delivering system call traps set a bit to allow …","Stop tracee at next vfork call and trace the vforked …","Stop tracee at vfork completion.","","","","","","","","","","","","","","","","","","","","Ptrace Request enum defining the action to be taken.","Returns the set containing all flags.","Attach to a running process, as with …","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Restart the stopped tracee process, as with …","Returns true if all of the flags in other are contained …","Detaches the current running process, as with …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Gets a ptrace event as described by …","Get user registers, as with ptrace(PTRACE_GETREGS, ...)","Get siginfo as with ptrace(PTRACE_GETSIGINFO,...)","","","","Inserts the specified flags in-place.","Stop a tracee, as with ptrace(PTRACE_INTERRUPT, ...)","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Issues a kill request as with ptrace(PTRACE_KILL, ...)","Returns the complement of this set of flags.","","","","Reads a word from a processes memory at the given address","Reads a word from a user area at offset. The user struct …","Removes the specified flags in-place.","Attach to a running process, as with …","Inserts or removes the specified flags depending on the …","Set options, as with ptrace(PTRACE_SETOPTIONS,...).","Set user registers, as with ptrace(PTRACE_SETREGS, ...)","Set siginfo as with ptrace(PTRACE_SETSIGINFO,...)","Move the stopped tracee process forward by a single step …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Continue execution until the next syscall, as with …","Continue execution until the next syscall, as with …","Move the stopped tracee process forward by a single step …","","","","Toggles the specified flags in-place.","Sets the process as traceable, as with …","","","","","","","","","","Returns the union of between the flags in self and other.","Writes a word into the processes memory at the given …","Writes a word to a user area at offset. The user struct …","Wrapper type for if_dqblk","Specify a group quota","Use the original quota format.","Use the standard VFS v0 quota format.","Use the VFS v1 quota format.","All fields.","The block hard & soft limit fields.","The disk use time limit field.","The inode hard & soft limit fields.","The current inodes field.","The file quote time limit field.","All block & inode limits.","The current space field.","The time limit fields.","The space & inodes usage fields.","The type of quota format to use.","The scope of the quota.","Indicates the quota fields that are valid to read from.","Specify a user quota","Returns the set containing all flags.","Current number of allocated inodes.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Time limit for excessive disk use.","The absolute limit on disk quota blocks allocated.","Preferred limit on disk quota blocks","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","","","Time limit for excessive files.","Maximum number of allocated inodes.","Preferred inode limit","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Current occupied space (bytes).","","","","Get disk quota limits and current usage for the given …","Disable disk quotas for a block device.","Turn on disk quotas for a block device.","Configure quota values for the specified fields for a …","Update the on-disk copy of quota usages for a filesystem.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Set the time limit for excessive disk use.","Set the absolute limit on disk quota blocks allocated.","Set the preferred limit on disk quota blocks allocated.","Set the time limit for excessive files.","Set the maximum number of allocated inodes.","Set the preferred limit of allocated inodes.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Restart the system.","Halt the system.","Execute a kernel that has been loaded earlier with …","Stop the system and switch off power, if possible.","Suspend the system using software suspend.","How exactly should the system be rebooted.","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","Reboots or shuts down the system.","Enable or disable the reboot keystroke (Ctrl-Alt-Delete).","","","","","The maximum amount (in bytes) of virtual memory the …","The largest size (in bytes) core(5) file that may be …","The maximum amount of cpu time (in seconds) to be used by …","The maximum size (in bytes) of the data segment for a …","The largest size (in bytes) file that may be created.","A limit on the combined number of flock locks and fcntl …","The maximum size (in bytes) which a process may lock into …","A limit on the number of bytes that can be allocated for …","A ceiling to which the process’s nice value can be …","The maximum number of open files for this process.","The maximum number of simultaneous processes for this user …","When there is memory pressure and swap is available, …","A ceiling on the real-time priority that may be set for …","A limit (in microseconds) on the amount of CPU time that a …","A limit on the number of signals that may be queued for …","The maximum size (in bytes) of the stack segment for a …","","Resource usage for all the children that have terminated …","Resource usage for the current process.","Resource usage for the calling thread.","Types of process resources.","Output of getrusage with information about resource usage. …","Whose resource usage should be returned by getrusage.","","","Number of times a read was done from a block device.","Number of times a write was done to a block device.","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Number of times all of the memory was fully swapped out.","Get the current processes resource limits","Get usage information for a process, its children or the …","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Number of times a context switch was imposed by the kernel …","Number of IPC messages received.","Number of IPC messages sent.","Number of page faults that were served through I/O (i.e. …","The resident set size at its peak, in kilobytes.","Number of page faults that were served without resorting …","","","","Set the current processes resource limits","Integral value expressed in kilobytes times ticks of …","Number of signals received.","Total amount of time spent executing in kernel mode.","","","","","","","","","","","","","Integral value expressed in kilobytes times ticks of …","Integral value expressed in kilobytes times ticks of …","Total amount of time spent executing in user mode.","Number of times a context switch was voluntarily invoked.","","Contains a set of file descriptors used by select","Iterator over FdSet.","","","","","Remove all file descriptors from this FdSet.","","","Test an FdSet for the presence of a certain file …","","","Returns an iterator over the file descriptors in the set.","","","Returns the argument unchanged.","Returns the argument unchanged.","","Finds the highest file descriptor in the set.","Add a file descriptor to an FdSet","Calls U::from(self).","Calls U::from(self).","","Create an empty FdSet","","","Monitors file descriptors for readiness with an altered …","Remove a file descriptor from an FdSet","Monitors file descriptors for readiness","","","","","","","","","Copy up to count bytes to out_fd from in_fd starting at …","Copy up to count bytes to out_fd from in_fd starting at …","Use the given signal-catching function, which takes in the …","When catching a Signal::SIGCHLD signal, the signal will be …","When catching a Signal::SIGCHLD signal, the system will not","Further occurrences of the delivered signal are not masked …","The system will deliver the signal to the process on a …","The handler is reset back to the default at the moment the …","Requests that certain system calls restart if interrupted …","This flag is controlled internally by Nix.","Abort","Alarm clock","Bus error","To parent on child stop or exit","Continue a stopped process","Floating point exception","Hangup","Illegal instruction (not reset when caught)","Interrupt","Input/output possible signal","Alias for SIGABRT","Kill (cannot be caught or ignored)","Write on a pipe with no one to read it","Alias for SIGIO","Profiling time alarm","Power failure imminent.","Quit","Segmentation violation","Stack fault (obsolete)","Sendable stop signal not from tty","Bad system call","Software termination signal from kill","Trace trap (not reset when caught)","Stop signal from tty","To readers pgrp upon background tty read","Like TTIN if (tp->t_local&LTOSTOP)","Alias for SIGSYS","Urgent condition on IO channel","User defined signal 1","User defined signal 2","Virtual time alarm","Window size changes","Exceeded CPU time limit","Exceeded file size limit","The new mask is the union of the current mask and the …","The current mask is replaced by the specified set.","The new mask is the intersection of the current mask and …","Controls the behavior of a SigAction","Action to take on receipt of a signal. Corresponds to …","Use the given signal-catching function, which takes in the …","Default signal handling.","Used to request asynchronous notification of the …","A signal handler.","Request that the signal be ignored.","Specifies a set of Signals that may be blocked, waited …","Iterator for a SigSet.","No notification will be delivered","Specifies the notification method used by a SigEvent","Notify by delivering a signal to the process.","Notify by delivering a signal to a thread.","Specifies how certain functions should manipulate a signal …","Types of operating system signals","Iterate through all signals defined by this operating …","Add the specified signal to the set.","Returns the set containing all flags.","Initialize to include all signals.","Returns a mutable pointer to the sigevent wrapped by self","","","Returns name of signal.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","Remove all signals from this set.","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Return whether this set includes the specified signal.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Initialize to include nothing.","","","","","","","","","","","","Returns the flags set on the action.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Converts a libc::sigset_t object to a SigSet without …","","Returns the action’s handler.","","","","","","","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns an iterator that yields the signals contained in …","Iterate through all signals defined by this OS","Send a signal to a process","Send a signal to a process group","Returns the set of signals that are blocked during …","Creates a new action.","Note: this constructor does not allow the user to set the …","","","Returns the complement of this set of flags.","","","","Manages the signal mask (set of blocked signals) for the …","Send a signal to the current thread","Removes the specified flags in-place.","Remove the specified signal from this set.","Inserts or removes the specified flags depending on the …","Changes the action taken by a process on receipt of a …","Return a copy of the inner structure","Signal management (see signal(3p))","Examine and change blocked signals.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Adds the set of signals to the signal mask for the calling …","Gets the currently blocked (masked) set of signals for the …","Sets the set of signals as the signal mask for the calling …","Sets the set of signals as the signal mask, and returns …","Removes the set of signals from the signal mask for the …","","","","","","","","","","","","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Identifies a thread for SigevNotify::SigevThreadId","Returns the union of between the flags in self and other.","Suspends execution of the calling thread until one of the …","","","Will be present in the si_value field of the …","Will be present in the si_value field of the …","Signal to deliver","Signal to send","LWP ID of the thread to notify","","","","","","","A helper struct for creating, reading and closing a …","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","","","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","","","","Creates a new file descriptor for reading signals.","","","","","","","","","","","","","","","","","","","","","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","","","","These constants specify the protocol family to be used in …","","Interface to kernel crypto API","Socket address for the Linux kernel crypto API","Set the length of associated authentication data (AAD) …","Set IV for AF_ALG crypto API.","Set crypto operation for AF_ALG crypto API. It may be one …","AppleTalk","","Access to raw ATM PVCs","Access to ATM Switched Virtual Circuits","Amateur radio AX.25 protocol","Bluetooth low-level socket protocol","Further receptions and transmissions will be disallowed.","Can’t be used for creating sockets; mostly used for …","Ericsson’s Communication CPU to Application CPU …","Controller Area Network automotive bus protocol","","A type-safe zero-copy wrapper around a single control …","A type-safe wrapper around a single control message, as …","Supports datagrams (connectionless, unreliable messages of …","DECet protocol sockets.","Acorn Econet protocol","Non-DIX type protocol number defined for the Ethernet IEEE …","Represents a socket option that can be retrieved.","InfiniBand native addressing","IEEE 802.15.4 WPAN (wireless personal area network) raw …","","IPv4 Internet protocols (see ip(7))","IPv6 Internet protocols (see ipv6(7))","","","","Request for multicast socket operations","","","","Configure the sending addressing and interface for v4","Socket error queue control messages read with the …","","Request for ipv6 multicast socket operations","","","Configure the sending addressing and interface for v6","Socket error queue control messages read with the …","IPX - Novell protocols","Socket interface over IrDA","New “modular ISDN” driver interface protocol","IUCV (inter-user communication vehicle) z/VM protocol for …","Key management protocol.","Datalink address (MAC)","Hardware Address","Logical link control (IEEE 802.2 LLC) protocol","Set the close-on-exec flag for the file descriptor …","Receive flags: Control Data was discarded (buffer too …","Enables nonblocking operation; if the operation would …","Terminates a record (when this notion is supported, as for …","This flag specifies that queued errors should be received …","Requests not to send SIGPIPE errors when the other end …","Sends or requests out-of-band data on sockets that support …","Peeks at an incoming message. The data is treated as …","For raw (Packet), Internet datagram (since Linux …","Receive operation blocks until the full amount of data can …","Multiprotocol Label Switching","Flags for send/recv and their relatives","Preallocated structures needed for recvmmsg and sendmmsg …","Iterator over results of recvmmsg/sendmmsg","Reserved for “802.2LLC project”; never used.","AX.25 packet layer protocol. (see netrom(4))","","Kernel user interface device (see netlink(7))","Address for the Linux kernel user interface device.","Auditing (ref)","Netlink interface to request information about ciphers …","DECnet routing messages (ref)","Access to FIB lookup from user space (ref)","Transport IPv6 packets from netfilter to user space. Used …","Open-iSCSI (ref)","Kernel messages to user space (ref)","Netfilter subsystem (ref)","Infiniband RDMA (ref)","Receives routing and link updates and may be used to …","SCSI Transports (ref)","SELinux event notifications. (ref)","Query information about sockets of various protocol …","Reserved for user-mode socket protocols (ref)","Near field communication","Low level packet interface (see packet(7))","Nokia cellular modem IPC/RPC interface","Generic PPP transport layer, for setting up L2 tunnels …","Provides raw network protocol access.","Raw sockets (raw(7))","Provides a reliable datagram layer that does not guarantee …","Reliable Datagram Sockets (RDS) protocol","Further receptions will be disallowed.","Contains outcome of sending or receiving a message","RATS (Radio Amateur Telecommunications Society) Open …","Rx, Andrew File System remote procedure call protocol","SO_RXQ_OVFL indicates that an unsigned 32 bit value …","SO_RXQ_OVFL indicates that an unsigned 32 bit value …","Set close-on-exec on the new descriptor","Set non-blocking mode on the new socket","Report hardware timestamps as generated by …","Collect receiving timestamps as reported by hardware","Collect receiving timestamps as reported by software","Report any software timestamps when available.","Collect transmiting timestamps as reported by hardware","Collect transmiting timestamps as reported by software","Received version of ControlMessage::ScmCredentials","A message of type SCM_CREDENTIALS, containing the pid, uid …","Received version of ControlMessage::ScmRights","A message of type SCM_RIGHTS, containing an array of file …","A message of type SCM_TIMESTAMP, containing the time the …","Nanoseconds resolution timestamp","A set of nanosecond resolution timestamps","This was a short-lived (between Linux 2.1.30 and …","Provides a sequenced, reliable, two-way connection- based …","Represents a socket option that can be set.","","IBM SNA","Represents a socket address","Additional socket options","Constants used in socket and socketpair to specify the …","These constants are used to specify the communication …","An IPv4 socket address","An IPv6 socket address","Anything that, in C, can be cast back and forth to sockaddr…","A container for any sockaddr type","Provides sequenced, reliable, two-way, connection- based …","TCP protocol (ip(7))","Configuration flags for SO_TIMESTAMPING interface","For representing packet timestamps via SO_TIMESTAMPING …","TIPC, “cluster domain sockets” protocol","Configure the transmission time of packets.","UDP protocol (ip(7))","UDP Generic Receive Offload (GRO) allows receiving …","UDP GSO makes it possible for applications to generate …","","Local communication (see unix(7))","A wrapper around sockaddr_un.","Unix credentials of the sending process.","Unspecified address family, (see getaddrinfo(3))","","","","","","","","VMWare VSockets protocol for hypervisor-guest interaction.","Socket address for VMWare VSockets protocol","Legacy protocol for wide area network (WAN) connectivity …","Further transmissions will be disallowed.","ITU-T X.25 / ISO-8208 protocol (see x25(7))","Accept a connection on a socket","Accept a connection on a socket","Physical-layer address (MAC)","","Return the socket’s cipher name, for example sha1.","Return the socket’s cipher type, for example hash or aead…","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","","If this address represents an abstract socket, return its …","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Conversion from nix’s SockAddr type to the underlying …","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Returns a mutable pointer to the raw sockaddr_un struct","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Returns a raw pointer to the inner structure. Useful for …","Returns a raw pointer to the inner structure. Useful for …","Returns a pointer to the raw sockaddr_un struct","","","","","","","","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Safely and falliably downcast to a mutable reference","Downcast to an immutable [UnixAddr] reference.","Downcast to a mutable [UnixAddr] reference.","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Bind a name to a socket","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Context Identifier (CID)","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Iterate over the valid control messages pointed to by this …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Initiate a connection on a socket","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the address family of this socket","Return the address family of this socket","","","Returns the flow information associated with this address.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Create a new AddressFamily from an integer value retrieved …","","","","Unsafe constructor from a variable length source","","","","","","","","","","","","","","","","Look up the value of this socket option on the given …","Get the address of the peer connected to the socket fd.","Get the current address to which the socket fd is bound.","Get the current value for the requested socket option","Returns the group identifier","Return the socket’s multicast groups mask","Length of MAC address","","","","","","","","","","","","","","","","","","","","","","","","","","","ARP hardware type","hardware based timestamp","legacy timestamp, usually empty","Interface number","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Iterate over the filled io slices pointed by this msghdr","Returns the IP address associated with this socket address.","Returns the IP address associated with this socket …","Gets the IP address associated with this socket address.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Check if this address is an “unnamed” unix socket …","Return the length of valid data in the sockaddr structure.","Return the length of valid data in the sockaddr structure.","","","Listen for connections on a socket","","","","","","","","","Create a new sockaddr_un representing a filesystem path.","Construct a new socket address from its port ID and …","Construct an AF_ALG socket from its cipher name and type.","Construct a VsockAddr from its raw fields.","Instantiate a new IpMembershipRequest","Instantiate a new Ipv6MembershipRequest","Creates a new instance with the credentials of the current …","Creates a new socket address from IPv4 octets and a port …","","","","Create a new sockaddr_un representing an address in the “…","","","","","Create a new sockaddr_un representing an “unnamed” …","Create a new IpAddr that contains an IPv4 address.","Create a new IpAddr that contains an IPv6 address.","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","If this address represents a filesystem path, return that …","Returns the addrlen of this socket - …","Return the socket’s port ID.","Returns the process identifier","Packet type","Port number","Returns the port number associated with this socket …","Returns the port number associated with this socket …","Gets the port number associated with this socket address","Preallocate structure used by recvmmsg and sendmmsg takes …","Physical-layer protocol","Receive data from a connection-oriented socket. Returns …","Receive data from a connectionless or connection-oriented …","An extension of recvmsg that allows the caller to receive …","Receive message in scatter-gather vectors from a socket, …","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","","","","Returns the scope ID associated with this address.","Return the eight 16-bit segments that make up this address","Send data to a connection-oriented socket. Returns the …","An extension of sendmsg that allows the caller to transmit …","Send data in scatter-gather vectors to a socket, possibly …","Send a message to a socket","Set the value of this socket option on the given socket.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Sets the value for the requested socket option","Shut down part of a full-duplex connection.","","","","","","","","","","Return the available space in the structure","Return the available space in the structure","","","","","","Return the appropriate SockAddr type from a …","","Create an endpoint for communication","Create a pair of connected sockets","Socket options as used by setsockopt and getsockopt.","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","","","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","software based timestamp, usually one containing data","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the user identifier","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a value indicating whether or not this socket has …","","","Bind this socket to a particular device like “eth0”.","Set or get the broadcast flag.","Set or get the don’t route flag.","","Join a multicast group","Leave a multicast group.","If enabled, this boolean option allows binding to an IP …","Fetch the current system-estimated Path MTU.","Set or read a boolean integer argument that determines …","Set or read the time-to-live value of outgoing multicast …","Set or receive the Type-Of-Service (TOS) field that is …","Setting this boolean option enables transparent proxying …","The recvmsg(2) call will return the destination IP address …","Pass an IP_PKTINFO ancillary message that contains a …","Enable extended reliable error message passing.","Set or retrieve the current time-to-live field that is …","Join an IPv6 multicast group.","Set “don’t fragment packet” flag on the IPv6 packet.","Leave an IPv6 multicast group.","The recvmsg(2) call will return the destination IP address …","Control receiving of asynchronous error options.","Set delivery of the IPV6_PKTINFO control message on …","Traffic class associated with outgoing packets","Set the unicast hop limit for the socket.","The socket is restricted to sending and receiving IPv6 …","Enable sending of keep-alive messages on …","When enabled, a close(2) or shutdown(2) will not return …","Set the mark for each packet sent through this socket …","If this option is enabled, out-of-band data is directly …","","Enable or disable the receiving of the SCM_CREDENTIALS …","Return the credentials of the foreign process connected to …","Set the protocol-defined priority for all packets to be …","Sets or gets the maximum socket receive buffer in bytes.","Using this socket option, a privileged (CAP_NET_ADMIN) …","Specify the receiving timeout until reporting an error.","Enable or disable the receiving of the SO_TIMESTAMP …","Enable or disable the receiving of the SO_TIMESTAMPNS …","Enables local address reuse","Permits multiple AF_INET or AF_INET6 sockets to be bound …","Indicates that an unsigned 32-bit value ancillary message …","Specify the sending timeout until reporting an error.","Sets or gets the maximum socket send buffer in bytes.","Using this socket option, a privileged (CAP_NET_ADMIN) …","Gets the socket type as an integer.","Get and clear the pending socket error.","This option allows the caller to set the TCP congestion …","The maximum number of keepalive probes TCP should send …","The time (in seconds) the connection needs to remain idle …","The time (in seconds) between individual keepalive probes.","The maximum segment size for outgoing TCP packets.","Under most circumstances, TCP sends data when it is …","","Specifies the maximum amount of time in milliseconds that …","Specifies exact type of timestamping information collected …","Configures the behavior of time-based transmission of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Flags for fchmodat function.","","","","","“File mode / permissions” flags.","","","“File type” flags for mknod and related functions.","","","","","","","","","Read fr group.","Read for other.","Read for owner.","Read write and execute for group.","Read, write and execute for other.","Read, write and execute for owner.","Set group id on execution.","Set user id on execution.","","Write for group.","Write for other.","Write for owner.","Execute for group.","Execute for other.","Execute for owner.","Flags for utimensat function.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","Change the file permission bits of the file specified by a …","Change the file permission bits.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Change the access and modification times of the file …","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Change the access and modification times of a file without …","","","","","Create a special or ordinary file, by pathname.","Create a special or ordinary file, relative to a given …","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","","","","","","","","","","","","","","","","","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Change the access and modification times of a file.","Change the access and modification times of a file.","","","","","","","","","","","","","","","","","","","","","","Describes the file system type as known by the operating …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Describes a mounted file system","","","","","","","Size of a block","Total data blocks in filesystem","Free blocks available to unprivileged user","Free blocks in filesystem","","","","","","","","","","Total file nodes in filesystem","Free file nodes in filesystem","Filesystem ID","Magic code defining system type","Get the mount flags","","","Returns the argument unchanged.","Returns the argument unchanged.","Identifies a mounted file system","Describes a mounted file system.","Calls U::from(self).","Calls U::from(self).","Maximum length of filenames","Optimal transfer block size","Describes a mounted file system.","","","","","","","","","File system mount Flags","Append-only file","Immutable file","Allow mandatory locks on the filesystem","Do not update access times on files","Do not interpret character or block-special devices","Do not update access times on files","Do not allow execution of binaries on the filesystem","Do not allow the set-uid bits to have an effect","Read Only","Update access time relative to modify/change time","All IO should be done synchronously","Write on file/directory/symlink","Wrapper around the POSIX statvfs struct","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","get the file system block size","Get the number of blocks.","Get the number of free blocks for unprivileged users","Get the number of free blocks in the file system","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","Get the total number of file inodes","Get the number of free file inodes for unprivileged users","Get the number of free file inodes","Get the file system id","Get the mount flags","","","","","","","Get the fundamental file system block size","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Return a Statvfs object with information about fd","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Get the maximum filename length","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Return a Statvfs object with information about the path","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","System info structure returned by sysinfo.","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","Returns the load average tuple.","Current number of processes.","Returns the total amount of installed RAM in Bytes.","Returns the amount of completely unused RAM in Bytes.","Returns the amount of unused swap memory in Bytes.","Returns the amount of swap memory in Bytes.","Returns system information.","","","","","Returns the time since system boot.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Baud rates supported by the system.","","","","","","","","","","","","","","","","","","","Flags for setting the control mode of a terminal","","","","","","","","","","","","","Specify how transmission flow should be altered","Specify a combination of the input and output buffers to …","","","","","","","","","","","","","","","","","Flags for configuring the input mode of a terminal","Flags for setting any local modes","","","","","","","","","","","","","","Flags for configuring the output mode of a terminal","","","","","Specify when a port configuration change should occur.","Indices into the termios.c_cc array for special characters.","","","","","","Flush data that was received but not read","Transmit a STOP character, which should disable a …","Flush both received data not read and written data not …","Transmit a START character, which should re-enable a …","Flush data written but not transmitted","Suspend transmission","Resume transmission","The change occurs after all output has been written","Same as TCSADRAIN, but will also flush the input buffer","The change will occur immediately","","Stores settings for the termios API","","","","","","","","","","","","","","","","","","","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","Get input baud rate (see cfgetispeed(3p)).","Get output baud rate (see cfgetospeed(3p)).","Configures the port to something like the “raw” mode …","Set input baud rate (see cfsetispeed(3p)).","Set output baud rate (see cfsetospeed(3p)).","Set both the input and output baud rates (see termios(3)).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Control characters (see termios.c_cc documentation)","Control mode flags (see termios.c_cflag documentation)","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","","","Input mode flags (see termios.c_iflag documentation)","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Line discipline (see termios.c_line documentation)","Local mode flags (see termios.c_lflag documentation)","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Output mode flags (see termios.c_oflag documentation)","","","","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Block until all output data is written (see tcdrain(3p)).","Suspend or resume the transmission or reception of data …","Discard data in the output or input queue (see tcflush(3p)…","Return the configuration of a port tcgetattr(3p)).","Get the session controlled by the given terminal (see …","Send a break for a specific duration (see tcsendbreak(3p)).","Set the configuration for a terminal (see tcsetattr(3p)).","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","Calls U::from(self).","Calls U::from(self).","","Makes a new TimeSpec with given number of microseconds.","Makes a new TimeVal with given number of microseconds.","","","","","","","","Makes a new TimeSpec with given number of nanoseconds.","Makes a new TimeVal with given number of nanoseconds. …","","","Construct a new TimeSpec from its components","Construct a new TimeVal from its components","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","An enumeration allowing the definition of the expiration …","Alarm will trigger every specified interval of time.","Alarm will trigger after a specified delay and then every …","Alarm will trigger once after the time given in TimeSpec","A Unix signal per-process timer.","Flags that are used for arming the timer.","","","","","Returns the argument unchanged.","Get the parameters for the alarm currently set, if any.","Calls U::from(self).","Creates a new timer based on the clock defined by clockid. …","Return the number of timers that have overrun","Set a new alarm on the timer.","","","","","","","","Like CLOCK_MONOTONIC, except that CLOCK_BOOTTIME includes …","Like CLOCK_BOOTTIME, but will wake the system if it is …","A non-settable monotonically increasing clock.","A settable system-wide real-time clock.","Like CLOCK_REALTIME, but will wake the system if it is …","The type of the clock used to mark the progress of the …","An enumeration allowing the definition of the expiration …","Alarm will trigger every specified interval of time.","Alarm will trigger after a specified delay and then every …","Alarm will trigger once after the time given in TimeSpec","Set the FD_CLOEXEC flag on the file descriptor.","Set the O_NONBLOCK flag on the open file description …","","A timerfd instance. This is also a file descriptor, you …","Additional flags to change the behaviour of the file …","Flags that are used for arming the timer.","Returns the set containing all flags.","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","Get the parameters for the alarm currently set, if any.","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Creates a new timer based on the clock defined by clockid. …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Sets a new alarm on the timer.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Remove the alarm if any is set.","Wait for the configured alarm to expire.","","","","","A vector of buffers.","A slice of memory in a remote process, starting at address …","View the IoVec as a Rust slice.","The starting address of this slice (iov_base).","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Create an IoVec from a mutable Rust slice.","Create an IoVec from a Rust slice.","","","Calls U::from(self).","Calls U::from(self).","The number of bytes in this slice (iov_len).","Low-level read from a file, with specified offset.","Read from fd at offset filling buffers in iov.","Read data directly from another process’s virtual memory …","Write data directly to another process’s virtual memory …","Low-level write to a file, with specified offset.","Write to fd at offset from buffers in iov.","Low-level vectored read from a raw file descriptor","","","","","","","","","Low-level vectored write to a raw file descriptor","Describes the running system. Return type of uname.","","","","","NIS or YP domain name of this machine.","","","Returns the argument unchanged.","","Calls U::from(self).","Machine hardware platform.","Network name of this machine.","Release level of the operating system.","Name of the operating system implementation.","","","","","Get system identification","Version level of the operating system.","Wait for any child","The process was previously stopped but has resumed …","The process exited normally (as with exit() or returning …","The ID argument for waitid","Wait for the child whose process group ID matches the …","Wait for the child referred to by the given PID file …","Wait for the child whose process ID matches the given PID","The traced process was stopped by a PTRACE_EVENT_* event. …","The traced process was stopped by execution of a system …","The process was killed by the given signal. The third field","There are currently no state changes to report in any …","The process is alive, but was stopped by the given signal. …","Report the status of selected processes that have …","Report the status of selected processes which have …","Do not block when there are no processes wishing to report …","Don’t reap, just poll status.","An alias for WUNTRACED.","Report the status of selected processes which are stopped …","Controls the behavior of waitpid.","Possible return values from wait() or waitpid().","Wait on all children, regardless of type","Wait for “clone” children only.","Don’t wait on children of other threads in this group","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Convert a raw wstatus as returned by waitpid/wait into a …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","","Extracts the PID from the WaitStatus unless it equals …","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","Wait for any child process to change status or a signal is …","Wait for a process to change status","Wait for a process to change status","","","","","","","","","","","","","","","","","","","","","","","","","","","Clock identifier","Gets the raw clockid_t wrapped by self","","","Get the clock id of the specified process id, (see …","Get the resolution of the specified clock, (see …","Get the time of the specified clock, (see clock_gettime(2)…","Set the time of the specified clock, (see clock_settime(2)…","","","","","","","","Returns the argument unchanged.","Creates ClockId from raw clockid_t","","Calls U::from(self).","Returns the current time on the clock id","","Returns ClockId of a pid CPU-time clock","Returns resolution of the clock id","Sets time to timespec on the clock id","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","Maximum number of I/O operations in a single list I/O call …","Maximum number of outstanding asynchronous I/O operations …","The maximum amount by which a process can decrease its …","Maximum length of argument to the exec functions including …","Maximum number of functions that may be registered with …","Options for access()","Maximum obase values allowed by the bc utility.","Maximum number of elements permitted in an array by the bc …","Maximum scale value allowed by the bc utility.","Maximum length of a string constant accepted by the bc …","Maximum number of simultaneous processes per real user ID.","","Maximum number of weights that can be assigned to an entry …","","Maximum number of timer expiration overruns.","Maximum number of expressions that can be nested within …","Minimum number of bits needed to represent, as a signed …","Test for existence of file.","Flags for fchownat function.","","Represents the successful result of calling fork","Initial size of getgrgid_r and getgrnam_r data buffers","Initial size of getpwuid_r and getpwnam_r data buffers","Group identifier","Representation of a Group, based on libc::group","Maximum length of a host name (not including the …","Maximum number of iovec structures that one process has …","Unless otherwise noted, the maximum length, in bytes, of a …","Maximum number of links to a single file.","Maximum length of a login name.","Flags for linkat function.","Maximum number of bytes in a terminal canonical input line.","Minimum number of bytes for which space is available in a …","The maximum number of open message queue descriptors a …","The maximum number of message priorities supported by the …","Maximum number of bytes in a filename (not including the …","Maximum number of simultaneous supplementary group IDs per …","","","","A value one greater than the maximum value that the system …","The size of a system page in bytes.","Maximum number of bytes the implementation will store as a …","Maximum number of bytes that is guaranteed to be atomic …","Symbolic links can be created.","Minimum number of bytes of storage actually allocated for …","Recommended increment for file transfer sizes between the …","Maximum recommended file transfer size.","Minimum recommended file transfer size.","Recommended file transfer buffer alignment.","","","","","","Variable names for pathconf","Process identifier","","Constant for UID = 0","","Test for read permission.","","Real, effective and saved group IDs.","Real, effective and saved user IDs.","","","","","Maximum number of bytes in a symbolic link.","","Specify an offset relative to the current file location.","Specify an offset relative to the next location in the …","Specify an offset relative to the end of the file.","Specify an offset relative to the next hole in the file …","Specify an offset relative to the start of the file.","","Variable names for sysconf","","","","User identifier","Flags for unlinkat function.","Representation of a User, based on libc::passwd","Test for write permission.","Directive that tells lseek and lseek64 what the offset is …","Test for execute (search) permission.","The number of currently available pages of physical memory.","The number of processors configured.","The number of processors currently online (available).","The number of pages of physical memory. Note that it is …","The implementation supports the Terminal Characteristics …","The implementation supports the C-Language Binding option.","The implementation supports the C-Language Development …","The implementation supports the FORTRAN Development …","The implementation supports the FORTRAN Runtime Utilities …","The implementation supports the creation of locales by the …","The implementation supports the Batch Environment Services …","The implementation supports the Batch Accounting option.","The implementation supports the Batch Checkpoint/Restart …","The implementation supports the Locate Batch Job Request …","The implementation supports the Batch Job Message Request …","The implementation supports the Track Batch Job Request …","The implementation supports the Software Development …","The implementation supports the User Portability Utilities …","Integer value indicating version of the Shell and …","The implementation supports the Advisory Information …","The implementation supports asynchronous input and output.","Asynchronous input or output operations may be performed …","The implementation supports barriers.","The use of chown and fchown is restricted to a process with","The implementation supports clock selection.","The implementation supports the Process CPU-Time Clocks …","The implementation supports the File Synchronization …","The implementation supports the IPv6 option.","The implementation supports job control.","The implementation supports memory mapped Files.","The implementation supports the Process Memory Locking …","The implementation supports the Range Memory Locking …","The implementation supports memory protection.","The implementation supports the Message Passing option.","The implementation supports the Monotonic Clock option.","Pathname components longer than {NAME_MAX} generate an …","The implementation supports the Prioritized Input and …","The implementation supports the Process Scheduling option.","Prioritized input or output operations may be performed …","The implementation supports the Raw Sockets option.","The implementation supports read-write locks.","The implementation supports realtime signals.","The implementation supports the Regular Expression …","Each process has a saved set-user-ID and a saved …","The implementation supports semaphores.","The implementation supports the Shared Memory Objects …","The implementation supports the POSIX shell.","The implementation supports the Spawn option.","The implementation supports spin locks.","The implementation supports the Process Sporadic Server …","","The implementation supports the Synchronized Input and …","Synchronized input or output operations may be performed …","The implementation supports threads.","The implementation supports the Thread Stack Address …","The implementation supports the Thread Stack Size …","The implementation supports the Thread CPU-Time Clocks …","The implementation supports the Thread Execution …","The implementation supports the Non-Robust Mutex Priority …","The implementation supports the Non-Robust Mutex Priority …","The implementation supports the Thread Process-Shared …","The implementation supports the Robust Mutex Priority …","The implementation supports the Robust Mutex Priority …","The implementation supports thread-safe functions.","The implementation supports the Thread Sporadic Server …","The implementation supports timeouts.","The implementation supports timers.","The implementation supports the Trace option.","The implementation supports the Trace Event Filter option.","","The implementation supports the Trace Inherit option.","The implementation supports the Trace Log option.","","","","The implementation supports the Typed Memory Objects …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","This symbol shall be defined to be the value of a …","Integer value indicating version of this standard …","The implementation supports the X/Open Encryption Option …","The implementation supports the Issue 4, Version 2 Enhanced","","The implementation supports the X/Open Realtime Option …","The implementation supports the X/Open Realtime Threads …","The implementation supports the Issue 4, Version 2 Shared …","The implementation supports the XSI STREAMS Option Group.","The implementation supports the XSI option","Integer value indicating version of the X/Open Portability …","Checks the file named by path for accessibility according …","","Alarm signal scheduling.","Returns the set containing all flags.","Get the raw uid_t wrapped by self.","Get the raw gid_t wrapped by self.","Get the raw pid_t wrapped by self.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Change the current working directory of the calling …","Change the ownership of the file at path to be owned by …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Close a raw file descriptor","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns Uid of calling process. This is practically a more …","Returns Gid of calling process. This is practically a more …","Daemonize this process by detaching from the controlling …","Returns the difference between the flags in self and other.","Home directory","Create a copy of the specified file descriptor (see dup(2)…","Create a copy of the specified file descriptor using the …","Create a new copy of the specified file descriptor using …","Checks the file named by path for accessibility according …","Returns effective Uid of calling process. This is …","Returns effective Gid of calling process. This is …","","","Returns an empty set of flags.","","","","","","","","","","","Replace the current process image with a new one (see …","Replace the current process image with a new one (see …","Execute program relative to a directory file descriptor …","Replace the current process image with a new one and …","Replace the current process image with a new one and …","","Checks the file named by path for accessibility according …","Change the current working directory of the process to the …","Change the ownership of the file referred to by the open …","Change the ownership of the file at path to be owned by …","Synchronize the data of a file","Replace the current process image with a new one (see …","","","","","","","","","","","","","","","","","","","","","","","Create a new child process duplicating the parent process (…","Like pathconf, but works with file descriptors instead of …","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Get a group by GID.","","Get a user by name.","Get a group by name.","Creates Uid from raw uid_t.","Creates Gid from raw gid_t.","Creates Pid from raw pid_t.","Get a user by UID.","Synchronize changes to a file","Truncate a file to a specified length","User information","Returns the current directory as a PathBuf","Get the effective group ID","Get the effective user ID","Get the real group ID","Calculate the supplementary group access list.","Get the list of supplementary group IDs of the calling …","Get the host name and store it in an internally allocated …","","Get the group id of the calling process (see getpgrp(3)).","Get the pid of this process (see getpid(2)).","Get the pid of this processes’ parent (see getpid(2)).","Gets the real, effective, and saved group IDs.","Gets the real, effective, and saved user IDs.","Get the process group ID of a session leader getsid(2).","Get the caller’s thread ID (see gettid(2).","Get a real user ID","Group ID","Group ID","","","","","","","Initialize the supplementary group access list.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Return true if this is the child process of the fork()","Returns true if no flags are currently stored.","Returns true if this is the parent process of the fork()","Returns true if the Uid represents privileged user - root. …","","Link one file to another file","Move the read/write file offset.","","List of Group members","Creates new directory path with access rights mode. (see …","Creates new fifo special file (named pipe) with path path …","Creates new fifo special file (named pipe) with path path …","Creates a regular file which persists even after process …","Username","Group name","Returns the complement of this set of flags.","Returns PID of parent of calling process","","","User password (probably hashed)","Group password","Get path-dependent configurable system variables (see …","Suspend the thread until a signal is received.","Create an interprocess channel.","Like pipe, but allows setting certain file descriptor …","","Read from a raw file descriptor.","","","Removes the specified flags in-place.","","","Inserts or removes the specified flags depending on the …","Set the effective group ID","Set the effective user ID","Set the group identity used for filesystem checks …","Set the user identity used for filesystem checks …","Set the group ID","Set the list of supplementary group IDs for the calling …","Set the system host name (see sethostname(2)).","Set a process group ID (see setpgid(2)).","Sets the real, effective, and saved gid. (see setresuid(2))","Sets the real, effective, and saved uid. (see setresuid(2))","Create new session and set process group id (see setsid(2)…","Set the user ID","Path to shell","Suspend execution for an interval of time","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Creates a symbolic link at path2 which points to path1.","Returns the symmetric difference between the flags in self …","Commit filesystem caches to disk","Commit filesystem caches containing file referred to by …","Get configurable system variables (see sysconf(3))","Get the terminal foreground process group (see tcgetpgrp(3)…","Set the terminal foreground process group (see tcgetpgrp(3)…","Returns PID of calling process","","","","","","","","","","","","","","","","","","","Toggles the specified flags in-place.","Truncate a file to a specified length","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the name of the terminal device that is open on file …","","","","","","","","","","","","","","","","User ID","Returns the union of between the flags in self and other.","Remove a directory entry","Remove a directory entry","Write to a raw file descriptor.","","Disable process accounting","Enable process accounting","Cancel an previously set alarm signal.","Schedule an alarm signal."],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,341,0,341,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,341,8,8,0,8,0,8,8,0,0,8,8,0,4,6,4,9,6,7,8,4,9,6,7,8,7,8,7,8,4,9,4,9,6,7,8,7,7,4,9,6,7,8,4,4,9,6,7,8,4,4,9,6,7,8,7,4,9,6,7,8,4,9,6,4,9,6,4,4,7,8,4,9,6,7,8,4,9,6,7,8,4,9,6,7,8,0,20,20,0,20,20,20,20,20,20,20,20,20,20,20,20,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,0,0,23,23,23,23,23,23,23,23,0,23,23,23,0,23,23,23,23,23,26,23,23,23,23,23,23,29,29,29,29,29,0,34,34,34,34,34,34,32,42,42,42,42,42,42,42,42,42,42,42,31,31,31,31,42,42,42,42,42,0,0,0,0,38,38,38,38,0,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,39,39,39,39,39,39,0,30,30,30,0,33,33,33,33,0,0,38,38,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,38,33,34,39,29,16,30,31,32,38,33,34,39,29,16,30,31,32,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,0,0,0,29,29,29,29,29,16,16,16,16,16,30,30,30,30,30,31,31,31,31,31,32,32,32,32,32,42,38,33,33,33,33,33,34,34,34,34,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,0,29,16,30,31,32,33,34,39,0,0,0,0,29,16,30,31,32,33,34,0,0,29,16,30,31,32,33,34,0,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,29,16,30,31,32,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,0,342,343,344,345,346,347,348,349,350,351,352,353,0,0,0,46,46,47,46,47,46,46,46,46,47,46,47,46,46,47,46,47,0,46,47,46,46,47,47,46,47,46,46,47,46,47,46,47,0,48,48,0,49,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,0,48,49,48,49,48,49,48,49,0,48,48,48,48,48,49,49,49,49,49,48,49,48,49,48,49,48,49,48,49,48,49,0,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,51,51,51,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,50,50,50,50,51,51,51,51,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,0,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,0,0,50,51,0,0,0,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,54,56,53,54,56,53,54,53,54,53,53,53,54,53,53,53,54,53,54,53,53,53,53,53,54,56,53,54,56,53,53,53,53,53,54,53,53,53,53,54,56,53,53,54,0,0,0,0,0,0,0,0,0,0,54,54,53,53,53,53,53,53,53,53,54,53,53,54,56,53,54,56,53,54,56,53,0,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,0,0,0,0,57,57,57,57,57,57,57,57,59,58,57,60,59,58,57,60,57,57,57,57,57,57,58,57,57,57,59,58,57,57,57,57,57,60,59,58,57,60,57,57,57,57,57,0,0,59,57,57,57,59,58,57,60,58,60,57,57,58,59,60,57,57,57,57,57,57,57,57,58,57,59,58,57,60,59,58,57,60,59,58,57,60,57,62,62,62,62,62,62,62,62,62,62,0,0,61,62,61,61,62,62,62,62,62,62,62,61,62,61,62,61,62,61,62,62,62,62,62,62,61,62,61,62,61,62,62,62,62,62,61,62,62,62,62,62,61,62,62,62,62,61,62,62,62,61,62,62,0,0,62,61,62,61,62,62,62,61,62,62,61,62,61,62,61,62,62,0,0,0,0,0,66,67,68,69,66,67,68,69,66,67,68,69,67,68,69,66,67,68,66,66,66,67,68,69,66,69,0,75,67,68,69,66,0,67,68,66,67,68,69,66,66,68,69,0,0,0,0,66,66,68,67,68,69,67,68,69,66,67,68,69,66,67,68,69,66,0,66,66,67,67,67,67,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,76,76,76,76,76,76,76,76,76,78,76,78,0,76,78,76,78,76,76,76,78,78,76,76,76,78,76,76,76,76,76,76,78,76,78,76,76,76,76,76,78,76,76,76,76,78,76,76,78,78,76,76,76,0,0,0,0,76,78,0,76,76,76,76,78,76,76,78,76,78,76,78,76,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,0,79,0,0,79,0,0,85,85,0,86,86,354,0,354,81,82,83,0,81,82,81,82,83,81,82,83,79,85,86,81,82,83,79,85,86,354,81,82,83,79,85,86,79,85,86,85,86,79,85,86,354,81,82,83,354,81,82,83,81,82,83,79,85,86,81,82,83,79,85,86,79,85,86,354,81,82,83,81,82,83,79,85,86,0,83,81,82,81,82,83,81,82,85,86,354,81,82,83,354,81,82,83,354,81,82,83,354,81,82,83,79,85,86,81,82,83,79,85,86,86,81,82,83,79,85,86,81,82,83,79,85,86,89,89,89,89,89,89,89,89,89,89,89,89,89,89,89,90,0,91,91,91,0,0,0,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,91,90,92,89,91,90,92,89,91,90,92,89,91,90,92,89,90,89,90,89,90,92,89,90,89,90,92,0,0,0,0,89,91,90,92,92,89,90,89,89,89,89,89,91,90,90,90,90,90,92,89,91,90,92,89,90,89,90,89,90,89,90,89,91,90,92,89,90,89,90,89,90,89,91,90,92,89,90,89,90,92,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,91,90,92,89,90,89,91,90,92,89,91,90,92,89,91,90,92,89,90,94,94,94,0,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,0,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,0,96,96,96,98,96,96,96,96,96,96,96,96,96,96,96,96,96,96,98,96,96,96,96,96,0,0,0,0,95,96,98,95,96,98,96,98,96,98,96,98,96,98,96,98,96,98,96,98,95,97,99,96,98,95,97,99,96,98,95,97,96,98,95,97,96,98,97,96,98,96,98,99,96,98,96,98,96,98,97,96,98,96,96,96,96,96,98,98,98,98,98,95,97,99,96,98,95,97,99,96,98,96,98,96,98,96,98,95,96,98,97,95,96,98,96,98,96,98,96,98,95,97,99,96,98,96,98,99,99,96,98,96,98,97,95,96,98,95,96,98,96,98,96,98,96,98,96,98,95,97,96,98,96,98,95,97,99,96,98,95,97,99,96,98,95,97,99,96,98,99,0,0,0,0,101,101,0,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,0,101,101,101,101,101,101,101,101,101,101,101,101,101,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,106,106,104,104,0,105,105,105,0,0,0,0,102,102,102,102,102,102,0,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,102,102,102,102,103,103,103,103,103,104,104,104,104,104,107,105,105,105,105,105,106,106,106,106,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,0,0,0,0,0,0,0,0,0,0,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,0,0,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,105,106,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,0,0,0,0,0,0,114,114,114,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,113,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,0,113,0,113,113,113,113,113,113,113,114,115,113,114,115,113,114,115,113,114,115,113,114,115,113,113,0,113,0,113,113,114,115,113,113,114,115,113,113,113,113,113,114,115,113,113,113,113,113,0,0,0,114,115,113,113,0,113,113,114,115,113,113,113,0,113,114,115,113,0,0,113,0,113,0,0,0,0,113,113,113,0,0,0,114,115,113,113,0,114,115,113,114,115,113,114,115,113,113,0,0,0,123,124,124,124,121,121,121,121,121,121,121,121,121,121,0,0,0,123,121,122,121,121,121,121,121,121,121,122,122,122,123,124,122,121,123,124,122,121,123,124,122,121,123,124,122,121,123,124,121,121,121,122,121,121,121,123,124,122,121,121,123,124,122,121,121,121,121,121,123,124,122,121,121,121,121,121,123,124,122,121,122,122,122,121,121,121,123,124,122,121,121,121,121,122,123,124,121,0,0,0,0,0,121,121,122,122,122,122,122,122,121,121,121,123,124,122,121,121,123,124,122,121,123,124,122,121,123,124,122,121,121,125,125,125,125,125,0,125,125,125,125,125,125,125,125,125,125,125,0,0,125,125,125,125,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,0,130,130,130,0,0,0,127,127,127,127,129,130,127,129,130,127,129,130,127,129,130,127,129,130,129,130,127,129,130,127,129,130,127,127,0,0,129,130,127,129,130,127,127,127,127,127,127,127,129,130,0,0,127,127,127,129,130,127,129,130,127,129,130,127,129,130,127,127,127,127,127,0,0,0,133,134,133,134,133,133,133,133,133,133,133,133,134,133,134,133,133,133,133,134,134,133,134,134,0,133,0,134,133,133,134,133,134,133,134,0,0,142,136,136,136,136,136,136,136,116,116,116,116,116,116,116,116,116,116,0,116,116,0,116,116,116,116,116,116,116,116,116,116,116,116,0,116,116,116,116,116,116,116,140,140,140,0,0,142,142,0,0,142,0,0,87,0,87,87,0,0,0,65,136,65,88,116,65,116,136,136,136,136,136,136,136,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,65,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,116,136,140,136,136,65,136,136,65,116,139,136,140,65,142,143,87,88,136,65,143,116,116,139,136,136,136,136,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,88,136,136,136,136,65,65,116,143,116,139,136,140,65,142,143,87,88,136,136,136,116,139,136,140,65,141,142,143,87,88,139,65,141,136,136,65,116,0,0,143,143,88,139,141,136,116,136,140,0,0,136,65,136,0,88,0,0,136,136,136,65,65,65,65,65,116,139,136,140,65,141,142,143,87,88,116,136,116,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,0,136,65,355,356,357,358,357,358,358,144,144,0,0,0,0,0,144,145,144,144,144,144,144,144,144,146,144,145,146,144,145,146,144,146,144,144,144,144,144,145,144,146,144,145,144,146,144,144,144,144,144,145,146,144,145,144,144,144,144,146,144,145,144,144,144,146,144,145,145,144,144,145,145,144,144,145,144,144,145,0,0,0,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,144,144,144,146,144,144,146,144,145,146,144,145,146,144,145,144,145,184,152,0,155,187,0,181,181,181,187,187,187,187,187,187,182,187,187,187,0,0,0,172,187,187,173,0,187,187,155,187,187,0,0,0,0,0,179,179,181,179,0,0,179,179,181,179,187,187,187,187,187,155,0,187,151,151,151,151,151,151,151,151,151,151,187,0,0,0,187,187,155,187,0,173,173,173,173,173,173,173,173,173,173,173,173,173,173,187,187,187,187,172,173,172,187,182,0,187,187,179,181,147,147,150,150,150,150,150,150,179,181,179,181,179,179,179,187,172,0,0,187,0,0,0,0,0,0,0,0,172,173,0,0,187,181,173,179,181,155,187,0,0,187,185,186,185,186,200,206,155,187,0,187,182,187,0,0,148,177,149,149,150,147,151,152,153,154,154,155,154,154,153,154,154,168,168,153,153,157,149,148,162,164,166,154,154,154,154,154,154,154,154,0,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,177,162,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,150,147,151,171,171,171,0,177,150,147,151,0,150,147,151,183,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,150,147,151,168,168,155,177,164,158,167,165,156,169,170,171,153,153,154,154,157,157,149,149,148,148,162,162,172,173,150,150,150,150,150,147,147,147,147,147,151,151,151,151,151,174,175,177,178,179,180,181,189,190,191,182,183,155,155,164,164,166,166,184,184,152,152,185,185,186,186,187,158,167,165,156,169,170,171,153,154,154,154,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,183,155,164,164,166,166,184,152,185,186,187,150,147,151,150,147,151,150,147,151,187,150,147,151,168,153,154,157,149,148,162,155,164,166,184,152,185,186,164,166,200,0,0,0,183,157,148,158,167,165,156,169,170,171,153,154,157,149,148,162,173,150,147,151,182,155,164,166,184,152,185,186,187,148,180,180,148,150,147,151,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,178,190,191,177,164,166,186,150,147,151,150,147,151,153,168,168,153,154,0,170,170,170,170,170,170,170,0,153,157,149,162,174,175,183,166,184,152,186,153,155,155,155,155,153,185,185,155,178,190,191,150,147,151,152,150,147,151,153,153,157,183,148,162,164,166,186,189,148,0,0,0,0,150,147,151,158,158,0,164,184,0,0,0,0,206,150,147,151,0,0,165,165,165,165,165,167,167,167,167,168,168,153,0,0,0,0,0,0,0,0,0,169,150,147,151,150,147,151,156,156,150,147,151,180,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,184,152,185,186,155,186,153,154,157,149,148,162,155,164,166,184,152,185,186,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,183,150,147,151,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,380,381,382,383,384,385,386,387,388,389,390,391,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,265,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,228,229,230,231,232,233,234,235,236,239,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,0,0,0,275,276,0,275,276,0,273,273,273,273,273,273,273,273,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,0,273,17,273,17,273,17,273,17,273,17,273,17,273,17,273,17,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,273,17,273,17,273,17,0,273,17,273,17,272,273,17,273,17,0,0,272,273,273,273,273,273,17,17,17,17,17,275,276,272,273,17,275,276,273,17,273,17,273,17,273,17,0,0,0,272,273,17,273,17,273,17,273,17,272,273,17,275,276,273,17,273,17,0,0,0,0,0,0,0,0,0,273,17,273,17,273,17,273,17,272,272,272,272,272,272,272,272,272,272,272,272,272,272,272,272,0,273,17,273,17,273,17,272,273,17,275,276,273,17,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,0,273,17,0,0,280,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,278,278,278,280,278,280,278,280,278,280,280,278,278,278,278,278,278,280,278,280,0,0,278,280,278,278,0,278,280,278,280,278,280,278,280,0,282,282,282,282,282,282,282,282,282,282,282,282,0,282,282,282,282,282,282,282,282,283,283,283,283,282,283,282,283,282,283,282,283,282,282,282,282,282,282,282,283,282,283,283,283,283,283,282,282,282,282,282,283,283,282,283,282,282,282,282,0,282,283,282,282,282,282,283,282,282,283,282,282,282,282,0,282,282,282,282,283,282,282,283,282,283,282,283,282,0,286,286,286,286,286,286,286,286,286,286,286,286,286,286,286,0,286,286,286,286,286,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,288,289,289,289,0,290,290,290,290,290,289,289,289,289,289,290,290,290,290,290,290,290,290,0,291,291,291,291,291,291,291,291,289,289,289,291,0,0,290,291,288,291,288,288,288,288,288,288,291,288,288,288,288,288,0,0,0,289,289,289,291,289,289,289,289,289,289,289,289,0,290,288,290,291,0,0,289,289,289,289,289,295,296,295,296,295,296,296,294,294,294,291,0,297,297,297,297,297,297,297,297,297,297,297,297,297,297,297,289,289,289,297,297,289,0,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,0,0,0,0,0,0,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,73,73,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,288,288,288,288,289,289,289,289,289,290,290,290,290,290,291,291,291,291,291,73,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,293,294,295,296,297,288,289,290,291,73,288,289,290,291,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,73,73,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,0,0,0,0,0,0,0,73,293,294,295,296,297,288,289,290,291,288,289,290,291,73,293,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,0,0,0,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,64,132,132,64,64,64,132,132,64,64,64,132,392,64,132,392,64,132,392,64,132,392,64,132,392,64,132,64,132,64,132,392,392,64,132,392,64,132,392,392,64,132,392,64,132,64,132,392,64,132,64,132,0,0,64,132,64,132,64,132,64,132,64,64,132,132,64,132,392,0,306,306,306,0,0,305,305,305,305,305,305,305,305,305,305,305,305,305,393,394,395,394,311,311,311,311,311,0,0,306,306,306,309,309,308,0,0,0,308,309,310,308,309,308,309,308,309,308,309,308,309,308,309,308,309,306,308,310,311,309,306,308,310,311,309,306,308,311,309,306,308,311,309,308,311,309,308,309,308,309,308,309,310,308,309,306,308,311,309,308,309,306,308,308,308,308,308,310,311,309,309,309,309,309,306,308,310,311,309,308,309,308,309,308,309,308,309,310,310,308,311,309,308,309,308,309,308,309,306,308,310,311,309,308,309,308,309,310,308,309,308,311,309,308,309,308,310,309,308,309,308,309,308,309,306,308,311,309,308,309,306,308,310,311,309,306,308,310,311,309,306,308,310,311,309,308,309,310,310,393,394,395,394,0,0,312,313,313,312,313,312,313,312,313,312,313,312,313,312,313,312,312,312,313,312,313,312,313,0,0,0,0,0,0,0,313,312,313,312,313,312,313,312,0,0,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,0,315,319,318,318,0,319,319,319,318,318,318,318,318,317,317,317,317,317,317,0,0,317,317,317,317,317,317,317,317,317,317,317,317,318,319,317,318,319,317,318,319,317,318,319,317,319,317,317,317,317,317,318,319,317,317,317,317,317,317,318,319,317,318,319,317,317,317,317,318,317,318,319,317,317,317,317,318,319,317,317,317,317,319,318,317,317,317,317,317,317,318,319,317,317,318,319,317,318,319,317,318,319,317,0,0,0,396,397,398,399,400,401,402,403,404,399,400,401,402,400,402,307,307,307,307,307,307,307,307,307,307,307,0,307,307,307,0,0,0,0,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,0,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,331,331,331,331,331,0,331,331,331,331,331,331,331,325,331,331,330,322,0,326,0,331,331,0,0,331,331,331,330,331,0,330,330,331,331,330,331,326,329,328,331,331,330,330,330,330,330,330,330,330,331,331,331,331,325,0,0,331,0,331,322,329,0,0,331,331,331,331,330,331,327,327,327,327,327,328,0,331,331,331,0,0,0,322,0,322,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,330,331,331,331,331,331,331,331,331,331,331,331,330,331,331,330,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,331,331,331,331,331,331,331,331,331,0,0,0,322,323,324,74,322,322,322,322,322,322,322,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,0,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,74,322,322,322,323,324,0,322,334,0,0,0,0,323,324,332,333,322,323,324,74,330,331,332,333,322,334,335,0,0,0,0,0,322,0,0,0,0,0,0,323,323,324,324,74,74,325,326,327,328,329,330,331,332,333,322,322,322,322,322,334,335,0,0,323,323,324,324,74,325,326,327,328,329,330,331,332,333,322,334,334,335,335,322,322,322,335,322,334,335,323,324,74,334,0,0,334,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,334,335,323,324,74,330,331,322,0,322,322,322,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,322,325,322,325,323,0,0,0,0,335,0,0,0,0,334,335,322,74,74,322,334,335,0,0,0,0,0,0,332,333,322,332,333,322,0,0,0,0,0,0,0,0,0,0,0,0,334,0,322,322,0,322,0,0,0,0,0,74,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,322,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,334,322,0,0,0,405,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],0,[[],2],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],3],0,0,0,0,0,0,0,0,0,0,0,0,[4,5],[6,5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[7,7],[8,8],[[]],[[]],[4],[9],[[4,4],1],[[9,9],1],[[6,6],1],[[7,7],1],[[8,8],1],[7,10],[7,[[11,[8]]]],[[4,12],13],[[9,12],13],[[6,12],13],[[7,12],13],[[8,12],13],[[]],[14,[[3,[4]]]],[[]],[[]],[[]],[[]],[5,[[3,[4]]]],[4],[9],[6],[7],[8],[7,15],[[]],[[]],[[]],[[]],[[]],[4],[[]],[[]],[4,9],[9,11],[6,11],[[16,17],[[3,[4]]]],[[5,16,17],[[3,[4]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[]],[[]],[[],[[18,[20]]]],[20,20],[[]],[[20,12],13],[[20,12],13],[[]],[[]],[21],[[]],[[],22],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[23,23],[[]],[23,24],[[23,23],1],[[],25],[[23,12],13],[[23,12],13],[[]],[25,23],[25,23],[[]],[[],23],[21],[[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,27]]]]]]]]]]]]]]]]]]],[[3,[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,27]]]]]]]]]]]]]]]]]],[[]],[[]],[[],22],[28,[[18,[23,28]]]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],29],[[],16],[[],30],[[],31],[[],32],[[],33],[[],34],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[29,35],[16,35],[30,36],[31,35],[32,35],[33,37],[34,35],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,29],[16,16],[30,30],[31,31],[32,32],[38,38],[33,33],[34,34],[39,39],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[29,29],40],[[16,16],40],[[30,30],40],[[31,31],40],[[32,32],40],[[33,33],40],[[34,34],40],[[39,39],40],[29,29],[16,16],[30,30],[31,31],[32,32],[33,33],[34,34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[33,33],1],[[34,34],1],[[5,[11,[41]],5,[11,[41]],2],[[3,[2]]]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[],29],[[],16],[[],30],[[],31],[[],32],[[],33],[[],34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[42,42],1],[[38,38],1],[[33,33],1],[[34,34],1],[[39,39],1],[[29,43]],[[16,43]],[[30,43]],[[31,43]],[[32,43]],[[33,43]],[[34,43]],[[5,34,44,44],3],[[5,42],[[3,[35]]]],[[5,38],3],[[29,12],13],[[29,12],13],[[29,12],13],[[29,12],13],[[29,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[42,12],13],[[38,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[39,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[35,[[11,[29]]]],[35,[[11,[16]]]],[36,[[11,[30]]]],[35,[[11,[31]]]],[35,[[11,[32]]]],[37,[[11,[33]]]],[35,[[11,[34]]]],[35,29],[35,16],[36,30],[35,31],[35,32],[37,33],[35,34],[35,29],[35,16],[36,30],[35,31],[35,32],[37,33],[35,34],[43,29],[43,16],[43,30],[43,31],[43,32],[43,33],[43,34],[29],[16],[30],[31],[32],[42],[38],[33],[34],[39],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[33,33],1],[[34,34],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,1],[16,1],[30,1],[31,1],[32,1],[33,1],[34,1],[29,1],[16,1],[30,1],[31,1],[32,1],[33,1],[34,1],[29,29],[16,16],[30,30],[31,31],[32,32],[33,33],[34,34],[[16,17],[[3,[5]]]],[[5,16,17],[[3,[5]]]],[[29,29],[[11,[40]]]],[[16,16],[[11,[40]]]],[[30,30],[[11,[40]]]],[[31,31],[[11,[40]]]],[[32,32],[[11,[40]]]],[[33,33],[[11,[40]]]],[[34,34],[[11,[40]]]],[[39,39],[[11,[40]]]],[[5,44,44,39],3],[[5,44,44],3],[[],[[3,[45]]]],[5,[[3,[45]]]],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[[11,[5]],[11,[5]]],3],[[[11,[5]],[11,[5]],30],3],[[29,29,1]],[[16,16,1]],[[30,30,1]],[[31,31,1]],[[32,32,1]],[[33,33,1]],[[34,34,1]],[[5,[11,[41]],5,[11,[41]],2,33],[[3,[2]]]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[5,5,2,33],[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[5,33],[[3,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,[[],1],0,0,0,[[]],[[]],[[]],[[]],0,[46,46],[[]],0,[47],[[46,46],1],[[47,47],1],0,[[46,12],13],[[47,12],13],[[]],[[]],[[],[[3,[47]]]],[46],[47],0,[[]],[[]],[[]],0,[47,11],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,0,0,0,0,0,[[],48],[[],49],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[48,37],[49,35],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[]],[[]],[[]],[[]],[48,48],[49,49],[[]],[[]],[[48,48],40],[[49,49],40],[48,48],[49,49],[[48,48],1],[[49,49],1],[[10,49],3],[[48,48],48],[[49,49],49],[[],48],[[],49],[[48,48],1],[[49,49],1],[[48,43]],[[49,43]],[[10,48],3],[[48,12],13],[[48,12],13],[[48,12],13],[[48,12],13],[[48,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[]],[[]],[37,[[11,[48]]]],[35,[[11,[49]]]],[37,48],[35,49],[37,48],[35,49],[43,48],[43,49],[48],[49],[10,3],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[48,48],1],[[49,49],1],[[]],[[]],[48,1],[49,1],[48,1],[49,1],[48,48],[49,49],[[48,48],[[11,[40]]]],[[49,49],[[11,[40]]]],[[48,48]],[[49,49]],[[48,48,1]],[[49,49,1]],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[]],[[]],[[48,48]],[[49,49]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[48,48],48],[[49,49],49],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],50],[[],51],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[50,52],[51,35],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[]],[[]],[[]],[[]],[50,50],[51,51],[[]],[[]],[[50,50],40],[[51,51],40],[50,50],[51,51],[[50,50],1],[[51,51],1],[[50,50],50],[[51,51],51],[[],50],[[],51],[[50,50],1],[[51,51],1],[[50,43]],[[51,43]],[[50,12],13],[[50,12],13],[[50,12],13],[[50,12],13],[[50,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[]],[[]],[52,[[11,[50]]]],[35,[[11,[51]]]],[52,50],[35,51],[52,50],[35,51],[43,50],[43,51],[50],[51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[50,50],1],[[51,51],1],[[]],[[]],[50,1],[51,1],[50,1],[51,1],[[11,11,50,11],3],[50,50],[51,51],[[50,50],[[11,[40]]]],[[51,51],[[11,[40]]]],[[50,50]],[[51,51]],[[50,50,1]],[[51,51,1]],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[]],[[]],[[50,50]],[[51,51]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],3],[51,3],[[50,50],50],[[51,51],51],0,0,0,0,0,0,0,0,0,0,[[],53],[[53,53],53],[[53,53]],[[53,53],53],[[53,53]],[53,35],[[53,53],53],[[53,53]],[[]],[[]],[[]],[[]],[[]],[[]],[53,53],[54,54],[[]],[[]],[[53,53],40],[53,53],[[53,53],1],[54,55],[[53,53],53],[[],53],[[53,53],1],[[54,54],1],[[53,43]],[54,55],[[53,12],13],[[53,12],13],[[53,12],13],[[53,12],13],[[53,12],13],[[54,12],13],[[56,12],13],[[]],[[]],[[]],[35,[[11,[53]]]],[35,53],[35,53],[43,53],[53],[54],[[53,53]],[[53,53],53],[[53,53],1],[[]],[[]],[[]],[53,1],[53,1],[54,55],0,[56,3],[56,[[3,[54]]]],[[10,53,17,[11,[54]]],[[3,[56]]]],[[56,36],[[3,[2]]]],[56,[[3,[54]]]],[[56,36],3],[56,[[3,[54]]]],[[56,54],[[3,[54]]]],[10,3],[54,55],[[55,55,55,55],54],[53,53],[[53,53],[[11,[40]]]],[[53,53]],[[53,53,1]],[[53,53],53],[[53,53]],[[53,53],53],[[]],[[]],[[53,53]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[53,53],53],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],57],[[57,57],57],[[57,57]],[[57,57],57],[[57,57]],[57,35],[[57,57],57],[[57,57]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[57,57],[[]],[[57,57],40],[57,57],[[57,57],1],[[57,57],57],[58],[[],57],[[57,57],1],[[57,43]],[[59,12],13],[[58,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[60,12],13],[[]],[[]],[[]],[[]],[35,[[11,[57]]]],[35,57],[35,57],[43,57],[57],[[],[[3,[58]]]],[[],[[3,[37]]]],[59,37],[[57,57]],[[57,57],57],[[57,57],1],[[]],[[]],[[]],[[]],[58],[[]],[57,1],[57,1],[58,60],[59,10],[60,11],[57,57],[[57,57],[[11,[40]]]],[[57,57]],[[57,57,1]],[[57,57],57],[[57,57]],[[57,57],57],[[]],[58],[[57,57]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[57,57],57],0,0,0,0,0,0,0,0,0,0,0,0,[61,[[11,[1]]]],[[],62],[61,[[11,[1]]]],[61,5],[[62,62],62],[[62,62]],[[62,62],62],[[62,62]],[62,63],[[62,62],62],[[62,62]],[[]],[[]],[[]],[[]],[61,61],[62,62],[[]],[[]],[[62,62],40],[62,62],[[62,62],1],[[62,62],62],[[],62],[[61,61],1],[[62,62],1],[61,62],[[62,43]],[[61,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[]],[[]],[63,[[11,[62]]]],[63,62],[63,62],[43,62],[61],[62],[[62,62]],[[62,62],62],[[62,62],1],[[]],[[]],[62,1],[62,1],[[5,62],61],[62,62],[[62,62],[[11,[40]]]],[35,[[3,[35]]]],[[[11,[64]],[11,[65]]],[[3,[35]]]],[[62,62]],[61,[[11,[62]]]],[[62,62,1]],[[61,62]],[[62,62],62],[[62,62]],[[62,62],62],[[]],[[]],[[62,62]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[62,62],62],0,0,0,0,0,[66,5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[67,67],[68,68],[69,69],[[]],[[]],[[]],[66],[[67,67],1],[[68,68],1],[[66,66],1],[66,70],[66,70],[[67,12],[[18,[71]]]],[[68,12],13],[[69,12],13],[[66,12],13],0,[[[72,[[11,[67]]]],[72,[[11,[73]]]]],[[3,[69]]]],[74,75],[[]],[[]],[[]],[[]],[66,3],[67],[68],[66],[[]],[[]],[[]],[[]],[66,5],0,0,[[[72,[[11,[67]]]],[72,[[11,[73]]]]],[[3,[68]]]],[16,[[3,[66]]]],[66,[[3,[22]]]],[66,[[3,[22]]]],[66,[[70,[2]]]],[66,[[70,[2]]]],0,[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[66,3],[66,[[70,[2]]]],[66,[[70,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],76],[[76,76],76],[[76,76]],[[76,76],76],[[76,76]],[76,35],[[76,76],76],[[76,76]],[[]],[[]],[[]],[[]],[[77,76,[11,[35]]],[[3,[74]]]],[76,76],[78,78],[[]],[[]],[[76,76],40],[76,76],[[76,76],1],[[],2],[[],78],[[76,76],76],[[],76],[[76,76],1],[[78,78],1],[[76,43]],[[76,12],13],[[76,12],13],[[76,12],13],[[76,12],13],[[76,12],13],[[78,12],13],[[]],[[]],[35,[[11,[76]]]],[35,76],[35,76],[43,76],[76],[78],[[76,76]],[[76,76],76],[[76,76],1],[[]],[[]],[76,1],[76,1],[[78,2],[[3,[1]]]],[[],78],[76,76],[[76,76],[[11,[40]]]],[[76,76]],[74,[[3,[78]]]],[[],[[3,[2]]]],[[74,78],3],[[],3],[[76,76,1]],[[78,2],3],[[5,76],3],[[76,76],76],[[76,76]],[[76,76],76],[[]],[[]],[[76,76]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[76,76],76],[[78,2],3],[76,3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,[[3,[79]]]],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[[11,[64]]],3],[81,84],[82,84],[81,84],[82,84],[83,84],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[80,[[3,[79]]]],[[[80,[81]]],[[3,[79]]]],[[[80,[82]]],[[3,[79]]]],[[[80,[83]]],[[3,[79]]]],[79,79],[85,85],[86,86],[[]],[[]],[[]],[[85,85],40],[[86,86],40],[[79,79],1],[[85,85],1],[[86,86],1],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[],5],[81,5],[82,5],[83,5],[[81,12],13],[[82,12],13],[[83,12],13],[[79,12],13],[[85,12],13],[[86,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[79],[85],[86],[[],1],[81,1],[82,1],[83,1],[[]],[[]],[[]],[[]],[[]],[[]],[[85,87],3],[83,86],[81,2],[82,2],[[5,44,25,87],81],[[5,44,25,87],82],[[5,86,25,87],83],[81,44],[82,44],[[85,85],[[11,[40]]]],[[86,86],[[11,[40]]]],[[],25],[81,25],[82,25],[83,25],[87],[[81,87]],[[82,87]],[[83,87]],[[],88],[81,88],[82,88],[83,88],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[25,[[3,[86]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],89],[[],90],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[89,35],[90,35],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[89,89],[91,91],[90,90],[92,92],[[]],[[]],[[]],[[]],[[89,89],40],[[90,90],40],[89,89],[90,90],[[89,89],1],[[90,90],1],[92,15],[[89,89],89],[[90,90],90],[[],89],[[],90],[[],92],[[],[[3,[5]]]],[90,[[3,[5]]]],[[5,91,5],3],[[5,93],[[3,[2]]]],[[89,89],1],[[91,91],1],[[90,90],1],[[92,92],1],[92,89],[[89,43]],[[90,43]],[[89,12],13],[[89,12],13],[[89,12],13],[[89,12],13],[[89,12],13],[[91,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[92,12],13],[[]],[[]],[[]],[[]],[35,[[11,[89]]]],[35,[[11,[90]]]],[35,89],[35,90],[35,89],[35,90],[43,89],[43,90],[89],[91],[90],[92],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[89,89],1],[[90,90],1],[[]],[[]],[[]],[[]],[89,1],[90,1],[89,1],[90,1],[[89,15],92],[89,89],[90,90],[[89,89],[[11,[40]]]],[[90,90],[[11,[40]]]],[[89,89]],[[90,90]],[[89,89,1]],[[90,90,1]],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[]],[[]],[[]],[[]],[[89,89]],[[90,90]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[89,89],89],[[90,90],90],0,0,0,0,[[],94],[[94,94],94],[[94,94]],[[94,94],94],[[94,94]],[94,35],[[94,94],94],[[94,94]],[[]],[[]],[94,94],[[]],[[94,94],40],[94,94],[[94,94],1],[[94,94],94],[[],94],[[94,94],1],[[37,94],[[3,[5]]]],[[94,43]],[[94,12],13],[[94,12],13],[[94,12],13],[[94,12],13],[[94,12],13],[[]],[35,[[11,[94]]]],[35,94],[35,94],[43,94],[94],[[94,94]],[[94,94],94],[[94,94],1],[[]],[94,1],[94,1],[94,94],[[94,94],[[11,[40]]]],[[94,94]],[[94,94,1]],[[94,94],94],[[94,94]],[[94,94],94],[[]],[[94,94]],[[],18],[[],18],[[],19],[[94,94],94],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[95,96],[[3,[97]]]],[[],96],[[],98],[95,5],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[96,36],[98,35],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[96,96],[98,98],[95,95],[97,97],[[]],[[]],[[]],[[]],[[96,96],40],[[98,98],40],[[97,97],40],[96,96],[98,98],[[96,96],1],[[98,98],1],0,[[96,96],96],[[98,98],98],[[],96],[[],98],[[96,96],1],[[98,98],1],[[97,97],1],[[96,43]],[[98,43]],[[96,12],13],[[96,12],13],[[96,12],13],[[96,12],13],[[96,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[95,12],13],[[97,12],13],[[99,12],13],[[]],[[]],[[]],[[]],[[]],[36,[[11,[96]]]],[35,[[11,[98]]]],[36,96],[35,98],[36,96],[35,98],[43,96],[43,98],[5,95],[96],[98],[97],[98,[[3,[95]]]],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[96,96],1],[[98,98],1],[[]],[[]],[[]],[[]],[[]],[96,1],[98,1],[96,1],[98,1],0,0,[96,96],[98,98],[[96,96],[[11,[40]]]],[[98,98],[[11,[40]]]],[[97,97],[[11,[40]]]],[95,[[3,[[100,[99]]]]]],[[96,96]],[[98,98]],[[95,97],3],[[96,96,1]],[[98,98,1]],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[]],[[]],[[]],[[]],[[96,96]],[[98,98]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[96,96],96],[[98,98],98],0,0,0,0,0,0,0,0,[[],101],[[101,101],101],[[101,101]],[[101,101],101],[[101,101]],[101,37],[[101,101],101],[[101,101]],[[]],[[]],[101,101],[[]],[[101,101],40],[101,101],[[101,101],1],[[101,101],101],[[],101],[[101,101],1],[[101,43]],[[101,12],13],[[101,12],13],[[101,12],13],[[101,12],13],[[101,12],13],[[]],[37,[[11,[101]]]],[37,101],[37,101],[43,101],[101],[[101,101]],[[101,101],101],[[101,101],1],[[]],[101,1],[101,1],[[10,101],[[3,[5]]]],[101,101],[[101,101],[[11,[40]]]],[[101,101]],[[101,101,1]],[[101,101],101],[[101,101]],[[101,101],101],[[]],[[101,101]],[[],18],[[],18],[[],19],[[101,101],101],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],102],[[],103],[[],104],[[],105],[[],106],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[102,35],[103,35],[104,35],[105,35],[106,35],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[102,102],[103,103],[104,104],[107,107],[105,105],[106,106],[[]],[[]],[[]],[[]],[[]],[[]],[[102,102],40],[[103,103],40],[[104,104],40],[[107,107],40],[[105,105],40],[[106,106],40],[102,102],[103,103],[104,104],[105,105],[106,106],[[102,102],1],[[103,103],1],[[104,104],1],[[105,105],1],[[106,106],1],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[],102],[[],103],[[],104],[[],105],[[],106],[[102,102],1],[[103,103],1],[[104,104],1],[[107,107],1],[[105,105],1],[[106,106],1],[[102,43]],[[103,43]],[[104,43]],[[105,43]],[[106,43]],[[102,12],13],[[102,12],13],[[102,12],13],[[102,12],13],[[102,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[107,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[35,[[11,[102]]]],[35,[[11,[103]]]],[35,[[11,[104]]]],[35,[[11,[105]]]],[35,[[11,[106]]]],[35,102],[35,103],[35,104],[35,105],[35,106],[35,102],[35,103],[35,104],[35,105],[35,106],[43,102],[43,103],[43,104],[43,105],[43,106],[102],[103],[104],[107],[105],[106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102],1],[[103,103],1],[[104,104],1],[[105,105],1],[[106,106],1],[[]],[[]],[[]],[[]],[[]],[[]],[102,1],[103,1],[104,1],[105,1],[106,1],[102,1],[103,1],[104,1],[105,1],[106,1],[[108,109,107],3],[[108,109],3],[106,3],[[[11,[110]],110,102,103,5,44],[[3,[108]]]],[[108,109,102],3],[[108,109,109,104,[11,[108]]],[[3,[108]]]],[[108,109,105],3],[[108,109],3],[[],3],[[108,109],3],[102,102],[103,103],[104,104],[105,105],[106,106],[[102,102],[[11,[40]]]],[[103,103],[[11,[40]]]],[[104,104],[[11,[40]]]],[[107,107],[[11,[40]]]],[[105,105],[[11,[40]]]],[[106,106],[[11,[40]]]],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102,1]],[[103,103,1]],[[104,104,1]],[[105,105,1]],[[106,106,1]],[[16,17],[[3,[5]]]],[[],3],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[]],[[]],[[]],[[]],[[]],[[]],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],0,0,0,0,0,0,0,0,0,0,0,0,[[],111],[[111,111],111],[[111,111]],[[111,111],111],[[111,111]],[111,35],[[111,111],111],[[111,111]],[[]],[[]],[111,111],[[]],[[111,111],40],[111,111],[[111,111],1],[[111,111],111],[[],111],[[111,111],1],[[111,43]],[[111,12],13],[[111,12],13],[[111,12],13],[[111,12],13],[[111,12],13],[[]],[35,[[11,[111]]]],[35,111],[35,111],[43,111],[[],[[3,[111]]]],[111],[[111,111]],[[111,111],111],[[111,111],1],[[]],[111,1],[111,1],[111,111],[[111,111],[[11,[40]]]],[[111,111]],[111,[[3,[111]]]],[[111,111,1]],[[111,111],111],[[111,111]],[[111,111],111],[[]],[[111,111]],[[],18],[[],18],[[],19],[[111,111],111],0,[112,3],[[],112],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],113],[74,3],[[113,113],113],[[113,113]],[[113,113],113],[[113,113]],[113,35],[[113,113],113],[[113,113]],[[]],[[]],[[]],[[]],[[]],[[]],[114,114],[115,115],[113,113],[[]],[[]],[[]],[[114,114],40],[[115,115],40],[[113,113],40],[113,113],[[74,[72,[[11,[116]]]]],3],[[113,113],1],[[74,[72,[[11,[116]]]]],3],[[113,113],113],[[],113],[[114,114],1],[[115,115],1],[[113,113],1],[[113,43]],[[114,12],13],[[115,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[]],[[]],[[]],[35,[[11,[113]]]],[35,113],[35,113],[43,113],[74,[[3,[117]]]],[74,[[3,[118]]]],[74,[[3,[119]]]],[114],[115],[113],[[113,113]],[74,3],[[113,113],113],[[113,113],1],[[]],[[]],[[]],[113,1],[113,1],[74,3],[113,113],[[114,114],[[11,[40]]]],[[115,115],[[11,[40]]]],[[113,113],[[11,[40]]]],[[74,120],[[3,[117]]]],[[74,120],[[3,[117]]]],[[113,113]],[[74,113],3],[[113,113,1]],[[74,113],3],[[74,118],3],[[74,119],3],[[74,[72,[[11,[116]]]]],3],[[113,113],113],[[113,113]],[[113,113],113],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[[]],[[]],[[]],[[113,113]],[[],3],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[113,113],113],[[74,120,108],3],[[74,120,108],3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],121],[122,[[11,[15]]]],[[121,121],121],[[121,121]],[[121,121],121],[[121,121]],[121,36],[[121,121],121],[[121,121]],[122,[[11,[15]]]],[122,[[11,[15]]]],[122,[[11,[15]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[123,123],[124,124],[122,122],[121,121],[[]],[[]],[[]],[[]],[[123,123],40],[[124,124],40],[[121,121],40],[121,121],[[121,121],1],[[],122],[[],121],[[121,121],121],[[],121],[[123,123],1],[[124,124],1],[[122,122],1],[[121,121],1],[[121,43]],[[123,12],13],[[124,12],13],[[122,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[]],[[]],[[]],[[]],[36,[[11,[121]]]],[36,121],[36,121],[43,121],[123],[124],[122],[121],[122,[[11,[15]]]],[122,[[11,[15]]]],[122,[[11,[15]]]],[[121,121]],[[121,121],121],[[121,121],1],[[]],[[]],[[]],[[]],[121,1],[121,1],[121,121],[122,[[11,[15]]]],[[123,123],[[11,[40]]]],[[124,124],[[11,[40]]]],[[121,121],[[11,[40]]]],[[123,35],[[3,[122]]]],[123,3],[[123,124],3],[[123,35,122,121],3],[[123,11],3],[[121,121]],[[121,121,1]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[121,121],121],[[121,121]],[[121,121],121],[[]],[[]],[[]],[[]],[[121,121]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[121,121],121],0,0,0,0,0,0,[[]],[[]],[125,125],[[]],[[125,125],40],[[125,125],1],[[125,12],13],[[]],[125],[[]],[[125,125],[[11,[40]]]],[125,[[3,[126]]]],[1,3],[[]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[127,128],[127,128],[127,117],[127,117],[[]],[[]],[[]],[[]],[[]],[[]],[129,129],[130,130],[127,127],[[]],[[]],[[]],[[129,129],40],[[130,130],40],[[129,129],1],[[130,130],1],[[127,127],1],[[129,12],13],[[130,12],13],[[127,12],13],[[]],[[]],[[]],[127,117],[129,3],[130,[[3,[127]]]],[129],[130],[127],[[]],[[]],[[]],[127,117],[127,117],[127,117],[127,117],[127,117],[127,117],[[129,129],[[11,[40]]]],[[130,130],[[11,[40]]]],0,[[129,131,131],3],[127,117],[127,117],[127,132],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[127,117],[127,117],[127,132],[127,117],0,0,0,[[]],[[]],[[]],[[]],[133],[133,133],[[]],[[133,5],1],[[],133],[[133,133],1],[[133,[11,[5]]],134],[[133,12],13],[[134,12],13],[[]],[[]],[133],[133,[[11,[5]]]],[[133,5]],[[]],[[]],[[]],[[],133],[134,[[11,[5]]]],[134,[[11,[5]]]],[[],[[3,[35]]]],[[133,5]],[[],[[3,[35]]]],[134],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[5,5,[11,[44]],2],[[3,[2]]]],[[5,5,[11,[135]],2],[[3,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[65,116]],[[],136],[[],65],[88,137],[116,24],[65,138],[116,24],[[136,136],136],[[136,136]],[[136,136],136],[[136,136]],[136,35],[[136,136],136],[[136,136]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[65],[116,116],[139,139],[136,136],[140,140],[65,65],[141,141],[142,142],[143,143],[87,87],[88,88],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[116,116],40],[[136,136],40],[[140,140],40],[136,136],[[136,136],1],[[65,116],1],[[136,136],136],[[],136],[[],65],[[116,116],1],[[139,139],1],[[136,136],1],[[140,140],1],[[65,65],1],[[142,142],1],[[143,143],1],[[87,87],1],[[88,88],1],[[136,43]],[65],[143,136],[[116,12],13],[[116,12],13],[[139,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[140,12],13],[[65,12],13],[[141,12],13],[[142,12],13],[[143,12],13],[[87,12],13],[[88,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[137,88],[35,[[11,[136]]]],[35,136],[35,136],[43,136],[[],65],[138,65],[24,[[3,[116]]]],[143,142],[116],[139],[136],[140],[65],[142],[143],[87],[88],[[136,136]],[[136,136],136],[[136,136],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[65],[[]],[136,1],[136,1],[65,141],[[],139],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[143,65],[[142,136,65],143],[87,88],[139,[[11,[116]]]],[141,[[11,[116]]]],[136,136],[[116,116],[[11,[40]]]],[[136,136],[[11,[40]]]],[[140,140],[[11,[40]]]],[[140,[11,[65]],[11,[65]]],3],[116,3],[[136,136]],[[65,116]],[[136,136,1]],[[116,143],[[3,[143]]]],[88,137],[[116,142],[[3,[142]]]],[[140,[11,[65]],[11,[65]]],3],[[136,136],136],[[136,136]],[[136,136],136],[65,3],[[],[[3,[65]]]],[65,3],[[65,140],[[3,[65]]]],[65,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[136,136]],[25,[[3,[116]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[136,136],136],[65,[[3,[116]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],144],[145,5],[[144,144],144],[[144,144]],[[144,144],144],[[144,144]],[144,35],[[144,144],144],[[144,144]],[[]],[[]],[[]],[[]],[[]],[[]],[146,146],[144,144],[[]],[[]],[[144,144],40],[144,144],[[144,144],1],[[144,144],144],[145],[[],144],[[146,146],1],[[144,144],1],[[145,145],1],[[144,43]],[[146,12],[[18,[71]]]],[[144,12],13],[[144,12],13],[[144,12],13],[[144,12],13],[[144,12],13],[[145,12],13],[[]],[[]],[[]],[35,[[11,[144]]]],[35,144],[35,144],[43,144],[146],[144],[145],[[144,144]],[[144,144],144],[[144,144],1],[[]],[[]],[[]],[[]],[144,1],[144,1],[65,[[3,[145]]]],[145,11],[144,144],[[144,144],[[11,[40]]]],[145,[[3,[[11,[146]]]]]],[[144,144]],[[144,144,1]],[[145,65],3],0,0,[[5,65,144],[[3,[5]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[144,144],144],[[144,144]],[[144,144],144],[[]],[[]],[[144,144]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[144,144],144],[[65,144],[[3,[145]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,[[3,[5]]]],[[5,147],[[3,[5]]]],[148,11],0,[149,10],[149,10],[[],150],[[],147],[[],151],[[],152],[153,11],[154,[[11,[149]]]],[154,[[11,[149]]]],[155],[154,[[11,[148]]]],[154,[[11,[148]]]],[153,156],[154,[[11,[157]]]],[154,[[11,[157]]]],[[],158],[[],158],[153,156],[153,156],[157,159],[149,160],[148,161],[162,163],[164,165],[166,167],[154,[[11,[166]]]],[154,[[11,[164]]]],[154,[[11,[164]]]],[154,[[11,[166]]]],[154,[[11,[153]]]],[154,[[11,[153]]]],[154,[[11,[162]]]],[154,[[11,[162]]]],[[5,168],3],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[150,37],[147,35],[151,35],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[162,36],[158,158],[167,167],[165,165],[156,156],[169,169],[170,170],[171,171],[153,153],[154,154],[157,157],[149,149],[148,148],[162,162],[172,172],[173,173],[150,150],[147,147],[151,151],[174,174],[175,175],[[[177,[176]]],[[177,[176]]]],[178,178],[179,179],[180,180],[181,181],[182,182],[183,183],[155,155],[164,164],[166,166],[184,184],[152,152],[185,185],[186,186],[187,187],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[150,150],40],[[147,147],40],[[151,151],40],0,0,0,0,[177,178],[150,150],[147,147],[151,151],[[5,168],3],[[150,150],1],[[147,147],1],[[151,151],1],[[],183],[[150,150],150],[[147,147],147],[[151,151],151],[[],150],[[],147],[[],151],[[158,158],1],[[167,167],1],[[165,165],1],[[156,156],1],[[169,169],1],[[170,170],1],[[171,171],1],[[153,153],1],[[154,154],1],[[157,157],1],[[149,149],1],[[148,148],1],[[162,162],1],[[172,172],1],[[173,173],1],[[150,150],1],[[147,147],1],[[151,151],1],[[174,174],1],[[175,175],1],[[[177,[27]],177],1],[[178,178],1],[[179,179],1],[[180,180],1],[[181,181],1],[[182,182],1],[[183,183],1],[[155,155],1],[[164,164],1],[[166,166],1],[[184,184],1],[[152,152],1],[[185,185],1],[[186,186],1],[[187,187],1],[[150,43]],[[147,43]],[[151,43]],[[],[[11,[187]]]],[[],[[11,[187]]]],[155,187],0,[164,36],[[158,12],[[18,[71]]]],[[167,12],[[18,[71]]]],[[165,12],[[18,[71]]]],[[156,12],[[18,[71]]]],[[169,12],[[18,[71]]]],[[170,12],[[18,[71]]]],[[171,12],[[18,[71]]]],[[153,12],13],[[153,12],13],[[154,12],13],[[154,12],13],[[157,12],13],[[157,12],13],[[149,12],13],[[149,12],13],[[148,12],13],[[148,12],13],[[162,12],13],[[162,12],13],[[172,12],13],[[173,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[174,12],13],[[175,12],13],[[[177,[188]],12],13],[[178,12],13],[[179,12],13],[[180,12],13],[[181,12],13],[[[189,[188]],12],13],[[[190,[188]],12],13],[[191,12],13],[[182,12],13],[[183,12],13],[[155,12],13],[[155,12],13],[[164,12],13],[[164,12],13],[[166,12],13],[[166,12],13],[[184,12],13],[[184,12],13],[[152,12],13],[[152,12],13],[[185,12],13],[[185,12],13],[[186,12],13],[[186,12],13],[[187,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[192,154],[193,154],[194,154],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[195,183],[[]],[194,164],[[]],[192,166],[[]],[[]],[[]],[[]],[[]],[[]],[37,[[11,[150]]]],[35,[[11,[147]]]],[35,[[11,[151]]]],[37,150],[35,147],[35,151],[37,150],[35,147],[35,151],[25,[[11,[187]]]],[43,150],[43,147],[43,151],[[158,[11,[196]]],11],[[158,[11,[196]]],[[11,[153]]]],[[158,[11,[196]]],[[11,[154]]]],[[158,[11,[196]]],[[11,[157]]]],[[158,[11,[196]]],[[11,[149]]]],[[158,[11,[196]]],[[11,[148]]]],[[158,[11,[196]]],[[11,[162]]]],[[158,[11,[196]]],[[11,[155]]]],[[158,[11,[196]]],[[11,[164]]]],[[158,[11,[196]]],[[11,[166]]]],[197,184],[198,152],[199,185],[193,186],[24,[[18,[164]]]],[24,[[18,[166]]]],[5,3],[5,[[3,[168]]]],[5,[[3,[168]]]],[[5,200],3],[183,201],[157,36],[148,2],[158],[167],[165],[156],[169],[170],[171],[153],[154],[157],[149],[148],[162],[173],[150],[147],[151],[182],[155],[164],[166],[184],[152],[185],[186],[187],[148,202],0,0,[148,2],[[150,150]],[[147,147]],[[151,151]],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150],1],[[147,147],1],[[151,151],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[177,191],[164,197],[166,203],[186,185],[150,1],[147,1],[151,1],[150,1],[147,1],[151,1],[153,1],[[],196],[[],196],[153,196],[154,196],[[5,2],3],0,0,0,0,0,0,0,0,[[],[[3,[153]]]],[[36,36],157],[[24,24],149],[[36,36],162],[[198,[11,[198]]],174],[197,175],[[],183],[[204,204,204,204,202],166],[[202,202,202,202,202,202,202,202],184],[[204,204,204,204],152],[[185,202],186],[[],[[3,[153]]]],[[24,24],155],[186,155],[[36,36],155],[[],[[3,[155]]]],[[],153],[[204,204,204,204],185],[[202,202,202,202,202,202,202,202],185],[[36,36],155],[178,[[11,[179]]]],[190,11],[191,11],[150,150],[147,147],[151,151],[152],[[150,150],[[11,[40]]]],[[147,147],[[11,[40]]]],[[151,151],[[11,[40]]]],[153,[[11,[205]]]],[153,2],[157,36],[183,75],[148,204],[162,36],[164,202],[166,202],[186,202],[[2,[11,[[100,[204]]]]],189],[148,202],[[5,151],[[3,[2]]]],[5,3],[[5,189,151,[11,[64]]],[[3,[190]]]],[[5,[11,[100]],151],[[3,[177]]]],[[150,150]],[[147,147]],[[151,151]],0,0,0,[164,36],[184],[[5,151],[[3,[2]]]],[[5,189,151],[[3,[190]]]],[[5,151,11],[[3,[2]]]],[[5,168,151],[[3,[2]]]],[5,3],[[150,150,1]],[[147,147,1]],[[151,151,1]],[[5,206],3],[[5,182],3],0,0,0,0,0,0,0,0,0,[[],196],[[],196],[[],196],0,0,0,0,[[169,2],[[3,[155]]]],0,[[187,172,147,[72,[[11,[173]]]]],[[3,[5]]]],[[187,172,[72,[[11,[173]]]],147],3],0,0,[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],0,0,[[150,150],150],[[147,147],147],[[151,151],151],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[184,197],[152,198],[185,199],[186,193],[155,22],[186,22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[150,150]],[[147,147]],[[151,151]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[25,[[3,[172]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[183,207],[[150,150],150],[[147,147],147],[[151,151],151],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[208,208],[209,209],[210,210],[211,211],[212,212],[213,213],[214,214],[215,215],[216,216],[217,217],[218,218],[219,219],[220,220],[221,221],[222,222],[223,223],[224,224],[225,225],[226,226],[227,227],[228,228],[229,229],[230,230],[231,231],[232,232],[233,233],[234,234],[235,235],[236,236],[237,237],[238,238],[239,239],[240,240],[241,241],[242,242],[243,243],[244,244],[245,245],[246,246],[247,247],[248,248],[249,249],[250,250],[251,251],[252,252],[253,253],[254,254],[255,255],[256,256],[257,257],[258,258],[259,259],[260,260],[261,261],[262,262],[263,263],[264,264],[[[265,[176]]],[[265,[176]]]],[266,266],[267,267],[268,268],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],265],[[208,208],1],[[209,209],1],[[210,210],1],[[211,211],1],[[212,212],1],[[213,213],1],[[214,214],1],[[215,215],1],[[216,216],1],[[217,217],1],[[218,218],1],[[219,219],1],[[220,220],1],[[221,221],1],[[222,222],1],[[223,223],1],[[224,224],1],[[225,225],1],[[226,226],1],[[227,227],1],[[228,228],1],[[229,229],1],[[230,230],1],[[231,231],1],[[232,232],1],[[233,233],1],[[234,234],1],[[235,235],1],[[236,236],1],[[237,237],1],[[238,238],1],[[239,239],1],[[240,240],1],[[241,241],1],[[242,242],1],[[243,243],1],[[244,244],1],[[245,245],1],[[246,246],1],[[247,247],1],[[248,248],1],[[249,249],1],[[250,250],1],[[251,251],1],[[252,252],1],[[253,253],1],[[254,254],1],[[255,255],1],[[256,256],1],[[257,257],1],[[258,258],1],[[259,259],1],[[260,260],1],[[261,261],1],[[262,262],1],[[263,263],1],[[266,266],1],[[267,267],1],[[268,268],1],[[208,12],13],[[209,12],13],[[210,12],13],[[211,12],13],[[212,12],13],[[213,12],13],[[214,12],13],[[215,12],13],[[216,12],13],[[217,12],13],[[218,12],13],[[219,12],13],[[220,12],13],[[221,12],13],[[222,12],13],[[223,12],13],[[224,12],13],[[225,12],13],[[226,12],13],[[227,12],13],[[228,12],13],[[229,12],13],[[230,12],13],[[231,12],13],[[232,12],13],[[233,12],13],[[234,12],13],[[235,12],13],[[236,12],13],[[237,12],13],[[238,12],13],[[239,12],13],[[240,12],13],[[241,12],13],[[242,12],13],[[243,12],13],[[244,12],13],[[245,12],13],[[246,12],13],[[247,12],13],[[248,12],13],[[249,12],13],[[250,12],13],[[251,12],13],[[252,12],13],[[253,12],13],[[254,12],13],[[255,12],13],[[256,12],13],[[257,12],13],[[258,12],13],[[259,12],13],[[260,12],13],[[261,12],13],[[262,12],13],[[263,12],13],[[264,12],13],[[[265,[188]],12],13],[[266,12],13],[[267,12],13],[[268,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[208,5],[[3,[1]]]],[[209,5],[[3,[1]]]],[[210,5],[[3,[1]]]],[[211,5],[[3,[269]]]],[[214,5],[[3,[204]]]],[[215,5],[[3,[1]]]],[[216,5],[[3,[35]]]],[[217,5],[[3,[35]]]],[[218,5],[[3,[35]]]],[[219,5],[[3,[1]]]],[[220,5],[[3,[132]]]],[[221,5],[[3,[132]]]],[[222,5],[[3,[1]]]],[[223,5],[[3,[1]]]],[[224,5],[[3,[25]]]],[[225,5],[[3,[1]]]],[[226,5],[[3,[1]]]],[[227,5],[[3,[183]]]],[[228,5],[[3,[36]]]],[[229,5],[[3,[36]]]],[[230,5],[[3,[36]]]],[[231,5],[[3,[36]]]],[[232,5],[[3,[36]]]],[[233,5],[[3,[2]]]],[[234,5],[[3,[2]]]],[[237,5],[[3,[172]]]],[[238,5],[[3,[1]]]],[[239,5],[[3,[45]]]],[[240,5],[[3,[167]]]],[[241,5],[[3,[165]]]],[[242,5],[[3,[150]]]],[[243,5],[[3,[1]]]],[[244,5],[[3,[1]]]],[[245,5],[[3,[1]]]],[[246,5],[[3,[36]]]],[[247,5],[[3,[1]]]],[[248,5],[[3,[45]]]],[[249,5],[[3,[1]]]],[[250,5],[[3,[1]]]],[[251,5],[[3,[1]]]],[[252,5],[[3,[35]]]],[[253,5],[[3,[1]]]],[[254,5],[[3,[270]]]],[[255,5],[[3,[35]]]],[[256,5],[[3,[1]]]],[[257,5],[[3,[1]]]],[[258,5],[[3,[1]]]],[[259,5],[[3,[35]]]],[[260,5],[[3,[35]]]],[[261,5],[[3,[35]]]],[[262,5],[[3,[1]]]],[[263,5],[[3,[1]]]],[[266,5],[[3,[36]]]],[208],[209],[210],[211],[212],[213],[214],[215],[216],[217],[218],[219],[220],[221],[222],[223],[224],[225],[226],[227],[228],[229],[230],[231],[232],[233],[234],[235],[236],[237],[238],[239],[240],[241],[242],[243],[244],[245],[246],[247],[248],[249],[250],[251],[252],[253],[254],[255],[256],[257],[258],[259],[260],[261],[262],[263],[266],[267],[268],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[208,5,1],3],[[209,5,1],3],[[210,5,1],3],[[211,5,269],3],[[212,5,174],3],[[213,5,174],3],[[214,5,204],3],[[215,5,1],3],[[216,5,35],3],[[217,5,35],3],[[218,5,35],3],[[219,5,1],3],[[220,5,132],3],[[221,5,132],3],[[222,5,1],3],[[223,5,1],3],[[225,5,1],3],[[226,5,1],3],[[228,5,36],3],[[229,5,36],3],[[230,5,36],3],[[231,5,36],3],[[232,5,36],3],[[233,5,2],3],[[234,5,2],3],[[235,5,2],3],[[236,5,2],3],[[239,5,45],3],[[242,5,150],3],[[243,5,1],3],[[244,5,1],3],[[245,5,1],3],[[246,5,36],3],[[247,5,1],3],[[248,5,45],3],[[249,5,1],3],[[250,5,1],3],[[251,5,1],3],[[252,5,35],3],[[253,5,1],3],[[254,5,270],3],[[255,5,35],3],[[256,5,1],3],[[257,5,1],3],[[258,5,1],3],[[260,5,35],3],[[261,5,35],3],[[262,5,1],3],[[263,5,1],3],[[264,5,2],3],[[265,5],3],[[266,5,36],3],[[267,5,175],3],[[268,5,175],3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,[[271,272],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],273],[[],17],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[273,274],[17,274],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[272,272],[273,273],[17,17],[275,275],[276,276],[[]],[[]],[[]],[[]],[[]],[[273,273],40],[[17,17],40],[273,273],[17,17],[[273,273],1],[[17,17],1],0,[[273,273],273],[[17,17],17],[[],273],[[],17],[[272,272],1],[[273,273],1],[[17,17],1],[[273,43]],[[17,43]],[[5,17],3],[[[11,[5]],17,275],3],[[272,12],[[18,[71]]]],[[273,12],13],[[273,12],13],[[273,12],13],[[273,12],13],[[273,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[275,12],13],[[276,12],13],[[]],[[]],[[]],[[]],[[]],[274,[[11,[273]]]],[274,[[11,[17]]]],[274,273],[274,17],[274,273],[274,17],[43,273],[43,17],[5,[[3,[272]]]],[[5,29],[[3,[272]]]],[[5,64,64],3],[272],[273],[17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[273,273],1],[[17,17],1],[[]],[[]],[[]],[[]],[[]],[273,1],[17,1],[273,1],[17,1],[[],[[3,[272]]]],[[132,132],3],[277,15],[[15,15],277],[277,15],[[5,17],3],[[273,17,277],3],[[5,273,17,277],3],0,[273,273],[17,17],[[273,273],[[11,[40]]]],[[17,17],[[11,[40]]]],[[273,273]],[[17,17]],[[273,273,1]],[[17,17,1]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[3,[272]]]],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[]],[[]],[[]],[[]],[[]],[[273,273]],[[17,17]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[17,17],[[273,273],273],[[17,17],17],[[[11,[5]],64,64,276],3],[[132,132],3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[278,279],[278,15],[278,15],[278,15],[[]],[[]],[[]],[[]],[278,278],[280,280],[[]],[[]],[[280,280],1],[278,15],[278,15],[278,281],[278,280],[278,282],[[278,12],13],[[280,12],13],[[]],[[]],0,[[],[[3,[278]]]],[[]],[[]],[278,279],[278,279],[[],[[3,[278]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],282],[[282,282],282],[[282,282]],[[282,282],282],[[282,282]],[282,52],[[282,282],282],[[282,282]],[283,52],[283,284],[283,284],[283,284],[[]],[[]],[[]],[[]],[282,282],[283,283],[[]],[[]],[[282,282],40],[282,282],[[282,282],1],[[],282],[[282,282],282],[[],282],[[282,282],1],[[283,283],1],[[282,43]],[283,285],[283,285],[283,285],[283,52],[283,282],[[282,12],13],[[282,12],13],[[282,12],13],[[282,12],13],[[282,12],13],[[283,12],13],[283,52],[[]],[[]],[52,[[11,[282]]]],[52,282],[52,282],[43,282],[[],[[3,[283]]]],[282],[283],[[282,282]],[[282,282],282],[[282,282],1],[[]],[[]],[282,1],[282,1],[283,52],[282,282],[[282,282],[[11,[40]]]],[[282,282]],[[282,282,1]],[[],[[3,[283]]]],[[282,282],282],[[282,282]],[[282,282],282],[[]],[[]],[[282,282]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[282,282],282],0,[[]],[[]],[286,286],[[]],[[286,286],1],[[286,12],13],[[]],[286],[[]],[286],[286,202],[286,15],[286,15],[286,15],[286,15],[[],[[3,[286]]]],[[]],[[],18],[[],18],[[],19],[286,287],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],288],[[],289],[[],290],[[],291],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[288,292],[289,292],[290,292],[291,292],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[73,293],[73,293],[73],[[73,293],3],[[73,293],3],[[73,293],3],[73,73],[293,293],[294,294],[295,295],[296,296],[297,297],[288,288],[289,289],[290,290],[291,291],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[293,293],40],[[294,294],40],[[295,295],40],[[296,296],40],[[297,297],40],[[288,288],40],[[289,289],40],[[290,290],40],[[291,291],40],[288,288],[289,289],[290,290],[291,291],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],0,0,[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[],288],[[],289],[[],290],[[],291],[[73,73],1],[[293,293],1],[[294,294],1],[[295,295],1],[[296,296],1],[[297,297],1],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],[[288,43]],[[289,43]],[[290,43]],[[291,43]],[[73,12],13],[[293,12],13],[[294,12],13],[[295,12],13],[[296,12],13],[[297,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[298,73],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[292,[[11,[288]]]],[292,[[11,[289]]]],[292,[[11,[290]]]],[292,[[11,[291]]]],[292,288],[292,289],[292,290],[292,291],[292,288],[292,289],[292,290],[292,291],[43,288],[43,289],[43,290],[43,291],[293],[294],[295],[296],[297],[288],[289],[290],[291],0,[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[288,1],[289,1],[290,1],[291,1],[288,1],[289,1],[290,1],[291,1],0,0,[288,288],[289,289],[290,290],[291,291],0,[[293,293],[[11,[40]]]],[[294,294],[[11,[40]]]],[[295,295],[[11,[40]]]],[[296,296],[[11,[40]]]],[[297,297],[[11,[40]]]],[[288,288],[[11,[40]]]],[[289,289],[[11,[40]]]],[[290,290],[[11,[40]]]],[[291,291],[[11,[40]]]],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288,1]],[[289,289,1]],[[290,290,1]],[[291,291,1]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[5,3],[[5,296],3],[[5,295],3],[5,[[3,[73]]]],[5,[[3,[74]]]],[[5,35],3],[[5,294,73],3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[],18],[[],18],[299,[[3,[293]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],0,0,0,[[64,64],64],[[132,132],132],[64,300],[132,301],[64,300],[132,301],[[]],[[]],[[]],[[]],[64,64],[132,132],[[]],[[]],[[64,64],40],[[132,132],40],[[64,25],64],[[132,25],132],[[64,64],1],[[132,132],1],[[64,12],13],[[64,12],13],[[132,12],13],[[132,12],13],[300,64],[287,64],[[]],[301,132],[[]],[287,64],[300,64],[64],[132],[302],[[]],[[]],[302],[302,64],[302,132],[302],[302,64],[302,132],[302],[[64,25],64],[[132,25],132],[302],[302,64],[302,132],[64,64],[132,132],[[303,117],64],[[303,304],132],[[],302],[[],302],[64,302],[132,302],[[],302],[64,302],[132,302],[[],302],[[],302],[64,302],[132,302],[[],302],[64,302],[132,302],[[64,64],[[11,[40]]]],[[132,132],[[11,[40]]]],[302],[302,64],[302,132],[[64,64],64],[[132,132],132],0,0,[[]],[[]],[[],22],[[],22],[[],18],[[],18],[[],18],[[],18],[64,117],[64,303],[132,303],[132,304],[[],19],[[],19],[[]],0,0,0,0,0,0,[[]],[[]],[305],[[305,12],13],[[]],[305,[[3,[[11,[306]]]]]],[[]],[[307,88],[[3,[305]]]],[305,25],[[305,306,308],3],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],308],[[],309],[310,5],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[308,35],[309,35],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[306,306],[308,308],[311,311],[309,309],[[]],[[]],[[]],[[]],[[308,308],40],[[311,311],40],[[309,309],40],[308,308],[309,309],[[308,308],1],[[309,309],1],[[308,308],308],[[309,309],309],[310],[[],308],[[],309],[[306,306],1],[[308,308],1],[[311,311],1],[[309,309],1],[[308,43]],[[309,43]],[[306,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[310,12],13],[[311,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[]],[[]],[[]],[[]],[[]],[35,[[11,[308]]]],[35,[[11,[309]]]],[35,308],[35,309],[35,308],[35,309],[43,308],[43,309],[5,310],[310,[[3,[[11,[306]]]]]],[308],[311],[309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[308,308],1],[[309,309],1],[[]],[[]],[[]],[[]],[[]],[308,1],[309,1],[308,1],[309,1],[[311,309],[[3,[310]]]],[308,308],[309,309],[[308,308],[[11,[40]]]],[[311,311],[[11,[40]]]],[[309,309],[[11,[40]]]],[[308,308]],[[309,309]],[[308,308,1]],[[310,306,308],3],[[309,309,1]],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[]],[[]],[[]],[[]],[[308,308]],[[309,309]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[308,308],308],[[309,309],309],[310,3],[310,3],0,0,0,0,0,0,[312],0,[[]],[[]],[[]],[[]],[313,313],[[[312,[176]]],[[312,[176]]]],[[]],[[]],[[313,313],1],[[[312,[27]],312],1],[[313,12],13],[[[312,[188]],12],13],[[]],[[]],[[],312],[[],312],[313],[[[312,[314]]]],[[]],[[]],0,[[5,44],[[3,[2]]]],[[5,44],[[3,[2]]]],[74,[[3,[2]]]],[74,[[3,[2]]]],[[5,44],[[3,[2]]]],[[5,44],[[3,[2]]]],[5,[[3,[2]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[5,[[3,[2]]]],0,[[]],[[]],[315,315],[[]],[315,316],[[315,315],1],[[315,12],13],[[]],[315],[[]],[315,316],[315,316],[315,316],[315,316],[[]],[[],18],[[],18],[[],19],[[],[[3,[315]]]],[315,316],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],317],[[317,317],317],[[317,317]],[[317,317],317],[[317,317]],[317,35],[[317,317],317],[[317,317]],[[]],[[]],[[]],[[]],[[]],[[]],[317,317],[318,318],[319,319],[[]],[[]],[[]],[[317,317],40],[[319,319],40],[317,317],[[317,317],1],[[317,317],317],[[],317],[[317,317],1],[[318,318],1],[[319,319],1],[[317,43]],[[317,12],13],[[317,12],13],[[317,12],13],[[317,12],13],[[317,12],13],[[318,12],13],[[319,12],13],[[]],[[]],[[]],[35,[[11,[317]]]],[35,317],[35,317],[43,317],[[74,25],[[3,[318]]]],[317],[318],[319],[[317,317]],[[317,317],317],[[317,317],1],[[]],[[]],[[]],[317,1],[317,1],[317,317],[[317,317],[[11,[40]]]],[[319,319],[[11,[40]]]],[318,[[11,[74]]]],[[317,317]],[[317,317,1]],[[317,317],317],[[317,317]],[[317,317],317],[[]],[[]],[[]],[[317,317]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[317,317],317],[[],[[3,[318]]]],[[319,317],[[3,[318]]]],[[[72,[[11,[74]]]],[11,[317]]],[[3,[318]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[307,320],[[]],[[]],[74,[[3,[307]]]],[307,[[3,[64]]]],[307,[[3,[64]]]],[[307,64],3],[307,307],[[]],[[307,307],40],[[307,307],1],[[307,12],13],[[307,12],13],[320,307],[[]],[320,307],[307],[[]],[307,[[3,[64]]]],[[307,307],[[11,[40]]]],[74,[[3,[307]]]],[307,[[3,[64]]]],[[307,64],3],[[]],[[],22],[[],18],[[],18],[[],19],0,[[]],[[]],[321,321],[[]],[[321,321],1],[[321,12],13],[[]],[[],[[3,[321]]]],[321],[[]],[321,3],[321,65],[321,65],[[]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[322,3],0,0,[[],322],[323,207],[324,201],[74,75],[[322,322],322],[[322,322]],[[322,322],322],[[322,322]],[322,35],[[322,322],322],[[322,322]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[[11,[323]],[11,[324]]],3],[[],3],[323,323],[324,324],[74,74],[325,325],[326,326],[327,327],[328,328],[329,329],[330,330],[331,331],[332,332],[333,333],[322,322],[334,334],[335,335],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,3],[[74,74],40],[[322,322],40],[322,322],[[322,322],1],[[],323],[[],324],[[1,1],3],[[322,322],322],0,[5,[[3,[5]]]],[[5,5],[[3,[5]]]],[[5,5,16],[[3,[5]]]],[322,3],[[],323],[[],324],0,0,[[],322],[[323,323],1],[[324,324],1],[[74,74],1],[[330,330],1],[[331,331],1],[[332,332],1],[[333,333],1],[[322,322],1],[[334,334],1],[[335,335],1],[10,[[3,[126]]]],[10,[[3,[126]]]],[[5,10,29],[[3,[126]]]],[10,[[3,[126]]]],[10,[[3,[126]]]],[[322,43]],[[[11,[5]],322,29],3],[5,3],[[5,[11,[323]],[11,[324]]],3],[[[11,[5]],[11,[323]],[11,[324]],326],3],[5,3],[5,[[3,[126]]]],[[323,12],13],[[323,12],13],[[324,12],13],[[324,12],13],[[74,12],13],[[74,12],13],[[325,12],13],[[326,12],13],[[327,12],13],[[328,12],13],[[329,12],13],[[330,12],13],[[331,12],13],[[332,12],13],[[333,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[334,12],13],[[335,12],13],[[],[[3,[325]]]],[[5,330],[[3,[[11,[117]]]]]],[207,323],[[]],[[]],[201,324],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[336,334],[[]],[337,335],[35,[[11,[322]]]],[35,322],[35,322],[324,[[3,[[11,[335]]]]]],[43,322],[24,[[3,[[11,[334]]]]]],[24,[[3,[[11,[335]]]]]],[207,323],[201,324],[75,74],[323,[[3,[[11,[334]]]]]],[5,3],[[5,44],3],0,[[],[[3,[338]]]],[[],324],[[],323],[[],324],[[10,324],[[3,[[100,[324]]]]]],[[],[[3,[[100,[324]]]]]],[[],[[3,[45]]]],[[[11,[74]]],[[3,[74]]]],[[],74],[[],74],[[],74],[[],[[3,[333]]]],[[],[[3,[332]]]],[[[11,[74]]],[[3,[74]]]],[[],74],[[],323],0,0,[323],[324],[74],[330],[331],[322],[[10,324],3],[[322,322]],[[322,322],322],[[322,322],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[322,1],[325,1],[322,1],[325,1],[323,1],[5,[[3,[1]]]],[[[11,[5]],[11,[5]],328],3],[[5,44,327],[[3,[44]]]],[[5,135,327],[[3,[135]]]],0,[17,3],[17,3],[[[11,[5]],17],3],[[],3],0,0,[322,322],[[],74],[[74,74],[[11,[40]]]],[[322,322],[[11,[40]]]],0,0,[330,[[3,[[11,[117]]]]]],[[]],[[],[[18,[339]]]],[16,3],[[],3],[5,[[3,[2]]]],0,0,[[322,322]],0,0,[[322,322,1]],[324,3],[323,3],[324,324],[323,323],[324,3],[[],3],[[[340,[316]]],3],[[74,74],3],[[324,324,324],3],[[323,323,323],3],[[],[[3,[74]]]],[323,3],0,[37,37],[[322,322],322],[[322,322]],[[[11,[5]]],3],[[322,322],322],[[]],[5,3],[331,[[3,[[11,[117]]]]]],[35,[[3,[74]]]],[[35,74],3],[[],74],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[],22],[[],22],[[322,322]],[44,3],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[5,[[3,[338]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[322,322],322],[[],3],[[[11,[5]],329],3],[5,[[3,[2]]]],0,[[],3],[[],3],[[],[[11,[37]]]],[37,[[11,[37]]]]],"p":[[15,"bool"],[15,"usize"],[6,"Result"],[3,"Dir"],[6,"RawFd"],[3,"OwningIter"],[3,"Entry"],[4,"Type"],[3,"Iter"],[3,"CStr"],[4,"Option"],[3,"Formatter"],[6,"Result"],[8,"IntoRawFd"],[15,"u64"],[3,"OFlag"],[3,"Mode"],[4,"Result"],[3,"TypeId"],[3,"ClearEnvError"],[3,"Demand"],[3,"String"],[4,"Errno"],[15,"str"],[15,"i32"],[8,"ErrnoSentinel"],[8,"PartialEq"],[3,"Error"],[3,"AtFlags"],[3,"RenameFlags"],[3,"SealFlag"],[3,"FdFlag"],[3,"SpliceFFlags"],[3,"FallocateFlags"],[6,"c_int"],[15,"u32"],[6,"c_uint"],[4,"FlockArg"],[4,"PosixFadviseAdvice"],[4,"Ordering"],[6,"loff_t"],[4,"FcntlArg"],[8,"IntoIterator"],[6,"off_t"],[3,"OsString"],[3,"InterfaceAddress"],[3,"InterfaceAddressIterator"],[3,"ModuleInitFlags"],[3,"DeleteModuleFlags"],[3,"MsFlags"],[3,"MntFlags"],[6,"c_ulong"],[3,"MQ_OFlag"],[3,"MqAttr"],[6,"mq_attr_member_t"],[3,"MqdT"],[3,"InterfaceFlags"],[3,"Interfaces"],[3,"Interface"],[3,"InterfacesIter"],[3,"PollFd"],[3,"PollFlags"],[6,"c_short"],[3,"TimeSpec"],[3,"SigSet"],[3,"PtyMaster"],[3,"Winsize"],[3,"OpenptyResult"],[3,"ForkptyResult"],[6,"Result"],[3,"Error"],[8,"Into"],[3,"Termios"],[3,"Pid"],[6,"SessionId"],[3,"CloneFlags"],[6,"CloneCb"],[3,"CpuSet"],[4,"AioCancelStat"],[3,"Pin"],[3,"AioWrite"],[3,"AioRead"],[3,"AioFsync"],[3,"aiocb"],[4,"LioMode"],[4,"AioFsyncMode"],[4,"SigevNotify"],[3,"SigEvent"],[3,"EpollFlags"],[3,"EpollCreateFlags"],[4,"EpollOp"],[3,"EpollEvent"],[15,"isize"],[3,"EfdFlags"],[3,"Inotify"],[3,"AddWatchFlags"],[3,"WatchDescriptor"],[3,"InitFlags"],[3,"InotifyEvent"],[3,"Vec"],[3,"MemFdCreateFlag"],[3,"ProtFlags"],[3,"MapFlags"],[3,"MRemapFlags"],[3,"MsFlags"],[3,"MlockAllFlags"],[4,"MmapAdvise"],[4,"c_void"],[6,"size_t"],[3,"NonZeroUsize"],[3,"Persona"],[6,"Pthread"],[3,"Options"],[4,"Request"],[4,"Event"],[4,"Signal"],[6,"c_long"],[3,"user_regs_struct"],[3,"siginfo_t"],[6,"AddressType"],[3,"QuotaValidFlags"],[3,"Dqblk"],[4,"QuotaType"],[4,"QuotaFmt"],[4,"RebootMode"],[4,"Infallible"],[3,"Usage"],[3,"rusage"],[4,"Resource"],[4,"UsageWho"],[6,"rlim_t"],[3,"TimeVal"],[3,"FdSet"],[3,"Fds"],[6,"off64_t"],[3,"SaFlags"],[3,"sigevent"],[3,"sigset_t"],[3,"SignalIterator"],[4,"SigmaskHow"],[3,"SigSetIter"],[4,"SigHandler"],[3,"SigAction"],[3,"SfdFlags"],[3,"SignalFd"],[3,"siginfo"],[3,"SockFlag"],[3,"LinkAddr"],[3,"AlgAddr"],[3,"TimestampingFlag"],[3,"MsgFlags"],[3,"Ipv4Addr"],[3,"UnixAddr"],[19,"SockaddrStorage"],[4,"SockAddr"],[3,"sockaddr_un"],[3,"NetlinkAddr"],[3,"sockaddr"],[3,"sockaddr_nl"],[3,"sockaddr_alg"],[3,"sockaddr_ll"],[3,"VsockAddr"],[3,"sockaddr_vm"],[3,"SockaddrIn6"],[3,"sockaddr_in6"],[3,"SockaddrIn"],[3,"sockaddr_in"],[8,"SockaddrLike"],[3,"sockaddr_storage"],[3,"msghdr"],[3,"cmsghdr"],[4,"SockType"],[4,"SockProtocol"],[3,"IpMembershipRequest"],[3,"Ipv6MembershipRequest"],[8,"Clone"],[3,"RecvMsg"],[3,"CmsgIterator"],[4,"ControlMessageOwned"],[3,"Timestamps"],[4,"ControlMessage"],[4,"Shutdown"],[3,"UnixCredentials"],[3,"Ipv6Addr"],[4,"IpAddr"],[4,"InetAddr"],[4,"AddressFamily"],[8,"Debug"],[3,"MultiHeaders"],[3,"MultiResults"],[3,"IoSliceIterator"],[3,"SocketAddrV4"],[4,"SocketAddr"],[3,"SocketAddrV6"],[3,"ucred"],[6,"socklen_t"],[3,"Ipv6Addr"],[3,"Ipv4Addr"],[4,"IpAddr"],[8,"GetSockOpt"],[6,"gid_t"],[15,"u16"],[6,"in_addr_t"],[15,"u8"],[3,"Path"],[8,"SetSockOpt"],[6,"uid_t"],[3,"ReuseAddr"],[3,"ReusePort"],[3,"TcpNoDelay"],[3,"Linger"],[3,"IpAddMembership"],[3,"IpDropMembership"],[3,"IpMulticastTtl"],[3,"IpMulticastLoop"],[3,"Priority"],[3,"IpTos"],[3,"Ipv6TClass"],[3,"IpFreebind"],[3,"ReceiveTimeout"],[3,"SendTimeout"],[3,"Broadcast"],[3,"OobInline"],[3,"SocketError"],[3,"DontRoute"],[3,"KeepAlive"],[3,"PeerCredentials"],[3,"TcpKeepIdle"],[3,"TcpKeepCount"],[3,"TcpRepair"],[3,"TcpKeepInterval"],[3,"TcpUserTimeout"],[3,"RcvBuf"],[3,"SndBuf"],[3,"RcvBufForce"],[3,"SndBufForce"],[3,"SockType"],[3,"AcceptConn"],[3,"BindToDevice"],[3,"OriginalDst"],[3,"Ip6tOriginalDst"],[3,"Timestamping"],[3,"ReceiveTimestamp"],[3,"ReceiveTimestampns"],[3,"IpTransparent"],[3,"Mark"],[3,"PassCred"],[3,"TcpCongestion"],[3,"Ipv4PacketInfo"],[3,"Ipv6RecvPacketInfo"],[3,"Ipv4OrigDstAddr"],[3,"UdpGsoSegment"],[3,"UdpGroSegment"],[3,"TxTime"],[3,"RxqOvfl"],[3,"Ipv6V6Only"],[3,"Ipv4RecvErr"],[3,"Ipv6RecvErr"],[3,"IpMtu"],[3,"Ipv4Ttl"],[3,"Ipv6Ttl"],[3,"Ipv6OrigDstAddr"],[3,"Ipv6DontFrag"],[3,"AlgSetAeadAuthSize"],[3,"AlgSetKey"],[3,"TcpMaxSeg"],[3,"Ipv6AddMembership"],[3,"Ipv6DropMembership"],[3,"linger"],[3,"sock_txtime"],[15,"i8"],[3,"FileStat"],[3,"SFlag"],[6,"mode_t"],[4,"FchmodatFlags"],[4,"UtimensatFlags"],[6,"dev_t"],[3,"Statfs"],[6,"__fsword_t"],[3,"FsType"],[6,"fsid_t"],[3,"FsFlags"],[3,"Statvfs"],[6,"fsblkcnt_t"],[6,"fsfilcnt_t"],[3,"SysInfo"],[3,"Duration"],[3,"InputFlags"],[3,"OutputFlags"],[3,"ControlFlags"],[3,"LocalFlags"],[6,"tcflag_t"],[4,"BaudRate"],[4,"SetArg"],[4,"FlushArg"],[4,"FlowArg"],[4,"SpecialCharacterIndices"],[3,"termios"],[6,"speed_t"],[3,"timespec"],[3,"timeval"],[15,"i64"],[6,"time_t"],[6,"suseconds_t"],[3,"Timer"],[4,"Expiration"],[3,"ClockId"],[3,"TimerSetTimeFlags"],[3,"TimerFlags"],[3,"TimerFd"],[4,"ClockId"],[3,"IoVec"],[3,"RemoteIoVec"],[8,"Hash"],[3,"UtsName"],[3,"OsStr"],[3,"WaitPidFlag"],[4,"WaitStatus"],[4,"Id"],[6,"clockid_t"],[3,"UContext"],[3,"AccessFlags"],[3,"Uid"],[3,"Gid"],[4,"ForkResult"],[4,"FchownatFlags"],[4,"Whence"],[4,"LinkatFlags"],[4,"UnlinkatFlags"],[4,"PathconfVar"],[4,"SysconfVar"],[3,"ResUid"],[3,"ResGid"],[3,"User"],[3,"Group"],[3,"passwd"],[3,"group"],[3,"PathBuf"],[6,"Error"],[8,"AsRef"],[8,"NixPath"],[13,"F_DUPFD"],[13,"F_DUPFD_CLOEXEC"],[13,"F_SETFD"],[13,"F_SETFL"],[13,"F_SETLK"],[13,"F_SETLKW"],[13,"F_GETLK"],[13,"F_OFD_SETLK"],[13,"F_OFD_SETLKW"],[13,"F_OFD_GETLK"],[13,"F_ADD_SEALS"],[13,"F_SETPIPE_SZ"],[8,"Aio"],[13,"Handler"],[13,"SigAction"],[13,"SigevSignal"],[13,"SigevThreadId"],[13,"ScmRights"],[13,"ScmCredentials"],[13,"AlgSetIv"],[13,"AlgSetOp"],[13,"AlgSetAeadAssoclen"],[13,"UdpGsoSegments"],[13,"Ipv4PacketInfo"],[13,"Ipv6PacketInfo"],[13,"RxqOvfl"],[13,"TxTime"],[13,"ScmRights"],[13,"ScmCredentials"],[13,"ScmTimestamp"],[13,"ScmTimestampsns"],[13,"ScmTimestampns"],[13,"Ipv4PacketInfo"],[13,"Ipv6PacketInfo"],[13,"Ipv4OrigDstAddr"],[13,"Ipv6OrigDstAddr"],[13,"UdpGroSegments"],[13,"RxqOvfl"],[13,"Ipv4RecvErr"],[13,"Ipv6RecvErr"],[13,"V4"],[13,"V6"],[13,"V4"],[13,"V6"],[13,"Inet"],[13,"Unix"],[13,"Netlink"],[13,"Alg"],[13,"Link"],[13,"Vsock"],[8,"TimeValLike"],[13,"OneShot"],[13,"IntervalDelayed"],[13,"Interval"],[13,"Pid"],[13,"PGid"],[13,"PIDFd"],[13,"Exited"],[13,"Signaled"],[13,"Stopped"],[13,"PtraceEvent"],[13,"PtraceSyscall"],[13,"Continued"],[13,"Parent"]],"a":{"fdopendir":[90],"getegid":[6952],"geteuid":[6951],"getgid":[6943],"getpid":[7135],"getppid":[7095],"getuid":[6942],"sigaddset":[3038],"sigdelset":[3191],"sigemptyset":[3072,3101],"sigfillset":[3040],"sigismember":[3098],"timer_create":[6272],"timer_getoverrun":[6273],"timer_gettime":[6270],"timer_settime":[6274],"timerfd_create":[6397],"timerfd_gettime":[6378],"timerfd_settime":[6406,6437]}},\ +"once_cell":{"doc":"Overview","t":[0,0,0,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["race","sync","unsync","OnceBool","OnceBox","OnceNonZeroUsize","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","default","default","default","drop","fmt","fmt","fmt","from","from","from","get","get","get","get_or_init","get_or_init","get_or_init","get_or_try_init","get_or_try_init","get_or_try_init","into","into","into","new","new","new","set","set","set","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","Lazy","OnceCell","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","force","force_mut","from","from","from","from","get","get","get_mut","get_mut","get_or_init","get_or_try_init","get_unchecked","into","into","into_inner","into_value","new","new","set","take","to_owned","try_from","try_from","try_insert","try_into","try_into","type_id","type_id","wait","with_value","Lazy","OnceCell","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","force","force_mut","from","from","from","from","get","get","get_mut","get_mut","get_or_init","get_or_try_init","into","into","into_inner","into_value","new","new","set","take","to_owned","try_from","try_from","try_insert","try_into","try_into","type_id","type_id","with_value"],"q":["once_cell","","","once_cell::race","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","once_cell::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","once_cell::unsync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Thread-safe, non-blocking, “first one wins” flavor of …","Thread-safe, blocking version of OnceCell.","Single-threaded version of OnceCell.","A thread-safe cell which can be written to only once.","A thread-safe cell which can be written to only once.","A thread-safe cell which can be written to only once.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Gets a reference to the underlying value.","Gets the underlying value.","Gets the underlying value.","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a new empty cell.","Creates a new empty cell.","Creates a new empty cell.","Sets the contents of this cell to value.","Sets the contents of this cell to value.","Sets the contents of this cell to value.","","","","","","","","","","A value which is initialized on the first access.","A thread-safe cell which can be written to only once.","","","","","","","","","Creates a new lazy value using Default as the initializing …","","","","","","Forces the evaluation of this lazy value and returns a …","Forces the evaluation of this lazy value and returns a …","","","Returns the argument unchanged.","Returns the argument unchanged.","Gets the reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets the mutable reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Get the reference to the underlying value, without …","Calls U::from(self).","Calls U::from(self).","Consumes the OnceCell, returning the wrapped value. Returns","Consumes this Lazy returning the stored value.","Creates a new empty cell.","Creates a new lazy value with the given initializing …","Sets the contents of this cell to value.","Takes the value out of this OnceCell, moving it back to an …","","","","Like set, but also returns a reference to the final cell …","","","","","Gets the reference to the underlying value, blocking the …","Creates a new initialized cell.","A value which is initialized on the first access.","A cell which can be written to only once. It is not thread …","","","","","","","","","Creates a new lazy value using Default as the initializing …","","","","","","Forces the evaluation of this lazy value and returns a …","Forces the evaluation of this lazy value and returns a …","Returns the argument unchanged.","","","Returns the argument unchanged.","Gets a reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets a mutable reference to the underlying value.","Gets the mutable reference to the result of this lazy …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Calls U::from(self).","Calls U::from(self).","Consumes the OnceCell, returning the wrapped value.","Consumes this Lazy returning the stored value.","Creates a new empty cell.","Creates a new lazy value with the given initializing …","Sets the contents of this cell to value.","Takes the value out of this OnceCell, moving it back to an …","","","","Like set, but also returns a reference to the final cell …","","","","","Creates a new initialized cell."],"i":[0,0,0,0,0,0,1,2,3,1,2,3,1,2,3,1,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,0,0,13,15,13,15,13,13,13,13,15,15,15,13,13,15,15,15,13,13,13,15,13,15,13,15,13,13,13,13,15,13,15,13,15,13,13,13,13,15,13,13,15,13,15,13,13,0,0,20,21,20,21,20,20,20,20,21,21,21,20,20,21,21,21,20,20,20,21,20,21,20,21,20,20,20,21,20,21,20,21,20,20,20,20,21,20,20,21,20,21,20],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[],2],[[],3],[1],[[1,4],5],[[2,4],5],[[3,4],5],[[]],[[]],[[]],[1,6],[2,[[6,[7]]]],[3,[[6,[8]]]],[1],[2,7],[3,8],[1,9],[2,[[9,[7]]]],[3,[[9,[8]]]],[[]],[[]],[[]],[[],1],[[],2],[[],3],[[1,10],[[9,[10]]]],[[2,7],9],[[3,8],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],11],[[],11],[[],11],0,0,[[]],[[]],[[]],[[]],[[[13,[12]]],[[13,[12]]]],[[[13,[12]],[13,[12]]]],[[]],[[],13],[[],[[15,[14]]]],[[[15,[16]]]],[[[15,[16]]]],[[[13,[17]],13],8],[[[13,[18]],4],5],[[[15,[18]],4],5],[15],[15],[[],13],[19],[[]],[[]],[13,6],[15,6],[13,6],[15,6],[13],[13,9],[13],[[]],[[]],[13,6],[15,9],[[],13],[[],15],[13,9],[13,6],[[]],[[],9],[[],9],[13,9],[[],9],[[],9],[[],11],[[],11],[13],[[],13],0,0,[[]],[[]],[[]],[[]],[[[20,[12]]],[[20,[12]]]],[[[20,[12]],[20,[12]]]],[[]],[[],20],[[],[[21,[14]]]],[[[21,[16]]]],[[[21,[16]]]],[[[20,[17]],[20,[17]]],8],[[[20,[18]],4],5],[[[21,[18]],4],5],[21],[21],[[]],[[],20],[19],[[]],[20,6],[21,6],[20,6],[21,6],[20],[20,9],[[]],[[]],[20,6],[21,9],[[],20],[[],21],[20,9],[20,6],[[]],[[],9],[[],9],[20,9],[[],9],[[],9],[[],11],[[],11],[[],20]],"p":[[3,"OnceBox"],[3,"OnceNonZeroUsize"],[3,"OnceBool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[3,"NonZeroUsize"],[15,"bool"],[4,"Result"],[3,"Box"],[3,"TypeId"],[8,"Clone"],[3,"OnceCell"],[8,"Default"],[3,"Lazy"],[8,"FnOnce"],[8,"PartialEq"],[8,"Debug"],[15,"never"],[3,"OnceCell"],[3,"Lazy"]]},\ +"parking_lot":{"doc":"This library provides implementations of Mutex, RwLock, …","t":[3,13,6,6,13,6,6,6,6,6,6,6,13,3,4,13,3,3,3,3,6,6,6,6,6,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Condvar","Done","FairMutex","FairMutexGuard","InProgress","MappedFairMutexGuard","MappedMutexGuard","MappedReentrantMutexGuard","MappedRwLockReadGuard","MappedRwLockWriteGuard","Mutex","MutexGuard","New","Once","OnceState","Poisoned","RawFairMutex","RawMutex","RawRwLock","RawThreadId","ReentrantMutex","ReentrantMutexGuard","RwLock","RwLockReadGuard","RwLockUpgradableReadGuard","RwLockWriteGuard","WaitTimeoutResult","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bump","bump","bump_exclusive","bump_shared","bump_upgradable","call_once","call_once_force","clone","clone","clone_into","clone_into","const_fair_mutex","const_mutex","const_reentrant_mutex","const_rwlock","default","default","done","downgrade","downgrade_to_upgradable","downgrade_upgradable","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","is_locked","is_locked","is_locked","is_locked_exclusive","lock","lock","lock_api","lock_exclusive","lock_shared","lock_shared_recursive","lock_upgradable","new","new","nonzero_thread_id","notify_all","notify_one","poisoned","state","timed_out","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock","try_lock_exclusive","try_lock_exclusive_for","try_lock_exclusive_until","try_lock_for","try_lock_for","try_lock_shared","try_lock_shared_for","try_lock_shared_recursive","try_lock_shared_recursive_for","try_lock_shared_recursive_until","try_lock_shared_until","try_lock_until","try_lock_until","try_lock_upgradable","try_lock_upgradable_for","try_lock_upgradable_until","try_upgrade","try_upgrade_for","try_upgrade_until","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unlock","unlock","unlock_exclusive","unlock_exclusive_fair","unlock_fair","unlock_fair","unlock_shared","unlock_shared_fair","unlock_upgradable","unlock_upgradable_fair","upgrade","wait","wait_for","wait_until","wait_while","wait_while_for","wait_while_until"],"q":["parking_lot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A Condition Variable","A closure has completed successfully.","A mutual exclusive primitive that is always fair, useful …","An RAII implementation of a “scoped lock” of a mutex. …","A thread is currently executing a closure.","An RAII mutex guard returned by FairMutexGuard::map, which …","An RAII mutex guard returned by MutexGuard::map, which can …","An RAII mutex guard returned by ReentrantMutexGuard::map, …","An RAII read lock guard returned by RwLockReadGuard::map, …","An RAII write lock guard returned by RwLockWriteGuard::map…","A mutual exclusion primitive useful for protecting shared …","An RAII implementation of a “scoped lock” of a mutex. …","A closure has not been executed yet","A synchronization primitive which can be used to run a …","Current state of a Once.","A closure was executed but panicked.","Raw fair mutex type backed by the parking lot.","Raw mutex type backed by the parking lot.","Raw reader-writer lock type backed by the parking lot.","Implementation of the GetThreadId trait for …","A mutex which can be recursively locked by a single thread.","An RAII implementation of a “scoped lock” of a …","A reader-writer lock","RAII structure used to release the shared read access of a …","RAII structure used to release the upgradable read access …","RAII structure used to release the exclusive write access …","A type indicating whether a timed wait on a condition …","","","","","","","","","","","","","","","","","","","","","","Performs an initialization routine once and only once. The …","Performs the same function as call_once except ignores …","","","","","Creates a new fair mutex in an unlocked state ready for …","Creates a new mutex in an unlocked state ready for use.","Creates a new reentrant mutex in an unlocked state ready …","Creates a new instance of an RwLock<T> which is unlocked.","","","Returns whether the associated Once has successfully …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","Creates a new condition variable which is ready to be …","Creates a new Once value.","","Wakes up all blocked threads on this condvar.","Wakes up one blocked thread on this condvar.","Returns whether the associated Once has been poisoned.","Returns the current state of this Once.","Returns whether the wait was known to have timed out.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Blocks the current thread until this condition variable …","Waits on this condition variable for a notification, …","Waits on this condition variable for a notification, …","Blocks the current thread until this condition variable …","Waits on this condition variable for a notification, …","Waits on this condition variable for a notification, …"],"i":[0,6,0,0,6,0,0,0,0,0,0,0,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,3,4,4,5,6,5,6,0,0,0,0,11,4,6,3,3,3,5,6,11,4,5,6,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,1,2,0,3,3,3,3,11,4,15,11,11,6,4,5,5,6,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,3,1,2,3,3,3,3,3,3,1,2,3,3,3,3,3,3,11,4,1,2,3,15,5,6,1,2,3,3,1,2,3,3,3,3,3,11,11,11,11,11,11],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2],[3],[3],[3],[4],[4],[5,5],[6,6],[[]],[[]],[[],7],[[],8],[[],9],[[],10],[[],11],[[],4],[6,12],[3],[3],[3],[[5,5],12],[[6,6],12],[[11,13],14],[[4,13],14],[[5,13],14],[[6,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,12],[2,12],[3,12],[3,12],[1],[2],0,[3],[3],[3],[3],[[],11],[[],4],[15,16],[11,17],[11,12],[6,12],[4,6],[5,12],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[1,12],[2,12],[3,12],[[3,19],12],[[3,20],12],[1,12],[[2,19],12],[3,12],[3,12],[3,12],[3,12],[3,12],[3,12],[1,12],[[2,20],12],[3,12],[[3,19],12],[[3,20],12],[3,12],[[3,19],12],[[3,20],12],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[1],[2],[3],[3],[1],[2],[3],[3],[3],[3],[3],[[11,22]],[[11,22,19],5],[[11,22,20],5],[[11,22]],[[11,22,19],5],[[11,22,20],5]],"p":[[3,"RawFairMutex"],[3,"RawMutex"],[3,"RawRwLock"],[3,"Once"],[3,"WaitTimeoutResult"],[4,"OnceState"],[6,"FairMutex"],[6,"Mutex"],[6,"ReentrantMutex"],[6,"RwLock"],[3,"Condvar"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"RawThreadId"],[3,"NonZeroUsize"],[15,"usize"],[4,"Result"],[3,"Duration"],[3,"Instant"],[3,"TypeId"],[6,"MutexGuard"]]},\ +"parking_lot_core":{"doc":"This library exposes a low-level API for creating your own …","t":[12,12,13,17,17,4,13,4,3,13,13,4,13,3,13,13,13,13,13,3,3,13,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,5,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,12,12,5,5],"n":["0","0","Abort","DEFAULT_PARK_TOKEN","DEFAULT_UNPARK_TOKEN","FilterOp","Invalid","ParkResult","ParkToken","RequeueAll","RequeueOne","RequeueOp","Skip","SpinWait","Stop","TimedOut","Unpark","UnparkOne","UnparkOneRequeueRest","UnparkResult","UnparkToken","Unparked","be_fair","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","deadlock","default","default","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","have_more_threads","into","into","into","into","into","into","into","is_unparked","new","park","requeued_threads","reset","spin","spin_no_yield","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unpark_all","unpark_filter","unpark_one","unpark_requeue","unparked_threads","0","acquire_resource","release_resource"],"q":["parking_lot_core","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","parking_lot_core::ParkResult","parking_lot_core::deadlock",""],"d":["","","Abort the operation without doing anything.","A default park token to use.","A default unpark token to use.","Operation that unpark_filter should perform for each …","The validation callback returned false.","Result of a park operation.","A value associated with a parked thread which can be used …","Requeue all threads onto the target queue.","Requeue one thread and leave the rest parked on the …","Operation that unpark_requeue should perform.","Don’t unpark the thread and continue scanning the list …","A counter used to perform exponential backoff in spin …","Don’t unpark the thread and stop scanning the list of …","The timeout expired.","Unpark the thread and continue scanning the list of parked …","Unpark one thread and leave the rest parked. No requeuing …","Unpark one thread and requeue the rest onto the target …","Result of an unpark operation.","A value which is passed from an unparker to a parked …","We were unparked by another thread with the given token.","This is set to true on average once every 0.5ms for any …","","","","","","","","","","","","","","","","","","","","","","","","","","","[Experimental] Deadlock detection","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Whether there are any threads remaining in the queue. This …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if we were unparked by another thread.","Creates a new SpinWait.","Parks the current thread in the queue associated with the …","The number of threads that were requeued.","Resets a SpinWait to its initial state.","Spins until the sleep threshold has been reached.","Spins without yielding the thread to the OS.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unparks all threads in the queue associated with the given …","Unparks a number of threads from the front of the queue …","Unparks one thread from the queue associated with the …","Removes all threads from the queue associated with key_from…","The number of threads that were unparked.","","Acquire a resource identified by key in the deadlock …","Release a resource identified by key in the deadlock …"],"i":[5,6,3,0,0,0,1,0,0,3,3,0,4,0,4,1,4,3,3,0,0,1,2,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,1,2,3,4,5,6,0,2,7,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,7,2,1,2,3,4,5,6,7,1,7,0,2,7,7,7,1,2,3,4,5,6,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,0,0,0,0,2,18,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[[]],[[]],[[]],[[]],[[]],[[]],0,[[],2],[[],7],[[1,1],8],[[2,2],8],[[3,3],8],[[4,4],8],[[5,5],8],[[6,6],8],[[1,9],10],[[2,9],10],[[3,9],10],[[4,9],10],[[5,9],10],[[6,9],10],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,8],[[],7],[[11,12,12,12,6,[14,[13]]],1],0,[7],[7,8],[7],[[]],[[]],[[]],[[]],[[]],[[]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[11,5],11],[[11,17,12],2],[[11,12],2],[[11,11,12,12],2],0,0,[11],[11]],"p":[[4,"ParkResult"],[3,"UnparkResult"],[4,"RequeueOp"],[4,"FilterOp"],[3,"UnparkToken"],[3,"ParkToken"],[3,"SpinWait"],[15,"bool"],[3,"Formatter"],[6,"Result"],[15,"usize"],[8,"FnOnce"],[3,"Instant"],[4,"Option"],[4,"Result"],[3,"TypeId"],[8,"FnMut"],[13,"Unparked"]]},\ +"pin_project_lite":{"doc":"A lightweight version of pin-project written with …","t":[14],"n":["pin_project"],"q":["pin_project_lite"],"d":["A macro that creates a projection type covering all the …"],"i":[0],"f":[0],"p":[]},\ +"pin_utils":{"doc":"Utilities for pinning","t":[14,14,14],"n":["pin_mut","unsafe_pinned","unsafe_unpinned"],"q":["pin_utils","",""],"d":["Pins a value on the stack.","A pinned projection of a struct field.","An unpinned projection of a struct field."],"i":[0,0,0],"f":[0,0,0],"p":[]},\ +"ppv_lite86":{"doc":"","t":[8,8,8,8,8,8,8,8,8,8,16,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,14,14,14,10,10,10,10,10,10,11,11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,10,11,11,10,10,10,11,11,2,2,2,10,10,10,0,6,6,3,3,3,3,3,3,6,6,6,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,19,19,19],"n":["AndNot","ArithOps","BSwap","BitOps0","BitOps128","BitOps32","BitOps64","LaneWords4","Machine","MultiLane","Output","RotateEachWord128","RotateEachWord32","RotateEachWord64","Store","StoreBytes","Swap64","UnsafeFrom","VZip","Vec2","Vec4","Vec4Ext","Vector","Words4","andnot","bswap","dispatch","dispatch_light128","dispatch_light256","extract","extract","from_lanes","insert","insert","instance","read_be","read_be","read_le","read_le","rotate_each_word_right11","rotate_each_word_right12","rotate_each_word_right16","rotate_each_word_right20","rotate_each_word_right24","rotate_each_word_right25","rotate_each_word_right32","rotate_each_word_right7","rotate_each_word_right8","shuffle1230","shuffle2301","shuffle3012","shuffle_lane_words1230","shuffle_lane_words2301","shuffle_lane_words3012","swap1","swap16","swap2","swap32","swap4","swap64","swap8","to_lanes","to_scalars","transpose4","u128x1","u128x1","u128x2","u128x2","u128x4","u128x4","u32x4","u32x4","u32x4x2","u32x4x2","u32x4x4","u32x4x4","u64x2","u64x2","u64x2x2","u64x2x2","u64x2x4","u64x2x4","u64x4","u64x4","unpack","unpack","unpack","unsafe_from","unsafe_read_be","unsafe_read_le","vec","vec","vec128_storage","vec256_storage","vec512_storage","vzip","write_be","write_le","x86_64","AVX","AVX2","Avx2Machine","NoA1","NoA2","NoNI","NoS3","NoS4","SSE2","SSE41","SSSE3","SseMachine","YesA1","YesA2","YesNI","YesS3","YesS4","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","default","default","default","eq","eq","eq","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","instance","instance","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new128","new128","split128","split128","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unpack","vec128_storage","vec256_storage","vec512_storage"],"q":["ppv_lite86","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","ppv_lite86::x86_64","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","Ops that depend on word size","","Ops that are independent of word size and endian","","","","A vector composed one or more lanes each composed of four …","","A vector composed of multiple 128-bit lanes.","","","","","","","Exchange neigboring ranges of bits of the specified size","","Combine single vectors into a multi-lane vector.","A vector composed of two elements, which may be words or …","A vector composed of four elements, which may be words or …","Vec4 functions which may not be implemented yet for all …","","A vector composed of four words; depending on their size, …","","","Generate the full set of optimized implementations to take …","Generate only the basic implementations necessary to be …","Generate only the basic implementations necessary to be …","","","Build a multi-lane vector from individual lanes.","","","Safety","","","","","","","","","","","","","","","","","","","","","","","","","","","Split a multi-lane vector into single-lane vectors.","","","","","","","","","","","","","","","","","","","","","","","Safety","","","","Safety","Safety","","","","","","","","","","AVX but not AVX2: only 128-bit integer operations, but use …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Generic wrapper for unparameterized storage of any of the …","",""],"i":[0,0,0,0,0,0,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,23,24,0,0,0,25,26,27,25,26,28,28,28,28,28,29,29,29,29,29,29,30,29,29,31,31,31,32,32,32,33,33,33,33,33,33,33,27,34,35,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,2,28,28,36,37,37,28,28,0,0,0,38,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,16,17,18,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,16,17,17,18,14,15,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,17,18,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,16,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,0,0,[1],[1],[[]],[1],[1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],2],[[],2],[[]],[[]],[[]],[[]],[[]],0,0,0,[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[11,11],[12,12],[[[14,[13,13,13]]],[[14,[13,13,13]]]],[[[15,[13]]],[[15,[13]]]],[16,16],[17,17],[18,18],[[],16],[[],17],[[],18],[[16,16],19],[[17,17],19],[[18,18],19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],16],[[],17],[[]],[[]],[[],[[14,[20,20,20]]]],[[],[[15,[20]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],17],[[],18],[17],[18],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[16,16],0,0,0],"p":[[15,"u32"],[8,"Store"],[3,"YesS3"],[3,"NoS3"],[3,"YesS4"],[3,"NoS4"],[3,"YesA1"],[3,"NoA1"],[3,"YesA2"],[3,"NoA2"],[3,"YesNI"],[3,"NoNI"],[8,"Clone"],[3,"SseMachine"],[3,"Avx2Machine"],[19,"vec128_storage"],[19,"vec256_storage"],[19,"vec512_storage"],[15,"bool"],[8,"Copy"],[4,"Result"],[3,"TypeId"],[8,"AndNot"],[8,"BSwap"],[8,"Vec2"],[8,"Vec4"],[8,"MultiLane"],[8,"Machine"],[8,"RotateEachWord32"],[8,"RotateEachWord64"],[8,"Words4"],[8,"LaneWords4"],[8,"Swap64"],[8,"Vector"],[8,"Vec4Ext"],[8,"UnsafeFrom"],[8,"StoreBytes"],[8,"VZip"]]},\ +"primitive_types":{"doc":"Primitive types shared by Substrate and Parity Ethereum.","t":[12,12,12,12,12,12,12,12,12,4,3,3,3,3,3,3,18,18,18,13,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","0","0","0","0","0","0","0","0","Error","H128","H160","H256","H384","H512","H768","MAX","MAX","MAX","Overflow","U128","U256","U512","abs_diff","abs_diff","abs_diff","add","add","add","add","add","add","add_assign","add_assign","add_assign","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_u128","as_u128","as_u128","as_u32","as_u32","as_u32","as_u64","as_u64","as_u64","as_usize","as_usize","as_usize","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","bit","bit","bit","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte","byte","byte","checked_add","checked_add","checked_add","checked_div","checked_div","checked_div","checked_mul","checked_mul","checked_mul","checked_neg","checked_neg","checked_neg","checked_pow","checked_pow","checked_pow","checked_rem","checked_rem","checked_rem","checked_sub","checked_sub","checked_sub","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","covers","covers","covers","covers","covers","covers","decode","decode","decode","decode","decode","decode","decode","decode","decode","default","default","default","default","default","default","default","default","default","div","div","div","div","div","div","div_assign","div_assign","div_assign","div_mod","div_mod","div_mod","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","exp10","exp10","exp10","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_big_endian","from_big_endian","from_big_endian","from_dec_str","from_dec_str","from_dec_str","from_little_endian","from_little_endian","from_little_endian","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_slice","from_slice","from_slice","from_slice","from_slice","from_slice","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str_radix","from_str_radix","from_str_radix","full_mul","full_mul","hash","hash","hash","hash","hash","hash","hash","hash","hash","index","index","index","index","index","index","index_mut","index_mut","index_mut","index_mut","index_mut","index_mut","integer_sqrt","integer_sqrt","integer_sqrt","into","into","into","into","into","into","into","into","into","into","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","leading_zeros","leading_zeros","leading_zeros","len_bytes","len_bytes","len_bytes","len_bytes","len_bytes","len_bytes","low_u128","low_u128","low_u128","low_u32","low_u32","low_u32","low_u64","low_u64","low_u64","max_value","max_value","max_value","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","not","not","not","one","one","one","overflowing_add","overflowing_add","overflowing_add","overflowing_mul","overflowing_mul","overflowing_mul","overflowing_neg","overflowing_neg","overflowing_neg","overflowing_pow","overflowing_pow","overflowing_pow","overflowing_sub","overflowing_sub","overflowing_sub","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","pow","pow","pow","random","random","random","random","random","random","random_using","random_using","random_using","random_using","random_using","random_using","randomize","randomize","randomize","randomize","randomize","randomize","randomize_using","randomize_using","randomize_using","randomize_using","randomize_using","randomize_using","rem","rem","rem","rem","rem","rem","rem_assign","rem_assign","rem_assign","repeat_byte","repeat_byte","repeat_byte","repeat_byte","repeat_byte","repeat_byte","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","saturating_add","saturating_add","saturating_add","saturating_mul","saturating_mul","saturating_mul","saturating_sub","saturating_sub","saturating_sub","shl","shl","shl","shl","shl","shl","shl_assign","shl_assign","shl_assign","shr","shr","shr","shr","shr","shr","shr_assign","shr_assign","shr_assign","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","to_big_endian","to_big_endian","to_big_endian","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_little_endian","to_little_endian","to_little_endian","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","trailing_zeros","trailing_zeros","trailing_zeros","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","zero","zero","zero","zero","zero","zero","zero","zero","zero"],"q":["primitive_types","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","Error type for conversion.","Fixed-size uninterpreted hash type with 16 bytes (128 …","Fixed-size uninterpreted hash type with 20 bytes (160 …","Fixed-size uninterpreted hash type with 32 bytes (256 …","Fixed-size uninterpreted hash type with 48 bytes (384 …","Fixed-size uninterpreted hash type with 64 bytes (512 …","Fixed-size uninterpreted hash type with 96 bytes (768 …","Maximum value.","Maximum value.","Maximum value.","Overflow encountered.","Little-endian large integer type 128-bit unsigned integer.","Little-endian large integer type 256-bit unsigned integer.","Little-endian large integer type 512-bits unsigned integer.","Computes the absolute difference between self and other.","Computes the absolute difference between self and other.","Computes the absolute difference between self and other.","","","","","","","","","","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","","","","","","","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","","","","","","","","","","Conversion to u128 with overflow checking","Conversion to u128 with overflow checking","Conversion to u128 with overflow checking","Conversion to u32 with overflow checking","Conversion to u32 with overflow checking","Conversion to u32 with overflow checking","Conversion to u64 with overflow checking","Conversion to u64 with overflow checking","Conversion to u64 with overflow checking","Conversion to usize with overflow checking","Conversion to usize with overflow checking","Conversion to usize with overflow checking","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Return if specific bit is set.","Return if specific bit is set.","Return if specific bit is set.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the least number of bits needed to represent the …","Return the least number of bits needed to represent the …","Return the least number of bits needed to represent the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return specific byte.","Return specific byte.","Return specific byte.","Checked addition. Returns None if overflow occurred.","Checked addition. Returns None if overflow occurred.","Checked addition. Returns None if overflow occurred.","Checked division. Returns None if other == 0.","Checked division. Returns None if other == 0.","Checked division. Returns None if other == 0.","Checked multiplication. Returns None if overflow occurred.","Checked multiplication. Returns None if overflow occurred.","Checked multiplication. Returns None if overflow occurred.","Checked negation. Returns None unless self == 0.","Checked negation. Returns None unless self == 0.","Checked negation. Returns None unless self == 0.","Checked exponentiation. Returns None if overflow occurred.","Checked exponentiation. Returns None if overflow occurred.","Checked exponentiation. Returns None if overflow occurred.","Checked modulus. Returns None if other == 0.","Checked modulus. Returns None if other == 0.","Checked modulus. Returns None if other == 0.","Checked subtraction. Returns None if overflow occurred.","Checked subtraction. Returns None if overflow occurred.","Checked subtraction. Returns None if overflow occurred.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a pair (self / other, self % other).","Returns a pair (self / other, self % other).","Returns a pair (self / other, self % other).","","","","","","","","","","","","","","","","","","","","","","","Create 10**n as this type.","Create 10**n as this type.","Create 10**n as this type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Returns the argument unchanged.","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Returns the argument unchanged.","Constructs a hash type from the given bytes array of fixed …","Converts from big endian representation bytes in memory.","Converts from big endian representation bytes in memory.","Converts from big endian representation bytes in memory.","Convert from a decimal string.","Convert from a decimal string.","Convert from a decimal string.","Converts from little endian representation bytes in memory.","Converts from little endian representation bytes in memory.","Converts from little endian representation bytes in memory.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","","","","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Converts a string slice in a given base to an integer. …","Converts a string slice in a given base to an integer. …","Converts a string slice in a given base to an integer. …","Multiplies two 128-bit integers to produce full 256-bit …","Multiplies two 256-bit integers to produce full 512-bit …","","","","","","","","","","","","","","","","","","","","","","Compute the highest n such that n * n <= self.","Compute the highest n such that n * n <= self.","Compute the highest n such that n * n <= self.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Whether this is zero.","Whether this is zero.","Whether this is zero.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns the number of leading zeros in the binary …","Returns the number of leading zeros in the binary …","Returns the number of leading zeros in the binary …","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Low 2 words (u128)","Low 2 words (u128)","Low 2 words (u128)","Conversion to u32","Conversion to u32","Conversion to u32","Low word (u64)","Low word (u64)","Low word (u64)","The maximum value which can be inhabited by this type.","The maximum value which can be inhabited by this type.","The maximum value which can be inhabited by this type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","One (multiplicative identity) of this type.","One (multiplicative identity) of this type.","One (multiplicative identity) of this type.","Addition which overflows and returns a flag if it does.","Addition which overflows and returns a flag if it does.","Addition which overflows and returns a flag if it does.","Multiply with overflow, returning a flag if it does.","Multiply with overflow, returning a flag if it does.","Multiply with overflow, returning a flag if it does.","Negation with overflow.","Negation with overflow.","Negation with overflow.","Fast exponentiation by squaring. Returns result and …","Fast exponentiation by squaring. Returns result and …","Fast exponentiation by squaring. Returns result and …","Subtraction which underflows and returns a flag if it does.","Subtraction which underflows and returns a flag if it does.","Subtraction which underflows and returns a flag if it does.","","","","","","","","","","Fast exponentiation by squaring …","Fast exponentiation by squaring …","Fast exponentiation by squaring …","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","","","","","","","","","","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","","","","","","","","","","Addition which saturates at the maximum value (Self::MAX).","Addition which saturates at the maximum value (Self::MAX).","Addition which saturates at the maximum value (Self::MAX).","Multiplication which saturates at the maximum value..","Multiplication which saturates at the maximum value..","Multiplication which saturates at the maximum value..","Subtraction which saturates at zero.","Subtraction which saturates at zero.","Subtraction which saturates at zero.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write to the slice in big-endian format.","Write to the slice in big-endian format.","Write to the slice in big-endian format.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Write to the slice in little-endian format.","Write to the slice in little-endian format.","Write to the slice in little-endian format.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","","","","","","","","","","","","","","","","","","","Returns the number of trailing zeros in the binary …","Returns the number of trailing zeros in the binary …","Returns the number of trailing zeros in the binary …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Zero (additive identity) of this type.","Zero (additive identity) of this type.","Zero (additive identity) of this type.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash."],"i":[1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,1,2,3,21,0,0,0,1,2,3,1,1,2,2,3,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,21,1,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,1,2,2,3,3,1,2,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,21,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,21,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,21,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,1,2,2,3,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,1,2,2,3,3,1,2,3,1,1,2,2,3,3,1,2,3,1,1,2,2,3,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,21,1,1,1,2,2,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,1],1],[[2,2],2],[[3,3],3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[[1,1]],[[2,2]],[[3,3]],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[1],[2],[3],[4],[5],[6],[7],[8],[9],[1,11],[2,11],[3,11],[1,12],[2,12],[3,12],[1,13],[2,13],[3,13],[1,14],[2,14],[3,14],[4],[5],[6],[7],[8],[9],[[1,14],15],[[2,14],15],[[3,14],15],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[1,14],[2,14],[3,14],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,14],10],[[2,14],10],[[3,14],10],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[1,[[16,[1]]]],[2,[[16,[2]]]],[3,[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,1],17],[[2,2],17],[[3,3],17],[[4,4],17],[[5,5],17],[[6,6],17],[[7,7],17],[[8,8],17],[[9,9],17],[[4,4],15],[[5,5],15],[[6,6],15],[[7,7],15],[[8,8],15],[[9,9],15],[18,[[20,[1,19]]]],[18,[[20,[2,19]]]],[18,[[20,[3,19]]]],[18,[[20,[4,19]]]],[18,[[20,[5,19]]]],[18,[[20,[6,19]]]],[18,[[20,[7,19]]]],[18,[[20,[8,19]]]],[18,[[20,[9,19]]]],[[],1],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[[1,1]],[[2,2]],[[3,3]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[21,21],15],[[1,1],15],[[2,2],15],[[3,3],15],[[4,4],15],[[5,5],15],[[6,6],15],[[7,7],15],[[8,8],15],[[9,9],15],[14,1],[14,2],[14,3],[[21,22],23],[[1,22],23],[[1,22],23],[[1,22],23],[[1,22],23],[[2,22],23],[[2,22],23],[[2,22],23],[[2,22],23],[[3,22],23],[[3,22],23],[[3,22],23],[[3,22],23],[[4,22],23],[[4,22],23],[[4,22],23],[[4,22],23],[[5,22],23],[[5,22],23],[[5,22],23],[[5,22],23],[[6,22],23],[[6,22],23],[[6,22],23],[[6,22],23],[[7,22],23],[[7,22],23],[[7,22],23],[[7,22],23],[[8,22],23],[[8,22],23],[[8,22],23],[[8,22],23],[[9,22],23],[[9,22],23],[[9,22],23],[[9,22],23],[[]],[1,1],[[],1],[24,1],[10,1],[13,1],[25,1],[12,1],[[]],[14,1],[26,1],[[],1],[27,1],[28,1],[[],1],[29,1],[30,1],[31,1],[11,1],[31,2],[[],2],[[],2],[13,2],[11,2],[1,2],[10,2],[27,2],[25,2],[12,2],[14,2],[26,2],[28,2],[29,2],[30,2],[24,2],[[],2],[2,2],[[]],[28,3],[[],3],[27,3],[11,3],[1,3],[10,3],[2,3],[25,3],[12,3],[14,3],[26,3],[31,3],[3,3],[29,3],[2,3],[30,3],[24,3],[[],3],[[],3],[13,3],[[]],[[]],[[],4],[[],4],[[],4],[[],5],[[]],[[],5],[6,5],[[],5],[[],6],[[],6],[[],6],[5,6],[[]],[[],7],[[],7],[[],7],[[]],[[],8],[[]],[[],8],[[],8],[[],9],[[],9],[[]],[[],9],[[],1],[[],2],[[],3],[31,[[20,[1,32]]]],[31,[[20,[2,32]]]],[31,[[20,[3,32]]]],[[],1],[[],2],[[],3],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[31,[[20,[1]]]],[31,[[20,[2]]]],[31,[[20,[3]]]],[31,[[20,[4,33]]]],[31,[[20,[5,33]]]],[31,[[20,[6,33]]]],[31,[[20,[7,33]]]],[31,[[20,[8,33]]]],[31,[[20,[9,33]]]],[[31,12],[[20,[1,34]]]],[[31,12],[[20,[2,34]]]],[[31,12],[[20,[3,34]]]],[[1,1],2],[[2,2],3],[1],[2],[3],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[1,1],[2,2],[3,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,15],[2,15],[3,15],[4,15],[5,15],[6,15],[7,15],[8,15],[9,15],[1,12],[2,12],[3,12],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[1,11],[2,11],[3,11],[1,12],[2,12],[3,12],[1,13],[2,13],[3,13],[[],1],[[],2],[[],3],[[1,25],1],[[1,30],1],[[1,29],1],[[1,29],1],[[1,30],1],[[1,29],1],[[1,26],1],[[1,29],1],[[1,28],1],[[1,28],1],[[1,13],1],[[1,30],1],[[1,30],1],[[1,26],1],[[1,26],1],[[1,14],1],[[1,10],1],[[1,10],1],[[1,10],1],[[1,10],1],[[1,25],1],[[1,25],1],[[1,28],1],[[1,26],1],[[1,28],1],[[1,25],1],[[1,12],1],[[1,14],1],[[1,12],1],[[1,12],1],[[1,12],1],[[1,1],1],[[1,1],1],[[1,13],1],[[1,13],1],[[1,14],1],[[1,1],1],[[1,1],1],[[1,13],1],[[1,24],1],[[1,24],1],[[1,14],1],[[1,24],1],[[1,24],1],[[2,25],2],[[2,28],2],[[2,29],2],[[2,2],2],[[2,2],2],[[2,2],2],[[2,2],2],[[2,10],2],[[2,10],2],[[2,10],2],[[2,10],2],[[2,24],2],[[2,25],2],[[2,25],2],[[2,25],2],[[2,24],2],[[2,24],2],[[2,24],2],[[2,12],2],[[2,12],2],[[2,26],2],[[2,26],2],[[2,26],2],[[2,26],2],[[2,12],2],[[2,30],2],[[2,12],2],[[2,30],2],[[2,30],2],[[2,30],2],[[2,13],2],[[2,29],2],[[2,29],2],[[2,13],2],[[2,29],2],[[2,13],2],[[2,13],2],[[2,14],2],[[2,14],2],[[2,14],2],[[2,14],2],[[2,28],2],[[2,28],2],[[2,28],2],[[3,10],3],[[3,26],3],[[3,10],3],[[3,10],3],[[3,10],3],[[3,3],3],[[3,25],3],[[3,25],3],[[3,25],3],[[3,25],3],[[3,3],3],[[3,12],3],[[3,12],3],[[3,12],3],[[3,12],3],[[3,3],3],[[3,13],3],[[3,13],3],[[3,13],3],[[3,13],3],[[3,3],3],[[3,14],3],[[3,14],3],[[3,14],3],[[3,14],3],[[3,24],3],[[3,28],3],[[3,28],3],[[3,28],3],[[3,28],3],[[3,24],3],[[3,29],3],[[3,29],3],[[3,29],3],[[3,29],3],[[3,24],3],[[3,30],3],[[3,30],3],[[3,30],3],[[3,30],3],[[3,24],3],[[3,26],3],[[3,26],3],[[3,26],3],[[1,29]],[[1,13]],[[1,10]],[[1,25]],[[1,12]],[[1,1]],[[1,24]],[[1,30]],[[1,14]],[[1,28]],[[1,26]],[[2,10]],[[2,24]],[[2,30]],[[2,14]],[[2,29]],[[2,12]],[[2,25]],[[2,13]],[[2,28]],[[2,26]],[[2,2]],[[3,26]],[[3,12]],[[3,10]],[[3,3]],[[3,24]],[[3,14]],[[3,28]],[[3,29]],[[3,13]],[[3,30]],[[3,25]],[1,1],[2,2],[3,3],[[],1],[[],2],[[],3],[[1,1]],[[2,2]],[[3,3]],[[1,1]],[[2,2]],[[3,3]],[1],[2],[3],[[1,1]],[[2,2]],[[3,3]],[[1,1]],[[2,2]],[[3,3]],[[1,1],[[16,[17]]]],[[2,2],[[16,[17]]]],[[3,3],[[16,[17]]]],[[4,4],[[16,[17]]]],[[5,5],[[16,[17]]]],[[6,6],[[16,[17]]]],[[7,7],[[16,[17]]]],[[8,8],[[16,[17]]]],[[9,9],[[16,[17]]]],[[1,1],1],[[2,2],2],[[3,3],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[10,4],[10,5],[10,6],[10,7],[10,8],[10,9],[[1,35]],[[2,35]],[[3,35]],[[4,35]],[[5,35]],[[6,35]],[[7,35]],[[8,35]],[[9,35]],[[1,1],1],[[2,2],2],[[3,3],3],[[1,1],1],[[2,2],2],[[3,3],3],[[1,1],1],[[2,2],2],[[3,3],3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[[1,1]],[[2,2]],[[3,3]],[1],[2],[3],[4],[5],[6],[7],[8],[9],[1],[2],[3],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[1,12],[2,12],[3,12],[[],20],[2,[[20,[1,21]]]],[[],20],[3,[[20,[1,21]]]],[3,[[20,[2,21]]]],[[],20],[3,[[20,[2,21]]]],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],1],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9]],"p":[[3,"U128"],[3,"U256"],[3,"U512"],[3,"H128"],[3,"H160"],[3,"H256"],[3,"H384"],[3,"H512"],[3,"H768"],[15,"u8"],[15,"u128"],[15,"u32"],[15,"u64"],[15,"usize"],[15,"bool"],[4,"Option"],[4,"Ordering"],[3,"Rlp"],[4,"DecoderError"],[4,"Result"],[4,"Error"],[3,"Formatter"],[6,"Result"],[15,"isize"],[15,"u16"],[15,"i64"],[15,"i128"],[15,"i8"],[15,"i16"],[15,"i32"],[15,"str"],[4,"FromDecStrErr"],[4,"FromHexError"],[3,"FromStrRadixErr"],[3,"RlpStream"],[3,"String"],[3,"TypeId"]]},\ +"proc_macro2":{"doc":"github crates-io docs-rs","t":[13,13,13,4,3,13,3,13,13,3,3,13,13,13,3,13,4,3,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Alone","Brace","Bracket","Delimiter","Group","Group","Ident","Ident","Joint","LexError","Literal","Literal","None","Parenthesis","Punct","Punct","Spacing","Span","TokenStream","TokenTree","as_char","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte_string","call_site","character","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","default","delimiter","eq","eq","eq","eq","extend","extend","f32_suffixed","f32_unsuffixed","f64_suffixed","f64_unsuffixed","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_str","from_str","hash","i128_suffixed","i128_unsuffixed","i16_suffixed","i16_unsuffixed","i32_suffixed","i32_unsuffixed","i64_suffixed","i64_unsuffixed","i8_suffixed","i8_unsuffixed","into","into","into","into","into","into","into","into","into","into","into_iter","is_empty","isize_suffixed","isize_unsuffixed","join","located_at","mixed_site","new","new","new","new","new_raw","partial_cmp","provide","resolved_at","set_span","set_span","set_span","set_span","set_span","spacing","span","span","span","span","span","span","span_close","span_open","stream","string","subspan","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","token_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","u128_suffixed","u128_unsuffixed","u16_suffixed","u16_unsuffixed","u32_suffixed","u32_unsuffixed","u64_suffixed","u64_unsuffixed","u8_suffixed","u8_unsuffixed","unwrap","usize_suffixed","usize_unsuffixed","IntoIter","TokenStream","borrow","borrow_mut","clone","clone_into","fmt","from","into","into_iter","next","size_hint","to_owned","try_from","try_into","type_id"],"q":["proc_macro2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","proc_macro2::token_stream","","","","","","","","","","","","","","",""],"d":["E.g. + is Alone in + =, +ident or +().","{ ... }","[ ... ]","Describes how a sequence of token trees is delimited.","A delimited token stream.","A token stream surrounded by bracket delimiters.","A word of Rust code, which may be a keyword or legal …","An identifier.","E.g. + is Joint in += or ' is Joint in '#.","Error returned from TokenStream::from_str.","A literal string ("hello"), byte string (b"hello"), …","A literal character ('a'), string ("hello"), number (2.3), …","Ø ... Ø","( ... )","A Punct is a single punctuation character like +, - or #.","A single punctuation character (+, ,, $, etc.).","Whether a Punct is followed immediately by another Punct …","A region of source code, along with macro expansion …","An abstract stream of tokens, or more concretely a …","A single token or a delimited sequence of token trees …","Returns the value of this punctuation character as char.","","","","","","","","","","","","","","","","","","","","","Byte string literal.","The span of the invocation of the current procedural macro.","Character literal.","","","","","","","","","","","","","","","","","","","","","Returns the delimiter of this Group","","","","","","","Creates a new suffixed floating-point literal.","Creates a new unsuffixed floating-point literal.","Creates a new suffixed floating-point literal.","Creates a new unsuffixed floating-point literal.","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Checks if this TokenStream is empty.","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Create a new span encompassing self and other.","Creates a new span with the same name resolution behavior …","The span located at the invocation of the procedural …","Returns an empty TokenStream containing no token trees.","Creates a new Group with the given delimiter and token …","Creates a new Punct from the given character and spacing.","Creates a new Ident with the given string as well as the …","Same as Ident::new, but creates a raw identifier (r#ident…","","","Creates a new span with the same line/column information …","Configures the span for only this token.","Configures the span for this Group’s delimiters, but not …","Configure the span for this punctuation character.","Configures the span of this Ident, possibly changing its …","Configures the span associated for this literal.","Returns the spacing of this punctuation character, …","","Returns the span of this tree, delegating to the span …","Returns the span for the delimiters of this token stream, …","Returns the span for this punctuation character.","Returns the span of this Ident.","Returns the span encompassing this literal.","Returns the span pointing to the closing delimiter of this …","Returns the span pointing to the opening delimiter of this …","Returns the TokenStream of tokens that are delimited in …","String literal.","Returns a Span that is a subset of self.span() containing …","","","","","","","","","","","","","","","","","Public implementation details for the TokenStream type, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Convert proc_macro2::Span to proc_macro::Span.","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","An iterator over TokenStream’s TokenTrees.","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","",""],"i":[9,8,8,0,0,6,0,6,9,0,0,6,8,8,0,6,0,0,0,0,1,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,3,4,3,5,4,6,7,8,1,9,10,3,5,4,6,7,8,1,9,10,3,10,5,7,8,9,10,10,5,5,3,3,3,3,16,16,5,5,4,6,6,7,7,8,1,1,9,10,10,3,3,16,5,5,5,4,4,6,6,6,6,6,7,8,1,9,10,3,5,5,5,3,10,3,3,3,3,3,3,3,3,3,3,16,5,4,6,7,8,1,9,10,3,5,5,3,3,4,4,4,5,7,1,10,10,10,16,4,6,7,1,10,3,1,16,6,7,1,10,3,7,7,7,3,3,5,4,6,7,8,1,9,10,3,16,5,6,7,1,10,3,0,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,3,3,3,3,3,3,3,3,3,3,4,3,3,0,0,28,28,28,28,28,28,28,28,28,28,28,28,28,28],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[],4],[2,3],[5,5],[4,4],[6,6],[7,7],[8,8],[1,1],[9,9],[10,10],[3,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[10,10],11],[[],5],[7,8],[[8,8],12],[[9,9],12],[10,12],[[10,10],12],[[5,13]],[[5,13]],[14,3],[14,3],[15,3],[15,3],[[16,17],18],[[16,17],18],[[5,17],18],[[5,17],18],[[4,17],18],[[6,17],18],[[6,17],18],[[7,17],18],[[7,17],18],[[8,17],18],[[1,17],18],[[1,17],18],[[9,17],18],[[10,17],18],[[10,17],18],[[3,17],18],[[3,17],18],[[]],[6,5],[[]],[19,5],[[]],[20,4],[[]],[7,6],[10,6],[1,6],[3,6],[[]],[[]],[[]],[[]],[[]],[[]],[13,5],[13,5],[21,[[22,[5,16]]]],[21,[[22,[3,16]]]],[10],[23,3],[23,3],[24,3],[24,3],[25,3],[25,3],[26,3],[26,3],[27,3],[27,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,28],[5,12],[29,3],[29,3],[[4,4],[[30,[4]]]],[[4,4],4],[[],4],[[],5],[[8,5],7],[[2,9],1],[[21,4],10],[[21,4],10],[[10,10],[[30,[11]]]],[31],[[4,4],4],[[6,4]],[[7,4]],[[1,4]],[[10,4]],[[3,4]],[1,9],[16,4],[6,4],[7,4],[1,4],[10,4],[3,4],[7,4],[7,4],[7,5],[21,3],[[3,[33,[32]]],[[30,[4]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],0,[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[36,3],[36,3],[37,3],[37,3],[38,3],[38,3],[39,3],[39,3],[40,3],[40,3],[4,20],[32,3],[32,3],0,0,[[]],[[]],[28,28],[[]],[[28,17],18],[[]],[[]],[[]],[28,[[30,[6]]]],[28],[[]],[[],22],[[],22],[[],35]],"p":[[3,"Punct"],[15,"char"],[3,"Literal"],[3,"Span"],[3,"TokenStream"],[4,"TokenTree"],[3,"Group"],[4,"Delimiter"],[4,"Spacing"],[3,"Ident"],[4,"Ordering"],[15,"bool"],[8,"IntoIterator"],[15,"f32"],[15,"f64"],[3,"LexError"],[3,"Formatter"],[6,"Result"],[3,"TokenStream"],[3,"Span"],[15,"str"],[4,"Result"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[3,"IntoIter"],[15,"isize"],[4,"Option"],[3,"Demand"],[15,"usize"],[8,"RangeBounds"],[3,"String"],[3,"TypeId"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"u8"]]},\ +"quote":{"doc":"github crates-io docs-rs","t":[8,8,8,10,10,10,10,10,14,11,11,14,14,11,11,11,11,10],"n":["IdentFragment","ToTokens","TokenStreamExt","append","append_all","append_separated","append_terminated","fmt","format_ident","into_token_stream","into_token_stream","quote","quote_spanned","span","span","to_token_stream","to_token_stream","to_tokens"],"q":["quote","","","","","","","","","","","","","","","","",""],"d":["Specialized formatting trait used by format_ident!.","Types that can be interpolated inside a quote! invocation.","TokenStream extension trait with methods for appending …","For use by ToTokens implementations.","For use by ToTokens implementations.","For use by ToTokens implementations.","For use by ToTokens implementations.","Format this value as an identifier fragment.","Formatting macro for constructing Idents.","Convert self directly into a TokenStream object.","Convert self directly into a TokenStream object.","The whole point.","Same as quote!, but applies a given span to all tokens …","Span associated with this IdentFragment.","Span associated with this IdentFragment.","Convert self directly into a TokenStream object.","Convert self directly into a TokenStream object.","Write self to the given TokenStream."],"i":[0,0,0,6,6,6,6,7,0,8,8,0,0,7,7,8,8,8],"f":[0,0,0,[[]],[[]],[[]],[[]],[1,2],0,[[],3],[[],3],0,0,[[],[[5,[4]]]],[[],[[5,[4]]]],[[],3],[[],3],[3]],"p":[[3,"Formatter"],[6,"Result"],[3,"TokenStream"],[3,"Span"],[4,"Option"],[8,"TokenStreamExt"],[8,"IdentFragment"],[8,"ToTokens"]]},\ +"rand":{"doc":"Utilities for random number generation","t":[18,8,3,8,18,8,8,16,8,11,11,11,0,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,10,0,11,11,11,0,11,11,11,11,11,0,11,11,11,10,11,11,10,11,11,11,11,13,3,3,4,3,3,8,8,13,13,13,3,3,3,3,13,3,4,3,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,0,8,8,8,16,3,3,3,3,3,8,16,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,4,3,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,3,11,11,11,11,11,11,11,11,11,11,2,2,2,2,2,2,2,3,0,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,16,8,3,8,11,11,10,11,10,11,11,10,10,11,10,10,11,11,0,11,11,11,11,10,10,11,11,11,11,11,4,4,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["CUSTOM_START","CryptoRng","Error","Fill","INTERNAL_START","Rng","RngCore","Seed","SeedableRng","borrow","borrow_mut","code","distributions","fill","fill","fill_bytes","fmt","fmt","from","from","from","from_entropy","from_rng","from_seed","gen","gen","gen_bool","gen_bool","gen_range","gen_range","gen_ratio","gen_ratio","inner","into","new","next_u32","next_u64","prelude","provide","raw_os_error","read","rngs","sample","sample","sample_iter","sample_iter","seed_from_u64","seq","source","take_inner","to_string","try_fill","try_fill","try_fill","try_fill_bytes","try_from","try_into","type_id","vzip","AllWeightsZero","Alphanumeric","Bernoulli","BernoulliError","DistIter","DistMap","DistString","Distribution","InvalidProbability","InvalidWeight","NoItem","Open01","OpenClosed01","Slice","Standard","TooMany","Uniform","WeightedError","WeightedIndex","append_string","append_string","append_string","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from_ratio","into","into","into","into","into","into","into","into","into","into","into_iter","map","map","new","new","next","provide","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample_iter","sample_iter","sample_string","sample_string","size_hint","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uniform","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","weighted","SampleBorrow","SampleRange","SampleUniform","Sampler","Uniform","UniformChar","UniformDuration","UniformFloat","UniformInt","UniformSampler","X","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","is_empty","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","AllWeightsZero","InvalidWeight","NoItem","TooMany","WeightedError","WeightedIndex","alias_method","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","from","from","into","into","new","provide","sample","to_owned","to_owned","to_string","try_from","try_from","try_into","try_into","type_id","type_id","update_weights","vzip","vzip","Weight","WeightedIndex","borrow","borrow_mut","fmt","from","into","new","try_from","try_into","type_id","vzip","CryptoRng","Distribution","IteratorRandom","Rng","RngCore","SeedableRng","SliceRandom","OsRng","adapter","as_rngcore","borrow","borrow_mut","clone","clone_into","default","fill_bytes","fmt","from","into","mock","next_u32","next_u64","to_owned","try_fill_bytes","try_from","try_into","type_id","vzip","ReadError","ReadRng","ReseedingRng","as_rngcore","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","fill_bytes","fill_bytes","fmt","fmt","fmt","fmt","from","from","from","into","into","into","new","new","next_u32","next_u32","next_u64","next_u64","provide","reseed","source","to_owned","to_string","try_fill_bytes","try_fill_bytes","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","StepRng","borrow","borrow_mut","clone","clone_into","eq","fill_bytes","fmt","from","into","new","next_u32","next_u64","to_owned","try_fill_bytes","try_from","try_into","type_id","vzip","Item","IteratorRandom","SliceChooseIter","SliceRandom","borrow","borrow_mut","choose","choose","choose_multiple","choose_multiple","choose_multiple_fill","choose_multiple_weighted","choose_mut","choose_stable","choose_weighted","choose_weighted_mut","fmt","from","index","into","into_iter","len","next","partial_shuffle","shuffle","size_hint","try_from","try_into","type_id","vzip","IndexVec","IndexVecIntoIter","IndexVecIter","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","fmt","fmt","fmt","from","from","from","from","from","index","into","into","into","into_iter","into_iter","into_iter","into_vec","is_empty","iter","len","next","next","sample","sample_weighted","size_hint","size_hint","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip"],"q":["rand","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::uniform","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::weighted","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::weighted::alias_method","","","","","","","","","","","","rand::prelude","","","","","","","rand::rngs","","","","","","","","","","","","","","","","","","","","","rand::rngs::adapter","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::rngs::mock","","","","","","","","","","","","","","","","","","","rand::seq","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::seq::index","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Codes at or above this point can be used by users to …","A marker trait used to indicate that an RngCore or …","Error type of random number generators","Types which may be filled with random data","Codes below this point represent OS Errors (i.e. positive …","An automatically-implemented extension trait on RngCore …","The core of a random number generator.","Seed type, which is restricted to types …","A random number generator that can be explicitly seeded.","","","Retrieve the error code, if any.","Generating random samples from probability distributions","Fill any type implementing Fill with random data","Fill any type implementing Fill with random data","Fill dest with random data.","","","","Returns the argument unchanged.","","Creates a new instance of the RNG seeded via getrandom.","Create a new PRNG seeded from another Rng.","Create a new PRNG using the given seed.","Return a random value supporting the Standard distribution.","Return a random value supporting the Standard distribution.","Return a bool with a probability p of being true.","Return a bool with a probability p of being true.","Generate a random value in the given range.","Generate a random value in the given range.","Return a bool with a probability of numerator/denominator …","Return a bool with a probability of numerator/denominator …","Reference the inner error (std only)","Calls U::from(self).","Construct from any type supporting std::error::Error","Return the next random u32.","Return the next random u64.","Convenience re-export of common members","","Extract the raw OS error code (if this error came from the …","","Random number generators and adapters","Sample a new value, using the given distribution.","Sample a new value, using the given distribution.","Create an iterator that generates values using the given …","Create an iterator that generates values using the given …","Create a new PRNG using a u64 seed.","Sequence-related functionality","","Unwrap the inner error (std only)","","Fill self with random data","Fill any type implementing Fill with random data","Fill any type implementing Fill with random data","Fill dest entirely with random data.","","","","","All items in the provided weight collection are zero.","Sample a u8, uniformly distributed over ASCII letters and …","The Bernoulli distribution.","Error type returned from Bernoulli::new.","An iterator that generates random values of T with …","A distribution of values of type S derived from the …","String sampler","Types (distributions) that can be used to create a random …","p < 0 or p > 1.","A weight is either less than zero, greater than the …","The provided weight collection contains no items.","A distribution to sample floating point numbers uniformly …","A distribution to sample floating point numbers uniformly …","A distribution to sample items uniformly from a slice.","A generic random value distribution, implemented for many …","Too many weights are provided (length greater than u32::MAX…","Sample values uniformly between two bounds.","Error type returned from WeightedIndex::new.","A distribution using weighted sampling of discrete items","Append len random chars to string","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Construct a new Bernoulli with the probability of success …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Create a distribution of values of ‘S’ by mapping the …","Create a distribution of values of ‘S’ by mapping the …","Construct a new Bernoulli with the given probability of …","Create a new Slice instance which samples uniformly from …","","","Generate a random value of T, using rng as the source of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create an iterator that generates random values of T, …","Create an iterator that generates random values of T, …","Generate a String of len random chars","Generate a String of len random chars","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A distribution uniformly sampling numbers within a given …","","","","","","","","","","","Weighted index sampling","Helper trait similar to Borrow but implemented only for …","Range that supports generating a single sample efficiently.","Helper trait for creating objects using the correct …","The UniformSampler implementation supporting type X.","Sample values uniformly between two bounds.","The back-end implementing UniformSampler for char.","The back-end implementing UniformSampler for Duration.","The back-end implementing UniformSampler for …","The back-end implementing UniformSampler for integer types.","Helper trait handling actual uniform sampling.","The type sampled by this implementation.","Immutably borrows from an owned value. See Borrow::borrow","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Check whether the range is empty.","Construct self, with inclusive lower bound and exclusive …","Create a new Uniform instance which samples uniformly from …","","","","","","","","","","","","","","","","","Construct self, with inclusive bounds [low, high].","Create a new Uniform instance which samples uniformly from …","","","","","","","","","","","","","","","","","Sample a value.","","","","","","","","","","","","","","","","","Generate a sample from the given range.","Sample a single value uniformly from a range with …","","","","","","","","","","","","","","","Sample a single value uniformly from a range with …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","All items in the provided weight collection are zero.","A weight is either less than zero, greater than the …","The provided weight collection contains no items.","Too many weights are provided (length greater than u32::MAX…","Error type returned from WeightedIndex::new.","A distribution using weighted sampling of discrete items","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Creates a new a WeightedIndex Distribution using the values","","","","","","","","","","","","Update a subset of weights, without changing the number of …","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","A random number generator that retrieves randomness from …","Wrappers / adapters forming RNGs","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Mock random number generator","","","","","","","","","ReadRng error type","An RNG that reads random bytes straight from any type …","A wrapper around any PRNG that implements BlockRngCore, …","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Create a new ReadRng from a Read.","Create a new ReseedingRng from an existing PRNG, combined …","","","","","","Reseed the internal PRNG.","","","","","","","","","","","","","","","","","","A simple implementation of RngCore for testing purposes.","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Create a StepRng, yielding an arithmetic sequence starting …","","","","","","","","","The element type.","Extension trait on iterators, providing random sampling …","An iterator over multiple slice elements.","Extension trait on slices, providing random mutation and …","","","Returns a reference to one random element of the slice, or …","Choose one element at random from the iterator.","Chooses amount elements from the slice at random, without …","Collects amount values at random from the iterator into a …","Collects values at random from the iterator into a …","Similar to choose_multiple, but where the likelihood of …","Returns a mutable reference to one random element of the …","Choose one element at random from the iterator.","Similar to choose, but where the likelihood of each …","Similar to choose_mut, but where the likelihood of each …","","Returns the argument unchanged.","Low-level API for sampling indices","Calls U::from(self).","","","","Shuffle a slice in place, but exit early.","Shuffle a mutable slice in place.","","","","","","A vector of indices.","Return type of IndexVec::into_iter.","Return type of IndexVec::iter.","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Return the value at the given index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert into an iterator over the indices as a sequence of …","","","Return result as a Vec<usize>. Conversion may or may not …","Returns true if the length is 0.","Iterate over the indices as a sequence of usize values","Returns the number of indices","","","Randomly sample exactly amount distinct indices from …","Randomly sample exactly amount distinct indices from …","","","","","","","","","","","","","","","",""],"i":[1,0,0,0,1,0,0,79,0,1,1,1,0,80,80,15,1,1,1,1,1,79,79,79,80,80,80,80,80,80,80,80,1,1,1,15,15,0,1,1,15,0,80,80,80,80,79,0,1,1,1,81,80,80,15,1,1,1,1,63,0,0,0,0,0,0,0,27,63,63,0,0,0,0,63,0,0,0,82,24,25,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,26,27,28,29,24,31,33,25,26,27,28,29,24,31,33,25,26,27,33,26,27,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,33,33,25,26,26,27,19,37,28,29,24,31,33,25,19,18,18,26,31,19,27,18,26,37,28,28,29,29,24,31,33,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,18,18,82,82,19,26,27,28,29,24,31,33,25,27,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,0,26,27,19,37,28,29,24,31,33,25,0,0,0,0,32,0,0,0,0,0,0,83,84,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,56,58,56,57,58,59,56,57,58,59,56,57,58,59,85,83,33,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,83,33,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,83,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,85,83,56,56,56,56,56,56,56,56,56,56,56,56,58,58,83,56,56,56,56,56,56,56,56,56,56,56,56,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,63,63,63,63,0,0,0,62,63,62,63,62,63,62,63,62,63,62,63,63,62,63,62,63,62,63,62,62,63,63,62,63,62,63,62,63,62,62,63,0,0,65,65,65,65,65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,67,67,67,67,67,67,67,67,67,67,0,67,67,67,67,67,67,67,67,0,0,0,68,70,71,68,70,71,68,68,68,70,68,70,71,71,68,70,71,68,70,71,68,70,68,70,68,70,68,71,68,71,68,71,70,68,70,71,68,70,71,68,70,71,68,70,71,68,0,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,86,0,0,0,73,73,86,87,86,87,87,86,86,87,86,86,73,73,0,73,73,73,73,86,86,73,73,73,73,73,0,0,0,76,78,77,76,78,77,76,77,76,77,76,76,78,77,76,76,76,78,77,76,76,78,77,76,78,77,76,76,76,76,78,77,0,0,78,77,76,77,76,78,77,76,78,77,76,78,77,76,78,77],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[1,[[3,[2]]]],0,[[]],[[]],[[]],[[1,4],[[6,[5]]]],[[1,4],[[6,[5]]]],[7,1],[[]],[2,1],[[]],[[],[[6,[1]]]],[[]],[[]],[[]],[8,9],[8,9],[[]],[[]],[[10,10],9],[[10,10],9],[1,11],[[]],[[],1],[[],10],[[],12],0,[13],[1,[[3,[14]]]],[15,[[6,[16,17]]]],0,[18],[18],[[],19],[[],19],[12],0,[1,[[3,[11]]]],[1,[[21,[11,20]]]],[[],22],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[22,16]],[[24,22,16]],[[25,22,16]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[26,26],[27,27],[28,28],[29,29],[24,24],[[[31,[30]]],[[31,[30]]]],[[[33,[[0,[30,32]]]]],[[33,[[0,[30,32]]]]]],[25,25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[26,26],9],[[27,27],9],[[[33,[[0,[34,32]]]],33],9],[[26,4],35],[[27,4],35],[[27,4],35],[[[19,[36,36,36]],4],35],[[[37,[36,36,36,36]],4],35],[[28,4],35],[[29,4],35],[[24,4],35],[[[31,[36]],4],35],[[[33,[[0,[36,32]]]],4],35],[[25,4],35],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[38,[32]]],[[33,[32]]]],[[[39,[32]]],[[33,[32]]]],[[]],[[10,10],[[6,[26,27]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[],37],[8,[[6,[26,27]]]],[[],[[6,[31,0]]]],[19,3],[13],[[]],[26,9],[37],[28,40],[28,8],[29,8],[29,40],[24,41],[31],[[[33,[32]]],32],[25],[25],[25,42],[25,10],[25,12],[25,43],[25,16],[25,44],[25,45],[25,46],[25,3],[25,14],[25,47],[25,48],[25,49],[25,50],[25],[25,51],[25],[25,2],[25,52],[25,53],[25,54],[25,55],[25],[25],[25,9],[25],[25],[25],[25],[25,41],[25],[25],[25],[25,8],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25,40],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[[],19],[[],19],[16,22],[16,22],[19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[56,[30]]],[[56,[30]]]],[57,57],[[[58,[30]]],[[58,[30]]]],[59,59],[[]],[[]],[[]],[[]],[[[56,[34]],56],9],[[[58,[34]],58],9],[[[56,[36]],4],35],[[57,4],35],[[[58,[36]],4],35],[[59,4],35],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],9],[[]],[[],[[33,[32]]]],[[],[[56,[16]]]],[[],[[56,[49]]]],[[],[[56,[46]]]],[[],[[56,[41]]]],[[],[[56,[48]]]],[[],[[56,[47]]]],[[],[[56,[44]]]],[[],[[56,[14]]]],[[],[[56,[10]]]],[[],[[56,[43]]]],[[],[[56,[12]]]],[[],[[56,[42]]]],[[],57],[[],[[58,[8]]]],[[],[[58,[40]]]],[[],59],[[]],[[],[[33,[32]]]],[[],[[56,[14]]]],[[],[[56,[12]]]],[[],[[56,[41]]]],[[],[[56,[16]]]],[[],[[56,[44]]]],[[],[[56,[49]]]],[[],[[56,[10]]]],[[],[[56,[46]]]],[[],[[56,[48]]]],[[],[[56,[42]]]],[[],[[56,[47]]]],[[],[[56,[43]]]],[[],57],[[],[[58,[8]]]],[[],[[58,[40]]]],[[],59],[[]],[[[56,[41]]]],[[[56,[42]]]],[[[56,[10]]]],[[[56,[14]]]],[[[56,[47]]]],[[[56,[16]]]],[[[56,[43]]]],[[[56,[48]]]],[[[56,[12]]]],[[[56,[46]]]],[[[56,[44]]]],[[[56,[49]]]],[57],[[[58,[40]]]],[[[58,[8]]]],[59,60],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[],23],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[[62,[[0,[30,32,61]]]]],[[62,[[0,[30,32,61]]]]]],[63,63],[[]],[[]],[[[62,[[0,[34,32,61]]]],62],9],[[63,63],9],[[[62,[[0,[36,32,61]]]],4],35],[[63,4],35],[[63,4],35],[[]],[[]],[[]],[[]],[[],[[6,[[62,[[0,[32,61]]]],63]]]],[13],[62,16],[[]],[[]],[[],22],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[[62,[[0,[32,61]]]]],[[6,[63]]]],[[]],[[]],0,0,[[]],[[]],[[[65,[[0,[36,64]]]],4],35],[[]],[[]],[[[66,[64]]],[[6,[[65,[64]],63]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,0,0,0,0,0,[[],15],[[]],[[]],[67,67],[[]],[[],67],[67],[[67,4],[[6,[5]]]],[[]],[[]],0,[67,10],[67,12],[[]],[67,[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,[[],15],[[]],[[]],[[]],[[]],[[]],[[]],[68,68],[[]],[[[70,[69]]]],[[[68,[15]]]],[[[70,[36]],4],35],[[71,4],35],[[71,4],35],[[[68,[36,36]],4],35],[[]],[[]],[[]],[[]],[[]],[[]],[69,[[70,[69]]]],[12,68],[[[70,[69]]],10],[[[68,[15]]],10],[[[70,[69]]],12],[[[68,[15]]],12],[13],[68,[[6,[1]]]],[71,[[3,[11]]]],[[]],[[],22],[[[70,[69]]],[[6,[1]]]],[[[68,[15]]],[[6,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[]],[[]],[[]],0,[[]],[[]],[72,72],[[]],[[72,72],9],[72],[[72,4],35],[[]],[[]],[[12,12],72],[72,10],[72,12],[[]],[72,[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,[[]],[[]],[[],3],[[],3],[16,73],[16,66],[[],16],[16,[[6,[73,63]]]],[[],3],[[],3],[[],[[6,[63]]]],[[],[[6,[63]]]],[[[73,[[0,[36,74]],36]],4],35],[[]],0,[[]],[[]],[[[73,[[0,[[75,[16]],74]]]]],16],[[[73,[[0,[[75,[16]],74]]]]],3],[16],[[]],[[[73,[[0,[[75,[16]],74]]]]]],[[],6],[[],6],[[],23],[[]],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[76,76],[77,77],[[]],[[]],[[76,76],9],[[76,4],35],[[78,4],35],[[77,4],35],[[[66,[10]]],76],[[[66,[16]]],76],[[]],[[]],[[]],[[76,16],16],[[]],[[]],[[]],[76,77],[[]],[[]],[76,[[66,[16]]]],[76,9],[76,78],[76,16],[78,[[3,[16]]]],[77,3],[[16,16],76],[[16,16],[[6,[76,63]]]],[78],[77],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[]],[[]],[[]]],"p":[[3,"Error"],[3,"NonZeroU32"],[4,"Option"],[3,"Formatter"],[3,"Error"],[4,"Result"],[3,"Error"],[15,"f64"],[15,"bool"],[15,"u32"],[8,"Error"],[15,"u64"],[3,"Demand"],[15,"i32"],[8,"RngCore"],[15,"usize"],[3,"Error"],[8,"Distribution"],[3,"DistIter"],[3,"Global"],[3,"Box"],[3,"String"],[3,"TypeId"],[3,"Alphanumeric"],[3,"Standard"],[3,"Bernoulli"],[4,"BernoulliError"],[3,"OpenClosed01"],[3,"Open01"],[8,"Clone"],[3,"Slice"],[8,"SampleUniform"],[3,"Uniform"],[8,"PartialEq"],[6,"Result"],[8,"Debug"],[3,"DistMap"],[3,"RangeInclusive"],[3,"Range"],[15,"f32"],[15,"u8"],[15,"u16"],[15,"u128"],[15,"i8"],[3,"Wrapping"],[15,"i16"],[15,"i64"],[15,"i128"],[15,"isize"],[3,"NonZeroU8"],[3,"NonZeroU16"],[3,"NonZeroU64"],[3,"NonZeroU128"],[3,"NonZeroUsize"],[15,"char"],[3,"UniformInt"],[3,"UniformChar"],[3,"UniformFloat"],[3,"UniformDuration"],[3,"Duration"],[8,"PartialOrd"],[3,"WeightedIndex"],[4,"WeightedError"],[8,"Weight"],[3,"WeightedIndex"],[3,"Vec"],[3,"OsRng"],[3,"ReseedingRng"],[8,"Read"],[3,"ReadRng"],[3,"ReadError"],[3,"StepRng"],[3,"SliceChooseIter"],[8,"Sized"],[8,"Index"],[4,"IndexVec"],[4,"IndexVecIntoIter"],[4,"IndexVecIter"],[8,"SeedableRng"],[8,"Rng"],[8,"Fill"],[8,"DistString"],[8,"UniformSampler"],[8,"SampleBorrow"],[8,"SampleRange"],[8,"SliceRandom"],[8,"IteratorRandom"]]},\ +"rand_chacha":{"doc":"The ChaCha random number generator.","t":[3,3,3,3,3,3,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["ChaCha12Core","ChaCha12Rng","ChaCha20Core","ChaCha20Rng","ChaCha8Core","ChaCha8Rng","ChaChaCore","ChaChaRng","as_rngcore","as_rngcore","as_rngcore","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","eq","eq","eq","eq","eq","eq","fill_bytes","fill_bytes","fill_bytes","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_seed","from_seed","from_seed","from_seed","from_seed","from_seed","generate","generate","generate","get_seed","get_seed","get_seed","get_stream","get_stream","get_stream","get_word_pos","get_word_pos","get_word_pos","into","into","into","into","into","into","next_u32","next_u32","next_u32","next_u64","next_u64","next_u64","rand_core","set_stream","set_stream","set_stream","set_word_pos","set_word_pos","set_word_pos","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_fill_bytes","try_fill_bytes","try_fill_bytes","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip"],"q":["rand_chacha","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["ChaCha with 12 rounds","A cryptographically secure random number generator that …","ChaCha with 20 rounds","A cryptographically secure random number generator that …","ChaCha with 8 rounds","A cryptographically secure random number generator that …","ChaCha with 20 rounds, low-level interface","ChaCha with 20 rounds","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","Get the seed.","Get the seed.","Get the seed.","Get the stream number.","Get the stream number.","Get the stream number.","Get the offset from the start of the stream, in 32-bit …","Get the offset from the start of the stream, in 32-bit …","Get the offset from the start of the stream, in 32-bit …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","Set the stream number.","Set the stream number.","Set the stream number.","Set the offset from the start of the stream, in 32-bit …","Set the offset from the start of the stream, in 32-bit …","Set the offset from the start of the stream, in 32-bit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,3,5,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,3,5,7,2,3,4,5,6,7,2,3,3,4,5,5,6,7,7,2,3,4,5,6,7,2,4,6,3,5,7,3,5,7,3,5,7,2,3,4,5,6,7,3,5,7,3,5,7,0,3,5,7,3,5,7,2,3,4,5,6,7,3,5,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7],"f":[0,0,0,0,0,0,0,0,[[],1],[[],1],[[],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[[]],[[]],[[]],[[]],[[]],[[]],[[2,2],8],[[3,3],8],[[4,4],8],[[5,5],8],[[6,6],8],[[7,7],8],[3],[5],[7],[[2,9],10],[[3,9],10],[[4,9],10],[[5,9],10],[[6,9],10],[[7,9],10],[[]],[[]],[2,3],[[]],[4,5],[[]],[[]],[[]],[6,7],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[2],[4],[6],[3],[5],[7],[3,11],[5,11],[7,11],[3,12],[5,12],[7,12],[[]],[[]],[[]],[[]],[[]],[[]],[3,13],[5,13],[7,13],[3,11],[5,11],[7,11],0,[[3,11]],[[5,11]],[[7,11]],[[3,12]],[[5,12]],[[7,12]],[[]],[[]],[[]],[[]],[[]],[[]],[3,[[15,[14]]]],[5,[[15,[14]]]],[7,[[15,[14]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[]],[[]],[[]],[[]],[[]],[[]]],"p":[[8,"RngCore"],[3,"ChaCha20Core"],[3,"ChaCha20Rng"],[3,"ChaCha12Core"],[3,"ChaCha12Rng"],[3,"ChaCha8Core"],[3,"ChaCha8Rng"],[15,"bool"],[3,"Formatter"],[6,"Result"],[15,"u64"],[15,"u128"],[15,"u32"],[3,"Error"],[4,"Result"],[3,"TypeId"]]},\ +"rand_core":{"doc":"Random number generation traits","t":[18,8,8,3,18,3,8,16,8,10,11,0,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,10,0,11,11,11,0,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,3,3,8,16,16,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5],"n":["CUSTOM_START","CryptoRng","CryptoRngCore","Error","INTERNAL_START","OsRng","RngCore","Seed","SeedableRng","as_rngcore","as_rngcore","block","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","code","default","fill_bytes","fill_bytes","fmt","fmt","fmt","from","from","from","from","from_entropy","from_rng","from_seed","impls","inner","into","into","le","new","next_u32","next_u32","next_u64","next_u64","provide","raw_os_error","read","seed_from_u64","source","take_inner","to_owned","to_string","try_fill_bytes","try_fill_bytes","try_from","try_from","try_into","try_into","type_id","type_id","BlockRng","BlockRng64","BlockRngCore","Item","Results","as_rngcore","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","core","core","fill_bytes","fill_bytes","fmt","fmt","from","from","from_rng","from_rng","from_seed","from_seed","generate","generate_and_set","generate_and_set","index","index","into","into","new","new","next_u32","next_u32","next_u64","next_u64","reset","reset","seed_from_u64","seed_from_u64","to_owned","to_owned","try_fill_bytes","try_fill_bytes","try_from","try_from","try_into","try_into","type_id","type_id","fill_bytes_via_next","fill_via_u32_chunks","fill_via_u64_chunks","next_u32_via_fill","next_u64_via_fill","next_u64_via_u32","read_u32_into","read_u64_into"],"q":["rand_core","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand_core::block","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand_core::impls","","","","","","rand_core::le",""],"d":["Codes at or above this point can be used by users to …","A marker trait used to indicate that an RngCore or …","An extension trait that is automatically implemented for …","Error type of random number generators","Codes below this point represent OS Errors (i.e. positive …","A random number generator that retrieves randomness from …","The core of a random number generator.","Seed type, which is restricted to types …","A random number generator that can be explicitly seeded.","Upcast to an RngCore trait object.","","The BlockRngCore trait and implementation helpers","","","","","","","Retrieve the error code, if any.","","Fill dest with random data.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Creates a new instance of the RNG seeded via getrandom.","Create a new PRNG seeded from another Rng.","Create a new PRNG using the given seed.","Helper functions for implementing RngCore functions.","Reference the inner error (std only)","Calls U::from(self).","Calls U::from(self).","Little-Endian utilities","Construct from any type supporting std::error::Error","Return the next random u32.","","Return the next random u64.","","","Extract the raw OS error code (if this error came from the …","","Create a new PRNG using a u64 seed.","","Unwrap the inner error (std only)","","","Fill dest entirely with random data.","","","","","","","","A wrapper type implementing RngCore for some type …","A wrapper type implementing RngCore for some type …","A trait for RNGs which do not generate random numbers …","Results element type, e.g. u32.","Results type. This is the ‘block’ an RNG implementing …","","","","","","","","","","The core part of the RNG, implementing the generate …","The core part of the RNG, implementing the generate …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Generate a new block of results.","Generate a new set of results immediately, setting the …","Generate a new set of results immediately, setting the …","Get the index into the result buffer.","Get the index into the result buffer.","Calls U::from(self).","Calls U::from(self).","Create a new BlockRng from an existing RNG implementing …","Create a new BlockRng from an existing RNG implementing …","","","","","Reset the number of available results. This will force a …","Reset the number of available results. This will force a …","","","","","","","","","","","","","Implement fill_bytes via next_u64 and next_u32, …","Implement fill_bytes by reading chunks from the output …","Implement fill_bytes by reading chunks from the output …","Implement next_u32 via fill_bytes, little-endian order.","Implement next_u64 via fill_bytes, little-endian order.","Implement next_u64 via next_u32, little-endian order.","Reads unsigned 32 bit integers from src into dst.","Reads unsigned 64 bit integers from src into dst."],"i":[3,0,0,0,3,0,0,26,0,27,2,0,3,2,3,2,2,2,3,2,1,2,3,3,2,3,3,3,2,26,26,26,0,3,3,2,0,3,1,2,1,2,3,3,1,26,3,3,2,3,1,2,3,2,3,2,3,2,0,0,0,21,21,23,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,21,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,0,0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,[[],1],[[],1],0,[[]],[[]],[[]],[[]],[2,2],[[]],[3,[[5,[4]]]],[[],2],[[]],[2],[[3,6],7],[[3,6],7],[[2,6],7],[4,3],[8,3],[[]],[[]],[[]],[1,[[9,[3]]]],[[]],0,[3,10],[[]],[[]],0,[[],3],[[],11],[2,11],[[],12],[2,12],[13],[3,[[5,[14]]]],[1,[[9,[15,16]]]],[12],[3,[[5,[10]]]],[3,[[17,[10]]]],[[]],[[],18],[[],[[9,[3]]]],[2,[[9,[3]]]],[[],9],[[],9],[[],9],[[],9],[[],19],[[],19],0,0,0,0,0,[[],1],[[]],[[]],[[]],[[]],[[[23,[[0,[20,21,22]]]]],[[23,[[0,[20,21,22]]]]]],[[[24,[[0,[20,21,22]]]]],[[24,[[0,[20,21,22]]]]]],[[]],[[]],0,0,[[[23,[21]]]],[[[24,[21]]]],[[[23,[[0,[21,25]]]],6],7],[[[24,[[0,[21,25]]]],6],7],[[]],[[]],[1,[[9,[[23,[[0,[21,26]]]],3]]]],[1,[[9,[[24,[[0,[21,26]]]],3]]]],[[],[[23,[[0,[21,26]]]]]],[[],[[24,[[0,[21,26]]]]]],[[]],[[[23,[21]],15]],[[[24,[21]],15]],[[[23,[21]]],15],[[[24,[21]]],15],[[]],[[]],[21,[[23,[21]]]],[21,[[24,[21]]]],[[[23,[21]]],11],[[[24,[21]]],11],[[[23,[21]]],12],[[[24,[21]]],12],[[[23,[21]]]],[[[24,[21]]]],[12,[[23,[[0,[21,26]]]]]],[12,[[24,[[0,[21,26]]]]]],[[]],[[]],[[[23,[21]]],[[9,[3]]]],[[[24,[21]]],[[9,[3]]]],[[],9],[[],9],[[],9],[[],9],[[],19],[[],19],[[]],[[]],[[]],[[],11],[[],12],[[],12],[[]],[[]]],"p":[[8,"RngCore"],[3,"OsRng"],[3,"Error"],[3,"NonZeroU32"],[4,"Option"],[3,"Formatter"],[6,"Result"],[3,"Error"],[4,"Result"],[8,"Error"],[15,"u32"],[15,"u64"],[3,"Demand"],[15,"i32"],[15,"usize"],[3,"Error"],[3,"Box"],[3,"String"],[3,"TypeId"],[8,"Clone"],[8,"BlockRngCore"],[8,"Sized"],[3,"BlockRng"],[3,"BlockRng64"],[8,"Debug"],[8,"SeedableRng"],[8,"CryptoRngCore"]]},\ +"regex":{"doc":"This crate provides a library for parsing, compiling, and …","t":[12,3,3,3,3,13,4,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","CaptureLocations","CaptureMatches","CaptureNames","Captures","CompiledTooBig","Error","Match","Matches","NoExpand","Regex","RegexBuilder","RegexSet","RegexSetBuilder","Replacer","ReplacerRef","SetMatches","SetMatchesIntoIter","SetMatchesIter","Split","SplitN","SubCaptureMatches","Syntax","as_str","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build","by_ref","by_ref","bytes","capture_locations","capture_names","captures","captures_iter","captures_len","captures_read","captures_read_at","case_insensitive","case_insensitive","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count","count","description","dfa_size_limit","dfa_size_limit","dot_matches_new_line","dot_matches_new_line","empty","end","eq","eq","escape","expand","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_str","get","get","ignore_whitespace","ignore_whitespace","index","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","is_match","is_match","is_match_at","iter","iter","len","len","len","len","matched","matched_any","matches","multi_line","multi_line","name","nest_limit","nest_limit","new","new","new","new","next","next","next","next","next","next","next","next","next_back","next_back","no_expansion","no_expansion","no_expansion","no_expansion","octal","octal","patterns","provide","range","replace","replace_all","replace_append","replace_append","replace_append","replacen","shortest_match","shortest_match_at","size_hint","size_hint","size_hint","size_hint","size_hint","size_limit","size_limit","split","splitn","start","swap_greed","swap_greed","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unicode","unicode","0","0","0","CaptureLocations","CaptureMatches","CaptureNames","Captures","Match","Matches","NoExpand","Regex","RegexBuilder","RegexSet","RegexSetBuilder","Replacer","ReplacerRef","SetMatches","SetMatchesIntoIter","SetMatchesIter","Split","SplitN","SubCaptureMatches","as_bytes","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build","by_ref","by_ref","capture_locations","capture_names","captures","captures_iter","captures_len","captures_read","captures_read_at","case_insensitive","case_insensitive","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count","dfa_size_limit","dfa_size_limit","dot_matches_new_line","dot_matches_new_line","empty","end","eq","expand","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_str","get","get","ignore_whitespace","ignore_whitespace","index","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","is_match","is_match","is_match_at","iter","iter","len","len","len","len","matched","matched_any","matches","multi_line","multi_line","name","nest_limit","nest_limit","new","new","new","new","next","next","next","next","next","next","next","next","next_back","next_back","no_expansion","no_expansion","no_expansion","no_expansion","octal","octal","patterns","range","replace","replace_all","replace_append","replace_append","replace_append","replacen","shortest_match","shortest_match_at","size_hint","size_hint","size_hint","size_hint","size_limit","size_limit","split","splitn","start","swap_greed","swap_greed","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unicode","unicode"],"q":["regex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex::Error","","regex::bytes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","CaptureLocations is a low level representation of the raw …","An iterator that yields all non-overlapping capture groups …","An iterator over the names of all possible captures.","Captures represents a group of captured strings for a …","The compiled program exceeded the set size limit. The …","An error that occurred during parsing or compiling a …","Match represents a single match of a regex in a haystack.","An iterator over all non-overlapping matches for a …","NoExpand indicates literal string replacement.","A compiled regular expression for matching Unicode strings.","A configurable builder for a regular expression.","Match multiple (possibly overlapping) regular expressions …","A configurable builder for a set of regular expressions.","Replacer describes types that can be used to replace …","By-reference adaptor for a Replacer","A set of matches returned by a regex set.","An owned iterator over the set of matches from a regex set.","A borrowed iterator over the set of matches from a regex …","Yields all substrings delimited by a regular expression …","Yields at most N substrings delimited by a regular …","An iterator that yields all capturing matches in the order …","A syntax error.","Returns the matched text.","Returns the original string of this regex.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Consume the builder and compile the regular expression.","Consume the builder and compile the regular expressions …","Return a Replacer that borrows and wraps this Replacer.","Return a Replacer that borrows and wraps this Replacer.","Match regular expressions on arbitrary bytes.","Returns an empty set of capture locations that can be …","Returns an iterator over the capture names.","Returns the capture groups corresponding to the …","Returns an iterator over all the non-overlapping capture …","Returns the number of captures.","This is like captures, but uses CaptureLocations instead of","Returns the same as captures, but starts the search at the …","Set the value for the case insensitive (i) flag.","Set the value for the case insensitive (i) flag.","","","","","","","","","","","","","","","","","","","","","","","","Set the approximate size of the cache used by the DFA.","Set the approximate size of the cache used by the DFA.","Set the value for the any character (s) flag, where in . …","Set the value for the any character (s) flag, where in . …","Create a new empty regex set.","Returns the ending byte offset of the match in the …","","","Escapes all regular expression meta characters in text.","Expands all instances of $name in replacement to the …","Returns the start and end byte range of the leftmost-first …","Returns the same as find, but starts the search at the …","Returns an iterator for each successive non-overlapping …","","","","","","","","","","","Shows the original regular expression.","Shows the original regular expression.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Attempts to parse a string into a regular expression","Returns the match associated with the capture group at …","Returns the start and end positions of the Nth capture …","Set the value for the ignore whitespace (x) flag.","Set the value for the ignore whitespace (x) flag.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","Returns true if this set contains no regular expressions.","Returns true if and only if one of the regexes in this set …","Returns true if and only if there is a match for the regex …","Returns the same as is_match, but starts the search at the …","An iterator that yields all capturing matches in the order …","Returns an iterator over indexes in the regex that matched.","Returns the total number of capture groups (even if they …","Returns the total number of regular expressions in this …","The total number of regexes in the set that created these …","Returns the total number of capture groups (even if they …","Whether the regex at the given index matched.","Whether this set contains any matches.","Returns the set of regular expressions that match in the …","Set the value for the multi-line matching (m) flag.","Set the value for the multi-line matching (m) flag.","Returns the match for the capture group named name. If name…","Set the nesting limit for this parser.","Set the nesting limit for this parser.","Create a new regular expression builder with the given …","Create a new regular expression builder with the given …","Create a new regex set with the given regular expressions.","Compiles a regular expression. Once compiled, it can be …","","","","","","","","","","","Return a fixed unchanging replacement string.","Return a fixed unchanging replacement string.","","","Whether to support octal syntax or not.","Whether to support octal syntax or not.","Returns the patterns that this set will match on.","","Returns the range over the starting and ending byte …","Replaces the leftmost-first match with the replacement …","Replaces all non-overlapping matches in text with the …","Appends text to dst to replace the current match.","","","Replaces at most limit non-overlapping matches in text …","Returns the end location of a match in the text given.","Returns the same as shortest_match, but starts the search …","","","","","","Set the approximate size limit of the compiled regular …","Set the approximate size limit of the compiled regular …","Returns an iterator of substrings of text delimited by a …","Returns an iterator of at most limit substrings of text …","Returns the starting byte offset of the match in the …","Set the value for the greedy swap (U) flag.","Set the value for the greedy swap (U) flag.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Set the value for the Unicode (u) flag.","Set the value for the Unicode (u) flag.","","","","CaptureLocations is a low level representation of the raw …","An iterator that yields all non-overlapping capture groups …","An iterator over the names of all possible captures.","Captures represents a group of captured byte strings for a …","Match represents a single match of a regex in a haystack.","An iterator over all non-overlapping matches for a …","NoExpand indicates literal byte string replacement.","A compiled regular expression for matching arbitrary bytes.","A configurable builder for a regular expression.","Match multiple (possibly overlapping) regular expressions …","A configurable builder for a set of regular expressions.","Replacer describes types that can be used to replace …","By-reference adaptor for a Replacer","A set of matches returned by a regex set.","An owned iterator over the set of matches from a regex set.","A borrowed iterator over the set of matches from a regex …","Yields all substrings delimited by a regular expression …","Yields at most N substrings delimited by a regular …","An iterator that yields all capturing matches in the order …","Returns the matched text.","Returns the original string of this regex.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Consume the builder and compile the regular expression.","Consume the builder and compile the regular expressions …","Return a Replacer that borrows and wraps this Replacer.","Return a Replacer that borrows and wraps this Replacer.","Returns an empty set of capture locations that can be …","Returns an iterator over the capture names.","Returns the capture groups corresponding to the …","Returns an iterator over all the non-overlapping capture …","Returns the number of captures.","This is like captures, but uses CaptureLocations instead of","Returns the same as captures_read, but starts the search …","Set the value for the case insensitive (i) flag.","Set the value for the case insensitive (i) flag.","","","","","","","","","","","","","","","","","","","","Set the approximate size of the cache used by the DFA.","Set the approximate size of the cache used by the DFA.","Set the value for the any character (s) flag, where in . …","Set the value for the any character (s) flag, where in . …","Create a new empty regex set.","Returns the ending byte offset of the match in the …","","Expands all instances of $name in replacement to the …","Returns the start and end byte range of the leftmost-first …","Returns the same as find, but starts the search at the …","Returns an iterator for each successive non-overlapping …","","","","","Shows the original regular expression.","Shows the original regular expression.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Attempts to parse a string into a regular expression","Returns the match associated with the capture group at …","Returns the start and end positions of the Nth capture …","Set the value for the ignore whitespace (x) flag.","Set the value for the ignore whitespace (x) flag.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","Returns true if this set contains no regular expressions.","Returns true if and only if there is a match for the regex …","Returns true if and only if one of the regexes in this set …","Returns the same as is_match, but starts the search at the …","An iterator that yields all capturing matches in the order …","Returns an iterator over indexes in the regex that matched.","Returns the total number of capture groups (even if they …","Returns the total number of capture groups (even if they …","Returns the total number of regular expressions in this …","The total number of regexes in the set that created these …","Whether the regex at the given index matched.","Whether this set contains any matches.","Returns the set of regular expressions that match in the …","Set the value for the multi-line matching (m) flag.","Set the value for the multi-line matching (m) flag.","Returns the match for the capture group named name. If name…","Set the nesting limit for this parser.","Set the nesting limit for this parser.","Create a new regular expression builder with the given …","Create a new regular expression builder with the given …","Compiles a regular expression. Once compiled, it can be …","Create a new regex set with the given regular expressions.","","","","","","","","","","","Return a fixed unchanging replacement byte string.","Return a fixed unchanging replacement byte string.","","","Whether to support octal syntax or not.","Whether to support octal syntax or not.","Returns the patterns that this set will match on.","Returns the range over the starting and ending byte …","Replaces the leftmost-first match with the replacement …","Replaces all non-overlapping matches in text with the …","Appends text to dst to replace the current match.","","","Replaces at most limit non-overlapping matches in text …","Returns the end location of a match in the text given.","Returns the same as shortest_match, but starts the search …","","","","","Set the approximate size limit of the compiled regular …","Set the approximate size limit of the compiled regular …","Returns an iterator of substrings of text delimited by a …","Returns an iterator of at most limit substrings of text …","Returns the starting byte offset of the match in the …","Set the value for the greedy swap (U) flag.","Set the value for the greedy swap (U) flag.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Set the value for the Unicode (u) flag.","Set the value for the Unicode (u) flag."],"i":[20,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,3,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,4,7,32,32,0,3,3,3,3,3,3,3,4,7,5,8,17,18,1,3,11,10,19,20,5,8,17,18,1,3,11,10,19,20,11,19,5,4,7,4,7,8,1,5,1,0,12,3,3,3,12,5,5,4,7,8,17,25,18,1,3,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,3,12,10,4,7,12,12,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,17,17,25,18,11,26,27,19,14,22,8,8,3,3,12,17,12,8,17,10,17,17,8,4,7,12,4,7,4,7,8,3,25,18,11,26,27,19,14,22,25,18,32,32,9,20,4,7,8,5,1,3,3,32,9,20,3,3,3,25,18,11,27,19,4,7,3,3,1,4,7,5,8,17,18,1,3,11,10,19,20,5,3,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,4,7,56,57,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,37,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,38,39,55,55,37,37,37,37,37,37,37,38,39,36,37,43,42,46,47,40,48,49,36,37,43,42,46,47,40,48,49,43,38,39,38,39,40,36,36,44,37,37,37,44,38,39,36,37,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,37,44,42,38,39,44,44,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,51,45,52,53,43,46,48,48,54,49,40,37,40,37,44,48,44,42,40,48,48,48,40,38,39,44,38,39,38,39,37,40,51,45,52,53,43,46,54,49,54,49,55,55,41,47,38,39,40,36,37,37,55,41,47,37,37,37,53,43,54,49,38,39,37,37,36,38,39,36,37,43,42,46,47,40,48,49,37,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,38,39],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[3,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,[[6,[3,5]]]],[7,[[6,[8,5]]]],[[],9],[[],9],0,[3,10],[3,11],[[3,2],[[13,[12]]]],[[3,2],14],[3,15],[[3,10,2],[[13,[1]]]],[[3,10,2,15],[[13,[1]]]],[[4,16],4],[[7,16],7],[5,5],[8,8],[17,17],[18,18],[1,1],[3,3],[11,11],[10,10],[19,19],[20,20],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[11,15],[19,15],[5,2],[[4,15],4],[[7,15],7],[[4,16],4],[[7,16],7],[[],8],[1,15],[[5,5],16],[[1,1],16],[2,21],[[12,2,21]],[[3,2],[[13,[1]]]],[[3,2,15],[[13,[1]]]],[[3,2],22],[[12,23],24],[[5,23],24],[[5,23],24],[[4,23],24],[[7,23],24],[[8,23],24],[[17,23],24],[[25,23],24],[[18,23],24],[[1,23],24],[[3,23],24],[[3,23],24],[[11,23],24],[[26,23],24],[[27,23],24],[[10,23],24],[[19,23],24],[[14,23],24],[[22,23],24],[[[9,[[0,[28,29]]]],23],24],[[20,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[6,[3,5]]]],[[12,15],[[13,[1]]]],[[10,15],13],[[4,16],4],[[7,16],7],[[12,2],2],[[12,15],2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[17],[17],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[8,16],[[8,2],16],[[3,2],16],[[3,2,15],16],[12,19],[17,18],[12,15],[8,15],[17,15],[10,15],[[17,15],16],[17,16],[[8,2],17],[[4,16],4],[[7,16],7],[[12,2],[[13,[1]]]],[[4,30],4],[[7,30],7],[2,4],[[],7],[[],[[6,[8,5]]]],[2,[[6,[3,5]]]],[25,[[13,[15]]]],[18,[[13,[15]]]],[11,[[13,[[13,[2]]]]]],[26,[[13,[2]]]],[27,[[13,[2]]]],[19,[[13,[[13,[1]]]]]],[14,[[13,[12]]]],[22,[[13,[1]]]],[25,[[13,[15]]]],[18,[[13,[15]]]],[[],[[13,[[31,[2]]]]]],[[],[[13,[[31,[2]]]]]],[[[9,[[0,[32,29]]]]],[[13,[[31,[2]]]]]],[20,[[13,[[31,[2]]]]]],[[4,16],4],[[7,16],7],[8],[33],[1,[[34,[15]]]],[[3,2,32],[[31,[2]]]],[[3,2,32],[[31,[2]]]],[[12,21]],[[[9,[[0,[32,29]]]],12,21]],[[20,12,21]],[[3,2,15,32],[[31,[2]]]],[[3,2],[[13,[15]]]],[[3,2,15],[[13,[15]]]],[25],[18],[11],[27],[19],[[4,15],4],[[7,15],7],[[3,2],26],[[3,2,15],27],[1,15],[[4,16],4],[[7,16],7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],21],[[],21],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[4,16],4],[[7,16],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[36],[37,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[38,[[6,[37,5]]]],[39,[[6,[40,5]]]],[[],41],[[],41],[37,42],[37,43],[37,[[13,[44]]]],[37,45],[37,15],[[37,42],[[13,[36]]]],[[37,42,15],[[13,[36]]]],[[38,16],38],[[39,16],39],[36,36],[37,37],[43,43],[42,42],[46,46],[47,47],[40,40],[48,48],[49,49],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[43,15],[[38,15],38],[[39,15],39],[[38,16],38],[[39,16],39],[[],40],[36,15],[[36,36],16],[[44,50]],[37,[[13,[36]]]],[[37,15],[[13,[36]]]],[37,51],[[44,23],24],[[38,23],24],[[39,23],24],[[36,23],24],[[37,23],24],[[37,23],24],[[51,23],24],[[45,23],24],[[52,23],24],[[53,23],24],[[43,23],24],[[42,23],24],[[46,23],24],[[[41,[[0,[28,29]]]],23],24],[[47,23],24],[[40,23],24],[[48,23],24],[[54,23],24],[[49,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[6,[37,5]]]],[[44,15],[[13,[36]]]],[[42,15],13],[[38,16],38],[[39,16],39],[[44,15]],[[44,2]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[48],[48],[[]],[[]],[40,16],[37,16],[40,16],[[37,15],16],[44,46],[48,49],[44,15],[42,15],[40,15],[48,15],[[48,15],16],[48,16],[40,48],[[38,16],38],[[39,16],39],[[44,2],[[13,[36]]]],[[38,30],38],[[39,30],39],[2,38],[[],39],[2,[[6,[37,5]]]],[[],[[6,[40,5]]]],[51,[[13,[36]]]],[45,[[13,[44]]]],[52,13],[53,13],[43,[[13,[[13,[2]]]]]],[46,[[13,[[13,[36]]]]]],[54,[[13,[15]]]],[49,[[13,[15]]]],[54,[[13,[15]]]],[49,[[13,[15]]]],[[],[[13,[31]]]],[[],[[13,[31]]]],[[[41,[[0,[55,29]]]]],[[13,[31]]]],[47,[[13,[31]]]],[[38,16],38],[[39,16],39],[40],[36,[[34,[15]]]],[[37,55],31],[[37,55],31],[[44,50]],[[[41,[[0,[55,29]]]],44,50]],[[47,44,50]],[[37,15,55],31],[37,[[13,[15]]]],[[37,15],[[13,[15]]]],[53],[43],[54],[49],[[38,15],38],[[39,15],39],[37,52],[[37,15],53],[36,15],[[38,16],38],[[39,16],39],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],21],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[38,16],38],[[39,16],39]],"p":[[3,"Match"],[15,"str"],[3,"Regex"],[3,"RegexBuilder"],[4,"Error"],[4,"Result"],[3,"RegexSetBuilder"],[3,"RegexSet"],[3,"ReplacerRef"],[3,"CaptureLocations"],[3,"CaptureNames"],[3,"Captures"],[4,"Option"],[3,"CaptureMatches"],[15,"usize"],[15,"bool"],[3,"SetMatches"],[3,"SetMatchesIter"],[3,"SubCaptureMatches"],[3,"NoExpand"],[3,"String"],[3,"Matches"],[3,"Formatter"],[6,"Result"],[3,"SetMatchesIntoIter"],[3,"Split"],[3,"SplitN"],[8,"Debug"],[8,"Sized"],[15,"u32"],[4,"Cow"],[8,"Replacer"],[3,"Demand"],[3,"Range"],[3,"TypeId"],[3,"Match"],[3,"Regex"],[3,"RegexBuilder"],[3,"RegexSetBuilder"],[3,"RegexSet"],[3,"ReplacerRef"],[3,"CaptureLocations"],[3,"CaptureNames"],[3,"Captures"],[3,"CaptureMatches"],[3,"SubCaptureMatches"],[3,"NoExpand"],[3,"SetMatches"],[3,"SetMatchesIter"],[3,"Vec"],[3,"Matches"],[3,"Split"],[3,"SplitN"],[3,"SetMatchesIntoIter"],[8,"Replacer"],[13,"Syntax"],[13,"CompiledTooBig"]]},\ +"regex_syntax":{"doc":"This crate provides a robust regular expression parser.","t":[4,13,3,3,6,13,3,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,0,12,12,13,13,3,13,13,13,3,13,4,4,13,13,13,13,13,13,13,13,13,3,13,13,13,4,13,3,4,3,13,3,4,13,13,4,3,4,4,3,3,13,3,4,4,13,13,3,3,13,13,13,13,13,13,13,13,13,13,13,13,13,16,3,4,13,13,13,13,13,13,4,13,13,13,13,13,13,3,13,3,4,13,13,3,13,4,13,13,13,13,13,13,13,13,4,13,13,13,13,3,13,13,4,13,13,13,13,13,13,13,13,13,13,13,13,16,13,13,3,13,13,13,13,13,3,13,13,13,13,4,13,3,4,3,13,13,13,3,13,4,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,8,3,13,13,13,13,13,13,13,11,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,12,12,0,11,11,11,0,11,11,12,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,13,4,13,13,13,13,13,13,13,13,13,3,4,13,3,3,3,3,3,3,13,13,13,13,13,16,3,4,13,3,13,4,3,4,13,4,13,13,13,16,13,3,13,4,4,13,13,13,13,13,13,13,13,13,13,13,8,4,13,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,0,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,3,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12],"n":["Error","Parse","Parser","ParserBuilder","Result","Translate","UnicodeWordError","allow_invalid_utf8","ast","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","case_insensitive","clone","clone","clone","clone_into","clone_into","clone_into","default","description","dot_matches_new_line","eq","escape","escape_into","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hir","ignore_whitespace","into","into","into","into","is_meta_character","is_word_byte","is_word_character","multi_line","nest_limit","new","new","octal","parse","provide","provide","swap_greed","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_is_word_character","type_id","type_id","type_id","type_id","unicode","utf8","0","0","Alnum","Alpha","Alternation","Alternation","Ascii","Ascii","Assertion","Assertion","AssertionKind","Ast","AtLeast","Bell","BinaryOp","Blank","Bounded","Bracketed","Bracketed","CaptureIndex","CaptureLimitExceeded","CaptureName","CaptureName","CarriageReturn","CaseInsensitive","Class","Class","ClassAscii","ClassAsciiKind","ClassBracketed","ClassEscapeInvalid","ClassPerl","ClassPerlKind","ClassRangeInvalid","ClassRangeLiteral","ClassSet","ClassSetBinaryOp","ClassSetBinaryOpKind","ClassSetItem","ClassSetRange","ClassSetUnion","ClassUnclosed","ClassUnicode","ClassUnicodeKind","ClassUnicodeOpKind","Cntrl","Colon","Comment","Concat","Concat","DecimalEmpty","DecimalInvalid","Difference","Digit","Digit","Dot","DotMatchesNewLine","Empty","Empty","EndLine","EndText","Equal","Err","Error","ErrorKind","EscapeHexEmpty","EscapeHexInvalid","EscapeHexInvalidDigit","EscapeUnexpectedEof","EscapeUnrecognized","Exactly","Flag","Flag","FlagDanglingNegation","FlagDuplicate","FlagRepeatedNegation","FlagUnexpectedEof","FlagUnrecognized","Flags","Flags","FlagsItem","FlagsItemKind","FormFeed","Graph","Group","Group","GroupKind","GroupNameDuplicate","GroupNameEmpty","GroupNameInvalid","GroupNameUnexpectedEof","GroupUnclosed","GroupUnopened","HexBrace","HexFixed","HexLiteralKind","IgnoreWhitespace","Intersection","Item","LineFeed","Literal","Literal","Literal","LiteralKind","Lower","MultiLine","Named","NamedValue","Negation","NestLimitExceeded","NonCapturing","NotEqual","NotWordBoundary","Octal","OneLetter","OneOrMore","Output","Perl","Perl","Position","Print","Punct","Punctuation","Range","Range","Repetition","Repetition","RepetitionCountDecimalEmpty","RepetitionCountInvalid","RepetitionCountUnclosed","RepetitionKind","RepetitionMissing","RepetitionOp","RepetitionRange","SetFlags","Space","Space","Space","Span","Special","SpecialLiteralKind","StartLine","StartText","SwapGreed","SymmetricDifference","Tab","Unicode","Unicode","Unicode","UnicodeClassInvalid","UnicodeLong","UnicodeShort","Union","UnsupportedBackreference","UnsupportedLookAround","Upper","Verbatim","VerticalTab","Visitor","WithComments","Word","Word","WordBoundary","X","Xdigit","ZeroOrMore","ZeroOrOne","add_item","ast","ast","ast","asts","asts","auxiliary_span","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte","c","capture_index","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","column","comment","comments","description","digits","drop","drop","end","end","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","finish","flag_state","flags","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_name","greedy","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_ast","into_ast","into_item","is_capturing","is_empty","is_empty","is_equal","is_negated","is_negation","is_one_line","is_valid","is_valid","items","items","kind","kind","kind","kind","kind","kind","kind","kind","kind","kind","kind","lhs","line","name","negated","negated","negated","negated","new","new","offset","op","parse","partial_cmp","partial_cmp","pattern","print","provide","push","rhs","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","splat","start","start","start","start","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","visit","visit_alternation_in","visit_alternation_in","visit_class_set_binary_op_in","visit_class_set_binary_op_in","visit_class_set_binary_op_post","visit_class_set_binary_op_post","visit_class_set_binary_op_pre","visit_class_set_binary_op_pre","visit_class_set_item_post","visit_class_set_item_post","visit_class_set_item_pre","visit_class_set_item_pre","visit_post","visit_post","visit_pre","visit_pre","with_end","with_start","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","name","op","value","0","original","original","original","0","0","0","0","0","0","0","0","0","0","0","1","Parser","ParserBuilder","borrow","borrow","borrow_mut","borrow_mut","build","clone","clone","clone_into","clone_into","default","fmt","fmt","from","from","ignore_whitespace","into","into","nest_limit","new","new","octal","parse","parse_with_comments","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","Printer","borrow","borrow_mut","fmt","from","into","new","print","try_from","try_into","type_id","Alternation","Anchor","Anchor","Ascii","AsciiNegate","AtLeast","Bounded","Byte","Bytes","CaptureIndex","CaptureName","CaseFoldError","Class","Class","ClassBytes","ClassBytesIter","ClassBytesRange","ClassUnicode","ClassUnicodeIter","ClassUnicodeRange","Concat","Empty","EmptyClassNotAllowed","EndLine","EndText","Err","Error","ErrorKind","Exactly","Group","Group","GroupKind","Hir","HirKind","InvalidUtf8","Literal","Literal","NonCapturing","OneOrMore","Output","Range","Repetition","Repetition","RepetitionKind","RepetitionRange","StartLine","StartText","Unicode","Unicode","Unicode","UnicodeCaseUnavailable","UnicodeNegate","UnicodeNotAllowed","UnicodePerlClassNotFound","UnicodePropertyNotFound","UnicodePropertyValueNotFound","Visitor","WordBoundary","WordBoundary","ZeroOrMore","ZeroOrOne","alternation","anchor","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","case_fold_simple","case_fold_simple","case_fold_simple","class","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","concat","default","default","description","difference","difference","dot","drop","empty","empty","empty","end","end","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","finish","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","greedy","group","has_subexprs","hir","hir","intersect","intersect","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_kind","is_all_ascii","is_all_ascii","is_all_assertions","is_alternation_literal","is_always_utf8","is_always_utf8","is_anchored_end","is_anchored_start","is_any_anchored_end","is_any_anchored_start","is_empty","is_line_anchored_end","is_line_anchored_start","is_literal","is_match_empty","is_match_empty","is_negated","is_unicode","iter","iter","kind","kind","kind","kind","literal","literal","negate","negate","negate","new","new","new","new","next","next","partial_cmp","partial_cmp","pattern","print","provide","provide","push","push","ranges","ranges","repetition","span","start","start","start","start","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","translate","try_case_fold_simple","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","visit","visit_alternation_in","visit_alternation_in","visit_post","visit_post","visit_pre","visit_pre","word_boundary","0","0","0","index","name","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","Literal","Literals","add","add_byte_class","add_char_class","all_complete","any_complete","as_ref","borrow","borrow","borrow_mut","borrow_mut","clear","clone","clone","clone_into","clone_into","cmp","contains_empty","cross_add","cross_product","cut","cut","deref","deref_mut","empty","empty","eq","eq","fmt","fmt","from","from","into","into","is_cut","is_empty","limit_class","limit_size","literals","longest_common_prefix","longest_common_suffix","min_len","new","partial_cmp","prefixes","reverse","set_limit_class","set_limit_size","suffixes","to_empty","to_owned","to_owned","trim_suffix","try_from","try_from","try_into","try_into","type_id","type_id","unambiguous_prefixes","unambiguous_suffixes","union","union_prefixes","union_suffixes","Printer","borrow","borrow_mut","fmt","from","into","new","print","try_from","try_into","type_id","Translator","TranslatorBuilder","allow_invalid_utf8","borrow","borrow","borrow_mut","borrow_mut","build","case_insensitive","clone","clone","clone_into","clone_into","default","dot_matches_new_line","fmt","fmt","from","from","into","into","multi_line","new","new","swap_greed","to_owned","to_owned","translate","try_from","try_from","try_into","try_into","type_id","type_id","unicode","Four","One","Three","Two","Utf8Range","Utf8Sequence","Utf8Sequences","as_slice","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","end","eq","eq","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","len","matches","matches","new","next","partial_cmp","partial_cmp","reverse","start","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","0","0"],"q":["regex_syntax","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::Error","","regex_syntax::ast","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::ast::Ast","","","","","","","","","","regex_syntax::ast::Class","","","regex_syntax::ast::ClassSet","","regex_syntax::ast::ClassSetItem","","","","","","","","regex_syntax::ast::ClassUnicodeKind","","","","","regex_syntax::ast::ErrorKind","","","","regex_syntax::ast::FlagsItemKind","regex_syntax::ast::GroupKind","","","regex_syntax::ast::LiteralKind","","","regex_syntax::ast::RepetitionKind","regex_syntax::ast::RepetitionRange","","","","regex_syntax::ast::parse","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::ast::print","","","","","","","","","","","regex_syntax::hir","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::hir::Class","","regex_syntax::hir::GroupKind","","","regex_syntax::hir::HirKind","","","","","","","","regex_syntax::hir::Literal","","regex_syntax::hir::RepetitionKind","regex_syntax::hir::RepetitionRange","","","","regex_syntax::hir::literal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::hir::print","","","","","","","","","","","regex_syntax::hir::translate","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::utf8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::utf8::Utf8Sequence","","",""],"d":["This error type encompasses any error that can be returned …","An error that occurred while translating concrete syntax …","A convenience parser for regular expressions.","A builder for a regular expression parser.","A type alias for dealing with errors returned by this …","An error that occurred while translating abstract syntax …","An error that occurs when the Unicode-aware \\\\w class is …","When enabled, the parser will permit the construction of a …","Defines an abstract syntax for regular expressions.","","","","","","","","","Build a parser from this configuration with the given …","Enable or disable the case insensitive flag by default.","","","","","","","","","Enable or disable the “dot matches any character” flag …","","Escapes all regular expression meta characters in text.","Escapes all meta characters in text and writes the result …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Defines a high-level intermediate representation for …","Enable verbose mode in the regular expression.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if the given character has significance in a …","Returns true if and only if the given character is an …","Returns true if and only if the given character is a …","Enable or disable the multi-line matching flag by default.","Set the nesting limit for this parser.","Create a new parser builder with a default configuration.","Create a new parser with a default configuration.","Whether to support octal syntax or not.","Parse the regular expression into a high level intermediate","","","Enable or disable the “swap greed” flag by default.","","","","","","","","","","","","","","Returns true if and only if the given character is a …","","","","","Enable or disable the Unicode flag (u) by default.","Converts ranges of Unicode scalar values to equivalent …","","","[0-9A-Za-z]","[A-Za-z]","An alternation of regular expressions.","An alternation of regular expressions.","[\\\\x00-\\\\x7F]","An ASCII character class, e.g., [:alnum:] or [:punct:].","A single zero-width assertion.","A single zero-width assertion.","An assertion kind.","An abstract syntax tree for a single regular expression.","{m,}","Bell, spelled \\\\a (\\\\x07).","A single binary operation (i.e., &&, – or ~~).","[ \\\\t]","{m,n}","A bracketed character class set, which may contain zero or …","A bracketed character class set, which may contain zero or …","(a)","The capturing group limit was exceeded.","A capture name.","(?P<name>a)","Carriage return, spelled \\\\r (\\\\x0D).","i","A single character class expression.","A single character class. This includes all forms of …","An ASCII character class.","The available ASCII character classes.","A bracketed character class, e.g., [a-z0-9].","An invalid escape sequence was found in a character class …","A Perl character class.","The available Perl character classes.","An invalid character class range was found. An invalid …","An invalid range boundary was found in a character class. …","A character class set.","A Unicode character class set operation.","The type of a Unicode character class set operation.","A single component of a character class set.","A single character class range in a set.","A union of items inside a character class set.","An opening [ was found with no corresponding closing ].","A Unicode character class.","The available forms of Unicode character classes.","The type of op used in a Unicode character class.","[\\\\x00-\\\\x1F\\\\x7F]","A property set to a specific value using a colon, e.g., …","A comment from a regular expression with an associated …","A concatenation of regular expressions.","A concatenation of regular expressions.","Note that this error variant is no longer used. Namely, a …","An invalid decimal number was given where one was expected.","The difference of two sets, e.g., \\\\pN--[0-9].","Decimal numbers.","[0-9]","The “any character” class.","s","An empty regex that matches everything.","An empty item.","$","\\\\z","A property set to a specific value, e.g., \\\\p{scx=Katakana}.","An error that visiting an AST might return.","An error that occurred while parsing a regular expression …","The type of an error that occurred while building an AST.","A bracketed hex literal was empty.","A bracketed hex literal did not correspond to a Unicode …","An invalid hexadecimal digit was found.","EOF was found before an escape sequence was completed.","An unrecognized escape sequence.","{m}","A single flag.","A single flag in a group.","A dangling negation was used when setting flags, e.g., i-.","A flag was used twice, e.g., i-i.","The negation operator was used twice, e.g., -i-s.","Expected a flag but got EOF, e.g., (?.","Unrecognized flag, e.g., a.","A group of flags.","A set of flags, e.g., (?is).","A single item in a group of flags.","The kind of an item in a group of flags.","Form feed, spelled \\\\f (\\\\x0C).","[!-~]","A grouped regular expression.","A grouped regular expression.","The kind of a group.","A duplicate capture name was found.","A capture group name is empty, e.g., (?P<>abc).","An invalid character was seen for a capture group name. …","A closing > could not be found for a capture group name.","An unclosed group, e.g., (ab.","An unopened group, e.g., ab).","The literal is written as a hex code with a bracketed …","The literal is written as a hex code with a fixed number …","The type of a Unicode hex literal.","x","The intersection of two sets, e.g., \\\\pN&&[a-z].","An item, which can be a single literal, range, nested …","Line feed, spelled \\\\n (\\\\x0A).","A single literal expression.","A single character literal, which includes escape …","A single literal.","The kind of a single literal expression.","[a-z]","m","A binary property, general category or script. The string …","A property name and an associated value.","A negation operator applied to all subsequent flags in the …","The nest limit was exceeded. The limit stored here is the …","(?:a) and (?i:a)","A property that isn’t a particular value, e.g., …","\\\\B","The literal is written as an octal escape, e.g., \\\\141.","A one letter abbreviated class, e.g., \\\\pN.","+","The result of visiting an AST.","A perl character class, e.g., \\\\d or \\\\W.","A perl character class, e.g., \\\\d or \\\\W.","A single position in a regular expression.","[ -~]","[!-/:-@\\\\[-{-~]`","The literal is written as an escape because it is …","A range between two literals.","{m,n}","A repetition operation applied to a regular expression.","A repetition operator applied to an arbitrary regular …","An opening { was not followed by a valid decimal value. …","The range provided in a counted repetition operator is …","An opening { was found with no corresponding closing }.","The kind of a repetition operator.","A repetition operator was applied to a missing …","The repetition operator itself.","A range repetition operator.","A group of flags that is not applied to a particular …","Space, spelled \\\\ (\\\\x20). Note that this can only appear …","Whitespace.","[\\\\t\\\\n\\\\v\\\\f\\\\r ]","Span represents the position information of a single AST …","The literal is written as a specially recognized escape, …","The type of a special literal.","^","\\\\A","U","The symmetric difference of two sets. The symmetric …","Tab, spelled \\\\t (\\\\x09).","A Unicode character class, e.g., \\\\pL or \\\\p{Greek}.","A Unicode character class, e.g., \\\\pL or \\\\p{Greek}.","u","The Unicode class is not valid. This typically occurs when …","A \\\\U prefix. When used without brackets, this form is …","A \\\\u prefix. When used without brackets, this form is …","A union of items.","When octal support is disabled, this error is produced …","When syntax similar to PCRE’s look-around is used, this …","[A-Z]","The literal is written verbatim, e.g., a or .","Vertical tab, spelled \\\\v (\\\\x0B).","A trait for visiting an abstract syntax tree (AST) in …","An abstract syntax tree for a singular expression along …","Word characters.","[0-9A-Za-z_]","\\\\b","A \\\\x prefix. When used without brackets, this form is …","[0-9A-Fa-f]","*","?","Add the given item to this sequence of flags.","The actual ast.","The regular expression under repetition.","The regular expression in this group.","The alternate regular expressions.","The concatenation regular expressions.","Return an auxiliary span. This span exists only for some …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","If this literal was written as a \\\\x hex escape, then this …","The Unicode scalar value corresponding to this literal.","Returns the capture index of this group, if this is a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The approximate column number, starting at 1.","The comment text, starting with the first character …","All comments found in the original regular expression.","","The number of digits that must be used with this literal …","","","The end byte offset.","The end of this range.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","All implementors of Visitor must provide a finish method, …","Returns the state of the given flag in this set.","If this group is non-capturing, then this returns the …","The actual sequence of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Return the corresponding ClassAsciiKind variant for the …","Whether this operation was applied greedily or not.","The capture index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Return this alternation as an AST.","Return this concatenation as an AST.","Return this union as a character class set item.","Returns true if and only if this group is capturing.","Returns true if and only if this span is empty. That is, …","Return true if and only if this Ast is empty.","Whether the op is an equality op or not.","Returns true if this class has been negated.","Returns true if and only if this item is a negation …","Returns true if and only if this span occurs on a single …","Returns true if and only if this character class range is …","Returns true if and only if this repetition range is valid.","The sequence of items that make up this union.","A sequence of flag items. Each item is either a flag or a …","Return the type of this error.","The kind of this literal.","The kind of Perl class.","The kind of ASCII class.","The kind of Unicode class.","The type of this set. A set is either a normal union of …","The type of this set operation.","The assertion kind, e.g., \\\\b or ^.","The type of operation.","The kind of this group.","The kind of this item.","The left hand side of the operation.","The line number, starting at 1.","The capture name.","Whether the class is negated or not. e.g., \\\\d is not …","Whether the class is negated or not. e.g., [[:alpha:]] is …","Whether this class is negated or not.","Whether this class is negated or not. e.g., [a] is not …","Create a new span with the given positions.","Create a new position with the given information.","The absolute offset of this position, starting at 0 from …","The actual operation.","This module provides a regular expression parser.","","","The original pattern string in which this error occurred.","This module provides a regular expression printer for Ast.","","Push a new item in this union.","The right hand side of the operation.","Return the span at which this error occurred.","Return the span of this abstract syntax tree.","Return the span of this character class.","Return the span of this character class set.","Return the span of this character class set item.","The span of this comment, including the beginning # and …","The span of this alternation.","The span of this concatenation.","The span of this literal.","The span of this class.","The span of this class.","The span of this class.","The span of this class.","The span of this range.","The span of the items in this operation. e.g., the a-z0-9 …","The span of this operation. e.g., the a-z--[h-p] in …","The span of this assertion.","The span of this operation.","The span of this operator. This includes things like +, *? …","The span of this group.","The span of this capture name.","The span of these flags, including the grouping …","The span of this group of flags.","The span of this item.","Create a new span using the given position as the start …","This method is called before beginning traversal of the …","This method is called before beginning traversal of the …","The start byte offset.","The start of this range.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Build a set from a union.","Executes an implementation of Visitor in constant stack …","This method is called between child nodes of an Alternation…","This method is called between child nodes of an Alternation…","This method is called between the left hand and right hand …","This method is called between the left hand and right hand …","This method is called on every ClassSetBinaryOp after …","This method is called on every ClassSetBinaryOp after …","This method is called on every ClassSetBinaryOp before …","This method is called on every ClassSetBinaryOp before …","This method is called on every ClassSetItem after …","This method is called on every ClassSetItem after …","This method is called on every ClassSetItem before …","This method is called on every ClassSetItem before …","This method is called on an Ast after descending all of …","This method is called on an Ast after descending all of …","This method is called on an Ast before descending into …","This method is called on an Ast before descending into …","Create a new span by replacing the ending the position …","Create a new span by replacing the starting the position …","","","","","","","","","","","","","","","","","","","","","","","","","","The property name (which may be empty).","The type of Unicode op used to associate name with value.","The property value (which may be empty).","","The position of the original flag. The error position …","The position of the original negation operator. The error …","The position of the initial occurrence of the capture …","","","","","","","","","","","","","A regular expression parser.","A builder for a regular expression parser.","","","","","Build a parser from this configuration with the given …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Enable verbose mode in the regular expression.","Calls U::from(self).","Calls U::from(self).","Set the nesting limit for this parser.","Create a new parser builder with a default configuration.","Create a new parser with a default configuration.","Whether to support octal syntax or not.","Parse the regular expression into an abstract syntax tree.","Parse the regular expression and return an abstract syntax …","","","","","","","","","A printer for a regular expression abstract syntax tree.","","","","Returns the argument unchanged.","Calls U::from(self).","Create a new printer.","Print the given Ast to the given writer. The writer must …","","","","An alternation of expressions. An alternation always has …","The high-level intermediate representation for an anchor …","An anchor assertion. An anchor assertion match always has …","Match an ASCII-only word boundary. That is, this matches a …","Match an ASCII-only negation of a word boundary.","Matches a sub-expression at least this many times.","Matches a sub-expression at least m times and at most n …","A single character represented by an arbitrary byte.","A set of characters represented by arbitrary bytes (one …","A normal unnamed capturing group.","A named capturing group.","An error that occurs when Unicode-aware simple case …","The high-level intermediate representation of a character …","A single character class that matches any of the …","A set of characters represented by arbitrary bytes (where …","An iterator over all ranges in a byte character class.","A single range of characters represented by arbitrary …","A set of characters represented by Unicode scalar values.","An iterator over all ranges in a Unicode character class.","A single range of characters represented by Unicode scalar …","A concatenation of expressions. A concatenation always has …","The empty regular expression, which matches everything, …","This occurs when the translator attempts to construct a …","Match the end of a line or the end of text. Specifically, …","Match the end of text. Specifically, this matches at the …","An error that visiting an HIR might return.","An error that can occur while translating an Ast to a Hir.","The type of an error that occurred while building an Hir.","Matches a sub-expression exactly this many times.","The high-level intermediate representation for a group.","A possibly capturing group, which contains a child …","The kind of group.","A high-level intermediate representation (HIR) for a …","The kind of an arbitrary Hir expression.","This error occurs when translating a pattern that could …","The high-level intermediate representation of a literal.","A single literal character that matches exactly this …","A non-capturing group.","Matches a sub-expression one or more times.","The result of visiting an HIR.","Matches a sub-expression within a bounded range of times.","The high-level intermediate representation of a repetition …","A repetition operation applied to a child expression.","The kind of a repetition operator.","The kind of a counted repetition operator.","Match the beginning of a line or the beginning of text. …","Match the beginning of text. Specifically, this matches at …","A single character represented by a Unicode scalar value.","A set of characters represented by Unicode scalar values.","Match a Unicode-aware word boundary. That is, this matches …","This occurs when the Unicode simple case mapping tables …","Match a Unicode-aware negation of a word boundary.","This error occurs when a Unicode feature is used when …","This occurs when a Unicode-aware Perl character class (\\\\w, …","This occurs when an unrecognized Unicode property name …","This occurs when an unrecognized Unicode property value …","A trait for visiting the high-level IR (HIR) in depth …","The high-level intermediate representation for a …","A word boundary assertion, which may or may not be Unicode …","Matches a sub-expression zero or more times.","Matches a sub-expression zero or one times.","Returns the alternation of the given expressions.","Creates an anchor assertion HIR expression.","Build an HIR expression for (?s)..","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Apply Unicode simple case folding to this character class, …","Expand this character class such that it contains all case …","Expand this character class such that it contains all case …","Creates a class HIR expression.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the concatenation of the given expressions.","","","","Subtract the given character class from this character …","Subtract the given byte class from this byte class, in …","Build an HIR expression for ..","","Returns an empty HIR expression.","Create a new class with no ranges.","Create a new class with no ranges.","Return the end of this range.","Return the end of this range.","","","","","","","","","","","","","","","","","","All implementors of Visitor must provide a finish method, …","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Whether this repetition operator is greedy or not. A …","Creates a group HIR expression.","Returns true if and only if this kind has any (including …","The expression inside the capturing group, which may be …","The expression being repeated.","Intersect this character class with the given character …","Intersect this byte class with the given byte class, in …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes ownership of this HIR expression and returns its …","Returns true if and only if this character class will …","Returns true if and only if this character class will …","Returns true if and only if this entire HIR expression is …","Return true if and only if this HIR is either a simple …","Return true if and only if this HIR will always match …","Returns true if and only if this character class will only …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR contains any …","Return true if and only if this HIR contains any …","Return true if and only if this HIR is the empty regular …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is a simple literal. …","Return true if and only if the empty string is part of the …","Returns true if and only if this repetition operator makes …","Returns true if and only if this word boundary assertion …","Returns true if and only if this literal corresponds to a …","Return an iterator over all ranges in this class.","Return an iterator over all ranges in this class.","Return the type of this error.","Returns a reference to the underlying HIR kind.","The kind of this group. If it is a capturing group, then …","The kind of this repetition operator.","Provides routines for extracting literal prefixes and …","Creates a literal HIR expression.","Negate this character class in place.","Negate this character class.","Negate this byte class.","Create a new class from a sequence of ranges.","Create a new Unicode scalar value range for a character …","Create a new class from a sequence of ranges.","Create a new byte range for a character class.","","","","","The original pattern string in which this error occurred.","This module provides a regular expression printer for Hir.","","","Add a new range to this set.","Add a new range to this set.","Return the underlying ranges as a slice.","Return the underlying ranges as a slice.","Creates a repetition HIR expression.","Return the span at which this error occurred.","This method is called before beginning traversal of the …","This method is called before beginning traversal of the …","Return the start of this range.","Return the start of this range.","Compute the symmetric difference of the given character …","Compute the symmetric difference of the given byte …","","","","","","","","","","","","","","","","","","","","","","Defines a translator that converts an Ast to an Hir.","Expand this character class such that it contains all case …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Union this character class with the given character class, …","Union this byte class with the given byte class, in place.","Executes an implementation of Visitor in constant stack …","This method is called between child nodes of an …","This method is called between child nodes of an …","This method is called on an Hir after descending all of …","This method is called on an Hir after descending all of …","This method is called on an Hir before descending into …","This method is called on an Hir before descending into …","Creates a word boundary assertion HIR expression.","","","","The capture index of the group.","The name of the group.","","","","","","","","","","","","","","","","A single member of a set of literals extracted from a …","A set of literal byte strings extracted from a regular …","Adds the given literal to this set.","Extends each literal in this set with the byte class given.","Extends each literal in this set with the character class …","Returns true if all members in this set are complete.","Returns true if any member in this set is complete.","","","","","","Clears this set of all members.","","","","","","Returns true if this set contains an empty literal.","Extends each literal in this set with the bytes given.","Extends this set with another set.","Cuts every member of this set. When a member is cut, it …","Cuts this literal.","","","Returns a new empty set of literals using default limits.","Returns a new complete empty literal.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Returns true if this literal was “cut.”","Returns true if this set is empty or if all of its members …","Get the character class size limit for this set.","Get the approximate size limit (in bytes) of this set.","Returns the set of literals as a slice. Its order is …","Returns the longest common prefix of all members in this …","Returns the longest common suffix of all members in this …","Returns the length of the smallest literal.","Returns a new complete literal with the bytes given.","","Returns a set of literal prefixes extracted from the given …","Reverses all members in place.","Limits the size of character(or byte) classes considered.","Set the approximate size limit (in bytes) of this set.","Returns a set of literal suffixes extracted from the given …","Returns a new empty set of literals using this set’s …","","","Returns a new set of literals with the given number of …","","","","","","","Returns a new set of prefixes of this set of literals that …","Returns a new set of suffixes of this set of literals that …","Unions this set with another set.","Unions the prefixes from the given expression to this set.","Unions the suffixes from the given expression to this set.","A printer for a regular expression’s high-level …","","","","Returns the argument unchanged.","Calls U::from(self).","Create a new printer.","Print the given Ast to the given writer. The writer must …","","","","A translator maps abstract syntax to a high level …","A builder for constructing an AST->HIR translator.","When enabled, translation will permit the construction of …","","","","","Build a translator using the current configuration.","Enable or disable the case insensitive flag (i) by default.","","","","","","Enable or disable the “dot matches any character” flag …","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Enable or disable the multi-line matching flag (m) by …","Create a new translator builder with a default c …","Create a new translator using the default configuration.","Enable or disable the “swap greed” flag (U) by default.","","","Translate the given abstract syntax tree (AST) into a high …","","","","","","","Enable or disable the Unicode flag (u) by default.","Four successive byte ranges.","One byte range.","Three successive byte ranges.","Two successive byte ranges.","A single inclusive range of UTF-8 bytes.","Utf8Sequence represents a sequence of byte ranges.","An iterator over ranges of matching UTF-8 byte sequences.","Returns the underlying sequence of byte ranges as a slice.","","","","","","","","","","","","","End of byte range (inclusive).","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Returns the number of byte ranges in this sequence.","Returns true if and only if a prefix of bytes matches this …","Returns true if and only if the given byte is in this …","Create a new iterator over UTF-8 byte ranges for the …","","","","Reverses the ranges in this sequence.","Start of byte range (inclusive).","","","","","","","","","","","","","","",""],"i":[0,4,0,0,0,4,0,1,0,4,1,3,9,4,1,3,9,1,1,4,1,3,4,1,3,1,4,1,4,0,0,4,4,1,3,9,9,4,4,4,1,3,9,0,1,4,1,3,9,0,0,0,1,1,1,3,1,3,4,9,1,4,1,3,4,9,4,1,3,9,4,1,3,9,0,4,1,3,9,1,0,97,98,41,41,0,31,41,47,0,31,0,0,57,35,46,41,57,37,47,58,27,0,58,35,62,0,31,0,0,0,27,0,0,27,27,0,0,0,0,0,0,27,0,0,0,41,44,0,0,31,27,27,51,39,41,31,62,31,47,53,53,44,64,0,0,27,27,27,27,27,57,0,61,27,27,27,27,27,0,31,0,0,35,41,0,31,0,27,27,27,27,27,27,34,34,0,62,51,46,35,0,31,47,0,41,62,43,43,61,27,58,44,53,34,43,56,64,37,47,0,41,41,34,47,56,0,31,27,27,27,0,27,0,0,0,35,39,41,0,34,0,53,53,62,51,35,37,47,62,27,36,36,47,27,27,41,34,35,0,0,39,41,53,36,41,56,56,20,29,54,26,32,33,10,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,25,25,26,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,24,28,28,30,29,10,36,31,46,24,48,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,64,20,26,60,10,10,27,27,24,28,29,30,31,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,41,54,59,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,32,33,49,26,24,31,44,42,61,24,48,57,49,20,10,25,38,40,42,45,50,52,55,26,21,50,28,59,38,40,42,45,24,28,28,54,0,24,28,10,0,10,49,50,10,31,37,46,47,30,32,33,25,38,40,42,45,48,49,50,52,54,55,26,59,60,20,21,24,64,64,24,48,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,31,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,46,0,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,24,24,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,124,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,139,0,0,65,66,65,66,65,65,66,65,66,65,65,66,65,66,65,65,66,65,65,66,65,66,66,65,66,65,66,65,66,65,66,0,67,67,67,67,67,67,67,67,67,67,75,0,75,79,79,84,84,76,71,81,81,0,0,75,0,0,0,0,0,0,75,75,74,70,70,88,0,0,84,0,75,0,0,0,74,0,75,81,83,88,83,0,75,0,0,70,70,76,71,79,74,79,74,74,74,74,0,0,75,83,83,15,15,15,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,71,72,73,15,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,77,78,15,77,78,11,72,73,15,15,15,72,73,77,78,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,88,11,11,74,74,15,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,82,15,75,80,82,72,73,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,85,86,15,72,73,15,15,15,71,15,15,15,15,75,15,15,15,15,82,79,76,72,73,11,15,80,82,0,15,71,72,73,72,77,73,78,85,86,77,78,11,0,11,87,72,73,72,73,15,11,88,88,77,78,72,73,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,11,74,15,87,0,72,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,72,73,0,88,88,88,88,88,88,15,140,141,142,143,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,157,0,0,89,89,89,89,89,90,89,90,89,90,89,89,90,89,90,90,89,89,89,89,90,90,90,89,90,89,90,89,90,89,90,89,90,90,89,89,89,89,89,89,89,90,90,89,89,89,89,89,89,89,90,89,89,90,89,90,89,90,89,89,89,89,89,0,91,91,91,91,91,91,91,91,91,91,0,0,92,92,93,92,93,92,92,92,93,92,93,92,92,92,93,92,93,92,93,92,92,93,92,92,93,93,92,93,92,93,92,93,92,94,94,94,94,0,0,0,94,94,95,96,94,95,96,94,95,94,95,94,95,95,94,95,94,95,96,94,95,96,94,95,96,94,96,94,94,95,96,96,94,95,94,95,94,95,94,95,96,94,95,96,94,95,96,158,159,160,161],"f":[0,0,0,0,0,0,0,[[1,2],1],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,3],[[1,2],1],[4,4],[1,1],[3,3],[[]],[[]],[[]],[[],1],[4,5],[[1,2],1],[[4,4],2],[5,6],[[5,6]],[[4,7],8],[[4,7],8],[[1,7],8],[[3,7],8],[[9,7],8],[[9,7],8],[10,4],[11,4],[[]],[[]],[[]],[[]],0,[[1,2],1],[[]],[[]],[[]],[[]],[12,2],[13,2],[12,2],[[1,2],1],[[1,14],1],[[],1],[[],3],[[1,2],1],[[3,5],[[16,[15]]]],[17],[17],[[1,2],1],[[]],[[]],[[]],[[],6],[[],6],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[12,[[18,[2,9]]]],[[],19],[[],19],[[],19],[[],19],[[1,2],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[20,21],[[23,[22]]]],0,0,0,0,0,[10,[[23,[24]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,[[23,[13]]]],0,[26,[[23,[14]]]],[10,10],[27,27],[24,24],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[25,25],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[26,26],[58,58],[59,59],[60,60],[20,20],[21,21],[61,61],[62,62],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[24,24],63],[[28,28],63],0,0,0,[10,5],[36,14],[31],[46],0,0,[[10,10],2],[[27,27],2],[[24,24],2],[[28,28],2],[[29,29],2],[[30,30],2],[[31,31],2],[[32,32],2],[[33,33],2],[[25,25],2],[[34,34],2],[[35,35],2],[[36,36],2],[[37,37],2],[[38,38],2],[[39,39],2],[[40,40],2],[[41,41],2],[[42,42],2],[[43,43],2],[[44,44],2],[[45,45],2],[[46,46],2],[[47,47],2],[[48,48],2],[[49,49],2],[[50,50],2],[[51,51],2],[[52,52],2],[[53,53],2],[[54,54],2],[[55,55],2],[[56,56],2],[[57,57],2],[[26,26],2],[[58,58],2],[[59,59],2],[[60,60],2],[[20,20],2],[[21,21],2],[[61,61],2],[[62,62],2],[[],18],[[20,62],[[23,[2]]]],[26,[[23,[20]]]],0,[[10,7],8],[[10,7],8],[[27,7],8],[[27,7],8],[[24,7],8],[[28,7],8],[[29,7],8],[[30,7],8],[[31,7],8],[[31,7],8],[[32,7],8],[[33,7],8],[[25,7],8],[[34,7],8],[[35,7],8],[[36,7],8],[[37,7],8],[[38,7],8],[[39,7],8],[[40,7],8],[[41,7],8],[[42,7],8],[[43,7],8],[[44,7],8],[[45,7],8],[[46,7],8],[[47,7],8],[[48,7],8],[[49,7],8],[[50,7],8],[[51,7],8],[[52,7],8],[[53,7],8],[[54,7],8],[[55,7],8],[[56,7],8],[[57,7],8],[[26,7],8],[[58,7],8],[[59,7],8],[[60,7],8],[[20,7],8],[[21,7],8],[[61,7],8],[[62,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,[[23,[41]]]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[32,31],[33,31],[49,47],[26,2],[24,2],[31,2],[44,2],[42,2],[61,2],[24,2],[48,2],[57,2],0,0,[10,27],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[28,28],24],[[22,22,22],28],0,0,0,[[24,24],[[23,[63]]]],[[28,28],[[23,[63]]]],[10,5],0,[17],[[49,47]],0,[10,24],[31,24],[37,24],[46,24],[47,24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[28,24],[[]],[[]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[49,46],[[31,64],18],[[],18],[[],18],[50,18],[50,18],[50,18],[50,18],[50,18],[50,18],[47,18],[47,18],[47,18],[47,18],[31,18],[31,18],[31,18],[31,18],[[24,28],24],[[24,28],24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[65,66],[65,65],[66,66],[[]],[[]],[[],65],[[65,7],8],[[66,7],8],[[]],[[]],[[65,2],65],[[]],[[]],[[65,14],65],[[],65],[[],66],[[65,2],65],[[66,5],[[18,[31,10]]]],[[66,5],[[18,[29,10]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,[[]],[[]],[[67,7],8],[[]],[[]],[[],67],[[67,31,68],8],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[69,[15]]],15],[70,15],[2,15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[71],[72],[73],[71,15],[11,11],[74,74],[15,15],[75,75],[76,76],[71,71],[72,72],[77,77],[73,73],[78,78],[70,70],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[77,77],63],[[78,78],63],[[[69,[15]]],15],[[],77],[[],78],[11,5],[[72,72]],[[73,73]],[2,15],[15],[[],15],[[],72],[[],73],[77,12],[78,13],[[11,11],2],[[74,74],2],[[15,15],2],[[75,75],2],[[76,76],2],[[71,71],2],[[72,72],2],[[77,77],2],[[73,73],2],[[78,78],2],[[70,70],2],[[79,79],2],[[80,80],2],[[81,81],2],[[82,82],2],[[83,83],2],[[84,84],2],[[],18],[[11,7],8],[[11,7],8],[[74,7],8],[[74,7],8],[[15,7],8],[[15,7],8],[[75,7],8],[[76,7],8],[[71,7],8],[[72,7],8],[[85,7],8],[[77,7],8],[[73,7],8],[[86,7],8],[[78,7],8],[[70,7],8],[[79,7],8],[[80,7],8],[[81,7],8],[[82,7],8],[[83,7],8],[[84,7],8],[[87,7],8],[[87,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[80,15],[75,2],0,0,[[72,72]],[[73,73]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[15,75],[72,2],[73,2],[15,2],[15,2],[15,2],[71,2],[15,2],[15,2],[15,2],[15,2],[75,2],[15,2],[15,2],[15,2],[15,2],[82,2],[79,2],[76,2],[72,85],[73,86],[11,74],[15,75],0,0,0,[76,15],[71],[72],[73],[[],72],[[12,12],77],[[],73],[[13,13],78],[85,[[23,[77]]]],[86,[[23,[78]]]],[[77,77],[[23,[63]]]],[[78,78],[[23,[63]]]],[11,5],0,[17],[17],[[72,77]],[[73,78]],[72],[73],[82,15],[11,24],[[]],[[]],[77,12],[78,13],[[72,72]],[[73,73]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],0,[72,[[18,[87]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[72,72]],[[73,73]],[[15,88],18],[[],18],[[],18],[15,18],[15,18],[15,18],[15,18],[79,15],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[89,90],2],[[89,73],2],[[89,72],2],[89,2],[89,2],[90],[[]],[[]],[[]],[[]],[89],[89,89],[90,90],[[]],[[]],[[90,90],63],[89,2],[89,2],[[89,89],2],[89],[90],[90,69],[90,69],[[],89],[[],90],[[89,89],2],[[90,90],2],[[89,7],8],[[90,7],8],[[]],[[]],[[]],[[]],[90,2],[89,2],[89,22],[89,22],[89],[89],[89],[89,[[23,[22]]]],[[[69,[13]]],90],[[90,90],[[23,[63]]]],[15,89],[89],[[89,22],89],[[89,22],89],[15,89],[89,89],[[]],[[]],[[89,22],[[23,[89]]]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[89,89],[89,89],[[89,89],2],[[89,15],2],[[89,15],2],0,[[]],[[]],[[91,7],8],[[]],[[]],[[],91],[[91,15,68],8],[[],18],[[],18],[[],19],0,0,[[92,2],92],[[]],[[]],[[]],[[]],[92,93],[[92,2],92],[92,92],[93,93],[[]],[[]],[[],92],[[92,2],92],[[92,7],8],[[93,7],8],[[]],[[]],[[]],[[]],[[92,2],92],[[],92],[[],93],[[92,2],92],[[]],[[]],[[93,5,31],[[18,[15,11]]]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[92,2],92],0,0,0,0,0,0,0,[94],[[]],[[]],[[]],[[]],[[]],[[]],[94,94],[95,95],[[]],[[]],[[94,94],63],[[95,95],63],0,[[94,94],2],[[95,95],2],[[94,7],8],[[95,7],8],[[96,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[94],[[]],[94,22],[94,2],[[95,13],2],[[12,12],96],[96,23],[[94,94],[[23,[63]]]],[[95,95],[[23,[63]]]],[94],0,[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],0,0,0,0],"p":[[3,"ParserBuilder"],[15,"bool"],[3,"Parser"],[4,"Error"],[15,"str"],[3,"String"],[3,"Formatter"],[6,"Result"],[3,"UnicodeWordError"],[3,"Error"],[3,"Error"],[15,"char"],[15,"u8"],[15,"u32"],[3,"Hir"],[6,"Result"],[3,"Demand"],[4,"Result"],[3,"TypeId"],[3,"Flags"],[3,"FlagsItem"],[15,"usize"],[4,"Option"],[3,"Span"],[3,"Literal"],[3,"Group"],[4,"ErrorKind"],[3,"Position"],[3,"WithComments"],[3,"Comment"],[4,"Ast"],[3,"Alternation"],[3,"Concat"],[4,"LiteralKind"],[4,"SpecialLiteralKind"],[4,"HexLiteralKind"],[4,"Class"],[3,"ClassPerl"],[4,"ClassPerlKind"],[3,"ClassAscii"],[4,"ClassAsciiKind"],[3,"ClassUnicode"],[4,"ClassUnicodeKind"],[4,"ClassUnicodeOpKind"],[3,"ClassBracketed"],[4,"ClassSet"],[4,"ClassSetItem"],[3,"ClassSetRange"],[3,"ClassSetUnion"],[3,"ClassSetBinaryOp"],[4,"ClassSetBinaryOpKind"],[3,"Assertion"],[4,"AssertionKind"],[3,"Repetition"],[3,"RepetitionOp"],[4,"RepetitionKind"],[4,"RepetitionRange"],[4,"GroupKind"],[3,"CaptureName"],[3,"SetFlags"],[4,"FlagsItemKind"],[4,"Flag"],[4,"Ordering"],[8,"Visitor"],[3,"ParserBuilder"],[3,"Parser"],[3,"Printer"],[8,"Write"],[3,"Vec"],[4,"Anchor"],[4,"Class"],[3,"ClassUnicode"],[3,"ClassBytes"],[4,"ErrorKind"],[4,"HirKind"],[4,"Literal"],[3,"ClassUnicodeRange"],[3,"ClassBytesRange"],[4,"WordBoundary"],[3,"Group"],[4,"GroupKind"],[3,"Repetition"],[4,"RepetitionKind"],[4,"RepetitionRange"],[3,"ClassUnicodeIter"],[3,"ClassBytesIter"],[3,"CaseFoldError"],[8,"Visitor"],[3,"Literals"],[3,"Literal"],[3,"Printer"],[3,"TranslatorBuilder"],[3,"Translator"],[4,"Utf8Sequence"],[3,"Utf8Range"],[3,"Utf8Sequences"],[13,"Parse"],[13,"Translate"],[13,"Empty"],[13,"Flags"],[13,"Literal"],[13,"Dot"],[13,"Assertion"],[13,"Class"],[13,"Repetition"],[13,"Group"],[13,"Alternation"],[13,"Concat"],[13,"Unicode"],[13,"Perl"],[13,"Bracketed"],[13,"Item"],[13,"BinaryOp"],[13,"Empty"],[13,"Literal"],[13,"Range"],[13,"Ascii"],[13,"Unicode"],[13,"Perl"],[13,"Bracketed"],[13,"Union"],[13,"OneLetter"],[13,"Named"],[13,"NamedValue"],[13,"NestLimitExceeded"],[13,"FlagDuplicate"],[13,"FlagRepeatedNegation"],[13,"GroupNameDuplicate"],[13,"Flag"],[13,"CaptureIndex"],[13,"CaptureName"],[13,"NonCapturing"],[13,"HexFixed"],[13,"HexBrace"],[13,"Special"],[13,"Range"],[13,"Exactly"],[13,"AtLeast"],[13,"Bounded"],[13,"Unicode"],[13,"Bytes"],[13,"CaptureIndex"],[13,"CaptureName"],[13,"Literal"],[13,"Class"],[13,"Anchor"],[13,"WordBoundary"],[13,"Repetition"],[13,"Group"],[13,"Concat"],[13,"Alternation"],[13,"Unicode"],[13,"Byte"],[13,"Range"],[13,"Exactly"],[13,"AtLeast"],[13,"Bounded"],[13,"One"],[13,"Two"],[13,"Three"],[13,"Four"]]},\ +"rlp":{"doc":"Recursive Length Prefix serialization crate.","t":[13,13,8,4,17,8,13,17,13,3,4,3,13,13,13,13,13,13,13,13,13,3,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,10,5,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12],"n":["Custom","Data","Decodable","DecoderError","EMPTY_LIST_RLP","Encodable","List","NULL_RLP","Null","PayloadInfo","Prototype","Rlp","RlpDataLenWithZeroPrefix","RlpExpectedToBeData","RlpExpectedToBeList","RlpInconsistentLengthAndData","RlpIncorrectListLen","RlpInvalidIndirection","RlpInvalidLength","RlpIsTooBig","RlpIsTooShort","RlpIterator","RlpListLenWithZeroPrefix","RlpStream","append","append_empty_data","append_internal","append_iter","append_list","append_raw","append_raw_checked","as_list","as_raw","as_raw","as_val","at","at_with_offset","begin_list","begin_unbounded_list","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone_into","clone_into","data","decode","decode","decode_list","decoder","default","description","encode","encode_list","encoder","eq","estimate_size","finalize_unbounded_list","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","header_len","into","into","into","into","into","into","into_iter","into_iter","is_data","is_empty","is_empty","is_finished","is_int","is_list","is_null","item_count","iter","len","len","list_at","new","new","new_list","new_list_with_buffer","new_with_buffer","next","out","payload_info","prototype","provide","rlp_append","rlp_bytes","rlp_bytes","size","to_owned","to_owned","to_string","to_string","total","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","val_at","value_len","0","0","0"],"q":["rlp","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rlp::DecoderError","rlp::Prototype",""],"d":["Custom rlp decoding error.","Value","RLP decodable trait","Error concerning the RLP decoder.","The RLP encoded empty list.","Structure encodable to RLP","List","The RLP encoded empty data (used to mean “null value”).","Empty","Stores basic information about item","RLP prototype","Data-oriented view onto rlp-slice.","Data length number has a prefixed zero byte, invalid for …","Expect encoded data, RLP was something else.","Expect an encoded list, RLP was something else.","Declared length is inconsistent with data specified after.","Expected a different size list.","Non-canonical (longer than necessary) representation used …","Declared length is invalid and results in overflow","Data has additional bytes at the end of the valid RLP …","Data has too few bytes for valid RLP.","Iterator over rlp-slice list elements.","List length number has a prefixed zero byte, invalid for …","Appendable rlp encoder.","Appends value to the end of stream, chainable.","Apends null to the end of stream, chainable.","Appends value to the end of stream, but do not count it as …","Appends iterator to the end of stream, chainable.","Appends list of values to the end of stream, chainable.","Appends raw (pre-serialised) RLP data. Use with caution. …","Appends raw (pre-serialised) RLP data. Checks for size …","","Get raw encoded bytes","","","Returns an Rlp item in a list at the given index.","Returns an Rlp item in a list at the given index along …","Declare appending the list of given size, chainable.","Declare appending the list of unknown size, chainable.","","","","","","","","","","","","","Clear the output stream so far.","","","","","","Shortcut function to decode trusted rlp","Decode a value from RLP bytes","","","","","Shortcut function to encode structure into rlp.","","","","Calculate total RLP size for appended payload.","Finalize current unbounded list. Panics if no unbounded …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create a new object from the given bytes RLP. The bytes","Returns the argument unchanged.","Returns the argument unchanged.","Header length in bytes","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Returns true if stream doesnt expect any more items.","","","","","","","Returns current RLP size in bytes for the data pushed into …","","Initializes instance of empty Stream.","","Initializes the Stream as a list.","Initializes the Stream as a list.","Initializes instance of empty Stream.","","Streams out encoded bytes.","","","","Append a value to the stream","Get rlp-encoded bytes for this instance","Get rlp-encoded bytes for this instance","","","","","","Total size of the RLP.","","","","","","","","","","","","","","","","","","","","Value length in bytes","","",""],"i":[6,12,0,0,0,0,12,0,12,0,0,0,6,6,6,6,6,6,6,6,6,0,6,0,1,1,1,1,1,1,1,4,1,4,4,4,4,1,1,15,1,6,12,13,4,15,1,6,12,13,4,1,6,4,6,4,4,0,20,0,4,1,6,0,0,1,6,1,1,6,6,12,13,4,4,15,1,6,12,13,13,4,13,15,1,6,12,13,4,15,4,4,1,4,1,4,4,4,4,4,15,1,4,1,4,1,1,1,15,1,4,4,6,21,21,21,4,6,4,6,4,13,15,1,6,12,13,4,15,1,6,12,13,4,15,1,6,12,13,4,4,13,22,23,24],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,1],[1,1],[1,1],[1,1],[1,1],[[1,2],1],[[1,2,2],3],[4,[[7,[5,6]]]],[1],[4],[4,[[7,[6]]]],[[4,2],[[7,[4,6]]]],[[4,2],[[7,[6]]]],[[1,2],1],[1,1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[6,6],[4,4],[[]],[[]],[4,[[7,[6]]]],[[],[[7,[6]]]],[4,[[7,[6]]]],[[],5],0,[[],1],[6,8],[[],9],[[],9],0,[[6,6],3],[[1,2],2],[1],[[6,10],11],[[6,10],11],[[12,10],11],[[13,10],11],[[4,10],11],[[4,10],[[7,[14]]]],[[]],[[]],[[]],[[]],[[],[[7,[13,6]]]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4],[4,3],[1,3],[4,3],[1,3],[4,3],[4,3],[4,3],[4,[[7,[2,6]]]],[4,15],[15,2],[1,2],[[4,2],[[7,[5,6]]]],[[],1],[[],4],[2,1],[[9,2],1],[9,1],[15,[[16,[4]]]],[1,9],[4,[[7,[13,6]]]],[4,[[7,[12,6]]]],[17],[1],[[],9],[[],9],[4,2],[[]],[[]],[[],18],[[],18],[13,2],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[4,2],[[7,[6]]]],0,0,0,0],"p":[[3,"RlpStream"],[15,"usize"],[15,"bool"],[3,"Rlp"],[3,"Vec"],[4,"DecoderError"],[4,"Result"],[15,"str"],[3,"BytesMut"],[3,"Formatter"],[6,"Result"],[4,"Prototype"],[3,"PayloadInfo"],[3,"Error"],[3,"RlpIterator"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"Decodable"],[8,"Encodable"],[13,"Custom"],[13,"Data"],[13,"List"]]},\ +"rustc_hex":{"doc":"Hex binary-to-text encoding","t":[8,4,3,13,13,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["FromHex","FromHexError","FromHexIter","InvalidHexCharacter","InvalidHexLength","ToHex","ToHexIter","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","description","fmt","fmt","from","from","from","from_hex","into","into","into","into_iter","into_iter","len","new","new","next","next","provide","size_hint","size_hint","to_hex","to_owned","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","1"],"q":["rustc_hex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rustc_hex::FromHexError",""],"d":["A from-hex conversion trait.","Errors that can occur when decoding a hex encoded string","An iterator decoding hex-encoded characters into bytes.","The input contained a character not part of the hex format","The input had an invalid length","A trait for converting a value to hexadecimal encoding","An iterator converting byte slice to a set of hex …","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts the value of self, interpreted as hexadecimal …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Create new hex-converting iterator.","Create new hex-decoding iterator.","","","","","","Converts the value of self to a hex value, constructed from","","","","","","","","","","","","",""],"i":[0,0,0,1,1,0,0,10,12,1,10,12,1,1,1,1,1,1,10,12,1,18,10,12,1,10,12,10,10,12,10,12,1,10,12,19,1,1,10,12,1,10,12,1,10,12,1,20,20],"f":[0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[[]],[1,2],[[1,3],4],[[1,3],4],[[]],[[]],[[]],[[],[[7,[[6,[5]],1]]]],[[]],[[]],[[]],[[]],[[]],[[[10,[[0,[8,9]]]]],11],[[],10],[2,12],[[[10,[9]]],[[14,[13]]]],[12,[[14,[[7,[5,1]]]]]],[15],[[[10,[9]]]],[12],[[],[[6,[13]]]],[[]],[[],16],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],17],[[],17],[[],17],0,0],"p":[[4,"FromHexError"],[15,"str"],[3,"Formatter"],[6,"Result"],[15,"u8"],[8,"FromIterator"],[4,"Result"],[8,"ExactSizeIterator"],[8,"Iterator"],[3,"ToHexIter"],[15,"usize"],[3,"FromHexIter"],[15,"char"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"FromHex"],[8,"ToHex"],[13,"InvalidHexCharacter"]]},\ +"scan_fmt":{"doc":"This crate provides a simple sscanf()-like interface to …","t":[5,0,14,14,14,14,14,14,14,12,3,11,11,11,11,11,11,11,11,5,11,11,11,11],"n":["get_input_unwrap","parse","scan_fmt","scan_fmt_help","scan_fmt_some","scanln_fmt","scanln_fmt","scanln_fmt_some","scanln_fmt_some","0","ScanError","borrow","borrow_mut","eq","fmt","fmt","from","into","provide","scan","to_string","try_from","try_into","type_id"],"q":["scan_fmt","","","","","","","","","scan_fmt::parse","","","","","","","","","","","","","",""],"d":["","","","","","(a,+) = scanln_fmt!( format_string, types,+ )","(a,+) = scanln_fmt!( format_string, types,+ )","(a,+) = scanln_fmt_some!( format_string, types,+ )","(a,+) = scanln_fmt_some!( format_string, types,+ )","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,2,0,2,2,2,2,2,2,2,2,0,2,2,2,2],"f":[[[],1],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[2,2],3],[[2,4],5],[[2,4],5],[[]],[[]],[6],[[7,7],[[8,[1]]]],[[],1],[[],9],[[],9],[[],10]],"p":[[3,"String"],[3,"ScanError"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[15,"str"],[3,"IntoIter"],[4,"Result"],[3,"TypeId"]]},\ +"scopeguard":{"doc":"A scope guard will run a given closure when it goes out of …","t":[4,3,8,11,11,11,11,14,11,11,11,11,11,11,11,5,11,11,11,10,11,11,11,11,11,11,11,11],"n":["Always","ScopeGuard","Strategy","borrow","borrow","borrow_mut","borrow_mut","defer","deref","deref_mut","drop","fmt","fmt","from","from","guard","into","into","into_inner","should_run","should_run","try_from","try_from","try_into","try_into","type_id","type_id","with_strategy"],"q":["scopeguard","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Always run on scope exit.","ScopeGuard is a scope guard that may own a protected value.","Controls in which cases the associated code should be run","","","","","Macro to create a ScopeGuard (always run).","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Create a new ScopeGuard owning v and with deferred closure …","Calls U::from(self).","Calls U::from(self).","“Defuse” the guard and extract the value without …","Return true if the guard’s associated code should run …","","","","","","","","Create a ScopeGuard that owns v (accessible through deref) …"],"i":[0,0,0,1,4,1,4,0,1,1,1,1,4,1,4,0,1,4,1,8,4,1,4,1,4,1,4,1],"f":[0,0,0,[[]],[[]],[[]],[[]],0,[1],[1],[1],[[1,2],3],[[4,2],3],[[]],[[]],[[],[[1,[4]]]],[[]],[[]],[1],[[],5],[[],5],[[],6],[[],6],[[],6],[[],6],[[],7],[[],7],[[],1]],"p":[[3,"ScopeGuard"],[3,"Formatter"],[6,"Result"],[4,"Always"],[15,"bool"],[4,"Result"],[3,"TypeId"],[8,"Strategy"]]},\ +"serde":{"doc":"Serde","t":[8,8,16,16,16,8,16,16,16,16,16,16,16,8,11,11,11,0,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,14,11,11,0,14,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,13,13,13,8,8,8,8,16,13,8,8,16,16,16,16,16,8,13,3,8,13,8,13,13,13,13,13,8,13,2,13,13,13,4,13,13,13,16,16,16,8,8,11,11,11,11,11,11,11,11,10,11,10,10,11,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,10,11,10,11,11,11,10,11,10,11,11,10,11,11,11,11,11,11,11,10,11,11,10,11,11,0,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,16,16,16,16,16,16,16,16,3,16,16,16,16,16,16,16,16,8,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,2,11,11,11,11,11,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,11,11,11,10,10,10,10,10,10,11,11,11,11,11,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,11,11,11,11,11,11],"n":["Deserialize","Deserializer","Error","Error","Ok","Serialize","SerializeMap","SerializeSeq","SerializeStruct","SerializeStructVariant","SerializeTuple","SerializeTupleStruct","SerializeTupleVariant","Serializer","collect_map","collect_seq","collect_str","de","deserialize","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","forward_to_deserialize_any","is_human_readable","is_human_readable","ser","serde_if_integer128","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_f32","serialize_f64","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","Bool","Bytes","Char","Deserialize","DeserializeOwned","DeserializeSeed","Deserializer","Deserializer","Enum","EnumAccess","Error","Error","Error","Error","Error","Error","Expected","Float","IgnoredAny","IntoDeserializer","Map","MapAccess","NewtypeStruct","NewtypeVariant","Option","Other","Seq","SeqAccess","Signed","StdError","Str","StructVariant","TupleVariant","Unexpected","Unit","UnitVariant","Unsigned","Value","Value","Variant","VariantAccess","Visitor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","custom","default","deserialize","deserialize","deserialize","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","duplicate_field","eq","expecting","expecting","fmt","fmt","fmt","fmt","fmt","fmt","from","from","into","into","into_deserializer","invalid_length","invalid_type","invalid_value","is_human_readable","missing_field","newtype_variant","newtype_variant_seed","next_element","next_element_seed","next_entry","next_entry_seed","next_key","next_key_seed","next_value","next_value_seed","size_hint","size_hint","struct_variant","to_owned","to_owned","to_string","try_from","try_from","try_into","try_into","tuple_variant","type_id","type_id","unit_variant","unknown_field","unknown_variant","value","variant","variant_seed","visit_bool","visit_bool","visit_borrowed_bytes","visit_borrowed_str","visit_byte_buf","visit_bytes","visit_bytes","visit_char","visit_enum","visit_enum","visit_f32","visit_f64","visit_f64","visit_i128","visit_i128","visit_i16","visit_i32","visit_i64","visit_i64","visit_i8","visit_map","visit_map","visit_newtype_struct","visit_newtype_struct","visit_none","visit_none","visit_seq","visit_seq","visit_some","visit_some","visit_str","visit_str","visit_string","visit_u128","visit_u128","visit_u16","visit_u32","visit_u64","visit_u64","visit_u8","visit_unit","visit_unit","0","0","0","0","0","0","0","0","BoolDeserializer","BorrowedBytesDeserializer","BorrowedStrDeserializer","BytesDeserializer","CharDeserializer","CowStrDeserializer","EnumAccessDeserializer","Error","F32Deserializer","F64Deserializer","I128Deserializer","I16Deserializer","I32Deserializer","I64Deserializer","I8Deserializer","IsizeDeserializer","MapAccessDeserializer","MapDeserializer","SeqAccessDeserializer","SeqDeserializer","StrDeserializer","StringDeserializer","U128Deserializer","U16Deserializer","U32Deserializer","U64Deserializer","U8Deserializer","UnitDeserializer","UsizeDeserializer","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","custom","custom","description","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","end","end","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","next_element_seed","next_element_seed","next_entry_seed","next_key_seed","next_value_seed","provide","size_hint","size_hint","size_hint","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","variant_seed","variant_seed","variant_seed","variant_seed","variant_seed","variant_seed","Error","Error","Error","Error","Error","Error","Error","Error","Error","Impossible","Ok","Ok","Ok","Ok","Ok","Ok","Ok","Ok","Serialize","SerializeMap","SerializeMap","SerializeSeq","SerializeSeq","SerializeStruct","SerializeStruct","SerializeStructVariant","SerializeStructVariant","SerializeTuple","SerializeTuple","SerializeTupleStruct","SerializeTupleStruct","SerializeTupleVariant","SerializeTupleVariant","Serializer","StdError","borrow","borrow_mut","collect_map","collect_seq","collect_str","custom","end","end","end","end","end","end","end","end","end","end","end","end","end","end","from","into","is_human_readable","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_element","serialize_element","serialize_element","serialize_element","serialize_entry","serialize_f32","serialize_f64","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_key","serialize_key","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","serialize_value","serialize_value","skip_field","skip_field","try_from","try_into","type_id"],"q":["serde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::de","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::de::Unexpected","","","","","","","","serde::de::value","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::ser","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A data structure that can be deserialized from any data …","A data format that can deserialize any data structure …","The error type that can be returned if some error occurs …","The error type when some error occurs during serialization.","The output type produced by this Serializer during …","A data structure that can be serialized into any data …","Type returned from serialize_map for serializing the …","Type returned from serialize_seq for serializing the …","Type returned from serialize_struct for serializing the …","Type returned from serialize_struct_variant for …","Type returned from serialize_tuple for serializing the …","Type returned from serialize_tuple_struct for serializing …","Type returned from serialize_tuple_variant for serializing …","A data format that can serialize any data structure …","Collect an iterator as a map.","Collect an iterator as a sequence.","Serialize a string produced by an implementation of Display…","Generic data structure deserialization framework.","Deserialize this value from the given Serde deserializer.","Require the Deserializer to figure out how to drive the …","Hint that the Deserialize type is expecting a bool value.","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a char value.","Hint that the Deserialize type is expecting an enum value …","Hint that the Deserialize type is expecting a f32 value.","Hint that the Deserialize type is expecting a f64 value.","Hint that the Deserialize type is expecting an i128 value.","Hint that the Deserialize type is expecting an i16 value.","Hint that the Deserialize type is expecting an i32 value.","Hint that the Deserialize type is expecting an i64 value.","Hint that the Deserialize type is expecting an i8 value.","Hint that the Deserialize type is expecting the name of a …","Hint that the Deserialize type needs to deserialize a …","Hint that the Deserialize type is expecting a map of …","Hint that the Deserialize type is expecting a newtype …","Hint that the Deserialize type is expecting an optional …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a struct with …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a tuple struct …","Hint that the Deserialize type is expecting an u128 value.","Hint that the Deserialize type is expecting a u16 value.","Hint that the Deserialize type is expecting a u32 value.","Hint that the Deserialize type is expecting a u64 value.","Hint that the Deserialize type is expecting a u8 value.","Hint that the Deserialize type is expecting a unit value.","Hint that the Deserialize type is expecting a unit struct …","Helper macro when implementing the Deserializer part of a …","Determine whether Deserialize implementations should …","Determine whether Serialize implementations should …","Generic data structure serialization framework.","Conditional compilation depending on whether Serde is …","Serialize this value into the given Serde serializer.","Serialize a bool value.","Serialize a chunk of raw byte data.","Serialize a character.","Serialize an f32 value.","Serialize an f64 value.","Serialize an i128 value.","Serialize an i16 value.","Serialize an i32 value.","Serialize an i64 value.","Serialize an i8 value.","Begin to serialize a map. This call must be followed by …","Serialize a newtype struct like struct Millimeters(u8).","Serialize a newtype variant like E::N in enum E { N(u8) }.","Serialize a None value.","Begin to serialize a variably sized sequence. This call …","Serialize a Some(T) value.","Serialize a &str.","Begin to serialize a struct like …","Begin to serialize a struct variant like E::S in …","Begin to serialize a statically sized sequence whose …","Begin to serialize a tuple struct like …","Begin to serialize a tuple variant like E::T in …","Serialize a u128 value.","Serialize a u16 value.","Serialize a u32 value.","Serialize a u64 value.","Serialize a u8 value.","Serialize a () value.","Serialize a unit struct like struct Unit or PhantomData<T>.","Serialize a unit variant like E::A in enum E { A, B }.","The input contained a boolean value that was not expected.","The input contained a &[u8] or Vec<u8> that was not …","The input contained a char that was not expected.","A data structure that can be deserialized from any data …","A data structure that can be deserialized without …","DeserializeSeed is the stateful form of the Deserialize …","A data format that can deserialize any data structure …","The type of the deserializer being converted into.","The input contained an enum that was not expected.","Provides a Visitor access to the data of an enum in the …","The Error trait allows Deserialize implementations to …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","Expected represents an explanation of what data a Visitor …","The input contained a floating point f32 or f64 that was …","An efficient way of discarding data from a deserializer.","Converts an existing value into a Deserializer from which …","The input contained a map that was not expected.","Provides a Visitor access to each entry of a map in the …","The input contained a newtype struct that was not expected.","The input contained a newtype variant that was not …","The input contained an Option<T> that was not expected.","A message stating what uncategorized thing the input …","The input contained a sequence that was not expected.","Provides a Visitor access to each element of a sequence in …","The input contained a signed integer i8, i16, i32 or i64 …","","The input contained a &str or String that was not expected.","The input contained a struct variant that was not expected.","The input contained a tuple variant that was not expected.","Unexpected represents an unexpected invocation of any one …","The input contained a unit () that was not expected.","The input contained a unit variant that was not expected.","The input contained an unsigned integer u8, u16, u32 or u64…","The type produced by using this seed.","The value produced by this visitor.","The Visitor that will be used to deserialize the content …","VariantAccess is a visitor that is created by the …","This trait represents a visitor that walks through a …","","","","","","","","","Raised when there is general error when deserializing a …","","Deserialize this value from the given Serde deserializer.","Equivalent to the more common Deserialize::deserialize …","","Require the Deserializer to figure out how to drive the …","Hint that the Deserialize type is expecting a bool value.","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a char value.","Hint that the Deserialize type is expecting an enum value …","Hint that the Deserialize type is expecting a f32 value.","Hint that the Deserialize type is expecting a f64 value.","Hint that the Deserialize type is expecting an i128 value.","Hint that the Deserialize type is expecting an i16 value.","Hint that the Deserialize type is expecting an i32 value.","Hint that the Deserialize type is expecting an i64 value.","Hint that the Deserialize type is expecting an i8 value.","Hint that the Deserialize type is expecting the name of a …","Hint that the Deserialize type needs to deserialize a …","Hint that the Deserialize type is expecting a map of …","Hint that the Deserialize type is expecting a newtype …","Hint that the Deserialize type is expecting an optional …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a struct with …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a tuple struct …","Hint that the Deserialize type is expecting an u128 value.","Hint that the Deserialize type is expecting a u16 value.","Hint that the Deserialize type is expecting a u32 value.","Hint that the Deserialize type is expecting a u64 value.","Hint that the Deserialize type is expecting a u8 value.","Hint that the Deserialize type is expecting a unit value.","Hint that the Deserialize type is expecting a unit struct …","Raised when a Deserialize struct type received more than …","","Format a message stating what data this Visitor expects to …","","Format an explanation of what data was being expected. …","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Convert this value into a deserializer.","Raised when deserializing a sequence or map and the input …","Raised when a Deserialize receives a type different from …","Raised when a Deserialize receives a value of the right …","Determine whether Deserialize implementations should …","Raised when a Deserialize struct type expected to receive …","Called when deserializing a variant with a single value.","Called when deserializing a variant with a single value.","This returns Ok(Some(value)) for the next value in the …","This returns Ok(Some(value)) for the next value in the …","This returns Ok(Some((key, value))) for the next …","This returns Ok(Some((key, value))) for the next …","This returns Ok(Some(key)) for the next key in the map, or …","This returns Ok(Some(key)) for the next key in the map, or …","This returns a Ok(value) for the next value in the map.","This returns a Ok(value) for the next value in the map.","Returns the number of elements remaining in the sequence, …","Returns the number of entries remaining in the map, if …","Called when deserializing a struct-like variant.","","","","","","","","Called when deserializing a tuple-like variant.","","","Called when deserializing a variant with no values.","Raised when a Deserialize struct type received a field …","Raised when a Deserialize enum type received a variant …","Building blocks for deserializing basic values using the …","variant is called to identify which variant to deserialize.","variant is called to identify which variant to deserialize.","The input contains a boolean.","","The input contains a byte array that lives at least as …","The input contains a string that lives at least as long as …","The input contains a byte array and ownership of the byte …","The input contains a byte array. The lifetime of the byte …","","The input contains a char.","The input contains an enum.","","The input contains an f32.","The input contains an f64.","","The input contains a i128.","","The input contains an i16.","The input contains an i32.","The input contains an i64.","","The input contains an i8.","The input contains a key-value map.","","The input contains a newtype struct.","","The input contains an optional that is absent.","","The input contains a sequence of elements.","","The input contains an optional that is present.","","The input contains a string. The lifetime of the string is …","","The input contains a string and ownership of the string is …","The input contains a u128.","","The input contains a u16.","The input contains a u32.","The input contains a u64.","","The input contains a u8.","The input contains a unit ().","","","","","","","","","","A deserializer holding a bool.","A deserializer holding a &[u8] with a lifetime tied to …","A deserializer holding a &str with a lifetime tied to …","A deserializer holding a &[u8]. Always calls …","A deserializer holding a char.","A deserializer holding a Cow<str>.","A deserializer holding an EnumAccess.","A minimal representation of all possible errors that can …","A deserializer holding an f32.","A deserializer holding an f64.","A deserializer holding an i128.","A deserializer holding an i16.","A deserializer holding an i32.","A deserializer holding an i64.","A deserializer holding an i8.","A deserializer holding an isize.","A deserializer holding a MapAccess.","A deserializer that iterates over a map.","A deserializer holding a SeqAccess.","A deserializer that iterates over a sequence.","A deserializer holding a &str.","A deserializer holding a String.","A deserializer holding a u128.","A deserializer holding a u16.","A deserializer holding a u32.","A deserializer holding a u64.","A deserializer holding a u8.","A deserializer holding a ().","A deserializer holding a usize.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check for remaining elements after passing a …","Check for remaining elements after passing a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Create a new borrowed deserializer from the given string.","","","Create a new deserializer from the given bytes.","Create a new borrowed deserializer from the given borrowed …","Construct a new MapDeserializer<I, E>.","","","","","","","","","","","","","","","","Construct a new SeqDeserializer<I, E>.","Construct a new SeqAccessDeserializer<A>.","Construct a new MapAccessDeserializer<A>.","Construct a new EnumAccessDeserializer<A>.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Trait used by Serialize implementations to generically …","The error type when some error occurs during serialization.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Helper type for implementing a Serializer that does not …","The output type produced by this Serializer during …","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","A data structure that can be serialized into any data …","Returned from Serializer::serialize_map.","Type returned from serialize_map for serializing the …","Returned from Serializer::serialize_seq.","Type returned from serialize_seq for serializing the …","Returned from Serializer::serialize_struct.","Type returned from serialize_struct for serializing the …","Returned from Serializer::serialize_struct_variant.","Type returned from serialize_struct_variant for …","Returned from Serializer::serialize_tuple.","Type returned from serialize_tuple for serializing the …","Returned from Serializer::serialize_tuple_struct.","Type returned from serialize_tuple_struct for serializing …","Returned from Serializer::serialize_tuple_variant.","Type returned from serialize_tuple_variant for serializing …","A data format that can serialize any data structure …","","","","Collect an iterator as a map.","Collect an iterator as a sequence.","Serialize a string produced by an implementation of Display…","Used when a Serialize implementation encounters any error …","Finish serializing a sequence.","Finish serializing a tuple.","Finish serializing a tuple struct.","Finish serializing a tuple variant.","Finish serializing a map.","Finish serializing a struct.","Finish serializing a struct variant.","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Determine whether Serialize implementations should …","Serialize this value into the given Serde serializer.","Serialize a bool value.","Serialize a chunk of raw byte data.","Serialize a character.","Serialize a sequence element.","Serialize a tuple element.","","","Serialize a map entry consisting of a key and a value.","Serialize an f32 value.","Serialize an f64 value.","Serialize a tuple struct field.","Serialize a tuple variant field.","Serialize a struct field.","Serialize a struct variant field.","","","","","Serialize an i128 value.","Serialize an i16 value.","Serialize an i32 value.","Serialize an i64 value.","Serialize an i8 value.","Serialize a map key.","","Begin to serialize a map. This call must be followed by …","Serialize a newtype struct like struct Millimeters(u8).","Serialize a newtype variant like E::N in enum E { N(u8) }.","Serialize a None value.","Begin to serialize a variably sized sequence. This call …","Serialize a Some(T) value.","Serialize a &str.","Begin to serialize a struct like …","Begin to serialize a struct variant like E::S in …","Begin to serialize a statically sized sequence whose …","Begin to serialize a tuple struct like …","Begin to serialize a tuple variant like E::T in …","Serialize a u128 value.","Serialize a u16 value.","Serialize a u32 value.","Serialize a u64 value.","Serialize a u8 value.","Serialize a () value.","Serialize a unit struct like struct Unit or PhantomData<T>.","Serialize a unit variant like E::A in enum E { A, B }.","Serialize a map value.","","Indicate that a struct field has been skipped.","Indicate that a struct variant field has been skipped.","","",""],"i":[0,0,63,64,64,0,64,64,64,64,64,64,64,0,64,64,64,0,65,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,0,63,64,0,0,66,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,20,20,20,0,0,0,0,67,20,0,0,63,68,69,70,71,0,20,0,0,20,0,20,20,20,20,20,0,20,0,20,20,20,0,20,20,20,72,73,70,0,0,19,20,19,20,19,20,19,20,74,19,65,72,19,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,74,20,73,19,23,23,19,19,20,20,19,20,19,20,67,74,74,74,63,74,71,71,68,68,69,69,69,69,69,69,68,69,71,19,20,20,19,20,19,20,71,19,20,71,74,74,0,70,70,73,19,73,73,73,73,19,73,73,19,73,73,19,73,19,73,73,73,19,73,73,19,73,19,73,19,73,19,73,19,73,19,73,73,19,73,73,73,19,73,73,19,75,76,77,78,79,80,81,82,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,37,37,37,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,36,54,37,28,29,30,31,32,33,34,35,36,37,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,36,54,36,36,36,37,36,36,54,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,37,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,29,30,31,32,33,56,0,64,83,84,85,86,87,88,89,0,64,83,84,85,86,87,88,89,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,62,62,64,64,64,90,83,84,85,86,87,88,89,62,62,62,62,62,62,62,62,62,64,66,64,64,64,83,84,62,62,87,64,64,85,86,88,89,62,62,62,62,64,64,64,64,64,87,62,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,87,62,88,89,62,62,62],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[],1],[[],1],0,[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[2,1],[3,1],[[2,3],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],0,[[],4],[[],4],0,0,[[],1],[4,1],[[],1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1],[12,1],[[[13,[3]]],1],[2,1],[[2,14,2],1],[[],1],[[[13,[3]]],1],[[],1],[2,1],[[2,3],1],[[2,14,2,3],1],[3,1],[[2,3],1],[[2,14,2,3],1],[15,1],[16,1],[14,1],[17,1],[18,1],[[],1],[2,1],[[2,14,2],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[19,19],[20,20],[[]],[[]],[[]],[[],19],[[],1],[[],1],[[],[[1,[19]]]],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[2,1],[3,1],[[2,3],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[2],[[20,20],4],[21,22],[[19,21],22],[21,22],[[23,21],22],[[19,21],22],[21,[[1,[24]]]],[[20,21],22],[[20,21],22],[[]],[[]],[[]],[[]],[[]],[[3,23]],[[20,23]],[[20,23]],[[],4],[2],[[],1],[[],1],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],1],[[],1],[[],[[13,[3]]]],[[],[[13,[3]]]],[[],1],[[]],[[]],[[],25],[[],1],[[],1],[[],1],[[],1],[3,1],[[],26],[[],26],[[],1],[2],[2],0,[[],1],[[],1],[4,1],[[19,4],1],[[],1],[2,1],[[[27,[18]]],1],[[],1],[19,1],[5,1],[[],1],[19,1],[6,1],[7,1],[[19,7],1],[8,1],[[19,8],1],[9,1],[10,1],[11,1],[[19,11],1],[12,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[2,1],[[19,2],1],[25,1],[15,1],[[19,15],1],[16,1],[14,1],[17,1],[[19,17],1],[18,1],[[],1],[19,1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[[[54,[53,53]]],[[54,[53,53]]]],[[[55,[53]]],[[55,[53]]]],[[[56,[53]]],[[56,[53]]]],[[[57,[53]]],[[57,[53]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[],37],[37,2],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[[28,3],1],[[29,3],1],[[30,3],1],[[31,3],1],[[32,3],1],[[33,3],1],[[34,3],1],[[35,3],1],[[36,3],1],[[38,3],1],[[39,3],1],[[40,3],1],[[41,3],1],[[42,3],1],[[43,3],1],[[44,3],1],[[45,3],1],[[46,3],1],[[47,3],1],[[48,3],1],[[49,3],1],[[50,3],1],[[51,3],1],[[52,3],1],[[54,3],1],[[55,3],1],[[56,3],1],[[57,3],1],[[28,2,3],1],[[29,2,3],1],[[30,2,3],1],[[31,2,3],1],[[32,2,3],1],[[33,2,3],1],[[34,2,3],1],[[35,2,3],1],[[36,2,3],1],[[38,2,3],1],[[39,2,3],1],[[40,2,3],1],[[41,2,3],1],[[42,2,3],1],[[43,2,3],1],[[44,2,3],1],[[45,2,3],1],[[46,2,3],1],[[47,2,3],1],[[48,2,3],1],[[49,2,3],1],[[50,2,3],1],[[51,2,3],1],[[52,2,3],1],[[54,2,3],1],[[55,2,3],1],[[56,2,3],1],[[57,2,3],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[36,1],[54,1],[[37,37],4],[[28,21],22],[[29,21],22],[[30,21],22],[[31,21],22],[[32,21],22],[[33,21],22],[[34,21],22],[[35,21],22],[[36,21],22],[[37,21],22],[[37,21],22],[[38,21],22],[[39,21],22],[[40,21],22],[[41,21],22],[[42,21],22],[[43,21],22],[[44,21],22],[[45,21],22],[[46,21],22],[[47,21],22],[[48,21],22],[[49,21],22],[[50,21],22],[[51,21],22],[[52,21],22],[[54,21],22],[[[55,[58]],21],22],[[[56,[58]],21],22],[[[57,[58]],21],22],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],28],[14,29],[2,30],[2,31],[25,32],[[[59,[2]]],33],[[],34],[[],35],[[],36],[4,38],[12,39],[9,40],[10,41],[11,42],[60,43],[18,44],[16,45],[17,46],[3,47],[6,48],[7,49],[5,50],[8,51],[15,52],[[],54],[[],55],[[],56],[[],57],[36,[[1,[13]]]],[54,[[1,[13]]]],[36,[[1,[13]]]],[36,[[1,[13]]]],[36,1],[61],[36,[[13,[3]]]],[36,[[13,[3]]]],[54,[[13,[3]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],25],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[29,1],[30,1],[31,1],[32,1],[33,1],[56,1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[],1],[[],1],[[],1],[[]],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[[]],[[]],[[],4],[[],1],[4,1],[[],1],[5,1],[[],1],[[],1],[62,1],[62,1],[[],1],[6,1],[7,1],[[],1],[[],1],[2,1],[2,1],[62,1],[62,1],[[62,2],1],[[62,2],1],[8,1],[9,1],[10,1],[11,1],[12,1],[[],1],[62,1],[[[13,[3]]],1],[2,1],[[2,14,2],1],[[],1],[[[13,[3]]],1],[[],1],[2,1],[[2,3],1],[[2,14,2,3],1],[3,1],[[2,3],1],[[2,14,2,3],1],[15,1],[16,1],[14,1],[17,1],[18,1],[[],1],[2,1],[[2,14,2],1],[[],1],[62,1],[2,1],[2,1],[[],1],[[],1],[[],26]],"p":[[4,"Result"],[15,"str"],[15,"usize"],[15,"bool"],[15,"char"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[4,"Option"],[15,"u32"],[15,"u128"],[15,"u16"],[15,"u64"],[15,"u8"],[3,"IgnoredAny"],[4,"Unexpected"],[3,"Formatter"],[6,"Result"],[8,"Expected"],[3,"Error"],[3,"String"],[3,"TypeId"],[3,"Vec"],[3,"UnitDeserializer"],[3,"U32Deserializer"],[3,"StrDeserializer"],[3,"BorrowedStrDeserializer"],[3,"StringDeserializer"],[3,"CowStrDeserializer"],[3,"BytesDeserializer"],[3,"BorrowedBytesDeserializer"],[3,"MapDeserializer"],[3,"Error"],[3,"BoolDeserializer"],[3,"I8Deserializer"],[3,"I16Deserializer"],[3,"I32Deserializer"],[3,"I64Deserializer"],[3,"IsizeDeserializer"],[3,"U8Deserializer"],[3,"U16Deserializer"],[3,"U64Deserializer"],[3,"UsizeDeserializer"],[3,"F32Deserializer"],[3,"F64Deserializer"],[3,"CharDeserializer"],[3,"I128Deserializer"],[3,"U128Deserializer"],[8,"Clone"],[3,"SeqDeserializer"],[3,"SeqAccessDeserializer"],[3,"MapAccessDeserializer"],[3,"EnumAccessDeserializer"],[8,"Debug"],[4,"Cow"],[15,"isize"],[3,"Demand"],[3,"Impossible"],[8,"Deserializer"],[8,"Serializer"],[8,"Deserialize"],[8,"Serialize"],[8,"IntoDeserializer"],[8,"SeqAccess"],[8,"MapAccess"],[8,"EnumAccess"],[8,"VariantAccess"],[8,"DeserializeSeed"],[8,"Visitor"],[8,"Error"],[13,"Bool"],[13,"Unsigned"],[13,"Signed"],[13,"Float"],[13,"Char"],[13,"Str"],[13,"Bytes"],[13,"Other"],[8,"SerializeSeq"],[8,"SerializeTuple"],[8,"SerializeTupleStruct"],[8,"SerializeTupleVariant"],[8,"SerializeMap"],[8,"SerializeStruct"],[8,"SerializeStructVariant"],[8,"Error"]]},\ +"serde_derive":{"doc":"This crate provides Serde’s two derive macros.","t":[24,24],"n":["Deserialize","Serialize"],"q":["serde_derive",""],"d":["",""],"i":[0,0],"f":[0,0],"p":[]},\ +"sha3":{"doc":"An implementation of the SHA-3 cryptographic hash …","t":[6,3,6,3,6,3,6,3,8,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,10,10,11,11,11,11,11,11,11,11,11,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["CShake128","CShake128Core","CShake128Reader","CShake128ReaderCore","CShake256","CShake256Core","CShake256Reader","CShake256ReaderCore","Digest","Keccak224","Keccak224Core","Keccak256","Keccak256Core","Keccak256Full","Keccak256FullCore","Keccak384","Keccak384Core","Keccak512","Keccak512Core","Sha3_224","Sha3_224Core","Sha3_256","Sha3_256Core","Sha3_384","Sha3_384Core","Sha3_512","Sha3_512Core","Shake128","Shake128Core","Shake128Reader","Shake128ReaderCore","Shake256","Shake256Core","Shake256Reader","Shake256ReaderCore","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chain_update","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","default","default","default","default","default","default","default","default","default","digest","digest","finalize","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_into","finalize_into_reset","finalize_reset","finalize_xof_core","finalize_xof_core","finalize_xof_core","finalize_xof_core","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new","new","new","new_with_function_name","new_with_function_name","new_with_prefix","output_size","read_block","read_block","read_block","read_block","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name"],"q":["sha3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["CSHAKE128 hasher state.","Core CSHAKE128 hasher state.","CSHAKE128 reader state.","Core CSHAKE128 reader state.","CSHAKE256 hasher state.","Core CSHAKE256 hasher state.","CSHAKE256 reader state.","Core CSHAKE256 reader state.","Convenience wrapper trait covering functionality of …","Keccak-224 hasher state.","Core Keccak-224 hasher state.","Keccak-256 hasher state.","Core Keccak-256 hasher state.","SHA-3 CryptoNight variant hasher state.","Core SHA-3 CryptoNight variant hasher state.","Keccak-384 hasher state.","Core Keccak-384 hasher state.","Keccak-512 hasher state.","Core Keccak-512 hasher state.","SHA-3-224 hasher state.","Core SHA-3-224 hasher state.","SHA-3-256 hasher state.","Core SHA-3-256 hasher state.","SHA-3-384 hasher state.","Core SHA-3-384 hasher state.","SHA-3-512 hasher state.","Core SHA-3-512 hasher state.","SHAKE128 hasher state.","Core SHAKE128 hasher state.","SHAKE128 reader state.","Core SHAKE128 reader state.","SHAKE256 hasher state.","Core SHAKE256 hasher state.","SHAKE256 reader state.","Core SHAKE256 reader state.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Process input data in a chained manner.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Compute hash of data.","Retrieve result and consume hasher instance.","","","","","","","","","","Write result into provided array and consume the hasher …","Write result into provided array and reset the hasher …","Retrieve result and reset hasher instance.","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Create new hasher instance.","Creates a new CSHAKE instance with the given customization.","Creates a new CSHAKE instance with the given customization.","Creates a new CSHAKE instance with the given function name …","Creates a new CSHAKE instance with the given function name …","Create new hasher instance which has processed the …","Get output size of the hasher","","","","","Reset hasher instance to its initial state.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Process data, updating the internal state.","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,13,0,29,29,2,3,4,5,6,7,8,9,10,29,29,29,11,13,15,17,2,3,4,5,6,7,8,9,10,11,13,15,17,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,15,17,15,17,29,29,12,14,16,18,29,2,3,4,5,6,7,8,9,10,11,13,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,2,3,4,5,6,7,8,9,10,11,13,15,17,2,3,4,5,6,7,8,9,10,11,13,15,17],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[[],10],[[],11],[[],13],0,[1,[[20,[19]]]],[[],[[20,[19]]]],[[2,21,22]],[[3,21,22]],[[4,21,22]],[[5,21,22]],[[6,21,22]],[[7,21,22]],[[8,21,22]],[[9,21,22]],[[10,21,22]],[20],[20],[[],[[20,[19]]]],[[11,21]],[[13,21]],[[15,21]],[[17,21]],[[2,23],24],[[3,23],24],[[4,23],24],[[5,23],24],[[6,23],24],[[7,23],24],[[8,23],24],[[9,23],24],[[10,23],24],[[11,23],24],[[13,23],24],[[15,23],24],[[17,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],15],[[],17],[[],15],[[],17],[1],[[],25],[12,[[26,[12]]]],[14,[[26,[14]]]],[16,[[26,[16]]]],[18,[[26,[18]]]],[[]],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[13],[15],[17],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24]],"p":[[8,"AsRef"],[3,"Keccak224Core"],[3,"Keccak256Core"],[3,"Keccak384Core"],[3,"Keccak512Core"],[3,"Keccak256FullCore"],[3,"Sha3_224Core"],[3,"Sha3_256Core"],[3,"Sha3_384Core"],[3,"Sha3_512Core"],[3,"Shake128Core"],[3,"Shake128ReaderCore"],[3,"Shake256Core"],[3,"Shake256ReaderCore"],[3,"CShake128Core"],[3,"CShake128ReaderCore"],[3,"CShake256Core"],[3,"CShake256ReaderCore"],[15,"u8"],[3,"GenericArray"],[6,"Buffer"],[6,"Output"],[3,"Formatter"],[6,"Result"],[15,"usize"],[6,"Block"],[4,"Result"],[3,"TypeId"],[8,"Digest"]]},\ +"shale":{"doc":"","t":[13,3,17,13,8,8,8,3,3,3,3,3,13,3,4,8,13,6,8,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,12,10,11,10,11,11,11,11,11,11,11,10,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,10,11,10,11,10,11,11,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,10,10,11,11,11,11,10,11,3,3,3,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5],"n":["DecodeError","DiskWrite","INVALID_SPACE_ID","LinearMemStoreError","MemStore","MemView","MummyItem","MummyObj","Obj","ObjCache","ObjPtr","ObjRef","ObjRefError","PlainMem","ShaleError","ShaleStore","SliceError","SpaceID","TypedView","addr","as_ptr","block","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","compact","data","dehydrate","dehydrate","dehydrated_len","dehydrated_len","deref","deref","deref","drop","drop","eq","estimate_mem_image","estimate_mem_image","flush_dirty","flush_dirty","flush_dirty","fmt","fmt","fmt","free_item","from","from","from","from","from","from","from","from","from_typed_view","get","get_hash","get_item","get_mem_store","get_mem_store","get_offset","get_offset","get_shared","get_shared","get_space_id","get_view","get_view","hash","hydrate","hydrate","id","id","into","into","into","into","into","into","into","into","is_mem_mapped","is_mem_mapped","is_mem_mapped","is_null","item_to_obj","new","new","new_from_addr","null","pop","ptr_to_obj","put","put_item","slice","space_id","space_off","to_dehydrated","to_longlive","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","util","write","write","write","write","write","write","write_mem_image","write_mem_image","CompactHeader","CompactSpace","CompactSpaceHeader","MSIZE","MSIZE","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","dehydrate","dehydrate","dehydrated_len","dehydrated_len","flush_dirty","free_item","from","from","from","get_item","hydrate","hydrate","into","into","into","is_freed","new","new","payload_size","put_item","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","get_raw_bytes"],"q":["shale","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","shale::compact","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","shale::util"],"d":["","","","","In-memory store that offers access to intervals from a …","A handle that pins and provides a readable access to a …","A stored item type that can be decoded from or encoded to …","Reference implementation of TypedView. It takes any type …","A wrapper of TypedView to enable writes. The direct …","ObjRef pool that is used by ShaleStore implementation to …","Opaque typed pointer in the 64-bit virtual addressable …","User handle that offers read & write access to the stored …","","Purely volatile, vector-based implementation for MemStore. …","","A persistent item storage backed by linear logical space. …","","","A addressed, typed, and read-writable handle for the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Estimate the serialized length of the current type …","","Flush all dirty writes.","","","","","","Free an item and recycle its space when applicable.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Dereference ObjPtr to a unique handle that allows direct …","Access it as a MemStore object.","","Get the offset of the initial byte in the linear space.","","Returns a handle that allows shared access to the store.","","","Returns a handle that pins the length of bytes starting …","","","","","Returns the identifier of this storage space.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns if the typed content is memory-mapped (i.e., all …","","","","","","","","","","","","Allocate a new item.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write the change to the portion of the linear space …","Gain mutable access to the typed content. By changing it, …","Write to the underlying object. Returns Some(()) on …","","","","Serialize the type content to the memory image. It defines …","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","",""],"i":[16,0,0,16,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,2,4,0,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,2,2,0,11,9,2,9,2,4,5,6,4,5,2,17,6,34,4,10,11,2,16,34,11,2,4,5,6,21,10,16,4,10,2,34,17,6,17,6,19,21,4,19,21,2,9,2,19,21,11,2,4,5,6,21,10,16,17,9,6,2,6,21,10,2,2,10,6,10,34,6,11,11,0,5,2,2,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,0,19,17,4,5,6,21,17,6,0,0,0,30,31,30,31,32,30,31,32,30,31,30,31,32,32,30,31,32,32,30,31,30,31,32,30,31,32,30,32,30,31,32,30,31,32,30,31,32,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[2,[1]]],3],[[[4,[1]]],[[2,[1]]]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,2],[[]],0,0,[[]],[2],[[],3],[2,3],[4],[5,4],[6],[[[4,[1]]]],[5],[[2,2],7],[[],[[8,[3]]]],[[[6,[9]]],[[8,[3]]]],[[],8],[[[4,[1]]]],[10,8],[[11,12],[[14,[13]]]],[[[2,[1]],12],15],[[16,12],15],[2,[[14,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[18,[17]]],[[4,[1]]]],[[10,2],[[14,[[8,[5]],16]]]],[[],3],[2,[[14,[5,16]]]],[[],19],[[[6,[9]]],19],[[],3],[[[6,[9]]],3],[[],[[8,[[18,[20]]]]]],[21,[[8,[[18,[20]]]]]],[[[4,[1]]],22],[[3,3],[[8,[[18,[23]]]]]],[[21,3,3],[[8,[[18,[23]]]]]],[2],[[3,19],[[14,[16]]]],[[3,19],[[14,[2,16]]]],[[],22],[21,22],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],7],[[],7],[[[6,[9]]],7],[[[2,[1]]],7],[[19,3,3,9],[[14,[[4,[9]],16]]]],[[3,22],21],[24,10],[3,[[2,[1]]]],[[],[[2,[1]]]],[[10,2]],[[19,[2,[9]],3],[[14,[[4,[9]],16]]]],[[10,4],5],[3,[[14,[5,16]]]],[[4,3,3,9],[[14,[[4,[9]],16]]]],0,0,[9,[[26,[25]]]],[5,5],[[]],[[],27],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],0,[3],[[]],[[[4,[1]],29],8],[[5,29],8],[[[6,[9]]]],[[21,3]],[[]],[[[6,[9]]]],0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[30],[31],[30,3],[31,3],[[[32,[9]]],8],[[[32,[9]],[2,[9]]],[[14,[16]]]],[[]],[[]],[[]],[[[32,[9]],[2,[9]]],[[14,[[5,[9]],16]]]],[[3,19],[[14,[30,16]]]],[[3,19],[[14,[31,16]]]],[[]],[[]],[[]],[30,7],[[3,3],31],[[[33,[19]],[33,[19]],[4,[31]],[10,[9]],3,3],[[14,[[32,[9]],16]]]],[30,3],[[[32,[9]],9,3],[[14,[[5,[9]],16]]]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],28],[[],28],[[],28],[[],[[26,[25]]]]],"p":[[8,"Sized"],[3,"ObjPtr"],[15,"u64"],[3,"Obj"],[3,"ObjRef"],[3,"MummyObj"],[15,"bool"],[4,"Option"],[8,"MummyItem"],[3,"ObjCache"],[3,"DiskWrite"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[4,"ShaleError"],[8,"TypedView"],[3,"Box"],[8,"MemStore"],[8,"Deref"],[3,"PlainMem"],[6,"SpaceID"],[8,"MemView"],[15,"usize"],[15,"u8"],[3,"Vec"],[3,"String"],[3,"TypeId"],[8,"FnOnce"],[3,"CompactHeader"],[3,"CompactSpaceHeader"],[3,"CompactSpace"],[3,"Rc"],[8,"ShaleStore"]]},\ +"slab":{"doc":"Pre-allocated storage for a uniform data type.","t":[3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Drain","IntoIter","Iter","IterMut","Slab","VacantEntry","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone_into","clone_into","compact","contains","default","drain","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_iter","get","get2_mut","get2_unchecked_mut","get_mut","get_unchecked","get_unchecked_mut","index","index_mut","insert","insert","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","key","key_of","len","len","len","len","len","new","next","next","next","next","next_back","next_back","next_back","next_back","remove","reserve","reserve_exact","retain","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_remove","type_id","type_id","type_id","type_id","type_id","type_id","vacant_entry","vacant_key","with_capacity"],"q":["slab","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A draining iterator for Slab","A consuming iterator over the values stored in a Slab","An iterator over the values stored in the Slab","A mutable iterator over the values stored in the Slab","Pre-allocated storage for a uniform data type","A handle to a vacant entry in a Slab.","","","","","","","","","","","","","Return the number of values the slab can store without …","Clear the slab of all values.","","","","","Reduce the capacity as much as possible, changing the key …","Return true if a value is associated with the given key.","","Return a draining iterator that removes all elements from …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Return a reference to the value associated with the given …","Return two mutable references to the values associated …","Return two mutable references to the values associated …","Return a mutable reference to the value associated with …","Return a reference to the value associated with the given …","Return a mutable reference to the value associated with …","","","Insert a value in the slab, returning key assigned to the …","Insert a value in the entry, returning a mutable reference …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","Return true if there are no values stored in the slab.","Return an iterator over the slab.","Return an iterator that allows modifying each value.","Return the key associated with this entry.","Get the key for an element in the slab.","","","","","Return the number of stored values.","Construct a new, empty Slab.","","","","","","","","","Remove and return the value associated with the given key.","Reserve capacity for at least additional more values to be …","Reserve the minimum capacity required to store exactly …","Retain only the elements specified by the predicate.","Shrink the capacity of the slab as much as possible …","","","","","","","","","","","","","","","","","","","Tries to remove the value associated with the given key, …","","","","","","","Return a handle to a vacant entry allowing for further …","Returns the key of the next vacant entry.","Construct a new, empty Slab with the specified capacity."],"i":[0,0,0,0,0,0,7,3,10,6,1,12,7,3,10,6,1,12,1,1,3,1,3,1,1,1,1,1,7,3,10,6,1,12,7,3,10,6,1,12,1,1,1,1,1,1,1,1,1,1,12,7,3,10,6,1,12,7,3,10,6,1,1,1,1,1,1,12,1,7,3,10,6,1,1,7,3,10,6,7,3,10,6,1,1,1,1,1,7,3,10,6,3,1,7,3,10,6,1,12,7,3,10,6,1,12,1,7,3,10,6,1,12,1,1,1],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,2],[1],[3,3],[[[1,[4]]],[[1,[4]]]],[[]],[[]],[1],[[1,2],5],[[],1],[1,6],[[7,8],9],[[3,8],9],[[10,8],9],[[6,8],9],[[1,8],9],[[[12,[11]],8],9],[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[1,2],13],[[1,2,2],13],[[1,2,2]],[[1,2],13],[[1,2]],[[1,2]],[[1,2]],[[1,2]],[1,2],[12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,10],[1,3],[1,7],[1,5],[1,3],[1,10],[12,2],[1,2],[7,2],[3,2],[10,2],[6,2],[1,2],[[],1],[7,13],[3,13],[10,13],[6,13],[7,13],[3,13],[10,13],[6,13],[[1,2]],[[1,2]],[[1,2]],[1],[1],[7],[3],[10],[6],[[]],[[]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[1,2],13],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[1,12],[1,2],[2,1]],"p":[[3,"Slab"],[15,"usize"],[3,"Iter"],[8,"Clone"],[15,"bool"],[3,"Drain"],[3,"IntoIter"],[3,"Formatter"],[6,"Result"],[3,"IterMut"],[8,"Debug"],[3,"VacantEntry"],[4,"Option"],[4,"Result"],[3,"TypeId"]]},\ +"smallvec":{"doc":"Small vectors in various sizes. These store a certain …","t":[13,8,13,4,3,3,16,3,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,14,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12],"n":["AllocErr","Array","CapacityOverflow","CollectionAllocErr","Drain","IntoIter","Item","SmallVec","ToSmallVec","append","as_mut","as_mut_ptr","as_mut_slice","as_mut_slice","as_ptr","as_ref","as_slice","as_slice","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone_from","clone_into","clone_into","cmp","dedup","dedup_by","dedup_by_key","default","deref","deref_mut","drain","drop","drop","drop","eq","extend","extend_from_slice","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_buf","from_buf_and_len","from_buf_and_len_unchecked","from_elem","from_iter","from_raw_parts","from_slice","from_vec","grow","hash","index","index_mut","inline_size","insert","insert_from_slice","insert_many","into","into","into","into","into_boxed_slice","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_vec","is_empty","len","len","new","next","next","next_back","next_back","partial_cmp","pop","push","remove","reserve","reserve_exact","resize","resize_with","retain","retain_mut","set_len","shrink_to_fit","size","size_hint","size_hint","smallvec","spilled","swap_remove","to_owned","to_owned","to_smallvec","to_string","truncate","try_from","try_from","try_from","try_from","try_grow","try_into","try_into","try_into","try_into","try_reserve","try_reserve_exact","type_id","type_id","type_id","type_id","with_capacity","layout"],"q":["smallvec","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","smallvec::CollectionAllocErr"],"d":["The allocator return an error","Types that can be used as the backing store for a SmallVec","Overflow usize::MAX or other error during size computation","Error type for APIs with fallible heap allocation","An iterator that removes the items from a SmallVec and …","An iterator that consumes a SmallVec and yields its items …","The type of the array’s elements.","A Vec-like container that can store a small number of …","Convenience trait for constructing a SmallVec","Moves all the elements of other into self, leaving other …","","Returns a raw mutable pointer to the vector’s buffer.","Extracts a mutable slice of the entire vector.","Returns the remaining items of this iterator as a mutable …","Returns a raw pointer to the vector’s buffer.","","Extracts a slice containing the entire vector.","Returns the remaining items of this iterator as a slice.","","","","","","","","","","","The number of items the vector can hold without …","Remove all elements from the vector.","","","","","","","Removes consecutive duplicate elements.","Removes consecutive duplicate elements using the given …","Removes consecutive elements that map to the same key.","","","","Creates a draining iterator that removes the specified …","","","","","","Copy elements from a slice and append them to the vector.","","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Constructs a new SmallVec on the stack from an A without …","Constructs a new SmallVec on the stack from an A without …","Constructs a new SmallVec on the stack from an A without …","Creates a SmallVec with n copies of elem.","","Creates a SmallVec directly from the raw components of …","Copy the elements from a slice into a new SmallVec.","Construct a new SmallVec from a Vec<A::Item>.","Re-allocate to set the capacity to …","","","","The maximum number of elements this vector can hold inline","Insert an element at position index, shifting all elements …","Copy elements from a slice into the vector at position …","Insert multiple elements at position index, shifting all …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Converts a SmallVec into a Box<[T]> without reallocating …","Convert the SmallVec into an A if possible. Otherwise …","","","","","","Convert a SmallVec to a Vec, without reallocating if the …","Returns true if the vector is empty","","The number of elements stored in the vector","Construct an empty vector","","","","","","Remove an item from the end of the vector and return it, …","Append an item to the vector.","Remove and return the element at position index, shifting …","Reserve capacity for additional more elements to be …","Reserve the minimum capacity for additional more elements …","Resizes the vector so that its length is equal to len.","Resizes the SmallVec in-place so that len is equal to …","Retains only the elements specified by the predicate.","Retains only the elements specified by the predicate.","Sets the length of a vector.","Shrink the capacity of the vector as much as possible.","Returns the number of items the array can hold.","","","Creates a SmallVec containing the arguments.","Returns true if the data has spilled into a separate …","Remove the element at position index, replacing it with …","","","Construct a new SmallVec from a slice.","","Shorten the vector, keeping the first len elements and …","","","","","Re-allocate to set the capacity to …","","","","","Reserve capacity for additional more elements to be …","Reserve the minimum capacity for additional more elements …","","","","","Construct an empty vector with enough capacity …","The layout that was passed to the allocator"],"i":[12,0,12,0,0,0,1,0,0,2,2,2,2,3,2,2,2,3,7,2,2,3,12,7,2,2,3,12,2,2,2,3,2,2,3,2,2,2,2,2,2,2,2,7,2,3,2,2,2,7,2,3,12,12,7,2,2,2,2,2,3,12,12,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,2,3,12,2,2,7,2,2,2,3,2,2,7,2,2,7,3,7,3,2,2,2,2,2,2,2,2,2,2,2,2,1,7,3,0,2,2,2,3,24,12,2,7,2,3,12,2,7,2,3,12,2,2,7,2,3,12,2,25],"f":[0,0,0,0,0,0,0,0,0,[[[2,[1]],2]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[]],[[[2,[1]]]],[[]],[[]],[[]],[[]],[[[2,[1]]]],[[]],[[]],[[]],[[[2,[1]]],4],[[[2,[1]]]],[[[2,[1]]],[[2,[1]]]],[[[3,[[0,[1,5]]]]],[[3,[[0,[1,5]]]]]],[[[2,[1]],[2,[1]]]],[[]],[[]],[[[2,[1]],2],6],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[],[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]],[[7,[1]]]],[[[7,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[[2,[1]],2],8],[[[2,[1]],9]],[[[2,[1]]]],[[[7,[1]],10],11],[[[2,[1]],10],11],[[[3,[1]],10],11],[[12,10],11],[[12,10],11],[[]],[1,[[2,[1]]]],[[],[[2,[1]]]],[13,[[2,[1]]]],[14],[[]],[[]],[15,12],[[]],[1,[[2,[1]]]],[[1,4],[[2,[1]]]],[[[16,[1]],4],[[2,[1]]]],[4,[[2,[1]]]],[9,[[2,[1]]]],[[4,4],[[2,[1]]]],[[],[[2,[1]]]],[13,[[2,[1]]]],[[[2,[1]],4]],[[[2,[1]]]],[[[2,[1]],17]],[[[2,[1]],17]],[[[2,[1]]],4],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4,9]],[[]],[[]],[[]],[[]],[[[2,[1]]],18],[[[2,[1]]],[[19,[1,[2,[1]]]]]],[[]],[2],[2],[[[2,[1]]]],[[]],[[[2,[1]]],13],[[[2,[1]]],8],[[[7,[1]]],4],[[[2,[1]]],4],[[],[[2,[1]]]],[[[7,[1]]],20],[[[3,[1]]],20],[[[7,[1]]],20],[[[3,[1]]],20],[[[2,[1]],2],[[20,[6]]]],[[[2,[1]]],20],[[[2,[1]]]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],21]],[[[2,[1]],21]],[[[2,[1]],4]],[[[2,[1]]]],[[],4],[[[7,[1]]]],[[[3,[1]]]],0,[[[2,[1]]],8],[[[2,[1]],4]],[[]],[[]],[[],2],[[],22],[[[2,[1]],4]],[[],19],[[],19],[[],19],[[],19],[[[2,[1]],4],[[19,[12]]]],[[],19],[[],19],[[],19],[[],19],[[[2,[1]],4],[[19,[12]]]],[[[2,[1]],4],[[19,[12]]]],[[],23],[[],23],[[],23],[[],23],[4,[[2,[1]]]],0],"p":[[8,"Array"],[3,"SmallVec"],[3,"IntoIter"],[15,"usize"],[8,"Clone"],[4,"Ordering"],[3,"Drain"],[15,"bool"],[8,"IntoIterator"],[3,"Formatter"],[6,"Result"],[4,"CollectionAllocErr"],[3,"Vec"],[15,"never"],[6,"LayoutErr"],[19,"MaybeUninit"],[8,"SliceIndex"],[3,"Box"],[4,"Result"],[4,"Option"],[8,"FnMut"],[3,"String"],[3,"TypeId"],[8,"ToSmallVec"],[13,"AllocErr"]]},\ +"static_assertions":{"doc":"Banner","t":[14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14],"n":["assert_cfg","assert_eq_align","assert_eq_size","assert_eq_size_ptr","assert_eq_size_val","assert_fields","assert_impl_all","assert_impl_any","assert_impl_one","assert_not_impl_all","assert_not_impl_any","assert_obj_safe","assert_trait_sub_all","assert_trait_super_all","assert_type_eq_all","assert_type_ne_all","const_assert","const_assert_eq","const_assert_ne"],"q":["static_assertions","","","","","","","","","","","","","","","","","",""],"d":["Asserts that a given configuration is set.","Asserts that types are equal in alignment.","Asserts that types are equal in size.","Asserts that values pointed to are equal in size.","Asserts that values are equal in size.","Asserts that the type has the given fields.","Asserts that the type implements all of the given traits.","Asserts that the type implements any of the given traits.","Asserts that the type implements exactly one in a set of …","Asserts that the type does not implement all of the given …","Asserts that the type does not implement any of the given …","Asserts that the traits support dynamic dispatch (…","Asserts that the trait is a child of all of the other …","Asserts that the trait is a parent of all of the other …","Asserts that all types in a list are equal to each other.","Asserts that all types are not equal to each other.","Asserts that constant expressions evaluate to true.","Asserts that constants are equal in value.","Asserts that constants are not equal in value."],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"p":[]},\ +"syn":{"doc":"github crates-io docs-rs","t":[3,13,13,13,13,3,3,13,13,13,13,13,4,3,6,13,13,3,4,13,3,13,13,13,13,13,13,13,3,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,3,3,13,13,13,13,4,3,3,3,13,13,3,13,13,13,13,13,13,3,4,13,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,3,13,3,3,4,3,3,3,13,13,13,4,13,4,3,3,3,3,13,13,4,4,4,3,13,13,13,13,13,13,3,13,13,13,3,4,3,3,3,3,13,3,13,13,13,13,13,4,13,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,13,3,13,13,13,13,3,13,4,13,13,13,3,3,3,3,3,3,3,3,13,13,13,3,13,13,13,13,13,13,13,13,4,13,13,4,4,13,3,3,13,13,13,3,13,13,13,13,13,13,13,13,13,4,13,13,13,13,13,13,13,13,13,13,13,3,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,13,13,13,13,4,3,3,3,3,13,13,3,13,13,4,13,3,13,13,13,13,13,13,13,13,13,13,6,13,4,13,13,13,13,13,3,13,13,13,13,4,13,4,13,13,13,13,13,13,14,13,13,13,3,4,4,3,3,3,3,13,13,13,13,13,13,13,3,4,13,13,13,13,13,13,13,13,13,13,13,3,3,3,3,3,3,3,3,3,4,3,3,3,3,3,3,3,13,4,13,13,13,13,13,13,13,13,3,3,3,3,3,4,3,3,13,13,13,13,13,13,13,13,3,3,3,4,3,4,13,13,13,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,14,12,12,12,12,12,12,12,14,12,0,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,12,12,12,12,12,12,12,12,12,12,14,14,12,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,11,11,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,11,12,12,12,11,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,14,0,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,5,11,14,11,11,11,11,14,14,5,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,0,11,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,11,11,11,11,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,18,10,3,3,3,16,8,3,6,8,8,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,13,3,3,3,3,4,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11],"n":["Abi","Add","AddEq","And","AngleBracketed","AngleBracketedGenericArguments","Arm","Array","Array","Assign","AssignOp","Async","AttrStyle","Attribute","AttributeArgs","Await","BareFn","BareFnArg","BinOp","Binary","Binding","Binding","BitAnd","BitAndEq","BitOr","BitOrEq","BitXor","BitXorEq","Block","Block","Bool","BoundLifetimes","Box","Box","Brace","Bracket","Break","Byte","ByteStr","Call","Cast","Char","Closed","Closure","Const","Const","Const","Const","Const","Const","ConstParam","Constraint","Constraint","Continue","Cooked","Crate","Data","DataEnum","DataStruct","DataUnion","Default","Deref","DeriveInput","Div","DivEq","Enum","Enum","Eq","Eq","Error","Expr","Expr","ExprArray","ExprAssign","ExprAssignOp","ExprAsync","ExprAwait","ExprBinary","ExprBlock","ExprBox","ExprBreak","ExprCall","ExprCast","ExprClosure","ExprContinue","ExprField","ExprForLoop","ExprGroup","ExprIf","ExprIndex","ExprLet","ExprLit","ExprLoop","ExprMacro","ExprMatch","ExprMethodCall","ExprParen","ExprPath","ExprRange","ExprReference","ExprRepeat","ExprReturn","ExprStruct","ExprTry","ExprTryBlock","ExprTuple","ExprType","ExprUnary","ExprUnsafe","ExprWhile","ExprYield","ExternCrate","Field","Field","FieldPat","FieldValue","Fields","FieldsNamed","FieldsUnnamed","File","Float","Fn","Fn","FnArg","ForLoop","ForeignItem","ForeignItemFn","ForeignItemMacro","ForeignItemStatic","ForeignItemType","ForeignMod","Ge","GenericArgument","GenericMethodArgument","GenericParam","Generics","Glob","Group","Group","Group","Gt","HalfOpen","Ident","Ident","If","Impl","ImplGenerics","ImplItem","ImplItemConst","ImplItemMacro","ImplItemMethod","ImplItemType","ImplTrait","Index","Index","Infer","Inherited","Inner","Int","Item","Item","ItemConst","ItemEnum","ItemExternCrate","ItemFn","ItemForeignMod","ItemImpl","ItemMacro","ItemMacro2","ItemMod","ItemStatic","ItemStruct","ItemTrait","ItemTraitAlias","ItemType","ItemUnion","ItemUse","Label","Le","Let","Lifetime","Lifetime","Lifetime","Lifetime","Lifetime","LifetimeDef","List","Lit","Lit","Lit","Lit","LitBool","LitByte","LitByteStr","LitChar","LitFloat","LitInt","LitStr","Local","Local","Loop","Lt","Macro","Macro","Macro","Macro","Macro","Macro","Macro","Macro","Macro2","MacroDelimiter","Match","Maybe","Member","Meta","Meta","MetaList","MetaNameValue","Method","Method","MethodCall","MethodTurbofish","Mod","Mul","MulEq","Name","NameValue","Named","Named","Ne","Neg","NestedMeta","Never","None","None","Not","Or","Or","Outer","Paren","Paren","Paren","Parenthesized","ParenthesizedGenericArguments","Pat","PatBox","PatIdent","PatLit","PatMacro","PatOr","PatPath","PatRange","PatReference","PatRest","PatSlice","PatStruct","PatTuple","PatTupleStruct","PatType","PatWild","Path","Path","Path","Path","Path","Path","PathArguments","PathSegment","PredicateEq","PredicateLifetime","PredicateType","Ptr","Public","QSelf","Range","Range","RangeLimits","Raw","Receiver","Receiver","Reference","Reference","Reference","Rem","RemEq","Rename","Repeat","Rest","Restricted","Result","Return","ReturnType","Semi","Shl","ShlEq","Shr","ShrEq","Signature","Slice","Slice","Static","Static","Stmt","Str","StrStyle","Struct","Struct","Struct","Struct","Sub","SubEq","Token","Trait","Trait","TraitAlias","TraitBound","TraitBoundModifier","TraitItem","TraitItemConst","TraitItemMacro","TraitItemMethod","TraitItemType","TraitObject","Try","TryBlock","Tuple","Tuple","Tuple","TupleStruct","Turbofish","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","TypeArray","TypeBareFn","TypeGenerics","TypeGroup","TypeImplTrait","TypeInfer","TypeMacro","TypeNever","TypeParam","TypeParamBound","TypeParen","TypePath","TypePtr","TypeReference","TypeSlice","TypeTraitObject","TypeTuple","Typed","UnOp","Unary","Union","Union","Unit","Unnamed","Unnamed","Unsafe","Use","UseGlob","UseGroup","UseName","UsePath","UseRename","UseTree","Variadic","Variant","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","VisCrate","VisPublic","VisRestricted","Visibility","WhereClause","WherePredicate","While","Wild","Yield","abi","abi","abi","and_token","and_token","and_token","apostrophe","args","args","args","args","arguments","arms","as_token","as_token","as_token","as_turbofish","async_token","asyncness","asyncness","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","auto_token","await_token","bang_token","bang_token","base","base","base10_digits","base10_digits","base10_parse","base10_parse","block","block","block","block","block","block","body","body","body","body","body","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bounded_ty","bounds","bounds","bounds","bounds","bounds","bounds","bounds","bounds","bounds","box_token","box_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","braced","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracketed","break_token","buffer","by_ref","capture","capture","cases","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","colon2_token","colon2_token","colon2_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","comma","cond","cond","const_params","const_params_mut","const_token","const_token","const_token","const_token","const_token","constness","content","continue_token","crate_token","crate_token","custom_keyword","custom_punctuation","data","default","default","default","default","default","default","default","default","defaultness","defaultness","defaultness","defaultness","delimiter","discriminant","dot2_token","dot2_token","dot2_token","dot_token","dot_token","dot_token","dots","dyn_token","elem","elem","elem","elem","elem","elem","elems","elems","elems","elems","elems","else_branch","enum_token","enum_token","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","ext","extern_token","extern_token","fat_arrow_token","fields","fields","fields","fields","fields","fields","fields","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fn_token","fn_token","for_token","for_token","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","func","generics","generics","generics","generics","generics","generics","generics","generics","generics","generics","generics","get_ident","group_token","group_token","gt_token","gt_token","gt_token","gt_token","gt_token","guard","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hi","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","if_token","impl_token","impl_token","in_token","in_token","index","index","init","inputs","inputs","inputs","inputs","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_empty","is_empty","is_ident","is_none","items","items","items","items","items","iter","iter_mut","label","label","label","label","label","label","leading_colon","leading_colon","leading_vert","left","left","left","len","len","len","let_token","let_token","lhs_ty","lifetime","lifetime","lifetime","lifetime","lifetimes","lifetimes","lifetimes","lifetimes","lifetimes","lifetimes_mut","limits","limits","lit","lit","lo","loop_token","lt_token","lt_token","lt_token","lt_token","lt_token","mac","mac","mac","mac","mac","mac","mac","macro_token","make_where_clause","match_token","member","member","member","method","mod_token","modifier","movability","mutability","mutability","mutability","mutability","mutability","mutability","mutability","mutability","name","name","name","named","nested","new","new","new","new","new","new","new","new","new","new","new","new_raw","op","op","op","or1_token","or2_token","output","output","output","output","params","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","parenthesized","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse2","parse_any","parse_args","parse_args_with","parse_body","parse_body_with","parse_file","parse_inner","parse_macro_input","parse_meta","parse_mod_style","parse_named","parse_outer","parse_quote","parse_quote_spanned","parse_str","parse_unnamed","parse_with","parse_within","parse_without_eager_brace","partial_cmp","partial_cmp","pat","pat","pat","pat","pat","pat","pat","pat","pat","path","path","path","path","path","path","path","path","path","path","path","path","path","position","pound_token","predicates","pub_token","pub_token","punctuated","qself","qself","qself","question_token","raw","receiver","receiver","reference","rename","rename","rest","return_token","rhs_ty","right","right","right","rules","segments","self_token","self_ty","semi","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","shebang","sig","sig","sig","sig","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","spanned","split_for_impl","star_token","star_token","static_token","static_token","stmts","struct_token","struct_token","style","subpat","suffix","suffix","suffix","suffix","suffix","suffix","suffix","supertraits","then_branch","to","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","token","token","token","token","token","token","token","token","tokens","tokens","trait_","trait_token","trait_token","tree","tree","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_token","turbofish","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_params","type_params_mut","type_token","type_token","type_token","type_token","underscore_token","underscore_token","union_token","union_token","unnamed","unraw","unsafe_token","unsafety","unsafety","unsafety","unsafety","use_token","value","value","value","value","value","value","variadic","variadic","variants","variants","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","visit_mut","where_clause","where_token","while_token","without_plus","without_plus","without_plus","without_plus","yield_token","Cursor","TokenBuffer","begin","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","empty","eof","eq","from","from","group","ident","into","into","lifetime","literal","new","new2","partial_cmp","punct","span","to_owned","token_stream","token_tree","try_from","try_from","try_into","try_into","type_id","type_id","IdentExt","parse_any","peek_any","unraw","Error","Lookahead1","Nothing","Output","Parse","ParseBuffer","ParseStream","Parser","Peek","Result","StepCursor","advance_to","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","call","clone","clone","clone_into","clone_into","combine","cursor","deref","discouraged","drop","eq","error","error","error","extend","fmt","fmt","fmt","fmt","fmt","fork","from","from","from","from","from","from","hash","into","into","into","into","into","into_compile_error","into_iter","into_iter","is_empty","lookahead1","new","new_spanned","parse","parse","parse","parse","parse2","parse_str","parse_terminated","peek","peek","peek2","peek3","provide","span","span","step","to_compile_error","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","Speculative","advance_to","End","IntoIter","IntoPairs","Iter","IterMut","Pair","Pairs","PairsMut","Punctuated","Punctuated","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","empty_or_trailing","eq","extend","extend","first","first_mut","fmt","from","from","from","from","from","from","from","from","from_iter","from_iter","hash","index","index_mut","insert","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_pairs","into_tuple","into_value","is_empty","iter","iter_mut","last","last_mut","len","len","len","len","len","len","len","new","new","next","next","next","next","next","next","next_back","next_back","next_back","next_back","next_back","next_back","pairs","pairs_mut","parse_separated_nonempty","parse_separated_nonempty_with","parse_terminated","parse_terminated_with","pop","punct","punct_mut","push","push_punct","push_value","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","span","span","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_tokens","to_tokens","trailing_punct","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","value","value_mut","Spanned","span","Abstract","Add","AddEq","And","AndAnd","AndEq","As","Async","At","Auto","Await","Bang","Become","Box","Brace","Bracket","Break","Caret","CaretEq","Colon","Colon2","Comma","Const","Continue","Crate","Default","Div","DivEq","Do","Dollar","Dot","Dot2","Dot3","DotDotEq","Dyn","Else","Enum","Eq","EqEq","Extern","FatArrow","Final","Fn","For","Ge","Group","Gt","If","Impl","In","LArrow","Le","Let","Loop","Lt","Macro","Match","Mod","Move","MulEq","Mut","Ne","Or","OrEq","OrOr","Override","Paren","Pound","Priv","Pub","Question","RArrow","Ref","Rem","RemEq","Return","SelfType","SelfValue","Semi","Shl","ShlEq","Shr","ShrEq","Star","Static","Struct","Sub","SubEq","Super","Tilde","Token","Trait","Try","Type","Typeof","Underscore","Union","Unsafe","Unsized","Use","Virtual","Where","While","Yield","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","surround","surround","surround","surround","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","VisitMut","visit_abi_mut","visit_abi_mut","visit_abi_mut","visit_angle_bracketed_generic_arguments_mut","visit_angle_bracketed_generic_arguments_mut","visit_angle_bracketed_generic_arguments_mut","visit_arm_mut","visit_arm_mut","visit_arm_mut","visit_attr_style_mut","visit_attr_style_mut","visit_attr_style_mut","visit_attribute_mut","visit_attribute_mut","visit_attribute_mut","visit_bare_fn_arg_mut","visit_bare_fn_arg_mut","visit_bare_fn_arg_mut","visit_bin_op_mut","visit_bin_op_mut","visit_bin_op_mut","visit_binding_mut","visit_binding_mut","visit_binding_mut","visit_block_mut","visit_block_mut","visit_block_mut","visit_bound_lifetimes_mut","visit_bound_lifetimes_mut","visit_bound_lifetimes_mut","visit_const_param_mut","visit_const_param_mut","visit_const_param_mut","visit_constraint_mut","visit_constraint_mut","visit_constraint_mut","visit_data_enum_mut","visit_data_enum_mut","visit_data_enum_mut","visit_data_mut","visit_data_mut","visit_data_mut","visit_data_struct_mut","visit_data_struct_mut","visit_data_struct_mut","visit_data_union_mut","visit_data_union_mut","visit_data_union_mut","visit_derive_input_mut","visit_derive_input_mut","visit_derive_input_mut","visit_expr_array_mut","visit_expr_array_mut","visit_expr_array_mut","visit_expr_assign_mut","visit_expr_assign_mut","visit_expr_assign_mut","visit_expr_assign_op_mut","visit_expr_assign_op_mut","visit_expr_assign_op_mut","visit_expr_async_mut","visit_expr_async_mut","visit_expr_async_mut","visit_expr_await_mut","visit_expr_await_mut","visit_expr_await_mut","visit_expr_binary_mut","visit_expr_binary_mut","visit_expr_binary_mut","visit_expr_block_mut","visit_expr_block_mut","visit_expr_block_mut","visit_expr_box_mut","visit_expr_box_mut","visit_expr_box_mut","visit_expr_break_mut","visit_expr_break_mut","visit_expr_break_mut","visit_expr_call_mut","visit_expr_call_mut","visit_expr_call_mut","visit_expr_cast_mut","visit_expr_cast_mut","visit_expr_cast_mut","visit_expr_closure_mut","visit_expr_closure_mut","visit_expr_closure_mut","visit_expr_continue_mut","visit_expr_continue_mut","visit_expr_continue_mut","visit_expr_field_mut","visit_expr_field_mut","visit_expr_field_mut","visit_expr_for_loop_mut","visit_expr_for_loop_mut","visit_expr_for_loop_mut","visit_expr_group_mut","visit_expr_group_mut","visit_expr_group_mut","visit_expr_if_mut","visit_expr_if_mut","visit_expr_if_mut","visit_expr_index_mut","visit_expr_index_mut","visit_expr_index_mut","visit_expr_let_mut","visit_expr_let_mut","visit_expr_let_mut","visit_expr_lit_mut","visit_expr_lit_mut","visit_expr_lit_mut","visit_expr_loop_mut","visit_expr_loop_mut","visit_expr_loop_mut","visit_expr_macro_mut","visit_expr_macro_mut","visit_expr_macro_mut","visit_expr_match_mut","visit_expr_match_mut","visit_expr_match_mut","visit_expr_method_call_mut","visit_expr_method_call_mut","visit_expr_method_call_mut","visit_expr_mut","visit_expr_mut","visit_expr_mut","visit_expr_paren_mut","visit_expr_paren_mut","visit_expr_paren_mut","visit_expr_path_mut","visit_expr_path_mut","visit_expr_path_mut","visit_expr_range_mut","visit_expr_range_mut","visit_expr_range_mut","visit_expr_reference_mut","visit_expr_reference_mut","visit_expr_reference_mut","visit_expr_repeat_mut","visit_expr_repeat_mut","visit_expr_repeat_mut","visit_expr_return_mut","visit_expr_return_mut","visit_expr_return_mut","visit_expr_struct_mut","visit_expr_struct_mut","visit_expr_struct_mut","visit_expr_try_block_mut","visit_expr_try_block_mut","visit_expr_try_block_mut","visit_expr_try_mut","visit_expr_try_mut","visit_expr_try_mut","visit_expr_tuple_mut","visit_expr_tuple_mut","visit_expr_tuple_mut","visit_expr_type_mut","visit_expr_type_mut","visit_expr_type_mut","visit_expr_unary_mut","visit_expr_unary_mut","visit_expr_unary_mut","visit_expr_unsafe_mut","visit_expr_unsafe_mut","visit_expr_unsafe_mut","visit_expr_while_mut","visit_expr_while_mut","visit_expr_while_mut","visit_expr_yield_mut","visit_expr_yield_mut","visit_expr_yield_mut","visit_field_mut","visit_field_mut","visit_field_mut","visit_field_pat_mut","visit_field_pat_mut","visit_field_pat_mut","visit_field_value_mut","visit_field_value_mut","visit_field_value_mut","visit_fields_mut","visit_fields_mut","visit_fields_mut","visit_fields_named_mut","visit_fields_named_mut","visit_fields_named_mut","visit_fields_unnamed_mut","visit_fields_unnamed_mut","visit_fields_unnamed_mut","visit_file_mut","visit_file_mut","visit_file_mut","visit_fn_arg_mut","visit_fn_arg_mut","visit_fn_arg_mut","visit_foreign_item_fn_mut","visit_foreign_item_fn_mut","visit_foreign_item_fn_mut","visit_foreign_item_macro_mut","visit_foreign_item_macro_mut","visit_foreign_item_macro_mut","visit_foreign_item_mut","visit_foreign_item_mut","visit_foreign_item_mut","visit_foreign_item_static_mut","visit_foreign_item_static_mut","visit_foreign_item_static_mut","visit_foreign_item_type_mut","visit_foreign_item_type_mut","visit_foreign_item_type_mut","visit_generic_argument_mut","visit_generic_argument_mut","visit_generic_argument_mut","visit_generic_method_argument_mut","visit_generic_method_argument_mut","visit_generic_method_argument_mut","visit_generic_param_mut","visit_generic_param_mut","visit_generic_param_mut","visit_generics_mut","visit_generics_mut","visit_generics_mut","visit_ident_mut","visit_ident_mut","visit_ident_mut","visit_impl_item_const_mut","visit_impl_item_const_mut","visit_impl_item_const_mut","visit_impl_item_macro_mut","visit_impl_item_macro_mut","visit_impl_item_macro_mut","visit_impl_item_method_mut","visit_impl_item_method_mut","visit_impl_item_method_mut","visit_impl_item_mut","visit_impl_item_mut","visit_impl_item_mut","visit_impl_item_type_mut","visit_impl_item_type_mut","visit_impl_item_type_mut","visit_index_mut","visit_index_mut","visit_index_mut","visit_item_const_mut","visit_item_const_mut","visit_item_const_mut","visit_item_enum_mut","visit_item_enum_mut","visit_item_enum_mut","visit_item_extern_crate_mut","visit_item_extern_crate_mut","visit_item_extern_crate_mut","visit_item_fn_mut","visit_item_fn_mut","visit_item_fn_mut","visit_item_foreign_mod_mut","visit_item_foreign_mod_mut","visit_item_foreign_mod_mut","visit_item_impl_mut","visit_item_impl_mut","visit_item_impl_mut","visit_item_macro2_mut","visit_item_macro2_mut","visit_item_macro2_mut","visit_item_macro_mut","visit_item_macro_mut","visit_item_macro_mut","visit_item_mod_mut","visit_item_mod_mut","visit_item_mod_mut","visit_item_mut","visit_item_mut","visit_item_mut","visit_item_static_mut","visit_item_static_mut","visit_item_static_mut","visit_item_struct_mut","visit_item_struct_mut","visit_item_struct_mut","visit_item_trait_alias_mut","visit_item_trait_alias_mut","visit_item_trait_alias_mut","visit_item_trait_mut","visit_item_trait_mut","visit_item_trait_mut","visit_item_type_mut","visit_item_type_mut","visit_item_type_mut","visit_item_union_mut","visit_item_union_mut","visit_item_union_mut","visit_item_use_mut","visit_item_use_mut","visit_item_use_mut","visit_label_mut","visit_label_mut","visit_label_mut","visit_lifetime_def_mut","visit_lifetime_def_mut","visit_lifetime_def_mut","visit_lifetime_mut","visit_lifetime_mut","visit_lifetime_mut","visit_lit_bool_mut","visit_lit_bool_mut","visit_lit_bool_mut","visit_lit_byte_mut","visit_lit_byte_mut","visit_lit_byte_mut","visit_lit_byte_str_mut","visit_lit_byte_str_mut","visit_lit_byte_str_mut","visit_lit_char_mut","visit_lit_char_mut","visit_lit_char_mut","visit_lit_float_mut","visit_lit_float_mut","visit_lit_float_mut","visit_lit_int_mut","visit_lit_int_mut","visit_lit_int_mut","visit_lit_mut","visit_lit_mut","visit_lit_mut","visit_lit_str_mut","visit_lit_str_mut","visit_lit_str_mut","visit_local_mut","visit_local_mut","visit_local_mut","visit_macro_delimiter_mut","visit_macro_delimiter_mut","visit_macro_delimiter_mut","visit_macro_mut","visit_macro_mut","visit_macro_mut","visit_member_mut","visit_member_mut","visit_member_mut","visit_meta_list_mut","visit_meta_list_mut","visit_meta_list_mut","visit_meta_mut","visit_meta_mut","visit_meta_mut","visit_meta_name_value_mut","visit_meta_name_value_mut","visit_meta_name_value_mut","visit_method_turbofish_mut","visit_method_turbofish_mut","visit_method_turbofish_mut","visit_nested_meta_mut","visit_nested_meta_mut","visit_nested_meta_mut","visit_parenthesized_generic_arguments_mut","visit_parenthesized_generic_arguments_mut","visit_parenthesized_generic_arguments_mut","visit_pat_box_mut","visit_pat_box_mut","visit_pat_box_mut","visit_pat_ident_mut","visit_pat_ident_mut","visit_pat_ident_mut","visit_pat_lit_mut","visit_pat_lit_mut","visit_pat_lit_mut","visit_pat_macro_mut","visit_pat_macro_mut","visit_pat_macro_mut","visit_pat_mut","visit_pat_mut","visit_pat_mut","visit_pat_or_mut","visit_pat_or_mut","visit_pat_or_mut","visit_pat_path_mut","visit_pat_path_mut","visit_pat_path_mut","visit_pat_range_mut","visit_pat_range_mut","visit_pat_range_mut","visit_pat_reference_mut","visit_pat_reference_mut","visit_pat_reference_mut","visit_pat_rest_mut","visit_pat_rest_mut","visit_pat_rest_mut","visit_pat_slice_mut","visit_pat_slice_mut","visit_pat_slice_mut","visit_pat_struct_mut","visit_pat_struct_mut","visit_pat_struct_mut","visit_pat_tuple_mut","visit_pat_tuple_mut","visit_pat_tuple_mut","visit_pat_tuple_struct_mut","visit_pat_tuple_struct_mut","visit_pat_tuple_struct_mut","visit_pat_type_mut","visit_pat_type_mut","visit_pat_type_mut","visit_pat_wild_mut","visit_pat_wild_mut","visit_pat_wild_mut","visit_path_arguments_mut","visit_path_arguments_mut","visit_path_arguments_mut","visit_path_mut","visit_path_mut","visit_path_mut","visit_path_segment_mut","visit_path_segment_mut","visit_path_segment_mut","visit_predicate_eq_mut","visit_predicate_eq_mut","visit_predicate_eq_mut","visit_predicate_lifetime_mut","visit_predicate_lifetime_mut","visit_predicate_lifetime_mut","visit_predicate_type_mut","visit_predicate_type_mut","visit_predicate_type_mut","visit_qself_mut","visit_qself_mut","visit_qself_mut","visit_range_limits_mut","visit_range_limits_mut","visit_range_limits_mut","visit_receiver_mut","visit_receiver_mut","visit_receiver_mut","visit_return_type_mut","visit_return_type_mut","visit_return_type_mut","visit_signature_mut","visit_signature_mut","visit_signature_mut","visit_span_mut","visit_span_mut","visit_span_mut","visit_stmt_mut","visit_stmt_mut","visit_stmt_mut","visit_trait_bound_modifier_mut","visit_trait_bound_modifier_mut","visit_trait_bound_modifier_mut","visit_trait_bound_mut","visit_trait_bound_mut","visit_trait_bound_mut","visit_trait_item_const_mut","visit_trait_item_const_mut","visit_trait_item_const_mut","visit_trait_item_macro_mut","visit_trait_item_macro_mut","visit_trait_item_macro_mut","visit_trait_item_method_mut","visit_trait_item_method_mut","visit_trait_item_method_mut","visit_trait_item_mut","visit_trait_item_mut","visit_trait_item_mut","visit_trait_item_type_mut","visit_trait_item_type_mut","visit_trait_item_type_mut","visit_type_array_mut","visit_type_array_mut","visit_type_array_mut","visit_type_bare_fn_mut","visit_type_bare_fn_mut","visit_type_bare_fn_mut","visit_type_group_mut","visit_type_group_mut","visit_type_group_mut","visit_type_impl_trait_mut","visit_type_impl_trait_mut","visit_type_impl_trait_mut","visit_type_infer_mut","visit_type_infer_mut","visit_type_infer_mut","visit_type_macro_mut","visit_type_macro_mut","visit_type_macro_mut","visit_type_mut","visit_type_mut","visit_type_mut","visit_type_never_mut","visit_type_never_mut","visit_type_never_mut","visit_type_param_bound_mut","visit_type_param_bound_mut","visit_type_param_bound_mut","visit_type_param_mut","visit_type_param_mut","visit_type_param_mut","visit_type_paren_mut","visit_type_paren_mut","visit_type_paren_mut","visit_type_path_mut","visit_type_path_mut","visit_type_path_mut","visit_type_ptr_mut","visit_type_ptr_mut","visit_type_ptr_mut","visit_type_reference_mut","visit_type_reference_mut","visit_type_reference_mut","visit_type_slice_mut","visit_type_slice_mut","visit_type_slice_mut","visit_type_trait_object_mut","visit_type_trait_object_mut","visit_type_trait_object_mut","visit_type_tuple_mut","visit_type_tuple_mut","visit_type_tuple_mut","visit_un_op_mut","visit_un_op_mut","visit_un_op_mut","visit_use_glob_mut","visit_use_glob_mut","visit_use_glob_mut","visit_use_group_mut","visit_use_group_mut","visit_use_group_mut","visit_use_name_mut","visit_use_name_mut","visit_use_name_mut","visit_use_path_mut","visit_use_path_mut","visit_use_path_mut","visit_use_rename_mut","visit_use_rename_mut","visit_use_rename_mut","visit_use_tree_mut","visit_use_tree_mut","visit_use_tree_mut","visit_variadic_mut","visit_variadic_mut","visit_variadic_mut","visit_variant_mut","visit_variant_mut","visit_variant_mut","visit_vis_crate_mut","visit_vis_crate_mut","visit_vis_crate_mut","visit_vis_public_mut","visit_vis_public_mut","visit_vis_public_mut","visit_vis_restricted_mut","visit_vis_restricted_mut","visit_vis_restricted_mut","visit_visibility_mut","visit_visibility_mut","visit_visibility_mut","visit_where_clause_mut","visit_where_clause_mut","visit_where_clause_mut","visit_where_predicate_mut","visit_where_predicate_mut","visit_where_predicate_mut"],"q":["syn","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::buffer","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::ext","","","","syn::parse","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::parse::discouraged","","syn::punctuated","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::spanned","","syn::token","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::visit_mut","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["The binary interface of a function: extern "C".","The + operator (addition)","The += operator","The && operator (logical and)","The <'a, T> in std::slice::iter<'a, T>.","Angle bracketed arguments of a path segment: the <K, V> in …","One arm of a match expression: 0...10 => { return true; }.","A slice literal expression: [a, b, c, d].","A fixed size array type: [T; n].","An assignment expression: a = compute().","A compound assignment expression: counter += 1.","An async block: async { ... }.","Distinguishes between attributes that decorate an item and …","An attribute like #[repr(transparent)].","Conventional argument type associated with an invocation …","An await expression: fut.await.","A bare function type: fn(usize) -> bool.","An argument in a function type: the usize in …","A binary operator: +, +=, &.","A binary operation: a + b, a * b.","A binding (equality constraint) on an associated type: …","A binding (equality constraint) on an associated type: the …","The & operator (bitwise and)","The &= operator","The | operator (bitwise or)","The |= operator","The ^ operator (bitwise xor)","The ^= operator","A braced block containing Rust statements.","A blocked scope: { ... }.","A boolean literal: true or false.","A set of bound lifetimes: for<'a, 'b, 'c>.","A box expression: box f.","A box pattern: box v.","","","A break, with an optional label to break and an optional …","A byte literal: b'f'.","A byte string literal: b"foo".","A function call expression: invoke(a, b).","A cast expression: foo as f64.","A character literal: 'a'.","Inclusive at the beginning and end.","A closure expression: |a, b| a + b.","A const expression. Must be inside of a block.","A const generic parameter: const LENGTH: usize.","A constant item: const MAX: u16 = 65535.","An associated constant within the definition of a trait.","An associated constant within an impl block.","A const expression. Must be inside of a block.","A const generic parameter: const LENGTH: usize.","An associated type bound: Iterator<Item: Display>.","An associated type bound: Iterator<Item: Display>.","A continue, with an optional label.","An ordinary string like "data".","A crate-level visibility: crate.","The storage of a struct, enum or union data structure.","An enum input to a proc_macro_derive macro.","A struct input to a proc_macro_derive macro.","An untagged union input to a proc_macro_derive macro.","Return type is not specified.","The * operator for dereferencing","Data structure sent to a proc_macro_derive macro.","The / operator (division)","The /= operator","An enum definition: enum Foo<A, B> { A(A), B(B) }.","An enum input to a proc_macro_derive macro.","An equality predicate in a where clause (unsupported).","The == operator (equality)","Error returned when a Syn parser cannot parse the input …","A Rust expression.","Expr without trailing semicolon.","A slice literal expression: [a, b, c, d].","An assignment expression: a = compute().","A compound assignment expression: counter += 1.","An async block: async { ... }.","An await expression: fut.await.","A binary operation: a + b, a * b.","A blocked scope: { ... }.","A box expression: box f.","A break, with an optional label to break and an optional …","A function call expression: invoke(a, b).","A cast expression: foo as f64.","A closure expression: |a, b| a + b.","A continue, with an optional label.","Access of a named struct field (obj.k) or unnamed tuple …","A for loop: for pat in expr { ... }.","An expression contained within invisible delimiters.","An if expression with an optional else block: …","A square bracketed indexing expression: vector[2].","A let guard: let Some(x) = opt.","A literal in place of an expression: 1, "foo".","Conditionless loop: loop { ... }.","A macro invocation expression: format!("{}", q).","A match expression: match n { Some(n) => {}, None => {} }.","A method call expression: x.foo::<T>(a, b).","A parenthesized expression: (a + b).","A path like std::mem::replace possibly containing generic …","A range expression: 1..2, 1.., ..2, 1..=2, ..=2.","A referencing operation: &a or &mut a.","An array literal constructed from one repeated element: …","A return, with an optional value to be returned.","A struct literal expression: Point { x: 1, y: 1 }.","A try-expression: expr?.","A try block: try { ... }.","A tuple expression: (a, b, c, d).","A type ascription expression: foo: f64.","A unary operation: !x, *x.","An unsafe block: unsafe { ... }.","A while loop: while expr { ... }.","A yield expression: yield expr.","An extern crate item: extern crate serde.","A field of a struct or enum variant.","Access of a named struct field (obj.k) or unnamed tuple …","A single field in a struct pattern.","A field-value pair in a struct literal.","Data stored within an enum variant or struct.","Named fields of a struct or struct variant such as …","Unnamed fields of a tuple struct or tuple variant such as …","A complete file of Rust source code.","A floating point literal: 1f64 or 1.0e10f64.","A free-standing function: …","A foreign function in an extern block.","An argument in a function signature: the n: usize in …","A for loop: for pat in expr { ... }.","An item within an extern block.","A foreign function in an extern block.","A macro invocation within an extern block.","A foreign static item in an extern block: static ext: u8.","A foreign type in an extern block: type void.","A block of foreign items: extern "C" { ... }.","The >= operator (greater than or equal to)","An individual generic argument, like 'a, T, or Item = T.","An individual generic argument to a method, like T.","A generic type parameter, lifetime, or const generic: …","Lifetimes and type parameters attached to a declaration of …","A glob import in a use item: *.","An expression contained within invisible delimiters.","A braced group of imports in a use item: {A, B, C}.","A type contained within invisible delimiters.","The > operator (greater than)","Inclusive at the beginning, exclusive at the end.","A word of Rust code, which may be a keyword or legal …","A pattern that binds a new variable: …","An if expression with an optional else block: …","An impl block providing trait or associated items: …","Returned by Generics::split_for_impl.","An item within an impl block.","An associated constant within an impl block.","A macro invocation within an impl block.","A method within an impl block.","An associated type within an impl block.","An impl Bound1 + Bound2 + Bound3 type where Bound is a …","The index of an unnamed tuple struct field.","A square bracketed indexing expression: vector[2].","Indication that a type should be inferred by the compiler: …","An inherited visibility, which usually means private.","","An integer literal: 1 or 1u16.","Things that can appear directly inside of a module or …","An item definition.","A constant item: const MAX: u16 = 65535.","An enum definition: enum Foo<A, B> { A(A), B(B) }.","An extern crate item: extern crate serde.","A free-standing function: …","A block of foreign items: extern "C" { ... }.","An impl block providing trait or associated items: …","A macro invocation, which includes macro_rules! …","A 2.0-style declarative macro introduced by the macro …","A module or module declaration: mod m or mod m { ... }.","A static item: static BIKE: Shed = Shed(42).","A struct definition: struct Foo<A> { x: A }.","A trait definition: pub trait Iterator { ... }.","A trait alias: pub trait SharableIterator = Iterator + Sync…","A type alias: …","A union definition: union Foo<A, B> { x: A, y: B }.","A use declaration: use std::collections::HashMap.","A lifetime labeling a for, while, or loop.","The <= operator (less than or equal to)","A let guard: let Some(x) = opt.","A Rust lifetime: 'a.","A lifetime definition: 'a: 'b + 'c + 'd.","","A lifetime predicate in a where clause: 'a: 'b + 'c.","A lifetime argument.","A lifetime definition: 'a: 'b + 'c + 'd.","A structured list within an attribute, like …","A Rust literal such as a string or integer or boolean.","A Rust literal, like the "new_name" in …","A literal in place of an expression: 1, "foo".","A literal pattern: 0.","A boolean literal: true or false.","A byte literal: b'f'.","A byte string literal: b"foo".","A character literal: 'a'.","A floating point literal: 1f64 or 1.0e10f64.","An integer literal: 1 or 1u16.","A UTF-8 string literal: "foo".","A local let binding: let x: u64 = s.parse()?.","A local (let) binding.","Conditionless loop: loop { ... }.","The < operator (less than)","A macro invocation: println!("{}", mac).","A macro invocation expression: format!("{}", q).","A macro invocation, which includes macro_rules! …","A macro invocation within an extern block.","A macro invocation within the definition of a trait.","A macro invocation within an impl block.","A macro in the type position.","A macro in pattern position.","A 2.0-style declarative macro introduced by the macro …","A grouping token that surrounds a macro body: m!(...) or …","A match expression: match n { Some(n) => {}, None => {} }.","","A struct or tuple struct field accessed in a struct …","Content of a compile-time structured attribute.","A structured meta item, like the Copy in #[derive(Copy)] …","A structured list within an attribute, like …","A name-value pair within an attribute, like …","A trait method within the definition of a trait.","A method within an impl block.","A method call expression: x.foo::<T>(a, b).","The ::<> explicit type parameters passed to a method call: …","A module or module declaration: mod m or mod m { ... }.","The * operator (multiplication)","The *= operator","An identifier imported by a use item: HashMap.","A name-value pair within an attribute, like …","Named fields of a struct or struct variant such as …","A named field like self.x.","The != operator (not equal to)","The - operator for negation","Element of a compile-time attribute list.","The never type: !.","","","The ! operator for logical inversion","The || operator (logical or)","A pattern that matches any one of a set of cases.","","A parenthesized expression: (a + b).","","A parenthesized type equivalent to the inner type.","The (A, B) -> C in Fn(A, B) -> C.","Arguments of a function path segment: the (A, B) -> C in …","A pattern in a local binding, function signature, match …","A box pattern: box v.","A pattern that binds a new variable: …","A literal pattern: 0.","A macro in pattern position.","A pattern that matches any one of a set of cases.","A path pattern like Color::Red, optionally qualified with a","A range pattern: 1..=2.","A reference pattern: &mut var.","The dots in a tuple or slice pattern: [0, 1, ..]","A dynamically sized slice pattern: [a, b, ref i @ .., y, z]…","A struct or struct variant pattern: Variant { x, y, .. }.","A tuple pattern: (a, b).","A tuple struct or tuple variant pattern: …","A type ascription pattern: foo: f64.","A pattern that matches any value: _.","A path at which a named item is exported (e.g. …","","A path like std::mem::replace possibly containing generic …","A path prefix of imports in a use item: std::....","A path like std::slice::Iter, optionally qualified with a …","A path pattern like Color::Red, optionally qualified with a","Angle bracketed or parenthesized arguments of a path …","A segment of a path together with any path arguments on …","An equality predicate in a where clause (unsupported).","A lifetime predicate in a where clause: 'a: 'b + 'c.","A type predicate in a where clause: …","A raw pointer type: *const T or *mut T.","A public visibility level: pub.","The explicit Self type in a qualified path: the T in …","A range expression: 1..2, 1.., ..2, 1..=2, ..=2.","A range pattern: 1..=2.","Limit types of a range, inclusive or exclusive.","A raw string like r##"data"##.","The self argument of an associated method, whether taken …","The self argument of an associated method, whether taken …","A referencing operation: &a or &mut a.","A reference type: &'a T or &'a mut T.","A reference pattern: &mut var.","The % operator (modulus)","The %= operator","An renamed identifier imported by a use item: …","An array literal constructed from one repeated element: …","The dots in a tuple or slice pattern: [0, 1, ..]","A visibility level restricted to some path: pub(self) or …","The result of a Syn parser.","A return, with an optional value to be returned.","Return type of a function signature.","Expression with trailing semicolon.","The << operator (shift left)","The <<= operator","The >> operator (shift right)","The >>= operator","A function signature in a trait or implementation: …","A dynamically sized slice type: [T].","A dynamically sized slice pattern: [a, b, ref i @ .., y, z]…","A static item: static BIKE: Shed = Shed(42).","A foreign static item in an extern block: static ext: u8.","A statement, usually ending in a semicolon.","A UTF-8 string literal: "foo".","The style of a string literal, either plain quoted or a …","A struct literal expression: Point { x: 1, y: 1 }.","A struct definition: struct Foo<A> { x: A }.","A struct input to a proc_macro_derive macro.","A struct or struct variant pattern: Variant { x, y, .. }.","The - operator (subtraction)","The -= operator","A type-macro that expands to the name of the Rust type …","","A trait definition: pub trait Iterator { ... }.","A trait alias: pub trait SharableIterator = Iterator + Sync…","A trait used as a bound on a type parameter.","A modifier on a trait bound, currently only used for the ? …","An item declaration within the definition of a trait.","An associated constant within the definition of a trait.","A macro invocation within the definition of a trait.","A trait method within the definition of a trait.","An associated type within the definition of a trait.","A trait object type dyn Bound1 + Bound2 + Bound3 where …","A try-expression: expr?.","A try block: try { ... }.","A tuple expression: (a, b, c, d).","A tuple type: (A, B, C, String).","A tuple pattern: (a, b).","A tuple struct or tuple variant pattern: …","Returned by TypeGenerics::as_turbofish.","The possible types that a Rust value could have.","A type ascription expression: foo: f64.","A type argument.","A generic type parameter: T: Into<String>.","A type predicate in a where clause: …","A type alias: …","A foreign type in an extern block: type void.","An associated type within the definition of a trait.","An associated type within an impl block.","A particular type is returned.","A type ascription pattern: foo: f64.","A type argument.","A fixed size array type: [T; n].","A bare function type: fn(usize) -> bool.","Returned by Generics::split_for_impl.","A type contained within invisible delimiters.","An impl Bound1 + Bound2 + Bound3 type where Bound is a …","Indication that a type should be inferred by the compiler: …","A macro in the type position.","The never type: !.","A generic type parameter: T: Into<String>.","A trait or lifetime used as a bound on a type parameter.","A parenthesized type equivalent to the inner type.","A path like std::slice::Iter, optionally qualified with a …","A raw pointer type: *const T or *mut T.","A reference type: &'a T or &'a mut T.","A dynamically sized slice type: [T].","A trait object type dyn Bound1 + Bound2 + Bound3 where …","A tuple type: (A, B, C, String).","A function argument accepted by pattern and type.","A unary operator: *, !, -.","A unary operation: !x, *x.","A union definition: union Foo<A, B> { x: A, y: B }.","An untagged union input to a proc_macro_derive macro.","Unit struct or unit variant such as None.","Unnamed fields of a tuple struct or tuple variant such as …","An unnamed field like self.0.","An unsafe block: unsafe { ... }.","A use declaration: use std::collections::HashMap.","A glob import in a use item: *.","A braced group of imports in a use item: {A, B, C}.","An identifier imported by a use item: HashMap.","A path prefix of imports in a use item: std::....","An renamed identifier imported by a use item: …","A suffix of an import tree in a use item: Type as Renamed …","The variadic argument of a foreign function.","An enum variant.","Tokens in expression position not interpreted by Syn.","Tokens forming an item not interpreted by Syn.","Tokens in an extern block not interpreted by Syn.","Tokens within the definition of a trait not interpreted by …","Tokens within an impl block not interpreted by Syn.","A raw token literal not interpreted by Syn.","Tokens in type position not interpreted by Syn.","Tokens in pattern position not interpreted by Syn.","A crate-level visibility: crate.","A public visibility level: pub.","A visibility level restricted to some path: pub(self) or …","The visibility level of an item: inherited or pub or …","A where clause in a definition: …","A single predicate in a where clause: T: Deserialize<'de>.","A while loop: while expr { ... }.","A pattern that matches any value: _.","A yield expression: yield expr.","","","","","","","","","","","","","","","","","Turn a type’s generics like <X, Y> into a turbofish like …","","","","Attributes tagged on the variant.","Attributes tagged on the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attributes tagged on the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attributes tagged on the whole struct or enum.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parses the literal into a selected number type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The type being bounded","","","Trait and lifetime bounds (Clone+Send+'static)","","","","","","","","","","","","","","","","","","","","Parse a set of curly braces and expose their content to …","","","","","","","","Parse a set of square brackets and expose their content to …","","A stably addressed token buffer supporting efficient …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The colon in Struct { x: x }. If written in shorthand like …","","","","","","","","","","","","","","","","","","","","Returns an Iterator<Item = &ConstParam> over the constant …","Returns an Iterator<Item = &mut ConstParam> over the …","","","","","","","","","","","Define a type that supports parsing and printing a given …","Define a type that supports parsing and printing a …","Data within the struct or enum.","","","","","","","","","","","","","","Explicit discriminant: Variant = 1","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Value of the field.","","","","","Extension traits to provide parsing methods on foreign …","","","","Content stored in the variant.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","Generics required to complete the definition.","If this path consists of a single ident, returns the ident.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Name of the variant.","Name of the field, if any.","","","","","","The example in macro_rules! example { ... }.","","","","","","","","","","","","","","","","","","","Name of the struct or enum.","","","","","","","","","","","","","","","","(A, B)","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if there are zero fields.","","Determines whether this is a path of length 1 equal to the …","","","","","","","Get an iterator over the borrowed Field items in this …","Get an iterator over the mutably borrowed Field items in …","","","","","","","","","","","","","Returns the number of fields.","","","","","","","","","","Returns an Iterator<Item = &LifetimeDef> over the lifetime …","","The for<'a> in for<'a> Foo<&'a T>","Any lifetimes from a for binding","","Returns an Iterator<Item = &mut LifetimeDef> over the …","","","","","","","","","","","","","","","","","","","","Initializes an empty where-clause if there is not one …","","","Name or index of the field.","","","","","","","","","","","","","","","","","","","Creates a new Ident with the given string as well as the …","Panics","","Interpret a Syn literal from a proc-macro2 literal.","","","","","","","","Same as Ident::new, but creates a raw identifier (r#ident…","","","","","","","","","C","","","","","","","","","","","","","","","","Parse a set of parentheses and expose their content to …","Parsing interface for parsing a token stream into a syntax …","Parse tokens of source code into the chosen syntax tree …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parse a syntax tree node from the content of this string …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parse a proc-macro2 token stream into the chosen syntax …","","Parse the arguments to the attribute as a syntax tree.","Parse the arguments to the attribute using the given …","Parse the tokens within the macro invocation’s …","Parse the tokens within the macro invocation’s …","Parse the content of a file of Rust code.","Parses zero or more inner attributes from the stream.","Parse the input TokenStream of a macro, triggering a …","Parses the content of the attribute, consisting of the …","Parse a Path containing no path arguments on any of its …","Parses a named (braced struct) field.","Parses zero or more outer attributes from the stream.","Quasi-quotation macro that accepts input like the quote! …","This macro is parse_quote! + quote_spanned!.","Parse a string of Rust code into the chosen syntax tree …","Parses an unnamed (tuple struct) field.","Invoke parser on the content of this string literal.","Parse the body of a block as zero or more statements, …","An alternative to the primary Expr::parse parser (from the …","","","","","","","","","","","","Returns the identifier that begins this structured meta …","","","","","","","The Foo<&'a T> in for<'a> Foo<&'a T>","","","","","","","","","","","A punctuated sequence of syntax tree nodes separated by …","","","","","","A method’s self receiver, such as &self or …","","","","","","","","","","","","","","The Self type of the impl.","","","","","","","","","","","","","","","","","","","","","","","","","Configures the span of this Ident, possibly changing its …","","","","","","","","","","","","","","","Returns the span of this Ident.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A trait that can provide the Span of the complete contents …","Split a type’s generics into the pieces required for impl…","","","","","Statements in a block","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tokens representing Rust punctuation, keywords, and …","","","","","","","","","","Trait this impl implements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Type of the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns an Iterator<Item = &TypeParam> over the type …","Returns an Iterator<Item = &mut TypeParam> over the type …","","","","","","","","","","","","","","","","","","","","","","","","","","","Visibility of the field.","","","","","","","","","","","","","","","","","","","","Visibility of the struct or enum.","Syntax tree traversal to mutate an exclusive borrow of a …","","","","In some positions, types may not contain the + character, …","","","","","A cheaply copyable cursor into a TokenBuffer.","A buffer that can be efficiently traversed multiple times, …","Creates a cursor referencing the first token in the buffer …","","","","","","","Creates a cursor referencing a static empty TokenStream.","Checks whether the cursor is currently pointing at the end …","","Returns the argument unchanged.","Returns the argument unchanged.","If the cursor is pointing at a Group with the given …","If the cursor is pointing at a Ident, returns it along …","Calls U::from(self).","Calls U::from(self).","If the cursor is pointing at a Lifetime, returns it along …","If the cursor is pointing at a Literal, return it along …","Creates a TokenBuffer containing all the tokens from the …","Creates a TokenBuffer containing all the tokens from the …","","If the cursor is pointing at a Punct, returns it along …","Returns the Span of the current token, or Span::call_site()…","","Copies all remaining tokens visible from this cursor into a","If the cursor is pointing at a TokenTree, returns it along …","","","","","","","Additional methods for Ident not provided by proc-macro2 …","Parses any identifier including keywords.","Peeks any identifier including keywords. Usage: …","Strips the raw marker r#, if any, from the beginning of an …","Error returned when a Syn parser cannot parse the input …","Support for checking the next token in a stream to decide …","An empty syntax tree node that consumes no tokens when …","","Parsing interface implemented by all types that can be …","Cursor position within a buffered token stream.","Input to a Syn parser function.","Parser that can parse Rust tokens into a particular syntax …","Types that can be parsed by looking at just one token.","The result of a Syn parser.","Cursor state associated with speculative parsing.","","","","","","","","","","","","Calls the given parser function to parse a syntax tree …","","","","","Add another error message to self such that when …","Provides low-level access to the token representation …","","Extensions to the parsing API with niche applicability.","","","Triggers an error at the current position of the parse …","Triggers an error at the current position of the parse …","Triggers an error at the current position of the parse …","","","","","","","Forks a parse stream so that parsing tokens out of either …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Render the error as an invocation of compile_error!.","","","Returns whether there are tokens remaining in this stream.","Constructs a helper for peeking at the next token in this …","Usually the ParseStream::error method will be used …","Creates an error with the specified message spanning the …","","Parses a syntax tree node of type T, advancing the …","Parse tokens of source code into the chosen syntax tree …","","Parse a proc-macro2 token stream into the chosen syntax …","Parse a string of Rust code into the chosen syntax tree …","Parses zero or more occurrences of T separated by …","Looks at the next token in the parse stream to determine …","Looks at the next token in the parse stream to determine …","Looks at the second-next token in the parse stream.","Looks at the third-next token in the parse stream.","","Returns the Span of the next token in the parse stream, or …","The source location of the error.","Speculatively parses tokens from this parse stream, …","Render the error as an invocation of compile_error!.","","","","","","","","","","","","","","","","","","","","Extensions to the ParseStream API to support speculative …","Advance this parse stream to the position of a forked …","","An iterator over owned values of type T.","An iterator over owned pairs of type Pair<T, P>.","An iterator over borrowed values of type &T.","An iterator over mutably borrowed values of type &mut T.","A single syntax tree node of type T followed by its …","An iterator over borrowed pairs of type Pair<&T, &P>.","An iterator over mutably borrowed pairs of type …","A punctuated sequence of syntax tree nodes of type T …","","","","","","","","","","","","","","","","","","Clears the sequence of all values and punctuation, making …","","","","","","","","","","","","","","Returns true if either this Punctuated is empty, or it has …","","","","Borrows the first element in this sequence.","Mutably borrows the first element in this sequence.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Inserts an element at position index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","Returns an iterator over the contents of this sequence as …","Produces this punctuated pair as a tuple of syntax tree …","Extracts the syntax tree node from this punctuated pair, …","Determines whether this punctuated sequence is empty, …","Returns an iterator over borrowed syntax tree nodes of …","Returns an iterator over mutably borrowed syntax tree …","Borrows the last element in this sequence.","Mutably borrows the last element in this sequence.","Returns the number of syntax tree nodes in this punctuated …","","","","","","","Creates an empty punctuated sequence.","Creates a punctuated pair out of a syntax tree node and an …","","","","","","","","","","","","","Returns an iterator over the contents of this sequence as …","Returns an iterator over the contents of this sequence as …","Parses one or more occurrences of T separated by …","Parses one or more occurrences of T using the given parse …","Parses zero or more occurrences of T separated by …","Parses zero or more occurrences of T using the given parse …","Removes the last punctuated pair from this sequence, or …","Borrows the punctuation from this punctuated pair, unless …","Mutably borrows the punctuation from this punctuated pair, …","Appends a syntax tree node onto the end of this punctuated …","Appends a trailing punctuation onto the end of this …","Appends a syntax tree node onto the end of this punctuated …","","","","","","","","","","","","","","","","","Determines whether this punctuated sequence ends with a …","","","","","","","","","","","","","","","","","","","","","","","","","Borrows the syntax tree node from this punctuated pair.","Mutably borrows the syntax tree node from this punctuated …","A trait that can provide the Span of the complete contents …","Returns a Span covering the complete contents of this …","abstract","+","+=","&","&&","&=","as","async","@","auto","await","!","become","box","{...}","[...]","break","^","^=",":","::",",","const","continue","crate","default","/","/=","do","$",".","..","...","..=","dyn","else","enum","=","==","extern","=>","final","fn","for",">=","None-delimited group",">","if","impl","in","<-","<=","let","loop","<","macro","match","mod","move","*=","mut","!=","|","|=","||","override","(...)","#","priv","pub","?","->","ref","%","%=","return","Self","self",";","<<","<<=",">>",">>=","*","static","struct","-","-=","super","~","Marker trait for types that represent single tokens.","trait","try","type","typeof","_","union","unsafe","unsized","use","virtual","where","while","yield","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Syntax tree traversal to mutate an exclusive borrow of a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,142,142,142,185,0,0,25,147,25,25,25,0,0,0,25,147,0,0,25,0,186,142,142,142,142,142,142,0,25,129,0,25,166,136,136,25,129,129,25,25,129,72,25,68,74,87,115,120,186,0,0,186,25,338,21,0,0,0,0,165,143,0,142,142,87,138,83,142,0,0,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,87,0,25,0,0,0,0,0,0,129,87,110,0,25,0,0,0,0,0,87,142,0,0,0,0,104,25,104,147,142,72,0,166,25,87,0,0,0,0,0,0,147,0,25,147,21,11,129,0,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,25,0,74,79,83,186,0,12,0,15,25,166,0,0,0,0,0,0,0,0,145,25,142,0,25,87,110,115,120,147,166,87,0,25,81,0,0,15,0,0,115,120,25,0,87,142,142,104,12,17,65,142,143,0,147,81,185,143,142,166,11,25,136,147,185,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,25,104,147,166,0,0,0,0,0,147,21,0,25,166,0,338,0,126,25,147,166,142,142,104,25,166,21,0,25,0,145,142,142,142,142,0,147,166,87,110,0,129,0,25,87,138,166,142,142,0,79,87,87,0,0,0,0,0,0,0,147,25,25,25,147,166,166,0,0,25,68,74,83,87,110,115,120,165,166,186,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,25,87,138,17,17,65,25,87,0,0,0,0,0,0,0,0,25,87,110,115,120,129,147,166,0,0,0,0,0,0,25,166,25,92,125,149,53,158,174,9,35,49,67,187,184,48,36,107,191,1,29,37,125,16,20,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,69,71,75,76,77,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,111,112,113,114,116,117,118,119,121,122,123,124,127,128,137,146,163,164,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,99,30,135,154,30,39,3,5,3,5,29,32,58,62,91,122,37,40,46,63,71,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,84,75,76,84,85,100,118,151,160,189,33,167,18,48,56,89,92,93,99,109,140,144,177,0,10,26,43,54,148,159,176,0,34,0,168,29,37,171,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,9,67,105,187,20,60,69,70,75,76,77,84,85,88,97,99,112,116,118,121,180,182,189,71,42,63,73,73,77,88,116,121,157,125,96,38,23,90,0,0,137,73,78,185,75,77,116,117,118,93,121,122,123,135,16,56,175,177,30,39,49,164,160,148,150,155,157,158,159,26,59,161,176,178,42,89,140,7,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,14,27,44,75,77,86,88,97,100,101,121,123,188,33,34,36,40,41,43,44,48,50,53,54,55,57,60,61,64,69,88,97,121,169,0,90,162,71,16,56,98,102,139,141,177,7,7,7,8,1,2,9,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,3,5,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,125,149,40,78,7,7,7,7,7,7,7,8,1,2,9,10,11,12,12,12,12,13,14,15,15,15,16,17,17,17,18,19,20,21,21,21,21,22,23,24,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,65,65,66,66,67,68,69,70,71,72,73,74,74,74,74,75,75,76,77,78,79,79,79,80,81,82,83,83,83,83,84,85,86,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,104,104,104,104,104,105,106,107,108,109,110,110,110,110,110,111,112,113,114,115,115,115,115,115,116,117,118,119,120,120,120,120,120,121,122,123,124,125,126,126,126,127,128,129,129,129,129,129,129,129,129,130,131,132,133,3,3,5,5,134,338,135,136,137,137,137,137,138,138,138,138,139,140,141,142,143,144,145,146,147,147,147,147,147,147,147,147,147,147,147,147,147,147,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,183,184,184,185,186,187,188,189,190,191,52,35,89,93,98,99,100,101,102,118,123,125,137,183,41,150,67,73,78,187,191,71,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,173,9,16,20,75,77,88,89,90,94,95,96,97,98,99,100,101,102,105,106,107,112,113,116,118,121,123,125,137,168,184,188,189,42,93,151,24,40,43,66,146,37,125,149,190,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,17,17,17,17,185,183,185,92,93,99,109,128,17,17,32,34,38,40,46,63,103,183,171,27,28,31,17,54,148,44,146,86,127,76,85,158,73,78,80,84,149,73,52,173,14,45,173,46,67,73,78,187,191,47,94,114,119,124,153,170,95,73,48,39,69,182,49,96,80,37,53,97,112,127,157,158,168,174,70,162,163,18,13,7,9,76,129,130,131,132,133,3,5,134,7,28,31,61,37,37,37,125,149,190,73,13,19,24,35,49,50,59,80,125,149,155,161,178,190,0,0,0,7,9,12,13,14,15,16,18,19,21,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,130,131,132,133,3,5,134,135,137,142,143,144,145,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,165,166,183,184,186,187,188,189,190,0,7,10,10,135,135,0,10,0,10,183,20,10,0,0,0,20,130,144,25,7,9,40,44,71,146,167,174,179,180,182,12,10,13,14,24,51,56,80,135,156,172,177,179,191,10,82,22,24,0,51,156,172,57,53,125,49,127,90,107,56,55,86,27,28,31,95,183,127,93,96,54,88,90,94,97,98,100,101,103,111,112,113,114,116,117,118,119,121,123,124,139,146,148,7,9,129,130,131,132,133,3,5,134,128,91,111,117,122,7,7,7,8,1,2,9,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129,130,130,131,131,132,132,133,133,3,3,5,5,134,134,135,137,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,66,134,0,73,108,157,97,112,144,98,139,10,168,129,130,131,132,133,3,5,99,42,52,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,9,3,5,7,8,1,2,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,137,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,0,130,131,132,133,3,5,134,10,135,93,99,100,103,105,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,58,49,20,36,60,77,88,97,101,112,116,121,123,163,180,188,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,73,73,101,113,118,123,152,181,102,141,19,7,62,93,99,125,149,103,130,131,132,133,134,134,125,149,89,140,20,88,89,90,91,95,96,97,98,99,100,101,102,103,111,112,113,121,122,123,137,0,73,82,63,147,151,160,165,64,0,0,220,220,221,220,221,221,221,221,221,221,220,221,221,221,220,221,221,221,220,220,221,221,221,221,221,221,220,221,220,221,220,221,0,339,339,339,0,0,0,216,0,0,0,0,0,0,0,223,227,223,224,226,225,227,223,224,226,225,223,224,225,224,225,225,223,224,0,223,226,227,223,224,225,223,223,226,225,225,223,227,223,224,226,225,225,226,227,223,224,226,225,225,225,225,223,223,225,225,213,223,216,226,216,216,223,227,223,223,223,225,223,225,223,225,224,225,223,225,227,223,224,226,225,227,223,224,226,225,227,223,224,226,225,0,340,238,0,0,0,0,0,0,0,0,238,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,232,232,235,236,237,207,238,232,235,236,237,207,238,232,232,232,232,232,232,232,232,232,235,240,236,237,207,208,238,232,232,232,232,232,232,232,235,240,236,237,207,208,238,232,232,232,235,240,236,237,207,208,232,238,238,232,232,232,232,232,232,235,240,236,237,207,208,232,238,235,240,236,237,207,208,235,240,236,237,207,208,232,232,232,232,232,232,232,238,238,232,232,232,235,240,236,237,207,208,232,238,232,235,236,237,207,238,232,238,232,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,238,238,0,341,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,288,290,293,294,295,297,299,300,302,303,307,310,312,315,318,319,322,325,330,331,333,199,288,290,293,294,295,297,299,300,302,303,307,310,312,315,318,319,322,325,330,331,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,334,335,336,337,199,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,0,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[3,4],[5,4],[3,6],[5,6],0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[7,7],[8,8],[1,1],[2,2],[9,9],[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[61,61],[62,62],[63,63],[64,64],[65,65],[66,66],[67,67],[68,68],[69,69],[70,70],[71,71],[72,72],[73,73],[74,74],[75,75],[76,76],[77,77],[78,78],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[85,85],[86,86],[87,87],[88,88],[89,89],[90,90],[91,91],[92,92],[93,93],[94,94],[95,95],[96,96],[97,97],[98,98],[99,99],[100,100],[101,101],[102,102],[103,103],[104,104],[105,105],[106,106],[107,107],[108,108],[109,109],[110,110],[111,111],[112,112],[113,113],[114,114],[115,115],[116,116],[117,117],[118,118],[119,119],[120,120],[121,121],[122,122],[123,123],[124,124],[125,125],[126,126],[127,127],[128,128],[129,129],[130,130],[131,131],[132,132],[133,133],[3,3],[5,5],[134,134],[135,135],[136,136],[137,137],[138,138],[139,139],[140,140],[141,141],[142,142],[143,143],[144,144],[145,145],[146,146],[147,147],[148,148],[149,149],[150,150],[151,151],[152,152],[153,153],[154,154],[155,155],[156,156],[157,157],[158,158],[159,159],[160,160],[161,161],[162,162],[163,163],[164,164],[165,165],[166,166],[167,167],[168,168],[169,169],[170,170],[171,171],[172,172],[173,173],[174,174],[175,175],[176,176],[177,177],[178,178],[179,179],[180,180],[181,181],[182,182],[183,183],[184,184],[185,185],[186,186],[187,187],[188,188],[189,189],[190,190],[191,191],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[7,7],192],[[9,9],192],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],73],[[],78],[[],185],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,7],193],[7,193],[[8,8],193],[[1,1],193],[[2,2],193],[[9,9],193],[[10,10],193],[[11,11],193],[[12,12],193],[[13,13],193],[[14,14],193],[[15,15],193],[[16,16],193],[[17,17],193],[[18,18],193],[[19,19],193],[[20,20],193],[[21,21],193],[[22,22],193],[[23,23],193],[[24,24],193],[[25,25],193],[[26,26],193],[[27,27],193],[[28,28],193],[[29,29],193],[[30,30],193],[[31,31],193],[[32,32],193],[[33,33],193],[[34,34],193],[[35,35],193],[[36,36],193],[[37,37],193],[[38,38],193],[[39,39],193],[[40,40],193],[[41,41],193],[[42,42],193],[[43,43],193],[[44,44],193],[[45,45],193],[[46,46],193],[[47,47],193],[[48,48],193],[[49,49],193],[[50,50],193],[[51,51],193],[[52,52],193],[[53,53],193],[[54,54],193],[[55,55],193],[[56,56],193],[[57,57],193],[[58,58],193],[[59,59],193],[[60,60],193],[[61,61],193],[[62,62],193],[[63,63],193],[[64,64],193],[[65,65],193],[[66,66],193],[[67,67],193],[[68,68],193],[[69,69],193],[[70,70],193],[[71,71],193],[[72,72],193],[[73,73],193],[[74,74],193],[[75,75],193],[[76,76],193],[[77,77],193],[[78,78],193],[[79,79],193],[[80,80],193],[[81,81],193],[[82,82],193],[[83,83],193],[[84,84],193],[[85,85],193],[[86,86],193],[[87,87],193],[[88,88],193],[[89,89],193],[[90,90],193],[[91,91],193],[[92,92],193],[[93,93],193],[[94,94],193],[[95,95],193],[[96,96],193],[[97,97],193],[[98,98],193],[[99,99],193],[[100,100],193],[[101,101],193],[[102,102],193],[[103,103],193],[[104,104],193],[[105,105],193],[[106,106],193],[[107,107],193],[[108,108],193],[[109,109],193],[[110,110],193],[[111,111],193],[[112,112],193],[[113,113],193],[[114,114],193],[[115,115],193],[[116,116],193],[[117,117],193],[[118,118],193],[[119,119],193],[[120,120],193],[[121,121],193],[[122,122],193],[[123,123],193],[[124,124],193],[[125,125],193],[[126,126],193],[[127,127],193],[[128,128],193],[[129,129],193],[[130,130],193],[[131,131],193],[[132,132],193],[[133,133],193],[[3,3],193],[[5,5],193],[[134,134],193],[[135,135],193],[[136,136],193],[[137,137],193],[[138,138],193],[[139,139],193],[[140,140],193],[[141,141],193],[[142,142],193],[[143,143],193],[[144,144],193],[[145,145],193],[[146,146],193],[[147,147],193],[[148,148],193],[[149,149],193],[[150,150],193],[[151,151],193],[[152,152],193],[[153,153],193],[[154,154],193],[[155,155],193],[[156,156],193],[[157,157],193],[[158,158],193],[[159,159],193],[[160,160],193],[[161,161],193],[[162,162],193],[[163,163],193],[[164,164],193],[[165,165],193],[[166,166],193],[[167,167],193],[[168,168],193],[[169,169],193],[[170,170],193],[[171,171],193],[[172,172],193],[[173,173],193],[[174,174],193],[[175,175],193],[[176,176],193],[[177,177],193],[[178,178],193],[[179,179],193],[[180,180],193],[[181,181],193],[[182,182],193],[[183,183],193],[[184,184],193],[[185,185],193],[[186,186],193],[[187,187],193],[[188,188],193],[[189,189],193],[[190,190],193],[[191,191],193],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,194],[[196,[195]]]],[[7,194],[[196,[195]]]],[[7,194],[[196,[195]]]],[[8,194],197],[[1,194],197],[[2,194],197],[[9,194],197],[[9,194],197],[[10,194],197],[[11,194],197],[[12,194],197],[[13,194],197],[[14,194],197],[[15,194],197],[[16,194],197],[[17,194],197],[[18,194],197],[[19,194],197],[[20,194],197],[[21,194],197],[[22,194],197],[[23,194],197],[[24,194],197],[[25,194],197],[[26,194],197],[[27,194],197],[[28,194],197],[[29,194],197],[[30,194],197],[[31,194],197],[[32,194],197],[[33,194],197],[[34,194],197],[[35,194],197],[[36,194],197],[[37,194],197],[[38,194],197],[[39,194],197],[[40,194],197],[[41,194],197],[[42,194],197],[[43,194],197],[[44,194],197],[[45,194],197],[[46,194],197],[[47,194],197],[[48,194],197],[[49,194],197],[[50,194],197],[[51,194],197],[[52,194],197],[[53,194],197],[[54,194],197],[[55,194],197],[[56,194],197],[[57,194],197],[[58,194],197],[[59,194],197],[[60,194],197],[[61,194],197],[[62,194],197],[[63,194],197],[[64,194],197],[[65,194],197],[[65,194],197],[[66,194],197],[[66,194],197],[[67,194],197],[[68,194],197],[[69,194],197],[[70,194],197],[[71,194],197],[[72,194],197],[[73,194],197],[[74,194],197],[[75,194],197],[[76,194],197],[[77,194],197],[[78,194],197],[[79,194],197],[[80,194],197],[[81,194],197],[[82,194],197],[[83,194],197],[[84,194],197],[[85,194],197],[[86,194],197],[[87,194],197],[[88,194],197],[[89,194],197],[[90,194],197],[[91,194],197],[[92,194],197],[[93,194],197],[[94,194],197],[[95,194],197],[[96,194],197],[[97,194],197],[[98,194],197],[[99,194],197],[[100,194],197],[[101,194],197],[[102,194],197],[[103,194],197],[[104,194],197],[[105,194],197],[[106,194],197],[[107,194],197],[[108,194],197],[[109,194],197],[[110,194],197],[[111,194],197],[[112,194],197],[[113,194],197],[[114,194],197],[[115,194],197],[[116,194],197],[[117,194],197],[[118,194],197],[[119,194],197],[[120,194],197],[[121,194],197],[[122,194],197],[[123,194],197],[[124,194],197],[[125,194],197],[[126,194],197],[[127,194],197],[[128,194],197],[[129,194],197],[[130,194],197],[[131,194],197],[[132,194],197],[[133,194],197],[[3,194],197],[[3,194],197],[[5,194],197],[[5,194],197],[[134,194],197],[[135,194],197],[[136,194],197],[[137,194],197],[[138,194],197],[[139,194],197],[[140,194],197],[[141,194],197],[[142,194],197],[[143,194],197],[[144,194],197],[[145,194],197],[[146,194],197],[[147,194],197],[[148,194],197],[[149,194],197],[[150,194],197],[[151,194],197],[[152,194],197],[[153,194],197],[[154,194],197],[[155,194],197],[[156,194],197],[[157,194],197],[[158,194],197],[[159,194],197],[[160,194],197],[[161,194],197],[[162,194],197],[[163,194],197],[[164,194],197],[[165,194],197],[[166,194],197],[[167,194],197],[[168,194],197],[[169,194],197],[[170,194],197],[[171,194],197],[[172,194],197],[[173,194],197],[[174,194],197],[[175,194],197],[[176,194],197],[[177,194],197],[[178,194],197],[[179,194],197],[[180,194],197],[[181,194],197],[[182,194],197],[[183,194],197],[[184,194],197],[[185,194],197],[[186,194],197],[[187,194],197],[[188,194],197],[[189,194],197],[[190,194],197],[[191,194],197],0,0,0,0,[198,7],[199,7],[200,7],[201,7],[202,7],[203,7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[183,12],[13,12],[14,12],[[]],[[]],[[]],[12,15],[129,15],[[]],[[]],[18,17],[19,17],[[]],[[]],[[]],[[]],[23,21],[24,21],[[]],[22,21],[[]],[[]],[[]],[26,25],[35,25],[62,25],[63,25],[64,25],[30,25],[29,25],[51,25],[27,25],[55,25],[31,25],[56,25],[32,25],[54,25],[53,25],[52,25],[50,25],[49,25],[[]],[48,25],[47,25],[60,25],[59,25],[58,25],[57,25],[46,25],[33,25],[40,25],[61,25],[34,25],[45,25],[28,25],[36,25],[37,25],[38,25],[44,25],[39,25],[41,25],[43,25],[42,25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[204,65],[[]],[66,65],[7,65],[204,66],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[76,74],[77,74],[75,74],[[]],[7,75],[[]],[[]],[[]],[[]],[9,79],[[]],[80,79],[[]],[[]],[[]],[86,83],[85,83],[84,83],[[]],[[]],[[]],[[]],[89,87],[95,87],[98,87],[99,87],[88,87],[100,87],[101,87],[90,87],[91,87],[92,87],[102,87],[[]],[93,87],[94,87],[103,87],[97,87],[137,87],[96,87],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[107,104],[108,104],[109,104],[106,104],[[]],[105,104],[[]],[[]],[[]],[[]],[[]],[114,110],[113,110],[112,110],[[]],[111,110],[[]],[[]],[[]],[[]],[[]],[116,115],[119,115],[118,115],[117,115],[[]],[[]],[[]],[[]],[[]],[122,120],[123,120],[124,120],[121,120],[[]],[[]],[[]],[[]],[[]],[180,126],[127,126],[[]],[[]],[[]],[134,129],[[]],[132,129],[133,129],[3,129],[131,129],[5,129],[130,129],[[]],[[]],[[]],[[]],[205,3],[[]],[205,5],[[]],[[]],[[]],[[]],[[]],[89,137],[[]],[102,137],[98,137],[141,138],[140,138],[139,138],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[159,147],[161,147],[160,147],[[]],[155,147],[158,147],[157,147],[156,147],[153,147],[154,147],[148,147],[149,147],[150,147],[151,147],[152,147],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[171,166],[168,166],[181,166],[180,166],[179,166],[178,166],[177,166],[176,166],[169,166],[175,166],[170,166],[174,166],[173,166],[167,166],[[]],[172,166],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],183],[[]],[[],184],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,[183,[[206,[7]]]],0,0,0,0,0,0,0,0,[7],[8],[1],[2],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31],[32],[33],[34],[35],[36],[37],[38],[39],[40],[41],[42],[43],[44],[45],[46],[47],[48],[49],[50],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60],[61],[62],[63],[64],[65],[66],[67],[68],[69],[70],[71],[72],[73],[74],[75],[76],[77],[78],[79],[80],[81],[82],[83],[84],[85],[86],[87],[88],[89],[90],[91],[92],[93],[94],[95],[96],[97],[98],[99],[100],[101],[102],[103],[104],[105],[106],[107],[108],[109],[110],[111],[112],[113],[114],[115],[116],[117],[118],[119],[120],[121],[122],[123],[124],[125],[126],[127],[128],[129],[130],[131],[132],[133],[3],[5],[134],[135],[136],[137],[138],[139],[140],[141],[142],[143],[144],[145],[146],[147],[148],[149],[150],[151],[152],[153],[154],[155],[156],[157],[158],[159],[160],[161],[162],[163],[164],[165],[166],[167],[168],[169],[170],[171],[172],[173],[174],[175],[176],[177],[178],[179],[180],[181],[182],[183],[184],[185],[186],[187],[188],[189],[190],[191],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[17],[17],[17],[17,193],[185,193],[183,193],[185,193],0,0,0,0,0,[17,[[207,[20]]]],[17,[[208,[20]]]],0,0,0,0,0,0,0,0,0,0,0,0,[17,204],0,0,0,0,0,[127,[[206,[9]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[73,82],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[4,209],7],[[4,209],9],[9,76],[205,129],[[4,209],130],[209,131],[[210,209],132],[[211,209],133],[[4,209],3],[[4,209],5],[[193,209],134],[[4,209],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[212,[[6,[213]]]],[214,[[6,[7]]]],[214,[[6,[9]]]],[214,[[6,[12]]]],[214,[[6,[13]]]],[214,[[6,[14]]]],[214,[[6,[15]]]],[214,[[6,[16]]]],[214,[[6,[18]]]],[214,[[6,[19]]]],[214,[[6,[21]]]],[214,[[6,[25]]]],[214,[[6,[26]]]],[214,[[6,[27]]]],[214,[[6,[28]]]],[214,[[6,[29]]]],[214,[[6,[30]]]],[214,[[6,[31]]]],[214,[[6,[32]]]],[214,[[6,[33]]]],[214,[[6,[34]]]],[214,[[6,[35]]]],[214,[[6,[36]]]],[214,[[6,[37]]]],[214,[[6,[38]]]],[214,[[6,[39]]]],[214,[[6,[40]]]],[214,[[6,[42]]]],[214,[[6,[43]]]],[214,[[6,[44]]]],[214,[[6,[45]]]],[214,[[6,[46]]]],[214,[[6,[47]]]],[214,[[6,[48]]]],[214,[[6,[49]]]],[214,[[6,[50]]]],[214,[[6,[51]]]],[214,[[6,[52]]]],[214,[[6,[53]]]],[214,[[6,[54]]]],[214,[[6,[55]]]],[214,[[6,[56]]]],[214,[[6,[57]]]],[214,[[6,[58]]]],[214,[[6,[59]]]],[214,[[6,[60]]]],[214,[[6,[61]]]],[214,[[6,[62]]]],[214,[[6,[63]]]],[214,[[6,[64]]]],[214,[[6,[65]]]],[214,[[6,[66]]]],[214,[[6,[67]]]],[214,[[6,[68]]]],[214,[[6,[69]]]],[214,[[6,[70]]]],[214,[[6,[71]]]],[214,[[6,[72]]]],[214,[[6,[73]]]],[214,[[6,[74]]]],[214,[[6,[75]]]],[214,[[6,[76]]]],[214,[[6,[77]]]],[214,[[6,[78]]]],[214,[[6,[79]]]],[214,[[6,[80]]]],[214,[[6,[81]]]],[214,[[6,[82]]]],[214,[[6,[83]]]],[214,[[6,[87]]]],[214,[[6,[88]]]],[214,[[6,[89]]]],[214,[[6,[90]]]],[214,[[6,[91]]]],[214,[[6,[92]]]],[214,[[6,[93]]]],[214,[[6,[94]]]],[214,[[6,[95]]]],[214,[[6,[96]]]],[214,[[6,[97]]]],[214,[[6,[98]]]],[214,[[6,[99]]]],[214,[[6,[100]]]],[214,[[6,[101]]]],[214,[[6,[102]]]],[214,[[6,[103]]]],[214,[[6,[104]]]],[214,[[6,[110]]]],[214,[[6,[111]]]],[214,[[6,[112]]]],[214,[[6,[113]]]],[214,[[6,[114]]]],[214,[[6,[115]]]],[214,[[6,[116]]]],[214,[[6,[117]]]],[214,[[6,[118]]]],[214,[[6,[119]]]],[214,[[6,[120]]]],[214,[[6,[121]]]],[214,[[6,[122]]]],[214,[[6,[123]]]],[214,[[6,[124]]]],[214,[[6,[125]]]],[214,[[6,[126]]]],[214,[[6,[127]]]],[214,[[6,[128]]]],[214,[[6,[129]]]],[130,[[6,[213]]]],[214,[[6,[130]]]],[214,[[6,[131]]]],[214,[[6,[132]]]],[214,[[6,[133]]]],[214,[[6,[3]]]],[214,[[6,[5]]]],[214,[[6,[134]]]],[214,[[6,[135]]]],[214,[[6,[137]]]],[214,[[6,[142]]]],[214,[[6,[143]]]],[214,[[6,[144]]]],[214,[[6,[145]]]],[214,[[6,[147]]]],[214,[[6,[148]]]],[214,[[6,[149]]]],[214,[[6,[150]]]],[214,[[6,[151]]]],[214,[[6,[152]]]],[214,[[6,[153]]]],[214,[[6,[154]]]],[214,[[6,[155]]]],[214,[[6,[156]]]],[214,[[6,[157]]]],[214,[[6,[158]]]],[214,[[6,[159]]]],[214,[[6,[160]]]],[214,[[6,[161]]]],[214,[[6,[162]]]],[214,[[6,[163]]]],[214,[[6,[165]]]],[214,[[6,[166]]]],[214,[[6,[183]]]],[214,[[6,[184]]]],[214,[[6,[186]]]],[214,[[6,[187]]]],[214,[[6,[188]]]],[214,[[6,[189]]]],[214,[[6,[190]]]],[215,[[6,[213]]]],[214,[[6,[7]]]],[10,[[6,[213]]]],[[10,216],6],[135,[[6,[213]]]],[[135,216],6],[4,[[6,[128]]]],[214,[[6,[[217,[10]]]]]],0,[10,[[6,[12]]]],[214,[[6,[183]]]],[214,[[6,[20]]]],[214,[[6,[[217,[10]]]]]],0,0,[4,[[6,[213]]]],[214,[[6,[20]]]],[[130,216],6],[214,[[6,[[217,[145]]]]]],[214,[[6,[25]]]],[[7,7],[[206,[192]]]],[[9,9],[[206,[192]]]],0,0,0,0,0,0,0,0,0,[12,183],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[125,[[206,[126]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,209]],[[9,209]],[[129,209]],[[130,209]],[[131,209]],[[132,209]],[[133,209]],[[3,209]],[[5,209]],[[134,209]],0,0,0,0,0,[7,209],[7,[[206,[209]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[9,209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[65,[[206,[209]]]],[[],209],[66,[[206,[209]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[129,209],[[],209],[130,209],[131,209],[[],209],[[],209],[132,209],[[],209],[133,209],[3,209],[[],209],[5,209],[[],209],[134,209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],0,0,0,[73],0,0,0,0,0,0,0,0,0,[129,4],[130,4],[131,4],[132,4],[133,4],[3,4],[5,4],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],218],[[],218],[[],218],[[],218],[[7,215]],[[8,215]],[[1,215]],[[2,215]],[[9,215]],[[10,215]],[[12,215]],[[13,215]],[[14,215]],[[15,215]],[[16,215]],[[17,215]],[[18,215]],[[19,215]],[[20,215]],[[21,215]],[[22,215]],[[23,215]],[[24,215]],[[25,215]],[[26,215]],[[27,215]],[[28,215]],[[29,215]],[[30,215]],[[31,215]],[[32,215]],[[33,215]],[[34,215]],[[35,215]],[[36,215]],[[37,215]],[[38,215]],[[39,215]],[[40,215]],[[41,215]],[[42,215]],[[43,215]],[[44,215]],[[45,215]],[[46,215]],[[47,215]],[[48,215]],[[49,215]],[[50,215]],[[51,215]],[[52,215]],[[53,215]],[[54,215]],[[55,215]],[[56,215]],[[57,215]],[[58,215]],[[59,215]],[[60,215]],[[61,215]],[[62,215]],[[63,215]],[[64,215]],[[65,215]],[[66,215]],[[67,215]],[[68,215]],[[69,215]],[[70,215]],[[71,215]],[[72,215]],[[73,215]],[[74,215]],[[75,215]],[[76,215]],[[77,215]],[[78,215]],[[79,215]],[[80,215]],[[81,215]],[[82,215]],[[83,215]],[[84,215]],[[85,215]],[[86,215]],[[87,215]],[[88,215]],[[89,215]],[[90,215]],[[91,215]],[[92,215]],[[93,215]],[[94,215]],[[95,215]],[[96,215]],[[97,215]],[[98,215]],[[99,215]],[[100,215]],[[101,215]],[[102,215]],[[103,215]],[[104,215]],[[105,215]],[[106,215]],[[107,215]],[[108,215]],[[109,215]],[[110,215]],[[111,215]],[[112,215]],[[113,215]],[[114,215]],[[115,215]],[[116,215]],[[117,215]],[[118,215]],[[119,215]],[[120,215]],[[121,215]],[[122,215]],[[123,215]],[[124,215]],[[125,215]],[[126,215]],[[127,215]],[[128,215]],[[129,215]],[[130,215]],[[131,215]],[[132,215]],[[133,215]],[[3,215]],[[5,215]],[[134,215]],[[135,215]],[[137,215]],[[142,215]],[[143,215]],[[144,215]],[[145,215]],[[146,215]],[[147,215]],[[148,215]],[[149,215]],[[150,215]],[[151,215]],[[152,215]],[[153,215]],[[154,215]],[[155,215]],[[156,215]],[[157,215]],[[158,215]],[[159,215]],[[160,215]],[[161,215]],[[162,215]],[[163,215]],[[164,215]],[[165,215]],[[166,215]],[[167,215]],[[168,215]],[[169,215]],[[170,215]],[[171,215]],[[172,215]],[[173,215]],[[174,215]],[[175,215]],[[176,215]],[[177,215]],[[178,215]],[[179,215]],[[180,215]],[[181,215]],[[182,215]],[[183,215]],[[184,215]],[[185,215]],[[186,215]],[[187,215]],[[188,215]],[[189,215]],[[190,215]],0,[130,205],[131,205],[132,205],[133,205],[3,205],[5,205],[134,7],0,0,0,0,0,0,0,[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],0,0,0,0,0,0,0,0,0,0,0,[7,7],0,0,0,0,0,0,[130,218],[131,[[217,[210]]]],[132,210],[133,211],[134,193],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[214,[[6,[147]]]],[214,[[6,[151]]]],[214,[[6,[160]]]],[214,[[6,[165]]]],0,0,0,[220,221],[[]],[[]],[[]],[[]],[221,221],[[]],[[],221],[221,193],[[221,221],193],[[]],[[]],[[221,222],206],[221,206],[[]],[[]],[221,206],[221,206],[212,220],[215,220],[[221,221],[[206,[192]]]],[221,206],[221,209],[[]],[221,215],[221,206],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],0,[214,6],0,[[],7],0,0,0,0,0,0,0,0,0,0,0,[[223,223]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[223,6],[224,224],[225,225],[[]],[[]],[[225,225]],[223,221],[224],0,[223],[[226,226],193],[227,225],[[223,228],225],[[224,228],225],[[225,229]],[[223,194],197],[[223,194],197],[[226,194],197],[[225,194],197],[[225,194],197],[223,223],[[]],[[]],[[]],[[]],[230,225],[[]],[226],[[]],[[]],[[]],[[]],[[]],[225,215],[225],[225],[223,193],[223,227],[[209,228],225],[[231,228],225],[214,6],[223,[[6,[213]]]],[212,6],[214,[[6,[226]]]],[215,6],[4,6],[223,[[6,[[232,[213]]]]]],[[227,233],193],[[223,233],193],[[223,233],193],[[223,233],193],[234],[223,209],[225,209],[223,6],[225,215],[[]],[[]],[[],218],[[],218],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],0,[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[232],[232,232],[235,235],[236,236],[237,237],[207,207],[238,238],[[]],[[]],[[]],[[]],[[]],[[]],[[],232],[232,193],[[232,232],193],[[232,229]],[[232,229]],[232,206],[232,206],[[[232,[239,239]],194],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[229,232],[229,232],[232],[[232,204]],[[232,204]],[[232,204]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[232],[232],[232],[[]],[[]],[[]],[[]],[[]],[[]],[232,236],[238],[238],[232,193],[232,207],[232,208],[232,206],[232,206],[232,204],[235,204],[240,204],[236,204],[237,204],[207,204],[208,204],[[],232],[206,238],[235,206],[240,206],[236,206],[237,206],[207,206],[208,206],[235,206],[240,206],[236,206],[237,206],[207,206],[208,206],[232,235],[232,240],[214,[[6,[232]]]],[214,[[6,[232]]]],[214,[[6,[232]]]],[214,[[6,[232]]]],[232,[[206,[238]]]],[238,206],[238,206],[232],[232],[232],[235],[240],[236],[237],[207],[208],[[],209],[[],209],[[]],[[]],[[]],[[]],[[]],[[]],[[232,215]],[[238,215]],[232,193],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[238],[238],0,[[],209],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[199,199],[241,241],[242,242],[243,243],[244,244],[245,245],[246,246],[247,247],[248,248],[249,249],[250,250],[202,202],[251,251],[252,252],[253,253],[254,254],[255,255],[203,203],[256,256],[257,257],[258,258],[259,259],[260,260],[261,261],[262,262],[263,263],[264,264],[265,265],[266,266],[267,267],[268,268],[269,269],[270,270],[271,271],[272,272],[273,273],[200,200],[198,198],[274,274],[275,275],[201,201],[276,276],[277,277],[278,278],[279,279],[280,280],[281,281],[282,282],[283,283],[284,284],[285,285],[286,286],[287,287],[288,288],[289,289],[290,290],[291,291],[292,292],[293,293],[294,294],[295,295],[296,296],[297,297],[298,298],[299,299],[300,300],[301,301],[302,302],[303,303],[304,304],[305,305],[306,306],[307,307],[308,308],[309,309],[310,310],[311,311],[312,312],[313,313],[314,314],[315,315],[316,316],[317,317],[318,318],[319,319],[320,320],[321,321],[322,322],[323,323],[324,324],[325,325],[326,326],[327,327],[328,328],[329,329],[330,330],[331,331],[332,332],[333,333],[334,334],[335,335],[336,336],[337,337],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],199],[[],241],[[],242],[[],243],[[],244],[[],245],[[],246],[[],247],[[],248],[[],249],[[],250],[[],202],[[],251],[[],252],[[],253],[[],254],[[],255],[[],203],[[],256],[[],257],[[],258],[[],259],[[],260],[[],261],[[],262],[[],263],[[],264],[[],265],[[],266],[[],267],[[],268],[[],269],[[],270],[[],271],[[],272],[[],273],[[],200],[[],198],[[],274],[[],275],[[],201],[[],276],[[],277],[[],278],[[],279],[[],280],[[],281],[[],282],[[],283],[[],284],[[],285],[[],286],[[],287],[[],288],[[],289],[[],290],[[],291],[[],292],[[],293],[[],294],[[],295],[[],296],[[],297],[[],298],[[],299],[[],300],[[],301],[[],302],[[],303],[[],304],[[],305],[[],306],[[],307],[[],308],[[],309],[[],310],[[],311],[[],312],[[],313],[[],314],[[],315],[[],316],[[],317],[[],318],[[],319],[[],320],[[],321],[[],322],[[],323],[[],324],[[],325],[[],326],[[],327],[[],328],[[],329],[[],330],[[],331],[[],332],[[],333],[[],334],[[],335],[[],336],[[],337],[199],[288],[290],[293],[294],[295],[297],[299],[300],[302],[303],[307],[310],[312],[315],[318],[319],[322],[325],[330],[331],[333],[199],[288],[290],[293],[294],[295],[297],[299],[300],[302],[303],[307],[310],[312],[315],[318],[319],[322],[325],[330],[331],[333],[[199,199],193],[[241,241],193],[[242,242],193],[[243,243],193],[[244,244],193],[[245,245],193],[[246,246],193],[[247,247],193],[[248,248],193],[[249,249],193],[[250,250],193],[[202,202],193],[[251,251],193],[[252,252],193],[[253,253],193],[[254,254],193],[[255,255],193],[[203,203],193],[[256,256],193],[[257,257],193],[[258,258],193],[[259,259],193],[[260,260],193],[[261,261],193],[[262,262],193],[[263,263],193],[[264,264],193],[[265,265],193],[[266,266],193],[[267,267],193],[[268,268],193],[[269,269],193],[[270,270],193],[[271,271],193],[[272,272],193],[[273,273],193],[[200,200],193],[[198,198],193],[[274,274],193],[[275,275],193],[[201,201],193],[[276,276],193],[[277,277],193],[[278,278],193],[[279,279],193],[[280,280],193],[[281,281],193],[[282,282],193],[[283,283],193],[[284,284],193],[[285,285],193],[[286,286],193],[[287,287],193],[[288,288],193],[[289,289],193],[[290,290],193],[[291,291],193],[[292,292],193],[[293,293],193],[[294,294],193],[[295,295],193],[[296,296],193],[[297,297],193],[[298,298],193],[[299,299],193],[[300,300],193],[[301,301],193],[[302,302],193],[[303,303],193],[[304,304],193],[[305,305],193],[[306,306],193],[[307,307],193],[[308,308],193],[[309,309],193],[[310,310],193],[[311,311],193],[[312,312],193],[[313,313],193],[[314,314],193],[[315,315],193],[[316,316],193],[[317,317],193],[[318,318],193],[[319,319],193],[[320,320],193],[[321,321],193],[[322,322],193],[[323,323],193],[[324,324],193],[[325,325],193],[[326,326],193],[[327,327],193],[[328,328],193],[[329,329],193],[[330,330],193],[[331,331],193],[[332,332],193],[[333,333],193],[[334,334],193],[[335,335],193],[[336,336],193],[[337,337],193],[[199,194],197],[[241,194],197],[[242,194],197],[[243,194],197],[[244,194],197],[[245,194],197],[[246,194],197],[[247,194],197],[[248,194],197],[[249,194],197],[[250,194],197],[[202,194],197],[[251,194],197],[[252,194],197],[[253,194],197],[[254,194],197],[[255,194],197],[[203,194],197],[[256,194],197],[[257,194],197],[[258,194],197],[[259,194],197],[[260,194],197],[[261,194],197],[[262,194],197],[[263,194],197],[[264,194],197],[[265,194],197],[[266,194],197],[[267,194],197],[[268,194],197],[[269,194],197],[[270,194],197],[[271,194],197],[[272,194],197],[[273,194],197],[[200,194],197],[[198,194],197],[[274,194],197],[[275,194],197],[[201,194],197],[[276,194],197],[[277,194],197],[[278,194],197],[[279,194],197],[[280,194],197],[[281,194],197],[[282,194],197],[[283,194],197],[[284,194],197],[[285,194],197],[[286,194],197],[[287,194],197],[[288,194],197],[[289,194],197],[[290,194],197],[[291,194],197],[[292,194],197],[[293,194],197],[[294,194],197],[[295,194],197],[[296,194],197],[[297,194],197],[[298,194],197],[[299,194],197],[[300,194],197],[[301,194],197],[[302,194],197],[[303,194],197],[[304,194],197],[[305,194],197],[[306,194],197],[[307,194],197],[[308,194],197],[[309,194],197],[[310,194],197],[[311,194],197],[[312,194],197],[[313,194],197],[[314,194],197],[[315,194],197],[[316,194],197],[[317,194],197],[[318,194],197],[[319,194],197],[[320,194],197],[[321,194],197],[[322,194],197],[[323,194],197],[[324,194],197],[[325,194],197],[[326,194],197],[[327,194],197],[[328,194],197],[[329,194],197],[[330,194],197],[[331,194],197],[[332,194],197],[[333,194],197],[[334,194],197],[[335,194],197],[[336,194],197],[[337,194],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[199],[241],[242],[243],[244],[245],[246],[247],[248],[249],[250],[202],[251],[252],[253],[254],[255],[203],[256],[257],[258],[259],[260],[261],[262],[263],[264],[265],[266],[267],[268],[269],[270],[271],[272],[273],[200],[198],[274],[275],[201],[276],[277],[278],[279],[280],[281],[282],[283],[284],[285],[286],[287],[288],[289],[290],[291],[292],[293],[294],[295],[296],[297],[298],[299],[300],[301],[302],[303],[304],[305],[306],[307],[308],[309],[310],[311],[312],[313],[314],[315],[316],[317],[318],[319],[320],[321],[322],[323],[324],[325],[326],[327],[328],[329],[330],[331],[332],[333],[334],[335],[336],[337],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[214,[[6,[199]]]],[214,[[6,[241]]]],[214,[[6,[242]]]],[214,[[6,[243]]]],[214,[[6,[244]]]],[214,[[6,[245]]]],[214,[[6,[246]]]],[214,[[6,[247]]]],[214,[[6,[248]]]],[214,[[6,[249]]]],[214,[[6,[250]]]],[214,[[6,[202]]]],[214,[[6,[251]]]],[214,[[6,[252]]]],[214,[[6,[253]]]],[214,[[6,[254]]]],[214,[[6,[255]]]],[214,[[6,[203]]]],[214,[[6,[256]]]],[214,[[6,[257]]]],[214,[[6,[258]]]],[214,[[6,[259]]]],[214,[[6,[260]]]],[214,[[6,[261]]]],[214,[[6,[262]]]],[214,[[6,[263]]]],[214,[[6,[264]]]],[214,[[6,[265]]]],[214,[[6,[266]]]],[214,[[6,[267]]]],[214,[[6,[268]]]],[214,[[6,[269]]]],[214,[[6,[270]]]],[214,[[6,[271]]]],[214,[[6,[272]]]],[214,[[6,[273]]]],[214,[[6,[200]]]],[214,[[6,[198]]]],[214,[[6,[274]]]],[214,[[6,[275]]]],[214,[[6,[201]]]],[214,[[6,[276]]]],[214,[[6,[277]]]],[214,[[6,[278]]]],[214,[[6,[279]]]],[214,[[6,[280]]]],[214,[[6,[281]]]],[214,[[6,[282]]]],[214,[[6,[283]]]],[214,[[6,[284]]]],[214,[[6,[285]]]],[214,[[6,[286]]]],[214,[[6,[287]]]],[214,[[6,[288]]]],[214,[[6,[289]]]],[214,[[6,[290]]]],[214,[[6,[291]]]],[214,[[6,[292]]]],[214,[[6,[293]]]],[214,[[6,[294]]]],[214,[[6,[295]]]],[214,[[6,[296]]]],[214,[[6,[297]]]],[214,[[6,[298]]]],[214,[[6,[299]]]],[214,[[6,[300]]]],[214,[[6,[301]]]],[214,[[6,[302]]]],[214,[[6,[303]]]],[214,[[6,[304]]]],[214,[[6,[305]]]],[214,[[6,[306]]]],[214,[[6,[307]]]],[214,[[6,[308]]]],[214,[[6,[309]]]],[214,[[6,[310]]]],[214,[[6,[311]]]],[214,[[6,[312]]]],[214,[[6,[313]]]],[214,[[6,[314]]]],[214,[[6,[315]]]],[214,[[6,[316]]]],[214,[[6,[317]]]],[214,[[6,[318]]]],[214,[[6,[319]]]],[214,[[6,[320]]]],[214,[[6,[321]]]],[214,[[6,[322]]]],[214,[[6,[323]]]],[214,[[6,[324]]]],[214,[[6,[325]]]],[214,[[6,[326]]]],[214,[[6,[327]]]],[214,[[6,[328]]]],[214,[[6,[329]]]],[214,[[6,[330]]]],[214,[[6,[331]]]],[214,[[6,[332]]]],[214,[[6,[333]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[334,215]],[[335,215]],[[336,215]],[[337,215]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[199,215]],[[241,215]],[[242,215]],[[243,215]],[[244,215]],[[245,215]],[[246,215]],[[247,215]],[[248,215]],[[249,215]],[[250,215]],[[202,215]],[[251,215]],[[252,215]],[[253,215]],[[254,215]],[[255,215]],[[203,215]],[[256,215]],[[257,215]],[[258,215]],[[259,215]],[[260,215]],[[261,215]],[[262,215]],[[263,215]],[[264,215]],[[265,215]],[[266,215]],[[267,215]],[[268,215]],[[269,215]],[[270,215]],[[271,215]],[[272,215]],[[273,215]],[[200,215]],[[198,215]],[[274,215]],[[275,215]],[[201,215]],[[276,215]],[[277,215]],[[278,215]],[[279,215]],[[280,215]],[[281,215]],[[282,215]],[[283,215]],[[284,215]],[[285,215]],[[286,215]],[[287,215]],[[288,215]],[[289,215]],[[290,215]],[[291,215]],[[292,215]],[[293,215]],[[294,215]],[[295,215]],[[296,215]],[[297,215]],[[298,215]],[[299,215]],[[300,215]],[[301,215]],[[302,215]],[[303,215]],[[304,215]],[[305,215]],[[306,215]],[[307,215]],[[308,215]],[[309,215]],[[310,215]],[[311,215]],[[312,215]],[[313,215]],[[314,215]],[[315,215]],[[316,215]],[[317,215]],[[318,215]],[[319,215]],[[320,215]],[[321,215]],[[322,215]],[[323,215]],[[324,215]],[[325,215]],[[326,215]],[[327,215]],[[328,215]],[[329,215]],[[330,215]],[[331,215]],[[332,215]],[[333,215]],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],0,[162],[162],[162],[187],[187],[187],[71],[71],[71],[11],[11],[11],[10],[10],[10],[163],[163],[163],[142],[142],[142],[188],[188],[188],[144],[144],[144],[78],[78],[78],[77],[77],[77],[189],[189],[189],[140],[140],[140],[138],[138],[138],[139],[139],[139],[141],[141],[141],[137],[137],[137],[26],[26],[26],[27],[27],[27],[28],[28],[28],[29],[29],[29],[30],[30],[30],[31],[31],[31],[32],[32],[32],[33],[33],[33],[34],[34],[34],[35],[35],[35],[36],[36],[36],[37],[37],[37],[38],[38],[38],[39],[39],[39],[40],[40],[40],[41],[41],[41],[42],[42],[42],[43],[43],[43],[44],[44],[44],[45],[45],[45],[46],[46],[46],[47],[47],[47],[48],[48],[48],[49],[49],[49],[25],[25],[25],[50],[50],[50],[51],[51],[51],[52],[52],[52],[53],[53],[53],[54],[54],[54],[55],[55],[55],[56],[56],[56],[58],[58],[58],[57],[57],[57],[59],[59],[59],[60],[60],[60],[61],[61],[61],[62],[62],[62],[63],[63],[63],[64],[64],[64],[20],[20],[20],[182],[182],[182],[69],[69],[69],[17],[17],[17],[18],[18],[18],[19],[19],[19],[128],[128],[128],[126],[126],[126],[111],[111],[111],[114],[114],[114],[110],[110],[110],[112],[112],[112],[113],[113],[113],[186],[186],[186],[68],[68],[68],[74],[74],[74],[73],[73],[73],[7],[7],[7],[121],[121],[121],[124],[124],[124],[122],[122],[122],[120],[120],[120],[123],[123],[123],[66],[66],[66],[88],[88],[88],[89],[89],[89],[90],[90],[90],[91],[91],[91],[92],[92],[92],[93],[93],[93],[95],[95],[95],[94],[94],[94],[96],[96],[96],[87],[87],[87],[97],[97],[97],[98],[98],[98],[100],[100],[100],[99],[99],[99],[101],[101],[101],[102],[102],[102],[103],[103],[103],[70],[70],[70],[76],[76],[76],[9],[9],[9],[134],[134],[134],[132],[132],[132],[131],[131],[131],[133],[133],[133],[5],[5],[5],[3],[3],[3],[129],[129],[129],[130],[130],[130],[146],[146],[146],[136],[136],[136],[135],[135],[135],[65],[65],[65],[13],[13],[13],[12],[12],[12],[14],[14],[14],[67],[67],[67],[15],[15],[15],[190],[190],[190],[167],[167],[167],[168],[168],[168],[169],[169],[169],[170],[170],[170],[166],[166],[166],[171],[171],[171],[172],[172],[172],[173],[173],[173],[174],[174],[174],[175],[175],[175],[176],[176],[176],[177],[177],[177],[178],[178],[178],[179],[179],[179],[180],[180],[180],[181],[181],[181],[185],[185],[185],[183],[183],[183],[184],[184],[184],[86],[86],[86],[85],[85],[85],[84],[84],[84],[191],[191],[191],[72],[72],[72],[127],[127],[127],[165],[165],[165],[125],[125],[125],[209],[209],[209],[145],[145],[145],[81],[81],[81],[80],[80],[80],[116],[116],[116],[119],[119],[119],[117],[117],[117],[115],[115],[115],[118],[118],[118],[148],[148],[148],[149],[149],[149],[150],[150],[150],[151],[151],[151],[152],[152],[152],[153],[153],[153],[147],[147],[147],[154],[154],[154],[79],[79],[79],[75],[75],[75],[155],[155],[155],[156],[156],[156],[157],[157],[157],[158],[158],[158],[159],[159],[159],[160],[160],[160],[161],[161],[161],[143],[143],[143],[108],[108],[108],[109],[109],[109],[106],[106],[106],[105],[105],[105],[107],[107],[107],[104],[104],[104],[164],[164],[164],[16],[16],[16],[23],[23],[23],[22],[22],[22],[24],[24],[24],[21],[21],[21],[82],[82],[82],[83],[83],[83]],"p":[[3,"TypeGenerics"],[3,"Turbofish"],[3,"LitInt"],[15,"str"],[3,"LitFloat"],[6,"Result"],[3,"Ident"],[3,"ImplGenerics"],[3,"Lifetime"],[3,"Attribute"],[4,"AttrStyle"],[4,"Meta"],[3,"MetaList"],[3,"MetaNameValue"],[4,"NestedMeta"],[3,"Variant"],[4,"Fields"],[3,"FieldsNamed"],[3,"FieldsUnnamed"],[3,"Field"],[4,"Visibility"],[3,"VisPublic"],[3,"VisCrate"],[3,"VisRestricted"],[4,"Expr"],[3,"ExprArray"],[3,"ExprAssign"],[3,"ExprAssignOp"],[3,"ExprAsync"],[3,"ExprAwait"],[3,"ExprBinary"],[3,"ExprBlock"],[3,"ExprBox"],[3,"ExprBreak"],[3,"ExprCall"],[3,"ExprCast"],[3,"ExprClosure"],[3,"ExprContinue"],[3,"ExprField"],[3,"ExprForLoop"],[3,"ExprGroup"],[3,"ExprIf"],[3,"ExprIndex"],[3,"ExprLet"],[3,"ExprLit"],[3,"ExprLoop"],[3,"ExprMacro"],[3,"ExprMatch"],[3,"ExprMethodCall"],[3,"ExprParen"],[3,"ExprPath"],[3,"ExprRange"],[3,"ExprReference"],[3,"ExprRepeat"],[3,"ExprReturn"],[3,"ExprStruct"],[3,"ExprTry"],[3,"ExprTryBlock"],[3,"ExprTuple"],[3,"ExprType"],[3,"ExprUnary"],[3,"ExprUnsafe"],[3,"ExprWhile"],[3,"ExprYield"],[4,"Member"],[3,"Index"],[3,"MethodTurbofish"],[4,"GenericMethodArgument"],[3,"FieldValue"],[3,"Label"],[3,"Arm"],[4,"RangeLimits"],[3,"Generics"],[4,"GenericParam"],[3,"TypeParam"],[3,"LifetimeDef"],[3,"ConstParam"],[3,"BoundLifetimes"],[4,"TypeParamBound"],[3,"TraitBound"],[4,"TraitBoundModifier"],[3,"WhereClause"],[4,"WherePredicate"],[3,"PredicateType"],[3,"PredicateLifetime"],[3,"PredicateEq"],[4,"Item"],[3,"ItemConst"],[3,"ItemEnum"],[3,"ItemExternCrate"],[3,"ItemFn"],[3,"ItemForeignMod"],[3,"ItemImpl"],[3,"ItemMacro"],[3,"ItemMacro2"],[3,"ItemMod"],[3,"ItemStatic"],[3,"ItemStruct"],[3,"ItemTrait"],[3,"ItemTraitAlias"],[3,"ItemType"],[3,"ItemUnion"],[3,"ItemUse"],[4,"UseTree"],[3,"UsePath"],[3,"UseName"],[3,"UseRename"],[3,"UseGlob"],[3,"UseGroup"],[4,"ForeignItem"],[3,"ForeignItemFn"],[3,"ForeignItemStatic"],[3,"ForeignItemType"],[3,"ForeignItemMacro"],[4,"TraitItem"],[3,"TraitItemConst"],[3,"TraitItemMethod"],[3,"TraitItemType"],[3,"TraitItemMacro"],[4,"ImplItem"],[3,"ImplItemConst"],[3,"ImplItemMethod"],[3,"ImplItemType"],[3,"ImplItemMacro"],[3,"Signature"],[4,"FnArg"],[3,"Receiver"],[3,"File"],[4,"Lit"],[3,"LitStr"],[3,"LitByteStr"],[3,"LitByte"],[3,"LitChar"],[3,"LitBool"],[3,"Macro"],[4,"MacroDelimiter"],[3,"DeriveInput"],[4,"Data"],[3,"DataStruct"],[3,"DataEnum"],[3,"DataUnion"],[4,"BinOp"],[4,"UnOp"],[3,"Block"],[4,"Stmt"],[3,"Local"],[4,"Type"],[3,"TypeArray"],[3,"TypeBareFn"],[3,"TypeGroup"],[3,"TypeImplTrait"],[3,"TypeInfer"],[3,"TypeMacro"],[3,"TypeNever"],[3,"TypeParen"],[3,"TypePath"],[3,"TypePtr"],[3,"TypeReference"],[3,"TypeSlice"],[3,"TypeTraitObject"],[3,"TypeTuple"],[3,"Abi"],[3,"BareFnArg"],[3,"Variadic"],[4,"ReturnType"],[4,"Pat"],[3,"PatBox"],[3,"PatIdent"],[3,"PatLit"],[3,"PatMacro"],[3,"PatOr"],[3,"PatPath"],[3,"PatRange"],[3,"PatReference"],[3,"PatRest"],[3,"PatSlice"],[3,"PatStruct"],[3,"PatTuple"],[3,"PatTupleStruct"],[3,"PatType"],[3,"PatWild"],[3,"FieldPat"],[3,"Path"],[3,"PathSegment"],[4,"PathArguments"],[4,"GenericArgument"],[3,"AngleBracketedGenericArguments"],[3,"Binding"],[3,"Constraint"],[3,"ParenthesizedGenericArguments"],[3,"QSelf"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[3,"SelfValue"],[3,"Underscore"],[3,"SelfType"],[3,"Super"],[3,"Crate"],[3,"Extern"],[15,"usize"],[3,"Literal"],[4,"Option"],[3,"Iter"],[3,"IterMut"],[3,"Span"],[15,"u8"],[15,"char"],[3,"TokenStream"],[8,"Parse"],[6,"ParseStream"],[3,"TokenStream"],[8,"Parser"],[3,"Vec"],[3,"String"],[3,"TypeId"],[3,"TokenBuffer"],[3,"Cursor"],[4,"Delimiter"],[3,"ParseBuffer"],[3,"StepCursor"],[3,"Error"],[3,"Nothing"],[3,"Lookahead1"],[8,"Display"],[8,"IntoIterator"],[3,"LexError"],[8,"ToTokens"],[3,"Punctuated"],[8,"Peek"],[3,"Demand"],[3,"Pairs"],[3,"IntoPairs"],[3,"IntoIter"],[4,"Pair"],[8,"Debug"],[3,"PairsMut"],[3,"Abstract"],[3,"As"],[3,"Async"],[3,"Auto"],[3,"Await"],[3,"Become"],[3,"Box"],[3,"Break"],[3,"Const"],[3,"Continue"],[3,"Default"],[3,"Do"],[3,"Dyn"],[3,"Else"],[3,"Enum"],[3,"Final"],[3,"Fn"],[3,"For"],[3,"If"],[3,"Impl"],[3,"In"],[3,"Let"],[3,"Loop"],[3,"Macro"],[3,"Match"],[3,"Mod"],[3,"Move"],[3,"Mut"],[3,"Override"],[3,"Priv"],[3,"Pub"],[3,"Ref"],[3,"Return"],[3,"Static"],[3,"Struct"],[3,"Trait"],[3,"Try"],[3,"Type"],[3,"Typeof"],[3,"Union"],[3,"Unsafe"],[3,"Unsized"],[3,"Use"],[3,"Virtual"],[3,"Where"],[3,"While"],[3,"Yield"],[3,"Add"],[3,"AddEq"],[3,"And"],[3,"AndAnd"],[3,"AndEq"],[3,"At"],[3,"Bang"],[3,"Caret"],[3,"CaretEq"],[3,"Colon"],[3,"Colon2"],[3,"Comma"],[3,"Div"],[3,"DivEq"],[3,"Dollar"],[3,"Dot"],[3,"Dot2"],[3,"Dot3"],[3,"DotDotEq"],[3,"Eq"],[3,"EqEq"],[3,"Ge"],[3,"Gt"],[3,"Le"],[3,"Lt"],[3,"MulEq"],[3,"Ne"],[3,"Or"],[3,"OrEq"],[3,"OrOr"],[3,"Pound"],[3,"Question"],[3,"RArrow"],[3,"LArrow"],[3,"Rem"],[3,"RemEq"],[3,"FatArrow"],[3,"Semi"],[3,"Shl"],[3,"ShlEq"],[3,"Shr"],[3,"ShrEq"],[3,"Star"],[3,"Sub"],[3,"SubEq"],[3,"Tilde"],[3,"Brace"],[3,"Bracket"],[3,"Paren"],[3,"Group"],[4,"StrStyle"],[8,"IdentExt"],[8,"Speculative"],[8,"Spanned"],[8,"VisitMut"]]},\ +"tokio":{"doc":"A runtime for writing reliable network applications …","t":[0,14,23,0,14,0,14,5,0,0,0,14,23,14,8,8,8,8,2,2,3,2,2,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,11,11,11,11,11,10,11,11,11,11,11,11,8,3,13,3,3,13,3,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,13,3,3,13,13,18,3,3,3,13,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,12,12,3,3,11,11,11,11,5,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,13,13,13,13,13,4,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,3,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,13,13,13,13,3,4,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,3,3,11,11,11,11,11,5,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,3,11,11,11,11,11,11,11,11,11,11,11],"n":["io","join","main","net","pin","runtime","select","spawn","stream","sync","task","task_local","test","try_join","AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","ErrorKind","ReadBuf","Result","SeekFrom","advance","assume_init","borrow","borrow_mut","capacity","clear","consume","filled","filled_mut","fmt","from","initialize_unfilled","initialize_unfilled_to","initialized","initialized_mut","inner_mut","into","is_write_vectored","is_write_vectored","new","poll_complete","poll_fill_buf","poll_flush","poll_read","poll_shutdown","poll_write","poll_write_vectored","poll_write_vectored","put_slice","remaining","set_filled","start_seek","take","try_from","try_into","type_id","unfilled_mut","uninit","ToSocketAddrs","Builder","CurrentThread","EnterGuard","Handle","MultiThread","Runtime","RuntimeFlavor","TryCurrentError","block_on","block_on","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","clone","clone_into","current","drop","enable_all","enter","enter","eq","event_interval","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","global_queue_interval","handle","into","into","into","into","into","into","is_missing_context","is_thread_local_destroyed","max_blocking_threads","new_current_thread","on_thread_park","on_thread_start","on_thread_stop","on_thread_unpark","provide","runtime_flavor","shutdown_background","shutdown_timeout","spawn","spawn","spawn_blocking","spawn_blocking","thread_keep_alive","thread_name","thread_name_fn","thread_stack_size","to_owned","to_string","try_current","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","worker_threads","AcquireError","AlreadyInitializedError","Barrier","BarrierWaitResult","Closed","InitializingError","MAX_PERMITS","MappedMutexGuard","Mutex","MutexGuard","NoPermits","Notify","OnceCell","OwnedMutexGuard","OwnedRwLockMappedWriteGuard","OwnedRwLockReadGuard","OwnedRwLockWriteGuard","OwnedSemaphorePermit","RwLock","RwLockMappedWriteGuard","RwLockReadGuard","RwLockWriteGuard","Semaphore","SemaphorePermit","SetError","TryAcquireError","TryLockError","acquire","acquire_many","acquire_many_owned","acquire_owned","add_permits","available_permits","blocking_lock","blocking_lock_owned","blocking_read","blocking_write","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","broadcast","clone","clone","clone_into","clone_into","close","default","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","downgrade","downgrade","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","futures","get","get_mut","get_mut","get_mut","get_or_init","get_or_try_init","initialized","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_mapped","into_mapped","is_already_init_err","is_closed","is_initializing_err","is_leader","lock","lock_owned","map","map","map","map","map","map","map","map","merge","merge","mpsc","mutex","mutex","new","new","new","new","new","new","new_with","notified","notify_one","notify_waiters","oneshot","provide","provide","provide","provide","read","read_owned","set","take","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","try_acquire","try_acquire_many","try_acquire_many_owned","try_acquire_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_read","try_read_owned","try_write","try_write_owned","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","wait","watch","with_max_readers","write","write_owned","0","0","Receiver","Sender","borrow","borrow","borrow_mut","borrow_mut","channel","clone","clone_into","drop","drop","error","fmt","fmt","from","from","into","into","is_empty","len","receiver_count","recv","resubscribe","send","subscribe","to_owned","try_from","try_from","try_into","try_into","try_recv","type_id","type_id","0","Closed","Closed","Empty","Lagged","Lagged","RecvError","SendError","TryRecvError","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","into","into","into","provide","provide","provide","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","Notified","borrow","borrow_mut","drop","enable","fmt","from","into","into_future","poll","try_from","try_into","type_id","OwnedPermit","Permit","Receiver","Sender","UnboundedReceiver","UnboundedSender","WeakSender","WeakUnboundedSender","blocking_recv","blocking_recv","blocking_send","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","closed","closed","downgrade","downgrade","drop","drop","error","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","is_closed","is_closed","max_capacity","poll_recv","poll_recv","recv","recv","release","reserve","reserve_owned","same_channel","same_channel","send","send","send","send","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_recv","try_recv","try_reserve","try_reserve_owned","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded_channel","upgrade","upgrade","0","Closed","Disconnected","Empty","Full","SendError","TryRecvError","TrySendError","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","provide","provide","provide","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","Receiver","Sender","blocking_recv","borrow","borrow","borrow_mut","borrow_mut","channel","close","closed","drop","drop","error","fmt","fmt","from","from","into","into","into_future","is_closed","poll","poll_closed","send","try_from","try_from","try_into","try_into","try_recv","type_id","type_id","Closed","Empty","RecvError","TryRecvError","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","from","from","into","into","provide","provide","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","Receiver","Ref","Sender","borrow","borrow","borrow","borrow","borrow","borrow_and_update","borrow_mut","borrow_mut","borrow_mut","changed","channel","clone","clone_into","closed","deref","drop","drop","error","fmt","fmt","fmt","from","from","from","has_changed","has_changed","into","into","into","is_closed","receiver_count","same_channel","send","send_if_modified","send_modify","send_replace","subscribe","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","RecvError","SendError","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","fmt","fmt","fmt","fmt","from","from","into","into","provide","provide","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","AbortHandle","JoinError","JoinHandle","JoinSet","LocalEnterGuard","LocalKey","LocalSet","Unconstrained","abort","abort","abort_all","block_on","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","default","detach_all","drop","drop","drop","drop","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","futures","get","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_panic","is_cancelled","is_empty","is_finished","is_finished","is_panic","join_next","len","new","new","poll","poll","poll","provide","run_until","scope","shutdown","spawn","spawn","spawn_blocking","spawn_local","spawn_local","spawn_local","spawn_local_on","spawn_on","sync_scope","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into_panic","try_with","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unconstrained","with","yield_now","TaskLocalFuture","borrow","borrow_mut","drop","fmt","from","into","into_future","poll","try_from","try_into","type_id"],"q":["tokio","","","","","","","","","","","","","","tokio::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::net","tokio::runtime","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::SetError","","tokio::sync::broadcast","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::broadcast::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::broadcast::error::RecvError","tokio::sync::broadcast::error::TryRecvError","tokio::sync::futures","","","","","","","","","","","","","tokio::sync::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::mpsc::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::mpsc::error::TrySendError","","tokio::sync::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::oneshot::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::watch","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::watch::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::task::futures","","","","","","","","","","",""],"d":["Traits, helpers, and type definitions for asynchronous I/O …","Waits on multiple concurrent branches, returning when all …","Marks async function to be executed by selected runtime. …","TCP/UDP/Unix bindings for tokio.","Pins a value on the stack.","The Tokio runtime.","Waits on multiple concurrent branches, returning when the …","Spawns a new asynchronous task, returning a JoinHandle for …","Due to the Stream trait’s inclusion in std landing later …","Synchronization primitives for use in asynchronous …","Asynchronous green-threads.","Declares a new task-local key of type tokio::task::LocalKey…","Marks async function to be executed by runtime, suitable …","Waits on multiple concurrent branches, returning when all …","Reads bytes asynchronously.","Reads bytes from a source.","Seek bytes asynchronously.","Writes bytes asynchronously.","","","A wrapper around a byte buffer that is incrementally …","","","Advances the size of the filled region of the buffer.","Asserts that the first n unfilled bytes of the buffer are …","","","Returns the total capacity of the buffer.","Clears the buffer, resetting the filled region to empty.","Tells this buffer that amt bytes have been consumed from …","Returns a shared reference to the filled portion of the …","Returns a mutable reference to the filled portion of the …","","Returns the argument unchanged.","Returns a mutable reference to the unfilled part of the …","Returns a mutable reference to the first n bytes of the …","Returns a shared reference to the initialized portion of …","Returns a mutable reference to the initialized portion of …","Returns a mutable reference to the entire buffer, without …","Calls U::from(self).","Determines if this writer has an efficient …","Determines if this writer has an efficient …","Creates a new ReadBuf from a fully initialized buffer.","Waits for a seek operation to complete.","Attempts to return the contents of the internal buffer, …","Attempts to flush the object, ensuring that any buffered …","Attempts to read from the AsyncRead into buf.","Initiates or attempts to shut down this writer, returning …","Attempt to write bytes from buf into the object.","Like poll_write, except that it writes from a slice of …","Like poll_write, except that it writes from a slice of …","Appends data to the buffer, advancing the written position …","Returns the number of bytes at the end of the slice that …","Sets the size of the filled region of the buffer.","Attempts to seek to an offset, in bytes, in a stream.","Returns a new ReadBuf comprised of the unfilled section up …","","","","Returns a mutable reference to the unfilled part of the …","Creates a new ReadBuf from a fully uninitialized buffer.","Converts or resolves without blocking to one or more …","Builds Tokio Runtime with custom configuration values.","The flavor that executes all tasks on the current thread.","Runtime context guard.","Handle to the runtime.","The flavor that executes tasks across multiple threads.","The Tokio runtime.","The flavor of a Runtime.","Error returned by try_current when no Runtime has been …","Runs a future to completion on this Handle’s associated …","Runs a future to completion on the Tokio runtime. This is …","","","","","","","","","","","","","Creates the configured Runtime.","","","Returns a Handle view over the currently running Runtime.","","Enables both I/O and time drivers.","Enters the runtime context. This allows you to construct …","Enters the runtime context.","","Sets the number of scheduler ticks after which the …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Sets the number of scheduler ticks after which the …","Returns a handle to the runtime’s spawner.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if the call failed because there is currently …","Returns true if the call failed because the Tokio context …","Specifies the limit for additional threads spawned by the …","Returns a new builder with the current thread scheduler …","Executes function f just before a thread is parked (goes …","Executes function f after each thread is started but …","Executes function f before each thread stops.","Executes function f just after a thread unparks (starts …","","Returns the flavor of the current Runtime.","Shuts down the runtime, without waiting for any spawned …","Shuts down the runtime, waiting for at most duration for …","Spawns a future onto the Tokio runtime.","Spawns a future onto the Tokio runtime.","Runs the provided function on an executor dedicated to …","Runs the provided function on an executor dedicated to …","Sets a custom timeout for a thread in the blocking pool.","Sets name of threads spawned by the Runtime’s thread …","Sets a function used to generate the name of threads …","Sets the stack size (in bytes) for worker threads.","","","Returns a Handle view over the currently running Runtime","","","","","","","","","","","","","","","","","","","Sets the number of worker threads the Runtime will use.","Error returned from the Semaphore::acquire function.","The cell was already initialized when OnceCell::set was …","A barrier enables multiple tasks to synchronize the …","A BarrierWaitResult is returned by wait when all tasks in …","The semaphore has been closed and cannot issue new permits.","The cell is currently being initialized.","The maximum number of permits which a semaphore can hold. …","A handle to a held Mutex that has had a function applied …","An asynchronous Mutex-like type.","A handle to a held Mutex. The guard can be held across any …","The semaphore has no available permits.","Notifies a single task to wake up.","A thread-safe cell that can be written to only once.","An owned handle to a held Mutex.","Owned RAII structure used to release the exclusive write …","Owned RAII structure used to release the shared read …","Owned RAII structure used to release the exclusive write …","An owned permit from the semaphore.","An asynchronous reader-writer lock.","RAII structure used to release the exclusive write access …","RAII structure used to release the shared read access of a …","RAII structure used to release the exclusive write access …","Counting semaphore performing asynchronous permit …","A permit from the semaphore.","Errors that can be returned from OnceCell::set.","Error returned from the Semaphore::try_acquire function.","Error returned from the Mutex::try_lock, RwLock::try_read …","Acquires a permit from the semaphore.","Acquires n permits from the semaphore.","Acquires n permits from the semaphore.","Acquires a permit from the semaphore.","Adds n new permits to the semaphore.","Returns the current number of available permits.","Blockingly locks this Mutex. When the lock has been …","Blockingly locks this Mutex. When the lock has been …","Blockingly locks this RwLock with shared read access.","Blockingly locks this RwLock with exclusive write access.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A multi-producer, multi-consumer broadcast queue. Each …","","","","","Closes the semaphore.","","","","","","","","","","","","","","","","","","","","","Atomically downgrades a write lock into a read lock …","Atomically downgrades a write lock into a read lock …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Forgets the permit without releasing it back to the …","Forgets the permit without releasing it back to the …","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Named future types.","Returns a reference to the value currently stored in the …","Returns a mutable reference to the underlying data.","Returns a mutable reference to the value currently stored …","Returns a mutable reference to the underlying data.","Gets the value currently in the OnceCell, or initialize it …","Gets the value currently in the OnceCell, or initialize it …","Returns true if the OnceCell currently contains a value, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes the mutex, returning the underlying data.","Takes the value from the cell, destroying the cell in the …","Consumes the lock, returning the underlying data.","Converts this OwnedRwLockWriteGuard into an …","Converts this RwLockWriteGuard into an …","Whether SetError is SetError::AlreadyInitializedError.","Returns true if the semaphore is closed","Whether SetError is SetError::InitializingError","Returns true if this task from wait is the “leader task…","Locks this mutex, causing the current task to yield until …","Locks this mutex, causing the current task to yield until …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new OwnedRwLockReadGuard for a component of the …","Makes a new OwnedRwLockMappedWriteGuard for a component of …","Makes a new OwnedRwLockMappedWriteGuard for a component of …","Makes a new RwLockReadGuard for a component of the locked …","Makes a new RwLockMappedWriteGuard for a component of the …","Makes a new RwLockMappedWriteGuard for a component of the …","Merge two SemaphorePermit instances together, consuming …","Merge two OwnedSemaphorePermit instances together, …","A multi-producer, single-consumer queue for sending values …","Returns a reference to the original Mutex.","Returns a reference to the original Arc<Mutex>.","Creates a new lock in an unlocked state ready for use.","Creates a new empty OnceCell instance.","Creates a new barrier that can block a given number of …","Create a new Notify, initialized without a permit.","Creates a new semaphore with the initial number of permits.","Creates a new instance of an RwLock<T> which is unlocked.","Creates a new OnceCell that contains the provided value, …","Wait for a notification.","Notifies a waiting task.","Notifies all waiting tasks.","A one-shot channel is used for sending a single message …","","","","","Locks this RwLock with shared read access, causing the …","Locks this RwLock with shared read access, causing the …","Sets the value of the OnceCell to the given value if the …","Takes ownership of the current value, leaving the cell …","","","","","","","","","","","","","","","","Tries to acquire a permit from the semaphore.","Tries to acquire n permits from the semaphore.","Tries to acquire n permits from the semaphore.","Tries to acquire a permit from the semaphore.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attempts to acquire the lock, and returns TryLockError if …","Attempts to acquire the lock, and returns TryLockError if …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new OwnedRwLockReadGuard for a …","Attempts to make a new OwnedRwLockMappedWriteGuard for a …","Attempts to make a new OwnedRwLockMappedWriteGuard for a …","Attempts to make a new RwLockReadGuard for a component of …","Attempts to make a new RwLockMappedWriteGuard for a …","Attempts to make a new RwLockMappedWriteGuard for a …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with exclusive write …","Attempts to acquire this RwLock with exclusive write …","","","","","","","","","","","","","","","","","","","","","","","Does not resolve until all tasks have rendezvoused here.","A single-producer, multi-consumer channel that only …","Creates a new instance of an RwLock<T> which is unlocked …","Locks this RwLock with exclusive write access, causing the …","Locks this RwLock with exclusive write access, causing the …","","","Receiving-half of the broadcast channel.","Sending-half of the broadcast channel.","","","","","Create a bounded, multi-producer, multi-consumer channel …","","","","","Broadcast error types","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Returns true if there aren’t any messages in the channel …","Returns the number of messages that were sent into the …","Returns the number of active receivers","Receives the next value for this receiver.","Re-subscribes to the channel starting from the current …","Attempts to send a value to all active Receiver handles, …","Creates a new Receiver handle that will receive values …","","","","","","Attempts to return a pending value on this receiver …","","","","There are no more active senders implying no further …","There are no more active senders implying no further …","The channel is currently empty. There are still active …","The receiver lagged too far behind. Attempting to receive …","The receiver lagged too far behind and has been forcibly …","An error returned from the recv function on a Receiver.","Error returned by from the send function on a Sender.","An error returned from the try_recv function on a Receiver.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","Future returned from Notify::notified().","","","","Adds this future to the list of futures that are ready to …","","Returns the argument unchanged.","Calls U::from(self).","","","","","","Owned permit to send one value into the channel.","Permits to send one value into the channel.","Receives values from the associated Sender.","Sends values to the associated Receiver.","Receive values from the associated UnboundedSender.","Send values to the associated UnboundedReceiver.","A sender that does not prevent the channel from being …","An unbounded sender that does not prevent the channel from …","Blocking receive to call outside of asynchronous contexts.","Blocking receive to call outside of asynchronous contexts.","Blocking send to call outside of asynchronous contexts.","","","","","","","","","","","","","","","","","Returns the current capacity of the channel.","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel without dropping it.","Closes the receiving half of a channel, without dropping …","Completes when the receiver has dropped.","Completes when the receiver has dropped.","Converts the Sender to a WeakSender that does not count …","Converts the UnboundedSender to a WeakUnboundedSender that …","","","Channel error types.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Checks if the channel has been closed. This happens when …","Checks if the channel has been closed. This happens when …","Returns the maximum buffer capacity of the channel.","Polls to receive the next message on this channel.","Polls to receive the next message on this channel.","Receives the next value for this receiver.","Receives the next value for this receiver.","Releases the reserved capacity without sending a message, …","Waits for channel capacity. Once capacity to send one …","Waits for channel capacity, moving the Sender and …","Returns true if senders belong to the same channel.","Returns true if senders belong to the same channel.","Sends a value, waiting until there is capacity.","Sends a value using the reserved capacity.","Sends a value using the reserved capacity.","Attempts to send a message on this UnboundedSender without …","","","","","","","","","","","","","","","","","","","","","Tries to receive the next value for this receiver.","Tries to receive the next value for this receiver.","Tries to acquire a slot in the channel without waiting for …","Tries to acquire a slot in the channel without waiting for …","Attempts to immediately send a message on this Sender","","","","","","","","","Creates an unbounded mpsc channel for communicating …","Tries to convert a WeakSender into a Sender. This will …","Tries to convert a WeakUnboundedSender into an …","","The receive half of the channel was explicitly closed or …","The channel’s sending half has become disconnected, and …","This channel is currently empty, but the Sender(s) have …","The data could not be sent on the channel because the …","Error returned by the Sender.","Error returned by try_recv.","This enumeration is the list of the possible error …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Receives a value from the associated Sender.","Sends a value to the associated Receiver.","Blocking receive to call outside of asynchronous contexts.","","","","","Creates a new one-shot channel for sending single values …","Prevents the associated Sender handle from sending a value.","Waits for the associated Receiver handle to close.","","","Oneshot error types.","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","Returns true if the associated Receiver handle has been …","","Checks whether the oneshot channel has been closed, and if …","Attempts to send a value on this channel, returning it …","","","","","Attempts to receive a value.","","","The send half of the channel was dropped without sending a …","The send half of the channel has not yet sent a value.","Error returned by the Future implementation for Receiver.","Error returned by the try_recv function on Receiver.","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","Receives values from the associated Sender.","Returns a reference to the inner value.","Sends values to the associated Receiver.","","Returns a reference to the most recently sent value.","","Returns a reference to the most recently sent value","","Returns a reference to the most recently sent value and …","","","","Waits for a change notification, then marks the newest …","Creates a new watch channel, returning the “send” and …","","","Completes when all receivers have dropped.","","","","Watch error types.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Checks if this channel contains a message that this …","Indicates if the borrowed value is considered as changed …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Checks if the channel has been closed. This happens when …","Returns the number of receivers that currently exist.","Returns true if receivers belong to the same channel.","Sends a new value via the channel, notifying all receivers.","Modifies the watched value conditionally in-place, …","Modifies the watched value unconditionally in-place, …","Sends a new value via the channel, notifying all receivers …","Creates a new Receiver connected to this Sender.","","","","","","","","","","","","Error produced when receiving a change notification.","Error produced when sending a value fails.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","An owned permission to abort a spawned task, without …","Task failed to execute to completion.","An owned permission to join on a task (await its …","A collection of tasks spawned on a Tokio runtime.","Context guard for LocalSet","A key for task-local data.","A set of tasks which are executed on the same thread.","Future for the unconstrained method.","Abort the task associated with the handle.","Abort the task associated with the handle.","Aborts all tasks on this JoinSet.","Runs a future to completion on the provided runtime, …","","","","","","","","","","","","","","","","","","","Removes all tasks from this JoinSet without aborting them.","","","","","","Enters the context of this LocalSet.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Task-related futures.","Returns a copy of the task-local value if the task-local …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Consumes the join error, returning the object with which …","Returns true if the error was caused by the task being …","Returns whether the JoinSet is empty.","Checks if the task associated with this AbortHandle has …","Checks if the task associated with this JoinHandle has …","Returns true if the error was caused by the task panicking.","Waits until one of the tasks in the set completes and …","Returns the number of tasks currently in the JoinSet.","Create a new JoinSet.","Returns a new local task set.","","","","","Runs a future to completion on the local set, returning …","Sets a value T as the task-local value for the future F.","Aborts all tasks and waits for them to finish shutting …","Spawns a new asynchronous task, returning a JoinHandle for …","Spawn the provided task on the JoinSet, returning an …","Runs the provided closure on a thread where blocking is …","Spawns a !Send future on the current LocalSet.","Spawn the provided task on the current LocalSet and store …","Spawns a !Send task onto the local task set.","Spawn the provided task on the provided LocalSet and store …","Spawn the provided task on the provided runtime and store …","Sets a value T as the task-local value for the closure F.","","","","","","","","","","","","","","","","","","Consumes the join error, returning the object with which …","Accesses the current task-local and runs the provided …","","","","","","","","","Turn off cooperative scheduling for a future. The future …","Accesses the current task-local and runs the provided …","Yields execution back to the Tokio runtime.","A future that sets a value T of a task local for the …","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,95,2,2,2,2,2,2,2,2,2,2,96,96,2,97,95,96,98,96,96,96,96,2,2,2,97,2,2,2,2,2,2,0,0,21,0,0,21,0,0,0,16,18,19,16,20,23,18,21,19,16,20,23,18,21,19,16,16,16,18,19,16,18,21,19,19,16,20,23,23,18,21,19,16,20,23,18,21,19,18,19,16,20,23,18,21,23,23,19,19,19,19,19,19,23,16,18,18,16,18,16,18,19,19,19,19,16,23,16,19,16,20,23,18,21,19,16,20,23,18,21,19,16,20,23,18,21,19,0,51,0,0,50,51,28,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,28,28,28,28,34,34,37,37,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,0,41,42,41,42,28,34,41,43,37,35,36,44,45,46,47,38,39,48,35,36,44,46,47,39,48,46,39,35,36,44,45,46,47,38,39,48,41,29,32,41,50,51,34,35,35,36,36,44,44,45,45,46,46,47,47,38,38,39,39,48,48,41,54,42,55,55,43,50,50,30,30,28,29,32,37,51,51,29,32,34,34,34,35,36,44,45,46,47,38,39,48,41,41,41,54,42,55,43,50,30,28,29,32,37,37,37,51,0,41,34,41,37,41,41,41,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,41,37,46,39,51,28,51,42,34,34,35,44,45,46,47,38,39,48,29,32,0,35,36,34,41,54,43,28,37,41,43,43,43,0,55,50,30,51,37,37,41,41,41,42,35,36,44,45,46,47,38,39,48,55,50,30,51,28,28,28,28,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,34,35,44,45,46,47,38,39,48,37,37,37,37,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,54,0,37,37,37,99,100,0,0,59,60,59,60,0,59,59,59,60,0,59,60,59,60,59,60,60,60,59,60,60,59,59,59,59,60,59,60,60,59,60,62,61,63,63,61,63,0,0,0,62,61,63,62,61,63,61,63,61,63,61,63,62,62,61,61,63,63,62,61,63,62,61,63,62,61,63,61,63,62,61,63,62,61,63,62,61,63,62,61,63,101,102,0,58,58,58,58,58,58,58,58,58,58,58,58,0,0,0,0,0,0,0,0,64,65,66,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,0,66,68,69,70,66,68,69,70,64,65,66,69,66,69,71,72,0,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,69,66,64,65,64,65,72,66,66,66,69,66,71,72,69,66,68,69,70,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,64,65,66,66,66,66,68,71,72,64,69,70,65,0,68,70,67,74,73,73,74,0,0,0,67,74,73,67,74,73,73,73,74,73,67,67,74,74,73,73,67,74,74,73,67,74,73,67,74,73,73,67,74,73,67,74,73,67,74,73,67,74,73,103,104,0,0,75,77,75,77,75,0,75,77,77,75,0,77,75,77,75,77,75,75,77,75,77,77,77,75,77,75,75,77,75,78,78,0,0,76,78,76,78,76,78,76,78,76,78,76,76,78,78,76,78,76,78,76,78,76,78,76,78,76,78,76,78,76,78,0,0,0,79,79,81,81,80,79,79,81,80,79,0,79,79,81,80,79,81,0,79,81,80,79,81,80,79,80,79,81,80,81,81,79,81,81,81,81,81,79,79,81,80,79,81,80,79,81,80,83,0,0,83,82,83,82,82,82,83,83,82,82,83,82,83,82,83,82,82,83,82,83,82,83,82,83,82,0,0,0,0,0,0,0,0,84,1,85,86,84,88,1,87,89,85,86,93,84,88,1,87,89,85,86,93,85,86,85,84,1,87,85,86,86,84,88,88,1,87,89,85,86,84,88,1,87,89,85,86,93,0,89,84,88,1,87,89,85,86,93,1,86,93,88,88,85,84,1,88,85,85,85,86,1,86,93,88,86,89,85,0,85,0,0,85,86,85,85,89,88,84,88,1,87,89,85,86,93,84,88,1,87,89,85,86,93,88,89,84,88,1,87,89,85,86,93,0,89,0,0,94,94,94,94,94,94,94,94,94,94,94],"f":[0,0,0,0,0,0,0,[[],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,3]],[[2,3]],[[]],[[]],[2,3],[2],[[4,3]],[2],[2],[[2,5],6],[[]],[2],[[2,3]],[2],[2],[2],[[]],[[],7],[[],7],[[],2],[[4,8],[[11,[[10,[9]]]]]],[[4,8],[[11,[10]]]],[[4,8],[[11,[[13,[12]]]]]],[[4,8,2],[[11,[10]]]],[[4,8],[[11,[[13,[12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[2],[2,3],[[2,3]],[[4,14],10],[[2,3],2],[[],13],[[],13],[[],15],[2],[[],2],0,0,0,0,0,0,0,0,0,[[16,17]],[[18,17]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,[[10,[18]]]],[16,16],[[]],[[],16],[18],[19,19],[16,20],[18,20],[[21,21],7],[[19,22],19],[[19,5],6],[[16,5],6],[[20,5],6],[[23,5],6],[[23,5],6],[[18,5],6],[[21,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[19,22],19],[18,16],[[]],[[]],[[]],[[]],[[]],[[]],[23,7],[23,7],[[19,3],19],[[],19],[19,19],[19,19],[19,19],[19,19],[24],[16,21],[18],[[18,25]],[16,1],[18,1],[16,1],[18,1],[[19,25],19],[[19,[27,[26]]],19],[19,19],[[19,3],19],[[]],[[],26],[[],[[13,[16,23]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[19,3],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[28,[[13,[29,30]]]],[[28,22],[[13,[29,30]]]],[[[31,[28]],22],[[13,[32,30]]]],[[[31,[28]]],[[13,[32,30]]]],[[28,3]],[28,3],[[[34,[33]]],[[35,[33]]]],[[[31,[[34,[33]]]]],[[36,[33]]]],[[[37,[33]]],[[38,[33]]]],[[[37,[33]]],[[39,[33]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[[41,[40]]],[[41,[40]]]],[42,42],[[]],[[]],[28],[[],34],[[],41],[[],43],[[],[[37,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[45,[33,33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[38,[33]]]],[[[39,[33]]]],[[[48,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[39,[33]]]],[[[48,[33]]]],[[[46,[33]]],[[45,[33]]]],[[[39,[33]]],[[38,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[45,[33,33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[38,[33]]]],[[[39,[33]]]],[[[48,[33]]]],[41],[29],[32],[[[41,[49]],41],7],[[50,50],7],[[[51,[49]],51],7],[[[34,[33]],5],6],[[[35,[[0,[33,52]]]],5],6],[[[35,[[0,[33,53]]]],5],6],[[[36,[[0,[33,52]]]],5],6],[[[36,[[0,[33,53]]]],5],6],[[[44,[[0,[33,52]]]],5],6],[[[44,[[0,[33,53]]]],5],6],[[[45,[33,33]],5],6],[[[45,[33,33]],5],6],[[[46,[33]],5],6],[[[46,[33]],5],6],[[[47,[33,33]],5],6],[[[47,[33,33]],5],6],[[[38,[33]],5],6],[[[38,[33]],5],6],[[[39,[33]],5],6],[[[39,[33]],5],6],[[[48,[33]],5],6],[[[48,[33]],5],6],[[[41,[52]],5],6],[[54,5],6],[[42,5],6],[[55,5],6],[[55,5],6],[[43,5],6],[[50,5],6],[[50,5],6],[[30,5],6],[[30,5],6],[[28,5],6],[[29,5],6],[[32,5],6],[[[37,[[0,[52,33]]]],5],6],[[[51,[52]],5],6],[[51,5],6],[29],[32],[[],34],[56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[56],[[],41],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[]],[56],[[]],0,[41,57],[[[34,[33]]]],[41,57],[[[37,[33]]]],[41],[41,13],[41,7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[34,[33]]],33],[41,57],[[[37,[33]]],33],[[[46,[33]]],[[47,[33]]]],[[[39,[33]]],[[48,[33]]]],[51,7],[28,7],[51,7],[42,7],[[[34,[33]]],[[35,[33]]]],[[[31,[[34,[33]]]]],[[36,[33]]]],[[[35,[33]]],44],[[[44,[33]]],44],[[[45,[33,33]]],[[45,[33,33]]]],[[[46,[33]]],[[47,[33,33]]]],[[[47,[33,33]]],[[47,[33,33]]]],[[[38,[33]]],[[38,[33]]]],[[[39,[33]]],[[48,[33]]]],[[[48,[33]]],[[48,[33]]]],[[29,29]],[[32,32]],0,[[[35,[33]]],34],[[[36,[33]]],31],[33,[[34,[33]]]],[[],41],[3,54],[[],43],[3,28],[33,[[37,[33]]]],[57,41],[43,58],[43],[43],0,[24],[24],[24],[24],[[[37,[33]]],[[38,[33]]]],[[[31,[[37,[33]]]]],[[45,[33]]]],[41,[[13,[51]]]],[41,57],[[]],[[]],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[28,[[13,[29,50]]]],[[28,22],[[13,[29,50]]]],[[[31,[28]],22],[[13,[32,50]]]],[[[31,[28]]],[[13,[32,50]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[[34,[33]]],[[13,[[35,[33]],55]]]],[[[31,[[34,[33]]]]],[[13,[[36,[33]],55]]]],[[[35,[33]]],[[13,[44,[35,[33]]]]]],[[[44,[33]]],[[13,[44,[44,[33]]]]]],[[[45,[33,33]]],[[13,[[45,[33,33]],[45,[33,33]]]]]],[[[46,[33]]],[[13,[[47,[33,33]],[46,[33]]]]]],[[[47,[33,33]]],[[13,[[47,[33,33]],[47,[33,33]]]]]],[[[38,[33]]],[[13,[[38,[33]],[38,[33]]]]]],[[[39,[33]]],[[13,[[48,[33]],[39,[33]]]]]],[[[48,[33]]],[[13,[[48,[33]],[48,[33]]]]]],[[[37,[33]]],[[13,[[38,[33]],55]]]],[[[31,[[37,[33]]]]],[[13,[[45,[33]],55]]]],[[[37,[33]]],[[13,[[39,[33]],55]]]],[[[31,[[37,[33]]]]],[[13,[[46,[33]],55]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[54,42],0,[[33,22],[[37,[33]]]],[[[37,[33]]],[[39,[33]]]],[[[31,[[37,[33]]]]],[[46,[33]]]],0,0,0,0,[[]],[[]],[[]],[[]],[3],[59,59],[[]],[59],[60],0,[[59,5],6],[[60,5],6],[[]],[[]],[[]],[[]],[60,7],[60,3],[59,3],[[[60,[40]]],[[13,[40,61]]]],[[[60,[40]]],[[60,[40]]]],[59,[[13,[3,62]]]],[59,60],[[]],[[],13],[[],13],[[],13],[[],13],[[[60,[40]]],[[13,[40,63]]]],[[],15],[[],15],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[61,61],[63,63],[[]],[[]],[[61,61],7],[[63,63],7],[[62,5],6],[[[62,[52]],5],6],[[61,5],6],[[61,5],6],[[63,5],6],[[63,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[24],[24],[24],[[]],[[]],[[],26],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,[[]],[[]],[58],[[[4,[58]]],7],[[58,5],6],[[]],[[]],[[]],[[[4,[58]],8],11],[[],13],[[],13],[[],15],0,0,0,0,0,0,0,0,[64,57],[65,57],[66,[[13,[67]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[66,3],[3],[66,66],[68,68],[69,69],[70,70],[[]],[[]],[[]],[[]],[64],[65],[66],[69],[66,68],[69,70],[71],[72],0,[[66,5],6],[[68,5],6],[[71,5],6],[[72,5],6],[[64,5],6],[[69,5],6],[[70,5],6],[[65,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[66,7],[69,7],[66,3],[[64,8],[[11,[57]]]],[[65,8],[[11,[57]]]],[64,57],[65,57],[72,66],[66,[[13,[71,67]]]],[66,[[13,[72,67]]]],[[66,66],7],[[69,69],7],[66,[[13,[67]]]],[71],[72,66],[69,[[13,[67]]]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[64,[[13,[73]]]],[65,[[13,[73]]]],[66,[[13,[71,74]]]],[66,[[13,[72,[74,[66]]]]]],[66,[[13,[74]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[]],[68,[[57,[66]]]],[70,[[57,[69]]]],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[73,73],[[]],[[[74,[49]],74],7],[[73,73],7],[[67,5],6],[[[67,[52]],5],6],[[[74,[52]],5],6],[[74,5],6],[[73,5],6],[[73,5],6],[[]],[[]],[67,74],[[]],[[]],[[]],[[]],[24],[24],[24],[[]],[[],26],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,0,[75,[[13,[76]]]],[[]],[[]],[[]],[[]],[[]],[75],[77],[77],[75],0,[[[77,[52]],5],6],[[[75,[52]],5],6],[[]],[[]],[[]],[[]],[[]],[77,7],[[[4,[75]],8],11],[[77,8],11],[77,13],[[],13],[[],13],[[],13],[[],13],[75,[[13,[78]]]],[[],15],[[],15],0,0,0,0,[[]],[[]],[[]],[[]],[76,76],[78,78],[[]],[[]],[[76,76],7],[[78,78],7],[[76,5],6],[[76,5],6],[[78,5],6],[[78,5],6],[[]],[[]],[[]],[[]],[24],[24],[[]],[[]],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],0,0,0,[[]],[79,80],[[]],[81,80],[[]],[79,80],[[]],[[]],[[]],[79,[[13,[82]]]],[[]],[79,79],[[]],[81],[80],[79],[81],0,[[[79,[52]],5],6],[[[81,[52]],5],6],[[[80,[52]],5],6],[[]],[[]],[[]],[79,[[13,[7,82]]]],[80,7],[[]],[[]],[[]],[81,7],[81,3],[[79,79],7],[81,[[13,[83]]]],[81,7],[81],[81],[81,79],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,[[]],[[]],[[]],[[]],[82,82],[[]],[[[83,[52]],5],6],[[[83,[52]],5],6],[[82,5],6],[[82,5],6],[[]],[[]],[[]],[[]],[24],[24],[[]],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],0,0,0,0,0,0,0,0,[84],[1],[85],[[86,18]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],85],[[],86],[85],[84],[1],[87],[85],[86],[86,87],[[84,5],6],[[88,5],6],[[88,5],6],[[1,5],6],[[87,5],6],[[89,5],6],[[85,5],6],[[86,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[[89,[90]]],90],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[88,[[92,[91]]]],[88,7],[85,7],[84,7],[1,7],[88,7],[85,[[57,[[13,[88]]]]]],[85,3],[[],85],[[],86],[[[4,[1]],8],11],[[[4,[86]],8],11],[[[4,[93]],8],11],[24],[86],[89,94],[85],[[],1],[85,84],[[],1],[[],1],[85,84],[86,1],[[85,86],84],[[85,16],84],[89],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[88,[[13,[[92,[91]],88]]]],[89,[[13,[0]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],93],[89],[[]],0,[[]],[[]],[94],[[94,5],6],[[]],[[]],[[]],[[[4,[[94,[17]]]],8],11],[[],13],[[],13],[[],15]],"p":[[3,"JoinHandle"],[3,"ReadBuf"],[15,"usize"],[3,"Pin"],[3,"Formatter"],[6,"Result"],[15,"bool"],[3,"Context"],[15,"u64"],[6,"Result"],[4,"Poll"],[3,"Error"],[4,"Result"],[4,"SeekFrom"],[3,"TypeId"],[3,"Handle"],[8,"Future"],[3,"Runtime"],[3,"Builder"],[3,"EnterGuard"],[4,"RuntimeFlavor"],[15,"u32"],[3,"TryCurrentError"],[3,"Demand"],[3,"Duration"],[3,"String"],[8,"Into"],[3,"Semaphore"],[3,"SemaphorePermit"],[3,"AcquireError"],[3,"Arc"],[3,"OwnedSemaphorePermit"],[8,"Sized"],[3,"Mutex"],[3,"MutexGuard"],[3,"OwnedMutexGuard"],[3,"RwLock"],[3,"RwLockReadGuard"],[3,"RwLockWriteGuard"],[8,"Clone"],[3,"OnceCell"],[3,"BarrierWaitResult"],[3,"Notify"],[3,"MappedMutexGuard"],[3,"OwnedRwLockReadGuard"],[3,"OwnedRwLockWriteGuard"],[3,"OwnedRwLockMappedWriteGuard"],[3,"RwLockMappedWriteGuard"],[8,"PartialEq"],[4,"TryAcquireError"],[4,"SetError"],[8,"Debug"],[8,"Display"],[3,"Barrier"],[3,"TryLockError"],[15,"never"],[4,"Option"],[3,"Notified"],[3,"Sender"],[3,"Receiver"],[4,"RecvError"],[3,"SendError"],[4,"TryRecvError"],[3,"Receiver"],[3,"UnboundedReceiver"],[3,"Sender"],[3,"SendError"],[3,"WeakSender"],[3,"UnboundedSender"],[3,"WeakUnboundedSender"],[3,"Permit"],[3,"OwnedPermit"],[4,"TryRecvError"],[4,"TrySendError"],[3,"Receiver"],[3,"RecvError"],[3,"Sender"],[4,"TryRecvError"],[3,"Receiver"],[3,"Ref"],[3,"Sender"],[3,"RecvError"],[3,"SendError"],[3,"AbortHandle"],[3,"JoinSet"],[3,"LocalSet"],[3,"LocalEnterGuard"],[3,"JoinError"],[3,"LocalKey"],[8,"Copy"],[8,"Any"],[3,"Box"],[3,"Unconstrained"],[3,"TaskLocalFuture"],[8,"AsyncBufRead"],[8,"AsyncWrite"],[8,"AsyncSeek"],[8,"AsyncRead"],[13,"AlreadyInitializedError"],[13,"InitializingError"],[13,"Lagged"],[13,"Lagged"],[13,"Full"],[13,"Closed"]]},\ +"tokio_macros":{"doc":"Macros for use with Tokio","t":[23,23,23,23,23,23],"n":["main","main_fail","main_rt","test","test_fail","test_rt"],"q":["tokio_macros","","","","",""],"d":["Marks async function to be executed by the selected …","Always fails with the error message below.","Marks async function to be executed by selected runtime. …","Marks async function to be executed by runtime, suitable …","Always fails with the error message below.","Marks async function to be executed by runtime, suitable …"],"i":[0,0,0,0,0,0],"f":[0,0,0,0,0,0],"p":[]},\ +"typed_builder":{"doc":"","t":[24],"n":["TypedBuilder"],"q":["typed_builder"],"d":["TypedBuilder is not a real type - deriving it will …"],"i":[0],"f":[0],"p":[]},\ +"typenum":{"doc":"This crate provides type-level numbers evaluated at …","t":[2,2,2,3,6,3,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,2,2,0,14,14,0,11,11,11,11,11,11,11,11,11,11,11,11,14,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,0,14,0,11,11,11,14,11,11,11,11,11,11,11,11,11,11,11,11,0,0,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,11,11,11,2,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,8,18,18,18,18,18,18,18,18,18,18,8,8,8,8,8,18,18,18,18,18,18,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,8,8,8,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,8,6,16,16,2,8,6,3,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["ATerm","B0","B1","Equal","False","Greater","Less","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","NInt","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","PInt","TArr","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","UInt","UTerm","Z0","array","assert_type","assert_type_eq","bit","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","cmp","cmp","cmp","cmp","consts","default","default","default","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","int","into","into","into","marker_traits","op","operator_aliases","partial_cmp","partial_cmp","partial_cmp","tarr","to_ordering","to_ordering","to_ordering","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_operators","uint","ATerm","TArr","add","add","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","div","div","eq","eq","fmt","fmt","from","from","hash","hash","into","into","len","len","mul","mul","neg","neg","partial_cmp","partial_cmp","partial_div","partial_div","rem","rem","sub","sub","try_from","try_from","try_into","try_into","type_id","type_id","B0","B1","Bit","bitand","bitand","bitand","bitor","bitor","bitor","bitxor","bitxor","bitxor","bitxor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","default","default","eq","eq","fmt","fmt","from","from","hash","hash","into","into","max","max","max","max","min","min","min","min","new","new","new","new","not","not","partial_cmp","partial_cmp","to_bool","to_bool","to_u8","to_u8","try_from","try_from","try_into","try_into","type_id","type_id","B0","B1","False","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","Z0","powi","powi","powi","Integer","NInt","PInt","Z0","add","add","add","add","add","add","add","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","cmp","cmp","cmp","default","default","default","div","div","div","div","div","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","into","into","into","max","max","max","max","max","max","max","max","max","min","min","min","min","min","min","min","min","min","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","neg","neg","neg","new","new","new","partial_cmp","partial_cmp","partial_cmp","partial_div","partial_div","partial_div","powi","powi","powi","powi","powi","powi","powi","powi","rem","rem","rem","rem","rem","sub","sub","sub","sub","sub","sub","sub","sub","sub","to_i16","to_i16","to_i16","to_i32","to_i32","to_i32","to_i64","to_i64","to_i64","to_i8","to_i8","to_i8","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_isize","to_isize","to_isize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","BOOL","Bit","I16","I16","I32","I32","I64","I64","I8","I8","ISIZE","ISIZE","Integer","NonZero","Ord","PowerOfTwo","TypeArray","U16","U32","U64","U8","U8","USIZE","Unsigned","Zero","new","to_bool","to_i16","to_i16","to_i32","to_i32","to_i64","to_i64","to_i8","to_i8","to_isize","to_isize","to_ordering","to_u16","to_u32","to_u64","to_u8","to_u8","to_usize","AbsVal","Add1","And","Compare","Cube","Diff","Double","Eq","Exp","Gcf","Gr","GrEq","Le","LeEq","Length","Log2","Maximum","Minimum","Mod","Negate","NotEq","Or","PartialQuot","Prod","Quot","Shleft","Shright","Sqrt","Square","Sub1","Sum","Xor","Abs","Cmp","Gcd","IsEqual","IsGreater","IsGreaterOrEqual","IsLess","IsLessOrEqual","IsNotEqual","Len","Logarithm2","Max","Min","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","PartialDiv","Pow","Same","SquareRoot","ToInt","is_equal","is_greater","is_greater_or_equal","is_less","is_less_or_equal","is_not_equal","len","max","min","partial_div","powi","to_int","GetBit","GetBitOut","Output","Output","PowerOfTwo","SetBit","SetBitOut","UInt","UTerm","Unsigned","add","add","add","add","add","add","add","add","add","add","add","bitand","bitand","bitor","bitor","bitor","bitor","bitor","bitor","bitxor","bitxor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","default","default","div","div","eq","eq","fmt","fmt","from","from","hash","hash","into","into","len","len","max","max","min","min","mul","mul","mul","mul","mul","mul","mul","mul","new","new","partial_cmp","partial_cmp","partial_div","partial_div","powi","powi","rem","rem","set_bit","set_bit","shl","shl","shl","shl","shl","shl","shl","shr","shr","shr","shr","shr","shr","shr","sub","sub","sub","sub","sub","sub","sub","to_i16","to_i16","to_i32","to_i32","to_i64","to_i64","to_i8","to_i8","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_isize","to_isize","to_u16","to_u16","to_u32","to_u32","to_u64","to_u64","to_u8","to_u8","to_usize","to_usize","try_from","try_from","try_into","try_into","type_id","type_id"],"q":["typenum","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::array","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::bit","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::consts","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::int","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::marker_traits","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::operator_aliases","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::type_operators","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::uint","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","A potential output from Cmp, this is the type equivalent …","","A potential output from Cmp, this is the type equivalent …","A potential output from Cmp, this is the type equivalent …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A type-level array of type-level numbers.","Asserts that a type is True, aka B1.","Asserts that two types are the same.","Type-level bits.","","","","","","","","","","","","","A convenience macro for comparing type numbers. Use op! …","Type aliases for many constants.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Type-level signed integers.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","All of the marker traits used in typenum.","Convenient type operations.","Aliases for the type operators used in this crate. Their …","","","","Create a new type-level arrray. Only usable on Rust 1.13.0 …","","","","","","","","","","","","","Useful type operators that are not defined in core::ops.","Type-level unsigned integers.","The terminating type for type arrays.","TArr is a type that acts as an array of types. It is …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","The type-level bit 0.","The type-level bit 1.","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","Instantiates a singleton representing this bit.","","Instantiates a singleton representing this bit.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Type-level signed integers with negative sign.","Type-level signed integers with positive sign.","The type-level signed integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Instantiates a singleton representing this strictly …","Instantiates a singleton representing this strictly …","Instantiates a singleton representing the integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The marker trait for compile time bits.","","","","","","","","","","","The marker trait for compile time signed integers.","A marker trait to designate that a type is not zero. All …","A Marker trait for the types Greater, Equal, and Less.","The marker trait for type-level numbers which are a power …","The marker trait for type-level arrays of type-level …","","","","","","","The marker trait for compile time unsigned integers.","A marker trait to designate that a type is zero. Only B0, …","Instantiates a singleton representing this bit.","","","","","","","","","","","","","","","","","","","Alias for the associated type of Abs: …","Alias to make it easy to add 1: …","Alias for the associated type of BitAnd: …","Alias for the associated type of Cmp: …","Alias to make it easy to cube. …","Alias for the associated type of Sub: …","Alias to make it easy to multiply by 2. …","Alias for the associated type of IsEqual: …","Alias for the associated type of Pow: …","Alias for the associated type of Gcd: …","Alias for the associated type of IsGreater: …","Alias for the associated type of IsGreaterOrEqual: …","Alias for the associated type of IsLess: …","Alias for the associated type of IsLessOrEqual: …","Alias for the associated type of Len: …","Alias for the associated type of Logarithm2: …","Alias for the associated type of Max: …","Alias for the associated type of Min: …","Alias for the associated type of Rem: …","Alias for the associated type of Neg: …","Alias for the associated type of IsNotEqual: …","Alias for the associated type of BitOr: …","Alias for the associated type of PartialDiv: …","Alias for the associated type of Mul: …","Alias for the associated type of Div: …","Alias for the associated type of Shl: …","Alias for the associated type of Shr: …","Alias for the associated type of SquareRoot: …","Alias to make it easy to square. …","Alias to make it easy to subtract 1: …","Alias for the associated type of Add: …","Alias for the associated type of BitXor: …","A type operator that returns the absolute value.","A type operator for comparing Self and Rhs. It provides a …","A type operator that computes the greatest common divisor …","A type operator that returns True if Self == Rhs, …","A type operator that returns True if Self > Rhs, otherwise …","A type operator that returns True if Self >= Rhs, …","A type operator that returns True if Self < Rhs, otherwise …","A type operator that returns True if Self <= Rhs, …","A type operator that returns True if Self != Rhs, …","A type operator that gives the length of an Array or the …","A type operator for taking the integer binary logarithm of …","A type operator that returns the maximum of Self and Rhs.","A type operator that returns the minimum of Self and Rhs.","Should always be Self","The absolute value.","The result of the exponentiation.","The result of the comparison. It should only ever be one …","The length as a type-level unsigned integer.","The type of the result of the division","The type of the minimum of Self and Rhs","The type of the maximum of Self and Rhs","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The result of the integer square root.","The result of the integer binary logarithm.","The greatest common divisor.","Division as a partial function. This type operator …","A type operator that provides exponentiation by repeated …","A type operator that ensures that Rhs is the same as Self, …","A type operator for taking the integer square root of Self.","A type operator for taking a concrete integer value from a …","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","This function isn’t used in this crate, but may be …","Method returning the maximum","Method returning the minimum","Method for performing the division","This function isn’t used in this crate, but may be …","Method returning the concrete value for the type.","","","","","","A type operator that, when implemented for unsigned …","Alias for the result of calling SetBit: …","UInt is defined recursively, where B is the least …","The terminating type for UInt; it always comes after the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","Instantiates a singleton representing this unsigned …","Instantiates a singleton representing this unsigned …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,1,2,3,1,2,3,1,2,3,0,0,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,0,1,2,3,0,0,0,1,2,3,0,1,2,3,1,2,3,1,2,3,1,2,3,0,0,0,0,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,0,0,0,19,21,21,19,19,21,19,19,21,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,19,21,21,19,19,21,21,19,19,21,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,27,27,0,0,0,0,29,29,29,26,26,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,26,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,29,26,26,26,30,30,30,29,29,29,26,26,26,30,30,30,29,29,29,29,29,26,26,26,26,26,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,26,26,26,30,30,30,29,29,26,26,30,29,29,29,26,26,26,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,29,29,26,26,26,26,30,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,20,0,24,31,24,31,24,31,24,31,24,31,0,0,0,0,0,24,24,24,20,24,24,0,0,20,20,24,31,24,31,24,31,24,31,24,31,43,24,24,24,20,24,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,0,0,0,0,0,53,54,57,52,55,56,48,51,50,49,46,61,0,0,62,63,0,0,0,0,0,0,28,28,28,28,28,28,28,28,42,42,42,28,42,28,28,28,28,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,28,28,28,28,42,42,42,28,42,28,42,28,42,28,42,28,42,28,42,28,28,28,28,42,42,42,28,28,28,28,42,42,42,28,28,28,28,28,42,42,28,42,28,42,28,42,28,42,28,28,28,28,28,28,28,28,28,42,42,42,42,42,42,42,42,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[[1,1],4],[[2,2],4],[[3,3],4],0,0,[[],1],[[],2],[[],3],[[1,1],5],[[2,2],5],[[3,3],5],[[1,6],7],[[2,6],7],[[3,6],7],[[]],[[]],[[]],[1],[2],[3],0,[[]],[[]],[[]],0,0,0,[[1,1],[[8,[4]]]],[[2,2],[[8,[4]]]],[[3,3],[[8,[4]]]],0,[[],4],[[],4],[[],4],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],[[],10],0,0,0,0,[[11,11]],[[12,12]],[[]],[[]],[[]],[[]],[11,11],[[[12,[13,13]]],[[12,[13,13]]]],[[11,11],4],[[[12,[14,14]],12],4],[11],[12],[[11,11],5],[[[12,[15,15]],12],5],[[11,6],7],[[[12,[16,16]],6],7],[[]],[[]],[11],[[[12,[17,17]]]],[[]],[[]],[11],[12],[11],[12],[11],[12],[[11,11],[[8,[4]]]],[[[12,[18,18]],12],[[8,[4]]]],[11],[12],[11],[12],[[11,11]],[[12,12]],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],0,0,0,[[19,20]],[[21,19]],[[21,21]],[[19,19]],[[19,21]],[[21,20]],[[19,19]],[[19,21]],[[21,21]],[[21,19]],[[]],[[]],[[]],[[]],[19,19],[21,21],[[19,19],4],[[21,21],4],[[],19],[[],21],[[19,19],5],[[21,21],5],[[19,6],7],[[21,6],7],[[]],[[]],[19],[21],[[]],[[]],[[19,21],21],[[19,19],19],[[21,19],21],[[21,21],21],[[19,21],19],[[19,19],19],[[21,19],19],[[21,21],21],[[],19],[[],19],[[],21],[[],21],[19],[21],[[19,19],[[8,[4]]]],[[21,21],[[8,[4]]]],[[],5],[[],5],[[],22],[[],22],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[23,[26,[[0,[24,25]]]]]],[[27,[26,[[28,[24,21]]]]]],[[27,[26,[[28,[24,19]]]]]],0,0,0,0,[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[30,31]],[[]],[[]],[[]],[[]],[[]],[[]],[[[29,[[0,[13,24,25]]]]],[[29,[[0,[13,24,25]]]]]],[[[26,[[0,[13,24,25]]]]],[[26,[[0,[13,24,25]]]]]],[30,30],[[[29,[[0,[14,24,25]]]],29],4],[[[26,[[0,[14,24,25]]]],26],4],[[30,30],4],[[],[[29,[[0,[32,24,25]]]]]],[[],[[26,[[0,[32,24,25]]]]]],[[],30],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[30,[0,[31,25]]]],[[[29,[[0,[15,24,25]]]],29],5],[[[26,[[0,[15,24,25]]]],26],5],[[30,30],5],[[[29,[[0,[16,24,25]]]],6],7],[[[26,[[0,[16,24,25]]]],6],7],[[30,6],7],[[]],[[]],[[]],[[[29,[[0,[17,24,25]]]]]],[[[26,[[0,[17,24,25]]]]]],[30],[[]],[[]],[[]],[[29,30]],[[29,29]],[[29,26]],[[26,26]],[[26,30]],[[26,29]],[[30,26]],[[30,29]],[[30,30]],[[29,26]],[[29,29]],[[29,30]],[[26,26]],[[26,29]],[[26,30]],[[30,30]],[[30,29]],[[30,26]],[[29,12]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[29,11]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[26,11]],[[26,12]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],30]],[[30,12]],[[30,11]],[[30,31]],[[[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]]]],[30],[[],[[29,[[0,[24,25]]]]]],[[],[[26,[[0,[24,25]]]]]],[[],30],[[[29,[[0,[18,24,25]]]],29],[[8,[4]]]],[[[26,[[0,[18,24,25]]]],26],[[8,[4]]]],[[30,30],[[8,[4]]]],[[]],[[]],[[]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[29,[[28,[24,21]]]]]],[[[26,[[0,[24,25]]]],[29,[[28,[24,19]]]]]],[[30,30]],[[30,[29,[[0,[24,25]]]]]],[[30,[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[30,[0,[31,25]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[30,[26,[[0,[24,25]]]]]],[[30,[29,[[0,[24,25]]]]]],[[30,30]],[[],33],[[],33],[[],33],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],36],[[],36],[[],36],[[],34],[[],36],[[],35],[[],33],[[],36],[[],33],[[],34],[[],35],[[],36],[[],33],[[],34],[[],35],[[],37],[[],37],[[],37],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],[[],10],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],5],[[],33],[[],33],[[],34],[[],34],[[],35],[[],35],[[],36],[[],36],[[],37],[[],37],[[],4],[[],38],[[],39],[[],40],[[],22],[[],22],[[],41],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[[[28,[24,20]],19]],[[[28,[24,19]],21]],[[[28,[24,21]],21]],[[[28,[24,20]],42]],[[[28,[24,19]],[28,[24,19]]]],[[[28,[24,19]],[28,[24,21]]]],[[[28,[24,21]],[28,[24,19]]]],[[[28,[24,21]],[28,[24,21]]]],[[42,19]],[[42,21]],[[42,24]],[[[28,[24,20]],24]],[[42,24]],[[[28,[24,20]],42]],[[[28,[24,21]],[28,[24,19]]]],[[[28,[24,21]],[28,[24,21]]]],[[[28,[24,19]],[28,[24,19]]]],[[[28,[24,19]],[28,[24,21]]]],[[42,24]],[[[28,[24,20]],24]],[[42,24]],[[]],[[]],[[]],[[]],[[[28,[13,13]]],[[28,[13,13]]]],[42,42],[[[28,[14,14]],28],4],[[42,42],4],[[],[[28,[32,32]]]],[[],42],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[[28,[15,15]],28],5],[[42,42],5],[[[28,[16,16]],6],7],[[42,6],7],[[]],[[]],[[[28,[17,17]]]],[42],[[]],[[]],[[[28,[24,20]]]],[42],[28],[42],[28],[42],[[[28,[24,21]],[28,[24,20]]]],[[[28,[24,20]],42]],[[[28,[24,19]],[28,[24,20]]]],[[[28,[24,20]],19]],[[[28,[24,20]],21]],[[42,21]],[[42,19]],[[42,24]],[[],[[28,[24,20]]]],[[],42],[[[28,[18,18]],28],[[8,[4]]]],[[42,42],[[8,[4]]]],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[]],[[]],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[]],[[]],[[[28,[24,20]],21]],[[[28,[24,20]],[28,[24,20]]]],[[[28,[24,20]],42]],[[[28,[24,20]],19]],[[42,24]],[[42,19]],[[42,21]],[[[28,[24,20]],[28,[24,20]]]],[[[28,[24,20]],21]],[[[28,[24,20]],19]],[[[28,[24,20]],42]],[[42,19]],[[42,24]],[[42,21]],[[[28,[24,20]],19]],[[[28,[[28,[24,20]],21]],21]],[[[28,[42,21]],21]],[[[28,[24,20]],24]],[[[28,[24,19]],21]],[[42,19]],[[42,42]],[[],33],[[],33],[[],34],[[],34],[[],35],[[],35],[[],36],[[],36],[[],41],[[],36],[[],40],[[],33],[[],34],[[],35],[[],22],[[],38],[[],39],[[],40],[[],39],[[],33],[[],34],[[],36],[[],41],[[],35],[[],22],[[],38],[[],37],[[],37],[[],38],[[],38],[[],39],[[],39],[[],40],[[],40],[[],22],[[],22],[[],41],[[],41],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10]],"p":[[3,"Greater"],[3,"Less"],[3,"Equal"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[4,"Result"],[3,"TypeId"],[3,"ATerm"],[3,"TArr"],[8,"Clone"],[8,"Ord"],[8,"PartialEq"],[8,"Debug"],[8,"Hash"],[8,"PartialOrd"],[3,"B0"],[8,"Bit"],[3,"B1"],[15,"u8"],[6,"P1"],[8,"Unsigned"],[8,"NonZero"],[3,"NInt"],[6,"N1"],[3,"UInt"],[3,"PInt"],[3,"Z0"],[8,"Integer"],[8,"Default"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"usize"],[3,"UTerm"],[8,"Ord"],[8,"Same"],[8,"Abs"],[8,"Pow"],[8,"Cmp"],[8,"Len"],[8,"PartialDiv"],[8,"Min"],[8,"Max"],[8,"IsLess"],[8,"IsEqual"],[8,"IsGreater"],[8,"IsLessOrEqual"],[8,"IsNotEqual"],[8,"IsGreaterOrEqual"],[8,"SquareRoot"],[8,"Logarithm2"],[8,"Gcd"],[8,"ToInt"],[8,"GetBit"],[8,"SetBit"]]},\ +"uint":{"doc":"Efficient large, fixed-size big integers and hashes.","t":[4,3,3,4,13,13,13,13,13,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14],"n":["FromDecStrErr","FromHexError","FromStrRadixErr","FromStrRadixErrKind","InvalidCharacter","InvalidCharacter","InvalidLength","InvalidLength","UnsupportedRadix","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","construct_uint","construct_uint","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hash","into","into","into","into","kind","provide","provide","provide","source","source","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unroll"],"q":["uint","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Conversion from decimal string error","","The error type for parsing numbers from strings.","A list of error categories encountered when parsing …","A character in the input string is not valid for the given …","Char not from range 0-9","The input length is not valid for the given radix.","Value does not fit into type","The given radix is not supported.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the corresponding FromStrRadixErrKind for this …","","","","","","","","","","","","","","","","","","","","","","Unroll the given for loop"],"i":[0,0,0,0,1,3,1,3,1,1,6,3,7,1,6,3,7,1,1,0,0,1,3,1,6,6,3,3,7,7,1,6,6,6,3,7,1,1,6,3,7,6,6,3,7,6,7,1,6,3,7,1,6,3,7,1,6,3,7,1,6,3,7,0],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[[]],0,0,[[1,1],2],[[3,3],2],[[1,4],5],[[6,4],5],[[6,4],5],[[3,4],5],[[3,4],5],[[7,4],5],[[7,4],5],[[]],[3,6],[[]],[7,6],[[]],[[]],[1],[[]],[[]],[[]],[[]],[6,1],[8],[8],[8],[6,[[10,[9]]]],[7,[[10,[9]]]],[[]],[[],11],[[],11],[[],11],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],[[],13],[[],13],0],"p":[[4,"FromStrRadixErrKind"],[15,"bool"],[4,"FromDecStrErr"],[3,"Formatter"],[6,"Result"],[3,"FromStrRadixErr"],[3,"FromHexError"],[3,"Demand"],[8,"Error"],[4,"Option"],[3,"String"],[4,"Result"],[3,"TypeId"]]},\ +"unicode_ident":{"doc":"github crates-io docs-rs","t":[5,5],"n":["is_xid_continue","is_xid_start"],"q":["unicode_ident",""],"d":["",""],"i":[0,0],"f":[[1,2],[1,2]],"p":[[15,"char"],[15,"bool"]]}\ +}'); +if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)}; +if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; diff --git a/docs/search.js b/docs/search.js new file mode 100644 index 000000000000..f0ccdfb1bf10 --- /dev/null +++ b/docs/search.js @@ -0,0 +1 @@ +"use strict";(function(){const itemTypes=["mod","externcrate","import","struct","enum","fn","type","static","trait","impl","tymethod","method","structfield","variant","macro","primitive","associatedtype","constant","associatedconstant","union","foreigntype","keyword","existential","attr","derive","traitalias",];const TY_PRIMITIVE=itemTypes.indexOf("primitive");const TY_KEYWORD=itemTypes.indexOf("keyword");const ROOT_PATH=typeof window!=="undefined"?window.rootPath:"../";function hasOwnPropertyRustdoc(obj,property){return Object.prototype.hasOwnProperty.call(obj,property)}function printTab(nb){let iter=0;let foundCurrentTab=false;let foundCurrentResultSet=false;onEachLazy(document.getElementById("titles").childNodes,elem=>{if(nb===iter){addClass(elem,"selected");foundCurrentTab=true}else{removeClass(elem,"selected")}iter+=1});iter=0;onEachLazy(document.getElementById("results").childNodes,elem=>{if(nb===iter){addClass(elem,"active");foundCurrentResultSet=true}else{removeClass(elem,"active")}iter+=1});if(foundCurrentTab&&foundCurrentResultSet){searchState.currentTab=nb}else if(nb!==0){printTab(0)}}const levenshtein_row2=[];function levenshtein(s1,s2){if(s1===s2){return 0}const s1_len=s1.length,s2_len=s2.length;if(s1_len&&s2_len){let i1=0,i2=0,a,b,c,c2;const row=levenshtein_row2;while(i1-".indexOf(c)!==-1}function isStopCharacter(c){return isWhitespace(c)||isEndCharacter(c)}function isErrorCharacter(c){return"()".indexOf(c)!==-1}function itemTypeFromName(typename){for(let i=0,len=itemTypes.length;i0){throw new Error("Cannot use literal search when there is more than one element")}parserState.pos+=1;const start=parserState.pos;const end=getIdentEndPosition(parserState);if(parserState.pos>=parserState.length){throw new Error("Unclosed `\"`")}else if(parserState.userQuery[end]!=="\""){throw new Error(`Unexpected \`${parserState.userQuery[end]}\` in a string element`)}else if(start===end){throw new Error("Cannot have empty string element")}parserState.pos+=1;query.literalSearch=true}function isPathStart(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="::"}function isReturnArrow(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="->"}function isIdentCharacter(c){return(c==="_"||(c>="0"&&c<="9")||(c>="a"&&c<="z")||(c>="A"&&c<="Z"))}function isSeparatorCharacter(c){return c===","||isWhitespaceCharacter(c)}function isWhitespaceCharacter(c){return c===" "||c==="\t"}function createQueryElement(query,parserState,name,generics,isInGenerics){if(name==="*"||(name.length===0&&generics.length===0)){return}if(query.literalSearch&&parserState.totalElems-parserState.genericsElems>0){throw new Error("You cannot have more than one element if you use quotes")}const pathSegments=name.split("::");if(pathSegments.length>1){for(let i=0,len=pathSegments.length;i=end){throw new Error("Found generics without a path")}parserState.pos+=1;getItemsBefore(query,parserState,generics,">")}if(start>=end&&generics.length===0){return}elems.push(createQueryElement(query,parserState,parserState.userQuery.slice(start,end),generics,isInGenerics))}function getItemsBefore(query,parserState,elems,endChar){let foundStopChar=true;while(parserState.pos"){extra="`<`"}else if(endChar===""){extra="`->`"}throw new Error("Unexpected `"+c+"` after "+extra)}if(!foundStopChar){if(endChar!==""){throw new Error(`Expected \`,\`, \` \` or \`${endChar}\`, found \`${c}\``)}throw new Error(`Expected \`,\` or \` \`, found \`${c}\``)}const posBefore=parserState.pos;getNextElem(query,parserState,elems,endChar===">");if(posBefore===parserState.pos){parserState.pos+=1}foundStopChar=false}parserState.pos+=1}function checkExtraTypeFilterCharacters(parserState){const query=parserState.userQuery;for(let pos=0;pos"){if(isReturnArrow(parserState)){break}throw new Error(`Unexpected \`${c}\` (did you mean \`->\`?)`)}throw new Error(`Unexpected \`${c}\``)}else if(c===":"&&!isPathStart(parserState)){if(parserState.typeFilter!==null){throw new Error("Unexpected `:`")}if(query.elems.length===0){throw new Error("Expected type filter before `:`")}else if(query.elems.length!==1||parserState.totalElems!==1){throw new Error("Unexpected `:`")}else if(query.literalSearch){throw new Error("You cannot use quotes on type filter")}checkExtraTypeFilterCharacters(parserState);parserState.typeFilter=query.elems.pop().name;parserState.pos+=1;parserState.totalElems=0;query.literalSearch=false;foundStopChar=true;continue}if(!foundStopChar){if(parserState.typeFilter!==null){throw new Error(`Expected \`,\`, \` \` or \`->\`, found \`${c}\``)}throw new Error(`Expected \`,\`, \` \`, \`:\` or \`->\`, found \`${c}\``)}before=query.elems.length;getNextElem(query,parserState,query.elems,false);if(query.elems.length===before){parserState.pos+=1}foundStopChar=false}while(parserState.pos`")}break}else{parserState.pos+=1}}}function newParsedQuery(userQuery){return{original:userQuery,userQuery:userQuery.toLowerCase(),typeFilter:NO_TYPE_FILTER,elems:[],returned:[],foundElems:0,literalSearch:false,error:null,}}function buildUrl(search,filterCrates){let extra="?search="+encodeURIComponent(search);if(filterCrates!==null){extra+="&filter-crate="+encodeURIComponent(filterCrates)}return getNakedUrl()+extra+window.location.hash}function getFilterCrates(){const elem=document.getElementById("crate-search");if(elem&&elem.value!=="all crates"&&hasOwnPropertyRustdoc(rawSearchIndex,elem.value)){return elem.value}return null}function parseQuery(userQuery){userQuery=userQuery.trim();const parserState={length:userQuery.length,pos:0,totalElems:0,genericsElems:0,typeFilter:null,userQuery:userQuery.toLowerCase(),};let query=newParsedQuery(userQuery);try{parseInput(query,parserState);if(parserState.typeFilter!==null){let typeFilter=parserState.typeFilter;if(typeFilter==="const"){typeFilter="constant"}query.typeFilter=itemTypeFromName(typeFilter)}}catch(err){query=newParsedQuery(userQuery);query.error=err.message;query.typeFilter=-1;return query}if(!query.literalSearch){query.literalSearch=parserState.totalElems>1}query.foundElems=query.elems.length+query.returned.length;return query}function createQueryResults(results_in_args,results_returned,results_others,parsedQuery){return{"in_args":results_in_args,"returned":results_returned,"others":results_others,"query":parsedQuery,}}function execQuery(parsedQuery,searchWords,filterCrates,currentCrate){const results_others={},results_in_args={},results_returned={};function transformResults(results){const duplicates={};const out=[];for(const result of results){if(result.id>-1){const obj=searchIndex[result.id];obj.lev=result.lev;const res=buildHrefAndPath(obj);obj.displayPath=pathSplitter(res[0]);obj.fullPath=obj.displayPath+obj.name;obj.fullPath+="|"+obj.ty;if(duplicates[obj.fullPath]){continue}duplicates[obj.fullPath]=true;obj.href=res[1];out.push(obj);if(out.length>=MAX_RESULTS){break}}}return out}function sortResults(results,isType,preferredCrate){const userQuery=parsedQuery.userQuery;const ar=[];for(const entry in results){if(hasOwnPropertyRustdoc(results,entry)){const result=results[entry];result.word=searchWords[result.id];result.item=searchIndex[result.id]||{};ar.push(result)}}results=ar;if(results.length===0){return[]}results.sort((aaa,bbb)=>{let a,b;a=(aaa.word!==userQuery);b=(bbb.word!==userQuery);if(a!==b){return a-b}a=(aaa.lev);b=(bbb.lev);if(a!==b){return a-b}a=(aaa.item.crate!==preferredCrate);b=(bbb.item.crate!==preferredCrate);if(a!==b){return a-b}a=aaa.word.length;b=bbb.word.length;if(a!==b){return a-b}a=aaa.word;b=bbb.word;if(a!==b){return(a>b?+1:-1)}a=(aaa.index<0);b=(bbb.index<0);if(a!==b){return a-b}a=aaa.index;b=bbb.index;if(a!==b){return a-b}if((aaa.item.ty===TY_PRIMITIVE&&bbb.item.ty!==TY_KEYWORD)||(aaa.item.ty===TY_KEYWORD&&bbb.item.ty!==TY_PRIMITIVE)){return-1}if((bbb.item.ty===TY_PRIMITIVE&&aaa.item.ty!==TY_PRIMITIVE)||(bbb.item.ty===TY_KEYWORD&&aaa.item.ty!==TY_KEYWORD)){return 1}a=(aaa.item.desc==="");b=(bbb.item.desc==="");if(a!==b){return a-b}a=aaa.item.ty;b=bbb.item.ty;if(a!==b){return a-b}a=aaa.item.path;b=bbb.item.path;if(a!==b){return(a>b?+1:-1)}return 0});let nameSplit=null;if(parsedQuery.elems.length===1){const hasPath=typeof parsedQuery.elems[0].path==="undefined";nameSplit=hasPath?null:parsedQuery.elems[0].path}for(const result of results){if(result.dontValidate){continue}const name=result.item.name.toLowerCase(),path=result.item.path.toLowerCase(),parent=result.item.parent;if(!isType&&!validateResult(name,path,nameSplit,parent)){result.id=-1}}return transformResults(results)}function checkGenerics(row,elem,defaultLev){if(row.generics.length===0){return elem.generics.length===0?defaultLev:MAX_LEV_DISTANCE+1}else if(row.generics.length>0&&row.generics[0].name===null){return checkGenerics(row.generics[0],elem,defaultLev)}let elem_name;if(elem.generics.length>0&&row.generics.length>=elem.generics.length){const elems=Object.create(null);for(const entry of row.generics){elem_name=entry.name;if(elem_name===""){if(checkGenerics(entry,elem,MAX_LEV_DISTANCE+1)!==0){return MAX_LEV_DISTANCE+1}continue}if(elems[elem_name]===undefined){elems[elem_name]=0}elems[elem_name]+=1}for(const generic of elem.generics){let match=null;if(elems[generic.name]){match=generic.name}else{for(elem_name in elems){if(!hasOwnPropertyRustdoc(elems,elem_name)){continue}if(elem_name===generic){match=elem_name;break}}}if(match===null){return MAX_LEV_DISTANCE+1}elems[match]-=1;if(elems[match]===0){delete elems[match]}}return 0}return MAX_LEV_DISTANCE+1}function checkIfInGenerics(row,elem){let lev=MAX_LEV_DISTANCE+1;for(const entry of row.generics){lev=Math.min(checkType(entry,elem,true),lev);if(lev===0){break}}return lev}function checkType(row,elem,literalSearch){if(row.name===null){if(row.generics.length>0){return checkIfInGenerics(row,elem)}return MAX_LEV_DISTANCE+1}let lev=levenshtein(row.name,elem.name);if(literalSearch){if(lev!==0){if(elem.generics.length===0){const checkGeneric=row.generics.length>0;if(checkGeneric&&row.generics.findIndex(tmp_elem=>tmp_elem.name===elem.name)!==-1){return 0}}return MAX_LEV_DISTANCE+1}else if(elem.generics.length>0){return checkGenerics(row,elem,MAX_LEV_DISTANCE+1)}return 0}else if(row.generics.length>0){if(elem.generics.length===0){if(lev===0){return 0}lev=checkIfInGenerics(row,elem);return lev+0.5}else if(lev>MAX_LEV_DISTANCE){return checkIfInGenerics(row,elem)}else{const tmp_lev=checkGenerics(row,elem,lev);if(tmp_lev>MAX_LEV_DISTANCE){return MAX_LEV_DISTANCE+1}return(tmp_lev+lev)/2}}else if(elem.generics.length>0){return MAX_LEV_DISTANCE+1}return lev}function findArg(row,elem,typeFilter){let lev=MAX_LEV_DISTANCE+1;if(row&&row.type&&row.type.inputs&&row.type.inputs.length>0){for(const input of row.type.inputs){if(!typePassesFilter(typeFilter,input.ty)){continue}lev=Math.min(lev,checkType(input,elem,parsedQuery.literalSearch));if(lev===0){return 0}}}return parsedQuery.literalSearch?MAX_LEV_DISTANCE+1:lev}function checkReturned(row,elem,typeFilter){let lev=MAX_LEV_DISTANCE+1;if(row&&row.type&&row.type.output.length>0){const ret=row.type.output;for(const ret_ty of ret){if(!typePassesFilter(typeFilter,ret_ty.ty)){continue}lev=Math.min(lev,checkType(ret_ty,elem,parsedQuery.literalSearch));if(lev===0){return 0}}}return parsedQuery.literalSearch?MAX_LEV_DISTANCE+1:lev}function checkPath(contains,ty){if(contains.length===0){return 0}let ret_lev=MAX_LEV_DISTANCE+1;const path=ty.path.split("::");if(ty.parent&&ty.parent.name){path.push(ty.parent.name.toLowerCase())}const length=path.length;const clength=contains.length;if(clength>length){return MAX_LEV_DISTANCE+1}for(let i=0;ilength){break}let lev_total=0;let aborted=false;for(let x=0;xMAX_LEV_DISTANCE){aborted=true;break}lev_total+=lev}if(!aborted){ret_lev=Math.min(ret_lev,Math.round(lev_total/clength))}}return ret_lev}function typePassesFilter(filter,type){if(filter<=NO_TYPE_FILTER||filter===type)return true;const name=itemTypes[type];switch(itemTypes[filter]){case"constant":return name==="associatedconstant";case"fn":return name==="method"||name==="tymethod";case"type":return name==="primitive"||name==="associatedtype";case"trait":return name==="traitalias"}return false}function createAliasFromItem(item){return{crate:item.crate,name:item.name,path:item.path,desc:item.desc,ty:item.ty,parent:item.parent,type:item.type,is_alias:true,}}function handleAliases(ret,query,filterCrates,currentCrate){const lowerQuery=query.toLowerCase();const aliases=[];const crateAliases=[];if(filterCrates!==null){if(ALIASES[filterCrates]&&ALIASES[filterCrates][lowerQuery]){const query_aliases=ALIASES[filterCrates][lowerQuery];for(const alias of query_aliases){aliases.push(createAliasFromItem(searchIndex[alias]))}}}else{Object.keys(ALIASES).forEach(crate=>{if(ALIASES[crate][lowerQuery]){const pushTo=crate===currentCrate?crateAliases:aliases;const query_aliases=ALIASES[crate][lowerQuery];for(const alias of query_aliases){pushTo.push(createAliasFromItem(searchIndex[alias]))}}})}const sortFunc=(aaa,bbb)=>{if(aaa.path{alias.alias=query;const res=buildHrefAndPath(alias);alias.displayPath=pathSplitter(res[0]);alias.fullPath=alias.displayPath+alias.name;alias.href=res[1];ret.others.unshift(alias);if(ret.others.length>MAX_RESULTS){ret.others.pop()}};aliases.forEach(pushFunc);crateAliases.forEach(pushFunc)}function addIntoResults(results,fullId,id,index,lev){if(lev===0||(!parsedQuery.literalSearch&&lev<=MAX_LEV_DISTANCE)){if(results[fullId]!==undefined){const result=results[fullId];if(result.dontValidate||result.lev<=lev){return}}results[fullId]={id:id,index:index,dontValidate:parsedQuery.literalSearch,lev:lev,}}}function handleSingleArg(row,pos,elem,results_others,results_in_args,results_returned){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return}let lev,lev_add=0,index=-1;const fullId=row.id;const in_args=findArg(row,elem,parsedQuery.typeFilter);const returned=checkReturned(row,elem,parsedQuery.typeFilter);addIntoResults(results_in_args,fullId,pos,index,in_args);addIntoResults(results_returned,fullId,pos,index,returned);if(!typePassesFilter(parsedQuery.typeFilter,row.ty)){return}const searchWord=searchWords[pos];if(parsedQuery.literalSearch){if(searchWord===elem.name){addIntoResults(results_others,fullId,pos,-1,0)}return}if(elem.name.length===0){if(row.type!==null){lev=checkGenerics(row.type,elem,MAX_LEV_DISTANCE+1);addIntoResults(results_others,fullId,pos,index,lev)}return}if(elem.fullPath.length>1){lev=checkPath(elem.pathWithoutLast,row);if(lev>MAX_LEV_DISTANCE||(parsedQuery.literalSearch&&lev!==0)){return}else if(lev>0){lev_add=lev/10}}if(searchWord.indexOf(elem.pathLast)>-1||row.normalizedName.indexOf(elem.pathLast)>-1){index=row.normalizedName.indexOf(elem.pathLast)}lev=levenshtein(searchWord,elem.pathLast);if(lev>0&&elem.pathLast.length>2&&searchWord.indexOf(elem.pathLast)>-1){if(elem.pathLast.length<6){lev=1}else{lev=0}}lev+=lev_add;if(lev>MAX_LEV_DISTANCE){return}else if(index!==-1&&elem.fullPath.length<2){lev-=1}if(lev<0){lev=0}addIntoResults(results_others,fullId,pos,index,lev)}function handleArgs(row,pos,results){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return}let totalLev=0;let nbLev=0;function checkArgs(elems,callback){for(const elem of elems){const lev=callback(row,elem,NO_TYPE_FILTER);if(lev<=1){nbLev+=1;totalLev+=lev}else{return false}}return true}if(!checkArgs(parsedQuery.elems,findArg)){return}if(!checkArgs(parsedQuery.returned,checkReturned)){return}if(nbLev===0){return}const lev=Math.round(totalLev/nbLev);addIntoResults(results,row.id,pos,0,lev)}function innerRunQuery(){let elem,i,nSearchWords,in_returned,row;if(parsedQuery.foundElems===1){if(parsedQuery.elems.length===1){elem=parsedQuery.elems[0];for(i=0,nSearchWords=searchWords.length;i0){for(i=0,nSearchWords=searchWords.length;i-1||path.indexOf(key)>-1||(parent!==undefined&&parent.name!==undefined&&parent.name.toLowerCase().indexOf(key)>-1)||levenshtein(name,key)<=MAX_LEV_DISTANCE)){return false}}return true}function nextTab(direction){const next=(searchState.currentTab+direction+3)%searchState.focusedByTab.length;searchState.focusedByTab[searchState.currentTab]=document.activeElement;printTab(next);focusSearchResult()}function focusSearchResult(){const target=searchState.focusedByTab[searchState.currentTab]||document.querySelectorAll(".search-results.active a").item(0)||document.querySelectorAll("#titles > button").item(searchState.currentTab);if(target){target.focus()}}function buildHrefAndPath(item){let displayPath;let href;const type=itemTypes[item.ty];const name=item.name;let path=item.path;if(type==="mod"){displayPath=path+"::";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+name+"/index.html"}else if(type==="import"){displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/index.html#reexport."+name}else if(type==="primitive"||type==="keyword"){displayPath="";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+type+"."+name+".html"}else if(type==="externcrate"){displayPath="";href=ROOT_PATH+name+"/index.html"}else if(item.parent!==undefined){const myparent=item.parent;let anchor="#"+type+"."+name;const parentType=itemTypes[myparent.ty];let pageType=parentType;let pageName=myparent.name;if(parentType==="primitive"){displayPath=myparent.name+"::"}else if(type==="structfield"&&parentType==="variant"){const enumNameIdx=item.path.lastIndexOf("::");const enumName=item.path.substr(enumNameIdx+2);path=item.path.substr(0,enumNameIdx);displayPath=path+"::"+enumName+"::"+myparent.name+"::";anchor="#variant."+myparent.name+".field."+name;pageType="enum";pageName=enumName}else{displayPath=path+"::"+myparent.name+"::"}href=ROOT_PATH+path.replace(/::/g,"/")+"/"+pageType+"."+pageName+".html"+anchor}else{displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/"+type+"."+name+".html"}return[displayPath,href]}function pathSplitter(path){const tmp=""+path.replace(/::/g,"::");if(tmp.endsWith("")){return tmp.slice(0,tmp.length-6)}return tmp}function addTab(array,query,display){let extraClass="";if(display===true){extraClass=" active"}const output=document.createElement("div");let length=0;if(array.length>0){output.className="search-results "+extraClass;array.forEach(item=>{const name=item.name;const type=itemTypes[item.ty];length+=1;let extra="";if(type==="primitive"){extra=" (primitive type)"}else if(type==="keyword"){extra=" (keyword)"}const link=document.createElement("a");link.className="result-"+type;link.href=item.href;const wrapper=document.createElement("div");const resultName=document.createElement("div");resultName.className="result-name";if(item.is_alias){const alias=document.createElement("span");alias.className="alias";const bold=document.createElement("b");bold.innerText=item.alias;alias.appendChild(bold);alias.insertAdjacentHTML("beforeend"," - see ");resultName.appendChild(alias)}resultName.insertAdjacentHTML("beforeend",item.displayPath+""+name+extra+"");wrapper.appendChild(resultName);const description=document.createElement("div");description.className="desc";const spanDesc=document.createElement("span");spanDesc.insertAdjacentHTML("beforeend",item.desc);description.appendChild(spanDesc);wrapper.appendChild(description);link.appendChild(wrapper);output.appendChild(link)})}else if(query.error===null){output.className="search-failed"+extraClass;output.innerHTML="No results :(
"+"Try on DuckDuckGo?

"+"Or try looking in one of these:"}return[output,length]}function makeTabHeader(tabNb,text,nbElems){if(searchState.currentTab===tabNb){return""}return""}function showResults(results,go_to_first,filterCrates){const search=searchState.outputElement();if(go_to_first||(results.others.length===1&&getSettingValue("go-to-only-result")==="true"&&(!search.firstChild||search.firstChild.innerText!==searchState.loadingText))){const elem=document.createElement("a");elem.href=results.others[0].href;removeClass(elem,"active");document.body.appendChild(elem);elem.click();return}if(results.query===undefined){results.query=parseQuery(searchState.input.value)}currentResults=results.query.userQuery;const ret_others=addTab(results.others,results.query,true);const ret_in_args=addTab(results.in_args,results.query,false);const ret_returned=addTab(results.returned,results.query,false);let currentTab=searchState.currentTab;if((currentTab===0&&ret_others[1]===0)||(currentTab===1&&ret_in_args[1]===0)||(currentTab===2&&ret_returned[1]===0)){if(ret_others[1]!==0){currentTab=0}else if(ret_in_args[1]!==0){currentTab=1}else if(ret_returned[1]!==0){currentTab=2}}let crates="";const crates_list=Object.keys(rawSearchIndex);if(crates_list.length>1){crates=" in 
"}let output=`

Results${crates}

`;if(results.query.error!==null){output+=`

Query parser error: "${results.query.error}".

`;output+="
"+makeTabHeader(0,"In Names",ret_others[1])+"
";currentTab=0}else if(results.query.foundElems<=1&&results.query.returned.length===0){output+="
"+makeTabHeader(0,"In Names",ret_others[1])+makeTabHeader(1,"In Parameters",ret_in_args[1])+makeTabHeader(2,"In Return Types",ret_returned[1])+"
"}else{const signatureTabTitle=results.query.elems.length===0?"In Function Return Types":results.query.returned.length===0?"In Function Parameters":"In Function Signatures";output+="
"+makeTabHeader(0,signatureTabTitle,ret_others[1])+"
";currentTab=0}const resultsElem=document.createElement("div");resultsElem.id="results";resultsElem.appendChild(ret_others[0]);resultsElem.appendChild(ret_in_args[0]);resultsElem.appendChild(ret_returned[0]);search.innerHTML=output;const crateSearch=document.getElementById("crate-search");if(crateSearch){crateSearch.addEventListener("input",updateCrate)}search.appendChild(resultsElem);searchState.showResults(search);const elems=document.getElementById("titles").childNodes;searchState.focusedByTab=[];let i=0;for(const elem of elems){const j=i;elem.onclick=()=>printTab(j);searchState.focusedByTab.push(null);i+=1}printTab(currentTab)}function search(e,forced){const params=searchState.getQueryStringParams();const query=parseQuery(searchState.input.value.trim());if(e){e.preventDefault()}if(!forced&&query.userQuery===currentResults){if(query.userQuery.length>0){putBackSearch()}return}let filterCrates=getFilterCrates();if(filterCrates===null&¶ms["filter-crate"]!==undefined){filterCrates=params["filter-crate"]}searchState.title="Results for "+query.original+" - Rust";if(browserSupportsHistoryApi()){const newURL=buildUrl(query.original,filterCrates);if(!history.state&&!params.search){history.pushState(null,"",newURL)}else{history.replaceState(null,"",newURL)}}showResults(execQuery(query,searchWords,filterCrates,window.currentCrate),params.go_to_first,filterCrates)}function buildItemSearchTypeAll(types,lowercasePaths){const PATH_INDEX_DATA=0;const GENERICS_DATA=1;return types.map(type=>{let pathIndex,generics;if(typeof type==="number"){pathIndex=type;generics=[]}else{pathIndex=type[PATH_INDEX_DATA];generics=buildItemSearchTypeAll(type[GENERICS_DATA],lowercasePaths)}return{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:generics,}})}function buildFunctionSearchType(functionSearchType,lowercasePaths){const INPUTS_DATA=0;const OUTPUT_DATA=1;if(functionSearchType===0){return null}let inputs,output;if(typeof functionSearchType[INPUTS_DATA]==="number"){const pathIndex=functionSearchType[INPUTS_DATA];inputs=[{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{inputs=buildItemSearchTypeAll(functionSearchType[INPUTS_DATA],lowercasePaths)}if(functionSearchType.length>1){if(typeof functionSearchType[OUTPUT_DATA]==="number"){const pathIndex=functionSearchType[OUTPUT_DATA];output=[{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{output=buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA],lowercasePaths)}}else{output=[]}return{inputs,output,}}function buildIndex(rawSearchIndex){searchIndex=[];const searchWords=[];let i,word;let currentIndex=0;let id=0;for(const crate in rawSearchIndex){if(!hasOwnPropertyRustdoc(rawSearchIndex,crate)){continue}let crateSize=0;const crateCorpus=rawSearchIndex[crate];searchWords.push(crate);const crateRow={crate:crate,ty:1,name:crate,path:"",desc:crateCorpus.doc,parent:undefined,type:null,id:id,normalizedName:crate.indexOf("_")===-1?crate:crate.replace(/_/g,""),};id+=1;searchIndex.push(crateRow);currentIndex+=1;const itemTypes=crateCorpus.t;const itemNames=crateCorpus.n;const itemPaths=crateCorpus.q;const itemDescs=crateCorpus.d;const itemParentIdxs=crateCorpus.i;const itemFunctionSearchTypes=crateCorpus.f;const paths=crateCorpus.p;const aliases=crateCorpus.a;const lowercasePaths=[];let len=paths.length;for(i=0;i0?paths[itemParentIdxs[i]-1]:undefined,type:buildFunctionSearchType(itemFunctionSearchTypes[i],lowercasePaths),id:id,normalizedName:word.indexOf("_")===-1?word:word.replace(/_/g,""),};id+=1;searchIndex.push(row);lastPath=row.path;crateSize+=1}if(aliases){ALIASES[crate]=Object.create(null);for(const alias_name in aliases){if(!hasOwnPropertyRustdoc(aliases,alias_name)){continue}if(!hasOwnPropertyRustdoc(ALIASES[crate],alias_name)){ALIASES[crate][alias_name]=[]}for(const local_alias of aliases[alias_name]){ALIASES[crate][alias_name].push(local_alias+currentIndex)}}}currentIndex+=crateSize}return searchWords}function onSearchSubmit(e){e.preventDefault();searchState.clearInputTimeout();search()}function putBackSearch(){const search_input=searchState.input;if(!searchState.input){return}if(search_input.value!==""&&!searchState.isDisplayed()){searchState.showResults();if(browserSupportsHistoryApi()){history.replaceState(null,"",buildUrl(search_input.value,getFilterCrates()))}document.title=searchState.title}}function registerSearchEvents(){const params=searchState.getQueryStringParams();if(searchState.input.value===""){searchState.input.value=params.search||""}const searchAfter500ms=()=>{searchState.clearInputTimeout();if(searchState.input.value.length===0){if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}searchState.hideResults()}else{searchState.timeout=setTimeout(search,500)}};searchState.input.onkeyup=searchAfter500ms;searchState.input.oninput=searchAfter500ms;document.getElementsByClassName("search-form")[0].onsubmit=onSearchSubmit;searchState.input.onchange=e=>{if(e.target!==document.activeElement){return}searchState.clearInputTimeout();setTimeout(search,0)};searchState.input.onpaste=searchState.input.onchange;searchState.outputElement().addEventListener("keydown",e=>{if(e.altKey||e.ctrlKey||e.shiftKey||e.metaKey){return}if(e.which===38){const previous=document.activeElement.previousElementSibling;if(previous){previous.focus()}else{searchState.focus()}e.preventDefault()}else if(e.which===40){const next=document.activeElement.nextElementSibling;if(next){next.focus()}const rect=document.activeElement.getBoundingClientRect();if(window.innerHeight-rect.bottom{if(e.which===40){focusSearchResult();e.preventDefault()}});searchState.input.addEventListener("focus",()=>{putBackSearch()});searchState.input.addEventListener("blur",()=>{searchState.input.placeholder=searchState.input.origPlaceholder});if(browserSupportsHistoryApi()){const previousTitle=document.title;window.addEventListener("popstate",e=>{const params=searchState.getQueryStringParams();document.title=previousTitle;currentResults=null;if(params.search&¶ms.search.length>0){searchState.input.value=params.search;search(e)}else{searchState.input.value="";searchState.hideResults()}})}window.onpageshow=()=>{const qSearch=searchState.getQueryStringParams().search;if(searchState.input.value===""&&qSearch){searchState.input.value=qSearch}search()}}function updateCrate(ev){if(ev.target.value==="all crates"){const params=searchState.getQueryStringParams();const query=searchState.input.value.trim();if(!history.state&&!params.search){history.pushState(null,"",buildUrl(query,null))}else{history.replaceState(null,"",buildUrl(query,null))}}currentResults=null;search(undefined,true)}const searchWords=buildIndex(rawSearchIndex);if(typeof window!=="undefined"){registerSearchEvents();if(window.searchState.getQueryStringParams().search){search()}}if(typeof exports!=="undefined"){exports.initSearch=initSearch;exports.execQuery=execQuery;exports.parseQuery=parseQuery}return searchWords}if(typeof window!=="undefined"){window.initSearch=initSearch;if(window.searchIndex!==undefined){initSearch(window.searchIndex)}}else{initSearch({})}})() \ No newline at end of file diff --git a/docs/settings.css b/docs/settings.css new file mode 100644 index 000000000000..ab01e577c5c0 --- /dev/null +++ b/docs/settings.css @@ -0,0 +1 @@ +.setting-line{margin:0.6em 0 0.6em 0.3em;position:relative;}.setting-line .choices{display:flex;flex-wrap:wrap;}.setting-line .radio-line input{margin-right:0.3em;height:1.2rem;width:1.2rem;color:inherit;border:1px solid currentColor;outline:none;-webkit-appearance:none;cursor:pointer;border-radius:50%;}.setting-line .radio-line input+span{padding-bottom:1px;}.radio-line .setting-name{width:100%;}.radio-line .choice{margin-top:0.1em;margin-bottom:0.1em;min-width:3.8em;padding:0.3em;display:flex;align-items:center;cursor:pointer;}.radio-line .choice+.choice{margin-left:0.5em;}.toggle{position:relative;width:100%;margin-right:20px;display:flex;align-items:center;cursor:pointer;}.toggle input{opacity:0;position:absolute;}.slider{position:relative;width:45px;min-width:45px;display:block;height:28px;margin-right:20px;cursor:pointer;background-color:#ccc;transition:.3s;}.slider:before{position:absolute;content:"";height:19px;width:19px;left:4px;bottom:4px;transition:.3s;}input:checked+.slider:before{transform:translateX(19px);}.setting-line>.sub-settings{padding-left:42px;width:100%;display:block;}#settings .setting-line{margin:1.2em 0.6em;}.setting-line .radio-line input:checked{box-shadow:inset 0 0 0 3px var(--main-background-color);background-color:var(--settings-input-color);}.setting-line .radio-line input:focus{box-shadow:0 0 1px 1px var(--settings-input-color);}.setting-line .radio-line input:checked:focus{box-shadow:inset 0 0 0 3px var(--main-background-color),0 0 2px 2px var(--settings-input-color);}.setting-line .radio-line input:hover{border-color:var(--settings-input-color) !important;}input:checked+.slider{background-color:var(--settings-input-color);} \ No newline at end of file diff --git a/docs/settings.html b/docs/settings.html new file mode 100644 index 000000000000..9bd8d4a9796c --- /dev/null +++ b/docs/settings.html @@ -0,0 +1 @@ +Rustdoc settings

Rustdoc settings

Back
\ No newline at end of file diff --git a/docs/settings.js b/docs/settings.js new file mode 100644 index 000000000000..834d29269db0 --- /dev/null +++ b/docs/settings.js @@ -0,0 +1,11 @@ +"use strict";(function(){const isSettingsPage=window.location.pathname.endsWith("/settings.html");function changeSetting(settingName,value){updateLocalStorage(settingName,value);switch(settingName){case"theme":case"preferred-dark-theme":case"preferred-light-theme":case"use-system-theme":updateSystemTheme();updateLightAndDark();break;case"line-numbers":if(value===true){window.rustdoc_add_line_numbers_to_examples()}else{window.rustdoc_remove_line_numbers_from_examples()}break}}function handleKey(ev){if(ev.ctrlKey||ev.altKey||ev.metaKey){return}switch(getVirtualKey(ev)){case"Enter":case"Return":case"Space":ev.target.checked=!ev.target.checked;ev.preventDefault();break}}function showLightAndDark(){addClass(document.getElementById("theme").parentElement,"hidden");removeClass(document.getElementById("preferred-light-theme").parentElement,"hidden");removeClass(document.getElementById("preferred-dark-theme").parentElement,"hidden")}function hideLightAndDark(){addClass(document.getElementById("preferred-light-theme").parentElement,"hidden");addClass(document.getElementById("preferred-dark-theme").parentElement,"hidden");removeClass(document.getElementById("theme").parentElement,"hidden")}function updateLightAndDark(){if(getSettingValue("use-system-theme")!=="false"){showLightAndDark()}else{hideLightAndDark()}}function setEvents(settingsElement){updateLightAndDark();onEachLazy(settingsElement.getElementsByClassName("slider"),elem=>{const toggle=elem.previousElementSibling;const settingId=toggle.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){toggle.checked=settingValue==="true"}toggle.onchange=function(){changeSetting(this.id,this.checked)};toggle.onkeyup=handleKey;toggle.onkeyrelease=handleKey});onEachLazy(settingsElement.getElementsByClassName("select-wrapper"),elem=>{const select=elem.getElementsByTagName("select")[0];const settingId=select.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){select.value=settingValue}select.onchange=function(){changeSetting(this.id,this.value)}});onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"),elem=>{const settingId=elem.name;const settingValue=getSettingValue(settingId);if(settingValue!==null&&settingValue!=="null"){elem.checked=settingValue===elem.value}elem.addEventListener("change",ev=>{changeSetting(ev.target.name,ev.target.value)})})}function buildSettingsPageSections(settings){let output="";for(const setting of settings){output+="
";const js_data_name=setting["js_name"];const setting_name=setting["name"];if(setting["options"]!==undefined){output+=`
\ + ${setting_name}\ +
`;onEach(setting["options"],option=>{const checked=option===setting["default"]?" checked":"";output+=``});output+="
"}else{const checked=setting["default"]===true?" checked":"";output+=``}output+="
"}return output}function buildSettingsPage(){const themes=getVar("themes").split(",");const settings=[{"name":"Use system theme","js_name":"use-system-theme","default":true,},{"name":"Theme","js_name":"theme","default":"light","options":themes,},{"name":"Preferred light theme","js_name":"preferred-light-theme","default":"light","options":themes,},{"name":"Preferred dark theme","js_name":"preferred-dark-theme","default":"dark","options":themes,},{"name":"Auto-hide item contents for large items","js_name":"auto-hide-large-items","default":true,},{"name":"Auto-hide item methods' documentation","js_name":"auto-hide-method-docs","default":false,},{"name":"Auto-hide trait implementation documentation","js_name":"auto-hide-trait-implementations","default":false,},{"name":"Directly go to item in search if there is only one result","js_name":"go-to-only-result","default":false,},{"name":"Show line numbers on code examples","js_name":"line-numbers","default":false,},{"name":"Disable keyboard shortcuts","js_name":"disable-shortcuts","default":false,},];const elementKind=isSettingsPage?"section":"div";const innerHTML=`
${buildSettingsPageSections(settings)}
`;const el=document.createElement(elementKind);el.id="settings";if(!isSettingsPage){el.className="popover"}el.innerHTML=innerHTML;if(isSettingsPage){document.getElementById(MAIN_ID).appendChild(el)}else{el.setAttribute("tabindex","-1");getSettingsButton().appendChild(el)}return el}const settingsMenu=buildSettingsPage();function displaySettings(){settingsMenu.style.display=""}function settingsBlurHandler(event){blurHandler(event,getSettingsButton(),window.hidePopoverMenus)}if(isSettingsPage){getSettingsButton().onclick=function(event){event.preventDefault()}}else{const settingsButton=getSettingsButton();const settingsMenu=document.getElementById("settings");settingsButton.onclick=function(event){if(elemIsInParent(event.target,settingsMenu)){return}event.preventDefault();const shouldDisplaySettings=settingsMenu.style.display==="none";window.hidePopoverMenus();if(shouldDisplaySettings){displaySettings()}};settingsButton.onblur=settingsBlurHandler;settingsButton.querySelector("a").onblur=settingsBlurHandler;onEachLazy(settingsMenu.querySelectorAll("input"),el=>{el.onblur=settingsBlurHandler});settingsMenu.onblur=settingsBlurHandler}setTimeout(()=>{setEvents(settingsMenu);if(!isSettingsPage){displaySettings()}removeClass(getSettingsButton(),"rotate")},0)})() \ No newline at end of file diff --git a/docs/source-files.js b/docs/source-files.js new file mode 100644 index 000000000000..c7cc6f7e9e0b --- /dev/null +++ b/docs/source-files.js @@ -0,0 +1,79 @@ +var sourcesIndex = JSON.parse('{\ +"ahash":["",[],["convert.rs","fallback_hash.rs","lib.rs","operations.rs","random_state.rs","specialize.rs"]],\ +"aho_corasick":["",[["packed",[["teddy",[],["compile.rs","mod.rs","runtime.rs"]]],["api.rs","mod.rs","pattern.rs","rabinkarp.rs","vector.rs"]]],["ahocorasick.rs","automaton.rs","buffer.rs","byte_frequencies.rs","classes.rs","dfa.rs","error.rs","lib.rs","nfa.rs","prefilter.rs","state_id.rs"]],\ +"aiofut":["",[],["abi.rs","lib.rs"]],\ +"async_trait":["",[],["args.rs","bound.rs","expand.rs","lib.rs","lifetime.rs","parse.rs","receiver.rs"]],\ +"bincode":["",[["config",[],["endian.rs","int.rs","legacy.rs","limit.rs","mod.rs","trailing.rs"]],["de",[],["mod.rs","read.rs"]],["ser",[],["mod.rs"]]],["byteorder.rs","error.rs","internal.rs","lib.rs"]],\ +"bitflags":["",[],["lib.rs"]],\ +"block_buffer":["",[],["lib.rs","sealed.rs"]],\ +"byteorder":["",[],["io.rs","lib.rs"]],\ +"bytes":["",[["buf",[],["buf_impl.rs","buf_mut.rs","chain.rs","iter.rs","limit.rs","mod.rs","reader.rs","take.rs","uninit_slice.rs","vec_deque.rs","writer.rs"]],["fmt",[],["debug.rs","hex.rs","mod.rs"]]],["bytes.rs","bytes_mut.rs","lib.rs","loom.rs"]],\ +"cfg_if":["",[],["lib.rs"]],\ +"cpufeatures":["",[],["aarch64.rs","lib.rs"]],\ +"crc":["",[],["crc128.rs","crc16.rs","crc32.rs","crc64.rs","crc8.rs","lib.rs","table.rs","util.rs"]],\ +"crc_catalog":["",[],["catalog.rs","lib.rs"]],\ +"crossbeam_channel":["",[["flavors",[],["array.rs","at.rs","list.rs","mod.rs","never.rs","tick.rs","zero.rs"]]],["channel.rs","context.rs","counter.rs","err.rs","lib.rs","select.rs","select_macro.rs","utils.rs","waker.rs"]],\ +"crossbeam_utils":["",[["atomic",[],["atomic_cell.rs","consume.rs","mod.rs","seq_lock.rs"]],["sync",[],["mod.rs","once_lock.rs","parker.rs","sharded_lock.rs","wait_group.rs"]]],["backoff.rs","cache_padded.rs","lib.rs","thread.rs"]],\ +"crunchy":["",[],["lib.rs"]],\ +"crypto_common":["",[],["lib.rs"]],\ +"digest":["",[["core_api",[],["ct_variable.rs","rt_variable.rs","wrapper.rs","xof_reader.rs"]]],["core_api.rs","digest.rs","lib.rs"]],\ +"enum_as_inner":["",[],["lib.rs"]],\ +"firewood":["",[],["account.rs","db.rs","file.rs","lib.rs","merkle.rs","proof.rs","storage.rs"]],\ +"fixed_hash":["",[],["hash.rs","lib.rs"]],\ +"futures":["",[],["lib.rs"]],\ +"futures_channel":["",[["mpsc",[],["mod.rs","queue.rs","sink_impl.rs"]]],["lib.rs","lock.rs","oneshot.rs"]],\ +"futures_core":["",[["task",[["__internal",[],["atomic_waker.rs","mod.rs"]]],["mod.rs","poll.rs"]]],["future.rs","lib.rs","stream.rs"]],\ +"futures_executor":["",[],["enter.rs","lib.rs","local_pool.rs"]],\ +"futures_io":["",[],["lib.rs"]],\ +"futures_macro":["",[],["executor.rs","join.rs","lib.rs","select.rs","stream_select.rs"]],\ +"futures_sink":["",[],["lib.rs"]],\ +"futures_task":["",[],["arc_wake.rs","future_obj.rs","lib.rs","noop_waker.rs","spawn.rs","waker.rs","waker_ref.rs"]],\ +"futures_util":["",[["async_await",[],["join_mod.rs","mod.rs","pending.rs","poll.rs","random.rs","select_mod.rs","stream_select_mod.rs"]],["future",[["future",[],["catch_unwind.rs","flatten.rs","fuse.rs","map.rs","mod.rs","remote_handle.rs","shared.rs"]],["try_future",[],["into_future.rs","mod.rs","try_flatten.rs","try_flatten_err.rs"]]],["abortable.rs","either.rs","join.rs","join_all.rs","lazy.rs","maybe_done.rs","mod.rs","option.rs","pending.rs","poll_fn.rs","poll_immediate.rs","ready.rs","select.rs","select_all.rs","select_ok.rs","try_join.rs","try_join_all.rs","try_maybe_done.rs","try_select.rs"]],["io",[],["allow_std.rs","buf_reader.rs","buf_writer.rs","chain.rs","close.rs","copy.rs","copy_buf.rs","copy_buf_abortable.rs","cursor.rs","empty.rs","fill_buf.rs","flush.rs","into_sink.rs","line_writer.rs","lines.rs","mod.rs","read.rs","read_exact.rs","read_line.rs","read_to_end.rs","read_to_string.rs","read_until.rs","read_vectored.rs","repeat.rs","seek.rs","sink.rs","split.rs","take.rs","window.rs","write.rs","write_all.rs","write_vectored.rs"]],["lock",[],["bilock.rs","mod.rs","mutex.rs"]],["sink",[],["buffer.rs","close.rs","drain.rs","err_into.rs","fanout.rs","feed.rs","flush.rs","map_err.rs","mod.rs","send.rs","send_all.rs","unfold.rs","with.rs","with_flat_map.rs"]],["stream",[["futures_unordered",[],["abort.rs","iter.rs","mod.rs","ready_to_run_queue.rs","task.rs"]],["stream",[],["all.rs","any.rs","buffer_unordered.rs","buffered.rs","catch_unwind.rs","chain.rs","chunks.rs","collect.rs","concat.rs","count.rs","cycle.rs","enumerate.rs","filter.rs","filter_map.rs","flatten.rs","flatten_unordered.rs","fold.rs","for_each.rs","for_each_concurrent.rs","forward.rs","fuse.rs","into_future.rs","map.rs","mod.rs","next.rs","peek.rs","ready_chunks.rs","scan.rs","select_next_some.rs","skip.rs","skip_while.rs","split.rs","take.rs","take_until.rs","take_while.rs","then.rs","unzip.rs","zip.rs"]],["try_stream",[],["and_then.rs","into_async_read.rs","into_stream.rs","mod.rs","or_else.rs","try_buffer_unordered.rs","try_buffered.rs","try_chunks.rs","try_collect.rs","try_concat.rs","try_filter.rs","try_filter_map.rs","try_flatten.rs","try_fold.rs","try_for_each.rs","try_for_each_concurrent.rs","try_next.rs","try_skip_while.rs","try_take_while.rs","try_unfold.rs"]]],["abortable.rs","empty.rs","futures_ordered.rs","iter.rs","mod.rs","once.rs","pending.rs","poll_fn.rs","poll_immediate.rs","repeat.rs","repeat_with.rs","select.rs","select_all.rs","select_with_strategy.rs","unfold.rs"]],["task",[],["mod.rs","spawn.rs"]]],["abortable.rs","fns.rs","lib.rs","never.rs","unfold_state.rs"]],\ +"generic_array":["",[],["arr.rs","functional.rs","hex.rs","impls.rs","iter.rs","lib.rs","sequence.rs"]],\ +"getrandom":["",[],["error.rs","error_impls.rs","lib.rs","macos.rs","use_file.rs","util.rs","util_libc.rs"]],\ +"growthring":["",[],["lib.rs","wal.rs"]],\ +"hashbrown":["",[["external_trait_impls",[],["mod.rs"]],["raw",[],["alloc.rs","bitmask.rs","mod.rs","sse2.rs"]]],["lib.rs","macros.rs","map.rs","scopeguard.rs","set.rs"]],\ +"heck":["",[],["kebab.rs","lib.rs","lower_camel.rs","shouty_kebab.rs","shouty_snake.rs","snake.rs","title.rs","upper_camel.rs"]],\ +"hex":["",[],["error.rs","lib.rs"]],\ +"impl_rlp":["",[],["lib.rs"]],\ +"keccak":["",[],["lib.rs","unroll.rs"]],\ +"libc":["",[["unix",[["bsd",[["apple",[["b64",[["aarch64",[],["align.rs","mod.rs"]]],["mod.rs"]]],["mod.rs"]]],["mod.rs"]]],["align.rs","mod.rs"]]],["fixed_width_ints.rs","lib.rs","macros.rs"]],\ +"lock_api":["",[],["lib.rs","mutex.rs","remutex.rs","rwlock.rs"]],\ +"lru":["",[],["lib.rs"]],\ +"memchr":["",[["memchr",[],["fallback.rs","iter.rs","mod.rs","naive.rs"]],["memmem",[["prefilter",[],["fallback.rs","mod.rs"]]],["byte_frequencies.rs","mod.rs","rabinkarp.rs","rarebytes.rs","twoway.rs","util.rs"]]],["cow.rs","lib.rs"]],\ +"memoffset":["",[],["lib.rs","offset_of.rs","raw_field.rs","span_of.rs"]],\ +"nix":["",[["mount",[],["linux.rs","mod.rs"]],["net",[],["if_.rs","mod.rs"]],["sys",[["ioctl",[],["linux.rs","mod.rs"]],["ptrace",[],["linux.rs","mod.rs"]],["socket",[],["addr.rs","mod.rs","sockopt.rs"]]],["aio.rs","epoll.rs","eventfd.rs","inotify.rs","memfd.rs","mman.rs","mod.rs","personality.rs","pthread.rs","quota.rs","reboot.rs","resource.rs","select.rs","sendfile.rs","signal.rs","signalfd.rs","stat.rs","statfs.rs","statvfs.rs","sysinfo.rs","termios.rs","time.rs","timer.rs","timerfd.rs","uio.rs","utsname.rs","wait.rs"]]],["dir.rs","env.rs","errno.rs","fcntl.rs","features.rs","ifaddrs.rs","kmod.rs","lib.rs","macros.rs","mqueue.rs","poll.rs","pty.rs","sched.rs","time.rs","ucontext.rs","unistd.rs"]],\ +"once_cell":["",[],["imp_std.rs","lib.rs","race.rs"]],\ +"parking_lot":["",[],["condvar.rs","deadlock.rs","elision.rs","fair_mutex.rs","lib.rs","mutex.rs","once.rs","raw_fair_mutex.rs","raw_mutex.rs","raw_rwlock.rs","remutex.rs","rwlock.rs","util.rs"]],\ +"parking_lot_core":["",[["thread_parker",[],["linux.rs","mod.rs"]]],["lib.rs","parking_lot.rs","spinwait.rs","util.rs","word_lock.rs"]],\ +"pin_project_lite":["",[],["lib.rs"]],\ +"pin_utils":["",[],["lib.rs","projection.rs","stack_pin.rs"]],\ +"ppv_lite86":["",[["x86_64",[],["mod.rs","sse2.rs"]]],["lib.rs","soft.rs","types.rs"]],\ +"primitive_types":["",[],["lib.rs"]],\ +"proc_macro2":["",[],["detection.rs","fallback.rs","lib.rs","marker.rs","parse.rs","rcvec.rs","wrapper.rs"]],\ +"quote":["",[],["ext.rs","format.rs","ident_fragment.rs","lib.rs","runtime.rs","spanned.rs","to_tokens.rs"]],\ +"rand":["",[["distributions",[],["bernoulli.rs","distribution.rs","float.rs","integer.rs","mod.rs","other.rs","slice.rs","uniform.rs","utils.rs","weighted.rs","weighted_index.rs"]],["rngs",[["adapter",[],["mod.rs","read.rs","reseeding.rs"]]],["mock.rs","mod.rs"]],["seq",[],["index.rs","mod.rs"]]],["lib.rs","prelude.rs","rng.rs"]],\ +"rand_chacha":["",[],["chacha.rs","guts.rs","lib.rs"]],\ +"rand_core":["",[],["block.rs","error.rs","impls.rs","le.rs","lib.rs","os.rs"]],\ +"regex":["",[["literal",[],["imp.rs","mod.rs"]]],["backtrack.rs","compile.rs","dfa.rs","error.rs","exec.rs","expand.rs","find_byte.rs","input.rs","lib.rs","pikevm.rs","pool.rs","prog.rs","re_builder.rs","re_bytes.rs","re_set.rs","re_trait.rs","re_unicode.rs","sparse.rs","utf8.rs"]],\ +"regex_syntax":["",[["ast",[],["mod.rs","parse.rs","print.rs","visitor.rs"]],["hir",[["literal",[],["mod.rs"]]],["interval.rs","mod.rs","print.rs","translate.rs","visitor.rs"]],["unicode_tables",[],["age.rs","case_folding_simple.rs","general_category.rs","grapheme_cluster_break.rs","mod.rs","perl_word.rs","property_bool.rs","property_names.rs","property_values.rs","script.rs","script_extension.rs","sentence_break.rs","word_break.rs"]]],["either.rs","error.rs","lib.rs","parser.rs","unicode.rs","utf8.rs"]],\ +"rlp":["",[],["error.rs","impls.rs","lib.rs","rlpin.rs","stream.rs","traits.rs"]],\ +"rustc_hex":["",[],["lib.rs"]],\ +"scan_fmt":["",[],["lib.rs","parse.rs"]],\ +"scopeguard":["",[],["lib.rs"]],\ +"serde":["",[["de",[],["format.rs","ignored_any.rs","impls.rs","mod.rs","seed.rs","utf8.rs","value.rs"]],["private",[],["de.rs","doc.rs","mod.rs","ser.rs","size_hint.rs"]],["ser",[],["fmt.rs","impls.rs","impossible.rs","mod.rs"]]],["integer128.rs","lib.rs","macros.rs"]],\ +"serde_derive":["",[["internals",[],["ast.rs","attr.rs","case.rs","check.rs","ctxt.rs","mod.rs","receiver.rs","respan.rs","symbol.rs"]]],["bound.rs","de.rs","dummy.rs","fragment.rs","lib.rs","pretend.rs","ser.rs","this.rs","try.rs"]],\ +"sha3":["",[],["lib.rs","macros.rs","state.rs"]],\ +"shale":["",[],["block.rs","compact.rs","lib.rs","util.rs"]],\ +"slab":["",[],["lib.rs"]],\ +"smallvec":["",[],["lib.rs"]],\ +"static_assertions":["",[],["assert_cfg.rs","assert_eq_align.rs","assert_eq_size.rs","assert_fields.rs","assert_impl.rs","assert_obj_safe.rs","assert_trait.rs","assert_type.rs","const_assert.rs","lib.rs"]],\ +"syn":["",[["gen",[],["clone.rs","debug.rs","eq.rs","gen_helper.rs","hash.rs","visit_mut.rs"]]],["attr.rs","await.rs","bigint.rs","buffer.rs","custom_keyword.rs","custom_punctuation.rs","data.rs","derive.rs","discouraged.rs","drops.rs","error.rs","export.rs","expr.rs","ext.rs","file.rs","generics.rs","group.rs","ident.rs","item.rs","lib.rs","lifetime.rs","lit.rs","lookahead.rs","mac.rs","macros.rs","op.rs","parse.rs","parse_macro_input.rs","parse_quote.rs","pat.rs","path.rs","print.rs","punctuated.rs","reserved.rs","sealed.rs","span.rs","spanned.rs","stmt.rs","thread.rs","token.rs","tt.rs","ty.rs","verbatim.rs","whitespace.rs"]],\ +"tokio":["",[["future",[],["block_on.rs","maybe_done.rs","mod.rs","poll_fn.rs"]],["io",[],["async_buf_read.rs","async_read.rs","async_seek.rs","async_write.rs","mod.rs","read_buf.rs"]],["loom",[["std",[],["atomic_u16.rs","atomic_u32.rs","atomic_u64.rs","atomic_usize.rs","mod.rs","mutex.rs","unsafe_cell.rs"]]],["mod.rs"]],["macros",[],["addr_of.rs","cfg.rs","join.rs","loom.rs","mod.rs","pin.rs","ready.rs","scoped_tls.rs","select.rs","support.rs","thread_local.rs","try_join.rs"]],["net",[],["addr.rs","mod.rs"]],["runtime",[["blocking",[],["mod.rs","pool.rs","schedule.rs","shutdown.rs","task.rs"]],["metrics",[],["mock.rs","mod.rs"]],["scheduler",[],["current_thread.rs","mod.rs"]],["task",[],["abort.rs","core.rs","error.rs","harness.rs","join.rs","list.rs","mod.rs","raw.rs","state.rs","waker.rs"]]],["builder.rs","config.rs","context.rs","coop.rs","driver.rs","handle.rs","mod.rs","park.rs","runtime.rs"]],["sync",[["mpsc",[],["block.rs","bounded.rs","chan.rs","error.rs","list.rs","mod.rs","unbounded.rs"]],["rwlock",[],["owned_read_guard.rs","owned_write_guard.rs","owned_write_guard_mapped.rs","read_guard.rs","write_guard.rs","write_guard_mapped.rs"]],["task",[],["atomic_waker.rs","mod.rs"]]],["barrier.rs","batch_semaphore.rs","broadcast.rs","mod.rs","mutex.rs","notify.rs","once_cell.rs","oneshot.rs","rwlock.rs","semaphore.rs","watch.rs"]],["task",[],["blocking.rs","join_set.rs","local.rs","mod.rs","spawn.rs","task_local.rs","unconstrained.rs","yield_now.rs"]],["util",[],["atomic_cell.rs","error.rs","idle_notified_set.rs","linked_list.rs","mod.rs","once_cell.rs","rand.rs","rc_cell.rs","sync_wrapper.rs","trace.rs","wake.rs","wake_list.rs"]]],["lib.rs"]],\ +"tokio_macros":["",[],["entry.rs","lib.rs","select.rs"]],\ +"typed_builder":["",[],["field_info.rs","lib.rs","struct_info.rs","util.rs"]],\ +"typenum":["",[],["array.rs","bit.rs","int.rs","lib.rs","marker_traits.rs","operator_aliases.rs","private.rs","type_operators.rs","uint.rs"]],\ +"uint":["",[],["lib.rs","uint.rs"]],\ +"unicode_ident":["",[],["lib.rs","tables.rs"]]\ +}'); +createSourceSidebar(); diff --git a/docs/source-script.js b/docs/source-script.js new file mode 100644 index 000000000000..d8b770a425c8 --- /dev/null +++ b/docs/source-script.js @@ -0,0 +1 @@ +"use strict";(function(){const rootPath=document.getElementById("rustdoc-vars").attributes["data-root-path"].value;const NAME_OFFSET=0;const DIRS_OFFSET=1;const FILES_OFFSET=2;function closeSidebarIfMobile(){if(window.innerWidth"){window.rustdocMobileScrollLock();addClass(document.documentElement,"source-sidebar-expanded");child.innerText="<";updateLocalStorage("source-sidebar-show","true")}else{window.rustdocMobileScrollUnlock();removeClass(document.documentElement,"source-sidebar-expanded");child.innerText=">";updateLocalStorage("source-sidebar-show","false")}}function createSidebarToggle(){const sidebarToggle=document.createElement("div");sidebarToggle.id="sidebar-toggle";const inner=document.createElement("button");if(getCurrentValue("source-sidebar-show")==="true"){inner.innerText="<"}else{inner.innerText=">"}inner.onclick=toggleSidebar;sidebarToggle.appendChild(inner);return sidebarToggle}function createSourceSidebar(){const container=document.querySelector("nav.sidebar");const sidebarToggle=createSidebarToggle();container.insertBefore(sidebarToggle,container.firstChild);const sidebar=document.createElement("div");sidebar.id="source-sidebar";let hasFoundFile=false;const title=document.createElement("div");title.className="title";title.innerText="Files";sidebar.appendChild(title);Object.keys(sourcesIndex).forEach(key=>{sourcesIndex[key][NAME_OFFSET]=key;hasFoundFile=createDirEntry(sourcesIndex[key],sidebar,"",hasFoundFile)});container.appendChild(sidebar);const selected_elem=sidebar.getElementsByClassName("selected")[0];if(typeof selected_elem!=="undefined"){selected_elem.focus()}}const lineNumbersRegex=/^#?(\d+)(?:-(\d+))?$/;function highlightSourceLines(match){if(typeof match==="undefined"){match=window.location.hash.match(lineNumbersRegex)}if(!match){return}let from=parseInt(match[1],10);let to=from;if(typeof match[2]!=="undefined"){to=parseInt(match[2],10)}if(to{onEachLazy(e.getElementsByTagName("span"),i_e=>{removeClass(i_e,"line-highlighted")})});for(let i=from;i<=to;++i){elem=document.getElementById(i);if(!elem){break}addClass(elem,"line-highlighted")}}const handleSourceHighlight=(function(){let prev_line_id=0;const set_fragment=name=>{const x=window.scrollX,y=window.scrollY;if(browserSupportsHistoryApi()){history.replaceState(null,null,"#"+name);highlightSourceLines()}else{location.replace("#"+name)}window.scrollTo(x,y)};return ev=>{let cur_line_id=parseInt(ev.target.id,10);if(isNaN(cur_line_id)){return}ev.preventDefault();if(ev.shiftKey&&prev_line_id){if(prev_line_id>cur_line_id){const tmp=prev_line_id;prev_line_id=cur_line_id;cur_line_id=tmp}set_fragment(prev_line_id+"-"+cur_line_id)}else{prev_line_id=cur_line_id;set_fragment(cur_line_id)}}}());window.addEventListener("hashchange",()=>{const match=window.location.hash.match(lineNumbersRegex);if(match){return highlightSourceLines(match)}});onEachLazy(document.getElementsByClassName("src-line-numbers"),el=>{el.addEventListener("click",handleSourceHighlight)});highlightSourceLines();window.createSourceSidebar=createSourceSidebar})() \ No newline at end of file diff --git a/docs/src/firewood/account.rs.html b/docs/src/firewood/account.rs.html new file mode 100644 index 000000000000..849d06bf77dd --- /dev/null +++ b/docs/src/firewood/account.rs.html @@ -0,0 +1,338 @@ +account.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+
use std::fmt;
+use std::io::{Cursor, Write};
+
+use crate::merkle::{Hash, Node, ValueTransformer};
+use primitive_types::U256;
+use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore};
+
+pub struct Account {
+    pub nonce: u64,
+    pub balance: U256,
+    pub root: ObjPtr<Node>,
+    pub code: ObjPtr<Blob>,
+    pub root_hash: Hash,
+    pub code_hash: Hash,
+}
+
+impl Account {
+    pub fn empty_code() -> &'static Hash {
+        use once_cell::sync::OnceCell;
+        static V: OnceCell<Hash> = OnceCell::new();
+        V.get_or_init(|| {
+            Hash(
+                hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
+                    .unwrap()
+                    .try_into()
+                    .unwrap(),
+            )
+        })
+    }
+
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut buff = Vec::new();
+        buff.extend(self.nonce.to_le_bytes());
+        buff.resize(40, 0);
+        self.balance.to_big_endian(&mut buff[8..40]);
+        buff.extend((self.root.addr()).to_le_bytes());
+        buff.extend((self.code.addr()).to_le_bytes());
+        buff.extend(self.root_hash.0);
+        buff.extend(self.code_hash.0);
+        buff
+    }
+
+    pub fn deserialize(raw: &[u8]) -> Self {
+        let nonce = u64::from_le_bytes(raw[..8].try_into().unwrap());
+        let balance = U256::from_big_endian(&raw[8..40]);
+        let root = u64::from_le_bytes(raw[40..48].try_into().unwrap());
+        let code = u64::from_le_bytes(raw[48..56].try_into().unwrap());
+        let root_hash = Hash(raw[56..88].try_into().unwrap());
+        let code_hash = Hash(raw[88..].try_into().unwrap());
+
+        unsafe {
+            Self {
+                nonce,
+                balance,
+                root: ObjPtr::new_from_addr(root),
+                code: ObjPtr::new_from_addr(code),
+                root_hash,
+                code_hash,
+            }
+        }
+    }
+
+    pub fn set_code(&mut self, code_hash: Hash, code: ObjPtr<Blob>) {
+        self.code_hash = code_hash;
+        self.code = code;
+    }
+}
+
+pub struct AccountRLP;
+
+impl ValueTransformer for AccountRLP {
+    fn transform(raw: &[u8]) -> Vec<u8> {
+        let acc = Account::deserialize(raw);
+        let mut stream = rlp::RlpStream::new_list(4);
+        stream.append(&acc.nonce);
+        stream.append(&acc.balance);
+        stream.append(&&acc.root_hash[..]);
+        stream.append(&&acc.code_hash[..]);
+        stream.out().into()
+    }
+}
+
+impl Default for Account {
+    fn default() -> Self {
+        Account {
+            nonce: 0,
+            balance: U256::zero(),
+            root: ObjPtr::null(),
+            code: ObjPtr::null(),
+            root_hash: crate::merkle::Merkle::empty_root().clone(),
+            code_hash: Self::empty_code().clone(),
+        }
+    }
+}
+
+pub enum Blob {
+    Code(Vec<u8>),
+}
+
+impl MummyItem for Blob {
+    // currently there is only one variant of Blob: Code
+    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
+        let raw = mem.get_view(addr, 4).ok_or(ShaleError::LinearMemStoreError)?;
+        let len = u32::from_le_bytes(raw[..].try_into().unwrap()) as u64;
+        let bytes = mem.get_view(addr + 4, len).ok_or(ShaleError::LinearMemStoreError)?;
+        Ok(Self::Code(bytes.to_vec()))
+    }
+
+    fn dehydrated_len(&self) -> u64 {
+        match self {
+            Self::Code(code) => 4 + code.len() as u64,
+        }
+    }
+
+    fn dehydrate(&self, to: &mut [u8]) {
+        match self {
+            Self::Code(code) => {
+                let mut cur = Cursor::new(to);
+                cur.write_all(&(code.len() as u32).to_le_bytes()).unwrap();
+                cur.write_all(code).unwrap();
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum BlobError {
+    Shale(ShaleError),
+}
+
+pub struct BlobStash {
+    store: Box<dyn ShaleStore<Blob>>,
+}
+
+impl BlobStash {
+    pub fn new(store: Box<dyn ShaleStore<Blob>>) -> Self {
+        Self { store }
+    }
+
+    pub fn get_blob(&self, ptr: ObjPtr<Blob>) -> Result<ObjRef<Blob>, BlobError> {
+        self.store.get_item(ptr).map_err(BlobError::Shale)
+    }
+
+    pub fn new_blob(&self, item: Blob) -> Result<ObjRef<Blob>, BlobError> {
+        self.store.put_item(item, 0).map_err(BlobError::Shale)
+    }
+
+    pub fn free_blob(&mut self, ptr: ObjPtr<Blob>) -> Result<(), BlobError> {
+        self.store.free_item(ptr).map_err(BlobError::Shale)
+    }
+
+    pub fn flush_dirty(&self) -> Option<()> {
+        self.store.flush_dirty()
+    }
+}
+
+impl fmt::Debug for Account {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(
+            f,
+            "<Account balance={} nonce={} code_hash={} state_hash={}>",
+            self.balance,
+            self.nonce,
+            hex::encode(*self.code_hash),
+            hex::encode(*self.root_hash)
+        )
+    }
+}
+
+
\ No newline at end of file diff --git a/docs/src/firewood/db.rs.html b/docs/src/firewood/db.rs.html new file mode 100644 index 000000000000..2667221bc88b --- /dev/null +++ b/docs/src/firewood/db.rs.html @@ -0,0 +1,2020 @@ +db.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+
use std::collections::VecDeque;
+use std::io::{Cursor, Write};
+use std::rc::Rc;
+use std::thread::JoinHandle;
+
+use parking_lot::{Mutex, MutexGuard};
+use primitive_types::U256;
+use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID};
+use typed_builder::TypedBuilder;
+
+use crate::account::{Account, AccountRLP, Blob, BlobStash};
+use crate::file;
+use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node};
+use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared};
+pub use crate::storage::{DiskBufferConfig, WALConfig};
+
+const MERKLE_META_SPACE: SpaceID = 0x0;
+const MERKLE_PAYLOAD_SPACE: SpaceID = 0x1;
+const BLOB_META_SPACE: SpaceID = 0x2;
+const BLOB_PAYLOAD_SPACE: SpaceID = 0x3;
+const SPACE_RESERVED: u64 = 0x1000;
+
+const MAGIC_STR: &[u8; 13] = b"firewood v0.1";
+
+#[derive(Debug)]
+pub enum DBError {
+    InvalidParams,
+    Merkle(MerkleError),
+    Blob(crate::account::BlobError),
+    System(nix::Error),
+}
+
+/// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the
+/// correct parameters are used when the DB is opened later (the parameters here will override the
+/// parameters in [DBConfig] if the DB already exists).
+#[repr(C)]
+struct DBParams {
+    magic: [u8; 16],
+    meta_file_nbit: u64,
+    payload_file_nbit: u64,
+    payload_regn_nbit: u64,
+    wal_file_nbit: u64,
+    wal_block_nbit: u64,
+}
+
+/// Config for accessing a version of the DB.
+#[derive(TypedBuilder, Clone)]
+pub struct DBRevConfig {
+    /// Maximum cached MPT objects.
+    #[builder(default = 1 << 20)]
+    merkle_ncached_objs: usize,
+    /// Maximum cached Blob (currently just `Account`) objects.
+    #[builder(default = 4096)]
+    blob_ncached_objs: usize,
+}
+
+/// Database configuration.
+#[derive(TypedBuilder)]
+pub struct DBConfig {
+    /// Maximum cached pages for the free list of the item stash.
+    #[builder(default = 16384)] // 64M total size by default
+    meta_ncached_pages: usize,
+    /// Maximum cached file descriptors for the free list of the item stash.
+    #[builder(default = 1024)] // 1K fds by default
+    meta_ncached_files: usize,
+    /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to
+    /// the power of 2 for the file size.
+    #[builder(default = 22)] // 4MB file by default
+    meta_file_nbit: u64,
+    /// Maximum cached pages for the item stash. This is the low-level cache used by the linear
+    /// space that holds MPT nodes and account objects.
+    #[builder(default = 262144)] // 1G total size by default
+    payload_ncached_pages: usize,
+    /// Maximum cached file descriptors for the item stash.
+    #[builder(default = 1024)] // 1K fds by default
+    payload_ncached_files: usize,
+    /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to
+    /// the power of 2 for the file size.
+    #[builder(default = 22)] // 4MB file by default
+    payload_file_nbit: u64,
+    /// Maximum steps of walk to recycle a freed item.
+    #[builder(default = 10)]
+    payload_max_walk: u64,
+    /// Region size in bits (should be not greater than `payload_file_nbit`). One file is
+    /// partitioned into multiple regions. Just use the default value.
+    #[builder(default = 22)]
+    payload_regn_nbit: u64,
+    /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its
+    /// existing contents will be lost.
+    #[builder(default = false)]
+    truncate: bool,
+    /// Config for accessing a version of the DB.
+    #[builder(default = DBRevConfig::builder().build())]
+    rev: DBRevConfig,
+    /// Config for the disk buffer.
+    #[builder(default = DiskBufferConfig::builder().build())]
+    buffer: DiskBufferConfig,
+    /// Config for WAL.
+    #[builder(default = WALConfig::builder().build())]
+    wal: WALConfig,
+}
+
+/// Necessary linear space instances bundled for a `CompactSpace`.
+struct SubUniverse<T> {
+    meta: T,
+    payload: T,
+}
+
+impl<T> SubUniverse<T> {
+    fn new(meta: T, payload: T) -> Self {
+        Self { meta, payload }
+    }
+}
+
+impl SubUniverse<StoreRevShared> {
+    fn to_mem_store_r(&self) -> SubUniverse<Rc<dyn MemStoreR>> {
+        SubUniverse {
+            meta: self.meta.inner().clone(),
+            payload: self.payload.inner().clone(),
+        }
+    }
+}
+
+impl SubUniverse<Rc<dyn MemStoreR>> {
+    fn rewind(&self, meta_writes: &[SpaceWrite], payload_writes: &[SpaceWrite]) -> SubUniverse<StoreRevShared> {
+        SubUniverse::new(
+            StoreRevShared::from_ash(self.meta.clone(), meta_writes),
+            StoreRevShared::from_ash(self.payload.clone(), payload_writes),
+        )
+    }
+}
+
+impl SubUniverse<Rc<CachedSpace>> {
+    fn to_mem_store_r(&self) -> SubUniverse<Rc<dyn MemStoreR>> {
+        SubUniverse {
+            meta: self.meta.clone(),
+            payload: self.payload.clone(),
+        }
+    }
+}
+
+/// DB-wide metadata, it keeps track of the roots of the top-level tries.
+struct DBHeader {
+    /// The root node of the account model storage. (Where the values are [Account] objects, which
+    /// may contain the root for the secondary trie.)
+    acc_root: ObjPtr<Node>,
+    /// The root node of the generic key-value store.
+    kv_root: ObjPtr<Node>,
+}
+
+impl DBHeader {
+    pub const MSIZE: u64 = 16;
+
+    pub fn new_empty() -> Self {
+        Self {
+            acc_root: ObjPtr::null(),
+            kv_root: ObjPtr::null(),
+        }
+    }
+}
+
+impl MummyItem for DBHeader {
+    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, shale::ShaleError> {
+        let raw = mem
+            .get_view(addr, Self::MSIZE)
+            .ok_or(shale::ShaleError::LinearMemStoreError)?;
+        let acc_root = u64::from_le_bytes(raw[..8].try_into().unwrap());
+        let kv_root = u64::from_le_bytes(raw[8..].try_into().unwrap());
+        unsafe {
+            Ok(Self {
+                acc_root: ObjPtr::new_from_addr(acc_root),
+                kv_root: ObjPtr::new_from_addr(kv_root),
+            })
+        }
+    }
+
+    fn dehydrated_len(&self) -> u64 {
+        Self::MSIZE
+    }
+
+    fn dehydrate(&self, to: &mut [u8]) {
+        let mut cur = Cursor::new(to);
+        cur.write_all(&self.acc_root.addr().to_le_bytes()).unwrap();
+        cur.write_all(&self.kv_root.addr().to_le_bytes()).unwrap();
+    }
+}
+
+/// Necessary linear space instances bundled for the state of the entire DB.
+struct Universe<T> {
+    merkle: SubUniverse<T>,
+    blob: SubUniverse<T>,
+}
+
+impl Universe<StoreRevShared> {
+    fn to_mem_store_r(&self) -> Universe<Rc<dyn MemStoreR>> {
+        Universe {
+            merkle: self.merkle.to_mem_store_r(),
+            blob: self.blob.to_mem_store_r(),
+        }
+    }
+}
+
+impl Universe<Rc<CachedSpace>> {
+    fn to_mem_store_r(&self) -> Universe<Rc<dyn MemStoreR>> {
+        Universe {
+            merkle: self.merkle.to_mem_store_r(),
+            blob: self.blob.to_mem_store_r(),
+        }
+    }
+}
+
+impl Universe<Rc<dyn MemStoreR>> {
+    fn rewind(
+        &self, merkle_meta_writes: &[SpaceWrite], merkle_payload_writes: &[SpaceWrite],
+        blob_meta_writes: &[SpaceWrite], blob_payload_writes: &[SpaceWrite],
+    ) -> Universe<StoreRevShared> {
+        Universe {
+            merkle: self.merkle.rewind(merkle_meta_writes, merkle_payload_writes),
+            blob: self.blob.rewind(blob_meta_writes, blob_payload_writes),
+        }
+    }
+}
+
+/// Some readable version of the DB.
+pub struct DBRev {
+    header: shale::Obj<DBHeader>,
+    merkle: Merkle,
+    blob: BlobStash,
+}
+
+impl DBRev {
+    fn flush_dirty(&mut self) -> Option<()> {
+        self.header.flush_dirty();
+        self.merkle.flush_dirty()?;
+        self.blob.flush_dirty()
+    }
+
+    fn borrow_split(&mut self) -> (&mut shale::Obj<DBHeader>, &mut Merkle, &mut BlobStash) {
+        (&mut self.header, &mut self.merkle, &mut self.blob)
+    }
+
+    /// Get root hash of the generic key-value storage.
+    pub fn kv_root_hash(&self) -> Result<Hash, DBError> {
+        self.merkle
+            .root_hash::<IdTrans>(self.header.kv_root)
+            .map_err(DBError::Merkle)
+    }
+
+    /// Dump the MPT of the generic key-value storage.
+    pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
+        self.merkle.dump(self.header.kv_root, w).map_err(DBError::Merkle)
+    }
+
+    /// Get root hash of the world state of all accounts.
+    pub fn root_hash(&self) -> Result<Hash, DBError> {
+        self.merkle
+            .root_hash::<AccountRLP>(self.header.acc_root)
+            .map_err(DBError::Merkle)
+    }
+
+    /// Dump the MPT of the entire account model storage.
+    pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
+        self.merkle.dump(self.header.acc_root, w).map_err(DBError::Merkle)
+    }
+
+    fn get_account(&self, key: &[u8]) -> Result<Account, DBError> {
+        Ok(match self.merkle.get(key, self.header.acc_root) {
+            Ok(Some(bytes)) => Account::deserialize(&bytes),
+            Ok(None) => Account::default(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        })
+    }
+
+    /// Dump the MPT of the state storage under an account.
+    pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> {
+        let acc = match self.merkle.get(key, self.header.acc_root) {
+            Ok(Some(bytes)) => Account::deserialize(&bytes),
+            Ok(None) => Account::default(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        };
+        writeln!(w, "{acc:?}").unwrap();
+        if !acc.root.is_null() {
+            self.merkle.dump(acc.root, w).map_err(DBError::Merkle)?;
+        }
+        Ok(())
+    }
+
+    /// Get balance of the account.
+    pub fn get_balance(&self, key: &[u8]) -> Result<U256, DBError> {
+        Ok(self.get_account(key)?.balance)
+    }
+
+    /// Get code of the account.
+    pub fn get_code(&self, key: &[u8]) -> Result<Vec<u8>, DBError> {
+        let code = self.get_account(key)?.code;
+        if code.is_null() {
+            return Ok(Vec::new())
+        }
+        let b = self.blob.get_blob(code).map_err(DBError::Blob)?;
+        Ok(match &**b {
+            Blob::Code(code) => code.clone(),
+        })
+    }
+
+    /// Get nonce of the account.
+    pub fn get_nonce(&self, key: &[u8]) -> Result<u64, DBError> {
+        Ok(self.get_account(key)?.nonce)
+    }
+
+    /// Get the state value indexed by `sub_key` in the account indexed by `key`.
+    pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result<Vec<u8>, DBError> {
+        let root = self.get_account(key)?.root;
+        if root.is_null() {
+            return Ok(Vec::new())
+        }
+        Ok(match self.merkle.get(sub_key, root) {
+            Ok(Some(v)) => v.to_vec(),
+            Ok(None) => Vec::new(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        })
+    }
+
+    /// Check if the account exists.
+    pub fn exist(&self, key: &[u8]) -> Result<bool, DBError> {
+        Ok(match self.merkle.get(key, self.header.acc_root) {
+            Ok(r) => r.is_some(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        })
+    }
+}
+
+struct DBInner {
+    latest: DBRev,
+    disk_requester: crate::storage::DiskBufferRequester,
+    disk_thread: Option<JoinHandle<()>>,
+    staging: Universe<Rc<StoreRevMut>>,
+    cached: Universe<Rc<CachedSpace>>,
+    revisions: VecDeque<Universe<StoreRevShared>>,
+    max_revisions: usize,
+}
+
+impl Drop for DBInner {
+    fn drop(&mut self) {
+        self.disk_requester.shutdown();
+        self.disk_thread.take().map(JoinHandle::join);
+    }
+}
+
+/// Firewood database handle.
+pub struct DB {
+    inner: Mutex<DBInner>,
+    payload_regn_nbit: u64,
+    rev_cfg: DBRevConfig,
+}
+
+impl DB {
+    /// Open a database.
+    pub fn new(db_path: &str, cfg: &DBConfig) -> Result<Self, DBError> {
+        // TODO: make sure all fds are released at the end
+        if cfg.truncate {
+            let _ = std::fs::remove_dir_all(db_path);
+        }
+        let (db_fd, reset) = file::open_dir(db_path, cfg.truncate).map_err(DBError::System)?;
+
+        let merkle_fd = file::touch_dir("merkle", db_fd).map_err(DBError::System)?;
+        let merkle_meta_fd = file::touch_dir("meta", merkle_fd).map_err(DBError::System)?;
+        let merkle_payload_fd = file::touch_dir("compact", merkle_fd).map_err(DBError::System)?;
+
+        let blob_fd = file::touch_dir("blob", db_fd).map_err(DBError::System)?;
+        let blob_meta_fd = file::touch_dir("meta", blob_fd).map_err(DBError::System)?;
+        let blob_payload_fd = file::touch_dir("compact", blob_fd).map_err(DBError::System)?;
+
+        let file0 = crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?;
+        let fd0 = file0.get_fd();
+
+        if reset {
+            // initialize DBParams
+            if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT {
+                return Err(DBError::InvalidParams)
+            }
+            nix::unistd::ftruncate(fd0, 0).map_err(DBError::System)?;
+            nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DBError::System)?;
+            let mut magic = [0; 16];
+            magic[..MAGIC_STR.len()].copy_from_slice(MAGIC_STR);
+            let header = DBParams {
+                magic,
+                meta_file_nbit: cfg.meta_file_nbit,
+                payload_file_nbit: cfg.payload_file_nbit,
+                payload_regn_nbit: cfg.payload_regn_nbit,
+                wal_file_nbit: cfg.wal.file_nbit,
+                wal_block_nbit: cfg.wal.block_nbit,
+            };
+            nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0).map_err(DBError::System)?;
+        }
+
+        // read DBParams
+        let mut header_bytes = [0; std::mem::size_of::<DBParams>()];
+        nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DBError::System)?;
+        drop(file0);
+        let mut offset = header_bytes.len() as u64;
+        let header = unsafe { std::mem::transmute::<_, DBParams>(header_bytes) };
+
+        // setup disk buffer
+        let cached = Universe {
+            merkle: SubUniverse::new(
+                Rc::new(
+                    CachedSpace::new(
+                        &StoreConfig::builder()
+                            .ncached_pages(cfg.meta_ncached_pages)
+                            .ncached_files(cfg.meta_ncached_files)
+                            .space_id(MERKLE_META_SPACE)
+                            .file_nbit(header.meta_file_nbit)
+                            .rootfd(merkle_meta_fd)
+                            .build(),
+                    )
+                    .unwrap(),
+                ),
+                Rc::new(
+                    CachedSpace::new(
+                        &StoreConfig::builder()
+                            .ncached_pages(cfg.payload_ncached_pages)
+                            .ncached_files(cfg.payload_ncached_files)
+                            .space_id(MERKLE_PAYLOAD_SPACE)
+                            .file_nbit(header.payload_file_nbit)
+                            .rootfd(merkle_payload_fd)
+                            .build(),
+                    )
+                    .unwrap(),
+                ),
+            ),
+            blob: SubUniverse::new(
+                Rc::new(
+                    CachedSpace::new(
+                        &StoreConfig::builder()
+                            .ncached_pages(cfg.meta_ncached_pages)
+                            .ncached_files(cfg.meta_ncached_files)
+                            .space_id(BLOB_META_SPACE)
+                            .file_nbit(header.meta_file_nbit)
+                            .rootfd(blob_meta_fd)
+                            .build(),
+                    )
+                    .unwrap(),
+                ),
+                Rc::new(
+                    CachedSpace::new(
+                        &StoreConfig::builder()
+                            .ncached_pages(cfg.payload_ncached_pages)
+                            .ncached_files(cfg.payload_ncached_files)
+                            .space_id(BLOB_PAYLOAD_SPACE)
+                            .file_nbit(header.payload_file_nbit)
+                            .rootfd(blob_payload_fd)
+                            .build(),
+                    )
+                    .unwrap(),
+                ),
+            ),
+        };
+
+        let wal = WALConfig::builder()
+            .file_nbit(header.wal_file_nbit)
+            .block_nbit(header.wal_block_nbit)
+            .max_revisions(cfg.wal.max_revisions)
+            .build();
+        let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered);
+        let disk_requester = crate::storage::DiskBufferRequester::new(sender);
+        let buffer = cfg.buffer.clone();
+        let disk_thread = Some(std::thread::spawn(move || {
+            let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap();
+            disk_buffer.run()
+        }));
+
+        disk_requester.reg_cached_space(cached.merkle.meta.as_ref());
+        disk_requester.reg_cached_space(cached.merkle.payload.as_ref());
+        disk_requester.reg_cached_space(cached.blob.meta.as_ref());
+        disk_requester.reg_cached_space(cached.blob.payload.as_ref());
+
+        let staging = Universe {
+            merkle: SubUniverse::new(
+                Rc::new(StoreRevMut::new(cached.merkle.meta.clone() as Rc<dyn MemStoreR>)),
+                Rc::new(StoreRevMut::new(cached.merkle.payload.clone() as Rc<dyn MemStoreR>)),
+            ),
+            blob: SubUniverse::new(
+                Rc::new(StoreRevMut::new(cached.blob.meta.clone() as Rc<dyn MemStoreR>)),
+                Rc::new(StoreRevMut::new(cached.blob.payload.clone() as Rc<dyn MemStoreR>)),
+            ),
+        };
+
+        // recover from WAL
+        disk_requester.init_wal("wal", db_fd);
+
+        // set up the storage layout
+        let db_header: ObjPtr<DBHeader>;
+        let merkle_payload_header: ObjPtr<CompactSpaceHeader>;
+        let blob_payload_header: ObjPtr<CompactSpaceHeader>;
+        unsafe {
+            db_header = ObjPtr::new_from_addr(offset);
+            offset += DBHeader::MSIZE;
+            merkle_payload_header = ObjPtr::new_from_addr(offset);
+            offset += CompactSpaceHeader::MSIZE;
+            assert!(offset <= SPACE_RESERVED);
+            blob_payload_header = ObjPtr::new_from_addr(0);
+        }
+
+        if reset {
+            // initialize space headers
+            staging.merkle.meta.write(
+                merkle_payload_header.addr(),
+                &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)),
+            );
+            staging
+                .merkle
+                .meta
+                .write(db_header.addr(), &shale::to_dehydrated(&DBHeader::new_empty()));
+            staging.blob.meta.write(
+                blob_payload_header.addr(),
+                &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)),
+            );
+        }
+
+        let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe {
+            let merkle_meta_ref = staging.merkle.meta.as_ref() as &dyn MemStore;
+            let blob_meta_ref = staging.blob.meta.as_ref() as &dyn MemStore;
+
+            (
+                MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(),
+                MummyObj::ptr_to_obj(
+                    merkle_meta_ref,
+                    merkle_payload_header,
+                    shale::compact::CompactHeader::MSIZE,
+                )
+                .unwrap(),
+                MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(),
+            )
+        };
+
+        let merkle_space = shale::compact::CompactSpace::new(
+            staging.merkle.meta.clone(),
+            staging.merkle.payload.clone(),
+            merkle_payload_header_ref,
+            shale::ObjCache::new(cfg.rev.merkle_ncached_objs),
+            cfg.payload_max_walk,
+            header.payload_regn_nbit,
+        )
+        .unwrap();
+
+        let blob_space = shale::compact::CompactSpace::new(
+            staging.blob.meta.clone(),
+            staging.blob.payload.clone(),
+            blob_payload_header_ref,
+            shale::ObjCache::new(cfg.rev.blob_ncached_objs),
+            cfg.payload_max_walk,
+            header.payload_regn_nbit,
+        )
+        .unwrap();
+
+        if db_header_ref.acc_root.is_null() {
+            let mut err = Ok(());
+            // create the sentinel node
+            db_header_ref
+                .write(|r| {
+                    err = (|| {
+                        Merkle::init_root(&mut r.acc_root, &merkle_space)?;
+                        Merkle::init_root(&mut r.kv_root, &merkle_space)
+                    })();
+                })
+                .unwrap();
+            err.map_err(DBError::Merkle)?
+        }
+
+        let mut latest = DBRev {
+            header: db_header_ref,
+            merkle: Merkle::new(Box::new(merkle_space)),
+            blob: BlobStash::new(Box::new(blob_space)),
+        };
+        latest.flush_dirty().unwrap();
+
+        Ok(Self {
+            inner: Mutex::new(DBInner {
+                latest,
+                disk_thread,
+                disk_requester,
+                staging,
+                cached,
+                revisions: VecDeque::new(),
+                max_revisions: cfg.wal.max_revisions as usize,
+            }),
+            payload_regn_nbit: header.payload_regn_nbit,
+            rev_cfg: cfg.rev.clone(),
+        })
+    }
+
+    /// Create a write batch.
+    pub fn new_writebatch(&self) -> WriteBatch {
+        WriteBatch {
+            m: self.inner.lock(),
+            root_hash_recalc: true,
+            committed: false,
+        }
+    }
+
+    /// Dump the MPT of the latest generic key-value storage.
+    pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
+        self.inner.lock().latest.kv_dump(w)
+    }
+
+    /// Dump the MPT of the latest entire account model storage.
+    pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
+        self.inner.lock().latest.dump(w)
+    }
+
+    /// Dump the MPT of the latest state storage under an account.
+    pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> {
+        self.inner.lock().latest.dump_account(key, w)
+    }
+
+    /// Get root hash of the latest generic key-value storage.
+    pub fn kv_root_hash(&self) -> Result<Hash, DBError> {
+        self.inner.lock().latest.kv_root_hash()
+    }
+
+    /// Get root hash of the latest world state of all accounts.
+    pub fn root_hash(&self) -> Result<Hash, DBError> {
+        self.inner.lock().latest.root_hash()
+    }
+
+    /// Get the latest balance of the account.
+    pub fn get_balance(&self, key: &[u8]) -> Result<U256, DBError> {
+        self.inner.lock().latest.get_balance(key)
+    }
+
+    /// Get the latest code of the account.
+    pub fn get_code(&self, key: &[u8]) -> Result<Vec<u8>, DBError> {
+        self.inner.lock().latest.get_code(key)
+    }
+
+    /// Get the latest nonce of the account.
+    pub fn get_nonce(&self, key: &[u8]) -> Result<u64, DBError> {
+        self.inner.lock().latest.get_nonce(key)
+    }
+
+    /// Get the latest state value indexed by `sub_key` in the account indexed by `key`.
+    pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result<Vec<u8>, DBError> {
+        self.inner.lock().latest.get_state(key, sub_key)
+    }
+
+    /// Check if the account exists in the latest world state.
+    pub fn exist(&self, key: &[u8]) -> Result<bool, DBError> {
+        self.inner.lock().latest.exist(key)
+    }
+
+    /// Get a handle that grants the access to some historical state of the entire DB.
+    pub fn get_revision(&self, nback: usize, cfg: Option<DBRevConfig>) -> Option<Revision> {
+        let mut inner = self.inner.lock();
+
+        let rlen = inner.revisions.len();
+        if nback == 0 || nback > inner.max_revisions {
+            return None
+        }
+        if rlen < nback {
+            let ashes = inner.disk_requester.collect_ash(nback);
+            for mut ash in ashes.into_iter().skip(rlen) {
+                for (_, a) in ash.0.iter_mut() {
+                    a.old.reverse()
+                }
+
+                let u = match inner.revisions.back() {
+                    Some(u) => u.to_mem_store_r(),
+                    None => inner.cached.to_mem_store_r(),
+                };
+                inner.revisions.push_back(u.rewind(
+                    &ash.0[&MERKLE_META_SPACE].old,
+                    &ash.0[&MERKLE_PAYLOAD_SPACE].old,
+                    &ash.0[&BLOB_META_SPACE].old,
+                    &ash.0[&BLOB_PAYLOAD_SPACE].old,
+                ));
+            }
+        }
+        if inner.revisions.len() < nback {
+            return None
+        }
+        // set up the storage layout
+        let db_header: ObjPtr<DBHeader>;
+        let merkle_payload_header: ObjPtr<CompactSpaceHeader>;
+        let blob_payload_header: ObjPtr<CompactSpaceHeader>;
+        unsafe {
+            let mut offset = std::mem::size_of::<DBParams>() as u64;
+            // DBHeader starts after DBParams in merkle meta space
+            db_header = ObjPtr::new_from_addr(offset);
+            offset += DBHeader::MSIZE;
+            // Merkle CompactHeader starts after DBHeader in merkle meta space
+            merkle_payload_header = ObjPtr::new_from_addr(offset);
+            offset += CompactSpaceHeader::MSIZE;
+            assert!(offset <= SPACE_RESERVED);
+            // Blob CompactSpaceHeader starts right in blob meta space
+            blob_payload_header = ObjPtr::new_from_addr(0);
+        }
+
+        let space = &inner.revisions[nback - 1];
+
+        let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe {
+            let merkle_meta_ref = &space.merkle.meta as &dyn MemStore;
+            let blob_meta_ref = &space.blob.meta as &dyn MemStore;
+
+            (
+                MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(),
+                MummyObj::ptr_to_obj(
+                    merkle_meta_ref,
+                    merkle_payload_header,
+                    shale::compact::CompactHeader::MSIZE,
+                )
+                .unwrap(),
+                MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(),
+            )
+        };
+
+        let merkle_space = shale::compact::CompactSpace::new(
+            Rc::new(space.merkle.meta.clone()),
+            Rc::new(space.merkle.payload.clone()),
+            merkle_payload_header_ref,
+            shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).merkle_ncached_objs),
+            0,
+            self.payload_regn_nbit,
+        )
+        .unwrap();
+
+        let blob_space = shale::compact::CompactSpace::new(
+            Rc::new(space.blob.meta.clone()),
+            Rc::new(space.blob.payload.clone()),
+            blob_payload_header_ref,
+            shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).blob_ncached_objs),
+            0,
+            self.payload_regn_nbit,
+        )
+        .unwrap();
+
+        Some(Revision {
+            _m: inner,
+            rev: DBRev {
+                header: db_header_ref,
+                merkle: Merkle::new(Box::new(merkle_space)),
+                blob: BlobStash::new(Box::new(blob_space)),
+            },
+        })
+    }
+}
+
+/// Lock protected handle to a readable version of the DB.
+pub struct Revision<'a> {
+    _m: MutexGuard<'a, DBInner>,
+    rev: DBRev,
+}
+
+impl<'a> std::ops::Deref for Revision<'a> {
+    type Target = DBRev;
+    fn deref(&self) -> &DBRev {
+        &self.rev
+    }
+}
+
+/// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself
+/// because when an error occurs, the write batch will be automaticlaly aborted so that the DB
+/// remains clean.
+pub struct WriteBatch<'a> {
+    m: MutexGuard<'a, DBInner>,
+    root_hash_recalc: bool,
+    committed: bool,
+}
+
+impl<'a> WriteBatch<'a> {
+    /// Insert an item to the generic key-value storage.
+    pub fn kv_insert<K: AsRef<[u8]>>(mut self, key: K, val: Vec<u8>) -> Result<Self, DBError> {
+        let (header, merkle, _) = self.m.latest.borrow_split();
+        merkle.insert(key, val, header.kv_root).map_err(DBError::Merkle)?;
+        Ok(self)
+    }
+
+    /// Remove an item from the generic key-value storage. `val` will be set to the value that is
+    /// removed from the storage if it exists.
+    pub fn kv_remove<K: AsRef<[u8]>>(mut self, key: K, val: &mut Option<Vec<u8>>) -> Result<Self, DBError> {
+        let (header, merkle, _) = self.m.latest.borrow_split();
+        *val = merkle.remove(key, header.kv_root).map_err(DBError::Merkle)?;
+        Ok(self)
+    }
+
+    fn change_account(
+        &mut self, key: &[u8], modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>,
+    ) -> Result<(), DBError> {
+        let (header, merkle, blob) = self.m.latest.borrow_split();
+        match merkle.get_mut(key, header.acc_root) {
+            Ok(Some(mut bytes)) => {
+                let mut ret = Ok(());
+                bytes
+                    .write(|b| {
+                        let mut acc = Account::deserialize(b);
+                        ret = modify(&mut acc, blob);
+                        if ret.is_err() {
+                            return
+                        }
+                        *b = acc.serialize();
+                    })
+                    .map_err(DBError::Merkle)?;
+                ret?;
+            }
+            Ok(None) => {
+                let mut acc = Account::default();
+                modify(&mut acc, blob)?;
+                merkle
+                    .insert(key, acc.serialize(), header.acc_root)
+                    .map_err(DBError::Merkle)?;
+            }
+            Err(e) => return Err(DBError::Merkle(e)),
+        }
+        Ok(())
+    }
+
+    /// Set balance of the account.
+    pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result<Self, DBError> {
+        self.change_account(key, |acc, _| {
+            acc.balance = balance;
+            Ok(())
+        })?;
+        Ok(self)
+    }
+
+    /// Set code of the account.
+    pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result<Self, DBError> {
+        use sha3::Digest;
+        self.change_account(key, |acc, blob_stash| {
+            if !acc.code.is_null() {
+                blob_stash.free_blob(acc.code).map_err(DBError::Blob)?;
+            }
+            acc.set_code(
+                Hash(sha3::Keccak256::digest(code).into()),
+                blob_stash
+                    .new_blob(Blob::Code(code.to_vec()))
+                    .map_err(DBError::Blob)?
+                    .as_ptr(),
+            );
+            Ok(())
+        })?;
+        Ok(self)
+    }
+
+    /// Set nonce of the account.
+    pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result<Self, DBError> {
+        self.change_account(key, |acc, _| {
+            acc.nonce = nonce;
+            Ok(())
+        })?;
+        Ok(self)
+    }
+
+    /// Set the state value indexed by `sub_key` in the account indexed by `key`.
+    pub fn set_state(mut self, key: &[u8], sub_key: &[u8], val: Vec<u8>) -> Result<Self, DBError> {
+        let (header, merkle, _) = self.m.latest.borrow_split();
+        let mut acc = match merkle.get(key, header.acc_root) {
+            Ok(Some(r)) => Account::deserialize(&r),
+            Ok(None) => Account::default(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        };
+        if acc.root.is_null() {
+            Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DBError::Merkle)?;
+        }
+        merkle.insert(sub_key, val, acc.root).map_err(DBError::Merkle)?;
+        acc.root_hash = merkle.root_hash::<IdTrans>(acc.root).map_err(DBError::Merkle)?;
+        merkle
+            .insert(key, acc.serialize(), header.acc_root)
+            .map_err(DBError::Merkle)?;
+        Ok(self)
+    }
+
+    /// Create an account.
+    pub fn create_account(mut self, key: &[u8]) -> Result<Self, DBError> {
+        let (header, merkle, _) = self.m.latest.borrow_split();
+        let old_balance = match merkle.get_mut(key, header.acc_root) {
+            Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance,
+            Ok(None) => U256::zero(),
+            Err(e) => return Err(DBError::Merkle(e)),
+        };
+        let acc = Account {
+            balance: old_balance,
+            ..Default::default()
+        };
+        merkle
+            .insert(key, acc.serialize(), header.acc_root)
+            .map_err(DBError::Merkle)?;
+        Ok(self)
+    }
+
+    /// Delete an account.
+    pub fn delete_account(mut self, key: &[u8], acc: &mut Option<Account>) -> Result<Self, DBError> {
+        let (header, merkle, blob_stash) = self.m.latest.borrow_split();
+        let mut a = match merkle.remove(key, header.acc_root) {
+            Ok(Some(bytes)) => Account::deserialize(&bytes),
+            Ok(None) => {
+                *acc = None;
+                return Ok(self)
+            }
+            Err(e) => return Err(DBError::Merkle(e)),
+        };
+        if !a.root.is_null() {
+            merkle.remove_tree(a.root).map_err(DBError::Merkle)?;
+            a.root = ObjPtr::null();
+        }
+        if !a.code.is_null() {
+            blob_stash.free_blob(a.code).map_err(DBError::Blob)?;
+            a.code = ObjPtr::null();
+        }
+        *acc = Some(a);
+        Ok(self)
+    }
+
+    /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root
+    /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits.
+    pub fn no_root_hash(mut self) -> Self {
+        self.root_hash_recalc = false;
+        self
+    }
+
+    /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are
+    /// either retained on disk or lost together during a crash.
+    pub fn commit(mut self) {
+        use crate::storage::BufferWrite;
+        let inner = &mut *self.m;
+        if self.root_hash_recalc {
+            inner.latest.root_hash().ok();
+            inner.latest.kv_root_hash().ok();
+        }
+        // clear the staging layer and apply changes to the CachedSpace
+        inner.latest.flush_dirty().unwrap();
+        let (merkle_payload_pages, merkle_payload_plain) = inner.staging.merkle.payload.take_delta();
+        let (merkle_meta_pages, merkle_meta_plain) = inner.staging.merkle.meta.take_delta();
+        let (blob_payload_pages, blob_payload_plain) = inner.staging.blob.payload.take_delta();
+        let (blob_meta_pages, blob_meta_plain) = inner.staging.blob.meta.take_delta();
+
+        let old_merkle_meta_delta = inner.cached.merkle.meta.update(&merkle_meta_pages).unwrap();
+        let old_merkle_payload_delta = inner.cached.merkle.payload.update(&merkle_payload_pages).unwrap();
+        let old_blob_meta_delta = inner.cached.blob.meta.update(&blob_meta_pages).unwrap();
+        let old_blob_payload_delta = inner.cached.blob.payload.update(&blob_payload_pages).unwrap();
+
+        // update the rolling window of past revisions
+        let new_base = Universe {
+            merkle: SubUniverse::new(
+                StoreRevShared::from_delta(inner.cached.merkle.meta.clone(), old_merkle_meta_delta),
+                StoreRevShared::from_delta(inner.cached.merkle.payload.clone(), old_merkle_payload_delta),
+            ),
+            blob: SubUniverse::new(
+                StoreRevShared::from_delta(inner.cached.blob.meta.clone(), old_blob_meta_delta),
+                StoreRevShared::from_delta(inner.cached.blob.payload.clone(), old_blob_payload_delta),
+            ),
+        };
+
+        if let Some(rev) = inner.revisions.front_mut() {
+            rev.merkle.meta.set_prev(new_base.merkle.meta.inner().clone());
+            rev.merkle.payload.set_prev(new_base.merkle.payload.inner().clone());
+            rev.blob.meta.set_prev(new_base.blob.meta.inner().clone());
+            rev.blob.payload.set_prev(new_base.blob.payload.inner().clone());
+        }
+        inner.revisions.push_front(new_base);
+        while inner.revisions.len() > inner.max_revisions {
+            inner.revisions.pop_back();
+        }
+
+        self.committed = true;
+
+        // schedule writes to the disk
+        inner.disk_requester.write(
+            vec![
+                BufferWrite {
+                    space_id: inner.staging.merkle.payload.id(),
+                    delta: merkle_payload_pages,
+                },
+                BufferWrite {
+                    space_id: inner.staging.merkle.meta.id(),
+                    delta: merkle_meta_pages,
+                },
+                BufferWrite {
+                    space_id: inner.staging.blob.payload.id(),
+                    delta: blob_payload_pages,
+                },
+                BufferWrite {
+                    space_id: inner.staging.blob.meta.id(),
+                    delta: blob_meta_pages,
+                },
+            ],
+            crate::storage::AshRecord(
+                [
+                    (MERKLE_META_SPACE, merkle_meta_plain),
+                    (MERKLE_PAYLOAD_SPACE, merkle_payload_plain),
+                    (BLOB_META_SPACE, blob_meta_plain),
+                    (BLOB_PAYLOAD_SPACE, blob_payload_plain),
+                ]
+                .into(),
+            ),
+        );
+    }
+}
+
+impl<'a> Drop for WriteBatch<'a> {
+    fn drop(&mut self) {
+        if !self.committed {
+            // drop the staging changes
+            self.m.staging.merkle.payload.take_delta();
+            self.m.staging.merkle.meta.take_delta();
+            self.m.staging.blob.payload.take_delta();
+            self.m.staging.blob.meta.take_delta();
+        }
+    }
+}
+
+
\ No newline at end of file diff --git a/docs/src/firewood/file.rs.html b/docs/src/firewood/file.rs.html new file mode 100644 index 000000000000..d422f5e0c1ca --- /dev/null +++ b/docs/src/firewood/file.rs.html @@ -0,0 +1,226 @@ +file.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+
// Copied from CedrusDB
+
+#![allow(dead_code)]
+
+pub(crate) use std::os::unix::io::RawFd as Fd;
+
+use nix::errno::Errno;
+use nix::fcntl::{open, openat, OFlag};
+use nix::sys::stat::Mode;
+use nix::unistd::{close, fsync, mkdir};
+
+pub struct File {
+    fd: Fd,
+    fid: u64,
+}
+
+impl File {
+    pub fn open_file(rootfd: Fd, fname: &str, truncate: bool) -> nix::Result<Fd> {
+        openat(
+            rootfd,
+            fname,
+            (if truncate { OFlag::O_TRUNC } else { OFlag::empty() }) | OFlag::O_RDWR,
+            Mode::S_IRUSR | Mode::S_IWUSR,
+        )
+    }
+
+    pub fn create_file(rootfd: Fd, fname: &str) -> Fd {
+        openat(
+            rootfd,
+            fname,
+            OFlag::O_CREAT | OFlag::O_RDWR,
+            Mode::S_IRUSR | Mode::S_IWUSR,
+        )
+        .unwrap()
+    }
+
+    fn _get_fname(fid: u64) -> String {
+        format!("{fid:08x}.fw")
+    }
+
+    pub fn new(fid: u64, flen: u64, rootfd: Fd) -> nix::Result<Self> {
+        let fname = Self::_get_fname(fid);
+        let fd = match Self::open_file(rootfd, &fname, false) {
+            Ok(fd) => fd,
+            Err(e) => match e {
+                Errno::ENOENT => {
+                    let fd = Self::create_file(rootfd, &fname);
+                    nix::unistd::ftruncate(fd, flen as nix::libc::off_t)?;
+                    fd
+                }
+                e => return Err(e),
+            },
+        };
+        Ok(File { fd, fid })
+    }
+
+    pub fn get_fd(&self) -> Fd {
+        self.fd
+    }
+    pub fn get_fid(&self) -> u64 {
+        self.fid
+    }
+    pub fn get_fname(&self) -> String {
+        Self::_get_fname(self.fid)
+    }
+
+    pub fn sync(&self) {
+        fsync(self.fd).unwrap();
+    }
+}
+
+impl Drop for File {
+    fn drop(&mut self) {
+        close(self.fd).unwrap();
+    }
+}
+
+pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result<Fd, Errno> {
+    use nix::sys::stat::mkdirat;
+    if mkdirat(rootfd, dirname, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR).is_err() {
+        let errno = nix::errno::from_i32(nix::errno::errno());
+        if errno != nix::errno::Errno::EEXIST {
+            return Err(errno)
+        }
+    }
+    openat(rootfd, dirname, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty())
+}
+
+pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> {
+    let mut reset_header = truncate;
+    if truncate {
+        let _ = std::fs::remove_dir_all(path);
+    }
+    match mkdir(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) {
+        Err(e) => {
+            if truncate {
+                return Err(e)
+            }
+        }
+        Ok(_) => {
+            // the DB did not exist
+            reset_header = true
+        }
+    }
+    Ok((
+        match open(path, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) {
+            Ok(fd) => fd,
+            Err(e) => return Err(e),
+        },
+        reset_header,
+    ))
+}
+
+
\ No newline at end of file diff --git a/docs/src/firewood/lib.rs.html b/docs/src/firewood/lib.rs.html new file mode 100644 index 000000000000..a800998f3ad6 --- /dev/null +++ b/docs/src/firewood/lib.rs.html @@ -0,0 +1,408 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+
//! # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval.
+//!
+//! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes
+//! access to latest state, by providing extremely fast reads, but also provides a limited view
+//! into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever
+//! growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk
+//! and apply in-place updates to it. This ensures that the database size is small and stable
+//! during the course of running firewood. Firewood was first conceived to provide a very fast
+//! storage layer for qEVM to enable a fast, complete EVM system with right design choices made
+//! totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible
+//! blockchain storage system, and fits for the general use of a certified key-value store of
+//! arbitrary data.
+//!
+//! Firewood is a robust database implemented from the ground up to directly store MPT nodes and
+//! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a
+//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses
+//! the tree structure as the index on disk. Thus, there is no additional "emulation" of the
+//! logical MPT to flatten out the data structure to feed into the underlying DB that is unaware
+//! of the data being stored.
+//!
+//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees
+//! atomicity and durability in the database, but also offers "reversibility": some portion
+//! of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover
+//! some past versions of the entire store back in memory. While running the store, new changes
+//! will also contribute to the configured window of changes (at batch granularity) to access any past
+//! versions with no additional cost at all.
+//!
+//! The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage
+//! space which can be both or selectively used the user. The account model portion of the storage
+//! offers something very similar to `StateDB` in geth, which captures the address-"state key"
+//! style of two-level access for an account's (smart contract's) state. Therefore, it takes
+//! minimal effort to delegate all state storage from an EVM implementation to firewood. The other
+//! portion of the storage supports generic MPT storage for arbitrary keys and values. When unused,
+//! there is no additional cost.
+//!
+//! # Design Philosophy & Overview
+//!
+//! With some on-going academic research efforts and increasing demand of faster local storage
+//! solutions for the chain state, we realized there are mainly two different regimes of designs.
+//!
+//! - "Archival" Storage: this style of design emphasizes on the ability to hold all historical
+//!   data and retrieve a revision of any wold state at a reasonable performance. To economically
+//!   store all historical certified data, usually copy-on-write merkle tries are used to just
+//!   capture the changes made by a committed block. The entire storage consists of a forest of these
+//!   "delta" tries. The total size of the storage will keep growing over the chain length and an ideal,
+//!   well-executed plan for this is to make sure the performance degradation is reasonable or
+//!   well-contained with respect to the ever-increasing size of the index. This design is useful
+//!   for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a
+//!   query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also
+//!   need this because the "canonical" branch of the chain could switch (but not necessarily a
+//!   practical concern nowadays) to a different fork at times.
+//!
+//! - "Validation" Storage: this regime optimizes for the storage footprint and the performance of
+//!   operations upon the latest/recent states. With the assumption that the chain's total state
+//!   size is relatively stable over ever-coming blocks, one can just make the latest state
+//!   persisted and available to the blockchain system as that's what matters for most of the time.
+//!   While one can still keep some volatile state versions in memory for mutation and VM
+//!   execution, the final commit to some state works on a singleton so the indexed merkle tries
+//!   may be typically updated in place. It is also possible (e.g., firewood) to allow some
+//!   infrequent access to historical versions with higher cost, and/or allow fast access to
+//!   versions of the store within certain limited recency. This style of storage is useful for
+//!   the blockchain systems where only (or mostly) the latest state is required and data footprint
+//!   should remain constant or grow slowly if possible for sustainability. Validators who
+//!   directly participate in the consensus and vote for the blocks, for example, can largely
+//!   benefit from such a design.
+//!
+//! In firewood, we take a closer look at the second regime and have come up with a simple but
+//! robust architecture that fulfills the need for such blockchain storage.
+//!
+//! ## Storage Model
+//!
+//! Firewood is built by three layers of abstractions that totally decouple the
+//! layout/representation of the data on disk from the actual logical data structure it retains:
+//!
+//! - Linear, memory-like space: the [shale](https://crates.io/crates/shale) crate from an academic
+//!   project (CedrusDB) code offers a `MemStore` abstraction for a (64-bit) byte-addressable space
+//!   that abstracts away the intricate method that actually persists the in-memory data on the
+//!   secondary storage medium (e.g., hard drive). The implementor of `MemStore` will provide the
+//!   functions to give the user of `MemStore` an illusion that the user is operating upon a
+//!   byte-addressable memory space. It is just a "magical" array of bytes one can view and change
+//!   that is mirrored to the disk. In reality, the linear space will be chunked into files under a
+//!   directory, but the user does not have to even know about this.
+//!
+//! - Persistent item storage stash: `ShaleStore` trait from `shale` defines a pool of typed
+//!   objects that are persisted on disk but also made accessible in memory transparently. It is
+//!   built on top of `MemStore` by defining how "items" of the given type are laid out, allocated
+//!   and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of
+//!   basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`).
+//!
+//! - Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking
+//!   `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`).
+//!   The data structure code is totally unaware of how its objects (i.e., nodes) are organized or
+//!   persisted on disk. It is as if they're just in memory, which makes it much easier to write
+//!   and maintain the code.
+//!
+//! The three layers are depicted as follows:
+//!
+//! <p align="center">
+//!     <img src="https://drive.google.com/uc?export=view&id=1KnlpqnxkmFd_aKZHwcferIdX137GVZJr" width="80%">
+//! </p>
+//!
+//! Given the abstraction, one can easily realize the fact that the actual data that affect the
+//! state of the data structure (MPT) is what the linear space (`MemStore`) keeps track of, that is,
+//! a flat but conceptually large byte vector. In other words, given a valid byte vector as the
+//! content of the linear space, the higher level data structure can be *uniquely* determined, there
+//! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching)
+//! or less than that, like a way to interpret the bytes. This nice property allows us to completely
+//! separate the logical data from its physical representation, greatly simplifies the storage
+//! management, and allows reusing the code. It is still a very versatile abstraction, as in theory
+//! any persistent data could be stored this way -- sometimes you need to swap in a different
+//! `MemShale` or `MemStore` implementation, but without having to touch the code for the persisted
+//! data structure.
+//!
+//! ## Page-based Shadowing and Revisions
+//!
+//! Following the idea that the MPTs are just a view of a linear byte space, all writes made to the
+//! MPTs inside Firewood will eventually be consolidated into some interval writes to the linear
+//! space. The writes may overlap and some frequent writes are even done to the same spot in the
+//! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit
+//! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the
+//! dirty pages in some `MemStore` instantiation (see `storage::StoreRevMut`). When a
+//! [`db::WriteBatch`] commits, both the recorded interval writes and the aggregated in-memory
+//! dirty pages induced by this write batch are taken out from the linear space. Although they are
+//! mathematically equivalent, interval writes are more compact than pages (which are 4K in size,
+//! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL
+//! subsystem (supported by [growthring](https://crates.io/crates/growth-ring)). After the
+//! WAL record is written (one record per write batch), the dirty pages are then pushed to the
+//! on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See
+//! the `BufferCmd::WriteBatch` part of `DiskBuffer::process` for the detailed logic.
+//!
+//! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood:
+//!
+//! - Traverse the MPT, and that induces the access to some nodes. Suppose the nodes are not already in
+//!   memory, then:
+//!
+//! - Bring the necessary pages that contain the accessed nodes into the memory and cache them
+//!   (`storage::CachedSpace`).
+//!
+//! - Make changes to the MPT, and that induces the writes to some nodes. The nodes are either
+//!   already cached in memory (its pages are cached, or its handle `ObjRef<Node>` is still in
+//!   `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the
+//!   second step for it).
+//!
+//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` space that
+//!   overlays atop `CachedSpace`, so all dirty pages during the current write batch will be
+//!   exactly captured in `StoreRevMut` (see `StoreRevMut::take_delta`).
+//!
+//! - Finally:
+//!
+//!   - Abort: when the write batch is dropped without invoking `db::WriteBatch::commit`, all in-memory
+//!     changes will be discarded, the dirty pages from `StoreRevMut` will be dropped and the merkle
+//!     will "revert" back to its original state without actually having to rollback anything.
+//!
+//!   - Commit: otherwise, the write batch is committed, the interval writes (`storage::Ash`) will be bundled
+//!     into a single WAL record (`storage::AshRecord`) and sent to WAL subsystem, before dirty pages
+//!     are scheduled to be written to the space files. Also the dirty pages are applied to the
+//!     underlying `CachedSpace`. `StoreRevMut` becomes empty again for further write batches.
+//!
+//! Parts of the following diagram show this normal flow, the "staging" space (implemented by
+//! `StoreRevMut`) concept is a bit similar to the staging area in Git, which enables the handling
+//! of (resuming from) write errors, clean abortion of an on-going write batch so the entire store
+//! state remains intact, and also reduces unnecessary premature disk writes. Essentially, we
+//! copy-on-write pages in the space that are touched upon, without directly mutating the
+//! underlying "master" space. The staging space is just a collection of these "shadowing" pages
+//! and a reference to the its base (master) so any reads could partially hit those dirty pages
+//! and/or fall through to the base, whereas all writes are captured. Finally, when things go well,
+//! we "push down" these changes to the base and clear up the staging space.
+//!
+//! <p align="center">
+//!     <img src="https://drive.google.com/uc?export=view&id=1l2CUbq85nX_g0GfQj44ClrKXd253sBFv" width="100%">
+//! </p>
+//!
+//! Thanks to the shadow pages, we can both revive some historical versions of the store and
+//! maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram
+//! shows previously logged write batch records could be kept even though they are no longer needed
+//! for the purpose of crash recovery. The interval writes from a record can be aggregated into
+//! pages (see `storage::StoreDelta::new`) and used to reconstruct a "ghost" image of past
+//! revision of the linear space (just like how staging space works, except that the ghost space is
+//! essentially read-only once constructed). The shadow pages there will function as some
+//! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the
+//! linear space is very likely untouched by that historical write batch.
+//!
+//! Then, with the three-layer abstraction we previously talked about, an historical MPT could be
+//! derived. In fact, because there is no mandatory traversal or scanning in the process, the
+//! only cost to revive a historical state from the log is to just playback the records and create
+//! those shadow pages. There is very little additional cost because the ghost space is summoned on an
+//! on-demand manner while one accesses the historical MPT.
+//!
+//! In the other direction, when new write batches are committed, the system moves forward, we can
+//! therefore maintain a rolling window of past revisions in memory with *zero* cost. The
+//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one
+//! step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be
+//! created to hold the version of the store before the commit. The backward delta is applied to
+//! counteract the change that has been made to the persisted store, which is also a set of shadow pages.
+//! No change is required for other historical ghost space instances. Finally, we can phase out
+//! some very old ghost space to keep the size of the rolling window invariant.
+//!
+pub(crate) mod account;
+pub mod db;
+pub(crate) mod file;
+pub mod merkle;
+pub mod proof;
+pub(crate) mod storage;
+
+
\ No newline at end of file diff --git a/docs/src/firewood/merkle.rs.html b/docs/src/firewood/merkle.rs.html new file mode 100644 index 000000000000..28720361621d --- /dev/null +++ b/docs/src/firewood/merkle.rs.html @@ -0,0 +1,3424 @@ +merkle.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+
use crate::proof::Proof;
+
+use enum_as_inner::EnumAsInner;
+use once_cell::unsync::OnceCell;
+use sha3::Digest;
+use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore};
+
+use std::cell::Cell;
+use std::collections::HashMap;
+use std::fmt::{self, Debug};
+use std::io::{Cursor, Read, Write};
+
+const NBRANCH: usize = 16;
+
+#[derive(Debug)]
+pub enum MerkleError {
+    Shale(ShaleError),
+    ReadOnly,
+    NotBranchNode,
+    Format(std::io::Error),
+}
+
+#[derive(PartialEq, Eq, Clone)]
+pub struct Hash(pub [u8; 32]);
+
+impl Hash {
+    const MSIZE: u64 = 32;
+}
+
+impl std::ops::Deref for Hash {
+    type Target = [u8; 32];
+    fn deref(&self) -> &[u8; 32] {
+        &self.0
+    }
+}
+
+impl MummyItem for Hash {
+    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
+        let raw = mem.get_view(addr, Self::MSIZE).ok_or(ShaleError::LinearMemStoreError)?;
+        Ok(Self(raw[..Self::MSIZE as usize].try_into().unwrap()))
+    }
+
+    fn dehydrated_len(&self) -> u64 {
+        Self::MSIZE
+    }
+
+    fn dehydrate(&self, to: &mut [u8]) {
+        Cursor::new(to).write_all(&self.0).unwrap()
+    }
+}
+
+/// PartialPath keeps a list of nibbles to represent a path on the MPT.
+#[derive(PartialEq, Eq, Clone)]
+pub struct PartialPath(Vec<u8>);
+
+impl Debug for PartialPath {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        for nib in self.0.iter() {
+            write!(f, "{:x}", *nib & 0xf)?;
+        }
+        Ok(())
+    }
+}
+
+impl std::ops::Deref for PartialPath {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl PartialPath {
+    pub fn into_inner(self) -> Vec<u8> {
+        self.0
+    }
+
+    fn encode(&self, term: bool) -> Vec<u8> {
+        let odd_len = (self.0.len() & 1) as u8;
+        let flags = if term { 2 } else { 0 } + odd_len;
+        let mut res = if odd_len == 1 { vec![flags] } else { vec![flags, 0x0] };
+        res.extend(&self.0);
+        res
+    }
+
+    pub fn decode<R: AsRef<[u8]>>(raw: R) -> (Self, bool) {
+        let raw = raw.as_ref();
+        let term = raw[0] > 1;
+        let odd_len = raw[0] & 1;
+        (
+            Self(if odd_len == 1 {
+                raw[1..].to_vec()
+            } else {
+                raw[2..].to_vec()
+            }),
+            term,
+        )
+    }
+
+    fn dehydrated_len(&self) -> u64 {
+        let len = self.0.len() as u64;
+        if len & 1 == 1 {
+            (len + 1) >> 1
+        } else {
+            (len >> 1) + 1
+        }
+    }
+}
+
+#[test]
+fn test_partial_path_encoding() {
+    let check = |steps: &[u8], term| {
+        let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term));
+        assert_eq!(d.0, steps);
+        assert_eq!(t, term);
+    };
+    for steps in [
+        vec![0x1, 0x2, 0x3, 0x4],
+        vec![0x1, 0x2, 0x3],
+        vec![0x0, 0x1, 0x2],
+        vec![0x1, 0x2],
+        vec![0x1],
+    ] {
+        for term in [true, false] {
+            check(&steps, term)
+        }
+    }
+}
+
+#[derive(PartialEq, Eq, Clone)]
+struct Data(Vec<u8>);
+
+impl std::ops::Deref for Data {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+#[derive(PartialEq, Eq, Clone)]
+struct BranchNode {
+    chd: [Option<ObjPtr<Node>>; NBRANCH],
+    value: Option<Data>,
+}
+
+impl Debug for BranchNode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(f, "[Branch")?;
+        for (i, c) in self.chd.iter().enumerate() {
+            if let Some(c) = c {
+                write!(f, " ({i:x} {c})")?;
+            }
+        }
+        write!(
+            f,
+            " v={}]",
+            match &self.value {
+                Some(v) => hex::encode(&**v),
+                None => "nil".to_string(),
+            }
+        )
+    }
+}
+
+impl BranchNode {
+    fn single_child(&self) -> (Option<(ObjPtr<Node>, u8)>, bool) {
+        let mut has_chd = false;
+        let mut only_chd = None;
+        for (i, c) in self.chd.iter().enumerate() {
+            if c.is_some() {
+                has_chd = true;
+                if only_chd.is_some() {
+                    only_chd = None;
+                    break
+                }
+                only_chd = (*c).map(|e| (e, i as u8))
+            }
+        }
+        (only_chd, has_chd)
+    }
+
+    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
+        let mut stream = rlp::RlpStream::new_list(17);
+        for c in self.chd.iter() {
+            match c {
+                Some(c) => {
+                    let mut c_ref = store.get_item(*c).unwrap();
+                    if c_ref.get_eth_rlp_long::<T>(store) {
+                        let s = stream.append(&&(*c_ref.get_root_hash::<T>(store))[..]);
+                        if c_ref.lazy_dirty.get() {
+                            c_ref.write(|_| {}).unwrap();
+                            c_ref.lazy_dirty.set(false)
+                        }
+                        s
+                    } else {
+                        let c_rlp = &c_ref.get_eth_rlp::<T>(store);
+                        stream.append_raw(c_rlp, 1)
+                    }
+                }
+                None => stream.append_empty_data(),
+            };
+        }
+        match &self.value {
+            Some(val) => stream.append(&val.to_vec()),
+            None => stream.append_empty_data(),
+        };
+        stream.out().into()
+    }
+}
+
+#[derive(PartialEq, Eq, Clone)]
+struct LeafNode(PartialPath, Data);
+
+impl Debug for LeafNode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1))
+    }
+}
+
+impl LeafNode {
+    fn calc_eth_rlp<T: ValueTransformer>(&self) -> Vec<u8> {
+        rlp::encode_list::<Vec<u8>, _>(&[from_nibbles(&self.0.encode(true)).collect(), T::transform(&self.1)]).into()
+    }
+}
+
+#[derive(PartialEq, Eq, Clone)]
+struct ExtNode(PartialPath, ObjPtr<Node>);
+
+impl Debug for ExtNode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(f, "[Extension {:?} {}]", self.0, self.1)
+    }
+}
+
+impl ExtNode {
+    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
+        let mut r = store.get_item(self.1).unwrap();
+        let mut stream = rlp::RlpStream::new_list(2);
+        stream.append(&from_nibbles(&self.0.encode(false)).collect::<Vec<_>>());
+        if r.get_eth_rlp_long::<T>(store) {
+            stream.append(&&(*r.get_root_hash::<T>(store))[..]);
+            if r.lazy_dirty.get() {
+                r.write(|_| {}).unwrap();
+                r.lazy_dirty.set(false)
+            }
+        } else {
+            stream.append_raw(r.get_eth_rlp::<T>(store), 1);
+        }
+        stream.out().into()
+    }
+}
+
+#[derive(PartialEq, Eq, Clone)]
+pub struct Node {
+    root_hash: OnceCell<Hash>,
+    eth_rlp_long: OnceCell<bool>,
+    eth_rlp: OnceCell<Vec<u8>>,
+    lazy_dirty: Cell<bool>,
+    inner: NodeType,
+}
+
+#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)]
+enum NodeType {
+    Branch(BranchNode),
+    Leaf(LeafNode),
+    Extension(ExtNode),
+}
+
+impl NodeType {
+    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
+        match &self {
+            NodeType::Leaf(n) => n.calc_eth_rlp::<T>(),
+            NodeType::Extension(n) => n.calc_eth_rlp::<T>(store),
+            NodeType::Branch(n) => n.calc_eth_rlp::<T>(store),
+        }
+    }
+}
+
+impl Node {
+    const BRANCH_NODE: u8 = 0x0;
+    const EXT_NODE: u8 = 0x1;
+    const LEAF_NODE: u8 = 0x2;
+
+    fn max_branch_node_size() -> u64 {
+        let max_size: OnceCell<u64> = OnceCell::new();
+        *max_size.get_or_init(|| {
+            Self {
+                root_hash: OnceCell::new(),
+                eth_rlp_long: OnceCell::new(),
+                eth_rlp: OnceCell::new(),
+                inner: NodeType::Branch(BranchNode {
+                    chd: [Some(ObjPtr::null()); NBRANCH],
+                    value: Some(Data(Vec::new())),
+                }),
+                lazy_dirty: Cell::new(false),
+            }
+            .dehydrated_len()
+        })
+    }
+
+    fn get_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> &[u8] {
+        self.eth_rlp.get_or_init(|| self.inner.calc_eth_rlp::<T>(store))
+    }
+
+    fn get_root_hash<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> &Hash {
+        self.root_hash.get_or_init(|| {
+            self.lazy_dirty.set(true);
+            Hash(sha3::Keccak256::digest(self.get_eth_rlp::<T>(store)).into())
+        })
+    }
+
+    fn get_eth_rlp_long<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> bool {
+        *self.eth_rlp_long.get_or_init(|| {
+            self.lazy_dirty.set(true);
+            self.get_eth_rlp::<T>(store).len() >= 32
+        })
+    }
+
+    fn rehash(&mut self) {
+        self.eth_rlp = OnceCell::new();
+        self.eth_rlp_long = OnceCell::new();
+        self.root_hash = OnceCell::new();
+    }
+
+    fn new(inner: NodeType) -> Self {
+        let mut s = Self {
+            root_hash: OnceCell::new(),
+            eth_rlp_long: OnceCell::new(),
+            eth_rlp: OnceCell::new(),
+            inner,
+            lazy_dirty: Cell::new(false),
+        };
+        s.rehash();
+        s
+    }
+
+    fn new_from_hash(root_hash: Option<Hash>, eth_rlp_long: Option<bool>, inner: NodeType) -> Self {
+        Self {
+            root_hash: match root_hash {
+                Some(h) => OnceCell::with_value(h),
+                None => OnceCell::new(),
+            },
+            eth_rlp_long: match eth_rlp_long {
+                Some(b) => OnceCell::with_value(b),
+                None => OnceCell::new(),
+            },
+            eth_rlp: OnceCell::new(),
+            inner,
+            lazy_dirty: Cell::new(false),
+        }
+    }
+
+    const ROOT_HASH_VALID_BIT: u8 = 1 << 0;
+    const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1;
+    const ETH_RLP_LONG_BIT: u8 = 1 << 2;
+}
+
+impl MummyItem for Node {
+    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
+        let dec_err = |_| ShaleError::DecodeError;
+        const META_SIZE: u64 = 32 + 1 + 1;
+        let meta_raw = mem.get_view(addr, META_SIZE).ok_or(ShaleError::LinearMemStoreError)?;
+        let attrs = meta_raw[32];
+        let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 {
+            None
+        } else {
+            Some(Hash(meta_raw[0..32].try_into().map_err(dec_err)?))
+        };
+        let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 {
+            None
+        } else {
+            Some(attrs & Node::ETH_RLP_LONG_BIT != 0)
+        };
+        match meta_raw[33] {
+            Self::BRANCH_NODE => {
+                let branch_header_size = NBRANCH as u64 * 8 + 4;
+                let node_raw = mem
+                    .get_view(addr + META_SIZE, branch_header_size)
+                    .ok_or(ShaleError::LinearMemStoreError)?;
+                let mut cur = Cursor::new(node_raw.deref());
+                let mut chd = [None; NBRANCH];
+                let mut buff = [0; 8];
+                for chd in chd.iter_mut() {
+                    cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
+                    let addr = u64::from_le_bytes(buff);
+                    if addr != 0 {
+                        *chd = Some(unsafe { ObjPtr::new_from_addr(addr) })
+                    }
+                }
+                cur.read_exact(&mut buff[..4]).map_err(|_| ShaleError::DecodeError)?;
+                let raw_len = u32::from_le_bytes(buff[..4].try_into().map_err(dec_err)?) as u64;
+                let value = if raw_len == u32::MAX as u64 {
+                    None
+                } else {
+                    Some(Data(
+                        mem.get_view(addr + META_SIZE + branch_header_size, raw_len)
+                            .ok_or(ShaleError::LinearMemStoreError)?
+                            .to_vec(),
+                    ))
+                };
+                Ok(Self::new_from_hash(
+                    root_hash,
+                    eth_rlp_long,
+                    NodeType::Branch(BranchNode { chd, value }),
+                ))
+            }
+            Self::EXT_NODE => {
+                let ext_header_size = 1 + 8;
+                let node_raw = mem
+                    .get_view(addr + META_SIZE, ext_header_size)
+                    .ok_or(ShaleError::LinearMemStoreError)?;
+                let mut cur = Cursor::new(node_raw.deref());
+                let mut buff = [0; 8];
+                cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?;
+                let len = buff[0] as u64;
+                cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
+                let ptr = u64::from_le_bytes(buff);
+                let nibbles: Vec<_> = to_nibbles(
+                    &mem.get_view(addr + META_SIZE + ext_header_size, len)
+                        .ok_or(ShaleError::LinearMemStoreError)?,
+                )
+                .collect();
+                let (path, _) = PartialPath::decode(nibbles);
+                Ok(Self::new_from_hash(
+                    root_hash,
+                    eth_rlp_long,
+                    NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) })),
+                ))
+            }
+            Self::LEAF_NODE => {
+                let leaf_header_size = 1 + 4;
+                let node_raw = mem
+                    .get_view(addr + META_SIZE, leaf_header_size)
+                    .ok_or(ShaleError::LinearMemStoreError)?;
+                let mut cur = Cursor::new(node_raw.deref());
+                let mut buff = [0; 4];
+                cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?;
+                let path_len = buff[0] as u64;
+                cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
+                let data_len = u32::from_le_bytes(buff) as u64;
+                let remainder = mem
+                    .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len)
+                    .ok_or(ShaleError::LinearMemStoreError)?;
+                let nibbles: Vec<_> = to_nibbles(&remainder[..path_len as usize]).collect();
+                let (path, _) = PartialPath::decode(nibbles);
+                let value = Data(remainder[path_len as usize..].to_vec());
+                Ok(Self::new_from_hash(
+                    root_hash,
+                    eth_rlp_long,
+                    NodeType::Leaf(LeafNode(path, value)),
+                ))
+            }
+            _ => Err(ShaleError::DecodeError),
+        }
+    }
+
+    fn dehydrated_len(&self) -> u64 {
+        32 + 1 +
+            1 +
+            match &self.inner {
+                NodeType::Branch(n) => {
+                    NBRANCH as u64 * 8 +
+                        4 +
+                        match &n.value {
+                            Some(val) => val.len() as u64,
+                            None => 0,
+                        }
+                }
+                NodeType::Extension(n) => 1 + 8 + n.0.dehydrated_len(),
+                NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64,
+            }
+    }
+
+    fn dehydrate(&self, to: &mut [u8]) {
+        let mut cur = Cursor::new(to);
+
+        let mut attrs = 0;
+        attrs |= match self.root_hash.get() {
+            Some(h) => {
+                cur.write_all(&h.0).unwrap();
+                Node::ROOT_HASH_VALID_BIT
+            }
+            None => {
+                cur.write_all(&[0; 32]).unwrap();
+                0
+            }
+        };
+        attrs |= match self.eth_rlp_long.get() {
+            Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT),
+            None => 0,
+        };
+        cur.write_all(&[attrs]).unwrap();
+
+        match &self.inner {
+            NodeType::Branch(n) => {
+                cur.write_all(&[Self::BRANCH_NODE]).unwrap();
+                for c in n.chd.iter() {
+                    cur.write_all(&match c {
+                        Some(p) => p.addr().to_le_bytes(),
+                        None => 0u64.to_le_bytes(),
+                    })
+                    .unwrap();
+                }
+                match &n.value {
+                    Some(val) => {
+                        cur.write_all(&(val.len() as u32).to_le_bytes()).unwrap();
+                        cur.write_all(val).unwrap();
+                    }
+                    None => {
+                        cur.write_all(&u32::MAX.to_le_bytes()).unwrap();
+                    }
+                }
+            }
+            NodeType::Extension(n) => {
+                cur.write_all(&[Self::EXT_NODE]).unwrap();
+                let path: Vec<u8> = from_nibbles(&n.0.encode(false)).collect();
+                cur.write_all(&[path.len() as u8]).unwrap();
+                cur.write_all(&n.1.addr().to_le_bytes()).unwrap();
+                cur.write_all(&path).unwrap();
+            }
+            NodeType::Leaf(n) => {
+                cur.write_all(&[Self::LEAF_NODE]).unwrap();
+                let path: Vec<u8> = from_nibbles(&n.0.encode(true)).collect();
+                cur.write_all(&[path.len() as u8]).unwrap();
+                cur.write_all(&(n.1.len() as u32).to_le_bytes()).unwrap();
+                cur.write_all(&path).unwrap();
+                cur.write_all(&n.1).unwrap();
+            }
+        }
+    }
+}
+
+#[test]
+fn test_merkle_node_encoding() {
+    let check = |node: Node| {
+        let mut bytes = Vec::new();
+        bytes.resize(node.dehydrated_len() as usize, 0);
+        node.dehydrate(&mut bytes);
+
+        let mem = shale::PlainMem::new(bytes.len() as u64, 0x0);
+        mem.write(0, &bytes);
+        println!("{bytes:?}");
+        let node_ = Node::hydrate(0, &mem).unwrap();
+        assert!(node == node_);
+    };
+    let chd0 = [None; NBRANCH];
+    let mut chd1 = chd0;
+    for node in chd1.iter_mut().take(NBRANCH / 2) {
+        *node = Some(unsafe { ObjPtr::new_from_addr(0xa) });
+    }
+    for node in [
+        Node::new_from_hash(
+            None,
+            None,
+            NodeType::Leaf(LeafNode(PartialPath(vec![0x1, 0x2, 0x3]), Data(vec![0x4, 0x5]))),
+        ),
+        Node::new_from_hash(
+            None,
+            None,
+            NodeType::Extension(ExtNode(PartialPath(vec![0x1, 0x2, 0x3]), unsafe {
+                ObjPtr::new_from_addr(0x42)
+            })),
+        ),
+        Node::new_from_hash(
+            None,
+            None,
+            NodeType::Branch(BranchNode {
+                chd: chd0,
+                value: Some(Data("hello, world!".as_bytes().to_vec())),
+            }),
+        ),
+        Node::new_from_hash(None, None, NodeType::Branch(BranchNode { chd: chd1, value: None })),
+    ] {
+        check(node);
+    }
+}
+
+macro_rules! write_node {
+    ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => {
+        if let None = $r.write($modify) {
+            let ptr = $self.new_node($r.clone())?.as_ptr();
+            $self.set_parent(ptr, $parents);
+            $deleted.push($r.as_ptr());
+            true
+        } else {
+            false
+        }
+    };
+}
+
+pub struct Merkle {
+    store: Box<dyn ShaleStore<Node>>,
+}
+
+impl Merkle {
+    fn get_node(&self, ptr: ObjPtr<Node>) -> Result<ObjRef<Node>, MerkleError> {
+        self.store.get_item(ptr).map_err(MerkleError::Shale)
+    }
+    fn new_node(&self, item: Node) -> Result<ObjRef<Node>, MerkleError> {
+        self.store.put_item(item, 0).map_err(MerkleError::Shale)
+    }
+    fn free_node(&mut self, ptr: ObjPtr<Node>) -> Result<(), MerkleError> {
+        self.store.free_item(ptr).map_err(MerkleError::Shale)
+    }
+}
+
+impl Merkle {
+    pub fn new(store: Box<dyn ShaleStore<Node>>) -> Self {
+        Self { store }
+    }
+
+    pub fn init_root(root: &mut ObjPtr<Node>, store: &dyn ShaleStore<Node>) -> Result<(), MerkleError> {
+        *root = store
+            .put_item(
+                Node::new(NodeType::Branch(BranchNode {
+                    chd: [None; NBRANCH],
+                    value: None,
+                })),
+                Node::max_branch_node_size(),
+            )
+            .map_err(MerkleError::Shale)?
+            .as_ptr();
+        Ok(())
+    }
+
+    pub fn get_store(&self) -> &dyn ShaleStore<Node> {
+        self.store.as_ref()
+    }
+
+    pub fn empty_root() -> &'static Hash {
+        use once_cell::sync::OnceCell;
+        static V: OnceCell<Hash> = OnceCell::new();
+        V.get_or_init(|| {
+            Hash(
+                hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+                    .unwrap()
+                    .try_into()
+                    .unwrap(),
+            )
+        })
+    }
+
+    pub fn root_hash<T: ValueTransformer>(&self, root: ObjPtr<Node>) -> Result<Hash, MerkleError> {
+        let root = self
+            .get_node(root)?
+            .inner
+            .as_branch()
+            .ok_or(MerkleError::NotBranchNode)?
+            .chd[0];
+        Ok(if let Some(root) = root {
+            let mut node = self.get_node(root)?;
+            let res = node.get_root_hash::<T>(self.store.as_ref()).clone();
+            if node.lazy_dirty.get() {
+                node.write(|_| {}).unwrap();
+                node.lazy_dirty.set(false)
+            }
+            res
+        } else {
+            Self::empty_root().clone()
+        })
+    }
+
+    fn dump_(&self, u: ObjPtr<Node>, w: &mut dyn Write) -> Result<(), MerkleError> {
+        let u_ref = self.get_node(u)?;
+        write!(
+            w,
+            "{} => {}: ",
+            u,
+            match u_ref.root_hash.get() {
+                Some(h) => hex::encode(**h),
+                None => "<lazy>".to_string(),
+            }
+        )
+        .map_err(MerkleError::Format)?;
+        match &u_ref.inner {
+            NodeType::Branch(n) => {
+                writeln!(w, "{n:?}").map_err(MerkleError::Format)?;
+                for c in n.chd.iter().flatten() {
+                    self.dump_(*c, w)?
+                }
+            }
+            NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(),
+            NodeType::Extension(n) => {
+                writeln!(w, "{n:?}").map_err(MerkleError::Format)?;
+                self.dump_(n.1, w)?
+            }
+        }
+        Ok(())
+    }
+
+    pub fn dump(&self, root: ObjPtr<Node>, w: &mut dyn Write) -> Result<(), MerkleError> {
+        if root.is_null() {
+            write!(w, "<Empty>").map_err(MerkleError::Format)?;
+        } else {
+            self.dump_(root, w)?;
+        };
+        Ok(())
+    }
+
+    fn set_parent<'b>(&self, new_chd: ObjPtr<Node>, parents: &mut [(ObjRef<'b, Node>, u8)]) {
+        let (p_ref, idx) = parents.last_mut().unwrap();
+        p_ref
+            .write(|p| {
+                match &mut p.inner {
+                    NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd),
+                    NodeType::Extension(pp) => pp.1 = new_chd,
+                    _ => unreachable!(),
+                }
+                p.rehash();
+            })
+            .unwrap();
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    fn split<'b>(
+        &self, mut u_ref: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], rem_path: &[u8], n_path: Vec<u8>,
+        n_value: Option<Data>, val: Vec<u8>, deleted: &mut Vec<ObjPtr<Node>>,
+    ) -> Result<Option<Vec<u8>>, MerkleError> {
+        let u_ptr = u_ref.as_ptr();
+        let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) {
+            Some(idx) => {
+                //                                                      _ [u (new path)]
+                //                                                     /
+                //  [parent] (-> [ExtNode (common prefix)]) -> [branch]*
+                //                                                     \_ [leaf (with val)]
+                u_ref
+                    .write(|u| {
+                        (*match &mut u.inner {
+                            NodeType::Leaf(u) => &mut u.0,
+                            NodeType::Extension(u) => &mut u.0,
+                            _ => unreachable!(),
+                        }) = PartialPath(n_path[idx + 1..].to_vec());
+                        u.rehash();
+                    })
+                    .unwrap();
+                let leaf_ptr = self
+                    .new_node(Node::new(NodeType::Leaf(LeafNode(
+                        PartialPath(rem_path[idx + 1..].to_vec()),
+                        Data(val),
+                    ))))?
+                    .as_ptr();
+                let mut chd = [None; NBRANCH];
+                chd[rem_path[idx] as usize] = Some(leaf_ptr);
+                chd[n_path[idx] as usize] = Some(match &u_ref.inner {
+                    NodeType::Extension(u) => {
+                        if u.0.len() == 0 {
+                            deleted.push(u_ptr);
+                            u.1
+                        } else {
+                            u_ptr
+                        }
+                    }
+                    _ => u_ptr,
+                });
+                drop(u_ref);
+                let t = NodeType::Branch(BranchNode { chd, value: None });
+                let branch_ptr = self.new_node(Node::new(t))?.as_ptr();
+                if idx > 0 {
+                    self.new_node(Node::new(NodeType::Extension(ExtNode(
+                        PartialPath(rem_path[..idx].to_vec()),
+                        branch_ptr,
+                    ))))?
+                    .as_ptr()
+                } else {
+                    branch_ptr
+                }
+            }
+            None => {
+                if rem_path.len() == n_path.len() {
+                    let mut err = None;
+                    write_node!(
+                        self,
+                        u_ref,
+                        |u| {
+                            #[allow(clippy::blocks_in_if_conditions)]
+                            match &mut u.inner {
+                                NodeType::Leaf(u) => u.1 = Data(val),
+                                NodeType::Extension(u) => {
+                                    if let Err(e) = (|| {
+                                        let mut b_ref = self.get_node(u.1)?;
+                                        if b_ref
+                                            .write(|b| {
+                                                b.inner.as_branch_mut().unwrap().value = Some(Data(val));
+                                                b.rehash()
+                                            })
+                                            .is_none()
+                                        {
+                                            u.1 = self.new_node(b_ref.clone())?.as_ptr();
+                                            deleted.push(b_ref.as_ptr());
+                                        }
+                                        Ok(())
+                                    })() {
+                                        err = Some(Err(e))
+                                    }
+                                }
+                                _ => unreachable!(),
+                            }
+                            u.rehash();
+                        },
+                        parents,
+                        deleted
+                    );
+                    return err.unwrap_or_else(|| Ok(None))
+                }
+                let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() {
+                    // key path is a prefix of the path to u
+                    u_ref
+                        .write(|u| {
+                            (*match &mut u.inner {
+                                NodeType::Leaf(u) => &mut u.0,
+                                NodeType::Extension(u) => &mut u.0,
+                                _ => unreachable!(),
+                            }) = PartialPath(n_path[rem_path.len() + 1..].to_vec());
+                            u.rehash();
+                        })
+                        .unwrap();
+                    (
+                        match &u_ref.inner {
+                            NodeType::Extension(u) => {
+                                if u.0.len() == 0 {
+                                    deleted.push(u_ptr);
+                                    u.1
+                                } else {
+                                    u_ptr
+                                }
+                            }
+                            _ => u_ptr,
+                        },
+                        rem_path,
+                        n_path[rem_path.len()],
+                        Some(Data(val)),
+                    )
+                } else {
+                    // key path extends the path to u
+                    if n_value.is_none() {
+                        // this case does not apply to an extension node, resume the tree walk
+                        return Ok(Some(val))
+                    }
+                    let leaf = self.new_node(Node::new(NodeType::Leaf(LeafNode(
+                        PartialPath(rem_path[n_path.len() + 1..].to_vec()),
+                        Data(val),
+                    ))))?;
+                    deleted.push(u_ptr);
+                    (leaf.as_ptr(), &n_path[..], rem_path[n_path.len()], n_value)
+                };
+                drop(u_ref);
+                // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf]
+                let mut chd = [None; NBRANCH];
+                chd[idx as usize] = Some(leaf_ptr);
+                let branch_ptr = self
+                    .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v })))?
+                    .as_ptr();
+                if !prefix.is_empty() {
+                    self.new_node(Node::new(NodeType::Extension(ExtNode(
+                        PartialPath(prefix.to_vec()),
+                        branch_ptr,
+                    ))))?
+                    .as_ptr()
+                } else {
+                    branch_ptr
+                }
+            }
+        };
+        // observation:
+        // - leaf/extension node can only be the child of a branch node
+        // - branch node can only be the child of a branch/extension node
+        self.set_parent(new_chd, parents);
+        Ok(None)
+    }
+
+    pub fn insert<K: AsRef<[u8]>>(&mut self, key: K, val: Vec<u8>, root: ObjPtr<Node>) -> Result<(), MerkleError> {
+        let mut deleted = Vec::new();
+        let mut chunks = vec![0];
+        chunks.extend(to_nibbles(key.as_ref()));
+        let mut parents = Vec::new();
+        let mut u_ref = Some(self.get_node(root)?);
+        let mut nskip = 0;
+        let mut val = Some(val);
+        for (i, nib) in chunks.iter().enumerate() {
+            if nskip > 0 {
+                nskip -= 1;
+                continue
+            }
+            let mut u = u_ref.take().unwrap();
+            let u_ptr = u.as_ptr();
+            let next_ptr = match &u.inner {
+                NodeType::Branch(n) => match n.chd[*nib as usize] {
+                    Some(c) => c,
+                    None => {
+                        // insert the leaf to the empty slot
+                        let leaf_ptr = self
+                            .new_node(Node::new(NodeType::Leaf(LeafNode(
+                                PartialPath(chunks[i + 1..].to_vec()),
+                                Data(val.take().unwrap()),
+                            ))))?
+                            .as_ptr();
+                        u.write(|u| {
+                            let uu = u.inner.as_branch_mut().unwrap();
+                            uu.chd[*nib as usize] = Some(leaf_ptr);
+                            u.rehash();
+                        })
+                        .unwrap();
+                        break
+                    }
+                },
+                NodeType::Leaf(n) => {
+                    let n_path = n.0.to_vec();
+                    let n_value = Some(n.1.clone());
+                    self.split(
+                        u,
+                        &mut parents,
+                        &chunks[i..],
+                        n_path,
+                        n_value,
+                        val.take().unwrap(),
+                        &mut deleted,
+                    )?;
+                    break
+                }
+                NodeType::Extension(n) => {
+                    let n_path = n.0.to_vec();
+                    let n_ptr = n.1;
+                    nskip = n_path.len() - 1;
+                    if let Some(v) = self.split(
+                        u,
+                        &mut parents,
+                        &chunks[i..],
+                        n_path,
+                        None,
+                        val.take().unwrap(),
+                        &mut deleted,
+                    )? {
+                        val = Some(v);
+                        u = self.get_node(u_ptr)?;
+                        n_ptr
+                    } else {
+                        break
+                    }
+                }
+            };
+
+            parents.push((u, *nib));
+            u_ref = Some(self.get_node(next_ptr)?);
+        }
+        if val.is_some() {
+            let mut info = None;
+            let u_ptr = {
+                let mut u = u_ref.take().unwrap();
+                write_node!(
+                    self,
+                    u,
+                    |u| {
+                        info = match &mut u.inner {
+                            NodeType::Branch(n) => {
+                                n.value = Some(Data(val.take().unwrap()));
+                                None
+                            }
+                            NodeType::Leaf(n) => {
+                                if n.0.len() == 0 {
+                                    n.1 = Data(val.take().unwrap());
+                                    None
+                                } else {
+                                    let idx = n.0[0];
+                                    n.0 = PartialPath(n.0[1..].to_vec());
+                                    u.rehash();
+                                    Some((idx, true, None))
+                                }
+                            }
+                            NodeType::Extension(n) => {
+                                let idx = n.0[0];
+                                let more = if n.0.len() > 1 {
+                                    n.0 = PartialPath(n.0[1..].to_vec());
+                                    true
+                                } else {
+                                    false
+                                };
+                                Some((idx, more, Some(n.1)))
+                            }
+                        };
+                        u.rehash()
+                    },
+                    &mut parents,
+                    &mut deleted
+                );
+                u.as_ptr()
+            };
+
+            if let Some((idx, more, ext)) = info {
+                let mut chd = [None; NBRANCH];
+                let c_ptr = if more {
+                    u_ptr
+                } else {
+                    deleted.push(u_ptr);
+                    ext.unwrap()
+                };
+                chd[idx as usize] = Some(c_ptr);
+                let branch = self
+                    .new_node(Node::new(NodeType::Branch(BranchNode {
+                        chd,
+                        value: Some(Data(val.take().unwrap())),
+                    })))?
+                    .as_ptr();
+                self.set_parent(branch, &mut parents);
+            }
+        }
+
+        drop(u_ref);
+
+        for (mut r, _) in parents.into_iter().rev() {
+            r.write(|u| u.rehash()).unwrap();
+        }
+
+        for ptr in deleted.into_iter() {
+            self.free_node(ptr)?
+        }
+        Ok(())
+    }
+
+    fn after_remove_leaf<'b>(
+        &self, parents: &mut Vec<(ObjRef<'b, Node>, u8)>, deleted: &mut Vec<ObjPtr<Node>>,
+    ) -> Result<(), MerkleError> {
+        let (b_chd, val) = {
+            let (mut b_ref, b_idx) = parents.pop().unwrap();
+            // the immediate parent of a leaf must be a branch
+            b_ref
+                .write(|b| {
+                    b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = None;
+                    b.rehash()
+                })
+                .unwrap();
+            let b_inner = b_ref.inner.as_branch().unwrap();
+            let (b_chd, has_chd) = b_inner.single_child();
+            if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() {
+                return Ok(())
+            }
+            deleted.push(b_ref.as_ptr());
+            (b_chd, b_inner.value.clone())
+        };
+        let (mut p_ref, p_idx) = parents.pop().unwrap();
+        let p_ptr = p_ref.as_ptr();
+        if let Some(val) = val {
+            match &p_ref.inner {
+                NodeType::Branch(_) => {
+                    // from: [p: Branch] -> [b (v)]x -> [Leaf]x
+                    // to: [p: Branch] -> [Leaf (v)]
+                    let leaf = self
+                        .new_node(Node::new(NodeType::Leaf(LeafNode(PartialPath(Vec::new()), val))))?
+                        .as_ptr();
+                    p_ref
+                        .write(|p| {
+                            p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(leaf);
+                            p.rehash()
+                        })
+                        .unwrap();
+                }
+                NodeType::Extension(n) => {
+                    // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x
+                    // to: P -> [Leaf (v)]
+                    let leaf = self
+                        .new_node(Node::new(NodeType::Leaf(LeafNode(
+                            PartialPath(n.0.clone().into_inner()),
+                            val,
+                        ))))?
+                        .as_ptr();
+                    deleted.push(p_ptr);
+                    self.set_parent(leaf, parents);
+                }
+                _ => unreachable!(),
+            }
+        } else {
+            let (c_ptr, idx) = b_chd.unwrap();
+            let mut c_ref = self.get_node(c_ptr)?;
+            match &c_ref.inner {
+                NodeType::Branch(_) => {
+                    drop(c_ref);
+                    match &p_ref.inner {
+                        NodeType::Branch(_) => {
+                            //                            ____[Branch]
+                            //                           /
+                            // from: [p: Branch] -> [b]x*
+                            //                           \____[Leaf]x
+                            // to: [p: Branch] -> [Ext] -> [Branch]
+                            let ext = self
+                                .new_node(Node::new(NodeType::Extension(ExtNode(PartialPath(vec![idx]), c_ptr))))?
+                                .as_ptr();
+                            self.set_parent(ext, &mut [(p_ref, p_idx)]);
+                        }
+                        NodeType::Extension(_) => {
+                            //                         ____[Branch]
+                            //                        /
+                            // from: [p: Ext] -> [b]x*
+                            //                        \____[Leaf]x
+                            // to: [p: Ext] -> [Branch]
+                            write_node!(
+                                self,
+                                p_ref,
+                                |p| {
+                                    let mut pp = p.inner.as_extension_mut().unwrap();
+                                    pp.0 .0.push(idx);
+                                    pp.1 = c_ptr;
+                                    p.rehash();
+                                },
+                                parents,
+                                deleted
+                            );
+                        }
+                        _ => unreachable!(),
+                    }
+                }
+                NodeType::Leaf(_) | NodeType::Extension(_) => {
+                    #[allow(clippy::blocks_in_if_conditions)]
+                    match &p_ref.inner {
+                        NodeType::Branch(_) => {
+                            //                            ____[Leaf/Ext]
+                            //                           /
+                            // from: [p: Branch] -> [b]x*
+                            //                           \____[Leaf]x
+                            // to: [p: Branch] -> [Leaf/Ext]
+                            let c_ptr = if c_ref
+                                .write(|c| {
+                                    (match &mut c.inner {
+                                        NodeType::Leaf(n) => &mut n.0,
+                                        NodeType::Extension(n) => &mut n.0,
+                                        _ => unreachable!(),
+                                    })
+                                    .0
+                                    .insert(0, idx);
+                                    c.rehash()
+                                })
+                                .is_none()
+                            {
+                                deleted.push(c_ptr);
+                                self.new_node(c_ref.clone())?.as_ptr()
+                            } else {
+                                c_ptr
+                            };
+                            drop(c_ref);
+                            p_ref
+                                .write(|p| {
+                                    p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(c_ptr);
+                                    p.rehash()
+                                })
+                                .unwrap();
+                        }
+                        NodeType::Extension(n) => {
+                            //                               ____[Leaf/Ext]
+                            //                              /
+                            // from: P -> [p: Ext]x -> [b]x*
+                            //                              \____[Leaf]x
+                            // to: P -> [p: Leaf/Ext]
+                            deleted.push(p_ptr);
+                            if !write_node!(
+                                self,
+                                c_ref,
+                                |c| {
+                                    let mut path = n.0.clone().into_inner();
+                                    path.push(idx);
+                                    let path0 = match &mut c.inner {
+                                        NodeType::Leaf(n) => &mut n.0,
+                                        NodeType::Extension(n) => &mut n.0,
+                                        _ => unreachable!(),
+                                    };
+                                    path.extend(&**path0);
+                                    *path0 = PartialPath(path);
+                                    c.rehash()
+                                },
+                                parents,
+                                deleted
+                            ) {
+                                drop(c_ref);
+                                self.set_parent(c_ptr, parents);
+                            }
+                        }
+                        _ => unreachable!(),
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn after_remove_branch<'b>(
+        &self, (c_ptr, idx): (ObjPtr<Node>, u8), parents: &mut Vec<(ObjRef<'b, Node>, u8)>,
+        deleted: &mut Vec<ObjPtr<Node>>,
+    ) -> Result<(), MerkleError> {
+        // [b] -> [u] -> [c]
+        let (mut b_ref, b_idx) = parents.pop().unwrap();
+        let mut c_ref = self.get_node(c_ptr).unwrap();
+        match &c_ref.inner {
+            NodeType::Branch(_) => {
+                drop(c_ref);
+                let mut err = None;
+                write_node!(
+                    self,
+                    b_ref,
+                    |b| {
+                        if let Err(e) = (|| {
+                            match &mut b.inner {
+                                NodeType::Branch(n) => {
+                                    // from: [Branch] -> [Branch]x -> [Branch]
+                                    // to: [Branch] -> [Ext] -> [Branch]
+                                    n.chd[b_idx as usize] = Some(
+                                        self.new_node(Node::new(NodeType::Extension(ExtNode(
+                                            PartialPath(vec![idx]),
+                                            c_ptr,
+                                        ))))?
+                                        .as_ptr(),
+                                    );
+                                }
+                                NodeType::Extension(n) => {
+                                    // from: [Ext] -> [Branch]x -> [Branch]
+                                    // to: [Ext] -> [Branch]
+                                    n.0 .0.push(idx);
+                                    n.1 = c_ptr
+                                }
+                                _ => unreachable!(),
+                            }
+                            b.rehash();
+                            Ok(())
+                        })() {
+                            err = Some(Err(e))
+                        }
+                    },
+                    parents,
+                    deleted
+                );
+                if let Some(e) = err {
+                    return e
+                }
+            }
+            NodeType::Leaf(_) | NodeType::Extension(_) => match &b_ref.inner {
+                NodeType::Branch(_) => {
+                    // from: [Branch] -> [Branch]x -> [Leaf/Ext]
+                    // to: [Branch] -> [Leaf/Ext]
+                    #[allow(clippy::blocks_in_if_conditions)]
+                    let c_ptr = if c_ref
+                        .write(|c| {
+                            match &mut c.inner {
+                                NodeType::Leaf(n) => &mut n.0,
+                                NodeType::Extension(n) => &mut n.0,
+                                _ => unreachable!(),
+                            }
+                            .0
+                            .insert(0, idx);
+                            c.rehash()
+                        })
+                        .is_none()
+                    {
+                        deleted.push(c_ptr);
+                        self.new_node(c_ref.clone())?.as_ptr()
+                    } else {
+                        c_ptr
+                    };
+                    drop(c_ref);
+                    b_ref
+                        .write(|b| {
+                            b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = Some(c_ptr);
+                            b.rehash()
+                        })
+                        .unwrap();
+                }
+                NodeType::Extension(n) => {
+                    // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext]
+                    // to: P -> [Leaf/Ext]
+                    #[allow(clippy::blocks_in_if_conditions)]
+                    let c_ptr = if c_ref
+                        .write(|c| {
+                            let mut path = n.0.clone().into_inner();
+                            path.push(idx);
+                            let path0 = match &mut c.inner {
+                                NodeType::Leaf(n) => &mut n.0,
+                                NodeType::Extension(n) => &mut n.0,
+                                _ => unreachable!(),
+                            };
+                            path.extend(&**path0);
+                            *path0 = PartialPath(path);
+                            c.rehash()
+                        })
+                        .is_none()
+                    {
+                        deleted.push(c_ptr);
+                        self.new_node(c_ref.clone())?.as_ptr()
+                    } else {
+                        c_ptr
+                    };
+                    deleted.push(b_ref.as_ptr());
+                    drop(c_ref);
+                    self.set_parent(c_ptr, parents);
+                }
+                _ => unreachable!(),
+            },
+        }
+        Ok(())
+    }
+
+    pub fn remove<K: AsRef<[u8]>>(&mut self, key: K, root: ObjPtr<Node>) -> Result<Option<Vec<u8>>, MerkleError> {
+        let mut chunks = vec![0];
+        chunks.extend(to_nibbles(key.as_ref()));
+
+        if root.is_null() {
+            return Ok(None)
+        }
+
+        let mut deleted = Vec::new();
+        let mut parents: Vec<(ObjRef<Node>, _)> = Vec::new();
+        let mut u_ref = self.get_node(root)?;
+        let mut nskip = 0;
+        let mut found = None;
+
+        for (i, nib) in chunks.iter().enumerate() {
+            if nskip > 0 {
+                nskip -= 1;
+                continue
+            }
+            let next_ptr = match &u_ref.inner {
+                NodeType::Branch(n) => match n.chd[*nib as usize] {
+                    Some(c) => c,
+                    None => return Ok(None),
+                },
+                NodeType::Leaf(n) => {
+                    if chunks[i..] != *n.0 {
+                        return Ok(None)
+                    }
+                    found = Some(n.1.clone());
+                    deleted.push(u_ref.as_ptr());
+                    self.after_remove_leaf(&mut parents, &mut deleted)?;
+                    break
+                }
+                NodeType::Extension(n) => {
+                    let n_path = &*n.0;
+                    let rem_path = &chunks[i..];
+                    if rem_path < n_path || &rem_path[..n_path.len()] != n_path {
+                        return Ok(None)
+                    }
+                    nskip = n_path.len() - 1;
+                    n.1
+                }
+            };
+
+            parents.push((u_ref, *nib));
+            u_ref = self.get_node(next_ptr)?;
+        }
+        if found.is_none() {
+            match &u_ref.inner {
+                NodeType::Branch(n) => {
+                    if n.value.is_none() {
+                        return Ok(None)
+                    }
+                    let (c_chd, _) = n.single_child();
+                    u_ref
+                        .write(|u| {
+                            found = u.inner.as_branch_mut().unwrap().value.take();
+                            u.rehash()
+                        })
+                        .unwrap();
+                    if let Some((c_ptr, idx)) = c_chd {
+                        deleted.push(u_ref.as_ptr());
+                        self.after_remove_branch((c_ptr, idx), &mut parents, &mut deleted)?
+                    }
+                }
+                NodeType::Leaf(n) => {
+                    if n.0.len() > 0 {
+                        return Ok(None)
+                    }
+                    found = Some(n.1.clone());
+                    deleted.push(u_ref.as_ptr());
+                    self.after_remove_leaf(&mut parents, &mut deleted)?
+                }
+                _ => (),
+            }
+        }
+
+        drop(u_ref);
+
+        for (mut r, _) in parents.into_iter().rev() {
+            r.write(|u| u.rehash()).unwrap();
+        }
+
+        for ptr in deleted.into_iter() {
+            self.free_node(ptr)?;
+        }
+        Ok(found.map(|e| e.0))
+    }
+
+    fn remove_tree_(&self, u: ObjPtr<Node>, deleted: &mut Vec<ObjPtr<Node>>) -> Result<(), MerkleError> {
+        let u_ref = self.get_node(u)?;
+        match &u_ref.inner {
+            NodeType::Branch(n) => {
+                for c in n.chd.iter().flatten() {
+                    self.remove_tree_(*c, deleted)?
+                }
+            }
+            NodeType::Leaf(_) => (),
+            NodeType::Extension(n) => self.remove_tree_(n.1, deleted)?,
+        }
+        deleted.push(u);
+        Ok(())
+    }
+
+    pub fn remove_tree(&mut self, root: ObjPtr<Node>) -> Result<(), MerkleError> {
+        let mut deleted = Vec::new();
+        if root.is_null() {
+            return Ok(())
+        }
+        self.remove_tree_(root, &mut deleted)?;
+        for ptr in deleted.into_iter() {
+            self.free_node(ptr)?;
+        }
+        Ok(())
+    }
+
+    pub fn get_mut<K: AsRef<[u8]>>(&mut self, key: K, root: ObjPtr<Node>) -> Result<Option<RefMut>, MerkleError> {
+        let mut chunks = vec![0];
+        chunks.extend(to_nibbles(key.as_ref()));
+        let mut parents = Vec::new();
+
+        if root.is_null() {
+            return Ok(None)
+        }
+
+        let mut u_ref = self.get_node(root)?;
+        let mut nskip = 0;
+
+        for (i, nib) in chunks.iter().enumerate() {
+            let u_ptr = u_ref.as_ptr();
+            if nskip > 0 {
+                nskip -= 1;
+                continue
+            }
+            let next_ptr = match &u_ref.inner {
+                NodeType::Branch(n) => match n.chd[*nib as usize] {
+                    Some(c) => c,
+                    None => return Ok(None),
+                },
+                NodeType::Leaf(n) => {
+                    if chunks[i..] != *n.0 {
+                        return Ok(None)
+                    }
+                    drop(u_ref);
+                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
+                }
+                NodeType::Extension(n) => {
+                    let n_path = &*n.0;
+                    let rem_path = &chunks[i..];
+                    if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path {
+                        return Ok(None)
+                    }
+                    nskip = n_path.len() - 1;
+                    n.1
+                }
+            };
+            parents.push((u_ptr, *nib));
+            u_ref = self.get_node(next_ptr)?;
+        }
+
+        let u_ptr = u_ref.as_ptr();
+        match &u_ref.inner {
+            NodeType::Branch(n) => {
+                if n.value.as_ref().is_some() {
+                    drop(u_ref);
+                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
+                }
+            }
+            NodeType::Leaf(n) => {
+                if n.0.len() == 0 {
+                    drop(u_ref);
+                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
+                }
+            }
+            _ => (),
+        }
+
+        Ok(None)
+    }
+
+    /// Constructs a merkle proof for key. The result contains all encoded nodes
+    /// on the path to the value at key. The value itself is also included in the
+    /// last node and can be retrieved by verifying the proof.
+    ///
+    /// If the trie does not contain a value for key, the returned proof contains
+    /// all nodes of the longest existing prefix of the key, ending with the node
+    /// that proves the absence of the key (at least the root node).
+    pub fn prove<K, T>(&self, key: K, root: ObjPtr<Node>) -> Result<Proof, MerkleError>
+    where
+        K: AsRef<[u8]>,
+        T: ValueTransformer,
+    {
+        let mut chunks = Vec::new();
+        chunks.extend(to_nibbles(key.as_ref()));
+
+        let mut proofs: HashMap<[u8; 32], Vec<u8>> = HashMap::new();
+        if root.is_null() {
+            return Ok(Proof(proofs))
+        }
+
+        // Skip the sentinel root
+        let root = self
+            .get_node(root)?
+            .inner
+            .as_branch()
+            .ok_or(MerkleError::NotBranchNode)?
+            .chd[0];
+        let mut u_ref = match root {
+            Some(root) => self.get_node(root)?,
+            None => return Ok(Proof(proofs)),
+        };
+
+        let mut nskip = 0;
+        let mut nodes: Vec<ObjPtr<Node>> = Vec::new();
+        for (i, nib) in chunks.iter().enumerate() {
+            if nskip > 0 {
+                nskip -= 1;
+                continue
+            }
+            nodes.push(u_ref.as_ptr());
+            let next_ptr: ObjPtr<Node> = match &u_ref.inner {
+                NodeType::Branch(n) => match n.chd[*nib as usize] {
+                    Some(c) => c,
+                    None => break,
+                },
+                NodeType::Leaf(_) => break,
+                NodeType::Extension(n) => {
+                    let n_path = &*n.0;
+                    let remaining_path = &chunks[i..];
+                    if remaining_path.len() < n_path.len() || &remaining_path[..n_path.len()] != n_path {
+                        break
+                    } else {
+                        nskip = n_path.len() - 1;
+                        n.1
+                    }
+                }
+            };
+            u_ref = self.get_node(next_ptr)?;
+        }
+
+        match &u_ref.inner {
+            NodeType::Branch(n) => {
+                if n.value.as_ref().is_some() {
+                    nodes.push(u_ref.as_ptr());
+                }
+            }
+            NodeType::Leaf(n) => {
+                if n.0.len() == 0 {
+                    nodes.push(u_ref.as_ptr());
+                }
+            }
+            _ => (),
+        }
+
+        drop(u_ref);
+        // Get the hashes of the nodes.
+        for node in nodes {
+            let node = self.get_node(node)?;
+            let rlp = <&[u8]>::clone(&node.get_eth_rlp::<T>(self.store.as_ref()));
+            let hash: [u8; 32] = sha3::Keccak256::digest(rlp).into();
+            proofs.insert(hash, rlp.to_vec());
+        }
+        Ok(Proof(proofs))
+    }
+
+    pub fn get<K: AsRef<[u8]>>(&self, key: K, root: ObjPtr<Node>) -> Result<Option<Ref>, MerkleError> {
+        let mut chunks = vec![0];
+        chunks.extend(to_nibbles(key.as_ref()));
+
+        if root.is_null() {
+            return Ok(None)
+        }
+
+        let mut u_ref = self.get_node(root)?;
+        let mut nskip = 0;
+
+        for (i, nib) in chunks.iter().enumerate() {
+            if nskip > 0 {
+                nskip -= 1;
+                continue
+            }
+            let next_ptr = match &u_ref.inner {
+                NodeType::Branch(n) => match n.chd[*nib as usize] {
+                    Some(c) => c,
+                    None => return Ok(None),
+                },
+                NodeType::Leaf(n) => {
+                    if chunks[i..] != *n.0 {
+                        return Ok(None)
+                    }
+                    return Ok(Some(Ref(u_ref)))
+                }
+                NodeType::Extension(n) => {
+                    let n_path = &*n.0;
+                    let rem_path = &chunks[i..];
+                    if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path {
+                        return Ok(None)
+                    }
+                    nskip = n_path.len() - 1;
+                    n.1
+                }
+            };
+            u_ref = self.get_node(next_ptr)?;
+        }
+
+        match &u_ref.inner {
+            NodeType::Branch(n) => {
+                if n.value.as_ref().is_some() {
+                    return Ok(Some(Ref(u_ref)))
+                }
+            }
+            NodeType::Leaf(n) => {
+                if n.0.len() == 0 {
+                    return Ok(Some(Ref(u_ref)))
+                }
+            }
+            _ => (),
+        }
+
+        Ok(None)
+    }
+
+    pub fn flush_dirty(&self) -> Option<()> {
+        self.store.flush_dirty()
+    }
+}
+
+pub struct Ref<'a>(ObjRef<'a, Node>);
+
+pub struct RefMut<'a> {
+    ptr: ObjPtr<Node>,
+    parents: Vec<(ObjPtr<Node>, u8)>,
+    merkle: &'a mut Merkle,
+}
+
+impl<'a> std::ops::Deref for Ref<'a> {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        match &self.0.inner {
+            NodeType::Branch(n) => n.value.as_ref().unwrap(),
+            NodeType::Leaf(n) => &n.1,
+            _ => unreachable!(),
+        }
+    }
+}
+
+impl<'a> RefMut<'a> {
+    fn new(ptr: ObjPtr<Node>, parents: Vec<(ObjPtr<Node>, u8)>, merkle: &'a mut Merkle) -> Self {
+        Self { ptr, parents, merkle }
+    }
+
+    pub fn get(&self) -> Ref {
+        Ref(self.merkle.get_node(self.ptr).unwrap())
+    }
+
+    pub fn write(&mut self, modify: impl FnOnce(&mut Vec<u8>)) -> Result<(), MerkleError> {
+        let mut deleted = Vec::new();
+        {
+            let mut u_ref = self.merkle.get_node(self.ptr).unwrap();
+            let mut parents: Vec<_> = self
+                .parents
+                .iter()
+                .map(|(ptr, nib)| (self.merkle.get_node(*ptr).unwrap(), *nib))
+                .collect();
+            write_node!(
+                self.merkle,
+                u_ref,
+                |u| {
+                    modify(match &mut u.inner {
+                        NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0,
+                        NodeType::Leaf(n) => &mut n.1 .0,
+                        _ => unreachable!(),
+                    });
+                    u.rehash()
+                },
+                &mut parents,
+                &mut deleted
+            );
+        }
+        for ptr in deleted.into_iter() {
+            self.merkle.free_node(ptr)?;
+        }
+        Ok(())
+    }
+}
+
+pub trait ValueTransformer {
+    fn transform(bytes: &[u8]) -> Vec<u8>;
+}
+
+pub struct IdTrans;
+
+impl ValueTransformer for IdTrans {
+    fn transform(bytes: &[u8]) -> Vec<u8> {
+        bytes.to_vec()
+    }
+}
+
+pub fn to_nibbles(bytes: &[u8]) -> impl Iterator<Item = u8> + '_ {
+    bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter())
+}
+
+pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator<Item = u8> + '_ {
+    assert!(nibbles.len() & 1 == 0);
+    nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1])
+}
+
+#[test]
+fn test_to_nibbles() {
+    for (bytes, nibbles) in [
+        (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]),
+        (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]),
+    ] {
+        let n: Vec<_> = to_nibbles(&bytes).collect();
+        assert_eq!(n, nibbles);
+    }
+}
+
+
\ No newline at end of file diff --git a/docs/src/firewood/storage.rs.html b/docs/src/firewood/storage.rs.html new file mode 100644 index 000000000000..1c9dc5c2b653 --- /dev/null +++ b/docs/src/firewood/storage.rs.html @@ -0,0 +1,2416 @@ +storage.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+
// TODO: try to get rid of the use `RefCell` in this file
+
+use std::cell::{RefCell, RefMut};
+use std::collections::HashMap;
+use std::fmt;
+use std::num::NonZeroUsize;
+use std::ops::Deref;
+use std::rc::Rc;
+use std::sync::Arc;
+
+use aiofut::{AIOBuilder, AIOManager};
+use growthring::{
+    wal::{RecoverPolicy, WALLoader, WALWriter},
+    WALStoreAIO,
+};
+use nix::fcntl::{flock, FlockArg};
+use shale::{MemStore, MemView, SpaceID};
+use tokio::sync::{mpsc, oneshot, Mutex, Semaphore};
+use typed_builder::TypedBuilder;
+
+use crate::file::{Fd, File};
+
+pub(crate) const PAGE_SIZE_NBIT: u64 = 12;
+pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT;
+pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1;
+
+pub trait MemStoreR {
+    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>>;
+    fn id(&self) -> SpaceID;
+}
+
+type Page = [u8; PAGE_SIZE as usize];
+
+#[derive(Debug)]
+pub struct SpaceWrite {
+    offset: u64,
+    data: Box<[u8]>,
+}
+
+#[derive(Debug)]
+pub struct Ash {
+    pub old: Vec<SpaceWrite>,
+    pub new: Vec<Box<[u8]>>,
+}
+
+impl Ash {
+    fn new() -> Self {
+        Self {
+            old: Vec::new(),
+            new: Vec::new(),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct AshRecord(pub HashMap<SpaceID, Ash>);
+
+impl growthring::wal::Record for AshRecord {
+    fn serialize(&self) -> growthring::wal::WALBytes {
+        let mut bytes = Vec::new();
+        bytes.extend((self.0.len() as u64).to_le_bytes());
+        for (space_id, w) in self.0.iter() {
+            bytes.extend((*space_id).to_le_bytes());
+            bytes.extend((w.old.len() as u32).to_le_bytes());
+            for (sw_old, sw_new) in w.old.iter().zip(w.new.iter()) {
+                bytes.extend(sw_old.offset.to_le_bytes());
+                bytes.extend((sw_old.data.len() as u64).to_le_bytes());
+                bytes.extend(&*sw_old.data);
+                bytes.extend(&**sw_new);
+            }
+        }
+        bytes.into()
+    }
+}
+
+impl AshRecord {
+    #[allow(clippy::boxed_local)]
+    fn deserialize(raw: growthring::wal::WALBytes) -> Self {
+        let mut r = &raw[..];
+        let len = u64::from_le_bytes(r[..8].try_into().unwrap());
+        r = &r[8..];
+        let writes = (0..len)
+            .map(|_| {
+                let space_id = u8::from_le_bytes(r[..1].try_into().unwrap());
+                let wlen = u32::from_le_bytes(r[1..5].try_into().unwrap());
+                r = &r[5..];
+                let mut old = Vec::new();
+                let mut new = Vec::new();
+                for _ in 0..wlen {
+                    let offset = u64::from_le_bytes(r[..8].try_into().unwrap());
+                    let data_len = u64::from_le_bytes(r[8..16].try_into().unwrap());
+                    r = &r[16..];
+                    let old_write = SpaceWrite {
+                        offset,
+                        data: r[..data_len as usize].into(),
+                    };
+                    r = &r[data_len as usize..];
+                    let new_data: Box<[u8]> = r[..data_len as usize].into();
+                    r = &r[data_len as usize..];
+                    old.push(old_write);
+                    new.push(new_data);
+                }
+                (space_id, Ash { old, new })
+            })
+            .collect();
+        Self(writes)
+    }
+}
+
+/// Basic copy-on-write item in the linear storage space for multi-versioning.
+pub struct DeltaPage(u64, Box<Page>);
+
+impl DeltaPage {
+    #[inline(always)]
+    fn offset(&self) -> u64 {
+        self.0 << PAGE_SIZE_NBIT
+    }
+
+    #[inline(always)]
+    fn data(&self) -> &[u8] {
+        self.1.as_ref()
+    }
+
+    #[inline(always)]
+    fn data_mut(&mut self) -> &mut [u8] {
+        self.1.as_mut()
+    }
+}
+
+#[derive(Default)]
+pub struct StoreDelta(Vec<DeltaPage>);
+
+impl fmt::Debug for StoreDelta {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(f, "<StoreDelta>")
+    }
+}
+
+impl Deref for StoreDelta {
+    type Target = [DeltaPage];
+    fn deref(&self) -> &[DeltaPage] {
+        &self.0
+    }
+}
+
+impl StoreDelta {
+    pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self {
+        let mut deltas = Vec::new();
+        let mut widx: Vec<_> = (0..writes.len()).filter(|i| writes[*i].data.len() > 0).collect();
+        if widx.is_empty() {
+            // the writes are all empty
+            return Self(deltas)
+        }
+
+        // sort by the starting point
+        widx.sort_by_key(|i| writes[*i].offset);
+
+        let mut witer = widx.into_iter();
+        let w0 = &writes[witer.next().unwrap()];
+        let mut head = w0.offset >> PAGE_SIZE_NBIT;
+        let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT;
+
+        macro_rules! create_dirty_pages {
+            ($l: expr, $r: expr) => {
+                for p in $l..=$r {
+                    let off = p << PAGE_SIZE_NBIT;
+                    deltas.push(DeltaPage(
+                        p,
+                        Box::new(src.get_slice(off, PAGE_SIZE).unwrap().try_into().unwrap()),
+                    ));
+                }
+            };
+        }
+
+        for i in witer {
+            let w = &writes[i];
+            let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT;
+            let wp = w.offset >> PAGE_SIZE_NBIT;
+            if wp > tail {
+                // all following writes won't go back past w.offset, so the previous continous
+                // write area is determined
+                create_dirty_pages!(head, tail);
+                head = wp;
+            }
+            tail = std::cmp::max(tail, ep)
+        }
+        create_dirty_pages!(head, tail);
+
+        let psize = PAGE_SIZE as usize;
+        for w in writes.iter() {
+            let mut l = 0;
+            let mut r = deltas.len();
+            while r - l > 1 {
+                let mid = (l + r) >> 1;
+                (*if w.offset < deltas[mid].offset() {
+                    &mut r
+                } else {
+                    &mut l
+                }) = mid;
+            }
+            let off = (w.offset - deltas[l].offset()) as usize;
+            let len = std::cmp::min(psize - off, w.data.len());
+            deltas[l].data_mut()[off..off + len].copy_from_slice(&w.data[..len]);
+            let mut data = &w.data[len..];
+            while data.len() >= psize {
+                l += 1;
+                deltas[l].data_mut().copy_from_slice(&data[..psize]);
+                data = &data[psize..];
+            }
+            if !data.is_empty() {
+                l += 1;
+                deltas[l].data_mut()[..data.len()].copy_from_slice(data);
+            }
+        }
+        Self(deltas)
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+}
+
+pub struct StoreRev {
+    prev: RefCell<Rc<dyn MemStoreR>>,
+    delta: StoreDelta,
+}
+
+impl fmt::Debug for StoreRev {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "<StoreRev")?;
+        for d in self.delta.iter() {
+            write!(f, " 0x{:x}", d.0)?;
+        }
+        writeln!(f, ">")
+    }
+}
+
+impl MemStoreR for StoreRev {
+    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>> {
+        let prev = self.prev.borrow();
+        let mut start = offset;
+        let end = start + length;
+        let delta = &self.delta;
+        let mut l = 0;
+        let mut r = delta.len();
+        // no dirty page, before or after all dirty pages
+        if r == 0 {
+            return prev.get_slice(start, end - start)
+        }
+        // otherwise, some dirty pages are covered by the range
+        while r - l > 1 {
+            let mid = (l + r) >> 1;
+            (*if start < delta[mid].offset() { &mut r } else { &mut l }) = mid;
+        }
+        if start >= delta[l].offset() + PAGE_SIZE {
+            l += 1
+        }
+        if l >= delta.len() || end < delta[l].offset() {
+            return prev.get_slice(start, end - start)
+        }
+        let mut data = Vec::new();
+        let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE);
+        if start < delta[l].offset() {
+            data.extend(prev.get_slice(start, delta[l].offset() - start)?);
+            data.extend(&delta[l].data()[..p_off as usize]);
+        } else {
+            data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]);
+        };
+        start = delta[l].offset() + p_off;
+        while start < end {
+            l += 1;
+            if l >= delta.len() || end < delta[l].offset() {
+                data.extend(prev.get_slice(start, end - start)?);
+                break
+            }
+            if delta[l].offset() > start {
+                data.extend(prev.get_slice(start, delta[l].offset() - start)?);
+            }
+            if end < delta[l].offset() + PAGE_SIZE {
+                data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]);
+                break
+            }
+            data.extend(delta[l].data());
+            start = delta[l].offset() + PAGE_SIZE;
+        }
+        assert!(data.len() == length as usize);
+        Some(data)
+    }
+
+    fn id(&self) -> SpaceID {
+        self.prev.borrow().id()
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct StoreRevShared(Rc<StoreRev>);
+
+impl StoreRevShared {
+    pub fn from_ash(prev: Rc<dyn MemStoreR>, writes: &[SpaceWrite]) -> Self {
+        let delta = StoreDelta::new(prev.as_ref(), writes);
+        let prev = RefCell::new(prev);
+        Self(Rc::new(StoreRev { prev, delta }))
+    }
+
+    pub fn from_delta(prev: Rc<dyn MemStoreR>, delta: StoreDelta) -> Self {
+        let prev = RefCell::new(prev);
+        Self(Rc::new(StoreRev { prev, delta }))
+    }
+
+    pub fn set_prev(&mut self, prev: Rc<dyn MemStoreR>) {
+        *self.0.prev.borrow_mut() = prev
+    }
+
+    pub fn inner(&self) -> &Rc<StoreRev> {
+        &self.0
+    }
+}
+
+impl MemStore for StoreRevShared {
+    fn get_view(&self, offset: u64, length: u64) -> Option<Box<dyn MemView>> {
+        let data = self.0.get_slice(offset, length)?;
+        Some(Box::new(StoreRef { data }))
+    }
+
+    fn get_shared(&self) -> Option<Box<dyn Deref<Target = dyn MemStore>>> {
+        Some(Box::new(StoreShared(self.clone())))
+    }
+
+    fn write(&self, _offset: u64, _change: &[u8]) {
+        // StoreRevShared is a read-only view version of MemStore
+        // Writes could be induced by lazy hashing and we can just ignore those
+    }
+
+    fn id(&self) -> SpaceID {
+        <StoreRev as MemStoreR>::id(&self.0)
+    }
+}
+
+struct StoreRef {
+    data: Vec<u8>,
+}
+
+impl Deref for StoreRef {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.data
+    }
+}
+
+impl MemView for StoreRef {}
+
+struct StoreShared<S: Clone + MemStore>(S);
+
+impl<S: Clone + MemStore + 'static> Deref for StoreShared<S> {
+    type Target = dyn MemStore;
+    fn deref(&self) -> &(dyn MemStore + 'static) {
+        &self.0
+    }
+}
+
+struct StoreRevMutDelta {
+    pages: HashMap<u64, Box<Page>>,
+    plain: Ash,
+}
+
+#[derive(Clone)]
+pub struct StoreRevMut {
+    prev: Rc<dyn MemStoreR>,
+    deltas: Rc<RefCell<StoreRevMutDelta>>,
+}
+
+impl StoreRevMut {
+    pub fn new(prev: Rc<dyn MemStoreR>) -> Self {
+        Self {
+            prev,
+            deltas: Rc::new(RefCell::new(StoreRevMutDelta {
+                pages: HashMap::new(),
+                plain: Ash::new(),
+            })),
+        }
+    }
+
+    fn get_page_mut(&self, pid: u64) -> RefMut<[u8]> {
+        let mut deltas = self.deltas.borrow_mut();
+        if deltas.pages.get(&pid).is_none() {
+            let page = Box::new(
+                self.prev
+                    .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE)
+                    .unwrap()
+                    .try_into()
+                    .unwrap(),
+            );
+            deltas.pages.insert(pid, page);
+        }
+        RefMut::map(deltas, |e| &mut e.pages.get_mut(&pid).unwrap()[..])
+    }
+
+    pub fn take_delta(&self) -> (StoreDelta, Ash) {
+        let mut pages = Vec::new();
+        let deltas = std::mem::replace(
+            &mut *self.deltas.borrow_mut(),
+            StoreRevMutDelta {
+                pages: HashMap::new(),
+                plain: Ash::new(),
+            },
+        );
+        for (pid, page) in deltas.pages.into_iter() {
+            pages.push(DeltaPage(pid, page));
+        }
+        pages.sort_by_key(|p| p.0);
+        (StoreDelta(pages), deltas.plain)
+    }
+}
+
+impl MemStore for StoreRevMut {
+    fn get_view(&self, offset: u64, length: u64) -> Option<Box<dyn MemView>> {
+        let data = if length == 0 {
+            Vec::new()
+        } else {
+            let end = offset + length - 1;
+            let s_pid = offset >> PAGE_SIZE_NBIT;
+            let s_off = (offset & PAGE_MASK) as usize;
+            let e_pid = end >> PAGE_SIZE_NBIT;
+            let e_off = (end & PAGE_MASK) as usize;
+            let deltas = &self.deltas.borrow().pages;
+            if s_pid == e_pid {
+                match deltas.get(&s_pid) {
+                    Some(p) => p[s_off..e_off + 1].to_vec(),
+                    None => self.prev.get_slice(offset, length)?,
+                }
+            } else {
+                let mut data = match deltas.get(&s_pid) {
+                    Some(p) => p[s_off..].to_vec(),
+                    None => self.prev.get_slice(offset, PAGE_SIZE - s_off as u64)?,
+                };
+                for p in s_pid + 1..e_pid {
+                    match deltas.get(&p) {
+                        Some(p) => data.extend(**p),
+                        None => data.extend(&self.prev.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?),
+                    };
+                }
+                match deltas.get(&e_pid) {
+                    Some(p) => data.extend(&p[..e_off + 1]),
+                    None => data.extend(self.prev.get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?),
+                }
+                data
+            }
+        };
+        Some(Box::new(StoreRef { data }))
+    }
+
+    fn get_shared(&self) -> Option<Box<dyn Deref<Target = dyn MemStore>>> {
+        Some(Box::new(StoreShared(self.clone())))
+    }
+
+    fn write(&self, offset: u64, mut change: &[u8]) {
+        let length = change.len() as u64;
+        let end = offset + length - 1;
+        let s_pid = offset >> PAGE_SIZE_NBIT;
+        let s_off = (offset & PAGE_MASK) as usize;
+        let e_pid = end >> PAGE_SIZE_NBIT;
+        let e_off = (end & PAGE_MASK) as usize;
+        let mut old: Vec<u8> = Vec::new();
+        let new: Box<[u8]> = change.into();
+        if s_pid == e_pid {
+            let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1];
+            old.extend(&*slice);
+            slice.copy_from_slice(change)
+        } else {
+            let len = PAGE_SIZE as usize - s_off;
+            {
+                let slice = &mut self.get_page_mut(s_pid)[s_off..];
+                old.extend(&*slice);
+                slice.copy_from_slice(&change[..len]);
+            }
+            change = &change[len..];
+            for p in s_pid + 1..e_pid {
+                let mut slice = self.get_page_mut(p);
+                old.extend(&*slice);
+                slice.copy_from_slice(&change[..PAGE_SIZE as usize]);
+                change = &change[PAGE_SIZE as usize..];
+            }
+            let slice = &mut self.get_page_mut(e_pid)[..e_off + 1];
+            old.extend(&*slice);
+            slice.copy_from_slice(change);
+        }
+        let plain = &mut self.deltas.borrow_mut().plain;
+        assert!(old.len() == new.len());
+        plain.old.push(SpaceWrite {
+            offset,
+            data: old.into(),
+        });
+        plain.new.push(new);
+    }
+
+    fn id(&self) -> SpaceID {
+        self.prev.id()
+    }
+}
+
+#[cfg(test)]
+#[derive(Clone)]
+pub struct ZeroStore(Rc<()>);
+
+#[cfg(test)]
+impl ZeroStore {
+    pub fn new() -> Self {
+        Self(Rc::new(()))
+    }
+}
+
+#[cfg(test)]
+impl MemStoreR for ZeroStore {
+    fn get_slice(&self, _: u64, length: u64) -> Option<Vec<u8>> {
+        Some(vec![0; length as usize])
+    }
+
+    fn id(&self) -> SpaceID {
+        shale::INVALID_SPACE_ID
+    }
+}
+
+#[test]
+fn test_from_ash() {
+    use rand::{rngs::StdRng, Rng, SeedableRng};
+    let mut rng = StdRng::seed_from_u64(42);
+    let min = rng.gen_range(0..2 * PAGE_SIZE);
+    let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE);
+    for _ in 0..2000 {
+        let n = 20;
+        let mut canvas = Vec::new();
+        canvas.resize((max - min) as usize, 0);
+        let mut writes: Vec<_> = Vec::new();
+        for _ in 0..n {
+            let l = rng.gen_range(min..max);
+            let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max));
+            let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect();
+            for (idx, byte) in (l..r).zip(data.iter()) {
+                canvas[(idx - min) as usize] = *byte;
+            }
+            println!("[0x{l:x}, 0x{r:x})");
+            writes.push(SpaceWrite { offset: l, data });
+        }
+        let z = Rc::new(ZeroStore::new());
+        let rev = StoreRevShared::from_ash(z, &writes);
+        println!("{rev:?}");
+        assert_eq!(&**rev.get_view(min, max - min).unwrap(), &canvas);
+        for _ in 0..2 * n {
+            let l = rng.gen_range(min..max);
+            let r = rng.gen_range(l + 1..max);
+            assert_eq!(
+                &**rev.get_view(l, r - l).unwrap(),
+                &canvas[(l - min) as usize..(r - min) as usize]
+            );
+        }
+    }
+}
+
+#[derive(TypedBuilder)]
+pub struct StoreConfig {
+    ncached_pages: usize,
+    ncached_files: usize,
+    #[builder(default = 22)] // 4MB file by default
+    file_nbit: u64,
+    space_id: SpaceID,
+    rootfd: Fd,
+}
+
+struct CachedSpaceInner {
+    cached_pages: lru::LruCache<u64, Box<Page>>,
+    pinned_pages: HashMap<u64, (usize, Box<Page>)>,
+    files: Arc<FilePool>,
+    disk_buffer: DiskBufferRequester,
+}
+
+#[derive(Clone)]
+pub struct CachedSpace {
+    inner: Rc<RefCell<CachedSpaceInner>>,
+    space_id: SpaceID,
+}
+
+impl CachedSpace {
+    pub fn new(cfg: &StoreConfig) -> Result<Self, StoreError> {
+        let space_id = cfg.space_id;
+        let files = Arc::new(FilePool::new(cfg)?);
+        Ok(Self {
+            inner: Rc::new(RefCell::new(CachedSpaceInner {
+                cached_pages: lru::LruCache::new(NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size")),
+                pinned_pages: HashMap::new(),
+                files,
+                disk_buffer: DiskBufferRequester::default(),
+            })),
+            space_id,
+        })
+    }
+
+    /// Apply `delta` to the store and return the StoreDelta that can undo this change.
+    pub fn update(&self, delta: &StoreDelta) -> Option<StoreDelta> {
+        let mut pages = Vec::new();
+        for DeltaPage(pid, page) in &delta.0 {
+            let data = self.inner.borrow_mut().pin_page(self.space_id, *pid).ok()?;
+            // save the original data
+            pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap())));
+            // apply the change
+            data.copy_from_slice(page.as_ref());
+        }
+        Some(StoreDelta(pages))
+    }
+}
+
+impl CachedSpaceInner {
+    fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result<Box<Page>, StoreError> {
+        if let Some(p) = self.disk_buffer.get_page(space_id, pid) {
+            return Ok(Box::new(*p))
+        }
+        let file_nbit = self.files.get_file_nbit();
+        let file_size = 1 << file_nbit;
+        let poff = pid << PAGE_SIZE_NBIT;
+        let file = self.files.get_file(poff >> file_nbit)?;
+        let mut page: Page = [0; PAGE_SIZE as usize];
+        nix::sys::uio::pread(file.get_fd(), &mut page, (poff & (file_size - 1)) as nix::libc::off_t)
+            .map_err(StoreError::System)?;
+        Ok(Box::new(page))
+    }
+
+    fn pin_page(&mut self, space_id: SpaceID, pid: u64) -> Result<&'static mut [u8], StoreError> {
+        let base = match self.pinned_pages.get_mut(&pid) {
+            Some(mut e) => {
+                e.0 += 1;
+                e.1.as_mut_ptr()
+            }
+            None => {
+                let mut page = match self.cached_pages.pop(&pid) {
+                    Some(p) => p,
+                    None => self.fetch_page(space_id, pid)?,
+                };
+                let ptr = page.as_mut_ptr();
+                self.pinned_pages.insert(pid, (1, page));
+                ptr
+            }
+        };
+        Ok(unsafe { std::slice::from_raw_parts_mut(base, PAGE_SIZE as usize) })
+    }
+
+    fn unpin_page(&mut self, pid: u64) {
+        use std::collections::hash_map::Entry::*;
+        let page = match self.pinned_pages.entry(pid) {
+            Occupied(mut e) => {
+                let cnt = &mut e.get_mut().0;
+                assert!(*cnt > 0);
+                *cnt -= 1;
+                if *cnt == 0 {
+                    e.remove().1
+                } else {
+                    return
+                }
+            }
+            _ => unreachable!(),
+        };
+        self.cached_pages.put(pid, page);
+    }
+}
+
+struct PageRef {
+    pid: u64,
+    data: &'static mut [u8],
+    store: CachedSpace,
+}
+
+impl std::ops::Deref for PageRef {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        self.data
+    }
+}
+
+impl std::ops::DerefMut for PageRef {
+    fn deref_mut(&mut self) -> &mut [u8] {
+        self.data
+    }
+}
+
+impl PageRef {
+    fn new(pid: u64, store: &CachedSpace) -> Option<Self> {
+        Some(Self {
+            pid,
+            data: store.inner.borrow_mut().pin_page(store.space_id, pid).ok()?,
+            store: store.clone(),
+        })
+    }
+}
+
+impl Drop for PageRef {
+    fn drop(&mut self) {
+        self.store.inner.borrow_mut().unpin_page(self.pid);
+    }
+}
+
+impl MemStoreR for CachedSpace {
+    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>> {
+        if length == 0 {
+            return Some(Default::default())
+        }
+        let end = offset + length - 1;
+        let s_pid = offset >> PAGE_SIZE_NBIT;
+        let s_off = (offset & PAGE_MASK) as usize;
+        let e_pid = end >> PAGE_SIZE_NBIT;
+        let e_off = (end & PAGE_MASK) as usize;
+        if s_pid == e_pid {
+            return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec())
+        }
+        let mut data: Vec<u8> = Vec::new();
+        {
+            data.extend(&PageRef::new(s_pid, self)?[s_off..]);
+            for p in s_pid + 1..e_pid {
+                data.extend(&PageRef::new(p, self)?[..]);
+            }
+            data.extend(&PageRef::new(e_pid, self)?[..e_off + 1]);
+        }
+        Some(data)
+    }
+
+    fn id(&self) -> SpaceID {
+        self.space_id
+    }
+}
+
+pub struct FilePool {
+    files: parking_lot::Mutex<lru::LruCache<u64, Arc<File>>>,
+    file_nbit: u64,
+    rootfd: Fd,
+}
+
+impl FilePool {
+    fn new(cfg: &StoreConfig) -> Result<Self, StoreError> {
+        let rootfd = cfg.rootfd;
+        let file_nbit = cfg.file_nbit;
+        let s = Self {
+            files: parking_lot::Mutex::new(lru::LruCache::new(
+                NonZeroUsize::new(cfg.ncached_files).expect("non-zero file num"),
+            )),
+            file_nbit,
+            rootfd,
+        };
+        let f0 = s.get_file(0)?;
+        if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() {
+            return Err(StoreError::InitError("the store is busy".into()))
+        }
+        Ok(s)
+    }
+
+    fn get_file(&self, fid: u64) -> Result<Arc<File>, StoreError> {
+        let mut files = self.files.lock();
+        let file_size = 1 << self.file_nbit;
+        Ok(match files.get(&fid) {
+            Some(f) => f.clone(),
+            None => {
+                files.put(
+                    fid,
+                    Arc::new(File::new(fid, file_size, self.rootfd).map_err(StoreError::System)?),
+                );
+                files.peek(&fid).unwrap().clone()
+            }
+        })
+    }
+
+    fn get_file_nbit(&self) -> u64 {
+        self.file_nbit
+    }
+}
+
+impl Drop for FilePool {
+    fn drop(&mut self) {
+        let f0 = self.get_file(0).unwrap();
+        flock(f0.get_fd(), FlockArg::UnlockNonblock).ok();
+        nix::unistd::close(self.rootfd).ok();
+    }
+}
+
+#[derive(Debug)]
+pub struct BufferWrite {
+    pub space_id: SpaceID,
+    pub delta: StoreDelta,
+}
+
+pub enum BufferCmd {
+    InitWAL(Fd, String),
+    WriteBatch(Vec<BufferWrite>, AshRecord),
+    GetPage((SpaceID, u64), oneshot::Sender<Option<Arc<Page>>>),
+    CollectAsh(usize, oneshot::Sender<Vec<AshRecord>>),
+    RegCachedSpace(SpaceID, Arc<FilePool>),
+    Shutdown,
+}
+
+#[derive(TypedBuilder, Clone)]
+pub struct WALConfig {
+    #[builder(default = 22)] // 4MB WAL logs
+    pub(crate) file_nbit: u64,
+    #[builder(default = 15)] // 32KB
+    pub(crate) block_nbit: u64,
+    #[builder(default = 100)] // preserve a rolling window of 100 past commits
+    pub(crate) max_revisions: u32,
+}
+
+/// Config for the disk buffer.
+#[derive(TypedBuilder, Clone)]
+pub struct DiskBufferConfig {
+    /// Maximum buffered disk buffer commands.
+    #[builder(default = 4096)]
+    pub(crate) max_buffered: usize,
+    /// Maximum number of pending pages.
+    #[builder(default = 65536)] // 256MB total size by default
+    max_pending: usize,
+    /// Maximum number of concurrent async I/O requests.
+    #[builder(default = 1024)]
+    max_aio_requests: u32,
+    /// Maximum number of async I/O responses that it polls for at a time.
+    #[builder(default = 128)]
+    max_aio_response: u16,
+    /// Maximum number of async I/O requests per submission.
+    #[builder(default = 128)]
+    max_aio_submit: usize,
+    /// Maximum number of concurrent async I/O requests in WAL.
+    #[builder(default = 256)]
+    wal_max_aio_requests: usize,
+    /// Maximum buffered WAL records.
+    #[builder(default = 1024)]
+    wal_max_buffered: usize,
+    /// Maximum batched WAL records per write.
+    #[builder(default = 4096)]
+    wal_max_batch: usize,
+}
+
+struct PendingPage {
+    staging_data: Arc<Page>,
+    file_nbit: u64,
+    staging_notifiers: Vec<Rc<Semaphore>>,
+    writing_notifiers: Vec<Rc<Semaphore>>,
+}
+
+pub struct DiskBuffer {
+    pending: HashMap<(SpaceID, u64), PendingPage>,
+    inbound: mpsc::Receiver<BufferCmd>,
+    fc_notifier: Option<oneshot::Sender<()>>,
+    fc_blocker: Option<oneshot::Receiver<()>>,
+    file_pools: [Option<Arc<FilePool>>; 255],
+    aiomgr: AIOManager,
+    local_pool: Rc<tokio::task::LocalSet>,
+    task_id: u64,
+    tasks: Rc<RefCell<HashMap<u64, Option<tokio::task::JoinHandle<()>>>>>,
+    wal: Option<Rc<Mutex<WALWriter<WALStoreAIO>>>>,
+    cfg: DiskBufferConfig,
+    wal_cfg: WALConfig,
+}
+
+impl DiskBuffer {
+    pub fn new(inbound: mpsc::Receiver<BufferCmd>, cfg: &DiskBufferConfig, wal: &WALConfig) -> Option<Self> {
+        const INIT: Option<Arc<FilePool>> = None;
+        let aiomgr = AIOBuilder::default()
+            .max_events(cfg.max_aio_requests)
+            .max_nwait(cfg.max_aio_response)
+            .max_nbatched(cfg.max_aio_submit)
+            .build()
+            .ok()?;
+
+        Some(Self {
+            pending: HashMap::new(),
+            cfg: cfg.clone(),
+            inbound,
+            fc_notifier: None,
+            fc_blocker: None,
+            file_pools: [INIT; 255],
+            aiomgr,
+            local_pool: Rc::new(tokio::task::LocalSet::new()),
+            task_id: 0,
+            tasks: Rc::new(RefCell::new(HashMap::new())),
+            wal: None,
+            wal_cfg: wal.clone(),
+        })
+    }
+
+    unsafe fn get_longlive_self(&mut self) -> &'static mut Self {
+        std::mem::transmute::<&mut Self, &'static mut Self>(self)
+    }
+
+    fn schedule_write(&mut self, page_key: (SpaceID, u64)) {
+        let p = self.pending.get(&page_key).unwrap();
+        let offset = page_key.1 << PAGE_SIZE_NBIT;
+        let fid = offset >> p.file_nbit;
+        let fmask = (1 << p.file_nbit) - 1;
+        let file = self.file_pools[page_key.0 as usize]
+            .as_ref()
+            .unwrap()
+            .get_file(fid)
+            .unwrap();
+        let fut = self
+            .aiomgr
+            .write(file.get_fd(), offset & fmask, Box::new(*p.staging_data), None);
+        let s = unsafe { self.get_longlive_self() };
+        self.start_task(async move {
+            let (res, _) = fut.await;
+            res.unwrap();
+            s.finish_write(page_key);
+        });
+    }
+
+    fn finish_write(&mut self, page_key: (SpaceID, u64)) {
+        use std::collections::hash_map::Entry::*;
+        match self.pending.entry(page_key) {
+            Occupied(mut e) => {
+                let slot = e.get_mut();
+                for notifier in std::mem::take(&mut slot.writing_notifiers) {
+                    notifier.add_permits(1)
+                }
+                if slot.staging_notifiers.is_empty() {
+                    e.remove();
+                    if self.pending.len() < self.cfg.max_pending {
+                        if let Some(notifier) = self.fc_notifier.take() {
+                            notifier.send(()).unwrap();
+                        }
+                    }
+                } else {
+                    assert!(slot.writing_notifiers.is_empty());
+                    std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers);
+                    // write again
+                    self.schedule_write(page_key);
+                }
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), ()> {
+        let mut aiobuilder = AIOBuilder::default();
+        aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32);
+        let aiomgr = aiobuilder.build().map_err(|_| ())?;
+        let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)).map_err(|_| ())?;
+        let mut loader = WALLoader::new();
+        loader
+            .file_nbit(self.wal_cfg.file_nbit)
+            .block_nbit(self.wal_cfg.block_nbit)
+            .recover_policy(RecoverPolicy::Strict);
+        if self.wal.is_some() {
+            // already initialized
+            return Ok(())
+        }
+        let wal = loader
+            .load(
+                store,
+                |raw, _| {
+                    let batch = AshRecord::deserialize(raw);
+                    for (space_id, Ash { old, new }) in batch.0 {
+                        for (old, data) in old.into_iter().zip(new.into_iter()) {
+                            let offset = old.offset;
+                            let file_pool = self.file_pools[space_id as usize].as_ref().unwrap();
+                            let file_nbit = file_pool.get_file_nbit();
+                            let file_mask = (1 << file_nbit) - 1;
+                            let fid = offset >> file_nbit;
+                            nix::sys::uio::pwrite(
+                                file_pool.get_file(fid).map_err(|_| ())?.get_fd(),
+                                &data,
+                                (offset & file_mask) as nix::libc::off_t,
+                            )
+                            .map_err(|_| ())?;
+                        }
+                    }
+                    Ok(())
+                },
+                self.wal_cfg.max_revisions,
+            )
+            .await?;
+        self.wal = Some(Rc::new(Mutex::new(wal)));
+        Ok(())
+    }
+
+    async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec<BufferWrite>, AshRecord)>) {
+        use std::collections::hash_map::Entry::*;
+        loop {
+            let mut bwrites = Vec::new();
+            let mut records = Vec::new();
+
+            if let Some((bw, ac)) = writes.recv().await {
+                records.push(ac);
+                bwrites.extend(bw);
+            } else {
+                break
+            }
+            while let Ok((bw, ac)) = writes.try_recv() {
+                records.push(ac);
+                bwrites.extend(bw);
+                if records.len() >= self.cfg.wal_max_batch {
+                    break
+                }
+            }
+            // first write to WAL
+            let ring_ids: Vec<_> = futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records))
+                .await
+                .into_iter()
+                .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1)
+                .collect();
+            let sem = Rc::new(tokio::sync::Semaphore::new(0));
+            let mut npermit = 0;
+            for BufferWrite { space_id, delta } in bwrites {
+                for w in delta.0 {
+                    let page_key = (space_id, w.0);
+                    match self.pending.entry(page_key) {
+                        Occupied(mut e) => {
+                            let e = e.get_mut();
+                            e.staging_data = w.1.into();
+                            e.staging_notifiers.push(sem.clone());
+                            npermit += 1;
+                        }
+                        Vacant(e) => {
+                            let file_nbit = self.file_pools[page_key.0 as usize].as_ref().unwrap().file_nbit;
+                            e.insert(PendingPage {
+                                staging_data: w.1.into(),
+                                file_nbit,
+                                staging_notifiers: Vec::new(),
+                                writing_notifiers: vec![sem.clone()],
+                            });
+                            npermit += 1;
+                            self.schedule_write(page_key);
+                        }
+                    }
+                }
+            }
+            let wal = self.wal.as_ref().unwrap().clone();
+            let max_revisions = self.wal_cfg.max_revisions;
+            self.start_task(async move {
+                let _ = sem.acquire_many(npermit).await.unwrap();
+                wal.lock()
+                    .await
+                    .peel(ring_ids, max_revisions)
+                    .await
+                    .map_err(|_| "WAL errore while pruning")
+                    .unwrap();
+            });
+            if self.pending.len() >= self.cfg.max_pending {
+                let (tx, rx) = oneshot::channel();
+                self.fc_notifier = Some(tx);
+                self.fc_blocker = Some(rx);
+            }
+        }
+    }
+
+    async fn process(&mut self, req: BufferCmd, wal_in: &mpsc::Sender<(Vec<BufferWrite>, AshRecord)>) -> bool {
+        match req {
+            BufferCmd::Shutdown => return false,
+            BufferCmd::InitWAL(rootfd, waldir) => {
+                if (self.init_wal(rootfd, waldir).await).is_err() {
+                    panic!("cannot initialize from WAL")
+                }
+            }
+            BufferCmd::GetPage(page_key, tx) => tx
+                .send(self.pending.get(&page_key).map(|e| e.staging_data.clone()))
+                .unwrap(),
+            BufferCmd::WriteBatch(writes, wal_writes) => {
+                wal_in.send((writes, wal_writes)).await.unwrap();
+            }
+            BufferCmd::CollectAsh(nrecords, tx) => {
+                // wait to ensure writes are paused for WAL
+                let ash = self
+                    .wal
+                    .as_ref()
+                    .unwrap()
+                    .clone()
+                    .lock()
+                    .await
+                    .read_recent_records(nrecords, &RecoverPolicy::Strict)
+                    .await
+                    .unwrap()
+                    .into_iter()
+                    .map(AshRecord::deserialize)
+                    .collect();
+                tx.send(ash).unwrap();
+            }
+            BufferCmd::RegCachedSpace(space_id, files) => self.file_pools[space_id as usize] = Some(files),
+        }
+        true
+    }
+
+    fn start_task<F: std::future::Future<Output = ()> + 'static>(&mut self, fut: F) {
+        let task_id = self.task_id;
+        self.task_id += 1;
+        let tasks = self.tasks.clone();
+        self.tasks.borrow_mut().insert(
+            task_id,
+            Some(self.local_pool.spawn_local(async move {
+                fut.await;
+                tasks.borrow_mut().remove(&task_id);
+            })),
+        );
+    }
+
+    #[tokio::main(flavor = "current_thread")]
+    pub async fn run(mut self) {
+        let wal_in = {
+            let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered);
+            let s = unsafe { self.get_longlive_self() };
+            self.start_task(s.run_wal_queue(rx));
+            tx
+        };
+        self.local_pool
+            .clone()
+            .run_until(async {
+                loop {
+                    if let Some(fc) = self.fc_blocker.take() {
+                        // flow control, wait until ready
+                        fc.await.unwrap();
+                    }
+                    let req = self.inbound.recv().await.unwrap();
+                    if !self.process(req, &wal_in).await {
+                        break
+                    }
+                }
+                drop(wal_in);
+                let handles: Vec<_> = self
+                    .tasks
+                    .borrow_mut()
+                    .iter_mut()
+                    .map(|(_, task)| task.take().unwrap())
+                    .collect();
+                for h in handles {
+                    h.await.unwrap();
+                }
+            })
+            .await;
+    }
+}
+
+#[derive(Clone)]
+pub struct DiskBufferRequester {
+    sender: mpsc::Sender<BufferCmd>,
+}
+
+impl Default for DiskBufferRequester {
+    fn default() -> Self {
+        Self {
+            sender: mpsc::channel(1).0,
+        }
+    }
+}
+
+impl DiskBufferRequester {
+    pub fn new(sender: mpsc::Sender<BufferCmd>) -> Self {
+        Self { sender }
+    }
+
+    pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option<Arc<Page>> {
+        let (resp_tx, resp_rx) = oneshot::channel();
+        self.sender
+            .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx))
+            .ok()
+            .unwrap();
+        resp_rx.blocking_recv().unwrap()
+    }
+
+    pub fn write(&self, page_batch: Vec<BufferWrite>, write_batch: AshRecord) {
+        self.sender
+            .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch))
+            .ok()
+            .unwrap()
+    }
+
+    pub fn shutdown(&self) {
+        self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap()
+    }
+
+    pub fn init_wal(&self, waldir: &str, rootfd: Fd) {
+        self.sender
+            .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string()))
+            .ok()
+            .unwrap()
+    }
+
+    pub fn collect_ash(&self, nrecords: usize) -> Vec<AshRecord> {
+        let (resp_tx, resp_rx) = oneshot::channel();
+        self.sender
+            .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx))
+            .ok()
+            .unwrap();
+        resp_rx.blocking_recv().unwrap()
+    }
+
+    pub fn reg_cached_space(&self, space: &CachedSpace) {
+        let mut inner = space.inner.borrow_mut();
+        inner.disk_buffer = self.clone();
+        self.sender
+            .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone()))
+            .ok()
+            .unwrap()
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum StoreError {
+    System(nix::Error),
+    InitError(String),
+    // TODO: more error report from the DiskBuffer
+    //WriterError,
+}
+
+impl From<nix::Error> for StoreError {
+    fn from(e: nix::Error) -> Self {
+        StoreError::System(e)
+    }
+}
+
+
\ No newline at end of file diff --git a/docs/storage.js b/docs/storage.js new file mode 100644 index 000000000000..17c1da81f183 --- /dev/null +++ b/docs/storage.js @@ -0,0 +1 @@ +"use strict";const darkThemes=["dark","ayu"];window.currentTheme=document.getElementById("themeStyle");window.mainTheme=document.getElementById("mainThemeStyle");window.RUSTDOC_MOBILE_BREAKPOINT=700;const settingsDataset=(function(){const settingsElement=document.getElementById("default-settings");if(settingsElement===null){return null}const dataset=settingsElement.dataset;if(dataset===undefined){return null}return dataset})();function getSettingValue(settingName){const current=getCurrentValue(settingName);if(current!==null){return current}if(settingsDataset!==null){const def=settingsDataset[settingName.replace(/-/g,"_")];if(def!==undefined){return def}}return null}const localStoredTheme=getSettingValue("theme");const savedHref=[];function hasClass(elem,className){return elem&&elem.classList&&elem.classList.contains(className)}function addClass(elem,className){if(!elem||!elem.classList){return}elem.classList.add(className)}function removeClass(elem,className){if(!elem||!elem.classList){return}elem.classList.remove(className)}function onEach(arr,func,reversed){if(arr&&arr.length>0&&func){if(reversed){const length=arr.length;for(let i=length-1;i>=0;--i){if(func(arr[i])){return true}}}else{for(const elem of arr){if(func(elem)){return true}}}}return false}function onEachLazy(lazyArray,func,reversed){return onEach(Array.prototype.slice.call(lazyArray),func,reversed)}function updateLocalStorage(name,value){try{window.localStorage.setItem("rustdoc-"+name,value)}catch(e){}}function getCurrentValue(name){try{return window.localStorage.getItem("rustdoc-"+name)}catch(e){return null}}function switchTheme(styleElem,mainStyleElem,newTheme,saveTheme){const newHref=mainStyleElem.href.replace(/\/rustdoc([^/]*)\.css/,"/"+newTheme+"$1"+".css");if(saveTheme){updateLocalStorage("theme",newTheme)}if(styleElem.href===newHref){return}let found=false;if(savedHref.length===0){onEachLazy(document.getElementsByTagName("link"),el=>{savedHref.push(el.href)})}onEach(savedHref,el=>{if(el===newHref){found=true;return true}});if(found){styleElem.href=newHref}}function useSystemTheme(value){if(value===undefined){value=true}updateLocalStorage("use-system-theme",value);const toggle=document.getElementById("use-system-theme");if(toggle&&toggle instanceof HTMLInputElement){toggle.checked=value}}const updateSystemTheme=(function(){if(!window.matchMedia){return()=>{const cssTheme=getComputedStyle(document.documentElement).getPropertyValue("content");switchTheme(window.currentTheme,window.mainTheme,JSON.parse(cssTheme)||"light",true)}}const mql=window.matchMedia("(prefers-color-scheme: dark)");function handlePreferenceChange(mql){const use=theme=>{switchTheme(window.currentTheme,window.mainTheme,theme,true)};if(getSettingValue("use-system-theme")!=="false"){const lightTheme=getSettingValue("preferred-light-theme")||"light";const darkTheme=getSettingValue("preferred-dark-theme")||"dark";if(mql.matches){use(darkTheme)}else{use(lightTheme)}}else{use(getSettingValue("theme"))}}mql.addListener(handlePreferenceChange);return()=>{handlePreferenceChange(mql)}})();function switchToSavedTheme(){switchTheme(window.currentTheme,window.mainTheme,getSettingValue("theme")||"light",false)}if(getSettingValue("use-system-theme")!=="false"&&window.matchMedia){if(getSettingValue("use-system-theme")===null&&getSettingValue("preferred-dark-theme")===null&&darkThemes.indexOf(localStoredTheme)>=0){updateLocalStorage("preferred-dark-theme",localStoredTheme)}updateSystemTheme()}else{switchToSavedTheme()}if(getSettingValue("source-sidebar-show")==="true"){addClass(document.documentElement,"source-sidebar-expanded")}window.addEventListener("pageshow",ev=>{if(ev.persisted){setTimeout(switchToSavedTheme,0)}}) \ No newline at end of file diff --git a/docs/toggle-minus.svg b/docs/toggle-minus.svg new file mode 100644 index 000000000000..73154788a0e8 --- /dev/null +++ b/docs/toggle-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/toggle-plus.svg b/docs/toggle-plus.svg new file mode 100644 index 000000000000..08b17033e164 --- /dev/null +++ b/docs/toggle-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/wheel.svg b/docs/wheel.svg new file mode 100644 index 000000000000..01da3b24c7c4 --- /dev/null +++ b/docs/wheel.svg @@ -0,0 +1 @@ + \ No newline at end of file From 19bcfd274e2ba22497487b5e9f2e402843c77ee4 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 10:35:36 -0700 Subject: [PATCH 0113/1053] README + website nits (#5) * nits * update website * more nits * update assets --- README.md | 36 ++++++---------- {assets => docs/assets}/architecture.svg | 0 {assets => docs/assets}/three-layers.svg | 0 docs/firewood/index.html | 55 ++++++++++++------------ docs/src/firewood/lib.rs.html | 37 ++++++++-------- firewood/src/lib.rs | 44 +++++++++---------- 6 files changed, 78 insertions(+), 94 deletions(-) rename {assets => docs/assets}/architecture.svg (100%) rename {assets => docs/assets}/three-layers.svg (100%) diff --git a/README.md b/README.md index a4b61ed81e22..9ad8cc211247 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,18 @@ Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes access to latest state, by providing extremely fast reads, but also provides a limited view into past state. It does not copy-on-write the -Merkle Patricia Trie (MPT) to generate an ever growing forest of tries like EVM, -but instead keeps one latest version of the MPT index on disk and apply +state trie to generate an ever growing forest of tries like other databases, +but instead keeps one latest version of the trie index on disk and apply in-place updates to it. This ensures that the database size is small and stable during the course of running firewood. Firewood was first conceived to provide -a very fast storage layer for qEVM to enable a fast, complete EVM system with -right design choices made totally from scratch, but it also serves as a drop-in -replacement for any EVM-compatible blockchain storage system, and fits for the -general use of a certified key-value store of arbitrary data. +a very fast storage layer for the EVM but could be used on any blockchain that +requires authenticated state. Firewood is a robust database implemented from the ground up to directly store -MPT nodes and user data. Unlike most (if not all) of the solutions in the field, +trie nodes and user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses the tree structure as the index on -disk. Thus, there is no additional “emulation” of the logical MPT to flatten +disk. Thus, there is no additional “emulation” of the logical trie to flatten out the data structure to feed into the underlying DB that is unaware of the data being stored. @@ -32,28 +30,20 @@ store back in memory. While running the store, new changes will also contribute to the configured window of changes (at batch granularity) to access any past versions with no additional cost at all. -The on-disk footprint of Firewood is more compact than geth. It provides two -isolated storage space which can be both or selectively used the user. The -account model portion of the storage offers something very similar to StateDB -in geth, which captures the address-“state key” style of two-level access for +Firewood provides two isolated storage spaces which can be both or selectively +used the user. The account model portion of the storage offers something very similar +to StateDB in geth, which captures the address-“state key” style of two-level access for an account’s (smart contract’s) state. Therefore, it takes minimal effort to delegate all state storage from an EVM implementation to firewood. The other -portion of the storage supports generic MPT storage for arbitrary keys and +portion of the storage supports generic trie storage for arbitrary keys and values. When unused, there is no additional cost. -Firewood is an embedded key-value store, optimized to store blockchain state. -It prioritizes access to latest state, by providing extremely fast reads, but -also provides a limited view into past state. It does not copy-on-write like -the EVM, but instead makes in-place changes to the state tree. This ensures -that the database size is small and stable during the course of running -firewood. Firewood exists to provide a very fast storage layer for [qEVM](https://github.com/ava-labs/qevm) to use in a custom subnet. - ## License firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). ## Roadmap -### Green Milestone +### Green Milestone This milestone will focus on additional code cleanup, including supporting concurrent access to a specific revision, as well as cleaning up the basic reader and writer interfaces to have consistent read/write semantics. @@ -64,6 +54,7 @@ are uniquely identified by root hashes. operations do not see any changes. - [ ] Be able to read-your-write in a batch that is not committed. Uncommitted changes will not be shown to any other concurrent readers. + ### Seasoned milestone This milestone will add support for proposals, including proposed future branches, with a cache to make committing these branches efficiently. @@ -71,6 +62,7 @@ branches, with a cache to make committing these branches efficiently. propose a batch against any existing proposed revision. - [ ] Be able to quickly commit a batch that has been proposed. Note that this invalidates all other proposals that are not children of the committed proposed batch. + ### Dried milestone The focus of this milestone will be to support synchronization to other instances to replicate the state. A synchronization library should also @@ -95,7 +87,7 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. -To integrate firewood into a custom VM or other project, see the [firewood-connection](./firewood-connection/README.md) for a straightforward way to use firewood via custom message-passing. +To integrate firewood into a custom VM or other project, see the [firewood-connection](./firewood-connection/README.md) for a straightforward way to use firewood via custom message-passing. ## CLI Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](fwdctl/README.md). diff --git a/assets/architecture.svg b/docs/assets/architecture.svg similarity index 100% rename from assets/architecture.svg rename to docs/assets/architecture.svg diff --git a/assets/three-layers.svg b/docs/assets/three-layers.svg similarity index 100% rename from assets/three-layers.svg rename to docs/assets/three-layers.svg diff --git a/docs/firewood/index.html b/docs/firewood/index.html index 6fee912c7079..ca4569495f75 100644 --- a/docs/firewood/index.html +++ b/docs/firewood/index.html @@ -1,19 +1,18 @@ firewood - Rust
Expand description

Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval.

-

Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes -access to latest state, by providing extremely fast reads, but also provides a limited view -into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever -growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk -and apply in-place updates to it. This ensures that the database size is small and stable -during the course of running firewood. Firewood was first conceived to provide a very fast -storage layer for qEVM to enable a fast, complete EVM system with right design choices made -totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible -blockchain storage system, and fits for the general use of a certified key-value store of -arbitrary data.

-

Firewood is a robust database implemented from the ground up to directly store MPT nodes and +

Firewood is an embedded key-value store, optimized to store blockchain state. +It prioritizes access to latest state, by providing extremely fast reads, but +also provides a limited view into past state. It does not copy-on-write the +state trie to generate an ever growing forest of tries like other databases, +but instead keeps one latest version of the trie index on disk and apply +in-place updates to it. This ensures that the database size is small and stable +during the course of running firewood. Firewood was first conceived to provide +a very fast storage layer for the EVM but could be used on any blockchain that +requires authenticated state.

+

Firewood is a robust database implemented from the ground up to directly store trie nodes and user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses the tree structure as the index on disk. Thus, there is no additional “emulation” of the -logical MPT to flatten out the data structure to feed into the underlying DB that is unaware +logical trie to flatten out the data structure to feed into the underlying DB that is unaware of the data being stored.

Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees atomicity and durability in the database, but also offers “reversibility”: some portion @@ -21,12 +20,12 @@ some past versions of the entire store back in memory. While running the store, new changes will also contribute to the configured window of changes (at batch granularity) to access any past versions with no additional cost at all.

-

The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage -space which can be both or selectively used the user. The account model portion of the storage -offers something very similar to StateDB in geth, which captures the address-“state key” -style of two-level access for an account’s (smart contract’s) state. Therefore, it takes +

Firewood provides two isolated storage spaces which can be both or selectively used the user. +The account model portion of the storage offers something very similar to +StateDB in geth, which captures the address-“state key” style of +two-level access for an account’s (smart contract’s) state. Therefore, it takes minimal effort to delegate all state storage from an EVM implementation to firewood. The other -portion of the storage supports generic MPT storage for arbitrary keys and values. When unused, +portion of the storage supports generic trie storage for arbitrary keys and values. When unused, there is no additional cost.

Design Philosophy & Overview

With some on-going academic research efforts and increasing demand of faster local storage @@ -85,7 +84,7 @@

Storage Model

basic implementation in shale crate, but one can always define his/her own ShaleStore).

  • -

    Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking +

    Data structure: in Firewood, one or more tries are maintained by invoking ShaleStore (see src/merkle.rs; another stash for code objects is in src/account.rs). The data structure code is totally unaware of how its objects (i.e., nodes) are organized or persisted on disk. It is as if they’re just in memory, which makes it much easier to write @@ -94,10 +93,10 @@

    Storage Model

    The three layers are depicted as follows:

    - +

    Given the abstraction, one can easily realize the fact that the actual data that affect the -state of the data structure (MPT) is what the linear space (MemStore) keeps track of, that is, +state of the data structure (trie) is what the linear space (MemStore) keeps track of, that is, a flat but conceptually large byte vector. In other words, given a valid byte vector as the content of the linear space, the higher level data structure can be uniquely determined, there is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) @@ -108,8 +107,8 @@

    Storage Model

    MemShale or MemStore implementation, but without having to touch the code for the persisted data structure.

    Page-based Shadowing and Revisions

    -

    Following the idea that the MPTs are just a view of a linear byte space, all writes made to the -MPTs inside Firewood will eventually be consolidated into some interval writes to the linear +

    Following the idea that the tries are just a view of a linear byte space, all writes made to the +tries inside Firewood will eventually be consolidated into some interval writes to the linear space. The writes may overlap and some frequent writes are even done to the same spot in the space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the @@ -125,7 +124,7 @@

    - +

    Thanks to the shadow pages, we can both revive some historical versions of the store and maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram @@ -181,11 +180,11 @@

    Modules

  • \ No newline at end of file +

    Modules

    diff --git a/docs/src/firewood/lib.rs.html b/docs/src/firewood/lib.rs.html index a800998f3ad6..cee6c171767e 100644 --- a/docs/src/firewood/lib.rs.html +++ b/docs/src/firewood/lib.rs.html @@ -205,20 +205,17 @@ //! //! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes //! access to latest state, by providing extremely fast reads, but also provides a limited view -//! into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever -//! growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk +//! into past state. It does not copy-on-write the state trie to generate an ever +//! growing forest of tries like other databases, but instead keeps one latest version of the trie index on disk //! and apply in-place updates to it. This ensures that the database size is small and stable //! during the course of running firewood. Firewood was first conceived to provide a very fast -//! storage layer for qEVM to enable a fast, complete EVM system with right design choices made -//! totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible -//! blockchain storage system, and fits for the general use of a certified key-value store of -//! arbitrary data. +//! storage layer for the EVM but could be used on any blockchain that requires authenticated state. //! -//! Firewood is a robust database implemented from the ground up to directly store MPT nodes and +//! Firewood is a robust database implemented from the ground up to directly store trie nodes and //! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a //! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses //! the tree structure as the index on disk. Thus, there is no additional "emulation" of the -//! logical MPT to flatten out the data structure to feed into the underlying DB that is unaware +//! logical trie to flatten out the data structure to feed into the underlying DB that is unaware //! of the data being stored. //! //! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees @@ -233,7 +230,7 @@ //! offers something very similar to `StateDB` in geth, which captures the address-"state key" //! style of two-level access for an account's (smart contract's) state. Therefore, it takes //! minimal effort to delegate all state storage from an EVM implementation to firewood. The other -//! portion of the storage supports generic MPT storage for arbitrary keys and values. When unused, +//! portion of the storage supports generic trie storage for arbitrary keys and values. When unused, //! there is no additional cost. //! //! # Design Philosophy & Overview @@ -290,7 +287,7 @@ //! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of //! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). //! -//! - Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking +//! - Data structure: in Firewood, one or more tries are maintained by invoking //! `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`). //! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or //! persisted on disk. It is as if they're just in memory, which makes it much easier to write @@ -299,11 +296,11 @@ //! The three layers are depicted as follows: //! //! <p align="center"> -//! <img src="https://drive.google.com/uc?export=view&id=1KnlpqnxkmFd_aKZHwcferIdX137GVZJr" width="80%"> +//! <img src="/assets/three-layers.svg" width="80%"> //! </p> //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (MPT) is what the linear space (`MemStore`) keeps track of, that is, +//! state of the data structure (trie) is what the linear space (`MemStore`) keeps track of, that is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the //! content of the linear space, the higher level data structure can be *uniquely* determined, there //! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) @@ -316,8 +313,8 @@ //! //! ## Page-based Shadowing and Revisions //! -//! Following the idea that the MPTs are just a view of a linear byte space, all writes made to the -//! MPTs inside Firewood will eventually be consolidated into some interval writes to the linear +//! Following the idea that the tries are just a view of a linear byte space, all writes made to the +//! tries inside Firewood will eventually be consolidated into some interval writes to the linear //! space. The writes may overlap and some frequent writes are even done to the same spot in the //! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit //! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the @@ -333,13 +330,13 @@ //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! -//! - Traverse the MPT, and that induces the access to some nodes. Suppose the nodes are not already in +//! - Traverse the trie, and that induces the access to some nodes. Suppose the nodes are not already in //! memory, then: //! //! - Bring the necessary pages that contain the accessed nodes into the memory and cache them //! (`storage::CachedSpace`). //! -//! - Make changes to the MPT, and that induces the writes to some nodes. The nodes are either +//! - Make changes to the trie, and that induces the writes to some nodes. The nodes are either //! already cached in memory (its pages are cached, or its handle `ObjRef<Node>` is still in //! `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the //! second step for it). @@ -370,7 +367,7 @@ //! we "push down" these changes to the base and clear up the staging space. //! //! <p align="center"> -//! <img src="https://drive.google.com/uc?export=view&id=1l2CUbq85nX_g0GfQj44ClrKXd253sBFv" width="100%"> +//! <img src="/assets/architecture.svg" width="100%"> //! </p> //! //! Thanks to the shadow pages, we can both revive some historical versions of the store and @@ -383,11 +380,11 @@ //! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the //! linear space is very likely untouched by that historical write batch. //! -//! Then, with the three-layer abstraction we previously talked about, an historical MPT could be +//! Then, with the three-layer abstraction we previously talked about, a historical trie could be //! derived. In fact, because there is no mandatory traversal or scanning in the process, the //! only cost to revive a historical state from the log is to just playback the records and create //! those shadow pages. There is very little additional cost because the ghost space is summoned on an -//! on-demand manner while one accesses the historical MPT. +//! on-demand manner while one accesses the historical trie. //! //! In the other direction, when new write batches are committed, the system moves forward, we can //! therefore maintain a rolling window of past revisions in memory with *zero* cost. The @@ -405,4 +402,4 @@ pub mod proof; pub(crate) mod storage; -
    \ No newline at end of file +
    diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index c77226a0cc2e..09e654cd92b2 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -5,20 +5,17 @@ //! //! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes //! access to latest state, by providing extremely fast reads, but also provides a limited view -//! into past state. It does not copy-on-write the Merkle Patricia Trie (MPT) to generate an ever -//! growing forest of tries like EVM, but instead keeps one latest version of the MPT index on disk +//! into past state. It does not copy-on-write the state trie to generate an ever +//! growing forest of tries like other databases, but instead keeps one latest version of the trie index on disk //! and apply in-place updates to it. This ensures that the database size is small and stable //! during the course of running firewood. Firewood was first conceived to provide a very fast -//! storage layer for qEVM to enable a fast, complete EVM system with right design choices made -//! totally from scratch, but it also serves as a drop-in replacement for any EVM-compatible -//! blockchain storage system, and fits for the general use of a certified key-value store of -//! arbitrary data. +//! storage layer for the EVM but could be used on any blockchain that requires authenticated state. //! -//! Firewood is a robust database implemented from the ground up to directly store MPT nodes and +//! Firewood is a robust database implemented from the ground up to directly store trie nodes and //! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a //! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses //! the tree structure as the index on disk. Thus, there is no additional "emulation" of the -//! logical MPT to flatten out the data structure to feed into the underlying DB that is unaware +//! logical trie to flatten out the data structure to feed into the underlying DB that is unaware //! of the data being stored. //! //! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees @@ -28,12 +25,11 @@ //! will also contribute to the configured window of changes (at batch granularity) to access any past //! versions with no additional cost at all. //! -//! The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage -//! space which can be both or selectively used the user. The account model portion of the storage -//! offers something very similar to `StateDB` in geth, which captures the address-"state key" -//! style of two-level access for an account's (smart contract's) state. Therefore, it takes -//! minimal effort to delegate all state storage from an EVM implementation to firewood. The other -//! portion of the storage supports generic MPT storage for arbitrary keys and values. When unused, +//! Firewood provides two isolated storage spaces which can be both or selectively used the user. +//! The account model portion of the storage offers something very similar to `StateDB` in geth, +//! which captures the address-"state key" style of two-level access for an account's (smart contract's) state. +//! Therefore, it takes minimal effort to delegate all state storage from an EVM implementation to firewood. The other +//! portion of the storage supports generic trie storage for arbitrary keys and values. When unused, //! there is no additional cost. //! //! # Design Philosophy & Overview @@ -90,7 +86,7 @@ //! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of //! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). //! -//! - Data structure: in Firewood, one or more Ethereum-style MPTs are maintained by invoking +//! - Data structure: in Firewood, one or more tries are maintained by invoking //! `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`). //! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or //! persisted on disk. It is as if they're just in memory, which makes it much easier to write @@ -99,11 +95,11 @@ //! The three layers are depicted as follows: //! //!

    -//! +//! //!

    //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (MPT) is what the linear space (`MemStore`) keeps track of, that is, +//! state of the data structure (trie) is what the linear space (`MemStore`) keeps track of, that is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the //! content of the linear space, the higher level data structure can be *uniquely* determined, there //! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) @@ -116,8 +112,8 @@ //! //! ## Page-based Shadowing and Revisions //! -//! Following the idea that the MPTs are just a view of a linear byte space, all writes made to the -//! MPTs inside Firewood will eventually be consolidated into some interval writes to the linear +//! Following the idea that the tries are just a view of a linear byte space, all writes made to the +//! tries inside Firewood will eventually be consolidated into some interval writes to the linear //! space. The writes may overlap and some frequent writes are even done to the same spot in the //! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit //! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the @@ -133,13 +129,13 @@ //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! -//! - Traverse the MPT, and that induces the access to some nodes. Suppose the nodes are not already in +//! - Traverse the trie, and that induces the access to some nodes. Suppose the nodes are not already in //! memory, then: //! //! - Bring the necessary pages that contain the accessed nodes into the memory and cache them //! (`storage::CachedSpace`). //! -//! - Make changes to the MPT, and that induces the writes to some nodes. The nodes are either +//! - Make changes to the trie, and that induces the writes to some nodes. The nodes are either //! already cached in memory (its pages are cached, or its handle `ObjRef` is still in //! `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the //! second step for it). @@ -170,7 +166,7 @@ //! we "push down" these changes to the base and clear up the staging space. //! //!

    -//! +//! //!

    //! //! Thanks to the shadow pages, we can both revive some historical versions of the store and @@ -183,11 +179,11 @@ //! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the //! linear space is very likely untouched by that historical write batch. //! -//! Then, with the three-layer abstraction we previously talked about, an historical MPT could be +//! Then, with the three-layer abstraction we previously talked about, a historical trie could be //! derived. In fact, because there is no mandatory traversal or scanning in the process, the //! only cost to revive a historical state from the log is to just playback the records and create //! those shadow pages. There is very little additional cost because the ghost space is summoned on an -//! on-demand manner while one accesses the historical MPT. +//! on-demand manner while one accesses the historical trie. //! //! In the other direction, when new write batches are committed, the system moves forward, we can //! therefore maintain a rolling window of past revisions in memory with *zero* cost. The From 1584376e2f05a6afff588d87db0711d01782c223 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 10:43:59 -0700 Subject: [PATCH 0114/1053] Fix image links (#6) * fix links * use links --- docs/firewood/index.html | 4 ++-- docs/src/firewood/lib.rs.html | 4 ++-- firewood/src/lib.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/firewood/index.html b/docs/firewood/index.html index ca4569495f75..6a1f40333cc5 100644 --- a/docs/firewood/index.html +++ b/docs/firewood/index.html @@ -93,7 +93,7 @@

    Storage Model

    The three layers are depicted as follows:

    - +

    Given the abstraction, one can easily realize the fact that the actual data that affect the state of the data structure (trie) is what the linear space (MemStore) keeps track of, that is, @@ -169,7 +169,7 @@

    - +

    Thanks to the shadow pages, we can both revive some historical versions of the store and maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram diff --git a/docs/src/firewood/lib.rs.html b/docs/src/firewood/lib.rs.html index cee6c171767e..1a5c4e1fda81 100644 --- a/docs/src/firewood/lib.rs.html +++ b/docs/src/firewood/lib.rs.html @@ -296,7 +296,7 @@ //! The three layers are depicted as follows: //! //! <p align="center"> -//! <img src="/assets/three-layers.svg" width="80%"> +//! <img src="https://ava-labs.github.io/firewood/assets/three-layers.svg" width="80%"> //! </p> //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the @@ -367,7 +367,7 @@ //! we "push down" these changes to the base and clear up the staging space. //! //! <p align="center"> -//! <img src="/assets/architecture.svg" width="100%"> +//! <img src="https://ava-labs.github.io/firewood/assets/architecture.svg" width="100%"> //! </p> //! //! Thanks to the shadow pages, we can both revive some historical versions of the store and diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 09e654cd92b2..3dc4077abaa1 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -95,7 +95,7 @@ //! The three layers are depicted as follows: //! //!

    -//! +//! //!

    //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the @@ -166,7 +166,7 @@ //! we "push down" these changes to the base and clear up the staging space. //! //!

    -//! +//! //!

    //! //! Thanks to the shadow pages, we can both revive some historical versions of the store and From b27b89f3cc2a835b2d8fb2ba4ad2719dd64da44a Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:53:32 -0700 Subject: [PATCH 0115/1053] Code comments clean up (#9) --- firewood/src/db.rs | 18 +++++++++--------- firewood/src/merkle.rs | 2 +- fwdctl/src/create.rs | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 22e8c5f3e3f1..571f9e08ee63 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -74,7 +74,7 @@ struct DBParams { /// Config for accessing a version of the DB. #[derive(TypedBuilder, Clone, Debug)] pub struct DBRevConfig { - /// Maximum cached MPT objects. + /// Maximum cached Trie objects. #[builder(default = 1 << 20)] pub merkle_ncached_objs: usize, /// Maximum cached Blob (currently just `Account`) objects. @@ -96,7 +96,7 @@ pub struct DBConfig { #[builder(default = 22)] // 4MB file by default pub meta_file_nbit: u64, /// Maximum cached pages for the item stash. This is the low-level cache used by the linear - /// space that holds MPT nodes and account objects. + /// space that holds Trie nodes and account objects. #[builder(default = 262144)] // 1G total size by default pub payload_ncached_pages: usize, /// Maximum cached file descriptors for the item stash. @@ -290,7 +290,7 @@ impl DBRev { } } - /// Dump the MPT of the generic key-value storage. + /// Dump the Trie of the generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.merkle .dump(self.header.kv_root, w) @@ -304,7 +304,7 @@ impl DBRev { .map_err(DBError::Merkle) } - /// Dump the MPT of the entire account model storage. + /// Dump the Trie of the entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.merkle .dump(self.header.acc_root, w) @@ -319,7 +319,7 @@ impl DBRev { }) } - /// Dump the MPT of the state storage under an account. + /// Dump the Trie of the state storage under an account. pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { let acc = match self.merkle.get(key, self.header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), @@ -350,7 +350,7 @@ impl DBRev { }) } - /// Provides a proof that a key is in the MPT. + /// Provides a proof that a key is in the Trie. pub fn prove>(&self, key: K) -> Result { self.merkle .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) @@ -687,17 +687,17 @@ impl DB { } } - /// Dump the MPT of the latest generic key-value storage. + /// Dump the Trie of the latest generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.inner.lock().latest.kv_dump(w) } - /// Dump the MPT of the latest entire account model storage. + /// Dump the Trie of the latest entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.inner.lock().latest.dump(w) } - /// Dump the MPT of the latest state storage under an account. + /// Dump the Trie of the latest state storage under an account. pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { self.inner.lock().latest.dump_account(key, w) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fb89117f5689..11191611f0e9 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -76,7 +76,7 @@ impl MummyItem for Hash { } } -/// PartialPath keeps a list of nibbles to represent a path on the MPT. +/// PartialPath keeps a list of nibbles to represent a path on the Trie. #[derive(PartialEq, Eq, Clone)] pub struct PartialPath(Vec); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index a441ec0aaf03..ee60196e1e4a 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -50,7 +50,7 @@ pub struct Options { default_value_t = 262144, value_name = "PAYLOAD_NCACHED_PAGES", help = "Maximum cached pages for the item stash. This is the low-level cache used by the linear - space that holds MPT nodes and account objects." + space that holds trie nodes and account objects." )] pub payload_ncached_pages: usize, @@ -110,7 +110,7 @@ pub struct Options { required = false, default_value_t = 1 << 20, value_name = "REV_MERKLE_NCACHED", - help = "Config for accessing a version of the DB. Maximum cached MPT objects.")] + help = "Config for accessing a version of the DB. Maximum cached trie objects.")] merkle_ncached_objs: usize, #[arg( From f5159c10f9658625a7da0f9c1cb6c9ff388ac023 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 12:28:51 -0700 Subject: [PATCH 0116/1053] fix publish (#10) --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 605fae1cd1b4..f8981230b80c 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,4 +20,4 @@ jobs: continue-on-error: true run: | cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish + cargo publish -p firewood From 8d466535509a44cd10ec0b8c5987a9897fed0658 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Wed, 12 Apr 2023 13:33:26 -0700 Subject: [PATCH 0117/1053] Add note about vendored crates (#11) --- CODEOWNERS | 2 +- README.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7523ae731747..fea89b3e01f0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # CODEOWNERS -* @Determinant @exdx @xinifinity @gyuho @hexfusion @rkuris +* @exdx @xinifinity @gyuho @hexfusion @rkuris diff --git a/README.md b/README.md index 9ad8cc211247..a7dc06760e2c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,17 @@ values. When unused, there is no additional cost. firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). +## Vendored Crates +The following crates are vendored in this repository to allow for making +modifications without requiring upstream approval: +* [`growth-ring`](https://github.com/Determinant/growth-ring) +* [`libaio-futures`](https://github.com/Determinant/libaio-futures) +* [`shale`](https://github.com/Determinant/shale) + +These crates will either be heavily modified or removed prior to the production +launch of firewood. If they are retained, all changes made will be shared +upstream. + ## Roadmap ### Green Milestone This milestone will focus on additional code cleanup, including supporting From 7452c5529c51caf3fafdd1225f19018b814cf647 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 13 Apr 2023 15:39:36 -0700 Subject: [PATCH 0118/1053] Propogate errors in growth-ring (#18) --- firewood/src/storage.rs | 16 +++--- growth-ring/Cargo.toml | 3 +- growth-ring/examples/demo1.rs | 3 +- growth-ring/src/lib.rs | 34 +++++++------ growth-ring/src/wal.rs | 89 +++++++++++++++++---------------- growth-ring/src/walerror.rs | 18 +++++++ growth-ring/tests/common/mod.rs | 44 ++++++++-------- 7 files changed, 117 insertions(+), 90 deletions(-) create mode 100644 growth-ring/src/walerror.rs diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index bbe181c2dbbd..8aea96fcb2a4 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use aiofut::{AIOBuilder, AIOManager}; use growthring::{ - wal::{RecoverPolicy, WALLoader, WALWriter}, + wal::{RecoverPolicy, WALError, WALLoader, WALWriter}, WALStoreAIO, }; use nix::fcntl::{flock, FlockArg}; @@ -1011,11 +1011,12 @@ impl DiskBuffer { } } - async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), ()> { + async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), WALError> { let mut aiobuilder = AIOBuilder::default(); aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); - let aiomgr = aiobuilder.build().map_err(|_| ())?; - let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)).map_err(|_| ())?; + let aiomgr = aiobuilder.build().map_err(|_| WALError::Other)?; + let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)) + .map_err(|_| WALError::Other)?; let mut loader = WALLoader::new(); loader .file_nbit(self.wal_cfg.file_nbit) @@ -1038,11 +1039,14 @@ impl DiskBuffer { let file_mask = (1 << file_nbit) - 1; let fid = offset >> file_nbit; nix::sys::uio::pwrite( - file_pool.get_file(fid).map_err(|_| ())?.get_fd(), + file_pool + .get_file(fid) + .map_err(|_| WALError::Other)? + .get_fd(), &data, (offset & file_mask) as nix::libc::off_t, ) - .map_err(|_| ())?; + .map_err(|_| WALError::Other)?; } } Ok(()) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 4d41fe4a2fee..61ad2ad5ae6e 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -20,7 +20,8 @@ async-trait = "0.1.57" futures = "0.3.24" nix = "0.26.2" libc = "0.2.133" -bytemuck = "1.13.1" +bytemuck = {version = "1.13.1", features = ["derive"]} +thiserror = "1.0.40" [dev-dependencies] hex = "0.4.3" diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs index 2cc4f259e600..d9f3c38b0d53 100644 --- a/growth-ring/examples/demo1.rs +++ b/growth-ring/examples/demo1.rs @@ -1,6 +1,7 @@ use futures::executor::block_on; use growthring::{ wal::{WALBytes, WALLoader, WALRingId, WALWriter}, + walerror::WALError, WALStoreAIO, }; use rand::{seq::SliceRandom, Rng, SeedableRng}; @@ -15,7 +16,7 @@ fn test(records: Vec, wal: &mut WALWriter) -> Vec Result<(), ()> { +fn recover(payload: WALBytes, ringid: WALRingId) -> Result<(), WALError> { println!( "recover(payload={}, ringid={:?}", std::str::from_utf8(&payload).unwrap(), diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 9f44432bffce..bb36f48bfa97 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -48,6 +48,7 @@ #[macro_use] extern crate scan_fmt; pub mod wal; +pub mod walerror; use aiofut::{AIOBuilder, AIOManager}; use async_trait::async_trait; @@ -61,6 +62,7 @@ use nix::unistd::{close, ftruncate, mkdir, unlinkat, UnlinkatFlags}; use std::os::unix::io::RawFd; use std::sync::Arc; use wal::{WALBytes, WALFile, WALPos, WALStore}; +use walerror::WALError; pub struct WALFileAIO { fd: RawFd, @@ -70,7 +72,7 @@ pub struct WALFileAIO { #[allow(clippy::result_unit_err)] // TODO: Refactor to return a meaningful error. impl WALFileAIO { - pub fn new(rootfd: RawFd, filename: &str, aiomgr: Arc) -> Result { + pub fn new(rootfd: RawFd, filename: &str, aiomgr: Arc) -> Result { openat( rootfd, filename, @@ -78,7 +80,7 @@ impl WALFileAIO { Mode::S_IRUSR | Mode::S_IWUSR, ) .map(|fd| WALFileAIO { fd, aiomgr }) - .map_err(|_| ()) + .map_err(From::from) } } @@ -91,7 +93,7 @@ impl Drop for WALFileAIO { #[async_trait(?Send)] impl WALFile for WALFileAIO { #[cfg(target_os = "linux")] - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()> { + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError> { // TODO: is there any async version of fallocate? return fallocate( self.fd, @@ -100,32 +102,32 @@ impl WALFile for WALFileAIO { length as off_t, ) .map(|_| ()) - .map_err(|_| ()); + .map_err(Into::into); } #[cfg(not(target_os = "linux"))] // TODO: macos support is possible here, but possibly unnecessary - async fn allocate(&self, _offset: WALPos, _length: usize) -> Result<(), ()> { + async fn allocate(&self, _offset: WALPos, _length: usize) -> Result<(), WALError> { Ok(()) } - fn truncate(&self, length: usize) -> Result<(), ()> { - ftruncate(self.fd, length as off_t).map_err(|_| ()) + async fn truncate(&self, length: usize) -> Result<(), WALError> { + ftruncate(self.fd, length as off_t).map_err(From::from) } - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()> { + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError> { let (res, data) = self.aiomgr.write(self.fd, offset, data, None).await; - res.map_err(|_| ()).and_then(|nwrote| { + res.map_err(Into::into).and_then(|nwrote| { if nwrote == data.len() { Ok(()) } else { - Err(()) + Err(WALError::Other) } }) } - async fn read(&self, offset: WALPos, length: usize) -> Result, ()> { + async fn read(&self, offset: WALPos, length: usize) -> Result, WALError> { let (res, data) = self.aiomgr.read(self.fd, offset, length, None).await; - res.map_err(|_| ()) + res.map_err(From::from) .map(|nread| if nread == length { Some(data) } else { None }) } } @@ -205,22 +207,22 @@ pub fn oflags() -> OFlag { impl WALStore for WALStoreAIO { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, _touch: bool) -> Result, ()> { + async fn open_file(&self, filename: &str, _touch: bool) -> Result, WALError> { let filename = filename.to_string(); WALFileAIO::new(self.rootfd, &filename, self.aiomgr.clone()) .map(|f| Box::new(f) as Box) } - async fn remove_file(&self, filename: String) -> Result<(), ()> { + async fn remove_file(&self, filename: String) -> Result<(), WALError> { unlinkat( Some(self.rootfd), filename.as_str(), UnlinkatFlags::NoRemoveDir, ) - .map_err(|_| ()) + .map_err(From::from) } - fn enumerate_files(&self) -> Result { + fn enumerate_files(&self) -> Result { let mut logfiles = Vec::new(); for ent in nix::dir::Dir::openat(self.rootfd, "./", OFlag::empty(), Mode::empty()) .unwrap() diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index e62625a41aba..f643698948ed 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -13,6 +13,8 @@ use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::pin::Pin; +pub use crate::walerror::WALError; + const FILENAME_FMT: &str = r"[0-9a-f]+\.log"; enum WALRingType { @@ -171,17 +173,17 @@ struct WALState { #[async_trait(?Send)] pub trait WALFile { /// Initialize the file space in [offset, offset + length) to zero. - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()>; + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError>; /// Write data with offset. We assume all previous `allocate`/`truncate` invocations are visible /// if ordered earlier (should be guaranteed by most OS). Additionally, the write caused /// by each invocation of this function should be _atomic_ (the entire single write should be /// all or nothing). - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()>; + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError>; /// Read data with offset. Return `Ok(None)` when it reaches EOF. - async fn read(&self, offset: WALPos, length: usize) -> Result, ()>; + async fn read(&self, offset: WALPos, length: usize) -> Result, WALError>; /// Truncate a file to a specified length. #[allow(clippy::result_unit_err)] - fn truncate(&self, length: usize) -> Result<(), ()>; + async fn truncate(&self, length: usize) -> Result<(), WALError>; } #[async_trait(?Send)] @@ -189,13 +191,13 @@ pub trait WALStore { type FileNameIter: Iterator; /// Open a file given the filename, create the file if not exists when `touch` is `true`. - async fn open_file(&self, filename: &str, touch: bool) -> Result, ()>; + async fn open_file(&self, filename: &str, touch: bool) -> Result, WALError>; /// Unlink a file given the filename. - async fn remove_file(&self, filename: String) -> Result<(), ()>; + async fn remove_file(&self, filename: String) -> Result<(), WALError>; /// Enumerate all WAL filenames. It should include all WAL files that are previously opened /// (created) but not removed. The list could be unordered. #[allow(clippy::result_unit_err)] - fn enumerate_files(&self) -> Result; + fn enumerate_files(&self) -> Result; } struct WALFileHandle<'a, F: WALStore> { @@ -228,9 +230,9 @@ struct WALFilePool { #[allow(clippy::type_complexity)] handle_used: RefCell, usize)>>>, #[allow(clippy::type_complexity)] - last_write: UnsafeCell>>>>>, + last_write: UnsafeCell>>>>>, #[allow(clippy::type_complexity)] - last_peel: UnsafeCell>>>>>, + last_peel: UnsafeCell>>>>>, file_nbit: u64, file_size: u64, block_nbit: u64, @@ -242,11 +244,11 @@ impl WALFilePool { file_nbit: u64, block_nbit: u64, cache_size: NonZeroUsize, - ) -> Result { + ) -> Result { let file_nbit = file_nbit; let block_nbit = block_nbit; let header_file = store.open_file("HEAD", true).await?; - header_file.truncate(HEADER_SIZE)?; + header_file.truncate(HEADER_SIZE).await?; Ok(WALFilePool { store, header_file, @@ -260,14 +262,14 @@ impl WALFilePool { }) } - async fn read_header(&self) -> Result { + async fn read_header(&self) -> Result { let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); let header: Header = cast_slice(&bytes)[0]; Ok(header) } - async fn write_header(&self, header: &Header) -> Result<(), ()> { + async fn write_header(&self, header: &Header) -> Result<(), WALError> { let base = header as *const Header as usize as *const u8; let bytes = unsafe { std::slice::from_raw_parts(base, HEADER_SIZE) }; self.header_file.write(0, bytes.into()).await?; @@ -276,7 +278,7 @@ impl WALFilePool { #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to remove mutable reference from being awaited. - async fn get_file(&self, fid: u64, touch: bool) -> Result, ()> { + async fn get_file(&self, fid: u64, touch: bool) -> Result, WALError> { let pool = self as *const WALFilePool; if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { @@ -325,7 +327,7 @@ impl WALFilePool { fn write<'a>( &'a mut self, writes: Vec<(WALPos, WALBytes)>, - ) -> Vec> + 'a>>> { + ) -> Vec> + 'a>>> { if writes.is_empty() { return Vec::new(); } @@ -350,7 +352,7 @@ impl WALFilePool { let alloc = async move { last_write.await?; let mut last_h: Option< - Pin, ()>> + 'a>>, + Pin, WALError>> + 'a>>, > = None; for ((next_fid, wl), h) in meta.into_iter().zip(files.into_iter()) { if let Some(lh) = last_h.take() { @@ -403,13 +405,13 @@ impl WALFilePool { &'a mut self, state: &mut WALState, keep_nrecords: u32, - ) -> impl Future> + 'a { + ) -> impl Future> + 'a { let last_peel = unsafe { std::mem::replace(&mut *self.last_peel.get(), std::mem::MaybeUninit::uninit()) .assume_init() }; - let mut removes: Vec>>>> = Vec::new(); + let mut removes: Vec>>>> = Vec::new(); while state.pending_removal.len() > 1 { let (fid, counter) = state.pending_removal.front().unwrap(); if counter_lt(counter + keep_nrecords, state.counter) { @@ -627,7 +629,7 @@ impl WALWriter { &mut self, records: T, keep_nrecords: u32, - ) -> Result<(), ()> { + ) -> Result<(), WALError> { let msize = self.msize as u64; let block_size = self.block_size as u64; let state = &mut self.state; @@ -670,7 +672,7 @@ impl WALWriter { &'a self, nrecords: usize, recover_policy: &RecoverPolicy, - ) -> Result, ()> { + ) -> Result, WALError> { let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); let file_pool = &self.file_pool; let file_nbit = file_pool.file_nbit; @@ -699,21 +701,21 @@ impl WALWriter { rings.push(ring); } for ring in rings.into_iter().rev() { - let ring = ring.map_err(|_| ())?; + let ring = ring.map_err(|_| WALError::Other)?; let (header, payload) = ring; let payload = payload.unwrap(); match header.rtype.try_into() { Ok(WALRingType::Full) => { assert!(chunks.is_none()); if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(()); + return Err(WALError::InvalidChecksum); } off += header.rsize as u64; records.push(payload); } Ok(WALRingType::First) => { if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(()); + return Err(WALError::InvalidChecksum); } if let Some(mut chunks) = chunks.take() { chunks.push(payload); @@ -743,7 +745,7 @@ impl WALWriter { } Ok(WALRingType::Null) => break, Err(_) => match recover_policy { - RecoverPolicy::Strict => return Err(()), + RecoverPolicy::Strict => return Err(WALError::Other), RecoverPolicy::BestEffort => break 'outer, }, } @@ -811,18 +813,18 @@ impl WALLoader { self } - fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { + fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { if checksum == CRC32.checksum(data) { Ok(true) } else { match p { - RecoverPolicy::Strict => Err(()), + RecoverPolicy::Strict => Err(WALError::Other), RecoverPolicy::BestEffort => Ok(false), } } } - fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { + fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { Self::verify_checksum_(data, checksum, &self.recover_policy) } @@ -916,7 +918,7 @@ impl WALLoader { Some(check!(check!( v.file.read(v.off, header.rsize as usize).await ) - .ok_or(()))) + .ok_or(WALError::Other))) } else { None }; @@ -1018,8 +1020,8 @@ impl WALLoader { match header.rtype.try_into() { Ok(WALRingType::Full) => { assert!(v.chunks.is_none()); - let payload = - check!(check!(v.file.read(v.off, rsize as usize).await).ok_or(())); + let payload = check!(check!(v.file.read(v.off, rsize as usize).await) + .ok_or(WALError::Other)); // TODO: improve the behavior when CRC32 fails if !check!(self.verify_checksum(&payload, header.crc32)) { die!() @@ -1037,8 +1039,8 @@ impl WALLoader { } Ok(WALRingType::First) => { assert!(v.chunks.is_none()); - let chunk = - check!(check!(v.file.read(v.off, rsize as usize).await).ok_or(())); + let chunk = check!(check!(v.file.read(v.off, rsize as usize).await) + .ok_or(WALError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } @@ -1050,8 +1052,8 @@ impl WALLoader { chunks, off, file, .. } = &mut *v; if let Some((chunks, _)) = chunks { - let chunk = - check!(check!(file.read(*off, rsize as usize).await).ok_or(())); + let chunk = check!(check!(file.read(*off, rsize as usize).await) + .ok_or(WALError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } @@ -1063,10 +1065,9 @@ impl WALLoader { let v_off = v.off; v.off += rsize as u64; if let Some((mut chunks, ringid_start)) = v.chunks.take() { - let chunk = check!(check!( - v.file.read(v_off, rsize as usize).await - ) - .ok_or(())); + let chunk = + check!(check!(v.file.read(v_off, rsize as usize).await) + .ok_or(WALError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } @@ -1105,12 +1106,12 @@ impl WALLoader { } /// Recover by reading the WAL files. - pub async fn load Result<(), ()>>( + pub async fn load Result<(), WALError>>( &self, store: S, mut recover_func: F, keep_nrecords: u32, - ) -> Result, ()> { + ) -> Result, WALError> { let msize = std::mem::size_of::(); assert!(self.file_nbit > self.block_nbit); assert!(msize < 1 << self.block_nbit); @@ -1152,7 +1153,7 @@ impl WALLoader { let (bytes, ring_id, _) = match res { Err(e) => { if e { - return Err(()); + return Err(WALError::Other); } else { break 'outer; } @@ -1170,7 +1171,7 @@ impl WALLoader { .collect() .await; for e in records.into_iter().rev() { - let (rec, _) = e.map_err(|_| ())?; + let (rec, _) = e.map_err(|_| WALError::Other)?; if rec.rtype == WALRingType::Full as u8 || rec.rtype == WALRingType::Last as u8 { counter = rec.counter + 1; break 'outer; @@ -1193,7 +1194,7 @@ impl WALLoader { let stream = Self::read_rings(&f, false, self.block_nbit, &self.recover_policy); futures::pin_mut!(stream); while let Some(r) = stream.next().await { - last = Some(r.map_err(|_| ())?); + last = Some(r.map_err(|_| WALError::Other)?); } if let Some((last_rec, _)) = last { if !counter_lt(last_rec.counter + keep_nrecords, counter) { @@ -1204,7 +1205,7 @@ impl WALLoader { } } if !skip_remove { - f.truncate(0)?; + f.truncate(0).await?; file_pool.store.remove_file(fname).await?; } } diff --git a/growth-ring/src/walerror.rs b/growth-ring/src/walerror.rs new file mode 100644 index 000000000000..c22c8c4e6dfc --- /dev/null +++ b/growth-ring/src/walerror.rs @@ -0,0 +1,18 @@ +use nix::errno::Errno; +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum WALError { + #[error("an unclassified error has occurred")] + Other, + #[error("an OS error {0} has occurred")] + UnixError(#[from] Errno), + #[error("a checksum check has failed")] + InvalidChecksum, +} + +impl From for WALError { + fn from(value: i32) -> Self { + Self::UnixError(Errno::from_i32(value)) + } +} diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 2ccef66a856e..6bde605136b0 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -2,7 +2,7 @@ #[allow(dead_code)] use async_trait::async_trait; use futures::executor::block_on; -use growthring::wal::{WALBytes, WALFile, WALLoader, WALPos, WALRingId, WALStore}; +use growthring::wal::{WALBytes, WALError, WALFile, WALLoader, WALPos, WALRingId, WALStore}; use indexmap::{map::Entry, IndexMap}; use rand::Rng; use std::cell::RefCell; @@ -38,9 +38,9 @@ pub struct WALFileEmul { #[async_trait(?Send)] impl WALFile for WALFileEmul { - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), ()> { + async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } let offset = offset as usize; if offset + length > self.file.borrow().len() { @@ -52,26 +52,26 @@ impl WALFile for WALFileEmul { Ok(()) } - fn truncate(&self, length: usize) -> Result<(), ()> { + async fn truncate(&self, length: usize) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } self.file.borrow_mut().resize(length, 0); Ok(()) } - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), ()> { + async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } let offset = offset as usize; self.file.borrow_mut()[offset..offset + data.len()].copy_from_slice(&data); Ok(()) } - async fn read(&self, offset: WALPos, length: usize) -> Result, ()> { + async fn read(&self, offset: WALPos, length: usize) -> Result, WALError> { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } let offset = offset as usize; @@ -126,9 +126,9 @@ where { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, touch: bool) -> Result, ()> { + async fn open_file(&self, filename: &str, touch: bool) -> Result, WALError> { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } match self.state.borrow_mut().files.entry(filename.to_string()) { hash_map::Entry::Occupied(e) => Ok(Box::new(WALFileEmul { @@ -142,28 +142,28 @@ where fgen: self.fgen.clone(), })) } else { - Err(()) + Err(WALError::Other) } } } } - async fn remove_file(&self, filename: String) -> Result<(), ()> { + async fn remove_file(&self, filename: String) -> Result<(), WALError> { //println!("remove_file(filename={})", filename); if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } self.state .borrow_mut() .files .remove(&filename) - .ok_or(()) + .ok_or(WALError::Other) .map(|_| ()) } - fn enumerate_files(&self) -> Result { + fn enumerate_files(&self) -> Result { if self.fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } let mut logfiles = Vec::new(); for (fname, _) in self.state.borrow().files.iter() { @@ -495,13 +495,13 @@ impl PaintingSim { ops: &mut Vec, ringid_map: &mut HashMap, fgen: Rc, - ) -> Result<(), ()> { + ) -> Result<(), WALError> { let mut rng = ::seed_from_u64(self.seed); let mut wal = block_on(loader.load( WALStoreEmul::new(state, fgen.clone()), |_, _| { if fgen.next_fail() { - Err(()) + Err(WALError::Other) } else { Ok(()) } @@ -529,11 +529,11 @@ impl PaintingSim { .zip(pss_.into_iter()) .map(|(r, ps)| -> Result<_, _> { ops.push(ps); - let (rec, rid) = futures::executor::block_on(r)?; + let (rec, rid) = futures::executor::block_on(r).map_err(|_| WALError::Other)?; ringid_map.insert(rid, ops.len() - 1); Ok((rec, rid)) }) - .collect::, ()>>()?; + .collect::, WALError>>()?; // finish appending to WAL /* for rid in rids.iter() { @@ -548,7 +548,7 @@ impl PaintingSim { for _ in 0..rng.gen_range(1..self.k) { // storage I/O could fail if fgen.next_fail() { - return Err(()); + return Err(WALError::Other); } if let Some((fin_rid, _)) = canvas.rand_paint(&mut rng) { if let Some(rid) = fin_rid { From c2047560d9dc0ab2e298ab8cb63d96c592b7145d Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 13 Apr 2023 20:53:51 -0700 Subject: [PATCH 0119/1053] [locking] Get a revision while a batch is active (#17) --- firewood/examples/rev.rs | 24 +++++ firewood/src/db.rs | 186 ++++++++++++++++++++++----------------- 2 files changed, 130 insertions(+), 80 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 628989c3597b..daa95a097ca7 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -21,10 +21,34 @@ fn main() { "{}", hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) ); + let root_hash = *db.get_revision(1, None).unwrap().kv_root_hash().unwrap(); println!( "{}", hex::encode(*db.get_revision(2, None).unwrap().kv_root_hash().unwrap()) ); + let write = db.new_writebatch().kv_insert("k", vec![b'v']).unwrap(); + + // Get a revision while a batch is active. + println!( + "{}", + hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) + ); + assert_eq!( + root_hash, + *db.get_revision(1, None).unwrap().kv_root_hash().unwrap() + ); + + // Read the uncommitted value while the batch is still active. + let val = db.kv_get("k").unwrap(); + assert_eq!("v".as_bytes().to_vec(), val); + + write.commit(); + println!( + "{}", + hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) + ); + let val = db.kv_get("k").unwrap(); + assert_eq!("v".as_bytes().to_vec(), val); } { let db = DB::new("rev_db", &cfg.truncate(false).build()).unwrap(); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 571f9e08ee63..4e0048a03465 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -7,10 +7,11 @@ use std::fmt; use std::io::{Cursor, Write}; use std::path::Path; use std::rc::Rc; +use std::sync::Arc; use std::thread::JoinHandle; use bytemuck::{cast_slice, AnyBitPattern}; -use parking_lot::{Mutex, MutexGuard}; +use parking_lot::{Mutex, RwLock}; use primitive_types::U256; use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID}; use typed_builder::TypedBuilder; @@ -403,8 +404,6 @@ struct DBInner { disk_thread: Option>, staging: Universe>, cached: Universe>, - revisions: VecDeque>, - max_revisions: usize, } impl Drop for DBInner { @@ -416,11 +415,17 @@ impl Drop for DBInner { /// Firewood database handle. pub struct DB { - inner: Mutex, + inner: Arc>, + revisions: Arc>, payload_regn_nbit: u64, rev_cfg: DBRevConfig, } +pub struct DBRevInner { + inner: VecDeque>, + max_revisions: usize, +} + impl DB { /// Open a database. pub fn new>(db_path: P, cfg: &DBConfig) -> Result { @@ -664,15 +669,17 @@ impl DB { latest.flush_dirty().unwrap(); Ok(Self { - inner: Mutex::new(DBInner { + inner: Arc::new(RwLock::new(DBInner { latest, disk_thread, disk_requester, staging, cached, - revisions: VecDeque::new(), + })), + revisions: Arc::new(Mutex::new(DBRevInner { + inner: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, - }), + })), payload_regn_nbit: header.payload_regn_nbit, rev_cfg: cfg.rev.clone(), }) @@ -681,7 +688,8 @@ impl DB { /// Create a write batch. pub fn new_writebatch(&self) -> WriteBatch { WriteBatch { - m: self.inner.lock(), + m: Arc::clone(&self.inner), + r: Arc::clone(&self.revisions), root_hash_recalc: true, committed: false, } @@ -689,28 +697,28 @@ impl DB { /// Dump the Trie of the latest generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { - self.inner.lock().latest.kv_dump(w) + self.inner.read().latest.kv_dump(w) } /// Dump the Trie of the latest entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { - self.inner.lock().latest.dump(w) + self.inner.read().latest.dump(w) } /// Dump the Trie of the latest state storage under an account. pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { - self.inner.lock().latest.dump_account(key, w) + self.inner.read().latest.dump_account(key, w) } /// Get root hash of the latest generic key-value storage. pub fn kv_root_hash(&self) -> Result { - self.inner.lock().latest.kv_root_hash() + self.inner.read().latest.kv_root_hash() } /// Get a value in the kv store associated with a particular key. pub fn kv_get>(&self, key: K) -> Result, DBError> { self.inner - .lock() + .read() .latest .kv_get(key) .ok_or(DBError::KeyNotFound) @@ -718,32 +726,32 @@ impl DB { /// Get root hash of the latest world state of all accounts. pub fn root_hash(&self) -> Result { - self.inner.lock().latest.root_hash() + self.inner.read().latest.root_hash() } /// Get the latest balance of the account. pub fn get_balance>(&self, key: K) -> Result { - self.inner.lock().latest.get_balance(key) + self.inner.read().latest.get_balance(key) } /// Get the latest code of the account. pub fn get_code>(&self, key: K) -> Result, DBError> { - self.inner.lock().latest.get_code(key) + self.inner.read().latest.get_code(key) } /// Get the latest nonce of the account. pub fn get_nonce>(&self, key: K) -> Result { - self.inner.lock().latest.get_nonce(key) + self.inner.read().latest.get_nonce(key) } /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { - self.inner.lock().latest.get_state(key, sub_key) + self.inner.read().latest.get_state(key, sub_key) } /// Check if the account exists in the latest world state. pub fn exist>(&self, key: K) -> Result { - self.inner.lock().latest.exist(key) + self.inner.read().latest.exist(key) } /// Get a handle that grants the access to some historical state of the entire DB. @@ -755,10 +763,11 @@ impl DB { /// If nback equals 0, or is above the configured maximum number of revisions, this function returns None. /// It also returns None in the case where the nback is larger than the number of revisions available. pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { - let mut inner = self.inner.lock(); + let mut revisions = self.revisions.lock(); + let inner = self.inner.read(); - let rlen = inner.revisions.len(); - if nback == 0 || nback > inner.max_revisions { + let rlen = revisions.inner.len(); + if nback == 0 || nback > revisions.max_revisions { return None; } if rlen < nback { @@ -769,11 +778,11 @@ impl DB { a.old.reverse() } - let u = match inner.revisions.back() { + let u = match revisions.inner.back() { Some(u) => u.to_mem_store_r(), None => inner.cached.to_mem_store_r(), }; - inner.revisions.push_back(u.rewind( + revisions.inner.push_back(u.rewind( &ash.0[&MERKLE_META_SPACE].old, &ash.0[&MERKLE_PAYLOAD_SPACE].old, &ash.0[&BLOB_META_SPACE].old, @@ -781,9 +790,10 @@ impl DB { )); } } - if inner.revisions.len() < nback { + if revisions.inner.len() < nback { return None; } + drop(inner); // set up the storage layout let mut offset = std::mem::size_of::() as u64; @@ -797,7 +807,7 @@ impl DB { // Blob CompactSpaceHeader starts right in blob meta space let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let space = &inner.revisions[nback - 1]; + let space = &revisions.inner[nback - 1]; let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { let merkle_meta_ref = &space.merkle.meta; @@ -839,9 +849,7 @@ impl DB { self.payload_regn_nbit, ) .unwrap(); - Some(Revision { - _m: inner, rev: DBRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), @@ -852,12 +860,11 @@ impl DB { } /// Lock protected handle to a readable version of the DB. -pub struct Revision<'a> { - _m: MutexGuard<'a, DBInner>, +pub struct Revision { rev: DBRev, } -impl<'a> std::ops::Deref for Revision<'a> { +impl std::ops::Deref for Revision { type Target = DBRev; fn deref(&self) -> &DBRev { &self.rev @@ -865,31 +872,36 @@ impl<'a> std::ops::Deref for Revision<'a> { } /// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself -/// because when an error occurs, the write batch will be automaticlaly aborted so that the DB +/// because when an error occurs, the write batch will be automatically aborted so that the DB /// remains clean. -pub struct WriteBatch<'a> { - m: MutexGuard<'a, DBInner>, +pub struct WriteBatch { + m: Arc>, + r: Arc>, root_hash_recalc: bool, committed: bool, } -impl<'a> WriteBatch<'a> { +impl WriteBatch { /// Insert an item to the generic key-value storage. - pub fn kv_insert>(mut self, key: K, val: Vec) -> Result { - let (header, merkle, _) = self.m.latest.borrow_split(); + pub fn kv_insert>(self, key: K, val: Vec) -> Result { + let mut rev = self.m.write(); + let (header, merkle, _) = rev.latest.borrow_split(); merkle .insert(key, val, header.kv_root) .map_err(DBError::Merkle)?; + drop(rev); Ok(self) } /// Remove an item from the generic key-value storage. `val` will be set to the value that is /// removed from the storage if it exists. - pub fn kv_remove>(mut self, key: K) -> Result<(Self, Option>), DBError> { - let (header, merkle, _) = self.m.latest.borrow_split(); + pub fn kv_remove>(self, key: K) -> Result<(Self, Option>), DBError> { + let mut rev = self.m.write(); + let (header, merkle, _) = rev.latest.borrow_split(); let old_value = merkle .remove(key, header.kv_root) .map_err(DBError::Merkle)?; + drop(rev); Ok((self, old_value)) } @@ -898,7 +910,8 @@ impl<'a> WriteBatch<'a> { key: &[u8], modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>, ) -> Result<(), DBError> { - let (header, merkle, blob) = self.m.latest.borrow_split(); + let mut rev = self.m.write(); + let (header, merkle, blob) = rev.latest.borrow_split(); match merkle.get_mut(key, header.acc_root) { Ok(Some(mut bytes)) => { let mut ret = Ok(()); @@ -964,8 +977,9 @@ impl<'a> WriteBatch<'a> { } /// Set the state value indexed by `sub_key` in the account indexed by `key`. - pub fn set_state(mut self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { - let (header, merkle, _) = self.m.latest.borrow_split(); + pub fn set_state(self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { + let mut rev = self.m.write(); + let (header, merkle, _) = rev.latest.borrow_split(); let mut acc = match merkle.get(key, header.acc_root) { Ok(Some(r)) => Account::deserialize(&r), Ok(None) => Account::default(), @@ -983,12 +997,14 @@ impl<'a> WriteBatch<'a> { merkle .insert(key, acc.serialize(), header.acc_root) .map_err(DBError::Merkle)?; + drop(rev); Ok(self) } /// Create an account. - pub fn create_account(mut self, key: &[u8]) -> Result { - let (header, merkle, _) = self.m.latest.borrow_split(); + pub fn create_account(self, key: &[u8]) -> Result { + let mut rev = self.m.write(); + let (header, merkle, _) = rev.latest.borrow_split(); let old_balance = match merkle.get_mut(key, header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance, Ok(None) => U256::zero(), @@ -1001,20 +1017,20 @@ impl<'a> WriteBatch<'a> { merkle .insert(key, acc.serialize(), header.acc_root) .map_err(DBError::Merkle)?; + + drop(rev); Ok(self) } /// Delete an account. - pub fn delete_account( - mut self, - key: &[u8], - acc: &mut Option, - ) -> Result { - let (header, merkle, blob_stash) = self.m.latest.borrow_split(); + pub fn delete_account(self, key: &[u8], acc: &mut Option) -> Result { + let mut rev = self.m.write(); + let (header, merkle, blob_stash) = rev.latest.borrow_split(); let mut a = match merkle.remove(key, header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => { *acc = None; + drop(rev); return Ok(self); } Err(e) => return Err(DBError::Merkle(e)), @@ -1028,6 +1044,7 @@ impl<'a> WriteBatch<'a> { a.code = ObjPtr::null(); } *acc = Some(a); + drop(rev); Ok(self) } @@ -1042,28 +1059,33 @@ impl<'a> WriteBatch<'a> { /// either retained on disk or lost together during a crash. pub fn commit(mut self) { use crate::storage::BufferWrite; - let inner = &mut *self.m; + let mut rev_inner = self.m.write(); if self.root_hash_recalc { - inner.latest.root_hash().ok(); - inner.latest.kv_root_hash().ok(); + rev_inner.latest.root_hash().ok(); + rev_inner.latest.kv_root_hash().ok(); } // clear the staging layer and apply changes to the CachedSpace - inner.latest.flush_dirty().unwrap(); + rev_inner.latest.flush_dirty().unwrap(); let (merkle_payload_pages, merkle_payload_plain) = - inner.staging.merkle.payload.take_delta(); - let (merkle_meta_pages, merkle_meta_plain) = inner.staging.merkle.meta.take_delta(); - let (blob_payload_pages, blob_payload_plain) = inner.staging.blob.payload.take_delta(); - let (blob_meta_pages, blob_meta_plain) = inner.staging.blob.meta.take_delta(); + rev_inner.staging.merkle.payload.take_delta(); + let (merkle_meta_pages, merkle_meta_plain) = rev_inner.staging.merkle.meta.take_delta(); + let (blob_payload_pages, blob_payload_plain) = rev_inner.staging.blob.payload.take_delta(); + let (blob_meta_pages, blob_meta_plain) = rev_inner.staging.blob.meta.take_delta(); - let old_merkle_meta_delta = inner.cached.merkle.meta.update(&merkle_meta_pages).unwrap(); - let old_merkle_payload_delta = inner + let old_merkle_meta_delta = rev_inner + .cached + .merkle + .meta + .update(&merkle_meta_pages) + .unwrap(); + let old_merkle_payload_delta = rev_inner .cached .merkle .payload .update(&merkle_payload_pages) .unwrap(); - let old_blob_meta_delta = inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); - let old_blob_payload_delta = inner + let old_blob_meta_delta = rev_inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); + let old_blob_payload_delta = rev_inner .cached .blob .payload @@ -1073,22 +1095,26 @@ impl<'a> WriteBatch<'a> { // update the rolling window of past revisions let new_base = Universe { merkle: SubUniverse::new( - StoreRevShared::from_delta(inner.cached.merkle.meta.clone(), old_merkle_meta_delta), StoreRevShared::from_delta( - inner.cached.merkle.payload.clone(), + rev_inner.cached.merkle.meta.clone(), + old_merkle_meta_delta, + ), + StoreRevShared::from_delta( + rev_inner.cached.merkle.payload.clone(), old_merkle_payload_delta, ), ), blob: SubUniverse::new( - StoreRevShared::from_delta(inner.cached.blob.meta.clone(), old_blob_meta_delta), + StoreRevShared::from_delta(rev_inner.cached.blob.meta.clone(), old_blob_meta_delta), StoreRevShared::from_delta( - inner.cached.blob.payload.clone(), + rev_inner.cached.blob.payload.clone(), old_blob_payload_delta, ), ), }; - if let Some(rev) = inner.revisions.front_mut() { + let mut revisions = self.r.lock(); + if let Some(rev) = revisions.inner.front_mut() { rev.merkle .meta .set_prev(new_base.merkle.meta.inner().clone()); @@ -1100,30 +1126,30 @@ impl<'a> WriteBatch<'a> { .payload .set_prev(new_base.blob.payload.inner().clone()); } - inner.revisions.push_front(new_base); - while inner.revisions.len() > inner.max_revisions { - inner.revisions.pop_back(); + revisions.inner.push_front(new_base); + while revisions.inner.len() > revisions.max_revisions { + revisions.inner.pop_back(); } self.committed = true; // schedule writes to the disk - inner.disk_requester.write( + rev_inner.disk_requester.write( vec![ BufferWrite { - space_id: inner.staging.merkle.payload.id(), + space_id: rev_inner.staging.merkle.payload.id(), delta: merkle_payload_pages, }, BufferWrite { - space_id: inner.staging.merkle.meta.id(), + space_id: rev_inner.staging.merkle.meta.id(), delta: merkle_meta_pages, }, BufferWrite { - space_id: inner.staging.blob.payload.id(), + space_id: rev_inner.staging.blob.payload.id(), delta: blob_payload_pages, }, BufferWrite { - space_id: inner.staging.blob.meta.id(), + space_id: rev_inner.staging.blob.meta.id(), delta: blob_meta_pages, }, ], @@ -1140,14 +1166,14 @@ impl<'a> WriteBatch<'a> { } } -impl<'a> Drop for WriteBatch<'a> { +impl Drop for WriteBatch { fn drop(&mut self) { if !self.committed { // drop the staging changes - self.m.staging.merkle.payload.take_delta(); - self.m.staging.merkle.meta.take_delta(); - self.m.staging.blob.payload.take_delta(); - self.m.staging.blob.meta.take_delta(); + self.m.read().staging.merkle.payload.take_delta(); + self.m.read().staging.merkle.meta.take_delta(); + self.m.read().staging.blob.payload.take_delta(); + self.m.read().staging.blob.meta.take_delta(); } } } From 47c5e6a78014cc5b6a245acf0ef4ada84f26fb64 Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 14 Apr 2023 10:21:50 -0400 Subject: [PATCH 0120/1053] fix: Update firewood sub-projects (#16) Signed-off-by: Dan Sover --- .github/workflows/publish.yaml | 15 +++++++++++++++ Cargo.toml | 6 +++--- {growth-ring => firewood-growth-ring}/Cargo.toml | 10 ++++------ {growth-ring => firewood-growth-ring}/README.rst | 0 .../examples/.gitignore | 0 .../examples/demo1.rs | 0 {growth-ring => firewood-growth-ring}/src/lib.rs | 2 +- {growth-ring => firewood-growth-ring}/src/wal.rs | 0 .../src/walerror.rs | 0 .../tests/common/mod.rs | 0 .../tests/rand_fail.rs | 0 {libaio-futures => firewood-libaio}/Cargo.toml | 8 +++----- {libaio-futures => firewood-libaio}/build.rs | 0 .../libaio/.gitignore | 0 .../libaio/COPYING | 0 .../libaio/Makefile | 0 .../libaio/aio_ring.h | 0 .../libaio/compat-0_1.c | 0 .../libaio/io_cancel.c | 0 .../libaio/io_destroy.c | 0 .../libaio/io_getevents.c | 0 .../libaio/io_pgetevents.c | 0 .../libaio/io_queue_init.c | 0 .../libaio/io_queue_release.c | 0 .../libaio/io_queue_run.c | 0 .../libaio/io_queue_wait.c | 0 .../libaio/io_setup.c | 0 .../libaio/io_submit.c | 0 .../libaio/libaio.h | 0 .../libaio/libaio.map | 0 .../libaio/raw_syscall.c | 0 .../libaio/syscall-alpha.h | 0 .../libaio/syscall-arm.h | 0 .../libaio/syscall-generic.h | 0 .../libaio/syscall-i386.h | 0 .../libaio/syscall-ia64.h | 0 .../libaio/syscall-ppc.h | 0 .../libaio/syscall-s390.h | 0 .../libaio/syscall-sparc.h | 0 .../libaio/syscall-x86_64.h | 0 .../libaio/syscall.h | 0 .../libaio/vsys_def.h | 0 {libaio-futures => firewood-libaio}/src/abi.rs | 0 {libaio-futures => firewood-libaio}/src/lib.rs | 0 .../tests/simple_test.rs | 0 {shale => firewood-shale}/Cargo.toml | 4 ++-- {shale => firewood-shale}/LICENSE | 0 {shale => firewood-shale}/benches/shale-bench.rs | 1 + {shale => firewood-shale}/src/compact.rs | 0 {shale => firewood-shale}/src/lib.rs | 0 {shale => firewood-shale}/src/util.rs | 0 firewood/Cargo.toml | 6 +++--- firewood/src/file.rs | 3 ++- firewood/src/lib.rs | 2 ++ firewood/src/storage.rs | 16 ++++++++++------ 55 files changed, 46 insertions(+), 27 deletions(-) rename {growth-ring => firewood-growth-ring}/Cargo.toml (73%) rename {growth-ring => firewood-growth-ring}/README.rst (100%) rename {growth-ring => firewood-growth-ring}/examples/.gitignore (100%) rename {growth-ring => firewood-growth-ring}/examples/demo1.rs (100%) rename {growth-ring => firewood-growth-ring}/src/lib.rs (99%) rename {growth-ring => firewood-growth-ring}/src/wal.rs (100%) rename {growth-ring => firewood-growth-ring}/src/walerror.rs (100%) rename {growth-ring => firewood-growth-ring}/tests/common/mod.rs (100%) rename {growth-ring => firewood-growth-ring}/tests/rand_fail.rs (100%) rename {libaio-futures => firewood-libaio}/Cargo.toml (69%) rename {libaio-futures => firewood-libaio}/build.rs (100%) rename {libaio-futures => firewood-libaio}/libaio/.gitignore (100%) rename {libaio-futures => firewood-libaio}/libaio/COPYING (100%) rename {libaio-futures => firewood-libaio}/libaio/Makefile (100%) rename {libaio-futures => firewood-libaio}/libaio/aio_ring.h (100%) rename {libaio-futures => firewood-libaio}/libaio/compat-0_1.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_cancel.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_destroy.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_getevents.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_pgetevents.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_queue_init.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_queue_release.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_queue_run.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_queue_wait.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_setup.c (100%) rename {libaio-futures => firewood-libaio}/libaio/io_submit.c (100%) rename {libaio-futures => firewood-libaio}/libaio/libaio.h (100%) rename {libaio-futures => firewood-libaio}/libaio/libaio.map (100%) rename {libaio-futures => firewood-libaio}/libaio/raw_syscall.c (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-alpha.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-arm.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-generic.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-i386.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-ia64.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-ppc.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-s390.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-sparc.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall-x86_64.h (100%) rename {libaio-futures => firewood-libaio}/libaio/syscall.h (100%) rename {libaio-futures => firewood-libaio}/libaio/vsys_def.h (100%) rename {libaio-futures => firewood-libaio}/src/abi.rs (100%) rename {libaio-futures => firewood-libaio}/src/lib.rs (100%) rename {libaio-futures => firewood-libaio}/tests/simple_test.rs (100%) rename {shale => firewood-shale}/Cargo.toml (89%) rename {shale => firewood-shale}/LICENSE (100%) rename {shale => firewood-shale}/benches/shale-bench.rs (96%) rename {shale => firewood-shale}/src/compact.rs (100%) rename {shale => firewood-shale}/src/lib.rs (100%) rename {shale => firewood-shale}/src/util.rs (100%) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index f8981230b80c..ff364e5de7e5 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -16,6 +16,21 @@ jobs: with: toolchain: stable override: true + - name: publish shale crate + continue-on-error: true + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-shale + - name: publish libaio crate + continue-on-error: true + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-libaio + - name: publish growth-ring crate + continue-on-error: true + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-growth-ring - name: publish firewood crate continue-on-error: true run: | diff --git a/Cargo.toml b/Cargo.toml index 5e168130918e..0742fcfcbda3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "growth-ring", - "libaio-futures", - "shale", + "firewood-growth-ring", + "firewood-libaio", + "firewood-shale", "firewood", "fwdctl", ] diff --git a/growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml similarity index 73% rename from growth-ring/Cargo.toml rename to firewood-growth-ring/Cargo.toml index 61ad2ad5ae6e..4825ac50160a 100644 --- a/growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -1,9 +1,7 @@ [package] -name = "growth-ring" -version = "0.3.0" -authors = ["Determinant "] -edition = "2018" -homepage = "https://github.com/Determinant/growth-ring" +name = "firewood-growth-ring" +version = "0.0.1" +edition = "2021" keywords = ["wal", "db", "futures"] license = "MIT" description = "Simple and modular write-ahead-logging implementation." @@ -11,7 +9,7 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libaio-futures = { version = "0.2.3", path = "../libaio-futures" } +firewood-libaio= { version = "0.0.1", path = "../firewood-libaio", package = "firewood-libaio" } crc = "3.0.0" lru = "0.10.0" scan_fmt = "0.2.6" diff --git a/growth-ring/README.rst b/firewood-growth-ring/README.rst similarity index 100% rename from growth-ring/README.rst rename to firewood-growth-ring/README.rst diff --git a/growth-ring/examples/.gitignore b/firewood-growth-ring/examples/.gitignore similarity index 100% rename from growth-ring/examples/.gitignore rename to firewood-growth-ring/examples/.gitignore diff --git a/growth-ring/examples/demo1.rs b/firewood-growth-ring/examples/demo1.rs similarity index 100% rename from growth-ring/examples/demo1.rs rename to firewood-growth-ring/examples/demo1.rs diff --git a/growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs similarity index 99% rename from growth-ring/src/lib.rs rename to firewood-growth-ring/src/lib.rs index bb36f48bfa97..1640ce19f3c0 100644 --- a/growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -50,8 +50,8 @@ extern crate scan_fmt; pub mod wal; pub mod walerror; -use aiofut::{AIOBuilder, AIOManager}; use async_trait::async_trait; +use firewood_libaio::{AIOBuilder, AIOManager}; use libc::off_t; #[cfg(target_os = "linux")] use nix::fcntl::{fallocate, open, openat, FallocateFlags, OFlag}; diff --git a/growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs similarity index 100% rename from growth-ring/src/wal.rs rename to firewood-growth-ring/src/wal.rs diff --git a/growth-ring/src/walerror.rs b/firewood-growth-ring/src/walerror.rs similarity index 100% rename from growth-ring/src/walerror.rs rename to firewood-growth-ring/src/walerror.rs diff --git a/growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs similarity index 100% rename from growth-ring/tests/common/mod.rs rename to firewood-growth-ring/tests/common/mod.rs diff --git a/growth-ring/tests/rand_fail.rs b/firewood-growth-ring/tests/rand_fail.rs similarity index 100% rename from growth-ring/tests/rand_fail.rs rename to firewood-growth-ring/tests/rand_fail.rs diff --git a/libaio-futures/Cargo.toml b/firewood-libaio/Cargo.toml similarity index 69% rename from libaio-futures/Cargo.toml rename to firewood-libaio/Cargo.toml index f33434995077..0e76dbcde5cd 100644 --- a/libaio-futures/Cargo.toml +++ b/firewood-libaio/Cargo.toml @@ -1,9 +1,7 @@ [package] -name = "libaio-futures" -version = "0.2.3" -authors = ["Determinant "] -edition = "2018" -homepage = "https://github.com/Determinant/libaio-futures" +name = "firewood-libaio" +version = "0.0.1" +edition = "2021" keywords = ["libaio", "aio", "async", "futures"] license = "MIT" description = "Straightforward Linux AIO using Futures/async/await." diff --git a/libaio-futures/build.rs b/firewood-libaio/build.rs similarity index 100% rename from libaio-futures/build.rs rename to firewood-libaio/build.rs diff --git a/libaio-futures/libaio/.gitignore b/firewood-libaio/libaio/.gitignore similarity index 100% rename from libaio-futures/libaio/.gitignore rename to firewood-libaio/libaio/.gitignore diff --git a/libaio-futures/libaio/COPYING b/firewood-libaio/libaio/COPYING similarity index 100% rename from libaio-futures/libaio/COPYING rename to firewood-libaio/libaio/COPYING diff --git a/libaio-futures/libaio/Makefile b/firewood-libaio/libaio/Makefile similarity index 100% rename from libaio-futures/libaio/Makefile rename to firewood-libaio/libaio/Makefile diff --git a/libaio-futures/libaio/aio_ring.h b/firewood-libaio/libaio/aio_ring.h similarity index 100% rename from libaio-futures/libaio/aio_ring.h rename to firewood-libaio/libaio/aio_ring.h diff --git a/libaio-futures/libaio/compat-0_1.c b/firewood-libaio/libaio/compat-0_1.c similarity index 100% rename from libaio-futures/libaio/compat-0_1.c rename to firewood-libaio/libaio/compat-0_1.c diff --git a/libaio-futures/libaio/io_cancel.c b/firewood-libaio/libaio/io_cancel.c similarity index 100% rename from libaio-futures/libaio/io_cancel.c rename to firewood-libaio/libaio/io_cancel.c diff --git a/libaio-futures/libaio/io_destroy.c b/firewood-libaio/libaio/io_destroy.c similarity index 100% rename from libaio-futures/libaio/io_destroy.c rename to firewood-libaio/libaio/io_destroy.c diff --git a/libaio-futures/libaio/io_getevents.c b/firewood-libaio/libaio/io_getevents.c similarity index 100% rename from libaio-futures/libaio/io_getevents.c rename to firewood-libaio/libaio/io_getevents.c diff --git a/libaio-futures/libaio/io_pgetevents.c b/firewood-libaio/libaio/io_pgetevents.c similarity index 100% rename from libaio-futures/libaio/io_pgetevents.c rename to firewood-libaio/libaio/io_pgetevents.c diff --git a/libaio-futures/libaio/io_queue_init.c b/firewood-libaio/libaio/io_queue_init.c similarity index 100% rename from libaio-futures/libaio/io_queue_init.c rename to firewood-libaio/libaio/io_queue_init.c diff --git a/libaio-futures/libaio/io_queue_release.c b/firewood-libaio/libaio/io_queue_release.c similarity index 100% rename from libaio-futures/libaio/io_queue_release.c rename to firewood-libaio/libaio/io_queue_release.c diff --git a/libaio-futures/libaio/io_queue_run.c b/firewood-libaio/libaio/io_queue_run.c similarity index 100% rename from libaio-futures/libaio/io_queue_run.c rename to firewood-libaio/libaio/io_queue_run.c diff --git a/libaio-futures/libaio/io_queue_wait.c b/firewood-libaio/libaio/io_queue_wait.c similarity index 100% rename from libaio-futures/libaio/io_queue_wait.c rename to firewood-libaio/libaio/io_queue_wait.c diff --git a/libaio-futures/libaio/io_setup.c b/firewood-libaio/libaio/io_setup.c similarity index 100% rename from libaio-futures/libaio/io_setup.c rename to firewood-libaio/libaio/io_setup.c diff --git a/libaio-futures/libaio/io_submit.c b/firewood-libaio/libaio/io_submit.c similarity index 100% rename from libaio-futures/libaio/io_submit.c rename to firewood-libaio/libaio/io_submit.c diff --git a/libaio-futures/libaio/libaio.h b/firewood-libaio/libaio/libaio.h similarity index 100% rename from libaio-futures/libaio/libaio.h rename to firewood-libaio/libaio/libaio.h diff --git a/libaio-futures/libaio/libaio.map b/firewood-libaio/libaio/libaio.map similarity index 100% rename from libaio-futures/libaio/libaio.map rename to firewood-libaio/libaio/libaio.map diff --git a/libaio-futures/libaio/raw_syscall.c b/firewood-libaio/libaio/raw_syscall.c similarity index 100% rename from libaio-futures/libaio/raw_syscall.c rename to firewood-libaio/libaio/raw_syscall.c diff --git a/libaio-futures/libaio/syscall-alpha.h b/firewood-libaio/libaio/syscall-alpha.h similarity index 100% rename from libaio-futures/libaio/syscall-alpha.h rename to firewood-libaio/libaio/syscall-alpha.h diff --git a/libaio-futures/libaio/syscall-arm.h b/firewood-libaio/libaio/syscall-arm.h similarity index 100% rename from libaio-futures/libaio/syscall-arm.h rename to firewood-libaio/libaio/syscall-arm.h diff --git a/libaio-futures/libaio/syscall-generic.h b/firewood-libaio/libaio/syscall-generic.h similarity index 100% rename from libaio-futures/libaio/syscall-generic.h rename to firewood-libaio/libaio/syscall-generic.h diff --git a/libaio-futures/libaio/syscall-i386.h b/firewood-libaio/libaio/syscall-i386.h similarity index 100% rename from libaio-futures/libaio/syscall-i386.h rename to firewood-libaio/libaio/syscall-i386.h diff --git a/libaio-futures/libaio/syscall-ia64.h b/firewood-libaio/libaio/syscall-ia64.h similarity index 100% rename from libaio-futures/libaio/syscall-ia64.h rename to firewood-libaio/libaio/syscall-ia64.h diff --git a/libaio-futures/libaio/syscall-ppc.h b/firewood-libaio/libaio/syscall-ppc.h similarity index 100% rename from libaio-futures/libaio/syscall-ppc.h rename to firewood-libaio/libaio/syscall-ppc.h diff --git a/libaio-futures/libaio/syscall-s390.h b/firewood-libaio/libaio/syscall-s390.h similarity index 100% rename from libaio-futures/libaio/syscall-s390.h rename to firewood-libaio/libaio/syscall-s390.h diff --git a/libaio-futures/libaio/syscall-sparc.h b/firewood-libaio/libaio/syscall-sparc.h similarity index 100% rename from libaio-futures/libaio/syscall-sparc.h rename to firewood-libaio/libaio/syscall-sparc.h diff --git a/libaio-futures/libaio/syscall-x86_64.h b/firewood-libaio/libaio/syscall-x86_64.h similarity index 100% rename from libaio-futures/libaio/syscall-x86_64.h rename to firewood-libaio/libaio/syscall-x86_64.h diff --git a/libaio-futures/libaio/syscall.h b/firewood-libaio/libaio/syscall.h similarity index 100% rename from libaio-futures/libaio/syscall.h rename to firewood-libaio/libaio/syscall.h diff --git a/libaio-futures/libaio/vsys_def.h b/firewood-libaio/libaio/vsys_def.h similarity index 100% rename from libaio-futures/libaio/vsys_def.h rename to firewood-libaio/libaio/vsys_def.h diff --git a/libaio-futures/src/abi.rs b/firewood-libaio/src/abi.rs similarity index 100% rename from libaio-futures/src/abi.rs rename to firewood-libaio/src/abi.rs diff --git a/libaio-futures/src/lib.rs b/firewood-libaio/src/lib.rs similarity index 100% rename from libaio-futures/src/lib.rs rename to firewood-libaio/src/lib.rs diff --git a/libaio-futures/tests/simple_test.rs b/firewood-libaio/tests/simple_test.rs similarity index 100% rename from libaio-futures/tests/simple_test.rs rename to firewood-libaio/tests/simple_test.rs diff --git a/shale/Cargo.toml b/firewood-shale/Cargo.toml similarity index 89% rename from shale/Cargo.toml rename to firewood-shale/Cargo.toml index ea4077fab38f..beb1dc1d4b05 100644 --- a/shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "shale" -version = "0.1.8" +name = "firewood-shale" +version = "0.0.1" edition = "2021" description = "Useful abstraction and light-weight implemenation for a key-value store." license = "MIT" diff --git a/shale/LICENSE b/firewood-shale/LICENSE similarity index 100% rename from shale/LICENSE rename to firewood-shale/LICENSE diff --git a/shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs similarity index 96% rename from shale/benches/shale-bench.rs rename to firewood-shale/benches/shale-bench.rs index 51080bf729c5..7e548d1fed3d 100644 --- a/shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -1,5 +1,6 @@ use bencher::{benchmark_group, benchmark_main, Bencher}; +extern crate firewood_shale as shale; use rand::Rng; use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr, PlainMem}; diff --git a/shale/src/compact.rs b/firewood-shale/src/compact.rs similarity index 100% rename from shale/src/compact.rs rename to firewood-shale/src/compact.rs diff --git a/shale/src/lib.rs b/firewood-shale/src/lib.rs similarity index 100% rename from shale/src/lib.rs rename to firewood-shale/src/lib.rs diff --git a/shale/src/util.rs b/firewood-shale/src/util.rs similarity index 100% rename from shale/src/util.rs rename to firewood-shale/src/util.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index ff5bc1799d54..423e42f55805 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -14,9 +14,9 @@ description = "Firewood is an embedded key-value store, optimized to store block license-file = "../LICENSE.md" homepage = "https://avalabs.org" [dependencies] -growth-ring = { version = "0.3.0", path = "../growth-ring" } -libaio-futures = {version = "0.2.2", path = "../libaio-futures" } -shale = { version = "0.1.7", path = "../shale" } +firewood-growth-ring = { version = "0.0.1", path = "../firewood-growth-ring", package = "firewood-growth-ring" } +firewood-libaio = {version = "0.0.1", path = "../firewood-libaio", package = "firewood-libaio" } +firewood-shale = { version = "0.0.1", path = "../firewood-shale", package = "firewood-shale" } enum-as-inner = "0.5.1" parking_lot = "0.12.1" rlp = "0.5.2" diff --git a/firewood/src/file.rs b/firewood/src/file.rs index c81f53988567..8910fe33d41a 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -6,7 +6,8 @@ pub(crate) use std::os::unix::io::RawFd as Fd; use std::path::Path; -use growthring::oflags; +extern crate firewood_growth_ring as growth_ring; +use growth_ring::oflags; use nix::errno::Errno; use nix::fcntl::{open, openat, OFlag}; use nix::sys::stat::Mode; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 3dc4077abaa1..ad127367fcc0 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -205,3 +205,5 @@ pub(crate) mod storage; pub mod api; pub mod service; + +extern crate firewood_shale as shale; diff --git a/firewood/src/storage.rs b/firewood/src/storage.rs index 8aea96fcb2a4..95dc6a44abb6 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage.rs @@ -11,13 +11,17 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; -use aiofut::{AIOBuilder, AIOManager}; -use growthring::{ +use firewood_libaio::{AIOBuilder, AIOManager}; + +extern crate firewood_growth_ring as growth_ring; +use growth_ring::{ wal::{RecoverPolicy, WALError, WALLoader, WALWriter}, WALStoreAIO, }; -use nix::fcntl::{flock, FlockArg}; + use shale::{MemStore, MemView, SpaceID}; + +use nix::fcntl::{flock, FlockArg}; use thiserror::Error; use tokio::sync::mpsc::error::SendError; use tokio::sync::oneshot::error::RecvError; @@ -75,8 +79,8 @@ impl Ash { #[derive(Debug)] pub struct AshRecord(pub HashMap); -impl growthring::wal::Record for AshRecord { - fn serialize(&self) -> growthring::wal::WALBytes { +impl growth_ring::wal::Record for AshRecord { + fn serialize(&self) -> growth_ring::wal::WALBytes { let mut bytes = Vec::new(); bytes.extend((self.0.len() as u64).to_le_bytes()); for (space_id, w) in self.0.iter() { @@ -95,7 +99,7 @@ impl growthring::wal::Record for AshRecord { impl AshRecord { #[allow(clippy::boxed_local)] - fn deserialize(raw: growthring::wal::WALBytes) -> Self { + fn deserialize(raw: growth_ring::wal::WALBytes) -> Self { let mut r = &raw[..]; let len = u64::from_le_bytes(r[..8].try_into().unwrap()); r = &r[8..]; From 3f6365541c05b31d63286171c1b74fb6e5c00071 Mon Sep 17 00:00:00 2001 From: exdx Date: Fri, 14 Apr 2023 10:54:54 -0400 Subject: [PATCH 0121/1053] ci: Fail in case of error publishing firewood crate (#21) Signed-off-by: Dan Sover --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ff364e5de7e5..27e4d7afde45 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -32,7 +32,7 @@ jobs: cargo login ${{ secrets.CARGO_TOKEN }} cargo publish -p firewood-growth-ring - name: publish firewood crate - continue-on-error: true + continue-on-error: false run: | cargo login ${{ secrets.CARGO_TOKEN }} cargo publish -p firewood From d40c626deb875a00fe674ab47cbdd7bb4a025b8e Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Fri, 14 Apr 2023 09:20:22 -0700 Subject: [PATCH 0122/1053] Remove old docs (#22) --- docs/COPYRIGHT.txt | 46 - docs/FiraSans-LICENSE.txt | 94 - docs/FiraSans-Medium.woff2 | Bin 132780 -> 0 bytes docs/FiraSans-Regular.woff2 | Bin 129188 -> 0 bytes docs/LICENSE-APACHE.txt | 201 - docs/LICENSE-MIT.txt | 23 - docs/NanumBarunGothic-LICENSE.txt | 99 - docs/NanumBarunGothic.ttf.woff2 | Bin 399468 -> 0 bytes docs/SourceCodePro-It.ttf.woff2 | Bin 44896 -> 0 bytes docs/SourceCodePro-LICENSE.txt | 93 - docs/SourceCodePro-Regular.ttf.woff2 | Bin 52228 -> 0 bytes docs/SourceCodePro-Semibold.ttf.woff2 | Bin 52348 -> 0 bytes docs/SourceSerif4-Bold.ttf.woff2 | Bin 81320 -> 0 bytes docs/SourceSerif4-It.ttf.woff2 | Bin 59860 -> 0 bytes docs/SourceSerif4-LICENSE.md | 93 - docs/SourceSerif4-Regular.ttf.woff2 | Bin 76180 -> 0 bytes docs/ayu.css | 1 - docs/clipboard.svg | 1 - docs/crates.js | 1 - docs/dark.css | 1 - docs/down-arrow.svg | 1 - docs/favicon-16x16.png | Bin 715 -> 0 bytes docs/favicon-32x32.png | Bin 1125 -> 0 bytes docs/favicon.svg | 24 - docs/firewood/all.html | 1 - docs/firewood/db/enum.DBError.html | 10 - docs/firewood/db/index.html | 3 - docs/firewood/db/sidebar-items.js | 1 - docs/firewood/db/struct.DB.html | 19 - docs/firewood/db/struct.DBConfig.html | 9 - docs/firewood/db/struct.DBRev.html | 16 - docs/firewood/db/struct.DBRevConfig.html | 9 - docs/firewood/db/struct.DiskBufferConfig.html | 9 - docs/firewood/db/struct.Revision.html | 16 - docs/firewood/db/struct.WALConfig.html | 8 - docs/firewood/db/struct.WriteBatch.html | 21 - docs/firewood/index.html | 196 - docs/firewood/merkle/enum.MerkleError.html | 10 - docs/firewood/merkle/index.html | 1 - docs/firewood/merkle/sidebar-items.js | 1 - docs/firewood/merkle/struct.Hash.html | 84 - docs/firewood/merkle/struct.IdTrans.html | 5 - docs/firewood/merkle/struct.Merkle.html | 11 - docs/firewood/merkle/struct.Node.html | 7 - docs/firewood/merkle/struct.Ref.html | 934 ----- docs/firewood/merkle/struct.RefMut.html | 5 - .../merkle/trait.ValueTransformer.html | 3 - docs/firewood/sidebar-items.js | 1 - .../storage/struct.DiskBufferConfig.html | 11 - docs/firewood/storage/struct.WALConfig.html | 11 - docs/implementors/core/clone/trait.Clone.js | 48 - docs/implementors/core/cmp/trait.Eq.js | 36 - docs/implementors/core/cmp/trait.PartialEq.js | 39 - docs/implementors/core/fmt/trait.Debug.js | 51 - docs/implementors/core/marker/trait.Freeze.js | 55 - docs/implementors/core/marker/trait.Send.js | 55 - .../core/marker/trait.StructuralEq.js | 29 - .../core/marker/trait.StructuralPartialEq.js | 32 - docs/implementors/core/marker/trait.Sync.js | 55 - docs/implementors/core/marker/trait.Unpin.js | 55 - .../core/ops/deref/trait.Deref.js | 17 - docs/implementors/core/ops/drop/trait.Drop.js | 24 - .../panic/unwind_safe/trait.RefUnwindSafe.js | 55 - .../panic/unwind_safe/trait.UnwindSafe.js | 55 - .../firewood/merkle/trait.ValueTransformer.js | 3 - docs/implementors/shale/trait.MummyItem.js | 4 - docs/index.html | 1 - docs/light.css | 1 - docs/main.js | 8 - docs/normalize.css | 2 - docs/noscript.css | 1 - docs/rust-logo.svg | 61 - docs/rustdoc.css | 1 - docs/search-index.js | 80 - docs/search.js | 1 - docs/settings.css | 1 - docs/settings.html | 1 - docs/settings.js | 11 - docs/source-files.js | 79 - docs/source-script.js | 1 - docs/src/firewood/account.rs.html | 338 -- docs/src/firewood/db.rs.html | 2020 ---------- docs/src/firewood/file.rs.html | 226 -- docs/src/firewood/lib.rs.html | 405 -- docs/src/firewood/merkle.rs.html | 3424 ----------------- docs/src/firewood/storage.rs.html | 2416 ------------ docs/storage.js | 1 - docs/toggle-minus.svg | 1 - docs/toggle-plus.svg | 1 - docs/wheel.svg | 1 - 90 files changed, 11775 deletions(-) delete mode 100644 docs/COPYRIGHT.txt delete mode 100644 docs/FiraSans-LICENSE.txt delete mode 100644 docs/FiraSans-Medium.woff2 delete mode 100644 docs/FiraSans-Regular.woff2 delete mode 100644 docs/LICENSE-APACHE.txt delete mode 100644 docs/LICENSE-MIT.txt delete mode 100644 docs/NanumBarunGothic-LICENSE.txt delete mode 100644 docs/NanumBarunGothic.ttf.woff2 delete mode 100644 docs/SourceCodePro-It.ttf.woff2 delete mode 100644 docs/SourceCodePro-LICENSE.txt delete mode 100644 docs/SourceCodePro-Regular.ttf.woff2 delete mode 100644 docs/SourceCodePro-Semibold.ttf.woff2 delete mode 100644 docs/SourceSerif4-Bold.ttf.woff2 delete mode 100644 docs/SourceSerif4-It.ttf.woff2 delete mode 100644 docs/SourceSerif4-LICENSE.md delete mode 100644 docs/SourceSerif4-Regular.ttf.woff2 delete mode 100644 docs/ayu.css delete mode 100644 docs/clipboard.svg delete mode 100644 docs/crates.js delete mode 100644 docs/dark.css delete mode 100644 docs/down-arrow.svg delete mode 100644 docs/favicon-16x16.png delete mode 100644 docs/favicon-32x32.png delete mode 100644 docs/favicon.svg delete mode 100644 docs/firewood/all.html delete mode 100644 docs/firewood/db/enum.DBError.html delete mode 100644 docs/firewood/db/index.html delete mode 100644 docs/firewood/db/sidebar-items.js delete mode 100644 docs/firewood/db/struct.DB.html delete mode 100644 docs/firewood/db/struct.DBConfig.html delete mode 100644 docs/firewood/db/struct.DBRev.html delete mode 100644 docs/firewood/db/struct.DBRevConfig.html delete mode 100644 docs/firewood/db/struct.DiskBufferConfig.html delete mode 100644 docs/firewood/db/struct.Revision.html delete mode 100644 docs/firewood/db/struct.WALConfig.html delete mode 100644 docs/firewood/db/struct.WriteBatch.html delete mode 100644 docs/firewood/index.html delete mode 100644 docs/firewood/merkle/enum.MerkleError.html delete mode 100644 docs/firewood/merkle/index.html delete mode 100644 docs/firewood/merkle/sidebar-items.js delete mode 100644 docs/firewood/merkle/struct.Hash.html delete mode 100644 docs/firewood/merkle/struct.IdTrans.html delete mode 100644 docs/firewood/merkle/struct.Merkle.html delete mode 100644 docs/firewood/merkle/struct.Node.html delete mode 100644 docs/firewood/merkle/struct.Ref.html delete mode 100644 docs/firewood/merkle/struct.RefMut.html delete mode 100644 docs/firewood/merkle/trait.ValueTransformer.html delete mode 100644 docs/firewood/sidebar-items.js delete mode 100644 docs/firewood/storage/struct.DiskBufferConfig.html delete mode 100644 docs/firewood/storage/struct.WALConfig.html delete mode 100644 docs/implementors/core/clone/trait.Clone.js delete mode 100644 docs/implementors/core/cmp/trait.Eq.js delete mode 100644 docs/implementors/core/cmp/trait.PartialEq.js delete mode 100644 docs/implementors/core/fmt/trait.Debug.js delete mode 100644 docs/implementors/core/marker/trait.Freeze.js delete mode 100644 docs/implementors/core/marker/trait.Send.js delete mode 100644 docs/implementors/core/marker/trait.StructuralEq.js delete mode 100644 docs/implementors/core/marker/trait.StructuralPartialEq.js delete mode 100644 docs/implementors/core/marker/trait.Sync.js delete mode 100644 docs/implementors/core/marker/trait.Unpin.js delete mode 100644 docs/implementors/core/ops/deref/trait.Deref.js delete mode 100644 docs/implementors/core/ops/drop/trait.Drop.js delete mode 100644 docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js delete mode 100644 docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js delete mode 100644 docs/implementors/firewood/merkle/trait.ValueTransformer.js delete mode 100644 docs/implementors/shale/trait.MummyItem.js delete mode 100644 docs/index.html delete mode 100644 docs/light.css delete mode 100644 docs/main.js delete mode 100644 docs/normalize.css delete mode 100644 docs/noscript.css delete mode 100644 docs/rust-logo.svg delete mode 100644 docs/rustdoc.css delete mode 100644 docs/search-index.js delete mode 100644 docs/search.js delete mode 100644 docs/settings.css delete mode 100644 docs/settings.html delete mode 100644 docs/settings.js delete mode 100644 docs/source-files.js delete mode 100644 docs/source-script.js delete mode 100644 docs/src/firewood/account.rs.html delete mode 100644 docs/src/firewood/db.rs.html delete mode 100644 docs/src/firewood/file.rs.html delete mode 100644 docs/src/firewood/lib.rs.html delete mode 100644 docs/src/firewood/merkle.rs.html delete mode 100644 docs/src/firewood/storage.rs.html delete mode 100644 docs/storage.js delete mode 100644 docs/toggle-minus.svg delete mode 100644 docs/toggle-plus.svg delete mode 100644 docs/wheel.svg diff --git a/docs/COPYRIGHT.txt b/docs/COPYRIGHT.txt deleted file mode 100644 index 34e48134cc34..000000000000 --- a/docs/COPYRIGHT.txt +++ /dev/null @@ -1,46 +0,0 @@ -These documentation pages include resources by third parties. This copyright -file applies only to those resources. The following third party resources are -included, and carry their own copyright notices and license terms: - -* Fira Sans (FiraSans-Regular.woff2, FiraSans-Medium.woff2): - - Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ - with Reserved Font Name Fira Sans. - - Copyright (c) 2014, Telefonica S.A. - - Licensed under the SIL Open Font License, Version 1.1. - See FiraSans-LICENSE.txt. - -* rustdoc.css, main.js, and playpen.js: - - Copyright 2015 The Rust Developers. - Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE.txt) or - the MIT license (LICENSE-MIT.txt) at your option. - -* normalize.css: - - Copyright (c) Nicolas Gallagher and Jonathan Neal. - Licensed under the MIT license (see LICENSE-MIT.txt). - -* Source Code Pro (SourceCodePro-Regular.ttf.woff2, - SourceCodePro-Semibold.ttf.woff2, SourceCodePro-It.ttf.woff2): - - Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), - with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark - of Adobe Systems Incorporated in the United States and/or other countries. - - Licensed under the SIL Open Font License, Version 1.1. - See SourceCodePro-LICENSE.txt. - -* Source Serif 4 (SourceSerif4-Regular.ttf.woff2, SourceSerif4-Bold.ttf.woff2, - SourceSerif4-It.ttf.woff2): - - Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name - 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United - States and/or other countries. - - Licensed under the SIL Open Font License, Version 1.1. - See SourceSerif4-LICENSE.md. - -This copyright file is intended to be distributed with rustdoc output. diff --git a/docs/FiraSans-LICENSE.txt b/docs/FiraSans-LICENSE.txt deleted file mode 100644 index ff9afab064a2..000000000000 --- a/docs/FiraSans-LICENSE.txt +++ /dev/null @@ -1,94 +0,0 @@ -Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. -with Reserved Font Name < Fira >, - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/FiraSans-Medium.woff2 b/docs/FiraSans-Medium.woff2 deleted file mode 100644 index 7a1e5fc548ef28137a32150b6aa50a568cd53d02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132780 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�|o|A<_G9#bJ&K$mXTSM@kZfZ^-ZYS)6EG~kHJ{! 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
    t5?f-0yF*m%K(ZzU5(G^0Gg?i8g+j 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

    TIWHPC<_9>em(Cvlhuf-BD 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 diff --git a/docs/FiraSans-Regular.woff2 b/docs/FiraSans-Regular.woff2 deleted file mode 100644 index e766e06ccb0d457fcdc8d4428efb796c7772a497..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129188 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

    )Thpg9J)x+@B(n?maf9&#`PQNHtKX<@|CQ`> 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 N#!G)s;2JO>~QPMdMZ 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

    xz5_8aF@(pQs@zgto+ItjS_s2^}<|M=lFQ2 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,qWUc(1+qXDfx7@=bp~~)(TqS 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 Psah$GwaNdR#Q^{S={DW} diff --git a/docs/SourceCodePro-It.ttf.woff2 b/docs/SourceCodePro-It.ttf.woff2 deleted file mode 100644 index 462c34efcd9d6b70b42359ca1a1d9476efe43eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44896 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 V7)~)yDZnpu49h_gZvJhn{{a>V-DCg& diff --git a/docs/SourceCodePro-LICENSE.txt b/docs/SourceCodePro-LICENSE.txt deleted file mode 100644 index 07542572e33b..000000000000 --- a/docs/SourceCodePro-LICENSE.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/SourceCodePro-Regular.ttf.woff2 b/docs/SourceCodePro-Regular.ttf.woff2 deleted file mode 100644 index 10b558e0b69a74b8329766fffbd5f64356c7230d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52228 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 z 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 D0=ucW diff --git a/docs/SourceSerif4-Bold.ttf.woff2 b/docs/SourceSerif4-Bold.ttf.woff2 deleted file mode 100644 index db57d21455c94e80aa7cca25cac803a08e1b01ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81320 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 N>ZPvCW|kqQ1ONsnDE9yW diff --git a/docs/SourceSerif4-It.ttf.woff2 b/docs/SourceSerif4-It.ttf.woff2 deleted file mode 100644 index 1cbc021a3aa22e1469542d531e9115fb78d497ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59860 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 diff --git a/docs/SourceSerif4-LICENSE.md b/docs/SourceSerif4-LICENSE.md deleted file mode 100644 index 68ea1892406c..000000000000 --- a/docs/SourceSerif4-LICENSE.md +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/SourceSerif4-Regular.ttf.woff2 b/docs/SourceSerif4-Regular.ttf.woff2 deleted file mode 100644 index 2db73fe2b49e8cfcaaf1d5311a610e730b8ed502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76180 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 diff --git a/docs/ayu.css b/docs/ayu.css deleted file mode 100644 index 55d0e436b71d..000000000000 --- a/docs/ayu.css +++ /dev/null @@ -1 +0,0 @@ - :root{--main-background-color:#0f1419;--main-color:#c5c5c5;--settings-input-color:#ffb454;--sidebar-background-color:#14191f;--sidebar-background-color-hover:rgba(70,70,70,0.33);--code-block-background-color:#191f26;--scrollbar-track-background-color:transparent;--scrollbar-thumb-background-color:#5c6773;--scrollbar-color:#5c6773 #24292f;--headings-border-bottom-color:#5c6773;--border-color:#5c6773;--button-background-color:#141920;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#5c6773;--copy-path-button-color:#fff;--copy-path-img-filter:invert(70%);--copy-path-img-hover-filter:invert(100%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ffa0a5;--trait-link-color:#39afd7;--assoc-item-link-color:#39afd7;--function-link-color:#fdd687;--macro-link-color:#a37acc;--keyword-link-color:#39afd7;--mod-link-color:#39afd7;--link-color:#39afd7;--sidebar-link-color:#53b1db;--sidebar-current-link-background-color:transparent;--search-result-link-focus-background-color:#3c3c3c;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--search-color:#fff;--code-highlight-kw-color:#ff7733;--code-highlight-kw-2-color:#ff7733;--code-highlight-lifetime-color:#ff7733;--code-highlight-prelude-color:#69f2df;--code-highlight-prelude-val-color:#ff7733;--code-highlight-number-color:#b8cc52;--code-highlight-string-color:#b8cc52;--code-highlight-literal-color:#ff7733;--code-highlight-attribute-color:#e6e1cf;--code-highlight-self-color:#36a3d9;--code-highlight-macro-color:#a37acc;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#788797;--code-highlight-doc-comment-color:#a1ac88;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}h1,h2,h3,h4{color:white;}h1 a{color:#fff;}h4{border:none;}.docblock code{color:#ffb454;}.code-header{color:#e6e1cf;}.docblock pre>code,pre>code{color:#e6e1cf;}.item-info code{color:#e6e1cf;}.docblock a>code{color:#39AFD7 !important;}pre,.rustdoc.source .example-wrap{color:#e6e1cf;}.rust-logo{filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);}.sidebar .current,.sidebar a:hover{color:#ffb44c;}.sidebar-elems .location{color:#ff7733;}.src-line-numbers span{color:#5c6773;}.src-line-numbers .line-highlighted{color:#708090;background-color:rgba(255,236,164,0.06);padding-right:4px;border-right:1px solid #ffb44c;}.search-results a:hover{color:#fff !important;background-color:#3c3c3c;}.search-results a:focus{color:#fff !important;background-color:#3c3c3c;}.search-results a{color:#0096cf;}.search-results a div.desc{color:#c5c5c5;}.content .item-info::before{color:#ccc;}.sidebar h2 a,.sidebar h3 a{color:white;}body.source .example-wrap pre.rust a{background:#333;}details.rustdoc-toggle>summary::before{filter:invert(100%);}#crate-search-div::after{filter:invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%);}#crate-search:hover,#crate-search:focus{border-color:#e0e0e0 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%);}.module-item .stab,.import-item .stab{color:#000;}.result-name .primitive>i,.result-name .keyword>i{color:#788797;}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{color:#5c67736e;border:none;}a.test-arrow{font-size:100%;color:#788797;border-radius:4px;background-color:rgba(57,175,215,0.09);}a.test-arrow:hover{background-color:rgba(57,175,215,0.368);color:#c5c5c5;}:target{background:rgba(255,236,164,0.06);border-right:3px solid rgba(255,180,76,0.85);}.search-failed a{color:#39AFD7;}.tooltip::after{background-color:#314559;color:#c5c5c5;}.tooltip::before{border-color:transparent #314559 transparent transparent;}.notable-traits-tooltiptext{background-color:#314559;}#titles>button.selected{background-color:#141920 !important;border-bottom:1px solid #ffb44c !important;border-top:none;}#titles>button:not(.selected){background-color:transparent !important;border:none;}#titles>button:hover{border-bottom:1px solid rgba(242,151,24,0.3);}#titles>button>div.count{color:#888;}pre.rust .lifetime{}pre.rust .kw{}#titles>button:hover,#titles>button.selected{}pre.rust .self,pre.rust .bool-val,pre.rust .prelude-val,pre.rust .attribute{}pre.rust .kw-2,pre.rust .prelude-ty{}kbd{color:#c5c5c5;background-color:#314559;box-shadow:inset 0 -1px 0 #5c6773;}#settings-menu>a,#help-button>a{color:#fff;}#settings-menu>a img{filter:invert(100);}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#e0e0e0;}.search-results .result-name span.alias{color:#c5c5c5;}.search-results .result-name span.grey{color:#999;}#source-sidebar>.title{color:#fff;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#14191f;color:#ffb44c;}#source-sidebar div.files>a.selected{background-color:#14191f;color:#ffb44c;}.scraped-example-list .scrape-help{border-color:#aaa;color:#eee;}.scraped-example-list .scrape-help:hover{border-color:white;color:white;}.scraped-example .example-wrap .rust span.highlight{background:rgb(91,59,1);}.scraped-example .example-wrap .rust span.highlight.focus{background:rgb(124,75,15);}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(15,20,25,1),rgba(15,20,25,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(15,20,25,1),rgba(15,20,25,0));}.toggle-line-inner{background:#999;}.toggle-line:hover .toggle-line-inner{background:#c5c5c5;} \ No newline at end of file diff --git a/docs/clipboard.svg b/docs/clipboard.svg deleted file mode 100644 index 8adbd9963048..000000000000 --- a/docs/clipboard.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/crates.js b/docs/crates.js deleted file mode 100644 index 2fa17f411555..000000000000 --- a/docs/crates.js +++ /dev/null @@ -1 +0,0 @@ -window.ALL_CRATES = ["ahash","aho_corasick","aiofut","async_trait","bincode","bitflags","block_buffer","byteorder","bytes","cfg_if","cpufeatures","crc","crc_catalog","crossbeam_channel","crossbeam_utils","crunchy","crypto_common","digest","enum_as_inner","firewood","fixed_hash","futures","futures_channel","futures_core","futures_executor","futures_io","futures_macro","futures_sink","futures_task","futures_util","generic_array","getrandom","growthring","hashbrown","heck","hex","impl_rlp","keccak","libc","lock_api","lru","memchr","memoffset","nix","once_cell","parking_lot","parking_lot_core","pin_project_lite","pin_utils","ppv_lite86","primitive_types","proc_macro2","quote","rand","rand_chacha","rand_core","regex","regex_syntax","rlp","rustc_hex","scan_fmt","scopeguard","serde","serde_derive","sha3","shale","slab","smallvec","static_assertions","syn","tokio","tokio_macros","typed_builder","typenum","uint","unicode_ident"]; \ No newline at end of file diff --git a/docs/dark.css b/docs/dark.css deleted file mode 100644 index 7f314acf1230..000000000000 --- a/docs/dark.css +++ /dev/null @@ -1 +0,0 @@ -:root{--main-background-color:#353535;--main-color:#ddd;--settings-input-color:#2196f3;--sidebar-background-color:#505050;--sidebar-background-color-hover:#676767;--code-block-background-color:#2A2A2A;--scrollbar-track-background-color:#717171;--scrollbar-thumb-background-color:rgba(32,34,37,.6);--scrollbar-color:rgba(32,34,37,.6) #5a5a5a;--headings-border-bottom-color:#d2d2d2;--border-color:#e0e0e0;--button-background-color:#f0f0f0;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#008dfd;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(65%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#2dbfb8;--trait-link-color:#b78cf2;--assoc-item-link-color:#d2991d;--function-link-color:#2bab63;--macro-link-color:#09bd00;--keyword-link-color:#d2991d;--mod-link-color:#d2991d;--link-color:#d2991d;--sidebar-link-color:#fdbf35;--sidebar-current-link-background-color:#444;--search-result-link-focus-background-color:#616161;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--search-color:#111;--code-highlight-kw-color:#ab8ac1;--code-highlight-kw-2-color:#769acb;--code-highlight-lifetime-color:#d97f26;--code-highlight-prelude-color:#769acb;--code-highlight-prelude-val-color:#ee6868;--code-highlight-number-color:#83a300;--code-highlight-string-color:#83a300;--code-highlight-literal-color:#ee6868;--code-highlight-attribute-color:#ee6868;--code-highlight-self-color:#ee6868;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8d8d8b;--code-highlight-doc-comment-color:#8ca375;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}.rust-logo{filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff)}.src-line-numbers span{color:#3B91E2;}.src-line-numbers .line-highlighted{background-color:#0a042f !important;}.content .item-info::before{color:#ccc;}body.source .example-wrap pre.rust a{background:#333;}details.rustdoc-toggle>summary::before{filter:invert(100%);}#crate-search-div::after{filter:invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);}#crate-search:hover,#crate-search:focus{border-color:#2196f3 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{border-color:#4a4949;}a.test-arrow{color:#dedede;background-color:rgba(78,139,202,0.2);}a.test-arrow:hover{background-color:#4e8bca;}:target{background-color:#494a3d;border-right:3px solid #bb7410;}.search-failed a{color:#0089ff;}.tooltip::after{background-color:#000;color:#fff;border-color:#000;}.tooltip::before{border-color:transparent black transparent transparent;}.notable-traits-tooltiptext{background-color:#111;}#titles>button:not(.selected){background-color:#252525;border-top-color:#252525;}#titles>button:hover,#titles>button.selected{border-top-color:#0089ff;background-color:#353535;}#titles>button>div.count{color:#888;}kbd{color:#000;background-color:#fafbfc;box-shadow:inset 0 -1px 0 #c6cbd1;}#settings-menu>a,#help-button>a{color:#000;}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#ffb900;}.search-results .result-name span.alias{color:#fff;}.search-results .result-name span.grey{color:#ccc;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#444;}#source-sidebar div.files>a.selected{background-color:#333;}.scraped-example-list .scrape-help{border-color:#aaa;color:#eee;}.scraped-example-list .scrape-help:hover{border-color:white;color:white;}.scraped-example .example-wrap .rust span.highlight{background:rgb(91,59,1);}.scraped-example .example-wrap .rust span.highlight.focus{background:rgb(124,75,15);}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(53,53,53,1),rgba(53,53,53,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(53,53,53,1),rgba(53,53,53,0));}.toggle-line-inner{background:#999;}.toggle-line:hover .toggle-line-inner{background:#c5c5c5;} \ No newline at end of file diff --git a/docs/down-arrow.svg b/docs/down-arrow.svg deleted file mode 100644 index 5d76a64e92c7..000000000000 --- a/docs/down-arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/favicon-16x16.png b/docs/favicon-16x16.png deleted file mode 100644 index ea4b45cae1618e6e20e6d61897da953f34b66b30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 715 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L9P|ym58ULAS1_A}yOFVsD*`G1;@(QxEEoEaloaX$3oUhmn3B1b>^ zXS8u>Ub4$r*u2F1FEiJ>rQJ&x%nC}Q!>*kacg*^JAVaGgCxj?;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1UT zt3o15f)dLW3X1a6GILTDN-7Id6*3D-k{K8(<~;ty!%-Nfp>fLp^cl~mK@7~w+k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SV3z!DH1*13q>1OJmp-&A)3@J_A;A0V7u0bkfJ)-`Krrb zGey1(YybBo_r#8#3kPrfo#tA4xb3R$F4j$a9H~9huUPE$JQDstTM~O>^@Ps(;>UH<3Fx0=LBZUre@8723HjVToWun1;~KVtGY7SF1E7liM5< zHa%KaZ1y+v;MF5PX0dXM#bh1)zI}?P`JCclPp)GktoyD%Uv%1HzQp-VwViVJr}FN4 zBVAi3pdsabJ2zzio=sD>mtWX++u%m3k>>5t|1&=?+*B*EnLW)#$^O=9J{D1Fvz#4w zCmkrSML-}_v8Imc2?OP1;|%KWmLM+u&^dKy+fI{C57UY0UhRg-3U_ zKl;3k)jRBCi*uZh#-8L8Gwj!FXV37syULEeYD%&1+S-jgUC&wB|>?y4oO5hW>!C8<`)MX5lF!N|bKNY}tn*U&h` z(Adh*+{(a0+rYrez#!Wq{4a`z-29Zxv`X9>q*C7l^C^QQ$cEtjw370~qEv?R@^Zb* zyzJuS#DY}4{G#;P?`))iio&ZxB1(c1%M}WW^3yVNQWZ)n3sMy_3rdn17%JvG{=~yk z7^b0d%K!8k&!<5Q%*xz)$=t%q!rqfbn1vNw8cYtSFe`5kQ8<0$%84Uqj>sHgKi%N5 cz)O$emAGKZCnwXXKr0wLUHx3vIVCg!0EmFw6951J diff --git a/docs/favicon.svg b/docs/favicon.svg deleted file mode 100644 index 8b34b511989e..000000000000 --- a/docs/favicon.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - diff --git a/docs/firewood/all.html b/docs/firewood/all.html deleted file mode 100644 index 0e910a2644aa..000000000000 --- a/docs/firewood/all.html +++ /dev/null @@ -1 +0,0 @@ -List of all items in this crate

    \ No newline at end of file diff --git a/docs/firewood/db/enum.DBError.html b/docs/firewood/db/enum.DBError.html deleted file mode 100644 index 40de0cb06195..000000000000 --- a/docs/firewood/db/enum.DBError.html +++ /dev/null @@ -1,10 +0,0 @@ -DBError in firewood::db - Rust
    pub enum DBError {
    -    InvalidParams,
    -    Merkle(MerkleError),
    -    Blob(BlobError),
    -    System(Error),
    -}

    Variants

    InvalidParams

    Merkle(MerkleError)

    Blob(BlobError)

    System(Error)

    Trait Implementations

    Formats the value using the given formatter. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/index.html b/docs/firewood/db/index.html deleted file mode 100644 index 7a941c876afd..000000000000 --- a/docs/firewood/db/index.html +++ /dev/null @@ -1,3 +0,0 @@ -firewood::db - Rust

    Structs

    Firewood database handle.
    Database configuration.
    Some readable version of the DB.
    Config for accessing a version of the DB.
    Config for the disk buffer.
    Lock protected handle to a readable version of the DB.
    An atomic batch of changes made to the DB. Each operation on a WriteBatch will move itself -because when an error occurs, the write batch will be automaticlaly aborted so that the DB -remains clean.

    Enums

    \ No newline at end of file diff --git a/docs/firewood/db/sidebar-items.js b/docs/firewood/db/sidebar-items.js deleted file mode 100644 index 402fc96865f1..000000000000 --- a/docs/firewood/db/sidebar-items.js +++ /dev/null @@ -1 +0,0 @@ -window.SIDEBAR_ITEMS = {"enum":[["DBError",""]],"struct":[["DB","Firewood database handle."],["DBConfig","Database configuration."],["DBRev","Some readable version of the DB."],["DBRevConfig","Config for accessing a version of the DB."],["DiskBufferConfig","Config for the disk buffer."],["Revision","Lock protected handle to a readable version of the DB."],["WALConfig",""],["WriteBatch","An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself because when an error occurs, the write batch will be automaticlaly aborted so that the DB remains clean."]]}; \ No newline at end of file diff --git a/docs/firewood/db/struct.DB.html b/docs/firewood/db/struct.DB.html deleted file mode 100644 index e65067945351..000000000000 --- a/docs/firewood/db/struct.DB.html +++ /dev/null @@ -1,19 +0,0 @@ -DB in firewood::db - Rust
    pub struct DB { /* private fields */ }
    Expand description

    Firewood database handle.

    -

    Implementations

    Open a database.

    -

    Create a write batch.

    -

    Dump the MPT of the latest generic key-value storage.

    -

    Dump the MPT of the latest entire account model storage.

    -

    Dump the MPT of the latest state storage under an account.

    -

    Get root hash of the latest generic key-value storage.

    -

    Get root hash of the latest world state of all accounts.

    -

    Get the latest balance of the account.

    -

    Get the latest code of the account.

    -

    Get the latest nonce of the account.

    -

    Get the latest state value indexed by sub_key in the account indexed by key.

    -

    Check if the account exists in the latest world state.

    -

    Get a handle that grants the access to some historical state of the entire DB.

    -

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.DBConfig.html b/docs/firewood/db/struct.DBConfig.html deleted file mode 100644 index 871c8fb767aa..000000000000 --- a/docs/firewood/db/struct.DBConfig.html +++ /dev/null @@ -1,9 +0,0 @@ -DBConfig in firewood::db - Rust
    pub struct DBConfig { /* private fields */ }
    Expand description

    Database configuration.

    -

    Implementations

    Create a builder for building DBConfig. -On the builder, call .meta_ncached_pages(...)(optional), .meta_ncached_files(...)(optional), .meta_file_nbit(...)(optional), .payload_ncached_pages(...)(optional), .payload_ncached_files(...)(optional), .payload_file_nbit(...)(optional), .payload_max_walk(...)(optional), .payload_regn_nbit(...)(optional), .truncate(...)(optional), .rev(...)(optional), .buffer(...)(optional), .wal(...)(optional) to set the values of the fields. -Finally, call .build() to create the instance of DBConfig.

    -

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.DBRev.html b/docs/firewood/db/struct.DBRev.html deleted file mode 100644 index e7e85037a4eb..000000000000 --- a/docs/firewood/db/struct.DBRev.html +++ /dev/null @@ -1,16 +0,0 @@ -DBRev in firewood::db - Rust
    pub struct DBRev { /* private fields */ }
    Expand description

    Some readable version of the DB.

    -

    Implementations

    Get root hash of the generic key-value storage.

    -

    Dump the MPT of the generic key-value storage.

    -

    Get root hash of the world state of all accounts.

    -

    Dump the MPT of the entire account model storage.

    -

    Dump the MPT of the state storage under an account.

    -

    Get balance of the account.

    -

    Get code of the account.

    -

    Get nonce of the account.

    -

    Get the state value indexed by sub_key in the account indexed by key.

    -

    Check if the account exists.

    -

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.DBRevConfig.html b/docs/firewood/db/struct.DBRevConfig.html deleted file mode 100644 index 836ef38eb498..000000000000 --- a/docs/firewood/db/struct.DBRevConfig.html +++ /dev/null @@ -1,9 +0,0 @@ -DBRevConfig in firewood::db - Rust
    pub struct DBRevConfig { /* private fields */ }
    Expand description

    Config for accessing a version of the DB.

    -

    Implementations

    Create a builder for building DBRevConfig. -On the builder, call .merkle_ncached_objs(...)(optional), .blob_ncached_objs(...)(optional) to set the values of the fields. -Finally, call .build() to create the instance of DBRevConfig.

    -

    Trait Implementations

    Returns a copy of the value. Read more
    Performs copy-assignment from source. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The resulting type after obtaining ownership.
    Creates owned data from borrowed data, usually by cloning. Read more
    Uses borrowed data to replace owned data, usually by cloning. Read more
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.DiskBufferConfig.html b/docs/firewood/db/struct.DiskBufferConfig.html deleted file mode 100644 index d7b42c10261b..000000000000 --- a/docs/firewood/db/struct.DiskBufferConfig.html +++ /dev/null @@ -1,9 +0,0 @@ -DiskBufferConfig in firewood::db - Rust
    pub struct DiskBufferConfig { /* private fields */ }
    Expand description

    Config for the disk buffer.

    -

    Implementations

    Create a builder for building DiskBufferConfig. -On the builder, call .max_buffered(...)(optional), .max_pending(...)(optional), .max_aio_requests(...)(optional), .max_aio_response(...)(optional), .max_aio_submit(...)(optional), .wal_max_aio_requests(...)(optional), .wal_max_buffered(...)(optional), .wal_max_batch(...)(optional) to set the values of the fields. -Finally, call .build() to create the instance of DiskBufferConfig.

    -

    Trait Implementations

    Returns a copy of the value. Read more
    Performs copy-assignment from source. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The resulting type after obtaining ownership.
    Creates owned data from borrowed data, usually by cloning. Read more
    Uses borrowed data to replace owned data, usually by cloning. Read more
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.Revision.html b/docs/firewood/db/struct.Revision.html deleted file mode 100644 index b7a7003ce3a7..000000000000 --- a/docs/firewood/db/struct.Revision.html +++ /dev/null @@ -1,16 +0,0 @@ -Revision in firewood::db - Rust
    pub struct Revision<'a> { /* private fields */ }
    Expand description

    Lock protected handle to a readable version of the DB.

    -

    Methods from Deref<Target = DBRev>

    Get root hash of the generic key-value storage.

    -

    Dump the MPT of the generic key-value storage.

    -

    Get root hash of the world state of all accounts.

    -

    Dump the MPT of the entire account model storage.

    -

    Dump the MPT of the state storage under an account.

    -

    Get balance of the account.

    -

    Get code of the account.

    -

    Get nonce of the account.

    -

    Get the state value indexed by sub_key in the account indexed by key.

    -

    Check if the account exists.

    -

    Trait Implementations

    The resulting type after dereferencing.
    Dereferences the value.

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.WALConfig.html b/docs/firewood/db/struct.WALConfig.html deleted file mode 100644 index df3384529383..000000000000 --- a/docs/firewood/db/struct.WALConfig.html +++ /dev/null @@ -1,8 +0,0 @@ -WALConfig in firewood::db - Rust
    pub struct WALConfig { /* private fields */ }

    Implementations

    Create a builder for building WALConfig. -On the builder, call .file_nbit(...)(optional), .block_nbit(...)(optional), .max_revisions(...)(optional) to set the values of the fields. -Finally, call .build() to create the instance of WALConfig.

    -

    Trait Implementations

    Returns a copy of the value. Read more
    Performs copy-assignment from source. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The resulting type after obtaining ownership.
    Creates owned data from borrowed data, usually by cloning. Read more
    Uses borrowed data to replace owned data, usually by cloning. Read more
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/db/struct.WriteBatch.html b/docs/firewood/db/struct.WriteBatch.html deleted file mode 100644 index 10010a981e5d..000000000000 --- a/docs/firewood/db/struct.WriteBatch.html +++ /dev/null @@ -1,21 +0,0 @@ -WriteBatch in firewood::db - Rust
    pub struct WriteBatch<'a> { /* private fields */ }
    Expand description

    An atomic batch of changes made to the DB. Each operation on a WriteBatch will move itself -because when an error occurs, the write batch will be automaticlaly aborted so that the DB -remains clean.

    -

    Implementations

    Insert an item to the generic key-value storage.

    -

    Remove an item from the generic key-value storage. val will be set to the value that is -removed from the storage if it exists.

    -

    Set balance of the account.

    -

    Set code of the account.

    -

    Set nonce of the account.

    -

    Set the state value indexed by sub_key in the account indexed by key.

    -

    Create an account.

    -

    Delete an account.

    -

    Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root -hashes to future invocation of root_hash, kv_root_hash or batch commits.

    -

    Persist all changes to the DB. The atomicity of the WriteBatch guarantees all changes are -either retained on disk or lost together during a crash.

    -

    Trait Implementations

    Executes the destructor for this type. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/index.html b/docs/firewood/index.html deleted file mode 100644 index 6a1f40333cc5..000000000000 --- a/docs/firewood/index.html +++ /dev/null @@ -1,196 +0,0 @@ -firewood - Rust
    Expand description

    Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval.

    -

    Firewood is an embedded key-value store, optimized to store blockchain state. -It prioritizes access to latest state, by providing extremely fast reads, but -also provides a limited view into past state. It does not copy-on-write the -state trie to generate an ever growing forest of tries like other databases, -but instead keeps one latest version of the trie index on disk and apply -in-place updates to it. This ensures that the database size is small and stable -during the course of running firewood. Firewood was first conceived to provide -a very fast storage layer for the EVM but could be used on any blockchain that -requires authenticated state.

    -

    Firewood is a robust database implemented from the ground up to directly store trie nodes and -user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a -generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses -the tree structure as the index on disk. Thus, there is no additional “emulation” of the -logical trie to flatten out the data structure to feed into the underlying DB that is unaware -of the data being stored.

    -

    Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees -atomicity and durability in the database, but also offers “reversibility”: some portion -of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover -some past versions of the entire store back in memory. While running the store, new changes -will also contribute to the configured window of changes (at batch granularity) to access any past -versions with no additional cost at all.

    -

    Firewood provides two isolated storage spaces which can be both or selectively used the user. -The account model portion of the storage offers something very similar to -StateDB in geth, which captures the address-“state key” style of -two-level access for an account’s (smart contract’s) state. Therefore, it takes -minimal effort to delegate all state storage from an EVM implementation to firewood. The other -portion of the storage supports generic trie storage for arbitrary keys and values. When unused, -there is no additional cost.

    -

    Design Philosophy & Overview

    -

    With some on-going academic research efforts and increasing demand of faster local storage -solutions for the chain state, we realized there are mainly two different regimes of designs.

    -
      -
    • -

      “Archival” Storage: this style of design emphasizes on the ability to hold all historical -data and retrieve a revision of any wold state at a reasonable performance. To economically -store all historical certified data, usually copy-on-write merkle tries are used to just -capture the changes made by a committed block. The entire storage consists of a forest of these -“delta” tries. The total size of the storage will keep growing over the chain length and an ideal, -well-executed plan for this is to make sure the performance degradation is reasonable or -well-contained with respect to the ever-increasing size of the index. This design is useful -for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a -query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also -need this because the “canonical” branch of the chain could switch (but not necessarily a -practical concern nowadays) to a different fork at times.

      -
    • -
    • -

      “Validation” Storage: this regime optimizes for the storage footprint and the performance of -operations upon the latest/recent states. With the assumption that the chain’s total state -size is relatively stable over ever-coming blocks, one can just make the latest state -persisted and available to the blockchain system as that’s what matters for most of the time. -While one can still keep some volatile state versions in memory for mutation and VM -execution, the final commit to some state works on a singleton so the indexed merkle tries -may be typically updated in place. It is also possible (e.g., firewood) to allow some -infrequent access to historical versions with higher cost, and/or allow fast access to -versions of the store within certain limited recency. This style of storage is useful for -the blockchain systems where only (or mostly) the latest state is required and data footprint -should remain constant or grow slowly if possible for sustainability. Validators who -directly participate in the consensus and vote for the blocks, for example, can largely -benefit from such a design.

      -
    • -
    -

    In firewood, we take a closer look at the second regime and have come up with a simple but -robust architecture that fulfills the need for such blockchain storage.

    -

    Storage Model

    -

    Firewood is built by three layers of abstractions that totally decouple the -layout/representation of the data on disk from the actual logical data structure it retains:

    -
      -
    • -

      Linear, memory-like space: the shale crate from an academic -project (CedrusDB) code offers a MemStore abstraction for a (64-bit) byte-addressable space -that abstracts away the intricate method that actually persists the in-memory data on the -secondary storage medium (e.g., hard drive). The implementor of MemStore will provide the -functions to give the user of MemStore an illusion that the user is operating upon a -byte-addressable memory space. It is just a “magical” array of bytes one can view and change -that is mirrored to the disk. In reality, the linear space will be chunked into files under a -directory, but the user does not have to even know about this.

      -
    • -
    • -

      Persistent item storage stash: ShaleStore trait from shale defines a pool of typed -objects that are persisted on disk but also made accessible in memory transparently. It is -built on top of MemStore by defining how “items” of the given type are laid out, allocated -and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of -basic implementation in shale crate, but one can always define his/her own ShaleStore).

      -
    • -
    • -

      Data structure: in Firewood, one or more tries are maintained by invoking -ShaleStore (see src/merkle.rs; another stash for code objects is in src/account.rs). -The data structure code is totally unaware of how its objects (i.e., nodes) are organized or -persisted on disk. It is as if they’re just in memory, which makes it much easier to write -and maintain the code.

      -
    • -
    -

    The three layers are depicted as follows:

    -

    - -

    -

    Given the abstraction, one can easily realize the fact that the actual data that affect the -state of the data structure (trie) is what the linear space (MemStore) keeps track of, that is, -a flat but conceptually large byte vector. In other words, given a valid byte vector as the -content of the linear space, the higher level data structure can be uniquely determined, there -is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) -or less than that, like a way to interpret the bytes. This nice property allows us to completely -separate the logical data from its physical representation, greatly simplifies the storage -management, and allows reusing the code. It is still a very versatile abstraction, as in theory -any persistent data could be stored this way – sometimes you need to swap in a different -MemShale or MemStore implementation, but without having to touch the code for the persisted -data structure.

    -

    Page-based Shadowing and Revisions

    -

    Following the idea that the tries are just a view of a linear byte space, all writes made to the -tries inside Firewood will eventually be consolidated into some interval writes to the linear -space. The writes may overlap and some frequent writes are even done to the same spot in the -space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit -virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the -dirty pages in some MemStore instantiation (see storage::StoreRevMut). When a -db::WriteBatch commits, both the recorded interval writes and the aggregated in-memory -dirty pages induced by this write batch are taken out from the linear space. Although they are -mathematically equivalent, interval writes are more compact than pages (which are 4K in size, -become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL -subsystem (supported by growthring). After the -WAL record is written (one record per write batch), the dirty pages are then pushed to the -on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See -the BufferCmd::WriteBatch part of DiskBuffer::process for the detailed logic.

    -

    In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood:

    -
      -
    • -

      Traverse the trie, and that induces the access to some nodes. Suppose the nodes are not already in -memory, then:

      -
    • -
    • -

      Bring the necessary pages that contain the accessed nodes into the memory and cache them -(storage::CachedSpace).

      -
    • -
    • -

      Make changes to the trie, and that induces the writes to some nodes. The nodes are either -already cached in memory (its pages are cached, or its handle ObjRef<Node> is still in -shale::ObjCache) or need to be brought into the memory (if that’s the case, go back to the -second step for it).

      -
    • -
    • -

      Writes to nodes are converted into interval writes to the stagging StoreRevMut space that -overlays atop CachedSpace, so all dirty pages during the current write batch will be -exactly captured in StoreRevMut (see StoreRevMut::take_delta).

      -
    • -
    • -

      Finally:

      -
        -
      • -

        Abort: when the write batch is dropped without invoking db::WriteBatch::commit, all in-memory -changes will be discarded, the dirty pages from StoreRevMut will be dropped and the merkle -will “revert” back to its original state without actually having to rollback anything.

        -
      • -
      • -

        Commit: otherwise, the write batch is committed, the interval writes (storage::Ash) will be bundled -into a single WAL record (storage::AshRecord) and sent to WAL subsystem, before dirty pages -are scheduled to be written to the space files. Also the dirty pages are applied to the -underlying CachedSpace. StoreRevMut becomes empty again for further write batches.

        -
      • -
      -
    • -
    -

    Parts of the following diagram show this normal flow, the “staging” space (implemented by -StoreRevMut) concept is a bit similar to the staging area in Git, which enables the handling -of (resuming from) write errors, clean abortion of an on-going write batch so the entire store -state remains intact, and also reduces unnecessary premature disk writes. Essentially, we -copy-on-write pages in the space that are touched upon, without directly mutating the -underlying “master” space. The staging space is just a collection of these “shadowing” pages -and a reference to the its base (master) so any reads could partially hit those dirty pages -and/or fall through to the base, whereas all writes are captured. Finally, when things go well, -we “push down” these changes to the base and clear up the staging space.

    -

    - -

    -

    Thanks to the shadow pages, we can both revive some historical versions of the store and -maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram -shows previously logged write batch records could be kept even though they are no longer needed -for the purpose of crash recovery. The interval writes from a record can be aggregated into -pages (see storage::StoreDelta::new) and used to reconstruct a “ghost” image of past -revision of the linear space (just like how staging space works, except that the ghost space is -essentially read-only once constructed). The shadow pages there will function as some -“rewinding” changes to patch the necessary locations in the linear space, while the rest of the -linear space is very likely untouched by that historical write batch.

    -

    Then, with the three-layer abstraction we previously talked about, a historical trie could be -derived. In fact, because there is no mandatory traversal or scanning in the process, the -only cost to revive a historical state from the log is to just playback the records and create -those shadow pages. There is very little additional cost because the ghost space is summoned on an -on-demand manner while one accesses the historical trie.

    -

    In the other direction, when new write batches are committed, the system moves forward, we can -therefore maintain a rolling window of past revisions in memory with zero cost. The -mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one -step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be -created to hold the version of the store before the commit. The backward delta is applied to -counteract the change that has been made to the persisted store, which is also a set of shadow pages. -No change is required for other historical ghost space instances. Finally, we can phase out -some very old ghost space to keep the size of the rolling window invariant.

    -

    Modules

    diff --git a/docs/firewood/merkle/enum.MerkleError.html b/docs/firewood/merkle/enum.MerkleError.html deleted file mode 100644 index 67bf18d7275f..000000000000 --- a/docs/firewood/merkle/enum.MerkleError.html +++ /dev/null @@ -1,10 +0,0 @@ -MerkleError in firewood::merkle - Rust
    pub enum MerkleError {
    -    Shale(ShaleError),
    -    ReadOnly,
    -    NotBranchNode,
    -    Format(Error),
    -}

    Variants

    Shale(ShaleError)

    ReadOnly

    NotBranchNode

    Format(Error)

    Trait Implementations

    Formats the value using the given formatter. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/index.html b/docs/firewood/merkle/index.html deleted file mode 100644 index b0ea654f14e6..000000000000 --- a/docs/firewood/merkle/index.html +++ /dev/null @@ -1 +0,0 @@ -firewood::merkle - Rust
    \ No newline at end of file diff --git a/docs/firewood/merkle/sidebar-items.js b/docs/firewood/merkle/sidebar-items.js deleted file mode 100644 index 6cafd793bb93..000000000000 --- a/docs/firewood/merkle/sidebar-items.js +++ /dev/null @@ -1 +0,0 @@ -window.SIDEBAR_ITEMS = {"enum":[["MerkleError",""]],"fn":[["from_nibbles",""],["to_nibbles",""]],"struct":[["Hash",""],["IdTrans",""],["Merkle",""],["Node",""],["PartialPath","PartialPath keeps a list of nibbles to represent a path on the MPT."],["Ref",""],["RefMut",""]],"trait":[["ValueTransformer",""]]}; \ No newline at end of file diff --git a/docs/firewood/merkle/struct.Hash.html b/docs/firewood/merkle/struct.Hash.html deleted file mode 100644 index e4df59470165..000000000000 --- a/docs/firewood/merkle/struct.Hash.html +++ /dev/null @@ -1,84 +0,0 @@ -Hash in firewood::merkle - Rust
    pub struct Hash(pub [u8; 32]);

    Tuple Fields

    0: [u8; 32]

    Methods from Deref<Target = [u8; 32]>

    Returns a slice containing the entire array. Equivalent to &s[..].

    -
    🔬This is a nightly-only experimental API. (array_methods)

    Borrows each element and returns an array of references with the same -size as self.

    -
    Example
    -
    #![feature(array_methods)]
    -
    -let floats = [3.1, 2.7, -1.0];
    -let float_refs: [&f64; 3] = floats.each_ref();
    -assert_eq!(float_refs, [&3.1, &2.7, &-1.0]);
    -

    This method is particularly useful if combined with other methods, like -map. This way, you can avoid moving the original -array if its elements are not Copy.

    - -
    #![feature(array_methods)]
    -
    -let strings = ["Ferris".to_string(), "♥".to_string(), "Rust".to_string()];
    -let is_ascii = strings.each_ref().map(|s| s.is_ascii());
    -assert_eq!(is_ascii, [true, false, true]);
    -
    -// We can still access the original array: it has not been moved.
    -assert_eq!(strings.len(), 3);
    -
    🔬This is a nightly-only experimental API. (split_array)

    Divides one array reference into two at an index.

    -

    The first will contain all indices from [0, M) (excluding -the index M itself) and the second will contain all -indices from [M, N) (excluding the index N itself).

    -
    Panics
    -

    Panics if M > N.

    -
    Examples
    -
    #![feature(split_array)]
    -
    -let v = [1, 2, 3, 4, 5, 6];
    -
    -{
    -   let (left, right) = v.split_array_ref::<0>();
    -   assert_eq!(left, &[]);
    -   assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_array_ref::<2>();
    -    assert_eq!(left, &[1, 2]);
    -    assert_eq!(right, &[3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_array_ref::<6>();
    -    assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
    -    assert_eq!(right, &[]);
    -}
    -
    🔬This is a nightly-only experimental API. (split_array)

    Divides one array reference into two at an index from the end.

    -

    The first will contain all indices from [0, N - M) (excluding -the index N - M itself) and the second will contain all -indices from [N - M, N) (excluding the index N itself).

    -
    Panics
    -

    Panics if M > N.

    -
    Examples
    -
    #![feature(split_array)]
    -
    -let v = [1, 2, 3, 4, 5, 6];
    -
    -{
    -   let (left, right) = v.rsplit_array_ref::<0>();
    -   assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
    -   assert_eq!(right, &[]);
    -}
    -
    -{
    -    let (left, right) = v.rsplit_array_ref::<2>();
    -    assert_eq!(left, &[1, 2, 3, 4]);
    -    assert_eq!(right, &[5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.rsplit_array_ref::<6>();
    -    assert_eq!(left, &[]);
    -    assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
    -}
    -

    Trait Implementations

    Returns a copy of the value. Read more
    Performs copy-assignment from source. Read more
    The resulting type after dereferencing.
    Dereferences the value.
    This method tests for self and other values to be equal, and is used -by ==. Read more
    This method tests for !=. The default implementation is almost always -sufficient, and should not be overridden without very good reason. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The resulting type after obtaining ownership.
    Creates owned data from borrowed data, usually by cloning. Read more
    Uses borrowed data to replace owned data, usually by cloning. Read more
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/struct.IdTrans.html b/docs/firewood/merkle/struct.IdTrans.html deleted file mode 100644 index 6701f80ede67..000000000000 --- a/docs/firewood/merkle/struct.IdTrans.html +++ /dev/null @@ -1,5 +0,0 @@ -IdTrans in firewood::merkle - Rust
    pub struct IdTrans;

    Trait Implementations

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/struct.Merkle.html b/docs/firewood/merkle/struct.Merkle.html deleted file mode 100644 index 17fbe7060cb9..000000000000 --- a/docs/firewood/merkle/struct.Merkle.html +++ /dev/null @@ -1,11 +0,0 @@ -Merkle in firewood::merkle - Rust
    pub struct Merkle { /* private fields */ }

    Implementations

    Constructs a merkle proof for key. The result contains all encoded nodes -on the path to the value at key. The value itself is also included in the -last node and can be retrieved by verifying the proof.

    -

    If the trie does not contain a value for key, the returned proof contains -all nodes of the longest existing prefix of the key, ending with the node -that proves the absence of the key (at least the root node).

    -

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/struct.Node.html b/docs/firewood/merkle/struct.Node.html deleted file mode 100644 index a5377eb6f224..000000000000 --- a/docs/firewood/merkle/struct.Node.html +++ /dev/null @@ -1,7 +0,0 @@ -Node in firewood::merkle - Rust
    pub struct Node { /* private fields */ }

    Trait Implementations

    Returns a copy of the value. Read more
    Performs copy-assignment from source. Read more
    This method tests for self and other values to be equal, and is used -by ==. Read more
    This method tests for !=. The default implementation is almost always -sufficient, and should not be overridden without very good reason. Read more

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The resulting type after obtaining ownership.
    Creates owned data from borrowed data, usually by cloning. Read more
    Uses borrowed data to replace owned data, usually by cloning. Read more
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/struct.Ref.html b/docs/firewood/merkle/struct.Ref.html deleted file mode 100644 index 8fbb6bd66eff..000000000000 --- a/docs/firewood/merkle/struct.Ref.html +++ /dev/null @@ -1,934 +0,0 @@ -Ref in firewood::merkle - Rust
    pub struct Ref<'a>(_);

    Methods from Deref<Target = [u8]>

    Returns the number of elements in the slice.

    -
    Examples
    -
    let a = [1, 2, 3];
    -assert_eq!(a.len(), 3);
    -

    Returns true if the slice has a length of 0.

    -
    Examples
    -
    let a = [1, 2, 3];
    -assert!(!a.is_empty());
    -

    Returns the first element of the slice, or None if it is empty.

    -
    Examples
    -
    let v = [10, 40, 30];
    -assert_eq!(Some(&10), v.first());
    -
    -let w: &[i32] = &[];
    -assert_eq!(None, w.first());
    -

    Returns the first and all the rest of the elements of the slice, or None if it is empty.

    -
    Examples
    -
    let x = &[0, 1, 2];
    -
    -if let Some((first, elements)) = x.split_first() {
    -    assert_eq!(first, &0);
    -    assert_eq!(elements, &[1, 2]);
    -}
    -

    Returns the last and all the rest of the elements of the slice, or None if it is empty.

    -
    Examples
    -
    let x = &[0, 1, 2];
    -
    -if let Some((last, elements)) = x.split_last() {
    -    assert_eq!(last, &2);
    -    assert_eq!(elements, &[0, 1]);
    -}
    -

    Returns the last element of the slice, or None if it is empty.

    -
    Examples
    -
    let v = [10, 40, 30];
    -assert_eq!(Some(&30), v.last());
    -
    -let w: &[i32] = &[];
    -assert_eq!(None, w.last());
    -

    Returns a reference to an element or subslice depending on the type of -index.

    -
      -
    • If given a position, returns a reference to the element at that -position or None if out of bounds.
    • -
    • If given a range, returns the subslice corresponding to that range, -or None if out of bounds.
    • -
    -
    Examples
    -
    let v = [10, 40, 30];
    -assert_eq!(Some(&40), v.get(1));
    -assert_eq!(Some(&[10, 40][..]), v.get(0..2));
    -assert_eq!(None, v.get(3));
    -assert_eq!(None, v.get(0..4));
    -

    Returns a reference to an element or subslice, without doing bounds -checking.

    -

    For a safe alternative see get.

    -
    Safety
    -

    Calling this method with an out-of-bounds index is undefined behavior -even if the resulting reference is not used.

    -
    Examples
    -
    let x = &[1, 2, 4];
    -
    -unsafe {
    -    assert_eq!(x.get_unchecked(1), &2);
    -}
    -

    Returns a raw pointer to the slice’s buffer.

    -

    The caller must ensure that the slice outlives the pointer this -function returns, or else it will end up pointing to garbage.

    -

    The caller must also ensure that the memory the pointer (non-transitively) points to -is never written to (except inside an UnsafeCell) using this pointer or any pointer -derived from it. If you need to mutate the contents of the slice, use as_mut_ptr.

    -

    Modifying the container referenced by this slice may cause its buffer -to be reallocated, which would also make any pointers to it invalid.

    -
    Examples
    -
    let x = &[1, 2, 4];
    -let x_ptr = x.as_ptr();
    -
    -unsafe {
    -    for i in 0..x.len() {
    -        assert_eq!(x.get_unchecked(i), &*x_ptr.add(i));
    -    }
    -}
    -

    Returns the two raw pointers spanning the slice.

    -

    The returned range is half-open, which means that the end pointer -points one past the last element of the slice. This way, an empty -slice is represented by two equal pointers, and the difference between -the two pointers represents the size of the slice.

    -

    See as_ptr for warnings on using these pointers. The end pointer -requires extra caution, as it does not point to a valid element in the -slice.

    -

    This function is useful for interacting with foreign interfaces which -use two pointers to refer to a range of elements in memory, as is -common in C++.

    -

    It can also be useful to check if a pointer to an element refers to an -element of this slice:

    - -
    let a = [1, 2, 3];
    -let x = &a[1] as *const _;
    -let y = &5 as *const _;
    -
    -assert!(a.as_ptr_range().contains(&x));
    -assert!(!a.as_ptr_range().contains(&y));
    -

    Returns an iterator over the slice.

    -

    The iterator yields all items from start to end.

    -
    Examples
    -
    let x = &[1, 2, 4];
    -let mut iterator = x.iter();
    -
    -assert_eq!(iterator.next(), Some(&1));
    -assert_eq!(iterator.next(), Some(&2));
    -assert_eq!(iterator.next(), Some(&4));
    -assert_eq!(iterator.next(), None);
    -

    Returns an iterator over all contiguous windows of length -size. The windows overlap. If the slice is shorter than -size, the iterator returns no values.

    -
    Panics
    -

    Panics if size is 0.

    -
    Examples
    -
    let slice = ['r', 'u', 's', 't'];
    -let mut iter = slice.windows(2);
    -assert_eq!(iter.next().unwrap(), &['r', 'u']);
    -assert_eq!(iter.next().unwrap(), &['u', 's']);
    -assert_eq!(iter.next().unwrap(), &['s', 't']);
    -assert!(iter.next().is_none());
    -

    If the slice is shorter than size:

    - -
    let slice = ['f', 'o', 'o'];
    -let mut iter = slice.windows(4);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over chunk_size elements of the slice at a time, starting at the -beginning of the slice.

    -

    The chunks are slices and do not overlap. If chunk_size does not divide the length of the -slice, then the last chunk will not have length chunk_size.

    -

    See chunks_exact for a variant of this iterator that returns chunks of always exactly -chunk_size elements, and rchunks for the same iterator but starting at the end of the -slice.

    -
    Panics
    -

    Panics if chunk_size is 0.

    -
    Examples
    -
    let slice = ['l', 'o', 'r', 'e', 'm'];
    -let mut iter = slice.chunks(2);
    -assert_eq!(iter.next().unwrap(), &['l', 'o']);
    -assert_eq!(iter.next().unwrap(), &['r', 'e']);
    -assert_eq!(iter.next().unwrap(), &['m']);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over chunk_size elements of the slice at a time, starting at the -beginning of the slice.

    -

    The chunks are slices and do not overlap. If chunk_size does not divide the length of the -slice, then the last up to chunk_size-1 elements will be omitted and can be retrieved -from the remainder function of the iterator.

    -

    Due to each chunk having exactly chunk_size elements, the compiler can often optimize the -resulting code better than in the case of chunks.

    -

    See chunks for a variant of this iterator that also returns the remainder as a smaller -chunk, and rchunks_exact for the same iterator but starting at the end of the slice.

    -
    Panics
    -

    Panics if chunk_size is 0.

    -
    Examples
    -
    let slice = ['l', 'o', 'r', 'e', 'm'];
    -let mut iter = slice.chunks_exact(2);
    -assert_eq!(iter.next().unwrap(), &['l', 'o']);
    -assert_eq!(iter.next().unwrap(), &['r', 'e']);
    -assert!(iter.next().is_none());
    -assert_eq!(iter.remainder(), &['m']);
    -
    🔬This is a nightly-only experimental API. (slice_as_chunks)

    Splits the slice into a slice of N-element arrays, -assuming that there’s no remainder.

    -
    Safety
    -

    This may only be called when

    -
      -
    • The slice splits exactly into N-element chunks (aka self.len() % N == 0).
    • -
    • N != 0.
    • -
    -
    Examples
    -
    #![feature(slice_as_chunks)]
    -let slice: &[char] = &['l', 'o', 'r', 'e', 'm', '!'];
    -let chunks: &[[char; 1]] =
    -    // SAFETY: 1-element chunks never have remainder
    -    unsafe { slice.as_chunks_unchecked() };
    -assert_eq!(chunks, &[['l'], ['o'], ['r'], ['e'], ['m'], ['!']]);
    -let chunks: &[[char; 3]] =
    -    // SAFETY: The slice length (6) is a multiple of 3
    -    unsafe { slice.as_chunks_unchecked() };
    -assert_eq!(chunks, &[['l', 'o', 'r'], ['e', 'm', '!']]);
    -
    -// These would be unsound:
    -// let chunks: &[[_; 5]] = slice.as_chunks_unchecked() // The slice length is not a multiple of 5
    -// let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // Zero-length chunks are never allowed
    -
    🔬This is a nightly-only experimental API. (slice_as_chunks)

    Splits the slice into a slice of N-element arrays, -starting at the beginning of the slice, -and a remainder slice with length strictly less than N.

    -
    Panics
    -

    Panics if N is 0. This check will most probably get changed to a compile time -error before this method gets stabilized.

    -
    Examples
    -
    #![feature(slice_as_chunks)]
    -let slice = ['l', 'o', 'r', 'e', 'm'];
    -let (chunks, remainder) = slice.as_chunks();
    -assert_eq!(chunks, &[['l', 'o'], ['r', 'e']]);
    -assert_eq!(remainder, &['m']);
    -
    🔬This is a nightly-only experimental API. (slice_as_chunks)

    Splits the slice into a slice of N-element arrays, -starting at the end of the slice, -and a remainder slice with length strictly less than N.

    -
    Panics
    -

    Panics if N is 0. This check will most probably get changed to a compile time -error before this method gets stabilized.

    -
    Examples
    -
    #![feature(slice_as_chunks)]
    -let slice = ['l', 'o', 'r', 'e', 'm'];
    -let (remainder, chunks) = slice.as_rchunks();
    -assert_eq!(remainder, &['l']);
    -assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);
    -
    🔬This is a nightly-only experimental API. (array_chunks)

    Returns an iterator over N elements of the slice at a time, starting at the -beginning of the slice.

    -

    The chunks are array references and do not overlap. If N does not divide the -length of the slice, then the last up to N-1 elements will be omitted and can be -retrieved from the remainder function of the iterator.

    -

    This method is the const generic equivalent of chunks_exact.

    -
    Panics
    -

    Panics if N is 0. This check will most probably get changed to a compile time -error before this method gets stabilized.

    -
    Examples
    -
    #![feature(array_chunks)]
    -let slice = ['l', 'o', 'r', 'e', 'm'];
    -let mut iter = slice.array_chunks();
    -assert_eq!(iter.next().unwrap(), &['l', 'o']);
    -assert_eq!(iter.next().unwrap(), &['r', 'e']);
    -assert!(iter.next().is_none());
    -assert_eq!(iter.remainder(), &['m']);
    -
    🔬This is a nightly-only experimental API. (array_windows)

    Returns an iterator over overlapping windows of N elements of a slice, -starting at the beginning of the slice.

    -

    This is the const generic equivalent of windows.

    -

    If N is greater than the size of the slice, it will return no windows.

    -
    Panics
    -

    Panics if N is 0. This check will most probably get changed to a compile time -error before this method gets stabilized.

    -
    Examples
    -
    #![feature(array_windows)]
    -let slice = [0, 1, 2, 3];
    -let mut iter = slice.array_windows();
    -assert_eq!(iter.next().unwrap(), &[0, 1]);
    -assert_eq!(iter.next().unwrap(), &[1, 2]);
    -assert_eq!(iter.next().unwrap(), &[2, 3]);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over chunk_size elements of the slice at a time, starting at the end -of the slice.

    -

    The chunks are slices and do not overlap. If chunk_size does not divide the length of the -slice, then the last chunk will not have length chunk_size.

    -

    See rchunks_exact for a variant of this iterator that returns chunks of always exactly -chunk_size elements, and chunks for the same iterator but starting at the beginning -of the slice.

    -
    Panics
    -

    Panics if chunk_size is 0.

    -
    Examples
    -
    let slice = ['l', 'o', 'r', 'e', 'm'];
    -let mut iter = slice.rchunks(2);
    -assert_eq!(iter.next().unwrap(), &['e', 'm']);
    -assert_eq!(iter.next().unwrap(), &['o', 'r']);
    -assert_eq!(iter.next().unwrap(), &['l']);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over chunk_size elements of the slice at a time, starting at the -end of the slice.

    -

    The chunks are slices and do not overlap. If chunk_size does not divide the length of the -slice, then the last up to chunk_size-1 elements will be omitted and can be retrieved -from the remainder function of the iterator.

    -

    Due to each chunk having exactly chunk_size elements, the compiler can often optimize the -resulting code better than in the case of rchunks.

    -

    See rchunks for a variant of this iterator that also returns the remainder as a smaller -chunk, and chunks_exact for the same iterator but starting at the beginning of the -slice.

    -
    Panics
    -

    Panics if chunk_size is 0.

    -
    Examples
    -
    let slice = ['l', 'o', 'r', 'e', 'm'];
    -let mut iter = slice.rchunks_exact(2);
    -assert_eq!(iter.next().unwrap(), &['e', 'm']);
    -assert_eq!(iter.next().unwrap(), &['o', 'r']);
    -assert!(iter.next().is_none());
    -assert_eq!(iter.remainder(), &['l']);
    -
    🔬This is a nightly-only experimental API. (slice_group_by)

    Returns an iterator over the slice producing non-overlapping runs -of elements using the predicate to separate them.

    -

    The predicate is called on two elements following themselves, -it means the predicate is called on slice[0] and slice[1] -then on slice[1] and slice[2] and so on.

    -
    Examples
    -
    #![feature(slice_group_by)]
    -
    -let slice = &[1, 1, 1, 3, 3, 2, 2, 2];
    -
    -let mut iter = slice.group_by(|a, b| a == b);
    -
    -assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
    -assert_eq!(iter.next(), Some(&[3, 3][..]));
    -assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
    -assert_eq!(iter.next(), None);
    -

    This method can be used to extract the sorted subslices:

    - -
    #![feature(slice_group_by)]
    -
    -let slice = &[1, 1, 2, 3, 2, 3, 2, 3, 4];
    -
    -let mut iter = slice.group_by(|a, b| a <= b);
    -
    -assert_eq!(iter.next(), Some(&[1, 1, 2, 3][..]));
    -assert_eq!(iter.next(), Some(&[2, 3][..]));
    -assert_eq!(iter.next(), Some(&[2, 3, 4][..]));
    -assert_eq!(iter.next(), None);
    -

    Divides one slice into two at an index.

    -

    The first will contain all indices from [0, mid) (excluding -the index mid itself) and the second will contain all -indices from [mid, len) (excluding the index len itself).

    -
    Panics
    -

    Panics if mid > len.

    -
    Examples
    -
    let v = [1, 2, 3, 4, 5, 6];
    -
    -{
    -   let (left, right) = v.split_at(0);
    -   assert_eq!(left, []);
    -   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_at(2);
    -    assert_eq!(left, [1, 2]);
    -    assert_eq!(right, [3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_at(6);
    -    assert_eq!(left, [1, 2, 3, 4, 5, 6]);
    -    assert_eq!(right, []);
    -}
    -
    🔬This is a nightly-only experimental API. (slice_split_at_unchecked)

    Divides one slice into two at an index, without doing bounds checking.

    -

    The first will contain all indices from [0, mid) (excluding -the index mid itself) and the second will contain all -indices from [mid, len) (excluding the index len itself).

    -

    For a safe alternative see split_at.

    -
    Safety
    -

    Calling this method with an out-of-bounds index is undefined behavior -even if the resulting reference is not used. The caller has to ensure that -0 <= mid <= self.len().

    -
    Examples
    -
    #![feature(slice_split_at_unchecked)]
    -
    -let v = [1, 2, 3, 4, 5, 6];
    -
    -unsafe {
    -   let (left, right) = v.split_at_unchecked(0);
    -   assert_eq!(left, []);
    -   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
    -}
    -
    -unsafe {
    -    let (left, right) = v.split_at_unchecked(2);
    -    assert_eq!(left, [1, 2]);
    -    assert_eq!(right, [3, 4, 5, 6]);
    -}
    -
    -unsafe {
    -    let (left, right) = v.split_at_unchecked(6);
    -    assert_eq!(left, [1, 2, 3, 4, 5, 6]);
    -    assert_eq!(right, []);
    -}
    -
    🔬This is a nightly-only experimental API. (split_array)

    Divides one slice into an array and a remainder slice at an index.

    -

    The array will contain all indices from [0, N) (excluding -the index N itself) and the slice will contain all -indices from [N, len) (excluding the index len itself).

    -
    Panics
    -

    Panics if N > len.

    -
    Examples
    -
    #![feature(split_array)]
    -
    -let v = &[1, 2, 3, 4, 5, 6][..];
    -
    -{
    -   let (left, right) = v.split_array_ref::<0>();
    -   assert_eq!(left, &[]);
    -   assert_eq!(right, [1, 2, 3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_array_ref::<2>();
    -    assert_eq!(left, &[1, 2]);
    -    assert_eq!(right, [3, 4, 5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.split_array_ref::<6>();
    -    assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
    -    assert_eq!(right, []);
    -}
    -
    🔬This is a nightly-only experimental API. (split_array)

    Divides one slice into an array and a remainder slice at an index from -the end.

    -

    The slice will contain all indices from [0, len - N) (excluding -the index len - N itself) and the array will contain all -indices from [len - N, len) (excluding the index len itself).

    -
    Panics
    -

    Panics if N > len.

    -
    Examples
    -
    #![feature(split_array)]
    -
    -let v = &[1, 2, 3, 4, 5, 6][..];
    -
    -{
    -   let (left, right) = v.rsplit_array_ref::<0>();
    -   assert_eq!(left, [1, 2, 3, 4, 5, 6]);
    -   assert_eq!(right, &[]);
    -}
    -
    -{
    -    let (left, right) = v.rsplit_array_ref::<2>();
    -    assert_eq!(left, [1, 2, 3, 4]);
    -    assert_eq!(right, &[5, 6]);
    -}
    -
    -{
    -    let (left, right) = v.rsplit_array_ref::<6>();
    -    assert_eq!(left, []);
    -    assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
    -}
    -

    Returns an iterator over subslices separated by elements that match -pred. The matched element is not contained in the subslices.

    -
    Examples
    -
    let slice = [10, 40, 33, 20];
    -let mut iter = slice.split(|num| num % 3 == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[10, 40]);
    -assert_eq!(iter.next().unwrap(), &[20]);
    -assert!(iter.next().is_none());
    -

    If the first element is matched, an empty slice will be the first item -returned by the iterator. Similarly, if the last element in the slice -is matched, an empty slice will be the last item returned by the -iterator:

    - -
    let slice = [10, 40, 33];
    -let mut iter = slice.split(|num| num % 3 == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[10, 40]);
    -assert_eq!(iter.next().unwrap(), &[]);
    -assert!(iter.next().is_none());
    -

    If two matched elements are directly adjacent, an empty slice will be -present between them:

    - -
    let slice = [10, 6, 33, 20];
    -let mut iter = slice.split(|num| num % 3 == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[10]);
    -assert_eq!(iter.next().unwrap(), &[]);
    -assert_eq!(iter.next().unwrap(), &[20]);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over subslices separated by elements that match -pred. The matched element is contained in the end of the previous -subslice as a terminator.

    -
    Examples
    -
    let slice = [10, 40, 33, 20];
    -let mut iter = slice.split_inclusive(|num| num % 3 == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
    -assert_eq!(iter.next().unwrap(), &[20]);
    -assert!(iter.next().is_none());
    -

    If the last element of the slice is matched, -that element will be considered the terminator of the preceding slice. -That slice will be the last item returned by the iterator.

    - -
    let slice = [3, 10, 40, 33];
    -let mut iter = slice.split_inclusive(|num| num % 3 == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[3]);
    -assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
    -assert!(iter.next().is_none());
    -

    Returns an iterator over subslices separated by elements that match -pred, starting at the end of the slice and working backwards. -The matched element is not contained in the subslices.

    -
    Examples
    -
    let slice = [11, 22, 33, 0, 44, 55];
    -let mut iter = slice.rsplit(|num| *num == 0);
    -
    -assert_eq!(iter.next().unwrap(), &[44, 55]);
    -assert_eq!(iter.next().unwrap(), &[11, 22, 33]);
    -assert_eq!(iter.next(), None);
    -

    As with split(), if the first or last element is matched, an empty -slice will be the first (or last) item returned by the iterator.

    - -
    let v = &[0, 1, 1, 2, 3, 5, 8];
    -let mut it = v.rsplit(|n| *n % 2 == 0);
    -assert_eq!(it.next().unwrap(), &[]);
    -assert_eq!(it.next().unwrap(), &[3, 5]);
    -assert_eq!(it.next().unwrap(), &[1, 1]);
    -assert_eq!(it.next().unwrap(), &[]);
    -assert_eq!(it.next(), None);
    -

    Returns an iterator over subslices separated by elements that match -pred, limited to returning at most n items. The matched element is -not contained in the subslices.

    -

    The last element returned, if any, will contain the remainder of the -slice.

    -
    Examples
    -

    Print the slice split once by numbers divisible by 3 (i.e., [10, 40], -[20, 60, 50]):

    - -
    let v = [10, 40, 30, 20, 60, 50];
    -
    -for group in v.splitn(2, |num| *num % 3 == 0) {
    -    println!("{group:?}");
    -}
    -

    Returns an iterator over subslices separated by elements that match -pred limited to returning at most n items. This starts at the end of -the slice and works backwards. The matched element is not contained in -the subslices.

    -

    The last element returned, if any, will contain the remainder of the -slice.

    -
    Examples
    -

    Print the slice split once, starting from the end, by numbers divisible -by 3 (i.e., [50], [10, 40, 30, 20]):

    - -
    let v = [10, 40, 30, 20, 60, 50];
    -
    -for group in v.rsplitn(2, |num| *num % 3 == 0) {
    -    println!("{group:?}");
    -}
    -

    Returns true if the slice contains an element with the given value.

    -

    This operation is O(n).

    -

    Note that if you have a sorted slice, binary_search may be faster.

    -
    Examples
    -
    let v = [10, 40, 30];
    -assert!(v.contains(&30));
    -assert!(!v.contains(&50));
    -

    If you do not have a &T, but some other value that you can compare -with one (for example, String implements PartialEq<str>), you can -use iter().any:

    - -
    let v = [String::from("hello"), String::from("world")]; // slice of `String`
    -assert!(v.iter().any(|e| e == "hello")); // search with `&str`
    -assert!(!v.iter().any(|e| e == "hi"));
    -

    Returns true if needle is a prefix of the slice.

    -
    Examples
    -
    let v = [10, 40, 30];
    -assert!(v.starts_with(&[10]));
    -assert!(v.starts_with(&[10, 40]));
    -assert!(!v.starts_with(&[50]));
    -assert!(!v.starts_with(&[10, 50]));
    -

    Always returns true if needle is an empty slice:

    - -
    let v = &[10, 40, 30];
    -assert!(v.starts_with(&[]));
    -let v: &[u8] = &[];
    -assert!(v.starts_with(&[]));
    -

    Returns true if needle is a suffix of the slice.

    -
    Examples
    -
    let v = [10, 40, 30];
    -assert!(v.ends_with(&[30]));
    -assert!(v.ends_with(&[40, 30]));
    -assert!(!v.ends_with(&[50]));
    -assert!(!v.ends_with(&[50, 30]));
    -

    Always returns true if needle is an empty slice:

    - -
    let v = &[10, 40, 30];
    -assert!(v.ends_with(&[]));
    -let v: &[u8] = &[];
    -assert!(v.ends_with(&[]));
    -

    Returns a subslice with the prefix removed.

    -

    If the slice starts with prefix, returns the subslice after the prefix, wrapped in Some. -If prefix is empty, simply returns the original slice.

    -

    If the slice does not start with prefix, returns None.

    -
    Examples
    -
    let v = &[10, 40, 30];
    -assert_eq!(v.strip_prefix(&[10]), Some(&[40, 30][..]));
    -assert_eq!(v.strip_prefix(&[10, 40]), Some(&[30][..]));
    -assert_eq!(v.strip_prefix(&[50]), None);
    -assert_eq!(v.strip_prefix(&[10, 50]), None);
    -
    -let prefix : &str = "he";
    -assert_eq!(b"hello".strip_prefix(prefix.as_bytes()),
    -           Some(b"llo".as_ref()));
    -

    Returns a subslice with the suffix removed.

    -

    If the slice ends with suffix, returns the subslice before the suffix, wrapped in Some. -If suffix is empty, simply returns the original slice.

    -

    If the slice does not end with suffix, returns None.

    -
    Examples
    -
    let v = &[10, 40, 30];
    -assert_eq!(v.strip_suffix(&[30]), Some(&[10, 40][..]));
    -assert_eq!(v.strip_suffix(&[40, 30]), Some(&[10][..]));
    -assert_eq!(v.strip_suffix(&[50]), None);
    -assert_eq!(v.strip_suffix(&[50, 30]), None);
    -

    Binary searches this slice for a given element. -This behaves similarly to contains if this slice is sorted.

    -

    If the value is found then Result::Ok is returned, containing the -index of the matching element. If there are multiple matches, then any -one of the matches could be returned. The index is chosen -deterministically, but is subject to change in future versions of Rust. -If the value is not found then Result::Err is returned, containing -the index where a matching element could be inserted while maintaining -sorted order.

    -

    See also binary_search_by, binary_search_by_key, and partition_point.

    -
    Examples
    -

    Looks up a series of four elements. The first is found, with a -uniquely determined position; the second and third are not -found; the fourth could match any position in [1, 4].

    - -
    let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
    -
    -assert_eq!(s.binary_search(&13),  Ok(9));
    -assert_eq!(s.binary_search(&4),   Err(7));
    -assert_eq!(s.binary_search(&100), Err(13));
    -let r = s.binary_search(&1);
    -assert!(match r { Ok(1..=4) => true, _ => false, });
    -

    If you want to find that whole range of matching items, rather than -an arbitrary matching one, that can be done using partition_point:

    - -
    let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
    -
    -let low = s.partition_point(|x| x < &1);
    -assert_eq!(low, 1);
    -let high = s.partition_point(|x| x <= &1);
    -assert_eq!(high, 5);
    -let r = s.binary_search(&1);
    -assert!((low..high).contains(&r.unwrap()));
    -
    -assert!(s[..low].iter().all(|&x| x < 1));
    -assert!(s[low..high].iter().all(|&x| x == 1));
    -assert!(s[high..].iter().all(|&x| x > 1));
    -
    -// For something not found, the "range" of equal items is empty
    -assert_eq!(s.partition_point(|x| x < &11), 9);
    -assert_eq!(s.partition_point(|x| x <= &11), 9);
    -assert_eq!(s.binary_search(&11), Err(9));
    -

    If you want to insert an item to a sorted vector, while maintaining -sort order, consider using partition_point:

    - -
    let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
    -let num = 42;
    -let idx = s.partition_point(|&x| x < num);
    -// The above is equivalent to `let idx = s.binary_search(&num).unwrap_or_else(|x| x);`
    -s.insert(idx, num);
    -assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
    -

    Binary searches this slice with a comparator function. -This behaves similarly to contains if this slice is sorted.

    -

    The comparator function should implement an order consistent -with the sort order of the underlying slice, returning an -order code that indicates whether its argument is Less, -Equal or Greater the desired target.

    -

    If the value is found then Result::Ok is returned, containing the -index of the matching element. If there are multiple matches, then any -one of the matches could be returned. The index is chosen -deterministically, but is subject to change in future versions of Rust. -If the value is not found then Result::Err is returned, containing -the index where a matching element could be inserted while maintaining -sorted order.

    -

    See also binary_search, binary_search_by_key, and partition_point.

    -
    Examples
    -

    Looks up a series of four elements. The first is found, with a -uniquely determined position; the second and third are not -found; the fourth could match any position in [1, 4].

    - -
    let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
    -
    -let seek = 13;
    -assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Ok(9));
    -let seek = 4;
    -assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(7));
    -let seek = 100;
    -assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(13));
    -let seek = 1;
    -let r = s.binary_search_by(|probe| probe.cmp(&seek));
    -assert!(match r { Ok(1..=4) => true, _ => false, });
    -

    Binary searches this slice with a key extraction function. -This behaves similarly to contains if this slice is sorted.

    -

    Assumes that the slice is sorted by the key, for instance with -sort_by_key using the same key extraction function.

    -

    If the value is found then Result::Ok is returned, containing the -index of the matching element. If there are multiple matches, then any -one of the matches could be returned. The index is chosen -deterministically, but is subject to change in future versions of Rust. -If the value is not found then Result::Err is returned, containing -the index where a matching element could be inserted while maintaining -sorted order.

    -

    See also binary_search, binary_search_by, and partition_point.

    -
    Examples
    -

    Looks up a series of four elements in a slice of pairs sorted by -their second elements. The first is found, with a uniquely -determined position; the second and third are not found; the -fourth could match any position in [1, 4].

    - -
    let s = [(0, 0), (2, 1), (4, 1), (5, 1), (3, 1),
    -         (1, 2), (2, 3), (4, 5), (5, 8), (3, 13),
    -         (1, 21), (2, 34), (4, 55)];
    -
    -assert_eq!(s.binary_search_by_key(&13, |&(a, b)| b),  Ok(9));
    -assert_eq!(s.binary_search_by_key(&4, |&(a, b)| b),   Err(7));
    -assert_eq!(s.binary_search_by_key(&100, |&(a, b)| b), Err(13));
    -let r = s.binary_search_by_key(&1, |&(a, b)| b);
    -assert!(match r { Ok(1..=4) => true, _ => false, });
    -

    Transmute the slice to a slice of another type, ensuring alignment of the types is -maintained.

    -

    This method splits the slice into three distinct slices: prefix, correctly aligned middle -slice of a new type, and the suffix slice. The method may make the middle slice the greatest -length possible for a given type and input slice, but only your algorithm’s performance -should depend on that, not its correctness. It is permissible for all of the input data to -be returned as the prefix or suffix slice.

    -

    This method has no purpose when either input element T or output element U are -zero-sized and will return the original slice without splitting anything.

    -
    Safety
    -

    This method is essentially a transmute with respect to the elements in the returned -middle slice, so all the usual caveats pertaining to transmute::<T, U> also apply here.

    -
    Examples
    -

    Basic usage:

    - -
    unsafe {
    -    let bytes: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
    -    let (prefix, shorts, suffix) = bytes.align_to::<u16>();
    -    // less_efficient_algorithm_for_bytes(prefix);
    -    // more_efficient_algorithm_for_aligned_shorts(shorts);
    -    // less_efficient_algorithm_for_bytes(suffix);
    -}
    -
    🔬This is a nightly-only experimental API. (portable_simd)

    Split a slice into a prefix, a middle of aligned SIMD types, and a suffix.

    -

    This is a safe wrapper around slice::align_to, so has the same weak -postconditions as that method. You’re only assured that -self.len() == prefix.len() + middle.len() * LANES + suffix.len().

    -

    Notably, all of the following are possible:

    -
      -
    • prefix.len() >= LANES.
    • -
    • middle.is_empty() despite self.len() >= 3 * LANES.
    • -
    • suffix.len() >= LANES.
    • -
    -

    That said, this is a safe method, so if you’re only writing safe code, -then this can at most cause incorrect logic, not unsoundness.

    -
    Panics
    -

    This will panic if the size of the SIMD type is different from -LANES times that of the scalar.

    -

    At the time of writing, the trait restrictions on Simd<T, LANES> keeps -that from ever happening, as only power-of-two numbers of lanes are -supported. It’s possible that, in the future, those restrictions might -be lifted in a way that would make it possible to see panics from this -method for something like LANES == 3.

    -
    Examples
    -
    #![feature(portable_simd)]
    -use core::simd::SimdFloat;
    -
    -let short = &[1, 2, 3];
    -let (prefix, middle, suffix) = short.as_simd::<4>();
    -assert_eq!(middle, []); // Not enough elements for anything in the middle
    -
    -// They might be split in any possible way between prefix and suffix
    -let it = prefix.iter().chain(suffix).copied();
    -assert_eq!(it.collect::<Vec<_>>(), vec![1, 2, 3]);
    -
    -fn basic_simd_sum(x: &[f32]) -> f32 {
    -    use std::ops::Add;
    -    use std::simd::f32x4;
    -    let (prefix, middle, suffix) = x.as_simd();
    -    let sums = f32x4::from_array([
    -        prefix.iter().copied().sum(),
    -        0.0,
    -        0.0,
    -        suffix.iter().copied().sum(),
    -    ]);
    -    let sums = middle.iter().copied().fold(sums, f32x4::add);
    -    sums.reduce_sum()
    -}
    -
    -let numbers: Vec<f32> = (1..101).map(|x| x as _).collect();
    -assert_eq!(basic_simd_sum(&numbers[1..99]), 4949.0);
    -
    🔬This is a nightly-only experimental API. (is_sorted)

    Checks if the elements of this slice are sorted.

    -

    That is, for each element a and its following element b, a <= b must hold. If the -slice yields exactly zero or one element, true is returned.

    -

    Note that if Self::Item is only PartialOrd, but not Ord, the above definition -implies that this function returns false if any two consecutive items are not -comparable.

    -
    Examples
    -
    #![feature(is_sorted)]
    -let empty: [i32; 0] = [];
    -
    -assert!([1, 2, 2, 9].is_sorted());
    -assert!(![1, 3, 2, 4].is_sorted());
    -assert!([0].is_sorted());
    -assert!(empty.is_sorted());
    -assert!(![0.0, 1.0, f32::NAN].is_sorted());
    -
    🔬This is a nightly-only experimental API. (is_sorted)

    Checks if the elements of this slice are sorted using the given comparator function.

    -

    Instead of using PartialOrd::partial_cmp, this function uses the given compare -function to determine the ordering of two elements. Apart from that, it’s equivalent to -is_sorted; see its documentation for more information.

    -
    🔬This is a nightly-only experimental API. (is_sorted)

    Checks if the elements of this slice are sorted using the given key extraction function.

    -

    Instead of comparing the slice’s elements directly, this function compares the keys of the -elements, as determined by f. Apart from that, it’s equivalent to is_sorted; see its -documentation for more information.

    -
    Examples
    -
    #![feature(is_sorted)]
    -
    -assert!(["c", "bb", "aaa"].is_sorted_by_key(|s| s.len()));
    -assert!(![-2i32, -1, 0, 3].is_sorted_by_key(|n| n.abs()));
    -

    Returns the index of the partition point according to the given predicate -(the index of the first element of the second partition).

    -

    The slice is assumed to be partitioned according to the given predicate. -This means that all elements for which the predicate returns true are at the start of the slice -and all elements for which the predicate returns false are at the end. -For example, [7, 15, 3, 5, 4, 12, 6] is a partitioned under the predicate x % 2 != 0 -(all odd numbers are at the start, all even at the end).

    -

    If this slice is not partitioned, the returned result is unspecified and meaningless, -as this method performs a kind of binary search.

    -

    See also binary_search, binary_search_by, and binary_search_by_key.

    -
    Examples
    -
    let v = [1, 2, 3, 3, 5, 6, 7];
    -let i = v.partition_point(|&x| x < 5);
    -
    -assert_eq!(i, 4);
    -assert!(v[..i].iter().all(|&x| x < 5));
    -assert!(v[i..].iter().all(|&x| !(x < 5)));
    -

    If all elements of the slice match the predicate, including if the slice -is empty, then the length of the slice will be returned:

    - -
    let a = [2, 4, 8];
    -assert_eq!(a.partition_point(|x| x < &100), a.len());
    -let a: [i32; 0] = [];
    -assert_eq!(a.partition_point(|x| x < &100), 0);
    -

    If you want to insert an item to a sorted vector, while maintaining -sort order:

    - -
    let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
    -let num = 42;
    -let idx = s.partition_point(|&x| x < num);
    -s.insert(idx, num);
    -assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
    -
    🔬This is a nightly-only experimental API. (slice_flatten)

    Takes a &[[T; N]], and flattens it to a &[T].

    -
    Panics
    -

    This panics if the length of the resulting slice would overflow a usize.

    -

    This is only possible when flattening a slice of arrays of zero-sized -types, and thus tends to be irrelevant in practice. If -size_of::<T>() > 0, this will never panic.

    -
    Examples
    -
    #![feature(slice_flatten)]
    -
    -assert_eq!([[1, 2, 3], [4, 5, 6]].flatten(), &[1, 2, 3, 4, 5, 6]);
    -
    -assert_eq!(
    -    [[1, 2, 3], [4, 5, 6]].flatten(),
    -    [[1, 2], [3, 4], [5, 6]].flatten(),
    -);
    -
    -let slice_of_empty_arrays: &[[i32; 0]] = &[[], [], [], [], []];
    -assert!(slice_of_empty_arrays.flatten().is_empty());
    -
    -let empty_slice_of_arrays: &[[u32; 10]] = &[];
    -assert!(empty_slice_of_arrays.flatten().is_empty());
    -

    Checks if all bytes in this slice are within the ASCII range.

    -

    Checks that two slices are an ASCII case-insensitive match.

    -

    Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), -but without allocating and copying temporaries.

    -

    Returns an iterator that produces an escaped version of this slice, -treating it as an ASCII string.

    -
    Examples
    -
    
    -let s = b"0\t\r\n'\"\\\x9d";
    -let escaped = s.escape_ascii().to_string();
    -assert_eq!(escaped, "0\\t\\r\\n\\'\\\"\\\\\\x9d");
    -
    🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

    Returns a byte slice with leading ASCII whitespace bytes removed.

    -

    ‘Whitespace’ refers to the definition used by -u8::is_ascii_whitespace.

    -
    Examples
    -
    #![feature(byte_slice_trim_ascii)]
    -
    -assert_eq!(b" \t hello world\n".trim_ascii_start(), b"hello world\n");
    -assert_eq!(b"  ".trim_ascii_start(), b"");
    -assert_eq!(b"".trim_ascii_start(), b"");
    -
    🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

    Returns a byte slice with trailing ASCII whitespace bytes removed.

    -

    ‘Whitespace’ refers to the definition used by -u8::is_ascii_whitespace.

    -
    Examples
    -
    #![feature(byte_slice_trim_ascii)]
    -
    -assert_eq!(b"\r hello world\n ".trim_ascii_end(), b"\r hello world");
    -assert_eq!(b"  ".trim_ascii_end(), b"");
    -assert_eq!(b"".trim_ascii_end(), b"");
    -
    🔬This is a nightly-only experimental API. (byte_slice_trim_ascii)

    Returns a byte slice with leading and trailing ASCII whitespace bytes -removed.

    -

    ‘Whitespace’ refers to the definition used by -u8::is_ascii_whitespace.

    -
    Examples
    -
    #![feature(byte_slice_trim_ascii)]
    -
    -assert_eq!(b"\r hello world\n ".trim_ascii(), b"hello world");
    -assert_eq!(b"  ".trim_ascii(), b"");
    -assert_eq!(b"".trim_ascii(), b"");
    -

    Returns a vector containing a copy of this slice where each byte -is mapped to its ASCII upper case equivalent.

    -

    ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, -but non-ASCII letters are unchanged.

    -

    To uppercase the value in-place, use make_ascii_uppercase.

    -

    Returns a vector containing a copy of this slice where each byte -is mapped to its ASCII lower case equivalent.

    -

    ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, -but non-ASCII letters are unchanged.

    -

    To lowercase the value in-place, use make_ascii_lowercase.

    -

    Copies self into a new Vec.

    -
    Examples
    -
    let s = [10, 40, 30];
    -let x = s.to_vec();
    -// Here, `s` and `x` can be modified independently.
    -
    🔬This is a nightly-only experimental API. (allocator_api)

    Copies self into a new Vec with an allocator.

    -
    Examples
    -
    #![feature(allocator_api)]
    -
    -use std::alloc::System;
    -
    -let s = [10, 40, 30];
    -let x = s.to_vec_in(System);
    -// Here, `s` and `x` can be modified independently.
    -

    Creates a vector by repeating a slice n times.

    -
    Panics
    -

    This function will panic if the capacity would overflow.

    -
    Examples
    -

    Basic usage:

    - -
    assert_eq!([1, 2].repeat(3), vec![1, 2, 1, 2, 1, 2]);
    -

    A panic upon overflow:

    - -
    // this will panic at runtime
    -b"0123456789abcdef".repeat(usize::MAX);
    -

    Flattens a slice of T into a single value Self::Output.

    -
    Examples
    -
    assert_eq!(["hello", "world"].concat(), "helloworld");
    -assert_eq!([[1, 2], [3, 4]].concat(), [1, 2, 3, 4]);
    -

    Flattens a slice of T into a single value Self::Output, placing a -given separator between each.

    -
    Examples
    -
    assert_eq!(["hello", "world"].join(" "), "hello world");
    -assert_eq!([[1, 2], [3, 4]].join(&0), [1, 2, 0, 3, 4]);
    -assert_eq!([[1, 2], [3, 4]].join(&[0, 0][..]), [1, 2, 0, 0, 3, 4]);
    -
    👎Deprecated since 1.3.0: renamed to join

    Flattens a slice of T into a single value Self::Output, placing a -given separator between each.

    -
    Examples
    -
    assert_eq!(["hello", "world"].connect(" "), "hello world");
    -assert_eq!([[1, 2], [3, 4]].connect(&0), [1, 2, 0, 3, 4]);
    -

    Trait Implementations

    The resulting type after dereferencing.
    Dereferences the value.

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/struct.RefMut.html b/docs/firewood/merkle/struct.RefMut.html deleted file mode 100644 index 8bb946c0c5b4..000000000000 --- a/docs/firewood/merkle/struct.RefMut.html +++ /dev/null @@ -1,5 +0,0 @@ -RefMut in firewood::merkle - Rust
    pub struct RefMut<'a> { /* private fields */ }

    Implementations

    Auto Trait Implementations

    Blanket Implementations

    Gets the TypeId of self. Read more
    Immutably borrows from an owned value. Read more
    Mutably borrows from an owned value. Read more

    Returns the argument unchanged.

    -

    Calls U::from(self).

    -

    That is, this conversion is whatever the implementation of -From<T> for U chooses to do.

    -
    Should always be Self
    The type returned in the event of a conversion error.
    Performs the conversion.
    The type returned in the event of a conversion error.
    Performs the conversion.
    \ No newline at end of file diff --git a/docs/firewood/merkle/trait.ValueTransformer.html b/docs/firewood/merkle/trait.ValueTransformer.html deleted file mode 100644 index 690841a320e6..000000000000 --- a/docs/firewood/merkle/trait.ValueTransformer.html +++ /dev/null @@ -1,3 +0,0 @@ -ValueTransformer in firewood::merkle - Rust
    pub trait ValueTransformer {
    -    fn transform(bytes: &[u8]) -> Vec<u8>Notable traits for Vec<u8, A>impl<A> Write for Vec<u8, A>where
        A: Allocator,
    ; -}

    Required Methods

    Implementors

    \ No newline at end of file diff --git a/docs/firewood/sidebar-items.js b/docs/firewood/sidebar-items.js deleted file mode 100644 index e49d00d449bc..000000000000 --- a/docs/firewood/sidebar-items.js +++ /dev/null @@ -1 +0,0 @@ -window.SIDEBAR_ITEMS = {"mod":[["db",""],["merkle",""],["proof",""]]}; \ No newline at end of file diff --git a/docs/firewood/storage/struct.DiskBufferConfig.html b/docs/firewood/storage/struct.DiskBufferConfig.html deleted file mode 100644 index af27f85453ed..000000000000 --- a/docs/firewood/storage/struct.DiskBufferConfig.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Redirection - - -

    Redirecting to ../../firewood/db/struct.DiskBufferConfig.html...

    - - - \ No newline at end of file diff --git a/docs/firewood/storage/struct.WALConfig.html b/docs/firewood/storage/struct.WALConfig.html deleted file mode 100644 index ccfe6aa53864..000000000000 --- a/docs/firewood/storage/struct.WALConfig.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Redirection - - -

    Redirecting to ../../firewood/db/struct.WALConfig.html...

    - - - \ No newline at end of file diff --git a/docs/implementors/core/clone/trait.Clone.js b/docs/implementors/core/clone/trait.Clone.js deleted file mode 100644 index 4ec68431b861..000000000000 --- a/docs/implementors/core/clone/trait.Clone.js +++ /dev/null @@ -1,48 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Clone for AHasher"],["impl Clone for RandomState"]], -"aho_corasick":[["impl<S: Clone + StateID> Clone for AhoCorasick<S>"],["impl Clone for AhoCorasickBuilder"],["impl Clone for MatchKind"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for MatchKind"],["impl Clone for Config"],["impl Clone for Builder"],["impl Clone for Searcher"],["impl Clone for Match"]], -"bincode":[["impl Clone for LittleEndian"],["impl Clone for BigEndian"],["impl Clone for NativeEndian"],["impl Clone for FixintEncoding"],["impl Clone for VarintEncoding"],["impl Clone for Config"],["impl Clone for Bounded"],["impl Clone for Infinite"],["impl Clone for AllowTrailing"],["impl Clone for RejectTrailing"],["impl Clone for DefaultOptions"],["impl<O: Clone + Options, L: Clone + SizeLimit> Clone for WithOtherLimit<O, L>"],["impl<O: Clone + Options, E: Clone + BincodeByteOrder> Clone for WithOtherEndian<O, E>"],["impl<O: Clone + Options, I: Clone + IntEncoding> Clone for WithOtherIntEncoding<O, I>"],["impl<O: Clone + Options, T: Clone + TrailingBytes> Clone for WithOtherTrailing<O, T>"]], -"block_buffer":[["impl Clone for Eager"],["impl Clone for Lazy"],["impl Clone for Error"],["impl<BlockSize, Kind> Clone for BlockBuffer<BlockSize, Kind>where
        BlockSize: ArrayLength<u8> + IsLess<U256>,
        Le<BlockSize, U256>: NonZero,
        Kind: BufferKind,
    "]], -"byteorder":[["impl Clone for BigEndian"],["impl Clone for LittleEndian"]], -"bytes":[["impl Clone for Bytes"],["impl Clone for BytesMut"]], -"crc":[["impl<'a, W: Clone + Width> Clone for Digest<'a, W>"]], -"crossbeam_channel":[["impl<T> Clone for Sender<T>"],["impl<T> Clone for Receiver<T>"],["impl<T: Clone> Clone for SendError<T>"],["impl<T: Clone> Clone for TrySendError<T>"],["impl<T: Clone> Clone for SendTimeoutError<T>"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl Clone for RecvTimeoutError"],["impl Clone for TrySelectError"],["impl Clone for SelectTimeoutError"],["impl Clone for TryReadyError"],["impl Clone for ReadyTimeoutError"],["impl<'a> Clone for Select<'a>"]], -"crossbeam_utils":[["impl<T: Clone> Clone for CachePadded<T>"],["impl Clone for Unparker"],["impl Clone for WaitGroup"]], -"crypto_common":[["impl Clone for InvalidLength"]], -"digest":[["impl<T: Clone, OutSize: Clone, O: Clone> Clone for CtVariableCoreWrapper<T, OutSize, O>where
        T: VariableOutputCore,
        OutSize: ArrayLength<u8> + IsLessOrEqual<T::OutputSize>,
        LeEq<OutSize, T::OutputSize>: NonZero,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
    "],["impl<T: Clone> Clone for RtVariableCoreWrapper<T>where
        T: VariableOutputCore + UpdateCore,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
        T::BlockSize: Clone,
        T::BufferKind: Clone,
    "],["impl<T: Clone> Clone for CoreWrapper<T>where
        T: BufferKindUser,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
        T::BlockSize: Clone,
        T::BufferKind: Clone,
    "],["impl<T: Clone> Clone for XofReaderCoreWrapper<T>where
        T: XofReaderCore,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
        T::BlockSize: Clone,
    "],["impl Clone for TruncSide"],["impl Clone for Box<dyn DynDigest>"],["impl Clone for InvalidOutputSize"],["impl Clone for InvalidBufferSize"]], -"firewood":[["impl Clone for DBRevConfig"],["impl Clone for Hash"],["impl Clone for PartialPath"],["impl Clone for Node"],["impl Clone for WALConfig"],["impl Clone for DiskBufferConfig"]], -"futures_channel":[["impl Clone for SendError"],["impl<T: Clone> Clone for TrySendError<T>"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for UnboundedSender<T>"],["impl Clone for Canceled"]], -"futures_executor":[["impl Clone for LocalSpawner"]], -"futures_util":[["impl<Fut: Future> Clone for WeakShared<Fut>"],["impl<Fut> Clone for Shared<Fut>where
        Fut: Future,
    "],["impl<T> Clone for Pending<T>"],["impl<F: Clone> Clone for OptionFuture<F>"],["impl<T: Clone> Clone for PollImmediate<T>"],["impl<T: Clone> Clone for Ready<T>"],["impl<A: Clone, B: Clone> Clone for Either<A, B>"],["impl<I: Clone> Clone for Iter<I>"],["impl<T: Clone> Clone for Repeat<T>"],["impl<F: Clone> Clone for RepeatWith<F>"],["impl<T> Clone for Empty<T>"],["impl<T> Clone for Pending<T>"],["impl<S: Clone> Clone for PollImmediate<S>"],["impl Clone for PollNext"],["impl<T> Clone for Drain<T>"],["impl<Si: Clone, F: Clone> Clone for SinkMapErr<Si, F>"],["impl<Si, Item, U, Fut, F> Clone for With<Si, Item, U, Fut, F>where
        Si: Clone,
        F: Clone,
        Fut: Clone,
    "],["impl<T: Clone> Clone for AllowStdIo<T>"],["impl<T: Clone> Clone for Cursor<T>"],["impl<T: Clone> Clone for Abortable<T>"],["impl Clone for AbortHandle"],["impl Clone for Aborted"]], -"generic_array":[["impl<T: Clone, N> Clone for GenericArray<T, N>where
        N: ArrayLength<T>,
    "],["impl<T: Clone, N> Clone for GenericArrayIter<T, N>where
        N: ArrayLength<T>,
    "]], -"getrandom":[["impl Clone for Error"]], -"growthring":[["impl Clone for WALRingId"],["impl Clone for RecoverPolicy"]], -"hashbrown":[["impl<K: Clone, V: Clone, S: Clone, A: Allocator + Clone> Clone for HashMap<K, V, S, A>"],["impl<K, V> Clone for Iter<'_, K, V>"],["impl<K, V> Clone for Keys<'_, K, V>"],["impl<K, V> Clone for Values<'_, K, V>"],["impl<T: Clone, S: Clone, A: Allocator + Clone> Clone for HashSet<T, S, A>"],["impl<K> Clone for Iter<'_, K>"],["impl<T, S, A: Allocator + Clone> Clone for Intersection<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for Difference<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for SymmetricDifference<'_, T, S, A>"],["impl<T, S, A: Allocator + Clone> Clone for Union<'_, T, S, A>"],["impl Clone for TryReserveError"]], -"hex":[["impl Clone for FromHexError"]], -"libc":[["impl Clone for DIR"],["impl Clone for group"],["impl Clone for utimbuf"],["impl Clone for timeval"],["impl Clone for timespec"],["impl Clone for rlimit"],["impl Clone for rusage"],["impl Clone for ipv6_mreq"],["impl Clone for hostent"],["impl Clone for iovec"],["impl Clone for pollfd"],["impl Clone for winsize"],["impl Clone for linger"],["impl Clone for sigval"],["impl Clone for itimerval"],["impl Clone for tms"],["impl Clone for servent"],["impl Clone for protoent"],["impl Clone for FILE"],["impl Clone for fpos_t"],["impl Clone for timezone"],["impl Clone for in_addr"],["impl Clone for ip_mreq"],["impl Clone for ip_mreqn"],["impl Clone for ip_mreq_source"],["impl Clone for sockaddr"],["impl Clone for sockaddr_in"],["impl Clone for sockaddr_in6"],["impl Clone for addrinfo"],["impl Clone for sockaddr_ll"],["impl Clone for fd_set"],["impl Clone for tm"],["impl Clone for sched_param"],["impl Clone for Dl_info"],["impl Clone for lconv"],["impl Clone for in_pktinfo"],["impl Clone for ifaddrs"],["impl Clone for in6_rtmsg"],["impl Clone for arpreq"],["impl Clone for arpreq_old"],["impl Clone for arphdr"],["impl Clone for mmsghdr"],["impl Clone for epoll_event"],["impl Clone for sockaddr_un"],["impl Clone for sockaddr_storage"],["impl Clone for utsname"],["impl Clone for sigevent"],["impl Clone for fpos64_t"],["impl Clone for rlimit64"],["impl Clone for glob_t"],["impl Clone for passwd"],["impl Clone for spwd"],["impl Clone for dqblk"],["impl Clone for signalfd_siginfo"],["impl Clone for itimerspec"],["impl Clone for fsid_t"],["impl Clone for packet_mreq"],["impl Clone for cpu_set_t"],["impl Clone for if_nameindex"],["impl Clone for msginfo"],["impl Clone for sembuf"],["impl Clone for input_event"],["impl Clone for input_id"],["impl Clone for input_absinfo"],["impl Clone for input_keymap_entry"],["impl Clone for input_mask"],["impl Clone for ff_replay"],["impl Clone for ff_trigger"],["impl Clone for ff_envelope"],["impl Clone for ff_constant_effect"],["impl Clone for ff_ramp_effect"],["impl Clone for ff_condition_effect"],["impl Clone for ff_periodic_effect"],["impl Clone for ff_rumble_effect"],["impl Clone for ff_effect"],["impl Clone for uinput_ff_upload"],["impl Clone for uinput_ff_erase"],["impl Clone for uinput_abs_setup"],["impl Clone for dl_phdr_info"],["impl Clone for Elf32_Ehdr"],["impl Clone for Elf64_Ehdr"],["impl Clone for Elf32_Sym"],["impl Clone for Elf64_Sym"],["impl Clone for Elf32_Phdr"],["impl Clone for Elf64_Phdr"],["impl Clone for Elf32_Shdr"],["impl Clone for Elf64_Shdr"],["impl Clone for ucred"],["impl Clone for mntent"],["impl Clone for posix_spawn_file_actions_t"],["impl Clone for posix_spawnattr_t"],["impl Clone for genlmsghdr"],["impl Clone for in6_pktinfo"],["impl Clone for arpd_request"],["impl Clone for inotify_event"],["impl Clone for fanotify_response"],["impl Clone for sockaddr_vm"],["impl Clone for regmatch_t"],["impl Clone for sock_extended_err"],["impl Clone for __c_anonymous_sockaddr_can_tp"],["impl Clone for __c_anonymous_sockaddr_can_j1939"],["impl Clone for can_filter"],["impl Clone for j1939_filter"],["impl Clone for sock_filter"],["impl Clone for sock_fprog"],["impl Clone for seccomp_data"],["impl Clone for nlmsghdr"],["impl Clone for nlmsgerr"],["impl Clone for nlattr"],["impl Clone for file_clone_range"],["impl Clone for __c_anonymous_ifru_map"],["impl Clone for in6_ifreq"],["impl Clone for sockaddr_nl"],["impl Clone for dirent"],["impl Clone for dirent64"],["impl Clone for sockaddr_alg"],["impl Clone for uinput_setup"],["impl Clone for uinput_user_dev"],["impl Clone for af_alg_iv"],["impl Clone for mq_attr"],["impl Clone for __c_anonymous_ifr_ifru"],["impl Clone for ifreq"],["impl Clone for sock_txtime"],["impl Clone for __c_anonymous_sockaddr_can_can_addr"],["impl Clone for sockaddr_can"],["impl Clone for statx"],["impl Clone for statx_timestamp"],["impl Clone for aiocb"],["impl Clone for __exit_status"],["impl Clone for __timeval"],["impl Clone for glob64_t"],["impl Clone for msghdr"],["impl Clone for cmsghdr"],["impl Clone for termios"],["impl Clone for mallinfo"],["impl Clone for mallinfo2"],["impl Clone for nl_pktinfo"],["impl Clone for nl_mmap_req"],["impl Clone for nl_mmap_hdr"],["impl Clone for rtentry"],["impl Clone for timex"],["impl Clone for ntptimeval"],["impl Clone for regex_t"],["impl Clone for Elf64_Chdr"],["impl Clone for Elf32_Chdr"],["impl Clone for seminfo"],["impl Clone for ptrace_peeksiginfo_args"],["impl Clone for __c_anonymous_ptrace_syscall_info_entry"],["impl Clone for __c_anonymous_ptrace_syscall_info_exit"],["impl Clone for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Clone for ptrace_syscall_info"],["impl Clone for __c_anonymous_ptrace_syscall_info_data"],["impl Clone for utmpx"],["impl Clone for sigset_t"],["impl Clone for sysinfo"],["impl Clone for msqid_ds"],["impl Clone for semid_ds"],["impl Clone for sigaction"],["impl Clone for statfs"],["impl Clone for flock"],["impl Clone for flock64"],["impl Clone for siginfo_t"],["impl Clone for stack_t"],["impl Clone for stat"],["impl Clone for stat64"],["impl Clone for statfs64"],["impl Clone for statvfs64"],["impl Clone for pthread_attr_t"],["impl Clone for _libc_fpxreg"],["impl Clone for _libc_xmmreg"],["impl Clone for _libc_fpstate"],["impl Clone for user_regs_struct"],["impl Clone for user"],["impl Clone for mcontext_t"],["impl Clone for ipc_perm"],["impl Clone for shmid_ds"],["impl Clone for seccomp_notif_sizes"],["impl Clone for ptrace_rseq_configuration"],["impl Clone for user_fpregs_struct"],["impl Clone for ucontext_t"],["impl Clone for statvfs"],["impl Clone for max_align_t"],["impl Clone for clone_args"],["impl Clone for sem_t"],["impl Clone for termios2"],["impl Clone for pthread_mutexattr_t"],["impl Clone for pthread_rwlockattr_t"],["impl Clone for pthread_condattr_t"],["impl Clone for fanotify_event_metadata"],["impl Clone for pthread_cond_t"],["impl Clone for pthread_mutex_t"],["impl Clone for pthread_rwlock_t"],["impl Clone for can_frame"],["impl Clone for canfd_frame"],["impl Clone for open_how"],["impl Clone for in6_addr"]], -"lru":[["impl<'a, K, V> Clone for Iter<'a, K, V>"]], -"memchr":[["impl Clone for Prefilter"],["impl<'n> Clone for Finder<'n>"],["impl<'n> Clone for FinderRev<'n>"],["impl Clone for FinderBuilder"]], -"nix":[["impl Clone for Entry"],["impl Clone for Type"],["impl Clone for ClearEnvError"],["impl Clone for Errno"],["impl Clone for AtFlags"],["impl Clone for OFlag"],["impl Clone for RenameFlags"],["impl Clone for SealFlag"],["impl Clone for FdFlag"],["impl Clone for FlockArg"],["impl Clone for SpliceFFlags"],["impl Clone for FallocateFlags"],["impl Clone for PosixFadviseAdvice"],["impl Clone for InterfaceAddress"],["impl Clone for InterfaceFlags"],["impl Clone for ModuleInitFlags"],["impl Clone for DeleteModuleFlags"],["impl Clone for MsFlags"],["impl Clone for MntFlags"],["impl Clone for MQ_OFlag"],["impl Clone for MqAttr"],["impl Clone for PollFd"],["impl Clone for PollFlags"],["impl Clone for OpenptyResult"],["impl Clone for ForkptyResult"],["impl Clone for CloneFlags"],["impl Clone for CpuSet"],["impl Clone for AioFsyncMode"],["impl Clone for LioMode"],["impl Clone for AioCancelStat"],["impl Clone for EpollFlags"],["impl Clone for EpollOp"],["impl Clone for EpollCreateFlags"],["impl Clone for EpollEvent"],["impl Clone for EfdFlags"],["impl Clone for MemFdCreateFlag"],["impl Clone for ProtFlags"],["impl Clone for MapFlags"],["impl Clone for MRemapFlags"],["impl Clone for MmapAdvise"],["impl Clone for MsFlags"],["impl Clone for MlockAllFlags"],["impl Clone for Persona"],["impl Clone for Request"],["impl Clone for Event"],["impl Clone for Options"],["impl Clone for QuotaType"],["impl Clone for QuotaFmt"],["impl Clone for QuotaValidFlags"],["impl Clone for Dqblk"],["impl Clone for RebootMode"],["impl Clone for Resource"],["impl Clone for UsageWho"],["impl Clone for Usage"],["impl Clone for FdSet"],["impl Clone for Signal"],["impl Clone for SignalIterator"],["impl Clone for SaFlags"],["impl Clone for SigmaskHow"],["impl Clone for SigSet"],["impl<'a> Clone for SigSetIter<'a>"],["impl Clone for SigHandler"],["impl Clone for SigAction"],["impl Clone for SigevNotify"],["impl Clone for SigEvent"],["impl Clone for SfdFlags"],["impl Clone for AddressFamily"],["impl Clone for InetAddr"],["impl Clone for IpAddr"],["impl Clone for Ipv4Addr"],["impl Clone for Ipv6Addr"],["impl Clone for UnixAddr"],["impl Clone for SockaddrIn"],["impl Clone for SockaddrIn6"],["impl Clone for SockaddrStorage"],["impl Clone for SockAddr"],["impl Clone for NetlinkAddr"],["impl Clone for AlgAddr"],["impl Clone for LinkAddr"],["impl Clone for VsockAddr"],["impl Clone for ReuseAddr"],["impl Clone for ReusePort"],["impl Clone for TcpNoDelay"],["impl Clone for Linger"],["impl Clone for IpAddMembership"],["impl Clone for IpDropMembership"],["impl Clone for Ipv6AddMembership"],["impl Clone for Ipv6DropMembership"],["impl Clone for IpMulticastTtl"],["impl Clone for IpMulticastLoop"],["impl Clone for Priority"],["impl Clone for IpTos"],["impl Clone for Ipv6TClass"],["impl Clone for IpFreebind"],["impl Clone for ReceiveTimeout"],["impl Clone for SendTimeout"],["impl Clone for Broadcast"],["impl Clone for OobInline"],["impl Clone for SocketError"],["impl Clone for DontRoute"],["impl Clone for KeepAlive"],["impl Clone for PeerCredentials"],["impl Clone for TcpKeepIdle"],["impl Clone for TcpMaxSeg"],["impl Clone for TcpKeepCount"],["impl Clone for TcpRepair"],["impl Clone for TcpKeepInterval"],["impl Clone for TcpUserTimeout"],["impl Clone for RcvBuf"],["impl Clone for SndBuf"],["impl Clone for RcvBufForce"],["impl Clone for SndBufForce"],["impl Clone for SockType"],["impl Clone for AcceptConn"],["impl Clone for BindToDevice"],["impl Clone for OriginalDst"],["impl Clone for Ip6tOriginalDst"],["impl Clone for Timestamping"],["impl Clone for ReceiveTimestamp"],["impl Clone for ReceiveTimestampns"],["impl Clone for IpTransparent"],["impl Clone for Mark"],["impl Clone for PassCred"],["impl Clone for TcpCongestion"],["impl Clone for Ipv4PacketInfo"],["impl Clone for Ipv6RecvPacketInfo"],["impl Clone for Ipv4OrigDstAddr"],["impl Clone for UdpGsoSegment"],["impl Clone for UdpGroSegment"],["impl Clone for TxTime"],["impl Clone for RxqOvfl"],["impl Clone for Ipv6V6Only"],["impl Clone for Ipv4RecvErr"],["impl Clone for Ipv6RecvErr"],["impl Clone for IpMtu"],["impl Clone for Ipv4Ttl"],["impl Clone for Ipv6Ttl"],["impl Clone for Ipv6OrigDstAddr"],["impl Clone for Ipv6DontFrag"],["impl Clone for AlgSetAeadAuthSize"],["impl<T: Clone> Clone for AlgSetKey<T>"],["impl Clone for SockType"],["impl Clone for SockProtocol"],["impl Clone for TimestampingFlag"],["impl Clone for SockFlag"],["impl Clone for MsgFlags"],["impl Clone for UnixCredentials"],["impl Clone for IpMembershipRequest"],["impl Clone for Ipv6MembershipRequest"],["impl<'a, 's, S: Clone> Clone for RecvMsg<'a, 's, S>"],["impl<'a> Clone for CmsgIterator<'a>"],["impl Clone for ControlMessageOwned"],["impl Clone for Timestamps"],["impl<'a> Clone for ControlMessage<'a>"],["impl Clone for Shutdown"],["impl Clone for SFlag"],["impl Clone for Mode"],["impl Clone for FchmodatFlags"],["impl Clone for UtimensatFlags"],["impl Clone for Statfs"],["impl Clone for FsType"],["impl Clone for FsFlags"],["impl Clone for Statvfs"],["impl Clone for SysInfo"],["impl Clone for Termios"],["impl Clone for BaudRate"],["impl Clone for SetArg"],["impl Clone for FlushArg"],["impl Clone for FlowArg"],["impl Clone for SpecialCharacterIndices"],["impl Clone for InputFlags"],["impl Clone for OutputFlags"],["impl Clone for ControlFlags"],["impl Clone for LocalFlags"],["impl Clone for Expiration"],["impl Clone for TimerSetTimeFlags"],["impl Clone for TimeSpec"],["impl Clone for TimeVal"],["impl Clone for RemoteIoVec"],["impl<T: Clone> Clone for IoVec<T>"],["impl Clone for UtsName"],["impl Clone for WaitPidFlag"],["impl Clone for WaitStatus"],["impl Clone for Id"],["impl Clone for AddWatchFlags"],["impl Clone for InitFlags"],["impl Clone for Inotify"],["impl Clone for WatchDescriptor"],["impl Clone for ClockId"],["impl Clone for TimerFlags"],["impl Clone for ClockId"],["impl Clone for UContext"],["impl Clone for Uid"],["impl Clone for Gid"],["impl Clone for Pid"],["impl Clone for ForkResult"],["impl Clone for FchownatFlags"],["impl Clone for Whence"],["impl Clone for LinkatFlags"],["impl Clone for UnlinkatFlags"],["impl Clone for PathconfVar"],["impl Clone for SysconfVar"],["impl Clone for ResUid"],["impl Clone for ResGid"],["impl Clone for AccessFlags"],["impl Clone for User"],["impl Clone for Group"]], -"once_cell":[["impl<T: Clone> Clone for OnceCell<T>"],["impl<T: Clone> Clone for OnceCell<T>"]], -"parking_lot":[["impl Clone for WaitTimeoutResult"],["impl Clone for OnceState"]], -"parking_lot_core":[["impl Clone for ParkResult"],["impl Clone for UnparkResult"],["impl Clone for RequeueOp"],["impl Clone for FilterOp"],["impl Clone for UnparkToken"],["impl Clone for ParkToken"]], -"ppv_lite86":[["impl Clone for YesS3"],["impl Clone for NoS3"],["impl Clone for YesS4"],["impl Clone for NoS4"],["impl Clone for YesA1"],["impl Clone for NoA1"],["impl Clone for YesA2"],["impl Clone for NoA2"],["impl Clone for YesNI"],["impl Clone for NoNI"],["impl<S3: Clone, S4: Clone, NI: Clone> Clone for SseMachine<S3, S4, NI>"],["impl<NI: Clone> Clone for Avx2Machine<NI>"],["impl Clone for vec128_storage"],["impl Clone for vec256_storage"],["impl Clone for vec512_storage"]], -"primitive_types":[["impl Clone for U128"],["impl Clone for U256"],["impl Clone for U512"],["impl Clone for H128"],["impl Clone for H160"],["impl Clone for H256"],["impl Clone for H384"],["impl Clone for H512"],["impl Clone for H768"]], -"proc_macro2":[["impl Clone for TokenStream"],["impl Clone for Span"],["impl Clone for TokenTree"],["impl Clone for Group"],["impl Clone for Delimiter"],["impl Clone for Punct"],["impl Clone for Spacing"],["impl Clone for Ident"],["impl Clone for Literal"],["impl Clone for IntoIter"]], -"rand":[["impl Clone for Bernoulli"],["impl Clone for BernoulliError"],["impl Clone for OpenClosed01"],["impl Clone for Open01"],["impl Clone for Alphanumeric"],["impl<'a, T: Clone> Clone for Slice<'a, T>"],["impl<X: Clone + SampleUniform + PartialOrd> Clone for WeightedIndex<X>where
        X::Sampler: Clone,
    "],["impl Clone for WeightedError"],["impl<X: Clone + SampleUniform> Clone for Uniform<X>where
        X::Sampler: Clone,
    "],["impl<X: Clone> Clone for UniformInt<X>"],["impl Clone for UniformChar"],["impl<X: Clone> Clone for UniformFloat<X>"],["impl Clone for UniformDuration"],["impl Clone for Standard"],["impl<R, Rsdr> Clone for ReseedingRng<R, Rsdr>where
        R: BlockRngCore + SeedableRng + Clone,
        Rsdr: RngCore + Clone,
    "],["impl Clone for StepRng"],["impl Clone for IndexVec"],["impl Clone for IndexVecIntoIter"]], -"rand_chacha":[["impl Clone for ChaCha20Core"],["impl Clone for ChaCha20Rng"],["impl Clone for ChaCha12Core"],["impl Clone for ChaCha12Rng"],["impl Clone for ChaCha8Core"],["impl Clone for ChaCha8Rng"]], -"rand_core":[["impl<R: Clone + BlockRngCore + ?Sized> Clone for BlockRng<R>where
        R::Results: Clone,
    "],["impl<R: Clone + BlockRngCore + ?Sized> Clone for BlockRng64<R>where
        R::Results: Clone,
    "],["impl Clone for OsRng"]], -"regex":[["impl Clone for Error"],["impl<'t> Clone for Match<'t>"],["impl Clone for Regex"],["impl<'r> Clone for CaptureNames<'r>"],["impl Clone for CaptureLocations"],["impl<'c, 't> Clone for SubCaptureMatches<'c, 't>"],["impl<'t> Clone for NoExpand<'t>"],["impl Clone for RegexSet"],["impl Clone for SetMatches"],["impl<'a> Clone for SetMatchesIter<'a>"],["impl Clone for RegexSet"],["impl Clone for SetMatches"],["impl<'a> Clone for SetMatchesIter<'a>"],["impl<'t> Clone for Match<'t>"],["impl Clone for Regex"],["impl<'r> Clone for CaptureNames<'r>"],["impl Clone for CaptureLocations"],["impl<'c, 't> Clone for SubCaptureMatches<'c, 't>"],["impl<'t> Clone for NoExpand<'t>"]], -"regex_syntax":[["impl Clone for ParserBuilder"],["impl Clone for Parser"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for Span"],["impl Clone for Position"],["impl Clone for WithComments"],["impl Clone for Comment"],["impl Clone for Ast"],["impl Clone for Alternation"],["impl Clone for Concat"],["impl Clone for Literal"],["impl Clone for LiteralKind"],["impl Clone for SpecialLiteralKind"],["impl Clone for HexLiteralKind"],["impl Clone for Class"],["impl Clone for ClassPerl"],["impl Clone for ClassPerlKind"],["impl Clone for ClassAscii"],["impl Clone for ClassAsciiKind"],["impl Clone for ClassUnicode"],["impl Clone for ClassUnicodeKind"],["impl Clone for ClassUnicodeOpKind"],["impl Clone for ClassBracketed"],["impl Clone for ClassSet"],["impl Clone for ClassSetItem"],["impl Clone for ClassSetRange"],["impl Clone for ClassSetUnion"],["impl Clone for ClassSetBinaryOp"],["impl Clone for ClassSetBinaryOpKind"],["impl Clone for Assertion"],["impl Clone for AssertionKind"],["impl Clone for Repetition"],["impl Clone for RepetitionOp"],["impl Clone for RepetitionKind"],["impl Clone for RepetitionRange"],["impl Clone for Group"],["impl Clone for GroupKind"],["impl Clone for CaptureName"],["impl Clone for SetFlags"],["impl Clone for Flags"],["impl Clone for FlagsItem"],["impl Clone for FlagsItemKind"],["impl Clone for Flag"],["impl Clone for Error"],["impl Clone for Literals"],["impl Clone for Literal"],["impl Clone for TranslatorBuilder"],["impl Clone for Translator"],["impl Clone for Error"],["impl Clone for ErrorKind"],["impl Clone for Hir"],["impl Clone for HirKind"],["impl Clone for Literal"],["impl Clone for Class"],["impl Clone for ClassUnicode"],["impl Clone for ClassUnicodeRange"],["impl Clone for ClassBytes"],["impl Clone for ClassBytesRange"],["impl Clone for Anchor"],["impl Clone for WordBoundary"],["impl Clone for Group"],["impl Clone for GroupKind"],["impl Clone for Repetition"],["impl Clone for RepetitionKind"],["impl Clone for RepetitionRange"],["impl Clone for ParserBuilder"],["impl Clone for Parser"],["impl Clone for Utf8Sequence"],["impl Clone for Utf8Range"]], -"rlp":[["impl Clone for DecoderError"],["impl<'a> Clone for Rlp<'a>"]], -"rustc_hex":[["impl Clone for FromHexError"]], -"serde":[["impl Clone for Error"],["impl<E> Clone for UnitDeserializer<E>"],["impl<E> Clone for BoolDeserializer<E>"],["impl<E> Clone for I8Deserializer<E>"],["impl<E> Clone for I16Deserializer<E>"],["impl<E> Clone for I32Deserializer<E>"],["impl<E> Clone for I64Deserializer<E>"],["impl<E> Clone for IsizeDeserializer<E>"],["impl<E> Clone for U8Deserializer<E>"],["impl<E> Clone for U16Deserializer<E>"],["impl<E> Clone for U64Deserializer<E>"],["impl<E> Clone for UsizeDeserializer<E>"],["impl<E> Clone for F32Deserializer<E>"],["impl<E> Clone for F64Deserializer<E>"],["impl<E> Clone for CharDeserializer<E>"],["impl<E> Clone for I128Deserializer<E>"],["impl<E> Clone for U128Deserializer<E>"],["impl<E> Clone for U32Deserializer<E>"],["impl<'de, E> Clone for StrDeserializer<'de, E>"],["impl<'de, E> Clone for BorrowedStrDeserializer<'de, E>"],["impl<E> Clone for StringDeserializer<E>"],["impl<'a, E> Clone for CowStrDeserializer<'a, E>"],["impl<'a, E> Clone for BytesDeserializer<'a, E>"],["impl<'de, E> Clone for BorrowedBytesDeserializer<'de, E>"],["impl<I: Clone, E: Clone> Clone for SeqDeserializer<I, E>"],["impl<A: Clone> Clone for SeqAccessDeserializer<A>"],["impl<'de, I, E> Clone for MapDeserializer<'de, I, E>where
        I: Iterator + Clone,
        I::Item: Pair,
        <I::Item as Pair>::Second: Clone,
    "],["impl<A: Clone> Clone for MapAccessDeserializer<A>"],["impl<A: Clone> Clone for EnumAccessDeserializer<A>"],["impl Clone for IgnoredAny"],["impl<'a> Clone for Unexpected<'a>"]], -"sha3":[["impl Clone for Keccak224Core"],["impl Clone for Keccak256Core"],["impl Clone for Keccak384Core"],["impl Clone for Keccak512Core"],["impl Clone for Keccak256FullCore"],["impl Clone for Sha3_224Core"],["impl Clone for Sha3_256Core"],["impl Clone for Sha3_384Core"],["impl Clone for Sha3_512Core"],["impl Clone for Shake128Core"],["impl Clone for Shake128ReaderCore"],["impl Clone for Shake256Core"],["impl Clone for Shake256ReaderCore"],["impl Clone for CShake128Core"],["impl Clone for CShake128ReaderCore"],["impl Clone for CShake256Core"],["impl Clone for CShake256ReaderCore"]], -"shale":[["impl<T> Clone for ObjPtr<T>"]], -"slab":[["impl<T: Clone> Clone for Slab<T>"],["impl<'a, T> Clone for Iter<'a, T>"]], -"smallvec":[["impl<A: Array> Clone for SmallVec<A>where
        A::Item: Clone,
    "],["impl<A: Array + Clone> Clone for IntoIter<A>where
        A::Item: Clone,
    "]], -"syn":[["impl Clone for Underscore"],["impl Clone for Abstract"],["impl Clone for As"],["impl Clone for Async"],["impl Clone for Auto"],["impl Clone for Await"],["impl Clone for Become"],["impl Clone for Box"],["impl Clone for Break"],["impl Clone for Const"],["impl Clone for Continue"],["impl Clone for Crate"],["impl Clone for Default"],["impl Clone for Do"],["impl Clone for Dyn"],["impl Clone for Else"],["impl Clone for Enum"],["impl Clone for Extern"],["impl Clone for Final"],["impl Clone for Fn"],["impl Clone for For"],["impl Clone for If"],["impl Clone for Impl"],["impl Clone for In"],["impl Clone for Let"],["impl Clone for Loop"],["impl Clone for Macro"],["impl Clone for Match"],["impl Clone for Mod"],["impl Clone for Move"],["impl Clone for Mut"],["impl Clone for Override"],["impl Clone for Priv"],["impl Clone for Pub"],["impl Clone for Ref"],["impl Clone for Return"],["impl Clone for SelfType"],["impl Clone for SelfValue"],["impl Clone for Static"],["impl Clone for Struct"],["impl Clone for Super"],["impl Clone for Trait"],["impl Clone for Try"],["impl Clone for Type"],["impl Clone for Typeof"],["impl Clone for Union"],["impl Clone for Unsafe"],["impl Clone for Unsized"],["impl Clone for Use"],["impl Clone for Virtual"],["impl Clone for Where"],["impl Clone for While"],["impl Clone for Yield"],["impl Clone for Add"],["impl Clone for AddEq"],["impl Clone for And"],["impl Clone for AndAnd"],["impl Clone for AndEq"],["impl Clone for At"],["impl Clone for Bang"],["impl Clone for Caret"],["impl Clone for CaretEq"],["impl Clone for Colon"],["impl Clone for Colon2"],["impl Clone for Comma"],["impl Clone for Div"],["impl Clone for DivEq"],["impl Clone for Dollar"],["impl Clone for Dot"],["impl Clone for Dot2"],["impl Clone for Dot3"],["impl Clone for DotDotEq"],["impl Clone for Eq"],["impl Clone for EqEq"],["impl Clone for Ge"],["impl Clone for Gt"],["impl Clone for Le"],["impl Clone for Lt"],["impl Clone for MulEq"],["impl Clone for Ne"],["impl Clone for Or"],["impl Clone for OrEq"],["impl Clone for OrOr"],["impl Clone for Pound"],["impl Clone for Question"],["impl Clone for RArrow"],["impl Clone for LArrow"],["impl Clone for Rem"],["impl Clone for RemEq"],["impl Clone for FatArrow"],["impl Clone for Semi"],["impl Clone for Shl"],["impl Clone for ShlEq"],["impl Clone for Shr"],["impl Clone for ShrEq"],["impl Clone for Star"],["impl Clone for Sub"],["impl Clone for SubEq"],["impl Clone for Tilde"],["impl Clone for Brace"],["impl Clone for Bracket"],["impl Clone for Paren"],["impl Clone for Group"],["impl<'a> Clone for ImplGenerics<'a>"],["impl<'a> Clone for TypeGenerics<'a>"],["impl<'a> Clone for Turbofish<'a>"],["impl Clone for Lifetime"],["impl Clone for LitStr"],["impl Clone for LitByteStr"],["impl Clone for LitByte"],["impl Clone for LitChar"],["impl Clone for LitInt"],["impl Clone for LitFloat"],["impl<'a> Clone for Cursor<'a>"],["impl<T, P> Clone for Punctuated<T, P>where
        T: Clone,
        P: Clone,
    "],["impl<'a, T, P> Clone for Pairs<'a, T, P>"],["impl<T, P> Clone for IntoPairs<T, P>where
        T: Clone,
        P: Clone,
    "],["impl<T> Clone for IntoIter<T>where
        T: Clone,
    "],["impl<'a, T> Clone for Iter<'a, T>"],["impl<T, P> Clone for Pair<T, P>where
        T: Clone,
        P: Clone,
    "],["impl Clone for Abi"],["impl Clone for AngleBracketedGenericArguments"],["impl Clone for Arm"],["impl Clone for AttrStyle"],["impl Clone for Attribute"],["impl Clone for BareFnArg"],["impl Clone for BinOp"],["impl Clone for Binding"],["impl Clone for Block"],["impl Clone for BoundLifetimes"],["impl Clone for ConstParam"],["impl Clone for Constraint"],["impl Clone for Data"],["impl Clone for DataEnum"],["impl Clone for DataStruct"],["impl Clone for DataUnion"],["impl Clone for DeriveInput"],["impl Clone for Expr"],["impl Clone for ExprArray"],["impl Clone for ExprAssign"],["impl Clone for ExprAssignOp"],["impl Clone for ExprAsync"],["impl Clone for ExprAwait"],["impl Clone for ExprBinary"],["impl Clone for ExprBlock"],["impl Clone for ExprBox"],["impl Clone for ExprBreak"],["impl Clone for ExprCall"],["impl Clone for ExprCast"],["impl Clone for ExprClosure"],["impl Clone for ExprContinue"],["impl Clone for ExprField"],["impl Clone for ExprForLoop"],["impl Clone for ExprGroup"],["impl Clone for ExprIf"],["impl Clone for ExprIndex"],["impl Clone for ExprLet"],["impl Clone for ExprLit"],["impl Clone for ExprLoop"],["impl Clone for ExprMacro"],["impl Clone for ExprMatch"],["impl Clone for ExprMethodCall"],["impl Clone for ExprParen"],["impl Clone for ExprPath"],["impl Clone for ExprRange"],["impl Clone for ExprReference"],["impl Clone for ExprRepeat"],["impl Clone for ExprReturn"],["impl Clone for ExprStruct"],["impl Clone for ExprTry"],["impl Clone for ExprTryBlock"],["impl Clone for ExprTuple"],["impl Clone for ExprType"],["impl Clone for ExprUnary"],["impl Clone for ExprUnsafe"],["impl Clone for ExprWhile"],["impl Clone for ExprYield"],["impl Clone for Field"],["impl Clone for FieldPat"],["impl Clone for FieldValue"],["impl Clone for Fields"],["impl Clone for FieldsNamed"],["impl Clone for FieldsUnnamed"],["impl Clone for File"],["impl Clone for FnArg"],["impl Clone for ForeignItem"],["impl Clone for ForeignItemFn"],["impl Clone for ForeignItemMacro"],["impl Clone for ForeignItemStatic"],["impl Clone for ForeignItemType"],["impl Clone for GenericArgument"],["impl Clone for GenericMethodArgument"],["impl Clone for GenericParam"],["impl Clone for Generics"],["impl Clone for ImplItem"],["impl Clone for ImplItemConst"],["impl Clone for ImplItemMacro"],["impl Clone for ImplItemMethod"],["impl Clone for ImplItemType"],["impl Clone for Index"],["impl Clone for Item"],["impl Clone for ItemConst"],["impl Clone for ItemEnum"],["impl Clone for ItemExternCrate"],["impl Clone for ItemFn"],["impl Clone for ItemForeignMod"],["impl Clone for ItemImpl"],["impl Clone for ItemMacro"],["impl Clone for ItemMacro2"],["impl Clone for ItemMod"],["impl Clone for ItemStatic"],["impl Clone for ItemStruct"],["impl Clone for ItemTrait"],["impl Clone for ItemTraitAlias"],["impl Clone for ItemType"],["impl Clone for ItemUnion"],["impl Clone for ItemUse"],["impl Clone for Label"],["impl Clone for LifetimeDef"],["impl Clone for Lit"],["impl Clone for LitBool"],["impl Clone for Local"],["impl Clone for Macro"],["impl Clone for MacroDelimiter"],["impl Clone for Member"],["impl Clone for Meta"],["impl Clone for MetaList"],["impl Clone for MetaNameValue"],["impl Clone for MethodTurbofish"],["impl Clone for NestedMeta"],["impl Clone for ParenthesizedGenericArguments"],["impl Clone for Pat"],["impl Clone for PatBox"],["impl Clone for PatIdent"],["impl Clone for PatLit"],["impl Clone for PatMacro"],["impl Clone for PatOr"],["impl Clone for PatPath"],["impl Clone for PatRange"],["impl Clone for PatReference"],["impl Clone for PatRest"],["impl Clone for PatSlice"],["impl Clone for PatStruct"],["impl Clone for PatTuple"],["impl Clone for PatTupleStruct"],["impl Clone for PatType"],["impl Clone for PatWild"],["impl Clone for Path"],["impl Clone for PathArguments"],["impl Clone for PathSegment"],["impl Clone for PredicateEq"],["impl Clone for PredicateLifetime"],["impl Clone for PredicateType"],["impl Clone for QSelf"],["impl Clone for RangeLimits"],["impl Clone for Receiver"],["impl Clone for ReturnType"],["impl Clone for Signature"],["impl Clone for Stmt"],["impl Clone for TraitBound"],["impl Clone for TraitBoundModifier"],["impl Clone for TraitItem"],["impl Clone for TraitItemConst"],["impl Clone for TraitItemMacro"],["impl Clone for TraitItemMethod"],["impl Clone for TraitItemType"],["impl Clone for Type"],["impl Clone for TypeArray"],["impl Clone for TypeBareFn"],["impl Clone for TypeGroup"],["impl Clone for TypeImplTrait"],["impl Clone for TypeInfer"],["impl Clone for TypeMacro"],["impl Clone for TypeNever"],["impl Clone for TypeParam"],["impl Clone for TypeParamBound"],["impl Clone for TypeParen"],["impl Clone for TypePath"],["impl Clone for TypePtr"],["impl Clone for TypeReference"],["impl Clone for TypeSlice"],["impl Clone for TypeTraitObject"],["impl Clone for TypeTuple"],["impl Clone for UnOp"],["impl Clone for UseGlob"],["impl Clone for UseGroup"],["impl Clone for UseName"],["impl Clone for UsePath"],["impl Clone for UseRename"],["impl Clone for UseTree"],["impl Clone for Variadic"],["impl Clone for Variant"],["impl Clone for VisCrate"],["impl Clone for VisPublic"],["impl Clone for VisRestricted"],["impl Clone for Visibility"],["impl Clone for WhereClause"],["impl Clone for WherePredicate"],["impl<'c, 'a> Clone for StepCursor<'c, 'a>"],["impl Clone for Error"]], -"tokio":[["impl Clone for Handle"],["impl Clone for BarrierWaitResult"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for Sender<T>"],["impl<T> Clone for WeakSender<T>"],["impl<T> Clone for UnboundedSender<T>"],["impl<T> Clone for WeakUnboundedSender<T>"],["impl Clone for TryRecvError"],["impl Clone for RecvError"],["impl Clone for TryRecvError"],["impl<T: Clone> Clone for OnceCell<T>"],["impl Clone for RecvError"],["impl<T> Clone for Receiver<T>"]], -"typenum":[["impl Clone for B0"],["impl Clone for B1"],["impl<U: Clone + Unsigned + NonZero> Clone for PInt<U>"],["impl<U: Clone + Unsigned + NonZero> Clone for NInt<U>"],["impl Clone for Z0"],["impl Clone for UTerm"],["impl<U: Clone, B: Clone> Clone for UInt<U, B>"],["impl Clone for ATerm"],["impl<V: Clone, A: Clone> Clone for TArr<V, A>"],["impl Clone for Greater"],["impl Clone for Less"],["impl Clone for Equal"]], -"uint":[["impl Clone for FromStrRadixErrKind"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/cmp/trait.Eq.js b/docs/implementors/core/cmp/trait.Eq.js deleted file mode 100644 index 4727063fccf9..000000000000 --- a/docs/implementors/core/cmp/trait.Eq.js +++ /dev/null @@ -1,36 +0,0 @@ -(function() {var implementors = { -"aho_corasick":[["impl Eq for MatchKind"],["impl Eq for MatchKind"],["impl Eq for Match"]], -"block_buffer":[["impl Eq for Error"]], -"byteorder":[["impl Eq for BigEndian"],["impl Eq for LittleEndian"]], -"bytes":[["impl Eq for Bytes"],["impl Eq for BytesMut"]], -"crossbeam_channel":[["impl<T: Eq> Eq for SendError<T>"],["impl<T: Eq> Eq for TrySendError<T>"],["impl<T: Eq> Eq for SendTimeoutError<T>"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl Eq for RecvTimeoutError"],["impl Eq for TrySelectError"],["impl Eq for SelectTimeoutError"],["impl Eq for TryReadyError"],["impl Eq for ReadyTimeoutError"]], -"crossbeam_utils":[["impl<T: Eq> Eq for CachePadded<T>"]], -"crypto_common":[["impl Eq for InvalidLength"]], -"digest":[["impl Eq for InvalidBufferSize"]], -"firewood":[["impl Eq for Hash"],["impl Eq for PartialPath"],["impl Eq for Node"]], -"futures_channel":[["impl Eq for SendError"],["impl<T: Eq> Eq for TrySendError<T>"],["impl Eq for Canceled"]], -"futures_util":[["impl<T: Eq, E: Eq> Eq for TryChunksError<T, E>"],["impl Eq for PollNext"],["impl<T: Eq> Eq for AllowStdIo<T>"],["impl Eq for Aborted"]], -"generic_array":[["impl<T: Eq, N> Eq for GenericArray<T, N>where
        N: ArrayLength<T>,
    "]], -"getrandom":[["impl Eq for Error"]], -"growthring":[["impl Eq for WALRingId"]], -"hashbrown":[["impl<K, V, S, A> Eq for HashMap<K, V, S, A>where
        K: Eq + Hash,
        V: Eq,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T, S, A> Eq for HashSet<T, S, A>where
        T: Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl Eq for TryReserveError"]], -"libc":[["impl Eq for group"],["impl Eq for utimbuf"],["impl Eq for timeval"],["impl Eq for timespec"],["impl Eq for rlimit"],["impl Eq for rusage"],["impl Eq for ipv6_mreq"],["impl Eq for hostent"],["impl Eq for iovec"],["impl Eq for pollfd"],["impl Eq for winsize"],["impl Eq for linger"],["impl Eq for sigval"],["impl Eq for itimerval"],["impl Eq for tms"],["impl Eq for servent"],["impl Eq for protoent"],["impl Eq for in_addr"],["impl Eq for ip_mreq"],["impl Eq for ip_mreqn"],["impl Eq for ip_mreq_source"],["impl Eq for sockaddr"],["impl Eq for sockaddr_in"],["impl Eq for sockaddr_in6"],["impl Eq for addrinfo"],["impl Eq for sockaddr_ll"],["impl Eq for fd_set"],["impl Eq for tm"],["impl Eq for sched_param"],["impl Eq for Dl_info"],["impl Eq for lconv"],["impl Eq for in_pktinfo"],["impl Eq for ifaddrs"],["impl Eq for in6_rtmsg"],["impl Eq for arpreq"],["impl Eq for arpreq_old"],["impl Eq for arphdr"],["impl Eq for mmsghdr"],["impl Eq for epoll_event"],["impl Eq for sockaddr_un"],["impl Eq for sockaddr_storage"],["impl Eq for utsname"],["impl Eq for sigevent"],["impl Eq for rlimit64"],["impl Eq for glob_t"],["impl Eq for passwd"],["impl Eq for spwd"],["impl Eq for dqblk"],["impl Eq for signalfd_siginfo"],["impl Eq for itimerspec"],["impl Eq for fsid_t"],["impl Eq for packet_mreq"],["impl Eq for cpu_set_t"],["impl Eq for if_nameindex"],["impl Eq for msginfo"],["impl Eq for sembuf"],["impl Eq for input_event"],["impl Eq for input_id"],["impl Eq for input_absinfo"],["impl Eq for input_keymap_entry"],["impl Eq for input_mask"],["impl Eq for ff_replay"],["impl Eq for ff_trigger"],["impl Eq for ff_envelope"],["impl Eq for ff_constant_effect"],["impl Eq for ff_ramp_effect"],["impl Eq for ff_condition_effect"],["impl Eq for ff_periodic_effect"],["impl Eq for ff_rumble_effect"],["impl Eq for ff_effect"],["impl Eq for uinput_ff_upload"],["impl Eq for uinput_ff_erase"],["impl Eq for uinput_abs_setup"],["impl Eq for dl_phdr_info"],["impl Eq for Elf32_Ehdr"],["impl Eq for Elf64_Ehdr"],["impl Eq for Elf32_Sym"],["impl Eq for Elf64_Sym"],["impl Eq for Elf32_Phdr"],["impl Eq for Elf64_Phdr"],["impl Eq for Elf32_Shdr"],["impl Eq for Elf64_Shdr"],["impl Eq for ucred"],["impl Eq for mntent"],["impl Eq for posix_spawn_file_actions_t"],["impl Eq for posix_spawnattr_t"],["impl Eq for genlmsghdr"],["impl Eq for in6_pktinfo"],["impl Eq for arpd_request"],["impl Eq for inotify_event"],["impl Eq for fanotify_response"],["impl Eq for sockaddr_vm"],["impl Eq for regmatch_t"],["impl Eq for sock_extended_err"],["impl Eq for __c_anonymous_sockaddr_can_tp"],["impl Eq for __c_anonymous_sockaddr_can_j1939"],["impl Eq for can_filter"],["impl Eq for j1939_filter"],["impl Eq for sock_filter"],["impl Eq for sock_fprog"],["impl Eq for seccomp_data"],["impl Eq for nlmsghdr"],["impl Eq for nlmsgerr"],["impl Eq for nlattr"],["impl Eq for file_clone_range"],["impl Eq for __c_anonymous_ifru_map"],["impl Eq for in6_ifreq"],["impl Eq for sockaddr_nl"],["impl Eq for dirent"],["impl Eq for dirent64"],["impl Eq for pthread_cond_t"],["impl Eq for pthread_mutex_t"],["impl Eq for pthread_rwlock_t"],["impl Eq for sockaddr_alg"],["impl Eq for uinput_setup"],["impl Eq for uinput_user_dev"],["impl Eq for af_alg_iv"],["impl Eq for mq_attr"],["impl Eq for statx"],["impl Eq for statx_timestamp"],["impl Eq for aiocb"],["impl Eq for __exit_status"],["impl Eq for __timeval"],["impl Eq for glob64_t"],["impl Eq for msghdr"],["impl Eq for cmsghdr"],["impl Eq for termios"],["impl Eq for mallinfo"],["impl Eq for mallinfo2"],["impl Eq for nl_pktinfo"],["impl Eq for nl_mmap_req"],["impl Eq for nl_mmap_hdr"],["impl Eq for rtentry"],["impl Eq for timex"],["impl Eq for ntptimeval"],["impl Eq for regex_t"],["impl Eq for Elf64_Chdr"],["impl Eq for Elf32_Chdr"],["impl Eq for seminfo"],["impl Eq for ptrace_peeksiginfo_args"],["impl Eq for __c_anonymous_ptrace_syscall_info_entry"],["impl Eq for __c_anonymous_ptrace_syscall_info_exit"],["impl Eq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Eq for ptrace_syscall_info"],["impl Eq for utmpx"],["impl Eq for __c_anonymous_ptrace_syscall_info_data"],["impl Eq for sigset_t"],["impl Eq for sysinfo"],["impl Eq for msqid_ds"],["impl Eq for semid_ds"],["impl Eq for sigaction"],["impl Eq for statfs"],["impl Eq for flock"],["impl Eq for flock64"],["impl Eq for siginfo_t"],["impl Eq for stack_t"],["impl Eq for stat"],["impl Eq for stat64"],["impl Eq for statfs64"],["impl Eq for statvfs64"],["impl Eq for pthread_attr_t"],["impl Eq for _libc_fpxreg"],["impl Eq for _libc_xmmreg"],["impl Eq for _libc_fpstate"],["impl Eq for user_regs_struct"],["impl Eq for user"],["impl Eq for mcontext_t"],["impl Eq for ipc_perm"],["impl Eq for shmid_ds"],["impl Eq for seccomp_notif_sizes"],["impl Eq for ptrace_rseq_configuration"],["impl Eq for user_fpregs_struct"],["impl Eq for ucontext_t"],["impl Eq for statvfs"],["impl Eq for clone_args"],["impl Eq for sem_t"],["impl Eq for termios2"],["impl Eq for pthread_mutexattr_t"],["impl Eq for pthread_rwlockattr_t"],["impl Eq for pthread_condattr_t"],["impl Eq for fanotify_event_metadata"],["impl Eq for open_how"],["impl Eq for in6_addr"]], -"nix":[["impl Eq for Dir"],["impl<'d> Eq for Iter<'d>"],["impl Eq for OwningIter"],["impl Eq for Entry"],["impl Eq for Type"],["impl Eq for Errno"],["impl Eq for AtFlags"],["impl Eq for OFlag"],["impl Eq for RenameFlags"],["impl Eq for SealFlag"],["impl Eq for FdFlag"],["impl<'a> Eq for FcntlArg<'a>"],["impl Eq for FlockArg"],["impl Eq for SpliceFFlags"],["impl Eq for FallocateFlags"],["impl Eq for PosixFadviseAdvice"],["impl Eq for InterfaceAddress"],["impl Eq for InterfaceAddressIterator"],["impl Eq for InterfaceFlags"],["impl Eq for ModuleInitFlags"],["impl Eq for DeleteModuleFlags"],["impl Eq for MsFlags"],["impl Eq for MntFlags"],["impl Eq for MQ_OFlag"],["impl Eq for MqAttr"],["impl Eq for PollFd"],["impl Eq for PollFlags"],["impl Eq for OpenptyResult"],["impl Eq for PtyMaster"],["impl Eq for CloneFlags"],["impl Eq for CpuSet"],["impl Eq for AioFsyncMode"],["impl Eq for LioMode"],["impl Eq for AioCancelStat"],["impl Eq for EpollFlags"],["impl Eq for EpollOp"],["impl Eq for EpollCreateFlags"],["impl Eq for EpollEvent"],["impl Eq for EfdFlags"],["impl Eq for MemFdCreateFlag"],["impl Eq for ProtFlags"],["impl Eq for MapFlags"],["impl Eq for MRemapFlags"],["impl Eq for MmapAdvise"],["impl Eq for MsFlags"],["impl Eq for MlockAllFlags"],["impl Eq for Persona"],["impl Eq for Request"],["impl Eq for Event"],["impl Eq for Options"],["impl Eq for QuotaType"],["impl Eq for QuotaFmt"],["impl Eq for QuotaValidFlags"],["impl Eq for Dqblk"],["impl Eq for RebootMode"],["impl Eq for Resource"],["impl Eq for UsageWho"],["impl Eq for Usage"],["impl Eq for FdSet"],["impl Eq for Signal"],["impl Eq for SignalIterator"],["impl Eq for SaFlags"],["impl Eq for SigmaskHow"],["impl Eq for SigSet"],["impl Eq for SigHandler"],["impl Eq for SigAction"],["impl Eq for SigevNotify"],["impl Eq for SigEvent"],["impl Eq for SfdFlags"],["impl Eq for SignalFd"],["impl Eq for AddressFamily"],["impl Eq for InetAddr"],["impl Eq for IpAddr"],["impl Eq for Ipv4Addr"],["impl Eq for Ipv6Addr"],["impl Eq for UnixAddr"],["impl Eq for SockaddrIn"],["impl Eq for SockaddrIn6"],["impl Eq for SockaddrStorage"],["impl Eq for SockAddr"],["impl Eq for NetlinkAddr"],["impl Eq for AlgAddr"],["impl Eq for LinkAddr"],["impl Eq for VsockAddr"],["impl Eq for ReuseAddr"],["impl Eq for ReusePort"],["impl Eq for TcpNoDelay"],["impl Eq for Linger"],["impl Eq for IpAddMembership"],["impl Eq for IpDropMembership"],["impl Eq for Ipv6AddMembership"],["impl Eq for Ipv6DropMembership"],["impl Eq for IpMulticastTtl"],["impl Eq for IpMulticastLoop"],["impl Eq for Priority"],["impl Eq for IpTos"],["impl Eq for Ipv6TClass"],["impl Eq for IpFreebind"],["impl Eq for ReceiveTimeout"],["impl Eq for SendTimeout"],["impl Eq for Broadcast"],["impl Eq for OobInline"],["impl Eq for SocketError"],["impl Eq for DontRoute"],["impl Eq for KeepAlive"],["impl Eq for PeerCredentials"],["impl Eq for TcpKeepIdle"],["impl Eq for TcpMaxSeg"],["impl Eq for TcpKeepCount"],["impl Eq for TcpRepair"],["impl Eq for TcpKeepInterval"],["impl Eq for TcpUserTimeout"],["impl Eq for RcvBuf"],["impl Eq for SndBuf"],["impl Eq for RcvBufForce"],["impl Eq for SndBufForce"],["impl Eq for SockType"],["impl Eq for AcceptConn"],["impl Eq for BindToDevice"],["impl Eq for OriginalDst"],["impl Eq for Ip6tOriginalDst"],["impl Eq for Timestamping"],["impl Eq for ReceiveTimestamp"],["impl Eq for ReceiveTimestampns"],["impl Eq for IpTransparent"],["impl Eq for Mark"],["impl Eq for PassCred"],["impl Eq for TcpCongestion"],["impl Eq for Ipv4PacketInfo"],["impl Eq for Ipv6RecvPacketInfo"],["impl Eq for Ipv4OrigDstAddr"],["impl Eq for UdpGsoSegment"],["impl Eq for UdpGroSegment"],["impl Eq for TxTime"],["impl Eq for RxqOvfl"],["impl Eq for Ipv6V6Only"],["impl Eq for Ipv4RecvErr"],["impl Eq for Ipv6RecvErr"],["impl Eq for IpMtu"],["impl Eq for Ipv4Ttl"],["impl Eq for Ipv6Ttl"],["impl Eq for Ipv6OrigDstAddr"],["impl Eq for Ipv6DontFrag"],["impl Eq for SockType"],["impl Eq for SockProtocol"],["impl Eq for TimestampingFlag"],["impl Eq for SockFlag"],["impl Eq for MsgFlags"],["impl Eq for UnixCredentials"],["impl Eq for IpMembershipRequest"],["impl Eq for Ipv6MembershipRequest"],["impl<'a, 's, S: Eq> Eq for RecvMsg<'a, 's, S>"],["impl<'a> Eq for CmsgIterator<'a>"],["impl Eq for ControlMessageOwned"],["impl Eq for Timestamps"],["impl<'a> Eq for ControlMessage<'a>"],["impl Eq for Shutdown"],["impl Eq for SFlag"],["impl Eq for Mode"],["impl Eq for FsType"],["impl Eq for FsFlags"],["impl Eq for Statvfs"],["impl Eq for SysInfo"],["impl Eq for Termios"],["impl Eq for BaudRate"],["impl Eq for SetArg"],["impl Eq for FlushArg"],["impl Eq for FlowArg"],["impl Eq for SpecialCharacterIndices"],["impl Eq for InputFlags"],["impl Eq for OutputFlags"],["impl Eq for ControlFlags"],["impl Eq for LocalFlags"],["impl Eq for Expiration"],["impl Eq for TimerSetTimeFlags"],["impl Eq for TimeSpec"],["impl Eq for TimeVal"],["impl Eq for RemoteIoVec"],["impl<T: Eq> Eq for IoVec<T>"],["impl Eq for UtsName"],["impl Eq for WaitPidFlag"],["impl Eq for WaitStatus"],["impl Eq for Id"],["impl Eq for AddWatchFlags"],["impl Eq for InitFlags"],["impl Eq for WatchDescriptor"],["impl Eq for ClockId"],["impl Eq for TimerFlags"],["impl Eq for ClockId"],["impl Eq for UContext"],["impl Eq for Uid"],["impl Eq for Gid"],["impl Eq for Pid"],["impl Eq for PathconfVar"],["impl Eq for SysconfVar"],["impl Eq for ResUid"],["impl Eq for ResGid"],["impl Eq for AccessFlags"],["impl Eq for User"],["impl Eq for Group"]], -"once_cell":[["impl<T: Eq> Eq for OnceCell<T>"],["impl<T: Eq> Eq for OnceCell<T>"]], -"parking_lot":[["impl Eq for WaitTimeoutResult"],["impl Eq for OnceState"]], -"parking_lot_core":[["impl Eq for ParkResult"],["impl Eq for UnparkResult"],["impl Eq for RequeueOp"],["impl Eq for FilterOp"],["impl Eq for UnparkToken"],["impl Eq for ParkToken"]], -"ppv_lite86":[["impl Eq for vec128_storage"],["impl Eq for vec256_storage"],["impl Eq for vec512_storage"]], -"primitive_types":[["impl Eq for Error"],["impl Eq for U128"],["impl Eq for U256"],["impl Eq for U512"],["impl Eq for H128"],["impl Eq for H160"],["impl Eq for H256"],["impl Eq for H384"],["impl Eq for H512"],["impl Eq for H768"]], -"proc_macro2":[["impl Eq for Delimiter"],["impl Eq for Spacing"],["impl Eq for Ident"]], -"rand":[["impl Eq for BernoulliError"],["impl Eq for WeightedError"],["impl Eq for StepRng"]], -"rand_chacha":[["impl Eq for ChaCha20Core"],["impl Eq for ChaCha20Rng"],["impl Eq for ChaCha12Core"],["impl Eq for ChaCha12Rng"],["impl Eq for ChaCha8Core"],["impl Eq for ChaCha8Rng"]], -"regex":[["impl<'t> Eq for Match<'t>"],["impl<'t> Eq for Match<'t>"]], -"regex_syntax":[["impl Eq for Error"],["impl Eq for ErrorKind"],["impl Eq for Span"],["impl Eq for Position"],["impl Eq for WithComments"],["impl Eq for Comment"],["impl Eq for Ast"],["impl Eq for Alternation"],["impl Eq for Concat"],["impl Eq for Literal"],["impl Eq for LiteralKind"],["impl Eq for SpecialLiteralKind"],["impl Eq for HexLiteralKind"],["impl Eq for Class"],["impl Eq for ClassPerl"],["impl Eq for ClassPerlKind"],["impl Eq for ClassAscii"],["impl Eq for ClassAsciiKind"],["impl Eq for ClassUnicode"],["impl Eq for ClassUnicodeKind"],["impl Eq for ClassUnicodeOpKind"],["impl Eq for ClassBracketed"],["impl Eq for ClassSet"],["impl Eq for ClassSetItem"],["impl Eq for ClassSetRange"],["impl Eq for ClassSetUnion"],["impl Eq for ClassSetBinaryOp"],["impl Eq for ClassSetBinaryOpKind"],["impl Eq for Assertion"],["impl Eq for AssertionKind"],["impl Eq for Repetition"],["impl Eq for RepetitionOp"],["impl Eq for RepetitionKind"],["impl Eq for RepetitionRange"],["impl Eq for Group"],["impl Eq for GroupKind"],["impl Eq for CaptureName"],["impl Eq for SetFlags"],["impl Eq for Flags"],["impl Eq for FlagsItem"],["impl Eq for FlagsItemKind"],["impl Eq for Flag"],["impl Eq for Error"],["impl Eq for Literals"],["impl Eq for Literal"],["impl Eq for Error"],["impl Eq for ErrorKind"],["impl Eq for Hir"],["impl Eq for HirKind"],["impl Eq for Literal"],["impl Eq for Class"],["impl Eq for ClassUnicode"],["impl Eq for ClassUnicodeRange"],["impl Eq for ClassBytes"],["impl Eq for ClassBytesRange"],["impl Eq for Anchor"],["impl Eq for WordBoundary"],["impl Eq for Group"],["impl Eq for GroupKind"],["impl Eq for Repetition"],["impl Eq for RepetitionKind"],["impl Eq for RepetitionRange"],["impl Eq for Utf8Sequence"],["impl Eq for Utf8Range"]], -"rlp":[["impl Eq for DecoderError"]], -"shale":[["impl<T> Eq for ObjPtr<T>"]], -"smallvec":[["impl<A: Array> Eq for SmallVec<A>where
        A::Item: Eq,
    "]], -"syn":[["impl Eq for Underscore"],["impl Eq for Abstract"],["impl Eq for As"],["impl Eq for Async"],["impl Eq for Auto"],["impl Eq for Await"],["impl Eq for Become"],["impl Eq for Box"],["impl Eq for Break"],["impl Eq for Const"],["impl Eq for Continue"],["impl Eq for Crate"],["impl Eq for Default"],["impl Eq for Do"],["impl Eq for Dyn"],["impl Eq for Else"],["impl Eq for Enum"],["impl Eq for Extern"],["impl Eq for Final"],["impl Eq for Fn"],["impl Eq for For"],["impl Eq for If"],["impl Eq for Impl"],["impl Eq for In"],["impl Eq for Let"],["impl Eq for Loop"],["impl Eq for Macro"],["impl Eq for Match"],["impl Eq for Mod"],["impl Eq for Move"],["impl Eq for Mut"],["impl Eq for Override"],["impl Eq for Priv"],["impl Eq for Pub"],["impl Eq for Ref"],["impl Eq for Return"],["impl Eq for SelfType"],["impl Eq for SelfValue"],["impl Eq for Static"],["impl Eq for Struct"],["impl Eq for Super"],["impl Eq for Trait"],["impl Eq for Try"],["impl Eq for Type"],["impl Eq for Typeof"],["impl Eq for Union"],["impl Eq for Unsafe"],["impl Eq for Unsized"],["impl Eq for Use"],["impl Eq for Virtual"],["impl Eq for Where"],["impl Eq for While"],["impl Eq for Yield"],["impl Eq for Add"],["impl Eq for AddEq"],["impl Eq for And"],["impl Eq for AndAnd"],["impl Eq for AndEq"],["impl Eq for At"],["impl Eq for Bang"],["impl Eq for Caret"],["impl Eq for CaretEq"],["impl Eq for Colon"],["impl Eq for Colon2"],["impl Eq for Comma"],["impl Eq for Div"],["impl Eq for DivEq"],["impl Eq for Dollar"],["impl Eq for Dot"],["impl Eq for Dot2"],["impl Eq for Dot3"],["impl Eq for DotDotEq"],["impl Eq for Eq"],["impl Eq for EqEq"],["impl Eq for Ge"],["impl Eq for Gt"],["impl Eq for Le"],["impl Eq for Lt"],["impl Eq for MulEq"],["impl Eq for Ne"],["impl Eq for Or"],["impl Eq for OrEq"],["impl Eq for OrOr"],["impl Eq for Pound"],["impl Eq for Question"],["impl Eq for RArrow"],["impl Eq for LArrow"],["impl Eq for Rem"],["impl Eq for RemEq"],["impl Eq for FatArrow"],["impl Eq for Semi"],["impl Eq for Shl"],["impl Eq for ShlEq"],["impl Eq for Shr"],["impl Eq for ShrEq"],["impl Eq for Star"],["impl Eq for Sub"],["impl Eq for SubEq"],["impl Eq for Tilde"],["impl Eq for Brace"],["impl Eq for Bracket"],["impl Eq for Paren"],["impl Eq for Group"],["impl Eq for Member"],["impl Eq for Index"],["impl<'a> Eq for ImplGenerics<'a>"],["impl<'a> Eq for TypeGenerics<'a>"],["impl<'a> Eq for Turbofish<'a>"],["impl Eq for Lifetime"],["impl<'a> Eq for Cursor<'a>"],["impl<T, P> Eq for Punctuated<T, P>where
        T: Eq,
        P: Eq,
    "],["impl Eq for Abi"],["impl Eq for AngleBracketedGenericArguments"],["impl Eq for Arm"],["impl Eq for AttrStyle"],["impl Eq for Attribute"],["impl Eq for BareFnArg"],["impl Eq for BinOp"],["impl Eq for Binding"],["impl Eq for Block"],["impl Eq for BoundLifetimes"],["impl Eq for ConstParam"],["impl Eq for Constraint"],["impl Eq for Data"],["impl Eq for DataEnum"],["impl Eq for DataStruct"],["impl Eq for DataUnion"],["impl Eq for DeriveInput"],["impl Eq for Expr"],["impl Eq for ExprArray"],["impl Eq for ExprAssign"],["impl Eq for ExprAssignOp"],["impl Eq for ExprAsync"],["impl Eq for ExprAwait"],["impl Eq for ExprBinary"],["impl Eq for ExprBlock"],["impl Eq for ExprBox"],["impl Eq for ExprBreak"],["impl Eq for ExprCall"],["impl Eq for ExprCast"],["impl Eq for ExprClosure"],["impl Eq for ExprContinue"],["impl Eq for ExprField"],["impl Eq for ExprForLoop"],["impl Eq for ExprGroup"],["impl Eq for ExprIf"],["impl Eq for ExprIndex"],["impl Eq for ExprLet"],["impl Eq for ExprLit"],["impl Eq for ExprLoop"],["impl Eq for ExprMacro"],["impl Eq for ExprMatch"],["impl Eq for ExprMethodCall"],["impl Eq for ExprParen"],["impl Eq for ExprPath"],["impl Eq for ExprRange"],["impl Eq for ExprReference"],["impl Eq for ExprRepeat"],["impl Eq for ExprReturn"],["impl Eq for ExprStruct"],["impl Eq for ExprTry"],["impl Eq for ExprTryBlock"],["impl Eq for ExprTuple"],["impl Eq for ExprType"],["impl Eq for ExprUnary"],["impl Eq for ExprUnsafe"],["impl Eq for ExprWhile"],["impl Eq for ExprYield"],["impl Eq for Field"],["impl Eq for FieldPat"],["impl Eq for FieldValue"],["impl Eq for Fields"],["impl Eq for FieldsNamed"],["impl Eq for FieldsUnnamed"],["impl Eq for File"],["impl Eq for FnArg"],["impl Eq for ForeignItem"],["impl Eq for ForeignItemFn"],["impl Eq for ForeignItemMacro"],["impl Eq for ForeignItemStatic"],["impl Eq for ForeignItemType"],["impl Eq for GenericArgument"],["impl Eq for GenericMethodArgument"],["impl Eq for GenericParam"],["impl Eq for Generics"],["impl Eq for ImplItem"],["impl Eq for ImplItemConst"],["impl Eq for ImplItemMacro"],["impl Eq for ImplItemMethod"],["impl Eq for ImplItemType"],["impl Eq for Item"],["impl Eq for ItemConst"],["impl Eq for ItemEnum"],["impl Eq for ItemExternCrate"],["impl Eq for ItemFn"],["impl Eq for ItemForeignMod"],["impl Eq for ItemImpl"],["impl Eq for ItemMacro"],["impl Eq for ItemMacro2"],["impl Eq for ItemMod"],["impl Eq for ItemStatic"],["impl Eq for ItemStruct"],["impl Eq for ItemTrait"],["impl Eq for ItemTraitAlias"],["impl Eq for ItemType"],["impl Eq for ItemUnion"],["impl Eq for ItemUse"],["impl Eq for Label"],["impl Eq for LifetimeDef"],["impl Eq for Lit"],["impl Eq for LitBool"],["impl Eq for LitByte"],["impl Eq for LitByteStr"],["impl Eq for LitChar"],["impl Eq for LitFloat"],["impl Eq for LitInt"],["impl Eq for LitStr"],["impl Eq for Local"],["impl Eq for Macro"],["impl Eq for MacroDelimiter"],["impl Eq for Meta"],["impl Eq for MetaList"],["impl Eq for MetaNameValue"],["impl Eq for MethodTurbofish"],["impl Eq for NestedMeta"],["impl Eq for ParenthesizedGenericArguments"],["impl Eq for Pat"],["impl Eq for PatBox"],["impl Eq for PatIdent"],["impl Eq for PatLit"],["impl Eq for PatMacro"],["impl Eq for PatOr"],["impl Eq for PatPath"],["impl Eq for PatRange"],["impl Eq for PatReference"],["impl Eq for PatRest"],["impl Eq for PatSlice"],["impl Eq for PatStruct"],["impl Eq for PatTuple"],["impl Eq for PatTupleStruct"],["impl Eq for PatType"],["impl Eq for PatWild"],["impl Eq for Path"],["impl Eq for PathArguments"],["impl Eq for PathSegment"],["impl Eq for PredicateEq"],["impl Eq for PredicateLifetime"],["impl Eq for PredicateType"],["impl Eq for QSelf"],["impl Eq for RangeLimits"],["impl Eq for Receiver"],["impl Eq for ReturnType"],["impl Eq for Signature"],["impl Eq for Stmt"],["impl Eq for TraitBound"],["impl Eq for TraitBoundModifier"],["impl Eq for TraitItem"],["impl Eq for TraitItemConst"],["impl Eq for TraitItemMacro"],["impl Eq for TraitItemMethod"],["impl Eq for TraitItemType"],["impl Eq for Type"],["impl Eq for TypeArray"],["impl Eq for TypeBareFn"],["impl Eq for TypeGroup"],["impl Eq for TypeImplTrait"],["impl Eq for TypeInfer"],["impl Eq for TypeMacro"],["impl Eq for TypeNever"],["impl Eq for TypeParam"],["impl Eq for TypeParamBound"],["impl Eq for TypeParen"],["impl Eq for TypePath"],["impl Eq for TypePtr"],["impl Eq for TypeReference"],["impl Eq for TypeSlice"],["impl Eq for TypeTraitObject"],["impl Eq for TypeTuple"],["impl Eq for UnOp"],["impl Eq for UseGlob"],["impl Eq for UseGroup"],["impl Eq for UseName"],["impl Eq for UsePath"],["impl Eq for UseRename"],["impl Eq for UseTree"],["impl Eq for Variadic"],["impl Eq for Variant"],["impl Eq for VisCrate"],["impl Eq for VisPublic"],["impl Eq for VisRestricted"],["impl Eq for Visibility"],["impl Eq for WhereClause"],["impl Eq for WherePredicate"],["impl Eq for Nothing"]], -"tokio":[["impl Eq for RuntimeFlavor"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl<T: Eq> Eq for TrySendError<T>"],["impl Eq for TryRecvError"],["impl Eq for RecvError"],["impl Eq for TryRecvError"],["impl Eq for TryAcquireError"],["impl<T: Eq> Eq for OnceCell<T>"],["impl<T: Eq> Eq for SetError<T>"]], -"typenum":[["impl Eq for B0"],["impl Eq for B1"],["impl<U: Eq + Unsigned + NonZero> Eq for PInt<U>"],["impl<U: Eq + Unsigned + NonZero> Eq for NInt<U>"],["impl Eq for Z0"],["impl Eq for UTerm"],["impl<U: Eq, B: Eq> Eq for UInt<U, B>"],["impl Eq for ATerm"],["impl<V: Eq, A: Eq> Eq for TArr<V, A>"],["impl Eq for Greater"],["impl Eq for Less"],["impl Eq for Equal"]], -"uint":[["impl Eq for FromStrRadixErrKind"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/cmp/trait.PartialEq.js b/docs/implementors/core/cmp/trait.PartialEq.js deleted file mode 100644 index b1fe8eb3036f..000000000000 --- a/docs/implementors/core/cmp/trait.PartialEq.js +++ /dev/null @@ -1,39 +0,0 @@ -(function() {var implementors = { -"aho_corasick":[["impl PartialEq<MatchKind> for MatchKind"],["impl PartialEq<MatchKind> for MatchKind"],["impl PartialEq<Match> for Match"]], -"block_buffer":[["impl PartialEq<Error> for Error"]], -"byteorder":[["impl PartialEq<BigEndian> for BigEndian"],["impl PartialEq<LittleEndian> for LittleEndian"]], -"bytes":[["impl PartialEq<Bytes> for Bytes"],["impl PartialEq<[u8]> for Bytes"],["impl PartialEq<Bytes> for [u8]"],["impl PartialEq<str> for Bytes"],["impl PartialEq<Bytes> for str"],["impl PartialEq<Vec<u8, Global>> for Bytes"],["impl PartialEq<Bytes> for Vec<u8>"],["impl PartialEq<String> for Bytes"],["impl PartialEq<Bytes> for String"],["impl PartialEq<Bytes> for &[u8]"],["impl PartialEq<Bytes> for &str"],["impl<'a, T: ?Sized> PartialEq<&'a T> for Byteswhere
        Bytes: PartialEq<T>,
    "],["impl PartialEq<BytesMut> for BytesMut"],["impl PartialEq<[u8]> for BytesMut"],["impl PartialEq<BytesMut> for [u8]"],["impl PartialEq<str> for BytesMut"],["impl PartialEq<BytesMut> for str"],["impl PartialEq<Vec<u8, Global>> for BytesMut"],["impl PartialEq<BytesMut> for Vec<u8>"],["impl PartialEq<String> for BytesMut"],["impl PartialEq<BytesMut> for String"],["impl<'a, T: ?Sized> PartialEq<&'a T> for BytesMutwhere
        BytesMut: PartialEq<T>,
    "],["impl PartialEq<BytesMut> for &[u8]"],["impl PartialEq<BytesMut> for &str"],["impl PartialEq<BytesMut> for Bytes"],["impl PartialEq<Bytes> for BytesMut"]], -"crossbeam_channel":[["impl<T: PartialEq> PartialEq<SendError<T>> for SendError<T>"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl<T: PartialEq> PartialEq<SendTimeoutError<T>> for SendTimeoutError<T>"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<RecvTimeoutError> for RecvTimeoutError"],["impl PartialEq<TrySelectError> for TrySelectError"],["impl PartialEq<SelectTimeoutError> for SelectTimeoutError"],["impl PartialEq<TryReadyError> for TryReadyError"],["impl PartialEq<ReadyTimeoutError> for ReadyTimeoutError"]], -"crossbeam_utils":[["impl<T: PartialEq> PartialEq<CachePadded<T>> for CachePadded<T>"]], -"crypto_common":[["impl PartialEq<InvalidLength> for InvalidLength"]], -"digest":[["impl PartialEq<InvalidBufferSize> for InvalidBufferSize"]], -"firewood":[["impl PartialEq<Hash> for Hash"],["impl PartialEq<PartialPath> for PartialPath"],["impl PartialEq<Node> for Node"]], -"futures_channel":[["impl PartialEq<SendError> for SendError"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl PartialEq<Canceled> for Canceled"]], -"futures_util":[["impl<T: PartialEq, E: PartialEq> PartialEq<TryChunksError<T, E>> for TryChunksError<T, E>"],["impl PartialEq<PollNext> for PollNext"],["impl<T: PartialEq> PartialEq<AllowStdIo<T>> for AllowStdIo<T>"],["impl PartialEq<Aborted> for Aborted"]], -"generic_array":[["impl<T: PartialEq, N> PartialEq<GenericArray<T, N>> for GenericArray<T, N>where
        N: ArrayLength<T>,
    "]], -"getrandom":[["impl PartialEq<Error> for Error"]], -"growthring":[["impl PartialEq<WALRingId> for WALRingId"]], -"hashbrown":[["impl<K, V, S, A> PartialEq<HashMap<K, V, S, A>> for HashMap<K, V, S, A>where
        K: Eq + Hash,
        V: PartialEq,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T, S, A> PartialEq<HashSet<T, S, A>> for HashSet<T, S, A>where
        T: Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl PartialEq<TryReserveError> for TryReserveError"]], -"hex":[["impl PartialEq<FromHexError> for FromHexError"]], -"libc":[["impl PartialEq<group> for group"],["impl PartialEq<utimbuf> for utimbuf"],["impl PartialEq<timeval> for timeval"],["impl PartialEq<timespec> for timespec"],["impl PartialEq<rlimit> for rlimit"],["impl PartialEq<rusage> for rusage"],["impl PartialEq<ipv6_mreq> for ipv6_mreq"],["impl PartialEq<hostent> for hostent"],["impl PartialEq<iovec> for iovec"],["impl PartialEq<pollfd> for pollfd"],["impl PartialEq<winsize> for winsize"],["impl PartialEq<linger> for linger"],["impl PartialEq<sigval> for sigval"],["impl PartialEq<itimerval> for itimerval"],["impl PartialEq<tms> for tms"],["impl PartialEq<servent> for servent"],["impl PartialEq<protoent> for protoent"],["impl PartialEq<in_addr> for in_addr"],["impl PartialEq<ip_mreq> for ip_mreq"],["impl PartialEq<ip_mreqn> for ip_mreqn"],["impl PartialEq<ip_mreq_source> for ip_mreq_source"],["impl PartialEq<sockaddr> for sockaddr"],["impl PartialEq<sockaddr_in> for sockaddr_in"],["impl PartialEq<sockaddr_in6> for sockaddr_in6"],["impl PartialEq<addrinfo> for addrinfo"],["impl PartialEq<sockaddr_ll> for sockaddr_ll"],["impl PartialEq<fd_set> for fd_set"],["impl PartialEq<tm> for tm"],["impl PartialEq<sched_param> for sched_param"],["impl PartialEq<Dl_info> for Dl_info"],["impl PartialEq<lconv> for lconv"],["impl PartialEq<in_pktinfo> for in_pktinfo"],["impl PartialEq<ifaddrs> for ifaddrs"],["impl PartialEq<in6_rtmsg> for in6_rtmsg"],["impl PartialEq<arpreq> for arpreq"],["impl PartialEq<arpreq_old> for arpreq_old"],["impl PartialEq<arphdr> for arphdr"],["impl PartialEq<mmsghdr> for mmsghdr"],["impl PartialEq<epoll_event> for epoll_event"],["impl PartialEq<sockaddr_un> for sockaddr_un"],["impl PartialEq<sockaddr_storage> for sockaddr_storage"],["impl PartialEq<utsname> for utsname"],["impl PartialEq<sigevent> for sigevent"],["impl PartialEq<rlimit64> for rlimit64"],["impl PartialEq<glob_t> for glob_t"],["impl PartialEq<passwd> for passwd"],["impl PartialEq<spwd> for spwd"],["impl PartialEq<dqblk> for dqblk"],["impl PartialEq<signalfd_siginfo> for signalfd_siginfo"],["impl PartialEq<itimerspec> for itimerspec"],["impl PartialEq<fsid_t> for fsid_t"],["impl PartialEq<packet_mreq> for packet_mreq"],["impl PartialEq<cpu_set_t> for cpu_set_t"],["impl PartialEq<if_nameindex> for if_nameindex"],["impl PartialEq<msginfo> for msginfo"],["impl PartialEq<sembuf> for sembuf"],["impl PartialEq<input_event> for input_event"],["impl PartialEq<input_id> for input_id"],["impl PartialEq<input_absinfo> for input_absinfo"],["impl PartialEq<input_keymap_entry> for input_keymap_entry"],["impl PartialEq<input_mask> for input_mask"],["impl PartialEq<ff_replay> for ff_replay"],["impl PartialEq<ff_trigger> for ff_trigger"],["impl PartialEq<ff_envelope> for ff_envelope"],["impl PartialEq<ff_constant_effect> for ff_constant_effect"],["impl PartialEq<ff_ramp_effect> for ff_ramp_effect"],["impl PartialEq<ff_condition_effect> for ff_condition_effect"],["impl PartialEq<ff_periodic_effect> for ff_periodic_effect"],["impl PartialEq<ff_rumble_effect> for ff_rumble_effect"],["impl PartialEq<ff_effect> for ff_effect"],["impl PartialEq<uinput_ff_upload> for uinput_ff_upload"],["impl PartialEq<uinput_ff_erase> for uinput_ff_erase"],["impl PartialEq<uinput_abs_setup> for uinput_abs_setup"],["impl PartialEq<dl_phdr_info> for dl_phdr_info"],["impl PartialEq<Elf32_Ehdr> for Elf32_Ehdr"],["impl PartialEq<Elf64_Ehdr> for Elf64_Ehdr"],["impl PartialEq<Elf32_Sym> for Elf32_Sym"],["impl PartialEq<Elf64_Sym> for Elf64_Sym"],["impl PartialEq<Elf32_Phdr> for Elf32_Phdr"],["impl PartialEq<Elf64_Phdr> for Elf64_Phdr"],["impl PartialEq<Elf32_Shdr> for Elf32_Shdr"],["impl PartialEq<Elf64_Shdr> for Elf64_Shdr"],["impl PartialEq<ucred> for ucred"],["impl PartialEq<mntent> for mntent"],["impl PartialEq<posix_spawn_file_actions_t> for posix_spawn_file_actions_t"],["impl PartialEq<posix_spawnattr_t> for posix_spawnattr_t"],["impl PartialEq<genlmsghdr> for genlmsghdr"],["impl PartialEq<in6_pktinfo> for in6_pktinfo"],["impl PartialEq<arpd_request> for arpd_request"],["impl PartialEq<inotify_event> for inotify_event"],["impl PartialEq<fanotify_response> for fanotify_response"],["impl PartialEq<sockaddr_vm> for sockaddr_vm"],["impl PartialEq<regmatch_t> for regmatch_t"],["impl PartialEq<sock_extended_err> for sock_extended_err"],["impl PartialEq<__c_anonymous_sockaddr_can_tp> for __c_anonymous_sockaddr_can_tp"],["impl PartialEq<__c_anonymous_sockaddr_can_j1939> for __c_anonymous_sockaddr_can_j1939"],["impl PartialEq<can_filter> for can_filter"],["impl PartialEq<j1939_filter> for j1939_filter"],["impl PartialEq<sock_filter> for sock_filter"],["impl PartialEq<sock_fprog> for sock_fprog"],["impl PartialEq<seccomp_data> for seccomp_data"],["impl PartialEq<nlmsghdr> for nlmsghdr"],["impl PartialEq<nlmsgerr> for nlmsgerr"],["impl PartialEq<nlattr> for nlattr"],["impl PartialEq<file_clone_range> for file_clone_range"],["impl PartialEq<__c_anonymous_ifru_map> for __c_anonymous_ifru_map"],["impl PartialEq<in6_ifreq> for in6_ifreq"],["impl PartialEq<sockaddr_nl> for sockaddr_nl"],["impl PartialEq<dirent> for dirent"],["impl PartialEq<dirent64> for dirent64"],["impl PartialEq<pthread_cond_t> for pthread_cond_t"],["impl PartialEq<pthread_mutex_t> for pthread_mutex_t"],["impl PartialEq<pthread_rwlock_t> for pthread_rwlock_t"],["impl PartialEq<sockaddr_alg> for sockaddr_alg"],["impl PartialEq<uinput_setup> for uinput_setup"],["impl PartialEq<uinput_user_dev> for uinput_user_dev"],["impl PartialEq<af_alg_iv> for af_alg_iv"],["impl PartialEq<mq_attr> for mq_attr"],["impl PartialEq<statx> for statx"],["impl PartialEq<statx_timestamp> for statx_timestamp"],["impl PartialEq<aiocb> for aiocb"],["impl PartialEq<__exit_status> for __exit_status"],["impl PartialEq<__timeval> for __timeval"],["impl PartialEq<glob64_t> for glob64_t"],["impl PartialEq<msghdr> for msghdr"],["impl PartialEq<cmsghdr> for cmsghdr"],["impl PartialEq<termios> for termios"],["impl PartialEq<mallinfo> for mallinfo"],["impl PartialEq<mallinfo2> for mallinfo2"],["impl PartialEq<nl_pktinfo> for nl_pktinfo"],["impl PartialEq<nl_mmap_req> for nl_mmap_req"],["impl PartialEq<nl_mmap_hdr> for nl_mmap_hdr"],["impl PartialEq<rtentry> for rtentry"],["impl PartialEq<timex> for timex"],["impl PartialEq<ntptimeval> for ntptimeval"],["impl PartialEq<regex_t> for regex_t"],["impl PartialEq<Elf64_Chdr> for Elf64_Chdr"],["impl PartialEq<Elf32_Chdr> for Elf32_Chdr"],["impl PartialEq<seminfo> for seminfo"],["impl PartialEq<ptrace_peeksiginfo_args> for ptrace_peeksiginfo_args"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_entry> for __c_anonymous_ptrace_syscall_info_entry"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_exit> for __c_anonymous_ptrace_syscall_info_exit"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_seccomp> for __c_anonymous_ptrace_syscall_info_seccomp"],["impl PartialEq<ptrace_syscall_info> for ptrace_syscall_info"],["impl PartialEq<utmpx> for utmpx"],["impl PartialEq<__c_anonymous_ptrace_syscall_info_data> for __c_anonymous_ptrace_syscall_info_data"],["impl PartialEq<sigset_t> for sigset_t"],["impl PartialEq<sysinfo> for sysinfo"],["impl PartialEq<msqid_ds> for msqid_ds"],["impl PartialEq<semid_ds> for semid_ds"],["impl PartialEq<sigaction> for sigaction"],["impl PartialEq<statfs> for statfs"],["impl PartialEq<flock> for flock"],["impl PartialEq<flock64> for flock64"],["impl PartialEq<siginfo_t> for siginfo_t"],["impl PartialEq<stack_t> for stack_t"],["impl PartialEq<stat> for stat"],["impl PartialEq<stat64> for stat64"],["impl PartialEq<statfs64> for statfs64"],["impl PartialEq<statvfs64> for statvfs64"],["impl PartialEq<pthread_attr_t> for pthread_attr_t"],["impl PartialEq<_libc_fpxreg> for _libc_fpxreg"],["impl PartialEq<_libc_xmmreg> for _libc_xmmreg"],["impl PartialEq<_libc_fpstate> for _libc_fpstate"],["impl PartialEq<user_regs_struct> for user_regs_struct"],["impl PartialEq<user> for user"],["impl PartialEq<mcontext_t> for mcontext_t"],["impl PartialEq<ipc_perm> for ipc_perm"],["impl PartialEq<shmid_ds> for shmid_ds"],["impl PartialEq<seccomp_notif_sizes> for seccomp_notif_sizes"],["impl PartialEq<ptrace_rseq_configuration> for ptrace_rseq_configuration"],["impl PartialEq<user_fpregs_struct> for user_fpregs_struct"],["impl PartialEq<ucontext_t> for ucontext_t"],["impl PartialEq<statvfs> for statvfs"],["impl PartialEq<clone_args> for clone_args"],["impl PartialEq<sem_t> for sem_t"],["impl PartialEq<termios2> for termios2"],["impl PartialEq<pthread_mutexattr_t> for pthread_mutexattr_t"],["impl PartialEq<pthread_rwlockattr_t> for pthread_rwlockattr_t"],["impl PartialEq<pthread_condattr_t> for pthread_condattr_t"],["impl PartialEq<fanotify_event_metadata> for fanotify_event_metadata"],["impl PartialEq<open_how> for open_how"],["impl PartialEq<in6_addr> for in6_addr"]], -"nix":[["impl PartialEq<Dir> for Dir"],["impl<'d> PartialEq<Iter<'d>> for Iter<'d>"],["impl PartialEq<OwningIter> for OwningIter"],["impl PartialEq<Entry> for Entry"],["impl PartialEq<Type> for Type"],["impl PartialEq<Errno> for Errno"],["impl PartialEq<AtFlags> for AtFlags"],["impl PartialEq<OFlag> for OFlag"],["impl PartialEq<RenameFlags> for RenameFlags"],["impl PartialEq<SealFlag> for SealFlag"],["impl PartialEq<FdFlag> for FdFlag"],["impl<'a> PartialEq<FcntlArg<'a>> for FcntlArg<'a>"],["impl PartialEq<FlockArg> for FlockArg"],["impl PartialEq<SpliceFFlags> for SpliceFFlags"],["impl PartialEq<FallocateFlags> for FallocateFlags"],["impl PartialEq<PosixFadviseAdvice> for PosixFadviseAdvice"],["impl PartialEq<InterfaceAddress> for InterfaceAddress"],["impl PartialEq<InterfaceAddressIterator> for InterfaceAddressIterator"],["impl PartialEq<InterfaceFlags> for InterfaceFlags"],["impl PartialEq<ModuleInitFlags> for ModuleInitFlags"],["impl PartialEq<DeleteModuleFlags> for DeleteModuleFlags"],["impl PartialEq<MsFlags> for MsFlags"],["impl PartialEq<MntFlags> for MntFlags"],["impl PartialEq<MQ_OFlag> for MQ_OFlag"],["impl PartialEq<MqAttr> for MqAttr"],["impl PartialEq<PollFd> for PollFd"],["impl PartialEq<PollFlags> for PollFlags"],["impl PartialEq<OpenptyResult> for OpenptyResult"],["impl PartialEq<PtyMaster> for PtyMaster"],["impl PartialEq<CloneFlags> for CloneFlags"],["impl PartialEq<CpuSet> for CpuSet"],["impl PartialEq<AioFsyncMode> for AioFsyncMode"],["impl PartialEq<LioMode> for LioMode"],["impl PartialEq<AioCancelStat> for AioCancelStat"],["impl PartialEq<EpollFlags> for EpollFlags"],["impl PartialEq<EpollOp> for EpollOp"],["impl PartialEq<EpollCreateFlags> for EpollCreateFlags"],["impl PartialEq<EpollEvent> for EpollEvent"],["impl PartialEq<EfdFlags> for EfdFlags"],["impl PartialEq<MemFdCreateFlag> for MemFdCreateFlag"],["impl PartialEq<ProtFlags> for ProtFlags"],["impl PartialEq<MapFlags> for MapFlags"],["impl PartialEq<MRemapFlags> for MRemapFlags"],["impl PartialEq<MmapAdvise> for MmapAdvise"],["impl PartialEq<MsFlags> for MsFlags"],["impl PartialEq<MlockAllFlags> for MlockAllFlags"],["impl PartialEq<Persona> for Persona"],["impl PartialEq<Request> for Request"],["impl PartialEq<Event> for Event"],["impl PartialEq<Options> for Options"],["impl PartialEq<QuotaType> for QuotaType"],["impl PartialEq<QuotaFmt> for QuotaFmt"],["impl PartialEq<QuotaValidFlags> for QuotaValidFlags"],["impl PartialEq<Dqblk> for Dqblk"],["impl PartialEq<RebootMode> for RebootMode"],["impl PartialEq<Resource> for Resource"],["impl PartialEq<UsageWho> for UsageWho"],["impl PartialEq<Usage> for Usage"],["impl PartialEq<FdSet> for FdSet"],["impl PartialEq<Signal> for Signal"],["impl PartialEq<SignalIterator> for SignalIterator"],["impl PartialEq<SaFlags> for SaFlags"],["impl PartialEq<SigmaskHow> for SigmaskHow"],["impl PartialEq<SigSet> for SigSet"],["impl PartialEq<SigHandler> for SigHandler"],["impl PartialEq<SigAction> for SigAction"],["impl PartialEq<SigevNotify> for SigevNotify"],["impl PartialEq<SigEvent> for SigEvent"],["impl PartialEq<SfdFlags> for SfdFlags"],["impl PartialEq<SignalFd> for SignalFd"],["impl PartialEq<AddressFamily> for AddressFamily"],["impl PartialEq<InetAddr> for InetAddr"],["impl PartialEq<IpAddr> for IpAddr"],["impl PartialEq<Ipv4Addr> for Ipv4Addr"],["impl PartialEq<Ipv6Addr> for Ipv6Addr"],["impl PartialEq<UnixAddr> for UnixAddr"],["impl PartialEq<SockaddrIn> for SockaddrIn"],["impl PartialEq<SockaddrIn6> for SockaddrIn6"],["impl PartialEq<SockaddrStorage> for SockaddrStorage"],["impl PartialEq<SockAddr> for SockAddr"],["impl PartialEq<NetlinkAddr> for NetlinkAddr"],["impl PartialEq<AlgAddr> for AlgAddr"],["impl PartialEq<LinkAddr> for LinkAddr"],["impl PartialEq<VsockAddr> for VsockAddr"],["impl PartialEq<ReuseAddr> for ReuseAddr"],["impl PartialEq<ReusePort> for ReusePort"],["impl PartialEq<TcpNoDelay> for TcpNoDelay"],["impl PartialEq<Linger> for Linger"],["impl PartialEq<IpAddMembership> for IpAddMembership"],["impl PartialEq<IpDropMembership> for IpDropMembership"],["impl PartialEq<Ipv6AddMembership> for Ipv6AddMembership"],["impl PartialEq<Ipv6DropMembership> for Ipv6DropMembership"],["impl PartialEq<IpMulticastTtl> for IpMulticastTtl"],["impl PartialEq<IpMulticastLoop> for IpMulticastLoop"],["impl PartialEq<Priority> for Priority"],["impl PartialEq<IpTos> for IpTos"],["impl PartialEq<Ipv6TClass> for Ipv6TClass"],["impl PartialEq<IpFreebind> for IpFreebind"],["impl PartialEq<ReceiveTimeout> for ReceiveTimeout"],["impl PartialEq<SendTimeout> for SendTimeout"],["impl PartialEq<Broadcast> for Broadcast"],["impl PartialEq<OobInline> for OobInline"],["impl PartialEq<SocketError> for SocketError"],["impl PartialEq<DontRoute> for DontRoute"],["impl PartialEq<KeepAlive> for KeepAlive"],["impl PartialEq<PeerCredentials> for PeerCredentials"],["impl PartialEq<TcpKeepIdle> for TcpKeepIdle"],["impl PartialEq<TcpMaxSeg> for TcpMaxSeg"],["impl PartialEq<TcpKeepCount> for TcpKeepCount"],["impl PartialEq<TcpRepair> for TcpRepair"],["impl PartialEq<TcpKeepInterval> for TcpKeepInterval"],["impl PartialEq<TcpUserTimeout> for TcpUserTimeout"],["impl PartialEq<RcvBuf> for RcvBuf"],["impl PartialEq<SndBuf> for SndBuf"],["impl PartialEq<RcvBufForce> for RcvBufForce"],["impl PartialEq<SndBufForce> for SndBufForce"],["impl PartialEq<SockType> for SockType"],["impl PartialEq<AcceptConn> for AcceptConn"],["impl PartialEq<BindToDevice> for BindToDevice"],["impl PartialEq<OriginalDst> for OriginalDst"],["impl PartialEq<Ip6tOriginalDst> for Ip6tOriginalDst"],["impl PartialEq<Timestamping> for Timestamping"],["impl PartialEq<ReceiveTimestamp> for ReceiveTimestamp"],["impl PartialEq<ReceiveTimestampns> for ReceiveTimestampns"],["impl PartialEq<IpTransparent> for IpTransparent"],["impl PartialEq<Mark> for Mark"],["impl PartialEq<PassCred> for PassCred"],["impl PartialEq<TcpCongestion> for TcpCongestion"],["impl PartialEq<Ipv4PacketInfo> for Ipv4PacketInfo"],["impl PartialEq<Ipv6RecvPacketInfo> for Ipv6RecvPacketInfo"],["impl PartialEq<Ipv4OrigDstAddr> for Ipv4OrigDstAddr"],["impl PartialEq<UdpGsoSegment> for UdpGsoSegment"],["impl PartialEq<UdpGroSegment> for UdpGroSegment"],["impl PartialEq<TxTime> for TxTime"],["impl PartialEq<RxqOvfl> for RxqOvfl"],["impl PartialEq<Ipv6V6Only> for Ipv6V6Only"],["impl PartialEq<Ipv4RecvErr> for Ipv4RecvErr"],["impl PartialEq<Ipv6RecvErr> for Ipv6RecvErr"],["impl PartialEq<IpMtu> for IpMtu"],["impl PartialEq<Ipv4Ttl> for Ipv4Ttl"],["impl PartialEq<Ipv6Ttl> for Ipv6Ttl"],["impl PartialEq<Ipv6OrigDstAddr> for Ipv6OrigDstAddr"],["impl PartialEq<Ipv6DontFrag> for Ipv6DontFrag"],["impl PartialEq<SockType> for SockType"],["impl PartialEq<SockProtocol> for SockProtocol"],["impl PartialEq<TimestampingFlag> for TimestampingFlag"],["impl PartialEq<SockFlag> for SockFlag"],["impl PartialEq<MsgFlags> for MsgFlags"],["impl PartialEq<UnixCredentials> for UnixCredentials"],["impl PartialEq<IpMembershipRequest> for IpMembershipRequest"],["impl PartialEq<Ipv6MembershipRequest> for Ipv6MembershipRequest"],["impl<'a, 's, S: PartialEq> PartialEq<RecvMsg<'a, 's, S>> for RecvMsg<'a, 's, S>"],["impl<'a> PartialEq<CmsgIterator<'a>> for CmsgIterator<'a>"],["impl PartialEq<ControlMessageOwned> for ControlMessageOwned"],["impl PartialEq<Timestamps> for Timestamps"],["impl<'a> PartialEq<ControlMessage<'a>> for ControlMessage<'a>"],["impl PartialEq<Shutdown> for Shutdown"],["impl PartialEq<SFlag> for SFlag"],["impl PartialEq<Mode> for Mode"],["impl PartialEq<FsType> for FsType"],["impl PartialEq<FsFlags> for FsFlags"],["impl PartialEq<Statvfs> for Statvfs"],["impl PartialEq<SysInfo> for SysInfo"],["impl PartialEq<Termios> for Termios"],["impl PartialEq<BaudRate> for BaudRate"],["impl PartialEq<SetArg> for SetArg"],["impl PartialEq<FlushArg> for FlushArg"],["impl PartialEq<FlowArg> for FlowArg"],["impl PartialEq<SpecialCharacterIndices> for SpecialCharacterIndices"],["impl PartialEq<InputFlags> for InputFlags"],["impl PartialEq<OutputFlags> for OutputFlags"],["impl PartialEq<ControlFlags> for ControlFlags"],["impl PartialEq<LocalFlags> for LocalFlags"],["impl PartialEq<Expiration> for Expiration"],["impl PartialEq<TimerSetTimeFlags> for TimerSetTimeFlags"],["impl PartialEq<TimeSpec> for TimeSpec"],["impl PartialEq<TimeVal> for TimeVal"],["impl PartialEq<RemoteIoVec> for RemoteIoVec"],["impl<T: PartialEq> PartialEq<IoVec<T>> for IoVec<T>"],["impl PartialEq<UtsName> for UtsName"],["impl PartialEq<WaitPidFlag> for WaitPidFlag"],["impl PartialEq<WaitStatus> for WaitStatus"],["impl PartialEq<Id> for Id"],["impl PartialEq<AddWatchFlags> for AddWatchFlags"],["impl PartialEq<InitFlags> for InitFlags"],["impl PartialEq<WatchDescriptor> for WatchDescriptor"],["impl PartialEq<ClockId> for ClockId"],["impl PartialEq<TimerFlags> for TimerFlags"],["impl PartialEq<ClockId> for ClockId"],["impl PartialEq<UContext> for UContext"],["impl PartialEq<Uid> for Uid"],["impl PartialEq<Gid> for Gid"],["impl PartialEq<Pid> for Pid"],["impl PartialEq<PathconfVar> for PathconfVar"],["impl PartialEq<SysconfVar> for SysconfVar"],["impl PartialEq<ResUid> for ResUid"],["impl PartialEq<ResGid> for ResGid"],["impl PartialEq<AccessFlags> for AccessFlags"],["impl PartialEq<User> for User"],["impl PartialEq<Group> for Group"]], -"once_cell":[["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"],["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"]], -"parking_lot":[["impl PartialEq<WaitTimeoutResult> for WaitTimeoutResult"],["impl PartialEq<OnceState> for OnceState"]], -"parking_lot_core":[["impl PartialEq<ParkResult> for ParkResult"],["impl PartialEq<UnparkResult> for UnparkResult"],["impl PartialEq<RequeueOp> for RequeueOp"],["impl PartialEq<FilterOp> for FilterOp"],["impl PartialEq<UnparkToken> for UnparkToken"],["impl PartialEq<ParkToken> for ParkToken"]], -"ppv_lite86":[["impl PartialEq<vec128_storage> for vec128_storage"],["impl PartialEq<vec256_storage> for vec256_storage"],["impl PartialEq<vec512_storage> for vec512_storage"]], -"primitive_types":[["impl PartialEq<Error> for Error"],["impl PartialEq<U128> for U128"],["impl PartialEq<U256> for U256"],["impl PartialEq<U512> for U512"],["impl PartialEq<H128> for H128"],["impl PartialEq<H160> for H160"],["impl PartialEq<H256> for H256"],["impl PartialEq<H384> for H384"],["impl PartialEq<H512> for H512"],["impl PartialEq<H768> for H768"]], -"proc_macro2":[["impl PartialEq<Delimiter> for Delimiter"],["impl PartialEq<Spacing> for Spacing"],["impl PartialEq<Ident> for Ident"],["impl<T> PartialEq<T> for Identwhere
        T: ?Sized + AsRef<str>,
    "]], -"rand":[["impl PartialEq<Bernoulli> for Bernoulli"],["impl PartialEq<BernoulliError> for BernoulliError"],["impl<X: PartialEq + SampleUniform + PartialOrd> PartialEq<WeightedIndex<X>> for WeightedIndex<X>where
        X::Sampler: PartialEq,
    "],["impl PartialEq<WeightedError> for WeightedError"],["impl<X: PartialEq + SampleUniform> PartialEq<Uniform<X>> for Uniform<X>where
        X::Sampler: PartialEq,
    "],["impl<X: PartialEq> PartialEq<UniformInt<X>> for UniformInt<X>"],["impl<X: PartialEq> PartialEq<UniformFloat<X>> for UniformFloat<X>"],["impl PartialEq<StepRng> for StepRng"],["impl PartialEq<IndexVec> for IndexVec"]], -"rand_chacha":[["impl PartialEq<ChaCha20Core> for ChaCha20Core"],["impl PartialEq<ChaCha20Rng> for ChaCha20Rng"],["impl PartialEq<ChaCha12Core> for ChaCha12Core"],["impl PartialEq<ChaCha12Rng> for ChaCha12Rng"],["impl PartialEq<ChaCha8Core> for ChaCha8Core"],["impl PartialEq<ChaCha8Rng> for ChaCha8Rng"]], -"regex":[["impl PartialEq<Error> for Error"],["impl<'t> PartialEq<Match<'t>> for Match<'t>"],["impl<'t> PartialEq<Match<'t>> for Match<'t>"]], -"regex_syntax":[["impl PartialEq<Error> for Error"],["impl PartialEq<ErrorKind> for ErrorKind"],["impl PartialEq<Span> for Span"],["impl PartialEq<Position> for Position"],["impl PartialEq<WithComments> for WithComments"],["impl PartialEq<Comment> for Comment"],["impl PartialEq<Ast> for Ast"],["impl PartialEq<Alternation> for Alternation"],["impl PartialEq<Concat> for Concat"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<LiteralKind> for LiteralKind"],["impl PartialEq<SpecialLiteralKind> for SpecialLiteralKind"],["impl PartialEq<HexLiteralKind> for HexLiteralKind"],["impl PartialEq<Class> for Class"],["impl PartialEq<ClassPerl> for ClassPerl"],["impl PartialEq<ClassPerlKind> for ClassPerlKind"],["impl PartialEq<ClassAscii> for ClassAscii"],["impl PartialEq<ClassAsciiKind> for ClassAsciiKind"],["impl PartialEq<ClassUnicode> for ClassUnicode"],["impl PartialEq<ClassUnicodeKind> for ClassUnicodeKind"],["impl PartialEq<ClassUnicodeOpKind> for ClassUnicodeOpKind"],["impl PartialEq<ClassBracketed> for ClassBracketed"],["impl PartialEq<ClassSet> for ClassSet"],["impl PartialEq<ClassSetItem> for ClassSetItem"],["impl PartialEq<ClassSetRange> for ClassSetRange"],["impl PartialEq<ClassSetUnion> for ClassSetUnion"],["impl PartialEq<ClassSetBinaryOp> for ClassSetBinaryOp"],["impl PartialEq<ClassSetBinaryOpKind> for ClassSetBinaryOpKind"],["impl PartialEq<Assertion> for Assertion"],["impl PartialEq<AssertionKind> for AssertionKind"],["impl PartialEq<Repetition> for Repetition"],["impl PartialEq<RepetitionOp> for RepetitionOp"],["impl PartialEq<RepetitionKind> for RepetitionKind"],["impl PartialEq<RepetitionRange> for RepetitionRange"],["impl PartialEq<Group> for Group"],["impl PartialEq<GroupKind> for GroupKind"],["impl PartialEq<CaptureName> for CaptureName"],["impl PartialEq<SetFlags> for SetFlags"],["impl PartialEq<Flags> for Flags"],["impl PartialEq<FlagsItem> for FlagsItem"],["impl PartialEq<FlagsItemKind> for FlagsItemKind"],["impl PartialEq<Flag> for Flag"],["impl PartialEq<Error> for Error"],["impl PartialEq<Literals> for Literals"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<Error> for Error"],["impl PartialEq<ErrorKind> for ErrorKind"],["impl PartialEq<Hir> for Hir"],["impl PartialEq<HirKind> for HirKind"],["impl PartialEq<Literal> for Literal"],["impl PartialEq<Class> for Class"],["impl PartialEq<ClassUnicode> for ClassUnicode"],["impl PartialEq<ClassUnicodeRange> for ClassUnicodeRange"],["impl PartialEq<ClassBytes> for ClassBytes"],["impl PartialEq<ClassBytesRange> for ClassBytesRange"],["impl PartialEq<Anchor> for Anchor"],["impl PartialEq<WordBoundary> for WordBoundary"],["impl PartialEq<Group> for Group"],["impl PartialEq<GroupKind> for GroupKind"],["impl PartialEq<Repetition> for Repetition"],["impl PartialEq<RepetitionKind> for RepetitionKind"],["impl PartialEq<RepetitionRange> for RepetitionRange"],["impl PartialEq<Utf8Sequence> for Utf8Sequence"],["impl PartialEq<Utf8Range> for Utf8Range"]], -"rlp":[["impl PartialEq<DecoderError> for DecoderError"]], -"scan_fmt":[["impl PartialEq<ScanError> for ScanError"]], -"serde":[["impl PartialEq<Error> for Error"],["impl<'a> PartialEq<Unexpected<'a>> for Unexpected<'a>"]], -"shale":[["impl<T> PartialEq<ObjPtr<T>> for ObjPtr<T>"]], -"smallvec":[["impl<A: Array, B: Array> PartialEq<SmallVec<B>> for SmallVec<A>where
        A::Item: PartialEq<B::Item>,
    "]], -"syn":[["impl PartialEq<Underscore> for Underscore"],["impl PartialEq<Abstract> for Abstract"],["impl PartialEq<As> for As"],["impl PartialEq<Async> for Async"],["impl PartialEq<Auto> for Auto"],["impl PartialEq<Await> for Await"],["impl PartialEq<Become> for Become"],["impl PartialEq<Box> for Box"],["impl PartialEq<Break> for Break"],["impl PartialEq<Const> for Const"],["impl PartialEq<Continue> for Continue"],["impl PartialEq<Crate> for Crate"],["impl PartialEq<Default> for Default"],["impl PartialEq<Do> for Do"],["impl PartialEq<Dyn> for Dyn"],["impl PartialEq<Else> for Else"],["impl PartialEq<Enum> for Enum"],["impl PartialEq<Extern> for Extern"],["impl PartialEq<Final> for Final"],["impl PartialEq<Fn> for Fn"],["impl PartialEq<For> for For"],["impl PartialEq<If> for If"],["impl PartialEq<Impl> for Impl"],["impl PartialEq<In> for In"],["impl PartialEq<Let> for Let"],["impl PartialEq<Loop> for Loop"],["impl PartialEq<Macro> for Macro"],["impl PartialEq<Match> for Match"],["impl PartialEq<Mod> for Mod"],["impl PartialEq<Move> for Move"],["impl PartialEq<Mut> for Mut"],["impl PartialEq<Override> for Override"],["impl PartialEq<Priv> for Priv"],["impl PartialEq<Pub> for Pub"],["impl PartialEq<Ref> for Ref"],["impl PartialEq<Return> for Return"],["impl PartialEq<SelfType> for SelfType"],["impl PartialEq<SelfValue> for SelfValue"],["impl PartialEq<Static> for Static"],["impl PartialEq<Struct> for Struct"],["impl PartialEq<Super> for Super"],["impl PartialEq<Trait> for Trait"],["impl PartialEq<Try> for Try"],["impl PartialEq<Type> for Type"],["impl PartialEq<Typeof> for Typeof"],["impl PartialEq<Union> for Union"],["impl PartialEq<Unsafe> for Unsafe"],["impl PartialEq<Unsized> for Unsized"],["impl PartialEq<Use> for Use"],["impl PartialEq<Virtual> for Virtual"],["impl PartialEq<Where> for Where"],["impl PartialEq<While> for While"],["impl PartialEq<Yield> for Yield"],["impl PartialEq<Add> for Add"],["impl PartialEq<AddEq> for AddEq"],["impl PartialEq<And> for And"],["impl PartialEq<AndAnd> for AndAnd"],["impl PartialEq<AndEq> for AndEq"],["impl PartialEq<At> for At"],["impl PartialEq<Bang> for Bang"],["impl PartialEq<Caret> for Caret"],["impl PartialEq<CaretEq> for CaretEq"],["impl PartialEq<Colon> for Colon"],["impl PartialEq<Colon2> for Colon2"],["impl PartialEq<Comma> for Comma"],["impl PartialEq<Div> for Div"],["impl PartialEq<DivEq> for DivEq"],["impl PartialEq<Dollar> for Dollar"],["impl PartialEq<Dot> for Dot"],["impl PartialEq<Dot2> for Dot2"],["impl PartialEq<Dot3> for Dot3"],["impl PartialEq<DotDotEq> for DotDotEq"],["impl PartialEq<Eq> for Eq"],["impl PartialEq<EqEq> for EqEq"],["impl PartialEq<Ge> for Ge"],["impl PartialEq<Gt> for Gt"],["impl PartialEq<Le> for Le"],["impl PartialEq<Lt> for Lt"],["impl PartialEq<MulEq> for MulEq"],["impl PartialEq<Ne> for Ne"],["impl PartialEq<Or> for Or"],["impl PartialEq<OrEq> for OrEq"],["impl PartialEq<OrOr> for OrOr"],["impl PartialEq<Pound> for Pound"],["impl PartialEq<Question> for Question"],["impl PartialEq<RArrow> for RArrow"],["impl PartialEq<LArrow> for LArrow"],["impl PartialEq<Rem> for Rem"],["impl PartialEq<RemEq> for RemEq"],["impl PartialEq<FatArrow> for FatArrow"],["impl PartialEq<Semi> for Semi"],["impl PartialEq<Shl> for Shl"],["impl PartialEq<ShlEq> for ShlEq"],["impl PartialEq<Shr> for Shr"],["impl PartialEq<ShrEq> for ShrEq"],["impl PartialEq<Star> for Star"],["impl PartialEq<Sub> for Sub"],["impl PartialEq<SubEq> for SubEq"],["impl PartialEq<Tilde> for Tilde"],["impl PartialEq<Brace> for Brace"],["impl PartialEq<Bracket> for Bracket"],["impl PartialEq<Paren> for Paren"],["impl PartialEq<Group> for Group"],["impl PartialEq<Member> for Member"],["impl PartialEq<Index> for Index"],["impl<'a> PartialEq<ImplGenerics<'a>> for ImplGenerics<'a>"],["impl<'a> PartialEq<TypeGenerics<'a>> for TypeGenerics<'a>"],["impl<'a> PartialEq<Turbofish<'a>> for Turbofish<'a>"],["impl PartialEq<Lifetime> for Lifetime"],["impl PartialEq<LitStr> for LitStr"],["impl PartialEq<LitByteStr> for LitByteStr"],["impl PartialEq<LitByte> for LitByte"],["impl PartialEq<LitChar> for LitChar"],["impl PartialEq<LitInt> for LitInt"],["impl PartialEq<LitFloat> for LitFloat"],["impl<'a> PartialEq<Cursor<'a>> for Cursor<'a>"],["impl<T, P> PartialEq<Punctuated<T, P>> for Punctuated<T, P>where
        T: PartialEq,
        P: PartialEq,
    "],["impl PartialEq<Abi> for Abi"],["impl PartialEq<AngleBracketedGenericArguments> for AngleBracketedGenericArguments"],["impl PartialEq<Arm> for Arm"],["impl PartialEq<AttrStyle> for AttrStyle"],["impl PartialEq<Attribute> for Attribute"],["impl PartialEq<BareFnArg> for BareFnArg"],["impl PartialEq<BinOp> for BinOp"],["impl PartialEq<Binding> for Binding"],["impl PartialEq<Block> for Block"],["impl PartialEq<BoundLifetimes> for BoundLifetimes"],["impl PartialEq<ConstParam> for ConstParam"],["impl PartialEq<Constraint> for Constraint"],["impl PartialEq<Data> for Data"],["impl PartialEq<DataEnum> for DataEnum"],["impl PartialEq<DataStruct> for DataStruct"],["impl PartialEq<DataUnion> for DataUnion"],["impl PartialEq<DeriveInput> for DeriveInput"],["impl PartialEq<Expr> for Expr"],["impl PartialEq<ExprArray> for ExprArray"],["impl PartialEq<ExprAssign> for ExprAssign"],["impl PartialEq<ExprAssignOp> for ExprAssignOp"],["impl PartialEq<ExprAsync> for ExprAsync"],["impl PartialEq<ExprAwait> for ExprAwait"],["impl PartialEq<ExprBinary> for ExprBinary"],["impl PartialEq<ExprBlock> for ExprBlock"],["impl PartialEq<ExprBox> for ExprBox"],["impl PartialEq<ExprBreak> for ExprBreak"],["impl PartialEq<ExprCall> for ExprCall"],["impl PartialEq<ExprCast> for ExprCast"],["impl PartialEq<ExprClosure> for ExprClosure"],["impl PartialEq<ExprContinue> for ExprContinue"],["impl PartialEq<ExprField> for ExprField"],["impl PartialEq<ExprForLoop> for ExprForLoop"],["impl PartialEq<ExprGroup> for ExprGroup"],["impl PartialEq<ExprIf> for ExprIf"],["impl PartialEq<ExprIndex> for ExprIndex"],["impl PartialEq<ExprLet> for ExprLet"],["impl PartialEq<ExprLit> for ExprLit"],["impl PartialEq<ExprLoop> for ExprLoop"],["impl PartialEq<ExprMacro> for ExprMacro"],["impl PartialEq<ExprMatch> for ExprMatch"],["impl PartialEq<ExprMethodCall> for ExprMethodCall"],["impl PartialEq<ExprParen> for ExprParen"],["impl PartialEq<ExprPath> for ExprPath"],["impl PartialEq<ExprRange> for ExprRange"],["impl PartialEq<ExprReference> for ExprReference"],["impl PartialEq<ExprRepeat> for ExprRepeat"],["impl PartialEq<ExprReturn> for ExprReturn"],["impl PartialEq<ExprStruct> for ExprStruct"],["impl PartialEq<ExprTry> for ExprTry"],["impl PartialEq<ExprTryBlock> for ExprTryBlock"],["impl PartialEq<ExprTuple> for ExprTuple"],["impl PartialEq<ExprType> for ExprType"],["impl PartialEq<ExprUnary> for ExprUnary"],["impl PartialEq<ExprUnsafe> for ExprUnsafe"],["impl PartialEq<ExprWhile> for ExprWhile"],["impl PartialEq<ExprYield> for ExprYield"],["impl PartialEq<Field> for Field"],["impl PartialEq<FieldPat> for FieldPat"],["impl PartialEq<FieldValue> for FieldValue"],["impl PartialEq<Fields> for Fields"],["impl PartialEq<FieldsNamed> for FieldsNamed"],["impl PartialEq<FieldsUnnamed> for FieldsUnnamed"],["impl PartialEq<File> for File"],["impl PartialEq<FnArg> for FnArg"],["impl PartialEq<ForeignItem> for ForeignItem"],["impl PartialEq<ForeignItemFn> for ForeignItemFn"],["impl PartialEq<ForeignItemMacro> for ForeignItemMacro"],["impl PartialEq<ForeignItemStatic> for ForeignItemStatic"],["impl PartialEq<ForeignItemType> for ForeignItemType"],["impl PartialEq<GenericArgument> for GenericArgument"],["impl PartialEq<GenericMethodArgument> for GenericMethodArgument"],["impl PartialEq<GenericParam> for GenericParam"],["impl PartialEq<Generics> for Generics"],["impl PartialEq<ImplItem> for ImplItem"],["impl PartialEq<ImplItemConst> for ImplItemConst"],["impl PartialEq<ImplItemMacro> for ImplItemMacro"],["impl PartialEq<ImplItemMethod> for ImplItemMethod"],["impl PartialEq<ImplItemType> for ImplItemType"],["impl PartialEq<Item> for Item"],["impl PartialEq<ItemConst> for ItemConst"],["impl PartialEq<ItemEnum> for ItemEnum"],["impl PartialEq<ItemExternCrate> for ItemExternCrate"],["impl PartialEq<ItemFn> for ItemFn"],["impl PartialEq<ItemForeignMod> for ItemForeignMod"],["impl PartialEq<ItemImpl> for ItemImpl"],["impl PartialEq<ItemMacro> for ItemMacro"],["impl PartialEq<ItemMacro2> for ItemMacro2"],["impl PartialEq<ItemMod> for ItemMod"],["impl PartialEq<ItemStatic> for ItemStatic"],["impl PartialEq<ItemStruct> for ItemStruct"],["impl PartialEq<ItemTrait> for ItemTrait"],["impl PartialEq<ItemTraitAlias> for ItemTraitAlias"],["impl PartialEq<ItemType> for ItemType"],["impl PartialEq<ItemUnion> for ItemUnion"],["impl PartialEq<ItemUse> for ItemUse"],["impl PartialEq<Label> for Label"],["impl PartialEq<LifetimeDef> for LifetimeDef"],["impl PartialEq<Lit> for Lit"],["impl PartialEq<LitBool> for LitBool"],["impl PartialEq<Local> for Local"],["impl PartialEq<Macro> for Macro"],["impl PartialEq<MacroDelimiter> for MacroDelimiter"],["impl PartialEq<Meta> for Meta"],["impl PartialEq<MetaList> for MetaList"],["impl PartialEq<MetaNameValue> for MetaNameValue"],["impl PartialEq<MethodTurbofish> for MethodTurbofish"],["impl PartialEq<NestedMeta> for NestedMeta"],["impl PartialEq<ParenthesizedGenericArguments> for ParenthesizedGenericArguments"],["impl PartialEq<Pat> for Pat"],["impl PartialEq<PatBox> for PatBox"],["impl PartialEq<PatIdent> for PatIdent"],["impl PartialEq<PatLit> for PatLit"],["impl PartialEq<PatMacro> for PatMacro"],["impl PartialEq<PatOr> for PatOr"],["impl PartialEq<PatPath> for PatPath"],["impl PartialEq<PatRange> for PatRange"],["impl PartialEq<PatReference> for PatReference"],["impl PartialEq<PatRest> for PatRest"],["impl PartialEq<PatSlice> for PatSlice"],["impl PartialEq<PatStruct> for PatStruct"],["impl PartialEq<PatTuple> for PatTuple"],["impl PartialEq<PatTupleStruct> for PatTupleStruct"],["impl PartialEq<PatType> for PatType"],["impl PartialEq<PatWild> for PatWild"],["impl PartialEq<Path> for Path"],["impl PartialEq<PathArguments> for PathArguments"],["impl PartialEq<PathSegment> for PathSegment"],["impl PartialEq<PredicateEq> for PredicateEq"],["impl PartialEq<PredicateLifetime> for PredicateLifetime"],["impl PartialEq<PredicateType> for PredicateType"],["impl PartialEq<QSelf> for QSelf"],["impl PartialEq<RangeLimits> for RangeLimits"],["impl PartialEq<Receiver> for Receiver"],["impl PartialEq<ReturnType> for ReturnType"],["impl PartialEq<Signature> for Signature"],["impl PartialEq<Stmt> for Stmt"],["impl PartialEq<TraitBound> for TraitBound"],["impl PartialEq<TraitBoundModifier> for TraitBoundModifier"],["impl PartialEq<TraitItem> for TraitItem"],["impl PartialEq<TraitItemConst> for TraitItemConst"],["impl PartialEq<TraitItemMacro> for TraitItemMacro"],["impl PartialEq<TraitItemMethod> for TraitItemMethod"],["impl PartialEq<TraitItemType> for TraitItemType"],["impl PartialEq<Type> for Type"],["impl PartialEq<TypeArray> for TypeArray"],["impl PartialEq<TypeBareFn> for TypeBareFn"],["impl PartialEq<TypeGroup> for TypeGroup"],["impl PartialEq<TypeImplTrait> for TypeImplTrait"],["impl PartialEq<TypeInfer> for TypeInfer"],["impl PartialEq<TypeMacro> for TypeMacro"],["impl PartialEq<TypeNever> for TypeNever"],["impl PartialEq<TypeParam> for TypeParam"],["impl PartialEq<TypeParamBound> for TypeParamBound"],["impl PartialEq<TypeParen> for TypeParen"],["impl PartialEq<TypePath> for TypePath"],["impl PartialEq<TypePtr> for TypePtr"],["impl PartialEq<TypeReference> for TypeReference"],["impl PartialEq<TypeSlice> for TypeSlice"],["impl PartialEq<TypeTraitObject> for TypeTraitObject"],["impl PartialEq<TypeTuple> for TypeTuple"],["impl PartialEq<UnOp> for UnOp"],["impl PartialEq<UseGlob> for UseGlob"],["impl PartialEq<UseGroup> for UseGroup"],["impl PartialEq<UseName> for UseName"],["impl PartialEq<UsePath> for UsePath"],["impl PartialEq<UseRename> for UseRename"],["impl PartialEq<UseTree> for UseTree"],["impl PartialEq<Variadic> for Variadic"],["impl PartialEq<Variant> for Variant"],["impl PartialEq<VisCrate> for VisCrate"],["impl PartialEq<VisPublic> for VisPublic"],["impl PartialEq<VisRestricted> for VisRestricted"],["impl PartialEq<Visibility> for Visibility"],["impl PartialEq<WhereClause> for WhereClause"],["impl PartialEq<WherePredicate> for WherePredicate"],["impl PartialEq<Nothing> for Nothing"]], -"tokio":[["impl PartialEq<RuntimeFlavor> for RuntimeFlavor"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl<T: PartialEq> PartialEq<TrySendError<T>> for TrySendError<T>"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<RecvError> for RecvError"],["impl PartialEq<TryRecvError> for TryRecvError"],["impl PartialEq<TryAcquireError> for TryAcquireError"],["impl<T: PartialEq> PartialEq<OnceCell<T>> for OnceCell<T>"],["impl<T: PartialEq> PartialEq<SetError<T>> for SetError<T>"]], -"typenum":[["impl PartialEq<B0> for B0"],["impl PartialEq<B1> for B1"],["impl<U: PartialEq + Unsigned + NonZero> PartialEq<PInt<U>> for PInt<U>"],["impl<U: PartialEq + Unsigned + NonZero> PartialEq<NInt<U>> for NInt<U>"],["impl PartialEq<Z0> for Z0"],["impl PartialEq<UTerm> for UTerm"],["impl<U: PartialEq, B: PartialEq> PartialEq<UInt<U, B>> for UInt<U, B>"],["impl PartialEq<ATerm> for ATerm"],["impl<V: PartialEq, A: PartialEq> PartialEq<TArr<V, A>> for TArr<V, A>"],["impl PartialEq<Greater> for Greater"],["impl PartialEq<Less> for Less"],["impl PartialEq<Equal> for Equal"]], -"uint":[["impl PartialEq<FromStrRadixErrKind> for FromStrRadixErrKind"],["impl PartialEq<FromDecStrErr> for FromDecStrErr"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/fmt/trait.Debug.js b/docs/implementors/core/fmt/trait.Debug.js deleted file mode 100644 index 0463240d1f35..000000000000 --- a/docs/implementors/core/fmt/trait.Debug.js +++ /dev/null @@ -1,51 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Debug for AHasher"],["impl Debug for RandomState"]], -"aho_corasick":[["impl<S: Debug + StateID> Debug for AhoCorasick<S>"],["impl<'a, 'b, S: Debug + StateID> Debug for FindIter<'a, 'b, S>"],["impl<'a, 'b, S: Debug + StateID> Debug for FindOverlappingIter<'a, 'b, S>"],["impl<'a, R: Debug, S: Debug + StateID> Debug for StreamFindIter<'a, R, S>"],["impl Debug for AhoCorasickBuilder"],["impl Debug for MatchKind"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for MatchKind"],["impl Debug for Config"],["impl Debug for Builder"],["impl Debug for Searcher"],["impl<'s, 'h> Debug for FindIter<'s, 'h>"],["impl Debug for Match"]], -"aiofut":[["impl Debug for Error"]], -"bincode":[["impl Debug for Config"],["impl Debug for ErrorKind"]], -"block_buffer":[["impl Debug for Eager"],["impl Debug for Lazy"],["impl Debug for Error"],["impl<BlockSize: Debug, Kind: Debug> Debug for BlockBuffer<BlockSize, Kind>where
        BlockSize: ArrayLength<u8> + IsLess<U256>,
        Le<BlockSize, U256>: NonZero,
        Kind: BufferKind,
    "]], -"byteorder":[["impl Debug for BigEndian"],["impl Debug for LittleEndian"]], -"bytes":[["impl<T: Debug, U: Debug> Debug for Chain<T, U>"],["impl<T: Debug> Debug for IntoIter<T>"],["impl<T: Debug> Debug for Limit<T>"],["impl<B: Debug> Debug for Reader<B>"],["impl<T: Debug> Debug for Take<T>"],["impl Debug for UninitSlice"],["impl<B: Debug> Debug for Writer<B>"],["impl Debug for Bytes"],["impl Debug for BytesMut"]], -"crossbeam_channel":[["impl<T> Debug for Sender<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Iter<'_, T>"],["impl<T> Debug for TryIter<'_, T>"],["impl<T> Debug for IntoIter<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl Debug for RecvTimeoutError"],["impl Debug for TrySelectError"],["impl Debug for SelectTimeoutError"],["impl Debug for TryReadyError"],["impl Debug for ReadyTimeoutError"],["impl<T> Debug for SendError<T>"],["impl<T> Debug for TrySendError<T>"],["impl<T> Debug for SendTimeoutError<T>"],["impl Debug for Select<'_>"],["impl Debug for SelectedOperation<'_>"]], -"crossbeam_utils":[["impl<T: Copy + Debug> Debug for AtomicCell<T>"],["impl<T: Debug> Debug for CachePadded<T>"],["impl Debug for Backoff"],["impl Debug for Parker"],["impl Debug for Unparker"],["impl<T: ?Sized + Debug> Debug for ShardedLock<T>"],["impl<T: Debug> Debug for ShardedLockReadGuard<'_, T>"],["impl<T: Debug> Debug for ShardedLockWriteGuard<'_, T>"],["impl Debug for WaitGroup"],["impl Debug for Scope<'_>"],["impl<'scope, 'env> Debug for ScopedThreadBuilder<'scope, 'env>"],["impl<T> Debug for ScopedJoinHandle<'_, T>"]], -"crypto_common":[["impl Debug for InvalidLength"]], -"digest":[["impl<T> Debug for RtVariableCoreWrapper<T>where
        T: VariableOutputCore + UpdateCore + AlgorithmName,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
    "],["impl<T> Debug for CoreWrapper<T>where
        T: BufferKindUser + AlgorithmName,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
    "],["impl<T> Debug for XofReaderCoreWrapper<T>where
        T: XofReaderCore + AlgorithmName,
        T::BlockSize: IsLess<U256>,
        Le<T::BlockSize, U256>: NonZero,
    "],["impl Debug for TruncSide"],["impl Debug for InvalidOutputSize"],["impl Debug for InvalidBufferSize"]], -"firewood":[["impl Debug for DBError"],["impl Debug for MerkleError"],["impl Debug for PartialPath"],["impl Debug for ProofError"]], -"futures_channel":[["impl<T: Debug> Debug for Sender<T>"],["impl<T: Debug> Debug for UnboundedSender<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl<T: Debug> Debug for UnboundedReceiver<T>"],["impl Debug for SendError"],["impl<T> Debug for TrySendError<T>"],["impl Debug for TryRecvError"],["impl<T: Debug> Debug for Sender<T>"],["impl<'a, T: Debug> Debug for Cancellation<'a, T>"],["impl Debug for Canceled"],["impl<T: Debug> Debug for Receiver<T>"]], -"futures_executor":[["impl Debug for LocalPool"],["impl Debug for LocalSpawner"],["impl<S: Debug + Stream + Unpin> Debug for BlockingStream<S>"],["impl Debug for EnterError"],["impl Debug for Enter"]], -"futures_task":[["impl Debug for SpawnError"],["impl<'a> Debug for WakerRef<'a>"],["impl<T> Debug for LocalFutureObj<'_, T>"],["impl<T> Debug for FutureObj<'_, T>"]], -"futures_util":[["impl<Fut: Debug> Debug for Fuse<Fut>"],["impl<F> Debug for Flatten<F>where
        Flatten<F, <F as Future>::Output>: Debug,
        F: Future,
    "],["impl<F> Debug for FlattenStream<F>where
        Flatten<F, <F as Future>::Output>: Debug,
        F: Future,
    "],["impl<Fut, F> Debug for Map<Fut, F>where
        Map<Fut, F>: Debug,
    "],["impl<F> Debug for IntoStream<F>where
        Once<F>: Debug,
    "],["impl<Fut, T> Debug for MapInto<Fut, T>where
        Map<Fut, IntoFn<T>>: Debug,
    "],["impl<Fut1, Fut2, F> Debug for Then<Fut1, Fut2, F>where
        Flatten<Map<Fut1, F>, Fut2>: Debug,
    "],["impl<Fut, F> Debug for Inspect<Fut, F>where
        Map<Fut, InspectFn<F>>: Debug,
    "],["impl<Fut> Debug for NeverError<Fut>where
        Map<Fut, OkFn<Never>>: Debug,
    "],["impl<Fut> Debug for UnitError<Fut>where
        Map<Fut, OkFn<()>>: Debug,
    "],["impl<Fut: Debug> Debug for CatchUnwind<Fut>"],["impl<T: Debug> Debug for RemoteHandle<T>"],["impl<Fut: Future + Debug> Debug for Remote<Fut>"],["impl<Fut: Future> Debug for Shared<Fut>"],["impl<Fut: Future> Debug for WeakShared<Fut>"],["impl<Fut: Debug> Debug for IntoFuture<Fut>"],["impl<Fut1, Fut2> Debug for TryFlatten<Fut1, Fut2>where
        TryFlatten<Fut1, Fut2>: Debug,
    "],["impl<Fut> Debug for TryFlattenStream<Fut>where
        TryFlatten<Fut, Fut::Ok>: Debug,
        Fut: TryFuture,
    "],["impl<Fut, Si> Debug for FlattenSink<Fut, Si>where
        TryFlatten<Fut, Si>: Debug,
    "],["impl<Fut1, Fut2, F> Debug for AndThen<Fut1, Fut2, F>where
        TryFlatten<MapOk<Fut1, F>, Fut2>: Debug,
    "],["impl<Fut1, Fut2, F> Debug for OrElse<Fut1, Fut2, F>where
        TryFlattenErr<MapErr<Fut1, F>, Fut2>: Debug,
    "],["impl<Fut, E> Debug for ErrInto<Fut, E>where
        MapErr<Fut, IntoFn<E>>: Debug,
    "],["impl<Fut, E> Debug for OkInto<Fut, E>where
        MapOk<Fut, IntoFn<E>>: Debug,
    "],["impl<Fut, F> Debug for InspectOk<Fut, F>where
        Inspect<IntoFuture<Fut>, InspectOkFn<F>>: Debug,
    "],["impl<Fut, F> Debug for InspectErr<Fut, F>where
        Inspect<IntoFuture<Fut>, InspectErrFn<F>>: Debug,
    "],["impl<Fut, F> Debug for MapOk<Fut, F>where
        Map<IntoFuture<Fut>, MapOkFn<F>>: Debug,
    "],["impl<Fut, F> Debug for MapErr<Fut, F>where
        Map<IntoFuture<Fut>, MapErrFn<F>>: Debug,
    "],["impl<Fut, F, G> Debug for MapOkOrElse<Fut, F, G>where
        Map<IntoFuture<Fut>, ChainFn<MapOkFn<F>, ChainFn<MapErrFn<G>, MergeResultFn>>>: Debug,
    "],["impl<Fut, F> Debug for UnwrapOrElse<Fut, F>where
        Map<IntoFuture<Fut>, UnwrapOrElseFn<F>>: Debug,
    "],["impl<F: Debug> Debug for Lazy<F>"],["impl<T: Debug> Debug for Pending<T>"],["impl<Fut: Debug + Future> Debug for MaybeDone<Fut>where
        Fut::Output: Debug,
    "],["impl<Fut: Debug + TryFuture> Debug for TryMaybeDone<Fut>where
        Fut::Ok: Debug,
    "],["impl<F: Debug> Debug for OptionFuture<F>"],["impl<F> Debug for PollFn<F>"],["impl<T: Debug> Debug for PollImmediate<T>"],["impl<T: Debug> Debug for Ready<T>"],["impl<Fut1, Fut2> Debug for Join<Fut1, Fut2>where
        Fut1: Future + Debug,
        Fut1::Output: Debug,
        Fut2: Future + Debug,
        Fut2::Output: Debug,
    "],["impl<Fut1, Fut2, Fut3> Debug for Join3<Fut1, Fut2, Fut3>where
        Fut1: Future + Debug,
        Fut1::Output: Debug,
        Fut2: Future + Debug,
        Fut2::Output: Debug,
        Fut3: Future + Debug,
        Fut3::Output: Debug,
    "],["impl<Fut1, Fut2, Fut3, Fut4> Debug for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Future + Debug,
        Fut1::Output: Debug,
        Fut2: Future + Debug,
        Fut2::Output: Debug,
        Fut3: Future + Debug,
        Fut3::Output: Debug,
        Fut4: Future + Debug,
        Fut4::Output: Debug,
    "],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Debug for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Future + Debug,
        Fut1::Output: Debug,
        Fut2: Future + Debug,
        Fut2::Output: Debug,
        Fut3: Future + Debug,
        Fut3::Output: Debug,
        Fut4: Future + Debug,
        Fut4::Output: Debug,
        Fut5: Future + Debug,
        Fut5::Output: Debug,
    "],["impl<F> Debug for JoinAll<F>where
        F: Future + Debug,
        F::Output: Debug,
    "],["impl<A: Debug, B: Debug> Debug for Select<A, B>"],["impl<Fut: Debug> Debug for SelectAll<Fut>"],["impl<Fut1, Fut2> Debug for TryJoin<Fut1, Fut2>where
        Fut1: TryFuture + Debug,
        Fut1::Ok: Debug,
        Fut1::Error: Debug,
        Fut2: TryFuture + Debug,
        Fut2::Ok: Debug,
        Fut2::Error: Debug,
    "],["impl<Fut1, Fut2, Fut3> Debug for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: TryFuture + Debug,
        Fut1::Ok: Debug,
        Fut1::Error: Debug,
        Fut2: TryFuture + Debug,
        Fut2::Ok: Debug,
        Fut2::Error: Debug,
        Fut3: TryFuture + Debug,
        Fut3::Ok: Debug,
        Fut3::Error: Debug,
    "],["impl<Fut1, Fut2, Fut3, Fut4> Debug for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: TryFuture + Debug,
        Fut1::Ok: Debug,
        Fut1::Error: Debug,
        Fut2: TryFuture + Debug,
        Fut2::Ok: Debug,
        Fut2::Error: Debug,
        Fut3: TryFuture + Debug,
        Fut3::Ok: Debug,
        Fut3::Error: Debug,
        Fut4: TryFuture + Debug,
        Fut4::Ok: Debug,
        Fut4::Error: Debug,
    "],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Debug for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: TryFuture + Debug,
        Fut1::Ok: Debug,
        Fut1::Error: Debug,
        Fut2: TryFuture + Debug,
        Fut2::Ok: Debug,
        Fut2::Error: Debug,
        Fut3: TryFuture + Debug,
        Fut3::Ok: Debug,
        Fut3::Error: Debug,
        Fut4: TryFuture + Debug,
        Fut4::Ok: Debug,
        Fut4::Error: Debug,
        Fut5: TryFuture + Debug,
        Fut5::Ok: Debug,
        Fut5::Error: Debug,
    "],["impl<F> Debug for TryJoinAll<F>where
        F: TryFuture + Debug,
        F::Ok: Debug,
        F::Error: Debug,
        F::Output: Debug,
    "],["impl<A: Debug, B: Debug> Debug for TrySelect<A, B>"],["impl<Fut: Debug> Debug for SelectOk<Fut>"],["impl<A: Debug, B: Debug> Debug for Either<A, B>"],["impl<St1: Debug, St2: Debug> Debug for Chain<St1, St2>"],["impl<St: Debug, C: Debug> Debug for Collect<St, C>"],["impl<St: Debug, FromA: Debug, FromB: Debug> Debug for Unzip<St, FromA, FromB>"],["impl<St: Debug + Stream> Debug for Concat<St>where
        St::Item: Debug,
    "],["impl<St: Debug> Debug for Cycle<St>"],["impl<St: Debug> Debug for Enumerate<St>"],["impl<St, Fut, F> Debug for Filter<St, Fut, F>where
        St: Stream + Debug,
        St::Item: Debug,
        Fut: Debug,
    "],["impl<St, Fut, F> Debug for FilterMap<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St> Debug for Flatten<St>where
        Flatten<St, St::Item>: Debug,
        St: Stream,
    "],["impl<St, Fut, T, F> Debug for Fold<St, Fut, T, F>where
        St: Debug,
        Fut: Debug,
        T: Debug,
    "],["impl<St, Si> Debug for Forward<St, Si>where
        Forward<St, Si, St::Ok>: Debug,
        St: TryStream,
    "],["impl<St, Fut, F> Debug for ForEach<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St: Debug> Debug for Fuse<St>"],["impl<St: Debug> Debug for StreamFuture<St>"],["impl<St, F> Debug for Inspect<St, F>where
        Map<St, InspectFn<F>>: Debug,
    "],["impl<St, F> Debug for Map<St, F>where
        St: Debug,
    "],["impl<St, U, F> Debug for FlatMap<St, U, F>where
        Flatten<Map<St, F>, U>: Debug,
    "],["impl<'a, St: Debug + ?Sized> Debug for Next<'a, St>"],["impl<'a, St: Debug + ?Sized> Debug for SelectNextSome<'a, St>"],["impl<St: Debug + Stream> Debug for Peekable<St>where
        St::Item: Debug,
    "],["impl<St> Debug for Peek<'_, St>where
        St: Stream + Debug,
        St::Item: Debug,
    "],["impl<St> Debug for PeekMut<'_, St>where
        St: Stream + Debug,
        St::Item: Debug,
    "],["impl<St, F> Debug for NextIf<'_, St, F>where
        St: Stream + Debug,
        St::Item: Debug,
    "],["impl<St, T> Debug for NextIfEq<'_, St, T>where
        St: Stream + Debug,
        St::Item: Debug,
        T: ?Sized,
    "],["impl<St: Debug> Debug for Skip<St>"],["impl<St, Fut, F> Debug for SkipWhile<St, Fut, F>where
        St: Stream + Debug,
        St::Item: Debug,
        Fut: Debug,
    "],["impl<St: Debug> Debug for Take<St>"],["impl<St, Fut, F> Debug for TakeWhile<St, Fut, F>where
        St: Stream + Debug,
        St::Item: Debug,
        Fut: Debug,
    "],["impl<St, Fut> Debug for TakeUntil<St, Fut>where
        St: Stream + Debug,
        St::Item: Debug,
        Fut: Future + Debug,
    "],["impl<St, Fut, F> Debug for Then<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St1: Debug + Stream, St2: Debug + Stream> Debug for Zip<St1, St2>where
        St1::Item: Debug,
        St2::Item: Debug,
    "],["impl<St: Debug + Stream> Debug for Chunks<St>where
        St::Item: Debug,
    "],["impl<St: Debug + Stream> Debug for ReadyChunks<St>where
        St::Item: Debug,
    "],["impl<St, S, Fut, F> Debug for Scan<St, S, Fut, F>where
        St: Stream + Debug,
        St::Item: Debug,
        S: Debug,
        Fut: Debug,
    "],["impl<St> Debug for BufferUnordered<St>where
        St: Stream + Debug,
    "],["impl<St> Debug for Buffered<St>where
        St: Stream + Debug,
        St::Item: Future,
    "],["impl<St, Fut, F> Debug for ForEachConcurrent<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<S: Debug> Debug for SplitStream<S>"],["impl<S: Debug, Item: Debug> Debug for SplitSink<S, Item>"],["impl<T, Item> Debug for ReuniteError<T, Item>"],["impl<St: Debug> Debug for CatchUnwind<St>"],["impl<St, Fut, F> Debug for AndThen<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St, E> Debug for ErrInto<St, E>where
        MapErr<St, IntoFn<E>>: Debug,
    "],["impl<St, F> Debug for InspectOk<St, F>where
        Inspect<IntoStream<St>, InspectOkFn<F>>: Debug,
    "],["impl<St, F> Debug for InspectErr<St, F>where
        Inspect<IntoStream<St>, InspectErrFn<F>>: Debug,
    "],["impl<St: Debug> Debug for IntoStream<St>"],["impl<St, F> Debug for MapOk<St, F>where
        Map<IntoStream<St>, MapOkFn<F>>: Debug,
    "],["impl<St, F> Debug for MapErr<St, F>where
        Map<IntoStream<St>, MapErrFn<F>>: Debug,
    "],["impl<St, Fut, F> Debug for OrElse<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<'a, St: Debug + ?Sized> Debug for TryNext<'a, St>"],["impl<St, Fut, F> Debug for TryForEach<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St, Fut, F> Debug for TryFilter<St, Fut, F>where
        St: TryStream + Debug,
        St::Ok: Debug,
        Fut: Debug,
    "],["impl<St, Fut, F> Debug for TryFilterMap<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St: Debug> Debug for TryFlatten<St>where
        St: TryStream,
        St::Ok: Debug,
    "],["impl<St: Debug, C: Debug> Debug for TryCollect<St, C>"],["impl<St: Debug + TryStream> Debug for TryConcat<St>where
        St::Ok: Debug,
    "],["impl<St: Debug + TryStream> Debug for TryChunks<St>where
        St::Ok: Debug,
    "],["impl<T, E: Debug> Debug for TryChunksError<T, E>"],["impl<St, Fut, T, F> Debug for TryFold<St, Fut, T, F>where
        St: Debug,
        Fut: Debug,
        T: Debug,
    "],["impl<T, F, Fut> Debug for TryUnfold<T, F, Fut>where
        T: Debug,
        Fut: Debug,
    "],["impl<St, Fut, F> Debug for TrySkipWhile<St, Fut, F>where
        St: TryStream + Debug,
        St::Ok: Debug,
        Fut: Debug,
    "],["impl<St, Fut, F> Debug for TryTakeWhile<St, Fut, F>where
        St: TryStream + Debug,
        St::Ok: Debug,
        Fut: Debug,
    "],["impl<St: Debug> Debug for TryBufferUnordered<St>where
        St: TryStream,
        St::Ok: Debug,
    "],["impl<St: Debug> Debug for TryBuffered<St>where
        St: TryStream,
        St::Ok: TryFuture,
        St::Ok: Debug,
    "],["impl<St, Fut, F> Debug for TryForEachConcurrent<St, Fut, F>where
        St: Debug,
        Fut: Debug,
    "],["impl<St: Debug> Debug for IntoAsyncRead<St>where
        St: TryStream<Error = Error>,
        St::Ok: AsRef<[u8]>,
        St::Ok: Debug,
    "],["impl<I: Debug> Debug for Iter<I>"],["impl<T: Debug> Debug for Repeat<T>"],["impl<F: Debug> Debug for RepeatWith<F>"],["impl<T: Debug> Debug for Empty<T>"],["impl<Fut: Debug> Debug for Once<Fut>"],["impl<T: Debug> Debug for Pending<T>"],["impl<F> Debug for PollFn<F>"],["impl<S: Debug> Debug for PollImmediate<S>"],["impl<St1: Debug, St2: Debug> Debug for Select<St1, St2>"],["impl Debug for PollNext"],["impl<St1, St2, Clos, State> Debug for SelectWithStrategy<St1, St2, Clos, State>where
        St1: Debug,
        St2: Debug,
        State: Debug,
    "],["impl<T, F, Fut> Debug for Unfold<T, F, Fut>where
        T: Debug,
        Fut: Debug,
    "],["impl<Fut: Future> Debug for FuturesOrdered<Fut>"],["impl<'a, Fut: Debug> Debug for IterPinMut<'a, Fut>"],["impl<'a, Fut: Debug + Unpin> Debug for IterMut<'a, Fut>"],["impl<'a, Fut: Debug> Debug for IterPinRef<'a, Fut>"],["impl<'a, Fut: Debug + Unpin> Debug for Iter<'a, Fut>"],["impl<Fut: Debug + Unpin> Debug for IntoIter<Fut>"],["impl<Fut> Debug for FuturesUnordered<Fut>"],["impl<St: Debug> Debug for SelectAll<St>"],["impl<'a, St: Debug + Unpin> Debug for Iter<'a, St>"],["impl<'a, St: Debug + Unpin> Debug for IterMut<'a, St>"],["impl<St: Debug + Unpin> Debug for IntoIter<St>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Close<'a, Si, Item>"],["impl<T: Debug> Debug for Drain<T>"],["impl<Si1: Debug, Si2: Debug> Debug for Fanout<Si1, Si2>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Feed<'a, Si, Item>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Flush<'a, Si, Item>"],["impl<Si: Debug + Sink<Item>, Item: Debug, E: Debug> Debug for SinkErrInto<Si, Item, E>where
        Si::Error: Debug,
    "],["impl<Si: Debug, F: Debug> Debug for SinkMapErr<Si, F>"],["impl<'a, Si: Debug + ?Sized, Item: Debug> Debug for Send<'a, Si, Item>"],["impl<Si, St> Debug for SendAll<'_, Si, St>where
        Si: Debug + ?Sized,
        St: Debug + ?Sized + TryStream,
        St::Ok: Debug,
    "],["impl<T: Debug, F: Debug, R: Debug> Debug for Unfold<T, F, R>"],["impl<Si, Item, U, Fut, F> Debug for With<Si, Item, U, Fut, F>where
        Si: Debug,
        Fut: Debug,
    "],["impl<Si, Item, U, St, F> Debug for WithFlatMap<Si, Item, U, St, F>where
        Si: Debug,
        St: Debug,
        Item: Debug,
    "],["impl<Si: Debug, Item: Debug> Debug for Buffer<Si, Item>"],["impl<T: Debug> Debug for AllowStdIo<T>"],["impl<R: Debug> Debug for BufReader<R>"],["impl<'a, R: Debug> Debug for SeeKRelative<'a, R>"],["impl<W: Debug> Debug for BufWriter<W>"],["impl<W: Debug + AsyncWrite> Debug for LineWriter<W>"],["impl<T, U> Debug for Chain<T, U>where
        T: Debug,
        U: Debug,
    "],["impl<'a, W: Debug + ?Sized> Debug for Close<'a, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for Copy<'a, R, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for CopyBuf<'a, R, W>"],["impl<'a, R: Debug, W: Debug + ?Sized> Debug for CopyBufAbortable<'a, R, W>"],["impl<T: Debug> Debug for Cursor<T>"],["impl Debug for Empty"],["impl<'a, R: Debug + ?Sized> Debug for FillBuf<'a, R>"],["impl<'a, W: Debug + ?Sized> Debug for Flush<'a, W>"],["impl<W: Debug, Item: Debug> Debug for IntoSink<W, Item>"],["impl<R: Debug> Debug for Lines<R>"],["impl<'a, R: Debug + ?Sized> Debug for Read<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadVectored<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadExact<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadLine<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadToEnd<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadToString<'a, R>"],["impl<'a, R: Debug + ?Sized> Debug for ReadUntil<'a, R>"],["impl Debug for Repeat"],["impl<'a, S: Debug + ?Sized> Debug for Seek<'a, S>"],["impl Debug for Sink"],["impl<T: Debug> Debug for ReadHalf<T>"],["impl<T: Debug> Debug for WriteHalf<T>"],["impl<T> Debug for ReuniteError<T>"],["impl<R: Debug> Debug for Take<R>"],["impl<T: Debug> Debug for Window<T>"],["impl<'a, W: Debug + ?Sized> Debug for Write<'a, W>"],["impl<'a, W: Debug + ?Sized> Debug for WriteVectored<'a, W>"],["impl<'a, W: Debug + ?Sized> Debug for WriteAll<'a, W>"],["impl<T: ?Sized> Debug for Mutex<T>"],["impl<T: ?Sized> Debug for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Debug> Debug for OwnedMutexGuard<T>"],["impl<T: ?Sized> Debug for MutexLockFuture<'_, T>"],["impl<T: ?Sized + Debug> Debug for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized + Debug> Debug for MappedMutexGuard<'_, T, U>"],["impl<T: Debug> Debug for Abortable<T>"],["impl Debug for AbortRegistration"],["impl Debug for AbortHandle"],["impl Debug for Aborted"]], -"generic_array":[["impl<T: Debug, N> Debug for GenericArray<T, N>where
        N: ArrayLength<T>,
    "],["impl<T: Debug, N> Debug for GenericArrayIter<T, N>where
        N: ArrayLength<T>,
    "]], -"getrandom":[["impl Debug for Error"]], -"growthring":[["impl Debug for WALRingId"]], -"hashbrown":[["impl<K, V, S, A> Debug for HashMap<K, V, S, A>where
        K: Debug,
        V: Debug,
        A: Allocator + Clone,
    "],["impl<K: Debug, V: Debug> Debug for Iter<'_, K, V>"],["impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for IntoKeys<K, V, A>"],["impl<K, V: Debug, A: Allocator + Clone> Debug for IntoValues<K, V, A>"],["impl<K: Debug, V> Debug for Keys<'_, K, V>"],["impl<K, V: Debug> Debug for Values<'_, K, V>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawEntryBuilderMut<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for RawEntryMut<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for RawOccupiedEntryMut<'_, K, V, S, A>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawVacantEntryMut<'_, K, V, S, A>"],["impl<K, V, S, A: Allocator + Clone> Debug for RawEntryBuilder<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for Entry<'_, K, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedEntry<'_, K, V, S, A>"],["impl<K: Debug, V, S, A: Allocator + Clone> Debug for VacantEntry<'_, K, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V: Debug, S, A: Allocator + Clone> Debug for EntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedEntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Borrow<Q>, Q: ?Sized + Debug, V, S, A: Allocator + Clone> Debug for VacantEntryRef<'_, '_, K, Q, V, S, A>"],["impl<K: Debug, V: Debug, S, A: Allocator + Clone> Debug for OccupiedError<'_, K, V, S, A>"],["impl<K, V> Debug for IterMut<'_, K, V>where
        K: Debug,
        V: Debug,
    "],["impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for IntoIter<K, V, A>"],["impl<K, V: Debug> Debug for ValuesMut<'_, K, V>"],["impl<K, V, A> Debug for Drain<'_, K, V, A>where
        K: Debug,
        V: Debug,
        A: Allocator + Clone,
    "],["impl<T, S, A> Debug for HashSet<T, S, A>where
        T: Debug,
        A: Allocator + Clone,
    "],["impl<K: Debug> Debug for Iter<'_, K>"],["impl<K: Debug, A: Allocator + Clone> Debug for IntoIter<K, A>"],["impl<K: Debug, A: Allocator + Clone> Debug for Drain<'_, K, A>"],["impl<T, S, A> Debug for Intersection<'_, T, S, A>where
        T: Debug + Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T, S, A> Debug for Difference<'_, T, S, A>where
        T: Debug + Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T, S, A> Debug for SymmetricDifference<'_, T, S, A>where
        T: Debug + Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T, S, A> Debug for Union<'_, T, S, A>where
        T: Debug + Eq + Hash,
        S: BuildHasher,
        A: Allocator + Clone,
    "],["impl<T: Debug, S, A: Allocator + Clone> Debug for Entry<'_, T, S, A>"],["impl<T: Debug, S, A: Allocator + Clone> Debug for OccupiedEntry<'_, T, S, A>"],["impl<T: Debug, S, A: Allocator + Clone> Debug for VacantEntry<'_, T, S, A>"],["impl Debug for TryReserveError"]], -"hex":[["impl Debug for FromHexError"]], -"libc":[["impl Debug for DIR"],["impl Debug for group"],["impl Debug for utimbuf"],["impl Debug for timeval"],["impl Debug for timespec"],["impl Debug for rlimit"],["impl Debug for rusage"],["impl Debug for ipv6_mreq"],["impl Debug for hostent"],["impl Debug for iovec"],["impl Debug for pollfd"],["impl Debug for winsize"],["impl Debug for linger"],["impl Debug for sigval"],["impl Debug for itimerval"],["impl Debug for tms"],["impl Debug for servent"],["impl Debug for protoent"],["impl Debug for FILE"],["impl Debug for fpos_t"],["impl Debug for timezone"],["impl Debug for in_addr"],["impl Debug for ip_mreq"],["impl Debug for ip_mreqn"],["impl Debug for ip_mreq_source"],["impl Debug for sockaddr"],["impl Debug for sockaddr_in"],["impl Debug for sockaddr_in6"],["impl Debug for addrinfo"],["impl Debug for sockaddr_ll"],["impl Debug for fd_set"],["impl Debug for tm"],["impl Debug for sched_param"],["impl Debug for Dl_info"],["impl Debug for lconv"],["impl Debug for in_pktinfo"],["impl Debug for ifaddrs"],["impl Debug for in6_rtmsg"],["impl Debug for arpreq"],["impl Debug for arpreq_old"],["impl Debug for arphdr"],["impl Debug for mmsghdr"],["impl Debug for epoll_event"],["impl Debug for sockaddr_un"],["impl Debug for sockaddr_storage"],["impl Debug for utsname"],["impl Debug for sigevent"],["impl Debug for fpos64_t"],["impl Debug for rlimit64"],["impl Debug for glob_t"],["impl Debug for passwd"],["impl Debug for spwd"],["impl Debug for dqblk"],["impl Debug for signalfd_siginfo"],["impl Debug for itimerspec"],["impl Debug for fsid_t"],["impl Debug for packet_mreq"],["impl Debug for cpu_set_t"],["impl Debug for if_nameindex"],["impl Debug for msginfo"],["impl Debug for sembuf"],["impl Debug for input_event"],["impl Debug for input_id"],["impl Debug for input_absinfo"],["impl Debug for input_keymap_entry"],["impl Debug for input_mask"],["impl Debug for ff_replay"],["impl Debug for ff_trigger"],["impl Debug for ff_envelope"],["impl Debug for ff_constant_effect"],["impl Debug for ff_ramp_effect"],["impl Debug for ff_condition_effect"],["impl Debug for ff_periodic_effect"],["impl Debug for ff_rumble_effect"],["impl Debug for ff_effect"],["impl Debug for uinput_ff_upload"],["impl Debug for uinput_ff_erase"],["impl Debug for uinput_abs_setup"],["impl Debug for dl_phdr_info"],["impl Debug for Elf32_Ehdr"],["impl Debug for Elf64_Ehdr"],["impl Debug for Elf32_Sym"],["impl Debug for Elf64_Sym"],["impl Debug for Elf32_Phdr"],["impl Debug for Elf64_Phdr"],["impl Debug for Elf32_Shdr"],["impl Debug for Elf64_Shdr"],["impl Debug for ucred"],["impl Debug for mntent"],["impl Debug for posix_spawn_file_actions_t"],["impl Debug for posix_spawnattr_t"],["impl Debug for genlmsghdr"],["impl Debug for in6_pktinfo"],["impl Debug for arpd_request"],["impl Debug for inotify_event"],["impl Debug for fanotify_response"],["impl Debug for sockaddr_vm"],["impl Debug for regmatch_t"],["impl Debug for sock_extended_err"],["impl Debug for __c_anonymous_sockaddr_can_tp"],["impl Debug for __c_anonymous_sockaddr_can_j1939"],["impl Debug for can_filter"],["impl Debug for j1939_filter"],["impl Debug for sock_filter"],["impl Debug for sock_fprog"],["impl Debug for seccomp_data"],["impl Debug for nlmsghdr"],["impl Debug for nlmsgerr"],["impl Debug for nlattr"],["impl Debug for file_clone_range"],["impl Debug for __c_anonymous_ifru_map"],["impl Debug for in6_ifreq"],["impl Debug for sockaddr_nl"],["impl Debug for dirent"],["impl Debug for dirent64"],["impl Debug for pthread_cond_t"],["impl Debug for pthread_mutex_t"],["impl Debug for pthread_rwlock_t"],["impl Debug for sockaddr_alg"],["impl Debug for uinput_setup"],["impl Debug for uinput_user_dev"],["impl Debug for af_alg_iv"],["impl Debug for mq_attr"],["impl Debug for __c_anonymous_ifr_ifru"],["impl Debug for ifreq"],["impl Debug for statx"],["impl Debug for statx_timestamp"],["impl Debug for aiocb"],["impl Debug for __exit_status"],["impl Debug for __timeval"],["impl Debug for glob64_t"],["impl Debug for msghdr"],["impl Debug for cmsghdr"],["impl Debug for termios"],["impl Debug for mallinfo"],["impl Debug for mallinfo2"],["impl Debug for nl_pktinfo"],["impl Debug for nl_mmap_req"],["impl Debug for nl_mmap_hdr"],["impl Debug for rtentry"],["impl Debug for timex"],["impl Debug for ntptimeval"],["impl Debug for regex_t"],["impl Debug for Elf64_Chdr"],["impl Debug for Elf32_Chdr"],["impl Debug for seminfo"],["impl Debug for ptrace_peeksiginfo_args"],["impl Debug for __c_anonymous_ptrace_syscall_info_entry"],["impl Debug for __c_anonymous_ptrace_syscall_info_exit"],["impl Debug for __c_anonymous_ptrace_syscall_info_seccomp"],["impl Debug for ptrace_syscall_info"],["impl Debug for utmpx"],["impl Debug for __c_anonymous_ptrace_syscall_info_data"],["impl Debug for sigset_t"],["impl Debug for sysinfo"],["impl Debug for msqid_ds"],["impl Debug for semid_ds"],["impl Debug for sigaction"],["impl Debug for statfs"],["impl Debug for flock"],["impl Debug for flock64"],["impl Debug for siginfo_t"],["impl Debug for stack_t"],["impl Debug for stat"],["impl Debug for stat64"],["impl Debug for statfs64"],["impl Debug for statvfs64"],["impl Debug for pthread_attr_t"],["impl Debug for _libc_fpxreg"],["impl Debug for _libc_xmmreg"],["impl Debug for _libc_fpstate"],["impl Debug for user_regs_struct"],["impl Debug for user"],["impl Debug for mcontext_t"],["impl Debug for ipc_perm"],["impl Debug for shmid_ds"],["impl Debug for seccomp_notif_sizes"],["impl Debug for ptrace_rseq_configuration"],["impl Debug for user_fpregs_struct"],["impl Debug for ucontext_t"],["impl Debug for statvfs"],["impl Debug for clone_args"],["impl Debug for sem_t"],["impl Debug for termios2"],["impl Debug for pthread_mutexattr_t"],["impl Debug for pthread_rwlockattr_t"],["impl Debug for pthread_condattr_t"],["impl Debug for fanotify_event_metadata"],["impl Debug for open_how"],["impl Debug for in6_addr"]], -"lock_api":[["impl<R: RawMutex, T: ?Sized + Debug> Debug for Mutex<R, T>"],["impl<'a, R: RawMutex + 'a, T: Debug + ?Sized + 'a> Debug for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: Debug + ?Sized + 'a> Debug for MappedMutexGuard<'a, R, T>"],["impl<R: RawMutex, G: GetThreadId, T: ?Sized + Debug> Debug for ReentrantMutex<R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: Debug + ?Sized + 'a> Debug for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: Debug + ?Sized + 'a> Debug for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<R: RawRwLock, T: ?Sized + Debug> Debug for RwLock<R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: Debug + ?Sized + 'a> Debug for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: Debug + ?Sized + 'a> Debug for MappedRwLockWriteGuard<'a, R, T>"]], -"lru":[["impl<K: Hash + Eq, V> Debug for LruCache<K, V>"]], -"memchr":[["impl Debug for Prefilter"],["impl<'h, 'n> Debug for FindIter<'h, 'n>"],["impl<'h, 'n> Debug for FindRevIter<'h, 'n>"],["impl<'n> Debug for Finder<'n>"],["impl<'n> Debug for FinderRev<'n>"],["impl Debug for FinderBuilder"]], -"nix":[["impl Debug for Dir"],["impl<'d> Debug for Iter<'d>"],["impl Debug for OwningIter"],["impl Debug for Entry"],["impl Debug for Type"],["impl Debug for ClearEnvError"],["impl Debug for Errno"],["impl Debug for AtFlags"],["impl Debug for OFlag"],["impl Debug for RenameFlags"],["impl Debug for SealFlag"],["impl Debug for FdFlag"],["impl<'a> Debug for FcntlArg<'a>"],["impl Debug for FlockArg"],["impl Debug for SpliceFFlags"],["impl Debug for FallocateFlags"],["impl Debug for PosixFadviseAdvice"],["impl Debug for InterfaceAddress"],["impl Debug for InterfaceAddressIterator"],["impl Debug for InterfaceFlags"],["impl Debug for Interface"],["impl Debug for Interfaces"],["impl<'a> Debug for InterfacesIter<'a>"],["impl Debug for ModuleInitFlags"],["impl Debug for DeleteModuleFlags"],["impl Debug for MsFlags"],["impl Debug for MntFlags"],["impl Debug for MQ_OFlag"],["impl Debug for MqAttr"],["impl Debug for MqdT"],["impl Debug for PollFd"],["impl Debug for PollFlags"],["impl Debug for OpenptyResult"],["impl Debug for ForkptyResult"],["impl Debug for PtyMaster"],["impl Debug for CloneFlags"],["impl Debug for CpuSet"],["impl Debug for AioFsyncMode"],["impl Debug for LioMode"],["impl Debug for AioCancelStat"],["impl Debug for AioFsync"],["impl<'a> Debug for AioRead<'a>"],["impl<'a> Debug for AioWrite<'a>"],["impl Debug for EpollFlags"],["impl Debug for EpollOp"],["impl Debug for EpollCreateFlags"],["impl Debug for EpollEvent"],["impl Debug for EfdFlags"],["impl Debug for MemFdCreateFlag"],["impl Debug for ProtFlags"],["impl Debug for MapFlags"],["impl Debug for MRemapFlags"],["impl Debug for MmapAdvise"],["impl Debug for MsFlags"],["impl Debug for MlockAllFlags"],["impl Debug for Persona"],["impl Debug for Request"],["impl Debug for Event"],["impl Debug for Options"],["impl Debug for QuotaType"],["impl Debug for QuotaFmt"],["impl Debug for QuotaValidFlags"],["impl Debug for Dqblk"],["impl Debug for RebootMode"],["impl Debug for Resource"],["impl Debug for UsageWho"],["impl Debug for Usage"],["impl Debug for FdSet"],["impl<'a> Debug for Fds<'a>"],["impl Debug for Signal"],["impl Debug for SignalIterator"],["impl Debug for SaFlags"],["impl Debug for SigmaskHow"],["impl Debug for SigSet"],["impl<'a> Debug for SigSetIter<'a>"],["impl Debug for SigHandler"],["impl Debug for SigAction"],["impl Debug for SigevNotify"],["impl Debug for SigEvent"],["impl Debug for SfdFlags"],["impl Debug for SignalFd"],["impl Debug for AddressFamily"],["impl Debug for InetAddr"],["impl Debug for IpAddr"],["impl Debug for Ipv4Addr"],["impl Debug for Ipv6Addr"],["impl Debug for UnixAddr"],["impl Debug for SockaddrIn"],["impl Debug for SockaddrIn6"],["impl Debug for SockaddrStorage"],["impl Debug for SockAddr"],["impl Debug for NetlinkAddr"],["impl Debug for AlgAddr"],["impl Debug for LinkAddr"],["impl Debug for VsockAddr"],["impl Debug for ReuseAddr"],["impl Debug for ReusePort"],["impl Debug for TcpNoDelay"],["impl Debug for Linger"],["impl Debug for IpAddMembership"],["impl Debug for IpDropMembership"],["impl Debug for Ipv6AddMembership"],["impl Debug for Ipv6DropMembership"],["impl Debug for IpMulticastTtl"],["impl Debug for IpMulticastLoop"],["impl Debug for Priority"],["impl Debug for IpTos"],["impl Debug for Ipv6TClass"],["impl Debug for IpFreebind"],["impl Debug for ReceiveTimeout"],["impl Debug for SendTimeout"],["impl Debug for Broadcast"],["impl Debug for OobInline"],["impl Debug for SocketError"],["impl Debug for DontRoute"],["impl Debug for KeepAlive"],["impl Debug for PeerCredentials"],["impl Debug for TcpKeepIdle"],["impl Debug for TcpMaxSeg"],["impl Debug for TcpKeepCount"],["impl Debug for TcpRepair"],["impl Debug for TcpKeepInterval"],["impl Debug for TcpUserTimeout"],["impl Debug for RcvBuf"],["impl Debug for SndBuf"],["impl Debug for RcvBufForce"],["impl Debug for SndBufForce"],["impl Debug for SockType"],["impl Debug for AcceptConn"],["impl Debug for BindToDevice"],["impl Debug for OriginalDst"],["impl Debug for Ip6tOriginalDst"],["impl Debug for Timestamping"],["impl Debug for ReceiveTimestamp"],["impl Debug for ReceiveTimestampns"],["impl Debug for IpTransparent"],["impl Debug for Mark"],["impl Debug for PassCred"],["impl Debug for TcpCongestion"],["impl Debug for Ipv4PacketInfo"],["impl Debug for Ipv6RecvPacketInfo"],["impl Debug for Ipv4OrigDstAddr"],["impl Debug for UdpGsoSegment"],["impl Debug for UdpGroSegment"],["impl Debug for TxTime"],["impl Debug for RxqOvfl"],["impl Debug for Ipv6V6Only"],["impl Debug for Ipv4RecvErr"],["impl Debug for Ipv6RecvErr"],["impl Debug for IpMtu"],["impl Debug for Ipv4Ttl"],["impl Debug for Ipv6Ttl"],["impl Debug for Ipv6OrigDstAddr"],["impl Debug for Ipv6DontFrag"],["impl Debug for AlgSetAeadAuthSize"],["impl<T: Debug> Debug for AlgSetKey<T>"],["impl Debug for SockType"],["impl Debug for SockProtocol"],["impl Debug for TimestampingFlag"],["impl Debug for SockFlag"],["impl Debug for MsgFlags"],["impl Debug for UnixCredentials"],["impl Debug for IpMembershipRequest"],["impl Debug for Ipv6MembershipRequest"],["impl<'a, 's, S: Debug> Debug for RecvMsg<'a, 's, S>"],["impl<'a> Debug for CmsgIterator<'a>"],["impl Debug for ControlMessageOwned"],["impl Debug for Timestamps"],["impl<'a> Debug for ControlMessage<'a>"],["impl<S: Debug> Debug for MultiHeaders<S>"],["impl<'a, S: Debug> Debug for MultiResults<'a, S>"],["impl<'a> Debug for IoSliceIterator<'a>"],["impl Debug for Shutdown"],["impl Debug for SFlag"],["impl Debug for Mode"],["impl Debug for FchmodatFlags"],["impl Debug for UtimensatFlags"],["impl Debug for FsType"],["impl Debug for Statfs"],["impl Debug for FsFlags"],["impl Debug for Statvfs"],["impl Debug for SysInfo"],["impl Debug for Termios"],["impl Debug for BaudRate"],["impl Debug for SetArg"],["impl Debug for FlushArg"],["impl Debug for FlowArg"],["impl Debug for SpecialCharacterIndices"],["impl Debug for InputFlags"],["impl Debug for OutputFlags"],["impl Debug for ControlFlags"],["impl Debug for LocalFlags"],["impl Debug for Expiration"],["impl Debug for TimerSetTimeFlags"],["impl Debug for TimeSpec"],["impl Debug for TimeVal"],["impl Debug for RemoteIoVec"],["impl<T: Debug> Debug for IoVec<T>"],["impl Debug for UtsName"],["impl Debug for WaitPidFlag"],["impl Debug for WaitStatus"],["impl Debug for Id"],["impl Debug for AddWatchFlags"],["impl Debug for InitFlags"],["impl Debug for Inotify"],["impl Debug for WatchDescriptor"],["impl Debug for InotifyEvent"],["impl Debug for TimerFd"],["impl Debug for ClockId"],["impl Debug for TimerFlags"],["impl Debug for Timer"],["impl Debug for ClockId"],["impl Debug for UContext"],["impl Debug for Uid"],["impl Debug for Gid"],["impl Debug for Pid"],["impl Debug for ForkResult"],["impl Debug for FchownatFlags"],["impl Debug for Whence"],["impl Debug for LinkatFlags"],["impl Debug for UnlinkatFlags"],["impl Debug for PathconfVar"],["impl Debug for SysconfVar"],["impl Debug for ResUid"],["impl Debug for ResGid"],["impl Debug for AccessFlags"],["impl Debug for User"],["impl Debug for Group"]], -"once_cell":[["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug, F> Debug for Lazy<T, F>"],["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug, F> Debug for Lazy<T, F>"],["impl Debug for OnceNonZeroUsize"],["impl Debug for OnceBool"],["impl<T> Debug for OnceBox<T>"]], -"parking_lot":[["impl Debug for WaitTimeoutResult"],["impl Debug for Condvar"],["impl Debug for OnceState"],["impl Debug for Once"]], -"parking_lot_core":[["impl Debug for ParkResult"],["impl Debug for UnparkResult"],["impl Debug for RequeueOp"],["impl Debug for FilterOp"],["impl Debug for UnparkToken"],["impl Debug for ParkToken"]], -"primitive_types":[["impl Debug for Error"],["impl Debug for U128"],["impl Debug for U256"],["impl Debug for U512"],["impl Debug for H128"],["impl Debug for H160"],["impl Debug for H256"],["impl Debug for H384"],["impl Debug for H512"],["impl Debug for H768"]], -"proc_macro2":[["impl Debug for TokenStream"],["impl Debug for LexError"],["impl Debug for Span"],["impl Debug for TokenTree"],["impl Debug for Delimiter"],["impl Debug for Group"],["impl Debug for Spacing"],["impl Debug for Punct"],["impl Debug for Ident"],["impl Debug for Literal"],["impl Debug for IntoIter"]], -"rand":[["impl Debug for Bernoulli"],["impl Debug for BernoulliError"],["impl<D: Debug, R: Debug, T: Debug> Debug for DistIter<D, R, T>"],["impl<D: Debug, F: Debug, T: Debug, S: Debug> Debug for DistMap<D, F, T, S>"],["impl Debug for OpenClosed01"],["impl Debug for Open01"],["impl Debug for Alphanumeric"],["impl<'a, T: Debug> Debug for Slice<'a, T>"],["impl<X: Debug + SampleUniform + PartialOrd> Debug for WeightedIndex<X>where
        X::Sampler: Debug,
    "],["impl Debug for WeightedError"],["impl<X: Debug + SampleUniform> Debug for Uniform<X>where
        X::Sampler: Debug,
    "],["impl<X: Debug> Debug for UniformInt<X>"],["impl Debug for UniformChar"],["impl<X: Debug> Debug for UniformFloat<X>"],["impl Debug for UniformDuration"],["impl<W: Debug + Weight> Debug for WeightedIndex<W>"],["impl Debug for Standard"],["impl<R: Debug> Debug for ReadRng<R>"],["impl Debug for ReadError"],["impl<R: Debug, Rsdr: Debug> Debug for ReseedingRng<R, Rsdr>where
        R: BlockRngCore + SeedableRng,
        Rsdr: RngCore,
    "],["impl Debug for StepRng"],["impl Debug for IndexVec"],["impl<'a> Debug for IndexVecIter<'a>"],["impl Debug for IndexVecIntoIter"],["impl<'a, S: Debug + ?Sized + 'a, T: Debug + 'a> Debug for SliceChooseIter<'a, S, T>"]], -"rand_chacha":[["impl Debug for ChaCha20Core"],["impl Debug for ChaCha20Rng"],["impl Debug for ChaCha12Core"],["impl Debug for ChaCha12Rng"],["impl Debug for ChaCha8Core"],["impl Debug for ChaCha8Rng"]], -"rand_core":[["impl<R: BlockRngCore + Debug> Debug for BlockRng<R>"],["impl<R: BlockRngCore + Debug> Debug for BlockRng64<R>"],["impl Debug for Error"],["impl Debug for OsRng"]], -"regex":[["impl Debug for Error"],["impl Debug for RegexBuilder"],["impl Debug for RegexBuilder"],["impl Debug for RegexSetBuilder"],["impl Debug for RegexSetBuilder"],["impl<'t> Debug for Match<'t>"],["impl Debug for Regex"],["impl<'r, 't> Debug for Matches<'r, 't>"],["impl<'r, 't> Debug for CaptureMatches<'r, 't>"],["impl<'r, 't> Debug for Split<'r, 't>"],["impl<'r, 't> Debug for SplitN<'r, 't>"],["impl<'r> Debug for CaptureNames<'r>"],["impl Debug for CaptureLocations"],["impl<'t> Debug for Captures<'t>"],["impl<'c, 't> Debug for SubCaptureMatches<'c, 't>"],["impl<'a, R: Debug + ?Sized> Debug for ReplacerRef<'a, R>"],["impl<'t> Debug for NoExpand<'t>"],["impl Debug for SetMatches"],["impl Debug for SetMatchesIntoIter"],["impl<'a> Debug for SetMatchesIter<'a>"],["impl Debug for RegexSet"],["impl Debug for SetMatches"],["impl Debug for SetMatchesIntoIter"],["impl<'a> Debug for SetMatchesIter<'a>"],["impl Debug for RegexSet"],["impl<'t> Debug for Match<'t>"],["impl Debug for Regex"],["impl<'r> Debug for CaptureNames<'r>"],["impl<'r, 't> Debug for Split<'r, 't>"],["impl<'r, 't> Debug for SplitN<'r, 't>"],["impl Debug for CaptureLocations"],["impl<'t> Debug for Captures<'t>"],["impl<'c, 't> Debug for SubCaptureMatches<'c, 't>"],["impl<'r, 't> Debug for CaptureMatches<'r, 't>"],["impl<'r, 't> Debug for Matches<'r, 't>"],["impl<'a, R: Debug + ?Sized> Debug for ReplacerRef<'a, R>"],["impl<'t> Debug for NoExpand<'t>"]], -"regex_syntax":[["impl Debug for ParserBuilder"],["impl Debug for Parser"],["impl Debug for Printer"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for Span"],["impl Debug for Position"],["impl Debug for WithComments"],["impl Debug for Comment"],["impl Debug for Ast"],["impl Debug for Alternation"],["impl Debug for Concat"],["impl Debug for Literal"],["impl Debug for LiteralKind"],["impl Debug for SpecialLiteralKind"],["impl Debug for HexLiteralKind"],["impl Debug for Class"],["impl Debug for ClassPerl"],["impl Debug for ClassPerlKind"],["impl Debug for ClassAscii"],["impl Debug for ClassAsciiKind"],["impl Debug for ClassUnicode"],["impl Debug for ClassUnicodeKind"],["impl Debug for ClassUnicodeOpKind"],["impl Debug for ClassBracketed"],["impl Debug for ClassSet"],["impl Debug for ClassSetItem"],["impl Debug for ClassSetRange"],["impl Debug for ClassSetUnion"],["impl Debug for ClassSetBinaryOp"],["impl Debug for ClassSetBinaryOpKind"],["impl Debug for Assertion"],["impl Debug for AssertionKind"],["impl Debug for Repetition"],["impl Debug for RepetitionOp"],["impl Debug for RepetitionKind"],["impl Debug for RepetitionRange"],["impl Debug for Group"],["impl Debug for GroupKind"],["impl Debug for CaptureName"],["impl Debug for SetFlags"],["impl Debug for Flags"],["impl Debug for FlagsItem"],["impl Debug for FlagsItemKind"],["impl Debug for Flag"],["impl Debug for Error"],["impl Debug for Literals"],["impl Debug for Literal"],["impl Debug for Printer"],["impl Debug for TranslatorBuilder"],["impl Debug for Translator"],["impl Debug for Error"],["impl Debug for ErrorKind"],["impl Debug for Hir"],["impl Debug for HirKind"],["impl Debug for Literal"],["impl Debug for Class"],["impl Debug for ClassUnicode"],["impl<'a> Debug for ClassUnicodeIter<'a>"],["impl Debug for ClassUnicodeRange"],["impl Debug for ClassBytes"],["impl<'a> Debug for ClassBytesIter<'a>"],["impl Debug for ClassBytesRange"],["impl Debug for Anchor"],["impl Debug for WordBoundary"],["impl Debug for Group"],["impl Debug for GroupKind"],["impl Debug for Repetition"],["impl Debug for RepetitionKind"],["impl Debug for RepetitionRange"],["impl Debug for ParserBuilder"],["impl Debug for Parser"],["impl Debug for CaseFoldError"],["impl Debug for UnicodeWordError"],["impl Debug for Utf8Sequence"],["impl Debug for Utf8Range"],["impl Debug for Utf8Sequences"]], -"rlp":[["impl Debug for DecoderError"],["impl Debug for Prototype"],["impl Debug for PayloadInfo"],["impl<'a> Debug for Rlp<'a>"]], -"rustc_hex":[["impl Debug for FromHexError"]], -"scan_fmt":[["impl Debug for ScanError"]], -"scopeguard":[["impl Debug for Always"],["impl<T, F, S> Debug for ScopeGuard<T, F, S>where
        T: Debug,
        F: FnOnce(T),
        S: Strategy,
    "]], -"serde":[["impl Debug for Error"],["impl<E> Debug for UnitDeserializer<E>"],["impl<E> Debug for BoolDeserializer<E>"],["impl<E> Debug for I8Deserializer<E>"],["impl<E> Debug for I16Deserializer<E>"],["impl<E> Debug for I32Deserializer<E>"],["impl<E> Debug for I64Deserializer<E>"],["impl<E> Debug for IsizeDeserializer<E>"],["impl<E> Debug for U8Deserializer<E>"],["impl<E> Debug for U16Deserializer<E>"],["impl<E> Debug for U64Deserializer<E>"],["impl<E> Debug for UsizeDeserializer<E>"],["impl<E> Debug for F32Deserializer<E>"],["impl<E> Debug for F64Deserializer<E>"],["impl<E> Debug for CharDeserializer<E>"],["impl<E> Debug for I128Deserializer<E>"],["impl<E> Debug for U128Deserializer<E>"],["impl<E> Debug for U32Deserializer<E>"],["impl<'a, E> Debug for StrDeserializer<'a, E>"],["impl<'de, E> Debug for BorrowedStrDeserializer<'de, E>"],["impl<E> Debug for StringDeserializer<E>"],["impl<'a, E> Debug for CowStrDeserializer<'a, E>"],["impl<'a, E> Debug for BytesDeserializer<'a, E>"],["impl<'de, E> Debug for BorrowedBytesDeserializer<'de, E>"],["impl<I, E> Debug for SeqDeserializer<I, E>where
        I: Debug,
    "],["impl<A: Debug> Debug for SeqAccessDeserializer<A>"],["impl<'de, I, E> Debug for MapDeserializer<'de, I, E>where
        I: Iterator + Debug,
        I::Item: Pair,
        <I::Item as Pair>::Second: Debug,
    "],["impl<A: Debug> Debug for MapAccessDeserializer<A>"],["impl<A: Debug> Debug for EnumAccessDeserializer<A>"],["impl Debug for IgnoredAny"],["impl<'a> Debug for Unexpected<'a>"]], -"sha3":[["impl Debug for Keccak224Core"],["impl Debug for Keccak256Core"],["impl Debug for Keccak384Core"],["impl Debug for Keccak512Core"],["impl Debug for Keccak256FullCore"],["impl Debug for Sha3_224Core"],["impl Debug for Sha3_256Core"],["impl Debug for Sha3_384Core"],["impl Debug for Sha3_512Core"],["impl Debug for Shake128Core"],["impl Debug for Shake256Core"],["impl Debug for CShake128Core"],["impl Debug for CShake256Core"]], -"shale":[["impl Debug for ShaleError"],["impl Debug for DiskWrite"]], -"slab":[["impl<'a, T: Debug> Debug for VacantEntry<'a, T>"],["impl<T> Debug for Slab<T>where
        T: Debug,
    "],["impl<T> Debug for IntoIter<T>where
        T: Debug,
    "],["impl<T> Debug for Iter<'_, T>where
        T: Debug,
    "],["impl<T> Debug for IterMut<'_, T>where
        T: Debug,
    "],["impl<T> Debug for Drain<'_, T>"]], -"smallvec":[["impl Debug for CollectionAllocErr"],["impl<'a, T: 'a + Array> Debug for Drain<'a, T>where
        T::Item: Debug,
    "],["impl<A: Array> Debug for SmallVec<A>where
        A::Item: Debug,
    "],["impl<A: Array> Debug for IntoIter<A>where
        A::Item: Debug,
    "]], -"syn":[["impl Debug for Underscore"],["impl Debug for Abstract"],["impl Debug for As"],["impl Debug for Async"],["impl Debug for Auto"],["impl Debug for Await"],["impl Debug for Become"],["impl Debug for Box"],["impl Debug for Break"],["impl Debug for Const"],["impl Debug for Continue"],["impl Debug for Crate"],["impl Debug for Default"],["impl Debug for Do"],["impl Debug for Dyn"],["impl Debug for Else"],["impl Debug for Enum"],["impl Debug for Extern"],["impl Debug for Final"],["impl Debug for Fn"],["impl Debug for For"],["impl Debug for If"],["impl Debug for Impl"],["impl Debug for In"],["impl Debug for Let"],["impl Debug for Loop"],["impl Debug for Macro"],["impl Debug for Match"],["impl Debug for Mod"],["impl Debug for Move"],["impl Debug for Mut"],["impl Debug for Override"],["impl Debug for Priv"],["impl Debug for Pub"],["impl Debug for Ref"],["impl Debug for Return"],["impl Debug for SelfType"],["impl Debug for SelfValue"],["impl Debug for Static"],["impl Debug for Struct"],["impl Debug for Super"],["impl Debug for Trait"],["impl Debug for Try"],["impl Debug for Type"],["impl Debug for Typeof"],["impl Debug for Union"],["impl Debug for Unsafe"],["impl Debug for Unsized"],["impl Debug for Use"],["impl Debug for Virtual"],["impl Debug for Where"],["impl Debug for While"],["impl Debug for Yield"],["impl Debug for Add"],["impl Debug for AddEq"],["impl Debug for And"],["impl Debug for AndAnd"],["impl Debug for AndEq"],["impl Debug for At"],["impl Debug for Bang"],["impl Debug for Caret"],["impl Debug for CaretEq"],["impl Debug for Colon"],["impl Debug for Colon2"],["impl Debug for Comma"],["impl Debug for Div"],["impl Debug for DivEq"],["impl Debug for Dollar"],["impl Debug for Dot"],["impl Debug for Dot2"],["impl Debug for Dot3"],["impl Debug for DotDotEq"],["impl Debug for Eq"],["impl Debug for EqEq"],["impl Debug for Ge"],["impl Debug for Gt"],["impl Debug for Le"],["impl Debug for Lt"],["impl Debug for MulEq"],["impl Debug for Ne"],["impl Debug for Or"],["impl Debug for OrEq"],["impl Debug for OrOr"],["impl Debug for Pound"],["impl Debug for Question"],["impl Debug for RArrow"],["impl Debug for LArrow"],["impl Debug for Rem"],["impl Debug for RemEq"],["impl Debug for FatArrow"],["impl Debug for Semi"],["impl Debug for Shl"],["impl Debug for ShlEq"],["impl Debug for Shr"],["impl Debug for ShrEq"],["impl Debug for Star"],["impl Debug for Sub"],["impl Debug for SubEq"],["impl Debug for Tilde"],["impl Debug for Brace"],["impl Debug for Bracket"],["impl Debug for Paren"],["impl Debug for Group"],["impl<'a> Debug for ImplGenerics<'a>"],["impl<'a> Debug for TypeGenerics<'a>"],["impl<'a> Debug for Turbofish<'a>"],["impl Debug for LitStr"],["impl Debug for LitByteStr"],["impl Debug for LitByte"],["impl Debug for LitChar"],["impl Debug for LitInt"],["impl Debug for LitFloat"],["impl Debug for LitBool"],["impl<T: Debug, P: Debug> Debug for Punctuated<T, P>"],["impl Debug for Abi"],["impl Debug for AngleBracketedGenericArguments"],["impl Debug for Arm"],["impl Debug for AttrStyle"],["impl Debug for Attribute"],["impl Debug for BareFnArg"],["impl Debug for BinOp"],["impl Debug for Binding"],["impl Debug for Block"],["impl Debug for BoundLifetimes"],["impl Debug for ConstParam"],["impl Debug for Constraint"],["impl Debug for Data"],["impl Debug for DataEnum"],["impl Debug for DataStruct"],["impl Debug for DataUnion"],["impl Debug for DeriveInput"],["impl Debug for Expr"],["impl Debug for ExprArray"],["impl Debug for ExprAssign"],["impl Debug for ExprAssignOp"],["impl Debug for ExprAsync"],["impl Debug for ExprAwait"],["impl Debug for ExprBinary"],["impl Debug for ExprBlock"],["impl Debug for ExprBox"],["impl Debug for ExprBreak"],["impl Debug for ExprCall"],["impl Debug for ExprCast"],["impl Debug for ExprClosure"],["impl Debug for ExprContinue"],["impl Debug for ExprField"],["impl Debug for ExprForLoop"],["impl Debug for ExprGroup"],["impl Debug for ExprIf"],["impl Debug for ExprIndex"],["impl Debug for ExprLet"],["impl Debug for ExprLit"],["impl Debug for ExprLoop"],["impl Debug for ExprMacro"],["impl Debug for ExprMatch"],["impl Debug for ExprMethodCall"],["impl Debug for ExprParen"],["impl Debug for ExprPath"],["impl Debug for ExprRange"],["impl Debug for ExprReference"],["impl Debug for ExprRepeat"],["impl Debug for ExprReturn"],["impl Debug for ExprStruct"],["impl Debug for ExprTry"],["impl Debug for ExprTryBlock"],["impl Debug for ExprTuple"],["impl Debug for ExprType"],["impl Debug for ExprUnary"],["impl Debug for ExprUnsafe"],["impl Debug for ExprWhile"],["impl Debug for ExprYield"],["impl Debug for Field"],["impl Debug for FieldPat"],["impl Debug for FieldValue"],["impl Debug for Fields"],["impl Debug for FieldsNamed"],["impl Debug for FieldsUnnamed"],["impl Debug for File"],["impl Debug for FnArg"],["impl Debug for ForeignItem"],["impl Debug for ForeignItemFn"],["impl Debug for ForeignItemMacro"],["impl Debug for ForeignItemStatic"],["impl Debug for ForeignItemType"],["impl Debug for GenericArgument"],["impl Debug for GenericMethodArgument"],["impl Debug for GenericParam"],["impl Debug for Generics"],["impl Debug for ImplItem"],["impl Debug for ImplItemConst"],["impl Debug for ImplItemMacro"],["impl Debug for ImplItemMethod"],["impl Debug for ImplItemType"],["impl Debug for Index"],["impl Debug for Item"],["impl Debug for ItemConst"],["impl Debug for ItemEnum"],["impl Debug for ItemExternCrate"],["impl Debug for ItemFn"],["impl Debug for ItemForeignMod"],["impl Debug for ItemImpl"],["impl Debug for ItemMacro"],["impl Debug for ItemMacro2"],["impl Debug for ItemMod"],["impl Debug for ItemStatic"],["impl Debug for ItemStruct"],["impl Debug for ItemTrait"],["impl Debug for ItemTraitAlias"],["impl Debug for ItemType"],["impl Debug for ItemUnion"],["impl Debug for ItemUse"],["impl Debug for Label"],["impl Debug for Lifetime"],["impl Debug for LifetimeDef"],["impl Debug for Lit"],["impl Debug for Local"],["impl Debug for Macro"],["impl Debug for MacroDelimiter"],["impl Debug for Member"],["impl Debug for Meta"],["impl Debug for MetaList"],["impl Debug for MetaNameValue"],["impl Debug for MethodTurbofish"],["impl Debug for NestedMeta"],["impl Debug for ParenthesizedGenericArguments"],["impl Debug for Pat"],["impl Debug for PatBox"],["impl Debug for PatIdent"],["impl Debug for PatLit"],["impl Debug for PatMacro"],["impl Debug for PatOr"],["impl Debug for PatPath"],["impl Debug for PatRange"],["impl Debug for PatReference"],["impl Debug for PatRest"],["impl Debug for PatSlice"],["impl Debug for PatStruct"],["impl Debug for PatTuple"],["impl Debug for PatTupleStruct"],["impl Debug for PatType"],["impl Debug for PatWild"],["impl Debug for Path"],["impl Debug for PathArguments"],["impl Debug for PathSegment"],["impl Debug for PredicateEq"],["impl Debug for PredicateLifetime"],["impl Debug for PredicateType"],["impl Debug for QSelf"],["impl Debug for RangeLimits"],["impl Debug for Receiver"],["impl Debug for ReturnType"],["impl Debug for Signature"],["impl Debug for Stmt"],["impl Debug for TraitBound"],["impl Debug for TraitBoundModifier"],["impl Debug for TraitItem"],["impl Debug for TraitItemConst"],["impl Debug for TraitItemMacro"],["impl Debug for TraitItemMethod"],["impl Debug for TraitItemType"],["impl Debug for Type"],["impl Debug for TypeArray"],["impl Debug for TypeBareFn"],["impl Debug for TypeGroup"],["impl Debug for TypeImplTrait"],["impl Debug for TypeInfer"],["impl Debug for TypeMacro"],["impl Debug for TypeNever"],["impl Debug for TypeParam"],["impl Debug for TypeParamBound"],["impl Debug for TypeParen"],["impl Debug for TypePath"],["impl Debug for TypePtr"],["impl Debug for TypeReference"],["impl Debug for TypeSlice"],["impl Debug for TypeTraitObject"],["impl Debug for TypeTuple"],["impl Debug for UnOp"],["impl Debug for UseGlob"],["impl Debug for UseGroup"],["impl Debug for UseName"],["impl Debug for UsePath"],["impl Debug for UseRename"],["impl Debug for UseTree"],["impl Debug for Variadic"],["impl Debug for Variant"],["impl Debug for VisCrate"],["impl Debug for VisPublic"],["impl Debug for VisRestricted"],["impl Debug for Visibility"],["impl Debug for WhereClause"],["impl Debug for WherePredicate"],["impl<'a> Debug for ParseBuffer<'a>"],["impl Debug for Nothing"],["impl Debug for Error"]], -"tokio":[["impl Debug for ReadBuf<'_>"],["impl Debug for JoinError"],["impl Debug for AbortHandle"],["impl<T> Debug for JoinHandle<T>where
        T: Debug,
    "],["impl Debug for Builder"],["impl Debug for Handle"],["impl<'a> Debug for EnterGuard<'a>"],["impl Debug for TryCurrentError"],["impl Debug for Runtime"],["impl Debug for RuntimeFlavor"],["impl Debug for Barrier"],["impl Debug for BarrierWaitResult"],["impl<T: Debug> Debug for SendError<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl<T> Debug for Sender<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Receiver<T>"],["impl<T> Debug for Sender<T>"],["impl<T> Debug for WeakSender<T>"],["impl<T> Debug for Permit<'_, T>"],["impl<T> Debug for OwnedPermit<T>"],["impl<T> Debug for UnboundedSender<T>"],["impl<T> Debug for UnboundedReceiver<T>"],["impl<T> Debug for WeakUnboundedSender<T>"],["impl<T: Debug> Debug for SendError<T>"],["impl<T: Debug> Debug for TrySendError<T>"],["impl Debug for TryRecvError"],["impl Debug for TryLockError"],["impl<T: ?Sized> Debug for Mutex<T>where
        T: Debug,
    "],["impl<T: ?Sized + Debug> Debug for MutexGuard<'_, T>"],["impl<T: ?Sized + Debug> Debug for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized + Debug> Debug for MappedMutexGuard<'a, T>"],["impl Debug for Notify"],["impl<'a> Debug for Notified<'a>"],["impl<T: Debug> Debug for Sender<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl Debug for RecvError"],["impl Debug for TryRecvError"],["impl Debug for TryAcquireError"],["impl Debug for AcquireError"],["impl Debug for Semaphore"],["impl<'a> Debug for SemaphorePermit<'a>"],["impl Debug for OwnedSemaphorePermit"],["impl<T: ?Sized, U: ?Sized> Debug for OwnedRwLockReadGuard<T, U>where
        U: Debug,
    "],["impl<T: ?Sized> Debug for OwnedRwLockWriteGuard<T>where
        T: Debug,
    "],["impl<T: ?Sized, U: ?Sized> Debug for OwnedRwLockMappedWriteGuard<T, U>where
        U: Debug,
    "],["impl<'a, T: ?Sized> Debug for RwLockReadGuard<'a, T>where
        T: Debug,
    "],["impl<'a, T: ?Sized> Debug for RwLockWriteGuard<'a, T>where
        T: Debug,
    "],["impl<'a, T: ?Sized> Debug for RwLockMappedWriteGuard<'a, T>where
        T: Debug,
    "],["impl<T: Debug + ?Sized> Debug for RwLock<T>"],["impl<T: Debug> Debug for OnceCell<T>"],["impl<T: Debug> Debug for SetError<T>"],["impl<T: Debug> Debug for Receiver<T>"],["impl<T: Debug> Debug for Sender<T>"],["impl<'a, T: Debug> Debug for Ref<'a, T>"],["impl<T: Debug> Debug for SendError<T>"],["impl Debug for RecvError"],["impl Debug for LocalEnterGuard"],["impl Debug for LocalSet"],["impl<T: 'static> Debug for LocalKey<T>"],["impl<T: 'static, F> Debug for TaskLocalFuture<T, F>where
        T: Debug,
    "],["impl<T> Debug for JoinSet<T>"]], -"typenum":[["impl Debug for B0"],["impl Debug for B1"],["impl<U: Debug + Unsigned + NonZero> Debug for PInt<U>"],["impl<U: Debug + Unsigned + NonZero> Debug for NInt<U>"],["impl Debug for Z0"],["impl Debug for UTerm"],["impl<U: Debug, B: Debug> Debug for UInt<U, B>"],["impl Debug for ATerm"],["impl<V: Debug, A: Debug> Debug for TArr<V, A>"],["impl Debug for Greater"],["impl Debug for Less"],["impl Debug for Equal"]], -"uint":[["impl Debug for FromStrRadixErrKind"],["impl Debug for FromStrRadixErr"],["impl Debug for FromDecStrErr"],["impl Debug for FromHexError"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Freeze.js b/docs/implementors/core/marker/trait.Freeze.js deleted file mode 100644 index 2c93a4acd3dc..000000000000 --- a/docs/implementors/core/marker/trait.Freeze.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Freeze for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Freeze for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> Freeze for AhoCorasick<S>where
        S: Freeze,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Freeze for FindIter<'a, 'b, S>",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Freeze for FindOverlappingIter<'a, 'b, S>where
        S: Freeze,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Freeze for StreamFindIter<'a, R, S>where
        R: Freeze,
        S: Freeze,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Freeze for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Freeze for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Freeze for Error",1,["aho_corasick::error::Error"]],["impl Freeze for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Freeze for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Freeze for Config",1,["aho_corasick::packed::api::Config"]],["impl Freeze for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Freeze for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Freeze for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Freeze for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl Freeze for Error",1,["aiofut::Error"]],["impl !Freeze for AIO",1,["aiofut::AIO"]],["impl Freeze for AIOFuture",1,["aiofut::AIOFuture"]],["impl !Freeze for AIONotifier",1,["aiofut::AIONotifier"]],["impl Freeze for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !Freeze for AIOManager",1,["aiofut::AIOManager"]],["impl !Freeze for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Freeze for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl Freeze for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Freeze for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Freeze for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Freeze for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Freeze for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Freeze for Config",1,["bincode::config::legacy::Config"]],["impl Freeze for Bounded",1,["bincode::config::limit::Bounded"]],["impl Freeze for Infinite",1,["bincode::config::limit::Infinite"]],["impl Freeze for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Freeze for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Freeze for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Freeze for WithOtherLimit<O, L>where
        L: Freeze,
        O: Freeze,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Freeze for WithOtherEndian<O, E>where
        O: Freeze,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Freeze for WithOtherIntEncoding<O, I>where
        O: Freeze,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Freeze for WithOtherTrailing<O, T>where
        O: Freeze,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Freeze for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Freeze for IoReader<R>where
        R: Freeze,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> Freeze for Deserializer<R, O>where
        O: Freeze,
        R: Freeze,
    ",1,["bincode::de::Deserializer"]],["impl Freeze for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Freeze for Serializer<W, O>where
        O: Freeze,
        W: Freeze,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl Freeze for Eager",1,["block_buffer::Eager"]],["impl Freeze for Lazy",1,["block_buffer::Lazy"]],["impl Freeze for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Freeze for BlockBuffer<BlockSize, Kind>where
        <BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl Freeze for BigEndian",1,["byteorder::BigEndian"]],["impl Freeze for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> Freeze for Chain<T, U>where
        T: Freeze,
        U: Freeze,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> Freeze for IntoIter<T>where
        T: Freeze,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> Freeze for Limit<T>where
        T: Freeze,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> Freeze for Reader<B>where
        B: Freeze,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> Freeze for Take<T>where
        T: Freeze,
    ",1,["bytes::buf::take::Take"]],["impl Freeze for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Freeze for Writer<B>where
        B: Freeze,
    ",1,["bytes::buf::writer::Writer"]],["impl !Freeze for Bytes",1,["bytes::bytes::Bytes"]],["impl Freeze for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], -"crc":[["impl<W> Freeze for Crc<W>where
        W: Freeze,
    ",1,["crc::Crc"]],["impl<'a, W> Freeze for Digest<'a, W>where
        W: Freeze,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> Freeze for Algorithm<W>where
        W: Freeze,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<T> Freeze for Sender<T>",1,["crossbeam_channel::channel::Sender"]],["impl<T> Freeze for Receiver<T>",1,["crossbeam_channel::channel::Receiver"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Freeze for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Freeze for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Freeze for SendError<T>where
        T: Freeze,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> Freeze for TrySendError<T>where
        T: Freeze,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Freeze for SendTimeoutError<T>where
        T: Freeze,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Freeze for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Freeze for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Freeze for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Freeze for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Freeze for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Freeze for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Freeze for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> Freeze for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> Freeze for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]]], -"crossbeam_utils":[["impl<T> !Freeze for AtomicCell<T>",1,["crossbeam_utils::atomic::atomic_cell::AtomicCell"]],["impl<T> Freeze for CachePadded<T>where
        T: Freeze,
    ",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl !Freeze for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl Freeze for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Freeze for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<T> !Freeze for ShardedLock<T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLock"]],["impl<'a, T: ?Sized> Freeze for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> Freeze for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Freeze for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Freeze for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Freeze for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> Freeze for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]]], -"crypto_common":[["impl Freeze for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> Freeze for CtVariableCoreWrapper<T, OutSize, O>where
        T: Freeze,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Freeze for RtVariableCoreWrapper<T>where
        T: Freeze,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Freeze for CoreWrapper<T>where
        T: Freeze,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Freeze for XofReaderCoreWrapper<T>where
        T: Freeze,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Freeze,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Freeze for TruncSide",1,["digest::core_api::TruncSide"]],["impl Freeze for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Freeze for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl Freeze for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Freeze for WALConfig",1,["firewood::storage::WALConfig"]],["impl Freeze for DBError",1,["firewood::db::DBError"]],["impl Freeze for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Freeze for DBConfig",1,["firewood::db::DBConfig"]],["impl Freeze for DBRev",1,["firewood::db::DBRev"]],["impl !Freeze for DB",1,["firewood::db::DB"]],["impl<'a> Freeze for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> Freeze for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Freeze for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Freeze for Hash",1,["firewood::merkle::Hash"]],["impl Freeze for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !Freeze for Node",1,["firewood::merkle::Node"]],["impl Freeze for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> Freeze for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> Freeze for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Freeze for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Freeze for Proof",1,["firewood::proof::Proof"]],["impl Freeze for ProofError",1,["firewood::proof::ProofError"]],["impl Freeze for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> Freeze for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> Freeze for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Freeze for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> Freeze for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Freeze for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Freeze for TrySendError<T>where
        T: Freeze,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl Freeze for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Freeze for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> Freeze for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Freeze for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl Freeze for Canceled",1,["futures_channel::oneshot::Canceled"]]], -"futures_executor":[["impl !Freeze for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl Freeze for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Freeze for BlockingStream<S>where
        S: Freeze,
    ",1,["futures_executor::local_pool::BlockingStream"]],["impl Freeze for Enter",1,["futures_executor::enter::Enter"]],["impl Freeze for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl Freeze for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Freeze for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> Freeze for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> Freeze for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], -"futures_util":[["impl<Fut> Freeze for Fuse<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Freeze for CatchUnwind<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Freeze for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Freeze for Remote<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Freeze for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Freeze for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Freeze for Flatten<F>where
        F: Freeze,
        <F as Future>::Output: Freeze,
    ",1,["futures_util::future::future::Flatten"]],["impl<F> Freeze for FlattenStream<F>where
        F: Freeze,
        <F as Future>::Output: Freeze,
    ",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Freeze for Map<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::future::Map"]],["impl<F> Freeze for IntoStream<F>where
        F: Freeze,
    ",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Freeze for MapInto<Fut, T>where
        Fut: Freeze,
    ",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Freeze for Then<Fut1, Fut2, F>where
        F: Freeze,
        Fut1: Freeze,
        Fut2: Freeze,
    ",1,["futures_util::future::future::Then"]],["impl<Fut, F> Freeze for Inspect<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::future::Inspect"]],["impl<Fut> Freeze for NeverError<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::future::NeverError"]],["impl<Fut> Freeze for UnitError<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::future::UnitError"]],["impl<Fut> Freeze for IntoFuture<Fut>where
        Fut: Freeze,
    ",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Freeze for TryFlatten<Fut1, Fut2>where
        Fut1: Freeze,
        Fut2: Freeze,
    ",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Freeze for TryFlattenStream<Fut>where
        Fut: Freeze,
        <Fut as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Freeze for FlattenSink<Fut, Si>where
        Fut: Freeze,
        Si: Freeze,
    ",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Freeze for AndThen<Fut1, Fut2, F>where
        F: Freeze,
        Fut1: Freeze,
        Fut2: Freeze,
    ",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Freeze for OrElse<Fut1, Fut2, F>where
        F: Freeze,
        Fut1: Freeze,
        Fut2: Freeze,
    ",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Freeze for ErrInto<Fut, E>where
        Fut: Freeze,
    ",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Freeze for OkInto<Fut, E>where
        Fut: Freeze,
    ",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Freeze for InspectOk<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Freeze for InspectErr<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Freeze for MapOk<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Freeze for MapErr<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Freeze for MapOkOrElse<Fut, F, G>where
        F: Freeze,
        Fut: Freeze,
        G: Freeze,
    ",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Freeze for UnwrapOrElse<Fut, F>where
        F: Freeze,
        Fut: Freeze,
    ",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Freeze for Lazy<F>where
        F: Freeze,
    ",1,["futures_util::future::lazy::Lazy"]],["impl<T> Freeze for Pending<T>",1,["futures_util::future::pending::Pending"]],["impl<Fut> Freeze for MaybeDone<Fut>where
        Fut: Freeze,
        <Fut as Future>::Output: Freeze,
    ",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Freeze for TryMaybeDone<Fut>where
        Fut: Freeze,
        <Fut as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Freeze for OptionFuture<F>where
        F: Freeze,
    ",1,["futures_util::future::option::OptionFuture"]],["impl<F> Freeze for PollFn<F>where
        F: Freeze,
    ",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Freeze for PollImmediate<T>where
        T: Freeze,
    ",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Freeze for Ready<T>where
        T: Freeze,
    ",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Freeze for Join<Fut1, Fut2>where
        Fut1: Freeze,
        Fut2: Freeze,
        <Fut1 as Future>::Output: Freeze,
        <Fut2 as Future>::Output: Freeze,
    ",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Freeze for Join3<Fut1, Fut2, Fut3>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        <Fut1 as Future>::Output: Freeze,
        <Fut2 as Future>::Output: Freeze,
        <Fut3 as Future>::Output: Freeze,
    ",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Freeze for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        Fut4: Freeze,
        <Fut1 as Future>::Output: Freeze,
        <Fut2 as Future>::Output: Freeze,
        <Fut3 as Future>::Output: Freeze,
        <Fut4 as Future>::Output: Freeze,
    ",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Freeze for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        Fut4: Freeze,
        Fut5: Freeze,
        <Fut1 as Future>::Output: Freeze,
        <Fut2 as Future>::Output: Freeze,
        <Fut3 as Future>::Output: Freeze,
        <Fut4 as Future>::Output: Freeze,
        <Fut5 as Future>::Output: Freeze,
    ",1,["futures_util::future::join::Join5"]],["impl<F> !Freeze for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Freeze for Select<A, B>where
        A: Freeze,
        B: Freeze,
    ",1,["futures_util::future::select::Select"]],["impl<Fut> Freeze for SelectAll<Fut>",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Freeze for TryJoin<Fut1, Fut2>where
        Fut1: Freeze,
        Fut2: Freeze,
        <Fut1 as TryFuture>::Ok: Freeze,
        <Fut2 as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Freeze for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        <Fut1 as TryFuture>::Ok: Freeze,
        <Fut2 as TryFuture>::Ok: Freeze,
        <Fut3 as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Freeze for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        Fut4: Freeze,
        <Fut1 as TryFuture>::Ok: Freeze,
        <Fut2 as TryFuture>::Ok: Freeze,
        <Fut3 as TryFuture>::Ok: Freeze,
        <Fut4 as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Freeze for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Freeze,
        Fut2: Freeze,
        Fut3: Freeze,
        Fut4: Freeze,
        Fut5: Freeze,
        <Fut1 as TryFuture>::Ok: Freeze,
        <Fut2 as TryFuture>::Ok: Freeze,
        <Fut3 as TryFuture>::Ok: Freeze,
        <Fut4 as TryFuture>::Ok: Freeze,
        <Fut5 as TryFuture>::Ok: Freeze,
    ",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !Freeze for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Freeze for TrySelect<A, B>where
        A: Freeze,
        B: Freeze,
    ",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Freeze for SelectOk<Fut>",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Freeze for Either<A, B>where
        A: Freeze,
        B: Freeze,
    ",1,["futures_util::future::either::Either"]],["impl Freeze for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Freeze for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Freeze for Abortable<T>where
        T: Freeze,
    ",1,["futures_util::abortable::Abortable"]],["impl Freeze for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Freeze for Chain<St1, St2>where
        St1: Freeze,
        St2: Freeze,
    ",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Freeze for Collect<St, C>where
        C: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Freeze for Unzip<St, FromA, FromB>where
        FromA: Freeze,
        FromB: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Freeze for Concat<St>where
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Freeze for Cycle<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Freeze for Enumerate<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Freeze for Filter<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Freeze for FilterMap<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Freeze for Fold<St, Fut, T, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        T: Freeze,
    ",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Freeze for ForEach<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Freeze for Fuse<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Freeze for StreamFuture<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Freeze for Map<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Freeze for Next<'a, St>",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Freeze for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Freeze for Peekable<St>where
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Freeze for Peek<'a, St>",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Freeze for PeekMut<'a, St>",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Freeze for NextIf<'a, St, F>where
        F: Freeze,
    ",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Freeze for NextIfEq<'a, St, T>",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Freeze for Skip<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Freeze for SkipWhile<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Freeze for Take<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Freeze for TakeWhile<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Freeze for TakeUntil<St, Fut>where
        Fut: Freeze,
        St: Freeze,
        <Fut as Future>::Output: Freeze,
    ",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Freeze for Then<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Freeze for Zip<St1, St2>where
        St1: Freeze,
        St2: Freeze,
        <St1 as Stream>::Item: Freeze,
        <St2 as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Freeze for Chunks<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Freeze for ReadyChunks<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Freeze for Scan<St, S, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        S: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !Freeze for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !Freeze for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !Freeze for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Freeze for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Freeze for SplitSink<S, Item>where
        Item: Freeze,
    ",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Freeze for ReuniteError<T, Item>where
        Item: Freeze,
    ",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Freeze for CatchUnwind<St>where
        St: Freeze,
    ",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Freeze for Flatten<St>where
        St: Freeze,
        <St as Stream>::Item: Freeze,
    ",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Freeze for Forward<St, Si>where
        Si: Freeze,
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Freeze for Inspect<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Freeze for FlatMap<St, U, F>where
        F: Freeze,
        St: Freeze,
        U: Freeze,
    ",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Freeze for AndThen<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Freeze for IntoStream<St>where
        St: Freeze,
    ",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Freeze for OrElse<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Freeze for TryNext<'a, St>",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Freeze for TryForEach<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Freeze for TryFilter<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Freeze for TryFilterMap<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Freeze for TryFlatten<St>where
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Freeze for TryCollect<St, C>where
        C: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Freeze for TryConcat<St>where
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Freeze for TryChunks<St>where
        St: Freeze,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Freeze for TryChunksError<T, E>where
        E: Freeze,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Freeze for TryFold<St, Fut, T, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        T: Freeze,
    ",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Freeze for TryUnfold<T, F, Fut>where
        F: Freeze,
        Fut: Freeze,
        T: Freeze,
    ",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Freeze for TrySkipWhile<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Freeze for TryTakeWhile<St, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !Freeze for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !Freeze for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !Freeze for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Freeze for IntoAsyncRead<St>where
        St: Freeze,
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Freeze for ErrInto<St, E>where
        St: Freeze,
    ",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Freeze for InspectOk<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Freeze for InspectErr<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Freeze for MapOk<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Freeze for MapErr<St, F>where
        F: Freeze,
        St: Freeze,
    ",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Freeze for Iter<I>where
        I: Freeze,
    ",1,["futures_util::stream::iter::Iter"]],["impl<T> Freeze for Repeat<T>where
        T: Freeze,
    ",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Freeze for RepeatWith<F>where
        F: Freeze,
    ",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Freeze for Empty<T>",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Freeze for Once<Fut>where
        Fut: Freeze,
    ",1,["futures_util::stream::once::Once"]],["impl<T> Freeze for Pending<T>",1,["futures_util::stream::pending::Pending"]],["impl<F> Freeze for PollFn<F>where
        F: Freeze,
    ",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Freeze for PollImmediate<S>where
        S: Freeze,
    ",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Freeze for Select<St1, St2>where
        St1: Freeze,
        St2: Freeze,
    ",1,["futures_util::stream::select::Select"]],["impl Freeze for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Freeze for SelectWithStrategy<St1, St2, Clos, State>where
        Clos: Freeze,
        St1: Freeze,
        St2: Freeze,
        State: Freeze,
    ",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Freeze for Unfold<T, F, Fut>where
        F: Freeze,
        Fut: Freeze,
        T: Freeze,
    ",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !Freeze for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Freeze for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> Freeze for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Freeze for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> Freeze for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !Freeze for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !Freeze for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !Freeze for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Freeze for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Freeze for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !Freeze for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Freeze for Close<'a, Si, Item>",1,["futures_util::sink::close::Close"]],["impl<T> Freeze for Drain<T>",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Freeze for Fanout<Si1, Si2>where
        Si1: Freeze,
        Si2: Freeze,
    ",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Freeze for Feed<'a, Si, Item>where
        Item: Freeze,
    ",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Freeze for Flush<'a, Si, Item>",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Freeze for SinkErrInto<Si, Item, E>where
        Si: Freeze,
    ",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Freeze for SinkMapErr<Si, F>where
        F: Freeze,
        Si: Freeze,
    ",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Freeze for Send<'a, Si, Item>where
        Item: Freeze,
    ",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Freeze for SendAll<'a, Si, St>where
        <St as TryStream>::Ok: Freeze,
    ",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Freeze for Unfold<T, F, R>where
        F: Freeze,
        R: Freeze,
        T: Freeze,
    ",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Freeze for With<Si, Item, U, Fut, F>where
        F: Freeze,
        Fut: Freeze,
        Si: Freeze,
    ",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Freeze for WithFlatMap<Si, Item, U, St, F>where
        F: Freeze,
        Item: Freeze,
        Si: Freeze,
        St: Freeze,
    ",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Freeze for Buffer<Si, Item>where
        Si: Freeze,
    ",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Freeze for AllowStdIo<T>where
        T: Freeze,
    ",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Freeze for BufReader<R>where
        R: Freeze,
    ",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Freeze for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Freeze for BufWriter<W>where
        W: Freeze,
    ",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Freeze for LineWriter<W>where
        W: Freeze,
    ",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Freeze for Chain<T, U>where
        T: Freeze,
        U: Freeze,
    ",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Freeze for Close<'a, W>",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Freeze for Copy<'a, R, W>where
        R: Freeze,
    ",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Freeze for CopyBuf<'a, R, W>where
        R: Freeze,
    ",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Freeze for CopyBufAbortable<'a, R, W>where
        R: Freeze,
    ",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Freeze for Cursor<T>where
        T: Freeze,
    ",1,["futures_util::io::cursor::Cursor"]],["impl Freeze for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Freeze for FillBuf<'a, R>",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Freeze for Flush<'a, W>",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Freeze for IntoSink<W, Item>where
        Item: Freeze,
        W: Freeze,
    ",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Freeze for Lines<R>where
        R: Freeze,
    ",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Freeze for Read<'a, R>",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Freeze for ReadVectored<'a, R>",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Freeze for ReadExact<'a, R>",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Freeze for ReadLine<'a, R>",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Freeze for ReadToEnd<'a, R>",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Freeze for ReadToString<'a, R>",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Freeze for ReadUntil<'a, R>",1,["futures_util::io::read_until::ReadUntil"]],["impl Freeze for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Freeze for Seek<'a, S>",1,["futures_util::io::seek::Seek"]],["impl Freeze for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Freeze for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> Freeze for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> Freeze for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> Freeze for Take<R>where
        R: Freeze,
    ",1,["futures_util::io::take::Take"]],["impl<T> Freeze for Window<T>where
        T: Freeze,
    ",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Freeze for Write<'a, W>",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Freeze for WriteVectored<'a, W>",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Freeze for WriteAll<'a, W>",1,["futures_util::io::write_all::WriteAll"]],["impl<T> !Freeze for Mutex<T>",1,["futures_util::lock::mutex::Mutex"]],["impl<T: ?Sized> Freeze for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T: ?Sized> Freeze for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Freeze for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T: ?Sized> Freeze for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T: ?Sized, U: ?Sized> Freeze for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], -"generic_array":[["impl<T, N> Freeze for GenericArrayIter<T, N>where
        <N as ArrayLength<T>>::ArrayType: Freeze,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> Freeze for GenericArray<T, U>where
        <U as ArrayLength<T>>::ArrayType: Freeze,
    ",1,["generic_array::GenericArray"]]], -"getrandom":[["impl Freeze for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl Freeze for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !Freeze for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl Freeze for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Freeze for WALLoader",1,["growthring::wal::WALLoader"]],["impl Freeze for WALFileAIO",1,["growthring::WALFileAIO"]],["impl Freeze for WALStoreAIO",1,["growthring::WALStoreAIO"]]], -"hashbrown":[["impl<K, V, S, A> Freeze for HashMap<K, V, S, A>where
        A: Freeze,
        S: Freeze,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Freeze for Iter<'a, K, V>",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Freeze for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Freeze for IntoIter<K, V, A>where
        A: Freeze,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Freeze for IntoKeys<K, V, A>where
        A: Freeze,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Freeze for IntoValues<K, V, A>where
        A: Freeze,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Freeze for Keys<'a, K, V>",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Freeze for Values<'a, K, V>",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Freeze for Drain<'a, K, V, A>where
        A: Freeze,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Freeze for DrainFilter<'a, K, V, F, A>where
        F: Freeze,
    ",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Freeze for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Freeze for RawEntryBuilder<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Freeze for Entry<'a, K, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Freeze for OccupiedEntry<'a, K, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> Freeze for VacantEntry<'a, K, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for EntryRef<'a, 'b, K, Q, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Freeze for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Freeze,
    ",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Freeze for OccupiedError<'a, K, V, S, A>where
        K: Freeze,
        V: Freeze,
    ",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Freeze for HashSet<T, S, A>where
        A: Freeze,
        S: Freeze,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> Freeze for Iter<'a, K>",1,["hashbrown::set::Iter"]],["impl<K, A> Freeze for IntoIter<K, A>where
        A: Freeze,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Freeze for Drain<'a, K, A>where
        A: Freeze,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Freeze for DrainFilter<'a, K, F, A>where
        F: Freeze,
    ",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Freeze for Intersection<'a, T, S, A>",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Freeze for Difference<'a, T, S, A>",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Freeze for SymmetricDifference<'a, T, S, A>",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Freeze for Union<'a, T, S, A>",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Freeze for Entry<'a, T, S, A>where
        T: Freeze,
    ",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Freeze for OccupiedEntry<'a, T, S, A>where
        T: Freeze,
    ",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Freeze for VacantEntry<'a, T, S, A>where
        T: Freeze,
    ",1,["hashbrown::set::VacantEntry"]],["impl Freeze for TryReserveError",1,["hashbrown::TryReserveError"]]], -"heck":[["impl<T> Freeze for AsKebabCase<T>where
        T: Freeze,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> Freeze for AsLowerCamelCase<T>where
        T: Freeze,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Freeze for AsShoutyKebabCase<T>where
        T: Freeze,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Freeze for AsShoutySnakeCase<T>where
        T: Freeze,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Freeze for AsSnakeCase<T>where
        T: Freeze,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> Freeze for AsTitleCase<T>where
        T: Freeze,
    ",1,["heck::title::AsTitleCase"]],["impl<T> Freeze for AsUpperCamelCase<T>where
        T: Freeze,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl Freeze for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl Freeze for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Freeze for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Freeze for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Freeze for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Freeze for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Freeze for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Freeze for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Freeze for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl Freeze for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Freeze for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Freeze for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Freeze for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Freeze for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Freeze for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Freeze for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Freeze for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Freeze for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Freeze for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl Freeze for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl Freeze for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Freeze for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Freeze for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Freeze for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Freeze for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Freeze for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl Freeze for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Freeze for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Freeze for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Freeze for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Freeze for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Freeze for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Freeze for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Freeze for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl Freeze for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Freeze for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Freeze for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl Freeze for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl Freeze for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Freeze for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Freeze for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Freeze for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Freeze for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Freeze for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Freeze for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Freeze for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl Freeze for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Freeze for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Freeze for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl Freeze for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Freeze for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Freeze for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Freeze for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Freeze for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Freeze for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Freeze for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Freeze for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Freeze for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Freeze for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Freeze for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Freeze for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl Freeze for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl Freeze for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl Freeze for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Freeze for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Freeze for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Freeze for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Freeze for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Freeze for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Freeze for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl Freeze for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Freeze for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Freeze for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Freeze for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Freeze for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Freeze for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Freeze for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Freeze for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Freeze for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Freeze for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Freeze for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Freeze for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Freeze for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Freeze for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl Freeze for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Freeze for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Freeze for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Freeze for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Freeze for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Freeze for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl Freeze for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Freeze for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Freeze for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Freeze for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Freeze for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Freeze for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Freeze for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Freeze for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Freeze for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Freeze for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl Freeze for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl Freeze for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Freeze for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Freeze for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Freeze for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Freeze for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Freeze for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Freeze for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Freeze for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Freeze for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Freeze for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Freeze for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Freeze for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Freeze for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Freeze for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Freeze for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl Freeze for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Freeze for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Freeze for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Freeze for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Freeze for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Freeze for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Freeze for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Freeze for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Freeze for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Freeze for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Freeze for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Freeze for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Freeze for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Freeze for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Freeze for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Freeze for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl Freeze for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl Freeze for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Freeze for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Freeze for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Freeze for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Freeze for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Freeze for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Freeze for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Freeze for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Freeze for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Freeze for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Freeze for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Freeze for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Freeze for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Freeze for timezone",1,["libc::unix::linux_like::timezone"]],["impl Freeze for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Freeze for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Freeze for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Freeze for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Freeze for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Freeze for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Freeze for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl Freeze for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Freeze for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Freeze for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl Freeze for tm",1,["libc::unix::linux_like::tm"]],["impl Freeze for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl Freeze for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl Freeze for lconv",1,["libc::unix::linux_like::lconv"]],["impl Freeze for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl Freeze for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Freeze for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Freeze for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Freeze for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Freeze for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl Freeze for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Freeze for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Freeze for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Freeze for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Freeze for utsname",1,["libc::unix::linux_like::utsname"]],["impl Freeze for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Freeze for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Freeze for DIR",1,["libc::unix::DIR"]],["impl Freeze for group",1,["libc::unix::group"]],["impl Freeze for utimbuf",1,["libc::unix::utimbuf"]],["impl Freeze for timeval",1,["libc::unix::timeval"]],["impl Freeze for timespec",1,["libc::unix::timespec"]],["impl Freeze for rlimit",1,["libc::unix::rlimit"]],["impl Freeze for rusage",1,["libc::unix::rusage"]],["impl Freeze for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl Freeze for hostent",1,["libc::unix::hostent"]],["impl Freeze for iovec",1,["libc::unix::iovec"]],["impl Freeze for pollfd",1,["libc::unix::pollfd"]],["impl Freeze for winsize",1,["libc::unix::winsize"]],["impl Freeze for linger",1,["libc::unix::linger"]],["impl Freeze for sigval",1,["libc::unix::sigval"]],["impl Freeze for itimerval",1,["libc::unix::itimerval"]],["impl Freeze for tms",1,["libc::unix::tms"]],["impl Freeze for servent",1,["libc::unix::servent"]],["impl Freeze for protoent",1,["libc::unix::protoent"]],["impl Freeze for FILE",1,["libc::unix::FILE"]],["impl Freeze for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<R, T> !Freeze for Mutex<R, T>",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T: ?Sized> Freeze for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> !Freeze for RawReentrantMutex<R, G>",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T> !Freeze for ReentrantMutex<R, G, T>",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T: ?Sized> Freeze for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T: ?Sized> Freeze for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T> !Freeze for RwLock<R, T>",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T: ?Sized> Freeze for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Freeze for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedRwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> Freeze for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl Freeze for GuardSend",1,["lock_api::GuardSend"]],["impl Freeze for GuardNoSend",1,["lock_api::GuardNoSend"]]], -"lru":[["impl<K, V, S> Freeze for LruCache<K, V, S>where
        S: Freeze,
    ",1,["lru::LruCache"]],["impl<'a, K, V> Freeze for Iter<'a, K, V>",1,["lru::Iter"]],["impl<'a, K, V> Freeze for IterMut<'a, K, V>",1,["lru::IterMut"]],["impl<K, V> Freeze for IntoIter<K, V>",1,["lru::IntoIter"]]], -"memchr":[["impl<'a> Freeze for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Freeze for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Freeze for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Freeze for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Freeze for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Freeze for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Freeze for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Freeze for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Freeze for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl Freeze for Dir",1,["nix::dir::Dir"]],["impl<'d> Freeze for Iter<'d>",1,["nix::dir::Iter"]],["impl Freeze for OwningIter",1,["nix::dir::OwningIter"]],["impl Freeze for Entry",1,["nix::dir::Entry"]],["impl Freeze for Type",1,["nix::dir::Type"]],["impl Freeze for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Freeze for Errno",1,["nix::errno::consts::Errno"]],["impl Freeze for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Freeze for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Freeze for OFlag",1,["nix::fcntl::OFlag"]],["impl Freeze for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Freeze for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Freeze for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Freeze for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Freeze for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Freeze for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Freeze for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Freeze for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl Freeze for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl Freeze for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl Freeze for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> Freeze for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Freeze for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Freeze for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Freeze for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Freeze for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Freeze for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Freeze for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Freeze for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Freeze for MqdT",1,["nix::mqueue::MqdT"]],["impl Freeze for PollFd",1,["nix::poll::PollFd"]],["impl Freeze for PollFlags",1,["nix::poll::PollFlags"]],["impl Freeze for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Freeze for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Freeze for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Freeze for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Freeze for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Freeze for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Freeze for LioMode",1,["nix::sys::aio::LioMode"]],["impl Freeze for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Freeze for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Freeze for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Freeze for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Freeze for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Freeze for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Freeze for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Freeze for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Freeze for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Freeze for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Freeze for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Freeze for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Freeze for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Freeze for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Freeze for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Freeze for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Freeze for Persona",1,["nix::sys::personality::Persona"]],["impl Freeze for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Freeze for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Freeze for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Freeze for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Freeze for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Freeze for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Freeze for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Freeze for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Freeze for Resource",1,["nix::sys::resource::Resource"]],["impl Freeze for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Freeze for Usage",1,["nix::sys::resource::Usage"]],["impl Freeze for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Freeze for Fds<'a>",1,["nix::sys::select::Fds"]],["impl Freeze for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Freeze for Signal",1,["nix::sys::signal::Signal"]],["impl Freeze for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Freeze for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Freeze for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Freeze for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Freeze for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Freeze for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Freeze for SigAction",1,["nix::sys::signal::SigAction"]],["impl Freeze for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Freeze for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Freeze for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Freeze for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Freeze for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Freeze for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Freeze for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Freeze for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Freeze for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Freeze for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Freeze for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Freeze for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Freeze for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Freeze for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Freeze for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Freeze for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Freeze for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Freeze for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Freeze for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Freeze for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Freeze for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Freeze for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Freeze for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Freeze for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Freeze for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Freeze for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Freeze for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Freeze for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Freeze for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Freeze for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Freeze for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Freeze for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Freeze for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Freeze for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Freeze for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Freeze for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Freeze for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Freeze for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Freeze for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Freeze for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Freeze for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Freeze for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Freeze for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Freeze for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Freeze for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Freeze for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Freeze for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Freeze for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Freeze for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Freeze for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Freeze for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Freeze for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Freeze for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Freeze for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Freeze for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Freeze for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Freeze for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Freeze for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Freeze for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Freeze for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Freeze for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Freeze for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Freeze for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Freeze for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Freeze for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Freeze for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Freeze for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Freeze for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Freeze for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Freeze for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Freeze for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Freeze for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Freeze for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Freeze for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Freeze for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Freeze for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Freeze for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Freeze for AlgSetKey<T>",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Freeze for SockType",1,["nix::sys::socket::SockType"]],["impl Freeze for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Freeze for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Freeze for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Freeze for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Freeze for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Freeze for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Freeze for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> Freeze for RecvMsg<'a, 's, S>where
        S: Freeze,
    ",1,["nix::sys::socket::RecvMsg"]],["impl<'a> Freeze for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Freeze for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Freeze for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Freeze for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> Freeze for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> Freeze for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Freeze for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Freeze for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Freeze for SFlag",1,["nix::sys::stat::SFlag"]],["impl Freeze for Mode",1,["nix::sys::stat::Mode"]],["impl Freeze for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Freeze for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Freeze for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Freeze for FsType",1,["nix::sys::statfs::FsType"]],["impl Freeze for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Freeze for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Freeze for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !Freeze for Termios",1,["nix::sys::termios::Termios"]],["impl Freeze for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Freeze for SetArg",1,["nix::sys::termios::SetArg"]],["impl Freeze for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Freeze for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Freeze for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Freeze for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Freeze for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Freeze for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Freeze for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Freeze for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Freeze for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Freeze for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Freeze for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Freeze for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> Freeze for IoVec<T>",1,["nix::sys::uio::IoVec"]],["impl Freeze for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Freeze for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Freeze for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Freeze for Id",1,["nix::sys::wait::Id"]],["impl Freeze for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Freeze for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Freeze for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Freeze for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Freeze for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Freeze for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Freeze for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Freeze for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl Freeze for Timer",1,["nix::sys::timer::Timer"]],["impl Freeze for ClockId",1,["nix::time::ClockId"]],["impl Freeze for UContext",1,["nix::ucontext::UContext"]],["impl Freeze for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Freeze for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Freeze for Uid",1,["nix::unistd::Uid"]],["impl Freeze for Gid",1,["nix::unistd::Gid"]],["impl Freeze for Pid",1,["nix::unistd::Pid"]],["impl Freeze for ForkResult",1,["nix::unistd::ForkResult"]],["impl Freeze for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Freeze for Whence",1,["nix::unistd::Whence"]],["impl Freeze for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Freeze for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Freeze for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Freeze for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Freeze for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Freeze for User",1,["nix::unistd::User"]],["impl Freeze for Group",1,["nix::unistd::Group"]]], -"once_cell":[["impl<T> !Freeze for OnceCell<T>",1,["once_cell::unsync::OnceCell"]],["impl<T, F = fn() -> T> !Freeze for Lazy<T, F>",1,["once_cell::unsync::Lazy"]],["impl<T> !Freeze for OnceCell<T>",1,["once_cell::sync::OnceCell"]],["impl<T, F = fn() -> T> !Freeze for Lazy<T, F>",1,["once_cell::sync::Lazy"]],["impl<T> !Freeze for OnceBox<T>",1,["once_cell::race::once_box::OnceBox"]],["impl !Freeze for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl !Freeze for OnceBool",1,["once_cell::race::OnceBool"]]], -"parking_lot":[["impl Freeze for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl !Freeze for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Freeze for OnceState",1,["parking_lot::once::OnceState"]],["impl !Freeze for Once",1,["parking_lot::once::Once"]],["impl !Freeze for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl !Freeze for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl !Freeze for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Freeze for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl Freeze for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Freeze for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Freeze for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Freeze for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Freeze for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Freeze for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Freeze for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl Freeze for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Freeze for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Freeze for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Freeze for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Freeze for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Freeze for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Freeze for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Freeze for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Freeze for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Freeze for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Freeze for SseMachine<S3, S4, NI>",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Freeze for Avx2Machine<NI>",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Freeze for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Freeze for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Freeze for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl Freeze for Error",1,["primitive_types::Error"]],["impl Freeze for U128",1,["primitive_types::U128"]],["impl Freeze for U256",1,["primitive_types::U256"]],["impl Freeze for U512",1,["primitive_types::U512"]],["impl Freeze for H128",1,["primitive_types::H128"]],["impl Freeze for H160",1,["primitive_types::H160"]],["impl Freeze for H256",1,["primitive_types::H256"]],["impl Freeze for H384",1,["primitive_types::H384"]],["impl Freeze for H512",1,["primitive_types::H512"]],["impl Freeze for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl Freeze for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl Freeze for TokenStream",1,["proc_macro2::TokenStream"]],["impl Freeze for LexError",1,["proc_macro2::LexError"]],["impl Freeze for Span",1,["proc_macro2::Span"]],["impl Freeze for TokenTree",1,["proc_macro2::TokenTree"]],["impl Freeze for Group",1,["proc_macro2::Group"]],["impl Freeze for Delimiter",1,["proc_macro2::Delimiter"]],["impl Freeze for Punct",1,["proc_macro2::Punct"]],["impl Freeze for Spacing",1,["proc_macro2::Spacing"]],["impl Freeze for Ident",1,["proc_macro2::Ident"]],["impl Freeze for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl Freeze for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Freeze for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Freeze for DistIter<D, R, T>where
        D: Freeze,
        R: Freeze,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Freeze for DistMap<D, F, T, S>where
        D: Freeze,
        F: Freeze,
    ",1,["rand::distributions::distribution::DistMap"]],["impl Freeze for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Freeze for Open01",1,["rand::distributions::float::Open01"]],["impl Freeze for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Freeze for Slice<'a, T>",1,["rand::distributions::slice::Slice"]],["impl<X> Freeze for WeightedIndex<X>where
        X: Freeze,
        <X as SampleUniform>::Sampler: Freeze,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Freeze for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Freeze for Uniform<X>where
        <X as SampleUniform>::Sampler: Freeze,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> Freeze for UniformInt<X>where
        X: Freeze,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl Freeze for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Freeze for UniformFloat<X>where
        X: Freeze,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl Freeze for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Freeze for WeightedIndex<W>",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Freeze for Standard",1,["rand::distributions::Standard"]],["impl<R> Freeze for ReadRng<R>where
        R: Freeze,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl Freeze for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Freeze for ReseedingRng<R, Rsdr>where
        R: Freeze,
        Rsdr: Freeze,
        <R as BlockRngCore>::Results: Freeze,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Freeze for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Freeze for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Freeze for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Freeze for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Freeze for SliceChooseIter<'a, S, T>",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl Freeze for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Freeze for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Freeze for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Freeze for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Freeze for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Freeze for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> Freeze for BlockRng<R>where
        R: Freeze,
        <R as BlockRngCore>::Results: Freeze,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Freeze for BlockRng64<R>where
        R: Freeze,
        <R as BlockRngCore>::Results: Freeze,
    ",1,["rand_core::block::BlockRng64"]],["impl Freeze for Error",1,["rand_core::error::Error"]],["impl Freeze for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl Freeze for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Freeze for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Freeze for Match<'t>",1,["regex::re_bytes::Match"]],["impl Freeze for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Freeze for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Freeze for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Freeze for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Freeze for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Freeze for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Freeze for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Freeze for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Freeze for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Freeze for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Freeze for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Freeze for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Freeze for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Freeze for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Freeze for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Freeze for Error",1,["regex::error::Error"]],["impl Freeze for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Freeze for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Freeze for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Freeze for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Freeze for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Freeze for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Freeze for Match<'t>",1,["regex::re_unicode::Match"]],["impl Freeze for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Freeze for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Freeze for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Freeze for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Freeze for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Freeze for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Freeze for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Freeze for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Freeze for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Freeze for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Freeze for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl Freeze for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !Freeze for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Freeze for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Freeze for Error",1,["regex_syntax::ast::Error"]],["impl Freeze for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Freeze for Span",1,["regex_syntax::ast::Span"]],["impl Freeze for Position",1,["regex_syntax::ast::Position"]],["impl Freeze for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Freeze for Comment",1,["regex_syntax::ast::Comment"]],["impl Freeze for Ast",1,["regex_syntax::ast::Ast"]],["impl Freeze for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Freeze for Concat",1,["regex_syntax::ast::Concat"]],["impl Freeze for Literal",1,["regex_syntax::ast::Literal"]],["impl Freeze for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Freeze for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Freeze for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Freeze for Class",1,["regex_syntax::ast::Class"]],["impl Freeze for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Freeze for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Freeze for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Freeze for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Freeze for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Freeze for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Freeze for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Freeze for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Freeze for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Freeze for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Freeze for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Freeze for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Freeze for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Freeze for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Freeze for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Freeze for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Freeze for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Freeze for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Freeze for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Freeze for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Freeze for Group",1,["regex_syntax::ast::Group"]],["impl Freeze for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Freeze for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Freeze for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Freeze for Flags",1,["regex_syntax::ast::Flags"]],["impl Freeze for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Freeze for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Freeze for Flag",1,["regex_syntax::ast::Flag"]],["impl Freeze for Error",1,["regex_syntax::error::Error"]],["impl Freeze for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Freeze for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Freeze for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Freeze for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !Freeze for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Freeze for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Freeze for Error",1,["regex_syntax::hir::Error"]],["impl Freeze for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Freeze for Hir",1,["regex_syntax::hir::Hir"]],["impl Freeze for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Freeze for Literal",1,["regex_syntax::hir::Literal"]],["impl Freeze for Class",1,["regex_syntax::hir::Class"]],["impl Freeze for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Freeze for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Freeze for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Freeze for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Freeze for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Freeze for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Freeze for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Freeze for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Freeze for Group",1,["regex_syntax::hir::Group"]],["impl Freeze for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Freeze for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Freeze for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Freeze for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Freeze for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !Freeze for Parser",1,["regex_syntax::parser::Parser"]],["impl Freeze for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Freeze for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Freeze for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Freeze for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl Freeze for DecoderError",1,["rlp::error::DecoderError"]],["impl Freeze for Prototype",1,["rlp::rlpin::Prototype"]],["impl Freeze for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !Freeze for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> Freeze for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Freeze for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> Freeze for ToHexIter<T>where
        T: Freeze,
    ",1,["rustc_hex::ToHexIter"]],["impl Freeze for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Freeze for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl Freeze for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl Freeze for Always",1,["scopeguard::Always"]],["impl<T, F, S> Freeze for ScopeGuard<T, F, S>where
        F: Freeze,
        T: Freeze,
    ",1,["scopeguard::ScopeGuard"]]], -"serde":[["impl Freeze for Error",1,["serde::de::value::Error"]],["impl<E> Freeze for UnitDeserializer<E>",1,["serde::de::value::UnitDeserializer"]],["impl<E> Freeze for BoolDeserializer<E>",1,["serde::de::value::BoolDeserializer"]],["impl<E> Freeze for I8Deserializer<E>",1,["serde::de::value::I8Deserializer"]],["impl<E> Freeze for I16Deserializer<E>",1,["serde::de::value::I16Deserializer"]],["impl<E> Freeze for I32Deserializer<E>",1,["serde::de::value::I32Deserializer"]],["impl<E> Freeze for I64Deserializer<E>",1,["serde::de::value::I64Deserializer"]],["impl<E> Freeze for IsizeDeserializer<E>",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Freeze for U8Deserializer<E>",1,["serde::de::value::U8Deserializer"]],["impl<E> Freeze for U16Deserializer<E>",1,["serde::de::value::U16Deserializer"]],["impl<E> Freeze for U64Deserializer<E>",1,["serde::de::value::U64Deserializer"]],["impl<E> Freeze for UsizeDeserializer<E>",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Freeze for F32Deserializer<E>",1,["serde::de::value::F32Deserializer"]],["impl<E> Freeze for F64Deserializer<E>",1,["serde::de::value::F64Deserializer"]],["impl<E> Freeze for CharDeserializer<E>",1,["serde::de::value::CharDeserializer"]],["impl<E> Freeze for I128Deserializer<E>",1,["serde::de::value::I128Deserializer"]],["impl<E> Freeze for U128Deserializer<E>",1,["serde::de::value::U128Deserializer"]],["impl<E> Freeze for U32Deserializer<E>",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Freeze for StrDeserializer<'a, E>",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Freeze for BorrowedStrDeserializer<'de, E>",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Freeze for StringDeserializer<E>",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Freeze for CowStrDeserializer<'a, E>",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Freeze for BytesDeserializer<'a, E>",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Freeze for BorrowedBytesDeserializer<'de, E>",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Freeze for SeqDeserializer<I, E>where
        I: Freeze,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> Freeze for SeqAccessDeserializer<A>where
        A: Freeze,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Freeze for MapDeserializer<'de, I, E>where
        I: Freeze,
        <<I as Iterator>::Item as Pair>::Second: Freeze,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> Freeze for MapAccessDeserializer<A>where
        A: Freeze,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Freeze for EnumAccessDeserializer<A>where
        A: Freeze,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl Freeze for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Freeze for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Freeze for Impossible<Ok, Error>",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl Freeze for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Freeze for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Freeze for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Freeze for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Freeze for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Freeze for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Freeze for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Freeze for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Freeze for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Freeze for Shake128Core",1,["sha3::Shake128Core"]],["impl Freeze for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Freeze for Shake256Core",1,["sha3::Shake256Core"]],["impl Freeze for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Freeze for CShake128Core",1,["sha3::CShake128Core"]],["impl Freeze for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Freeze for CShake256Core",1,["sha3::CShake256Core"]],["impl Freeze for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl Freeze for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Freeze for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Freeze for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Freeze for ShaleError",1,["shale::ShaleError"]],["impl Freeze for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Freeze for ObjPtr<T>",1,["shale::ObjPtr"]],["impl<T: ?Sized> Freeze for Obj<T>",1,["shale::Obj"]],["impl<'a, T> Freeze for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> Freeze for MummyObj<T>where
        T: Freeze,
    ",1,["shale::MummyObj"]],["impl Freeze for PlainMem",1,["shale::PlainMem"]],["impl<T: ?Sized> Freeze for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> Freeze for Slab<T>",1,["slab::Slab"]],["impl<'a, T> Freeze for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> Freeze for IntoIter<T>",1,["slab::IntoIter"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["slab::Iter"]],["impl<'a, T> Freeze for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> Freeze for Drain<'a, T>",1,["slab::Drain"]]], -"smallvec":[["impl Freeze for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> Freeze for Drain<'a, T>",1,["smallvec::Drain"]],["impl<A> Freeze for SmallVec<A>where
        A: Freeze,
    ",1,["smallvec::SmallVec"]],["impl<A> Freeze for IntoIter<A>where
        A: Freeze,
    ",1,["smallvec::IntoIter"]]], -"syn":[["impl Freeze for Underscore",1,["syn::token::Underscore"]],["impl Freeze for Abstract",1,["syn::token::Abstract"]],["impl Freeze for As",1,["syn::token::As"]],["impl Freeze for Async",1,["syn::token::Async"]],["impl Freeze for Auto",1,["syn::token::Auto"]],["impl Freeze for Await",1,["syn::token::Await"]],["impl Freeze for Become",1,["syn::token::Become"]],["impl Freeze for Box",1,["syn::token::Box"]],["impl Freeze for Break",1,["syn::token::Break"]],["impl Freeze for Const",1,["syn::token::Const"]],["impl Freeze for Continue",1,["syn::token::Continue"]],["impl Freeze for Crate",1,["syn::token::Crate"]],["impl Freeze for Default",1,["syn::token::Default"]],["impl Freeze for Do",1,["syn::token::Do"]],["impl Freeze for Dyn",1,["syn::token::Dyn"]],["impl Freeze for Else",1,["syn::token::Else"]],["impl Freeze for Enum",1,["syn::token::Enum"]],["impl Freeze for Extern",1,["syn::token::Extern"]],["impl Freeze for Final",1,["syn::token::Final"]],["impl Freeze for Fn",1,["syn::token::Fn"]],["impl Freeze for For",1,["syn::token::For"]],["impl Freeze for If",1,["syn::token::If"]],["impl Freeze for Impl",1,["syn::token::Impl"]],["impl Freeze for In",1,["syn::token::In"]],["impl Freeze for Let",1,["syn::token::Let"]],["impl Freeze for Loop",1,["syn::token::Loop"]],["impl Freeze for Macro",1,["syn::token::Macro"]],["impl Freeze for Match",1,["syn::token::Match"]],["impl Freeze for Mod",1,["syn::token::Mod"]],["impl Freeze for Move",1,["syn::token::Move"]],["impl Freeze for Mut",1,["syn::token::Mut"]],["impl Freeze for Override",1,["syn::token::Override"]],["impl Freeze for Priv",1,["syn::token::Priv"]],["impl Freeze for Pub",1,["syn::token::Pub"]],["impl Freeze for Ref",1,["syn::token::Ref"]],["impl Freeze for Return",1,["syn::token::Return"]],["impl Freeze for SelfType",1,["syn::token::SelfType"]],["impl Freeze for SelfValue",1,["syn::token::SelfValue"]],["impl Freeze for Static",1,["syn::token::Static"]],["impl Freeze for Struct",1,["syn::token::Struct"]],["impl Freeze for Super",1,["syn::token::Super"]],["impl Freeze for Trait",1,["syn::token::Trait"]],["impl Freeze for Try",1,["syn::token::Try"]],["impl Freeze for Type",1,["syn::token::Type"]],["impl Freeze for Typeof",1,["syn::token::Typeof"]],["impl Freeze for Union",1,["syn::token::Union"]],["impl Freeze for Unsafe",1,["syn::token::Unsafe"]],["impl Freeze for Unsized",1,["syn::token::Unsized"]],["impl Freeze for Use",1,["syn::token::Use"]],["impl Freeze for Virtual",1,["syn::token::Virtual"]],["impl Freeze for Where",1,["syn::token::Where"]],["impl Freeze for While",1,["syn::token::While"]],["impl Freeze for Yield",1,["syn::token::Yield"]],["impl Freeze for Add",1,["syn::token::Add"]],["impl Freeze for AddEq",1,["syn::token::AddEq"]],["impl Freeze for And",1,["syn::token::And"]],["impl Freeze for AndAnd",1,["syn::token::AndAnd"]],["impl Freeze for AndEq",1,["syn::token::AndEq"]],["impl Freeze for At",1,["syn::token::At"]],["impl Freeze for Bang",1,["syn::token::Bang"]],["impl Freeze for Caret",1,["syn::token::Caret"]],["impl Freeze for CaretEq",1,["syn::token::CaretEq"]],["impl Freeze for Colon",1,["syn::token::Colon"]],["impl Freeze for Colon2",1,["syn::token::Colon2"]],["impl Freeze for Comma",1,["syn::token::Comma"]],["impl Freeze for Div",1,["syn::token::Div"]],["impl Freeze for DivEq",1,["syn::token::DivEq"]],["impl Freeze for Dollar",1,["syn::token::Dollar"]],["impl Freeze for Dot",1,["syn::token::Dot"]],["impl Freeze for Dot2",1,["syn::token::Dot2"]],["impl Freeze for Dot3",1,["syn::token::Dot3"]],["impl Freeze for DotDotEq",1,["syn::token::DotDotEq"]],["impl Freeze for Eq",1,["syn::token::Eq"]],["impl Freeze for EqEq",1,["syn::token::EqEq"]],["impl Freeze for Ge",1,["syn::token::Ge"]],["impl Freeze for Gt",1,["syn::token::Gt"]],["impl Freeze for Le",1,["syn::token::Le"]],["impl Freeze for Lt",1,["syn::token::Lt"]],["impl Freeze for MulEq",1,["syn::token::MulEq"]],["impl Freeze for Ne",1,["syn::token::Ne"]],["impl Freeze for Or",1,["syn::token::Or"]],["impl Freeze for OrEq",1,["syn::token::OrEq"]],["impl Freeze for OrOr",1,["syn::token::OrOr"]],["impl Freeze for Pound",1,["syn::token::Pound"]],["impl Freeze for Question",1,["syn::token::Question"]],["impl Freeze for RArrow",1,["syn::token::RArrow"]],["impl Freeze for LArrow",1,["syn::token::LArrow"]],["impl Freeze for Rem",1,["syn::token::Rem"]],["impl Freeze for RemEq",1,["syn::token::RemEq"]],["impl Freeze for FatArrow",1,["syn::token::FatArrow"]],["impl Freeze for Semi",1,["syn::token::Semi"]],["impl Freeze for Shl",1,["syn::token::Shl"]],["impl Freeze for ShlEq",1,["syn::token::ShlEq"]],["impl Freeze for Shr",1,["syn::token::Shr"]],["impl Freeze for ShrEq",1,["syn::token::ShrEq"]],["impl Freeze for Star",1,["syn::token::Star"]],["impl Freeze for Sub",1,["syn::token::Sub"]],["impl Freeze for SubEq",1,["syn::token::SubEq"]],["impl Freeze for Tilde",1,["syn::token::Tilde"]],["impl Freeze for Brace",1,["syn::token::Brace"]],["impl Freeze for Bracket",1,["syn::token::Bracket"]],["impl Freeze for Paren",1,["syn::token::Paren"]],["impl Freeze for Group",1,["syn::token::Group"]],["impl Freeze for Attribute",1,["syn::attr::Attribute"]],["impl Freeze for AttrStyle",1,["syn::attr::AttrStyle"]],["impl Freeze for Meta",1,["syn::attr::Meta"]],["impl Freeze for MetaList",1,["syn::attr::MetaList"]],["impl Freeze for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl Freeze for NestedMeta",1,["syn::attr::NestedMeta"]],["impl Freeze for Variant",1,["syn::data::Variant"]],["impl Freeze for Fields",1,["syn::data::Fields"]],["impl Freeze for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl Freeze for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl Freeze for Field",1,["syn::data::Field"]],["impl Freeze for Visibility",1,["syn::data::Visibility"]],["impl Freeze for VisPublic",1,["syn::data::VisPublic"]],["impl Freeze for VisCrate",1,["syn::data::VisCrate"]],["impl Freeze for VisRestricted",1,["syn::data::VisRestricted"]],["impl Freeze for Expr",1,["syn::expr::Expr"]],["impl Freeze for ExprArray",1,["syn::expr::ExprArray"]],["impl Freeze for ExprAssign",1,["syn::expr::ExprAssign"]],["impl Freeze for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl Freeze for ExprAsync",1,["syn::expr::ExprAsync"]],["impl Freeze for ExprAwait",1,["syn::expr::ExprAwait"]],["impl Freeze for ExprBinary",1,["syn::expr::ExprBinary"]],["impl Freeze for ExprBlock",1,["syn::expr::ExprBlock"]],["impl Freeze for ExprBox",1,["syn::expr::ExprBox"]],["impl Freeze for ExprBreak",1,["syn::expr::ExprBreak"]],["impl Freeze for ExprCall",1,["syn::expr::ExprCall"]],["impl Freeze for ExprCast",1,["syn::expr::ExprCast"]],["impl Freeze for ExprClosure",1,["syn::expr::ExprClosure"]],["impl Freeze for ExprContinue",1,["syn::expr::ExprContinue"]],["impl Freeze for ExprField",1,["syn::expr::ExprField"]],["impl Freeze for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl Freeze for ExprGroup",1,["syn::expr::ExprGroup"]],["impl Freeze for ExprIf",1,["syn::expr::ExprIf"]],["impl Freeze for ExprIndex",1,["syn::expr::ExprIndex"]],["impl Freeze for ExprLet",1,["syn::expr::ExprLet"]],["impl Freeze for ExprLit",1,["syn::expr::ExprLit"]],["impl Freeze for ExprLoop",1,["syn::expr::ExprLoop"]],["impl Freeze for ExprMacro",1,["syn::expr::ExprMacro"]],["impl Freeze for ExprMatch",1,["syn::expr::ExprMatch"]],["impl Freeze for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl Freeze for ExprParen",1,["syn::expr::ExprParen"]],["impl Freeze for ExprPath",1,["syn::expr::ExprPath"]],["impl Freeze for ExprRange",1,["syn::expr::ExprRange"]],["impl Freeze for ExprReference",1,["syn::expr::ExprReference"]],["impl Freeze for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl Freeze for ExprReturn",1,["syn::expr::ExprReturn"]],["impl Freeze for ExprStruct",1,["syn::expr::ExprStruct"]],["impl Freeze for ExprTry",1,["syn::expr::ExprTry"]],["impl Freeze for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl Freeze for ExprTuple",1,["syn::expr::ExprTuple"]],["impl Freeze for ExprType",1,["syn::expr::ExprType"]],["impl Freeze for ExprUnary",1,["syn::expr::ExprUnary"]],["impl Freeze for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl Freeze for ExprWhile",1,["syn::expr::ExprWhile"]],["impl Freeze for ExprYield",1,["syn::expr::ExprYield"]],["impl Freeze for Member",1,["syn::expr::Member"]],["impl Freeze for Index",1,["syn::expr::Index"]],["impl Freeze for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl Freeze for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl Freeze for FieldValue",1,["syn::expr::FieldValue"]],["impl Freeze for Label",1,["syn::expr::Label"]],["impl Freeze for Arm",1,["syn::expr::Arm"]],["impl Freeze for RangeLimits",1,["syn::expr::RangeLimits"]],["impl Freeze for Generics",1,["syn::generics::Generics"]],["impl Freeze for GenericParam",1,["syn::generics::GenericParam"]],["impl Freeze for TypeParam",1,["syn::generics::TypeParam"]],["impl Freeze for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl Freeze for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> Freeze for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> Freeze for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> Freeze for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl Freeze for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl Freeze for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl Freeze for TraitBound",1,["syn::generics::TraitBound"]],["impl Freeze for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl Freeze for WhereClause",1,["syn::generics::WhereClause"]],["impl Freeze for WherePredicate",1,["syn::generics::WherePredicate"]],["impl Freeze for PredicateType",1,["syn::generics::PredicateType"]],["impl Freeze for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl Freeze for PredicateEq",1,["syn::generics::PredicateEq"]],["impl Freeze for Item",1,["syn::item::Item"]],["impl Freeze for ItemConst",1,["syn::item::ItemConst"]],["impl Freeze for ItemEnum",1,["syn::item::ItemEnum"]],["impl Freeze for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl Freeze for ItemFn",1,["syn::item::ItemFn"]],["impl Freeze for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl Freeze for ItemImpl",1,["syn::item::ItemImpl"]],["impl Freeze for ItemMacro",1,["syn::item::ItemMacro"]],["impl Freeze for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl Freeze for ItemMod",1,["syn::item::ItemMod"]],["impl Freeze for ItemStatic",1,["syn::item::ItemStatic"]],["impl Freeze for ItemStruct",1,["syn::item::ItemStruct"]],["impl Freeze for ItemTrait",1,["syn::item::ItemTrait"]],["impl Freeze for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl Freeze for ItemType",1,["syn::item::ItemType"]],["impl Freeze for ItemUnion",1,["syn::item::ItemUnion"]],["impl Freeze for ItemUse",1,["syn::item::ItemUse"]],["impl Freeze for UseTree",1,["syn::item::UseTree"]],["impl Freeze for UsePath",1,["syn::item::UsePath"]],["impl Freeze for UseName",1,["syn::item::UseName"]],["impl Freeze for UseRename",1,["syn::item::UseRename"]],["impl Freeze for UseGlob",1,["syn::item::UseGlob"]],["impl Freeze for UseGroup",1,["syn::item::UseGroup"]],["impl Freeze for ForeignItem",1,["syn::item::ForeignItem"]],["impl Freeze for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl Freeze for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl Freeze for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl Freeze for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl Freeze for TraitItem",1,["syn::item::TraitItem"]],["impl Freeze for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl Freeze for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl Freeze for TraitItemType",1,["syn::item::TraitItemType"]],["impl Freeze for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl Freeze for ImplItem",1,["syn::item::ImplItem"]],["impl Freeze for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl Freeze for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl Freeze for ImplItemType",1,["syn::item::ImplItemType"]],["impl Freeze for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl Freeze for Signature",1,["syn::item::Signature"]],["impl Freeze for FnArg",1,["syn::item::FnArg"]],["impl Freeze for Receiver",1,["syn::item::Receiver"]],["impl Freeze for File",1,["syn::file::File"]],["impl Freeze for Lifetime",1,["syn::lifetime::Lifetime"]],["impl Freeze for Lit",1,["syn::lit::Lit"]],["impl Freeze for LitStr",1,["syn::lit::LitStr"]],["impl Freeze for LitByteStr",1,["syn::lit::LitByteStr"]],["impl Freeze for LitByte",1,["syn::lit::LitByte"]],["impl Freeze for LitChar",1,["syn::lit::LitChar"]],["impl Freeze for LitInt",1,["syn::lit::LitInt"]],["impl Freeze for LitFloat",1,["syn::lit::LitFloat"]],["impl Freeze for LitBool",1,["syn::lit::LitBool"]],["impl Freeze for StrStyle",1,["syn::lit::StrStyle"]],["impl Freeze for Macro",1,["syn::mac::Macro"]],["impl Freeze for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl Freeze for DeriveInput",1,["syn::derive::DeriveInput"]],["impl Freeze for Data",1,["syn::derive::Data"]],["impl Freeze for DataStruct",1,["syn::derive::DataStruct"]],["impl Freeze for DataEnum",1,["syn::derive::DataEnum"]],["impl Freeze for DataUnion",1,["syn::derive::DataUnion"]],["impl Freeze for BinOp",1,["syn::op::BinOp"]],["impl Freeze for UnOp",1,["syn::op::UnOp"]],["impl Freeze for Block",1,["syn::stmt::Block"]],["impl Freeze for Stmt",1,["syn::stmt::Stmt"]],["impl Freeze for Local",1,["syn::stmt::Local"]],["impl Freeze for Type",1,["syn::ty::Type"]],["impl Freeze for TypeArray",1,["syn::ty::TypeArray"]],["impl Freeze for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl Freeze for TypeGroup",1,["syn::ty::TypeGroup"]],["impl Freeze for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl Freeze for TypeInfer",1,["syn::ty::TypeInfer"]],["impl Freeze for TypeMacro",1,["syn::ty::TypeMacro"]],["impl Freeze for TypeNever",1,["syn::ty::TypeNever"]],["impl Freeze for TypeParen",1,["syn::ty::TypeParen"]],["impl Freeze for TypePath",1,["syn::ty::TypePath"]],["impl Freeze for TypePtr",1,["syn::ty::TypePtr"]],["impl Freeze for TypeReference",1,["syn::ty::TypeReference"]],["impl Freeze for TypeSlice",1,["syn::ty::TypeSlice"]],["impl Freeze for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl Freeze for TypeTuple",1,["syn::ty::TypeTuple"]],["impl Freeze for Abi",1,["syn::ty::Abi"]],["impl Freeze for BareFnArg",1,["syn::ty::BareFnArg"]],["impl Freeze for Variadic",1,["syn::ty::Variadic"]],["impl Freeze for ReturnType",1,["syn::ty::ReturnType"]],["impl Freeze for Pat",1,["syn::pat::Pat"]],["impl Freeze for PatBox",1,["syn::pat::PatBox"]],["impl Freeze for PatIdent",1,["syn::pat::PatIdent"]],["impl Freeze for PatLit",1,["syn::pat::PatLit"]],["impl Freeze for PatMacro",1,["syn::pat::PatMacro"]],["impl Freeze for PatOr",1,["syn::pat::PatOr"]],["impl Freeze for PatPath",1,["syn::pat::PatPath"]],["impl Freeze for PatRange",1,["syn::pat::PatRange"]],["impl Freeze for PatReference",1,["syn::pat::PatReference"]],["impl Freeze for PatRest",1,["syn::pat::PatRest"]],["impl Freeze for PatSlice",1,["syn::pat::PatSlice"]],["impl Freeze for PatStruct",1,["syn::pat::PatStruct"]],["impl Freeze for PatTuple",1,["syn::pat::PatTuple"]],["impl Freeze for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl Freeze for PatType",1,["syn::pat::PatType"]],["impl Freeze for PatWild",1,["syn::pat::PatWild"]],["impl Freeze for FieldPat",1,["syn::pat::FieldPat"]],["impl Freeze for Path",1,["syn::path::Path"]],["impl Freeze for PathSegment",1,["syn::path::PathSegment"]],["impl Freeze for PathArguments",1,["syn::path::PathArguments"]],["impl Freeze for GenericArgument",1,["syn::path::GenericArgument"]],["impl Freeze for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl Freeze for Binding",1,["syn::path::Binding"]],["impl Freeze for Constraint",1,["syn::path::Constraint"]],["impl Freeze for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl Freeze for QSelf",1,["syn::path::QSelf"]],["impl Freeze for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> Freeze for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Freeze for Punctuated<T, P>",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Freeze for Pairs<'a, T, P>",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Freeze for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> Freeze for IntoPairs<T, P>where
        T: Freeze,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> Freeze for IntoIter<T>",1,["syn::punctuated::IntoIter"]],["impl<'a, T> Freeze for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> Freeze for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Freeze for Pair<T, P>where
        P: Freeze,
        T: Freeze,
    ",1,["syn::punctuated::Pair"]],["impl<'a> !Freeze for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Freeze for Error",1,["syn::error::Error"]],["impl<'a> !Freeze for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> Freeze for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Freeze for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> Freeze for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Freeze for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Freeze for AbortHandle",1,["tokio::runtime::task::abort::AbortHandle"]],["impl<T> Freeze for JoinHandle<T>",1,["tokio::runtime::task::join::JoinHandle"]],["impl !Freeze for Builder",1,["tokio::runtime::builder::Builder"]],["impl Freeze for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Freeze for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Freeze for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !Freeze for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Freeze for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !Freeze for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !Freeze for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Freeze for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Freeze for SendError<T>where
        T: Freeze,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl Freeze for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Freeze for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Freeze for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Freeze for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Freeze for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Freeze for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Freeze for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Freeze for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Freeze for SendError<T>where
        T: Freeze,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Freeze for TrySendError<T>where
        T: Freeze,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Freeze for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T> !Freeze for Mutex<T>",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T: ?Sized> Freeze for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Freeze for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Freeze for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl Freeze for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl !Freeze for Notify",1,["tokio::sync::notify::Notify"]],["impl Freeze for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Freeze for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl Freeze for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Freeze for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl !Freeze for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Freeze for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Freeze for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T: ?Sized, U: ?Sized> Freeze for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T: ?Sized> Freeze for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T: ?Sized, U: ?Sized> Freeze for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T: ?Sized> Freeze for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T> !Freeze for RwLock<T>",1,["tokio::sync::rwlock::RwLock"]],["impl<T> !Freeze for OnceCell<T>",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> Freeze for SetError<T>where
        T: Freeze,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> Freeze for SendError<T>where
        T: Freeze,
    ",1,["tokio::sync::watch::error::SendError"]],["impl Freeze for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Freeze for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> Freeze for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Freeze for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl !Freeze for LocalSet",1,["tokio::task::local::LocalSet"]],["impl Freeze for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Freeze for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Freeze for TaskLocalFuture<T, F>where
        F: Freeze,
        T: Freeze,
    ",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Freeze for Unconstrained<F>where
        F: Freeze,
    ",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Freeze for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]]], -"typenum":[["impl Freeze for B0",1,["typenum::bit::B0"]],["impl Freeze for B1",1,["typenum::bit::B1"]],["impl<U> Freeze for PInt<U>where
        U: Freeze,
    ",1,["typenum::int::PInt"]],["impl<U> Freeze for NInt<U>where
        U: Freeze,
    ",1,["typenum::int::NInt"]],["impl Freeze for Z0",1,["typenum::int::Z0"]],["impl Freeze for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Freeze for UInt<U, B>where
        B: Freeze,
        U: Freeze,
    ",1,["typenum::uint::UInt"]],["impl Freeze for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Freeze for TArr<V, A>where
        A: Freeze,
        V: Freeze,
    ",1,["typenum::array::TArr"]],["impl Freeze for Greater",1,["typenum::Greater"]],["impl Freeze for Less",1,["typenum::Less"]],["impl Freeze for Equal",1,["typenum::Equal"]]], -"uint":[["impl Freeze for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Freeze for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Freeze for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Freeze for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Send.js b/docs/implementors/core/marker/trait.Send.js deleted file mode 100644 index 222916262f8b..000000000000 --- a/docs/implementors/core/marker/trait.Send.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Send for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Send for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> Send for AhoCorasick<S>where
        S: Send,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Send for FindIter<'a, 'b, S>where
        S: Sync,
    ",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Send for FindOverlappingIter<'a, 'b, S>where
        S: Send + Sync,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Send for StreamFindIter<'a, R, S>where
        R: Send,
        S: Send + Sync,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Send for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Send for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Send for Error",1,["aho_corasick::error::Error"]],["impl Send for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Send for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Send for Config",1,["aho_corasick::packed::api::Config"]],["impl Send for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Send for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Send for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Send for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl Send for Error",1,["aiofut::Error"]],["impl Send for AIO",1,["aiofut::AIO"]],["impl Send for AIOFuture",1,["aiofut::AIOFuture"]],["impl Send for AIONotifier",1,["aiofut::AIONotifier"]],["impl Send for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl Send for AIOManager",1,["aiofut::AIOManager"]],["impl Send for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Send for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl Send for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Send for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Send for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Send for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Send for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Send for Config",1,["bincode::config::legacy::Config"]],["impl Send for Bounded",1,["bincode::config::limit::Bounded"]],["impl Send for Infinite",1,["bincode::config::limit::Infinite"]],["impl Send for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Send for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Send for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Send for WithOtherLimit<O, L>where
        L: Send,
        O: Send,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Send for WithOtherEndian<O, E>where
        E: Send,
        O: Send,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Send for WithOtherIntEncoding<O, I>where
        I: Send,
        O: Send,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Send for WithOtherTrailing<O, T>where
        O: Send,
        T: Send,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Send for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Send for IoReader<R>where
        R: Send,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> Send for Deserializer<R, O>where
        O: Send,
        R: Send,
    ",1,["bincode::de::Deserializer"]],["impl Send for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Send for Serializer<W, O>where
        O: Send,
        W: Send,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl Send for Eager",1,["block_buffer::Eager"]],["impl Send for Lazy",1,["block_buffer::Lazy"]],["impl Send for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Send for BlockBuffer<BlockSize, Kind>where
        Kind: Send,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl Send for BigEndian",1,["byteorder::BigEndian"]],["impl Send for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> Send for Chain<T, U>where
        T: Send,
        U: Send,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> Send for IntoIter<T>where
        T: Send,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> Send for Limit<T>where
        T: Send,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> Send for Reader<B>where
        B: Send,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> Send for Take<T>where
        T: Send,
    ",1,["bytes::buf::take::Take"]],["impl Send for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Send for Writer<B>where
        B: Send,
    ",1,["bytes::buf::writer::Writer"]],["impl Send for Bytes"],["impl Send for BytesMut"]], -"crc":[["impl<W> Send for Crc<W>where
        W: Send + Sync,
    ",1,["crc::Crc"]],["impl<'a, W> Send for Digest<'a, W>where
        W: Send + Sync,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> Send for Algorithm<W>where
        W: Send,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<'a, T> Send for Iter<'a, T>where
        T: Send,
    ",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Send for TryIter<'a, T>where
        T: Send,
    ",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Send for IntoIter<T>where
        T: Send,
    ",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Send for SendError<T>where
        T: Send,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> Send for TrySendError<T>where
        T: Send,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Send for SendTimeoutError<T>where
        T: Send,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Send for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Send for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Send for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Send for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Send for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Send for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Send for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !Send for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T: Send> Send for Sender<T>"],["impl<T: Send> Send for Receiver<T>"],["impl Send for Select<'_>"]], -"crossbeam_utils":[["impl Send for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl<'a, T> !Send for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T> !Send for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Send for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Send for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Send for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<T: Send> Send for AtomicCell<T>"],["impl<T: Send> Send for CachePadded<T>"],["impl Send for Parker"],["impl Send for Unparker"],["impl<T: ?Sized + Send> Send for ShardedLock<T>"],["impl<T> Send for ScopedJoinHandle<'_, T>"]], -"crypto_common":[["impl Send for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> Send for CtVariableCoreWrapper<T, OutSize, O>where
        O: Send,
        OutSize: Send,
        T: Send,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Send for RtVariableCoreWrapper<T>where
        T: Send,
        <T as BufferKindUser>::BufferKind: Send,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Send for CoreWrapper<T>where
        T: Send,
        <T as BufferKindUser>::BufferKind: Send,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Send for XofReaderCoreWrapper<T>where
        T: Send,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Send for TruncSide",1,["digest::core_api::TruncSide"]],["impl Send for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Send for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl Send for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Send for WALConfig",1,["firewood::storage::WALConfig"]],["impl Send for DBError",1,["firewood::db::DBError"]],["impl Send for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Send for DBConfig",1,["firewood::db::DBConfig"]],["impl !Send for DBRev",1,["firewood::db::DBRev"]],["impl !Send for DB",1,["firewood::db::DB"]],["impl<'a> !Send for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !Send for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Send for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Send for Hash",1,["firewood::merkle::Hash"]],["impl Send for PartialPath",1,["firewood::merkle::PartialPath"]],["impl Send for Node",1,["firewood::merkle::Node"]],["impl !Send for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !Send for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !Send for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Send for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Send for Proof",1,["firewood::proof::Proof"]],["impl Send for ProofError",1,["firewood::proof::ProofError"]],["impl Send for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> Send for Sender<T>where
        T: Send,
    ",1,["futures_channel::mpsc::Sender"]],["impl<T> Send for UnboundedSender<T>where
        T: Send,
    ",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Send for Receiver<T>where
        T: Send,
    ",1,["futures_channel::mpsc::Receiver"]],["impl<T> Send for UnboundedReceiver<T>where
        T: Send,
    ",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Send for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Send for TrySendError<T>where
        T: Send,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl Send for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Send for Receiver<T>where
        T: Send,
    ",1,["futures_channel::oneshot::Receiver"]],["impl<T> Send for Sender<T>where
        T: Send,
    ",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Send for Cancellation<'a, T>where
        T: Send,
    ",1,["futures_channel::oneshot::Cancellation"]],["impl Send for Canceled",1,["futures_channel::oneshot::Canceled"]]], -"futures_executor":[["impl !Send for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !Send for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Send for BlockingStream<S>where
        S: Send,
    ",1,["futures_executor::local_pool::BlockingStream"]],["impl Send for Enter",1,["futures_executor::enter::Enter"]],["impl Send for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl Send for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Send for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !Send for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<T> Send for FutureObj<'_, T>"]], -"futures_util":[["impl<Fut> Send for Fuse<Fut>where
        Fut: Send,
    ",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Send for CatchUnwind<Fut>where
        Fut: Send,
    ",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Send for RemoteHandle<T>where
        T: Send,
    ",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Send for Remote<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send,
    ",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Send for Shared<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send + Sync,
    ",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Send for WeakShared<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send + Sync,
    ",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Send for Flatten<F>where
        F: Send,
        <F as Future>::Output: Send,
    ",1,["futures_util::future::future::Flatten"]],["impl<F> Send for FlattenStream<F>where
        F: Send,
        <F as Future>::Output: Send,
    ",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Send for Map<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::future::Map"]],["impl<F> Send for IntoStream<F>where
        F: Send,
    ",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Send for MapInto<Fut, T>where
        Fut: Send,
    ",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Send for Then<Fut1, Fut2, F>where
        F: Send,
        Fut1: Send,
        Fut2: Send,
    ",1,["futures_util::future::future::Then"]],["impl<Fut, F> Send for Inspect<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::future::Inspect"]],["impl<Fut> Send for NeverError<Fut>where
        Fut: Send,
    ",1,["futures_util::future::future::NeverError"]],["impl<Fut> Send for UnitError<Fut>where
        Fut: Send,
    ",1,["futures_util::future::future::UnitError"]],["impl<Fut> Send for IntoFuture<Fut>where
        Fut: Send,
    ",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Send for TryFlatten<Fut1, Fut2>where
        Fut1: Send,
        Fut2: Send,
    ",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Send for TryFlattenStream<Fut>where
        Fut: Send,
        <Fut as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Send for FlattenSink<Fut, Si>where
        Fut: Send,
        Si: Send,
    ",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Send for AndThen<Fut1, Fut2, F>where
        F: Send,
        Fut1: Send,
        Fut2: Send,
    ",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Send for OrElse<Fut1, Fut2, F>where
        F: Send,
        Fut1: Send,
        Fut2: Send,
    ",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Send for ErrInto<Fut, E>where
        Fut: Send,
    ",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Send for OkInto<Fut, E>where
        Fut: Send,
    ",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Send for InspectOk<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Send for InspectErr<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Send for MapOk<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Send for MapErr<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Send for MapOkOrElse<Fut, F, G>where
        F: Send,
        Fut: Send,
        G: Send,
    ",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Send for UnwrapOrElse<Fut, F>where
        F: Send,
        Fut: Send,
    ",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Send for Lazy<F>where
        F: Send,
    ",1,["futures_util::future::lazy::Lazy"]],["impl<T> Send for Pending<T>where
        T: Send,
    ",1,["futures_util::future::pending::Pending"]],["impl<Fut> Send for MaybeDone<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send,
    ",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Send for TryMaybeDone<Fut>where
        Fut: Send,
        <Fut as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Send for OptionFuture<F>where
        F: Send,
    ",1,["futures_util::future::option::OptionFuture"]],["impl<F> Send for PollFn<F>where
        F: Send,
    ",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Send for PollImmediate<T>where
        T: Send,
    ",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Send for Ready<T>where
        T: Send,
    ",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Send for Join<Fut1, Fut2>where
        Fut1: Send,
        Fut2: Send,
        <Fut1 as Future>::Output: Send,
        <Fut2 as Future>::Output: Send,
    ",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Send for Join3<Fut1, Fut2, Fut3>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        <Fut1 as Future>::Output: Send,
        <Fut2 as Future>::Output: Send,
        <Fut3 as Future>::Output: Send,
    ",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Send for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        Fut4: Send,
        <Fut1 as Future>::Output: Send,
        <Fut2 as Future>::Output: Send,
        <Fut3 as Future>::Output: Send,
        <Fut4 as Future>::Output: Send,
    ",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Send for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        Fut4: Send,
        Fut5: Send,
        <Fut1 as Future>::Output: Send,
        <Fut2 as Future>::Output: Send,
        <Fut3 as Future>::Output: Send,
        <Fut4 as Future>::Output: Send,
        <Fut5 as Future>::Output: Send,
    ",1,["futures_util::future::join::Join5"]],["impl<F> Send for JoinAll<F>where
        F: Send,
        <F as Future>::Output: Send,
    ",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Send for Select<A, B>where
        A: Send,
        B: Send,
    ",1,["futures_util::future::select::Select"]],["impl<Fut> Send for SelectAll<Fut>where
        Fut: Send,
    ",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Send for TryJoin<Fut1, Fut2>where
        Fut1: Send,
        Fut2: Send,
        <Fut1 as TryFuture>::Ok: Send,
        <Fut2 as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Send for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        <Fut1 as TryFuture>::Ok: Send,
        <Fut2 as TryFuture>::Ok: Send,
        <Fut3 as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Send for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        Fut4: Send,
        <Fut1 as TryFuture>::Ok: Send,
        <Fut2 as TryFuture>::Ok: Send,
        <Fut3 as TryFuture>::Ok: Send,
        <Fut4 as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Send for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Send,
        Fut2: Send,
        Fut3: Send,
        Fut4: Send,
        Fut5: Send,
        <Fut1 as TryFuture>::Ok: Send,
        <Fut2 as TryFuture>::Ok: Send,
        <Fut3 as TryFuture>::Ok: Send,
        <Fut4 as TryFuture>::Ok: Send,
        <Fut5 as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> Send for TryJoinAll<F>where
        F: Send,
        <F as TryFuture>::Error: Send,
        <F as TryFuture>::Ok: Send,
    ",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Send for TrySelect<A, B>where
        A: Send,
        B: Send,
    ",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Send for SelectOk<Fut>where
        Fut: Send,
    ",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Send for Either<A, B>where
        A: Send,
        B: Send,
    ",1,["futures_util::future::either::Either"]],["impl Send for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Send for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Send for Abortable<T>where
        T: Send,
    ",1,["futures_util::abortable::Abortable"]],["impl Send for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Send for Chain<St1, St2>where
        St1: Send,
        St2: Send,
    ",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Send for Collect<St, C>where
        C: Send,
        St: Send,
    ",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Send for Unzip<St, FromA, FromB>where
        FromA: Send,
        FromB: Send,
        St: Send,
    ",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Send for Concat<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Send for Cycle<St>where
        St: Send,
    ",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Send for Enumerate<St>where
        St: Send,
    ",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Send for Filter<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Send for FilterMap<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Send for Fold<St, Fut, T, F>where
        F: Send,
        Fut: Send,
        St: Send,
        T: Send,
    ",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Send for ForEach<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Send for Fuse<St>where
        St: Send,
    ",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Send for StreamFuture<St>where
        St: Send,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Send for Map<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Send for Next<'a, St>where
        St: Send,
    ",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Send for SelectNextSome<'a, St>where
        St: Send,
    ",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Send for Peekable<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Send for Peek<'a, St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Send for PeekMut<'a, St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Send for NextIf<'a, St, F>where
        F: Send,
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Send for NextIfEq<'a, St, T>where
        St: Send,
        T: Sync,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Send for Skip<St>where
        St: Send,
    ",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Send for SkipWhile<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Send for Take<St>where
        St: Send,
    ",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Send for TakeWhile<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Send for TakeUntil<St, Fut>where
        Fut: Send,
        St: Send,
        <Fut as Future>::Output: Send,
    ",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Send for Then<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Send for Zip<St1, St2>where
        St1: Send,
        St2: Send,
        <St1 as Stream>::Item: Send,
        <St2 as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Send for Chunks<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Send for ReadyChunks<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Send for Scan<St, S, Fut, F>where
        F: Send,
        Fut: Send,
        S: Send,
        St: Send,
    ",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> Send for BufferUnordered<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> Send for Buffered<St>where
        St: Send,
        <St as Stream>::Item: Send,
        <<St as Stream>::Item as Future>::Output: Send,
    ",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> Send for ForEachConcurrent<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Send for SplitStream<S>where
        S: Send,
    ",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Send for SplitSink<S, Item>where
        Item: Send,
        S: Send,
    ",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Send for ReuniteError<T, Item>where
        Item: Send,
        T: Send,
    ",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Send for CatchUnwind<St>where
        St: Send,
    ",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Send for Flatten<St>where
        St: Send,
        <St as Stream>::Item: Send,
    ",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Send for Forward<St, Si>where
        Si: Send,
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Send for Inspect<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Send for FlatMap<St, U, F>where
        F: Send,
        St: Send,
        U: Send,
    ",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Send for AndThen<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Send for IntoStream<St>where
        St: Send,
    ",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Send for OrElse<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Send for TryNext<'a, St>where
        St: Send,
    ",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Send for TryForEach<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Send for TryFilter<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Send for TryFilterMap<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Send for TryFlatten<St>where
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Send for TryCollect<St, C>where
        C: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Send for TryConcat<St>where
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Send for TryChunks<St>where
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Send for TryChunksError<T, E>where
        E: Send,
        T: Send,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Send for TryFold<St, Fut, T, F>where
        F: Send,
        Fut: Send,
        St: Send,
        T: Send,
    ",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Send for TryUnfold<T, F, Fut>where
        F: Send,
        Fut: Send,
        T: Send,
    ",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Send for TrySkipWhile<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Send for TryTakeWhile<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> Send for TryBufferUnordered<St>where
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> Send for TryBuffered<St>where
        St: Send,
        <<St as TryStream>::Ok as TryFuture>::Error: Send,
        <St as TryStream>::Ok: Send,
        <<St as TryStream>::Ok as TryFuture>::Ok: Send,
    ",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> Send for TryForEachConcurrent<St, Fut, F>where
        F: Send,
        Fut: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Send for IntoAsyncRead<St>where
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Send for ErrInto<St, E>where
        St: Send,
    ",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Send for InspectOk<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Send for InspectErr<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Send for MapOk<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Send for MapErr<St, F>where
        F: Send,
        St: Send,
    ",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Send for Iter<I>where
        I: Send,
    ",1,["futures_util::stream::iter::Iter"]],["impl<T> Send for Repeat<T>where
        T: Send,
    ",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Send for RepeatWith<F>where
        F: Send,
    ",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Send for Empty<T>where
        T: Send,
    ",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Send for Once<Fut>where
        Fut: Send,
    ",1,["futures_util::stream::once::Once"]],["impl<T> Send for Pending<T>where
        T: Send,
    ",1,["futures_util::stream::pending::Pending"]],["impl<F> Send for PollFn<F>where
        F: Send,
    ",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Send for PollImmediate<S>where
        S: Send,
    ",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Send for Select<St1, St2>where
        St1: Send,
        St2: Send,
    ",1,["futures_util::stream::select::Select"]],["impl Send for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Send for SelectWithStrategy<St1, St2, Clos, State>where
        Clos: Send,
        St1: Send,
        St2: Send,
        State: Send,
    ",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Send for Unfold<T, F, Fut>where
        F: Send,
        Fut: Send,
        T: Send,
    ",1,["futures_util::stream::unfold::Unfold"]],["impl<T> Send for FuturesOrdered<T>where
        T: Send,
        <T as Future>::Output: Send,
    ",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Send for IterMut<'a, Fut>where
        Fut: Send,
    ",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Send for Iter<'a, Fut>where
        Fut: Send,
    ",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<St> Send for SelectAll<St>where
        St: Send,
    ",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Send for Iter<'a, St>where
        St: Send,
    ",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Send for IterMut<'a, St>where
        St: Send,
    ",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Send for IntoIter<St>where
        St: Send,
    ",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Send for Close<'a, Si, Item>where
        Si: Send,
    ",1,["futures_util::sink::close::Close"]],["impl<T> Send for Drain<T>where
        T: Send,
    ",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Send for Fanout<Si1, Si2>where
        Si1: Send,
        Si2: Send,
    ",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Send for Feed<'a, Si, Item>where
        Item: Send,
        Si: Send,
    ",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Send for Flush<'a, Si, Item>where
        Si: Send,
    ",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Send for SinkErrInto<Si, Item, E>where
        Si: Send,
    ",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Send for SinkMapErr<Si, F>where
        F: Send,
        Si: Send,
    ",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Send for Send<'a, Si, Item>where
        Item: Send,
        Si: Send,
    ",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Send for SendAll<'a, Si, St>where
        Si: Send,
        St: Send,
        <St as TryStream>::Ok: Send,
    ",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Send for Unfold<T, F, R>where
        F: Send,
        R: Send,
        T: Send,
    ",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Send for With<Si, Item, U, Fut, F>where
        F: Send,
        Fut: Send,
        Si: Send,
    ",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Send for WithFlatMap<Si, Item, U, St, F>where
        F: Send,
        Item: Send,
        Si: Send,
        St: Send,
    ",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Send for Buffer<Si, Item>where
        Item: Send,
        Si: Send,
    ",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Send for AllowStdIo<T>where
        T: Send,
    ",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Send for BufReader<R>where
        R: Send,
    ",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Send for SeeKRelative<'a, R>where
        R: Send,
    ",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Send for BufWriter<W>where
        W: Send,
    ",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Send for LineWriter<W>where
        W: Send,
    ",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Send for Chain<T, U>where
        T: Send,
        U: Send,
    ",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Send for Close<'a, W>where
        W: Send,
    ",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Send for Copy<'a, R, W>where
        R: Send,
        W: Send,
    ",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Send for CopyBuf<'a, R, W>where
        R: Send,
        W: Send,
    ",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Send for CopyBufAbortable<'a, R, W>where
        R: Send,
        W: Send,
    ",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Send for Cursor<T>where
        T: Send,
    ",1,["futures_util::io::cursor::Cursor"]],["impl Send for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Send for FillBuf<'a, R>where
        R: Send,
    ",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Send for Flush<'a, W>where
        W: Send,
    ",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Send for IntoSink<W, Item>where
        Item: Send,
        W: Send,
    ",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Send for Lines<R>where
        R: Send,
    ",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Send for Read<'a, R>where
        R: Send,
    ",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Send for ReadVectored<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Send for ReadExact<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Send for ReadLine<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Send for ReadToEnd<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Send for ReadToString<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Send for ReadUntil<'a, R>where
        R: Send,
    ",1,["futures_util::io::read_until::ReadUntil"]],["impl Send for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Send for Seek<'a, S>where
        S: Send,
    ",1,["futures_util::io::seek::Seek"]],["impl Send for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Send for ReadHalf<T>where
        T: Send,
    ",1,["futures_util::io::split::ReadHalf"]],["impl<T> Send for WriteHalf<T>where
        T: Send,
    ",1,["futures_util::io::split::WriteHalf"]],["impl<T> Send for ReuniteError<T>where
        T: Send,
    ",1,["futures_util::io::split::ReuniteError"]],["impl<R> Send for Take<R>where
        R: Send,
    ",1,["futures_util::io::take::Take"]],["impl<T> Send for Window<T>where
        T: Send,
    ",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Send for Write<'a, W>where
        W: Send,
    ",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Send for WriteVectored<'a, W>where
        W: Send,
    ",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Send for WriteAll<'a, W>where
        W: Send,
    ",1,["futures_util::io::write_all::WriteAll"]],["impl<Fut: Send> Send for IterPinRef<'_, Fut>"],["impl<Fut: Send> Send for IterPinMut<'_, Fut>"],["impl<Fut: Send + Unpin> Send for IntoIter<Fut>"],["impl<Fut: Send> Send for FuturesUnordered<Fut>"],["impl<T: ?Sized + Send> Send for Mutex<T>"],["impl<T: ?Sized + Send> Send for MutexLockFuture<'_, T>"],["impl<T: ?Sized + Send> Send for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Send> Send for MutexGuard<'_, T>"],["impl<T: ?Sized + Send> Send for OwnedMutexGuard<T>"],["impl<T: ?Sized + Send, U: ?Sized + Send> Send for MappedMutexGuard<'_, T, U>"]], -"generic_array":[["impl<T, N> Send for GenericArrayIter<T, N>where
        T: Send,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T: Send, N: ArrayLength<T>> Send for GenericArray<T, N>"]], -"getrandom":[["impl Send for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl Send for WALRingId",1,["growthring::wal::WALRingId"]],["impl Send for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Send for WALLoader",1,["growthring::wal::WALLoader"]],["impl !Send for WALFileAIO",1,["growthring::WALFileAIO"]],["impl<F> Send for WALWriter<F>where
        F: WALStore + Send,
    "],["impl Send for WALStoreAIO"]], -"hashbrown":[["impl<K, V, S, A> Send for HashMap<K, V, S, A>where
        A: Send,
        K: Send,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Send for Iter<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Iter"]],["impl<K, V, A> Send for IntoIter<K, V, A>where
        A: Send,
        K: Send,
        V: Send,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Send for IntoKeys<K, V, A>where
        A: Send,
        K: Send,
        V: Send,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Send for IntoValues<K, V, A>where
        A: Send,
        K: Send,
        V: Send,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Send for Keys<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Send for Values<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Send for Drain<'a, K, V, A>where
        A: Send + Copy,
        K: Send,
        V: Send,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Send for DrainFilter<'a, K, V, F, A>where
        A: Send,
        F: Send,
        K: Send,
        V: Send,
    ",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Send for ValuesMut<'a, K, V>where
        K: Send,
        V: Send,
    ",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Send for RawEntryBuilderMut<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Send for RawEntryMut<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Send + Sync,
        V: Send,
    ",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Send for RawVacantEntryMut<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Sync,
        V: Send,
    ",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Send for RawEntryBuilder<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Send for Entry<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Send for VacantEntry<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Send for EntryRef<'a, 'b, K, Q, V, S, A>where
        A: Send,
        K: Send,
        Q: Sync,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Send for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
        A: Send,
        K: Send,
        Q: Sync,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Send for OccupiedError<'a, K, V, S, A>where
        A: Send,
        K: Send,
        S: Send,
        V: Send,
    ",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Send for HashSet<T, S, A>where
        A: Send,
        S: Send,
        T: Send,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> Send for Iter<'a, K>where
        K: Sync,
    ",1,["hashbrown::set::Iter"]],["impl<K, A> Send for IntoIter<K, A>where
        A: Send,
        K: Send,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Send for Drain<'a, K, A>where
        A: Send + Copy,
        K: Send,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Send for DrainFilter<'a, K, F, A>where
        A: Send,
        F: Send,
        K: Send,
    ",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Send for Intersection<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Send for Difference<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Send for SymmetricDifference<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Send for Union<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Send for Entry<'a, T, S, A>where
        A: Send,
        S: Send,
        T: Send,
    ",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Send for OccupiedEntry<'a, T, S, A>where
        A: Send,
        S: Send,
        T: Send,
    ",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Send for VacantEntry<'a, T, S, A>where
        A: Send,
        S: Send,
        T: Send,
    ",1,["hashbrown::set::VacantEntry"]],["impl Send for TryReserveError",1,["hashbrown::TryReserveError"]],["impl<K: Send, V: Send> Send for IterMut<'_, K, V>"],["impl<K, V, S, A> Send for RawOccupiedEntryMut<'_, K, V, S, A>where
        K: Send,
        V: Send,
        S: Send,
        A: Send + Allocator + Clone,
    "],["impl<K, V, S, A> Send for OccupiedEntry<'_, K, V, S, A>where
        K: Send,
        V: Send,
        S: Send,
        A: Send + Allocator + Clone,
    "],["impl<'a, 'b, K, Q, V, S, A> Send for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Send,
        Q: Sync + ?Sized,
        V: Send,
        S: Send,
        A: Send + Allocator + Clone,
    "]], -"heck":[["impl<T> Send for AsKebabCase<T>where
        T: Send,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> Send for AsLowerCamelCase<T>where
        T: Send,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Send for AsShoutyKebabCase<T>where
        T: Send,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Send for AsShoutySnakeCase<T>where
        T: Send,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Send for AsSnakeCase<T>where
        T: Send,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> Send for AsTitleCase<T>where
        T: Send,
    ",1,["heck::title::AsTitleCase"]],["impl<T> Send for AsUpperCamelCase<T>where
        T: Send,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl Send for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl Send for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Send for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Send for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Send for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Send for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Send for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Send for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Send for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl !Send for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Send for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Send for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Send for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Send for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Send for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Send for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Send for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Send for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Send for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl !Send for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl !Send for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Send for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Send for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Send for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Send for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Send for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl !Send for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Send for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Send for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Send for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Send for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Send for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Send for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Send for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl !Send for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Send for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Send for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl !Send for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl !Send for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Send for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Send for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Send for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Send for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Send for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Send for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Send for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl !Send for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Send for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Send for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl !Send for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Send for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Send for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Send for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Send for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Send for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Send for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Send for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Send for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Send for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Send for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Send for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Send for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Send for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Send for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl !Send for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl !Send for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl !Send for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Send for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Send for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Send for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Send for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Send for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Send for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl !Send for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Send for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Send for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Send for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Send for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Send for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Send for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Send for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Send for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Send for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Send for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Send for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Send for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Send for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl !Send for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Send for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Send for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Send for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Send for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Send for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl !Send for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Send for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Send for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Send for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Send for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Send for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Send for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Send for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Send for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Send for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl !Send for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl !Send for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Send for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Send for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Send for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Send for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Send for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Send for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Send for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Send for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Send for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Send for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Send for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Send for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Send for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Send for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl !Send for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Send for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Send for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Send for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Send for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Send for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Send for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Send for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Send for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Send for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Send for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Send for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Send for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Send for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Send for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Send for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl !Send for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl !Send for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Send for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Send for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Send for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Send for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Send for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Send for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Send for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Send for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Send for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Send for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Send for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Send for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Send for timezone",1,["libc::unix::linux_like::timezone"]],["impl Send for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Send for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Send for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Send for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Send for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Send for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Send for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl !Send for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Send for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Send for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl !Send for tm",1,["libc::unix::linux_like::tm"]],["impl Send for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl !Send for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl !Send for lconv",1,["libc::unix::linux_like::lconv"]],["impl Send for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl !Send for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Send for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Send for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Send for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Send for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl !Send for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Send for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Send for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Send for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Send for utsname",1,["libc::unix::linux_like::utsname"]],["impl !Send for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Send for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Send for DIR",1,["libc::unix::DIR"]],["impl !Send for group",1,["libc::unix::group"]],["impl Send for utimbuf",1,["libc::unix::utimbuf"]],["impl Send for timeval",1,["libc::unix::timeval"]],["impl Send for timespec",1,["libc::unix::timespec"]],["impl Send for rlimit",1,["libc::unix::rlimit"]],["impl Send for rusage",1,["libc::unix::rusage"]],["impl Send for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl !Send for hostent",1,["libc::unix::hostent"]],["impl !Send for iovec",1,["libc::unix::iovec"]],["impl Send for pollfd",1,["libc::unix::pollfd"]],["impl Send for winsize",1,["libc::unix::winsize"]],["impl Send for linger",1,["libc::unix::linger"]],["impl !Send for sigval",1,["libc::unix::sigval"]],["impl Send for itimerval",1,["libc::unix::itimerval"]],["impl Send for tms",1,["libc::unix::tms"]],["impl !Send for servent",1,["libc::unix::servent"]],["impl !Send for protoent",1,["libc::unix::protoent"]],["impl Send for FILE",1,["libc::unix::FILE"]],["impl Send for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<'a, R, T: ?Sized> Send for MutexGuard<'a, R, T>where
        R: Sync,
        T: Send,
        <R as RawMutex>::GuardMarker: Send,
    ",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, G, T> !Send for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !Send for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockReadGuard<'a, R, T>where
        R: Sync,
        T: Send + Sync,
        <R as RawRwLock>::GuardMarker: Send,
    ",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockWriteGuard<'a, R, T>where
        R: Sync,
        T: Send + Sync,
        <R as RawRwLock>::GuardMarker: Send,
    ",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Send for RwLockUpgradableReadGuard<'a, R, T>where
        R: Sync,
        T: Send + Sync,
        <R as RawRwLock>::GuardMarker: Send,
    ",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl Send for GuardSend",1,["lock_api::GuardSend"]],["impl !Send for GuardNoSend",1,["lock_api::GuardNoSend"]],["impl<R: RawMutex + Send, T: ?Sized + Send> Send for Mutex<R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + Send + 'a> Send for MappedMutexGuard<'a, R, T>where
        R::GuardMarker: Send,
    "],["impl<R: RawMutex + Send, G: GetThreadId + Send> Send for RawReentrantMutex<R, G>"],["impl<R: RawMutex + Send, G: GetThreadId + Send, T: ?Sized + Send> Send for ReentrantMutex<R, G, T>"],["impl<R: RawRwLock + Send, T: ?Sized + Send> Send for RwLock<R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Send for MappedRwLockReadGuard<'a, R, T>where
        R::GuardMarker: Send,
    "],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Send + 'a> Send for MappedRwLockWriteGuard<'a, R, T>where
        R::GuardMarker: Send,
    "]], -"lru":[["impl<K, V> Send for IntoIter<K, V>where
        K: Send,
        V: Send,
    ",1,["lru::IntoIter"]],["impl<K: Send, V: Send, S: Send> Send for LruCache<K, V, S>"],["impl<'a, K: Send, V: Send> Send for Iter<'a, K, V>"],["impl<'a, K: Send, V: Send> Send for IterMut<'a, K, V>"]], -"memchr":[["impl<'a> Send for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Send for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Send for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Send for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Send for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Send for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Send for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Send for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Send for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl<'d> Send for Iter<'d>",1,["nix::dir::Iter"]],["impl Send for OwningIter",1,["nix::dir::OwningIter"]],["impl Send for Entry",1,["nix::dir::Entry"]],["impl Send for Type",1,["nix::dir::Type"]],["impl Send for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Send for Errno",1,["nix::errno::consts::Errno"]],["impl Send for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Send for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Send for OFlag",1,["nix::fcntl::OFlag"]],["impl Send for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Send for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Send for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Send for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Send for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Send for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Send for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Send for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl !Send for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl !Send for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl !Send for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> !Send for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Send for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Send for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Send for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Send for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Send for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Send for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Send for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Send for MqdT",1,["nix::mqueue::MqdT"]],["impl Send for PollFd",1,["nix::poll::PollFd"]],["impl Send for PollFlags",1,["nix::poll::PollFlags"]],["impl Send for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Send for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Send for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Send for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Send for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Send for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Send for LioMode",1,["nix::sys::aio::LioMode"]],["impl Send for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Send for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Send for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Send for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Send for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Send for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Send for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Send for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Send for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Send for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Send for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Send for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Send for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Send for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Send for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Send for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Send for Persona",1,["nix::sys::personality::Persona"]],["impl Send for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Send for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Send for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Send for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Send for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Send for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Send for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Send for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Send for Resource",1,["nix::sys::resource::Resource"]],["impl Send for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Send for Usage",1,["nix::sys::resource::Usage"]],["impl Send for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Send for Fds<'a>",1,["nix::sys::select::Fds"]],["impl !Send for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Send for Signal",1,["nix::sys::signal::Signal"]],["impl Send for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Send for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Send for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Send for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Send for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Send for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Send for SigAction",1,["nix::sys::signal::SigAction"]],["impl Send for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Send for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Send for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Send for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Send for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Send for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Send for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Send for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Send for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Send for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Send for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Send for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Send for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Send for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Send for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Send for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Send for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Send for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Send for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Send for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Send for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Send for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Send for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Send for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Send for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Send for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Send for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Send for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Send for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Send for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Send for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Send for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Send for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Send for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Send for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Send for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Send for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Send for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Send for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Send for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Send for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Send for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Send for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Send for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Send for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Send for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Send for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Send for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Send for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Send for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Send for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Send for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Send for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Send for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Send for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Send for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Send for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Send for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Send for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Send for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Send for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Send for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Send for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Send for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Send for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Send for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Send for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Send for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Send for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Send for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Send for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Send for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Send for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Send for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Send for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Send for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Send for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Send for AlgSetKey<T>where
        T: Send,
    ",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Send for SockType",1,["nix::sys::socket::SockType"]],["impl Send for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Send for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Send for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Send for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Send for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Send for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Send for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> !Send for RecvMsg<'a, 's, S>",1,["nix::sys::socket::RecvMsg"]],["impl<'a> !Send for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Send for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Send for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Send for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> !Send for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> !Send for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Send for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Send for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Send for SFlag",1,["nix::sys::stat::SFlag"]],["impl Send for Mode",1,["nix::sys::stat::Mode"]],["impl Send for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Send for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Send for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Send for FsType",1,["nix::sys::statfs::FsType"]],["impl Send for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Send for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Send for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl Send for Termios",1,["nix::sys::termios::Termios"]],["impl Send for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Send for SetArg",1,["nix::sys::termios::SetArg"]],["impl Send for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Send for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Send for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Send for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Send for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Send for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Send for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Send for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Send for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Send for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Send for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Send for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl Send for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Send for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Send for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Send for Id",1,["nix::sys::wait::Id"]],["impl Send for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Send for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Send for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Send for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Send for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Send for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Send for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Send for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl !Send for Timer",1,["nix::sys::timer::Timer"]],["impl Send for ClockId",1,["nix::time::ClockId"]],["impl !Send for UContext",1,["nix::ucontext::UContext"]],["impl Send for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Send for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Send for Uid",1,["nix::unistd::Uid"]],["impl Send for Gid",1,["nix::unistd::Gid"]],["impl Send for Pid",1,["nix::unistd::Pid"]],["impl Send for ForkResult",1,["nix::unistd::ForkResult"]],["impl Send for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Send for Whence",1,["nix::unistd::Whence"]],["impl Send for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Send for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Send for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Send for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Send for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Send for User",1,["nix::unistd::User"]],["impl Send for Group",1,["nix::unistd::Group"]],["impl Send for Dir"],["impl<T> Send for IoVec<T>where
        T: Send,
    "]], -"once_cell":[["impl<T> Send for OnceCell<T>where
        T: Send,
    ",1,["once_cell::unsync::OnceCell"]],["impl<T, F> Send for Lazy<T, F>where
        F: Send,
        T: Send,
    ",1,["once_cell::unsync::Lazy"]],["impl<T> Send for OnceCell<T>where
        T: Send,
    ",1,["once_cell::sync::OnceCell"]],["impl<T, F> Send for Lazy<T, F>where
        F: Send,
        T: Send,
    ",1,["once_cell::sync::Lazy"]],["impl<T> Send for OnceBox<T>where
        T: Send,
    ",1,["once_cell::race::once_box::OnceBox"]],["impl Send for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Send for OnceBool",1,["once_cell::race::OnceBool"]]], -"parking_lot":[["impl Send for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Send for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Send for OnceState",1,["parking_lot::once::OnceState"]],["impl Send for Once",1,["parking_lot::once::Once"]],["impl Send for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Send for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Send for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Send for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl Send for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Send for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Send for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Send for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Send for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Send for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Send for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl Send for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Send for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Send for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Send for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Send for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Send for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Send for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Send for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Send for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Send for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Send for SseMachine<S3, S4, NI>where
        NI: Send,
        S3: Send,
        S4: Send,
    ",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Send for Avx2Machine<NI>where
        NI: Send,
    ",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Send for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Send for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Send for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl Send for Error",1,["primitive_types::Error"]],["impl Send for U128",1,["primitive_types::U128"]],["impl Send for U256",1,["primitive_types::U256"]],["impl Send for U512",1,["primitive_types::U512"]],["impl Send for H128",1,["primitive_types::H128"]],["impl Send for H160",1,["primitive_types::H160"]],["impl Send for H256",1,["primitive_types::H256"]],["impl Send for H384",1,["primitive_types::H384"]],["impl Send for H512",1,["primitive_types::H512"]],["impl Send for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl !Send for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl !Send for TokenStream",1,["proc_macro2::TokenStream"]],["impl !Send for LexError",1,["proc_macro2::LexError"]],["impl !Send for Span",1,["proc_macro2::Span"]],["impl !Send for TokenTree",1,["proc_macro2::TokenTree"]],["impl !Send for Group",1,["proc_macro2::Group"]],["impl Send for Delimiter",1,["proc_macro2::Delimiter"]],["impl !Send for Punct",1,["proc_macro2::Punct"]],["impl Send for Spacing",1,["proc_macro2::Spacing"]],["impl !Send for Ident",1,["proc_macro2::Ident"]],["impl !Send for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl Send for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Send for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Send for DistIter<D, R, T>where
        D: Send,
        R: Send,
        T: Send,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Send for DistMap<D, F, T, S>where
        D: Send,
        F: Send,
    ",1,["rand::distributions::distribution::DistMap"]],["impl Send for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Send for Open01",1,["rand::distributions::float::Open01"]],["impl Send for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Send for Slice<'a, T>where
        T: Sync,
    ",1,["rand::distributions::slice::Slice"]],["impl<X> Send for WeightedIndex<X>where
        X: Send,
        <X as SampleUniform>::Sampler: Send,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Send for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Send for Uniform<X>where
        <X as SampleUniform>::Sampler: Send,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> Send for UniformInt<X>where
        X: Send,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl Send for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Send for UniformFloat<X>where
        X: Send,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl Send for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Send for WeightedIndex<W>where
        W: Send,
    ",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Send for Standard",1,["rand::distributions::Standard"]],["impl<R> Send for ReadRng<R>where
        R: Send,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl Send for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Send for ReseedingRng<R, Rsdr>where
        R: Send,
        Rsdr: Send,
        <R as BlockRngCore>::Results: Send,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Send for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Send for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Send for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Send for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Send for SliceChooseIter<'a, S, T>where
        S: Sync,
        T: Send,
    ",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl Send for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Send for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Send for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Send for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Send for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Send for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> Send for BlockRng<R>where
        R: Send,
        <R as BlockRngCore>::Results: Send,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Send for BlockRng64<R>where
        R: Send,
        <R as BlockRngCore>::Results: Send,
    ",1,["rand_core::block::BlockRng64"]],["impl Send for Error",1,["rand_core::error::Error"]],["impl Send for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl Send for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Send for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Send for Match<'t>",1,["regex::re_bytes::Match"]],["impl Send for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Send for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Send for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Send for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Send for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Send for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Send for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Send for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Send for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Send for ReplacerRef<'a, R>where
        R: Send,
    ",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Send for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Send for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Send for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Send for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Send for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Send for Error",1,["regex::error::Error"]],["impl Send for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Send for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Send for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Send for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Send for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Send for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Send for Match<'t>",1,["regex::re_unicode::Match"]],["impl Send for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Send for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Send for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Send for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Send for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Send for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Send for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Send for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Send for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Send for ReplacerRef<'a, R>where
        R: Send,
    ",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Send for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl Send for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl Send for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Send for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Send for Error",1,["regex_syntax::ast::Error"]],["impl Send for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Send for Span",1,["regex_syntax::ast::Span"]],["impl Send for Position",1,["regex_syntax::ast::Position"]],["impl Send for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Send for Comment",1,["regex_syntax::ast::Comment"]],["impl Send for Ast",1,["regex_syntax::ast::Ast"]],["impl Send for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Send for Concat",1,["regex_syntax::ast::Concat"]],["impl Send for Literal",1,["regex_syntax::ast::Literal"]],["impl Send for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Send for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Send for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Send for Class",1,["regex_syntax::ast::Class"]],["impl Send for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Send for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Send for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Send for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Send for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Send for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Send for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Send for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Send for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Send for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Send for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Send for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Send for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Send for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Send for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Send for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Send for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Send for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Send for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Send for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Send for Group",1,["regex_syntax::ast::Group"]],["impl Send for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Send for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Send for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Send for Flags",1,["regex_syntax::ast::Flags"]],["impl Send for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Send for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Send for Flag",1,["regex_syntax::ast::Flag"]],["impl Send for Error",1,["regex_syntax::error::Error"]],["impl Send for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Send for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Send for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Send for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl Send for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Send for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Send for Error",1,["regex_syntax::hir::Error"]],["impl Send for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Send for Hir",1,["regex_syntax::hir::Hir"]],["impl Send for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Send for Literal",1,["regex_syntax::hir::Literal"]],["impl Send for Class",1,["regex_syntax::hir::Class"]],["impl Send for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Send for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Send for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Send for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Send for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Send for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Send for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Send for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Send for Group",1,["regex_syntax::hir::Group"]],["impl Send for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Send for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Send for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Send for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Send for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl Send for Parser",1,["regex_syntax::parser::Parser"]],["impl Send for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Send for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Send for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Send for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl Send for DecoderError",1,["rlp::error::DecoderError"]],["impl Send for Prototype",1,["rlp::rlpin::Prototype"]],["impl Send for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> Send for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !Send for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Send for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> Send for ToHexIter<T>where
        T: Send,
    ",1,["rustc_hex::ToHexIter"]],["impl Send for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Send for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl Send for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl Send for Always",1,["scopeguard::Always"]],["impl<T, F, S> Send for ScopeGuard<T, F, S>where
        F: Send,
        T: Send,
    ",1,["scopeguard::ScopeGuard"]]], -"serde":[["impl Send for Error",1,["serde::de::value::Error"]],["impl<E> Send for UnitDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::UnitDeserializer"]],["impl<E> Send for BoolDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::BoolDeserializer"]],["impl<E> Send for I8Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::I8Deserializer"]],["impl<E> Send for I16Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::I16Deserializer"]],["impl<E> Send for I32Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::I32Deserializer"]],["impl<E> Send for I64Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::I64Deserializer"]],["impl<E> Send for IsizeDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Send for U8Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::U8Deserializer"]],["impl<E> Send for U16Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::U16Deserializer"]],["impl<E> Send for U64Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::U64Deserializer"]],["impl<E> Send for UsizeDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Send for F32Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::F32Deserializer"]],["impl<E> Send for F64Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::F64Deserializer"]],["impl<E> Send for CharDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::CharDeserializer"]],["impl<E> Send for I128Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::I128Deserializer"]],["impl<E> Send for U128Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::U128Deserializer"]],["impl<E> Send for U32Deserializer<E>where
        E: Send,
    ",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Send for StrDeserializer<'a, E>where
        E: Send,
    ",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Send for BorrowedStrDeserializer<'de, E>where
        E: Send,
    ",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Send for StringDeserializer<E>where
        E: Send,
    ",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Send for CowStrDeserializer<'a, E>where
        E: Send,
    ",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Send for BytesDeserializer<'a, E>where
        E: Send,
    ",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Send for BorrowedBytesDeserializer<'de, E>where
        E: Send,
    ",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Send for SeqDeserializer<I, E>where
        E: Send,
        I: Send,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> Send for SeqAccessDeserializer<A>where
        A: Send,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Send for MapDeserializer<'de, I, E>where
        E: Send,
        I: Send,
        <<I as Iterator>::Item as Pair>::Second: Send,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> Send for MapAccessDeserializer<A>where
        A: Send,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Send for EnumAccessDeserializer<A>where
        A: Send,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl Send for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Send for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Send for Impossible<Ok, Error>where
        Error: Send,
        Ok: Send,
    ",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl Send for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Send for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Send for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Send for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Send for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Send for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Send for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Send for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Send for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Send for Shake128Core",1,["sha3::Shake128Core"]],["impl Send for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Send for Shake256Core",1,["sha3::Shake256Core"]],["impl Send for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Send for CShake128Core",1,["sha3::CShake128Core"]],["impl Send for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Send for CShake256Core",1,["sha3::CShake256Core"]],["impl Send for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl Send for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Send for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Send for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Send for ShaleError",1,["shale::ShaleError"]],["impl Send for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Send for ObjPtr<T>where
        T: Send,
    ",1,["shale::ObjPtr"]],["impl<T> !Send for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !Send for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !Send for MummyObj<T>",1,["shale::MummyObj"]],["impl !Send for PlainMem",1,["shale::PlainMem"]],["impl<T> !Send for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> Send for Slab<T>where
        T: Send,
    ",1,["slab::Slab"]],["impl<'a, T> Send for VacantEntry<'a, T>where
        T: Send,
    ",1,["slab::VacantEntry"]],["impl<T> Send for IntoIter<T>where
        T: Send,
    ",1,["slab::IntoIter"]],["impl<'a, T> Send for Iter<'a, T>where
        T: Sync,
    ",1,["slab::Iter"]],["impl<'a, T> Send for IterMut<'a, T>where
        T: Send,
    ",1,["slab::IterMut"]],["impl<'a, T> Send for Drain<'a, T>where
        T: Send,
    ",1,["slab::Drain"]]], -"smallvec":[["impl Send for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<A> Send for IntoIter<A>where
        <A as Array>::Item: Send,
    ",1,["smallvec::IntoIter"]],["impl<'a, T: Send + Array> Send for Drain<'a, T>"],["impl<A: Array> Send for SmallVec<A>where
        A::Item: Send,
    "]], -"syn":[["impl !Send for Underscore",1,["syn::token::Underscore"]],["impl !Send for Abstract",1,["syn::token::Abstract"]],["impl !Send for As",1,["syn::token::As"]],["impl !Send for Async",1,["syn::token::Async"]],["impl !Send for Auto",1,["syn::token::Auto"]],["impl !Send for Await",1,["syn::token::Await"]],["impl !Send for Become",1,["syn::token::Become"]],["impl !Send for Box",1,["syn::token::Box"]],["impl !Send for Break",1,["syn::token::Break"]],["impl !Send for Const",1,["syn::token::Const"]],["impl !Send for Continue",1,["syn::token::Continue"]],["impl !Send for Crate",1,["syn::token::Crate"]],["impl !Send for Default",1,["syn::token::Default"]],["impl !Send for Do",1,["syn::token::Do"]],["impl !Send for Dyn",1,["syn::token::Dyn"]],["impl !Send for Else",1,["syn::token::Else"]],["impl !Send for Enum",1,["syn::token::Enum"]],["impl !Send for Extern",1,["syn::token::Extern"]],["impl !Send for Final",1,["syn::token::Final"]],["impl !Send for Fn",1,["syn::token::Fn"]],["impl !Send for For",1,["syn::token::For"]],["impl !Send for If",1,["syn::token::If"]],["impl !Send for Impl",1,["syn::token::Impl"]],["impl !Send for In",1,["syn::token::In"]],["impl !Send for Let",1,["syn::token::Let"]],["impl !Send for Loop",1,["syn::token::Loop"]],["impl !Send for Macro",1,["syn::token::Macro"]],["impl !Send for Match",1,["syn::token::Match"]],["impl !Send for Mod",1,["syn::token::Mod"]],["impl !Send for Move",1,["syn::token::Move"]],["impl !Send for Mut",1,["syn::token::Mut"]],["impl !Send for Override",1,["syn::token::Override"]],["impl !Send for Priv",1,["syn::token::Priv"]],["impl !Send for Pub",1,["syn::token::Pub"]],["impl !Send for Ref",1,["syn::token::Ref"]],["impl !Send for Return",1,["syn::token::Return"]],["impl !Send for SelfType",1,["syn::token::SelfType"]],["impl !Send for SelfValue",1,["syn::token::SelfValue"]],["impl !Send for Static",1,["syn::token::Static"]],["impl !Send for Struct",1,["syn::token::Struct"]],["impl !Send for Super",1,["syn::token::Super"]],["impl !Send for Trait",1,["syn::token::Trait"]],["impl !Send for Try",1,["syn::token::Try"]],["impl !Send for Type",1,["syn::token::Type"]],["impl !Send for Typeof",1,["syn::token::Typeof"]],["impl !Send for Union",1,["syn::token::Union"]],["impl !Send for Unsafe",1,["syn::token::Unsafe"]],["impl !Send for Unsized",1,["syn::token::Unsized"]],["impl !Send for Use",1,["syn::token::Use"]],["impl !Send for Virtual",1,["syn::token::Virtual"]],["impl !Send for Where",1,["syn::token::Where"]],["impl !Send for While",1,["syn::token::While"]],["impl !Send for Yield",1,["syn::token::Yield"]],["impl !Send for Add",1,["syn::token::Add"]],["impl !Send for AddEq",1,["syn::token::AddEq"]],["impl !Send for And",1,["syn::token::And"]],["impl !Send for AndAnd",1,["syn::token::AndAnd"]],["impl !Send for AndEq",1,["syn::token::AndEq"]],["impl !Send for At",1,["syn::token::At"]],["impl !Send for Bang",1,["syn::token::Bang"]],["impl !Send for Caret",1,["syn::token::Caret"]],["impl !Send for CaretEq",1,["syn::token::CaretEq"]],["impl !Send for Colon",1,["syn::token::Colon"]],["impl !Send for Colon2",1,["syn::token::Colon2"]],["impl !Send for Comma",1,["syn::token::Comma"]],["impl !Send for Div",1,["syn::token::Div"]],["impl !Send for DivEq",1,["syn::token::DivEq"]],["impl !Send for Dollar",1,["syn::token::Dollar"]],["impl !Send for Dot",1,["syn::token::Dot"]],["impl !Send for Dot2",1,["syn::token::Dot2"]],["impl !Send for Dot3",1,["syn::token::Dot3"]],["impl !Send for DotDotEq",1,["syn::token::DotDotEq"]],["impl !Send for Eq",1,["syn::token::Eq"]],["impl !Send for EqEq",1,["syn::token::EqEq"]],["impl !Send for Ge",1,["syn::token::Ge"]],["impl !Send for Gt",1,["syn::token::Gt"]],["impl !Send for Le",1,["syn::token::Le"]],["impl !Send for Lt",1,["syn::token::Lt"]],["impl !Send for MulEq",1,["syn::token::MulEq"]],["impl !Send for Ne",1,["syn::token::Ne"]],["impl !Send for Or",1,["syn::token::Or"]],["impl !Send for OrEq",1,["syn::token::OrEq"]],["impl !Send for OrOr",1,["syn::token::OrOr"]],["impl !Send for Pound",1,["syn::token::Pound"]],["impl !Send for Question",1,["syn::token::Question"]],["impl !Send for RArrow",1,["syn::token::RArrow"]],["impl !Send for LArrow",1,["syn::token::LArrow"]],["impl !Send for Rem",1,["syn::token::Rem"]],["impl !Send for RemEq",1,["syn::token::RemEq"]],["impl !Send for FatArrow",1,["syn::token::FatArrow"]],["impl !Send for Semi",1,["syn::token::Semi"]],["impl !Send for Shl",1,["syn::token::Shl"]],["impl !Send for ShlEq",1,["syn::token::ShlEq"]],["impl !Send for Shr",1,["syn::token::Shr"]],["impl !Send for ShrEq",1,["syn::token::ShrEq"]],["impl !Send for Star",1,["syn::token::Star"]],["impl !Send for Sub",1,["syn::token::Sub"]],["impl !Send for SubEq",1,["syn::token::SubEq"]],["impl !Send for Tilde",1,["syn::token::Tilde"]],["impl !Send for Brace",1,["syn::token::Brace"]],["impl !Send for Bracket",1,["syn::token::Bracket"]],["impl !Send for Paren",1,["syn::token::Paren"]],["impl !Send for Group",1,["syn::token::Group"]],["impl !Send for Attribute",1,["syn::attr::Attribute"]],["impl !Send for AttrStyle",1,["syn::attr::AttrStyle"]],["impl !Send for Meta",1,["syn::attr::Meta"]],["impl !Send for MetaList",1,["syn::attr::MetaList"]],["impl !Send for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl !Send for NestedMeta",1,["syn::attr::NestedMeta"]],["impl !Send for Variant",1,["syn::data::Variant"]],["impl !Send for Fields",1,["syn::data::Fields"]],["impl !Send for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl !Send for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl !Send for Field",1,["syn::data::Field"]],["impl !Send for Visibility",1,["syn::data::Visibility"]],["impl !Send for VisPublic",1,["syn::data::VisPublic"]],["impl !Send for VisCrate",1,["syn::data::VisCrate"]],["impl !Send for VisRestricted",1,["syn::data::VisRestricted"]],["impl !Send for Expr",1,["syn::expr::Expr"]],["impl !Send for ExprArray",1,["syn::expr::ExprArray"]],["impl !Send for ExprAssign",1,["syn::expr::ExprAssign"]],["impl !Send for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl !Send for ExprAsync",1,["syn::expr::ExprAsync"]],["impl !Send for ExprAwait",1,["syn::expr::ExprAwait"]],["impl !Send for ExprBinary",1,["syn::expr::ExprBinary"]],["impl !Send for ExprBlock",1,["syn::expr::ExprBlock"]],["impl !Send for ExprBox",1,["syn::expr::ExprBox"]],["impl !Send for ExprBreak",1,["syn::expr::ExprBreak"]],["impl !Send for ExprCall",1,["syn::expr::ExprCall"]],["impl !Send for ExprCast",1,["syn::expr::ExprCast"]],["impl !Send for ExprClosure",1,["syn::expr::ExprClosure"]],["impl !Send for ExprContinue",1,["syn::expr::ExprContinue"]],["impl !Send for ExprField",1,["syn::expr::ExprField"]],["impl !Send for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl !Send for ExprGroup",1,["syn::expr::ExprGroup"]],["impl !Send for ExprIf",1,["syn::expr::ExprIf"]],["impl !Send for ExprIndex",1,["syn::expr::ExprIndex"]],["impl !Send for ExprLet",1,["syn::expr::ExprLet"]],["impl !Send for ExprLit",1,["syn::expr::ExprLit"]],["impl !Send for ExprLoop",1,["syn::expr::ExprLoop"]],["impl !Send for ExprMacro",1,["syn::expr::ExprMacro"]],["impl !Send for ExprMatch",1,["syn::expr::ExprMatch"]],["impl !Send for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl !Send for ExprParen",1,["syn::expr::ExprParen"]],["impl !Send for ExprPath",1,["syn::expr::ExprPath"]],["impl !Send for ExprRange",1,["syn::expr::ExprRange"]],["impl !Send for ExprReference",1,["syn::expr::ExprReference"]],["impl !Send for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl !Send for ExprReturn",1,["syn::expr::ExprReturn"]],["impl !Send for ExprStruct",1,["syn::expr::ExprStruct"]],["impl !Send for ExprTry",1,["syn::expr::ExprTry"]],["impl !Send for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl !Send for ExprTuple",1,["syn::expr::ExprTuple"]],["impl !Send for ExprType",1,["syn::expr::ExprType"]],["impl !Send for ExprUnary",1,["syn::expr::ExprUnary"]],["impl !Send for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl !Send for ExprWhile",1,["syn::expr::ExprWhile"]],["impl !Send for ExprYield",1,["syn::expr::ExprYield"]],["impl !Send for Member",1,["syn::expr::Member"]],["impl !Send for Index",1,["syn::expr::Index"]],["impl !Send for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl !Send for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl !Send for FieldValue",1,["syn::expr::FieldValue"]],["impl !Send for Label",1,["syn::expr::Label"]],["impl !Send for Arm",1,["syn::expr::Arm"]],["impl !Send for RangeLimits",1,["syn::expr::RangeLimits"]],["impl !Send for Generics",1,["syn::generics::Generics"]],["impl !Send for GenericParam",1,["syn::generics::GenericParam"]],["impl !Send for TypeParam",1,["syn::generics::TypeParam"]],["impl !Send for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl !Send for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> !Send for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> !Send for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> !Send for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl !Send for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl !Send for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl !Send for TraitBound",1,["syn::generics::TraitBound"]],["impl !Send for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl !Send for WhereClause",1,["syn::generics::WhereClause"]],["impl !Send for WherePredicate",1,["syn::generics::WherePredicate"]],["impl !Send for PredicateType",1,["syn::generics::PredicateType"]],["impl !Send for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl !Send for PredicateEq",1,["syn::generics::PredicateEq"]],["impl !Send for Item",1,["syn::item::Item"]],["impl !Send for ItemConst",1,["syn::item::ItemConst"]],["impl !Send for ItemEnum",1,["syn::item::ItemEnum"]],["impl !Send for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl !Send for ItemFn",1,["syn::item::ItemFn"]],["impl !Send for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl !Send for ItemImpl",1,["syn::item::ItemImpl"]],["impl !Send for ItemMacro",1,["syn::item::ItemMacro"]],["impl !Send for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl !Send for ItemMod",1,["syn::item::ItemMod"]],["impl !Send for ItemStatic",1,["syn::item::ItemStatic"]],["impl !Send for ItemStruct",1,["syn::item::ItemStruct"]],["impl !Send for ItemTrait",1,["syn::item::ItemTrait"]],["impl !Send for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl !Send for ItemType",1,["syn::item::ItemType"]],["impl !Send for ItemUnion",1,["syn::item::ItemUnion"]],["impl !Send for ItemUse",1,["syn::item::ItemUse"]],["impl !Send for UseTree",1,["syn::item::UseTree"]],["impl !Send for UsePath",1,["syn::item::UsePath"]],["impl !Send for UseName",1,["syn::item::UseName"]],["impl !Send for UseRename",1,["syn::item::UseRename"]],["impl !Send for UseGlob",1,["syn::item::UseGlob"]],["impl !Send for UseGroup",1,["syn::item::UseGroup"]],["impl !Send for ForeignItem",1,["syn::item::ForeignItem"]],["impl !Send for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl !Send for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl !Send for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl !Send for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl !Send for TraitItem",1,["syn::item::TraitItem"]],["impl !Send for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl !Send for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl !Send for TraitItemType",1,["syn::item::TraitItemType"]],["impl !Send for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl !Send for ImplItem",1,["syn::item::ImplItem"]],["impl !Send for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl !Send for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl !Send for ImplItemType",1,["syn::item::ImplItemType"]],["impl !Send for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl !Send for Signature",1,["syn::item::Signature"]],["impl !Send for FnArg",1,["syn::item::FnArg"]],["impl !Send for Receiver",1,["syn::item::Receiver"]],["impl !Send for File",1,["syn::file::File"]],["impl !Send for Lifetime",1,["syn::lifetime::Lifetime"]],["impl !Send for Lit",1,["syn::lit::Lit"]],["impl !Send for LitStr",1,["syn::lit::LitStr"]],["impl !Send for LitByteStr",1,["syn::lit::LitByteStr"]],["impl !Send for LitByte",1,["syn::lit::LitByte"]],["impl !Send for LitChar",1,["syn::lit::LitChar"]],["impl !Send for LitInt",1,["syn::lit::LitInt"]],["impl !Send for LitFloat",1,["syn::lit::LitFloat"]],["impl !Send for LitBool",1,["syn::lit::LitBool"]],["impl Send for StrStyle",1,["syn::lit::StrStyle"]],["impl !Send for Macro",1,["syn::mac::Macro"]],["impl !Send for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl !Send for DeriveInput",1,["syn::derive::DeriveInput"]],["impl !Send for Data",1,["syn::derive::Data"]],["impl !Send for DataStruct",1,["syn::derive::DataStruct"]],["impl !Send for DataEnum",1,["syn::derive::DataEnum"]],["impl !Send for DataUnion",1,["syn::derive::DataUnion"]],["impl !Send for BinOp",1,["syn::op::BinOp"]],["impl !Send for UnOp",1,["syn::op::UnOp"]],["impl !Send for Block",1,["syn::stmt::Block"]],["impl !Send for Stmt",1,["syn::stmt::Stmt"]],["impl !Send for Local",1,["syn::stmt::Local"]],["impl !Send for Type",1,["syn::ty::Type"]],["impl !Send for TypeArray",1,["syn::ty::TypeArray"]],["impl !Send for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl !Send for TypeGroup",1,["syn::ty::TypeGroup"]],["impl !Send for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl !Send for TypeInfer",1,["syn::ty::TypeInfer"]],["impl !Send for TypeMacro",1,["syn::ty::TypeMacro"]],["impl !Send for TypeNever",1,["syn::ty::TypeNever"]],["impl !Send for TypeParen",1,["syn::ty::TypeParen"]],["impl !Send for TypePath",1,["syn::ty::TypePath"]],["impl !Send for TypePtr",1,["syn::ty::TypePtr"]],["impl !Send for TypeReference",1,["syn::ty::TypeReference"]],["impl !Send for TypeSlice",1,["syn::ty::TypeSlice"]],["impl !Send for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl !Send for TypeTuple",1,["syn::ty::TypeTuple"]],["impl !Send for Abi",1,["syn::ty::Abi"]],["impl !Send for BareFnArg",1,["syn::ty::BareFnArg"]],["impl !Send for Variadic",1,["syn::ty::Variadic"]],["impl !Send for ReturnType",1,["syn::ty::ReturnType"]],["impl !Send for Pat",1,["syn::pat::Pat"]],["impl !Send for PatBox",1,["syn::pat::PatBox"]],["impl !Send for PatIdent",1,["syn::pat::PatIdent"]],["impl !Send for PatLit",1,["syn::pat::PatLit"]],["impl !Send for PatMacro",1,["syn::pat::PatMacro"]],["impl !Send for PatOr",1,["syn::pat::PatOr"]],["impl !Send for PatPath",1,["syn::pat::PatPath"]],["impl !Send for PatRange",1,["syn::pat::PatRange"]],["impl !Send for PatReference",1,["syn::pat::PatReference"]],["impl !Send for PatRest",1,["syn::pat::PatRest"]],["impl !Send for PatSlice",1,["syn::pat::PatSlice"]],["impl !Send for PatStruct",1,["syn::pat::PatStruct"]],["impl !Send for PatTuple",1,["syn::pat::PatTuple"]],["impl !Send for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl !Send for PatType",1,["syn::pat::PatType"]],["impl !Send for PatWild",1,["syn::pat::PatWild"]],["impl !Send for FieldPat",1,["syn::pat::FieldPat"]],["impl !Send for Path",1,["syn::path::Path"]],["impl !Send for PathSegment",1,["syn::path::PathSegment"]],["impl !Send for PathArguments",1,["syn::path::PathArguments"]],["impl !Send for GenericArgument",1,["syn::path::GenericArgument"]],["impl !Send for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl !Send for Binding",1,["syn::path::Binding"]],["impl !Send for Constraint",1,["syn::path::Constraint"]],["impl !Send for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl !Send for QSelf",1,["syn::path::QSelf"]],["impl !Send for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> !Send for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Send for Punctuated<T, P>where
        P: Send,
        T: Send,
    ",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Send for Pairs<'a, T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Send for PairsMut<'a, T, P>where
        P: Send,
        T: Send,
    ",1,["syn::punctuated::PairsMut"]],["impl<T, P> Send for IntoPairs<T, P>where
        P: Send,
        T: Send,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> Send for IntoIter<T>where
        T: Send,
    ",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !Send for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !Send for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Send for Pair<T, P>where
        P: Send,
        T: Send,
    ",1,["syn::punctuated::Pair"]],["impl<'a> !Send for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Send for Error",1,["syn::error::Error"]],["impl<'a> !Send for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> !Send for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Send for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> Send for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Send for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Send for Builder",1,["tokio::runtime::builder::Builder"]],["impl Send for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Send for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Send for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Send for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Send for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl Send for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Send for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Send for SendError<T>where
        T: Send,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl Send for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Send for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Send for Sender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Send for WeakSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Send for Permit<'a, T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Send for OwnedPermit<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Send for Receiver<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Send for UnboundedSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Send for WeakUnboundedSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Send for UnboundedReceiver<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Send for SendError<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Send for TrySendError<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Send for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<'a, T: ?Sized> Send for MutexGuard<'a, T>where
        T: Send,
    ",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Send for OwnedMutexGuard<T>where
        T: Send,
    ",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl Send for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Send for Notify",1,["tokio::sync::notify::Notify"]],["impl Send for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Send for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Send for Sender<T>where
        T: Send,
    ",1,["tokio::sync::oneshot::Sender"]],["impl<T> Send for Receiver<T>where
        T: Send,
    ",1,["tokio::sync::oneshot::Receiver"]],["impl Send for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Send for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Send for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Send for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Send for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T> Send for SetError<T>where
        T: Send,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> Send for SendError<T>where
        T: Send,
    ",1,["tokio::sync::watch::error::SendError"]],["impl Send for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Send for Receiver<T>where
        T: Send + Sync,
    ",1,["tokio::sync::watch::Receiver"]],["impl<T> Send for Sender<T>where
        T: Send + Sync,
    ",1,["tokio::sync::watch::Sender"]],["impl<'a, T> !Send for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl !Send for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !Send for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Send for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Send for TaskLocalFuture<T, F>where
        F: Send,
        T: Send,
    ",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Send for Unconstrained<F>where
        F: Send,
    ",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Send for JoinSet<T>where
        T: Send,
    ",1,["tokio::task::join_set::JoinSet"]],["impl Send for AbortHandle"],["impl<T: Send> Send for JoinHandle<T>"],["impl<T: Send> Send for Sender<T>"],["impl<T: Send> Send for Receiver<T>"],["impl<T> Send for Mutex<T>where
        T: ?Sized + Send,
    "],["impl<'a, T> Send for MappedMutexGuard<'a, T>where
        T: ?Sized + Send + 'a,
    "],["impl<'a> Send for Notified<'a>"],["impl<T> Send for RwLock<T>where
        T: ?Sized + Send,
    "],["impl<T> Send for RwLockReadGuard<'_, T>where
        T: ?Sized + Sync,
    "],["impl<T, U> Send for OwnedRwLockReadGuard<T, U>where
        T: ?Sized + Send + Sync,
        U: ?Sized + Sync,
    "],["impl<T> Send for RwLockWriteGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Send for OwnedRwLockWriteGuard<T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Send for RwLockMappedWriteGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T, U> Send for OwnedRwLockMappedWriteGuard<T, U>where
        T: ?Sized + Send + Sync,
        U: ?Sized + Send + Sync,
    "],["impl<T: Send> Send for OnceCell<T>"]], -"typenum":[["impl Send for B0",1,["typenum::bit::B0"]],["impl Send for B1",1,["typenum::bit::B1"]],["impl<U> Send for PInt<U>where
        U: Send,
    ",1,["typenum::int::PInt"]],["impl<U> Send for NInt<U>where
        U: Send,
    ",1,["typenum::int::NInt"]],["impl Send for Z0",1,["typenum::int::Z0"]],["impl Send for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Send for UInt<U, B>where
        B: Send,
        U: Send,
    ",1,["typenum::uint::UInt"]],["impl Send for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Send for TArr<V, A>where
        A: Send,
        V: Send,
    ",1,["typenum::array::TArr"]],["impl Send for Greater",1,["typenum::Greater"]],["impl Send for Less",1,["typenum::Less"]],["impl Send for Equal",1,["typenum::Equal"]]], -"uint":[["impl Send for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Send for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Send for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Send for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.StructuralEq.js b/docs/implementors/core/marker/trait.StructuralEq.js deleted file mode 100644 index 3a20a20ed347..000000000000 --- a/docs/implementors/core/marker/trait.StructuralEq.js +++ /dev/null @@ -1,29 +0,0 @@ -(function() {var implementors = { -"aho_corasick":[["impl StructuralEq for MatchKind"],["impl StructuralEq for MatchKind"],["impl StructuralEq for Match"]], -"block_buffer":[["impl StructuralEq for Error"]], -"byteorder":[["impl StructuralEq for BigEndian"],["impl StructuralEq for LittleEndian"]], -"crossbeam_channel":[["impl<T> StructuralEq for SendError<T>"],["impl<T> StructuralEq for TrySendError<T>"],["impl<T> StructuralEq for SendTimeoutError<T>"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for RecvTimeoutError"],["impl StructuralEq for TrySelectError"],["impl StructuralEq for SelectTimeoutError"],["impl StructuralEq for TryReadyError"],["impl StructuralEq for ReadyTimeoutError"]], -"crossbeam_utils":[["impl<T> StructuralEq for CachePadded<T>"]], -"crypto_common":[["impl StructuralEq for InvalidLength"]], -"digest":[["impl StructuralEq for InvalidBufferSize"]], -"firewood":[["impl StructuralEq for Hash"],["impl StructuralEq for PartialPath"],["impl StructuralEq for Node"]], -"futures_channel":[["impl StructuralEq for SendError"],["impl<T> StructuralEq for TrySendError<T>"],["impl StructuralEq for Canceled"]], -"futures_util":[["impl<T, E> StructuralEq for TryChunksError<T, E>"],["impl StructuralEq for PollNext"],["impl<T> StructuralEq for AllowStdIo<T>"],["impl StructuralEq for Aborted"]], -"getrandom":[["impl StructuralEq for Error"]], -"growthring":[["impl StructuralEq for WALRingId"]], -"hashbrown":[["impl StructuralEq for TryReserveError"]], -"libc":[["impl StructuralEq for group"],["impl StructuralEq for utimbuf"],["impl StructuralEq for timeval"],["impl StructuralEq for timespec"],["impl StructuralEq for rlimit"],["impl StructuralEq for rusage"],["impl StructuralEq for ipv6_mreq"],["impl StructuralEq for hostent"],["impl StructuralEq for iovec"],["impl StructuralEq for pollfd"],["impl StructuralEq for winsize"],["impl StructuralEq for linger"],["impl StructuralEq for sigval"],["impl StructuralEq for itimerval"],["impl StructuralEq for tms"],["impl StructuralEq for servent"],["impl StructuralEq for protoent"],["impl StructuralEq for in_addr"],["impl StructuralEq for ip_mreq"],["impl StructuralEq for ip_mreqn"],["impl StructuralEq for ip_mreq_source"],["impl StructuralEq for sockaddr"],["impl StructuralEq for sockaddr_in"],["impl StructuralEq for sockaddr_in6"],["impl StructuralEq for addrinfo"],["impl StructuralEq for sockaddr_ll"],["impl StructuralEq for fd_set"],["impl StructuralEq for tm"],["impl StructuralEq for sched_param"],["impl StructuralEq for Dl_info"],["impl StructuralEq for lconv"],["impl StructuralEq for in_pktinfo"],["impl StructuralEq for ifaddrs"],["impl StructuralEq for in6_rtmsg"],["impl StructuralEq for arpreq"],["impl StructuralEq for arpreq_old"],["impl StructuralEq for arphdr"],["impl StructuralEq for mmsghdr"],["impl StructuralEq for rlimit64"],["impl StructuralEq for glob_t"],["impl StructuralEq for passwd"],["impl StructuralEq for spwd"],["impl StructuralEq for dqblk"],["impl StructuralEq for signalfd_siginfo"],["impl StructuralEq for itimerspec"],["impl StructuralEq for fsid_t"],["impl StructuralEq for packet_mreq"],["impl StructuralEq for cpu_set_t"],["impl StructuralEq for if_nameindex"],["impl StructuralEq for msginfo"],["impl StructuralEq for sembuf"],["impl StructuralEq for input_event"],["impl StructuralEq for input_id"],["impl StructuralEq for input_absinfo"],["impl StructuralEq for input_keymap_entry"],["impl StructuralEq for input_mask"],["impl StructuralEq for ff_replay"],["impl StructuralEq for ff_trigger"],["impl StructuralEq for ff_envelope"],["impl StructuralEq for ff_constant_effect"],["impl StructuralEq for ff_ramp_effect"],["impl StructuralEq for ff_condition_effect"],["impl StructuralEq for ff_periodic_effect"],["impl StructuralEq for ff_rumble_effect"],["impl StructuralEq for ff_effect"],["impl StructuralEq for uinput_ff_upload"],["impl StructuralEq for uinput_ff_erase"],["impl StructuralEq for uinput_abs_setup"],["impl StructuralEq for dl_phdr_info"],["impl StructuralEq for Elf32_Ehdr"],["impl StructuralEq for Elf64_Ehdr"],["impl StructuralEq for Elf32_Sym"],["impl StructuralEq for Elf64_Sym"],["impl StructuralEq for Elf32_Phdr"],["impl StructuralEq for Elf64_Phdr"],["impl StructuralEq for Elf32_Shdr"],["impl StructuralEq for Elf64_Shdr"],["impl StructuralEq for ucred"],["impl StructuralEq for mntent"],["impl StructuralEq for posix_spawn_file_actions_t"],["impl StructuralEq for posix_spawnattr_t"],["impl StructuralEq for genlmsghdr"],["impl StructuralEq for in6_pktinfo"],["impl StructuralEq for arpd_request"],["impl StructuralEq for inotify_event"],["impl StructuralEq for fanotify_response"],["impl StructuralEq for sockaddr_vm"],["impl StructuralEq for regmatch_t"],["impl StructuralEq for sock_extended_err"],["impl StructuralEq for __c_anonymous_sockaddr_can_tp"],["impl StructuralEq for __c_anonymous_sockaddr_can_j1939"],["impl StructuralEq for can_filter"],["impl StructuralEq for j1939_filter"],["impl StructuralEq for sock_filter"],["impl StructuralEq for sock_fprog"],["impl StructuralEq for seccomp_data"],["impl StructuralEq for nlmsghdr"],["impl StructuralEq for nlmsgerr"],["impl StructuralEq for nlattr"],["impl StructuralEq for file_clone_range"],["impl StructuralEq for __c_anonymous_ifru_map"],["impl StructuralEq for in6_ifreq"],["impl StructuralEq for statx"],["impl StructuralEq for statx_timestamp"],["impl StructuralEq for aiocb"],["impl StructuralEq for __exit_status"],["impl StructuralEq for __timeval"],["impl StructuralEq for glob64_t"],["impl StructuralEq for msghdr"],["impl StructuralEq for cmsghdr"],["impl StructuralEq for termios"],["impl StructuralEq for mallinfo"],["impl StructuralEq for mallinfo2"],["impl StructuralEq for nl_pktinfo"],["impl StructuralEq for nl_mmap_req"],["impl StructuralEq for nl_mmap_hdr"],["impl StructuralEq for rtentry"],["impl StructuralEq for timex"],["impl StructuralEq for ntptimeval"],["impl StructuralEq for regex_t"],["impl StructuralEq for Elf64_Chdr"],["impl StructuralEq for Elf32_Chdr"],["impl StructuralEq for seminfo"],["impl StructuralEq for ptrace_peeksiginfo_args"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_entry"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_exit"],["impl StructuralEq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl StructuralEq for ptrace_syscall_info"],["impl StructuralEq for sigset_t"],["impl StructuralEq for sysinfo"],["impl StructuralEq for msqid_ds"],["impl StructuralEq for semid_ds"],["impl StructuralEq for sigaction"],["impl StructuralEq for statfs"],["impl StructuralEq for flock"],["impl StructuralEq for flock64"],["impl StructuralEq for siginfo_t"],["impl StructuralEq for stack_t"],["impl StructuralEq for stat"],["impl StructuralEq for stat64"],["impl StructuralEq for statfs64"],["impl StructuralEq for statvfs64"],["impl StructuralEq for pthread_attr_t"],["impl StructuralEq for _libc_fpxreg"],["impl StructuralEq for _libc_xmmreg"],["impl StructuralEq for _libc_fpstate"],["impl StructuralEq for user_regs_struct"],["impl StructuralEq for user"],["impl StructuralEq for mcontext_t"],["impl StructuralEq for ipc_perm"],["impl StructuralEq for shmid_ds"],["impl StructuralEq for seccomp_notif_sizes"],["impl StructuralEq for ptrace_rseq_configuration"],["impl StructuralEq for statvfs"],["impl StructuralEq for clone_args"],["impl StructuralEq for sem_t"],["impl StructuralEq for termios2"],["impl StructuralEq for pthread_mutexattr_t"],["impl StructuralEq for pthread_rwlockattr_t"],["impl StructuralEq for pthread_condattr_t"],["impl StructuralEq for fanotify_event_metadata"],["impl StructuralEq for open_how"],["impl StructuralEq for in6_addr"]], -"nix":[["impl StructuralEq for Dir"],["impl<'d> StructuralEq for Iter<'d>"],["impl StructuralEq for OwningIter"],["impl StructuralEq for Entry"],["impl StructuralEq for Type"],["impl StructuralEq for Errno"],["impl StructuralEq for AtFlags"],["impl StructuralEq for OFlag"],["impl StructuralEq for RenameFlags"],["impl StructuralEq for SealFlag"],["impl StructuralEq for FdFlag"],["impl<'a> StructuralEq for FcntlArg<'a>"],["impl StructuralEq for FlockArg"],["impl StructuralEq for SpliceFFlags"],["impl StructuralEq for FallocateFlags"],["impl StructuralEq for PosixFadviseAdvice"],["impl StructuralEq for InterfaceAddress"],["impl StructuralEq for InterfaceAddressIterator"],["impl StructuralEq for InterfaceFlags"],["impl StructuralEq for ModuleInitFlags"],["impl StructuralEq for DeleteModuleFlags"],["impl StructuralEq for MsFlags"],["impl StructuralEq for MntFlags"],["impl StructuralEq for MQ_OFlag"],["impl StructuralEq for MqAttr"],["impl StructuralEq for PollFd"],["impl StructuralEq for PollFlags"],["impl StructuralEq for OpenptyResult"],["impl StructuralEq for PtyMaster"],["impl StructuralEq for CloneFlags"],["impl StructuralEq for CpuSet"],["impl StructuralEq for AioFsyncMode"],["impl StructuralEq for LioMode"],["impl StructuralEq for AioCancelStat"],["impl StructuralEq for EpollFlags"],["impl StructuralEq for EpollOp"],["impl StructuralEq for EpollCreateFlags"],["impl StructuralEq for EpollEvent"],["impl StructuralEq for EfdFlags"],["impl StructuralEq for MemFdCreateFlag"],["impl StructuralEq for ProtFlags"],["impl StructuralEq for MapFlags"],["impl StructuralEq for MRemapFlags"],["impl StructuralEq for MmapAdvise"],["impl StructuralEq for MsFlags"],["impl StructuralEq for MlockAllFlags"],["impl StructuralEq for Persona"],["impl StructuralEq for Request"],["impl StructuralEq for Event"],["impl StructuralEq for Options"],["impl StructuralEq for QuotaType"],["impl StructuralEq for QuotaFmt"],["impl StructuralEq for QuotaValidFlags"],["impl StructuralEq for Dqblk"],["impl StructuralEq for RebootMode"],["impl StructuralEq for Resource"],["impl StructuralEq for UsageWho"],["impl StructuralEq for Usage"],["impl StructuralEq for FdSet"],["impl StructuralEq for Signal"],["impl StructuralEq for SignalIterator"],["impl StructuralEq for SaFlags"],["impl StructuralEq for SigmaskHow"],["impl StructuralEq for SigSet"],["impl StructuralEq for SigHandler"],["impl StructuralEq for SigAction"],["impl StructuralEq for SigevNotify"],["impl StructuralEq for SigEvent"],["impl StructuralEq for SfdFlags"],["impl StructuralEq for SignalFd"],["impl StructuralEq for AddressFamily"],["impl StructuralEq for InetAddr"],["impl StructuralEq for IpAddr"],["impl StructuralEq for Ipv4Addr"],["impl StructuralEq for Ipv6Addr"],["impl StructuralEq for SockaddrIn"],["impl StructuralEq for SockaddrIn6"],["impl StructuralEq for SockAddr"],["impl StructuralEq for NetlinkAddr"],["impl StructuralEq for LinkAddr"],["impl StructuralEq for ReuseAddr"],["impl StructuralEq for ReusePort"],["impl StructuralEq for TcpNoDelay"],["impl StructuralEq for Linger"],["impl StructuralEq for IpAddMembership"],["impl StructuralEq for IpDropMembership"],["impl StructuralEq for Ipv6AddMembership"],["impl StructuralEq for Ipv6DropMembership"],["impl StructuralEq for IpMulticastTtl"],["impl StructuralEq for IpMulticastLoop"],["impl StructuralEq for Priority"],["impl StructuralEq for IpTos"],["impl StructuralEq for Ipv6TClass"],["impl StructuralEq for IpFreebind"],["impl StructuralEq for ReceiveTimeout"],["impl StructuralEq for SendTimeout"],["impl StructuralEq for Broadcast"],["impl StructuralEq for OobInline"],["impl StructuralEq for SocketError"],["impl StructuralEq for DontRoute"],["impl StructuralEq for KeepAlive"],["impl StructuralEq for PeerCredentials"],["impl StructuralEq for TcpKeepIdle"],["impl StructuralEq for TcpMaxSeg"],["impl StructuralEq for TcpKeepCount"],["impl StructuralEq for TcpRepair"],["impl StructuralEq for TcpKeepInterval"],["impl StructuralEq for TcpUserTimeout"],["impl StructuralEq for RcvBuf"],["impl StructuralEq for SndBuf"],["impl StructuralEq for RcvBufForce"],["impl StructuralEq for SndBufForce"],["impl StructuralEq for SockType"],["impl StructuralEq for AcceptConn"],["impl StructuralEq for BindToDevice"],["impl StructuralEq for OriginalDst"],["impl StructuralEq for Ip6tOriginalDst"],["impl StructuralEq for Timestamping"],["impl StructuralEq for ReceiveTimestamp"],["impl StructuralEq for ReceiveTimestampns"],["impl StructuralEq for IpTransparent"],["impl StructuralEq for Mark"],["impl StructuralEq for PassCred"],["impl StructuralEq for TcpCongestion"],["impl StructuralEq for Ipv4PacketInfo"],["impl StructuralEq for Ipv6RecvPacketInfo"],["impl StructuralEq for Ipv4OrigDstAddr"],["impl StructuralEq for UdpGsoSegment"],["impl StructuralEq for UdpGroSegment"],["impl StructuralEq for TxTime"],["impl StructuralEq for RxqOvfl"],["impl StructuralEq for Ipv6V6Only"],["impl StructuralEq for Ipv4RecvErr"],["impl StructuralEq for Ipv6RecvErr"],["impl StructuralEq for IpMtu"],["impl StructuralEq for Ipv4Ttl"],["impl StructuralEq for Ipv6Ttl"],["impl StructuralEq for Ipv6OrigDstAddr"],["impl StructuralEq for Ipv6DontFrag"],["impl StructuralEq for SockType"],["impl StructuralEq for SockProtocol"],["impl StructuralEq for TimestampingFlag"],["impl StructuralEq for SockFlag"],["impl StructuralEq for MsgFlags"],["impl StructuralEq for UnixCredentials"],["impl StructuralEq for IpMembershipRequest"],["impl StructuralEq for Ipv6MembershipRequest"],["impl<'a, 's, S> StructuralEq for RecvMsg<'a, 's, S>"],["impl<'a> StructuralEq for CmsgIterator<'a>"],["impl StructuralEq for ControlMessageOwned"],["impl StructuralEq for Timestamps"],["impl<'a> StructuralEq for ControlMessage<'a>"],["impl StructuralEq for Shutdown"],["impl StructuralEq for SFlag"],["impl StructuralEq for Mode"],["impl StructuralEq for FsType"],["impl StructuralEq for FsFlags"],["impl StructuralEq for Statvfs"],["impl StructuralEq for SysInfo"],["impl StructuralEq for Termios"],["impl StructuralEq for BaudRate"],["impl StructuralEq for SetArg"],["impl StructuralEq for FlushArg"],["impl StructuralEq for FlowArg"],["impl StructuralEq for SpecialCharacterIndices"],["impl StructuralEq for InputFlags"],["impl StructuralEq for OutputFlags"],["impl StructuralEq for ControlFlags"],["impl StructuralEq for LocalFlags"],["impl StructuralEq for Expiration"],["impl StructuralEq for TimerSetTimeFlags"],["impl StructuralEq for TimeSpec"],["impl StructuralEq for TimeVal"],["impl StructuralEq for RemoteIoVec"],["impl<T> StructuralEq for IoVec<T>"],["impl StructuralEq for UtsName"],["impl StructuralEq for WaitPidFlag"],["impl StructuralEq for WaitStatus"],["impl StructuralEq for Id"],["impl StructuralEq for AddWatchFlags"],["impl StructuralEq for InitFlags"],["impl StructuralEq for WatchDescriptor"],["impl StructuralEq for ClockId"],["impl StructuralEq for TimerFlags"],["impl StructuralEq for ClockId"],["impl StructuralEq for UContext"],["impl StructuralEq for Uid"],["impl StructuralEq for Gid"],["impl StructuralEq for Pid"],["impl StructuralEq for PathconfVar"],["impl StructuralEq for SysconfVar"],["impl StructuralEq for ResUid"],["impl StructuralEq for ResGid"],["impl StructuralEq for AccessFlags"],["impl StructuralEq for User"],["impl StructuralEq for Group"]], -"parking_lot":[["impl StructuralEq for WaitTimeoutResult"],["impl StructuralEq for OnceState"]], -"parking_lot_core":[["impl StructuralEq for ParkResult"],["impl StructuralEq for UnparkResult"],["impl StructuralEq for RequeueOp"],["impl StructuralEq for FilterOp"],["impl StructuralEq for UnparkToken"],["impl StructuralEq for ParkToken"]], -"primitive_types":[["impl StructuralEq for Error"],["impl StructuralEq for U128"],["impl StructuralEq for U256"],["impl StructuralEq for U512"]], -"proc_macro2":[["impl StructuralEq for Delimiter"],["impl StructuralEq for Spacing"]], -"rand":[["impl StructuralEq for BernoulliError"],["impl StructuralEq for WeightedError"],["impl StructuralEq for StepRng"]], -"rand_chacha":[["impl StructuralEq for ChaCha20Core"],["impl StructuralEq for ChaCha12Core"],["impl StructuralEq for ChaCha8Core"]], -"regex":[["impl<'t> StructuralEq for Match<'t>"],["impl<'t> StructuralEq for Match<'t>"]], -"regex_syntax":[["impl StructuralEq for Error"],["impl StructuralEq for ErrorKind"],["impl StructuralEq for Span"],["impl StructuralEq for Position"],["impl StructuralEq for WithComments"],["impl StructuralEq for Comment"],["impl StructuralEq for Ast"],["impl StructuralEq for Alternation"],["impl StructuralEq for Concat"],["impl StructuralEq for Literal"],["impl StructuralEq for LiteralKind"],["impl StructuralEq for SpecialLiteralKind"],["impl StructuralEq for HexLiteralKind"],["impl StructuralEq for Class"],["impl StructuralEq for ClassPerl"],["impl StructuralEq for ClassPerlKind"],["impl StructuralEq for ClassAscii"],["impl StructuralEq for ClassAsciiKind"],["impl StructuralEq for ClassUnicode"],["impl StructuralEq for ClassUnicodeKind"],["impl StructuralEq for ClassUnicodeOpKind"],["impl StructuralEq for ClassBracketed"],["impl StructuralEq for ClassSet"],["impl StructuralEq for ClassSetItem"],["impl StructuralEq for ClassSetRange"],["impl StructuralEq for ClassSetUnion"],["impl StructuralEq for ClassSetBinaryOp"],["impl StructuralEq for ClassSetBinaryOpKind"],["impl StructuralEq for Assertion"],["impl StructuralEq for AssertionKind"],["impl StructuralEq for Repetition"],["impl StructuralEq for RepetitionOp"],["impl StructuralEq for RepetitionKind"],["impl StructuralEq for RepetitionRange"],["impl StructuralEq for Group"],["impl StructuralEq for GroupKind"],["impl StructuralEq for CaptureName"],["impl StructuralEq for SetFlags"],["impl StructuralEq for Flags"],["impl StructuralEq for FlagsItem"],["impl StructuralEq for FlagsItemKind"],["impl StructuralEq for Flag"],["impl StructuralEq for Error"],["impl StructuralEq for Literals"],["impl StructuralEq for Literal"],["impl StructuralEq for Error"],["impl StructuralEq for ErrorKind"],["impl StructuralEq for Hir"],["impl StructuralEq for HirKind"],["impl StructuralEq for Literal"],["impl StructuralEq for Class"],["impl StructuralEq for ClassUnicode"],["impl StructuralEq for ClassUnicodeRange"],["impl StructuralEq for ClassBytes"],["impl StructuralEq for ClassBytesRange"],["impl StructuralEq for Anchor"],["impl StructuralEq for WordBoundary"],["impl StructuralEq for Group"],["impl StructuralEq for GroupKind"],["impl StructuralEq for Repetition"],["impl StructuralEq for RepetitionKind"],["impl StructuralEq for RepetitionRange"],["impl StructuralEq for Utf8Sequence"],["impl StructuralEq for Utf8Range"]], -"rlp":[["impl StructuralEq for DecoderError"]], -"tokio":[["impl StructuralEq for RuntimeFlavor"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl<T> StructuralEq for TrySendError<T>"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for RecvError"],["impl StructuralEq for TryRecvError"],["impl StructuralEq for TryAcquireError"],["impl<T> StructuralEq for SetError<T>"]], -"typenum":[["impl StructuralEq for B0"],["impl StructuralEq for B1"],["impl<U: Unsigned + NonZero> StructuralEq for PInt<U>"],["impl<U: Unsigned + NonZero> StructuralEq for NInt<U>"],["impl StructuralEq for Z0"],["impl StructuralEq for UTerm"],["impl<U, B> StructuralEq for UInt<U, B>"],["impl StructuralEq for ATerm"],["impl<V, A> StructuralEq for TArr<V, A>"],["impl StructuralEq for Greater"],["impl StructuralEq for Less"],["impl StructuralEq for Equal"]], -"uint":[["impl StructuralEq for FromStrRadixErrKind"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.StructuralPartialEq.js b/docs/implementors/core/marker/trait.StructuralPartialEq.js deleted file mode 100644 index 1ccfa885910d..000000000000 --- a/docs/implementors/core/marker/trait.StructuralPartialEq.js +++ /dev/null @@ -1,32 +0,0 @@ -(function() {var implementors = { -"aho_corasick":[["impl StructuralPartialEq for MatchKind"],["impl StructuralPartialEq for MatchKind"],["impl StructuralPartialEq for Match"]], -"block_buffer":[["impl StructuralPartialEq for Error"]], -"byteorder":[["impl StructuralPartialEq for BigEndian"],["impl StructuralPartialEq for LittleEndian"]], -"crossbeam_channel":[["impl<T> StructuralPartialEq for SendError<T>"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl<T> StructuralPartialEq for SendTimeoutError<T>"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for RecvTimeoutError"],["impl StructuralPartialEq for TrySelectError"],["impl StructuralPartialEq for SelectTimeoutError"],["impl StructuralPartialEq for TryReadyError"],["impl StructuralPartialEq for ReadyTimeoutError"]], -"crossbeam_utils":[["impl<T> StructuralPartialEq for CachePadded<T>"]], -"crypto_common":[["impl StructuralPartialEq for InvalidLength"]], -"digest":[["impl StructuralPartialEq for InvalidBufferSize"]], -"firewood":[["impl StructuralPartialEq for Hash"],["impl StructuralPartialEq for PartialPath"],["impl StructuralPartialEq for Node"]], -"futures_channel":[["impl StructuralPartialEq for SendError"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl StructuralPartialEq for Canceled"]], -"futures_util":[["impl<T, E> StructuralPartialEq for TryChunksError<T, E>"],["impl StructuralPartialEq for PollNext"],["impl<T> StructuralPartialEq for AllowStdIo<T>"],["impl StructuralPartialEq for Aborted"]], -"getrandom":[["impl StructuralPartialEq for Error"]], -"growthring":[["impl StructuralPartialEq for WALRingId"]], -"hashbrown":[["impl StructuralPartialEq for TryReserveError"]], -"hex":[["impl StructuralPartialEq for FromHexError"]], -"libc":[["impl StructuralPartialEq for group"],["impl StructuralPartialEq for utimbuf"],["impl StructuralPartialEq for timeval"],["impl StructuralPartialEq for timespec"],["impl StructuralPartialEq for rlimit"],["impl StructuralPartialEq for rusage"],["impl StructuralPartialEq for ipv6_mreq"],["impl StructuralPartialEq for hostent"],["impl StructuralPartialEq for iovec"],["impl StructuralPartialEq for pollfd"],["impl StructuralPartialEq for winsize"],["impl StructuralPartialEq for linger"],["impl StructuralPartialEq for sigval"],["impl StructuralPartialEq for itimerval"],["impl StructuralPartialEq for tms"],["impl StructuralPartialEq for servent"],["impl StructuralPartialEq for protoent"],["impl StructuralPartialEq for in_addr"],["impl StructuralPartialEq for ip_mreq"],["impl StructuralPartialEq for ip_mreqn"],["impl StructuralPartialEq for ip_mreq_source"],["impl StructuralPartialEq for sockaddr"],["impl StructuralPartialEq for sockaddr_in"],["impl StructuralPartialEq for sockaddr_in6"],["impl StructuralPartialEq for addrinfo"],["impl StructuralPartialEq for sockaddr_ll"],["impl StructuralPartialEq for fd_set"],["impl StructuralPartialEq for tm"],["impl StructuralPartialEq for sched_param"],["impl StructuralPartialEq for Dl_info"],["impl StructuralPartialEq for lconv"],["impl StructuralPartialEq for in_pktinfo"],["impl StructuralPartialEq for ifaddrs"],["impl StructuralPartialEq for in6_rtmsg"],["impl StructuralPartialEq for arpreq"],["impl StructuralPartialEq for arpreq_old"],["impl StructuralPartialEq for arphdr"],["impl StructuralPartialEq for mmsghdr"],["impl StructuralPartialEq for rlimit64"],["impl StructuralPartialEq for glob_t"],["impl StructuralPartialEq for passwd"],["impl StructuralPartialEq for spwd"],["impl StructuralPartialEq for dqblk"],["impl StructuralPartialEq for signalfd_siginfo"],["impl StructuralPartialEq for itimerspec"],["impl StructuralPartialEq for fsid_t"],["impl StructuralPartialEq for packet_mreq"],["impl StructuralPartialEq for cpu_set_t"],["impl StructuralPartialEq for if_nameindex"],["impl StructuralPartialEq for msginfo"],["impl StructuralPartialEq for sembuf"],["impl StructuralPartialEq for input_event"],["impl StructuralPartialEq for input_id"],["impl StructuralPartialEq for input_absinfo"],["impl StructuralPartialEq for input_keymap_entry"],["impl StructuralPartialEq for input_mask"],["impl StructuralPartialEq for ff_replay"],["impl StructuralPartialEq for ff_trigger"],["impl StructuralPartialEq for ff_envelope"],["impl StructuralPartialEq for ff_constant_effect"],["impl StructuralPartialEq for ff_ramp_effect"],["impl StructuralPartialEq for ff_condition_effect"],["impl StructuralPartialEq for ff_periodic_effect"],["impl StructuralPartialEq for ff_rumble_effect"],["impl StructuralPartialEq for ff_effect"],["impl StructuralPartialEq for uinput_ff_upload"],["impl StructuralPartialEq for uinput_ff_erase"],["impl StructuralPartialEq for uinput_abs_setup"],["impl StructuralPartialEq for dl_phdr_info"],["impl StructuralPartialEq for Elf32_Ehdr"],["impl StructuralPartialEq for Elf64_Ehdr"],["impl StructuralPartialEq for Elf32_Sym"],["impl StructuralPartialEq for Elf64_Sym"],["impl StructuralPartialEq for Elf32_Phdr"],["impl StructuralPartialEq for Elf64_Phdr"],["impl StructuralPartialEq for Elf32_Shdr"],["impl StructuralPartialEq for Elf64_Shdr"],["impl StructuralPartialEq for ucred"],["impl StructuralPartialEq for mntent"],["impl StructuralPartialEq for posix_spawn_file_actions_t"],["impl StructuralPartialEq for posix_spawnattr_t"],["impl StructuralPartialEq for genlmsghdr"],["impl StructuralPartialEq for in6_pktinfo"],["impl StructuralPartialEq for arpd_request"],["impl StructuralPartialEq for inotify_event"],["impl StructuralPartialEq for fanotify_response"],["impl StructuralPartialEq for sockaddr_vm"],["impl StructuralPartialEq for regmatch_t"],["impl StructuralPartialEq for sock_extended_err"],["impl StructuralPartialEq for __c_anonymous_sockaddr_can_tp"],["impl StructuralPartialEq for __c_anonymous_sockaddr_can_j1939"],["impl StructuralPartialEq for can_filter"],["impl StructuralPartialEq for j1939_filter"],["impl StructuralPartialEq for sock_filter"],["impl StructuralPartialEq for sock_fprog"],["impl StructuralPartialEq for seccomp_data"],["impl StructuralPartialEq for nlmsghdr"],["impl StructuralPartialEq for nlmsgerr"],["impl StructuralPartialEq for nlattr"],["impl StructuralPartialEq for file_clone_range"],["impl StructuralPartialEq for __c_anonymous_ifru_map"],["impl StructuralPartialEq for in6_ifreq"],["impl StructuralPartialEq for statx"],["impl StructuralPartialEq for statx_timestamp"],["impl StructuralPartialEq for aiocb"],["impl StructuralPartialEq for __exit_status"],["impl StructuralPartialEq for __timeval"],["impl StructuralPartialEq for glob64_t"],["impl StructuralPartialEq for msghdr"],["impl StructuralPartialEq for cmsghdr"],["impl StructuralPartialEq for termios"],["impl StructuralPartialEq for mallinfo"],["impl StructuralPartialEq for mallinfo2"],["impl StructuralPartialEq for nl_pktinfo"],["impl StructuralPartialEq for nl_mmap_req"],["impl StructuralPartialEq for nl_mmap_hdr"],["impl StructuralPartialEq for rtentry"],["impl StructuralPartialEq for timex"],["impl StructuralPartialEq for ntptimeval"],["impl StructuralPartialEq for regex_t"],["impl StructuralPartialEq for Elf64_Chdr"],["impl StructuralPartialEq for Elf32_Chdr"],["impl StructuralPartialEq for seminfo"],["impl StructuralPartialEq for ptrace_peeksiginfo_args"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_entry"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_exit"],["impl StructuralPartialEq for __c_anonymous_ptrace_syscall_info_seccomp"],["impl StructuralPartialEq for ptrace_syscall_info"],["impl StructuralPartialEq for sigset_t"],["impl StructuralPartialEq for sysinfo"],["impl StructuralPartialEq for msqid_ds"],["impl StructuralPartialEq for semid_ds"],["impl StructuralPartialEq for sigaction"],["impl StructuralPartialEq for statfs"],["impl StructuralPartialEq for flock"],["impl StructuralPartialEq for flock64"],["impl StructuralPartialEq for siginfo_t"],["impl StructuralPartialEq for stack_t"],["impl StructuralPartialEq for stat"],["impl StructuralPartialEq for stat64"],["impl StructuralPartialEq for statfs64"],["impl StructuralPartialEq for statvfs64"],["impl StructuralPartialEq for pthread_attr_t"],["impl StructuralPartialEq for _libc_fpxreg"],["impl StructuralPartialEq for _libc_xmmreg"],["impl StructuralPartialEq for _libc_fpstate"],["impl StructuralPartialEq for user_regs_struct"],["impl StructuralPartialEq for user"],["impl StructuralPartialEq for mcontext_t"],["impl StructuralPartialEq for ipc_perm"],["impl StructuralPartialEq for shmid_ds"],["impl StructuralPartialEq for seccomp_notif_sizes"],["impl StructuralPartialEq for ptrace_rseq_configuration"],["impl StructuralPartialEq for statvfs"],["impl StructuralPartialEq for clone_args"],["impl StructuralPartialEq for sem_t"],["impl StructuralPartialEq for termios2"],["impl StructuralPartialEq for pthread_mutexattr_t"],["impl StructuralPartialEq for pthread_rwlockattr_t"],["impl StructuralPartialEq for pthread_condattr_t"],["impl StructuralPartialEq for fanotify_event_metadata"],["impl StructuralPartialEq for open_how"],["impl StructuralPartialEq for in6_addr"]], -"nix":[["impl StructuralPartialEq for Dir"],["impl<'d> StructuralPartialEq for Iter<'d>"],["impl StructuralPartialEq for OwningIter"],["impl StructuralPartialEq for Entry"],["impl StructuralPartialEq for Type"],["impl StructuralPartialEq for Errno"],["impl StructuralPartialEq for AtFlags"],["impl StructuralPartialEq for OFlag"],["impl StructuralPartialEq for RenameFlags"],["impl StructuralPartialEq for SealFlag"],["impl StructuralPartialEq for FdFlag"],["impl<'a> StructuralPartialEq for FcntlArg<'a>"],["impl StructuralPartialEq for FlockArg"],["impl StructuralPartialEq for SpliceFFlags"],["impl StructuralPartialEq for FallocateFlags"],["impl StructuralPartialEq for PosixFadviseAdvice"],["impl StructuralPartialEq for InterfaceAddress"],["impl StructuralPartialEq for InterfaceAddressIterator"],["impl StructuralPartialEq for InterfaceFlags"],["impl StructuralPartialEq for ModuleInitFlags"],["impl StructuralPartialEq for DeleteModuleFlags"],["impl StructuralPartialEq for MsFlags"],["impl StructuralPartialEq for MntFlags"],["impl StructuralPartialEq for MQ_OFlag"],["impl StructuralPartialEq for MqAttr"],["impl StructuralPartialEq for PollFd"],["impl StructuralPartialEq for PollFlags"],["impl StructuralPartialEq for OpenptyResult"],["impl StructuralPartialEq for PtyMaster"],["impl StructuralPartialEq for CloneFlags"],["impl StructuralPartialEq for CpuSet"],["impl StructuralPartialEq for AioFsyncMode"],["impl StructuralPartialEq for LioMode"],["impl StructuralPartialEq for AioCancelStat"],["impl StructuralPartialEq for EpollFlags"],["impl StructuralPartialEq for EpollOp"],["impl StructuralPartialEq for EpollCreateFlags"],["impl StructuralPartialEq for EpollEvent"],["impl StructuralPartialEq for EfdFlags"],["impl StructuralPartialEq for MemFdCreateFlag"],["impl StructuralPartialEq for ProtFlags"],["impl StructuralPartialEq for MapFlags"],["impl StructuralPartialEq for MRemapFlags"],["impl StructuralPartialEq for MmapAdvise"],["impl StructuralPartialEq for MsFlags"],["impl StructuralPartialEq for MlockAllFlags"],["impl StructuralPartialEq for Persona"],["impl StructuralPartialEq for Request"],["impl StructuralPartialEq for Event"],["impl StructuralPartialEq for Options"],["impl StructuralPartialEq for QuotaType"],["impl StructuralPartialEq for QuotaFmt"],["impl StructuralPartialEq for QuotaValidFlags"],["impl StructuralPartialEq for Dqblk"],["impl StructuralPartialEq for RebootMode"],["impl StructuralPartialEq for Resource"],["impl StructuralPartialEq for UsageWho"],["impl StructuralPartialEq for Usage"],["impl StructuralPartialEq for FdSet"],["impl StructuralPartialEq for Signal"],["impl StructuralPartialEq for SignalIterator"],["impl StructuralPartialEq for SaFlags"],["impl StructuralPartialEq for SigmaskHow"],["impl StructuralPartialEq for SigSet"],["impl StructuralPartialEq for SigHandler"],["impl StructuralPartialEq for SigAction"],["impl StructuralPartialEq for SigevNotify"],["impl StructuralPartialEq for SigEvent"],["impl StructuralPartialEq for SfdFlags"],["impl StructuralPartialEq for SignalFd"],["impl StructuralPartialEq for AddressFamily"],["impl StructuralPartialEq for InetAddr"],["impl StructuralPartialEq for IpAddr"],["impl StructuralPartialEq for Ipv4Addr"],["impl StructuralPartialEq for Ipv6Addr"],["impl StructuralPartialEq for SockaddrIn"],["impl StructuralPartialEq for SockaddrIn6"],["impl StructuralPartialEq for SockAddr"],["impl StructuralPartialEq for NetlinkAddr"],["impl StructuralPartialEq for LinkAddr"],["impl StructuralPartialEq for ReuseAddr"],["impl StructuralPartialEq for ReusePort"],["impl StructuralPartialEq for TcpNoDelay"],["impl StructuralPartialEq for Linger"],["impl StructuralPartialEq for IpAddMembership"],["impl StructuralPartialEq for IpDropMembership"],["impl StructuralPartialEq for Ipv6AddMembership"],["impl StructuralPartialEq for Ipv6DropMembership"],["impl StructuralPartialEq for IpMulticastTtl"],["impl StructuralPartialEq for IpMulticastLoop"],["impl StructuralPartialEq for Priority"],["impl StructuralPartialEq for IpTos"],["impl StructuralPartialEq for Ipv6TClass"],["impl StructuralPartialEq for IpFreebind"],["impl StructuralPartialEq for ReceiveTimeout"],["impl StructuralPartialEq for SendTimeout"],["impl StructuralPartialEq for Broadcast"],["impl StructuralPartialEq for OobInline"],["impl StructuralPartialEq for SocketError"],["impl StructuralPartialEq for DontRoute"],["impl StructuralPartialEq for KeepAlive"],["impl StructuralPartialEq for PeerCredentials"],["impl StructuralPartialEq for TcpKeepIdle"],["impl StructuralPartialEq for TcpMaxSeg"],["impl StructuralPartialEq for TcpKeepCount"],["impl StructuralPartialEq for TcpRepair"],["impl StructuralPartialEq for TcpKeepInterval"],["impl StructuralPartialEq for TcpUserTimeout"],["impl StructuralPartialEq for RcvBuf"],["impl StructuralPartialEq for SndBuf"],["impl StructuralPartialEq for RcvBufForce"],["impl StructuralPartialEq for SndBufForce"],["impl StructuralPartialEq for SockType"],["impl StructuralPartialEq for AcceptConn"],["impl StructuralPartialEq for BindToDevice"],["impl StructuralPartialEq for OriginalDst"],["impl StructuralPartialEq for Ip6tOriginalDst"],["impl StructuralPartialEq for Timestamping"],["impl StructuralPartialEq for ReceiveTimestamp"],["impl StructuralPartialEq for ReceiveTimestampns"],["impl StructuralPartialEq for IpTransparent"],["impl StructuralPartialEq for Mark"],["impl StructuralPartialEq for PassCred"],["impl StructuralPartialEq for TcpCongestion"],["impl StructuralPartialEq for Ipv4PacketInfo"],["impl StructuralPartialEq for Ipv6RecvPacketInfo"],["impl StructuralPartialEq for Ipv4OrigDstAddr"],["impl StructuralPartialEq for UdpGsoSegment"],["impl StructuralPartialEq for UdpGroSegment"],["impl StructuralPartialEq for TxTime"],["impl StructuralPartialEq for RxqOvfl"],["impl StructuralPartialEq for Ipv6V6Only"],["impl StructuralPartialEq for Ipv4RecvErr"],["impl StructuralPartialEq for Ipv6RecvErr"],["impl StructuralPartialEq for IpMtu"],["impl StructuralPartialEq for Ipv4Ttl"],["impl StructuralPartialEq for Ipv6Ttl"],["impl StructuralPartialEq for Ipv6OrigDstAddr"],["impl StructuralPartialEq for Ipv6DontFrag"],["impl StructuralPartialEq for SockType"],["impl StructuralPartialEq for SockProtocol"],["impl StructuralPartialEq for TimestampingFlag"],["impl StructuralPartialEq for SockFlag"],["impl StructuralPartialEq for MsgFlags"],["impl StructuralPartialEq for UnixCredentials"],["impl StructuralPartialEq for IpMembershipRequest"],["impl StructuralPartialEq for Ipv6MembershipRequest"],["impl<'a, 's, S> StructuralPartialEq for RecvMsg<'a, 's, S>"],["impl<'a> StructuralPartialEq for CmsgIterator<'a>"],["impl StructuralPartialEq for ControlMessageOwned"],["impl StructuralPartialEq for Timestamps"],["impl<'a> StructuralPartialEq for ControlMessage<'a>"],["impl StructuralPartialEq for Shutdown"],["impl StructuralPartialEq for SFlag"],["impl StructuralPartialEq for Mode"],["impl StructuralPartialEq for FsType"],["impl StructuralPartialEq for FsFlags"],["impl StructuralPartialEq for Statvfs"],["impl StructuralPartialEq for SysInfo"],["impl StructuralPartialEq for Termios"],["impl StructuralPartialEq for BaudRate"],["impl StructuralPartialEq for SetArg"],["impl StructuralPartialEq for FlushArg"],["impl StructuralPartialEq for FlowArg"],["impl StructuralPartialEq for SpecialCharacterIndices"],["impl StructuralPartialEq for InputFlags"],["impl StructuralPartialEq for OutputFlags"],["impl StructuralPartialEq for ControlFlags"],["impl StructuralPartialEq for LocalFlags"],["impl StructuralPartialEq for Expiration"],["impl StructuralPartialEq for TimerSetTimeFlags"],["impl StructuralPartialEq for TimeSpec"],["impl StructuralPartialEq for TimeVal"],["impl StructuralPartialEq for RemoteIoVec"],["impl<T> StructuralPartialEq for IoVec<T>"],["impl StructuralPartialEq for UtsName"],["impl StructuralPartialEq for WaitPidFlag"],["impl StructuralPartialEq for WaitStatus"],["impl StructuralPartialEq for Id"],["impl StructuralPartialEq for AddWatchFlags"],["impl StructuralPartialEq for InitFlags"],["impl StructuralPartialEq for WatchDescriptor"],["impl StructuralPartialEq for ClockId"],["impl StructuralPartialEq for TimerFlags"],["impl StructuralPartialEq for ClockId"],["impl StructuralPartialEq for UContext"],["impl StructuralPartialEq for Uid"],["impl StructuralPartialEq for Gid"],["impl StructuralPartialEq for Pid"],["impl StructuralPartialEq for PathconfVar"],["impl StructuralPartialEq for SysconfVar"],["impl StructuralPartialEq for ResUid"],["impl StructuralPartialEq for ResGid"],["impl StructuralPartialEq for AccessFlags"],["impl StructuralPartialEq for User"],["impl StructuralPartialEq for Group"]], -"parking_lot":[["impl StructuralPartialEq for WaitTimeoutResult"],["impl StructuralPartialEq for OnceState"]], -"parking_lot_core":[["impl StructuralPartialEq for ParkResult"],["impl StructuralPartialEq for UnparkResult"],["impl StructuralPartialEq for RequeueOp"],["impl StructuralPartialEq for FilterOp"],["impl StructuralPartialEq for UnparkToken"],["impl StructuralPartialEq for ParkToken"]], -"primitive_types":[["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for U128"],["impl StructuralPartialEq for U256"],["impl StructuralPartialEq for U512"]], -"proc_macro2":[["impl StructuralPartialEq for Delimiter"],["impl StructuralPartialEq for Spacing"]], -"rand":[["impl StructuralPartialEq for Bernoulli"],["impl StructuralPartialEq for BernoulliError"],["impl<X: SampleUniform + PartialOrd> StructuralPartialEq for WeightedIndex<X>"],["impl StructuralPartialEq for WeightedError"],["impl<X: SampleUniform> StructuralPartialEq for Uniform<X>"],["impl<X> StructuralPartialEq for UniformInt<X>"],["impl<X> StructuralPartialEq for UniformFloat<X>"],["impl StructuralPartialEq for StepRng"]], -"rand_chacha":[["impl StructuralPartialEq for ChaCha20Core"],["impl StructuralPartialEq for ChaCha12Core"],["impl StructuralPartialEq for ChaCha8Core"]], -"regex":[["impl StructuralPartialEq for Error"],["impl<'t> StructuralPartialEq for Match<'t>"],["impl<'t> StructuralPartialEq for Match<'t>"]], -"regex_syntax":[["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for ErrorKind"],["impl StructuralPartialEq for Span"],["impl StructuralPartialEq for Position"],["impl StructuralPartialEq for WithComments"],["impl StructuralPartialEq for Comment"],["impl StructuralPartialEq for Ast"],["impl StructuralPartialEq for Alternation"],["impl StructuralPartialEq for Concat"],["impl StructuralPartialEq for Literal"],["impl StructuralPartialEq for LiteralKind"],["impl StructuralPartialEq for SpecialLiteralKind"],["impl StructuralPartialEq for HexLiteralKind"],["impl StructuralPartialEq for Class"],["impl StructuralPartialEq for ClassPerl"],["impl StructuralPartialEq for ClassPerlKind"],["impl StructuralPartialEq for ClassAscii"],["impl StructuralPartialEq for ClassAsciiKind"],["impl StructuralPartialEq for ClassUnicode"],["impl StructuralPartialEq for ClassUnicodeKind"],["impl StructuralPartialEq for ClassUnicodeOpKind"],["impl StructuralPartialEq for ClassBracketed"],["impl StructuralPartialEq for ClassSet"],["impl StructuralPartialEq for ClassSetItem"],["impl StructuralPartialEq for ClassSetRange"],["impl StructuralPartialEq for ClassSetUnion"],["impl StructuralPartialEq for ClassSetBinaryOp"],["impl StructuralPartialEq for ClassSetBinaryOpKind"],["impl StructuralPartialEq for Assertion"],["impl StructuralPartialEq for AssertionKind"],["impl StructuralPartialEq for Repetition"],["impl StructuralPartialEq for RepetitionOp"],["impl StructuralPartialEq for RepetitionKind"],["impl StructuralPartialEq for RepetitionRange"],["impl StructuralPartialEq for Group"],["impl StructuralPartialEq for GroupKind"],["impl StructuralPartialEq for CaptureName"],["impl StructuralPartialEq for SetFlags"],["impl StructuralPartialEq for Flags"],["impl StructuralPartialEq for FlagsItem"],["impl StructuralPartialEq for FlagsItemKind"],["impl StructuralPartialEq for Flag"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for Literals"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for ErrorKind"],["impl StructuralPartialEq for Hir"],["impl StructuralPartialEq for HirKind"],["impl StructuralPartialEq for Literal"],["impl StructuralPartialEq for Class"],["impl StructuralPartialEq for ClassUnicode"],["impl StructuralPartialEq for ClassUnicodeRange"],["impl StructuralPartialEq for ClassBytes"],["impl StructuralPartialEq for ClassBytesRange"],["impl StructuralPartialEq for Anchor"],["impl StructuralPartialEq for WordBoundary"],["impl StructuralPartialEq for Group"],["impl StructuralPartialEq for GroupKind"],["impl StructuralPartialEq for Repetition"],["impl StructuralPartialEq for RepetitionKind"],["impl StructuralPartialEq for RepetitionRange"],["impl StructuralPartialEq for Utf8Sequence"],["impl StructuralPartialEq for Utf8Range"]], -"rlp":[["impl StructuralPartialEq for DecoderError"]], -"scan_fmt":[["impl StructuralPartialEq for ScanError"]], -"serde":[["impl StructuralPartialEq for Error"],["impl<'a> StructuralPartialEq for Unexpected<'a>"]], -"tokio":[["impl StructuralPartialEq for RuntimeFlavor"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl<T> StructuralPartialEq for TrySendError<T>"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for RecvError"],["impl StructuralPartialEq for TryRecvError"],["impl StructuralPartialEq for TryAcquireError"],["impl<T> StructuralPartialEq for SetError<T>"]], -"typenum":[["impl StructuralPartialEq for B0"],["impl StructuralPartialEq for B1"],["impl<U: Unsigned + NonZero> StructuralPartialEq for PInt<U>"],["impl<U: Unsigned + NonZero> StructuralPartialEq for NInt<U>"],["impl StructuralPartialEq for Z0"],["impl StructuralPartialEq for UTerm"],["impl<U, B> StructuralPartialEq for UInt<U, B>"],["impl StructuralPartialEq for ATerm"],["impl<V, A> StructuralPartialEq for TArr<V, A>"],["impl StructuralPartialEq for Greater"],["impl StructuralPartialEq for Less"],["impl StructuralPartialEq for Equal"]], -"uint":[["impl StructuralPartialEq for FromStrRadixErrKind"],["impl StructuralPartialEq for FromDecStrErr"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Sync.js b/docs/implementors/core/marker/trait.Sync.js deleted file mode 100644 index 2162895de83e..000000000000 --- a/docs/implementors/core/marker/trait.Sync.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Sync for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Sync for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> Sync for AhoCorasick<S>where
        S: Sync,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Sync for FindIter<'a, 'b, S>where
        S: Sync,
    ",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Sync for FindOverlappingIter<'a, 'b, S>where
        S: Sync,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Sync for StreamFindIter<'a, R, S>where
        R: Sync,
        S: Sync,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Sync for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Sync for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Sync for Error",1,["aho_corasick::error::Error"]],["impl Sync for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Sync for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Sync for Config",1,["aho_corasick::packed::api::Config"]],["impl Sync for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Sync for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Sync for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Sync for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl Sync for Error",1,["aiofut::Error"]],["impl Sync for AIO",1,["aiofut::AIO"]],["impl Sync for AIOFuture",1,["aiofut::AIOFuture"]],["impl Sync for AIONotifier",1,["aiofut::AIONotifier"]],["impl Sync for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !Sync for AIOManager",1,["aiofut::AIOManager"]],["impl !Sync for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Sync for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl Sync for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Sync for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Sync for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Sync for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Sync for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Sync for Config",1,["bincode::config::legacy::Config"]],["impl Sync for Bounded",1,["bincode::config::limit::Bounded"]],["impl Sync for Infinite",1,["bincode::config::limit::Infinite"]],["impl Sync for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Sync for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Sync for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Sync for WithOtherLimit<O, L>where
        L: Sync,
        O: Sync,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Sync for WithOtherEndian<O, E>where
        E: Sync,
        O: Sync,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Sync for WithOtherIntEncoding<O, I>where
        I: Sync,
        O: Sync,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Sync for WithOtherTrailing<O, T>where
        O: Sync,
        T: Sync,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Sync for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Sync for IoReader<R>where
        R: Sync,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> Sync for Deserializer<R, O>where
        O: Sync,
        R: Sync,
    ",1,["bincode::de::Deserializer"]],["impl Sync for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Sync for Serializer<W, O>where
        O: Sync,
        W: Sync,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl Sync for Eager",1,["block_buffer::Eager"]],["impl Sync for Lazy",1,["block_buffer::Lazy"]],["impl Sync for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Sync for BlockBuffer<BlockSize, Kind>where
        Kind: Sync,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl Sync for BigEndian",1,["byteorder::BigEndian"]],["impl Sync for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> Sync for Chain<T, U>where
        T: Sync,
        U: Sync,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> Sync for IntoIter<T>where
        T: Sync,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> Sync for Limit<T>where
        T: Sync,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> Sync for Reader<B>where
        B: Sync,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> Sync for Take<T>where
        T: Sync,
    ",1,["bytes::buf::take::Take"]],["impl Sync for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Sync for Writer<B>where
        B: Sync,
    ",1,["bytes::buf::writer::Writer"]],["impl Sync for Bytes"],["impl Sync for BytesMut"]], -"crc":[["impl<W> Sync for Crc<W>where
        W: Sync,
    ",1,["crc::Crc"]],["impl<'a, W> Sync for Digest<'a, W>where
        W: Sync,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> Sync for Algorithm<W>where
        W: Sync,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<'a, T> Sync for Iter<'a, T>where
        T: Send,
    ",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Sync for TryIter<'a, T>where
        T: Send,
    ",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Sync for IntoIter<T>where
        T: Send,
    ",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Sync for SendError<T>where
        T: Sync,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> Sync for TrySendError<T>where
        T: Sync,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Sync for SendTimeoutError<T>where
        T: Sync,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Sync for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Sync for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Sync for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Sync for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Sync for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Sync for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Sync for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !Sync for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T: Send> Sync for Sender<T>"],["impl<T: Send> Sync for Receiver<T>"],["impl Sync for Select<'_>"]], -"crossbeam_utils":[["impl !Sync for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl !Sync for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Sync for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'scope, 'env> Sync for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<T: Send> Sync for AtomicCell<T>"],["impl<T: Sync> Sync for CachePadded<T>"],["impl Sync for Unparker"],["impl<T: ?Sized + Send + Sync> Sync for ShardedLock<T>"],["impl<T: ?Sized + Sync> Sync for ShardedLockReadGuard<'_, T>"],["impl<T: ?Sized + Sync> Sync for ShardedLockWriteGuard<'_, T>"],["impl Sync for Scope<'_>"],["impl<T> Sync for ScopedJoinHandle<'_, T>"]], -"crypto_common":[["impl Sync for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> Sync for CtVariableCoreWrapper<T, OutSize, O>where
        O: Sync,
        OutSize: Sync,
        T: Sync,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Sync for RtVariableCoreWrapper<T>where
        T: Sync,
        <T as BufferKindUser>::BufferKind: Sync,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Sync for CoreWrapper<T>where
        T: Sync,
        <T as BufferKindUser>::BufferKind: Sync,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Sync for XofReaderCoreWrapper<T>where
        T: Sync,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Sync for TruncSide",1,["digest::core_api::TruncSide"]],["impl Sync for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Sync for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl Sync for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Sync for WALConfig",1,["firewood::storage::WALConfig"]],["impl Sync for DBError",1,["firewood::db::DBError"]],["impl Sync for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Sync for DBConfig",1,["firewood::db::DBConfig"]],["impl !Sync for DBRev",1,["firewood::db::DBRev"]],["impl !Sync for DB",1,["firewood::db::DB"]],["impl<'a> !Sync for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !Sync for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Sync for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Sync for Hash",1,["firewood::merkle::Hash"]],["impl Sync for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !Sync for Node",1,["firewood::merkle::Node"]],["impl !Sync for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !Sync for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !Sync for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Sync for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Sync for Proof",1,["firewood::proof::Proof"]],["impl Sync for ProofError",1,["firewood::proof::ProofError"]],["impl Sync for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> Sync for Sender<T>where
        T: Send,
    ",1,["futures_channel::mpsc::Sender"]],["impl<T> Sync for UnboundedSender<T>where
        T: Send,
    ",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> Sync for Receiver<T>where
        T: Send,
    ",1,["futures_channel::mpsc::Receiver"]],["impl<T> Sync for UnboundedReceiver<T>where
        T: Send,
    ",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl Sync for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Sync for TrySendError<T>where
        T: Sync,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl Sync for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> Sync for Receiver<T>where
        T: Send,
    ",1,["futures_channel::oneshot::Receiver"]],["impl<T> Sync for Sender<T>where
        T: Send,
    ",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> Sync for Cancellation<'a, T>where
        T: Send,
    ",1,["futures_channel::oneshot::Cancellation"]],["impl Sync for Canceled",1,["futures_channel::oneshot::Canceled"]]], -"futures_executor":[["impl !Sync for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !Sync for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Sync for BlockingStream<S>where
        S: Sync,
    ",1,["futures_executor::local_pool::BlockingStream"]],["impl Sync for Enter",1,["futures_executor::enter::Enter"]],["impl Sync for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl Sync for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Sync for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !Sync for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !Sync for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], -"futures_util":[["impl<Fut> Sync for Fuse<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> Sync for CatchUnwind<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> Sync for RemoteHandle<T>where
        T: Send,
    ",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Sync for Remote<Fut>where
        Fut: Sync,
        <Fut as Future>::Output: Send,
    ",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> Sync for Shared<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send + Sync,
    ",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> Sync for WeakShared<Fut>where
        Fut: Send,
        <Fut as Future>::Output: Send + Sync,
    ",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Sync for Flatten<F>where
        F: Sync,
        <F as Future>::Output: Sync,
    ",1,["futures_util::future::future::Flatten"]],["impl<F> Sync for FlattenStream<F>where
        F: Sync,
        <F as Future>::Output: Sync,
    ",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> Sync for Map<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::future::Map"]],["impl<F> Sync for IntoStream<F>where
        F: Sync,
    ",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> Sync for MapInto<Fut, T>where
        Fut: Sync,
    ",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> Sync for Then<Fut1, Fut2, F>where
        F: Sync,
        Fut1: Sync,
        Fut2: Sync,
    ",1,["futures_util::future::future::Then"]],["impl<Fut, F> Sync for Inspect<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::future::Inspect"]],["impl<Fut> Sync for NeverError<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::future::NeverError"]],["impl<Fut> Sync for UnitError<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::future::UnitError"]],["impl<Fut> Sync for IntoFuture<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> Sync for TryFlatten<Fut1, Fut2>where
        Fut1: Sync,
        Fut2: Sync,
    ",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> Sync for TryFlattenStream<Fut>where
        Fut: Sync,
        <Fut as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> Sync for FlattenSink<Fut, Si>where
        Fut: Sync,
        Si: Sync,
    ",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> Sync for AndThen<Fut1, Fut2, F>where
        F: Sync,
        Fut1: Sync,
        Fut2: Sync,
    ",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> Sync for OrElse<Fut1, Fut2, F>where
        F: Sync,
        Fut1: Sync,
        Fut2: Sync,
    ",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> Sync for ErrInto<Fut, E>where
        Fut: Sync,
    ",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> Sync for OkInto<Fut, E>where
        Fut: Sync,
    ",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> Sync for InspectOk<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> Sync for InspectErr<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> Sync for MapOk<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> Sync for MapErr<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> Sync for MapOkOrElse<Fut, F, G>where
        F: Sync,
        Fut: Sync,
        G: Sync,
    ",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> Sync for UnwrapOrElse<Fut, F>where
        F: Sync,
        Fut: Sync,
    ",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> Sync for Lazy<F>where
        F: Sync,
    ",1,["futures_util::future::lazy::Lazy"]],["impl<T> Sync for Pending<T>where
        T: Sync,
    ",1,["futures_util::future::pending::Pending"]],["impl<Fut> Sync for MaybeDone<Fut>where
        Fut: Sync,
        <Fut as Future>::Output: Sync,
    ",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> Sync for TryMaybeDone<Fut>where
        Fut: Sync,
        <Fut as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> Sync for OptionFuture<F>where
        F: Sync,
    ",1,["futures_util::future::option::OptionFuture"]],["impl<F> Sync for PollFn<F>where
        F: Sync,
    ",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> Sync for PollImmediate<T>where
        T: Sync,
    ",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> Sync for Ready<T>where
        T: Sync,
    ",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> Sync for Join<Fut1, Fut2>where
        Fut1: Sync,
        Fut2: Sync,
        <Fut1 as Future>::Output: Sync,
        <Fut2 as Future>::Output: Sync,
    ",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> Sync for Join3<Fut1, Fut2, Fut3>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        <Fut1 as Future>::Output: Sync,
        <Fut2 as Future>::Output: Sync,
        <Fut3 as Future>::Output: Sync,
    ",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> Sync for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        Fut4: Sync,
        <Fut1 as Future>::Output: Sync,
        <Fut2 as Future>::Output: Sync,
        <Fut3 as Future>::Output: Sync,
        <Fut4 as Future>::Output: Sync,
    ",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Sync for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        Fut4: Sync,
        Fut5: Sync,
        <Fut1 as Future>::Output: Sync,
        <Fut2 as Future>::Output: Sync,
        <Fut3 as Future>::Output: Sync,
        <Fut4 as Future>::Output: Sync,
        <Fut5 as Future>::Output: Sync,
    ",1,["futures_util::future::join::Join5"]],["impl<F> Sync for JoinAll<F>where
        F: Sync,
        <F as Future>::Output: Sync,
    ",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> Sync for Select<A, B>where
        A: Sync,
        B: Sync,
    ",1,["futures_util::future::select::Select"]],["impl<Fut> Sync for SelectAll<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> Sync for TryJoin<Fut1, Fut2>where
        Fut1: Sync,
        Fut2: Sync,
        <Fut1 as TryFuture>::Ok: Sync,
        <Fut2 as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> Sync for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        <Fut1 as TryFuture>::Ok: Sync,
        <Fut2 as TryFuture>::Ok: Sync,
        <Fut3 as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> Sync for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        Fut4: Sync,
        <Fut1 as TryFuture>::Ok: Sync,
        <Fut2 as TryFuture>::Ok: Sync,
        <Fut3 as TryFuture>::Ok: Sync,
        <Fut4 as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> Sync for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: Sync,
        Fut2: Sync,
        Fut3: Sync,
        Fut4: Sync,
        Fut5: Sync,
        <Fut1 as TryFuture>::Ok: Sync,
        <Fut2 as TryFuture>::Ok: Sync,
        <Fut3 as TryFuture>::Ok: Sync,
        <Fut4 as TryFuture>::Ok: Sync,
        <Fut5 as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> Sync for TryJoinAll<F>where
        F: Sync,
        <F as TryFuture>::Error: Sync,
        <F as TryFuture>::Ok: Sync,
    ",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Sync for TrySelect<A, B>where
        A: Sync,
        B: Sync,
    ",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> Sync for SelectOk<Fut>where
        Fut: Sync,
    ",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> Sync for Either<A, B>where
        A: Sync,
        B: Sync,
    ",1,["futures_util::future::either::Either"]],["impl Sync for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Sync for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> Sync for Abortable<T>where
        T: Sync,
    ",1,["futures_util::abortable::Abortable"]],["impl Sync for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> Sync for Chain<St1, St2>where
        St1: Sync,
        St2: Sync,
    ",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> Sync for Collect<St, C>where
        C: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> Sync for Unzip<St, FromA, FromB>where
        FromA: Sync,
        FromB: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> Sync for Concat<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> Sync for Cycle<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> Sync for Enumerate<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> Sync for Filter<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> Sync for FilterMap<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> Sync for Fold<St, Fut, T, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        T: Sync,
    ",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> Sync for ForEach<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> Sync for Fuse<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> Sync for StreamFuture<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> Sync for Map<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> Sync for Next<'a, St>where
        St: Sync,
    ",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> Sync for SelectNextSome<'a, St>where
        St: Sync,
    ",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> Sync for Peekable<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> Sync for Peek<'a, St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> Sync for PeekMut<'a, St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> Sync for NextIf<'a, St, F>where
        F: Sync,
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> Sync for NextIfEq<'a, St, T>where
        St: Sync,
        T: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> Sync for Skip<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> Sync for SkipWhile<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> Sync for Take<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> Sync for TakeWhile<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> Sync for TakeUntil<St, Fut>where
        Fut: Sync,
        St: Sync,
        <Fut as Future>::Output: Sync,
    ",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> Sync for Then<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> Sync for Zip<St1, St2>where
        St1: Sync,
        St2: Sync,
        <St1 as Stream>::Item: Sync,
        <St2 as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> Sync for Chunks<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> Sync for ReadyChunks<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> Sync for Scan<St, S, Fut, F>where
        F: Sync,
        Fut: Sync,
        S: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> Sync for BufferUnordered<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> Sync for Buffered<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
        <<St as Stream>::Item as Future>::Output: Sync,
    ",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> Sync for ForEachConcurrent<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> Sync for SplitStream<S>where
        S: Send,
    ",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> Sync for SplitSink<S, Item>where
        Item: Sync,
        S: Send,
    ",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> Sync for ReuniteError<T, Item>where
        Item: Sync,
        T: Send,
    ",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> Sync for CatchUnwind<St>where
        St: Sync,
    ",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> Sync for Flatten<St>where
        St: Sync,
        <St as Stream>::Item: Sync,
    ",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> Sync for Forward<St, Si>where
        Si: Sync,
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::stream::Forward"]],["impl<St, F> Sync for Inspect<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> Sync for FlatMap<St, U, F>where
        F: Sync,
        St: Sync,
        U: Sync,
    ",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> Sync for AndThen<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> Sync for IntoStream<St>where
        St: Sync,
    ",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> Sync for OrElse<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> Sync for TryNext<'a, St>where
        St: Sync,
    ",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> Sync for TryForEach<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> Sync for TryFilter<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> Sync for TryFilterMap<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> Sync for TryFlatten<St>where
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> Sync for TryCollect<St, C>where
        C: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> Sync for TryConcat<St>where
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> Sync for TryChunks<St>where
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> Sync for TryChunksError<T, E>where
        E: Sync,
        T: Sync,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> Sync for TryFold<St, Fut, T, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        T: Sync,
    ",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> Sync for TryUnfold<T, F, Fut>where
        F: Sync,
        Fut: Sync,
        T: Sync,
    ",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> Sync for TrySkipWhile<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> Sync for TryTakeWhile<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> Sync for TryBufferUnordered<St>where
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> Sync for TryBuffered<St>where
        St: Sync,
        <<St as TryStream>::Ok as TryFuture>::Error: Sync,
        <St as TryStream>::Ok: Sync,
        <<St as TryStream>::Ok as TryFuture>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> Sync for TryForEachConcurrent<St, Fut, F>where
        F: Sync,
        Fut: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> Sync for IntoAsyncRead<St>where
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> Sync for ErrInto<St, E>where
        St: Sync,
    ",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> Sync for InspectOk<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> Sync for InspectErr<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> Sync for MapOk<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> Sync for MapErr<St, F>where
        F: Sync,
        St: Sync,
    ",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> Sync for Iter<I>where
        I: Sync,
    ",1,["futures_util::stream::iter::Iter"]],["impl<T> Sync for Repeat<T>where
        T: Sync,
    ",1,["futures_util::stream::repeat::Repeat"]],["impl<F> Sync for RepeatWith<F>where
        F: Sync,
    ",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> Sync for Empty<T>where
        T: Sync,
    ",1,["futures_util::stream::empty::Empty"]],["impl<Fut> Sync for Once<Fut>where
        Fut: Sync,
    ",1,["futures_util::stream::once::Once"]],["impl<T> Sync for Pending<T>where
        T: Sync,
    ",1,["futures_util::stream::pending::Pending"]],["impl<F> Sync for PollFn<F>where
        F: Sync,
    ",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> Sync for PollImmediate<S>where
        S: Sync,
    ",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> Sync for Select<St1, St2>where
        St1: Sync,
        St2: Sync,
    ",1,["futures_util::stream::select::Select"]],["impl Sync for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> Sync for SelectWithStrategy<St1, St2, Clos, State>where
        Clos: Sync,
        St1: Sync,
        St2: Sync,
        State: Sync,
    ",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> Sync for Unfold<T, F, Fut>where
        F: Sync,
        Fut: Sync,
        T: Sync,
    ",1,["futures_util::stream::unfold::Unfold"]],["impl<T> Sync for FuturesOrdered<T>where
        T: Sync,
        <T as Future>::Output: Sync,
    ",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> Sync for IterMut<'a, Fut>where
        Fut: Sync,
    ",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Sync for Iter<'a, Fut>where
        Fut: Sync,
    ",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<St> Sync for SelectAll<St>where
        St: Sync,
    ",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> Sync for Iter<'a, St>where
        St: Sync,
    ",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Sync for IterMut<'a, St>where
        St: Sync,
    ",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Sync for IntoIter<St>where
        St: Sync,
    ",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> Sync for Close<'a, Si, Item>where
        Si: Sync,
    ",1,["futures_util::sink::close::Close"]],["impl<T> Sync for Drain<T>where
        T: Sync,
    ",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> Sync for Fanout<Si1, Si2>where
        Si1: Sync,
        Si2: Sync,
    ",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> Sync for Feed<'a, Si, Item>where
        Item: Sync,
        Si: Sync,
    ",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> Sync for Flush<'a, Si, Item>where
        Si: Sync,
    ",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> Sync for SinkErrInto<Si, Item, E>where
        Si: Sync,
    ",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> Sync for SinkMapErr<Si, F>where
        F: Sync,
        Si: Sync,
    ",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> Sync for Send<'a, Si, Item>where
        Item: Sync,
        Si: Sync,
    ",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> Sync for SendAll<'a, Si, St>where
        Si: Sync,
        St: Sync,
        <St as TryStream>::Ok: Sync,
    ",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> Sync for Unfold<T, F, R>where
        F: Sync,
        R: Sync,
        T: Sync,
    ",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> Sync for With<Si, Item, U, Fut, F>where
        F: Sync,
        Fut: Sync,
        Si: Sync,
    ",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> Sync for WithFlatMap<Si, Item, U, St, F>where
        F: Sync,
        Item: Sync,
        Si: Sync,
        St: Sync,
    ",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> Sync for Buffer<Si, Item>where
        Item: Sync,
        Si: Sync,
    ",1,["futures_util::sink::buffer::Buffer"]],["impl<T> Sync for AllowStdIo<T>where
        T: Sync,
    ",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> Sync for BufReader<R>where
        R: Sync,
    ",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> Sync for SeeKRelative<'a, R>where
        R: Sync,
    ",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> Sync for BufWriter<W>where
        W: Sync,
    ",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> Sync for LineWriter<W>where
        W: Sync,
    ",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> Sync for Chain<T, U>where
        T: Sync,
        U: Sync,
    ",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> Sync for Close<'a, W>where
        W: Sync,
    ",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> Sync for Copy<'a, R, W>where
        R: Sync,
        W: Sync,
    ",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> Sync for CopyBuf<'a, R, W>where
        R: Sync,
        W: Sync,
    ",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W: ?Sized> Sync for CopyBufAbortable<'a, R, W>where
        R: Sync,
        W: Sync,
    ",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> Sync for Cursor<T>where
        T: Sync,
    ",1,["futures_util::io::cursor::Cursor"]],["impl Sync for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> Sync for FillBuf<'a, R>where
        R: Sync,
    ",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> Sync for Flush<'a, W>where
        W: Sync,
    ",1,["futures_util::io::flush::Flush"]],["impl<W, Item> Sync for IntoSink<W, Item>where
        Item: Sync,
        W: Sync,
    ",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> Sync for Lines<R>where
        R: Sync,
    ",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> Sync for Read<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> Sync for ReadVectored<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> Sync for ReadExact<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> Sync for ReadLine<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> Sync for ReadToEnd<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> Sync for ReadToString<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> Sync for ReadUntil<'a, R>where
        R: Sync,
    ",1,["futures_util::io::read_until::ReadUntil"]],["impl Sync for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> Sync for Seek<'a, S>where
        S: Sync,
    ",1,["futures_util::io::seek::Seek"]],["impl Sync for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Sync for ReadHalf<T>where
        T: Send,
    ",1,["futures_util::io::split::ReadHalf"]],["impl<T> Sync for WriteHalf<T>where
        T: Send,
    ",1,["futures_util::io::split::WriteHalf"]],["impl<T> Sync for ReuniteError<T>where
        T: Send,
    ",1,["futures_util::io::split::ReuniteError"]],["impl<R> Sync for Take<R>where
        R: Sync,
    ",1,["futures_util::io::take::Take"]],["impl<T> Sync for Window<T>where
        T: Sync,
    ",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> Sync for Write<'a, W>where
        W: Sync,
    ",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> Sync for WriteVectored<'a, W>where
        W: Sync,
    ",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> Sync for WriteAll<'a, W>where
        W: Sync,
    ",1,["futures_util::io::write_all::WriteAll"]],["impl<Fut: Sync> Sync for IterPinRef<'_, Fut>"],["impl<Fut: Sync> Sync for IterPinMut<'_, Fut>"],["impl<Fut: Sync + Unpin> Sync for IntoIter<Fut>"],["impl<Fut: Sync> Sync for FuturesUnordered<Fut>"],["impl<T: ?Sized + Send> Sync for Mutex<T>"],["impl<T: ?Sized> Sync for MutexLockFuture<'_, T>"],["impl<T: ?Sized> Sync for OwnedMutexLockFuture<T>"],["impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T>"],["impl<T: ?Sized + Sync> Sync for OwnedMutexGuard<T>"],["impl<T: ?Sized + Sync, U: ?Sized + Sync> Sync for MappedMutexGuard<'_, T, U>"]], -"generic_array":[["impl<T, N> Sync for GenericArrayIter<T, N>where
        T: Sync,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T: Sync, N: ArrayLength<T>> Sync for GenericArray<T, N>"]], -"getrandom":[["impl Sync for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl Sync for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !Sync for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl Sync for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Sync for WALLoader",1,["growthring::wal::WALLoader"]],["impl !Sync for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !Sync for WALStoreAIO",1,["growthring::WALStoreAIO"]]], -"hashbrown":[["impl<K, V, S, A> Sync for HashMap<K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Sync for Iter<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Sync for IterMut<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Sync for IntoIter<K, V, A>where
        A: Sync,
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Sync for IntoKeys<K, V, A>where
        A: Sync,
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Sync for IntoValues<K, V, A>where
        A: Sync,
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Sync for Keys<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Sync for Values<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Sync for Drain<'a, K, V, A>where
        A: Copy + Sync,
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Sync for DrainFilter<'a, K, V, F, A>where
        A: Sync,
        F: Sync,
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Sync for ValuesMut<'a, K, V>where
        K: Sync,
        V: Sync,
    ",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Sync for RawEntryBuilderMut<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Sync for RawEntryMut<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Sync for RawVacantEntryMut<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Sync for RawEntryBuilder<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Sync for Entry<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Sync for VacantEntry<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Sync for EntryRef<'a, 'b, K, Q, V, S, A>where
        A: Sync,
        K: Sync,
        Q: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Sync for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
        A: Sync,
        K: Sync,
        Q: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Sync for OccupiedError<'a, K, V, S, A>where
        A: Sync,
        K: Sync,
        S: Sync,
        V: Sync,
    ",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Sync for HashSet<T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> Sync for Iter<'a, K>where
        K: Sync,
    ",1,["hashbrown::set::Iter"]],["impl<K, A> Sync for IntoIter<K, A>where
        A: Sync,
        K: Sync,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Sync for Drain<'a, K, A>where
        A: Copy + Sync,
        K: Sync,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Sync for DrainFilter<'a, K, F, A>where
        A: Sync,
        F: Sync,
        K: Sync,
    ",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Sync for Intersection<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Sync for Difference<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Sync for SymmetricDifference<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Sync for Union<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Sync for Entry<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Sync for OccupiedEntry<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Sync for VacantEntry<'a, T, S, A>where
        A: Sync,
        S: Sync,
        T: Sync,
    ",1,["hashbrown::set::VacantEntry"]],["impl Sync for TryReserveError",1,["hashbrown::TryReserveError"]],["impl<K, V, S, A> Sync for RawOccupiedEntryMut<'_, K, V, S, A>where
        K: Sync,
        V: Sync,
        S: Sync,
        A: Sync + Allocator + Clone,
    "],["impl<K, V, S, A> Sync for OccupiedEntry<'_, K, V, S, A>where
        K: Sync,
        V: Sync,
        S: Sync,
        A: Sync + Allocator + Clone,
    "],["impl<'a, 'b, K, Q, V, S, A> Sync for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Sync,
        Q: Sync + ?Sized,
        V: Sync,
        S: Sync,
        A: Sync + Allocator + Clone,
    "]], -"heck":[["impl<T> Sync for AsKebabCase<T>where
        T: Sync,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> Sync for AsLowerCamelCase<T>where
        T: Sync,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Sync for AsShoutyKebabCase<T>where
        T: Sync,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Sync for AsShoutySnakeCase<T>where
        T: Sync,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Sync for AsSnakeCase<T>where
        T: Sync,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> Sync for AsTitleCase<T>where
        T: Sync,
    ",1,["heck::title::AsTitleCase"]],["impl<T> Sync for AsUpperCamelCase<T>where
        T: Sync,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl Sync for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl Sync for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Sync for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Sync for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Sync for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Sync for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Sync for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Sync for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Sync for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl !Sync for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Sync for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Sync for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Sync for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Sync for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Sync for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Sync for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Sync for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Sync for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Sync for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl !Sync for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl !Sync for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Sync for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Sync for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Sync for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Sync for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Sync for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl !Sync for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Sync for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Sync for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Sync for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Sync for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Sync for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Sync for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Sync for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl !Sync for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Sync for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Sync for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl !Sync for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl !Sync for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Sync for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Sync for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Sync for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Sync for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Sync for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Sync for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Sync for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl !Sync for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Sync for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Sync for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl !Sync for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Sync for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Sync for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Sync for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Sync for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Sync for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Sync for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Sync for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Sync for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Sync for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Sync for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Sync for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Sync for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Sync for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Sync for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl !Sync for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl !Sync for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl !Sync for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Sync for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Sync for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Sync for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Sync for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Sync for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Sync for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl !Sync for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Sync for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Sync for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Sync for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Sync for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Sync for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Sync for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Sync for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Sync for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Sync for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Sync for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Sync for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Sync for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Sync for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl !Sync for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Sync for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Sync for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Sync for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Sync for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Sync for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl !Sync for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Sync for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Sync for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Sync for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Sync for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Sync for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Sync for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Sync for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Sync for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Sync for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl !Sync for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl !Sync for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Sync for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Sync for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Sync for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Sync for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Sync for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Sync for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Sync for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Sync for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Sync for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Sync for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Sync for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Sync for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Sync for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Sync for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl !Sync for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Sync for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Sync for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Sync for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Sync for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Sync for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Sync for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Sync for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Sync for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Sync for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Sync for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Sync for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Sync for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Sync for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Sync for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Sync for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl !Sync for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl !Sync for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Sync for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Sync for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Sync for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Sync for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Sync for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Sync for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Sync for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Sync for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Sync for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Sync for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Sync for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Sync for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Sync for timezone",1,["libc::unix::linux_like::timezone"]],["impl Sync for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Sync for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Sync for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Sync for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Sync for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Sync for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Sync for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl !Sync for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Sync for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Sync for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl !Sync for tm",1,["libc::unix::linux_like::tm"]],["impl Sync for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl !Sync for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl !Sync for lconv",1,["libc::unix::linux_like::lconv"]],["impl Sync for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl !Sync for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Sync for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Sync for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Sync for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Sync for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl !Sync for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Sync for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Sync for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Sync for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Sync for utsname",1,["libc::unix::linux_like::utsname"]],["impl !Sync for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Sync for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Sync for DIR",1,["libc::unix::DIR"]],["impl !Sync for group",1,["libc::unix::group"]],["impl Sync for utimbuf",1,["libc::unix::utimbuf"]],["impl Sync for timeval",1,["libc::unix::timeval"]],["impl Sync for timespec",1,["libc::unix::timespec"]],["impl Sync for rlimit",1,["libc::unix::rlimit"]],["impl Sync for rusage",1,["libc::unix::rusage"]],["impl Sync for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl !Sync for hostent",1,["libc::unix::hostent"]],["impl !Sync for iovec",1,["libc::unix::iovec"]],["impl Sync for pollfd",1,["libc::unix::pollfd"]],["impl Sync for winsize",1,["libc::unix::winsize"]],["impl Sync for linger",1,["libc::unix::linger"]],["impl !Sync for sigval",1,["libc::unix::sigval"]],["impl Sync for itimerval",1,["libc::unix::itimerval"]],["impl Sync for tms",1,["libc::unix::tms"]],["impl !Sync for servent",1,["libc::unix::servent"]],["impl !Sync for protoent",1,["libc::unix::protoent"]],["impl Sync for FILE",1,["libc::unix::FILE"]],["impl Sync for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<'a, R, T: ?Sized> Sync for RwLockReadGuard<'a, R, T>where
        R: Sync,
        T: Send + Sync,
        <R as RawRwLock>::GuardMarker: Sync,
    ",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Sync for RwLockWriteGuard<'a, R, T>where
        R: Sync,
        T: Send + Sync,
        <R as RawRwLock>::GuardMarker: Sync,
    ",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl Sync for GuardSend",1,["lock_api::GuardSend"]],["impl Sync for GuardNoSend"],["impl<R: RawMutex + Sync, T: ?Sized + Send> Sync for Mutex<R, T>"],["impl<'a, R: RawMutex + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MappedMutexGuard<'a, R, T>"],["impl<R: RawMutex + Sync, G: GetThreadId + Sync> Sync for RawReentrantMutex<R, G>"],["impl<R: RawMutex + Sync, G: GetThreadId + Sync, T: ?Sized + Send> Sync for ReentrantMutex<R, G, T>"],["impl<'a, R: RawMutex + Sync + 'a, G: GetThreadId + Sync + 'a, T: ?Sized + Sync + 'a> Sync for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + Sync + 'a, G: GetThreadId + Sync + 'a, T: ?Sized + Sync + 'a> Sync for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<R: RawRwLock + Sync, T: ?Sized + Send + Sync> Sync for RwLock<R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + Sync + 'a> Sync for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Sync for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync + 'a> Sync for MappedRwLockWriteGuard<'a, R, T>"]], -"lru":[["impl<K, V> Sync for IntoIter<K, V>where
        K: Sync,
        V: Sync,
    ",1,["lru::IntoIter"]],["impl<K: Sync, V: Sync, S: Sync> Sync for LruCache<K, V, S>"],["impl<'a, K: Sync, V: Sync> Sync for Iter<'a, K, V>"],["impl<'a, K: Sync, V: Sync> Sync for IterMut<'a, K, V>"]], -"memchr":[["impl<'a> Sync for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Sync for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Sync for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Sync for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Sync for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Sync for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Sync for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Sync for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Sync for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl !Sync for Dir",1,["nix::dir::Dir"]],["impl<'d> !Sync for Iter<'d>",1,["nix::dir::Iter"]],["impl !Sync for OwningIter",1,["nix::dir::OwningIter"]],["impl Sync for Entry",1,["nix::dir::Entry"]],["impl Sync for Type",1,["nix::dir::Type"]],["impl Sync for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Sync for Errno",1,["nix::errno::consts::Errno"]],["impl Sync for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Sync for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Sync for OFlag",1,["nix::fcntl::OFlag"]],["impl Sync for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Sync for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Sync for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Sync for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Sync for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Sync for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Sync for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Sync for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl !Sync for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl !Sync for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl !Sync for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> !Sync for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Sync for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Sync for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Sync for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Sync for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Sync for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Sync for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Sync for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Sync for MqdT",1,["nix::mqueue::MqdT"]],["impl Sync for PollFd",1,["nix::poll::PollFd"]],["impl Sync for PollFlags",1,["nix::poll::PollFlags"]],["impl Sync for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Sync for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Sync for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Sync for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Sync for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Sync for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Sync for LioMode",1,["nix::sys::aio::LioMode"]],["impl Sync for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl Sync for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> Sync for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> Sync for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Sync for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Sync for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Sync for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Sync for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Sync for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Sync for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Sync for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Sync for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Sync for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Sync for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Sync for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Sync for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Sync for Persona",1,["nix::sys::personality::Persona"]],["impl Sync for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Sync for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Sync for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Sync for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Sync for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Sync for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Sync for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Sync for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Sync for Resource",1,["nix::sys::resource::Resource"]],["impl Sync for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Sync for Usage",1,["nix::sys::resource::Usage"]],["impl Sync for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Sync for Fds<'a>",1,["nix::sys::select::Fds"]],["impl !Sync for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Sync for Signal",1,["nix::sys::signal::Signal"]],["impl Sync for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Sync for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Sync for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Sync for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Sync for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Sync for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Sync for SigAction",1,["nix::sys::signal::SigAction"]],["impl Sync for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Sync for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Sync for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Sync for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Sync for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Sync for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Sync for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Sync for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Sync for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Sync for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Sync for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Sync for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Sync for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Sync for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Sync for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Sync for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Sync for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Sync for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Sync for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Sync for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Sync for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Sync for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Sync for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Sync for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Sync for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Sync for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Sync for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Sync for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Sync for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Sync for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Sync for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Sync for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Sync for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Sync for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Sync for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Sync for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Sync for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Sync for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Sync for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Sync for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Sync for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Sync for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Sync for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Sync for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Sync for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Sync for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Sync for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Sync for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Sync for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Sync for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Sync for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Sync for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Sync for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Sync for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Sync for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Sync for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Sync for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Sync for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Sync for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Sync for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Sync for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Sync for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Sync for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Sync for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Sync for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Sync for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Sync for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Sync for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Sync for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Sync for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Sync for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Sync for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Sync for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Sync for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Sync for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Sync for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Sync for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Sync for AlgSetKey<T>where
        T: Sync,
    ",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Sync for SockType",1,["nix::sys::socket::SockType"]],["impl Sync for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Sync for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Sync for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Sync for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Sync for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Sync for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Sync for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> !Sync for RecvMsg<'a, 's, S>",1,["nix::sys::socket::RecvMsg"]],["impl<'a> !Sync for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Sync for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Sync for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Sync for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> !Sync for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> !Sync for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Sync for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Sync for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Sync for SFlag",1,["nix::sys::stat::SFlag"]],["impl Sync for Mode",1,["nix::sys::stat::Mode"]],["impl Sync for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Sync for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Sync for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Sync for FsType",1,["nix::sys::statfs::FsType"]],["impl Sync for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Sync for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Sync for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !Sync for Termios",1,["nix::sys::termios::Termios"]],["impl Sync for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Sync for SetArg",1,["nix::sys::termios::SetArg"]],["impl Sync for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Sync for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Sync for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Sync for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Sync for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Sync for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Sync for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Sync for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Sync for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Sync for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Sync for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Sync for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl Sync for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Sync for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Sync for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Sync for Id",1,["nix::sys::wait::Id"]],["impl Sync for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Sync for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Sync for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Sync for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Sync for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Sync for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Sync for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Sync for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl !Sync for Timer",1,["nix::sys::timer::Timer"]],["impl Sync for ClockId",1,["nix::time::ClockId"]],["impl !Sync for UContext",1,["nix::ucontext::UContext"]],["impl Sync for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Sync for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Sync for Uid",1,["nix::unistd::Uid"]],["impl Sync for Gid",1,["nix::unistd::Gid"]],["impl Sync for Pid",1,["nix::unistd::Pid"]],["impl Sync for ForkResult",1,["nix::unistd::ForkResult"]],["impl Sync for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Sync for Whence",1,["nix::unistd::Whence"]],["impl Sync for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Sync for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Sync for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Sync for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Sync for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Sync for User",1,["nix::unistd::User"]],["impl Sync for Group",1,["nix::unistd::Group"]],["impl<T> Sync for IoVec<T>where
        T: Sync,
    "]], -"once_cell":[["impl<T> !Sync for OnceCell<T>",1,["once_cell::unsync::OnceCell"]],["impl<T, F = fn() -> T> !Sync for Lazy<T, F>",1,["once_cell::unsync::Lazy"]],["impl<T> Sync for OnceCell<T>where
        T: Send + Sync,
    ",1,["once_cell::sync::OnceCell"]],["impl Sync for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Sync for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T, F: Send> Sync for Lazy<T, F>where
        OnceCell<T>: Sync,
    "],["impl<T: Sync + Send> Sync for OnceBox<T>"]], -"parking_lot":[["impl Sync for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Sync for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Sync for OnceState",1,["parking_lot::once::OnceState"]],["impl Sync for Once",1,["parking_lot::once::Once"]],["impl Sync for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Sync for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Sync for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Sync for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl Sync for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Sync for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Sync for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Sync for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Sync for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Sync for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Sync for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl Sync for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Sync for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Sync for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Sync for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Sync for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Sync for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Sync for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Sync for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Sync for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Sync for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Sync for SseMachine<S3, S4, NI>where
        NI: Sync,
        S3: Sync,
        S4: Sync,
    ",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Sync for Avx2Machine<NI>where
        NI: Sync,
    ",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Sync for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Sync for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Sync for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl Sync for Error",1,["primitive_types::Error"]],["impl Sync for U128",1,["primitive_types::U128"]],["impl Sync for U256",1,["primitive_types::U256"]],["impl Sync for U512",1,["primitive_types::U512"]],["impl Sync for H128",1,["primitive_types::H128"]],["impl Sync for H160",1,["primitive_types::H160"]],["impl Sync for H256",1,["primitive_types::H256"]],["impl Sync for H384",1,["primitive_types::H384"]],["impl Sync for H512",1,["primitive_types::H512"]],["impl Sync for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl !Sync for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl !Sync for TokenStream",1,["proc_macro2::TokenStream"]],["impl !Sync for LexError",1,["proc_macro2::LexError"]],["impl !Sync for Span",1,["proc_macro2::Span"]],["impl !Sync for TokenTree",1,["proc_macro2::TokenTree"]],["impl !Sync for Group",1,["proc_macro2::Group"]],["impl Sync for Delimiter",1,["proc_macro2::Delimiter"]],["impl !Sync for Punct",1,["proc_macro2::Punct"]],["impl Sync for Spacing",1,["proc_macro2::Spacing"]],["impl !Sync for Ident",1,["proc_macro2::Ident"]],["impl !Sync for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl Sync for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Sync for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Sync for DistIter<D, R, T>where
        D: Sync,
        R: Sync,
        T: Sync,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Sync for DistMap<D, F, T, S>where
        D: Sync,
        F: Sync,
    ",1,["rand::distributions::distribution::DistMap"]],["impl Sync for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Sync for Open01",1,["rand::distributions::float::Open01"]],["impl Sync for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Sync for Slice<'a, T>where
        T: Sync,
    ",1,["rand::distributions::slice::Slice"]],["impl<X> Sync for WeightedIndex<X>where
        X: Sync,
        <X as SampleUniform>::Sampler: Sync,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Sync for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Sync for Uniform<X>where
        <X as SampleUniform>::Sampler: Sync,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> Sync for UniformInt<X>where
        X: Sync,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl Sync for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Sync for UniformFloat<X>where
        X: Sync,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl Sync for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Sync for WeightedIndex<W>where
        W: Sync,
    ",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Sync for Standard",1,["rand::distributions::Standard"]],["impl<R> Sync for ReadRng<R>where
        R: Sync,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl Sync for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Sync for ReseedingRng<R, Rsdr>where
        R: Sync,
        Rsdr: Sync,
        <R as BlockRngCore>::Results: Sync,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Sync for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Sync for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Sync for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Sync for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Sync for SliceChooseIter<'a, S, T>where
        S: Sync,
        T: Sync,
    ",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl Sync for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Sync for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Sync for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Sync for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Sync for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Sync for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> Sync for BlockRng<R>where
        R: Sync,
        <R as BlockRngCore>::Results: Sync,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Sync for BlockRng64<R>where
        R: Sync,
        <R as BlockRngCore>::Results: Sync,
    ",1,["rand_core::block::BlockRng64"]],["impl Sync for Error",1,["rand_core::error::Error"]],["impl Sync for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl Sync for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Sync for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Sync for Match<'t>",1,["regex::re_bytes::Match"]],["impl Sync for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> !Sync for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> !Sync for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> !Sync for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> !Sync for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Sync for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Sync for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Sync for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Sync for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Sync for ReplacerRef<'a, R>where
        R: Sync,
    ",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Sync for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Sync for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Sync for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Sync for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Sync for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Sync for Error",1,["regex::error::Error"]],["impl Sync for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Sync for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Sync for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Sync for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Sync for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Sync for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Sync for Match<'t>",1,["regex::re_unicode::Match"]],["impl Sync for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Sync for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> !Sync for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> !Sync for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Sync for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Sync for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Sync for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> !Sync for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> !Sync for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Sync for ReplacerRef<'a, R>where
        R: Sync,
    ",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Sync for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl Sync for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !Sync for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Sync for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Sync for Error",1,["regex_syntax::ast::Error"]],["impl Sync for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Sync for Span",1,["regex_syntax::ast::Span"]],["impl Sync for Position",1,["regex_syntax::ast::Position"]],["impl Sync for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Sync for Comment",1,["regex_syntax::ast::Comment"]],["impl Sync for Ast",1,["regex_syntax::ast::Ast"]],["impl Sync for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Sync for Concat",1,["regex_syntax::ast::Concat"]],["impl Sync for Literal",1,["regex_syntax::ast::Literal"]],["impl Sync for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Sync for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Sync for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Sync for Class",1,["regex_syntax::ast::Class"]],["impl Sync for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Sync for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Sync for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Sync for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Sync for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Sync for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Sync for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Sync for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Sync for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Sync for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Sync for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Sync for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Sync for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Sync for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Sync for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Sync for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Sync for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Sync for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Sync for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Sync for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Sync for Group",1,["regex_syntax::ast::Group"]],["impl Sync for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Sync for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Sync for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Sync for Flags",1,["regex_syntax::ast::Flags"]],["impl Sync for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Sync for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Sync for Flag",1,["regex_syntax::ast::Flag"]],["impl Sync for Error",1,["regex_syntax::error::Error"]],["impl Sync for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Sync for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Sync for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Sync for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !Sync for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Sync for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Sync for Error",1,["regex_syntax::hir::Error"]],["impl Sync for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Sync for Hir",1,["regex_syntax::hir::Hir"]],["impl Sync for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Sync for Literal",1,["regex_syntax::hir::Literal"]],["impl Sync for Class",1,["regex_syntax::hir::Class"]],["impl Sync for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Sync for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Sync for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Sync for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Sync for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Sync for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Sync for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Sync for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Sync for Group",1,["regex_syntax::hir::Group"]],["impl Sync for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Sync for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Sync for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Sync for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Sync for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !Sync for Parser",1,["regex_syntax::parser::Parser"]],["impl Sync for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Sync for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Sync for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Sync for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl Sync for DecoderError",1,["rlp::error::DecoderError"]],["impl Sync for Prototype",1,["rlp::rlpin::Prototype"]],["impl Sync for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !Sync for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !Sync for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl Sync for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> Sync for ToHexIter<T>where
        T: Sync,
    ",1,["rustc_hex::ToHexIter"]],["impl Sync for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Sync for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl Sync for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl Sync for Always",1,["scopeguard::Always"]],["impl<T, F, S> Sync for ScopeGuard<T, F, S>where
        T: Sync,
        F: FnOnce(T),
        S: Strategy,
    "]], -"serde":[["impl Sync for Error",1,["serde::de::value::Error"]],["impl<E> Sync for UnitDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::UnitDeserializer"]],["impl<E> Sync for BoolDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::BoolDeserializer"]],["impl<E> Sync for I8Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::I8Deserializer"]],["impl<E> Sync for I16Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::I16Deserializer"]],["impl<E> Sync for I32Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::I32Deserializer"]],["impl<E> Sync for I64Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::I64Deserializer"]],["impl<E> Sync for IsizeDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Sync for U8Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::U8Deserializer"]],["impl<E> Sync for U16Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::U16Deserializer"]],["impl<E> Sync for U64Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::U64Deserializer"]],["impl<E> Sync for UsizeDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Sync for F32Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::F32Deserializer"]],["impl<E> Sync for F64Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::F64Deserializer"]],["impl<E> Sync for CharDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::CharDeserializer"]],["impl<E> Sync for I128Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::I128Deserializer"]],["impl<E> Sync for U128Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::U128Deserializer"]],["impl<E> Sync for U32Deserializer<E>where
        E: Sync,
    ",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Sync for StrDeserializer<'a, E>where
        E: Sync,
    ",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Sync for BorrowedStrDeserializer<'de, E>where
        E: Sync,
    ",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Sync for StringDeserializer<E>where
        E: Sync,
    ",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Sync for CowStrDeserializer<'a, E>where
        E: Sync,
    ",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Sync for BytesDeserializer<'a, E>where
        E: Sync,
    ",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Sync for BorrowedBytesDeserializer<'de, E>where
        E: Sync,
    ",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Sync for SeqDeserializer<I, E>where
        E: Sync,
        I: Sync,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> Sync for SeqAccessDeserializer<A>where
        A: Sync,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Sync for MapDeserializer<'de, I, E>where
        E: Sync,
        I: Sync,
        <<I as Iterator>::Item as Pair>::Second: Sync,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> Sync for MapAccessDeserializer<A>where
        A: Sync,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Sync for EnumAccessDeserializer<A>where
        A: Sync,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl Sync for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Sync for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Sync for Impossible<Ok, Error>where
        Error: Sync,
        Ok: Sync,
    ",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl Sync for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Sync for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Sync for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Sync for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Sync for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Sync for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Sync for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Sync for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Sync for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Sync for Shake128Core",1,["sha3::Shake128Core"]],["impl Sync for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Sync for Shake256Core",1,["sha3::Shake256Core"]],["impl Sync for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Sync for CShake128Core",1,["sha3::CShake128Core"]],["impl Sync for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Sync for CShake256Core",1,["sha3::CShake256Core"]],["impl Sync for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl Sync for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Sync for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !Sync for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Sync for ShaleError",1,["shale::ShaleError"]],["impl Sync for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Sync for ObjPtr<T>where
        T: Sync,
    ",1,["shale::ObjPtr"]],["impl<T> !Sync for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !Sync for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !Sync for MummyObj<T>",1,["shale::MummyObj"]],["impl !Sync for PlainMem",1,["shale::PlainMem"]],["impl<T> !Sync for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> Sync for Slab<T>where
        T: Sync,
    ",1,["slab::Slab"]],["impl<'a, T> Sync for VacantEntry<'a, T>where
        T: Sync,
    ",1,["slab::VacantEntry"]],["impl<T> Sync for IntoIter<T>where
        T: Sync,
    ",1,["slab::IntoIter"]],["impl<'a, T> Sync for Iter<'a, T>where
        T: Sync,
    ",1,["slab::Iter"]],["impl<'a, T> Sync for IterMut<'a, T>where
        T: Sync,
    ",1,["slab::IterMut"]],["impl<'a, T> Sync for Drain<'a, T>where
        T: Sync,
    ",1,["slab::Drain"]]], -"smallvec":[["impl Sync for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<A> Sync for SmallVec<A>where
        A: Sync,
    ",1,["smallvec::SmallVec"]],["impl<A> Sync for IntoIter<A>where
        A: Sync,
    ",1,["smallvec::IntoIter"]],["impl<'a, T: Sync + Array> Sync for Drain<'a, T>"]], -"syn":[["impl !Sync for Underscore",1,["syn::token::Underscore"]],["impl !Sync for Abstract",1,["syn::token::Abstract"]],["impl !Sync for As",1,["syn::token::As"]],["impl !Sync for Async",1,["syn::token::Async"]],["impl !Sync for Auto",1,["syn::token::Auto"]],["impl !Sync for Await",1,["syn::token::Await"]],["impl !Sync for Become",1,["syn::token::Become"]],["impl !Sync for Box",1,["syn::token::Box"]],["impl !Sync for Break",1,["syn::token::Break"]],["impl !Sync for Const",1,["syn::token::Const"]],["impl !Sync for Continue",1,["syn::token::Continue"]],["impl !Sync for Crate",1,["syn::token::Crate"]],["impl !Sync for Default",1,["syn::token::Default"]],["impl !Sync for Do",1,["syn::token::Do"]],["impl !Sync for Dyn",1,["syn::token::Dyn"]],["impl !Sync for Else",1,["syn::token::Else"]],["impl !Sync for Enum",1,["syn::token::Enum"]],["impl !Sync for Extern",1,["syn::token::Extern"]],["impl !Sync for Final",1,["syn::token::Final"]],["impl !Sync for Fn",1,["syn::token::Fn"]],["impl !Sync for For",1,["syn::token::For"]],["impl !Sync for If",1,["syn::token::If"]],["impl !Sync for Impl",1,["syn::token::Impl"]],["impl !Sync for In",1,["syn::token::In"]],["impl !Sync for Let",1,["syn::token::Let"]],["impl !Sync for Loop",1,["syn::token::Loop"]],["impl !Sync for Macro",1,["syn::token::Macro"]],["impl !Sync for Match",1,["syn::token::Match"]],["impl !Sync for Mod",1,["syn::token::Mod"]],["impl !Sync for Move",1,["syn::token::Move"]],["impl !Sync for Mut",1,["syn::token::Mut"]],["impl !Sync for Override",1,["syn::token::Override"]],["impl !Sync for Priv",1,["syn::token::Priv"]],["impl !Sync for Pub",1,["syn::token::Pub"]],["impl !Sync for Ref",1,["syn::token::Ref"]],["impl !Sync for Return",1,["syn::token::Return"]],["impl !Sync for SelfType",1,["syn::token::SelfType"]],["impl !Sync for SelfValue",1,["syn::token::SelfValue"]],["impl !Sync for Static",1,["syn::token::Static"]],["impl !Sync for Struct",1,["syn::token::Struct"]],["impl !Sync for Super",1,["syn::token::Super"]],["impl !Sync for Trait",1,["syn::token::Trait"]],["impl !Sync for Try",1,["syn::token::Try"]],["impl !Sync for Type",1,["syn::token::Type"]],["impl !Sync for Typeof",1,["syn::token::Typeof"]],["impl !Sync for Union",1,["syn::token::Union"]],["impl !Sync for Unsafe",1,["syn::token::Unsafe"]],["impl !Sync for Unsized",1,["syn::token::Unsized"]],["impl !Sync for Use",1,["syn::token::Use"]],["impl !Sync for Virtual",1,["syn::token::Virtual"]],["impl !Sync for Where",1,["syn::token::Where"]],["impl !Sync for While",1,["syn::token::While"]],["impl !Sync for Yield",1,["syn::token::Yield"]],["impl !Sync for Add",1,["syn::token::Add"]],["impl !Sync for AddEq",1,["syn::token::AddEq"]],["impl !Sync for And",1,["syn::token::And"]],["impl !Sync for AndAnd",1,["syn::token::AndAnd"]],["impl !Sync for AndEq",1,["syn::token::AndEq"]],["impl !Sync for At",1,["syn::token::At"]],["impl !Sync for Bang",1,["syn::token::Bang"]],["impl !Sync for Caret",1,["syn::token::Caret"]],["impl !Sync for CaretEq",1,["syn::token::CaretEq"]],["impl !Sync for Colon",1,["syn::token::Colon"]],["impl !Sync for Colon2",1,["syn::token::Colon2"]],["impl !Sync for Comma",1,["syn::token::Comma"]],["impl !Sync for Div",1,["syn::token::Div"]],["impl !Sync for DivEq",1,["syn::token::DivEq"]],["impl !Sync for Dollar",1,["syn::token::Dollar"]],["impl !Sync for Dot",1,["syn::token::Dot"]],["impl !Sync for Dot2",1,["syn::token::Dot2"]],["impl !Sync for Dot3",1,["syn::token::Dot3"]],["impl !Sync for DotDotEq",1,["syn::token::DotDotEq"]],["impl !Sync for Eq",1,["syn::token::Eq"]],["impl !Sync for EqEq",1,["syn::token::EqEq"]],["impl !Sync for Ge",1,["syn::token::Ge"]],["impl !Sync for Gt",1,["syn::token::Gt"]],["impl !Sync for Le",1,["syn::token::Le"]],["impl !Sync for Lt",1,["syn::token::Lt"]],["impl !Sync for MulEq",1,["syn::token::MulEq"]],["impl !Sync for Ne",1,["syn::token::Ne"]],["impl !Sync for Or",1,["syn::token::Or"]],["impl !Sync for OrEq",1,["syn::token::OrEq"]],["impl !Sync for OrOr",1,["syn::token::OrOr"]],["impl !Sync for Pound",1,["syn::token::Pound"]],["impl !Sync for Question",1,["syn::token::Question"]],["impl !Sync for RArrow",1,["syn::token::RArrow"]],["impl !Sync for LArrow",1,["syn::token::LArrow"]],["impl !Sync for Rem",1,["syn::token::Rem"]],["impl !Sync for RemEq",1,["syn::token::RemEq"]],["impl !Sync for FatArrow",1,["syn::token::FatArrow"]],["impl !Sync for Semi",1,["syn::token::Semi"]],["impl !Sync for Shl",1,["syn::token::Shl"]],["impl !Sync for ShlEq",1,["syn::token::ShlEq"]],["impl !Sync for Shr",1,["syn::token::Shr"]],["impl !Sync for ShrEq",1,["syn::token::ShrEq"]],["impl !Sync for Star",1,["syn::token::Star"]],["impl !Sync for Sub",1,["syn::token::Sub"]],["impl !Sync for SubEq",1,["syn::token::SubEq"]],["impl !Sync for Tilde",1,["syn::token::Tilde"]],["impl !Sync for Brace",1,["syn::token::Brace"]],["impl !Sync for Bracket",1,["syn::token::Bracket"]],["impl !Sync for Paren",1,["syn::token::Paren"]],["impl !Sync for Group",1,["syn::token::Group"]],["impl !Sync for Attribute",1,["syn::attr::Attribute"]],["impl !Sync for AttrStyle",1,["syn::attr::AttrStyle"]],["impl !Sync for Meta",1,["syn::attr::Meta"]],["impl !Sync for MetaList",1,["syn::attr::MetaList"]],["impl !Sync for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl !Sync for NestedMeta",1,["syn::attr::NestedMeta"]],["impl !Sync for Variant",1,["syn::data::Variant"]],["impl !Sync for Fields",1,["syn::data::Fields"]],["impl !Sync for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl !Sync for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl !Sync for Field",1,["syn::data::Field"]],["impl !Sync for Visibility",1,["syn::data::Visibility"]],["impl !Sync for VisPublic",1,["syn::data::VisPublic"]],["impl !Sync for VisCrate",1,["syn::data::VisCrate"]],["impl !Sync for VisRestricted",1,["syn::data::VisRestricted"]],["impl !Sync for Expr",1,["syn::expr::Expr"]],["impl !Sync for ExprArray",1,["syn::expr::ExprArray"]],["impl !Sync for ExprAssign",1,["syn::expr::ExprAssign"]],["impl !Sync for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl !Sync for ExprAsync",1,["syn::expr::ExprAsync"]],["impl !Sync for ExprAwait",1,["syn::expr::ExprAwait"]],["impl !Sync for ExprBinary",1,["syn::expr::ExprBinary"]],["impl !Sync for ExprBlock",1,["syn::expr::ExprBlock"]],["impl !Sync for ExprBox",1,["syn::expr::ExprBox"]],["impl !Sync for ExprBreak",1,["syn::expr::ExprBreak"]],["impl !Sync for ExprCall",1,["syn::expr::ExprCall"]],["impl !Sync for ExprCast",1,["syn::expr::ExprCast"]],["impl !Sync for ExprClosure",1,["syn::expr::ExprClosure"]],["impl !Sync for ExprContinue",1,["syn::expr::ExprContinue"]],["impl !Sync for ExprField",1,["syn::expr::ExprField"]],["impl !Sync for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl !Sync for ExprGroup",1,["syn::expr::ExprGroup"]],["impl !Sync for ExprIf",1,["syn::expr::ExprIf"]],["impl !Sync for ExprIndex",1,["syn::expr::ExprIndex"]],["impl !Sync for ExprLet",1,["syn::expr::ExprLet"]],["impl !Sync for ExprLit",1,["syn::expr::ExprLit"]],["impl !Sync for ExprLoop",1,["syn::expr::ExprLoop"]],["impl !Sync for ExprMacro",1,["syn::expr::ExprMacro"]],["impl !Sync for ExprMatch",1,["syn::expr::ExprMatch"]],["impl !Sync for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl !Sync for ExprParen",1,["syn::expr::ExprParen"]],["impl !Sync for ExprPath",1,["syn::expr::ExprPath"]],["impl !Sync for ExprRange",1,["syn::expr::ExprRange"]],["impl !Sync for ExprReference",1,["syn::expr::ExprReference"]],["impl !Sync for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl !Sync for ExprReturn",1,["syn::expr::ExprReturn"]],["impl !Sync for ExprStruct",1,["syn::expr::ExprStruct"]],["impl !Sync for ExprTry",1,["syn::expr::ExprTry"]],["impl !Sync for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl !Sync for ExprTuple",1,["syn::expr::ExprTuple"]],["impl !Sync for ExprType",1,["syn::expr::ExprType"]],["impl !Sync for ExprUnary",1,["syn::expr::ExprUnary"]],["impl !Sync for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl !Sync for ExprWhile",1,["syn::expr::ExprWhile"]],["impl !Sync for ExprYield",1,["syn::expr::ExprYield"]],["impl !Sync for Member",1,["syn::expr::Member"]],["impl !Sync for Index",1,["syn::expr::Index"]],["impl !Sync for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl !Sync for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl !Sync for FieldValue",1,["syn::expr::FieldValue"]],["impl !Sync for Label",1,["syn::expr::Label"]],["impl !Sync for Arm",1,["syn::expr::Arm"]],["impl !Sync for RangeLimits",1,["syn::expr::RangeLimits"]],["impl !Sync for Generics",1,["syn::generics::Generics"]],["impl !Sync for GenericParam",1,["syn::generics::GenericParam"]],["impl !Sync for TypeParam",1,["syn::generics::TypeParam"]],["impl !Sync for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl !Sync for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> !Sync for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> !Sync for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> !Sync for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl !Sync for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl !Sync for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl !Sync for TraitBound",1,["syn::generics::TraitBound"]],["impl !Sync for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl !Sync for WhereClause",1,["syn::generics::WhereClause"]],["impl !Sync for WherePredicate",1,["syn::generics::WherePredicate"]],["impl !Sync for PredicateType",1,["syn::generics::PredicateType"]],["impl !Sync for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl !Sync for PredicateEq",1,["syn::generics::PredicateEq"]],["impl !Sync for Item",1,["syn::item::Item"]],["impl !Sync for ItemConst",1,["syn::item::ItemConst"]],["impl !Sync for ItemEnum",1,["syn::item::ItemEnum"]],["impl !Sync for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl !Sync for ItemFn",1,["syn::item::ItemFn"]],["impl !Sync for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl !Sync for ItemImpl",1,["syn::item::ItemImpl"]],["impl !Sync for ItemMacro",1,["syn::item::ItemMacro"]],["impl !Sync for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl !Sync for ItemMod",1,["syn::item::ItemMod"]],["impl !Sync for ItemStatic",1,["syn::item::ItemStatic"]],["impl !Sync for ItemStruct",1,["syn::item::ItemStruct"]],["impl !Sync for ItemTrait",1,["syn::item::ItemTrait"]],["impl !Sync for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl !Sync for ItemType",1,["syn::item::ItemType"]],["impl !Sync for ItemUnion",1,["syn::item::ItemUnion"]],["impl !Sync for ItemUse",1,["syn::item::ItemUse"]],["impl !Sync for UseTree",1,["syn::item::UseTree"]],["impl !Sync for UsePath",1,["syn::item::UsePath"]],["impl !Sync for UseName",1,["syn::item::UseName"]],["impl !Sync for UseRename",1,["syn::item::UseRename"]],["impl !Sync for UseGlob",1,["syn::item::UseGlob"]],["impl !Sync for UseGroup",1,["syn::item::UseGroup"]],["impl !Sync for ForeignItem",1,["syn::item::ForeignItem"]],["impl !Sync for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl !Sync for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl !Sync for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl !Sync for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl !Sync for TraitItem",1,["syn::item::TraitItem"]],["impl !Sync for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl !Sync for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl !Sync for TraitItemType",1,["syn::item::TraitItemType"]],["impl !Sync for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl !Sync for ImplItem",1,["syn::item::ImplItem"]],["impl !Sync for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl !Sync for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl !Sync for ImplItemType",1,["syn::item::ImplItemType"]],["impl !Sync for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl !Sync for Signature",1,["syn::item::Signature"]],["impl !Sync for FnArg",1,["syn::item::FnArg"]],["impl !Sync for Receiver",1,["syn::item::Receiver"]],["impl !Sync for File",1,["syn::file::File"]],["impl !Sync for Lifetime",1,["syn::lifetime::Lifetime"]],["impl !Sync for Lit",1,["syn::lit::Lit"]],["impl !Sync for LitStr",1,["syn::lit::LitStr"]],["impl !Sync for LitByteStr",1,["syn::lit::LitByteStr"]],["impl !Sync for LitByte",1,["syn::lit::LitByte"]],["impl !Sync for LitChar",1,["syn::lit::LitChar"]],["impl !Sync for LitInt",1,["syn::lit::LitInt"]],["impl !Sync for LitFloat",1,["syn::lit::LitFloat"]],["impl !Sync for LitBool",1,["syn::lit::LitBool"]],["impl Sync for StrStyle",1,["syn::lit::StrStyle"]],["impl !Sync for Macro",1,["syn::mac::Macro"]],["impl !Sync for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl !Sync for DeriveInput",1,["syn::derive::DeriveInput"]],["impl !Sync for Data",1,["syn::derive::Data"]],["impl !Sync for DataStruct",1,["syn::derive::DataStruct"]],["impl !Sync for DataEnum",1,["syn::derive::DataEnum"]],["impl !Sync for DataUnion",1,["syn::derive::DataUnion"]],["impl !Sync for BinOp",1,["syn::op::BinOp"]],["impl !Sync for UnOp",1,["syn::op::UnOp"]],["impl !Sync for Block",1,["syn::stmt::Block"]],["impl !Sync for Stmt",1,["syn::stmt::Stmt"]],["impl !Sync for Local",1,["syn::stmt::Local"]],["impl !Sync for Type",1,["syn::ty::Type"]],["impl !Sync for TypeArray",1,["syn::ty::TypeArray"]],["impl !Sync for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl !Sync for TypeGroup",1,["syn::ty::TypeGroup"]],["impl !Sync for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl !Sync for TypeInfer",1,["syn::ty::TypeInfer"]],["impl !Sync for TypeMacro",1,["syn::ty::TypeMacro"]],["impl !Sync for TypeNever",1,["syn::ty::TypeNever"]],["impl !Sync for TypeParen",1,["syn::ty::TypeParen"]],["impl !Sync for TypePath",1,["syn::ty::TypePath"]],["impl !Sync for TypePtr",1,["syn::ty::TypePtr"]],["impl !Sync for TypeReference",1,["syn::ty::TypeReference"]],["impl !Sync for TypeSlice",1,["syn::ty::TypeSlice"]],["impl !Sync for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl !Sync for TypeTuple",1,["syn::ty::TypeTuple"]],["impl !Sync for Abi",1,["syn::ty::Abi"]],["impl !Sync for BareFnArg",1,["syn::ty::BareFnArg"]],["impl !Sync for Variadic",1,["syn::ty::Variadic"]],["impl !Sync for ReturnType",1,["syn::ty::ReturnType"]],["impl !Sync for Pat",1,["syn::pat::Pat"]],["impl !Sync for PatBox",1,["syn::pat::PatBox"]],["impl !Sync for PatIdent",1,["syn::pat::PatIdent"]],["impl !Sync for PatLit",1,["syn::pat::PatLit"]],["impl !Sync for PatMacro",1,["syn::pat::PatMacro"]],["impl !Sync for PatOr",1,["syn::pat::PatOr"]],["impl !Sync for PatPath",1,["syn::pat::PatPath"]],["impl !Sync for PatRange",1,["syn::pat::PatRange"]],["impl !Sync for PatReference",1,["syn::pat::PatReference"]],["impl !Sync for PatRest",1,["syn::pat::PatRest"]],["impl !Sync for PatSlice",1,["syn::pat::PatSlice"]],["impl !Sync for PatStruct",1,["syn::pat::PatStruct"]],["impl !Sync for PatTuple",1,["syn::pat::PatTuple"]],["impl !Sync for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl !Sync for PatType",1,["syn::pat::PatType"]],["impl !Sync for PatWild",1,["syn::pat::PatWild"]],["impl !Sync for FieldPat",1,["syn::pat::FieldPat"]],["impl !Sync for Path",1,["syn::path::Path"]],["impl !Sync for PathSegment",1,["syn::path::PathSegment"]],["impl !Sync for PathArguments",1,["syn::path::PathArguments"]],["impl !Sync for GenericArgument",1,["syn::path::GenericArgument"]],["impl !Sync for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl !Sync for Binding",1,["syn::path::Binding"]],["impl !Sync for Constraint",1,["syn::path::Constraint"]],["impl !Sync for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl !Sync for QSelf",1,["syn::path::QSelf"]],["impl !Sync for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> !Sync for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Sync for Punctuated<T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Sync for Pairs<'a, T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Sync for PairsMut<'a, T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::PairsMut"]],["impl<T, P> Sync for IntoPairs<T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> Sync for IntoIter<T>where
        T: Sync,
    ",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !Sync for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !Sync for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Sync for Pair<T, P>where
        P: Sync,
        T: Sync,
    ",1,["syn::punctuated::Pair"]],["impl<'a> !Sync for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Sync for Error",1,["syn::error::Error"]],["impl<'a> !Sync for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> !Sync for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Sync for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> Sync for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Sync for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Sync for Builder",1,["tokio::runtime::builder::Builder"]],["impl Sync for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Sync for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Sync for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Sync for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Sync for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl Sync for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Sync for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Sync for SendError<T>where
        T: Sync,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl Sync for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Sync for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Sync for Sender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Sync for WeakSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Sync for Permit<'a, T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Sync for OwnedPermit<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Sync for Receiver<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> Sync for UnboundedSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Sync for WeakUnboundedSender<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Sync for UnboundedReceiver<T>where
        T: Send,
    ",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Sync for SendError<T>where
        T: Sync,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Sync for TrySendError<T>where
        T: Sync,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Sync for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl Sync for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Sync for Notify",1,["tokio::sync::notify::Notify"]],["impl Sync for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Sync for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Sync for Sender<T>where
        T: Send,
    ",1,["tokio::sync::oneshot::Sender"]],["impl<T> Sync for Receiver<T>where
        T: Send,
    ",1,["tokio::sync::oneshot::Receiver"]],["impl Sync for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Sync for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Sync for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Sync for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Sync for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T> Sync for SetError<T>where
        T: Sync,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> Sync for SendError<T>where
        T: Sync,
    ",1,["tokio::sync::watch::error::SendError"]],["impl Sync for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Sync for Receiver<T>where
        T: Send + Sync,
    ",1,["tokio::sync::watch::Receiver"]],["impl<T> Sync for Sender<T>where
        T: Send + Sync,
    ",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Sync for Ref<'a, T>where
        T: Sync,
    ",1,["tokio::sync::watch::Ref"]],["impl !Sync for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !Sync for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Sync for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> Sync for TaskLocalFuture<T, F>where
        F: Sync,
        T: Sync,
    ",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> Sync for Unconstrained<F>where
        F: Sync,
    ",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> Sync for JoinSet<T>where
        T: Send,
    ",1,["tokio::task::join_set::JoinSet"]],["impl Sync for AbortHandle"],["impl<T: Send> Sync for JoinHandle<T>"],["impl<T: Send> Sync for Sender<T>"],["impl<T: Send> Sync for Receiver<T>"],["impl<T> Sync for Mutex<T>where
        T: ?Sized + Send,
    "],["impl<T> Sync for MutexGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Sync for OwnedMutexGuard<T>where
        T: ?Sized + Send + Sync,
    "],["impl<'a, T> Sync for MappedMutexGuard<'a, T>where
        T: ?Sized + Sync + 'a,
    "],["impl<'a> Sync for Notified<'a>"],["impl<T> Sync for RwLock<T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Sync for RwLockReadGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T, U> Sync for OwnedRwLockReadGuard<T, U>where
        T: ?Sized + Send + Sync,
        U: ?Sized + Send + Sync,
    "],["impl<T> Sync for RwLockWriteGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Sync for OwnedRwLockWriteGuard<T>where
        T: ?Sized + Send + Sync,
    "],["impl<T> Sync for RwLockMappedWriteGuard<'_, T>where
        T: ?Sized + Send + Sync,
    "],["impl<T, U> Sync for OwnedRwLockMappedWriteGuard<T, U>where
        T: ?Sized + Send + Sync,
        U: ?Sized + Send + Sync,
    "],["impl<T: Sync + Send> Sync for OnceCell<T>"]], -"typenum":[["impl Sync for B0",1,["typenum::bit::B0"]],["impl Sync for B1",1,["typenum::bit::B1"]],["impl<U> Sync for PInt<U>where
        U: Sync,
    ",1,["typenum::int::PInt"]],["impl<U> Sync for NInt<U>where
        U: Sync,
    ",1,["typenum::int::NInt"]],["impl Sync for Z0",1,["typenum::int::Z0"]],["impl Sync for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Sync for UInt<U, B>where
        B: Sync,
        U: Sync,
    ",1,["typenum::uint::UInt"]],["impl Sync for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Sync for TArr<V, A>where
        A: Sync,
        V: Sync,
    ",1,["typenum::array::TArr"]],["impl Sync for Greater",1,["typenum::Greater"]],["impl Sync for Less",1,["typenum::Less"]],["impl Sync for Equal",1,["typenum::Equal"]]], -"uint":[["impl Sync for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Sync for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Sync for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Sync for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/marker/trait.Unpin.js b/docs/implementors/core/marker/trait.Unpin.js deleted file mode 100644 index 175a0c40ec5d..000000000000 --- a/docs/implementors/core/marker/trait.Unpin.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl Unpin for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl Unpin for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> Unpin for AhoCorasick<S>where
        S: Unpin,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> Unpin for FindIter<'a, 'b, S>",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> Unpin for FindOverlappingIter<'a, 'b, S>where
        S: Unpin,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> Unpin for StreamFindIter<'a, R, S>where
        R: Unpin,
        S: Unpin,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl Unpin for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl Unpin for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl Unpin for Error",1,["aho_corasick::error::Error"]],["impl Unpin for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl Unpin for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl Unpin for Config",1,["aho_corasick::packed::api::Config"]],["impl Unpin for Builder",1,["aho_corasick::packed::api::Builder"]],["impl Unpin for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> Unpin for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl Unpin for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl Unpin for Error",1,["aiofut::Error"]],["impl Unpin for AIO",1,["aiofut::AIO"]],["impl Unpin for AIOFuture",1,["aiofut::AIOFuture"]],["impl Unpin for AIONotifier",1,["aiofut::AIONotifier"]],["impl Unpin for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl Unpin for AIOManager",1,["aiofut::AIOManager"]],["impl Unpin for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl Unpin for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl Unpin for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl Unpin for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl Unpin for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl Unpin for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl Unpin for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl Unpin for Config",1,["bincode::config::legacy::Config"]],["impl Unpin for Bounded",1,["bincode::config::limit::Bounded"]],["impl Unpin for Infinite",1,["bincode::config::limit::Infinite"]],["impl Unpin for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl Unpin for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl Unpin for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> Unpin for WithOtherLimit<O, L>where
        L: Unpin,
        O: Unpin,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> Unpin for WithOtherEndian<O, E>where
        E: Unpin,
        O: Unpin,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> Unpin for WithOtherIntEncoding<O, I>where
        I: Unpin,
        O: Unpin,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> Unpin for WithOtherTrailing<O, T>where
        O: Unpin,
        T: Unpin,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> Unpin for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> Unpin for IoReader<R>where
        R: Unpin,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> Unpin for Deserializer<R, O>where
        O: Unpin,
        R: Unpin,
    ",1,["bincode::de::Deserializer"]],["impl Unpin for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> Unpin for Serializer<W, O>where
        O: Unpin,
        W: Unpin,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl Unpin for Eager",1,["block_buffer::Eager"]],["impl Unpin for Lazy",1,["block_buffer::Lazy"]],["impl Unpin for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> Unpin for BlockBuffer<BlockSize, Kind>where
        Kind: Unpin,
        <BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl Unpin for BigEndian",1,["byteorder::BigEndian"]],["impl Unpin for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> Unpin for Chain<T, U>where
        T: Unpin,
        U: Unpin,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> Unpin for IntoIter<T>where
        T: Unpin,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> Unpin for Limit<T>where
        T: Unpin,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> Unpin for Reader<B>where
        B: Unpin,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> Unpin for Take<T>where
        T: Unpin,
    ",1,["bytes::buf::take::Take"]],["impl Unpin for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> Unpin for Writer<B>where
        B: Unpin,
    ",1,["bytes::buf::writer::Writer"]],["impl Unpin for Bytes",1,["bytes::bytes::Bytes"]],["impl Unpin for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], -"crc":[["impl<W> Unpin for Crc<W>where
        W: Unpin,
    ",1,["crc::Crc"]],["impl<'a, W> Unpin for Digest<'a, W>where
        W: Unpin,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> Unpin for Algorithm<W>where
        W: Unpin,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<T> Unpin for Sender<T>",1,["crossbeam_channel::channel::Sender"]],["impl<T> Unpin for Receiver<T>where
        T: Unpin,
    ",1,["crossbeam_channel::channel::Receiver"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> Unpin for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> Unpin for IntoIter<T>where
        T: Unpin,
    ",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> Unpin for SendError<T>where
        T: Unpin,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> Unpin for TrySendError<T>where
        T: Unpin,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> Unpin for SendTimeoutError<T>where
        T: Unpin,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl Unpin for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl Unpin for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl Unpin for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl Unpin for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl Unpin for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl Unpin for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl Unpin for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> Unpin for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> Unpin for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]]], -"crossbeam_utils":[["impl<T> Unpin for AtomicCell<T>where
        T: Unpin,
    ",1,["crossbeam_utils::atomic::atomic_cell::AtomicCell"]],["impl<T> Unpin for CachePadded<T>where
        T: Unpin,
    ",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl Unpin for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl Unpin for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl Unpin for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<T: ?Sized> Unpin for ShardedLock<T>where
        T: Unpin,
    ",1,["crossbeam_utils::sync::sharded_lock::ShardedLock"]],["impl<'a, T: ?Sized> Unpin for ShardedLockReadGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> Unpin for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl Unpin for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> Unpin for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> Unpin for ScopedThreadBuilder<'scope, 'env>where
        'env: 'scope,
    ",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> Unpin for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]]], -"crypto_common":[["impl Unpin for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> Unpin for CtVariableCoreWrapper<T, OutSize, O>where
        O: Unpin,
        OutSize: Unpin,
        T: Unpin,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> Unpin for RtVariableCoreWrapper<T>where
        T: Unpin,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
        <T as BufferKindUser>::BufferKind: Unpin,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> Unpin for CoreWrapper<T>where
        T: Unpin,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
        <T as BufferKindUser>::BufferKind: Unpin,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> Unpin for XofReaderCoreWrapper<T>where
        T: Unpin,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: Unpin,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl Unpin for TruncSide",1,["digest::core_api::TruncSide"]],["impl Unpin for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl Unpin for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl Unpin for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl Unpin for WALConfig",1,["firewood::storage::WALConfig"]],["impl Unpin for DBError",1,["firewood::db::DBError"]],["impl Unpin for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl Unpin for DBConfig",1,["firewood::db::DBConfig"]],["impl Unpin for DBRev",1,["firewood::db::DBRev"]],["impl Unpin for DB",1,["firewood::db::DB"]],["impl<'a> Unpin for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> Unpin for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl Unpin for MerkleError",1,["firewood::merkle::MerkleError"]],["impl Unpin for Hash",1,["firewood::merkle::Hash"]],["impl Unpin for PartialPath",1,["firewood::merkle::PartialPath"]],["impl Unpin for Node",1,["firewood::merkle::Node"]],["impl Unpin for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> Unpin for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> Unpin for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl Unpin for IdTrans",1,["firewood::merkle::IdTrans"]],["impl Unpin for Proof",1,["firewood::proof::Proof"]],["impl Unpin for ProofError",1,["firewood::proof::ProofError"]],["impl Unpin for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> Unpin for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> Unpin for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl Unpin for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> Unpin for TrySendError<T>where
        T: Unpin,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl Unpin for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<'a, T> Unpin for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl Unpin for Canceled",1,["futures_channel::oneshot::Canceled"]],["impl<T> Unpin for UnboundedReceiver<T>"],["impl<T> Unpin for Receiver<T>"],["impl<T> Unpin for Receiver<T>"],["impl<T> Unpin for Sender<T>"]], -"futures_executor":[["impl Unpin for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl Unpin for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> Unpin for BlockingStream<S>",1,["futures_executor::local_pool::BlockingStream"]],["impl Unpin for Enter",1,["futures_executor::enter::Enter"]],["impl Unpin for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl Unpin for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> Unpin for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<T> Unpin for LocalFutureObj<'_, T>"],["impl<T> Unpin for FutureObj<'_, T>"]], -"futures_util":[["impl<T> Unpin for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> Unpin for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> Unpin for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<F> Unpin for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> Unpin for Either<A, B>where
        A: Unpin,
        B: Unpin,
    ",1,["futures_util::future::either::Either"]],["impl Unpin for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl Unpin for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl Unpin for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St> Unpin for StreamFuture<St>where
        St: Unpin,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<'a, St: ?Sized> Unpin for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<T, Item> Unpin for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<T, E> Unpin for TryChunksError<T, E>where
        E: Unpin,
        T: Unpin,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl Unpin for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<'a, Fut> Unpin for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> Unpin for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> Unpin for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> Unpin for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> Unpin for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<'a, St> Unpin for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> Unpin for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> Unpin for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, R> Unpin for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<T> Unpin for Cursor<T>where
        T: Unpin,
    ",1,["futures_util::io::cursor::Cursor"]],["impl Unpin for Empty",1,["futures_util::io::empty::Empty"]],["impl Unpin for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl Unpin for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> Unpin for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> Unpin for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> Unpin for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<T> Unpin for Window<T>where
        T: Unpin,
    ",1,["futures_util::io::window::Window"]],["impl<T: ?Sized> Unpin for Mutex<T>where
        T: Unpin,
    ",1,["futures_util::lock::mutex::Mutex"]],["impl<T: ?Sized> Unpin for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T: ?Sized> Unpin for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Unpin for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T: ?Sized> Unpin for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T: ?Sized, U: ?Sized> Unpin for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]],["impl<'__pin, Fut> Unpin for Fuse<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<'__pin, F> Unpin for Flatten<F>where
        __Origin<'__pin, F>: Unpin,
        F: Future,
    "],["impl<'__pin, F> Unpin for FlattenStream<F>where
        __Origin<'__pin, F>: Unpin,
        F: Future,
    "],["impl<'__pin, Fut, F> Unpin for Map<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, F> Unpin for IntoStream<F>where
        __Origin<'__pin, F>: Unpin,
    "],["impl<'__pin, Fut, T> Unpin for MapInto<Fut, T>where
        __Origin<'__pin, Fut, T>: Unpin,
    "],["impl<'__pin, Fut1, Fut2, F> Unpin for Then<Fut1, Fut2, F>where
        __Origin<'__pin, Fut1, Fut2, F>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for Inspect<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, Fut> Unpin for NeverError<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<'__pin, Fut> Unpin for UnitError<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<'__pin, Fut> Unpin for CatchUnwind<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<'__pin, Fut: Future> Unpin for Remote<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<Fut: Future> Unpin for Shared<Fut>"],["impl<'__pin, Fut> Unpin for IntoFuture<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<'__pin, Fut1, Fut2> Unpin for TryFlatten<Fut1, Fut2>where
        __Origin<'__pin, Fut1, Fut2>: Unpin,
    "],["impl<'__pin, Fut> Unpin for TryFlattenStream<Fut>where
        __Origin<'__pin, Fut>: Unpin,
        Fut: TryFuture,
    "],["impl<'__pin, Fut, Si> Unpin for FlattenSink<Fut, Si>where
        __Origin<'__pin, Fut, Si>: Unpin,
    "],["impl<'__pin, Fut1, Fut2, F> Unpin for AndThen<Fut1, Fut2, F>where
        __Origin<'__pin, Fut1, Fut2, F>: Unpin,
    "],["impl<'__pin, Fut1, Fut2, F> Unpin for OrElse<Fut1, Fut2, F>where
        __Origin<'__pin, Fut1, Fut2, F>: Unpin,
    "],["impl<'__pin, Fut, E> Unpin for ErrInto<Fut, E>where
        __Origin<'__pin, Fut, E>: Unpin,
    "],["impl<'__pin, Fut, E> Unpin for OkInto<Fut, E>where
        __Origin<'__pin, Fut, E>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for InspectOk<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for InspectErr<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for MapOk<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for MapErr<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<'__pin, Fut, F, G> Unpin for MapOkOrElse<Fut, F, G>where
        __Origin<'__pin, Fut, F, G>: Unpin,
    "],["impl<'__pin, Fut, F> Unpin for UnwrapOrElse<Fut, F>where
        __Origin<'__pin, Fut, F>: Unpin,
    "],["impl<F> Unpin for Lazy<F>"],["impl<T> Unpin for Pending<T>"],["impl<Fut: Future + Unpin> Unpin for MaybeDone<Fut>"],["impl<Fut: TryFuture + Unpin> Unpin for TryMaybeDone<Fut>"],["impl<'__pin, F> Unpin for OptionFuture<F>where
        __Origin<'__pin, F>: Unpin,
    "],["impl<F> Unpin for PollFn<F>"],["impl<'__pin, T> Unpin for PollImmediate<T>where
        __Origin<'__pin, T>: Unpin,
    "],["impl<T> Unpin for Ready<T>"],["impl<'__pin, Fut1: Future, Fut2: Future> Unpin for Join<Fut1, Fut2>where
        __Origin<'__pin, Fut1, Fut2>: Unpin,
    "],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future> Unpin for Join3<Fut1, Fut2, Fut3>where
        __Origin<'__pin, Fut1, Fut2, Fut3>: Unpin,
    "],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future, Fut4: Future> Unpin for Join4<Fut1, Fut2, Fut3, Fut4>where
        __Origin<'__pin, Fut1, Fut2, Fut3, Fut4>: Unpin,
    "],["impl<'__pin, Fut1: Future, Fut2: Future, Fut3: Future, Fut4: Future, Fut5: Future> Unpin for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        __Origin<'__pin, Fut1, Fut2, Fut3, Fut4, Fut5>: Unpin,
    "],["impl<A: Unpin, B: Unpin> Unpin for Select<A, B>"],["impl<Fut: Unpin> Unpin for SelectAll<Fut>"],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture> Unpin for TryJoin<Fut1, Fut2>where
        __Origin<'__pin, Fut1, Fut2>: Unpin,
    "],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture> Unpin for TryJoin3<Fut1, Fut2, Fut3>where
        __Origin<'__pin, Fut1, Fut2, Fut3>: Unpin,
    "],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture, Fut4: TryFuture> Unpin for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        __Origin<'__pin, Fut1, Fut2, Fut3, Fut4>: Unpin,
    "],["impl<'__pin, Fut1: TryFuture, Fut2: TryFuture, Fut3: TryFuture, Fut4: TryFuture, Fut5: TryFuture> Unpin for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        __Origin<'__pin, Fut1, Fut2, Fut3, Fut4, Fut5>: Unpin,
    "],["impl<A: Unpin, B: Unpin> Unpin for TrySelect<A, B>"],["impl<Fut: Unpin> Unpin for SelectOk<Fut>"],["impl<'__pin, St1, St2> Unpin for Chain<St1, St2>where
        __Origin<'__pin, St1, St2>: Unpin,
    "],["impl<'__pin, St, C> Unpin for Collect<St, C>where
        __Origin<'__pin, St, C>: Unpin,
    "],["impl<'__pin, St, FromA, FromB> Unpin for Unzip<St, FromA, FromB>where
        __Origin<'__pin, St, FromA, FromB>: Unpin,
    "],["impl<'__pin, St: Stream> Unpin for Concat<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St> Unpin for Cycle<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St> Unpin for Enumerate<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for Filter<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
        St: Stream,
    "],["impl<'__pin, St, Fut, F> Unpin for FilterMap<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St> Unpin for Flatten<St>where
        __Origin<'__pin, St>: Unpin,
        St: Stream,
    "],["impl<'__pin, St, Fut, T, F> Unpin for Fold<St, Fut, T, F>where
        __Origin<'__pin, St, Fut, T, F>: Unpin,
    "],["impl<'__pin, St, Si> Unpin for Forward<St, Si>where
        __Origin<'__pin, St, Si>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St, Fut, F> Unpin for ForEach<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St> Unpin for Fuse<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, F> Unpin for Inspect<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St, F> Unpin for Map<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St, U, F> Unpin for FlatMap<St, U, F>where
        __Origin<'__pin, St, U, F>: Unpin,
    "],["impl<St: ?Sized + Unpin> Unpin for Next<'_, St>"],["impl<'__pin, St: Stream> Unpin for Peekable<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, 'a, St: Stream> Unpin for Peek<'a, St>where
        __Origin<'__pin, 'a, St>: Unpin,
    "],["impl<'__pin, 'a, St: Stream> Unpin for PeekMut<'a, St>where
        __Origin<'__pin, 'a, St>: Unpin,
    "],["impl<'__pin, 'a, St: Stream, F> Unpin for NextIf<'a, St, F>where
        __Origin<'__pin, 'a, St, F>: Unpin,
    "],["impl<'__pin, 'a, St: Stream, T: ?Sized> Unpin for NextIfEq<'a, St, T>where
        __Origin<'__pin, 'a, St, T>: Unpin,
    "],["impl<'__pin, St> Unpin for Skip<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for SkipWhile<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
        St: Stream,
    "],["impl<'__pin, St> Unpin for Take<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St: Stream, Fut, F> Unpin for TakeWhile<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St: Stream, Fut: Future> Unpin for TakeUntil<St, Fut>where
        __Origin<'__pin, St, Fut>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for Then<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St1: Stream, St2: Stream> Unpin for Zip<St1, St2>where
        __Origin<'__pin, St1, St2>: Unpin,
    "],["impl<'__pin, St: Stream> Unpin for Chunks<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St: Stream> Unpin for ReadyChunks<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St: Stream, S, Fut, F> Unpin for Scan<St, S, Fut, F>where
        __Origin<'__pin, St, S, Fut, F>: Unpin,
    "],["impl<'__pin, St> Unpin for BufferUnordered<St>where
        __Origin<'__pin, St>: Unpin,
        St: Stream,
    "],["impl<'__pin, St> Unpin for Buffered<St>where
        __Origin<'__pin, St>: Unpin,
        St: Stream,
        St::Item: Future,
    "],["impl<'__pin, St, Fut, F> Unpin for ForEachConcurrent<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<S> Unpin for SplitStream<S>"],["impl<S, Item> Unpin for SplitSink<S, Item>"],["impl<'__pin, St> Unpin for CatchUnwind<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for AndThen<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St, E> Unpin for ErrInto<St, E>where
        __Origin<'__pin, St, E>: Unpin,
    "],["impl<'__pin, St, F> Unpin for InspectOk<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St, F> Unpin for InspectErr<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St> Unpin for IntoStream<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, F> Unpin for MapOk<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St, F> Unpin for MapErr<St, F>where
        __Origin<'__pin, St, F>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for OrElse<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<St: ?Sized + Unpin> Unpin for TryNext<'_, St>"],["impl<'__pin, St, Fut, F> Unpin for TryForEach<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for TryFilter<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St, Fut, F> Unpin for TryFilterMap<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St> Unpin for TryFlatten<St>where
        __Origin<'__pin, St>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St, C> Unpin for TryCollect<St, C>where
        __Origin<'__pin, St, C>: Unpin,
    "],["impl<'__pin, St: TryStream> Unpin for TryConcat<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St: TryStream> Unpin for TryChunks<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<'__pin, St, Fut, T, F> Unpin for TryFold<St, Fut, T, F>where
        __Origin<'__pin, St, Fut, T, F>: Unpin,
    "],["impl<'__pin, T, F, Fut> Unpin for TryUnfold<T, F, Fut>where
        __Origin<'__pin, T, F, Fut>: Unpin,
    "],["impl<'__pin, St, Fut, F> Unpin for TrySkipWhile<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St, Fut, F> Unpin for TryTakeWhile<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St> Unpin for TryBufferUnordered<St>where
        __Origin<'__pin, St>: Unpin,
        St: TryStream,
    "],["impl<'__pin, St> Unpin for TryBuffered<St>where
        __Origin<'__pin, St>: Unpin,
        St: TryStream,
        St::Ok: TryFuture,
    "],["impl<'__pin, St, Fut, F> Unpin for TryForEachConcurrent<St, Fut, F>where
        __Origin<'__pin, St, Fut, F>: Unpin,
    "],["impl<'__pin, St> Unpin for IntoAsyncRead<St>where
        __Origin<'__pin, St>: Unpin,
        St: TryStream<Error = Error>,
        St::Ok: AsRef<[u8]>,
    "],["impl<I> Unpin for Iter<I>"],["impl<T> Unpin for Repeat<T>"],["impl<A, F: FnMut() -> A> Unpin for RepeatWith<F>"],["impl<T> Unpin for Empty<T>"],["impl<'__pin, Fut> Unpin for Once<Fut>where
        __Origin<'__pin, Fut>: Unpin,
    "],["impl<T> Unpin for Pending<T>"],["impl<F> Unpin for PollFn<F>"],["impl<'__pin, S> Unpin for PollImmediate<S>where
        __Origin<'__pin, S>: Unpin,
    "],["impl<'__pin, St1, St2> Unpin for Select<St1, St2>where
        __Origin<'__pin, St1, St2>: Unpin,
    "],["impl<'__pin, St1, St2, Clos, State> Unpin for SelectWithStrategy<St1, St2, Clos, State>where
        __Origin<'__pin, St1, St2, Clos, State>: Unpin,
    "],["impl<'__pin, T, F, Fut> Unpin for Unfold<T, F, Fut>where
        __Origin<'__pin, T, F, Fut>: Unpin,
    "],["impl<T: Future> Unpin for FuturesOrdered<T>"],["impl<Fut> Unpin for FuturesUnordered<Fut>"],["impl<'__pin, St> Unpin for SelectAll<St>where
        __Origin<'__pin, St>: Unpin,
    "],["impl<Si: Unpin + ?Sized, Item> Unpin for Close<'_, Si, Item>"],["impl<T> Unpin for Drain<T>"],["impl<'__pin, Si1, Si2> Unpin for Fanout<Si1, Si2>where
        __Origin<'__pin, Si1, Si2>: Unpin,
    "],["impl<Si: Unpin + ?Sized, Item> Unpin for Feed<'_, Si, Item>"],["impl<Si: Unpin + ?Sized, Item> Unpin for Flush<'_, Si, Item>"],["impl<'__pin, Si: Sink<Item>, Item, E> Unpin for SinkErrInto<Si, Item, E>where
        __Origin<'__pin, Si, Item, E>: Unpin,
    "],["impl<'__pin, Si, F> Unpin for SinkMapErr<Si, F>where
        __Origin<'__pin, Si, F>: Unpin,
    "],["impl<Si: Unpin + ?Sized, Item> Unpin for Send<'_, Si, Item>"],["impl<Si, St> Unpin for SendAll<'_, Si, St>where
        Si: Unpin + ?Sized,
        St: TryStream + Unpin + ?Sized,
    "],["impl<'__pin, T, F, R> Unpin for Unfold<T, F, R>where
        __Origin<'__pin, T, F, R>: Unpin,
    "],["impl<'__pin, Si, Item, U, Fut, F> Unpin for With<Si, Item, U, Fut, F>where
        __Origin<'__pin, Si, Item, U, Fut, F>: Unpin,
    "],["impl<'__pin, Si, Item, U, St, F> Unpin for WithFlatMap<Si, Item, U, St, F>where
        __Origin<'__pin, Si, Item, U, St, F>: Unpin,
    "],["impl<'__pin, Si, Item> Unpin for Buffer<Si, Item>where
        __Origin<'__pin, Si, Item>: Unpin,
    "],["impl<T> Unpin for AllowStdIo<T>"],["impl<'__pin, R> Unpin for BufReader<R>where
        __Origin<'__pin, R>: Unpin,
    "],["impl<'__pin, W> Unpin for BufWriter<W>where
        __Origin<'__pin, W>: Unpin,
    "],["impl<'__pin, W: AsyncWrite> Unpin for LineWriter<W>where
        __Origin<'__pin, W>: Unpin,
    "],["impl<'__pin, T, U> Unpin for Chain<T, U>where
        __Origin<'__pin, T, U>: Unpin,
    "],["impl<W: ?Sized + Unpin> Unpin for Close<'_, W>"],["impl<'__pin, 'a, R, W: ?Sized> Unpin for Copy<'a, R, W>where
        __Origin<'__pin, 'a, R, W>: Unpin,
    "],["impl<'__pin, 'a, R, W: ?Sized> Unpin for CopyBuf<'a, R, W>where
        __Origin<'__pin, 'a, R, W>: Unpin,
    "],["impl<'__pin, 'a, R, W: ?Sized> Unpin for CopyBufAbortable<'a, R, W>where
        __Origin<'__pin, 'a, R, W>: Unpin,
    "],["impl<R: ?Sized> Unpin for FillBuf<'_, R>"],["impl<W: ?Sized + Unpin> Unpin for Flush<'_, W>"],["impl<'__pin, W, Item> Unpin for IntoSink<W, Item>where
        __Origin<'__pin, W, Item>: Unpin,
    "],["impl<'__pin, R> Unpin for Lines<R>where
        __Origin<'__pin, R>: Unpin,
    "],["impl<R: ?Sized + Unpin> Unpin for Read<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadVectored<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadExact<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadLine<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadToEnd<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadToString<'_, R>"],["impl<R: ?Sized + Unpin> Unpin for ReadUntil<'_, R>"],["impl<S: ?Sized + Unpin> Unpin for Seek<'_, S>"],["impl<'__pin, R> Unpin for Take<R>where
        __Origin<'__pin, R>: Unpin,
    "],["impl<W: ?Sized + Unpin> Unpin for Write<'_, W>"],["impl<W: ?Sized + Unpin> Unpin for WriteVectored<'_, W>"],["impl<W: ?Sized + Unpin> Unpin for WriteAll<'_, W>"],["impl<'__pin, T> Unpin for Abortable<T>where
        __Origin<'__pin, T>: Unpin,
    "]], -"generic_array":[["impl<T, N> Unpin for GenericArrayIter<T, N>where
        <N as ArrayLength<T>>::ArrayType: Unpin,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> Unpin for GenericArray<T, U>where
        <U as ArrayLength<T>>::ArrayType: Unpin,
    ",1,["generic_array::GenericArray"]]], -"getrandom":[["impl Unpin for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl Unpin for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> Unpin for WALWriter<F>where
        F: Unpin,
    ",1,["growthring::wal::WALWriter"]],["impl Unpin for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl Unpin for WALLoader",1,["growthring::wal::WALLoader"]],["impl Unpin for WALFileAIO",1,["growthring::WALFileAIO"]],["impl Unpin for WALStoreAIO",1,["growthring::WALStoreAIO"]]], -"hashbrown":[["impl<K, V, S, A> Unpin for HashMap<K, V, S, A>where
        A: Unpin,
        K: Unpin,
        S: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> Unpin for Iter<'a, K, V>",1,["hashbrown::map::Iter"]],["impl<'a, K, V> Unpin for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> Unpin for IntoIter<K, V, A>where
        A: Unpin,
        K: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> Unpin for IntoKeys<K, V, A>where
        A: Unpin,
        K: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> Unpin for IntoValues<K, V, A>where
        A: Unpin,
        K: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> Unpin for Keys<'a, K, V>",1,["hashbrown::map::Keys"]],["impl<'a, K, V> Unpin for Values<'a, K, V>",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> Unpin for Drain<'a, K, V, A>where
        A: Unpin,
        K: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> Unpin for DrainFilter<'a, K, V, F, A>where
        F: Unpin,
    ",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> Unpin for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> Unpin for RawEntryBuilder<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> Unpin for Entry<'a, K, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> Unpin for OccupiedEntry<'a, K, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> Unpin for VacantEntry<'a, K, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for EntryRef<'a, 'b, K, Q, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> Unpin for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
        K: Unpin,
    ",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> Unpin for OccupiedError<'a, K, V, S, A>where
        K: Unpin,
        V: Unpin,
    ",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> Unpin for HashSet<T, S, A>where
        A: Unpin,
        S: Unpin,
        T: Unpin,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> Unpin for Iter<'a, K>",1,["hashbrown::set::Iter"]],["impl<K, A> Unpin for IntoIter<K, A>where
        A: Unpin,
        K: Unpin,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> Unpin for Drain<'a, K, A>where
        A: Unpin,
        K: Unpin,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> Unpin for DrainFilter<'a, K, F, A>where
        F: Unpin,
    ",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> Unpin for Intersection<'a, T, S, A>",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> Unpin for Difference<'a, T, S, A>",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> Unpin for SymmetricDifference<'a, T, S, A>",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> Unpin for Union<'a, T, S, A>",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> Unpin for Entry<'a, T, S, A>where
        T: Unpin,
    ",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> Unpin for OccupiedEntry<'a, T, S, A>where
        T: Unpin,
    ",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> Unpin for VacantEntry<'a, T, S, A>where
        T: Unpin,
    ",1,["hashbrown::set::VacantEntry"]],["impl Unpin for TryReserveError",1,["hashbrown::TryReserveError"]]], -"heck":[["impl<T> Unpin for AsKebabCase<T>where
        T: Unpin,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> Unpin for AsLowerCamelCase<T>where
        T: Unpin,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> Unpin for AsShoutyKebabCase<T>where
        T: Unpin,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> Unpin for AsShoutySnakeCase<T>where
        T: Unpin,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> Unpin for AsSnakeCase<T>where
        T: Unpin,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> Unpin for AsTitleCase<T>where
        T: Unpin,
    ",1,["heck::title::AsTitleCase"]],["impl<T> Unpin for AsUpperCamelCase<T>where
        T: Unpin,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl Unpin for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl Unpin for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl Unpin for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl Unpin for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl Unpin for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl Unpin for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl Unpin for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl Unpin for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl Unpin for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl Unpin for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl Unpin for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl Unpin for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl Unpin for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl Unpin for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl Unpin for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl Unpin for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl Unpin for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl Unpin for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl Unpin for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl Unpin for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl Unpin for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl Unpin for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl Unpin for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl Unpin for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl Unpin for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl Unpin for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl Unpin for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl Unpin for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl Unpin for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl Unpin for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl Unpin for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl Unpin for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl Unpin for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl Unpin for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl Unpin for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl Unpin for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl Unpin for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl Unpin for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl Unpin for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl Unpin for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl Unpin for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl Unpin for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl Unpin for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl Unpin for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl Unpin for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl Unpin for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl Unpin for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl Unpin for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl Unpin for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl Unpin for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl Unpin for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl Unpin for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl Unpin for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl Unpin for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl Unpin for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl Unpin for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl Unpin for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl Unpin for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl Unpin for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl Unpin for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl Unpin for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl Unpin for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl Unpin for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl Unpin for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl Unpin for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl Unpin for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl Unpin for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl Unpin for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl Unpin for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl Unpin for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl Unpin for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl Unpin for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl Unpin for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl Unpin for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl Unpin for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl Unpin for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl Unpin for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl Unpin for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl Unpin for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl Unpin for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl Unpin for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl Unpin for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl Unpin for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl Unpin for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl Unpin for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl Unpin for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl Unpin for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl Unpin for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl Unpin for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl Unpin for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl Unpin for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl Unpin for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl Unpin for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl Unpin for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl Unpin for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl Unpin for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl Unpin for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl Unpin for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl Unpin for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl Unpin for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl Unpin for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl Unpin for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl Unpin for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl Unpin for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl Unpin for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl Unpin for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl Unpin for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl Unpin for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl Unpin for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl Unpin for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl Unpin for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl Unpin for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl Unpin for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl Unpin for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl Unpin for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl Unpin for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl Unpin for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl Unpin for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl Unpin for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl Unpin for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl Unpin for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl Unpin for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl Unpin for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl Unpin for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl Unpin for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl Unpin for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl Unpin for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl Unpin for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl Unpin for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl Unpin for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl Unpin for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl Unpin for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl Unpin for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl Unpin for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl Unpin for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl Unpin for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl Unpin for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl Unpin for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl Unpin for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl Unpin for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl Unpin for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl Unpin for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl Unpin for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl Unpin for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl Unpin for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl Unpin for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl Unpin for timezone",1,["libc::unix::linux_like::timezone"]],["impl Unpin for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl Unpin for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl Unpin for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl Unpin for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl Unpin for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl Unpin for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl Unpin for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl Unpin for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl Unpin for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl Unpin for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl Unpin for tm",1,["libc::unix::linux_like::tm"]],["impl Unpin for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl Unpin for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl Unpin for lconv",1,["libc::unix::linux_like::lconv"]],["impl Unpin for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl Unpin for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl Unpin for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl Unpin for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl Unpin for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl Unpin for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl Unpin for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl Unpin for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl Unpin for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl Unpin for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl Unpin for utsname",1,["libc::unix::linux_like::utsname"]],["impl Unpin for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl Unpin for in6_addr",1,["libc::unix::align::in6_addr"]],["impl Unpin for DIR",1,["libc::unix::DIR"]],["impl Unpin for group",1,["libc::unix::group"]],["impl Unpin for utimbuf",1,["libc::unix::utimbuf"]],["impl Unpin for timeval",1,["libc::unix::timeval"]],["impl Unpin for timespec",1,["libc::unix::timespec"]],["impl Unpin for rlimit",1,["libc::unix::rlimit"]],["impl Unpin for rusage",1,["libc::unix::rusage"]],["impl Unpin for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl Unpin for hostent",1,["libc::unix::hostent"]],["impl Unpin for iovec",1,["libc::unix::iovec"]],["impl Unpin for pollfd",1,["libc::unix::pollfd"]],["impl Unpin for winsize",1,["libc::unix::winsize"]],["impl Unpin for linger",1,["libc::unix::linger"]],["impl Unpin for sigval",1,["libc::unix::sigval"]],["impl Unpin for itimerval",1,["libc::unix::itimerval"]],["impl Unpin for tms",1,["libc::unix::tms"]],["impl Unpin for servent",1,["libc::unix::servent"]],["impl Unpin for protoent",1,["libc::unix::protoent"]],["impl Unpin for FILE",1,["libc::unix::FILE"]],["impl Unpin for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<R, T: ?Sized> Unpin for Mutex<R, T>where
        R: Unpin,
        T: Unpin,
    ",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T: ?Sized> Unpin for MutexGuard<'a, R, T>where
        <R as RawMutex>::GuardMarker: Unpin,
    ",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> Unpin for RawReentrantMutex<R, G>where
        G: Unpin,
        R: Unpin,
    ",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T: ?Sized> Unpin for ReentrantMutex<R, G, T>where
        G: Unpin,
        R: Unpin,
        T: Unpin,
    ",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T: ?Sized> Unpin for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T: ?Sized> Unpin for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T: ?Sized> Unpin for RwLock<R, T>where
        R: Unpin,
        T: Unpin,
    ",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T: ?Sized> Unpin for RwLockReadGuard<'a, R, T>where
        <R as RawRwLock>::GuardMarker: Unpin,
    ",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for RwLockWriteGuard<'a, R, T>where
        <R as RawRwLock>::GuardMarker: Unpin,
    ",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T: ?Sized> Unpin for RwLockUpgradableReadGuard<'a, R, T>where
        <R as RawRwLock>::GuardMarker: Unpin,
    ",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedRwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> Unpin for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl Unpin for GuardSend",1,["lock_api::GuardSend"]],["impl Unpin for GuardNoSend",1,["lock_api::GuardNoSend"]]], -"lru":[["impl<K, V, S> Unpin for LruCache<K, V, S>where
        S: Unpin,
    ",1,["lru::LruCache"]],["impl<'a, K, V> Unpin for Iter<'a, K, V>",1,["lru::Iter"]],["impl<'a, K, V> Unpin for IterMut<'a, K, V>",1,["lru::IterMut"]],["impl<K, V> Unpin for IntoIter<K, V>",1,["lru::IntoIter"]]], -"memchr":[["impl<'a> Unpin for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> Unpin for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> Unpin for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl Unpin for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> Unpin for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> Unpin for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> Unpin for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> Unpin for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl Unpin for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl Unpin for Dir",1,["nix::dir::Dir"]],["impl<'d> Unpin for Iter<'d>",1,["nix::dir::Iter"]],["impl Unpin for OwningIter",1,["nix::dir::OwningIter"]],["impl Unpin for Entry",1,["nix::dir::Entry"]],["impl Unpin for Type",1,["nix::dir::Type"]],["impl Unpin for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl Unpin for Errno",1,["nix::errno::consts::Errno"]],["impl Unpin for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl Unpin for AtFlags",1,["nix::fcntl::AtFlags"]],["impl Unpin for OFlag",1,["nix::fcntl::OFlag"]],["impl Unpin for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl Unpin for SealFlag",1,["nix::fcntl::SealFlag"]],["impl Unpin for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> Unpin for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl Unpin for FlockArg",1,["nix::fcntl::FlockArg"]],["impl Unpin for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl Unpin for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl Unpin for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl Unpin for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl Unpin for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl Unpin for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> Unpin for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl Unpin for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl Unpin for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl Unpin for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl Unpin for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl Unpin for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl Unpin for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl Unpin for MqAttr",1,["nix::mqueue::MqAttr"]],["impl Unpin for MqdT",1,["nix::mqueue::MqdT"]],["impl Unpin for PollFd",1,["nix::poll::PollFd"]],["impl Unpin for PollFlags",1,["nix::poll::PollFlags"]],["impl Unpin for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl Unpin for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl Unpin for PtyMaster",1,["nix::pty::PtyMaster"]],["impl Unpin for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl Unpin for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl Unpin for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl Unpin for LioMode",1,["nix::sys::aio::LioMode"]],["impl Unpin for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl !Unpin for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> !Unpin for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> !Unpin for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl Unpin for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl Unpin for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl Unpin for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl Unpin for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl Unpin for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl Unpin for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl Unpin for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl Unpin for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl Unpin for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl Unpin for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl Unpin for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl Unpin for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl Unpin for Persona",1,["nix::sys::personality::Persona"]],["impl Unpin for Request",1,["nix::sys::ptrace::linux::Request"]],["impl Unpin for Event",1,["nix::sys::ptrace::linux::Event"]],["impl Unpin for Options",1,["nix::sys::ptrace::linux::Options"]],["impl Unpin for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl Unpin for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl Unpin for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl Unpin for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl Unpin for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl Unpin for Resource",1,["nix::sys::resource::Resource"]],["impl Unpin for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl Unpin for Usage",1,["nix::sys::resource::Usage"]],["impl Unpin for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> Unpin for Fds<'a>",1,["nix::sys::select::Fds"]],["impl Unpin for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl Unpin for Signal",1,["nix::sys::signal::Signal"]],["impl Unpin for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl Unpin for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl Unpin for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl Unpin for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> Unpin for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl Unpin for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl Unpin for SigAction",1,["nix::sys::signal::SigAction"]],["impl Unpin for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl Unpin for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl Unpin for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl Unpin for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl Unpin for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl Unpin for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl Unpin for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl Unpin for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl Unpin for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl Unpin for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl Unpin for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl Unpin for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl Unpin for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl Unpin for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl Unpin for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl Unpin for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl Unpin for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl Unpin for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl Unpin for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl Unpin for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl Unpin for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl Unpin for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl Unpin for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl Unpin for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl Unpin for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl Unpin for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl Unpin for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl Unpin for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl Unpin for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl Unpin for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl Unpin for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl Unpin for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl Unpin for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl Unpin for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl Unpin for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl Unpin for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl Unpin for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl Unpin for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl Unpin for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl Unpin for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl Unpin for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl Unpin for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl Unpin for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl Unpin for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl Unpin for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl Unpin for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl Unpin for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl Unpin for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl Unpin for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl Unpin for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl Unpin for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl Unpin for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl Unpin for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl Unpin for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl Unpin for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl Unpin for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl Unpin for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl Unpin for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl Unpin for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl Unpin for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl Unpin for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl Unpin for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl Unpin for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl Unpin for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl Unpin for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl Unpin for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl Unpin for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl Unpin for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl Unpin for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl Unpin for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl Unpin for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl Unpin for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl Unpin for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl Unpin for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl Unpin for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl Unpin for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl Unpin for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> Unpin for AlgSetKey<T>where
        T: Unpin,
    ",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl Unpin for SockType",1,["nix::sys::socket::SockType"]],["impl Unpin for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl Unpin for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl Unpin for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl Unpin for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl Unpin for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl Unpin for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl Unpin for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> Unpin for RecvMsg<'a, 's, S>where
        S: Unpin,
    ",1,["nix::sys::socket::RecvMsg"]],["impl<'a> Unpin for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl Unpin for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl Unpin for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> Unpin for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> Unpin for MultiHeaders<S>",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> Unpin for MultiResults<'a, S>",1,["nix::sys::socket::MultiResults"]],["impl<'a> Unpin for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl Unpin for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl Unpin for SFlag",1,["nix::sys::stat::SFlag"]],["impl Unpin for Mode",1,["nix::sys::stat::Mode"]],["impl Unpin for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl Unpin for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl Unpin for Statfs",1,["nix::sys::statfs::Statfs"]],["impl Unpin for FsType",1,["nix::sys::statfs::FsType"]],["impl Unpin for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl Unpin for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl Unpin for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl Unpin for Termios",1,["nix::sys::termios::Termios"]],["impl Unpin for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl Unpin for SetArg",1,["nix::sys::termios::SetArg"]],["impl Unpin for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl Unpin for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl Unpin for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl Unpin for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl Unpin for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl Unpin for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl Unpin for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl Unpin for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl Unpin for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl Unpin for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl Unpin for TimeVal",1,["nix::sys::time::TimeVal"]],["impl Unpin for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> Unpin for IoVec<T>where
        T: Unpin,
    ",1,["nix::sys::uio::IoVec"]],["impl Unpin for UtsName",1,["nix::sys::utsname::UtsName"]],["impl Unpin for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl Unpin for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl Unpin for Id",1,["nix::sys::wait::Id"]],["impl Unpin for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl Unpin for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl Unpin for Inotify",1,["nix::sys::inotify::Inotify"]],["impl Unpin for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl Unpin for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl Unpin for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl Unpin for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl Unpin for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl Unpin for Timer",1,["nix::sys::timer::Timer"]],["impl Unpin for ClockId",1,["nix::time::ClockId"]],["impl Unpin for UContext",1,["nix::ucontext::UContext"]],["impl Unpin for ResUid",1,["nix::unistd::getres::ResUid"]],["impl Unpin for ResGid",1,["nix::unistd::getres::ResGid"]],["impl Unpin for Uid",1,["nix::unistd::Uid"]],["impl Unpin for Gid",1,["nix::unistd::Gid"]],["impl Unpin for Pid",1,["nix::unistd::Pid"]],["impl Unpin for ForkResult",1,["nix::unistd::ForkResult"]],["impl Unpin for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl Unpin for Whence",1,["nix::unistd::Whence"]],["impl Unpin for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl Unpin for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl Unpin for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl Unpin for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl Unpin for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl Unpin for User",1,["nix::unistd::User"]],["impl Unpin for Group",1,["nix::unistd::Group"]]], -"once_cell":[["impl<T> Unpin for OnceCell<T>where
        T: Unpin,
    ",1,["once_cell::unsync::OnceCell"]],["impl<T, F> Unpin for Lazy<T, F>where
        F: Unpin,
        T: Unpin,
    ",1,["once_cell::unsync::Lazy"]],["impl<T> Unpin for OnceCell<T>where
        T: Unpin,
    ",1,["once_cell::sync::OnceCell"]],["impl<T, F> Unpin for Lazy<T, F>where
        F: Unpin,
        T: Unpin,
    ",1,["once_cell::sync::Lazy"]],["impl<T> Unpin for OnceBox<T>",1,["once_cell::race::once_box::OnceBox"]],["impl Unpin for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl Unpin for OnceBool",1,["once_cell::race::OnceBool"]]], -"parking_lot":[["impl Unpin for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl Unpin for Condvar",1,["parking_lot::condvar::Condvar"]],["impl Unpin for OnceState",1,["parking_lot::once::OnceState"]],["impl Unpin for Once",1,["parking_lot::once::Once"]],["impl Unpin for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl Unpin for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl Unpin for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl Unpin for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl Unpin for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl Unpin for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl Unpin for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl Unpin for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl Unpin for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl Unpin for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl Unpin for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl Unpin for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl Unpin for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl Unpin for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl Unpin for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl Unpin for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl Unpin for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl Unpin for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl Unpin for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl Unpin for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl Unpin for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> Unpin for SseMachine<S3, S4, NI>where
        NI: Unpin,
        S3: Unpin,
        S4: Unpin,
    ",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> Unpin for Avx2Machine<NI>where
        NI: Unpin,
    ",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl Unpin for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl Unpin for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl Unpin for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl Unpin for Error",1,["primitive_types::Error"]],["impl Unpin for U128",1,["primitive_types::U128"]],["impl Unpin for U256",1,["primitive_types::U256"]],["impl Unpin for U512",1,["primitive_types::U512"]],["impl Unpin for H128",1,["primitive_types::H128"]],["impl Unpin for H160",1,["primitive_types::H160"]],["impl Unpin for H256",1,["primitive_types::H256"]],["impl Unpin for H384",1,["primitive_types::H384"]],["impl Unpin for H512",1,["primitive_types::H512"]],["impl Unpin for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl Unpin for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl Unpin for TokenStream",1,["proc_macro2::TokenStream"]],["impl Unpin for LexError",1,["proc_macro2::LexError"]],["impl Unpin for Span",1,["proc_macro2::Span"]],["impl Unpin for TokenTree",1,["proc_macro2::TokenTree"]],["impl Unpin for Group",1,["proc_macro2::Group"]],["impl Unpin for Delimiter",1,["proc_macro2::Delimiter"]],["impl Unpin for Punct",1,["proc_macro2::Punct"]],["impl Unpin for Spacing",1,["proc_macro2::Spacing"]],["impl Unpin for Ident",1,["proc_macro2::Ident"]],["impl Unpin for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl Unpin for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl Unpin for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> Unpin for DistIter<D, R, T>where
        D: Unpin,
        R: Unpin,
        T: Unpin,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> Unpin for DistMap<D, F, T, S>where
        D: Unpin,
        F: Unpin,
    ",1,["rand::distributions::distribution::DistMap"]],["impl Unpin for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl Unpin for Open01",1,["rand::distributions::float::Open01"]],["impl Unpin for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> Unpin for Slice<'a, T>",1,["rand::distributions::slice::Slice"]],["impl<X> Unpin for WeightedIndex<X>where
        X: Unpin,
        <X as SampleUniform>::Sampler: Unpin,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl Unpin for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> Unpin for Uniform<X>where
        <X as SampleUniform>::Sampler: Unpin,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> Unpin for UniformInt<X>where
        X: Unpin,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl Unpin for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> Unpin for UniformFloat<X>where
        X: Unpin,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl Unpin for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> Unpin for WeightedIndex<W>where
        W: Unpin,
    ",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl Unpin for Standard",1,["rand::distributions::Standard"]],["impl<R> Unpin for ReadRng<R>where
        R: Unpin,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl Unpin for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> Unpin for ReseedingRng<R, Rsdr>where
        R: Unpin,
        Rsdr: Unpin,
        <R as BlockRngCore>::Results: Unpin,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl Unpin for StepRng",1,["rand::rngs::mock::StepRng"]],["impl Unpin for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> Unpin for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl Unpin for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> Unpin for SliceChooseIter<'a, S, T>where
        T: Unpin,
    ",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl Unpin for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl Unpin for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl Unpin for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl Unpin for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl Unpin for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl Unpin for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> Unpin for BlockRng<R>where
        R: Unpin,
        <R as BlockRngCore>::Results: Unpin,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> Unpin for BlockRng64<R>where
        R: Unpin,
        <R as BlockRngCore>::Results: Unpin,
    ",1,["rand_core::block::BlockRng64"]],["impl Unpin for Error",1,["rand_core::error::Error"]],["impl Unpin for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl Unpin for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl Unpin for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> Unpin for Match<'t>",1,["regex::re_bytes::Match"]],["impl Unpin for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> Unpin for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> Unpin for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> Unpin for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> Unpin for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> Unpin for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl Unpin for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> Unpin for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> Unpin for SubCaptureMatches<'c, 't>where
        't: 'c,
    ",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> Unpin for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> Unpin for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl Unpin for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl Unpin for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl Unpin for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> Unpin for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl Unpin for Error",1,["regex::error::Error"]],["impl Unpin for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl Unpin for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl Unpin for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl Unpin for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl Unpin for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> Unpin for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> Unpin for Match<'t>",1,["regex::re_unicode::Match"]],["impl Unpin for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> Unpin for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> Unpin for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> Unpin for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl Unpin for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> Unpin for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> Unpin for SubCaptureMatches<'c, 't>where
        't: 'c,
    ",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> Unpin for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> Unpin for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> Unpin for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> Unpin for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl Unpin for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl Unpin for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl Unpin for Printer",1,["regex_syntax::ast::print::Printer"]],["impl Unpin for Error",1,["regex_syntax::ast::Error"]],["impl Unpin for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl Unpin for Span",1,["regex_syntax::ast::Span"]],["impl Unpin for Position",1,["regex_syntax::ast::Position"]],["impl Unpin for WithComments",1,["regex_syntax::ast::WithComments"]],["impl Unpin for Comment",1,["regex_syntax::ast::Comment"]],["impl Unpin for Ast",1,["regex_syntax::ast::Ast"]],["impl Unpin for Alternation",1,["regex_syntax::ast::Alternation"]],["impl Unpin for Concat",1,["regex_syntax::ast::Concat"]],["impl Unpin for Literal",1,["regex_syntax::ast::Literal"]],["impl Unpin for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl Unpin for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl Unpin for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl Unpin for Class",1,["regex_syntax::ast::Class"]],["impl Unpin for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl Unpin for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl Unpin for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl Unpin for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl Unpin for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl Unpin for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl Unpin for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl Unpin for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl Unpin for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl Unpin for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl Unpin for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl Unpin for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl Unpin for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl Unpin for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl Unpin for Assertion",1,["regex_syntax::ast::Assertion"]],["impl Unpin for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl Unpin for Repetition",1,["regex_syntax::ast::Repetition"]],["impl Unpin for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl Unpin for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl Unpin for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl Unpin for Group",1,["regex_syntax::ast::Group"]],["impl Unpin for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl Unpin for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl Unpin for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl Unpin for Flags",1,["regex_syntax::ast::Flags"]],["impl Unpin for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl Unpin for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl Unpin for Flag",1,["regex_syntax::ast::Flag"]],["impl Unpin for Error",1,["regex_syntax::error::Error"]],["impl Unpin for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl Unpin for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl Unpin for Printer",1,["regex_syntax::hir::print::Printer"]],["impl Unpin for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl Unpin for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl Unpin for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl Unpin for Error",1,["regex_syntax::hir::Error"]],["impl Unpin for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl Unpin for Hir",1,["regex_syntax::hir::Hir"]],["impl Unpin for HirKind",1,["regex_syntax::hir::HirKind"]],["impl Unpin for Literal",1,["regex_syntax::hir::Literal"]],["impl Unpin for Class",1,["regex_syntax::hir::Class"]],["impl Unpin for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> Unpin for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl Unpin for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl Unpin for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> Unpin for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl Unpin for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl Unpin for Anchor",1,["regex_syntax::hir::Anchor"]],["impl Unpin for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl Unpin for Group",1,["regex_syntax::hir::Group"]],["impl Unpin for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl Unpin for Repetition",1,["regex_syntax::hir::Repetition"]],["impl Unpin for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl Unpin for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl Unpin for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl Unpin for Parser",1,["regex_syntax::parser::Parser"]],["impl Unpin for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl Unpin for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl Unpin for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl Unpin for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl Unpin for DecoderError",1,["rlp::error::DecoderError"]],["impl Unpin for Prototype",1,["rlp::rlpin::Prototype"]],["impl Unpin for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> Unpin for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> Unpin for RlpIterator<'a, 'view>where
        'a: 'view,
    ",1,["rlp::rlpin::RlpIterator"]],["impl Unpin for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> Unpin for ToHexIter<T>where
        T: Unpin,
    ",1,["rustc_hex::ToHexIter"]],["impl Unpin for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> Unpin for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl Unpin for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl Unpin for Always",1,["scopeguard::Always"]],["impl<T, F, S> Unpin for ScopeGuard<T, F, S>where
        F: Unpin,
        T: Unpin,
    ",1,["scopeguard::ScopeGuard"]]], -"serde":[["impl Unpin for Error",1,["serde::de::value::Error"]],["impl<E> Unpin for UnitDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::UnitDeserializer"]],["impl<E> Unpin for BoolDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::BoolDeserializer"]],["impl<E> Unpin for I8Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::I8Deserializer"]],["impl<E> Unpin for I16Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::I16Deserializer"]],["impl<E> Unpin for I32Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::I32Deserializer"]],["impl<E> Unpin for I64Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::I64Deserializer"]],["impl<E> Unpin for IsizeDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::IsizeDeserializer"]],["impl<E> Unpin for U8Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::U8Deserializer"]],["impl<E> Unpin for U16Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::U16Deserializer"]],["impl<E> Unpin for U64Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::U64Deserializer"]],["impl<E> Unpin for UsizeDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::UsizeDeserializer"]],["impl<E> Unpin for F32Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::F32Deserializer"]],["impl<E> Unpin for F64Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::F64Deserializer"]],["impl<E> Unpin for CharDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::CharDeserializer"]],["impl<E> Unpin for I128Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::I128Deserializer"]],["impl<E> Unpin for U128Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::U128Deserializer"]],["impl<E> Unpin for U32Deserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> Unpin for StrDeserializer<'a, E>where
        E: Unpin,
    ",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> Unpin for BorrowedStrDeserializer<'de, E>where
        E: Unpin,
    ",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> Unpin for StringDeserializer<E>where
        E: Unpin,
    ",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> Unpin for CowStrDeserializer<'a, E>where
        E: Unpin,
    ",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> Unpin for BytesDeserializer<'a, E>where
        E: Unpin,
    ",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> Unpin for BorrowedBytesDeserializer<'de, E>where
        E: Unpin,
    ",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> Unpin for SeqDeserializer<I, E>where
        E: Unpin,
        I: Unpin,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> Unpin for SeqAccessDeserializer<A>where
        A: Unpin,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> Unpin for MapDeserializer<'de, I, E>where
        E: Unpin,
        I: Unpin,
        <<I as Iterator>::Item as Pair>::Second: Unpin,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> Unpin for MapAccessDeserializer<A>where
        A: Unpin,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> Unpin for EnumAccessDeserializer<A>where
        A: Unpin,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl Unpin for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> Unpin for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> Unpin for Impossible<Ok, Error>where
        Error: Unpin,
        Ok: Unpin,
    ",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl Unpin for Keccak224Core",1,["sha3::Keccak224Core"]],["impl Unpin for Keccak256Core",1,["sha3::Keccak256Core"]],["impl Unpin for Keccak384Core",1,["sha3::Keccak384Core"]],["impl Unpin for Keccak512Core",1,["sha3::Keccak512Core"]],["impl Unpin for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl Unpin for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl Unpin for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl Unpin for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl Unpin for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl Unpin for Shake128Core",1,["sha3::Shake128Core"]],["impl Unpin for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl Unpin for Shake256Core",1,["sha3::Shake256Core"]],["impl Unpin for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl Unpin for CShake128Core",1,["sha3::CShake128Core"]],["impl Unpin for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl Unpin for CShake256Core",1,["sha3::CShake256Core"]],["impl Unpin for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl Unpin for CompactHeader",1,["shale::compact::CompactHeader"]],["impl Unpin for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> Unpin for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl Unpin for ShaleError",1,["shale::ShaleError"]],["impl Unpin for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> Unpin for ObjPtr<T>where
        T: Unpin,
    ",1,["shale::ObjPtr"]],["impl<T: ?Sized> Unpin for Obj<T>",1,["shale::Obj"]],["impl<'a, T> Unpin for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> Unpin for MummyObj<T>where
        T: Unpin,
    ",1,["shale::MummyObj"]],["impl Unpin for PlainMem",1,["shale::PlainMem"]],["impl<T: ?Sized> Unpin for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> Unpin for Slab<T>where
        T: Unpin,
    ",1,["slab::Slab"]],["impl<'a, T> Unpin for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> Unpin for IntoIter<T>where
        T: Unpin,
    ",1,["slab::IntoIter"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["slab::Iter"]],["impl<'a, T> Unpin for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> Unpin for Drain<'a, T>",1,["slab::Drain"]]], -"smallvec":[["impl Unpin for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> Unpin for Drain<'a, T>",1,["smallvec::Drain"]],["impl<A> Unpin for SmallVec<A>where
        A: Unpin,
    ",1,["smallvec::SmallVec"]],["impl<A> Unpin for IntoIter<A>where
        A: Unpin,
    ",1,["smallvec::IntoIter"]]], -"syn":[["impl Unpin for Underscore",1,["syn::token::Underscore"]],["impl Unpin for Abstract",1,["syn::token::Abstract"]],["impl Unpin for As",1,["syn::token::As"]],["impl Unpin for Async",1,["syn::token::Async"]],["impl Unpin for Auto",1,["syn::token::Auto"]],["impl Unpin for Await",1,["syn::token::Await"]],["impl Unpin for Become",1,["syn::token::Become"]],["impl Unpin for Box",1,["syn::token::Box"]],["impl Unpin for Break",1,["syn::token::Break"]],["impl Unpin for Const",1,["syn::token::Const"]],["impl Unpin for Continue",1,["syn::token::Continue"]],["impl Unpin for Crate",1,["syn::token::Crate"]],["impl Unpin for Default",1,["syn::token::Default"]],["impl Unpin for Do",1,["syn::token::Do"]],["impl Unpin for Dyn",1,["syn::token::Dyn"]],["impl Unpin for Else",1,["syn::token::Else"]],["impl Unpin for Enum",1,["syn::token::Enum"]],["impl Unpin for Extern",1,["syn::token::Extern"]],["impl Unpin for Final",1,["syn::token::Final"]],["impl Unpin for Fn",1,["syn::token::Fn"]],["impl Unpin for For",1,["syn::token::For"]],["impl Unpin for If",1,["syn::token::If"]],["impl Unpin for Impl",1,["syn::token::Impl"]],["impl Unpin for In",1,["syn::token::In"]],["impl Unpin for Let",1,["syn::token::Let"]],["impl Unpin for Loop",1,["syn::token::Loop"]],["impl Unpin for Macro",1,["syn::token::Macro"]],["impl Unpin for Match",1,["syn::token::Match"]],["impl Unpin for Mod",1,["syn::token::Mod"]],["impl Unpin for Move",1,["syn::token::Move"]],["impl Unpin for Mut",1,["syn::token::Mut"]],["impl Unpin for Override",1,["syn::token::Override"]],["impl Unpin for Priv",1,["syn::token::Priv"]],["impl Unpin for Pub",1,["syn::token::Pub"]],["impl Unpin for Ref",1,["syn::token::Ref"]],["impl Unpin for Return",1,["syn::token::Return"]],["impl Unpin for SelfType",1,["syn::token::SelfType"]],["impl Unpin for SelfValue",1,["syn::token::SelfValue"]],["impl Unpin for Static",1,["syn::token::Static"]],["impl Unpin for Struct",1,["syn::token::Struct"]],["impl Unpin for Super",1,["syn::token::Super"]],["impl Unpin for Trait",1,["syn::token::Trait"]],["impl Unpin for Try",1,["syn::token::Try"]],["impl Unpin for Type",1,["syn::token::Type"]],["impl Unpin for Typeof",1,["syn::token::Typeof"]],["impl Unpin for Union",1,["syn::token::Union"]],["impl Unpin for Unsafe",1,["syn::token::Unsafe"]],["impl Unpin for Unsized",1,["syn::token::Unsized"]],["impl Unpin for Use",1,["syn::token::Use"]],["impl Unpin for Virtual",1,["syn::token::Virtual"]],["impl Unpin for Where",1,["syn::token::Where"]],["impl Unpin for While",1,["syn::token::While"]],["impl Unpin for Yield",1,["syn::token::Yield"]],["impl Unpin for Add",1,["syn::token::Add"]],["impl Unpin for AddEq",1,["syn::token::AddEq"]],["impl Unpin for And",1,["syn::token::And"]],["impl Unpin for AndAnd",1,["syn::token::AndAnd"]],["impl Unpin for AndEq",1,["syn::token::AndEq"]],["impl Unpin for At",1,["syn::token::At"]],["impl Unpin for Bang",1,["syn::token::Bang"]],["impl Unpin for Caret",1,["syn::token::Caret"]],["impl Unpin for CaretEq",1,["syn::token::CaretEq"]],["impl Unpin for Colon",1,["syn::token::Colon"]],["impl Unpin for Colon2",1,["syn::token::Colon2"]],["impl Unpin for Comma",1,["syn::token::Comma"]],["impl Unpin for Div",1,["syn::token::Div"]],["impl Unpin for DivEq",1,["syn::token::DivEq"]],["impl Unpin for Dollar",1,["syn::token::Dollar"]],["impl Unpin for Dot",1,["syn::token::Dot"]],["impl Unpin for Dot2",1,["syn::token::Dot2"]],["impl Unpin for Dot3",1,["syn::token::Dot3"]],["impl Unpin for DotDotEq",1,["syn::token::DotDotEq"]],["impl Unpin for Eq",1,["syn::token::Eq"]],["impl Unpin for EqEq",1,["syn::token::EqEq"]],["impl Unpin for Ge",1,["syn::token::Ge"]],["impl Unpin for Gt",1,["syn::token::Gt"]],["impl Unpin for Le",1,["syn::token::Le"]],["impl Unpin for Lt",1,["syn::token::Lt"]],["impl Unpin for MulEq",1,["syn::token::MulEq"]],["impl Unpin for Ne",1,["syn::token::Ne"]],["impl Unpin for Or",1,["syn::token::Or"]],["impl Unpin for OrEq",1,["syn::token::OrEq"]],["impl Unpin for OrOr",1,["syn::token::OrOr"]],["impl Unpin for Pound",1,["syn::token::Pound"]],["impl Unpin for Question",1,["syn::token::Question"]],["impl Unpin for RArrow",1,["syn::token::RArrow"]],["impl Unpin for LArrow",1,["syn::token::LArrow"]],["impl Unpin for Rem",1,["syn::token::Rem"]],["impl Unpin for RemEq",1,["syn::token::RemEq"]],["impl Unpin for FatArrow",1,["syn::token::FatArrow"]],["impl Unpin for Semi",1,["syn::token::Semi"]],["impl Unpin for Shl",1,["syn::token::Shl"]],["impl Unpin for ShlEq",1,["syn::token::ShlEq"]],["impl Unpin for Shr",1,["syn::token::Shr"]],["impl Unpin for ShrEq",1,["syn::token::ShrEq"]],["impl Unpin for Star",1,["syn::token::Star"]],["impl Unpin for Sub",1,["syn::token::Sub"]],["impl Unpin for SubEq",1,["syn::token::SubEq"]],["impl Unpin for Tilde",1,["syn::token::Tilde"]],["impl Unpin for Brace",1,["syn::token::Brace"]],["impl Unpin for Bracket",1,["syn::token::Bracket"]],["impl Unpin for Paren",1,["syn::token::Paren"]],["impl Unpin for Group",1,["syn::token::Group"]],["impl Unpin for Attribute",1,["syn::attr::Attribute"]],["impl Unpin for AttrStyle",1,["syn::attr::AttrStyle"]],["impl Unpin for Meta",1,["syn::attr::Meta"]],["impl Unpin for MetaList",1,["syn::attr::MetaList"]],["impl Unpin for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl Unpin for NestedMeta",1,["syn::attr::NestedMeta"]],["impl Unpin for Variant",1,["syn::data::Variant"]],["impl Unpin for Fields",1,["syn::data::Fields"]],["impl Unpin for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl Unpin for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl Unpin for Field",1,["syn::data::Field"]],["impl Unpin for Visibility",1,["syn::data::Visibility"]],["impl Unpin for VisPublic",1,["syn::data::VisPublic"]],["impl Unpin for VisCrate",1,["syn::data::VisCrate"]],["impl Unpin for VisRestricted",1,["syn::data::VisRestricted"]],["impl Unpin for Expr",1,["syn::expr::Expr"]],["impl Unpin for ExprArray",1,["syn::expr::ExprArray"]],["impl Unpin for ExprAssign",1,["syn::expr::ExprAssign"]],["impl Unpin for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl Unpin for ExprAsync",1,["syn::expr::ExprAsync"]],["impl Unpin for ExprAwait",1,["syn::expr::ExprAwait"]],["impl Unpin for ExprBinary",1,["syn::expr::ExprBinary"]],["impl Unpin for ExprBlock",1,["syn::expr::ExprBlock"]],["impl Unpin for ExprBox",1,["syn::expr::ExprBox"]],["impl Unpin for ExprBreak",1,["syn::expr::ExprBreak"]],["impl Unpin for ExprCall",1,["syn::expr::ExprCall"]],["impl Unpin for ExprCast",1,["syn::expr::ExprCast"]],["impl Unpin for ExprClosure",1,["syn::expr::ExprClosure"]],["impl Unpin for ExprContinue",1,["syn::expr::ExprContinue"]],["impl Unpin for ExprField",1,["syn::expr::ExprField"]],["impl Unpin for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl Unpin for ExprGroup",1,["syn::expr::ExprGroup"]],["impl Unpin for ExprIf",1,["syn::expr::ExprIf"]],["impl Unpin for ExprIndex",1,["syn::expr::ExprIndex"]],["impl Unpin for ExprLet",1,["syn::expr::ExprLet"]],["impl Unpin for ExprLit",1,["syn::expr::ExprLit"]],["impl Unpin for ExprLoop",1,["syn::expr::ExprLoop"]],["impl Unpin for ExprMacro",1,["syn::expr::ExprMacro"]],["impl Unpin for ExprMatch",1,["syn::expr::ExprMatch"]],["impl Unpin for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl Unpin for ExprParen",1,["syn::expr::ExprParen"]],["impl Unpin for ExprPath",1,["syn::expr::ExprPath"]],["impl Unpin for ExprRange",1,["syn::expr::ExprRange"]],["impl Unpin for ExprReference",1,["syn::expr::ExprReference"]],["impl Unpin for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl Unpin for ExprReturn",1,["syn::expr::ExprReturn"]],["impl Unpin for ExprStruct",1,["syn::expr::ExprStruct"]],["impl Unpin for ExprTry",1,["syn::expr::ExprTry"]],["impl Unpin for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl Unpin for ExprTuple",1,["syn::expr::ExprTuple"]],["impl Unpin for ExprType",1,["syn::expr::ExprType"]],["impl Unpin for ExprUnary",1,["syn::expr::ExprUnary"]],["impl Unpin for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl Unpin for ExprWhile",1,["syn::expr::ExprWhile"]],["impl Unpin for ExprYield",1,["syn::expr::ExprYield"]],["impl Unpin for Member",1,["syn::expr::Member"]],["impl Unpin for Index",1,["syn::expr::Index"]],["impl Unpin for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl Unpin for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl Unpin for FieldValue",1,["syn::expr::FieldValue"]],["impl Unpin for Label",1,["syn::expr::Label"]],["impl Unpin for Arm",1,["syn::expr::Arm"]],["impl Unpin for RangeLimits",1,["syn::expr::RangeLimits"]],["impl Unpin for Generics",1,["syn::generics::Generics"]],["impl Unpin for GenericParam",1,["syn::generics::GenericParam"]],["impl Unpin for TypeParam",1,["syn::generics::TypeParam"]],["impl Unpin for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl Unpin for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> Unpin for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> Unpin for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> Unpin for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl Unpin for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl Unpin for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl Unpin for TraitBound",1,["syn::generics::TraitBound"]],["impl Unpin for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl Unpin for WhereClause",1,["syn::generics::WhereClause"]],["impl Unpin for WherePredicate",1,["syn::generics::WherePredicate"]],["impl Unpin for PredicateType",1,["syn::generics::PredicateType"]],["impl Unpin for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl Unpin for PredicateEq",1,["syn::generics::PredicateEq"]],["impl Unpin for Item",1,["syn::item::Item"]],["impl Unpin for ItemConst",1,["syn::item::ItemConst"]],["impl Unpin for ItemEnum",1,["syn::item::ItemEnum"]],["impl Unpin for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl Unpin for ItemFn",1,["syn::item::ItemFn"]],["impl Unpin for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl Unpin for ItemImpl",1,["syn::item::ItemImpl"]],["impl Unpin for ItemMacro",1,["syn::item::ItemMacro"]],["impl Unpin for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl Unpin for ItemMod",1,["syn::item::ItemMod"]],["impl Unpin for ItemStatic",1,["syn::item::ItemStatic"]],["impl Unpin for ItemStruct",1,["syn::item::ItemStruct"]],["impl Unpin for ItemTrait",1,["syn::item::ItemTrait"]],["impl Unpin for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl Unpin for ItemType",1,["syn::item::ItemType"]],["impl Unpin for ItemUnion",1,["syn::item::ItemUnion"]],["impl Unpin for ItemUse",1,["syn::item::ItemUse"]],["impl Unpin for UseTree",1,["syn::item::UseTree"]],["impl Unpin for UsePath",1,["syn::item::UsePath"]],["impl Unpin for UseName",1,["syn::item::UseName"]],["impl Unpin for UseRename",1,["syn::item::UseRename"]],["impl Unpin for UseGlob",1,["syn::item::UseGlob"]],["impl Unpin for UseGroup",1,["syn::item::UseGroup"]],["impl Unpin for ForeignItem",1,["syn::item::ForeignItem"]],["impl Unpin for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl Unpin for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl Unpin for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl Unpin for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl Unpin for TraitItem",1,["syn::item::TraitItem"]],["impl Unpin for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl Unpin for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl Unpin for TraitItemType",1,["syn::item::TraitItemType"]],["impl Unpin for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl Unpin for ImplItem",1,["syn::item::ImplItem"]],["impl Unpin for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl Unpin for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl Unpin for ImplItemType",1,["syn::item::ImplItemType"]],["impl Unpin for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl Unpin for Signature",1,["syn::item::Signature"]],["impl Unpin for FnArg",1,["syn::item::FnArg"]],["impl Unpin for Receiver",1,["syn::item::Receiver"]],["impl Unpin for File",1,["syn::file::File"]],["impl Unpin for Lifetime",1,["syn::lifetime::Lifetime"]],["impl Unpin for Lit",1,["syn::lit::Lit"]],["impl Unpin for LitStr",1,["syn::lit::LitStr"]],["impl Unpin for LitByteStr",1,["syn::lit::LitByteStr"]],["impl Unpin for LitByte",1,["syn::lit::LitByte"]],["impl Unpin for LitChar",1,["syn::lit::LitChar"]],["impl Unpin for LitInt",1,["syn::lit::LitInt"]],["impl Unpin for LitFloat",1,["syn::lit::LitFloat"]],["impl Unpin for LitBool",1,["syn::lit::LitBool"]],["impl Unpin for StrStyle",1,["syn::lit::StrStyle"]],["impl Unpin for Macro",1,["syn::mac::Macro"]],["impl Unpin for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl Unpin for DeriveInput",1,["syn::derive::DeriveInput"]],["impl Unpin for Data",1,["syn::derive::Data"]],["impl Unpin for DataStruct",1,["syn::derive::DataStruct"]],["impl Unpin for DataEnum",1,["syn::derive::DataEnum"]],["impl Unpin for DataUnion",1,["syn::derive::DataUnion"]],["impl Unpin for BinOp",1,["syn::op::BinOp"]],["impl Unpin for UnOp",1,["syn::op::UnOp"]],["impl Unpin for Block",1,["syn::stmt::Block"]],["impl Unpin for Stmt",1,["syn::stmt::Stmt"]],["impl Unpin for Local",1,["syn::stmt::Local"]],["impl Unpin for Type",1,["syn::ty::Type"]],["impl Unpin for TypeArray",1,["syn::ty::TypeArray"]],["impl Unpin for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl Unpin for TypeGroup",1,["syn::ty::TypeGroup"]],["impl Unpin for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl Unpin for TypeInfer",1,["syn::ty::TypeInfer"]],["impl Unpin for TypeMacro",1,["syn::ty::TypeMacro"]],["impl Unpin for TypeNever",1,["syn::ty::TypeNever"]],["impl Unpin for TypeParen",1,["syn::ty::TypeParen"]],["impl Unpin for TypePath",1,["syn::ty::TypePath"]],["impl Unpin for TypePtr",1,["syn::ty::TypePtr"]],["impl Unpin for TypeReference",1,["syn::ty::TypeReference"]],["impl Unpin for TypeSlice",1,["syn::ty::TypeSlice"]],["impl Unpin for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl Unpin for TypeTuple",1,["syn::ty::TypeTuple"]],["impl Unpin for Abi",1,["syn::ty::Abi"]],["impl Unpin for BareFnArg",1,["syn::ty::BareFnArg"]],["impl Unpin for Variadic",1,["syn::ty::Variadic"]],["impl Unpin for ReturnType",1,["syn::ty::ReturnType"]],["impl Unpin for Pat",1,["syn::pat::Pat"]],["impl Unpin for PatBox",1,["syn::pat::PatBox"]],["impl Unpin for PatIdent",1,["syn::pat::PatIdent"]],["impl Unpin for PatLit",1,["syn::pat::PatLit"]],["impl Unpin for PatMacro",1,["syn::pat::PatMacro"]],["impl Unpin for PatOr",1,["syn::pat::PatOr"]],["impl Unpin for PatPath",1,["syn::pat::PatPath"]],["impl Unpin for PatRange",1,["syn::pat::PatRange"]],["impl Unpin for PatReference",1,["syn::pat::PatReference"]],["impl Unpin for PatRest",1,["syn::pat::PatRest"]],["impl Unpin for PatSlice",1,["syn::pat::PatSlice"]],["impl Unpin for PatStruct",1,["syn::pat::PatStruct"]],["impl Unpin for PatTuple",1,["syn::pat::PatTuple"]],["impl Unpin for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl Unpin for PatType",1,["syn::pat::PatType"]],["impl Unpin for PatWild",1,["syn::pat::PatWild"]],["impl Unpin for FieldPat",1,["syn::pat::FieldPat"]],["impl Unpin for Path",1,["syn::path::Path"]],["impl Unpin for PathSegment",1,["syn::path::PathSegment"]],["impl Unpin for PathArguments",1,["syn::path::PathArguments"]],["impl Unpin for GenericArgument",1,["syn::path::GenericArgument"]],["impl Unpin for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl Unpin for Binding",1,["syn::path::Binding"]],["impl Unpin for Constraint",1,["syn::path::Constraint"]],["impl Unpin for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl Unpin for QSelf",1,["syn::path::QSelf"]],["impl Unpin for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> Unpin for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> Unpin for Punctuated<T, P>where
        P: Unpin,
        T: Unpin,
    ",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> Unpin for Pairs<'a, T, P>",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> Unpin for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> Unpin for IntoPairs<T, P>where
        P: Unpin,
        T: Unpin,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> Unpin for IntoIter<T>where
        T: Unpin,
    ",1,["syn::punctuated::IntoIter"]],["impl<'a, T> Unpin for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> Unpin for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> Unpin for Pair<T, P>where
        P: Unpin,
        T: Unpin,
    ",1,["syn::punctuated::Pair"]],["impl<'a> Unpin for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl Unpin for Error",1,["syn::error::Error"]],["impl<'a> Unpin for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> Unpin for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl Unpin for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> Unpin for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl Unpin for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl Unpin for AbortHandle",1,["tokio::runtime::task::abort::AbortHandle"]],["impl Unpin for Builder",1,["tokio::runtime::builder::Builder"]],["impl Unpin for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> Unpin for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl Unpin for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl Unpin for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl Unpin for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !Unpin for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl Unpin for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl Unpin for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> Unpin for SendError<T>where
        T: Unpin,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl Unpin for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl Unpin for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> Unpin for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> Unpin for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> Unpin for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> Unpin for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> Unpin for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> Unpin for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> Unpin for SendError<T>where
        T: Unpin,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> Unpin for TrySendError<T>where
        T: Unpin,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl Unpin for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T: ?Sized> Unpin for Mutex<T>where
        T: Unpin,
    ",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T: ?Sized> Unpin for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T: ?Sized> Unpin for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T: ?Sized> Unpin for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl Unpin for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl Unpin for Notify",1,["tokio::sync::notify::Notify"]],["impl Unpin for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl Unpin for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl Unpin for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl Unpin for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl Unpin for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> Unpin for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl Unpin for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T: ?Sized, U: ?Sized> Unpin for OwnedRwLockReadGuard<T, U>where
        T: Unpin,
    ",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T: ?Sized> Unpin for OwnedRwLockWriteGuard<T>where
        T: Unpin,
    ",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T: ?Sized, U: ?Sized> Unpin for OwnedRwLockMappedWriteGuard<T, U>where
        T: Unpin,
    ",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T: ?Sized> Unpin for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T: ?Sized> Unpin for RwLock<T>where
        T: Unpin,
    ",1,["tokio::sync::rwlock::RwLock"]],["impl<T> Unpin for OnceCell<T>where
        T: Unpin,
    ",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> Unpin for SetError<T>where
        T: Unpin,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> Unpin for SendError<T>where
        T: Unpin,
    ",1,["tokio::sync::watch::error::SendError"]],["impl Unpin for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> Unpin for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> Unpin for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> Unpin for Ref<'a, T>",1,["tokio::sync::watch::Ref"]],["impl Unpin for LocalSet",1,["tokio::task::local::LocalSet"]],["impl Unpin for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> Unpin for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> !Unpin for TaskLocalFuture<T, F>",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<T> Unpin for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl<T> Unpin for JoinHandle<T>"],["impl<T> Unpin for Receiver<T>"],["impl<'__pin, F> Unpin for Unconstrained<F>where
        __Origin<'__pin, F>: Unpin,
    "]], -"typenum":[["impl Unpin for B0",1,["typenum::bit::B0"]],["impl Unpin for B1",1,["typenum::bit::B1"]],["impl<U> Unpin for PInt<U>where
        U: Unpin,
    ",1,["typenum::int::PInt"]],["impl<U> Unpin for NInt<U>where
        U: Unpin,
    ",1,["typenum::int::NInt"]],["impl Unpin for Z0",1,["typenum::int::Z0"]],["impl Unpin for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> Unpin for UInt<U, B>where
        B: Unpin,
        U: Unpin,
    ",1,["typenum::uint::UInt"]],["impl Unpin for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> Unpin for TArr<V, A>where
        A: Unpin,
        V: Unpin,
    ",1,["typenum::array::TArr"]],["impl Unpin for Greater",1,["typenum::Greater"]],["impl Unpin for Less",1,["typenum::Less"]],["impl Unpin for Equal",1,["typenum::Equal"]]], -"uint":[["impl Unpin for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl Unpin for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl Unpin for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl Unpin for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/ops/deref/trait.Deref.js b/docs/implementors/core/ops/deref/trait.Deref.js deleted file mode 100644 index 511eb982b7ca..000000000000 --- a/docs/implementors/core/ops/deref/trait.Deref.js +++ /dev/null @@ -1,17 +0,0 @@ -(function() {var implementors = { -"bytes":[["impl Deref for Bytes"],["impl Deref for BytesMut"]], -"crossbeam_utils":[["impl<T> Deref for CachePadded<T>"],["impl<T: ?Sized> Deref for ShardedLockReadGuard<'_, T>"],["impl<T: ?Sized> Deref for ShardedLockWriteGuard<'_, T>"]], -"firewood":[["impl<'a> Deref for Revision<'a>"],["impl Deref for Hash"],["impl Deref for PartialPath"],["impl<'a> Deref for Ref<'a>"]], -"futures_executor":[["impl<S: Stream + Unpin> Deref for BlockingStream<S>"]], -"futures_task":[["impl Deref for WakerRef<'_>"]], -"futures_util":[["impl<T: ?Sized> Deref for OwnedMutexGuard<T>"],["impl<T: ?Sized> Deref for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized> Deref for MappedMutexGuard<'_, T, U>"]], -"generic_array":[["impl<T, N> Deref for GenericArray<T, N>where
        N: ArrayLength<T>,
    "]], -"lock_api":[["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Deref for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Deref for MappedMutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Deref for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Deref for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + 'a> Deref for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Deref for MappedRwLockWriteGuard<'a, R, T>"]], -"once_cell":[["impl<T, F: FnOnce() -> T> Deref for Lazy<T, F>"],["impl<T, F: FnOnce() -> T> Deref for Lazy<T, F>"]], -"regex_syntax":[["impl Deref for Literal"]], -"scopeguard":[["impl<T, F, S> Deref for ScopeGuard<T, F, S>where
        F: FnOnce(T),
        S: Strategy,
    "]], -"shale":[["impl<T> Deref for Obj<T>"],["impl<'a, T> Deref for ObjRef<'a, T>"],["impl<T> Deref for MummyObj<T>"]], -"smallvec":[["impl<A: Array> Deref for SmallVec<A>"]], -"syn":[["impl Deref for Underscore"],["impl Deref for Add"],["impl Deref for And"],["impl Deref for At"],["impl Deref for Bang"],["impl Deref for Caret"],["impl Deref for Colon"],["impl Deref for Comma"],["impl Deref for Div"],["impl Deref for Dollar"],["impl Deref for Dot"],["impl Deref for Eq"],["impl Deref for Gt"],["impl Deref for Lt"],["impl Deref for Or"],["impl Deref for Pound"],["impl Deref for Question"],["impl Deref for Rem"],["impl Deref for Semi"],["impl Deref for Star"],["impl Deref for Sub"],["impl Deref for Tilde"],["impl<'c, 'a> Deref for StepCursor<'c, 'a>"]], -"tokio":[["impl<T: ?Sized> Deref for MutexGuard<'_, T>"],["impl<T: ?Sized> Deref for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized> Deref for MappedMutexGuard<'a, T>"],["impl<T: ?Sized, U: ?Sized> Deref for OwnedRwLockReadGuard<T, U>"],["impl<T: ?Sized> Deref for OwnedRwLockWriteGuard<T>"],["impl<T: ?Sized, U: ?Sized> Deref for OwnedRwLockMappedWriteGuard<T, U>"],["impl<T: ?Sized> Deref for RwLockReadGuard<'_, T>"],["impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T>"],["impl<T: ?Sized> Deref for RwLockMappedWriteGuard<'_, T>"],["impl<T> Deref for Ref<'_, T>"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/ops/drop/trait.Drop.js b/docs/implementors/core/ops/drop/trait.Drop.js deleted file mode 100644 index 8f3097fe6835..000000000000 --- a/docs/implementors/core/ops/drop/trait.Drop.js +++ /dev/null @@ -1,24 +0,0 @@ -(function() {var implementors = { -"aiofut":[["impl Drop for AIO"],["impl Drop for AIOFuture"],["impl Drop for AIOManager"]], -"bytes":[["impl Drop for Bytes"],["impl Drop for BytesMut"]], -"crossbeam_channel":[["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl Drop for SelectedOperation<'_>"]], -"crossbeam_utils":[["impl<T> Drop for AtomicCell<T>"],["impl<T: ?Sized> Drop for ShardedLockWriteGuard<'_, T>"],["impl Drop for WaitGroup"]], -"firewood":[["impl<'a> Drop for WriteBatch<'a>"]], -"futures_channel":[["impl<T> Drop for Receiver<T>"],["impl<T> Drop for UnboundedReceiver<T>"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"]], -"futures_executor":[["impl Drop for Enter"]], -"futures_task":[["impl<T> Drop for LocalFutureObj<'_, T>"]], -"futures_util":[["impl<Fut> Drop for Shared<Fut>where
        Fut: Future,
    "],["impl<Fut> Drop for FuturesUnordered<Fut>"],["impl<T: ?Sized> Drop for OwnedMutexLockFuture<T>"],["impl<T: ?Sized> Drop for OwnedMutexGuard<T>"],["impl<T: ?Sized> Drop for MutexLockFuture<'_, T>"],["impl<T: ?Sized> Drop for MutexGuard<'_, T>"],["impl<T: ?Sized, U: ?Sized> Drop for MappedMutexGuard<'_, T, U>"]], -"generic_array":[["impl<T, N> Drop for GenericArrayIter<T, N>where
        N: ArrayLength<T>,
    "]], -"growthring":[["impl Drop for WALFileAIO"],["impl Drop for WALStoreAIO"]], -"hashbrown":[["impl<'a, K, V, F, A> Drop for DrainFilter<'a, K, V, F, A>where
        F: FnMut(&K, &mut V) -> bool,
        A: Allocator + Clone,
    "],["impl<'a, K, F, A: Allocator + Clone> Drop for DrainFilter<'a, K, F, A>where
        F: FnMut(&K) -> bool,
    "]], -"lock_api":[["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Drop for MutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, T: ?Sized + 'a> Drop for MappedMutexGuard<'a, R, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Drop for ReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawMutex + 'a, G: GetThreadId + 'a, T: ?Sized + 'a> Drop for MappedReentrantMutexGuard<'a, R, G, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for RwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for RwLockWriteGuard<'a, R, T>"],["impl<'a, R: RawRwLockUpgrade + 'a, T: ?Sized + 'a> Drop for RwLockUpgradableReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for MappedRwLockReadGuard<'a, R, T>"],["impl<'a, R: RawRwLock + 'a, T: ?Sized + 'a> Drop for MappedRwLockWriteGuard<'a, R, T>"]], -"lru":[["impl<K, V, S> Drop for LruCache<K, V, S>"]], -"nix":[["impl Drop for Dir"],["impl<'d> Drop for Iter<'d>"],["impl Drop for InterfaceAddressIterator"],["impl Drop for Interfaces"],["impl Drop for PtyMaster"],["impl Drop for SignalFd"],["impl Drop for TimerFd"],["impl Drop for Timer"]], -"once_cell":[["impl<T> Drop for OnceBox<T>"]], -"regex_syntax":[["impl Drop for Ast"],["impl Drop for ClassSet"],["impl Drop for Hir"]], -"scopeguard":[["impl<T, F, S> Drop for ScopeGuard<T, F, S>where
        F: FnOnce(T),
        S: Strategy,
    "]], -"shale":[["impl<T: ?Sized> Drop for Obj<T>"],["impl<'a, T> Drop for ObjRef<'a, T>"]], -"smallvec":[["impl<'a, T: 'a + Array> Drop for Drain<'a, T>"],["impl<A: Array> Drop for SmallVec<A>"],["impl<A: Array> Drop for IntoIter<A>"]], -"syn":[["impl<'a> Drop for ParseBuffer<'a>"]], -"tokio":[["impl Drop for AbortHandle"],["impl<T> Drop for JoinHandle<T>"],["impl Drop for Runtime"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl<T> Drop for Permit<'_, T>"],["impl<T> Drop for OwnedPermit<T>"],["impl<T: ?Sized> Drop for MutexGuard<'_, T>"],["impl<T: ?Sized> Drop for OwnedMutexGuard<T>"],["impl<'a, T: ?Sized> Drop for MappedMutexGuard<'a, T>"],["impl Drop for Notified<'_>"],["impl<T> Drop for Sender<T>"],["impl<T> Drop for Receiver<T>"],["impl Drop for SemaphorePermit<'_>"],["impl Drop for OwnedSemaphorePermit"],["impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockReadGuard<T, U>"],["impl<T: ?Sized> Drop for OwnedRwLockWriteGuard<T>"],["impl<T: ?Sized, U: ?Sized> Drop for OwnedRwLockMappedWriteGuard<T, U>"],["impl<'a, T: ?Sized> Drop for RwLockReadGuard<'a, T>"],["impl<'a, T: ?Sized> Drop for RwLockWriteGuard<'a, T>"],["impl<'a, T: ?Sized> Drop for RwLockMappedWriteGuard<'a, T>"],["impl<T> Drop for OnceCell<T>"],["impl<T> Drop for Receiver<T>"],["impl<T> Drop for Sender<T>"],["impl Drop for LocalEnterGuard"],["impl Drop for LocalSet"],["impl<T: 'static, F> Drop for TaskLocalFuture<T, F>"],["impl<T> Drop for JoinSet<T>"]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js b/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js deleted file mode 100644 index c9d2d9a6b51b..000000000000 --- a/docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl RefUnwindSafe for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl RefUnwindSafe for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> RefUnwindSafe for AhoCorasick<S>where
        S: RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> RefUnwindSafe for FindIter<'a, 'b, S>where
        S: RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> RefUnwindSafe for FindOverlappingIter<'a, 'b, S>where
        S: RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> RefUnwindSafe for StreamFindIter<'a, R, S>where
        R: RefUnwindSafe,
        S: RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl RefUnwindSafe for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl RefUnwindSafe for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl RefUnwindSafe for Error",1,["aho_corasick::error::Error"]],["impl RefUnwindSafe for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl RefUnwindSafe for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl RefUnwindSafe for Config",1,["aho_corasick::packed::api::Config"]],["impl RefUnwindSafe for Builder",1,["aho_corasick::packed::api::Builder"]],["impl RefUnwindSafe for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> RefUnwindSafe for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl RefUnwindSafe for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl RefUnwindSafe for Error",1,["aiofut::Error"]],["impl RefUnwindSafe for AIO",1,["aiofut::AIO"]],["impl !RefUnwindSafe for AIOFuture",1,["aiofut::AIOFuture"]],["impl !RefUnwindSafe for AIONotifier",1,["aiofut::AIONotifier"]],["impl RefUnwindSafe for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !RefUnwindSafe for AIOManager",1,["aiofut::AIOManager"]],["impl !RefUnwindSafe for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl RefUnwindSafe for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl RefUnwindSafe for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl RefUnwindSafe for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl RefUnwindSafe for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl RefUnwindSafe for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl RefUnwindSafe for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl RefUnwindSafe for Config",1,["bincode::config::legacy::Config"]],["impl RefUnwindSafe for Bounded",1,["bincode::config::limit::Bounded"]],["impl RefUnwindSafe for Infinite",1,["bincode::config::limit::Infinite"]],["impl RefUnwindSafe for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl RefUnwindSafe for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl RefUnwindSafe for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> RefUnwindSafe for WithOtherLimit<O, L>where
        L: RefUnwindSafe,
        O: RefUnwindSafe,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> RefUnwindSafe for WithOtherEndian<O, E>where
        E: RefUnwindSafe,
        O: RefUnwindSafe,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> RefUnwindSafe for WithOtherIntEncoding<O, I>where
        I: RefUnwindSafe,
        O: RefUnwindSafe,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> RefUnwindSafe for WithOtherTrailing<O, T>where
        O: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> RefUnwindSafe for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> RefUnwindSafe for IoReader<R>where
        R: RefUnwindSafe,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> RefUnwindSafe for Deserializer<R, O>where
        O: RefUnwindSafe,
        R: RefUnwindSafe,
    ",1,["bincode::de::Deserializer"]],["impl !RefUnwindSafe for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> RefUnwindSafe for Serializer<W, O>where
        O: RefUnwindSafe,
        W: RefUnwindSafe,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl RefUnwindSafe for Eager",1,["block_buffer::Eager"]],["impl RefUnwindSafe for Lazy",1,["block_buffer::Lazy"]],["impl RefUnwindSafe for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> RefUnwindSafe for BlockBuffer<BlockSize, Kind>where
        Kind: RefUnwindSafe,
        <BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl RefUnwindSafe for BigEndian",1,["byteorder::BigEndian"]],["impl RefUnwindSafe for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> RefUnwindSafe for Chain<T, U>where
        T: RefUnwindSafe,
        U: RefUnwindSafe,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> RefUnwindSafe for IntoIter<T>where
        T: RefUnwindSafe,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> RefUnwindSafe for Limit<T>where
        T: RefUnwindSafe,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> RefUnwindSafe for Reader<B>where
        B: RefUnwindSafe,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> RefUnwindSafe for Take<T>where
        T: RefUnwindSafe,
    ",1,["bytes::buf::take::Take"]],["impl RefUnwindSafe for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> RefUnwindSafe for Writer<B>where
        B: RefUnwindSafe,
    ",1,["bytes::buf::writer::Writer"]],["impl RefUnwindSafe for Bytes",1,["bytes::bytes::Bytes"]],["impl RefUnwindSafe for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], -"crc":[["impl<W> RefUnwindSafe for Crc<W>where
        W: RefUnwindSafe,
    ",1,["crc::Crc"]],["impl<'a, W> RefUnwindSafe for Digest<'a, W>where
        W: RefUnwindSafe,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> RefUnwindSafe for Algorithm<W>where
        W: RefUnwindSafe,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<'a, T> RefUnwindSafe for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> RefUnwindSafe for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> RefUnwindSafe for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> RefUnwindSafe for SendError<T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> RefUnwindSafe for SendTimeoutError<T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl RefUnwindSafe for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl RefUnwindSafe for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl RefUnwindSafe for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl RefUnwindSafe for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl RefUnwindSafe for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl RefUnwindSafe for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !RefUnwindSafe for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> RefUnwindSafe for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T> RefUnwindSafe for Sender<T>"],["impl<T> RefUnwindSafe for Receiver<T>"]], -"crossbeam_utils":[["impl<T> RefUnwindSafe for CachePadded<T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl !RefUnwindSafe for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl RefUnwindSafe for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl RefUnwindSafe for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<'a, T: ?Sized> RefUnwindSafe for ShardedLockReadGuard<'a, T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> RefUnwindSafe for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl RefUnwindSafe for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> RefUnwindSafe for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> RefUnwindSafe for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> RefUnwindSafe for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]],["impl<T> RefUnwindSafe for AtomicCell<T>"],["impl<T: ?Sized> RefUnwindSafe for ShardedLock<T>"]], -"crypto_common":[["impl RefUnwindSafe for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> RefUnwindSafe for CtVariableCoreWrapper<T, OutSize, O>where
        O: RefUnwindSafe,
        OutSize: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> RefUnwindSafe for RtVariableCoreWrapper<T>where
        T: RefUnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
        <T as BufferKindUser>::BufferKind: RefUnwindSafe,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> RefUnwindSafe for CoreWrapper<T>where
        T: RefUnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
        <T as BufferKindUser>::BufferKind: RefUnwindSafe,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> RefUnwindSafe for XofReaderCoreWrapper<T>where
        T: RefUnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: RefUnwindSafe,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl RefUnwindSafe for TruncSide",1,["digest::core_api::TruncSide"]],["impl RefUnwindSafe for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl RefUnwindSafe for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl RefUnwindSafe for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl RefUnwindSafe for WALConfig",1,["firewood::storage::WALConfig"]],["impl !RefUnwindSafe for DBError",1,["firewood::db::DBError"]],["impl RefUnwindSafe for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl RefUnwindSafe for DBConfig",1,["firewood::db::DBConfig"]],["impl !RefUnwindSafe for DBRev",1,["firewood::db::DBRev"]],["impl !RefUnwindSafe for DB",1,["firewood::db::DB"]],["impl<'a> !RefUnwindSafe for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !RefUnwindSafe for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl !RefUnwindSafe for MerkleError",1,["firewood::merkle::MerkleError"]],["impl RefUnwindSafe for Hash",1,["firewood::merkle::Hash"]],["impl RefUnwindSafe for PartialPath",1,["firewood::merkle::PartialPath"]],["impl !RefUnwindSafe for Node",1,["firewood::merkle::Node"]],["impl !RefUnwindSafe for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !RefUnwindSafe for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !RefUnwindSafe for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl RefUnwindSafe for IdTrans",1,["firewood::merkle::IdTrans"]],["impl RefUnwindSafe for Proof",1,["firewood::proof::Proof"]],["impl RefUnwindSafe for ProofError",1,["firewood::proof::ProofError"]],["impl RefUnwindSafe for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> !RefUnwindSafe for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> !RefUnwindSafe for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> !RefUnwindSafe for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl RefUnwindSafe for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
        T: RefUnwindSafe,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl RefUnwindSafe for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> !RefUnwindSafe for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl RefUnwindSafe for Canceled",1,["futures_channel::oneshot::Canceled"]]], -"futures_executor":[["impl !RefUnwindSafe for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !RefUnwindSafe for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> RefUnwindSafe for BlockingStream<S>where
        S: RefUnwindSafe,
    ",1,["futures_executor::local_pool::BlockingStream"]],["impl RefUnwindSafe for Enter",1,["futures_executor::enter::Enter"]],["impl RefUnwindSafe for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl RefUnwindSafe for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> RefUnwindSafe for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !RefUnwindSafe for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !RefUnwindSafe for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], -"futures_util":[["impl<Fut> RefUnwindSafe for Fuse<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> RefUnwindSafe for CatchUnwind<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> !RefUnwindSafe for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> !RefUnwindSafe for Remote<Fut>",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> !RefUnwindSafe for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> !RefUnwindSafe for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> RefUnwindSafe for Flatten<F>where
        F: RefUnwindSafe,
        <F as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::future::Flatten"]],["impl<F> RefUnwindSafe for FlattenStream<F>where
        F: RefUnwindSafe,
        <F as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> RefUnwindSafe for Map<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::Map"]],["impl<F> RefUnwindSafe for IntoStream<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> RefUnwindSafe for MapInto<Fut, T>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> RefUnwindSafe for Then<Fut1, Fut2, F>where
        F: RefUnwindSafe,
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
    ",1,["futures_util::future::future::Then"]],["impl<Fut, F> RefUnwindSafe for Inspect<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::Inspect"]],["impl<Fut> RefUnwindSafe for NeverError<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::NeverError"]],["impl<Fut> RefUnwindSafe for UnitError<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::future::UnitError"]],["impl<Fut> RefUnwindSafe for IntoFuture<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> RefUnwindSafe for TryFlatten<Fut1, Fut2>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
    ",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> RefUnwindSafe for TryFlattenStream<Fut>where
        Fut: RefUnwindSafe,
        <Fut as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> RefUnwindSafe for FlattenSink<Fut, Si>where
        Fut: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> RefUnwindSafe for AndThen<Fut1, Fut2, F>where
        F: RefUnwindSafe,
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
    ",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> RefUnwindSafe for OrElse<Fut1, Fut2, F>where
        F: RefUnwindSafe,
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
    ",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> RefUnwindSafe for ErrInto<Fut, E>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> RefUnwindSafe for OkInto<Fut, E>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> RefUnwindSafe for InspectOk<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> RefUnwindSafe for InspectErr<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> RefUnwindSafe for MapOk<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> RefUnwindSafe for MapErr<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> RefUnwindSafe for MapOkOrElse<Fut, F, G>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        G: RefUnwindSafe,
    ",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> RefUnwindSafe for UnwrapOrElse<Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> RefUnwindSafe for Lazy<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::future::lazy::Lazy"]],["impl<T> RefUnwindSafe for Pending<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::future::pending::Pending"]],["impl<Fut> RefUnwindSafe for MaybeDone<Fut>where
        Fut: RefUnwindSafe,
        <Fut as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> RefUnwindSafe for TryMaybeDone<Fut>where
        Fut: RefUnwindSafe,
        <Fut as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> RefUnwindSafe for OptionFuture<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::future::option::OptionFuture"]],["impl<F> RefUnwindSafe for PollFn<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> RefUnwindSafe for PollImmediate<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> RefUnwindSafe for Ready<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> RefUnwindSafe for Join<Fut1, Fut2>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        <Fut1 as Future>::Output: RefUnwindSafe,
        <Fut2 as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> RefUnwindSafe for Join3<Fut1, Fut2, Fut3>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        <Fut1 as Future>::Output: RefUnwindSafe,
        <Fut2 as Future>::Output: RefUnwindSafe,
        <Fut3 as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> RefUnwindSafe for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        Fut4: RefUnwindSafe,
        <Fut1 as Future>::Output: RefUnwindSafe,
        <Fut2 as Future>::Output: RefUnwindSafe,
        <Fut3 as Future>::Output: RefUnwindSafe,
        <Fut4 as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> RefUnwindSafe for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        Fut4: RefUnwindSafe,
        Fut5: RefUnwindSafe,
        <Fut1 as Future>::Output: RefUnwindSafe,
        <Fut2 as Future>::Output: RefUnwindSafe,
        <Fut3 as Future>::Output: RefUnwindSafe,
        <Fut4 as Future>::Output: RefUnwindSafe,
        <Fut5 as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::future::join::Join5"]],["impl<F> !RefUnwindSafe for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> RefUnwindSafe for Select<A, B>where
        A: RefUnwindSafe,
        B: RefUnwindSafe,
    ",1,["futures_util::future::select::Select"]],["impl<Fut> RefUnwindSafe for SelectAll<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> RefUnwindSafe for TryJoin<Fut1, Fut2>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        <Fut1 as TryFuture>::Ok: RefUnwindSafe,
        <Fut2 as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> RefUnwindSafe for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        <Fut1 as TryFuture>::Ok: RefUnwindSafe,
        <Fut2 as TryFuture>::Ok: RefUnwindSafe,
        <Fut3 as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> RefUnwindSafe for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        Fut4: RefUnwindSafe,
        <Fut1 as TryFuture>::Ok: RefUnwindSafe,
        <Fut2 as TryFuture>::Ok: RefUnwindSafe,
        <Fut3 as TryFuture>::Ok: RefUnwindSafe,
        <Fut4 as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> RefUnwindSafe for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: RefUnwindSafe,
        Fut2: RefUnwindSafe,
        Fut3: RefUnwindSafe,
        Fut4: RefUnwindSafe,
        Fut5: RefUnwindSafe,
        <Fut1 as TryFuture>::Ok: RefUnwindSafe,
        <Fut2 as TryFuture>::Ok: RefUnwindSafe,
        <Fut3 as TryFuture>::Ok: RefUnwindSafe,
        <Fut4 as TryFuture>::Ok: RefUnwindSafe,
        <Fut5 as TryFuture>::Ok: RefUnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !RefUnwindSafe for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> RefUnwindSafe for TrySelect<A, B>where
        A: RefUnwindSafe,
        B: RefUnwindSafe,
    ",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> RefUnwindSafe for SelectOk<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> RefUnwindSafe for Either<A, B>where
        A: RefUnwindSafe,
        B: RefUnwindSafe,
    ",1,["futures_util::future::either::Either"]],["impl !RefUnwindSafe for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl !RefUnwindSafe for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> !RefUnwindSafe for Abortable<T>",1,["futures_util::abortable::Abortable"]],["impl RefUnwindSafe for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> RefUnwindSafe for Chain<St1, St2>where
        St1: RefUnwindSafe,
        St2: RefUnwindSafe,
    ",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> RefUnwindSafe for Collect<St, C>where
        C: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> RefUnwindSafe for Unzip<St, FromA, FromB>where
        FromA: RefUnwindSafe,
        FromB: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> RefUnwindSafe for Concat<St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> RefUnwindSafe for Cycle<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> RefUnwindSafe for Enumerate<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> RefUnwindSafe for Filter<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> RefUnwindSafe for FilterMap<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> RefUnwindSafe for Fold<St, Fut, T, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> RefUnwindSafe for ForEach<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> RefUnwindSafe for Fuse<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> RefUnwindSafe for StreamFuture<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> RefUnwindSafe for Map<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St: ?Sized> RefUnwindSafe for Next<'a, St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St: ?Sized> RefUnwindSafe for SelectNextSome<'a, St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> RefUnwindSafe for Peekable<St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> RefUnwindSafe for Peek<'a, St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> RefUnwindSafe for PeekMut<'a, St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> RefUnwindSafe for NextIf<'a, St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T: ?Sized> RefUnwindSafe for NextIfEq<'a, St, T>where
        St: RefUnwindSafe,
        T: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> RefUnwindSafe for Skip<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> RefUnwindSafe for SkipWhile<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> RefUnwindSafe for Take<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> RefUnwindSafe for TakeWhile<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> RefUnwindSafe for TakeUntil<St, Fut>where
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <Fut as Future>::Output: RefUnwindSafe,
    ",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> RefUnwindSafe for Then<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> RefUnwindSafe for Zip<St1, St2>where
        St1: RefUnwindSafe,
        St2: RefUnwindSafe,
        <St1 as Stream>::Item: RefUnwindSafe,
        <St2 as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> RefUnwindSafe for Chunks<St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> RefUnwindSafe for ReadyChunks<St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> RefUnwindSafe for Scan<St, S, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        S: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !RefUnwindSafe for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !RefUnwindSafe for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !RefUnwindSafe for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> !RefUnwindSafe for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> !RefUnwindSafe for SplitSink<S, Item>",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> !RefUnwindSafe for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> RefUnwindSafe for CatchUnwind<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> RefUnwindSafe for Flatten<St>where
        St: RefUnwindSafe,
        <St as Stream>::Item: RefUnwindSafe,
    ",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> RefUnwindSafe for Forward<St, Si>where
        Si: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::stream::Forward"]],["impl<St, F> RefUnwindSafe for Inspect<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> RefUnwindSafe for FlatMap<St, U, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
        U: RefUnwindSafe,
    ",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> RefUnwindSafe for AndThen<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> RefUnwindSafe for IntoStream<St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> RefUnwindSafe for OrElse<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St: ?Sized> RefUnwindSafe for TryNext<'a, St>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> RefUnwindSafe for TryForEach<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> RefUnwindSafe for TryFilter<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> RefUnwindSafe for TryFilterMap<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> RefUnwindSafe for TryFlatten<St>where
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> RefUnwindSafe for TryCollect<St, C>where
        C: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> RefUnwindSafe for TryConcat<St>where
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> RefUnwindSafe for TryChunks<St>where
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> RefUnwindSafe for TryChunksError<T, E>where
        E: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> RefUnwindSafe for TryFold<St, Fut, T, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> RefUnwindSafe for TryUnfold<T, F, Fut>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> RefUnwindSafe for TrySkipWhile<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> RefUnwindSafe for TryTakeWhile<St, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !RefUnwindSafe for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !RefUnwindSafe for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !RefUnwindSafe for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> RefUnwindSafe for IntoAsyncRead<St>where
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> RefUnwindSafe for ErrInto<St, E>where
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> RefUnwindSafe for InspectOk<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> RefUnwindSafe for InspectErr<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> RefUnwindSafe for MapOk<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> RefUnwindSafe for MapErr<St, F>where
        F: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> RefUnwindSafe for Iter<I>where
        I: RefUnwindSafe,
    ",1,["futures_util::stream::iter::Iter"]],["impl<T> RefUnwindSafe for Repeat<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::stream::repeat::Repeat"]],["impl<F> RefUnwindSafe for RepeatWith<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> RefUnwindSafe for Empty<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::stream::empty::Empty"]],["impl<Fut> RefUnwindSafe for Once<Fut>where
        Fut: RefUnwindSafe,
    ",1,["futures_util::stream::once::Once"]],["impl<T> RefUnwindSafe for Pending<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::stream::pending::Pending"]],["impl<F> RefUnwindSafe for PollFn<F>where
        F: RefUnwindSafe,
    ",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> RefUnwindSafe for PollImmediate<S>where
        S: RefUnwindSafe,
    ",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> RefUnwindSafe for Select<St1, St2>where
        St1: RefUnwindSafe,
        St2: RefUnwindSafe,
    ",1,["futures_util::stream::select::Select"]],["impl RefUnwindSafe for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> RefUnwindSafe for SelectWithStrategy<St1, St2, Clos, State>where
        Clos: RefUnwindSafe,
        St1: RefUnwindSafe,
        St2: RefUnwindSafe,
        State: RefUnwindSafe,
    ",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> RefUnwindSafe for Unfold<T, F, Fut>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !RefUnwindSafe for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> !RefUnwindSafe for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> !RefUnwindSafe for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> !RefUnwindSafe for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> !RefUnwindSafe for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !RefUnwindSafe for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !RefUnwindSafe for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !RefUnwindSafe for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> !RefUnwindSafe for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> !RefUnwindSafe for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !RefUnwindSafe for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Close<'a, Si, Item>where
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::close::Close"]],["impl<T> RefUnwindSafe for Drain<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> RefUnwindSafe for Fanout<Si1, Si2>where
        Si1: RefUnwindSafe,
        Si2: RefUnwindSafe,
    ",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Feed<'a, Si, Item>where
        Item: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Flush<'a, Si, Item>where
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> RefUnwindSafe for SinkErrInto<Si, Item, E>where
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> RefUnwindSafe for SinkMapErr<Si, F>where
        F: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si: ?Sized, Item> RefUnwindSafe for Send<'a, Si, Item>where
        Item: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::send::Send"]],["impl<'a, Si: ?Sized, St: ?Sized> RefUnwindSafe for SendAll<'a, Si, St>where
        Si: RefUnwindSafe,
        St: RefUnwindSafe,
        <St as TryStream>::Ok: RefUnwindSafe,
    ",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> RefUnwindSafe for Unfold<T, F, R>where
        F: RefUnwindSafe,
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> RefUnwindSafe for With<Si, Item, U, Fut, F>where
        F: RefUnwindSafe,
        Fut: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> RefUnwindSafe for WithFlatMap<Si, Item, U, St, F>where
        F: RefUnwindSafe,
        Item: RefUnwindSafe,
        Si: RefUnwindSafe,
        St: RefUnwindSafe,
    ",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> RefUnwindSafe for Buffer<Si, Item>where
        Item: RefUnwindSafe,
        Si: RefUnwindSafe,
    ",1,["futures_util::sink::buffer::Buffer"]],["impl<T> RefUnwindSafe for AllowStdIo<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> RefUnwindSafe for BufReader<R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> RefUnwindSafe for SeeKRelative<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> RefUnwindSafe for BufWriter<W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> RefUnwindSafe for LineWriter<W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> RefUnwindSafe for Chain<T, U>where
        T: RefUnwindSafe,
        U: RefUnwindSafe,
    ",1,["futures_util::io::chain::Chain"]],["impl<'a, W: ?Sized> RefUnwindSafe for Close<'a, W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::close::Close"]],["impl<'a, R, W: ?Sized> RefUnwindSafe for Copy<'a, R, W>where
        R: RefUnwindSafe,
        W: RefUnwindSafe,
    ",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W: ?Sized> RefUnwindSafe for CopyBuf<'a, R, W>where
        R: RefUnwindSafe,
        W: RefUnwindSafe,
    ",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W> !RefUnwindSafe for CopyBufAbortable<'a, R, W>",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> RefUnwindSafe for Cursor<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::io::cursor::Cursor"]],["impl RefUnwindSafe for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R: ?Sized> RefUnwindSafe for FillBuf<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W: ?Sized> RefUnwindSafe for Flush<'a, W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::flush::Flush"]],["impl<W, Item> RefUnwindSafe for IntoSink<W, Item>where
        Item: RefUnwindSafe,
        W: RefUnwindSafe,
    ",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> RefUnwindSafe for Lines<R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::lines::Lines"]],["impl<'a, R: ?Sized> RefUnwindSafe for Read<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read::Read"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadVectored<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadExact<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadLine<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadToEnd<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadToString<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReadUntil<'a, R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::read_until::ReadUntil"]],["impl RefUnwindSafe for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S: ?Sized> RefUnwindSafe for Seek<'a, S>where
        S: RefUnwindSafe,
    ",1,["futures_util::io::seek::Seek"]],["impl RefUnwindSafe for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> !RefUnwindSafe for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> !RefUnwindSafe for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> !RefUnwindSafe for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> RefUnwindSafe for Take<R>where
        R: RefUnwindSafe,
    ",1,["futures_util::io::take::Take"]],["impl<T> RefUnwindSafe for Window<T>where
        T: RefUnwindSafe,
    ",1,["futures_util::io::window::Window"]],["impl<'a, W: ?Sized> RefUnwindSafe for Write<'a, W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::write::Write"]],["impl<'a, W: ?Sized> RefUnwindSafe for WriteVectored<'a, W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W: ?Sized> RefUnwindSafe for WriteAll<'a, W>where
        W: RefUnwindSafe,
    ",1,["futures_util::io::write_all::WriteAll"]],["impl<T> !RefUnwindSafe for Mutex<T>",1,["futures_util::lock::mutex::Mutex"]],["impl<T> !RefUnwindSafe for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T> !RefUnwindSafe for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T> !RefUnwindSafe for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T> !RefUnwindSafe for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T, U> !RefUnwindSafe for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], -"generic_array":[["impl<T, N> RefUnwindSafe for GenericArrayIter<T, N>where
        <N as ArrayLength<T>>::ArrayType: RefUnwindSafe,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> RefUnwindSafe for GenericArray<T, U>where
        <U as ArrayLength<T>>::ArrayType: RefUnwindSafe,
    ",1,["generic_array::GenericArray"]]], -"getrandom":[["impl RefUnwindSafe for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl RefUnwindSafe for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !RefUnwindSafe for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl RefUnwindSafe for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl RefUnwindSafe for WALLoader",1,["growthring::wal::WALLoader"]],["impl !RefUnwindSafe for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !RefUnwindSafe for WALStoreAIO",1,["growthring::WALStoreAIO"]]], -"hashbrown":[["impl<K, V, S, A> RefUnwindSafe for HashMap<K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> RefUnwindSafe for Iter<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Iter"]],["impl<'a, K, V> RefUnwindSafe for IterMut<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::IterMut"]],["impl<K, V, A> RefUnwindSafe for IntoIter<K, V, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> RefUnwindSafe for IntoKeys<K, V, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> RefUnwindSafe for IntoValues<K, V, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> RefUnwindSafe for Keys<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Keys"]],["impl<'a, K, V> RefUnwindSafe for Values<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> RefUnwindSafe for Drain<'a, K, V, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A> RefUnwindSafe for DrainFilter<'a, K, V, F, A>where
        A: RefUnwindSafe,
        F: RefUnwindSafe,
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> RefUnwindSafe for ValuesMut<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryBuilderMut<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryMut<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawOccupiedEntryMut<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawVacantEntryMut<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> RefUnwindSafe for RawEntryBuilder<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A> RefUnwindSafe for Entry<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A> RefUnwindSafe for OccupiedEntry<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A> RefUnwindSafe for VacantEntry<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for EntryRef<'a, 'b, K, Q, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        Q: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        Q: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q: ?Sized, V, S, A> RefUnwindSafe for VacantEntryRef<'a, 'b, K, Q, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        Q: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A> RefUnwindSafe for OccupiedError<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> RefUnwindSafe for HashSet<T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> RefUnwindSafe for Iter<'a, K>where
        K: RefUnwindSafe,
    ",1,["hashbrown::set::Iter"]],["impl<K, A> RefUnwindSafe for IntoIter<K, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> RefUnwindSafe for Drain<'a, K, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A> RefUnwindSafe for DrainFilter<'a, K, F, A>where
        A: RefUnwindSafe,
        F: RefUnwindSafe,
        K: RefUnwindSafe,
    ",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> RefUnwindSafe for Intersection<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> RefUnwindSafe for Difference<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> RefUnwindSafe for SymmetricDifference<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> RefUnwindSafe for Union<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Union"]],["impl<'a, T, S, A> RefUnwindSafe for Entry<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A> RefUnwindSafe for OccupiedEntry<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A> RefUnwindSafe for VacantEntry<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::VacantEntry"]],["impl RefUnwindSafe for TryReserveError",1,["hashbrown::TryReserveError"]]], -"heck":[["impl<T> RefUnwindSafe for AsKebabCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> RefUnwindSafe for AsLowerCamelCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> RefUnwindSafe for AsShoutyKebabCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> RefUnwindSafe for AsShoutySnakeCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> RefUnwindSafe for AsSnakeCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> RefUnwindSafe for AsTitleCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::title::AsTitleCase"]],["impl<T> RefUnwindSafe for AsUpperCamelCase<T>where
        T: RefUnwindSafe,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl RefUnwindSafe for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl RefUnwindSafe for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl RefUnwindSafe for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl RefUnwindSafe for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl RefUnwindSafe for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl RefUnwindSafe for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl RefUnwindSafe for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl RefUnwindSafe for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl RefUnwindSafe for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl RefUnwindSafe for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl RefUnwindSafe for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl RefUnwindSafe for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl RefUnwindSafe for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl RefUnwindSafe for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl RefUnwindSafe for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl RefUnwindSafe for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl RefUnwindSafe for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl RefUnwindSafe for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl RefUnwindSafe for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl RefUnwindSafe for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl RefUnwindSafe for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl RefUnwindSafe for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl RefUnwindSafe for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl RefUnwindSafe for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl RefUnwindSafe for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl RefUnwindSafe for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl RefUnwindSafe for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl RefUnwindSafe for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl RefUnwindSafe for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl RefUnwindSafe for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl RefUnwindSafe for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl RefUnwindSafe for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl RefUnwindSafe for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl RefUnwindSafe for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl RefUnwindSafe for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl RefUnwindSafe for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl RefUnwindSafe for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl RefUnwindSafe for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl RefUnwindSafe for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl RefUnwindSafe for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl RefUnwindSafe for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl RefUnwindSafe for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl RefUnwindSafe for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl RefUnwindSafe for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl RefUnwindSafe for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl RefUnwindSafe for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl RefUnwindSafe for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl RefUnwindSafe for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl RefUnwindSafe for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl RefUnwindSafe for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl RefUnwindSafe for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl RefUnwindSafe for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl RefUnwindSafe for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl RefUnwindSafe for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl RefUnwindSafe for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl RefUnwindSafe for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl RefUnwindSafe for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl RefUnwindSafe for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl RefUnwindSafe for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl RefUnwindSafe for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl RefUnwindSafe for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl RefUnwindSafe for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl RefUnwindSafe for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl RefUnwindSafe for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl RefUnwindSafe for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl RefUnwindSafe for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl RefUnwindSafe for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl RefUnwindSafe for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl RefUnwindSafe for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl RefUnwindSafe for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl RefUnwindSafe for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl RefUnwindSafe for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl RefUnwindSafe for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl RefUnwindSafe for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl RefUnwindSafe for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl RefUnwindSafe for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl RefUnwindSafe for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl RefUnwindSafe for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl RefUnwindSafe for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl RefUnwindSafe for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl RefUnwindSafe for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl RefUnwindSafe for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl RefUnwindSafe for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl RefUnwindSafe for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl RefUnwindSafe for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl RefUnwindSafe for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl RefUnwindSafe for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl RefUnwindSafe for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl RefUnwindSafe for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl RefUnwindSafe for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl RefUnwindSafe for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl RefUnwindSafe for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl RefUnwindSafe for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl RefUnwindSafe for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl RefUnwindSafe for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl RefUnwindSafe for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl RefUnwindSafe for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl RefUnwindSafe for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl RefUnwindSafe for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl RefUnwindSafe for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl RefUnwindSafe for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl RefUnwindSafe for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl RefUnwindSafe for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl RefUnwindSafe for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl RefUnwindSafe for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl RefUnwindSafe for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl RefUnwindSafe for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl RefUnwindSafe for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl RefUnwindSafe for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl RefUnwindSafe for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl RefUnwindSafe for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl RefUnwindSafe for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl RefUnwindSafe for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl RefUnwindSafe for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl RefUnwindSafe for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl RefUnwindSafe for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl RefUnwindSafe for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl RefUnwindSafe for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl RefUnwindSafe for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl RefUnwindSafe for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl RefUnwindSafe for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl RefUnwindSafe for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl RefUnwindSafe for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl RefUnwindSafe for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl RefUnwindSafe for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl RefUnwindSafe for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl RefUnwindSafe for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl RefUnwindSafe for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl RefUnwindSafe for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl RefUnwindSafe for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl RefUnwindSafe for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl RefUnwindSafe for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl RefUnwindSafe for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl RefUnwindSafe for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl RefUnwindSafe for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl RefUnwindSafe for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl RefUnwindSafe for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl RefUnwindSafe for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl RefUnwindSafe for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl RefUnwindSafe for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl RefUnwindSafe for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl RefUnwindSafe for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl RefUnwindSafe for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl RefUnwindSafe for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl RefUnwindSafe for timezone",1,["libc::unix::linux_like::timezone"]],["impl RefUnwindSafe for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl RefUnwindSafe for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl RefUnwindSafe for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl RefUnwindSafe for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl RefUnwindSafe for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl RefUnwindSafe for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl RefUnwindSafe for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl RefUnwindSafe for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl RefUnwindSafe for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl RefUnwindSafe for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl RefUnwindSafe for tm",1,["libc::unix::linux_like::tm"]],["impl RefUnwindSafe for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl RefUnwindSafe for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl RefUnwindSafe for lconv",1,["libc::unix::linux_like::lconv"]],["impl RefUnwindSafe for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl RefUnwindSafe for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl RefUnwindSafe for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl RefUnwindSafe for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl RefUnwindSafe for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl RefUnwindSafe for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl RefUnwindSafe for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl RefUnwindSafe for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl RefUnwindSafe for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl RefUnwindSafe for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl RefUnwindSafe for utsname",1,["libc::unix::linux_like::utsname"]],["impl RefUnwindSafe for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl RefUnwindSafe for in6_addr",1,["libc::unix::align::in6_addr"]],["impl RefUnwindSafe for DIR",1,["libc::unix::DIR"]],["impl RefUnwindSafe for group",1,["libc::unix::group"]],["impl RefUnwindSafe for utimbuf",1,["libc::unix::utimbuf"]],["impl RefUnwindSafe for timeval",1,["libc::unix::timeval"]],["impl RefUnwindSafe for timespec",1,["libc::unix::timespec"]],["impl RefUnwindSafe for rlimit",1,["libc::unix::rlimit"]],["impl RefUnwindSafe for rusage",1,["libc::unix::rusage"]],["impl RefUnwindSafe for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl RefUnwindSafe for hostent",1,["libc::unix::hostent"]],["impl RefUnwindSafe for iovec",1,["libc::unix::iovec"]],["impl RefUnwindSafe for pollfd",1,["libc::unix::pollfd"]],["impl RefUnwindSafe for winsize",1,["libc::unix::winsize"]],["impl RefUnwindSafe for linger",1,["libc::unix::linger"]],["impl RefUnwindSafe for sigval",1,["libc::unix::sigval"]],["impl RefUnwindSafe for itimerval",1,["libc::unix::itimerval"]],["impl RefUnwindSafe for tms",1,["libc::unix::tms"]],["impl RefUnwindSafe for servent",1,["libc::unix::servent"]],["impl RefUnwindSafe for protoent",1,["libc::unix::protoent"]],["impl RefUnwindSafe for FILE",1,["libc::unix::FILE"]],["impl RefUnwindSafe for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<R, T> !RefUnwindSafe for Mutex<R, T>",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T> !RefUnwindSafe for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedMutexGuard<'a, R, T>where
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> !RefUnwindSafe for RawReentrantMutex<R, G>",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T> !RefUnwindSafe for ReentrantMutex<R, G, T>",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T> !RefUnwindSafe for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !RefUnwindSafe for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T> !RefUnwindSafe for RwLock<R, T>",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T> !RefUnwindSafe for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T> !RefUnwindSafe for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T> !RefUnwindSafe for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedRwLockReadGuard<'a, R, T>where
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T: ?Sized> RefUnwindSafe for MappedRwLockWriteGuard<'a, R, T>where
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl RefUnwindSafe for GuardSend",1,["lock_api::GuardSend"]],["impl RefUnwindSafe for GuardNoSend",1,["lock_api::GuardNoSend"]]], -"lru":[["impl<K, V, S> RefUnwindSafe for LruCache<K, V, S>where
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::LruCache"]],["impl<'a, K, V> RefUnwindSafe for Iter<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::Iter"]],["impl<'a, K, V> RefUnwindSafe for IterMut<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::IterMut"]],["impl<K, V> RefUnwindSafe for IntoIter<K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::IntoIter"]]], -"memchr":[["impl<'a> RefUnwindSafe for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> RefUnwindSafe for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> RefUnwindSafe for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl RefUnwindSafe for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> RefUnwindSafe for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> RefUnwindSafe for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> RefUnwindSafe for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> RefUnwindSafe for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl RefUnwindSafe for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl RefUnwindSafe for Dir",1,["nix::dir::Dir"]],["impl<'d> RefUnwindSafe for Iter<'d>",1,["nix::dir::Iter"]],["impl RefUnwindSafe for OwningIter",1,["nix::dir::OwningIter"]],["impl RefUnwindSafe for Entry",1,["nix::dir::Entry"]],["impl RefUnwindSafe for Type",1,["nix::dir::Type"]],["impl RefUnwindSafe for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl RefUnwindSafe for Errno",1,["nix::errno::consts::Errno"]],["impl RefUnwindSafe for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl RefUnwindSafe for AtFlags",1,["nix::fcntl::AtFlags"]],["impl RefUnwindSafe for OFlag",1,["nix::fcntl::OFlag"]],["impl RefUnwindSafe for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl RefUnwindSafe for SealFlag",1,["nix::fcntl::SealFlag"]],["impl RefUnwindSafe for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> RefUnwindSafe for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl RefUnwindSafe for FlockArg",1,["nix::fcntl::FlockArg"]],["impl RefUnwindSafe for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl RefUnwindSafe for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl RefUnwindSafe for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl RefUnwindSafe for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl RefUnwindSafe for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl RefUnwindSafe for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> RefUnwindSafe for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl RefUnwindSafe for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl RefUnwindSafe for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl RefUnwindSafe for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl RefUnwindSafe for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl RefUnwindSafe for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl RefUnwindSafe for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl RefUnwindSafe for MqAttr",1,["nix::mqueue::MqAttr"]],["impl RefUnwindSafe for MqdT",1,["nix::mqueue::MqdT"]],["impl RefUnwindSafe for PollFd",1,["nix::poll::PollFd"]],["impl RefUnwindSafe for PollFlags",1,["nix::poll::PollFlags"]],["impl RefUnwindSafe for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl RefUnwindSafe for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl RefUnwindSafe for PtyMaster",1,["nix::pty::PtyMaster"]],["impl RefUnwindSafe for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl RefUnwindSafe for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl RefUnwindSafe for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl RefUnwindSafe for LioMode",1,["nix::sys::aio::LioMode"]],["impl RefUnwindSafe for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl RefUnwindSafe for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> RefUnwindSafe for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> RefUnwindSafe for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl RefUnwindSafe for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl RefUnwindSafe for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl RefUnwindSafe for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl RefUnwindSafe for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl RefUnwindSafe for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl RefUnwindSafe for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl RefUnwindSafe for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl RefUnwindSafe for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl RefUnwindSafe for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl RefUnwindSafe for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl RefUnwindSafe for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl RefUnwindSafe for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl RefUnwindSafe for Persona",1,["nix::sys::personality::Persona"]],["impl RefUnwindSafe for Request",1,["nix::sys::ptrace::linux::Request"]],["impl RefUnwindSafe for Event",1,["nix::sys::ptrace::linux::Event"]],["impl RefUnwindSafe for Options",1,["nix::sys::ptrace::linux::Options"]],["impl RefUnwindSafe for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl RefUnwindSafe for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl RefUnwindSafe for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl RefUnwindSafe for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl RefUnwindSafe for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl RefUnwindSafe for Resource",1,["nix::sys::resource::Resource"]],["impl RefUnwindSafe for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl RefUnwindSafe for Usage",1,["nix::sys::resource::Usage"]],["impl RefUnwindSafe for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> RefUnwindSafe for Fds<'a>",1,["nix::sys::select::Fds"]],["impl RefUnwindSafe for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl RefUnwindSafe for Signal",1,["nix::sys::signal::Signal"]],["impl RefUnwindSafe for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl RefUnwindSafe for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl RefUnwindSafe for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl RefUnwindSafe for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> RefUnwindSafe for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl RefUnwindSafe for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl RefUnwindSafe for SigAction",1,["nix::sys::signal::SigAction"]],["impl RefUnwindSafe for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl RefUnwindSafe for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl RefUnwindSafe for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl RefUnwindSafe for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl RefUnwindSafe for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl RefUnwindSafe for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl RefUnwindSafe for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl RefUnwindSafe for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl RefUnwindSafe for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl RefUnwindSafe for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl RefUnwindSafe for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl RefUnwindSafe for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl RefUnwindSafe for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl RefUnwindSafe for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl RefUnwindSafe for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl RefUnwindSafe for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl RefUnwindSafe for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl RefUnwindSafe for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl RefUnwindSafe for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl RefUnwindSafe for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl RefUnwindSafe for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl RefUnwindSafe for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl RefUnwindSafe for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl RefUnwindSafe for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl RefUnwindSafe for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl RefUnwindSafe for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl RefUnwindSafe for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl RefUnwindSafe for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl RefUnwindSafe for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl RefUnwindSafe for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl RefUnwindSafe for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl RefUnwindSafe for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl RefUnwindSafe for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl RefUnwindSafe for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl RefUnwindSafe for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl RefUnwindSafe for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl RefUnwindSafe for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl RefUnwindSafe for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl RefUnwindSafe for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl RefUnwindSafe for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl RefUnwindSafe for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl RefUnwindSafe for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl RefUnwindSafe for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl RefUnwindSafe for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl RefUnwindSafe for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl RefUnwindSafe for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl RefUnwindSafe for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl RefUnwindSafe for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl RefUnwindSafe for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl RefUnwindSafe for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl RefUnwindSafe for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl RefUnwindSafe for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl RefUnwindSafe for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl RefUnwindSafe for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl RefUnwindSafe for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl RefUnwindSafe for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl RefUnwindSafe for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl RefUnwindSafe for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl RefUnwindSafe for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl RefUnwindSafe for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl RefUnwindSafe for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl RefUnwindSafe for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl RefUnwindSafe for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl RefUnwindSafe for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl RefUnwindSafe for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl RefUnwindSafe for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl RefUnwindSafe for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl RefUnwindSafe for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl RefUnwindSafe for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl RefUnwindSafe for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl RefUnwindSafe for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl RefUnwindSafe for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl RefUnwindSafe for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl RefUnwindSafe for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl RefUnwindSafe for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl RefUnwindSafe for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl RefUnwindSafe for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> RefUnwindSafe for AlgSetKey<T>where
        T: RefUnwindSafe,
    ",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl RefUnwindSafe for SockType",1,["nix::sys::socket::SockType"]],["impl RefUnwindSafe for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl RefUnwindSafe for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl RefUnwindSafe for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl RefUnwindSafe for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl RefUnwindSafe for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl RefUnwindSafe for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl RefUnwindSafe for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> RefUnwindSafe for RecvMsg<'a, 's, S>where
        S: RefUnwindSafe,
    ",1,["nix::sys::socket::RecvMsg"]],["impl<'a> RefUnwindSafe for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl RefUnwindSafe for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl RefUnwindSafe for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> RefUnwindSafe for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> RefUnwindSafe for MultiHeaders<S>where
        S: RefUnwindSafe,
    ",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> RefUnwindSafe for MultiResults<'a, S>where
        S: RefUnwindSafe,
    ",1,["nix::sys::socket::MultiResults"]],["impl<'a> RefUnwindSafe for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl RefUnwindSafe for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl RefUnwindSafe for SFlag",1,["nix::sys::stat::SFlag"]],["impl RefUnwindSafe for Mode",1,["nix::sys::stat::Mode"]],["impl RefUnwindSafe for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl RefUnwindSafe for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl RefUnwindSafe for Statfs",1,["nix::sys::statfs::Statfs"]],["impl RefUnwindSafe for FsType",1,["nix::sys::statfs::FsType"]],["impl RefUnwindSafe for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl RefUnwindSafe for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl RefUnwindSafe for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl !RefUnwindSafe for Termios",1,["nix::sys::termios::Termios"]],["impl RefUnwindSafe for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl RefUnwindSafe for SetArg",1,["nix::sys::termios::SetArg"]],["impl RefUnwindSafe for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl RefUnwindSafe for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl RefUnwindSafe for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl RefUnwindSafe for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl RefUnwindSafe for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl RefUnwindSafe for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl RefUnwindSafe for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl RefUnwindSafe for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl RefUnwindSafe for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl RefUnwindSafe for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl RefUnwindSafe for TimeVal",1,["nix::sys::time::TimeVal"]],["impl RefUnwindSafe for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> RefUnwindSafe for IoVec<T>where
        T: RefUnwindSafe,
    ",1,["nix::sys::uio::IoVec"]],["impl RefUnwindSafe for UtsName",1,["nix::sys::utsname::UtsName"]],["impl RefUnwindSafe for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl RefUnwindSafe for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl RefUnwindSafe for Id",1,["nix::sys::wait::Id"]],["impl RefUnwindSafe for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl RefUnwindSafe for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl RefUnwindSafe for Inotify",1,["nix::sys::inotify::Inotify"]],["impl RefUnwindSafe for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl RefUnwindSafe for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl RefUnwindSafe for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl RefUnwindSafe for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl RefUnwindSafe for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl RefUnwindSafe for Timer",1,["nix::sys::timer::Timer"]],["impl RefUnwindSafe for ClockId",1,["nix::time::ClockId"]],["impl RefUnwindSafe for UContext",1,["nix::ucontext::UContext"]],["impl RefUnwindSafe for ResUid",1,["nix::unistd::getres::ResUid"]],["impl RefUnwindSafe for ResGid",1,["nix::unistd::getres::ResGid"]],["impl RefUnwindSafe for Uid",1,["nix::unistd::Uid"]],["impl RefUnwindSafe for Gid",1,["nix::unistd::Gid"]],["impl RefUnwindSafe for Pid",1,["nix::unistd::Pid"]],["impl RefUnwindSafe for ForkResult",1,["nix::unistd::ForkResult"]],["impl RefUnwindSafe for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl RefUnwindSafe for Whence",1,["nix::unistd::Whence"]],["impl RefUnwindSafe for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl RefUnwindSafe for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl RefUnwindSafe for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl RefUnwindSafe for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl RefUnwindSafe for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl RefUnwindSafe for User",1,["nix::unistd::User"]],["impl RefUnwindSafe for Group",1,["nix::unistd::Group"]]], -"once_cell":[["impl<T> RefUnwindSafe for OnceCell<T>where
        T: UnwindSafe + RefUnwindSafe,
    ",1,["once_cell::sync::OnceCell"]],["impl<T> RefUnwindSafe for OnceBox<T>where
        T: RefUnwindSafe,
    ",1,["once_cell::race::once_box::OnceBox"]],["impl RefUnwindSafe for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl RefUnwindSafe for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceCell<T>"],["impl<T, F: RefUnwindSafe> RefUnwindSafe for Lazy<T, F>where
        OnceCell<T>: RefUnwindSafe,
    "],["impl<T, F: RefUnwindSafe> RefUnwindSafe for Lazy<T, F>where
        OnceCell<T>: RefUnwindSafe,
    "]], -"parking_lot":[["impl RefUnwindSafe for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl RefUnwindSafe for Condvar",1,["parking_lot::condvar::Condvar"]],["impl RefUnwindSafe for OnceState",1,["parking_lot::once::OnceState"]],["impl RefUnwindSafe for Once",1,["parking_lot::once::Once"]],["impl RefUnwindSafe for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl RefUnwindSafe for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl RefUnwindSafe for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl RefUnwindSafe for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl RefUnwindSafe for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl RefUnwindSafe for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl RefUnwindSafe for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl RefUnwindSafe for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl RefUnwindSafe for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl RefUnwindSafe for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl RefUnwindSafe for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl RefUnwindSafe for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl RefUnwindSafe for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl RefUnwindSafe for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl RefUnwindSafe for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl RefUnwindSafe for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl RefUnwindSafe for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl RefUnwindSafe for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl RefUnwindSafe for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl RefUnwindSafe for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl RefUnwindSafe for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> RefUnwindSafe for SseMachine<S3, S4, NI>where
        NI: RefUnwindSafe,
        S3: RefUnwindSafe,
        S4: RefUnwindSafe,
    ",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> RefUnwindSafe for Avx2Machine<NI>where
        NI: RefUnwindSafe,
    ",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl RefUnwindSafe for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl RefUnwindSafe for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl RefUnwindSafe for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl RefUnwindSafe for Error",1,["primitive_types::Error"]],["impl RefUnwindSafe for U128",1,["primitive_types::U128"]],["impl RefUnwindSafe for U256",1,["primitive_types::U256"]],["impl RefUnwindSafe for U512",1,["primitive_types::U512"]],["impl RefUnwindSafe for H128",1,["primitive_types::H128"]],["impl RefUnwindSafe for H160",1,["primitive_types::H160"]],["impl RefUnwindSafe for H256",1,["primitive_types::H256"]],["impl RefUnwindSafe for H384",1,["primitive_types::H384"]],["impl RefUnwindSafe for H512",1,["primitive_types::H512"]],["impl RefUnwindSafe for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl RefUnwindSafe for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl RefUnwindSafe for TokenStream",1,["proc_macro2::TokenStream"]],["impl RefUnwindSafe for LexError",1,["proc_macro2::LexError"]],["impl RefUnwindSafe for Span",1,["proc_macro2::Span"]],["impl RefUnwindSafe for TokenTree",1,["proc_macro2::TokenTree"]],["impl RefUnwindSafe for Group",1,["proc_macro2::Group"]],["impl RefUnwindSafe for Delimiter",1,["proc_macro2::Delimiter"]],["impl RefUnwindSafe for Punct",1,["proc_macro2::Punct"]],["impl RefUnwindSafe for Spacing",1,["proc_macro2::Spacing"]],["impl RefUnwindSafe for Ident",1,["proc_macro2::Ident"]],["impl RefUnwindSafe for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl RefUnwindSafe for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl RefUnwindSafe for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> RefUnwindSafe for DistIter<D, R, T>where
        D: RefUnwindSafe,
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> RefUnwindSafe for DistMap<D, F, T, S>where
        D: RefUnwindSafe,
        F: RefUnwindSafe,
    ",1,["rand::distributions::distribution::DistMap"]],["impl RefUnwindSafe for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl RefUnwindSafe for Open01",1,["rand::distributions::float::Open01"]],["impl RefUnwindSafe for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> RefUnwindSafe for Slice<'a, T>where
        T: RefUnwindSafe,
    ",1,["rand::distributions::slice::Slice"]],["impl<X> RefUnwindSafe for WeightedIndex<X>where
        X: RefUnwindSafe,
        <X as SampleUniform>::Sampler: RefUnwindSafe,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl RefUnwindSafe for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> RefUnwindSafe for Uniform<X>where
        <X as SampleUniform>::Sampler: RefUnwindSafe,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> RefUnwindSafe for UniformInt<X>where
        X: RefUnwindSafe,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl RefUnwindSafe for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> RefUnwindSafe for UniformFloat<X>where
        X: RefUnwindSafe,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl RefUnwindSafe for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> RefUnwindSafe for WeightedIndex<W>where
        W: RefUnwindSafe,
    ",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl RefUnwindSafe for Standard",1,["rand::distributions::Standard"]],["impl<R> RefUnwindSafe for ReadRng<R>where
        R: RefUnwindSafe,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl !RefUnwindSafe for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> RefUnwindSafe for ReseedingRng<R, Rsdr>where
        R: RefUnwindSafe,
        Rsdr: RefUnwindSafe,
        <R as BlockRngCore>::Results: RefUnwindSafe,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl RefUnwindSafe for StepRng",1,["rand::rngs::mock::StepRng"]],["impl RefUnwindSafe for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> RefUnwindSafe for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl RefUnwindSafe for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> RefUnwindSafe for SliceChooseIter<'a, S, T>where
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl RefUnwindSafe for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl RefUnwindSafe for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl RefUnwindSafe for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl RefUnwindSafe for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl RefUnwindSafe for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl RefUnwindSafe for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> RefUnwindSafe for BlockRng<R>where
        R: RefUnwindSafe,
        <R as BlockRngCore>::Results: RefUnwindSafe,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> RefUnwindSafe for BlockRng64<R>where
        R: RefUnwindSafe,
        <R as BlockRngCore>::Results: RefUnwindSafe,
    ",1,["rand_core::block::BlockRng64"]],["impl !RefUnwindSafe for Error",1,["rand_core::error::Error"]],["impl RefUnwindSafe for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl RefUnwindSafe for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl RefUnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> RefUnwindSafe for Match<'t>",1,["regex::re_bytes::Match"]],["impl RefUnwindSafe for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> RefUnwindSafe for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> RefUnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> RefUnwindSafe for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> RefUnwindSafe for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> RefUnwindSafe for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl RefUnwindSafe for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> RefUnwindSafe for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> RefUnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReplacerRef<'a, R>where
        R: RefUnwindSafe,
    ",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> RefUnwindSafe for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl RefUnwindSafe for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl RefUnwindSafe for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl RefUnwindSafe for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> RefUnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl RefUnwindSafe for Error",1,["regex::error::Error"]],["impl RefUnwindSafe for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl RefUnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl RefUnwindSafe for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl RefUnwindSafe for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl RefUnwindSafe for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> RefUnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> RefUnwindSafe for Match<'t>",1,["regex::re_unicode::Match"]],["impl RefUnwindSafe for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> RefUnwindSafe for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> RefUnwindSafe for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> RefUnwindSafe for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl RefUnwindSafe for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> RefUnwindSafe for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> RefUnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> RefUnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> RefUnwindSafe for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R: ?Sized> RefUnwindSafe for ReplacerRef<'a, R>where
        R: RefUnwindSafe,
    ",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> RefUnwindSafe for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl RefUnwindSafe for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl !RefUnwindSafe for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl RefUnwindSafe for Printer",1,["regex_syntax::ast::print::Printer"]],["impl RefUnwindSafe for Error",1,["regex_syntax::ast::Error"]],["impl RefUnwindSafe for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl RefUnwindSafe for Span",1,["regex_syntax::ast::Span"]],["impl RefUnwindSafe for Position",1,["regex_syntax::ast::Position"]],["impl RefUnwindSafe for WithComments",1,["regex_syntax::ast::WithComments"]],["impl RefUnwindSafe for Comment",1,["regex_syntax::ast::Comment"]],["impl RefUnwindSafe for Ast",1,["regex_syntax::ast::Ast"]],["impl RefUnwindSafe for Alternation",1,["regex_syntax::ast::Alternation"]],["impl RefUnwindSafe for Concat",1,["regex_syntax::ast::Concat"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::ast::Literal"]],["impl RefUnwindSafe for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl RefUnwindSafe for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl RefUnwindSafe for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl RefUnwindSafe for Class",1,["regex_syntax::ast::Class"]],["impl RefUnwindSafe for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl RefUnwindSafe for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl RefUnwindSafe for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl RefUnwindSafe for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl RefUnwindSafe for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl RefUnwindSafe for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl RefUnwindSafe for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl RefUnwindSafe for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl RefUnwindSafe for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl RefUnwindSafe for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl RefUnwindSafe for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl RefUnwindSafe for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl RefUnwindSafe for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl RefUnwindSafe for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl RefUnwindSafe for Assertion",1,["regex_syntax::ast::Assertion"]],["impl RefUnwindSafe for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl RefUnwindSafe for Repetition",1,["regex_syntax::ast::Repetition"]],["impl RefUnwindSafe for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl RefUnwindSafe for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl RefUnwindSafe for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl RefUnwindSafe for Group",1,["regex_syntax::ast::Group"]],["impl RefUnwindSafe for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl RefUnwindSafe for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl RefUnwindSafe for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl RefUnwindSafe for Flags",1,["regex_syntax::ast::Flags"]],["impl RefUnwindSafe for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl RefUnwindSafe for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl RefUnwindSafe for Flag",1,["regex_syntax::ast::Flag"]],["impl RefUnwindSafe for Error",1,["regex_syntax::error::Error"]],["impl RefUnwindSafe for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl RefUnwindSafe for Printer",1,["regex_syntax::hir::print::Printer"]],["impl RefUnwindSafe for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl !RefUnwindSafe for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl RefUnwindSafe for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl RefUnwindSafe for Error",1,["regex_syntax::hir::Error"]],["impl RefUnwindSafe for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl RefUnwindSafe for Hir",1,["regex_syntax::hir::Hir"]],["impl RefUnwindSafe for HirKind",1,["regex_syntax::hir::HirKind"]],["impl RefUnwindSafe for Literal",1,["regex_syntax::hir::Literal"]],["impl RefUnwindSafe for Class",1,["regex_syntax::hir::Class"]],["impl RefUnwindSafe for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> RefUnwindSafe for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl RefUnwindSafe for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl RefUnwindSafe for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> RefUnwindSafe for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl RefUnwindSafe for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl RefUnwindSafe for Anchor",1,["regex_syntax::hir::Anchor"]],["impl RefUnwindSafe for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl RefUnwindSafe for Group",1,["regex_syntax::hir::Group"]],["impl RefUnwindSafe for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl RefUnwindSafe for Repetition",1,["regex_syntax::hir::Repetition"]],["impl RefUnwindSafe for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl RefUnwindSafe for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl RefUnwindSafe for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl !RefUnwindSafe for Parser",1,["regex_syntax::parser::Parser"]],["impl RefUnwindSafe for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl RefUnwindSafe for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl RefUnwindSafe for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl RefUnwindSafe for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl RefUnwindSafe for DecoderError",1,["rlp::error::DecoderError"]],["impl RefUnwindSafe for Prototype",1,["rlp::rlpin::Prototype"]],["impl RefUnwindSafe for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> !RefUnwindSafe for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !RefUnwindSafe for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl RefUnwindSafe for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> RefUnwindSafe for ToHexIter<T>where
        T: RefUnwindSafe,
    ",1,["rustc_hex::ToHexIter"]],["impl RefUnwindSafe for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> RefUnwindSafe for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl RefUnwindSafe for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl RefUnwindSafe for Always",1,["scopeguard::Always"]],["impl<T, F, S> RefUnwindSafe for ScopeGuard<T, F, S>where
        F: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["scopeguard::ScopeGuard"]]], -"serde":[["impl RefUnwindSafe for Error",1,["serde::de::value::Error"]],["impl<E> RefUnwindSafe for UnitDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::UnitDeserializer"]],["impl<E> RefUnwindSafe for BoolDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::BoolDeserializer"]],["impl<E> RefUnwindSafe for I8Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::I8Deserializer"]],["impl<E> RefUnwindSafe for I16Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::I16Deserializer"]],["impl<E> RefUnwindSafe for I32Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::I32Deserializer"]],["impl<E> RefUnwindSafe for I64Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::I64Deserializer"]],["impl<E> RefUnwindSafe for IsizeDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::IsizeDeserializer"]],["impl<E> RefUnwindSafe for U8Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::U8Deserializer"]],["impl<E> RefUnwindSafe for U16Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::U16Deserializer"]],["impl<E> RefUnwindSafe for U64Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::U64Deserializer"]],["impl<E> RefUnwindSafe for UsizeDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::UsizeDeserializer"]],["impl<E> RefUnwindSafe for F32Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::F32Deserializer"]],["impl<E> RefUnwindSafe for F64Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::F64Deserializer"]],["impl<E> RefUnwindSafe for CharDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::CharDeserializer"]],["impl<E> RefUnwindSafe for I128Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::I128Deserializer"]],["impl<E> RefUnwindSafe for U128Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::U128Deserializer"]],["impl<E> RefUnwindSafe for U32Deserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> RefUnwindSafe for StrDeserializer<'a, E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> RefUnwindSafe for BorrowedStrDeserializer<'de, E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> RefUnwindSafe for StringDeserializer<E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> RefUnwindSafe for CowStrDeserializer<'a, E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> RefUnwindSafe for BytesDeserializer<'a, E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> RefUnwindSafe for BorrowedBytesDeserializer<'de, E>where
        E: RefUnwindSafe,
    ",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> RefUnwindSafe for SeqDeserializer<I, E>where
        E: RefUnwindSafe,
        I: RefUnwindSafe,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> RefUnwindSafe for SeqAccessDeserializer<A>where
        A: RefUnwindSafe,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> RefUnwindSafe for MapDeserializer<'de, I, E>where
        E: RefUnwindSafe,
        I: RefUnwindSafe,
        <<I as Iterator>::Item as Pair>::Second: RefUnwindSafe,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> RefUnwindSafe for MapAccessDeserializer<A>where
        A: RefUnwindSafe,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> RefUnwindSafe for EnumAccessDeserializer<A>where
        A: RefUnwindSafe,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl RefUnwindSafe for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> RefUnwindSafe for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> RefUnwindSafe for Impossible<Ok, Error>where
        Error: RefUnwindSafe,
        Ok: RefUnwindSafe,
    ",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl RefUnwindSafe for Keccak224Core",1,["sha3::Keccak224Core"]],["impl RefUnwindSafe for Keccak256Core",1,["sha3::Keccak256Core"]],["impl RefUnwindSafe for Keccak384Core",1,["sha3::Keccak384Core"]],["impl RefUnwindSafe for Keccak512Core",1,["sha3::Keccak512Core"]],["impl RefUnwindSafe for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl RefUnwindSafe for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl RefUnwindSafe for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl RefUnwindSafe for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl RefUnwindSafe for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl RefUnwindSafe for Shake128Core",1,["sha3::Shake128Core"]],["impl RefUnwindSafe for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl RefUnwindSafe for Shake256Core",1,["sha3::Shake256Core"]],["impl RefUnwindSafe for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl RefUnwindSafe for CShake128Core",1,["sha3::CShake128Core"]],["impl RefUnwindSafe for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl RefUnwindSafe for CShake256Core",1,["sha3::CShake256Core"]],["impl RefUnwindSafe for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl RefUnwindSafe for CompactHeader",1,["shale::compact::CompactHeader"]],["impl RefUnwindSafe for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !RefUnwindSafe for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl RefUnwindSafe for ShaleError",1,["shale::ShaleError"]],["impl RefUnwindSafe for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> RefUnwindSafe for ObjPtr<T>where
        T: RefUnwindSafe,
    ",1,["shale::ObjPtr"]],["impl<T> !RefUnwindSafe for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !RefUnwindSafe for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !RefUnwindSafe for MummyObj<T>",1,["shale::MummyObj"]],["impl !RefUnwindSafe for PlainMem",1,["shale::PlainMem"]],["impl<T> !RefUnwindSafe for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> RefUnwindSafe for Slab<T>where
        T: RefUnwindSafe,
    ",1,["slab::Slab"]],["impl<'a, T> RefUnwindSafe for VacantEntry<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::VacantEntry"]],["impl<T> RefUnwindSafe for IntoIter<T>where
        T: RefUnwindSafe,
    ",1,["slab::IntoIter"]],["impl<'a, T> RefUnwindSafe for Iter<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::Iter"]],["impl<'a, T> RefUnwindSafe for IterMut<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::IterMut"]],["impl<'a, T> RefUnwindSafe for Drain<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::Drain"]]], -"smallvec":[["impl RefUnwindSafe for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> RefUnwindSafe for Drain<'a, T>where
        T: RefUnwindSafe,
        <T as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::Drain"]],["impl<A> RefUnwindSafe for SmallVec<A>where
        A: RefUnwindSafe,
        <A as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::SmallVec"]],["impl<A> RefUnwindSafe for IntoIter<A>where
        A: RefUnwindSafe,
        <A as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::IntoIter"]]], -"syn":[["impl RefUnwindSafe for Underscore",1,["syn::token::Underscore"]],["impl RefUnwindSafe for Abstract",1,["syn::token::Abstract"]],["impl RefUnwindSafe for As",1,["syn::token::As"]],["impl RefUnwindSafe for Async",1,["syn::token::Async"]],["impl RefUnwindSafe for Auto",1,["syn::token::Auto"]],["impl RefUnwindSafe for Await",1,["syn::token::Await"]],["impl RefUnwindSafe for Become",1,["syn::token::Become"]],["impl RefUnwindSafe for Box",1,["syn::token::Box"]],["impl RefUnwindSafe for Break",1,["syn::token::Break"]],["impl RefUnwindSafe for Const",1,["syn::token::Const"]],["impl RefUnwindSafe for Continue",1,["syn::token::Continue"]],["impl RefUnwindSafe for Crate",1,["syn::token::Crate"]],["impl RefUnwindSafe for Default",1,["syn::token::Default"]],["impl RefUnwindSafe for Do",1,["syn::token::Do"]],["impl RefUnwindSafe for Dyn",1,["syn::token::Dyn"]],["impl RefUnwindSafe for Else",1,["syn::token::Else"]],["impl RefUnwindSafe for Enum",1,["syn::token::Enum"]],["impl RefUnwindSafe for Extern",1,["syn::token::Extern"]],["impl RefUnwindSafe for Final",1,["syn::token::Final"]],["impl RefUnwindSafe for Fn",1,["syn::token::Fn"]],["impl RefUnwindSafe for For",1,["syn::token::For"]],["impl RefUnwindSafe for If",1,["syn::token::If"]],["impl RefUnwindSafe for Impl",1,["syn::token::Impl"]],["impl RefUnwindSafe for In",1,["syn::token::In"]],["impl RefUnwindSafe for Let",1,["syn::token::Let"]],["impl RefUnwindSafe for Loop",1,["syn::token::Loop"]],["impl RefUnwindSafe for Macro",1,["syn::token::Macro"]],["impl RefUnwindSafe for Match",1,["syn::token::Match"]],["impl RefUnwindSafe for Mod",1,["syn::token::Mod"]],["impl RefUnwindSafe for Move",1,["syn::token::Move"]],["impl RefUnwindSafe for Mut",1,["syn::token::Mut"]],["impl RefUnwindSafe for Override",1,["syn::token::Override"]],["impl RefUnwindSafe for Priv",1,["syn::token::Priv"]],["impl RefUnwindSafe for Pub",1,["syn::token::Pub"]],["impl RefUnwindSafe for Ref",1,["syn::token::Ref"]],["impl RefUnwindSafe for Return",1,["syn::token::Return"]],["impl RefUnwindSafe for SelfType",1,["syn::token::SelfType"]],["impl RefUnwindSafe for SelfValue",1,["syn::token::SelfValue"]],["impl RefUnwindSafe for Static",1,["syn::token::Static"]],["impl RefUnwindSafe for Struct",1,["syn::token::Struct"]],["impl RefUnwindSafe for Super",1,["syn::token::Super"]],["impl RefUnwindSafe for Trait",1,["syn::token::Trait"]],["impl RefUnwindSafe for Try",1,["syn::token::Try"]],["impl RefUnwindSafe for Type",1,["syn::token::Type"]],["impl RefUnwindSafe for Typeof",1,["syn::token::Typeof"]],["impl RefUnwindSafe for Union",1,["syn::token::Union"]],["impl RefUnwindSafe for Unsafe",1,["syn::token::Unsafe"]],["impl RefUnwindSafe for Unsized",1,["syn::token::Unsized"]],["impl RefUnwindSafe for Use",1,["syn::token::Use"]],["impl RefUnwindSafe for Virtual",1,["syn::token::Virtual"]],["impl RefUnwindSafe for Where",1,["syn::token::Where"]],["impl RefUnwindSafe for While",1,["syn::token::While"]],["impl RefUnwindSafe for Yield",1,["syn::token::Yield"]],["impl RefUnwindSafe for Add",1,["syn::token::Add"]],["impl RefUnwindSafe for AddEq",1,["syn::token::AddEq"]],["impl RefUnwindSafe for And",1,["syn::token::And"]],["impl RefUnwindSafe for AndAnd",1,["syn::token::AndAnd"]],["impl RefUnwindSafe for AndEq",1,["syn::token::AndEq"]],["impl RefUnwindSafe for At",1,["syn::token::At"]],["impl RefUnwindSafe for Bang",1,["syn::token::Bang"]],["impl RefUnwindSafe for Caret",1,["syn::token::Caret"]],["impl RefUnwindSafe for CaretEq",1,["syn::token::CaretEq"]],["impl RefUnwindSafe for Colon",1,["syn::token::Colon"]],["impl RefUnwindSafe for Colon2",1,["syn::token::Colon2"]],["impl RefUnwindSafe for Comma",1,["syn::token::Comma"]],["impl RefUnwindSafe for Div",1,["syn::token::Div"]],["impl RefUnwindSafe for DivEq",1,["syn::token::DivEq"]],["impl RefUnwindSafe for Dollar",1,["syn::token::Dollar"]],["impl RefUnwindSafe for Dot",1,["syn::token::Dot"]],["impl RefUnwindSafe for Dot2",1,["syn::token::Dot2"]],["impl RefUnwindSafe for Dot3",1,["syn::token::Dot3"]],["impl RefUnwindSafe for DotDotEq",1,["syn::token::DotDotEq"]],["impl RefUnwindSafe for Eq",1,["syn::token::Eq"]],["impl RefUnwindSafe for EqEq",1,["syn::token::EqEq"]],["impl RefUnwindSafe for Ge",1,["syn::token::Ge"]],["impl RefUnwindSafe for Gt",1,["syn::token::Gt"]],["impl RefUnwindSafe for Le",1,["syn::token::Le"]],["impl RefUnwindSafe for Lt",1,["syn::token::Lt"]],["impl RefUnwindSafe for MulEq",1,["syn::token::MulEq"]],["impl RefUnwindSafe for Ne",1,["syn::token::Ne"]],["impl RefUnwindSafe for Or",1,["syn::token::Or"]],["impl RefUnwindSafe for OrEq",1,["syn::token::OrEq"]],["impl RefUnwindSafe for OrOr",1,["syn::token::OrOr"]],["impl RefUnwindSafe for Pound",1,["syn::token::Pound"]],["impl RefUnwindSafe for Question",1,["syn::token::Question"]],["impl RefUnwindSafe for RArrow",1,["syn::token::RArrow"]],["impl RefUnwindSafe for LArrow",1,["syn::token::LArrow"]],["impl RefUnwindSafe for Rem",1,["syn::token::Rem"]],["impl RefUnwindSafe for RemEq",1,["syn::token::RemEq"]],["impl RefUnwindSafe for FatArrow",1,["syn::token::FatArrow"]],["impl RefUnwindSafe for Semi",1,["syn::token::Semi"]],["impl RefUnwindSafe for Shl",1,["syn::token::Shl"]],["impl RefUnwindSafe for ShlEq",1,["syn::token::ShlEq"]],["impl RefUnwindSafe for Shr",1,["syn::token::Shr"]],["impl RefUnwindSafe for ShrEq",1,["syn::token::ShrEq"]],["impl RefUnwindSafe for Star",1,["syn::token::Star"]],["impl RefUnwindSafe for Sub",1,["syn::token::Sub"]],["impl RefUnwindSafe for SubEq",1,["syn::token::SubEq"]],["impl RefUnwindSafe for Tilde",1,["syn::token::Tilde"]],["impl RefUnwindSafe for Brace",1,["syn::token::Brace"]],["impl RefUnwindSafe for Bracket",1,["syn::token::Bracket"]],["impl RefUnwindSafe for Paren",1,["syn::token::Paren"]],["impl RefUnwindSafe for Group",1,["syn::token::Group"]],["impl RefUnwindSafe for Attribute",1,["syn::attr::Attribute"]],["impl RefUnwindSafe for AttrStyle",1,["syn::attr::AttrStyle"]],["impl RefUnwindSafe for Meta",1,["syn::attr::Meta"]],["impl RefUnwindSafe for MetaList",1,["syn::attr::MetaList"]],["impl RefUnwindSafe for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl RefUnwindSafe for NestedMeta",1,["syn::attr::NestedMeta"]],["impl RefUnwindSafe for Variant",1,["syn::data::Variant"]],["impl RefUnwindSafe for Fields",1,["syn::data::Fields"]],["impl RefUnwindSafe for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl RefUnwindSafe for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl RefUnwindSafe for Field",1,["syn::data::Field"]],["impl RefUnwindSafe for Visibility",1,["syn::data::Visibility"]],["impl RefUnwindSafe for VisPublic",1,["syn::data::VisPublic"]],["impl RefUnwindSafe for VisCrate",1,["syn::data::VisCrate"]],["impl RefUnwindSafe for VisRestricted",1,["syn::data::VisRestricted"]],["impl RefUnwindSafe for Expr",1,["syn::expr::Expr"]],["impl RefUnwindSafe for ExprArray",1,["syn::expr::ExprArray"]],["impl RefUnwindSafe for ExprAssign",1,["syn::expr::ExprAssign"]],["impl RefUnwindSafe for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl RefUnwindSafe for ExprAsync",1,["syn::expr::ExprAsync"]],["impl RefUnwindSafe for ExprAwait",1,["syn::expr::ExprAwait"]],["impl RefUnwindSafe for ExprBinary",1,["syn::expr::ExprBinary"]],["impl RefUnwindSafe for ExprBlock",1,["syn::expr::ExprBlock"]],["impl RefUnwindSafe for ExprBox",1,["syn::expr::ExprBox"]],["impl RefUnwindSafe for ExprBreak",1,["syn::expr::ExprBreak"]],["impl RefUnwindSafe for ExprCall",1,["syn::expr::ExprCall"]],["impl RefUnwindSafe for ExprCast",1,["syn::expr::ExprCast"]],["impl RefUnwindSafe for ExprClosure",1,["syn::expr::ExprClosure"]],["impl RefUnwindSafe for ExprContinue",1,["syn::expr::ExprContinue"]],["impl RefUnwindSafe for ExprField",1,["syn::expr::ExprField"]],["impl RefUnwindSafe for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl RefUnwindSafe for ExprGroup",1,["syn::expr::ExprGroup"]],["impl RefUnwindSafe for ExprIf",1,["syn::expr::ExprIf"]],["impl RefUnwindSafe for ExprIndex",1,["syn::expr::ExprIndex"]],["impl RefUnwindSafe for ExprLet",1,["syn::expr::ExprLet"]],["impl RefUnwindSafe for ExprLit",1,["syn::expr::ExprLit"]],["impl RefUnwindSafe for ExprLoop",1,["syn::expr::ExprLoop"]],["impl RefUnwindSafe for ExprMacro",1,["syn::expr::ExprMacro"]],["impl RefUnwindSafe for ExprMatch",1,["syn::expr::ExprMatch"]],["impl RefUnwindSafe for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl RefUnwindSafe for ExprParen",1,["syn::expr::ExprParen"]],["impl RefUnwindSafe for ExprPath",1,["syn::expr::ExprPath"]],["impl RefUnwindSafe for ExprRange",1,["syn::expr::ExprRange"]],["impl RefUnwindSafe for ExprReference",1,["syn::expr::ExprReference"]],["impl RefUnwindSafe for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl RefUnwindSafe for ExprReturn",1,["syn::expr::ExprReturn"]],["impl RefUnwindSafe for ExprStruct",1,["syn::expr::ExprStruct"]],["impl RefUnwindSafe for ExprTry",1,["syn::expr::ExprTry"]],["impl RefUnwindSafe for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl RefUnwindSafe for ExprTuple",1,["syn::expr::ExprTuple"]],["impl RefUnwindSafe for ExprType",1,["syn::expr::ExprType"]],["impl RefUnwindSafe for ExprUnary",1,["syn::expr::ExprUnary"]],["impl RefUnwindSafe for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl RefUnwindSafe for ExprWhile",1,["syn::expr::ExprWhile"]],["impl RefUnwindSafe for ExprYield",1,["syn::expr::ExprYield"]],["impl RefUnwindSafe for Member",1,["syn::expr::Member"]],["impl RefUnwindSafe for Index",1,["syn::expr::Index"]],["impl RefUnwindSafe for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl RefUnwindSafe for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl RefUnwindSafe for FieldValue",1,["syn::expr::FieldValue"]],["impl RefUnwindSafe for Label",1,["syn::expr::Label"]],["impl RefUnwindSafe for Arm",1,["syn::expr::Arm"]],["impl RefUnwindSafe for RangeLimits",1,["syn::expr::RangeLimits"]],["impl RefUnwindSafe for Generics",1,["syn::generics::Generics"]],["impl RefUnwindSafe for GenericParam",1,["syn::generics::GenericParam"]],["impl RefUnwindSafe for TypeParam",1,["syn::generics::TypeParam"]],["impl RefUnwindSafe for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl RefUnwindSafe for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> RefUnwindSafe for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> RefUnwindSafe for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> RefUnwindSafe for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl RefUnwindSafe for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl RefUnwindSafe for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl RefUnwindSafe for TraitBound",1,["syn::generics::TraitBound"]],["impl RefUnwindSafe for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl RefUnwindSafe for WhereClause",1,["syn::generics::WhereClause"]],["impl RefUnwindSafe for WherePredicate",1,["syn::generics::WherePredicate"]],["impl RefUnwindSafe for PredicateType",1,["syn::generics::PredicateType"]],["impl RefUnwindSafe for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl RefUnwindSafe for PredicateEq",1,["syn::generics::PredicateEq"]],["impl RefUnwindSafe for Item",1,["syn::item::Item"]],["impl RefUnwindSafe for ItemConst",1,["syn::item::ItemConst"]],["impl RefUnwindSafe for ItemEnum",1,["syn::item::ItemEnum"]],["impl RefUnwindSafe for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl RefUnwindSafe for ItemFn",1,["syn::item::ItemFn"]],["impl RefUnwindSafe for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl RefUnwindSafe for ItemImpl",1,["syn::item::ItemImpl"]],["impl RefUnwindSafe for ItemMacro",1,["syn::item::ItemMacro"]],["impl RefUnwindSafe for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl RefUnwindSafe for ItemMod",1,["syn::item::ItemMod"]],["impl RefUnwindSafe for ItemStatic",1,["syn::item::ItemStatic"]],["impl RefUnwindSafe for ItemStruct",1,["syn::item::ItemStruct"]],["impl RefUnwindSafe for ItemTrait",1,["syn::item::ItemTrait"]],["impl RefUnwindSafe for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl RefUnwindSafe for ItemType",1,["syn::item::ItemType"]],["impl RefUnwindSafe for ItemUnion",1,["syn::item::ItemUnion"]],["impl RefUnwindSafe for ItemUse",1,["syn::item::ItemUse"]],["impl RefUnwindSafe for UseTree",1,["syn::item::UseTree"]],["impl RefUnwindSafe for UsePath",1,["syn::item::UsePath"]],["impl RefUnwindSafe for UseName",1,["syn::item::UseName"]],["impl RefUnwindSafe for UseRename",1,["syn::item::UseRename"]],["impl RefUnwindSafe for UseGlob",1,["syn::item::UseGlob"]],["impl RefUnwindSafe for UseGroup",1,["syn::item::UseGroup"]],["impl RefUnwindSafe for ForeignItem",1,["syn::item::ForeignItem"]],["impl RefUnwindSafe for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl RefUnwindSafe for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl RefUnwindSafe for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl RefUnwindSafe for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl RefUnwindSafe for TraitItem",1,["syn::item::TraitItem"]],["impl RefUnwindSafe for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl RefUnwindSafe for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl RefUnwindSafe for TraitItemType",1,["syn::item::TraitItemType"]],["impl RefUnwindSafe for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl RefUnwindSafe for ImplItem",1,["syn::item::ImplItem"]],["impl RefUnwindSafe for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl RefUnwindSafe for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl RefUnwindSafe for ImplItemType",1,["syn::item::ImplItemType"]],["impl RefUnwindSafe for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl RefUnwindSafe for Signature",1,["syn::item::Signature"]],["impl RefUnwindSafe for FnArg",1,["syn::item::FnArg"]],["impl RefUnwindSafe for Receiver",1,["syn::item::Receiver"]],["impl RefUnwindSafe for File",1,["syn::file::File"]],["impl RefUnwindSafe for Lifetime",1,["syn::lifetime::Lifetime"]],["impl RefUnwindSafe for Lit",1,["syn::lit::Lit"]],["impl RefUnwindSafe for LitStr",1,["syn::lit::LitStr"]],["impl RefUnwindSafe for LitByteStr",1,["syn::lit::LitByteStr"]],["impl RefUnwindSafe for LitByte",1,["syn::lit::LitByte"]],["impl RefUnwindSafe for LitChar",1,["syn::lit::LitChar"]],["impl RefUnwindSafe for LitInt",1,["syn::lit::LitInt"]],["impl RefUnwindSafe for LitFloat",1,["syn::lit::LitFloat"]],["impl RefUnwindSafe for LitBool",1,["syn::lit::LitBool"]],["impl RefUnwindSafe for StrStyle",1,["syn::lit::StrStyle"]],["impl RefUnwindSafe for Macro",1,["syn::mac::Macro"]],["impl RefUnwindSafe for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl RefUnwindSafe for DeriveInput",1,["syn::derive::DeriveInput"]],["impl RefUnwindSafe for Data",1,["syn::derive::Data"]],["impl RefUnwindSafe for DataStruct",1,["syn::derive::DataStruct"]],["impl RefUnwindSafe for DataEnum",1,["syn::derive::DataEnum"]],["impl RefUnwindSafe for DataUnion",1,["syn::derive::DataUnion"]],["impl RefUnwindSafe for BinOp",1,["syn::op::BinOp"]],["impl RefUnwindSafe for UnOp",1,["syn::op::UnOp"]],["impl RefUnwindSafe for Block",1,["syn::stmt::Block"]],["impl RefUnwindSafe for Stmt",1,["syn::stmt::Stmt"]],["impl RefUnwindSafe for Local",1,["syn::stmt::Local"]],["impl RefUnwindSafe for Type",1,["syn::ty::Type"]],["impl RefUnwindSafe for TypeArray",1,["syn::ty::TypeArray"]],["impl RefUnwindSafe for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl RefUnwindSafe for TypeGroup",1,["syn::ty::TypeGroup"]],["impl RefUnwindSafe for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl RefUnwindSafe for TypeInfer",1,["syn::ty::TypeInfer"]],["impl RefUnwindSafe for TypeMacro",1,["syn::ty::TypeMacro"]],["impl RefUnwindSafe for TypeNever",1,["syn::ty::TypeNever"]],["impl RefUnwindSafe for TypeParen",1,["syn::ty::TypeParen"]],["impl RefUnwindSafe for TypePath",1,["syn::ty::TypePath"]],["impl RefUnwindSafe for TypePtr",1,["syn::ty::TypePtr"]],["impl RefUnwindSafe for TypeReference",1,["syn::ty::TypeReference"]],["impl RefUnwindSafe for TypeSlice",1,["syn::ty::TypeSlice"]],["impl RefUnwindSafe for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl RefUnwindSafe for TypeTuple",1,["syn::ty::TypeTuple"]],["impl RefUnwindSafe for Abi",1,["syn::ty::Abi"]],["impl RefUnwindSafe for BareFnArg",1,["syn::ty::BareFnArg"]],["impl RefUnwindSafe for Variadic",1,["syn::ty::Variadic"]],["impl RefUnwindSafe for ReturnType",1,["syn::ty::ReturnType"]],["impl RefUnwindSafe for Pat",1,["syn::pat::Pat"]],["impl RefUnwindSafe for PatBox",1,["syn::pat::PatBox"]],["impl RefUnwindSafe for PatIdent",1,["syn::pat::PatIdent"]],["impl RefUnwindSafe for PatLit",1,["syn::pat::PatLit"]],["impl RefUnwindSafe for PatMacro",1,["syn::pat::PatMacro"]],["impl RefUnwindSafe for PatOr",1,["syn::pat::PatOr"]],["impl RefUnwindSafe for PatPath",1,["syn::pat::PatPath"]],["impl RefUnwindSafe for PatRange",1,["syn::pat::PatRange"]],["impl RefUnwindSafe for PatReference",1,["syn::pat::PatReference"]],["impl RefUnwindSafe for PatRest",1,["syn::pat::PatRest"]],["impl RefUnwindSafe for PatSlice",1,["syn::pat::PatSlice"]],["impl RefUnwindSafe for PatStruct",1,["syn::pat::PatStruct"]],["impl RefUnwindSafe for PatTuple",1,["syn::pat::PatTuple"]],["impl RefUnwindSafe for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl RefUnwindSafe for PatType",1,["syn::pat::PatType"]],["impl RefUnwindSafe for PatWild",1,["syn::pat::PatWild"]],["impl RefUnwindSafe for FieldPat",1,["syn::pat::FieldPat"]],["impl RefUnwindSafe for Path",1,["syn::path::Path"]],["impl RefUnwindSafe for PathSegment",1,["syn::path::PathSegment"]],["impl RefUnwindSafe for PathArguments",1,["syn::path::PathArguments"]],["impl RefUnwindSafe for GenericArgument",1,["syn::path::GenericArgument"]],["impl RefUnwindSafe for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl RefUnwindSafe for Binding",1,["syn::path::Binding"]],["impl RefUnwindSafe for Constraint",1,["syn::path::Constraint"]],["impl RefUnwindSafe for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl RefUnwindSafe for QSelf",1,["syn::path::QSelf"]],["impl RefUnwindSafe for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> RefUnwindSafe for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> RefUnwindSafe for Punctuated<T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> RefUnwindSafe for Pairs<'a, T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> RefUnwindSafe for PairsMut<'a, T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::PairsMut"]],["impl<T, P> RefUnwindSafe for IntoPairs<T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> RefUnwindSafe for IntoIter<T>where
        T: RefUnwindSafe,
    ",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !RefUnwindSafe for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !RefUnwindSafe for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> RefUnwindSafe for Pair<T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::Pair"]],["impl<'a> !RefUnwindSafe for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl RefUnwindSafe for Error",1,["syn::error::Error"]],["impl<'a> !RefUnwindSafe for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> RefUnwindSafe for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl RefUnwindSafe for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> RefUnwindSafe for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl !RefUnwindSafe for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl !RefUnwindSafe for Builder",1,["tokio::runtime::builder::Builder"]],["impl !RefUnwindSafe for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> !RefUnwindSafe for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl RefUnwindSafe for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !RefUnwindSafe for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl RefUnwindSafe for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !RefUnwindSafe for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !RefUnwindSafe for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl RefUnwindSafe for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> RefUnwindSafe for SendError<T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> !RefUnwindSafe for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> !RefUnwindSafe for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> !RefUnwindSafe for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> !RefUnwindSafe for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> !RefUnwindSafe for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> !RefUnwindSafe for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> RefUnwindSafe for SendError<T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> RefUnwindSafe for TrySendError<T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Mutex<T>",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T> !RefUnwindSafe for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T> !RefUnwindSafe for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T> !RefUnwindSafe for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl RefUnwindSafe for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl RefUnwindSafe for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl RefUnwindSafe for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl RefUnwindSafe for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl !RefUnwindSafe for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> !RefUnwindSafe for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl !RefUnwindSafe for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T, U = T> !RefUnwindSafe for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T> !RefUnwindSafe for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T, U = T> !RefUnwindSafe for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T> !RefUnwindSafe for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T> !RefUnwindSafe for RwLock<T>",1,["tokio::sync::rwlock::RwLock"]],["impl<T> !RefUnwindSafe for OnceCell<T>",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> RefUnwindSafe for SetError<T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> RefUnwindSafe for SendError<T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::watch::error::SendError"]],["impl RefUnwindSafe for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> !RefUnwindSafe for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> !RefUnwindSafe for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> RefUnwindSafe for Ref<'a, T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::watch::Ref"]],["impl !RefUnwindSafe for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !RefUnwindSafe for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> RefUnwindSafe for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> RefUnwindSafe for TaskLocalFuture<T, F>where
        F: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> RefUnwindSafe for Unconstrained<F>where
        F: RefUnwindSafe,
    ",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> RefUnwindSafe for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl RefUnwindSafe for AbortHandle"],["impl<T> RefUnwindSafe for JoinHandle<T>"],["impl RefUnwindSafe for Notify"]], -"typenum":[["impl RefUnwindSafe for B0",1,["typenum::bit::B0"]],["impl RefUnwindSafe for B1",1,["typenum::bit::B1"]],["impl<U> RefUnwindSafe for PInt<U>where
        U: RefUnwindSafe,
    ",1,["typenum::int::PInt"]],["impl<U> RefUnwindSafe for NInt<U>where
        U: RefUnwindSafe,
    ",1,["typenum::int::NInt"]],["impl RefUnwindSafe for Z0",1,["typenum::int::Z0"]],["impl RefUnwindSafe for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> RefUnwindSafe for UInt<U, B>where
        B: RefUnwindSafe,
        U: RefUnwindSafe,
    ",1,["typenum::uint::UInt"]],["impl RefUnwindSafe for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> RefUnwindSafe for TArr<V, A>where
        A: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["typenum::array::TArr"]],["impl RefUnwindSafe for Greater",1,["typenum::Greater"]],["impl RefUnwindSafe for Less",1,["typenum::Less"]],["impl RefUnwindSafe for Equal",1,["typenum::Equal"]]], -"uint":[["impl RefUnwindSafe for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl RefUnwindSafe for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl RefUnwindSafe for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl RefUnwindSafe for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js b/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js deleted file mode 100644 index bd3cf161c3dd..000000000000 --- a/docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() {var implementors = { -"ahash":[["impl UnwindSafe for AHasher",1,["ahash::fallback_hash::AHasher"]],["impl UnwindSafe for RandomState",1,["ahash::random_state::RandomState"]]], -"aho_corasick":[["impl<S> UnwindSafe for AhoCorasick<S>where
        S: UnwindSafe,
    ",1,["aho_corasick::ahocorasick::AhoCorasick"]],["impl<'a, 'b, S> UnwindSafe for FindIter<'a, 'b, S>where
        S: RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::FindIter"]],["impl<'a, 'b, S> UnwindSafe for FindOverlappingIter<'a, 'b, S>where
        S: UnwindSafe + RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::FindOverlappingIter"]],["impl<'a, R, S> UnwindSafe for StreamFindIter<'a, R, S>where
        R: UnwindSafe,
        S: UnwindSafe + RefUnwindSafe,
    ",1,["aho_corasick::ahocorasick::StreamFindIter"]],["impl UnwindSafe for AhoCorasickBuilder",1,["aho_corasick::ahocorasick::AhoCorasickBuilder"]],["impl UnwindSafe for MatchKind",1,["aho_corasick::ahocorasick::MatchKind"]],["impl UnwindSafe for Error",1,["aho_corasick::error::Error"]],["impl UnwindSafe for ErrorKind",1,["aho_corasick::error::ErrorKind"]],["impl UnwindSafe for MatchKind",1,["aho_corasick::packed::api::MatchKind"]],["impl UnwindSafe for Config",1,["aho_corasick::packed::api::Config"]],["impl UnwindSafe for Builder",1,["aho_corasick::packed::api::Builder"]],["impl UnwindSafe for Searcher",1,["aho_corasick::packed::api::Searcher"]],["impl<'s, 'h> UnwindSafe for FindIter<'s, 'h>",1,["aho_corasick::packed::api::FindIter"]],["impl UnwindSafe for Match",1,["aho_corasick::Match"]]], -"aiofut":[["impl UnwindSafe for Error",1,["aiofut::Error"]],["impl UnwindSafe for AIO",1,["aiofut::AIO"]],["impl !UnwindSafe for AIOFuture",1,["aiofut::AIOFuture"]],["impl UnwindSafe for AIONotifier",1,["aiofut::AIONotifier"]],["impl UnwindSafe for AIOBuilder",1,["aiofut::AIOBuilder"]],["impl !UnwindSafe for AIOManager",1,["aiofut::AIOManager"]],["impl UnwindSafe for AIOBatchSchedulerIn",1,["aiofut::AIOBatchSchedulerIn"]],["impl UnwindSafe for AIOBatchSchedulerOut",1,["aiofut::AIOBatchSchedulerOut"]]], -"bincode":[["impl UnwindSafe for LittleEndian",1,["bincode::config::endian::LittleEndian"]],["impl UnwindSafe for BigEndian",1,["bincode::config::endian::BigEndian"]],["impl UnwindSafe for NativeEndian",1,["bincode::config::endian::NativeEndian"]],["impl UnwindSafe for FixintEncoding",1,["bincode::config::int::FixintEncoding"]],["impl UnwindSafe for VarintEncoding",1,["bincode::config::int::VarintEncoding"]],["impl UnwindSafe for Config",1,["bincode::config::legacy::Config"]],["impl UnwindSafe for Bounded",1,["bincode::config::limit::Bounded"]],["impl UnwindSafe for Infinite",1,["bincode::config::limit::Infinite"]],["impl UnwindSafe for AllowTrailing",1,["bincode::config::trailing::AllowTrailing"]],["impl UnwindSafe for RejectTrailing",1,["bincode::config::trailing::RejectTrailing"]],["impl UnwindSafe for DefaultOptions",1,["bincode::config::DefaultOptions"]],["impl<O, L> UnwindSafe for WithOtherLimit<O, L>where
        L: UnwindSafe,
        O: UnwindSafe,
    ",1,["bincode::config::WithOtherLimit"]],["impl<O, E> UnwindSafe for WithOtherEndian<O, E>where
        E: UnwindSafe,
        O: UnwindSafe,
    ",1,["bincode::config::WithOtherEndian"]],["impl<O, I> UnwindSafe for WithOtherIntEncoding<O, I>where
        I: UnwindSafe,
        O: UnwindSafe,
    ",1,["bincode::config::WithOtherIntEncoding"]],["impl<O, T> UnwindSafe for WithOtherTrailing<O, T>where
        O: UnwindSafe,
        T: UnwindSafe,
    ",1,["bincode::config::WithOtherTrailing"]],["impl<'storage> UnwindSafe for SliceReader<'storage>",1,["bincode::de::read::SliceReader"]],["impl<R> UnwindSafe for IoReader<R>where
        R: UnwindSafe,
    ",1,["bincode::de::read::IoReader"]],["impl<R, O> UnwindSafe for Deserializer<R, O>where
        O: UnwindSafe,
        R: UnwindSafe,
    ",1,["bincode::de::Deserializer"]],["impl !UnwindSafe for ErrorKind",1,["bincode::error::ErrorKind"]],["impl<W, O> UnwindSafe for Serializer<W, O>where
        O: UnwindSafe,
        W: UnwindSafe,
    ",1,["bincode::ser::Serializer"]]], -"block_buffer":[["impl UnwindSafe for Eager",1,["block_buffer::Eager"]],["impl UnwindSafe for Lazy",1,["block_buffer::Lazy"]],["impl UnwindSafe for Error",1,["block_buffer::Error"]],["impl<BlockSize, Kind> UnwindSafe for BlockBuffer<BlockSize, Kind>where
        Kind: UnwindSafe,
        <BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
    ",1,["block_buffer::BlockBuffer"]]], -"byteorder":[["impl UnwindSafe for BigEndian",1,["byteorder::BigEndian"]],["impl UnwindSafe for LittleEndian",1,["byteorder::LittleEndian"]]], -"bytes":[["impl<T, U> UnwindSafe for Chain<T, U>where
        T: UnwindSafe,
        U: UnwindSafe,
    ",1,["bytes::buf::chain::Chain"]],["impl<T> UnwindSafe for IntoIter<T>where
        T: UnwindSafe,
    ",1,["bytes::buf::iter::IntoIter"]],["impl<T> UnwindSafe for Limit<T>where
        T: UnwindSafe,
    ",1,["bytes::buf::limit::Limit"]],["impl<B> UnwindSafe for Reader<B>where
        B: UnwindSafe,
    ",1,["bytes::buf::reader::Reader"]],["impl<T> UnwindSafe for Take<T>where
        T: UnwindSafe,
    ",1,["bytes::buf::take::Take"]],["impl UnwindSafe for UninitSlice",1,["bytes::buf::uninit_slice::UninitSlice"]],["impl<B> UnwindSafe for Writer<B>where
        B: UnwindSafe,
    ",1,["bytes::buf::writer::Writer"]],["impl UnwindSafe for Bytes",1,["bytes::bytes::Bytes"]],["impl UnwindSafe for BytesMut",1,["bytes::bytes_mut::BytesMut"]]], -"crc":[["impl<W> UnwindSafe for Crc<W>where
        W: UnwindSafe + RefUnwindSafe,
    ",1,["crc::Crc"]],["impl<'a, W> UnwindSafe for Digest<'a, W>where
        W: UnwindSafe + RefUnwindSafe,
    ",1,["crc::Digest"]]], -"crc_catalog":[["impl<W> UnwindSafe for Algorithm<W>where
        W: UnwindSafe,
    ",1,["crc_catalog::Algorithm"]]], -"crossbeam_channel":[["impl<'a, T> UnwindSafe for Iter<'a, T>",1,["crossbeam_channel::channel::Iter"]],["impl<'a, T> UnwindSafe for TryIter<'a, T>",1,["crossbeam_channel::channel::TryIter"]],["impl<T> UnwindSafe for IntoIter<T>",1,["crossbeam_channel::channel::IntoIter"]],["impl<T> UnwindSafe for SendError<T>where
        T: UnwindSafe,
    ",1,["crossbeam_channel::err::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
        T: UnwindSafe,
    ",1,["crossbeam_channel::err::TrySendError"]],["impl<T> UnwindSafe for SendTimeoutError<T>where
        T: UnwindSafe,
    ",1,["crossbeam_channel::err::SendTimeoutError"]],["impl UnwindSafe for RecvError",1,["crossbeam_channel::err::RecvError"]],["impl UnwindSafe for TryRecvError",1,["crossbeam_channel::err::TryRecvError"]],["impl UnwindSafe for RecvTimeoutError",1,["crossbeam_channel::err::RecvTimeoutError"]],["impl UnwindSafe for TrySelectError",1,["crossbeam_channel::err::TrySelectError"]],["impl UnwindSafe for SelectTimeoutError",1,["crossbeam_channel::err::SelectTimeoutError"]],["impl UnwindSafe for TryReadyError",1,["crossbeam_channel::err::TryReadyError"]],["impl UnwindSafe for ReadyTimeoutError",1,["crossbeam_channel::err::ReadyTimeoutError"]],["impl<'a> !UnwindSafe for Select<'a>",1,["crossbeam_channel::select::Select"]],["impl<'a> UnwindSafe for SelectedOperation<'a>",1,["crossbeam_channel::select::SelectedOperation"]],["impl<T> UnwindSafe for Sender<T>"],["impl<T> UnwindSafe for Receiver<T>"]], -"crossbeam_utils":[["impl<T> UnwindSafe for CachePadded<T>where
        T: UnwindSafe,
    ",1,["crossbeam_utils::cache_padded::CachePadded"]],["impl UnwindSafe for Backoff",1,["crossbeam_utils::backoff::Backoff"]],["impl UnwindSafe for Parker",1,["crossbeam_utils::sync::parker::Parker"]],["impl UnwindSafe for Unparker",1,["crossbeam_utils::sync::parker::Unparker"]],["impl<'a, T: ?Sized> UnwindSafe for ShardedLockReadGuard<'a, T>where
        T: RefUnwindSafe,
    ",1,["crossbeam_utils::sync::sharded_lock::ShardedLockReadGuard"]],["impl<'a, T: ?Sized> UnwindSafe for ShardedLockWriteGuard<'a, T>",1,["crossbeam_utils::sync::sharded_lock::ShardedLockWriteGuard"]],["impl UnwindSafe for WaitGroup",1,["crossbeam_utils::sync::wait_group::WaitGroup"]],["impl<'env> !UnwindSafe for Scope<'env>",1,["crossbeam_utils::thread::Scope"]],["impl<'scope, 'env> UnwindSafe for ScopedThreadBuilder<'scope, 'env>",1,["crossbeam_utils::thread::ScopedThreadBuilder"]],["impl<'scope, T> UnwindSafe for ScopedJoinHandle<'scope, T>",1,["crossbeam_utils::thread::ScopedJoinHandle"]],["impl<T> UnwindSafe for AtomicCell<T>"],["impl<T: ?Sized> UnwindSafe for ShardedLock<T>"]], -"crypto_common":[["impl UnwindSafe for InvalidLength",1,["crypto_common::InvalidLength"]]], -"digest":[["impl<T, OutSize, O> UnwindSafe for CtVariableCoreWrapper<T, OutSize, O>where
        O: UnwindSafe,
        OutSize: UnwindSafe,
        T: UnwindSafe,
    ",1,["digest::core_api::ct_variable::CtVariableCoreWrapper"]],["impl<T> UnwindSafe for RtVariableCoreWrapper<T>where
        T: UnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
        <T as BufferKindUser>::BufferKind: UnwindSafe,
    ",1,["digest::core_api::rt_variable::RtVariableCoreWrapper"]],["impl<T> UnwindSafe for CoreWrapper<T>where
        T: UnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
        <T as BufferKindUser>::BufferKind: UnwindSafe,
    ",1,["digest::core_api::wrapper::CoreWrapper"]],["impl<T> UnwindSafe for XofReaderCoreWrapper<T>where
        T: UnwindSafe,
        <<T as BlockSizeUser>::BlockSize as ArrayLength<u8>>::ArrayType: UnwindSafe,
    ",1,["digest::core_api::xof_reader::XofReaderCoreWrapper"]],["impl UnwindSafe for TruncSide",1,["digest::core_api::TruncSide"]],["impl UnwindSafe for InvalidOutputSize",1,["digest::InvalidOutputSize"]],["impl UnwindSafe for InvalidBufferSize",1,["digest::InvalidBufferSize"]]], -"firewood":[["impl UnwindSafe for DiskBufferConfig",1,["firewood::storage::DiskBufferConfig"]],["impl UnwindSafe for WALConfig",1,["firewood::storage::WALConfig"]],["impl !UnwindSafe for DBError",1,["firewood::db::DBError"]],["impl UnwindSafe for DBRevConfig",1,["firewood::db::DBRevConfig"]],["impl UnwindSafe for DBConfig",1,["firewood::db::DBConfig"]],["impl !UnwindSafe for DBRev",1,["firewood::db::DBRev"]],["impl !UnwindSafe for DB",1,["firewood::db::DB"]],["impl<'a> !UnwindSafe for Revision<'a>",1,["firewood::db::Revision"]],["impl<'a> !UnwindSafe for WriteBatch<'a>",1,["firewood::db::WriteBatch"]],["impl !UnwindSafe for MerkleError",1,["firewood::merkle::MerkleError"]],["impl UnwindSafe for Hash",1,["firewood::merkle::Hash"]],["impl UnwindSafe for PartialPath",1,["firewood::merkle::PartialPath"]],["impl UnwindSafe for Node",1,["firewood::merkle::Node"]],["impl !UnwindSafe for Merkle",1,["firewood::merkle::Merkle"]],["impl<'a> !UnwindSafe for Ref<'a>",1,["firewood::merkle::Ref"]],["impl<'a> !UnwindSafe for RefMut<'a>",1,["firewood::merkle::RefMut"]],["impl UnwindSafe for IdTrans",1,["firewood::merkle::IdTrans"]],["impl UnwindSafe for Proof",1,["firewood::proof::Proof"]],["impl UnwindSafe for ProofError",1,["firewood::proof::ProofError"]],["impl UnwindSafe for SubProof",1,["firewood::proof::SubProof"]]], -"futures_channel":[["impl<T> !UnwindSafe for Sender<T>",1,["futures_channel::mpsc::Sender"]],["impl<T> !UnwindSafe for UnboundedSender<T>",1,["futures_channel::mpsc::UnboundedSender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["futures_channel::mpsc::Receiver"]],["impl<T> !UnwindSafe for UnboundedReceiver<T>",1,["futures_channel::mpsc::UnboundedReceiver"]],["impl UnwindSafe for SendError",1,["futures_channel::mpsc::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
        T: UnwindSafe,
    ",1,["futures_channel::mpsc::TrySendError"]],["impl UnwindSafe for TryRecvError",1,["futures_channel::mpsc::TryRecvError"]],["impl<T> !UnwindSafe for Receiver<T>",1,["futures_channel::oneshot::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["futures_channel::oneshot::Sender"]],["impl<'a, T> !UnwindSafe for Cancellation<'a, T>",1,["futures_channel::oneshot::Cancellation"]],["impl UnwindSafe for Canceled",1,["futures_channel::oneshot::Canceled"]]], -"futures_executor":[["impl !UnwindSafe for LocalPool",1,["futures_executor::local_pool::LocalPool"]],["impl !UnwindSafe for LocalSpawner",1,["futures_executor::local_pool::LocalSpawner"]],["impl<S> UnwindSafe for BlockingStream<S>where
        S: UnwindSafe,
    ",1,["futures_executor::local_pool::BlockingStream"]],["impl UnwindSafe for Enter",1,["futures_executor::enter::Enter"]],["impl UnwindSafe for EnterError",1,["futures_executor::enter::EnterError"]]], -"futures_task":[["impl UnwindSafe for SpawnError",1,["futures_task::spawn::SpawnError"]],["impl<'a> UnwindSafe for WakerRef<'a>",1,["futures_task::waker_ref::WakerRef"]],["impl<'a, T> !UnwindSafe for LocalFutureObj<'a, T>",1,["futures_task::future_obj::LocalFutureObj"]],["impl<'a, T> !UnwindSafe for FutureObj<'a, T>",1,["futures_task::future_obj::FutureObj"]]], -"futures_util":[["impl<Fut> UnwindSafe for Fuse<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::fuse::Fuse"]],["impl<Fut> UnwindSafe for CatchUnwind<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::catch_unwind::CatchUnwind"]],["impl<T> !UnwindSafe for RemoteHandle<T>",1,["futures_util::future::future::remote_handle::RemoteHandle"]],["impl<Fut> !UnwindSafe for Remote<Fut>",1,["futures_util::future::future::remote_handle::Remote"]],["impl<Fut> !UnwindSafe for Shared<Fut>",1,["futures_util::future::future::shared::Shared"]],["impl<Fut> !UnwindSafe for WeakShared<Fut>",1,["futures_util::future::future::shared::WeakShared"]],["impl<F> UnwindSafe for Flatten<F>where
        F: UnwindSafe,
        <F as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::future::Flatten"]],["impl<F> UnwindSafe for FlattenStream<F>where
        F: UnwindSafe,
        <F as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::future::FlattenStream"]],["impl<Fut, F> UnwindSafe for Map<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::Map"]],["impl<F> UnwindSafe for IntoStream<F>where
        F: UnwindSafe,
    ",1,["futures_util::future::future::IntoStream"]],["impl<Fut, T> UnwindSafe for MapInto<Fut, T>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::MapInto"]],["impl<Fut1, Fut2, F> UnwindSafe for Then<Fut1, Fut2, F>where
        F: UnwindSafe,
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
    ",1,["futures_util::future::future::Then"]],["impl<Fut, F> UnwindSafe for Inspect<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::Inspect"]],["impl<Fut> UnwindSafe for NeverError<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::NeverError"]],["impl<Fut> UnwindSafe for UnitError<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::future::UnitError"]],["impl<Fut> UnwindSafe for IntoFuture<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::into_future::IntoFuture"]],["impl<Fut1, Fut2> UnwindSafe for TryFlatten<Fut1, Fut2>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
    ",1,["futures_util::future::try_future::TryFlatten"]],["impl<Fut> UnwindSafe for TryFlattenStream<Fut>where
        Fut: UnwindSafe,
        <Fut as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_future::TryFlattenStream"]],["impl<Fut, Si> UnwindSafe for FlattenSink<Fut, Si>where
        Fut: UnwindSafe,
        Si: UnwindSafe,
    ",1,["futures_util::future::try_future::FlattenSink"]],["impl<Fut1, Fut2, F> UnwindSafe for AndThen<Fut1, Fut2, F>where
        F: UnwindSafe,
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
    ",1,["futures_util::future::try_future::AndThen"]],["impl<Fut1, Fut2, F> UnwindSafe for OrElse<Fut1, Fut2, F>where
        F: UnwindSafe,
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
    ",1,["futures_util::future::try_future::OrElse"]],["impl<Fut, E> UnwindSafe for ErrInto<Fut, E>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::ErrInto"]],["impl<Fut, E> UnwindSafe for OkInto<Fut, E>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::OkInto"]],["impl<Fut, F> UnwindSafe for InspectOk<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::InspectOk"]],["impl<Fut, F> UnwindSafe for InspectErr<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::InspectErr"]],["impl<Fut, F> UnwindSafe for MapOk<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::MapOk"]],["impl<Fut, F> UnwindSafe for MapErr<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::MapErr"]],["impl<Fut, F, G> UnwindSafe for MapOkOrElse<Fut, F, G>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        G: UnwindSafe,
    ",1,["futures_util::future::try_future::MapOkOrElse"]],["impl<Fut, F> UnwindSafe for UnwrapOrElse<Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
    ",1,["futures_util::future::try_future::UnwrapOrElse"]],["impl<F> UnwindSafe for Lazy<F>where
        F: UnwindSafe,
    ",1,["futures_util::future::lazy::Lazy"]],["impl<T> UnwindSafe for Pending<T>where
        T: UnwindSafe,
    ",1,["futures_util::future::pending::Pending"]],["impl<Fut> UnwindSafe for MaybeDone<Fut>where
        Fut: UnwindSafe,
        <Fut as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::maybe_done::MaybeDone"]],["impl<Fut> UnwindSafe for TryMaybeDone<Fut>where
        Fut: UnwindSafe,
        <Fut as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_maybe_done::TryMaybeDone"]],["impl<F> UnwindSafe for OptionFuture<F>where
        F: UnwindSafe,
    ",1,["futures_util::future::option::OptionFuture"]],["impl<F> UnwindSafe for PollFn<F>where
        F: UnwindSafe,
    ",1,["futures_util::future::poll_fn::PollFn"]],["impl<T> UnwindSafe for PollImmediate<T>where
        T: UnwindSafe,
    ",1,["futures_util::future::poll_immediate::PollImmediate"]],["impl<T> UnwindSafe for Ready<T>where
        T: UnwindSafe,
    ",1,["futures_util::future::ready::Ready"]],["impl<Fut1, Fut2> UnwindSafe for Join<Fut1, Fut2>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        <Fut1 as Future>::Output: UnwindSafe,
        <Fut2 as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::join::Join"]],["impl<Fut1, Fut2, Fut3> UnwindSafe for Join3<Fut1, Fut2, Fut3>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        <Fut1 as Future>::Output: UnwindSafe,
        <Fut2 as Future>::Output: UnwindSafe,
        <Fut3 as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::join::Join3"]],["impl<Fut1, Fut2, Fut3, Fut4> UnwindSafe for Join4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        Fut4: UnwindSafe,
        <Fut1 as Future>::Output: UnwindSafe,
        <Fut2 as Future>::Output: UnwindSafe,
        <Fut3 as Future>::Output: UnwindSafe,
        <Fut4 as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::join::Join4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> UnwindSafe for Join5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        Fut4: UnwindSafe,
        Fut5: UnwindSafe,
        <Fut1 as Future>::Output: UnwindSafe,
        <Fut2 as Future>::Output: UnwindSafe,
        <Fut3 as Future>::Output: UnwindSafe,
        <Fut4 as Future>::Output: UnwindSafe,
        <Fut5 as Future>::Output: UnwindSafe,
    ",1,["futures_util::future::join::Join5"]],["impl<F> !UnwindSafe for JoinAll<F>",1,["futures_util::future::join_all::JoinAll"]],["impl<A, B> UnwindSafe for Select<A, B>where
        A: UnwindSafe,
        B: UnwindSafe,
    ",1,["futures_util::future::select::Select"]],["impl<Fut> UnwindSafe for SelectAll<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::select_all::SelectAll"]],["impl<Fut1, Fut2> UnwindSafe for TryJoin<Fut1, Fut2>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        <Fut1 as TryFuture>::Ok: UnwindSafe,
        <Fut2 as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin"]],["impl<Fut1, Fut2, Fut3> UnwindSafe for TryJoin3<Fut1, Fut2, Fut3>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        <Fut1 as TryFuture>::Ok: UnwindSafe,
        <Fut2 as TryFuture>::Ok: UnwindSafe,
        <Fut3 as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin3"]],["impl<Fut1, Fut2, Fut3, Fut4> UnwindSafe for TryJoin4<Fut1, Fut2, Fut3, Fut4>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        Fut4: UnwindSafe,
        <Fut1 as TryFuture>::Ok: UnwindSafe,
        <Fut2 as TryFuture>::Ok: UnwindSafe,
        <Fut3 as TryFuture>::Ok: UnwindSafe,
        <Fut4 as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin4"]],["impl<Fut1, Fut2, Fut3, Fut4, Fut5> UnwindSafe for TryJoin5<Fut1, Fut2, Fut3, Fut4, Fut5>where
        Fut1: UnwindSafe,
        Fut2: UnwindSafe,
        Fut3: UnwindSafe,
        Fut4: UnwindSafe,
        Fut5: UnwindSafe,
        <Fut1 as TryFuture>::Ok: UnwindSafe,
        <Fut2 as TryFuture>::Ok: UnwindSafe,
        <Fut3 as TryFuture>::Ok: UnwindSafe,
        <Fut4 as TryFuture>::Ok: UnwindSafe,
        <Fut5 as TryFuture>::Ok: UnwindSafe,
    ",1,["futures_util::future::try_join::TryJoin5"]],["impl<F> !UnwindSafe for TryJoinAll<F>",1,["futures_util::future::try_join_all::TryJoinAll"]],["impl<A, B> UnwindSafe for TrySelect<A, B>where
        A: UnwindSafe,
        B: UnwindSafe,
    ",1,["futures_util::future::try_select::TrySelect"]],["impl<Fut> UnwindSafe for SelectOk<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::future::select_ok::SelectOk"]],["impl<A, B> UnwindSafe for Either<A, B>where
        A: UnwindSafe,
        B: UnwindSafe,
    ",1,["futures_util::future::either::Either"]],["impl !UnwindSafe for AbortHandle",1,["futures_util::abortable::AbortHandle"]],["impl !UnwindSafe for AbortRegistration",1,["futures_util::abortable::AbortRegistration"]],["impl<T> !UnwindSafe for Abortable<T>",1,["futures_util::abortable::Abortable"]],["impl UnwindSafe for Aborted",1,["futures_util::abortable::Aborted"]],["impl<St1, St2> UnwindSafe for Chain<St1, St2>where
        St1: UnwindSafe,
        St2: UnwindSafe,
    ",1,["futures_util::stream::stream::chain::Chain"]],["impl<St, C> UnwindSafe for Collect<St, C>where
        C: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::collect::Collect"]],["impl<St, FromA, FromB> UnwindSafe for Unzip<St, FromA, FromB>where
        FromA: UnwindSafe,
        FromB: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::unzip::Unzip"]],["impl<St> UnwindSafe for Concat<St>where
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::concat::Concat"]],["impl<St> UnwindSafe for Cycle<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::cycle::Cycle"]],["impl<St> UnwindSafe for Enumerate<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::enumerate::Enumerate"]],["impl<St, Fut, F> UnwindSafe for Filter<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::filter::Filter"]],["impl<St, Fut, F> UnwindSafe for FilterMap<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::filter_map::FilterMap"]],["impl<St, Fut, T, F> UnwindSafe for Fold<St, Fut, T, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::stream::stream::fold::Fold"]],["impl<St, Fut, F> UnwindSafe for ForEach<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::for_each::ForEach"]],["impl<St> UnwindSafe for Fuse<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::fuse::Fuse"]],["impl<St> UnwindSafe for StreamFuture<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::into_future::StreamFuture"]],["impl<St, F> UnwindSafe for Map<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::map::Map"]],["impl<'a, St> !UnwindSafe for Next<'a, St>",1,["futures_util::stream::stream::next::Next"]],["impl<'a, St> !UnwindSafe for SelectNextSome<'a, St>",1,["futures_util::stream::stream::select_next_some::SelectNextSome"]],["impl<St> UnwindSafe for Peekable<St>where
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::peek::Peekable"]],["impl<'a, St> !UnwindSafe for Peek<'a, St>",1,["futures_util::stream::stream::peek::Peek"]],["impl<'a, St> !UnwindSafe for PeekMut<'a, St>",1,["futures_util::stream::stream::peek::PeekMut"]],["impl<'a, St, F> !UnwindSafe for NextIf<'a, St, F>",1,["futures_util::stream::stream::peek::NextIf"]],["impl<'a, St, T> !UnwindSafe for NextIfEq<'a, St, T>",1,["futures_util::stream::stream::peek::NextIfEq"]],["impl<St> UnwindSafe for Skip<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::skip::Skip"]],["impl<St, Fut, F> UnwindSafe for SkipWhile<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::skip_while::SkipWhile"]],["impl<St> UnwindSafe for Take<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::take::Take"]],["impl<St, Fut, F> UnwindSafe for TakeWhile<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::take_while::TakeWhile"]],["impl<St, Fut> UnwindSafe for TakeUntil<St, Fut>where
        Fut: UnwindSafe,
        St: UnwindSafe,
        <Fut as Future>::Output: UnwindSafe,
    ",1,["futures_util::stream::stream::take_until::TakeUntil"]],["impl<St, Fut, F> UnwindSafe for Then<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::then::Then"]],["impl<St1, St2> UnwindSafe for Zip<St1, St2>where
        St1: UnwindSafe,
        St2: UnwindSafe,
        <St1 as Stream>::Item: UnwindSafe,
        <St2 as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::zip::Zip"]],["impl<St> UnwindSafe for Chunks<St>where
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::chunks::Chunks"]],["impl<St> UnwindSafe for ReadyChunks<St>where
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::ready_chunks::ReadyChunks"]],["impl<St, S, Fut, F> UnwindSafe for Scan<St, S, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        S: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::scan::Scan"]],["impl<St> !UnwindSafe for BufferUnordered<St>",1,["futures_util::stream::stream::buffer_unordered::BufferUnordered"]],["impl<St> !UnwindSafe for Buffered<St>",1,["futures_util::stream::stream::buffered::Buffered"]],["impl<St, Fut, F> !UnwindSafe for ForEachConcurrent<St, Fut, F>",1,["futures_util::stream::stream::for_each_concurrent::ForEachConcurrent"]],["impl<S> !UnwindSafe for SplitStream<S>",1,["futures_util::stream::stream::split::SplitStream"]],["impl<S, Item> !UnwindSafe for SplitSink<S, Item>",1,["futures_util::stream::stream::split::SplitSink"]],["impl<T, Item> !UnwindSafe for ReuniteError<T, Item>",1,["futures_util::stream::stream::split::ReuniteError"]],["impl<St> UnwindSafe for CatchUnwind<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::catch_unwind::CatchUnwind"]],["impl<St> UnwindSafe for Flatten<St>where
        St: UnwindSafe,
        <St as Stream>::Item: UnwindSafe,
    ",1,["futures_util::stream::stream::Flatten"]],["impl<St, Si> UnwindSafe for Forward<St, Si>where
        Si: UnwindSafe,
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::stream::Forward"]],["impl<St, F> UnwindSafe for Inspect<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::stream::Inspect"]],["impl<St, U, F> UnwindSafe for FlatMap<St, U, F>where
        F: UnwindSafe,
        St: UnwindSafe,
        U: UnwindSafe,
    ",1,["futures_util::stream::stream::FlatMap"]],["impl<St, Fut, F> UnwindSafe for AndThen<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::and_then::AndThen"]],["impl<St> UnwindSafe for IntoStream<St>where
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::into_stream::IntoStream"]],["impl<St, Fut, F> UnwindSafe for OrElse<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::or_else::OrElse"]],["impl<'a, St> !UnwindSafe for TryNext<'a, St>",1,["futures_util::stream::try_stream::try_next::TryNext"]],["impl<St, Fut, F> UnwindSafe for TryForEach<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_for_each::TryForEach"]],["impl<St, Fut, F> UnwindSafe for TryFilter<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_filter::TryFilter"]],["impl<St, Fut, F> UnwindSafe for TryFilterMap<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_filter_map::TryFilterMap"]],["impl<St> UnwindSafe for TryFlatten<St>where
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_flatten::TryFlatten"]],["impl<St, C> UnwindSafe for TryCollect<St, C>where
        C: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_collect::TryCollect"]],["impl<St> UnwindSafe for TryConcat<St>where
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_concat::TryConcat"]],["impl<St> UnwindSafe for TryChunks<St>where
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunks"]],["impl<T, E> UnwindSafe for TryChunksError<T, E>where
        E: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_chunks::TryChunksError"]],["impl<St, Fut, T, F> UnwindSafe for TryFold<St, Fut, T, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_fold::TryFold"]],["impl<T, F, Fut> UnwindSafe for TryUnfold<T, F, Fut>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_unfold::TryUnfold"]],["impl<St, Fut, F> UnwindSafe for TrySkipWhile<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_skip_while::TrySkipWhile"]],["impl<St, Fut, F> UnwindSafe for TryTakeWhile<St, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::try_take_while::TryTakeWhile"]],["impl<St> !UnwindSafe for TryBufferUnordered<St>",1,["futures_util::stream::try_stream::try_buffer_unordered::TryBufferUnordered"]],["impl<St> !UnwindSafe for TryBuffered<St>",1,["futures_util::stream::try_stream::try_buffered::TryBuffered"]],["impl<St, Fut, F> !UnwindSafe for TryForEachConcurrent<St, Fut, F>",1,["futures_util::stream::try_stream::try_for_each_concurrent::TryForEachConcurrent"]],["impl<St> UnwindSafe for IntoAsyncRead<St>where
        St: UnwindSafe,
        <St as TryStream>::Ok: UnwindSafe,
    ",1,["futures_util::stream::try_stream::into_async_read::IntoAsyncRead"]],["impl<St, E> UnwindSafe for ErrInto<St, E>where
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::ErrInto"]],["impl<St, F> UnwindSafe for InspectOk<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::InspectOk"]],["impl<St, F> UnwindSafe for InspectErr<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::InspectErr"]],["impl<St, F> UnwindSafe for MapOk<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::MapOk"]],["impl<St, F> UnwindSafe for MapErr<St, F>where
        F: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::stream::try_stream::MapErr"]],["impl<I> UnwindSafe for Iter<I>where
        I: UnwindSafe,
    ",1,["futures_util::stream::iter::Iter"]],["impl<T> UnwindSafe for Repeat<T>where
        T: UnwindSafe,
    ",1,["futures_util::stream::repeat::Repeat"]],["impl<F> UnwindSafe for RepeatWith<F>where
        F: UnwindSafe,
    ",1,["futures_util::stream::repeat_with::RepeatWith"]],["impl<T> UnwindSafe for Empty<T>where
        T: UnwindSafe,
    ",1,["futures_util::stream::empty::Empty"]],["impl<Fut> UnwindSafe for Once<Fut>where
        Fut: UnwindSafe,
    ",1,["futures_util::stream::once::Once"]],["impl<T> UnwindSafe for Pending<T>where
        T: UnwindSafe,
    ",1,["futures_util::stream::pending::Pending"]],["impl<F> UnwindSafe for PollFn<F>where
        F: UnwindSafe,
    ",1,["futures_util::stream::poll_fn::PollFn"]],["impl<S> UnwindSafe for PollImmediate<S>where
        S: UnwindSafe,
    ",1,["futures_util::stream::poll_immediate::PollImmediate"]],["impl<St1, St2> UnwindSafe for Select<St1, St2>where
        St1: UnwindSafe,
        St2: UnwindSafe,
    ",1,["futures_util::stream::select::Select"]],["impl UnwindSafe for PollNext",1,["futures_util::stream::select_with_strategy::PollNext"]],["impl<St1, St2, Clos, State> UnwindSafe for SelectWithStrategy<St1, St2, Clos, State>where
        Clos: UnwindSafe,
        St1: UnwindSafe,
        St2: UnwindSafe,
        State: UnwindSafe,
    ",1,["futures_util::stream::select_with_strategy::SelectWithStrategy"]],["impl<T, F, Fut> UnwindSafe for Unfold<T, F, Fut>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::stream::unfold::Unfold"]],["impl<T> !UnwindSafe for FuturesOrdered<T>",1,["futures_util::stream::futures_ordered::FuturesOrdered"]],["impl<'a, Fut> !UnwindSafe for IterPinMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinMut"]],["impl<'a, Fut> !UnwindSafe for IterMut<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterMut"]],["impl<'a, Fut> !UnwindSafe for IterPinRef<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::IterPinRef"]],["impl<'a, Fut> !UnwindSafe for Iter<'a, Fut>",1,["futures_util::stream::futures_unordered::iter::Iter"]],["impl<Fut> !UnwindSafe for IntoIter<Fut>",1,["futures_util::stream::futures_unordered::iter::IntoIter"]],["impl<Fut> !UnwindSafe for FuturesUnordered<Fut>",1,["futures_util::stream::futures_unordered::FuturesUnordered"]],["impl<St> !UnwindSafe for SelectAll<St>",1,["futures_util::stream::select_all::SelectAll"]],["impl<'a, St> !UnwindSafe for Iter<'a, St>",1,["futures_util::stream::select_all::Iter"]],["impl<'a, St> !UnwindSafe for IterMut<'a, St>",1,["futures_util::stream::select_all::IterMut"]],["impl<St> !UnwindSafe for IntoIter<St>",1,["futures_util::stream::select_all::IntoIter"]],["impl<'a, Si, Item> !UnwindSafe for Close<'a, Si, Item>",1,["futures_util::sink::close::Close"]],["impl<T> UnwindSafe for Drain<T>where
        T: UnwindSafe,
    ",1,["futures_util::sink::drain::Drain"]],["impl<Si1, Si2> UnwindSafe for Fanout<Si1, Si2>where
        Si1: UnwindSafe,
        Si2: UnwindSafe,
    ",1,["futures_util::sink::fanout::Fanout"]],["impl<'a, Si, Item> !UnwindSafe for Feed<'a, Si, Item>",1,["futures_util::sink::feed::Feed"]],["impl<'a, Si, Item> !UnwindSafe for Flush<'a, Si, Item>",1,["futures_util::sink::flush::Flush"]],["impl<Si, Item, E> UnwindSafe for SinkErrInto<Si, Item, E>where
        Si: UnwindSafe,
    ",1,["futures_util::sink::err_into::SinkErrInto"]],["impl<Si, F> UnwindSafe for SinkMapErr<Si, F>where
        F: UnwindSafe,
        Si: UnwindSafe,
    ",1,["futures_util::sink::map_err::SinkMapErr"]],["impl<'a, Si, Item> !UnwindSafe for Send<'a, Si, Item>",1,["futures_util::sink::send::Send"]],["impl<'a, Si, St> !UnwindSafe for SendAll<'a, Si, St>",1,["futures_util::sink::send_all::SendAll"]],["impl<T, F, R> UnwindSafe for Unfold<T, F, R>where
        F: UnwindSafe,
        R: UnwindSafe,
        T: UnwindSafe,
    ",1,["futures_util::sink::unfold::Unfold"]],["impl<Si, Item, U, Fut, F> UnwindSafe for With<Si, Item, U, Fut, F>where
        F: UnwindSafe,
        Fut: UnwindSafe,
        Si: UnwindSafe,
    ",1,["futures_util::sink::with::With"]],["impl<Si, Item, U, St, F> UnwindSafe for WithFlatMap<Si, Item, U, St, F>where
        F: UnwindSafe,
        Item: UnwindSafe,
        Si: UnwindSafe,
        St: UnwindSafe,
    ",1,["futures_util::sink::with_flat_map::WithFlatMap"]],["impl<Si, Item> UnwindSafe for Buffer<Si, Item>where
        Item: UnwindSafe,
        Si: UnwindSafe,
    ",1,["futures_util::sink::buffer::Buffer"]],["impl<T> UnwindSafe for AllowStdIo<T>where
        T: UnwindSafe,
    ",1,["futures_util::io::allow_std::AllowStdIo"]],["impl<R> UnwindSafe for BufReader<R>where
        R: UnwindSafe,
    ",1,["futures_util::io::buf_reader::BufReader"]],["impl<'a, R> !UnwindSafe for SeeKRelative<'a, R>",1,["futures_util::io::buf_reader::SeeKRelative"]],["impl<W> UnwindSafe for BufWriter<W>where
        W: UnwindSafe,
    ",1,["futures_util::io::buf_writer::BufWriter"]],["impl<W> UnwindSafe for LineWriter<W>where
        W: UnwindSafe,
    ",1,["futures_util::io::line_writer::LineWriter"]],["impl<T, U> UnwindSafe for Chain<T, U>where
        T: UnwindSafe,
        U: UnwindSafe,
    ",1,["futures_util::io::chain::Chain"]],["impl<'a, W> !UnwindSafe for Close<'a, W>",1,["futures_util::io::close::Close"]],["impl<'a, R, W> !UnwindSafe for Copy<'a, R, W>",1,["futures_util::io::copy::Copy"]],["impl<'a, R, W> !UnwindSafe for CopyBuf<'a, R, W>",1,["futures_util::io::copy_buf::CopyBuf"]],["impl<'a, R, W> !UnwindSafe for CopyBufAbortable<'a, R, W>",1,["futures_util::io::copy_buf_abortable::CopyBufAbortable"]],["impl<T> UnwindSafe for Cursor<T>where
        T: UnwindSafe,
    ",1,["futures_util::io::cursor::Cursor"]],["impl UnwindSafe for Empty",1,["futures_util::io::empty::Empty"]],["impl<'a, R> !UnwindSafe for FillBuf<'a, R>",1,["futures_util::io::fill_buf::FillBuf"]],["impl<'a, W> !UnwindSafe for Flush<'a, W>",1,["futures_util::io::flush::Flush"]],["impl<W, Item> UnwindSafe for IntoSink<W, Item>where
        Item: UnwindSafe,
        W: UnwindSafe,
    ",1,["futures_util::io::into_sink::IntoSink"]],["impl<R> UnwindSafe for Lines<R>where
        R: UnwindSafe,
    ",1,["futures_util::io::lines::Lines"]],["impl<'a, R> !UnwindSafe for Read<'a, R>",1,["futures_util::io::read::Read"]],["impl<'a, R> !UnwindSafe for ReadVectored<'a, R>",1,["futures_util::io::read_vectored::ReadVectored"]],["impl<'a, R> !UnwindSafe for ReadExact<'a, R>",1,["futures_util::io::read_exact::ReadExact"]],["impl<'a, R> !UnwindSafe for ReadLine<'a, R>",1,["futures_util::io::read_line::ReadLine"]],["impl<'a, R> !UnwindSafe for ReadToEnd<'a, R>",1,["futures_util::io::read_to_end::ReadToEnd"]],["impl<'a, R> !UnwindSafe for ReadToString<'a, R>",1,["futures_util::io::read_to_string::ReadToString"]],["impl<'a, R> !UnwindSafe for ReadUntil<'a, R>",1,["futures_util::io::read_until::ReadUntil"]],["impl UnwindSafe for Repeat",1,["futures_util::io::repeat::Repeat"]],["impl<'a, S> !UnwindSafe for Seek<'a, S>",1,["futures_util::io::seek::Seek"]],["impl UnwindSafe for Sink",1,["futures_util::io::sink::Sink"]],["impl<T> !UnwindSafe for ReadHalf<T>",1,["futures_util::io::split::ReadHalf"]],["impl<T> !UnwindSafe for WriteHalf<T>",1,["futures_util::io::split::WriteHalf"]],["impl<T> !UnwindSafe for ReuniteError<T>",1,["futures_util::io::split::ReuniteError"]],["impl<R> UnwindSafe for Take<R>where
        R: UnwindSafe,
    ",1,["futures_util::io::take::Take"]],["impl<T> UnwindSafe for Window<T>where
        T: UnwindSafe,
    ",1,["futures_util::io::window::Window"]],["impl<'a, W> !UnwindSafe for Write<'a, W>",1,["futures_util::io::write::Write"]],["impl<'a, W> !UnwindSafe for WriteVectored<'a, W>",1,["futures_util::io::write_vectored::WriteVectored"]],["impl<'a, W> !UnwindSafe for WriteAll<'a, W>",1,["futures_util::io::write_all::WriteAll"]],["impl<T: ?Sized> UnwindSafe for Mutex<T>where
        T: UnwindSafe,
    ",1,["futures_util::lock::mutex::Mutex"]],["impl<T> !UnwindSafe for OwnedMutexLockFuture<T>",1,["futures_util::lock::mutex::OwnedMutexLockFuture"]],["impl<T> !UnwindSafe for OwnedMutexGuard<T>",1,["futures_util::lock::mutex::OwnedMutexGuard"]],["impl<'a, T> !UnwindSafe for MutexLockFuture<'a, T>",1,["futures_util::lock::mutex::MutexLockFuture"]],["impl<'a, T> !UnwindSafe for MutexGuard<'a, T>",1,["futures_util::lock::mutex::MutexGuard"]],["impl<'a, T, U> !UnwindSafe for MappedMutexGuard<'a, T, U>",1,["futures_util::lock::mutex::MappedMutexGuard"]]], -"generic_array":[["impl<T, N> UnwindSafe for GenericArrayIter<T, N>where
        <N as ArrayLength<T>>::ArrayType: UnwindSafe,
    ",1,["generic_array::iter::GenericArrayIter"]],["impl<T, U> UnwindSafe for GenericArray<T, U>where
        <U as ArrayLength<T>>::ArrayType: UnwindSafe,
    ",1,["generic_array::GenericArray"]]], -"getrandom":[["impl UnwindSafe for Error",1,["getrandom::error::Error"]]], -"growthring":[["impl UnwindSafe for WALRingId",1,["growthring::wal::WALRingId"]],["impl<F> !UnwindSafe for WALWriter<F>",1,["growthring::wal::WALWriter"]],["impl UnwindSafe for RecoverPolicy",1,["growthring::wal::RecoverPolicy"]],["impl UnwindSafe for WALLoader",1,["growthring::wal::WALLoader"]],["impl !UnwindSafe for WALFileAIO",1,["growthring::WALFileAIO"]],["impl !UnwindSafe for WALStoreAIO",1,["growthring::WALStoreAIO"]]], -"hashbrown":[["impl<K, V, S, A> UnwindSafe for HashMap<K, V, S, A>where
        A: UnwindSafe,
        K: UnwindSafe,
        S: UnwindSafe,
        V: UnwindSafe,
    ",1,["hashbrown::map::HashMap"]],["impl<'a, K, V> UnwindSafe for Iter<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Iter"]],["impl<'a, K, V> !UnwindSafe for IterMut<'a, K, V>",1,["hashbrown::map::IterMut"]],["impl<K, V, A> UnwindSafe for IntoIter<K, V, A>where
        A: UnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::map::IntoIter"]],["impl<K, V, A> UnwindSafe for IntoKeys<K, V, A>where
        A: UnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::map::IntoKeys"]],["impl<K, V, A> UnwindSafe for IntoValues<K, V, A>where
        A: UnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::map::IntoValues"]],["impl<'a, K, V> UnwindSafe for Keys<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Keys"]],["impl<'a, K, V> UnwindSafe for Values<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::Values"]],["impl<'a, K, V, A> UnwindSafe for Drain<'a, K, V, A>where
        A: UnwindSafe + RefUnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::map::Drain"]],["impl<'a, K, V, F, A = Global> !UnwindSafe for DrainFilter<'a, K, V, F, A>",1,["hashbrown::map::DrainFilter"]],["impl<'a, K, V> !UnwindSafe for ValuesMut<'a, K, V>",1,["hashbrown::map::ValuesMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawEntryBuilderMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryBuilderMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawEntryMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawOccupiedEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawOccupiedEntryMut"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for RawVacantEntryMut<'a, K, V, S, A>",1,["hashbrown::map::RawVacantEntryMut"]],["impl<'a, K, V, S, A> UnwindSafe for RawEntryBuilder<'a, K, V, S, A>where
        A: RefUnwindSafe,
        K: RefUnwindSafe,
        S: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["hashbrown::map::RawEntryBuilder"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for Entry<'a, K, V, S, A>",1,["hashbrown::map::Entry"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for OccupiedEntry<'a, K, V, S, A>",1,["hashbrown::map::OccupiedEntry"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for VacantEntry<'a, K, V, S, A>",1,["hashbrown::map::VacantEntry"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for EntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::EntryRef"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for OccupiedEntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::OccupiedEntryRef"]],["impl<'a, 'b, K, Q, V, S, A = Global> !UnwindSafe for VacantEntryRef<'a, 'b, K, Q, V, S, A>",1,["hashbrown::map::VacantEntryRef"]],["impl<'a, K, V, S, A = Global> !UnwindSafe for OccupiedError<'a, K, V, S, A>",1,["hashbrown::map::OccupiedError"]],["impl<T, S, A> UnwindSafe for HashSet<T, S, A>where
        A: UnwindSafe,
        S: UnwindSafe,
        T: UnwindSafe,
    ",1,["hashbrown::set::HashSet"]],["impl<'a, K> UnwindSafe for Iter<'a, K>where
        K: RefUnwindSafe,
    ",1,["hashbrown::set::Iter"]],["impl<K, A> UnwindSafe for IntoIter<K, A>where
        A: UnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::set::IntoIter"]],["impl<'a, K, A> UnwindSafe for Drain<'a, K, A>where
        A: UnwindSafe + RefUnwindSafe,
        K: UnwindSafe + RefUnwindSafe,
    ",1,["hashbrown::set::Drain"]],["impl<'a, K, F, A = Global> !UnwindSafe for DrainFilter<'a, K, F, A>",1,["hashbrown::set::DrainFilter"]],["impl<'a, T, S, A> UnwindSafe for Intersection<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Intersection"]],["impl<'a, T, S, A> UnwindSafe for Difference<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Difference"]],["impl<'a, T, S, A> UnwindSafe for SymmetricDifference<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::SymmetricDifference"]],["impl<'a, T, S, A> UnwindSafe for Union<'a, T, S, A>where
        A: RefUnwindSafe,
        S: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["hashbrown::set::Union"]],["impl<'a, T, S, A = Global> !UnwindSafe for Entry<'a, T, S, A>",1,["hashbrown::set::Entry"]],["impl<'a, T, S, A = Global> !UnwindSafe for OccupiedEntry<'a, T, S, A>",1,["hashbrown::set::OccupiedEntry"]],["impl<'a, T, S, A = Global> !UnwindSafe for VacantEntry<'a, T, S, A>",1,["hashbrown::set::VacantEntry"]],["impl UnwindSafe for TryReserveError",1,["hashbrown::TryReserveError"]]], -"heck":[["impl<T> UnwindSafe for AsKebabCase<T>where
        T: UnwindSafe,
    ",1,["heck::kebab::AsKebabCase"]],["impl<T> UnwindSafe for AsLowerCamelCase<T>where
        T: UnwindSafe,
    ",1,["heck::lower_camel::AsLowerCamelCase"]],["impl<T> UnwindSafe for AsShoutyKebabCase<T>where
        T: UnwindSafe,
    ",1,["heck::shouty_kebab::AsShoutyKebabCase"]],["impl<T> UnwindSafe for AsShoutySnakeCase<T>where
        T: UnwindSafe,
    ",1,["heck::shouty_snake::AsShoutySnakeCase"]],["impl<T> UnwindSafe for AsSnakeCase<T>where
        T: UnwindSafe,
    ",1,["heck::snake::AsSnakeCase"]],["impl<T> UnwindSafe for AsTitleCase<T>where
        T: UnwindSafe,
    ",1,["heck::title::AsTitleCase"]],["impl<T> UnwindSafe for AsUpperCamelCase<T>where
        T: UnwindSafe,
    ",1,["heck::upper_camel::AsUpperCamelCase"]]], -"hex":[["impl UnwindSafe for FromHexError",1,["hex::error::FromHexError"]]], -"libc":[["impl UnwindSafe for statvfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::statvfs"]],["impl UnwindSafe for max_align_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::max_align_t"]],["impl UnwindSafe for clone_args",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::align::clone_args"]],["impl UnwindSafe for sigaction",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::sigaction"]],["impl UnwindSafe for statfs",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs"]],["impl UnwindSafe for flock",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock"]],["impl UnwindSafe for flock64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::flock64"]],["impl UnwindSafe for siginfo_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::siginfo_t"]],["impl UnwindSafe for stack_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stack_t"]],["impl UnwindSafe for stat",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat"]],["impl UnwindSafe for stat64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::stat64"]],["impl UnwindSafe for statfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statfs64"]],["impl UnwindSafe for statvfs64",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::statvfs64"]],["impl UnwindSafe for pthread_attr_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::pthread_attr_t"]],["impl UnwindSafe for _libc_fpxreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpxreg"]],["impl UnwindSafe for _libc_xmmreg",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_xmmreg"]],["impl UnwindSafe for _libc_fpstate",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::_libc_fpstate"]],["impl UnwindSafe for user_regs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_regs_struct"]],["impl UnwindSafe for user",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user"]],["impl UnwindSafe for mcontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::mcontext_t"]],["impl UnwindSafe for ipc_perm",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ipc_perm"]],["impl UnwindSafe for shmid_ds",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::shmid_ds"]],["impl UnwindSafe for seccomp_notif_sizes",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::seccomp_notif_sizes"]],["impl UnwindSafe for ptrace_rseq_configuration",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ptrace_rseq_configuration"]],["impl UnwindSafe for user_fpregs_struct",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::user_fpregs_struct"]],["impl UnwindSafe for ucontext_t",1,["libc::unix::linux_like::linux::gnu::b64::x86_64::ucontext_t"]],["impl UnwindSafe for sigset_t",1,["libc::unix::linux_like::linux::gnu::b64::sigset_t"]],["impl UnwindSafe for sysinfo",1,["libc::unix::linux_like::linux::gnu::b64::sysinfo"]],["impl UnwindSafe for msqid_ds",1,["libc::unix::linux_like::linux::gnu::b64::msqid_ds"]],["impl UnwindSafe for semid_ds",1,["libc::unix::linux_like::linux::gnu::b64::semid_ds"]],["impl UnwindSafe for sem_t",1,["libc::unix::linux_like::linux::gnu::align::sem_t"]],["impl UnwindSafe for statx",1,["libc::unix::linux_like::linux::gnu::statx"]],["impl UnwindSafe for statx_timestamp",1,["libc::unix::linux_like::linux::gnu::statx_timestamp"]],["impl UnwindSafe for aiocb",1,["libc::unix::linux_like::linux::gnu::aiocb"]],["impl UnwindSafe for __exit_status",1,["libc::unix::linux_like::linux::gnu::__exit_status"]],["impl UnwindSafe for __timeval",1,["libc::unix::linux_like::linux::gnu::__timeval"]],["impl UnwindSafe for glob64_t",1,["libc::unix::linux_like::linux::gnu::glob64_t"]],["impl UnwindSafe for msghdr",1,["libc::unix::linux_like::linux::gnu::msghdr"]],["impl UnwindSafe for cmsghdr",1,["libc::unix::linux_like::linux::gnu::cmsghdr"]],["impl UnwindSafe for termios",1,["libc::unix::linux_like::linux::gnu::termios"]],["impl UnwindSafe for mallinfo",1,["libc::unix::linux_like::linux::gnu::mallinfo"]],["impl UnwindSafe for mallinfo2",1,["libc::unix::linux_like::linux::gnu::mallinfo2"]],["impl UnwindSafe for nl_pktinfo",1,["libc::unix::linux_like::linux::gnu::nl_pktinfo"]],["impl UnwindSafe for nl_mmap_req",1,["libc::unix::linux_like::linux::gnu::nl_mmap_req"]],["impl UnwindSafe for nl_mmap_hdr",1,["libc::unix::linux_like::linux::gnu::nl_mmap_hdr"]],["impl UnwindSafe for rtentry",1,["libc::unix::linux_like::linux::gnu::rtentry"]],["impl UnwindSafe for timex",1,["libc::unix::linux_like::linux::gnu::timex"]],["impl UnwindSafe for ntptimeval",1,["libc::unix::linux_like::linux::gnu::ntptimeval"]],["impl UnwindSafe for regex_t",1,["libc::unix::linux_like::linux::gnu::regex_t"]],["impl UnwindSafe for Elf64_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf64_Chdr"]],["impl UnwindSafe for Elf32_Chdr",1,["libc::unix::linux_like::linux::gnu::Elf32_Chdr"]],["impl UnwindSafe for seminfo",1,["libc::unix::linux_like::linux::gnu::seminfo"]],["impl UnwindSafe for ptrace_peeksiginfo_args",1,["libc::unix::linux_like::linux::gnu::ptrace_peeksiginfo_args"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_entry",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_entry"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_exit",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_exit"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_seccomp",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_seccomp"]],["impl UnwindSafe for ptrace_syscall_info",1,["libc::unix::linux_like::linux::gnu::ptrace_syscall_info"]],["impl UnwindSafe for __c_anonymous_ptrace_syscall_info_data",1,["libc::unix::linux_like::linux::gnu::__c_anonymous_ptrace_syscall_info_data"]],["impl UnwindSafe for utmpx",1,["libc::unix::linux_like::linux::gnu::utmpx"]],["impl UnwindSafe for termios2",1,["libc::unix::linux_like::linux::arch::generic::termios2"]],["impl UnwindSafe for open_how",1,["libc::unix::linux_like::linux::non_exhaustive::open_how"]],["impl UnwindSafe for fpos64_t",1,["libc::unix::linux_like::linux::fpos64_t"]],["impl UnwindSafe for rlimit64",1,["libc::unix::linux_like::linux::rlimit64"]],["impl UnwindSafe for glob_t",1,["libc::unix::linux_like::linux::glob_t"]],["impl UnwindSafe for passwd",1,["libc::unix::linux_like::linux::passwd"]],["impl UnwindSafe for spwd",1,["libc::unix::linux_like::linux::spwd"]],["impl UnwindSafe for dqblk",1,["libc::unix::linux_like::linux::dqblk"]],["impl UnwindSafe for signalfd_siginfo",1,["libc::unix::linux_like::linux::signalfd_siginfo"]],["impl UnwindSafe for itimerspec",1,["libc::unix::linux_like::linux::itimerspec"]],["impl UnwindSafe for fsid_t",1,["libc::unix::linux_like::linux::fsid_t"]],["impl UnwindSafe for packet_mreq",1,["libc::unix::linux_like::linux::packet_mreq"]],["impl UnwindSafe for cpu_set_t",1,["libc::unix::linux_like::linux::cpu_set_t"]],["impl UnwindSafe for if_nameindex",1,["libc::unix::linux_like::linux::if_nameindex"]],["impl UnwindSafe for msginfo",1,["libc::unix::linux_like::linux::msginfo"]],["impl UnwindSafe for sembuf",1,["libc::unix::linux_like::linux::sembuf"]],["impl UnwindSafe for input_event",1,["libc::unix::linux_like::linux::input_event"]],["impl UnwindSafe for input_id",1,["libc::unix::linux_like::linux::input_id"]],["impl UnwindSafe for input_absinfo",1,["libc::unix::linux_like::linux::input_absinfo"]],["impl UnwindSafe for input_keymap_entry",1,["libc::unix::linux_like::linux::input_keymap_entry"]],["impl UnwindSafe for input_mask",1,["libc::unix::linux_like::linux::input_mask"]],["impl UnwindSafe for ff_replay",1,["libc::unix::linux_like::linux::ff_replay"]],["impl UnwindSafe for ff_trigger",1,["libc::unix::linux_like::linux::ff_trigger"]],["impl UnwindSafe for ff_envelope",1,["libc::unix::linux_like::linux::ff_envelope"]],["impl UnwindSafe for ff_constant_effect",1,["libc::unix::linux_like::linux::ff_constant_effect"]],["impl UnwindSafe for ff_ramp_effect",1,["libc::unix::linux_like::linux::ff_ramp_effect"]],["impl UnwindSafe for ff_condition_effect",1,["libc::unix::linux_like::linux::ff_condition_effect"]],["impl UnwindSafe for ff_periodic_effect",1,["libc::unix::linux_like::linux::ff_periodic_effect"]],["impl UnwindSafe for ff_rumble_effect",1,["libc::unix::linux_like::linux::ff_rumble_effect"]],["impl UnwindSafe for ff_effect",1,["libc::unix::linux_like::linux::ff_effect"]],["impl UnwindSafe for uinput_ff_upload",1,["libc::unix::linux_like::linux::uinput_ff_upload"]],["impl UnwindSafe for uinput_ff_erase",1,["libc::unix::linux_like::linux::uinput_ff_erase"]],["impl UnwindSafe for uinput_abs_setup",1,["libc::unix::linux_like::linux::uinput_abs_setup"]],["impl UnwindSafe for dl_phdr_info",1,["libc::unix::linux_like::linux::dl_phdr_info"]],["impl UnwindSafe for Elf32_Ehdr",1,["libc::unix::linux_like::linux::Elf32_Ehdr"]],["impl UnwindSafe for Elf64_Ehdr",1,["libc::unix::linux_like::linux::Elf64_Ehdr"]],["impl UnwindSafe for Elf32_Sym",1,["libc::unix::linux_like::linux::Elf32_Sym"]],["impl UnwindSafe for Elf64_Sym",1,["libc::unix::linux_like::linux::Elf64_Sym"]],["impl UnwindSafe for Elf32_Phdr",1,["libc::unix::linux_like::linux::Elf32_Phdr"]],["impl UnwindSafe for Elf64_Phdr",1,["libc::unix::linux_like::linux::Elf64_Phdr"]],["impl UnwindSafe for Elf32_Shdr",1,["libc::unix::linux_like::linux::Elf32_Shdr"]],["impl UnwindSafe for Elf64_Shdr",1,["libc::unix::linux_like::linux::Elf64_Shdr"]],["impl UnwindSafe for ucred",1,["libc::unix::linux_like::linux::ucred"]],["impl UnwindSafe for mntent",1,["libc::unix::linux_like::linux::mntent"]],["impl UnwindSafe for posix_spawn_file_actions_t",1,["libc::unix::linux_like::linux::posix_spawn_file_actions_t"]],["impl UnwindSafe for posix_spawnattr_t",1,["libc::unix::linux_like::linux::posix_spawnattr_t"]],["impl UnwindSafe for genlmsghdr",1,["libc::unix::linux_like::linux::genlmsghdr"]],["impl UnwindSafe for in6_pktinfo",1,["libc::unix::linux_like::linux::in6_pktinfo"]],["impl UnwindSafe for arpd_request",1,["libc::unix::linux_like::linux::arpd_request"]],["impl UnwindSafe for inotify_event",1,["libc::unix::linux_like::linux::inotify_event"]],["impl UnwindSafe for fanotify_response",1,["libc::unix::linux_like::linux::fanotify_response"]],["impl UnwindSafe for sockaddr_vm",1,["libc::unix::linux_like::linux::sockaddr_vm"]],["impl UnwindSafe for regmatch_t",1,["libc::unix::linux_like::linux::regmatch_t"]],["impl UnwindSafe for sock_extended_err",1,["libc::unix::linux_like::linux::sock_extended_err"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_tp",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_tp"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_j1939",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_j1939"]],["impl UnwindSafe for can_filter",1,["libc::unix::linux_like::linux::can_filter"]],["impl UnwindSafe for j1939_filter",1,["libc::unix::linux_like::linux::j1939_filter"]],["impl UnwindSafe for sock_filter",1,["libc::unix::linux_like::linux::sock_filter"]],["impl UnwindSafe for sock_fprog",1,["libc::unix::linux_like::linux::sock_fprog"]],["impl UnwindSafe for seccomp_data",1,["libc::unix::linux_like::linux::seccomp_data"]],["impl UnwindSafe for nlmsghdr",1,["libc::unix::linux_like::linux::nlmsghdr"]],["impl UnwindSafe for nlmsgerr",1,["libc::unix::linux_like::linux::nlmsgerr"]],["impl UnwindSafe for nlattr",1,["libc::unix::linux_like::linux::nlattr"]],["impl UnwindSafe for file_clone_range",1,["libc::unix::linux_like::linux::file_clone_range"]],["impl UnwindSafe for __c_anonymous_ifru_map",1,["libc::unix::linux_like::linux::__c_anonymous_ifru_map"]],["impl UnwindSafe for in6_ifreq",1,["libc::unix::linux_like::linux::in6_ifreq"]],["impl UnwindSafe for sockaddr_nl",1,["libc::unix::linux_like::linux::sockaddr_nl"]],["impl UnwindSafe for dirent",1,["libc::unix::linux_like::linux::dirent"]],["impl UnwindSafe for dirent64",1,["libc::unix::linux_like::linux::dirent64"]],["impl UnwindSafe for sockaddr_alg",1,["libc::unix::linux_like::linux::sockaddr_alg"]],["impl UnwindSafe for uinput_setup",1,["libc::unix::linux_like::linux::uinput_setup"]],["impl UnwindSafe for uinput_user_dev",1,["libc::unix::linux_like::linux::uinput_user_dev"]],["impl UnwindSafe for af_alg_iv",1,["libc::unix::linux_like::linux::af_alg_iv"]],["impl UnwindSafe for mq_attr",1,["libc::unix::linux_like::linux::mq_attr"]],["impl UnwindSafe for __c_anonymous_ifr_ifru",1,["libc::unix::linux_like::linux::__c_anonymous_ifr_ifru"]],["impl UnwindSafe for ifreq",1,["libc::unix::linux_like::linux::ifreq"]],["impl UnwindSafe for sock_txtime",1,["libc::unix::linux_like::linux::sock_txtime"]],["impl UnwindSafe for __c_anonymous_sockaddr_can_can_addr",1,["libc::unix::linux_like::linux::__c_anonymous_sockaddr_can_can_addr"]],["impl UnwindSafe for sockaddr_can",1,["libc::unix::linux_like::linux::sockaddr_can"]],["impl UnwindSafe for pthread_mutexattr_t",1,["libc::unix::linux_like::linux::pthread_mutexattr_t"]],["impl UnwindSafe for pthread_rwlockattr_t",1,["libc::unix::linux_like::linux::pthread_rwlockattr_t"]],["impl UnwindSafe for pthread_condattr_t",1,["libc::unix::linux_like::linux::pthread_condattr_t"]],["impl UnwindSafe for fanotify_event_metadata",1,["libc::unix::linux_like::linux::fanotify_event_metadata"]],["impl UnwindSafe for pthread_cond_t",1,["libc::unix::linux_like::linux::pthread_cond_t"]],["impl UnwindSafe for pthread_mutex_t",1,["libc::unix::linux_like::linux::pthread_mutex_t"]],["impl UnwindSafe for pthread_rwlock_t",1,["libc::unix::linux_like::linux::pthread_rwlock_t"]],["impl UnwindSafe for can_frame",1,["libc::unix::linux_like::linux::can_frame"]],["impl UnwindSafe for canfd_frame",1,["libc::unix::linux_like::linux::canfd_frame"]],["impl UnwindSafe for timezone",1,["libc::unix::linux_like::timezone"]],["impl UnwindSafe for in_addr",1,["libc::unix::linux_like::in_addr"]],["impl UnwindSafe for ip_mreq",1,["libc::unix::linux_like::ip_mreq"]],["impl UnwindSafe for ip_mreqn",1,["libc::unix::linux_like::ip_mreqn"]],["impl UnwindSafe for ip_mreq_source",1,["libc::unix::linux_like::ip_mreq_source"]],["impl UnwindSafe for sockaddr",1,["libc::unix::linux_like::sockaddr"]],["impl UnwindSafe for sockaddr_in",1,["libc::unix::linux_like::sockaddr_in"]],["impl UnwindSafe for sockaddr_in6",1,["libc::unix::linux_like::sockaddr_in6"]],["impl UnwindSafe for addrinfo",1,["libc::unix::linux_like::addrinfo"]],["impl UnwindSafe for sockaddr_ll",1,["libc::unix::linux_like::sockaddr_ll"]],["impl UnwindSafe for fd_set",1,["libc::unix::linux_like::fd_set"]],["impl UnwindSafe for tm",1,["libc::unix::linux_like::tm"]],["impl UnwindSafe for sched_param",1,["libc::unix::linux_like::sched_param"]],["impl UnwindSafe for Dl_info",1,["libc::unix::linux_like::Dl_info"]],["impl UnwindSafe for lconv",1,["libc::unix::linux_like::lconv"]],["impl UnwindSafe for in_pktinfo",1,["libc::unix::linux_like::in_pktinfo"]],["impl UnwindSafe for ifaddrs",1,["libc::unix::linux_like::ifaddrs"]],["impl UnwindSafe for in6_rtmsg",1,["libc::unix::linux_like::in6_rtmsg"]],["impl UnwindSafe for arpreq",1,["libc::unix::linux_like::arpreq"]],["impl UnwindSafe for arpreq_old",1,["libc::unix::linux_like::arpreq_old"]],["impl UnwindSafe for arphdr",1,["libc::unix::linux_like::arphdr"]],["impl UnwindSafe for mmsghdr",1,["libc::unix::linux_like::mmsghdr"]],["impl UnwindSafe for epoll_event",1,["libc::unix::linux_like::epoll_event"]],["impl UnwindSafe for sockaddr_un",1,["libc::unix::linux_like::sockaddr_un"]],["impl UnwindSafe for sockaddr_storage",1,["libc::unix::linux_like::sockaddr_storage"]],["impl UnwindSafe for utsname",1,["libc::unix::linux_like::utsname"]],["impl UnwindSafe for sigevent",1,["libc::unix::linux_like::sigevent"]],["impl UnwindSafe for in6_addr",1,["libc::unix::align::in6_addr"]],["impl UnwindSafe for DIR",1,["libc::unix::DIR"]],["impl UnwindSafe for group",1,["libc::unix::group"]],["impl UnwindSafe for utimbuf",1,["libc::unix::utimbuf"]],["impl UnwindSafe for timeval",1,["libc::unix::timeval"]],["impl UnwindSafe for timespec",1,["libc::unix::timespec"]],["impl UnwindSafe for rlimit",1,["libc::unix::rlimit"]],["impl UnwindSafe for rusage",1,["libc::unix::rusage"]],["impl UnwindSafe for ipv6_mreq",1,["libc::unix::ipv6_mreq"]],["impl UnwindSafe for hostent",1,["libc::unix::hostent"]],["impl UnwindSafe for iovec",1,["libc::unix::iovec"]],["impl UnwindSafe for pollfd",1,["libc::unix::pollfd"]],["impl UnwindSafe for winsize",1,["libc::unix::winsize"]],["impl UnwindSafe for linger",1,["libc::unix::linger"]],["impl UnwindSafe for sigval",1,["libc::unix::sigval"]],["impl UnwindSafe for itimerval",1,["libc::unix::itimerval"]],["impl UnwindSafe for tms",1,["libc::unix::tms"]],["impl UnwindSafe for servent",1,["libc::unix::servent"]],["impl UnwindSafe for protoent",1,["libc::unix::protoent"]],["impl UnwindSafe for FILE",1,["libc::unix::FILE"]],["impl UnwindSafe for fpos_t",1,["libc::unix::fpos_t"]]], -"lock_api":[["impl<R, T: ?Sized> UnwindSafe for Mutex<R, T>where
        R: UnwindSafe,
        T: UnwindSafe,
    ",1,["lock_api::mutex::Mutex"]],["impl<'a, R, T> !UnwindSafe for MutexGuard<'a, R, T>",1,["lock_api::mutex::MutexGuard"]],["impl<'a, R, T> !UnwindSafe for MappedMutexGuard<'a, R, T>",1,["lock_api::mutex::MappedMutexGuard"]],["impl<R, G> UnwindSafe for RawReentrantMutex<R, G>where
        G: UnwindSafe,
        R: UnwindSafe,
    ",1,["lock_api::remutex::RawReentrantMutex"]],["impl<R, G, T: ?Sized> UnwindSafe for ReentrantMutex<R, G, T>where
        G: UnwindSafe,
        R: UnwindSafe,
        T: UnwindSafe,
    ",1,["lock_api::remutex::ReentrantMutex"]],["impl<'a, R, G, T> !UnwindSafe for ReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::ReentrantMutexGuard"]],["impl<'a, R, G, T> !UnwindSafe for MappedReentrantMutexGuard<'a, R, G, T>",1,["lock_api::remutex::MappedReentrantMutexGuard"]],["impl<R, T: ?Sized> UnwindSafe for RwLock<R, T>where
        R: UnwindSafe,
        T: UnwindSafe,
    ",1,["lock_api::rwlock::RwLock"]],["impl<'a, R, T> !UnwindSafe for RwLockReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockReadGuard"]],["impl<'a, R, T> !UnwindSafe for RwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::RwLockWriteGuard"]],["impl<'a, R, T> !UnwindSafe for RwLockUpgradableReadGuard<'a, R, T>",1,["lock_api::rwlock::RwLockUpgradableReadGuard"]],["impl<'a, R, T: ?Sized> UnwindSafe for MappedRwLockReadGuard<'a, R, T>where
        R: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["lock_api::rwlock::MappedRwLockReadGuard"]],["impl<'a, R, T> !UnwindSafe for MappedRwLockWriteGuard<'a, R, T>",1,["lock_api::rwlock::MappedRwLockWriteGuard"]],["impl UnwindSafe for GuardSend",1,["lock_api::GuardSend"]],["impl UnwindSafe for GuardNoSend",1,["lock_api::GuardNoSend"]]], -"lru":[["impl<K, V, S> UnwindSafe for LruCache<K, V, S>where
        K: UnwindSafe + RefUnwindSafe,
        S: UnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["lru::LruCache"]],["impl<'a, K, V> UnwindSafe for Iter<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::Iter"]],["impl<'a, K, V> UnwindSafe for IterMut<'a, K, V>where
        K: RefUnwindSafe,
        V: RefUnwindSafe,
    ",1,["lru::IterMut"]],["impl<K, V> UnwindSafe for IntoIter<K, V>where
        K: UnwindSafe + RefUnwindSafe,
        V: UnwindSafe + RefUnwindSafe,
    ",1,["lru::IntoIter"]]], -"memchr":[["impl<'a> UnwindSafe for Memchr<'a>",1,["memchr::memchr::iter::Memchr"]],["impl<'a> UnwindSafe for Memchr2<'a>",1,["memchr::memchr::iter::Memchr2"]],["impl<'a> UnwindSafe for Memchr3<'a>",1,["memchr::memchr::iter::Memchr3"]],["impl UnwindSafe for Prefilter",1,["memchr::memmem::prefilter::Prefilter"]],["impl<'h, 'n> UnwindSafe for FindIter<'h, 'n>",1,["memchr::memmem::FindIter"]],["impl<'h, 'n> UnwindSafe for FindRevIter<'h, 'n>",1,["memchr::memmem::FindRevIter"]],["impl<'n> UnwindSafe for Finder<'n>",1,["memchr::memmem::Finder"]],["impl<'n> UnwindSafe for FinderRev<'n>",1,["memchr::memmem::FinderRev"]],["impl UnwindSafe for FinderBuilder",1,["memchr::memmem::FinderBuilder"]]], -"nix":[["impl UnwindSafe for Dir",1,["nix::dir::Dir"]],["impl<'d> !UnwindSafe for Iter<'d>",1,["nix::dir::Iter"]],["impl UnwindSafe for OwningIter",1,["nix::dir::OwningIter"]],["impl UnwindSafe for Entry",1,["nix::dir::Entry"]],["impl UnwindSafe for Type",1,["nix::dir::Type"]],["impl UnwindSafe for ClearEnvError",1,["nix::env::ClearEnvError"]],["impl UnwindSafe for Errno",1,["nix::errno::consts::Errno"]],["impl UnwindSafe for PosixFadviseAdvice",1,["nix::fcntl::posix_fadvise::PosixFadviseAdvice"]],["impl UnwindSafe for AtFlags",1,["nix::fcntl::AtFlags"]],["impl UnwindSafe for OFlag",1,["nix::fcntl::OFlag"]],["impl UnwindSafe for RenameFlags",1,["nix::fcntl::RenameFlags"]],["impl UnwindSafe for SealFlag",1,["nix::fcntl::SealFlag"]],["impl UnwindSafe for FdFlag",1,["nix::fcntl::FdFlag"]],["impl<'a> !UnwindSafe for FcntlArg<'a>",1,["nix::fcntl::FcntlArg"]],["impl UnwindSafe for FlockArg",1,["nix::fcntl::FlockArg"]],["impl UnwindSafe for SpliceFFlags",1,["nix::fcntl::SpliceFFlags"]],["impl UnwindSafe for FallocateFlags",1,["nix::fcntl::FallocateFlags"]],["impl UnwindSafe for InterfaceAddress",1,["nix::ifaddrs::InterfaceAddress"]],["impl UnwindSafe for InterfaceAddressIterator",1,["nix::ifaddrs::InterfaceAddressIterator"]],["impl UnwindSafe for Interface",1,["nix::net::if_::if_nameindex::Interface"]],["impl UnwindSafe for Interfaces",1,["nix::net::if_::if_nameindex::Interfaces"]],["impl<'a> UnwindSafe for InterfacesIter<'a>",1,["nix::net::if_::if_nameindex::InterfacesIter"]],["impl UnwindSafe for InterfaceFlags",1,["nix::net::if_::InterfaceFlags"]],["impl UnwindSafe for ModuleInitFlags",1,["nix::kmod::ModuleInitFlags"]],["impl UnwindSafe for DeleteModuleFlags",1,["nix::kmod::DeleteModuleFlags"]],["impl UnwindSafe for MsFlags",1,["nix::mount::linux::MsFlags"]],["impl UnwindSafe for MntFlags",1,["nix::mount::linux::MntFlags"]],["impl UnwindSafe for MQ_OFlag",1,["nix::mqueue::MQ_OFlag"]],["impl UnwindSafe for MqAttr",1,["nix::mqueue::MqAttr"]],["impl UnwindSafe for MqdT",1,["nix::mqueue::MqdT"]],["impl UnwindSafe for PollFd",1,["nix::poll::PollFd"]],["impl UnwindSafe for PollFlags",1,["nix::poll::PollFlags"]],["impl UnwindSafe for OpenptyResult",1,["nix::pty::OpenptyResult"]],["impl UnwindSafe for ForkptyResult",1,["nix::pty::ForkptyResult"]],["impl UnwindSafe for PtyMaster",1,["nix::pty::PtyMaster"]],["impl UnwindSafe for CloneFlags",1,["nix::sched::sched_linux_like::CloneFlags"]],["impl UnwindSafe for CpuSet",1,["nix::sched::sched_affinity::CpuSet"]],["impl UnwindSafe for AioFsyncMode",1,["nix::sys::aio::AioFsyncMode"]],["impl UnwindSafe for LioMode",1,["nix::sys::aio::LioMode"]],["impl UnwindSafe for AioCancelStat",1,["nix::sys::aio::AioCancelStat"]],["impl UnwindSafe for AioFsync",1,["nix::sys::aio::AioFsync"]],["impl<'a> UnwindSafe for AioRead<'a>",1,["nix::sys::aio::AioRead"]],["impl<'a> UnwindSafe for AioWrite<'a>",1,["nix::sys::aio::AioWrite"]],["impl UnwindSafe for EpollFlags",1,["nix::sys::epoll::EpollFlags"]],["impl UnwindSafe for EpollOp",1,["nix::sys::epoll::EpollOp"]],["impl UnwindSafe for EpollCreateFlags",1,["nix::sys::epoll::EpollCreateFlags"]],["impl UnwindSafe for EpollEvent",1,["nix::sys::epoll::EpollEvent"]],["impl UnwindSafe for EfdFlags",1,["nix::sys::eventfd::EfdFlags"]],["impl UnwindSafe for MemFdCreateFlag",1,["nix::sys::memfd::MemFdCreateFlag"]],["impl UnwindSafe for ProtFlags",1,["nix::sys::mman::ProtFlags"]],["impl UnwindSafe for MapFlags",1,["nix::sys::mman::MapFlags"]],["impl UnwindSafe for MRemapFlags",1,["nix::sys::mman::MRemapFlags"]],["impl UnwindSafe for MmapAdvise",1,["nix::sys::mman::MmapAdvise"]],["impl UnwindSafe for MsFlags",1,["nix::sys::mman::MsFlags"]],["impl UnwindSafe for MlockAllFlags",1,["nix::sys::mman::MlockAllFlags"]],["impl UnwindSafe for Persona",1,["nix::sys::personality::Persona"]],["impl UnwindSafe for Request",1,["nix::sys::ptrace::linux::Request"]],["impl UnwindSafe for Event",1,["nix::sys::ptrace::linux::Event"]],["impl UnwindSafe for Options",1,["nix::sys::ptrace::linux::Options"]],["impl UnwindSafe for QuotaType",1,["nix::sys::quota::QuotaType"]],["impl UnwindSafe for QuotaFmt",1,["nix::sys::quota::QuotaFmt"]],["impl UnwindSafe for QuotaValidFlags",1,["nix::sys::quota::QuotaValidFlags"]],["impl UnwindSafe for Dqblk",1,["nix::sys::quota::Dqblk"]],["impl UnwindSafe for RebootMode",1,["nix::sys::reboot::RebootMode"]],["impl UnwindSafe for Resource",1,["nix::sys::resource::Resource"]],["impl UnwindSafe for UsageWho",1,["nix::sys::resource::UsageWho"]],["impl UnwindSafe for Usage",1,["nix::sys::resource::Usage"]],["impl UnwindSafe for FdSet",1,["nix::sys::select::FdSet"]],["impl<'a> UnwindSafe for Fds<'a>",1,["nix::sys::select::Fds"]],["impl UnwindSafe for SigEvent",1,["nix::sys::signal::sigevent::SigEvent"]],["impl UnwindSafe for Signal",1,["nix::sys::signal::Signal"]],["impl UnwindSafe for SignalIterator",1,["nix::sys::signal::SignalIterator"]],["impl UnwindSafe for SaFlags",1,["nix::sys::signal::SaFlags"]],["impl UnwindSafe for SigmaskHow",1,["nix::sys::signal::SigmaskHow"]],["impl UnwindSafe for SigSet",1,["nix::sys::signal::SigSet"]],["impl<'a> UnwindSafe for SigSetIter<'a>",1,["nix::sys::signal::SigSetIter"]],["impl UnwindSafe for SigHandler",1,["nix::sys::signal::SigHandler"]],["impl UnwindSafe for SigAction",1,["nix::sys::signal::SigAction"]],["impl UnwindSafe for SigevNotify",1,["nix::sys::signal::SigevNotify"]],["impl UnwindSafe for SfdFlags",1,["nix::sys::signalfd::SfdFlags"]],["impl UnwindSafe for SignalFd",1,["nix::sys::signalfd::SignalFd"]],["impl UnwindSafe for NetlinkAddr",1,["nix::sys::socket::addr::netlink::NetlinkAddr"]],["impl UnwindSafe for AlgAddr",1,["nix::sys::socket::addr::alg::AlgAddr"]],["impl UnwindSafe for LinkAddr",1,["nix::sys::socket::addr::datalink::LinkAddr"]],["impl UnwindSafe for VsockAddr",1,["nix::sys::socket::addr::vsock::VsockAddr"]],["impl UnwindSafe for AddressFamily",1,["nix::sys::socket::addr::AddressFamily"]],["impl UnwindSafe for InetAddr",1,["nix::sys::socket::addr::InetAddr"]],["impl UnwindSafe for IpAddr",1,["nix::sys::socket::addr::IpAddr"]],["impl UnwindSafe for Ipv4Addr",1,["nix::sys::socket::addr::Ipv4Addr"]],["impl UnwindSafe for Ipv6Addr",1,["nix::sys::socket::addr::Ipv6Addr"]],["impl UnwindSafe for UnixAddr",1,["nix::sys::socket::addr::UnixAddr"]],["impl UnwindSafe for SockaddrIn",1,["nix::sys::socket::addr::SockaddrIn"]],["impl UnwindSafe for SockaddrIn6",1,["nix::sys::socket::addr::SockaddrIn6"]],["impl UnwindSafe for SockaddrStorage",1,["nix::sys::socket::addr::SockaddrStorage"]],["impl UnwindSafe for SockAddr",1,["nix::sys::socket::addr::SockAddr"]],["impl UnwindSafe for ReuseAddr",1,["nix::sys::socket::sockopt::ReuseAddr"]],["impl UnwindSafe for ReusePort",1,["nix::sys::socket::sockopt::ReusePort"]],["impl UnwindSafe for TcpNoDelay",1,["nix::sys::socket::sockopt::TcpNoDelay"]],["impl UnwindSafe for Linger",1,["nix::sys::socket::sockopt::Linger"]],["impl UnwindSafe for IpAddMembership",1,["nix::sys::socket::sockopt::IpAddMembership"]],["impl UnwindSafe for IpDropMembership",1,["nix::sys::socket::sockopt::IpDropMembership"]],["impl UnwindSafe for Ipv6AddMembership",1,["nix::sys::socket::sockopt::Ipv6AddMembership"]],["impl UnwindSafe for Ipv6DropMembership",1,["nix::sys::socket::sockopt::Ipv6DropMembership"]],["impl UnwindSafe for IpMulticastTtl",1,["nix::sys::socket::sockopt::IpMulticastTtl"]],["impl UnwindSafe for IpMulticastLoop",1,["nix::sys::socket::sockopt::IpMulticastLoop"]],["impl UnwindSafe for Priority",1,["nix::sys::socket::sockopt::Priority"]],["impl UnwindSafe for IpTos",1,["nix::sys::socket::sockopt::IpTos"]],["impl UnwindSafe for Ipv6TClass",1,["nix::sys::socket::sockopt::Ipv6TClass"]],["impl UnwindSafe for IpFreebind",1,["nix::sys::socket::sockopt::IpFreebind"]],["impl UnwindSafe for ReceiveTimeout",1,["nix::sys::socket::sockopt::ReceiveTimeout"]],["impl UnwindSafe for SendTimeout",1,["nix::sys::socket::sockopt::SendTimeout"]],["impl UnwindSafe for Broadcast",1,["nix::sys::socket::sockopt::Broadcast"]],["impl UnwindSafe for OobInline",1,["nix::sys::socket::sockopt::OobInline"]],["impl UnwindSafe for SocketError",1,["nix::sys::socket::sockopt::SocketError"]],["impl UnwindSafe for DontRoute",1,["nix::sys::socket::sockopt::DontRoute"]],["impl UnwindSafe for KeepAlive",1,["nix::sys::socket::sockopt::KeepAlive"]],["impl UnwindSafe for PeerCredentials",1,["nix::sys::socket::sockopt::PeerCredentials"]],["impl UnwindSafe for TcpKeepIdle",1,["nix::sys::socket::sockopt::TcpKeepIdle"]],["impl UnwindSafe for TcpMaxSeg",1,["nix::sys::socket::sockopt::TcpMaxSeg"]],["impl UnwindSafe for TcpKeepCount",1,["nix::sys::socket::sockopt::TcpKeepCount"]],["impl UnwindSafe for TcpRepair",1,["nix::sys::socket::sockopt::TcpRepair"]],["impl UnwindSafe for TcpKeepInterval",1,["nix::sys::socket::sockopt::TcpKeepInterval"]],["impl UnwindSafe for TcpUserTimeout",1,["nix::sys::socket::sockopt::TcpUserTimeout"]],["impl UnwindSafe for RcvBuf",1,["nix::sys::socket::sockopt::RcvBuf"]],["impl UnwindSafe for SndBuf",1,["nix::sys::socket::sockopt::SndBuf"]],["impl UnwindSafe for RcvBufForce",1,["nix::sys::socket::sockopt::RcvBufForce"]],["impl UnwindSafe for SndBufForce",1,["nix::sys::socket::sockopt::SndBufForce"]],["impl UnwindSafe for SockType",1,["nix::sys::socket::sockopt::SockType"]],["impl UnwindSafe for AcceptConn",1,["nix::sys::socket::sockopt::AcceptConn"]],["impl UnwindSafe for BindToDevice",1,["nix::sys::socket::sockopt::BindToDevice"]],["impl UnwindSafe for OriginalDst",1,["nix::sys::socket::sockopt::OriginalDst"]],["impl UnwindSafe for Ip6tOriginalDst",1,["nix::sys::socket::sockopt::Ip6tOriginalDst"]],["impl UnwindSafe for Timestamping",1,["nix::sys::socket::sockopt::Timestamping"]],["impl UnwindSafe for ReceiveTimestamp",1,["nix::sys::socket::sockopt::ReceiveTimestamp"]],["impl UnwindSafe for ReceiveTimestampns",1,["nix::sys::socket::sockopt::ReceiveTimestampns"]],["impl UnwindSafe for IpTransparent",1,["nix::sys::socket::sockopt::IpTransparent"]],["impl UnwindSafe for Mark",1,["nix::sys::socket::sockopt::Mark"]],["impl UnwindSafe for PassCred",1,["nix::sys::socket::sockopt::PassCred"]],["impl UnwindSafe for TcpCongestion",1,["nix::sys::socket::sockopt::TcpCongestion"]],["impl UnwindSafe for Ipv4PacketInfo",1,["nix::sys::socket::sockopt::Ipv4PacketInfo"]],["impl UnwindSafe for Ipv6RecvPacketInfo",1,["nix::sys::socket::sockopt::Ipv6RecvPacketInfo"]],["impl UnwindSafe for Ipv4OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv4OrigDstAddr"]],["impl UnwindSafe for UdpGsoSegment",1,["nix::sys::socket::sockopt::UdpGsoSegment"]],["impl UnwindSafe for UdpGroSegment",1,["nix::sys::socket::sockopt::UdpGroSegment"]],["impl UnwindSafe for TxTime",1,["nix::sys::socket::sockopt::TxTime"]],["impl UnwindSafe for RxqOvfl",1,["nix::sys::socket::sockopt::RxqOvfl"]],["impl UnwindSafe for Ipv6V6Only",1,["nix::sys::socket::sockopt::Ipv6V6Only"]],["impl UnwindSafe for Ipv4RecvErr",1,["nix::sys::socket::sockopt::Ipv4RecvErr"]],["impl UnwindSafe for Ipv6RecvErr",1,["nix::sys::socket::sockopt::Ipv6RecvErr"]],["impl UnwindSafe for IpMtu",1,["nix::sys::socket::sockopt::IpMtu"]],["impl UnwindSafe for Ipv4Ttl",1,["nix::sys::socket::sockopt::Ipv4Ttl"]],["impl UnwindSafe for Ipv6Ttl",1,["nix::sys::socket::sockopt::Ipv6Ttl"]],["impl UnwindSafe for Ipv6OrigDstAddr",1,["nix::sys::socket::sockopt::Ipv6OrigDstAddr"]],["impl UnwindSafe for Ipv6DontFrag",1,["nix::sys::socket::sockopt::Ipv6DontFrag"]],["impl UnwindSafe for AlgSetAeadAuthSize",1,["nix::sys::socket::sockopt::AlgSetAeadAuthSize"]],["impl<T> UnwindSafe for AlgSetKey<T>where
        T: UnwindSafe,
    ",1,["nix::sys::socket::sockopt::AlgSetKey"]],["impl UnwindSafe for SockType",1,["nix::sys::socket::SockType"]],["impl UnwindSafe for SockProtocol",1,["nix::sys::socket::SockProtocol"]],["impl UnwindSafe for TimestampingFlag",1,["nix::sys::socket::TimestampingFlag"]],["impl UnwindSafe for SockFlag",1,["nix::sys::socket::SockFlag"]],["impl UnwindSafe for MsgFlags",1,["nix::sys::socket::MsgFlags"]],["impl UnwindSafe for UnixCredentials",1,["nix::sys::socket::UnixCredentials"]],["impl UnwindSafe for IpMembershipRequest",1,["nix::sys::socket::IpMembershipRequest"]],["impl UnwindSafe for Ipv6MembershipRequest",1,["nix::sys::socket::Ipv6MembershipRequest"]],["impl<'a, 's, S> UnwindSafe for RecvMsg<'a, 's, S>where
        S: UnwindSafe,
    ",1,["nix::sys::socket::RecvMsg"]],["impl<'a> UnwindSafe for CmsgIterator<'a>",1,["nix::sys::socket::CmsgIterator"]],["impl UnwindSafe for ControlMessageOwned",1,["nix::sys::socket::ControlMessageOwned"]],["impl UnwindSafe for Timestamps",1,["nix::sys::socket::Timestamps"]],["impl<'a> UnwindSafe for ControlMessage<'a>",1,["nix::sys::socket::ControlMessage"]],["impl<S> UnwindSafe for MultiHeaders<S>where
        S: UnwindSafe,
    ",1,["nix::sys::socket::MultiHeaders"]],["impl<'a, S> UnwindSafe for MultiResults<'a, S>where
        S: RefUnwindSafe,
    ",1,["nix::sys::socket::MultiResults"]],["impl<'a> UnwindSafe for IoSliceIterator<'a>",1,["nix::sys::socket::IoSliceIterator"]],["impl UnwindSafe for Shutdown",1,["nix::sys::socket::Shutdown"]],["impl UnwindSafe for SFlag",1,["nix::sys::stat::SFlag"]],["impl UnwindSafe for Mode",1,["nix::sys::stat::Mode"]],["impl UnwindSafe for FchmodatFlags",1,["nix::sys::stat::FchmodatFlags"]],["impl UnwindSafe for UtimensatFlags",1,["nix::sys::stat::UtimensatFlags"]],["impl UnwindSafe for Statfs",1,["nix::sys::statfs::Statfs"]],["impl UnwindSafe for FsType",1,["nix::sys::statfs::FsType"]],["impl UnwindSafe for FsFlags",1,["nix::sys::statvfs::FsFlags"]],["impl UnwindSafe for Statvfs",1,["nix::sys::statvfs::Statvfs"]],["impl UnwindSafe for SysInfo",1,["nix::sys::sysinfo::SysInfo"]],["impl UnwindSafe for Termios",1,["nix::sys::termios::Termios"]],["impl UnwindSafe for BaudRate",1,["nix::sys::termios::BaudRate"]],["impl UnwindSafe for SetArg",1,["nix::sys::termios::SetArg"]],["impl UnwindSafe for FlushArg",1,["nix::sys::termios::FlushArg"]],["impl UnwindSafe for FlowArg",1,["nix::sys::termios::FlowArg"]],["impl UnwindSafe for SpecialCharacterIndices",1,["nix::sys::termios::SpecialCharacterIndices"]],["impl UnwindSafe for InputFlags",1,["nix::sys::termios::InputFlags"]],["impl UnwindSafe for OutputFlags",1,["nix::sys::termios::OutputFlags"]],["impl UnwindSafe for ControlFlags",1,["nix::sys::termios::ControlFlags"]],["impl UnwindSafe for LocalFlags",1,["nix::sys::termios::LocalFlags"]],["impl UnwindSafe for Expiration",1,["nix::sys::time::timer::Expiration"]],["impl UnwindSafe for TimerSetTimeFlags",1,["nix::sys::time::timer::TimerSetTimeFlags"]],["impl UnwindSafe for TimeSpec",1,["nix::sys::time::TimeSpec"]],["impl UnwindSafe for TimeVal",1,["nix::sys::time::TimeVal"]],["impl UnwindSafe for RemoteIoVec",1,["nix::sys::uio::RemoteIoVec"]],["impl<T> UnwindSafe for IoVec<T>where
        T: UnwindSafe,
    ",1,["nix::sys::uio::IoVec"]],["impl UnwindSafe for UtsName",1,["nix::sys::utsname::UtsName"]],["impl UnwindSafe for WaitPidFlag",1,["nix::sys::wait::WaitPidFlag"]],["impl UnwindSafe for WaitStatus",1,["nix::sys::wait::WaitStatus"]],["impl UnwindSafe for Id",1,["nix::sys::wait::Id"]],["impl UnwindSafe for AddWatchFlags",1,["nix::sys::inotify::AddWatchFlags"]],["impl UnwindSafe for InitFlags",1,["nix::sys::inotify::InitFlags"]],["impl UnwindSafe for Inotify",1,["nix::sys::inotify::Inotify"]],["impl UnwindSafe for WatchDescriptor",1,["nix::sys::inotify::WatchDescriptor"]],["impl UnwindSafe for InotifyEvent",1,["nix::sys::inotify::InotifyEvent"]],["impl UnwindSafe for TimerFd",1,["nix::sys::timerfd::TimerFd"]],["impl UnwindSafe for ClockId",1,["nix::sys::timerfd::ClockId"]],["impl UnwindSafe for TimerFlags",1,["nix::sys::timerfd::TimerFlags"]],["impl UnwindSafe for Timer",1,["nix::sys::timer::Timer"]],["impl UnwindSafe for ClockId",1,["nix::time::ClockId"]],["impl UnwindSafe for UContext",1,["nix::ucontext::UContext"]],["impl UnwindSafe for ResUid",1,["nix::unistd::getres::ResUid"]],["impl UnwindSafe for ResGid",1,["nix::unistd::getres::ResGid"]],["impl UnwindSafe for Uid",1,["nix::unistd::Uid"]],["impl UnwindSafe for Gid",1,["nix::unistd::Gid"]],["impl UnwindSafe for Pid",1,["nix::unistd::Pid"]],["impl UnwindSafe for ForkResult",1,["nix::unistd::ForkResult"]],["impl UnwindSafe for FchownatFlags",1,["nix::unistd::FchownatFlags"]],["impl UnwindSafe for Whence",1,["nix::unistd::Whence"]],["impl UnwindSafe for LinkatFlags",1,["nix::unistd::LinkatFlags"]],["impl UnwindSafe for UnlinkatFlags",1,["nix::unistd::UnlinkatFlags"]],["impl UnwindSafe for PathconfVar",1,["nix::unistd::PathconfVar"]],["impl UnwindSafe for SysconfVar",1,["nix::unistd::SysconfVar"]],["impl UnwindSafe for AccessFlags",1,["nix::unistd::AccessFlags"]],["impl UnwindSafe for User",1,["nix::unistd::User"]],["impl UnwindSafe for Group",1,["nix::unistd::Group"]]], -"once_cell":[["impl<T, F> UnwindSafe for Lazy<T, F>where
        F: UnwindSafe,
        T: UnwindSafe,
    ",1,["once_cell::unsync::Lazy"]],["impl<T> UnwindSafe for OnceCell<T>where
        T: UnwindSafe,
    ",1,["once_cell::sync::OnceCell"]],["impl<T, F> UnwindSafe for Lazy<T, F>where
        F: UnwindSafe,
        T: UnwindSafe,
    ",1,["once_cell::sync::Lazy"]],["impl<T> UnwindSafe for OnceBox<T>where
        T: UnwindSafe + RefUnwindSafe,
    ",1,["once_cell::race::once_box::OnceBox"]],["impl UnwindSafe for OnceNonZeroUsize",1,["once_cell::race::OnceNonZeroUsize"]],["impl UnwindSafe for OnceBool",1,["once_cell::race::OnceBool"]],["impl<T: UnwindSafe> UnwindSafe for OnceCell<T>"]], -"parking_lot":[["impl UnwindSafe for WaitTimeoutResult",1,["parking_lot::condvar::WaitTimeoutResult"]],["impl UnwindSafe for Condvar",1,["parking_lot::condvar::Condvar"]],["impl UnwindSafe for OnceState",1,["parking_lot::once::OnceState"]],["impl UnwindSafe for Once",1,["parking_lot::once::Once"]],["impl UnwindSafe for RawFairMutex",1,["parking_lot::raw_fair_mutex::RawFairMutex"]],["impl UnwindSafe for RawMutex",1,["parking_lot::raw_mutex::RawMutex"]],["impl UnwindSafe for RawRwLock",1,["parking_lot::raw_rwlock::RawRwLock"]],["impl UnwindSafe for RawThreadId",1,["parking_lot::remutex::RawThreadId"]]], -"parking_lot_core":[["impl UnwindSafe for ParkResult",1,["parking_lot_core::parking_lot::ParkResult"]],["impl UnwindSafe for UnparkResult",1,["parking_lot_core::parking_lot::UnparkResult"]],["impl UnwindSafe for RequeueOp",1,["parking_lot_core::parking_lot::RequeueOp"]],["impl UnwindSafe for FilterOp",1,["parking_lot_core::parking_lot::FilterOp"]],["impl UnwindSafe for UnparkToken",1,["parking_lot_core::parking_lot::UnparkToken"]],["impl UnwindSafe for ParkToken",1,["parking_lot_core::parking_lot::ParkToken"]],["impl UnwindSafe for SpinWait",1,["parking_lot_core::spinwait::SpinWait"]]], -"ppv_lite86":[["impl UnwindSafe for YesS3",1,["ppv_lite86::x86_64::YesS3"]],["impl UnwindSafe for NoS3",1,["ppv_lite86::x86_64::NoS3"]],["impl UnwindSafe for YesS4",1,["ppv_lite86::x86_64::YesS4"]],["impl UnwindSafe for NoS4",1,["ppv_lite86::x86_64::NoS4"]],["impl UnwindSafe for YesA1",1,["ppv_lite86::x86_64::YesA1"]],["impl UnwindSafe for NoA1",1,["ppv_lite86::x86_64::NoA1"]],["impl UnwindSafe for YesA2",1,["ppv_lite86::x86_64::YesA2"]],["impl UnwindSafe for NoA2",1,["ppv_lite86::x86_64::NoA2"]],["impl UnwindSafe for YesNI",1,["ppv_lite86::x86_64::YesNI"]],["impl UnwindSafe for NoNI",1,["ppv_lite86::x86_64::NoNI"]],["impl<S3, S4, NI> UnwindSafe for SseMachine<S3, S4, NI>where
        NI: UnwindSafe,
        S3: UnwindSafe,
        S4: UnwindSafe,
    ",1,["ppv_lite86::x86_64::SseMachine"]],["impl<NI> UnwindSafe for Avx2Machine<NI>where
        NI: UnwindSafe,
    ",1,["ppv_lite86::x86_64::Avx2Machine"]],["impl UnwindSafe for vec128_storage",1,["ppv_lite86::x86_64::vec128_storage"]],["impl UnwindSafe for vec256_storage",1,["ppv_lite86::x86_64::vec256_storage"]],["impl UnwindSafe for vec512_storage",1,["ppv_lite86::x86_64::vec512_storage"]]], -"primitive_types":[["impl UnwindSafe for Error",1,["primitive_types::Error"]],["impl UnwindSafe for U128",1,["primitive_types::U128"]],["impl UnwindSafe for U256",1,["primitive_types::U256"]],["impl UnwindSafe for U512",1,["primitive_types::U512"]],["impl UnwindSafe for H128",1,["primitive_types::H128"]],["impl UnwindSafe for H160",1,["primitive_types::H160"]],["impl UnwindSafe for H256",1,["primitive_types::H256"]],["impl UnwindSafe for H384",1,["primitive_types::H384"]],["impl UnwindSafe for H512",1,["primitive_types::H512"]],["impl UnwindSafe for H768",1,["primitive_types::H768"]]], -"proc_macro2":[["impl UnwindSafe for IntoIter",1,["proc_macro2::token_stream::IntoIter"]],["impl UnwindSafe for TokenStream",1,["proc_macro2::TokenStream"]],["impl UnwindSafe for LexError",1,["proc_macro2::LexError"]],["impl UnwindSafe for Span",1,["proc_macro2::Span"]],["impl UnwindSafe for TokenTree",1,["proc_macro2::TokenTree"]],["impl UnwindSafe for Group",1,["proc_macro2::Group"]],["impl UnwindSafe for Delimiter",1,["proc_macro2::Delimiter"]],["impl UnwindSafe for Punct",1,["proc_macro2::Punct"]],["impl UnwindSafe for Spacing",1,["proc_macro2::Spacing"]],["impl UnwindSafe for Ident",1,["proc_macro2::Ident"]],["impl UnwindSafe for Literal",1,["proc_macro2::Literal"]]], -"rand":[["impl UnwindSafe for Bernoulli",1,["rand::distributions::bernoulli::Bernoulli"]],["impl UnwindSafe for BernoulliError",1,["rand::distributions::bernoulli::BernoulliError"]],["impl<D, R, T> UnwindSafe for DistIter<D, R, T>where
        D: UnwindSafe,
        R: UnwindSafe,
        T: UnwindSafe,
    ",1,["rand::distributions::distribution::DistIter"]],["impl<D, F, T, S> UnwindSafe for DistMap<D, F, T, S>where
        D: UnwindSafe,
        F: UnwindSafe,
    ",1,["rand::distributions::distribution::DistMap"]],["impl UnwindSafe for OpenClosed01",1,["rand::distributions::float::OpenClosed01"]],["impl UnwindSafe for Open01",1,["rand::distributions::float::Open01"]],["impl UnwindSafe for Alphanumeric",1,["rand::distributions::other::Alphanumeric"]],["impl<'a, T> UnwindSafe for Slice<'a, T>where
        T: RefUnwindSafe,
    ",1,["rand::distributions::slice::Slice"]],["impl<X> UnwindSafe for WeightedIndex<X>where
        X: UnwindSafe,
        <X as SampleUniform>::Sampler: UnwindSafe,
    ",1,["rand::distributions::weighted_index::WeightedIndex"]],["impl UnwindSafe for WeightedError",1,["rand::distributions::weighted_index::WeightedError"]],["impl<X> UnwindSafe for Uniform<X>where
        <X as SampleUniform>::Sampler: UnwindSafe,
    ",1,["rand::distributions::uniform::Uniform"]],["impl<X> UnwindSafe for UniformInt<X>where
        X: UnwindSafe,
    ",1,["rand::distributions::uniform::UniformInt"]],["impl UnwindSafe for UniformChar",1,["rand::distributions::uniform::UniformChar"]],["impl<X> UnwindSafe for UniformFloat<X>where
        X: UnwindSafe,
    ",1,["rand::distributions::uniform::UniformFloat"]],["impl UnwindSafe for UniformDuration",1,["rand::distributions::uniform::UniformDuration"]],["impl<W> UnwindSafe for WeightedIndex<W>where
        W: UnwindSafe,
    ",1,["rand::distributions::weighted::alias_method::WeightedIndex"]],["impl UnwindSafe for Standard",1,["rand::distributions::Standard"]],["impl<R> UnwindSafe for ReadRng<R>where
        R: UnwindSafe,
    ",1,["rand::rngs::adapter::read::ReadRng"]],["impl !UnwindSafe for ReadError",1,["rand::rngs::adapter::read::ReadError"]],["impl<R, Rsdr> UnwindSafe for ReseedingRng<R, Rsdr>where
        R: UnwindSafe,
        Rsdr: UnwindSafe,
        <R as BlockRngCore>::Results: UnwindSafe,
    ",1,["rand::rngs::adapter::reseeding::ReseedingRng"]],["impl UnwindSafe for StepRng",1,["rand::rngs::mock::StepRng"]],["impl UnwindSafe for IndexVec",1,["rand::seq::index::IndexVec"]],["impl<'a> UnwindSafe for IndexVecIter<'a>",1,["rand::seq::index::IndexVecIter"]],["impl UnwindSafe for IndexVecIntoIter",1,["rand::seq::index::IndexVecIntoIter"]],["impl<'a, S: ?Sized, T> UnwindSafe for SliceChooseIter<'a, S, T>where
        S: RefUnwindSafe,
        T: UnwindSafe,
    ",1,["rand::seq::SliceChooseIter"]]], -"rand_chacha":[["impl UnwindSafe for ChaCha20Core",1,["rand_chacha::chacha::ChaCha20Core"]],["impl UnwindSafe for ChaCha20Rng",1,["rand_chacha::chacha::ChaCha20Rng"]],["impl UnwindSafe for ChaCha12Core",1,["rand_chacha::chacha::ChaCha12Core"]],["impl UnwindSafe for ChaCha12Rng",1,["rand_chacha::chacha::ChaCha12Rng"]],["impl UnwindSafe for ChaCha8Core",1,["rand_chacha::chacha::ChaCha8Core"]],["impl UnwindSafe for ChaCha8Rng",1,["rand_chacha::chacha::ChaCha8Rng"]]], -"rand_core":[["impl<R: ?Sized> UnwindSafe for BlockRng<R>where
        R: UnwindSafe,
        <R as BlockRngCore>::Results: UnwindSafe,
    ",1,["rand_core::block::BlockRng"]],["impl<R: ?Sized> UnwindSafe for BlockRng64<R>where
        R: UnwindSafe,
        <R as BlockRngCore>::Results: UnwindSafe,
    ",1,["rand_core::block::BlockRng64"]],["impl !UnwindSafe for Error",1,["rand_core::error::Error"]],["impl UnwindSafe for OsRng",1,["rand_core::os::OsRng"]]], -"regex":[["impl UnwindSafe for RegexBuilder",1,["regex::re_builder::bytes::RegexBuilder"]],["impl UnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_bytes::RegexSetBuilder"]],["impl<'t> UnwindSafe for Match<'t>",1,["regex::re_bytes::Match"]],["impl UnwindSafe for Regex",1,["regex::re_bytes::Regex"]],["impl<'r, 't> UnwindSafe for Matches<'r, 't>",1,["regex::re_bytes::Matches"]],["impl<'r, 't> UnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_bytes::CaptureMatches"]],["impl<'r, 't> UnwindSafe for Split<'r, 't>",1,["regex::re_bytes::Split"]],["impl<'r, 't> UnwindSafe for SplitN<'r, 't>",1,["regex::re_bytes::SplitN"]],["impl<'r> UnwindSafe for CaptureNames<'r>",1,["regex::re_bytes::CaptureNames"]],["impl UnwindSafe for CaptureLocations",1,["regex::re_bytes::CaptureLocations"]],["impl<'t> UnwindSafe for Captures<'t>",1,["regex::re_bytes::Captures"]],["impl<'c, 't> UnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_bytes::SubCaptureMatches"]],["impl<'a, R> !UnwindSafe for ReplacerRef<'a, R>",1,["regex::re_bytes::ReplacerRef"]],["impl<'t> UnwindSafe for NoExpand<'t>",1,["regex::re_bytes::NoExpand"]],["impl UnwindSafe for RegexSet",1,["regex::re_set::bytes::RegexSet"]],["impl UnwindSafe for SetMatches",1,["regex::re_set::bytes::SetMatches"]],["impl UnwindSafe for SetMatchesIntoIter",1,["regex::re_set::bytes::SetMatchesIntoIter"]],["impl<'a> UnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::bytes::SetMatchesIter"]],["impl UnwindSafe for Error",1,["regex::error::Error"]],["impl UnwindSafe for RegexBuilder",1,["regex::re_builder::unicode::RegexBuilder"]],["impl UnwindSafe for RegexSetBuilder",1,["regex::re_builder::set_unicode::RegexSetBuilder"]],["impl UnwindSafe for RegexSet",1,["regex::re_set::unicode::RegexSet"]],["impl UnwindSafe for SetMatches",1,["regex::re_set::unicode::SetMatches"]],["impl UnwindSafe for SetMatchesIntoIter",1,["regex::re_set::unicode::SetMatchesIntoIter"]],["impl<'a> UnwindSafe for SetMatchesIter<'a>",1,["regex::re_set::unicode::SetMatchesIter"]],["impl<'t> UnwindSafe for Match<'t>",1,["regex::re_unicode::Match"]],["impl UnwindSafe for Regex",1,["regex::re_unicode::Regex"]],["impl<'r> UnwindSafe for CaptureNames<'r>",1,["regex::re_unicode::CaptureNames"]],["impl<'r, 't> UnwindSafe for Split<'r, 't>",1,["regex::re_unicode::Split"]],["impl<'r, 't> UnwindSafe for SplitN<'r, 't>",1,["regex::re_unicode::SplitN"]],["impl UnwindSafe for CaptureLocations",1,["regex::re_unicode::CaptureLocations"]],["impl<'t> UnwindSafe for Captures<'t>",1,["regex::re_unicode::Captures"]],["impl<'c, 't> UnwindSafe for SubCaptureMatches<'c, 't>",1,["regex::re_unicode::SubCaptureMatches"]],["impl<'r, 't> UnwindSafe for CaptureMatches<'r, 't>",1,["regex::re_unicode::CaptureMatches"]],["impl<'r, 't> UnwindSafe for Matches<'r, 't>",1,["regex::re_unicode::Matches"]],["impl<'a, R> !UnwindSafe for ReplacerRef<'a, R>",1,["regex::re_unicode::ReplacerRef"]],["impl<'t> UnwindSafe for NoExpand<'t>",1,["regex::re_unicode::NoExpand"]]], -"regex_syntax":[["impl UnwindSafe for ParserBuilder",1,["regex_syntax::ast::parse::ParserBuilder"]],["impl UnwindSafe for Parser",1,["regex_syntax::ast::parse::Parser"]],["impl UnwindSafe for Printer",1,["regex_syntax::ast::print::Printer"]],["impl UnwindSafe for Error",1,["regex_syntax::ast::Error"]],["impl UnwindSafe for ErrorKind",1,["regex_syntax::ast::ErrorKind"]],["impl UnwindSafe for Span",1,["regex_syntax::ast::Span"]],["impl UnwindSafe for Position",1,["regex_syntax::ast::Position"]],["impl UnwindSafe for WithComments",1,["regex_syntax::ast::WithComments"]],["impl UnwindSafe for Comment",1,["regex_syntax::ast::Comment"]],["impl UnwindSafe for Ast",1,["regex_syntax::ast::Ast"]],["impl UnwindSafe for Alternation",1,["regex_syntax::ast::Alternation"]],["impl UnwindSafe for Concat",1,["regex_syntax::ast::Concat"]],["impl UnwindSafe for Literal",1,["regex_syntax::ast::Literal"]],["impl UnwindSafe for LiteralKind",1,["regex_syntax::ast::LiteralKind"]],["impl UnwindSafe for SpecialLiteralKind",1,["regex_syntax::ast::SpecialLiteralKind"]],["impl UnwindSafe for HexLiteralKind",1,["regex_syntax::ast::HexLiteralKind"]],["impl UnwindSafe for Class",1,["regex_syntax::ast::Class"]],["impl UnwindSafe for ClassPerl",1,["regex_syntax::ast::ClassPerl"]],["impl UnwindSafe for ClassPerlKind",1,["regex_syntax::ast::ClassPerlKind"]],["impl UnwindSafe for ClassAscii",1,["regex_syntax::ast::ClassAscii"]],["impl UnwindSafe for ClassAsciiKind",1,["regex_syntax::ast::ClassAsciiKind"]],["impl UnwindSafe for ClassUnicode",1,["regex_syntax::ast::ClassUnicode"]],["impl UnwindSafe for ClassUnicodeKind",1,["regex_syntax::ast::ClassUnicodeKind"]],["impl UnwindSafe for ClassUnicodeOpKind",1,["regex_syntax::ast::ClassUnicodeOpKind"]],["impl UnwindSafe for ClassBracketed",1,["regex_syntax::ast::ClassBracketed"]],["impl UnwindSafe for ClassSet",1,["regex_syntax::ast::ClassSet"]],["impl UnwindSafe for ClassSetItem",1,["regex_syntax::ast::ClassSetItem"]],["impl UnwindSafe for ClassSetRange",1,["regex_syntax::ast::ClassSetRange"]],["impl UnwindSafe for ClassSetUnion",1,["regex_syntax::ast::ClassSetUnion"]],["impl UnwindSafe for ClassSetBinaryOp",1,["regex_syntax::ast::ClassSetBinaryOp"]],["impl UnwindSafe for ClassSetBinaryOpKind",1,["regex_syntax::ast::ClassSetBinaryOpKind"]],["impl UnwindSafe for Assertion",1,["regex_syntax::ast::Assertion"]],["impl UnwindSafe for AssertionKind",1,["regex_syntax::ast::AssertionKind"]],["impl UnwindSafe for Repetition",1,["regex_syntax::ast::Repetition"]],["impl UnwindSafe for RepetitionOp",1,["regex_syntax::ast::RepetitionOp"]],["impl UnwindSafe for RepetitionKind",1,["regex_syntax::ast::RepetitionKind"]],["impl UnwindSafe for RepetitionRange",1,["regex_syntax::ast::RepetitionRange"]],["impl UnwindSafe for Group",1,["regex_syntax::ast::Group"]],["impl UnwindSafe for GroupKind",1,["regex_syntax::ast::GroupKind"]],["impl UnwindSafe for CaptureName",1,["regex_syntax::ast::CaptureName"]],["impl UnwindSafe for SetFlags",1,["regex_syntax::ast::SetFlags"]],["impl UnwindSafe for Flags",1,["regex_syntax::ast::Flags"]],["impl UnwindSafe for FlagsItem",1,["regex_syntax::ast::FlagsItem"]],["impl UnwindSafe for FlagsItemKind",1,["regex_syntax::ast::FlagsItemKind"]],["impl UnwindSafe for Flag",1,["regex_syntax::ast::Flag"]],["impl UnwindSafe for Error",1,["regex_syntax::error::Error"]],["impl UnwindSafe for Literals",1,["regex_syntax::hir::literal::Literals"]],["impl UnwindSafe for Literal",1,["regex_syntax::hir::literal::Literal"]],["impl UnwindSafe for Printer",1,["regex_syntax::hir::print::Printer"]],["impl UnwindSafe for TranslatorBuilder",1,["regex_syntax::hir::translate::TranslatorBuilder"]],["impl UnwindSafe for Translator",1,["regex_syntax::hir::translate::Translator"]],["impl UnwindSafe for CaseFoldError",1,["regex_syntax::unicode::CaseFoldError"]],["impl UnwindSafe for Error",1,["regex_syntax::hir::Error"]],["impl UnwindSafe for ErrorKind",1,["regex_syntax::hir::ErrorKind"]],["impl UnwindSafe for Hir",1,["regex_syntax::hir::Hir"]],["impl UnwindSafe for HirKind",1,["regex_syntax::hir::HirKind"]],["impl UnwindSafe for Literal",1,["regex_syntax::hir::Literal"]],["impl UnwindSafe for Class",1,["regex_syntax::hir::Class"]],["impl UnwindSafe for ClassUnicode",1,["regex_syntax::hir::ClassUnicode"]],["impl<'a> UnwindSafe for ClassUnicodeIter<'a>",1,["regex_syntax::hir::ClassUnicodeIter"]],["impl UnwindSafe for ClassUnicodeRange",1,["regex_syntax::hir::ClassUnicodeRange"]],["impl UnwindSafe for ClassBytes",1,["regex_syntax::hir::ClassBytes"]],["impl<'a> UnwindSafe for ClassBytesIter<'a>",1,["regex_syntax::hir::ClassBytesIter"]],["impl UnwindSafe for ClassBytesRange",1,["regex_syntax::hir::ClassBytesRange"]],["impl UnwindSafe for Anchor",1,["regex_syntax::hir::Anchor"]],["impl UnwindSafe for WordBoundary",1,["regex_syntax::hir::WordBoundary"]],["impl UnwindSafe for Group",1,["regex_syntax::hir::Group"]],["impl UnwindSafe for GroupKind",1,["regex_syntax::hir::GroupKind"]],["impl UnwindSafe for Repetition",1,["regex_syntax::hir::Repetition"]],["impl UnwindSafe for RepetitionKind",1,["regex_syntax::hir::RepetitionKind"]],["impl UnwindSafe for RepetitionRange",1,["regex_syntax::hir::RepetitionRange"]],["impl UnwindSafe for ParserBuilder",1,["regex_syntax::parser::ParserBuilder"]],["impl UnwindSafe for Parser",1,["regex_syntax::parser::Parser"]],["impl UnwindSafe for UnicodeWordError",1,["regex_syntax::unicode::UnicodeWordError"]],["impl UnwindSafe for Utf8Sequence",1,["regex_syntax::utf8::Utf8Sequence"]],["impl UnwindSafe for Utf8Range",1,["regex_syntax::utf8::Utf8Range"]],["impl UnwindSafe for Utf8Sequences",1,["regex_syntax::utf8::Utf8Sequences"]]], -"rlp":[["impl UnwindSafe for DecoderError",1,["rlp::error::DecoderError"]],["impl UnwindSafe for Prototype",1,["rlp::rlpin::Prototype"]],["impl UnwindSafe for PayloadInfo",1,["rlp::rlpin::PayloadInfo"]],["impl<'a> UnwindSafe for Rlp<'a>",1,["rlp::rlpin::Rlp"]],["impl<'a, 'view> !UnwindSafe for RlpIterator<'a, 'view>",1,["rlp::rlpin::RlpIterator"]],["impl UnwindSafe for RlpStream",1,["rlp::stream::RlpStream"]]], -"rustc_hex":[["impl<T> UnwindSafe for ToHexIter<T>where
        T: UnwindSafe,
    ",1,["rustc_hex::ToHexIter"]],["impl UnwindSafe for FromHexError",1,["rustc_hex::FromHexError"]],["impl<'a> UnwindSafe for FromHexIter<'a>",1,["rustc_hex::FromHexIter"]]], -"scan_fmt":[["impl UnwindSafe for ScanError",1,["scan_fmt::parse::ScanError"]]], -"scopeguard":[["impl UnwindSafe for Always",1,["scopeguard::Always"]],["impl<T, F, S> UnwindSafe for ScopeGuard<T, F, S>where
        F: UnwindSafe,
        T: UnwindSafe,
    ",1,["scopeguard::ScopeGuard"]]], -"serde":[["impl UnwindSafe for Error",1,["serde::de::value::Error"]],["impl<E> UnwindSafe for UnitDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::UnitDeserializer"]],["impl<E> UnwindSafe for BoolDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::BoolDeserializer"]],["impl<E> UnwindSafe for I8Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::I8Deserializer"]],["impl<E> UnwindSafe for I16Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::I16Deserializer"]],["impl<E> UnwindSafe for I32Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::I32Deserializer"]],["impl<E> UnwindSafe for I64Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::I64Deserializer"]],["impl<E> UnwindSafe for IsizeDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::IsizeDeserializer"]],["impl<E> UnwindSafe for U8Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::U8Deserializer"]],["impl<E> UnwindSafe for U16Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::U16Deserializer"]],["impl<E> UnwindSafe for U64Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::U64Deserializer"]],["impl<E> UnwindSafe for UsizeDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::UsizeDeserializer"]],["impl<E> UnwindSafe for F32Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::F32Deserializer"]],["impl<E> UnwindSafe for F64Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::F64Deserializer"]],["impl<E> UnwindSafe for CharDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::CharDeserializer"]],["impl<E> UnwindSafe for I128Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::I128Deserializer"]],["impl<E> UnwindSafe for U128Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::U128Deserializer"]],["impl<E> UnwindSafe for U32Deserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::U32Deserializer"]],["impl<'a, E> UnwindSafe for StrDeserializer<'a, E>where
        E: UnwindSafe,
    ",1,["serde::de::value::StrDeserializer"]],["impl<'de, E> UnwindSafe for BorrowedStrDeserializer<'de, E>where
        E: UnwindSafe,
    ",1,["serde::de::value::BorrowedStrDeserializer"]],["impl<E> UnwindSafe for StringDeserializer<E>where
        E: UnwindSafe,
    ",1,["serde::de::value::StringDeserializer"]],["impl<'a, E> UnwindSafe for CowStrDeserializer<'a, E>where
        E: UnwindSafe,
    ",1,["serde::de::value::CowStrDeserializer"]],["impl<'a, E> UnwindSafe for BytesDeserializer<'a, E>where
        E: UnwindSafe,
    ",1,["serde::de::value::BytesDeserializer"]],["impl<'de, E> UnwindSafe for BorrowedBytesDeserializer<'de, E>where
        E: UnwindSafe,
    ",1,["serde::de::value::BorrowedBytesDeserializer"]],["impl<I, E> UnwindSafe for SeqDeserializer<I, E>where
        E: UnwindSafe,
        I: UnwindSafe,
    ",1,["serde::de::value::SeqDeserializer"]],["impl<A> UnwindSafe for SeqAccessDeserializer<A>where
        A: UnwindSafe,
    ",1,["serde::de::value::SeqAccessDeserializer"]],["impl<'de, I, E> UnwindSafe for MapDeserializer<'de, I, E>where
        E: UnwindSafe,
        I: UnwindSafe,
        <<I as Iterator>::Item as Pair>::Second: UnwindSafe,
    ",1,["serde::de::value::MapDeserializer"]],["impl<A> UnwindSafe for MapAccessDeserializer<A>where
        A: UnwindSafe,
    ",1,["serde::de::value::MapAccessDeserializer"]],["impl<A> UnwindSafe for EnumAccessDeserializer<A>where
        A: UnwindSafe,
    ",1,["serde::de::value::EnumAccessDeserializer"]],["impl UnwindSafe for IgnoredAny",1,["serde::de::ignored_any::IgnoredAny"]],["impl<'a> UnwindSafe for Unexpected<'a>",1,["serde::de::Unexpected"]],["impl<Ok, Error> UnwindSafe for Impossible<Ok, Error>where
        Error: UnwindSafe,
        Ok: UnwindSafe,
    ",1,["serde::ser::impossible::Impossible"]]], -"sha3":[["impl UnwindSafe for Keccak224Core",1,["sha3::Keccak224Core"]],["impl UnwindSafe for Keccak256Core",1,["sha3::Keccak256Core"]],["impl UnwindSafe for Keccak384Core",1,["sha3::Keccak384Core"]],["impl UnwindSafe for Keccak512Core",1,["sha3::Keccak512Core"]],["impl UnwindSafe for Keccak256FullCore",1,["sha3::Keccak256FullCore"]],["impl UnwindSafe for Sha3_224Core",1,["sha3::Sha3_224Core"]],["impl UnwindSafe for Sha3_256Core",1,["sha3::Sha3_256Core"]],["impl UnwindSafe for Sha3_384Core",1,["sha3::Sha3_384Core"]],["impl UnwindSafe for Sha3_512Core",1,["sha3::Sha3_512Core"]],["impl UnwindSafe for Shake128Core",1,["sha3::Shake128Core"]],["impl UnwindSafe for Shake128ReaderCore",1,["sha3::Shake128ReaderCore"]],["impl UnwindSafe for Shake256Core",1,["sha3::Shake256Core"]],["impl UnwindSafe for Shake256ReaderCore",1,["sha3::Shake256ReaderCore"]],["impl UnwindSafe for CShake128Core",1,["sha3::CShake128Core"]],["impl UnwindSafe for CShake128ReaderCore",1,["sha3::CShake128ReaderCore"]],["impl UnwindSafe for CShake256Core",1,["sha3::CShake256Core"]],["impl UnwindSafe for CShake256ReaderCore",1,["sha3::CShake256ReaderCore"]]], -"shale":[["impl UnwindSafe for CompactHeader",1,["shale::compact::CompactHeader"]],["impl UnwindSafe for CompactSpaceHeader",1,["shale::compact::CompactSpaceHeader"]],["impl<T> !UnwindSafe for CompactSpace<T>",1,["shale::compact::CompactSpace"]],["impl UnwindSafe for ShaleError",1,["shale::ShaleError"]],["impl UnwindSafe for DiskWrite",1,["shale::DiskWrite"]],["impl<T: ?Sized> UnwindSafe for ObjPtr<T>where
        T: UnwindSafe,
    ",1,["shale::ObjPtr"]],["impl<T> !UnwindSafe for Obj<T>",1,["shale::Obj"]],["impl<'a, T> !UnwindSafe for ObjRef<'a, T>",1,["shale::ObjRef"]],["impl<T> !UnwindSafe for MummyObj<T>",1,["shale::MummyObj"]],["impl !UnwindSafe for PlainMem",1,["shale::PlainMem"]],["impl<T> !UnwindSafe for ObjCache<T>",1,["shale::ObjCache"]]], -"slab":[["impl<T> UnwindSafe for Slab<T>where
        T: UnwindSafe,
    ",1,["slab::Slab"]],["impl<'a, T> !UnwindSafe for VacantEntry<'a, T>",1,["slab::VacantEntry"]],["impl<T> UnwindSafe for IntoIter<T>where
        T: UnwindSafe + RefUnwindSafe,
    ",1,["slab::IntoIter"]],["impl<'a, T> UnwindSafe for Iter<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::Iter"]],["impl<'a, T> !UnwindSafe for IterMut<'a, T>",1,["slab::IterMut"]],["impl<'a, T> UnwindSafe for Drain<'a, T>where
        T: RefUnwindSafe,
    ",1,["slab::Drain"]]], -"smallvec":[["impl UnwindSafe for CollectionAllocErr",1,["smallvec::CollectionAllocErr"]],["impl<'a, T> UnwindSafe for Drain<'a, T>where
        T: RefUnwindSafe,
        <T as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::Drain"]],["impl<A> UnwindSafe for SmallVec<A>where
        A: UnwindSafe,
        <A as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::SmallVec"]],["impl<A> UnwindSafe for IntoIter<A>where
        A: UnwindSafe,
        <A as Array>::Item: RefUnwindSafe,
    ",1,["smallvec::IntoIter"]]], -"syn":[["impl UnwindSafe for Underscore",1,["syn::token::Underscore"]],["impl UnwindSafe for Abstract",1,["syn::token::Abstract"]],["impl UnwindSafe for As",1,["syn::token::As"]],["impl UnwindSafe for Async",1,["syn::token::Async"]],["impl UnwindSafe for Auto",1,["syn::token::Auto"]],["impl UnwindSafe for Await",1,["syn::token::Await"]],["impl UnwindSafe for Become",1,["syn::token::Become"]],["impl UnwindSafe for Box",1,["syn::token::Box"]],["impl UnwindSafe for Break",1,["syn::token::Break"]],["impl UnwindSafe for Const",1,["syn::token::Const"]],["impl UnwindSafe for Continue",1,["syn::token::Continue"]],["impl UnwindSafe for Crate",1,["syn::token::Crate"]],["impl UnwindSafe for Default",1,["syn::token::Default"]],["impl UnwindSafe for Do",1,["syn::token::Do"]],["impl UnwindSafe for Dyn",1,["syn::token::Dyn"]],["impl UnwindSafe for Else",1,["syn::token::Else"]],["impl UnwindSafe for Enum",1,["syn::token::Enum"]],["impl UnwindSafe for Extern",1,["syn::token::Extern"]],["impl UnwindSafe for Final",1,["syn::token::Final"]],["impl UnwindSafe for Fn",1,["syn::token::Fn"]],["impl UnwindSafe for For",1,["syn::token::For"]],["impl UnwindSafe for If",1,["syn::token::If"]],["impl UnwindSafe for Impl",1,["syn::token::Impl"]],["impl UnwindSafe for In",1,["syn::token::In"]],["impl UnwindSafe for Let",1,["syn::token::Let"]],["impl UnwindSafe for Loop",1,["syn::token::Loop"]],["impl UnwindSafe for Macro",1,["syn::token::Macro"]],["impl UnwindSafe for Match",1,["syn::token::Match"]],["impl UnwindSafe for Mod",1,["syn::token::Mod"]],["impl UnwindSafe for Move",1,["syn::token::Move"]],["impl UnwindSafe for Mut",1,["syn::token::Mut"]],["impl UnwindSafe for Override",1,["syn::token::Override"]],["impl UnwindSafe for Priv",1,["syn::token::Priv"]],["impl UnwindSafe for Pub",1,["syn::token::Pub"]],["impl UnwindSafe for Ref",1,["syn::token::Ref"]],["impl UnwindSafe for Return",1,["syn::token::Return"]],["impl UnwindSafe for SelfType",1,["syn::token::SelfType"]],["impl UnwindSafe for SelfValue",1,["syn::token::SelfValue"]],["impl UnwindSafe for Static",1,["syn::token::Static"]],["impl UnwindSafe for Struct",1,["syn::token::Struct"]],["impl UnwindSafe for Super",1,["syn::token::Super"]],["impl UnwindSafe for Trait",1,["syn::token::Trait"]],["impl UnwindSafe for Try",1,["syn::token::Try"]],["impl UnwindSafe for Type",1,["syn::token::Type"]],["impl UnwindSafe for Typeof",1,["syn::token::Typeof"]],["impl UnwindSafe for Union",1,["syn::token::Union"]],["impl UnwindSafe for Unsafe",1,["syn::token::Unsafe"]],["impl UnwindSafe for Unsized",1,["syn::token::Unsized"]],["impl UnwindSafe for Use",1,["syn::token::Use"]],["impl UnwindSafe for Virtual",1,["syn::token::Virtual"]],["impl UnwindSafe for Where",1,["syn::token::Where"]],["impl UnwindSafe for While",1,["syn::token::While"]],["impl UnwindSafe for Yield",1,["syn::token::Yield"]],["impl UnwindSafe for Add",1,["syn::token::Add"]],["impl UnwindSafe for AddEq",1,["syn::token::AddEq"]],["impl UnwindSafe for And",1,["syn::token::And"]],["impl UnwindSafe for AndAnd",1,["syn::token::AndAnd"]],["impl UnwindSafe for AndEq",1,["syn::token::AndEq"]],["impl UnwindSafe for At",1,["syn::token::At"]],["impl UnwindSafe for Bang",1,["syn::token::Bang"]],["impl UnwindSafe for Caret",1,["syn::token::Caret"]],["impl UnwindSafe for CaretEq",1,["syn::token::CaretEq"]],["impl UnwindSafe for Colon",1,["syn::token::Colon"]],["impl UnwindSafe for Colon2",1,["syn::token::Colon2"]],["impl UnwindSafe for Comma",1,["syn::token::Comma"]],["impl UnwindSafe for Div",1,["syn::token::Div"]],["impl UnwindSafe for DivEq",1,["syn::token::DivEq"]],["impl UnwindSafe for Dollar",1,["syn::token::Dollar"]],["impl UnwindSafe for Dot",1,["syn::token::Dot"]],["impl UnwindSafe for Dot2",1,["syn::token::Dot2"]],["impl UnwindSafe for Dot3",1,["syn::token::Dot3"]],["impl UnwindSafe for DotDotEq",1,["syn::token::DotDotEq"]],["impl UnwindSafe for Eq",1,["syn::token::Eq"]],["impl UnwindSafe for EqEq",1,["syn::token::EqEq"]],["impl UnwindSafe for Ge",1,["syn::token::Ge"]],["impl UnwindSafe for Gt",1,["syn::token::Gt"]],["impl UnwindSafe for Le",1,["syn::token::Le"]],["impl UnwindSafe for Lt",1,["syn::token::Lt"]],["impl UnwindSafe for MulEq",1,["syn::token::MulEq"]],["impl UnwindSafe for Ne",1,["syn::token::Ne"]],["impl UnwindSafe for Or",1,["syn::token::Or"]],["impl UnwindSafe for OrEq",1,["syn::token::OrEq"]],["impl UnwindSafe for OrOr",1,["syn::token::OrOr"]],["impl UnwindSafe for Pound",1,["syn::token::Pound"]],["impl UnwindSafe for Question",1,["syn::token::Question"]],["impl UnwindSafe for RArrow",1,["syn::token::RArrow"]],["impl UnwindSafe for LArrow",1,["syn::token::LArrow"]],["impl UnwindSafe for Rem",1,["syn::token::Rem"]],["impl UnwindSafe for RemEq",1,["syn::token::RemEq"]],["impl UnwindSafe for FatArrow",1,["syn::token::FatArrow"]],["impl UnwindSafe for Semi",1,["syn::token::Semi"]],["impl UnwindSafe for Shl",1,["syn::token::Shl"]],["impl UnwindSafe for ShlEq",1,["syn::token::ShlEq"]],["impl UnwindSafe for Shr",1,["syn::token::Shr"]],["impl UnwindSafe for ShrEq",1,["syn::token::ShrEq"]],["impl UnwindSafe for Star",1,["syn::token::Star"]],["impl UnwindSafe for Sub",1,["syn::token::Sub"]],["impl UnwindSafe for SubEq",1,["syn::token::SubEq"]],["impl UnwindSafe for Tilde",1,["syn::token::Tilde"]],["impl UnwindSafe for Brace",1,["syn::token::Brace"]],["impl UnwindSafe for Bracket",1,["syn::token::Bracket"]],["impl UnwindSafe for Paren",1,["syn::token::Paren"]],["impl UnwindSafe for Group",1,["syn::token::Group"]],["impl UnwindSafe for Attribute",1,["syn::attr::Attribute"]],["impl UnwindSafe for AttrStyle",1,["syn::attr::AttrStyle"]],["impl UnwindSafe for Meta",1,["syn::attr::Meta"]],["impl UnwindSafe for MetaList",1,["syn::attr::MetaList"]],["impl UnwindSafe for MetaNameValue",1,["syn::attr::MetaNameValue"]],["impl UnwindSafe for NestedMeta",1,["syn::attr::NestedMeta"]],["impl UnwindSafe for Variant",1,["syn::data::Variant"]],["impl UnwindSafe for Fields",1,["syn::data::Fields"]],["impl UnwindSafe for FieldsNamed",1,["syn::data::FieldsNamed"]],["impl UnwindSafe for FieldsUnnamed",1,["syn::data::FieldsUnnamed"]],["impl UnwindSafe for Field",1,["syn::data::Field"]],["impl UnwindSafe for Visibility",1,["syn::data::Visibility"]],["impl UnwindSafe for VisPublic",1,["syn::data::VisPublic"]],["impl UnwindSafe for VisCrate",1,["syn::data::VisCrate"]],["impl UnwindSafe for VisRestricted",1,["syn::data::VisRestricted"]],["impl UnwindSafe for Expr",1,["syn::expr::Expr"]],["impl UnwindSafe for ExprArray",1,["syn::expr::ExprArray"]],["impl UnwindSafe for ExprAssign",1,["syn::expr::ExprAssign"]],["impl UnwindSafe for ExprAssignOp",1,["syn::expr::ExprAssignOp"]],["impl UnwindSafe for ExprAsync",1,["syn::expr::ExprAsync"]],["impl UnwindSafe for ExprAwait",1,["syn::expr::ExprAwait"]],["impl UnwindSafe for ExprBinary",1,["syn::expr::ExprBinary"]],["impl UnwindSafe for ExprBlock",1,["syn::expr::ExprBlock"]],["impl UnwindSafe for ExprBox",1,["syn::expr::ExprBox"]],["impl UnwindSafe for ExprBreak",1,["syn::expr::ExprBreak"]],["impl UnwindSafe for ExprCall",1,["syn::expr::ExprCall"]],["impl UnwindSafe for ExprCast",1,["syn::expr::ExprCast"]],["impl UnwindSafe for ExprClosure",1,["syn::expr::ExprClosure"]],["impl UnwindSafe for ExprContinue",1,["syn::expr::ExprContinue"]],["impl UnwindSafe for ExprField",1,["syn::expr::ExprField"]],["impl UnwindSafe for ExprForLoop",1,["syn::expr::ExprForLoop"]],["impl UnwindSafe for ExprGroup",1,["syn::expr::ExprGroup"]],["impl UnwindSafe for ExprIf",1,["syn::expr::ExprIf"]],["impl UnwindSafe for ExprIndex",1,["syn::expr::ExprIndex"]],["impl UnwindSafe for ExprLet",1,["syn::expr::ExprLet"]],["impl UnwindSafe for ExprLit",1,["syn::expr::ExprLit"]],["impl UnwindSafe for ExprLoop",1,["syn::expr::ExprLoop"]],["impl UnwindSafe for ExprMacro",1,["syn::expr::ExprMacro"]],["impl UnwindSafe for ExprMatch",1,["syn::expr::ExprMatch"]],["impl UnwindSafe for ExprMethodCall",1,["syn::expr::ExprMethodCall"]],["impl UnwindSafe for ExprParen",1,["syn::expr::ExprParen"]],["impl UnwindSafe for ExprPath",1,["syn::expr::ExprPath"]],["impl UnwindSafe for ExprRange",1,["syn::expr::ExprRange"]],["impl UnwindSafe for ExprReference",1,["syn::expr::ExprReference"]],["impl UnwindSafe for ExprRepeat",1,["syn::expr::ExprRepeat"]],["impl UnwindSafe for ExprReturn",1,["syn::expr::ExprReturn"]],["impl UnwindSafe for ExprStruct",1,["syn::expr::ExprStruct"]],["impl UnwindSafe for ExprTry",1,["syn::expr::ExprTry"]],["impl UnwindSafe for ExprTryBlock",1,["syn::expr::ExprTryBlock"]],["impl UnwindSafe for ExprTuple",1,["syn::expr::ExprTuple"]],["impl UnwindSafe for ExprType",1,["syn::expr::ExprType"]],["impl UnwindSafe for ExprUnary",1,["syn::expr::ExprUnary"]],["impl UnwindSafe for ExprUnsafe",1,["syn::expr::ExprUnsafe"]],["impl UnwindSafe for ExprWhile",1,["syn::expr::ExprWhile"]],["impl UnwindSafe for ExprYield",1,["syn::expr::ExprYield"]],["impl UnwindSafe for Member",1,["syn::expr::Member"]],["impl UnwindSafe for Index",1,["syn::expr::Index"]],["impl UnwindSafe for MethodTurbofish",1,["syn::expr::MethodTurbofish"]],["impl UnwindSafe for GenericMethodArgument",1,["syn::expr::GenericMethodArgument"]],["impl UnwindSafe for FieldValue",1,["syn::expr::FieldValue"]],["impl UnwindSafe for Label",1,["syn::expr::Label"]],["impl UnwindSafe for Arm",1,["syn::expr::Arm"]],["impl UnwindSafe for RangeLimits",1,["syn::expr::RangeLimits"]],["impl UnwindSafe for Generics",1,["syn::generics::Generics"]],["impl UnwindSafe for GenericParam",1,["syn::generics::GenericParam"]],["impl UnwindSafe for TypeParam",1,["syn::generics::TypeParam"]],["impl UnwindSafe for LifetimeDef",1,["syn::generics::LifetimeDef"]],["impl UnwindSafe for ConstParam",1,["syn::generics::ConstParam"]],["impl<'a> UnwindSafe for ImplGenerics<'a>",1,["syn::generics::ImplGenerics"]],["impl<'a> UnwindSafe for TypeGenerics<'a>",1,["syn::generics::TypeGenerics"]],["impl<'a> UnwindSafe for Turbofish<'a>",1,["syn::generics::Turbofish"]],["impl UnwindSafe for BoundLifetimes",1,["syn::generics::BoundLifetimes"]],["impl UnwindSafe for TypeParamBound",1,["syn::generics::TypeParamBound"]],["impl UnwindSafe for TraitBound",1,["syn::generics::TraitBound"]],["impl UnwindSafe for TraitBoundModifier",1,["syn::generics::TraitBoundModifier"]],["impl UnwindSafe for WhereClause",1,["syn::generics::WhereClause"]],["impl UnwindSafe for WherePredicate",1,["syn::generics::WherePredicate"]],["impl UnwindSafe for PredicateType",1,["syn::generics::PredicateType"]],["impl UnwindSafe for PredicateLifetime",1,["syn::generics::PredicateLifetime"]],["impl UnwindSafe for PredicateEq",1,["syn::generics::PredicateEq"]],["impl UnwindSafe for Item",1,["syn::item::Item"]],["impl UnwindSafe for ItemConst",1,["syn::item::ItemConst"]],["impl UnwindSafe for ItemEnum",1,["syn::item::ItemEnum"]],["impl UnwindSafe for ItemExternCrate",1,["syn::item::ItemExternCrate"]],["impl UnwindSafe for ItemFn",1,["syn::item::ItemFn"]],["impl UnwindSafe for ItemForeignMod",1,["syn::item::ItemForeignMod"]],["impl UnwindSafe for ItemImpl",1,["syn::item::ItemImpl"]],["impl UnwindSafe for ItemMacro",1,["syn::item::ItemMacro"]],["impl UnwindSafe for ItemMacro2",1,["syn::item::ItemMacro2"]],["impl UnwindSafe for ItemMod",1,["syn::item::ItemMod"]],["impl UnwindSafe for ItemStatic",1,["syn::item::ItemStatic"]],["impl UnwindSafe for ItemStruct",1,["syn::item::ItemStruct"]],["impl UnwindSafe for ItemTrait",1,["syn::item::ItemTrait"]],["impl UnwindSafe for ItemTraitAlias",1,["syn::item::ItemTraitAlias"]],["impl UnwindSafe for ItemType",1,["syn::item::ItemType"]],["impl UnwindSafe for ItemUnion",1,["syn::item::ItemUnion"]],["impl UnwindSafe for ItemUse",1,["syn::item::ItemUse"]],["impl UnwindSafe for UseTree",1,["syn::item::UseTree"]],["impl UnwindSafe for UsePath",1,["syn::item::UsePath"]],["impl UnwindSafe for UseName",1,["syn::item::UseName"]],["impl UnwindSafe for UseRename",1,["syn::item::UseRename"]],["impl UnwindSafe for UseGlob",1,["syn::item::UseGlob"]],["impl UnwindSafe for UseGroup",1,["syn::item::UseGroup"]],["impl UnwindSafe for ForeignItem",1,["syn::item::ForeignItem"]],["impl UnwindSafe for ForeignItemFn",1,["syn::item::ForeignItemFn"]],["impl UnwindSafe for ForeignItemStatic",1,["syn::item::ForeignItemStatic"]],["impl UnwindSafe for ForeignItemType",1,["syn::item::ForeignItemType"]],["impl UnwindSafe for ForeignItemMacro",1,["syn::item::ForeignItemMacro"]],["impl UnwindSafe for TraitItem",1,["syn::item::TraitItem"]],["impl UnwindSafe for TraitItemConst",1,["syn::item::TraitItemConst"]],["impl UnwindSafe for TraitItemMethod",1,["syn::item::TraitItemMethod"]],["impl UnwindSafe for TraitItemType",1,["syn::item::TraitItemType"]],["impl UnwindSafe for TraitItemMacro",1,["syn::item::TraitItemMacro"]],["impl UnwindSafe for ImplItem",1,["syn::item::ImplItem"]],["impl UnwindSafe for ImplItemConst",1,["syn::item::ImplItemConst"]],["impl UnwindSafe for ImplItemMethod",1,["syn::item::ImplItemMethod"]],["impl UnwindSafe for ImplItemType",1,["syn::item::ImplItemType"]],["impl UnwindSafe for ImplItemMacro",1,["syn::item::ImplItemMacro"]],["impl UnwindSafe for Signature",1,["syn::item::Signature"]],["impl UnwindSafe for FnArg",1,["syn::item::FnArg"]],["impl UnwindSafe for Receiver",1,["syn::item::Receiver"]],["impl UnwindSafe for File",1,["syn::file::File"]],["impl UnwindSafe for Lifetime",1,["syn::lifetime::Lifetime"]],["impl UnwindSafe for Lit",1,["syn::lit::Lit"]],["impl UnwindSafe for LitStr",1,["syn::lit::LitStr"]],["impl UnwindSafe for LitByteStr",1,["syn::lit::LitByteStr"]],["impl UnwindSafe for LitByte",1,["syn::lit::LitByte"]],["impl UnwindSafe for LitChar",1,["syn::lit::LitChar"]],["impl UnwindSafe for LitInt",1,["syn::lit::LitInt"]],["impl UnwindSafe for LitFloat",1,["syn::lit::LitFloat"]],["impl UnwindSafe for LitBool",1,["syn::lit::LitBool"]],["impl UnwindSafe for StrStyle",1,["syn::lit::StrStyle"]],["impl UnwindSafe for Macro",1,["syn::mac::Macro"]],["impl UnwindSafe for MacroDelimiter",1,["syn::mac::MacroDelimiter"]],["impl UnwindSafe for DeriveInput",1,["syn::derive::DeriveInput"]],["impl UnwindSafe for Data",1,["syn::derive::Data"]],["impl UnwindSafe for DataStruct",1,["syn::derive::DataStruct"]],["impl UnwindSafe for DataEnum",1,["syn::derive::DataEnum"]],["impl UnwindSafe for DataUnion",1,["syn::derive::DataUnion"]],["impl UnwindSafe for BinOp",1,["syn::op::BinOp"]],["impl UnwindSafe for UnOp",1,["syn::op::UnOp"]],["impl UnwindSafe for Block",1,["syn::stmt::Block"]],["impl UnwindSafe for Stmt",1,["syn::stmt::Stmt"]],["impl UnwindSafe for Local",1,["syn::stmt::Local"]],["impl UnwindSafe for Type",1,["syn::ty::Type"]],["impl UnwindSafe for TypeArray",1,["syn::ty::TypeArray"]],["impl UnwindSafe for TypeBareFn",1,["syn::ty::TypeBareFn"]],["impl UnwindSafe for TypeGroup",1,["syn::ty::TypeGroup"]],["impl UnwindSafe for TypeImplTrait",1,["syn::ty::TypeImplTrait"]],["impl UnwindSafe for TypeInfer",1,["syn::ty::TypeInfer"]],["impl UnwindSafe for TypeMacro",1,["syn::ty::TypeMacro"]],["impl UnwindSafe for TypeNever",1,["syn::ty::TypeNever"]],["impl UnwindSafe for TypeParen",1,["syn::ty::TypeParen"]],["impl UnwindSafe for TypePath",1,["syn::ty::TypePath"]],["impl UnwindSafe for TypePtr",1,["syn::ty::TypePtr"]],["impl UnwindSafe for TypeReference",1,["syn::ty::TypeReference"]],["impl UnwindSafe for TypeSlice",1,["syn::ty::TypeSlice"]],["impl UnwindSafe for TypeTraitObject",1,["syn::ty::TypeTraitObject"]],["impl UnwindSafe for TypeTuple",1,["syn::ty::TypeTuple"]],["impl UnwindSafe for Abi",1,["syn::ty::Abi"]],["impl UnwindSafe for BareFnArg",1,["syn::ty::BareFnArg"]],["impl UnwindSafe for Variadic",1,["syn::ty::Variadic"]],["impl UnwindSafe for ReturnType",1,["syn::ty::ReturnType"]],["impl UnwindSafe for Pat",1,["syn::pat::Pat"]],["impl UnwindSafe for PatBox",1,["syn::pat::PatBox"]],["impl UnwindSafe for PatIdent",1,["syn::pat::PatIdent"]],["impl UnwindSafe for PatLit",1,["syn::pat::PatLit"]],["impl UnwindSafe for PatMacro",1,["syn::pat::PatMacro"]],["impl UnwindSafe for PatOr",1,["syn::pat::PatOr"]],["impl UnwindSafe for PatPath",1,["syn::pat::PatPath"]],["impl UnwindSafe for PatRange",1,["syn::pat::PatRange"]],["impl UnwindSafe for PatReference",1,["syn::pat::PatReference"]],["impl UnwindSafe for PatRest",1,["syn::pat::PatRest"]],["impl UnwindSafe for PatSlice",1,["syn::pat::PatSlice"]],["impl UnwindSafe for PatStruct",1,["syn::pat::PatStruct"]],["impl UnwindSafe for PatTuple",1,["syn::pat::PatTuple"]],["impl UnwindSafe for PatTupleStruct",1,["syn::pat::PatTupleStruct"]],["impl UnwindSafe for PatType",1,["syn::pat::PatType"]],["impl UnwindSafe for PatWild",1,["syn::pat::PatWild"]],["impl UnwindSafe for FieldPat",1,["syn::pat::FieldPat"]],["impl UnwindSafe for Path",1,["syn::path::Path"]],["impl UnwindSafe for PathSegment",1,["syn::path::PathSegment"]],["impl UnwindSafe for PathArguments",1,["syn::path::PathArguments"]],["impl UnwindSafe for GenericArgument",1,["syn::path::GenericArgument"]],["impl UnwindSafe for AngleBracketedGenericArguments",1,["syn::path::AngleBracketedGenericArguments"]],["impl UnwindSafe for Binding",1,["syn::path::Binding"]],["impl UnwindSafe for Constraint",1,["syn::path::Constraint"]],["impl UnwindSafe for ParenthesizedGenericArguments",1,["syn::path::ParenthesizedGenericArguments"]],["impl UnwindSafe for QSelf",1,["syn::path::QSelf"]],["impl UnwindSafe for TokenBuffer",1,["syn::buffer::TokenBuffer"]],["impl<'a> UnwindSafe for Cursor<'a>",1,["syn::buffer::Cursor"]],["impl<T, P> UnwindSafe for Punctuated<T, P>where
        P: UnwindSafe,
        T: UnwindSafe,
    ",1,["syn::punctuated::Punctuated"]],["impl<'a, T, P> UnwindSafe for Pairs<'a, T, P>where
        P: RefUnwindSafe,
        T: RefUnwindSafe,
    ",1,["syn::punctuated::Pairs"]],["impl<'a, T, P> !UnwindSafe for PairsMut<'a, T, P>",1,["syn::punctuated::PairsMut"]],["impl<T, P> UnwindSafe for IntoPairs<T, P>where
        P: UnwindSafe + RefUnwindSafe,
        T: UnwindSafe + RefUnwindSafe,
    ",1,["syn::punctuated::IntoPairs"]],["impl<T> UnwindSafe for IntoIter<T>where
        T: UnwindSafe + RefUnwindSafe,
    ",1,["syn::punctuated::IntoIter"]],["impl<'a, T> !UnwindSafe for Iter<'a, T>",1,["syn::punctuated::Iter"]],["impl<'a, T> !UnwindSafe for IterMut<'a, T>",1,["syn::punctuated::IterMut"]],["impl<T, P> UnwindSafe for Pair<T, P>where
        P: UnwindSafe,
        T: UnwindSafe,
    ",1,["syn::punctuated::Pair"]],["impl<'a> UnwindSafe for Lookahead1<'a>",1,["syn::lookahead::Lookahead1"]],["impl UnwindSafe for Error",1,["syn::error::Error"]],["impl<'a> !UnwindSafe for ParseBuffer<'a>",1,["syn::parse::ParseBuffer"]],["impl<'c, 'a> UnwindSafe for StepCursor<'c, 'a>",1,["syn::parse::StepCursor"]],["impl UnwindSafe for Nothing",1,["syn::parse::Nothing"]]], -"tokio":[["impl<'a> !UnwindSafe for ReadBuf<'a>",1,["tokio::io::read_buf::ReadBuf"]],["impl !UnwindSafe for JoinError",1,["tokio::runtime::task::error::JoinError"]],["impl !UnwindSafe for Builder",1,["tokio::runtime::builder::Builder"]],["impl !UnwindSafe for Handle",1,["tokio::runtime::handle::Handle"]],["impl<'a> !UnwindSafe for EnterGuard<'a>",1,["tokio::runtime::handle::EnterGuard"]],["impl UnwindSafe for TryCurrentError",1,["tokio::runtime::handle::TryCurrentError"]],["impl !UnwindSafe for Runtime",1,["tokio::runtime::runtime::Runtime"]],["impl UnwindSafe for RuntimeFlavor",1,["tokio::runtime::runtime::RuntimeFlavor"]],["impl<'a> !UnwindSafe for Notified<'a>",1,["tokio::sync::notify::Notified"]],["impl !UnwindSafe for Barrier",1,["tokio::sync::barrier::Barrier"]],["impl UnwindSafe for BarrierWaitResult",1,["tokio::sync::barrier::BarrierWaitResult"]],["impl<T> UnwindSafe for SendError<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::broadcast::error::SendError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::broadcast::error::RecvError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::broadcast::error::TryRecvError"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::broadcast::Sender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::broadcast::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::mpsc::bounded::Sender"]],["impl<T> !UnwindSafe for WeakSender<T>",1,["tokio::sync::mpsc::bounded::WeakSender"]],["impl<'a, T> !UnwindSafe for Permit<'a, T>",1,["tokio::sync::mpsc::bounded::Permit"]],["impl<T> !UnwindSafe for OwnedPermit<T>",1,["tokio::sync::mpsc::bounded::OwnedPermit"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::mpsc::bounded::Receiver"]],["impl<T> !UnwindSafe for UnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::UnboundedSender"]],["impl<T> !UnwindSafe for WeakUnboundedSender<T>",1,["tokio::sync::mpsc::unbounded::WeakUnboundedSender"]],["impl<T> !UnwindSafe for UnboundedReceiver<T>",1,["tokio::sync::mpsc::unbounded::UnboundedReceiver"]],["impl<T> UnwindSafe for SendError<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::mpsc::error::SendError"]],["impl<T> UnwindSafe for TrySendError<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::mpsc::error::TrySendError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::mpsc::error::TryRecvError"]],["impl<T: ?Sized> UnwindSafe for Mutex<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::mutex::Mutex"]],["impl<'a, T> !UnwindSafe for MutexGuard<'a, T>",1,["tokio::sync::mutex::MutexGuard"]],["impl<T> !UnwindSafe for OwnedMutexGuard<T>",1,["tokio::sync::mutex::OwnedMutexGuard"]],["impl<'a, T> !UnwindSafe for MappedMutexGuard<'a, T>",1,["tokio::sync::mutex::MappedMutexGuard"]],["impl UnwindSafe for TryLockError",1,["tokio::sync::mutex::TryLockError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::oneshot::error::RecvError"]],["impl UnwindSafe for TryRecvError",1,["tokio::sync::oneshot::error::TryRecvError"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::oneshot::Sender"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::oneshot::Receiver"]],["impl UnwindSafe for TryAcquireError",1,["tokio::sync::batch_semaphore::TryAcquireError"]],["impl UnwindSafe for AcquireError",1,["tokio::sync::batch_semaphore::AcquireError"]],["impl UnwindSafe for Semaphore",1,["tokio::sync::semaphore::Semaphore"]],["impl<'a> !UnwindSafe for SemaphorePermit<'a>",1,["tokio::sync::semaphore::SemaphorePermit"]],["impl !UnwindSafe for OwnedSemaphorePermit",1,["tokio::sync::semaphore::OwnedSemaphorePermit"]],["impl<T, U = T> !UnwindSafe for OwnedRwLockReadGuard<T, U>",1,["tokio::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard"]],["impl<T> !UnwindSafe for OwnedRwLockWriteGuard<T>",1,["tokio::sync::rwlock::owned_write_guard::OwnedRwLockWriteGuard"]],["impl<T, U = T> !UnwindSafe for OwnedRwLockMappedWriteGuard<T, U>",1,["tokio::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard"]],["impl<'a, T> !UnwindSafe for RwLockReadGuard<'a, T>",1,["tokio::sync::rwlock::read_guard::RwLockReadGuard"]],["impl<'a, T> !UnwindSafe for RwLockWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard::RwLockWriteGuard"]],["impl<'a, T> !UnwindSafe for RwLockMappedWriteGuard<'a, T>",1,["tokio::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard"]],["impl<T: ?Sized> UnwindSafe for RwLock<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::rwlock::RwLock"]],["impl<T> UnwindSafe for OnceCell<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::once_cell::OnceCell"]],["impl<T> UnwindSafe for SetError<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::once_cell::SetError"]],["impl<T> UnwindSafe for SendError<T>where
        T: UnwindSafe,
    ",1,["tokio::sync::watch::error::SendError"]],["impl UnwindSafe for RecvError",1,["tokio::sync::watch::error::RecvError"]],["impl<T> !UnwindSafe for Receiver<T>",1,["tokio::sync::watch::Receiver"]],["impl<T> !UnwindSafe for Sender<T>",1,["tokio::sync::watch::Sender"]],["impl<'a, T> UnwindSafe for Ref<'a, T>where
        T: RefUnwindSafe,
    ",1,["tokio::sync::watch::Ref"]],["impl !UnwindSafe for LocalSet",1,["tokio::task::local::LocalSet"]],["impl !UnwindSafe for LocalEnterGuard",1,["tokio::task::local::LocalEnterGuard"]],["impl<T> UnwindSafe for LocalKey<T>",1,["tokio::task::task_local::LocalKey"]],["impl<T, F> UnwindSafe for TaskLocalFuture<T, F>where
        F: UnwindSafe,
        T: UnwindSafe,
    ",1,["tokio::task::task_local::TaskLocalFuture"]],["impl<F> UnwindSafe for Unconstrained<F>where
        F: UnwindSafe,
    ",1,["tokio::task::unconstrained::Unconstrained"]],["impl<T> UnwindSafe for JoinSet<T>",1,["tokio::task::join_set::JoinSet"]],["impl UnwindSafe for AbortHandle"],["impl<T> UnwindSafe for JoinHandle<T>"],["impl UnwindSafe for Notify"]], -"typenum":[["impl UnwindSafe for B0",1,["typenum::bit::B0"]],["impl UnwindSafe for B1",1,["typenum::bit::B1"]],["impl<U> UnwindSafe for PInt<U>where
        U: UnwindSafe,
    ",1,["typenum::int::PInt"]],["impl<U> UnwindSafe for NInt<U>where
        U: UnwindSafe,
    ",1,["typenum::int::NInt"]],["impl UnwindSafe for Z0",1,["typenum::int::Z0"]],["impl UnwindSafe for UTerm",1,["typenum::uint::UTerm"]],["impl<U, B> UnwindSafe for UInt<U, B>where
        B: UnwindSafe,
        U: UnwindSafe,
    ",1,["typenum::uint::UInt"]],["impl UnwindSafe for ATerm",1,["typenum::array::ATerm"]],["impl<V, A> UnwindSafe for TArr<V, A>where
        A: UnwindSafe,
        V: UnwindSafe,
    ",1,["typenum::array::TArr"]],["impl UnwindSafe for Greater",1,["typenum::Greater"]],["impl UnwindSafe for Less",1,["typenum::Less"]],["impl UnwindSafe for Equal",1,["typenum::Equal"]]], -"uint":[["impl UnwindSafe for FromStrRadixErrKind",1,["uint::uint::FromStrRadixErrKind"]],["impl UnwindSafe for FromStrRadixErr",1,["uint::uint::FromStrRadixErr"]],["impl UnwindSafe for FromDecStrErr",1,["uint::uint::FromDecStrErr"]],["impl UnwindSafe for FromHexError",1,["uint::uint::FromHexError"]]] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/firewood/merkle/trait.ValueTransformer.js b/docs/implementors/firewood/merkle/trait.ValueTransformer.js deleted file mode 100644 index 1961d5cb23d9..000000000000 --- a/docs/implementors/firewood/merkle/trait.ValueTransformer.js +++ /dev/null @@ -1,3 +0,0 @@ -(function() {var implementors = { -"firewood":[] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/implementors/shale/trait.MummyItem.js b/docs/implementors/shale/trait.MummyItem.js deleted file mode 100644 index cb5ab49451d1..000000000000 --- a/docs/implementors/shale/trait.MummyItem.js +++ /dev/null @@ -1,4 +0,0 @@ -(function() {var implementors = { -"firewood":[["impl MummyItem for Hash"],["impl MummyItem for Node"]], -"shale":[] -};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 1f05c1c90ff3..000000000000 --- a/docs/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/light.css b/docs/light.css deleted file mode 100644 index 1aa2dceef964..000000000000 --- a/docs/light.css +++ /dev/null @@ -1 +0,0 @@ -:root{--main-background-color:white;--main-color:black;--settings-input-color:#2196f3;--sidebar-background-color:#F5F5F5;--sidebar-background-color-hover:#E0E0E0;--code-block-background-color:#F5F5F5;--scrollbar-track-background-color:#dcdcdc;--scrollbar-thumb-background-color:rgba(36,37,39,0.6);--scrollbar-color:rgba(36,37,39,0.6) #d9d9d9;--headings-border-bottom-color:#ddd;--border-color:#e0e0e0;--button-background-color:#fff;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--search-input-focused-border-color:#66afe9;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(35%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ad378a;--trait-link-color:#6e4fc9;--assoc-item-link-color:#3873ad;--function-link-color:#ad7c37;--macro-link-color:#068000;--keyword-link-color:#3873ad;--mod-link-color:#3873ad;--link-color:#3873ad;--sidebar-link-color:#356da4;--sidebar-current-link-background-color:#fff;--search-result-link-focus-background-color:#ccc;--stab-background-color:#fff5d6;--stab-code-color:#000;--search-color:#000;--code-highlight-kw-color:#8959a8;--code-highlight-kw-2-color:#4271ae;--code-highlight-lifetime-color:#b76514;--code-highlight-prelude-color:#4271ae;--code-highlight-prelude-val-color:#c82829;--code-highlight-number-color:#718c00;--code-highlight-string-color:#718c00;--code-highlight-literal-color:#c82829;--code-highlight-attribute-color:#c82829;--code-highlight-self-color:#c82829;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8e908c;--code-highlight-doc-comment-color:#4d4d4c;}.slider{background-color:#ccc;}.slider:before{background-color:white;}input:focus+.slider{box-shadow:0 0 0 2px #0a84ff,0 0 0 6px rgba(10,132,255,0.3);}.rust-logo{}.src-line-numbers span{color:#c67e2d;}.src-line-numbers .line-highlighted{background-color:#FDFFD3 !important;}.content .item-info::before{color:#ccc;}body.source .example-wrap pre.rust a{background:#eee;}#crate-search-div::after{filter:invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);}#crate-search:hover,#crate-search:focus{border-color:#717171 !important;}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);}.src-line-numbers :target{background-color:transparent;}pre.example-line-numbers{border-color:#c7c7c7;}a.test-arrow{color:#f5f5f5;background-color:rgba(78,139,202,0.2);}a.test-arrow:hover{background-color:#4e8bca;}:target{background:#FDFFD3;border-right:3px solid #AD7C37;}.search-failed a{color:#3873AD;}.tooltip::after{background-color:#000;color:#fff;}.tooltip::before{border-color:transparent black transparent transparent;}.notable-traits-tooltiptext{background-color:#eee;}#titles>button:not(.selected){background-color:#e6e6e6;border-top-color:#e6e6e6;}#titles>button:hover,#titles>button.selected{background-color:#ffffff;border-top-color:#0089ff;}#titles>button>div.count{color:#888;}kbd{color:#000;background-color:#fafbfc;box-shadow:inset 0 -1px 0 #c6cbd1;}#settings-menu>a,#help-button>a{color:#000;}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:#717171;}.search-results .result-name span.alias{color:#000;}.search-results .result-name span.grey{color:#999;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:#E0E0E0;}#source-sidebar div.files>a.selected{background-color:#fff;}.scraped-example-list .scrape-help{border-color:#555;color:#333;}.scraped-example-list .scrape-help:hover{border-color:black;color:black;}.scraped-example .example-wrap .rust span.highlight{background:#fcffd6;}.scraped-example .example-wrap .rust span.highlight.focus{background:#f6fdb0;}.scraped-example:not(.expanded) .code-wrapper:before{background:linear-gradient(to bottom,rgba(255,255,255,1),rgba(255,255,255,0));}.scraped-example:not(.expanded) .code-wrapper:after{background:linear-gradient(to top,rgba(255,255,255,1),rgba(255,255,255,0));}.toggle-line-inner{background:#ccc;}.toggle-line:hover .toggle-line-inner{background:#999;} \ No newline at end of file diff --git a/docs/main.js b/docs/main.js deleted file mode 100644 index 63d1a20b5583..000000000000 --- a/docs/main.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict";function getVar(name){const el=document.getElementById("rustdoc-vars");if(el){return el.attributes["data-"+name].value}else{return null}}function resourcePath(basename,extension){return getVar("root-path")+basename+getVar("resource-suffix")+extension}function hideMain(){addClass(document.getElementById(MAIN_ID),"hidden")}function showMain(){removeClass(document.getElementById(MAIN_ID),"hidden")}function elemIsInParent(elem,parent){while(elem&&elem!==document.body){if(elem===parent){return true}elem=elem.parentElement}return false}function blurHandler(event,parentElem,hideCallback){if(!elemIsInParent(document.activeElement,parentElem)&&!elemIsInParent(event.relatedTarget,parentElem)){hideCallback()}}(function(){window.rootPath=getVar("root-path");window.currentCrate=getVar("current-crate")}());function setMobileTopbar(){const mobileLocationTitle=document.querySelector(".mobile-topbar h2");const locationTitle=document.querySelector(".sidebar h2.location");if(mobileLocationTitle&&locationTitle){mobileLocationTitle.innerHTML=locationTitle.innerHTML}}function getVirtualKey(ev){if("key"in ev&&typeof ev.key!=="undefined"){return ev.key}const c=ev.charCode||ev.keyCode;if(c===27){return"Escape"}return String.fromCharCode(c)}const MAIN_ID="main-content";const SETTINGS_BUTTON_ID="settings-menu";const ALTERNATIVE_DISPLAY_ID="alternative-display";const NOT_DISPLAYED_ID="not-displayed";const HELP_BUTTON_ID="help-button";function getSettingsButton(){return document.getElementById(SETTINGS_BUTTON_ID)}function getHelpButton(){return document.getElementById(HELP_BUTTON_ID)}function getNakedUrl(){return window.location.href.split("?")[0].split("#")[0]}function insertAfter(newNode,referenceNode){referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling)}function getOrCreateSection(id,classes){let el=document.getElementById(id);if(!el){el=document.createElement("section");el.id=id;el.className=classes;insertAfter(el,document.getElementById(MAIN_ID))}return el}function getAlternativeDisplayElem(){return getOrCreateSection(ALTERNATIVE_DISPLAY_ID,"content hidden")}function getNotDisplayedElem(){return getOrCreateSection(NOT_DISPLAYED_ID,"hidden")}function switchDisplayedElement(elemToDisplay){const el=getAlternativeDisplayElem();if(el.children.length>0){getNotDisplayedElem().appendChild(el.firstElementChild)}if(elemToDisplay===null){addClass(el,"hidden");showMain();return}el.appendChild(elemToDisplay);hideMain();removeClass(el,"hidden")}function browserSupportsHistoryApi(){return window.history&&typeof window.history.pushState==="function"}function loadCss(cssFileName){const link=document.createElement("link");link.href=resourcePath(cssFileName,".css");link.type="text/css";link.rel="stylesheet";document.getElementsByTagName("head")[0].appendChild(link)}(function(){const isHelpPage=window.location.pathname.endsWith("/help.html");function loadScript(url){const script=document.createElement("script");script.src=url;document.head.append(script)}getSettingsButton().onclick=event=>{if(event.ctrlKey||event.altKey||event.metaKey){return}addClass(getSettingsButton(),"rotate");event.preventDefault();loadCss("settings");loadScript(resourcePath("settings",".js"))};window.searchState={loadingText:"Loading search results...",input:document.getElementsByClassName("search-input")[0],outputElement:()=>{let el=document.getElementById("search");if(!el){el=document.createElement("section");el.id="search";getNotDisplayedElem().appendChild(el)}return el},title:document.title,titleBeforeSearch:document.title,timeout:null,currentTab:0,focusedByTab:[null,null,null],clearInputTimeout:()=>{if(searchState.timeout!==null){clearTimeout(searchState.timeout);searchState.timeout=null}},isDisplayed:()=>searchState.outputElement().parentElement.id===ALTERNATIVE_DISPLAY_ID,focus:()=>{searchState.input.focus()},defocus:()=>{searchState.input.blur()},showResults:search=>{if(search===null||typeof search==="undefined"){search=searchState.outputElement()}switchDisplayedElement(search);searchState.mouseMovedAfterSearch=false;document.title=searchState.title},hideResults:()=>{switchDisplayedElement(null);document.title=searchState.titleBeforeSearch;if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}},getQueryStringParams:()=>{const params={};window.location.search.substring(1).split("&").map(s=>{const pair=s.split("=");params[decodeURIComponent(pair[0])]=typeof pair[1]==="undefined"?null:decodeURIComponent(pair[1])});return params},setup:()=>{const search_input=searchState.input;if(!searchState.input){return}let searchLoaded=false;function loadSearch(){if(!searchLoaded){searchLoaded=true;loadScript(resourcePath("search",".js"));loadScript(resourcePath("search-index",".js"))}}search_input.addEventListener("focus",()=>{search_input.origPlaceholder=search_input.placeholder;search_input.placeholder="Type your search here.";loadSearch()});if(search_input.value!==""){loadSearch()}const params=searchState.getQueryStringParams();if(params.search!==undefined){const search=searchState.outputElement();search.innerHTML="

    "+searchState.loadingText+"

    ";searchState.showResults(search);loadSearch()}},};function getPageId(){if(window.location.hash){const tmp=window.location.hash.replace(/^#/,"");if(tmp.length>0){return tmp}}return null}const toggleAllDocsId="toggle-all-docs";let savedHash="";function handleHashes(ev){if(ev!==null&&searchState.isDisplayed()&&ev.newURL){switchDisplayedElement(null);const hash=ev.newURL.slice(ev.newURL.indexOf("#")+1);if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.search+"#"+hash)}const elem=document.getElementById(hash);if(elem){elem.scrollIntoView()}}if(savedHash!==window.location.hash){savedHash=window.location.hash;if(savedHash.length===0){return}expandSection(savedHash.slice(1))}}function onHashChange(ev){hideSidebar();handleHashes(ev)}function openParentDetails(elem){while(elem){if(elem.tagName==="DETAILS"){elem.open=true}elem=elem.parentNode}}function expandSection(id){openParentDetails(document.getElementById(id))}function handleEscape(ev){searchState.clearInputTimeout();switchDisplayedElement(null);if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}ev.preventDefault();searchState.defocus();window.hidePopoverMenus()}function handleShortcut(ev){const disableShortcuts=getSettingValue("disable-shortcuts")==="true";if(ev.ctrlKey||ev.altKey||ev.metaKey||disableShortcuts){return}if(document.activeElement.tagName==="INPUT"&&document.activeElement.type!=="checkbox"){switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break}}else{switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break;case"s":case"S":ev.preventDefault();searchState.focus();break;case"+":ev.preventDefault();expandAllDocs();break;case"-":ev.preventDefault();collapseAllDocs();break;case"?":showHelp();break;default:break}}}document.addEventListener("keypress",handleShortcut);document.addEventListener("keydown",handleShortcut);function addSidebarItems(){if(!window.SIDEBAR_ITEMS){return}const sidebar=document.getElementsByClassName("sidebar-elems")[0];function block(shortty,id,longty){const filtered=window.SIDEBAR_ITEMS[shortty];if(!filtered){return}const h3=document.createElement("h3");h3.innerHTML=`${longty}`;const ul=document.createElement("ul");ul.className="block "+shortty;for(const item of filtered){const name=item[0];const desc=item[1];let path;if(shortty==="mod"){path=name+"/index.html"}else{path=shortty+"."+name+".html"}const current_page=document.location.href.split("/").pop();const link=document.createElement("a");link.href=path;link.title=desc;if(path===current_page){link.className="current"}link.textContent=name;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebar.appendChild(h3);sidebar.appendChild(ul)}if(sidebar){block("primitive","primitives","Primitive Types");block("mod","modules","Modules");block("macro","macros","Macros");block("struct","structs","Structs");block("enum","enums","Enums");block("union","unions","Unions");block("constant","constants","Constants");block("static","static","Statics");block("trait","traits","Traits");block("fn","functions","Functions");block("type","types","Type Definitions");block("foreigntype","foreign-types","Foreign Types");block("keyword","keywords","Keywords");block("traitalias","trait-aliases","Trait Aliases")}}window.register_implementors=imp=>{const implementors=document.getElementById("implementors-list");const synthetic_implementors=document.getElementById("synthetic-implementors-list");const inlined_types=new Set();const TEXT_IDX=0;const SYNTHETIC_IDX=1;const TYPES_IDX=2;if(synthetic_implementors){onEachLazy(synthetic_implementors.getElementsByClassName("impl"),el=>{const aliases=el.getAttribute("data-aliases");if(!aliases){return}aliases.split(",").forEach(alias=>{inlined_types.add(alias)})})}let currentNbImpls=implementors.getElementsByClassName("impl").length;const traitName=document.querySelector("h1.fqn > .trait").textContent;const baseIdName="impl-"+traitName+"-";const libs=Object.getOwnPropertyNames(imp);const script=document.querySelector("script[data-ignore-extern-crates]");const ignoreExternCrates=script?script.getAttribute("data-ignore-extern-crates"):"";for(const lib of libs){if(lib===window.currentCrate||ignoreExternCrates.indexOf(lib)!==-1){continue}const structs=imp[lib];struct_loop:for(const struct of structs){const list=struct[SYNTHETIC_IDX]?synthetic_implementors:implementors;if(struct[SYNTHETIC_IDX]){for(const struct_type of struct[TYPES_IDX]){if(inlined_types.has(struct_type)){continue struct_loop}inlined_types.add(struct_type)}}const code=document.createElement("h3");code.innerHTML=struct[TEXT_IDX];addClass(code,"code-header");onEachLazy(code.getElementsByTagName("a"),elem=>{const href=elem.getAttribute("href");if(href&&href.indexOf("http")!==0){elem.setAttribute("href",window.rootPath+href)}});const currentId=baseIdName+currentNbImpls;const anchor=document.createElement("a");anchor.href="#"+currentId;addClass(anchor,"anchor");const display=document.createElement("div");display.id=currentId;addClass(display,"impl");display.appendChild(anchor);display.appendChild(code);list.appendChild(display);currentNbImpls+=1}}};if(window.pending_implementors){window.register_implementors(window.pending_implementors)}function addSidebarCrates(){if(!window.ALL_CRATES){return}const sidebarElems=document.getElementsByClassName("sidebar-elems")[0];if(!sidebarElems){return}const h3=document.createElement("h3");h3.innerHTML="Crates";const ul=document.createElement("ul");ul.className="block crate";for(const crate of window.ALL_CRATES){const link=document.createElement("a");link.href=window.rootPath+crate+"/index.html";if(window.rootPath!=="./"&&crate===window.currentCrate){link.className="current"}link.textContent=crate;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebarElems.appendChild(h3);sidebarElems.appendChild(ul)}function expandAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);removeClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(!hasClass(e,"type-contents-toggle")){e.open=true}});innerToggle.title="collapse all docs";innerToggle.children[0].innerText="\u2212"}function collapseAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);addClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(e.parentNode.id!=="implementations-list"||(!hasClass(e,"implementors-toggle")&&!hasClass(e,"type-contents-toggle"))){e.open=false}});innerToggle.title="expand all docs";innerToggle.children[0].innerText="+"}function toggleAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);if(!innerToggle){return}if(hasClass(innerToggle,"will-expand")){expandAllDocs()}else{collapseAllDocs()}}(function(){const toggles=document.getElementById(toggleAllDocsId);if(toggles){toggles.onclick=toggleAllDocs}const hideMethodDocs=getSettingValue("auto-hide-method-docs")==="true";const hideImplementations=getSettingValue("auto-hide-trait-implementations")==="true";const hideLargeItemContents=getSettingValue("auto-hide-large-items")!=="false";function setImplementorsTogglesOpen(id,open){const list=document.getElementById(id);if(list!==null){onEachLazy(list.getElementsByClassName("implementors-toggle"),e=>{e.open=open})}}if(hideImplementations){setImplementorsTogglesOpen("trait-implementations-list",false);setImplementorsTogglesOpen("blanket-implementations-list",false)}onEachLazy(document.getElementsByClassName("rustdoc-toggle"),e=>{if(!hideLargeItemContents&&hasClass(e,"type-contents-toggle")){e.open=true}if(hideMethodDocs&&hasClass(e,"method-toggle")){e.open=false}});const pageId=getPageId();if(pageId!==null){expandSection(pageId)}}());window.rustdoc_add_line_numbers_to_examples=()=>{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");if(line_numbers.length>0){return}const count=x.textContent.split("\n").length;const elems=[];for(let i=0;i{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");for(const node of line_numbers){parent.removeChild(node)}})};(function(){if(getSettingValue("line-numbers")==="true"){window.rustdoc_add_line_numbers_to_examples()}}());let oldSidebarScrollPosition=null;window.rustdocMobileScrollLock=function(){const mobile_topbar=document.querySelector(".mobile-topbar");if(window.innerWidth<=window.RUSTDOC_MOBILE_BREAKPOINT){oldSidebarScrollPosition=window.scrollY;document.body.style.width=`${document.body.offsetWidth}px`;document.body.style.position="fixed";document.body.style.top=`-${oldSidebarScrollPosition}px`;if(mobile_topbar){mobile_topbar.style.top=`${oldSidebarScrollPosition}px`;mobile_topbar.style.position="relative"}}else{oldSidebarScrollPosition=null}};window.rustdocMobileScrollUnlock=function(){const mobile_topbar=document.querySelector(".mobile-topbar");if(oldSidebarScrollPosition!==null){document.body.style.width="";document.body.style.position="";document.body.style.top="";if(mobile_topbar){mobile_topbar.style.top="";mobile_topbar.style.position=""}window.scrollTo(0,oldSidebarScrollPosition);oldSidebarScrollPosition=null}};function showSidebar(){window.rustdocMobileScrollLock();const sidebar=document.getElementsByClassName("sidebar")[0];addClass(sidebar,"shown")}function hideSidebar(){window.rustdocMobileScrollUnlock();const sidebar=document.getElementsByClassName("sidebar")[0];removeClass(sidebar,"shown")}window.addEventListener("resize",()=>{if(window.innerWidth>window.RUSTDOC_MOBILE_BREAKPOINT&&oldSidebarScrollPosition!==null){hideSidebar()}});function handleClick(id,f){const elem=document.getElementById(id);if(elem){elem.addEventListener("click",f)}}handleClick(MAIN_ID,()=>{hideSidebar()});onEachLazy(document.getElementsByTagName("a"),el=>{if(el.hash){el.addEventListener("click",()=>{expandSection(el.hash.slice(1));hideSidebar()})}});onEachLazy(document.querySelectorAll(".rustdoc-toggle > summary:not(.hideme)"),el=>{el.addEventListener("click",e=>{if(e.target.tagName!=="SUMMARY"&&e.target.tagName!=="A"){e.preventDefault()}})});onEachLazy(document.getElementsByClassName("notable-traits"),e=>{e.onclick=function(){this.getElementsByClassName("notable-traits-tooltiptext")[0].classList.toggle("force-tooltip")}});const sidebar_menu_toggle=document.getElementsByClassName("sidebar-menu-toggle")[0];if(sidebar_menu_toggle){sidebar_menu_toggle.addEventListener("click",()=>{const sidebar=document.getElementsByClassName("sidebar")[0];if(!hasClass(sidebar,"shown")){showSidebar()}else{hideSidebar()}})}function helpBlurHandler(event){blurHandler(event,getHelpButton(),window.hidePopoverMenus)}function buildHelpMenu(){const book_info=document.createElement("span");book_info.className="top";book_info.innerHTML="You can find more information in \ - the rustdoc book.";const shortcuts=[["?","Show this help dialog"],["S","Focus the search field"],["↑","Move up in search results"],["↓","Move down in search results"],["← / →","Switch result tab (when results focused)"],["⏎","Go to active search result"],["+","Expand all sections"],["-","Collapse all sections"],].map(x=>"
    "+x[0].split(" ").map((y,index)=>((index&1)===0?""+y+"":" "+y+" ")).join("")+"
    "+x[1]+"
    ").join("");const div_shortcuts=document.createElement("div");addClass(div_shortcuts,"shortcuts");div_shortcuts.innerHTML="

    Keyboard Shortcuts

    "+shortcuts+"
    ";const infos=["Prefix searches with a type followed by a colon (e.g., fn:) to \ - restrict the search to a given item kind.","Accepted kinds are: fn, mod, struct, \ - enum, trait, type, macro, \ - and const.","Search functions by type signature (e.g., vec -> usize or \ - -> vec)","Search multiple things at once by splitting your query with comma (e.g., \ - str,u8 or String,struct:Vec,test)","You can look for items with an exact name by putting double quotes around \ - your request: \"string\"","Look for items inside another one by searching for a path: vec::Vec",].map(x=>"

    "+x+"

    ").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="

    Search Tricks

    "+infos;const rustdoc_version=document.createElement("span");rustdoc_version.className="bottom";const rustdoc_version_code=document.createElement("code");rustdoc_version_code.innerText="rustdoc "+getVar("rustdoc-version");rustdoc_version.appendChild(rustdoc_version_code);const container=document.createElement("div");if(!isHelpPage){container.className="popover"}container.id="help";container.style.display="none";const side_by_side=document.createElement("div");side_by_side.className="side-by-side";side_by_side.appendChild(div_shortcuts);side_by_side.appendChild(div_infos);container.appendChild(book_info);container.appendChild(side_by_side);container.appendChild(rustdoc_version);if(isHelpPage){const help_section=document.createElement("section");help_section.appendChild(container);document.getElementById("main-content").appendChild(help_section);container.style.display="block"}else{const help_button=getHelpButton();help_button.appendChild(container);container.onblur=helpBlurHandler;container.onclick=event=>{event.preventDefault()};help_button.onblur=helpBlurHandler;help_button.children[0].onblur=helpBlurHandler}return container}window.hidePopoverMenus=function(){onEachLazy(document.querySelectorAll(".search-container .popover"),elem=>{elem.style.display="none"})};function getHelpMenu(buildNeeded){let menu=getHelpButton().querySelector(".popover");if(!menu&&buildNeeded){menu=buildHelpMenu()}return menu}function showHelp(){const menu=getHelpMenu(true);if(menu.style.display==="none"){window.hidePopoverMenus();menu.style.display=""}}if(isHelpPage){showHelp();document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault()})}else{document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault();const menu=getHelpMenu(true);const shouldShowHelp=menu.style.display==="none";if(shouldShowHelp){showHelp()}else{window.hidePopoverMenus()}})}setMobileTopbar();addSidebarItems();addSidebarCrates();onHashChange(null);window.addEventListener("hashchange",onHashChange);searchState.setup()}());(function(){let reset_button_timeout=null;window.copy_path=but=>{const parent=but.parentElement;const path=[];onEach(parent.childNodes,child=>{if(child.tagName==="A"){path.push(child.textContent)}});const el=document.createElement("textarea");el.value=path.join("::");el.setAttribute("readonly","");el.style.position="absolute";el.style.left="-9999px";document.body.appendChild(el);el.select();document.execCommand("copy");document.body.removeChild(el);but.children[0].style.display="none";let tmp;if(but.childNodes.length<2){tmp=document.createTextNode("✓");but.appendChild(tmp)}else{onEachLazy(but.childNodes,e=>{if(e.nodeType===Node.TEXT_NODE){tmp=e;return true}});tmp.textContent="✓"}if(reset_button_timeout!==null){window.clearTimeout(reset_button_timeout)}function reset_button(){tmp.textContent="";reset_button_timeout=null;but.children[0].style.display=""}reset_button_timeout=window.setTimeout(reset_button,1000)}}()) \ No newline at end of file diff --git a/docs/normalize.css b/docs/normalize.css deleted file mode 100644 index 469959f13729..000000000000 --- a/docs/normalize.css +++ /dev/null @@ -1,2 +0,0 @@ - /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ -html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} \ No newline at end of file diff --git a/docs/noscript.css b/docs/noscript.css deleted file mode 100644 index e816793aa128..000000000000 --- a/docs/noscript.css +++ /dev/null @@ -1 +0,0 @@ - #main-content .attributes{margin-left:0 !important;}#copy-path{display:none;}nav.sub{display:none;} \ No newline at end of file diff --git a/docs/rust-logo.svg b/docs/rust-logo.svg deleted file mode 100644 index 62424d8ffd76..000000000000 --- a/docs/rust-logo.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/docs/rustdoc.css b/docs/rustdoc.css deleted file mode 100644 index 0110592b81f3..000000000000 --- a/docs/rustdoc.css +++ /dev/null @@ -1 +0,0 @@ - @font-face {font-family:'Fira Sans';font-style:normal;font-weight:400;src:local('Fira Sans'),url("FiraSans-Regular.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:500;src:local('Fira Sans Medium'),url("FiraSans-Medium.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:400;src:local('Source Serif 4'),url("SourceSerif4-Regular.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:italic;font-weight:400;src:local('Source Serif 4 Italic'),url("SourceSerif4-It.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:700;src:local('Source Serif 4 Bold'),url("SourceSerif4-Bold.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:400;src:url("SourceCodePro-Regular.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:italic;font-weight:400;src:url("SourceCodePro-It.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:600;src:url("SourceCodePro-Semibold.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'NanumBarunGothic';src:url("NanumBarunGothic.ttf.woff2") format("woff2");font-display:swap;unicode-range:U+AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF;}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}html{content:"";}@media (prefers-color-scheme:light){html{content:"light";}}@media (prefers-color-scheme:dark){html{content:"dark";}}body{font:1rem/1.5 "Source Serif 4",NanumBarunGothic,serif;margin:0;position:relative;overflow-wrap:break-word;overflow-wrap:anywhere;-webkit-font-feature-settings:"kern","liga";-moz-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";background-color:var(--main-background-color);color:var(--main-color);}h1{font-size:1.5rem;}h2{font-size:1.375rem;}h3{font-size:1.25rem;}h1,h2,h3,h4,h5,h6{font-weight:500;}h1,h2,h3,h4{margin:25px 0 15px 0;padding-bottom:6px;}.docblock h3,.docblock h4,h5,h6{margin:15px 0 5px 0;}.docblock>h2:first-child,.docblock>h3:first-child,.docblock>h4:first-child,.docblock>h5:first-child,.docblock>h6:first-child{margin-top:0;}h1.fqn{margin:0;padding:0;flex-grow:1;overflow-wrap:break-word;overflow-wrap:anywhere;}.main-heading{display:flex;flex-wrap:wrap;justify-content:space-between;padding-bottom:6px;margin-bottom:15px;}#toggle-all-docs{text-decoration:none;}.content h2,.top-doc .docblock>h3,.top-doc .docblock>h4{border-bottom:1px solid var(--headings-border-bottom-color);}h3.code-header{font-size:1.125rem;}h4.code-header{font-size:1rem;}.code-header{font-weight:600;border-bottom-style:none;margin:0;padding:0;}#crate-search,h1,h2,h3,h4,h5,h6,.sidebar,.mobile-topbar,.search-input,.search-results .result-name,.item-left>a,.out-of-band,span.since,a.srclink,#help-button>a,details.rustdoc-toggle.top-doc>summary,details.rustdoc-toggle.non-exhaustive>summary,.scraped-example-title,.more-examples-toggle summary,.more-examples-toggle .hide-more,.example-links a,ul.all-items{font-family:"Fira Sans",Arial,NanumBarunGothic,sans-serif;}a#toggle-all-docs,a.anchor,.small-section-header a,#source-sidebar a,pre.rust a,.sidebar h2 a,.sidebar h3 a,.mobile-topbar h2 a,h1 a,.search-results a,.module-item .stab,.import-item .stab,.result-name .primitive>i,.result-name .keyword>i,.method .where,.fn .where,.where.fmt-newline{color:var(--main-color);}.content span.enum,.content a.enum,.content span.struct,.content a.struct,.content span.union,.content a.union,.content span.primitive,.content a.primitive,.content span.type,.content a.type,.content span.foreigntype,.content a.foreigntype{color:var(--type-link-color);}.content span.trait,.content a.trait,.content span.traitalias,.content a.traitalias{color:var(--trait-link-color);}.content span.associatedtype,.content a.associatedtype,.content span.constant,.content a.constant,.content span.static,.content a.static{color:var(--assoc-item-link-color);}.content span.fn,.content a.fn,.content .fnname,.content span.method,.content a.method,.content span.tymethod,.content a.tymethod{color:var(--function-link-color);}.content span.attr,.content a.attr,.content span.derive,.content a.derive,.content span.macro,.content a.macro{color:var(--macro-link-color);}.content span.mod,.content a.mod{color:var(--mod-link-color);}.content span.keyword,.content a.keyword{color:var(--keyword-link-color);}a{color:var(--link-color);}ol,ul{padding-left:24px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:.625em;}p{margin:0 0 .75em 0;}p:last-child{margin:0;}summary{outline:none;}button{padding:1px 6px;}.rustdoc{display:flex;flex-direction:row;flex-wrap:nowrap;}main{position:relative;flex-grow:1;padding:10px 15px 40px 45px;min-width:0;}.source main{padding:15px;}.width-limiter{max-width:960px;margin-right:auto;}.source .width-limiter{max-width:unset;}details:not(.rustdoc-toggle) summary{margin-bottom:.6em;}code,pre,a.test-arrow,.code-header{font-family:"Source Code Pro",monospace;}.docblock code,.docblock-short code{border-radius:3px;padding:0 0.125em;}.docblock pre code,.docblock-short pre code{padding:0;}pre{padding:14px;}.item-decl pre{overflow-x:auto;}.source .content pre{padding:20px;}img{max-width:100%;}.source .content{overflow:visible;}.sub-logo-container{line-height:0;}.sub-logo-container>img{height:60px;width:60px;object-fit:contain;}.sidebar,.mobile-topbar,.sidebar-menu-toggle{background-color:var(--sidebar-background-color);}.sidebar{font-size:0.875rem;width:200px;min-width:200px;overflow-y:scroll;position:sticky;height:100vh;top:0;left:0;}.rustdoc.source .sidebar{width:50px;min-width:0px;max-width:300px;flex-grow:0;flex-shrink:0;flex-basis:auto;border-right:1px solid;overflow-x:hidden;overflow-y:hidden;}.rustdoc.source .sidebar .sidebar-logo{display:none;}.source .sidebar,#sidebar-toggle,#source-sidebar{background-color:var(--sidebar-background-color);}#sidebar-toggle>button:hover,#sidebar-toggle>button:focus{background-color:var(--sidebar-background-color-hover);}.source .sidebar>*:not(#sidebar-toggle){visibility:hidden;}.source-sidebar-expanded .source .sidebar{overflow-y:auto;width:300px;}.source-sidebar-expanded .source .sidebar>*:not(#sidebar-toggle){visibility:visible;}#all-types{margin-top:1em;}*{scrollbar-width:initial;scrollbar-color:var(--scrollbar-color);}.sidebar{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color);}::-webkit-scrollbar{width:12px;}.sidebar::-webkit-scrollbar{width:8px;}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0;background-color:var(--scrollbar-track-background-color);}.sidebar::-webkit-scrollbar-track{background-color:var(--scrollbar-track-background-color);}::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-background-color);}.hidden{display:none !important;}.sidebar .logo-container{display:flex;margin-top:10px;margin-bottom:10px;justify-content:center;}.version{overflow-wrap:break-word;}.logo-container>img{height:100px;width:100px;}ul.block,.block li{padding:0;margin:0;list-style:none;}.block a,.sidebar h2 a,.sidebar h3 a{display:block;padding:0.25rem;margin-left:-0.25rem;text-overflow:ellipsis;overflow:hidden;}.sidebar h2{overflow-wrap:anywhere;padding:0;margin:0;margin-top:0.7rem;margin-bottom:0.7rem;}.sidebar h3{font-size:1.125rem;padding:0;margin:0;}.sidebar-elems,.sidebar>h2{padding-left:24px;}.sidebar a,.sidebar .current{color:var(--sidebar-link-color);}.sidebar .current,.sidebar a:hover{background-color:var(--sidebar-current-link-background-color);}.sidebar-elems .block{margin-bottom:2em;}.sidebar-elems .block li a{white-space:nowrap;}.mobile-topbar{display:none;}.source .content pre.rust{white-space:pre;overflow:auto;padding-left:0;}.rustdoc .example-wrap{display:flex;position:relative;margin-bottom:10px;}.rustdoc .example-wrap:last-child{margin-bottom:0px;}pre.example-line-numbers{overflow:initial;border:1px solid;padding:13px 8px;text-align:right;border-top-left-radius:5px;border-bottom-left-radius:5px;}.src-line-numbers{text-align:right;}.rustdoc:not(.source) .example-wrap>pre:not(.example-line-numbers){width:100%;overflow-x:auto;}.rustdoc:not(.source) .example-wrap>pre.src-line-numbers{width:auto;overflow-x:visible;}.rustdoc .example-wrap>pre{margin:0;}.search-loading{text-align:center;}.content>.example-wrap pre.src-line-numbers{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}.src-line-numbers span{cursor:pointer;}.docblock-short{overflow-wrap:break-word;overflow-wrap:anywhere;overflow:hidden;text-overflow:ellipsis;}.docblock>:not(pre)>code,.docblock-short>code{white-space:pre-wrap;}.top-doc .docblock h2{font-size:1.375rem;}.top-doc .docblock h3{font-size:1.25rem;}.top-doc .docblock h4,.top-doc .docblock h5{font-size:1.125rem;}.top-doc .docblock h6{font-size:1rem;}.docblock h5{font-size:1rem;}.docblock h6{font-size:0.875rem;}.docblock{margin-left:24px;position:relative;}.docblock>:not(.more-examples-toggle):not(.example-wrap){max-width:100%;overflow-x:auto;}.out-of-band{flex-grow:0;font-size:1.125rem;}.docblock code,.docblock-short code,pre,.rustdoc.source .example-wrap{background-color:var(--code-block-background-color);}#main-content{position:relative;}.docblock table{margin:.5em 0;width:calc(100% - 2px);overflow-x:auto;display:block;border-collapse:collapse;}.docblock table td{padding:.5em;border:1px dashed var(--border-color);vertical-align:top;}.docblock table th{padding:.5em;text-align:left;border:1px solid var(--border-color);}.method .where,.fn .where,.where.fmt-newline{display:block;font-size:0.875rem;}.item-info{display:block;margin-left:24px;}.item-info code{font-size:0.875rem;}#main-content>.item-info{margin-top:0;margin-left:0;}nav.sub{flex-grow:1;flex-flow:row nowrap;margin:4px 0 25px 0;display:flex;align-items:center;}nav.sub form{flex-grow:1;}.source nav.sub{margin:0 0 15px 0;}.source nav.sub form{margin-left:32px;}a{text-decoration:none;}.small-section-header{display:flex;justify-content:space-between;position:relative;}.small-section-header:hover>.anchor{display:initial;}.impl:hover>.anchor,.trait-impl:hover>.anchor{display:inline-block;position:absolute;}.anchor{display:none;position:absolute;left:-0.5em;background:none !important;}.anchor.field{left:-5px;}.small-section-header>.anchor{left:-15px;padding-right:8px;}h2.small-section-header>.anchor{padding-right:6px;}.anchor::before{content:'§';}.main-heading a:hover,.example-wrap>pre.rust a:hover,.all-items a:hover,.docblock a:not(.test-arrow):not(.scrape-help):hover,.docblock-short a:not(.test-arrow):not(.scrape-help):hover,.item-info a{text-decoration:underline;}.crate.block a.current{font-weight:500;}table,.item-table{overflow-wrap:break-word;}.item-table{display:table;}.item-row{display:table-row;}.item-left,.item-right{display:table-cell;}.item-left{padding-right:1.25rem;}.search-container{position:relative;display:flex;height:34px;}.search-results-title{margin-top:0;white-space:nowrap;display:inline-flex;max-width:100%;align-items:baseline;}#crate-search-div{display:inline-block;position:relative;min-width:5em;}#crate-search{min-width:115px;padding:0;padding-left:4px;padding-right:23px;max-width:100%;text-overflow:ellipsis;border:1px solid var(--border-color);border-radius:4px;outline:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;text-indent:0.01px;background-color:var(--main-background-color);color:inherit;line-height:1.5;font-weight:500;}@-moz-document url-prefix(){#crate-search{padding-left:0px;padding-right:19px;}}#crate-search-div::after{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0;content:"";background-repeat:no-repeat;background-size:20px;background-position:calc(100% - 2px) 56%;background-image:url("down-arrow.svg");}#crate-search>option{font-size:1rem;}.search-input{-webkit-appearance:none;-moz-box-sizing:border-box !important;box-sizing:border-box !important;outline:none;border:1px solid var(--border-color);border-radius:2px;padding:8px;font-size:1rem;width:100%;background-color:var(--button-background-color);color:var(--search-color);}.search-input:focus{border-color:var(--search-input-focused-border-color);}.search-results{display:none;padding-bottom:2em;}.search-results.active{display:block;clear:both;}.search-results .desc>span{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:block;}.search-results>a{display:block;margin-left:2px;margin-right:2px;border-bottom:1px solid #aaa3;}.search-results>a>div{display:flex;flex-flow:row wrap;}.search-results .result-name,.search-results div.desc{width:50%;}.search-results .result-name{padding-right:1em;}.search-results a:hover,.search-results a:focus{background-color:var(--search-result-link-focus-background-color);}.popover{font-size:1rem;position:absolute;right:0;z-index:2;display:block;margin-top:7px;border-radius:3px;border:1px solid var(--border-color);font-size:1rem;}.popover::before{content:'';position:absolute;right:11px;border:solid var(--border-color);border-width:1px 1px 0 0;display:inline-block;padding:4px;transform:rotate(-45deg);top:-5px;}.popover,.popover::before{background-color:var(--main-background-color);color:var(--main-color);}#help.popover{max-width:600px;}#help.popover::before{right:48px;}#help dt{float:left;clear:left;display:block;margin-right:0.5rem;}#help span.top,#help span.bottom{text-align:center;display:block;font-size:1.125rem;}#help span.top{margin:10px 0;border-bottom:1px solid var(--border-color);padding-bottom:4px;margin-bottom:6px;}#help span.bottom{clear:both;border-top:1px solid var(--border-color);}.side-by-side>div{width:50%;float:left;padding:0 20px 20px 17px;}.item-info .stab{width:fit-content;min-height:36px;display:flex;align-items:center;white-space:pre-wrap;}.stab{padding:3px;margin-bottom:5px;font-size:0.875rem;font-weight:normal;color:var(--main-color);background-color:var(--stab-background-color);}.stab.portability>code{background:none;color:var(--stab-code-color);}.stab .emoji{font-size:1.25rem;margin-right:0.3rem;}.docblock .stab{padding:0 0.125em;margin-bottom:0;}.emoji{text-shadow:1px 0 0 black,-1px 0 0 black,0 1px 0 black,0 -1px 0 black;}.module-item .stab,.import-item .stab{border-radius:3px;display:inline-block;font-size:0.875rem;line-height:1.2;margin-bottom:0;margin-left:0.3125em;padding:2px;vertical-align:text-bottom;}.module-item.unstable,.import-item.unstable{opacity:0.65;}.since{font-weight:normal;font-size:initial;}.rightside{padding-left:12px;padding-right:2px;float:right;}.rightside:not(a),.out-of-band{color:var(--right-side-color);}pre.rust{tab-size:4;-moz-tab-size:4;}pre.rust .kw{color:var(--code-highlight-kw-color);}pre.rust .kw-2{color:var(--code-highlight-kw-2-color);}pre.rust .lifetime{color:var(--code-highlight-lifetime-color);}pre.rust .prelude-ty{color:var(--code-highlight-prelude-color);}pre.rust .prelude-val{color:var(--code-highlight-prelude-val-color);}pre.rust .string{color:var(--code-highlight-string-color);}pre.rust .number{color:var(--code-highlight-number-color);}pre.rust .bool-val{color:var(--code-highlight-literal-color);}pre.rust .self{color:var(--code-highlight-self-color);}pre.rust .attribute{color:var(--code-highlight-attribute-color);}pre.rust .macro,pre.rust .macro-nonterminal{color:var(--code-highlight-macro-color);}pre.rust .question-mark{font-weight:bold;color:var(--code-highlight-question-mark-color);}pre.rust .comment{color:var(--code-highlight-comment-color);}pre.rust .doccomment{color:var(--code-highlight-doc-comment-color);}.example-wrap.compile_fail,.example-wrap.should_panic{border-left:2px solid var(--codeblock-error-color);}.ignore.example-wrap{border-left:2px solid var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover,.example-wrap.should_panic:hover{border-left:2px solid var(--codeblock-error-hover-color);}.example-wrap.ignore:hover{border-left:2px solid var(--codeblock-ignore-hover-color);}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip{color:var(--codeblock-error-color);}.example-wrap.ignore .tooltip{color:var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover .tooltip,.example-wrap.should_panic:hover .tooltip{color:var(--codeblock-error-hover-color);}.example-wrap.ignore:hover .tooltip{color:var(--codeblock-ignore-hover-color);}.example-wrap .tooltip{position:absolute;display:block;cursor:pointer;left:-25px;top:5px;}.example-wrap .tooltip::after{display:none;text-align:center;padding:5px 3px 3px 3px;border-radius:6px;margin-left:5px;font-size:1rem;border:1px solid var(--border-color);position:absolute;width:max-content;top:-2px;z-index:1;}.example-wrap .tooltip::before{content:" ";position:absolute;top:50%;left:16px;margin-top:-5px;border-width:5px;border-style:solid;display:none;z-index:1;}.example-wrap.ignore .tooltip::after{content:"This example is not tested";}.example-wrap.compile_fail .tooltip::after{content:"This example deliberately fails to compile";}.example-wrap.should_panic .tooltip::after{content:"This example panics";}.example-wrap.edition .tooltip::after{content:"This code runs with edition " attr(data-edition);}.example-wrap .tooltip:hover::before,.example-wrap .tooltip:hover::after{display:inline;}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip,.example-wrap.ignore .tooltip{font-weight:bold;font-size:1.25rem;}a.test-arrow{display:inline-block;visibility:hidden;position:absolute;padding:5px 10px 5px 10px;border-radius:5px;font-size:1.375rem;top:5px;right:5px;z-index:1;}.example-wrap:hover .test-arrow{visibility:visible;}a.test-arrow:hover{text-decoration:none;}.code-attribute{font-weight:300;color:var(--code-attribute-color);}.item-spacer{width:100%;height:12px;}.out-of-band>span.since{font-size:1.25rem;}h3.variant{font-weight:600;font-size:1.125rem;margin-bottom:10px;}.sub-variant h4{font-size:1rem;font-weight:400;margin-top:0;margin-bottom:0;}.sub-variant{margin-left:24px;margin-bottom:40px;}.sub-variant>.sub-variant-field{margin-left:24px;}:target>code,:target>.code-header{opacity:1;}:target{padding-right:3px;}.notable-traits-tooltip{display:inline-block;cursor:pointer;}.notable-traits:hover .notable-traits-tooltiptext,.notable-traits .notable-traits-tooltiptext.force-tooltip{display:inline-block;}.notable-traits .notable-traits-tooltiptext{display:none;padding:5px 3px 3px 3px;border-radius:6px;margin-left:5px;z-index:10;font-size:1rem;cursor:default;position:absolute;border:1px solid;}.notable-traits-tooltip::after{content:"\00a0\00a0\00a0";}.notable-traits .notable,.notable-traits .docblock{margin:0;}.notable-traits .notable{margin:0;margin-bottom:13px;font-size:1.1875rem;font-weight:600;display:block;}.notable-traits .docblock code.content{margin:0;padding:0;font-size:1.25rem;}.search-failed{text-align:center;margin-top:20px;display:none;}.search-failed.active{display:block;}.search-failed>ul{text-align:left;max-width:570px;margin-left:auto;margin-right:auto;}#titles{display:flex;flex-direction:row;gap:1px;margin-bottom:4px;}#titles>button{text-align:center;font-size:1.125rem;cursor:pointer;border:0;border-top:2px solid;flex:1;line-height:1.5;color:inherit;}#titles>button>div.count{display:inline-block;font-size:1rem;}.notable-traits{cursor:pointer;z-index:2;margin-left:5px;}#sidebar-toggle{position:sticky;top:0;left:0;font-size:1.25rem;border-bottom:1px solid;display:flex;height:40px;justify-content:center;align-items:center;z-index:10;}#source-sidebar{width:100%;overflow:auto;}#source-sidebar>.title{font-size:1.5rem;text-align:center;border-bottom:1px solid var(--border-color);margin-bottom:6px;}#sidebar-toggle>button{font-size:inherit;font-weight:bold;background:none;color:inherit;cursor:pointer;text-align:center;border:none;outline:none;position:absolute;top:0;bottom:0;left:0;right:0;width:100%;-webkit-appearance:none;opacity:1;}#settings-menu,#help-button{margin-left:4px;outline:none;}#settings-menu>a,#help-button>a,#copy-path{width:33px;cursor:pointer;line-height:1.5;}#settings-menu>a,#help-button>a{padding:5px;height:100%;display:block;background-color:var(--button-background-color);border:1px solid var(--border-color);border-radius:2px;}#copy-path{color:var(--copy-path-button-color);background:var(--main-background-color);height:34px;margin-left:10px;padding:0;padding-left:2px;border:0;}#copy-path>img{filter:var(--copy-path-img-filter);}#copy-path:hover>img{filter:var(--copy-path-img-hover-filter);}@keyframes rotating{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}#settings-menu.rotate>a img{animation:rotating 2s linear infinite;}#help-button>a{text-align:center;font-size:20px;padding-top:2px;}kbd{display:inline-block;padding:3px 5px;font:15px monospace;line-height:10px;vertical-align:middle;border:solid 1px var(--border-color);border-radius:3px;cursor:default;}ul.all-items>li{list-style:none;}details.dir-entry{padding-left:4px;}details.dir-entry>summary::after{content:" ►";position:absolute;left:-15px;top:0px;font-size:80%;padding:2px 0px;width:25px;}details[open].dir-entry>summary::after{content:" ▼";}details.dir-entry>summary::-webkit-details-marker,details.dir-entry>summary::marker{display:none;}details.dir-entry>summary{margin:0 0 0 13px;list-style:none;cursor:pointer;position:relative;}details.dir-entry div.folders,details.dir-entry div.files{padding-left:23px;}details.dir-entry a{display:block;}details.rustdoc-toggle{contain:layout;position:relative;}details.rustdoc-toggle>summary.hideme{cursor:pointer;}details.rustdoc-toggle>summary{list-style:none;}details.rustdoc-toggle>summary::-webkit-details-marker,details.rustdoc-toggle>summary::marker{display:none;}details.rustdoc-toggle>summary.hideme>span{margin-left:9px;}details.rustdoc-toggle>summary::before{content:"";cursor:pointer;width:16px;height:16px;background-repeat:no-repeat;background-position:top left;display:inline-block;vertical-align:middle;opacity:.5;}details.rustdoc-toggle>summary.hideme>span,.more-examples-toggle summary,.more-examples-toggle .hide-more{color:var(--toggles-color);}details.rustdoc-toggle>summary::after{content:"Expand";overflow:hidden;width:0;height:0;position:absolute;}details.rustdoc-toggle>summary.hideme::after{content:"";}details.rustdoc-toggle>summary:focus::before,details.rustdoc-toggle>summary:hover::before{opacity:1;}details.rustdoc-toggle.top-doc>summary,details.rustdoc-toggle.top-doc>summary::before,details.rustdoc-toggle.non-exhaustive>summary,details.rustdoc-toggle.non-exhaustive>summary::before{font-size:1rem;}details.non-exhaustive{margin-bottom:8px;}details.rustdoc-toggle>summary.hideme::before{position:relative;}details.rustdoc-toggle>summary:not(.hideme)::before{position:absolute;left:-24px;top:4px;}.impl-items>details.rustdoc-toggle>summary:not(.hideme)::before{position:absolute;left:-24px;}details.rustdoc-toggle[open] >summary.hideme{position:absolute;}details.rustdoc-toggle[open] >summary.hideme>span{display:none;}details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle[open] >summary.hideme::before{background-image:url("toggle-minus.svg");}details.rustdoc-toggle>summary::before{background-image:url("toggle-plus.svg");}details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle[open] >summary.hideme::before{width:16px;height:16px;background-repeat:no-repeat;background-position:top left;display:inline-block;content:"";}details.rustdoc-toggle[open] >summary::after,details.rustdoc-toggle[open] >summary.hideme::after{content:"Collapse";}.docblock summary>*{display:inline-block;}.docblock>.example-wrap:first-child .tooltip{margin-top:16px;}@media (max-width:700px){*[id]{scroll-margin-top:45px;}.rustdoc{padding-top:0px;display:block;}main{padding-left:15px;padding-top:0px;}.main-heading{flex-direction:column;}.out-of-band{text-align:left;margin-left:initial;padding:initial;}.out-of-band .since::before{content:"Since ";}#copy-path{display:none;}.sidebar .sidebar-logo,.sidebar .location{display:none;}.sidebar{position:fixed;top:45px;left:-1000px;margin-left:0;margin:0;padding:0;z-index:11;height:calc(100vh - 45px);}.source main,.rustdoc.source .sidebar{top:0;padding:0;height:100vh;border:0;}.sidebar.shown,.source-sidebar-expanded .source .sidebar,.sidebar:focus-within{left:0;}.rustdoc.source>.sidebar{width:0;}.mobile-topbar h2{padding-bottom:0;margin:auto 0.5em auto auto;overflow:hidden;font-size:24px;}.mobile-topbar h2 a{display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}.mobile-topbar .logo-container{max-height:45px;}.mobile-topbar .logo-container>img{max-width:35px;max-height:35px;margin-left:20px;margin-top:5px;margin-bottom:5px;}.mobile-topbar{display:flex;flex-direction:row;position:sticky;z-index:10;font-size:2rem;height:45px;width:100%;left:0;top:0;}.sidebar-menu-toggle{width:45px;font-size:32px;border:none;color:var(--main-color);}.sidebar-elems{margin-top:1em;background-color:var(--sidebar-background-color);}.content{margin-left:0px;}.anchor{display:none !important;}#titles>button>div.count{display:block;}#sidebar-filler{position:fixed;left:45px;width:calc(100% - 45px);top:0;height:45px;z-index:-1;border-bottom:1px solid;}#main-content>details.rustdoc-toggle>summary::before,#main-content>div>details.rustdoc-toggle>summary::before{left:-11px;}#sidebar-toggle{position:fixed;left:1px;top:100px;width:30px;font-size:1.5rem;text-align:center;padding:0;z-index:10;border-top-right-radius:3px;border-bottom-right-radius:3px;cursor:pointer;border:1px solid;border-left:0;}.source-sidebar-expanded #sidebar-toggle{left:unset;top:unset;width:unset;border-top-right-radius:unset;border-bottom-right-radius:unset;position:sticky;border:0;border-bottom:1px solid;}.notable-traits .notable-traits-tooltiptext{left:0;top:100%;}#help-button{display:none;}.item-table{display:block;}.item-row{display:flex;flex-flow:column wrap;}.item-left,.item-right{width:100%;}.search-results>a{border-bottom:1px solid #aaa9;padding:5px 0px;}.search-results .result-name,.search-results div.desc{width:100%;}.search-results div.desc,.item-right{padding-left:2em;}.source-sidebar-expanded .source .sidebar{max-width:100vw;width:100vw;}details.rustdoc-toggle:not(.top-doc)>summary{margin-left:10px;}.impl-items>details.rustdoc-toggle>summary:not(.hideme)::before,#main-content>details.rustdoc-toggle:not(.top-doc)>summary::before,#main-content>div>details.rustdoc-toggle>summary::before{left:-11px;}.impl-items>.item-info{margin-left:34px;}.source nav.sub{margin:0;padding:8px;}}@media print{nav.sidebar,nav.sub,.out-of-band,a.srclink,#copy-path,details.rustdoc-toggle[open] >summary::before,details.rustdoc-toggle>summary::before,details.rustdoc-toggle.top-doc>summary{display:none;}.docblock{margin-left:0;}main{padding:10px;}}@media (max-width:464px){.docblock{margin-left:12px;}.docblock code{overflow-wrap:break-word;overflow-wrap:anywhere;}nav.sub{flex-direction:column;}nav.sub form{align-self:stretch;}.sub-logo-container>img{height:35px;width:35px;}#sidebar-toggle{top:10px;}.source-sidebar-expanded #sidebar-toggle{top:unset;}}.method-toggle>summary,.implementors-toggle>summary,.impl,#implementors-list>.docblock,.impl-items>section,.methods>section{margin-bottom:0.75em;}.method-toggle[open]:not(:last-child),.implementors-toggle[open]:not(:last-child){margin-bottom:2em;}#trait-implementations-list .method-toggle:not(:last-child),#synthetic-implementations-list .method-toggle:not(:last-child),#blanket-implementations-list .method-toggle:not(:last-child){margin-bottom:1em;}.scraped-example-list .scrape-help{margin-left:10px;padding:0 4px;font-weight:normal;font-size:12px;position:relative;bottom:1px;background:transparent;border-width:1px;border-style:solid;border-radius:50px;}.scraped-example .code-wrapper{position:relative;display:flex;flex-direction:row;flex-wrap:wrap;width:100%;}.scraped-example:not(.expanded) .code-wrapper{max-height:240px;}.scraped-example:not(.expanded) .code-wrapper pre{overflow-y:hidden;max-height:240px;padding-bottom:0;}.scraped-example:not(.expanded) .code-wrapper pre.src-line-numbers{overflow-x:hidden;}.scraped-example .code-wrapper .prev{position:absolute;top:0.25em;right:2.25em;z-index:100;cursor:pointer;}.scraped-example .code-wrapper .next{position:absolute;top:0.25em;right:1.25em;z-index:100;cursor:pointer;}.scraped-example .code-wrapper .expand{position:absolute;top:0.25em;right:0.25em;z-index:100;cursor:pointer;}.scraped-example:not(.expanded) .code-wrapper:before{content:" ";width:100%;height:5px;position:absolute;z-index:100;top:0;}.scraped-example:not(.expanded) .code-wrapper:after{content:" ";width:100%;height:5px;position:absolute;z-index:100;bottom:0;}.scraped-example .code-wrapper .src-line-numbers{margin:0;padding:14px 0;}.scraped-example .code-wrapper .src-line-numbers span{padding:0 14px;}.scraped-example .code-wrapper .example-wrap{flex:1;overflow-x:auto;overflow-y:hidden;margin-bottom:0;}.scraped-example:not(.expanded) .code-wrapper .example-wrap{overflow-x:hidden;}.scraped-example .code-wrapper .example-wrap pre.rust{overflow-x:inherit;width:inherit;overflow-y:hidden;}.more-examples-toggle{max-width:calc(100% + 25px);margin-top:10px;margin-left:-25px;}.more-examples-toggle .hide-more{margin-left:25px;margin-bottom:5px;cursor:pointer;}.more-scraped-examples{margin-left:5px;display:flex;flex-direction:row;}.more-scraped-examples-inner{width:calc(100% - 20px);}.toggle-line{align-self:stretch;margin-right:10px;margin-top:5px;padding:0 4px;cursor:pointer;}.toggle-line-inner{min-width:2px;height:100%;}.more-scraped-examples .scraped-example{margin-bottom:20px;}.more-scraped-examples .scraped-example:last-child{margin-bottom:0;}.example-links a{margin-top:20px;}.example-links ul{margin-bottom:0;} \ No newline at end of file diff --git a/docs/search-index.js b/docs/search-index.js deleted file mode 100644 index 8a6424369d84..000000000000 --- a/docs/search-index.js +++ /dev/null @@ -1,80 +0,0 @@ -var searchIndex = JSON.parse('{\ -"ahash":{"doc":"AHash is a hashing algorithm is intended to be a high …","t":[3,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AHasher","CallHasher","RandomState","borrow","borrow","borrow_mut","borrow_mut","build_hasher","clone","clone","clone_into","clone_into","default","default","finish","fmt","fmt","from","from","generate_with","get_hash","into","into","new","new_with_keys","set_random_source","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","with_seed","with_seeds","write","write_u128","write_u16","write_u32","write_u64","write_u8","write_usize"],"q":["ahash","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A Hasher for hashing an arbitrary stream of bytes.","Provides a way to get an optimized hasher for a given data …","Provides a Hasher factory. This is typically used (e.g. by …","","","","","Constructs a new AHasher with keys based on this …","","","","","Constructs a new AHasher with fixed keys. If std is …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Allows for supplying seeds, but each time it is called the …","","Calls U::from(self).","Calls U::from(self).","Use randomly generated keys","Creates a new hasher keyed to the provided key.","Provides an optional way to manually supply a source of …","","","","","","","","","Allows for explicitly setting a seed to used.","Allows for explicitly setting the seeds to used.","","","","","","",""],"i":[0,0,0,2,1,2,1,1,2,1,2,1,2,1,2,2,1,2,1,1,16,2,1,1,2,1,2,1,2,1,2,1,2,1,1,1,2,2,2,2,2,2,2],"f":[0,0,0,[[]],[[]],[[]],[[]],[1,2],[2,2],[1,1],[[]],[[]],[[],2],[[],1],[2,3],[[2,4],5],[[1,4],5],[[]],[[]],[[3,3,3,3],1],[[],3],[[]],[[]],[[],1],[[6,6],2],[[[0,[0,7,8]]],[[10,[9]]]],[[]],[[]],[[],10],[[],10],[[],10],[[],10],[[],11],[[],11],[12,1],[[3,3,3,3],1],[2],[[2,6]],[[2,13]],[[2,14]],[[2,3]],[[2,15]],[[2,12]]],"p":[[3,"RandomState"],[3,"AHasher"],[15,"u64"],[3,"Formatter"],[6,"Result"],[15,"u128"],[8,"Send"],[8,"Sync"],[15,"bool"],[4,"Result"],[3,"TypeId"],[15,"usize"],[15,"u16"],[15,"u32"],[15,"u8"],[8,"CallHasher"]]},\ -"aho_corasick":{"doc":"A library for finding occurrences of many patterns at …","t":[3,3,3,4,3,3,13,13,3,4,13,13,8,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,13,13,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AhoCorasick","AhoCorasickBuilder","Error","ErrorKind","FindIter","FindOverlappingIter","LeftmostFirst","LeftmostLongest","Match","MatchKind","PremultiplyOverflow","Standard","StateID","StateIDOverflow","StreamFindIter","anchored","ascii_case_insensitive","auto_configure","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build_with_size","byte_classes","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","dense_depth","description","dfa","earliest_find","end","eq","eq","find","find_iter","find_overlapping_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_usize","hash","heap_bytes","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_empty","is_match","kind","len","match_kind","match_kind","max_id","max_pattern_len","new","new","new_auto_configured","next","next","next","packed","pattern","pattern_count","prefilter","premultiply","provide","replace_all","replace_all_bytes","replace_all_with","replace_all_with_bytes","start","stream_find_iter","stream_replace_all","stream_replace_all_with","supports_overlapping","supports_stream","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_usize","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","max","max","requested_max","Builder","Config","FindIter","LeftmostFirst","LeftmostLongest","MatchKind","Searcher","add","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","builder","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","default","default","default","eq","extend","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","heap_bytes","into","into","into","into","into","into_iter","match_kind","match_kind","minimum_len","new","new","new","next","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id"],"q":["aho_corasick","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","aho_corasick::ErrorKind","","","aho_corasick::packed","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An automaton for searching multiple strings in linear time.","A builder for configuring an Aho-Corasick automaton.","An error that occurred during the construction of an …","The kind of error that occurred.","An iterator of non-overlapping matches in a particular …","An iterator of overlapping matches in a particular …","Use leftmost-first match semantics, which reports leftmost …","Use leftmost-longest match semantics, which reports …","A representation of a match reported by an Aho-Corasick …","A knob for controlling the match semantics of an …","An error that occurs when premultiplication of state IDs …","Use standard match semantics, which support overlapping …","A trait describing the representation of an automaton’s …","An error that occurs when constructing an automaton would …","An iterator that reports Aho-Corasick matches in a stream.","Enable anchored mode, which requires all matches to start …","Enable ASCII-aware case insensitive matching.","Automatically configure the settings on this builder …","","","","","","","","","","","","","","","","","","","Build an Aho-Corasick automaton using the configuration …","Build an Aho-Corasick automaton using the configuration …","Shrink the size of the transition alphabet by mapping …","","","","","","","","","","","","","","","Set the limit on how many NFA states use a dense …","","Compile the standard Aho-Corasick automaton into a …","Returns the location of the first detected match in …","The ending position of the match.","","","Returns the location of the first match according to the …","Returns an iterator of non-overlapping matches, using the …","Returns an iterator of overlapping matches in the given …","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from a usize to this implementation’s …","","Returns the approximate total amount of heap used by this …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if and only if this match is empty. That is, …","Returns true if and only if this automaton matches the …","Return the kind of this error.","The length, in bytes, of the match.","Returns the match kind used by this automaton.","Set the desired match semantics.","Return the maximum state identifier supported by this …","Returns the length of the longest pattern matched by this …","Create a new Aho-Corasick automaton using the default …","Create a new builder for configuring an Aho-Corasick …","Build an Aho-Corasick automaton with an automatically …","","","","A lower level API for packed multiple substring search, …","Returns the identifier of the pattern that matched.","Return the total number of patterns matched by this …","Enable heuristic prefilter optimizations.","Premultiply state identifiers in the transition table. …","","Replace all matches with a corresponding value in the …","Replace all matches using raw bytes with a corresponding …","Replace all matches using a closure called on each match. …","Replace all matches using raw bytes with a closure called …","The starting position of the match.","Returns an iterator of non-overlapping matches in the given","Search for and replace all matches of this automaton in …","Search the given reader and replace all matches of this …","Returns true if and only if this automaton supports …","Returns true if and only if this automaton supports stream …","","","","","","","","Convert this implementation’s representation to a usize.","","","","","","","","","","","","","","","","","","","","","","","","","","","","The maximum possible state ID.","The maximum possible state id.","The maximum ID required by premultiplication.","A builder for constructing a packed searcher from a …","The configuration for a packed multiple pattern searcher.","An iterator over non-overlapping matches from a packed …","Use leftmost-first match semantics, which reports leftmost …","Use leftmost-longest match semantics, which reports …","A knob for controlling the match semantics of a packed …","A packed searcher for quickly finding occurrences of …","Add the given pattern to this set to match.","","","","","","","","","","","Build a searcher from the patterns added to this builder …","Create a packed builder from this configuration. The …","","","","","","","","","","","","","Add the given iterator of patterns to this set to match.","Return the first occurrence of any of the patterns in this …","Return the first occurrence of any of the patterns in this …","Return an iterator of non-overlapping occurrences of the …","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the approximate total amount of heap used by this …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Set the match semantics for this configuration.","Returns the match kind used by this packed searcher.","Returns the minimum length of a haystack that is required …","Create a new default configuration. A default …","Create a new builder for constructing a multi-pattern …","A convenience function for constructing a searcher from an …","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,8,8,0,0,9,8,0,9,0,1,1,1,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,1,1,1,3,1,8,4,9,10,3,1,8,4,9,10,1,8,1,4,1,3,10,8,10,3,3,3,3,15,16,20,1,8,4,4,9,10,3,15,16,20,1,8,4,9,10,7,10,3,3,15,16,20,1,8,4,9,10,15,16,20,10,3,4,10,3,1,7,3,3,1,3,15,16,20,0,10,3,1,1,4,3,3,3,3,10,3,3,3,3,3,3,1,8,4,9,10,4,7,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,3,15,16,20,1,8,4,9,10,33,34,34,0,0,0,31,31,0,0,28,31,30,28,29,32,31,30,28,29,32,28,30,31,30,28,29,31,30,28,29,31,30,28,31,28,29,29,29,31,30,28,29,32,31,30,28,29,32,29,31,30,28,29,32,32,30,29,29,30,28,29,32,31,30,28,29,31,30,28,29,32,31,30,28,29,32,31,30,28,29,32],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2],1],[[1,2],1],[1,1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,3],[1,[[5,[3,4]]]],[[1,2],1],[[[3,[[0,[6,7]]]]],[[3,[[0,[6,7]]]]]],[1,1],[8,8],[4,4],[9,9],[10,10],[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[],8],[[1,11],1],[4,12],[[1,2],1],[[[3,[7]],13],[[14,[10]]]],[10,11],[[8,8],2],[[10,10],2],[[[3,[7]],13],[[14,[10]]]],[[[3,[7]]],[[15,[7]]]],[[[3,[7]]],[[16,[7]]]],[[[3,[[0,[17,7]]]],18],19],[[[15,[[0,[17,7]]]],18],19],[[[16,[[0,[17,7]]]],18],19],[[[20,[17,[0,[17,7]]]],18],19],[[1,18],19],[[8,18],19],[[4,18],19],[[4,18],19],[[9,18],19],[[10,18],19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[11],[10],[[[3,[7]]],11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[10,2],[[[3,[7]],13],2],[4,9],[10,11],[[[3,[7]]],8],[[1,8],1],[[],11],[[[3,[7]]],11],[[],3],[[],1],[[],3],[[[15,[7]]],[[14,[10]]]],[[[16,[7]]],[[14,[10]]]],[[[20,[21,7]]],[[14,[[22,[10]]]]]],0,[10,11],[[[3,[7]]],11],[[1,2],1],[[1,2],1],[23],[[[3,[7]],12],24],[[[3,[7]]],[[26,[25]]]],[[[3,[7]],12,24]],[[[3,[7]],26]],[10,11],[[[3,[7]],21],[[20,[21,7]]]],[[[3,[7]]],22],[[[3,[7]]],22],[[[3,[7]]],2],[[[3,[7]]],2],[[]],[[]],[[]],[[]],[[]],[[]],[[],24],[[],11],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],0,0,0,0,0,0,0,0,0,0,[[28,13],28],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[28,[[14,[29]]]],[30,28],[31,31],[30,30],[28,28],[29,29],[[]],[[]],[[]],[[]],[[],31],[[],30],[[],28],[[31,31],2],[28,28],[[29,13],[[14,[10]]]],[[29,13,11],[[14,[10]]]],[29,32],[[31,18],19],[[30,18],19],[[28,18],19],[[29,18],19],[[32,18],19],[[]],[[]],[[]],[[]],[[]],[29,11],[[]],[[]],[[]],[[]],[[]],[[]],[[30,31],30],[29,31],[29,11],[[],30],[[],28],[[],[[14,[29]]]],[32,[[14,[10]]]],[[]],[[]],[[]],[[]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],27],[[],27],[[],27],[[],27],[[],27]],"p":[[3,"AhoCorasickBuilder"],[15,"bool"],[3,"AhoCorasick"],[3,"Error"],[4,"Result"],[8,"Clone"],[8,"StateID"],[4,"MatchKind"],[4,"ErrorKind"],[3,"Match"],[15,"usize"],[15,"str"],[8,"AsRef"],[4,"Option"],[3,"FindIter"],[3,"FindOverlappingIter"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[3,"StreamFindIter"],[8,"Read"],[6,"Result"],[3,"Demand"],[3,"String"],[15,"u8"],[3,"Vec"],[3,"TypeId"],[3,"Builder"],[3,"Searcher"],[3,"Config"],[4,"MatchKind"],[3,"FindIter"],[13,"StateIDOverflow"],[13,"PremultiplyOverflow"]]},\ -"aiofut":{"doc":"Straightforward Linux AIO using Futures/async/await.","t":[3,3,3,3,3,3,3,6,8,6,4,13,13,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["AIO","AIOBatchSchedulerIn","AIOBatchSchedulerOut","AIOBuilder","AIOFuture","AIOManager","AIONotifier","AIOResult","EmulatedFailure","EmulatedFailureShared","Error","LowKernelRes","MaxEventsTooLarge","NotSupported","OtherError","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","copy_data","default","drop","drop","drop","fmt","from","from","from","from","from","from","from","from","get_id","get_npending","into","into","into","into","into","into","into","into","into_future","max_events","max_nbatched","max_nwait","poll","read","tick","timeout","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write"],"q":["aiofut","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Represent the necessary data for an AIO operation. …","","","","Represents a scheduled (future) asynchronous I/O …","Manager all AIOs.","The state machine for finished AIO operations and wakes up …","The result of an AIO operation: the number of bytes …","","","","","","","","","","","","","","","","","","","","","","","","Build an AIOManager object based on the configuration (and …","Get a copy of the current data in the buffer.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Get the number of pending AIOs (approximation).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Maximum concurrent async IO operations.","Maximum number of IOs per submission.","Maximum complete IOs per poll.","","","","Timeout for a polling iteration (default is None).","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,1,2,1,9,10,2,3,9,10,23,1,2,24,25,3,10,2,9,10,23,1,2,24,25,3,10,1,1,1,10,2,26,1,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,9,10,23,1,2,24,25,3,2],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,[[4,[2,3]]]],[[2,5],[[8,[[7,[6]]]]]],[[],1],[9],[10],[2],[[3,11],12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[10,5],[2,13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,14],1],[[1,13],1],[[1,15],1],[[[16,[10]],17],18],[[2,19,5,13,[8,[15]]],10],[[],[[8,[20]]]],[[1,14],1],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[2,19,5,22,[8,[15]]],10]],"p":[[3,"AIOBuilder"],[3,"AIOManager"],[4,"Error"],[4,"Result"],[15,"u64"],[15,"u8"],[3,"Vec"],[4,"Option"],[3,"AIO"],[3,"AIOFuture"],[3,"Formatter"],[6,"Result"],[15,"usize"],[15,"u32"],[15,"u16"],[3,"Pin"],[3,"Context"],[4,"Poll"],[6,"RawFd"],[15,"i64"],[3,"TypeId"],[3,"Box"],[3,"AIONotifier"],[3,"AIOBatchSchedulerIn"],[3,"AIOBatchSchedulerOut"],[8,"EmulatedFailure"]]},\ -"async_trait":{"doc":"github crates-io docs-rs","t":[23],"n":["async_trait"],"q":["async_trait"],"d":[""],"i":[0],"f":[0],"p":[]},\ -"bincode":{"doc":"Bincode is a crate for encoding and decoding using a tiny …","t":[2,3,13,2,13,2,6,4,13,13,13,13,13,2,6,13,3,13,11,11,11,11,11,0,5,11,11,0,11,5,5,5,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,8,3,3,11,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["BincodeRead","Config","Custom","DefaultOptions","DeserializeAnyNotSupported","Deserializer","Error","ErrorKind","InvalidBoolEncoding","InvalidCharEncoding","InvalidTagEncoding","InvalidUtf8Encoding","Io","Options","Result","SequenceMustHaveLength","Serializer","SizeLimit","borrow","borrow","borrow_mut","borrow_mut","cause","config","config","custom","custom","de","description","deserialize","deserialize_from","deserialize_from_custom","fmt","fmt","from","from","from","into","into","is_human_readable","new","options","provide","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_f32","serialize_f64","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_into","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","serialized_size","to_string","try_from","try_from","try_into","try_into","type_id","type_id","0","0","0","0","0","0","AllowTrailing","BigEndian","Bounded","Config","DefaultOptions","FixintEncoding","Infinite","LittleEndian","NativeEndian","Options","RejectTrailing","VarintEncoding","WithOtherEndian","WithOtherIntEncoding","WithOtherLimit","WithOtherTrailing","allow_trailing_bytes","big_endian","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","deserialize","deserialize","deserialize_from","deserialize_from","deserialize_from_custom","deserialize_from_custom","deserialize_from_custom_seed","deserialize_from_custom_seed","deserialize_from_seed","deserialize_from_seed","deserialize_seed","deserialize_seed","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","limit","little_endian","native_endian","new","no_limit","reject_trailing_bytes","serialize","serialize","serialize_into","serialize_into","serialized_size","serialized_size","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_big_endian","with_fixint_encoding","with_limit","with_little_endian","with_native_endian","with_no_limit","with_varint_encoding","Deserializer","borrow","borrow_mut","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","from","from_slice","into","is_human_readable","newtype_variant_seed","read","struct_variant","try_from","try_into","tuple_variant","type_id","unit_variant","variant_seed","with_bincode_read","with_reader","BincodeRead","IoReader","SliceReader","borrow","borrow","borrow_mut","borrow_mut","forward_read_bytes","forward_read_bytes","forward_read_bytes","forward_read_str","forward_read_str","forward_read_str","from","from","get_byte_buffer","get_byte_buffer","get_byte_buffer","into","into","read","read","read_exact","read_exact","try_from","try_from","try_into","try_into","type_id","type_id"],"q":["bincode","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::ErrorKind","","","","","bincode::config","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::de","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bincode::de::read","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","A configuration builder whose options Bincode will use …","A custom error message from Serde.","","Serde has a deserialize_any method that lets the format …","","An error that can be produced during (de)serializing.","The kind of error that can be produced during a …","Returned if the deserializer attempts to deserialize a …","Returned if the deserializer attempts to deserialize a …","Returned if the deserializer attempts to deserialize the …","Returned if the deserializer attempts to deserialize a …","If the error stems from the reader/writer that is being …","","The result of a serialization or deserialization operation.","Bincode can not encode sequences of unknown length (like …","An Serializer that encodes values directly into a Writer.","If (de)serializing a message takes more than the provided …","","","","","","bincode uses a Builder-pattern to configure the …","Get a default configuration object.","","","Deserialize bincode data to a Rust data structure.","","Deserializes a slice of bytes into an instance of T using …","Deserializes an object directly from a Reader using the …","Deserializes an object from a custom BincodeReader using …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","Creates a new Serializer with the given Writer.","Get a default configuration object.","","Serializes a serializable object into a Vec of bytes using …","","","","","","","","","","","Serializes an object directly into a Writer using the …","","","","","","","","","","","","","","","","","","","","","Returns the size that an object would be if serialized …","","","","","","","","","","","","","","A TrailingBytes config that will allow trailing bytes in …","Big-endian byte ordering.","A SizeLimit that restricts serialized or deserialized …","A configuration builder whose options Bincode will use …","The default options for bincode …","Fixed-size integer encoding.","A SizeLimit without a limit! Use this if you don’t care …","Little-endian byte ordering.","The native byte ordering of the current system.","A configuration builder trait whose options Bincode will …","A TrailingBytes config that will cause bincode to produce …","Variable-size integer encoding (excepting [ui]8).","A configuration struct with a user-specified endian order","A configuration struct with a user-specified length …","A configuration struct with a user-specified byte limit","A configuration struct with a user-specified trailing …","Sets the deserializer to allow trailing bytes","Sets the endianness to big-endian","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Deserializes a slice of bytes into an instance of T using …","Deserializes a slice of bytes into an instance of T using …","Deserializes an object directly from a Reader using this …","Deserializes an object directly from a Reader using this …","Deserializes an object from a custom BincodeReader using …","Deserializes an object from a custom BincodeReader using …","Deserializes an object from a custom BincodeReader with …","Deserializes an object from a custom BincodeReader with …","Deserializes an object directly from a Reader with state …","Deserializes an object directly from a Reader with state …","Deserializes a slice of bytes with state seed using this …","Deserializes a slice of bytes with state seed using this …","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Sets the byte limit to limit.","Sets the endianness to little-endian This is the default.","Sets the endianness to the the machine-native endianness","Get a default configuration object.","Sets the byte limit to be unlimited. This is the default.","Sets the deserializer to reject trailing bytes","Serializes a serializable object into a Vec of bytes using …","Serializes a serializable object into a Vec of bytes using …","Serializes an object directly into a Writer using this …","Serializes an object directly into a Writer using this …","Returns the size that an object would be if serialized …","Returns the size that an object would be if serialized …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Sets the endianness to big-endian","Sets the length encoding to be fixed","Sets the byte limit to limit.","Sets the endianness to little-endian This is the default.","Sets the endianness to the the machine-native endianness","Sets the byte limit to be unlimited. This is the default.","Sets the length encoding to varint","A Deserializer that reads bytes from a buffer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Creates a new Deserializer that will read from the given …","Calls U::from(self).","","","Specialized ways to read data into bincode.","","","","","","","","Creates a new Deserializer with the given BincodeReader","Creates a new Deserializer with a given Reader and options.","An optional Read trait for advanced Bincode usage.","A BincodeRead implementation for io::Readers","A BincodeRead implementation for byte slices","","","","","Pass a slice of the next length bytes on to the serde …","","","Check that the next length bytes are a valid string and …","","","Returns the argument unchanged.","Returns the argument unchanged.","Transfer ownership of the next length bytes to the caller.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","",""],"i":[0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,1,0,1,12,1,12,1,1,0,0,6,6,0,1,0,0,0,1,1,6,12,1,12,1,12,12,0,1,0,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,1,12,1,12,1,12,1,59,60,61,62,63,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,16,15,4,15,4,15,4,15,4,15,4,15,4,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,4,4,4,16,4,15,15,4,15,4,15,4,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,38,39,40,41,42,4,43,44,36,45,16,47,48,49,37,15,15,15,15,15,15,15,0,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,0,55,55,55,55,55,55,55,55,55,0,0,0,56,57,56,57,53,56,57,53,56,57,56,57,53,56,57,56,57,56,57,56,57,56,57,56,57,56,57],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[1,[[3,[2]]]],0,[[],4],[5,6],[5,6],0,[1,7],[[],8],[[],8],[[],8],[[1,9],10],[[1,9],10],[11,6],[[]],[[]],[[]],[[]],[12,13],[[14,15],[[12,[14,15]]]],[[],16],[17],[[],[[8,[[19,[18]]]]]],[[12,13],8],[12,8],[[12,20],8],[[12,21],8],[[12,22],8],[[12,23],8],[[12,24],8],[[12,25],8],[[12,26],8],[[12,27],8],[[],8],[[12,[3,[28]]],8],[[12,7],8],[[12,7,29,7],8],[12,8],[[12,[3,[28]]],8],[12,8],[[12,7],8],[[12,7,28],8],[[12,7,29,7,28],8],[[12,28],8],[[12,7,28],8],[[12,7,29,7,28],8],[[12,30],8],[[12,31],8],[[12,29],8],[[12,32],8],[[12,18],8],[12,8],[[12,7],8],[[12,7,29,7],8],[[],[[8,[32]]]],[[],33],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[37,[36]]]],[4,4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[38,38],[39,39],[40,40],[41,41],[42,42],[4,4],[43,43],[44,44],[36,36],[45,45],[16,16],[[[47,[[0,[46,15]],[0,[46,0]]]]],[[47,[[0,[46,15]],[0,[46,0]]]]]],[[[48,[[0,[46,15]],[0,[46,0]]]]],[[48,[[0,[46,15]],[0,[46,0]]]]]],[[[49,[[0,[46,15]],[0,[46,0]]]]],[[49,[[0,[46,15]],[0,[46,0]]]]]],[[[37,[[0,[46,15]],[0,[46,0]]]]],[[37,[[0,[46,15]],[0,[46,0]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],16],[[],[[8,[50]]]],[4,[[8,[50]]]],[51,[[8,[52]]]],[[4,51],[[8,[52]]]],[53,[[8,[52]]]],[[4,53],[[8,[52]]]],[[54,53],8],[[4,54,53],8],[[54,51],8],[[4,54,51],8],[54,8],[[4,54],8],[[4,9],10],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[4,32],4],[4,4],[4,4],[[],16],[4,4],[[],[[37,[45]]]],[[],[[8,[[19,[18]]]]]],[4,[[8,[[19,[18]]]]]],[14,8],[[4,14],8],[[],[[8,[32]]]],[4,[[8,[32]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],[[48,[39]]]],[[],[[49,[41]]]],[32,[[47,[43]]]],[[],[[48,[38]]]],[[],[[48,[40]]]],[[],[[47,[44]]]],[[],[[49,[42]]]],0,[[]],[[]],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[[55,28],8],[[55,7,28],8],[55,8],[55,8],[55,8],[55,8],[55,8],[55,8],[[55,7],8],[[]],[15,[[55,[56,15]]]],[[]],[55,13],[55,8],0,[55,8],[[],34],[[],34],[[55,28],8],[[],35],[55,8],[55,8],[[53,15],[[55,[53,15]]]],[[51,15],[[55,[[57,[51]],15]]]],0,0,0,[[]],[[]],[[]],[[]],[28,8],[[56,28],8],[[57,28],8],[28,8],[[56,28],8],[[57,28],8],[[]],[[]],[28,[[8,[[19,[18]]]]]],[[56,28],[[8,[[19,[18]]]]]],[[57,28],[[8,[[19,[18]]]]]],[[]],[[]],[56,[[58,[28]]]],[[[57,[51]]],[[58,[28]]]],[56,58],[[[57,[51]]],58],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35]],"p":[[4,"ErrorKind"],[8,"Error"],[4,"Option"],[3,"Config"],[8,"Display"],[6,"Error"],[15,"str"],[6,"Result"],[3,"Formatter"],[6,"Result"],[3,"Error"],[3,"Serializer"],[15,"bool"],[8,"Write"],[8,"Options"],[3,"DefaultOptions"],[3,"Demand"],[15,"u8"],[3,"Vec"],[15,"char"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"usize"],[15,"u32"],[15,"u128"],[15,"u16"],[15,"u64"],[3,"String"],[4,"Result"],[3,"TypeId"],[3,"AllowTrailing"],[3,"WithOtherTrailing"],[3,"LittleEndian"],[3,"BigEndian"],[3,"NativeEndian"],[3,"FixintEncoding"],[3,"VarintEncoding"],[3,"Bounded"],[3,"Infinite"],[3,"RejectTrailing"],[8,"Clone"],[3,"WithOtherLimit"],[3,"WithOtherEndian"],[3,"WithOtherIntEncoding"],[8,"Deserialize"],[8,"Read"],[8,"DeserializeOwned"],[8,"BincodeRead"],[8,"DeserializeSeed"],[3,"Deserializer"],[3,"SliceReader"],[3,"IoReader"],[6,"Result"],[13,"Io"],[13,"InvalidUtf8Encoding"],[13,"InvalidBoolEncoding"],[13,"InvalidTagEncoding"],[13,"Custom"]]},\ -"bitflags":{"doc":"A typesafe bitmask flag generator useful for sets of …","t":[14],"n":["bitflags"],"q":["bitflags"],"d":["The macro used to generate the flag structures."],"i":[0],"f":[0],"p":[]},\ -"block_buffer":{"doc":"Fixed size buffer for block processing of data.","t":[6,3,8,3,6,3,3,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Block","BlockBuffer","BufferKind","Eager","EagerBuffer","Error","Lazy","LazyBuffer","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","default","default","default","digest_blocks","digest_pad","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","generic_array","get_data","get_pos","into","into","into","into","len128_padding_be","len64_padding_be","len64_padding_le","new","pad_with_zeros","remaining","reset","set","set_data","size","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_new","type_id","type_id","type_id","type_id"],"q":["block_buffer","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Block on which BlockBuffer operates.","Buffer for block processing of data.","Trait for buffer kinds.","Eager block buffer kind, which guarantees that buffer …","Eager block buffer.","Block buffer error.","Lazy block buffer kind, which guarantees that buffer …","Lazy block buffer.","","","","","","","","","","","","","","","","Digest data in input in blocks of size BlockSize using the …","Compress remaining data after padding it with delim, zeros …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Return slice of data stored inside the buffer.","Return current cursor position.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Pad message with 0x80, zeros and 128-bit message length …","Pad message with 0x80, zeros and 64-bit message length …","Pad message with 0x80, zeros and 64-bit message length …","Create new buffer from slice.","Pad remaining data with zeros and return resulting block.","Return number of remaining bytes in the internall buffer.","Reset buffer by setting cursor position to zero.","Set buffer content and cursor position.","Set data to generated blocks.","Return size of the internall buffer in bytes.","","","","","","","","","Create new buffer from slice.","","","",""],"i":[0,0,0,0,0,0,0,0,1,2,3,4,1,2,3,4,1,2,3,4,1,2,4,4,4,3,1,2,3,3,4,1,2,3,4,0,4,4,1,2,3,4,4,4,4,4,4,4,4,4,4,4,1,2,3,4,1,2,3,4,4,1,2,3,4],"f":[0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[4,4],[[],1],[[],2],[[],4],[[4,5]],[[[4,[1]],6,5]],[[3,3],7],[[1,8],9],[[2,8],9],[[3,8],[[11,[10]]]],[[3,8],9],[[[4,[12,12]],8],9],[[]],[[]],[[]],[[]],0,[4],[4,13],[[]],[[]],[[]],[[]],[[[4,[1]],14,5]],[[[4,[1]],15,5]],[[[4,[1]],15,5]],[[],4],[4,16],[4,13],[4],[[4,16,13]],[[[4,[1]],5]],[4,13],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],[[11,[4,3]]]],[[],17],[[],17],[[],17],[[],17]],"p":[[3,"Eager"],[3,"Lazy"],[3,"Error"],[3,"BlockBuffer"],[8,"FnMut"],[15,"u8"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Error"],[4,"Result"],[8,"Debug"],[15,"usize"],[15,"u128"],[15,"u64"],[6,"Block"],[3,"TypeId"]]},\ -"byteorder":{"doc":"This crate provides convenience methods for encoding and …","t":[6,4,8,6,4,6,6,8,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,10,11,11,10,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11,10,11,11,11,11],"n":["BE","BigEndian","ByteOrder","LE","LittleEndian","NativeEndian","NetworkEndian","ReadBytesExt","WriteBytesExt","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","default","default","eq","eq","fmt","fmt","from","from","from_slice_f32","from_slice_f32","from_slice_f32","from_slice_f64","from_slice_f64","from_slice_f64","from_slice_i128","from_slice_i16","from_slice_i32","from_slice_i64","from_slice_u128","from_slice_u128","from_slice_u128","from_slice_u16","from_slice_u16","from_slice_u16","from_slice_u32","from_slice_u32","from_slice_u32","from_slice_u64","from_slice_u64","from_slice_u64","hash","hash","into","into","partial_cmp","partial_cmp","read_f32","read_f32","read_f32","read_f32_into","read_f32_into","read_f32_into","read_f32_into_unchecked","read_f32_into_unchecked","read_f32_into_unchecked","read_f64","read_f64","read_f64","read_f64_into","read_f64_into","read_f64_into","read_f64_into_unchecked","read_f64_into_unchecked","read_f64_into_unchecked","read_i128","read_i128","read_i128","read_i128_into","read_i128_into","read_i128_into","read_i16","read_i16","read_i16","read_i16_into","read_i16_into","read_i16_into","read_i24","read_i24","read_i24","read_i32","read_i32","read_i32","read_i32_into","read_i32_into","read_i32_into","read_i48","read_i48","read_i48","read_i64","read_i64","read_i64","read_i64_into","read_i64_into","read_i64_into","read_i8","read_i8","read_i8_into","read_i8_into","read_int","read_int","read_int","read_int128","read_int128","read_int128","read_u128","read_u128","read_u128","read_u128","read_u128","read_u128_into","read_u128_into","read_u128_into","read_u128_into","read_u128_into","read_u16","read_u16","read_u16","read_u16","read_u16","read_u16_into","read_u16_into","read_u16_into","read_u16_into","read_u16_into","read_u24","read_u24","read_u24","read_u32","read_u32","read_u32","read_u32","read_u32","read_u32_into","read_u32_into","read_u32_into","read_u32_into","read_u32_into","read_u48","read_u48","read_u48","read_u64","read_u64","read_u64","read_u64","read_u64","read_u64_into","read_u64_into","read_u64_into","read_u64_into","read_u64_into","read_u8","read_u8","read_uint","read_uint","read_uint","read_uint","read_uint","read_uint128","read_uint128","read_uint128","read_uint128","read_uint128","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","write_f32","write_f32","write_f32","write_f32_into","write_f64","write_f64","write_f64","write_f64_into","write_i128","write_i128","write_i128","write_i128_into","write_i16","write_i16","write_i16","write_i16_into","write_i24","write_i24","write_i24","write_i32","write_i32","write_i32","write_i32_into","write_i48","write_i48","write_i48","write_i64","write_i64","write_i64","write_i64_into","write_i8","write_i8","write_i8_into","write_int","write_int","write_int","write_int128","write_int128","write_int128","write_u128","write_u128","write_u128","write_u128","write_u128","write_u128_into","write_u128_into","write_u128_into","write_u16","write_u16","write_u16","write_u16","write_u16","write_u16_into","write_u16_into","write_u16_into","write_u24","write_u24","write_u24","write_u32","write_u32","write_u32","write_u32","write_u32","write_u32_into","write_u32_into","write_u32_into","write_u48","write_u48","write_u48","write_u64","write_u64","write_u64","write_u64","write_u64","write_u64_into","write_u64_into","write_u64_into","write_u8","write_u8","write_uint","write_uint","write_uint","write_uint","write_uint","write_uint128","write_uint128","write_uint128","write_uint128","write_uint128"],"q":["byteorder","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A type alias for BigEndian.","Defines big-endian serialization.","ByteOrder describes types that can serialize integers as …","A type alias for LittleEndian.","Defines little-endian serialization.","Defines system native-endian serialization.","Defines network byte order serialization.","Extends Read with methods for reading numbers. (For std::io…","Extends Write with methods for writing numbers. (For …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Converts the given slice of IEEE754 single-precision (4 …","","","Converts the given slice of IEEE754 double-precision (8 …","","","Converts the given slice of signed 128 bit integers to a …","Converts the given slice of signed 16 bit integers to a …","Converts the given slice of signed 32 bit integers to a …","Converts the given slice of signed 64 bit integers to a …","Converts the given slice of unsigned 128 bit integers to a …","","","Converts the given slice of unsigned 16 bit integers to a …","","","Converts the given slice of unsigned 32 bit integers to a …","","","Converts the given slice of unsigned 64 bit integers to a …","","","","","Calls U::from(self).","Calls U::from(self).","","","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a IEEE754 single-precision (4 bytes) floating point …","Reads a sequence of IEEE754 single-precision (4 bytes) …","Reads a sequence of IEEE754 single-precision (4 bytes) …","Reads IEEE754 single-precision (4 bytes) floating point …","DEPRECATED.","DEPRECATED.","DEPRECATED.","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a IEEE754 double-precision (8 bytes) floating point …","Reads a sequence of IEEE754 double-precision (8 bytes) …","Reads a sequence of IEEE754 double-precision (8 bytes) …","Reads IEEE754 single-precision (4 bytes) floating point …","DEPRECATED.","DEPRECATED.","DEPRECATED.","Reads a signed 128 bit integer from the underlying reader.","Reads a signed 128 bit integer from the underlying reader.","Reads a signed 128 bit integer from buf.","Reads a sequence of signed 128 bit integers from the …","Reads a sequence of signed 128 bit integers from the …","Reads signed 128 bit integers from src into dst.","Reads a signed 16 bit integer from the underlying reader.","Reads a signed 16 bit integer from the underlying reader.","Reads a signed 16 bit integer from buf.","Reads a sequence of signed 16 bit integers from the …","Reads a sequence of signed 16 bit integers from the …","Reads signed 16 bit integers from src to dst.","Reads a signed 24 bit integer from the underlying reader.","Reads a signed 24 bit integer from the underlying reader.","Reads a signed 24 bit integer from buf, stored in i32.","Reads a signed 32 bit integer from the underlying reader.","Reads a signed 32 bit integer from the underlying reader.","Reads a signed 32 bit integer from buf.","Reads a sequence of signed 32 bit integers from the …","Reads a sequence of signed 32 bit integers from the …","Reads signed 32 bit integers from src into dst.","Reads a signed 48 bit integer from the underlying reader.","Reads a signed 48 bit integer from the underlying reader.","Reads a signed 48 bit integer from buf, stored in i64.","Reads a signed 64 bit integer from the underlying reader.","Reads a signed 64 bit integer from the underlying reader.","Reads a signed 64 bit integer from buf.","Reads a sequence of signed 64 bit integers from the …","Reads a sequence of signed 64 bit integers from the …","Reads signed 64 bit integers from src into dst.","Reads a signed 8 bit integer from the underlying reader.","Reads a signed 8 bit integer from the underlying reader.","Reads a sequence of signed 8 bit integers from the …","Reads a sequence of signed 8 bit integers from the …","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from buf.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from the underlying reader.","Reads a signed n-bytes integer from buf.","Reads an unsigned 128 bit integer from buf.","Reads an unsigned 128 bit integer from the underlying …","Reads an unsigned 128 bit integer from the underlying …","","","Reads unsigned 128 bit integers from src into dst.","Reads a sequence of unsigned 128 bit integers from the …","Reads a sequence of unsigned 128 bit integers from the …","","","Reads an unsigned 16 bit integer from buf.","Reads an unsigned 16 bit integer from the underlying …","Reads an unsigned 16 bit integer from the underlying …","","","Reads unsigned 16 bit integers from src into dst.","Reads a sequence of unsigned 16 bit integers from the …","Reads a sequence of unsigned 16 bit integers from the …","","","Reads an unsigned 24 bit integer from the underlying …","Reads an unsigned 24 bit integer from the underlying …","Reads an unsigned 24 bit integer from buf, stored in u32.","Reads an unsigned 32 bit integer from buf.","Reads an unsigned 32 bit integer from the underlying …","Reads an unsigned 32 bit integer from the underlying …","","","Reads unsigned 32 bit integers from src into dst.","Reads a sequence of unsigned 32 bit integers from the …","Reads a sequence of unsigned 32 bit integers from the …","","","Reads an unsigned 48 bit integer from the underlying …","Reads an unsigned 48 bit integer from the underlying …","Reads an unsigned 48 bit integer from buf, stored in u64.","Reads an unsigned 64 bit integer from buf.","Reads an unsigned 64 bit integer from the underlying …","Reads an unsigned 64 bit integer from the underlying …","","","Reads unsigned 64 bit integers from src into dst.","Reads a sequence of unsigned 64 bit integers from the …","Reads a sequence of unsigned 64 bit integers from the …","","","Reads an unsigned 8 bit integer from the underlying reader.","Reads an unsigned 8 bit integer from the underlying reader.","Reads an unsigned n-bytes integer from buf.","Reads an unsigned n-bytes integer from the underlying …","Reads an unsigned n-bytes integer from the underlying …","","","Reads an unsigned n-bytes integer from buf.","Reads an unsigned n-bytes integer from the underlying …","Reads an unsigned n-bytes integer from the underlying …","","","","","","","","","","","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 single-precision (4 bytes) floating point …","Writes IEEE754 single-precision (4 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes a IEEE754 double-precision (8 bytes) floating point …","Writes IEEE754 double-precision (8 bytes) floating point …","Writes a signed 128 bit integer to the underlying writer.","Writes a signed 128 bit integer to the underlying writer.","Writes a signed 128 bit integer n to buf.","Writes signed 128 bit integers from src into dst.","Writes a signed 16 bit integer to the underlying writer.","Writes a signed 16 bit integer to the underlying writer.","Writes a signed 16 bit integer n to buf.","Writes signed 16 bit integers from src into dst.","Writes a signed 24 bit integer to the underlying writer.","Writes a signed 24 bit integer to the underlying writer.","Writes a signed 24 bit integer n to buf, stored in i32.","Writes a signed 32 bit integer to the underlying writer.","Writes a signed 32 bit integer to the underlying writer.","Writes a signed 32 bit integer n to buf.","Writes signed 32 bit integers from src into dst.","Writes a signed 48 bit integer to the underlying writer.","Writes a signed 48 bit integer to the underlying writer.","Writes a signed 48 bit integer n to buf, stored in i64.","Writes a signed 64 bit integer to the underlying writer.","Writes a signed 64 bit integer to the underlying writer.","Writes a signed 64 bit integer n to buf.","Writes signed 64 bit integers from src into dst.","Writes a signed 8 bit integer to the underlying writer.","Writes a signed 8 bit integer to the underlying writer.","Writes signed 8 bit integers from src into dst.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed integer n to buf using only nbytes.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed n-bytes integer to the underlying writer.","Writes a signed integer n to buf using only nbytes.","Writes an unsigned 128 bit integer n to buf.","Writes an unsigned 128 bit integer to the underlying …","Writes an unsigned 128 bit integer to the underlying …","","","Writes unsigned 128 bit integers from src into dst.","","","Writes an unsigned 16 bit integer n to buf.","Writes an unsigned 16 bit integer to the underlying writer.","Writes an unsigned 16 bit integer to the underlying writer.","","","Writes unsigned 16 bit integers from src into dst.","","","Writes an unsigned 24 bit integer to the underlying writer.","Writes an unsigned 24 bit integer to the underlying writer.","Writes an unsigned 24 bit integer n to buf, stored in u32.","Writes an unsigned 32 bit integer n to buf.","Writes an unsigned 32 bit integer to the underlying writer.","Writes an unsigned 32 bit integer to the underlying writer.","","","Writes unsigned 32 bit integers from src into dst.","","","Writes an unsigned 48 bit integer to the underlying writer.","Writes an unsigned 48 bit integer to the underlying writer.","Writes an unsigned 48 bit integer n to buf, stored in u64.","Writes an unsigned 64 bit integer n to buf.","Writes an unsigned 64 bit integer to the underlying writer.","Writes an unsigned 64 bit integer to the underlying writer.","","","Writes unsigned 64 bit integers from src into dst.","","","Writes an unsigned 8 bit integer to the underlying writer.","Writes an unsigned 8 bit integer to the underlying writer.","Writes an unsigned integer n to buf using only nbytes.","Writes an unsigned n-bytes integer to the underlying …","Writes an unsigned n-bytes integer to the underlying …","","","Writes an unsigned integer n to buf using only nbytes.","Writes an unsigned n-bytes integer to the underlying …","Writes an unsigned n-bytes integer to the underlying …","",""],"i":[0,0,0,0,0,0,0,0,0,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,24,1,2,24,1,2,24,24,24,24,24,1,2,24,1,2,24,1,2,24,1,2,1,2,1,2,1,2,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,24,25,25,25,25,25,25,24,25,25,24,24,25,25,1,2,24,25,25,1,2,24,25,25,1,2,24,25,25,1,2,25,25,24,24,25,25,1,2,24,25,25,1,2,25,25,24,24,25,25,1,2,24,25,25,1,2,25,25,24,25,25,1,2,24,25,25,1,2,1,2,1,2,1,2,1,2,26,26,24,24,26,26,24,24,26,26,24,24,26,26,24,24,26,26,24,26,26,24,24,26,26,24,26,26,24,24,26,26,24,26,26,24,26,26,24,24,26,26,1,2,24,1,2,24,26,26,1,2,24,1,2,26,26,24,24,26,26,1,2,24,1,2,26,26,24,24,26,26,1,2,24,1,2,26,26,24,26,26,1,2,24,26,26,1,2],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[1,1],[2,2],[[]],[[]],[[1,1],3],[[2,2],3],[[],1],[[],2],[[1,1],4],[[2,2],4],[[1,5],6],[[2,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2],[[]],[[]],[[1,1],[[7,[3]]]],[[2,2],[[7,[3]]]],[[],[[9,[8]]]],[[],[[9,[8]]]],[[],8],[[],9],[[],9],[[]],[[],9],[[],9],[[]],[[],[[9,[10]]]],[[],[[9,[10]]]],[[],10],[[],9],[[],9],[[]],[[],9],[[],9],[[]],[[],[[9,[11]]]],[[],[[9,[11]]]],[[],11],[[],9],[[],9],[[]],[[],[[9,[12]]]],[[],[[9,[12]]]],[[],12],[[],9],[[],9],[[]],[[],[[9,[13]]]],[[],[[9,[13]]]],[[],13],[[],[[9,[13]]]],[[],[[9,[13]]]],[[],13],[[],9],[[],9],[[]],[[],[[9,[14]]]],[[],[[9,[14]]]],[[],14],[[],[[9,[14]]]],[[],[[9,[14]]]],[[],14],[[],9],[[],9],[[]],[[],[[9,[15]]]],[[],[[9,[15]]]],[[],9],[[],9],[16,[[9,[14]]]],[16,[[9,[14]]]],[16,14],[16,[[9,[11]]]],[16,[[9,[11]]]],[16,11],[[],17],[[],[[9,[17]]]],[[],[[9,[17]]]],[[],17],[[],17],[[]],[[],9],[[],9],[[]],[[]],[[],18],[[],[[9,[18]]]],[[],[[9,[18]]]],[[],18],[[],18],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[19]]]],[[],[[9,[19]]]],[[],19],[[],19],[[],[[9,[19]]]],[[],[[9,[19]]]],[[],19],[[],19],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[20]]]],[[],[[9,[20]]]],[[],20],[[],20],[[],[[9,[20]]]],[[],[[9,[20]]]],[[],20],[[],20],[[]],[[],9],[[],9],[[]],[[]],[[],[[9,[21]]]],[[],[[9,[21]]]],[16,20],[16,[[9,[20]]]],[16,[[9,[20]]]],[16,20],[16,20],[16,17],[16,[[9,[17]]]],[16,[[9,[17]]]],[16,17],[16,17],[[]],[[]],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],[8,9],[8,9],[8],[[]],[10,9],[10,9],[10],[[]],[11,9],[11,9],[11],[[]],[12,9],[12,9],[12],[[]],[13,9],[13,9],[13],[13,9],[13,9],[13],[[]],[14,9],[14,9],[14],[14,9],[14,9],[14],[[]],[15,9],[15,9],[[]],[[14,16],9],[[14,16],9],[[14,16]],[[11,16],9],[[11,16],9],[[11,16]],[17],[17,9],[17,9],[17],[17],[[]],[[]],[[]],[18],[18,9],[18,9],[18],[18],[[]],[[]],[[]],[19,9],[19,9],[19],[19],[19,9],[19,9],[19],[19],[[]],[[]],[[]],[20,9],[20,9],[20],[20],[20,9],[20,9],[20],[20],[[]],[[]],[[]],[21,9],[21,9],[[20,16]],[[20,16],9],[[20,16],9],[[20,16]],[[20,16]],[[17,16]],[[17,16],9],[[17,16],9],[[17,16]],[[17,16]]],"p":[[4,"BigEndian"],[4,"LittleEndian"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[15,"f32"],[6,"Result"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"usize"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"u8"],[4,"Result"],[3,"TypeId"],[8,"ByteOrder"],[8,"ReadBytesExt"],[8,"WriteBytesExt"]]},\ -"bytes":{"doc":"Provides abstractions for working with bytes.","t":[8,8,3,3,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,0,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,8,3,3,3,3,3,3,3,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Buf","BufMut","Bytes","BytesMut","advance","advance","advance","advance_mut","advance_mut","as_mut","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","buf","capacity","chunk","chunk","chunk","chunk_mut","chunk_mut","clear","clear","clone","clone","clone_into","clone_into","cmp","cmp","copy_from_slice","copy_to_bytes","copy_to_bytes","default","default","deref","deref","deref_mut","drop","drop","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend_from_slice","fmt","fmt","fmt","fmt","fmt","fmt","freeze","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_static","hash","hash","into","into","into_iter","into_iter","into_iter","into_iter","is_empty","is_empty","len","len","new","new","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","put","put_bytes","put_slice","remaining","remaining","remaining","remaining_mut","remaining_mut","reserve","resize","set_len","slice","slice_ref","spare_capacity_mut","split","split_off","split_off","split_to","split_to","to_owned","to_owned","truncate","truncate","try_from","try_from","try_into","try_into","type_id","type_id","unsplit","with_capacity","write_fmt","write_str","zeroed","Buf","BufMut","Chain","IntoIter","Limit","Reader","Take","UninitSlice","Writer","advance","advance","advance","advance_mut","advance_mut","advance_mut","as_mut_ptr","as_uninit_slice_mut","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chain","chain","chain","chain_mut","chain_mut","chain_mut","chunk","chunk","chunk","chunk_mut","chunk_mut","chunk_mut","chunks_vectored","chunks_vectored","chunks_vectored","chunks_vectored","consume","copy_from_slice","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_bytes","copy_to_slice","copy_to_slice","copy_to_slice","fill_buf","first_mut","first_ref","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_raw_parts_mut","get_f32","get_f32","get_f32","get_f32_le","get_f32_le","get_f32_le","get_f32_ne","get_f32_ne","get_f32_ne","get_f64","get_f64","get_f64","get_f64_le","get_f64_le","get_f64_le","get_f64_ne","get_f64_ne","get_f64_ne","get_i128","get_i128","get_i128","get_i128_le","get_i128_le","get_i128_le","get_i128_ne","get_i128_ne","get_i128_ne","get_i16","get_i16","get_i16","get_i16_le","get_i16_le","get_i16_le","get_i16_ne","get_i16_ne","get_i16_ne","get_i32","get_i32","get_i32","get_i32_le","get_i32_le","get_i32_le","get_i32_ne","get_i32_ne","get_i32_ne","get_i64","get_i64","get_i64","get_i64_le","get_i64_le","get_i64_le","get_i64_ne","get_i64_ne","get_i64_ne","get_i8","get_i8","get_i8","get_int","get_int","get_int","get_int_le","get_int_le","get_int_le","get_int_ne","get_int_ne","get_int_ne","get_mut","get_mut","get_mut","get_mut","get_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_u128","get_u128","get_u128","get_u128_le","get_u128_le","get_u128_le","get_u128_ne","get_u128_ne","get_u128_ne","get_u16","get_u16","get_u16","get_u16_le","get_u16_le","get_u16_le","get_u16_ne","get_u16_ne","get_u16_ne","get_u32","get_u32","get_u32","get_u32_le","get_u32_le","get_u32_le","get_u32_ne","get_u32_ne","get_u32_ne","get_u64","get_u64","get_u64","get_u64_le","get_u64_le","get_u64_le","get_u64_ne","get_u64_ne","get_u64_ne","get_u8","get_u8","get_u8","get_uint","get_uint","get_uint","get_uint_le","get_uint_le","get_uint_le","get_uint_ne","get_uint_ne","get_uint_ne","has_remaining","has_remaining","has_remaining","has_remaining_mut","has_remaining_mut","has_remaining_mut","index","index","index","index","index","index","index_mut","index_mut","index_mut","index_mut","index_mut","index_mut","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","last_mut","last_ref","len","limit","limit","limit","limit","limit","next","put","put","put","put_bytes","put_bytes","put_bytes","put_f32","put_f32","put_f32","put_f32_le","put_f32_le","put_f32_le","put_f32_ne","put_f32_ne","put_f32_ne","put_f64","put_f64","put_f64","put_f64_le","put_f64_le","put_f64_le","put_f64_ne","put_f64_ne","put_f64_ne","put_i128","put_i128","put_i128","put_i128_le","put_i128_le","put_i128_le","put_i128_ne","put_i128_ne","put_i128_ne","put_i16","put_i16","put_i16","put_i16_le","put_i16_le","put_i16_le","put_i16_ne","put_i16_ne","put_i16_ne","put_i32","put_i32","put_i32","put_i32_le","put_i32_le","put_i32_le","put_i32_ne","put_i32_ne","put_i32_ne","put_i64","put_i64","put_i64","put_i64_le","put_i64_le","put_i64_le","put_i64_ne","put_i64_ne","put_i64_ne","put_i8","put_i8","put_i8","put_int","put_int","put_int","put_int_le","put_int_le","put_int_le","put_int_ne","put_int_ne","put_int_ne","put_slice","put_slice","put_slice","put_u128","put_u128","put_u128","put_u128_le","put_u128_le","put_u128_le","put_u128_ne","put_u128_ne","put_u128_ne","put_u16","put_u16","put_u16","put_u16_le","put_u16_le","put_u16_le","put_u16_ne","put_u16_ne","put_u16_ne","put_u32","put_u32","put_u32","put_u32_le","put_u32_le","put_u32_le","put_u32_ne","put_u32_ne","put_u32_ne","put_u64","put_u64","put_u64","put_u64_le","put_u64_le","put_u64_le","put_u64_ne","put_u64_ne","put_u64_ne","put_u8","put_u8","put_u8","put_uint","put_uint","put_uint","put_uint_le","put_uint_le","put_uint_le","put_uint_ne","put_uint_ne","put_uint_ne","read","reader","reader","reader","remaining","remaining","remaining","remaining_mut","remaining_mut","remaining_mut","set_limit","set_limit","size_hint","take","take","take","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write","write_byte","writer","writer","writer"],"q":["bytes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","bytes::buf","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Read bytes from a buffer.","A trait for values that provide sequential write access to …","A cheaply cloneable and sliceable chunk of contiguous …","A unique reference to a contiguous slice of memory.","Advance the internal cursor of the Buf","","","Advance the internal cursor of the BufMut","","","","","","","","","","","","Utilities for working with buffers.","Returns the number of bytes the BytesMut can hold without …","Returns a slice starting at the current position and of …","","","Returns a mutable slice starting at the current BufMut …","","Clears the buffer, removing all data.","Clears the buffer, removing all data. Existing capacity is …","","","","","","","Creates Bytes instance from slice, by copying it.","","","","","","","","","","","","","","","","","","","","","","","","","","","Appends given bytes to this BytesMut.","","","","","","","Converts self into an immutable Bytes.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Creates a new Bytes from a static slice.","","","Calls U::from(self).","Calls U::from(self).","","","","","Returns true if the Bytes has a length of 0.","Returns true if the BytesMut has a length of 0.","Returns the number of bytes contained in this Bytes.","Returns the number of bytes contained in this BytesMut.","Creates a new empty Bytes.","Creates a new BytesMut with default capacity.","","","","","","","","","","","","","","","","Returns the number of bytes between the current position …","","","Returns the number of bytes that can be written from the …","","Reserves capacity for at least additional more bytes to be …","Resizes the buffer so that len is equal to new_len.","Sets the length of the buffer.","Returns a slice of self for the provided range.","Returns a slice of self that is equivalent to the given …","Returns the remaining spare capacity of the buffer as a …","Removes the bytes from the current view, returning them in …","Splits the bytes into two at the given index.","Splits the bytes into two at the given index.","Splits the bytes into two at the given index.","Splits the buffer into two at the given index.","","","Shortens the buffer, keeping the first len bytes and …","Shortens the buffer, keeping the first len bytes and …","","","","","","","Absorbs a BytesMut that was previously split off.","Creates a new BytesMut with the specified capacity.","","","Creates a new BytesMut, which is initialized with zero.","Read bytes from a buffer.","A trait for values that provide sequential write access to …","A Chain sequences two buffers.","Iterator over the bytes contained by the buffer.","A BufMut adapter which limits the amount of bytes that can …","A Buf adapter which implements io::Read for the inner …","A Buf adapter which limits the bytes read from an …","Uninitialized byte slice.","A BufMut adapter which implements io::Write for the inner …","Advance the internal cursor of the Buf","","","Advance the internal cursor of the BufMut","","","Return a raw pointer to the slice’s buffer.","Return a &mut [MaybeUninit<u8>] to this slice’s buffer.","","","","","","","","","","","","","","","Creates an adaptor which will chain this buffer with …","Creates an adaptor which will chain this buffer with …","Creates an adaptor which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Creates an adapter which will chain this buffer with …","Returns a slice starting at the current position and of …","","","Returns a mutable slice starting at the current BufMut …","","","Fills dst with potentially multiple slices starting at self…","Fills dst with potentially multiple slices starting at self…","Fills dst with potentially multiple slices starting at self…","","","Copies bytes from src into self.","Consumes len bytes inside self and returns new instance of …","Consumes len bytes inside self and returns new instance of …","Consumes len bytes inside self and returns new instance of …","","","Copies bytes from self into dst.","Copies bytes from self into dst.","Copies bytes from self into dst.","","Gets a mutable reference to the first underlying Buf.","Gets a reference to the first underlying Buf.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create a &mut UninitSlice from a pointer and a length.","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 single-precision (4 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets an IEEE754 double-precision (8 bytes) floating point …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in big-endian byte …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in little-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 128 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in big-endian byte …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in little-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 16 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in big-endian byte …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in little-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 32 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in big-endian byte …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in little-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 64 bit integer from self in native-endian …","Gets a signed 8 bit integer from self.","Gets a signed 8 bit integer from self.","Gets a signed 8 bit integer from self.","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in big-endian byte …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in little-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a signed n-byte integer from self in native-endian …","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying BufMut.","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying Buf.","Gets a mutable reference to the underlying BufMut.","Gets a reference to the underlying Buf.","Gets a reference to the underlying BufMut.","Gets a reference to the underlying Buf.","Gets a reference to the underlying Buf.","Gets a reference to the underlying BufMut.","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in big-endian …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 128 bit integer from self in …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in big-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in little-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 16 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in the …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 32 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in big-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in little-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 64 bit integer from self in native-endian …","Gets an unsigned 8 bit integer from self.","Gets an unsigned 8 bit integer from self.","Gets an unsigned 8 bit integer from self.","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in big-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in little-endian …","Gets an unsigned n-byte integer from self in native-endian …","Gets an unsigned n-byte integer from self in native-endian …","Gets an unsigned n-byte integer from self in native-endian …","Returns true if there are any more bytes to consume","Returns true if there are any more bytes to consume","Returns true if there are any more bytes to consume","Returns true if there is space in self for more bytes.","Returns true if there is space in self for more bytes.","Returns true if there is space in self for more bytes.","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this Chain, returning the underlying values.","Consumes this IntoIter, returning the underlying value.","Consumes this Limit, returning the underlying value.","Consumes this Reader, returning the underlying value.","Consumes this Take, returning the underlying value.","Consumes this Writer, returning the underlying value.","","","Gets a mutable reference to the last underlying Buf.","Gets a reference to the last underlying Buf.","Returns the number of bytes in the slice.","Creates an adaptor which can write at most limit bytes to …","Creates an adaptor which can write at most limit bytes to …","Creates an adaptor which can write at most limit bytes to …","Returns the maximum number of bytes that can be written","Returns the maximum number of bytes that can be read.","","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Put cnt bytes val into self.","Put cnt bytes val into self.","Put cnt bytes val into self.","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 single-precision (4 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes an IEEE754 double-precision (8 bytes) floating …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in the big-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in little-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 128 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in big-endian byte …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in little-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 16 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in big-endian byte …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in little-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 32 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in the big-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in little-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 64 bit integer to self in native-endian …","Writes a signed 8 bit integer to self.","Writes a signed 8 bit integer to self.","Writes a signed 8 bit integer to self.","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Writes low nbytes of a signed integer to self in …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Transfer bytes into self from src and advance the cursor …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in the …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 128 bit integer to self in …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in big-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in little-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 16 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in big-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in little-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 32 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in the …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in little-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 64 bit integer to self in native-endian …","Writes an unsigned 8 bit integer to self.","Writes an unsigned 8 bit integer to self.","Writes an unsigned 8 bit integer to self.","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in big-endian …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","Writes an unsigned n-byte integer to self in the …","","Creates an adaptor which implements the Read trait for self…","Creates an adaptor which implements the Read trait for self…","Creates an adaptor which implements the Read trait for self…","Returns the number of bytes between the current position …","","","Returns the number of bytes that can be written from the …","","","Sets the maximum number of bytes that can be written.","Sets the maximum number of bytes that can be read.","","Creates an adaptor which will read at most limit bytes …","Creates an adaptor which will read at most limit bytes …","Creates an adaptor which will read at most limit bytes …","","","","","","","","","","","","","","","","","","","","","Write a single byte at the specified offset.","Creates an adaptor which implements the Write trait for …","Creates an adaptor which implements the Write trait for …","Creates an adaptor which implements the Write trait for …"],"i":[0,0,0,0,16,2,3,23,3,3,2,3,2,2,3,3,2,3,3,0,3,16,2,3,23,3,2,3,2,3,2,3,2,3,2,2,3,2,3,2,3,3,2,3,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,3,3,3,2,2,2,2,2,2,2,3,3,3,2,3,3,2,2,3,2,3,2,2,3,3,2,3,2,3,2,3,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,16,2,3,23,3,3,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,16,21,22,23,21,24,4,4,4,21,30,24,26,22,28,4,21,30,24,26,22,28,16,16,16,23,23,23,16,21,22,23,21,24,16,16,16,21,26,4,16,16,16,21,22,16,16,16,26,21,21,28,4,21,30,24,26,22,28,21,30,24,26,22,28,4,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,30,24,26,22,28,30,24,26,22,28,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,23,23,23,4,4,4,4,4,4,4,4,4,4,4,4,21,30,24,26,22,28,21,30,24,26,22,28,21,30,21,21,4,23,23,23,24,22,30,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,26,16,16,16,16,21,22,23,21,24,24,22,30,16,16,16,21,30,24,26,22,28,21,30,24,26,22,28,4,21,30,24,26,22,28,28,4,23,23,23],"f":[0,0,0,0,[1],[[2,1]],[[3,1]],[1],[[3,1]],[3],[2],[3],[[]],[2],[3],[[]],[[]],[3],[[]],0,[3,1],[[]],[2],[3],[[],4],[3,4],[2],[3],[2,2],[3,3],[[]],[[]],[[2,2],5],[[3,3],5],[[],2],[[2,1],2],[[3,1],2],[[],2],[[],3],[2],[3],[3],[2],[3],[[2,6],7],[[2,3],7],[2,7],[[2,8],7],[[2,2],7],[[2,9],7],[2,7],[[3,3],7],[[3,6],7],[3,7],[[3,2],7],[3,7],[[3,8],7],[[3,9],7],[3],[3],[3],[3],[[2,10],11],[[2,10],11],[[2,10],11],[[3,10],11],[[3,10],11],[[3,10],11],[3,2],[12,2],[3,2],[[[9,[13]]],2],[6,2],[8,2],[[],2],[[]],[[]],[8,3],[[],3],[14,2],[14,3],[14,3],[[],2],[2],[3],[[]],[[]],[2],[2],[3],[3],[2,7],[3,7],[2,1],[3,1],[[],2],[[],3],[2,[[15,[5]]]],[2,[[15,[5]]]],[[2,8],[[15,[5]]]],[[2,9],[[15,[5]]]],[[2,6],[[15,[5]]]],[[2,2],[[15,[5]]]],[[3,3],[[15,[5]]]],[3,[[15,[5]]]],[[3,6],[[15,[5]]]],[[3,9],[[15,[5]]]],[[3,8],[[15,[5]]]],[3,[[15,[5]]]],[[3,16]],[[3,13,1]],[3],[[],1],[2,1],[3,1],[[],1],[3,1],[[3,1]],[[3,1,13]],[[3,1]],[[2,[17,[1]]],2],[2,2],[3],[3,3],[[2,1],2],[[3,1],3],[[2,1],2],[[3,1],3],[[]],[[]],[[2,1]],[[3,1]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[3,3]],[1,3],[[3,20],11],[[3,8],11],[1,3],0,0,0,0,0,0,0,0,0,[1],[[21,1]],[[[22,[16]],1]],[1],[[21,1]],[[[24,[23]],1]],[4,13],[4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,[[21,[16]]]],[16,[[21,[16]]]],[16,[[21,[16]]]],[23,[[21,[23]]]],[23,[[21,[23]]]],[23,[[21,[23]]]],[[]],[21],[[[22,[16]]]],[[],4],[21,4],[[[24,[23]]],4],[[],1],[[],1],[[],1],[21,1],[[[26,[[0,[16,25]]]],1]],[4],[1,2],[1,2],[1,2],[[21,1],2],[[[22,[16]],1],2],[[]],[[]],[[]],[[[26,[[0,[16,25]]]]],27],[21],[21],[[[28,[[0,[23,25]]]]],27],[[4,10],11],[[[21,[29,29]],10],11],[[[30,[29]],10],11],[[[24,[29]],10],11],[[[26,[29]],10],11],[[[22,[29]],10],11],[[[28,[29]],10],11],[[]],[[]],[[]],[[]],[[]],[[]],[[13,1],4],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],31],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],37],[[],37],[[],37],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[1,36],[30],[24],[[[26,[16]]]],[22],[[[28,[23]]]],[30],[24],[[[26,[16]]]],[22],[[[28,[23]]]],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],38],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],40],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],41],[[],13],[[],13],[[],13],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[1,41],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[4,[42,[1]]],4],[[4,[43,[1]]],4],[[4,44],4],[[4,[45,[1]]],4],[[4,[46,[1]]],4],[[4,[47,[1]]],4],[[4,[43,[1]]],4],[[4,[46,[1]]],4],[[4,[47,[1]]],4],[[4,44],4],[[4,[42,[1]]],4],[[4,[45,[1]]],4],[[]],[[]],[[]],[[]],[[]],[[]],[21],[30],[24],[[[26,[16]]],16],[22],[[[28,[23]]],23],[21],[[]],[21],[21],[4,1],[1,24],[1,24],[1,24],[24,1],[22,1],[[[30,[16]]],[[15,[13]]]],[16],[16],[16],[[13,1]],[[13,1]],[[13,1]],[31],[31],[31],[31],[31],[31],[31],[31],[31],[32],[32],[32],[32],[32],[32],[32],[32],[32],[33],[33],[33],[33],[33],[33],[33],[33],[33],[34],[34],[34],[34],[34],[34],[34],[34],[34],[35],[35],[35],[35],[35],[35],[35],[35],[35],[36],[36],[36],[36],[36],[36],[36],[36],[36],[37],[37],[37],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[36,1]],[[]],[[]],[[]],[38],[38],[38],[38],[38],[38],[38],[38],[38],[39],[39],[39],[39],[39],[39],[39],[39],[39],[40],[40],[40],[40],[40],[40],[40],[40],[40],[41],[41],[41],[41],[41],[41],[41],[41],[41],[13],[13],[13],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[41,1]],[[[26,[[0,[16,25]]]]],[[27,[1]]]],[[],26],[[],26],[[],26],[[],1],[21,1],[[[22,[16]]],1],[[],1],[21,1],[[[24,[23]]],1],[[24,1]],[[22,1]],[[[30,[16]]]],[1,22],[1,22],[1,22],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[[28,[[0,[23,25]]]]],[[27,[1]]]],[[4,1,13]],[[],28],[[],28],[[],28]],"p":[[15,"usize"],[3,"Bytes"],[3,"BytesMut"],[3,"UninitSlice"],[4,"Ordering"],[3,"String"],[15,"bool"],[15,"str"],[3,"Vec"],[3,"Formatter"],[6,"Result"],[3,"Box"],[15,"u8"],[8,"IntoIterator"],[4,"Option"],[8,"Buf"],[8,"RangeBounds"],[4,"Result"],[3,"TypeId"],[3,"Arguments"],[3,"Chain"],[3,"Take"],[8,"BufMut"],[3,"Limit"],[8,"Sized"],[3,"Reader"],[6,"Result"],[3,"Writer"],[8,"Debug"],[3,"IntoIter"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[3,"RangeFrom"],[3,"RangeInclusive"],[3,"RangeFull"],[3,"Range"],[3,"RangeToInclusive"],[3,"RangeTo"]]},\ -"cfg_if":{"doc":"A macro for defining #[cfg] if-else statements.","t":[14],"n":["cfg_if"],"q":["cfg_if"],"d":["The main macro provided by this crate. See crate …"],"i":[0],"f":[0],"p":[]},\ -"cpufeatures":{"doc":"This crate provides macros for runtime CPU feature …","t":[14],"n":["new"],"q":["cpufeatures"],"d":["Create module with CPU feature detection code."],"i":[0],"f":[0],"p":[]},\ -"crc":{"doc":"crc","t":[3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,3,8,12,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["Algorithm","CRC_10_ATM","CRC_10_CDMA2000","CRC_10_GSM","CRC_11_FLEXRAY","CRC_11_UMTS","CRC_12_CDMA2000","CRC_12_DECT","CRC_12_GSM","CRC_12_UMTS","CRC_13_BBC","CRC_14_DARC","CRC_14_GSM","CRC_15_CAN","CRC_15_MPT1327","CRC_16_ARC","CRC_16_CDMA2000","CRC_16_CMS","CRC_16_DDS_110","CRC_16_DECT_R","CRC_16_DECT_X","CRC_16_DNP","CRC_16_EN_13757","CRC_16_GENIBUS","CRC_16_GSM","CRC_16_IBM_3740","CRC_16_IBM_SDLC","CRC_16_ISO_IEC_14443_3_A","CRC_16_KERMIT","CRC_16_LJ1200","CRC_16_MAXIM_DOW","CRC_16_MCRF4XX","CRC_16_MODBUS","CRC_16_NRSC_5","CRC_16_OPENSAFETY_A","CRC_16_OPENSAFETY_B","CRC_16_PROFIBUS","CRC_16_RIELLO","CRC_16_SPI_FUJITSU","CRC_16_T10_DIF","CRC_16_TELEDISK","CRC_16_TMS37157","CRC_16_UMTS","CRC_16_USB","CRC_16_XMODEM","CRC_17_CAN_FD","CRC_21_CAN_FD","CRC_24_BLE","CRC_24_FLEXRAY_A","CRC_24_FLEXRAY_B","CRC_24_INTERLAKEN","CRC_24_LTE_A","CRC_24_LTE_B","CRC_24_OPENPGP","CRC_24_OS_9","CRC_30_CDMA","CRC_31_PHILIPS","CRC_32_AIXM","CRC_32_AUTOSAR","CRC_32_BASE91_D","CRC_32_BZIP2","CRC_32_CD_ROM_EDC","CRC_32_CKSUM","CRC_32_ISCSI","CRC_32_ISO_HDLC","CRC_32_JAMCRC","CRC_32_MEF","CRC_32_MPEG_2","CRC_32_XFER","CRC_3_GSM","CRC_3_ROHC","CRC_40_GSM","CRC_4_G_704","CRC_4_INTERLAKEN","CRC_5_EPC_C1G2","CRC_5_G_704","CRC_5_USB","CRC_64_ECMA_182","CRC_64_GO_ISO","CRC_64_MS","CRC_64_WE","CRC_64_XZ","CRC_6_CDMA2000_A","CRC_6_CDMA2000_B","CRC_6_DARC","CRC_6_GSM","CRC_6_G_704","CRC_7_MMC","CRC_7_ROHC","CRC_7_UMTS","CRC_82_DARC","CRC_8_AUTOSAR","CRC_8_BLUETOOTH","CRC_8_CDMA2000","CRC_8_DARC","CRC_8_DVB_S2","CRC_8_GSM_A","CRC_8_GSM_B","CRC_8_HITAG","CRC_8_I_432_1","CRC_8_I_CODE","CRC_8_LTE","CRC_8_MAXIM_DOW","CRC_8_MIFARE_MAD","CRC_8_NRSC_5","CRC_8_OPENSAFETY","CRC_8_ROHC","CRC_8_SAE_J1850","CRC_8_SMBUS","CRC_8_TECH_3250","CRC_8_WCDMA","Crc","Digest","Width","algorithm","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","check","checksum","checksum","checksum","checksum","checksum","clone","digest","digest","digest","digest","digest","digest_with_initial","digest_with_initial","digest_with_initial","digest_with_initial","digest_with_initial","finalize","finalize","finalize","finalize","finalize","from","from","from","init","into","into","into","new","new","new","new","new","poly","refin","refout","residue","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","update","update","update","update","update","width","xorout"],"q":["crc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["This struct describes a CRC algorithm using the fields …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The contents of the register after initialising, reading …","","","","","","","","","","","","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","Construct a Digest with a given initial value.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","The settings of the bit cells at the start of each …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","The generator polynomial that sets the feedback tap …","If equal to false, specifies that the characters of the …","If equal to false, specifies that the contents of the …","The contents of the register after initialising, reading …","","","","","","","","","","","","","","","The number of bit cells in the linear feedback shift …","The XOR value applied to the contents of the register …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,10,2,9,10,2,9,10,2,2,2,2,2,9,2,2,2,2,2,2,2,2,2,2,9,9,9,9,9,10,2,9,10,10,2,9,2,2,2,2,2,10,10,10,10,10,2,9,10,2,9,10,2,9,9,9,9,9,9,10,10],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],0,[[[2,[1]]],1],[[[2,[3]]],3],[[[2,[4]]],4],[[[2,[5]]],5],[[[2,[6]]],6],[[[9,[[0,[7,8]]]]],[[9,[[0,[7,8]]]]]],[[[2,[3]]],[[9,[3]]]],[[[2,[6]]],[[9,[6]]]],[[[2,[4]]],[[9,[4]]]],[[[2,[5]]],[[9,[5]]]],[[[2,[1]]],[[9,[1]]]],[[[2,[6]],6],[[9,[6]]]],[[[2,[4]],4],[[9,[4]]]],[[[2,[5]],5],[[9,[5]]]],[[[2,[1]],1],[[9,[1]]]],[[[2,[3]],3],[[9,[3]]]],[[[9,[3]]],3],[[[9,[4]]],4],[[[9,[6]]],6],[[[9,[1]]],1],[[[9,[5]]],5],[[]],[[]],[[]],0,[[]],[[]],[[]],[10,[[2,[4]]]],[10,[[2,[3]]]],[10,[[2,[6]]]],[10,[[2,[1]]]],[10,[[2,[5]]]],0,0,0,0,[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],12],[[],12],[[],12],[[[9,[6]]]],[[[9,[5]]]],[[[9,[1]]]],[[[9,[4]]]],[[[9,[3]]]],0,0],"p":[[15,"u128"],[3,"Crc"],[15,"u8"],[15,"u16"],[15,"u64"],[15,"u32"],[8,"Clone"],[8,"Width"],[3,"Digest"],[3,"Algorithm"],[4,"Result"],[3,"TypeId"]]},\ -"crc_catalog":{"doc":"","t":[3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,8,11,11,12,11,12,11,12,12,12,12,11,11,11,12,12],"n":["Algorithm","CRC_10_ATM","CRC_10_CDMA2000","CRC_10_GSM","CRC_11_FLEXRAY","CRC_11_UMTS","CRC_12_CDMA2000","CRC_12_DECT","CRC_12_GSM","CRC_12_UMTS","CRC_13_BBC","CRC_14_DARC","CRC_14_GSM","CRC_15_CAN","CRC_15_MPT1327","CRC_16_ARC","CRC_16_CDMA2000","CRC_16_CMS","CRC_16_DDS_110","CRC_16_DECT_R","CRC_16_DECT_X","CRC_16_DNP","CRC_16_EN_13757","CRC_16_GENIBUS","CRC_16_GSM","CRC_16_IBM_3740","CRC_16_IBM_SDLC","CRC_16_ISO_IEC_14443_3_A","CRC_16_KERMIT","CRC_16_LJ1200","CRC_16_MAXIM_DOW","CRC_16_MCRF4XX","CRC_16_MODBUS","CRC_16_NRSC_5","CRC_16_OPENSAFETY_A","CRC_16_OPENSAFETY_B","CRC_16_PROFIBUS","CRC_16_RIELLO","CRC_16_SPI_FUJITSU","CRC_16_T10_DIF","CRC_16_TELEDISK","CRC_16_TMS37157","CRC_16_UMTS","CRC_16_USB","CRC_16_XMODEM","CRC_17_CAN_FD","CRC_21_CAN_FD","CRC_24_BLE","CRC_24_FLEXRAY_A","CRC_24_FLEXRAY_B","CRC_24_INTERLAKEN","CRC_24_LTE_A","CRC_24_LTE_B","CRC_24_OPENPGP","CRC_24_OS_9","CRC_30_CDMA","CRC_31_PHILIPS","CRC_32_AIXM","CRC_32_AUTOSAR","CRC_32_BASE91_D","CRC_32_BZIP2","CRC_32_CD_ROM_EDC","CRC_32_CKSUM","CRC_32_ISCSI","CRC_32_ISO_HDLC","CRC_32_JAMCRC","CRC_32_MEF","CRC_32_MPEG_2","CRC_32_XFER","CRC_3_GSM","CRC_3_ROHC","CRC_40_GSM","CRC_4_G_704","CRC_4_INTERLAKEN","CRC_5_EPC_C1G2","CRC_5_G_704","CRC_5_USB","CRC_64_ECMA_182","CRC_64_GO_ISO","CRC_64_MS","CRC_64_WE","CRC_64_XZ","CRC_6_CDMA2000_A","CRC_6_CDMA2000_B","CRC_6_DARC","CRC_6_GSM","CRC_6_G_704","CRC_7_MMC","CRC_7_ROHC","CRC_7_UMTS","CRC_82_DARC","CRC_8_AUTOSAR","CRC_8_BLUETOOTH","CRC_8_CDMA2000","CRC_8_DARC","CRC_8_DVB_S2","CRC_8_GSM_A","CRC_8_GSM_B","CRC_8_HITAG","CRC_8_I_432_1","CRC_8_I_CODE","CRC_8_LTE","CRC_8_MAXIM_DOW","CRC_8_MIFARE_MAD","CRC_8_NRSC_5","CRC_8_OPENSAFETY","CRC_8_ROHC","CRC_8_SAE_J1850","CRC_8_SMBUS","CRC_8_TECH_3250","CRC_8_WCDMA","Width","borrow","borrow_mut","check","from","init","into","poly","refin","refout","residue","try_from","try_into","type_id","width","xorout"],"q":["crc_catalog","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["This struct describes a CRC algorithm using the fields …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The contents of the register after initialising, reading …","Returns the argument unchanged.","The settings of the bit cells at the start of each …","Calls U::from(self).","The generator polynomial that sets the feedback tap …","If equal to false, specifies that the characters of the …","If equal to false, specifies that the contents of the …","The contents of the register after initialising, reading …","","","","The number of bit cells in the linear feedback shift …","The XOR value applied to the contents of the register …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,[[]],0,[[]],0,0,0,0,[[],1],[[],1],[[],2],0,0],"p":[[4,"Result"],[3,"TypeId"],[3,"Algorithm"]]},\ -"crossbeam_channel":{"doc":"Multi-producer multi-consumer channels for message passing.","t":[12,13,13,13,13,13,13,3,3,3,3,3,4,3,3,3,3,4,3,13,13,3,3,4,3,4,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12],"n":["0","Disconnected","Disconnected","Disconnected","Disconnected","Empty","Full","IntoIter","Iter","ReadyTimeoutError","Receiver","RecvError","RecvTimeoutError","Select","SelectTimeoutError","SelectedOperation","SendError","SendTimeoutError","Sender","Timeout","Timeout","TryIter","TryReadyError","TryRecvError","TrySelectError","TrySendError","after","at","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bounded","capacity","capacity","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","drop","drop","drop","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","is_disconnected","is_disconnected","is_disconnected","is_disconnected","is_empty","is_empty","is_empty","is_full","is_full","is_full","is_timeout","is_timeout","iter","len","len","never","new","next","next","next","provide","provide","provide","provide","provide","provide","provide","provide","ready","ready_deadline","ready_timeout","recv","recv","recv","recv_deadline","recv_timeout","remove","same_channel","same_channel","select","select","select_deadline","select_timeout","send","send","send","send_deadline","send_timeout","tick","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_iter","try_ready","try_recv","try_select","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","0","0","0","0"],"q":["crossbeam_channel","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_channel::SendTimeoutError","","crossbeam_channel::TrySendError",""],"d":["","The message could not be sent because the channel is …","The message could not be sent because the channel is …","The message could not be received because the channel is …","The message could not be received because the channel is …","A message could not be received because the channel is …","The message could not be sent because the channel is full.","A blocking iterator over messages in a channel.","A blocking iterator over messages in a channel.","An error returned from the ready_timeout method.","The receiving side of a channel.","An error returned from the recv method.","An error returned from the recv_timeout method.","Selects from a set of channel operations.","An error returned from the select_timeout method.","A selected operation that needs to be completed.","An error returned from the send method.","An error returned from the send_timeout method.","The sending side of a channel.","The message could not be sent because the channel is full …","A message could not be received because the channel is …","A non-blocking iterator over messages in a channel.","An error returned from the try_ready method.","An error returned from the try_recv method.","An error returned from the try_select method.","An error returned from the try_send method.","Creates a receiver that delivers a message after a certain …","Creates a receiver that delivers a message at a certain …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a channel of bounded capacity.","If the channel is bounded, returns its capacity.","If the channel is bounded, returns its capacity.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the index of the selected operation.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Unwraps the message.","Unwraps the message.","Unwraps the message.","","","","","","Returns true if the send operation failed because the …","Returns true if the send operation failed because the …","Returns true if the receive operation failed because the …","Returns true if the receive operation failed because the …","Returns true if the channel is empty.","Returns true if the channel is empty.","Returns true if the receive operation failed because the …","Returns true if the channel is full.","Returns true if the channel is full.","Returns true if the send operation failed because the …","Returns true if the send operation timed out.","Returns true if the receive operation timed out.","A blocking iterator over messages in the channel.","Returns the number of messages in the channel.","Returns the number of messages in the channel.","Creates a receiver that never delivers messages.","Creates an empty list of channel operations for selection.","","","","","","","","","","","","Blocks until one of the operations becomes ready.","Blocks until a given deadline, or until one of the …","Blocks for a limited time until one of the operations …","Blocks the current thread until a message is received or …","Adds a receive operation.","Completes the receive operation.","Waits for a message to be received from the channel, but …","Waits for a message to be received from the channel, but …","Removes a previously added operation.","Returns true if senders belong to the same channel.","Returns true if receivers belong to the same channel.","Blocks until one of the operations becomes ready and …","Selects from a set of channel operations.","Blocks until a given deadline, or until one of the …","Blocks for a limited time until one of the operations …","Blocks the current thread until a message is sent or the …","Adds a send operation.","Completes the send operation.","Waits for a message to be sent into the channel, but only …","Waits for a message to be sent into the channel, but only …","Creates a receiver that delivers messages periodically.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A non-blocking iterator over messages in the channel.","Attempts to find a ready operation without blocking.","Attempts to receive a message from the channel without …","Attempts to select one of the operations without blocking.","Attempts to send a message into the channel without …","","","","","","","","","","","","","","","","","","Creates a channel of unbounded capacity.","","","",""],"i":[9,10,11,13,14,13,10,0,0,0,0,0,0,0,0,0,0,0,0,11,14,0,0,0,0,0,0,0,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,0,5,3,5,3,7,9,10,11,12,13,14,15,16,17,18,5,3,7,9,10,11,12,13,14,15,16,17,18,7,5,3,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,18,5,3,24,25,26,7,19,9,10,10,11,11,12,13,13,14,14,15,16,17,18,19,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,9,10,11,3,3,24,25,26,10,11,13,14,5,3,13,5,3,10,11,14,3,5,3,0,7,24,25,26,9,10,11,12,13,14,15,16,7,7,7,3,7,19,3,3,7,5,3,7,0,7,7,5,7,19,5,5,0,5,3,7,9,10,11,12,13,14,15,16,17,18,9,10,11,12,13,14,15,16,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,3,7,3,7,5,5,3,24,25,26,7,19,9,10,11,12,13,14,15,16,17,18,0,31,32,33,34],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,[[3,[2]]]],[2,[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4],[5,[[6,[4]]]],[3,[[6,[4]]]],[5,5],[3,3],[7,7],[[[9,[8]]],[[9,[8]]]],[[[10,[8]]],[[10,[8]]]],[[[11,[8]]],[[11,[8]]]],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],7],[5],[3],[19],[[[9,[20]],9],21],[[[10,[20]],10],21],[[[11,[20]],11],21],[[12,12],21],[[13,13],21],[[14,14],21],[[15,15],21],[[16,16],21],[[17,17],21],[[18,18],21],[[5,22],23],[[3,22],23],[[24,22],23],[[25,22],23],[[26,22],23],[[7,22],23],[[19,22],23],[[9,22],23],[[9,22],23],[[10,22],23],[[10,22],23],[[11,22],23],[[11,22],23],[[12,22],23],[[12,22],23],[[13,22],23],[[13,22],23],[[14,22],23],[[14,22],23],[[15,22],23],[[15,22],23],[[16,22],23],[[16,22],23],[[17,22],23],[[18,22],23],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9,10],[[]],[9,11],[[]],[[]],[[]],[12,13],[[]],[12,14],[[]],[[]],[[]],[[]],[19,4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9],[10],[11],[3],[3],[[]],[[]],[[]],[10,21],[11,21],[13,21],[14,21],[5,21],[3,21],[13,21],[5,21],[3,21],[10,21],[11,21],[14,21],[3,24],[5,4],[3,4],[[],3],[[],7],[24,6],[25,6],[26,6],[27],[27],[27],[27],[27],[27],[27],[27],[7,4],[[7,2],[[28,[4,18]]]],[[7,1],[[28,[4,18]]]],[3,[[28,[12]]]],[[7,3],4],[[19,3],[[28,[12]]]],[[3,2],[[28,[14]]]],[[3,1],[[28,[14]]]],[[7,4]],[[5,5],21],[[3,3],21],[7,19],0,[[7,2],[[28,[19,16]]]],[[7,1],[[28,[19,16]]]],[5,[[28,[9]]]],[[7,5],4],[[19,5],[[28,[9]]]],[[5,2],[[28,[11]]]],[[5,1],[[28,[11]]]],[1,[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[3,25],[7,[[28,[4,17]]]],[3,[[28,[13]]]],[7,[[28,[19,15]]]],[5,[[28,[10]]]],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[]],0,0,0,0],"p":[[3,"Duration"],[3,"Instant"],[3,"Receiver"],[15,"usize"],[3,"Sender"],[4,"Option"],[3,"Select"],[8,"Clone"],[3,"SendError"],[4,"TrySendError"],[4,"SendTimeoutError"],[3,"RecvError"],[4,"TryRecvError"],[4,"RecvTimeoutError"],[3,"TrySelectError"],[3,"SelectTimeoutError"],[3,"TryReadyError"],[3,"ReadyTimeoutError"],[3,"SelectedOperation"],[8,"PartialEq"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Iter"],[3,"TryIter"],[3,"IntoIter"],[3,"Demand"],[4,"Result"],[3,"String"],[3,"TypeId"],[13,"Timeout"],[13,"Disconnected"],[13,"Full"],[13,"Disconnected"]]},\ -"crossbeam_utils":{"doc":"Miscellaneous tools for concurrent programming.","t":[3,3,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,0,11,11,11,11,11,11,11,3,8,16,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Backoff","CachePadded","atomic","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","from","from","from","from","hash","into","into","into_inner","is_completed","new","new","reset","snooze","spin","sync","thread","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","AtomicCell","AtomicConsume","Val","as_ptr","borrow","borrow_mut","compare_and_swap","compare_exchange","default","drop","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_add","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_and","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_max","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_min","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_nand","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_or","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_sub","fetch_update","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fetch_xor","fmt","from","from","from","into","into_inner","is_lock_free","load","load_consume","new","store","swap","take","try_from","try_into","type_id","Parker","ShardedLock","ShardedLockReadGuard","ShardedLockWriteGuard","Unparker","WaitGroup","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","default","default","default","deref","deref","deref_mut","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from_raw","from_raw","get_mut","into","into","into","into","into","into","into_inner","into_raw","into_raw","is_poisoned","new","new","new","park","park_deadline","park_timeout","read","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_read","try_write","type_id","type_id","type_id","type_id","type_id","type_id","unpark","unparker","wait","write","Scope","ScopedJoinHandle","ScopedThreadBuilder","as_pthread_t","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","builder","fmt","fmt","fmt","from","from","from","into","into","into","into_pthread_t","join","name","scope","spawn","spawn","stack_size","thread","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id"],"q":["crossbeam_utils","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::atomic","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","crossbeam_utils::thread","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Performs exponential backoff in spin loops.","Pads and aligns a value to the length of a cache line.","Atomic types.","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Returns the inner value.","Returns true if exponential backoff has completed and …","Creates a new Backoff.","Pads and aligns a value to the length of a cache line.","Resets the Backoff.","Backs off in a blocking loop.","Backs off in a lock-free loop.","Thread synchronization primitives.","Threads that can borrow variables from the stack.","","","","","","","","A thread-safe mutable memory location.","Trait which allows reading from primitive atomic types …","Type returned by load_consume.","Returns a raw pointer to the underlying data in this …","","","If the current value equals current, stores new into the …","If the current value equals current, stores new into the …","","","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Increments the current value by val and returns the …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies logical “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Applies bitwise “and” to the current value and returns …","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the maximum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Compares and sets the minimum of the current value and val,","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies logical “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies bitwise “nand” to the current value and …","Applies logical “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Applies bitwise “or” to the current value and returns …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Decrements the current value by val and returns the …","Fetches the value, and applies a function to it that …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies logical “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","Applies bitwise “xor” to the current value and returns …","","","Returns the argument unchanged.","","Calls U::from(self).","Consumes the atomic and returns the contained value.","Returns true if operations on values of this type are …","Loads a value from the atomic cell.","Loads a value from the atomic using a “consume” memory …","Creates a new atomic cell initialized with val.","Stores val into the atomic cell.","Stores val into the atomic cell and returns the previous …","Takes the value of the atomic cell, leaving …","","","","A thread parking primitive.","A sharded reader-writer lock.","A guard used to release the shared read access of a …","A guard used to release the exclusive write access of a …","Unparks a thread parked by the associated Parker.","Enables threads to synchronize the beginning or end of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts a raw pointer into a Parker.","Converts a raw pointer into an Unparker.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this lock, returning the underlying data.","Converts a Parker into a raw pointer.","Converts an Unparker into a raw pointer.","Returns true if the lock is poisoned.","Creates a new Parker.","Creates a new sharded reader-writer lock.","Creates a new wait group and returns the single reference …","Blocks the current thread until the token is made …","Blocks the current thread until the token is made …","Blocks the current thread until the token is made …","Locks with shared read access, blocking the current thread …","","","","","","","","","","","","","","","","","Attempts to acquire this lock with shared read access.","Attempts to acquire this lock with exclusive write access.","","","","","","","Atomically makes the token available if it is not already.","Returns a reference to an associated Unparker.","Drops this reference and waits until all other references …","Locks with exclusive write access, blocking the current …","A scope for spawning threads.","A handle that can be used to join its scoped thread.","Configures the properties of a new thread.","","","","","","","","Creates a builder that can configure a thread before …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Waits for the thread to finish and returns its result.","Sets the name for the new thread.","Creates a new scope for spawning threads.","Spawns a scoped thread.","Spawns a scoped thread with this configuration.","Sets the size of the stack for the new thread.","Returns a handle to the underlying thread.","","","","","","","","",""],"i":[0,0,0,3,2,3,2,2,2,3,2,2,2,2,3,2,3,2,2,2,2,3,2,2,3,3,2,3,3,3,0,0,2,3,2,3,2,3,2,0,0,49,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,49,14,14,14,14,14,14,14,0,0,0,0,0,0,31,29,32,34,35,30,31,29,32,34,35,30,29,30,29,30,31,32,30,34,35,35,35,30,31,29,32,34,34,35,35,30,31,29,32,32,32,34,35,30,31,29,32,31,29,32,34,35,30,32,31,29,32,31,32,30,31,31,31,32,29,30,34,35,31,29,32,34,35,30,31,29,32,34,35,30,32,32,31,29,32,34,35,30,29,31,30,32,0,0,0,42,44,42,45,44,42,45,44,44,42,45,44,42,45,44,42,45,42,42,45,0,44,45,45,42,44,42,45,44,42,45,44,42,45],"f":[0,0,0,[[]],[[]],[[]],[[]],[[[2,[1]]],[[2,[1]]]],[[]],[[],3],[[],[[2,[4]]]],[2],[2],[[[2,[5]],2],6],[[3,7],8],[[[2,[9]],7],8],[[]],[[],2],[[]],[10],[[[2,[11]]]],[[]],[[]],[2],[3,6],[[],3],[[],2],[3],[3],[3],0,0,[[]],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],0,0,0,[14],[[]],[[]],[[[14,[[0,[15,16]]]],[0,[15,16]],[0,[15,16]]],[[0,[15,16]]]],[[[14,[[0,[15,16]]]],[0,[15,16]],[0,[15,16]]],[[12,[[0,[15,16]],[0,[15,16]]]]]],[[],[[14,[4]]]],[14],[[[14,[17]],17],17],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[24]],24],24],[[[14,[25]],25],25],[[[14,[26]],26],26],[[[14,[27]],27],27],[[[14,[28]],28],28],[[[14,[27]],27],27],[[[14,[25]],25],25],[[[14,[26]],26],26],[[[14,[28]],28],28],[[[14,[18]],18],18],[[[14,[6]],6],6],[[[14,[19]],19],19],[[[14,[17]],17],17],[[[14,[22]],22],22],[[[14,[24]],24],24],[[[14,[23]],23],23],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[21]],21],21],[[[14,[26]],26],26],[[[14,[23]],23],23],[[[14,[20]],20],20],[[[14,[25]],25],25],[[[14,[28]],28],28],[[[14,[17]],17],17],[[[14,[27]],27],27],[[[14,[22]],22],22],[[[14,[20]],20],20],[[[14,[21]],21],21],[[[14,[18]],18],18],[[[14,[26]],26],26],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[23]],23],23],[[[14,[17]],17],17],[[[14,[28]],28],28],[[[14,[22]],22],22],[[[14,[25]],25],25],[[[14,[27]],27],27],[[[14,[27]],27],27],[[[14,[28]],28],28],[[[14,[25]],25],25],[[[14,[20]],20],20],[[[14,[18]],18],18],[[[14,[6]],6],6],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[26]],26],26],[[[14,[21]],21],21],[[[14,[17]],17],17],[[[14,[6]],6],6],[[[14,[20]],20],20],[[[14,[18]],18],18],[[[14,[24]],24],24],[[[14,[21]],21],21],[[[14,[23]],23],23],[[[14,[27]],27],27],[[[14,[26]],26],26],[[[14,[22]],22],22],[[[14,[28]],28],28],[[[14,[17]],17],17],[[[14,[19]],19],19],[[[14,[25]],25],25],[[[14,[27]],27],27],[[[14,[25]],25],25],[[[14,[22]],22],22],[[[14,[23]],23],23],[[[14,[19]],19],19],[[[14,[21]],21],21],[[[14,[26]],26],26],[[[14,[20]],20],20],[[[14,[28]],28],28],[[[14,[18]],18],18],[[[14,[17]],17],17],[[[14,[24]],24],24],[[[14,[[0,[15,16]]]]],[[12,[[0,[15,16]],[0,[15,16]]]]]],[[[14,[21]],21],21],[[[14,[28]],28],28],[[[14,[26]],26],26],[[[14,[25]],25],25],[[[14,[6]],6],6],[[[14,[22]],22],22],[[[14,[18]],18],18],[[[14,[19]],19],19],[[[14,[24]],24],24],[[[14,[20]],20],20],[[[14,[27]],27],27],[[[14,[23]],23],23],[[[14,[17]],17],17],[[[14,[[0,[15,9]]]],7],8],[10],[[]],[[],14],[[]],[14],[[],6],[[[14,[15]]],15],[[]],[[],14],[14],[14],[[[14,[4]]],4],[[],12],[[],12],[[],13],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,29],[30,30],[[]],[[]],[[],31],[[],[[32,[4]]]],[[],30],[[[34,[33]]]],[[[35,[33]]]],[[[35,[33]]]],[[[35,[33]]]],[30],[[31,7],8],[[29,7],8],[[[32,[[0,[33,9]]]],7],8],[[[34,[[0,[33,36]]]],7],8],[[[34,[9]],7],8],[[[35,[[0,[33,36]]]],7],8],[[[35,[9]],7],8],[[30,7],8],[[]],[[]],[[],32],[[]],[10],[[]],[[]],[[]],[[],31],[[],29],[[[32,[33]]],37],[[]],[[]],[[]],[[]],[[]],[[]],[32,37],[31],[29],[[[32,[33]]],6],[[],31],[[],32],[[],30],[31],[[31,38]],[[31,39]],[[[32,[33]]],[[37,[[34,[33]]]]]],[[]],[[]],[[],40],[[],40],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[[32,[33]]],[[41,[[34,[33]]]]]],[[[32,[33]]],[[41,[[35,[33]]]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[29],[31,29],[30],[[[32,[33]]],[[37,[[35,[33]]]]]],0,0,0,[42,43],[[]],[[]],[[]],[[]],[[]],[[]],[44,45],[[44,7],8],[[42,7],8],[[45,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[42,43],[42,46],[[45,40],45],[[],46],[44,42],[45,[[47,[42]]]],[[45,22],45],[42,48],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],[[],13]],"p":[[8,"Clone"],[3,"CachePadded"],[3,"Backoff"],[8,"Default"],[8,"PartialEq"],[15,"bool"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[15,"never"],[8,"Hash"],[4,"Result"],[3,"TypeId"],[3,"AtomicCell"],[8,"Copy"],[8,"Eq"],[15,"isize"],[15,"u128"],[15,"i128"],[15,"i64"],[15,"u64"],[15,"usize"],[15,"i32"],[15,"u16"],[15,"i8"],[15,"u32"],[15,"u8"],[15,"i16"],[3,"Unparker"],[3,"WaitGroup"],[3,"Parker"],[3,"ShardedLock"],[8,"Sized"],[3,"ShardedLockReadGuard"],[3,"ShardedLockWriteGuard"],[8,"Display"],[6,"LockResult"],[3,"Instant"],[3,"Duration"],[3,"String"],[6,"TryLockResult"],[3,"ScopedJoinHandle"],[6,"RawPthread"],[3,"Scope"],[3,"ScopedThreadBuilder"],[6,"Result"],[6,"Result"],[3,"Thread"],[8,"AtomicConsume"]]},\ -"crunchy":{"doc":"The crunchy unroller - deterministically unroll constant …","t":[14],"n":["unroll"],"q":["crunchy"],"d":["Unroll the given for loop"],"i":[0],"f":[0],"p":[]},\ -"crypto_common":{"doc":"Common cryptographic traits.","t":[8,6,16,8,16,8,8,8,3,6,16,8,6,8,8,16,8,6,16,8,6,16,8,8,11,11,11,11,11,11,11,11,11,2,10,10,11,11,11,11,10,10,11,11,11,11,10,11,11,11,11,11,2,10],"n":["AlgorithmName","Block","BlockSize","BlockSizeUser","Inner","InnerInit","InnerIvInit","InnerUser","InvalidLength","Iv","IvSize","IvSizeUser","Key","KeyInit","KeyIvInit","KeySize","KeySizeUser","Output","OutputSize","OutputSizeUser","ParBlocks","ParBlocksSize","ParBlocksSizeUser","Reset","block_size","borrow","borrow_mut","clone","clone_into","eq","fmt","fmt","from","generic_array","inner_init","inner_iv_init","inner_iv_slice_init","into","iv_size","key_size","new","new","new_from_slice","new_from_slices","output_size","provide","reset","to_owned","to_string","try_from","try_into","type_id","typenum","write_alg_name"],"q":["crypto_common","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Trait which stores algorithm name constant, used in Debug …","Block on which BlockSizeUser implementors operate.","Size of the block in bytes.","Types which process data in blocks.","Inner type.","Types which can be initialized from another type (usually …","Types which can be initialized from another type and …","Types which use another type for initialization.","The error type returned when key and/or IV used in the …","Initialization vector (nonce) used by IvSizeUser …","Initialization vector size in bytes.","Types which use initialization vector (nonce) for …","Key used by KeySizeUser implementors.","Types which can be initialized from key.","Types which can be initialized from key and initialization …","Key size in bytes.","Types which use key for initialization.","Output array of OutputSizeUser implementors.","Size of the output in bytes.","Types which return data with the given size.","Parallel blocks on which ParBlocksSizeUser implementors …","Number of blocks which can be processed in parallel.","Types which can process blocks in parallel.","Resettable types.","Return block size in bytes.","","","","","","","","Returns the argument unchanged.","","Initialize value from the inner.","Initialize value using inner and iv array.","Initialize value using inner and iv slice.","Calls U::from(self).","Return IV size in bytes.","Return key size in bytes.","Create new value from fixed size key.","Create new value from fixed length key and nonce.","Create new value from variable size key.","Create new value from variable length key and nonce.","Return output size in bytes.","","Reset state to its initial value.","","","","","","","Write algorithm name into f."],"i":[0,0,13,0,14,0,0,0,0,0,15,0,0,0,0,16,0,0,17,0,0,18,0,0,13,2,2,2,2,2,2,2,2,0,19,20,20,2,15,16,21,22,21,22,17,2,23,2,2,2,2,2,0,24],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[]],[[]],[2,2],[[]],[[2,2],3],[[2,4],[[6,[5]]]],[[2,4],7],[[]],0,[[]],[8],[[],[[6,[2]]]],[[]],[[],1],[[],1],[9],[[9,8]],[[],[[6,[2]]]],[[],[[6,[2]]]],[[],1],[10],[[]],[[]],[[],11],[[],6],[[],6],[[],12],0,[4,7]],"p":[[15,"usize"],[3,"InvalidLength"],[15,"bool"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[6,"Iv"],[6,"Key"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"BlockSizeUser"],[8,"InnerUser"],[8,"IvSizeUser"],[8,"KeySizeUser"],[8,"OutputSizeUser"],[8,"ParBlocksSizeUser"],[8,"InnerInit"],[8,"InnerIvInit"],[8,"KeyInit"],[8,"KeyIvInit"],[8,"Reset"],[8,"AlgorithmName"]]},\ -"digest":{"doc":"This crate provides traits which describe functionality of …","t":[8,8,8,8,8,8,8,3,3,18,6,16,8,16,8,8,8,8,8,2,11,11,11,11,10,11,10,11,11,11,11,0,0,2,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,11,11,10,10,10,11,10,11,11,11,11,11,11,11,2,14,11,11,10,10,10,10,10,10,11,11,11,10,11,10,10,10,11,11,11,11,11,11,11,11,11,11,2,10,10,10,3,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,6,16,8,6,16,8,16,8,3,3,8,8,13,16,8,16,8,13,3,18,4,8,8,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11],"n":["Digest","DynDigest","ExtendableOutput","ExtendableOutputReset","FixedOutput","FixedOutputReset","HashMarker","InvalidBufferSize","InvalidOutputSize","MAX_OUTPUT_SIZE","Output","OutputSize","OutputSizeUser","Reader","Reset","Update","VariableOutput","VariableOutputReset","XofReader","block_buffer","borrow","borrow","borrow_mut","borrow_mut","box_clone","chain","chain_update","clone","clone","clone_into","clone_into","consts","core_api","crypto_common","default","default","digest","digest_variable","digest_xof","eq","finalize","finalize","finalize","finalize_boxed","finalize_boxed","finalize_boxed_reset","finalize_boxed_reset","finalize_fixed","finalize_fixed_reset","finalize_into","finalize_into","finalize_into","finalize_into_reset","finalize_into_reset","finalize_into_reset","finalize_reset","finalize_reset","finalize_reset","finalize_variable","finalize_variable_reset","finalize_xof","finalize_xof_into","finalize_xof_reset","finalize_xof_reset_into","fmt","fmt","fmt","fmt","from","from","generic_array","impl_oid_carrier","into","into","new","new","new_with_prefix","output_size","output_size","output_size","output_size","provide","provide","read","read_boxed","reset","reset","reset","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","typenum","update","update","update","B0","B1","False","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","Z0","add","bitand","bitand","bitand","bitor","bitor","bitor","bitxor","bitxor","bitxor","bitxor","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","cmp","default","default","default","div","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","into","into","into","max","max","max","max","max","max","max","min","min","min","min","min","min","min","mul","mul","mul","neg","new","new","new","new","new","not","not","partial_cmp","partial_cmp","partial_cmp","partial_div","powi","powi","powi","rem","sub","sub","sub","to_bool","to_bool","to_i16","to_i32","to_i64","to_i8","to_int","to_int","to_int","to_int","to_isize","to_owned","to_owned","to_owned","to_u8","to_u8","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","AlgorithmName","Block","BlockSize","BlockSizeUser","Buffer","BufferKind","BufferKindUser","Core","CoreProxy","CoreWrapper","CtVariableCoreWrapper","ExtendableOutputCore","FixedOutputCore","Left","OutputSize","OutputSizeUser","ReaderCore","Reset","Right","RtVariableCoreWrapper","TRUNC_SIDE","TruncSide","UpdateCore","VariableOutputCore","XofReaderCore","XofReaderCoreWrapper","block_size","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","decompose","default","default","default","finalize_fixed_core","finalize_fixed_core","finalize_into","finalize_into_reset","finalize_variable","finalize_variable_core","finalize_variable_reset","finalize_xof","finalize_xof_core","finalize_xof_reset","flush","flush","fmt","fmt","fmt","fmt","from","from","from","from","from","from_core","into","into","into","into","into","new","new","new","new_from_slice","output_size","output_size","read","read","read_block","reset","reset","reset","reset","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","update","update","update_blocks","update_blocks","write","write","write_alg_name","write_alg_name"],"q":["digest","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","digest::consts","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","digest::core_api","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Convenience wrapper trait covering functionality of …","Modification of the Digest trait suitable for trait …","Trait for hash functions with extendable-output (XOF).","Trait for hash functions with extendable-output (XOF) able …","Trait for hash functions with fixed-size output.","Trait for hash functions with fixed-size output able to …","Marker trait for cryptographic hash functions.","Buffer length is not equal to hash output size.","The error type used in variable hash traits.","Maximum size of output hash.","Output array of OutputSizeUser implementors.","Size of the output in bytes.","Types which return data with the given size.","Reader","Resettable types.","Types which consume data with byte granularity.","Trait for hash functions with variable-size output.","Trait for hash functions with variable-size output able to …","Trait for reader types which are used to extract …","","","","","","Clone hasher state into a boxed trait object","Digest input data in a chained manner.","Process input data in a chained manner.","","","","","Type aliases for many constants.","Low-level traits operating on blocks and wrappers around …","","","","Compute hash of data.","Compute hash of data and write it to output.","Compute hash of data and write it into output.","","Retrieve result and consume hasher instance.","Retrieve result and consume boxed hasher instance","Retrieve result and consume boxed hasher instance","Retrieve result into a boxed slice of the specified size …","Retrieve result into a boxed slice and consume hasher.","Retrieve result into a boxed slice of the specified size …","Retrieve result into a boxed slice and reset the hasher …","Retrieve result and consume the hasher instance.","Retrieve result and reset the hasher state.","Write result into provided array and consume the hasher …","Write result into provided array and consume the hasher …","Consume value and write result into provided array.","Write result into provided array and reset the hasher …","Write result into provided array and reset the hasher …","Write result into provided array and reset the hasher …","Retrieve result and reset hasher instance.","Retrieve result and reset hasher instance","Retrieve result and reset hasher instance","Write result into the output buffer.","Write result into the output buffer and reset the hasher …","Retrieve XOF reader and consume hasher instance.","Finalize XOF and write result into out.","Retrieve XOF reader and reset hasher instance state.","Finalize XOF, write result into out, and reset the hasher …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Implement dummy type with hidden docs which is used to “…","Calls U::from(self).","Calls U::from(self).","Create new hasher instance.","Create new hasher instance with the given output size.","Create new hasher instance which has processed the …","Get output size of the hasher","Get output size of the hasher","Get output size of the hasher instance provided to the new …","Return output size in bytes.","","","Read output into the buffer. Can be called an unlimited …","Read output into a boxed slice of the specified size.","Reset state to its initial value.","Reset hasher instance to its initial state.","Reset hasher instance to its initial state.","","","","","","","","","","","","Process data, updating the internal state.","Digest input data.","Update state using the provided data.","The type-level bit 0.","The type-level bit 1.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The type-level signed integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","Instantiates a singleton representing this bit.","Instantiates a singleton representing this bit.","","Instantiates a singleton representing the integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Trait which stores algorithm name constant, used in Debug …","Block on which BlockSizeUser implementors operate.","Size of the block in bytes.","Types which process data in blocks.","Buffer type used by type which implements BufferKindUser.","Block buffer kind over which type operates.","Types which use BlockBuffer functionality.","Type wrapped by CoreWrapper.","A proxy trait to a core type implemented by CoreWrapper","Wrapper around BufferKindUser.","Wrapper around VariableOutputCore which selects output size","Core trait for hash functions with extendable (XOF) output …","Core trait for hash functions with fixed output size.","Truncate left side, i.e. &out[..n].","Size of the output in bytes.","Types which return data with the given size.","XOF reader core state.","Resettable types.","Truncate right side, i.e. &out[m..].","Wrapper around VariableOutputCore which selects output size","Side which should be used in a truncated result.","Type which used for defining truncation side in the …","Types which consume data in blocks.","Core trait for hash functions with variable output size.","Core reader trait for extendable-output function (XOF) …","Wrapper around XofReaderCore implementations.","Return block size in bytes.","","","","","","","","","","","","","","","","","","","","","Decompose wrapper into inner parts.","","","","Finalize state using remaining data stored in the provided …","","","","","Finalize hasher and write full hashing result into the out …","","","Retrieve XOF reader using remaining data stored in the …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create new wrapper from core.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Initialize hasher state for given output size.","","","","Return output size in bytes.","","","","Read next XOF block.","Reset state to its initial value.","","","","","","","","","","","","","","","","","","","","","","","","","","Update state using the provided data blocks.","","","","Write algorithm name into f.",""],"i":[0,0,0,0,0,0,0,0,0,44,0,45,0,46,0,0,0,0,0,0,4,5,4,5,1,47,48,4,5,4,5,0,0,0,4,5,48,44,46,5,48,1,1,46,44,49,50,51,52,48,1,51,48,1,52,48,1,1,44,50,46,46,49,49,4,4,5,5,4,5,0,0,4,5,48,44,48,48,1,44,45,4,5,53,53,54,48,1,4,5,4,5,4,5,4,5,4,5,0,48,1,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,16,17,17,16,16,17,16,16,17,17,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,15,16,17,15,16,17,15,16,17,15,16,17,15,16,17,15,16,16,17,17,15,15,15,16,16,17,17,15,15,15,15,15,15,15,16,16,17,17,15,16,17,16,17,15,15,15,15,15,15,15,15,15,16,17,15,15,15,15,15,15,15,15,15,16,17,15,16,17,16,17,15,16,17,15,16,17,15,0,0,55,0,0,56,0,57,0,0,0,0,0,36,45,0,58,0,36,0,59,0,0,0,0,0,55,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,34,32,34,35,60,32,34,34,33,59,33,34,58,34,33,34,33,34,35,36,32,33,34,35,36,34,32,33,34,35,36,59,33,34,34,45,33,35,35,61,54,32,33,34,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,32,33,34,35,36,33,34,62,32,33,34,63,32],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[],[[2,[1]]]],[3],[3],[4,4],[5,5],[[]],[[]],0,0,0,[[],4],[[],5],[3,6],[3,[[7,[4]]]],[3],[[5,5],8],[[],6],[2,2],[2,2],[9,2],[[],2],[9,2],[[],2],[[],6],[[],6],[6],[[],[[7,[5]]]],[6],[6],[[],[[7,[5]]]],[6],[[],6],[[],2],[[],2],[[],[[7,[5]]]],[[],[[7,[5]]]],[[]],[[]],[[]],[[]],[[4,10],11],[[4,10],11],[[5,10],11],[[5,10],11],[[]],[[]],0,0,[[]],[[]],[[]],[9,[[7,[4]]]],[3],[[],9],[[],9],[[],9],[[],9],[12],[12],[[]],[9,2],[[]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],0,[3],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[15],[16],[[17,16]],[[17,17]],[[16,17]],[[16,16]],[17],[[16,17]],[[16,16]],[[17,17]],[[17,16]],[[]],[[]],[[]],[[]],[[]],[[]],[16,16],[17,17],[15,15],[[]],[[]],[[]],[[16,16],18],[[17,17],18],[[15,15],18],[[],16],[[],17],[[],15],[15],[[16,16],8],[[17,17],8],[[15,15],8],[[16,10],[[7,[19]]]],[[17,10],[[7,[19]]]],[[15,10],[[7,[19]]]],[[]],[[]],[[]],[16],[17],[15],[[]],[[]],[[]],[[16,17],17],[[16,16],16],[[17,16],17],[[17,17],17],[[15,20]],[[15,21]],[[15,15]],[[16,17],16],[[16,16],16],[[17,16],16],[[17,17],17],[[15,21]],[[15,15]],[[15,20]],[[15,22]],[[15,23]],[15],[15],[[],16],[[],16],[[],17],[[],17],[[],15],[16],[17],[[16,16],[[24,[18]]]],[[17,17],[[24,[18]]]],[[15,15],[[24,[18]]]],[[]],[[15,15]],[[15,21]],[[15,20]],[15],[[15,20]],[[15,21]],[[15,15]],[[],8],[[],8],[[],25],[[],26],[[],27],[[],28],[[],26],[[],27],[[],28],[[],25],[[],29],[[]],[[]],[[]],[[],30],[[],30],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],[[],14],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],9],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[32,[31,31,31]]],[[32,[31,31,31]]]],[[[33,[31]]],[[33,[31]]]],[[[34,[31]]],[[34,[31]]]],[[[35,[31]]],[[35,[31]]]],[36,36],[[]],[[]],[[]],[[]],[[]],[34],[[],32],[[],[[34,[37]]]],[[],[[35,[37]]]],[[38,6]],[[32,38,39]],[[34,6]],[[34,6]],[33,[[7,[5]]]],[[38,6]],[33,[[7,[5]]]],[34],[38],[34],[33,40],[34,40],[[33,10],[[7,[19]]]],[[34,10],[[7,[19]]]],[[35,10],[[7,[19]]]],[[36,10],11],[[]],[[]],[[]],[[]],[[]],[[],34],[[]],[[]],[[]],[[]],[[]],[9,[[7,[4]]]],[9,[[7,[33,4]]]],[41,34],[[],[[7,[34,42]]]],[[],9],[33,9],[35,[[40,[9]]]],[35],[[],43],[[]],[32],[33],[34],[[]],[[]],[[]],[[]],[[]],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],14],[[],14],[[],14],[[],14],[[],14],[33],[34],[[]],[32],[33,[[40,[9]]]],[34,[[40,[9]]]],[10,[[7,[19]]]],[10,11]],"p":[[8,"DynDigest"],[3,"Box"],[8,"AsRef"],[3,"InvalidOutputSize"],[3,"InvalidBufferSize"],[6,"Output"],[4,"Result"],[15,"bool"],[15,"usize"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Z0"],[3,"B0"],[3,"B1"],[4,"Ordering"],[3,"Error"],[3,"PInt"],[3,"NInt"],[3,"TArr"],[3,"ATerm"],[4,"Option"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"u8"],[8,"Clone"],[3,"CtVariableCoreWrapper"],[3,"RtVariableCoreWrapper"],[3,"CoreWrapper"],[3,"XofReaderCoreWrapper"],[4,"TruncSide"],[8,"Default"],[6,"Buffer"],[3,"GenericArray"],[6,"Result"],[6,"Key"],[3,"InvalidLength"],[6,"Block"],[8,"VariableOutput"],[8,"OutputSizeUser"],[8,"ExtendableOutput"],[8,"Update"],[8,"Digest"],[8,"ExtendableOutputReset"],[8,"VariableOutputReset"],[8,"FixedOutput"],[8,"FixedOutputReset"],[8,"XofReader"],[8,"Reset"],[8,"BlockSizeUser"],[8,"BufferKindUser"],[8,"CoreProxy"],[8,"ExtendableOutputCore"],[8,"VariableOutputCore"],[8,"FixedOutputCore"],[8,"XofReaderCore"],[8,"UpdateCore"],[8,"AlgorithmName"]]},\ -"enum_as_inner":{"doc":"enum-as-inner","t":[24],"n":["EnumAsInner"],"q":["enum_as_inner"],"d":["Derive functions on an Enum for easily accessing …"],"i":[0],"f":[0],"p":[]},\ -"firewood":{"doc":"Firewood: non-archival blockchain key-value store with …","t":[0,0,0,13,3,3,4,3,3,3,13,13,3,13,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,13,3,3,3,4,3,13,3,13,3,3,13,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,13,13,3,4,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["db","merkle","proof","Blob","DB","DBConfig","DBError","DBRev","DBRevConfig","DiskBufferConfig","InvalidParams","Merkle","Revision","System","WALConfig","WriteBatch","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","builder","builder","builder","builder","clone","clone","clone","clone_into","clone_into","clone_into","commit","create_account","delete_account","deref","drop","dump","dump","dump_account","dump_account","exist","exist","fmt","from","from","from","from","from","from","from","from","from","get_balance","get_balance","get_code","get_code","get_nonce","get_nonce","get_revision","get_state","get_state","into","into","into","into","into","into","into","into","into","kv_dump","kv_dump","kv_insert","kv_remove","kv_root_hash","kv_root_hash","new","new_writebatch","no_root_hash","root_hash","root_hash","set_balance","set_code","set_nonce","set_state","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","0","0","0","0","Format","Hash","IdTrans","Merkle","MerkleError","Node","NotBranchNode","PartialPath","ReadOnly","Ref","RefMut","Shale","ValueTransformer","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","decode","dehydrate","dehydrate","dehydrated_len","dehydrated_len","deref","deref","deref","dump","empty_root","eq","eq","eq","flush_dirty","fmt","fmt","from","from","from","from","from","from","from","from","from_nibbles","get","get","get_mut","get_store","hydrate","hydrate","init_root","insert","into","into","into","into","into","into","into","into","into_inner","new","prove","remove","remove_tree","root_hash","to_nibbles","to_owned","to_owned","to_owned","transform","transform","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","write","0","0","0","DecodeError","NoSuchNode","Proof","ProofError","ProofNodeMissing","SubProof","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","deserialize","fmt","from","from","from","into","into","into","serialize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","verify_proof"],"q":["firewood","","","firewood::db","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","firewood::db::DBError","","","firewood::merkle","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","firewood::merkle::MerkleError","","firewood::proof","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","Firewood database handle.","Database configuration.","","Some readable version of the DB.","Config for accessing a version of the DB.","Config for the disk buffer.","","","Lock protected handle to a readable version of the DB.","","","An atomic batch of changes made to the DB. Each operation …","","","","","","","","","","","","","","","","","","","Create a builder for building DBRevConfig. On the builder, …","Create a builder for building DBConfig. On the builder, …","Create a builder for building WALConfig. On the builder, …","Create a builder for building DiskBufferConfig. On the …","","","","","","","Persist all changes to the DB. The atomicity of the …","Create an account.","Delete an account.","","","Dump the MPT of the entire account model storage.","Dump the MPT of the latest entire account model storage.","Dump the MPT of the state storage under an account.","Dump the MPT of the latest state storage under an account.","Check if the account exists.","Check if the account exists in the latest world state.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get balance of the account.","Get the latest balance of the account.","Get code of the account.","Get the latest code of the account.","Get nonce of the account.","Get the latest nonce of the account.","Get a handle that grants the access to some historical …","Get the state value indexed by sub_key in the account …","Get the latest state value indexed by sub_key in the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Dump the MPT of the generic key-value storage.","Dump the MPT of the latest generic key-value storage.","Insert an item to the generic key-value storage.","Remove an item from the generic key-value storage. val …","Get root hash of the generic key-value storage.","Get root hash of the latest generic key-value storage.","Open a database.","Create a write batch.","Do not rehash merkle roots upon commit. This will leave …","Get root hash of the world state of all accounts.","Get root hash of the latest world state of all accounts.","Set balance of the account.","Set code of the account.","Set nonce of the account.","Set the state value indexed by sub_key in the account …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","PartialPath keeps a list of nibbles to represent a path on …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Constructs a merkle proof for key. The result contains all …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Hash -> RLP encoding map","","","SubProof contains the RLP encoding and the hash value of a …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","verify_proof checks merkle proofs. The given proof must …"],"i":[0,0,0,5,0,0,0,0,0,0,5,5,0,5,0,0,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,1,23,2,3,1,2,3,1,2,3,4,4,4,8,4,9,11,9,11,9,11,5,9,11,8,4,5,1,23,2,3,9,11,9,11,9,11,11,9,11,9,11,8,4,5,1,23,2,3,9,11,4,4,9,11,11,11,4,9,11,4,4,4,4,1,2,3,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,9,11,8,4,5,1,23,2,3,41,42,43,21,30,0,0,0,0,0,30,0,30,0,0,30,0,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,21,25,26,21,25,26,25,21,26,21,26,27,21,25,28,28,21,25,26,28,30,25,28,27,33,44,30,21,25,26,0,28,33,28,28,21,26,28,28,28,27,33,44,30,21,25,26,25,28,28,28,28,28,0,21,25,26,45,44,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,28,27,33,44,30,21,25,26,33,46,47,38,40,40,0,0,40,0,48,38,40,48,38,40,38,40,48,38,40,48,38,40,38,48,38,40,48,38,40,48,38,40,38],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[1,1],[2,2],[3,3],[[]],[[]],[[]],[4],[4,[[6,[4,5]]]],[[4,7],[[6,[4,5]]]],[8,9],[4],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[9,[[6,[12,5]]]],[11,[[6,[12,5]]]],[[5,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9,[[6,[15,5]]]],[11,[[6,[15,5]]]],[9,[[6,[[17,[16]],5]]]],[11,[[6,[[17,[16]],5]]]],[9,[[6,[18,5]]]],[11,[[6,[18,5]]]],[[11,19,[7,[1]]],[[7,[8]]]],[9,[[6,[[17,[16]],5]]]],[11,[[6,[[17,[16]],5]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[9,10],[[6,[5]]]],[[11,10],[[6,[5]]]],[[4,20,[17,[16]]],[[6,[4,5]]]],[[4,20,7],[[6,[4,5]]]],[9,[[6,[21,5]]]],[11,[[6,[21,5]]]],[[22,23],[[6,[11,5]]]],[11,4],[4,4],[9,[[6,[21,5]]]],[11,[[6,[21,5]]]],[[4,15],[[6,[4,5]]]],[4,[[6,[4,5]]]],[[4,18],[[6,[4,5]]]],[[4,[17,[16]]],[[6,[4,5]]]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[21,21],[25,25],[26,26],[[]],[[]],[[]],[20],[21],[26],[21,18],[26,18],[27],[21],[25],[[28,[29,[26]],10],[[6,[30]]]],[[],21],[[21,21],12],[[25,25],12],[[26,26],12],[28,7],[[30,13],14],[[25,13],[[6,[31]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],32],[[28,20,[29,[26]]],[[6,[[7,[27]],30]]]],[33,27],[[28,20,[29,[26]]],[[6,[[7,[33]],30]]]],[28,34],[[18,35],[[6,[21,36]]]],[[18,35],[[6,[26,36]]]],[[29,34],[[6,[30]]]],[[28,20,[17,[16]],[29,[26]]],[[6,[30]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,[[17,[16]]]],[[[37,[34]]],28],[[28,[29,[26]]],[[6,[38,30]]]],[[28,20,[29,[26]]],[[6,[[7,[[17,[16]]]],30]]]],[[28,[29,[26]]],[[6,[30]]]],[[28,[29,[26]]],[[6,[21,30]]]],[[],32],[[]],[[]],[[]],[[],[[17,[16]]]],[[],[[17,[16]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[33,39],[[6,[30]]]],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[],[[6,[38]]]],[[40,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[38,6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],24],[[],24],[[],24],[[38,20],[[6,[[7,[[17,[16]]]],40]]]]],"p":[[3,"DBRevConfig"],[3,"WALConfig"],[3,"DiskBufferConfig"],[3,"WriteBatch"],[4,"DBError"],[4,"Result"],[4,"Option"],[3,"Revision"],[3,"DBRev"],[8,"Write"],[3,"DB"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"U256"],[15,"u8"],[3,"Vec"],[15,"u64"],[15,"usize"],[8,"AsRef"],[3,"Hash"],[15,"str"],[3,"DBConfig"],[3,"TypeId"],[3,"PartialPath"],[3,"Node"],[3,"Ref"],[3,"Merkle"],[3,"ObjPtr"],[4,"MerkleError"],[3,"Error"],[8,"Iterator"],[3,"RefMut"],[8,"ShaleStore"],[8,"MemStore"],[4,"ShaleError"],[3,"Box"],[3,"Proof"],[8,"FnOnce"],[4,"ProofError"],[13,"Merkle"],[13,"Blob"],[13,"System"],[3,"IdTrans"],[8,"ValueTransformer"],[13,"Shale"],[13,"Format"],[3,"SubProof"]]},\ -"fixed_hash":{"doc":"","t":[14,14],"n":["construct_fixed_hash","impl_fixed_hash_conversions"],"q":["fixed_hash",""],"d":["Construct a fixed-size hash type.","Implements lossy conversions between the given types."],"i":[0,0],"f":[0,0],"p":[]},\ -"futures":{"doc":"Abstractions for asynchronous programming.","t":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,14,0,0,14,14,14,0,14,14,14,0,0,14,0,14,0,0,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,3,3,3,3,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,8,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,16,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,5,11,11,5,11,11,11,11,11,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,13,13,3,13,13,8,8,8,8,8,8,8,8,13,3,3,3,3,13,13,13,3,3,3,13,13,3,13,13,3,13,3,4,13,13,13,13,3,3,13,13,3,13,13,13,3,3,13,3,3,13,13,13,13,13,13,13,13,13,3,3,3,3,13,3,3,3,3,3,13,6,3,3,3,4,3,13,13,13,3,13,13,13,13,3,13,3,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,8,8,8,8,16,16,16,8,16,16,16,16,8,8,8,8,2,2,2,2,2,2,2,2,2,10,0,10,10,10,10,10,10,10,10,11,10,10,10,11,0,11,10,0,10,10,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,8,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,16,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,5,11,11,11,11,10,5,11,11,11,11,11,11,11,11,11,10,11,10,5,5,5,5,5,5,11,11,11,11,11,11,5,11,11,5,11,11,5,10,5,5,11,5,11,11,5,5,5,11,11,11,11,5,5,5,5,5,5,10,11,5,11,11,12,12,12,12,12,12,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,5,11,11,11,11,10,11,10,11,10,11,11,11,11,11,11,10,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,10,5,11,11,11,11,11,5,11,11,5,5,5,10,11,11,5,5,11,11,5,0,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,5,5,11,11,3,3,3,3,3,3,3,3,3,3,5,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,5,0,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,8,3,3,3,3,8,8,13,4,3,3,13,8,3,8,8,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,5,11,5,11,12],"n":["AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","Future","FutureExt","Sink","SinkExt","Stream","StreamExt","TryFuture","TryFutureExt","TryStream","TryStreamExt","executor","future","io","join","lock","never","pending","pin_mut","poll","prelude","ready","select","select_biased","sink","stream","stream_select","task","try_join","mpsc","oneshot","Receiver","SendError","Sender","TryRecvError","TrySendError","UnboundedReceiver","UnboundedSender","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","close_channel","close_channel","disconnect","disconnect","drop","drop","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","hash_receiver","hash_receiver","into","into","into","into","into","into","into","into_inner","into_send_error","is_closed","is_closed","is_connected_to","is_connected_to","is_disconnected","is_disconnected","is_full","is_full","is_terminated","is_terminated","poll_close","poll_close","poll_close","poll_flush","poll_flush","poll_flush","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","provide","provide","provide","same_receiver","same_receiver","start_send","start_send","start_send","start_send","start_send","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll_next","try_poll_next","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","unbounded_send","Canceled","Cancellation","Receiver","Sender","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancellation","channel","clone","clone_into","close","drop","drop","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","into_future","into_future","is_canceled","is_connected_to","is_terminated","poll","poll","poll_canceled","provide","send","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_poll","try_recv","type_id","type_id","type_id","type_id","BlockingStream","Enter","EnterError","LocalPool","LocalSpawner","block_on","block_on_stream","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","default","deref","deref_mut","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_inner","into_iter","new","next","provide","run","run_until","run_until_stalled","size_hint","spawn_local_obj","spawn_obj","spawner","status","status_local","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_run_one","type_id","type_id","type_id","type_id","type_id","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Output","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abortable","and_then","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed_local","catch_unwind","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","consume","default","downgrade","drop","drop","drop","err","err_into","factor_first","factor_second","flatten","flatten_sink","flatten_stream","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_iter","fuse","inspect","inspect_err","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_raw","into_raw","into_stream","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","map","map_err","map_into","map_ok","map_ok_or_else","maybe_done","never_error","now_or_never","ok","ok_into","or_else","output_mut","output_mut","peek","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_read","poll_read_vectored","poll_ready","poll_ready","poll_ready","poll_ready","poll_seek","poll_unpin","poll_write","poll_write_vectored","ready","remote_handle","right_future","select","select_all","select_ok","shared","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","strong_count","take_output","take_output","terminated","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_flatten","try_flatten_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_unpin","try_select","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unit_error","unwrap_or_else","upgrade","weak_count","0","0","0","0","0","0","0","1","AddrInUse","AddrNotAvailable","AllowStdIo","AlreadyExists","ArgumentListTooLong","AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","BrokenPipe","BufReader","BufWriter","Chain","Close","ConnectionAborted","ConnectionRefused","ConnectionReset","Copy","CopyBuf","CopyBufAbortable","CrossesDevices","Current","Cursor","Deadlock","DirectoryNotEmpty","Empty","End","Error","ErrorKind","ExecutableFileBusy","FileTooLarge","FilesystemLoop","FilesystemQuotaExceeded","FillBuf","Flush","HostUnreachable","Interrupted","IntoSink","InvalidData","InvalidFilename","InvalidInput","IoSlice","IoSliceMut","IsADirectory","LineWriter","Lines","NetworkDown","NetworkUnreachable","NotADirectory","NotConnected","NotFound","NotSeekable","Other","OutOfMemory","PermissionDenied","Read","ReadExact","ReadHalf","ReadLine","ReadOnlyFilesystem","ReadToEnd","ReadToString","ReadUntil","ReadVectored","Repeat","ResourceBusy","Result","ReuniteError","SeeKRelative","Seek","SeekFrom","Sink","StaleNetworkFileHandle","Start","StorageFull","Take","TimedOut","TooManyLinks","UnexpectedEof","Unsupported","Window","WouldBlock","Write","WriteAll","WriteHalf","WriteVectored","WriteZero","advance","advance","advance_slices","advance_slices","as_mut","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","buffer","buffer","cause","chain","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","close","cmp","cmp","consume","consume","consume","consume","consume","consume","consume","consume","consume","consume_unpin","copy","copy_buf","copy_buf_abortable","default","deref","deref","deref_mut","description","downcast","empty","end","eq","eq","eq","fill_buf","fill_buf","flush","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_raw_os_error","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_sink","kind","last_os_error","limit","lines","new","new","new","new","new","new","new","new","new","other","partial_cmp","partial_cmp","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_next","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_ready","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek_relative","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","position","provide","provide","raw_os_error","read","read","read_exact","read_exact","read_line","read_to_end","read_to_end","read_to_string","read_to_string","read_until","read_vectored","read_vectored","repeat","reunite","reunite","seek","seek","seek_relative","set","set_limit","set_position","sink","source","split","start","start_send","stream_position","take","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_capacity","with_capacity","with_capacity","write","write","write_all","write_all","write_fmt","write_vectored","write_vectored","0","0","0","MappedMutexGuard","Mutex","MutexGuard","MutexLockFuture","OwnedMutexGuard","OwnedMutexLockFuture","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","deref","deref","deref_mut","deref_mut","deref_mut","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","get_mut","into","into","into","into","into","into","into_future","into_future","into_inner","is_terminated","is_terminated","lock","lock_owned","map","map","new","poll","poll","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","type_id","type_id","type_id","type_id","type_id","type_id","Never","AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","Error","Error","Future","Item","Ok","Ok","Output","Sink","Stream","TryFuture","TryStream","_","_","_","_","_","_","_","_","_","consume","future","poll","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_next","poll_read","poll_read_vectored","poll_ready","poll_seek","poll_write","poll_write_vectored","sink","size_hint","start_send","stream","try_poll","try_poll_next","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Output","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abortable","and_then","boxed","boxed_local","catch_unwind","drop","err","err_into","flatten","flatten_sink","flatten_stream","fuse","inspect","inspect_err","inspect_ok","into_future","into_raw","into_stream","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","map","map_err","map_into","map_ok","map_ok_or_else","maybe_done","never_error","now_or_never","ok","ok_into","or_else","pending","poll","poll_fn","poll_immediate","poll_unpin","ready","remote_handle","right_future","select","select_all","select_ok","shared","then","try_flatten","try_flatten_stream","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll_unpin","try_select","unit_error","unwrap_or_else","0","0","0","0","0","0","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","buffer","close","drain","fanout","feed","flush","left_sink","poll_close","poll_close_unpin","poll_flush","poll_flush_unpin","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","start_send","start_send_unpin","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abortable","all","and_then","any","boxed","boxed_local","buffer_unordered","buffered","by_ref","catch_unwind","chain","chunks","collect","concat","count","cycle","empty","enumerate","err_into","filter","filter_map","flat_map","flat_map_unordered","flatten","flatten_unordered","fold","for_each","for_each_concurrent","forward","fuse","futures_unordered","inspect","inspect_err","inspect_ok","into_async_read","into_future","into_stream","is_terminated","iter","left_stream","map","map_err","map_ok","next","once","or_else","peekable","pending","poll_fn","poll_immediate","poll_next","poll_next_unpin","ready_chunks","repeat","repeat_with","right_stream","scan","select","select_all","select_all","select_next_some","select_with_strategy","size_hint","skip","skip_while","split","take","take_until","take_while","then","try_buffer_unordered","try_buffered","try_chunks","try_collect","try_concat","try_filter","try_filter_map","try_flatten","try_fold","try_for_each","try_for_each_concurrent","try_next","try_poll_next","try_poll_next_unpin","try_skip_while","try_take_while","try_unfold","unfold","unzip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","IntoIter","Iter","IterMut","SelectAll","select_all","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","clone","clone","clone","clone_into","clone_into","clone_into","close","drain","fanout","feed","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","is_terminated","is_terminated","is_terminated","is_terminated","left_sink","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close_unpin","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush_unpin","poll_next","poll_next","poll_next","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send_unpin","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abort","abortable","all","and_then","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed_local","buffer_unordered","buffered","by_ref","catch_unwind","chain","chunks","clear","clear","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","collect","concat","consume","count","cycle","default","default","default","default","drop","empty","enumerate","eq","eq","eq","err_into","extend","extend","extend","filter","filter_map","flat_map","flat_map_unordered","flatten","flatten_unordered","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fold","for_each","for_each_concurrent","forward","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","fuse","futures_unordered","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","inspect","inspect_err","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_async_read","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_stream","is_aborted","is_done","is_empty","is_empty","is_empty","is_stopped","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","iter","iter","iter","iter_mut","iter_mut","iter_pin_mut","iter_pin_ref","left_stream","len","len","len","map","map_err","map_ok","new","new","new","new","new_pair","next","next_if","next_if_eq","once","or_else","peek","peek_mut","peekable","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next_unpin","poll_peek","poll_peek_mut","poll_read","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_write","provide","provide","provide","push","push","push","push_back","push_front","ready_chunks","repeat","repeat_with","reunite","reunite","right_stream","scan","select","select_all","select_all","select_next_some","select_with_strategy","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","skip","skip_while","spawn_local_obj","spawn_obj","split","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","take","take_future","take_result","take_until","take_while","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","toggle","try_buffer_unordered","try_buffered","try_chunks","try_collect","try_concat","try_filter","try_filter_map","try_flatten","try_fold","try_for_each","try_for_each_concurrent","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next_unpin","try_skip_while","try_take_while","try_unfold","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","unzip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","next","next","next","next","next","size_hint","size_hint","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","IntoIter","Iter","IterMut","SelectAll","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","into_iter","next","next","next","select_all","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","ArcWake","AtomicWaker","Context","FutureObj","LocalFutureObj","LocalSpawn","LocalSpawnExt","Pending","Poll","RawWaker","RawWakerVTable","Ready","Spawn","SpawnError","SpawnExt","UnsafeFutureObj","Waker","WakerRef","as_raw","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","branch","branch","clone","clone","clone","clone_into","clone_into","clone_into","cmp","data","default","deref","drop","drop","drop","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_output","from_output","from_raw","from_residual","from_residual","from_residual","from_waker","hash","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_pending","is_ready","is_shutdown","map","map_err","map_err","map_ok","map_ok","new","new","new","new","new","new","new_unowned","noop_waker","noop_waker_ref","partial_cmp","poll","poll","provide","ready","register","shutdown","spawn","spawn_local","spawn_local_obj","spawn_local_with_handle","spawn_obj","spawn_with_handle","status","status_local","take","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vtable","wake","wake","wake","wake_by_ref","wake_by_ref","waker","waker","waker_ref","will_wake","0"],"q":["futures","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::channel","","futures::channel::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::channel::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::executor","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::future::Either","","futures::future::MaybeDone","","futures::future::TryMaybeDone","","futures::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::io::SeekFrom","","","futures::lock","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::never","futures::prelude","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::future::Either","","futures::prelude::future::MaybeDone","","futures::prelude::future::TryMaybeDone","","futures::prelude::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::prelude::stream::futures_unordered","","","","","","futures::prelude::stream::select_all","","","","","futures::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream::futures_unordered","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::stream::select_all","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures::task::Poll"],"d":["","","","","","","","","","","","","","","","","","","Built-in executors and related tools.","Asynchronous values.","Asynchronous I/O.","Polls multiple futures simultaneously, returning a tuple …","Futures-powered synchronization primitives.","This module contains the Never type.","A macro which yields to the event loop once.","Pins a value on the stack.","A macro which returns the result of polling a future once …","A “prelude” for crates using the futures crate.","Extracts the successful type of a Poll<T>.","Polls multiple futures and streams simultaneously, …","Polls multiple futures and streams simultaneously, …","Asynchronous sinks.","Asynchronous streams.","Combines several streams, all producing the same Item …","Tools for working with tasks.","Polls multiple futures simultaneously, resolving to a …","A multi-producer, single-consumer queue for sending values …","A channel for sending a single message between …","The receiving end of a bounded mpsc channel.","The error type for Senders used as Sinks.","The transmission end of a bounded mpsc channel.","The error type returned from try_next.","The error type returned from try_send.","The receiving end of an unbounded mpsc channel.","The transmission end of an unbounded mpsc channel.","","","","","","","","","","","","","","","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel, without dropping …","Closes the receiving half of a channel, without dropping …","Closes this channel from the sender side, preventing any …","Closes this channel from the sender side, preventing any …","Disconnects this sender from the channel, closing it if …","Disconnects this sender from the channel, closing it if …","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Hashes the receiver into the provided hasher","Hashes the receiver into the provided hasher","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the message that was attempted to be sent but …","Drops the message and converts into a SendError.","Returns whether this channel is closed without needing a …","Returns whether this channel is closed without needing a …","Returns whether the sender send to this receiver.","Returns whether the sender send to this receiver.","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the channel …","Returns true if this error is a result of the channel …","","","","","","","","","","","","Polls the channel to determine if there is guaranteed …","","","Check if the channel is ready to receive a message.","","","","Returns whether the senders send to the same receiver.","Returns whether the senders send to the same receiver.","","Send a message on the channel.","","Send a message on the channel.","","","","","","","","","","","","","","","","","","","","","","","Tries to receive the next message without notifying a …","Tries to receive the next message without notifying a …","","","Attempts to send a message on this Sender, returning the …","","","","","","","","Creates an unbounded mpsc channel for communicating …","Sends a message along this channel.","Error returned from a Receiver when the corresponding …","A future that resolves when the receiving end of a channel …","A future for a value that will be provided by another …","A means of transmitting a single value to another task.","","","","","","","","","Creates a future that resolves when this Sender’s …","Creates a new one-shot channel for sending a single value …","","","Gracefully close this receiver, preventing any subsequent …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Tests to see whether this Sender’s corresponding Receiver","Tests to see whether this Sender is connected to the given …","","","","Polls this Sender half to detect whether its associated …","","Completes this oneshot with a successful result.","","","","","","","","","","","","Attempts to receive a message outside of the context of a …","","","","","An iterator which blocks on values from a stream until …","Represents an executor context.","An error returned by enter if an execution scope has …","A single-threaded task pool for polling futures to …","A handle to a LocalPool that implements Spawn.","Run a future to completion on the current thread.","Turn a stream into a blocking iterator.","","","","","","","","","","","","","","","","","Marks the current thread as being within the dynamic …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this BlockingStream into the inner Stream type.","","Create a new, empty pool of tasks.","","","Run all tasks in the pool to completion.","Runs all the tasks in the pool until the given future …","Runs all tasks in the pool and returns if no more progress …","","","","Get a clonable handle to the pool as a Spawn.","","","","","","","","","","","","","","","Runs all tasks and returns after completing one future or …","","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","A future represents an asynchronous computation obtained …","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","The type of value produced on completion.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","","","","","","","","","","","","","","","","","Creates a new WeakShared for this Shared.","Drops the future represented by the given fat pointer.","","","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Factor out a homogeneous type from an either of pairs.","Factor out a homogeneous type from an either of pairs.","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Drops this handle without canceling the underlying future.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the success value of a future before …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Wraps a TryFuture into a type that implements Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unwraps the value from this immediately ready future.","Consumes this combinator, returning the underlying futures.","Extract the value of an either over two equivalent types.","Convert an owned instance into a (conceptually owned) fat …","","Convert this future into a single element stream.","Returns true if the underlying future should no longer be …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Returns an Option containing a mutable reference to the …","Returns an Option containing a mutable reference to the …","Returns Some containing a reference to this Shared’s …","Creates a future which never resolves, representing a …","Attempt to resolve the future to a final value, registering","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","","","","","","","","","","","","","","A convenience for calling Future::poll on Unpin future …","","","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","","","","","","","","","","Gets the number of strong pointers to this allocation.","Attempt to take the output of a MaybeDone without driving …","Attempt to take the output of a TryMaybeDone without …","Creates a new Fuse-wrapped future which is already …","Chain on a computation for when a future finished, passing …","","","","","","","","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","Attempts to upgrade this WeakShared into a Shared.","Gets the number of weak pointers to this allocation.","","","","","","","","","A socket address could not be bound because the address is …","A nonexistent interface was requested or the requested …","A simple wrapper type which allows types which implement …","An entity already exists, often a file.","Program argument list too long.","Read bytes asynchronously.","An extension trait which adds utility methods to …","Read bytes asynchronously.","An extension trait which adds utility methods to AsyncRead …","Seek bytes asynchronously.","An extension trait which adds utility methods to AsyncSeek …","Write bytes asynchronously.","An extension trait which adds utility methods to AsyncWrite…","The operation failed because a pipe was closed.","The BufReader struct adds buffering to any reader.","Wraps a writer and buffers its output.","Reader for the chain method.","Future for the close method.","The connection was aborted (terminated) by the remote …","The connection was refused by the remote server.","The connection was reset by the remote server.","Future for the copy() function.","Future for the copy_buf() function.","Future for the [copy_buf()] function.","Cross-device or cross-filesystem (hard) link or rename.","Sets the offset to the current position plus the specified …","A Cursor wraps an in-memory buffer and provides it with a …","Deadlock (avoided).","A non-empty directory was specified where an empty …","Reader for the empty() function.","Sets the offset to the size of this object plus the …","The error type for I/O operations of the Read, Write, Seek…","A list specifying general categories of I/O error.","Executable file is busy.","File larger than allowed or supported.","Loop in the filesystem or IO subsystem; often, too many …","Filesystem quota was exceeded.","Future for the fill_buf method.","Future for the flush method.","The remote host is not reachable.","This operation was interrupted.","Sink for the into_sink method.","Data not valid for the operation were encountered.","A filename was invalid.","A parameter was incorrect.","A buffer type used with Write::write_vectored.","A buffer type used with Read::read_vectored.","The filesystem object is, unexpectedly, a directory.","Wrap a writer, like BufWriter does, but prioritizes …","Stream for the lines method.","The system’s networking is down.","The network containing the remote host is not reachable.","A filesystem object is, unexpectedly, not a directory.","The network operation failed because it was not connected …","An entity was not found, often a file.","Seek on unseekable file.","A custom error that does not fall under any other I/O …","An operation could not be completed, because it failed to …","The operation lacked the necessary privileges to complete.","Future for the read method.","Future for the read_exact method.","The readable half of an object returned from …","Future for the read_line method.","The filesystem or storage medium is read-only, but a write …","Future for the read_to_end method.","Future for the read_to_string method.","Future for the read_until method.","Future for the read_vectored method.","Reader for the repeat() function.","Resource is busy.","A specialized Result type for I/O operations.","Error indicating a ReadHalf<T> and WriteHalf<T> were not …","Future for the BufReader::seek_relative method.","Future for the seek method.","Enumeration of possible methods to seek within an I/O …","Writer for the sink() function.","Stale network file handle.","Sets the offset to the provided number of bytes.","The underlying storage (typically, a filesystem) is full.","Reader for the take method.","The I/O operation’s timeout expired, causing it to be …","Too many (hard) links to the same filesystem object.","An error returned when an operation could not be completed …","This operation is unsupported on this platform.","A owned window around an underlying buffer.","The operation needs to block to complete, but the blocking …","Future for the write method.","Future for the write_all method.","The writable half of an object returned from …","Future for the write_vectored method.","An error returned when an operation could not be completed …","Advance the internal cursor of the slice.","Advance the internal cursor of the slice.","Advance a slice of slices.","Advance a slice of slices.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a reference to the internally buffered data.","Returns a reference to the internally buffered data.","Returns a reference to buf_writer’s internally buffered …","","Creates an adaptor which will chain this stream with …","","","","","","","","","","","Creates a future which will entirely close this AsyncWrite.","","","Tells this buffer that amt bytes have been consumed from …","","","","","","","","","A convenience for calling AsyncBufRead::consume on Unpin …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","","","","","","Attempt to downgrade the inner error to E if any.","Constructs a new handle to an empty reader.","Returns the end index of this window into the underlying …","","","","Creates a future which will wait for a non-empty buffer to …","","Creates a future which will entirely flush this AsyncWrite.","","","","","","","","","","Shows a human-readable description of the ErrorKind.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts a [alloc::ffi::NulError] into a Error.","","Returns the argument unchanged.","Converts an ErrorKind into an Error.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Creates a new instance of an Error from a particular OS …","Returns a mutable reference to the inner error wrapped by …","Returns a mutable reference to the contained IO object.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Gets mutable references to the underlying readers in this …","Gets a mutable reference to the underlying value in this …","Acquires a mutable reference to the underlying sink or …","Gets a mutable reference to the underlying buffer inside …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Gets pinned mutable references to the underlying readers …","Acquires a pinned mutable reference to the underlying sink …","Returns a reference to the inner error wrapped by this …","Returns a reference to the contained IO object.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Gets references to the underlying readers in this Chain.","Gets a reference to the underlying value in this cursor.","Acquires a reference to the underlying sink or stream that …","Gets a shared reference to the underlying buffer inside of …","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Consumes the Error, returning its inner error (if any).","Consumes self and returns the contained IO object.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes the Chain, returning the wrapped readers.","Consumes this cursor, returning the underlying value.","Consumes this combinator, returning the underlying sink or …","Consumes this Window, returning the underlying buffer.","Allow using an AsyncWrite as a Sink<Item: AsRef<[u8]>>.","Returns the corresponding ErrorKind for this error.","Returns an error representing the last OS error which …","Returns the remaining number of bytes that can be read …","Returns a stream over the lines of this reader. This …","Creates a new I/O error from a known kind of error as well …","Creates a new IoSliceMut wrapping a byte slice.","Creates a new IoSlice wrapping a byte slice.","Creates a new AllowStdIo from an existing IO object.","Creates a new BufReader with a default buffer capacity. …","Creates a new BufWriter with a default buffer capacity. …","Create a new LineWriter with default buffer capacity. The …","Creates a new cursor wrapping the provided underlying …","Creates a new window around the buffer t defaulting to the …","Creates a new I/O error from an arbitrary error payload.","","","","","","","","","","","","","","","","","","","","","Attempt to close the object.","","","","","Forward to buf_writer ’s BufWriter::poll_close()","","","","","","","Attempt to return the contents of the internal buffer, …","","","","","","","","Attempt to flush the object, ensuring that any buffered …","","","","","Forward to buf_writer ’s BufWriter::poll_flush()","","","","","","","","Attempt to read from the AsyncRead into buf.","","","","","","","","","","Attempt to read from the AsyncRead into bufs using vectored","","","","","","","","","Attempt to seek to an offset, in bytes, in a stream.","","Seek to an offset, in bytes, in the underlying reader.","Seek to the offset, in bytes, in the underlying writer.","","Attempts to seek relative to the current position. If the …","Attempt to write bytes from buf into the object.","","","","","","","","","","","Attempt to write bytes from bufs into the object using …","","","","","","","","","","","Returns the current position of this cursor.","","","Returns the OS error that this error represents (if any).","Tries to read some bytes directly into the given buf in …","","Creates a future which will read exactly enough bytes to …","","Creates a future which will read all the bytes associated …","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes associated …","Creates a future which will read from the AsyncRead into …","","Creates an instance of a reader that infinitely repeats …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Creates a future which will seek an IO object, and then …","","Seeks relative to the current position. If the new …","Changes the range of this window to the range specified.","Sets the number of bytes that can be read before this …","Sets the position of this cursor.","Creates an instance of a writer which will successfully …","","Helper method for splitting this read/write object into …","Returns the starting index of this window into the …","","Creates a future which will return the current seek …","Creates an AsyncRead adapter which will read at most limit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new BufReader with the specified buffer capacity.","Creates a new BufWriter with the specified buffer capacity.","Creates a new LineWriter with the specified buffer …","Creates a future which will write bytes from buf into the …","","Write data into this object.","","","Creates a future which will write bytes from bufs into the …","","","","","An RAII guard returned by the MutexGuard::map and …","A futures-aware mutex.","An RAII guard returned by the lock and try_lock methods. …","A future which resolves when the target mutex has been …","An RAII guard returned by the lock_owned and try_lock_owned…","A future which resolves when the target mutex has been …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes this mutex, returning the underlying data.","","","Acquire the lock asynchronously.","Acquire the lock asynchronously.","Returns a locked view over a portion of the locked data.","Returns a locked view over a portion of the locked data.","Creates a new futures-aware mutex.","","","","","","","","","","","","","","","Attempt to acquire the lock immediately.","Attempt to acquire the lock immediately.","","","","","","","A type with no possible values.","Read bytes asynchronously.","Read bytes asynchronously.","Seek bytes asynchronously.","Write bytes asynchronously.","The type of value produced by the sink when an error …","The type of failures yielded by this future","The type of failures yielded by this future","A future represents an asynchronous computation obtained …","Values yielded by the stream.","The type of successful values yielded by this future","The type of successful values yielded by this future","The type of value produced on completion.","A Sink is a value into which other values can be sent, …","A stream of values produced asynchronously.","A convenience for futures that return Result values that …","A convenience for streams that return Result values that …","","","","","","","","","","Tells this buffer that amt bytes have been consumed from …","Asynchronous values.","Attempt to resolve the future to a final value, registering","Flush any remaining output and close this sink, if …","Attempt to close the object.","Attempt to return the contents of the internal buffer, …","Flush any remaining output from this sink.","Attempt to flush the object, ensuring that any buffered …","Attempt to pull out the next value of this stream, …","Attempt to read from the AsyncRead into buf.","Attempt to read from the AsyncRead into bufs using vectored","Attempts to prepare the Sink to receive a value.","Attempt to seek to an offset, in bytes, in a stream.","Attempt to write bytes from buf into the object.","Attempt to write bytes from bufs into the object using …","Asynchronous sinks.","Returns the bounds on the remaining length of the stream.","Begin the process of sending a value to the sink. Each …","Asynchronous streams.","Poll this TryFuture as if it were a Future.","Poll this TryStream as if it were a Stream.","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","A future represents an asynchronous computation obtained …","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","The type of value produced on completion.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","Drops the future represented by the given fat pointer.","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the success value of a future before …","Wraps a TryFuture into a type that implements Future.","Convert an owned instance into a (conceptually owned) fat …","Convert this future into a single element stream.","Returns true if the underlying future should no longer be …","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Creates a future which never resolves, representing a …","Attempt to resolve the future to a final value, registering","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","A convenience for calling Future::poll on Unpin future …","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","Chain on a computation for when a future finished, passing …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","","","","","","","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","Adds a fixed-size buffer to the current sink.","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","Wrap this sink in an Either sink, making it the left-hand …","Flush any remaining output and close this sink, if …","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","A convenience method for calling Sink::poll_flush on Unpin …","Attempts to prepare the Sink to receive a value.","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","Begin the process of sending a value to the sink. Each …","A convenience method for calling Sink::start_send on Unpin …","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","Wraps the current stream in a new stream which converts …","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","Wraps a TryStream into a type that implements Stream","Returns true if the stream should no longer be polled.","Converts an Iterator into a Stream which is always ready …","Wrap this stream in an Either stream, making it the …","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Creates a future that resolves to the next item in the …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","A convenience method for calling Stream::poll_next on Unpin","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","Splits this Stream + Sink object into separate Sink and …","Creates a new stream of at most n items of the underlying …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Creates a future that attempts to resolve the next item in …","Poll this TryStream as if it were a Stream.","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","","","","","","","","","","","","","","","","","","","","","","","","","","","Adds a fixed-size buffer to the current sink.","","","","","","","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get a mutable reference to the inner sinks.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Get a pinned mutable reference to the inner sinks.","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Get a shared reference to the inner sinks.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Consumes this combinator, returning the underlying sinks.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","","","","","Wrap this sink in an Either sink, making it the left-hand …","","","","","","Flush any remaining output and close this sink, if …","","","","","","","","","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","","","","","","","","","A convenience method for calling Sink::poll_flush on Unpin …","","","","","","Attempts to prepare the Sink to receive a value.","","","","","","","","","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","","","","","","Begin the process of sending a value to the sink. Each …","","","","","","","","","A convenience method for calling Sink::start_send on Unpin …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Abort the Abortable stream/future associated with this …","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","Clears the set, removing all futures.","Clears the set, removing all streams.","","","","","","","","","","","","","","","","","","","","","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","","","","","","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","","","","Wraps the current stream in a new stream which converts …","","","","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying stream that …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying streams …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying stream that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying streams that this …","","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","","","","","","","","","","","","","","","","","","","","","","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying stream.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying streams.","","","","","","","Wraps a TryStream into a type that implements Stream","Checks whether the task has been aborted. Note that all …","Returns whether the underlying stream has finished or not.","Returns true if the queue contains no futures","Returns true if the set contains no futures.","Returns true if the set contains no streams","Whether the stream was stopped yet by the stopping future …","Returns true if the stream should no longer be polled.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts an Iterator into a Stream which is always ready …","Returns an iterator that allows inspecting each future in …","Returns an iterator that allows inspecting each stream in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows modifying each stream in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows inspecting each future in …","Wrap this stream in an Either stream, making it the …","Returns the number of futures contained in the queue.","Returns the number of futures contained in the set.","Returns the number of streams contained in the set.","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Constructs a new, empty FuturesOrdered","Constructs a new, empty FuturesUnordered.","Constructs a new, empty SelectAll","Creates a new Abortable future/stream using an existing …","Creates an (AbortHandle, AbortRegistration) pair which can …","Creates a future that resolves to the next item in the …","Creates a future which will consume and return the next …","Creates a future which will consume and return the next …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Produces a future which retrieves a reference to the next …","Produces a future which retrieves a mutable reference to …","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling Stream::poll_next on Unpin","Peek retrieves a reference to the next item in the stream.","Peek retrieves a mutable reference to the next item in the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Push a future into the queue.","Push a future into the set.","Push a stream into the set.","Pushes a future to the back of the queue.","Pushes a future to the front of the queue.","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","","","Splits this Stream + Sink object into separate Sink and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream of at most n items of the underlying …","Extract the stopping future out of the combinator. The …","Once the stopping future is resolved, this method can be …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","","","","","","","","","","","","","","Toggle the value and return the old one.","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a future that attempts to resolve the next item in …","","","","","","","","","","","","Poll this TryStream as if it were a Stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","Convert a list of streams into a Stream of results from …","","","","","","","","","","","","","A way of waking up a specific task.","A synchronization primitive for task wakeup.","The context of an asynchronous task.","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","Extension trait for LocalSpawn.","Represents that a value is not ready yet.","Indicates whether a value is available or if the current …","A RawWaker allows the implementor of a task executor to …","A virtual function pointer table (vtable) that specifies …","Represents that a value is immediately ready.","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","Extension trait for Spawn.","A custom implementation of a future trait object for …","A Waker is a handle for waking up a task by notifying its …","A Waker that is only valid for a given lifetime.","Get a reference to the underlying RawWaker.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the data pointer used to create this RawWaker.","","","Drops the future represented by the given fat pointer.","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Use a Wake-able type as a Waker.","Moves the value into a Poll::Ready to make a Poll<T>.","Returns the argument unchanged.","","Returns the argument unchanged.","Use a Wake-able type as a RawWaker.","Returns the argument unchanged.","","","Creates a new Waker from RawWaker.","","","","Create a new Context from a &Waker.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Returns true if the poll is a Pending value.","Returns true if the poll is a Poll::Ready value.","Check whether spawning failed to the executor being shut …","Maps a Poll<T> to Poll<U> by applying a function to a …","Maps a Poll::Ready<Option<Result<T, E>>> to …","Maps a Poll::Ready<Result<T, E>> to …","Maps a Poll<Result<T, E>> to Poll<Result<U, E>> by …","Maps a Poll<Option<Result<T, E>>> to …","Create an AtomicWaker.","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Creates a new RawWaker from the provided data pointer and …","Creates a new RawWakerVTable from the provided clone, wake,","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","","Extracts the successful type of a Poll<T>.","Registers the waker to be notified on calls to wake.","Spawning failed because the executor has been shut down.","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Returns the last Waker passed to register, so that the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the vtable pointer used to create this RawWaker.","Indicates that the associated task is ready to make …","Calls wake on the last Waker passed to register.","Wake up the task associated with this Waker.","Indicates that the associated task is ready to make …","Wake up the task associated with this Waker without …","Creates a Waker from an Arc<impl ArcWake>.","Returns a reference to the Waker for the current task.","Creates a reference to a Waker from a reference to …","Returns true if this Waker and another Waker would awake …",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,2,3,6,7,4,5,9,2,3,6,7,4,5,0,2,3,4,5,2,3,4,5,6,7,2,3,2,3,6,7,4,5,9,9,2,3,6,7,4,4,5,5,9,2,3,6,7,4,5,2,3,9,2,3,6,7,4,5,5,5,2,3,2,3,4,5,4,5,6,7,2,3,3,2,3,3,6,7,2,2,3,3,3,9,4,5,2,3,2,2,3,3,3,2,3,4,5,9,4,5,9,2,3,6,7,4,5,9,2,3,6,7,4,5,6,7,6,7,2,9,2,3,6,7,4,5,0,3,0,0,0,0,23,20,21,22,23,20,21,22,20,0,22,22,23,23,20,22,23,20,21,22,22,23,20,21,22,23,20,21,22,23,21,20,20,23,23,21,20,22,20,22,22,23,20,21,22,23,20,21,22,23,23,23,20,21,22,0,0,0,0,0,0,0,27,28,26,25,24,27,28,26,25,24,25,25,26,24,24,27,0,27,28,28,26,25,24,27,28,26,25,24,27,28,26,25,24,24,24,26,24,28,26,26,26,24,25,25,26,25,25,25,28,27,28,26,25,24,27,28,26,25,24,26,27,28,26,25,24,0,0,0,0,0,0,0,73,74,0,0,267,0,0,0,0,0,0,73,74,0,0,73,74,0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,267,0,0,0,33,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,269,269,269,37,38,39,40,41,42,43,37,38,39,40,41,42,43,43,40,37,270,37,33,0,268,43,43,269,268,269,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,59,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,49,50,80,86,269,269,268,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,268,48,49,50,51,45,52,54,55,56,57,58,36,59,60,61,62,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,42,80,43,270,33,269,271,37,51,45,47,52,53,54,55,56,57,58,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,43,43,0,0,0,0,0,0,269,269,268,269,268,268,0,269,269,0,268,268,73,74,37,0,33,37,48,49,50,51,45,52,54,55,56,57,58,36,59,60,61,62,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,47,63,46,43,43,43,47,63,46,43,43,0,0,47,53,63,46,41,43,43,43,47,63,46,43,43,269,43,43,0,269,269,0,0,0,269,47,53,63,46,43,47,63,46,43,37,73,74,51,269,37,38,39,40,41,42,43,268,268,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,0,0,0,0,0,0,267,37,48,50,51,45,52,54,55,56,57,58,36,59,61,62,32,64,44,65,66,67,68,69,70,71,72,39,74,42,81,82,83,84,85,86,43,47,53,63,46,43,268,0,37,38,48,49,50,51,45,47,52,53,54,55,56,57,58,36,59,60,61,62,63,46,32,64,44,65,66,67,68,69,70,71,72,39,73,74,40,41,42,75,76,77,78,79,80,81,82,83,84,85,86,43,269,268,38,37,272,273,274,275,276,277,113,113,99,99,0,99,99,0,0,0,0,0,0,0,0,99,0,0,0,0,99,99,99,0,0,0,99,89,0,99,99,0,89,0,0,99,99,99,99,0,0,99,99,0,99,99,99,0,0,99,0,0,99,99,99,99,99,99,99,99,99,0,0,0,0,99,0,0,0,0,0,99,0,0,0,0,0,0,99,89,99,0,99,99,99,99,0,99,0,0,0,0,99,91,92,91,92,93,93,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,94,95,96,88,278,99,92,89,100,101,99,92,89,100,101,279,99,100,280,104,100,100,94,95,98,101,105,281,0,0,0,101,91,92,91,88,88,0,93,99,89,100,281,100,279,100,88,88,91,104,111,112,113,113,99,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,88,88,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,88,100,94,95,98,101,105,93,94,95,98,105,88,100,94,95,96,98,101,105,93,99,100,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,88,100,94,95,98,101,105,93,279,88,88,105,281,88,91,92,100,94,95,96,101,93,88,99,100,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,282,112,100,94,95,96,101,101,101,101,116,127,280,104,100,94,95,98,101,105,282,112,100,94,95,96,101,101,101,101,116,127,117,283,104,111,100,94,95,98,101,126,105,283,111,100,94,95,98,101,126,116,284,100,94,95,101,94,282,112,100,94,95,96,101,101,101,101,127,282,112,100,94,95,96,101,101,101,101,127,101,88,113,88,278,100,278,100,281,278,100,278,100,281,278,100,0,126,127,285,100,94,93,105,101,0,88,278,93,116,285,278,99,92,89,100,101,88,113,99,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,114,102,106,107,115,109,110,118,119,120,121,122,123,124,125,128,129,130,117,88,91,104,111,112,113,99,92,89,100,94,114,95,96,98,102,106,107,115,101,109,110,116,117,118,119,120,121,122,123,124,125,126,127,105,93,128,129,130,94,95,96,279,100,279,100,100,279,100,286,287,288,0,0,0,0,0,0,137,141,138,142,139,140,137,141,138,142,139,140,137,138,139,140,138,139,140,141,138,142,139,140,137,141,138,142,139,140,137,137,137,141,138,142,139,140,137,137,141,138,142,139,140,141,142,137,141,142,137,137,139,140,137,141,142,137,141,138,142,139,140,137,141,138,142,139,140,137,137,137,141,138,142,139,140,0,0,0,0,0,289,267,290,0,161,267,290,33,0,0,0,0,0,0,0,0,0,0,0,0,0,280,0,33,289,282,280,289,282,161,283,283,289,284,282,282,0,161,289,0,267,290,0,0,0,0,0,0,0,73,74,0,0,267,0,0,0,0,0,0,73,74,0,0,73,74,0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,267,0,0,0,33,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,268,269,269,269,270,0,268,269,268,269,269,269,268,268,268,270,269,271,0,0,0,0,0,0,269,269,268,269,268,268,0,269,269,0,268,268,0,33,0,0,269,0,269,269,0,0,0,269,269,268,268,0,0,0,0,0,0,267,268,0,269,268,272,273,274,275,276,277,0,0,0,289,0,0,0,0,0,0,0,0,0,0,0,0,291,291,0,291,291,291,291,289,291,289,291,289,291,291,291,291,291,291,289,291,0,291,291,242,241,242,241,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,0,237,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,292,293,292,292,292,292,292,292,292,292,292,292,292,292,292,0,292,293,292,292,292,292,292,292,292,292,292,292,292,0,292,293,293,293,292,293,294,0,292,292,293,293,292,0,293,292,0,0,0,161,292,292,0,0,292,292,0,0,0,292,0,161,292,292,292,292,292,292,292,293,293,293,293,293,293,293,293,293,293,293,293,290,293,293,293,0,0,292,292,0,0,0,0,0,0,0,0,0,0,0,0,0,0,289,0,0,0,0,0,0,0,0,0,0,0,0,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,291,147,154,156,147,154,156,291,0,291,291,291,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,148,153,154,156,157,145,148,153,154,156,157,145,148,153,154,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,149,150,151,148,153,154,156,157,145,153,154,157,145,291,152,146,149,150,151,289,147,148,153,154,155,156,157,145,291,289,147,148,153,154,155,156,157,145,291,153,154,156,157,145,289,147,148,153,154,155,156,157,145,291,291,291,291,291,291,153,154,156,157,145,289,147,148,153,154,155,156,157,145,291,147,154,156,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,152,146,149,150,151,153,154,156,157,145,152,146,147,148,149,150,153,154,151,155,156,157,145,0,291,291,242,241,242,241,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,0,237,0,0,0,0,0,0,0,290,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,235,0,292,293,292,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,292,292,292,292,292,292,292,292,236,208,192,204,205,171,200,202,237,238,235,239,192,204,205,171,200,202,237,238,235,239,292,292,189,292,292,240,236,237,208,236,0,292,241,237,239,293,240,236,208,292,292,292,292,292,292,242,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,239,292,292,292,292,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,240,236,208,292,0,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,237,292,293,293,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,293,292,167,233,168,181,184,182,190,196,209,243,244,245,246,183,228,226,220,221,225,227,238,172,174,175,179,185,190,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,207,210,236,236,236,208,208,208,293,238,185,240,236,208,214,294,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,183,164,159,173,188,187,191,195,194,198,228,222,223,224,220,219,225,229,230,227,204,205,171,197,200,202,207,210,232,208,0,236,208,236,208,236,236,292,240,236,208,292,293,293,240,236,208,238,235,292,199,199,0,293,199,199,292,0,167,233,168,181,184,182,190,196,209,243,244,245,246,183,228,226,220,221,225,227,238,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,189,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,0,0,161,201,240,236,165,170,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,247,164,159,173,188,187,191,195,194,198,222,223,224,219,231,229,230,217,218,192,204,205,171,197,200,202,207,210,232,208,238,292,199,199,189,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,189,242,241,239,240,236,208,240,240,292,0,0,247,248,292,292,0,0,0,292,0,161,240,236,165,170,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,234,166,203,206,162,163,164,159,173,188,187,191,195,194,198,222,223,219,229,230,192,204,205,171,197,200,202,292,292,236,236,292,172,174,175,179,185,186,193,176,199,211,212,213,215,214,216,166,203,206,162,163,248,159,173,188,187,191,195,194,198,222,223,224,219,229,230,217,218,292,214,214,292,292,292,192,204,205,171,200,202,237,238,235,239,242,241,239,237,293,293,293,293,293,293,293,293,293,293,293,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,293,168,181,184,209,228,226,220,221,225,227,238,290,201,240,236,165,170,174,175,179,185,186,193,176,199,211,212,213,215,214,216,206,162,163,247,164,159,173,188,187,191,195,194,198,222,223,224,219,231,229,230,217,218,192,204,205,171,197,200,207,210,232,208,238,293,293,293,0,242,201,240,236,165,167,233,168,170,172,174,175,179,181,184,182,185,190,186,193,176,196,209,199,243,244,245,246,211,212,213,215,214,216,234,166,203,206,162,163,183,247,248,164,159,173,188,187,191,195,194,198,228,226,222,223,224,220,221,219,241,225,231,229,230,217,218,227,189,192,204,205,171,197,200,202,207,237,210,232,208,238,249,235,239,0,292,292,0,0,0,0,0,0,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,254,252,255,250,256,0,0,0,0,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,251,253,257,0,251,253,257,251,253,257,251,253,257,251,253,257,0,0,0,0,0,0,0,15,0,0,0,15,0,0,0,0,0,0,258,30,262,29,31,263,14,258,15,259,261,30,262,29,31,263,14,258,15,259,261,15,15,258,15,261,258,15,261,15,259,262,263,270,29,258,15,259,261,30,30,262,29,31,263,14,258,15,259,261,30,262,29,29,29,29,29,29,31,31,31,31,31,263,14,258,258,15,15,15,259,259,261,15,15,258,15,15,15,14,15,30,262,29,31,263,14,258,15,259,261,29,31,29,270,15,15,30,15,15,15,15,15,262,29,31,263,259,261,263,0,0,15,29,31,30,15,262,30,295,296,297,296,298,295,298,297,262,258,15,261,30,30,262,29,31,263,14,258,15,259,261,30,262,29,31,263,14,258,15,259,261,29,31,30,262,29,31,263,14,258,15,259,261,259,299,262,258,299,258,0,14,0,258,300],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[5,5],[[]],[[]],[[]],[[]],[6],[7],[2],[3],[2],[3],[6],[7],[[4,4],8],[[5,5],8],[[9,10],[[12,[11]]]],[[9,10],[[12,[11]]]],[[2,10],[[12,[11]]]],[[3,10],[[12,[11]]]],[[6,10],[[12,[11]]]],[[7,10],[[12,[11]]]],[[4,10],[[12,[11]]]],[[4,10],[[12,[11]]]],[[5,10],[[12,[11]]]],[[5,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2],[3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5],[5,4],[2,8],[3,8],[[2,6],8],[[3,7],8],[4,8],[5,8],[4,8],[5,8],[6,8],[7,8],[[[13,[2]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[2]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[6]],14],[[15,[16]]]],[[[13,[7]],14],[[15,[16]]]],[[[13,[2]],14],[[15,[12]]]],[[2,14],[[15,[[12,[4]]]]]],[[[13,[3]],14],[[15,[12]]]],[[[13,[3]],14],[[15,[12]]]],[[3,14],[[15,[[12,[4]]]]]],[17],[17],[17],[[2,2],8],[[3,3],8],[[[13,[2]]],12],[2,[[12,[4]]]],[[[13,[3]]],12],[3,[[12,[4]]]],[[[13,[3]]],12],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[6,[[12,[16,9]]]],[7,[[12,[16,9]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[2,[[12,[5]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[]],[3,[[12,[5]]]],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[20,21],[[]],[22,22],[[]],[23],[23],[20],[[22,22],8],[[23,10],[[12,[11]]]],[[20,10],[[12,[11]]]],[[21,10],[[12,[11]]]],[[22,10],[[12,[11]]]],[[22,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[20,8],[[20,23],8],[23,8],[[[13,[23]],14],[[15,[[12,[22]]]]]],[[[13,[21]],14],15],[[20,14],15],[17],[20,12],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[23,[[12,[16,22]]]],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,[[]],[[],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,25],[[]],[[],26],[24],[24],[27],[[],[[12,[27,28]]]],[[27,10],[[12,[11]]]],[[28,10],[[12,[11]]]],[[28,10],[[12,[11]]]],[[26,10],[[12,[11]]]],[[25,10],[[12,[11]]]],[[24,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[24],[[]],[[],26],[24,16],[17],[26],[26],[26],[24],[[25,29],[[12,[30]]]],[[25,31],[[12,[30]]]],[26,25],[25,[[12,[30]]]],[25,[[12,[30]]]],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[26,8],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],32],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[13,[[35,[33,34]]]]]],[[],[[13,[[35,[33,34]]]]]],[[],36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[13,[43]],1]],[[],40],[37,[[16,[38]]]],[33],[37],[33],[[],[[42,[12]]]],[[],44],[43],[43],[[],45],[[],46],[[],47],[[37,10],[[12,[11]]]],[[38,10],[[12,[11]]]],[[48,10],[[12,[11]]]],[[49,10],[[12,[11]]]],[[50,10],[[12,[11]]]],[[51,10],[[12,[11]]]],[[45,10],[[12,[11]]]],[[47,10],[[12,[11]]]],[[52,10],[[12,[11]]]],[[53,10],[[12,[11]]]],[[54,10],[[12,[11]]]],[[55,10],[[12,[11]]]],[[56,10],[[12,[11]]]],[[57,10],[[12,[11]]]],[[58,10],[[12,[11]]]],[[36,10],[[12,[11]]]],[[59,10],[[12,[11]]]],[[60,10],[[12,[11]]]],[[61,10],[[12,[11]]]],[[62,10],[[12,[11]]]],[[63,10],[[12,[11]]]],[[46,10],[[12,[11]]]],[[32,10],[[12,[11]]]],[[64,10],[[12,[11]]]],[[44,10],[[12,[11]]]],[[65,10],[[12,[11]]]],[[66,10],[[12,[11]]]],[[67,10],[[12,[11]]]],[[68,10],[[12,[11]]]],[[69,10],[[12,[11]]]],[[70,10],[[12,[11]]]],[[71,10],[[12,[11]]]],[[72,10],[[12,[11]]]],[[39,10],[[12,[11]]]],[[73,10],[[12,[11]]]],[[74,10],[[12,[11]]]],[[40,10],[[12,[11]]]],[[41,10],[[12,[11]]]],[[42,10],[[12,[11]]]],[[75,10],[[12,[11]]]],[[76,10],[[12,[11]]]],[[77,10],[[12,[11]]]],[[78,10],[[12,[11]]]],[[79,10],[[12,[11]]]],[[80,10],[[12,[11]]]],[[81,10],[[12,[11]]]],[[82,10],[[12,[11]]]],[[83,10],[[12,[11]]]],[[84,10],[[12,[11]]]],[[85,10],[[12,[11]]]],[[86,10],[[12,[11]]]],[[43,10],[[12,[11]]]],[59],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,40],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],49],[[],50],[[],80],[[],86],[[],51],[[],56],[[],67],[[],66],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],61],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[42],[80,[[87,[34]]]],[43],[[],33],[33,33],[[],53],[[],8],[37,8],[51,8],[45,8],[47,8],[52,8],[53,8],[54,8],[55,8],[56,8],[57,8],[58,8],[61,8],[62,8],[63,8],[46,8],[32,8],[64,8],[44,8],[65,8],[66,8],[67,8],[68,8],[69,8],[70,8],[71,8],[72,8],[39,8],[73,8],[74,8],[40,8],[41,8],[42,8],[75,8],[76,8],[77,8],[78,8],[79,8],[43,8],[43,8],[[],75],[[],76],[[],77],[[],78],[[],49],[[],72],[[],43],[[],52],[[],69],[[],54],[[],68],[[],70],[[],73],[[],57],[[],16],[[],[[42,[12]]]],[[],65],[[],64],[[[13,[73]]],16],[[[13,[74]]],16],[37,16],[[],39],[[13,14],15],[[[13,[37]],14],15],[[[13,[48]],14],15],[[[13,[49]],14],15],[[[13,[50]],14],15],[[[13,[51]],14],15],[[[13,[45]],14],15],[[[13,[52]],14],15],[[[13,[54]],14],15],[[[13,[55]],14],15],[[[13,[56]],14],15],[[[13,[57]],14],15],[[[13,[58]],14],15],[[[13,[36]],14],15],[[[13,[59]],14],15],[[[13,[60]],14],15],[[[13,[61]],14],15],[[[13,[62]],14],15],[[[13,[32]],14],15],[[[13,[64]],14],15],[[[13,[44]],14],15],[[[13,[65]],14],15],[[[13,[66]],14],15],[[[13,[67]],14],15],[[[13,[68]],14],15],[[[13,[69]],14],15],[[[13,[70]],14],15],[[[13,[71]],14],15],[[[13,[72]],14],15],[[[13,[39]],14],15],[[[13,[73]],14],15],[[[13,[74]],14],15],[[[13,[40]],14],15],[[[13,[41]],14],[[15,[16]]]],[[[13,[42]],14],15],[[[13,[75]],14],15],[[[13,[76]],14],15],[[[13,[77]],14],15],[[[13,[78]],14],15],[[[13,[79]],14],15],[[[13,[80]],14],15],[[[13,[81]],14],15],[[[13,[82]],14],15],[[[13,[83]],14],15],[[[13,[84]],14],15],[[[13,[85]],14],15],[[[13,[86]],14],15],[[[13,[43]],14],15],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[43]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[[12,[88]]]]]],[[[13,[43]],14],[[15,[12]]]],[[],48],[[],41],[[[13,[47]],14],[[15,[16]]]],[[[13,[53]],14],[[15,[16]]]],[[[13,[63]],14],[[15,[16]]]],[[[13,[46]],14],[[15,[16]]]],[[[13,[41]],14],[[15,[16]]]],[[[13,[43]],14],[[15,[16]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[47]],14],[[15,[12]]]],[[[13,[63]],14],[[15,[12]]]],[[[13,[46]],14],[[15,[12]]]],[[[13,[43]],14],[[15,[12]]]],[[[13,[43]],14,89],[[15,[[12,[90,88]]]]]],[14,15],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[[13,[43]],14],[[15,[[12,[1,88]]]]]],[[],42],[[]],[[],43],[[],79],[[],80],[[],86],[[],37],[47],[53],[63],[46],[43],[[[13,[47]]],12],[[[13,[63]]],12],[[[13,[46]]],12],[[[13,[43]]],12],[37,[[16,[1]]]],[[[13,[73]]],16],[[[13,[74]]],16],[[],51],[[],55],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],62],[[],63],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],81],[[],82],[[],83],[[],84],[[],50],[[],74],[[13,14],[[15,[12]]]],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[12]]]],[[],85],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],58],[[],71],[38,[[16,[37]]]],[37,[[16,[1]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[91,1]],[[92,1]],[1],[1],[93],[93],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[94],[95],[96],[88,[[16,[97]]]],[[],98],[99,99],[92,92],[89,89],[100,100],[101,101],[[]],[[]],[[]],[[]],[[]],[[],102],[[99,99],103],[[100,100],103],[[13,1]],[[[13,[104]],1]],[[100,1]],[[[13,[100]],1]],[[[13,[94]],1]],[[[13,[95]],1]],[[[13,[98]],1]],[[[13,[101]],1]],[[[13,[105]],1]],[1],[[],106],[[],107],[[]],[[],101],[91],[92],[91],[88,108],[88,[[12,[[35,[34]],88]]]],[[],104],[93,1],[[99,99],8],[[89,89],8],[[100,100],8],[[],109],[100,[[12,[88]]]],[[],110],[100,[[12,[88]]]],[[88,10],[[12,[11]]]],[[88,10],[[12,[11]]]],[[91,10],[[12,[11]]]],[[104,10],[[12,[11]]]],[[111,10],[[12,[11]]]],[[112,10],[[12,[11]]]],[[113,10],[[12,[11]]]],[[113,10],[[12,[11]]]],[[99,10],[[12,[11]]]],[[99,10],[[12,[11]]]],[[92,10],[[12,[11]]]],[[89,10],[[12,[11]]]],[[100,10],[[12,[11]]]],[[94,10],[[12,[11]]]],[[114,10],[[12,[11]]]],[[95,10],[[12,[11]]]],[[96,10],[[12,[11]]]],[[98,10],[[12,[11]]]],[[102,10],[[12,[11]]]],[[106,10],[[12,[11]]]],[[107,10],[[12,[11]]]],[[115,10],[[12,[11]]]],[[101,10],[[12,[11]]]],[[109,10],[[12,[11]]]],[[110,10],[[12,[11]]]],[[116,10],[[12,[11]]]],[[117,10],[[12,[11]]]],[[118,10],[[12,[11]]]],[[119,10],[[12,[11]]]],[[120,10],[[12,[11]]]],[[121,10],[[12,[11]]]],[[122,10],[[12,[11]]]],[[123,10],[[12,[11]]]],[[124,10],[[12,[11]]]],[[125,10],[[12,[11]]]],[[126,10],[[12,[11]]]],[[127,10],[[12,[11]]]],[[105,10],[[12,[11]]]],[[93,10],[[12,[11]]]],[[128,10],[[12,[11]]]],[[129,10],[[12,[11]]]],[[130,10],[[12,[11]]]],[131,88],[132,88],[[]],[99,88],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[133,88],[88,[[16,[97]]]],[100],[94],[95],[98],[101],[105],[93],[[[13,[94]]],13],[[[13,[95]]],13],[[[13,[98]]]],[[[13,[105]]],13],[88,[[16,[97]]]],[100],[94],[95],[96],[98],[101],[105],[93],[99],[100],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[88,[[16,[[35,[97,34]]]]]],[100],[94],[95],[98],[101],[105],[93],[[],116],[88,99],[[],88],[105,90],[[],117],[99,88],[[],91],[[],92],[[],100],[[],94],[[],95],[[],96],[[],101],[[],93],[[],88],[[99,99],[[16,[103]]]],[[100,100],[[16,[103]]]],[[[13,[114]],14],15],[[[13,[102]],14],15],[[[13,[106]],14],15],[[[13,[107]],14],15],[[[13,[115]],14],15],[[[13,[109]],14],15],[[[13,[110]],14],15],[[[13,[118]],14],15],[[[13,[119]],14],15],[[[13,[120]],14],15],[[[13,[121]],14],15],[[[13,[122]],14],15],[[[13,[123]],14],15],[[[13,[124]],14],15],[[[13,[125]],14],15],[[[13,[128]],14],15],[[[13,[129]],14],15],[[[13,[130]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[112]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[96]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[[13,[127]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[104]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[98]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[105]],14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[[13,[112]],14],[[15,[[12,[88]]]]]],[[[13,[100]],14],[[15,[[12,[88]]]]]],[[[13,[94]],14],[[15,[[12,[88]]]]]],[[[13,[95]],14],[[15,[[12,[88]]]]]],[[[13,[96]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[101]],14],[[15,[[12,[88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[[13,[127]],14],[[15,[[12,[88]]]]]],[[[13,[117]],14],[[15,[16]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[104]],14],[[15,[[12,[1,88]]]]]],[[[13,[111]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[98]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[126]],14],[[15,[[12,[1,88]]]]]],[[[13,[105]],14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[111]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[98]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[126]],14],[[15,[[12,[1,88]]]]]],[[[13,[116]],14],[[15,[12]]]],[[13,14,89],[[15,[[12,[90,88]]]]]],[[[13,[100]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[94]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[95]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[101]],14,89],[[15,[[12,[90,88]]]]]],[[[13,[94]],14,134],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[112]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[96]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[127]],14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[[13,[112]],14],[[15,[[12,[1,88]]]]]],[[[13,[100]],14],[[15,[[12,[1,88]]]]]],[[[13,[94]],14],[[15,[[12,[1,88]]]]]],[[[13,[95]],14],[[15,[[12,[1,88]]]]]],[[[13,[96]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[101]],14],[[15,[[12,[1,88]]]]]],[[[13,[127]],14],[[15,[[12,[1,88]]]]]],[101,90],[17],[17],[88,[[16,[133]]]],[[],118],[100,[[12,[1,88]]]],[[],120],[100,[[12,[88]]]],[18,121],[87,122],[[100,87],[[12,[1,88]]]],[18,123],[[100,18],[[12,[1,88]]]],[[135,87],124],[[],119],[100,[[12,[1,88]]]],[135,111],[[126,127],[[12,[113]]]],[[127,126],[[12,[113]]]],[89,125],[[100,89],[[12,[90,88]]]],[[[13,[94]],134],114],[93],[[105,90]],[[101,90]],[[],112],[88,[[16,[97]]]],[[]],[93,1],[[[13,[116]]],12],[[],125],[90,105],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[1,94],[1,95],[1,96],[[],128],[100,[[12,[1,88]]]],[[],130],[100,[[12,[88]]]],[[100,136],[[12,[88]]]],[[],129],[100,[[12,[1,88]]]],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],137],[138],[139],[140],[138],[139],[140],[141],[138],[142],[139],[140],[[137,10],[[12,[11]]]],[[141,10],[[12,[11]]]],[[138,10],[[12,[11]]]],[[142,10],[[12,[11]]]],[[139,10],[[12,[11]]]],[[140,10],[[12,[11]]]],[143],[[]],[[],137],[[]],[[]],[[]],[[]],[[]],[137],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[137],[141,8],[142,8],[137,142],[[[144,[137]]],141],[139,140],[140,140],[[],137],[[[13,[141]],14],15],[[[13,[142]],14],15],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[137,[[16,[139]]]],[144,[[16,[138]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[13,1]],0,[[13,14],15],[[13,14],[[15,[12]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[12]]]],[[13,14],[[15,[[12,[88]]]]]],[[13,14],[[15,[16]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[12]]]],[[13,14,89],[[15,[[12,[90,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],[[13,14],[[15,[[12,[1,88]]]]]],0,[[]],[13,12],0,[[13,14],[[15,[12]]]],[[13,14],[[15,[[16,[12]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],32],[[],[[13,[[35,[33,34]]]]]],[[],[[13,[[35,[33,34]]]]]],[[],36],[33],[[],[[42,[12]]]],[[],44],[[],45],[[],46],[[],47],[[],51],[[],56],[[],67],[[],66],[[],61],[[],33],[[],53],[[],8],[[],75],[[],76],[[],77],[[],78],[[],49],[[],72],[[],43],[[],52],[[],69],[[],54],[[],68],[[],70],[[],73],[[],57],[[],16],[[],[[42,[12]]]],[[],65],[[],64],[[],39],[[13,14],15],[[],48],[[],41],[14,15],[[],42],[[]],[[],43],[[],79],[[],80],[[],86],[[],37],[[],55],[[],62],[[],63],[[],81],[[],82],[[],83],[[],84],[[],50],[[],74],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[],85],[[],58],[[],71],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,145],[[],146],[[],147],[[],148],[[],149],[[],150],[[],43],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[14,[[15,[12]]]],[[],43],[[],151],[[],152],[[],153],[[],154],[13,12],[[],12],[[],155],[[],156],[[],157],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],158],[[],159],[[],160],[[],[[13,[[35,[161,34]]]]]],[[],[[13,[[35,[161,34]]]]]],[1,162],[1,163],[[]],[[],164],[[],165],[1,166],[[],167],[[],168],[[],169],[[],170],[[],171],[[],172],[[],173],[[],174],[[],175],[[],176],[[[177,[[16,[1]]]]],178],[[],179],[[[177,[[16,[1]]]]],180],[[],181],[[],182],[[[177,[[16,[1]]]]],183],[[],184],[[],185],0,[[],186],[[],187],[[],188],[[],189],[[],190],[[],191],[[],8],[[],192],[[],43],[[],193],[[],194],[[],195],[[],196],[[],197],[[],198],[[],199],[[],200],[[],201],[[],202],[[13,14],[[15,[16]]]],[14,[[15,[16]]]],[1,203],[[],204],[[],205],[[],43],[[],206],[[],207],0,[[],208],[[],209],[[],210],[[]],[1,211],[[],212],[[]],[1,213],[[],214],[[],215],[[],216],[1,217],[1,218],[1,219],[[],220],[[],221],[[],222],[[],223],[[],224],[[],225],[[],226],[[[177,[[16,[1]]]]],227],[[],228],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[[16,[12]]]]]],[[],229],[[],230],[[],231],[[],232],[[],233],[[],234],0,0,0,0,0,0,0,0,0,0,[[],208],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,145],[147,147],[154,154],[156,156],[[]],[[]],[[]],[[],146],[[],147],[[],148],[[],149],[[],150],[[152,10],[[12,[11]]]],[[146,10],[[12,[11]]]],[[147,10],[[12,[11]]]],[[148,10],[[12,[11]]]],[[149,10],[[12,[11]]]],[[150,10],[[12,[11]]]],[[153,10],[[12,[11]]]],[[154,10],[[12,[11]]]],[[151,10],[[12,[11]]]],[[155,10],[[12,[11]]]],[[156,10],[[12,[11]]]],[[157,10],[[12,[11]]]],[[145,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[154],[156],[157],[145],[[[13,[148]]]],[[[13,[153]]],13],[[[13,[154]]],13],[[[13,[156]]],13],[[[13,[157]]],13],[[[13,[145]]],13],[148],[153],[154],[156],[157],[145],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[154],[156],[157],[145],[153,8],[154,8],[157,8],[145,8],[[],43],[[[13,[152]],14],15],[[[13,[146]],14],15],[[[13,[149]],14],15],[[[13,[150]],14],15],[[[13,[151]],14],15],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[[13,[153]],14],[[15,[16]]]],[[[13,[154]],14],[[15,[16]]]],[[[13,[156]],14],[[15,[16]]]],[[[13,[157]],14],[[15,[16]]]],[[[13,[145]],14],[[15,[16]]]],[[13,14],[[15,[12]]]],[[[13,[147]],14],[[15,[12]]]],[[[13,[148]],14],[[15,[12]]]],[[[13,[153]],14],[[15,[12]]]],[[[13,[154]],14],[[15,[12]]]],[[[13,[155]],14],[[15,[12]]]],[[[13,[156]],14],[[15,[12]]]],[[[13,[157]],14],[[15,[12]]]],[[[13,[145]],14],[[15,[12]]]],[14,[[15,[12]]]],[[],43],[[],151],[[],152],[[],153],[[],154],[153],[154],[156],[157],[145],[13,12],[[[13,[147]]],12],[[[13,[148]]],12],[[[13,[153]]],12],[[[13,[154]]],12],[[[13,[155]]],12],[[[13,[156]]],12],[[[13,[157]]],12],[[[13,[145]]],12],[[],12],[[]],[[]],[[]],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],155],[[],156],[[],157],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[235],[[]],[[],158],[[],159],[[],160],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[13,[[35,[161,34]]]]]],[[],[[13,[[35,[161,34]]]]]],[1,162],[1,163],[[]],[[],164],[[],165],[1,166],[236],[208],[192,192],[204,204],[205,205],[171,171],[200,200],[202,202],[237,237],[238,238],[235,235],[239,239],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],167],[[],168],[[[13,[189]],1]],[[],169],[[],170],[[],240],[[],236],[[],237],[[],208],[236],[[],171],[[],172],[[241,241],8],[[237,237],8],[[239,239],8],[[],173],[240],[236],[208],[[],174],[[],175],[[],176],[[[177,[[16,[1]]]]],178],[[],179],[[[177,[[16,[1]]]]],180],[[242,10],[[12,[11]]]],[[242,10],[[12,[11]]]],[[201,10],[[12,[11]]]],[[240,10],[[12,[11]]]],[[236,10],[[12,[11]]]],[[165,10],[[12,[11]]]],[[167,10],[[12,[11]]]],[[233,10],[[12,[11]]]],[[168,10],[[12,[11]]]],[[170,10],[[12,[11]]]],[[172,10],[[12,[11]]]],[[174,10],[[12,[11]]]],[[175,10],[[12,[11]]]],[[179,10],[[12,[11]]]],[[181,10],[[12,[11]]]],[[184,10],[[12,[11]]]],[[182,10],[[12,[11]]]],[[185,10],[[12,[11]]]],[[190,10],[[12,[11]]]],[[186,10],[[12,[11]]]],[[193,10],[[12,[11]]]],[[176,10],[[12,[11]]]],[[196,10],[[12,[11]]]],[[209,10],[[12,[11]]]],[[199,10],[[12,[11]]]],[[243,10],[[12,[11]]]],[[244,10],[[12,[11]]]],[[245,10],[[12,[11]]]],[[246,10],[[12,[11]]]],[[211,10],[[12,[11]]]],[[212,10],[[12,[11]]]],[[213,10],[[12,[11]]]],[[215,10],[[12,[11]]]],[[214,10],[[12,[11]]]],[[216,10],[[12,[11]]]],[[234,10],[[12,[11]]]],[[166,10],[[12,[11]]]],[[203,10],[[12,[11]]]],[[206,10],[[12,[11]]]],[[162,10],[[12,[11]]]],[[163,10],[[12,[11]]]],[[183,10],[[12,[11]]]],[[247,10],[[12,[11]]]],[[248,10],[[12,[11]]]],[[164,10],[[12,[11]]]],[[159,10],[[12,[11]]]],[[173,10],[[12,[11]]]],[[188,10],[[12,[11]]]],[[187,10],[[12,[11]]]],[[191,10],[[12,[11]]]],[[195,10],[[12,[11]]]],[[194,10],[[12,[11]]]],[[198,10],[[12,[11]]]],[[228,10],[[12,[11]]]],[[226,10],[[12,[11]]]],[[222,10],[[12,[11]]]],[[223,10],[[12,[11]]]],[[224,10],[[12,[11]]]],[[220,10],[[12,[11]]]],[[221,10],[[12,[11]]]],[[219,10],[[12,[11]]]],[[241,10],[[12,[11]]]],[[241,10],[[12,[11]]]],[[225,10],[[12,[11]]]],[[231,10],[[12,[11]]]],[[229,10],[[12,[11]]]],[[230,10],[[12,[11]]]],[[217,10],[[12,[11]]]],[[218,10],[[12,[11]]]],[[227,10],[[12,[11]]]],[[189,10],[[12,[11]]]],[[192,10],[[12,[11]]]],[[204,10],[[12,[11]]]],[[205,10],[[12,[11]]]],[[171,10],[[12,[11]]]],[[197,10],[[12,[11]]]],[[200,10],[[12,[11]]]],[[202,10],[[12,[11]]]],[[207,10],[[12,[11]]]],[[237,10],[[12,[11]]]],[[210,10],[[12,[11]]]],[[232,10],[[12,[11]]]],[[208,10],[[12,[11]]]],[[238,10],[[12,[11]]]],[[249,10],[[12,[11]]]],[[235,10],[[12,[11]]]],[[239,10],[[12,[11]]]],[[239,10],[[12,[11]]]],[[],181],[[],182],[[[177,[[16,[1]]]]],183],[[],184],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],240],[[],236],[[],208],[[],185],0,[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[[[13,[172]]],13],[[[13,[174]]],13],[[[13,[175]]],13],[[[13,[179]]],13],[[[13,[185]]],13],[[[13,[190]]],[[16,[13]]]],[[[13,[186]]],13],[[[13,[193]]],13],[[[13,[176]]],13],[[[13,[199]]],13],[[[13,[211]]],13],[[[13,[212]]],13],[[[13,[213]]],13],[[[13,[215]]],13],[[[13,[214]]],13],[[[13,[216]]],13],[[[13,[234]]]],[[[13,[166]]],13],[[[13,[203]]],13],[[[13,[206]]],13],[[[13,[162]]],13],[[[13,[163]]],13],[[[13,[164]]],13],[[[13,[159]]],13],[[[13,[173]]],13],[[[13,[188]]],13],[[[13,[187]]],13],[[[13,[191]]],13],[[[13,[195]]],13],[[[13,[194]]],13],[[[13,[198]]],13],[[[13,[222]]],13],[[[13,[223]]],13],[[[13,[224]]],13],[[[13,[219]]],13],[[[13,[229]]],13],[[[13,[230]]],13],[[[13,[217]]],13],[[[13,[218]]],13],[[[13,[207]]]],[[[13,[210]]]],[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[237],[[],186],[[],187],[[],188],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],189],[[],190],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[172],[174],[175],[179],[185],[190,16],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[224],[219],[229],[230],[217],[218],[207],[210],[236],[236],[236],[208],[208],[208],[[],191],[238,8],[185,8],[240,8],[236,8],[208,8],[214,8],[[],8],[240,8],[236,8],[165,8],[167,8],[233,8],[168,8],[170,8],[172,8],[174,8],[175,8],[179,8],[181,8],[184,8],[182,8],[185,8],[190,8],[186,8],[193,8],[176,8],[196,8],[209,8],[199,8],[243,8],[244,8],[245,8],[246,8],[211,8],[212,8],[213,8],[215,8],[214,8],[216,8],[234,8],[166,8],[203,8],[206,8],[162,8],[183,8],[164,8],[159,8],[173,8],[188,8],[187,8],[191,8],[195,8],[194,8],[198,8],[228,8],[222,8],[223,8],[224,8],[220,8],[219,8],[225,8],[229,8],[230,8],[227,8],[204,8],[205,8],[171,8],[197,8],[200,8],[202,8],[207,8],[210,8],[232,8],[208,8],[[],192],[236,250],[208,251],[236,252],[208,253],[[[13,[236]]],254],[[[13,[236]]],255],[[],43],[240,1],[236,1],[208,1],[[],193],[[],194],[[],195],[[],240],[[],236],[[],208],[249,238],[[]],[[],196],[[[13,[199]]],245],[[[13,[199]]],246],[[],197],[[],198],[[[13,[199]]],243],[[[13,[199]]],244],[[],199],[[],200],[[[13,[167]],14],15],[[[13,[233]],14],15],[[[13,[168]],14],15],[[[13,[181]],14],15],[[[13,[184]],14],15],[[[13,[182]],14],15],[[[13,[190]],14],15],[[[13,[196]],14],15],[[[13,[209]],14],15],[[[13,[243]],14],15],[[[13,[244]],14],15],[[[13,[245]],14],15],[[[13,[246]],14],15],[[[13,[183]],14],15],[[[13,[228]],14],15],[[[13,[226]],14],15],[[[13,[220]],14],15],[[[13,[221]],14],15],[[[13,[225]],14],15],[[[13,[227]],14],15],[[[13,[238]],14],15],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[88]]]]]],[[],201],[[],202],[[13,14],[[15,[16]]]],[[[13,[201]],14],[[15,[16]]]],[[[13,[240]],14],[[15,[16]]]],[[[13,[236]],14],[[15,[16]]]],[[[13,[165]],14],[[15,[16]]]],[[[13,[170]],14],[[15,[16]]]],[[[13,[172]],14],[[15,[16]]]],[[[13,[174]],14],[[15,[16]]]],[[[13,[175]],14],[[15,[16]]]],[[[13,[179]],14],[[15,[16]]]],[[[13,[185]],14],[[15,[16]]]],[[[13,[186]],14],[[15,[16]]]],[[[13,[193]],14],[[15,[16]]]],[[[13,[176]],14],[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[211]],14],[[15,[16]]]],[[[13,[212]],14],[[15,[16]]]],[[[13,[213]],14],[[15,[16]]]],[[[13,[215]],14],[[15,[16]]]],[[[13,[214]],14],[[15,[16]]]],[[[13,[216]],14],[[15,[16]]]],[[[13,[234]],14],[[15,[16]]]],[[[13,[166]],14],[[15,[16]]]],[[[13,[203]],14],[[15,[16]]]],[[[13,[206]],14],[[15,[16]]]],[[[13,[162]],14],[[15,[16]]]],[[[13,[163]],14],[[15,[16]]]],[[[13,[247]],14],[[15,[16]]]],[[[13,[164]],14],[[15,[16]]]],[[[13,[159]],14],[[15,[16]]]],[[[13,[173]],14],[[15,[16]]]],[[[13,[188]],14],[[15,[16]]]],[[[13,[187]],14],[[15,[16]]]],[[[13,[191]],14],[[15,[16]]]],[[[13,[195]],14],[[15,[16]]]],[[[13,[194]],14],[[15,[16]]]],[[[13,[198]],14],[[15,[16]]]],[[[13,[222]],14],[[15,[16]]]],[[[13,[223]],14],[[15,[16]]]],[[[13,[224]],14],[[15,[16]]]],[[[13,[219]],14],[[15,[16]]]],[[[13,[231]],14],[[15,[16]]]],[[[13,[229]],14],[[15,[16]]]],[[[13,[230]],14],[[15,[16]]]],[[[13,[217]],14],[[15,[16]]]],[[[13,[218]],14],[[15,[16]]]],[[[13,[192]],14],[[15,[16]]]],[[[13,[204]],14],[[15,[16]]]],[[[13,[205]],14],[[15,[16]]]],[[[13,[171]],14],[[15,[16]]]],[[[13,[197]],14],[[15,[16]]]],[[[13,[200]],14],[[15,[16]]]],[[[13,[202]],14],[[15,[16]]]],[[[13,[207]],14],[[15,[16]]]],[[[13,[210]],14],[[15,[16]]]],[[[13,[232]],14],[[15,[16]]]],[[[13,[208]],14],[[15,[16]]]],[[[13,[238]],14],[[15,[16]]]],[14,[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[199]],14],[[15,[16]]]],[[[13,[189]],14],[[15,[[12,[1,88]]]]]],[[[13,[172]],14],[[15,[12]]]],[[[13,[174]],14],[[15,[12]]]],[[[13,[175]],14],[[15,[12]]]],[[[13,[179]],14],[[15,[12]]]],[[[13,[185]],14],[[15,[12]]]],[[[13,[186]],14],[[15,[12]]]],[[[13,[193]],14],[[15,[12]]]],[[[13,[176]],14],[[15,[12]]]],[[[13,[199]],14],[[15,[12]]]],[[[13,[211]],14],[[15,[12]]]],[[[13,[212]],14],[[15,[12]]]],[[[13,[213]],14],[[15,[12]]]],[[[13,[215]],14],[[15,[12]]]],[[[13,[214]],14],[[15,[12]]]],[[[13,[216]],14],[[15,[12]]]],[[[13,[166]],14],[[15,[12]]]],[[[13,[203]],14],[[15,[12]]]],[[[13,[206]],14],[[15,[12]]]],[[[13,[162]],14],[[15,[12]]]],[[[13,[163]],14],[[15,[12]]]],[[[13,[248]],14],[[15,[12]]]],[[[13,[159]],14],[[15,[12]]]],[[[13,[173]],14],[[15,[12]]]],[[[13,[188]],14],[[15,[12]]]],[[[13,[187]],14],[[15,[12]]]],[[[13,[191]],14],[[15,[12]]]],[[[13,[195]],14],[[15,[12]]]],[[[13,[194]],14],[[15,[12]]]],[[[13,[198]],14],[[15,[12]]]],[[[13,[222]],14],[[15,[12]]]],[[[13,[223]],14],[[15,[12]]]],[[[13,[224]],14],[[15,[12]]]],[[[13,[219]],14],[[15,[12]]]],[[[13,[229]],14],[[15,[12]]]],[[[13,[230]],14],[[15,[12]]]],[[[13,[217]],14],[[15,[12]]]],[[[13,[218]],14],[[15,[12]]]],[[[13,[189]],14],[[15,[[12,[1,88]]]]]],[17],[17],[17],[240],[236],[208],[240],[240],[1,203],[[],204],[[],205],[[247,248],[[12,[242]]]],[[248,247],[[12,[242]]]],[[],43],[[],206],[[],207],0,[[],208],[[],209],[[],210],[[]],[240],[236],[165],[170],[172],[174],[175],[179],[185],[186],[193],[176],[199],[211],[212],[213],[215],[214],[216],[234],[166],[203],[206],[162],[163],[164],[159],[173],[188],[187],[191],[195],[194],[198],[222],[223],[219],[229],[230],[192],[204],[205],[171],[197],[200],[202],[1,211],[[],212],[[[236,[29]],29],[[12,[30]]]],[[[236,[31]],31],[[12,[30]]]],[[]],[[[13,[172]]],12],[[[13,[174]]],12],[[[13,[175]]],12],[[[13,[179]]],12],[[[13,[185]]],12],[[[13,[186]]],12],[[[13,[193]]],12],[[[13,[176]]],12],[[[13,[199]]],12],[[[13,[211]]],12],[[[13,[212]]],12],[[[13,[213]]],12],[[[13,[215]]],12],[[[13,[214]]],12],[[[13,[216]]],12],[[[13,[166]]],12],[[[13,[203]]],12],[[[13,[206]]],12],[[[13,[162]]],12],[[[13,[163]]],12],[[[13,[248]]],12],[[[13,[159]]],12],[[[13,[173]]],12],[[[13,[188]]],12],[[[13,[187]]],12],[[[13,[191]]],12],[[[13,[195]]],12],[[[13,[194]]],12],[[[13,[198]]],12],[[[13,[222]]],12],[[[13,[223]]],12],[[[13,[224]]],12],[[[13,[219]]],12],[[[13,[229]]],12],[[[13,[230]]],12],[[[13,[217]]],12],[[[13,[218]]],12],[1,213],[214,16],[214,16],[[],214],[[],215],[[],216],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[237,237],[1,217],[1,218],[1,219],[[],220],[[],221],[[],222],[[],223],[[],224],[[],225],[[],226],[[[177,[[16,[1]]]]],227],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],228],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],15],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[[13,14],[[15,[[16,[12]]]]]],[14,[[15,[[16,[12]]]]]],[[],229],[[],230],[[],231],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],232],[[],233],[[],234],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[254,10],[[12,[11]]]],[[252,10],[[12,[11]]]],[[255,10],[[12,[11]]]],[[250,10],[[12,[11]]]],[[256,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[254,16],[252,16],[255,16],[250,16],[256,16],[254],[252],[255],[250],[256],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[251,10],[[12,[11]]]],[[253,10],[[12,[11]]]],[[257,10],[[12,[11]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[251,16],[253,16],[257,16],[[],208],[251],[253],[257],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[258,259],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[15,[[16,[12]]]]],260],[[[15,[12]]],260],[258,258],[15,15],[261,261],[[]],[[]],[[]],[[15,15],103],[259],[[],262],[263,258],[33],[29],[258],[[15,15],8],[[259,259],8],[[261,261],8],[[30,10],[[12,[11]]]],[[30,10],[[12,[11]]]],[[262,10],[[12,[11]]]],[[29,10],[[12,[11]]]],[[31,10],[[12,[11]]]],[[263,10],[[12,[11]]]],[[14,10],[[12,[11]]]],[[258,10],[[12,[11]]]],[[15,10],[[12,[11]]]],[[259,10],[[12,[11]]]],[[261,10],[[12,[11]]]],[[]],[[]],[[[35,[34]]],29],[[[13,[[35,[34]]]]],29],[31,29],[[[35,[33,34]]],29],[[]],[[[13,[[35,[33,34]]]]],29],[[[13,[[35,[34]]]]],31],[[[35,[33,34]]],31],[[[35,[34]]],31],[[]],[[[13,[[35,[33,34]]]]],31],[[]],[[]],[[]],[144,258],[[],15],[[]],[143],[[]],[144,259],[[]],[[],[[15,[12]]]],[[],[[15,[[16,[12]]]]]],[259,258],[[[265,[264]]],15],[[[12,[264]]],[[15,[12]]]],[[[12,[264]]],[[15,[[16,[12]]]]]],[258,14],[15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,31],[[],33],[15,8],[15,8],[30,8],[15,15],[[[15,[[16,[12]]]]],[[15,[[16,[12]]]]]],[[[15,[12]]],[[15,[12]]]],[[[15,[12]]],[[15,[12]]]],[[[15,[[16,[12]]]]],[[15,[[16,[12]]]]]],[[],262],[[],29],[[],31],[258,263],[261,259],[[],261],[[[266,[258]]],263],[[],258],[[],258],[[15,15],[[16,[103]]]],[[[13,[29]],14],15],[[[13,[31]],14],15],[17],[15,265],[[262,258]],[[],30],[[],[[12,[30]]]],[[],[[12,[30]]]],[29,[[12,[30]]]],[[],[[12,[59,30]]]],[31,[[12,[30]]]],[[],[[12,[59,30]]]],[[],[[12,[30]]]],[[],[[12,[30]]]],[262,[[16,[258]]]],[[]],[[]],[[]],[[],18],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[13,14],15],[[13,14],15],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[259,261],[144],[262],[258],[144],[258],[144,258],[14,258],[144,263],[[258,258],8],0],"p":[[15,"usize"],[3,"Sender"],[3,"UnboundedSender"],[3,"SendError"],[3,"TrySendError"],[3,"Receiver"],[3,"UnboundedReceiver"],[15,"bool"],[3,"TryRecvError"],[3,"Formatter"],[3,"Error"],[4,"Result"],[3,"Pin"],[3,"Context"],[4,"Poll"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Sender"],[3,"Cancellation"],[3,"Canceled"],[3,"Receiver"],[3,"BlockingStream"],[3,"LocalSpawner"],[3,"LocalPool"],[3,"Enter"],[3,"EnterError"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"AndThen"],[8,"Future"],[3,"Global"],[3,"Box"],[3,"CatchUnwind"],[3,"Shared"],[3,"WeakShared"],[3,"Pending"],[3,"OptionFuture"],[3,"PollImmediate"],[3,"Ready"],[4,"Either"],[3,"ErrInto"],[3,"Flatten"],[3,"FlattenSink"],[3,"FlattenStream"],[3,"PollFn"],[3,"JoinAll"],[3,"TryJoinAll"],[3,"Fuse"],[3,"Map"],[3,"IntoStream"],[3,"MapInto"],[3,"Then"],[3,"Inspect"],[3,"NeverError"],[3,"UnitError"],[3,"RemoteHandle"],[3,"Remote"],[3,"IntoFuture"],[3,"TryFlatten"],[3,"TryFlattenStream"],[3,"OrElse"],[3,"OkInto"],[3,"InspectOk"],[3,"InspectErr"],[3,"MapOk"],[3,"MapErr"],[3,"MapOkOrElse"],[3,"UnwrapOrElse"],[3,"Lazy"],[4,"MaybeDone"],[4,"TryMaybeDone"],[3,"Join"],[3,"Join3"],[3,"Join4"],[3,"Join5"],[3,"Select"],[3,"SelectAll"],[3,"TryJoin"],[3,"TryJoin3"],[3,"TryJoin4"],[3,"TryJoin5"],[3,"TrySelect"],[3,"SelectOk"],[3,"Vec"],[3,"Error"],[4,"SeekFrom"],[15,"u64"],[3,"IoSliceMut"],[3,"IoSlice"],[3,"Window"],[3,"BufReader"],[3,"BufWriter"],[3,"LineWriter"],[8,"Error"],[3,"Chain"],[4,"ErrorKind"],[3,"AllowStdIo"],[3,"Cursor"],[3,"Close"],[4,"Ordering"],[3,"Empty"],[3,"Take"],[3,"Copy"],[3,"CopyBuf"],[15,"str"],[3,"FillBuf"],[3,"Flush"],[3,"Repeat"],[3,"Sink"],[3,"ReuniteError"],[3,"SeeKRelative"],[3,"CopyBufAbortable"],[3,"IntoSink"],[3,"Lines"],[3,"Read"],[3,"ReadVectored"],[3,"ReadExact"],[3,"ReadLine"],[3,"ReadToEnd"],[3,"ReadToString"],[3,"ReadUntil"],[3,"Seek"],[3,"ReadHalf"],[3,"WriteHalf"],[3,"Write"],[3,"WriteVectored"],[3,"WriteAll"],[3,"NulError"],[3,"IntoInnerError"],[15,"i32"],[15,"i64"],[15,"u8"],[3,"Arguments"],[3,"Mutex"],[3,"OwnedMutexGuard"],[3,"MutexGuard"],[3,"MappedMutexGuard"],[3,"OwnedMutexLockFuture"],[3,"MutexLockFuture"],[15,"never"],[3,"Arc"],[3,"Buffer"],[3,"Close"],[3,"Drain"],[3,"Fanout"],[3,"Feed"],[3,"Flush"],[3,"Send"],[3,"SendAll"],[3,"SinkErrInto"],[3,"SinkMapErr"],[3,"Unfold"],[3,"With"],[3,"WithFlatMap"],[3,"All"],[3,"AndThen"],[3,"Any"],[8,"Stream"],[3,"BufferUnordered"],[3,"Buffered"],[3,"CatchUnwind"],[3,"Chain"],[3,"Chunks"],[3,"Collect"],[3,"Concat"],[3,"Count"],[3,"Cycle"],[3,"Empty"],[3,"Enumerate"],[3,"ErrInto"],[3,"Filter"],[3,"FilterMap"],[3,"FlatMap"],[8,"Into"],[3,"FlatMapUnordered"],[3,"Flatten"],[3,"FlattenUnordered"],[3,"Fold"],[3,"ForEach"],[3,"ForEachConcurrent"],[3,"Forward"],[3,"Fuse"],[3,"Inspect"],[3,"InspectErr"],[3,"InspectOk"],[3,"IntoAsyncRead"],[3,"StreamFuture"],[3,"IntoStream"],[3,"Iter"],[3,"Map"],[3,"MapErr"],[3,"MapOk"],[3,"Next"],[3,"Once"],[3,"OrElse"],[3,"Peekable"],[3,"Pending"],[3,"PollFn"],[3,"PollImmediate"],[3,"ReadyChunks"],[3,"Repeat"],[3,"RepeatWith"],[3,"Scan"],[3,"Select"],[3,"SelectAll"],[3,"SelectNextSome"],[3,"SelectWithStrategy"],[3,"Skip"],[3,"SkipWhile"],[3,"Take"],[3,"TakeUntil"],[3,"TakeWhile"],[3,"Then"],[3,"TryBufferUnordered"],[3,"TryBuffered"],[3,"TryChunks"],[3,"TryCollect"],[3,"TryConcat"],[3,"TryFilter"],[3,"TryFilterMap"],[3,"TryFlatten"],[3,"TryFold"],[3,"TryForEach"],[3,"TryForEachConcurrent"],[3,"TryNext"],[3,"TrySkipWhile"],[3,"TryTakeWhile"],[3,"TryUnfold"],[3,"Unfold"],[3,"Unzip"],[3,"Zip"],[3,"AbortHandle"],[3,"FuturesUnordered"],[4,"PollNext"],[3,"Abortable"],[3,"Aborted"],[3,"FuturesOrdered"],[3,"TryChunksError"],[3,"ReuniteError"],[3,"Peek"],[3,"PeekMut"],[3,"NextIf"],[3,"NextIfEq"],[3,"SplitStream"],[3,"SplitSink"],[3,"AbortRegistration"],[3,"Iter"],[3,"Iter"],[3,"IterMut"],[3,"IterMut"],[3,"IterPinMut"],[3,"IterPinRef"],[3,"IntoIter"],[3,"IntoIter"],[3,"Waker"],[3,"RawWaker"],[4,"ControlFlow"],[3,"RawWakerVTable"],[3,"AtomicWaker"],[3,"WakerRef"],[4,"Infallible"],[3,"Ready"],[3,"ManuallyDrop"],[8,"TryFuture"],[8,"TryFutureExt"],[8,"FutureExt"],[8,"UnsafeFutureObj"],[8,"FusedFuture"],[13,"Left"],[13,"Right"],[13,"Future"],[13,"Done"],[13,"Future"],[13,"Done"],[8,"AsyncReadExt"],[8,"AsyncWriteExt"],[8,"AsyncBufRead"],[8,"AsyncBufReadExt"],[8,"AsyncWrite"],[8,"AsyncRead"],[8,"AsyncSeek"],[8,"AsyncSeekExt"],[13,"Start"],[13,"End"],[13,"Current"],[8,"Sink"],[8,"TryStream"],[8,"SinkExt"],[8,"StreamExt"],[8,"TryStreamExt"],[8,"FusedStream"],[8,"SpawnExt"],[8,"LocalSpawnExt"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"],[13,"Ready"]],"a":{"errno":[1521],"getlasterror":[1521]}},\ -"futures_channel":{"doc":"Asynchronous channels.","t":[0,0,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,3,3,3,3,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["mpsc","oneshot","Receiver","SendError","Sender","TryRecvError","TrySendError","UnboundedReceiver","UnboundedSender","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","close_channel","close_channel","disconnect","disconnect","drop","drop","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","hash_receiver","hash_receiver","into","into","into","into","into","into","into","into_inner","into_send_error","is_closed","is_closed","is_connected_to","is_connected_to","is_disconnected","is_disconnected","is_full","is_full","is_terminated","is_terminated","poll_close","poll_close","poll_close","poll_flush","poll_flush","poll_flush","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","provide","provide","provide","same_receiver","same_receiver","start_send","start_send","start_send","start_send","start_send","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll_next","try_poll_next","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded","unbounded_send","Canceled","Cancellation","Receiver","Sender","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancellation","channel","clone","clone_into","close","drop","drop","eq","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","into_future","into_future","is_canceled","is_connected_to","is_terminated","poll","poll","poll_canceled","provide","send","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_poll","try_recv","type_id","type_id","type_id","type_id"],"q":["futures_channel","","futures_channel::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_channel::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A multi-producer, single-consumer queue for sending values …","A channel for sending a single message between …","The receiving end of a bounded mpsc channel.","The error type for Senders used as Sinks.","The transmission end of a bounded mpsc channel.","The error type returned from try_next.","The error type returned from try_send.","The receiving end of an unbounded mpsc channel.","The transmission end of an unbounded mpsc channel.","","","","","","","","","","","","","","","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel, without dropping …","Closes the receiving half of a channel, without dropping …","Closes this channel from the sender side, preventing any …","Closes this channel from the sender side, preventing any …","Disconnects this sender from the channel, closing it if …","Disconnects this sender from the channel, closing it if …","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Hashes the receiver into the provided hasher","Hashes the receiver into the provided hasher","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the message that was attempted to be sent but …","Drops the message and converts into a SendError.","Returns whether this channel is closed without needing a …","Returns whether this channel is closed without needing a …","Returns whether the sender send to this receiver.","Returns whether the sender send to this receiver.","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the receiver …","Returns true if this error is a result of the channel …","Returns true if this error is a result of the channel …","","","","","","","","","","","","Polls the channel to determine if there is guaranteed …","","","Check if the channel is ready to receive a message.","","","","Returns whether the senders send to the same receiver.","Returns whether the senders send to the same receiver.","Send a message on the channel.","","Send a message on the channel.","","","","","","","","","","","","","","","","","","","","","","","","Tries to receive the next message without notifying a …","Tries to receive the next message without notifying a …","","","Attempts to send a message on this Sender, returning the …","","","","","","","","Creates an unbounded mpsc channel for communicating …","Sends a message along this channel.","Error returned from a Receiver when the corresponding …","A future that resolves when the receiving end of a channel …","A future for a value that will be provided by another …","A means of transmitting a single value to another task.","","","","","","","","","Creates a future that resolves when this Sender’s …","Creates a new one-shot channel for sending a single value …","","","Gracefully close this receiver, preventing any subsequent …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Tests to see whether this Sender’s corresponding Receiver","Tests to see whether this Sender is connected to the given …","","","","Polls this Sender half to detect whether its associated …","","Completes this oneshot with a successful result.","","","","","","","","","","","","Attempts to receive a message outside of the context of a …","","","",""],"i":[0,0,0,0,0,0,0,0,0,11,2,3,7,8,4,6,11,2,3,7,8,4,6,0,2,3,4,6,2,3,4,6,7,8,2,3,2,3,7,8,4,6,11,11,2,3,7,8,4,4,6,6,11,2,3,7,8,4,6,2,3,11,2,3,7,8,4,6,6,6,2,3,2,3,4,6,4,6,7,8,2,3,3,2,3,3,7,8,2,2,3,3,3,11,4,6,2,3,2,2,3,3,3,2,3,4,6,11,4,6,11,2,3,7,8,4,6,11,2,3,7,8,4,6,7,8,7,8,2,11,2,3,7,8,4,6,0,3,0,0,0,0,26,23,24,25,26,23,24,25,23,0,25,25,26,26,23,25,26,23,24,25,25,26,23,24,25,26,23,24,25,26,24,23,23,26,26,24,23,25,23,25,25,26,23,24,25,26,23,24,25,26,26,26,23,24,25],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[[[6,[5]]],[[6,[5]]]],[[]],[[]],[[]],[[]],[7],[8],[2],[3],[2],[3],[7],[8],[[4,4],9],[[[6,[10]],6],9],[[11,12],13],[[11,12],13],[[[2,[14]],12],13],[[[3,[14]],12],13],[[[7,[14]],12],13],[[[8,[14]],12],13],[[4,12],13],[[4,12],13],[[6,12],13],[[6,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2],[3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[6],[6,4],[2,9],[3,9],[[2,7],9],[[3,8],9],[4,9],[6,9],[4,9],[6,9],[7,9],[8,9],[[[15,[2]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[2]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[7]],16],[[18,[19]]]],[[[15,[8]],16],[[18,[19]]]],[[[15,[2]],16],[[18,[17]]]],[[2,16],[[18,[[17,[4]]]]]],[[[15,[3]],16],[[18,[17]]]],[[[15,[3]],16],[[18,[17]]]],[[3,16],[[18,[[17,[4]]]]]],[20],[20],[20],[[2,2],9],[[3,3],9],[2,[[17,[4]]]],[[[15,[2]]],17],[3,[[17,[4]]]],[[[15,[3]]],17],[[[15,[3]]],17],[[]],[[]],[[]],[[]],[[],21],[[],21],[[],21],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[7,[[17,[19,11]]]],[8,[[17,[19,11]]]],[[15,16],[[18,[[19,[17]]]]]],[[15,16],[[18,[[19,[17]]]]]],[2,[[17,[6]]]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[]],[3,[[17,[6]]]],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[23,24],[[]],[25,25],[[]],[26],[26],[23],[[25,25],9],[[[26,[14]],12],13],[[[23,[14]],12],13],[[[24,[14]],12],13],[[25,12],13],[[25,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[23,9],[[23,26],9],[26,9],[[[15,[26]],16],[[18,[[17,[25]]]]]],[[[15,[24]],16],18],[[23,16],18],[20],[23,17],[[]],[[],21],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[15,16],18],[26,[[17,[19,25]]]],[[],22],[[],22],[[],22],[[],22]],"p":[[15,"usize"],[3,"Sender"],[3,"UnboundedSender"],[3,"SendError"],[8,"Clone"],[3,"TrySendError"],[3,"Receiver"],[3,"UnboundedReceiver"],[15,"bool"],[8,"PartialEq"],[3,"TryRecvError"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[3,"Sender"],[3,"Cancellation"],[3,"Canceled"],[3,"Receiver"]]},\ -"futures_core":{"doc":"Core traits and types for asynchronous operations in Rust.","t":[2,2,2,2,2,2,0,14,0,0,6,16,8,2,6,16,8,10,10,6,16,8,16,6,16,8,8,10,10,11,10,2,2,2,2,2],"n":["FusedFuture","FusedStream","Future","Stream","TryFuture","TryStream","future","ready","stream","task","BoxFuture","Error","FusedFuture","Future","LocalBoxFuture","Ok","TryFuture","is_terminated","try_poll","BoxStream","Error","FusedStream","Item","LocalBoxStream","Ok","Stream","TryStream","is_terminated","poll_next","size_hint","try_poll_next","Context","Poll","RawWaker","RawWakerVTable","Waker"],"q":["futures_core","","","","","","","","","","futures_core::future","","","","","","","","","futures_core::stream","","","","","","","","","","","","futures_core::task","","","",""],"d":["","","","","","","Futures.","Extracts the successful type of a Poll<T>.","Asynchronous streams.","Task notification.","An owned dynamically typed Future for use in cases where …","The type of failures yielded by this future","A future which tracks whether or not the underlying future …","","BoxFuture, but without the Send requirement.","The type of successful values yielded by this future","A convenience for futures that return Result values that …","Returns true if the underlying future should no longer be …","Poll this TryFuture as if it were a Future.","An owned dynamically typed Stream for use in cases where …","The type of failures yielded by this future","A stream which tracks whether or not the underlying stream …","Values yielded by the stream.","BoxStream, but without the Send requirement.","The type of successful values yielded by this future","A stream of values produced asynchronously.","A convenience for streams that return Result values that …","Returns true if the stream should no longer be polled.","Attempt to pull out the next value of this stream, …","Returns the bounds on the remaining length of the stream.","Poll this TryStream as if it were a Stream.","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,7,0,8,7,0,9,0,10,0,9,0,0,11,10,10,9,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[2,3],[[5,[4]]]],0,0,0,0,0,0,0,0,[[],1],[[2,3],[[5,[6]]]],[[]],[[2,3],[[5,[[6,[4]]]]]],0,0,0,0,0],"p":[[15,"bool"],[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[4,"Option"],[8,"TryFuture"],[8,"FusedFuture"],[8,"TryStream"],[8,"Stream"],[8,"FusedStream"]]},\ -"futures_executor":{"doc":"Built-in executors and related tools.","t":[3,3,3,3,3,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["BlockingStream","Enter","EnterError","LocalPool","LocalSpawner","block_on","block_on_stream","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","default","deref","deref_mut","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_inner","into_iter","new","next","provide","run","run_until","run_until_stalled","size_hint","spawn_local_obj","spawn_obj","spawner","status","status_local","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_run_one","type_id","type_id","type_id","type_id","type_id"],"q":["futures_executor","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An iterator which blocks on values from a stream until …","Represents an executor context.","An error returned by enter if an execution scope has …","A single-threaded task pool for polling futures to …","A handle to a LocalPool that implements Spawn.","Run a future to completion on the current thread.","Turn a stream into a blocking iterator.","","","","","","","","","","","","","","","","","Marks the current thread as being within the dynamic …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this BlockingStream into the inner Stream type.","","Create a new, empty pool of tasks.","","","Run all tasks in the pool to completion.","Runs all the tasks in the pool until the given future …","Runs all tasks in the pool and returns if no more progress …","","","","Get a clonable handle to the pool as a Spawn.","","","","","","","","","","","","","","","Runs all tasks and returns after completing one future or …","","","","",""],"i":[0,0,0,0,0,0,0,7,8,6,5,4,7,8,6,5,4,5,5,6,4,4,7,0,7,8,8,6,5,4,7,8,6,5,4,7,8,6,5,4,4,4,6,4,8,6,6,6,4,5,5,6,5,5,5,8,7,8,6,5,4,7,8,6,5,4,6,7,8,6,5,4],"f":[0,0,0,0,0,[1],[[[0,[2,3]]],[[4,[[0,[2,3]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,5],[[]],[[],6],[[[4,[[0,[2,3]]]]]],[[[4,[[0,[2,3]]]]]],[7],[[],[[9,[7,8]]]],[[7,10],11],[[8,10],11],[[8,10],11],[[6,10],11],[[5,10],11],[[[4,[[0,[12,2,3]]]],10],11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[4,[[0,[2,3]]]]],[[0,[2,3]]]],[[]],[[],6],[[[4,[[0,[2,3]]]]],13],[14],[6],[[6,1]],[6],[[[4,[[0,[2,3]]]]]],[[5,15],[[9,[16]]]],[[5,17],[[9,[16]]]],[6,5],[5,[[9,[16]]]],[5,[[9,[16]]]],[[]],[[],18],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[6,19],[[],20],[[],20],[[],20],[[],20],[[],20]],"p":[[8,"Future"],[8,"Stream"],[8,"Unpin"],[3,"BlockingStream"],[3,"LocalSpawner"],[3,"LocalPool"],[3,"Enter"],[3,"EnterError"],[4,"Result"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[4,"Option"],[3,"Demand"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"String"],[15,"bool"],[3,"TypeId"]]},\ -"futures_io":{"doc":"Asynchronous I/O","t":[8,8,8,8,2,2,2,2,2,2,10,10,10,10,10,11,11,10,10,11,11],"n":["AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","ErrorKind","IoSlice","IoSliceMut","Result","SeekFrom","consume","poll_close","poll_fill_buf","poll_flush","poll_read","poll_read_vectored","poll_read_vectored","poll_seek","poll_write","poll_write_vectored","poll_write_vectored"],"q":["futures_io","","","","","","","","","","","","","","","","","","","",""],"d":["Read bytes asynchronously.","Read bytes asynchronously.","Seek bytes asynchronously.","Write bytes asynchronously.","","","","","","","Tells this buffer that amt bytes have been consumed from …","Attempt to close the object.","Attempt to return the contents of the internal buffer, …","Attempt to flush the object, ensuring that any buffered …","Attempt to read from the AsyncRead into buf.","Attempt to read from the AsyncRead into bufs using vectored","Attempt to read from the AsyncRead into bufs using vectored","Attempt to seek to an offset, in bytes, in a stream.","Attempt to write bytes from buf into the object.","Attempt to write bytes from bufs into the object using …","Attempt to write bytes from bufs into the object using …"],"i":[0,0,0,0,0,0,0,0,0,0,8,9,8,9,10,10,10,11,9,9,9],"f":[0,0,0,0,0,0,0,0,0,0,[[1,2]],[[1,3],[[5,[4]]]],[[1,3],[[5,[4]]]],[[1,3],[[5,[4]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3,6],[[5,[[4,[7]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]],[[1,3],[[5,[[4,[2]]]]]]],"p":[[3,"Pin"],[15,"usize"],[3,"Context"],[6,"Result"],[4,"Poll"],[4,"SeekFrom"],[15,"u64"],[8,"AsyncBufRead"],[8,"AsyncWrite"],[8,"AsyncRead"],[8,"AsyncSeek"]]},\ -"futures_macro":{"doc":"The futures-rs procedural macro implementations.","t":[14,14,14,14,23,14],"n":["join_internal","select_biased_internal","select_internal","stream_select_internal","test_internal","try_join_internal"],"q":["futures_macro","","","","",""],"d":["The join! macro.","The select_biased! macro.","The select! macro.","The stream_select! macro.","","The try_join! macro."],"i":[0,0,0,0,0,0],"f":[0,0,0,0,0,0],"p":[]},\ -"futures_sink":{"doc":"Asynchronous sinks","t":[16,8,10,10,10,10],"n":["Error","Sink","poll_close","poll_flush","poll_ready","start_send"],"q":["futures_sink","","","","",""],"d":["The type of value produced by the sink when an error …","A Sink is a value into which other values can be sent, …","Flush any remaining output and close this sink, if …","Flush any remaining output from this sink.","Attempts to prepare the Sink to receive a value.","Begin the process of sending a value to the sink. Each …"],"i":[5,0,5,5,5,5],"f":[0,0,[[1,2],[[4,[3]]]],[[1,2],[[4,[3]]]],[[1,2],[[4,[3]]]],[1,3]],"p":[[3,"Pin"],[3,"Context"],[4,"Result"],[4,"Poll"],[8,"Sink"]]},\ -"futures_task":{"doc":"Tools for working with tasks.","t":[8,2,3,3,8,2,2,2,8,3,8,2,3,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,5,5,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5],"n":["ArcWake","Context","FutureObj","LocalFutureObj","LocalSpawn","Poll","RawWaker","RawWakerVTable","Spawn","SpawnError","UnsafeFutureObj","Waker","WakerRef","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","deref","drop","drop","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_shutdown","new","new","new","new_unowned","noop_waker","noop_waker_ref","poll","poll","provide","shutdown","spawn_local_obj","spawn_obj","status","status","status_local","status_local","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","wake","wake","wake_by_ref","waker","waker_ref"],"q":["futures_task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A way of waking up a specific task.","","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","","","","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","A custom implementation of a future trait object for …","","A Waker that is only valid for a given lifetime.","","","","","","","","","","Drops the future represented by the given fat pointer.","","","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Check whether spawning failed to the executor being shut …","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","Spawning failed because the executor has been shut down.","Spawns a future that will be run to completion.","Spawns a future that will be run to completion.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","","","","","","","","","","","","","","Indicates that the associated task is ready to make …","Indicates that the associated task is ready to make …","Indicates that the associated task is ready to make …","Creates a Waker from an Arc<impl ArcWake>.","Creates a reference to a Waker from a reference to …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,5,4,8,1,5,4,8,1,1,13,4,5,5,4,8,1,5,4,4,4,4,4,4,8,8,8,8,8,1,5,4,8,1,4,8,4,13,5,4,8,1,1,0,0,4,8,5,5,22,23,23,23,22,22,5,5,4,8,1,5,4,8,1,5,4,8,1,24,24,24,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,2],[3],[4],[[5,6],7],[[5,6],7],[[4,6],7],[[8,6],7],[[1,6],7],[[]],[8,4],[[[10,[[9,[3]]]]],4],[[]],[[[9,[3]]],4],[[[9,[3]]],4],[[[10,[[9,[3]]]]],4],[[[10,[[9,[[0,[3,11]]]]]]],8],[[[9,[3]]],8],[[[9,[[0,[3,11]]]]],8],[[[10,[[9,[3]]]]],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,8],[[],3],[5,12],[13,4],[[[0,[13,11]]],8],[2,1],[[[14,[2]]],1],[[],2],[[],2],[[[10,[4]],15],16],[[[10,[8]],15],16],[17],[[],5],[4,[[18,[5]]]],[8,[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],[[18,[5]]]],[[],19],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],20],[[],20],[[],20],[[],20],[21],[21],[21],[21,2],[21,1]],"p":[[3,"WakerRef"],[3,"Waker"],[8,"Future"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"Formatter"],[6,"Result"],[3,"FutureObj"],[3,"Box"],[3,"Pin"],[8,"Send"],[15,"bool"],[8,"UnsafeFutureObj"],[3,"ManuallyDrop"],[3,"Context"],[4,"Poll"],[3,"Demand"],[4,"Result"],[3,"String"],[3,"TypeId"],[3,"Arc"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"]]},\ -"futures_util":{"doc":"Combinators and utilities for working with Futures, Stream…","t":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,14,0,0,14,14,14,14,14,14,0,0,14,0,14,3,3,3,3,3,6,3,13,13,4,3,16,3,3,3,3,8,2,13,13,8,3,13,13,3,3,3,3,3,3,3,3,3,3,3,13,6,3,3,3,3,3,3,4,3,16,3,3,3,3,3,3,3,3,3,13,3,3,3,3,3,3,3,8,8,3,3,3,3,3,4,3,3,8,3,3,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,3,8,8,8,8,8,8,8,8,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,2,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,5,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,3,3,3,16,3,3,3,3,3,8,3,8,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,3,3,3,3,3,6,3,3,3,3,3,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,16,3,13,6,3,3,3,3,3,3,16,3,3,3,3,3,3,3,3,4,3,3,3,3,13,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,8,3,3,3,3,3,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,5,0,5,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,8,3,2,3,3,8,8,2,2,2,8,3,8,8,2,3,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5],"n":["AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","Future","FutureExt","Sink","SinkExt","Stream","StreamExt","TryFuture","TryFutureExt","TryStream","TryStreamExt","future","io","join","lock","never","pending","pin_mut","poll","ready","select","select_biased","sink","stream","stream_select","task","try_join","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxFuture","CatchUnwind","Done","Done","Either","ErrInto","Error","Flatten","FlattenSink","FlattenStream","Fuse","FusedFuture","Future","Future","Future","FutureExt","FutureObj","Gone","Gone","Inspect","InspectErr","InspectOk","IntoFuture","IntoStream","Join","Join3","Join4","Join5","JoinAll","Lazy","Left","LocalBoxFuture","LocalFutureObj","Map","MapErr","MapInto","MapOk","MapOkOrElse","MaybeDone","NeverError","Ok","OkInto","OptionFuture","OrElse","Pending","PollFn","PollImmediate","Ready","Remote","RemoteHandle","Right","Select","SelectAll","SelectOk","Shared","Then","TryFlatten","TryFlattenStream","TryFuture","TryFutureExt","TryJoin","TryJoin3","TryJoin4","TryJoin5","TryJoinAll","TryMaybeDone","TrySelect","UnitError","UnsafeFutureObj","UnwrapOrElse","WeakShared","abort","abortable","and_then","and_then","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed","boxed_local","boxed_local","catch_unwind","catch_unwind","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","consume","default","downgrade","drop","drop","eq","err","err_into","err_into","factor_first","factor_second","flatten","flatten","flatten_sink","flatten_sink","flatten_stream","flatten_stream","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","from_iter","fuse","fuse","inspect","inspect","inspect_err","inspect_err","inspect_ok","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_raw","into_stream","into_stream","is_aborted","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","join","join3","join4","join5","join_all","lazy","left_future","left_future","map","map","map_err","map_err","map_into","map_into","map_ok","map_ok","map_ok_or_else","map_ok_or_else","maybe_done","never_error","never_error","new","new_pair","now_or_never","now_or_never","ok","ok_into","ok_into","or_else","or_else","output_mut","output_mut","peek","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_read","poll_read_vectored","poll_ready","poll_ready","poll_ready","poll_ready","poll_seek","poll_unpin","poll_unpin","poll_write","poll_write_vectored","provide","ready","remote_handle","remote_handle","right_future","right_future","select","select_all","select_ok","shared","shared","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","strong_count","take_output","take_output","terminated","then","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_flatten","try_flatten","try_flatten_stream","try_flatten_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_join","try_join3","try_join4","try_join5","try_join_all","try_maybe_done","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_unpin","try_poll_unpin","try_select","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unit_error","unit_error","unwrap_or_else","unwrap_or_else","upgrade","weak_count","0","0","0","0","0","0","0","1","AllowStdIo","AsyncBufRead","AsyncBufReadExt","AsyncRead","AsyncReadExt","AsyncSeek","AsyncSeekExt","AsyncWrite","AsyncWriteExt","BufReader","BufWriter","Chain","Close","Copy","CopyBuf","CopyBufAbortable","Cursor","Empty","Error","ErrorKind","FillBuf","Flush","IntoSink","IoSlice","IoSliceMut","LineWriter","Lines","Read","ReadExact","ReadHalf","ReadLine","ReadToEnd","ReadToString","ReadUntil","ReadVectored","Repeat","Result","ReuniteError","SeeKRelative","Seek","SeekFrom","Sink","Take","Window","Write","WriteAll","WriteHalf","WriteVectored","as_mut","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","buffer","buffer","chain","clone","clone","clone_into","clone_into","close","cmp","consume","consume","consume","consume","consume","consume","consume","consume","consume","consume_unpin","copy","copy_buf","copy_buf_abortable","default","empty","end","eq","fill_buf","fill_buf","flush","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_sink","limit","lines","new","new","new","new","new","new","partial_cmp","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_next","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_read_vectored","poll_ready","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek","poll_seek_relative","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","poll_write_vectored","position","provide","read","read","read_exact","read_exact","read_line","read_to_end","read_to_end","read_to_string","read_to_string","read_until","read_vectored","read_vectored","repeat","reunite","reunite","seek","seek","seek_relative","set","set_limit","set_position","sink","split","start","start_send","stream_position","take","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","with_capacity","with_capacity","with_capacity","write","write","write_all","write_all","write_fmt","write_vectored","write_vectored","MappedMutexGuard","Mutex","MutexGuard","MutexLockFuture","OwnedMutexGuard","OwnedMutexLockFuture","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","deref","deref","deref_mut","deref_mut","deref_mut","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","get_mut","into","into","into","into","into","into","into_future","into_future","into_inner","is_terminated","is_terminated","lock","lock_owned","map","map","new","poll","poll","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","type_id","type_id","type_id","type_id","type_id","type_id","Never","Buffer","Close","Drain","Error","Fanout","Feed","Flush","Send","SendAll","Sink","SinkErrInto","SinkExt","SinkMapErr","Unfold","With","WithFlatMap","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","buffer","clone","clone","clone","clone_into","clone_into","clone_into","close","drain","fanout","feed","flush","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","into","into","into","into","into","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","is_terminated","is_terminated","is_terminated","is_terminated","left_sink","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close_unpin","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush_unpin","poll_next","poll_next","poll_next","poll_next","poll_next","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready_unpin","right_sink","send","send_all","sink_err_into","sink_map_err","size_hint","size_hint","size_hint","size_hint","size_hint","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send_unpin","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","with","with_flat_map","0","0","1","1","AbortHandle","AbortRegistration","Abortable","Aborted","AndThen","BoxStream","BufferUnordered","Buffered","CatchUnwind","Chain","Chunks","Collect","Concat","Cycle","Empty","Enumerate","ErrInto","Error","Filter","FilterMap","FlatMap","Flatten","Fold","ForEach","ForEachConcurrent","Forward","Fuse","FusedStream","FuturesOrdered","FuturesUnordered","Inspect","InspectErr","InspectOk","IntoAsyncRead","IntoStream","Item","Iter","Left","LocalBoxStream","Map","MapErr","MapOk","Next","NextIf","NextIfEq","Ok","Once","OrElse","Peek","PeekMut","Peekable","Pending","PollFn","PollImmediate","PollNext","ReadyChunks","Repeat","RepeatWith","ReuniteError","Right","Scan","Select","SelectAll","SelectNextSome","SelectWithStrategy","Skip","SkipWhile","SplitSink","SplitStream","Stream","StreamExt","StreamFuture","Take","TakeUntil","TakeWhile","Then","TryBufferUnordered","TryBuffered","TryChunks","TryChunksError","TryCollect","TryConcat","TryFilter","TryFilterMap","TryFlatten","TryFold","TryForEach","TryForEachConcurrent","TryNext","TrySkipWhile","TryStream","TryStreamExt","TryTakeWhile","TryUnfold","Unfold","Unzip","Zip","abortable","all","all","and_then","and_then","any","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","boxed","boxed","boxed_local","boxed_local","buffer_unordered","buffer_unordered","buffered","buffered","by_ref","by_ref","catch_unwind","catch_unwind","chain","chain","chunks","chunks","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","collect","collect","concat","concat","consume","count","count","cycle","cycle","default","default","default","default","drop","empty","enumerate","enumerate","eq","eq","err_into","err_into","extend","extend","extend","filter","filter","filter_map","filter_map","flat_map","flat_map","flat_map_unordered","flat_map_unordered","flatten","flatten","flatten_unordered","flatten_unordered","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fold","fold","for_each","for_each","for_each_concurrent","for_each_concurrent","forward","forward","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_iter","fuse","fuse","futures_unordered","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_pin_mut","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","get_ref","hash","inspect","inspect","inspect_err","inspect_err","inspect_ok","inspect_ok","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_async_read","into_async_read","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_future","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_stream","into_stream","is_done","is_empty","is_stopped","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","is_terminated","iter","left_stream","left_stream","len","map","map","map_err","map_err","map_ok","map_ok","new","next","next","next_if","next_if_eq","once","or_else","or_else","peek","peek_mut","peekable","peekable","pending","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_close","poll_fill_buf","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_flush","poll_fn","poll_immediate","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next","poll_next_unpin","poll_next_unpin","poll_peek","poll_peek_mut","poll_read","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_ready","poll_write","provide","provide","push","push_back","push_front","ready_chunks","ready_chunks","repeat","repeat_with","reunite","reunite","right_stream","right_stream","scan","scan","select","select_all","select_all","select_next_some","select_next_some","select_with_strategy","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","skip","skip","skip_while","skip_while","spawn_local_obj","spawn_obj","split","split","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","start_send","take","take","take_future","take_result","take_until","take_until","take_while","take_while","then","then","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","toggle","try_buffer_unordered","try_buffer_unordered","try_buffered","try_buffered","try_chunks","try_chunks","try_collect","try_collect","try_concat","try_concat","try_filter","try_filter","try_filter_map","try_filter_map","try_flatten","try_flatten","try_fold","try_fold","try_for_each","try_for_each","try_for_each_concurrent","try_for_each_concurrent","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_next","try_next","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next","try_poll_next_unpin","try_poll_next_unpin","try_skip_while","try_skip_while","try_take_while","try_take_while","try_unfold","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unfold","unzip","unzip","zip","zip","FuturesUnordered","IntoIter","Iter","IterMut","IterPinMut","IterPinRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","iter_pin_mut","iter_pin_ref","len","new","next","next","next","next","next","push","size_hint","size_hint","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","IntoIter","Iter","IterMut","SelectAll","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clear","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","len","new","next","next","next","push","select_all","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","ArcWake","AtomicWaker","Context","FutureObj","LocalFutureObj","LocalSpawn","LocalSpawnExt","Poll","RawWaker","RawWakerVTable","Spawn","SpawnError","SpawnExt","UnsafeFutureObj","Waker","WakerRef","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","deref","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into_future","into_future","into_future_obj","into_raw","is_shutdown","new","new","new","new","new_unowned","noop_waker","noop_waker_ref","poll","poll","provide","register","shutdown","spawn","spawn","spawn_local","spawn_local","spawn_local_obj","spawn_local_with_handle","spawn_local_with_handle","spawn_obj","spawn_with_handle","spawn_with_handle","status","status_local","take","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_poll","try_poll","type_id","type_id","type_id","type_id","type_id","wake","wake","wake_by_ref","waker","waker_ref"],"q":["futures_util","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::future","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::future::Either","","futures_util::future::MaybeDone","","futures_util::future::TryMaybeDone","","futures_util::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::lock","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::never","futures_util::sink","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream::futures_unordered","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::stream::select_all","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","futures_util::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","","","","","","","","","Asynchronous values.","Asynchronous I/O.","Polls multiple futures simultaneously, returning a tuple …","Futures-powered synchronization primitives.","This module contains the Never type.","A macro which yields to the event loop once.","Pins a value on the stack.","A macro which returns the result of polling a future once …","Extracts the successful type of a Poll<T>.","Polls multiple futures and streams simultaneously, …","Polls multiple futures and streams simultaneously, …","Asynchronous sinks.","Asynchronous streams.","Combines several streams, all producing the same Item …","Tools for working with tasks.","Polls multiple futures simultaneously, resolving to a …","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Future for the and_then method.","An owned dynamically typed Future for use in cases where …","Future for the catch_unwind method.","The output of the completed future","The output of the completed future","Combines two different futures, streams, or sinks having …","Future for the err_into method.","The type of failures yielded by this future","Future for the flatten method.","Sink for the flatten_sink method.","Stream for the flatten_stream method.","Future for the fuse method.","A future which tracks whether or not the underlying future …","","A not-yet-completed future","A not-yet-completed future","An extension trait for Futures that provides a variety of …","A custom trait object for polling futures, roughly akin to …","The empty variant after the result of a MaybeDone has been …","The empty variant after the result of a TryMaybeDone has …","Future for the inspect method.","Future for the inspect_err method.","Future for the inspect_ok method.","Future for the into_future method.","Stream for the into_stream method.","Future for the join function.","Future for the join3 function.","Future for the join4 function.","Future for the join5 function.","Future for the join_all function.","Future for the lazy function.","First branch of the type","BoxFuture, but without the Send requirement.","A custom trait object for polling futures, roughly akin to …","Future for the map method.","Future for the map_err method.","Future for the map_into combinator.","Future for the map_ok method.","Future for the map_ok_or_else method.","A future that may have completed.","Future for the never_error combinator.","The type of successful values yielded by this future","Future for the ok_into method.","A future representing a value which may or may not be …","Future for the or_else method.","Future for the pending() function.","Future for the poll_fn function.","Future for the poll_immediate function.","Future for the ready function.","A future which sends its output to the corresponding …","The handle to a remote future returned by remote_handle. …","Second branch of the type","Future for the select() function.","Future for the select_all function.","Future for the select_ok function.","Future for the shared method.","Future for the then method.","Future for the try_flatten method.","Future for the try_flatten_stream method.","A convenience for futures that return Result values that …","Adapters specific to Result-returning futures","Future for the try_join function.","Future for the try_join3 function.","Future for the try_join4 function.","Future for the try_join5 function.","Future for the try_join_all function.","A future that may have completed with an error.","Future for the try_select() function.","Future for the unit_error combinator.","A custom implementation of a future trait object for …","Future for the unwrap_or_else method.","A weak reference to a Shared that can be upgraded much …","Abort the Abortable stream/future associated with this …","Creates a new Abortable future and an AbortHandle which …","Executes another future after this one resolves …","Executes another future after this one resolves …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Wrap the future in a Box, pinning it.","Catches unwinding panics while polling the future.","Catches unwinding panics while polling the future.","","","","","","","","","","","","","","","","","","","","","","","Creates a new WeakShared for this Shared.","Drops the future represented by the given fat pointer.","","","Create a future that is immediately ready with an error …","Maps this future’s Error to a new error type using the …","Maps this future’s Error to a new error type using the …","Factor out a homogeneous type from an either of pairs.","Factor out a homogeneous type from an either of pairs.","Flatten the execution of this future when the output of …","Flatten the execution of this future when the output of …","Flattens the execution of this future when the successful …","Flattens the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Drops this handle without canceling the underlying future.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Fuse a future such that poll will never again be called …","Fuse a future such that poll will never again be called …","Do something with the output of a future before passing it …","Do something with the output of a future before passing it …","Do something with the error value of a future before …","Do something with the error value of a future before …","Do something with the success value of a future before …","Do something with the success value of a future before …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Wraps a TryFuture into a type that implements Future.","Wraps a TryFuture into a type that implements Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unwraps the value from this immediately ready future.","Consumes this combinator, returning the underlying futures.","Extract the value of an either over two equivalent types.","Convert an owned instance into a (conceptually owned) fat …","Convert this future into a single element stream.","Convert this future into a single element stream.","Checks whether the task has been aborted. Note that all …","Returns true if the underlying future should no longer be …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as join, but with more futures.","Same as join, but with more futures.","Same as join, but with more futures.","Creates a future which represents a collection of the …","Creates a new future that allows delayed execution of a …","Wrap this future in an Either future, making it the …","Wrap this future in an Either future, making it the …","Map this future’s output to a different type, returning …","Map this future’s output to a different type, returning …","Maps this future’s error value to a different value.","Maps this future’s error value to a different value.","Map this future’s output to a different type, returning …","Map this future’s output to a different type, returning …","Maps this future’s success value to a different value.","Maps this future’s success value to a different value.","Maps this future’s success value to a different value, …","Maps this future’s success value to a different value, …","Wraps a future into a MaybeDone","Turns a Future<Output = T> into a …","Turns a Future<Output = T> into a …","Creates a new Abortable future/stream using an existing …","Creates an (AbortHandle, AbortRegistration) pair which can …","Evaluates and consumes the future, returning the resulting …","Evaluates and consumes the future, returning the resulting …","Create a future that is immediately ready with a success …","Maps this future’s Ok to a new type using the Into trait.","Maps this future’s Ok to a new type using the Into trait.","Executes another future if this one resolves to an error. …","Executes another future if this one resolves to an error. …","Returns an Option containing a mutable reference to the …","Returns an Option containing a mutable reference to the …","Returns Some containing a reference to this Shared’s …","Creates a future which never resolves, representing a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new future wrapping around a function returning …","Creates a future that is immediately ready with an Option …","","","","","","","","","","","","","","","A convenience for calling Future::poll on Unpin future …","A convenience for calling Future::poll on Unpin future …","","","","Creates a future that is immediately ready with a value.","Turn this future into a future that yields () on …","Turn this future into a future that yields () on …","Wrap this future in an Either future, making it the …","Wrap this future in an Either future, making it the …","Waits for either one of two differently-typed futures to …","Creates a new future which will select over a list of …","Creates a new future which will select the first …","Create a cloneable handle to this future where all handles …","Create a cloneable handle to this future where all handles …","","","","","","","","","","Gets the number of strong pointers to this allocation.","Attempt to take the output of a MaybeDone without driving …","Attempt to take the output of a TryMaybeDone without …","Creates a new Fuse-wrapped future which is already …","Chain on a computation for when a future finished, passing …","Chain on a computation for when a future finished, passing …","","","","","","","","","","","","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","Flatten the execution of this future when the successful …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Joins the result of two futures, waiting for them both to …","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Same as try_join, but with more futures.","Creates a future which represents either a collection of …","Wraps a future into a TryMaybeDone","Poll this TryFuture as if it were a Future.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryFuture::try_poll on …","A convenience method for calling TryFuture::try_poll on …","Waits for either one of two differently-typed futures to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Turns a Future<Output = T> into a …","Turns a Future<Output = T> into a …","Unwraps this future’s output, producing a future with …","Unwraps this future’s output, producing a future with …","Attempts to upgrade this WeakShared into a Shared.","Gets the number of weak pointers to this allocation.","","","","","","","","","A simple wrapper type which allows types which implement …","Read bytes asynchronously.","An extension trait which adds utility methods to …","Read bytes asynchronously.","An extension trait which adds utility methods to AsyncRead …","Seek bytes asynchronously.","An extension trait which adds utility methods to AsyncSeek …","Write bytes asynchronously.","An extension trait which adds utility methods to AsyncWrite…","The BufReader struct adds buffering to any reader.","Wraps a writer and buffers its output.","Reader for the chain method.","Future for the close method.","Future for the copy() function.","Future for the copy_buf() function.","Future for the [copy_buf()] function.","A Cursor wraps an in-memory buffer and provides it with a …","Reader for the empty() function.","","","Future for the fill_buf method.","Future for the flush method.","Sink for the into_sink method.","","","Wrap a writer, like BufWriter does, but prioritizes …","Stream for the lines method.","Future for the read method.","Future for the read_exact method.","The readable half of an object returned from …","Future for the read_line method.","Future for the read_to_end method.","Future for the read_to_string method.","Future for the read_until method.","Future for the read_vectored method.","Reader for the repeat() function.","","Error indicating a ReadHalf<T> and WriteHalf<T> were not …","Future for the BufReader::seek_relative method.","Future for the seek method.","","Writer for the sink() function.","Reader for the take method.","A owned window around an underlying buffer.","Future for the write method.","Future for the write_all method.","The writable half of an object returned from …","Future for the write_vectored method.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a reference to the internally buffered data.","Returns a reference to the internally buffered data.","Returns a reference to buf_writer’s internally buffered …","Creates an adaptor which will chain this stream with …","","","","","Creates a future which will entirely close this AsyncWrite.","","Tells this buffer that amt bytes have been consumed from …","","","","","","","","","A convenience for calling AsyncBufRead::consume on Unpin …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","Creates a future which copies all the bytes from one …","","Constructs a new handle to an empty reader.","Returns the end index of this window into the underlying …","","Creates a future which will wait for a non-empty buffer to …","","Creates a future which will entirely flush this AsyncWrite.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the contained IO object.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Gets mutable references to the underlying readers in this …","Gets a mutable reference to the underlying value in this …","Acquires a mutable reference to the underlying sink or …","Gets a mutable reference to the underlying buffer inside …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Gets pinned mutable references to the underlying readers …","Acquires a pinned mutable reference to the underlying sink …","Returns a reference to the contained IO object.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Gets references to the underlying readers in this Chain.","Gets a reference to the underlying value in this cursor.","Acquires a reference to the underlying sink or stream that …","Gets a shared reference to the underlying buffer inside of …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Consumes self and returns the contained IO object.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes the Chain, returning the wrapped readers.","Consumes this cursor, returning the underlying value.","Consumes this combinator, returning the underlying sink or …","Consumes this Window, returning the underlying buffer.","Allow using an AsyncWrite as a Sink<Item: AsRef<[u8]>>.","Returns the remaining number of bytes that can be read …","Returns a stream over the lines of this reader. This …","Creates a new AllowStdIo from an existing IO object.","Creates a new BufReader with a default buffer capacity. …","Creates a new BufWriter with a default buffer capacity. …","Create a new LineWriter with default buffer capacity. The …","Creates a new cursor wrapping the provided underlying …","Creates a new window around the buffer t defaulting to the …","","","","","","","","","","","","","","","","","","","","Attempt to close the object.","","","","","Forward to buf_writer ’s BufWriter::poll_close()","","","","","","","Attempt to return the contents of the internal buffer, …","","","","","","","","Attempt to flush the object, ensuring that any buffered …","","","","","Forward to buf_writer ’s BufWriter::poll_flush()","","","","","","","","Attempt to read from the AsyncRead into buf.","","","","","","","","","","Attempt to read from the AsyncRead into bufs using vectored","","","","","","","","","Attempt to seek to an offset, in bytes, in a stream.","","Seek to an offset, in bytes, in the underlying reader.","Seek to the offset, in bytes, in the underlying writer.","","Attempts to seek relative to the current position. If the …","Attempt to write bytes from buf into the object.","","","","","","","","","","","Attempt to write bytes from bufs into the object using …","","","","","","","","","","","Returns the current position of this cursor.","","Tries to read some bytes directly into the given buf in …","","Creates a future which will read exactly enough bytes to …","","Creates a future which will read all the bytes associated …","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes from this …","","Creates a future which will read all the bytes associated …","Creates a future which will read from the AsyncRead into …","","Creates an instance of a reader that infinitely repeats …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Creates a future which will seek an IO object, and then …","","Seeks relative to the current position. If the new …","Changes the range of this window to the range specified.","Sets the number of bytes that can be read before this …","Sets the position of this cursor.","Creates an instance of a writer which will successfully …","Helper method for splitting this read/write object into …","Returns the starting index of this window into the …","","Creates a future which will return the current seek …","Creates an AsyncRead adapter which will read at most limit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new BufReader with the specified buffer capacity.","Creates a new BufWriter with the specified buffer capacity.","Creates a new LineWriter with the specified buffer …","Creates a future which will write bytes from buf into the …","","Write data into this object.","","","Creates a future which will write bytes from bufs into the …","","An RAII guard returned by the MutexGuard::map and …","A futures-aware mutex.","An RAII guard returned by the lock and try_lock methods. …","A future which resolves when the target mutex has been …","An RAII guard returned by the lock_owned and try_lock_owned…","A future which resolves when the target mutex has been …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes this mutex, returning the underlying data.","","","Acquire the lock asynchronously.","Acquire the lock asynchronously.","Returns a locked view over a portion of the locked data.","Returns a locked view over a portion of the locked data.","Creates a new futures-aware mutex.","","","","","","","","","","","","","","","Attempt to acquire the lock immediately.","Attempt to acquire the lock immediately.","","","","","","","A type with no possible values.","Sink for the buffer method.","Future for the close method.","Sink for the drain function.","The type of value produced by the sink when an error …","Sink that clones incoming items and forwards them to two …","Future for the feed method.","Future for the flush method.","Future for the send method.","Future for the send_all method.","A Sink is a value into which other values can be sent, …","Sink for the sink_err_into method.","An extension trait for Sinks that provides a variety of …","Sink for the sink_map_err method.","Sink for the unfold function.","Sink for the with method.","Sink for the with_flat_map method.","","","","","","","","","","","","","","","","","","","","","","","","","","","Adds a fixed-size buffer to the current sink.","","","","","","","Close the sink.","Create a sink that will just discard all items given to it.","Fanout items to multiple sinks.","A future that completes after the given item has been …","Flush the sink, processing all pending items.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get a mutable reference to the inner sinks.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Get a pinned mutable reference to the inner sinks.","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Get a shared reference to the inner sinks.","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Consumes this combinator, returning the underlying sinks.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","","","","","Wrap this sink in an Either sink, making it the left-hand …","","","","","","Flush any remaining output and close this sink, if …","","","","","","","","","A convenience method for calling Sink::poll_close on Unpin …","Flush any remaining output from this sink.","","","","","","","","","A convenience method for calling Sink::poll_flush on Unpin …","","","","","","Attempts to prepare the Sink to receive a value.","","","","","","","","","A convenience method for calling Sink::poll_ready on Unpin …","Wrap this stream in an Either stream, making it the …","A future that completes after the given item has been …","A future that completes after the given stream has been …","Map this sink’s error to a different error type using …","Transforms the error returned by the sink.","","","","","","Begin the process of sending a value to the sink. Each …","","","","","","","","","A convenience method for calling Sink::start_send on Unpin …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create a sink from a function which processes one item at …","Composes a function in front of the sink.","Composes a function in front of the sink.","","","","","A handle to an Abortable task.","A registration handle for an Abortable task. Values of …","A future/stream which can be remotely short-circuited …","Indicator that the Abortable task was aborted.","Stream for the and_then method.","An owned dynamically typed Stream for use in cases where …","Stream for the buffer_unordered method.","Stream for the buffered method.","Stream for the catch_unwind method.","Stream for the chain method.","Stream for the chunks method.","Future for the collect method.","Future for the concat method.","Stream for the cycle method.","Stream for the empty function.","Stream for the enumerate method.","Stream for the err_into method.","The type of failures yielded by this future","Stream for the filter method.","Stream for the filter_map method.","Stream for the flat_map method.","Stream for the flatten method.","Future for the fold method.","Future for the for_each method.","Future for the for_each_concurrent method.","Future for the forward method.","Stream for the fuse method.","A stream which tracks whether or not the underlying stream …","An unbounded queue of futures.","A set of futures which may complete in any order.","Stream for the inspect method.","Stream for the inspect_err method.","Stream for the inspect_ok method.","Reader for the into_async_read method.","Stream for the into_stream method.","Values yielded by the stream.","Stream for the iter function.","Poll the first stream.","BoxStream, but without the Send requirement.","Stream for the map method.","Stream for the map_err method.","Stream for the map_ok method.","Future for the next method.","Future for the Peekable::next_if method.","Future for the Peekable::next_if_eq method.","The type of successful values yielded by this future","A stream which emits single element and then EOF.","Stream for the or_else method.","Future for the Peekable::peek method.","Future for the Peekable::peek_mut method.","A Stream that implements a peek method.","Stream for the pending() function.","Stream for the poll_fn function.","Stream for the poll_immediate function.","Type to tell SelectWithStrategy which stream to poll next.","Stream for the ready_chunks method.","Stream for the repeat function.","An stream that repeats elements of type A endlessly by …","Error indicating a SplitSink<S> and SplitStream<S> were …","Poll the second stream.","Stream for the scan method.","Stream for the select() function.","An unbounded set of streams","Future for the select_next_some method.","Stream for the select_with_strategy() function. See …","Stream for the skip method.","Stream for the skip_while method.","A Sink part of the split pair","A Stream part of the split pair","A stream of values produced asynchronously.","An extension trait for Streams that provides a variety of …","Future for the into_future method.","Stream for the take method.","Stream for the take_until method.","Stream for the take_while method.","Stream for the then method.","Stream for the try_buffer_unordered method.","Stream for the try_buffered method.","Stream for the try_chunks method.","Error indicating, that while chunk was collected inner …","Future for the try_collect method.","Future for the try_concat method.","Stream for the try_filter method.","Stream for the try_filter_map method.","Stream for the try_flatten method.","Future for the try_fold method.","Future for the try_for_each method.","Future for the try_for_each_concurrent method.","Future for the try_next method.","Stream for the try_skip_while method.","A convenience for streams that return Result values that …","Adapters specific to Result-returning streams","Stream for the try_take_while method.","Stream for the try_unfold function.","Stream for the unfold function.","Future for the unzip method.","Stream for the zip method.","Creates a new Abortable stream and an AbortHandle which …","Execute predicate over asynchronous stream, and return true…","Execute predicate over asynchronous stream, and return true…","Chain on a computation for when a value is ready, passing …","Chain on a computation for when a value is ready, passing …","Execute predicate over asynchronous stream, and return true…","Execute predicate over asynchronous stream, and return true…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","Wrap the stream in a Box, pinning it.","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures …","An adaptor for creating a buffered list of pending futures.","An adaptor for creating a buffered list of pending futures.","Borrows a stream, rather than consuming it.","Borrows a stream, rather than consuming it.","Catches unwinding panics while polling the stream.","Catches unwinding panics while polling the stream.","Adapter for chaining two streams.","Adapter for chaining two streams.","An adaptor for chunking up items of the stream inside a …","An adaptor for chunking up items of the stream inside a …","","","","","","","","","","","","","","","Transforms a stream into a collection, returning a future …","Transforms a stream into a collection, returning a future …","Concatenate all items of a stream into a single extendable …","Concatenate all items of a stream into a single extendable …","","Drives the stream to completion, counting the number of …","Drives the stream to completion, counting the number of …","Repeats a stream endlessly.","Repeats a stream endlessly.","","","","","","Creates a stream which contains no elements.","Creates a stream which gives the current iteration count …","Creates a stream which gives the current iteration count …","","","Wraps the current stream in a new stream which converts …","Wraps the current stream in a new stream which converts …","","","","Filters the values produced by this stream according to …","Filters the values produced by this stream according to …","Filters the values produced by this stream while …","Filters the values produced by this stream while …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Maps a stream like StreamExt::map but flattens nested …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Execute an accumulating asynchronous computation over a …","Execute an accumulating asynchronous computation over a …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","Runs this stream to completion, executing the provided …","A future that completes after the given stream has been …","A future that completes after the given stream has been …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Fuse a stream such that poll_next will never again be …","Fuse a stream such that poll_next will never again be …","An unbounded set of futures.","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying stream that …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying sink or …","Acquires a mutable reference to the underlying streams …","Acquires a mutable reference to the underlying streams …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying sink …","Acquires a pinned mutable reference to the underlying …","Acquires a pinned mutable reference to the underlying …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying stream that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying sink or stream that …","Acquires a reference to the underlying streams that this …","Acquires a reference to the underlying streams that this …","","Do something with each item of this stream, afterwards …","Do something with each item of this stream, afterwards …","Do something with the error value of this stream, …","Do something with the error value of this stream, …","Do something with the success value of this stream, …","Do something with the success value of this stream, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Adapter that converts this stream into an AsyncBufRead.","Adapter that converts this stream into an AsyncBufRead.","Converts this stream into a future of …","Converts this stream into a future of …","","","","","","","","","","","","","","","","","","","","","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying stream.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying sink or …","Consumes this combinator, returning the underlying streams.","Consumes this combinator, returning the underlying streams.","","","","","","","Wraps a TryStream into a type that implements Stream","Wraps a TryStream into a type that implements Stream","Returns whether the underlying stream has finished or not.","Returns true if the queue contains no futures","Whether the stream was stopped yet by the stopping future …","Returns true if the stream should no longer be polled.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts an Iterator into a Stream which is always ready …","Wrap this stream in an Either stream, making it the …","Wrap this stream in an Either stream, making it the …","Returns the number of futures contained in the queue.","Maps this stream’s items to a different type, returning …","Maps this stream’s items to a different type, returning …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Wraps the current stream in a new stream which maps the …","Constructs a new, empty FuturesOrdered","Creates a future that resolves to the next item in the …","Creates a future that resolves to the next item in the …","Creates a future which will consume and return the next …","Creates a future which will consume and return the next …","Creates a stream of a single element.","Chain on a computation for when an error happens, passing …","Chain on a computation for when an error happens, passing …","Produces a future which retrieves a reference to the next …","Produces a future which retrieves a mutable reference to …","Creates a new stream which exposes a peek method.","Creates a new stream which exposes a peek method.","Creates a stream which never returns any elements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream wrapping a function returning …","Creates a new stream that always immediately returns …","Attempt to pull out the next value of this stream, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling Stream::poll_next on Unpin","A convenience method for calling Stream::poll_next on Unpin","Peek retrieves a reference to the next item in the stream.","Peek retrieves a mutable reference to the next item in the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Push a future into the queue.","Pushes a future to the back of the queue.","Pushes a future to the front of the queue.","An adaptor for chunking up ready items of the stream …","An adaptor for chunking up ready items of the stream …","Create a stream which produces the same item repeatedly.","Creates a new stream that repeats elements of type A …","Attempts to put the two “halves” of a split …","Attempts to put the two “halves” of a split …","Wrap this stream in an Either stream, making it the …","Wrap this stream in an Either stream, making it the …","Combinator similar to StreamExt::fold that holds internal …","Combinator similar to StreamExt::fold that holds internal …","This function will attempt to pull items from both …","An unbounded set of streams","Convert a list of streams into a Stream of results from …","Returns a Future that resolves when the next item in this …","Returns a Future that resolves when the next item in this …","This function will attempt to pull items from both …","Returns the bounds on the remaining length of the stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream which skips n items of the underlying …","Creates a new stream which skips n items of the underlying …","Skip elements on this stream while the provided …","Skip elements on this stream while the provided …","","","Splits this Stream + Sink object into separate Sink and …","Splits this Stream + Sink object into separate Sink and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new stream of at most n items of the underlying …","Creates a new stream of at most n items of the underlying …","Extract the stopping future out of the combinator. The …","Once the stopping future is resolved, this method can be …","Take elements from this stream until the provided future …","Take elements from this stream until the provided future …","Take elements from this stream while the provided …","Take elements from this stream while the provided …","Computes from this stream’s items new items of a …","Computes from this stream’s items new items of a …","","","","","","","","","","Toggle the value and return the old one.","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","Attempt to execute several futures from a stream …","An adaptor for chunking up successful items of the stream …","An adaptor for chunking up successful items of the stream …","Attempt to transform a stream into a collection, returning …","Attempt to transform a stream into a collection, returning …","Attempt to concatenate all items of a stream into a single …","Attempt to concatenate all items of a stream into a single …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream …","Attempt to filter the values produced by this stream while …","Attempt to filter the values produced by this stream while …","Flattens a stream of streams into just one continuous …","Flattens a stream of streams into just one continuous …","Attempt to execute an accumulating asynchronous …","Attempt to execute an accumulating asynchronous …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","Attempts to run this stream to completion, executing the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a future that attempts to resolve the next item in …","Creates a future that attempts to resolve the next item in …","","","","","","","","","","","Poll this TryStream as if it were a Stream.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A convenience method for calling TryStream::try_poll_next …","A convenience method for calling TryStream::try_poll_next …","Skip elements on this stream while the provided …","Skip elements on this stream while the provided …","Take elements on this stream while the provided …","Take elements on this stream while the provided …","Creates a TryStream from a seed and a closure returning a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a Stream from a seed and a closure returning a …","Converts a stream of pairs into a future, which resolves …","Converts a stream of pairs into a future, which resolves …","An adapter for zipping two streams together.","An adapter for zipping two streams together.","A set of futures which may complete in any order.","Owned iterator over all futures in the unordered set.","Immutable iterator over all the futures in the unordered …","Mutable iterator over all futures in the unordered set.","Mutable iterator over all futures in the unordered set.","Immutable iterator over all futures in the unordered set.","","","","","","","","","","","Clears the set, removing all futures.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Returns true if the set contains no futures.","Returns an iterator that allows inspecting each future in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows modifying each future in …","Returns an iterator that allows inspecting each future in …","Returns the number of futures contained in the set.","Constructs a new, empty FuturesUnordered.","","","","","","Push a future into the set.","","","","","","","","","","","","","","","","","","","","","Owned iterator over all streams in the unordered set.","Immutable iterator over all streams in the unordered set.","Mutable iterator over all streams in the unordered set.","An unbounded set of streams","","","","","","","Clears the set, removing all streams.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if the set contains no streams","Returns an iterator that allows inspecting each stream in …","Returns an iterator that allows modifying each stream in …","Returns the number of streams contained in the set.","Constructs a new, empty SelectAll","","","","Push a stream into the set.","Convert a list of streams into a Stream of results from …","","","","","","","","","","","","","A way of waking up a specific task.","A synchronization primitive for task wakeup.","","A custom trait object for polling futures, roughly akin to …","A custom trait object for polling futures, roughly akin to …","The LocalSpawn is similar to Spawn, but allows spawning …","Extension trait for LocalSpawn.","","","","The Spawn trait allows for pushing futures onto an …","An error that occurred during spawning.","Extension trait for Spawn.","A custom implementation of a future trait object for …","","A Waker that is only valid for a given lifetime.","","","","","","","","","","","","","Drops the future represented by the given fat pointer.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Converts the LocalFutureObj into a FutureObj.","Convert an owned instance into a (conceptually owned) fat …","Check whether spawning failed to the executor being shut …","Create an AtomicWaker.","Create a LocalFutureObj from a custom trait object …","Create a FutureObj from a custom trait object …","Create a new WakerRef from a Waker reference.","Create a new WakerRef from a Waker that must not be …","Create a new Waker which does nothing when wake() is …","Get a static reference to a Waker which does nothing when …","","","","Registers the waker to be notified on calls to wake.","Spawning failed because the executor has been shut down.","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a task that polls the given future with output () to","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a task that polls the given future to completion …","Spawns a future that will be run to completion.","Spawns a task that polls the given future to completion …","Spawns a task that polls the given future to completion …","Determines whether the executor is able to spawn new tasks.","Determines whether the executor is able to spawn new tasks.","Returns the last Waker passed to register, so that the …","","","","","","","","","","","","","","","","","","","Indicates that the associated task is ready to make …","Calls wake on the last Waker passed to register.","Indicates that the associated task is ready to make …","Creates a Waker from an Arc<impl ArcWake>.","Creates a reference to a Waker from a reference to …"],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,56,0,0,55,0,0,0,0,0,0,54,56,0,0,54,56,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,55,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,266,266,266,266,266,266,6,8,9,11,12,13,14,15,1,16,6,8,9,11,12,13,14,15,1,16,14,11,6,267,6,16,0,265,265,14,14,266,266,265,265,266,266,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,16,40,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,29,30,62,68,266,266,266,266,265,265,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,265,265,28,29,30,32,23,33,35,36,37,38,39,5,40,41,42,43,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,13,62,14,267,266,266,15,73,6,32,23,25,33,34,35,36,37,38,39,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,14,14,0,0,0,0,0,0,266,266,266,266,265,265,266,266,265,265,265,265,0,266,266,15,1,266,266,0,265,265,265,265,54,56,6,0,6,28,29,30,32,23,33,35,36,37,38,39,5,40,41,42,43,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,25,44,24,14,14,14,25,44,24,14,14,0,0,25,34,44,24,12,14,15,14,14,25,44,24,14,14,266,266,14,14,16,0,266,266,266,266,0,0,0,266,266,25,34,44,24,14,25,44,24,14,6,54,56,32,266,266,6,8,9,11,12,13,14,15,1,16,16,265,265,265,265,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,0,0,0,0,0,0,55,6,28,30,32,23,33,35,36,37,38,39,5,40,42,43,2,45,22,46,47,48,49,50,51,52,53,9,56,13,63,64,65,66,67,68,14,15,25,34,44,24,14,15,265,265,0,6,8,28,29,30,32,23,25,33,34,35,36,37,38,39,5,40,41,42,43,44,24,2,45,22,46,47,48,49,50,51,52,53,9,54,56,11,12,13,57,58,59,60,61,62,63,64,65,66,67,68,14,15,69,1,16,266,266,265,265,8,6,268,269,270,271,272,273,107,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,83,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,86,88,89,274,91,92,91,92,275,91,97,96,91,91,86,88,90,92,98,276,0,0,0,92,0,83,91,276,91,275,91,96,105,106,107,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,91,86,88,90,92,98,83,86,88,90,98,91,86,88,89,90,92,98,83,91,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,91,86,88,90,92,98,83,275,98,276,91,86,88,89,92,83,91,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,87,106,91,86,88,89,92,92,92,92,111,122,97,96,91,86,88,90,92,98,87,106,91,86,88,89,92,92,92,92,111,122,112,85,96,105,91,86,88,90,92,121,98,85,105,91,86,88,90,92,121,111,128,91,86,88,92,86,87,106,91,86,88,89,92,92,92,92,122,87,106,91,86,88,89,92,92,92,92,122,92,107,274,91,274,91,276,274,91,274,91,276,274,91,0,121,122,277,91,86,83,98,92,0,274,83,111,277,274,91,92,107,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,108,93,99,100,110,103,104,113,114,115,116,117,118,119,120,123,124,125,112,96,105,106,107,91,86,108,88,89,90,93,99,100,110,92,103,104,111,112,113,114,115,116,117,118,119,120,121,122,98,83,123,124,125,86,88,89,275,91,275,91,91,275,91,0,0,0,0,0,0,135,139,136,140,137,138,135,139,136,140,137,138,135,136,137,138,136,137,138,139,136,140,137,138,135,139,136,140,137,138,135,135,135,139,136,140,137,138,135,135,139,136,140,137,138,139,140,135,139,140,135,135,137,138,135,139,140,135,139,136,140,137,138,135,139,136,140,137,138,135,135,135,139,136,140,137,138,0,0,0,0,152,0,0,0,0,0,0,0,0,0,0,0,0,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,278,144,145,146,144,145,146,278,0,278,278,278,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,148,153,145,146,156,143,148,153,145,146,156,143,148,153,145,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,149,150,154,148,153,145,146,156,143,153,145,156,143,278,151,147,149,150,154,152,144,148,153,145,155,146,156,143,278,152,144,148,153,145,155,146,156,143,278,153,145,146,156,143,152,144,148,153,145,155,146,156,143,278,278,278,278,278,278,153,145,146,156,143,152,144,148,153,145,155,146,156,143,278,144,145,146,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,151,147,149,150,154,153,145,146,156,143,151,147,144,148,149,150,153,145,154,155,146,156,143,0,278,278,190,183,190,183,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,0,173,0,0,0,0,0,0,0,231,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,279,279,280,280,279,279,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,167,168,169,170,171,172,173,167,168,169,170,171,172,173,279,279,279,279,177,279,279,279,279,179,180,173,181,180,0,279,279,183,173,280,280,179,180,181,279,279,279,279,279,279,279,279,279,279,279,279,190,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,279,279,279,279,279,279,279,279,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,179,180,181,279,279,0,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,173,279,279,280,280,280,280,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,280,280,279,279,175,192,176,193,194,195,197,200,201,203,204,205,206,216,225,226,230,232,235,241,182,185,186,189,196,197,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,243,244,180,180,180,181,181,181,280,280,196,179,211,157,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,216,164,159,184,219,220,221,222,223,224,225,227,228,229,230,233,235,237,238,241,168,169,170,242,171,172,243,244,245,181,0,279,279,179,279,279,280,280,280,280,179,279,279,202,202,0,280,280,202,202,279,279,0,175,192,176,193,194,195,197,200,201,203,204,205,206,216,225,226,230,232,235,241,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,177,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,0,0,158,191,179,180,165,178,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,217,164,159,184,219,220,221,222,223,224,227,228,229,233,236,237,238,239,240,167,168,169,170,242,171,172,243,244,245,181,279,279,202,202,177,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,177,190,183,179,179,179,279,279,0,0,217,218,279,279,279,279,0,0,0,279,279,0,158,179,180,165,178,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,213,166,214,215,162,163,164,159,184,219,220,221,222,223,224,227,228,233,237,238,167,168,169,170,242,171,172,279,279,279,279,180,180,279,279,182,185,186,189,196,198,199,187,202,207,208,209,210,211,212,166,214,215,162,163,218,159,184,219,220,221,222,223,224,227,228,229,233,237,238,239,240,279,279,211,211,279,279,279,279,279,279,167,168,169,170,171,172,173,190,183,173,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,280,280,176,193,194,201,225,226,230,232,235,241,231,191,179,180,165,178,185,186,189,196,198,199,187,202,207,208,209,210,211,212,215,162,163,217,164,159,184,219,220,221,222,223,224,227,228,229,233,236,237,238,239,240,167,168,169,170,242,171,243,244,245,181,280,280,280,280,280,280,0,190,191,179,180,165,175,192,176,178,182,185,186,189,193,194,195,196,197,198,199,187,200,201,202,203,204,205,206,207,208,209,210,211,212,213,166,214,215,162,163,216,217,218,164,159,184,219,220,221,222,223,224,225,226,227,228,229,230,232,233,183,235,236,237,238,239,240,241,177,167,168,169,170,242,171,172,243,173,244,245,181,0,279,279,279,279,0,0,0,0,0,0,251,252,253,254,255,251,252,253,254,255,180,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,180,180,180,180,180,180,180,251,252,253,254,255,180,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,251,252,253,254,255,0,0,0,0,256,257,258,256,257,258,181,256,257,258,256,257,258,256,257,258,256,257,258,181,181,181,181,181,256,257,258,181,0,256,257,258,256,257,258,256,257,258,256,257,258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,249,259,248,250,260,249,259,248,250,260,259,260,267,248,249,249,259,248,250,260,249,259,248,248,248,248,248,248,250,250,250,250,250,260,249,259,248,250,260,248,250,248,267,249,259,248,250,260,260,0,0,248,250,249,259,249,281,281,282,282,283,282,282,284,281,281,284,283,259,249,249,259,248,250,260,249,259,248,250,260,248,250,249,259,248,250,260,285,259,285,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1],[[]],[[],2],[[],2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[],3],[[],4],[[],4],[[],5],[[],5],[6,6],[[[8,[7]]],[[8,[7]]]],[9,9],[[[11,[10]]],[[11,[10]]]],[[[12,[10]]],[[12,[10]]]],[[[13,[10]]],[[13,[10]]]],[[[14,[10,10]]],[[14,[10,10]]]],[[[15,[10]]],[[15,[10]]]],[1,1],[16,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[17,[14]],18]],[[],11],[6,[[19,[8]]]],[7],[6],[[16,16],20],[[],[[13,[21]]]],[[],22],[[],22],[14],[14],[[],23],[[],23],[[],24],[[],24],[[],25],[[],25],[[[6,[7]],26],27],[[[8,[7]],26],27],[[28,26],27],[[29,26],27],[[30,26],27],[[[32,[31]],26],27],[[23,26],27],[[25,26],27],[[33,26],27],[[34,26],27],[[35,26],27],[[36,26],27],[[37,26],27],[[38,26],27],[[39,26],27],[[[5,[31]],26],27],[[[40,[31]],26],27],[[[41,[[0,[7,31]]]],26],27],[[[42,[31]],26],27],[[43,26],27],[[44,26],27],[[24,26],27],[[2,26],27],[[45,26],27],[[22,26],27],[[46,26],27],[[47,26],27],[[48,26],27],[[49,26],27],[[50,26],27],[[51,26],27],[[52,26],27],[[[53,[31]],26],27],[[[9,[31]],26],27],[[[54,[[0,[31,7]]]],26],27],[[[56,[[0,[31,55]]]],26],27],[[[11,[31]],26],27],[[[12,[31]],26],27],[[[13,[31]],26],27],[[57,26],27],[[58,26],27],[[59,26],27],[[60,26],27],[[[61,[31,31]],26],27],[[[62,[31]],26],27],[[63,26],27],[[64,26],27],[[65,26],27],[[66,26],27],[[[67,[31,31]],26],27],[[[68,[31]],26],27],[[[14,[31,31]],26],27],[[[15,[31]],26],27],[[69,26],27],[[1,26],27],[[16,26],27],[[16,26],27],[40],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[70,[[29,[7]]]],[70,30],[70,[[62,[[0,[7,71]]]]]],[70,[[68,[[0,[55,71]]]]]],[[],32],[[],32],[[],37],[[],37],[[],48],[[],48],[[],47],[[],47],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],42],[[],42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[13],[62,72],[14],[[],7],[[],34],[[],34],[15,20],[[],20],[6,20],[[[32,[7]]],20],[23,20],[25,20],[33,20],[34,20],[35,20],[36,20],[37,20],[38,20],[39,20],[[[42,[[0,[55,73]]]]],20],[43,20],[44,20],[24,20],[2,20],[45,20],[22,20],[46,20],[47,20],[48,20],[49,20],[50,20],[51,20],[52,20],[53,20],[9,20],[[[54,[7]]],20],[[[56,[55]]],20],[[[11,[73]]],20],[[[12,[7]]],20],[13,20],[[[57,[73,73]]],20],[[[58,[73,73,73]]],20],[[[59,[73,73,73,73]]],20],[[[60,[73,73,73,73,73]]],20],[61,20],[14,20],[14,20],[[],57],[[],58],[[],59],[[],60],[[],29],[[],53],[[],14],[[],14],[[],33],[[],33],[[],50],[[],50],[[],35],[[],35],[[],49],[[],49],[[],51],[[],51],[7,[[54,[7]]]],[[],38],[[],38],[69,15],[[]],[[],19],[[],19],[[],[[13,[21]]]],[[],46],[[],46],[[],45],[[],45],[[[17,[[54,[7]]]]],19],[[[17,[[56,[55]]]]],19],[6,19],[[],9],[[[17,[6]],74],75],[[[17,[28]],74],75],[[[17,[29]],74],75],[[[17,[30]],74],75],[[[17,[[32,[7]]]],74],75],[[[17,[23]],74],75],[[[17,[33]],74],75],[[[17,[35]],74],75],[[[17,[36]],74],75],[[[17,[37]],74],75],[[[17,[38]],74],75],[[[17,[39]],74],75],[[[17,[5]],74],75],[[[17,[40]],74],75],[[[17,[[41,[7]]]],74],75],[[[17,[[42,[55]]]],74],75],[[[17,[43]],74],75],[[[17,[2]],74],75],[[[17,[45]],74],75],[[[17,[22]],74],75],[[[17,[46]],74],75],[[[17,[47]],74],75],[[[17,[48]],74],75],[[[17,[49]],74],75],[[[17,[50]],74],75],[[[17,[51]],74],75],[[[17,[52]],74],75],[[[17,[53]],74],75],[[[17,[9]],74],75],[[[17,[[54,[7]]]],74],75],[[[17,[[56,[55]]]],74],75],[[[17,[[11,[7]]]],74],75],[[[17,[12]],74],[[75,[19]]]],[[[17,[13]],74],75],[[[17,[[57,[7,7]]]],74],75],[[[17,[[58,[7,7,7]]]],74],75],[[[17,[[59,[7,7,7,7]]]],74],75],[[[17,[[60,[7,7,7,7,7]]]],74],75],[[[17,[61]],74],75],[[[17,[[62,[[0,[7,71]]]]]],74],75],[[[17,[63]],74],75],[[[17,[64]],74],75],[[[17,[65]],74],75],[[[17,[66]],74],75],[[[17,[[67,[71,71]]]],74],75],[[[17,[[68,[[0,[55,71]]]]]],74],75],[[[17,[14]],74],75],[[[17,[15]],74],75],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[76]]]],[[[17,[14]],74],[[75,[21]]]],[[],28],[7,[[12,[7]]]],[[[17,[25]],74],[[75,[19]]]],[[[17,[34]],74],[[75,[19]]]],[[[17,[44]],74],[[75,[19]]]],[[[17,[24]],74],[[75,[19]]]],[[[17,[12]],74],[[75,[19]]]],[[[17,[14]],74],[[75,[19]]]],[[[17,[15]],74],[[75,[19]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[25]],74],[[75,[21]]]],[[[17,[44]],74],[[75,[21]]]],[[[17,[24]],74],[[75,[21]]]],[[[17,[14]],74],[[75,[21]]]],[[[17,[14]],74,77],[[75,[[76,[78]]]]]],[74,75],[74,75],[[[17,[14]],74],[[75,[[76,[18]]]]]],[[[17,[14]],74],[[75,[[76,[18]]]]]],[79],[[],13],[[]],[[]],[[],14],[[],14],[[],61],[[],62],[[],68],[[],6],[[],6],[25],[34],[44],[24],[14],[[[17,[25]]],21],[[[17,[44]]],21],[[[17,[24]]],21],[[[17,[14]]],21],[6,[[19,[18]]]],[[[17,[[54,[7]]]]],19],[[[17,[[56,[55]]]]],19],[[],[[32,[7]]]],[[],36],[[],36],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],80],[[],43],[[],43],[[],44],[[],44],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],63],[[],64],[[],65],[[],66],[[],30],[55,[[56,[55]]]],[[17,74],[[75,[21]]]],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[74,[[75,[21]]]],[74,[[75,[21]]]],[[],67],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],39],[[],39],[[],52],[[],52],[[[8,[7]]],[[19,[[6,[7]]]]]],[6,[[19,[18]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[83,[82]]]],[[[83,[84]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[86,[85]]]],[[[88,[87]]]],[[[89,[87]]]],[[],90],[[[91,[10]]],[[91,[10]]]],[[[92,[10]]],[[92,[10]]]],[[]],[[]],[[],93],[[[91,[94]],91],95],[[17,18]],[[[17,[96]],18]],[[91,18]],[[[17,[91]],18]],[[[17,[[86,[85]]]],18]],[[[17,[[88,[97]]]],18]],[[[17,[90]],18]],[[[17,[92]],18]],[[[17,[[98,[97]]]],18]],[18],[[],99],[[],100],[[]],[[],[[92,[101]]]],[[],96],[[[83,[84]]],18],[[[91,[102]],91],20],[[],103],[91,76],[[],104],[91,76],[[96,26],27],[[105,26],27],[[106,26],27],[[107,26],27],[[107,26],27],[[[91,[31]],26],27],[[[86,[31]],26],27],[[[108,[31]],26],27],[[[88,[31]],26],27],[[[89,[[0,[31,87]]]],26],27],[[90,26],27],[[[93,[[0,[31,109]]]],26],27],[[[99,[31,[0,[31,109]]]],26],27],[[[100,[31,[0,[31,109]]]],26],27],[[[110,[31,[0,[31,109]]]],26],27],[[[92,[31]],26],27],[[[103,[[0,[31,109]]]],26],27],[[[104,[[0,[31,109]]]],26],27],[[[111,[31,31]],26],27],[[[112,[31]],26],27],[[[113,[[0,[31,109]]]],26],27],[[[114,[[0,[31,109]]]],26],27],[[[115,[[0,[31,109]]]],26],27],[[[116,[[0,[31,109]]]],26],27],[[[117,[[0,[31,109]]]],26],27],[[[118,[[0,[31,109]]]],26],27],[[[119,[[0,[31,109]]]],26],27],[[[120,[[0,[31,109]]]],26],27],[[[121,[31]],26],27],[[[122,[31]],26],27],[[[98,[31]],26],27],[[[83,[31]],26],27],[[[123,[[0,[31,109]]]],26],27],[[[124,[[0,[31,109]]]],26],27],[[[125,[[0,[31,109]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[91],[[[86,[85]]]],[[[88,[87]]]],[90],[92],[[[98,[85]]]],[[[83,[84]]]],[[[17,[[86,[85]]]]],17],[[[17,[[88,[87]]]]],17],[[[17,[90]]]],[[[17,[[98,[85]]]]],17],[91],[[[86,[85]]]],[[[88,[87]]]],[[[89,[87]]]],[90],[92],[[[98,[85]]]],[[[83,[84]]]],[[[91,[126]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[91],[[[86,[85]]],85],[[[88,[87]]],87],[90],[92],[[[98,[85]]],85],[[[83,[84]]],84],[[],[[111,[84]]]],[[[98,[85]]],78],[[],112],[[],91],[85,[[86,[85]]]],[87,[[88,[87]]]],[87,[[89,[87]]]],[[],92],[84,[[83,[84]]]],[[[91,[127]],91],[[19,[95]]]],[[[17,[108]],74],75],[[[17,[[93,[[0,[87,109,71]]]]]],74],75],[[[17,[[99,[85,[0,[87,71,109]]]]]],74],75],[[[17,[100]],74],75],[[[17,[110]],74],75],[[[17,[103]],74],75],[[[17,[104]],74],75],[[[17,[[113,[[0,[85,109,71]]]]]],74],75],[[[17,[[114,[[0,[85,109,71]]]]]],74],75],[[[17,[[115,[[0,[85,109,71]]]]]],74],75],[[[17,[[116,[[0,[97,109,71]]]]]],74],75],[[[17,[117]],74],75],[[[17,[118]],74],75],[[[17,[[119,[[0,[97,109,71]]]]]],74],75],[[[17,[[120,[[0,[128,109,71]]]]]],74],75],[[[17,[[123,[[0,[87,109,71]]]]]],74],75],[[[17,[[124,[[0,[87,109,71]]]]]],74],75],[[[17,[[125,[[0,[87,109,71]]]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[106]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[87]]]],74],[[75,[76]]]],[[[17,[[88,[87]]]],74],[[75,[76]]]],[[[17,[[89,[87]]]],74],[[75,[76]]]],[[[17,[[92,[130]]]],74],[[75,[76]]]],[[[17,[[92,[72]]]],74],[[75,[76]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[[17,[[122,[87]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[96]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[85]]]],74],[[75,[76]]]],[[[17,[[88,[97]]]],74],[[75,[76]]]],[[[17,[90]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[98,[97]]]],74],[[75,[76]]]],[[17,74],[[75,[[21,[129]]]]]],[[[17,[106]],74],[[75,[76]]]],[[[17,[91]],74],[[75,[76]]]],[[[17,[[86,[87]]]],74],[[75,[76]]]],[[[17,[[88,[87]]]],74],[[75,[76]]]],[[[17,[[89,[87]]]],74],[[75,[76]]]],[[[17,[[92,[130]]]],74],[[75,[76]]]],[[[17,[[92,[72]]]],74],[[75,[76]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[76]]]],[[[17,[92]],74],[[75,[76]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[[17,[[122,[87]]]],74],[[75,[76]]]],[[[17,[[112,[97]]]],74],[[75,[19]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[96]],74],[[75,[[76,[18]]]]]],[[[17,[105]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[90]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[0,[84,71]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[121,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[98,[85]]]],74],[[75,[[21,[18,129]]]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[105]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[90]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[0,[84,71]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[121,[85]]]],74],[[75,[[76,[18]]]]]],[[[17,[[111,[87,84]]]],74],[[75,[21]]]],[[17,74,77],[[75,[[21,[78,129]]]]]],[[[17,[91]],74,77],[[75,[[76,[78]]]]]],[[[17,[[86,[[0,[85,128]]]]]],74,77],[[75,[[76,[78]]]]]],[[[17,[[88,[[0,[87,128]]]]]],74,77],[[75,[[76,[78]]]]]],[[[17,[92]],74,77],[[75,[[76,[78]]]]]],[[[17,[[86,[[0,[85,128]]]]]],74,132],[[75,[76]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[106]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[89,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[130]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[72]]]],74],[[75,[[76,[18]]]]]],[[[17,[92]],74],[[75,[[76,[18]]]]]],[[[17,[[122,[87]]]],74],[[75,[[76,[18]]]]]],[[17,74],[[75,[[21,[18,129]]]]]],[[[17,[106]],74],[[75,[[76,[18]]]]]],[[[17,[91]],74],[[75,[[76,[18]]]]]],[[[17,[[86,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[88,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[89,[87]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[[72,[131]]]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[72]]]],74],[[75,[[76,[18]]]]]],[[[17,[[92,[130]]]],74],[[75,[[76,[18]]]]]],[[[17,[92]],74],[[75,[[76,[18]]]]]],[[[17,[[122,[87]]]],74],[[75,[[76,[18]]]]]],[92,78],[79],[[],113],[91,[[76,[18]]]],[[],115],[91,76],[80,116],[72,117],[[91,72],[[76,[18]]]],[80,118],[[91,80],[[76,[18]]]],[[131,72],119],[[],114],[91,[[76,[18]]]],[131,105],[[[121,[71]],[122,[71]]],[[21,[71,[107,[71]]]]]],[[[122,[71]],[121,[71]]],[[21,[71,[107,[71]]]]]],[77,120],[[91,77],[[76,[78]]]],[[[17,[[86,[[0,[85,128]]]]]],132],[[108,[[0,[85,128]]]]]],[[[83,[84]],[133,[18]]]],[[[98,[85]],78]],[[92,78]],[[],106],[[]],[[[83,[84]]],18],[[[17,[[111,[87,84]]]],84],21],[[],120],[78,98],[[]],[[]],[[],80],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[18,85],[[86,[85]]]],[[18,87],[[88,[87]]]],[[18,87],[[89,[87]]]],[[],123],[91,[[76,[18]]]],[[],125],[91,76],[[91,134],76],[[],124],[91,[[76,[18]]]],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[135,[101]]]],[[[136,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[136,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[139,[109]]]],[[[136,[109]]]],[[[140,[109]]]],[[[137,[109]]]],[[[138,[109,109]]]],[[[135,[109]],26],27],[[[139,[109]],26],27],[[[136,[[0,[109,31]]]],26],27],[[[140,[109]],26],27],[[[137,[[0,[109,31]]]],26],27],[[[138,[109,[0,[109,31]]]],26],27],[141],[[]],[[],135],[[]],[[]],[[]],[[]],[[]],[[[135,[109]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[135],[[[139,[109]]],20],[[[140,[109]]],20],[[[135,[109]]],[[140,[109]]]],[[[142,[[135,[109]]]]],[[139,[109]]]],[[[137,[109]]],[[138,[109,109]]]],[[[138,[109,109]]],[[138,[109,109]]]],[[],135],[[[17,[[139,[109]]]],74],75],[[[17,[[140,[109]]]],74],75],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[[135,[109]]],[[19,[[137,[109]]]]]],[142,[[19,[[136,[109]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[18,143],[144,144],[[[145,[10,10]]],[[145,[10,10]]]],[146,146],[[]],[[]],[[]],[[],147],[[],144],[[],148],[[],149],[[],150],[[151,26],27],[[[147,[[0,[31,109]],31]],26],27],[[[144,[31]],26],27],[[[148,[31,31]],26],27],[[[149,[[0,[31,109]],31]],26],27],[[[150,[[0,[31,109]],31]],26],27],[[[153,[[0,[31,[152,[31]]]],31,31]],26],27],[[[145,[31,31]],26],27],[[[154,[[0,[31,109]],31]],26],27],[[[155,[31,31,31]],26],27],[[146,26],27],[[156,26],27],[[[143,[31,31]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[145],[146],[156],[[[143,[152]]]],[[[17,[148]]]],[[[17,[153]]],17],[[[17,[145]]],17],[[[17,[146]]],17],[[[17,[156]]],17],[[[17,[[143,[152]]]]],17],[148],[153],[145],[146],[156],[[[143,[152]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[148],[153],[145],[146],[156],[[[143,[152]]],152],[153,20],[[[145,[157]]],20],[156,20],[143,20],[[],14],[[[17,[151]],74],75],[[[17,[[147,[[0,[152,71,109]]]]]],74],75],[[[17,[[149,[[0,[152,71,109]]]]]],74],75],[[[17,[[150,[[0,[152,71,109]]]]]],74],75],[[[17,[[154,[[0,[152,71,109]]]]]],74],75],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[[17,[153]],74],[[75,[19]]]],[[[17,[[145,[158]]]],74],[[75,[19]]]],[[[17,[146]],74],[[75,[19]]]],[[[17,[156]],74],[[75,[19]]]],[[[17,[143]],74],[[75,[19]]]],[[17,74],[[75,[21]]]],[[[17,[144]],74],[[75,[21]]]],[[[17,[148]],74],[[75,[21]]]],[[[17,[153]],74],[[75,[21]]]],[[[17,[145]],74],[[75,[21]]]],[[[17,[155]],74],[[75,[21]]]],[[[17,[146]],74],[[75,[21]]]],[[[17,[156]],74],[[75,[21]]]],[[[17,[[143,[152]]]],74],[[75,[21]]]],[74,[[75,[21]]]],[[],14],[[],154],[[],151],[[],153],[[],145],[153],[[[145,[158]]]],[146],[156],[143],[17,21],[[[17,[144]]],21],[[[17,[148]]],21],[[[17,[153]]],21],[[[17,[145]]],21],[[[17,[155]]],21],[[[17,[146]]],21],[[[17,[156]]],21],[[[17,[[143,[152]]]]],21],[[],21],[[]],[[]],[[]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],155],[[],146],[[],156],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,[[],159],[[],159],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],160],[[],160],[[],161],[[],161],[18,162],[18,162],[18,163],[18,163],[[]],[[]],[[],164],[[],164],[[],165],[[],165],[18,166],[18,166],[[[167,[10]]],[[167,[10]]]],[[[168,[10]]],[[168,[10]]]],[[[169,[10]]],[[169,[10]]]],[170,170],[171,171],[[[172,[10]]],[[172,[10]]]],[173,173],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[175,[[0,[101,174]]]]]],[[],[[175,[[0,[101,174]]]]]],[[],176],[[],176],[[[17,[177]],18]],0,0,[[],178],[[],178],[[],[[179,[7]]]],[[],180],[[],173],[[],[[181,[[0,[158,71]]]]]],[180],[[],170],[[],182],[[],182],[[[183,[102,102]],183],20],[[173,173],20],[[],184],[[],184],[[[179,[7]]]],[180],[[[181,[[0,[158,71]]]],70]],[[],185],[[],185],[[],186],[[],186],[[],187],[[],187],0,0,[[],189],[[],189],0,0,[[190,26],27],[[190,26],27],[[191,26],27],[[[179,[7]],26],27],[[180,26],27],[[[165,[31,31]],26],27],[[[175,[31,31]],26],27],[[[192,[31,31,31]],26],27],[[[176,[[0,[31,158]]]],26],27],[[[178,[31]],26],27],[[[182,[31]],26],27],[[185,26],27],[[186,26],27],[[189,26],27],[[193,26],27],[[194,26],27],[[195,26],27],[[[196,[31]],26],27],[[[197,[31]],26],27],[[198,26],27],[[199,26],27],[[187,26],27],[[[200,[[0,[31,109]]]],26],27],[[[201,[[0,[31,109]]]],26],27],[[[202,[[0,[31,158]]]],26],27],[[203,26],27],[[204,26],27],[[205,26],27],[[206,26],27],[[[207,[31]],26],27],[[208,26],27],[[[209,[31]],26],27],[[210,26],27],[[211,26],27],[[212,26],27],[[[213,[[0,[31,158]],[0,[31,158]]]],26],27],[[[166,[[0,[31,158]]]],26],27],[[[214,[[0,[31,158]]]],26],27],[[215,26],27],[[162,26],27],[[163,26],27],[[216,26],27],[[[217,[31]],26],27],[[[218,[31,31]],26],27],[[[164,[31]],26],27],[[159,26],27],[[184,26],27],[[219,26],27],[[220,26],27],[[[221,[31]],26],27],[[222,26],27],[[223,26],27],[[224,26],27],[[[225,[[0,[31,109]]]],26],27],[[226,26],27],[[227,26],27],[[228,26],27],[[[229,[31]],26],27],[[[230,[31,31]],26],27],[[[232,[[0,[31,231]]]],26],27],[[[233,[[0,[31,231]]]],26],27],[[[183,[234]],26],27],[[[183,[31]],26],27],[[235,26],27],[[236,26],27],[[237,26],27],[[238,26],27],[[[239,[31]],26],27],[[[240,[31]],26],27],[[241,26],27],[[[177,[31]],26],27],[[[167,[31]],26],27],[[[168,[31]],26],27],[[[169,[31]],26],27],[[[170,[31]],26],27],[[[242,[31]],26],27],[[[171,[31]],26],27],[[[172,[31]],26],27],[[[243,[31,31]],26],27],[[173,26],27],[[244,26],27],[[245,26],27],[[[181,[31]],26],27],[[],193],[[],193],[[],195],[[],195],[[[188,[[19,[18]]]]],216],[[[188,[[19,[18]]]]],216],[[],194],[[],194],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[179,[7]]]],[[],180],[70,[[181,[[0,[158,71]]]]]],[[],196],[[],196],0,[[[182,[158]]]],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],19],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[[[209,[158]]]],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]]],[237],[238],[239],[240],[243],[244],[[[17,[[182,[158]]]]],17],[[[17,[185]]],17],[[[17,[186]]],17],[[[17,[189]]],17],[[[17,[196]]],17],[[[17,[[197,[[0,[158,71]]]]]]],[[19,[17]]]],[[[17,[198]]],17],[[[17,[199]]],17],[[[17,[187]]],17],[[[17,[[202,[158]]]]],17],[[[17,[[207,[158]]]]],17],[[[17,[208]]],17],[[[17,[[209,[158]]]]],17],[[[17,[210]]],17],[[[17,[211]]],17],[[[17,[212]]],17],[[[17,[[213,[158,158]]]]]],[[[17,[[166,[158]]]]],17],[[[17,[[214,[158]]]]],17],[[[17,[215]]],17],[[[17,[162]]],17],[[[17,[163]]],17],[[[17,[[164,[[0,[158,246]]]]]]],17],[[[17,[159]]],17],[[[17,[184]]],17],[[[17,[219]]],17],[[[17,[220]]],17],[[[17,[221]]],17],[[[17,[222]]],17],[[[17,[223]]],17],[[[17,[224]]],17],[[[17,[227]]],17],[[[17,[228]]],17],[[[17,[229]]],17],[[[17,[[233,[231]]]]],17],[[[17,[237]]],17],[[[17,[238]]],17],[[[17,[239]]],17],[[[17,[240]]],17],[[[17,[243]]]],[[[17,[244]]]],[[[182,[158]]]],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],19],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[[[209,[158]]]],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]]],[237],[238],[239],[240],[243],[244],[173],[[],198],[[],198],[[],220],[[],220],[[],219],[[],219],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],177],[[],177],[[],197],[[],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[182,[158]]],158],[185],[186],[189],[196],[[[197,[[0,[158,71]]]]],[[19,[[0,[158,71]]]]]],[198],[199],[187],[[[202,[158]]],158],[[[207,[158]]],158],[208],[[[209,[158]]],158],[210],[211],[212],[[[213,[158,158]]]],[[[166,[158]]],158],[[[214,[158]]],158],[215],[162],[163],[[[164,[[0,[158,246]]]]],[[0,[158,246]]]],[159],[184],[219],[220],[221],[222],[223],[224],[227],[228],[229],[[[233,[231]]],231],[237],[238],[239],[240],[243],[244],[180],[[[180,[71]]]],[180],[181],[[[181,[[0,[158,71]]]]]],[181],[[],221],[[],221],[196,20],[[[179,[7]]],20],[211,20],[[],20],[[[179,[7]]],20],[[[180,[7]]],20],[165,20],[175,20],[192,20],[176,20],[178,20],[[[182,[[0,[158,157]]]]],20],[185,20],[186,20],[189,20],[193,20],[194,20],[195,20],[[[196,[158]]],20],[[[197,[[0,[158,71]]]]],20],[198,20],[199,20],[187,20],[[[200,[[0,[109,157,71]]]]],20],[[[201,[[0,[109,157,71]]]]],20],[[[202,[158]]],20],[[[203,[158]]],20],[[[204,[158]]],20],[205,20],[206,20],[[[207,[157]]],20],[208,20],[209,20],[210,20],[211,20],[212,20],[213,20],[[[166,[157]]],20],[[[214,[157]]],20],[215,20],[162,20],[216,20],[[[164,[[0,[157,246]]]]],20],[159,20],[184,20],[219,20],[220,20],[[[221,[[0,[231,157]]]]],20],[222,20],[223,20],[224,20],[[[225,[[0,[109,231,71,157]]]]],20],[227,20],[228,20],[229,20],[230,20],[[[233,[[0,[231,157]]]]],20],[235,20],[237,20],[238,20],[241,20],[168,20],[[[169,[247]]],20],[170,20],[[[242,[7]]],20],[171,20],[[[172,[158]]],20],[243,20],[244,20],[245,20],[[[181,[[0,[158,71]]]]],20],[[],167],[[],14],[[],14],[[[179,[7]]],18],[[],199],[[],199],[[],223],[[],223],[[],222],[[],222],[[],[[179,[7]]]],[[],200],[[],200],[[[17,[[202,[158]]]]],[[205,[158]]]],[[[17,[[202,[158]]]]],[[206,[158]]]],[7,[[242,[7]]]],[[],224],[[],224],[[[17,[[202,[158]]]]],[[203,[158]]]],[[[17,[[202,[158]]]]],[[204,[158]]]],[[],202],[[],202],[[],171],[[[17,[175]],74],75],[[[17,[192]],74],75],[[[17,[176]],74],75],[[[17,[193]],74],75],[[[17,[194]],74],75],[[[17,[195]],74],75],[[[17,[[197,[[0,[158,71]]]]]],74],75],[[[17,[[200,[[0,[109,158,71]]]]]],74],75],[[[17,[[201,[[0,[109,157,71]]]]]],74],75],[[[17,[203]],74],75],[[[17,[204]],74],75],[[[17,[205]],74],75],[[[17,[206]],74],75],[[[17,[216]],74],75],[[[17,[[225,[[0,[109,231,71]]]]]],74],75],[[[17,[226]],74],75],[[[17,[230]],74],75],[[[17,[232]],74],75],[[[17,[235]],74],75],[[[17,[241]],74],75],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[76]]]],[[[17,[177]],74],[[75,[76]]]],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[76]]]],[[],191],[158,[[172,[158]]]],[[17,74],[[75,[19]]]],[[[17,[191]],74],[[75,[19]]]],[[[17,[[179,[7]]]],74],[[75,[19]]]],[[[17,[[180,[7]]]],74],[[75,[19]]]],[[[17,[165]],74],[[75,[19]]]],[[[17,[178]],74],[[75,[19]]]],[[[17,[[182,[158]]]],74],[[75,[19]]]],[[[17,[185]],74],[[75,[19]]]],[[[17,[186]],74],[[75,[19]]]],[[[17,[189]],74],[[75,[19]]]],[[[17,[[196,[158]]]],74],[[75,[19]]]],[[[17,[198]],74],[[75,[19]]]],[[[17,[199]],74],[[75,[19]]]],[[[17,[187]],74],[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[[207,[158]]]],74],[[75,[19]]]],[[[17,[208]],74],[[75,[19]]]],[[[17,[209]],74],[[75,[19]]]],[[[17,[210]],74],[[75,[19]]]],[[[17,[211]],74],[[75,[19]]]],[[[17,[212]],74],[[75,[19]]]],[[[17,[213]],74],[[75,[19]]]],[[[17,[[166,[158]]]],74],[[75,[19]]]],[[[17,[[214,[158]]]],74],[[75,[19]]]],[[[17,[215]],74],[[75,[19]]]],[[[17,[162]],74],[[75,[19]]]],[[[17,[163]],74],[[75,[19]]]],[[[17,[[217,[158]]]],74],[[75,[19]]]],[[[17,[[164,[[0,[158,246]]]]]],74],[[75,[19]]]],[[[17,[159]],74],[[75,[19]]]],[[[17,[184]],74],[[75,[19]]]],[[[17,[219]],74],[[75,[19]]]],[[[17,[220]],74],[[75,[19]]]],[[[17,[[221,[231]]]],74],[[75,[19]]]],[[[17,[222]],74],[[75,[19]]]],[[[17,[223]],74],[[75,[19]]]],[[[17,[224]],74],[[75,[19]]]],[[[17,[227]],74],[[75,[19]]]],[[[17,[228]],74],[[75,[19]]]],[[[17,[229]],74],[[75,[19]]]],[[[17,[[233,[231]]]],74],[[75,[19]]]],[[[17,[236]],74],[[75,[19]]]],[[[17,[237]],74],[[75,[19]]]],[[[17,[238]],74],[[75,[19]]]],[[[17,[239]],74],[[75,[19]]]],[[[17,[240]],74],[[75,[19]]]],[[[17,[167]],74],[[75,[19]]]],[[[17,[168]],74],[[75,[19]]]],[[[17,[[169,[247]]]],74],[[75,[19]]]],[[[17,[170]],74],[[75,[19]]]],[[[17,[[242,[7]]]],74],[[75,[19]]]],[[[17,[171]],74],[[75,[19]]]],[[[17,[172]],74],[[75,[19]]]],[[[17,[243]],74],[[75,[19]]]],[[[17,[244]],74],[[75,[19]]]],[[[17,[245]],74],[[75,[19]]]],[[[17,[[181,[[0,[158,71]]]]]],74],[[75,[19]]]],[74,[[75,[19]]]],[74,[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[[202,[158]]]],74],[[75,[19]]]],[[[17,[177]],74],[[75,[[76,[18]]]]]],[[[17,[182]],74],[[75,[21]]]],[[[17,[185]],74],[[75,[21]]]],[[[17,[186]],74],[[75,[21]]]],[[[17,[189]],74],[[75,[21]]]],[[[17,[[196,[[0,[158,152]]]]]],74],[[75,[21]]]],[[[17,[198]],74],[[75,[21]]]],[[[17,[199]],74],[[75,[21]]]],[[[17,[187]],74],[[75,[21]]]],[[[17,[202]],74],[[75,[21]]]],[[[17,[207]],74],[[75,[21]]]],[[[17,[208]],74],[[75,[21]]]],[[[17,[209]],74],[[75,[21]]]],[[[17,[210]],74],[[75,[21]]]],[[[17,[211]],74],[[75,[21]]]],[[[17,[212]],74],[[75,[21]]]],[[[17,[166]],74],[[75,[21]]]],[[[17,[214]],74],[[75,[21]]]],[[[17,[215]],74],[[75,[21]]]],[[[17,[162]],74],[[75,[21]]]],[[[17,[163]],74],[[75,[21]]]],[[[17,[[218,[152]]]],74],[[75,[21]]]],[[[17,[159]],74],[[75,[21]]]],[[[17,[184]],74],[[75,[21]]]],[[[17,[219]],74],[[75,[21]]]],[[[17,[220]],74],[[75,[21]]]],[[[17,[[221,[152]]]],74],[[75,[21]]]],[[[17,[222]],74],[[75,[21]]]],[[[17,[223]],74],[[75,[21]]]],[[[17,[224]],74],[[75,[21]]]],[[[17,[227]],74],[[75,[21]]]],[[[17,[228]],74],[[75,[21]]]],[[[17,[229]],74],[[75,[21]]]],[[[17,[233]],74],[[75,[21]]]],[[[17,[237]],74],[[75,[21]]]],[[[17,[238]],74],[[75,[21]]]],[[[17,[239]],74],[[75,[21]]]],[[[17,[240]],74],[[75,[21]]]],[[[17,[177]],74],[[75,[[76,[18]]]]]],[79],[79],[[[179,[7]],7]],[[[179,[7]],7]],[[[179,[7]],7]],[18,214],[18,214],[[],168],[247,[[169,[247]]]],[[[217,[71]],[218,[71]]],[[21,[71,[190,[71]]]]]],[[[218,[[0,[152,71]]]],[217,[[0,[152,71]]]]],[[21,[[0,[152,71]],[190,[[0,[152,71]]]]]]]],[[],14],[[],14],[[],215],[[],215],[[],243],0,[[],181],[[],201],[[],201],[[],244],[[]],[[[179,[7]]]],[[[180,[7]]]],[165],[178],[[[182,[158]]]],[185],[186],[189],[[[196,[158]]]],[198],[199],[187],[[[202,[158]]]],[[[207,[158]]]],[208],[209],[210],[211],[212],[213],[[[166,[158]]]],[[[214,[158]]]],[215],[162],[163],[[[164,[[0,[158,246]]]]]],[159],[184],[219],[220],[[[221,[231]]]],[222],[223],[224],[227],[228],[[[233,[231]]]],[237],[238],[167],[168],[[[169,[247]]]],[170],[[[242,[7]]]],[171],[172],[18,207],[18,207],[[],208],[[],208],[[[180,[248]],248],[[21,[249]]]],[[[180,[250]],250],[[21,[249]]]],[[]],[[]],[[[17,[182]]],21],[[[17,[185]]],21],[[[17,[186]]],21],[[[17,[189]]],21],[[[17,[[196,[[0,[158,152]]]]]]],21],[[[17,[198]]],21],[[[17,[199]]],21],[[[17,[187]]],21],[[[17,[202]]],21],[[[17,[207]]],21],[[[17,[208]]],21],[[[17,[209]]],21],[[[17,[210]]],21],[[[17,[211]]],21],[[[17,[212]]],21],[[[17,[166]]],21],[[[17,[214]]],21],[[[17,[215]]],21],[[[17,[162]]],21],[[[17,[163]]],21],[[[17,[[218,[152]]]]],21],[[[17,[159]]],21],[[[17,[184]]],21],[[[17,[219]]],21],[[[17,[220]]],21],[[[17,[[221,[152]]]]],21],[[[17,[222]]],21],[[[17,[223]]],21],[[[17,[224]]],21],[[[17,[227]]],21],[[[17,[228]]],21],[[[17,[229]]],21],[[[17,[233]]],21],[[[17,[237]]],21],[[[17,[238]]],21],[[[17,[239]]],21],[[[17,[240]]],21],[18,209],[18,209],[211,19],[211,19],[[],211],[[],211],[[],210],[[],210],[[],212],[[],212],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],80],[[],80],[173,173],[18,239],[18,239],[18,240],[18,240],[18,233],[18,233],[[],[[230,[[0,[101,174]]]]]],[[],[[230,[[0,[101,174]]]]]],[[],232],[[],232],[[],227],[[],227],[[],228],[[],228],[[],229],[[],229],[[],235],[[],235],[[],226],[[],226],[[[188,[[19,[18]]]]],241],[[[188,[[19,[18]]]]],241],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],225],[[],225],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],75],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[[17,74],[[75,[[19,[21]]]]]],[74,[[75,[[19,[21]]]]]],[74,[[75,[[19,[21]]]]]],[[],237],[[],237],[[],238],[[],238],[[],236],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],81],[[],245],[[],192],[[],192],[[],213],[[],213],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[180],[[[251,[31]],26],27],[[[252,[[0,[31,71]]]],26],27],[[[253,[31]],26],27],[[[254,[[0,[31,71]]]],26],27],[[[255,[[0,[31,71]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[180,20],[180,254],[180,252],[[[17,[180]]],251],[[[17,[180]]],253],[180,18],[[],180],[251,19],[[[252,[71]]],19],[253,19],[[[254,[71]]],19],[[[255,[71]]],19],[180],[251],[[[252,[71]]]],[253],[[[254,[71]]]],[[[255,[71]]]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],81],[[],81],[[],81],[[],81],[[],81],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[[181,[[0,[158,71]]]]]],[[[256,[[0,[31,71]]]],26],27],[[[257,[[0,[31,71]]]],26],27],[[[258,[[0,[31,71]]]],26],27],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[181,[[0,[158,71]]]]],20],[[[181,[[0,[158,71]]]]],[[256,[[0,[158,71]]]]]],[[[181,[[0,[158,71]]]]],[[257,[[0,[158,71]]]]]],[[[181,[[0,[158,71]]]]],18],[[],[[181,[[0,[158,71]]]]]],[[[256,[[0,[158,71]]]]],19],[[[257,[[0,[158,71]]]]],19],[[[258,[[0,[158,71]]]]],19],[[[181,[[0,[158,71]]]],[0,[158,71]]]],[[],181],[[[256,[[0,[158,71]]]]]],[[[257,[[0,[158,71]]]]]],[[[258,[[0,[158,71]]]]]],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],81],[[],81],[[],81],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],259],[260,261],[7],[248],[[249,26],[[21,[262]]]],[[249,26],[[21,[262]]]],[[259,26],[[21,[262]]]],[[248,26],[[21,[262]]]],[[250,26],[[21,[262]]]],[[260,26],[[21,[262]]]],[[]],[[]],[[[130,[263]]],248],[[[17,[[130,[263]]]]],248],[[[130,[7,263]]],248],[[[17,[[130,[7,263]]]]],248],[[]],[250,248],[[]],[[[17,[[130,[263]]]]],250],[[[130,[7,263]]],250],[[[17,[[130,[7,263]]]]],250],[[[130,[263]]],250],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[248,250],[[],7],[249,20],[[],259],[[],248],[[],250],[261,260],[[[264,[261]]],260],[[],261],[[],261],[[[17,[248]],74],75],[[[17,[250]],74],75],[79],[[259,261]],[[],249],[[],[[21,[249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[248,[[21,[249]]]],[[],[[21,[40,249]]]],[[],[[21,[40,249]]]],[250,[[21,[249]]]],[[],[[21,[40,249]]]],[[],[[21,[40,249]]]],[[],[[21,[249]]]],[[],[[21,[249]]]],[259,[[19,[261]]]],[[],80],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[17,74],75],[[17,74],75],[[],81],[[],81],[[],81],[[],81],[[],81],[142],[259],[142],[142,261],[142,260]],"p":[[3,"AbortHandle"],[3,"AndThen"],[6,"BoxFuture"],[6,"LocalBoxFuture"],[3,"CatchUnwind"],[3,"Shared"],[8,"Future"],[3,"WeakShared"],[3,"Pending"],[8,"Clone"],[3,"OptionFuture"],[3,"PollImmediate"],[3,"Ready"],[4,"Either"],[3,"Abortable"],[3,"Aborted"],[3,"Pin"],[15,"usize"],[4,"Option"],[15,"bool"],[4,"Result"],[3,"ErrInto"],[3,"Flatten"],[3,"FlattenSink"],[3,"FlattenStream"],[3,"Formatter"],[6,"Result"],[3,"PollFn"],[3,"JoinAll"],[3,"TryJoinAll"],[8,"Debug"],[3,"Fuse"],[3,"Map"],[3,"IntoStream"],[3,"MapInto"],[3,"Then"],[3,"Inspect"],[3,"NeverError"],[3,"UnitError"],[3,"RemoteHandle"],[3,"Remote"],[3,"IntoFuture"],[3,"TryFlatten"],[3,"TryFlattenStream"],[3,"OrElse"],[3,"OkInto"],[3,"InspectOk"],[3,"InspectErr"],[3,"MapOk"],[3,"MapErr"],[3,"MapOkOrElse"],[3,"UnwrapOrElse"],[3,"Lazy"],[4,"MaybeDone"],[8,"TryFuture"],[4,"TryMaybeDone"],[3,"Join"],[3,"Join3"],[3,"Join4"],[3,"Join5"],[3,"Select"],[3,"SelectAll"],[3,"TryJoin"],[3,"TryJoin3"],[3,"TryJoin4"],[3,"TryJoin5"],[3,"TrySelect"],[3,"SelectOk"],[3,"AbortRegistration"],[8,"IntoIterator"],[8,"Unpin"],[3,"Vec"],[8,"FusedFuture"],[3,"Context"],[4,"Poll"],[6,"Result"],[4,"SeekFrom"],[15,"u64"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"AsMut"],[3,"Window"],[8,"AsRef"],[8,"AsyncRead"],[3,"BufReader"],[8,"AsyncWrite"],[3,"BufWriter"],[3,"LineWriter"],[3,"Chain"],[3,"AllowStdIo"],[3,"Cursor"],[3,"Close"],[8,"Ord"],[4,"Ordering"],[3,"Empty"],[8,"AsyncBufRead"],[3,"Take"],[3,"Copy"],[3,"CopyBuf"],[8,"Default"],[8,"PartialEq"],[3,"FillBuf"],[3,"Flush"],[3,"Repeat"],[3,"Sink"],[3,"ReuniteError"],[3,"SeeKRelative"],[8,"Sized"],[3,"CopyBufAbortable"],[3,"IntoSink"],[3,"Lines"],[3,"Read"],[3,"ReadVectored"],[3,"ReadExact"],[3,"ReadLine"],[3,"ReadToEnd"],[3,"ReadToString"],[3,"ReadUntil"],[3,"Seek"],[3,"ReadHalf"],[3,"WriteHalf"],[3,"Write"],[3,"WriteVectored"],[3,"WriteAll"],[8,"Hash"],[8,"PartialOrd"],[8,"AsyncSeek"],[3,"Error"],[3,"Box"],[15,"u8"],[15,"i64"],[8,"RangeBounds"],[3,"Arguments"],[3,"Mutex"],[3,"OwnedMutexGuard"],[3,"MutexGuard"],[3,"MappedMutexGuard"],[3,"OwnedMutexLockFuture"],[3,"MutexLockFuture"],[15,"never"],[3,"Arc"],[3,"Buffer"],[3,"Drain"],[3,"SinkMapErr"],[3,"With"],[3,"Close"],[3,"Fanout"],[3,"Feed"],[3,"Flush"],[3,"SendAll"],[8,"Sink"],[3,"SinkErrInto"],[3,"Send"],[3,"Unfold"],[3,"WithFlatMap"],[8,"FusedStream"],[8,"Stream"],[3,"AndThen"],[6,"BoxStream"],[6,"LocalBoxStream"],[3,"BufferUnordered"],[3,"Buffered"],[3,"CatchUnwind"],[3,"Chain"],[3,"Chunks"],[3,"Iter"],[3,"Repeat"],[3,"RepeatWith"],[3,"Empty"],[3,"Pending"],[3,"PollImmediate"],[4,"PollNext"],[8,"Extend"],[3,"Collect"],[3,"Concat"],[3,"IntoAsyncRead"],[3,"Cycle"],[3,"FuturesOrdered"],[3,"FuturesUnordered"],[3,"SelectAll"],[3,"Enumerate"],[3,"TryChunksError"],[3,"ErrInto"],[3,"Filter"],[3,"FilterMap"],[3,"FlatMap"],[8,"Into"],[3,"Flatten"],[3,"ReuniteError"],[3,"PollFn"],[3,"Unzip"],[3,"Fold"],[3,"Forward"],[3,"ForEach"],[3,"Fuse"],[3,"StreamFuture"],[3,"Inspect"],[3,"Map"],[3,"Next"],[3,"SelectNextSome"],[3,"Peekable"],[3,"Peek"],[3,"PeekMut"],[3,"NextIf"],[3,"NextIfEq"],[3,"Skip"],[3,"SkipWhile"],[3,"Take"],[3,"TakeWhile"],[3,"TakeUntil"],[3,"Then"],[3,"Zip"],[3,"ReadyChunks"],[3,"Scan"],[3,"ForEachConcurrent"],[3,"SplitStream"],[3,"SplitSink"],[3,"InspectOk"],[3,"InspectErr"],[3,"IntoStream"],[3,"MapOk"],[3,"MapErr"],[3,"OrElse"],[3,"TryNext"],[3,"TryForEach"],[3,"TryFilter"],[3,"TryFilterMap"],[3,"TryFlatten"],[3,"TryCollect"],[8,"TryStream"],[3,"TryConcat"],[3,"TryChunks"],[8,"Display"],[3,"TryFold"],[3,"TryUnfold"],[3,"TrySkipWhile"],[3,"TryTakeWhile"],[3,"TryBufferUnordered"],[3,"TryBuffered"],[3,"TryForEachConcurrent"],[3,"Once"],[3,"Select"],[3,"SelectWithStrategy"],[3,"Unfold"],[8,"UnwindSafe"],[8,"FnMut"],[3,"LocalFutureObj"],[3,"SpawnError"],[3,"FutureObj"],[3,"IterPinMut"],[3,"IterMut"],[3,"IterPinRef"],[3,"Iter"],[3,"IntoIter"],[3,"Iter"],[3,"IterMut"],[3,"IntoIter"],[3,"AtomicWaker"],[3,"WakerRef"],[3,"Waker"],[3,"Error"],[3,"Global"],[3,"ManuallyDrop"],[8,"TryFutureExt"],[8,"FutureExt"],[8,"UnsafeFutureObj"],[13,"Left"],[13,"Right"],[13,"Future"],[13,"Done"],[13,"Future"],[13,"Done"],[8,"AsyncReadExt"],[8,"AsyncWriteExt"],[8,"AsyncBufReadExt"],[8,"AsyncSeekExt"],[8,"SinkExt"],[8,"StreamExt"],[8,"TryStreamExt"],[8,"SpawnExt"],[8,"LocalSpawnExt"],[8,"LocalSpawn"],[8,"Spawn"],[8,"ArcWake"]]},\ -"generic_array":{"doc":"This crate implements a structure that can be used as a …","t":[8,16,3,2,11,0,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,0,11,11,11,11,11,0,11,11,11,11,11,11,11,8,6,16,8,16,8,6,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,16,8,16,8,16,16,16,16,16,6,8,16,8,10,10,10,10,10,10,10],"n":["ArrayLength","ArrayType","GenericArray","GenericArrayIter","append","arr","arr","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut_slice","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_slice","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from_slice","cmp","concat","default","deref","deref_mut","eq","fmt","fmt","fmt","fold","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_exact_iter","from_iter","from_mut_slice","from_slice","functional","generate","hash","into","into_iter","into_iter","into_iter","iter","map","partial_cmp","pop_back","pop_front","prepend","sequence","split","split","split","try_from","try_into","type_id","zip","AddLength","Inc","Output","FunctionalSequence","Mapped","MappedGenericSequence","MappedSequence","fold","map","zip","GenericArrayIter","as_mut_slice","as_slice","borrow","borrow_mut","clone","count","drop","fmt","fold","from","into","into_iter","last","len","next","next_back","nth","rfold","size_hint","try_from","try_into","type_id","Concat","First","GenericSequence","Length","Lengthen","Longer","Output","Rest","Second","Sequence","SequenceItem","Shorten","Shorter","Split","append","concat","generate","pop_back","pop_front","prepend","split"],"q":["generic_array","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","generic_array::arr","","","generic_array::functional","","","","","","","generic_array::iter","","","","","","","","","","","","","","","","","","","","","","","generic_array::sequence","","","","","","","","","","","","","","","","","","","",""],"d":["Trait making GenericArray work, marking types to be used …","Associated type representing the array type for the number","Struct representing a generic array - GenericArray<T, N> …","","","Implementation for arr! macro.","Macro allowing for easy generation of Generic Arrays. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Extracts a mutable slice containing the entire array.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Extracts a slice containing the entire array.","","","","","","Construct a GenericArray from a slice by cloning its …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts mutable slice to a mutable generic array reference","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Converts slice to a generic array reference with inferred …","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new GenericArray instance from an iterator with …","","Converts mutable slice to a mutable generic array reference","Converts slice to a generic array reference with inferred …","Functional programming with generic sequences","","","Calls U::from(self).","","","","GenericArray iterator implementation.","","","","","","Useful traits for manipulating sequences of data stored in …","","","","","","","","Helper trait for arr! macro","Helper type for arr! macro","Resulting length","Defines functional programming methods for generic …","Mapped sequence type","Defines the relationship between one generic sequence and …","Accessor type for a mapped generic sequence","Folds (or reduces) a sequence of data into a single value.","Maps a GenericSequence to another GenericSequence.","Combines two GenericSequence instances and iterates …","An iterator that moves out of a GenericArray","Returns the remaining items of this iterator as a mutable …","Returns the remaining items of this iterator as a slice","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","Defines GenericSequences which can be joined together, …","First part of the resulting split array","Defines some sequence with an associated length and …","GenericArray associated length","Defines any GenericSequence which can be lengthened or …","GenericSequence that has one more element than Self","Resulting sequence formed by the concatenation.","Sequence to be concatenated with self","Second part of the resulting split array","Concrete sequence type used in conjuction with reference …","Accessor for GenericSequence item type, which is really …","Defines a GenericSequence which can be shortened by …","GenericSequence that has one less element than Self","Defines a GenericSequence that can be split into two parts …","Returns a new array with the given element appended to the …","Concatenate, or join, two sequences.","Initializes a new sequence instance using the given …","Returns a new array without the last element, and the last …","Returns a new array without the first element, and the …","Returns a new array with the given element prepended to …","Splits an array at the given index, returning the separate …"],"i":[0,1,0,0,2,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,0,2,2,2,2,2,0,2,2,2,2,2,2,2,0,0,98,0,99,0,0,100,100,100,0,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,0,101,0,102,0,103,104,104,101,102,0,0,105,0,103,104,102,105,105,103,101],"f":[0,0,0,0,[[[2,[1]]]],0,0,[[[2,[3]]]],[[[2,[4]]]],[[[2,[5]]]],[[[2,[6]]]],[[[2,[7]]]],[[[2,[8]]]],[[[2,[9]]]],[[[2,[10]]]],[[[2,[11]]]],[[[2,[12]]]],[[[2,[13]]]],[[[2,[14]]]],[[[2,[15]]]],[[[2,[16]]]],[[[2,[17]]]],[[[2,[18]]]],[[[2,[19]]]],[[[2,[20]]]],[[[2,[21]]]],[[[2,[22]]]],[[[2,[23]]]],[[[2,[24]]]],[[[2,[25]]]],[[[2,[26]]]],[[[2,[27]]]],[[[2,[28]]]],[[[2,[29]]]],[[[2,[30]]]],[[[2,[31]]]],[[[2,[32]]]],[[[2,[33]]]],[[[2,[34]]]],[[[2,[35]]]],[[[2,[36]]]],[[[2,[37]]]],[[[2,[38]]]],[[[2,[39]]]],[[[2,[40]]]],[[[2,[41]]]],[[[2,[42]]]],[[[2,[43]]]],[[[2,[44]]]],[[[2,[45]]]],[[[2,[46]]]],[[[2,[47]]]],[[[2,[48]]]],[[[2,[49]]]],[[[2,[50]]]],[[[2,[51]]]],[[[2,[52]]]],[[[2,[53]]]],[[[2,[54]]]],[[[2,[55]]]],[[[2,[56]]]],[[[2,[57]]]],[[[2,[58]]]],[[[2,[59]]]],[[[2,[60]]]],[[[2,[61]]]],[[[2,[62]]]],[[[2,[63]]]],[[[2,[64]]]],[[[2,[65]]]],[[[2,[66]]]],[[[2,[67]]]],[[[2,[68]]]],[[[2,[69]]]],[[[2,[70]]]],[2],[[[2,[71]]]],[[[2,[72]]]],[[[2,[73]]]],[[[2,[74]]]],[[[2,[75]]]],[[[2,[76]]]],[[[2,[77]]]],[[[2,[78]]]],[[[2,[79]]]],[2],[[[2,[24]]]],[[[2,[25]]]],[[[2,[77]]]],[[[2,[76]]]],[[[2,[73]]]],[[[2,[72]]]],[[[2,[40]]]],[[[2,[32]]]],[[[2,[65]]]],[[[2,[41]]]],[2],[[[2,[5]]]],[[[2,[69]]]],[[[2,[6]]]],[[[2,[67]]]],[[[2,[7]]]],[[[2,[68]]]],[[[2,[8]]]],[[[2,[4]]]],[[[2,[64]]]],[[[2,[43]]]],[[[2,[9]]]],[[[2,[63]]]],[[[2,[11]]]],[[[2,[61]]]],[[[2,[12]]]],[[[2,[62]]]],[[[2,[13]]]],[[[2,[60]]]],[[[2,[59]]]],[[[2,[58]]]],[[[2,[56]]]],[[[2,[57]]]],[[[2,[14]]]],[[[2,[53]]]],[[[2,[52]]]],[[[2,[54]]]],[[[2,[15]]]],[[[2,[50]]]],[[[2,[49]]]],[[[2,[48]]]],[[[2,[51]]]],[[[2,[46]]]],[[[2,[47]]]],[[[2,[45]]]],[[[2,[44]]]],[[[2,[66]]]],[[[2,[75]]]],[[[2,[17]]]],[[[2,[3]]]],[[[2,[78]]]],[[[2,[42]]]],[[[2,[18]]]],[[[2,[70]]]],[[[2,[39]]]],[[[2,[16]]]],[[[2,[37]]]],[[[2,[38]]]],[[[2,[35]]]],[[[2,[34]]]],[[[2,[33]]]],[[[2,[31]]]],[[[2,[36]]]],[[[2,[27]]]],[[[2,[30]]]],[[[2,[29]]]],[[[2,[28]]]],[[[2,[71]]]],[[[2,[55]]]],[[[2,[26]]]],[[[2,[74]]]],[[[2,[79]]]],[[[2,[23]]]],[[[2,[20]]]],[[[2,[22]]]],[[[2,[21]]]],[[[2,[19]]]],[[[2,[10]]]],[2],[2],[[]],[[]],[2],[[[2,[80]]],[[2,[80]]]],[[],[[2,[80]]]],[[[2,[81]],2],82],[2],[[],[[2,[83]]]],[2],[2],[[[2,[84]],[2,[84]]],85],[[[2,[86,[1,[86]]]],87],88],[[[2,[86,[1,[86]]]],87],88],[[[2,[89]],87],88],[2],[[],2],[[],2],[[],2],[[],2],[[],[[2,[26]]]],[[],2],[[],2],[[],[[2,[22]]]],[[],2],[[],2],[[],[[2,[18]]]],[[],2],[[],2],[[],[[2,[12]]]],[[],2],[[],2],[[],[[2,[6]]]],[[],2],[[],2],[[],[[2,[40]]]],[[],2],[[],2],[[],[[2,[28]]]],[[],2],[[],2],[[],[[2,[20]]]],[[],2],[[],2],[[],[[2,[14]]]],[[],2],[[],2],[[],[[2,[5]]]],[[],2],[[],2],[[],[[2,[27]]]],[[],2],[[],2],[[],[[2,[16]]]],[[],2],[[],2],[[],[[2,[32]]]],[[],2],[[],2],[[],[[2,[10]]]],[[],2],[[],2],[[],[[2,[24]]]],[[],2],[[],2],[[],[[2,[8]]]],[[],2],[[],2],[[],[[2,[36]]]],[[],2],[[],2],[[],2],[[],[[2,[38]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[4]]]],[[],2],[[],2],[[],[[2,[34]]]],[[],2],[[],[[2,[42]]]],[[],2],[[],2],[[],2],[[],[[2,[39]]]],[[],[[2,[43]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[47]]]],[[],2],[[],2],[[],[[2,[3]]]],[[],2],[[],[[2,[51]]]],[[],2],[[],2],[[],2],[[],[[2,[45]]]],[[],[[2,[54]]]],[[],2],[[],2],[[],2],[[],[[2,[49]]]],[[],[[2,[57]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[60]]]],[[],2],[[],2],[[],[[2,[53]]]],[[],2],[[],[[2,[62]]]],[[],2],[[],2],[[],2],[[],[[2,[59]]]],[[],[[2,[63]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[66]]]],[[],2],[[],2],[[],[[2,[61]]]],[[],2],[[],[[2,[68]]]],[[],2],[[],2],[[],2],[[],[[2,[64]]]],[[],[[2,[69]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[55]]]],[[],2],[[],2],[[],[[2,[67]]]],[[],2],[[],[[2,[75]]]],[[],2],[[],2],[[],2],[[],[[2,[70]]]],[[],[[2,[77]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[78]]]],[[],2],[[],2],[[],[[2,[73]]]],[[],2],[[],[[2,[74]]]],[[],2],[[],2],[[],2],[[],[[2,[79]]]],[[],[[2,[72]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[65]]]],[[],[[2,[30]]]],[[],2],[[],[[2,[76]]]],[[],2],[[],[[2,[58]]]],[[],2],[[],2],[[],2],[[]],[[],[[2,[56]]]],[[],2],[[],2],[[],2],[[],[[2,[7]]]],[[],[[2,[52]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[50]]]],[[],2],[[],2],[[],[[2,[9]]]],[[],2],[[],[[2,[48]]]],[[],2],[[],2],[[],2],[[],[[2,[11]]]],[[],[[2,[46]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[44]]]],[[],2],[[],2],[[],[[2,[13]]]],[[],2],[[],[[2,[41]]]],[[],2],[[],2],[[],2],[[],[[2,[15]]]],[[],[[2,[37]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[35]]]],[[],2],[[],2],[[],[[2,[17]]]],[[],2],[[],[[2,[33]]]],[[],2],[[],2],[[],2],[[],[[2,[19]]]],[[],[[2,[31]]]],[[],2],[[],2],[[],2],[[],2],[[],[[2,[29]]]],[[],2],[[],2],[[],[[2,[21]]]],[[],2],[[],[[2,[71]]]],[[],2],[[],2],[[],2],[[],[[2,[23]]]],[[],[[2,[25]]]],[[],2],[[],2],[[],[[90,[2]]]],[[],2],[[],2],[[],2],0,[[],2],[[[2,[91]]]],[[]],[2],[2],[2],0,[2,[[92,[2]]]],[[[2,[93]],2],[[90,[82]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],0,[2],[2],[2],[[],94],[[],94],[[],95],[2,[[92,[2]]]],0,0,0,0,0,0,0,[[]],[[],92],[[],92],0,[96],[96],[[]],[[]],[[[96,[80]]],[[96,[80]]]],[96,97],[96],[[[96,[89]],87],88],[96],[[]],[[]],[[]],[96,90],[96,97],[96,90],[96,90],[[96,97],90],[96],[96],[[],94],[[],94],[[],95],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]]],"p":[[8,"ArrayLength"],[3,"GenericArray"],[6,"U12"],[6,"U34"],[6,"U25"],[6,"U20"],[6,"U1024"],[6,"U31"],[6,"U1000"],[6,"U29"],[6,"U512"],[6,"U19"],[6,"U256"],[6,"U24"],[6,"U128"],[6,"U27"],[6,"U500"],[6,"U18"],[6,"U400"],[6,"U23"],[6,"U300"],[6,"U17"],[6,"U200"],[6,"U30"],[6,"U100"],[6,"U16"],[6,"U26"],[6,"U22"],[6,"U80"],[6,"U15"],[6,"U70"],[6,"U28"],[6,"U64"],[6,"U14"],[6,"U63"],[6,"U32"],[6,"U62"],[6,"U33"],[6,"U13"],[6,"U21"],[6,"U61"],[6,"U35"],[6,"U36"],[6,"U60"],[6,"U11"],[6,"U59"],[6,"U37"],[6,"U58"],[6,"U10"],[6,"U57"],[6,"U38"],[6,"U56"],[6,"U9"],[6,"U39"],[6,"U47"],[6,"U55"],[6,"U40"],[6,"U54"],[6,"U8"],[6,"U41"],[6,"U7"],[6,"U42"],[6,"U43"],[6,"U6"],[6,"U53"],[6,"U44"],[6,"U5"],[6,"U45"],[6,"U46"],[6,"U4"],[6,"U90"],[6,"U52"],[6,"U3"],[6,"U51"],[6,"U48"],[6,"U1"],[6,"U49"],[6,"U50"],[6,"U2"],[8,"Clone"],[8,"Ord"],[4,"Ordering"],[8,"Default"],[8,"PartialEq"],[15,"bool"],[15,"u8"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[4,"Option"],[8,"Hash"],[6,"MappedSequence"],[8,"PartialOrd"],[4,"Result"],[3,"TypeId"],[3,"GenericArrayIter"],[15,"usize"],[8,"AddLength"],[8,"MappedGenericSequence"],[8,"FunctionalSequence"],[8,"Split"],[8,"GenericSequence"],[8,"Lengthen"],[8,"Concat"],[8,"Shorten"]]},\ -"getrandom":{"doc":"Interface to the operating system’s random number …","t":[18,18,3,18,18,18,18,18,18,18,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11],"n":["CUSTOM_START","ERRNO_NOT_POSITIVE","Error","FAILED_RDRAND","INTERNAL_START","IOS_SEC_RANDOM","NODE_CRYPTO","NODE_ES_MODULE","NODE_RANDOM_FILL_SYNC","NO_RDRAND","UNSUPPORTED","VXWORKS_RAND_SECURE","WEB_CRYPTO","WEB_GET_RANDOM_VALUES","WINDOWS_RTL_GEN_RANDOM","borrow","borrow_mut","clone","clone_into","code","eq","fmt","fmt","from","from","getrandom","into","provide","raw_os_error","to_owned","to_string","try_from","try_into","type_id"],"q":["getrandom","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Codes at or above this point can be used by users to …","The platform-specific errno returned a non-positive value.","A small and no_std compatible error type","RDRAND instruction failed due to a hardware issue.","Codes below this point represent OS Errors (i.e. positive …","Call to iOS SecRandomCopyBytes failed.","Node.js does not have the crypto CommonJS module.","Called from an ES module on Node.js. This is unsupported, …","Calling Node.js function crypto.randomFillSync failed.","RDRAND instruction unsupported on this target.","This target/platform is not supported by getrandom.","On VxWorks, call to randSecure failed (random number …","The environment does not support the Web Crypto API.","Calling Web Crypto API crypto.getRandomValues failed.","Call to Windows RtlGenRandom failed.","","","","","Extract the bare error code.","","","","","Returns the argument unchanged.","Fill dest with random bytes from the system’s preferred …","Calls U::from(self).","","Extract the raw OS error code (if this error came from the …","","","","",""],"i":[1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[1,1],[[]],[1,2],[[1,1],3],[[1,4],5],[[1,4],5],[2,1],[[]],[[],[[6,[1]]]],[[]],[7],[1,[[9,[8]]]],[[]],[[],10],[[],6],[[],6],[[],11]],"p":[[3,"Error"],[3,"NonZeroU32"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"Demand"],[15,"i32"],[4,"Option"],[3,"String"],[3,"TypeId"]]},\ -"growthring":{"doc":"Simple and modular write-ahead-logging implementation.","t":[3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,13,17,16,8,4,13,6,8,3,6,3,8,3,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,10,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,10],"n":["WALFileAIO","WALStoreAIO","allocate","borrow","borrow","borrow_mut","borrow_mut","drop","drop","enumerate_files","from","from","into","into","new","new","open_file","read","remove_file","truncate","try_from","try_from","try_into","try_into","type_id","type_id","wal","write","BestEffort","CRC32","FileNameIter","Record","RecoverPolicy","Strict","WALBytes","WALFile","WALLoader","WALPos","WALRingId","WALStore","WALWriter","allocate","block_nbit","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cache_size","clone","clone","clone_into","clone_into","cmp","default","empty_id","enumerate_files","eq","file_nbit","file_pool_in_use","fmt","from","from","from","from","get_end","get_hash","get_start","grow","hash","into","into","into","into","load","new","open_file","partial_cmp","peel","read","read_recent_records","recover_policy","remove_file","serialize","serialize","to_owned","to_owned","truncate","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","write"],"q":["growthring","","","","","","","","","","","","","","","","","","","","","","","","","","","","growthring::wal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","stop recovering when hitting the first corrupted record","","","","","all checksums must be correct, otherwise recovery fails","","","","","","","","Initialize the file space in [offset, offset + length) to …","","","","","","","","","","","","","","","","","","Enumerate all WAL filenames. It should include all WAL …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Submit a sequence of records to WAL. It returns a vector …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Recover by reading the WAL files.","","Open a file given the filename, create the file if not …","","Inform the WALWriter that some data writes are complete so …","Read data with offset. Return Ok(None) when it reaches EOF.","","","Unlink a file given the filename.","","","","","Truncate a file to a specified length.","","","","","","","","","","","","","Write data with offset. We assume all previous allocate/…"],"i":[0,0,1,1,7,1,7,1,7,7,1,7,1,7,1,7,7,1,7,1,1,7,1,7,1,7,0,1,22,0,24,0,0,22,0,0,0,0,0,0,0,33,18,25,18,21,22,25,18,21,22,18,21,22,21,22,21,18,21,24,21,18,25,21,25,18,21,22,21,21,21,25,21,25,18,21,22,18,18,24,21,25,33,25,18,24,28,17,21,22,33,25,18,21,22,25,18,21,22,25,18,21,22,33],"f":[0,0,[[1,2,3],[[6,[[5,[4]]]]]],[[]],[[]],[[]],[[]],[1],[7],[7,8],[[]],[[]],[[]],[[]],[[9,10,[12,[11]]],[[8,[1]]]],[[10,13,[14,[9]],[14,[11]]],[[8,[7]]]],[[7,10,13],[[6,[[5,[4]]]]]],[[1,2,3],[[6,[[5,[4]]]]]],[[7,15],[[6,[[5,[4]]]]]],[[1,3],8],[[],8],[[],8],[[],8],[[],8],[[],16],[[],16],0,[[1,2,17],[[6,[[5,[4]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,3],[[6,[[5,[4]]]]]],[[18,19],18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[18,20],18],[21,21],[22,22],[[]],[[]],[[21,21],23],[[],18],[[],21],[[],8],[[21,21],13],[[18,19],18],[[[25,[24]]],3],[[21,26],27],[[]],[[]],[[]],[[]],[21,2],[[],19],[21,2],[[[25,[24]],[29,[28]]],[[29,[4]]]],[21],[[]],[[]],[[]],[[]],[[18,24,30,31],[[8,[[25,[24]]]]]],[[],18],[[10,13],[[6,[[5,[4]]]]]],[[21,21],[[14,[23]]]],[[[25,[24]],32,31],8],[[2,3],[[6,[[5,[4]]]]]],[[[25,[24]],3,22],[[8,[[29,[17]]]]]],[[18,22],18],[15,[[6,[[5,[4]]]]]],[[],17],[17,17],[[]],[[]],[3,8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],16],[[],16],[[],16],[[],16],[[2,17],[[6,[[5,[4]]]]]]],"p":[[3,"WALFileAIO"],[6,"WALPos"],[15,"usize"],[8,"Future"],[3,"Box"],[3,"Pin"],[3,"WALStoreAIO"],[4,"Result"],[6,"RawFd"],[15,"str"],[3,"AIOManager"],[3,"Arc"],[15,"bool"],[4,"Option"],[3,"String"],[3,"TypeId"],[6,"WALBytes"],[3,"WALLoader"],[15,"u64"],[3,"NonZeroUsize"],[3,"WALRingId"],[4,"RecoverPolicy"],[4,"Ordering"],[8,"WALStore"],[3,"WALWriter"],[3,"Formatter"],[6,"Result"],[8,"Record"],[3,"Vec"],[8,"FnMut"],[15,"u32"],[8,"AsRef"],[8,"WALFile"]]},\ -"hashbrown":{"doc":"This crate is a Rust port of Google’s high-performance …","t":[13,13,3,3,4,11,11,11,11,11,11,11,0,0,11,11,11,11,11,12,6,3,3,4,4,3,3,3,3,3,3,3,13,13,13,3,3,3,3,3,4,3,3,13,13,13,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,12,12,12,12,12,12,3,3,3,4,3,3,3,3,13,3,3,3,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["AllocError","CapacityOverflow","HashMap","HashSet","TryReserveError","borrow","borrow_mut","clone","clone_into","eq","fmt","from","hash_map","hash_set","into","to_owned","try_from","try_into","type_id","layout","DefaultHashBuilder","Drain","DrainFilter","Entry","EntryRef","HashMap","IntoIter","IntoKeys","IntoValues","Iter","IterMut","Keys","Occupied","Occupied","Occupied","OccupiedEntry","OccupiedEntryRef","OccupiedError","RawEntryBuilder","RawEntryBuilderMut","RawEntryMut","RawOccupiedEntryMut","RawVacantEntryMut","Vacant","Vacant","Vacant","VacantEntry","VacantEntryRef","Values","ValuesMut","allocator","and_modify","and_modify","and_modify","and_replace_entry_with","and_replace_entry_with","and_replace_entry_with","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone","clone","clone_from","clone_into","clone_into","clone_into","clone_into","contains_key","default","drain","drain_filter","drop","entry","entry","entry_ref","eq","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_hash","from_hash","from_iter","from_key","from_key","from_key_hashed_nocheck","from_key_hashed_nocheck","get","get","get","get","get_key_value","get_key_value","get_key_value_mut","get_key_value_mut","get_many_key_value_mut","get_many_key_value_unchecked_mut","get_many_mut","get_many_unchecked_mut","get_mut","get_mut","get_mut","get_mut","hasher","index","insert","insert","insert","insert","insert","insert","insert","insert","insert","insert","insert_hashed_nocheck","insert_key","insert_unique_unchecked","insert_with_hasher","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_key","into_key","into_key","into_key_value","into_keys","into_mut","into_mut","into_mut","into_values","is_empty","iter","iter_mut","key","key","key","key","key","key","key","key_mut","keys","len","len","len","len","len","len","len","len","len","len","new","new_in","next","next","next","next","next","next","next","next","next","next","or_default","or_default","or_insert","or_insert","or_insert","or_insert_with","or_insert_with","or_insert_with","or_insert_with_key","or_insert_with_key","raw_entry","raw_entry_mut","remove","remove","remove","remove","remove_entry","remove_entry","remove_entry","remove_entry","replace_entry","replace_entry","replace_entry_with","replace_entry_with","replace_entry_with","replace_key","replace_key","reserve","retain","shrink_to","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_insert","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_reserve","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","value","values","values_mut","with_capacity","with_capacity_and_hasher","with_capacity_and_hasher_in","with_capacity_in","with_hasher","with_hasher_in","0","0","0","0","0","0","Difference","Drain","DrainFilter","Entry","HashSet","Intersection","IntoIter","Iter","Occupied","OccupiedEntry","SymmetricDifference","Union","Vacant","VacantEntry","allocator","bitand","bitor","bitxor","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone","clone","clone","clone","clone_from","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","contains","default","difference","drain","drain_filter","drop","entry","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","get","get","get","get","get_or_insert","get_or_insert_owned","get_or_insert_with","hasher","insert","insert","insert","insert_unique_unchecked","intersection","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_value","is_disjoint","is_empty","is_subset","is_superset","iter","len","len","len","len","new","new_in","next","next","next","next","next","next","next","next","or_insert","remove","remove","replace","replace","reserve","retain","shrink_to","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","sub","symmetric_difference","take","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_reserve","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","with_capacity","with_capacity_and_hasher","with_capacity_and_hasher_in","with_capacity_in","with_hasher","with_hasher_in","0","0"],"q":["hashbrown","","","","","","","","","","","","","","","","","","","hashbrown::TryReserveError","hashbrown::hash_map","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hashbrown::hash_map::Entry","","hashbrown::hash_map::EntryRef","","hashbrown::hash_map::RawEntryMut","","hashbrown::hash_set","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hashbrown::hash_set::Entry",""],"d":["The memory allocator returned an error","Error due to the computed capacity exceeding the collection…","A hash map implemented with quadratic probing and SIMD …","A hash set implemented as a HashMap where the value is ().","The error type for try_reserve methods.","","","","","","","Returns the argument unchanged.","A hash map implemented with quadratic probing and SIMD …","A hash set implemented as a HashMap where the value is ().","Calls U::from(self).","","","","","The layout of the allocation request that failed.","Default hasher for HashMap.","A draining iterator over the entries of a HashMap in …","A draining iterator over entries of a HashMap which don’…","A view into a single entry in a map, which may either be …","A view into a single entry in a map, which may either be …","A hash map implemented with quadratic probing and SIMD …","An owning iterator over the entries of a HashMap in …","An owning iterator over the keys of a HashMap in arbitrary …","An owning iterator over the values of a HashMap in …","An iterator over the entries of a HashMap in arbitrary …","A mutable iterator over the entries of a HashMap in …","An iterator over the keys of a HashMap in arbitrary order. …","An occupied entry.","An occupied entry.","An occupied entry.","A view into an occupied entry in a HashMap. It is part of …","A view into an occupied entry in a HashMap. It is part of …","The error returned by try_insert when the key already …","A builder for computing where in a HashMap a key-value …","A builder for computing where in a HashMap a key-value …","A view into a single entry in a map, which may either be …","A view into an occupied entry in a HashMap. It is part of …","A view into a vacant entry in a HashMap. It is part of the …","A vacant entry.","A vacant entry.","A vacant entry.","A view into a vacant entry in a HashMap. It is part of the …","A view into a vacant entry in a HashMap. It is part of the …","An iterator over the values of a HashMap in arbitrary …","A mutable iterator over the values of a HashMap in …","Returns a reference to the underlying allocator.","Provides in-place mutable access to an occupied entry …","Provides in-place mutable access to an occupied entry …","Provides in-place mutable access to an occupied entry …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the number of elements the map can hold without …","Clears the map, removing all key-value pairs. Keeps the …","","","","","","","","","","Returns true if the map contains a value for the specified …","Creates an empty HashMap<K, V, S, A>, with the Default …","Clears the map, returning all key-value pairs as an …","Drains elements which are true under the given predicate, …","","Gets the given key’s corresponding entry in the map for …","The entry in the map that was already occupied.","Gets the given key’s corresponding entry by reference in …","","Inserts all new key-values from the iterator to existing …","Inserts all new key-values from the iterator to existing …","Inserts all new key-values from the iterator to existing …","","","","","","","","","","","","","","","","","","","","","","","","Examples","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Creates a RawEntryMut from the given hash and matching …","Access an immutable entry by hash and matching function.","","Creates a RawEntryMut from the given key.","Access an immutable entry by key.","Creates a RawEntryMut from the given key and its hash.","Access an immutable entry by a key and its hash.","Returns a reference to the value corresponding to the key.","Gets a reference to the value in the entry.","Gets a reference to the value in the entry.","Gets a reference to the value in the entry.","Returns the key-value pair corresponding to the supplied …","Gets a reference to the key and value in the entry.","Returns the key-value pair corresponding to the supplied …","Gets a mutable reference to the key and value in the entry.","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Attempts to get mutable references to N values in the map …","Returns a mutable reference to the value corresponding to …","Gets a mutable reference to the value in the entry.","Gets a mutable reference to the value in the entry.","Gets a mutable reference to the value in the entry.","Returns a reference to the map’s BuildHasher.","Returns a reference to the value corresponding to the …","Inserts a key-value pair into the map.","Sets the value of the entry, and returns a …","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns an OccupiedEntry.","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns an …","Sets the value of the entry, and returns the entry’s old …","Sets the value of the entry with the VacantEntryRef’s …","Sets the value of the entry with the VacantEntry’s key, …","Sets the value of the entry, and returns the entry’s old …","Insert a key-value pair into the map without checking if …","Set the value of an entry with a custom hasher function.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a consuming iterator, that is, one that moves each …","Creates an iterator over the entries of a HashMap in …","Creates an iterator over the entries of a HashMap in …","","","","","","","","","","","Converts the entry into a mutable reference to the key in …","Take ownership of the key.","Take ownership of the key.","Converts the OccupiedEntry into a mutable reference to the …","Creates a consuming iterator visiting all the keys in …","Converts the OccupiedEntry into a mutable reference to the …","Converts the OccupiedEntry into a mutable reference to the …","Converts the OccupiedEntryRef into a mutable reference to …","Creates a consuming iterator visiting all the values in …","Returns true if the map contains no elements.","An iterator visiting all key-value pairs in arbitrary …","An iterator visiting all key-value pairs in arbitrary …","Gets a reference to the key in the entry.","Returns a reference to this entry’s key.","Gets a reference to the key in the entry.","Gets a reference to the key that would be used when …","Returns a reference to this entry’s key.","Gets a reference to the key in the entry.","Gets a reference to the key that would be used when …","Gets a mutable reference to the key in the entry.","An iterator visiting all keys in arbitrary order. The …","Returns the number of elements in the map.","","","","","","","","","","Creates an empty HashMap.","Creates an empty HashMap using the given allocator.","","","","","","","","","","","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the default …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting the result of …","Ensures a value is in the entry by inserting, if empty, …","Ensures a value is in the entry by inserting, if empty, …","Creates a raw immutable entry builder for the HashMap.","Creates a raw entry builder for the HashMap.","Removes a key from the map, returning the value at the key …","Takes the value out of the entry, and returns it.","Takes the value out of the entry, and returns it. Keeps …","Takes the value out of the entry, and returns it. Keeps …","Removes a key from the map, returning the stored key and …","Take the ownership of the key and value from the map.","Take the ownership of the key and value from the map. …","Take the ownership of the key and value from the map. …","Replaces the entry, returning the old key and value. The …","Replaces the entry, returning the old key and value. The …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Provides shared access to the key and owned access to the …","Replaces the key in the hash map with the key used to …","Replaces the key in the hash map with the key used to …","Reserves capacity for at least additional more elements to …","Retains only the elements specified by the predicate. …","Shrinks the capacity of the map with a lower limit. It …","Shrinks the capacity of the map as much as possible. It …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tries to insert a key-value pair into the map, and returns …","","","","","","","","","","","","","","","","","","","","","","","","Tries to reserve capacity for at least additional more …","","","","","","","","","","","","","","","","","","","","","","","","The value which was not inserted, because the entry was …","An iterator visiting all values in arbitrary order. The …","An iterator visiting all values mutably in arbitrary order.","Creates an empty HashMap with the specified capacity.","Creates an empty HashMap with the specified capacity, …","Creates an empty HashMap with the specified capacity, …","Creates an empty HashMap with the specified capacity using …","Creates an empty HashMap which will use the given hash …","Creates an empty HashMap which will use the given hash …","","","","","","","A lazy iterator producing elements in the difference of …","A draining iterator over the items of a HashSet.","A draining iterator over entries of a HashSet which don’…","A view into a single entry in a set, which may either be …","A hash set implemented as a HashMap where the value is ().","A lazy iterator producing elements in the intersection of …","An owning iterator over the items of a HashSet.","An iterator over the items of a HashSet.","An occupied entry.","A view into an occupied entry in a HashSet. It is part of …","A lazy iterator producing elements in the symmetric …","A lazy iterator producing elements in the union of HashSet…","A vacant entry.","A view into a vacant entry in a HashSet. It is part of the …","Returns a reference to the underlying allocator.","Returns the intersection of self and rhs as a new …","Returns the union of self and rhs as a new HashSet<T, S>.","Returns the symmetric difference of self and rhs as a new …","","","","","","","","","","","","","","","","","","","","","","","","","Returns the number of elements the set can hold without …","Clears the set, removing all values.","","","","","","","","","","","","","","Returns true if the set contains a value.","Creates an empty HashSet<T, S> with the Default value for …","Visits the values representing the difference, i.e., the …","Clears the set, returning all elements in an iterator.","Drains elements which are true under the given predicate, …","","Gets the given value’s corresponding entry in the set …","","","","","","","","","","","","","","","Examples","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns a reference to the value in the set, if any, that …","Returns a reference to this entry’s value.","Gets a reference to the value in the entry.","Gets a reference to the value that would be used when …","Inserts the given value into the set if it is not present, …","Inserts an owned copy of the given value into the set if …","Inserts a value computed from f into the set if the given …","Returns a reference to the set’s BuildHasher.","Adds a value to the set.","Sets the value of the entry, and returns an OccupiedEntry.","Sets the value of the entry with the VacantEntry’s value.","Insert a value the set without checking if the value …","Visits the values representing the intersection, i.e., the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Creates a consuming iterator, that is, one that moves each …","","","","","","","","","Take ownership of the value.","Returns true if self has no elements in common with other. …","Returns true if the set contains no elements.","Returns true if the set is a subset of another, i.e., other…","Returns true if the set is a superset of another, i.e., …","An iterator visiting all elements in arbitrary order. The …","Returns the number of elements in the set.","","","","Creates an empty HashSet.","Creates an empty HashSet.","","","","","","","","","Ensures a value is in the entry by inserting if it was …","Removes a value from the set. Returns whether the value was","Takes the value out of the entry, and returns it. Keeps …","Adds a value to the set, replacing the existing value, if …","Replaces the entry, returning the old value. The new value …","Reserves capacity for at least additional more elements to …","Retains only the elements specified by the predicate.","Shrinks the capacity of the set with a lower limit. It …","Shrinks the capacity of the set as much as possible. It …","","","","","","","","","Returns the difference of self and rhs as a new …","Visits the values representing the symmetric difference, …","Removes and returns the value in the set, if any, that is …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tries to reserve capacity for at least additional more …","","","","","","","","","","","","","Visits the values representing the union, i.e., all the …","Creates an empty HashSet with the specified capacity.","Creates an empty HashSet with the specified capacity, using","Creates an empty HashSet with the specified capacity, using","Creates an empty HashSet with the specified capacity.","Creates a new empty hash set which will use the given …","Creates a new empty hash set which will use the given …","",""],"i":[1,1,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,56,0,0,0,0,0,0,0,0,0,0,0,0,9,10,12,0,0,0,0,0,0,0,0,9,10,12,0,0,0,0,8,9,10,12,9,10,12,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,8,14,15,16,8,8,14,15,16,8,8,8,8,18,8,35,8,8,8,8,8,8,14,21,22,23,24,15,16,17,25,26,9,27,28,29,10,30,31,12,33,34,35,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,26,29,8,26,29,26,29,8,27,30,33,8,27,8,27,8,8,8,8,8,27,30,33,8,8,8,9,27,28,10,30,31,12,33,34,28,27,8,28,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,8,14,21,22,23,24,15,16,17,18,25,27,31,34,27,8,27,30,33,8,8,8,8,27,10,30,31,12,33,34,27,8,8,14,21,22,23,24,15,16,17,25,8,8,14,21,22,23,24,15,16,17,18,25,10,12,9,10,12,9,10,12,10,12,8,8,8,27,30,33,8,27,30,33,30,33,27,30,33,30,33,8,8,8,8,14,21,22,23,24,15,16,17,18,25,8,14,15,16,35,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,8,8,14,21,22,23,24,15,16,17,18,25,26,9,27,28,29,10,30,31,12,33,34,35,35,8,8,8,8,8,8,8,8,57,58,59,60,61,62,0,0,0,0,0,0,0,0,50,0,0,0,50,0,42,42,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,43,51,48,49,44,45,46,47,50,52,53,42,42,42,43,44,45,46,47,42,42,43,44,45,46,47,42,42,42,42,42,49,42,42,42,42,42,43,51,48,44,45,46,47,50,52,53,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,50,52,53,42,42,42,42,42,50,53,42,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,43,51,48,49,44,45,46,47,53,42,42,42,42,42,42,43,51,48,42,42,43,51,48,49,44,45,46,47,50,42,52,42,52,42,42,42,42,43,51,48,49,44,45,46,47,42,42,42,42,43,44,45,46,47,42,43,51,48,49,44,45,46,47,50,52,53,42,43,51,48,49,44,45,46,47,50,52,53,42,42,43,51,48,49,44,45,46,47,50,52,53,42,42,42,42,42,42,42,63,64],"f":[0,0,0,0,0,[[]],[[]],[1,1],[[]],[[1,1],2],[[1,3],4],[[]],0,0,[[]],[[]],[[],5],[[],5],[[],6],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[8,[[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[8,[[0,[0,7]]]]],13],[[[8,[[0,[0,7]]]]]],[[[8,[7,7,7,[0,[0,7]]]]],[[8,[7,7,7,[0,[0,7]]]]]],[14,14],[15,15],[16,16],[[[8,[7,7,7,[0,[0,7]]]],[8,[7,7,7,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[8,2],[[],8],[[[8,[[0,[0,7]]]]],[[17,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[18,[[0,[0,7]]]]]],[18],[8,10],0,[8,[[12,[11]]]],[[8,8],2],[[8,19]],[[8,19]],[[8,19]],[[8,3],4],[[[14,[20,20]],3],4],[[21,3],4],[[[22,[20,20,[0,[0,7]]]],3],4],[[[23,[20,20,[0,[0,7]]]],3],4],[[[24,[20,[0,[0,7]]]],3],4],[[[15,[20]],3],4],[[[16,[20]],3],4],[[17,3],4],[[[25,[20]],3],4],[[[26,[[0,[0,7]]]],3],4],[[[9,[20,20,[0,[0,7]]]],3],4],[[[27,[20,20,[0,[0,7]]]],3],4],[[[28,[[0,[0,7]]]],3],4],[[[29,[[0,[0,7]]]],3],4],[[[10,[20,20,[0,[0,7]]]],3],4],[[[30,[20,20,[0,[0,7]]]],3],4],[[[31,[20,[0,[0,7]]]],3],4],[[[12,[[32,[[0,[11,20]]]],[0,[11,20]],20,[0,[0,7]]]],3],4],[[[33,[[32,[[0,[11,20]]]],[0,[11,20]],20,[0,[0,7]]]],3],4],[[[34,[[32,[[0,[11,20]]]],[0,[11,20]],[0,[0,7]]]],3],4],[[[35,[20,20,[0,[0,7]]]],3],4],[[[35,[20,20,[0,[0,7]]]],3],4],[[],[[8,[36]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[26,[[0,[0,7]]]],37],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]],37],38],[19,8],[[[26,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]]],38],[[[26,[[0,[0,7]]]],37],[[9,[[0,[0,7]]]]]],[[[29,[[0,[0,7]]]],37],38],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[8,38],[8,38],[8,38],[8,38],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]]],[8],[8,38],[[[9,[[0,[0,7]]]]],[[27,[[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[28,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]],[[30,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]],[[33,[11,[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[28,[[0,[0,7]]]],37]],[[[27,[[0,[0,7]]]]]],[8],[[[28,[[0,[0,7]]]],37]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[8,[[0,[0,7]]]]],[[22,[[0,[0,7]]]]]],[8,21],[8,14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[27,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[23,[[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[24,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],2],[[[8,[[0,[0,7]]]]],14],[[[8,[[0,[0,7]]]]],21],[[[27,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[31,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[34,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],15],[[[8,[[0,[0,7]]]]],13],[14,13],[21,13],[[[22,[[0,[0,7]]]]],13],[[[23,[[0,[0,7]]]]],13],[[[24,[[0,[0,7]]]]],13],[15,13],[16,13],[[[17,[[0,[0,7]]]]],13],[25,13],[[],[[8,[36]]]],[[[0,[0,7]]],[[8,[36,[0,[0,7]]]]]],[14,38],[21,38],[[[22,[[0,[0,7]]]]],38],[[[23,[[0,[0,7]]]]],38],[[[24,[[0,[0,7]]]]],38],[15,38],[16,38],[[[17,[[0,[0,7]]]]],38],[18,38],[25,38],[[[10,[39,[0,[0,7]]]]]],[[[12,[11,39,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]]]],[[[12,[11,[0,[0,7]]]]]],[[[9,[[0,[0,7]]]]]],[[[10,[[0,[0,7]]]],40]],[[[12,[11,[0,[0,7]]]],40]],[[[10,[[0,[0,7]]]],40]],[[[12,[11,[0,[0,7]]]],40]],[[[8,[[0,[0,7]]]]],[[29,[[0,[0,7]]]]]],[[[8,[[0,[0,7]]]]],[[26,[[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[8,38],[[[27,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[[27,[[0,[0,7]]]]],[[9,[[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]],[[10,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]],[[12,[11,[0,[0,7]]]]]],[[[30,[[0,[0,7]]]]]],[[[33,[11,[0,[0,7]]]]]],[[8,13]],[[[8,[[0,[0,7]]]]]],[[8,13]],[8],[14],[21],[[[22,[[0,[0,7]]]]]],[[[23,[[0,[0,7]]]]]],[[[24,[[0,[0,7]]]]]],[15],[16],[[[17,[[0,[0,7]]]]]],[18],[25],[[]],[[]],[[]],[[]],[[],41],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[8,[[5,[35]]]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[8,13],[[5,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],0,[[[8,[[0,[0,7]]]]],16],[[[8,[[0,[0,7]]]]],25],[13,[[8,[36]]]],[13,8],[[13,[0,[0,7]]],[[8,[[0,[0,7]]]]]],[[13,[0,[0,7]]],[[8,[36,[0,[0,7]]]]]],[[],8],[[[0,[0,7]]],[[8,[[0,[0,7]]]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[42],[[42,42],42],[[42,42],42],[[42,42],42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[42,[[0,[0,7]]]]],13],[[[42,[[0,[0,7]]]]]],[[[42,[7,7,[0,[0,7]]]]],[[42,[7,7,[0,[0,7]]]]]],[43,43],[[[44,[[0,[0,7]]]]],[[44,[[0,[0,7]]]]]],[[[45,[[0,[0,7]]]]],[[45,[[0,[0,7]]]]]],[[[46,[[0,[0,7]]]]],[[46,[[0,[0,7]]]]]],[[[47,[[0,[0,7]]]]],[[47,[[0,[0,7]]]]]],[[[42,[7,7,[0,[0,7]]]],[42,[7,7,[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[42,2],[[],42],[[42,42],45],[[[42,[[0,[0,7]]]]],[[48,[[0,[0,7]]]]]],[[[42,[[0,[0,7]]]]],[[49,[[0,[0,7]]]]]],[[[49,[[0,[0,7]]]]]],[42,50],[[42,42],2],[[42,19]],[[42,19]],[[42,3],4],[[[43,[20]],3],4],[[[51,[20,[0,[0,7]]]],3],4],[[[48,[20,[0,[0,7]]]],3],4],[[44,3],4],[[45,3],4],[[46,3],4],[[47,3],4],[[[50,[20,[0,[0,7]]]],3],4],[[[52,[20,[0,[0,7]]]],3],4],[[[53,[20,[0,[0,7]]]],3],4],[[],[[42,[36]]]],[8,42],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,42],[42,38],[[[50,[[0,[0,7]]]]]],[[[52,[[0,[0,7]]]]]],[[[53,[[0,[0,7]]]]]],[42],[42],[42],[42],[42,2],[[[50,[[0,[0,7]]]]],[[52,[[0,[0,7]]]]]],[[[53,[[0,[0,7]]]]]],[42],[[42,42],44],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[42,43],[[[42,[[0,[0,7]]]]],[[51,[[0,[0,7]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[53,[[0,[0,7]]]]]],[[42,42],2],[[[42,[[0,[0,7]]]]],2],[[42,42],2],[[42,42],2],[[[42,[[0,[0,7]]]]],43],[[[42,[[0,[0,7]]]]],13],[43,13],[[[51,[[0,[0,7]]]]],13],[[[48,[[0,[0,7]]]]],13],[[],[[42,[36]]]],[[[0,[0,7]]],[[42,[[0,[54,55]],36,[0,[0,7]]]]]],[43,38],[[[51,[[0,[0,7]]]]],38],[[[48,[[0,[0,7]]]]],38],[[[49,[[0,[0,7]]]]],38],[44,38],[45,38],[46,38],[47,38],[[[50,[[0,[0,7]]]]]],[42,2],[[[52,[[0,[0,7]]]]]],[42,38],[[[52,[[0,[0,7]]]]]],[[42,13]],[[[42,[[0,[0,7]]]]]],[[42,13]],[42],[43],[[[51,[[0,[0,7]]]]]],[[[48,[[0,[0,7]]]]]],[[[49,[[0,[0,7]]]]]],[44],[45],[46],[47],[[42,42],42],[[42,42],46],[42,38],[[]],[[]],[[]],[[]],[[]],[[]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[42,13],[[5,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[42,42],47],[13,[[42,[36]]]],[13,[[42,[0]]]],[13,42],[[13,[0,[0,7]]],[[42,[[0,[54,55]],36,[0,[0,7]]]]]],[[],[[42,[0]]]],[[],42],0,0],"p":[[4,"TryReserveError"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"TypeId"],[8,"Clone"],[3,"HashMap"],[4,"RawEntryMut"],[4,"Entry"],[8,"Sized"],[4,"EntryRef"],[15,"usize"],[3,"Iter"],[3,"Keys"],[3,"Values"],[3,"Drain"],[3,"DrainFilter"],[8,"IntoIterator"],[8,"Debug"],[3,"IterMut"],[3,"IntoIter"],[3,"IntoKeys"],[3,"IntoValues"],[3,"ValuesMut"],[3,"RawEntryBuilderMut"],[3,"RawOccupiedEntryMut"],[3,"RawVacantEntryMut"],[3,"RawEntryBuilder"],[3,"OccupiedEntry"],[3,"VacantEntry"],[8,"Borrow"],[3,"OccupiedEntryRef"],[3,"VacantEntryRef"],[3,"OccupiedError"],[6,"DefaultHashBuilder"],[15,"u64"],[4,"Option"],[8,"Default"],[8,"FnOnce"],[3,"String"],[3,"HashSet"],[3,"Iter"],[3,"Intersection"],[3,"Difference"],[3,"SymmetricDifference"],[3,"Union"],[3,"Drain"],[3,"DrainFilter"],[4,"Entry"],[3,"IntoIter"],[3,"OccupiedEntry"],[3,"VacantEntry"],[8,"Hash"],[8,"Eq"],[13,"AllocError"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"],[13,"Occupied"],[13,"Vacant"]]},\ -"heck":{"doc":"heck is a case conversion library.","t":[12,12,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,10,8,8,8,8,8,8,8,8,8,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","0","0","0","0","0","0","0","0","0","AsKebabCase","AsLowerCamelCase","AsPascalCase","AsShoutyKebabCase","AsShoutySnakeCase","AsShoutySnekCase","AsSnakeCase","AsSnekCase","AsTitleCase","AsUpperCamelCase","TO_SHOUTY_SNEK_CASE","ToKebabCase","ToLowerCamelCase","ToPascalCase","ToShoutyKebabCase","ToShoutySnakeCase","ToShoutySnekCase","ToSnakeCase","ToSnekCase","ToTitleCase","ToUpperCamelCase","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","into","into","into","into","into","into","into","to_kebab_case","to_lower_camel_case","to_pascal_case","to_shouty_kebab_case","to_shouty_snake_case","to_snake_case","to_snek_case","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_title_case","to_upper_camel_case","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id"],"q":["heck","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","This wrapper performs a kebab case conversion in …","This wrapper performs a lower camel case conversion in …","This wrapper performs a upper camel case conversion in …","This wrapper performs a kebab case conversion in …","This wrapper performs a shouty snake case conversion in …","This wrapper performs a shouty snake case conversion in …","This wrapper performs a snake case conversion in …","This wrapper performs a snake case conversion in …","This wrapper performs a title case conversion in …","This wrapper performs a upper camel case conversion in …","CONVERT THIS TYPE TO SNEK CASE.","This trait defines a kebab case conversion.","This trait defines a lower camel case conversion.","ToPascalCase is an alias for ToUpperCamelCase. See …","This trait defines a shouty kebab case conversion.","This trait defines a shouty snake case conversion.","Oh heck, ToShoutySnekCase is an alias for …","This trait defines a snake case conversion.","Oh heck, SnekCase is an alias for ToSnakeCase. See …","This trait defines a title case conversion.","This trait defines an upper camel case conversion.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert this type to kebab case.","Convert this type to lower camel case.","Convert this type to upper camel case.","Convert this type to shouty kebab case.","Convert this type to shouty snake case.","Convert this type to snake case.","Convert this type to snek case.","","","","","","","","Convert this type to title case.","Convert this type to upper camel case.","","","","","","","","","","","","","","","","","","","","",""],"i":[3,6,7,8,8,9,9,10,11,11,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11,16,17,18,19,20,21,22,3,6,7,8,9,10,11,23,24,3,6,7,8,9,10,11,3,6,7,8,9,10,11,3,6,7,8,9,10,11],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[3,[[2,[1]]]],4],5],[[[6,[[2,[1]]]],4],5],[[[7,[[2,[1]]]],4],5],[[[8,[[2,[1]]]],4],5],[[[9,[[2,[1]]]],4],5],[[[10,[[2,[1]]]],4],5],[[[11,[[2,[1]]]],4],5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14]],"p":[[15,"str"],[8,"AsRef"],[3,"AsKebabCase"],[3,"Formatter"],[6,"Result"],[3,"AsLowerCamelCase"],[3,"AsShoutyKebabCase"],[3,"AsShoutySnakeCase"],[3,"AsSnakeCase"],[3,"AsTitleCase"],[3,"AsUpperCamelCase"],[3,"String"],[4,"Result"],[3,"TypeId"],[8,"ToShoutySnekCase"],[8,"ToKebabCase"],[8,"ToLowerCamelCase"],[8,"ToPascalCase"],[8,"ToShoutyKebabCase"],[8,"ToShoutySnakeCase"],[8,"ToSnakeCase"],[8,"ToSnekCase"],[8,"ToTitleCase"],[8,"ToUpperCamelCase"]]},\ -"hex":{"doc":"Encoding and decoding hex strings.","t":[16,8,4,13,13,13,8,11,11,11,11,5,5,5,10,10,5,5,11,11,11,11,10,11,11,11,11,11,11,11,12,12],"n":["Error","FromHex","FromHexError","InvalidHexCharacter","InvalidStringLength","OddLength","ToHex","borrow","borrow_mut","clone","clone_into","decode","decode_to_slice","encode","encode_hex","encode_hex_upper","encode_to_slice","encode_upper","eq","fmt","fmt","from","from_hex","into","provide","to_owned","to_string","try_from","try_into","type_id","c","index"],"q":["hex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","hex::FromHexError",""],"d":["","Types that can be decoded from a hex string.","The error type for decoding a hex string into Vec<u8> or …","An invalid character was found. Valid ones are: 0...9, …","If the hex string is decoded into a fixed sized container, …","A hex string’s length needs to be even, as two digits …","Encoding values as hex string.","","","","","Decodes a hex string into raw bytes.","Decode a hex string into a mutable bytes slice.","Encodes data as hex string using lowercase characters.","Encode the hex strict representing self into the result. …","Encode the hex strict representing self into the result. …","Encodes some bytes into a mutable slice of bytes.","Encodes data as hex string using uppercase characters.","","","","Returns the argument unchanged.","Creates an instance of type Self from the given hex …","Calls U::from(self).","","","","","","","",""],"i":[14,0,0,1,1,1,0,1,1,1,1,0,0,0,15,15,0,0,1,1,1,1,14,1,1,1,1,1,1,1,16,16],"f":[0,0,0,0,0,0,0,[[]],[[]],[1,1],[[]],[2,[[5,[[4,[3]],1]]]],[2,[[5,[1]]]],[2,6],[[],[[8,[7]]]],[[],[[8,[7]]]],[2,[[5,[1]]]],[2,6],[[1,1],9],[[1,10],11],[[1,10],11],[[]],[2,5],[[]],[12],[[]],[[],6],[[],5],[[],5],[[],13],0,0],"p":[[4,"FromHexError"],[8,"AsRef"],[15,"u8"],[3,"Vec"],[4,"Result"],[3,"String"],[15,"char"],[8,"FromIterator"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"TypeId"],[8,"FromHex"],[8,"ToHex"],[13,"InvalidHexCharacter"]]},\ -"impl_rlp":{"doc":"RLP serialization support for uint and fixed hash.","t":[14,14],"n":["impl_fixed_hash_rlp","impl_uint_rlp"],"q":["impl_rlp",""],"d":["Add RLP serialization support to a fixed-sized hash type …","Add RLP serialization support to an integer created by …"],"i":[0,0],"f":[0,0],"p":[]},\ -"keccak":{"doc":"Keccak sponge function.","t":[18,8,5,5,5,5,5,10,10,14,14],"n":["KECCAK_F_ROUND_COUNT","LaneSize","f1600","f200","f400","f800","keccak_p","rotate_left","truncate_rc","unroll24","unroll5"],"q":["keccak","","","","","","","","","",""],"d":["","","Keccak-f sponge function","Keccak-f sponge function","Keccak-f sponge function","Keccak-f sponge function","Generic Keccak-p sponge function","","","",""],"i":[4,0,0,0,0,0,0,4,4,0,0],"f":[0,0,[[]],[[]],[[]],[[]],[1],[2],[3],0,0],"p":[[15,"usize"],[15,"u32"],[15,"u64"],[8,"LaneSize"]]},\ -"libc":{"doc":"libc - Raw FFI bindings to platforms’ system libraries","t":[17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,6,6,5,6,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,5,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,4,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,5,17,5,17,17,17,4,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,13,13,13,13,13,13,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,5,5,5,5,5,17,17,17,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,5,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,5,17,17,17,17,17,17,12,3,3,3,3,5,12,12,12,12,12,12,12,6,6,12,12,12,12,12,12,6,6,12,12,5,5,5,5,5,12,12,5,5,5,5,5,12,12,12,12,3,5,12,12,12,12,12,12,12,12,12,12,5,5,12,5,12,12,12,5,12,5,12,5,5,3,5,12,12,12,12,12,5,5,5,3,12,5,5,5,12,12,6,3,3,3,5,5,5,5,5,5,5,12,12,12,12,5,12,6,6,12,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,5,5,12,12,12,12,12,12,6,6,6,12,6,12,12,6,6,12,12,6,6,6,6,6,6,6,4,12,5,12,6,5,5,5,5,5,5,12,5,5,5,5,5,12,12,5,5,5,5,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,12,12,12,12,12,12,12,12,12,3,12,12,12,12,12,5,5,5,12,12,5,6,6,12,12,12,6,12,12,6,12,12,12,12,12,12,12,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,5,12,12,3,5,5,5,12,5,5,5,12,12,12,12,5,5,12,12,12,12,12,12,12,12,12,12,3,5,5,5,5,12,12,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,5,5,5,5,5,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,12,12,5,5,5,5,5,5,5,5,5,5,12,3,5,5,5,5,12,12,5,5,5,5,5,5,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,3,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,12,5,5,4,5,5,5,12,5,5,12,12,5,5,5,5,5,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,5,5,5,5,5,6,3,12,12,12,12,12,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,6,12,12,12,5,3,5,5,5,12,12,12,12,5,3,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,6,6,6,5,5,5,6,3,5,5,5,5,6,6,12,12,6,3,3,5,12,5,3,3,12,3,5,5,12,12,12,12,12,12,12,3,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,12,12,12,12,12,12,12,12,12,3,3,3,6,3,6,12,12,5,12,12,6,6,6,6,6,12,12,12,12,12,12,12,12,6,12,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,12,12,3,3,3,3,3,12,12,12,12,12,3,12,12,12,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,3,12,12,5,17,17,17,17,17,17,17,17,17,17,17,17,17,6,3,5,5,3,6,5,5,5,12,12,12,12,12,12,12,12,12,12,5,5,5,3,6,6,3,5,5,5,5,5,12,3,6,5,5,5,5,5,3,5,12,12,5,5,5,5,5,12,3,3,5,6,6,3,6,6,5,7,5,3,5,6,6,5,6,6,12,5,12,12,5,5,5,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,3,5,3,12,12,12,12,12,6,6,5,5,5,5,5,6,6,5,5,5,5,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,12,6,12,12,12,12,12,5,5,5,12,12,12,12,12,12,12,3,3,5,5,5,5,5,12,12,12,5,12,6,12,12,12,5,6,5,6,5,6,12,12,5,12,12,5,5,3,12,6,12,12,5,5,5,5,5,5,5,5,6,5,6,5,5,5,6,6,6,5,5,5,3,6,5,5,12,12,12,12,12,12,12,12,12,12,3,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,12,5,6,5,12,12,6,5,3,5,12,5,5,5,5,5,5,5,5,5,6,5,5,5,5,5,5,5,5,5,5,5,5,6,5,12,5,5,12,12,5,12,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,6,6,12,3,6,6,6,6,6,3,6,6,3,6,6,3,5,17,17,17,17,17,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,5,5,5,5,5,5,5,5,3,5,5,5,5,5,3,5,5,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,5,6,5,5,5,5,5,5,5,6,5,5,5,5,5,3,5,5,5,5,5,5,5,3,5,5,5,3,5,5,5,5,5,5,5,5,3,5,5,5,5,5,5,6,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,6,5,12,12,12,12,5,5,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,5,5,4,5,5,5,12,12,3,5,5,12,12,5,5,5,5,5,5,5,5,5,5,5,5,5,3,5,5,3,6,12,5,5,5,5,5,5,5,12,12,12,12,12,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,3,12,12,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,6,3,3,3,3,3,12,12,12,12,12,12,12,3,12,6,12,12,12,12,6,6,12,12,12,12,12,5,12,12,12,12,12,5,5,5,3,12,5,12,12,12,12,12,12,12,12,12,5,5,3,3,12,12,5,12,5,12,12,12,12,12,5,12,12,12,12,12,5,6,5,5,5,3,5,5,3,5,19,5,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,12,12,12,12,12,12,12,12,5,12,12,5,5,6,5,5,5,3,5,11,12,12,12,11,12,12,11,12,11,12,11,3,5,5,5,5,5,12,12,12,12,3,5,6,3,5,5,5,5,6,3,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,6,12,12,5,12,12,12,12,12,5,3,3,3,3,3,3,3,3,3,5,5,6,12,12,6,5,5,5,12,12,12,12,12,12,5,6,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,5,3,5,12,3,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,12,6,12,12,12,12,5,5,5,5,5,5,5,5,5,4,4,6,5,5,12,5,12,12,12,12,12,5,6,5,5,6,6,5,6,5,3,6,6,5,5,6,5,5,5,5,5,5,5,5,5,3,12,6,6,3,6,6,3,6,6,3,6,6,12,3,6,6,3,6,6,6,12,12,3,6,6,5,6,6,3,6,6,6,12,6,5,5,6,3,6,6,3,6,6,6,3,6,6,6,12,3,6,6,12,5,12,12,6,3,5,5,12,3,3,3,3,4,3,12,12,12,12,12,12,12,12,12,12,12,5,5,3,12,12,12,12,12,5,12,5,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,3,12,12,12,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,6,5,12,12,12,5,12,12,12,12,12,12,12,3,5,5,5,3,5,3,12,6,12,12,12,12,12,12,12,12,3,12,12,12,6,5,6,6,6,7,6,3,6,3,3,6,6,6,6,12,12,12,12,3,3,3,3,6,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,5,5,5,6,5,5,3,12,12,5,5,5,12,12,12,12,12,12,12,12,12,3,3,12,12],"n":["ABDAY_1","ABDAY_2","ABDAY_3","ABDAY_4","ABDAY_5","ABDAY_6","ABDAY_7","ABMON_1","ABMON_10","ABMON_11","ABMON_12","ABMON_2","ABMON_3","ABMON_4","ABMON_5","ABMON_6","ABMON_7","ABMON_8","ABMON_9","ACCOUNTING","AF_APPLETALK","AF_CCITT","AF_CHAOS","AF_CNT","AF_COIP","AF_DATAKIT","AF_DECnet","AF_DLI","AF_E164","AF_ECMA","AF_HYLINK","AF_IMPLINK","AF_INET","AF_INET6","AF_IPX","AF_ISDN","AF_ISO","AF_LAT","AF_LINK","AF_LOCAL","AF_NATM","AF_NDRV","AF_NETBIOS","AF_NS","AF_OSI","AF_PPP","AF_PUP","AF_ROUTE","AF_SIP","AF_SNA","AF_SYSTEM","AF_SYS_CONTROL","AF_UNIX","AF_UNSPEC","AIO_ALLDONE","AIO_CANCELED","AIO_LISTIO_MAX","AIO_NOTCANCELED","AI_ADDRCONFIG","AI_ALL","AI_CANONNAME","AI_DEFAULT","AI_MASK","AI_NUMERICHOST","AI_NUMERICSERV","AI_PASSIVE","AI_UNUSABLE","AI_V4MAPPED","AI_V4MAPPED_CFG","ALTWERASE","ALT_DIGITS","AM_STR","ARPOP_REPLY","ARPOP_REQUEST","ATF_COM","ATF_PERM","ATF_PUBL","ATF_USETRAILERS","ATTR_BIT_MAP_COUNT","ATTR_CMNEXT_CLONEID","ATTR_CMNEXT_EXT_FLAGS","ATTR_CMNEXT_LINKID","ATTR_CMNEXT_NOFIRMLINKPATH","ATTR_CMNEXT_PRIVATESIZE","ATTR_CMNEXT_REALDEVID","ATTR_CMNEXT_REALFSID","ATTR_CMNEXT_RECURSIVE_GENCOUNT","ATTR_CMNEXT_RELPATH","ATTR_CMN_ACCESSMASK","ATTR_CMN_ACCTIME","ATTR_CMN_ADDEDTIME","ATTR_CMN_BKUPTIME","ATTR_CMN_CHGTIME","ATTR_CMN_CRTIME","ATTR_CMN_DATA_PROTECT_FLAGS","ATTR_CMN_DEVID","ATTR_CMN_DOCUMENT_ID","ATTR_CMN_EXTENDED_SECURITY","ATTR_CMN_FILEID","ATTR_CMN_FLAGS","ATTR_CMN_FNDRINFO","ATTR_CMN_FSID","ATTR_CMN_FULLPATH","ATTR_CMN_GEN_COUNT","ATTR_CMN_GRPID","ATTR_CMN_GRPUUID","ATTR_CMN_MODTIME","ATTR_CMN_NAME","ATTR_CMN_OBJID","ATTR_CMN_OBJPERMANENTID","ATTR_CMN_OBJTAG","ATTR_CMN_OBJTYPE","ATTR_CMN_OWNERID","ATTR_CMN_PARENTID","ATTR_CMN_PAROBJID","ATTR_CMN_RETURNED_ATTRS","ATTR_CMN_SCRIPT","ATTR_CMN_USERACCESS","ATTR_CMN_UUID","ATTR_DIR_ALLOCSIZE","ATTR_DIR_DATALENGTH","ATTR_DIR_ENTRYCOUNT","ATTR_DIR_IOBLOCKSIZE","ATTR_DIR_LINKCOUNT","ATTR_DIR_MOUNTSTATUS","ATTR_FILE_ALLOCSIZE","ATTR_FILE_DATAALLOCSIZE","ATTR_FILE_DATALENGTH","ATTR_FILE_DEVTYPE","ATTR_FILE_FORKCOUNT","ATTR_FILE_FORKLIST","ATTR_FILE_IOBLOCKSIZE","ATTR_FILE_LINKCOUNT","ATTR_FILE_RSRCALLOCSIZE","ATTR_FILE_RSRCLENGTH","ATTR_FILE_TOTALSIZE","ATTR_VOL_ALLOCATIONCLUMP","ATTR_VOL_ATTRIBUTES","ATTR_VOL_CAPABILITIES","ATTR_VOL_DIRCOUNT","ATTR_VOL_ENCODINGSUSED","ATTR_VOL_FILECOUNT","ATTR_VOL_FSTYPE","ATTR_VOL_INFO","ATTR_VOL_IOBLOCKSIZE","ATTR_VOL_MAXOBJCOUNT","ATTR_VOL_MINALLOCATION","ATTR_VOL_MOUNTEDDEVICE","ATTR_VOL_MOUNTFLAGS","ATTR_VOL_MOUNTPOINT","ATTR_VOL_NAME","ATTR_VOL_OBJCOUNT","ATTR_VOL_QUOTA_SIZE","ATTR_VOL_RESERVED_SIZE","ATTR_VOL_SIGNATURE","ATTR_VOL_SIZE","ATTR_VOL_SPACEAVAIL","ATTR_VOL_SPACEFREE","ATTR_VOL_SPACEUSED","ATTR_VOL_UUID","AT_EACCESS","AT_FDCWD","AT_REMOVEDIR","AT_SYMLINK_FOLLOW","AT_SYMLINK_NOFOLLOW","B0","B110","B115200","B1200","B134","B14400","B150","B1800","B19200","B200","B230400","B2400","B28800","B300","B38400","B4800","B50","B57600","B600","B7200","B75","B76800","B9600","BIOCFLUSH","BIOCGBLEN","BIOCGDLT","BIOCGDLTLIST","BIOCGETIF","BIOCGHDRCMPLT","BIOCGRSIG","BIOCGRTIMEOUT","BIOCGSEESENT","BIOCGSTATS","BIOCIMMEDIATE","BIOCPROMISC","BIOCSBLEN","BIOCSDLT","BIOCSETF","BIOCSETFNR","BIOCSETIF","BIOCSHDRCMPLT","BIOCSRSIG","BIOCSRTIMEOUT","BIOCSSEESENT","BIOCVERSION","BOOT_TIME","BPF_ALIGNMENT","BRKINT","BS0","BS1","BSDLY","BUFSIZ","BUS_ADRALN","BUS_ADRERR","BUS_OBJERR","CCCryptorStatus","CCRNGStatus","CCRandomGenerateBytes","CCStatus","CIGNORE","CLD_CONTINUED","CLD_DUMPED","CLD_EXITED","CLD_KILLED","CLD_STOPPED","CLD_TRAPPED","CLOCAL","CLOCK_MONOTONIC","CLOCK_MONOTONIC_RAW","CLOCK_MONOTONIC_RAW_APPROX","CLOCK_PROCESS_CPUTIME_ID","CLOCK_REALTIME","CLOCK_THREAD_CPUTIME_ID","CLOCK_UPTIME_RAW","CLOCK_UPTIME_RAW_APPROX","CMSG_DATA","CMSG_FIRSTHDR","CMSG_LEN","CMSG_NXTHDR","CMSG_SPACE","CODESET","CONNECT_DATA_AUTHENTICATED","CONNECT_DATA_IDEMPOTENT","CONNECT_RESUME_ON_READ_WRITE","COPYFILE_ACL","COPYFILE_CHECK","COPYFILE_CLONE","COPYFILE_CLONE_FORCE","COPYFILE_CONTINUE","COPYFILE_COPY_DATA","COPYFILE_COPY_XATTR","COPYFILE_DATA","COPYFILE_DATA_SPARSE","COPYFILE_ERR","COPYFILE_EXCL","COPYFILE_FINISH","COPYFILE_METADATA","COPYFILE_MOVE","COPYFILE_NOFOLLOW","COPYFILE_NOFOLLOW_DST","COPYFILE_NOFOLLOW_SRC","COPYFILE_PACK","COPYFILE_PRESERVE_DST_TRACKED","COPYFILE_PROGRESS","COPYFILE_QUIT","COPYFILE_RECURSE_DIR","COPYFILE_RECURSE_DIR_CLEANUP","COPYFILE_RECURSE_ERROR","COPYFILE_RECURSE_FILE","COPYFILE_RECURSIVE","COPYFILE_RUN_IN_PLACE","COPYFILE_SECURITY","COPYFILE_SKIP","COPYFILE_START","COPYFILE_STAT","COPYFILE_UNLINK","COPYFILE_UNPACK","COPYFILE_VERBOSE","COPYFILE_XATTR","CPU_STATE_IDLE","CPU_STATE_MAX","CPU_STATE_NICE","CPU_STATE_SYSTEM","CPU_STATE_USER","CR0","CR1","CR2","CR3","CRDLY","CREAD","CRNCYSTR","CRTSCTS","CS5","CS6","CS7","CS8","CSIZE","CSTOPB","CTLFLAG_ANYBODY","CTLFLAG_KERN","CTLFLAG_LOCKED","CTLFLAG_MASKED","CTLFLAG_NOAUTO","CTLFLAG_NOLOCK","CTLFLAG_OID2","CTLFLAG_RD","CTLFLAG_RW","CTLFLAG_SECURE","CTLFLAG_WR","CTLTYPE","CTLTYPE_INT","CTLTYPE_NODE","CTLTYPE_OPAQUE","CTLTYPE_QUAD","CTLTYPE_STRING","CTLTYPE_STRUCT","CTL_DEBUG","CTL_DEBUG_MAXID","CTL_DEBUG_NAME","CTL_DEBUG_VALUE","CTL_HW","CTL_KERN","CTL_MACHDEP","CTL_MAXID","CTL_NET","CTL_UNSPEC","CTL_USER","CTL_VFS","CTL_VM","DAY_1","DAY_2","DAY_3","DAY_4","DAY_5","DAY_6","DAY_7","DEAD_PROCESS","DIR","DIR_MNTSTATUS_MNTPOINT","DLT_ARCNET","DLT_ATM_RFC1483","DLT_AX25","DLT_CHAOS","DLT_EN10MB","DLT_EN3MB","DLT_FDDI","DLT_IEEE802","DLT_LOOP","DLT_NULL","DLT_PPP","DLT_PRONET","DLT_RAW","DLT_SLIP","DT_BLK","DT_CHR","DT_DIR","DT_FIFO","DT_LNK","DT_REG","DT_SOCK","DT_UNKNOWN","D_FMT","D_MD_ORDER","D_T_FMT","Dl_info","E2BIG","EACCES","EADDRINUSE","EADDRNOTAVAIL","EAFNOSUPPORT","EAGAIN","EAI_AGAIN","EAI_BADFLAGS","EAI_FAIL","EAI_FAMILY","EAI_MEMORY","EAI_NODATA","EAI_NONAME","EAI_OVERFLOW","EAI_SERVICE","EAI_SOCKTYPE","EAI_SYSTEM","EALREADY","EAUTH","EBADARCH","EBADEXEC","EBADF","EBADMACHO","EBADMSG","EBADRPC","EBUSY","ECANCELED","ECHILD","ECHO","ECHOCTL","ECHOE","ECHOK","ECHOKE","ECHONL","ECHOPRT","ECONNABORTED","ECONNREFUSED","ECONNRESET","EDEADLK","EDESTADDRREQ","EDEVERR","EDOM","EDQUOT","EEXIST","EFAULT","EFBIG","EFTYPE","EHOSTDOWN","EHOSTUNREACH","EIDRM","EILSEQ","EINPROGRESS","EINTR","EINVAL","EIO","EISCONN","EISDIR","ELAST","ELOOP","EMFILE","EMLINK","EMPTY","EMSGSIZE","EMULTIHOP","ENAMETOOLONG","ENEEDAUTH","ENETDOWN","ENETRESET","ENETUNREACH","ENFILE","ENOATTR","ENOBUFS","ENODATA","ENODEV","ENOENT","ENOEXEC","ENOLCK","ENOLINK","ENOMEM","ENOMSG","ENOPOLICY","ENOPROTOOPT","ENOSPC","ENOSR","ENOSTR","ENOSYS","ENOTBLK","ENOTCONN","ENOTDIR","ENOTEMPTY","ENOTRECOVERABLE","ENOTSOCK","ENOTSUP","ENOTTY","ENXIO","EOF","EOPNOTSUPP","EOVERFLOW","EOWNERDEAD","EPERM","EPFNOSUPPORT","EPIPE","EPROCLIM","EPROCUNAVAIL","EPROGMISMATCH","EPROGUNAVAIL","EPROTO","EPROTONOSUPPORT","EPROTOTYPE","EPWROFF","EQFULL","ERA","ERANGE","ERA_D_FMT","ERA_D_T_FMT","ERA_T_FMT","EREMOTE","EROFS","ERPCMISMATCH","ESHLIBVERS","ESHUTDOWN","ESOCKTNOSUPPORT","ESPIPE","ESRCH","ESTALE","ETIME","ETIMEDOUT","ETOOMANYREFS","ETXTBSY","EUSERS","EVFILT_AIO","EVFILT_FS","EVFILT_MACHPORT","EVFILT_PROC","EVFILT_READ","EVFILT_SIGNAL","EVFILT_TIMER","EVFILT_USER","EVFILT_VM","EVFILT_VNODE","EVFILT_WRITE","EV_ADD","EV_CLEAR","EV_DELETE","EV_DISABLE","EV_DISPATCH","EV_ENABLE","EV_EOF","EV_ERROR","EV_FLAG0","EV_FLAG1","EV_ONESHOT","EV_OOBAND","EV_POLL","EV_RECEIPT","EV_SYSFLAGS","EWOULDBLOCK","EXDEV","EXIT_FAILURE","EXIT_SUCCESS","EXTA","EXTB","EXTPROC","FD_CLOEXEC","FD_CLR","FD_ISSET","FD_SET","FD_SETSIZE","FD_ZERO","FF0","FF1","FFDLY","FILE","FILENAME_MAX","FIOASYNC","FIOCLEX","FIODTYPE","FIOGETOWN","FIONBIO","FIONCLEX","FIONREAD","FIOSETOWN","FLUSHO","FOPEN_MAX","FSOPT_ATTR_CMN_EXTENDED","FSOPT_NOFOLLOW","FSOPT_NOFOLLOW_ANY","FSOPT_PACK_INVAL_ATTRS","FSOPT_REPORT_FULLSIZE","FSOPT_RETURN_REALDEV","F_ALLOCATEALL","F_ALLOCATECONTIG","F_BARRIERFSYNC","F_DUPFD","F_DUPFD_CLOEXEC","F_FREEZE_FS","F_FULLFSYNC","F_GETFD","F_GETFL","F_GETLK","F_GETOWN","F_GETPATH","F_GETPATH_NOFIRMLINK","F_GLOBAL_NOCACHE","F_LOCK","F_LOG2PHYS","F_LOG2PHYS_EXT","F_NOCACHE","F_NODIRECT","F_OK","F_PEOFPOSMODE","F_PREALLOCATE","F_RDADVISE","F_RDAHEAD","F_RDLCK","F_SETFD","F_SETFL","F_SETLK","F_SETLKW","F_SETOWN","F_TEST","F_THAW_FS","F_TLOCK","F_ULOCK","F_UNLCK","F_VOLPOSMODE","F_WRLCK","GETALL","GETNCNT","GETPID","GETVAL","GETZCNT","GLOB_ABORTED","GLOB_APPEND","GLOB_DOOFFS","GLOB_ERR","GLOB_MARK","GLOB_NOCHECK","GLOB_NOESCAPE","GLOB_NOMATCH","GLOB_NOSORT","GLOB_NOSPACE","GRPQUOTA","HOST_CPU_LOAD_INFO","HOST_CPU_LOAD_INFO_COUNT","HOST_EXPIRED_TASK_INFO","HOST_EXTMOD_INFO64","HOST_LOAD_INFO","HOST_VM_INFO","HOST_VM_INFO64","HOST_VM_INFO64_COUNT","HUPCL","HW_AVAILCPU","HW_BUS_FREQ","HW_BYTEORDER","HW_CACHELINE","HW_CPU_FREQ","HW_DISKNAMES","HW_DISKSTATS","HW_EPOCH","HW_FLOATINGPT","HW_L1DCACHESIZE","HW_L1ICACHESIZE","HW_L2CACHESIZE","HW_L2SETTINGS","HW_L3CACHESIZE","HW_L3SETTINGS","HW_MACHINE","HW_MACHINE_ARCH","HW_MAXID","HW_MEMSIZE","HW_MODEL","HW_NCPU","HW_PAGESIZE","HW_PHYSMEM","HW_PRODUCT","HW_TARGET","HW_TB_FREQ","HW_USERMEM","HW_VECTORUNIT","ICANON","ICRNL","IEXTEN","IFF_ALLMULTI","IFF_ALTPHYS","IFF_BROADCAST","IFF_DEBUG","IFF_LINK0","IFF_LINK1","IFF_LINK2","IFF_LOOPBACK","IFF_MULTICAST","IFF_NOARP","IFF_NOTRAILERS","IFF_OACTIVE","IFF_POINTOPOINT","IFF_PROMISC","IFF_RUNNING","IFF_SIMPLEX","IFF_UP","IFNAMSIZ","IF_NAMESIZE","IGNBRK","IGNCR","IGNPAR","IMAXBEL","INADDR_ANY","INADDR_BROADCAST","INADDR_LOOPBACK","INADDR_NONE","INIT_PROCESS","INLCR","INPCK","INT_MAX","INT_MIN","IOV_MAX","IPC_CREAT","IPC_EXCL","IPC_M","IPC_NOWAIT","IPC_PRIVATE","IPC_R","IPC_RMID","IPC_SET","IPC_STAT","IPC_W","IPPROTO_3PC","IPPROTO_ADFS","IPPROTO_AH","IPPROTO_AHIP","IPPROTO_APES","IPPROTO_ARGUS","IPPROTO_AX25","IPPROTO_BHA","IPPROTO_BLT","IPPROTO_BRSATMON","IPPROTO_CFTP","IPPROTO_CHAOS","IPPROTO_CMTP","IPPROTO_CPHB","IPPROTO_CPNX","IPPROTO_DDP","IPPROTO_DGP","IPPROTO_DIVERT","IPPROTO_DONE","IPPROTO_DSTOPTS","IPPROTO_EGP","IPPROTO_EMCON","IPPROTO_ENCAP","IPPROTO_EON","IPPROTO_ESP","IPPROTO_ETHERIP","IPPROTO_FRAGMENT","IPPROTO_GGP","IPPROTO_GMTP","IPPROTO_GRE","IPPROTO_HELLO","IPPROTO_HMP","IPPROTO_HOPOPTS","IPPROTO_ICMP","IPPROTO_ICMPV6","IPPROTO_IDP","IPPROTO_IDPR","IPPROTO_IDRP","IPPROTO_IGMP","IPPROTO_IGP","IPPROTO_IGRP","IPPROTO_IL","IPPROTO_INLSP","IPPROTO_INP","IPPROTO_IP","IPPROTO_IPCOMP","IPPROTO_IPCV","IPPROTO_IPEIP","IPPROTO_IPIP","IPPROTO_IPPC","IPPROTO_IPV6","IPPROTO_IRTP","IPPROTO_KRYPTOLAN","IPPROTO_LARP","IPPROTO_LEAF1","IPPROTO_LEAF2","IPPROTO_MAX","IPPROTO_MEAS","IPPROTO_MHRP","IPPROTO_MICP","IPPROTO_MTP","IPPROTO_MUX","IPPROTO_ND","IPPROTO_NHRP","IPPROTO_NONE","IPPROTO_NSP","IPPROTO_NVPII","IPPROTO_OSPFIGP","IPPROTO_PGM","IPPROTO_PIGP","IPPROTO_PIM","IPPROTO_PRM","IPPROTO_PUP","IPPROTO_PVP","IPPROTO_RAW","IPPROTO_RCCMON","IPPROTO_RDP","IPPROTO_ROUTING","IPPROTO_RSVP","IPPROTO_RVD","IPPROTO_SATEXPAK","IPPROTO_SATMON","IPPROTO_SCCSP","IPPROTO_SCTP","IPPROTO_SDRP","IPPROTO_SEP","IPPROTO_SRPC","IPPROTO_ST","IPPROTO_SVMTP","IPPROTO_SWIPE","IPPROTO_TCF","IPPROTO_TCP","IPPROTO_TP","IPPROTO_TPXX","IPPROTO_TRUNK1","IPPROTO_TRUNK2","IPPROTO_TTP","IPPROTO_UDP","IPPROTO_VINES","IPPROTO_VISA","IPPROTO_VMTP","IPPROTO_WBEXPAK","IPPROTO_WBMON","IPPROTO_WSN","IPPROTO_XNET","IPPROTO_XTP","IPTOS_ECN_CE","IPTOS_ECN_ECT0","IPTOS_ECN_ECT1","IPTOS_ECN_MASK","IPTOS_ECN_NOTECT","IPV6_BOUND_IF","IPV6_CHECKSUM","IPV6_DONTFRAG","IPV6_HOPLIMIT","IPV6_JOIN_GROUP","IPV6_LEAVE_GROUP","IPV6_MULTICAST_HOPS","IPV6_MULTICAST_IF","IPV6_MULTICAST_LOOP","IPV6_PKTINFO","IPV6_RECVPKTINFO","IPV6_RECVTCLASS","IPV6_TCLASS","IPV6_UNICAST_HOPS","IPV6_V6ONLY","IP_ADD_MEMBERSHIP","IP_ADD_SOURCE_MEMBERSHIP","IP_BLOCK_SOURCE","IP_BOUND_IF","IP_DONTFRAG","IP_DROP_MEMBERSHIP","IP_DROP_SOURCE_MEMBERSHIP","IP_HDRINCL","IP_MULTICAST_IF","IP_MULTICAST_LOOP","IP_MULTICAST_TTL","IP_PKTINFO","IP_RECVDSTADDR","IP_RECVIF","IP_RECVTOS","IP_TOS","IP_TTL","IP_UNBLOCK_SOURCE","ISIG","ISTRIP","ITIMER_PROF","ITIMER_REAL","ITIMER_VIRTUAL","IUTF8","IXANY","IXOFF","IXON","KERN_ABORTED","KERN_AFFINITY","KERN_AIOMAX","KERN_AIOPROCMAX","KERN_AIOTHREADS","KERN_ALREADY_IN_SET","KERN_ALREADY_WAITING","KERN_ARGMAX","KERN_BOOTFILE","KERN_BOOTTIME","KERN_CHECKOPENEVT","KERN_CLASSIC","KERN_CLASSICHANDLER","KERN_CLOCKRATE","KERN_CODESIGN_ERROR","KERN_COREDUMP","KERN_COREFILE","KERN_DEFAULT_SET","KERN_DOMAINNAME","KERN_DUMMY","KERN_DUMPDEV","KERN_EXCEPTION_PROTECTED","KERN_EXEC","KERN_FAILURE","KERN_FILE","KERN_HOSTID","KERN_HOSTNAME","KERN_INSUFFICIENT_BUFFER_SIZE","KERN_INVALID_ADDRESS","KERN_INVALID_ARGUMENT","KERN_INVALID_CAPABILITY","KERN_INVALID_HOST","KERN_INVALID_LEDGER","KERN_INVALID_MEMORY_CONTROL","KERN_INVALID_NAME","KERN_INVALID_OBJECT","KERN_INVALID_POLICY","KERN_INVALID_PROCESSOR_SET","KERN_INVALID_RIGHT","KERN_INVALID_SECURITY","KERN_INVALID_TASK","KERN_INVALID_VALUE","KERN_IPC","KERN_JOB_CONTROL","KERN_KDBUFWAIT","KERN_KDCPUMAP","KERN_KDDFLAGS","KERN_KDEBUG","KERN_KDEFLAGS","KERN_KDENABLE","KERN_KDGETBUF","KERN_KDGETENTROPY","KERN_KDGETREG","KERN_KDPIDEX","KERN_KDPIDTR","KERN_KDREADCURTHRMAP","KERN_KDREADTR","KERN_KDREMOVE","KERN_KDSETBUF","KERN_KDSETREG","KERN_KDSETRTCDEC","KERN_KDSETUP","KERN_KDSET_TYPEFILTER","KERN_KDTHRMAP","KERN_KDWRITEMAP","KERN_KDWRITETR","KERN_LOCK_OWNED","KERN_LOCK_OWNED_SELF","KERN_LOCK_SET_DESTROYED","KERN_LOCK_UNSTABLE","KERN_LOGSIGEXIT","KERN_LOW_PRI_DELAY","KERN_LOW_PRI_WINDOW","KERN_MAXFILES","KERN_MAXFILESPERPROC","KERN_MAXID","KERN_MAXPARTITIONS","KERN_MAXPROC","KERN_MAXPROCPERUID","KERN_MAXVNODES","KERN_MEMORY_DATA_MOVED","KERN_MEMORY_ERROR","KERN_MEMORY_FAILURE","KERN_MEMORY_PRESENT","KERN_MEMORY_RESTART_COPY","KERN_NAME_EXISTS","KERN_NETBOOT","KERN_NGROUPS","KERN_NISDOMAINNAME","KERN_NODE_DOWN","KERN_NOT_DEPRESSED","KERN_NOT_IN_SET","KERN_NOT_RECEIVER","KERN_NOT_SUPPORTED","KERN_NOT_WAITING","KERN_NO_ACCESS","KERN_NO_SPACE","KERN_NTP_PLL","KERN_NX_PROTECTION","KERN_OPENEVT_PROC","KERN_OPERATION_TIMED_OUT","KERN_OSRELDATE","KERN_OSRELEASE","KERN_OSREV","KERN_OSTYPE","KERN_OSVERSION","KERN_POLICY_LIMIT","KERN_POLICY_STATIC","KERN_POSIX","KERN_POSIX1","KERN_PROC","KERN_PROCARGS","KERN_PROCARGS2","KERN_PROCDELAYTERM","KERN_PROCNAME","KERN_PROC_ALL","KERN_PROC_LCID","KERN_PROC_PGRP","KERN_PROC_PID","KERN_PROC_RUID","KERN_PROC_SESSION","KERN_PROC_TTY","KERN_PROC_UID","KERN_PROF","KERN_PROTECTION_FAILURE","KERN_PS_STRINGS","KERN_RAGEVNODE","KERN_RAGE_PROC","KERN_RAGE_THREAD","KERN_RESOURCE_SHORTAGE","KERN_RIGHT_EXISTS","KERN_RPC_CONTINUE_ORPHAN","KERN_RPC_SERVER_TERMINATED","KERN_RPC_TERMINATE_ORPHAN","KERN_SAFEBOOT","KERN_SAVED_IDS","KERN_SECURELVL","KERN_SEMAPHORE_DESTROYED","KERN_SHREG_PRIVATIZABLE","KERN_SPECULATIVE_READS","KERN_SUCCESS","KERN_SUGID_COREDUMP","KERN_SYMFILE","KERN_SYSV","KERN_TERMINATED","KERN_TFP","KERN_TFP_POLICY","KERN_TFP_POLICY_DEFAULT","KERN_TFP_POLICY_DENY","KERN_THALTSTACK","KERN_THREADNAME","KERN_TRANSLATE","KERN_TTY","KERN_UNOPENEVT_PROC","KERN_UNRAGE_PROC","KERN_UNRAGE_THREAD","KERN_UPDATEINTERVAL","KERN_UREFS_OVERFLOW","KERN_USRSTACK32","KERN_USRSTACK64","KERN_VERSION","KERN_VNODE","KIPC_MAXSOCKBUF","KIPC_MAX_DATALEN","KIPC_MAX_HDR","KIPC_MAX_LINKHDR","KIPC_MAX_PROTOHDR","KIPC_MBSTAT","KIPC_NMBCLUSTERS","KIPC_SOCKBUF_WASTE","KIPC_SOMAXCONN","KIPC_SOQLIMITCOMPAT","LC_ALL","LC_ALL_MASK","LC_COLLATE","LC_COLLATE_MASK","LC_CTYPE","LC_CTYPE_MASK","LC_MESSAGES","LC_MESSAGES_MASK","LC_MONETARY","LC_MONETARY_MASK","LC_NUMERIC","LC_NUMERIC_MASK","LC_SEGMENT","LC_SEGMENT_64","LC_TIME","LC_TIME_MASK","LIO_NOP","LIO_NOWAIT","LIO_READ","LIO_WAIT","LIO_WRITE","LOCAL_PEERCRED","LOCAL_PEEREPID","LOCAL_PEEREUUID","LOCAL_PEERPID","LOCAL_PEERUUID","LOCK_EX","LOCK_NB","LOCK_SH","LOCK_UN","LOGIN_PROCESS","LOG_ALERT","LOG_AUTH","LOG_AUTHPRIV","LOG_CONS","LOG_CRIT","LOG_CRON","LOG_DAEMON","LOG_DEBUG","LOG_EMERG","LOG_ERR","LOG_FACMASK","LOG_FTP","LOG_INFO","LOG_INSTALL","LOG_KERN","LOG_LAUNCHD","LOG_LOCAL0","LOG_LOCAL1","LOG_LOCAL2","LOG_LOCAL3","LOG_LOCAL4","LOG_LOCAL5","LOG_LOCAL6","LOG_LOCAL7","LOG_LPR","LOG_MAIL","LOG_NDELAY","LOG_NETINFO","LOG_NEWS","LOG_NFACILITIES","LOG_NOTICE","LOG_NOWAIT","LOG_ODELAY","LOG_PERROR","LOG_PID","LOG_PRIMASK","LOG_RAS","LOG_REMOTEAUTH","LOG_SYSLOG","LOG_USER","LOG_UUCP","LOG_WARNING","L_tmpnam","MACH_PORT_NULL","MACH_TASK_BASIC_INFO","MACH_TASK_BASIC_INFO_COUNT","MADV_CAN_REUSE","MADV_DONTNEED","MADV_FREE","MADV_FREE_REUSABLE","MADV_FREE_REUSE","MADV_NORMAL","MADV_RANDOM","MADV_SEQUENTIAL","MADV_WILLNEED","MADV_ZERO_WIRED_PAGES","MAP_ANON","MAP_ANONYMOUS","MAP_COPY","MAP_FAILED","MAP_FILE","MAP_FIXED","MAP_HASSEMAPHORE","MAP_JIT","MAP_NOCACHE","MAP_NOEXTEND","MAP_NORESERVE","MAP_PRIVATE","MAP_RENAME","MAP_SHARED","MAXCOMLEN","MAXFREQ","MAXPATHLEN","MAXPHASE","MAXSEC","MAXTC","MAXTHREADNAMESIZE","MCL_CURRENT","MCL_FUTURE","MDMBUF","MEMORY_OBJECT_NULL","MH_MAGIC","MH_MAGIC_64","MINCORE_INCORE","MINCORE_MODIFIED","MINCORE_MODIFIED_OTHER","MINCORE_REFERENCED","MINCORE_REFERENCED_OTHER","MINSEC","MINSIGSTKSZ","MNT_ASYNC","MNT_AUTOMOUNTED","MNT_CPROTECT","MNT_DEFWRITE","MNT_DONTBROWSE","MNT_DOVOLFS","MNT_EXPORTED","MNT_FORCE","MNT_IGNORE_OWNERSHIP","MNT_JOURNALED","MNT_LOCAL","MNT_MULTILABEL","MNT_NOATIME","MNT_NOBLOCK","MNT_NODEV","MNT_NOEXEC","MNT_NOSUID","MNT_NOUSERXATTR","MNT_NOWAIT","MNT_QUARANTINE","MNT_QUOTA","MNT_RDONLY","MNT_RELOAD","MNT_ROOTFS","MNT_SNAPSHOT","MNT_SYNCHRONOUS","MNT_UNION","MNT_UPDATE","MNT_WAIT","MOD_CLKA","MOD_CLKB","MOD_ESTERROR","MOD_FREQUENCY","MOD_MAXERROR","MOD_MICRO","MOD_NANO","MOD_OFFSET","MOD_PPSMAX","MOD_STATUS","MOD_TAI","MOD_TIMECONST","MON_1","MON_10","MON_11","MON_12","MON_2","MON_3","MON_4","MON_5","MON_6","MON_7","MON_8","MON_9","MSG_CTRUNC","MSG_DONTROUTE","MSG_DONTWAIT","MSG_EOF","MSG_EOR","MSG_FLUSH","MSG_HAVEMORE","MSG_HOLD","MSG_OOB","MSG_PEEK","MSG_RCVMORE","MSG_SEND","MSG_TRUNC","MSG_WAITALL","MS_ASYNC","MS_DEACTIVATE","MS_INVALIDATE","MS_KILLPAGES","MS_SYNC","NANOSECOND","NCCS","NET_RT_DUMP","NET_RT_FLAGS","NET_RT_IFLIST","NET_RT_IFLIST2","NEW_TIME","NI_DGRAM","NI_MAXHOST","NI_MAXSERV","NI_NAMEREQD","NI_NOFQDN","NI_NUMERICHOST","NI_NUMERICSCOPE","NI_NUMERICSERV","NL0","NL1","NLDLY","NOEXPR","NOFLSH","NOKERNINFO","NOSTR","NOTE_ABSOLUTE","NOTE_ATTRIB","NOTE_BACKGROUND","NOTE_CHILD","NOTE_CRITICAL","NOTE_DELETE","NOTE_EXEC","NOTE_EXIT","NOTE_EXITSTATUS","NOTE_EXIT_CSERROR","NOTE_EXIT_DECRYPTFAIL","NOTE_EXIT_DETAIL","NOTE_EXIT_DETAIL_MASK","NOTE_EXIT_MEMORY","NOTE_EXTEND","NOTE_FFAND","NOTE_FFCOPY","NOTE_FFCTRLMASK","NOTE_FFLAGSMASK","NOTE_FFNOP","NOTE_FFOR","NOTE_FORK","NOTE_LEEWAY","NOTE_LINK","NOTE_LOWAT","NOTE_NONE","NOTE_NSECONDS","NOTE_PCTRLMASK","NOTE_PDATAMASK","NOTE_RENAME","NOTE_REVOKE","NOTE_SECONDS","NOTE_SIGNAL","NOTE_TRACK","NOTE_TRACKERR","NOTE_TRIGGER","NOTE_USECONDS","NOTE_VM_ERROR","NOTE_VM_PRESSURE","NOTE_VM_PRESSURE_SUDDEN_TERMINATE","NOTE_VM_PRESSURE_TERMINATE","NOTE_WRITE","NTP_API","OCRNL","OFDEL","OFILL","OLD_TIME","ONLCR","ONLRET","ONOCR","ONOEOT","OPOST","OS_LOG_TYPE_DEBUG","OS_LOG_TYPE_DEFAULT","OS_LOG_TYPE_ERROR","OS_LOG_TYPE_FAULT","OS_LOG_TYPE_INFO","OS_SIGNPOST_EVENT","OS_SIGNPOST_INTERVAL_BEGIN","OS_SIGNPOST_INTERVAL_END","OS_UNFAIR_LOCK_INIT","OXTABS","O_ACCMODE","O_APPEND","O_ASYNC","O_CLOEXEC","O_CREAT","O_DIRECTORY","O_DSYNC","O_EVTONLY","O_EXCL","O_EXLOCK","O_FSYNC","O_NDELAY","O_NOCTTY","O_NOFOLLOW","O_NOFOLLOW_ANY","O_NONBLOCK","O_RDONLY","O_RDWR","O_SHLOCK","O_SYMLINK","O_SYNC","O_TRUNC","O_WRONLY","PARENB","PARMRK","PARODD","PATH_MAX","PENDIN","PF_APPLETALK","PF_CCITT","PF_CHAOS","PF_CNT","PF_COIP","PF_DATAKIT","PF_DECnet","PF_DLI","PF_ECMA","PF_HYLINK","PF_IMPLINK","PF_INET","PF_INET6","PF_IPX","PF_ISDN","PF_ISO","PF_KEY","PF_LAT","PF_LINK","PF_LOCAL","PF_NATM","PF_NDRV","PF_NETBIOS","PF_NS","PF_OSI","PF_PIP","PF_PPP","PF_PUP","PF_ROUTE","PF_RTIP","PF_SIP","PF_SNA","PF_SYSTEM","PF_UNIX","PF_UNSPEC","PF_XTP","PIPE_BUF","PM_STR","POLLERR","POLLHUP","POLLIN","POLLNVAL","POLLOUT","POLLPRI","POLLRDBAND","POLLRDNORM","POLLWRBAND","POLLWRNORM","POSIX_MADV_DONTNEED","POSIX_MADV_NORMAL","POSIX_MADV_RANDOM","POSIX_MADV_SEQUENTIAL","POSIX_MADV_WILLNEED","POSIX_SPAWN_CLOEXEC_DEFAULT","POSIX_SPAWN_RESETIDS","POSIX_SPAWN_SETEXEC","POSIX_SPAWN_SETPGROUP","POSIX_SPAWN_SETSIGDEF","POSIX_SPAWN_SETSIGMASK","POSIX_SPAWN_START_SUSPENDED","PRIO_DARWIN_BG","PRIO_DARWIN_NONUI","PRIO_DARWIN_PROCESS","PRIO_DARWIN_THREAD","PRIO_MAX","PRIO_MIN","PRIO_PGRP","PRIO_PROCESS","PRIO_USER","PROCESSOR_BASIC_INFO","PROCESSOR_CPU_LOAD_INFO","PROCESSOR_PM_REGS_INFO","PROCESSOR_SET_BASIC_INFO","PROCESSOR_SET_LOAD_INFO","PROCESSOR_TEMPERATURE","PROC_CSM_ALL","PROC_CSM_NOSMT","PROC_CSM_TECS","PROC_PIDPATHINFO_MAXSIZE","PROC_PIDTASKALLINFO","PROC_PIDTASKINFO","PROC_PIDTBSDINFO","PROC_PIDTHREADINFO","PROC_PIDVNODEPATHINFO","PROT_EXEC","PROT_NONE","PROT_READ","PROT_WRITE","PTHREAD_COND_INITIALIZER","PTHREAD_CREATE_DETACHED","PTHREAD_CREATE_JOINABLE","PTHREAD_INTROSPECTION_THREAD_CREATE","PTHREAD_INTROSPECTION_THREAD_DESTROY","PTHREAD_INTROSPECTION_THREAD_START","PTHREAD_INTROSPECTION_THREAD_TERMINATE","PTHREAD_MUTEX_DEFAULT","PTHREAD_MUTEX_ERRORCHECK","PTHREAD_MUTEX_INITIALIZER","PTHREAD_MUTEX_NORMAL","PTHREAD_MUTEX_RECURSIVE","PTHREAD_PROCESS_PRIVATE","PTHREAD_PROCESS_SHARED","PTHREAD_RWLOCK_INITIALIZER","PTHREAD_STACK_MIN","PT_ATTACH","PT_ATTACHEXC","PT_CONTINUE","PT_DENY_ATTACH","PT_DETACH","PT_FIRSTMACH","PT_FORCEQUOTA","PT_KILL","PT_READ_D","PT_READ_I","PT_READ_U","PT_SIGEXC","PT_STEP","PT_THUPDATE","PT_TRACE_ME","PT_WRITE_D","PT_WRITE_I","PT_WRITE_U","P_ALL","P_PGID","P_PID","QCMD","QOS_CLASS_BACKGROUND","QOS_CLASS_DEFAULT","QOS_CLASS_UNSPECIFIED","QOS_CLASS_USER_INITIATED","QOS_CLASS_USER_INTERACTIVE","QOS_CLASS_UTILITY","Q_GETQUOTA","Q_QUOTAOFF","Q_QUOTAON","Q_SETQUOTA","Q_SYNC","RADIXCHAR","RAND_MAX","REG_ASSERT","REG_ATOI","REG_BACKR","REG_BADBR","REG_BADPAT","REG_BADRPT","REG_BASIC","REG_DUMP","REG_EBRACE","REG_EBRACK","REG_ECOLLATE","REG_ECTYPE","REG_EESCAPE","REG_EMPTY","REG_EPAREN","REG_ERANGE","REG_ESPACE","REG_ESUBREG","REG_EXTENDED","REG_ICASE","REG_INVARG","REG_ITOA","REG_LARGE","REG_NEWLINE","REG_NOMATCH","REG_NOSPEC","REG_NOSUB","REG_NOTBOL","REG_NOTEOL","REG_PEND","REG_STARTEND","REG_TRACE","RENAME_EXCL","RENAME_SWAP","RLIMIT_AS","RLIMIT_CORE","RLIMIT_CPU","RLIMIT_DATA","RLIMIT_FSIZE","RLIMIT_MEMLOCK","RLIMIT_NOFILE","RLIMIT_NPROC","RLIMIT_RSS","RLIMIT_STACK","RLIM_INFINITY","RLIM_NLIMITS","RTAX_AUTHOR","RTAX_BRD","RTAX_DST","RTAX_GATEWAY","RTAX_GENMASK","RTAX_IFA","RTAX_IFP","RTAX_MAX","RTAX_NETMASK","RTA_AUTHOR","RTA_BRD","RTA_DST","RTA_GATEWAY","RTA_GENMASK","RTA_IFA","RTA_IFP","RTA_NETMASK","RTF_BLACKHOLE","RTF_BROADCAST","RTF_CLONING","RTF_CONDEMNED","RTF_DEAD","RTF_DELCLONE","RTF_DONE","RTF_DYNAMIC","RTF_GATEWAY","RTF_GLOBAL","RTF_HOST","RTF_IFREF","RTF_IFSCOPE","RTF_LLINFO","RTF_LOCAL","RTF_MODIFIED","RTF_MULTICAST","RTF_NOIFREF","RTF_PINNED","RTF_PRCLONING","RTF_PROTO1","RTF_PROTO2","RTF_PROTO3","RTF_PROXY","RTF_REJECT","RTF_ROUTER","RTF_STATIC","RTF_UP","RTF_WASCLONED","RTF_XRESOLVE","RTLD_DEFAULT","RTLD_FIRST","RTLD_GLOBAL","RTLD_LAZY","RTLD_LOCAL","RTLD_NEXT","RTLD_NODELETE","RTLD_NOLOAD","RTLD_NOW","RTLD_SELF","RTM_ADD","RTM_CHANGE","RTM_DELADDR","RTM_DELETE","RTM_DELMADDR","RTM_GET","RTM_GET2","RTM_IFINFO","RTM_IFINFO2","RTM_LOCK","RTM_LOSING","RTM_MISS","RTM_NEWADDR","RTM_NEWMADDR","RTM_NEWMADDR2","RTM_OLDADD","RTM_OLDDEL","RTM_REDIRECT","RTM_RESOLVE","RTM_VERSION","RTV_EXPIRE","RTV_HOPCOUNT","RTV_MTU","RTV_RPIPE","RTV_RTT","RTV_RTTVAR","RTV_SPIPE","RTV_SSTHRESH","RUN_LVL","RUSAGE_CHILDREN","RUSAGE_INFO_V0","RUSAGE_INFO_V1","RUSAGE_INFO_V2","RUSAGE_INFO_V3","RUSAGE_INFO_V4","RUSAGE_SELF","R_OK","SAE_ASSOCID_ALL","SAE_ASSOCID_ANY","SAE_CONNID_ALL","SAE_CONNID_ANY","SA_NOCLDSTOP","SA_NOCLDWAIT","SA_NODEFER","SA_ONSTACK","SA_RESETHAND","SA_RESTART","SA_SIGINFO","SCALE_PPM","SCHED_FIFO","SCHED_OTHER","SCHED_RR","SCM_CREDS","SCM_RIGHTS","SCM_TIMESTAMP","SEEK_CUR","SEEK_DATA","SEEK_END","SEEK_HOLE","SEEK_SET","SEM_FAILED","SEM_UNDO","SETALL","SETVAL","SF_APPEND","SF_ARCHIVED","SF_IMMUTABLE","SF_SETTABLE","SHMLBA","SHM_R","SHM_RDONLY","SHM_RND","SHM_W","SHUTDOWN_TIME","SHUT_RD","SHUT_RDWR","SHUT_WR","SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGEMT","SIGEV_NONE","SIGEV_SIGNAL","SIGEV_THREAD","SIGFPE","SIGHUP","SIGILL","SIGINFO","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGNATURE","SIGPIPE","SIGPROF","SIGQUIT","SIGSEGV","SIGSTKSZ","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIG_BLOCK","SIG_DFL","SIG_ERR","SIG_IGN","SIG_SETMASK","SIG_UNBLOCK","SIOCGIFADDR","SOCK_DGRAM","SOCK_MAXADDRLEN","SOCK_RAW","SOCK_RDM","SOCK_SEQPACKET","SOCK_STREAM","SOL_LOCAL","SOL_SOCKET","SOMAXCONN","SO_ACCEPTCONN","SO_BROADCAST","SO_DEBUG","SO_DONTROUTE","SO_DONTTRUNC","SO_ERROR","SO_KEEPALIVE","SO_LABEL","SO_LINGER","SO_LINGER_SEC","SO_NKE","SO_NOADDRERR","SO_NOSIGPIPE","SO_NOTIFYCONFLICT","SO_NP_EXTENSIONS","SO_NREAD","SO_NWRITE","SO_OOBINLINE","SO_PEERLABEL","SO_RANDOMPORT","SO_RCVBUF","SO_RCVLOWAT","SO_RCVTIMEO","SO_REUSEADDR","SO_REUSEPORT","SO_REUSESHAREUID","SO_SNDBUF","SO_SNDLOWAT","SO_SNDTIMEO","SO_TIMESTAMP","SO_TIMESTAMP_MONOTONIC","SO_TYPE","SO_USELOOPBACK","SO_WANTMORE","SO_WANTOOBFLAG","SS_DISABLE","SS_ONSTACK","STA_CLK","STA_CLOCKERR","STA_DEL","STA_FLL","STA_FREQHOLD","STA_INS","STA_MODE","STA_NANO","STA_PLL","STA_PPSERROR","STA_PPSFREQ","STA_PPSJITTER","STA_PPSSIGNAL","STA_PPSTIME","STA_PPSWANDER","STA_RONLY","STA_UNSYNC","STDERR_FILENO","STDIN_FILENO","STDOUT_FILENO","ST_NOSUID","ST_RDONLY","SUPERPAGE_NONE","SUPERPAGE_SIZE_2MB","SUPERPAGE_SIZE_ANY","SYSDIR_DIRECTORY_ADMIN_APPLICATION","SYSDIR_DIRECTORY_ALL_APPLICATIONS","SYSDIR_DIRECTORY_ALL_LIBRARIES","SYSDIR_DIRECTORY_APPLICATION","SYSDIR_DIRECTORY_APPLICATION_SUPPORT","SYSDIR_DIRECTORY_AUTOSAVED_INFORMATION","SYSDIR_DIRECTORY_CACHES","SYSDIR_DIRECTORY_CORESERVICE","SYSDIR_DIRECTORY_DEMO_APPLICATION","SYSDIR_DIRECTORY_DESKTOP","SYSDIR_DIRECTORY_DEVELOPER","SYSDIR_DIRECTORY_DEVELOPER_APPLICATION","SYSDIR_DIRECTORY_DOCUMENT","SYSDIR_DIRECTORY_DOCUMENTATION","SYSDIR_DIRECTORY_DOWNLOADS","SYSDIR_DIRECTORY_INPUT_METHODS","SYSDIR_DIRECTORY_LIBRARY","SYSDIR_DIRECTORY_MOVIES","SYSDIR_DIRECTORY_MUSIC","SYSDIR_DIRECTORY_PICTURES","SYSDIR_DIRECTORY_PREFERENCE_PANES","SYSDIR_DIRECTORY_PRINTER_DESCRIPTION","SYSDIR_DIRECTORY_SHARED_PUBLIC","SYSDIR_DIRECTORY_USER","SYSDIR_DOMAIN_MASK_ALL","SYSDIR_DOMAIN_MASK_LOCAL","SYSDIR_DOMAIN_MASK_NETWORK","SYSDIR_DOMAIN_MASK_SYSTEM","SYSDIR_DOMAIN_MASK_USER","SYSPROTO_CONTROL","SYSPROTO_EVENT","S_IEXEC","S_IFBLK","S_IFCHR","S_IFDIR","S_IFIFO","S_IFLNK","S_IFMT","S_IFREG","S_IFSOCK","S_IREAD","S_IRGRP","S_IROTH","S_IRUSR","S_IRWXG","S_IRWXO","S_IRWXU","S_ISGID","S_ISUID","S_ISVTX","S_IWGRP","S_IWOTH","S_IWRITE","S_IWUSR","S_IXGRP","S_IXOTH","S_IXUSR","TAB0","TAB1","TAB2","TAB3","TABDLY","TASK_THREAD_TIMES_INFO","TASK_THREAD_TIMES_INFO_COUNT","TCIFLUSH","TCIOFF","TCIOFLUSH","TCION","TCOFLUSH","TCOOFF","TCOON","TCP_FASTOPEN","TCP_KEEPALIVE","TCP_KEEPCNT","TCP_KEEPINTVL","TCP_MAXSEG","TCP_NODELAY","TCP_NOOPT","TCP_NOPUSH","TCSADRAIN","TCSAFLUSH","TCSANOW","THOUSEP","THREAD_AFFINITY_POLICY","THREAD_AFFINITY_POLICY_COUNT","THREAD_AFFINITY_TAG_NULL","THREAD_BACKGROUND_POLICY","THREAD_BACKGROUND_POLICY_COUNT","THREAD_BACKGROUND_POLICY_DARWIN_BG","THREAD_BASIC_INFO","THREAD_BASIC_INFO_COUNT","THREAD_EXTENDED_INFO","THREAD_EXTENDED_INFO_COUNT","THREAD_EXTENDED_POLICY","THREAD_EXTENDED_POLICY_COUNT","THREAD_IDENTIFIER_INFO","THREAD_IDENTIFIER_INFO_COUNT","THREAD_LATENCY_QOS_POLICY","THREAD_LATENCY_QOS_POLICY_COUNT","THREAD_PRECEDENCE_POLICY","THREAD_PRECEDENCE_POLICY_COUNT","THREAD_STANDARD_POLICY","THREAD_STANDARD_POLICY_COUNT","THREAD_THROUGHPUT_QOS_POLICY","THREAD_THROUGHPUT_QOS_POLICY_COUNT","THREAD_TIME_CONSTRAINT_POLICY","THREAD_TIME_CONSTRAINT_POLICY_COUNT","TH_FLAGS_GLOBAL_FORCED_IDLE","TH_FLAGS_IDLE","TH_FLAGS_SWAPPED","TH_STATE_HALTED","TH_STATE_RUNNING","TH_STATE_STOPPED","TH_STATE_UNINTERRUPTIBLE","TH_STATE_WAITING","TIME_DEL","TIME_ERROR","TIME_INS","TIME_OK","TIME_OOP","TIME_WAIT","TIOCCBRK","TIOCCDTR","TIOCCONS","TIOCDCDTIMESTAMP","TIOCDRAIN","TIOCDSIMICROCODE","TIOCEXCL","TIOCEXT","TIOCFLUSH","TIOCGDRAINWAIT","TIOCGETD","TIOCGPGRP","TIOCGWINSZ","TIOCIXOFF","TIOCIXON","TIOCMBIC","TIOCMBIS","TIOCMGDTRWAIT","TIOCMGET","TIOCMODG","TIOCMODS","TIOCMSDTRWAIT","TIOCMSET","TIOCM_CAR","TIOCM_CD","TIOCM_CTS","TIOCM_DSR","TIOCM_DTR","TIOCM_LE","TIOCM_RI","TIOCM_RNG","TIOCM_RTS","TIOCM_SR","TIOCM_ST","TIOCNOTTY","TIOCNXCL","TIOCOUTQ","TIOCPKT","TIOCPKT_DATA","TIOCPKT_DOSTOP","TIOCPKT_FLUSHREAD","TIOCPKT_FLUSHWRITE","TIOCPKT_IOCTL","TIOCPKT_NOSTOP","TIOCPKT_START","TIOCPKT_STOP","TIOCPTYGNAME","TIOCPTYGRANT","TIOCPTYUNLK","TIOCREMOTE","TIOCSBRK","TIOCSCONS","TIOCSCTTY","TIOCSDRAINWAIT","TIOCSDTR","TIOCSETD","TIOCSIG","TIOCSPGRP","TIOCSTART","TIOCSTAT","TIOCSTI","TIOCSTOP","TIOCSWINSZ","TIOCTIMESTAMP","TIOCUCNTL","TMP_MAX","TOSTOP","T_FMT","T_FMT_AMPM","UF_APPEND","UF_COMPRESSED","UF_HIDDEN","UF_IMMUTABLE","UF_NODUMP","UF_OPAQUE","UF_SETTABLE","UF_TRACKED","USER_BC_BASE_MAX","USER_BC_DIM_MAX","USER_BC_SCALE_MAX","USER_BC_STRING_MAX","USER_COLL_WEIGHTS_MAX","USER_CS_PATH","USER_EXPR_NEST_MAX","USER_LINE_MAX","USER_MAXID","USER_POSIX2_CHAR_TERM","USER_POSIX2_C_BIND","USER_POSIX2_C_DEV","USER_POSIX2_FORT_DEV","USER_POSIX2_FORT_RUN","USER_POSIX2_LOCALEDEF","USER_POSIX2_SW_DEV","USER_POSIX2_UPE","USER_POSIX2_VERSION","USER_PROCESS","USER_RE_DUP_MAX","USER_STREAM_MAX","USER_TZNAME_MAX","USRQUOTA","UTIME_NOW","UTIME_OMIT","UTUN_OPT_FLAGS","UTUN_OPT_IFNAME","VDISCARD","VDSUSP","VEOF","VEOL","VEOL2","VERASE","VINTR","VKILL","VLNEXT","VMIN","VM_FLAGS_ALIAS_MASK","VM_FLAGS_ANYWHERE","VM_FLAGS_FIXED","VM_FLAGS_NO_CACHE","VM_FLAGS_OVERWRITE","VM_FLAGS_PURGABLE","VM_FLAGS_RANDOM_ADDR","VM_FLAGS_RESILIENT_CODESIGN","VM_FLAGS_RESILIENT_MEDIA","VM_FLAGS_RETURN_4K_DATA_ADDR","VM_FLAGS_RETURN_DATA_ADDR","VM_FLAGS_SUPERPAGE_MASK","VM_FLAGS_SUPERPAGE_NONE","VM_FLAGS_SUPERPAGE_SHIFT","VM_FLAGS_SUPERPAGE_SIZE_2MB","VM_FLAGS_SUPERPAGE_SIZE_ANY","VM_FLAGS_USER_ALLOCATE","VM_FLAGS_USER_MAP","VM_FLAGS_USER_REMAP","VM_LOADAVG","VM_MACHFACTOR","VM_MAKE_TAG","VM_MAXID","VM_MEMORY_ACCELERATE","VM_MEMORY_ANALYSIS_TOOL","VM_MEMORY_APPKIT","VM_MEMORY_APPLICATION_SPECIFIC_1","VM_MEMORY_APPLICATION_SPECIFIC_16","VM_MEMORY_ASL","VM_MEMORY_ASSETSD","VM_MEMORY_ATS","VM_MEMORY_CARBON","VM_MEMORY_CGIMAGE","VM_MEMORY_COREDATA","VM_MEMORY_COREDATA_OBJECTIDS","VM_MEMORY_COREGRAPHICS","VM_MEMORY_COREGRAPHICS_BACKINGSTORES","VM_MEMORY_COREGRAPHICS_DATA","VM_MEMORY_COREGRAPHICS_FRAMEBUFFERS","VM_MEMORY_COREGRAPHICS_MISC","VM_MEMORY_COREGRAPHICS_SHARED","VM_MEMORY_COREGRAPHICS_XALLOC","VM_MEMORY_COREIMAGE","VM_MEMORY_COREPROFILE","VM_MEMORY_CORESERVICES","VM_MEMORY_COREUI","VM_MEMORY_COREUIFILE","VM_MEMORY_CORPSEINFO","VM_MEMORY_DHMM","VM_MEMORY_DYLD","VM_MEMORY_DYLD_MALLOC","VM_MEMORY_DYLIB","VM_MEMORY_FOUNDATION","VM_MEMORY_GENEALOGY","VM_MEMORY_GLSL","VM_MEMORY_GUARD","VM_MEMORY_IMAGEIO","VM_MEMORY_IOKIT","VM_MEMORY_JAVA","VM_MEMORY_JAVASCRIPT_CORE","VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR","VM_MEMORY_JAVASCRIPT_JIT_REGISTER_FILE","VM_MEMORY_LAYERKIT","VM_MEMORY_LIBDISPATCH","VM_MEMORY_MACH_MSG","VM_MEMORY_MALLOC","VM_MEMORY_MALLOC_HUGE","VM_MEMORY_MALLOC_LARGE","VM_MEMORY_MALLOC_LARGE_REUSABLE","VM_MEMORY_MALLOC_LARGE_REUSED","VM_MEMORY_MALLOC_NANO","VM_MEMORY_MALLOC_SMALL","VM_MEMORY_MALLOC_TINY","VM_MEMORY_OBJC_DISPATCHERS","VM_MEMORY_OPENCL","VM_MEMORY_OS_ALLOC_ONCE","VM_MEMORY_RAWCAMERA","VM_MEMORY_REALLOC","VM_MEMORY_SBRK","VM_MEMORY_SCENEKIT","VM_MEMORY_SHARED_PMAP","VM_MEMORY_SKYWALK","VM_MEMORY_SQLITE","VM_MEMORY_STACK","VM_MEMORY_SWIFT_METADATA","VM_MEMORY_SWIFT_RUNTIME","VM_MEMORY_TCMALLOC","VM_MEMORY_UNSHARED_PMAP","VM_MEMORY_WEBCORE_PURGEABLE_BUFFERS","VM_METER","VM_PAGE_QUERY_PAGE_COPIED","VM_PAGE_QUERY_PAGE_CS_NX","VM_PAGE_QUERY_PAGE_CS_TAINTED","VM_PAGE_QUERY_PAGE_CS_VALIDATED","VM_PAGE_QUERY_PAGE_DIRTY","VM_PAGE_QUERY_PAGE_EXTERNAL","VM_PAGE_QUERY_PAGE_FICTITIOUS","VM_PAGE_QUERY_PAGE_PAGED_OUT","VM_PAGE_QUERY_PAGE_PRESENT","VM_PAGE_QUERY_PAGE_REF","VM_PAGE_QUERY_PAGE_SPECULATIVE","VM_PROT_EXECUTE","VM_PROT_NONE","VM_PROT_READ","VM_PROT_WRITE","VM_SWAPUSAGE","VOL_CAPABILITIES_FORMAT","VOL_CAPABILITIES_INTERFACES","VOL_CAP_FMT_2TB_FILESIZE","VOL_CAP_FMT_64BIT_OBJECT_IDS","VOL_CAP_FMT_CASE_PRESERVING","VOL_CAP_FMT_CASE_SENSITIVE","VOL_CAP_FMT_DECMPFS_COMPRESSION","VOL_CAP_FMT_DIR_HARDLINKS","VOL_CAP_FMT_DOCUMENT_ID","VOL_CAP_FMT_FAST_STATFS","VOL_CAP_FMT_HARDLINKS","VOL_CAP_FMT_HIDDEN_FILES","VOL_CAP_FMT_JOURNAL","VOL_CAP_FMT_JOURNAL_ACTIVE","VOL_CAP_FMT_NO_IMMUTABLE_FILES","VOL_CAP_FMT_NO_PERMISSIONS","VOL_CAP_FMT_NO_ROOT_TIMES","VOL_CAP_FMT_NO_VOLUME_SIZES","VOL_CAP_FMT_OPENDENYMODES","VOL_CAP_FMT_PATH_FROM_ID","VOL_CAP_FMT_PERSISTENTOBJECTIDS","VOL_CAP_FMT_SEALED","VOL_CAP_FMT_SHARED_SPACE","VOL_CAP_FMT_SPARSE_FILES","VOL_CAP_FMT_SYMBOLICLINKS","VOL_CAP_FMT_VOL_GROUPS","VOL_CAP_FMT_WRITE_GENERATION_COUNT","VOL_CAP_FMT_ZERO_RUNS","VOL_CAP_INT_ADVLOCK","VOL_CAP_INT_ALLOCATE","VOL_CAP_INT_ATTRLIST","VOL_CAP_INT_CLONE","VOL_CAP_INT_COPYFILE","VOL_CAP_INT_EXCHANGEDATA","VOL_CAP_INT_EXTENDED_ATTR","VOL_CAP_INT_EXTENDED_SECURITY","VOL_CAP_INT_FLOCK","VOL_CAP_INT_MANLOCK","VOL_CAP_INT_NAMEDSTREAMS","VOL_CAP_INT_NFSEXPORT","VOL_CAP_INT_READDIRATTR","VOL_CAP_INT_RENAME_EXCL","VOL_CAP_INT_RENAME_OPENFAIL","VOL_CAP_INT_RENAME_SWAP","VOL_CAP_INT_SEARCHFS","VOL_CAP_INT_SNAPSHOT","VOL_CAP_INT_USERACCESS","VOL_CAP_INT_VOL_RENAME","VQUIT","VREPRINT","VSTART","VSTATUS","VSTOP","VSUSP","VT0","VT1","VTDLY","VTIME","VWERASE","WCONTINUED","WCOREDUMP","WEXITED","WEXITSTATUS","WIFCONTINUED","WIFEXITED","WIFSIGNALED","WIFSTOPPED","WNOHANG","WNOWAIT","WSTOPPED","WSTOPSIG","WTERMSIG","WUNTRACED","W_OK","XATTR_CREATE","XATTR_NODEFAULT","XATTR_NOFOLLOW","XATTR_NOSECURITY","XATTR_REPLACE","XATTR_SHOWCOMPRESSION","XUCRED_VERSION","X_OK","YESEXPR","YESSTR","_CS_DARWIN_USER_CACHE_DIR","_CS_DARWIN_USER_DIR","_CS_DARWIN_USER_TEMP_DIR","_CS_PATH","_IOFBF","_IOLBF","_IONBF","_NSGetEnviron","_NSGetExecutablePath","_PC_CHOWN_RESTRICTED","_PC_LINK_MAX","_PC_MAX_CANON","_PC_MAX_INPUT","_PC_NAME_MAX","_PC_NO_TRUNC","_PC_PATH_MAX","_PC_PIPE_BUF","_PC_VDISABLE","_POSIX_VDISABLE","_PTHREAD_COND_SIG_init","_PTHREAD_MUTEX_SIG_init","_PTHREAD_RWLOCK_SIG_init","_RLIMIT_POSIX_FLAG","_SC_2_CHAR_TERM","_SC_2_C_BIND","_SC_2_C_DEV","_SC_2_FORT_DEV","_SC_2_FORT_RUN","_SC_2_LOCALEDEF","_SC_2_PBS","_SC_2_PBS_ACCOUNTING","_SC_2_PBS_CHECKPOINT","_SC_2_PBS_LOCATE","_SC_2_PBS_MESSAGE","_SC_2_PBS_TRACK","_SC_2_SW_DEV","_SC_2_UPE","_SC_2_VERSION","_SC_ADVISORY_INFO","_SC_AIO_LISTIO_MAX","_SC_AIO_MAX","_SC_AIO_PRIO_DELTA_MAX","_SC_ARG_MAX","_SC_ASYNCHRONOUS_IO","_SC_ATEXIT_MAX","_SC_BARRIERS","_SC_BC_BASE_MAX","_SC_BC_DIM_MAX","_SC_BC_SCALE_MAX","_SC_BC_STRING_MAX","_SC_CHILD_MAX","_SC_CLK_TCK","_SC_CLOCK_SELECTION","_SC_COLL_WEIGHTS_MAX","_SC_CPUTIME","_SC_DELAYTIMER_MAX","_SC_EXPR_NEST_MAX","_SC_FILE_LOCKING","_SC_FSYNC","_SC_GETGR_R_SIZE_MAX","_SC_GETPW_R_SIZE_MAX","_SC_HOST_NAME_MAX","_SC_IOV_MAX","_SC_IPV6","_SC_JOB_CONTROL","_SC_LINE_MAX","_SC_LOGIN_NAME_MAX","_SC_MAPPED_FILES","_SC_MEMLOCK","_SC_MEMLOCK_RANGE","_SC_MEMORY_PROTECTION","_SC_MESSAGE_PASSING","_SC_MONOTONIC_CLOCK","_SC_MQ_OPEN_MAX","_SC_MQ_PRIO_MAX","_SC_NGROUPS_MAX","_SC_NPROCESSORS_CONF","_SC_NPROCESSORS_ONLN","_SC_OPEN_MAX","_SC_PAGESIZE","_SC_PAGE_SIZE","_SC_PASS_MAX","_SC_PHYS_PAGES","_SC_PRIORITIZED_IO","_SC_PRIORITY_SCHEDULING","_SC_RAW_SOCKETS","_SC_READER_WRITER_LOCKS","_SC_REALTIME_SIGNALS","_SC_REGEXP","_SC_RE_DUP_MAX","_SC_RTSIG_MAX","_SC_SAVED_IDS","_SC_SEMAPHORES","_SC_SEM_NSEMS_MAX","_SC_SEM_VALUE_MAX","_SC_SHARED_MEMORY_OBJECTS","_SC_SHELL","_SC_SIGQUEUE_MAX","_SC_SPAWN","_SC_SPIN_LOCKS","_SC_SPORADIC_SERVER","_SC_SS_REPL_MAX","_SC_STREAM_MAX","_SC_SYMLOOP_MAX","_SC_SYNCHRONIZED_IO","_SC_THREADS","_SC_THREAD_ATTR_STACKADDR","_SC_THREAD_ATTR_STACKSIZE","_SC_THREAD_CPUTIME","_SC_THREAD_DESTRUCTOR_ITERATIONS","_SC_THREAD_KEYS_MAX","_SC_THREAD_PRIORITY_SCHEDULING","_SC_THREAD_PRIO_INHERIT","_SC_THREAD_PRIO_PROTECT","_SC_THREAD_PROCESS_SHARED","_SC_THREAD_SAFE_FUNCTIONS","_SC_THREAD_SPORADIC_SERVER","_SC_THREAD_STACK_MIN","_SC_THREAD_THREADS_MAX","_SC_TIMEOUTS","_SC_TIMERS","_SC_TIMER_MAX","_SC_TRACE","_SC_TRACE_EVENT_FILTER","_SC_TRACE_EVENT_NAME_MAX","_SC_TRACE_INHERIT","_SC_TRACE_LOG","_SC_TRACE_NAME_MAX","_SC_TRACE_SYS_MAX","_SC_TRACE_USER_EVENT_MAX","_SC_TTY_NAME_MAX","_SC_TYPED_MEMORY_OBJECTS","_SC_TZNAME_MAX","_SC_V6_ILP32_OFF32","_SC_V6_ILP32_OFFBIG","_SC_V6_LP64_OFF64","_SC_V6_LPBIG_OFFBIG","_SC_VERSION","_SC_XBS5_ILP32_OFF32","_SC_XBS5_ILP32_OFFBIG","_SC_XBS5_LP64_OFF64","_SC_XBS5_LPBIG_OFFBIG","_SC_XOPEN_CRYPT","_SC_XOPEN_ENH_I18N","_SC_XOPEN_LEGACY","_SC_XOPEN_REALTIME","_SC_XOPEN_REALTIME_THREADS","_SC_XOPEN_SHM","_SC_XOPEN_STREAMS","_SC_XOPEN_UNIX","_SC_XOPEN_VERSION","_SC_XOPEN_XCU_VERSION","_UTX_HOSTSIZE","_UTX_IDSIZE","_UTX_LINESIZE","_UTX_USERSIZE","_WSTATUS","_WSTOPPED","__PTHREAD_CONDATTR_SIZE__","__PTHREAD_COND_SIZE__","__PTHREAD_MUTEX_SIZE__","__PTHREAD_RWLOCKATTR_SIZE__","__PTHREAD_RWLOCK_SIZE__","__cpsr","__darwin_arm_exception_state64","__darwin_arm_neon_state64","__darwin_arm_thread_state64","__darwin_mcontext64","__error","__es","__esr","__exception","__far","__fp","__fpcr","__fpsr","__int128","__int128_t","__lr","__ns","__pad","__pc","__sp","__ss","__uint128","__uint128_t","__v","__x","_dyld_get_image_header","_dyld_get_image_name","_dyld_get_image_vmaddr_slide","_dyld_image_count","_exit","_key","_seq","abort","abs","accept","access","acct","actime","active_count","active_count","address","addrinfo","adjtime","affinity_tag","ai_addr","ai_addrlen","ai_canonname","ai_family","ai_flags","ai_next","ai_protocol","ai_socktype","aio_buf","aio_cancel","aio_error","aio_fildes","aio_fsync","aio_lio_opcode","aio_nbytes","aio_offset","aio_read","aio_reqprio","aio_return","aio_sigevent","aio_suspend","aio_write","aiocb","alarm","ar_hln","ar_hrd","ar_op","ar_pln","ar_pro","arc4random","arc4random_buf","arc4random_uniform","arphdr","array","atexit","atof","atoi","attr_dataoffset","attr_length","attrgroup_t","attribute_set_t","attrlist","attrreference_t","backtrace","backtrace_async","backtrace_from_fp","backtrace_image_offsets","backtrace_symbols","backtrace_symbols_fd","basename","bh_caplen","bh_datalen","bh_hdrlen","bh_tstamp","bind","bitmapcount","blkcnt_t","blksize_t","blocks_in_use","boolean_t","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bpf_hdr","brk","bsearch","buf","bytes_free","bytes_total","bytes_used","c_cc","c_cflag","c_char","c_double","c_float","c_iflag","c_int","c_ispeed","c_lflag","c_long","c_longlong","c_oflag","c_ospeed","c_schar","c_short","c_uchar","c_uint","c_ulong","c_ulonglong","c_ushort","c_void","calcnt","calloc","capabilities","cc_t","cfgetispeed","cfgetospeed","cfmakeraw","cfsetispeed","cfsetospeed","cfsetspeed","cgid","chdir","chflags","chmod","chown","chroot","chunks_free","chunks_used","clearerr","clock_getres","clock_gettime","clock_settime","clock_t","clockid_t","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clonefile","clonefileat","close","closedir","closelog","cmd","cmd","cmd","cmdsize","cmdsize","cmdsize","cmsg_len","cmsg_level","cmsg_type","cmsghdr","commonattr","commonattr","compressions","compressor_page_count","computation","confstr","connect","connectx","constant","constraint","copyfile","copyfile_flags_t","copyfile_state_t","cow_faults","cow_faults","cpu_subtype","cpu_subtype_t","cpu_ticks","cpu_type","cpu_type_t","cpu_usage","cpusubtype","cpusubtype","cputype","cputype","cr_groups","cr_ngroups","cr_uid","cr_version","creat","cuid","currency_symbol","d_ino","d_name","d_namlen","d_reclen","d_seekoff","d_type","data","data","decimal_point","decompressions","default_policy","denom","dev_t","difftime","dirattr","dirattr","dirent","dirfd","dirname","disconnectx","dispatch_qaddr","dladdr","dlclose","dlerror","dli_fbase","dli_fname","dli_saddr","dli_sname","dlopen","dlsym","dqb_bhardlimit","dqb_bsoftlimit","dqb_btime","dqb_curbytes","dqb_curinodes","dqb_id","dqb_ihardlimit","dqb_isoftlimit","dqb_itime","dqb_spare","dqblk","drand48","dup","dup2","duplocale","e_tdev","e_tpgid","endgrent","endpwent","endservent","endutxent","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","erand48","errcnt","esterror","esterror","events","exchangedata","execl","execle","execlp","execv","execve","execvp","exit","ext","external_page_count","f_bavail","f_bavail","f_bfree","f_bfree","f_blocks","f_blocks","f_bsize","f_bsize","f_favail","f_ffree","f_ffree","f_files","f_files","f_flag","f_flags","f_flags_ext","f_frsize","f_fsid","f_fsid","f_fssubtype","f_fstypename","f_iosize","f_mntfromname","f_mntonname","f_namemax","f_owner","f_reserved","f_type","faccessat","faults","faults","fchdir","fchflags","fchmod","fchmodat","fchown","fchownat","fclonefileat","fclose","fcntl","fcopyfile","fd","fd_set","fdopen","fdopendir","feof","ferror","fflags","fflags","fflush","fgetattrlist","fgetc","fgetpos","fgets","fgetxattr","fileattr","fileattr","fileno","fileoff","fileoff","filesize","filesize","filetype","filetype","filter","filter","flags","flags","flags","flags","flags","flags","flags","flistxattr","flock","flock","fmemopen","fmount","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fopen","fork","forkattr","forkattr","forkpty","fpathconf","fpos_t","fprintf","fputc","fputs","frac_digits","fread","free","free_count","free_count","freeaddrinfo","freeifaddrs","freelocale","fremovexattr","freopen","freq","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","fsblkcnt_t","fscanf","fseek","fseeko","fsetattrlist","fsetpos","fsetxattr","fsfilcnt_t","fsid_t","fst_bytesalloc","fst_flags","fst_length","fst_offset","fst_posmode","fstat","fstatat","fstatfs","fstatvfs","fstore_t","fsync","ftell","ftello","ftok","ftruncate","futimens","futimes","fwrite","gai_strerror","getaddrinfo","getattrlist","getattrlistat","getattrlistbulk","getchar","getchar_unlocked","getcwd","getdomainname","getdtablesize","getegid","getenv","geteuid","getfsstat","getgid","getgrent","getgrgid","getgrgid_r","getgrnam","getgrnam_r","getgrouplist","getgroups","gethostid","gethostname","gethostuuid","getifaddrs","getitimer","getline","getloadavg","getlogin","getmntinfo","getnameinfo","getopt","getpeereid","getpeername","getpgid","getpgrp","getpid","getppid","getpriority","getprogname","getprotobyname","getprotobynumber","getpwent","getpwnam","getpwnam_r","getpwuid","getpwuid_r","getrlimit","getrusage","getservbyname","getservbyport","getservent","getsid","getsockname","getsockopt","gettimeofday","getuid","getutxent","getutxid","getutxline","getxattr","gid","gid_t","gl_offs","gl_pathc","gl_pathv","glob","glob_t","globfree","gmtime","gmtime_r","gr_gid","gr_mem","gr_name","gr_passwd","grantpt","group","grouping","h_addr_list","h_addrtype","h_aliases","h_length","h_name","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hdr_cnt","headers","hits","hits","host_flavor_t","host_info64_t","host_info_t","host_processor_info","host_statistics","host_statistics64","host_t","hostent","hstrerror","iconv","iconv_close","iconv_open","iconv_t","id_t","ident","ident","idtype_t","if_data","if_data64","if_freenameindex","if_index","if_indextoname","if_msghdr","if_msghdr2","if_name","if_nameindex","if_nameindex","if_nametoindex","ifa_addr","ifa_data","ifa_dstaddr","ifa_flags","ifa_name","ifa_netmask","ifa_next","ifaddrs","ifi_addrlen","ifi_addrlen","ifi_baudrate","ifi_baudrate","ifi_collisions","ifi_collisions","ifi_hdrlen","ifi_hdrlen","ifi_hwassist","ifi_ibytes","ifi_ibytes","ifi_ierrors","ifi_ierrors","ifi_imcasts","ifi_imcasts","ifi_ipackets","ifi_ipackets","ifi_iqdrops","ifi_iqdrops","ifi_lastchange","ifi_lastchange","ifi_metric","ifi_metric","ifi_mtu","ifi_mtu","ifi_noproto","ifi_noproto","ifi_obytes","ifi_obytes","ifi_oerrors","ifi_oerrors","ifi_omcasts","ifi_omcasts","ifi_opackets","ifi_opackets","ifi_physical","ifi_physical","ifi_recvquota","ifi_recvquota","ifi_recvtiming","ifi_recvtiming","ifi_reserved1","ifi_reserved2","ifi_type","ifi_type","ifi_typelen","ifi_typelen","ifi_unused1","ifi_unused1","ifi_unused2","ifi_xmitquota","ifi_xmitquota","ifi_xmittiming","ifi_xmittiming","ifm_addrs","ifm_addrs","ifm_data","ifm_data","ifm_flags","ifm_flags","ifm_index","ifm_index","ifm_msglen","ifm_msglen","ifm_snd_drops","ifm_snd_len","ifm_snd_maxlen","ifm_timer","ifm_type","ifm_type","ifm_version","ifm_version","image_offset","importance","imr_address","imr_ifindex","imr_interface","imr_interface","imr_multiaddr","imr_multiaddr","imr_multiaddr","imr_sourceaddr","in6_addr","in6_pktinfo","in_addr","in_addr_t","in_pktinfo","in_port_t","inactive_count","inactive_count","initgroups","initprot","initprot","ino_t","int16_t","int32_t","int64_t","int8_t","int_curr_symbol","int_frac_digits","int_n_cs_precedes","int_n_sep_by_space","int_n_sign_posn","int_p_cs_precedes","int_p_sep_by_space","int_p_sign_posn","integer_t","internal_page_count","intmax_t","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","intptr_t","ioctl","iov_base","iov_len","iovec","ip_mreq","ip_mreq_source","ip_mreqn","ipc_perm","ipi6_addr","ipi6_ifindex","ipi_addr","ipi_ifindex","ipi_spec_dst","ipv6_mreq","ipv6mr_interface","ipv6mr_multiaddr","is_master","isalnum","isalpha","isatty","isblank","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","it_interval","it_value","itimerval","jitcnt","jitter","jrand48","kCCAlignmentError","kCCBufferTooSmall","kCCCallSequenceError","kCCDecodeError","kCCInvalidKey","kCCKeySizeError","kCCMemoryFailure","kCCOverflow","kCCParamError","kCCRNGFailure","kCCSuccess","kCCUnimplemented","kCCUnspecifiedError","kern_return_t","kevent","kevent","kevent64","kevent64_s","key_t","kill","killpg","kqueue","l2p_contigbytes","l2p_devoffset","l2p_flags","l_len","l_linger","l_onoff","l_pid","l_start","l_type","l_whence","labs","lchown","lcong48","lconv","ledger_array_t","ledger_t","linger","link","linkat","lio_listio","listen","listxattr","load_average","load_command","locale_t","localeconv","localeconv_l","localtime","localtime_r","lockf","log2phys","login_tty","lookups","lookups","lrand48","lseek","lstat","lutimes","mach_absolute_time","mach_factor","mach_header","mach_header_64","mach_host_self","mach_msg_type_number_t","mach_port_t","mach_task_basic_info","mach_task_basic_info_data_t","mach_task_basic_info_t","mach_task_self","mach_task_self_","mach_thread_self","mach_timebase_info","mach_timebase_info","mach_timebase_info_data_t","mach_vm_address_t","mach_vm_map","mach_vm_offset_t","mach_vm_size_t","machine","madvise","magic","magic","major","makedev","malloc","malloc_default_zone","malloc_good_size","malloc_printf","malloc_size","malloc_statistics_t","malloc_zone_calloc","malloc_zone_check","malloc_zone_free","malloc_zone_from_ptr","malloc_zone_log","malloc_zone_malloc","malloc_zone_print","malloc_zone_print_ptr_info","malloc_zone_realloc","malloc_zone_statistics","malloc_zone_t","malloc_zone_valloc","max_align_t","max_size_in_use","maxerror","maxerror","maxprot","maxprot","mcontext_t","mem_entry_name_port_t","memchr","memcmp","memcpy","memmem","memmove","memory_object_offset_t","memory_object_t","memset","memset_pattern16","memset_pattern4","memset_pattern8","memset_s","microseconds","mincore","minor","mkdir","mkdirat","mkdtemp","mkfifo","mknod","mkstemp","mkstemps","mktime","mlock","mlockall","mmap","mode","mode_t","modes","modtime","mon_decimal_point","mon_grouping","mon_thousands_sep","mount","mprotect","mrand48","msg_control","msg_controllen","msg_flags","msg_iov","msg_iovlen","msg_name","msg_namelen","msghdr","mstats","mstats","msync","munlock","munlockall","munmap","n_cs_precedes","n_sep_by_space","n_sign_posn","nanosleep","nativeattr","natural_t","ncmds","ncmds","negative_sign","newlocale","nfds_t","nice","nl_item","nl_langinfo","nlink_t","no_data","nodename","nrand48","nsects","nsects","ntp_adjtime","ntp_gettime","ntptimeval","numer","off_t","offset","offset","open","open_memstream","open_wmemstream","openat","opendir","openlog","openpty","os_log_create","os_log_t","os_log_type_enabled","os_log_type_t","os_signpost_enabled","os_signpost_id_generate","os_signpost_id_make_with_pointer","os_signpost_id_t","os_signpost_type_t","os_unfair_lock","os_unfair_lock_assert_not_owner","os_unfair_lock_assert_owner","os_unfair_lock_lock","os_unfair_lock_s","os_unfair_lock_t","os_unfair_lock_trylock","os_unfair_lock_unlock","p_aliases","p_cs_precedes","p_name","p_proto","p_sep_by_space","p_sign_posn","pageins","pageins","pageouts","pageouts","passwd","pathconf","pause","pbi_comm","pbi_flags","pbi_gid","pbi_name","pbi_nfiles","pbi_nice","pbi_pgid","pbi_pid","pbi_pjobc","pbi_ppid","pbi_rgid","pbi_ruid","pbi_start_tvsec","pbi_start_tvusec","pbi_status","pbi_svgid","pbi_svuid","pbi_uid","pbi_xstatus","pbsd","pclose","period","perror","pid_t","pipe","policy","policy","policy_t","poll","pollfd","popen","positive_sign","posix_madvise","posix_memalign","posix_openpt","posix_spawn","posix_spawn_file_actions_addclose","posix_spawn_file_actions_adddup2","posix_spawn_file_actions_addopen","posix_spawn_file_actions_destroy","posix_spawn_file_actions_init","posix_spawn_file_actions_t","posix_spawnattr_destroy","posix_spawnattr_getarchpref_np","posix_spawnattr_getflags","posix_spawnattr_getpgroup","posix_spawnattr_getsigdefault","posix_spawnattr_getsigmask","posix_spawnattr_init","posix_spawnattr_setarchpref_np","posix_spawnattr_setflags","posix_spawnattr_setpgroup","posix_spawnattr_setsigdefault","posix_spawnattr_setsigmask","posix_spawnattr_t","posix_spawnp","ppsfreq","pread","preadv","precision","preemptible","printf","priority","proc_bsdinfo","proc_kmsgbuf","proc_libversion","proc_listallpids","proc_listchildpids","proc_listpgrppids","proc_listpids","proc_name","proc_pid_rusage","proc_pidfdinfo","proc_pidfileportinfo","proc_pidinfo","proc_pidpath","proc_regionfilename","proc_set_csm","proc_set_no_smt","proc_setthread_csm","proc_setthread_no_smt","proc_taskallinfo","proc_taskinfo","proc_threadinfo","proc_vnodepathinfo","processor_basic_info","processor_basic_info_data_t","processor_basic_info_t","processor_count","processor_cpu_load_info","processor_cpu_load_info_data_t","processor_cpu_load_info_t","processor_flavor_t","processor_info_array_t","processor_info_t","processor_set_basic_info","processor_set_basic_info_data_t","processor_set_basic_info_t","processor_set_load_info","processor_set_load_info_data_t","processor_set_load_info_t","protoent","pselect","pseudo_AF_HDRCMPLT","pseudo_AF_KEY","pseudo_AF_PIP","pseudo_AF_RTIP","pseudo_AF_XTP","pth_cpu_usage","pth_cpu_usage","pth_curpri","pth_curpri","pth_flags","pth_flags","pth_maxpriority","pth_maxpriority","pth_name","pth_name","pth_policy","pth_policy","pth_priority","pth_priority","pth_run_state","pth_run_state","pth_sleep_time","pth_sleep_time","pth_system_time","pth_system_time","pth_user_time","pth_user_time","pthread_atfork","pthread_attr_destroy","pthread_attr_get_qos_class_np","pthread_attr_getschedparam","pthread_attr_init","pthread_attr_set_qos_class_np","pthread_attr_setdetachstate","pthread_attr_setschedparam","pthread_attr_setstacksize","pthread_attr_t","pthread_cancel","pthread_cond_broadcast","pthread_cond_destroy","pthread_cond_init","pthread_cond_signal","pthread_cond_t","pthread_cond_timedwait","pthread_cond_wait","pthread_condattr_destroy","pthread_condattr_getpshared","pthread_condattr_init","pthread_condattr_setpshared","pthread_condattr_t","pthread_cpu_number_np","pthread_create","pthread_create_from_mach_thread","pthread_detach","pthread_exit","pthread_from_mach_thread_np","pthread_get_qos_class_np","pthread_get_stackaddr_np","pthread_get_stacksize_np","pthread_getname_np","pthread_getschedparam","pthread_getspecific","pthread_introspection_getspecific_np","pthread_introspection_hook_install","pthread_introspection_hook_t","pthread_introspection_setspecific_np","pthread_jit_write_callback_t","pthread_jit_write_freeze_callbacks_np","pthread_jit_write_protect_np","pthread_jit_write_protect_supported_np","pthread_jit_write_with_callback_np","pthread_join","pthread_key_create","pthread_key_delete","pthread_key_t","pthread_kill","pthread_mach_thread_np","pthread_mutex_destroy","pthread_mutex_init","pthread_mutex_lock","pthread_mutex_t","pthread_mutex_trylock","pthread_mutex_unlock","pthread_mutexattr_destroy","pthread_mutexattr_getpshared","pthread_mutexattr_init","pthread_mutexattr_setpshared","pthread_mutexattr_settype","pthread_mutexattr_t","pthread_rwlock_destroy","pthread_rwlock_init","pthread_rwlock_rdlock","pthread_rwlock_t","pthread_rwlock_tryrdlock","pthread_rwlock_trywrlock","pthread_rwlock_unlock","pthread_rwlock_wrlock","pthread_rwlockattr_destroy","pthread_rwlockattr_getpshared","pthread_rwlockattr_init","pthread_rwlockattr_setpshared","pthread_rwlockattr_t","pthread_self","pthread_set_qos_class_self_np","pthread_setname_np","pthread_setschedparam","pthread_setspecific","pthread_sigmask","pthread_t","pthread_threadid_np","pti_cow_faults","pti_csw","pti_faults","pti_messages_received","pti_messages_sent","pti_numrunning","pti_pageins","pti_policy","pti_priority","pti_resident_size","pti_syscalls_mach","pti_syscalls_unix","pti_threadnum","pti_threads_system","pti_threads_user","pti_total_system","pti_total_user","pti_virtual_size","ptinfo","ptrace","ptrdiff_t","ptsname","purgeable_count","purgeable_count","purges","purges","putchar","putchar_unlocked","putenv","puts","pututxline","pvi_cdir","pvi_rdir","pw_change","pw_class","pw_dir","pw_expire","pw_gecos","pw_gid","pw_name","pw_passwd","pw_shell","pw_uid","pwrite","pwritev","qos_class_t","qsort","querylocale","quotactl","ra_count","ra_offset","radvisory","raise","rand","reactivations","reactivations","read","readdir","readdir_r","readlink","readlinkat","readv","realloc","realpath","recv","recvfrom","recvmsg","regcomp","regerror","regex_t","regexec","regfree","regmatch_t","regoff_t","release","remove","removexattr","rename","renameat","renameatx_np","renamex_np","res_init","reserved","reserved","resident_size","resident_size_max","revents","rewind","rewinddir","rfu_1","ri_billed_energy","ri_billed_system_time","ri_billed_system_time","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_elapsed_abstime","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_interrupt_wkups","ri_child_pageins","ri_child_pageins","ri_child_pageins","ri_child_pageins","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_pkg_idle_wkups","ri_child_system_time","ri_child_system_time","ri_child_system_time","ri_child_system_time","ri_child_user_time","ri_child_user_time","ri_child_user_time","ri_child_user_time","ri_cpu_time_qos_background","ri_cpu_time_qos_background","ri_cpu_time_qos_default","ri_cpu_time_qos_default","ri_cpu_time_qos_legacy","ri_cpu_time_qos_legacy","ri_cpu_time_qos_maintenance","ri_cpu_time_qos_maintenance","ri_cpu_time_qos_user_initiated","ri_cpu_time_qos_user_initiated","ri_cpu_time_qos_user_interactive","ri_cpu_time_qos_user_interactive","ri_cpu_time_qos_utility","ri_cpu_time_qos_utility","ri_cycles","ri_diskio_bytesread","ri_diskio_bytesread","ri_diskio_bytesread","ri_diskio_byteswritten","ri_diskio_byteswritten","ri_diskio_byteswritten","ri_instructions","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interrupt_wkups","ri_interval_max_phys_footprint","ri_lifetime_max_phys_footprint","ri_logical_writes","ri_pageins","ri_pageins","ri_pageins","ri_pageins","ri_pageins","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_phys_footprint","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_pkg_idle_wkups","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_exit_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_proc_start_abstime","ri_resident_size","ri_resident_size","ri_resident_size","ri_resident_size","ri_resident_size","ri_runnable_time","ri_serviced_energy","ri_serviced_system_time","ri_serviced_system_time","ri_system_time","ri_system_time","ri_system_time","ri_system_time","ri_system_time","ri_user_time","ri_user_time","ri_user_time","ri_user_time","ri_user_time","ri_uuid","ri_uuid","ri_uuid","ri_uuid","ri_uuid","ri_wired_size","ri_wired_size","ri_wired_size","ri_wired_size","ri_wired_size","rlim_cur","rlim_max","rlim_t","rlimit","rm_eo","rm_so","rmdir","ru_idrss","ru_inblock","ru_isrss","ru_ixrss","ru_majflt","ru_maxrss","ru_minflt","ru_msgrcv","ru_msgsnd","ru_nivcsw","ru_nsignals","ru_nswap","ru_nvcsw","ru_oublock","ru_stime","ru_utime","run_state","running","rusage","rusage_info_t","rusage_info_v0","rusage_info_v1","rusage_info_v2","rusage_info_v3","rusage_info_v4","s6_addr","s_addr","s_aliases","s_name","s_port","s_proto","sa_data","sa_endpoints_t","sa_family","sa_family_t","sa_flags","sa_len","sa_mask","sa_sigaction","sae_associd_t","sae_connid_t","sae_dstaddr","sae_dstaddrlen","sae_srcaddr","sae_srcaddrlen","sae_srcif","sbrk","sc_family","sc_id","sc_len","sc_reserved","sc_unit","scanf","sched_get_priority_max","sched_get_priority_min","sched_param","sched_priority","sched_yield","sdl_alen","sdl_data","sdl_family","sdl_index","sdl_len","sdl_nlen","sdl_slen","sdl_type","seconds","seed48","seekdir","segment_command","segment_command_64","segname","segname","select","sem_base","sem_close","sem_ctime","sem_flg","sem_nsems","sem_num","sem_op","sem_open","sem_otime","sem_pad1","sem_pad2","sem_pad3","sem_perm","sem_post","sem_t","sem_trywait","sem_unlink","sem_wait","sembuf","semctl","semget","semid_ds","semop","semun","send","sendfile","sendmsg","sendto","servent","setattrlist","setattrlistat","setbuf","setdomainname","setegid","setenv","seteuid","setgid","setgrent","setgroups","sethostid","sethostname","setitimer","setlocale","setlogmask","setpgid","setpriority","setprogname","setpwent","setregid","setreuid","setrlimit","setservent","setsid","setsockopt","settimeofday","setuid","setutxent","setvbuf","setxattr","sf_hdtr","shift","shm_atime","shm_cpid","shm_ctime","shm_dtime","shm_internal","shm_lpid","shm_nattch","shm_open","shm_perm","shm_segsz","shm_unlink","shmat","shmatt_t","shmctl","shmdt","shmget","shmid_ds","shutdown","si_addr","si_addr","si_code","si_errno","si_pid","si_pid","si_signo","si_status","si_status","si_uid","si_uid","si_value","sigaction","sigaction","sigaddset","sigaltstack","sigdelset","sigemptyset","sigev_notify","sigev_notify_attributes","sigev_signo","sigev_value","sigevent","sigfillset","sighandler_t","siginfo_t","sigismember","signal","sigpending","sigprocmask","sigset_t","sigval","sigwait","sin6_addr","sin6_family","sin6_flowinfo","sin6_len","sin6_port","sin6_scope_id","sin_addr","sin_addr","sin_family","sin_family","sin_len","sin_len","sin_other","sin_port","sin_port","sin_srcaddr","sin_tos","sin_zero","sival_ptr","size","size_allocated","size_in_use","size_t","sizeofcmds","sizeofcmds","sleep","sleep_time","slot_num","snd_family","snd_len","snd_name","snprintf","sockaddr","sockaddr_ctl","sockaddr_dl","sockaddr_in","sockaddr_in6","sockaddr_inarp","sockaddr_ndrv","sockaddr_storage","sockaddr_un","socket","socketpair","socklen_t","speculative_count","speculative_count","speed_t","sprintf","srand","srand48","ss_family","ss_flags","ss_len","ss_size","ss_sp","ss_sysaddr","sscanf","ssize_t","st_atime","st_atime_nsec","st_birthtime","st_birthtime_nsec","st_blksize","st_blocks","st_ctime","st_ctime_nsec","st_dev","st_flags","st_gen","st_gid","st_ino","st_lspare","st_mode","st_mtime","st_mtime_nsec","st_nlink","st_qspare","st_rdev","st_size","st_uid","stabil","stack_t","stat","stat","statfs","statfs","status","statvfs","statvfs","stbcnt","stpcpy","stpncpy","strcasecmp","strcasestr","strcat","strchr","strcmp","strcoll","strcpy","strcspn","strdup","strerror","strerror_r","strlen","strncasecmp","strncat","strncmp","strncpy","strndup","strnlen","strpbrk","strrchr","strsignal","strspn","strstr","strtod","strtof","strtok","strtok_r","strtol","strtonum","strtoul","strxfrm","sun_family","sun_len","sun_path","suseconds_t","suspend_count","suspend_count","swapins","swapouts","symlink","symlinkat","sync","syscall","sysconf","sysctl","sysctlbyname","sysctlnametomib","sysdir_get_next_search_path_enumeration","sysdir_search_path_directory_t","sysdir_search_path_domain_mask_t","sysdir_search_path_enumeration_state","sysdir_start_search_path_enumeration","syslog","sysname","system","system_time","system_time","system_time","tai","task_count","task_create","task_flavor_t","task_for_pid","task_info","task_info_t","task_inspect_t","task_set_info","task_t","task_terminate","task_thread_times_info","task_thread_times_info_data_t","task_thread_times_info_t","task_threads","tcdrain","tcflag_t","tcflow","tcflush","tcgetattr","tcgetpgrp","tcgetsid","tcsendbreak","tcsetattr","tcsetpgrp","telldir","termios","thousands_sep","thread_act_array_t","thread_act_t","thread_affinity_policy","thread_affinity_policy_data_t","thread_affinity_policy_t","thread_background_policy","thread_background_policy_data_t","thread_background_policy_t","thread_basic_info","thread_basic_info_data_t","thread_basic_info_t","thread_count","thread_extended_info","thread_extended_info_data_t","thread_extended_info_t","thread_extended_policy","thread_extended_policy_data_t","thread_extended_policy_t","thread_flavor_t","thread_handle","thread_id","thread_identifier_info","thread_identifier_info_data_t","thread_identifier_info_t","thread_info","thread_info_t","thread_inspect_t","thread_latency_qos_policy","thread_latency_qos_policy_data_t","thread_latency_qos_policy_t","thread_latency_qos_t","thread_latency_qos_tier","thread_policy_flavor_t","thread_policy_get","thread_policy_set","thread_policy_t","thread_precedence_policy","thread_precedence_policy_data_t","thread_precedence_policy_t","thread_standard_policy","thread_standard_policy_data_t","thread_standard_policy_t","thread_t","thread_throughput_qos_policy","thread_throughput_qos_policy_data_t","thread_throughput_qos_policy_t","thread_throughput_qos_t","thread_throughput_qos_tier","thread_time_constraint_policy","thread_time_constraint_policy_data_t","thread_time_constraint_policy_t","throttled_count","time","time","time_state","time_t","time_value_t","timegm","times","timeshare","timespec","timeval","timeval32","timex","timezone","tm","tm_gmtoff","tm_hour","tm_isdst","tm_mday","tm_min","tm_mon","tm_sec","tm_wday","tm_yday","tm_year","tm_zone","tmpfile","tmpnam","tms","tms_cstime","tms_cutime","tms_stime","tms_utime","tolerance","tolower","total_uncompressed_pages_in_compressor","toupper","trailers","trl_cnt","truncate","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","ttyname","ttyname_r","tv_nsec","tv_sec","tv_sec","tv_sec","tv_usec","tv_usec","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uc_link","uc_mcontext","uc_mcsize","uc_onstack","uc_sigmask","uc_stack","ucontext_t","udata","udata","uid","uid_t","uint16_t","uint32_t","uint64_t","uint8_t","uintmax_t","uintptr_t","umask","uname","ungetc","unlink","unlinkat","unlockpt","unmount","unsetenv","useconds_t","uselocale","user_time","user_time","user_time","usleep","ut_host","ut_id","ut_line","ut_pid","ut_tv","ut_type","ut_user","utimbuf","utime","utimensat","utimes","utmpx","utmpxname","utsname","uuid","uuid_t","val","valid","validattr","version","vi_fsid","vi_pad","vi_stat","vi_type","vinfo_stat","vip_path","vip_vi","virtual_size","vm_address_t","vm_deallocate","vm_inherit_t","vm_map_t","vm_offset_t","vm_page_size","vm_prot_t","vm_range_t","vm_size_t","vm_statistics","vm_statistics64","vm_statistics64_data_t","vm_statistics64_t","vm_statistics_data_t","vm_statistics_t","vmaddr","vmaddr","vmsize","vmsize","vnode_info","vnode_info_path","vol_attributes_attr_t","vol_capabilities_attr_t","vol_capabilities_set_t","volattr","volattr","vst_atime","vst_atimensec","vst_birthtime","vst_birthtimensec","vst_blksize","vst_blocks","vst_ctime","vst_ctimensec","vst_dev","vst_flags","vst_gen","vst_gid","vst_ino","vst_mode","vst_mtime","vst_mtimensec","vst_nlink","vst_qspare","vst_rdev","vst_size","vst_uid","wait","wait4","waitid","waitpid","wchar_t","wcslen","wcstombs","winsize","wire_count","wire_count","wmemchr","write","writev","ws_col","ws_row","ws_xpixel","ws_ypixel","xsu_avail","xsu_encrypted","xsu_pagesize","xsu_total","xsu_used","xsw_usage","xucred","zero_fill_count","zero_fill_count"],"q":["libc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","data includes security that replaces the TFO-cookie","data is idempotent","resume connect() on read/write","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Third Party Connect","Any distributed FS","IP6 Auth Header","any host internal protocol","any private encr. scheme","Argus","AX.25 Frames","BHA","Bulk Data Transfer","BackRoom SATNET Monitoring","CFTP","Chaos","Control Message Transport","Comp. Prot. HeartBeat","Comp. Prot. Net. Executive","Datagram Delivery","dissimilar gateway prot.","divert pseudo-protocol","last return value of *_input(), meaning “all job for …","IP6 destination option","exterior gateway protocol","EMCON","encapsulation header","ISO cnlp","IP6 Encap Sec. Payload","Ethernet IP encapsulation","IP6 fragmentation header","gateway2 (deprecated)","GMTP","General Routing Encap.","“hello” routing protocol","Host Monitoring","IP6 hop-by-hop options","","","xns idp","InterDomain Policy Routing","InterDomain Routing","group mgmt protocol","NSFNET-IGP","Cisco/GXS IGRP","IL transport protocol","Integ. Net Layer Security","Merit Internodal","","payload compression (IPComp)","Packet Core Utility","IP encapsulated in IP","for compatibility","Pluribus Packet Core","","Reliable Transaction","Kryptolan","Locus Address Resoloution","Leaf-1","Leaf-2","","DCN Measurement Subsystems","Mobile Host Routing","Mobile Int.ing control","Multicast Transport","Multiplexing","Sun net disk proto (temp.)","Next Hop Resolution","IP6 no next header","Network Services","network voice protocol","OSPFIGP","PGM","private interior gateway","Protocol Independent Mcast","Packet Radio Measurement","pup","Packet Video Protocol","raw IP packet","BBN RCC Monitoring","Reliable Data","IP6 routing header","resource reservation","Remote Virtual Disk","SATNET/Backroom EXPAK","Satnet Monitoring","Semaphore Comm. security","SCTP","Source Demand Routing","Sequential Exchange","Strite RPC protocol","Stream protocol II.","Secure VMTP","IP with encryption","TCF","","tp-4 w/ class negotiation","TP++ Transport","Trunk-1","Trunk-2","TTP","","Banyon VINES","VISA Protocol","VMTP","WIDEBAND EXPAK","WIDEBAND Monitoring","Wang Span Network","Cross Net Debugger","XTP","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","((sae_associd_t)(-1ULL))","","((sae_connid_t)(-1ULL))","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Enable/Disable TCP Fastopen on this socket","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","C __int128 (a GCC extension that’s part of many ABIs)","C __int128_t (alternate name for __int128)","","","","","","","C unsigned __int128 (a GCC extension that’s part of many …","C __uint128_t (alternate name for __uint128)","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Equivalent to C’s void type when used as a pointer.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Notes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The 64-bit libc on Solaris and illumos only has readdir_r. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,42,42,42,42,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,0,0,0,0,0,157,158,158,158,159,160,160,0,0,159,157,159,159,159,157,0,0,160,159,0,0,0,0,0,86,86,0,0,0,0,0,12,110,145,104,0,0,98,50,50,50,50,50,50,50,50,48,0,0,48,0,48,48,48,0,48,0,48,0,0,0,0,88,88,88,88,88,0,0,0,0,149,0,0,0,119,119,0,0,0,0,0,0,0,0,0,0,0,152,152,152,152,0,118,0,0,102,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,149,103,103,103,67,67,0,0,0,67,0,67,67,0,0,67,67,0,0,0,0,0,0,0,0,92,0,120,0,0,0,0,0,0,0,86,0,0,0,0,0,103,103,0,0,0,0,0,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,0,0,78,79,80,78,79,80,1,1,1,0,118,121,145,145,96,0,0,0,92,96,0,0,0,110,145,136,0,135,136,0,140,76,77,76,77,75,75,75,75,0,86,70,128,128,128,128,128,128,64,123,70,145,137,51,0,0,118,121,0,0,0,0,141,0,0,0,62,62,62,62,0,0,65,65,65,65,65,65,65,65,65,65,0,0,0,0,0,72,72,0,0,0,0,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,0,92,92,93,20,0,0,0,0,0,0,0,0,64,145,61,127,61,127,61,127,61,127,61,61,127,61,127,61,127,127,61,61,127,127,127,127,127,127,61,127,127,127,0,110,145,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,64,123,0,0,0,0,0,0,118,121,0,78,79,78,79,76,77,64,123,64,76,77,78,79,123,140,0,0,0,0,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,0,0,118,121,0,0,0,0,0,0,70,0,0,110,145,0,0,0,0,0,92,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,0,0,0,0,0,0,0,59,59,59,59,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,86,0,49,49,49,0,0,0,0,0,11,11,11,11,0,0,70,18,18,18,18,18,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,30,31,32,33,6,34,3,1,35,36,37,38,39,40,162,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,149,150,151,152,153,154,156,157,158,159,160,161,69,69,110,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,123,0,0,0,0,36,0,0,0,36,0,0,0,33,33,33,33,33,33,33,0,143,151,143,151,143,151,143,151,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,143,151,151,151,143,151,143,151,143,151,151,143,151,143,151,66,144,66,144,66,144,66,144,66,144,144,144,144,144,66,144,66,144,0,97,46,46,45,47,45,46,47,47,0,0,0,0,0,0,110,145,0,78,79,0,0,0,0,0,70,70,70,70,70,70,70,70,0,145,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,19,19,0,0,0,0,0,85,85,84,84,84,0,17,17,136,0,0,0,0,0,0,0,0,0,0,0,0,0,24,24,0,92,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,147,147,68,22,22,68,68,68,68,0,0,0,0,0,0,0,0,0,0,0,0,138,0,0,0,0,0,0,0,0,0,110,145,0,0,0,0,0,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,76,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,92,93,78,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,139,0,0,0,0,0,0,0,0,0,0,0,0,0,86,0,92,12,70,70,70,0,0,0,3,3,3,3,3,3,3,0,0,0,0,0,0,0,70,70,70,0,122,0,76,77,70,0,0,0,0,0,0,94,40,0,78,79,0,0,0,51,0,92,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,70,27,27,70,70,110,145,110,145,0,0,0,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,73,0,96,0,0,0,140,146,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,0,0,92,96,0,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,126,142,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,73,0,0,0,110,145,110,145,0,0,0,0,0,109,109,32,32,32,32,32,32,32,32,32,32,0,0,0,0,0,0,60,60,0,0,0,110,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,77,118,146,146,20,0,0,72,116,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,113,114,115,116,115,116,115,116,115,116,115,116,115,116,115,116,115,116,116,114,115,116,114,115,116,116,112,113,114,115,116,116,116,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,116,116,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,112,113,114,115,116,15,15,0,0,38,38,0,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,140,136,0,0,0,0,0,0,0,161,89,26,26,26,26,30,0,30,0,57,30,57,57,0,0,91,91,91,91,91,0,83,83,83,83,83,0,0,0,0,105,0,81,81,81,81,81,81,81,81,139,0,0,0,0,78,79,0,124,0,124,87,124,87,87,0,124,124,124,124,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,125,125,125,125,125,125,125,0,125,125,0,0,0,0,0,0,0,0,56,56,56,56,56,56,56,56,56,56,56,56,0,0,0,0,0,0,134,134,134,134,0,0,0,0,0,0,0,0,0,0,0,31,31,31,31,31,31,63,82,63,82,63,82,82,63,82,82,82,63,23,104,102,102,0,76,77,0,140,136,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,110,145,0,0,0,0,132,58,132,58,58,83,0,0,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,92,0,0,0,0,0,92,0,0,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,39,39,0,140,146,145,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,0,111,140,146,93,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,138,0,0,0,0,0,0,0,141,141,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,145,0,93,93,0,0,0,0,95,0,0,0,0,0,0,34,34,34,34,34,34,34,34,34,34,34,0,0,0,25,25,25,25,92,0,145,0,69,69,0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,0,0,14,13,14,150,13,150,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,6,34,3,1,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,165,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,156,156,156,156,156,156,0,64,123,86,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,111,140,146,0,133,133,133,133,133,133,133,0,0,0,0,0,0,0,117,0,149,120,122,40,107,107,107,107,0,108,108,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,79,78,79,0,0,0,0,0,118,121,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,0,0,0,0,0,0,0,0,110,145,0,0,0,21,21,21,21,74,74,74,74,74,0,0,110,145],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[3,1],[4,4],[[3,1],1],[4,4],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[5,6]],[[5,6],7],[[5,6]],0,[6],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[5,5],5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[8,9],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,7],0,[5,5],[5,7],[5,7],[5,7],[5,7],0,0,0,[5,5],[5,5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,5],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[6,6],[34,34],[3,3],[1,1],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[61,61],[62,62],[63,63],[64,64],[65,65],[66,66],[67,67],[68,68],[69,69],[70,70],[71,71],[72,72],[73,73],[74,74],[75,75],[76,76],[77,77],[78,78],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[85,85],[86,86],[87,87],[88,88],[89,89],[90,90],[91,91],[92,92],[93,93],[94,94],[95,95],[96,96],[97,97],[98,98],[99,99],[100,100],[101,101],[102,102],[103,103],[104,104],[105,105],[106,106],[107,107],[108,108],[109,109],[110,110],[111,111],[112,112],[113,113],[114,114],[115,115],[116,116],[117,117],[118,118],[119,119],[120,120],[121,121],[122,122],[123,123],[124,124],[125,125],[126,126],[127,127],[128,128],[129,129],[130,130],[131,131],[132,132],[133,133],[134,134],[135,135],[136,136],[137,137],[138,138],[139,139],[140,140],[141,141],[142,142],[143,143],[144,144],[145,145],[146,146],[147,147],[148,148],[149,149],[150,150],[151,151],[152,152],[153,153],[154,154],[155,155],[156,156],[157,157],[158,158],[159,159],[160,160],[161,161],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[11,11],7],[[12,12],7],[[13,13],7],[[14,14],7],[[15,15],7],[[16,16],7],[[17,17],7],[[18,18],7],[[19,19],7],[[20,20],7],[[21,21],7],[[22,22],7],[[23,23],7],[[24,24],7],[[25,25],7],[[26,26],7],[[27,27],7],[[30,30],7],[[31,31],7],[[32,32],7],[[33,33],7],[[6,6],7],[[34,34],7],[[3,3],7],[[1,1],7],[[35,35],7],[[36,36],7],[[37,37],7],[[38,38],7],[[39,39],7],[[40,40],7],[[162,162],7],[[45,45],7],[[46,46],7],[[47,47],7],[[48,48],7],[[49,49],7],[[50,50],7],[[51,51],7],[[52,52],7],[[53,53],7],[[54,54],7],[[55,55],7],[[56,56],7],[[57,57],7],[[58,58],7],[[59,59],7],[[60,60],7],[[61,61],7],[[62,62],7],[[63,63],7],[[64,64],7],[[65,65],7],[[66,66],7],[[67,67],7],[[68,68],7],[[69,69],7],[[70,70],7],[[71,71],7],[[72,72],7],[[73,73],7],[[74,74],7],[[75,75],7],[[76,76],7],[[77,77],7],[[78,78],7],[[79,79],7],[[80,80],7],[[81,81],7],[[82,82],7],[[83,83],7],[[84,84],7],[[85,85],7],[[86,86],7],[[87,87],7],[[88,88],7],[[89,89],7],[[90,90],7],[[91,91],7],[[92,92],7],[[93,93],7],[[94,94],7],[[95,95],7],[[96,96],7],[[97,97],7],[[98,98],7],[[99,99],7],[[100,100],7],[[101,101],7],[[102,102],7],[[103,103],7],[[104,104],7],[[105,105],7],[[106,106],7],[[107,107],7],[[108,108],7],[[109,109],7],[[110,110],7],[[111,111],7],[[112,112],7],[[113,113],7],[[114,114],7],[[115,115],7],[[116,116],7],[[117,117],7],[[118,118],7],[[119,119],7],[[120,120],7],[[121,121],7],[[122,122],7],[[123,123],7],[[124,124],7],[[125,125],7],[[126,126],7],[[127,127],7],[[128,128],7],[[129,129],7],[[130,130],7],[[131,131],7],[[132,132],7],[[133,133],7],[[134,134],7],[[135,135],7],[[136,136],7],[[137,137],7],[[138,138],7],[[139,139],7],[[140,140],7],[[141,141],7],[[142,142],7],[[143,143],7],[[144,144],7],[[145,145],7],[[146,146],7],[[147,147],7],[[149,149],7],[[150,150],7],[[151,151],7],[[152,152],7],[[153,153],7],[[154,154],7],[[156,156],7],[[157,157],7],[[158,158],7],[[159,159],7],[[160,160],7],[[161,161],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[10,163],164],[[11,163],164],[[12,163],164],[[13,163],164],[[14,163],164],[[15,163],164],[[16,163],164],[[17,163],164],[[18,163],164],[[19,163],164],[[20,163],164],[[21,163],164],[[22,163],164],[[23,163],164],[[24,163],164],[[25,163],164],[[26,163],164],[[27,163],164],[[28,163],164],[[29,163],164],[[30,163],164],[[31,163],164],[[32,163],164],[[33,163],164],[[6,163],164],[[34,163],164],[[3,163],164],[[1,163],164],[[35,163],164],[[36,163],164],[[37,163],164],[[38,163],164],[[39,163],164],[[40,163],164],[[162,163],164],[[41,163],164],[[42,163],164],[[43,163],164],[[44,163],164],[[45,163],164],[[46,163],164],[[47,163],164],[[48,163],164],[[49,163],164],[[50,163],164],[[51,163],164],[[52,163],164],[[53,163],164],[[54,163],164],[[55,163],164],[[56,163],164],[[57,163],164],[[58,163],164],[[59,163],164],[[60,163],164],[[61,163],164],[[62,163],164],[[63,163],164],[[64,163],164],[[65,163],164],[[66,163],164],[[67,163],164],[[68,163],164],[[69,163],164],[[70,163],164],[[71,163],164],[[72,163],164],[[73,163],164],[[74,163],164],[[75,163],164],[[76,163],164],[[77,163],164],[[78,163],164],[[79,163],164],[[80,163],164],[[81,163],164],[[82,163],164],[[83,163],164],[[84,163],164],[[85,163],164],[[86,163],164],[[87,163],164],[[88,163],164],[[89,163],164],[[90,163],164],[[91,163],164],[[92,163],164],[[93,163],164],[[94,163],164],[[95,163],164],[[96,163],164],[[97,163],164],[[98,163],164],[[99,163],164],[[100,163],164],[[101,163],164],[[102,163],164],[[103,163],164],[[104,163],164],[[105,163],164],[[106,163],164],[[107,163],164],[[108,163],164],[[109,163],164],[[110,163],164],[[111,163],164],[[112,163],164],[[113,163],164],[[114,163],164],[[115,163],164],[[116,163],164],[[117,163],164],[[118,163],164],[[119,163],164],[[120,163],164],[[121,163],164],[[122,163],164],[[123,163],164],[[124,163],164],[[125,163],164],[[126,163],164],[[127,163],164],[[128,163],164],[[129,163],164],[[130,163],164],[[131,163],164],[[132,163],164],[[133,163],164],[[165,163],[[167,[166]]]],[[134,163],164],[[135,163],164],[[136,163],164],[[137,163],164],[[138,163],164],[[139,163],164],[[140,163],164],[[141,163],164],[[142,163],164],[[143,163],164],[[144,163],164],[[145,163],164],[[146,163],164],[[147,163],164],[[149,163],164],[[150,163],164],[[151,163],164],[[152,163],164],[[153,163],164],[[154,163],164],[[156,163],164],[[157,163],164],[[158,163],164],[[159,163],164],[[160,163],164],[[161,163],164],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[30],[31],[32],[33],[6],[34],[3],[1],[35],[36],[37],[38],[39],[40],[162],[45],[46],[47],[48],[49],[50],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60],[61],[62],[63],[64],[65],[66],[67],[68],[69],[70],[71],[72],[73],[74],[75],[76],[77],[78],[79],[80],[81],[82],[83],[84],[85],[86],[87],[88],[89],[90],[91],[92],[93],[94],[95],[96],[97],[98],[99],[100],[101],[102],[103],[104],[105],[106],[107],[108],[109],[110],[111],[112],[113],[114],[115],[116],[117],[118],[119],[120],[121],[122],[123],[124],[125],[126],[127],[128],[129],[130],[131],[132],[133],[134],[135],[136],[137],[138],[139],[140],[141],[142],[143],[144],[145],[146],[147],[149],[150],[151],[152],[153],[154],[156],[157],[158],[159],[160],[161],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],168],0,0,0,0,0,0,0,0,0,0,0,0,0,[169,170],[[170,170],169],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[169,170],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[56,165],0,0,0,[56,171],0,0,[56,5],0,[56,172],0,[56,23],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],[[],167],0,0,0,0,0,0,0,0,[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],[[],173],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"p":[[3,"cmsghdr"],[6,"c_uchar"],[3,"msghdr"],[6,"c_uint"],[6,"c_int"],[3,"fd_set"],[15,"bool"],[15,"u8"],[15,"u32"],[4,"DIR"],[3,"group"],[3,"utimbuf"],[3,"timeval"],[3,"timespec"],[3,"rlimit"],[3,"rusage"],[3,"ipv6_mreq"],[3,"hostent"],[3,"iovec"],[3,"pollfd"],[3,"winsize"],[3,"linger"],[3,"sigval"],[3,"itimerval"],[3,"tms"],[3,"servent"],[3,"protoent"],[4,"FILE"],[4,"fpos_t"],[3,"sockaddr"],[3,"sockaddr_in6"],[3,"passwd"],[3,"ifaddrs"],[3,"tm"],[3,"fsid_t"],[3,"if_nameindex"],[3,"regex_t"],[3,"regmatch_t"],[3,"sockaddr_un"],[3,"utsname"],[4,"timezone"],[4,"qos_class_t"],[4,"sysdir_search_path_directory_t"],[4,"sysdir_search_path_domain_mask_t"],[3,"ip_mreq"],[3,"ip_mreqn"],[3,"ip_mreq_source"],[3,"aiocb"],[3,"glob_t"],[3,"addrinfo"],[3,"mach_timebase_info"],[3,"stat"],[3,"pthread_mutexattr_t"],[3,"pthread_condattr_t"],[3,"pthread_rwlockattr_t"],[3,"siginfo_t"],[3,"sigaction"],[3,"stack_t"],[3,"fstore_t"],[3,"radvisory"],[3,"statvfs"],[3,"Dl_info"],[3,"sockaddr_in"],[3,"kevent64_s"],[3,"dqblk"],[3,"if_msghdr"],[3,"termios"],[3,"flock"],[3,"sf_hdtr"],[3,"lconv"],[3,"proc_taskinfo"],[3,"proc_bsdinfo"],[3,"proc_taskallinfo"],[3,"xsw_usage"],[3,"xucred"],[3,"mach_header"],[3,"mach_header_64"],[3,"segment_command"],[3,"segment_command_64"],[3,"load_command"],[3,"sockaddr_dl"],[3,"sockaddr_inarp"],[3,"sockaddr_ctl"],[3,"in_pktinfo"],[3,"in6_pktinfo"],[3,"ipc_perm"],[3,"sembuf"],[3,"arphdr"],[3,"in_addr"],[3,"sockaddr_ndrv"],[3,"sa_endpoints_t"],[3,"timex"],[3,"ntptimeval"],[3,"thread_standard_policy"],[3,"thread_extended_policy"],[3,"thread_time_constraint_policy"],[3,"thread_precedence_policy"],[3,"thread_affinity_policy"],[3,"thread_background_policy"],[3,"thread_latency_qos_policy"],[3,"thread_throughput_qos_policy"],[3,"malloc_statistics_t"],[3,"mstats"],[3,"vm_range_t"],[3,"sched_param"],[3,"vinfo_stat"],[3,"vnode_info"],[3,"vnode_info_path"],[3,"proc_vnodepathinfo"],[3,"vm_statistics"],[3,"task_thread_times_info"],[3,"rusage_info_v0"],[3,"rusage_info_v1"],[3,"rusage_info_v2"],[3,"rusage_info_v3"],[3,"rusage_info_v4"],[3,"image_offset"],[3,"attrlist"],[3,"attrreference_t"],[3,"vol_capabilities_attr_t"],[3,"attribute_set_t"],[3,"vol_attributes_attr_t"],[3,"kevent"],[3,"semid_ds"],[3,"shmid_ds"],[3,"proc_threadinfo"],[3,"statfs"],[3,"dirent"],[3,"pthread_rwlock_t"],[3,"pthread_mutex_t"],[3,"pthread_cond_t"],[3,"sockaddr_storage"],[3,"utmpx"],[3,"sigevent"],[3,"processor_cpu_load_info"],[3,"processor_basic_info"],[3,"processor_set_basic_info"],[3,"processor_set_load_info"],[3,"time_value_t"],[3,"thread_basic_info"],[3,"thread_identifier_info"],[3,"thread_extended_info"],[3,"if_data64"],[3,"if_msghdr2"],[3,"vm_statistics64"],[3,"mach_task_basic_info"],[3,"log2phys"],[3,"os_unfair_lock_s"],[19,"semun"],[3,"timeval32"],[3,"if_data"],[3,"bpf_hdr"],[3,"pthread_attr_t"],[3,"malloc_zone_t"],[3,"max_align_t"],[3,"ucontext_t"],[3,"__darwin_mcontext64"],[3,"__darwin_arm_exception_state64"],[3,"__darwin_arm_thread_state64"],[3,"__darwin_arm_neon_state64"],[3,"in6_addr"],[6,"os_unfair_lock"],[3,"Formatter"],[6,"Result"],[4,"c_void"],[3,"Error"],[4,"Result"],[6,"mach_port_t"],[6,"dev_t"],[15,"i32"],[6,"pid_t"],[6,"uid_t"],[3,"TypeId"]],"a":{"__errno_location":[2353],"errno":[2353]}},\ -"lock_api":{"doc":"This library provides type-safe and fully-featured Mutex …","t":[16,16,8,16,16,3,3,18,18,18,18,16,16,3,3,3,3,3,3,8,8,8,3,8,8,8,8,8,8,8,8,8,8,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,10,10,10,11,11,11,10,10,10,10,10,10,10,11,11,11,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,10,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,10,10,10,11,11,11,11,11,11,11,11,11,11,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,10,11,11],"n":["Duration","Duration","GetThreadId","GuardMarker","GuardMarker","GuardNoSend","GuardSend","INIT","INIT","INIT","INIT","Instant","Instant","MappedMutexGuard","MappedReentrantMutexGuard","MappedRwLockReadGuard","MappedRwLockWriteGuard","Mutex","MutexGuard","RawMutex","RawMutexFair","RawMutexTimed","RawReentrantMutex","RawRwLock","RawRwLockDowngrade","RawRwLockFair","RawRwLockRecursive","RawRwLockRecursiveTimed","RawRwLockTimed","RawRwLockUpgrade","RawRwLockUpgradeDowngrade","RawRwLockUpgradeFair","RawRwLockUpgradeTimed","ReentrantMutex","ReentrantMutexGuard","RwLock","RwLockReadGuard","RwLockUpgradableReadGuard","RwLockWriteGuard","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bump","bump","bump","bump","bump","bump","bump","bump","bump_exclusive","bump_exclusive","bump_shared","bump_shared","bump_upgradable","bump_upgradable","const_new","const_new","const_new","data_ptr","data_ptr","data_ptr","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","downgrade","downgrade","downgrade","downgrade_to_upgradable","downgrade_to_upgradable","downgrade_upgradable","drop","drop","drop","drop","drop","drop","drop","drop","drop","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","force_unlock","force_unlock","force_unlock_fair","force_unlock_fair","force_unlock_read","force_unlock_read_fair","force_unlock_write","force_unlock_write_fair","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_mut","get_mut","get_mut","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked","is_locked_exclusive","is_locked_exclusive","is_locked_exclusive","is_owned_by_current_thread","is_owned_by_current_thread","leak","lock","lock","lock","lock","lock_exclusive","lock_shared","lock_shared_recursive","lock_upgradable","map","map","map","map","map","map","map","map","mutex","new","new","new","nonzero_thread_id","raw","raw","raw","read","read_recursive","remutex","rwlock","rwlock","rwlock","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock","try_lock","try_lock","try_lock_exclusive","try_lock_exclusive_for","try_lock_exclusive_until","try_lock_for","try_lock_for","try_lock_for","try_lock_for","try_lock_shared","try_lock_shared_for","try_lock_shared_recursive","try_lock_shared_recursive_for","try_lock_shared_recursive_until","try_lock_shared_until","try_lock_until","try_lock_until","try_lock_until","try_lock_until","try_lock_upgradable","try_lock_upgradable_for","try_lock_upgradable_until","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_read","try_read_for","try_read_recursive","try_read_recursive_for","try_read_recursive_until","try_read_until","try_upgradable_read","try_upgradable_read_for","try_upgradable_read_until","try_upgrade","try_upgrade","try_upgrade_for","try_upgrade_for","try_upgrade_until","try_upgrade_until","try_write","try_write_for","try_write_until","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unlock","unlock","unlock_exclusive","unlock_exclusive_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_fair","unlock_shared","unlock_shared_fair","unlock_upgradable","unlock_upgradable_fair","unlocked","unlocked","unlocked","unlocked","unlocked","unlocked_fair","unlocked_fair","unlocked_fair","unlocked_fair","unlocked_fair","upgradable_read","upgrade","upgrade","write"],"q":["lock_api","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Duration type used for try_lock_for.","Duration type used for try_lock_for.","Helper trait which returns a non-zero thread ID.","Marker type which determines whether a lock guard should …","Marker type which determines whether a lock guard should …","Marker type which indicates that the Guard type for a lock …","Marker type which indicates that the Guard type for a lock …","Initial value for an unlocked mutex.","Initial value.","Initial value for an unlocked mutex.","Initial value for an unlocked RwLock.","Instant type used for try_lock_until.","Instant type used for try_lock_until.","An RAII mutex guard returned by MutexGuard::map, which can …","An RAII mutex guard returned by ReentrantMutexGuard::map, …","An RAII read lock guard returned by RwLockReadGuard::map, …","An RAII write lock guard returned by RwLockWriteGuard::map…","A mutual exclusion primitive useful for protecting shared …","An RAII implementation of a “scoped lock” of a mutex. …","Basic operations for a mutex.","Additional methods for mutexes which support fair …","Additional methods for mutexes which support locking with …","A raw mutex type that wraps another raw mutex to provide …","Basic operations for a reader-writer lock.","Additional methods for RwLocks which support atomically …","Additional methods for RwLocks which support fair …","Additional methods for RwLocks which support recursive …","Additional methods for RwLocks which support recursive …","Additional methods for RwLocks which support locking with …","Additional methods for RwLocks which support atomically …","Additional methods for RwLocks which support upgradable …","Additional methods for RwLocks which support upgradable …","Additional methods for RwLocks which support upgradable …","A mutex which can be recursively locked by a single thread.","An RAII implementation of a “scoped lock” of a …","A reader-writer lock","RAII structure used to release the shared read access of a …","RAII structure used to release the upgradable read access …","RAII structure used to release the exclusive write access …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the mutex to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields the RwLock to a waiting thread if there …","Temporarily yields an exclusive lock to a waiting thread …","Temporarily yields an exclusive lock to a waiting thread …","Temporarily yields a shared lock to a waiting thread if …","Temporarily yields a shared lock to a waiting thread if …","Temporarily yields an upgradable lock to a waiting thread …","Temporarily yields an upgradable lock to a waiting thread …","Creates a new mutex based on a pre-existing raw mutex.","Creates a new reentrant mutex based on a pre-existing raw …","Creates a new new instance of an RwLock<T> based on a …","Returns a raw pointer to the underlying data.","Returns a raw pointer to the underlying data.","Returns a raw pointer to the underlying data.","","","","","","","","","","","","","","","","","Atomically downgrades an exclusive lock into a shared lock …","Atomically downgrades a write lock into a read lock …","Atomically downgrades an upgradable read lock lock into a …","Downgrades an exclusive lock to an upgradable lock.","Atomically downgrades a write lock into an upgradable read …","Downgrades an upgradable lock to a shared lock.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Forcibly unlocks the mutex.","Forcibly unlocks the mutex.","Forcibly unlocks the mutex using a fair unlock procotol.","Forcibly unlocks the mutex using a fair unlock protocol.","Forcibly unlocks a read lock.","Forcibly unlocks a read lock using a fair unlock procotol.","Forcibly unlocks a write lock.","Forcibly unlocks a write lock using a fair unlock procotol.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a mutable reference to the underlying data.","Returns a mutable reference to the underlying data.","Returns a mutable reference to the underlying data.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes this mutex, returning the underlying data.","Consumes this mutex, returning the underlying data.","Consumes this RwLock, returning the underlying data.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks whether the mutex is currently locked.","Checks if this RwLock is currently locked in any way.","Checks if this RwLock is currently locked in any way.","Checks whether this RwLock is currently locked in any way.","Check if this RwLock is currently exclusively locked.","Check if this RwLock is currently exclusively locked.","Check if this RwLock is currently exclusively locked.","Checks whether the mutex is currently held by the current …","Checks whether the mutex is currently held by the current …","Leaks the mutex guard and returns a mutable reference to …","Acquires this mutex, blocking the current thread until it …","Acquires a mutex, blocking the current thread until it is …","Acquires this mutex, blocking if it’s held by another …","Acquires a reentrant mutex, blocking the current thread …","Acquires an exclusive lock, blocking the current thread …","Acquires a shared lock, blocking the current thread until …","Acquires a shared lock without deadlocking in case of a …","Acquires an upgradable lock, blocking the current thread …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedReentrantMutexGuard for a component of …","Makes a new MappedReentrantMutexGuard for a component of …","Make a new MappedRwLockReadGuard for a component of the …","Make a new MappedRwLockWriteGuard for a component of the …","Make a new MappedRwLockReadGuard for a component of the …","Make a new MappedRwLockWriteGuard for a component of the …","Returns a reference to the original Mutex object.","Creates a new mutex in an unlocked state ready for use.","Creates a new reentrant mutex in an unlocked state ready …","Creates a new instance of an RwLock<T> which is unlocked.","Returns a non-zero thread ID which identifies the current …","Returns the underlying raw mutex object.","Returns the underlying raw mutex object.","Returns the underlying raw reader-writer lock object.","Locks this RwLock with shared read access, blocking the …","Locks this RwLock with shared read access, blocking the …","Returns a reference to the original ReentrantMutex object.","Returns a reference to the original reader-writer lock …","Returns a reference to the original reader-writer lock …","Returns a reference to the original reader-writer lock …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attempts to acquire this mutex without blocking. Returns …","Attempts to acquire this lock.","Attempts to acquire this mutex without blocking. Returns …","Attempts to acquire this lock.","Attempts to acquire an exclusive lock without blocking.","Attempts to acquire an exclusive lock until a timeout is …","Attempts to acquire an exclusive lock until a timeout is …","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire a shared lock without blocking.","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock without deadlocking in …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire a shared lock until a timeout is …","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire this lock until a timeout is reached.","Attempts to acquire an upgradable lock without blocking.","Attempts to acquire an upgradable lock until a timeout is …","Attempts to acquire an upgradable lock until a timeout is …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedReentrantMutexGuard for a …","Attempts to make a new MappedReentrantMutexGuard for a …","Attempts to make a new MappedRwLockReadGuard for a …","Attempts to make a new MappedRwLockWriteGuard for a …","Attempts to make a new MappedRwLockReadGuard for a …","Attempts to make a new MappedRwLockWriteGuard for a …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with shared read access …","Attempts to acquire this RwLock with upgradable read …","Attempts to acquire this RwLock with upgradable read …","Attempts to acquire this RwLock with upgradable read …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to upgrade an upgradable lock to an exclusive …","Tries to atomically upgrade an upgradable read lock into a …","Attempts to lock this RwLock with exclusive write access.","Attempts to acquire this RwLock with exclusive write …","Attempts to acquire this RwLock with exclusive write …","","","","","","","","","","","","","","","","Unlocks this mutex.","Unlocks this mutex. The inner mutex may not be unlocked if …","Releases an exclusive lock.","Releases an exclusive lock using a fair unlock protocol.","Unlocks this mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks this mutex using a fair unlock protocol. The inner …","Unlocks the mutex using a fair unlock protocol.","Unlocks the mutex using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Unlocks the RwLock using a fair unlock protocol.","Releases a shared lock.","Releases a shared lock using a fair unlock protocol.","Releases an upgradable lock.","Releases an upgradable lock using a fair unlock protocol.","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the mutex to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Temporarily unlocks the RwLock to execute the given …","Locks this RwLock with upgradable read access, blocking …","Upgrades an upgradable lock to an exclusive lock.","Atomically upgrades an upgradable read lock lock into a …","Locks this RwLock with exclusive write access, blocking …"],"i":[35,36,0,15,16,0,0,15,4,5,16,35,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,1,1,3,5,6,8,9,11,7,7,7,7,10,10,12,13,14,12,13,14,12,13,14,3,18,6,19,8,9,11,21,22,3,18,9,22,23,9,11,24,9,24,3,18,6,19,8,9,11,21,22,12,3,3,18,18,13,6,6,19,19,14,8,8,9,9,11,11,21,21,22,22,12,13,12,13,14,14,14,14,40,41,12,12,12,3,18,5,13,13,13,6,19,14,14,14,8,9,11,21,22,12,13,14,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,12,13,14,15,15,12,5,13,16,16,14,16,16,14,5,13,3,15,12,5,13,16,16,32,20,3,18,6,19,8,9,21,22,3,12,13,14,4,12,13,14,14,14,6,8,9,11,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,15,12,5,13,16,36,36,35,12,5,13,16,36,32,37,37,36,35,12,5,13,20,38,38,3,18,6,19,8,9,21,22,14,14,14,14,14,14,14,14,14,20,11,38,11,38,11,14,14,14,40,41,12,3,18,5,13,6,19,14,8,9,11,21,22,15,5,16,7,1,3,18,5,6,19,8,9,11,21,22,16,7,20,10,3,6,8,9,11,3,6,8,9,11,14,20,11,14],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[3,[1,2]]]],[[[5,[1,4]]]],[[[6,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[],12],[[],13],[[],14],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[],[[12,[15,[0,[2,17]]]]]],[[],[[13,[15,4,[0,[2,17]]]]]],[[],[[14,[16,[0,[2,17]]]]]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[6,[15,4,2]]]],[[[19,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[21,[16,2]]]],[[[22,[16,2]]]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[9,[16,2]]]],[[[22,[16,2]]]],[[]],[[[9,[23,2]]],[[8,[23,2]]]],[[[11,[24,2]]],[[8,[24,2]]]],[[]],[[[9,[24,2]]],[[11,[24,2]]]],[[]],[[[3,[15,2]]]],[[[18,[15,2]]]],[[[6,[15,4,2]]]],[[[19,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[21,[16,2]]]],[[[22,[16,2]]]],[[[12,[15,[0,[2,25]]]],26],27],[[[3,[15,[0,[25,2]]]],26],27],[[[3,[15,[0,[28,2]]]],26],27],[[[18,[15,[0,[28,2]]]],26],27],[[[18,[15,[0,[25,2]]]],26],27],[[[13,[15,4,[0,[2,25]]]],26],27],[[[6,[15,4,[0,[28,2]]]],26],27],[[[6,[15,4,[0,[25,2]]]],26],27],[[[19,[15,4,[0,[28,2]]]],26],27],[[[19,[15,4,[0,[25,2]]]],26],27],[[[14,[16,[0,[2,25]]]],26],27],[[[8,[16,[0,[25,2]]]],26],27],[[[8,[16,[0,[28,2]]]],26],27],[[[9,[16,[0,[28,2]]]],26],27],[[[9,[16,[0,[25,2]]]],26],27],[[[11,[20,[0,[25,2]]]],26],27],[[[11,[20,[0,[28,2]]]],26],27],[[[21,[16,[0,[25,2]]]],26],27],[[[21,[16,[0,[28,2]]]],26],27],[[[22,[16,[0,[28,2]]]],26],27],[[[22,[16,[0,[25,2]]]],26],27],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[12,[1,2]]]],[[[13,[1,4,2]]]],[[[14,[16,2]]]],[[[14,[7,2]]]],[[[14,[16,2]]]],[[[14,[7,2]]]],[[]],[[]],[[]],[29],[[],[[12,[15]]]],[[]],[[]],[[]],[[],[[13,[15,4]]]],[29],[[]],[[]],[[]],[29],[[],[[14,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[12,[15]]]],[[[13,[15,4]]]],[[[14,[16]]]],[[],30],[[],30],[[[12,[15,2]]],30],[[[5,[15,4]]],30],[[[13,[15,4,2]]],30],[[],30],[[],30],[[[14,[16,2]]],30],[[],30],[[],30],[[[14,[16,2]]],30],[[[5,[15,4]]],30],[[[13,[15,4,2]]],30],[[[3,[15,2]]]],[[]],[[[12,[15,2]]],[[3,[15,2]]]],[[[5,[15,4]]]],[[[13,[15,4,2]]],[[6,[15,4,2]]]],[[]],[[]],[[]],[[]],[[[3,[15,2]]],[[18,[15,2]]]],[[[18,[15,2]]],[[18,[15,2]]]],[[[6,[15,4,2]]],[[19,[15,4,2]]]],[[[19,[15,4,2]]],[[19,[15,4,2]]]],[[[8,[16,2]]],[[21,[16,2]]]],[[[9,[16,2]]],[[22,[16,2]]]],[[[21,[16,2]]],[[21,[16,2]]]],[[[22,[16,2]]],[[22,[16,2]]]],[[[3,[15,2]]],12],[[],[[12,[15]]]],[[],[[13,[15,4]]]],[[],[[14,[16]]]],[[],31],[[[12,[15,2]]]],[[[13,[15,4,2]]]],[[[14,[16,2]]]],[[[14,[16,2]]],[[8,[16,2]]]],[[[14,[32,2]]],[[8,[32,2]]]],[[[6,[15,4,2]]],13],[[[8,[16,2]]],14],[[[9,[16,2]]],14],[[[11,[20,2]]],14],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],33],[[],30],[[[12,[15,2]]],[[34,[[3,[15,2]]]]]],[[[5,[15,4]]],30],[[[13,[15,4,2]]],[[34,[[6,[15,4,2]]]]]],[[],30],[[],30],[[],30],[[],30],[[[12,[35,2]]],[[34,[[3,[35,2]]]]]],[[[5,[35,4]]],30],[[[13,[35,4,2]]],[[34,[[6,[35,4,2]]]]]],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[],30],[[[12,[35,2]]],[[34,[[3,[35,2]]]]]],[[[5,[35,4]]],30],[[[13,[35,4,2]]],[[34,[[6,[35,4,2]]]]]],[[],30],[[],30],[[],30],[[[3,[15,2]]],[[33,[[18,[15,2]],[3,[15,2]]]]]],[[[18,[15,2]]],[[33,[[18,[15,2]],[18,[15,2]]]]]],[[[6,[15,4,2]]],[[33,[[19,[15,4,2]],[6,[15,4,2]]]]]],[[[19,[15,4,2]]],[[33,[[19,[15,4,2]],[19,[15,4,2]]]]]],[[[8,[16,2]]],[[33,[[21,[16,2]],[8,[16,2]]]]]],[[[9,[16,2]]],[[33,[[22,[16,2]],[9,[16,2]]]]]],[[[21,[16,2]]],[[33,[[21,[16,2]],[21,[16,2]]]]]],[[[22,[16,2]]],[[33,[[22,[16,2]],[22,[16,2]]]]]],[[[14,[16,2]]],[[34,[[8,[16,2]]]]]],[[[14,[36,2]]],[[34,[[8,[36,2]]]]]],[[[14,[32,2]]],[[34,[[8,[32,2]]]]]],[[[14,[37,2]]],[[34,[[8,[37,2]]]]]],[[[14,[37,2]]],[[34,[[8,[37,2]]]]]],[[[14,[36,2]]],[[34,[[8,[36,2]]]]]],[[[14,[20,2]]],[[34,[[11,[20,2]]]]]],[[[14,[38,2]]],[[34,[[11,[38,2]]]]]],[[[14,[38,2]]],[[34,[[11,[38,2]]]]]],[[],30],[[[11,[20,2]]],[[33,[[9,[20,2]],[11,[20,2]]]]]],[[],30],[[[11,[38,2]]],[[33,[[9,[38,2]],[11,[38,2]]]]]],[[],30],[[[11,[38,2]]],[[33,[[9,[38,2]],[11,[38,2]]]]]],[[[14,[16,2]]],[[34,[[9,[16,2]]]]]],[[[14,[36,2]]],[[34,[[9,[36,2]]]]]],[[[14,[36,2]]],[[34,[[9,[36,2]]]]]],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[],39],[[]],[[[5,[15,4]]]],[[]],[[]],[[]],[[[3,[1,2]]]],[[[18,[1,2]]]],[[[5,[1,4]]]],[[[6,[1,4,2]]]],[[[19,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[[21,[7,2]]]],[[[22,[7,2]]]],[[]],[[]],[[]],[[]],[[[3,[15,2]]]],[[[6,[15,4,2]]]],[[[8,[16,2]]]],[[[9,[16,2]]]],[[[11,[20,2]]]],[[[3,[1,2]]]],[[[6,[1,4,2]]]],[[[8,[7,2]]]],[[[9,[7,2]]]],[[[11,[10,2]]]],[[[14,[20,2]]],[[11,[20,2]]]],[[]],[[[11,[20,2]]],[[9,[20,2]]]],[[[14,[16,2]]],[[9,[16,2]]]]],"p":[[8,"RawMutexFair"],[8,"Sized"],[3,"MutexGuard"],[8,"GetThreadId"],[3,"RawReentrantMutex"],[3,"ReentrantMutexGuard"],[8,"RawRwLockFair"],[3,"RwLockReadGuard"],[3,"RwLockWriteGuard"],[8,"RawRwLockUpgradeFair"],[3,"RwLockUpgradableReadGuard"],[3,"Mutex"],[3,"ReentrantMutex"],[3,"RwLock"],[8,"RawMutex"],[8,"RawRwLock"],[8,"Default"],[3,"MappedMutexGuard"],[3,"MappedReentrantMutexGuard"],[8,"RawRwLockUpgrade"],[3,"MappedRwLockReadGuard"],[3,"MappedRwLockWriteGuard"],[8,"RawRwLockDowngrade"],[8,"RawRwLockUpgradeDowngrade"],[8,"Debug"],[3,"Formatter"],[6,"Result"],[8,"Display"],[15,"never"],[15,"bool"],[3,"NonZeroUsize"],[8,"RawRwLockRecursive"],[4,"Result"],[4,"Option"],[8,"RawMutexTimed"],[8,"RawRwLockTimed"],[8,"RawRwLockRecursiveTimed"],[8,"RawRwLockUpgradeTimed"],[3,"TypeId"],[3,"GuardSend"],[3,"GuardNoSend"]]},\ -"lru":{"doc":"An implementation of a LRU cache. The cache supports get, …","t":[6,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["DefaultHasher","IntoIter","Iter","IterMut","LruCache","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cap","clear","clone","clone_into","contains","count","count","count","demote","drop","fmt","from","from","from","from","get","get_mut","get_or_insert","get_or_insert_mut","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","len","new","next","next","next","next_back","next_back","peek","peek_lru","peek_mut","pop","pop_entry","pop_lru","promote","push","put","resize","size_hint","size_hint","size_hint","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unbounded","unbounded_with_hasher","with_hasher"],"q":["lru","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","An iterator that moves out of a LruCache.","An iterator over the entries of a LruCache.","An iterator over mutables entries of a LruCache.","An LRU Cache","","","","","","","","","Returns the maximum number of key-value pairs the cache …","Clears the contents of the cache.","","","Returns a bool indicating whether the given key is in the …","","","","Marks the key as the least recently used one.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns a reference to the value of the key in the cache …","Returns a mutable reference to the value of the key in the …","Returns a reference to the value of the key in the cache …","Returns a mutable reference to the value of the key in the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","Returns a bool indicating whether the cache is empty or …","An iterator visiting all entries in most-recently used …","An iterator visiting all entries in most-recently-used …","Returns the number of key-value pairs that are currently …","Creates a new LRU Cache that holds at most cap items.","","","","","","Returns a reference to the value corresponding to the key …","Returns the value corresponding to the least recently used …","Returns a mutable reference to the value corresponding to …","Removes and returns the value corresponding to the key …","Removes and returns the key and the value corresponding to …","Removes and returns the key and value corresponding to the …","Marks the key as the most recently used one.","Pushes a key-value pair into the cache. If an entry with …","Puts a key-value pair into cache. If the key already …","Resizes the cache. If the new capacity is smaller than the …","","","","","","","","","","","","","","","","","Creates a new LRU Cache that never automatically evicts …","Creates a new LRU Cache that never automatically evicts …","Creates a new LRU Cache that holds at most cap items and …"],"i":[0,0,0,0,0,4,6,9,10,4,6,9,10,4,4,6,6,4,6,9,10,4,4,4,4,6,9,10,4,4,4,4,4,6,9,10,4,4,4,6,9,10,4,4,4,4,4,6,9,10,6,9,4,4,4,4,4,4,4,4,4,4,6,9,10,6,4,6,9,10,4,6,9,10,4,6,9,10,4,4,4],"f":[0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],5],[[[4,[[0,[1,2]],3]]]],[6,6],[[]],[[[4,[[0,[1,2]],3]]],7],[6,8],[9,8],[10,8],[[[4,[[0,[1,2]],3]]]],[4],[[[4,[[0,[1,2]]]],11],12],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]],[0,[1,2]]]],[[[4,[[0,[1,2]],3]],[0,[1,2]]]],[[]],[[]],[[]],[[]],[[[4,[[0,[1,2]]]]],[[10,[[0,[1,2]]]]]],[4,[[6,[[0,[1,2]]]]]],[4,[[9,[[0,[1,2]]]]]],[[]],[[]],[[]],[[[4,[[0,[1,2]],3]]],7],[[[4,[[0,[1,2]],3]]],[[6,[[0,[1,2]]]]]],[[[4,[[0,[1,2]],3]]],[[9,[[0,[1,2]]]]]],[[[4,[[0,[1,2]],3]]],8],[5,[[4,[[0,[1,2]]]]]],[6,13],[9,13],[10,13],[6,13],[9,13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]],13],[[[4,[[0,[1,2]],3]]]],[[[4,[[0,[1,2]],3]],[0,[1,2]]],13],[[[4,[[0,[1,2]],3]],[0,[1,2]]],13],[[[4,[[0,[1,2]],3]],5]],[6],[9],[10],[[]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],15],[[],15],[[],15],[[],15],[[],[[4,[[0,[1,2]]]]]],[3,[[4,[[0,[1,2]],3]]]],[[5,3],[[4,[[0,[1,2]],3]]]]],"p":[[8,"Hash"],[8,"Eq"],[8,"BuildHasher"],[3,"LruCache"],[3,"NonZeroUsize"],[3,"Iter"],[15,"bool"],[15,"usize"],[3,"IterMut"],[3,"IntoIter"],[3,"Formatter"],[6,"Result"],[4,"Option"],[4,"Result"],[3,"TypeId"]]},\ -"memchr":{"doc":"This library provides heavily optimized routines for …","t":[3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,0,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,3,3,3,3,3,13,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Memchr","Memchr2","Memchr3","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","from","from","from","into","into","into","into_iter","into_iter","into_iter","memchr","memchr2","memchr2_iter","memchr3","memchr3_iter","memchr_iter","memmem","memrchr","memrchr2","memrchr2_iter","memrchr3","memrchr3_iter","memrchr_iter","new","new","new","next","next","next","next_back","next_back","next_back","size_hint","size_hint","size_hint","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","Auto","FindIter","FindRevIter","Finder","FinderBuilder","FinderRev","None","Prefilter","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build_forward","build_reverse","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","default","default","find","find","find_iter","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","into","into","into","into","into","into","into_iter","into_iter","into_owned","into_owned","into_owned","into_owned","needle","needle","new","new","new","next","next","prefilter","rfind","rfind","rfind_iter","rfind_iter","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id"],"q":["memchr","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","memchr::memmem","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["An iterator for memchr.","An iterator for memchr2.","An iterator for memchr3.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Search for the first occurrence of a byte in a slice.","Like memchr, but searches for either of two bytes instead …","An iterator over all occurrences of the needles in a …","Like memchr, but searches for any of three bytes instead …","An iterator over all occurrences of the needles in a …","An iterator over all occurrences of the needle in a …","This module provides forward and reverse substring search …","Search for the last occurrence of a byte in a slice.","Like memrchr, but searches for either of two bytes instead …","An iterator over all occurrences of the needles in a …","Like memrchr, but searches for any of three bytes instead …","An iterator over all occurrences of the needles in a …","An iterator over all occurrences of the needle in a …","Creates a new iterator that yields all positions of needle …","Creates a new iterator that yields all positions of needle …","Create a new Memchr3 that’s initialized to zero with a …","","","","","","","","","","","","","","","","","","","Automatically detect whether a heuristic prefilter should …","An iterator over non-overlapping substring matches.","An iterator over non-overlapping substring matches in …","A single substring searcher fixed to a particular needle.","A builder for constructing non-default forward or reverse …","A single substring reverse searcher fixed to a particular …","Never used a prefilter in substring search.","Prefilter controls whether heuristics are used to …","Convert this finder into its borrowed variant.","Convert this finder into its borrowed variant.","","","","","","","","","","","","","Build a forward finder using the given needle from the …","Build a reverse finder using the given needle from the …","","","","","","","","","","","Returns the index of the first occurrence of the given …","Returns the index of the first occurrence of this needle …","Returns an iterator over all non-overlapping occurrences …","Returns an iterator over all occurrences of a substring in …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Convert this iterator into its owned variant, such that it …","Convert this iterator into its owned variant, such that it …","Convert this finder into its owned variant, such that it …","Convert this finder into its owned variant, such that it …","Returns the needle that this finder searches for.","Returns the needle that this finder searches for.","Create a new finder for the given needle.","Create a new reverse finder for the given needle.","Create a new finder builder with default settings.","","","Configure the prefilter setting for the finder.","Returns the index of the last occurrence of the given …","Returns the index of the last occurrence of this needle in …","Returns a reverse iterator over all non-overlapping …","Returns a reverse iterator over all occurrences of a …","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,0,0,0,0,0,0,0,0,0,0,0,0,0,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,6,4,5,13,0,0,0,0,0,13,0,10,11,13,14,17,10,11,12,13,14,17,10,11,12,12,12,13,10,11,12,13,10,11,12,13,12,0,10,0,10,13,14,17,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12,14,17,14,17,10,11,10,11,10,11,12,14,17,12,0,11,0,11,13,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12,13,14,17,10,11,12],"f":[0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,[[3,[2]]]],[[1,1],[[3,[2]]]],[[1,1],4],[[1,1,1],[[3,[2]]]],[[1,1,1],5],[1,6],0,[1,[[3,[2]]]],[[1,1],[[3,[2]]]],[[1,1],[[7,[4]]]],[[1,1,1],[[3,[2]]]],[[1,1,1],[[7,[5]]]],[1,[[7,[6]]]],[1,6],[[1,1],4],[[1,1,1],5],[6,[[3,[2]]]],[4,[[3,[2]]]],[5,[[3,[2]]]],[6,3],[4,3],[5,3],[6],[4],[5],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],9],[[],9],[[],9],0,0,0,0,0,0,0,0,[10,10],[11,11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[12,10],[12,11],[13,13],[10,10],[11,11],[12,12],[[]],[[]],[[]],[[]],[[],13],[[],12],[[],[[3,[2]]]],[10,[[3,[2]]]],[[],14],[10,14],[[13,15],16],[[14,15],16],[[17,15],16],[[10,15],16],[[11,15],16],[[12,15],16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[14,14],[17,17],[10,10],[11,11],[10],[11],[[],10],[[],11],[[],12],[14,[[3,[2]]]],[17,[[3,[2]]]],[[12,13],12],[[],[[3,[2]]]],[[11,18],[[3,[2]]]],[[],17],[11,17],[[]],[[]],[[]],[[]],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9]],"p":[[15,"u8"],[15,"usize"],[4,"Option"],[3,"Memchr2"],[3,"Memchr3"],[3,"Memchr"],[3,"Rev"],[4,"Result"],[3,"TypeId"],[3,"Finder"],[3,"FinderRev"],[3,"FinderBuilder"],[4,"Prefilter"],[3,"FindIter"],[3,"Formatter"],[6,"Result"],[3,"FindRevIter"],[8,"AsRef"]]},\ -"memoffset":{"doc":"A crate used for calculating offsets of struct members and …","t":[14,14,14,14,14,14,14],"n":["offset_of","offset_of_tuple","offset_of_union","raw_field","raw_field_tuple","raw_field_union","span_of"],"q":["memoffset","","","","","",""],"d":["Calculates the offset of the specified field from the …","Calculates the offset of the specified field from the …","Calculates the offset of the specified union member from …","Computes a const raw pointer to the given field of the …","Computes a const raw pointer to the given field of the …","Computes a const raw pointer to the given field of the …","Produces a range instance representing the sub-slice …"],"i":[0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0],"p":[]},\ -"nix":{"doc":"Rust friendly bindings to the various *nix system …","t":[6,8,6,14,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,10,0,10,2,0,0,0,0,0,14,14,14,14,0,0,0,0,0,10,13,13,3,13,3,13,13,3,3,13,13,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,13,13,4,8,13,11,11,11,11,11,11,11,5,11,11,11,5,11,11,11,11,11,10,11,11,11,11,11,11,18,18,18,18,18,3,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,13,13,13,13,13,3,4,3,4,13,13,13,13,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,13,13,13,13,13,13,4,18,18,18,3,18,18,18,18,3,3,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,12,12,12,12,12,12,12,12,5,3,3,12,11,11,11,11,12,11,11,12,11,11,11,12,11,11,11,11,5,11,11,12,11,11,11,12,11,11,11,11,11,11,11,11,3,18,18,3,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,3,3,3,18,18,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,5,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,5,11,11,11,11,11,5,11,11,11,11,11,11,11,11,12,12,5,5,5,5,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,12,12,12,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,6,3,3,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,13,4,13,3,4,13,3,3,13,13,4,13,13,16,5,10,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,13,13,13,3,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,14,14,14,14,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,3,3,4,3,18,18,18,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,18,18,18,18,18,3,18,18,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,6,5,5,6,4,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,4,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,5,11,11,11,11,11,11,11,5,11,11,11,11,5,5,11,5,11,5,5,5,5,11,11,11,5,5,5,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,5,5,3,13,13,13,13,18,18,18,18,18,18,18,18,18,18,4,4,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,4,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,17,13,13,13,4,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,17,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,11,11,11,11,11,11,11,11,5,5,13,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,17,13,13,17,13,13,13,13,13,13,13,13,13,13,13,13,17,13,13,13,13,13,13,13,13,13,13,3,3,13,13,3,4,13,3,3,13,4,13,13,4,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,5,5,11,11,11,5,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,11,11,12,12,12,12,12,12,12,18,18,17,17,3,2,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,2,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,4,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,3,4,4,13,13,13,13,8,13,13,13,13,13,4,3,4,3,3,13,13,13,13,3,3,13,13,13,13,13,13,13,13,13,13,3,13,18,18,18,18,18,18,18,18,18,18,13,3,3,3,13,13,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,3,13,13,13,13,18,18,18,18,18,18,18,18,13,13,13,13,13,13,13,13,13,8,4,13,4,3,4,4,3,3,8,19,13,13,3,3,13,13,13,13,13,13,13,3,3,13,13,13,13,13,16,16,13,13,3,13,13,13,5,5,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,12,12,12,12,12,12,12,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,12,12,6,11,11,5,5,5,5,10,11,11,11,5,5,12,12,12,12,12,12,12,12,12,11,11,11,3,3,3,3,5,3,5,5,0,12,11,11,11,11,11,11,12,12,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,4,3,5,13,13,3,13,13,3,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5,6,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,5,5,12,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,3,17,17,17,17,17,17,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,6,5,11,11,11,11,5,11,11,11,11,11,11,11,11,3,18,18,18,18,18,18,18,18,18,18,18,18,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,18,4,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,18,18,18,18,18,18,18,18,18,4,4,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,3,3,17,18,18,18,18,18,18,18,18,18,18,18,18,3,18,18,18,18,4,4,18,18,18,18,18,13,13,13,13,13,13,13,13,13,13,18,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,18,18,18,13,13,18,17,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,10,11,11,10,11,11,11,10,11,11,10,11,11,11,11,10,11,11,11,11,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,4,13,13,13,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,13,13,13,13,13,4,4,13,13,13,18,18,18,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,3,3,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,5,5,5,5,5,5,5,11,11,11,11,11,11,11,11,5,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,13,13,13,4,13,13,13,13,13,13,13,13,18,18,18,18,18,18,3,4,18,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,18,18,18,18,18,18,18,18,18,18,18,3,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,3,13,13,13,13,13,13,13,13,13,13,13,18,4,13,4,13,13,3,3,13,13,13,13,13,4,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,4,3,13,17,13,18,13,3,3,13,13,13,13,13,13,13,13,13,13,13,13,4,13,13,13,3,4,3,18,4,18,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,5,0,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,5,11,12,5,5,5,5,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,11,5,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,12,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,12,12,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,12,5,5,5,5,12,12,11,11,11,11,12,12,5,5,5,5,5,5,12,12,11,12,12,11,5,5,5,5,5,5,5,5,5,5,5,5,12,5,11,11,5,11,5,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,5,5,5,12,5,5,5,5],"n":["Error","NixPath","Result","cmsg_space","dir","env","errno","fcntl","features","ifaddrs","ioctl_none","ioctl_none_bad","ioctl_read","ioctl_read_bad","ioctl_read_buf","ioctl_readwrite","ioctl_readwrite_bad","ioctl_readwrite_buf","ioctl_write_buf","ioctl_write_int","ioctl_write_int_bad","ioctl_write_ptr","ioctl_write_ptr_bad","is_empty","kmod","len","libc","mount","mqueue","net","poll","pty","request_code_none","request_code_read","request_code_readwrite","request_code_write","sched","sys","time","ucontext","unistd","with_nix_path","BlockDevice","CharacterDevice","Dir","Directory","Entry","Fifo","File","Iter","OwningIter","Socket","Symlink","Type","as_raw_fd","as_raw_fd","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","drop","drop","eq","eq","eq","eq","eq","file_name","file_type","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_fd","hash","hash","hash","hash","hash","ino","into","into","into","into","into","into_iter","into_iter","into_iter","iter","next","next","open","openat","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","ClearEnvError","borrow","borrow_mut","clearenv","clone","clone_into","fmt","fmt","from","into","provide","to_owned","to_string","try_from","try_into","type_id","E2BIG","EACCES","EADDRINUSE","EADDRNOTAVAIL","EADV","EAFNOSUPPORT","EAGAIN","EALREADY","EBADE","EBADF","EBADFD","EBADMSG","EBADR","EBADRQC","EBADSLT","EBFONT","EBUSY","ECANCELED","ECHILD","ECHRNG","ECOMM","ECONNABORTED","ECONNREFUSED","ECONNRESET","EDEADLK","EDEADLOCK","EDESTADDRREQ","EDOM","EDOTDOT","EDQUOT","EEXIST","EFAULT","EFBIG","EHOSTDOWN","EHOSTUNREACH","EHWPOISON","EIDRM","EILSEQ","EINPROGRESS","EINTR","EINVAL","EIO","EISCONN","EISDIR","EISNAM","EKEYEXPIRED","EKEYREJECTED","EKEYREVOKED","EL2HLT","EL2NSYNC","EL3HLT","EL3RST","ELIBACC","ELIBBAD","ELIBEXEC","ELIBMAX","ELIBSCN","ELNRNG","ELOOP","EMEDIUMTYPE","EMFILE","EMLINK","EMSGSIZE","EMULTIHOP","ENAMETOOLONG","ENAVAIL","ENETDOWN","ENETRESET","ENETUNREACH","ENFILE","ENOANO","ENOBUFS","ENOCSI","ENODATA","ENODEV","ENOENT","ENOEXEC","ENOKEY","ENOLCK","ENOLINK","ENOMEDIUM","ENOMEM","ENOMSG","ENONET","ENOPKG","ENOPROTOOPT","ENOSPC","ENOSR","ENOSTR","ENOSYS","ENOTBLK","ENOTCONN","ENOTDIR","ENOTEMPTY","ENOTNAM","ENOTRECOVERABLE","ENOTSOCK","ENOTSUP","ENOTTY","ENOTUNIQ","ENXIO","EOPNOTSUPP","EOVERFLOW","EOWNERDEAD","EPERM","EPFNOSUPPORT","EPIPE","EPROTO","EPROTONOSUPPORT","EPROTOTYPE","ERANGE","EREMCHG","EREMOTE","EREMOTEIO","ERESTART","ERFKILL","EROFS","ESHUTDOWN","ESOCKTNOSUPPORT","ESPIPE","ESRCH","ESRMNT","ESTALE","ESTRPIPE","ETIME","ETIMEDOUT","ETOOMANYREFS","ETXTBSY","EUCLEAN","EUNATCH","EUSERS","EWOULDBLOCK","EXDEV","EXFULL","Errno","ErrnoSentinel","UnknownErrno","borrow","borrow_mut","clear","clone","clone_into","desc","eq","errno","fmt","fmt","from","from_i32","from_i32","into","last","provide","result","sentinel","to_owned","to_string","try_from","try_from","try_into","type_id","AT_EMPTY_PATH","AT_NO_AUTOMOUNT","AT_REMOVEDIR","AT_SYMLINK_FOLLOW","AT_SYMLINK_NOFOLLOW","AtFlags","FALLOC_FL_COLLAPSE_RANGE","FALLOC_FL_INSERT_RANGE","FALLOC_FL_KEEP_SIZE","FALLOC_FL_PUNCH_HOLE","FALLOC_FL_UNSHARE_RANGE","FALLOC_FL_ZERO_RANGE","FD_CLOEXEC","F_ADD_SEALS","F_DUPFD","F_DUPFD_CLOEXEC","F_GETFD","F_GETFL","F_GETLK","F_GETPIPE_SZ","F_GET_SEALS","F_OFD_GETLK","F_OFD_SETLK","F_OFD_SETLKW","F_SEAL_GROW","F_SEAL_SEAL","F_SEAL_SHRINK","F_SEAL_WRITE","F_SETFD","F_SETFL","F_SETLK","F_SETLKW","F_SETPIPE_SZ","FallocateFlags","FcntlArg","FdFlag","FlockArg","LockExclusive","LockExclusiveNonblock","LockShared","LockSharedNonblock","OFlag","O_ACCMODE","O_APPEND","O_ASYNC","O_CLOEXEC","O_CREAT","O_DIRECT","O_DIRECTORY","O_DSYNC","O_EXCL","O_FSYNC","O_LARGEFILE","O_NDELAY","O_NOATIME","O_NOCTTY","O_NOFOLLOW","O_NONBLOCK","O_PATH","O_RDONLY","O_RDWR","O_RSYNC","O_SYNC","O_TMPFILE","O_TRUNC","O_WRONLY","POSIX_FADV_DONTNEED","POSIX_FADV_NOREUSE","POSIX_FADV_NORMAL","POSIX_FADV_RANDOM","POSIX_FADV_SEQUENTIAL","POSIX_FADV_WILLNEED","PosixFadviseAdvice","RENAME_EXCHANGE","RENAME_NOREPLACE","RENAME_WHITEOUT","RenameFlags","SPLICE_F_GIFT","SPLICE_F_MORE","SPLICE_F_MOVE","SPLICE_F_NONBLOCK","SealFlag","SpliceFFlags","Unlock","UnlockNonblock","all","all","all","all","all","all","all","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","complement","complement","complement","contains","contains","contains","contains","contains","contains","contains","copy_file_range","difference","difference","difference","difference","difference","difference","difference","empty","empty","empty","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","extend","extend","extend","fallocate","fcntl","flock","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","insert","insert","insert","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","is_empty","is_empty","is_empty","not","not","not","not","not","not","not","open","openat","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","posix_fadvise","posix_fallocate","readlink","readlinkat","remove","remove","remove","remove","remove","remove","remove","renameat","renameat2","set","set","set","set","set","set","set","splice","sub","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","tee","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","union","union","union","vmsplice","0","0","0","0","0","0","0","0","0","0","0","0","socket_atomic_cloexec","InterfaceAddress","InterfaceAddressIterator","address","borrow","borrow","borrow_mut","borrow_mut","broadcast","clone","clone_into","destination","drop","eq","eq","flags","fmt","fmt","from","from","getifaddrs","hash","hash","interface_name","into","into","into_iter","netmask","next","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","DeleteModuleFlags","MODULE_INIT_IGNORE_MODVERSIONS","MODULE_INIT_IGNORE_VERMAGIC","ModuleInitFlags","O_NONBLOCK","O_TRUNC","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","delete_module","difference","difference","empty","empty","eq","eq","extend","extend","finit_module","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","init_module","insert","insert","intersection","intersection","intersects","intersects","into","into","is_all","is_all","is_empty","is_empty","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","toggle","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","union","MNT_DETACH","MNT_EXPIRE","MNT_FORCE","MS_ACTIVE","MS_BIND","MS_DIRSYNC","MS_I_VERSION","MS_KERNMOUNT","MS_LAZYTIME","MS_MANDLOCK","MS_MGC_MSK","MS_MGC_VAL","MS_MOVE","MS_NOATIME","MS_NODEV","MS_NODIRATIME","MS_NOEXEC","MS_NOSUID","MS_NOUSER","MS_POSIXACL","MS_PRIVATE","MS_RDONLY","MS_REC","MS_RELATIME","MS_REMOUNT","MS_RMT_MASK","MS_SHARED","MS_SILENT","MS_SLAVE","MS_STRICTATIME","MS_SYNCHRONOUS","MS_UNBINDABLE","MntFlags","MsFlags","UMOUNT_NOFOLLOW","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","difference","difference","empty","empty","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","is_all","is_all","is_empty","is_empty","mount","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","toggle","toggle","try_from","try_from","try_into","try_into","type_id","type_id","umount","umount2","union","union","MQ_OFlag","MqAttr","MqdT","O_CLOEXEC","O_CREAT","O_EXCL","O_NONBLOCK","O_RDONLY","O_RDWR","O_WRONLY","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","curmsgs","difference","empty","eq","eq","extend","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","into","is_all","is_empty","maxmsg","mq_attr_member_t","mq_close","mq_getattr","mq_open","mq_receive","mq_remove_nonblock","mq_send","mq_set_nonblock","mq_setattr","mq_unlink","msgsize","new","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","if_","IFF_ALLMULTI","IFF_AUTOMEDIA","IFF_BROADCAST","IFF_DEBUG","IFF_DORMANT","IFF_DYNAMIC","IFF_ECHO","IFF_LOOPBACK","IFF_LOWER_UP","IFF_MASTER","IFF_MULTICAST","IFF_NOARP","IFF_NOTRAILERS","IFF_NO_PI","IFF_POINTOPOINT","IFF_PORTSEL","IFF_PROMISC","IFF_RUNNING","IFF_SLAVE","IFF_TAP","IFF_TUN","IFF_UP","Interface","InterfaceFlags","Interfaces","InterfacesIter","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","cmp","complement","contains","difference","drop","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","if_nameindex","if_nametoindex","index","insert","intersection","intersects","into","into","into","into","into_iter","into_iter","is_all","is_empty","iter","name","next","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_slice","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","POLLERR","POLLHUP","POLLIN","POLLNVAL","POLLOUT","POLLPRI","POLLRDBAND","POLLRDNORM","POLLWRBAND","POLLWRNORM","PollFd","PollFlags","all","all","any","as_raw_fd","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","difference","empty","eq","eq","events","extend","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","new","not","partial_cmp","poll","ppoll","remove","revents","set","set_events","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","ForkptyResult","OpenptyResult","PtyMaster","SessionId","Winsize","as_raw_fd","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","drop","eq","eq","eq","flush","flush","fmt","fmt","fmt","fmt","fork_result","forkpty","from","from","from","from","from","grantpt","hash","hash","hash","into","into","into","into","into_raw_fd","master","master","openpty","posix_openpt","ptsname","ptsname_r","read","read","slave","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unlockpt","write","write","ws_col","ws_row","ws_xpixel","ws_ypixel","CLONE_DETACHED","CLONE_FILES","CLONE_FS","CLONE_IO","CLONE_NEWCGROUP","CLONE_NEWIPC","CLONE_NEWNET","CLONE_NEWNS","CLONE_NEWPID","CLONE_NEWUSER","CLONE_NEWUTS","CLONE_PARENT","CLONE_PTRACE","CLONE_SIGHAND","CLONE_SYSVSEM","CLONE_THREAD","CLONE_UNTRACED","CLONE_VFORK","CLONE_VM","CloneCb","CloneFlags","CpuSet","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","cmp","complement","contains","count","default","difference","empty","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","is_set","new","not","partial_cmp","remove","sched_getaffinity","sched_getcpu","sched_setaffinity","sched_yield","set","set","setns","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","unset","unshare","aio","epoll","eventfd","inotify","ioctl","memfd","mman","personality","pthread","ptrace","quota","reboot","resource","select","sendfile","signal","signalfd","socket","stat","statfs","statvfs","sysinfo","termios","time","timer","timerfd","uio","utsname","wait","Aio","AioAllDone","AioCancelStat","AioCanceled","AioFsync","AioFsyncMode","AioNotCanceled","AioRead","AioWrite","LIO_NOWAIT","LIO_WAIT","LioMode","O_DSYNC","O_SYNC","Output","aio_cancel_all","aio_return","aio_return","aio_return","aio_return","aio_suspend","as_mut","as_mut","as_ref","as_ref","as_ref","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cancel","cancel","cancel","cancel","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","eq","eq","eq","error","error","error","error","fd","fd","fd","fd","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hash","hash","hash","in_progress","in_progress","in_progress","in_progress","into","into","into","into","into","into","lio_listio","mode","nbytes","nbytes","new","new","new","offset","offset","partial_cmp","partial_cmp","priority","priority","priority","priority","set_sigev_notify","set_sigev_notify","set_sigev_notify","set_sigev_notify","sigevent","sigevent","sigevent","sigevent","submit","submit","submit","submit","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","EPOLLERR","EPOLLET","EPOLLEXCLUSIVE","EPOLLHUP","EPOLLIN","EPOLLMSG","EPOLLONESHOT","EPOLLOUT","EPOLLPRI","EPOLLRDBAND","EPOLLRDHUP","EPOLLRDNORM","EPOLLWAKEUP","EPOLLWRBAND","EPOLLWRNORM","EPOLL_CLOEXEC","EpollCreateFlags","EpollCtlAdd","EpollCtlDel","EpollCtlMod","EpollEvent","EpollFlags","EpollOp","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","data","difference","difference","empty","empty","empty","epoll_create","epoll_create1","epoll_ctl","epoll_wait","eq","eq","eq","eq","events","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","hash","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","is_all","is_all","is_empty","is_empty","new","not","not","partial_cmp","partial_cmp","remove","remove","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","union","EFD_CLOEXEC","EFD_NONBLOCK","EFD_SEMAPHORE","EfdFlags","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","eventfd","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","insert","intersection","intersects","into","is_all","is_empty","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","AddWatchFlags","IN_ACCESS","IN_ALL_EVENTS","IN_ATTRIB","IN_CLOEXEC","IN_CLOSE","IN_CLOSE_NOWRITE","IN_CLOSE_WRITE","IN_CREATE","IN_DELETE","IN_DELETE_SELF","IN_DONT_FOLLOW","IN_IGNORED","IN_ISDIR","IN_MODIFY","IN_MOVE","IN_MOVED_FROM","IN_MOVED_TO","IN_MOVE_SELF","IN_NONBLOCK","IN_ONESHOT","IN_ONLYDIR","IN_OPEN","IN_Q_OVERFLOW","IN_UNMOUNT","InitFlags","Inotify","InotifyEvent","WatchDescriptor","add_watch","all","all","as_raw_fd","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","complement","contains","contains","cookie","difference","difference","empty","empty","eq","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_raw_fd","hash","hash","hash","init","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","mask","name","not","not","partial_cmp","partial_cmp","partial_cmp","read_events","remove","remove","rm_watch","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","union","union","wd","request_code_none","request_code_read","request_code_readwrite","request_code_write","MFD_ALLOW_SEALING","MFD_CLOEXEC","MemFdCreateFlag","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","insert","intersection","intersects","into","is_all","is_empty","memfd_create","not","partial_cmp","remove","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","MADV_DODUMP","MADV_DOFORK","MADV_DONTDUMP","MADV_DONTFORK","MADV_DONTNEED","MADV_FREE","MADV_HUGEPAGE","MADV_HWPOISON","MADV_MERGEABLE","MADV_NOHUGEPAGE","MADV_NORMAL","MADV_RANDOM","MADV_REMOVE","MADV_SEQUENTIAL","MADV_SOFT_OFFLINE","MADV_UNMERGEABLE","MADV_WILLNEED","MAP_32BIT","MAP_ANON","MAP_ANONYMOUS","MAP_DENYWRITE","MAP_EXECUTABLE","MAP_FILE","MAP_FIXED","MAP_FIXED_NOREPLACE","MAP_GROWSDOWN","MAP_HUGETLB","MAP_HUGE_16GB","MAP_HUGE_16MB","MAP_HUGE_1GB","MAP_HUGE_1MB","MAP_HUGE_256MB","MAP_HUGE_2GB","MAP_HUGE_2MB","MAP_HUGE_32MB","MAP_HUGE_512KB","MAP_HUGE_512MB","MAP_HUGE_64KB","MAP_HUGE_8MB","MAP_LOCKED","MAP_NONBLOCK","MAP_NORESERVE","MAP_POPULATE","MAP_PRIVATE","MAP_SHARED","MAP_STACK","MCL_CURRENT","MCL_FUTURE","MREMAP_FIXED","MREMAP_MAYMOVE","MRemapFlags","MS_ASYNC","MS_INVALIDATE","MS_SYNC","MapFlags","MlockAllFlags","MmapAdvise","MsFlags","PROT_EXEC","PROT_GROWSDOWN","PROT_GROWSUP","PROT_NONE","PROT_READ","PROT_WRITE","ProtFlags","all","all","all","all","all","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","complement","contains","contains","contains","contains","contains","difference","difference","difference","difference","difference","empty","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","insert","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","intersects","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","is_empty","madvise","mlock","mlockall","mmap","mprotect","mremap","msync","munlock","munlockall","munmap","not","not","not","not","not","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","remove","remove","remove","remove","remove","set","set","set","set","set","shm_open","shm_unlink","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","union","ADDR_COMPAT_LAYOUT","ADDR_LIMIT_32BIT","ADDR_LIMIT_3GB","ADDR_NO_RANDOMIZE","FDPIC_FUNCPTRS","MMAP_PAGE_ZERO","Persona","READ_IMPLIES_EXEC","SHORT_INODE","STICKY_TIMEOUTS","UNAME26","WHOLE_SECONDS","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow_mut","clone","clone_into","cmp","complement","contains","difference","empty","eq","extend","fmt","fmt","fmt","fmt","fmt","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","get","hash","insert","intersection","intersects","into","is_all","is_empty","not","partial_cmp","remove","set","set","sub","sub_assign","symmetric_difference","to_owned","toggle","try_from","try_into","type_id","union","Pthread","pthread_kill","pthread_self","AddressType","Event","Options","PTRACE_ATTACH","PTRACE_CONT","PTRACE_DETACH","PTRACE_EVENT_CLONE","PTRACE_EVENT_EXEC","PTRACE_EVENT_EXIT","PTRACE_EVENT_FORK","PTRACE_EVENT_SECCOMP","PTRACE_EVENT_STOP","PTRACE_EVENT_VFORK","PTRACE_EVENT_VFORK_DONE","PTRACE_GETEVENTMSG","PTRACE_GETFPREGS","PTRACE_GETFPXREGS","PTRACE_GETREGS","PTRACE_GETREGSET","PTRACE_GETSIGINFO","PTRACE_INTERRUPT","PTRACE_KILL","PTRACE_LISTEN","PTRACE_O_EXITKILL","PTRACE_O_TRACECLONE","PTRACE_O_TRACEEXEC","PTRACE_O_TRACEEXIT","PTRACE_O_TRACEFORK","PTRACE_O_TRACESECCOMP","PTRACE_O_TRACESYSGOOD","PTRACE_O_TRACEVFORK","PTRACE_O_TRACEVFORKDONE","PTRACE_PEEKDATA","PTRACE_PEEKSIGINFO","PTRACE_PEEKTEXT","PTRACE_PEEKUSER","PTRACE_POKEDATA","PTRACE_POKETEXT","PTRACE_POKEUSER","PTRACE_SEIZE","PTRACE_SETFPREGS","PTRACE_SETFPXREGS","PTRACE_SETOPTIONS","PTRACE_SETREGS","PTRACE_SETREGSET","PTRACE_SETSIGINFO","PTRACE_SINGLESTEP","PTRACE_SYSCALL","PTRACE_SYSEMU","PTRACE_SYSEMU_SINGLESTEP","PTRACE_TRACEME","Request","all","attach","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","cont","contains","detach","difference","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","getevent","getregs","getsiginfo","hash","hash","hash","insert","interrupt","intersection","intersects","into","into","into","is_all","is_empty","kill","not","partial_cmp","partial_cmp","partial_cmp","read","read_user","remove","seize","set","setoptions","setregs","setsiginfo","step","sub","sub_assign","symmetric_difference","syscall","sysemu","sysemu_step","to_owned","to_owned","to_owned","toggle","traceme","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","write","write_user","Dqblk","GRPQUOTA","QFMT_VFS_OLD","QFMT_VFS_V0","QFMT_VFS_V1","QIF_ALL","QIF_BLIMITS","QIF_BTIME","QIF_ILIMITS","QIF_INODES","QIF_ITIME","QIF_LIMITS","QIF_SPACE","QIF_TIMES","QIF_USAGE","QuotaFmt","QuotaType","QuotaValidFlags","USRQUOTA","all","allocated_inodes","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","block_time_limit","blocks_hard_limit","blocks_soft_limit","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","contains","default","default","difference","empty","eq","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","hash","hash","inode_time_limit","inodes_hard_limit","inodes_soft_limit","insert","intersection","intersects","into","into","into","into","is_all","is_empty","not","occupied_space","partial_cmp","partial_cmp","partial_cmp","quotactl_get","quotactl_off","quotactl_on","quotactl_set","quotactl_sync","remove","set","set_block_time_limit","set_blocks_hard_limit","set_blocks_soft_limit","set_inode_time_limit","set_inodes_hard_limit","set_inodes_soft_limit","sub","sub_assign","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","union","RB_AUTOBOOT","RB_HALT_SYSTEM","RB_KEXEC","RB_POWER_OFF","RB_SW_SUSPEND","RebootMode","borrow","borrow_mut","clone","clone_into","cmp","eq","fmt","from","hash","into","partial_cmp","reboot","set_cad_enabled","to_owned","try_from","try_into","type_id","RLIMIT_AS","RLIMIT_CORE","RLIMIT_CPU","RLIMIT_DATA","RLIMIT_FSIZE","RLIMIT_LOCKS","RLIMIT_MEMLOCK","RLIMIT_MSGQUEUE","RLIMIT_NICE","RLIMIT_NOFILE","RLIMIT_NPROC","RLIMIT_RSS","RLIMIT_RTPRIO","RLIMIT_RTTIME","RLIMIT_SIGPENDING","RLIMIT_STACK","RLIM_INFINITY","RUSAGE_CHILDREN","RUSAGE_SELF","RUSAGE_THREAD","Resource","Usage","UsageWho","as_mut","as_ref","block_reads","block_writes","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","eq","eq","eq","fmt","fmt","fmt","from","from","from","full_swaps","getrlimit","getrusage","hash","hash","hash","into","into","into","involuntary_context_switches","ipc_receives","ipc_sends","major_page_faults","max_rss","minor_page_faults","partial_cmp","partial_cmp","rlim_t","setrlimit","shared_integral","signals","system_time","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","unshared_data_integral","unshared_stack_integral","user_time","voluntary_context_switches","FD_SETSIZE","FdSet","Fds","borrow","borrow","borrow_mut","borrow_mut","clear","clone","clone_into","contains","default","eq","fds","fmt","fmt","from","from","hash","highest","insert","into","into","into_iter","new","next","next_back","pselect","remove","select","size_hint","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","sendfile","sendfile64","Handler","SA_NOCLDSTOP","SA_NOCLDWAIT","SA_NODEFER","SA_ONSTACK","SA_RESETHAND","SA_RESTART","SA_SIGINFO","SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCONT","SIGFPE","SIGHUP","SIGILL","SIGINT","SIGIO","SIGIOT","SIGKILL","SIGPIPE","SIGPOLL","SIGPROF","SIGPWR","SIGQUIT","SIGSEGV","SIGSTKFLT","SIGSTOP","SIGSYS","SIGTERM","SIGTRAP","SIGTSTP","SIGTTIN","SIGTTOU","SIGUNUSED","SIGURG","SIGUSR1","SIGUSR2","SIGVTALRM","SIGWINCH","SIGXCPU","SIGXFSZ","SIG_BLOCK","SIG_SETMASK","SIG_UNBLOCK","SaFlags","SigAction","SigAction","SigDfl","SigEvent","SigHandler","SigIgn","SigSet","SigSetIter","SigevNone","SigevNotify","SigevSignal","SigevThreadId","SigmaskHow","Signal","SignalIterator","add","all","all","as_mut_ptr","as_ref","as_ref","as_str","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","contains","contains","difference","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","from_iter","from_sigset_t_unchecked","from_str","handler","hash","hash","hash","hash","hash","hash","hash","hash","hash","insert","intersection","intersects","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_all","is_empty","iter","iterator","kill","killpg","mask","new","new","next","next","not","partial_cmp","partial_cmp","partial_cmp","pthread_sigmask","raise","remove","remove","set","sigaction","sigevent","signal","sigprocmask","sub","sub_assign","symmetric_difference","thread_block","thread_get_mask","thread_set_mask","thread_swap_mask","thread_unblock","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_of_thread_id","union","wait","0","0","si_value","si_value","signal","signal","thread_id","SFD_CLOEXEC","SFD_NONBLOCK","SIGNALFD_NEW","SIGNALFD_SIGINFO_SIZE","SfdFlags","SigSet","SignalFd","all","as_raw_fd","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","difference","drop","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","hash","hash","hash","insert","intersection","intersects","into","into","into","into_iter","is_all","is_empty","new","next","not","partial_cmp","read_signal","remove","set","set_mask","siginfo","signal","signalfd","ssi_addr","ssi_addr_lsb","ssi_arch","ssi_band","ssi_call_addr","ssi_code","ssi_errno","ssi_fd","ssi_int","ssi_overrun","ssi_pid","ssi_ptr","ssi_signo","ssi_status","ssi_stime","ssi_syscall","ssi_tid","ssi_trapno","ssi_uid","ssi_utime","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","with_flags","0","0","AddressFamily","Alg","Alg","AlgAddr","AlgSetAeadAssoclen","AlgSetIv","AlgSetOp","AppleTalk","Ash","AtmPvc","AtmSvc","Ax25","Bluetooth","Both","Bridge","Caif","Can","CmsgIterator","ControlMessage","ControlMessageOwned","Datagram","Decnet","Econet","EthAll","GetSockOpt","Ib","Ieee802154","Inet","Inet","Inet6","InetAddr","IoSliceIterator","IpAddr","IpMembershipRequest","Ipv4Addr","Ipv4OrigDstAddr","Ipv4PacketInfo","Ipv4PacketInfo","Ipv4RecvErr","Ipv6Addr","Ipv6MembershipRequest","Ipv6OrigDstAddr","Ipv6PacketInfo","Ipv6PacketInfo","Ipv6RecvErr","Ipx","Irda","Isdn","Iucv","Key","Link","LinkAddr","Llc","MSG_CMSG_CLOEXEC","MSG_CTRUNC","MSG_DONTWAIT","MSG_EOR","MSG_ERRQUEUE","MSG_NOSIGNAL","MSG_OOB","MSG_PEEK","MSG_TRUNC","MSG_WAITALL","Mpls","MsgFlags","MultiHeaders","MultiResults","NetBeui","NetRom","Netlink","Netlink","NetlinkAddr","NetlinkAudit","NetlinkCrypto","NetlinkDECNetRoutingMessage","NetlinkFIBLookup","NetlinkIPv6Firewall","NetlinkISCSI","NetlinkKObjectUEvent","NetlinkNetFilter","NetlinkRDMA","NetlinkRoute","NetlinkSCSITransport","NetlinkSELinux","NetlinkSockDiag","NetlinkUserSock","Nfc","Packet","Phonet","Pppox","Raw","Raw","Rdm","Rds","Read","RecvMsg","Rose","RxRpc","RxqOvfl","RxqOvfl","SOCK_CLOEXEC","SOCK_NONBLOCK","SOF_TIMESTAMPING_RAW_HARDWARE","SOF_TIMESTAMPING_RX_HARDWARE","SOF_TIMESTAMPING_RX_SOFTWARE","SOF_TIMESTAMPING_SOFTWARE","SOF_TIMESTAMPING_TX_HARDWARE","SOF_TIMESTAMPING_TX_SOFTWARE","ScmCredentials","ScmCredentials","ScmRights","ScmRights","ScmTimestamp","ScmTimestampns","ScmTimestampsns","Security","SeqPacket","SetSockOpt","Shutdown","Sna","SockAddr","SockFlag","SockProtocol","SockType","SockaddrIn","SockaddrIn6","SockaddrLike","SockaddrStorage","Stream","Tcp","TimestampingFlag","Timestamps","Tipc","TxTime","Udp","UdpGroSegments","UdpGsoSegments","Unix","Unix","UnixAddr","UnixCredentials","Unspec","V4","V4","V6","V6","Val","Val","Vsock","Vsock","VsockAddr","Wanpipe","Write","X25","accept","accept4","addr","address","alg_name","alg_type","all","all","all","any","as_abstract","as_alg_addr","as_alg_addr_mut","as_ffi_pair","as_link_addr","as_link_addr_mut","as_mut_ptr","as_netlink_addr","as_netlink_addr_mut","as_ptr","as_ptr","as_ptr","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_sockaddr_in","as_sockaddr_in6","as_sockaddr_in6_mut","as_sockaddr_in_mut","as_unix_addr","as_unix_addr_mut","as_vsock_addr","as_vsock_addr_mut","bind","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bytes","cid","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmsg_len","cmsg_level","cmsg_type","cmsghdr","cmsgs","complement","complement","complement","connect","contains","contains","contains","default","difference","difference","difference","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","family","family","family","flags","flowinfo","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_i32","from_iter","from_iter","from_iter","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_raw","from_std","from_std","from_std","from_std","from_str","from_str","get","getpeername","getsockname","getsockopt","gid","groups","halen","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hatype","hw_raw","hw_trans","ifindex","insert","insert","insert","intersection","intersection","intersection","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","iovs","ip","ip","ip","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_unnamed","len","len","len","len","listen","msg_control","msg_controllen","msg_flags","msg_iov","msg_iovlen","msg_name","msg_namelen","msghdr","new","new","new","new","new","new","new","new","new","new","new","new_abstract","new_alg","new_inet","new_netlink","new_unix","new_unnamed","new_v4","new_v6","new_vsock","next","next","next","not","not","not","octets","partial_cmp","partial_cmp","partial_cmp","path","path_len","pid","pid","pkttype","port","port","port","port","preallocate","protocol","recv","recvfrom","recvmmsg","recvmsg","remove","remove","remove","sa_data","sa_family","sa_family_t","scope_id","segments","send","sendmmsg","sendmsg","sendto","set","set","set","set","setsockopt","shutdown","sin6_addr","sin6_family","sin6_flowinfo","sin6_port","sin6_scope_id","sin_addr","sin_family","sin_port","sin_zero","size","size","size","sockaddr","sockaddr_in","sockaddr_in6","sockaddr_storage","sockaddr_storage_to_addr","sockaddr_un","socket","socketpair","sockopt","ss_family","sub","sub","sub","sub_assign","sub_assign","sub_assign","sun_family","sun_path","symmetric_difference","symmetric_difference","symmetric_difference","system","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_std","to_std","to_std","to_std","to_str","to_str","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","union","union","union","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","1","0","0","0","0","0","0","0","0","0","0","AcceptConn","AlgSetAeadAuthSize","AlgSetKey","BindToDevice","Broadcast","DontRoute","Ip6tOriginalDst","IpAddMembership","IpDropMembership","IpFreebind","IpMtu","IpMulticastLoop","IpMulticastTtl","IpTos","IpTransparent","Ipv4OrigDstAddr","Ipv4PacketInfo","Ipv4RecvErr","Ipv4Ttl","Ipv6AddMembership","Ipv6DontFrag","Ipv6DropMembership","Ipv6OrigDstAddr","Ipv6RecvErr","Ipv6RecvPacketInfo","Ipv6TClass","Ipv6Ttl","Ipv6V6Only","KeepAlive","Linger","Mark","OobInline","OriginalDst","PassCred","PeerCredentials","Priority","RcvBuf","RcvBufForce","ReceiveTimeout","ReceiveTimestamp","ReceiveTimestampns","ReuseAddr","ReusePort","RxqOvfl","SendTimeout","SndBuf","SndBufForce","SockType","SocketError","TcpCongestion","TcpKeepCount","TcpKeepIdle","TcpKeepInterval","TcpMaxSeg","TcpNoDelay","TcpRepair","TcpUserTimeout","Timestamping","TxTime","UdpGroSegment","UdpGsoSegment","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","get","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","set","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","FchmodatFlags","FileStat","FileStat","FollowSymlink","FollowSymlink","Mode","NoFollowSymlink","NoFollowSymlink","SFlag","S_IFBLK","S_IFCHR","S_IFDIR","S_IFIFO","S_IFLNK","S_IFMT","S_IFREG","S_IFSOCK","S_IRGRP","S_IROTH","S_IRUSR","S_IRWXG","S_IRWXO","S_IRWXU","S_ISGID","S_ISUID","S_ISVTX","S_IWGRP","S_IWOTH","S_IWUSR","S_IXGRP","S_IXOTH","S_IXUSR","UtimensatFlags","all","all","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","complement","complement","contains","contains","dev_t","difference","difference","empty","empty","eq","eq","eq","extend","extend","fchmod","fchmodat","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","fstat","fstatat","futimens","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","lstat","lutimes","major","makedev","minor","mkdirat","mknod","mknodat","mode_t","not","not","partial_cmp","partial_cmp","remove","remove","set","set","st_atime","st_atime_nsec","st_blksize","st_blocks","st_ctime","st_ctime_nsec","st_dev","st_gid","st_ino","st_mode","st_mtime","st_mtime_nsec","st_nlink","st_rdev","st_size","st_uid","stat","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","umask","union","union","utimensat","utimes","0","ADFS_SUPER_MAGIC","AFFS_SUPER_MAGIC","AFS_SUPER_MAGIC","AUTOFS_SUPER_MAGIC","BPF_FS_MAGIC","BTRFS_SUPER_MAGIC","CGROUP2_SUPER_MAGIC","CGROUP_SUPER_MAGIC","CODA_SUPER_MAGIC","CRAMFS_MAGIC","DEBUGFS_MAGIC","DEVPTS_SUPER_MAGIC","ECRYPTFS_SUPER_MAGIC","EFS_SUPER_MAGIC","EXT2_SUPER_MAGIC","EXT3_SUPER_MAGIC","EXT4_SUPER_MAGIC","F2FS_SUPER_MAGIC","FUSE_SUPER_MAGIC","FUTEXFS_SUPER_MAGIC","FsType","HOSTFS_SUPER_MAGIC","HPFS_SUPER_MAGIC","HUGETLBFS_MAGIC","ISOFS_SUPER_MAGIC","JFFS2_SUPER_MAGIC","MINIX2_SUPER_MAGIC","MINIX2_SUPER_MAGIC2","MINIX3_SUPER_MAGIC","MINIX_SUPER_MAGIC","MINIX_SUPER_MAGIC2","MSDOS_SUPER_MAGIC","NCP_SUPER_MAGIC","NFS_SUPER_MAGIC","NILFS_SUPER_MAGIC","NSFS_MAGIC","OCFS2_SUPER_MAGIC","OPENPROM_SUPER_MAGIC","OVERLAYFS_SUPER_MAGIC","PROC_SUPER_MAGIC","QNX4_SUPER_MAGIC","QNX6_SUPER_MAGIC","RDTGROUP_SUPER_MAGIC","REISERFS_SUPER_MAGIC","SECURITYFS_MAGIC","SELINUX_MAGIC","SMACK_MAGIC","SMB_SUPER_MAGIC","SYSFS_MAGIC","Statfs","TMPFS_MAGIC","TRACEFS_MAGIC","UDF_SUPER_MAGIC","USBDEVICE_SUPER_MAGIC","XENFS_SUPER_MAGIC","XFS_SUPER_MAGIC","block_size","blocks","blocks_available","blocks_free","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","files","files_free","filesystem_id","filesystem_type","flags","fmt","fmt","from","from","fsid_t","fstatfs","into","into","maximum_name_length","optimal_transfer_size","statfs","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","FsFlags","ST_APPEND","ST_IMMUTABLE","ST_MANDLOCK","ST_NOATIME","ST_NODEV","ST_NODIRATIME","ST_NOEXEC","ST_NOSUID","ST_RDONLY","ST_RELATIME","ST_SYNCHRONOUS","ST_WRITE","Statvfs","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","block_size","blocks","blocks_available","blocks_free","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","complement","contains","default","difference","empty","eq","eq","extend","files","files_available","files_free","filesystem_id","flags","fmt","fmt","fmt","fmt","fmt","fmt","fragment_size","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","fstatvfs","hash","hash","insert","intersection","intersects","into","into","is_all","is_empty","name_max","not","partial_cmp","remove","set","statvfs","sub","sub_assign","symmetric_difference","to_owned","to_owned","toggle","try_from","try_from","try_into","try_into","type_id","type_id","union","SysInfo","borrow","borrow_mut","clone","clone_into","eq","fmt","from","hash","into","load_average","process_count","ram_total","ram_unused","swap_free","swap_total","sysinfo","to_owned","try_from","try_into","type_id","uptime","B0","B1000000","B110","B115200","B1152000","B1200","B134","B150","B1500000","B1800","B19200","B200","B2000000","B230400","B2400","B2500000","B300","B3000000","B3500000","B38400","B4000000","B460800","B4800","B50","B500000","B57600","B576000","B600","B75","B921600","B9600","BRKINT","BS0","BS1","BSDLY","BaudRate","CBAUD","CBAUDEX","CIBAUD","CLOCAL","CMSPAR","CR0","CR1","CR2","CR3","CRDLY","CREAD","CRTSCTS","CS5","CS6","CS7","CS8","CSIZE","CSTOPB","ControlFlags","ECHO","ECHOCTL","ECHOE","ECHOK","ECHOKE","ECHONL","ECHOPRT","EXTPROC","FF0","FF1","FFDLY","FLUSHO","FlowArg","FlushArg","HUPCL","ICANON","ICRNL","IEXTEN","IGNBRK","IGNCR","IGNPAR","IMAXBEL","INLCR","INPCK","ISIG","ISTRIP","IUTF8","IXANY","IXOFF","IXON","InputFlags","LocalFlags","NCCS","NL0","NL1","NLDLY","NOFLSH","OCRNL","OFDEL","OFILL","OLCUC","ONLCR","ONLRET","ONOCR","OPOST","OutputFlags","PARENB","PARMRK","PARODD","PENDIN","SetArg","SpecialCharacterIndices","TAB0","TAB1","TAB2","TAB3","TABDLY","TCIFLUSH","TCIOFF","TCIOFLUSH","TCION","TCOFLUSH","TCOOFF","TCOON","TCSADRAIN","TCSAFLUSH","TCSANOW","TOSTOP","Termios","VDISCARD","VEOF","VEOL","VEOL2","VERASE","VINTR","VKILL","VLNEXT","VMIN","VQUIT","VREPRINT","VSTART","VSTOP","VSUSP","VSWTC","VT0","VT1","VTDLY","VTIME","VWERASE","XTABS","_POSIX_VDISABLE","all","all","all","all","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cfgetispeed","cfgetospeed","cfmakeraw","cfsetispeed","cfsetospeed","cfsetspeed","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","complement","complement","complement","complement","contains","contains","contains","contains","control_chars","control_flags","difference","difference","difference","difference","empty","empty","empty","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","extend","extend","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_iter","from_iter","hash","hash","hash","hash","hash","hash","hash","hash","hash","input_flags","insert","insert","insert","insert","intersection","intersection","intersection","intersection","intersects","intersects","intersects","intersects","into","into","into","into","into","into","into","into","into","into","is_all","is_all","is_all","is_all","is_empty","is_empty","is_empty","is_empty","line_discipline","local_flags","not","not","not","not","output_flags","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","remove","remove","remove","remove","set","set","set","set","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","sub_assign","symmetric_difference","symmetric_difference","symmetric_difference","symmetric_difference","tcdrain","tcflow","tcflush","tcgetattr","tcgetsid","tcsendbreak","tcsetattr","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","toggle","toggle","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","union","union","TimeSpec","TimeVal","TimeValLike","add","add","as_mut","as_mut","as_ref","as_ref","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","div","div","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from_duration","from_timespec","hash","hash","hours","into","into","microseconds","microseconds","microseconds","milliseconds","milliseconds","milliseconds","minutes","mul","mul","nanoseconds","nanoseconds","nanoseconds","neg","neg","new","new","num_hours","num_microseconds","num_microseconds","num_microseconds","num_milliseconds","num_milliseconds","num_milliseconds","num_minutes","num_nanoseconds","num_nanoseconds","num_nanoseconds","num_seconds","num_seconds","num_seconds","partial_cmp","partial_cmp","seconds","seconds","seconds","sub","sub","suseconds_t","time_t","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","tv_nsec","tv_sec","tv_sec","tv_usec","type_id","type_id","zero","Expiration","Interval","IntervalDelayed","OneShot","Timer","TimerSetTimeFlags","borrow","borrow_mut","drop","fmt","from","get","into","new","overruns","set","try_from","try_into","type_id","0","0","0","1","CLOCK_BOOTTIME","CLOCK_BOOTTIME_ALARM","CLOCK_MONOTONIC","CLOCK_REALTIME","CLOCK_REALTIME_ALARM","ClockId","Expiration","Interval","IntervalDelayed","OneShot","TFD_CLOEXEC","TFD_NONBLOCK","TFD_TIMER_ABSTIME","TimerFd","TimerFlags","TimerSetTimeFlags","all","all","as_raw_fd","bitand","bitand","bitand_assign","bitand_assign","bitor","bitor","bitor_assign","bitor_assign","bits","bits","bitxor","bitxor","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","complement","complement","contains","contains","difference","difference","drop","empty","empty","eq","eq","eq","eq","extend","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from_bits","from_bits","from_bits_truncate","from_bits_truncate","from_bits_unchecked","from_bits_unchecked","from_iter","from_iter","from_raw_fd","get","hash","hash","hash","insert","insert","intersection","intersection","intersects","intersects","into","into","into","into","into","is_all","is_all","is_empty","is_empty","new","not","not","partial_cmp","partial_cmp","partial_cmp","remove","remove","set","set","set","sub","sub","sub_assign","sub_assign","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","toggle","toggle","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","union","union","unset","wait","0","0","0","1","IoVec","RemoteIoVec","as_slice","base","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","from","from","from_mut_slice","from_slice","hash","hash","into","into","len","pread","preadv","process_vm_readv","process_vm_writev","pwrite","pwritev","readv","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","writev","UtsName","borrow","borrow_mut","clone","clone_into","domainname","eq","fmt","from","hash","into","machine","nodename","release","sysname","to_owned","try_from","try_into","type_id","uname","version","All","Continued","Exited","Id","PGid","PIDFd","Pid","PtraceEvent","PtraceSyscall","Signaled","StillAlive","Stopped","WCONTINUED","WEXITED","WNOHANG","WNOWAIT","WSTOPPED","WUNTRACED","WaitPidFlag","WaitStatus","__WALL","__WCLONE","__WNOTHREAD","all","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","cmp","complement","contains","difference","empty","eq","eq","eq","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","from_raw","hash","hash","hash","insert","intersection","intersects","into","into","into","is_all","is_empty","not","partial_cmp","partial_cmp","pid","remove","set","sub","sub_assign","symmetric_difference","to_owned","to_owned","to_owned","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","union","wait","waitid","waitpid","0","0","0","0","0","0","0","0","0","1","1","1","1","2","2","CLOCK_BOOTTIME","CLOCK_BOOTTIME_ALARM","CLOCK_MONOTONIC","CLOCK_MONOTONIC_COARSE","CLOCK_MONOTONIC_RAW","CLOCK_PROCESS_CPUTIME_ID","CLOCK_REALTIME","CLOCK_REALTIME_ALARM","CLOCK_REALTIME_COARSE","CLOCK_TAI","CLOCK_THREAD_CPUTIME_ID","ClockId","as_raw","borrow","borrow_mut","clock_getcpuclockid","clock_getres","clock_gettime","clock_settime","clone","clone_into","cmp","eq","fmt","fmt","from","from","from_raw","hash","into","now","partial_cmp","pid_cpu_clock_id","res","set_time","to_owned","to_string","try_from","try_into","type_id","UContext","borrow","borrow_mut","clone","clone_into","eq","fmt","from","get","hash","into","set","sigmask","sigmask_mut","to_owned","try_from","try_into","type_id","AIO_LISTIO_MAX","AIO_MAX","AIO_PRIO_DELTA_MAX","ARG_MAX","ATEXIT_MAX","AccessFlags","BC_BASE_MAX","BC_DIM_MAX","BC_SCALE_MAX","BC_STRING_MAX","CHILD_MAX","CLK_TCK","COLL_WEIGHTS_MAX","Child","DELAYTIMER_MAX","EXPR_NEST_MAX","FILESIZEBITS","F_OK","FchownatFlags","FollowSymlink","ForkResult","GETGR_R_SIZE_MAX","GETPW_R_SIZE_MAX","Gid","Group","HOST_NAME_MAX","IOV_MAX","LINE_MAX","LINK_MAX","LOGIN_NAME_MAX","LinkatFlags","MAX_CANON","MAX_INPUT","MQ_OPEN_MAX","MQ_PRIO_MAX","NAME_MAX","NGROUPS_MAX","NoFollowSymlink","NoRemoveDir","NoSymlinkFollow","OPEN_MAX","PAGE_SIZE","PATH_MAX","PIPE_BUF","POSIX2_SYMLINKS","POSIX_ALLOC_SIZE_MIN","POSIX_REC_INCR_XFER_SIZE","POSIX_REC_MAX_XFER_SIZE","POSIX_REC_MIN_XFER_SIZE","POSIX_REC_XFER_ALIGN","PTHREAD_DESTRUCTOR_ITERATIONS","PTHREAD_KEYS_MAX","PTHREAD_STACK_MIN","PTHREAD_THREADS_MAX","Parent","PathconfVar","Pid","RE_DUP_MAX","ROOT","RTSIG_MAX","R_OK","RemoveDir","ResGid","ResUid","SEM_NSEMS_MAX","SEM_VALUE_MAX","SIGQUEUE_MAX","STREAM_MAX","SYMLINK_MAX","SYMLOOP_MAX","SeekCur","SeekData","SeekEnd","SeekHole","SeekSet","SymlinkFollow","SysconfVar","TIMER_MAX","TTY_NAME_MAX","TZNAME_MAX","Uid","UnlinkatFlags","User","W_OK","Whence","X_OK","_AVPHYS_PAGES","_NPROCESSORS_CONF","_NPROCESSORS_ONLN","_PHYS_PAGES","_POSIX2_CHAR_TERM","_POSIX2_C_BIND","_POSIX2_C_DEV","_POSIX2_FORT_DEV","_POSIX2_FORT_RUN","_POSIX2_LOCALEDEF","_POSIX2_PBS","_POSIX2_PBS_ACCOUNTING","_POSIX2_PBS_CHECKPOINT","_POSIX2_PBS_LOCATE","_POSIX2_PBS_MESSAGE","_POSIX2_PBS_TRACK","_POSIX2_SW_DEV","_POSIX2_UPE","_POSIX2_VERSION","_POSIX_ADVISORY_INFO","_POSIX_ASYNCHRONOUS_IO","_POSIX_ASYNC_IO","_POSIX_BARRIERS","_POSIX_CHOWN_RESTRICTED","_POSIX_CLOCK_SELECTION","_POSIX_CPUTIME","_POSIX_FSYNC","_POSIX_IPV6","_POSIX_JOB_CONTROL","_POSIX_MAPPED_FILES","_POSIX_MEMLOCK","_POSIX_MEMLOCK_RANGE","_POSIX_MEMORY_PROTECTION","_POSIX_MESSAGE_PASSING","_POSIX_MONOTONIC_CLOCK","_POSIX_NO_TRUNC","_POSIX_PRIORITIZED_IO","_POSIX_PRIORITY_SCHEDULING","_POSIX_PRIO_IO","_POSIX_RAW_SOCKETS","_POSIX_READER_WRITER_LOCKS","_POSIX_REALTIME_SIGNALS","_POSIX_REGEXP","_POSIX_SAVED_IDS","_POSIX_SEMAPHORES","_POSIX_SHARED_MEMORY_OBJECTS","_POSIX_SHELL","_POSIX_SPAWN","_POSIX_SPIN_LOCKS","_POSIX_SPORADIC_SERVER","_POSIX_SS_REPL_MAX","_POSIX_SYNCHRONIZED_IO","_POSIX_SYNC_IO","_POSIX_THREADS","_POSIX_THREAD_ATTR_STACKADDR","_POSIX_THREAD_ATTR_STACKSIZE","_POSIX_THREAD_CPUTIME","_POSIX_THREAD_PRIORITY_SCHEDULING","_POSIX_THREAD_PRIO_INHERIT","_POSIX_THREAD_PRIO_PROTECT","_POSIX_THREAD_PROCESS_SHARED","_POSIX_THREAD_ROBUST_PRIO_INHERIT","_POSIX_THREAD_ROBUST_PRIO_PROTECT","_POSIX_THREAD_SAFE_FUNCTIONS","_POSIX_THREAD_SPORADIC_SERVER","_POSIX_TIMEOUTS","_POSIX_TIMERS","_POSIX_TRACE","_POSIX_TRACE_EVENT_FILTER","_POSIX_TRACE_EVENT_NAME_MAX","_POSIX_TRACE_INHERIT","_POSIX_TRACE_LOG","_POSIX_TRACE_NAME_MAX","_POSIX_TRACE_SYS_MAX","_POSIX_TRACE_USER_EVENT_MAX","_POSIX_TYPED_MEMORY_OBJECTS","_POSIX_V6_ILP32_OFF32","_POSIX_V6_ILP32_OFFBIG","_POSIX_V6_LP64_OFF64","_POSIX_V6_LPBIG_OFFBIG","_POSIX_VDISABLE","_POSIX_VERSION","_XOPEN_CRYPT","_XOPEN_ENH_I18N","_XOPEN_LEGACY","_XOPEN_REALTIME","_XOPEN_REALTIME_THREADS","_XOPEN_SHM","_XOPEN_STREAMS","_XOPEN_UNIX","_XOPEN_VERSION","access","acct","alarm","all","as_raw","as_raw","as_raw","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chdir","chown","chroot","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","close","cmp","cmp","complement","contains","current","current","daemon","difference","dir","dup","dup2","dup3","eaccess","effective","effective","effective","effective","empty","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","execv","execve","execveat","execvp","execvpe","extend","faccessat","fchdir","fchown","fchownat","fdatasync","fexecve","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fork","fpathconf","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_gid","from_iter","from_name","from_name","from_raw","from_raw","from_raw","from_uid","fsync","ftruncate","gecos","getcwd","getegid","geteuid","getgid","getgrouplist","getgroups","gethostname","getpgid","getpgrp","getpid","getppid","getresgid","getresuid","getsid","gettid","getuid","gid","gid","hash","hash","hash","hash","hash","hash","initgroups","insert","intersection","intersects","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","is_all","is_child","is_empty","is_parent","is_root","isatty","linkat","lseek","lseek64","mem","mkdir","mkfifo","mkfifoat","mkstemp","name","name","not","parent","partial_cmp","partial_cmp","passwd","passwd","pathconf","pause","pipe","pipe2","pivot_root","read","real","real","remove","saved","saved","set","setegid","seteuid","setfsgid","setfsuid","setgid","setgroups","sethostname","setpgid","setresgid","setresuid","setsid","setuid","shell","sleep","sub","sub_assign","symlinkat","symmetric_difference","sync","syncfs","sysconf","tcgetpgrp","tcsetpgrp","this","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","toggle","truncate","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","ttyname","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","union","unlink","unlinkat","write","child","disable","enable","cancel","set"],"q":["nix","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::dir","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::env","","","","","","","","","","","","","","","","nix::errno","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::fcntl","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::fcntl::FcntlArg","","","","","","","","","","","","nix::features","nix::ifaddrs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::kmod","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::mount","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::mqueue","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::net","nix::net::if_","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::poll","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::pty","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sched","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::aio","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::epoll","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::eventfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::inotify","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::ioctl","","","","nix::sys::memfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::mman","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::personality","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::pthread","","","nix::sys::ptrace","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::quota","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::reboot","","","","","","","","","","","","","","","","","","","","","","","nix::sys::resource","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::select","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::sendfile","","nix::sys::signal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::signal::SigHandler","","nix::sys::signal::SigevNotify","","","","","nix::sys::signalfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::socket","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::socket::ControlMessage","","","","","","","","","","nix::sys::socket::ControlMessageOwned","","","","","","","","","","","","","","","nix::sys::socket::InetAddr","","nix::sys::socket::IpAddr","","nix::sys::socket::SockAddr","","","","","","nix::sys::socket::sockopt","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::stat","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::statfs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::statvfs","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::sysinfo","","","","","","","","","","","","","","","","","","","","","","nix::sys::termios","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::time","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::timer","","","","","","","","","","","","","","","","","","","nix::sys::timer::Expiration","","","","nix::sys::timerfd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::timerfd::Expiration","","","","nix::sys::uio","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::utsname","","","","","","","","","","","","","","","","","","","","","nix::sys::wait","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::sys::wait::Id","","","nix::sys::wait::WaitStatus","","","","","","","","","","","","nix::time","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::ucontext","","","","","","","","","","","","","","","","","","nix::unistd","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","nix::unistd::ForkResult","nix::unistd::acct","","nix::unistd::alarm",""],"d":["Nix’s main error type.","Common trait used to represent file system paths by many …","Nix Result Type","Create a buffer large enough for storing some control …","List directory contents","Environment variables","","","Feature tests for OS functionality","Query network interface addresses","Generates a wrapper function for an ioctl that passes no …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads data …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads an …","Generates a wrapper function for an ioctl that reads and …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that reads and …","Generates a wrapper function for an ioctl that writes an …","Generates a wrapper function for a ioctl that writes an …","Generates a wrapper function for a “bad” ioctl that …","Generates a wrapper function for an ioctl that writes data …","Generates a wrapper function for a “bad” ioctl that …","Is the path empty?","Load and unload kernel modules.","Length of the path in bytes","","Mount file systems","Posix Message Queue functions","Functionality involving network interfaces","Wait for events to trigger on specific file descriptors","Create master and slave virtual pseudo-terminals (PTYs)","Generate an ioctl request code for a command that passes …","Generate an ioctl request code for a command that reads.","Generate an ioctl request code for a command that reads …","Generate an ioctl request code for a command that writes.","Execution scheduling","Mostly platform-specific functionality","","","Safe wrappers around functions found in libc “unistd.h”…","Execute a function with this path as a CStr.","Block device","Character device","An open directory.","Directory","A directory entry, similar to std::fs::DirEntry.","FIFO (Named pipe)","Regular file","Return type of Dir::iter.","The return type of Dir::into_iter","Unix-domain socket","Symbolic link","Type of file referenced by a directory entry","","","","","","","","","","","","","","","","","","","","","","","","Returns the bare file name of this directory entry without …","Returns the type of this directory entry, if known.","","","","","","Returns the argument unchanged.","Converts from a descriptor-based object, closing the …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts from a file descriptor, closing it on success or …","","","","","","Returns the inode number (d_ino) of the underlying dirent.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a owning iterator, that is, one that takes …","","","Returns an iterator of Result<Entry> which rewinds when …","","","Opens the given path as with fcntl::open.","Opens the given path as with fcntl::openat.","","","","","","","","","","","","","","","","","","Indicates that clearenv failed for some unknown reason","","","Clear the environment of all name-value pairs.","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The sentinel value indicates that a function failed and …","","","","","","","","","Returns the platform-specific value of errno","","","Returns the argument unchanged.","","","Calls U::from(self).","","","Returns Ok(value) if it does not contain the sentinel …","","","","","","","","","","","","","","Removes byte range from a file without leaving a hole.","Increases file space by inserting a hole within the file …","File size is not changed.","Deallocates space by creating a hole.","Shared file data extants are made private to the file.","Zeroes space in specified byte range.","The file descriptor will automatically be closed during a …","","","","","","","","","","","","The size of the file cannot be increased.","Prevents further calls to fcntl() with F_ADD_SEALS.","The file cannot be reduced in size.","The file contents cannot be modified.","","","","","","Mode argument flags for fallocate determining operation …","","Additional configuration flags for fcntl’s F_SETFD.","","","","","","Configuration options for opened files.","Mask for the access mode of the file.","Open the file in append-only mode.","Generate a signal when input or output becomes possible.","Closes the file descriptor once an execve call is made.","Create the file if it does not exist.","Try to minimize cache effects of the I/O for this file.","If the specified path isn’t a directory, fail.","Implicitly follow each write() with an fdatasync().","Error out if a file was not created.","Same as O_SYNC.","Allow files whose sizes can’t be represented in an off_t …","Same as O_NONBLOCK.","Do not update the file last access time during read(2)s.","Don’t attach the device as the process’ controlling …","open() will fail if the given path is a symbolic link.","When possible, open the file in nonblocking mode.","Obtain a file descriptor for low-level access.","Only allow reading.","Allow both reading and writing.","Similar to O_DSYNC but applies to reads instead.","Implicitly follow each write() with an fsync().","Create an unnamed temporary file.","Truncate an existing regular file to 0 length if it allows …","Only allow writing.","","","","","","","","","","","","Gift the user pages to the kernel.","Hint that more data will be coming in a subsequent splice.","Request that pages be moved instead of copied.","Do not block on I/O.","Additional flags for file sealing, which allows for …","Additional flags to splice and friends.","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Copy a range of data from one file to another","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","Manipulates file space.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","","","","","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","","","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","Check if the OS supports atomic close-on-exec for sockets","Describes a single address for an interface as returned by …","Holds the results of getifaddrs.","Network address of this interface","","","","","Broadcast address of this interface, if applicable","","","Point-to-point destination address","","","","Flags as from SIOCGIFFLAGS ioctl","","","Returns the argument unchanged.","Returns the argument unchanged.","Get interface addresses using libc’s getifaddrs","","","Name of the network interface","Calls U::from(self).","Calls U::from(self).","","Netmask of this interface","","","","","","","","","Flags used by delete_module.","Ignore symbol version hashes.","Ignore kernel version magic.","Flags used by the finit_module function.","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Unloads the kernel module with the given name.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","Loads a kernel module from a given file descriptor.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Loads a kernel module from a buffer.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","Linux 2.4.0 - Bind directory at different place","Directory modifications are synchronous","","","","Allow mandatory locks on a FS","","","","Do not update access times","Disallow access to device special files","Do not update directory access times","Disallow program execution","Ignore suid and sgid bits","","","","Mount read-only","","","Alter flags of a mounted FS","","","","","","Writes are synced at once","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Used with mq_open.","A message-queue attribute, optionally used with mq_setattr …","Identifies an open POSIX Message Queue","Set the close-on-exec flag for the message queue …","Create a message queue.","If set along with O_CREAT, mq_open will fail if the message","mq_send and mq_receive should fail with EAGAIN rather than …","Open the message queue for receiving messages.","Open the queue for both receiving and sending messages","Open the queue for sending messages.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","The number of messages currently held in the queue","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","The current flags, either 0 or O_NONBLOCK.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","The max number of messages that can be held by the queue","Size of a message queue attribute member","Close a message queue","Get message queue attributes","Open a message queue","Receive a message from a message queue","Convenience function. Removes O_NONBLOCK attribute for a …","Send a message to a message queue","Convenience function. Sets the O_NONBLOCK attribute for a …","Set the attributes of the message queue. Only O_NONBLOCK …","Remove a message queue","The maximum size of each message (in bytes)","Create a new message queue attribute","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","Network interface name resolution.","Receive all multicast packets. (see netdevice(7))","Auto media selection active. (see netdevice(7))","Valid broadcast address set. (see netdevice(7))","Internal debugging flag. (see netdevice(7))","Driver signals dormant. Volatile.","The addresses are lost when the interface goes down. (see …","Echo sent packets. Volatile.","Interface is a loopback interface. (see netdevice(7))","Driver signals L1 up. Volatile.","Master of a load balancing bundle. (see netdevice(7))","Supports multicast. (see netdevice(7))","No arp protocol, L2 destination address not set. (see …","Avoid use of trailers. (see netdevice(7))","Do not provide packet information","Interface is a point-to-point link. (see netdevice(7))","Is able to select media type via ifmap. (see netdevice(7))","Interface is in promiscuous mode. (see netdevice(7))","Resources allocated. (see netdevice(7))","Slave of a load balancing bundle. (see netdevice(7))","TAP device","TUN device (no Ethernet headers)","Interface is running. (see netdevice(7))","A network interface. Has a name like “eth0” or “…","Standard interface flags, used by getifaddrs","A list of the network interfaces available on this system. …","An iterator over the interfaces in an Interfaces.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Retrieve a list of the network interfaces available on the …","Resolve an interface into a interface number.","Obtain the index of this interface.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Iterate over the interfaces in this list.","Obtain the name of this interface.","","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Convert this to a slice of interfaces. Note that the …","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Error condition (only returned in PollFd::revents; ignored …","Hang up (only returned in PollFd::revents; ignored in …","There is data to read.","Invalid request: fd not open (only returned in …","Writing is now possible, though a write larger that the …","There is some exceptional condition on the file descriptor.","Priority band data can be read (generally unused on Linux).","Equivalent to POLLIN","Priority data may be written.","Equivalent to POLLOUT","This is a wrapper around libc::pollfd.","These flags define the different events that can be …","Returns if all the events of interest occured in the last …","Returns the set containing all flags.","Returns if any of the events of interest occured in the …","","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","The events of interest for this PollFd.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Creates a new PollFd specifying the events of interest for …","Returns the complement of this set of flags.","","poll waits for one of a set of file descriptors to become …","ppoll() allows an application to safely wait until either …","Removes the specified flags in-place.","Returns the events that occurred in the last call to poll …","Inserts or removes the specified flags depending on the …","Modify the events of interest for this PollFd.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Representation of a master with a forked pty","Representation of a master/slave pty pair","Representation of the Master device in a master/slave pty …","","","","","","","","","","","","","","","","","","","","","","","","","","","","Metadata about forked process","Create a new pseudoterminal, returning the master file …","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Grant access to a slave pseudoterminal (see grantpt(3))","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","The master port in a virtual pty pair","The master port in a virtual pty pair","Create a new pseudoterminal, returning the slave and …","Open a pseudoterminal device (see posix_openpt(3))","Get the name of the slave pseudoterminal (see ptsname(3))","Get the name of the slave pseudoterminal (see ptsname(3))","","","The slave port in a virtual pty pair","","","","","","","","","","","","","","","","Unlock a pseudoterminal master/slave pseudoterminal pair …","","","","","","","Unused since Linux 2.6.2","The calling process and the child process share the same …","The caller and the child process share the same filesystem","The new process shares an I/O context with the calling …","Create the process in a new cgroup namespace.","Create the process in a new IPC namespace.","Create the process in a new network namespace.","The cloned child is started in a new mount namespace.","Create the process in a new PID namespace.","Create the process in a new user namespace.","Create the process in a new UTS namespace.","The parent of the new child (as returned by getppid(2)) …","If the calling process is being traced, then trace the …","The calling process and the child process share the same …","The child and the calling process share a single list of …","The child is placed in the same thread group as the calling","A tracing process cannot force CLONE_PTRACE on this child …","The execution of the calling process is suspended until the","The calling process and the child process run in the same …","Type for the function executed by clone.","Options for use with clone","CpuSet represent a bit-mask of CPUs. CpuSets are used by …","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","clone create a child process (clone(2))","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Return the maximum number of CPU in CpuSet","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Test to see if a CPU is in the CpuSet. field is the CPU id …","Create a new and empty CpuSet.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","sched_getaffinity get a thread’s CPU affinity mask (…","Determines the CPU on which the calling thread is running.","sched_setaffinity set a thread’s CPU affinity mask (…","Explicitly yield the processor to other threads.","Inserts or removes the specified flags depending on the …","Add a CPU to CpuSet. field is the CPU id to add","reassociate thread with a namespace","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","Remove a CPU from CpuSet. field is the CPU id to remove","disassociate parts of the process execution context","POSIX Asynchronous I/O","","","Monitoring API for filesystem events.","Provide helpers for making ioctl system calls.","Interfaces for managing memory-backed files.","Memory management declarations.","Process execution domains","Low level threading primitives","","Set and configure disk quotas for users, groups, or …","Reboot/shutdown or enable/disable Ctrl-Alt-Delete.","Configure the process resource limits.","Portably monitor a group of file descriptors for readiness.","Send data from a file to a socket, bypassing userland.","Operating system signals.","Interface for the signalfd syscall.","Socket interface functions","","Get filesystem statistics, non-portably","Get filesystem statistics","","An interface for controlling asynchronous communication …","","Timer API via signals.","Timer API via file descriptors.","Vectored I/O","Get system identification","Wait for a process to change status","Methods common to all AIO operations","All of the requests have already finished","Return values for AioCb::cancel and aio_cancel_all","All outstanding requests were canceled","An asynchronous version of fsync(2).","Mode for AioCb::fsync. Controls whether only data or both …","Some requests were not canceled. Their status should be …","Asynchronously reads from a file descriptor into a buffer","Asynchronously writes from a buffer to a file descriptor","Requests that lio_listio return immediately","Requests that lio_listio block until all requested …","Mode for lio_listio","on supported operating systems only, do it like fdatasync","do it like fsync","The return type of Aio::aio_return.","Cancels outstanding AIO requests for a given file …","Retrieve return status of an asynchronous operation.","","","","Suspends the calling process until at least one of the …","","","","","","","","","","","","","","","","","","Cancels an outstanding AIO request.","","","","","","","","","","","","","","","Retrieve error status of an asynchronous operation.","","","","Returns the underlying file descriptor associated with the …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Does this operation currently have any in-kernel state?","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Submits multiple asynchronous I/O requests with a single …","Returns the operation’s fsync mode: data and metadata or …","Returns the requested length of the aio operation in bytes","Returns the requested length of the aio operation in bytes","Construct a new AioWrite.","Create a new AioRead, placing the data in a mutable slice.","Create a new AioFsync.","Returns the file offset of the operation.","Returns the file offset of the operation.","","","Returns the priority of the AioCb","","","","Update the notification settings for an existing AIO …","","","","Returns the SigEvent that will be used for notification.","","","","Actually start the I/O operation.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Configuration options for inotify_add_watch.","File was accessed.","All of the events.","Metadata changed.","Set the FD_CLOEXEC flag on the file descriptor.","Combination of IN_CLOSE_WRITE and IN_CLOSE_NOWRITE.","Nonwritable file was closed.","Writable file was closed.","Subfile was created.","Subfile was deleted.","Self was deleted.","Don’t follow symlinks.","File was ignored.","Event occurred against directory.","File was modified.","Combination of IN_MOVED_FROM and IN_MOVED_TO.","File was moved from X.","File was moved to Y.","Self was moved.","Set the O_NONBLOCK flag on the open file description …","Only send event once.","Only watch the path if it is a directory.","File was opened.","Event queue overflowed.","Backing filesystem was unmounted.","Configuration options for inotify_init1.","An inotify instance. This is also a file descriptor, you …","A single inotify event.","This object is returned when you create a new watch on an …","Adds a new watch on the target file or directory.","Returns the set containing all flags.","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","This cookie is a number that allows you to connect related …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","Initialize a new inotify instance.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Event mask. This field is a bitfield describing the exact …","Filename. This field exists only if the event was …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","Reads a collection of events from the inotify file …","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes an existing watch using the watch descriptor …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Watch descriptor. This field corresponds to the watch …","Generate an ioctl request code for a command that passes …","Generate an ioctl request code for a command that reads.","Generate an ioctl request code for a command that reads …","Generate an ioctl request code for a command that writes.","Allow sealing operations on this file.","Set the close-on-exec (FD_CLOEXEC) flag on the new file …","Options that change the behavior of memfd_create.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Creates an anonymous file that lives in memory, and return …","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Undo the effect of an earlier MADV_DONTDUMP.","Undo the effect of MADV_DONTFORK.","Exclude the given range from a core dump.","Do not make pages in this range available to the child …","Do not expect access in the near future.","Specify that the application no longer needs the pages in …","Enable Transparent Huge Pages (THP) for pages in the given …","Poison the given pages.","Enable Kernel Samepage Merging (KSM) for the given pages.","Undo the effect of MADV_HUGEPAGE.","No further special treatment. This is the default.","Expect random page references.","Free up a given range of pages and its associated backing …","Expect sequential page references.","Preserve the memory of each page but offline the original …","Undo the effect of MADV_MERGEABLE","Expect access in the near future.","Put the mapping into the first 2GB of the process address …","Synonym for MAP_ANONYMOUS.","The mapping is not backed by any file.","Compatibility flag. Ignored.","Compatibility flag. Ignored.","Compatibility flag. Ignored.","Place the mapping at exactly the address specified in addr.","Place the mapping at exactly the address specified in addr…","Used for stacks; indicates to the kernel that the mapping …","Allocate the mapping using “huge pages.”","Make use of 16GB huge page (must be supported by the …","Make use of 16MB huge page (must be supported by the …","Make use of 1GB huge page (must be supported by the system)","Make use of 1MB huge page (must be supported by the system)","Make use of 256MB huge page (must be supported by the …","Make use of 2GB huge page (must be supported by the system)","Make use of 2MB huge page (must be supported by the system)","Make use of 32MB huge page (must be supported by the …","Make use of 512KB huge page (must be supported by the …","Make use of 512MB huge page (must be supported by the …","Make use of 64KB huge page (must be supported by the …","Make use of 8MB huge page (must be supported by the system)","Mark the mmaped region to be locked in the same way as …","Only meaningful when used with MAP_POPULATE. Don’t …","Do not reserve swap space for this mapping.","Populate page tables for a mapping.","Create a private copy-on-write mapping. Mutually exclusive …","Share this mapping. Mutually exclusive with MAP_PRIVATE.","Region grows down, like a stack.","Lock pages that are currently mapped into the address …","Lock pages which will become mapped into the address space …","Place the mapping at exactly the address specified in …","Permit the kernel to relocate the mapping to a new virtual …","Options for mremap.","Schedule an update but return immediately.","Invalidate all cached data.","Perform an update and wait for it to complete.","Additional parameters for mmap.","Flags for mlockall.","Usage information for a range of memory to allow for …","Configuration flags for msync.","Pages can be executed","Apply protection up to the end of a mapping that grows …","Apply protection down to the beginning of a mapping that …","Pages cannot be accessed.","Pages can be read.","Pages can be written.","Desired memory protection of a memory mapping.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","give advice about use of memory","Locks all memory pages that contain part of the address …","Locks all memory pages mapped into this process’ address …","allocate memory, or map files or devices into memory","Set protection of memory mapping.","Expands (or shrinks) an existing memory mapping, …","synchronize a mapped region","Unlocks all memory pages that contain part of the address …","Unlocks all memory pages mapped into this process’ …","remove a mapping","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Creates and opens a new, or opens an existing, POSIX …","Performs the converse of shm_open, removing an object …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Provide the legacy virtual address space layout.","Limit the address space to 32 bits.","Use 0xc0000000 as the offset at which to search a virtual …","Disable address-space-layout randomization.","User-space function pointers to signal handlers point to …","Map page 0 as read-only.","Flags used and returned by get() and set().","PROT_READ implies PROT_EXEC for mmap(2).","No effects.","select(2), pselect(2), and ppoll(2) do not modify the …","Have uname(2) report a 2.6.40+ version number rather than …","No effects.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Retrieve the current process personality.","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Set the current process personality.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","Toggles the specified flags in-place.","","","","Returns the union of between the flags in self and other.","Identifies an individual thread.","Send a signal to a thread (see pthread_kill(3)).","Obtain ID of the calling thread (see pthread_self(3)","","Using the ptrace options the tracer can configure the …","Ptrace options used in conjunction with the …","","","","Event that stops before a return from clone.","Event that stops before a return from execve.","Event for a stop before an exit. Unlike the waitpid Exit …","Event that stops before a return from fork or clone.","Stop triggered by a seccomp rule on a tracee.","Stop triggered by the INTERRUPT syscall, or a group stop, …","Event that stops before a return from vfork or clone.","Event for a return from vfork.","","","","","","","","","","Send a SIGKILL to the tracee if the tracer exits. This is …","Stop tracee at next clone call and trace the cloned …","Stop tracee at next execve call.","Stop tracee at next exit call. Stops before exit commences …","Stop tracee at next fork and start tracing the forked …","Stop tracee when a SECCOMP_RET_TRACE rule is triggered. …","When delivering system call traps set a bit to allow …","Stop tracee at next vfork call and trace the vforked …","Stop tracee at vfork completion.","","","","","","","","","","","","","","","","","","","","Ptrace Request enum defining the action to be taken.","Returns the set containing all flags.","Attach to a running process, as with …","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Restart the stopped tracee process, as with …","Returns true if all of the flags in other are contained …","Detaches the current running process, as with …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Gets a ptrace event as described by …","Get user registers, as with ptrace(PTRACE_GETREGS, ...)","Get siginfo as with ptrace(PTRACE_GETSIGINFO,...)","","","","Inserts the specified flags in-place.","Stop a tracee, as with ptrace(PTRACE_INTERRUPT, ...)","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Issues a kill request as with ptrace(PTRACE_KILL, ...)","Returns the complement of this set of flags.","","","","Reads a word from a processes memory at the given address","Reads a word from a user area at offset. The user struct …","Removes the specified flags in-place.","Attach to a running process, as with …","Inserts or removes the specified flags depending on the …","Set options, as with ptrace(PTRACE_SETOPTIONS,...).","Set user registers, as with ptrace(PTRACE_SETREGS, ...)","Set siginfo as with ptrace(PTRACE_SETSIGINFO,...)","Move the stopped tracee process forward by a single step …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Continue execution until the next syscall, as with …","Continue execution until the next syscall, as with …","Move the stopped tracee process forward by a single step …","","","","Toggles the specified flags in-place.","Sets the process as traceable, as with …","","","","","","","","","","Returns the union of between the flags in self and other.","Writes a word into the processes memory at the given …","Writes a word to a user area at offset. The user struct …","Wrapper type for if_dqblk","Specify a group quota","Use the original quota format.","Use the standard VFS v0 quota format.","Use the VFS v1 quota format.","All fields.","The block hard & soft limit fields.","The disk use time limit field.","The inode hard & soft limit fields.","The current inodes field.","The file quote time limit field.","All block & inode limits.","The current space field.","The time limit fields.","The space & inodes usage fields.","The type of quota format to use.","The scope of the quota.","Indicates the quota fields that are valid to read from.","Specify a user quota","Returns the set containing all flags.","Current number of allocated inodes.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Time limit for excessive disk use.","The absolute limit on disk quota blocks allocated.","Preferred limit on disk quota blocks","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","","","Time limit for excessive files.","Maximum number of allocated inodes.","Preferred inode limit","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","Current occupied space (bytes).","","","","Get disk quota limits and current usage for the given …","Disable disk quotas for a block device.","Turn on disk quotas for a block device.","Configure quota values for the specified fields for a …","Update the on-disk copy of quota usages for a filesystem.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Set the time limit for excessive disk use.","Set the absolute limit on disk quota blocks allocated.","Set the preferred limit on disk quota blocks allocated.","Set the time limit for excessive files.","Set the maximum number of allocated inodes.","Set the preferred limit of allocated inodes.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Restart the system.","Halt the system.","Execute a kernel that has been loaded earlier with …","Stop the system and switch off power, if possible.","Suspend the system using software suspend.","How exactly should the system be rebooted.","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","Reboots or shuts down the system.","Enable or disable the reboot keystroke (Ctrl-Alt-Delete).","","","","","The maximum amount (in bytes) of virtual memory the …","The largest size (in bytes) core(5) file that may be …","The maximum amount of cpu time (in seconds) to be used by …","The maximum size (in bytes) of the data segment for a …","The largest size (in bytes) file that may be created.","A limit on the combined number of flock locks and fcntl …","The maximum size (in bytes) which a process may lock into …","A limit on the number of bytes that can be allocated for …","A ceiling to which the process’s nice value can be …","The maximum number of open files for this process.","The maximum number of simultaneous processes for this user …","When there is memory pressure and swap is available, …","A ceiling on the real-time priority that may be set for …","A limit (in microseconds) on the amount of CPU time that a …","A limit on the number of signals that may be queued for …","The maximum size (in bytes) of the stack segment for a …","","Resource usage for all the children that have terminated …","Resource usage for the current process.","Resource usage for the calling thread.","Types of process resources.","Output of getrusage with information about resource usage. …","Whose resource usage should be returned by getrusage.","","","Number of times a read was done from a block device.","Number of times a write was done to a block device.","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Number of times all of the memory was fully swapped out.","Get the current processes resource limits","Get usage information for a process, its children or the …","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Number of times a context switch was imposed by the kernel …","Number of IPC messages received.","Number of IPC messages sent.","Number of page faults that were served through I/O (i.e. …","The resident set size at its peak, in kilobytes.","Number of page faults that were served without resorting …","","","","Set the current processes resource limits","Integral value expressed in kilobytes times ticks of …","Number of signals received.","Total amount of time spent executing in kernel mode.","","","","","","","","","","","","","Integral value expressed in kilobytes times ticks of …","Integral value expressed in kilobytes times ticks of …","Total amount of time spent executing in user mode.","Number of times a context switch was voluntarily invoked.","","Contains a set of file descriptors used by select","Iterator over FdSet.","","","","","Remove all file descriptors from this FdSet.","","","Test an FdSet for the presence of a certain file …","","","Returns an iterator over the file descriptors in the set.","","","Returns the argument unchanged.","Returns the argument unchanged.","","Finds the highest file descriptor in the set.","Add a file descriptor to an FdSet","Calls U::from(self).","Calls U::from(self).","","Create an empty FdSet","","","Monitors file descriptors for readiness with an altered …","Remove a file descriptor from an FdSet","Monitors file descriptors for readiness","","","","","","","","","Copy up to count bytes to out_fd from in_fd starting at …","Copy up to count bytes to out_fd from in_fd starting at …","Use the given signal-catching function, which takes in the …","When catching a Signal::SIGCHLD signal, the signal will be …","When catching a Signal::SIGCHLD signal, the system will not","Further occurrences of the delivered signal are not masked …","The system will deliver the signal to the process on a …","The handler is reset back to the default at the moment the …","Requests that certain system calls restart if interrupted …","This flag is controlled internally by Nix.","Abort","Alarm clock","Bus error","To parent on child stop or exit","Continue a stopped process","Floating point exception","Hangup","Illegal instruction (not reset when caught)","Interrupt","Input/output possible signal","Alias for SIGABRT","Kill (cannot be caught or ignored)","Write on a pipe with no one to read it","Alias for SIGIO","Profiling time alarm","Power failure imminent.","Quit","Segmentation violation","Stack fault (obsolete)","Sendable stop signal not from tty","Bad system call","Software termination signal from kill","Trace trap (not reset when caught)","Stop signal from tty","To readers pgrp upon background tty read","Like TTIN if (tp->t_local&LTOSTOP)","Alias for SIGSYS","Urgent condition on IO channel","User defined signal 1","User defined signal 2","Virtual time alarm","Window size changes","Exceeded CPU time limit","Exceeded file size limit","The new mask is the union of the current mask and the …","The current mask is replaced by the specified set.","The new mask is the intersection of the current mask and …","Controls the behavior of a SigAction","Action to take on receipt of a signal. Corresponds to …","Use the given signal-catching function, which takes in the …","Default signal handling.","Used to request asynchronous notification of the …","A signal handler.","Request that the signal be ignored.","Specifies a set of Signals that may be blocked, waited …","Iterator for a SigSet.","No notification will be delivered","Specifies the notification method used by a SigEvent","Notify by delivering a signal to the process.","Notify by delivering a signal to a thread.","Specifies how certain functions should manipulate a signal …","Types of operating system signals","Iterate through all signals defined by this operating …","Add the specified signal to the set.","Returns the set containing all flags.","Initialize to include all signals.","Returns a mutable pointer to the sigevent wrapped by self","","","Returns name of signal.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","Remove all signals from this set.","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Return whether this set includes the specified signal.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Initialize to include nothing.","","","","","","","","","","","","Returns the flags set on the action.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","Converts a libc::sigset_t object to a SigSet without …","","Returns the action’s handler.","","","","","","","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns an iterator that yields the signals contained in …","Iterate through all signals defined by this OS","Send a signal to a process","Send a signal to a process group","Returns the set of signals that are blocked during …","Creates a new action.","Note: this constructor does not allow the user to set the …","","","Returns the complement of this set of flags.","","","","Manages the signal mask (set of blocked signals) for the …","Send a signal to the current thread","Removes the specified flags in-place.","Remove the specified signal from this set.","Inserts or removes the specified flags depending on the …","Changes the action taken by a process on receipt of a …","Return a copy of the inner structure","Signal management (see signal(3p))","Examine and change blocked signals.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Adds the set of signals to the signal mask for the calling …","Gets the currently blocked (masked) set of signals for the …","Sets the set of signals as the signal mask for the calling …","Sets the set of signals as the signal mask, and returns …","Removes the set of signals from the signal mask for the …","","","","","","","","","","","","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Identifies a thread for SigevNotify::SigevThreadId","Returns the union of between the flags in self and other.","Suspends execution of the calling thread until one of the …","","","Will be present in the si_value field of the …","Will be present in the si_value field of the …","Signal to deliver","Signal to send","LWP ID of the thread to notify","","","","","","","A helper struct for creating, reading and closing a …","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","","","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","","","","Creates a new file descriptor for reading signals.","","","","","","","","","","","","","","","","","","","","","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","","","","These constants specify the protocol family to be used in …","","Interface to kernel crypto API","Socket address for the Linux kernel crypto API","Set the length of associated authentication data (AAD) …","Set IV for AF_ALG crypto API.","Set crypto operation for AF_ALG crypto API. It may be one …","AppleTalk","","Access to raw ATM PVCs","Access to ATM Switched Virtual Circuits","Amateur radio AX.25 protocol","Bluetooth low-level socket protocol","Further receptions and transmissions will be disallowed.","Can’t be used for creating sockets; mostly used for …","Ericsson’s Communication CPU to Application CPU …","Controller Area Network automotive bus protocol","","A type-safe zero-copy wrapper around a single control …","A type-safe wrapper around a single control message, as …","Supports datagrams (connectionless, unreliable messages of …","DECet protocol sockets.","Acorn Econet protocol","Non-DIX type protocol number defined for the Ethernet IEEE …","Represents a socket option that can be retrieved.","InfiniBand native addressing","IEEE 802.15.4 WPAN (wireless personal area network) raw …","","IPv4 Internet protocols (see ip(7))","IPv6 Internet protocols (see ipv6(7))","","","","Request for multicast socket operations","","","","Configure the sending addressing and interface for v4","Socket error queue control messages read with the …","","Request for ipv6 multicast socket operations","","","Configure the sending addressing and interface for v6","Socket error queue control messages read with the …","IPX - Novell protocols","Socket interface over IrDA","New “modular ISDN” driver interface protocol","IUCV (inter-user communication vehicle) z/VM protocol for …","Key management protocol.","Datalink address (MAC)","Hardware Address","Logical link control (IEEE 802.2 LLC) protocol","Set the close-on-exec flag for the file descriptor …","Receive flags: Control Data was discarded (buffer too …","Enables nonblocking operation; if the operation would …","Terminates a record (when this notion is supported, as for …","This flag specifies that queued errors should be received …","Requests not to send SIGPIPE errors when the other end …","Sends or requests out-of-band data on sockets that support …","Peeks at an incoming message. The data is treated as …","For raw (Packet), Internet datagram (since Linux …","Receive operation blocks until the full amount of data can …","Multiprotocol Label Switching","Flags for send/recv and their relatives","Preallocated structures needed for recvmmsg and sendmmsg …","Iterator over results of recvmmsg/sendmmsg","Reserved for “802.2LLC project”; never used.","AX.25 packet layer protocol. (see netrom(4))","","Kernel user interface device (see netlink(7))","Address for the Linux kernel user interface device.","Auditing (ref)","Netlink interface to request information about ciphers …","DECnet routing messages (ref)","Access to FIB lookup from user space (ref)","Transport IPv6 packets from netfilter to user space. Used …","Open-iSCSI (ref)","Kernel messages to user space (ref)","Netfilter subsystem (ref)","Infiniband RDMA (ref)","Receives routing and link updates and may be used to …","SCSI Transports (ref)","SELinux event notifications. (ref)","Query information about sockets of various protocol …","Reserved for user-mode socket protocols (ref)","Near field communication","Low level packet interface (see packet(7))","Nokia cellular modem IPC/RPC interface","Generic PPP transport layer, for setting up L2 tunnels …","Provides raw network protocol access.","Raw sockets (raw(7))","Provides a reliable datagram layer that does not guarantee …","Reliable Datagram Sockets (RDS) protocol","Further receptions will be disallowed.","Contains outcome of sending or receiving a message","RATS (Radio Amateur Telecommunications Society) Open …","Rx, Andrew File System remote procedure call protocol","SO_RXQ_OVFL indicates that an unsigned 32 bit value …","SO_RXQ_OVFL indicates that an unsigned 32 bit value …","Set close-on-exec on the new descriptor","Set non-blocking mode on the new socket","Report hardware timestamps as generated by …","Collect receiving timestamps as reported by hardware","Collect receiving timestamps as reported by software","Report any software timestamps when available.","Collect transmiting timestamps as reported by hardware","Collect transmiting timestamps as reported by software","Received version of ControlMessage::ScmCredentials","A message of type SCM_CREDENTIALS, containing the pid, uid …","Received version of ControlMessage::ScmRights","A message of type SCM_RIGHTS, containing an array of file …","A message of type SCM_TIMESTAMP, containing the time the …","Nanoseconds resolution timestamp","A set of nanosecond resolution timestamps","This was a short-lived (between Linux 2.1.30 and …","Provides a sequenced, reliable, two-way connection- based …","Represents a socket option that can be set.","","IBM SNA","Represents a socket address","Additional socket options","Constants used in socket and socketpair to specify the …","These constants are used to specify the communication …","An IPv4 socket address","An IPv6 socket address","Anything that, in C, can be cast back and forth to sockaddr…","A container for any sockaddr type","Provides sequenced, reliable, two-way, connection- based …","TCP protocol (ip(7))","Configuration flags for SO_TIMESTAMPING interface","For representing packet timestamps via SO_TIMESTAMPING …","TIPC, “cluster domain sockets” protocol","Configure the transmission time of packets.","UDP protocol (ip(7))","UDP Generic Receive Offload (GRO) allows receiving …","UDP GSO makes it possible for applications to generate …","","Local communication (see unix(7))","A wrapper around sockaddr_un.","Unix credentials of the sending process.","Unspecified address family, (see getaddrinfo(3))","","","","","","","","VMWare VSockets protocol for hypervisor-guest interaction.","Socket address for VMWare VSockets protocol","Legacy protocol for wide area network (WAN) connectivity …","Further transmissions will be disallowed.","ITU-T X.25 / ISO-8208 protocol (see x25(7))","Accept a connection on a socket","Accept a connection on a socket","Physical-layer address (MAC)","","Return the socket’s cipher name, for example sha1.","Return the socket’s cipher type, for example hash or aead…","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","","If this address represents an abstract socket, return its …","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Conversion from nix’s SockAddr type to the underlying …","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Returns a mutable pointer to the raw sockaddr_un struct","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Returns a raw pointer to the inner structure. Useful for …","Returns a raw pointer to the inner structure. Useful for …","Returns a pointer to the raw sockaddr_un struct","","","","","","","","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Safely and falliably downcast to a mutable reference","Downcast to an immutable [UnixAddr] reference.","Downcast to a mutable [UnixAddr] reference.","Safely and falliably downcast to an immutable reference","Safely and falliably downcast to a mutable reference","Bind a name to a socket","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Context Identifier (CID)","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Iterate over the valid control messages pointed to by this …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Initiate a connection on a socket","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the address family of this socket","Return the address family of this socket","","","Returns the flow information associated with this address.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Create a new AddressFamily from an integer value retrieved …","","","","Unsafe constructor from a variable length source","","","","","","","","","","","","","","","","Look up the value of this socket option on the given …","Get the address of the peer connected to the socket fd.","Get the current address to which the socket fd is bound.","Get the current value for the requested socket option","Returns the group identifier","Return the socket’s multicast groups mask","Length of MAC address","","","","","","","","","","","","","","","","","","","","","","","","","","","ARP hardware type","hardware based timestamp","legacy timestamp, usually empty","Interface number","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Iterate over the filled io slices pointed by this msghdr","Returns the IP address associated with this socket address.","Returns the IP address associated with this socket …","Gets the IP address associated with this socket address.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Check if this address is an “unnamed” unix socket …","Return the length of valid data in the sockaddr structure.","Return the length of valid data in the sockaddr structure.","","","Listen for connections on a socket","","","","","","","","","Create a new sockaddr_un representing a filesystem path.","Construct a new socket address from its port ID and …","Construct an AF_ALG socket from its cipher name and type.","Construct a VsockAddr from its raw fields.","Instantiate a new IpMembershipRequest","Instantiate a new Ipv6MembershipRequest","Creates a new instance with the credentials of the current …","Creates a new socket address from IPv4 octets and a port …","","","","Create a new sockaddr_un representing an address in the “…","","","","","Create a new sockaddr_un representing an “unnamed” …","Create a new IpAddr that contains an IPv4 address.","Create a new IpAddr that contains an IPv6 address.","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","","If this address represents a filesystem path, return that …","Returns the addrlen of this socket - …","Return the socket’s port ID.","Returns the process identifier","Packet type","Port number","Returns the port number associated with this socket …","Returns the port number associated with this socket …","Gets the port number associated with this socket address","Preallocate structure used by recvmmsg and sendmmsg takes …","Physical-layer protocol","Receive data from a connection-oriented socket. Returns …","Receive data from a connectionless or connection-oriented …","An extension of recvmsg that allows the caller to receive …","Receive message in scatter-gather vectors from a socket, …","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","","","","Returns the scope ID associated with this address.","Return the eight 16-bit segments that make up this address","Send data to a connection-oriented socket. Returns the …","An extension of sendmsg that allows the caller to transmit …","Send data in scatter-gather vectors to a socket, possibly …","Send a message to a socket","Set the value of this socket option on the given socket.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Sets the value for the requested socket option","Shut down part of a full-duplex connection.","","","","","","","","","","Return the available space in the structure","Return the available space in the structure","","","","","","Return the appropriate SockAddr type from a …","","Create an endpoint for communication","Create a pair of connected sockets","Socket options as used by setsockopt and getsockopt.","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","","","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","software based timestamp, usually one containing data","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the user identifier","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a value indicating whether or not this socket has …","","","Bind this socket to a particular device like “eth0”.","Set or get the broadcast flag.","Set or get the don’t route flag.","","Join a multicast group","Leave a multicast group.","If enabled, this boolean option allows binding to an IP …","Fetch the current system-estimated Path MTU.","Set or read a boolean integer argument that determines …","Set or read the time-to-live value of outgoing multicast …","Set or receive the Type-Of-Service (TOS) field that is …","Setting this boolean option enables transparent proxying …","The recvmsg(2) call will return the destination IP address …","Pass an IP_PKTINFO ancillary message that contains a …","Enable extended reliable error message passing.","Set or retrieve the current time-to-live field that is …","Join an IPv6 multicast group.","Set “don’t fragment packet” flag on the IPv6 packet.","Leave an IPv6 multicast group.","The recvmsg(2) call will return the destination IP address …","Control receiving of asynchronous error options.","Set delivery of the IPV6_PKTINFO control message on …","Traffic class associated with outgoing packets","Set the unicast hop limit for the socket.","The socket is restricted to sending and receiving IPv6 …","Enable sending of keep-alive messages on …","When enabled, a close(2) or shutdown(2) will not return …","Set the mark for each packet sent through this socket …","If this option is enabled, out-of-band data is directly …","","Enable or disable the receiving of the SCM_CREDENTIALS …","Return the credentials of the foreign process connected to …","Set the protocol-defined priority for all packets to be …","Sets or gets the maximum socket receive buffer in bytes.","Using this socket option, a privileged (CAP_NET_ADMIN) …","Specify the receiving timeout until reporting an error.","Enable or disable the receiving of the SO_TIMESTAMP …","Enable or disable the receiving of the SO_TIMESTAMPNS …","Enables local address reuse","Permits multiple AF_INET or AF_INET6 sockets to be bound …","Indicates that an unsigned 32-bit value ancillary message …","Specify the sending timeout until reporting an error.","Sets or gets the maximum socket send buffer in bytes.","Using this socket option, a privileged (CAP_NET_ADMIN) …","Gets the socket type as an integer.","Get and clear the pending socket error.","This option allows the caller to set the TCP congestion …","The maximum number of keepalive probes TCP should send …","The time (in seconds) the connection needs to remain idle …","The time (in seconds) between individual keepalive probes.","The maximum segment size for outgoing TCP packets.","Under most circumstances, TCP sends data when it is …","","Specifies the maximum amount of time in milliseconds that …","Specifies exact type of timestamping information collected …","Configures the behavior of time-based transmission of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Flags for fchmodat function.","","","","","“File mode / permissions” flags.","","","“File type” flags for mknod and related functions.","","","","","","","","","Read fr group.","Read for other.","Read for owner.","Read write and execute for group.","Read, write and execute for other.","Read, write and execute for owner.","Set group id on execution.","Set user id on execution.","","Write for group.","Write for other.","Write for owner.","Execute for group.","Execute for other.","Execute for owner.","Flags for utimensat function.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","Change the file permission bits of the file specified by a …","Change the file permission bits.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","Change the access and modification times of the file …","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","","Change the access and modification times of a file without …","","","","","Create a special or ordinary file, by pathname.","Create a special or ordinary file, relative to a given …","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","","","","","","","","","","","","","","","","","","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Change the access and modification times of a file.","Change the access and modification times of a file.","","","","","","","","","","","","","","","","","","","","","","Describes the file system type as known by the operating …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Describes a mounted file system","","","","","","","Size of a block","Total data blocks in filesystem","Free blocks available to unprivileged user","Free blocks in filesystem","","","","","","","","","","Total file nodes in filesystem","Free file nodes in filesystem","Filesystem ID","Magic code defining system type","Get the mount flags","","","Returns the argument unchanged.","Returns the argument unchanged.","Identifies a mounted file system","Describes a mounted file system.","Calls U::from(self).","Calls U::from(self).","Maximum length of filenames","Optimal transfer block size","Describes a mounted file system.","","","","","","","","","File system mount Flags","Append-only file","Immutable file","Allow mandatory locks on the filesystem","Do not update access times on files","Do not interpret character or block-special devices","Do not update access times on files","Do not allow execution of binaries on the filesystem","Do not allow the set-uid bits to have an effect","Read Only","Update access time relative to modify/change time","All IO should be done synchronously","Write on file/directory/symlink","Wrapper around the POSIX statvfs struct","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","get the file system block size","Get the number of blocks.","Get the number of free blocks for unprivileged users","Get the number of free blocks in the file system","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","Get the total number of file inodes","Get the number of free file inodes for unprivileged users","Get the number of free file inodes","Get the file system id","Get the mount flags","","","","","","","Get the fundamental file system block size","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Return a Statvfs object with information about fd","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Get the maximum filename length","Returns the complement of this set of flags.","","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Return a Statvfs object with information about the path","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","Toggles the specified flags in-place.","","","","","","","Returns the union of between the flags in self and other.","System info structure returned by sysinfo.","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","Returns the load average tuple.","Current number of processes.","Returns the total amount of installed RAM in Bytes.","Returns the amount of completely unused RAM in Bytes.","Returns the amount of unused swap memory in Bytes.","Returns the amount of swap memory in Bytes.","Returns system information.","","","","","Returns the time since system boot.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Baud rates supported by the system.","","","","","","","","","","","","","","","","","","","Flags for setting the control mode of a terminal","","","","","","","","","","","","","Specify how transmission flow should be altered","Specify a combination of the input and output buffers to …","","","","","","","","","","","","","","","","","Flags for configuring the input mode of a terminal","Flags for setting any local modes","","","","","","","","","","","","","","Flags for configuring the output mode of a terminal","","","","","Specify when a port configuration change should occur.","Indices into the termios.c_cc array for special characters.","","","","","","Flush data that was received but not read","Transmit a STOP character, which should disable a …","Flush both received data not read and written data not …","Transmit a START character, which should re-enable a …","Flush data written but not transmitted","Suspend transmission","Resume transmission","The change occurs after all output has been written","Same as TCSADRAIN, but will also flush the input buffer","The change will occur immediately","","Stores settings for the termios API","","","","","","","","","","","","","","","","","","","","","","","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","Get input baud rate (see cfgetispeed(3p)).","Get output baud rate (see cfgetospeed(3p)).","Configures the port to something like the “raw” mode …","Set input baud rate (see cfsetispeed(3p)).","Set output baud rate (see cfsetospeed(3p)).","Set both the input and output baud rates (see termios(3)).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Control characters (see termios.c_cc documentation)","Control mode flags (see termios.c_cflag documentation)","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","","","","","","","","","","","Input mode flags (see termios.c_iflag documentation)","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Line discipline (see termios.c_line documentation)","Local mode flags (see termios.c_lflag documentation)","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Output mode flags (see termios.c_oflag documentation)","","","","","","","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","Block until all output data is written (see tcdrain(3p)).","Suspend or resume the transmission or reception of data …","Discard data in the output or input queue (see tcflush(3p)…","Return the configuration of a port tcgetattr(3p)).","Get the session controlled by the given terminal (see …","Send a break for a specific duration (see tcsendbreak(3p)).","Set the configuration for a terminal (see tcsetattr(3p)).","","","","","","","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","Calls U::from(self).","Calls U::from(self).","","Makes a new TimeSpec with given number of microseconds.","Makes a new TimeVal with given number of microseconds.","","","","","","","","Makes a new TimeSpec with given number of nanoseconds.","Makes a new TimeVal with given number of nanoseconds. …","","","Construct a new TimeSpec from its components","Construct a new TimeVal from its components","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","An enumeration allowing the definition of the expiration …","Alarm will trigger every specified interval of time.","Alarm will trigger after a specified delay and then every …","Alarm will trigger once after the time given in TimeSpec","A Unix signal per-process timer.","Flags that are used for arming the timer.","","","","","Returns the argument unchanged.","Get the parameters for the alarm currently set, if any.","Calls U::from(self).","Creates a new timer based on the clock defined by clockid. …","Return the number of timers that have overrun","Set a new alarm on the timer.","","","","","","","","Like CLOCK_MONOTONIC, except that CLOCK_BOOTTIME includes …","Like CLOCK_BOOTTIME, but will wake the system if it is …","A non-settable monotonically increasing clock.","A settable system-wide real-time clock.","Like CLOCK_REALTIME, but will wake the system if it is …","The type of the clock used to mark the progress of the …","An enumeration allowing the definition of the expiration …","Alarm will trigger every specified interval of time.","Alarm will trigger after a specified delay and then every …","Alarm will trigger once after the time given in TimeSpec","Set the FD_CLOEXEC flag on the file descriptor.","Set the O_NONBLOCK flag on the open file description …","","A timerfd instance. This is also a file descriptor, you …","Additional flags to change the behaviour of the file …","Flags that are used for arming the timer.","Returns the set containing all flags.","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Returns the union of the two sets of flags.","Adds the set of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Returns the left flags, but with all the right flags …","Toggles the set of flags.","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns the difference between the flags in self and other.","","Returns an empty set of flags.","Returns an empty set of flags.","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Convert from underlying bit representation, preserving all …","","","","Get the parameters for the alarm currently set, if any.","","","","Inserts the specified flags in-place.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns true if no flags are currently stored.","Creates a new timer based on the clock defined by clockid. …","Returns the complement of this set of flags.","Returns the complement of this set of flags.","","","","Removes the specified flags in-place.","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Sets a new alarm on the timer.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","Returns the symmetric difference between the flags in self …","","","","","Toggles the specified flags in-place.","Toggles the specified flags in-place.","","","","","","","","","","","","","","","","Returns the union of between the flags in self and other.","Returns the union of between the flags in self and other.","Remove the alarm if any is set.","Wait for the configured alarm to expire.","","","","","A vector of buffers.","A slice of memory in a remote process, starting at address …","View the IoVec as a Rust slice.","The starting address of this slice (iov_base).","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Create an IoVec from a mutable Rust slice.","Create an IoVec from a Rust slice.","","","Calls U::from(self).","Calls U::from(self).","The number of bytes in this slice (iov_len).","Low-level read from a file, with specified offset.","Read from fd at offset filling buffers in iov.","Read data directly from another process’s virtual memory …","Write data directly to another process’s virtual memory …","Low-level write to a file, with specified offset.","Write to fd at offset from buffers in iov.","Low-level vectored read from a raw file descriptor","","","","","","","","","Low-level vectored write to a raw file descriptor","Describes the running system. Return type of uname.","","","","","NIS or YP domain name of this machine.","","","Returns the argument unchanged.","","Calls U::from(self).","Machine hardware platform.","Network name of this machine.","Release level of the operating system.","Name of the operating system implementation.","","","","","Get system identification","Version level of the operating system.","Wait for any child","The process was previously stopped but has resumed …","The process exited normally (as with exit() or returning …","The ID argument for waitid","Wait for the child whose process group ID matches the …","Wait for the child referred to by the given PID file …","Wait for the child whose process ID matches the given PID","The traced process was stopped by a PTRACE_EVENT_* event. …","The traced process was stopped by execution of a system …","The process was killed by the given signal. The third field","There are currently no state changes to report in any …","The process is alive, but was stopped by the given signal. …","Report the status of selected processes that have …","Report the status of selected processes which have …","Do not block when there are no processes wishing to report …","Don’t reap, just poll status.","An alias for WUNTRACED.","Report the status of selected processes which are stopped …","Controls the behavior of waitpid.","Possible return values from wait() or waitpid().","Wait on all children, regardless of type","Wait for “clone” children only.","Don’t wait on children of other threads in this group","Returns the set containing all flags.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","Convert a raw wstatus as returned by waitpid/wait into a …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","Returns the complement of this set of flags.","","","Extracts the PID from the WaitStatus unless it equals …","Removes the specified flags in-place.","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","","Toggles the specified flags in-place.","","","","","","","","","","Returns the union of between the flags in self and other.","Wait for any child process to change status or a signal is …","Wait for a process to change status","Wait for a process to change status","","","","","","","","","","","","","","","","","","","","","","","","","","","Clock identifier","Gets the raw clockid_t wrapped by self","","","Get the clock id of the specified process id, (see …","Get the resolution of the specified clock, (see …","Get the time of the specified clock, (see clock_gettime(2)…","Set the time of the specified clock, (see clock_settime(2)…","","","","","","","","Returns the argument unchanged.","Creates ClockId from raw clockid_t","","Calls U::from(self).","Returns the current time on the clock id","","Returns ClockId of a pid CPU-time clock","Returns resolution of the clock id","Sets time to timespec on the clock id","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","Maximum number of I/O operations in a single list I/O call …","Maximum number of outstanding asynchronous I/O operations …","The maximum amount by which a process can decrease its …","Maximum length of argument to the exec functions including …","Maximum number of functions that may be registered with …","Options for access()","Maximum obase values allowed by the bc utility.","Maximum number of elements permitted in an array by the bc …","Maximum scale value allowed by the bc utility.","Maximum length of a string constant accepted by the bc …","Maximum number of simultaneous processes per real user ID.","","Maximum number of weights that can be assigned to an entry …","","Maximum number of timer expiration overruns.","Maximum number of expressions that can be nested within …","Minimum number of bits needed to represent, as a signed …","Test for existence of file.","Flags for fchownat function.","","Represents the successful result of calling fork","Initial size of getgrgid_r and getgrnam_r data buffers","Initial size of getpwuid_r and getpwnam_r data buffers","Group identifier","Representation of a Group, based on libc::group","Maximum length of a host name (not including the …","Maximum number of iovec structures that one process has …","Unless otherwise noted, the maximum length, in bytes, of a …","Maximum number of links to a single file.","Maximum length of a login name.","Flags for linkat function.","Maximum number of bytes in a terminal canonical input line.","Minimum number of bytes for which space is available in a …","The maximum number of open message queue descriptors a …","The maximum number of message priorities supported by the …","Maximum number of bytes in a filename (not including the …","Maximum number of simultaneous supplementary group IDs per …","","","","A value one greater than the maximum value that the system …","The size of a system page in bytes.","Maximum number of bytes the implementation will store as a …","Maximum number of bytes that is guaranteed to be atomic …","Symbolic links can be created.","Minimum number of bytes of storage actually allocated for …","Recommended increment for file transfer sizes between the …","Maximum recommended file transfer size.","Minimum recommended file transfer size.","Recommended file transfer buffer alignment.","","","","","","Variable names for pathconf","Process identifier","","Constant for UID = 0","","Test for read permission.","","Real, effective and saved group IDs.","Real, effective and saved user IDs.","","","","","Maximum number of bytes in a symbolic link.","","Specify an offset relative to the current file location.","Specify an offset relative to the next location in the …","Specify an offset relative to the end of the file.","Specify an offset relative to the next hole in the file …","Specify an offset relative to the start of the file.","","Variable names for sysconf","","","","User identifier","Flags for unlinkat function.","Representation of a User, based on libc::passwd","Test for write permission.","Directive that tells lseek and lseek64 what the offset is …","Test for execute (search) permission.","The number of currently available pages of physical memory.","The number of processors configured.","The number of processors currently online (available).","The number of pages of physical memory. Note that it is …","The implementation supports the Terminal Characteristics …","The implementation supports the C-Language Binding option.","The implementation supports the C-Language Development …","The implementation supports the FORTRAN Development …","The implementation supports the FORTRAN Runtime Utilities …","The implementation supports the creation of locales by the …","The implementation supports the Batch Environment Services …","The implementation supports the Batch Accounting option.","The implementation supports the Batch Checkpoint/Restart …","The implementation supports the Locate Batch Job Request …","The implementation supports the Batch Job Message Request …","The implementation supports the Track Batch Job Request …","The implementation supports the Software Development …","The implementation supports the User Portability Utilities …","Integer value indicating version of the Shell and …","The implementation supports the Advisory Information …","The implementation supports asynchronous input and output.","Asynchronous input or output operations may be performed …","The implementation supports barriers.","The use of chown and fchown is restricted to a process with","The implementation supports clock selection.","The implementation supports the Process CPU-Time Clocks …","The implementation supports the File Synchronization …","The implementation supports the IPv6 option.","The implementation supports job control.","The implementation supports memory mapped Files.","The implementation supports the Process Memory Locking …","The implementation supports the Range Memory Locking …","The implementation supports memory protection.","The implementation supports the Message Passing option.","The implementation supports the Monotonic Clock option.","Pathname components longer than {NAME_MAX} generate an …","The implementation supports the Prioritized Input and …","The implementation supports the Process Scheduling option.","Prioritized input or output operations may be performed …","The implementation supports the Raw Sockets option.","The implementation supports read-write locks.","The implementation supports realtime signals.","The implementation supports the Regular Expression …","Each process has a saved set-user-ID and a saved …","The implementation supports semaphores.","The implementation supports the Shared Memory Objects …","The implementation supports the POSIX shell.","The implementation supports the Spawn option.","The implementation supports spin locks.","The implementation supports the Process Sporadic Server …","","The implementation supports the Synchronized Input and …","Synchronized input or output operations may be performed …","The implementation supports threads.","The implementation supports the Thread Stack Address …","The implementation supports the Thread Stack Size …","The implementation supports the Thread CPU-Time Clocks …","The implementation supports the Thread Execution …","The implementation supports the Non-Robust Mutex Priority …","The implementation supports the Non-Robust Mutex Priority …","The implementation supports the Thread Process-Shared …","The implementation supports the Robust Mutex Priority …","The implementation supports the Robust Mutex Priority …","The implementation supports thread-safe functions.","The implementation supports the Thread Sporadic Server …","The implementation supports timeouts.","The implementation supports timers.","The implementation supports the Trace option.","The implementation supports the Trace Event Filter option.","","The implementation supports the Trace Inherit option.","The implementation supports the Trace Log option.","","","","The implementation supports the Typed Memory Objects …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","The implementation provides a C-language compilation …","This symbol shall be defined to be the value of a …","Integer value indicating version of this standard …","The implementation supports the X/Open Encryption Option …","The implementation supports the Issue 4, Version 2 Enhanced","","The implementation supports the X/Open Realtime Option …","The implementation supports the X/Open Realtime Threads …","The implementation supports the Issue 4, Version 2 Shared …","The implementation supports the XSI STREAMS Option Group.","The implementation supports the XSI option","Integer value indicating version of the X/Open Portability …","Checks the file named by path for accessibility according …","","Alarm signal scheduling.","Returns the set containing all flags.","Get the raw uid_t wrapped by self.","Get the raw gid_t wrapped by self.","Get the raw pid_t wrapped by self.","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Change the current working directory of the calling …","Change the ownership of the file at path to be owned by …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Close a raw file descriptor","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","Returns Uid of calling process. This is practically a more …","Returns Gid of calling process. This is practically a more …","Daemonize this process by detaching from the controlling …","Returns the difference between the flags in self and other.","Home directory","Create a copy of the specified file descriptor (see dup(2)…","Create a copy of the specified file descriptor using the …","Create a new copy of the specified file descriptor using …","Checks the file named by path for accessibility according …","Returns effective Uid of calling process. This is …","Returns effective Gid of calling process. This is …","","","Returns an empty set of flags.","","","","","","","","","","","Replace the current process image with a new one (see …","Replace the current process image with a new one (see …","Execute program relative to a directory file descriptor …","Replace the current process image with a new one and …","Replace the current process image with a new one and …","","Checks the file named by path for accessibility according …","Change the current working directory of the process to the …","Change the ownership of the file referred to by the open …","Change the ownership of the file at path to be owned by …","Synchronize the data of a file","Replace the current process image with a new one (see …","","","","","","","","","","","","","","","","","","","","","","","Create a new child process duplicating the parent process (…","Like pathconf, but works with file descriptors instead of …","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","Get a group by GID.","","Get a user by name.","Get a group by name.","Creates Uid from raw uid_t.","Creates Gid from raw gid_t.","Creates Pid from raw pid_t.","Get a user by UID.","Synchronize changes to a file","Truncate a file to a specified length","User information","Returns the current directory as a PathBuf","Get the effective group ID","Get the effective user ID","Get the real group ID","Calculate the supplementary group access list.","Get the list of supplementary group IDs of the calling …","Get the host name and store it in an internally allocated …","","Get the group id of the calling process (see getpgrp(3)).","Get the pid of this process (see getpid(2)).","Get the pid of this processes’ parent (see getpid(2)).","Gets the real, effective, and saved group IDs.","Gets the real, effective, and saved user IDs.","Get the process group ID of a session leader getsid(2).","Get the caller’s thread ID (see gettid(2).","Get a real user ID","Group ID","Group ID","","","","","","","Initialize the supplementary group access list.","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if all flags are currently set.","Return true if this is the child process of the fork()","Returns true if no flags are currently stored.","Returns true if this is the parent process of the fork()","Returns true if the Uid represents privileged user - root. …","","Link one file to another file","Move the read/write file offset.","","List of Group members","Creates new directory path with access rights mode. (see …","Creates new fifo special file (named pipe) with path path …","Creates new fifo special file (named pipe) with path path …","Creates a regular file which persists even after process …","Username","Group name","Returns the complement of this set of flags.","Returns PID of parent of calling process","","","User password (probably hashed)","Group password","Get path-dependent configurable system variables (see …","Suspend the thread until a signal is received.","Create an interprocess channel.","Like pipe, but allows setting certain file descriptor …","","Read from a raw file descriptor.","","","Removes the specified flags in-place.","","","Inserts or removes the specified flags depending on the …","Set the effective group ID","Set the effective user ID","Set the group identity used for filesystem checks …","Set the user identity used for filesystem checks …","Set the group ID","Set the list of supplementary group IDs for the calling …","Set the system host name (see sethostname(2)).","Set a process group ID (see setpgid(2)).","Sets the real, effective, and saved gid. (see setresuid(2))","Sets the real, effective, and saved uid. (see setresuid(2))","Create new session and set process group id (see setsid(2)…","Set the user ID","Path to shell","Suspend execution for an interval of time","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Creates a symbolic link at path2 which points to path1.","Returns the symmetric difference between the flags in self …","Commit filesystem caches to disk","Commit filesystem caches containing file referred to by …","Get configurable system variables (see sysconf(3))","Get the terminal foreground process group (see tcgetpgrp(3)…","Set the terminal foreground process group (see tcgetpgrp(3)…","Returns PID of calling process","","","","","","","","","","","","","","","","","","","Toggles the specified flags in-place.","Truncate a file to a specified length","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the name of the terminal device that is open on file …","","","","","","","","","","","","","","","","User ID","Returns the union of between the flags in self and other.","Remove a directory entry","Remove a directory entry","Write to a raw file descriptor.","","Disable process accounting","Enable process accounting","Cancel an previously set alarm signal.","Schedule an alarm signal."],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,341,0,341,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,341,8,8,0,8,0,8,8,0,0,8,8,0,4,6,4,9,6,7,8,4,9,6,7,8,7,8,7,8,4,9,4,9,6,7,8,7,7,4,9,6,7,8,4,4,9,6,7,8,4,4,9,6,7,8,7,4,9,6,7,8,4,9,6,4,9,6,4,4,7,8,4,9,6,7,8,4,9,6,7,8,4,9,6,7,8,0,20,20,0,20,20,20,20,20,20,20,20,20,20,20,20,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,0,0,23,23,23,23,23,23,23,23,0,23,23,23,0,23,23,23,23,23,26,23,23,23,23,23,23,29,29,29,29,29,0,34,34,34,34,34,34,32,42,42,42,42,42,42,42,42,42,42,42,31,31,31,31,42,42,42,42,42,0,0,0,0,38,38,38,38,0,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,39,39,39,39,39,39,0,30,30,30,0,33,33,33,33,0,0,38,38,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,38,33,34,39,29,16,30,31,32,38,33,34,39,29,16,30,31,32,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,0,0,0,29,29,29,29,29,16,16,16,16,16,30,30,30,30,30,31,31,31,31,31,32,32,32,32,32,42,38,33,33,33,33,33,34,34,34,34,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,0,29,16,30,31,32,33,34,39,0,0,0,0,29,16,30,31,32,33,34,0,0,29,16,30,31,32,33,34,0,29,16,30,31,32,33,34,29,16,30,31,32,33,34,29,16,30,31,32,33,34,0,29,16,30,31,32,38,33,34,39,29,16,30,31,32,33,34,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,42,38,33,34,39,29,16,30,31,32,33,34,0,342,343,344,345,346,347,348,349,350,351,352,353,0,0,0,46,46,47,46,47,46,46,46,46,47,46,47,46,46,47,46,47,0,46,47,46,46,47,47,46,47,46,46,47,46,47,46,47,0,48,48,0,49,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,0,48,49,48,49,48,49,48,49,0,48,48,48,48,48,49,49,49,49,49,48,49,48,49,48,49,48,49,48,49,48,49,0,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,48,49,51,51,51,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,50,50,50,50,51,51,51,51,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,0,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,50,51,0,0,50,51,0,0,0,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,54,56,53,54,56,53,54,53,54,53,53,53,54,53,53,53,54,53,54,53,53,53,53,53,54,56,53,54,56,53,53,53,53,53,54,53,53,53,53,54,56,53,53,54,0,0,0,0,0,0,0,0,0,0,54,54,53,53,53,53,53,53,53,53,54,53,53,54,56,53,54,56,53,54,56,53,0,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,0,0,0,0,57,57,57,57,57,57,57,57,59,58,57,60,59,58,57,60,57,57,57,57,57,57,58,57,57,57,59,58,57,57,57,57,57,60,59,58,57,60,57,57,57,57,57,0,0,59,57,57,57,59,58,57,60,58,60,57,57,58,59,60,57,57,57,57,57,57,57,57,58,57,59,58,57,60,59,58,57,60,59,58,57,60,57,62,62,62,62,62,62,62,62,62,62,0,0,61,62,61,61,62,62,62,62,62,62,62,61,62,61,62,61,62,61,62,62,62,62,62,62,61,62,61,62,61,62,62,62,62,62,61,62,62,62,62,62,61,62,62,62,62,61,62,62,62,61,62,62,0,0,62,61,62,61,62,62,62,61,62,62,61,62,61,62,61,62,62,0,0,0,0,0,66,67,68,69,66,67,68,69,66,67,68,69,67,68,69,66,67,68,66,66,66,67,68,69,66,69,0,75,67,68,69,66,0,67,68,66,67,68,69,66,66,68,69,0,0,0,0,66,66,68,67,68,69,67,68,69,66,67,68,69,66,67,68,69,66,0,66,66,67,67,67,67,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,76,76,76,76,76,76,76,76,76,78,76,78,0,76,78,76,78,76,76,76,78,78,76,76,76,78,76,76,76,76,76,76,78,76,78,76,76,76,76,76,78,76,76,76,76,78,76,76,78,78,76,76,76,0,0,0,0,76,78,0,76,76,76,76,78,76,76,78,76,78,76,78,76,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,0,79,0,0,79,0,0,85,85,0,86,86,354,0,354,81,82,83,0,81,82,81,82,83,81,82,83,79,85,86,81,82,83,79,85,86,354,81,82,83,79,85,86,79,85,86,85,86,79,85,86,354,81,82,83,354,81,82,83,81,82,83,79,85,86,81,82,83,79,85,86,79,85,86,354,81,82,83,81,82,83,79,85,86,0,83,81,82,81,82,83,81,82,85,86,354,81,82,83,354,81,82,83,354,81,82,83,354,81,82,83,79,85,86,81,82,83,79,85,86,86,81,82,83,79,85,86,81,82,83,79,85,86,89,89,89,89,89,89,89,89,89,89,89,89,89,89,89,90,0,91,91,91,0,0,0,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,91,90,92,89,91,90,92,89,91,90,92,89,91,90,92,89,90,89,90,89,90,92,89,90,89,90,92,0,0,0,0,89,91,90,92,92,89,90,89,89,89,89,89,91,90,90,90,90,90,92,89,91,90,92,89,90,89,90,89,90,89,90,89,91,90,92,89,90,89,90,89,90,89,91,90,92,89,90,89,90,92,89,90,89,90,89,90,89,90,89,90,89,90,89,90,89,91,90,92,89,90,89,91,90,92,89,91,90,92,89,91,90,92,89,90,94,94,94,0,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,0,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,0,96,96,96,98,96,96,96,96,96,96,96,96,96,96,96,96,96,96,98,96,96,96,96,96,0,0,0,0,95,96,98,95,96,98,96,98,96,98,96,98,96,98,96,98,96,98,96,98,95,97,99,96,98,95,97,99,96,98,95,97,96,98,95,97,96,98,97,96,98,96,98,99,96,98,96,98,96,98,97,96,98,96,96,96,96,96,98,98,98,98,98,95,97,99,96,98,95,97,99,96,98,96,98,96,98,96,98,95,96,98,97,95,96,98,96,98,96,98,96,98,95,97,99,96,98,96,98,99,99,96,98,96,98,97,95,96,98,95,96,98,96,98,96,98,96,98,96,98,95,97,96,98,96,98,95,97,99,96,98,95,97,99,96,98,95,97,99,96,98,99,0,0,0,0,101,101,0,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,0,101,101,101,101,101,101,101,101,101,101,101,101,101,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,106,106,104,104,0,105,105,105,0,0,0,0,102,102,102,102,102,102,0,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,102,102,102,102,103,103,103,103,103,104,104,104,104,104,107,105,105,105,105,105,106,106,106,106,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,0,0,0,0,0,0,0,0,0,0,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,105,106,0,0,102,103,104,105,106,102,103,104,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,107,105,106,102,103,104,105,106,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,0,111,111,111,111,111,111,111,111,111,111,0,0,0,0,0,0,114,114,114,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,113,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,0,113,0,113,113,113,113,113,113,113,114,115,113,114,115,113,114,115,113,114,115,113,114,115,113,113,0,113,0,113,113,114,115,113,113,114,115,113,113,113,113,113,114,115,113,113,113,113,113,0,0,0,114,115,113,113,0,113,113,114,115,113,113,113,0,113,114,115,113,0,0,113,0,113,0,0,0,0,113,113,113,0,0,0,114,115,113,113,0,114,115,113,114,115,113,114,115,113,113,0,0,0,123,124,124,124,121,121,121,121,121,121,121,121,121,121,0,0,0,123,121,122,121,121,121,121,121,121,121,122,122,122,123,124,122,121,123,124,122,121,123,124,122,121,123,124,122,121,123,124,121,121,121,122,121,121,121,123,124,122,121,121,123,124,122,121,121,121,121,121,123,124,122,121,121,121,121,121,123,124,122,121,122,122,122,121,121,121,123,124,122,121,121,121,121,122,123,124,121,0,0,0,0,0,121,121,122,122,122,122,122,122,121,121,121,123,124,122,121,121,123,124,122,121,123,124,122,121,123,124,122,121,121,125,125,125,125,125,0,125,125,125,125,125,125,125,125,125,125,125,0,0,125,125,125,125,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,129,0,130,130,130,0,0,0,127,127,127,127,129,130,127,129,130,127,129,130,127,129,130,127,129,130,129,130,127,129,130,127,129,130,127,127,0,0,129,130,127,129,130,127,127,127,127,127,127,127,129,130,0,0,127,127,127,129,130,127,129,130,127,129,130,127,129,130,127,127,127,127,127,0,0,0,133,134,133,134,133,133,133,133,133,133,133,133,134,133,134,133,133,133,133,134,134,133,134,134,0,133,0,134,133,133,134,133,134,133,134,0,0,142,136,136,136,136,136,136,136,116,116,116,116,116,116,116,116,116,116,0,116,116,0,116,116,116,116,116,116,116,116,116,116,116,116,0,116,116,116,116,116,116,116,140,140,140,0,0,142,142,0,0,142,0,0,87,0,87,87,0,0,0,65,136,65,88,116,65,116,136,136,136,136,136,136,136,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,65,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,116,136,140,136,136,65,136,136,65,116,139,136,140,65,142,143,87,88,136,65,143,116,116,139,136,136,136,136,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,88,136,136,136,136,65,65,116,143,116,139,136,140,65,142,143,87,88,136,136,136,116,139,136,140,65,141,142,143,87,88,139,65,141,136,136,65,116,0,0,143,143,88,139,141,136,116,136,140,0,0,136,65,136,0,88,0,0,136,136,136,65,65,65,65,65,116,139,136,140,65,141,142,143,87,88,116,136,116,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,116,139,136,140,65,141,142,143,87,88,0,136,65,355,356,357,358,357,358,358,144,144,0,0,0,0,0,144,145,144,144,144,144,144,144,144,146,144,145,146,144,145,146,144,146,144,144,144,144,144,145,144,146,144,145,144,146,144,144,144,144,144,145,146,144,145,144,144,144,144,146,144,145,144,144,144,146,144,145,145,144,144,145,145,144,144,145,144,144,145,0,0,0,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,144,144,144,146,144,144,146,144,145,146,144,145,146,144,145,144,145,184,152,0,155,187,0,181,181,181,187,187,187,187,187,187,182,187,187,187,0,0,0,172,187,187,173,0,187,187,155,187,187,0,0,0,0,0,179,179,181,179,0,0,179,179,181,179,187,187,187,187,187,155,0,187,151,151,151,151,151,151,151,151,151,151,187,0,0,0,187,187,155,187,0,173,173,173,173,173,173,173,173,173,173,173,173,173,173,187,187,187,187,172,173,172,187,182,0,187,187,179,181,147,147,150,150,150,150,150,150,179,181,179,181,179,179,179,187,172,0,0,187,0,0,0,0,0,0,0,0,172,173,0,0,187,181,173,179,181,155,187,0,0,187,185,186,185,186,200,206,155,187,0,187,182,187,0,0,148,177,149,149,150,147,151,152,153,154,154,155,154,154,153,154,154,168,168,153,153,157,149,148,162,164,166,154,154,154,154,154,154,154,154,0,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,177,162,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,150,147,151,171,171,171,0,177,150,147,151,0,150,147,151,183,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,150,147,151,168,168,155,177,164,158,167,165,156,169,170,171,153,153,154,154,157,157,149,149,148,148,162,162,172,173,150,150,150,150,150,147,147,147,147,147,151,151,151,151,151,174,175,177,178,179,180,181,189,190,191,182,183,155,155,164,164,166,166,184,184,152,152,185,185,186,186,187,158,167,165,156,169,170,171,153,154,154,154,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,183,155,164,164,166,166,184,152,185,186,187,150,147,151,150,147,151,150,147,151,187,150,147,151,168,153,154,157,149,148,162,155,164,166,184,152,185,186,164,166,200,0,0,0,183,157,148,158,167,165,156,169,170,171,153,154,157,149,148,162,173,150,147,151,182,155,164,166,184,152,185,186,187,148,180,180,148,150,147,151,150,147,151,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,178,190,191,177,164,166,186,150,147,151,150,147,151,153,168,168,153,154,0,170,170,170,170,170,170,170,0,153,157,149,162,174,175,183,166,184,152,186,153,155,155,155,155,153,185,185,155,178,190,191,150,147,151,152,150,147,151,153,153,157,183,148,162,164,166,186,189,148,0,0,0,0,150,147,151,158,158,0,164,184,0,0,0,0,206,150,147,151,0,0,165,165,165,165,165,167,167,167,167,168,168,153,0,0,0,0,0,0,0,0,0,169,150,147,151,150,147,151,156,156,150,147,151,180,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,182,183,155,164,166,184,152,185,186,187,184,152,185,186,155,186,153,154,157,149,148,162,155,164,166,184,152,185,186,150,147,151,158,167,165,156,169,170,171,153,154,157,149,148,162,172,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,158,167,165,156,169,170,171,153,154,157,149,148,162,172,173,150,147,151,174,175,177,178,179,180,181,189,190,191,182,183,155,164,166,184,152,185,186,187,183,150,147,151,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,380,381,382,383,384,385,386,387,388,389,390,391,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,265,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,228,229,230,231,232,233,234,235,236,239,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,0,0,0,275,276,0,275,276,0,273,273,273,273,273,273,273,273,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,0,273,17,273,17,273,17,273,17,273,17,273,17,273,17,273,17,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,273,17,273,17,273,17,0,273,17,273,17,272,273,17,273,17,0,0,272,273,273,273,273,273,17,17,17,17,17,275,276,272,273,17,275,276,273,17,273,17,273,17,273,17,0,0,0,272,273,17,273,17,273,17,273,17,272,273,17,275,276,273,17,273,17,0,0,0,0,0,0,0,0,0,273,17,273,17,273,17,273,17,272,272,272,272,272,272,272,272,272,272,272,272,272,272,272,272,0,273,17,273,17,273,17,272,273,17,275,276,273,17,272,273,17,275,276,272,273,17,275,276,272,273,17,275,276,0,273,17,0,0,280,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,278,278,278,278,280,278,280,278,280,278,280,280,278,278,278,278,278,278,280,278,280,0,0,278,280,278,278,0,278,280,278,280,278,280,278,280,0,282,282,282,282,282,282,282,282,282,282,282,282,0,282,282,282,282,282,282,282,282,283,283,283,283,282,283,282,283,282,283,282,283,282,282,282,282,282,282,282,283,282,283,283,283,283,283,282,282,282,282,282,283,283,282,283,282,282,282,282,0,282,283,282,282,282,282,283,282,282,283,282,282,282,282,0,282,282,282,282,283,282,282,283,282,283,282,283,282,0,286,286,286,286,286,286,286,286,286,286,286,286,286,286,286,0,286,286,286,286,286,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,288,289,289,289,0,290,290,290,290,290,289,289,289,289,289,290,290,290,290,290,290,290,290,0,291,291,291,291,291,291,291,291,289,289,289,291,0,0,290,291,288,291,288,288,288,288,288,288,291,288,288,288,288,288,0,0,0,289,289,289,291,289,289,289,289,289,289,289,289,0,290,288,290,291,0,0,289,289,289,289,289,295,296,295,296,295,296,296,294,294,294,291,0,297,297,297,297,297,297,297,297,297,297,297,297,297,297,297,289,289,289,297,297,289,0,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,0,0,0,0,0,0,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,73,73,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,288,288,288,288,289,289,289,289,289,290,290,290,290,290,291,291,291,291,291,73,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,293,294,295,296,297,288,289,290,291,73,288,289,290,291,288,289,290,291,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,73,73,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,288,289,290,291,0,0,0,0,0,0,0,73,293,294,295,296,297,288,289,290,291,288,289,290,291,73,293,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,73,293,294,295,296,297,288,289,290,291,288,289,290,291,0,0,0,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,132,64,64,132,132,64,64,64,132,132,64,64,64,132,392,64,132,392,64,132,392,64,132,392,64,132,392,64,132,64,132,64,132,392,392,64,132,392,64,132,392,392,64,132,392,64,132,64,132,392,64,132,64,132,0,0,64,132,64,132,64,132,64,132,64,64,132,132,64,132,392,0,306,306,306,0,0,305,305,305,305,305,305,305,305,305,305,305,305,305,393,394,395,394,311,311,311,311,311,0,0,306,306,306,309,309,308,0,0,0,308,309,310,308,309,308,309,308,309,308,309,308,309,308,309,308,309,306,308,310,311,309,306,308,310,311,309,306,308,311,309,306,308,311,309,308,311,309,308,309,308,309,308,309,310,308,309,306,308,311,309,308,309,306,308,308,308,308,308,310,311,309,309,309,309,309,306,308,310,311,309,308,309,308,309,308,309,308,309,310,310,308,311,309,308,309,308,309,308,309,306,308,310,311,309,308,309,308,309,310,308,309,308,311,309,308,309,308,310,309,308,309,308,309,308,309,306,308,311,309,308,309,306,308,310,311,309,306,308,310,311,309,306,308,310,311,309,308,309,310,310,393,394,395,394,0,0,312,313,313,312,313,312,313,312,313,312,313,312,313,312,313,312,312,312,313,312,313,312,313,0,0,0,0,0,0,0,313,312,313,312,313,312,313,312,0,0,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,315,0,315,319,318,318,0,319,319,319,318,318,318,318,318,317,317,317,317,317,317,0,0,317,317,317,317,317,317,317,317,317,317,317,317,318,319,317,318,319,317,318,319,317,318,319,317,319,317,317,317,317,317,318,319,317,317,317,317,317,317,318,319,317,318,319,317,317,317,317,318,317,318,319,317,317,317,317,318,319,317,317,317,317,319,318,317,317,317,317,317,317,318,319,317,317,318,319,317,318,319,317,318,319,317,0,0,0,396,397,398,399,400,401,402,403,404,399,400,401,402,400,402,307,307,307,307,307,307,307,307,307,307,307,0,307,307,307,0,0,0,0,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,0,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,321,331,331,331,331,331,0,331,331,331,331,331,331,331,325,331,331,330,322,0,326,0,331,331,0,0,331,331,331,330,331,0,330,330,331,331,330,331,326,329,328,331,331,330,330,330,330,330,330,330,330,331,331,331,331,325,0,0,331,0,331,322,329,0,0,331,331,331,331,330,331,327,327,327,327,327,328,0,331,331,331,0,0,0,322,0,322,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,330,331,331,331,331,331,331,331,331,331,331,331,330,331,331,330,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,331,330,331,331,331,331,331,331,331,331,331,331,0,0,0,322,323,324,74,322,322,322,322,322,322,322,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,0,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,74,322,322,322,323,324,0,322,334,0,0,0,0,323,324,332,333,322,323,324,74,330,331,332,333,322,334,335,0,0,0,0,0,322,0,0,0,0,0,0,323,323,324,324,74,74,325,326,327,328,329,330,331,332,333,322,322,322,322,322,334,335,0,0,323,323,324,324,74,325,326,327,328,329,330,331,332,333,322,334,334,335,335,322,322,322,335,322,334,335,323,324,74,334,0,0,334,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,334,335,323,324,74,330,331,322,0,322,322,322,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,322,325,322,325,323,0,0,0,0,335,0,0,0,0,334,335,322,74,74,322,334,335,0,0,0,0,0,0,332,333,322,332,333,322,0,0,0,0,0,0,0,0,0,0,0,0,334,0,322,322,0,322,0,0,0,0,0,74,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,322,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,0,323,324,74,325,326,327,328,329,330,331,332,333,322,334,335,334,322,0,0,0,405,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],0,[[],2],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],3],0,0,0,0,0,0,0,0,0,0,0,0,[4,5],[6,5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[7,7],[8,8],[[]],[[]],[4],[9],[[4,4],1],[[9,9],1],[[6,6],1],[[7,7],1],[[8,8],1],[7,10],[7,[[11,[8]]]],[[4,12],13],[[9,12],13],[[6,12],13],[[7,12],13],[[8,12],13],[[]],[14,[[3,[4]]]],[[]],[[]],[[]],[[]],[5,[[3,[4]]]],[4],[9],[6],[7],[8],[7,15],[[]],[[]],[[]],[[]],[[]],[4],[[]],[[]],[4,9],[9,11],[6,11],[[16,17],[[3,[4]]]],[[5,16,17],[[3,[4]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[]],[[]],[[],[[18,[20]]]],[20,20],[[]],[[20,12],13],[[20,12],13],[[]],[[]],[21],[[]],[[],22],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[23,23],[[]],[23,24],[[23,23],1],[[],25],[[23,12],13],[[23,12],13],[[]],[25,23],[25,23],[[]],[[],23],[21],[[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,27]]]]]]]]]]]]]]]]]]],[[3,[[0,[26,[27,[[0,[26,[27,[[0,[26,[27,[[0,[26,27]]]]]]]]]]]]]]]]]],[[]],[[]],[[],22],[28,[[18,[23,28]]]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],29],[[],16],[[],30],[[],31],[[],32],[[],33],[[],34],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[29,35],[16,35],[30,36],[31,35],[32,35],[33,37],[34,35],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,29],[16,16],[30,30],[31,31],[32,32],[38,38],[33,33],[34,34],[39,39],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[29,29],40],[[16,16],40],[[30,30],40],[[31,31],40],[[32,32],40],[[33,33],40],[[34,34],40],[[39,39],40],[29,29],[16,16],[30,30],[31,31],[32,32],[33,33],[34,34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[33,33],1],[[34,34],1],[[5,[11,[41]],5,[11,[41]],2],[[3,[2]]]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[],29],[[],16],[[],30],[[],31],[[],32],[[],33],[[],34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[42,42],1],[[38,38],1],[[33,33],1],[[34,34],1],[[39,39],1],[[29,43]],[[16,43]],[[30,43]],[[31,43]],[[32,43]],[[33,43]],[[34,43]],[[5,34,44,44],3],[[5,42],[[3,[35]]]],[[5,38],3],[[29,12],13],[[29,12],13],[[29,12],13],[[29,12],13],[[29,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[16,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[30,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[31,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[32,12],13],[[42,12],13],[[38,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[33,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[34,12],13],[[39,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[35,[[11,[29]]]],[35,[[11,[16]]]],[36,[[11,[30]]]],[35,[[11,[31]]]],[35,[[11,[32]]]],[37,[[11,[33]]]],[35,[[11,[34]]]],[35,29],[35,16],[36,30],[35,31],[35,32],[37,33],[35,34],[35,29],[35,16],[36,30],[35,31],[35,32],[37,33],[35,34],[43,29],[43,16],[43,30],[43,31],[43,32],[43,33],[43,34],[29],[16],[30],[31],[32],[42],[38],[33],[34],[39],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29],1],[[16,16],1],[[30,30],1],[[31,31],1],[[32,32],1],[[33,33],1],[[34,34],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,1],[16,1],[30,1],[31,1],[32,1],[33,1],[34,1],[29,1],[16,1],[30,1],[31,1],[32,1],[33,1],[34,1],[29,29],[16,16],[30,30],[31,31],[32,32],[33,33],[34,34],[[16,17],[[3,[5]]]],[[5,16,17],[[3,[5]]]],[[29,29],[[11,[40]]]],[[16,16],[[11,[40]]]],[[30,30],[[11,[40]]]],[[31,31],[[11,[40]]]],[[32,32],[[11,[40]]]],[[33,33],[[11,[40]]]],[[34,34],[[11,[40]]]],[[39,39],[[11,[40]]]],[[5,44,44,39],3],[[5,44,44],3],[[],[[3,[45]]]],[5,[[3,[45]]]],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[[11,[5]],[11,[5]]],3],[[[11,[5]],[11,[5]],30],3],[[29,29,1]],[[16,16,1]],[[30,30,1]],[[31,31,1]],[[32,32,1]],[[33,33,1]],[[34,34,1]],[[5,[11,[41]],5,[11,[41]],2,33],[[3,[2]]]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[5,5,2,33],[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[29,29]],[[16,16]],[[30,30]],[[31,31]],[[32,32]],[[33,33]],[[34,34]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[29,29],29],[[16,16],16],[[30,30],30],[[31,31],31],[[32,32],32],[[33,33],33],[[34,34],34],[[5,33],[[3,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,[[],1],0,0,0,[[]],[[]],[[]],[[]],0,[46,46],[[]],0,[47],[[46,46],1],[[47,47],1],0,[[46,12],13],[[47,12],13],[[]],[[]],[[],[[3,[47]]]],[46],[47],0,[[]],[[]],[[]],0,[47,11],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,0,0,0,0,0,[[],48],[[],49],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[48,37],[49,35],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[]],[[]],[[]],[[]],[48,48],[49,49],[[]],[[]],[[48,48],40],[[49,49],40],[48,48],[49,49],[[48,48],1],[[49,49],1],[[10,49],3],[[48,48],48],[[49,49],49],[[],48],[[],49],[[48,48],1],[[49,49],1],[[48,43]],[[49,43]],[[10,48],3],[[48,12],13],[[48,12],13],[[48,12],13],[[48,12],13],[[48,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[49,12],13],[[]],[[]],[37,[[11,[48]]]],[35,[[11,[49]]]],[37,48],[35,49],[37,48],[35,49],[43,48],[43,49],[48],[49],[10,3],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[48,48],1],[[49,49],1],[[]],[[]],[48,1],[49,1],[48,1],[49,1],[48,48],[49,49],[[48,48],[[11,[40]]]],[[49,49],[[11,[40]]]],[[48,48]],[[49,49]],[[48,48,1]],[[49,49,1]],[[48,48],48],[[49,49],49],[[48,48]],[[49,49]],[[48,48],48],[[49,49],49],[[]],[[]],[[48,48]],[[49,49]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[48,48],48],[[49,49],49],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],50],[[],51],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[50,52],[51,35],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[]],[[]],[[]],[[]],[50,50],[51,51],[[]],[[]],[[50,50],40],[[51,51],40],[50,50],[51,51],[[50,50],1],[[51,51],1],[[50,50],50],[[51,51],51],[[],50],[[],51],[[50,50],1],[[51,51],1],[[50,43]],[[51,43]],[[50,12],13],[[50,12],13],[[50,12],13],[[50,12],13],[[50,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[51,12],13],[[]],[[]],[52,[[11,[50]]]],[35,[[11,[51]]]],[52,50],[35,51],[52,50],[35,51],[43,50],[43,51],[50],[51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[50,50],1],[[51,51],1],[[]],[[]],[50,1],[51,1],[50,1],[51,1],[[11,11,50,11],3],[50,50],[51,51],[[50,50],[[11,[40]]]],[[51,51],[[11,[40]]]],[[50,50]],[[51,51]],[[50,50,1]],[[51,51,1]],[[50,50],50],[[51,51],51],[[50,50]],[[51,51]],[[50,50],50],[[51,51],51],[[]],[[]],[[50,50]],[[51,51]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],3],[51,3],[[50,50],50],[[51,51],51],0,0,0,0,0,0,0,0,0,0,[[],53],[[53,53],53],[[53,53]],[[53,53],53],[[53,53]],[53,35],[[53,53],53],[[53,53]],[[]],[[]],[[]],[[]],[[]],[[]],[53,53],[54,54],[[]],[[]],[[53,53],40],[53,53],[[53,53],1],[54,55],[[53,53],53],[[],53],[[53,53],1],[[54,54],1],[[53,43]],[54,55],[[53,12],13],[[53,12],13],[[53,12],13],[[53,12],13],[[53,12],13],[[54,12],13],[[56,12],13],[[]],[[]],[[]],[35,[[11,[53]]]],[35,53],[35,53],[43,53],[53],[54],[[53,53]],[[53,53],53],[[53,53],1],[[]],[[]],[[]],[53,1],[53,1],[54,55],0,[56,3],[56,[[3,[54]]]],[[10,53,17,[11,[54]]],[[3,[56]]]],[[56,36],[[3,[2]]]],[56,[[3,[54]]]],[[56,36],3],[56,[[3,[54]]]],[[56,54],[[3,[54]]]],[10,3],[54,55],[[55,55,55,55],54],[53,53],[[53,53],[[11,[40]]]],[[53,53]],[[53,53,1]],[[53,53],53],[[53,53]],[[53,53],53],[[]],[[]],[[53,53]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[53,53],53],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],57],[[57,57],57],[[57,57]],[[57,57],57],[[57,57]],[57,35],[[57,57],57],[[57,57]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[57,57],[[]],[[57,57],40],[57,57],[[57,57],1],[[57,57],57],[58],[[],57],[[57,57],1],[[57,43]],[[59,12],13],[[58,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[57,12],13],[[60,12],13],[[]],[[]],[[]],[[]],[35,[[11,[57]]]],[35,57],[35,57],[43,57],[57],[[],[[3,[58]]]],[[],[[3,[37]]]],[59,37],[[57,57]],[[57,57],57],[[57,57],1],[[]],[[]],[[]],[[]],[58],[[]],[57,1],[57,1],[58,60],[59,10],[60,11],[57,57],[[57,57],[[11,[40]]]],[[57,57]],[[57,57,1]],[[57,57],57],[[57,57]],[[57,57],57],[[]],[58],[[57,57]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[57,57],57],0,0,0,0,0,0,0,0,0,0,0,0,[61,[[11,[1]]]],[[],62],[61,[[11,[1]]]],[61,5],[[62,62],62],[[62,62]],[[62,62],62],[[62,62]],[62,63],[[62,62],62],[[62,62]],[[]],[[]],[[]],[[]],[61,61],[62,62],[[]],[[]],[[62,62],40],[62,62],[[62,62],1],[[62,62],62],[[],62],[[61,61],1],[[62,62],1],[61,62],[[62,43]],[[61,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[62,12],13],[[]],[[]],[63,[[11,[62]]]],[63,62],[63,62],[43,62],[61],[62],[[62,62]],[[62,62],62],[[62,62],1],[[]],[[]],[62,1],[62,1],[[5,62],61],[62,62],[[62,62],[[11,[40]]]],[35,[[3,[35]]]],[[[11,[64]],[11,[65]]],[[3,[35]]]],[[62,62]],[61,[[11,[62]]]],[[62,62,1]],[[61,62]],[[62,62],62],[[62,62]],[[62,62],62],[[]],[[]],[[62,62]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[62,62],62],0,0,0,0,0,[66,5],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[67,67],[68,68],[69,69],[[]],[[]],[[]],[66],[[67,67],1],[[68,68],1],[[66,66],1],[66,70],[66,70],[[67,12],[[18,[71]]]],[[68,12],13],[[69,12],13],[[66,12],13],0,[[[72,[[11,[67]]]],[72,[[11,[73]]]]],[[3,[69]]]],[74,75],[[]],[[]],[[]],[[]],[66,3],[67],[68],[66],[[]],[[]],[[]],[[]],[66,5],0,0,[[[72,[[11,[67]]]],[72,[[11,[73]]]]],[[3,[68]]]],[16,[[3,[66]]]],[66,[[3,[22]]]],[66,[[3,[22]]]],[66,[[70,[2]]]],[66,[[70,[2]]]],0,[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[66,3],[66,[[70,[2]]]],[66,[[70,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],76],[[76,76],76],[[76,76]],[[76,76],76],[[76,76]],[76,35],[[76,76],76],[[76,76]],[[]],[[]],[[]],[[]],[[77,76,[11,[35]]],[[3,[74]]]],[76,76],[78,78],[[]],[[]],[[76,76],40],[76,76],[[76,76],1],[[],2],[[],78],[[76,76],76],[[],76],[[76,76],1],[[78,78],1],[[76,43]],[[76,12],13],[[76,12],13],[[76,12],13],[[76,12],13],[[76,12],13],[[78,12],13],[[]],[[]],[35,[[11,[76]]]],[35,76],[35,76],[43,76],[76],[78],[[76,76]],[[76,76],76],[[76,76],1],[[]],[[]],[76,1],[76,1],[[78,2],[[3,[1]]]],[[],78],[76,76],[[76,76],[[11,[40]]]],[[76,76]],[74,[[3,[78]]]],[[],[[3,[2]]]],[[74,78],3],[[],3],[[76,76,1]],[[78,2],3],[[5,76],3],[[76,76],76],[[76,76]],[[76,76],76],[[]],[[]],[[76,76]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[76,76],76],[[78,2],3],[76,3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,[[3,[79]]]],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[[11,[64]]],3],[81,84],[82,84],[81,84],[82,84],[83,84],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[80,[[3,[79]]]],[[[80,[81]]],[[3,[79]]]],[[[80,[82]]],[[3,[79]]]],[[[80,[83]]],[[3,[79]]]],[79,79],[85,85],[86,86],[[]],[[]],[[]],[[85,85],40],[[86,86],40],[[79,79],1],[[85,85],1],[[86,86],1],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[],5],[81,5],[82,5],[83,5],[[81,12],13],[[82,12],13],[[83,12],13],[[79,12],13],[[85,12],13],[[86,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[79],[85],[86],[[],1],[81,1],[82,1],[83,1],[[]],[[]],[[]],[[]],[[]],[[]],[[85,87],3],[83,86],[81,2],[82,2],[[5,44,25,87],81],[[5,44,25,87],82],[[5,86,25,87],83],[81,44],[82,44],[[85,85],[[11,[40]]]],[[86,86],[[11,[40]]]],[[],25],[81,25],[82,25],[83,25],[87],[[81,87]],[[82,87]],[[83,87]],[[],88],[81,88],[82,88],[83,88],[80,3],[[[80,[81]]],3],[[[80,[82]]],3],[[[80,[83]]],3],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[25,[[3,[86]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],89],[[],90],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[89,35],[90,35],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[89,89],[91,91],[90,90],[92,92],[[]],[[]],[[]],[[]],[[89,89],40],[[90,90],40],[89,89],[90,90],[[89,89],1],[[90,90],1],[92,15],[[89,89],89],[[90,90],90],[[],89],[[],90],[[],92],[[],[[3,[5]]]],[90,[[3,[5]]]],[[5,91,5],3],[[5,93],[[3,[2]]]],[[89,89],1],[[91,91],1],[[90,90],1],[[92,92],1],[92,89],[[89,43]],[[90,43]],[[89,12],13],[[89,12],13],[[89,12],13],[[89,12],13],[[89,12],13],[[91,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[90,12],13],[[92,12],13],[[]],[[]],[[]],[[]],[35,[[11,[89]]]],[35,[[11,[90]]]],[35,89],[35,90],[35,89],[35,90],[43,89],[43,90],[89],[91],[90],[92],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[89,89],1],[[90,90],1],[[]],[[]],[[]],[[]],[89,1],[90,1],[89,1],[90,1],[[89,15],92],[89,89],[90,90],[[89,89],[[11,[40]]]],[[90,90],[[11,[40]]]],[[89,89]],[[90,90]],[[89,89,1]],[[90,90,1]],[[89,89],89],[[90,90],90],[[89,89]],[[90,90]],[[89,89],89],[[90,90],90],[[]],[[]],[[]],[[]],[[89,89]],[[90,90]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[89,89],89],[[90,90],90],0,0,0,0,[[],94],[[94,94],94],[[94,94]],[[94,94],94],[[94,94]],[94,35],[[94,94],94],[[94,94]],[[]],[[]],[94,94],[[]],[[94,94],40],[94,94],[[94,94],1],[[94,94],94],[[],94],[[94,94],1],[[37,94],[[3,[5]]]],[[94,43]],[[94,12],13],[[94,12],13],[[94,12],13],[[94,12],13],[[94,12],13],[[]],[35,[[11,[94]]]],[35,94],[35,94],[43,94],[94],[[94,94]],[[94,94],94],[[94,94],1],[[]],[94,1],[94,1],[94,94],[[94,94],[[11,[40]]]],[[94,94]],[[94,94,1]],[[94,94],94],[[94,94]],[[94,94],94],[[]],[[94,94]],[[],18],[[],18],[[],19],[[94,94],94],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[95,96],[[3,[97]]]],[[],96],[[],98],[95,5],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[96,36],[98,35],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[96,96],[98,98],[95,95],[97,97],[[]],[[]],[[]],[[]],[[96,96],40],[[98,98],40],[[97,97],40],[96,96],[98,98],[[96,96],1],[[98,98],1],0,[[96,96],96],[[98,98],98],[[],96],[[],98],[[96,96],1],[[98,98],1],[[97,97],1],[[96,43]],[[98,43]],[[96,12],13],[[96,12],13],[[96,12],13],[[96,12],13],[[96,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[98,12],13],[[95,12],13],[[97,12],13],[[99,12],13],[[]],[[]],[[]],[[]],[[]],[36,[[11,[96]]]],[35,[[11,[98]]]],[36,96],[35,98],[36,96],[35,98],[43,96],[43,98],[5,95],[96],[98],[97],[98,[[3,[95]]]],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[96,96],1],[[98,98],1],[[]],[[]],[[]],[[]],[[]],[96,1],[98,1],[96,1],[98,1],0,0,[96,96],[98,98],[[96,96],[[11,[40]]]],[[98,98],[[11,[40]]]],[[97,97],[[11,[40]]]],[95,[[3,[[100,[99]]]]]],[[96,96]],[[98,98]],[[95,97],3],[[96,96,1]],[[98,98,1]],[[96,96],96],[[98,98],98],[[96,96]],[[98,98]],[[96,96],96],[[98,98],98],[[]],[[]],[[]],[[]],[[96,96]],[[98,98]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[96,96],96],[[98,98],98],0,0,0,0,0,0,0,0,[[],101],[[101,101],101],[[101,101]],[[101,101],101],[[101,101]],[101,37],[[101,101],101],[[101,101]],[[]],[[]],[101,101],[[]],[[101,101],40],[101,101],[[101,101],1],[[101,101],101],[[],101],[[101,101],1],[[101,43]],[[101,12],13],[[101,12],13],[[101,12],13],[[101,12],13],[[101,12],13],[[]],[37,[[11,[101]]]],[37,101],[37,101],[43,101],[101],[[101,101]],[[101,101],101],[[101,101],1],[[]],[101,1],[101,1],[[10,101],[[3,[5]]]],[101,101],[[101,101],[[11,[40]]]],[[101,101]],[[101,101,1]],[[101,101],101],[[101,101]],[[101,101],101],[[]],[[101,101]],[[],18],[[],18],[[],19],[[101,101],101],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],102],[[],103],[[],104],[[],105],[[],106],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[102,35],[103,35],[104,35],[105,35],[106,35],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[102,102],[103,103],[104,104],[107,107],[105,105],[106,106],[[]],[[]],[[]],[[]],[[]],[[]],[[102,102],40],[[103,103],40],[[104,104],40],[[107,107],40],[[105,105],40],[[106,106],40],[102,102],[103,103],[104,104],[105,105],[106,106],[[102,102],1],[[103,103],1],[[104,104],1],[[105,105],1],[[106,106],1],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[],102],[[],103],[[],104],[[],105],[[],106],[[102,102],1],[[103,103],1],[[104,104],1],[[107,107],1],[[105,105],1],[[106,106],1],[[102,43]],[[103,43]],[[104,43]],[[105,43]],[[106,43]],[[102,12],13],[[102,12],13],[[102,12],13],[[102,12],13],[[102,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[103,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[104,12],13],[[107,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[105,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[106,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[35,[[11,[102]]]],[35,[[11,[103]]]],[35,[[11,[104]]]],[35,[[11,[105]]]],[35,[[11,[106]]]],[35,102],[35,103],[35,104],[35,105],[35,106],[35,102],[35,103],[35,104],[35,105],[35,106],[43,102],[43,103],[43,104],[43,105],[43,106],[102],[103],[104],[107],[105],[106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102],1],[[103,103],1],[[104,104],1],[[105,105],1],[[106,106],1],[[]],[[]],[[]],[[]],[[]],[[]],[102,1],[103,1],[104,1],[105,1],[106,1],[102,1],[103,1],[104,1],[105,1],[106,1],[[108,109,107],3],[[108,109],3],[106,3],[[[11,[110]],110,102,103,5,44],[[3,[108]]]],[[108,109,102],3],[[108,109,109,104,[11,[108]]],[[3,[108]]]],[[108,109,105],3],[[108,109],3],[[],3],[[108,109],3],[102,102],[103,103],[104,104],[105,105],[106,106],[[102,102],[[11,[40]]]],[[103,103],[[11,[40]]]],[[104,104],[[11,[40]]]],[[107,107],[[11,[40]]]],[[105,105],[[11,[40]]]],[[106,106],[[11,[40]]]],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102,1]],[[103,103,1]],[[104,104,1]],[[105,105,1]],[[106,106,1]],[[16,17],[[3,[5]]]],[[],3],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],[[]],[[]],[[]],[[]],[[]],[[]],[[102,102]],[[103,103]],[[104,104]],[[105,105]],[[106,106]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[102,102],102],[[103,103],103],[[104,104],104],[[105,105],105],[[106,106],106],0,0,0,0,0,0,0,0,0,0,0,0,[[],111],[[111,111],111],[[111,111]],[[111,111],111],[[111,111]],[111,35],[[111,111],111],[[111,111]],[[]],[[]],[111,111],[[]],[[111,111],40],[111,111],[[111,111],1],[[111,111],111],[[],111],[[111,111],1],[[111,43]],[[111,12],13],[[111,12],13],[[111,12],13],[[111,12],13],[[111,12],13],[[]],[35,[[11,[111]]]],[35,111],[35,111],[43,111],[[],[[3,[111]]]],[111],[[111,111]],[[111,111],111],[[111,111],1],[[]],[111,1],[111,1],[111,111],[[111,111],[[11,[40]]]],[[111,111]],[111,[[3,[111]]]],[[111,111,1]],[[111,111],111],[[111,111]],[[111,111],111],[[]],[[111,111]],[[],18],[[],18],[[],19],[[111,111],111],0,[112,3],[[],112],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],113],[74,3],[[113,113],113],[[113,113]],[[113,113],113],[[113,113]],[113,35],[[113,113],113],[[113,113]],[[]],[[]],[[]],[[]],[[]],[[]],[114,114],[115,115],[113,113],[[]],[[]],[[]],[[114,114],40],[[115,115],40],[[113,113],40],[113,113],[[74,[72,[[11,[116]]]]],3],[[113,113],1],[[74,[72,[[11,[116]]]]],3],[[113,113],113],[[],113],[[114,114],1],[[115,115],1],[[113,113],1],[[113,43]],[[114,12],13],[[115,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[113,12],13],[[]],[[]],[[]],[35,[[11,[113]]]],[35,113],[35,113],[43,113],[74,[[3,[117]]]],[74,[[3,[118]]]],[74,[[3,[119]]]],[114],[115],[113],[[113,113]],[74,3],[[113,113],113],[[113,113],1],[[]],[[]],[[]],[113,1],[113,1],[74,3],[113,113],[[114,114],[[11,[40]]]],[[115,115],[[11,[40]]]],[[113,113],[[11,[40]]]],[[74,120],[[3,[117]]]],[[74,120],[[3,[117]]]],[[113,113]],[[74,113],3],[[113,113,1]],[[74,113],3],[[74,118],3],[[74,119],3],[[74,[72,[[11,[116]]]]],3],[[113,113],113],[[113,113]],[[113,113],113],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[[]],[[]],[[]],[[113,113]],[[],3],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[113,113],113],[[74,120,108],3],[[74,120,108],3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],121],[122,[[11,[15]]]],[[121,121],121],[[121,121]],[[121,121],121],[[121,121]],[121,36],[[121,121],121],[[121,121]],[122,[[11,[15]]]],[122,[[11,[15]]]],[122,[[11,[15]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[123,123],[124,124],[122,122],[121,121],[[]],[[]],[[]],[[]],[[123,123],40],[[124,124],40],[[121,121],40],[121,121],[[121,121],1],[[],122],[[],121],[[121,121],121],[[],121],[[123,123],1],[[124,124],1],[[122,122],1],[[121,121],1],[[121,43]],[[123,12],13],[[124,12],13],[[122,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[121,12],13],[[]],[[]],[[]],[[]],[36,[[11,[121]]]],[36,121],[36,121],[43,121],[123],[124],[122],[121],[122,[[11,[15]]]],[122,[[11,[15]]]],[122,[[11,[15]]]],[[121,121]],[[121,121],121],[[121,121],1],[[]],[[]],[[]],[[]],[121,1],[121,1],[121,121],[122,[[11,[15]]]],[[123,123],[[11,[40]]]],[[124,124],[[11,[40]]]],[[121,121],[[11,[40]]]],[[123,35],[[3,[122]]]],[123,3],[[123,124],3],[[123,35,122,121],3],[[123,11],3],[[121,121]],[[121,121,1]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[122,15]],[[121,121],121],[[121,121]],[[121,121],121],[[]],[[]],[[]],[[]],[[121,121]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[121,121],121],0,0,0,0,0,0,[[]],[[]],[125,125],[[]],[[125,125],40],[[125,125],1],[[125,12],13],[[]],[125],[[]],[[125,125],[[11,[40]]]],[125,[[3,[126]]]],[1,3],[[]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[127,128],[127,128],[127,117],[127,117],[[]],[[]],[[]],[[]],[[]],[[]],[129,129],[130,130],[127,127],[[]],[[]],[[]],[[129,129],40],[[130,130],40],[[129,129],1],[[130,130],1],[[127,127],1],[[129,12],13],[[130,12],13],[[127,12],13],[[]],[[]],[[]],[127,117],[129,3],[130,[[3,[127]]]],[129],[130],[127],[[]],[[]],[[]],[127,117],[127,117],[127,117],[127,117],[127,117],[127,117],[[129,129],[[11,[40]]]],[[130,130],[[11,[40]]]],0,[[129,131,131],3],[127,117],[127,117],[127,132],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[127,117],[127,117],[127,132],[127,117],0,0,0,[[]],[[]],[[]],[[]],[133],[133,133],[[]],[[133,5],1],[[],133],[[133,133],1],[[133,[11,[5]]],134],[[133,12],13],[[134,12],13],[[]],[[]],[133],[133,[[11,[5]]]],[[133,5]],[[]],[[]],[[]],[[],133],[134,[[11,[5]]]],[134,[[11,[5]]]],[[],[[3,[35]]]],[[133,5]],[[],[[3,[35]]]],[134],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[5,5,[11,[44]],2],[[3,[2]]]],[[5,5,[11,[135]],2],[[3,[2]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[65,116]],[[],136],[[],65],[88,137],[116,24],[65,138],[116,24],[[136,136],136],[[136,136]],[[136,136],136],[[136,136]],[136,35],[[136,136],136],[[136,136]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[65],[116,116],[139,139],[136,136],[140,140],[65,65],[141,141],[142,142],[143,143],[87,87],[88,88],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[116,116],40],[[136,136],40],[[140,140],40],[136,136],[[136,136],1],[[65,116],1],[[136,136],136],[[],136],[[],65],[[116,116],1],[[139,139],1],[[136,136],1],[[140,140],1],[[65,65],1],[[142,142],1],[[143,143],1],[[87,87],1],[[88,88],1],[[136,43]],[65],[143,136],[[116,12],13],[[116,12],13],[[139,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[136,12],13],[[140,12],13],[[65,12],13],[[141,12],13],[[142,12],13],[[143,12],13],[[87,12],13],[[88,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[137,88],[35,[[11,[136]]]],[35,136],[35,136],[43,136],[[],65],[138,65],[24,[[3,[116]]]],[143,142],[116],[139],[136],[140],[65],[142],[143],[87],[88],[[136,136]],[[136,136],136],[[136,136],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[65],[[]],[136,1],[136,1],[65,141],[[],139],[[74,[72,[[11,[116]]]]],3],[[74,[72,[[11,[116]]]]],3],[143,65],[[142,136,65],143],[87,88],[139,[[11,[116]]]],[141,[[11,[116]]]],[136,136],[[116,116],[[11,[40]]]],[[136,136],[[11,[40]]]],[[140,140],[[11,[40]]]],[[140,[11,[65]],[11,[65]]],3],[116,3],[[136,136]],[[65,116]],[[136,136,1]],[[116,143],[[3,[143]]]],[88,137],[[116,142],[[3,[142]]]],[[140,[11,[65]],[11,[65]]],3],[[136,136],136],[[136,136]],[[136,136],136],[65,3],[[],[[3,[65]]]],[65,3],[[65,140],[[3,[65]]]],[65,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[136,136]],[25,[[3,[116]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[136,136],136],[65,[[3,[116]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],144],[145,5],[[144,144],144],[[144,144]],[[144,144],144],[[144,144]],[144,35],[[144,144],144],[[144,144]],[[]],[[]],[[]],[[]],[[]],[[]],[146,146],[144,144],[[]],[[]],[[144,144],40],[144,144],[[144,144],1],[[144,144],144],[145],[[],144],[[146,146],1],[[144,144],1],[[145,145],1],[[144,43]],[[146,12],[[18,[71]]]],[[144,12],13],[[144,12],13],[[144,12],13],[[144,12],13],[[144,12],13],[[145,12],13],[[]],[[]],[[]],[35,[[11,[144]]]],[35,144],[35,144],[43,144],[146],[144],[145],[[144,144]],[[144,144],144],[[144,144],1],[[]],[[]],[[]],[[]],[144,1],[144,1],[65,[[3,[145]]]],[145,11],[144,144],[[144,144],[[11,[40]]]],[145,[[3,[[11,[146]]]]]],[[144,144]],[[144,144,1]],[[145,65],3],0,0,[[5,65,144],[[3,[5]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[144,144],144],[[144,144]],[[144,144],144],[[]],[[]],[[144,144]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[144,144],144],[[65,144],[[3,[145]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[5,[[3,[5]]]],[[5,147],[[3,[5]]]],[148,11],0,[149,10],[149,10],[[],150],[[],147],[[],151],[[],152],[153,11],[154,[[11,[149]]]],[154,[[11,[149]]]],[155],[154,[[11,[148]]]],[154,[[11,[148]]]],[153,156],[154,[[11,[157]]]],[154,[[11,[157]]]],[[],158],[[],158],[153,156],[153,156],[157,159],[149,160],[148,161],[162,163],[164,165],[166,167],[154,[[11,[166]]]],[154,[[11,[164]]]],[154,[[11,[164]]]],[154,[[11,[166]]]],[154,[[11,[153]]]],[154,[[11,[153]]]],[154,[[11,[162]]]],[154,[[11,[162]]]],[[5,168],3],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[150,37],[147,35],[151,35],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[162,36],[158,158],[167,167],[165,165],[156,156],[169,169],[170,170],[171,171],[153,153],[154,154],[157,157],[149,149],[148,148],[162,162],[172,172],[173,173],[150,150],[147,147],[151,151],[174,174],[175,175],[[[177,[176]]],[[177,[176]]]],[178,178],[179,179],[180,180],[181,181],[182,182],[183,183],[155,155],[164,164],[166,166],[184,184],[152,152],[185,185],[186,186],[187,187],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[150,150],40],[[147,147],40],[[151,151],40],0,0,0,0,[177,178],[150,150],[147,147],[151,151],[[5,168],3],[[150,150],1],[[147,147],1],[[151,151],1],[[],183],[[150,150],150],[[147,147],147],[[151,151],151],[[],150],[[],147],[[],151],[[158,158],1],[[167,167],1],[[165,165],1],[[156,156],1],[[169,169],1],[[170,170],1],[[171,171],1],[[153,153],1],[[154,154],1],[[157,157],1],[[149,149],1],[[148,148],1],[[162,162],1],[[172,172],1],[[173,173],1],[[150,150],1],[[147,147],1],[[151,151],1],[[174,174],1],[[175,175],1],[[[177,[27]],177],1],[[178,178],1],[[179,179],1],[[180,180],1],[[181,181],1],[[182,182],1],[[183,183],1],[[155,155],1],[[164,164],1],[[166,166],1],[[184,184],1],[[152,152],1],[[185,185],1],[[186,186],1],[[187,187],1],[[150,43]],[[147,43]],[[151,43]],[[],[[11,[187]]]],[[],[[11,[187]]]],[155,187],0,[164,36],[[158,12],[[18,[71]]]],[[167,12],[[18,[71]]]],[[165,12],[[18,[71]]]],[[156,12],[[18,[71]]]],[[169,12],[[18,[71]]]],[[170,12],[[18,[71]]]],[[171,12],[[18,[71]]]],[[153,12],13],[[153,12],13],[[154,12],13],[[154,12],13],[[157,12],13],[[157,12],13],[[149,12],13],[[149,12],13],[[148,12],13],[[148,12],13],[[162,12],13],[[162,12],13],[[172,12],13],[[173,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[150,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[147,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[151,12],13],[[174,12],13],[[175,12],13],[[[177,[188]],12],13],[[178,12],13],[[179,12],13],[[180,12],13],[[181,12],13],[[[189,[188]],12],13],[[[190,[188]],12],13],[[191,12],13],[[182,12],13],[[183,12],13],[[155,12],13],[[155,12],13],[[164,12],13],[[164,12],13],[[166,12],13],[[166,12],13],[[184,12],13],[[184,12],13],[[152,12],13],[[152,12],13],[[185,12],13],[[185,12],13],[[186,12],13],[[186,12],13],[[187,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[192,154],[193,154],[194,154],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[195,183],[[]],[194,164],[[]],[192,166],[[]],[[]],[[]],[[]],[[]],[[]],[37,[[11,[150]]]],[35,[[11,[147]]]],[35,[[11,[151]]]],[37,150],[35,147],[35,151],[37,150],[35,147],[35,151],[25,[[11,[187]]]],[43,150],[43,147],[43,151],[[158,[11,[196]]],11],[[158,[11,[196]]],[[11,[153]]]],[[158,[11,[196]]],[[11,[154]]]],[[158,[11,[196]]],[[11,[157]]]],[[158,[11,[196]]],[[11,[149]]]],[[158,[11,[196]]],[[11,[148]]]],[[158,[11,[196]]],[[11,[162]]]],[[158,[11,[196]]],[[11,[155]]]],[[158,[11,[196]]],[[11,[164]]]],[[158,[11,[196]]],[[11,[166]]]],[197,184],[198,152],[199,185],[193,186],[24,[[18,[164]]]],[24,[[18,[166]]]],[5,3],[5,[[3,[168]]]],[5,[[3,[168]]]],[[5,200],3],[183,201],[157,36],[148,2],[158],[167],[165],[156],[169],[170],[171],[153],[154],[157],[149],[148],[162],[173],[150],[147],[151],[182],[155],[164],[166],[184],[152],[185],[186],[187],[148,202],0,0,[148,2],[[150,150]],[[147,147]],[[151,151]],[[150,150],150],[[147,147],147],[[151,151],151],[[150,150],1],[[147,147],1],[[151,151],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[177,191],[164,197],[166,203],[186,185],[150,1],[147,1],[151,1],[150,1],[147,1],[151,1],[153,1],[[],196],[[],196],[153,196],[154,196],[[5,2],3],0,0,0,0,0,0,0,0,[[],[[3,[153]]]],[[36,36],157],[[24,24],149],[[36,36],162],[[198,[11,[198]]],174],[197,175],[[],183],[[204,204,204,204,202],166],[[202,202,202,202,202,202,202,202],184],[[204,204,204,204],152],[[185,202],186],[[],[[3,[153]]]],[[24,24],155],[186,155],[[36,36],155],[[],[[3,[155]]]],[[],153],[[204,204,204,204],185],[[202,202,202,202,202,202,202,202],185],[[36,36],155],[178,[[11,[179]]]],[190,11],[191,11],[150,150],[147,147],[151,151],[152],[[150,150],[[11,[40]]]],[[147,147],[[11,[40]]]],[[151,151],[[11,[40]]]],[153,[[11,[205]]]],[153,2],[157,36],[183,75],[148,204],[162,36],[164,202],[166,202],[186,202],[[2,[11,[[100,[204]]]]],189],[148,202],[[5,151],[[3,[2]]]],[5,3],[[5,189,151,[11,[64]]],[[3,[190]]]],[[5,[11,[100]],151],[[3,[177]]]],[[150,150]],[[147,147]],[[151,151]],0,0,0,[164,36],[184],[[5,151],[[3,[2]]]],[[5,189,151],[[3,[190]]]],[[5,151,11],[[3,[2]]]],[[5,168,151],[[3,[2]]]],[5,3],[[150,150,1]],[[147,147,1]],[[151,151,1]],[[5,206],3],[[5,182],3],0,0,0,0,0,0,0,0,0,[[],196],[[],196],[[],196],0,0,0,0,[[169,2],[[3,[155]]]],0,[[187,172,147,[72,[[11,[173]]]]],[[3,[5]]]],[[187,172,[72,[[11,[173]]]],147],3],0,0,[[150,150],150],[[147,147],147],[[151,151],151],[[150,150]],[[147,147]],[[151,151]],0,0,[[150,150],150],[[147,147],147],[[151,151],151],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[184,197],[152,198],[185,199],[186,193],[155,22],[186,22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[150,150]],[[147,147]],[[151,151]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[25,[[3,[172]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[183,207],[[150,150],150],[[147,147],147],[[151,151],151],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[208,208],[209,209],[210,210],[211,211],[212,212],[213,213],[214,214],[215,215],[216,216],[217,217],[218,218],[219,219],[220,220],[221,221],[222,222],[223,223],[224,224],[225,225],[226,226],[227,227],[228,228],[229,229],[230,230],[231,231],[232,232],[233,233],[234,234],[235,235],[236,236],[237,237],[238,238],[239,239],[240,240],[241,241],[242,242],[243,243],[244,244],[245,245],[246,246],[247,247],[248,248],[249,249],[250,250],[251,251],[252,252],[253,253],[254,254],[255,255],[256,256],[257,257],[258,258],[259,259],[260,260],[261,261],[262,262],[263,263],[264,264],[[[265,[176]]],[[265,[176]]]],[266,266],[267,267],[268,268],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],265],[[208,208],1],[[209,209],1],[[210,210],1],[[211,211],1],[[212,212],1],[[213,213],1],[[214,214],1],[[215,215],1],[[216,216],1],[[217,217],1],[[218,218],1],[[219,219],1],[[220,220],1],[[221,221],1],[[222,222],1],[[223,223],1],[[224,224],1],[[225,225],1],[[226,226],1],[[227,227],1],[[228,228],1],[[229,229],1],[[230,230],1],[[231,231],1],[[232,232],1],[[233,233],1],[[234,234],1],[[235,235],1],[[236,236],1],[[237,237],1],[[238,238],1],[[239,239],1],[[240,240],1],[[241,241],1],[[242,242],1],[[243,243],1],[[244,244],1],[[245,245],1],[[246,246],1],[[247,247],1],[[248,248],1],[[249,249],1],[[250,250],1],[[251,251],1],[[252,252],1],[[253,253],1],[[254,254],1],[[255,255],1],[[256,256],1],[[257,257],1],[[258,258],1],[[259,259],1],[[260,260],1],[[261,261],1],[[262,262],1],[[263,263],1],[[266,266],1],[[267,267],1],[[268,268],1],[[208,12],13],[[209,12],13],[[210,12],13],[[211,12],13],[[212,12],13],[[213,12],13],[[214,12],13],[[215,12],13],[[216,12],13],[[217,12],13],[[218,12],13],[[219,12],13],[[220,12],13],[[221,12],13],[[222,12],13],[[223,12],13],[[224,12],13],[[225,12],13],[[226,12],13],[[227,12],13],[[228,12],13],[[229,12],13],[[230,12],13],[[231,12],13],[[232,12],13],[[233,12],13],[[234,12],13],[[235,12],13],[[236,12],13],[[237,12],13],[[238,12],13],[[239,12],13],[[240,12],13],[[241,12],13],[[242,12],13],[[243,12],13],[[244,12],13],[[245,12],13],[[246,12],13],[[247,12],13],[[248,12],13],[[249,12],13],[[250,12],13],[[251,12],13],[[252,12],13],[[253,12],13],[[254,12],13],[[255,12],13],[[256,12],13],[[257,12],13],[[258,12],13],[[259,12],13],[[260,12],13],[[261,12],13],[[262,12],13],[[263,12],13],[[264,12],13],[[[265,[188]],12],13],[[266,12],13],[[267,12],13],[[268,12],13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[208,5],[[3,[1]]]],[[209,5],[[3,[1]]]],[[210,5],[[3,[1]]]],[[211,5],[[3,[269]]]],[[214,5],[[3,[204]]]],[[215,5],[[3,[1]]]],[[216,5],[[3,[35]]]],[[217,5],[[3,[35]]]],[[218,5],[[3,[35]]]],[[219,5],[[3,[1]]]],[[220,5],[[3,[132]]]],[[221,5],[[3,[132]]]],[[222,5],[[3,[1]]]],[[223,5],[[3,[1]]]],[[224,5],[[3,[25]]]],[[225,5],[[3,[1]]]],[[226,5],[[3,[1]]]],[[227,5],[[3,[183]]]],[[228,5],[[3,[36]]]],[[229,5],[[3,[36]]]],[[230,5],[[3,[36]]]],[[231,5],[[3,[36]]]],[[232,5],[[3,[36]]]],[[233,5],[[3,[2]]]],[[234,5],[[3,[2]]]],[[237,5],[[3,[172]]]],[[238,5],[[3,[1]]]],[[239,5],[[3,[45]]]],[[240,5],[[3,[167]]]],[[241,5],[[3,[165]]]],[[242,5],[[3,[150]]]],[[243,5],[[3,[1]]]],[[244,5],[[3,[1]]]],[[245,5],[[3,[1]]]],[[246,5],[[3,[36]]]],[[247,5],[[3,[1]]]],[[248,5],[[3,[45]]]],[[249,5],[[3,[1]]]],[[250,5],[[3,[1]]]],[[251,5],[[3,[1]]]],[[252,5],[[3,[35]]]],[[253,5],[[3,[1]]]],[[254,5],[[3,[270]]]],[[255,5],[[3,[35]]]],[[256,5],[[3,[1]]]],[[257,5],[[3,[1]]]],[[258,5],[[3,[1]]]],[[259,5],[[3,[35]]]],[[260,5],[[3,[35]]]],[[261,5],[[3,[35]]]],[[262,5],[[3,[1]]]],[[263,5],[[3,[1]]]],[[266,5],[[3,[36]]]],[208],[209],[210],[211],[212],[213],[214],[215],[216],[217],[218],[219],[220],[221],[222],[223],[224],[225],[226],[227],[228],[229],[230],[231],[232],[233],[234],[235],[236],[237],[238],[239],[240],[241],[242],[243],[244],[245],[246],[247],[248],[249],[250],[251],[252],[253],[254],[255],[256],[257],[258],[259],[260],[261],[262],[263],[266],[267],[268],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[208,5,1],3],[[209,5,1],3],[[210,5,1],3],[[211,5,269],3],[[212,5,174],3],[[213,5,174],3],[[214,5,204],3],[[215,5,1],3],[[216,5,35],3],[[217,5,35],3],[[218,5,35],3],[[219,5,1],3],[[220,5,132],3],[[221,5,132],3],[[222,5,1],3],[[223,5,1],3],[[225,5,1],3],[[226,5,1],3],[[228,5,36],3],[[229,5,36],3],[[230,5,36],3],[[231,5,36],3],[[232,5,36],3],[[233,5,2],3],[[234,5,2],3],[[235,5,2],3],[[236,5,2],3],[[239,5,45],3],[[242,5,150],3],[[243,5,1],3],[[244,5,1],3],[[245,5,1],3],[[246,5,36],3],[[247,5,1],3],[[248,5,45],3],[[249,5,1],3],[[250,5,1],3],[[251,5,1],3],[[252,5,35],3],[[253,5,1],3],[[254,5,270],3],[[255,5,35],3],[[256,5,1],3],[[257,5,1],3],[[258,5,1],3],[[260,5,35],3],[[261,5,35],3],[[262,5,1],3],[[263,5,1],3],[[264,5,2],3],[[265,5],3],[[266,5,36],3],[[267,5,175],3],[[268,5,175],3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,0,[[271,272],25],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],273],[[],17],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[273,274],[17,274],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[272,272],[273,273],[17,17],[275,275],[276,276],[[]],[[]],[[]],[[]],[[]],[[273,273],40],[[17,17],40],[273,273],[17,17],[[273,273],1],[[17,17],1],0,[[273,273],273],[[17,17],17],[[],273],[[],17],[[272,272],1],[[273,273],1],[[17,17],1],[[273,43]],[[17,43]],[[5,17],3],[[[11,[5]],17,275],3],[[272,12],[[18,[71]]]],[[273,12],13],[[273,12],13],[[273,12],13],[[273,12],13],[[273,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[17,12],13],[[275,12],13],[[276,12],13],[[]],[[]],[[]],[[]],[[]],[274,[[11,[273]]]],[274,[[11,[17]]]],[274,273],[274,17],[274,273],[274,17],[43,273],[43,17],[5,[[3,[272]]]],[[5,29],[[3,[272]]]],[[5,64,64],3],[272],[273],[17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[273,273],1],[[17,17],1],[[]],[[]],[[]],[[]],[[]],[273,1],[17,1],[273,1],[17,1],[[],[[3,[272]]]],[[132,132],3],[277,15],[[15,15],277],[277,15],[[5,17],3],[[273,17,277],3],[[5,273,17,277],3],0,[273,273],[17,17],[[273,273],[[11,[40]]]],[[17,17],[[11,[40]]]],[[273,273]],[[17,17]],[[273,273,1]],[[17,17,1]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[3,[272]]]],[[273,273],273],[[17,17],17],[[273,273]],[[17,17]],[[273,273],273],[[17,17],17],[[]],[[]],[[]],[[]],[[]],[[273,273]],[[17,17]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[17,17],[[273,273],273],[[17,17],17],[[[11,[5]],64,64,276],3],[[132,132],3],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[278,279],[278,15],[278,15],[278,15],[[]],[[]],[[]],[[]],[278,278],[280,280],[[]],[[]],[[280,280],1],[278,15],[278,15],[278,281],[278,280],[278,282],[[278,12],13],[[280,12],13],[[]],[[]],0,[[],[[3,[278]]]],[[]],[[]],[278,279],[278,279],[[],[[3,[278]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],282],[[282,282],282],[[282,282]],[[282,282],282],[[282,282]],[282,52],[[282,282],282],[[282,282]],[283,52],[283,284],[283,284],[283,284],[[]],[[]],[[]],[[]],[282,282],[283,283],[[]],[[]],[[282,282],40],[282,282],[[282,282],1],[[],282],[[282,282],282],[[],282],[[282,282],1],[[283,283],1],[[282,43]],[283,285],[283,285],[283,285],[283,52],[283,282],[[282,12],13],[[282,12],13],[[282,12],13],[[282,12],13],[[282,12],13],[[283,12],13],[283,52],[[]],[[]],[52,[[11,[282]]]],[52,282],[52,282],[43,282],[[],[[3,[283]]]],[282],[283],[[282,282]],[[282,282],282],[[282,282],1],[[]],[[]],[282,1],[282,1],[283,52],[282,282],[[282,282],[[11,[40]]]],[[282,282]],[[282,282,1]],[[],[[3,[283]]]],[[282,282],282],[[282,282]],[[282,282],282],[[]],[[]],[[282,282]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[282,282],282],0,[[]],[[]],[286,286],[[]],[[286,286],1],[[286,12],13],[[]],[286],[[]],[286],[286,202],[286,15],[286,15],[286,15],[286,15],[[],[[3,[286]]]],[[]],[[],18],[[],18],[[],19],[286,287],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],288],[[],289],[[],290],[[],291],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[288,292],[289,292],[290,292],[291,292],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[73,293],[73,293],[73],[[73,293],3],[[73,293],3],[[73,293],3],[73,73],[293,293],[294,294],[295,295],[296,296],[297,297],[288,288],[289,289],[290,290],[291,291],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[293,293],40],[[294,294],40],[[295,295],40],[[296,296],40],[[297,297],40],[[288,288],40],[[289,289],40],[[290,290],40],[[291,291],40],[288,288],[289,289],[290,290],[291,291],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],0,0,[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[],288],[[],289],[[],290],[[],291],[[73,73],1],[[293,293],1],[[294,294],1],[[295,295],1],[[296,296],1],[[297,297],1],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],[[288,43]],[[289,43]],[[290,43]],[[291,43]],[[73,12],13],[[293,12],13],[[294,12],13],[[295,12],13],[[296,12],13],[[297,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[288,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[289,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[290,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[[291,12],13],[298,73],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[292,[[11,[288]]]],[292,[[11,[289]]]],[292,[[11,[290]]]],[292,[[11,[291]]]],[292,288],[292,289],[292,290],[292,291],[292,288],[292,289],[292,290],[292,291],[43,288],[43,289],[43,290],[43,291],[293],[294],[295],[296],[297],[288],[289],[290],[291],0,[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288],1],[[289,289],1],[[290,290],1],[[291,291],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[288,1],[289,1],[290,1],[291,1],[288,1],[289,1],[290,1],[291,1],0,0,[288,288],[289,289],[290,290],[291,291],0,[[293,293],[[11,[40]]]],[[294,294],[[11,[40]]]],[[295,295],[[11,[40]]]],[[296,296],[[11,[40]]]],[[297,297],[[11,[40]]]],[[288,288],[[11,[40]]]],[[289,289],[[11,[40]]]],[[290,290],[[11,[40]]]],[[291,291],[[11,[40]]]],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288,1]],[[289,289,1]],[[290,290,1]],[[291,291,1]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],[5,3],[[5,296],3],[[5,295],3],[5,[[3,[73]]]],[5,[[3,[74]]]],[[5,35],3],[[5,294,73],3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[288,288]],[[289,289]],[[290,290]],[[291,291]],[[],18],[[],18],[299,[[3,[293]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[288,288],288],[[289,289],289],[[290,290],290],[[291,291],291],0,0,0,[[64,64],64],[[132,132],132],[64,300],[132,301],[64,300],[132,301],[[]],[[]],[[]],[[]],[64,64],[132,132],[[]],[[]],[[64,64],40],[[132,132],40],[[64,25],64],[[132,25],132],[[64,64],1],[[132,132],1],[[64,12],13],[[64,12],13],[[132,12],13],[[132,12],13],[300,64],[287,64],[[]],[301,132],[[]],[287,64],[300,64],[64],[132],[302],[[]],[[]],[302],[302,64],[302,132],[302],[302,64],[302,132],[302],[[64,25],64],[[132,25],132],[302],[302,64],[302,132],[64,64],[132,132],[[303,117],64],[[303,304],132],[[],302],[[],302],[64,302],[132,302],[[],302],[64,302],[132,302],[[],302],[[],302],[64,302],[132,302],[[],302],[64,302],[132,302],[[64,64],[[11,[40]]]],[[132,132],[[11,[40]]]],[302],[302,64],[302,132],[[64,64],64],[[132,132],132],0,0,[[]],[[]],[[],22],[[],22],[[],18],[[],18],[[],18],[[],18],[64,117],[64,303],[132,303],[132,304],[[],19],[[],19],[[]],0,0,0,0,0,0,[[]],[[]],[305],[[305,12],13],[[]],[305,[[3,[[11,[306]]]]]],[[]],[[307,88],[[3,[305]]]],[305,25],[[305,306,308],3],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],308],[[],309],[310,5],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[308,35],[309,35],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[306,306],[308,308],[311,311],[309,309],[[]],[[]],[[]],[[]],[[308,308],40],[[311,311],40],[[309,309],40],[308,308],[309,309],[[308,308],1],[[309,309],1],[[308,308],308],[[309,309],309],[310],[[],308],[[],309],[[306,306],1],[[308,308],1],[[311,311],1],[[309,309],1],[[308,43]],[[309,43]],[[306,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[308,12],13],[[310,12],13],[[311,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[309,12],13],[[]],[[]],[[]],[[]],[[]],[35,[[11,[308]]]],[35,[[11,[309]]]],[35,308],[35,309],[35,308],[35,309],[43,308],[43,309],[5,310],[310,[[3,[[11,[306]]]]]],[308],[311],[309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[308,308],1],[[309,309],1],[[]],[[]],[[]],[[]],[[]],[308,1],[309,1],[308,1],[309,1],[[311,309],[[3,[310]]]],[308,308],[309,309],[[308,308],[[11,[40]]]],[[311,311],[[11,[40]]]],[[309,309],[[11,[40]]]],[[308,308]],[[309,309]],[[308,308,1]],[[310,306,308],3],[[309,309,1]],[[308,308],308],[[309,309],309],[[308,308]],[[309,309]],[[308,308],308],[[309,309],309],[[]],[[]],[[]],[[]],[[308,308]],[[309,309]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[308,308],308],[[309,309],309],[310,3],[310,3],0,0,0,0,0,0,[312],0,[[]],[[]],[[]],[[]],[313,313],[[[312,[176]]],[[312,[176]]]],[[]],[[]],[[313,313],1],[[[312,[27]],312],1],[[313,12],13],[[[312,[188]],12],13],[[]],[[]],[[],312],[[],312],[313],[[[312,[314]]]],[[]],[[]],0,[[5,44],[[3,[2]]]],[[5,44],[[3,[2]]]],[74,[[3,[2]]]],[74,[[3,[2]]]],[[5,44],[[3,[2]]]],[[5,44],[[3,[2]]]],[5,[[3,[2]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[5,[[3,[2]]]],0,[[]],[[]],[315,315],[[]],[315,316],[[315,315],1],[[315,12],13],[[]],[315],[[]],[315,316],[315,316],[315,316],[315,316],[[]],[[],18],[[],18],[[],19],[[],[[3,[315]]]],[315,316],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],317],[[317,317],317],[[317,317]],[[317,317],317],[[317,317]],[317,35],[[317,317],317],[[317,317]],[[]],[[]],[[]],[[]],[[]],[[]],[317,317],[318,318],[319,319],[[]],[[]],[[]],[[317,317],40],[[319,319],40],[317,317],[[317,317],1],[[317,317],317],[[],317],[[317,317],1],[[318,318],1],[[319,319],1],[[317,43]],[[317,12],13],[[317,12],13],[[317,12],13],[[317,12],13],[[317,12],13],[[318,12],13],[[319,12],13],[[]],[[]],[[]],[35,[[11,[317]]]],[35,317],[35,317],[43,317],[[74,25],[[3,[318]]]],[317],[318],[319],[[317,317]],[[317,317],317],[[317,317],1],[[]],[[]],[[]],[317,1],[317,1],[317,317],[[317,317],[[11,[40]]]],[[319,319],[[11,[40]]]],[318,[[11,[74]]]],[[317,317]],[[317,317,1]],[[317,317],317],[[317,317]],[[317,317],317],[[]],[[]],[[]],[[317,317]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[317,317],317],[[],[[3,[318]]]],[[319,317],[[3,[318]]]],[[[72,[[11,[74]]]],[11,[317]]],[[3,[318]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[307,320],[[]],[[]],[74,[[3,[307]]]],[307,[[3,[64]]]],[307,[[3,[64]]]],[[307,64],3],[307,307],[[]],[[307,307],40],[[307,307],1],[[307,12],13],[[307,12],13],[320,307],[[]],[320,307],[307],[[]],[307,[[3,[64]]]],[[307,307],[[11,[40]]]],[74,[[3,[307]]]],[307,[[3,[64]]]],[[307,64],3],[[]],[[],22],[[],18],[[],18],[[],19],0,[[]],[[]],[321,321],[[]],[[321,321],1],[[321,12],13],[[]],[[],[[3,[321]]]],[321],[[]],[321,3],[321,65],[321,65],[[]],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[322,3],0,0,[[],322],[323,207],[324,201],[74,75],[[322,322],322],[[322,322]],[[322,322],322],[[322,322]],[322,35],[[322,322],322],[[322,322]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[[11,[323]],[11,[324]]],3],[[],3],[323,323],[324,324],[74,74],[325,325],[326,326],[327,327],[328,328],[329,329],[330,330],[331,331],[332,332],[333,333],[322,322],[334,334],[335,335],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,3],[[74,74],40],[[322,322],40],[322,322],[[322,322],1],[[],323],[[],324],[[1,1],3],[[322,322],322],0,[5,[[3,[5]]]],[[5,5],[[3,[5]]]],[[5,5,16],[[3,[5]]]],[322,3],[[],323],[[],324],0,0,[[],322],[[323,323],1],[[324,324],1],[[74,74],1],[[330,330],1],[[331,331],1],[[332,332],1],[[333,333],1],[[322,322],1],[[334,334],1],[[335,335],1],[10,[[3,[126]]]],[10,[[3,[126]]]],[[5,10,29],[[3,[126]]]],[10,[[3,[126]]]],[10,[[3,[126]]]],[[322,43]],[[[11,[5]],322,29],3],[5,3],[[5,[11,[323]],[11,[324]]],3],[[[11,[5]],[11,[323]],[11,[324]],326],3],[5,3],[5,[[3,[126]]]],[[323,12],13],[[323,12],13],[[324,12],13],[[324,12],13],[[74,12],13],[[74,12],13],[[325,12],13],[[326,12],13],[[327,12],13],[[328,12],13],[[329,12],13],[[330,12],13],[[331,12],13],[[332,12],13],[[333,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[322,12],13],[[334,12],13],[[335,12],13],[[],[[3,[325]]]],[[5,330],[[3,[[11,[117]]]]]],[207,323],[[]],[[]],[201,324],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[336,334],[[]],[337,335],[35,[[11,[322]]]],[35,322],[35,322],[324,[[3,[[11,[335]]]]]],[43,322],[24,[[3,[[11,[334]]]]]],[24,[[3,[[11,[335]]]]]],[207,323],[201,324],[75,74],[323,[[3,[[11,[334]]]]]],[5,3],[[5,44],3],0,[[],[[3,[338]]]],[[],324],[[],323],[[],324],[[10,324],[[3,[[100,[324]]]]]],[[],[[3,[[100,[324]]]]]],[[],[[3,[45]]]],[[[11,[74]]],[[3,[74]]]],[[],74],[[],74],[[],74],[[],[[3,[333]]]],[[],[[3,[332]]]],[[[11,[74]]],[[3,[74]]]],[[],74],[[],323],0,0,[323],[324],[74],[330],[331],[322],[[10,324],3],[[322,322]],[[322,322],322],[[322,322],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[322,1],[325,1],[322,1],[325,1],[323,1],[5,[[3,[1]]]],[[[11,[5]],[11,[5]],328],3],[[5,44,327],[[3,[44]]]],[[5,135,327],[[3,[135]]]],0,[17,3],[17,3],[[[11,[5]],17],3],[[],3],0,0,[322,322],[[],74],[[74,74],[[11,[40]]]],[[322,322],[[11,[40]]]],0,0,[330,[[3,[[11,[117]]]]]],[[]],[[],[[18,[339]]]],[16,3],[[],3],[5,[[3,[2]]]],0,0,[[322,322]],0,0,[[322,322,1]],[324,3],[323,3],[324,324],[323,323],[324,3],[[],3],[[[340,[316]]],3],[[74,74],3],[[324,324,324],3],[[323,323,323],3],[[],[[3,[74]]]],[323,3],0,[37,37],[[322,322],322],[[322,322]],[[[11,[5]]],3],[[322,322],322],[[]],[5,3],[331,[[3,[[11,[117]]]]]],[35,[[3,[74]]]],[[35,74],3],[[],74],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[],22],[[],22],[[322,322]],[44,3],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[5,[[3,[338]]]],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],0,[[322,322],322],[[],3],[[[11,[5]],329],3],[5,[[3,[2]]]],0,[[],3],[[],3],[[],[[11,[37]]]],[37,[[11,[37]]]]],"p":[[15,"bool"],[15,"usize"],[6,"Result"],[3,"Dir"],[6,"RawFd"],[3,"OwningIter"],[3,"Entry"],[4,"Type"],[3,"Iter"],[3,"CStr"],[4,"Option"],[3,"Formatter"],[6,"Result"],[8,"IntoRawFd"],[15,"u64"],[3,"OFlag"],[3,"Mode"],[4,"Result"],[3,"TypeId"],[3,"ClearEnvError"],[3,"Demand"],[3,"String"],[4,"Errno"],[15,"str"],[15,"i32"],[8,"ErrnoSentinel"],[8,"PartialEq"],[3,"Error"],[3,"AtFlags"],[3,"RenameFlags"],[3,"SealFlag"],[3,"FdFlag"],[3,"SpliceFFlags"],[3,"FallocateFlags"],[6,"c_int"],[15,"u32"],[6,"c_uint"],[4,"FlockArg"],[4,"PosixFadviseAdvice"],[4,"Ordering"],[6,"loff_t"],[4,"FcntlArg"],[8,"IntoIterator"],[6,"off_t"],[3,"OsString"],[3,"InterfaceAddress"],[3,"InterfaceAddressIterator"],[3,"ModuleInitFlags"],[3,"DeleteModuleFlags"],[3,"MsFlags"],[3,"MntFlags"],[6,"c_ulong"],[3,"MQ_OFlag"],[3,"MqAttr"],[6,"mq_attr_member_t"],[3,"MqdT"],[3,"InterfaceFlags"],[3,"Interfaces"],[3,"Interface"],[3,"InterfacesIter"],[3,"PollFd"],[3,"PollFlags"],[6,"c_short"],[3,"TimeSpec"],[3,"SigSet"],[3,"PtyMaster"],[3,"Winsize"],[3,"OpenptyResult"],[3,"ForkptyResult"],[6,"Result"],[3,"Error"],[8,"Into"],[3,"Termios"],[3,"Pid"],[6,"SessionId"],[3,"CloneFlags"],[6,"CloneCb"],[3,"CpuSet"],[4,"AioCancelStat"],[3,"Pin"],[3,"AioWrite"],[3,"AioRead"],[3,"AioFsync"],[3,"aiocb"],[4,"LioMode"],[4,"AioFsyncMode"],[4,"SigevNotify"],[3,"SigEvent"],[3,"EpollFlags"],[3,"EpollCreateFlags"],[4,"EpollOp"],[3,"EpollEvent"],[15,"isize"],[3,"EfdFlags"],[3,"Inotify"],[3,"AddWatchFlags"],[3,"WatchDescriptor"],[3,"InitFlags"],[3,"InotifyEvent"],[3,"Vec"],[3,"MemFdCreateFlag"],[3,"ProtFlags"],[3,"MapFlags"],[3,"MRemapFlags"],[3,"MsFlags"],[3,"MlockAllFlags"],[4,"MmapAdvise"],[4,"c_void"],[6,"size_t"],[3,"NonZeroUsize"],[3,"Persona"],[6,"Pthread"],[3,"Options"],[4,"Request"],[4,"Event"],[4,"Signal"],[6,"c_long"],[3,"user_regs_struct"],[3,"siginfo_t"],[6,"AddressType"],[3,"QuotaValidFlags"],[3,"Dqblk"],[4,"QuotaType"],[4,"QuotaFmt"],[4,"RebootMode"],[4,"Infallible"],[3,"Usage"],[3,"rusage"],[4,"Resource"],[4,"UsageWho"],[6,"rlim_t"],[3,"TimeVal"],[3,"FdSet"],[3,"Fds"],[6,"off64_t"],[3,"SaFlags"],[3,"sigevent"],[3,"sigset_t"],[3,"SignalIterator"],[4,"SigmaskHow"],[3,"SigSetIter"],[4,"SigHandler"],[3,"SigAction"],[3,"SfdFlags"],[3,"SignalFd"],[3,"siginfo"],[3,"SockFlag"],[3,"LinkAddr"],[3,"AlgAddr"],[3,"TimestampingFlag"],[3,"MsgFlags"],[3,"Ipv4Addr"],[3,"UnixAddr"],[19,"SockaddrStorage"],[4,"SockAddr"],[3,"sockaddr_un"],[3,"NetlinkAddr"],[3,"sockaddr"],[3,"sockaddr_nl"],[3,"sockaddr_alg"],[3,"sockaddr_ll"],[3,"VsockAddr"],[3,"sockaddr_vm"],[3,"SockaddrIn6"],[3,"sockaddr_in6"],[3,"SockaddrIn"],[3,"sockaddr_in"],[8,"SockaddrLike"],[3,"sockaddr_storage"],[3,"msghdr"],[3,"cmsghdr"],[4,"SockType"],[4,"SockProtocol"],[3,"IpMembershipRequest"],[3,"Ipv6MembershipRequest"],[8,"Clone"],[3,"RecvMsg"],[3,"CmsgIterator"],[4,"ControlMessageOwned"],[3,"Timestamps"],[4,"ControlMessage"],[4,"Shutdown"],[3,"UnixCredentials"],[3,"Ipv6Addr"],[4,"IpAddr"],[4,"InetAddr"],[4,"AddressFamily"],[8,"Debug"],[3,"MultiHeaders"],[3,"MultiResults"],[3,"IoSliceIterator"],[3,"SocketAddrV4"],[4,"SocketAddr"],[3,"SocketAddrV6"],[3,"ucred"],[6,"socklen_t"],[3,"Ipv6Addr"],[3,"Ipv4Addr"],[4,"IpAddr"],[8,"GetSockOpt"],[6,"gid_t"],[15,"u16"],[6,"in_addr_t"],[15,"u8"],[3,"Path"],[8,"SetSockOpt"],[6,"uid_t"],[3,"ReuseAddr"],[3,"ReusePort"],[3,"TcpNoDelay"],[3,"Linger"],[3,"IpAddMembership"],[3,"IpDropMembership"],[3,"IpMulticastTtl"],[3,"IpMulticastLoop"],[3,"Priority"],[3,"IpTos"],[3,"Ipv6TClass"],[3,"IpFreebind"],[3,"ReceiveTimeout"],[3,"SendTimeout"],[3,"Broadcast"],[3,"OobInline"],[3,"SocketError"],[3,"DontRoute"],[3,"KeepAlive"],[3,"PeerCredentials"],[3,"TcpKeepIdle"],[3,"TcpKeepCount"],[3,"TcpRepair"],[3,"TcpKeepInterval"],[3,"TcpUserTimeout"],[3,"RcvBuf"],[3,"SndBuf"],[3,"RcvBufForce"],[3,"SndBufForce"],[3,"SockType"],[3,"AcceptConn"],[3,"BindToDevice"],[3,"OriginalDst"],[3,"Ip6tOriginalDst"],[3,"Timestamping"],[3,"ReceiveTimestamp"],[3,"ReceiveTimestampns"],[3,"IpTransparent"],[3,"Mark"],[3,"PassCred"],[3,"TcpCongestion"],[3,"Ipv4PacketInfo"],[3,"Ipv6RecvPacketInfo"],[3,"Ipv4OrigDstAddr"],[3,"UdpGsoSegment"],[3,"UdpGroSegment"],[3,"TxTime"],[3,"RxqOvfl"],[3,"Ipv6V6Only"],[3,"Ipv4RecvErr"],[3,"Ipv6RecvErr"],[3,"IpMtu"],[3,"Ipv4Ttl"],[3,"Ipv6Ttl"],[3,"Ipv6OrigDstAddr"],[3,"Ipv6DontFrag"],[3,"AlgSetAeadAuthSize"],[3,"AlgSetKey"],[3,"TcpMaxSeg"],[3,"Ipv6AddMembership"],[3,"Ipv6DropMembership"],[3,"linger"],[3,"sock_txtime"],[15,"i8"],[3,"FileStat"],[3,"SFlag"],[6,"mode_t"],[4,"FchmodatFlags"],[4,"UtimensatFlags"],[6,"dev_t"],[3,"Statfs"],[6,"__fsword_t"],[3,"FsType"],[6,"fsid_t"],[3,"FsFlags"],[3,"Statvfs"],[6,"fsblkcnt_t"],[6,"fsfilcnt_t"],[3,"SysInfo"],[3,"Duration"],[3,"InputFlags"],[3,"OutputFlags"],[3,"ControlFlags"],[3,"LocalFlags"],[6,"tcflag_t"],[4,"BaudRate"],[4,"SetArg"],[4,"FlushArg"],[4,"FlowArg"],[4,"SpecialCharacterIndices"],[3,"termios"],[6,"speed_t"],[3,"timespec"],[3,"timeval"],[15,"i64"],[6,"time_t"],[6,"suseconds_t"],[3,"Timer"],[4,"Expiration"],[3,"ClockId"],[3,"TimerSetTimeFlags"],[3,"TimerFlags"],[3,"TimerFd"],[4,"ClockId"],[3,"IoVec"],[3,"RemoteIoVec"],[8,"Hash"],[3,"UtsName"],[3,"OsStr"],[3,"WaitPidFlag"],[4,"WaitStatus"],[4,"Id"],[6,"clockid_t"],[3,"UContext"],[3,"AccessFlags"],[3,"Uid"],[3,"Gid"],[4,"ForkResult"],[4,"FchownatFlags"],[4,"Whence"],[4,"LinkatFlags"],[4,"UnlinkatFlags"],[4,"PathconfVar"],[4,"SysconfVar"],[3,"ResUid"],[3,"ResGid"],[3,"User"],[3,"Group"],[3,"passwd"],[3,"group"],[3,"PathBuf"],[6,"Error"],[8,"AsRef"],[8,"NixPath"],[13,"F_DUPFD"],[13,"F_DUPFD_CLOEXEC"],[13,"F_SETFD"],[13,"F_SETFL"],[13,"F_SETLK"],[13,"F_SETLKW"],[13,"F_GETLK"],[13,"F_OFD_SETLK"],[13,"F_OFD_SETLKW"],[13,"F_OFD_GETLK"],[13,"F_ADD_SEALS"],[13,"F_SETPIPE_SZ"],[8,"Aio"],[13,"Handler"],[13,"SigAction"],[13,"SigevSignal"],[13,"SigevThreadId"],[13,"ScmRights"],[13,"ScmCredentials"],[13,"AlgSetIv"],[13,"AlgSetOp"],[13,"AlgSetAeadAssoclen"],[13,"UdpGsoSegments"],[13,"Ipv4PacketInfo"],[13,"Ipv6PacketInfo"],[13,"RxqOvfl"],[13,"TxTime"],[13,"ScmRights"],[13,"ScmCredentials"],[13,"ScmTimestamp"],[13,"ScmTimestampsns"],[13,"ScmTimestampns"],[13,"Ipv4PacketInfo"],[13,"Ipv6PacketInfo"],[13,"Ipv4OrigDstAddr"],[13,"Ipv6OrigDstAddr"],[13,"UdpGroSegments"],[13,"RxqOvfl"],[13,"Ipv4RecvErr"],[13,"Ipv6RecvErr"],[13,"V4"],[13,"V6"],[13,"V4"],[13,"V6"],[13,"Inet"],[13,"Unix"],[13,"Netlink"],[13,"Alg"],[13,"Link"],[13,"Vsock"],[8,"TimeValLike"],[13,"OneShot"],[13,"IntervalDelayed"],[13,"Interval"],[13,"Pid"],[13,"PGid"],[13,"PIDFd"],[13,"Exited"],[13,"Signaled"],[13,"Stopped"],[13,"PtraceEvent"],[13,"PtraceSyscall"],[13,"Continued"],[13,"Parent"]],"a":{"fdopendir":[90],"getegid":[6952],"geteuid":[6951],"getgid":[6943],"getpid":[7135],"getppid":[7095],"getuid":[6942],"sigaddset":[3038],"sigdelset":[3191],"sigemptyset":[3072,3101],"sigfillset":[3040],"sigismember":[3098],"timer_create":[6272],"timer_getoverrun":[6273],"timer_gettime":[6270],"timer_settime":[6274],"timerfd_create":[6397],"timerfd_gettime":[6378],"timerfd_settime":[6406,6437]}},\ -"once_cell":{"doc":"Overview","t":[0,0,0,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["race","sync","unsync","OnceBool","OnceBox","OnceNonZeroUsize","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","default","default","default","drop","fmt","fmt","fmt","from","from","from","get","get","get","get_or_init","get_or_init","get_or_init","get_or_try_init","get_or_try_init","get_or_try_init","into","into","into","new","new","new","set","set","set","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","Lazy","OnceCell","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","force","force_mut","from","from","from","from","get","get","get_mut","get_mut","get_or_init","get_or_try_init","get_unchecked","into","into","into_inner","into_value","new","new","set","take","to_owned","try_from","try_from","try_insert","try_into","try_into","type_id","type_id","wait","with_value","Lazy","OnceCell","borrow","borrow","borrow_mut","borrow_mut","clone","clone_from","clone_into","default","default","deref","deref_mut","eq","fmt","fmt","force","force_mut","from","from","from","from","get","get","get_mut","get_mut","get_or_init","get_or_try_init","into","into","into_inner","into_value","new","new","set","take","to_owned","try_from","try_from","try_insert","try_into","try_into","type_id","type_id","with_value"],"q":["once_cell","","","once_cell::race","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","once_cell::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","once_cell::unsync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Thread-safe, non-blocking, “first one wins” flavor of …","Thread-safe, blocking version of OnceCell.","Single-threaded version of OnceCell.","A thread-safe cell which can be written to only once.","A thread-safe cell which can be written to only once.","A thread-safe cell which can be written to only once.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Gets a reference to the underlying value.","Gets the underlying value.","Gets the underlying value.","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Creates a new empty cell.","Creates a new empty cell.","Creates a new empty cell.","Sets the contents of this cell to value.","Sets the contents of this cell to value.","Sets the contents of this cell to value.","","","","","","","","","","A value which is initialized on the first access.","A thread-safe cell which can be written to only once.","","","","","","","","","Creates a new lazy value using Default as the initializing …","","","","","","Forces the evaluation of this lazy value and returns a …","Forces the evaluation of this lazy value and returns a …","","","Returns the argument unchanged.","Returns the argument unchanged.","Gets the reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets the mutable reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Get the reference to the underlying value, without …","Calls U::from(self).","Calls U::from(self).","Consumes the OnceCell, returning the wrapped value. Returns","Consumes this Lazy returning the stored value.","Creates a new empty cell.","Creates a new lazy value with the given initializing …","Sets the contents of this cell to value.","Takes the value out of this OnceCell, moving it back to an …","","","","Like set, but also returns a reference to the final cell …","","","","","Gets the reference to the underlying value, blocking the …","Creates a new initialized cell.","A value which is initialized on the first access.","A cell which can be written to only once. It is not thread …","","","","","","","","","Creates a new lazy value using Default as the initializing …","","","","","","Forces the evaluation of this lazy value and returns a …","Forces the evaluation of this lazy value and returns a …","Returns the argument unchanged.","","","Returns the argument unchanged.","Gets a reference to the underlying value.","Gets the reference to the result of this lazy value if it …","Gets a mutable reference to the underlying value.","Gets the mutable reference to the result of this lazy …","Gets the contents of the cell, initializing it with f if …","Gets the contents of the cell, initializing it with f if …","Calls U::from(self).","Calls U::from(self).","Consumes the OnceCell, returning the wrapped value.","Consumes this Lazy returning the stored value.","Creates a new empty cell.","Creates a new lazy value with the given initializing …","Sets the contents of this cell to value.","Takes the value out of this OnceCell, moving it back to an …","","","","Like set, but also returns a reference to the final cell …","","","","","Creates a new initialized cell."],"i":[0,0,0,0,0,0,1,2,3,1,2,3,1,2,3,1,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,0,0,13,15,13,15,13,13,13,13,15,15,15,13,13,15,15,15,13,13,13,15,13,15,13,15,13,13,13,13,15,13,15,13,15,13,13,13,13,15,13,13,15,13,15,13,13,0,0,20,21,20,21,20,20,20,20,21,21,21,20,20,21,21,21,20,20,20,21,20,21,20,21,20,20,20,21,20,21,20,21,20,20,20,20,21,20,20,21,20,21,20],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[],2],[[],3],[1],[[1,4],5],[[2,4],5],[[3,4],5],[[]],[[]],[[]],[1,6],[2,[[6,[7]]]],[3,[[6,[8]]]],[1],[2,7],[3,8],[1,9],[2,[[9,[7]]]],[3,[[9,[8]]]],[[]],[[]],[[]],[[],1],[[],2],[[],3],[[1,10],[[9,[10]]]],[[2,7],9],[[3,8],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],11],[[],11],[[],11],0,0,[[]],[[]],[[]],[[]],[[[13,[12]]],[[13,[12]]]],[[[13,[12]],[13,[12]]]],[[]],[[],13],[[],[[15,[14]]]],[[[15,[16]]]],[[[15,[16]]]],[[[13,[17]],13],8],[[[13,[18]],4],5],[[[15,[18]],4],5],[15],[15],[[],13],[19],[[]],[[]],[13,6],[15,6],[13,6],[15,6],[13],[13,9],[13],[[]],[[]],[13,6],[15,9],[[],13],[[],15],[13,9],[13,6],[[]],[[],9],[[],9],[13,9],[[],9],[[],9],[[],11],[[],11],[13],[[],13],0,0,[[]],[[]],[[]],[[]],[[[20,[12]]],[[20,[12]]]],[[[20,[12]],[20,[12]]]],[[]],[[],20],[[],[[21,[14]]]],[[[21,[16]]]],[[[21,[16]]]],[[[20,[17]],[20,[17]]],8],[[[20,[18]],4],5],[[[21,[18]],4],5],[21],[21],[[]],[[],20],[19],[[]],[20,6],[21,6],[20,6],[21,6],[20],[20,9],[[]],[[]],[20,6],[21,9],[[],20],[[],21],[20,9],[20,6],[[]],[[],9],[[],9],[20,9],[[],9],[[],9],[[],11],[[],11],[[],20]],"p":[[3,"OnceBox"],[3,"OnceNonZeroUsize"],[3,"OnceBool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[3,"NonZeroUsize"],[15,"bool"],[4,"Result"],[3,"Box"],[3,"TypeId"],[8,"Clone"],[3,"OnceCell"],[8,"Default"],[3,"Lazy"],[8,"FnOnce"],[8,"PartialEq"],[8,"Debug"],[15,"never"],[3,"OnceCell"],[3,"Lazy"]]},\ -"parking_lot":{"doc":"This library provides implementations of Mutex, RwLock, …","t":[3,13,6,6,13,6,6,6,6,6,6,6,13,3,4,13,3,3,3,3,6,6,6,6,6,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Condvar","Done","FairMutex","FairMutexGuard","InProgress","MappedFairMutexGuard","MappedMutexGuard","MappedReentrantMutexGuard","MappedRwLockReadGuard","MappedRwLockWriteGuard","Mutex","MutexGuard","New","Once","OnceState","Poisoned","RawFairMutex","RawMutex","RawRwLock","RawThreadId","ReentrantMutex","ReentrantMutexGuard","RwLock","RwLockReadGuard","RwLockUpgradableReadGuard","RwLockWriteGuard","WaitTimeoutResult","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bump","bump","bump_exclusive","bump_shared","bump_upgradable","call_once","call_once_force","clone","clone","clone_into","clone_into","const_fair_mutex","const_mutex","const_reentrant_mutex","const_rwlock","default","default","done","downgrade","downgrade_to_upgradable","downgrade_upgradable","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","is_locked","is_locked","is_locked","is_locked_exclusive","lock","lock","lock_api","lock_exclusive","lock_shared","lock_shared_recursive","lock_upgradable","new","new","nonzero_thread_id","notify_all","notify_one","poisoned","state","timed_out","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock","try_lock_exclusive","try_lock_exclusive_for","try_lock_exclusive_until","try_lock_for","try_lock_for","try_lock_shared","try_lock_shared_for","try_lock_shared_recursive","try_lock_shared_recursive_for","try_lock_shared_recursive_until","try_lock_shared_until","try_lock_until","try_lock_until","try_lock_upgradable","try_lock_upgradable_for","try_lock_upgradable_until","try_upgrade","try_upgrade_for","try_upgrade_until","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unlock","unlock","unlock_exclusive","unlock_exclusive_fair","unlock_fair","unlock_fair","unlock_shared","unlock_shared_fair","unlock_upgradable","unlock_upgradable_fair","upgrade","wait","wait_for","wait_until","wait_while","wait_while_for","wait_while_until"],"q":["parking_lot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A Condition Variable","A closure has completed successfully.","A mutual exclusive primitive that is always fair, useful …","An RAII implementation of a “scoped lock” of a mutex. …","A thread is currently executing a closure.","An RAII mutex guard returned by FairMutexGuard::map, which …","An RAII mutex guard returned by MutexGuard::map, which can …","An RAII mutex guard returned by ReentrantMutexGuard::map, …","An RAII read lock guard returned by RwLockReadGuard::map, …","An RAII write lock guard returned by RwLockWriteGuard::map…","A mutual exclusion primitive useful for protecting shared …","An RAII implementation of a “scoped lock” of a mutex. …","A closure has not been executed yet","A synchronization primitive which can be used to run a …","Current state of a Once.","A closure was executed but panicked.","Raw fair mutex type backed by the parking lot.","Raw mutex type backed by the parking lot.","Raw reader-writer lock type backed by the parking lot.","Implementation of the GetThreadId trait for …","A mutex which can be recursively locked by a single thread.","An RAII implementation of a “scoped lock” of a …","A reader-writer lock","RAII structure used to release the shared read access of a …","RAII structure used to release the upgradable read access …","RAII structure used to release the exclusive write access …","A type indicating whether a timed wait on a condition …","","","","","","","","","","","","","","","","","","","","","","Performs an initialization routine once and only once. The …","Performs the same function as call_once except ignores …","","","","","Creates a new fair mutex in an unlocked state ready for …","Creates a new mutex in an unlocked state ready for use.","Creates a new reentrant mutex in an unlocked state ready …","Creates a new instance of an RwLock<T> which is unlocked.","","","Returns whether the associated Once has successfully …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","Creates a new condition variable which is ready to be …","Creates a new Once value.","","Wakes up all blocked threads on this condvar.","Wakes up one blocked thread on this condvar.","Returns whether the associated Once has been poisoned.","Returns the current state of this Once.","Returns whether the wait was known to have timed out.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Blocks the current thread until this condition variable …","Waits on this condition variable for a notification, …","Waits on this condition variable for a notification, …","Blocks the current thread until this condition variable …","Waits on this condition variable for a notification, …","Waits on this condition variable for a notification, …"],"i":[0,6,0,0,6,0,0,0,0,0,0,0,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,3,4,4,5,6,5,6,0,0,0,0,11,4,6,3,3,3,5,6,11,4,5,6,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,1,2,0,3,3,3,3,11,4,15,11,11,6,4,5,5,6,11,4,1,2,3,15,5,6,11,4,1,2,3,15,5,6,1,2,3,3,3,1,2,3,3,3,3,3,3,1,2,3,3,3,3,3,3,11,4,1,2,3,15,5,6,1,2,3,3,1,2,3,3,3,3,3,11,11,11,11,11,11],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2],[3],[3],[3],[4],[4],[5,5],[6,6],[[]],[[]],[[],7],[[],8],[[],9],[[],10],[[],11],[[],4],[6,12],[3],[3],[3],[[5,5],12],[[6,6],12],[[11,13],14],[[4,13],14],[[5,13],14],[[6,13],14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,12],[2,12],[3,12],[3,12],[1],[2],0,[3],[3],[3],[3],[[],11],[[],4],[15,16],[11,17],[11,12],[6,12],[4,6],[5,12],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[1,12],[2,12],[3,12],[[3,19],12],[[3,20],12],[1,12],[[2,19],12],[3,12],[3,12],[3,12],[3,12],[3,12],[3,12],[1,12],[[2,20],12],[3,12],[[3,19],12],[[3,20],12],[3,12],[[3,19],12],[[3,20],12],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[1],[2],[3],[3],[1],[2],[3],[3],[3],[3],[3],[[11,22]],[[11,22,19],5],[[11,22,20],5],[[11,22]],[[11,22,19],5],[[11,22,20],5]],"p":[[3,"RawFairMutex"],[3,"RawMutex"],[3,"RawRwLock"],[3,"Once"],[3,"WaitTimeoutResult"],[4,"OnceState"],[6,"FairMutex"],[6,"Mutex"],[6,"ReentrantMutex"],[6,"RwLock"],[3,"Condvar"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"RawThreadId"],[3,"NonZeroUsize"],[15,"usize"],[4,"Result"],[3,"Duration"],[3,"Instant"],[3,"TypeId"],[6,"MutexGuard"]]},\ -"parking_lot_core":{"doc":"This library exposes a low-level API for creating your own …","t":[12,12,13,17,17,4,13,4,3,13,13,4,13,3,13,13,13,13,13,3,3,13,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,5,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,12,12,5,5],"n":["0","0","Abort","DEFAULT_PARK_TOKEN","DEFAULT_UNPARK_TOKEN","FilterOp","Invalid","ParkResult","ParkToken","RequeueAll","RequeueOne","RequeueOp","Skip","SpinWait","Stop","TimedOut","Unpark","UnparkOne","UnparkOneRequeueRest","UnparkResult","UnparkToken","Unparked","be_fair","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","deadlock","default","default","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","have_more_threads","into","into","into","into","into","into","into","is_unparked","new","park","requeued_threads","reset","spin","spin_no_yield","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unpark_all","unpark_filter","unpark_one","unpark_requeue","unparked_threads","0","acquire_resource","release_resource"],"q":["parking_lot_core","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","parking_lot_core::ParkResult","parking_lot_core::deadlock",""],"d":["","","Abort the operation without doing anything.","A default park token to use.","A default unpark token to use.","Operation that unpark_filter should perform for each …","The validation callback returned false.","Result of a park operation.","A value associated with a parked thread which can be used …","Requeue all threads onto the target queue.","Requeue one thread and leave the rest parked on the …","Operation that unpark_requeue should perform.","Don’t unpark the thread and continue scanning the list …","A counter used to perform exponential backoff in spin …","Don’t unpark the thread and stop scanning the list of …","The timeout expired.","Unpark the thread and continue scanning the list of parked …","Unpark one thread and leave the rest parked. No requeuing …","Unpark one thread and requeue the rest onto the target …","Result of an unpark operation.","A value which is passed from an unparker to a parked …","We were unparked by another thread with the given token.","This is set to true on average once every 0.5ms for any …","","","","","","","","","","","","","","","","","","","","","","","","","","","[Experimental] Deadlock detection","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Whether there are any threads remaining in the queue. This …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if we were unparked by another thread.","Creates a new SpinWait.","Parks the current thread in the queue associated with the …","The number of threads that were requeued.","Resets a SpinWait to its initial state.","Spins until the sleep threshold has been reached.","Spins without yielding the thread to the OS.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unparks all threads in the queue associated with the given …","Unparks a number of threads from the front of the queue …","Unparks one thread from the queue associated with the …","Removes all threads from the queue associated with key_from…","The number of threads that were unparked.","","Acquire a resource identified by key in the deadlock …","Release a resource identified by key in the deadlock …"],"i":[5,6,3,0,0,0,1,0,0,3,3,0,4,0,4,1,4,3,3,0,0,1,2,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,1,2,3,4,5,6,0,2,7,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,7,2,1,2,3,4,5,6,7,1,7,0,2,7,7,7,1,2,3,4,5,6,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,0,0,0,0,2,18,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[[]],[[]],[[]],[[]],[[]],[[]],0,[[],2],[[],7],[[1,1],8],[[2,2],8],[[3,3],8],[[4,4],8],[[5,5],8],[[6,6],8],[[1,9],10],[[2,9],10],[[3,9],10],[[4,9],10],[[5,9],10],[[6,9],10],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,8],[[],7],[[11,12,12,12,6,[14,[13]]],1],0,[7],[7,8],[7],[[]],[[]],[[]],[[]],[[]],[[]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[11,5],11],[[11,17,12],2],[[11,12],2],[[11,11,12,12],2],0,0,[11],[11]],"p":[[4,"ParkResult"],[3,"UnparkResult"],[4,"RequeueOp"],[4,"FilterOp"],[3,"UnparkToken"],[3,"ParkToken"],[3,"SpinWait"],[15,"bool"],[3,"Formatter"],[6,"Result"],[15,"usize"],[8,"FnOnce"],[3,"Instant"],[4,"Option"],[4,"Result"],[3,"TypeId"],[8,"FnMut"],[13,"Unparked"]]},\ -"pin_project_lite":{"doc":"A lightweight version of pin-project written with …","t":[14],"n":["pin_project"],"q":["pin_project_lite"],"d":["A macro that creates a projection type covering all the …"],"i":[0],"f":[0],"p":[]},\ -"pin_utils":{"doc":"Utilities for pinning","t":[14,14,14],"n":["pin_mut","unsafe_pinned","unsafe_unpinned"],"q":["pin_utils","",""],"d":["Pins a value on the stack.","A pinned projection of a struct field.","An unpinned projection of a struct field."],"i":[0,0,0],"f":[0,0,0],"p":[]},\ -"ppv_lite86":{"doc":"","t":[8,8,8,8,8,8,8,8,8,8,16,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,14,14,14,10,10,10,10,10,10,11,11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,16,10,11,11,10,10,10,11,11,2,2,2,10,10,10,0,6,6,3,3,3,3,3,3,6,6,6,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,19,19,19],"n":["AndNot","ArithOps","BSwap","BitOps0","BitOps128","BitOps32","BitOps64","LaneWords4","Machine","MultiLane","Output","RotateEachWord128","RotateEachWord32","RotateEachWord64","Store","StoreBytes","Swap64","UnsafeFrom","VZip","Vec2","Vec4","Vec4Ext","Vector","Words4","andnot","bswap","dispatch","dispatch_light128","dispatch_light256","extract","extract","from_lanes","insert","insert","instance","read_be","read_be","read_le","read_le","rotate_each_word_right11","rotate_each_word_right12","rotate_each_word_right16","rotate_each_word_right20","rotate_each_word_right24","rotate_each_word_right25","rotate_each_word_right32","rotate_each_word_right7","rotate_each_word_right8","shuffle1230","shuffle2301","shuffle3012","shuffle_lane_words1230","shuffle_lane_words2301","shuffle_lane_words3012","swap1","swap16","swap2","swap32","swap4","swap64","swap8","to_lanes","to_scalars","transpose4","u128x1","u128x1","u128x2","u128x2","u128x4","u128x4","u32x4","u32x4","u32x4x2","u32x4x2","u32x4x4","u32x4x4","u64x2","u64x2","u64x2x2","u64x2x2","u64x2x4","u64x2x4","u64x4","u64x4","unpack","unpack","unpack","unsafe_from","unsafe_read_be","unsafe_read_le","vec","vec","vec128_storage","vec256_storage","vec512_storage","vzip","write_be","write_le","x86_64","AVX","AVX2","Avx2Machine","NoA1","NoA2","NoNI","NoS3","NoS4","SSE2","SSE41","SSSE3","SseMachine","YesA1","YesA2","YesNI","YesS3","YesS4","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","default","default","default","eq","eq","eq","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","instance","instance","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new128","new128","split128","split128","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unpack","vec128_storage","vec256_storage","vec512_storage"],"q":["ppv_lite86","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","ppv_lite86::x86_64","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","Ops that depend on word size","","Ops that are independent of word size and endian","","","","A vector composed one or more lanes each composed of four …","","A vector composed of multiple 128-bit lanes.","","","","","","","Exchange neigboring ranges of bits of the specified size","","Combine single vectors into a multi-lane vector.","A vector composed of two elements, which may be words or …","A vector composed of four elements, which may be words or …","Vec4 functions which may not be implemented yet for all …","","A vector composed of four words; depending on their size, …","","","Generate the full set of optimized implementations to take …","Generate only the basic implementations necessary to be …","Generate only the basic implementations necessary to be …","","","Build a multi-lane vector from individual lanes.","","","Safety","","","","","","","","","","","","","","","","","","","","","","","","","","","Split a multi-lane vector into single-lane vectors.","","","","","","","","","","","","","","","","","","","","","","","Safety","","","","Safety","Safety","","","","","","","","","","AVX but not AVX2: only 128-bit integer operations, but use …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Generic wrapper for unparameterized storage of any of the …","",""],"i":[0,0,0,0,0,0,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,23,24,0,0,0,25,26,27,25,26,28,28,28,28,28,29,29,29,29,29,29,30,29,29,31,31,31,32,32,32,33,33,33,33,33,33,33,27,34,35,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,2,28,28,36,37,37,28,28,0,0,0,38,37,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,16,17,18,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,16,17,17,18,14,15,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,17,18,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,16,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,0,0,[1],[1],[[]],[1],[1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],2],[[],2],[[]],[[]],[[]],[[]],[[]],0,0,0,[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[11,11],[12,12],[[[14,[13,13,13]]],[[14,[13,13,13]]]],[[[15,[13]]],[[15,[13]]]],[16,16],[17,17],[18,18],[[],16],[[],17],[[],18],[[16,16],19],[[17,17],19],[[18,18],19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],16],[[],17],[[]],[[]],[[],[[14,[20,20,20]]]],[[],[[15,[20]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],17],[[],18],[17],[18],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[16,16],0,0,0],"p":[[15,"u32"],[8,"Store"],[3,"YesS3"],[3,"NoS3"],[3,"YesS4"],[3,"NoS4"],[3,"YesA1"],[3,"NoA1"],[3,"YesA2"],[3,"NoA2"],[3,"YesNI"],[3,"NoNI"],[8,"Clone"],[3,"SseMachine"],[3,"Avx2Machine"],[19,"vec128_storage"],[19,"vec256_storage"],[19,"vec512_storage"],[15,"bool"],[8,"Copy"],[4,"Result"],[3,"TypeId"],[8,"AndNot"],[8,"BSwap"],[8,"Vec2"],[8,"Vec4"],[8,"MultiLane"],[8,"Machine"],[8,"RotateEachWord32"],[8,"RotateEachWord64"],[8,"Words4"],[8,"LaneWords4"],[8,"Swap64"],[8,"Vector"],[8,"Vec4Ext"],[8,"UnsafeFrom"],[8,"StoreBytes"],[8,"VZip"]]},\ -"primitive_types":{"doc":"Primitive types shared by Substrate and Parity Ethereum.","t":[12,12,12,12,12,12,12,12,12,4,3,3,3,3,3,3,18,18,18,13,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","0","0","0","0","0","0","0","0","Error","H128","H160","H256","H384","H512","H768","MAX","MAX","MAX","Overflow","U128","U256","U512","abs_diff","abs_diff","abs_diff","add","add","add","add","add","add","add_assign","add_assign","add_assign","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_bytes_mut","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_fixed_bytes_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_mut_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ptr","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","as_u128","as_u128","as_u128","as_u32","as_u32","as_u32","as_u64","as_u64","as_u64","as_usize","as_usize","as_usize","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","assign_from_slice","bit","bit","bit","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitand_assign","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bitor_assign","bits","bits","bits","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","bitxor_assign","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte","byte","byte","checked_add","checked_add","checked_add","checked_div","checked_div","checked_div","checked_mul","checked_mul","checked_mul","checked_neg","checked_neg","checked_neg","checked_pow","checked_pow","checked_pow","checked_rem","checked_rem","checked_rem","checked_sub","checked_sub","checked_sub","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","cmp","covers","covers","covers","covers","covers","covers","decode","decode","decode","decode","decode","decode","decode","decode","decode","default","default","default","default","default","default","default","default","default","div","div","div","div","div","div","div_assign","div_assign","div_assign","div_mod","div_mod","div_mod","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","exp10","exp10","exp10","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_big_endian","from_big_endian","from_big_endian","from_dec_str","from_dec_str","from_dec_str","from_little_endian","from_little_endian","from_little_endian","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_be","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_le","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_low_u64_ne","from_slice","from_slice","from_slice","from_slice","from_slice","from_slice","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str","from_str_radix","from_str_radix","from_str_radix","full_mul","full_mul","hash","hash","hash","hash","hash","hash","hash","hash","hash","index","index","index","index","index","index","index_mut","index_mut","index_mut","index_mut","index_mut","index_mut","integer_sqrt","integer_sqrt","integer_sqrt","into","into","into","into","into","into","into","into","into","into","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","is_zero","leading_zeros","leading_zeros","leading_zeros","len_bytes","len_bytes","len_bytes","len_bytes","len_bytes","len_bytes","low_u128","low_u128","low_u128","low_u32","low_u32","low_u32","low_u64","low_u64","low_u64","max_value","max_value","max_value","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","mul_assign","not","not","not","one","one","one","overflowing_add","overflowing_add","overflowing_add","overflowing_mul","overflowing_mul","overflowing_mul","overflowing_neg","overflowing_neg","overflowing_neg","overflowing_pow","overflowing_pow","overflowing_pow","overflowing_sub","overflowing_sub","overflowing_sub","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","pow","pow","pow","random","random","random","random","random","random","random_using","random_using","random_using","random_using","random_using","random_using","randomize","randomize","randomize","randomize","randomize","randomize","randomize_using","randomize_using","randomize_using","randomize_using","randomize_using","randomize_using","rem","rem","rem","rem","rem","rem","rem_assign","rem_assign","rem_assign","repeat_byte","repeat_byte","repeat_byte","repeat_byte","repeat_byte","repeat_byte","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","rlp_append","saturating_add","saturating_add","saturating_add","saturating_mul","saturating_mul","saturating_mul","saturating_sub","saturating_sub","saturating_sub","shl","shl","shl","shl","shl","shl","shl_assign","shl_assign","shl_assign","shr","shr","shr","shr","shr","shr","shr_assign","shr_assign","shr_assign","sub","sub","sub","sub","sub","sub","sub_assign","sub_assign","sub_assign","to_big_endian","to_big_endian","to_big_endian","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_fixed_bytes","to_little_endian","to_little_endian","to_little_endian","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_be","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_le","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_low_u64_ne","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","trailing_zeros","trailing_zeros","trailing_zeros","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","zero","zero","zero","zero","zero","zero","zero","zero","zero"],"q":["primitive_types","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","","","","","","","Error type for conversion.","Fixed-size uninterpreted hash type with 16 bytes (128 …","Fixed-size uninterpreted hash type with 20 bytes (160 …","Fixed-size uninterpreted hash type with 32 bytes (256 …","Fixed-size uninterpreted hash type with 48 bytes (384 …","Fixed-size uninterpreted hash type with 64 bytes (512 …","Fixed-size uninterpreted hash type with 96 bytes (768 …","Maximum value.","Maximum value.","Maximum value.","Overflow encountered.","Little-endian large integer type 128-bit unsigned integer.","Little-endian large integer type 256-bit unsigned integer.","Little-endian large integer type 512-bits unsigned integer.","Computes the absolute difference between self and other.","Computes the absolute difference between self and other.","Computes the absolute difference between self and other.","","","","","","","","","","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a byte slice containing the entire fixed hash.","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a mutable byte slice containing the entire fixed …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","Extracts a reference to the byte array containing the …","","","","","","","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a mutable raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","Returns a constant raw pointer to the value.","","","","","","","","","","Conversion to u128 with overflow checking","Conversion to u128 with overflow checking","Conversion to u128 with overflow checking","Conversion to u32 with overflow checking","Conversion to u32 with overflow checking","Conversion to u32 with overflow checking","Conversion to u64 with overflow checking","Conversion to u64 with overflow checking","Conversion to u64 with overflow checking","Conversion to usize with overflow checking","Conversion to usize with overflow checking","Conversion to usize with overflow checking","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Assign the bytes from the byte slice src to self.","Return if specific bit is set.","Return if specific bit is set.","Return if specific bit is set.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the least number of bits needed to represent the …","Return the least number of bits needed to represent the …","Return the least number of bits needed to represent the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return specific byte.","Return specific byte.","Return specific byte.","Checked addition. Returns None if overflow occurred.","Checked addition. Returns None if overflow occurred.","Checked addition. Returns None if overflow occurred.","Checked division. Returns None if other == 0.","Checked division. Returns None if other == 0.","Checked division. Returns None if other == 0.","Checked multiplication. Returns None if overflow occurred.","Checked multiplication. Returns None if overflow occurred.","Checked multiplication. Returns None if overflow occurred.","Checked negation. Returns None unless self == 0.","Checked negation. Returns None unless self == 0.","Checked negation. Returns None unless self == 0.","Checked exponentiation. Returns None if overflow occurred.","Checked exponentiation. Returns None if overflow occurred.","Checked exponentiation. Returns None if overflow occurred.","Checked modulus. Returns None if other == 0.","Checked modulus. Returns None if other == 0.","Checked modulus. Returns None if other == 0.","Checked subtraction. Returns None if overflow occurred.","Checked subtraction. Returns None if overflow occurred.","Checked subtraction. Returns None if overflow occurred.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","Returns true if all bits set in b are also set in self.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns a pair (self / other, self % other).","Returns a pair (self / other, self % other).","Returns a pair (self / other, self % other).","","","","","","","","","","","","","","","","","","","","","","","Create 10**n as this type.","Create 10**n as this type.","Create 10**n as this type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given bytes array of fixed …","Returns the argument unchanged.","Constructs a hash type from the given reference to the …","Returns the argument unchanged.","Constructs a hash type from the given bytes array of fixed …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Constructs a hash type from the given reference to the …","Returns the argument unchanged.","Constructs a hash type from the given bytes array of fixed …","Converts from big endian representation bytes in memory.","Converts from big endian representation bytes in memory.","Converts from big endian representation bytes in memory.","Convert from a decimal string.","Convert from a decimal string.","Convert from a decimal string.","Converts from little endian representation bytes in memory.","Converts from little endian representation bytes in memory.","Converts from little endian representation bytes in memory.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Creates a new hash type from the given u64 value.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","Create a new fixed-hash from the given slice src.","","","","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Creates a hash type instance from the given string.","Converts a string slice in a given base to an integer. …","Converts a string slice in a given base to an integer. …","Converts a string slice in a given base to an integer. …","Multiplies two 128-bit integers to produce full 256-bit …","Multiplies two 256-bit integers to produce full 512-bit …","","","","","","","","","","","","","","","","","","","","","","Compute the highest n such that n * n <= self.","Compute the highest n such that n * n <= self.","Compute the highest n such that n * n <= self.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Whether this is zero.","Whether this is zero.","Whether this is zero.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns true if no bits are set.","Returns the number of leading zeros in the binary …","Returns the number of leading zeros in the binary …","Returns the number of leading zeros in the binary …","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Returns the size of this hash in bytes.","Low 2 words (u128)","Low 2 words (u128)","Low 2 words (u128)","Conversion to u32","Conversion to u32","Conversion to u32","Low word (u64)","Low word (u64)","Low word (u64)","The maximum value which can be inhabited by this type.","The maximum value which can be inhabited by this type.","The maximum value which can be inhabited by this type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","One (multiplicative identity) of this type.","One (multiplicative identity) of this type.","One (multiplicative identity) of this type.","Addition which overflows and returns a flag if it does.","Addition which overflows and returns a flag if it does.","Addition which overflows and returns a flag if it does.","Multiply with overflow, returning a flag if it does.","Multiply with overflow, returning a flag if it does.","Multiply with overflow, returning a flag if it does.","Negation with overflow.","Negation with overflow.","Negation with overflow.","Fast exponentiation by squaring. Returns result and …","Fast exponentiation by squaring. Returns result and …","Fast exponentiation by squaring. Returns result and …","Subtraction which underflows and returns a flag if it does.","Subtraction which underflows and returns a flag if it does.","Subtraction which underflows and returns a flag if it does.","","","","","","","","","","Fast exponentiation by squaring …","Fast exponentiation by squaring …","Fast exponentiation by squaring …","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content.","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Create a new hash with cryptographically random content …","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value.","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","Assign self to a cryptographically random value using the …","","","","","","","","","","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","Returns a new fixed hash where all bits are set to the …","","","","","","","","","","Addition which saturates at the maximum value (Self::MAX).","Addition which saturates at the maximum value (Self::MAX).","Addition which saturates at the maximum value (Self::MAX).","Multiplication which saturates at the maximum value..","Multiplication which saturates at the maximum value..","Multiplication which saturates at the maximum value..","Subtraction which saturates at zero.","Subtraction which saturates at zero.","Subtraction which saturates at zero.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write to the slice in big-endian format.","Write to the slice in big-endian format.","Write to the slice in big-endian format.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Returns the inner bytes array.","Write to the slice in little-endian format.","Write to the slice in little-endian format.","Write to the slice in little-endian format.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as big-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as little-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","Returns the lowest 8 bytes interpreted as native-endian.","","","","","","","","","","","","","","","","","","","Returns the number of trailing zeros in the binary …","Returns the number of trailing zeros in the binary …","Returns the number of trailing zeros in the binary …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Zero (additive identity) of this type.","Zero (additive identity) of this type.","Zero (additive identity) of this type.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash.","Returns a new zero-initialized fixed hash."],"i":[1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,1,2,3,21,0,0,0,1,2,3,1,1,2,2,3,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,1,2,3,4,4,5,5,6,6,7,7,8,8,9,9,21,1,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,1,2,2,3,3,1,2,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,21,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,21,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,21,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,2,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,1,2,2,3,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,1,2,3,1,2,3,1,1,2,2,3,3,1,2,3,1,1,2,2,3,3,1,2,3,1,1,2,2,3,3,1,2,3,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,4,5,6,7,8,9,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,21,1,1,1,2,2,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,21,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,1],1],[[2,2],2],[[3,3],3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[[1,1]],[[2,2]],[[3,3]],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[1],[2],[3],[4],[5],[6],[7],[8],[9],[1,11],[2,11],[3,11],[1,12],[2,12],[3,12],[1,13],[2,13],[3,13],[1,14],[2,14],[3,14],[4],[5],[6],[7],[8],[9],[[1,14],15],[[2,14],15],[[3,14],15],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[1,14],[2,14],[3,14],[[1,1],1],[[2,2],2],[[3,3],3],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[1,1]],[[2,2]],[[3,3]],[[4,4]],[[4,4]],[[5,5]],[[5,5]],[[6,6]],[[6,6]],[[7,7]],[[7,7]],[[8,8]],[[8,8]],[[9,9]],[[9,9]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,14],10],[[2,14],10],[[3,14],10],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[1,[[16,[1]]]],[2,[[16,[2]]]],[3,[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[[1,1],[[16,[1]]]],[[2,2],[[16,[2]]]],[[3,3],[[16,[3]]]],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,1],17],[[2,2],17],[[3,3],17],[[4,4],17],[[5,5],17],[[6,6],17],[[7,7],17],[[8,8],17],[[9,9],17],[[4,4],15],[[5,5],15],[[6,6],15],[[7,7],15],[[8,8],15],[[9,9],15],[18,[[20,[1,19]]]],[18,[[20,[2,19]]]],[18,[[20,[3,19]]]],[18,[[20,[4,19]]]],[18,[[20,[5,19]]]],[18,[[20,[6,19]]]],[18,[[20,[7,19]]]],[18,[[20,[8,19]]]],[18,[[20,[9,19]]]],[[],1],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[[1,1]],[[2,2]],[[3,3]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[21,21],15],[[1,1],15],[[2,2],15],[[3,3],15],[[4,4],15],[[5,5],15],[[6,6],15],[[7,7],15],[[8,8],15],[[9,9],15],[14,1],[14,2],[14,3],[[21,22],23],[[1,22],23],[[1,22],23],[[1,22],23],[[1,22],23],[[2,22],23],[[2,22],23],[[2,22],23],[[2,22],23],[[3,22],23],[[3,22],23],[[3,22],23],[[3,22],23],[[4,22],23],[[4,22],23],[[4,22],23],[[4,22],23],[[5,22],23],[[5,22],23],[[5,22],23],[[5,22],23],[[6,22],23],[[6,22],23],[[6,22],23],[[6,22],23],[[7,22],23],[[7,22],23],[[7,22],23],[[7,22],23],[[8,22],23],[[8,22],23],[[8,22],23],[[8,22],23],[[9,22],23],[[9,22],23],[[9,22],23],[[9,22],23],[[]],[1,1],[[],1],[24,1],[10,1],[13,1],[25,1],[12,1],[[]],[14,1],[26,1],[[],1],[27,1],[28,1],[[],1],[29,1],[30,1],[31,1],[11,1],[31,2],[[],2],[[],2],[13,2],[11,2],[1,2],[10,2],[27,2],[25,2],[12,2],[14,2],[26,2],[28,2],[29,2],[30,2],[24,2],[[],2],[2,2],[[]],[28,3],[[],3],[27,3],[11,3],[1,3],[10,3],[2,3],[25,3],[12,3],[14,3],[26,3],[31,3],[3,3],[29,3],[2,3],[30,3],[24,3],[[],3],[[],3],[13,3],[[]],[[]],[[],4],[[],4],[[],4],[[],5],[[]],[[],5],[6,5],[[],5],[[],6],[[],6],[[],6],[5,6],[[]],[[],7],[[],7],[[],7],[[]],[[],8],[[]],[[],8],[[],8],[[],9],[[],9],[[]],[[],9],[[],1],[[],2],[[],3],[31,[[20,[1,32]]]],[31,[[20,[2,32]]]],[31,[[20,[3,32]]]],[[],1],[[],2],[[],3],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[13,4],[13,5],[13,6],[13,7],[13,8],[13,9],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[31,[[20,[1]]]],[31,[[20,[2]]]],[31,[[20,[3]]]],[31,[[20,[4,33]]]],[31,[[20,[5,33]]]],[31,[[20,[6,33]]]],[31,[[20,[7,33]]]],[31,[[20,[8,33]]]],[31,[[20,[9,33]]]],[[31,12],[[20,[1,34]]]],[[31,12],[[20,[2,34]]]],[[31,12],[[20,[3,34]]]],[[1,1],2],[[2,2],3],[1],[2],[3],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[1,1],[2,2],[3,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,15],[2,15],[3,15],[4,15],[5,15],[6,15],[7,15],[8,15],[9,15],[1,12],[2,12],[3,12],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[1,11],[2,11],[3,11],[1,12],[2,12],[3,12],[1,13],[2,13],[3,13],[[],1],[[],2],[[],3],[[1,25],1],[[1,30],1],[[1,29],1],[[1,29],1],[[1,30],1],[[1,29],1],[[1,26],1],[[1,29],1],[[1,28],1],[[1,28],1],[[1,13],1],[[1,30],1],[[1,30],1],[[1,26],1],[[1,26],1],[[1,14],1],[[1,10],1],[[1,10],1],[[1,10],1],[[1,10],1],[[1,25],1],[[1,25],1],[[1,28],1],[[1,26],1],[[1,28],1],[[1,25],1],[[1,12],1],[[1,14],1],[[1,12],1],[[1,12],1],[[1,12],1],[[1,1],1],[[1,1],1],[[1,13],1],[[1,13],1],[[1,14],1],[[1,1],1],[[1,1],1],[[1,13],1],[[1,24],1],[[1,24],1],[[1,14],1],[[1,24],1],[[1,24],1],[[2,25],2],[[2,28],2],[[2,29],2],[[2,2],2],[[2,2],2],[[2,2],2],[[2,2],2],[[2,10],2],[[2,10],2],[[2,10],2],[[2,10],2],[[2,24],2],[[2,25],2],[[2,25],2],[[2,25],2],[[2,24],2],[[2,24],2],[[2,24],2],[[2,12],2],[[2,12],2],[[2,26],2],[[2,26],2],[[2,26],2],[[2,26],2],[[2,12],2],[[2,30],2],[[2,12],2],[[2,30],2],[[2,30],2],[[2,30],2],[[2,13],2],[[2,29],2],[[2,29],2],[[2,13],2],[[2,29],2],[[2,13],2],[[2,13],2],[[2,14],2],[[2,14],2],[[2,14],2],[[2,14],2],[[2,28],2],[[2,28],2],[[2,28],2],[[3,10],3],[[3,26],3],[[3,10],3],[[3,10],3],[[3,10],3],[[3,3],3],[[3,25],3],[[3,25],3],[[3,25],3],[[3,25],3],[[3,3],3],[[3,12],3],[[3,12],3],[[3,12],3],[[3,12],3],[[3,3],3],[[3,13],3],[[3,13],3],[[3,13],3],[[3,13],3],[[3,3],3],[[3,14],3],[[3,14],3],[[3,14],3],[[3,14],3],[[3,24],3],[[3,28],3],[[3,28],3],[[3,28],3],[[3,28],3],[[3,24],3],[[3,29],3],[[3,29],3],[[3,29],3],[[3,29],3],[[3,24],3],[[3,30],3],[[3,30],3],[[3,30],3],[[3,30],3],[[3,24],3],[[3,26],3],[[3,26],3],[[3,26],3],[[1,29]],[[1,13]],[[1,10]],[[1,25]],[[1,12]],[[1,1]],[[1,24]],[[1,30]],[[1,14]],[[1,28]],[[1,26]],[[2,10]],[[2,24]],[[2,30]],[[2,14]],[[2,29]],[[2,12]],[[2,25]],[[2,13]],[[2,28]],[[2,26]],[[2,2]],[[3,26]],[[3,12]],[[3,10]],[[3,3]],[[3,24]],[[3,14]],[[3,28]],[[3,29]],[[3,13]],[[3,30]],[[3,25]],[1,1],[2,2],[3,3],[[],1],[[],2],[[],3],[[1,1]],[[2,2]],[[3,3]],[[1,1]],[[2,2]],[[3,3]],[1],[2],[3],[[1,1]],[[2,2]],[[3,3]],[[1,1]],[[2,2]],[[3,3]],[[1,1],[[16,[17]]]],[[2,2],[[16,[17]]]],[[3,3],[[16,[17]]]],[[4,4],[[16,[17]]]],[[5,5],[[16,[17]]]],[[6,6],[[16,[17]]]],[[7,7],[[16,[17]]]],[[8,8],[[16,[17]]]],[[9,9],[[16,[17]]]],[[1,1],1],[[2,2],2],[[3,3],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[4],[5],[6],[7],[8],[9],[4],[5],[6],[7],[8],[9],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[10,4],[10,5],[10,6],[10,7],[10,8],[10,9],[[1,35]],[[2,35]],[[3,35]],[[4,35]],[[5,35]],[[6,35]],[[7,35]],[[8,35]],[[9,35]],[[1,1],1],[[2,2],2],[[3,3],3],[[1,1],1],[[2,2],2],[[3,3],3],[[1,1],1],[[2,2],2],[[3,3],3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[1],[2],[3],[1,1],[1,1],[2,2],[2,2],[3,3],[3,3],[[1,1]],[[2,2]],[[3,3]],[1],[2],[3],[4],[5],[6],[7],[8],[9],[1],[2],[3],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[4,13],[5,13],[6,13],[7,13],[8,13],[9,13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[1,12],[2,12],[3,12],[[],20],[2,[[20,[1,21]]]],[[],20],[3,[[20,[1,21]]]],[3,[[20,[2,21]]]],[[],20],[3,[[20,[2,21]]]],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],37],[[],1],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9]],"p":[[3,"U128"],[3,"U256"],[3,"U512"],[3,"H128"],[3,"H160"],[3,"H256"],[3,"H384"],[3,"H512"],[3,"H768"],[15,"u8"],[15,"u128"],[15,"u32"],[15,"u64"],[15,"usize"],[15,"bool"],[4,"Option"],[4,"Ordering"],[3,"Rlp"],[4,"DecoderError"],[4,"Result"],[4,"Error"],[3,"Formatter"],[6,"Result"],[15,"isize"],[15,"u16"],[15,"i64"],[15,"i128"],[15,"i8"],[15,"i16"],[15,"i32"],[15,"str"],[4,"FromDecStrErr"],[4,"FromHexError"],[3,"FromStrRadixErr"],[3,"RlpStream"],[3,"String"],[3,"TypeId"]]},\ -"proc_macro2":{"doc":"github crates-io docs-rs","t":[13,13,13,4,3,13,3,13,13,3,3,13,13,13,3,13,4,3,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Alone","Brace","Bracket","Delimiter","Group","Group","Ident","Ident","Joint","LexError","Literal","Literal","None","Parenthesis","Punct","Punct","Spacing","Span","TokenStream","TokenTree","as_char","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte_string","call_site","character","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","default","delimiter","eq","eq","eq","eq","extend","extend","f32_suffixed","f32_unsuffixed","f64_suffixed","f64_unsuffixed","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_iter","from_iter","from_str","from_str","hash","i128_suffixed","i128_unsuffixed","i16_suffixed","i16_unsuffixed","i32_suffixed","i32_unsuffixed","i64_suffixed","i64_unsuffixed","i8_suffixed","i8_unsuffixed","into","into","into","into","into","into","into","into","into","into","into_iter","is_empty","isize_suffixed","isize_unsuffixed","join","located_at","mixed_site","new","new","new","new","new_raw","partial_cmp","provide","resolved_at","set_span","set_span","set_span","set_span","set_span","spacing","span","span","span","span","span","span","span_close","span_open","stream","string","subspan","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","token_stream","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","u128_suffixed","u128_unsuffixed","u16_suffixed","u16_unsuffixed","u32_suffixed","u32_unsuffixed","u64_suffixed","u64_unsuffixed","u8_suffixed","u8_unsuffixed","unwrap","usize_suffixed","usize_unsuffixed","IntoIter","TokenStream","borrow","borrow_mut","clone","clone_into","fmt","from","into","into_iter","next","size_hint","to_owned","try_from","try_into","type_id"],"q":["proc_macro2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","proc_macro2::token_stream","","","","","","","","","","","","","","",""],"d":["E.g. + is Alone in + =, +ident or +().","{ ... }","[ ... ]","Describes how a sequence of token trees is delimited.","A delimited token stream.","A token stream surrounded by bracket delimiters.","A word of Rust code, which may be a keyword or legal …","An identifier.","E.g. + is Joint in += or ' is Joint in '#.","Error returned from TokenStream::from_str.","A literal string ("hello"), byte string (b"hello"), …","A literal character ('a'), string ("hello"), number (2.3), …","Ø ... Ø","( ... )","A Punct is a single punctuation character like +, - or #.","A single punctuation character (+, ,, $, etc.).","Whether a Punct is followed immediately by another Punct …","A region of source code, along with macro expansion …","An abstract stream of tokens, or more concretely a …","A single token or a delimited sequence of token trees …","Returns the value of this punctuation character as char.","","","","","","","","","","","","","","","","","","","","","Byte string literal.","The span of the invocation of the current procedural macro.","Character literal.","","","","","","","","","","","","","","","","","","","","","Returns the delimiter of this Group","","","","","","","Creates a new suffixed floating-point literal.","Creates a new unsuffixed floating-point literal.","Creates a new suffixed floating-point literal.","Creates a new unsuffixed floating-point literal.","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Checks if this TokenStream is empty.","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Create a new span encompassing self and other.","Creates a new span with the same name resolution behavior …","The span located at the invocation of the procedural …","Returns an empty TokenStream containing no token trees.","Creates a new Group with the given delimiter and token …","Creates a new Punct from the given character and spacing.","Creates a new Ident with the given string as well as the …","Same as Ident::new, but creates a raw identifier (r#ident…","","","Creates a new span with the same line/column information …","Configures the span for only this token.","Configures the span for this Group’s delimiters, but not …","Configure the span for this punctuation character.","Configures the span of this Ident, possibly changing its …","Configures the span associated for this literal.","Returns the spacing of this punctuation character, …","","Returns the span of this tree, delegating to the span …","Returns the span for the delimiters of this token stream, …","Returns the span for this punctuation character.","Returns the span of this Ident.","Returns the span encompassing this literal.","Returns the span pointing to the closing delimiter of this …","Returns the span pointing to the opening delimiter of this …","Returns the TokenStream of tokens that are delimited in …","String literal.","Returns a Span that is a subset of self.span() containing …","","","","","","","","","","","","","","","","","Public implementation details for the TokenStream type, …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","Convert proc_macro2::Span to proc_macro::Span.","Creates a new suffixed integer literal with the specified …","Creates a new unsuffixed integer literal with the …","An iterator over TokenStream’s TokenTrees.","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","",""],"i":[9,8,8,0,0,6,0,6,9,0,0,6,8,8,0,6,0,0,0,0,1,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,3,4,3,5,4,6,7,8,1,9,10,3,5,4,6,7,8,1,9,10,3,10,5,7,8,9,10,10,5,5,3,3,3,3,16,16,5,5,4,6,6,7,7,8,1,1,9,10,10,3,3,16,5,5,5,4,4,6,6,6,6,6,7,8,1,9,10,3,5,5,5,3,10,3,3,3,3,3,3,3,3,3,3,16,5,4,6,7,8,1,9,10,3,5,5,3,3,4,4,4,5,7,1,10,10,10,16,4,6,7,1,10,3,1,16,6,7,1,10,3,7,7,7,3,3,5,4,6,7,8,1,9,10,3,16,5,6,7,1,10,3,0,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,16,5,4,6,7,8,1,9,10,3,3,3,3,3,3,3,3,3,3,3,4,3,3,0,0,28,28,28,28,28,28,28,28,28,28,28,28,28,28],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[],4],[2,3],[5,5],[4,4],[6,6],[7,7],[8,8],[1,1],[9,9],[10,10],[3,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[10,10],11],[[],5],[7,8],[[8,8],12],[[9,9],12],[10,12],[[10,10],12],[[5,13]],[[5,13]],[14,3],[14,3],[15,3],[15,3],[[16,17],18],[[16,17],18],[[5,17],18],[[5,17],18],[[4,17],18],[[6,17],18],[[6,17],18],[[7,17],18],[[7,17],18],[[8,17],18],[[1,17],18],[[1,17],18],[[9,17],18],[[10,17],18],[[10,17],18],[[3,17],18],[[3,17],18],[[]],[6,5],[[]],[19,5],[[]],[20,4],[[]],[7,6],[10,6],[1,6],[3,6],[[]],[[]],[[]],[[]],[[]],[[]],[13,5],[13,5],[21,[[22,[5,16]]]],[21,[[22,[3,16]]]],[10],[23,3],[23,3],[24,3],[24,3],[25,3],[25,3],[26,3],[26,3],[27,3],[27,3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,28],[5,12],[29,3],[29,3],[[4,4],[[30,[4]]]],[[4,4],4],[[],4],[[],5],[[8,5],7],[[2,9],1],[[21,4],10],[[21,4],10],[[10,10],[[30,[11]]]],[31],[[4,4],4],[[6,4]],[[7,4]],[[1,4]],[[10,4]],[[3,4]],[1,9],[16,4],[6,4],[7,4],[1,4],[10,4],[3,4],[7,4],[7,4],[7,5],[21,3],[[3,[33,[32]]],[[30,[4]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],[[],34],0,[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[36,3],[36,3],[37,3],[37,3],[38,3],[38,3],[39,3],[39,3],[40,3],[40,3],[4,20],[32,3],[32,3],0,0,[[]],[[]],[28,28],[[]],[[28,17],18],[[]],[[]],[[]],[28,[[30,[6]]]],[28],[[]],[[],22],[[],22],[[],35]],"p":[[3,"Punct"],[15,"char"],[3,"Literal"],[3,"Span"],[3,"TokenStream"],[4,"TokenTree"],[3,"Group"],[4,"Delimiter"],[4,"Spacing"],[3,"Ident"],[4,"Ordering"],[15,"bool"],[8,"IntoIterator"],[15,"f32"],[15,"f64"],[3,"LexError"],[3,"Formatter"],[6,"Result"],[3,"TokenStream"],[3,"Span"],[15,"str"],[4,"Result"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[3,"IntoIter"],[15,"isize"],[4,"Option"],[3,"Demand"],[15,"usize"],[8,"RangeBounds"],[3,"String"],[3,"TypeId"],[15,"u128"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"u8"]]},\ -"quote":{"doc":"github crates-io docs-rs","t":[8,8,8,10,10,10,10,10,14,11,11,14,14,11,11,11,11,10],"n":["IdentFragment","ToTokens","TokenStreamExt","append","append_all","append_separated","append_terminated","fmt","format_ident","into_token_stream","into_token_stream","quote","quote_spanned","span","span","to_token_stream","to_token_stream","to_tokens"],"q":["quote","","","","","","","","","","","","","","","","",""],"d":["Specialized formatting trait used by format_ident!.","Types that can be interpolated inside a quote! invocation.","TokenStream extension trait with methods for appending …","For use by ToTokens implementations.","For use by ToTokens implementations.","For use by ToTokens implementations.","For use by ToTokens implementations.","Format this value as an identifier fragment.","Formatting macro for constructing Idents.","Convert self directly into a TokenStream object.","Convert self directly into a TokenStream object.","The whole point.","Same as quote!, but applies a given span to all tokens …","Span associated with this IdentFragment.","Span associated with this IdentFragment.","Convert self directly into a TokenStream object.","Convert self directly into a TokenStream object.","Write self to the given TokenStream."],"i":[0,0,0,6,6,6,6,7,0,8,8,0,0,7,7,8,8,8],"f":[0,0,0,[[]],[[]],[[]],[[]],[1,2],0,[[],3],[[],3],0,0,[[],[[5,[4]]]],[[],[[5,[4]]]],[[],3],[[],3],[3]],"p":[[3,"Formatter"],[6,"Result"],[3,"TokenStream"],[3,"Span"],[4,"Option"],[8,"TokenStreamExt"],[8,"IdentFragment"],[8,"ToTokens"]]},\ -"rand":{"doc":"Utilities for random number generation","t":[18,8,3,8,18,8,8,16,8,11,11,11,0,11,11,10,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,10,0,11,11,11,0,11,11,11,11,11,0,11,11,11,10,11,11,10,11,11,11,11,13,3,3,4,3,3,8,8,13,13,13,3,3,3,3,13,3,4,3,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,0,8,8,8,16,3,3,3,3,3,8,16,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,4,3,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,3,11,11,11,11,11,11,11,11,11,11,2,2,2,2,2,2,2,3,0,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,16,8,3,8,11,11,10,11,10,11,11,10,10,11,10,10,11,11,0,11,11,11,11,10,10,11,11,11,11,11,4,4,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["CUSTOM_START","CryptoRng","Error","Fill","INTERNAL_START","Rng","RngCore","Seed","SeedableRng","borrow","borrow_mut","code","distributions","fill","fill","fill_bytes","fmt","fmt","from","from","from","from_entropy","from_rng","from_seed","gen","gen","gen_bool","gen_bool","gen_range","gen_range","gen_ratio","gen_ratio","inner","into","new","next_u32","next_u64","prelude","provide","raw_os_error","read","rngs","sample","sample","sample_iter","sample_iter","seed_from_u64","seq","source","take_inner","to_string","try_fill","try_fill","try_fill","try_fill_bytes","try_from","try_into","type_id","vzip","AllWeightsZero","Alphanumeric","Bernoulli","BernoulliError","DistIter","DistMap","DistString","Distribution","InvalidProbability","InvalidWeight","NoItem","Open01","OpenClosed01","Slice","Standard","TooMany","Uniform","WeightedError","WeightedIndex","append_string","append_string","append_string","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from_ratio","into","into","into","into","into","into","into","into","into","into","into_iter","map","map","new","new","next","provide","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample_iter","sample_iter","sample_string","sample_string","size_hint","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uniform","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","weighted","SampleBorrow","SampleRange","SampleUniform","Sampler","Uniform","UniformChar","UniformDuration","UniformFloat","UniformInt","UniformSampler","X","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","into","is_empty","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","new_inclusive","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","sample_single_inclusive","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","AllWeightsZero","InvalidWeight","NoItem","TooMany","WeightedError","WeightedIndex","alias_method","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","from","from","into","into","new","provide","sample","to_owned","to_owned","to_string","try_from","try_from","try_into","try_into","type_id","type_id","update_weights","vzip","vzip","Weight","WeightedIndex","borrow","borrow_mut","fmt","from","into","new","try_from","try_into","type_id","vzip","CryptoRng","Distribution","IteratorRandom","Rng","RngCore","SeedableRng","SliceRandom","OsRng","adapter","as_rngcore","borrow","borrow_mut","clone","clone_into","default","fill_bytes","fmt","from","into","mock","next_u32","next_u64","to_owned","try_fill_bytes","try_from","try_into","type_id","vzip","ReadError","ReadRng","ReseedingRng","as_rngcore","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","fill_bytes","fill_bytes","fmt","fmt","fmt","fmt","from","from","from","into","into","into","new","new","next_u32","next_u32","next_u64","next_u64","provide","reseed","source","to_owned","to_string","try_fill_bytes","try_fill_bytes","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","StepRng","borrow","borrow_mut","clone","clone_into","eq","fill_bytes","fmt","from","into","new","next_u32","next_u64","to_owned","try_fill_bytes","try_from","try_into","type_id","vzip","Item","IteratorRandom","SliceChooseIter","SliceRandom","borrow","borrow_mut","choose","choose","choose_multiple","choose_multiple","choose_multiple_fill","choose_multiple_weighted","choose_mut","choose_stable","choose_weighted","choose_weighted_mut","fmt","from","index","into","into_iter","len","next","partial_shuffle","shuffle","size_hint","try_from","try_into","type_id","vzip","IndexVec","IndexVecIntoIter","IndexVecIter","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","fmt","fmt","fmt","from","from","from","from","from","index","into","into","into","into_iter","into_iter","into_iter","into_vec","is_empty","iter","len","next","next","sample","sample_weighted","size_hint","size_hint","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip"],"q":["rand","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::uniform","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::weighted","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::distributions::weighted::alias_method","","","","","","","","","","","","rand::prelude","","","","","","","rand::rngs","","","","","","","","","","","","","","","","","","","","","rand::rngs::adapter","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::rngs::mock","","","","","","","","","","","","","","","","","","","rand::seq","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand::seq::index","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Codes at or above this point can be used by users to …","A marker trait used to indicate that an RngCore or …","Error type of random number generators","Types which may be filled with random data","Codes below this point represent OS Errors (i.e. positive …","An automatically-implemented extension trait on RngCore …","The core of a random number generator.","Seed type, which is restricted to types …","A random number generator that can be explicitly seeded.","","","Retrieve the error code, if any.","Generating random samples from probability distributions","Fill any type implementing Fill with random data","Fill any type implementing Fill with random data","Fill dest with random data.","","","","Returns the argument unchanged.","","Creates a new instance of the RNG seeded via getrandom.","Create a new PRNG seeded from another Rng.","Create a new PRNG using the given seed.","Return a random value supporting the Standard distribution.","Return a random value supporting the Standard distribution.","Return a bool with a probability p of being true.","Return a bool with a probability p of being true.","Generate a random value in the given range.","Generate a random value in the given range.","Return a bool with a probability of numerator/denominator …","Return a bool with a probability of numerator/denominator …","Reference the inner error (std only)","Calls U::from(self).","Construct from any type supporting std::error::Error","Return the next random u32.","Return the next random u64.","Convenience re-export of common members","","Extract the raw OS error code (if this error came from the …","","Random number generators and adapters","Sample a new value, using the given distribution.","Sample a new value, using the given distribution.","Create an iterator that generates values using the given …","Create an iterator that generates values using the given …","Create a new PRNG using a u64 seed.","Sequence-related functionality","","Unwrap the inner error (std only)","","Fill self with random data","Fill any type implementing Fill with random data","Fill any type implementing Fill with random data","Fill dest entirely with random data.","","","","","All items in the provided weight collection are zero.","Sample a u8, uniformly distributed over ASCII letters and …","The Bernoulli distribution.","Error type returned from Bernoulli::new.","An iterator that generates random values of T with …","A distribution of values of type S derived from the …","String sampler","Types (distributions) that can be used to create a random …","p < 0 or p > 1.","A weight is either less than zero, greater than the …","The provided weight collection contains no items.","A distribution to sample floating point numbers uniformly …","A distribution to sample floating point numbers uniformly …","A distribution to sample items uniformly from a slice.","A generic random value distribution, implemented for many …","Too many weights are provided (length greater than u32::MAX…","Sample values uniformly between two bounds.","Error type returned from WeightedIndex::new.","A distribution using weighted sampling of discrete items","Append len random chars to string","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Construct a new Bernoulli with the probability of success …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Create a distribution of values of ‘S’ by mapping the …","Create a distribution of values of ‘S’ by mapping the …","Construct a new Bernoulli with the given probability of …","Create a new Slice instance which samples uniformly from …","","","Generate a random value of T, using rng as the source of …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Create an iterator that generates random values of T, …","Create an iterator that generates random values of T, …","Generate a String of len random chars","Generate a String of len random chars","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A distribution uniformly sampling numbers within a given …","","","","","","","","","","","Weighted index sampling","Helper trait similar to Borrow but implemented only for …","Range that supports generating a single sample efficiently.","Helper trait for creating objects using the correct …","The UniformSampler implementation supporting type X.","Sample values uniformly between two bounds.","The back-end implementing UniformSampler for char.","The back-end implementing UniformSampler for Duration.","The back-end implementing UniformSampler for …","The back-end implementing UniformSampler for integer types.","Helper trait handling actual uniform sampling.","The type sampled by this implementation.","Immutably borrows from an owned value. See Borrow::borrow","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Check whether the range is empty.","Construct self, with inclusive lower bound and exclusive …","Create a new Uniform instance which samples uniformly from …","","","","","","","","","","","","","","","","","Construct self, with inclusive bounds [low, high].","Create a new Uniform instance which samples uniformly from …","","","","","","","","","","","","","","","","","Sample a value.","","","","","","","","","","","","","","","","","Generate a sample from the given range.","Sample a single value uniformly from a range with …","","","","","","","","","","","","","","","Sample a single value uniformly from a range with …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","All items in the provided weight collection are zero.","A weight is either less than zero, greater than the …","The provided weight collection contains no items.","Too many weights are provided (length greater than u32::MAX…","Error type returned from WeightedIndex::new.","A distribution using weighted sampling of discrete items","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Creates a new a WeightedIndex Distribution using the values","","","","","","","","","","","","Update a subset of weights, without changing the number of …","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","A random number generator that retrieves randomness from …","Wrappers / adapters forming RNGs","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Mock random number generator","","","","","","","","","ReadRng error type","An RNG that reads random bytes straight from any type …","A wrapper around any PRNG that implements BlockRngCore, …","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Create a new ReadRng from a Read.","Create a new ReseedingRng from an existing PRNG, combined …","","","","","","Reseed the internal PRNG.","","","","","","","","","","","","","","","","","","A simple implementation of RngCore for testing purposes.","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Create a StepRng, yielding an arithmetic sequence starting …","","","","","","","","","The element type.","Extension trait on iterators, providing random sampling …","An iterator over multiple slice elements.","Extension trait on slices, providing random mutation and …","","","Returns a reference to one random element of the slice, or …","Choose one element at random from the iterator.","Chooses amount elements from the slice at random, without …","Collects amount values at random from the iterator into a …","Collects values at random from the iterator into a …","Similar to choose_multiple, but where the likelihood of …","Returns a mutable reference to one random element of the …","Choose one element at random from the iterator.","Similar to choose, but where the likelihood of each …","Similar to choose_mut, but where the likelihood of each …","","Returns the argument unchanged.","Low-level API for sampling indices","Calls U::from(self).","","","","Shuffle a slice in place, but exit early.","Shuffle a mutable slice in place.","","","","","","A vector of indices.","Return type of IndexVec::into_iter.","Return type of IndexVec::iter.","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Return the value at the given index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert into an iterator over the indices as a sequence of …","","","Return result as a Vec<usize>. Conversion may or may not …","Returns true if the length is 0.","Iterate over the indices as a sequence of usize values","Returns the number of indices","","","Randomly sample exactly amount distinct indices from …","Randomly sample exactly amount distinct indices from …","","","","","","","","","","","","","","","",""],"i":[1,0,0,0,1,0,0,79,0,1,1,1,0,80,80,15,1,1,1,1,1,79,79,79,80,80,80,80,80,80,80,80,1,1,1,15,15,0,1,1,15,0,80,80,80,80,79,0,1,1,1,81,80,80,15,1,1,1,1,63,0,0,0,0,0,0,0,27,63,63,0,0,0,0,63,0,0,0,82,24,25,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,26,27,28,29,24,31,33,25,26,27,28,29,24,31,33,25,26,27,33,26,27,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,33,33,25,26,26,27,19,37,28,29,24,31,33,25,19,18,18,26,31,19,27,18,26,37,28,28,29,29,24,31,33,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,18,18,82,82,19,26,27,28,29,24,31,33,25,27,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,26,27,19,37,28,29,24,31,33,25,0,26,27,19,37,28,29,24,31,33,25,0,0,0,0,32,0,0,0,0,0,0,83,84,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,56,58,56,57,58,59,56,57,58,59,56,57,58,59,85,83,33,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,83,33,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,83,56,56,56,56,56,56,56,56,56,56,56,56,57,58,58,59,85,83,56,56,56,56,56,56,56,56,56,56,56,56,58,58,83,56,56,56,56,56,56,56,56,56,56,56,56,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,56,57,58,59,63,63,63,63,0,0,0,62,63,62,63,62,63,62,63,62,63,62,63,63,62,63,62,63,62,63,62,62,63,63,62,63,62,63,62,63,62,62,63,0,0,65,65,65,65,65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,67,67,67,67,67,67,67,67,67,67,0,67,67,67,67,67,67,67,67,0,0,0,68,70,71,68,70,71,68,68,68,70,68,70,71,71,68,70,71,68,70,71,68,70,68,70,68,70,68,71,68,71,68,71,70,68,70,71,68,70,71,68,70,71,68,70,71,68,0,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,72,86,0,0,0,73,73,86,87,86,87,87,86,86,87,86,86,73,73,0,73,73,73,73,86,86,73,73,73,73,73,0,0,0,76,78,77,76,78,77,76,77,76,77,76,76,78,77,76,76,76,78,77,76,76,78,77,76,78,77,76,76,76,76,78,77,0,0,78,77,76,77,76,78,77,76,78,77,76,78,77,76,78,77],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[1,[[3,[2]]]],0,[[]],[[]],[[]],[[1,4],[[6,[5]]]],[[1,4],[[6,[5]]]],[7,1],[[]],[2,1],[[]],[[],[[6,[1]]]],[[]],[[]],[[]],[8,9],[8,9],[[]],[[]],[[10,10],9],[[10,10],9],[1,11],[[]],[[],1],[[],10],[[],12],0,[13],[1,[[3,[14]]]],[15,[[6,[16,17]]]],0,[18],[18],[[],19],[[],19],[12],0,[1,[[3,[11]]]],[1,[[21,[11,20]]]],[[],22],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[22,16]],[[24,22,16]],[[25,22,16]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[26,26],[27,27],[28,28],[29,29],[24,24],[[[31,[30]]],[[31,[30]]]],[[[33,[[0,[30,32]]]]],[[33,[[0,[30,32]]]]]],[25,25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[26,26],9],[[27,27],9],[[[33,[[0,[34,32]]]],33],9],[[26,4],35],[[27,4],35],[[27,4],35],[[[19,[36,36,36]],4],35],[[[37,[36,36,36,36]],4],35],[[28,4],35],[[29,4],35],[[24,4],35],[[[31,[36]],4],35],[[[33,[[0,[36,32]]]],4],35],[[25,4],35],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[38,[32]]],[[33,[32]]]],[[[39,[32]]],[[33,[32]]]],[[]],[[10,10],[[6,[26,27]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[],37],[8,[[6,[26,27]]]],[[],[[6,[31,0]]]],[19,3],[13],[[]],[26,9],[37],[28,40],[28,8],[29,8],[29,40],[24,41],[31],[[[33,[32]]],32],[25],[25],[25,42],[25,10],[25,12],[25,43],[25,16],[25,44],[25,45],[25,46],[25,3],[25,14],[25,47],[25,48],[25,49],[25,50],[25],[25,51],[25],[25,2],[25,52],[25,53],[25,54],[25,55],[25],[25],[25,9],[25],[25],[25],[25],[25,41],[25],[25],[25],[25,8],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25,40],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[25],[[],19],[[],19],[16,22],[16,22],[19],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[56,[30]]],[[56,[30]]]],[57,57],[[[58,[30]]],[[58,[30]]]],[59,59],[[]],[[]],[[]],[[]],[[[56,[34]],56],9],[[[58,[34]],58],9],[[[56,[36]],4],35],[[57,4],35],[[[58,[36]],4],35],[[59,4],35],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],9],[[]],[[],[[33,[32]]]],[[],[[56,[16]]]],[[],[[56,[49]]]],[[],[[56,[46]]]],[[],[[56,[41]]]],[[],[[56,[48]]]],[[],[[56,[47]]]],[[],[[56,[44]]]],[[],[[56,[14]]]],[[],[[56,[10]]]],[[],[[56,[43]]]],[[],[[56,[12]]]],[[],[[56,[42]]]],[[],57],[[],[[58,[8]]]],[[],[[58,[40]]]],[[],59],[[]],[[],[[33,[32]]]],[[],[[56,[14]]]],[[],[[56,[12]]]],[[],[[56,[41]]]],[[],[[56,[16]]]],[[],[[56,[44]]]],[[],[[56,[49]]]],[[],[[56,[10]]]],[[],[[56,[46]]]],[[],[[56,[48]]]],[[],[[56,[42]]]],[[],[[56,[47]]]],[[],[[56,[43]]]],[[],57],[[],[[58,[8]]]],[[],[[58,[40]]]],[[],59],[[]],[[[56,[41]]]],[[[56,[42]]]],[[[56,[10]]]],[[[56,[14]]]],[[[56,[47]]]],[[[56,[16]]]],[[[56,[43]]]],[[[56,[48]]]],[[[56,[12]]]],[[[56,[46]]]],[[[56,[44]]]],[[[56,[49]]]],[57],[[[58,[40]]]],[[[58,[8]]]],[59,60],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[],23],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[[62,[[0,[30,32,61]]]]],[[62,[[0,[30,32,61]]]]]],[63,63],[[]],[[]],[[[62,[[0,[34,32,61]]]],62],9],[[63,63],9],[[[62,[[0,[36,32,61]]]],4],35],[[63,4],35],[[63,4],35],[[]],[[]],[[]],[[]],[[],[[6,[[62,[[0,[32,61]]]],63]]]],[13],[62,16],[[]],[[]],[[],22],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[[62,[[0,[32,61]]]]],[[6,[63]]]],[[]],[[]],0,0,[[]],[[]],[[[65,[[0,[36,64]]]],4],35],[[]],[[]],[[[66,[64]]],[[6,[[65,[64]],63]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,0,0,0,0,0,[[],15],[[]],[[]],[67,67],[[]],[[],67],[67],[[67,4],[[6,[5]]]],[[]],[[]],0,[67,10],[67,12],[[]],[67,[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,[[],15],[[]],[[]],[[]],[[]],[[]],[[]],[68,68],[[]],[[[70,[69]]]],[[[68,[15]]]],[[[70,[36]],4],35],[[71,4],35],[[71,4],35],[[[68,[36,36]],4],35],[[]],[[]],[[]],[[]],[[]],[[]],[69,[[70,[69]]]],[12,68],[[[70,[69]]],10],[[[68,[15]]],10],[[[70,[69]]],12],[[[68,[15]]],12],[13],[68,[[6,[1]]]],[71,[[3,[11]]]],[[]],[[],22],[[[70,[69]]],[[6,[1]]]],[[[68,[15]]],[[6,[1]]]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[]],[[]],[[]],0,[[]],[[]],[72,72],[[]],[[72,72],9],[72],[[72,4],35],[[]],[[]],[[12,12],72],[72,10],[72,12],[[]],[72,[[6,[1]]]],[[],6],[[],6],[[],23],[[]],0,0,0,0,[[]],[[]],[[],3],[[],3],[16,73],[16,66],[[],16],[16,[[6,[73,63]]]],[[],3],[[],3],[[],[[6,[63]]]],[[],[[6,[63]]]],[[[73,[[0,[36,74]],36]],4],35],[[]],0,[[]],[[]],[[[73,[[0,[[75,[16]],74]]]]],16],[[[73,[[0,[[75,[16]],74]]]]],3],[16],[[]],[[[73,[[0,[[75,[16]],74]]]]]],[[],6],[[],6],[[],23],[[]],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[76,76],[77,77],[[]],[[]],[[76,76],9],[[76,4],35],[[78,4],35],[[77,4],35],[[[66,[10]]],76],[[[66,[16]]],76],[[]],[[]],[[]],[[76,16],16],[[]],[[]],[[]],[76,77],[[]],[[]],[76,[[66,[16]]]],[76,9],[76,78],[76,16],[78,[[3,[16]]]],[77,3],[[16,16],76],[[16,16],[[6,[76,63]]]],[78],[77],[[]],[[]],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],23],[[],23],[[],23],[[]],[[]],[[]]],"p":[[3,"Error"],[3,"NonZeroU32"],[4,"Option"],[3,"Formatter"],[3,"Error"],[4,"Result"],[3,"Error"],[15,"f64"],[15,"bool"],[15,"u32"],[8,"Error"],[15,"u64"],[3,"Demand"],[15,"i32"],[8,"RngCore"],[15,"usize"],[3,"Error"],[8,"Distribution"],[3,"DistIter"],[3,"Global"],[3,"Box"],[3,"String"],[3,"TypeId"],[3,"Alphanumeric"],[3,"Standard"],[3,"Bernoulli"],[4,"BernoulliError"],[3,"OpenClosed01"],[3,"Open01"],[8,"Clone"],[3,"Slice"],[8,"SampleUniform"],[3,"Uniform"],[8,"PartialEq"],[6,"Result"],[8,"Debug"],[3,"DistMap"],[3,"RangeInclusive"],[3,"Range"],[15,"f32"],[15,"u8"],[15,"u16"],[15,"u128"],[15,"i8"],[3,"Wrapping"],[15,"i16"],[15,"i64"],[15,"i128"],[15,"isize"],[3,"NonZeroU8"],[3,"NonZeroU16"],[3,"NonZeroU64"],[3,"NonZeroU128"],[3,"NonZeroUsize"],[15,"char"],[3,"UniformInt"],[3,"UniformChar"],[3,"UniformFloat"],[3,"UniformDuration"],[3,"Duration"],[8,"PartialOrd"],[3,"WeightedIndex"],[4,"WeightedError"],[8,"Weight"],[3,"WeightedIndex"],[3,"Vec"],[3,"OsRng"],[3,"ReseedingRng"],[8,"Read"],[3,"ReadRng"],[3,"ReadError"],[3,"StepRng"],[3,"SliceChooseIter"],[8,"Sized"],[8,"Index"],[4,"IndexVec"],[4,"IndexVecIntoIter"],[4,"IndexVecIter"],[8,"SeedableRng"],[8,"Rng"],[8,"Fill"],[8,"DistString"],[8,"UniformSampler"],[8,"SampleBorrow"],[8,"SampleRange"],[8,"SliceRandom"],[8,"IteratorRandom"]]},\ -"rand_chacha":{"doc":"The ChaCha random number generator.","t":[3,3,3,3,3,3,6,6,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["ChaCha12Core","ChaCha12Rng","ChaCha20Core","ChaCha20Rng","ChaCha8Core","ChaCha8Rng","ChaChaCore","ChaChaRng","as_rngcore","as_rngcore","as_rngcore","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","eq","eq","eq","eq","eq","eq","fill_bytes","fill_bytes","fill_bytes","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_seed","from_seed","from_seed","from_seed","from_seed","from_seed","generate","generate","generate","get_seed","get_seed","get_seed","get_stream","get_stream","get_stream","get_word_pos","get_word_pos","get_word_pos","into","into","into","into","into","into","next_u32","next_u32","next_u32","next_u64","next_u64","next_u64","rand_core","set_stream","set_stream","set_stream","set_word_pos","set_word_pos","set_word_pos","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_fill_bytes","try_fill_bytes","try_fill_bytes","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip"],"q":["rand_chacha","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["ChaCha with 12 rounds","A cryptographically secure random number generator that …","ChaCha with 20 rounds","A cryptographically secure random number generator that …","ChaCha with 8 rounds","A cryptographically secure random number generator that …","ChaCha with 20 rounds, low-level interface","ChaCha with 20 rounds","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","Get the seed.","Get the seed.","Get the seed.","Get the stream number.","Get the stream number.","Get the stream number.","Get the offset from the start of the stream, in 32-bit …","Get the offset from the start of the stream, in 32-bit …","Get the offset from the start of the stream, in 32-bit …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","Set the stream number.","Set the stream number.","Set the stream number.","Set the offset from the start of the stream, in 32-bit …","Set the offset from the start of the stream, in 32-bit …","Set the offset from the start of the stream, in 32-bit …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,3,5,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,3,5,7,2,3,4,5,6,7,2,3,3,4,5,5,6,7,7,2,3,4,5,6,7,2,4,6,3,5,7,3,5,7,3,5,7,2,3,4,5,6,7,3,5,7,3,5,7,0,3,5,7,3,5,7,2,3,4,5,6,7,3,5,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7,2,3,4,5,6,7],"f":[0,0,0,0,0,0,0,0,[[],1],[[],1],[[],1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[[]],[[]],[[]],[[]],[[]],[[]],[[2,2],8],[[3,3],8],[[4,4],8],[[5,5],8],[[6,6],8],[[7,7],8],[3],[5],[7],[[2,9],10],[[3,9],10],[[4,9],10],[[5,9],10],[[6,9],10],[[7,9],10],[[]],[[]],[2,3],[[]],[4,5],[[]],[[]],[[]],[6,7],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[2],[4],[6],[3],[5],[7],[3,11],[5,11],[7,11],[3,12],[5,12],[7,12],[[]],[[]],[[]],[[]],[[]],[[]],[3,13],[5,13],[7,13],[3,11],[5,11],[7,11],0,[[3,11]],[[5,11]],[[7,11]],[[3,12]],[[5,12]],[[7,12]],[[]],[[]],[[]],[[]],[[]],[[]],[3,[[15,[14]]]],[5,[[15,[14]]]],[7,[[15,[14]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[]],[[]],[[]],[[]],[[]],[[]]],"p":[[8,"RngCore"],[3,"ChaCha20Core"],[3,"ChaCha20Rng"],[3,"ChaCha12Core"],[3,"ChaCha12Rng"],[3,"ChaCha8Core"],[3,"ChaCha8Rng"],[15,"bool"],[3,"Formatter"],[6,"Result"],[15,"u64"],[15,"u128"],[15,"u32"],[3,"Error"],[4,"Result"],[3,"TypeId"]]},\ -"rand_core":{"doc":"Random number generation traits","t":[18,8,8,3,18,3,8,16,8,10,11,0,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,10,0,11,11,11,0,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,3,3,8,16,16,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,5,5,5,5,5,5],"n":["CUSTOM_START","CryptoRng","CryptoRngCore","Error","INTERNAL_START","OsRng","RngCore","Seed","SeedableRng","as_rngcore","as_rngcore","block","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","code","default","fill_bytes","fill_bytes","fmt","fmt","fmt","from","from","from","from","from_entropy","from_rng","from_seed","impls","inner","into","into","le","new","next_u32","next_u32","next_u64","next_u64","provide","raw_os_error","read","seed_from_u64","source","take_inner","to_owned","to_string","try_fill_bytes","try_fill_bytes","try_from","try_from","try_into","try_into","type_id","type_id","BlockRng","BlockRng64","BlockRngCore","Item","Results","as_rngcore","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","core","core","fill_bytes","fill_bytes","fmt","fmt","from","from","from_rng","from_rng","from_seed","from_seed","generate","generate_and_set","generate_and_set","index","index","into","into","new","new","next_u32","next_u32","next_u64","next_u64","reset","reset","seed_from_u64","seed_from_u64","to_owned","to_owned","try_fill_bytes","try_fill_bytes","try_from","try_from","try_into","try_into","type_id","type_id","fill_bytes_via_next","fill_via_u32_chunks","fill_via_u64_chunks","next_u32_via_fill","next_u64_via_fill","next_u64_via_u32","read_u32_into","read_u64_into"],"q":["rand_core","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand_core::block","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rand_core::impls","","","","","","rand_core::le",""],"d":["Codes at or above this point can be used by users to …","A marker trait used to indicate that an RngCore or …","An extension trait that is automatically implemented for …","Error type of random number generators","Codes below this point represent OS Errors (i.e. positive …","A random number generator that retrieves randomness from …","The core of a random number generator.","Seed type, which is restricted to types …","A random number generator that can be explicitly seeded.","Upcast to an RngCore trait object.","","The BlockRngCore trait and implementation helpers","","","","","","","Retrieve the error code, if any.","","Fill dest with random data.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Creates a new instance of the RNG seeded via getrandom.","Create a new PRNG seeded from another Rng.","Create a new PRNG using the given seed.","Helper functions for implementing RngCore functions.","Reference the inner error (std only)","Calls U::from(self).","Calls U::from(self).","Little-Endian utilities","Construct from any type supporting std::error::Error","Return the next random u32.","","Return the next random u64.","","","Extract the raw OS error code (if this error came from the …","","Create a new PRNG using a u64 seed.","","Unwrap the inner error (std only)","","","Fill dest entirely with random data.","","","","","","","","A wrapper type implementing RngCore for some type …","A wrapper type implementing RngCore for some type …","A trait for RNGs which do not generate random numbers …","Results element type, e.g. u32.","Results type. This is the ‘block’ an RNG implementing …","","","","","","","","","","The core part of the RNG, implementing the generate …","The core part of the RNG, implementing the generate …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Generate a new block of results.","Generate a new set of results immediately, setting the …","Generate a new set of results immediately, setting the …","Get the index into the result buffer.","Get the index into the result buffer.","Calls U::from(self).","Calls U::from(self).","Create a new BlockRng from an existing RNG implementing …","Create a new BlockRng from an existing RNG implementing …","","","","","Reset the number of available results. This will force a …","Reset the number of available results. This will force a …","","","","","","","","","","","","","Implement fill_bytes via next_u64 and next_u32, …","Implement fill_bytes by reading chunks from the output …","Implement fill_bytes by reading chunks from the output …","Implement next_u32 via fill_bytes, little-endian order.","Implement next_u64 via fill_bytes, little-endian order.","Implement next_u64 via next_u32, little-endian order.","Reads unsigned 32 bit integers from src into dst.","Reads unsigned 64 bit integers from src into dst."],"i":[3,0,0,0,3,0,0,26,0,27,2,0,3,2,3,2,2,2,3,2,1,2,3,3,2,3,3,3,2,26,26,26,0,3,3,2,0,3,1,2,1,2,3,3,1,26,3,3,2,3,1,2,3,2,3,2,3,2,0,0,0,21,21,23,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,21,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,23,24,0,0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,[[],1],[[],1],0,[[]],[[]],[[]],[[]],[2,2],[[]],[3,[[5,[4]]]],[[],2],[[]],[2],[[3,6],7],[[3,6],7],[[2,6],7],[4,3],[8,3],[[]],[[]],[[]],[1,[[9,[3]]]],[[]],0,[3,10],[[]],[[]],0,[[],3],[[],11],[2,11],[[],12],[2,12],[13],[3,[[5,[14]]]],[1,[[9,[15,16]]]],[12],[3,[[5,[10]]]],[3,[[17,[10]]]],[[]],[[],18],[[],[[9,[3]]]],[2,[[9,[3]]]],[[],9],[[],9],[[],9],[[],9],[[],19],[[],19],0,0,0,0,0,[[],1],[[]],[[]],[[]],[[]],[[[23,[[0,[20,21,22]]]]],[[23,[[0,[20,21,22]]]]]],[[[24,[[0,[20,21,22]]]]],[[24,[[0,[20,21,22]]]]]],[[]],[[]],0,0,[[[23,[21]]]],[[[24,[21]]]],[[[23,[[0,[21,25]]]],6],7],[[[24,[[0,[21,25]]]],6],7],[[]],[[]],[1,[[9,[[23,[[0,[21,26]]]],3]]]],[1,[[9,[[24,[[0,[21,26]]]],3]]]],[[],[[23,[[0,[21,26]]]]]],[[],[[24,[[0,[21,26]]]]]],[[]],[[[23,[21]],15]],[[[24,[21]],15]],[[[23,[21]]],15],[[[24,[21]]],15],[[]],[[]],[21,[[23,[21]]]],[21,[[24,[21]]]],[[[23,[21]]],11],[[[24,[21]]],11],[[[23,[21]]],12],[[[24,[21]]],12],[[[23,[21]]]],[[[24,[21]]]],[12,[[23,[[0,[21,26]]]]]],[12,[[24,[[0,[21,26]]]]]],[[]],[[]],[[[23,[21]]],[[9,[3]]]],[[[24,[21]]],[[9,[3]]]],[[],9],[[],9],[[],9],[[],9],[[],19],[[],19],[[]],[[]],[[]],[[],11],[[],12],[[],12],[[]],[[]]],"p":[[8,"RngCore"],[3,"OsRng"],[3,"Error"],[3,"NonZeroU32"],[4,"Option"],[3,"Formatter"],[6,"Result"],[3,"Error"],[4,"Result"],[8,"Error"],[15,"u32"],[15,"u64"],[3,"Demand"],[15,"i32"],[15,"usize"],[3,"Error"],[3,"Box"],[3,"String"],[3,"TypeId"],[8,"Clone"],[8,"BlockRngCore"],[8,"Sized"],[3,"BlockRng"],[3,"BlockRng64"],[8,"Debug"],[8,"SeedableRng"],[8,"CryptoRngCore"]]},\ -"regex":{"doc":"This crate provides a library for parsing, compiling, and …","t":[12,3,3,3,3,13,4,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,3,3,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["0","CaptureLocations","CaptureMatches","CaptureNames","Captures","CompiledTooBig","Error","Match","Matches","NoExpand","Regex","RegexBuilder","RegexSet","RegexSetBuilder","Replacer","ReplacerRef","SetMatches","SetMatchesIntoIter","SetMatchesIter","Split","SplitN","SubCaptureMatches","Syntax","as_str","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build","by_ref","by_ref","bytes","capture_locations","capture_names","captures","captures_iter","captures_len","captures_read","captures_read_at","case_insensitive","case_insensitive","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count","count","description","dfa_size_limit","dfa_size_limit","dot_matches_new_line","dot_matches_new_line","empty","end","eq","eq","escape","expand","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_str","get","get","ignore_whitespace","ignore_whitespace","index","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","is_match","is_match","is_match_at","iter","iter","len","len","len","len","matched","matched_any","matches","multi_line","multi_line","name","nest_limit","nest_limit","new","new","new","new","next","next","next","next","next","next","next","next","next_back","next_back","no_expansion","no_expansion","no_expansion","no_expansion","octal","octal","patterns","provide","range","replace","replace_all","replace_append","replace_append","replace_append","replacen","shortest_match","shortest_match_at","size_hint","size_hint","size_hint","size_hint","size_hint","size_limit","size_limit","split","splitn","start","swap_greed","swap_greed","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unicode","unicode","0","0","0","CaptureLocations","CaptureMatches","CaptureNames","Captures","Match","Matches","NoExpand","Regex","RegexBuilder","RegexSet","RegexSetBuilder","Replacer","ReplacerRef","SetMatches","SetMatchesIntoIter","SetMatchesIter","Split","SplitN","SubCaptureMatches","as_bytes","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","build","by_ref","by_ref","capture_locations","capture_names","captures","captures_iter","captures_len","captures_read","captures_read_at","case_insensitive","case_insensitive","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count","dfa_size_limit","dfa_size_limit","dot_matches_new_line","dot_matches_new_line","empty","end","eq","expand","find","find_at","find_iter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_str","get","get","ignore_whitespace","ignore_whitespace","index","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","is_match","is_match","is_match_at","iter","iter","len","len","len","len","matched","matched_any","matches","multi_line","multi_line","name","nest_limit","nest_limit","new","new","new","new","next","next","next","next","next","next","next","next","next_back","next_back","no_expansion","no_expansion","no_expansion","no_expansion","octal","octal","patterns","range","replace","replace_all","replace_append","replace_append","replace_append","replacen","shortest_match","shortest_match_at","size_hint","size_hint","size_hint","size_hint","size_limit","size_limit","split","splitn","start","swap_greed","swap_greed","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unicode","unicode"],"q":["regex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex::Error","","regex::bytes","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","CaptureLocations is a low level representation of the raw …","An iterator that yields all non-overlapping capture groups …","An iterator over the names of all possible captures.","Captures represents a group of captured strings for a …","The compiled program exceeded the set size limit. The …","An error that occurred during parsing or compiling a …","Match represents a single match of a regex in a haystack.","An iterator over all non-overlapping matches for a …","NoExpand indicates literal string replacement.","A compiled regular expression for matching Unicode strings.","A configurable builder for a regular expression.","Match multiple (possibly overlapping) regular expressions …","A configurable builder for a set of regular expressions.","Replacer describes types that can be used to replace …","By-reference adaptor for a Replacer","A set of matches returned by a regex set.","An owned iterator over the set of matches from a regex set.","A borrowed iterator over the set of matches from a regex …","Yields all substrings delimited by a regular expression …","Yields at most N substrings delimited by a regular …","An iterator that yields all capturing matches in the order …","A syntax error.","Returns the matched text.","Returns the original string of this regex.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Consume the builder and compile the regular expression.","Consume the builder and compile the regular expressions …","Return a Replacer that borrows and wraps this Replacer.","Return a Replacer that borrows and wraps this Replacer.","Match regular expressions on arbitrary bytes.","Returns an empty set of capture locations that can be …","Returns an iterator over the capture names.","Returns the capture groups corresponding to the …","Returns an iterator over all the non-overlapping capture …","Returns the number of captures.","This is like captures, but uses CaptureLocations instead of","Returns the same as captures, but starts the search at the …","Set the value for the case insensitive (i) flag.","Set the value for the case insensitive (i) flag.","","","","","","","","","","","","","","","","","","","","","","","","Set the approximate size of the cache used by the DFA.","Set the approximate size of the cache used by the DFA.","Set the value for the any character (s) flag, where in . …","Set the value for the any character (s) flag, where in . …","Create a new empty regex set.","Returns the ending byte offset of the match in the …","","","Escapes all regular expression meta characters in text.","Expands all instances of $name in replacement to the …","Returns the start and end byte range of the leftmost-first …","Returns the same as find, but starts the search at the …","Returns an iterator for each successive non-overlapping …","","","","","","","","","","","Shows the original regular expression.","Shows the original regular expression.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Attempts to parse a string into a regular expression","Returns the match associated with the capture group at …","Returns the start and end positions of the Nth capture …","Set the value for the ignore whitespace (x) flag.","Set the value for the ignore whitespace (x) flag.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","Returns true if this set contains no regular expressions.","Returns true if and only if one of the regexes in this set …","Returns true if and only if there is a match for the regex …","Returns the same as is_match, but starts the search at the …","An iterator that yields all capturing matches in the order …","Returns an iterator over indexes in the regex that matched.","Returns the total number of capture groups (even if they …","Returns the total number of regular expressions in this …","The total number of regexes in the set that created these …","Returns the total number of capture groups (even if they …","Whether the regex at the given index matched.","Whether this set contains any matches.","Returns the set of regular expressions that match in the …","Set the value for the multi-line matching (m) flag.","Set the value for the multi-line matching (m) flag.","Returns the match for the capture group named name. If name…","Set the nesting limit for this parser.","Set the nesting limit for this parser.","Create a new regular expression builder with the given …","Create a new regular expression builder with the given …","Create a new regex set with the given regular expressions.","Compiles a regular expression. Once compiled, it can be …","","","","","","","","","","","Return a fixed unchanging replacement string.","Return a fixed unchanging replacement string.","","","Whether to support octal syntax or not.","Whether to support octal syntax or not.","Returns the patterns that this set will match on.","","Returns the range over the starting and ending byte …","Replaces the leftmost-first match with the replacement …","Replaces all non-overlapping matches in text with the …","Appends text to dst to replace the current match.","","","Replaces at most limit non-overlapping matches in text …","Returns the end location of a match in the text given.","Returns the same as shortest_match, but starts the search …","","","","","","Set the approximate size limit of the compiled regular …","Set the approximate size limit of the compiled regular …","Returns an iterator of substrings of text delimited by a …","Returns an iterator of at most limit substrings of text …","Returns the starting byte offset of the match in the …","Set the value for the greedy swap (U) flag.","Set the value for the greedy swap (U) flag.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Set the value for the Unicode (u) flag.","Set the value for the Unicode (u) flag.","","","","CaptureLocations is a low level representation of the raw …","An iterator that yields all non-overlapping capture groups …","An iterator over the names of all possible captures.","Captures represents a group of captured byte strings for a …","Match represents a single match of a regex in a haystack.","An iterator over all non-overlapping matches for a …","NoExpand indicates literal byte string replacement.","A compiled regular expression for matching arbitrary bytes.","A configurable builder for a regular expression.","Match multiple (possibly overlapping) regular expressions …","A configurable builder for a set of regular expressions.","Replacer describes types that can be used to replace …","By-reference adaptor for a Replacer","A set of matches returned by a regex set.","An owned iterator over the set of matches from a regex set.","A borrowed iterator over the set of matches from a regex …","Yields all substrings delimited by a regular expression …","Yields at most N substrings delimited by a regular …","An iterator that yields all capturing matches in the order …","Returns the matched text.","Returns the original string of this regex.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Consume the builder and compile the regular expression.","Consume the builder and compile the regular expressions …","Return a Replacer that borrows and wraps this Replacer.","Return a Replacer that borrows and wraps this Replacer.","Returns an empty set of capture locations that can be …","Returns an iterator over the capture names.","Returns the capture groups corresponding to the …","Returns an iterator over all the non-overlapping capture …","Returns the number of captures.","This is like captures, but uses CaptureLocations instead of","Returns the same as captures_read, but starts the search …","Set the value for the case insensitive (i) flag.","Set the value for the case insensitive (i) flag.","","","","","","","","","","","","","","","","","","","","Set the approximate size of the cache used by the DFA.","Set the approximate size of the cache used by the DFA.","Set the value for the any character (s) flag, where in . …","Set the value for the any character (s) flag, where in . …","Create a new empty regex set.","Returns the ending byte offset of the match in the …","","Expands all instances of $name in replacement to the …","Returns the start and end byte range of the leftmost-first …","Returns the same as find, but starts the search at the …","Returns an iterator for each successive non-overlapping …","","","","","Shows the original regular expression.","Shows the original regular expression.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Attempts to parse a string into a regular expression","Returns the match associated with the capture group at …","Returns the start and end positions of the Nth capture …","Set the value for the ignore whitespace (x) flag.","Set the value for the ignore whitespace (x) flag.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","Returns true if this set contains no regular expressions.","Returns true if and only if there is a match for the regex …","Returns true if and only if one of the regexes in this set …","Returns the same as is_match, but starts the search at the …","An iterator that yields all capturing matches in the order …","Returns an iterator over indexes in the regex that matched.","Returns the total number of capture groups (even if they …","Returns the total number of capture groups (even if they …","Returns the total number of regular expressions in this …","The total number of regexes in the set that created these …","Whether the regex at the given index matched.","Whether this set contains any matches.","Returns the set of regular expressions that match in the …","Set the value for the multi-line matching (m) flag.","Set the value for the multi-line matching (m) flag.","Returns the match for the capture group named name. If name…","Set the nesting limit for this parser.","Set the nesting limit for this parser.","Create a new regular expression builder with the given …","Create a new regular expression builder with the given …","Compiles a regular expression. Once compiled, it can be …","Create a new regex set with the given regular expressions.","","","","","","","","","","","Return a fixed unchanging replacement byte string.","Return a fixed unchanging replacement byte string.","","","Whether to support octal syntax or not.","Whether to support octal syntax or not.","Returns the patterns that this set will match on.","Returns the range over the starting and ending byte …","Replaces the leftmost-first match with the replacement …","Replaces all non-overlapping matches in text with the …","Appends text to dst to replace the current match.","","","Replaces at most limit non-overlapping matches in text …","Returns the end location of a match in the text given.","Returns the same as shortest_match, but starts the search …","","","","","Set the approximate size limit of the compiled regular …","Set the approximate size limit of the compiled regular …","Returns an iterator of substrings of text delimited by a …","Returns an iterator of at most limit substrings of text …","Returns the starting byte offset of the match in the …","Set the value for the greedy swap (U) flag.","Set the value for the greedy swap (U) flag.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Set the value for the Unicode (u) flag.","Set the value for the Unicode (u) flag."],"i":[20,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,3,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,4,7,32,32,0,3,3,3,3,3,3,3,4,7,5,8,17,18,1,3,11,10,19,20,5,8,17,18,1,3,11,10,19,20,11,19,5,4,7,4,7,8,1,5,1,0,12,3,3,3,12,5,5,4,7,8,17,25,18,1,3,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,3,12,10,4,7,12,12,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,17,17,25,18,11,26,27,19,14,22,8,8,3,3,12,17,12,8,17,10,17,17,8,4,7,12,4,7,4,7,8,3,25,18,11,26,27,19,14,22,25,18,32,32,9,20,4,7,8,5,1,3,3,32,9,20,3,3,3,25,18,11,27,19,4,7,3,3,1,4,7,5,8,17,18,1,3,11,10,19,20,5,3,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,12,5,4,7,8,17,25,18,1,3,11,26,27,10,19,14,22,9,20,4,7,56,57,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,37,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,38,39,55,55,37,37,37,37,37,37,37,38,39,36,37,43,42,46,47,40,48,49,36,37,43,42,46,47,40,48,49,43,38,39,38,39,40,36,36,44,37,37,37,44,38,39,36,37,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,37,44,42,38,39,44,44,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,51,45,52,53,43,46,48,48,54,49,40,37,40,37,44,48,44,42,40,48,48,48,40,38,39,44,38,39,38,39,37,40,51,45,52,53,43,46,54,49,54,49,55,55,41,47,38,39,40,36,37,37,55,41,47,37,37,37,53,43,54,49,38,39,37,37,36,38,39,36,37,43,42,46,47,40,48,49,37,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,44,38,39,36,37,51,45,52,53,43,42,46,41,47,40,48,54,49,38,39],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[3,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,[[6,[3,5]]]],[7,[[6,[8,5]]]],[[],9],[[],9],0,[3,10],[3,11],[[3,2],[[13,[12]]]],[[3,2],14],[3,15],[[3,10,2],[[13,[1]]]],[[3,10,2,15],[[13,[1]]]],[[4,16],4],[[7,16],7],[5,5],[8,8],[17,17],[18,18],[1,1],[3,3],[11,11],[10,10],[19,19],[20,20],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[11,15],[19,15],[5,2],[[4,15],4],[[7,15],7],[[4,16],4],[[7,16],7],[[],8],[1,15],[[5,5],16],[[1,1],16],[2,21],[[12,2,21]],[[3,2],[[13,[1]]]],[[3,2,15],[[13,[1]]]],[[3,2],22],[[12,23],24],[[5,23],24],[[5,23],24],[[4,23],24],[[7,23],24],[[8,23],24],[[17,23],24],[[25,23],24],[[18,23],24],[[1,23],24],[[3,23],24],[[3,23],24],[[11,23],24],[[26,23],24],[[27,23],24],[[10,23],24],[[19,23],24],[[14,23],24],[[22,23],24],[[[9,[[0,[28,29]]]],23],24],[[20,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[6,[3,5]]]],[[12,15],[[13,[1]]]],[[10,15],13],[[4,16],4],[[7,16],7],[[12,2],2],[[12,15],2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[17],[17],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[8,16],[[8,2],16],[[3,2],16],[[3,2,15],16],[12,19],[17,18],[12,15],[8,15],[17,15],[10,15],[[17,15],16],[17,16],[[8,2],17],[[4,16],4],[[7,16],7],[[12,2],[[13,[1]]]],[[4,30],4],[[7,30],7],[2,4],[[],7],[[],[[6,[8,5]]]],[2,[[6,[3,5]]]],[25,[[13,[15]]]],[18,[[13,[15]]]],[11,[[13,[[13,[2]]]]]],[26,[[13,[2]]]],[27,[[13,[2]]]],[19,[[13,[[13,[1]]]]]],[14,[[13,[12]]]],[22,[[13,[1]]]],[25,[[13,[15]]]],[18,[[13,[15]]]],[[],[[13,[[31,[2]]]]]],[[],[[13,[[31,[2]]]]]],[[[9,[[0,[32,29]]]]],[[13,[[31,[2]]]]]],[20,[[13,[[31,[2]]]]]],[[4,16],4],[[7,16],7],[8],[33],[1,[[34,[15]]]],[[3,2,32],[[31,[2]]]],[[3,2,32],[[31,[2]]]],[[12,21]],[[[9,[[0,[32,29]]]],12,21]],[[20,12,21]],[[3,2,15,32],[[31,[2]]]],[[3,2],[[13,[15]]]],[[3,2,15],[[13,[15]]]],[25],[18],[11],[27],[19],[[4,15],4],[[7,15],7],[[3,2],26],[[3,2,15],27],[1,15],[[4,16],4],[[7,16],7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],21],[[],21],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[4,16],4],[[7,16],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[36],[37,2],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[38,[[6,[37,5]]]],[39,[[6,[40,5]]]],[[],41],[[],41],[37,42],[37,43],[37,[[13,[44]]]],[37,45],[37,15],[[37,42],[[13,[36]]]],[[37,42,15],[[13,[36]]]],[[38,16],38],[[39,16],39],[36,36],[37,37],[43,43],[42,42],[46,46],[47,47],[40,40],[48,48],[49,49],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[43,15],[[38,15],38],[[39,15],39],[[38,16],38],[[39,16],39],[[],40],[36,15],[[36,36],16],[[44,50]],[37,[[13,[36]]]],[[37,15],[[13,[36]]]],[37,51],[[44,23],24],[[38,23],24],[[39,23],24],[[36,23],24],[[37,23],24],[[37,23],24],[[51,23],24],[[45,23],24],[[52,23],24],[[53,23],24],[[43,23],24],[[42,23],24],[[46,23],24],[[[41,[[0,[28,29]]]],23],24],[[47,23],24],[[40,23],24],[[48,23],24],[[54,23],24],[[49,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,[[6,[37,5]]]],[[44,15],[[13,[36]]]],[[42,15],13],[[38,16],38],[[39,16],39],[[44,15]],[[44,2]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[48],[48],[[]],[[]],[40,16],[37,16],[40,16],[[37,15],16],[44,46],[48,49],[44,15],[42,15],[40,15],[48,15],[[48,15],16],[48,16],[40,48],[[38,16],38],[[39,16],39],[[44,2],[[13,[36]]]],[[38,30],38],[[39,30],39],[2,38],[[],39],[2,[[6,[37,5]]]],[[],[[6,[40,5]]]],[51,[[13,[36]]]],[45,[[13,[44]]]],[52,13],[53,13],[43,[[13,[[13,[2]]]]]],[46,[[13,[[13,[36]]]]]],[54,[[13,[15]]]],[49,[[13,[15]]]],[54,[[13,[15]]]],[49,[[13,[15]]]],[[],[[13,[31]]]],[[],[[13,[31]]]],[[[41,[[0,[55,29]]]]],[[13,[31]]]],[47,[[13,[31]]]],[[38,16],38],[[39,16],39],[40],[36,[[34,[15]]]],[[37,55],31],[[37,55],31],[[44,50]],[[[41,[[0,[55,29]]]],44,50]],[[47,44,50]],[[37,15,55],31],[37,[[13,[15]]]],[[37,15],[[13,[15]]]],[53],[43],[54],[49],[[38,15],38],[[39,15],39],[37,52],[[37,15],53],[36,15],[[38,16],38],[[39,16],39],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],21],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[],35],[[38,16],38],[[39,16],39]],"p":[[3,"Match"],[15,"str"],[3,"Regex"],[3,"RegexBuilder"],[4,"Error"],[4,"Result"],[3,"RegexSetBuilder"],[3,"RegexSet"],[3,"ReplacerRef"],[3,"CaptureLocations"],[3,"CaptureNames"],[3,"Captures"],[4,"Option"],[3,"CaptureMatches"],[15,"usize"],[15,"bool"],[3,"SetMatches"],[3,"SetMatchesIter"],[3,"SubCaptureMatches"],[3,"NoExpand"],[3,"String"],[3,"Matches"],[3,"Formatter"],[6,"Result"],[3,"SetMatchesIntoIter"],[3,"Split"],[3,"SplitN"],[8,"Debug"],[8,"Sized"],[15,"u32"],[4,"Cow"],[8,"Replacer"],[3,"Demand"],[3,"Range"],[3,"TypeId"],[3,"Match"],[3,"Regex"],[3,"RegexBuilder"],[3,"RegexSetBuilder"],[3,"RegexSet"],[3,"ReplacerRef"],[3,"CaptureLocations"],[3,"CaptureNames"],[3,"Captures"],[3,"CaptureMatches"],[3,"SubCaptureMatches"],[3,"NoExpand"],[3,"SetMatches"],[3,"SetMatchesIter"],[3,"Vec"],[3,"Matches"],[3,"Split"],[3,"SplitN"],[3,"SetMatchesIntoIter"],[8,"Replacer"],[13,"Syntax"],[13,"CompiledTooBig"]]},\ -"regex_syntax":{"doc":"This crate provides a robust regular expression parser.","t":[4,13,3,3,6,13,3,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,5,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,0,12,12,13,13,3,13,13,13,3,13,4,4,13,13,13,13,13,13,13,13,13,3,13,13,13,4,13,3,4,3,13,3,4,13,13,4,3,4,4,3,3,13,3,4,4,13,13,3,3,13,13,13,13,13,13,13,13,13,13,13,13,13,16,3,4,13,13,13,13,13,13,4,13,13,13,13,13,13,3,13,3,4,13,13,3,13,4,13,13,13,13,13,13,13,13,4,13,13,13,13,3,13,13,4,13,13,13,13,13,13,13,13,13,13,13,13,16,13,13,3,13,13,13,13,13,3,13,13,13,13,4,13,3,4,3,13,13,13,3,13,4,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,8,3,13,13,13,13,13,13,13,11,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,12,12,0,11,11,11,0,11,11,12,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,13,4,13,13,13,13,13,13,13,13,13,3,4,13,3,3,3,3,3,3,13,13,13,13,13,16,3,4,13,3,13,4,3,4,13,4,13,13,13,16,13,3,13,4,4,13,13,13,13,13,13,13,13,13,13,13,8,4,13,13,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,0,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,11,11,11,11,11,11,11,11,11,11,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,3,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12],"n":["Error","Parse","Parser","ParserBuilder","Result","Translate","UnicodeWordError","allow_invalid_utf8","ast","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","case_insensitive","clone","clone","clone","clone_into","clone_into","clone_into","default","description","dot_matches_new_line","eq","escape","escape_into","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hir","ignore_whitespace","into","into","into","into","is_meta_character","is_word_byte","is_word_character","multi_line","nest_limit","new","new","octal","parse","provide","provide","swap_greed","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_is_word_character","type_id","type_id","type_id","type_id","unicode","utf8","0","0","Alnum","Alpha","Alternation","Alternation","Ascii","Ascii","Assertion","Assertion","AssertionKind","Ast","AtLeast","Bell","BinaryOp","Blank","Bounded","Bracketed","Bracketed","CaptureIndex","CaptureLimitExceeded","CaptureName","CaptureName","CarriageReturn","CaseInsensitive","Class","Class","ClassAscii","ClassAsciiKind","ClassBracketed","ClassEscapeInvalid","ClassPerl","ClassPerlKind","ClassRangeInvalid","ClassRangeLiteral","ClassSet","ClassSetBinaryOp","ClassSetBinaryOpKind","ClassSetItem","ClassSetRange","ClassSetUnion","ClassUnclosed","ClassUnicode","ClassUnicodeKind","ClassUnicodeOpKind","Cntrl","Colon","Comment","Concat","Concat","DecimalEmpty","DecimalInvalid","Difference","Digit","Digit","Dot","DotMatchesNewLine","Empty","Empty","EndLine","EndText","Equal","Err","Error","ErrorKind","EscapeHexEmpty","EscapeHexInvalid","EscapeHexInvalidDigit","EscapeUnexpectedEof","EscapeUnrecognized","Exactly","Flag","Flag","FlagDanglingNegation","FlagDuplicate","FlagRepeatedNegation","FlagUnexpectedEof","FlagUnrecognized","Flags","Flags","FlagsItem","FlagsItemKind","FormFeed","Graph","Group","Group","GroupKind","GroupNameDuplicate","GroupNameEmpty","GroupNameInvalid","GroupNameUnexpectedEof","GroupUnclosed","GroupUnopened","HexBrace","HexFixed","HexLiteralKind","IgnoreWhitespace","Intersection","Item","LineFeed","Literal","Literal","Literal","LiteralKind","Lower","MultiLine","Named","NamedValue","Negation","NestLimitExceeded","NonCapturing","NotEqual","NotWordBoundary","Octal","OneLetter","OneOrMore","Output","Perl","Perl","Position","Print","Punct","Punctuation","Range","Range","Repetition","Repetition","RepetitionCountDecimalEmpty","RepetitionCountInvalid","RepetitionCountUnclosed","RepetitionKind","RepetitionMissing","RepetitionOp","RepetitionRange","SetFlags","Space","Space","Space","Span","Special","SpecialLiteralKind","StartLine","StartText","SwapGreed","SymmetricDifference","Tab","Unicode","Unicode","Unicode","UnicodeClassInvalid","UnicodeLong","UnicodeShort","Union","UnsupportedBackreference","UnsupportedLookAround","Upper","Verbatim","VerticalTab","Visitor","WithComments","Word","Word","WordBoundary","X","Xdigit","ZeroOrMore","ZeroOrOne","add_item","ast","ast","ast","asts","asts","auxiliary_span","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","byte","c","capture_index","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","column","comment","comments","description","digits","drop","drop","end","end","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","finish","flag_state","flags","flags","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_name","greedy","index","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_ast","into_ast","into_item","is_capturing","is_empty","is_empty","is_equal","is_negated","is_negation","is_one_line","is_valid","is_valid","items","items","kind","kind","kind","kind","kind","kind","kind","kind","kind","kind","kind","lhs","line","name","negated","negated","negated","negated","new","new","offset","op","parse","partial_cmp","partial_cmp","pattern","print","provide","push","rhs","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","splat","start","start","start","start","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","visit","visit_alternation_in","visit_alternation_in","visit_class_set_binary_op_in","visit_class_set_binary_op_in","visit_class_set_binary_op_post","visit_class_set_binary_op_post","visit_class_set_binary_op_pre","visit_class_set_binary_op_pre","visit_class_set_item_post","visit_class_set_item_post","visit_class_set_item_pre","visit_class_set_item_pre","visit_post","visit_post","visit_pre","visit_pre","with_end","with_start","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","name","op","value","0","original","original","original","0","0","0","0","0","0","0","0","0","0","0","1","Parser","ParserBuilder","borrow","borrow","borrow_mut","borrow_mut","build","clone","clone","clone_into","clone_into","default","fmt","fmt","from","from","ignore_whitespace","into","into","nest_limit","new","new","octal","parse","parse_with_comments","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","Printer","borrow","borrow_mut","fmt","from","into","new","print","try_from","try_into","type_id","Alternation","Anchor","Anchor","Ascii","AsciiNegate","AtLeast","Bounded","Byte","Bytes","CaptureIndex","CaptureName","CaseFoldError","Class","Class","ClassBytes","ClassBytesIter","ClassBytesRange","ClassUnicode","ClassUnicodeIter","ClassUnicodeRange","Concat","Empty","EmptyClassNotAllowed","EndLine","EndText","Err","Error","ErrorKind","Exactly","Group","Group","GroupKind","Hir","HirKind","InvalidUtf8","Literal","Literal","NonCapturing","OneOrMore","Output","Range","Repetition","Repetition","RepetitionKind","RepetitionRange","StartLine","StartText","Unicode","Unicode","Unicode","UnicodeCaseUnavailable","UnicodeNegate","UnicodeNotAllowed","UnicodePerlClassNotFound","UnicodePropertyNotFound","UnicodePropertyValueNotFound","Visitor","WordBoundary","WordBoundary","ZeroOrMore","ZeroOrOne","alternation","anchor","any","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","case_fold_simple","case_fold_simple","case_fold_simple","class","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","concat","default","default","description","difference","difference","dot","drop","empty","empty","empty","end","end","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","finish","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","greedy","group","has_subexprs","hir","hir","intersect","intersect","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_kind","is_all_ascii","is_all_ascii","is_all_assertions","is_alternation_literal","is_always_utf8","is_always_utf8","is_anchored_end","is_anchored_start","is_any_anchored_end","is_any_anchored_start","is_empty","is_line_anchored_end","is_line_anchored_start","is_literal","is_match_empty","is_match_empty","is_negated","is_unicode","iter","iter","kind","kind","kind","kind","literal","literal","negate","negate","negate","new","new","new","new","next","next","partial_cmp","partial_cmp","pattern","print","provide","provide","push","push","ranges","ranges","repetition","span","start","start","start","start","symmetric_difference","symmetric_difference","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","translate","try_case_fold_simple","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","union","union","visit","visit_alternation_in","visit_alternation_in","visit_post","visit_post","visit_pre","visit_pre","word_boundary","0","0","0","index","name","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","Literal","Literals","add","add_byte_class","add_char_class","all_complete","any_complete","as_ref","borrow","borrow","borrow_mut","borrow_mut","clear","clone","clone","clone_into","clone_into","cmp","contains_empty","cross_add","cross_product","cut","cut","deref","deref_mut","empty","empty","eq","eq","fmt","fmt","from","from","into","into","is_cut","is_empty","limit_class","limit_size","literals","longest_common_prefix","longest_common_suffix","min_len","new","partial_cmp","prefixes","reverse","set_limit_class","set_limit_size","suffixes","to_empty","to_owned","to_owned","trim_suffix","try_from","try_from","try_into","try_into","type_id","type_id","unambiguous_prefixes","unambiguous_suffixes","union","union_prefixes","union_suffixes","Printer","borrow","borrow_mut","fmt","from","into","new","print","try_from","try_into","type_id","Translator","TranslatorBuilder","allow_invalid_utf8","borrow","borrow","borrow_mut","borrow_mut","build","case_insensitive","clone","clone","clone_into","clone_into","default","dot_matches_new_line","fmt","fmt","from","from","into","into","multi_line","new","new","swap_greed","to_owned","to_owned","translate","try_from","try_from","try_into","try_into","type_id","type_id","unicode","Four","One","Three","Two","Utf8Range","Utf8Sequence","Utf8Sequences","as_slice","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","end","eq","eq","fmt","fmt","fmt","from","from","from","into","into","into","into_iter","into_iter","len","matches","matches","new","next","partial_cmp","partial_cmp","reverse","start","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","0","0"],"q":["regex_syntax","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::Error","","regex_syntax::ast","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::ast::Ast","","","","","","","","","","regex_syntax::ast::Class","","","regex_syntax::ast::ClassSet","","regex_syntax::ast::ClassSetItem","","","","","","","","regex_syntax::ast::ClassUnicodeKind","","","","","regex_syntax::ast::ErrorKind","","","","regex_syntax::ast::FlagsItemKind","regex_syntax::ast::GroupKind","","","regex_syntax::ast::LiteralKind","","","regex_syntax::ast::RepetitionKind","regex_syntax::ast::RepetitionRange","","","","regex_syntax::ast::parse","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::ast::print","","","","","","","","","","","regex_syntax::hir","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::hir::Class","","regex_syntax::hir::GroupKind","","","regex_syntax::hir::HirKind","","","","","","","","regex_syntax::hir::Literal","","regex_syntax::hir::RepetitionKind","regex_syntax::hir::RepetitionRange","","","","regex_syntax::hir::literal","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::hir::print","","","","","","","","","","","regex_syntax::hir::translate","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::utf8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","regex_syntax::utf8::Utf8Sequence","","",""],"d":["This error type encompasses any error that can be returned …","An error that occurred while translating concrete syntax …","A convenience parser for regular expressions.","A builder for a regular expression parser.","A type alias for dealing with errors returned by this …","An error that occurred while translating abstract syntax …","An error that occurs when the Unicode-aware \\\\w class is …","When enabled, the parser will permit the construction of a …","Defines an abstract syntax for regular expressions.","","","","","","","","","Build a parser from this configuration with the given …","Enable or disable the case insensitive flag by default.","","","","","","","","","Enable or disable the “dot matches any character” flag …","","Escapes all regular expression meta characters in text.","Escapes all meta characters in text and writes the result …","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Defines a high-level intermediate representation for …","Enable verbose mode in the regular expression.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if the given character has significance in a …","Returns true if and only if the given character is an …","Returns true if and only if the given character is a …","Enable or disable the multi-line matching flag by default.","Set the nesting limit for this parser.","Create a new parser builder with a default configuration.","Create a new parser with a default configuration.","Whether to support octal syntax or not.","Parse the regular expression into a high level intermediate","","","Enable or disable the “swap greed” flag by default.","","","","","","","","","","","","","","Returns true if and only if the given character is a …","","","","","Enable or disable the Unicode flag (u) by default.","Converts ranges of Unicode scalar values to equivalent …","","","[0-9A-Za-z]","[A-Za-z]","An alternation of regular expressions.","An alternation of regular expressions.","[\\\\x00-\\\\x7F]","An ASCII character class, e.g., [:alnum:] or [:punct:].","A single zero-width assertion.","A single zero-width assertion.","An assertion kind.","An abstract syntax tree for a single regular expression.","{m,}","Bell, spelled \\\\a (\\\\x07).","A single binary operation (i.e., &&, – or ~~).","[ \\\\t]","{m,n}","A bracketed character class set, which may contain zero or …","A bracketed character class set, which may contain zero or …","(a)","The capturing group limit was exceeded.","A capture name.","(?P<name>a)","Carriage return, spelled \\\\r (\\\\x0D).","i","A single character class expression.","A single character class. This includes all forms of …","An ASCII character class.","The available ASCII character classes.","A bracketed character class, e.g., [a-z0-9].","An invalid escape sequence was found in a character class …","A Perl character class.","The available Perl character classes.","An invalid character class range was found. An invalid …","An invalid range boundary was found in a character class. …","A character class set.","A Unicode character class set operation.","The type of a Unicode character class set operation.","A single component of a character class set.","A single character class range in a set.","A union of items inside a character class set.","An opening [ was found with no corresponding closing ].","A Unicode character class.","The available forms of Unicode character classes.","The type of op used in a Unicode character class.","[\\\\x00-\\\\x1F\\\\x7F]","A property set to a specific value using a colon, e.g., …","A comment from a regular expression with an associated …","A concatenation of regular expressions.","A concatenation of regular expressions.","Note that this error variant is no longer used. Namely, a …","An invalid decimal number was given where one was expected.","The difference of two sets, e.g., \\\\pN--[0-9].","Decimal numbers.","[0-9]","The “any character” class.","s","An empty regex that matches everything.","An empty item.","$","\\\\z","A property set to a specific value, e.g., \\\\p{scx=Katakana}.","An error that visiting an AST might return.","An error that occurred while parsing a regular expression …","The type of an error that occurred while building an AST.","A bracketed hex literal was empty.","A bracketed hex literal did not correspond to a Unicode …","An invalid hexadecimal digit was found.","EOF was found before an escape sequence was completed.","An unrecognized escape sequence.","{m}","A single flag.","A single flag in a group.","A dangling negation was used when setting flags, e.g., i-.","A flag was used twice, e.g., i-i.","The negation operator was used twice, e.g., -i-s.","Expected a flag but got EOF, e.g., (?.","Unrecognized flag, e.g., a.","A group of flags.","A set of flags, e.g., (?is).","A single item in a group of flags.","The kind of an item in a group of flags.","Form feed, spelled \\\\f (\\\\x0C).","[!-~]","A grouped regular expression.","A grouped regular expression.","The kind of a group.","A duplicate capture name was found.","A capture group name is empty, e.g., (?P<>abc).","An invalid character was seen for a capture group name. …","A closing > could not be found for a capture group name.","An unclosed group, e.g., (ab.","An unopened group, e.g., ab).","The literal is written as a hex code with a bracketed …","The literal is written as a hex code with a fixed number …","The type of a Unicode hex literal.","x","The intersection of two sets, e.g., \\\\pN&&[a-z].","An item, which can be a single literal, range, nested …","Line feed, spelled \\\\n (\\\\x0A).","A single literal expression.","A single character literal, which includes escape …","A single literal.","The kind of a single literal expression.","[a-z]","m","A binary property, general category or script. The string …","A property name and an associated value.","A negation operator applied to all subsequent flags in the …","The nest limit was exceeded. The limit stored here is the …","(?:a) and (?i:a)","A property that isn’t a particular value, e.g., …","\\\\B","The literal is written as an octal escape, e.g., \\\\141.","A one letter abbreviated class, e.g., \\\\pN.","+","The result of visiting an AST.","A perl character class, e.g., \\\\d or \\\\W.","A perl character class, e.g., \\\\d or \\\\W.","A single position in a regular expression.","[ -~]","[!-/:-@\\\\[-{-~]`","The literal is written as an escape because it is …","A range between two literals.","{m,n}","A repetition operation applied to a regular expression.","A repetition operator applied to an arbitrary regular …","An opening { was not followed by a valid decimal value. …","The range provided in a counted repetition operator is …","An opening { was found with no corresponding closing }.","The kind of a repetition operator.","A repetition operator was applied to a missing …","The repetition operator itself.","A range repetition operator.","A group of flags that is not applied to a particular …","Space, spelled \\\\ (\\\\x20). Note that this can only appear …","Whitespace.","[\\\\t\\\\n\\\\v\\\\f\\\\r ]","Span represents the position information of a single AST …","The literal is written as a specially recognized escape, …","The type of a special literal.","^","\\\\A","U","The symmetric difference of two sets. The symmetric …","Tab, spelled \\\\t (\\\\x09).","A Unicode character class, e.g., \\\\pL or \\\\p{Greek}.","A Unicode character class, e.g., \\\\pL or \\\\p{Greek}.","u","The Unicode class is not valid. This typically occurs when …","A \\\\U prefix. When used without brackets, this form is …","A \\\\u prefix. When used without brackets, this form is …","A union of items.","When octal support is disabled, this error is produced …","When syntax similar to PCRE’s look-around is used, this …","[A-Z]","The literal is written verbatim, e.g., a or .","Vertical tab, spelled \\\\v (\\\\x0B).","A trait for visiting an abstract syntax tree (AST) in …","An abstract syntax tree for a singular expression along …","Word characters.","[0-9A-Za-z_]","\\\\b","A \\\\x prefix. When used without brackets, this form is …","[0-9A-Fa-f]","*","?","Add the given item to this sequence of flags.","The actual ast.","The regular expression under repetition.","The regular expression in this group.","The alternate regular expressions.","The concatenation regular expressions.","Return an auxiliary span. This span exists only for some …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","If this literal was written as a \\\\x hex escape, then this …","The Unicode scalar value corresponding to this literal.","Returns the capture index of this group, if this is a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The approximate column number, starting at 1.","The comment text, starting with the first character …","All comments found in the original regular expression.","","The number of digits that must be used with this literal …","","","The end byte offset.","The end of this range.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","All implementors of Visitor must provide a finish method, …","Returns the state of the given flag in this set.","If this group is non-capturing, then this returns the …","The actual sequence of flags.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Return the corresponding ClassAsciiKind variant for the …","Whether this operation was applied greedily or not.","The capture index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Return this alternation as an AST.","Return this concatenation as an AST.","Return this union as a character class set item.","Returns true if and only if this group is capturing.","Returns true if and only if this span is empty. That is, …","Return true if and only if this Ast is empty.","Whether the op is an equality op or not.","Returns true if this class has been negated.","Returns true if and only if this item is a negation …","Returns true if and only if this span occurs on a single …","Returns true if and only if this character class range is …","Returns true if and only if this repetition range is valid.","The sequence of items that make up this union.","A sequence of flag items. Each item is either a flag or a …","Return the type of this error.","The kind of this literal.","The kind of Perl class.","The kind of ASCII class.","The kind of Unicode class.","The type of this set. A set is either a normal union of …","The type of this set operation.","The assertion kind, e.g., \\\\b or ^.","The type of operation.","The kind of this group.","The kind of this item.","The left hand side of the operation.","The line number, starting at 1.","The capture name.","Whether the class is negated or not. e.g., \\\\d is not …","Whether the class is negated or not. e.g., [[:alpha:]] is …","Whether this class is negated or not.","Whether this class is negated or not. e.g., [a] is not …","Create a new span with the given positions.","Create a new position with the given information.","The absolute offset of this position, starting at 0 from …","The actual operation.","This module provides a regular expression parser.","","","The original pattern string in which this error occurred.","This module provides a regular expression printer for Ast.","","Push a new item in this union.","The right hand side of the operation.","Return the span at which this error occurred.","Return the span of this abstract syntax tree.","Return the span of this character class.","Return the span of this character class set.","Return the span of this character class set item.","The span of this comment, including the beginning # and …","The span of this alternation.","The span of this concatenation.","The span of this literal.","The span of this class.","The span of this class.","The span of this class.","The span of this class.","The span of this range.","The span of the items in this operation. e.g., the a-z0-9 …","The span of this operation. e.g., the a-z--[h-p] in …","The span of this assertion.","The span of this operation.","The span of this operator. This includes things like +, *? …","The span of this group.","The span of this capture name.","The span of these flags, including the grouping …","The span of this group of flags.","The span of this item.","Create a new span using the given position as the start …","This method is called before beginning traversal of the …","This method is called before beginning traversal of the …","The start byte offset.","The start of this range.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Build a set from a union.","Executes an implementation of Visitor in constant stack …","This method is called between child nodes of an Alternation…","This method is called between child nodes of an Alternation…","This method is called between the left hand and right hand …","This method is called between the left hand and right hand …","This method is called on every ClassSetBinaryOp after …","This method is called on every ClassSetBinaryOp after …","This method is called on every ClassSetBinaryOp before …","This method is called on every ClassSetBinaryOp before …","This method is called on every ClassSetItem after …","This method is called on every ClassSetItem after …","This method is called on every ClassSetItem before …","This method is called on every ClassSetItem before …","This method is called on an Ast after descending all of …","This method is called on an Ast after descending all of …","This method is called on an Ast before descending into …","This method is called on an Ast before descending into …","Create a new span by replacing the ending the position …","Create a new span by replacing the starting the position …","","","","","","","","","","","","","","","","","","","","","","","","","","The property name (which may be empty).","The type of Unicode op used to associate name with value.","The property value (which may be empty).","","The position of the original flag. The error position …","The position of the original negation operator. The error …","The position of the initial occurrence of the capture …","","","","","","","","","","","","","A regular expression parser.","A builder for a regular expression parser.","","","","","Build a parser from this configuration with the given …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Enable verbose mode in the regular expression.","Calls U::from(self).","Calls U::from(self).","Set the nesting limit for this parser.","Create a new parser builder with a default configuration.","Create a new parser with a default configuration.","Whether to support octal syntax or not.","Parse the regular expression into an abstract syntax tree.","Parse the regular expression and return an abstract syntax …","","","","","","","","","A printer for a regular expression abstract syntax tree.","","","","Returns the argument unchanged.","Calls U::from(self).","Create a new printer.","Print the given Ast to the given writer. The writer must …","","","","An alternation of expressions. An alternation always has …","The high-level intermediate representation for an anchor …","An anchor assertion. An anchor assertion match always has …","Match an ASCII-only word boundary. That is, this matches a …","Match an ASCII-only negation of a word boundary.","Matches a sub-expression at least this many times.","Matches a sub-expression at least m times and at most n …","A single character represented by an arbitrary byte.","A set of characters represented by arbitrary bytes (one …","A normal unnamed capturing group.","A named capturing group.","An error that occurs when Unicode-aware simple case …","The high-level intermediate representation of a character …","A single character class that matches any of the …","A set of characters represented by arbitrary bytes (where …","An iterator over all ranges in a byte character class.","A single range of characters represented by arbitrary …","A set of characters represented by Unicode scalar values.","An iterator over all ranges in a Unicode character class.","A single range of characters represented by Unicode scalar …","A concatenation of expressions. A concatenation always has …","The empty regular expression, which matches everything, …","This occurs when the translator attempts to construct a …","Match the end of a line or the end of text. Specifically, …","Match the end of text. Specifically, this matches at the …","An error that visiting an HIR might return.","An error that can occur while translating an Ast to a Hir.","The type of an error that occurred while building an Hir.","Matches a sub-expression exactly this many times.","The high-level intermediate representation for a group.","A possibly capturing group, which contains a child …","The kind of group.","A high-level intermediate representation (HIR) for a …","The kind of an arbitrary Hir expression.","This error occurs when translating a pattern that could …","The high-level intermediate representation of a literal.","A single literal character that matches exactly this …","A non-capturing group.","Matches a sub-expression one or more times.","The result of visiting an HIR.","Matches a sub-expression within a bounded range of times.","The high-level intermediate representation of a repetition …","A repetition operation applied to a child expression.","The kind of a repetition operator.","The kind of a counted repetition operator.","Match the beginning of a line or the beginning of text. …","Match the beginning of text. Specifically, this matches at …","A single character represented by a Unicode scalar value.","A set of characters represented by Unicode scalar values.","Match a Unicode-aware word boundary. That is, this matches …","This occurs when the Unicode simple case mapping tables …","Match a Unicode-aware negation of a word boundary.","This error occurs when a Unicode feature is used when …","This occurs when a Unicode-aware Perl character class (\\\\w, …","This occurs when an unrecognized Unicode property name …","This occurs when an unrecognized Unicode property value …","A trait for visiting the high-level IR (HIR) in depth …","The high-level intermediate representation for a …","A word boundary assertion, which may or may not be Unicode …","Matches a sub-expression zero or more times.","Matches a sub-expression zero or one times.","Returns the alternation of the given expressions.","Creates an anchor assertion HIR expression.","Build an HIR expression for (?s)..","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Apply Unicode simple case folding to this character class, …","Expand this character class such that it contains all case …","Expand this character class such that it contains all case …","Creates a class HIR expression.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the concatenation of the given expressions.","","","","Subtract the given character class from this character …","Subtract the given byte class from this byte class, in …","Build an HIR expression for ..","","Returns an empty HIR expression.","Create a new class with no ranges.","Create a new class with no ranges.","Return the end of this range.","Return the end of this range.","","","","","","","","","","","","","","","","","","All implementors of Visitor must provide a finish method, …","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Whether this repetition operator is greedy or not. A …","Creates a group HIR expression.","Returns true if and only if this kind has any (including …","The expression inside the capturing group, which may be …","The expression being repeated.","Intersect this character class with the given character …","Intersect this byte class with the given byte class, in …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Consumes ownership of this HIR expression and returns its …","Returns true if and only if this character class will …","Returns true if and only if this character class will …","Returns true if and only if this entire HIR expression is …","Return true if and only if this HIR is either a simple …","Return true if and only if this HIR will always match …","Returns true if and only if this character class will only …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR contains any …","Return true if and only if this HIR contains any …","Return true if and only if this HIR is the empty regular …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is required to match …","Return true if and only if this HIR is a simple literal. …","Return true if and only if the empty string is part of the …","Returns true if and only if this repetition operator makes …","Returns true if and only if this word boundary assertion …","Returns true if and only if this literal corresponds to a …","Return an iterator over all ranges in this class.","Return an iterator over all ranges in this class.","Return the type of this error.","Returns a reference to the underlying HIR kind.","The kind of this group. If it is a capturing group, then …","The kind of this repetition operator.","Provides routines for extracting literal prefixes and …","Creates a literal HIR expression.","Negate this character class in place.","Negate this character class.","Negate this byte class.","Create a new class from a sequence of ranges.","Create a new Unicode scalar value range for a character …","Create a new class from a sequence of ranges.","Create a new byte range for a character class.","","","","","The original pattern string in which this error occurred.","This module provides a regular expression printer for Hir.","","","Add a new range to this set.","Add a new range to this set.","Return the underlying ranges as a slice.","Return the underlying ranges as a slice.","Creates a repetition HIR expression.","Return the span at which this error occurred.","This method is called before beginning traversal of the …","This method is called before beginning traversal of the …","Return the start of this range.","Return the start of this range.","Compute the symmetric difference of the given character …","Compute the symmetric difference of the given byte …","","","","","","","","","","","","","","","","","","","","","","Defines a translator that converts an Ast to an Hir.","Expand this character class such that it contains all case …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Union this character class with the given character class, …","Union this byte class with the given byte class, in place.","Executes an implementation of Visitor in constant stack …","This method is called between child nodes of an …","This method is called between child nodes of an …","This method is called on an Hir after descending all of …","This method is called on an Hir after descending all of …","This method is called on an Hir before descending into …","This method is called on an Hir before descending into …","Creates a word boundary assertion HIR expression.","","","","The capture index of the group.","The name of the group.","","","","","","","","","","","","","","","","A single member of a set of literals extracted from a …","A set of literal byte strings extracted from a regular …","Adds the given literal to this set.","Extends each literal in this set with the byte class given.","Extends each literal in this set with the character class …","Returns true if all members in this set are complete.","Returns true if any member in this set is complete.","","","","","","Clears this set of all members.","","","","","","Returns true if this set contains an empty literal.","Extends each literal in this set with the bytes given.","Extends this set with another set.","Cuts every member of this set. When a member is cut, it …","Cuts this literal.","","","Returns a new empty set of literals using default limits.","Returns a new complete empty literal.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Returns true if this literal was “cut.”","Returns true if this set is empty or if all of its members …","Get the character class size limit for this set.","Get the approximate size limit (in bytes) of this set.","Returns the set of literals as a slice. Its order is …","Returns the longest common prefix of all members in this …","Returns the longest common suffix of all members in this …","Returns the length of the smallest literal.","Returns a new complete literal with the bytes given.","","Returns a set of literal prefixes extracted from the given …","Reverses all members in place.","Limits the size of character(or byte) classes considered.","Set the approximate size limit (in bytes) of this set.","Returns a set of literal suffixes extracted from the given …","Returns a new empty set of literals using this set’s …","","","Returns a new set of literals with the given number of …","","","","","","","Returns a new set of prefixes of this set of literals that …","Returns a new set of suffixes of this set of literals that …","Unions this set with another set.","Unions the prefixes from the given expression to this set.","Unions the suffixes from the given expression to this set.","A printer for a regular expression’s high-level …","","","","Returns the argument unchanged.","Calls U::from(self).","Create a new printer.","Print the given Ast to the given writer. The writer must …","","","","A translator maps abstract syntax to a high level …","A builder for constructing an AST->HIR translator.","When enabled, translation will permit the construction of …","","","","","Build a translator using the current configuration.","Enable or disable the case insensitive flag (i) by default.","","","","","","Enable or disable the “dot matches any character” flag …","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Enable or disable the multi-line matching flag (m) by …","Create a new translator builder with a default c …","Create a new translator using the default configuration.","Enable or disable the “swap greed” flag (U) by default.","","","Translate the given abstract syntax tree (AST) into a high …","","","","","","","Enable or disable the Unicode flag (u) by default.","Four successive byte ranges.","One byte range.","Three successive byte ranges.","Two successive byte ranges.","A single inclusive range of UTF-8 bytes.","Utf8Sequence represents a sequence of byte ranges.","An iterator over ranges of matching UTF-8 byte sequences.","Returns the underlying sequence of byte ranges as a slice.","","","","","","","","","","","","","End of byte range (inclusive).","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Returns the number of byte ranges in this sequence.","Returns true if and only if a prefix of bytes matches this …","Returns true if and only if the given byte is in this …","Create a new iterator over UTF-8 byte ranges for the …","","","","Reverses the ranges in this sequence.","Start of byte range (inclusive).","","","","","","","","","","","","","","",""],"i":[0,4,0,0,0,4,0,1,0,4,1,3,9,4,1,3,9,1,1,4,1,3,4,1,3,1,4,1,4,0,0,4,4,1,3,9,9,4,4,4,1,3,9,0,1,4,1,3,9,0,0,0,1,1,1,3,1,3,4,9,1,4,1,3,4,9,4,1,3,9,4,1,3,9,0,4,1,3,9,1,0,97,98,41,41,0,31,41,47,0,31,0,0,57,35,46,41,57,37,47,58,27,0,58,35,62,0,31,0,0,0,27,0,0,27,27,0,0,0,0,0,0,27,0,0,0,41,44,0,0,31,27,27,51,39,41,31,62,31,47,53,53,44,64,0,0,27,27,27,27,27,57,0,61,27,27,27,27,27,0,31,0,0,35,41,0,31,0,27,27,27,27,27,27,34,34,0,62,51,46,35,0,31,47,0,41,62,43,43,61,27,58,44,53,34,43,56,64,37,47,0,41,41,34,47,56,0,31,27,27,27,0,27,0,0,0,35,39,41,0,34,0,53,53,62,51,35,37,47,62,27,36,36,47,27,27,41,34,35,0,0,39,41,53,36,41,56,56,20,29,54,26,32,33,10,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,25,25,26,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,24,28,28,30,29,10,36,31,46,24,48,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,64,20,26,60,10,10,27,27,24,28,29,30,31,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,41,54,59,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,32,33,49,26,24,31,44,42,61,24,48,57,49,20,10,25,38,40,42,45,50,52,55,26,21,50,28,59,38,40,42,45,24,28,28,54,0,24,28,10,0,10,49,50,10,31,37,46,47,30,32,33,25,38,40,42,45,48,49,50,52,54,55,26,59,60,20,21,24,64,64,24,48,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,31,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,10,27,24,28,29,30,31,32,33,25,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,26,58,59,60,20,21,61,62,46,0,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,24,24,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,124,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,139,0,0,65,66,65,66,65,65,66,65,66,65,65,66,65,66,65,65,66,65,65,66,65,66,66,65,66,65,66,65,66,65,66,0,67,67,67,67,67,67,67,67,67,67,75,0,75,79,79,84,84,76,71,81,81,0,0,75,0,0,0,0,0,0,75,75,74,70,70,88,0,0,84,0,75,0,0,0,74,0,75,81,83,88,83,0,75,0,0,70,70,76,71,79,74,79,74,74,74,74,0,0,75,83,83,15,15,15,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,71,72,73,15,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,77,78,15,77,78,11,72,73,15,15,15,72,73,77,78,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,88,11,11,74,74,15,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,82,15,75,80,82,72,73,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,85,86,15,72,73,15,15,15,71,15,15,15,15,75,15,15,15,15,82,79,76,72,73,11,15,80,82,0,15,71,72,73,72,77,73,78,85,86,77,78,11,0,11,87,72,73,72,73,15,11,88,88,77,78,72,73,11,74,15,75,76,71,72,77,73,78,70,79,80,81,82,83,84,11,74,15,87,0,72,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,11,74,15,75,76,71,72,85,77,73,86,78,70,79,80,81,82,83,84,87,72,73,0,88,88,88,88,88,88,15,140,141,142,143,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,157,0,0,89,89,89,89,89,90,89,90,89,90,89,89,90,89,90,90,89,89,89,89,90,90,90,89,90,89,90,89,90,89,90,89,90,90,89,89,89,89,89,89,89,90,90,89,89,89,89,89,89,89,90,89,89,90,89,90,89,90,89,89,89,89,89,0,91,91,91,91,91,91,91,91,91,91,0,0,92,92,93,92,93,92,92,92,93,92,93,92,92,92,93,92,93,92,93,92,92,93,92,92,93,93,92,93,92,93,92,93,92,94,94,94,94,0,0,0,94,94,95,96,94,95,96,94,95,94,95,94,95,95,94,95,94,95,96,94,95,96,94,95,96,94,96,94,94,95,96,96,94,95,94,95,94,95,94,95,96,94,95,96,94,95,96,158,159,160,161],"f":[0,0,0,0,0,0,0,[[1,2],1],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,3],[[1,2],1],[4,4],[1,1],[3,3],[[]],[[]],[[]],[[],1],[4,5],[[1,2],1],[[4,4],2],[5,6],[[5,6]],[[4,7],8],[[4,7],8],[[1,7],8],[[3,7],8],[[9,7],8],[[9,7],8],[10,4],[11,4],[[]],[[]],[[]],[[]],0,[[1,2],1],[[]],[[]],[[]],[[]],[12,2],[13,2],[12,2],[[1,2],1],[[1,14],1],[[],1],[[],3],[[1,2],1],[[3,5],[[16,[15]]]],[17],[17],[[1,2],1],[[]],[[]],[[]],[[],6],[[],6],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[12,[[18,[2,9]]]],[[],19],[[],19],[[],19],[[],19],[[1,2],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[20,21],[[23,[22]]]],0,0,0,0,0,[10,[[23,[24]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[25,[[23,[13]]]],0,[26,[[23,[14]]]],[10,10],[27,27],[24,24],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[25,25],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[26,26],[58,58],[59,59],[60,60],[20,20],[21,21],[61,61],[62,62],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[24,24],63],[[28,28],63],0,0,0,[10,5],[36,14],[31],[46],0,0,[[10,10],2],[[27,27],2],[[24,24],2],[[28,28],2],[[29,29],2],[[30,30],2],[[31,31],2],[[32,32],2],[[33,33],2],[[25,25],2],[[34,34],2],[[35,35],2],[[36,36],2],[[37,37],2],[[38,38],2],[[39,39],2],[[40,40],2],[[41,41],2],[[42,42],2],[[43,43],2],[[44,44],2],[[45,45],2],[[46,46],2],[[47,47],2],[[48,48],2],[[49,49],2],[[50,50],2],[[51,51],2],[[52,52],2],[[53,53],2],[[54,54],2],[[55,55],2],[[56,56],2],[[57,57],2],[[26,26],2],[[58,58],2],[[59,59],2],[[60,60],2],[[20,20],2],[[21,21],2],[[61,61],2],[[62,62],2],[[],18],[[20,62],[[23,[2]]]],[26,[[23,[20]]]],0,[[10,7],8],[[10,7],8],[[27,7],8],[[27,7],8],[[24,7],8],[[28,7],8],[[29,7],8],[[30,7],8],[[31,7],8],[[31,7],8],[[32,7],8],[[33,7],8],[[25,7],8],[[34,7],8],[[35,7],8],[[36,7],8],[[37,7],8],[[38,7],8],[[39,7],8],[[40,7],8],[[41,7],8],[[42,7],8],[[43,7],8],[[44,7],8],[[45,7],8],[[46,7],8],[[47,7],8],[[48,7],8],[[49,7],8],[[50,7],8],[[51,7],8],[[52,7],8],[[53,7],8],[[54,7],8],[[55,7],8],[[56,7],8],[[57,7],8],[[26,7],8],[[58,7],8],[[59,7],8],[[60,7],8],[[20,7],8],[[21,7],8],[[61,7],8],[[62,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,[[23,[41]]]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[32,31],[33,31],[49,47],[26,2],[24,2],[31,2],[44,2],[42,2],[61,2],[24,2],[48,2],[57,2],0,0,[10,27],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[28,28],24],[[22,22,22],28],0,0,0,[[24,24],[[23,[63]]]],[[28,28],[[23,[63]]]],[10,5],0,[17],[[49,47]],0,[10,24],[31,24],[37,24],[46,24],[47,24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[28,24],[[]],[[]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[49,46],[[31,64],18],[[],18],[[],18],[50,18],[50,18],[50,18],[50,18],[50,18],[50,18],[47,18],[47,18],[47,18],[47,18],[31,18],[31,18],[31,18],[31,18],[[24,28],24],[[24,28],24],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[65,66],[65,65],[66,66],[[]],[[]],[[],65],[[65,7],8],[[66,7],8],[[]],[[]],[[65,2],65],[[]],[[]],[[65,14],65],[[],65],[[],66],[[65,2],65],[[66,5],[[18,[31,10]]]],[[66,5],[[18,[29,10]]]],[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],0,[[]],[[]],[[67,7],8],[[]],[[]],[[],67],[[67,31,68],8],[[],18],[[],18],[[],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[69,[15]]],15],[70,15],[2,15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[71],[72],[73],[71,15],[11,11],[74,74],[15,15],[75,75],[76,76],[71,71],[72,72],[77,77],[73,73],[78,78],[70,70],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[77,77],63],[[78,78],63],[[[69,[15]]],15],[[],77],[[],78],[11,5],[[72,72]],[[73,73]],[2,15],[15],[[],15],[[],72],[[],73],[77,12],[78,13],[[11,11],2],[[74,74],2],[[15,15],2],[[75,75],2],[[76,76],2],[[71,71],2],[[72,72],2],[[77,77],2],[[73,73],2],[[78,78],2],[[70,70],2],[[79,79],2],[[80,80],2],[[81,81],2],[[82,82],2],[[83,83],2],[[84,84],2],[[],18],[[11,7],8],[[11,7],8],[[74,7],8],[[74,7],8],[[15,7],8],[[15,7],8],[[75,7],8],[[76,7],8],[[71,7],8],[[72,7],8],[[85,7],8],[[77,7],8],[[73,7],8],[[86,7],8],[[78,7],8],[[70,7],8],[[79,7],8],[[80,7],8],[[81,7],8],[[82,7],8],[[83,7],8],[[84,7],8],[[87,7],8],[[87,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[80,15],[75,2],0,0,[[72,72]],[[73,73]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[15,75],[72,2],[73,2],[15,2],[15,2],[15,2],[71,2],[15,2],[15,2],[15,2],[15,2],[75,2],[15,2],[15,2],[15,2],[15,2],[82,2],[79,2],[76,2],[72,85],[73,86],[11,74],[15,75],0,0,0,[76,15],[71],[72],[73],[[],72],[[12,12],77],[[],73],[[13,13],78],[85,[[23,[77]]]],[86,[[23,[78]]]],[[77,77],[[23,[63]]]],[[78,78],[[23,[63]]]],[11,5],0,[17],[17],[[72,77]],[[73,78]],[72],[73],[82,15],[11,24],[[]],[[]],[77,12],[78,13],[[72,72]],[[73,73]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],6],[[],6],[[],6],[[],6],0,[72,[[18,[87]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[72,72]],[[73,73]],[[15,88],18],[[],18],[[],18],[15,18],[15,18],[15,18],[15,18],[79,15],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[89,90],2],[[89,73],2],[[89,72],2],[89,2],[89,2],[90],[[]],[[]],[[]],[[]],[89],[89,89],[90,90],[[]],[[]],[[90,90],63],[89,2],[89,2],[[89,89],2],[89],[90],[90,69],[90,69],[[],89],[[],90],[[89,89],2],[[90,90],2],[[89,7],8],[[90,7],8],[[]],[[]],[[]],[[]],[90,2],[89,2],[89,22],[89,22],[89],[89],[89],[89,[[23,[22]]]],[[[69,[13]]],90],[[90,90],[[23,[63]]]],[15,89],[89],[[89,22],89],[[89,22],89],[15,89],[89,89],[[]],[[]],[[89,22],[[23,[89]]]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[89,89],[89,89],[[89,89],2],[[89,15],2],[[89,15],2],0,[[]],[[]],[[91,7],8],[[]],[[]],[[],91],[[91,15,68],8],[[],18],[[],18],[[],19],0,0,[[92,2],92],[[]],[[]],[[]],[[]],[92,93],[[92,2],92],[92,92],[93,93],[[]],[[]],[[],92],[[92,2],92],[[92,7],8],[[93,7],8],[[]],[[]],[[]],[[]],[[92,2],92],[[],92],[[],93],[[92,2],92],[[]],[[]],[[93,5,31],[[18,[15,11]]]],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[92,2],92],0,0,0,0,0,0,0,[94],[[]],[[]],[[]],[[]],[[]],[[]],[94,94],[95,95],[[]],[[]],[[94,94],63],[[95,95],63],0,[[94,94],2],[[95,95],2],[[94,7],8],[[95,7],8],[[96,7],8],[[]],[[]],[[]],[[]],[[]],[[]],[94],[[]],[94,22],[94,2],[[95,13],2],[[12,12],96],[96,23],[[94,94],[[23,[63]]]],[[95,95],[[23,[63]]]],[94],0,[[]],[[]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],19],[[],19],[[],19],0,0,0,0],"p":[[3,"ParserBuilder"],[15,"bool"],[3,"Parser"],[4,"Error"],[15,"str"],[3,"String"],[3,"Formatter"],[6,"Result"],[3,"UnicodeWordError"],[3,"Error"],[3,"Error"],[15,"char"],[15,"u8"],[15,"u32"],[3,"Hir"],[6,"Result"],[3,"Demand"],[4,"Result"],[3,"TypeId"],[3,"Flags"],[3,"FlagsItem"],[15,"usize"],[4,"Option"],[3,"Span"],[3,"Literal"],[3,"Group"],[4,"ErrorKind"],[3,"Position"],[3,"WithComments"],[3,"Comment"],[4,"Ast"],[3,"Alternation"],[3,"Concat"],[4,"LiteralKind"],[4,"SpecialLiteralKind"],[4,"HexLiteralKind"],[4,"Class"],[3,"ClassPerl"],[4,"ClassPerlKind"],[3,"ClassAscii"],[4,"ClassAsciiKind"],[3,"ClassUnicode"],[4,"ClassUnicodeKind"],[4,"ClassUnicodeOpKind"],[3,"ClassBracketed"],[4,"ClassSet"],[4,"ClassSetItem"],[3,"ClassSetRange"],[3,"ClassSetUnion"],[3,"ClassSetBinaryOp"],[4,"ClassSetBinaryOpKind"],[3,"Assertion"],[4,"AssertionKind"],[3,"Repetition"],[3,"RepetitionOp"],[4,"RepetitionKind"],[4,"RepetitionRange"],[4,"GroupKind"],[3,"CaptureName"],[3,"SetFlags"],[4,"FlagsItemKind"],[4,"Flag"],[4,"Ordering"],[8,"Visitor"],[3,"ParserBuilder"],[3,"Parser"],[3,"Printer"],[8,"Write"],[3,"Vec"],[4,"Anchor"],[4,"Class"],[3,"ClassUnicode"],[3,"ClassBytes"],[4,"ErrorKind"],[4,"HirKind"],[4,"Literal"],[3,"ClassUnicodeRange"],[3,"ClassBytesRange"],[4,"WordBoundary"],[3,"Group"],[4,"GroupKind"],[3,"Repetition"],[4,"RepetitionKind"],[4,"RepetitionRange"],[3,"ClassUnicodeIter"],[3,"ClassBytesIter"],[3,"CaseFoldError"],[8,"Visitor"],[3,"Literals"],[3,"Literal"],[3,"Printer"],[3,"TranslatorBuilder"],[3,"Translator"],[4,"Utf8Sequence"],[3,"Utf8Range"],[3,"Utf8Sequences"],[13,"Parse"],[13,"Translate"],[13,"Empty"],[13,"Flags"],[13,"Literal"],[13,"Dot"],[13,"Assertion"],[13,"Class"],[13,"Repetition"],[13,"Group"],[13,"Alternation"],[13,"Concat"],[13,"Unicode"],[13,"Perl"],[13,"Bracketed"],[13,"Item"],[13,"BinaryOp"],[13,"Empty"],[13,"Literal"],[13,"Range"],[13,"Ascii"],[13,"Unicode"],[13,"Perl"],[13,"Bracketed"],[13,"Union"],[13,"OneLetter"],[13,"Named"],[13,"NamedValue"],[13,"NestLimitExceeded"],[13,"FlagDuplicate"],[13,"FlagRepeatedNegation"],[13,"GroupNameDuplicate"],[13,"Flag"],[13,"CaptureIndex"],[13,"CaptureName"],[13,"NonCapturing"],[13,"HexFixed"],[13,"HexBrace"],[13,"Special"],[13,"Range"],[13,"Exactly"],[13,"AtLeast"],[13,"Bounded"],[13,"Unicode"],[13,"Bytes"],[13,"CaptureIndex"],[13,"CaptureName"],[13,"Literal"],[13,"Class"],[13,"Anchor"],[13,"WordBoundary"],[13,"Repetition"],[13,"Group"],[13,"Concat"],[13,"Alternation"],[13,"Unicode"],[13,"Byte"],[13,"Range"],[13,"Exactly"],[13,"AtLeast"],[13,"Bounded"],[13,"One"],[13,"Two"],[13,"Three"],[13,"Four"]]},\ -"rlp":{"doc":"Recursive Length Prefix serialization crate.","t":[13,13,8,4,17,8,13,17,13,3,4,3,13,13,13,13,13,13,13,13,13,3,13,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,10,5,11,11,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12],"n":["Custom","Data","Decodable","DecoderError","EMPTY_LIST_RLP","Encodable","List","NULL_RLP","Null","PayloadInfo","Prototype","Rlp","RlpDataLenWithZeroPrefix","RlpExpectedToBeData","RlpExpectedToBeList","RlpInconsistentLengthAndData","RlpIncorrectListLen","RlpInvalidIndirection","RlpInvalidLength","RlpIsTooBig","RlpIsTooShort","RlpIterator","RlpListLenWithZeroPrefix","RlpStream","append","append_empty_data","append_internal","append_iter","append_list","append_raw","append_raw_checked","as_list","as_raw","as_raw","as_val","at","at_with_offset","begin_list","begin_unbounded_list","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone_into","clone_into","data","decode","decode","decode_list","decoder","default","description","encode","encode_list","encoder","eq","estimate_size","finalize_unbounded_list","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","header_len","into","into","into","into","into","into","into_iter","into_iter","is_data","is_empty","is_empty","is_finished","is_int","is_list","is_null","item_count","iter","len","len","list_at","new","new","new_list","new_list_with_buffer","new_with_buffer","next","out","payload_info","prototype","provide","rlp_append","rlp_bytes","rlp_bytes","size","to_owned","to_owned","to_string","to_string","total","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","val_at","value_len","0","0","0"],"q":["rlp","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rlp::DecoderError","rlp::Prototype",""],"d":["Custom rlp decoding error.","Value","RLP decodable trait","Error concerning the RLP decoder.","The RLP encoded empty list.","Structure encodable to RLP","List","The RLP encoded empty data (used to mean “null value”).","Empty","Stores basic information about item","RLP prototype","Data-oriented view onto rlp-slice.","Data length number has a prefixed zero byte, invalid for …","Expect encoded data, RLP was something else.","Expect an encoded list, RLP was something else.","Declared length is inconsistent with data specified after.","Expected a different size list.","Non-canonical (longer than necessary) representation used …","Declared length is invalid and results in overflow","Data has additional bytes at the end of the valid RLP …","Data has too few bytes for valid RLP.","Iterator over rlp-slice list elements.","List length number has a prefixed zero byte, invalid for …","Appendable rlp encoder.","Appends value to the end of stream, chainable.","Apends null to the end of stream, chainable.","Appends value to the end of stream, but do not count it as …","Appends iterator to the end of stream, chainable.","Appends list of values to the end of stream, chainable.","Appends raw (pre-serialised) RLP data. Use with caution. …","Appends raw (pre-serialised) RLP data. Checks for size …","","Get raw encoded bytes","","","Returns an Rlp item in a list at the given index.","Returns an Rlp item in a list at the given index along …","Declare appending the list of given size, chainable.","Declare appending the list of unknown size, chainable.","","","","","","","","","","","","","Clear the output stream so far.","","","","","","Shortcut function to decode trusted rlp","Decode a value from RLP bytes","","","","","Shortcut function to encode structure into rlp.","","","","Calculate total RLP size for appended payload.","Finalize current unbounded list. Panics if no unbounded …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create a new object from the given bytes RLP. The bytes","Returns the argument unchanged.","Returns the argument unchanged.","Header length in bytes","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","Returns true if stream doesnt expect any more items.","","","","","","","Returns current RLP size in bytes for the data pushed into …","","Initializes instance of empty Stream.","","Initializes the Stream as a list.","Initializes the Stream as a list.","Initializes instance of empty Stream.","","Streams out encoded bytes.","","","","Append a value to the stream","Get rlp-encoded bytes for this instance","Get rlp-encoded bytes for this instance","","","","","","Total size of the RLP.","","","","","","","","","","","","","","","","","","","","Value length in bytes","","",""],"i":[6,12,0,0,0,0,12,0,12,0,0,0,6,6,6,6,6,6,6,6,6,0,6,0,1,1,1,1,1,1,1,4,1,4,4,4,4,1,1,15,1,6,12,13,4,15,1,6,12,13,4,1,6,4,6,4,4,0,20,0,4,1,6,0,0,1,6,1,1,6,6,12,13,4,4,15,1,6,12,13,13,4,13,15,1,6,12,13,4,15,4,4,1,4,1,4,4,4,4,4,15,1,4,1,4,1,1,1,15,1,4,4,6,21,21,21,4,6,4,6,4,13,15,1,6,12,13,4,15,1,6,12,13,4,15,1,6,12,13,4,4,13,22,23,24],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,1],[1,1],[1,1],[1,1],[1,1],[[1,2],1],[[1,2,2],3],[4,[[7,[5,6]]]],[1],[4],[4,[[7,[6]]]],[[4,2],[[7,[4,6]]]],[[4,2],[[7,[6]]]],[[1,2],1],[1,1],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[6,6],[4,4],[[]],[[]],[4,[[7,[6]]]],[[],[[7,[6]]]],[4,[[7,[6]]]],[[],5],0,[[],1],[6,8],[[],9],[[],9],0,[[6,6],3],[[1,2],2],[1],[[6,10],11],[[6,10],11],[[12,10],11],[[13,10],11],[[4,10],11],[[4,10],[[7,[14]]]],[[]],[[]],[[]],[[]],[[],[[7,[13,6]]]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4],[4,3],[1,3],[4,3],[1,3],[4,3],[4,3],[4,3],[4,[[7,[2,6]]]],[4,15],[15,2],[1,2],[[4,2],[[7,[5,6]]]],[[],1],[[],4],[2,1],[[9,2],1],[9,1],[15,[[16,[4]]]],[1,9],[4,[[7,[13,6]]]],[4,[[7,[12,6]]]],[17],[1],[[],9],[[],9],[4,2],[[]],[[]],[[],18],[[],18],[13,2],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],19],[[],19],[[],19],[[],19],[[],19],[[],19],[[4,2],[[7,[6]]]],0,0,0,0],"p":[[3,"RlpStream"],[15,"usize"],[15,"bool"],[3,"Rlp"],[3,"Vec"],[4,"DecoderError"],[4,"Result"],[15,"str"],[3,"BytesMut"],[3,"Formatter"],[6,"Result"],[4,"Prototype"],[3,"PayloadInfo"],[3,"Error"],[3,"RlpIterator"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"Decodable"],[8,"Encodable"],[13,"Custom"],[13,"Data"],[13,"List"]]},\ -"rustc_hex":{"doc":"Hex binary-to-text encoding","t":[8,4,3,13,13,8,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,12,12],"n":["FromHex","FromHexError","FromHexIter","InvalidHexCharacter","InvalidHexLength","ToHex","ToHexIter","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","description","fmt","fmt","from","from","from","from_hex","into","into","into","into_iter","into_iter","len","new","new","next","next","provide","size_hint","size_hint","to_hex","to_owned","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","1"],"q":["rustc_hex","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","rustc_hex::FromHexError",""],"d":["A from-hex conversion trait.","Errors that can occur when decoding a hex encoded string","An iterator decoding hex-encoded characters into bytes.","The input contained a character not part of the hex format","The input had an invalid length","A trait for converting a value to hexadecimal encoding","An iterator converting byte slice to a set of hex …","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Converts the value of self, interpreted as hexadecimal …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Create new hex-converting iterator.","Create new hex-decoding iterator.","","","","","","Converts the value of self to a hex value, constructed from","","","","","","","","","","","","",""],"i":[0,0,0,1,1,0,0,10,12,1,10,12,1,1,1,1,1,1,10,12,1,18,10,12,1,10,12,10,10,12,10,12,1,10,12,19,1,1,10,12,1,10,12,1,10,12,1,20,20],"f":[0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[[]],[1,2],[[1,3],4],[[1,3],4],[[]],[[]],[[]],[[],[[7,[[6,[5]],1]]]],[[]],[[]],[[]],[[]],[[]],[[[10,[[0,[8,9]]]]],11],[[],10],[2,12],[[[10,[9]]],[[14,[13]]]],[12,[[14,[[7,[5,1]]]]]],[15],[[[10,[9]]]],[12],[[],[[6,[13]]]],[[]],[[],16],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],17],[[],17],[[],17],0,0],"p":[[4,"FromHexError"],[15,"str"],[3,"Formatter"],[6,"Result"],[15,"u8"],[8,"FromIterator"],[4,"Result"],[8,"ExactSizeIterator"],[8,"Iterator"],[3,"ToHexIter"],[15,"usize"],[3,"FromHexIter"],[15,"char"],[4,"Option"],[3,"Demand"],[3,"String"],[3,"TypeId"],[8,"FromHex"],[8,"ToHex"],[13,"InvalidHexCharacter"]]},\ -"scan_fmt":{"doc":"This crate provides a simple sscanf()-like interface to …","t":[5,0,14,14,14,14,14,14,14,12,3,11,11,11,11,11,11,11,11,5,11,11,11,11],"n":["get_input_unwrap","parse","scan_fmt","scan_fmt_help","scan_fmt_some","scanln_fmt","scanln_fmt","scanln_fmt_some","scanln_fmt_some","0","ScanError","borrow","borrow_mut","eq","fmt","fmt","from","into","provide","scan","to_string","try_from","try_into","type_id"],"q":["scan_fmt","","","","","","","","","scan_fmt::parse","","","","","","","","","","","","","",""],"d":["","","","","","(a,+) = scanln_fmt!( format_string, types,+ )","(a,+) = scanln_fmt!( format_string, types,+ )","(a,+) = scanln_fmt_some!( format_string, types,+ )","(a,+) = scanln_fmt_some!( format_string, types,+ )","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,2,0,2,2,2,2,2,2,2,2,0,2,2,2,2],"f":[[[],1],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[2,2],3],[[2,4],5],[[2,4],5],[[]],[[]],[6],[[7,7],[[8,[1]]]],[[],1],[[],9],[[],9],[[],10]],"p":[[3,"String"],[3,"ScanError"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[15,"str"],[3,"IntoIter"],[4,"Result"],[3,"TypeId"]]},\ -"scopeguard":{"doc":"A scope guard will run a given closure when it goes out of …","t":[4,3,8,11,11,11,11,14,11,11,11,11,11,11,11,5,11,11,11,10,11,11,11,11,11,11,11,11],"n":["Always","ScopeGuard","Strategy","borrow","borrow","borrow_mut","borrow_mut","defer","deref","deref_mut","drop","fmt","fmt","from","from","guard","into","into","into_inner","should_run","should_run","try_from","try_from","try_into","try_into","type_id","type_id","with_strategy"],"q":["scopeguard","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Always run on scope exit.","ScopeGuard is a scope guard that may own a protected value.","Controls in which cases the associated code should be run","","","","","Macro to create a ScopeGuard (always run).","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Create a new ScopeGuard owning v and with deferred closure …","Calls U::from(self).","Calls U::from(self).","“Defuse” the guard and extract the value without …","Return true if the guard’s associated code should run …","","","","","","","","Create a ScopeGuard that owns v (accessible through deref) …"],"i":[0,0,0,1,4,1,4,0,1,1,1,1,4,1,4,0,1,4,1,8,4,1,4,1,4,1,4,1],"f":[0,0,0,[[]],[[]],[[]],[[]],0,[1],[1],[1],[[1,2],3],[[4,2],3],[[]],[[]],[[],[[1,[4]]]],[[]],[[]],[1],[[],5],[[],5],[[],6],[[],6],[[],6],[[],6],[[],7],[[],7],[[],1]],"p":[[3,"ScopeGuard"],[3,"Formatter"],[6,"Result"],[4,"Always"],[15,"bool"],[4,"Result"],[3,"TypeId"],[8,"Strategy"]]},\ -"serde":{"doc":"Serde","t":[8,8,16,16,16,8,16,16,16,16,16,16,16,8,11,11,11,0,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,14,11,11,0,14,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,13,13,13,8,8,8,8,16,13,8,8,16,16,16,16,16,8,13,3,8,13,8,13,13,13,13,13,8,13,2,13,13,13,4,13,13,13,16,16,16,8,8,11,11,11,11,11,11,11,11,10,11,10,10,11,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,10,11,10,11,11,11,10,11,10,11,11,10,11,11,11,11,11,11,11,10,11,11,10,11,11,0,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,16,16,16,16,16,16,16,16,3,16,16,16,16,16,16,16,16,8,8,16,8,16,8,16,8,16,8,16,8,16,8,16,8,2,11,11,11,11,11,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,11,11,11,10,10,10,10,10,10,11,11,11,11,11,10,10,10,10,10,11,10,10,10,10,10,10,10,10,10,10,10,10,11,10,10,10,10,10,10,10,10,11,11,11,11,11,11],"n":["Deserialize","Deserializer","Error","Error","Ok","Serialize","SerializeMap","SerializeSeq","SerializeStruct","SerializeStructVariant","SerializeTuple","SerializeTupleStruct","SerializeTupleVariant","Serializer","collect_map","collect_seq","collect_str","de","deserialize","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","forward_to_deserialize_any","is_human_readable","is_human_readable","ser","serde_if_integer128","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_f32","serialize_f64","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","Bool","Bytes","Char","Deserialize","DeserializeOwned","DeserializeSeed","Deserializer","Deserializer","Enum","EnumAccess","Error","Error","Error","Error","Error","Error","Expected","Float","IgnoredAny","IntoDeserializer","Map","MapAccess","NewtypeStruct","NewtypeVariant","Option","Other","Seq","SeqAccess","Signed","StdError","Str","StructVariant","TupleVariant","Unexpected","Unit","UnitVariant","Unsigned","Value","Value","Variant","VariantAccess","Visitor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","custom","default","deserialize","deserialize","deserialize","deserialize_any","deserialize_bool","deserialize_byte_buf","deserialize_bytes","deserialize_char","deserialize_enum","deserialize_f32","deserialize_f64","deserialize_i128","deserialize_i16","deserialize_i32","deserialize_i64","deserialize_i8","deserialize_identifier","deserialize_ignored_any","deserialize_map","deserialize_newtype_struct","deserialize_option","deserialize_seq","deserialize_str","deserialize_string","deserialize_struct","deserialize_tuple","deserialize_tuple_struct","deserialize_u128","deserialize_u16","deserialize_u32","deserialize_u64","deserialize_u8","deserialize_unit","deserialize_unit_struct","duplicate_field","eq","expecting","expecting","fmt","fmt","fmt","fmt","fmt","fmt","from","from","into","into","into_deserializer","invalid_length","invalid_type","invalid_value","is_human_readable","missing_field","newtype_variant","newtype_variant_seed","next_element","next_element_seed","next_entry","next_entry_seed","next_key","next_key_seed","next_value","next_value_seed","size_hint","size_hint","struct_variant","to_owned","to_owned","to_string","try_from","try_from","try_into","try_into","tuple_variant","type_id","type_id","unit_variant","unknown_field","unknown_variant","value","variant","variant_seed","visit_bool","visit_bool","visit_borrowed_bytes","visit_borrowed_str","visit_byte_buf","visit_bytes","visit_bytes","visit_char","visit_enum","visit_enum","visit_f32","visit_f64","visit_f64","visit_i128","visit_i128","visit_i16","visit_i32","visit_i64","visit_i64","visit_i8","visit_map","visit_map","visit_newtype_struct","visit_newtype_struct","visit_none","visit_none","visit_seq","visit_seq","visit_some","visit_some","visit_str","visit_str","visit_string","visit_u128","visit_u128","visit_u16","visit_u32","visit_u64","visit_u64","visit_u8","visit_unit","visit_unit","0","0","0","0","0","0","0","0","BoolDeserializer","BorrowedBytesDeserializer","BorrowedStrDeserializer","BytesDeserializer","CharDeserializer","CowStrDeserializer","EnumAccessDeserializer","Error","F32Deserializer","F64Deserializer","I128Deserializer","I16Deserializer","I32Deserializer","I64Deserializer","I8Deserializer","IsizeDeserializer","MapAccessDeserializer","MapDeserializer","SeqAccessDeserializer","SeqDeserializer","StrDeserializer","StringDeserializer","U128Deserializer","U16Deserializer","U32Deserializer","U64Deserializer","U8Deserializer","UnitDeserializer","UsizeDeserializer","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","custom","custom","description","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_any","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_bool","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_byte_buf","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_bytes","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_char","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_enum","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f32","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_f64","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i128","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i16","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i32","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i64","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_i8","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_identifier","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_ignored_any","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_map","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_newtype_struct","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_option","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_seq","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_str","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_string","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_struct","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_tuple_struct","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u128","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u16","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u32","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u64","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_u8","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","deserialize_unit_struct","end","end","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","new","next_element_seed","next_element_seed","next_entry_seed","next_key_seed","next_value_seed","provide","size_hint","size_hint","size_hint","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","variant_seed","variant_seed","variant_seed","variant_seed","variant_seed","variant_seed","Error","Error","Error","Error","Error","Error","Error","Error","Error","Impossible","Ok","Ok","Ok","Ok","Ok","Ok","Ok","Ok","Serialize","SerializeMap","SerializeMap","SerializeSeq","SerializeSeq","SerializeStruct","SerializeStruct","SerializeStructVariant","SerializeStructVariant","SerializeTuple","SerializeTuple","SerializeTupleStruct","SerializeTupleStruct","SerializeTupleVariant","SerializeTupleVariant","Serializer","StdError","borrow","borrow_mut","collect_map","collect_seq","collect_str","custom","end","end","end","end","end","end","end","end","end","end","end","end","end","end","from","into","is_human_readable","serialize","serialize_bool","serialize_bytes","serialize_char","serialize_element","serialize_element","serialize_element","serialize_element","serialize_entry","serialize_f32","serialize_f64","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_field","serialize_i128","serialize_i16","serialize_i32","serialize_i64","serialize_i8","serialize_key","serialize_key","serialize_map","serialize_newtype_struct","serialize_newtype_variant","serialize_none","serialize_seq","serialize_some","serialize_str","serialize_struct","serialize_struct_variant","serialize_tuple","serialize_tuple_struct","serialize_tuple_variant","serialize_u128","serialize_u16","serialize_u32","serialize_u64","serialize_u8","serialize_unit","serialize_unit_struct","serialize_unit_variant","serialize_value","serialize_value","skip_field","skip_field","try_from","try_into","type_id"],"q":["serde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::de","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::de::Unexpected","","","","","","","","serde::de::value","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","serde::ser","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A data structure that can be deserialized from any data …","A data format that can deserialize any data structure …","The error type that can be returned if some error occurs …","The error type when some error occurs during serialization.","The output type produced by this Serializer during …","A data structure that can be serialized into any data …","Type returned from serialize_map for serializing the …","Type returned from serialize_seq for serializing the …","Type returned from serialize_struct for serializing the …","Type returned from serialize_struct_variant for …","Type returned from serialize_tuple for serializing the …","Type returned from serialize_tuple_struct for serializing …","Type returned from serialize_tuple_variant for serializing …","A data format that can serialize any data structure …","Collect an iterator as a map.","Collect an iterator as a sequence.","Serialize a string produced by an implementation of Display…","Generic data structure deserialization framework.","Deserialize this value from the given Serde deserializer.","Require the Deserializer to figure out how to drive the …","Hint that the Deserialize type is expecting a bool value.","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a char value.","Hint that the Deserialize type is expecting an enum value …","Hint that the Deserialize type is expecting a f32 value.","Hint that the Deserialize type is expecting a f64 value.","Hint that the Deserialize type is expecting an i128 value.","Hint that the Deserialize type is expecting an i16 value.","Hint that the Deserialize type is expecting an i32 value.","Hint that the Deserialize type is expecting an i64 value.","Hint that the Deserialize type is expecting an i8 value.","Hint that the Deserialize type is expecting the name of a …","Hint that the Deserialize type needs to deserialize a …","Hint that the Deserialize type is expecting a map of …","Hint that the Deserialize type is expecting a newtype …","Hint that the Deserialize type is expecting an optional …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a struct with …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a tuple struct …","Hint that the Deserialize type is expecting an u128 value.","Hint that the Deserialize type is expecting a u16 value.","Hint that the Deserialize type is expecting a u32 value.","Hint that the Deserialize type is expecting a u64 value.","Hint that the Deserialize type is expecting a u8 value.","Hint that the Deserialize type is expecting a unit value.","Hint that the Deserialize type is expecting a unit struct …","Helper macro when implementing the Deserializer part of a …","Determine whether Deserialize implementations should …","Determine whether Serialize implementations should …","Generic data structure serialization framework.","Conditional compilation depending on whether Serde is …","Serialize this value into the given Serde serializer.","Serialize a bool value.","Serialize a chunk of raw byte data.","Serialize a character.","Serialize an f32 value.","Serialize an f64 value.","Serialize an i128 value.","Serialize an i16 value.","Serialize an i32 value.","Serialize an i64 value.","Serialize an i8 value.","Begin to serialize a map. This call must be followed by …","Serialize a newtype struct like struct Millimeters(u8).","Serialize a newtype variant like E::N in enum E { N(u8) }.","Serialize a None value.","Begin to serialize a variably sized sequence. This call …","Serialize a Some(T) value.","Serialize a &str.","Begin to serialize a struct like …","Begin to serialize a struct variant like E::S in …","Begin to serialize a statically sized sequence whose …","Begin to serialize a tuple struct like …","Begin to serialize a tuple variant like E::T in …","Serialize a u128 value.","Serialize a u16 value.","Serialize a u32 value.","Serialize a u64 value.","Serialize a u8 value.","Serialize a () value.","Serialize a unit struct like struct Unit or PhantomData<T>.","Serialize a unit variant like E::A in enum E { A, B }.","The input contained a boolean value that was not expected.","The input contained a &[u8] or Vec<u8> that was not …","The input contained a char that was not expected.","A data structure that can be deserialized from any data …","A data structure that can be deserialized without …","DeserializeSeed is the stateful form of the Deserialize …","A data format that can deserialize any data structure …","The type of the deserializer being converted into.","The input contained an enum that was not expected.","Provides a Visitor access to the data of an enum in the …","The Error trait allows Deserialize implementations to …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","The error type that can be returned if some error occurs …","Expected represents an explanation of what data a Visitor …","The input contained a floating point f32 or f64 that was …","An efficient way of discarding data from a deserializer.","Converts an existing value into a Deserializer from which …","The input contained a map that was not expected.","Provides a Visitor access to each entry of a map in the …","The input contained a newtype struct that was not expected.","The input contained a newtype variant that was not …","The input contained an Option<T> that was not expected.","A message stating what uncategorized thing the input …","The input contained a sequence that was not expected.","Provides a Visitor access to each element of a sequence in …","The input contained a signed integer i8, i16, i32 or i64 …","","The input contained a &str or String that was not expected.","The input contained a struct variant that was not expected.","The input contained a tuple variant that was not expected.","Unexpected represents an unexpected invocation of any one …","The input contained a unit () that was not expected.","The input contained a unit variant that was not expected.","The input contained an unsigned integer u8, u16, u32 or u64…","The type produced by using this seed.","The value produced by this visitor.","The Visitor that will be used to deserialize the content …","VariantAccess is a visitor that is created by the …","This trait represents a visitor that walks through a …","","","","","","","","","Raised when there is general error when deserializing a …","","Deserialize this value from the given Serde deserializer.","Equivalent to the more common Deserialize::deserialize …","","Require the Deserializer to figure out how to drive the …","Hint that the Deserialize type is expecting a bool value.","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a byte array …","Hint that the Deserialize type is expecting a char value.","Hint that the Deserialize type is expecting an enum value …","Hint that the Deserialize type is expecting a f32 value.","Hint that the Deserialize type is expecting a f64 value.","Hint that the Deserialize type is expecting an i128 value.","Hint that the Deserialize type is expecting an i16 value.","Hint that the Deserialize type is expecting an i32 value.","Hint that the Deserialize type is expecting an i64 value.","Hint that the Deserialize type is expecting an i8 value.","Hint that the Deserialize type is expecting the name of a …","Hint that the Deserialize type needs to deserialize a …","Hint that the Deserialize type is expecting a map of …","Hint that the Deserialize type is expecting a newtype …","Hint that the Deserialize type is expecting an optional …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a string value …","Hint that the Deserialize type is expecting a struct with …","Hint that the Deserialize type is expecting a sequence of …","Hint that the Deserialize type is expecting a tuple struct …","Hint that the Deserialize type is expecting an u128 value.","Hint that the Deserialize type is expecting a u16 value.","Hint that the Deserialize type is expecting a u32 value.","Hint that the Deserialize type is expecting a u64 value.","Hint that the Deserialize type is expecting a u8 value.","Hint that the Deserialize type is expecting a unit value.","Hint that the Deserialize type is expecting a unit struct …","Raised when a Deserialize struct type received more than …","","Format a message stating what data this Visitor expects to …","","Format an explanation of what data was being expected. …","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Convert this value into a deserializer.","Raised when deserializing a sequence or map and the input …","Raised when a Deserialize receives a type different from …","Raised when a Deserialize receives a value of the right …","Determine whether Deserialize implementations should …","Raised when a Deserialize struct type expected to receive …","Called when deserializing a variant with a single value.","Called when deserializing a variant with a single value.","This returns Ok(Some(value)) for the next value in the …","This returns Ok(Some(value)) for the next value in the …","This returns Ok(Some((key, value))) for the next …","This returns Ok(Some((key, value))) for the next …","This returns Ok(Some(key)) for the next key in the map, or …","This returns Ok(Some(key)) for the next key in the map, or …","This returns a Ok(value) for the next value in the map.","This returns a Ok(value) for the next value in the map.","Returns the number of elements remaining in the sequence, …","Returns the number of entries remaining in the map, if …","Called when deserializing a struct-like variant.","","","","","","","","Called when deserializing a tuple-like variant.","","","Called when deserializing a variant with no values.","Raised when a Deserialize struct type received a field …","Raised when a Deserialize enum type received a variant …","Building blocks for deserializing basic values using the …","variant is called to identify which variant to deserialize.","variant is called to identify which variant to deserialize.","The input contains a boolean.","","The input contains a byte array that lives at least as …","The input contains a string that lives at least as long as …","The input contains a byte array and ownership of the byte …","The input contains a byte array. The lifetime of the byte …","","The input contains a char.","The input contains an enum.","","The input contains an f32.","The input contains an f64.","","The input contains a i128.","","The input contains an i16.","The input contains an i32.","The input contains an i64.","","The input contains an i8.","The input contains a key-value map.","","The input contains a newtype struct.","","The input contains an optional that is absent.","","The input contains a sequence of elements.","","The input contains an optional that is present.","","The input contains a string. The lifetime of the string is …","","The input contains a string and ownership of the string is …","The input contains a u128.","","The input contains a u16.","The input contains a u32.","The input contains a u64.","","The input contains a u8.","The input contains a unit ().","","","","","","","","","","A deserializer holding a bool.","A deserializer holding a &[u8] with a lifetime tied to …","A deserializer holding a &str with a lifetime tied to …","A deserializer holding a &[u8]. Always calls …","A deserializer holding a char.","A deserializer holding a Cow<str>.","A deserializer holding an EnumAccess.","A minimal representation of all possible errors that can …","A deserializer holding an f32.","A deserializer holding an f64.","A deserializer holding an i128.","A deserializer holding an i16.","A deserializer holding an i32.","A deserializer holding an i64.","A deserializer holding an i8.","A deserializer holding an isize.","A deserializer holding a MapAccess.","A deserializer that iterates over a map.","A deserializer holding a SeqAccess.","A deserializer that iterates over a sequence.","A deserializer holding a &str.","A deserializer holding a String.","A deserializer holding a u128.","A deserializer holding a u16.","A deserializer holding a u32.","A deserializer holding a u64.","A deserializer holding a u8.","A deserializer holding a ().","A deserializer holding a usize.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check for remaining elements after passing a …","Check for remaining elements after passing a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Create a new borrowed deserializer from the given string.","","","Create a new deserializer from the given bytes.","Create a new borrowed deserializer from the given borrowed …","Construct a new MapDeserializer<I, E>.","","","","","","","","","","","","","","","","Construct a new SeqDeserializer<I, E>.","Construct a new SeqAccessDeserializer<A>.","Construct a new MapAccessDeserializer<A>.","Construct a new EnumAccessDeserializer<A>.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Trait used by Serialize implementations to generically …","The error type when some error occurs during serialization.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Must match the Error type of our Serializer.","Helper type for implementing a Serializer that does not …","The output type produced by this Serializer during …","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","Must match the Ok type of our Serializer.","A data structure that can be serialized into any data …","Returned from Serializer::serialize_map.","Type returned from serialize_map for serializing the …","Returned from Serializer::serialize_seq.","Type returned from serialize_seq for serializing the …","Returned from Serializer::serialize_struct.","Type returned from serialize_struct for serializing the …","Returned from Serializer::serialize_struct_variant.","Type returned from serialize_struct_variant for …","Returned from Serializer::serialize_tuple.","Type returned from serialize_tuple for serializing the …","Returned from Serializer::serialize_tuple_struct.","Type returned from serialize_tuple_struct for serializing …","Returned from Serializer::serialize_tuple_variant.","Type returned from serialize_tuple_variant for serializing …","A data format that can serialize any data structure …","","","","Collect an iterator as a map.","Collect an iterator as a sequence.","Serialize a string produced by an implementation of Display…","Used when a Serialize implementation encounters any error …","Finish serializing a sequence.","Finish serializing a tuple.","Finish serializing a tuple struct.","Finish serializing a tuple variant.","Finish serializing a map.","Finish serializing a struct.","Finish serializing a struct variant.","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Determine whether Serialize implementations should …","Serialize this value into the given Serde serializer.","Serialize a bool value.","Serialize a chunk of raw byte data.","Serialize a character.","Serialize a sequence element.","Serialize a tuple element.","","","Serialize a map entry consisting of a key and a value.","Serialize an f32 value.","Serialize an f64 value.","Serialize a tuple struct field.","Serialize a tuple variant field.","Serialize a struct field.","Serialize a struct variant field.","","","","","Serialize an i128 value.","Serialize an i16 value.","Serialize an i32 value.","Serialize an i64 value.","Serialize an i8 value.","Serialize a map key.","","Begin to serialize a map. This call must be followed by …","Serialize a newtype struct like struct Millimeters(u8).","Serialize a newtype variant like E::N in enum E { N(u8) }.","Serialize a None value.","Begin to serialize a variably sized sequence. This call …","Serialize a Some(T) value.","Serialize a &str.","Begin to serialize a struct like …","Begin to serialize a struct variant like E::S in …","Begin to serialize a statically sized sequence whose …","Begin to serialize a tuple struct like …","Begin to serialize a tuple variant like E::T in …","Serialize a u128 value.","Serialize a u16 value.","Serialize a u32 value.","Serialize a u64 value.","Serialize a u8 value.","Serialize a () value.","Serialize a unit struct like struct Unit or PhantomData<T>.","Serialize a unit variant like E::A in enum E { A, B }.","Serialize a map value.","","Indicate that a struct field has been skipped.","Indicate that a struct variant field has been skipped.","","",""],"i":[0,0,63,64,64,0,64,64,64,64,64,64,64,0,64,64,64,0,65,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,0,63,64,0,0,66,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,20,20,20,0,0,0,0,67,20,0,0,63,68,69,70,71,0,20,0,0,20,0,20,20,20,20,20,0,20,0,20,20,20,0,20,20,20,72,73,70,0,0,19,20,19,20,19,20,19,20,74,19,65,72,19,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,74,20,73,19,23,23,19,19,20,20,19,20,19,20,67,74,74,74,63,74,71,71,68,68,69,69,69,69,69,69,68,69,71,19,20,20,19,20,19,20,71,19,20,71,74,74,0,70,70,73,19,73,73,73,73,19,73,73,19,73,73,19,73,19,73,73,73,19,73,73,19,73,19,73,19,73,19,73,19,73,19,73,73,19,73,73,73,19,73,73,19,75,76,77,78,79,80,81,82,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,37,37,37,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,36,54,37,28,29,30,31,32,33,34,35,36,37,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,36,54,36,36,36,37,36,36,54,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,37,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,29,30,31,32,33,56,0,64,83,84,85,86,87,88,89,0,64,83,84,85,86,87,88,89,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,62,62,64,64,64,90,83,84,85,86,87,88,89,62,62,62,62,62,62,62,62,62,64,66,64,64,64,83,84,62,62,87,64,64,85,86,88,89,62,62,62,62,64,64,64,64,64,87,62,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,87,62,88,89,62,62,62],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],1],[[],1],[[],1],0,[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[2,1],[3,1],[[2,3],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],0,[[],4],[[],4],0,0,[[],1],[4,1],[[],1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1],[12,1],[[[13,[3]]],1],[2,1],[[2,14,2],1],[[],1],[[[13,[3]]],1],[[],1],[2,1],[[2,3],1],[[2,14,2,3],1],[3,1],[[2,3],1],[[2,14,2,3],1],[15,1],[16,1],[14,1],[17,1],[18,1],[[],1],[2,1],[[2,14,2],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[19,19],[20,20],[[]],[[]],[[]],[[],19],[[],1],[[],1],[[],[[1,[19]]]],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[[],1],[[],1],[[],1],[[],1],[2,1],[3,1],[[2,3],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[2,1],[2],[[20,20],4],[21,22],[[19,21],22],[21,22],[[23,21],22],[[19,21],22],[21,[[1,[24]]]],[[20,21],22],[[20,21],22],[[]],[[]],[[]],[[]],[[]],[[3,23]],[[20,23]],[[20,23]],[[],4],[2],[[],1],[[],1],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],[[1,[13]]]],[[],1],[[],1],[[],[[13,[3]]]],[[],[[13,[3]]]],[[],1],[[]],[[]],[[],25],[[],1],[[],1],[[],1],[[],1],[3,1],[[],26],[[],26],[[],1],[2],[2],0,[[],1],[[],1],[4,1],[[19,4],1],[[],1],[2,1],[[[27,[18]]],1],[[],1],[19,1],[5,1],[[],1],[19,1],[6,1],[7,1],[[19,7],1],[8,1],[[19,8],1],[9,1],[10,1],[11,1],[[19,11],1],[12,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[[],1],[19,1],[2,1],[[19,2],1],[25,1],[15,1],[[19,15],1],[16,1],[14,1],[17,1],[[19,17],1],[18,1],[[],1],[19,1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[[[54,[53,53]]],[[54,[53,53]]]],[[[55,[53]]],[[55,[53]]]],[[[56,[53]]],[[56,[53]]]],[[[57,[53]]],[[57,[53]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[],37],[37,2],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[[28,3],1],[[29,3],1],[[30,3],1],[[31,3],1],[[32,3],1],[[33,3],1],[[34,3],1],[[35,3],1],[[36,3],1],[[38,3],1],[[39,3],1],[[40,3],1],[[41,3],1],[[42,3],1],[[43,3],1],[[44,3],1],[[45,3],1],[[46,3],1],[[47,3],1],[[48,3],1],[[49,3],1],[[50,3],1],[[51,3],1],[[52,3],1],[[54,3],1],[[55,3],1],[[56,3],1],[[57,3],1],[[28,2,3],1],[[29,2,3],1],[[30,2,3],1],[[31,2,3],1],[[32,2,3],1],[[33,2,3],1],[[34,2,3],1],[[35,2,3],1],[[36,2,3],1],[[38,2,3],1],[[39,2,3],1],[[40,2,3],1],[[41,2,3],1],[[42,2,3],1],[[43,2,3],1],[[44,2,3],1],[[45,2,3],1],[[46,2,3],1],[[47,2,3],1],[[48,2,3],1],[[49,2,3],1],[[50,2,3],1],[[51,2,3],1],[[52,2,3],1],[[54,2,3],1],[[55,2,3],1],[[56,2,3],1],[[57,2,3],1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[28,1],[29,1],[30,1],[31,1],[32,1],[33,1],[34,1],[35,1],[36,1],[38,1],[39,1],[40,1],[41,1],[42,1],[43,1],[44,1],[45,1],[46,1],[47,1],[48,1],[49,1],[50,1],[51,1],[52,1],[54,1],[55,1],[56,1],[57,1],[[28,2],1],[[29,2],1],[[30,2],1],[[31,2],1],[[32,2],1],[[33,2],1],[[34,2],1],[[35,2],1],[[36,2],1],[[38,2],1],[[39,2],1],[[40,2],1],[[41,2],1],[[42,2],1],[[43,2],1],[[44,2],1],[[45,2],1],[[46,2],1],[[47,2],1],[[48,2],1],[[49,2],1],[[50,2],1],[[51,2],1],[[52,2],1],[[54,2],1],[[55,2],1],[[56,2],1],[[57,2],1],[36,1],[54,1],[[37,37],4],[[28,21],22],[[29,21],22],[[30,21],22],[[31,21],22],[[32,21],22],[[33,21],22],[[34,21],22],[[35,21],22],[[36,21],22],[[37,21],22],[[37,21],22],[[38,21],22],[[39,21],22],[[40,21],22],[[41,21],22],[[42,21],22],[[43,21],22],[[44,21],22],[[45,21],22],[[46,21],22],[[47,21],22],[[48,21],22],[[49,21],22],[[50,21],22],[[51,21],22],[[52,21],22],[[54,21],22],[[[55,[58]],21],22],[[[56,[58]],21],22],[[[57,[58]],21],22],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],28],[14,29],[2,30],[2,31],[25,32],[[[59,[2]]],33],[[],34],[[],35],[[],36],[4,38],[12,39],[9,40],[10,41],[11,42],[60,43],[18,44],[16,45],[17,46],[3,47],[6,48],[7,49],[5,50],[8,51],[15,52],[[],54],[[],55],[[],56],[[],57],[36,[[1,[13]]]],[54,[[1,[13]]]],[36,[[1,[13]]]],[36,[[1,[13]]]],[36,1],[61],[36,[[13,[3]]]],[36,[[13,[3]]]],[54,[[13,[3]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],25],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[29,1],[30,1],[31,1],[32,1],[33,1],[56,1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[],1],[[],1],[[],1],[[]],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[[],1],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[[]],[[]],[[],4],[[],1],[4,1],[[],1],[5,1],[[],1],[[],1],[62,1],[62,1],[[],1],[6,1],[7,1],[[],1],[[],1],[2,1],[2,1],[62,1],[62,1],[[62,2],1],[[62,2],1],[8,1],[9,1],[10,1],[11,1],[12,1],[[],1],[62,1],[[[13,[3]]],1],[2,1],[[2,14,2],1],[[],1],[[[13,[3]]],1],[[],1],[2,1],[[2,3],1],[[2,14,2,3],1],[3,1],[[2,3],1],[[2,14,2,3],1],[15,1],[16,1],[14,1],[17,1],[18,1],[[],1],[2,1],[[2,14,2],1],[[],1],[62,1],[2,1],[2,1],[[],1],[[],1],[[],26]],"p":[[4,"Result"],[15,"str"],[15,"usize"],[15,"bool"],[15,"char"],[15,"f32"],[15,"f64"],[15,"i128"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[4,"Option"],[15,"u32"],[15,"u128"],[15,"u16"],[15,"u64"],[15,"u8"],[3,"IgnoredAny"],[4,"Unexpected"],[3,"Formatter"],[6,"Result"],[8,"Expected"],[3,"Error"],[3,"String"],[3,"TypeId"],[3,"Vec"],[3,"UnitDeserializer"],[3,"U32Deserializer"],[3,"StrDeserializer"],[3,"BorrowedStrDeserializer"],[3,"StringDeserializer"],[3,"CowStrDeserializer"],[3,"BytesDeserializer"],[3,"BorrowedBytesDeserializer"],[3,"MapDeserializer"],[3,"Error"],[3,"BoolDeserializer"],[3,"I8Deserializer"],[3,"I16Deserializer"],[3,"I32Deserializer"],[3,"I64Deserializer"],[3,"IsizeDeserializer"],[3,"U8Deserializer"],[3,"U16Deserializer"],[3,"U64Deserializer"],[3,"UsizeDeserializer"],[3,"F32Deserializer"],[3,"F64Deserializer"],[3,"CharDeserializer"],[3,"I128Deserializer"],[3,"U128Deserializer"],[8,"Clone"],[3,"SeqDeserializer"],[3,"SeqAccessDeserializer"],[3,"MapAccessDeserializer"],[3,"EnumAccessDeserializer"],[8,"Debug"],[4,"Cow"],[15,"isize"],[3,"Demand"],[3,"Impossible"],[8,"Deserializer"],[8,"Serializer"],[8,"Deserialize"],[8,"Serialize"],[8,"IntoDeserializer"],[8,"SeqAccess"],[8,"MapAccess"],[8,"EnumAccess"],[8,"VariantAccess"],[8,"DeserializeSeed"],[8,"Visitor"],[8,"Error"],[13,"Bool"],[13,"Unsigned"],[13,"Signed"],[13,"Float"],[13,"Char"],[13,"Str"],[13,"Bytes"],[13,"Other"],[8,"SerializeSeq"],[8,"SerializeTuple"],[8,"SerializeTupleStruct"],[8,"SerializeTupleVariant"],[8,"SerializeMap"],[8,"SerializeStruct"],[8,"SerializeStructVariant"],[8,"Error"]]},\ -"serde_derive":{"doc":"This crate provides Serde’s two derive macros.","t":[24,24],"n":["Deserialize","Serialize"],"q":["serde_derive",""],"d":["",""],"i":[0,0],"f":[0,0],"p":[]},\ -"sha3":{"doc":"An implementation of the SHA-3 cryptographic hash …","t":[6,3,6,3,6,3,6,3,8,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,10,10,11,11,11,11,11,11,11,11,11,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,10,10,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["CShake128","CShake128Core","CShake128Reader","CShake128ReaderCore","CShake256","CShake256Core","CShake256Reader","CShake256ReaderCore","Digest","Keccak224","Keccak224Core","Keccak256","Keccak256Core","Keccak256Full","Keccak256FullCore","Keccak384","Keccak384Core","Keccak512","Keccak512Core","Sha3_224","Sha3_224Core","Sha3_256","Sha3_256Core","Sha3_384","Sha3_384Core","Sha3_512","Sha3_512Core","Shake128","Shake128Core","Shake128Reader","Shake128ReaderCore","Shake256","Shake256Core","Shake256Reader","Shake256ReaderCore","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","chain_update","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","default","default","default","default","default","default","default","default","default","digest","digest","finalize","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_fixed_core","finalize_into","finalize_into_reset","finalize_reset","finalize_xof_core","finalize_xof_core","finalize_xof_core","finalize_xof_core","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","new","new","new","new_with_function_name","new_with_function_name","new_with_prefix","output_size","read_block","read_block","read_block","read_block","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","reset","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","update_blocks","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name","write_alg_name"],"q":["sha3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["CSHAKE128 hasher state.","Core CSHAKE128 hasher state.","CSHAKE128 reader state.","Core CSHAKE128 reader state.","CSHAKE256 hasher state.","Core CSHAKE256 hasher state.","CSHAKE256 reader state.","Core CSHAKE256 reader state.","Convenience wrapper trait covering functionality of …","Keccak-224 hasher state.","Core Keccak-224 hasher state.","Keccak-256 hasher state.","Core Keccak-256 hasher state.","SHA-3 CryptoNight variant hasher state.","Core SHA-3 CryptoNight variant hasher state.","Keccak-384 hasher state.","Core Keccak-384 hasher state.","Keccak-512 hasher state.","Core Keccak-512 hasher state.","SHA-3-224 hasher state.","Core SHA-3-224 hasher state.","SHA-3-256 hasher state.","Core SHA-3-256 hasher state.","SHA-3-384 hasher state.","Core SHA-3-384 hasher state.","SHA-3-512 hasher state.","Core SHA-3-512 hasher state.","SHAKE128 hasher state.","Core SHAKE128 hasher state.","SHAKE128 reader state.","Core SHAKE128 reader state.","SHAKE256 hasher state.","Core SHAKE256 hasher state.","SHAKE256 reader state.","Core SHAKE256 reader state.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Process input data in a chained manner.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Compute hash of data.","Retrieve result and consume hasher instance.","","","","","","","","","","Write result into provided array and consume the hasher …","Write result into provided array and reset the hasher …","Retrieve result and reset hasher instance.","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Create new hasher instance.","Creates a new CSHAKE instance with the given customization.","Creates a new CSHAKE instance with the given customization.","Creates a new CSHAKE instance with the given function name …","Creates a new CSHAKE instance with the given function name …","Create new hasher instance which has processed the …","Get output size of the hasher","","","","","Reset hasher instance to its initial state.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Process data, updating the internal state.","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,13,0,29,29,2,3,4,5,6,7,8,9,10,29,29,29,11,13,15,17,2,3,4,5,6,7,8,9,10,11,13,15,17,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,15,17,15,17,29,29,12,14,16,18,29,2,3,4,5,6,7,8,9,10,11,13,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,29,2,3,4,5,6,7,8,9,10,11,13,15,17,2,3,4,5,6,7,8,9,10,11,13,15,17],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],2],[[],3],[[],4],[[],5],[[],6],[[],7],[[],8],[[],9],[[],10],[[],11],[[],13],0,[1,[[20,[19]]]],[[],[[20,[19]]]],[[2,21,22]],[[3,21,22]],[[4,21,22]],[[5,21,22]],[[6,21,22]],[[7,21,22]],[[8,21,22]],[[9,21,22]],[[10,21,22]],[20],[20],[[],[[20,[19]]]],[[11,21]],[[13,21]],[[15,21]],[[17,21]],[[2,23],24],[[3,23],24],[[4,23],24],[[5,23],24],[[6,23],24],[[7,23],24],[[8,23],24],[[9,23],24],[[10,23],24],[[11,23],24],[[13,23],24],[[15,23],24],[[17,23],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],15],[[],17],[[],15],[[],17],[1],[[],25],[12,[[26,[12]]]],[14,[[26,[14]]]],[16,[[26,[16]]]],[18,[[26,[18]]]],[[]],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[13],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[13],[15],[17],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24],[23,24]],"p":[[8,"AsRef"],[3,"Keccak224Core"],[3,"Keccak256Core"],[3,"Keccak384Core"],[3,"Keccak512Core"],[3,"Keccak256FullCore"],[3,"Sha3_224Core"],[3,"Sha3_256Core"],[3,"Sha3_384Core"],[3,"Sha3_512Core"],[3,"Shake128Core"],[3,"Shake128ReaderCore"],[3,"Shake256Core"],[3,"Shake256ReaderCore"],[3,"CShake128Core"],[3,"CShake128ReaderCore"],[3,"CShake256Core"],[3,"CShake256ReaderCore"],[15,"u8"],[3,"GenericArray"],[6,"Buffer"],[6,"Output"],[3,"Formatter"],[6,"Result"],[15,"usize"],[6,"Block"],[4,"Result"],[3,"TypeId"],[8,"Digest"]]},\ -"shale":{"doc":"","t":[13,3,17,13,8,8,8,3,3,3,3,3,13,3,4,8,13,6,8,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,12,10,11,10,11,11,11,11,11,11,11,10,11,10,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,10,11,10,11,10,11,11,10,11,11,10,11,10,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,10,11,12,12,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,10,10,11,11,11,11,10,11,3,3,3,18,18,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5],"n":["DecodeError","DiskWrite","INVALID_SPACE_ID","LinearMemStoreError","MemStore","MemView","MummyItem","MummyObj","Obj","ObjCache","ObjPtr","ObjRef","ObjRefError","PlainMem","ShaleError","ShaleStore","SliceError","SpaceID","TypedView","addr","as_ptr","block","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","compact","data","dehydrate","dehydrate","dehydrated_len","dehydrated_len","deref","deref","deref","drop","drop","eq","estimate_mem_image","estimate_mem_image","flush_dirty","flush_dirty","flush_dirty","fmt","fmt","fmt","free_item","from","from","from","from","from","from","from","from","from_typed_view","get","get_hash","get_item","get_mem_store","get_mem_store","get_offset","get_offset","get_shared","get_shared","get_space_id","get_view","get_view","hash","hydrate","hydrate","id","id","into","into","into","into","into","into","into","into","is_mem_mapped","is_mem_mapped","is_mem_mapped","is_null","item_to_obj","new","new","new_from_addr","null","pop","ptr_to_obj","put","put_item","slice","space_id","space_off","to_dehydrated","to_longlive","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","util","write","write","write","write","write","write","write_mem_image","write_mem_image","CompactHeader","CompactSpace","CompactSpaceHeader","MSIZE","MSIZE","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","dehydrate","dehydrate","dehydrated_len","dehydrated_len","flush_dirty","free_item","from","from","from","get_item","hydrate","hydrate","into","into","into","is_freed","new","new","payload_size","put_item","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","get_raw_bytes"],"q":["shale","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","shale::compact","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","shale::util"],"d":["","","","","In-memory store that offers access to intervals from a …","A handle that pins and provides a readable access to a …","A stored item type that can be decoded from or encoded to …","Reference implementation of TypedView. It takes any type …","A wrapper of TypedView to enable writes. The direct …","ObjRef pool that is used by ShaleStore implementation to …","Opaque typed pointer in the 64-bit virtual addressable …","User handle that offers read & write access to the stored …","","Purely volatile, vector-based implementation for MemStore. …","","A persistent item storage backed by linear logical space. …","","","A addressed, typed, and read-writable handle for the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Estimate the serialized length of the current type …","","Flush all dirty writes.","","","","","","Free an item and recycle its space when applicable.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Dereference ObjPtr to a unique handle that allows direct …","Access it as a MemStore object.","","Get the offset of the initial byte in the linear space.","","Returns a handle that allows shared access to the store.","","","Returns a handle that pins the length of bytes starting …","","","","","Returns the identifier of this storage space.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns if the typed content is memory-mapped (i.e., all …","","","","","","","","","","","","Allocate a new item.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Write the change to the portion of the linear space …","Gain mutable access to the typed content. By changing it, …","Write to the underlying object. Returns Some(()) on …","","","","Serialize the type content to the memory image. It defines …","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","",""],"i":[16,0,0,16,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,2,4,0,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,2,2,0,11,9,2,9,2,4,5,6,4,5,2,17,6,34,4,10,11,2,16,34,11,2,4,5,6,21,10,16,4,10,2,34,17,6,17,6,19,21,4,19,21,2,9,2,19,21,11,2,4,5,6,21,10,16,17,9,6,2,6,21,10,2,2,10,6,10,34,6,11,11,0,5,2,2,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,11,2,4,5,6,21,10,16,0,19,17,4,5,6,21,17,6,0,0,0,30,31,30,31,32,30,31,32,30,31,30,31,32,32,30,31,32,32,30,31,30,31,32,30,31,32,30,32,30,31,32,30,31,32,30,31,32,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[2,[1]]],3],[[[4,[1]]],[[2,[1]]]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,2],[[]],0,0,[[]],[2],[[],3],[2,3],[4],[5,4],[6],[[[4,[1]]]],[5],[[2,2],7],[[],[[8,[3]]]],[[[6,[9]]],[[8,[3]]]],[[],8],[[[4,[1]]]],[10,8],[[11,12],[[14,[13]]]],[[[2,[1]],12],15],[[16,12],15],[2,[[14,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[18,[17]]],[[4,[1]]]],[[10,2],[[14,[[8,[5]],16]]]],[[],3],[2,[[14,[5,16]]]],[[],19],[[[6,[9]]],19],[[],3],[[[6,[9]]],3],[[],[[8,[[18,[20]]]]]],[21,[[8,[[18,[20]]]]]],[[[4,[1]]],22],[[3,3],[[8,[[18,[23]]]]]],[[21,3,3],[[8,[[18,[23]]]]]],[2],[[3,19],[[14,[16]]]],[[3,19],[[14,[2,16]]]],[[],22],[21,22],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],7],[[],7],[[[6,[9]]],7],[[[2,[1]]],7],[[19,3,3,9],[[14,[[4,[9]],16]]]],[[3,22],21],[24,10],[3,[[2,[1]]]],[[],[[2,[1]]]],[[10,2]],[[19,[2,[9]],3],[[14,[[4,[9]],16]]]],[[10,4],5],[3,[[14,[5,16]]]],[[4,3,3,9],[[14,[[4,[9]],16]]]],0,0,[9,[[26,[25]]]],[5,5],[[]],[[],27],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],0,[3],[[]],[[[4,[1]],29],8],[[5,29],8],[[[6,[9]]]],[[21,3]],[[]],[[[6,[9]]]],0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[30],[31],[30,3],[31,3],[[[32,[9]]],8],[[[32,[9]],[2,[9]]],[[14,[16]]]],[[]],[[]],[[]],[[[32,[9]],[2,[9]]],[[14,[[5,[9]],16]]]],[[3,19],[[14,[30,16]]]],[[3,19],[[14,[31,16]]]],[[]],[[]],[[]],[30,7],[[3,3],31],[[[33,[19]],[33,[19]],[4,[31]],[10,[9]],3,3],[[14,[[32,[9]],16]]]],[30,3],[[[32,[9]],9,3],[[14,[[5,[9]],16]]]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],28],[[],28],[[],28],[[],[[26,[25]]]]],"p":[[8,"Sized"],[3,"ObjPtr"],[15,"u64"],[3,"Obj"],[3,"ObjRef"],[3,"MummyObj"],[15,"bool"],[4,"Option"],[8,"MummyItem"],[3,"ObjCache"],[3,"DiskWrite"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[4,"ShaleError"],[8,"TypedView"],[3,"Box"],[8,"MemStore"],[8,"Deref"],[3,"PlainMem"],[6,"SpaceID"],[8,"MemView"],[15,"usize"],[15,"u8"],[3,"Vec"],[3,"String"],[3,"TypeId"],[8,"FnOnce"],[3,"CompactHeader"],[3,"CompactSpaceHeader"],[3,"CompactSpace"],[3,"Rc"],[8,"ShaleStore"]]},\ -"slab":{"doc":"Pre-allocated storage for a uniform data type.","t":[3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["Drain","IntoIter","Iter","IterMut","Slab","VacantEntry","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone_into","clone_into","compact","contains","default","drain","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_iter","get","get2_mut","get2_unchecked_mut","get_mut","get_unchecked","get_unchecked_mut","index","index_mut","insert","insert","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","is_empty","iter","iter_mut","key","key_of","len","len","len","len","len","new","next","next","next","next","next_back","next_back","next_back","next_back","remove","reserve","reserve_exact","retain","shrink_to_fit","size_hint","size_hint","size_hint","size_hint","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_remove","type_id","type_id","type_id","type_id","type_id","type_id","vacant_entry","vacant_key","with_capacity"],"q":["slab","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["A draining iterator for Slab","A consuming iterator over the values stored in a Slab","An iterator over the values stored in the Slab","A mutable iterator over the values stored in the Slab","Pre-allocated storage for a uniform data type","A handle to a vacant entry in a Slab.","","","","","","","","","","","","","Return the number of values the slab can store without …","Clear the slab of all values.","","","","","Reduce the capacity as much as possible, changing the key …","Return true if a value is associated with the given key.","","Return a draining iterator that removes all elements from …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Return a reference to the value associated with the given …","Return two mutable references to the values associated …","Return two mutable references to the values associated …","Return a mutable reference to the value associated with …","Return a reference to the value associated with the given …","Return a mutable reference to the value associated with …","","","Insert a value in the slab, returning key assigned to the …","Insert a value in the entry, returning a mutable reference …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","Return true if there are no values stored in the slab.","Return an iterator over the slab.","Return an iterator that allows modifying each value.","Return the key associated with this entry.","Get the key for an element in the slab.","","","","","Return the number of stored values.","Construct a new, empty Slab.","","","","","","","","","Remove and return the value associated with the given key.","Reserve capacity for at least additional more values to be …","Reserve the minimum capacity required to store exactly …","Retain only the elements specified by the predicate.","Shrink the capacity of the slab as much as possible …","","","","","","","","","","","","","","","","","","","Tries to remove the value associated with the given key, …","","","","","","","Return a handle to a vacant entry allowing for further …","Returns the key of the next vacant entry.","Construct a new, empty Slab with the specified capacity."],"i":[0,0,0,0,0,0,7,3,10,6,1,12,7,3,10,6,1,12,1,1,3,1,3,1,1,1,1,1,7,3,10,6,1,12,7,3,10,6,1,12,1,1,1,1,1,1,1,1,1,1,12,7,3,10,6,1,12,7,3,10,6,1,1,1,1,1,1,12,1,7,3,10,6,1,1,7,3,10,6,7,3,10,6,1,1,1,1,1,7,3,10,6,3,1,7,3,10,6,1,12,7,3,10,6,1,12,1,7,3,10,6,1,12,1,1,1],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,2],[1],[3,3],[[[1,[4]]],[[1,[4]]]],[[]],[[]],[1],[[1,2],5],[[],1],[1,6],[[7,8],9],[[3,8],9],[[10,8],9],[[6,8],9],[[1,8],9],[[[12,[11]],8],9],[[]],[[]],[[]],[[]],[[]],[[]],[[],1],[[1,2],13],[[1,2,2],13],[[1,2,2]],[[1,2],13],[[1,2]],[[1,2]],[[1,2]],[[1,2]],[1,2],[12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,10],[1,3],[1,7],[1,5],[1,3],[1,10],[12,2],[1,2],[7,2],[3,2],[10,2],[6,2],[1,2],[[],1],[7,13],[3,13],[10,13],[6,13],[7,13],[3,13],[10,13],[6,13],[[1,2]],[[1,2]],[[1,2]],[1],[1],[7],[3],[10],[6],[[]],[[]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[1,2],13],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[1,12],[1,2],[2,1]],"p":[[3,"Slab"],[15,"usize"],[3,"Iter"],[8,"Clone"],[15,"bool"],[3,"Drain"],[3,"IntoIter"],[3,"Formatter"],[6,"Result"],[3,"IterMut"],[8,"Debug"],[3,"VacantEntry"],[4,"Option"],[4,"Result"],[3,"TypeId"]]},\ -"smallvec":{"doc":"Small vectors in various sizes. These store a certain …","t":[13,8,13,4,3,3,16,3,8,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,14,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12],"n":["AllocErr","Array","CapacityOverflow","CollectionAllocErr","Drain","IntoIter","Item","SmallVec","ToSmallVec","append","as_mut","as_mut_ptr","as_mut_slice","as_mut_slice","as_ptr","as_ref","as_slice","as_slice","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","clear","clone","clone","clone_from","clone_into","clone_into","cmp","dedup","dedup_by","dedup_by_key","default","deref","deref_mut","drain","drop","drop","drop","eq","extend","extend_from_slice","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_buf","from_buf_and_len","from_buf_and_len_unchecked","from_elem","from_iter","from_raw_parts","from_slice","from_vec","grow","hash","index","index_mut","inline_size","insert","insert_from_slice","insert_many","into","into","into","into","into_boxed_slice","into_inner","into_iter","into_iter","into_iter","into_iter","into_iter","into_vec","is_empty","len","len","new","next","next","next_back","next_back","partial_cmp","pop","push","remove","reserve","reserve_exact","resize","resize_with","retain","retain_mut","set_len","shrink_to_fit","size","size_hint","size_hint","smallvec","spilled","swap_remove","to_owned","to_owned","to_smallvec","to_string","truncate","try_from","try_from","try_from","try_from","try_grow","try_into","try_into","try_into","try_into","try_reserve","try_reserve_exact","type_id","type_id","type_id","type_id","with_capacity","layout"],"q":["smallvec","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","smallvec::CollectionAllocErr"],"d":["The allocator return an error","Types that can be used as the backing store for a SmallVec","Overflow usize::MAX or other error during size computation","Error type for APIs with fallible heap allocation","An iterator that removes the items from a SmallVec and …","An iterator that consumes a SmallVec and yields its items …","The type of the array’s elements.","A Vec-like container that can store a small number of …","Convenience trait for constructing a SmallVec","Moves all the elements of other into self, leaving other …","","Returns a raw mutable pointer to the vector’s buffer.","Extracts a mutable slice of the entire vector.","Returns the remaining items of this iterator as a mutable …","Returns a raw pointer to the vector’s buffer.","","Extracts a slice containing the entire vector.","Returns the remaining items of this iterator as a slice.","","","","","","","","","","","The number of items the vector can hold without …","Remove all elements from the vector.","","","","","","","Removes consecutive duplicate elements.","Removes consecutive duplicate elements using the given …","Removes consecutive elements that map to the same key.","","","","Creates a draining iterator that removes the specified …","","","","","","Copy elements from a slice and append them to the vector.","","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Constructs a new SmallVec on the stack from an A without …","Constructs a new SmallVec on the stack from an A without …","Constructs a new SmallVec on the stack from an A without …","Creates a SmallVec with n copies of elem.","","Creates a SmallVec directly from the raw components of …","Copy the elements from a slice into a new SmallVec.","Construct a new SmallVec from a Vec<A::Item>.","Re-allocate to set the capacity to …","","","","The maximum number of elements this vector can hold inline","Insert an element at position index, shifting all elements …","Copy elements from a slice into the vector at position …","Insert multiple elements at position index, shifting all …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Converts a SmallVec into a Box<[T]> without reallocating …","Convert the SmallVec into an A if possible. Otherwise …","","","","","","Convert a SmallVec to a Vec, without reallocating if the …","Returns true if the vector is empty","","The number of elements stored in the vector","Construct an empty vector","","","","","","Remove an item from the end of the vector and return it, …","Append an item to the vector.","Remove and return the element at position index, shifting …","Reserve capacity for additional more elements to be …","Reserve the minimum capacity for additional more elements …","Resizes the vector so that its length is equal to len.","Resizes the SmallVec in-place so that len is equal to …","Retains only the elements specified by the predicate.","Retains only the elements specified by the predicate.","Sets the length of a vector.","Shrink the capacity of the vector as much as possible.","Returns the number of items the array can hold.","","","Creates a SmallVec containing the arguments.","Returns true if the data has spilled into a separate …","Remove the element at position index, replacing it with …","","","Construct a new SmallVec from a slice.","","Shorten the vector, keeping the first len elements and …","","","","","Re-allocate to set the capacity to …","","","","","Reserve capacity for additional more elements to be …","Reserve the minimum capacity for additional more elements …","","","","","Construct an empty vector with enough capacity …","The layout that was passed to the allocator"],"i":[12,0,12,0,0,0,1,0,0,2,2,2,2,3,2,2,2,3,7,2,2,3,12,7,2,2,3,12,2,2,2,3,2,2,3,2,2,2,2,2,2,2,2,7,2,3,2,2,2,7,2,3,12,12,7,2,2,2,2,2,3,12,12,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,2,3,12,2,2,7,2,2,2,3,2,2,7,2,2,7,3,7,3,2,2,2,2,2,2,2,2,2,2,2,2,1,7,3,0,2,2,2,3,24,12,2,7,2,3,12,2,7,2,3,12,2,2,7,2,3,12,2,25],"f":[0,0,0,0,0,0,0,0,0,[[[2,[1]],2]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[]],[[[2,[1]]]],[[]],[[]],[[]],[[]],[[[2,[1]]]],[[]],[[]],[[]],[[[2,[1]]],4],[[[2,[1]]]],[[[2,[1]]],[[2,[1]]]],[[[3,[[0,[1,5]]]]],[[3,[[0,[1,5]]]]]],[[[2,[1]],[2,[1]]]],[[]],[[]],[[[2,[1]],2],6],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[],[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]]],[[[2,[1]]],[[7,[1]]]],[[[7,[1]]]],[[[2,[1]]]],[[[3,[1]]]],[[[2,[1]],2],8],[[[2,[1]],9]],[[[2,[1]]]],[[[7,[1]],10],11],[[[2,[1]],10],11],[[[3,[1]],10],11],[[12,10],11],[[12,10],11],[[]],[1,[[2,[1]]]],[[],[[2,[1]]]],[13,[[2,[1]]]],[14],[[]],[[]],[15,12],[[]],[1,[[2,[1]]]],[[1,4],[[2,[1]]]],[[[16,[1]],4],[[2,[1]]]],[4,[[2,[1]]]],[9,[[2,[1]]]],[[4,4],[[2,[1]]]],[[],[[2,[1]]]],[13,[[2,[1]]]],[[[2,[1]],4]],[[[2,[1]]]],[[[2,[1]],17]],[[[2,[1]],17]],[[[2,[1]]],4],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4,9]],[[]],[[]],[[]],[[]],[[[2,[1]]],18],[[[2,[1]]],[[19,[1,[2,[1]]]]]],[[]],[2],[2],[[[2,[1]]]],[[]],[[[2,[1]]],13],[[[2,[1]]],8],[[[7,[1]]],4],[[[2,[1]]],4],[[],[[2,[1]]]],[[[7,[1]]],20],[[[3,[1]]],20],[[[7,[1]]],20],[[[3,[1]]],20],[[[2,[1]],2],[[20,[6]]]],[[[2,[1]]],20],[[[2,[1]]]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],4]],[[[2,[1]],21]],[[[2,[1]],21]],[[[2,[1]],4]],[[[2,[1]]]],[[],4],[[[7,[1]]]],[[[3,[1]]]],0,[[[2,[1]]],8],[[[2,[1]],4]],[[]],[[]],[[],2],[[],22],[[[2,[1]],4]],[[],19],[[],19],[[],19],[[],19],[[[2,[1]],4],[[19,[12]]]],[[],19],[[],19],[[],19],[[],19],[[[2,[1]],4],[[19,[12]]]],[[[2,[1]],4],[[19,[12]]]],[[],23],[[],23],[[],23],[[],23],[4,[[2,[1]]]],0],"p":[[8,"Array"],[3,"SmallVec"],[3,"IntoIter"],[15,"usize"],[8,"Clone"],[4,"Ordering"],[3,"Drain"],[15,"bool"],[8,"IntoIterator"],[3,"Formatter"],[6,"Result"],[4,"CollectionAllocErr"],[3,"Vec"],[15,"never"],[6,"LayoutErr"],[19,"MaybeUninit"],[8,"SliceIndex"],[3,"Box"],[4,"Result"],[4,"Option"],[8,"FnMut"],[3,"String"],[3,"TypeId"],[8,"ToSmallVec"],[13,"AllocErr"]]},\ -"static_assertions":{"doc":"Banner","t":[14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14],"n":["assert_cfg","assert_eq_align","assert_eq_size","assert_eq_size_ptr","assert_eq_size_val","assert_fields","assert_impl_all","assert_impl_any","assert_impl_one","assert_not_impl_all","assert_not_impl_any","assert_obj_safe","assert_trait_sub_all","assert_trait_super_all","assert_type_eq_all","assert_type_ne_all","const_assert","const_assert_eq","const_assert_ne"],"q":["static_assertions","","","","","","","","","","","","","","","","","",""],"d":["Asserts that a given configuration is set.","Asserts that types are equal in alignment.","Asserts that types are equal in size.","Asserts that values pointed to are equal in size.","Asserts that values are equal in size.","Asserts that the type has the given fields.","Asserts that the type implements all of the given traits.","Asserts that the type implements any of the given traits.","Asserts that the type implements exactly one in a set of …","Asserts that the type does not implement all of the given …","Asserts that the type does not implement any of the given …","Asserts that the traits support dynamic dispatch (…","Asserts that the trait is a child of all of the other …","Asserts that the trait is a parent of all of the other …","Asserts that all types in a list are equal to each other.","Asserts that all types are not equal to each other.","Asserts that constant expressions evaluate to true.","Asserts that constants are equal in value.","Asserts that constants are not equal in value."],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"p":[]},\ -"syn":{"doc":"github crates-io docs-rs","t":[3,13,13,13,13,3,3,13,13,13,13,13,4,3,6,13,13,3,4,13,3,13,13,13,13,13,13,13,3,13,13,3,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,3,3,13,13,13,13,4,3,3,3,13,13,3,13,13,13,13,13,13,3,4,13,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,3,13,3,3,4,3,3,3,13,13,13,4,13,4,3,3,3,3,13,13,4,4,4,3,13,13,13,13,13,13,3,13,13,13,3,4,3,3,3,3,13,3,13,13,13,13,13,4,13,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,13,3,13,13,13,13,3,13,4,13,13,13,3,3,3,3,3,3,3,3,13,13,13,3,13,13,13,13,13,13,13,13,4,13,13,4,4,13,3,3,13,13,13,3,13,13,13,13,13,13,13,13,13,4,13,13,13,13,13,13,13,13,13,13,13,3,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,13,13,13,13,13,4,3,3,3,3,13,13,3,13,13,4,13,3,13,13,13,13,13,13,13,13,13,13,6,13,4,13,13,13,13,13,3,13,13,13,13,4,13,4,13,13,13,13,13,13,14,13,13,13,3,4,4,3,3,3,3,13,13,13,13,13,13,13,3,4,13,13,13,13,13,13,13,13,13,13,13,3,3,3,3,3,3,3,3,3,4,3,3,3,3,3,3,3,13,4,13,13,13,13,13,13,13,13,3,3,3,3,3,4,3,3,13,13,13,13,13,13,13,13,3,3,3,4,3,4,13,13,13,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,14,12,12,12,12,12,12,12,14,12,0,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,12,12,12,12,12,12,12,12,12,12,14,14,12,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,11,11,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,11,12,12,12,11,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,14,0,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,5,11,14,11,11,11,11,14,14,5,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,12,12,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,0,11,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,11,12,12,12,12,12,12,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,12,12,12,11,11,11,11,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,18,10,3,3,3,16,8,3,6,8,8,6,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,13,3,3,3,3,4,3,3,3,13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,10,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,8,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11,5,11,11],"n":["Abi","Add","AddEq","And","AngleBracketed","AngleBracketedGenericArguments","Arm","Array","Array","Assign","AssignOp","Async","AttrStyle","Attribute","AttributeArgs","Await","BareFn","BareFnArg","BinOp","Binary","Binding","Binding","BitAnd","BitAndEq","BitOr","BitOrEq","BitXor","BitXorEq","Block","Block","Bool","BoundLifetimes","Box","Box","Brace","Bracket","Break","Byte","ByteStr","Call","Cast","Char","Closed","Closure","Const","Const","Const","Const","Const","Const","ConstParam","Constraint","Constraint","Continue","Cooked","Crate","Data","DataEnum","DataStruct","DataUnion","Default","Deref","DeriveInput","Div","DivEq","Enum","Enum","Eq","Eq","Error","Expr","Expr","ExprArray","ExprAssign","ExprAssignOp","ExprAsync","ExprAwait","ExprBinary","ExprBlock","ExprBox","ExprBreak","ExprCall","ExprCast","ExprClosure","ExprContinue","ExprField","ExprForLoop","ExprGroup","ExprIf","ExprIndex","ExprLet","ExprLit","ExprLoop","ExprMacro","ExprMatch","ExprMethodCall","ExprParen","ExprPath","ExprRange","ExprReference","ExprRepeat","ExprReturn","ExprStruct","ExprTry","ExprTryBlock","ExprTuple","ExprType","ExprUnary","ExprUnsafe","ExprWhile","ExprYield","ExternCrate","Field","Field","FieldPat","FieldValue","Fields","FieldsNamed","FieldsUnnamed","File","Float","Fn","Fn","FnArg","ForLoop","ForeignItem","ForeignItemFn","ForeignItemMacro","ForeignItemStatic","ForeignItemType","ForeignMod","Ge","GenericArgument","GenericMethodArgument","GenericParam","Generics","Glob","Group","Group","Group","Gt","HalfOpen","Ident","Ident","If","Impl","ImplGenerics","ImplItem","ImplItemConst","ImplItemMacro","ImplItemMethod","ImplItemType","ImplTrait","Index","Index","Infer","Inherited","Inner","Int","Item","Item","ItemConst","ItemEnum","ItemExternCrate","ItemFn","ItemForeignMod","ItemImpl","ItemMacro","ItemMacro2","ItemMod","ItemStatic","ItemStruct","ItemTrait","ItemTraitAlias","ItemType","ItemUnion","ItemUse","Label","Le","Let","Lifetime","Lifetime","Lifetime","Lifetime","Lifetime","LifetimeDef","List","Lit","Lit","Lit","Lit","LitBool","LitByte","LitByteStr","LitChar","LitFloat","LitInt","LitStr","Local","Local","Loop","Lt","Macro","Macro","Macro","Macro","Macro","Macro","Macro","Macro","Macro2","MacroDelimiter","Match","Maybe","Member","Meta","Meta","MetaList","MetaNameValue","Method","Method","MethodCall","MethodTurbofish","Mod","Mul","MulEq","Name","NameValue","Named","Named","Ne","Neg","NestedMeta","Never","None","None","Not","Or","Or","Outer","Paren","Paren","Paren","Parenthesized","ParenthesizedGenericArguments","Pat","PatBox","PatIdent","PatLit","PatMacro","PatOr","PatPath","PatRange","PatReference","PatRest","PatSlice","PatStruct","PatTuple","PatTupleStruct","PatType","PatWild","Path","Path","Path","Path","Path","Path","PathArguments","PathSegment","PredicateEq","PredicateLifetime","PredicateType","Ptr","Public","QSelf","Range","Range","RangeLimits","Raw","Receiver","Receiver","Reference","Reference","Reference","Rem","RemEq","Rename","Repeat","Rest","Restricted","Result","Return","ReturnType","Semi","Shl","ShlEq","Shr","ShrEq","Signature","Slice","Slice","Static","Static","Stmt","Str","StrStyle","Struct","Struct","Struct","Struct","Sub","SubEq","Token","Trait","Trait","TraitAlias","TraitBound","TraitBoundModifier","TraitItem","TraitItemConst","TraitItemMacro","TraitItemMethod","TraitItemType","TraitObject","Try","TryBlock","Tuple","Tuple","Tuple","TupleStruct","Turbofish","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","Type","TypeArray","TypeBareFn","TypeGenerics","TypeGroup","TypeImplTrait","TypeInfer","TypeMacro","TypeNever","TypeParam","TypeParamBound","TypeParen","TypePath","TypePtr","TypeReference","TypeSlice","TypeTraitObject","TypeTuple","Typed","UnOp","Unary","Union","Union","Unit","Unnamed","Unnamed","Unsafe","Use","UseGlob","UseGroup","UseName","UsePath","UseRename","UseTree","Variadic","Variant","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","Verbatim","VisCrate","VisPublic","VisRestricted","Visibility","WhereClause","WherePredicate","While","Wild","Yield","abi","abi","abi","and_token","and_token","and_token","apostrophe","args","args","args","args","arguments","arms","as_token","as_token","as_token","as_turbofish","async_token","asyncness","asyncness","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","attrs","auto_token","await_token","bang_token","bang_token","base","base","base10_digits","base10_digits","base10_parse","base10_parse","block","block","block","block","block","block","body","body","body","body","body","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bounded_ty","bounds","bounds","bounds","bounds","bounds","bounds","bounds","bounds","bounds","box_token","box_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","brace_token","braced","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracket_token","bracketed","break_token","buffer","by_ref","capture","capture","cases","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","colon2_token","colon2_token","colon2_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","colon_token","comma","cond","cond","const_params","const_params_mut","const_token","const_token","const_token","const_token","const_token","constness","content","continue_token","crate_token","crate_token","custom_keyword","custom_punctuation","data","default","default","default","default","default","default","default","default","defaultness","defaultness","defaultness","defaultness","delimiter","discriminant","dot2_token","dot2_token","dot2_token","dot_token","dot_token","dot_token","dots","dyn_token","elem","elem","elem","elem","elem","elem","elems","elems","elems","elems","elems","else_branch","enum_token","enum_token","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","eq_token","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","expr","ext","extern_token","extern_token","fat_arrow_token","fields","fields","fields","fields","fields","fields","fields","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fn_token","fn_token","for_token","for_token","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","func","generics","generics","generics","generics","generics","generics","generics","generics","generics","generics","generics","get_ident","group_token","group_token","gt_token","gt_token","gt_token","gt_token","gt_token","guard","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hi","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","ident","if_token","impl_token","impl_token","in_token","in_token","index","index","init","inputs","inputs","inputs","inputs","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","is_empty","is_empty","is_ident","is_none","items","items","items","items","items","iter","iter_mut","label","label","label","label","label","label","leading_colon","leading_colon","leading_vert","left","left","left","len","len","len","let_token","let_token","lhs_ty","lifetime","lifetime","lifetime","lifetime","lifetimes","lifetimes","lifetimes","lifetimes","lifetimes","lifetimes_mut","limits","limits","lit","lit","lo","loop_token","lt_token","lt_token","lt_token","lt_token","lt_token","mac","mac","mac","mac","mac","mac","mac","macro_token","make_where_clause","match_token","member","member","member","method","mod_token","modifier","movability","mutability","mutability","mutability","mutability","mutability","mutability","mutability","mutability","name","name","name","named","nested","new","new","new","new","new","new","new","new","new","new","new","new_raw","op","op","op","or1_token","or2_token","output","output","output","output","params","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","paren_token","parenthesized","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse2","parse_any","parse_args","parse_args_with","parse_body","parse_body_with","parse_file","parse_inner","parse_macro_input","parse_meta","parse_mod_style","parse_named","parse_outer","parse_quote","parse_quote_spanned","parse_str","parse_unnamed","parse_with","parse_within","parse_without_eager_brace","partial_cmp","partial_cmp","pat","pat","pat","pat","pat","pat","pat","pat","pat","path","path","path","path","path","path","path","path","path","path","path","path","path","position","pound_token","predicates","pub_token","pub_token","punctuated","qself","qself","qself","question_token","raw","receiver","receiver","reference","rename","rename","rest","return_token","rhs_ty","right","right","right","rules","segments","self_token","self_ty","semi","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","semi_token","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","set_span","shebang","sig","sig","sig","sig","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","spanned","split_for_impl","star_token","star_token","static_token","static_token","stmts","struct_token","struct_token","style","subpat","suffix","suffix","suffix","suffix","suffix","suffix","suffix","supertraits","then_branch","to","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","token","token","token","token","token","token","token","token","tokens","tokens","trait_","trait_token","trait_token","tree","tree","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_token","turbofish","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","ty","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_params","type_params_mut","type_token","type_token","type_token","type_token","underscore_token","underscore_token","union_token","union_token","unnamed","unraw","unsafe_token","unsafety","unsafety","unsafety","unsafety","use_token","value","value","value","value","value","value","variadic","variadic","variants","variants","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","vis","visit_mut","where_clause","where_token","while_token","without_plus","without_plus","without_plus","without_plus","yield_token","Cursor","TokenBuffer","begin","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","empty","eof","eq","from","from","group","ident","into","into","lifetime","literal","new","new2","partial_cmp","punct","span","to_owned","token_stream","token_tree","try_from","try_from","try_into","try_into","type_id","type_id","IdentExt","parse_any","peek_any","unraw","Error","Lookahead1","Nothing","Output","Parse","ParseBuffer","ParseStream","Parser","Peek","Result","StepCursor","advance_to","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","call","clone","clone","clone_into","clone_into","combine","cursor","deref","discouraged","drop","eq","error","error","error","extend","fmt","fmt","fmt","fmt","fmt","fork","from","from","from","from","from","from","hash","into","into","into","into","into","into_compile_error","into_iter","into_iter","is_empty","lookahead1","new","new_spanned","parse","parse","parse","parse","parse2","parse_str","parse_terminated","peek","peek","peek2","peek3","provide","span","span","step","to_compile_error","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","Speculative","advance_to","End","IntoIter","IntoPairs","Iter","IterMut","Pair","Pairs","PairsMut","Punctuated","Punctuated","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","empty_or_trailing","eq","extend","extend","first","first_mut","fmt","from","from","from","from","from","from","from","from","from_iter","from_iter","hash","index","index_mut","insert","into","into","into","into","into","into","into","into","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_iter","into_pairs","into_tuple","into_value","is_empty","iter","iter_mut","last","last_mut","len","len","len","len","len","len","len","new","new","next","next","next","next","next","next","next_back","next_back","next_back","next_back","next_back","next_back","pairs","pairs_mut","parse_separated_nonempty","parse_separated_nonempty_with","parse_terminated","parse_terminated_with","pop","punct","punct_mut","push","push_punct","push_value","size_hint","size_hint","size_hint","size_hint","size_hint","size_hint","span","span","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_tokens","to_tokens","trailing_punct","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","value","value_mut","Spanned","span","Abstract","Add","AddEq","And","AndAnd","AndEq","As","Async","At","Auto","Await","Bang","Become","Box","Brace","Bracket","Break","Caret","CaretEq","Colon","Colon2","Comma","Const","Continue","Crate","Default","Div","DivEq","Do","Dollar","Dot","Dot2","Dot3","DotDotEq","Dyn","Else","Enum","Eq","EqEq","Extern","FatArrow","Final","Fn","For","Ge","Group","Gt","If","Impl","In","LArrow","Le","Let","Loop","Lt","Macro","Match","Mod","Move","MulEq","Mut","Ne","Or","OrEq","OrOr","Override","Paren","Pound","Priv","Pub","Question","RArrow","Ref","Rem","RemEq","Return","SelfType","SelfValue","Semi","Shl","ShlEq","Shr","ShrEq","Star","Static","Struct","Sub","SubEq","Super","Tilde","Token","Trait","Try","Type","Typeof","Underscore","Union","Unsafe","Unsized","Use","Virtual","Where","While","Yield","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","parse","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","span","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","spans","surround","surround","surround","surround","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","to_tokens","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","VisitMut","visit_abi_mut","visit_abi_mut","visit_abi_mut","visit_angle_bracketed_generic_arguments_mut","visit_angle_bracketed_generic_arguments_mut","visit_angle_bracketed_generic_arguments_mut","visit_arm_mut","visit_arm_mut","visit_arm_mut","visit_attr_style_mut","visit_attr_style_mut","visit_attr_style_mut","visit_attribute_mut","visit_attribute_mut","visit_attribute_mut","visit_bare_fn_arg_mut","visit_bare_fn_arg_mut","visit_bare_fn_arg_mut","visit_bin_op_mut","visit_bin_op_mut","visit_bin_op_mut","visit_binding_mut","visit_binding_mut","visit_binding_mut","visit_block_mut","visit_block_mut","visit_block_mut","visit_bound_lifetimes_mut","visit_bound_lifetimes_mut","visit_bound_lifetimes_mut","visit_const_param_mut","visit_const_param_mut","visit_const_param_mut","visit_constraint_mut","visit_constraint_mut","visit_constraint_mut","visit_data_enum_mut","visit_data_enum_mut","visit_data_enum_mut","visit_data_mut","visit_data_mut","visit_data_mut","visit_data_struct_mut","visit_data_struct_mut","visit_data_struct_mut","visit_data_union_mut","visit_data_union_mut","visit_data_union_mut","visit_derive_input_mut","visit_derive_input_mut","visit_derive_input_mut","visit_expr_array_mut","visit_expr_array_mut","visit_expr_array_mut","visit_expr_assign_mut","visit_expr_assign_mut","visit_expr_assign_mut","visit_expr_assign_op_mut","visit_expr_assign_op_mut","visit_expr_assign_op_mut","visit_expr_async_mut","visit_expr_async_mut","visit_expr_async_mut","visit_expr_await_mut","visit_expr_await_mut","visit_expr_await_mut","visit_expr_binary_mut","visit_expr_binary_mut","visit_expr_binary_mut","visit_expr_block_mut","visit_expr_block_mut","visit_expr_block_mut","visit_expr_box_mut","visit_expr_box_mut","visit_expr_box_mut","visit_expr_break_mut","visit_expr_break_mut","visit_expr_break_mut","visit_expr_call_mut","visit_expr_call_mut","visit_expr_call_mut","visit_expr_cast_mut","visit_expr_cast_mut","visit_expr_cast_mut","visit_expr_closure_mut","visit_expr_closure_mut","visit_expr_closure_mut","visit_expr_continue_mut","visit_expr_continue_mut","visit_expr_continue_mut","visit_expr_field_mut","visit_expr_field_mut","visit_expr_field_mut","visit_expr_for_loop_mut","visit_expr_for_loop_mut","visit_expr_for_loop_mut","visit_expr_group_mut","visit_expr_group_mut","visit_expr_group_mut","visit_expr_if_mut","visit_expr_if_mut","visit_expr_if_mut","visit_expr_index_mut","visit_expr_index_mut","visit_expr_index_mut","visit_expr_let_mut","visit_expr_let_mut","visit_expr_let_mut","visit_expr_lit_mut","visit_expr_lit_mut","visit_expr_lit_mut","visit_expr_loop_mut","visit_expr_loop_mut","visit_expr_loop_mut","visit_expr_macro_mut","visit_expr_macro_mut","visit_expr_macro_mut","visit_expr_match_mut","visit_expr_match_mut","visit_expr_match_mut","visit_expr_method_call_mut","visit_expr_method_call_mut","visit_expr_method_call_mut","visit_expr_mut","visit_expr_mut","visit_expr_mut","visit_expr_paren_mut","visit_expr_paren_mut","visit_expr_paren_mut","visit_expr_path_mut","visit_expr_path_mut","visit_expr_path_mut","visit_expr_range_mut","visit_expr_range_mut","visit_expr_range_mut","visit_expr_reference_mut","visit_expr_reference_mut","visit_expr_reference_mut","visit_expr_repeat_mut","visit_expr_repeat_mut","visit_expr_repeat_mut","visit_expr_return_mut","visit_expr_return_mut","visit_expr_return_mut","visit_expr_struct_mut","visit_expr_struct_mut","visit_expr_struct_mut","visit_expr_try_block_mut","visit_expr_try_block_mut","visit_expr_try_block_mut","visit_expr_try_mut","visit_expr_try_mut","visit_expr_try_mut","visit_expr_tuple_mut","visit_expr_tuple_mut","visit_expr_tuple_mut","visit_expr_type_mut","visit_expr_type_mut","visit_expr_type_mut","visit_expr_unary_mut","visit_expr_unary_mut","visit_expr_unary_mut","visit_expr_unsafe_mut","visit_expr_unsafe_mut","visit_expr_unsafe_mut","visit_expr_while_mut","visit_expr_while_mut","visit_expr_while_mut","visit_expr_yield_mut","visit_expr_yield_mut","visit_expr_yield_mut","visit_field_mut","visit_field_mut","visit_field_mut","visit_field_pat_mut","visit_field_pat_mut","visit_field_pat_mut","visit_field_value_mut","visit_field_value_mut","visit_field_value_mut","visit_fields_mut","visit_fields_mut","visit_fields_mut","visit_fields_named_mut","visit_fields_named_mut","visit_fields_named_mut","visit_fields_unnamed_mut","visit_fields_unnamed_mut","visit_fields_unnamed_mut","visit_file_mut","visit_file_mut","visit_file_mut","visit_fn_arg_mut","visit_fn_arg_mut","visit_fn_arg_mut","visit_foreign_item_fn_mut","visit_foreign_item_fn_mut","visit_foreign_item_fn_mut","visit_foreign_item_macro_mut","visit_foreign_item_macro_mut","visit_foreign_item_macro_mut","visit_foreign_item_mut","visit_foreign_item_mut","visit_foreign_item_mut","visit_foreign_item_static_mut","visit_foreign_item_static_mut","visit_foreign_item_static_mut","visit_foreign_item_type_mut","visit_foreign_item_type_mut","visit_foreign_item_type_mut","visit_generic_argument_mut","visit_generic_argument_mut","visit_generic_argument_mut","visit_generic_method_argument_mut","visit_generic_method_argument_mut","visit_generic_method_argument_mut","visit_generic_param_mut","visit_generic_param_mut","visit_generic_param_mut","visit_generics_mut","visit_generics_mut","visit_generics_mut","visit_ident_mut","visit_ident_mut","visit_ident_mut","visit_impl_item_const_mut","visit_impl_item_const_mut","visit_impl_item_const_mut","visit_impl_item_macro_mut","visit_impl_item_macro_mut","visit_impl_item_macro_mut","visit_impl_item_method_mut","visit_impl_item_method_mut","visit_impl_item_method_mut","visit_impl_item_mut","visit_impl_item_mut","visit_impl_item_mut","visit_impl_item_type_mut","visit_impl_item_type_mut","visit_impl_item_type_mut","visit_index_mut","visit_index_mut","visit_index_mut","visit_item_const_mut","visit_item_const_mut","visit_item_const_mut","visit_item_enum_mut","visit_item_enum_mut","visit_item_enum_mut","visit_item_extern_crate_mut","visit_item_extern_crate_mut","visit_item_extern_crate_mut","visit_item_fn_mut","visit_item_fn_mut","visit_item_fn_mut","visit_item_foreign_mod_mut","visit_item_foreign_mod_mut","visit_item_foreign_mod_mut","visit_item_impl_mut","visit_item_impl_mut","visit_item_impl_mut","visit_item_macro2_mut","visit_item_macro2_mut","visit_item_macro2_mut","visit_item_macro_mut","visit_item_macro_mut","visit_item_macro_mut","visit_item_mod_mut","visit_item_mod_mut","visit_item_mod_mut","visit_item_mut","visit_item_mut","visit_item_mut","visit_item_static_mut","visit_item_static_mut","visit_item_static_mut","visit_item_struct_mut","visit_item_struct_mut","visit_item_struct_mut","visit_item_trait_alias_mut","visit_item_trait_alias_mut","visit_item_trait_alias_mut","visit_item_trait_mut","visit_item_trait_mut","visit_item_trait_mut","visit_item_type_mut","visit_item_type_mut","visit_item_type_mut","visit_item_union_mut","visit_item_union_mut","visit_item_union_mut","visit_item_use_mut","visit_item_use_mut","visit_item_use_mut","visit_label_mut","visit_label_mut","visit_label_mut","visit_lifetime_def_mut","visit_lifetime_def_mut","visit_lifetime_def_mut","visit_lifetime_mut","visit_lifetime_mut","visit_lifetime_mut","visit_lit_bool_mut","visit_lit_bool_mut","visit_lit_bool_mut","visit_lit_byte_mut","visit_lit_byte_mut","visit_lit_byte_mut","visit_lit_byte_str_mut","visit_lit_byte_str_mut","visit_lit_byte_str_mut","visit_lit_char_mut","visit_lit_char_mut","visit_lit_char_mut","visit_lit_float_mut","visit_lit_float_mut","visit_lit_float_mut","visit_lit_int_mut","visit_lit_int_mut","visit_lit_int_mut","visit_lit_mut","visit_lit_mut","visit_lit_mut","visit_lit_str_mut","visit_lit_str_mut","visit_lit_str_mut","visit_local_mut","visit_local_mut","visit_local_mut","visit_macro_delimiter_mut","visit_macro_delimiter_mut","visit_macro_delimiter_mut","visit_macro_mut","visit_macro_mut","visit_macro_mut","visit_member_mut","visit_member_mut","visit_member_mut","visit_meta_list_mut","visit_meta_list_mut","visit_meta_list_mut","visit_meta_mut","visit_meta_mut","visit_meta_mut","visit_meta_name_value_mut","visit_meta_name_value_mut","visit_meta_name_value_mut","visit_method_turbofish_mut","visit_method_turbofish_mut","visit_method_turbofish_mut","visit_nested_meta_mut","visit_nested_meta_mut","visit_nested_meta_mut","visit_parenthesized_generic_arguments_mut","visit_parenthesized_generic_arguments_mut","visit_parenthesized_generic_arguments_mut","visit_pat_box_mut","visit_pat_box_mut","visit_pat_box_mut","visit_pat_ident_mut","visit_pat_ident_mut","visit_pat_ident_mut","visit_pat_lit_mut","visit_pat_lit_mut","visit_pat_lit_mut","visit_pat_macro_mut","visit_pat_macro_mut","visit_pat_macro_mut","visit_pat_mut","visit_pat_mut","visit_pat_mut","visit_pat_or_mut","visit_pat_or_mut","visit_pat_or_mut","visit_pat_path_mut","visit_pat_path_mut","visit_pat_path_mut","visit_pat_range_mut","visit_pat_range_mut","visit_pat_range_mut","visit_pat_reference_mut","visit_pat_reference_mut","visit_pat_reference_mut","visit_pat_rest_mut","visit_pat_rest_mut","visit_pat_rest_mut","visit_pat_slice_mut","visit_pat_slice_mut","visit_pat_slice_mut","visit_pat_struct_mut","visit_pat_struct_mut","visit_pat_struct_mut","visit_pat_tuple_mut","visit_pat_tuple_mut","visit_pat_tuple_mut","visit_pat_tuple_struct_mut","visit_pat_tuple_struct_mut","visit_pat_tuple_struct_mut","visit_pat_type_mut","visit_pat_type_mut","visit_pat_type_mut","visit_pat_wild_mut","visit_pat_wild_mut","visit_pat_wild_mut","visit_path_arguments_mut","visit_path_arguments_mut","visit_path_arguments_mut","visit_path_mut","visit_path_mut","visit_path_mut","visit_path_segment_mut","visit_path_segment_mut","visit_path_segment_mut","visit_predicate_eq_mut","visit_predicate_eq_mut","visit_predicate_eq_mut","visit_predicate_lifetime_mut","visit_predicate_lifetime_mut","visit_predicate_lifetime_mut","visit_predicate_type_mut","visit_predicate_type_mut","visit_predicate_type_mut","visit_qself_mut","visit_qself_mut","visit_qself_mut","visit_range_limits_mut","visit_range_limits_mut","visit_range_limits_mut","visit_receiver_mut","visit_receiver_mut","visit_receiver_mut","visit_return_type_mut","visit_return_type_mut","visit_return_type_mut","visit_signature_mut","visit_signature_mut","visit_signature_mut","visit_span_mut","visit_span_mut","visit_span_mut","visit_stmt_mut","visit_stmt_mut","visit_stmt_mut","visit_trait_bound_modifier_mut","visit_trait_bound_modifier_mut","visit_trait_bound_modifier_mut","visit_trait_bound_mut","visit_trait_bound_mut","visit_trait_bound_mut","visit_trait_item_const_mut","visit_trait_item_const_mut","visit_trait_item_const_mut","visit_trait_item_macro_mut","visit_trait_item_macro_mut","visit_trait_item_macro_mut","visit_trait_item_method_mut","visit_trait_item_method_mut","visit_trait_item_method_mut","visit_trait_item_mut","visit_trait_item_mut","visit_trait_item_mut","visit_trait_item_type_mut","visit_trait_item_type_mut","visit_trait_item_type_mut","visit_type_array_mut","visit_type_array_mut","visit_type_array_mut","visit_type_bare_fn_mut","visit_type_bare_fn_mut","visit_type_bare_fn_mut","visit_type_group_mut","visit_type_group_mut","visit_type_group_mut","visit_type_impl_trait_mut","visit_type_impl_trait_mut","visit_type_impl_trait_mut","visit_type_infer_mut","visit_type_infer_mut","visit_type_infer_mut","visit_type_macro_mut","visit_type_macro_mut","visit_type_macro_mut","visit_type_mut","visit_type_mut","visit_type_mut","visit_type_never_mut","visit_type_never_mut","visit_type_never_mut","visit_type_param_bound_mut","visit_type_param_bound_mut","visit_type_param_bound_mut","visit_type_param_mut","visit_type_param_mut","visit_type_param_mut","visit_type_paren_mut","visit_type_paren_mut","visit_type_paren_mut","visit_type_path_mut","visit_type_path_mut","visit_type_path_mut","visit_type_ptr_mut","visit_type_ptr_mut","visit_type_ptr_mut","visit_type_reference_mut","visit_type_reference_mut","visit_type_reference_mut","visit_type_slice_mut","visit_type_slice_mut","visit_type_slice_mut","visit_type_trait_object_mut","visit_type_trait_object_mut","visit_type_trait_object_mut","visit_type_tuple_mut","visit_type_tuple_mut","visit_type_tuple_mut","visit_un_op_mut","visit_un_op_mut","visit_un_op_mut","visit_use_glob_mut","visit_use_glob_mut","visit_use_glob_mut","visit_use_group_mut","visit_use_group_mut","visit_use_group_mut","visit_use_name_mut","visit_use_name_mut","visit_use_name_mut","visit_use_path_mut","visit_use_path_mut","visit_use_path_mut","visit_use_rename_mut","visit_use_rename_mut","visit_use_rename_mut","visit_use_tree_mut","visit_use_tree_mut","visit_use_tree_mut","visit_variadic_mut","visit_variadic_mut","visit_variadic_mut","visit_variant_mut","visit_variant_mut","visit_variant_mut","visit_vis_crate_mut","visit_vis_crate_mut","visit_vis_crate_mut","visit_vis_public_mut","visit_vis_public_mut","visit_vis_public_mut","visit_vis_restricted_mut","visit_vis_restricted_mut","visit_vis_restricted_mut","visit_visibility_mut","visit_visibility_mut","visit_visibility_mut","visit_where_clause_mut","visit_where_clause_mut","visit_where_clause_mut","visit_where_predicate_mut","visit_where_predicate_mut","visit_where_predicate_mut"],"q":["syn","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::buffer","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::ext","","","","syn::parse","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::parse::discouraged","","syn::punctuated","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::spanned","","syn::token","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","syn::visit_mut","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["The binary interface of a function: extern "C".","The + operator (addition)","The += operator","The && operator (logical and)","The <'a, T> in std::slice::iter<'a, T>.","Angle bracketed arguments of a path segment: the <K, V> in …","One arm of a match expression: 0...10 => { return true; }.","A slice literal expression: [a, b, c, d].","A fixed size array type: [T; n].","An assignment expression: a = compute().","A compound assignment expression: counter += 1.","An async block: async { ... }.","Distinguishes between attributes that decorate an item and …","An attribute like #[repr(transparent)].","Conventional argument type associated with an invocation …","An await expression: fut.await.","A bare function type: fn(usize) -> bool.","An argument in a function type: the usize in …","A binary operator: +, +=, &.","A binary operation: a + b, a * b.","A binding (equality constraint) on an associated type: …","A binding (equality constraint) on an associated type: the …","The & operator (bitwise and)","The &= operator","The | operator (bitwise or)","The |= operator","The ^ operator (bitwise xor)","The ^= operator","A braced block containing Rust statements.","A blocked scope: { ... }.","A boolean literal: true or false.","A set of bound lifetimes: for<'a, 'b, 'c>.","A box expression: box f.","A box pattern: box v.","","","A break, with an optional label to break and an optional …","A byte literal: b'f'.","A byte string literal: b"foo".","A function call expression: invoke(a, b).","A cast expression: foo as f64.","A character literal: 'a'.","Inclusive at the beginning and end.","A closure expression: |a, b| a + b.","A const expression. Must be inside of a block.","A const generic parameter: const LENGTH: usize.","A constant item: const MAX: u16 = 65535.","An associated constant within the definition of a trait.","An associated constant within an impl block.","A const expression. Must be inside of a block.","A const generic parameter: const LENGTH: usize.","An associated type bound: Iterator<Item: Display>.","An associated type bound: Iterator<Item: Display>.","A continue, with an optional label.","An ordinary string like "data".","A crate-level visibility: crate.","The storage of a struct, enum or union data structure.","An enum input to a proc_macro_derive macro.","A struct input to a proc_macro_derive macro.","An untagged union input to a proc_macro_derive macro.","Return type is not specified.","The * operator for dereferencing","Data structure sent to a proc_macro_derive macro.","The / operator (division)","The /= operator","An enum definition: enum Foo<A, B> { A(A), B(B) }.","An enum input to a proc_macro_derive macro.","An equality predicate in a where clause (unsupported).","The == operator (equality)","Error returned when a Syn parser cannot parse the input …","A Rust expression.","Expr without trailing semicolon.","A slice literal expression: [a, b, c, d].","An assignment expression: a = compute().","A compound assignment expression: counter += 1.","An async block: async { ... }.","An await expression: fut.await.","A binary operation: a + b, a * b.","A blocked scope: { ... }.","A box expression: box f.","A break, with an optional label to break and an optional …","A function call expression: invoke(a, b).","A cast expression: foo as f64.","A closure expression: |a, b| a + b.","A continue, with an optional label.","Access of a named struct field (obj.k) or unnamed tuple …","A for loop: for pat in expr { ... }.","An expression contained within invisible delimiters.","An if expression with an optional else block: …","A square bracketed indexing expression: vector[2].","A let guard: let Some(x) = opt.","A literal in place of an expression: 1, "foo".","Conditionless loop: loop { ... }.","A macro invocation expression: format!("{}", q).","A match expression: match n { Some(n) => {}, None => {} }.","A method call expression: x.foo::<T>(a, b).","A parenthesized expression: (a + b).","A path like std::mem::replace possibly containing generic …","A range expression: 1..2, 1.., ..2, 1..=2, ..=2.","A referencing operation: &a or &mut a.","An array literal constructed from one repeated element: …","A return, with an optional value to be returned.","A struct literal expression: Point { x: 1, y: 1 }.","A try-expression: expr?.","A try block: try { ... }.","A tuple expression: (a, b, c, d).","A type ascription expression: foo: f64.","A unary operation: !x, *x.","An unsafe block: unsafe { ... }.","A while loop: while expr { ... }.","A yield expression: yield expr.","An extern crate item: extern crate serde.","A field of a struct or enum variant.","Access of a named struct field (obj.k) or unnamed tuple …","A single field in a struct pattern.","A field-value pair in a struct literal.","Data stored within an enum variant or struct.","Named fields of a struct or struct variant such as …","Unnamed fields of a tuple struct or tuple variant such as …","A complete file of Rust source code.","A floating point literal: 1f64 or 1.0e10f64.","A free-standing function: …","A foreign function in an extern block.","An argument in a function signature: the n: usize in …","A for loop: for pat in expr { ... }.","An item within an extern block.","A foreign function in an extern block.","A macro invocation within an extern block.","A foreign static item in an extern block: static ext: u8.","A foreign type in an extern block: type void.","A block of foreign items: extern "C" { ... }.","The >= operator (greater than or equal to)","An individual generic argument, like 'a, T, or Item = T.","An individual generic argument to a method, like T.","A generic type parameter, lifetime, or const generic: …","Lifetimes and type parameters attached to a declaration of …","A glob import in a use item: *.","An expression contained within invisible delimiters.","A braced group of imports in a use item: {A, B, C}.","A type contained within invisible delimiters.","The > operator (greater than)","Inclusive at the beginning, exclusive at the end.","A word of Rust code, which may be a keyword or legal …","A pattern that binds a new variable: …","An if expression with an optional else block: …","An impl block providing trait or associated items: …","Returned by Generics::split_for_impl.","An item within an impl block.","An associated constant within an impl block.","A macro invocation within an impl block.","A method within an impl block.","An associated type within an impl block.","An impl Bound1 + Bound2 + Bound3 type where Bound is a …","The index of an unnamed tuple struct field.","A square bracketed indexing expression: vector[2].","Indication that a type should be inferred by the compiler: …","An inherited visibility, which usually means private.","","An integer literal: 1 or 1u16.","Things that can appear directly inside of a module or …","An item definition.","A constant item: const MAX: u16 = 65535.","An enum definition: enum Foo<A, B> { A(A), B(B) }.","An extern crate item: extern crate serde.","A free-standing function: …","A block of foreign items: extern "C" { ... }.","An impl block providing trait or associated items: …","A macro invocation, which includes macro_rules! …","A 2.0-style declarative macro introduced by the macro …","A module or module declaration: mod m or mod m { ... }.","A static item: static BIKE: Shed = Shed(42).","A struct definition: struct Foo<A> { x: A }.","A trait definition: pub trait Iterator { ... }.","A trait alias: pub trait SharableIterator = Iterator + Sync…","A type alias: …","A union definition: union Foo<A, B> { x: A, y: B }.","A use declaration: use std::collections::HashMap.","A lifetime labeling a for, while, or loop.","The <= operator (less than or equal to)","A let guard: let Some(x) = opt.","A Rust lifetime: 'a.","A lifetime definition: 'a: 'b + 'c + 'd.","","A lifetime predicate in a where clause: 'a: 'b + 'c.","A lifetime argument.","A lifetime definition: 'a: 'b + 'c + 'd.","A structured list within an attribute, like …","A Rust literal such as a string or integer or boolean.","A Rust literal, like the "new_name" in …","A literal in place of an expression: 1, "foo".","A literal pattern: 0.","A boolean literal: true or false.","A byte literal: b'f'.","A byte string literal: b"foo".","A character literal: 'a'.","A floating point literal: 1f64 or 1.0e10f64.","An integer literal: 1 or 1u16.","A UTF-8 string literal: "foo".","A local let binding: let x: u64 = s.parse()?.","A local (let) binding.","Conditionless loop: loop { ... }.","The < operator (less than)","A macro invocation: println!("{}", mac).","A macro invocation expression: format!("{}", q).","A macro invocation, which includes macro_rules! …","A macro invocation within an extern block.","A macro invocation within the definition of a trait.","A macro invocation within an impl block.","A macro in the type position.","A macro in pattern position.","A 2.0-style declarative macro introduced by the macro …","A grouping token that surrounds a macro body: m!(...) or …","A match expression: match n { Some(n) => {}, None => {} }.","","A struct or tuple struct field accessed in a struct …","Content of a compile-time structured attribute.","A structured meta item, like the Copy in #[derive(Copy)] …","A structured list within an attribute, like …","A name-value pair within an attribute, like …","A trait method within the definition of a trait.","A method within an impl block.","A method call expression: x.foo::<T>(a, b).","The ::<> explicit type parameters passed to a method call: …","A module or module declaration: mod m or mod m { ... }.","The * operator (multiplication)","The *= operator","An identifier imported by a use item: HashMap.","A name-value pair within an attribute, like …","Named fields of a struct or struct variant such as …","A named field like self.x.","The != operator (not equal to)","The - operator for negation","Element of a compile-time attribute list.","The never type: !.","","","The ! operator for logical inversion","The || operator (logical or)","A pattern that matches any one of a set of cases.","","A parenthesized expression: (a + b).","","A parenthesized type equivalent to the inner type.","The (A, B) -> C in Fn(A, B) -> C.","Arguments of a function path segment: the (A, B) -> C in …","A pattern in a local binding, function signature, match …","A box pattern: box v.","A pattern that binds a new variable: …","A literal pattern: 0.","A macro in pattern position.","A pattern that matches any one of a set of cases.","A path pattern like Color::Red, optionally qualified with a","A range pattern: 1..=2.","A reference pattern: &mut var.","The dots in a tuple or slice pattern: [0, 1, ..]","A dynamically sized slice pattern: [a, b, ref i @ .., y, z]…","A struct or struct variant pattern: Variant { x, y, .. }.","A tuple pattern: (a, b).","A tuple struct or tuple variant pattern: …","A type ascription pattern: foo: f64.","A pattern that matches any value: _.","A path at which a named item is exported (e.g. …","","A path like std::mem::replace possibly containing generic …","A path prefix of imports in a use item: std::....","A path like std::slice::Iter, optionally qualified with a …","A path pattern like Color::Red, optionally qualified with a","Angle bracketed or parenthesized arguments of a path …","A segment of a path together with any path arguments on …","An equality predicate in a where clause (unsupported).","A lifetime predicate in a where clause: 'a: 'b + 'c.","A type predicate in a where clause: …","A raw pointer type: *const T or *mut T.","A public visibility level: pub.","The explicit Self type in a qualified path: the T in …","A range expression: 1..2, 1.., ..2, 1..=2, ..=2.","A range pattern: 1..=2.","Limit types of a range, inclusive or exclusive.","A raw string like r##"data"##.","The self argument of an associated method, whether taken …","The self argument of an associated method, whether taken …","A referencing operation: &a or &mut a.","A reference type: &'a T or &'a mut T.","A reference pattern: &mut var.","The % operator (modulus)","The %= operator","An renamed identifier imported by a use item: …","An array literal constructed from one repeated element: …","The dots in a tuple or slice pattern: [0, 1, ..]","A visibility level restricted to some path: pub(self) or …","The result of a Syn parser.","A return, with an optional value to be returned.","Return type of a function signature.","Expression with trailing semicolon.","The << operator (shift left)","The <<= operator","The >> operator (shift right)","The >>= operator","A function signature in a trait or implementation: …","A dynamically sized slice type: [T].","A dynamically sized slice pattern: [a, b, ref i @ .., y, z]…","A static item: static BIKE: Shed = Shed(42).","A foreign static item in an extern block: static ext: u8.","A statement, usually ending in a semicolon.","A UTF-8 string literal: "foo".","The style of a string literal, either plain quoted or a …","A struct literal expression: Point { x: 1, y: 1 }.","A struct definition: struct Foo<A> { x: A }.","A struct input to a proc_macro_derive macro.","A struct or struct variant pattern: Variant { x, y, .. }.","The - operator (subtraction)","The -= operator","A type-macro that expands to the name of the Rust type …","","A trait definition: pub trait Iterator { ... }.","A trait alias: pub trait SharableIterator = Iterator + Sync…","A trait used as a bound on a type parameter.","A modifier on a trait bound, currently only used for the ? …","An item declaration within the definition of a trait.","An associated constant within the definition of a trait.","A macro invocation within the definition of a trait.","A trait method within the definition of a trait.","An associated type within the definition of a trait.","A trait object type dyn Bound1 + Bound2 + Bound3 where …","A try-expression: expr?.","A try block: try { ... }.","A tuple expression: (a, b, c, d).","A tuple type: (A, B, C, String).","A tuple pattern: (a, b).","A tuple struct or tuple variant pattern: …","Returned by TypeGenerics::as_turbofish.","The possible types that a Rust value could have.","A type ascription expression: foo: f64.","A type argument.","A generic type parameter: T: Into<String>.","A type predicate in a where clause: …","A type alias: …","A foreign type in an extern block: type void.","An associated type within the definition of a trait.","An associated type within an impl block.","A particular type is returned.","A type ascription pattern: foo: f64.","A type argument.","A fixed size array type: [T; n].","A bare function type: fn(usize) -> bool.","Returned by Generics::split_for_impl.","A type contained within invisible delimiters.","An impl Bound1 + Bound2 + Bound3 type where Bound is a …","Indication that a type should be inferred by the compiler: …","A macro in the type position.","The never type: !.","A generic type parameter: T: Into<String>.","A trait or lifetime used as a bound on a type parameter.","A parenthesized type equivalent to the inner type.","A path like std::slice::Iter, optionally qualified with a …","A raw pointer type: *const T or *mut T.","A reference type: &'a T or &'a mut T.","A dynamically sized slice type: [T].","A trait object type dyn Bound1 + Bound2 + Bound3 where …","A tuple type: (A, B, C, String).","A function argument accepted by pattern and type.","A unary operator: *, !, -.","A unary operation: !x, *x.","A union definition: union Foo<A, B> { x: A, y: B }.","An untagged union input to a proc_macro_derive macro.","Unit struct or unit variant such as None.","Unnamed fields of a tuple struct or tuple variant such as …","An unnamed field like self.0.","An unsafe block: unsafe { ... }.","A use declaration: use std::collections::HashMap.","A glob import in a use item: *.","A braced group of imports in a use item: {A, B, C}.","An identifier imported by a use item: HashMap.","A path prefix of imports in a use item: std::....","An renamed identifier imported by a use item: …","A suffix of an import tree in a use item: Type as Renamed …","The variadic argument of a foreign function.","An enum variant.","Tokens in expression position not interpreted by Syn.","Tokens forming an item not interpreted by Syn.","Tokens in an extern block not interpreted by Syn.","Tokens within the definition of a trait not interpreted by …","Tokens within an impl block not interpreted by Syn.","A raw token literal not interpreted by Syn.","Tokens in type position not interpreted by Syn.","Tokens in pattern position not interpreted by Syn.","A crate-level visibility: crate.","A public visibility level: pub.","A visibility level restricted to some path: pub(self) or …","The visibility level of an item: inherited or pub or …","A where clause in a definition: …","A single predicate in a where clause: T: Deserialize<'de>.","A while loop: while expr { ... }.","A pattern that matches any value: _.","A yield expression: yield expr.","","","","","","","","","","","","","","","","","Turn a type’s generics like <X, Y> into a turbofish like …","","","","Attributes tagged on the variant.","Attributes tagged on the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attributes tagged on the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attributes tagged on the whole struct or enum.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parses the literal into a selected number type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The type being bounded","","","Trait and lifetime bounds (Clone+Send+'static)","","","","","","","","","","","","","","","","","","","","Parse a set of curly braces and expose their content to …","","","","","","","","Parse a set of square brackets and expose their content to …","","A stably addressed token buffer supporting efficient …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The colon in Struct { x: x }. If written in shorthand like …","","","","","","","","","","","","","","","","","","","","Returns an Iterator<Item = &ConstParam> over the constant …","Returns an Iterator<Item = &mut ConstParam> over the …","","","","","","","","","","","Define a type that supports parsing and printing a given …","Define a type that supports parsing and printing a …","Data within the struct or enum.","","","","","","","","","","","","","","Explicit discriminant: Variant = 1","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Value of the field.","","","","","Extension traits to provide parsing methods on foreign …","","","","Content stored in the variant.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","Generics required to complete the definition.","If this path consists of a single ident, returns the ident.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Name of the variant.","Name of the field, if any.","","","","","","The example in macro_rules! example { ... }.","","","","","","","","","","","","","","","","","","","Name of the struct or enum.","","","","","","","","","","","","","","","","(A, B)","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Returns true if there are zero fields.","","Determines whether this is a path of length 1 equal to the …","","","","","","","Get an iterator over the borrowed Field items in this …","Get an iterator over the mutably borrowed Field items in …","","","","","","","","","","","","","Returns the number of fields.","","","","","","","","","","Returns an Iterator<Item = &LifetimeDef> over the lifetime …","","The for<'a> in for<'a> Foo<&'a T>","Any lifetimes from a for binding","","Returns an Iterator<Item = &mut LifetimeDef> over the …","","","","","","","","","","","","","","","","","","","","Initializes an empty where-clause if there is not one …","","","Name or index of the field.","","","","","","","","","","","","","","","","","","","Creates a new Ident with the given string as well as the …","Panics","","Interpret a Syn literal from a proc-macro2 literal.","","","","","","","","Same as Ident::new, but creates a raw identifier (r#ident…","","","","","","","","","C","","","","","","","","","","","","","","","","Parse a set of parentheses and expose their content to …","Parsing interface for parsing a token stream into a syntax …","Parse tokens of source code into the chosen syntax tree …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parse a syntax tree node from the content of this string …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Parse a proc-macro2 token stream into the chosen syntax …","","Parse the arguments to the attribute as a syntax tree.","Parse the arguments to the attribute using the given …","Parse the tokens within the macro invocation’s …","Parse the tokens within the macro invocation’s …","Parse the content of a file of Rust code.","Parses zero or more inner attributes from the stream.","Parse the input TokenStream of a macro, triggering a …","Parses the content of the attribute, consisting of the …","Parse a Path containing no path arguments on any of its …","Parses a named (braced struct) field.","Parses zero or more outer attributes from the stream.","Quasi-quotation macro that accepts input like the quote! …","This macro is parse_quote! + quote_spanned!.","Parse a string of Rust code into the chosen syntax tree …","Parses an unnamed (tuple struct) field.","Invoke parser on the content of this string literal.","Parse the body of a block as zero or more statements, …","An alternative to the primary Expr::parse parser (from the …","","","","","","","","","","","","Returns the identifier that begins this structured meta …","","","","","","","The Foo<&'a T> in for<'a> Foo<&'a T>","","","","","","","","","","","A punctuated sequence of syntax tree nodes separated by …","","","","","","A method’s self receiver, such as &self or …","","","","","","","","","","","","","","The Self type of the impl.","","","","","","","","","","","","","","","","","","","","","","","","","Configures the span of this Ident, possibly changing its …","","","","","","","","","","","","","","","Returns the span of this Ident.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A trait that can provide the Span of the complete contents …","Split a type’s generics into the pieces required for impl…","","","","","Statements in a block","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tokens representing Rust punctuation, keywords, and …","","","","","","","","","","Trait this impl implements.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Type of the field.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns an Iterator<Item = &TypeParam> over the type …","Returns an Iterator<Item = &mut TypeParam> over the type …","","","","","","","","","","","","","","","","","","","","","","","","","","","Visibility of the field.","","","","","","","","","","","","","","","","","","","","Visibility of the struct or enum.","Syntax tree traversal to mutate an exclusive borrow of a …","","","","In some positions, types may not contain the + character, …","","","","","A cheaply copyable cursor into a TokenBuffer.","A buffer that can be efficiently traversed multiple times, …","Creates a cursor referencing the first token in the buffer …","","","","","","","Creates a cursor referencing a static empty TokenStream.","Checks whether the cursor is currently pointing at the end …","","Returns the argument unchanged.","Returns the argument unchanged.","If the cursor is pointing at a Group with the given …","If the cursor is pointing at a Ident, returns it along …","Calls U::from(self).","Calls U::from(self).","If the cursor is pointing at a Lifetime, returns it along …","If the cursor is pointing at a Literal, return it along …","Creates a TokenBuffer containing all the tokens from the …","Creates a TokenBuffer containing all the tokens from the …","","If the cursor is pointing at a Punct, returns it along …","Returns the Span of the current token, or Span::call_site()…","","Copies all remaining tokens visible from this cursor into a","If the cursor is pointing at a TokenTree, returns it along …","","","","","","","Additional methods for Ident not provided by proc-macro2 …","Parses any identifier including keywords.","Peeks any identifier including keywords. Usage: …","Strips the raw marker r#, if any, from the beginning of an …","Error returned when a Syn parser cannot parse the input …","Support for checking the next token in a stream to decide …","An empty syntax tree node that consumes no tokens when …","","Parsing interface implemented by all types that can be …","Cursor position within a buffered token stream.","Input to a Syn parser function.","Parser that can parse Rust tokens into a particular syntax …","Types that can be parsed by looking at just one token.","The result of a Syn parser.","Cursor state associated with speculative parsing.","","","","","","","","","","","","Calls the given parser function to parse a syntax tree …","","","","","Add another error message to self such that when …","Provides low-level access to the token representation …","","Extensions to the parsing API with niche applicability.","","","Triggers an error at the current position of the parse …","Triggers an error at the current position of the parse …","Triggers an error at the current position of the parse …","","","","","","","Forks a parse stream so that parsing tokens out of either …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Render the error as an invocation of compile_error!.","","","Returns whether there are tokens remaining in this stream.","Constructs a helper for peeking at the next token in this …","Usually the ParseStream::error method will be used …","Creates an error with the specified message spanning the …","","Parses a syntax tree node of type T, advancing the …","Parse tokens of source code into the chosen syntax tree …","","Parse a proc-macro2 token stream into the chosen syntax …","Parse a string of Rust code into the chosen syntax tree …","Parses zero or more occurrences of T separated by …","Looks at the next token in the parse stream to determine …","Looks at the next token in the parse stream to determine …","Looks at the second-next token in the parse stream.","Looks at the third-next token in the parse stream.","","Returns the Span of the next token in the parse stream, or …","The source location of the error.","Speculatively parses tokens from this parse stream, …","Render the error as an invocation of compile_error!.","","","","","","","","","","","","","","","","","","","","Extensions to the ParseStream API to support speculative …","Advance this parse stream to the position of a forked …","","An iterator over owned values of type T.","An iterator over owned pairs of type Pair<T, P>.","An iterator over borrowed values of type &T.","An iterator over mutably borrowed values of type &mut T.","A single syntax tree node of type T followed by its …","An iterator over borrowed pairs of type Pair<&T, &P>.","An iterator over mutably borrowed pairs of type …","A punctuated sequence of syntax tree nodes of type T …","","","","","","","","","","","","","","","","","","Clears the sequence of all values and punctuation, making …","","","","","","","","","","","","","","Returns true if either this Punctuated is empty, or it has …","","","","Borrows the first element in this sequence.","Mutably borrows the first element in this sequence.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Inserts an element at position index.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","Returns an iterator over the contents of this sequence as …","Produces this punctuated pair as a tuple of syntax tree …","Extracts the syntax tree node from this punctuated pair, …","Determines whether this punctuated sequence is empty, …","Returns an iterator over borrowed syntax tree nodes of …","Returns an iterator over mutably borrowed syntax tree …","Borrows the last element in this sequence.","Mutably borrows the last element in this sequence.","Returns the number of syntax tree nodes in this punctuated …","","","","","","","Creates an empty punctuated sequence.","Creates a punctuated pair out of a syntax tree node and an …","","","","","","","","","","","","","Returns an iterator over the contents of this sequence as …","Returns an iterator over the contents of this sequence as …","Parses one or more occurrences of T separated by …","Parses one or more occurrences of T using the given parse …","Parses zero or more occurrences of T separated by …","Parses zero or more occurrences of T using the given parse …","Removes the last punctuated pair from this sequence, or …","Borrows the punctuation from this punctuated pair, unless …","Mutably borrows the punctuation from this punctuated pair, …","Appends a syntax tree node onto the end of this punctuated …","Appends a trailing punctuation onto the end of this …","Appends a syntax tree node onto the end of this punctuated …","","","","","","","","","","","","","","","","","Determines whether this punctuated sequence ends with a …","","","","","","","","","","","","","","","","","","","","","","","","","Borrows the syntax tree node from this punctuated pair.","Mutably borrows the syntax tree node from this punctuated …","A trait that can provide the Span of the complete contents …","Returns a Span covering the complete contents of this …","abstract","+","+=","&","&&","&=","as","async","@","auto","await","!","become","box","{...}","[...]","break","^","^=",":","::",",","const","continue","crate","default","/","/=","do","$",".","..","...","..=","dyn","else","enum","=","==","extern","=>","final","fn","for",">=","None-delimited group",">","if","impl","in","<-","<=","let","loop","<","macro","match","mod","move","*=","mut","!=","|","|=","||","override","(...)","#","priv","pub","?","->","ref","%","%=","return","Self","self",";","<<","<<=",">>",">>=","*","static","struct","-","-=","super","~","Marker trait for types that represent single tokens.","trait","try","type","typeof","_","union","unsafe","unsized","use","virtual","where","while","yield","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Syntax tree traversal to mutate an exclusive borrow of a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,142,142,142,185,0,0,25,147,25,25,25,0,0,0,25,147,0,0,25,0,186,142,142,142,142,142,142,0,25,129,0,25,166,136,136,25,129,129,25,25,129,72,25,68,74,87,115,120,186,0,0,186,25,338,21,0,0,0,0,165,143,0,142,142,87,138,83,142,0,0,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,87,0,25,0,0,0,0,0,0,129,87,110,0,25,0,0,0,0,0,87,142,0,0,0,0,104,25,104,147,142,72,0,166,25,87,0,0,0,0,0,0,147,0,25,147,21,11,129,0,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,25,0,74,79,83,186,0,12,0,15,25,166,0,0,0,0,0,0,0,0,145,25,142,0,25,87,110,115,120,147,166,87,0,25,81,0,0,15,0,0,115,120,25,0,87,142,142,104,12,17,65,142,143,0,147,81,185,143,142,166,11,25,136,147,185,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,25,104,147,166,0,0,0,0,0,147,21,0,25,166,0,338,0,126,25,147,166,142,142,104,25,166,21,0,25,0,145,142,142,142,142,0,147,166,87,110,0,129,0,25,87,138,166,142,142,0,79,87,87,0,0,0,0,0,0,0,147,25,25,25,147,166,166,0,0,25,68,74,83,87,110,115,120,165,166,186,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,25,87,138,17,17,65,25,87,0,0,0,0,0,0,0,0,25,87,110,115,120,129,147,166,0,0,0,0,0,0,25,166,25,92,125,149,53,158,174,9,35,49,67,187,184,48,36,107,191,1,29,37,125,16,20,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,69,71,75,76,77,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,111,112,113,114,116,117,118,119,121,122,123,124,127,128,137,146,163,164,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,99,30,135,154,30,39,3,5,3,5,29,32,58,62,91,122,37,40,46,63,71,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,84,75,76,84,85,100,118,151,160,189,33,167,18,48,56,89,92,93,99,109,140,144,177,0,10,26,43,54,148,159,176,0,34,0,168,29,37,171,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,9,67,105,187,20,60,69,70,75,76,77,84,85,88,97,99,112,116,118,121,180,182,189,71,42,63,73,73,77,88,116,121,157,125,96,38,23,90,0,0,137,73,78,185,75,77,116,117,118,93,121,122,123,135,16,56,175,177,30,39,49,164,160,148,150,155,157,158,159,26,59,161,176,178,42,89,140,7,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,14,27,44,75,77,86,88,97,100,101,121,123,188,33,34,36,40,41,43,44,48,50,53,54,55,57,60,61,64,69,88,97,121,169,0,90,162,71,16,56,98,102,139,141,177,7,7,7,8,1,2,9,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,3,5,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,125,149,40,78,7,7,7,7,7,7,7,8,1,2,9,10,11,12,12,12,12,13,14,15,15,15,16,17,17,17,18,19,20,21,21,21,21,22,23,24,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,65,65,66,66,67,68,69,70,71,72,73,74,74,74,74,75,75,76,77,78,79,79,79,80,81,82,83,83,83,83,84,85,86,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,104,104,104,104,104,105,106,107,108,109,110,110,110,110,110,111,112,113,114,115,115,115,115,115,116,117,118,119,120,120,120,120,120,121,122,123,124,125,126,126,126,127,128,129,129,129,129,129,129,129,129,130,131,132,133,3,3,5,5,134,338,135,136,137,137,137,137,138,138,138,138,139,140,141,142,143,144,145,146,147,147,147,147,147,147,147,147,147,147,147,147,147,147,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,183,184,184,185,186,187,188,189,190,191,52,35,89,93,98,99,100,101,102,118,123,125,137,183,41,150,67,73,78,187,191,71,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,173,9,16,20,75,77,88,89,90,94,95,96,97,98,99,100,101,102,105,106,107,112,113,116,118,121,123,125,137,168,184,188,189,42,93,151,24,40,43,66,146,37,125,149,190,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,17,17,17,17,185,183,185,92,93,99,109,128,17,17,32,34,38,40,46,63,103,183,171,27,28,31,17,54,148,44,146,86,127,76,85,158,73,78,80,84,149,73,52,173,14,45,173,46,67,73,78,187,191,47,94,114,119,124,153,170,95,73,48,39,69,182,49,96,80,37,53,97,112,127,157,158,168,174,70,162,163,18,13,7,9,76,129,130,131,132,133,3,5,134,7,28,31,61,37,37,37,125,149,190,73,13,19,24,35,49,50,59,80,125,149,155,161,178,190,0,0,0,7,9,12,13,14,15,16,18,19,21,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,130,131,132,133,3,5,134,135,137,142,143,144,145,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,165,166,183,184,186,187,188,189,190,0,7,10,10,135,135,0,10,0,10,183,20,10,0,0,0,20,130,144,25,7,9,40,44,71,146,167,174,179,180,182,12,10,13,14,24,51,56,80,135,156,172,177,179,191,10,82,22,24,0,51,156,172,57,53,125,49,127,90,107,56,55,86,27,28,31,95,183,127,93,96,54,88,90,94,97,98,100,101,103,111,112,113,114,116,117,118,119,121,123,124,139,146,148,7,9,129,130,131,132,133,3,5,134,128,91,111,117,122,7,7,7,8,1,2,9,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129,130,130,131,131,132,132,133,133,3,3,5,5,134,134,135,137,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,66,134,0,73,108,157,97,112,144,98,139,10,168,129,130,131,132,133,3,5,99,42,52,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,9,3,5,7,8,1,2,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,135,137,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,0,130,131,132,133,3,5,134,10,135,93,99,100,103,105,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,58,49,20,36,60,77,88,97,101,112,116,121,123,163,180,188,191,7,8,1,2,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,3,5,134,338,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,73,73,101,113,118,123,152,181,102,141,19,7,62,93,99,125,149,103,130,131,132,133,134,134,125,149,89,140,20,88,89,90,91,95,96,97,98,99,100,101,102,103,111,112,113,121,122,123,137,0,73,82,63,147,151,160,165,64,0,0,220,220,221,220,221,221,221,221,221,221,220,221,221,221,220,221,221,221,220,220,221,221,221,221,221,221,220,221,220,221,220,221,0,339,339,339,0,0,0,216,0,0,0,0,0,0,0,223,227,223,224,226,225,227,223,224,226,225,223,224,225,224,225,225,223,224,0,223,226,227,223,224,225,223,223,226,225,225,223,227,223,224,226,225,225,226,227,223,224,226,225,225,225,225,223,223,225,225,213,223,216,226,216,216,223,227,223,223,223,225,223,225,223,225,224,225,223,225,227,223,224,226,225,227,223,224,226,225,227,223,224,226,225,0,340,238,0,0,0,0,0,0,0,0,238,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,232,232,235,236,237,207,238,232,235,236,237,207,238,232,232,232,232,232,232,232,232,232,235,240,236,237,207,208,238,232,232,232,232,232,232,232,235,240,236,237,207,208,238,232,232,232,235,240,236,237,207,208,232,238,238,232,232,232,232,232,232,235,240,236,237,207,208,232,238,235,240,236,237,207,208,235,240,236,237,207,208,232,232,232,232,232,232,232,238,238,232,232,232,235,240,236,237,207,208,232,238,232,235,236,237,207,238,232,238,232,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,232,235,240,236,237,207,208,238,238,238,0,341,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,288,290,293,294,295,297,299,300,302,303,307,310,312,315,318,319,322,325,330,331,333,199,288,290,293,294,295,297,299,300,302,303,307,310,312,315,318,319,322,325,330,331,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,334,335,336,337,199,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,199,241,242,243,244,245,246,247,248,249,250,202,251,252,253,254,255,203,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,200,198,274,275,201,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,0,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342,0,342,342],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[3,4],[5,4],[3,6],[5,6],0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[7,7],[8,8],[1,1],[2,2],[9,9],[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[61,61],[62,62],[63,63],[64,64],[65,65],[66,66],[67,67],[68,68],[69,69],[70,70],[71,71],[72,72],[73,73],[74,74],[75,75],[76,76],[77,77],[78,78],[79,79],[80,80],[81,81],[82,82],[83,83],[84,84],[85,85],[86,86],[87,87],[88,88],[89,89],[90,90],[91,91],[92,92],[93,93],[94,94],[95,95],[96,96],[97,97],[98,98],[99,99],[100,100],[101,101],[102,102],[103,103],[104,104],[105,105],[106,106],[107,107],[108,108],[109,109],[110,110],[111,111],[112,112],[113,113],[114,114],[115,115],[116,116],[117,117],[118,118],[119,119],[120,120],[121,121],[122,122],[123,123],[124,124],[125,125],[126,126],[127,127],[128,128],[129,129],[130,130],[131,131],[132,132],[133,133],[3,3],[5,5],[134,134],[135,135],[136,136],[137,137],[138,138],[139,139],[140,140],[141,141],[142,142],[143,143],[144,144],[145,145],[146,146],[147,147],[148,148],[149,149],[150,150],[151,151],[152,152],[153,153],[154,154],[155,155],[156,156],[157,157],[158,158],[159,159],[160,160],[161,161],[162,162],[163,163],[164,164],[165,165],[166,166],[167,167],[168,168],[169,169],[170,170],[171,171],[172,172],[173,173],[174,174],[175,175],[176,176],[177,177],[178,178],[179,179],[180,180],[181,181],[182,182],[183,183],[184,184],[185,185],[186,186],[187,187],[188,188],[189,189],[190,190],[191,191],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[7,7],192],[[9,9],192],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],73],[[],78],[[],185],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,7],193],[7,193],[[8,8],193],[[1,1],193],[[2,2],193],[[9,9],193],[[10,10],193],[[11,11],193],[[12,12],193],[[13,13],193],[[14,14],193],[[15,15],193],[[16,16],193],[[17,17],193],[[18,18],193],[[19,19],193],[[20,20],193],[[21,21],193],[[22,22],193],[[23,23],193],[[24,24],193],[[25,25],193],[[26,26],193],[[27,27],193],[[28,28],193],[[29,29],193],[[30,30],193],[[31,31],193],[[32,32],193],[[33,33],193],[[34,34],193],[[35,35],193],[[36,36],193],[[37,37],193],[[38,38],193],[[39,39],193],[[40,40],193],[[41,41],193],[[42,42],193],[[43,43],193],[[44,44],193],[[45,45],193],[[46,46],193],[[47,47],193],[[48,48],193],[[49,49],193],[[50,50],193],[[51,51],193],[[52,52],193],[[53,53],193],[[54,54],193],[[55,55],193],[[56,56],193],[[57,57],193],[[58,58],193],[[59,59],193],[[60,60],193],[[61,61],193],[[62,62],193],[[63,63],193],[[64,64],193],[[65,65],193],[[66,66],193],[[67,67],193],[[68,68],193],[[69,69],193],[[70,70],193],[[71,71],193],[[72,72],193],[[73,73],193],[[74,74],193],[[75,75],193],[[76,76],193],[[77,77],193],[[78,78],193],[[79,79],193],[[80,80],193],[[81,81],193],[[82,82],193],[[83,83],193],[[84,84],193],[[85,85],193],[[86,86],193],[[87,87],193],[[88,88],193],[[89,89],193],[[90,90],193],[[91,91],193],[[92,92],193],[[93,93],193],[[94,94],193],[[95,95],193],[[96,96],193],[[97,97],193],[[98,98],193],[[99,99],193],[[100,100],193],[[101,101],193],[[102,102],193],[[103,103],193],[[104,104],193],[[105,105],193],[[106,106],193],[[107,107],193],[[108,108],193],[[109,109],193],[[110,110],193],[[111,111],193],[[112,112],193],[[113,113],193],[[114,114],193],[[115,115],193],[[116,116],193],[[117,117],193],[[118,118],193],[[119,119],193],[[120,120],193],[[121,121],193],[[122,122],193],[[123,123],193],[[124,124],193],[[125,125],193],[[126,126],193],[[127,127],193],[[128,128],193],[[129,129],193],[[130,130],193],[[131,131],193],[[132,132],193],[[133,133],193],[[3,3],193],[[5,5],193],[[134,134],193],[[135,135],193],[[136,136],193],[[137,137],193],[[138,138],193],[[139,139],193],[[140,140],193],[[141,141],193],[[142,142],193],[[143,143],193],[[144,144],193],[[145,145],193],[[146,146],193],[[147,147],193],[[148,148],193],[[149,149],193],[[150,150],193],[[151,151],193],[[152,152],193],[[153,153],193],[[154,154],193],[[155,155],193],[[156,156],193],[[157,157],193],[[158,158],193],[[159,159],193],[[160,160],193],[[161,161],193],[[162,162],193],[[163,163],193],[[164,164],193],[[165,165],193],[[166,166],193],[[167,167],193],[[168,168],193],[[169,169],193],[[170,170],193],[[171,171],193],[[172,172],193],[[173,173],193],[[174,174],193],[[175,175],193],[[176,176],193],[[177,177],193],[[178,178],193],[[179,179],193],[[180,180],193],[[181,181],193],[[182,182],193],[[183,183],193],[[184,184],193],[[185,185],193],[[186,186],193],[[187,187],193],[[188,188],193],[[189,189],193],[[190,190],193],[[191,191],193],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,194],[[196,[195]]]],[[7,194],[[196,[195]]]],[[7,194],[[196,[195]]]],[[8,194],197],[[1,194],197],[[2,194],197],[[9,194],197],[[9,194],197],[[10,194],197],[[11,194],197],[[12,194],197],[[13,194],197],[[14,194],197],[[15,194],197],[[16,194],197],[[17,194],197],[[18,194],197],[[19,194],197],[[20,194],197],[[21,194],197],[[22,194],197],[[23,194],197],[[24,194],197],[[25,194],197],[[26,194],197],[[27,194],197],[[28,194],197],[[29,194],197],[[30,194],197],[[31,194],197],[[32,194],197],[[33,194],197],[[34,194],197],[[35,194],197],[[36,194],197],[[37,194],197],[[38,194],197],[[39,194],197],[[40,194],197],[[41,194],197],[[42,194],197],[[43,194],197],[[44,194],197],[[45,194],197],[[46,194],197],[[47,194],197],[[48,194],197],[[49,194],197],[[50,194],197],[[51,194],197],[[52,194],197],[[53,194],197],[[54,194],197],[[55,194],197],[[56,194],197],[[57,194],197],[[58,194],197],[[59,194],197],[[60,194],197],[[61,194],197],[[62,194],197],[[63,194],197],[[64,194],197],[[65,194],197],[[65,194],197],[[66,194],197],[[66,194],197],[[67,194],197],[[68,194],197],[[69,194],197],[[70,194],197],[[71,194],197],[[72,194],197],[[73,194],197],[[74,194],197],[[75,194],197],[[76,194],197],[[77,194],197],[[78,194],197],[[79,194],197],[[80,194],197],[[81,194],197],[[82,194],197],[[83,194],197],[[84,194],197],[[85,194],197],[[86,194],197],[[87,194],197],[[88,194],197],[[89,194],197],[[90,194],197],[[91,194],197],[[92,194],197],[[93,194],197],[[94,194],197],[[95,194],197],[[96,194],197],[[97,194],197],[[98,194],197],[[99,194],197],[[100,194],197],[[101,194],197],[[102,194],197],[[103,194],197],[[104,194],197],[[105,194],197],[[106,194],197],[[107,194],197],[[108,194],197],[[109,194],197],[[110,194],197],[[111,194],197],[[112,194],197],[[113,194],197],[[114,194],197],[[115,194],197],[[116,194],197],[[117,194],197],[[118,194],197],[[119,194],197],[[120,194],197],[[121,194],197],[[122,194],197],[[123,194],197],[[124,194],197],[[125,194],197],[[126,194],197],[[127,194],197],[[128,194],197],[[129,194],197],[[130,194],197],[[131,194],197],[[132,194],197],[[133,194],197],[[3,194],197],[[3,194],197],[[5,194],197],[[5,194],197],[[134,194],197],[[135,194],197],[[136,194],197],[[137,194],197],[[138,194],197],[[139,194],197],[[140,194],197],[[141,194],197],[[142,194],197],[[143,194],197],[[144,194],197],[[145,194],197],[[146,194],197],[[147,194],197],[[148,194],197],[[149,194],197],[[150,194],197],[[151,194],197],[[152,194],197],[[153,194],197],[[154,194],197],[[155,194],197],[[156,194],197],[[157,194],197],[[158,194],197],[[159,194],197],[[160,194],197],[[161,194],197],[[162,194],197],[[163,194],197],[[164,194],197],[[165,194],197],[[166,194],197],[[167,194],197],[[168,194],197],[[169,194],197],[[170,194],197],[[171,194],197],[[172,194],197],[[173,194],197],[[174,194],197],[[175,194],197],[[176,194],197],[[177,194],197],[[178,194],197],[[179,194],197],[[180,194],197],[[181,194],197],[[182,194],197],[[183,194],197],[[184,194],197],[[185,194],197],[[186,194],197],[[187,194],197],[[188,194],197],[[189,194],197],[[190,194],197],[[191,194],197],0,0,0,0,[198,7],[199,7],[200,7],[201,7],[202,7],[203,7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[183,12],[13,12],[14,12],[[]],[[]],[[]],[12,15],[129,15],[[]],[[]],[18,17],[19,17],[[]],[[]],[[]],[[]],[23,21],[24,21],[[]],[22,21],[[]],[[]],[[]],[26,25],[35,25],[62,25],[63,25],[64,25],[30,25],[29,25],[51,25],[27,25],[55,25],[31,25],[56,25],[32,25],[54,25],[53,25],[52,25],[50,25],[49,25],[[]],[48,25],[47,25],[60,25],[59,25],[58,25],[57,25],[46,25],[33,25],[40,25],[61,25],[34,25],[45,25],[28,25],[36,25],[37,25],[38,25],[44,25],[39,25],[41,25],[43,25],[42,25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[204,65],[[]],[66,65],[7,65],[204,66],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[76,74],[77,74],[75,74],[[]],[7,75],[[]],[[]],[[]],[[]],[9,79],[[]],[80,79],[[]],[[]],[[]],[86,83],[85,83],[84,83],[[]],[[]],[[]],[[]],[89,87],[95,87],[98,87],[99,87],[88,87],[100,87],[101,87],[90,87],[91,87],[92,87],[102,87],[[]],[93,87],[94,87],[103,87],[97,87],[137,87],[96,87],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[107,104],[108,104],[109,104],[106,104],[[]],[105,104],[[]],[[]],[[]],[[]],[[]],[114,110],[113,110],[112,110],[[]],[111,110],[[]],[[]],[[]],[[]],[[]],[116,115],[119,115],[118,115],[117,115],[[]],[[]],[[]],[[]],[[]],[122,120],[123,120],[124,120],[121,120],[[]],[[]],[[]],[[]],[[]],[180,126],[127,126],[[]],[[]],[[]],[134,129],[[]],[132,129],[133,129],[3,129],[131,129],[5,129],[130,129],[[]],[[]],[[]],[[]],[205,3],[[]],[205,5],[[]],[[]],[[]],[[]],[[]],[89,137],[[]],[102,137],[98,137],[141,138],[140,138],[139,138],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[159,147],[161,147],[160,147],[[]],[155,147],[158,147],[157,147],[156,147],[153,147],[154,147],[148,147],[149,147],[150,147],[151,147],[152,147],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[171,166],[168,166],[181,166],[180,166],[179,166],[178,166],[177,166],[176,166],[169,166],[175,166],[170,166],[174,166],[173,166],[167,166],[[]],[172,166],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],183],[[]],[[],184],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,[183,[[206,[7]]]],0,0,0,0,0,0,0,0,[7],[8],[1],[2],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31],[32],[33],[34],[35],[36],[37],[38],[39],[40],[41],[42],[43],[44],[45],[46],[47],[48],[49],[50],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60],[61],[62],[63],[64],[65],[66],[67],[68],[69],[70],[71],[72],[73],[74],[75],[76],[77],[78],[79],[80],[81],[82],[83],[84],[85],[86],[87],[88],[89],[90],[91],[92],[93],[94],[95],[96],[97],[98],[99],[100],[101],[102],[103],[104],[105],[106],[107],[108],[109],[110],[111],[112],[113],[114],[115],[116],[117],[118],[119],[120],[121],[122],[123],[124],[125],[126],[127],[128],[129],[130],[131],[132],[133],[3],[5],[134],[135],[136],[137],[138],[139],[140],[141],[142],[143],[144],[145],[146],[147],[148],[149],[150],[151],[152],[153],[154],[155],[156],[157],[158],[159],[160],[161],[162],[163],[164],[165],[166],[167],[168],[169],[170],[171],[172],[173],[174],[175],[176],[177],[178],[179],[180],[181],[182],[183],[184],[185],[186],[187],[188],[189],[190],[191],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[17],[17],[17],[17,193],[185,193],[183,193],[185,193],0,0,0,0,0,[17,[[207,[20]]]],[17,[[208,[20]]]],0,0,0,0,0,0,0,0,0,0,0,0,[17,204],0,0,0,0,0,[127,[[206,[9]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[73,82],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[4,209],7],[[4,209],9],[9,76],[205,129],[[4,209],130],[209,131],[[210,209],132],[[211,209],133],[[4,209],3],[[4,209],5],[[193,209],134],[[4,209],7],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[212,[[6,[213]]]],[214,[[6,[7]]]],[214,[[6,[9]]]],[214,[[6,[12]]]],[214,[[6,[13]]]],[214,[[6,[14]]]],[214,[[6,[15]]]],[214,[[6,[16]]]],[214,[[6,[18]]]],[214,[[6,[19]]]],[214,[[6,[21]]]],[214,[[6,[25]]]],[214,[[6,[26]]]],[214,[[6,[27]]]],[214,[[6,[28]]]],[214,[[6,[29]]]],[214,[[6,[30]]]],[214,[[6,[31]]]],[214,[[6,[32]]]],[214,[[6,[33]]]],[214,[[6,[34]]]],[214,[[6,[35]]]],[214,[[6,[36]]]],[214,[[6,[37]]]],[214,[[6,[38]]]],[214,[[6,[39]]]],[214,[[6,[40]]]],[214,[[6,[42]]]],[214,[[6,[43]]]],[214,[[6,[44]]]],[214,[[6,[45]]]],[214,[[6,[46]]]],[214,[[6,[47]]]],[214,[[6,[48]]]],[214,[[6,[49]]]],[214,[[6,[50]]]],[214,[[6,[51]]]],[214,[[6,[52]]]],[214,[[6,[53]]]],[214,[[6,[54]]]],[214,[[6,[55]]]],[214,[[6,[56]]]],[214,[[6,[57]]]],[214,[[6,[58]]]],[214,[[6,[59]]]],[214,[[6,[60]]]],[214,[[6,[61]]]],[214,[[6,[62]]]],[214,[[6,[63]]]],[214,[[6,[64]]]],[214,[[6,[65]]]],[214,[[6,[66]]]],[214,[[6,[67]]]],[214,[[6,[68]]]],[214,[[6,[69]]]],[214,[[6,[70]]]],[214,[[6,[71]]]],[214,[[6,[72]]]],[214,[[6,[73]]]],[214,[[6,[74]]]],[214,[[6,[75]]]],[214,[[6,[76]]]],[214,[[6,[77]]]],[214,[[6,[78]]]],[214,[[6,[79]]]],[214,[[6,[80]]]],[214,[[6,[81]]]],[214,[[6,[82]]]],[214,[[6,[83]]]],[214,[[6,[87]]]],[214,[[6,[88]]]],[214,[[6,[89]]]],[214,[[6,[90]]]],[214,[[6,[91]]]],[214,[[6,[92]]]],[214,[[6,[93]]]],[214,[[6,[94]]]],[214,[[6,[95]]]],[214,[[6,[96]]]],[214,[[6,[97]]]],[214,[[6,[98]]]],[214,[[6,[99]]]],[214,[[6,[100]]]],[214,[[6,[101]]]],[214,[[6,[102]]]],[214,[[6,[103]]]],[214,[[6,[104]]]],[214,[[6,[110]]]],[214,[[6,[111]]]],[214,[[6,[112]]]],[214,[[6,[113]]]],[214,[[6,[114]]]],[214,[[6,[115]]]],[214,[[6,[116]]]],[214,[[6,[117]]]],[214,[[6,[118]]]],[214,[[6,[119]]]],[214,[[6,[120]]]],[214,[[6,[121]]]],[214,[[6,[122]]]],[214,[[6,[123]]]],[214,[[6,[124]]]],[214,[[6,[125]]]],[214,[[6,[126]]]],[214,[[6,[127]]]],[214,[[6,[128]]]],[214,[[6,[129]]]],[130,[[6,[213]]]],[214,[[6,[130]]]],[214,[[6,[131]]]],[214,[[6,[132]]]],[214,[[6,[133]]]],[214,[[6,[3]]]],[214,[[6,[5]]]],[214,[[6,[134]]]],[214,[[6,[135]]]],[214,[[6,[137]]]],[214,[[6,[142]]]],[214,[[6,[143]]]],[214,[[6,[144]]]],[214,[[6,[145]]]],[214,[[6,[147]]]],[214,[[6,[148]]]],[214,[[6,[149]]]],[214,[[6,[150]]]],[214,[[6,[151]]]],[214,[[6,[152]]]],[214,[[6,[153]]]],[214,[[6,[154]]]],[214,[[6,[155]]]],[214,[[6,[156]]]],[214,[[6,[157]]]],[214,[[6,[158]]]],[214,[[6,[159]]]],[214,[[6,[160]]]],[214,[[6,[161]]]],[214,[[6,[162]]]],[214,[[6,[163]]]],[214,[[6,[165]]]],[214,[[6,[166]]]],[214,[[6,[183]]]],[214,[[6,[184]]]],[214,[[6,[186]]]],[214,[[6,[187]]]],[214,[[6,[188]]]],[214,[[6,[189]]]],[214,[[6,[190]]]],[215,[[6,[213]]]],[214,[[6,[7]]]],[10,[[6,[213]]]],[[10,216],6],[135,[[6,[213]]]],[[135,216],6],[4,[[6,[128]]]],[214,[[6,[[217,[10]]]]]],0,[10,[[6,[12]]]],[214,[[6,[183]]]],[214,[[6,[20]]]],[214,[[6,[[217,[10]]]]]],0,0,[4,[[6,[213]]]],[214,[[6,[20]]]],[[130,216],6],[214,[[6,[[217,[145]]]]]],[214,[[6,[25]]]],[[7,7],[[206,[192]]]],[[9,9],[[206,[192]]]],0,0,0,0,0,0,0,0,0,[12,183],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[125,[[206,[126]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[7,209]],[[9,209]],[[129,209]],[[130,209]],[[131,209]],[[132,209]],[[133,209]],[[3,209]],[[5,209]],[[134,209]],0,0,0,0,0,[7,209],[7,[[206,[209]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[9,209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[65,[[206,[209]]]],[[],209],[66,[[206,[209]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[129,209],[[],209],[130,209],[131,209],[[],209],[[],209],[132,209],[[],209],[133,209],[3,209],[[],209],[5,209],[[],209],[134,209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],0,0,0,[73],0,0,0,0,0,0,0,0,0,[129,4],[130,4],[131,4],[132,4],[133,4],[3,4],[5,4],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],218],[[],218],[[],218],[[],218],[[7,215]],[[8,215]],[[1,215]],[[2,215]],[[9,215]],[[10,215]],[[12,215]],[[13,215]],[[14,215]],[[15,215]],[[16,215]],[[17,215]],[[18,215]],[[19,215]],[[20,215]],[[21,215]],[[22,215]],[[23,215]],[[24,215]],[[25,215]],[[26,215]],[[27,215]],[[28,215]],[[29,215]],[[30,215]],[[31,215]],[[32,215]],[[33,215]],[[34,215]],[[35,215]],[[36,215]],[[37,215]],[[38,215]],[[39,215]],[[40,215]],[[41,215]],[[42,215]],[[43,215]],[[44,215]],[[45,215]],[[46,215]],[[47,215]],[[48,215]],[[49,215]],[[50,215]],[[51,215]],[[52,215]],[[53,215]],[[54,215]],[[55,215]],[[56,215]],[[57,215]],[[58,215]],[[59,215]],[[60,215]],[[61,215]],[[62,215]],[[63,215]],[[64,215]],[[65,215]],[[66,215]],[[67,215]],[[68,215]],[[69,215]],[[70,215]],[[71,215]],[[72,215]],[[73,215]],[[74,215]],[[75,215]],[[76,215]],[[77,215]],[[78,215]],[[79,215]],[[80,215]],[[81,215]],[[82,215]],[[83,215]],[[84,215]],[[85,215]],[[86,215]],[[87,215]],[[88,215]],[[89,215]],[[90,215]],[[91,215]],[[92,215]],[[93,215]],[[94,215]],[[95,215]],[[96,215]],[[97,215]],[[98,215]],[[99,215]],[[100,215]],[[101,215]],[[102,215]],[[103,215]],[[104,215]],[[105,215]],[[106,215]],[[107,215]],[[108,215]],[[109,215]],[[110,215]],[[111,215]],[[112,215]],[[113,215]],[[114,215]],[[115,215]],[[116,215]],[[117,215]],[[118,215]],[[119,215]],[[120,215]],[[121,215]],[[122,215]],[[123,215]],[[124,215]],[[125,215]],[[126,215]],[[127,215]],[[128,215]],[[129,215]],[[130,215]],[[131,215]],[[132,215]],[[133,215]],[[3,215]],[[5,215]],[[134,215]],[[135,215]],[[137,215]],[[142,215]],[[143,215]],[[144,215]],[[145,215]],[[146,215]],[[147,215]],[[148,215]],[[149,215]],[[150,215]],[[151,215]],[[152,215]],[[153,215]],[[154,215]],[[155,215]],[[156,215]],[[157,215]],[[158,215]],[[159,215]],[[160,215]],[[161,215]],[[162,215]],[[163,215]],[[164,215]],[[165,215]],[[166,215]],[[167,215]],[[168,215]],[[169,215]],[[170,215]],[[171,215]],[[172,215]],[[173,215]],[[174,215]],[[175,215]],[[176,215]],[[177,215]],[[178,215]],[[179,215]],[[180,215]],[[181,215]],[[182,215]],[[183,215]],[[184,215]],[[185,215]],[[186,215]],[[187,215]],[[188,215]],[[189,215]],[[190,215]],0,[130,205],[131,205],[132,205],[133,205],[3,205],[5,205],[134,7],0,0,0,0,0,0,0,[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],0,0,0,0,0,0,0,0,0,0,0,[7,7],0,0,0,0,0,0,[130,218],[131,[[217,[210]]]],[132,210],[133,211],[134,193],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[214,[[6,[147]]]],[214,[[6,[151]]]],[214,[[6,[160]]]],[214,[[6,[165]]]],0,0,0,[220,221],[[]],[[]],[[]],[[]],[221,221],[[]],[[],221],[221,193],[[221,221],193],[[]],[[]],[[221,222],206],[221,206],[[]],[[]],[221,206],[221,206],[212,220],[215,220],[[221,221],[[206,[192]]]],[221,206],[221,209],[[]],[221,215],[221,206],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],0,[214,6],0,[[],7],0,0,0,0,0,0,0,0,0,0,0,[[223,223]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[223,6],[224,224],[225,225],[[]],[[]],[[225,225]],[223,221],[224],0,[223],[[226,226],193],[227,225],[[223,228],225],[[224,228],225],[[225,229]],[[223,194],197],[[223,194],197],[[226,194],197],[[225,194],197],[[225,194],197],[223,223],[[]],[[]],[[]],[[]],[230,225],[[]],[226],[[]],[[]],[[]],[[]],[[]],[225,215],[225],[225],[223,193],[223,227],[[209,228],225],[[231,228],225],[214,6],[223,[[6,[213]]]],[212,6],[214,[[6,[226]]]],[215,6],[4,6],[223,[[6,[[232,[213]]]]]],[[227,233],193],[[223,233],193],[[223,233],193],[[223,233],193],[234],[223,209],[225,209],[223,6],[225,215],[[]],[[]],[[],218],[[],218],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],0,[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[232],[232,232],[235,235],[236,236],[237,237],[207,207],[238,238],[[]],[[]],[[]],[[]],[[]],[[]],[[],232],[232,193],[[232,232],193],[[232,229]],[[232,229]],[232,206],[232,206],[[[232,[239,239]],194],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[229,232],[229,232],[232],[[232,204]],[[232,204]],[[232,204]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[232],[232],[232],[[]],[[]],[[]],[[]],[[]],[[]],[232,236],[238],[238],[232,193],[232,207],[232,208],[232,206],[232,206],[232,204],[235,204],[240,204],[236,204],[237,204],[207,204],[208,204],[[],232],[206,238],[235,206],[240,206],[236,206],[237,206],[207,206],[208,206],[235,206],[240,206],[236,206],[237,206],[207,206],[208,206],[232,235],[232,240],[214,[[6,[232]]]],[214,[[6,[232]]]],[214,[[6,[232]]]],[214,[[6,[232]]]],[232,[[206,[238]]]],[238,206],[238,206],[232],[232],[232],[235],[240],[236],[237],[207],[208],[[],209],[[],209],[[]],[[]],[[]],[[]],[[]],[[]],[[232,215]],[[238,215]],[232,193],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[238],[238],0,[[],209],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[199,199],[241,241],[242,242],[243,243],[244,244],[245,245],[246,246],[247,247],[248,248],[249,249],[250,250],[202,202],[251,251],[252,252],[253,253],[254,254],[255,255],[203,203],[256,256],[257,257],[258,258],[259,259],[260,260],[261,261],[262,262],[263,263],[264,264],[265,265],[266,266],[267,267],[268,268],[269,269],[270,270],[271,271],[272,272],[273,273],[200,200],[198,198],[274,274],[275,275],[201,201],[276,276],[277,277],[278,278],[279,279],[280,280],[281,281],[282,282],[283,283],[284,284],[285,285],[286,286],[287,287],[288,288],[289,289],[290,290],[291,291],[292,292],[293,293],[294,294],[295,295],[296,296],[297,297],[298,298],[299,299],[300,300],[301,301],[302,302],[303,303],[304,304],[305,305],[306,306],[307,307],[308,308],[309,309],[310,310],[311,311],[312,312],[313,313],[314,314],[315,315],[316,316],[317,317],[318,318],[319,319],[320,320],[321,321],[322,322],[323,323],[324,324],[325,325],[326,326],[327,327],[328,328],[329,329],[330,330],[331,331],[332,332],[333,333],[334,334],[335,335],[336,336],[337,337],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],199],[[],241],[[],242],[[],243],[[],244],[[],245],[[],246],[[],247],[[],248],[[],249],[[],250],[[],202],[[],251],[[],252],[[],253],[[],254],[[],255],[[],203],[[],256],[[],257],[[],258],[[],259],[[],260],[[],261],[[],262],[[],263],[[],264],[[],265],[[],266],[[],267],[[],268],[[],269],[[],270],[[],271],[[],272],[[],273],[[],200],[[],198],[[],274],[[],275],[[],201],[[],276],[[],277],[[],278],[[],279],[[],280],[[],281],[[],282],[[],283],[[],284],[[],285],[[],286],[[],287],[[],288],[[],289],[[],290],[[],291],[[],292],[[],293],[[],294],[[],295],[[],296],[[],297],[[],298],[[],299],[[],300],[[],301],[[],302],[[],303],[[],304],[[],305],[[],306],[[],307],[[],308],[[],309],[[],310],[[],311],[[],312],[[],313],[[],314],[[],315],[[],316],[[],317],[[],318],[[],319],[[],320],[[],321],[[],322],[[],323],[[],324],[[],325],[[],326],[[],327],[[],328],[[],329],[[],330],[[],331],[[],332],[[],333],[[],334],[[],335],[[],336],[[],337],[199],[288],[290],[293],[294],[295],[297],[299],[300],[302],[303],[307],[310],[312],[315],[318],[319],[322],[325],[330],[331],[333],[199],[288],[290],[293],[294],[295],[297],[299],[300],[302],[303],[307],[310],[312],[315],[318],[319],[322],[325],[330],[331],[333],[[199,199],193],[[241,241],193],[[242,242],193],[[243,243],193],[[244,244],193],[[245,245],193],[[246,246],193],[[247,247],193],[[248,248],193],[[249,249],193],[[250,250],193],[[202,202],193],[[251,251],193],[[252,252],193],[[253,253],193],[[254,254],193],[[255,255],193],[[203,203],193],[[256,256],193],[[257,257],193],[[258,258],193],[[259,259],193],[[260,260],193],[[261,261],193],[[262,262],193],[[263,263],193],[[264,264],193],[[265,265],193],[[266,266],193],[[267,267],193],[[268,268],193],[[269,269],193],[[270,270],193],[[271,271],193],[[272,272],193],[[273,273],193],[[200,200],193],[[198,198],193],[[274,274],193],[[275,275],193],[[201,201],193],[[276,276],193],[[277,277],193],[[278,278],193],[[279,279],193],[[280,280],193],[[281,281],193],[[282,282],193],[[283,283],193],[[284,284],193],[[285,285],193],[[286,286],193],[[287,287],193],[[288,288],193],[[289,289],193],[[290,290],193],[[291,291],193],[[292,292],193],[[293,293],193],[[294,294],193],[[295,295],193],[[296,296],193],[[297,297],193],[[298,298],193],[[299,299],193],[[300,300],193],[[301,301],193],[[302,302],193],[[303,303],193],[[304,304],193],[[305,305],193],[[306,306],193],[[307,307],193],[[308,308],193],[[309,309],193],[[310,310],193],[[311,311],193],[[312,312],193],[[313,313],193],[[314,314],193],[[315,315],193],[[316,316],193],[[317,317],193],[[318,318],193],[[319,319],193],[[320,320],193],[[321,321],193],[[322,322],193],[[323,323],193],[[324,324],193],[[325,325],193],[[326,326],193],[[327,327],193],[[328,328],193],[[329,329],193],[[330,330],193],[[331,331],193],[[332,332],193],[[333,333],193],[[334,334],193],[[335,335],193],[[336,336],193],[[337,337],193],[[199,194],197],[[241,194],197],[[242,194],197],[[243,194],197],[[244,194],197],[[245,194],197],[[246,194],197],[[247,194],197],[[248,194],197],[[249,194],197],[[250,194],197],[[202,194],197],[[251,194],197],[[252,194],197],[[253,194],197],[[254,194],197],[[255,194],197],[[203,194],197],[[256,194],197],[[257,194],197],[[258,194],197],[[259,194],197],[[260,194],197],[[261,194],197],[[262,194],197],[[263,194],197],[[264,194],197],[[265,194],197],[[266,194],197],[[267,194],197],[[268,194],197],[[269,194],197],[[270,194],197],[[271,194],197],[[272,194],197],[[273,194],197],[[200,194],197],[[198,194],197],[[274,194],197],[[275,194],197],[[201,194],197],[[276,194],197],[[277,194],197],[[278,194],197],[[279,194],197],[[280,194],197],[[281,194],197],[[282,194],197],[[283,194],197],[[284,194],197],[[285,194],197],[[286,194],197],[[287,194],197],[[288,194],197],[[289,194],197],[[290,194],197],[[291,194],197],[[292,194],197],[[293,194],197],[[294,194],197],[[295,194],197],[[296,194],197],[[297,194],197],[[298,194],197],[[299,194],197],[[300,194],197],[[301,194],197],[[302,194],197],[[303,194],197],[[304,194],197],[[305,194],197],[[306,194],197],[[307,194],197],[[308,194],197],[[309,194],197],[[310,194],197],[[311,194],197],[[312,194],197],[[313,194],197],[[314,194],197],[[315,194],197],[[316,194],197],[[317,194],197],[[318,194],197],[[319,194],197],[[320,194],197],[[321,194],197],[[322,194],197],[[323,194],197],[[324,194],197],[[325,194],197],[[326,194],197],[[327,194],197],[[328,194],197],[[329,194],197],[[330,194],197],[[331,194],197],[[332,194],197],[[333,194],197],[[334,194],197],[[335,194],197],[[336,194],197],[[337,194],197],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[199],[241],[242],[243],[244],[245],[246],[247],[248],[249],[250],[202],[251],[252],[253],[254],[255],[203],[256],[257],[258],[259],[260],[261],[262],[263],[264],[265],[266],[267],[268],[269],[270],[271],[272],[273],[200],[198],[274],[275],[201],[276],[277],[278],[279],[280],[281],[282],[283],[284],[285],[286],[287],[288],[289],[290],[291],[292],[293],[294],[295],[296],[297],[298],[299],[300],[301],[302],[303],[304],[305],[306],[307],[308],[309],[310],[311],[312],[313],[314],[315],[316],[317],[318],[319],[320],[321],[322],[323],[324],[325],[326],[327],[328],[329],[330],[331],[332],[333],[334],[335],[336],[337],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[214,[[6,[199]]]],[214,[[6,[241]]]],[214,[[6,[242]]]],[214,[[6,[243]]]],[214,[[6,[244]]]],[214,[[6,[245]]]],[214,[[6,[246]]]],[214,[[6,[247]]]],[214,[[6,[248]]]],[214,[[6,[249]]]],[214,[[6,[250]]]],[214,[[6,[202]]]],[214,[[6,[251]]]],[214,[[6,[252]]]],[214,[[6,[253]]]],[214,[[6,[254]]]],[214,[[6,[255]]]],[214,[[6,[203]]]],[214,[[6,[256]]]],[214,[[6,[257]]]],[214,[[6,[258]]]],[214,[[6,[259]]]],[214,[[6,[260]]]],[214,[[6,[261]]]],[214,[[6,[262]]]],[214,[[6,[263]]]],[214,[[6,[264]]]],[214,[[6,[265]]]],[214,[[6,[266]]]],[214,[[6,[267]]]],[214,[[6,[268]]]],[214,[[6,[269]]]],[214,[[6,[270]]]],[214,[[6,[271]]]],[214,[[6,[272]]]],[214,[[6,[273]]]],[214,[[6,[200]]]],[214,[[6,[198]]]],[214,[[6,[274]]]],[214,[[6,[275]]]],[214,[[6,[201]]]],[214,[[6,[276]]]],[214,[[6,[277]]]],[214,[[6,[278]]]],[214,[[6,[279]]]],[214,[[6,[280]]]],[214,[[6,[281]]]],[214,[[6,[282]]]],[214,[[6,[283]]]],[214,[[6,[284]]]],[214,[[6,[285]]]],[214,[[6,[286]]]],[214,[[6,[287]]]],[214,[[6,[288]]]],[214,[[6,[289]]]],[214,[[6,[290]]]],[214,[[6,[291]]]],[214,[[6,[292]]]],[214,[[6,[293]]]],[214,[[6,[294]]]],[214,[[6,[295]]]],[214,[[6,[296]]]],[214,[[6,[297]]]],[214,[[6,[298]]]],[214,[[6,[299]]]],[214,[[6,[300]]]],[214,[[6,[301]]]],[214,[[6,[302]]]],[214,[[6,[303]]]],[214,[[6,[304]]]],[214,[[6,[305]]]],[214,[[6,[306]]]],[214,[[6,[307]]]],[214,[[6,[308]]]],[214,[[6,[309]]]],[214,[[6,[310]]]],[214,[[6,[311]]]],[214,[[6,[312]]]],[214,[[6,[313]]]],[214,[[6,[314]]]],[214,[[6,[315]]]],[214,[[6,[316]]]],[214,[[6,[317]]]],[214,[[6,[318]]]],[214,[[6,[319]]]],[214,[[6,[320]]]],[214,[[6,[321]]]],[214,[[6,[322]]]],[214,[[6,[323]]]],[214,[[6,[324]]]],[214,[[6,[325]]]],[214,[[6,[326]]]],[214,[[6,[327]]]],[214,[[6,[328]]]],[214,[[6,[329]]]],[214,[[6,[330]]]],[214,[[6,[331]]]],[214,[[6,[332]]]],[214,[[6,[333]]]],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],[[],209],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[334,215]],[[335,215]],[[336,215]],[[337,215]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[199,215]],[[241,215]],[[242,215]],[[243,215]],[[244,215]],[[245,215]],[[246,215]],[[247,215]],[[248,215]],[[249,215]],[[250,215]],[[202,215]],[[251,215]],[[252,215]],[[253,215]],[[254,215]],[[255,215]],[[203,215]],[[256,215]],[[257,215]],[[258,215]],[[259,215]],[[260,215]],[[261,215]],[[262,215]],[[263,215]],[[264,215]],[[265,215]],[[266,215]],[[267,215]],[[268,215]],[[269,215]],[[270,215]],[[271,215]],[[272,215]],[[273,215]],[[200,215]],[[198,215]],[[274,215]],[[275,215]],[[201,215]],[[276,215]],[[277,215]],[[278,215]],[[279,215]],[[280,215]],[[281,215]],[[282,215]],[[283,215]],[[284,215]],[[285,215]],[[286,215]],[[287,215]],[[288,215]],[[289,215]],[[290,215]],[[291,215]],[[292,215]],[[293,215]],[[294,215]],[[295,215]],[[296,215]],[[297,215]],[[298,215]],[[299,215]],[[300,215]],[[301,215]],[[302,215]],[[303,215]],[[304,215]],[[305,215]],[[306,215]],[[307,215]],[[308,215]],[[309,215]],[[310,215]],[[311,215]],[[312,215]],[[313,215]],[[314,215]],[[315,215]],[[316,215]],[[317,215]],[[318,215]],[[319,215]],[[320,215]],[[321,215]],[[322,215]],[[323,215]],[[324,215]],[[325,215]],[[326,215]],[[327,215]],[[328,215]],[[329,215]],[[330,215]],[[331,215]],[[332,215]],[[333,215]],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],196],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],[[],219],0,[162],[162],[162],[187],[187],[187],[71],[71],[71],[11],[11],[11],[10],[10],[10],[163],[163],[163],[142],[142],[142],[188],[188],[188],[144],[144],[144],[78],[78],[78],[77],[77],[77],[189],[189],[189],[140],[140],[140],[138],[138],[138],[139],[139],[139],[141],[141],[141],[137],[137],[137],[26],[26],[26],[27],[27],[27],[28],[28],[28],[29],[29],[29],[30],[30],[30],[31],[31],[31],[32],[32],[32],[33],[33],[33],[34],[34],[34],[35],[35],[35],[36],[36],[36],[37],[37],[37],[38],[38],[38],[39],[39],[39],[40],[40],[40],[41],[41],[41],[42],[42],[42],[43],[43],[43],[44],[44],[44],[45],[45],[45],[46],[46],[46],[47],[47],[47],[48],[48],[48],[49],[49],[49],[25],[25],[25],[50],[50],[50],[51],[51],[51],[52],[52],[52],[53],[53],[53],[54],[54],[54],[55],[55],[55],[56],[56],[56],[58],[58],[58],[57],[57],[57],[59],[59],[59],[60],[60],[60],[61],[61],[61],[62],[62],[62],[63],[63],[63],[64],[64],[64],[20],[20],[20],[182],[182],[182],[69],[69],[69],[17],[17],[17],[18],[18],[18],[19],[19],[19],[128],[128],[128],[126],[126],[126],[111],[111],[111],[114],[114],[114],[110],[110],[110],[112],[112],[112],[113],[113],[113],[186],[186],[186],[68],[68],[68],[74],[74],[74],[73],[73],[73],[7],[7],[7],[121],[121],[121],[124],[124],[124],[122],[122],[122],[120],[120],[120],[123],[123],[123],[66],[66],[66],[88],[88],[88],[89],[89],[89],[90],[90],[90],[91],[91],[91],[92],[92],[92],[93],[93],[93],[95],[95],[95],[94],[94],[94],[96],[96],[96],[87],[87],[87],[97],[97],[97],[98],[98],[98],[100],[100],[100],[99],[99],[99],[101],[101],[101],[102],[102],[102],[103],[103],[103],[70],[70],[70],[76],[76],[76],[9],[9],[9],[134],[134],[134],[132],[132],[132],[131],[131],[131],[133],[133],[133],[5],[5],[5],[3],[3],[3],[129],[129],[129],[130],[130],[130],[146],[146],[146],[136],[136],[136],[135],[135],[135],[65],[65],[65],[13],[13],[13],[12],[12],[12],[14],[14],[14],[67],[67],[67],[15],[15],[15],[190],[190],[190],[167],[167],[167],[168],[168],[168],[169],[169],[169],[170],[170],[170],[166],[166],[166],[171],[171],[171],[172],[172],[172],[173],[173],[173],[174],[174],[174],[175],[175],[175],[176],[176],[176],[177],[177],[177],[178],[178],[178],[179],[179],[179],[180],[180],[180],[181],[181],[181],[185],[185],[185],[183],[183],[183],[184],[184],[184],[86],[86],[86],[85],[85],[85],[84],[84],[84],[191],[191],[191],[72],[72],[72],[127],[127],[127],[165],[165],[165],[125],[125],[125],[209],[209],[209],[145],[145],[145],[81],[81],[81],[80],[80],[80],[116],[116],[116],[119],[119],[119],[117],[117],[117],[115],[115],[115],[118],[118],[118],[148],[148],[148],[149],[149],[149],[150],[150],[150],[151],[151],[151],[152],[152],[152],[153],[153],[153],[147],[147],[147],[154],[154],[154],[79],[79],[79],[75],[75],[75],[155],[155],[155],[156],[156],[156],[157],[157],[157],[158],[158],[158],[159],[159],[159],[160],[160],[160],[161],[161],[161],[143],[143],[143],[108],[108],[108],[109],[109],[109],[106],[106],[106],[105],[105],[105],[107],[107],[107],[104],[104],[104],[164],[164],[164],[16],[16],[16],[23],[23],[23],[22],[22],[22],[24],[24],[24],[21],[21],[21],[82],[82],[82],[83],[83],[83]],"p":[[3,"TypeGenerics"],[3,"Turbofish"],[3,"LitInt"],[15,"str"],[3,"LitFloat"],[6,"Result"],[3,"Ident"],[3,"ImplGenerics"],[3,"Lifetime"],[3,"Attribute"],[4,"AttrStyle"],[4,"Meta"],[3,"MetaList"],[3,"MetaNameValue"],[4,"NestedMeta"],[3,"Variant"],[4,"Fields"],[3,"FieldsNamed"],[3,"FieldsUnnamed"],[3,"Field"],[4,"Visibility"],[3,"VisPublic"],[3,"VisCrate"],[3,"VisRestricted"],[4,"Expr"],[3,"ExprArray"],[3,"ExprAssign"],[3,"ExprAssignOp"],[3,"ExprAsync"],[3,"ExprAwait"],[3,"ExprBinary"],[3,"ExprBlock"],[3,"ExprBox"],[3,"ExprBreak"],[3,"ExprCall"],[3,"ExprCast"],[3,"ExprClosure"],[3,"ExprContinue"],[3,"ExprField"],[3,"ExprForLoop"],[3,"ExprGroup"],[3,"ExprIf"],[3,"ExprIndex"],[3,"ExprLet"],[3,"ExprLit"],[3,"ExprLoop"],[3,"ExprMacro"],[3,"ExprMatch"],[3,"ExprMethodCall"],[3,"ExprParen"],[3,"ExprPath"],[3,"ExprRange"],[3,"ExprReference"],[3,"ExprRepeat"],[3,"ExprReturn"],[3,"ExprStruct"],[3,"ExprTry"],[3,"ExprTryBlock"],[3,"ExprTuple"],[3,"ExprType"],[3,"ExprUnary"],[3,"ExprUnsafe"],[3,"ExprWhile"],[3,"ExprYield"],[4,"Member"],[3,"Index"],[3,"MethodTurbofish"],[4,"GenericMethodArgument"],[3,"FieldValue"],[3,"Label"],[3,"Arm"],[4,"RangeLimits"],[3,"Generics"],[4,"GenericParam"],[3,"TypeParam"],[3,"LifetimeDef"],[3,"ConstParam"],[3,"BoundLifetimes"],[4,"TypeParamBound"],[3,"TraitBound"],[4,"TraitBoundModifier"],[3,"WhereClause"],[4,"WherePredicate"],[3,"PredicateType"],[3,"PredicateLifetime"],[3,"PredicateEq"],[4,"Item"],[3,"ItemConst"],[3,"ItemEnum"],[3,"ItemExternCrate"],[3,"ItemFn"],[3,"ItemForeignMod"],[3,"ItemImpl"],[3,"ItemMacro"],[3,"ItemMacro2"],[3,"ItemMod"],[3,"ItemStatic"],[3,"ItemStruct"],[3,"ItemTrait"],[3,"ItemTraitAlias"],[3,"ItemType"],[3,"ItemUnion"],[3,"ItemUse"],[4,"UseTree"],[3,"UsePath"],[3,"UseName"],[3,"UseRename"],[3,"UseGlob"],[3,"UseGroup"],[4,"ForeignItem"],[3,"ForeignItemFn"],[3,"ForeignItemStatic"],[3,"ForeignItemType"],[3,"ForeignItemMacro"],[4,"TraitItem"],[3,"TraitItemConst"],[3,"TraitItemMethod"],[3,"TraitItemType"],[3,"TraitItemMacro"],[4,"ImplItem"],[3,"ImplItemConst"],[3,"ImplItemMethod"],[3,"ImplItemType"],[3,"ImplItemMacro"],[3,"Signature"],[4,"FnArg"],[3,"Receiver"],[3,"File"],[4,"Lit"],[3,"LitStr"],[3,"LitByteStr"],[3,"LitByte"],[3,"LitChar"],[3,"LitBool"],[3,"Macro"],[4,"MacroDelimiter"],[3,"DeriveInput"],[4,"Data"],[3,"DataStruct"],[3,"DataEnum"],[3,"DataUnion"],[4,"BinOp"],[4,"UnOp"],[3,"Block"],[4,"Stmt"],[3,"Local"],[4,"Type"],[3,"TypeArray"],[3,"TypeBareFn"],[3,"TypeGroup"],[3,"TypeImplTrait"],[3,"TypeInfer"],[3,"TypeMacro"],[3,"TypeNever"],[3,"TypeParen"],[3,"TypePath"],[3,"TypePtr"],[3,"TypeReference"],[3,"TypeSlice"],[3,"TypeTraitObject"],[3,"TypeTuple"],[3,"Abi"],[3,"BareFnArg"],[3,"Variadic"],[4,"ReturnType"],[4,"Pat"],[3,"PatBox"],[3,"PatIdent"],[3,"PatLit"],[3,"PatMacro"],[3,"PatOr"],[3,"PatPath"],[3,"PatRange"],[3,"PatReference"],[3,"PatRest"],[3,"PatSlice"],[3,"PatStruct"],[3,"PatTuple"],[3,"PatTupleStruct"],[3,"PatType"],[3,"PatWild"],[3,"FieldPat"],[3,"Path"],[3,"PathSegment"],[4,"PathArguments"],[4,"GenericArgument"],[3,"AngleBracketedGenericArguments"],[3,"Binding"],[3,"Constraint"],[3,"ParenthesizedGenericArguments"],[3,"QSelf"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[3,"Error"],[4,"Result"],[6,"Result"],[3,"SelfValue"],[3,"Underscore"],[3,"SelfType"],[3,"Super"],[3,"Crate"],[3,"Extern"],[15,"usize"],[3,"Literal"],[4,"Option"],[3,"Iter"],[3,"IterMut"],[3,"Span"],[15,"u8"],[15,"char"],[3,"TokenStream"],[8,"Parse"],[6,"ParseStream"],[3,"TokenStream"],[8,"Parser"],[3,"Vec"],[3,"String"],[3,"TypeId"],[3,"TokenBuffer"],[3,"Cursor"],[4,"Delimiter"],[3,"ParseBuffer"],[3,"StepCursor"],[3,"Error"],[3,"Nothing"],[3,"Lookahead1"],[8,"Display"],[8,"IntoIterator"],[3,"LexError"],[8,"ToTokens"],[3,"Punctuated"],[8,"Peek"],[3,"Demand"],[3,"Pairs"],[3,"IntoPairs"],[3,"IntoIter"],[4,"Pair"],[8,"Debug"],[3,"PairsMut"],[3,"Abstract"],[3,"As"],[3,"Async"],[3,"Auto"],[3,"Await"],[3,"Become"],[3,"Box"],[3,"Break"],[3,"Const"],[3,"Continue"],[3,"Default"],[3,"Do"],[3,"Dyn"],[3,"Else"],[3,"Enum"],[3,"Final"],[3,"Fn"],[3,"For"],[3,"If"],[3,"Impl"],[3,"In"],[3,"Let"],[3,"Loop"],[3,"Macro"],[3,"Match"],[3,"Mod"],[3,"Move"],[3,"Mut"],[3,"Override"],[3,"Priv"],[3,"Pub"],[3,"Ref"],[3,"Return"],[3,"Static"],[3,"Struct"],[3,"Trait"],[3,"Try"],[3,"Type"],[3,"Typeof"],[3,"Union"],[3,"Unsafe"],[3,"Unsized"],[3,"Use"],[3,"Virtual"],[3,"Where"],[3,"While"],[3,"Yield"],[3,"Add"],[3,"AddEq"],[3,"And"],[3,"AndAnd"],[3,"AndEq"],[3,"At"],[3,"Bang"],[3,"Caret"],[3,"CaretEq"],[3,"Colon"],[3,"Colon2"],[3,"Comma"],[3,"Div"],[3,"DivEq"],[3,"Dollar"],[3,"Dot"],[3,"Dot2"],[3,"Dot3"],[3,"DotDotEq"],[3,"Eq"],[3,"EqEq"],[3,"Ge"],[3,"Gt"],[3,"Le"],[3,"Lt"],[3,"MulEq"],[3,"Ne"],[3,"Or"],[3,"OrEq"],[3,"OrOr"],[3,"Pound"],[3,"Question"],[3,"RArrow"],[3,"LArrow"],[3,"Rem"],[3,"RemEq"],[3,"FatArrow"],[3,"Semi"],[3,"Shl"],[3,"ShlEq"],[3,"Shr"],[3,"ShrEq"],[3,"Star"],[3,"Sub"],[3,"SubEq"],[3,"Tilde"],[3,"Brace"],[3,"Bracket"],[3,"Paren"],[3,"Group"],[4,"StrStyle"],[8,"IdentExt"],[8,"Speculative"],[8,"Spanned"],[8,"VisitMut"]]},\ -"tokio":{"doc":"A runtime for writing reliable network applications …","t":[0,14,23,0,14,0,14,5,0,0,0,14,23,14,8,8,8,8,2,2,3,2,2,11,11,11,11,11,11,10,11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,11,11,11,11,11,10,11,11,11,11,11,11,8,3,13,3,3,13,3,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,13,3,3,13,13,18,3,3,3,13,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,12,12,3,3,11,11,11,11,5,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,13,13,13,13,13,4,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,3,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,11,12,13,13,13,13,3,4,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,3,3,11,11,11,11,11,5,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,3,4,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,11,11,11,11,11,11,11,11,11,11,5,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,5,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,5,11,5,3,11,11,11,11,11,11,11,11,11,11,11],"n":["io","join","main","net","pin","runtime","select","spawn","stream","sync","task","task_local","test","try_join","AsyncBufRead","AsyncRead","AsyncSeek","AsyncWrite","Error","ErrorKind","ReadBuf","Result","SeekFrom","advance","assume_init","borrow","borrow_mut","capacity","clear","consume","filled","filled_mut","fmt","from","initialize_unfilled","initialize_unfilled_to","initialized","initialized_mut","inner_mut","into","is_write_vectored","is_write_vectored","new","poll_complete","poll_fill_buf","poll_flush","poll_read","poll_shutdown","poll_write","poll_write_vectored","poll_write_vectored","put_slice","remaining","set_filled","start_seek","take","try_from","try_into","type_id","unfilled_mut","uninit","ToSocketAddrs","Builder","CurrentThread","EnterGuard","Handle","MultiThread","Runtime","RuntimeFlavor","TryCurrentError","block_on","block_on","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","build","clone","clone_into","current","drop","enable_all","enter","enter","eq","event_interval","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","global_queue_interval","handle","into","into","into","into","into","into","is_missing_context","is_thread_local_destroyed","max_blocking_threads","new_current_thread","on_thread_park","on_thread_start","on_thread_stop","on_thread_unpark","provide","runtime_flavor","shutdown_background","shutdown_timeout","spawn","spawn","spawn_blocking","spawn_blocking","thread_keep_alive","thread_name","thread_name_fn","thread_stack_size","to_owned","to_string","try_current","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","worker_threads","AcquireError","AlreadyInitializedError","Barrier","BarrierWaitResult","Closed","InitializingError","MAX_PERMITS","MappedMutexGuard","Mutex","MutexGuard","NoPermits","Notify","OnceCell","OwnedMutexGuard","OwnedRwLockMappedWriteGuard","OwnedRwLockReadGuard","OwnedRwLockWriteGuard","OwnedSemaphorePermit","RwLock","RwLockMappedWriteGuard","RwLockReadGuard","RwLockWriteGuard","Semaphore","SemaphorePermit","SetError","TryAcquireError","TryLockError","acquire","acquire_many","acquire_many_owned","acquire_owned","add_permits","available_permits","blocking_lock","blocking_lock_owned","blocking_read","blocking_write","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","broadcast","clone","clone","clone_into","clone_into","close","default","default","default","default","deref","deref","deref","deref","deref","deref","deref","deref","deref","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","deref_mut","downgrade","downgrade","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","drop","eq","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","forget","forget","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","futures","get","get_mut","get_mut","get_mut","get_or_init","get_or_try_init","initialized","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","into_inner","into_inner","into_mapped","into_mapped","is_already_init_err","is_closed","is_initializing_err","is_leader","lock","lock_owned","map","map","map","map","map","map","map","map","merge","merge","mpsc","mutex","mutex","new","new","new","new","new","new","new_with","notified","notify_one","notify_waiters","oneshot","provide","provide","provide","provide","read","read_owned","set","take","to_owned","to_owned","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","to_string","try_acquire","try_acquire_many","try_acquire_many_owned","try_acquire_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_lock","try_lock_owned","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_map","try_read","try_read_owned","try_write","try_write_owned","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","wait","watch","with_max_readers","write","write_owned","0","0","Receiver","Sender","borrow","borrow","borrow_mut","borrow_mut","channel","clone","clone_into","drop","drop","error","fmt","fmt","from","from","into","into","is_empty","len","receiver_count","recv","resubscribe","send","subscribe","to_owned","try_from","try_from","try_into","try_into","try_recv","type_id","type_id","0","Closed","Closed","Empty","Lagged","Lagged","RecvError","SendError","TryRecvError","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","into","into","into","provide","provide","provide","to_owned","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","Notified","borrow","borrow_mut","drop","enable","fmt","from","into","into_future","poll","try_from","try_into","type_id","OwnedPermit","Permit","Receiver","Sender","UnboundedReceiver","UnboundedSender","WeakSender","WeakUnboundedSender","blocking_recv","blocking_recv","blocking_send","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capacity","channel","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","close","close","closed","closed","downgrade","downgrade","drop","drop","error","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","is_closed","is_closed","max_capacity","poll_recv","poll_recv","recv","recv","release","reserve","reserve_owned","same_channel","same_channel","send","send","send","send","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_recv","try_recv","try_reserve","try_reserve_owned","try_send","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unbounded_channel","upgrade","upgrade","0","Closed","Disconnected","Empty","Full","SendError","TryRecvError","TrySendError","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","into","into","into","provide","provide","provide","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","0","Receiver","Sender","blocking_recv","borrow","borrow","borrow_mut","borrow_mut","channel","close","closed","drop","drop","error","fmt","fmt","from","from","into","into","into_future","is_closed","poll","poll_closed","send","try_from","try_from","try_into","try_into","try_recv","type_id","type_id","Closed","Empty","RecvError","TryRecvError","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","eq","fmt","fmt","fmt","fmt","from","from","into","into","provide","provide","to_owned","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","Receiver","Ref","Sender","borrow","borrow","borrow","borrow","borrow","borrow_and_update","borrow_mut","borrow_mut","borrow_mut","changed","channel","clone","clone_into","closed","deref","drop","drop","error","fmt","fmt","fmt","from","from","from","has_changed","has_changed","into","into","into","is_closed","receiver_count","same_channel","send","send_if_modified","send_modify","send_replace","subscribe","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","0","RecvError","SendError","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","fmt","fmt","fmt","fmt","from","from","into","into","provide","provide","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","AbortHandle","JoinError","JoinHandle","JoinSet","LocalEnterGuard","LocalKey","LocalSet","Unconstrained","abort","abort","abort_all","block_on","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","default","detach_all","drop","drop","drop","drop","drop","enter","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","futures","get","into","into","into","into","into","into","into","into","into_future","into_future","into_future","into_panic","is_cancelled","is_empty","is_finished","is_finished","is_panic","join_next","len","new","new","poll","poll","poll","provide","run_until","scope","shutdown","spawn","spawn","spawn_blocking","spawn_local","spawn_local","spawn_local","spawn_local_on","spawn_on","sync_scope","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into_panic","try_with","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unconstrained","with","yield_now","TaskLocalFuture","borrow","borrow_mut","drop","fmt","from","into","into_future","poll","try_from","try_into","type_id"],"q":["tokio","","","","","","","","","","","","","","tokio::io","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::net","tokio::runtime","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::SetError","","tokio::sync::broadcast","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::broadcast::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::broadcast::error::RecvError","tokio::sync::broadcast::error::TryRecvError","tokio::sync::futures","","","","","","","","","","","","","tokio::sync::mpsc","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::mpsc::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::mpsc::error::TrySendError","","tokio::sync::oneshot","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::oneshot::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::watch","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::sync::watch::error","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::task","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","tokio::task::futures","","","","","","","","","","",""],"d":["Traits, helpers, and type definitions for asynchronous I/O …","Waits on multiple concurrent branches, returning when all …","Marks async function to be executed by selected runtime. …","TCP/UDP/Unix bindings for tokio.","Pins a value on the stack.","The Tokio runtime.","Waits on multiple concurrent branches, returning when the …","Spawns a new asynchronous task, returning a JoinHandle for …","Due to the Stream trait’s inclusion in std landing later …","Synchronization primitives for use in asynchronous …","Asynchronous green-threads.","Declares a new task-local key of type tokio::task::LocalKey…","Marks async function to be executed by runtime, suitable …","Waits on multiple concurrent branches, returning when all …","Reads bytes asynchronously.","Reads bytes from a source.","Seek bytes asynchronously.","Writes bytes asynchronously.","","","A wrapper around a byte buffer that is incrementally …","","","Advances the size of the filled region of the buffer.","Asserts that the first n unfilled bytes of the buffer are …","","","Returns the total capacity of the buffer.","Clears the buffer, resetting the filled region to empty.","Tells this buffer that amt bytes have been consumed from …","Returns a shared reference to the filled portion of the …","Returns a mutable reference to the filled portion of the …","","Returns the argument unchanged.","Returns a mutable reference to the unfilled part of the …","Returns a mutable reference to the first n bytes of the …","Returns a shared reference to the initialized portion of …","Returns a mutable reference to the initialized portion of …","Returns a mutable reference to the entire buffer, without …","Calls U::from(self).","Determines if this writer has an efficient …","Determines if this writer has an efficient …","Creates a new ReadBuf from a fully initialized buffer.","Waits for a seek operation to complete.","Attempts to return the contents of the internal buffer, …","Attempts to flush the object, ensuring that any buffered …","Attempts to read from the AsyncRead into buf.","Initiates or attempts to shut down this writer, returning …","Attempt to write bytes from buf into the object.","Like poll_write, except that it writes from a slice of …","Like poll_write, except that it writes from a slice of …","Appends data to the buffer, advancing the written position …","Returns the number of bytes at the end of the slice that …","Sets the size of the filled region of the buffer.","Attempts to seek to an offset, in bytes, in a stream.","Returns a new ReadBuf comprised of the unfilled section up …","","","","Returns a mutable reference to the unfilled part of the …","Creates a new ReadBuf from a fully uninitialized buffer.","Converts or resolves without blocking to one or more …","Builds Tokio Runtime with custom configuration values.","The flavor that executes all tasks on the current thread.","Runtime context guard.","Handle to the runtime.","The flavor that executes tasks across multiple threads.","The Tokio runtime.","The flavor of a Runtime.","Error returned by try_current when no Runtime has been …","Runs a future to completion on this Handle’s associated …","Runs a future to completion on the Tokio runtime. This is …","","","","","","","","","","","","","Creates the configured Runtime.","","","Returns a Handle view over the currently running Runtime.","","Enables both I/O and time drivers.","Enters the runtime context. This allows you to construct …","Enters the runtime context.","","Sets the number of scheduler ticks after which the …","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Sets the number of scheduler ticks after which the …","Returns a handle to the runtime’s spawner.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns true if the call failed because there is currently …","Returns true if the call failed because the Tokio context …","Specifies the limit for additional threads spawned by the …","Returns a new builder with the current thread scheduler …","Executes function f just before a thread is parked (goes …","Executes function f after each thread is started but …","Executes function f before each thread stops.","Executes function f just after a thread unparks (starts …","","Returns the flavor of the current Runtime.","Shuts down the runtime, without waiting for any spawned …","Shuts down the runtime, waiting for at most duration for …","Spawns a future onto the Tokio runtime.","Spawns a future onto the Tokio runtime.","Runs the provided function on an executor dedicated to …","Runs the provided function on an executor dedicated to …","Sets a custom timeout for a thread in the blocking pool.","Sets name of threads spawned by the Runtime’s thread …","Sets a function used to generate the name of threads …","Sets the stack size (in bytes) for worker threads.","","","Returns a Handle view over the currently running Runtime","","","","","","","","","","","","","","","","","","","Sets the number of worker threads the Runtime will use.","Error returned from the Semaphore::acquire function.","The cell was already initialized when OnceCell::set was …","A barrier enables multiple tasks to synchronize the …","A BarrierWaitResult is returned by wait when all tasks in …","The semaphore has been closed and cannot issue new permits.","The cell is currently being initialized.","The maximum number of permits which a semaphore can hold. …","A handle to a held Mutex that has had a function applied …","An asynchronous Mutex-like type.","A handle to a held Mutex. The guard can be held across any …","The semaphore has no available permits.","Notifies a single task to wake up.","A thread-safe cell that can be written to only once.","An owned handle to a held Mutex.","Owned RAII structure used to release the exclusive write …","Owned RAII structure used to release the shared read …","Owned RAII structure used to release the exclusive write …","An owned permit from the semaphore.","An asynchronous reader-writer lock.","RAII structure used to release the exclusive write access …","RAII structure used to release the shared read access of a …","RAII structure used to release the exclusive write access …","Counting semaphore performing asynchronous permit …","A permit from the semaphore.","Errors that can be returned from OnceCell::set.","Error returned from the Semaphore::try_acquire function.","Error returned from the Mutex::try_lock, RwLock::try_read …","Acquires a permit from the semaphore.","Acquires n permits from the semaphore.","Acquires n permits from the semaphore.","Acquires a permit from the semaphore.","Adds n new permits to the semaphore.","Returns the current number of available permits.","Blockingly locks this Mutex. When the lock has been …","Blockingly locks this Mutex. When the lock has been …","Blockingly locks this RwLock with shared read access.","Blockingly locks this RwLock with exclusive write access.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A multi-producer, multi-consumer broadcast queue. Each …","","","","","Closes the semaphore.","","","","","","","","","","","","","","","","","","","","","Atomically downgrades a write lock into a read lock …","Atomically downgrades a write lock into a read lock …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Forgets the permit without releasing it back to the …","Forgets the permit without releasing it back to the …","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Named future types.","Returns a reference to the value currently stored in the …","Returns a mutable reference to the underlying data.","Returns a mutable reference to the value currently stored …","Returns a mutable reference to the underlying data.","Gets the value currently in the OnceCell, or initialize it …","Gets the value currently in the OnceCell, or initialize it …","Returns true if the OnceCell currently contains a value, …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Consumes the mutex, returning the underlying data.","Takes the value from the cell, destroying the cell in the …","Consumes the lock, returning the underlying data.","Converts this OwnedRwLockWriteGuard into an …","Converts this RwLockWriteGuard into an …","Whether SetError is SetError::AlreadyInitializedError.","Returns true if the semaphore is closed","Whether SetError is SetError::InitializingError","Returns true if this task from wait is the “leader task…","Locks this mutex, causing the current task to yield until …","Locks this mutex, causing the current task to yield until …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new MappedMutexGuard for a component of the locked …","Makes a new OwnedRwLockReadGuard for a component of the …","Makes a new OwnedRwLockMappedWriteGuard for a component of …","Makes a new OwnedRwLockMappedWriteGuard for a component of …","Makes a new RwLockReadGuard for a component of the locked …","Makes a new RwLockMappedWriteGuard for a component of the …","Makes a new RwLockMappedWriteGuard for a component of the …","Merge two SemaphorePermit instances together, consuming …","Merge two OwnedSemaphorePermit instances together, …","A multi-producer, single-consumer queue for sending values …","Returns a reference to the original Mutex.","Returns a reference to the original Arc<Mutex>.","Creates a new lock in an unlocked state ready for use.","Creates a new empty OnceCell instance.","Creates a new barrier that can block a given number of …","Create a new Notify, initialized without a permit.","Creates a new semaphore with the initial number of permits.","Creates a new instance of an RwLock<T> which is unlocked.","Creates a new OnceCell that contains the provided value, …","Wait for a notification.","Notifies a waiting task.","Notifies all waiting tasks.","A one-shot channel is used for sending a single message …","","","","","Locks this RwLock with shared read access, causing the …","Locks this RwLock with shared read access, causing the …","Sets the value of the OnceCell to the given value if the …","Takes ownership of the current value, leaving the cell …","","","","","","","","","","","","","","","","Tries to acquire a permit from the semaphore.","Tries to acquire n permits from the semaphore.","Tries to acquire n permits from the semaphore.","Tries to acquire a permit from the semaphore.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Attempts to acquire the lock, and returns TryLockError if …","Attempts to acquire the lock, and returns TryLockError if …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new MappedMutexGuard for a component of …","Attempts to make a new OwnedRwLockReadGuard for a …","Attempts to make a new OwnedRwLockMappedWriteGuard for a …","Attempts to make a new OwnedRwLockMappedWriteGuard for a …","Attempts to make a new RwLockReadGuard for a component of …","Attempts to make a new RwLockMappedWriteGuard for a …","Attempts to make a new RwLockMappedWriteGuard for a …","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with shared read access.","Attempts to acquire this RwLock with exclusive write …","Attempts to acquire this RwLock with exclusive write …","","","","","","","","","","","","","","","","","","","","","","","Does not resolve until all tasks have rendezvoused here.","A single-producer, multi-consumer channel that only …","Creates a new instance of an RwLock<T> which is unlocked …","Locks this RwLock with exclusive write access, causing the …","Locks this RwLock with exclusive write access, causing the …","","","Receiving-half of the broadcast channel.","Sending-half of the broadcast channel.","","","","","Create a bounded, multi-producer, multi-consumer channel …","","","","","Broadcast error types","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Returns true if there aren’t any messages in the channel …","Returns the number of messages that were sent into the …","Returns the number of active receivers","Receives the next value for this receiver.","Re-subscribes to the channel starting from the current …","Attempts to send a value to all active Receiver handles, …","Creates a new Receiver handle that will receive values …","","","","","","Attempts to return a pending value on this receiver …","","","","There are no more active senders implying no further …","There are no more active senders implying no further …","The channel is currently empty. There are still active …","The receiver lagged too far behind. Attempting to receive …","The receiver lagged too far behind and has been forcibly …","An error returned from the recv function on a Receiver.","Error returned by from the send function on a Sender.","An error returned from the try_recv function on a Receiver.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","Future returned from Notify::notified().","","","","Adds this future to the list of futures that are ready to …","","Returns the argument unchanged.","Calls U::from(self).","","","","","","Owned permit to send one value into the channel.","Permits to send one value into the channel.","Receives values from the associated Sender.","Sends values to the associated Receiver.","Receive values from the associated UnboundedSender.","Send values to the associated UnboundedReceiver.","A sender that does not prevent the channel from being …","An unbounded sender that does not prevent the channel from …","Blocking receive to call outside of asynchronous contexts.","Blocking receive to call outside of asynchronous contexts.","Blocking send to call outside of asynchronous contexts.","","","","","","","","","","","","","","","","","Returns the current capacity of the channel.","Creates a bounded mpsc channel for communicating between …","","","","","","","","","Closes the receiving half of a channel without dropping it.","Closes the receiving half of a channel, without dropping …","Completes when the receiver has dropped.","Completes when the receiver has dropped.","Converts the Sender to a WeakSender that does not count …","Converts the UnboundedSender to a WeakUnboundedSender that …","","","Channel error types.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Checks if the channel has been closed. This happens when …","Checks if the channel has been closed. This happens when …","Returns the maximum buffer capacity of the channel.","Polls to receive the next message on this channel.","Polls to receive the next message on this channel.","Receives the next value for this receiver.","Receives the next value for this receiver.","Releases the reserved capacity without sending a message, …","Waits for channel capacity. Once capacity to send one …","Waits for channel capacity, moving the Sender and …","Returns true if senders belong to the same channel.","Returns true if senders belong to the same channel.","Sends a value, waiting until there is capacity.","Sends a value using the reserved capacity.","Sends a value using the reserved capacity.","Attempts to send a message on this UnboundedSender without …","","","","","","","","","","","","","","","","","","","","","Tries to receive the next value for this receiver.","Tries to receive the next value for this receiver.","Tries to acquire a slot in the channel without waiting for …","Tries to acquire a slot in the channel without waiting for …","Attempts to immediately send a message on this Sender","","","","","","","","","Creates an unbounded mpsc channel for communicating …","Tries to convert a WeakSender into a Sender. This will …","Tries to convert a WeakUnboundedSender into an …","","The receive half of the channel was explicitly closed or …","The channel’s sending half has become disconnected, and …","This channel is currently empty, but the Sender(s) have …","The data could not be sent on the channel because the …","Error returned by the Sender.","Error returned by try_recv.","This enumeration is the list of the possible error …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Receives a value from the associated Sender.","Sends a value to the associated Receiver.","Blocking receive to call outside of asynchronous contexts.","","","","","Creates a new one-shot channel for sending single values …","Prevents the associated Sender handle from sending a value.","Waits for the associated Receiver handle to close.","","","Oneshot error types.","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","Returns true if the associated Receiver handle has been …","","Checks whether the oneshot channel has been closed, and if …","Attempts to send a value on this channel, returning it …","","","","","Attempts to receive a value.","","","The send half of the channel was dropped without sending a …","The send half of the channel has not yet sent a value.","Error returned by the Future implementation for Receiver.","Error returned by the try_recv function on Receiver.","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","Receives values from the associated Sender.","Returns a reference to the inner value.","Sends values to the associated Receiver.","","Returns a reference to the most recently sent value.","","Returns a reference to the most recently sent value","","Returns a reference to the most recently sent value and …","","","","Waits for a change notification, then marks the newest …","Creates a new watch channel, returning the “send” and …","","","Completes when all receivers have dropped.","","","","Watch error types.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Checks if this channel contains a message that this …","Indicates if the borrowed value is considered as changed …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Checks if the channel has been closed. This happens when …","Returns the number of receivers that currently exist.","Returns true if receivers belong to the same channel.","Sends a new value via the channel, notifying all receivers.","Modifies the watched value conditionally in-place, …","Modifies the watched value unconditionally in-place, …","Sends a new value via the channel, notifying all receivers …","Creates a new Receiver connected to this Sender.","","","","","","","","","","","","Error produced when receiving a change notification.","Error produced when sending a value fails.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","An owned permission to abort a spawned task, without …","Task failed to execute to completion.","An owned permission to join on a task (await its …","A collection of tasks spawned on a Tokio runtime.","Context guard for LocalSet","A key for task-local data.","A set of tasks which are executed on the same thread.","Future for the unconstrained method.","Abort the task associated with the handle.","Abort the task associated with the handle.","Aborts all tasks on this JoinSet.","Runs a future to completion on the provided runtime, …","","","","","","","","","","","","","","","","","","","Removes all tasks from this JoinSet without aborting them.","","","","","","Enters the context of this LocalSet.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Task-related futures.","Returns a copy of the task-local value if the task-local …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Consumes the join error, returning the object with which …","Returns true if the error was caused by the task being …","Returns whether the JoinSet is empty.","Checks if the task associated with this AbortHandle has …","Checks if the task associated with this JoinHandle has …","Returns true if the error was caused by the task panicking.","Waits until one of the tasks in the set completes and …","Returns the number of tasks currently in the JoinSet.","Create a new JoinSet.","Returns a new local task set.","","","","","Runs a future to completion on the local set, returning …","Sets a value T as the task-local value for the future F.","Aborts all tasks and waits for them to finish shutting …","Spawns a new asynchronous task, returning a JoinHandle for …","Spawn the provided task on the JoinSet, returning an …","Runs the provided closure on a thread where blocking is …","Spawns a !Send future on the current LocalSet.","Spawn the provided task on the current LocalSet and store …","Spawns a !Send task onto the local task set.","Spawn the provided task on the provided LocalSet and store …","Spawn the provided task on the provided runtime and store …","Sets a value T as the task-local value for the closure F.","","","","","","","","","","","","","","","","","","Consumes the join error, returning the object with which …","Accesses the current task-local and runs the provided …","","","","","","","","","Turn off cooperative scheduling for a future. The future …","Accesses the current task-local and runs the provided …","Yields execution back to the Tokio runtime.","A future that sets a value T of a task local for the …","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,95,2,2,2,2,2,2,2,2,2,2,96,96,2,97,95,96,98,96,96,96,96,2,2,2,97,2,2,2,2,2,2,0,0,21,0,0,21,0,0,0,16,18,19,16,20,23,18,21,19,16,20,23,18,21,19,16,16,16,18,19,16,18,21,19,19,16,20,23,23,18,21,19,16,20,23,18,21,19,18,19,16,20,23,18,21,23,23,19,19,19,19,19,19,23,16,18,18,16,18,16,18,19,19,19,19,16,23,16,19,16,20,23,18,21,19,16,20,23,18,21,19,16,20,23,18,21,19,0,51,0,0,50,51,28,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,28,28,28,28,34,34,37,37,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,0,41,42,41,42,28,34,41,43,37,35,36,44,45,46,47,38,39,48,35,36,44,46,47,39,48,46,39,35,36,44,45,46,47,38,39,48,41,29,32,41,50,51,34,35,35,36,36,44,44,45,45,46,46,47,47,38,38,39,39,48,48,41,54,42,55,55,43,50,50,30,30,28,29,32,37,51,51,29,32,34,34,34,35,36,44,45,46,47,38,39,48,41,41,41,54,42,55,43,50,30,28,29,32,37,37,37,51,0,41,34,41,37,41,41,41,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,41,37,46,39,51,28,51,42,34,34,35,44,45,46,47,38,39,48,29,32,0,35,36,34,41,54,43,28,37,41,43,43,43,0,55,50,30,51,37,37,41,41,41,42,35,36,44,45,46,47,38,39,48,55,50,30,51,28,28,28,28,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,34,34,35,44,45,46,47,38,39,48,37,37,37,37,34,35,36,44,45,46,47,38,39,48,41,54,42,55,43,50,30,28,29,32,37,51,54,0,37,37,37,99,100,0,0,59,60,59,60,0,59,59,59,60,0,59,60,59,60,59,60,60,60,59,60,60,59,59,59,59,60,59,60,60,59,60,62,61,63,63,61,63,0,0,0,62,61,63,62,61,63,61,63,61,63,61,63,62,62,61,61,63,63,62,61,63,62,61,63,62,61,63,61,63,62,61,63,62,61,63,62,61,63,62,61,63,101,102,0,58,58,58,58,58,58,58,58,58,58,58,58,0,0,0,0,0,0,0,0,64,65,66,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,0,66,68,69,70,66,68,69,70,64,65,66,69,66,69,71,72,0,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,66,69,66,64,65,64,65,72,66,66,66,69,66,71,72,69,66,68,69,70,66,68,71,72,64,69,70,65,66,68,71,72,64,69,70,65,64,65,66,66,66,66,68,71,72,64,69,70,65,0,68,70,67,74,73,73,74,0,0,0,67,74,73,67,74,73,73,73,74,73,67,67,74,74,73,73,67,74,74,73,67,74,73,67,74,73,73,67,74,73,67,74,73,67,74,73,67,74,73,103,104,0,0,75,77,75,77,75,0,75,77,77,75,0,77,75,77,75,77,75,75,77,75,77,77,77,75,77,75,75,77,75,78,78,0,0,76,78,76,78,76,78,76,78,76,78,76,76,78,78,76,78,76,78,76,78,76,78,76,78,76,78,76,78,76,78,0,0,0,79,79,81,81,80,79,79,81,80,79,0,79,79,81,80,79,81,0,79,81,80,79,81,80,79,80,79,81,80,81,81,79,81,81,81,81,81,79,79,81,80,79,81,80,79,81,80,83,0,0,83,82,83,82,82,82,83,83,82,82,83,82,83,82,83,82,82,83,82,83,82,83,82,83,82,0,0,0,0,0,0,0,0,84,1,85,86,84,88,1,87,89,85,86,93,84,88,1,87,89,85,86,93,85,86,85,84,1,87,85,86,86,84,88,88,1,87,89,85,86,84,88,1,87,89,85,86,93,0,89,84,88,1,87,89,85,86,93,1,86,93,88,88,85,84,1,88,85,85,85,86,1,86,93,88,86,89,85,0,85,0,0,85,86,85,85,89,88,84,88,1,87,89,85,86,93,84,88,1,87,89,85,86,93,88,89,84,88,1,87,89,85,86,93,0,89,0,0,94,94,94,94,94,94,94,94,94,94,94],"f":[0,0,0,0,0,0,0,[[],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,3]],[[2,3]],[[]],[[]],[2,3],[2],[[4,3]],[2],[2],[[2,5],6],[[]],[2],[[2,3]],[2],[2],[2],[[]],[[],7],[[],7],[[],2],[[4,8],[[11,[[10,[9]]]]]],[[4,8],[[11,[10]]]],[[4,8],[[11,[[13,[12]]]]]],[[4,8,2],[[11,[10]]]],[[4,8],[[11,[[13,[12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[[4,8],[[11,[[13,[3,12]]]]]],[2],[2,3],[[2,3]],[[4,14],10],[[2,3],2],[[],13],[[],13],[[],15],[2],[[],2],0,0,0,0,0,0,0,0,0,[[16,17]],[[18,17]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[19,[[10,[18]]]],[16,16],[[]],[[],16],[18],[19,19],[16,20],[18,20],[[21,21],7],[[19,22],19],[[19,5],6],[[16,5],6],[[20,5],6],[[23,5],6],[[23,5],6],[[18,5],6],[[21,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[19,22],19],[18,16],[[]],[[]],[[]],[[]],[[]],[[]],[23,7],[23,7],[[19,3],19],[[],19],[19,19],[19,19],[19,19],[19,19],[24],[16,21],[18],[[18,25]],[16,1],[18,1],[16,1],[18,1],[[19,25],19],[[19,[27,[26]]],19],[19,19],[[19,3],19],[[]],[[],26],[[],[[13,[16,23]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[19,3],19],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[28,[[13,[29,30]]]],[[28,22],[[13,[29,30]]]],[[[31,[28]],22],[[13,[32,30]]]],[[[31,[28]]],[[13,[32,30]]]],[[28,3]],[28,3],[[[34,[33]]],[[35,[33]]]],[[[31,[[34,[33]]]]],[[36,[33]]]],[[[37,[33]]],[[38,[33]]]],[[[37,[33]]],[[39,[33]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[[41,[40]]],[[41,[40]]]],[42,42],[[]],[[]],[28],[[],34],[[],41],[[],43],[[],[[37,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[45,[33,33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[38,[33]]]],[[[39,[33]]]],[[[48,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[39,[33]]]],[[[48,[33]]]],[[[46,[33]]],[[45,[33]]]],[[[39,[33]]],[[38,[33]]]],[[[35,[33]]]],[[[36,[33]]]],[[[44,[33]]]],[[[45,[33,33]]]],[[[46,[33]]]],[[[47,[33,33]]]],[[[38,[33]]]],[[[39,[33]]]],[[[48,[33]]]],[41],[29],[32],[[[41,[49]],41],7],[[50,50],7],[[[51,[49]],51],7],[[[34,[33]],5],6],[[[35,[[0,[33,52]]]],5],6],[[[35,[[0,[33,53]]]],5],6],[[[36,[[0,[33,52]]]],5],6],[[[36,[[0,[33,53]]]],5],6],[[[44,[[0,[33,52]]]],5],6],[[[44,[[0,[33,53]]]],5],6],[[[45,[33,33]],5],6],[[[45,[33,33]],5],6],[[[46,[33]],5],6],[[[46,[33]],5],6],[[[47,[33,33]],5],6],[[[47,[33,33]],5],6],[[[38,[33]],5],6],[[[38,[33]],5],6],[[[39,[33]],5],6],[[[39,[33]],5],6],[[[48,[33]],5],6],[[[48,[33]],5],6],[[[41,[52]],5],6],[[54,5],6],[[42,5],6],[[55,5],6],[[55,5],6],[[43,5],6],[[50,5],6],[[50,5],6],[[30,5],6],[[30,5],6],[[28,5],6],[[29,5],6],[[32,5],6],[[[37,[[0,[52,33]]]],5],6],[[[51,[52]],5],6],[[51,5],6],[29],[32],[[],34],[56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[56],[[],41],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],37],[[]],[56],[[]],0,[41,57],[[[34,[33]]]],[41,57],[[[37,[33]]]],[41],[41,13],[41,7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[34,[33]]],33],[41,57],[[[37,[33]]],33],[[[46,[33]]],[[47,[33]]]],[[[39,[33]]],[[48,[33]]]],[51,7],[28,7],[51,7],[42,7],[[[34,[33]]],[[35,[33]]]],[[[31,[[34,[33]]]]],[[36,[33]]]],[[[35,[33]]],44],[[[44,[33]]],44],[[[45,[33,33]]],[[45,[33,33]]]],[[[46,[33]]],[[47,[33,33]]]],[[[47,[33,33]]],[[47,[33,33]]]],[[[38,[33]]],[[38,[33]]]],[[[39,[33]]],[[48,[33]]]],[[[48,[33]]],[[48,[33]]]],[[29,29]],[[32,32]],0,[[[35,[33]]],34],[[[36,[33]]],31],[33,[[34,[33]]]],[[],41],[3,54],[[],43],[3,28],[33,[[37,[33]]]],[57,41],[43,58],[43],[43],0,[24],[24],[24],[24],[[[37,[33]]],[[38,[33]]]],[[[31,[[37,[33]]]]],[[45,[33]]]],[41,[[13,[51]]]],[41,57],[[]],[[]],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[28,[[13,[29,50]]]],[[28,22],[[13,[29,50]]]],[[[31,[28]],22],[[13,[32,50]]]],[[[31,[28]]],[[13,[32,50]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[[34,[33]]],[[13,[[35,[33]],55]]]],[[[31,[[34,[33]]]]],[[13,[[36,[33]],55]]]],[[[35,[33]]],[[13,[44,[35,[33]]]]]],[[[44,[33]]],[[13,[44,[44,[33]]]]]],[[[45,[33,33]]],[[13,[[45,[33,33]],[45,[33,33]]]]]],[[[46,[33]]],[[13,[[47,[33,33]],[46,[33]]]]]],[[[47,[33,33]]],[[13,[[47,[33,33]],[47,[33,33]]]]]],[[[38,[33]]],[[13,[[38,[33]],[38,[33]]]]]],[[[39,[33]]],[[13,[[48,[33]],[39,[33]]]]]],[[[48,[33]]],[[13,[[48,[33]],[48,[33]]]]]],[[[37,[33]]],[[13,[[38,[33]],55]]]],[[[31,[[37,[33]]]]],[[13,[[45,[33]],55]]]],[[[37,[33]]],[[13,[[39,[33]],55]]]],[[[31,[[37,[33]]]]],[[13,[[46,[33]],55]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[54,42],0,[[33,22],[[37,[33]]]],[[[37,[33]]],[[39,[33]]]],[[[31,[[37,[33]]]]],[[46,[33]]]],0,0,0,0,[[]],[[]],[[]],[[]],[3],[59,59],[[]],[59],[60],0,[[59,5],6],[[60,5],6],[[]],[[]],[[]],[[]],[60,7],[60,3],[59,3],[[[60,[40]]],[[13,[40,61]]]],[[[60,[40]]],[[60,[40]]]],[59,[[13,[3,62]]]],[59,60],[[]],[[],13],[[],13],[[],13],[[],13],[[[60,[40]]],[[13,[40,63]]]],[[],15],[[],15],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[61,61],[63,63],[[]],[[]],[[61,61],7],[[63,63],7],[[62,5],6],[[[62,[52]],5],6],[[61,5],6],[[61,5],6],[[63,5],6],[[63,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[24],[24],[24],[[]],[[]],[[],26],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,[[]],[[]],[58],[[[4,[58]]],7],[[58,5],6],[[]],[[]],[[]],[[[4,[58]],8],11],[[],13],[[],13],[[],15],0,0,0,0,0,0,0,0,[64,57],[65,57],[66,[[13,[67]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[66,3],[3],[66,66],[68,68],[69,69],[70,70],[[]],[[]],[[]],[[]],[64],[65],[66],[69],[66,68],[69,70],[71],[72],0,[[66,5],6],[[68,5],6],[[71,5],6],[[72,5],6],[[64,5],6],[[69,5],6],[[70,5],6],[[65,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[66,7],[69,7],[66,3],[[64,8],[[11,[57]]]],[[65,8],[[11,[57]]]],[64,57],[65,57],[72,66],[66,[[13,[71,67]]]],[66,[[13,[72,67]]]],[[66,66],7],[[69,69],7],[66,[[13,[67]]]],[71],[72,66],[69,[[13,[67]]]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[64,[[13,[73]]]],[65,[[13,[73]]]],[66,[[13,[71,74]]]],[66,[[13,[72,[74,[66]]]]]],[66,[[13,[74]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[]],[68,[[57,[66]]]],[70,[[57,[69]]]],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[73,73],[[]],[[[74,[49]],74],7],[[73,73],7],[[67,5],6],[[[67,[52]],5],6],[[[74,[52]],5],6],[[74,5],6],[[73,5],6],[[73,5],6],[[]],[[]],[67,74],[[]],[[]],[[]],[[]],[24],[24],[24],[[]],[[],26],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,0,[75,[[13,[76]]]],[[]],[[]],[[]],[[]],[[]],[75],[77],[77],[75],0,[[[77,[52]],5],6],[[[75,[52]],5],6],[[]],[[]],[[]],[[]],[[]],[77,7],[[[4,[75]],8],11],[[77,8],11],[77,13],[[],13],[[],13],[[],13],[[],13],[75,[[13,[78]]]],[[],15],[[],15],0,0,0,0,[[]],[[]],[[]],[[]],[76,76],[78,78],[[]],[[]],[[76,76],7],[[78,78],7],[[76,5],6],[[76,5],6],[[78,5],6],[[78,5],6],[[]],[[]],[[]],[[]],[24],[24],[[]],[[]],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],0,0,0,[[]],[79,80],[[]],[81,80],[[]],[79,80],[[]],[[]],[[]],[79,[[13,[82]]]],[[]],[79,79],[[]],[81],[80],[79],[81],0,[[[79,[52]],5],6],[[[81,[52]],5],6],[[[80,[52]],5],6],[[]],[[]],[[]],[79,[[13,[7,82]]]],[80,7],[[]],[[]],[[]],[81,7],[81,3],[[79,79],7],[81,[[13,[83]]]],[81,7],[81],[81],[81,79],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],[[],15],0,0,0,[[]],[[]],[[]],[[]],[82,82],[[]],[[[83,[52]],5],6],[[[83,[52]],5],6],[[82,5],6],[[82,5],6],[[]],[[]],[[]],[[]],[24],[24],[[]],[[],26],[[],26],[[],13],[[],13],[[],13],[[],13],[[],15],[[],15],0,0,0,0,0,0,0,0,[84],[1],[85],[[86,18]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],85],[[],86],[85],[84],[1],[87],[85],[86],[86,87],[[84,5],6],[[88,5],6],[[88,5],6],[[1,5],6],[[87,5],6],[[89,5],6],[[85,5],6],[[86,5],6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[[89,[90]]],90],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[88,[[92,[91]]]],[88,7],[85,7],[84,7],[1,7],[88,7],[85,[[57,[[13,[88]]]]]],[85,3],[[],85],[[],86],[[[4,[1]],8],11],[[[4,[86]],8],11],[[[4,[93]],8],11],[24],[86],[89,94],[85],[[],1],[85,84],[[],1],[[],1],[85,84],[86,1],[[85,86],84],[[85,16],84],[89],[[],26],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[88,[[13,[[92,[91]],88]]]],[89,[[13,[0]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],93],[89],[[]],0,[[]],[[]],[94],[[94,5],6],[[]],[[]],[[]],[[[4,[[94,[17]]]],8],11],[[],13],[[],13],[[],15]],"p":[[3,"JoinHandle"],[3,"ReadBuf"],[15,"usize"],[3,"Pin"],[3,"Formatter"],[6,"Result"],[15,"bool"],[3,"Context"],[15,"u64"],[6,"Result"],[4,"Poll"],[3,"Error"],[4,"Result"],[4,"SeekFrom"],[3,"TypeId"],[3,"Handle"],[8,"Future"],[3,"Runtime"],[3,"Builder"],[3,"EnterGuard"],[4,"RuntimeFlavor"],[15,"u32"],[3,"TryCurrentError"],[3,"Demand"],[3,"Duration"],[3,"String"],[8,"Into"],[3,"Semaphore"],[3,"SemaphorePermit"],[3,"AcquireError"],[3,"Arc"],[3,"OwnedSemaphorePermit"],[8,"Sized"],[3,"Mutex"],[3,"MutexGuard"],[3,"OwnedMutexGuard"],[3,"RwLock"],[3,"RwLockReadGuard"],[3,"RwLockWriteGuard"],[8,"Clone"],[3,"OnceCell"],[3,"BarrierWaitResult"],[3,"Notify"],[3,"MappedMutexGuard"],[3,"OwnedRwLockReadGuard"],[3,"OwnedRwLockWriteGuard"],[3,"OwnedRwLockMappedWriteGuard"],[3,"RwLockMappedWriteGuard"],[8,"PartialEq"],[4,"TryAcquireError"],[4,"SetError"],[8,"Debug"],[8,"Display"],[3,"Barrier"],[3,"TryLockError"],[15,"never"],[4,"Option"],[3,"Notified"],[3,"Sender"],[3,"Receiver"],[4,"RecvError"],[3,"SendError"],[4,"TryRecvError"],[3,"Receiver"],[3,"UnboundedReceiver"],[3,"Sender"],[3,"SendError"],[3,"WeakSender"],[3,"UnboundedSender"],[3,"WeakUnboundedSender"],[3,"Permit"],[3,"OwnedPermit"],[4,"TryRecvError"],[4,"TrySendError"],[3,"Receiver"],[3,"RecvError"],[3,"Sender"],[4,"TryRecvError"],[3,"Receiver"],[3,"Ref"],[3,"Sender"],[3,"RecvError"],[3,"SendError"],[3,"AbortHandle"],[3,"JoinSet"],[3,"LocalSet"],[3,"LocalEnterGuard"],[3,"JoinError"],[3,"LocalKey"],[8,"Copy"],[8,"Any"],[3,"Box"],[3,"Unconstrained"],[3,"TaskLocalFuture"],[8,"AsyncBufRead"],[8,"AsyncWrite"],[8,"AsyncSeek"],[8,"AsyncRead"],[13,"AlreadyInitializedError"],[13,"InitializingError"],[13,"Lagged"],[13,"Lagged"],[13,"Full"],[13,"Closed"]]},\ -"tokio_macros":{"doc":"Macros for use with Tokio","t":[23,23,23,23,23,23],"n":["main","main_fail","main_rt","test","test_fail","test_rt"],"q":["tokio_macros","","","","",""],"d":["Marks async function to be executed by the selected …","Always fails with the error message below.","Marks async function to be executed by selected runtime. …","Marks async function to be executed by runtime, suitable …","Always fails with the error message below.","Marks async function to be executed by runtime, suitable …"],"i":[0,0,0,0,0,0],"f":[0,0,0,0,0,0],"p":[]},\ -"typed_builder":{"doc":"","t":[24],"n":["TypedBuilder"],"q":["typed_builder"],"d":["TypedBuilder is not a real type - deriving it will …"],"i":[0],"f":[0],"p":[]},\ -"typenum":{"doc":"This crate provides type-level numbers evaluated at …","t":[2,2,2,3,6,3,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,2,2,0,14,14,0,11,11,11,11,11,11,11,11,11,11,11,11,14,0,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,0,11,11,11,0,14,0,11,11,11,14,11,11,11,11,11,11,11,11,11,11,11,11,0,0,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,2,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,2,11,11,11,2,3,3,3,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,18,8,18,18,18,18,18,18,18,18,18,18,8,8,8,8,8,18,18,18,18,18,18,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,8,8,8,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,8,8,8,8,8,10,10,10,10,10,10,10,10,10,10,10,10,8,6,16,16,2,8,6,3,3,2,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11],"n":["ATerm","B0","B1","Equal","False","Greater","Less","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","NInt","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","PInt","TArr","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","UInt","UTerm","Z0","array","assert_type","assert_type_eq","bit","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","cmp","cmp","cmp","cmp","consts","default","default","default","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","int","into","into","into","marker_traits","op","operator_aliases","partial_cmp","partial_cmp","partial_cmp","tarr","to_ordering","to_ordering","to_ordering","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_operators","uint","ATerm","TArr","add","add","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","div","div","eq","eq","fmt","fmt","from","from","hash","hash","into","into","len","len","mul","mul","neg","neg","partial_cmp","partial_cmp","partial_div","partial_div","rem","rem","sub","sub","try_from","try_from","try_into","try_into","type_id","type_id","B0","B1","Bit","bitand","bitand","bitand","bitor","bitor","bitor","bitxor","bitxor","bitxor","bitxor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","default","default","eq","eq","fmt","fmt","from","from","hash","hash","into","into","max","max","max","max","min","min","min","min","new","new","new","new","not","not","partial_cmp","partial_cmp","to_bool","to_bool","to_u8","to_u8","try_from","try_from","try_into","try_into","type_id","type_id","B0","B1","False","N1","N10","N100","N1000","N10000","N100000","N1000000","N10000000","N100000000","N1000000000","N10000000000","N100000000000","N1000000000000","N10000000000000","N100000000000000","N1000000000000000","N10000000000000000","N100000000000000000","N1000000000000000000","N1001","N1002","N1003","N1004","N1005","N1006","N1007","N1008","N1009","N101","N1010","N1011","N1012","N1013","N1014","N1015","N1016","N1017","N1018","N1019","N102","N1020","N1021","N1022","N1023","N1024","N103","N104","N1048576","N105","N106","N107","N1073741824","N108","N109","N1099511627776","N11","N110","N111","N112","N1125899906842624","N113","N114","N115","N1152921504606846976","N116","N117","N118","N119","N12","N120","N121","N122","N123","N124","N125","N126","N127","N128","N129","N13","N130","N131","N131072","N132","N133","N134","N134217728","N135","N136","N137","N137438953472","N138","N139","N14","N140","N140737488355328","N141","N142","N143","N144","N144115188075855872","N145","N146","N147","N148","N149","N15","N150","N151","N152","N153","N154","N155","N156","N157","N158","N159","N16","N160","N161","N162","N163","N16384","N164","N165","N166","N167","N16777216","N168","N169","N17","N170","N171","N17179869184","N172","N173","N174","N175","N17592186044416","N176","N177","N178","N179","N18","N180","N18014398509481984","N181","N182","N183","N184","N185","N186","N187","N188","N189","N19","N190","N191","N192","N193","N194","N195","N196","N197","N198","N199","N2","N20","N200","N201","N202","N203","N204","N2048","N205","N206","N207","N208","N209","N2097152","N21","N210","N211","N212","N213","N214","N2147483648","N215","N216","N217","N218","N219","N2199023255552","N22","N220","N221","N222","N223","N224","N225","N2251799813685248","N226","N227","N228","N229","N23","N230","N2305843009213693952","N231","N232","N233","N234","N235","N236","N237","N238","N239","N24","N240","N241","N242","N243","N244","N245","N246","N247","N248","N249","N25","N250","N251","N252","N253","N254","N255","N256","N257","N258","N259","N26","N260","N261","N262","N262144","N263","N264","N265","N266","N267","N268","N268435456","N269","N27","N270","N271","N272","N273","N274","N274877906944","N275","N276","N277","N278","N279","N28","N280","N281","N281474976710656","N282","N283","N284","N285","N286","N287","N288","N288230376151711744","N289","N29","N290","N291","N292","N293","N294","N295","N296","N297","N298","N299","N3","N30","N300","N301","N302","N303","N304","N305","N306","N307","N308","N309","N31","N310","N311","N312","N313","N314","N315","N316","N317","N318","N319","N32","N320","N321","N322","N323","N324","N325","N326","N327","N32768","N328","N329","N33","N330","N331","N332","N333","N334","N335","N33554432","N336","N337","N338","N339","N34","N340","N341","N342","N343","N34359738368","N344","N345","N346","N347","N348","N349","N35","N350","N351","N35184372088832","N352","N353","N354","N355","N356","N357","N358","N359","N36","N360","N36028797018963968","N361","N362","N363","N364","N365","N366","N367","N368","N369","N37","N370","N371","N372","N373","N374","N375","N376","N377","N378","N379","N38","N380","N381","N382","N383","N384","N385","N386","N387","N388","N389","N39","N390","N391","N392","N393","N394","N395","N396","N397","N398","N399","N4","N40","N400","N401","N402","N403","N404","N405","N406","N407","N408","N409","N4096","N41","N410","N411","N412","N413","N414","N415","N416","N417","N418","N419","N4194304","N42","N420","N421","N422","N423","N424","N425","N426","N427","N428","N429","N4294967296","N43","N430","N431","N432","N433","N434","N435","N436","N437","N438","N439","N4398046511104","N44","N440","N441","N442","N443","N444","N445","N446","N447","N448","N449","N45","N450","N4503599627370496","N451","N452","N453","N454","N455","N456","N457","N458","N459","N46","N460","N461","N4611686018427387904","N462","N463","N464","N465","N466","N467","N468","N469","N47","N470","N471","N472","N473","N474","N475","N476","N477","N478","N479","N48","N480","N481","N482","N483","N484","N485","N486","N487","N488","N489","N49","N490","N491","N492","N493","N494","N495","N496","N497","N498","N499","N5","N50","N500","N501","N502","N503","N504","N505","N506","N507","N508","N509","N51","N510","N511","N512","N513","N514","N515","N516","N517","N518","N519","N52","N520","N521","N522","N523","N524","N524288","N525","N526","N527","N528","N529","N53","N530","N531","N532","N533","N534","N535","N536","N536870912","N537","N538","N539","N54","N540","N541","N542","N543","N544","N545","N546","N547","N548","N549","N549755813888","N55","N550","N551","N552","N553","N554","N555","N556","N557","N558","N559","N56","N560","N561","N562","N562949953421312","N563","N564","N565","N566","N567","N568","N569","N57","N570","N571","N572","N573","N574","N575","N576","N576460752303423488","N577","N578","N579","N58","N580","N581","N582","N583","N584","N585","N586","N587","N588","N589","N59","N590","N591","N592","N593","N594","N595","N596","N597","N598","N599","N6","N60","N600","N601","N602","N603","N604","N605","N606","N607","N608","N609","N61","N610","N611","N612","N613","N614","N615","N616","N617","N618","N619","N62","N620","N621","N622","N623","N624","N625","N626","N627","N628","N629","N63","N630","N631","N632","N633","N634","N635","N636","N637","N638","N639","N64","N640","N641","N642","N643","N644","N645","N646","N647","N648","N649","N65","N650","N651","N652","N653","N654","N655","N65536","N656","N657","N658","N659","N66","N660","N661","N662","N663","N664","N665","N666","N667","N668","N669","N67","N670","N671","N67108864","N672","N673","N674","N675","N676","N677","N678","N679","N68","N680","N681","N682","N683","N684","N685","N686","N687","N68719476736","N688","N689","N69","N690","N691","N692","N693","N694","N695","N696","N697","N698","N699","N7","N70","N700","N701","N702","N703","N70368744177664","N704","N705","N706","N707","N708","N709","N71","N710","N711","N712","N713","N714","N715","N716","N717","N718","N719","N72","N720","N72057594037927936","N721","N722","N723","N724","N725","N726","N727","N728","N729","N73","N730","N731","N732","N733","N734","N735","N736","N737","N738","N739","N74","N740","N741","N742","N743","N744","N745","N746","N747","N748","N749","N75","N750","N751","N752","N753","N754","N755","N756","N757","N758","N759","N76","N760","N761","N762","N763","N764","N765","N766","N767","N768","N769","N77","N770","N771","N772","N773","N774","N775","N776","N777","N778","N779","N78","N780","N781","N782","N783","N784","N785","N786","N787","N788","N789","N79","N790","N791","N792","N793","N794","N795","N796","N797","N798","N799","N8","N80","N800","N801","N802","N803","N804","N805","N806","N807","N808","N809","N81","N810","N811","N812","N813","N814","N815","N816","N817","N818","N819","N8192","N82","N820","N821","N822","N823","N824","N825","N826","N827","N828","N829","N83","N830","N831","N832","N833","N834","N835","N836","N837","N838","N8388608","N839","N84","N840","N841","N842","N843","N844","N845","N846","N847","N848","N849","N85","N850","N851","N852","N853","N854","N855","N856","N857","N858","N8589934592","N859","N86","N860","N861","N862","N863","N864","N865","N866","N867","N868","N869","N87","N870","N871","N872","N873","N874","N875","N876","N877","N878","N879","N8796093022208","N88","N880","N881","N882","N883","N884","N885","N886","N887","N888","N889","N89","N890","N891","N892","N893","N894","N895","N896","N897","N898","N899","N9","N90","N900","N9007199254740992","N901","N902","N903","N904","N905","N906","N907","N908","N909","N91","N910","N911","N912","N913","N914","N915","N916","N917","N918","N919","N92","N920","N921","N922","N923","N924","N925","N926","N927","N928","N929","N93","N930","N931","N932","N933","N934","N935","N936","N937","N938","N939","N94","N940","N941","N942","N943","N944","N945","N946","N947","N948","N949","N95","N950","N951","N952","N953","N954","N955","N956","N957","N958","N959","N96","N960","N961","N962","N963","N964","N965","N966","N967","N968","N969","N97","N970","N971","N972","N973","N974","N975","N976","N977","N978","N979","N98","N980","N981","N982","N983","N984","N985","N986","N987","N988","N989","N99","N990","N991","N992","N993","N994","N995","N996","N997","N998","N999","P1","P10","P100","P1000","P10000","P100000","P1000000","P10000000","P100000000","P1000000000","P10000000000","P100000000000","P1000000000000","P10000000000000","P100000000000000","P1000000000000000","P10000000000000000","P100000000000000000","P1000000000000000000","P1001","P1002","P1003","P1004","P1005","P1006","P1007","P1008","P1009","P101","P1010","P1011","P1012","P1013","P1014","P1015","P1016","P1017","P1018","P1019","P102","P1020","P1021","P1022","P1023","P1024","P103","P104","P1048576","P105","P106","P107","P1073741824","P108","P109","P1099511627776","P11","P110","P111","P112","P1125899906842624","P113","P114","P115","P1152921504606846976","P116","P117","P118","P119","P12","P120","P121","P122","P123","P124","P125","P126","P127","P128","P129","P13","P130","P131","P131072","P132","P133","P134","P134217728","P135","P136","P137","P137438953472","P138","P139","P14","P140","P140737488355328","P141","P142","P143","P144","P144115188075855872","P145","P146","P147","P148","P149","P15","P150","P151","P152","P153","P154","P155","P156","P157","P158","P159","P16","P160","P161","P162","P163","P16384","P164","P165","P166","P167","P16777216","P168","P169","P17","P170","P171","P17179869184","P172","P173","P174","P175","P17592186044416","P176","P177","P178","P179","P18","P180","P18014398509481984","P181","P182","P183","P184","P185","P186","P187","P188","P189","P19","P190","P191","P192","P193","P194","P195","P196","P197","P198","P199","P2","P20","P200","P201","P202","P203","P204","P2048","P205","P206","P207","P208","P209","P2097152","P21","P210","P211","P212","P213","P214","P2147483648","P215","P216","P217","P218","P219","P2199023255552","P22","P220","P221","P222","P223","P224","P225","P2251799813685248","P226","P227","P228","P229","P23","P230","P2305843009213693952","P231","P232","P233","P234","P235","P236","P237","P238","P239","P24","P240","P241","P242","P243","P244","P245","P246","P247","P248","P249","P25","P250","P251","P252","P253","P254","P255","P256","P257","P258","P259","P26","P260","P261","P262","P262144","P263","P264","P265","P266","P267","P268","P268435456","P269","P27","P270","P271","P272","P273","P274","P274877906944","P275","P276","P277","P278","P279","P28","P280","P281","P281474976710656","P282","P283","P284","P285","P286","P287","P288","P288230376151711744","P289","P29","P290","P291","P292","P293","P294","P295","P296","P297","P298","P299","P3","P30","P300","P301","P302","P303","P304","P305","P306","P307","P308","P309","P31","P310","P311","P312","P313","P314","P315","P316","P317","P318","P319","P32","P320","P321","P322","P323","P324","P325","P326","P327","P32768","P328","P329","P33","P330","P331","P332","P333","P334","P335","P33554432","P336","P337","P338","P339","P34","P340","P341","P342","P343","P34359738368","P344","P345","P346","P347","P348","P349","P35","P350","P351","P35184372088832","P352","P353","P354","P355","P356","P357","P358","P359","P36","P360","P36028797018963968","P361","P362","P363","P364","P365","P366","P367","P368","P369","P37","P370","P371","P372","P373","P374","P375","P376","P377","P378","P379","P38","P380","P381","P382","P383","P384","P385","P386","P387","P388","P389","P39","P390","P391","P392","P393","P394","P395","P396","P397","P398","P399","P4","P40","P400","P401","P402","P403","P404","P405","P406","P407","P408","P409","P4096","P41","P410","P411","P412","P413","P414","P415","P416","P417","P418","P419","P4194304","P42","P420","P421","P422","P423","P424","P425","P426","P427","P428","P429","P4294967296","P43","P430","P431","P432","P433","P434","P435","P436","P437","P438","P439","P4398046511104","P44","P440","P441","P442","P443","P444","P445","P446","P447","P448","P449","P45","P450","P4503599627370496","P451","P452","P453","P454","P455","P456","P457","P458","P459","P46","P460","P461","P4611686018427387904","P462","P463","P464","P465","P466","P467","P468","P469","P47","P470","P471","P472","P473","P474","P475","P476","P477","P478","P479","P48","P480","P481","P482","P483","P484","P485","P486","P487","P488","P489","P49","P490","P491","P492","P493","P494","P495","P496","P497","P498","P499","P5","P50","P500","P501","P502","P503","P504","P505","P506","P507","P508","P509","P51","P510","P511","P512","P513","P514","P515","P516","P517","P518","P519","P52","P520","P521","P522","P523","P524","P524288","P525","P526","P527","P528","P529","P53","P530","P531","P532","P533","P534","P535","P536","P536870912","P537","P538","P539","P54","P540","P541","P542","P543","P544","P545","P546","P547","P548","P549","P549755813888","P55","P550","P551","P552","P553","P554","P555","P556","P557","P558","P559","P56","P560","P561","P562","P562949953421312","P563","P564","P565","P566","P567","P568","P569","P57","P570","P571","P572","P573","P574","P575","P576","P576460752303423488","P577","P578","P579","P58","P580","P581","P582","P583","P584","P585","P586","P587","P588","P589","P59","P590","P591","P592","P593","P594","P595","P596","P597","P598","P599","P6","P60","P600","P601","P602","P603","P604","P605","P606","P607","P608","P609","P61","P610","P611","P612","P613","P614","P615","P616","P617","P618","P619","P62","P620","P621","P622","P623","P624","P625","P626","P627","P628","P629","P63","P630","P631","P632","P633","P634","P635","P636","P637","P638","P639","P64","P640","P641","P642","P643","P644","P645","P646","P647","P648","P649","P65","P650","P651","P652","P653","P654","P655","P65536","P656","P657","P658","P659","P66","P660","P661","P662","P663","P664","P665","P666","P667","P668","P669","P67","P670","P671","P67108864","P672","P673","P674","P675","P676","P677","P678","P679","P68","P680","P681","P682","P683","P684","P685","P686","P687","P68719476736","P688","P689","P69","P690","P691","P692","P693","P694","P695","P696","P697","P698","P699","P7","P70","P700","P701","P702","P703","P70368744177664","P704","P705","P706","P707","P708","P709","P71","P710","P711","P712","P713","P714","P715","P716","P717","P718","P719","P72","P720","P72057594037927936","P721","P722","P723","P724","P725","P726","P727","P728","P729","P73","P730","P731","P732","P733","P734","P735","P736","P737","P738","P739","P74","P740","P741","P742","P743","P744","P745","P746","P747","P748","P749","P75","P750","P751","P752","P753","P754","P755","P756","P757","P758","P759","P76","P760","P761","P762","P763","P764","P765","P766","P767","P768","P769","P77","P770","P771","P772","P773","P774","P775","P776","P777","P778","P779","P78","P780","P781","P782","P783","P784","P785","P786","P787","P788","P789","P79","P790","P791","P792","P793","P794","P795","P796","P797","P798","P799","P8","P80","P800","P801","P802","P803","P804","P805","P806","P807","P808","P809","P81","P810","P811","P812","P813","P814","P815","P816","P817","P818","P819","P8192","P82","P820","P821","P822","P823","P824","P825","P826","P827","P828","P829","P83","P830","P831","P832","P833","P834","P835","P836","P837","P838","P8388608","P839","P84","P840","P841","P842","P843","P844","P845","P846","P847","P848","P849","P85","P850","P851","P852","P853","P854","P855","P856","P857","P858","P8589934592","P859","P86","P860","P861","P862","P863","P864","P865","P866","P867","P868","P869","P87","P870","P871","P872","P873","P874","P875","P876","P877","P878","P879","P8796093022208","P88","P880","P881","P882","P883","P884","P885","P886","P887","P888","P889","P89","P890","P891","P892","P893","P894","P895","P896","P897","P898","P899","P9","P90","P900","P9007199254740992","P901","P902","P903","P904","P905","P906","P907","P908","P909","P91","P910","P911","P912","P913","P914","P915","P916","P917","P918","P919","P92","P920","P921","P922","P923","P924","P925","P926","P927","P928","P929","P93","P930","P931","P932","P933","P934","P935","P936","P937","P938","P939","P94","P940","P941","P942","P943","P944","P945","P946","P947","P948","P949","P95","P950","P951","P952","P953","P954","P955","P956","P957","P958","P959","P96","P960","P961","P962","P963","P964","P965","P966","P967","P968","P969","P97","P970","P971","P972","P973","P974","P975","P976","P977","P978","P979","P98","P980","P981","P982","P983","P984","P985","P986","P987","P988","P989","P99","P990","P991","P992","P993","P994","P995","P996","P997","P998","P999","True","U0","U1","U10","U100","U1000","U10000","U100000","U1000000","U10000000","U100000000","U1000000000","U10000000000","U100000000000","U1000000000000","U10000000000000","U100000000000000","U1000000000000000","U10000000000000000","U100000000000000000","U1000000000000000000","U10000000000000000000","U1001","U1002","U1003","U1004","U1005","U1006","U1007","U1008","U1009","U101","U1010","U1011","U1012","U1013","U1014","U1015","U1016","U1017","U1018","U1019","U102","U1020","U1021","U1022","U1023","U1024","U103","U104","U1048576","U105","U106","U107","U1073741824","U108","U109","U1099511627776","U11","U110","U111","U112","U1125899906842624","U113","U114","U115","U1152921504606846976","U116","U117","U118","U119","U12","U120","U121","U122","U123","U124","U125","U126","U127","U128","U129","U13","U130","U131","U131072","U132","U133","U134","U134217728","U135","U136","U137","U137438953472","U138","U139","U14","U140","U140737488355328","U141","U142","U143","U144","U144115188075855872","U145","U146","U147","U148","U149","U15","U150","U151","U152","U153","U154","U155","U156","U157","U158","U159","U16","U160","U161","U162","U163","U16384","U164","U165","U166","U167","U16777216","U168","U169","U17","U170","U171","U17179869184","U172","U173","U174","U175","U17592186044416","U176","U177","U178","U179","U18","U180","U18014398509481984","U181","U182","U183","U184","U185","U186","U187","U188","U189","U19","U190","U191","U192","U193","U194","U195","U196","U197","U198","U199","U2","U20","U200","U201","U202","U203","U204","U2048","U205","U206","U207","U208","U209","U2097152","U21","U210","U211","U212","U213","U214","U2147483648","U215","U216","U217","U218","U219","U2199023255552","U22","U220","U221","U222","U223","U224","U225","U2251799813685248","U226","U227","U228","U229","U23","U230","U2305843009213693952","U231","U232","U233","U234","U235","U236","U237","U238","U239","U24","U240","U241","U242","U243","U244","U245","U246","U247","U248","U249","U25","U250","U251","U252","U253","U254","U255","U256","U257","U258","U259","U26","U260","U261","U262","U262144","U263","U264","U265","U266","U267","U268","U268435456","U269","U27","U270","U271","U272","U273","U274","U274877906944","U275","U276","U277","U278","U279","U28","U280","U281","U281474976710656","U282","U283","U284","U285","U286","U287","U288","U288230376151711744","U289","U29","U290","U291","U292","U293","U294","U295","U296","U297","U298","U299","U3","U30","U300","U301","U302","U303","U304","U305","U306","U307","U308","U309","U31","U310","U311","U312","U313","U314","U315","U316","U317","U318","U319","U32","U320","U321","U322","U323","U324","U325","U326","U327","U32768","U328","U329","U33","U330","U331","U332","U333","U334","U335","U33554432","U336","U337","U338","U339","U34","U340","U341","U342","U343","U34359738368","U344","U345","U346","U347","U348","U349","U35","U350","U351","U35184372088832","U352","U353","U354","U355","U356","U357","U358","U359","U36","U360","U36028797018963968","U361","U362","U363","U364","U365","U366","U367","U368","U369","U37","U370","U371","U372","U373","U374","U375","U376","U377","U378","U379","U38","U380","U381","U382","U383","U384","U385","U386","U387","U388","U389","U39","U390","U391","U392","U393","U394","U395","U396","U397","U398","U399","U4","U40","U400","U401","U402","U403","U404","U405","U406","U407","U408","U409","U4096","U41","U410","U411","U412","U413","U414","U415","U416","U417","U418","U419","U4194304","U42","U420","U421","U422","U423","U424","U425","U426","U427","U428","U429","U4294967296","U43","U430","U431","U432","U433","U434","U435","U436","U437","U438","U439","U4398046511104","U44","U440","U441","U442","U443","U444","U445","U446","U447","U448","U449","U45","U450","U4503599627370496","U451","U452","U453","U454","U455","U456","U457","U458","U459","U46","U460","U461","U4611686018427387904","U462","U463","U464","U465","U466","U467","U468","U469","U47","U470","U471","U472","U473","U474","U475","U476","U477","U478","U479","U48","U480","U481","U482","U483","U484","U485","U486","U487","U488","U489","U49","U490","U491","U492","U493","U494","U495","U496","U497","U498","U499","U5","U50","U500","U501","U502","U503","U504","U505","U506","U507","U508","U509","U51","U510","U511","U512","U513","U514","U515","U516","U517","U518","U519","U52","U520","U521","U522","U523","U524","U524288","U525","U526","U527","U528","U529","U53","U530","U531","U532","U533","U534","U535","U536","U536870912","U537","U538","U539","U54","U540","U541","U542","U543","U544","U545","U546","U547","U548","U549","U549755813888","U55","U550","U551","U552","U553","U554","U555","U556","U557","U558","U559","U56","U560","U561","U562","U562949953421312","U563","U564","U565","U566","U567","U568","U569","U57","U570","U571","U572","U573","U574","U575","U576","U576460752303423488","U577","U578","U579","U58","U580","U581","U582","U583","U584","U585","U586","U587","U588","U589","U59","U590","U591","U592","U593","U594","U595","U596","U597","U598","U599","U6","U60","U600","U601","U602","U603","U604","U605","U606","U607","U608","U609","U61","U610","U611","U612","U613","U614","U615","U616","U617","U618","U619","U62","U620","U621","U622","U623","U624","U625","U626","U627","U628","U629","U63","U630","U631","U632","U633","U634","U635","U636","U637","U638","U639","U64","U640","U641","U642","U643","U644","U645","U646","U647","U648","U649","U65","U650","U651","U652","U653","U654","U655","U65536","U656","U657","U658","U659","U66","U660","U661","U662","U663","U664","U665","U666","U667","U668","U669","U67","U670","U671","U67108864","U672","U673","U674","U675","U676","U677","U678","U679","U68","U680","U681","U682","U683","U684","U685","U686","U687","U68719476736","U688","U689","U69","U690","U691","U692","U693","U694","U695","U696","U697","U698","U699","U7","U70","U700","U701","U702","U703","U70368744177664","U704","U705","U706","U707","U708","U709","U71","U710","U711","U712","U713","U714","U715","U716","U717","U718","U719","U72","U720","U72057594037927936","U721","U722","U723","U724","U725","U726","U727","U728","U729","U73","U730","U731","U732","U733","U734","U735","U736","U737","U738","U739","U74","U740","U741","U742","U743","U744","U745","U746","U747","U748","U749","U75","U750","U751","U752","U753","U754","U755","U756","U757","U758","U759","U76","U760","U761","U762","U763","U764","U765","U766","U767","U768","U769","U77","U770","U771","U772","U773","U774","U775","U776","U777","U778","U779","U78","U780","U781","U782","U783","U784","U785","U786","U787","U788","U789","U79","U790","U791","U792","U793","U794","U795","U796","U797","U798","U799","U8","U80","U800","U801","U802","U803","U804","U805","U806","U807","U808","U809","U81","U810","U811","U812","U813","U814","U815","U816","U817","U818","U819","U8192","U82","U820","U821","U822","U823","U824","U825","U826","U827","U828","U829","U83","U830","U831","U832","U833","U834","U835","U836","U837","U838","U8388608","U839","U84","U840","U841","U842","U843","U844","U845","U846","U847","U848","U849","U85","U850","U851","U852","U853","U854","U855","U856","U857","U858","U8589934592","U859","U86","U860","U861","U862","U863","U864","U865","U866","U867","U868","U869","U87","U870","U871","U872","U873","U874","U875","U876","U877","U878","U879","U8796093022208","U88","U880","U881","U882","U883","U884","U885","U886","U887","U888","U889","U89","U890","U891","U892","U893","U894","U895","U896","U897","U898","U899","U9","U90","U900","U9007199254740992","U901","U902","U903","U904","U905","U906","U907","U908","U909","U91","U910","U911","U912","U913","U914","U915","U916","U917","U918","U919","U92","U920","U921","U922","U9223372036854775808","U923","U924","U925","U926","U927","U928","U929","U93","U930","U931","U932","U933","U934","U935","U936","U937","U938","U939","U94","U940","U941","U942","U943","U944","U945","U946","U947","U948","U949","U95","U950","U951","U952","U953","U954","U955","U956","U957","U958","U959","U96","U960","U961","U962","U963","U964","U965","U966","U967","U968","U969","U97","U970","U971","U972","U973","U974","U975","U976","U977","U978","U979","U98","U980","U981","U982","U983","U984","U985","U986","U987","U988","U989","U99","U990","U991","U992","U993","U994","U995","U996","U997","U998","U999","Z0","powi","powi","powi","Integer","NInt","PInt","Z0","add","add","add","add","add","add","add","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","cmp","cmp","cmp","default","default","default","div","div","div","div","div","eq","eq","eq","fmt","fmt","fmt","from","from","from","hash","hash","hash","into","into","into","max","max","max","max","max","max","max","max","max","min","min","min","min","min","min","min","min","min","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","mul","neg","neg","neg","new","new","new","partial_cmp","partial_cmp","partial_cmp","partial_div","partial_div","partial_div","powi","powi","powi","powi","powi","powi","powi","powi","rem","rem","rem","rem","rem","sub","sub","sub","sub","sub","sub","sub","sub","sub","to_i16","to_i16","to_i16","to_i32","to_i32","to_i32","to_i64","to_i64","to_i64","to_i8","to_i8","to_i8","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_isize","to_isize","to_isize","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","BOOL","Bit","I16","I16","I32","I32","I64","I64","I8","I8","ISIZE","ISIZE","Integer","NonZero","Ord","PowerOfTwo","TypeArray","U16","U32","U64","U8","U8","USIZE","Unsigned","Zero","new","to_bool","to_i16","to_i16","to_i32","to_i32","to_i64","to_i64","to_i8","to_i8","to_isize","to_isize","to_ordering","to_u16","to_u32","to_u64","to_u8","to_u8","to_usize","AbsVal","Add1","And","Compare","Cube","Diff","Double","Eq","Exp","Gcf","Gr","GrEq","Le","LeEq","Length","Log2","Maximum","Minimum","Mod","Negate","NotEq","Or","PartialQuot","Prod","Quot","Shleft","Shright","Sqrt","Square","Sub1","Sum","Xor","Abs","Cmp","Gcd","IsEqual","IsGreater","IsGreaterOrEqual","IsLess","IsLessOrEqual","IsNotEqual","Len","Logarithm2","Max","Min","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","Output","PartialDiv","Pow","Same","SquareRoot","ToInt","is_equal","is_greater","is_greater_or_equal","is_less","is_less_or_equal","is_not_equal","len","max","min","partial_div","powi","to_int","GetBit","GetBitOut","Output","Output","PowerOfTwo","SetBit","SetBitOut","UInt","UTerm","Unsigned","add","add","add","add","add","add","add","add","add","add","add","bitand","bitand","bitor","bitor","bitor","bitor","bitor","bitor","bitxor","bitxor","borrow","borrow","borrow_mut","borrow_mut","clone","clone","cmp","cmp","default","default","div","div","eq","eq","fmt","fmt","from","from","hash","hash","into","into","len","len","max","max","min","min","mul","mul","mul","mul","mul","mul","mul","mul","new","new","partial_cmp","partial_cmp","partial_div","partial_div","powi","powi","rem","rem","set_bit","set_bit","shl","shl","shl","shl","shl","shl","shl","shr","shr","shr","shr","shr","shr","shr","sub","sub","sub","sub","sub","sub","sub","to_i16","to_i16","to_i32","to_i32","to_i64","to_i64","to_i8","to_i8","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_int","to_isize","to_isize","to_u16","to_u16","to_u32","to_u32","to_u64","to_u64","to_u8","to_u8","to_usize","to_usize","try_from","try_from","try_into","try_into","type_id","type_id"],"q":["typenum","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::array","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::bit","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::consts","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::int","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::marker_traits","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::operator_aliases","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::type_operators","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","typenum::uint","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["","","","A potential output from Cmp, this is the type equivalent …","","A potential output from Cmp, this is the type equivalent …","A potential output from Cmp, this is the type equivalent …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A type-level array of type-level numbers.","Asserts that a type is True, aka B1.","Asserts that two types are the same.","Type-level bits.","","","","","","","","","","","","","A convenience macro for comparing type numbers. Use op! …","Type aliases for many constants.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Type-level signed integers.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","All of the marker traits used in typenum.","Convenient type operations.","Aliases for the type operators used in this crate. Their …","","","","Create a new type-level arrray. Only usable on Rust 1.13.0 …","","","","","","","","","","","","","Useful type operators that are not defined in core::ops.","Type-level unsigned integers.","The terminating type for type arrays.","TArr is a type that acts as an array of types. It is …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","The type-level bit 0.","The type-level bit 1.","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","Instantiates a singleton representing this bit.","","Instantiates a singleton representing this bit.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Type-level signed integers with negative sign.","Type-level signed integers with positive sign.","The type-level signed integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Instantiates a singleton representing this strictly …","Instantiates a singleton representing this strictly …","Instantiates a singleton representing the integer 0.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The marker trait for compile time bits.","","","","","","","","","","","The marker trait for compile time signed integers.","A marker trait to designate that a type is not zero. All …","A Marker trait for the types Greater, Equal, and Less.","The marker trait for type-level numbers which are a power …","The marker trait for type-level arrays of type-level …","","","","","","","The marker trait for compile time unsigned integers.","A marker trait to designate that a type is zero. Only B0, …","Instantiates a singleton representing this bit.","","","","","","","","","","","","","","","","","","","Alias for the associated type of Abs: …","Alias to make it easy to add 1: …","Alias for the associated type of BitAnd: …","Alias for the associated type of Cmp: …","Alias to make it easy to cube. …","Alias for the associated type of Sub: …","Alias to make it easy to multiply by 2. …","Alias for the associated type of IsEqual: …","Alias for the associated type of Pow: …","Alias for the associated type of Gcd: …","Alias for the associated type of IsGreater: …","Alias for the associated type of IsGreaterOrEqual: …","Alias for the associated type of IsLess: …","Alias for the associated type of IsLessOrEqual: …","Alias for the associated type of Len: …","Alias for the associated type of Logarithm2: …","Alias for the associated type of Max: …","Alias for the associated type of Min: …","Alias for the associated type of Rem: …","Alias for the associated type of Neg: …","Alias for the associated type of IsNotEqual: …","Alias for the associated type of BitOr: …","Alias for the associated type of PartialDiv: …","Alias for the associated type of Mul: …","Alias for the associated type of Div: …","Alias for the associated type of Shl: …","Alias for the associated type of Shr: …","Alias for the associated type of SquareRoot: …","Alias to make it easy to square. …","Alias to make it easy to subtract 1: …","Alias for the associated type of Add: …","Alias for the associated type of BitXor: …","A type operator that returns the absolute value.","A type operator for comparing Self and Rhs. It provides a …","A type operator that computes the greatest common divisor …","A type operator that returns True if Self == Rhs, …","A type operator that returns True if Self > Rhs, otherwise …","A type operator that returns True if Self >= Rhs, …","A type operator that returns True if Self < Rhs, otherwise …","A type operator that returns True if Self <= Rhs, …","A type operator that returns True if Self != Rhs, …","A type operator that gives the length of an Array or the …","A type operator for taking the integer binary logarithm of …","A type operator that returns the maximum of Self and Rhs.","A type operator that returns the minimum of Self and Rhs.","Should always be Self","The absolute value.","The result of the exponentiation.","The result of the comparison. It should only ever be one …","The length as a type-level unsigned integer.","The type of the result of the division","The type of the minimum of Self and Rhs","The type of the maximum of Self and Rhs","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The type representing either True or False","The result of the integer square root.","The result of the integer binary logarithm.","The greatest common divisor.","Division as a partial function. This type operator …","A type operator that provides exponentiation by repeated …","A type operator that ensures that Rhs is the same as Self, …","A type operator for taking the integer square root of Self.","A type operator for taking a concrete integer value from a …","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","Method returning True or False.","This function isn’t used in this crate, but may be …","Method returning the maximum","Method returning the minimum","Method for performing the division","This function isn’t used in this crate, but may be …","Method returning the concrete value for the type.","","","","","","A type operator that, when implemented for unsigned …","Alias for the result of calling SetBit: …","UInt is defined recursively, where B is the least …","The terminating type for UInt; it always comes after the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","Instantiates a singleton representing this unsigned …","Instantiates a singleton representing this unsigned …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,1,2,3,1,2,3,1,2,3,0,0,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,0,1,2,3,0,0,0,1,2,3,0,1,2,3,1,2,3,1,2,3,1,2,3,0,0,0,0,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,11,12,0,0,0,19,21,21,19,19,21,19,19,21,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,19,19,21,21,19,19,21,21,19,19,21,21,19,21,19,21,19,21,19,21,19,21,19,21,19,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,27,27,0,0,0,0,29,29,29,26,26,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,26,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,29,26,26,26,30,30,30,29,29,29,26,26,26,30,30,30,29,29,29,29,29,26,26,26,26,26,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,26,26,26,30,30,30,29,29,26,26,30,29,29,29,26,26,26,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,29,29,29,29,26,26,26,26,30,30,30,30,29,26,30,29,26,30,29,26,30,29,26,30,20,0,24,31,24,31,24,31,24,31,24,31,0,0,0,0,0,24,24,24,20,24,24,0,0,20,20,24,31,24,31,24,31,24,31,24,31,43,24,24,24,20,24,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,0,0,0,0,0,53,54,57,52,55,56,48,51,50,49,46,61,0,0,62,63,0,0,0,0,0,0,28,28,28,28,28,28,28,28,42,42,42,28,42,28,28,28,28,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,28,28,28,28,42,42,42,28,42,28,42,28,42,28,42,28,42,28,42,28,28,28,28,42,42,42,28,28,28,28,42,42,42,28,28,28,28,28,42,42,28,42,28,42,28,42,28,42,28,28,28,28,28,28,28,28,28,42,42,42,42,42,42,42,42,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42,28,42],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[2,2],[3,3],[[1,1],4],[[2,2],4],[[3,3],4],0,0,[[],1],[[],2],[[],3],[[1,1],5],[[2,2],5],[[3,3],5],[[1,6],7],[[2,6],7],[[3,6],7],[[]],[[]],[[]],[1],[2],[3],0,[[]],[[]],[[]],0,0,0,[[1,1],[[8,[4]]]],[[2,2],[[8,[4]]]],[[3,3],[[8,[4]]]],0,[[],4],[[],4],[[],4],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],[[],10],0,0,0,0,[[11,11]],[[12,12]],[[]],[[]],[[]],[[]],[11,11],[[[12,[13,13]]],[[12,[13,13]]]],[[11,11],4],[[[12,[14,14]],12],4],[11],[12],[[11,11],5],[[[12,[15,15]],12],5],[[11,6],7],[[[12,[16,16]],6],7],[[]],[[]],[11],[[[12,[17,17]]]],[[]],[[]],[11],[12],[11],[12],[11],[12],[[11,11],[[8,[4]]]],[[[12,[18,18]],12],[[8,[4]]]],[11],[12],[11],[12],[[11,11]],[[12,12]],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],0,0,0,[[19,20]],[[21,19]],[[21,21]],[[19,19]],[[19,21]],[[21,20]],[[19,19]],[[19,21]],[[21,21]],[[21,19]],[[]],[[]],[[]],[[]],[19,19],[21,21],[[19,19],4],[[21,21],4],[[],19],[[],21],[[19,19],5],[[21,21],5],[[19,6],7],[[21,6],7],[[]],[[]],[19],[21],[[]],[[]],[[19,21],21],[[19,19],19],[[21,19],21],[[21,21],21],[[19,21],19],[[19,19],19],[[21,19],19],[[21,21],21],[[],19],[[],19],[[],21],[[],21],[19],[21],[[19,19],[[8,[4]]]],[[21,21],[[8,[4]]]],[[],5],[[],5],[[],22],[[],22],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[23,[26,[[0,[24,25]]]]]],[[27,[26,[[28,[24,21]]]]]],[[27,[26,[[28,[24,19]]]]]],0,0,0,0,[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[30,31]],[[]],[[]],[[]],[[]],[[]],[[]],[[[29,[[0,[13,24,25]]]]],[[29,[[0,[13,24,25]]]]]],[[[26,[[0,[13,24,25]]]]],[[26,[[0,[13,24,25]]]]]],[30,30],[[[29,[[0,[14,24,25]]]],29],4],[[[26,[[0,[14,24,25]]]],26],4],[[30,30],4],[[],[[29,[[0,[32,24,25]]]]]],[[],[[26,[[0,[32,24,25]]]]]],[[],30],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[30,[0,[31,25]]]],[[[29,[[0,[15,24,25]]]],29],5],[[[26,[[0,[15,24,25]]]],26],5],[[30,30],5],[[[29,[[0,[16,24,25]]]],6],7],[[[26,[[0,[16,24,25]]]],6],7],[[30,6],7],[[]],[[]],[[]],[[[29,[[0,[17,24,25]]]]]],[[[26,[[0,[17,24,25]]]]]],[30],[[]],[[]],[[]],[[29,30]],[[29,29]],[[29,26]],[[26,26]],[[26,30]],[[26,29]],[[30,26]],[[30,29]],[[30,30]],[[29,26]],[[29,29]],[[29,30]],[[26,26]],[[26,29]],[[26,30]],[[30,30]],[[30,29]],[[30,26]],[[29,12]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[29,11]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[26,11]],[[26,12]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],30]],[[30,12]],[[30,11]],[[30,31]],[[[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]]]],[30],[[],[[29,[[0,[24,25]]]]]],[[],[[26,[[0,[24,25]]]]]],[[],30],[[[29,[[0,[18,24,25]]]],29],[[8,[4]]]],[[[26,[[0,[18,24,25]]]],26],[[8,[4]]]],[[30,30],[[8,[4]]]],[[]],[[]],[[]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[29,[[28,[24,21]]]]]],[[[26,[[0,[24,25]]]],[29,[[28,[24,19]]]]]],[[30,30]],[[30,[29,[[0,[24,25]]]]]],[[30,[26,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[30,[0,[31,25]]]],[[[29,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[[29,[[0,[24,25]]]],30]],[[[29,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],30]],[[[26,[[0,[24,25]]]],[26,[[0,[24,25]]]]]],[[[26,[[0,[24,25]]]],[29,[[0,[24,25]]]]]],[[30,[26,[[0,[24,25]]]]]],[[30,[29,[[0,[24,25]]]]]],[[30,30]],[[],33],[[],33],[[],33],[[],34],[[],34],[[],34],[[],35],[[],35],[[],35],[[],36],[[],36],[[],36],[[],34],[[],36],[[],35],[[],33],[[],36],[[],33],[[],34],[[],35],[[],36],[[],33],[[],34],[[],35],[[],37],[[],37],[[],37],[[],9],[[],9],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10],[[],10],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],5],[[],33],[[],33],[[],34],[[],34],[[],35],[[],35],[[],36],[[],36],[[],37],[[],37],[[],4],[[],38],[[],39],[[],40],[[],22],[[],22],[[],41],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[[[28,[24,20]],19]],[[[28,[24,19]],21]],[[[28,[24,21]],21]],[[[28,[24,20]],42]],[[[28,[24,19]],[28,[24,19]]]],[[[28,[24,19]],[28,[24,21]]]],[[[28,[24,21]],[28,[24,19]]]],[[[28,[24,21]],[28,[24,21]]]],[[42,19]],[[42,21]],[[42,24]],[[[28,[24,20]],24]],[[42,24]],[[[28,[24,20]],42]],[[[28,[24,21]],[28,[24,19]]]],[[[28,[24,21]],[28,[24,21]]]],[[[28,[24,19]],[28,[24,19]]]],[[[28,[24,19]],[28,[24,21]]]],[[42,24]],[[[28,[24,20]],24]],[[42,24]],[[]],[[]],[[]],[[]],[[[28,[13,13]]],[[28,[13,13]]]],[42,42],[[[28,[14,14]],28],4],[[42,42],4],[[],[[28,[32,32]]]],[[],42],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[[28,[15,15]],28],5],[[42,42],5],[[[28,[16,16]],6],7],[[42,6],7],[[]],[[]],[[[28,[17,17]]]],[42],[[]],[[]],[[[28,[24,20]]]],[42],[28],[42],[28],[42],[[[28,[24,21]],[28,[24,20]]]],[[[28,[24,20]],42]],[[[28,[24,19]],[28,[24,20]]]],[[[28,[24,20]],19]],[[[28,[24,20]],21]],[[42,21]],[[42,19]],[[42,24]],[[],[[28,[24,20]]]],[[],42],[[[28,[18,18]],28],[[8,[4]]]],[[42,42],[[8,[4]]]],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[]],[[]],[[[28,[24,20]],[28,[24,20]]]],[[42,[28,[24,20]]]],[[]],[[]],[[[28,[24,20]],21]],[[[28,[24,20]],[28,[24,20]]]],[[[28,[24,20]],42]],[[[28,[24,20]],19]],[[42,24]],[[42,19]],[[42,21]],[[[28,[24,20]],[28,[24,20]]]],[[[28,[24,20]],21]],[[[28,[24,20]],19]],[[[28,[24,20]],42]],[[42,19]],[[42,24]],[[42,21]],[[[28,[24,20]],19]],[[[28,[[28,[24,20]],21]],21]],[[[28,[42,21]],21]],[[[28,[24,20]],24]],[[[28,[24,19]],21]],[[42,19]],[[42,42]],[[],33],[[],33],[[],34],[[],34],[[],35],[[],35],[[],36],[[],36],[[],41],[[],36],[[],40],[[],33],[[],34],[[],35],[[],22],[[],38],[[],39],[[],40],[[],39],[[],33],[[],34],[[],36],[[],41],[[],35],[[],22],[[],38],[[],37],[[],37],[[],38],[[],38],[[],39],[[],39],[[],40],[[],40],[[],22],[[],22],[[],41],[[],41],[[],9],[[],9],[[],9],[[],9],[[],10],[[],10]],"p":[[3,"Greater"],[3,"Less"],[3,"Equal"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Option"],[4,"Result"],[3,"TypeId"],[3,"ATerm"],[3,"TArr"],[8,"Clone"],[8,"Ord"],[8,"PartialEq"],[8,"Debug"],[8,"Hash"],[8,"PartialOrd"],[3,"B0"],[8,"Bit"],[3,"B1"],[15,"u8"],[6,"P1"],[8,"Unsigned"],[8,"NonZero"],[3,"NInt"],[6,"N1"],[3,"UInt"],[3,"PInt"],[3,"Z0"],[8,"Integer"],[8,"Default"],[15,"i16"],[15,"i32"],[15,"i64"],[15,"i8"],[15,"isize"],[15,"u16"],[15,"u32"],[15,"u64"],[15,"usize"],[3,"UTerm"],[8,"Ord"],[8,"Same"],[8,"Abs"],[8,"Pow"],[8,"Cmp"],[8,"Len"],[8,"PartialDiv"],[8,"Min"],[8,"Max"],[8,"IsLess"],[8,"IsEqual"],[8,"IsGreater"],[8,"IsLessOrEqual"],[8,"IsNotEqual"],[8,"IsGreaterOrEqual"],[8,"SquareRoot"],[8,"Logarithm2"],[8,"Gcd"],[8,"ToInt"],[8,"GetBit"],[8,"SetBit"]]},\ -"uint":{"doc":"Efficient large, fixed-size big integers and hashes.","t":[4,3,3,4,13,13,13,13,13,11,11,11,11,11,11,11,11,11,11,14,14,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,14],"n":["FromDecStrErr","FromHexError","FromStrRadixErr","FromStrRadixErrKind","InvalidCharacter","InvalidCharacter","InvalidLength","InvalidLength","UnsupportedRadix","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","construct_uint","construct_uint","eq","eq","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","hash","into","into","into","into","kind","provide","provide","provide","source","source","to_owned","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unroll"],"q":["uint","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"d":["Conversion from decimal string error","","The error type for parsing numbers from strings.","A list of error categories encountered when parsing …","A character in the input string is not valid for the given …","Char not from range 0-9","The input length is not valid for the given radix.","Value does not fit into type","The given radix is not supported.","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the corresponding FromStrRadixErrKind for this …","","","","","","","","","","","","","","","","","","","","","","Unroll the given for loop"],"i":[0,0,0,0,1,3,1,3,1,1,6,3,7,1,6,3,7,1,1,0,0,1,3,1,6,6,3,3,7,7,1,6,6,6,3,7,1,1,6,3,7,6,6,3,7,6,7,1,6,3,7,1,6,3,7,1,6,3,7,1,6,3,7,0],"f":[0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[[]],0,0,[[1,1],2],[[3,3],2],[[1,4],5],[[6,4],5],[[6,4],5],[[3,4],5],[[3,4],5],[[7,4],5],[[7,4],5],[[]],[3,6],[[]],[7,6],[[]],[[]],[1],[[]],[[]],[[]],[[]],[6,1],[8],[8],[8],[6,[[10,[9]]]],[7,[[10,[9]]]],[[]],[[],11],[[],11],[[],11],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13],[[],13],[[],13],0],"p":[[4,"FromStrRadixErrKind"],[15,"bool"],[4,"FromDecStrErr"],[3,"Formatter"],[6,"Result"],[3,"FromStrRadixErr"],[3,"FromHexError"],[3,"Demand"],[8,"Error"],[4,"Option"],[3,"String"],[4,"Result"],[3,"TypeId"]]},\ -"unicode_ident":{"doc":"github crates-io docs-rs","t":[5,5],"n":["is_xid_continue","is_xid_start"],"q":["unicode_ident",""],"d":["",""],"i":[0,0],"f":[[1,2],[1,2]],"p":[[15,"char"],[15,"bool"]]}\ -}'); -if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)}; -if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; diff --git a/docs/search.js b/docs/search.js deleted file mode 100644 index f0ccdfb1bf10..000000000000 --- a/docs/search.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(function(){const itemTypes=["mod","externcrate","import","struct","enum","fn","type","static","trait","impl","tymethod","method","structfield","variant","macro","primitive","associatedtype","constant","associatedconstant","union","foreigntype","keyword","existential","attr","derive","traitalias",];const TY_PRIMITIVE=itemTypes.indexOf("primitive");const TY_KEYWORD=itemTypes.indexOf("keyword");const ROOT_PATH=typeof window!=="undefined"?window.rootPath:"../";function hasOwnPropertyRustdoc(obj,property){return Object.prototype.hasOwnProperty.call(obj,property)}function printTab(nb){let iter=0;let foundCurrentTab=false;let foundCurrentResultSet=false;onEachLazy(document.getElementById("titles").childNodes,elem=>{if(nb===iter){addClass(elem,"selected");foundCurrentTab=true}else{removeClass(elem,"selected")}iter+=1});iter=0;onEachLazy(document.getElementById("results").childNodes,elem=>{if(nb===iter){addClass(elem,"active");foundCurrentResultSet=true}else{removeClass(elem,"active")}iter+=1});if(foundCurrentTab&&foundCurrentResultSet){searchState.currentTab=nb}else if(nb!==0){printTab(0)}}const levenshtein_row2=[];function levenshtein(s1,s2){if(s1===s2){return 0}const s1_len=s1.length,s2_len=s2.length;if(s1_len&&s2_len){let i1=0,i2=0,a,b,c,c2;const row=levenshtein_row2;while(i1-".indexOf(c)!==-1}function isStopCharacter(c){return isWhitespace(c)||isEndCharacter(c)}function isErrorCharacter(c){return"()".indexOf(c)!==-1}function itemTypeFromName(typename){for(let i=0,len=itemTypes.length;i0){throw new Error("Cannot use literal search when there is more than one element")}parserState.pos+=1;const start=parserState.pos;const end=getIdentEndPosition(parserState);if(parserState.pos>=parserState.length){throw new Error("Unclosed `\"`")}else if(parserState.userQuery[end]!=="\""){throw new Error(`Unexpected \`${parserState.userQuery[end]}\` in a string element`)}else if(start===end){throw new Error("Cannot have empty string element")}parserState.pos+=1;query.literalSearch=true}function isPathStart(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="::"}function isReturnArrow(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="->"}function isIdentCharacter(c){return(c==="_"||(c>="0"&&c<="9")||(c>="a"&&c<="z")||(c>="A"&&c<="Z"))}function isSeparatorCharacter(c){return c===","||isWhitespaceCharacter(c)}function isWhitespaceCharacter(c){return c===" "||c==="\t"}function createQueryElement(query,parserState,name,generics,isInGenerics){if(name==="*"||(name.length===0&&generics.length===0)){return}if(query.literalSearch&&parserState.totalElems-parserState.genericsElems>0){throw new Error("You cannot have more than one element if you use quotes")}const pathSegments=name.split("::");if(pathSegments.length>1){for(let i=0,len=pathSegments.length;i=end){throw new Error("Found generics without a path")}parserState.pos+=1;getItemsBefore(query,parserState,generics,">")}if(start>=end&&generics.length===0){return}elems.push(createQueryElement(query,parserState,parserState.userQuery.slice(start,end),generics,isInGenerics))}function getItemsBefore(query,parserState,elems,endChar){let foundStopChar=true;while(parserState.pos"){extra="`<`"}else if(endChar===""){extra="`->`"}throw new Error("Unexpected `"+c+"` after "+extra)}if(!foundStopChar){if(endChar!==""){throw new Error(`Expected \`,\`, \` \` or \`${endChar}\`, found \`${c}\``)}throw new Error(`Expected \`,\` or \` \`, found \`${c}\``)}const posBefore=parserState.pos;getNextElem(query,parserState,elems,endChar===">");if(posBefore===parserState.pos){parserState.pos+=1}foundStopChar=false}parserState.pos+=1}function checkExtraTypeFilterCharacters(parserState){const query=parserState.userQuery;for(let pos=0;pos"){if(isReturnArrow(parserState)){break}throw new Error(`Unexpected \`${c}\` (did you mean \`->\`?)`)}throw new Error(`Unexpected \`${c}\``)}else if(c===":"&&!isPathStart(parserState)){if(parserState.typeFilter!==null){throw new Error("Unexpected `:`")}if(query.elems.length===0){throw new Error("Expected type filter before `:`")}else if(query.elems.length!==1||parserState.totalElems!==1){throw new Error("Unexpected `:`")}else if(query.literalSearch){throw new Error("You cannot use quotes on type filter")}checkExtraTypeFilterCharacters(parserState);parserState.typeFilter=query.elems.pop().name;parserState.pos+=1;parserState.totalElems=0;query.literalSearch=false;foundStopChar=true;continue}if(!foundStopChar){if(parserState.typeFilter!==null){throw new Error(`Expected \`,\`, \` \` or \`->\`, found \`${c}\``)}throw new Error(`Expected \`,\`, \` \`, \`:\` or \`->\`, found \`${c}\``)}before=query.elems.length;getNextElem(query,parserState,query.elems,false);if(query.elems.length===before){parserState.pos+=1}foundStopChar=false}while(parserState.pos`")}break}else{parserState.pos+=1}}}function newParsedQuery(userQuery){return{original:userQuery,userQuery:userQuery.toLowerCase(),typeFilter:NO_TYPE_FILTER,elems:[],returned:[],foundElems:0,literalSearch:false,error:null,}}function buildUrl(search,filterCrates){let extra="?search="+encodeURIComponent(search);if(filterCrates!==null){extra+="&filter-crate="+encodeURIComponent(filterCrates)}return getNakedUrl()+extra+window.location.hash}function getFilterCrates(){const elem=document.getElementById("crate-search");if(elem&&elem.value!=="all crates"&&hasOwnPropertyRustdoc(rawSearchIndex,elem.value)){return elem.value}return null}function parseQuery(userQuery){userQuery=userQuery.trim();const parserState={length:userQuery.length,pos:0,totalElems:0,genericsElems:0,typeFilter:null,userQuery:userQuery.toLowerCase(),};let query=newParsedQuery(userQuery);try{parseInput(query,parserState);if(parserState.typeFilter!==null){let typeFilter=parserState.typeFilter;if(typeFilter==="const"){typeFilter="constant"}query.typeFilter=itemTypeFromName(typeFilter)}}catch(err){query=newParsedQuery(userQuery);query.error=err.message;query.typeFilter=-1;return query}if(!query.literalSearch){query.literalSearch=parserState.totalElems>1}query.foundElems=query.elems.length+query.returned.length;return query}function createQueryResults(results_in_args,results_returned,results_others,parsedQuery){return{"in_args":results_in_args,"returned":results_returned,"others":results_others,"query":parsedQuery,}}function execQuery(parsedQuery,searchWords,filterCrates,currentCrate){const results_others={},results_in_args={},results_returned={};function transformResults(results){const duplicates={};const out=[];for(const result of results){if(result.id>-1){const obj=searchIndex[result.id];obj.lev=result.lev;const res=buildHrefAndPath(obj);obj.displayPath=pathSplitter(res[0]);obj.fullPath=obj.displayPath+obj.name;obj.fullPath+="|"+obj.ty;if(duplicates[obj.fullPath]){continue}duplicates[obj.fullPath]=true;obj.href=res[1];out.push(obj);if(out.length>=MAX_RESULTS){break}}}return out}function sortResults(results,isType,preferredCrate){const userQuery=parsedQuery.userQuery;const ar=[];for(const entry in results){if(hasOwnPropertyRustdoc(results,entry)){const result=results[entry];result.word=searchWords[result.id];result.item=searchIndex[result.id]||{};ar.push(result)}}results=ar;if(results.length===0){return[]}results.sort((aaa,bbb)=>{let a,b;a=(aaa.word!==userQuery);b=(bbb.word!==userQuery);if(a!==b){return a-b}a=(aaa.lev);b=(bbb.lev);if(a!==b){return a-b}a=(aaa.item.crate!==preferredCrate);b=(bbb.item.crate!==preferredCrate);if(a!==b){return a-b}a=aaa.word.length;b=bbb.word.length;if(a!==b){return a-b}a=aaa.word;b=bbb.word;if(a!==b){return(a>b?+1:-1)}a=(aaa.index<0);b=(bbb.index<0);if(a!==b){return a-b}a=aaa.index;b=bbb.index;if(a!==b){return a-b}if((aaa.item.ty===TY_PRIMITIVE&&bbb.item.ty!==TY_KEYWORD)||(aaa.item.ty===TY_KEYWORD&&bbb.item.ty!==TY_PRIMITIVE)){return-1}if((bbb.item.ty===TY_PRIMITIVE&&aaa.item.ty!==TY_PRIMITIVE)||(bbb.item.ty===TY_KEYWORD&&aaa.item.ty!==TY_KEYWORD)){return 1}a=(aaa.item.desc==="");b=(bbb.item.desc==="");if(a!==b){return a-b}a=aaa.item.ty;b=bbb.item.ty;if(a!==b){return a-b}a=aaa.item.path;b=bbb.item.path;if(a!==b){return(a>b?+1:-1)}return 0});let nameSplit=null;if(parsedQuery.elems.length===1){const hasPath=typeof parsedQuery.elems[0].path==="undefined";nameSplit=hasPath?null:parsedQuery.elems[0].path}for(const result of results){if(result.dontValidate){continue}const name=result.item.name.toLowerCase(),path=result.item.path.toLowerCase(),parent=result.item.parent;if(!isType&&!validateResult(name,path,nameSplit,parent)){result.id=-1}}return transformResults(results)}function checkGenerics(row,elem,defaultLev){if(row.generics.length===0){return elem.generics.length===0?defaultLev:MAX_LEV_DISTANCE+1}else if(row.generics.length>0&&row.generics[0].name===null){return checkGenerics(row.generics[0],elem,defaultLev)}let elem_name;if(elem.generics.length>0&&row.generics.length>=elem.generics.length){const elems=Object.create(null);for(const entry of row.generics){elem_name=entry.name;if(elem_name===""){if(checkGenerics(entry,elem,MAX_LEV_DISTANCE+1)!==0){return MAX_LEV_DISTANCE+1}continue}if(elems[elem_name]===undefined){elems[elem_name]=0}elems[elem_name]+=1}for(const generic of elem.generics){let match=null;if(elems[generic.name]){match=generic.name}else{for(elem_name in elems){if(!hasOwnPropertyRustdoc(elems,elem_name)){continue}if(elem_name===generic){match=elem_name;break}}}if(match===null){return MAX_LEV_DISTANCE+1}elems[match]-=1;if(elems[match]===0){delete elems[match]}}return 0}return MAX_LEV_DISTANCE+1}function checkIfInGenerics(row,elem){let lev=MAX_LEV_DISTANCE+1;for(const entry of row.generics){lev=Math.min(checkType(entry,elem,true),lev);if(lev===0){break}}return lev}function checkType(row,elem,literalSearch){if(row.name===null){if(row.generics.length>0){return checkIfInGenerics(row,elem)}return MAX_LEV_DISTANCE+1}let lev=levenshtein(row.name,elem.name);if(literalSearch){if(lev!==0){if(elem.generics.length===0){const checkGeneric=row.generics.length>0;if(checkGeneric&&row.generics.findIndex(tmp_elem=>tmp_elem.name===elem.name)!==-1){return 0}}return MAX_LEV_DISTANCE+1}else if(elem.generics.length>0){return checkGenerics(row,elem,MAX_LEV_DISTANCE+1)}return 0}else if(row.generics.length>0){if(elem.generics.length===0){if(lev===0){return 0}lev=checkIfInGenerics(row,elem);return lev+0.5}else if(lev>MAX_LEV_DISTANCE){return checkIfInGenerics(row,elem)}else{const tmp_lev=checkGenerics(row,elem,lev);if(tmp_lev>MAX_LEV_DISTANCE){return MAX_LEV_DISTANCE+1}return(tmp_lev+lev)/2}}else if(elem.generics.length>0){return MAX_LEV_DISTANCE+1}return lev}function findArg(row,elem,typeFilter){let lev=MAX_LEV_DISTANCE+1;if(row&&row.type&&row.type.inputs&&row.type.inputs.length>0){for(const input of row.type.inputs){if(!typePassesFilter(typeFilter,input.ty)){continue}lev=Math.min(lev,checkType(input,elem,parsedQuery.literalSearch));if(lev===0){return 0}}}return parsedQuery.literalSearch?MAX_LEV_DISTANCE+1:lev}function checkReturned(row,elem,typeFilter){let lev=MAX_LEV_DISTANCE+1;if(row&&row.type&&row.type.output.length>0){const ret=row.type.output;for(const ret_ty of ret){if(!typePassesFilter(typeFilter,ret_ty.ty)){continue}lev=Math.min(lev,checkType(ret_ty,elem,parsedQuery.literalSearch));if(lev===0){return 0}}}return parsedQuery.literalSearch?MAX_LEV_DISTANCE+1:lev}function checkPath(contains,ty){if(contains.length===0){return 0}let ret_lev=MAX_LEV_DISTANCE+1;const path=ty.path.split("::");if(ty.parent&&ty.parent.name){path.push(ty.parent.name.toLowerCase())}const length=path.length;const clength=contains.length;if(clength>length){return MAX_LEV_DISTANCE+1}for(let i=0;ilength){break}let lev_total=0;let aborted=false;for(let x=0;xMAX_LEV_DISTANCE){aborted=true;break}lev_total+=lev}if(!aborted){ret_lev=Math.min(ret_lev,Math.round(lev_total/clength))}}return ret_lev}function typePassesFilter(filter,type){if(filter<=NO_TYPE_FILTER||filter===type)return true;const name=itemTypes[type];switch(itemTypes[filter]){case"constant":return name==="associatedconstant";case"fn":return name==="method"||name==="tymethod";case"type":return name==="primitive"||name==="associatedtype";case"trait":return name==="traitalias"}return false}function createAliasFromItem(item){return{crate:item.crate,name:item.name,path:item.path,desc:item.desc,ty:item.ty,parent:item.parent,type:item.type,is_alias:true,}}function handleAliases(ret,query,filterCrates,currentCrate){const lowerQuery=query.toLowerCase();const aliases=[];const crateAliases=[];if(filterCrates!==null){if(ALIASES[filterCrates]&&ALIASES[filterCrates][lowerQuery]){const query_aliases=ALIASES[filterCrates][lowerQuery];for(const alias of query_aliases){aliases.push(createAliasFromItem(searchIndex[alias]))}}}else{Object.keys(ALIASES).forEach(crate=>{if(ALIASES[crate][lowerQuery]){const pushTo=crate===currentCrate?crateAliases:aliases;const query_aliases=ALIASES[crate][lowerQuery];for(const alias of query_aliases){pushTo.push(createAliasFromItem(searchIndex[alias]))}}})}const sortFunc=(aaa,bbb)=>{if(aaa.path{alias.alias=query;const res=buildHrefAndPath(alias);alias.displayPath=pathSplitter(res[0]);alias.fullPath=alias.displayPath+alias.name;alias.href=res[1];ret.others.unshift(alias);if(ret.others.length>MAX_RESULTS){ret.others.pop()}};aliases.forEach(pushFunc);crateAliases.forEach(pushFunc)}function addIntoResults(results,fullId,id,index,lev){if(lev===0||(!parsedQuery.literalSearch&&lev<=MAX_LEV_DISTANCE)){if(results[fullId]!==undefined){const result=results[fullId];if(result.dontValidate||result.lev<=lev){return}}results[fullId]={id:id,index:index,dontValidate:parsedQuery.literalSearch,lev:lev,}}}function handleSingleArg(row,pos,elem,results_others,results_in_args,results_returned){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return}let lev,lev_add=0,index=-1;const fullId=row.id;const in_args=findArg(row,elem,parsedQuery.typeFilter);const returned=checkReturned(row,elem,parsedQuery.typeFilter);addIntoResults(results_in_args,fullId,pos,index,in_args);addIntoResults(results_returned,fullId,pos,index,returned);if(!typePassesFilter(parsedQuery.typeFilter,row.ty)){return}const searchWord=searchWords[pos];if(parsedQuery.literalSearch){if(searchWord===elem.name){addIntoResults(results_others,fullId,pos,-1,0)}return}if(elem.name.length===0){if(row.type!==null){lev=checkGenerics(row.type,elem,MAX_LEV_DISTANCE+1);addIntoResults(results_others,fullId,pos,index,lev)}return}if(elem.fullPath.length>1){lev=checkPath(elem.pathWithoutLast,row);if(lev>MAX_LEV_DISTANCE||(parsedQuery.literalSearch&&lev!==0)){return}else if(lev>0){lev_add=lev/10}}if(searchWord.indexOf(elem.pathLast)>-1||row.normalizedName.indexOf(elem.pathLast)>-1){index=row.normalizedName.indexOf(elem.pathLast)}lev=levenshtein(searchWord,elem.pathLast);if(lev>0&&elem.pathLast.length>2&&searchWord.indexOf(elem.pathLast)>-1){if(elem.pathLast.length<6){lev=1}else{lev=0}}lev+=lev_add;if(lev>MAX_LEV_DISTANCE){return}else if(index!==-1&&elem.fullPath.length<2){lev-=1}if(lev<0){lev=0}addIntoResults(results_others,fullId,pos,index,lev)}function handleArgs(row,pos,results){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return}let totalLev=0;let nbLev=0;function checkArgs(elems,callback){for(const elem of elems){const lev=callback(row,elem,NO_TYPE_FILTER);if(lev<=1){nbLev+=1;totalLev+=lev}else{return false}}return true}if(!checkArgs(parsedQuery.elems,findArg)){return}if(!checkArgs(parsedQuery.returned,checkReturned)){return}if(nbLev===0){return}const lev=Math.round(totalLev/nbLev);addIntoResults(results,row.id,pos,0,lev)}function innerRunQuery(){let elem,i,nSearchWords,in_returned,row;if(parsedQuery.foundElems===1){if(parsedQuery.elems.length===1){elem=parsedQuery.elems[0];for(i=0,nSearchWords=searchWords.length;i0){for(i=0,nSearchWords=searchWords.length;i-1||path.indexOf(key)>-1||(parent!==undefined&&parent.name!==undefined&&parent.name.toLowerCase().indexOf(key)>-1)||levenshtein(name,key)<=MAX_LEV_DISTANCE)){return false}}return true}function nextTab(direction){const next=(searchState.currentTab+direction+3)%searchState.focusedByTab.length;searchState.focusedByTab[searchState.currentTab]=document.activeElement;printTab(next);focusSearchResult()}function focusSearchResult(){const target=searchState.focusedByTab[searchState.currentTab]||document.querySelectorAll(".search-results.active a").item(0)||document.querySelectorAll("#titles > button").item(searchState.currentTab);if(target){target.focus()}}function buildHrefAndPath(item){let displayPath;let href;const type=itemTypes[item.ty];const name=item.name;let path=item.path;if(type==="mod"){displayPath=path+"::";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+name+"/index.html"}else if(type==="import"){displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/index.html#reexport."+name}else if(type==="primitive"||type==="keyword"){displayPath="";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+type+"."+name+".html"}else if(type==="externcrate"){displayPath="";href=ROOT_PATH+name+"/index.html"}else if(item.parent!==undefined){const myparent=item.parent;let anchor="#"+type+"."+name;const parentType=itemTypes[myparent.ty];let pageType=parentType;let pageName=myparent.name;if(parentType==="primitive"){displayPath=myparent.name+"::"}else if(type==="structfield"&&parentType==="variant"){const enumNameIdx=item.path.lastIndexOf("::");const enumName=item.path.substr(enumNameIdx+2);path=item.path.substr(0,enumNameIdx);displayPath=path+"::"+enumName+"::"+myparent.name+"::";anchor="#variant."+myparent.name+".field."+name;pageType="enum";pageName=enumName}else{displayPath=path+"::"+myparent.name+"::"}href=ROOT_PATH+path.replace(/::/g,"/")+"/"+pageType+"."+pageName+".html"+anchor}else{displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/"+type+"."+name+".html"}return[displayPath,href]}function pathSplitter(path){const tmp=""+path.replace(/::/g,"::");if(tmp.endsWith("")){return tmp.slice(0,tmp.length-6)}return tmp}function addTab(array,query,display){let extraClass="";if(display===true){extraClass=" active"}const output=document.createElement("div");let length=0;if(array.length>0){output.className="search-results "+extraClass;array.forEach(item=>{const name=item.name;const type=itemTypes[item.ty];length+=1;let extra="";if(type==="primitive"){extra=" (primitive type)"}else if(type==="keyword"){extra=" (keyword)"}const link=document.createElement("a");link.className="result-"+type;link.href=item.href;const wrapper=document.createElement("div");const resultName=document.createElement("div");resultName.className="result-name";if(item.is_alias){const alias=document.createElement("span");alias.className="alias";const bold=document.createElement("b");bold.innerText=item.alias;alias.appendChild(bold);alias.insertAdjacentHTML("beforeend"," - see ");resultName.appendChild(alias)}resultName.insertAdjacentHTML("beforeend",item.displayPath+""+name+extra+"");wrapper.appendChild(resultName);const description=document.createElement("div");description.className="desc";const spanDesc=document.createElement("span");spanDesc.insertAdjacentHTML("beforeend",item.desc);description.appendChild(spanDesc);wrapper.appendChild(description);link.appendChild(wrapper);output.appendChild(link)})}else if(query.error===null){output.className="search-failed"+extraClass;output.innerHTML="No results :(
    "+"Try on DuckDuckGo?

    "+"Or try looking in one of these:"}return[output,length]}function makeTabHeader(tabNb,text,nbElems){if(searchState.currentTab===tabNb){return""}return""}function showResults(results,go_to_first,filterCrates){const search=searchState.outputElement();if(go_to_first||(results.others.length===1&&getSettingValue("go-to-only-result")==="true"&&(!search.firstChild||search.firstChild.innerText!==searchState.loadingText))){const elem=document.createElement("a");elem.href=results.others[0].href;removeClass(elem,"active");document.body.appendChild(elem);elem.click();return}if(results.query===undefined){results.query=parseQuery(searchState.input.value)}currentResults=results.query.userQuery;const ret_others=addTab(results.others,results.query,true);const ret_in_args=addTab(results.in_args,results.query,false);const ret_returned=addTab(results.returned,results.query,false);let currentTab=searchState.currentTab;if((currentTab===0&&ret_others[1]===0)||(currentTab===1&&ret_in_args[1]===0)||(currentTab===2&&ret_returned[1]===0)){if(ret_others[1]!==0){currentTab=0}else if(ret_in_args[1]!==0){currentTab=1}else if(ret_returned[1]!==0){currentTab=2}}let crates="";const crates_list=Object.keys(rawSearchIndex);if(crates_list.length>1){crates=" in 
    "}let output=`

    Results${crates}

    `;if(results.query.error!==null){output+=`

    Query parser error: "${results.query.error}".

    `;output+="
    "+makeTabHeader(0,"In Names",ret_others[1])+"
    ";currentTab=0}else if(results.query.foundElems<=1&&results.query.returned.length===0){output+="
    "+makeTabHeader(0,"In Names",ret_others[1])+makeTabHeader(1,"In Parameters",ret_in_args[1])+makeTabHeader(2,"In Return Types",ret_returned[1])+"
    "}else{const signatureTabTitle=results.query.elems.length===0?"In Function Return Types":results.query.returned.length===0?"In Function Parameters":"In Function Signatures";output+="
    "+makeTabHeader(0,signatureTabTitle,ret_others[1])+"
    ";currentTab=0}const resultsElem=document.createElement("div");resultsElem.id="results";resultsElem.appendChild(ret_others[0]);resultsElem.appendChild(ret_in_args[0]);resultsElem.appendChild(ret_returned[0]);search.innerHTML=output;const crateSearch=document.getElementById("crate-search");if(crateSearch){crateSearch.addEventListener("input",updateCrate)}search.appendChild(resultsElem);searchState.showResults(search);const elems=document.getElementById("titles").childNodes;searchState.focusedByTab=[];let i=0;for(const elem of elems){const j=i;elem.onclick=()=>printTab(j);searchState.focusedByTab.push(null);i+=1}printTab(currentTab)}function search(e,forced){const params=searchState.getQueryStringParams();const query=parseQuery(searchState.input.value.trim());if(e){e.preventDefault()}if(!forced&&query.userQuery===currentResults){if(query.userQuery.length>0){putBackSearch()}return}let filterCrates=getFilterCrates();if(filterCrates===null&¶ms["filter-crate"]!==undefined){filterCrates=params["filter-crate"]}searchState.title="Results for "+query.original+" - Rust";if(browserSupportsHistoryApi()){const newURL=buildUrl(query.original,filterCrates);if(!history.state&&!params.search){history.pushState(null,"",newURL)}else{history.replaceState(null,"",newURL)}}showResults(execQuery(query,searchWords,filterCrates,window.currentCrate),params.go_to_first,filterCrates)}function buildItemSearchTypeAll(types,lowercasePaths){const PATH_INDEX_DATA=0;const GENERICS_DATA=1;return types.map(type=>{let pathIndex,generics;if(typeof type==="number"){pathIndex=type;generics=[]}else{pathIndex=type[PATH_INDEX_DATA];generics=buildItemSearchTypeAll(type[GENERICS_DATA],lowercasePaths)}return{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:generics,}})}function buildFunctionSearchType(functionSearchType,lowercasePaths){const INPUTS_DATA=0;const OUTPUT_DATA=1;if(functionSearchType===0){return null}let inputs,output;if(typeof functionSearchType[INPUTS_DATA]==="number"){const pathIndex=functionSearchType[INPUTS_DATA];inputs=[{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{inputs=buildItemSearchTypeAll(functionSearchType[INPUTS_DATA],lowercasePaths)}if(functionSearchType.length>1){if(typeof functionSearchType[OUTPUT_DATA]==="number"){const pathIndex=functionSearchType[OUTPUT_DATA];output=[{name:pathIndex===0?null:lowercasePaths[pathIndex-1].name,ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{output=buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA],lowercasePaths)}}else{output=[]}return{inputs,output,}}function buildIndex(rawSearchIndex){searchIndex=[];const searchWords=[];let i,word;let currentIndex=0;let id=0;for(const crate in rawSearchIndex){if(!hasOwnPropertyRustdoc(rawSearchIndex,crate)){continue}let crateSize=0;const crateCorpus=rawSearchIndex[crate];searchWords.push(crate);const crateRow={crate:crate,ty:1,name:crate,path:"",desc:crateCorpus.doc,parent:undefined,type:null,id:id,normalizedName:crate.indexOf("_")===-1?crate:crate.replace(/_/g,""),};id+=1;searchIndex.push(crateRow);currentIndex+=1;const itemTypes=crateCorpus.t;const itemNames=crateCorpus.n;const itemPaths=crateCorpus.q;const itemDescs=crateCorpus.d;const itemParentIdxs=crateCorpus.i;const itemFunctionSearchTypes=crateCorpus.f;const paths=crateCorpus.p;const aliases=crateCorpus.a;const lowercasePaths=[];let len=paths.length;for(i=0;i0?paths[itemParentIdxs[i]-1]:undefined,type:buildFunctionSearchType(itemFunctionSearchTypes[i],lowercasePaths),id:id,normalizedName:word.indexOf("_")===-1?word:word.replace(/_/g,""),};id+=1;searchIndex.push(row);lastPath=row.path;crateSize+=1}if(aliases){ALIASES[crate]=Object.create(null);for(const alias_name in aliases){if(!hasOwnPropertyRustdoc(aliases,alias_name)){continue}if(!hasOwnPropertyRustdoc(ALIASES[crate],alias_name)){ALIASES[crate][alias_name]=[]}for(const local_alias of aliases[alias_name]){ALIASES[crate][alias_name].push(local_alias+currentIndex)}}}currentIndex+=crateSize}return searchWords}function onSearchSubmit(e){e.preventDefault();searchState.clearInputTimeout();search()}function putBackSearch(){const search_input=searchState.input;if(!searchState.input){return}if(search_input.value!==""&&!searchState.isDisplayed()){searchState.showResults();if(browserSupportsHistoryApi()){history.replaceState(null,"",buildUrl(search_input.value,getFilterCrates()))}document.title=searchState.title}}function registerSearchEvents(){const params=searchState.getQueryStringParams();if(searchState.input.value===""){searchState.input.value=params.search||""}const searchAfter500ms=()=>{searchState.clearInputTimeout();if(searchState.input.value.length===0){if(browserSupportsHistoryApi()){history.replaceState(null,window.currentCrate+" - Rust",getNakedUrl()+window.location.hash)}searchState.hideResults()}else{searchState.timeout=setTimeout(search,500)}};searchState.input.onkeyup=searchAfter500ms;searchState.input.oninput=searchAfter500ms;document.getElementsByClassName("search-form")[0].onsubmit=onSearchSubmit;searchState.input.onchange=e=>{if(e.target!==document.activeElement){return}searchState.clearInputTimeout();setTimeout(search,0)};searchState.input.onpaste=searchState.input.onchange;searchState.outputElement().addEventListener("keydown",e=>{if(e.altKey||e.ctrlKey||e.shiftKey||e.metaKey){return}if(e.which===38){const previous=document.activeElement.previousElementSibling;if(previous){previous.focus()}else{searchState.focus()}e.preventDefault()}else if(e.which===40){const next=document.activeElement.nextElementSibling;if(next){next.focus()}const rect=document.activeElement.getBoundingClientRect();if(window.innerHeight-rect.bottom{if(e.which===40){focusSearchResult();e.preventDefault()}});searchState.input.addEventListener("focus",()=>{putBackSearch()});searchState.input.addEventListener("blur",()=>{searchState.input.placeholder=searchState.input.origPlaceholder});if(browserSupportsHistoryApi()){const previousTitle=document.title;window.addEventListener("popstate",e=>{const params=searchState.getQueryStringParams();document.title=previousTitle;currentResults=null;if(params.search&¶ms.search.length>0){searchState.input.value=params.search;search(e)}else{searchState.input.value="";searchState.hideResults()}})}window.onpageshow=()=>{const qSearch=searchState.getQueryStringParams().search;if(searchState.input.value===""&&qSearch){searchState.input.value=qSearch}search()}}function updateCrate(ev){if(ev.target.value==="all crates"){const params=searchState.getQueryStringParams();const query=searchState.input.value.trim();if(!history.state&&!params.search){history.pushState(null,"",buildUrl(query,null))}else{history.replaceState(null,"",buildUrl(query,null))}}currentResults=null;search(undefined,true)}const searchWords=buildIndex(rawSearchIndex);if(typeof window!=="undefined"){registerSearchEvents();if(window.searchState.getQueryStringParams().search){search()}}if(typeof exports!=="undefined"){exports.initSearch=initSearch;exports.execQuery=execQuery;exports.parseQuery=parseQuery}return searchWords}if(typeof window!=="undefined"){window.initSearch=initSearch;if(window.searchIndex!==undefined){initSearch(window.searchIndex)}}else{initSearch({})}})() \ No newline at end of file diff --git a/docs/settings.css b/docs/settings.css deleted file mode 100644 index ab01e577c5c0..000000000000 --- a/docs/settings.css +++ /dev/null @@ -1 +0,0 @@ -.setting-line{margin:0.6em 0 0.6em 0.3em;position:relative;}.setting-line .choices{display:flex;flex-wrap:wrap;}.setting-line .radio-line input{margin-right:0.3em;height:1.2rem;width:1.2rem;color:inherit;border:1px solid currentColor;outline:none;-webkit-appearance:none;cursor:pointer;border-radius:50%;}.setting-line .radio-line input+span{padding-bottom:1px;}.radio-line .setting-name{width:100%;}.radio-line .choice{margin-top:0.1em;margin-bottom:0.1em;min-width:3.8em;padding:0.3em;display:flex;align-items:center;cursor:pointer;}.radio-line .choice+.choice{margin-left:0.5em;}.toggle{position:relative;width:100%;margin-right:20px;display:flex;align-items:center;cursor:pointer;}.toggle input{opacity:0;position:absolute;}.slider{position:relative;width:45px;min-width:45px;display:block;height:28px;margin-right:20px;cursor:pointer;background-color:#ccc;transition:.3s;}.slider:before{position:absolute;content:"";height:19px;width:19px;left:4px;bottom:4px;transition:.3s;}input:checked+.slider:before{transform:translateX(19px);}.setting-line>.sub-settings{padding-left:42px;width:100%;display:block;}#settings .setting-line{margin:1.2em 0.6em;}.setting-line .radio-line input:checked{box-shadow:inset 0 0 0 3px var(--main-background-color);background-color:var(--settings-input-color);}.setting-line .radio-line input:focus{box-shadow:0 0 1px 1px var(--settings-input-color);}.setting-line .radio-line input:checked:focus{box-shadow:inset 0 0 0 3px var(--main-background-color),0 0 2px 2px var(--settings-input-color);}.setting-line .radio-line input:hover{border-color:var(--settings-input-color) !important;}input:checked+.slider{background-color:var(--settings-input-color);} \ No newline at end of file diff --git a/docs/settings.html b/docs/settings.html deleted file mode 100644 index 9bd8d4a9796c..000000000000 --- a/docs/settings.html +++ /dev/null @@ -1 +0,0 @@ -Rustdoc settings

    Rustdoc settings

    Back
    \ No newline at end of file diff --git a/docs/settings.js b/docs/settings.js deleted file mode 100644 index 834d29269db0..000000000000 --- a/docs/settings.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict";(function(){const isSettingsPage=window.location.pathname.endsWith("/settings.html");function changeSetting(settingName,value){updateLocalStorage(settingName,value);switch(settingName){case"theme":case"preferred-dark-theme":case"preferred-light-theme":case"use-system-theme":updateSystemTheme();updateLightAndDark();break;case"line-numbers":if(value===true){window.rustdoc_add_line_numbers_to_examples()}else{window.rustdoc_remove_line_numbers_from_examples()}break}}function handleKey(ev){if(ev.ctrlKey||ev.altKey||ev.metaKey){return}switch(getVirtualKey(ev)){case"Enter":case"Return":case"Space":ev.target.checked=!ev.target.checked;ev.preventDefault();break}}function showLightAndDark(){addClass(document.getElementById("theme").parentElement,"hidden");removeClass(document.getElementById("preferred-light-theme").parentElement,"hidden");removeClass(document.getElementById("preferred-dark-theme").parentElement,"hidden")}function hideLightAndDark(){addClass(document.getElementById("preferred-light-theme").parentElement,"hidden");addClass(document.getElementById("preferred-dark-theme").parentElement,"hidden");removeClass(document.getElementById("theme").parentElement,"hidden")}function updateLightAndDark(){if(getSettingValue("use-system-theme")!=="false"){showLightAndDark()}else{hideLightAndDark()}}function setEvents(settingsElement){updateLightAndDark();onEachLazy(settingsElement.getElementsByClassName("slider"),elem=>{const toggle=elem.previousElementSibling;const settingId=toggle.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){toggle.checked=settingValue==="true"}toggle.onchange=function(){changeSetting(this.id,this.checked)};toggle.onkeyup=handleKey;toggle.onkeyrelease=handleKey});onEachLazy(settingsElement.getElementsByClassName("select-wrapper"),elem=>{const select=elem.getElementsByTagName("select")[0];const settingId=select.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){select.value=settingValue}select.onchange=function(){changeSetting(this.id,this.value)}});onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"),elem=>{const settingId=elem.name;const settingValue=getSettingValue(settingId);if(settingValue!==null&&settingValue!=="null"){elem.checked=settingValue===elem.value}elem.addEventListener("change",ev=>{changeSetting(ev.target.name,ev.target.value)})})}function buildSettingsPageSections(settings){let output="";for(const setting of settings){output+="
    ";const js_data_name=setting["js_name"];const setting_name=setting["name"];if(setting["options"]!==undefined){output+=`
    \ - ${setting_name}\ -
    `;onEach(setting["options"],option=>{const checked=option===setting["default"]?" checked":"";output+=``});output+="
    "}else{const checked=setting["default"]===true?" checked":"";output+=``}output+="
    "}return output}function buildSettingsPage(){const themes=getVar("themes").split(",");const settings=[{"name":"Use system theme","js_name":"use-system-theme","default":true,},{"name":"Theme","js_name":"theme","default":"light","options":themes,},{"name":"Preferred light theme","js_name":"preferred-light-theme","default":"light","options":themes,},{"name":"Preferred dark theme","js_name":"preferred-dark-theme","default":"dark","options":themes,},{"name":"Auto-hide item contents for large items","js_name":"auto-hide-large-items","default":true,},{"name":"Auto-hide item methods' documentation","js_name":"auto-hide-method-docs","default":false,},{"name":"Auto-hide trait implementation documentation","js_name":"auto-hide-trait-implementations","default":false,},{"name":"Directly go to item in search if there is only one result","js_name":"go-to-only-result","default":false,},{"name":"Show line numbers on code examples","js_name":"line-numbers","default":false,},{"name":"Disable keyboard shortcuts","js_name":"disable-shortcuts","default":false,},];const elementKind=isSettingsPage?"section":"div";const innerHTML=`
    ${buildSettingsPageSections(settings)}
    `;const el=document.createElement(elementKind);el.id="settings";if(!isSettingsPage){el.className="popover"}el.innerHTML=innerHTML;if(isSettingsPage){document.getElementById(MAIN_ID).appendChild(el)}else{el.setAttribute("tabindex","-1");getSettingsButton().appendChild(el)}return el}const settingsMenu=buildSettingsPage();function displaySettings(){settingsMenu.style.display=""}function settingsBlurHandler(event){blurHandler(event,getSettingsButton(),window.hidePopoverMenus)}if(isSettingsPage){getSettingsButton().onclick=function(event){event.preventDefault()}}else{const settingsButton=getSettingsButton();const settingsMenu=document.getElementById("settings");settingsButton.onclick=function(event){if(elemIsInParent(event.target,settingsMenu)){return}event.preventDefault();const shouldDisplaySettings=settingsMenu.style.display==="none";window.hidePopoverMenus();if(shouldDisplaySettings){displaySettings()}};settingsButton.onblur=settingsBlurHandler;settingsButton.querySelector("a").onblur=settingsBlurHandler;onEachLazy(settingsMenu.querySelectorAll("input"),el=>{el.onblur=settingsBlurHandler});settingsMenu.onblur=settingsBlurHandler}setTimeout(()=>{setEvents(settingsMenu);if(!isSettingsPage){displaySettings()}removeClass(getSettingsButton(),"rotate")},0)})() \ No newline at end of file diff --git a/docs/source-files.js b/docs/source-files.js deleted file mode 100644 index c7cc6f7e9e0b..000000000000 --- a/docs/source-files.js +++ /dev/null @@ -1,79 +0,0 @@ -var sourcesIndex = JSON.parse('{\ -"ahash":["",[],["convert.rs","fallback_hash.rs","lib.rs","operations.rs","random_state.rs","specialize.rs"]],\ -"aho_corasick":["",[["packed",[["teddy",[],["compile.rs","mod.rs","runtime.rs"]]],["api.rs","mod.rs","pattern.rs","rabinkarp.rs","vector.rs"]]],["ahocorasick.rs","automaton.rs","buffer.rs","byte_frequencies.rs","classes.rs","dfa.rs","error.rs","lib.rs","nfa.rs","prefilter.rs","state_id.rs"]],\ -"aiofut":["",[],["abi.rs","lib.rs"]],\ -"async_trait":["",[],["args.rs","bound.rs","expand.rs","lib.rs","lifetime.rs","parse.rs","receiver.rs"]],\ -"bincode":["",[["config",[],["endian.rs","int.rs","legacy.rs","limit.rs","mod.rs","trailing.rs"]],["de",[],["mod.rs","read.rs"]],["ser",[],["mod.rs"]]],["byteorder.rs","error.rs","internal.rs","lib.rs"]],\ -"bitflags":["",[],["lib.rs"]],\ -"block_buffer":["",[],["lib.rs","sealed.rs"]],\ -"byteorder":["",[],["io.rs","lib.rs"]],\ -"bytes":["",[["buf",[],["buf_impl.rs","buf_mut.rs","chain.rs","iter.rs","limit.rs","mod.rs","reader.rs","take.rs","uninit_slice.rs","vec_deque.rs","writer.rs"]],["fmt",[],["debug.rs","hex.rs","mod.rs"]]],["bytes.rs","bytes_mut.rs","lib.rs","loom.rs"]],\ -"cfg_if":["",[],["lib.rs"]],\ -"cpufeatures":["",[],["aarch64.rs","lib.rs"]],\ -"crc":["",[],["crc128.rs","crc16.rs","crc32.rs","crc64.rs","crc8.rs","lib.rs","table.rs","util.rs"]],\ -"crc_catalog":["",[],["catalog.rs","lib.rs"]],\ -"crossbeam_channel":["",[["flavors",[],["array.rs","at.rs","list.rs","mod.rs","never.rs","tick.rs","zero.rs"]]],["channel.rs","context.rs","counter.rs","err.rs","lib.rs","select.rs","select_macro.rs","utils.rs","waker.rs"]],\ -"crossbeam_utils":["",[["atomic",[],["atomic_cell.rs","consume.rs","mod.rs","seq_lock.rs"]],["sync",[],["mod.rs","once_lock.rs","parker.rs","sharded_lock.rs","wait_group.rs"]]],["backoff.rs","cache_padded.rs","lib.rs","thread.rs"]],\ -"crunchy":["",[],["lib.rs"]],\ -"crypto_common":["",[],["lib.rs"]],\ -"digest":["",[["core_api",[],["ct_variable.rs","rt_variable.rs","wrapper.rs","xof_reader.rs"]]],["core_api.rs","digest.rs","lib.rs"]],\ -"enum_as_inner":["",[],["lib.rs"]],\ -"firewood":["",[],["account.rs","db.rs","file.rs","lib.rs","merkle.rs","proof.rs","storage.rs"]],\ -"fixed_hash":["",[],["hash.rs","lib.rs"]],\ -"futures":["",[],["lib.rs"]],\ -"futures_channel":["",[["mpsc",[],["mod.rs","queue.rs","sink_impl.rs"]]],["lib.rs","lock.rs","oneshot.rs"]],\ -"futures_core":["",[["task",[["__internal",[],["atomic_waker.rs","mod.rs"]]],["mod.rs","poll.rs"]]],["future.rs","lib.rs","stream.rs"]],\ -"futures_executor":["",[],["enter.rs","lib.rs","local_pool.rs"]],\ -"futures_io":["",[],["lib.rs"]],\ -"futures_macro":["",[],["executor.rs","join.rs","lib.rs","select.rs","stream_select.rs"]],\ -"futures_sink":["",[],["lib.rs"]],\ -"futures_task":["",[],["arc_wake.rs","future_obj.rs","lib.rs","noop_waker.rs","spawn.rs","waker.rs","waker_ref.rs"]],\ -"futures_util":["",[["async_await",[],["join_mod.rs","mod.rs","pending.rs","poll.rs","random.rs","select_mod.rs","stream_select_mod.rs"]],["future",[["future",[],["catch_unwind.rs","flatten.rs","fuse.rs","map.rs","mod.rs","remote_handle.rs","shared.rs"]],["try_future",[],["into_future.rs","mod.rs","try_flatten.rs","try_flatten_err.rs"]]],["abortable.rs","either.rs","join.rs","join_all.rs","lazy.rs","maybe_done.rs","mod.rs","option.rs","pending.rs","poll_fn.rs","poll_immediate.rs","ready.rs","select.rs","select_all.rs","select_ok.rs","try_join.rs","try_join_all.rs","try_maybe_done.rs","try_select.rs"]],["io",[],["allow_std.rs","buf_reader.rs","buf_writer.rs","chain.rs","close.rs","copy.rs","copy_buf.rs","copy_buf_abortable.rs","cursor.rs","empty.rs","fill_buf.rs","flush.rs","into_sink.rs","line_writer.rs","lines.rs","mod.rs","read.rs","read_exact.rs","read_line.rs","read_to_end.rs","read_to_string.rs","read_until.rs","read_vectored.rs","repeat.rs","seek.rs","sink.rs","split.rs","take.rs","window.rs","write.rs","write_all.rs","write_vectored.rs"]],["lock",[],["bilock.rs","mod.rs","mutex.rs"]],["sink",[],["buffer.rs","close.rs","drain.rs","err_into.rs","fanout.rs","feed.rs","flush.rs","map_err.rs","mod.rs","send.rs","send_all.rs","unfold.rs","with.rs","with_flat_map.rs"]],["stream",[["futures_unordered",[],["abort.rs","iter.rs","mod.rs","ready_to_run_queue.rs","task.rs"]],["stream",[],["all.rs","any.rs","buffer_unordered.rs","buffered.rs","catch_unwind.rs","chain.rs","chunks.rs","collect.rs","concat.rs","count.rs","cycle.rs","enumerate.rs","filter.rs","filter_map.rs","flatten.rs","flatten_unordered.rs","fold.rs","for_each.rs","for_each_concurrent.rs","forward.rs","fuse.rs","into_future.rs","map.rs","mod.rs","next.rs","peek.rs","ready_chunks.rs","scan.rs","select_next_some.rs","skip.rs","skip_while.rs","split.rs","take.rs","take_until.rs","take_while.rs","then.rs","unzip.rs","zip.rs"]],["try_stream",[],["and_then.rs","into_async_read.rs","into_stream.rs","mod.rs","or_else.rs","try_buffer_unordered.rs","try_buffered.rs","try_chunks.rs","try_collect.rs","try_concat.rs","try_filter.rs","try_filter_map.rs","try_flatten.rs","try_fold.rs","try_for_each.rs","try_for_each_concurrent.rs","try_next.rs","try_skip_while.rs","try_take_while.rs","try_unfold.rs"]]],["abortable.rs","empty.rs","futures_ordered.rs","iter.rs","mod.rs","once.rs","pending.rs","poll_fn.rs","poll_immediate.rs","repeat.rs","repeat_with.rs","select.rs","select_all.rs","select_with_strategy.rs","unfold.rs"]],["task",[],["mod.rs","spawn.rs"]]],["abortable.rs","fns.rs","lib.rs","never.rs","unfold_state.rs"]],\ -"generic_array":["",[],["arr.rs","functional.rs","hex.rs","impls.rs","iter.rs","lib.rs","sequence.rs"]],\ -"getrandom":["",[],["error.rs","error_impls.rs","lib.rs","macos.rs","use_file.rs","util.rs","util_libc.rs"]],\ -"growthring":["",[],["lib.rs","wal.rs"]],\ -"hashbrown":["",[["external_trait_impls",[],["mod.rs"]],["raw",[],["alloc.rs","bitmask.rs","mod.rs","sse2.rs"]]],["lib.rs","macros.rs","map.rs","scopeguard.rs","set.rs"]],\ -"heck":["",[],["kebab.rs","lib.rs","lower_camel.rs","shouty_kebab.rs","shouty_snake.rs","snake.rs","title.rs","upper_camel.rs"]],\ -"hex":["",[],["error.rs","lib.rs"]],\ -"impl_rlp":["",[],["lib.rs"]],\ -"keccak":["",[],["lib.rs","unroll.rs"]],\ -"libc":["",[["unix",[["bsd",[["apple",[["b64",[["aarch64",[],["align.rs","mod.rs"]]],["mod.rs"]]],["mod.rs"]]],["mod.rs"]]],["align.rs","mod.rs"]]],["fixed_width_ints.rs","lib.rs","macros.rs"]],\ -"lock_api":["",[],["lib.rs","mutex.rs","remutex.rs","rwlock.rs"]],\ -"lru":["",[],["lib.rs"]],\ -"memchr":["",[["memchr",[],["fallback.rs","iter.rs","mod.rs","naive.rs"]],["memmem",[["prefilter",[],["fallback.rs","mod.rs"]]],["byte_frequencies.rs","mod.rs","rabinkarp.rs","rarebytes.rs","twoway.rs","util.rs"]]],["cow.rs","lib.rs"]],\ -"memoffset":["",[],["lib.rs","offset_of.rs","raw_field.rs","span_of.rs"]],\ -"nix":["",[["mount",[],["linux.rs","mod.rs"]],["net",[],["if_.rs","mod.rs"]],["sys",[["ioctl",[],["linux.rs","mod.rs"]],["ptrace",[],["linux.rs","mod.rs"]],["socket",[],["addr.rs","mod.rs","sockopt.rs"]]],["aio.rs","epoll.rs","eventfd.rs","inotify.rs","memfd.rs","mman.rs","mod.rs","personality.rs","pthread.rs","quota.rs","reboot.rs","resource.rs","select.rs","sendfile.rs","signal.rs","signalfd.rs","stat.rs","statfs.rs","statvfs.rs","sysinfo.rs","termios.rs","time.rs","timer.rs","timerfd.rs","uio.rs","utsname.rs","wait.rs"]]],["dir.rs","env.rs","errno.rs","fcntl.rs","features.rs","ifaddrs.rs","kmod.rs","lib.rs","macros.rs","mqueue.rs","poll.rs","pty.rs","sched.rs","time.rs","ucontext.rs","unistd.rs"]],\ -"once_cell":["",[],["imp_std.rs","lib.rs","race.rs"]],\ -"parking_lot":["",[],["condvar.rs","deadlock.rs","elision.rs","fair_mutex.rs","lib.rs","mutex.rs","once.rs","raw_fair_mutex.rs","raw_mutex.rs","raw_rwlock.rs","remutex.rs","rwlock.rs","util.rs"]],\ -"parking_lot_core":["",[["thread_parker",[],["linux.rs","mod.rs"]]],["lib.rs","parking_lot.rs","spinwait.rs","util.rs","word_lock.rs"]],\ -"pin_project_lite":["",[],["lib.rs"]],\ -"pin_utils":["",[],["lib.rs","projection.rs","stack_pin.rs"]],\ -"ppv_lite86":["",[["x86_64",[],["mod.rs","sse2.rs"]]],["lib.rs","soft.rs","types.rs"]],\ -"primitive_types":["",[],["lib.rs"]],\ -"proc_macro2":["",[],["detection.rs","fallback.rs","lib.rs","marker.rs","parse.rs","rcvec.rs","wrapper.rs"]],\ -"quote":["",[],["ext.rs","format.rs","ident_fragment.rs","lib.rs","runtime.rs","spanned.rs","to_tokens.rs"]],\ -"rand":["",[["distributions",[],["bernoulli.rs","distribution.rs","float.rs","integer.rs","mod.rs","other.rs","slice.rs","uniform.rs","utils.rs","weighted.rs","weighted_index.rs"]],["rngs",[["adapter",[],["mod.rs","read.rs","reseeding.rs"]]],["mock.rs","mod.rs"]],["seq",[],["index.rs","mod.rs"]]],["lib.rs","prelude.rs","rng.rs"]],\ -"rand_chacha":["",[],["chacha.rs","guts.rs","lib.rs"]],\ -"rand_core":["",[],["block.rs","error.rs","impls.rs","le.rs","lib.rs","os.rs"]],\ -"regex":["",[["literal",[],["imp.rs","mod.rs"]]],["backtrack.rs","compile.rs","dfa.rs","error.rs","exec.rs","expand.rs","find_byte.rs","input.rs","lib.rs","pikevm.rs","pool.rs","prog.rs","re_builder.rs","re_bytes.rs","re_set.rs","re_trait.rs","re_unicode.rs","sparse.rs","utf8.rs"]],\ -"regex_syntax":["",[["ast",[],["mod.rs","parse.rs","print.rs","visitor.rs"]],["hir",[["literal",[],["mod.rs"]]],["interval.rs","mod.rs","print.rs","translate.rs","visitor.rs"]],["unicode_tables",[],["age.rs","case_folding_simple.rs","general_category.rs","grapheme_cluster_break.rs","mod.rs","perl_word.rs","property_bool.rs","property_names.rs","property_values.rs","script.rs","script_extension.rs","sentence_break.rs","word_break.rs"]]],["either.rs","error.rs","lib.rs","parser.rs","unicode.rs","utf8.rs"]],\ -"rlp":["",[],["error.rs","impls.rs","lib.rs","rlpin.rs","stream.rs","traits.rs"]],\ -"rustc_hex":["",[],["lib.rs"]],\ -"scan_fmt":["",[],["lib.rs","parse.rs"]],\ -"scopeguard":["",[],["lib.rs"]],\ -"serde":["",[["de",[],["format.rs","ignored_any.rs","impls.rs","mod.rs","seed.rs","utf8.rs","value.rs"]],["private",[],["de.rs","doc.rs","mod.rs","ser.rs","size_hint.rs"]],["ser",[],["fmt.rs","impls.rs","impossible.rs","mod.rs"]]],["integer128.rs","lib.rs","macros.rs"]],\ -"serde_derive":["",[["internals",[],["ast.rs","attr.rs","case.rs","check.rs","ctxt.rs","mod.rs","receiver.rs","respan.rs","symbol.rs"]]],["bound.rs","de.rs","dummy.rs","fragment.rs","lib.rs","pretend.rs","ser.rs","this.rs","try.rs"]],\ -"sha3":["",[],["lib.rs","macros.rs","state.rs"]],\ -"shale":["",[],["block.rs","compact.rs","lib.rs","util.rs"]],\ -"slab":["",[],["lib.rs"]],\ -"smallvec":["",[],["lib.rs"]],\ -"static_assertions":["",[],["assert_cfg.rs","assert_eq_align.rs","assert_eq_size.rs","assert_fields.rs","assert_impl.rs","assert_obj_safe.rs","assert_trait.rs","assert_type.rs","const_assert.rs","lib.rs"]],\ -"syn":["",[["gen",[],["clone.rs","debug.rs","eq.rs","gen_helper.rs","hash.rs","visit_mut.rs"]]],["attr.rs","await.rs","bigint.rs","buffer.rs","custom_keyword.rs","custom_punctuation.rs","data.rs","derive.rs","discouraged.rs","drops.rs","error.rs","export.rs","expr.rs","ext.rs","file.rs","generics.rs","group.rs","ident.rs","item.rs","lib.rs","lifetime.rs","lit.rs","lookahead.rs","mac.rs","macros.rs","op.rs","parse.rs","parse_macro_input.rs","parse_quote.rs","pat.rs","path.rs","print.rs","punctuated.rs","reserved.rs","sealed.rs","span.rs","spanned.rs","stmt.rs","thread.rs","token.rs","tt.rs","ty.rs","verbatim.rs","whitespace.rs"]],\ -"tokio":["",[["future",[],["block_on.rs","maybe_done.rs","mod.rs","poll_fn.rs"]],["io",[],["async_buf_read.rs","async_read.rs","async_seek.rs","async_write.rs","mod.rs","read_buf.rs"]],["loom",[["std",[],["atomic_u16.rs","atomic_u32.rs","atomic_u64.rs","atomic_usize.rs","mod.rs","mutex.rs","unsafe_cell.rs"]]],["mod.rs"]],["macros",[],["addr_of.rs","cfg.rs","join.rs","loom.rs","mod.rs","pin.rs","ready.rs","scoped_tls.rs","select.rs","support.rs","thread_local.rs","try_join.rs"]],["net",[],["addr.rs","mod.rs"]],["runtime",[["blocking",[],["mod.rs","pool.rs","schedule.rs","shutdown.rs","task.rs"]],["metrics",[],["mock.rs","mod.rs"]],["scheduler",[],["current_thread.rs","mod.rs"]],["task",[],["abort.rs","core.rs","error.rs","harness.rs","join.rs","list.rs","mod.rs","raw.rs","state.rs","waker.rs"]]],["builder.rs","config.rs","context.rs","coop.rs","driver.rs","handle.rs","mod.rs","park.rs","runtime.rs"]],["sync",[["mpsc",[],["block.rs","bounded.rs","chan.rs","error.rs","list.rs","mod.rs","unbounded.rs"]],["rwlock",[],["owned_read_guard.rs","owned_write_guard.rs","owned_write_guard_mapped.rs","read_guard.rs","write_guard.rs","write_guard_mapped.rs"]],["task",[],["atomic_waker.rs","mod.rs"]]],["barrier.rs","batch_semaphore.rs","broadcast.rs","mod.rs","mutex.rs","notify.rs","once_cell.rs","oneshot.rs","rwlock.rs","semaphore.rs","watch.rs"]],["task",[],["blocking.rs","join_set.rs","local.rs","mod.rs","spawn.rs","task_local.rs","unconstrained.rs","yield_now.rs"]],["util",[],["atomic_cell.rs","error.rs","idle_notified_set.rs","linked_list.rs","mod.rs","once_cell.rs","rand.rs","rc_cell.rs","sync_wrapper.rs","trace.rs","wake.rs","wake_list.rs"]]],["lib.rs"]],\ -"tokio_macros":["",[],["entry.rs","lib.rs","select.rs"]],\ -"typed_builder":["",[],["field_info.rs","lib.rs","struct_info.rs","util.rs"]],\ -"typenum":["",[],["array.rs","bit.rs","int.rs","lib.rs","marker_traits.rs","operator_aliases.rs","private.rs","type_operators.rs","uint.rs"]],\ -"uint":["",[],["lib.rs","uint.rs"]],\ -"unicode_ident":["",[],["lib.rs","tables.rs"]]\ -}'); -createSourceSidebar(); diff --git a/docs/source-script.js b/docs/source-script.js deleted file mode 100644 index d8b770a425c8..000000000000 --- a/docs/source-script.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(function(){const rootPath=document.getElementById("rustdoc-vars").attributes["data-root-path"].value;const NAME_OFFSET=0;const DIRS_OFFSET=1;const FILES_OFFSET=2;function closeSidebarIfMobile(){if(window.innerWidth"){window.rustdocMobileScrollLock();addClass(document.documentElement,"source-sidebar-expanded");child.innerText="<";updateLocalStorage("source-sidebar-show","true")}else{window.rustdocMobileScrollUnlock();removeClass(document.documentElement,"source-sidebar-expanded");child.innerText=">";updateLocalStorage("source-sidebar-show","false")}}function createSidebarToggle(){const sidebarToggle=document.createElement("div");sidebarToggle.id="sidebar-toggle";const inner=document.createElement("button");if(getCurrentValue("source-sidebar-show")==="true"){inner.innerText="<"}else{inner.innerText=">"}inner.onclick=toggleSidebar;sidebarToggle.appendChild(inner);return sidebarToggle}function createSourceSidebar(){const container=document.querySelector("nav.sidebar");const sidebarToggle=createSidebarToggle();container.insertBefore(sidebarToggle,container.firstChild);const sidebar=document.createElement("div");sidebar.id="source-sidebar";let hasFoundFile=false;const title=document.createElement("div");title.className="title";title.innerText="Files";sidebar.appendChild(title);Object.keys(sourcesIndex).forEach(key=>{sourcesIndex[key][NAME_OFFSET]=key;hasFoundFile=createDirEntry(sourcesIndex[key],sidebar,"",hasFoundFile)});container.appendChild(sidebar);const selected_elem=sidebar.getElementsByClassName("selected")[0];if(typeof selected_elem!=="undefined"){selected_elem.focus()}}const lineNumbersRegex=/^#?(\d+)(?:-(\d+))?$/;function highlightSourceLines(match){if(typeof match==="undefined"){match=window.location.hash.match(lineNumbersRegex)}if(!match){return}let from=parseInt(match[1],10);let to=from;if(typeof match[2]!=="undefined"){to=parseInt(match[2],10)}if(to{onEachLazy(e.getElementsByTagName("span"),i_e=>{removeClass(i_e,"line-highlighted")})});for(let i=from;i<=to;++i){elem=document.getElementById(i);if(!elem){break}addClass(elem,"line-highlighted")}}const handleSourceHighlight=(function(){let prev_line_id=0;const set_fragment=name=>{const x=window.scrollX,y=window.scrollY;if(browserSupportsHistoryApi()){history.replaceState(null,null,"#"+name);highlightSourceLines()}else{location.replace("#"+name)}window.scrollTo(x,y)};return ev=>{let cur_line_id=parseInt(ev.target.id,10);if(isNaN(cur_line_id)){return}ev.preventDefault();if(ev.shiftKey&&prev_line_id){if(prev_line_id>cur_line_id){const tmp=prev_line_id;prev_line_id=cur_line_id;cur_line_id=tmp}set_fragment(prev_line_id+"-"+cur_line_id)}else{prev_line_id=cur_line_id;set_fragment(cur_line_id)}}}());window.addEventListener("hashchange",()=>{const match=window.location.hash.match(lineNumbersRegex);if(match){return highlightSourceLines(match)}});onEachLazy(document.getElementsByClassName("src-line-numbers"),el=>{el.addEventListener("click",handleSourceHighlight)});highlightSourceLines();window.createSourceSidebar=createSourceSidebar})() \ No newline at end of file diff --git a/docs/src/firewood/account.rs.html b/docs/src/firewood/account.rs.html deleted file mode 100644 index 849d06bf77dd..000000000000 --- a/docs/src/firewood/account.rs.html +++ /dev/null @@ -1,338 +0,0 @@ -account.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -
    use std::fmt;
    -use std::io::{Cursor, Write};
    -
    -use crate::merkle::{Hash, Node, ValueTransformer};
    -use primitive_types::U256;
    -use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore};
    -
    -pub struct Account {
    -    pub nonce: u64,
    -    pub balance: U256,
    -    pub root: ObjPtr<Node>,
    -    pub code: ObjPtr<Blob>,
    -    pub root_hash: Hash,
    -    pub code_hash: Hash,
    -}
    -
    -impl Account {
    -    pub fn empty_code() -> &'static Hash {
    -        use once_cell::sync::OnceCell;
    -        static V: OnceCell<Hash> = OnceCell::new();
    -        V.get_or_init(|| {
    -            Hash(
    -                hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
    -                    .unwrap()
    -                    .try_into()
    -                    .unwrap(),
    -            )
    -        })
    -    }
    -
    -    pub fn serialize(&self) -> Vec<u8> {
    -        let mut buff = Vec::new();
    -        buff.extend(self.nonce.to_le_bytes());
    -        buff.resize(40, 0);
    -        self.balance.to_big_endian(&mut buff[8..40]);
    -        buff.extend((self.root.addr()).to_le_bytes());
    -        buff.extend((self.code.addr()).to_le_bytes());
    -        buff.extend(self.root_hash.0);
    -        buff.extend(self.code_hash.0);
    -        buff
    -    }
    -
    -    pub fn deserialize(raw: &[u8]) -> Self {
    -        let nonce = u64::from_le_bytes(raw[..8].try_into().unwrap());
    -        let balance = U256::from_big_endian(&raw[8..40]);
    -        let root = u64::from_le_bytes(raw[40..48].try_into().unwrap());
    -        let code = u64::from_le_bytes(raw[48..56].try_into().unwrap());
    -        let root_hash = Hash(raw[56..88].try_into().unwrap());
    -        let code_hash = Hash(raw[88..].try_into().unwrap());
    -
    -        unsafe {
    -            Self {
    -                nonce,
    -                balance,
    -                root: ObjPtr::new_from_addr(root),
    -                code: ObjPtr::new_from_addr(code),
    -                root_hash,
    -                code_hash,
    -            }
    -        }
    -    }
    -
    -    pub fn set_code(&mut self, code_hash: Hash, code: ObjPtr<Blob>) {
    -        self.code_hash = code_hash;
    -        self.code = code;
    -    }
    -}
    -
    -pub struct AccountRLP;
    -
    -impl ValueTransformer for AccountRLP {
    -    fn transform(raw: &[u8]) -> Vec<u8> {
    -        let acc = Account::deserialize(raw);
    -        let mut stream = rlp::RlpStream::new_list(4);
    -        stream.append(&acc.nonce);
    -        stream.append(&acc.balance);
    -        stream.append(&&acc.root_hash[..]);
    -        stream.append(&&acc.code_hash[..]);
    -        stream.out().into()
    -    }
    -}
    -
    -impl Default for Account {
    -    fn default() -> Self {
    -        Account {
    -            nonce: 0,
    -            balance: U256::zero(),
    -            root: ObjPtr::null(),
    -            code: ObjPtr::null(),
    -            root_hash: crate::merkle::Merkle::empty_root().clone(),
    -            code_hash: Self::empty_code().clone(),
    -        }
    -    }
    -}
    -
    -pub enum Blob {
    -    Code(Vec<u8>),
    -}
    -
    -impl MummyItem for Blob {
    -    // currently there is only one variant of Blob: Code
    -    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
    -        let raw = mem.get_view(addr, 4).ok_or(ShaleError::LinearMemStoreError)?;
    -        let len = u32::from_le_bytes(raw[..].try_into().unwrap()) as u64;
    -        let bytes = mem.get_view(addr + 4, len).ok_or(ShaleError::LinearMemStoreError)?;
    -        Ok(Self::Code(bytes.to_vec()))
    -    }
    -
    -    fn dehydrated_len(&self) -> u64 {
    -        match self {
    -            Self::Code(code) => 4 + code.len() as u64,
    -        }
    -    }
    -
    -    fn dehydrate(&self, to: &mut [u8]) {
    -        match self {
    -            Self::Code(code) => {
    -                let mut cur = Cursor::new(to);
    -                cur.write_all(&(code.len() as u32).to_le_bytes()).unwrap();
    -                cur.write_all(code).unwrap();
    -            }
    -        }
    -    }
    -}
    -
    -#[derive(Debug)]
    -pub enum BlobError {
    -    Shale(ShaleError),
    -}
    -
    -pub struct BlobStash {
    -    store: Box<dyn ShaleStore<Blob>>,
    -}
    -
    -impl BlobStash {
    -    pub fn new(store: Box<dyn ShaleStore<Blob>>) -> Self {
    -        Self { store }
    -    }
    -
    -    pub fn get_blob(&self, ptr: ObjPtr<Blob>) -> Result<ObjRef<Blob>, BlobError> {
    -        self.store.get_item(ptr).map_err(BlobError::Shale)
    -    }
    -
    -    pub fn new_blob(&self, item: Blob) -> Result<ObjRef<Blob>, BlobError> {
    -        self.store.put_item(item, 0).map_err(BlobError::Shale)
    -    }
    -
    -    pub fn free_blob(&mut self, ptr: ObjPtr<Blob>) -> Result<(), BlobError> {
    -        self.store.free_item(ptr).map_err(BlobError::Shale)
    -    }
    -
    -    pub fn flush_dirty(&self) -> Option<()> {
    -        self.store.flush_dirty()
    -    }
    -}
    -
    -impl fmt::Debug for Account {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        write!(
    -            f,
    -            "<Account balance={} nonce={} code_hash={} state_hash={}>",
    -            self.balance,
    -            self.nonce,
    -            hex::encode(*self.code_hash),
    -            hex::encode(*self.root_hash)
    -        )
    -    }
    -}
    -
    -
    \ No newline at end of file diff --git a/docs/src/firewood/db.rs.html b/docs/src/firewood/db.rs.html deleted file mode 100644 index 2667221bc88b..000000000000 --- a/docs/src/firewood/db.rs.html +++ /dev/null @@ -1,2020 +0,0 @@ -db.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    -180
    -181
    -182
    -183
    -184
    -185
    -186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    -200
    -201
    -202
    -203
    -204
    -205
    -206
    -207
    -208
    -209
    -210
    -211
    -212
    -213
    -214
    -215
    -216
    -217
    -218
    -219
    -220
    -221
    -222
    -223
    -224
    -225
    -226
    -227
    -228
    -229
    -230
    -231
    -232
    -233
    -234
    -235
    -236
    -237
    -238
    -239
    -240
    -241
    -242
    -243
    -244
    -245
    -246
    -247
    -248
    -249
    -250
    -251
    -252
    -253
    -254
    -255
    -256
    -257
    -258
    -259
    -260
    -261
    -262
    -263
    -264
    -265
    -266
    -267
    -268
    -269
    -270
    -271
    -272
    -273
    -274
    -275
    -276
    -277
    -278
    -279
    -280
    -281
    -282
    -283
    -284
    -285
    -286
    -287
    -288
    -289
    -290
    -291
    -292
    -293
    -294
    -295
    -296
    -297
    -298
    -299
    -300
    -301
    -302
    -303
    -304
    -305
    -306
    -307
    -308
    -309
    -310
    -311
    -312
    -313
    -314
    -315
    -316
    -317
    -318
    -319
    -320
    -321
    -322
    -323
    -324
    -325
    -326
    -327
    -328
    -329
    -330
    -331
    -332
    -333
    -334
    -335
    -336
    -337
    -338
    -339
    -340
    -341
    -342
    -343
    -344
    -345
    -346
    -347
    -348
    -349
    -350
    -351
    -352
    -353
    -354
    -355
    -356
    -357
    -358
    -359
    -360
    -361
    -362
    -363
    -364
    -365
    -366
    -367
    -368
    -369
    -370
    -371
    -372
    -373
    -374
    -375
    -376
    -377
    -378
    -379
    -380
    -381
    -382
    -383
    -384
    -385
    -386
    -387
    -388
    -389
    -390
    -391
    -392
    -393
    -394
    -395
    -396
    -397
    -398
    -399
    -400
    -401
    -402
    -403
    -404
    -405
    -406
    -407
    -408
    -409
    -410
    -411
    -412
    -413
    -414
    -415
    -416
    -417
    -418
    -419
    -420
    -421
    -422
    -423
    -424
    -425
    -426
    -427
    -428
    -429
    -430
    -431
    -432
    -433
    -434
    -435
    -436
    -437
    -438
    -439
    -440
    -441
    -442
    -443
    -444
    -445
    -446
    -447
    -448
    -449
    -450
    -451
    -452
    -453
    -454
    -455
    -456
    -457
    -458
    -459
    -460
    -461
    -462
    -463
    -464
    -465
    -466
    -467
    -468
    -469
    -470
    -471
    -472
    -473
    -474
    -475
    -476
    -477
    -478
    -479
    -480
    -481
    -482
    -483
    -484
    -485
    -486
    -487
    -488
    -489
    -490
    -491
    -492
    -493
    -494
    -495
    -496
    -497
    -498
    -499
    -500
    -501
    -502
    -503
    -504
    -505
    -506
    -507
    -508
    -509
    -510
    -511
    -512
    -513
    -514
    -515
    -516
    -517
    -518
    -519
    -520
    -521
    -522
    -523
    -524
    -525
    -526
    -527
    -528
    -529
    -530
    -531
    -532
    -533
    -534
    -535
    -536
    -537
    -538
    -539
    -540
    -541
    -542
    -543
    -544
    -545
    -546
    -547
    -548
    -549
    -550
    -551
    -552
    -553
    -554
    -555
    -556
    -557
    -558
    -559
    -560
    -561
    -562
    -563
    -564
    -565
    -566
    -567
    -568
    -569
    -570
    -571
    -572
    -573
    -574
    -575
    -576
    -577
    -578
    -579
    -580
    -581
    -582
    -583
    -584
    -585
    -586
    -587
    -588
    -589
    -590
    -591
    -592
    -593
    -594
    -595
    -596
    -597
    -598
    -599
    -600
    -601
    -602
    -603
    -604
    -605
    -606
    -607
    -608
    -609
    -610
    -611
    -612
    -613
    -614
    -615
    -616
    -617
    -618
    -619
    -620
    -621
    -622
    -623
    -624
    -625
    -626
    -627
    -628
    -629
    -630
    -631
    -632
    -633
    -634
    -635
    -636
    -637
    -638
    -639
    -640
    -641
    -642
    -643
    -644
    -645
    -646
    -647
    -648
    -649
    -650
    -651
    -652
    -653
    -654
    -655
    -656
    -657
    -658
    -659
    -660
    -661
    -662
    -663
    -664
    -665
    -666
    -667
    -668
    -669
    -670
    -671
    -672
    -673
    -674
    -675
    -676
    -677
    -678
    -679
    -680
    -681
    -682
    -683
    -684
    -685
    -686
    -687
    -688
    -689
    -690
    -691
    -692
    -693
    -694
    -695
    -696
    -697
    -698
    -699
    -700
    -701
    -702
    -703
    -704
    -705
    -706
    -707
    -708
    -709
    -710
    -711
    -712
    -713
    -714
    -715
    -716
    -717
    -718
    -719
    -720
    -721
    -722
    -723
    -724
    -725
    -726
    -727
    -728
    -729
    -730
    -731
    -732
    -733
    -734
    -735
    -736
    -737
    -738
    -739
    -740
    -741
    -742
    -743
    -744
    -745
    -746
    -747
    -748
    -749
    -750
    -751
    -752
    -753
    -754
    -755
    -756
    -757
    -758
    -759
    -760
    -761
    -762
    -763
    -764
    -765
    -766
    -767
    -768
    -769
    -770
    -771
    -772
    -773
    -774
    -775
    -776
    -777
    -778
    -779
    -780
    -781
    -782
    -783
    -784
    -785
    -786
    -787
    -788
    -789
    -790
    -791
    -792
    -793
    -794
    -795
    -796
    -797
    -798
    -799
    -800
    -801
    -802
    -803
    -804
    -805
    -806
    -807
    -808
    -809
    -810
    -811
    -812
    -813
    -814
    -815
    -816
    -817
    -818
    -819
    -820
    -821
    -822
    -823
    -824
    -825
    -826
    -827
    -828
    -829
    -830
    -831
    -832
    -833
    -834
    -835
    -836
    -837
    -838
    -839
    -840
    -841
    -842
    -843
    -844
    -845
    -846
    -847
    -848
    -849
    -850
    -851
    -852
    -853
    -854
    -855
    -856
    -857
    -858
    -859
    -860
    -861
    -862
    -863
    -864
    -865
    -866
    -867
    -868
    -869
    -870
    -871
    -872
    -873
    -874
    -875
    -876
    -877
    -878
    -879
    -880
    -881
    -882
    -883
    -884
    -885
    -886
    -887
    -888
    -889
    -890
    -891
    -892
    -893
    -894
    -895
    -896
    -897
    -898
    -899
    -900
    -901
    -902
    -903
    -904
    -905
    -906
    -907
    -908
    -909
    -910
    -911
    -912
    -913
    -914
    -915
    -916
    -917
    -918
    -919
    -920
    -921
    -922
    -923
    -924
    -925
    -926
    -927
    -928
    -929
    -930
    -931
    -932
    -933
    -934
    -935
    -936
    -937
    -938
    -939
    -940
    -941
    -942
    -943
    -944
    -945
    -946
    -947
    -948
    -949
    -950
    -951
    -952
    -953
    -954
    -955
    -956
    -957
    -958
    -959
    -960
    -961
    -962
    -963
    -964
    -965
    -966
    -967
    -968
    -969
    -970
    -971
    -972
    -973
    -974
    -975
    -976
    -977
    -978
    -979
    -980
    -981
    -982
    -983
    -984
    -985
    -986
    -987
    -988
    -989
    -990
    -991
    -992
    -993
    -994
    -995
    -996
    -997
    -998
    -999
    -1000
    -1001
    -1002
    -1003
    -1004
    -1005
    -1006
    -1007
    -1008
    -1009
    -
    use std::collections::VecDeque;
    -use std::io::{Cursor, Write};
    -use std::rc::Rc;
    -use std::thread::JoinHandle;
    -
    -use parking_lot::{Mutex, MutexGuard};
    -use primitive_types::U256;
    -use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID};
    -use typed_builder::TypedBuilder;
    -
    -use crate::account::{Account, AccountRLP, Blob, BlobStash};
    -use crate::file;
    -use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node};
    -use crate::storage::{CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared};
    -pub use crate::storage::{DiskBufferConfig, WALConfig};
    -
    -const MERKLE_META_SPACE: SpaceID = 0x0;
    -const MERKLE_PAYLOAD_SPACE: SpaceID = 0x1;
    -const BLOB_META_SPACE: SpaceID = 0x2;
    -const BLOB_PAYLOAD_SPACE: SpaceID = 0x3;
    -const SPACE_RESERVED: u64 = 0x1000;
    -
    -const MAGIC_STR: &[u8; 13] = b"firewood v0.1";
    -
    -#[derive(Debug)]
    -pub enum DBError {
    -    InvalidParams,
    -    Merkle(MerkleError),
    -    Blob(crate::account::BlobError),
    -    System(nix::Error),
    -}
    -
    -/// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the
    -/// correct parameters are used when the DB is opened later (the parameters here will override the
    -/// parameters in [DBConfig] if the DB already exists).
    -#[repr(C)]
    -struct DBParams {
    -    magic: [u8; 16],
    -    meta_file_nbit: u64,
    -    payload_file_nbit: u64,
    -    payload_regn_nbit: u64,
    -    wal_file_nbit: u64,
    -    wal_block_nbit: u64,
    -}
    -
    -/// Config for accessing a version of the DB.
    -#[derive(TypedBuilder, Clone)]
    -pub struct DBRevConfig {
    -    /// Maximum cached MPT objects.
    -    #[builder(default = 1 << 20)]
    -    merkle_ncached_objs: usize,
    -    /// Maximum cached Blob (currently just `Account`) objects.
    -    #[builder(default = 4096)]
    -    blob_ncached_objs: usize,
    -}
    -
    -/// Database configuration.
    -#[derive(TypedBuilder)]
    -pub struct DBConfig {
    -    /// Maximum cached pages for the free list of the item stash.
    -    #[builder(default = 16384)] // 64M total size by default
    -    meta_ncached_pages: usize,
    -    /// Maximum cached file descriptors for the free list of the item stash.
    -    #[builder(default = 1024)] // 1K fds by default
    -    meta_ncached_files: usize,
    -    /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to
    -    /// the power of 2 for the file size.
    -    #[builder(default = 22)] // 4MB file by default
    -    meta_file_nbit: u64,
    -    /// Maximum cached pages for the item stash. This is the low-level cache used by the linear
    -    /// space that holds MPT nodes and account objects.
    -    #[builder(default = 262144)] // 1G total size by default
    -    payload_ncached_pages: usize,
    -    /// Maximum cached file descriptors for the item stash.
    -    #[builder(default = 1024)] // 1K fds by default
    -    payload_ncached_files: usize,
    -    /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to
    -    /// the power of 2 for the file size.
    -    #[builder(default = 22)] // 4MB file by default
    -    payload_file_nbit: u64,
    -    /// Maximum steps of walk to recycle a freed item.
    -    #[builder(default = 10)]
    -    payload_max_walk: u64,
    -    /// Region size in bits (should be not greater than `payload_file_nbit`). One file is
    -    /// partitioned into multiple regions. Just use the default value.
    -    #[builder(default = 22)]
    -    payload_regn_nbit: u64,
    -    /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its
    -    /// existing contents will be lost.
    -    #[builder(default = false)]
    -    truncate: bool,
    -    /// Config for accessing a version of the DB.
    -    #[builder(default = DBRevConfig::builder().build())]
    -    rev: DBRevConfig,
    -    /// Config for the disk buffer.
    -    #[builder(default = DiskBufferConfig::builder().build())]
    -    buffer: DiskBufferConfig,
    -    /// Config for WAL.
    -    #[builder(default = WALConfig::builder().build())]
    -    wal: WALConfig,
    -}
    -
    -/// Necessary linear space instances bundled for a `CompactSpace`.
    -struct SubUniverse<T> {
    -    meta: T,
    -    payload: T,
    -}
    -
    -impl<T> SubUniverse<T> {
    -    fn new(meta: T, payload: T) -> Self {
    -        Self { meta, payload }
    -    }
    -}
    -
    -impl SubUniverse<StoreRevShared> {
    -    fn to_mem_store_r(&self) -> SubUniverse<Rc<dyn MemStoreR>> {
    -        SubUniverse {
    -            meta: self.meta.inner().clone(),
    -            payload: self.payload.inner().clone(),
    -        }
    -    }
    -}
    -
    -impl SubUniverse<Rc<dyn MemStoreR>> {
    -    fn rewind(&self, meta_writes: &[SpaceWrite], payload_writes: &[SpaceWrite]) -> SubUniverse<StoreRevShared> {
    -        SubUniverse::new(
    -            StoreRevShared::from_ash(self.meta.clone(), meta_writes),
    -            StoreRevShared::from_ash(self.payload.clone(), payload_writes),
    -        )
    -    }
    -}
    -
    -impl SubUniverse<Rc<CachedSpace>> {
    -    fn to_mem_store_r(&self) -> SubUniverse<Rc<dyn MemStoreR>> {
    -        SubUniverse {
    -            meta: self.meta.clone(),
    -            payload: self.payload.clone(),
    -        }
    -    }
    -}
    -
    -/// DB-wide metadata, it keeps track of the roots of the top-level tries.
    -struct DBHeader {
    -    /// The root node of the account model storage. (Where the values are [Account] objects, which
    -    /// may contain the root for the secondary trie.)
    -    acc_root: ObjPtr<Node>,
    -    /// The root node of the generic key-value store.
    -    kv_root: ObjPtr<Node>,
    -}
    -
    -impl DBHeader {
    -    pub const MSIZE: u64 = 16;
    -
    -    pub fn new_empty() -> Self {
    -        Self {
    -            acc_root: ObjPtr::null(),
    -            kv_root: ObjPtr::null(),
    -        }
    -    }
    -}
    -
    -impl MummyItem for DBHeader {
    -    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, shale::ShaleError> {
    -        let raw = mem
    -            .get_view(addr, Self::MSIZE)
    -            .ok_or(shale::ShaleError::LinearMemStoreError)?;
    -        let acc_root = u64::from_le_bytes(raw[..8].try_into().unwrap());
    -        let kv_root = u64::from_le_bytes(raw[8..].try_into().unwrap());
    -        unsafe {
    -            Ok(Self {
    -                acc_root: ObjPtr::new_from_addr(acc_root),
    -                kv_root: ObjPtr::new_from_addr(kv_root),
    -            })
    -        }
    -    }
    -
    -    fn dehydrated_len(&self) -> u64 {
    -        Self::MSIZE
    -    }
    -
    -    fn dehydrate(&self, to: &mut [u8]) {
    -        let mut cur = Cursor::new(to);
    -        cur.write_all(&self.acc_root.addr().to_le_bytes()).unwrap();
    -        cur.write_all(&self.kv_root.addr().to_le_bytes()).unwrap();
    -    }
    -}
    -
    -/// Necessary linear space instances bundled for the state of the entire DB.
    -struct Universe<T> {
    -    merkle: SubUniverse<T>,
    -    blob: SubUniverse<T>,
    -}
    -
    -impl Universe<StoreRevShared> {
    -    fn to_mem_store_r(&self) -> Universe<Rc<dyn MemStoreR>> {
    -        Universe {
    -            merkle: self.merkle.to_mem_store_r(),
    -            blob: self.blob.to_mem_store_r(),
    -        }
    -    }
    -}
    -
    -impl Universe<Rc<CachedSpace>> {
    -    fn to_mem_store_r(&self) -> Universe<Rc<dyn MemStoreR>> {
    -        Universe {
    -            merkle: self.merkle.to_mem_store_r(),
    -            blob: self.blob.to_mem_store_r(),
    -        }
    -    }
    -}
    -
    -impl Universe<Rc<dyn MemStoreR>> {
    -    fn rewind(
    -        &self, merkle_meta_writes: &[SpaceWrite], merkle_payload_writes: &[SpaceWrite],
    -        blob_meta_writes: &[SpaceWrite], blob_payload_writes: &[SpaceWrite],
    -    ) -> Universe<StoreRevShared> {
    -        Universe {
    -            merkle: self.merkle.rewind(merkle_meta_writes, merkle_payload_writes),
    -            blob: self.blob.rewind(blob_meta_writes, blob_payload_writes),
    -        }
    -    }
    -}
    -
    -/// Some readable version of the DB.
    -pub struct DBRev {
    -    header: shale::Obj<DBHeader>,
    -    merkle: Merkle,
    -    blob: BlobStash,
    -}
    -
    -impl DBRev {
    -    fn flush_dirty(&mut self) -> Option<()> {
    -        self.header.flush_dirty();
    -        self.merkle.flush_dirty()?;
    -        self.blob.flush_dirty()
    -    }
    -
    -    fn borrow_split(&mut self) -> (&mut shale::Obj<DBHeader>, &mut Merkle, &mut BlobStash) {
    -        (&mut self.header, &mut self.merkle, &mut self.blob)
    -    }
    -
    -    /// Get root hash of the generic key-value storage.
    -    pub fn kv_root_hash(&self) -> Result<Hash, DBError> {
    -        self.merkle
    -            .root_hash::<IdTrans>(self.header.kv_root)
    -            .map_err(DBError::Merkle)
    -    }
    -
    -    /// Dump the MPT of the generic key-value storage.
    -    pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
    -        self.merkle.dump(self.header.kv_root, w).map_err(DBError::Merkle)
    -    }
    -
    -    /// Get root hash of the world state of all accounts.
    -    pub fn root_hash(&self) -> Result<Hash, DBError> {
    -        self.merkle
    -            .root_hash::<AccountRLP>(self.header.acc_root)
    -            .map_err(DBError::Merkle)
    -    }
    -
    -    /// Dump the MPT of the entire account model storage.
    -    pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
    -        self.merkle.dump(self.header.acc_root, w).map_err(DBError::Merkle)
    -    }
    -
    -    fn get_account(&self, key: &[u8]) -> Result<Account, DBError> {
    -        Ok(match self.merkle.get(key, self.header.acc_root) {
    -            Ok(Some(bytes)) => Account::deserialize(&bytes),
    -            Ok(None) => Account::default(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        })
    -    }
    -
    -    /// Dump the MPT of the state storage under an account.
    -    pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> {
    -        let acc = match self.merkle.get(key, self.header.acc_root) {
    -            Ok(Some(bytes)) => Account::deserialize(&bytes),
    -            Ok(None) => Account::default(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        };
    -        writeln!(w, "{acc:?}").unwrap();
    -        if !acc.root.is_null() {
    -            self.merkle.dump(acc.root, w).map_err(DBError::Merkle)?;
    -        }
    -        Ok(())
    -    }
    -
    -    /// Get balance of the account.
    -    pub fn get_balance(&self, key: &[u8]) -> Result<U256, DBError> {
    -        Ok(self.get_account(key)?.balance)
    -    }
    -
    -    /// Get code of the account.
    -    pub fn get_code(&self, key: &[u8]) -> Result<Vec<u8>, DBError> {
    -        let code = self.get_account(key)?.code;
    -        if code.is_null() {
    -            return Ok(Vec::new())
    -        }
    -        let b = self.blob.get_blob(code).map_err(DBError::Blob)?;
    -        Ok(match &**b {
    -            Blob::Code(code) => code.clone(),
    -        })
    -    }
    -
    -    /// Get nonce of the account.
    -    pub fn get_nonce(&self, key: &[u8]) -> Result<u64, DBError> {
    -        Ok(self.get_account(key)?.nonce)
    -    }
    -
    -    /// Get the state value indexed by `sub_key` in the account indexed by `key`.
    -    pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result<Vec<u8>, DBError> {
    -        let root = self.get_account(key)?.root;
    -        if root.is_null() {
    -            return Ok(Vec::new())
    -        }
    -        Ok(match self.merkle.get(sub_key, root) {
    -            Ok(Some(v)) => v.to_vec(),
    -            Ok(None) => Vec::new(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        })
    -    }
    -
    -    /// Check if the account exists.
    -    pub fn exist(&self, key: &[u8]) -> Result<bool, DBError> {
    -        Ok(match self.merkle.get(key, self.header.acc_root) {
    -            Ok(r) => r.is_some(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        })
    -    }
    -}
    -
    -struct DBInner {
    -    latest: DBRev,
    -    disk_requester: crate::storage::DiskBufferRequester,
    -    disk_thread: Option<JoinHandle<()>>,
    -    staging: Universe<Rc<StoreRevMut>>,
    -    cached: Universe<Rc<CachedSpace>>,
    -    revisions: VecDeque<Universe<StoreRevShared>>,
    -    max_revisions: usize,
    -}
    -
    -impl Drop for DBInner {
    -    fn drop(&mut self) {
    -        self.disk_requester.shutdown();
    -        self.disk_thread.take().map(JoinHandle::join);
    -    }
    -}
    -
    -/// Firewood database handle.
    -pub struct DB {
    -    inner: Mutex<DBInner>,
    -    payload_regn_nbit: u64,
    -    rev_cfg: DBRevConfig,
    -}
    -
    -impl DB {
    -    /// Open a database.
    -    pub fn new(db_path: &str, cfg: &DBConfig) -> Result<Self, DBError> {
    -        // TODO: make sure all fds are released at the end
    -        if cfg.truncate {
    -            let _ = std::fs::remove_dir_all(db_path);
    -        }
    -        let (db_fd, reset) = file::open_dir(db_path, cfg.truncate).map_err(DBError::System)?;
    -
    -        let merkle_fd = file::touch_dir("merkle", db_fd).map_err(DBError::System)?;
    -        let merkle_meta_fd = file::touch_dir("meta", merkle_fd).map_err(DBError::System)?;
    -        let merkle_payload_fd = file::touch_dir("compact", merkle_fd).map_err(DBError::System)?;
    -
    -        let blob_fd = file::touch_dir("blob", db_fd).map_err(DBError::System)?;
    -        let blob_meta_fd = file::touch_dir("meta", blob_fd).map_err(DBError::System)?;
    -        let blob_payload_fd = file::touch_dir("compact", blob_fd).map_err(DBError::System)?;
    -
    -        let file0 = crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?;
    -        let fd0 = file0.get_fd();
    -
    -        if reset {
    -            // initialize DBParams
    -            if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT {
    -                return Err(DBError::InvalidParams)
    -            }
    -            nix::unistd::ftruncate(fd0, 0).map_err(DBError::System)?;
    -            nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DBError::System)?;
    -            let mut magic = [0; 16];
    -            magic[..MAGIC_STR.len()].copy_from_slice(MAGIC_STR);
    -            let header = DBParams {
    -                magic,
    -                meta_file_nbit: cfg.meta_file_nbit,
    -                payload_file_nbit: cfg.payload_file_nbit,
    -                payload_regn_nbit: cfg.payload_regn_nbit,
    -                wal_file_nbit: cfg.wal.file_nbit,
    -                wal_block_nbit: cfg.wal.block_nbit,
    -            };
    -            nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0).map_err(DBError::System)?;
    -        }
    -
    -        // read DBParams
    -        let mut header_bytes = [0; std::mem::size_of::<DBParams>()];
    -        nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DBError::System)?;
    -        drop(file0);
    -        let mut offset = header_bytes.len() as u64;
    -        let header = unsafe { std::mem::transmute::<_, DBParams>(header_bytes) };
    -
    -        // setup disk buffer
    -        let cached = Universe {
    -            merkle: SubUniverse::new(
    -                Rc::new(
    -                    CachedSpace::new(
    -                        &StoreConfig::builder()
    -                            .ncached_pages(cfg.meta_ncached_pages)
    -                            .ncached_files(cfg.meta_ncached_files)
    -                            .space_id(MERKLE_META_SPACE)
    -                            .file_nbit(header.meta_file_nbit)
    -                            .rootfd(merkle_meta_fd)
    -                            .build(),
    -                    )
    -                    .unwrap(),
    -                ),
    -                Rc::new(
    -                    CachedSpace::new(
    -                        &StoreConfig::builder()
    -                            .ncached_pages(cfg.payload_ncached_pages)
    -                            .ncached_files(cfg.payload_ncached_files)
    -                            .space_id(MERKLE_PAYLOAD_SPACE)
    -                            .file_nbit(header.payload_file_nbit)
    -                            .rootfd(merkle_payload_fd)
    -                            .build(),
    -                    )
    -                    .unwrap(),
    -                ),
    -            ),
    -            blob: SubUniverse::new(
    -                Rc::new(
    -                    CachedSpace::new(
    -                        &StoreConfig::builder()
    -                            .ncached_pages(cfg.meta_ncached_pages)
    -                            .ncached_files(cfg.meta_ncached_files)
    -                            .space_id(BLOB_META_SPACE)
    -                            .file_nbit(header.meta_file_nbit)
    -                            .rootfd(blob_meta_fd)
    -                            .build(),
    -                    )
    -                    .unwrap(),
    -                ),
    -                Rc::new(
    -                    CachedSpace::new(
    -                        &StoreConfig::builder()
    -                            .ncached_pages(cfg.payload_ncached_pages)
    -                            .ncached_files(cfg.payload_ncached_files)
    -                            .space_id(BLOB_PAYLOAD_SPACE)
    -                            .file_nbit(header.payload_file_nbit)
    -                            .rootfd(blob_payload_fd)
    -                            .build(),
    -                    )
    -                    .unwrap(),
    -                ),
    -            ),
    -        };
    -
    -        let wal = WALConfig::builder()
    -            .file_nbit(header.wal_file_nbit)
    -            .block_nbit(header.wal_block_nbit)
    -            .max_revisions(cfg.wal.max_revisions)
    -            .build();
    -        let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered);
    -        let disk_requester = crate::storage::DiskBufferRequester::new(sender);
    -        let buffer = cfg.buffer.clone();
    -        let disk_thread = Some(std::thread::spawn(move || {
    -            let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap();
    -            disk_buffer.run()
    -        }));
    -
    -        disk_requester.reg_cached_space(cached.merkle.meta.as_ref());
    -        disk_requester.reg_cached_space(cached.merkle.payload.as_ref());
    -        disk_requester.reg_cached_space(cached.blob.meta.as_ref());
    -        disk_requester.reg_cached_space(cached.blob.payload.as_ref());
    -
    -        let staging = Universe {
    -            merkle: SubUniverse::new(
    -                Rc::new(StoreRevMut::new(cached.merkle.meta.clone() as Rc<dyn MemStoreR>)),
    -                Rc::new(StoreRevMut::new(cached.merkle.payload.clone() as Rc<dyn MemStoreR>)),
    -            ),
    -            blob: SubUniverse::new(
    -                Rc::new(StoreRevMut::new(cached.blob.meta.clone() as Rc<dyn MemStoreR>)),
    -                Rc::new(StoreRevMut::new(cached.blob.payload.clone() as Rc<dyn MemStoreR>)),
    -            ),
    -        };
    -
    -        // recover from WAL
    -        disk_requester.init_wal("wal", db_fd);
    -
    -        // set up the storage layout
    -        let db_header: ObjPtr<DBHeader>;
    -        let merkle_payload_header: ObjPtr<CompactSpaceHeader>;
    -        let blob_payload_header: ObjPtr<CompactSpaceHeader>;
    -        unsafe {
    -            db_header = ObjPtr::new_from_addr(offset);
    -            offset += DBHeader::MSIZE;
    -            merkle_payload_header = ObjPtr::new_from_addr(offset);
    -            offset += CompactSpaceHeader::MSIZE;
    -            assert!(offset <= SPACE_RESERVED);
    -            blob_payload_header = ObjPtr::new_from_addr(0);
    -        }
    -
    -        if reset {
    -            // initialize space headers
    -            staging.merkle.meta.write(
    -                merkle_payload_header.addr(),
    -                &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)),
    -            );
    -            staging
    -                .merkle
    -                .meta
    -                .write(db_header.addr(), &shale::to_dehydrated(&DBHeader::new_empty()));
    -            staging.blob.meta.write(
    -                blob_payload_header.addr(),
    -                &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(SPACE_RESERVED, SPACE_RESERVED)),
    -            );
    -        }
    -
    -        let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe {
    -            let merkle_meta_ref = staging.merkle.meta.as_ref() as &dyn MemStore;
    -            let blob_meta_ref = staging.blob.meta.as_ref() as &dyn MemStore;
    -
    -            (
    -                MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(),
    -                MummyObj::ptr_to_obj(
    -                    merkle_meta_ref,
    -                    merkle_payload_header,
    -                    shale::compact::CompactHeader::MSIZE,
    -                )
    -                .unwrap(),
    -                MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(),
    -            )
    -        };
    -
    -        let merkle_space = shale::compact::CompactSpace::new(
    -            staging.merkle.meta.clone(),
    -            staging.merkle.payload.clone(),
    -            merkle_payload_header_ref,
    -            shale::ObjCache::new(cfg.rev.merkle_ncached_objs),
    -            cfg.payload_max_walk,
    -            header.payload_regn_nbit,
    -        )
    -        .unwrap();
    -
    -        let blob_space = shale::compact::CompactSpace::new(
    -            staging.blob.meta.clone(),
    -            staging.blob.payload.clone(),
    -            blob_payload_header_ref,
    -            shale::ObjCache::new(cfg.rev.blob_ncached_objs),
    -            cfg.payload_max_walk,
    -            header.payload_regn_nbit,
    -        )
    -        .unwrap();
    -
    -        if db_header_ref.acc_root.is_null() {
    -            let mut err = Ok(());
    -            // create the sentinel node
    -            db_header_ref
    -                .write(|r| {
    -                    err = (|| {
    -                        Merkle::init_root(&mut r.acc_root, &merkle_space)?;
    -                        Merkle::init_root(&mut r.kv_root, &merkle_space)
    -                    })();
    -                })
    -                .unwrap();
    -            err.map_err(DBError::Merkle)?
    -        }
    -
    -        let mut latest = DBRev {
    -            header: db_header_ref,
    -            merkle: Merkle::new(Box::new(merkle_space)),
    -            blob: BlobStash::new(Box::new(blob_space)),
    -        };
    -        latest.flush_dirty().unwrap();
    -
    -        Ok(Self {
    -            inner: Mutex::new(DBInner {
    -                latest,
    -                disk_thread,
    -                disk_requester,
    -                staging,
    -                cached,
    -                revisions: VecDeque::new(),
    -                max_revisions: cfg.wal.max_revisions as usize,
    -            }),
    -            payload_regn_nbit: header.payload_regn_nbit,
    -            rev_cfg: cfg.rev.clone(),
    -        })
    -    }
    -
    -    /// Create a write batch.
    -    pub fn new_writebatch(&self) -> WriteBatch {
    -        WriteBatch {
    -            m: self.inner.lock(),
    -            root_hash_recalc: true,
    -            committed: false,
    -        }
    -    }
    -
    -    /// Dump the MPT of the latest generic key-value storage.
    -    pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
    -        self.inner.lock().latest.kv_dump(w)
    -    }
    -
    -    /// Dump the MPT of the latest entire account model storage.
    -    pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> {
    -        self.inner.lock().latest.dump(w)
    -    }
    -
    -    /// Dump the MPT of the latest state storage under an account.
    -    pub fn dump_account(&self, key: &[u8], w: &mut dyn Write) -> Result<(), DBError> {
    -        self.inner.lock().latest.dump_account(key, w)
    -    }
    -
    -    /// Get root hash of the latest generic key-value storage.
    -    pub fn kv_root_hash(&self) -> Result<Hash, DBError> {
    -        self.inner.lock().latest.kv_root_hash()
    -    }
    -
    -    /// Get root hash of the latest world state of all accounts.
    -    pub fn root_hash(&self) -> Result<Hash, DBError> {
    -        self.inner.lock().latest.root_hash()
    -    }
    -
    -    /// Get the latest balance of the account.
    -    pub fn get_balance(&self, key: &[u8]) -> Result<U256, DBError> {
    -        self.inner.lock().latest.get_balance(key)
    -    }
    -
    -    /// Get the latest code of the account.
    -    pub fn get_code(&self, key: &[u8]) -> Result<Vec<u8>, DBError> {
    -        self.inner.lock().latest.get_code(key)
    -    }
    -
    -    /// Get the latest nonce of the account.
    -    pub fn get_nonce(&self, key: &[u8]) -> Result<u64, DBError> {
    -        self.inner.lock().latest.get_nonce(key)
    -    }
    -
    -    /// Get the latest state value indexed by `sub_key` in the account indexed by `key`.
    -    pub fn get_state(&self, key: &[u8], sub_key: &[u8]) -> Result<Vec<u8>, DBError> {
    -        self.inner.lock().latest.get_state(key, sub_key)
    -    }
    -
    -    /// Check if the account exists in the latest world state.
    -    pub fn exist(&self, key: &[u8]) -> Result<bool, DBError> {
    -        self.inner.lock().latest.exist(key)
    -    }
    -
    -    /// Get a handle that grants the access to some historical state of the entire DB.
    -    pub fn get_revision(&self, nback: usize, cfg: Option<DBRevConfig>) -> Option<Revision> {
    -        let mut inner = self.inner.lock();
    -
    -        let rlen = inner.revisions.len();
    -        if nback == 0 || nback > inner.max_revisions {
    -            return None
    -        }
    -        if rlen < nback {
    -            let ashes = inner.disk_requester.collect_ash(nback);
    -            for mut ash in ashes.into_iter().skip(rlen) {
    -                for (_, a) in ash.0.iter_mut() {
    -                    a.old.reverse()
    -                }
    -
    -                let u = match inner.revisions.back() {
    -                    Some(u) => u.to_mem_store_r(),
    -                    None => inner.cached.to_mem_store_r(),
    -                };
    -                inner.revisions.push_back(u.rewind(
    -                    &ash.0[&MERKLE_META_SPACE].old,
    -                    &ash.0[&MERKLE_PAYLOAD_SPACE].old,
    -                    &ash.0[&BLOB_META_SPACE].old,
    -                    &ash.0[&BLOB_PAYLOAD_SPACE].old,
    -                ));
    -            }
    -        }
    -        if inner.revisions.len() < nback {
    -            return None
    -        }
    -        // set up the storage layout
    -        let db_header: ObjPtr<DBHeader>;
    -        let merkle_payload_header: ObjPtr<CompactSpaceHeader>;
    -        let blob_payload_header: ObjPtr<CompactSpaceHeader>;
    -        unsafe {
    -            let mut offset = std::mem::size_of::<DBParams>() as u64;
    -            // DBHeader starts after DBParams in merkle meta space
    -            db_header = ObjPtr::new_from_addr(offset);
    -            offset += DBHeader::MSIZE;
    -            // Merkle CompactHeader starts after DBHeader in merkle meta space
    -            merkle_payload_header = ObjPtr::new_from_addr(offset);
    -            offset += CompactSpaceHeader::MSIZE;
    -            assert!(offset <= SPACE_RESERVED);
    -            // Blob CompactSpaceHeader starts right in blob meta space
    -            blob_payload_header = ObjPtr::new_from_addr(0);
    -        }
    -
    -        let space = &inner.revisions[nback - 1];
    -
    -        let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = unsafe {
    -            let merkle_meta_ref = &space.merkle.meta as &dyn MemStore;
    -            let blob_meta_ref = &space.blob.meta as &dyn MemStore;
    -
    -            (
    -                MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(),
    -                MummyObj::ptr_to_obj(
    -                    merkle_meta_ref,
    -                    merkle_payload_header,
    -                    shale::compact::CompactHeader::MSIZE,
    -                )
    -                .unwrap(),
    -                MummyObj::ptr_to_obj(blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE).unwrap(),
    -            )
    -        };
    -
    -        let merkle_space = shale::compact::CompactSpace::new(
    -            Rc::new(space.merkle.meta.clone()),
    -            Rc::new(space.merkle.payload.clone()),
    -            merkle_payload_header_ref,
    -            shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).merkle_ncached_objs),
    -            0,
    -            self.payload_regn_nbit,
    -        )
    -        .unwrap();
    -
    -        let blob_space = shale::compact::CompactSpace::new(
    -            Rc::new(space.blob.meta.clone()),
    -            Rc::new(space.blob.payload.clone()),
    -            blob_payload_header_ref,
    -            shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).blob_ncached_objs),
    -            0,
    -            self.payload_regn_nbit,
    -        )
    -        .unwrap();
    -
    -        Some(Revision {
    -            _m: inner,
    -            rev: DBRev {
    -                header: db_header_ref,
    -                merkle: Merkle::new(Box::new(merkle_space)),
    -                blob: BlobStash::new(Box::new(blob_space)),
    -            },
    -        })
    -    }
    -}
    -
    -/// Lock protected handle to a readable version of the DB.
    -pub struct Revision<'a> {
    -    _m: MutexGuard<'a, DBInner>,
    -    rev: DBRev,
    -}
    -
    -impl<'a> std::ops::Deref for Revision<'a> {
    -    type Target = DBRev;
    -    fn deref(&self) -> &DBRev {
    -        &self.rev
    -    }
    -}
    -
    -/// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself
    -/// because when an error occurs, the write batch will be automaticlaly aborted so that the DB
    -/// remains clean.
    -pub struct WriteBatch<'a> {
    -    m: MutexGuard<'a, DBInner>,
    -    root_hash_recalc: bool,
    -    committed: bool,
    -}
    -
    -impl<'a> WriteBatch<'a> {
    -    /// Insert an item to the generic key-value storage.
    -    pub fn kv_insert<K: AsRef<[u8]>>(mut self, key: K, val: Vec<u8>) -> Result<Self, DBError> {
    -        let (header, merkle, _) = self.m.latest.borrow_split();
    -        merkle.insert(key, val, header.kv_root).map_err(DBError::Merkle)?;
    -        Ok(self)
    -    }
    -
    -    /// Remove an item from the generic key-value storage. `val` will be set to the value that is
    -    /// removed from the storage if it exists.
    -    pub fn kv_remove<K: AsRef<[u8]>>(mut self, key: K, val: &mut Option<Vec<u8>>) -> Result<Self, DBError> {
    -        let (header, merkle, _) = self.m.latest.borrow_split();
    -        *val = merkle.remove(key, header.kv_root).map_err(DBError::Merkle)?;
    -        Ok(self)
    -    }
    -
    -    fn change_account(
    -        &mut self, key: &[u8], modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>,
    -    ) -> Result<(), DBError> {
    -        let (header, merkle, blob) = self.m.latest.borrow_split();
    -        match merkle.get_mut(key, header.acc_root) {
    -            Ok(Some(mut bytes)) => {
    -                let mut ret = Ok(());
    -                bytes
    -                    .write(|b| {
    -                        let mut acc = Account::deserialize(b);
    -                        ret = modify(&mut acc, blob);
    -                        if ret.is_err() {
    -                            return
    -                        }
    -                        *b = acc.serialize();
    -                    })
    -                    .map_err(DBError::Merkle)?;
    -                ret?;
    -            }
    -            Ok(None) => {
    -                let mut acc = Account::default();
    -                modify(&mut acc, blob)?;
    -                merkle
    -                    .insert(key, acc.serialize(), header.acc_root)
    -                    .map_err(DBError::Merkle)?;
    -            }
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        }
    -        Ok(())
    -    }
    -
    -    /// Set balance of the account.
    -    pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result<Self, DBError> {
    -        self.change_account(key, |acc, _| {
    -            acc.balance = balance;
    -            Ok(())
    -        })?;
    -        Ok(self)
    -    }
    -
    -    /// Set code of the account.
    -    pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result<Self, DBError> {
    -        use sha3::Digest;
    -        self.change_account(key, |acc, blob_stash| {
    -            if !acc.code.is_null() {
    -                blob_stash.free_blob(acc.code).map_err(DBError::Blob)?;
    -            }
    -            acc.set_code(
    -                Hash(sha3::Keccak256::digest(code).into()),
    -                blob_stash
    -                    .new_blob(Blob::Code(code.to_vec()))
    -                    .map_err(DBError::Blob)?
    -                    .as_ptr(),
    -            );
    -            Ok(())
    -        })?;
    -        Ok(self)
    -    }
    -
    -    /// Set nonce of the account.
    -    pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result<Self, DBError> {
    -        self.change_account(key, |acc, _| {
    -            acc.nonce = nonce;
    -            Ok(())
    -        })?;
    -        Ok(self)
    -    }
    -
    -    /// Set the state value indexed by `sub_key` in the account indexed by `key`.
    -    pub fn set_state(mut self, key: &[u8], sub_key: &[u8], val: Vec<u8>) -> Result<Self, DBError> {
    -        let (header, merkle, _) = self.m.latest.borrow_split();
    -        let mut acc = match merkle.get(key, header.acc_root) {
    -            Ok(Some(r)) => Account::deserialize(&r),
    -            Ok(None) => Account::default(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        };
    -        if acc.root.is_null() {
    -            Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DBError::Merkle)?;
    -        }
    -        merkle.insert(sub_key, val, acc.root).map_err(DBError::Merkle)?;
    -        acc.root_hash = merkle.root_hash::<IdTrans>(acc.root).map_err(DBError::Merkle)?;
    -        merkle
    -            .insert(key, acc.serialize(), header.acc_root)
    -            .map_err(DBError::Merkle)?;
    -        Ok(self)
    -    }
    -
    -    /// Create an account.
    -    pub fn create_account(mut self, key: &[u8]) -> Result<Self, DBError> {
    -        let (header, merkle, _) = self.m.latest.borrow_split();
    -        let old_balance = match merkle.get_mut(key, header.acc_root) {
    -            Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance,
    -            Ok(None) => U256::zero(),
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        };
    -        let acc = Account {
    -            balance: old_balance,
    -            ..Default::default()
    -        };
    -        merkle
    -            .insert(key, acc.serialize(), header.acc_root)
    -            .map_err(DBError::Merkle)?;
    -        Ok(self)
    -    }
    -
    -    /// Delete an account.
    -    pub fn delete_account(mut self, key: &[u8], acc: &mut Option<Account>) -> Result<Self, DBError> {
    -        let (header, merkle, blob_stash) = self.m.latest.borrow_split();
    -        let mut a = match merkle.remove(key, header.acc_root) {
    -            Ok(Some(bytes)) => Account::deserialize(&bytes),
    -            Ok(None) => {
    -                *acc = None;
    -                return Ok(self)
    -            }
    -            Err(e) => return Err(DBError::Merkle(e)),
    -        };
    -        if !a.root.is_null() {
    -            merkle.remove_tree(a.root).map_err(DBError::Merkle)?;
    -            a.root = ObjPtr::null();
    -        }
    -        if !a.code.is_null() {
    -            blob_stash.free_blob(a.code).map_err(DBError::Blob)?;
    -            a.code = ObjPtr::null();
    -        }
    -        *acc = Some(a);
    -        Ok(self)
    -    }
    -
    -    /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root
    -    /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits.
    -    pub fn no_root_hash(mut self) -> Self {
    -        self.root_hash_recalc = false;
    -        self
    -    }
    -
    -    /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are
    -    /// either retained on disk or lost together during a crash.
    -    pub fn commit(mut self) {
    -        use crate::storage::BufferWrite;
    -        let inner = &mut *self.m;
    -        if self.root_hash_recalc {
    -            inner.latest.root_hash().ok();
    -            inner.latest.kv_root_hash().ok();
    -        }
    -        // clear the staging layer and apply changes to the CachedSpace
    -        inner.latest.flush_dirty().unwrap();
    -        let (merkle_payload_pages, merkle_payload_plain) = inner.staging.merkle.payload.take_delta();
    -        let (merkle_meta_pages, merkle_meta_plain) = inner.staging.merkle.meta.take_delta();
    -        let (blob_payload_pages, blob_payload_plain) = inner.staging.blob.payload.take_delta();
    -        let (blob_meta_pages, blob_meta_plain) = inner.staging.blob.meta.take_delta();
    -
    -        let old_merkle_meta_delta = inner.cached.merkle.meta.update(&merkle_meta_pages).unwrap();
    -        let old_merkle_payload_delta = inner.cached.merkle.payload.update(&merkle_payload_pages).unwrap();
    -        let old_blob_meta_delta = inner.cached.blob.meta.update(&blob_meta_pages).unwrap();
    -        let old_blob_payload_delta = inner.cached.blob.payload.update(&blob_payload_pages).unwrap();
    -
    -        // update the rolling window of past revisions
    -        let new_base = Universe {
    -            merkle: SubUniverse::new(
    -                StoreRevShared::from_delta(inner.cached.merkle.meta.clone(), old_merkle_meta_delta),
    -                StoreRevShared::from_delta(inner.cached.merkle.payload.clone(), old_merkle_payload_delta),
    -            ),
    -            blob: SubUniverse::new(
    -                StoreRevShared::from_delta(inner.cached.blob.meta.clone(), old_blob_meta_delta),
    -                StoreRevShared::from_delta(inner.cached.blob.payload.clone(), old_blob_payload_delta),
    -            ),
    -        };
    -
    -        if let Some(rev) = inner.revisions.front_mut() {
    -            rev.merkle.meta.set_prev(new_base.merkle.meta.inner().clone());
    -            rev.merkle.payload.set_prev(new_base.merkle.payload.inner().clone());
    -            rev.blob.meta.set_prev(new_base.blob.meta.inner().clone());
    -            rev.blob.payload.set_prev(new_base.blob.payload.inner().clone());
    -        }
    -        inner.revisions.push_front(new_base);
    -        while inner.revisions.len() > inner.max_revisions {
    -            inner.revisions.pop_back();
    -        }
    -
    -        self.committed = true;
    -
    -        // schedule writes to the disk
    -        inner.disk_requester.write(
    -            vec![
    -                BufferWrite {
    -                    space_id: inner.staging.merkle.payload.id(),
    -                    delta: merkle_payload_pages,
    -                },
    -                BufferWrite {
    -                    space_id: inner.staging.merkle.meta.id(),
    -                    delta: merkle_meta_pages,
    -                },
    -                BufferWrite {
    -                    space_id: inner.staging.blob.payload.id(),
    -                    delta: blob_payload_pages,
    -                },
    -                BufferWrite {
    -                    space_id: inner.staging.blob.meta.id(),
    -                    delta: blob_meta_pages,
    -                },
    -            ],
    -            crate::storage::AshRecord(
    -                [
    -                    (MERKLE_META_SPACE, merkle_meta_plain),
    -                    (MERKLE_PAYLOAD_SPACE, merkle_payload_plain),
    -                    (BLOB_META_SPACE, blob_meta_plain),
    -                    (BLOB_PAYLOAD_SPACE, blob_payload_plain),
    -                ]
    -                .into(),
    -            ),
    -        );
    -    }
    -}
    -
    -impl<'a> Drop for WriteBatch<'a> {
    -    fn drop(&mut self) {
    -        if !self.committed {
    -            // drop the staging changes
    -            self.m.staging.merkle.payload.take_delta();
    -            self.m.staging.merkle.meta.take_delta();
    -            self.m.staging.blob.payload.take_delta();
    -            self.m.staging.blob.meta.take_delta();
    -        }
    -    }
    -}
    -
    -
    \ No newline at end of file diff --git a/docs/src/firewood/file.rs.html b/docs/src/firewood/file.rs.html deleted file mode 100644 index d422f5e0c1ca..000000000000 --- a/docs/src/firewood/file.rs.html +++ /dev/null @@ -1,226 +0,0 @@ -file.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -
    // Copied from CedrusDB
    -
    -#![allow(dead_code)]
    -
    -pub(crate) use std::os::unix::io::RawFd as Fd;
    -
    -use nix::errno::Errno;
    -use nix::fcntl::{open, openat, OFlag};
    -use nix::sys::stat::Mode;
    -use nix::unistd::{close, fsync, mkdir};
    -
    -pub struct File {
    -    fd: Fd,
    -    fid: u64,
    -}
    -
    -impl File {
    -    pub fn open_file(rootfd: Fd, fname: &str, truncate: bool) -> nix::Result<Fd> {
    -        openat(
    -            rootfd,
    -            fname,
    -            (if truncate { OFlag::O_TRUNC } else { OFlag::empty() }) | OFlag::O_RDWR,
    -            Mode::S_IRUSR | Mode::S_IWUSR,
    -        )
    -    }
    -
    -    pub fn create_file(rootfd: Fd, fname: &str) -> Fd {
    -        openat(
    -            rootfd,
    -            fname,
    -            OFlag::O_CREAT | OFlag::O_RDWR,
    -            Mode::S_IRUSR | Mode::S_IWUSR,
    -        )
    -        .unwrap()
    -    }
    -
    -    fn _get_fname(fid: u64) -> String {
    -        format!("{fid:08x}.fw")
    -    }
    -
    -    pub fn new(fid: u64, flen: u64, rootfd: Fd) -> nix::Result<Self> {
    -        let fname = Self::_get_fname(fid);
    -        let fd = match Self::open_file(rootfd, &fname, false) {
    -            Ok(fd) => fd,
    -            Err(e) => match e {
    -                Errno::ENOENT => {
    -                    let fd = Self::create_file(rootfd, &fname);
    -                    nix::unistd::ftruncate(fd, flen as nix::libc::off_t)?;
    -                    fd
    -                }
    -                e => return Err(e),
    -            },
    -        };
    -        Ok(File { fd, fid })
    -    }
    -
    -    pub fn get_fd(&self) -> Fd {
    -        self.fd
    -    }
    -    pub fn get_fid(&self) -> u64 {
    -        self.fid
    -    }
    -    pub fn get_fname(&self) -> String {
    -        Self::_get_fname(self.fid)
    -    }
    -
    -    pub fn sync(&self) {
    -        fsync(self.fd).unwrap();
    -    }
    -}
    -
    -impl Drop for File {
    -    fn drop(&mut self) {
    -        close(self.fd).unwrap();
    -    }
    -}
    -
    -pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result<Fd, Errno> {
    -    use nix::sys::stat::mkdirat;
    -    if mkdirat(rootfd, dirname, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR).is_err() {
    -        let errno = nix::errno::from_i32(nix::errno::errno());
    -        if errno != nix::errno::Errno::EEXIST {
    -            return Err(errno)
    -        }
    -    }
    -    openat(rootfd, dirname, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty())
    -}
    -
    -pub fn open_dir(path: &str, truncate: bool) -> Result<(Fd, bool), nix::Error> {
    -    let mut reset_header = truncate;
    -    if truncate {
    -        let _ = std::fs::remove_dir_all(path);
    -    }
    -    match mkdir(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) {
    -        Err(e) => {
    -            if truncate {
    -                return Err(e)
    -            }
    -        }
    -        Ok(_) => {
    -            // the DB did not exist
    -            reset_header = true
    -        }
    -    }
    -    Ok((
    -        match open(path, OFlag::O_DIRECTORY | OFlag::O_PATH, Mode::empty()) {
    -            Ok(fd) => fd,
    -            Err(e) => return Err(e),
    -        },
    -        reset_header,
    -    ))
    -}
    -
    -
    \ No newline at end of file diff --git a/docs/src/firewood/lib.rs.html b/docs/src/firewood/lib.rs.html deleted file mode 100644 index 1a5c4e1fda81..000000000000 --- a/docs/src/firewood/lib.rs.html +++ /dev/null @@ -1,405 +0,0 @@ -lib.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    -180
    -181
    -182
    -183
    -184
    -185
    -186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    -200
    -201
    -202
    -203
    -
    //! # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval.
    -//!
    -//! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes
    -//! access to latest state, by providing extremely fast reads, but also provides a limited view
    -//! into past state. It does not copy-on-write the state trie to generate an ever
    -//! growing forest of tries like other databases, but instead keeps one latest version of the trie index on disk
    -//! and apply in-place updates to it. This ensures that the database size is small and stable
    -//! during the course of running firewood. Firewood was first conceived to provide a very fast
    -//! storage layer for the EVM but could be used on any blockchain that requires authenticated state.
    -//!
    -//! Firewood is a robust database implemented from the ground up to directly store trie nodes and
    -//! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a
    -//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses
    -//! the tree structure as the index on disk. Thus, there is no additional "emulation" of the
    -//! logical trie to flatten out the data structure to feed into the underlying DB that is unaware
    -//! of the data being stored.
    -//!
    -//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees
    -//! atomicity and durability in the database, but also offers "reversibility": some portion
    -//! of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover
    -//! some past versions of the entire store back in memory. While running the store, new changes
    -//! will also contribute to the configured window of changes (at batch granularity) to access any past
    -//! versions with no additional cost at all.
    -//!
    -//! The on-disk footprint of Firewood is more compact than geth. It provides two isolated storage
    -//! space which can be both or selectively used the user. The account model portion of the storage
    -//! offers something very similar to `StateDB` in geth, which captures the address-"state key"
    -//! style of two-level access for an account's (smart contract's) state. Therefore, it takes
    -//! minimal effort to delegate all state storage from an EVM implementation to firewood. The other
    -//! portion of the storage supports generic trie storage for arbitrary keys and values. When unused,
    -//! there is no additional cost.
    -//!
    -//! # Design Philosophy & Overview
    -//!
    -//! With some on-going academic research efforts and increasing demand of faster local storage
    -//! solutions for the chain state, we realized there are mainly two different regimes of designs.
    -//!
    -//! - "Archival" Storage: this style of design emphasizes on the ability to hold all historical
    -//!   data and retrieve a revision of any wold state at a reasonable performance. To economically
    -//!   store all historical certified data, usually copy-on-write merkle tries are used to just
    -//!   capture the changes made by a committed block. The entire storage consists of a forest of these
    -//!   "delta" tries. The total size of the storage will keep growing over the chain length and an ideal,
    -//!   well-executed plan for this is to make sure the performance degradation is reasonable or
    -//!   well-contained with respect to the ever-increasing size of the index. This design is useful
    -//!   for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a
    -//!   query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also
    -//!   need this because the "canonical" branch of the chain could switch (but not necessarily a
    -//!   practical concern nowadays) to a different fork at times.
    -//!
    -//! - "Validation" Storage: this regime optimizes for the storage footprint and the performance of
    -//!   operations upon the latest/recent states. With the assumption that the chain's total state
    -//!   size is relatively stable over ever-coming blocks, one can just make the latest state
    -//!   persisted and available to the blockchain system as that's what matters for most of the time.
    -//!   While one can still keep some volatile state versions in memory for mutation and VM
    -//!   execution, the final commit to some state works on a singleton so the indexed merkle tries
    -//!   may be typically updated in place. It is also possible (e.g., firewood) to allow some
    -//!   infrequent access to historical versions with higher cost, and/or allow fast access to
    -//!   versions of the store within certain limited recency. This style of storage is useful for
    -//!   the blockchain systems where only (or mostly) the latest state is required and data footprint
    -//!   should remain constant or grow slowly if possible for sustainability. Validators who
    -//!   directly participate in the consensus and vote for the blocks, for example, can largely
    -//!   benefit from such a design.
    -//!
    -//! In firewood, we take a closer look at the second regime and have come up with a simple but
    -//! robust architecture that fulfills the need for such blockchain storage.
    -//!
    -//! ## Storage Model
    -//!
    -//! Firewood is built by three layers of abstractions that totally decouple the
    -//! layout/representation of the data on disk from the actual logical data structure it retains:
    -//!
    -//! - Linear, memory-like space: the [shale](https://crates.io/crates/shale) crate from an academic
    -//!   project (CedrusDB) code offers a `MemStore` abstraction for a (64-bit) byte-addressable space
    -//!   that abstracts away the intricate method that actually persists the in-memory data on the
    -//!   secondary storage medium (e.g., hard drive). The implementor of `MemStore` will provide the
    -//!   functions to give the user of `MemStore` an illusion that the user is operating upon a
    -//!   byte-addressable memory space. It is just a "magical" array of bytes one can view and change
    -//!   that is mirrored to the disk. In reality, the linear space will be chunked into files under a
    -//!   directory, but the user does not have to even know about this.
    -//!
    -//! - Persistent item storage stash: `ShaleStore` trait from `shale` defines a pool of typed
    -//!   objects that are persisted on disk but also made accessible in memory transparently. It is
    -//!   built on top of `MemStore` by defining how "items" of the given type are laid out, allocated
    -//!   and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of
    -//!   basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`).
    -//!
    -//! - Data structure: in Firewood, one or more tries are maintained by invoking
    -//!   `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`).
    -//!   The data structure code is totally unaware of how its objects (i.e., nodes) are organized or
    -//!   persisted on disk. It is as if they're just in memory, which makes it much easier to write
    -//!   and maintain the code.
    -//!
    -//! The three layers are depicted as follows:
    -//!
    -//! <p align="center">
    -//!     <img src="https://ava-labs.github.io/firewood/assets/three-layers.svg" width="80%">
    -//! </p>
    -//!
    -//! Given the abstraction, one can easily realize the fact that the actual data that affect the
    -//! state of the data structure (trie) is what the linear space (`MemStore`) keeps track of, that is,
    -//! a flat but conceptually large byte vector. In other words, given a valid byte vector as the
    -//! content of the linear space, the higher level data structure can be *uniquely* determined, there
    -//! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching)
    -//! or less than that, like a way to interpret the bytes. This nice property allows us to completely
    -//! separate the logical data from its physical representation, greatly simplifies the storage
    -//! management, and allows reusing the code. It is still a very versatile abstraction, as in theory
    -//! any persistent data could be stored this way -- sometimes you need to swap in a different
    -//! `MemShale` or `MemStore` implementation, but without having to touch the code for the persisted
    -//! data structure.
    -//!
    -//! ## Page-based Shadowing and Revisions
    -//!
    -//! Following the idea that the tries are just a view of a linear byte space, all writes made to the
    -//! tries inside Firewood will eventually be consolidated into some interval writes to the linear
    -//! space. The writes may overlap and some frequent writes are even done to the same spot in the
    -//! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit
    -//! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the
    -//! dirty pages in some `MemStore` instantiation (see `storage::StoreRevMut`). When a
    -//! [`db::WriteBatch`] commits, both the recorded interval writes and the aggregated in-memory
    -//! dirty pages induced by this write batch are taken out from the linear space. Although they are
    -//! mathematically equivalent, interval writes are more compact than pages (which are 4K in size,
    -//! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL
    -//! subsystem (supported by [growthring](https://crates.io/crates/growth-ring)). After the
    -//! WAL record is written (one record per write batch), the dirty pages are then pushed to the
    -//! on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See
    -//! the `BufferCmd::WriteBatch` part of `DiskBuffer::process` for the detailed logic.
    -//!
    -//! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood:
    -//!
    -//! - Traverse the trie, and that induces the access to some nodes. Suppose the nodes are not already in
    -//!   memory, then:
    -//!
    -//! - Bring the necessary pages that contain the accessed nodes into the memory and cache them
    -//!   (`storage::CachedSpace`).
    -//!
    -//! - Make changes to the trie, and that induces the writes to some nodes. The nodes are either
    -//!   already cached in memory (its pages are cached, or its handle `ObjRef<Node>` is still in
    -//!   `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the
    -//!   second step for it).
    -//!
    -//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` space that
    -//!   overlays atop `CachedSpace`, so all dirty pages during the current write batch will be
    -//!   exactly captured in `StoreRevMut` (see `StoreRevMut::take_delta`).
    -//!
    -//! - Finally:
    -//!
    -//!   - Abort: when the write batch is dropped without invoking `db::WriteBatch::commit`, all in-memory
    -//!     changes will be discarded, the dirty pages from `StoreRevMut` will be dropped and the merkle
    -//!     will "revert" back to its original state without actually having to rollback anything.
    -//!
    -//!   - Commit: otherwise, the write batch is committed, the interval writes (`storage::Ash`) will be bundled
    -//!     into a single WAL record (`storage::AshRecord`) and sent to WAL subsystem, before dirty pages
    -//!     are scheduled to be written to the space files. Also the dirty pages are applied to the
    -//!     underlying `CachedSpace`. `StoreRevMut` becomes empty again for further write batches.
    -//!
    -//! Parts of the following diagram show this normal flow, the "staging" space (implemented by
    -//! `StoreRevMut`) concept is a bit similar to the staging area in Git, which enables the handling
    -//! of (resuming from) write errors, clean abortion of an on-going write batch so the entire store
    -//! state remains intact, and also reduces unnecessary premature disk writes. Essentially, we
    -//! copy-on-write pages in the space that are touched upon, without directly mutating the
    -//! underlying "master" space. The staging space is just a collection of these "shadowing" pages
    -//! and a reference to the its base (master) so any reads could partially hit those dirty pages
    -//! and/or fall through to the base, whereas all writes are captured. Finally, when things go well,
    -//! we "push down" these changes to the base and clear up the staging space.
    -//!
    -//! <p align="center">
    -//!     <img src="https://ava-labs.github.io/firewood/assets/architecture.svg" width="100%">
    -//! </p>
    -//!
    -//! Thanks to the shadow pages, we can both revive some historical versions of the store and
    -//! maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram
    -//! shows previously logged write batch records could be kept even though they are no longer needed
    -//! for the purpose of crash recovery. The interval writes from a record can be aggregated into
    -//! pages (see `storage::StoreDelta::new`) and used to reconstruct a "ghost" image of past
    -//! revision of the linear space (just like how staging space works, except that the ghost space is
    -//! essentially read-only once constructed). The shadow pages there will function as some
    -//! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the
    -//! linear space is very likely untouched by that historical write batch.
    -//!
    -//! Then, with the three-layer abstraction we previously talked about, a historical trie could be
    -//! derived. In fact, because there is no mandatory traversal or scanning in the process, the
    -//! only cost to revive a historical state from the log is to just playback the records and create
    -//! those shadow pages. There is very little additional cost because the ghost space is summoned on an
    -//! on-demand manner while one accesses the historical trie.
    -//!
    -//! In the other direction, when new write batches are committed, the system moves forward, we can
    -//! therefore maintain a rolling window of past revisions in memory with *zero* cost. The
    -//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one
    -//! step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be
    -//! created to hold the version of the store before the commit. The backward delta is applied to
    -//! counteract the change that has been made to the persisted store, which is also a set of shadow pages.
    -//! No change is required for other historical ghost space instances. Finally, we can phase out
    -//! some very old ghost space to keep the size of the rolling window invariant.
    -//!
    -pub(crate) mod account;
    -pub mod db;
    -pub(crate) mod file;
    -pub mod merkle;
    -pub mod proof;
    -pub(crate) mod storage;
    -
    -
    diff --git a/docs/src/firewood/merkle.rs.html b/docs/src/firewood/merkle.rs.html deleted file mode 100644 index 28720361621d..000000000000 --- a/docs/src/firewood/merkle.rs.html +++ /dev/null @@ -1,3424 +0,0 @@ -merkle.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    -180
    -181
    -182
    -183
    -184
    -185
    -186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    -200
    -201
    -202
    -203
    -204
    -205
    -206
    -207
    -208
    -209
    -210
    -211
    -212
    -213
    -214
    -215
    -216
    -217
    -218
    -219
    -220
    -221
    -222
    -223
    -224
    -225
    -226
    -227
    -228
    -229
    -230
    -231
    -232
    -233
    -234
    -235
    -236
    -237
    -238
    -239
    -240
    -241
    -242
    -243
    -244
    -245
    -246
    -247
    -248
    -249
    -250
    -251
    -252
    -253
    -254
    -255
    -256
    -257
    -258
    -259
    -260
    -261
    -262
    -263
    -264
    -265
    -266
    -267
    -268
    -269
    -270
    -271
    -272
    -273
    -274
    -275
    -276
    -277
    -278
    -279
    -280
    -281
    -282
    -283
    -284
    -285
    -286
    -287
    -288
    -289
    -290
    -291
    -292
    -293
    -294
    -295
    -296
    -297
    -298
    -299
    -300
    -301
    -302
    -303
    -304
    -305
    -306
    -307
    -308
    -309
    -310
    -311
    -312
    -313
    -314
    -315
    -316
    -317
    -318
    -319
    -320
    -321
    -322
    -323
    -324
    -325
    -326
    -327
    -328
    -329
    -330
    -331
    -332
    -333
    -334
    -335
    -336
    -337
    -338
    -339
    -340
    -341
    -342
    -343
    -344
    -345
    -346
    -347
    -348
    -349
    -350
    -351
    -352
    -353
    -354
    -355
    -356
    -357
    -358
    -359
    -360
    -361
    -362
    -363
    -364
    -365
    -366
    -367
    -368
    -369
    -370
    -371
    -372
    -373
    -374
    -375
    -376
    -377
    -378
    -379
    -380
    -381
    -382
    -383
    -384
    -385
    -386
    -387
    -388
    -389
    -390
    -391
    -392
    -393
    -394
    -395
    -396
    -397
    -398
    -399
    -400
    -401
    -402
    -403
    -404
    -405
    -406
    -407
    -408
    -409
    -410
    -411
    -412
    -413
    -414
    -415
    -416
    -417
    -418
    -419
    -420
    -421
    -422
    -423
    -424
    -425
    -426
    -427
    -428
    -429
    -430
    -431
    -432
    -433
    -434
    -435
    -436
    -437
    -438
    -439
    -440
    -441
    -442
    -443
    -444
    -445
    -446
    -447
    -448
    -449
    -450
    -451
    -452
    -453
    -454
    -455
    -456
    -457
    -458
    -459
    -460
    -461
    -462
    -463
    -464
    -465
    -466
    -467
    -468
    -469
    -470
    -471
    -472
    -473
    -474
    -475
    -476
    -477
    -478
    -479
    -480
    -481
    -482
    -483
    -484
    -485
    -486
    -487
    -488
    -489
    -490
    -491
    -492
    -493
    -494
    -495
    -496
    -497
    -498
    -499
    -500
    -501
    -502
    -503
    -504
    -505
    -506
    -507
    -508
    -509
    -510
    -511
    -512
    -513
    -514
    -515
    -516
    -517
    -518
    -519
    -520
    -521
    -522
    -523
    -524
    -525
    -526
    -527
    -528
    -529
    -530
    -531
    -532
    -533
    -534
    -535
    -536
    -537
    -538
    -539
    -540
    -541
    -542
    -543
    -544
    -545
    -546
    -547
    -548
    -549
    -550
    -551
    -552
    -553
    -554
    -555
    -556
    -557
    -558
    -559
    -560
    -561
    -562
    -563
    -564
    -565
    -566
    -567
    -568
    -569
    -570
    -571
    -572
    -573
    -574
    -575
    -576
    -577
    -578
    -579
    -580
    -581
    -582
    -583
    -584
    -585
    -586
    -587
    -588
    -589
    -590
    -591
    -592
    -593
    -594
    -595
    -596
    -597
    -598
    -599
    -600
    -601
    -602
    -603
    -604
    -605
    -606
    -607
    -608
    -609
    -610
    -611
    -612
    -613
    -614
    -615
    -616
    -617
    -618
    -619
    -620
    -621
    -622
    -623
    -624
    -625
    -626
    -627
    -628
    -629
    -630
    -631
    -632
    -633
    -634
    -635
    -636
    -637
    -638
    -639
    -640
    -641
    -642
    -643
    -644
    -645
    -646
    -647
    -648
    -649
    -650
    -651
    -652
    -653
    -654
    -655
    -656
    -657
    -658
    -659
    -660
    -661
    -662
    -663
    -664
    -665
    -666
    -667
    -668
    -669
    -670
    -671
    -672
    -673
    -674
    -675
    -676
    -677
    -678
    -679
    -680
    -681
    -682
    -683
    -684
    -685
    -686
    -687
    -688
    -689
    -690
    -691
    -692
    -693
    -694
    -695
    -696
    -697
    -698
    -699
    -700
    -701
    -702
    -703
    -704
    -705
    -706
    -707
    -708
    -709
    -710
    -711
    -712
    -713
    -714
    -715
    -716
    -717
    -718
    -719
    -720
    -721
    -722
    -723
    -724
    -725
    -726
    -727
    -728
    -729
    -730
    -731
    -732
    -733
    -734
    -735
    -736
    -737
    -738
    -739
    -740
    -741
    -742
    -743
    -744
    -745
    -746
    -747
    -748
    -749
    -750
    -751
    -752
    -753
    -754
    -755
    -756
    -757
    -758
    -759
    -760
    -761
    -762
    -763
    -764
    -765
    -766
    -767
    -768
    -769
    -770
    -771
    -772
    -773
    -774
    -775
    -776
    -777
    -778
    -779
    -780
    -781
    -782
    -783
    -784
    -785
    -786
    -787
    -788
    -789
    -790
    -791
    -792
    -793
    -794
    -795
    -796
    -797
    -798
    -799
    -800
    -801
    -802
    -803
    -804
    -805
    -806
    -807
    -808
    -809
    -810
    -811
    -812
    -813
    -814
    -815
    -816
    -817
    -818
    -819
    -820
    -821
    -822
    -823
    -824
    -825
    -826
    -827
    -828
    -829
    -830
    -831
    -832
    -833
    -834
    -835
    -836
    -837
    -838
    -839
    -840
    -841
    -842
    -843
    -844
    -845
    -846
    -847
    -848
    -849
    -850
    -851
    -852
    -853
    -854
    -855
    -856
    -857
    -858
    -859
    -860
    -861
    -862
    -863
    -864
    -865
    -866
    -867
    -868
    -869
    -870
    -871
    -872
    -873
    -874
    -875
    -876
    -877
    -878
    -879
    -880
    -881
    -882
    -883
    -884
    -885
    -886
    -887
    -888
    -889
    -890
    -891
    -892
    -893
    -894
    -895
    -896
    -897
    -898
    -899
    -900
    -901
    -902
    -903
    -904
    -905
    -906
    -907
    -908
    -909
    -910
    -911
    -912
    -913
    -914
    -915
    -916
    -917
    -918
    -919
    -920
    -921
    -922
    -923
    -924
    -925
    -926
    -927
    -928
    -929
    -930
    -931
    -932
    -933
    -934
    -935
    -936
    -937
    -938
    -939
    -940
    -941
    -942
    -943
    -944
    -945
    -946
    -947
    -948
    -949
    -950
    -951
    -952
    -953
    -954
    -955
    -956
    -957
    -958
    -959
    -960
    -961
    -962
    -963
    -964
    -965
    -966
    -967
    -968
    -969
    -970
    -971
    -972
    -973
    -974
    -975
    -976
    -977
    -978
    -979
    -980
    -981
    -982
    -983
    -984
    -985
    -986
    -987
    -988
    -989
    -990
    -991
    -992
    -993
    -994
    -995
    -996
    -997
    -998
    -999
    -1000
    -1001
    -1002
    -1003
    -1004
    -1005
    -1006
    -1007
    -1008
    -1009
    -1010
    -1011
    -1012
    -1013
    -1014
    -1015
    -1016
    -1017
    -1018
    -1019
    -1020
    -1021
    -1022
    -1023
    -1024
    -1025
    -1026
    -1027
    -1028
    -1029
    -1030
    -1031
    -1032
    -1033
    -1034
    -1035
    -1036
    -1037
    -1038
    -1039
    -1040
    -1041
    -1042
    -1043
    -1044
    -1045
    -1046
    -1047
    -1048
    -1049
    -1050
    -1051
    -1052
    -1053
    -1054
    -1055
    -1056
    -1057
    -1058
    -1059
    -1060
    -1061
    -1062
    -1063
    -1064
    -1065
    -1066
    -1067
    -1068
    -1069
    -1070
    -1071
    -1072
    -1073
    -1074
    -1075
    -1076
    -1077
    -1078
    -1079
    -1080
    -1081
    -1082
    -1083
    -1084
    -1085
    -1086
    -1087
    -1088
    -1089
    -1090
    -1091
    -1092
    -1093
    -1094
    -1095
    -1096
    -1097
    -1098
    -1099
    -1100
    -1101
    -1102
    -1103
    -1104
    -1105
    -1106
    -1107
    -1108
    -1109
    -1110
    -1111
    -1112
    -1113
    -1114
    -1115
    -1116
    -1117
    -1118
    -1119
    -1120
    -1121
    -1122
    -1123
    -1124
    -1125
    -1126
    -1127
    -1128
    -1129
    -1130
    -1131
    -1132
    -1133
    -1134
    -1135
    -1136
    -1137
    -1138
    -1139
    -1140
    -1141
    -1142
    -1143
    -1144
    -1145
    -1146
    -1147
    -1148
    -1149
    -1150
    -1151
    -1152
    -1153
    -1154
    -1155
    -1156
    -1157
    -1158
    -1159
    -1160
    -1161
    -1162
    -1163
    -1164
    -1165
    -1166
    -1167
    -1168
    -1169
    -1170
    -1171
    -1172
    -1173
    -1174
    -1175
    -1176
    -1177
    -1178
    -1179
    -1180
    -1181
    -1182
    -1183
    -1184
    -1185
    -1186
    -1187
    -1188
    -1189
    -1190
    -1191
    -1192
    -1193
    -1194
    -1195
    -1196
    -1197
    -1198
    -1199
    -1200
    -1201
    -1202
    -1203
    -1204
    -1205
    -1206
    -1207
    -1208
    -1209
    -1210
    -1211
    -1212
    -1213
    -1214
    -1215
    -1216
    -1217
    -1218
    -1219
    -1220
    -1221
    -1222
    -1223
    -1224
    -1225
    -1226
    -1227
    -1228
    -1229
    -1230
    -1231
    -1232
    -1233
    -1234
    -1235
    -1236
    -1237
    -1238
    -1239
    -1240
    -1241
    -1242
    -1243
    -1244
    -1245
    -1246
    -1247
    -1248
    -1249
    -1250
    -1251
    -1252
    -1253
    -1254
    -1255
    -1256
    -1257
    -1258
    -1259
    -1260
    -1261
    -1262
    -1263
    -1264
    -1265
    -1266
    -1267
    -1268
    -1269
    -1270
    -1271
    -1272
    -1273
    -1274
    -1275
    -1276
    -1277
    -1278
    -1279
    -1280
    -1281
    -1282
    -1283
    -1284
    -1285
    -1286
    -1287
    -1288
    -1289
    -1290
    -1291
    -1292
    -1293
    -1294
    -1295
    -1296
    -1297
    -1298
    -1299
    -1300
    -1301
    -1302
    -1303
    -1304
    -1305
    -1306
    -1307
    -1308
    -1309
    -1310
    -1311
    -1312
    -1313
    -1314
    -1315
    -1316
    -1317
    -1318
    -1319
    -1320
    -1321
    -1322
    -1323
    -1324
    -1325
    -1326
    -1327
    -1328
    -1329
    -1330
    -1331
    -1332
    -1333
    -1334
    -1335
    -1336
    -1337
    -1338
    -1339
    -1340
    -1341
    -1342
    -1343
    -1344
    -1345
    -1346
    -1347
    -1348
    -1349
    -1350
    -1351
    -1352
    -1353
    -1354
    -1355
    -1356
    -1357
    -1358
    -1359
    -1360
    -1361
    -1362
    -1363
    -1364
    -1365
    -1366
    -1367
    -1368
    -1369
    -1370
    -1371
    -1372
    -1373
    -1374
    -1375
    -1376
    -1377
    -1378
    -1379
    -1380
    -1381
    -1382
    -1383
    -1384
    -1385
    -1386
    -1387
    -1388
    -1389
    -1390
    -1391
    -1392
    -1393
    -1394
    -1395
    -1396
    -1397
    -1398
    -1399
    -1400
    -1401
    -1402
    -1403
    -1404
    -1405
    -1406
    -1407
    -1408
    -1409
    -1410
    -1411
    -1412
    -1413
    -1414
    -1415
    -1416
    -1417
    -1418
    -1419
    -1420
    -1421
    -1422
    -1423
    -1424
    -1425
    -1426
    -1427
    -1428
    -1429
    -1430
    -1431
    -1432
    -1433
    -1434
    -1435
    -1436
    -1437
    -1438
    -1439
    -1440
    -1441
    -1442
    -1443
    -1444
    -1445
    -1446
    -1447
    -1448
    -1449
    -1450
    -1451
    -1452
    -1453
    -1454
    -1455
    -1456
    -1457
    -1458
    -1459
    -1460
    -1461
    -1462
    -1463
    -1464
    -1465
    -1466
    -1467
    -1468
    -1469
    -1470
    -1471
    -1472
    -1473
    -1474
    -1475
    -1476
    -1477
    -1478
    -1479
    -1480
    -1481
    -1482
    -1483
    -1484
    -1485
    -1486
    -1487
    -1488
    -1489
    -1490
    -1491
    -1492
    -1493
    -1494
    -1495
    -1496
    -1497
    -1498
    -1499
    -1500
    -1501
    -1502
    -1503
    -1504
    -1505
    -1506
    -1507
    -1508
    -1509
    -1510
    -1511
    -1512
    -1513
    -1514
    -1515
    -1516
    -1517
    -1518
    -1519
    -1520
    -1521
    -1522
    -1523
    -1524
    -1525
    -1526
    -1527
    -1528
    -1529
    -1530
    -1531
    -1532
    -1533
    -1534
    -1535
    -1536
    -1537
    -1538
    -1539
    -1540
    -1541
    -1542
    -1543
    -1544
    -1545
    -1546
    -1547
    -1548
    -1549
    -1550
    -1551
    -1552
    -1553
    -1554
    -1555
    -1556
    -1557
    -1558
    -1559
    -1560
    -1561
    -1562
    -1563
    -1564
    -1565
    -1566
    -1567
    -1568
    -1569
    -1570
    -1571
    -1572
    -1573
    -1574
    -1575
    -1576
    -1577
    -1578
    -1579
    -1580
    -1581
    -1582
    -1583
    -1584
    -1585
    -1586
    -1587
    -1588
    -1589
    -1590
    -1591
    -1592
    -1593
    -1594
    -1595
    -1596
    -1597
    -1598
    -1599
    -1600
    -1601
    -1602
    -1603
    -1604
    -1605
    -1606
    -1607
    -1608
    -1609
    -1610
    -1611
    -1612
    -1613
    -1614
    -1615
    -1616
    -1617
    -1618
    -1619
    -1620
    -1621
    -1622
    -1623
    -1624
    -1625
    -1626
    -1627
    -1628
    -1629
    -1630
    -1631
    -1632
    -1633
    -1634
    -1635
    -1636
    -1637
    -1638
    -1639
    -1640
    -1641
    -1642
    -1643
    -1644
    -1645
    -1646
    -1647
    -1648
    -1649
    -1650
    -1651
    -1652
    -1653
    -1654
    -1655
    -1656
    -1657
    -1658
    -1659
    -1660
    -1661
    -1662
    -1663
    -1664
    -1665
    -1666
    -1667
    -1668
    -1669
    -1670
    -1671
    -1672
    -1673
    -1674
    -1675
    -1676
    -1677
    -1678
    -1679
    -1680
    -1681
    -1682
    -1683
    -1684
    -1685
    -1686
    -1687
    -1688
    -1689
    -1690
    -1691
    -1692
    -1693
    -1694
    -1695
    -1696
    -1697
    -1698
    -1699
    -1700
    -1701
    -1702
    -1703
    -1704
    -1705
    -1706
    -1707
    -1708
    -1709
    -1710
    -1711
    -
    use crate::proof::Proof;
    -
    -use enum_as_inner::EnumAsInner;
    -use once_cell::unsync::OnceCell;
    -use sha3::Digest;
    -use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore};
    -
    -use std::cell::Cell;
    -use std::collections::HashMap;
    -use std::fmt::{self, Debug};
    -use std::io::{Cursor, Read, Write};
    -
    -const NBRANCH: usize = 16;
    -
    -#[derive(Debug)]
    -pub enum MerkleError {
    -    Shale(ShaleError),
    -    ReadOnly,
    -    NotBranchNode,
    -    Format(std::io::Error),
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -pub struct Hash(pub [u8; 32]);
    -
    -impl Hash {
    -    const MSIZE: u64 = 32;
    -}
    -
    -impl std::ops::Deref for Hash {
    -    type Target = [u8; 32];
    -    fn deref(&self) -> &[u8; 32] {
    -        &self.0
    -    }
    -}
    -
    -impl MummyItem for Hash {
    -    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
    -        let raw = mem.get_view(addr, Self::MSIZE).ok_or(ShaleError::LinearMemStoreError)?;
    -        Ok(Self(raw[..Self::MSIZE as usize].try_into().unwrap()))
    -    }
    -
    -    fn dehydrated_len(&self) -> u64 {
    -        Self::MSIZE
    -    }
    -
    -    fn dehydrate(&self, to: &mut [u8]) {
    -        Cursor::new(to).write_all(&self.0).unwrap()
    -    }
    -}
    -
    -/// PartialPath keeps a list of nibbles to represent a path on the MPT.
    -#[derive(PartialEq, Eq, Clone)]
    -pub struct PartialPath(Vec<u8>);
    -
    -impl Debug for PartialPath {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        for nib in self.0.iter() {
    -            write!(f, "{:x}", *nib & 0xf)?;
    -        }
    -        Ok(())
    -    }
    -}
    -
    -impl std::ops::Deref for PartialPath {
    -    type Target = [u8];
    -    fn deref(&self) -> &[u8] {
    -        &self.0
    -    }
    -}
    -
    -impl PartialPath {
    -    pub fn into_inner(self) -> Vec<u8> {
    -        self.0
    -    }
    -
    -    fn encode(&self, term: bool) -> Vec<u8> {
    -        let odd_len = (self.0.len() & 1) as u8;
    -        let flags = if term { 2 } else { 0 } + odd_len;
    -        let mut res = if odd_len == 1 { vec![flags] } else { vec![flags, 0x0] };
    -        res.extend(&self.0);
    -        res
    -    }
    -
    -    pub fn decode<R: AsRef<[u8]>>(raw: R) -> (Self, bool) {
    -        let raw = raw.as_ref();
    -        let term = raw[0] > 1;
    -        let odd_len = raw[0] & 1;
    -        (
    -            Self(if odd_len == 1 {
    -                raw[1..].to_vec()
    -            } else {
    -                raw[2..].to_vec()
    -            }),
    -            term,
    -        )
    -    }
    -
    -    fn dehydrated_len(&self) -> u64 {
    -        let len = self.0.len() as u64;
    -        if len & 1 == 1 {
    -            (len + 1) >> 1
    -        } else {
    -            (len >> 1) + 1
    -        }
    -    }
    -}
    -
    -#[test]
    -fn test_partial_path_encoding() {
    -    let check = |steps: &[u8], term| {
    -        let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term));
    -        assert_eq!(d.0, steps);
    -        assert_eq!(t, term);
    -    };
    -    for steps in [
    -        vec![0x1, 0x2, 0x3, 0x4],
    -        vec![0x1, 0x2, 0x3],
    -        vec![0x0, 0x1, 0x2],
    -        vec![0x1, 0x2],
    -        vec![0x1],
    -    ] {
    -        for term in [true, false] {
    -            check(&steps, term)
    -        }
    -    }
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -struct Data(Vec<u8>);
    -
    -impl std::ops::Deref for Data {
    -    type Target = [u8];
    -    fn deref(&self) -> &[u8] {
    -        &self.0
    -    }
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -struct BranchNode {
    -    chd: [Option<ObjPtr<Node>>; NBRANCH],
    -    value: Option<Data>,
    -}
    -
    -impl Debug for BranchNode {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        write!(f, "[Branch")?;
    -        for (i, c) in self.chd.iter().enumerate() {
    -            if let Some(c) = c {
    -                write!(f, " ({i:x} {c})")?;
    -            }
    -        }
    -        write!(
    -            f,
    -            " v={}]",
    -            match &self.value {
    -                Some(v) => hex::encode(&**v),
    -                None => "nil".to_string(),
    -            }
    -        )
    -    }
    -}
    -
    -impl BranchNode {
    -    fn single_child(&self) -> (Option<(ObjPtr<Node>, u8)>, bool) {
    -        let mut has_chd = false;
    -        let mut only_chd = None;
    -        for (i, c) in self.chd.iter().enumerate() {
    -            if c.is_some() {
    -                has_chd = true;
    -                if only_chd.is_some() {
    -                    only_chd = None;
    -                    break
    -                }
    -                only_chd = (*c).map(|e| (e, i as u8))
    -            }
    -        }
    -        (only_chd, has_chd)
    -    }
    -
    -    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
    -        let mut stream = rlp::RlpStream::new_list(17);
    -        for c in self.chd.iter() {
    -            match c {
    -                Some(c) => {
    -                    let mut c_ref = store.get_item(*c).unwrap();
    -                    if c_ref.get_eth_rlp_long::<T>(store) {
    -                        let s = stream.append(&&(*c_ref.get_root_hash::<T>(store))[..]);
    -                        if c_ref.lazy_dirty.get() {
    -                            c_ref.write(|_| {}).unwrap();
    -                            c_ref.lazy_dirty.set(false)
    -                        }
    -                        s
    -                    } else {
    -                        let c_rlp = &c_ref.get_eth_rlp::<T>(store);
    -                        stream.append_raw(c_rlp, 1)
    -                    }
    -                }
    -                None => stream.append_empty_data(),
    -            };
    -        }
    -        match &self.value {
    -            Some(val) => stream.append(&val.to_vec()),
    -            None => stream.append_empty_data(),
    -        };
    -        stream.out().into()
    -    }
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -struct LeafNode(PartialPath, Data);
    -
    -impl Debug for LeafNode {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1))
    -    }
    -}
    -
    -impl LeafNode {
    -    fn calc_eth_rlp<T: ValueTransformer>(&self) -> Vec<u8> {
    -        rlp::encode_list::<Vec<u8>, _>(&[from_nibbles(&self.0.encode(true)).collect(), T::transform(&self.1)]).into()
    -    }
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -struct ExtNode(PartialPath, ObjPtr<Node>);
    -
    -impl Debug for ExtNode {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        write!(f, "[Extension {:?} {}]", self.0, self.1)
    -    }
    -}
    -
    -impl ExtNode {
    -    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
    -        let mut r = store.get_item(self.1).unwrap();
    -        let mut stream = rlp::RlpStream::new_list(2);
    -        stream.append(&from_nibbles(&self.0.encode(false)).collect::<Vec<_>>());
    -        if r.get_eth_rlp_long::<T>(store) {
    -            stream.append(&&(*r.get_root_hash::<T>(store))[..]);
    -            if r.lazy_dirty.get() {
    -                r.write(|_| {}).unwrap();
    -                r.lazy_dirty.set(false)
    -            }
    -        } else {
    -            stream.append_raw(r.get_eth_rlp::<T>(store), 1);
    -        }
    -        stream.out().into()
    -    }
    -}
    -
    -#[derive(PartialEq, Eq, Clone)]
    -pub struct Node {
    -    root_hash: OnceCell<Hash>,
    -    eth_rlp_long: OnceCell<bool>,
    -    eth_rlp: OnceCell<Vec<u8>>,
    -    lazy_dirty: Cell<bool>,
    -    inner: NodeType,
    -}
    -
    -#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)]
    -enum NodeType {
    -    Branch(BranchNode),
    -    Leaf(LeafNode),
    -    Extension(ExtNode),
    -}
    -
    -impl NodeType {
    -    fn calc_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> Vec<u8> {
    -        match &self {
    -            NodeType::Leaf(n) => n.calc_eth_rlp::<T>(),
    -            NodeType::Extension(n) => n.calc_eth_rlp::<T>(store),
    -            NodeType::Branch(n) => n.calc_eth_rlp::<T>(store),
    -        }
    -    }
    -}
    -
    -impl Node {
    -    const BRANCH_NODE: u8 = 0x0;
    -    const EXT_NODE: u8 = 0x1;
    -    const LEAF_NODE: u8 = 0x2;
    -
    -    fn max_branch_node_size() -> u64 {
    -        let max_size: OnceCell<u64> = OnceCell::new();
    -        *max_size.get_or_init(|| {
    -            Self {
    -                root_hash: OnceCell::new(),
    -                eth_rlp_long: OnceCell::new(),
    -                eth_rlp: OnceCell::new(),
    -                inner: NodeType::Branch(BranchNode {
    -                    chd: [Some(ObjPtr::null()); NBRANCH],
    -                    value: Some(Data(Vec::new())),
    -                }),
    -                lazy_dirty: Cell::new(false),
    -            }
    -            .dehydrated_len()
    -        })
    -    }
    -
    -    fn get_eth_rlp<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> &[u8] {
    -        self.eth_rlp.get_or_init(|| self.inner.calc_eth_rlp::<T>(store))
    -    }
    -
    -    fn get_root_hash<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> &Hash {
    -        self.root_hash.get_or_init(|| {
    -            self.lazy_dirty.set(true);
    -            Hash(sha3::Keccak256::digest(self.get_eth_rlp::<T>(store)).into())
    -        })
    -    }
    -
    -    fn get_eth_rlp_long<T: ValueTransformer>(&self, store: &dyn ShaleStore<Node>) -> bool {
    -        *self.eth_rlp_long.get_or_init(|| {
    -            self.lazy_dirty.set(true);
    -            self.get_eth_rlp::<T>(store).len() >= 32
    -        })
    -    }
    -
    -    fn rehash(&mut self) {
    -        self.eth_rlp = OnceCell::new();
    -        self.eth_rlp_long = OnceCell::new();
    -        self.root_hash = OnceCell::new();
    -    }
    -
    -    fn new(inner: NodeType) -> Self {
    -        let mut s = Self {
    -            root_hash: OnceCell::new(),
    -            eth_rlp_long: OnceCell::new(),
    -            eth_rlp: OnceCell::new(),
    -            inner,
    -            lazy_dirty: Cell::new(false),
    -        };
    -        s.rehash();
    -        s
    -    }
    -
    -    fn new_from_hash(root_hash: Option<Hash>, eth_rlp_long: Option<bool>, inner: NodeType) -> Self {
    -        Self {
    -            root_hash: match root_hash {
    -                Some(h) => OnceCell::with_value(h),
    -                None => OnceCell::new(),
    -            },
    -            eth_rlp_long: match eth_rlp_long {
    -                Some(b) => OnceCell::with_value(b),
    -                None => OnceCell::new(),
    -            },
    -            eth_rlp: OnceCell::new(),
    -            inner,
    -            lazy_dirty: Cell::new(false),
    -        }
    -    }
    -
    -    const ROOT_HASH_VALID_BIT: u8 = 1 << 0;
    -    const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1;
    -    const ETH_RLP_LONG_BIT: u8 = 1 << 2;
    -}
    -
    -impl MummyItem for Node {
    -    fn hydrate(addr: u64, mem: &dyn MemStore) -> Result<Self, ShaleError> {
    -        let dec_err = |_| ShaleError::DecodeError;
    -        const META_SIZE: u64 = 32 + 1 + 1;
    -        let meta_raw = mem.get_view(addr, META_SIZE).ok_or(ShaleError::LinearMemStoreError)?;
    -        let attrs = meta_raw[32];
    -        let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 {
    -            None
    -        } else {
    -            Some(Hash(meta_raw[0..32].try_into().map_err(dec_err)?))
    -        };
    -        let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 {
    -            None
    -        } else {
    -            Some(attrs & Node::ETH_RLP_LONG_BIT != 0)
    -        };
    -        match meta_raw[33] {
    -            Self::BRANCH_NODE => {
    -                let branch_header_size = NBRANCH as u64 * 8 + 4;
    -                let node_raw = mem
    -                    .get_view(addr + META_SIZE, branch_header_size)
    -                    .ok_or(ShaleError::LinearMemStoreError)?;
    -                let mut cur = Cursor::new(node_raw.deref());
    -                let mut chd = [None; NBRANCH];
    -                let mut buff = [0; 8];
    -                for chd in chd.iter_mut() {
    -                    cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
    -                    let addr = u64::from_le_bytes(buff);
    -                    if addr != 0 {
    -                        *chd = Some(unsafe { ObjPtr::new_from_addr(addr) })
    -                    }
    -                }
    -                cur.read_exact(&mut buff[..4]).map_err(|_| ShaleError::DecodeError)?;
    -                let raw_len = u32::from_le_bytes(buff[..4].try_into().map_err(dec_err)?) as u64;
    -                let value = if raw_len == u32::MAX as u64 {
    -                    None
    -                } else {
    -                    Some(Data(
    -                        mem.get_view(addr + META_SIZE + branch_header_size, raw_len)
    -                            .ok_or(ShaleError::LinearMemStoreError)?
    -                            .to_vec(),
    -                    ))
    -                };
    -                Ok(Self::new_from_hash(
    -                    root_hash,
    -                    eth_rlp_long,
    -                    NodeType::Branch(BranchNode { chd, value }),
    -                ))
    -            }
    -            Self::EXT_NODE => {
    -                let ext_header_size = 1 + 8;
    -                let node_raw = mem
    -                    .get_view(addr + META_SIZE, ext_header_size)
    -                    .ok_or(ShaleError::LinearMemStoreError)?;
    -                let mut cur = Cursor::new(node_raw.deref());
    -                let mut buff = [0; 8];
    -                cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?;
    -                let len = buff[0] as u64;
    -                cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
    -                let ptr = u64::from_le_bytes(buff);
    -                let nibbles: Vec<_> = to_nibbles(
    -                    &mem.get_view(addr + META_SIZE + ext_header_size, len)
    -                        .ok_or(ShaleError::LinearMemStoreError)?,
    -                )
    -                .collect();
    -                let (path, _) = PartialPath::decode(nibbles);
    -                Ok(Self::new_from_hash(
    -                    root_hash,
    -                    eth_rlp_long,
    -                    NodeType::Extension(ExtNode(path, unsafe { ObjPtr::new_from_addr(ptr) })),
    -                ))
    -            }
    -            Self::LEAF_NODE => {
    -                let leaf_header_size = 1 + 4;
    -                let node_raw = mem
    -                    .get_view(addr + META_SIZE, leaf_header_size)
    -                    .ok_or(ShaleError::LinearMemStoreError)?;
    -                let mut cur = Cursor::new(node_raw.deref());
    -                let mut buff = [0; 4];
    -                cur.read_exact(&mut buff[..1]).map_err(|_| ShaleError::DecodeError)?;
    -                let path_len = buff[0] as u64;
    -                cur.read_exact(&mut buff).map_err(|_| ShaleError::DecodeError)?;
    -                let data_len = u32::from_le_bytes(buff) as u64;
    -                let remainder = mem
    -                    .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len)
    -                    .ok_or(ShaleError::LinearMemStoreError)?;
    -                let nibbles: Vec<_> = to_nibbles(&remainder[..path_len as usize]).collect();
    -                let (path, _) = PartialPath::decode(nibbles);
    -                let value = Data(remainder[path_len as usize..].to_vec());
    -                Ok(Self::new_from_hash(
    -                    root_hash,
    -                    eth_rlp_long,
    -                    NodeType::Leaf(LeafNode(path, value)),
    -                ))
    -            }
    -            _ => Err(ShaleError::DecodeError),
    -        }
    -    }
    -
    -    fn dehydrated_len(&self) -> u64 {
    -        32 + 1 +
    -            1 +
    -            match &self.inner {
    -                NodeType::Branch(n) => {
    -                    NBRANCH as u64 * 8 +
    -                        4 +
    -                        match &n.value {
    -                            Some(val) => val.len() as u64,
    -                            None => 0,
    -                        }
    -                }
    -                NodeType::Extension(n) => 1 + 8 + n.0.dehydrated_len(),
    -                NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64,
    -            }
    -    }
    -
    -    fn dehydrate(&self, to: &mut [u8]) {
    -        let mut cur = Cursor::new(to);
    -
    -        let mut attrs = 0;
    -        attrs |= match self.root_hash.get() {
    -            Some(h) => {
    -                cur.write_all(&h.0).unwrap();
    -                Node::ROOT_HASH_VALID_BIT
    -            }
    -            None => {
    -                cur.write_all(&[0; 32]).unwrap();
    -                0
    -            }
    -        };
    -        attrs |= match self.eth_rlp_long.get() {
    -            Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT),
    -            None => 0,
    -        };
    -        cur.write_all(&[attrs]).unwrap();
    -
    -        match &self.inner {
    -            NodeType::Branch(n) => {
    -                cur.write_all(&[Self::BRANCH_NODE]).unwrap();
    -                for c in n.chd.iter() {
    -                    cur.write_all(&match c {
    -                        Some(p) => p.addr().to_le_bytes(),
    -                        None => 0u64.to_le_bytes(),
    -                    })
    -                    .unwrap();
    -                }
    -                match &n.value {
    -                    Some(val) => {
    -                        cur.write_all(&(val.len() as u32).to_le_bytes()).unwrap();
    -                        cur.write_all(val).unwrap();
    -                    }
    -                    None => {
    -                        cur.write_all(&u32::MAX.to_le_bytes()).unwrap();
    -                    }
    -                }
    -            }
    -            NodeType::Extension(n) => {
    -                cur.write_all(&[Self::EXT_NODE]).unwrap();
    -                let path: Vec<u8> = from_nibbles(&n.0.encode(false)).collect();
    -                cur.write_all(&[path.len() as u8]).unwrap();
    -                cur.write_all(&n.1.addr().to_le_bytes()).unwrap();
    -                cur.write_all(&path).unwrap();
    -            }
    -            NodeType::Leaf(n) => {
    -                cur.write_all(&[Self::LEAF_NODE]).unwrap();
    -                let path: Vec<u8> = from_nibbles(&n.0.encode(true)).collect();
    -                cur.write_all(&[path.len() as u8]).unwrap();
    -                cur.write_all(&(n.1.len() as u32).to_le_bytes()).unwrap();
    -                cur.write_all(&path).unwrap();
    -                cur.write_all(&n.1).unwrap();
    -            }
    -        }
    -    }
    -}
    -
    -#[test]
    -fn test_merkle_node_encoding() {
    -    let check = |node: Node| {
    -        let mut bytes = Vec::new();
    -        bytes.resize(node.dehydrated_len() as usize, 0);
    -        node.dehydrate(&mut bytes);
    -
    -        let mem = shale::PlainMem::new(bytes.len() as u64, 0x0);
    -        mem.write(0, &bytes);
    -        println!("{bytes:?}");
    -        let node_ = Node::hydrate(0, &mem).unwrap();
    -        assert!(node == node_);
    -    };
    -    let chd0 = [None; NBRANCH];
    -    let mut chd1 = chd0;
    -    for node in chd1.iter_mut().take(NBRANCH / 2) {
    -        *node = Some(unsafe { ObjPtr::new_from_addr(0xa) });
    -    }
    -    for node in [
    -        Node::new_from_hash(
    -            None,
    -            None,
    -            NodeType::Leaf(LeafNode(PartialPath(vec![0x1, 0x2, 0x3]), Data(vec![0x4, 0x5]))),
    -        ),
    -        Node::new_from_hash(
    -            None,
    -            None,
    -            NodeType::Extension(ExtNode(PartialPath(vec![0x1, 0x2, 0x3]), unsafe {
    -                ObjPtr::new_from_addr(0x42)
    -            })),
    -        ),
    -        Node::new_from_hash(
    -            None,
    -            None,
    -            NodeType::Branch(BranchNode {
    -                chd: chd0,
    -                value: Some(Data("hello, world!".as_bytes().to_vec())),
    -            }),
    -        ),
    -        Node::new_from_hash(None, None, NodeType::Branch(BranchNode { chd: chd1, value: None })),
    -    ] {
    -        check(node);
    -    }
    -}
    -
    -macro_rules! write_node {
    -    ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => {
    -        if let None = $r.write($modify) {
    -            let ptr = $self.new_node($r.clone())?.as_ptr();
    -            $self.set_parent(ptr, $parents);
    -            $deleted.push($r.as_ptr());
    -            true
    -        } else {
    -            false
    -        }
    -    };
    -}
    -
    -pub struct Merkle {
    -    store: Box<dyn ShaleStore<Node>>,
    -}
    -
    -impl Merkle {
    -    fn get_node(&self, ptr: ObjPtr<Node>) -> Result<ObjRef<Node>, MerkleError> {
    -        self.store.get_item(ptr).map_err(MerkleError::Shale)
    -    }
    -    fn new_node(&self, item: Node) -> Result<ObjRef<Node>, MerkleError> {
    -        self.store.put_item(item, 0).map_err(MerkleError::Shale)
    -    }
    -    fn free_node(&mut self, ptr: ObjPtr<Node>) -> Result<(), MerkleError> {
    -        self.store.free_item(ptr).map_err(MerkleError::Shale)
    -    }
    -}
    -
    -impl Merkle {
    -    pub fn new(store: Box<dyn ShaleStore<Node>>) -> Self {
    -        Self { store }
    -    }
    -
    -    pub fn init_root(root: &mut ObjPtr<Node>, store: &dyn ShaleStore<Node>) -> Result<(), MerkleError> {
    -        *root = store
    -            .put_item(
    -                Node::new(NodeType::Branch(BranchNode {
    -                    chd: [None; NBRANCH],
    -                    value: None,
    -                })),
    -                Node::max_branch_node_size(),
    -            )
    -            .map_err(MerkleError::Shale)?
    -            .as_ptr();
    -        Ok(())
    -    }
    -
    -    pub fn get_store(&self) -> &dyn ShaleStore<Node> {
    -        self.store.as_ref()
    -    }
    -
    -    pub fn empty_root() -> &'static Hash {
    -        use once_cell::sync::OnceCell;
    -        static V: OnceCell<Hash> = OnceCell::new();
    -        V.get_or_init(|| {
    -            Hash(
    -                hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
    -                    .unwrap()
    -                    .try_into()
    -                    .unwrap(),
    -            )
    -        })
    -    }
    -
    -    pub fn root_hash<T: ValueTransformer>(&self, root: ObjPtr<Node>) -> Result<Hash, MerkleError> {
    -        let root = self
    -            .get_node(root)?
    -            .inner
    -            .as_branch()
    -            .ok_or(MerkleError::NotBranchNode)?
    -            .chd[0];
    -        Ok(if let Some(root) = root {
    -            let mut node = self.get_node(root)?;
    -            let res = node.get_root_hash::<T>(self.store.as_ref()).clone();
    -            if node.lazy_dirty.get() {
    -                node.write(|_| {}).unwrap();
    -                node.lazy_dirty.set(false)
    -            }
    -            res
    -        } else {
    -            Self::empty_root().clone()
    -        })
    -    }
    -
    -    fn dump_(&self, u: ObjPtr<Node>, w: &mut dyn Write) -> Result<(), MerkleError> {
    -        let u_ref = self.get_node(u)?;
    -        write!(
    -            w,
    -            "{} => {}: ",
    -            u,
    -            match u_ref.root_hash.get() {
    -                Some(h) => hex::encode(**h),
    -                None => "<lazy>".to_string(),
    -            }
    -        )
    -        .map_err(MerkleError::Format)?;
    -        match &u_ref.inner {
    -            NodeType::Branch(n) => {
    -                writeln!(w, "{n:?}").map_err(MerkleError::Format)?;
    -                for c in n.chd.iter().flatten() {
    -                    self.dump_(*c, w)?
    -                }
    -            }
    -            NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(),
    -            NodeType::Extension(n) => {
    -                writeln!(w, "{n:?}").map_err(MerkleError::Format)?;
    -                self.dump_(n.1, w)?
    -            }
    -        }
    -        Ok(())
    -    }
    -
    -    pub fn dump(&self, root: ObjPtr<Node>, w: &mut dyn Write) -> Result<(), MerkleError> {
    -        if root.is_null() {
    -            write!(w, "<Empty>").map_err(MerkleError::Format)?;
    -        } else {
    -            self.dump_(root, w)?;
    -        };
    -        Ok(())
    -    }
    -
    -    fn set_parent<'b>(&self, new_chd: ObjPtr<Node>, parents: &mut [(ObjRef<'b, Node>, u8)]) {
    -        let (p_ref, idx) = parents.last_mut().unwrap();
    -        p_ref
    -            .write(|p| {
    -                match &mut p.inner {
    -                    NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd),
    -                    NodeType::Extension(pp) => pp.1 = new_chd,
    -                    _ => unreachable!(),
    -                }
    -                p.rehash();
    -            })
    -            .unwrap();
    -    }
    -
    -    #[allow(clippy::too_many_arguments)]
    -    fn split<'b>(
    -        &self, mut u_ref: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], rem_path: &[u8], n_path: Vec<u8>,
    -        n_value: Option<Data>, val: Vec<u8>, deleted: &mut Vec<ObjPtr<Node>>,
    -    ) -> Result<Option<Vec<u8>>, MerkleError> {
    -        let u_ptr = u_ref.as_ptr();
    -        let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) {
    -            Some(idx) => {
    -                //                                                      _ [u (new path)]
    -                //                                                     /
    -                //  [parent] (-> [ExtNode (common prefix)]) -> [branch]*
    -                //                                                     \_ [leaf (with val)]
    -                u_ref
    -                    .write(|u| {
    -                        (*match &mut u.inner {
    -                            NodeType::Leaf(u) => &mut u.0,
    -                            NodeType::Extension(u) => &mut u.0,
    -                            _ => unreachable!(),
    -                        }) = PartialPath(n_path[idx + 1..].to_vec());
    -                        u.rehash();
    -                    })
    -                    .unwrap();
    -                let leaf_ptr = self
    -                    .new_node(Node::new(NodeType::Leaf(LeafNode(
    -                        PartialPath(rem_path[idx + 1..].to_vec()),
    -                        Data(val),
    -                    ))))?
    -                    .as_ptr();
    -                let mut chd = [None; NBRANCH];
    -                chd[rem_path[idx] as usize] = Some(leaf_ptr);
    -                chd[n_path[idx] as usize] = Some(match &u_ref.inner {
    -                    NodeType::Extension(u) => {
    -                        if u.0.len() == 0 {
    -                            deleted.push(u_ptr);
    -                            u.1
    -                        } else {
    -                            u_ptr
    -                        }
    -                    }
    -                    _ => u_ptr,
    -                });
    -                drop(u_ref);
    -                let t = NodeType::Branch(BranchNode { chd, value: None });
    -                let branch_ptr = self.new_node(Node::new(t))?.as_ptr();
    -                if idx > 0 {
    -                    self.new_node(Node::new(NodeType::Extension(ExtNode(
    -                        PartialPath(rem_path[..idx].to_vec()),
    -                        branch_ptr,
    -                    ))))?
    -                    .as_ptr()
    -                } else {
    -                    branch_ptr
    -                }
    -            }
    -            None => {
    -                if rem_path.len() == n_path.len() {
    -                    let mut err = None;
    -                    write_node!(
    -                        self,
    -                        u_ref,
    -                        |u| {
    -                            #[allow(clippy::blocks_in_if_conditions)]
    -                            match &mut u.inner {
    -                                NodeType::Leaf(u) => u.1 = Data(val),
    -                                NodeType::Extension(u) => {
    -                                    if let Err(e) = (|| {
    -                                        let mut b_ref = self.get_node(u.1)?;
    -                                        if b_ref
    -                                            .write(|b| {
    -                                                b.inner.as_branch_mut().unwrap().value = Some(Data(val));
    -                                                b.rehash()
    -                                            })
    -                                            .is_none()
    -                                        {
    -                                            u.1 = self.new_node(b_ref.clone())?.as_ptr();
    -                                            deleted.push(b_ref.as_ptr());
    -                                        }
    -                                        Ok(())
    -                                    })() {
    -                                        err = Some(Err(e))
    -                                    }
    -                                }
    -                                _ => unreachable!(),
    -                            }
    -                            u.rehash();
    -                        },
    -                        parents,
    -                        deleted
    -                    );
    -                    return err.unwrap_or_else(|| Ok(None))
    -                }
    -                let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() {
    -                    // key path is a prefix of the path to u
    -                    u_ref
    -                        .write(|u| {
    -                            (*match &mut u.inner {
    -                                NodeType::Leaf(u) => &mut u.0,
    -                                NodeType::Extension(u) => &mut u.0,
    -                                _ => unreachable!(),
    -                            }) = PartialPath(n_path[rem_path.len() + 1..].to_vec());
    -                            u.rehash();
    -                        })
    -                        .unwrap();
    -                    (
    -                        match &u_ref.inner {
    -                            NodeType::Extension(u) => {
    -                                if u.0.len() == 0 {
    -                                    deleted.push(u_ptr);
    -                                    u.1
    -                                } else {
    -                                    u_ptr
    -                                }
    -                            }
    -                            _ => u_ptr,
    -                        },
    -                        rem_path,
    -                        n_path[rem_path.len()],
    -                        Some(Data(val)),
    -                    )
    -                } else {
    -                    // key path extends the path to u
    -                    if n_value.is_none() {
    -                        // this case does not apply to an extension node, resume the tree walk
    -                        return Ok(Some(val))
    -                    }
    -                    let leaf = self.new_node(Node::new(NodeType::Leaf(LeafNode(
    -                        PartialPath(rem_path[n_path.len() + 1..].to_vec()),
    -                        Data(val),
    -                    ))))?;
    -                    deleted.push(u_ptr);
    -                    (leaf.as_ptr(), &n_path[..], rem_path[n_path.len()], n_value)
    -                };
    -                drop(u_ref);
    -                // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf]
    -                let mut chd = [None; NBRANCH];
    -                chd[idx as usize] = Some(leaf_ptr);
    -                let branch_ptr = self
    -                    .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v })))?
    -                    .as_ptr();
    -                if !prefix.is_empty() {
    -                    self.new_node(Node::new(NodeType::Extension(ExtNode(
    -                        PartialPath(prefix.to_vec()),
    -                        branch_ptr,
    -                    ))))?
    -                    .as_ptr()
    -                } else {
    -                    branch_ptr
    -                }
    -            }
    -        };
    -        // observation:
    -        // - leaf/extension node can only be the child of a branch node
    -        // - branch node can only be the child of a branch/extension node
    -        self.set_parent(new_chd, parents);
    -        Ok(None)
    -    }
    -
    -    pub fn insert<K: AsRef<[u8]>>(&mut self, key: K, val: Vec<u8>, root: ObjPtr<Node>) -> Result<(), MerkleError> {
    -        let mut deleted = Vec::new();
    -        let mut chunks = vec![0];
    -        chunks.extend(to_nibbles(key.as_ref()));
    -        let mut parents = Vec::new();
    -        let mut u_ref = Some(self.get_node(root)?);
    -        let mut nskip = 0;
    -        let mut val = Some(val);
    -        for (i, nib) in chunks.iter().enumerate() {
    -            if nskip > 0 {
    -                nskip -= 1;
    -                continue
    -            }
    -            let mut u = u_ref.take().unwrap();
    -            let u_ptr = u.as_ptr();
    -            let next_ptr = match &u.inner {
    -                NodeType::Branch(n) => match n.chd[*nib as usize] {
    -                    Some(c) => c,
    -                    None => {
    -                        // insert the leaf to the empty slot
    -                        let leaf_ptr = self
    -                            .new_node(Node::new(NodeType::Leaf(LeafNode(
    -                                PartialPath(chunks[i + 1..].to_vec()),
    -                                Data(val.take().unwrap()),
    -                            ))))?
    -                            .as_ptr();
    -                        u.write(|u| {
    -                            let uu = u.inner.as_branch_mut().unwrap();
    -                            uu.chd[*nib as usize] = Some(leaf_ptr);
    -                            u.rehash();
    -                        })
    -                        .unwrap();
    -                        break
    -                    }
    -                },
    -                NodeType::Leaf(n) => {
    -                    let n_path = n.0.to_vec();
    -                    let n_value = Some(n.1.clone());
    -                    self.split(
    -                        u,
    -                        &mut parents,
    -                        &chunks[i..],
    -                        n_path,
    -                        n_value,
    -                        val.take().unwrap(),
    -                        &mut deleted,
    -                    )?;
    -                    break
    -                }
    -                NodeType::Extension(n) => {
    -                    let n_path = n.0.to_vec();
    -                    let n_ptr = n.1;
    -                    nskip = n_path.len() - 1;
    -                    if let Some(v) = self.split(
    -                        u,
    -                        &mut parents,
    -                        &chunks[i..],
    -                        n_path,
    -                        None,
    -                        val.take().unwrap(),
    -                        &mut deleted,
    -                    )? {
    -                        val = Some(v);
    -                        u = self.get_node(u_ptr)?;
    -                        n_ptr
    -                    } else {
    -                        break
    -                    }
    -                }
    -            };
    -
    -            parents.push((u, *nib));
    -            u_ref = Some(self.get_node(next_ptr)?);
    -        }
    -        if val.is_some() {
    -            let mut info = None;
    -            let u_ptr = {
    -                let mut u = u_ref.take().unwrap();
    -                write_node!(
    -                    self,
    -                    u,
    -                    |u| {
    -                        info = match &mut u.inner {
    -                            NodeType::Branch(n) => {
    -                                n.value = Some(Data(val.take().unwrap()));
    -                                None
    -                            }
    -                            NodeType::Leaf(n) => {
    -                                if n.0.len() == 0 {
    -                                    n.1 = Data(val.take().unwrap());
    -                                    None
    -                                } else {
    -                                    let idx = n.0[0];
    -                                    n.0 = PartialPath(n.0[1..].to_vec());
    -                                    u.rehash();
    -                                    Some((idx, true, None))
    -                                }
    -                            }
    -                            NodeType::Extension(n) => {
    -                                let idx = n.0[0];
    -                                let more = if n.0.len() > 1 {
    -                                    n.0 = PartialPath(n.0[1..].to_vec());
    -                                    true
    -                                } else {
    -                                    false
    -                                };
    -                                Some((idx, more, Some(n.1)))
    -                            }
    -                        };
    -                        u.rehash()
    -                    },
    -                    &mut parents,
    -                    &mut deleted
    -                );
    -                u.as_ptr()
    -            };
    -
    -            if let Some((idx, more, ext)) = info {
    -                let mut chd = [None; NBRANCH];
    -                let c_ptr = if more {
    -                    u_ptr
    -                } else {
    -                    deleted.push(u_ptr);
    -                    ext.unwrap()
    -                };
    -                chd[idx as usize] = Some(c_ptr);
    -                let branch = self
    -                    .new_node(Node::new(NodeType::Branch(BranchNode {
    -                        chd,
    -                        value: Some(Data(val.take().unwrap())),
    -                    })))?
    -                    .as_ptr();
    -                self.set_parent(branch, &mut parents);
    -            }
    -        }
    -
    -        drop(u_ref);
    -
    -        for (mut r, _) in parents.into_iter().rev() {
    -            r.write(|u| u.rehash()).unwrap();
    -        }
    -
    -        for ptr in deleted.into_iter() {
    -            self.free_node(ptr)?
    -        }
    -        Ok(())
    -    }
    -
    -    fn after_remove_leaf<'b>(
    -        &self, parents: &mut Vec<(ObjRef<'b, Node>, u8)>, deleted: &mut Vec<ObjPtr<Node>>,
    -    ) -> Result<(), MerkleError> {
    -        let (b_chd, val) = {
    -            let (mut b_ref, b_idx) = parents.pop().unwrap();
    -            // the immediate parent of a leaf must be a branch
    -            b_ref
    -                .write(|b| {
    -                    b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = None;
    -                    b.rehash()
    -                })
    -                .unwrap();
    -            let b_inner = b_ref.inner.as_branch().unwrap();
    -            let (b_chd, has_chd) = b_inner.single_child();
    -            if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() {
    -                return Ok(())
    -            }
    -            deleted.push(b_ref.as_ptr());
    -            (b_chd, b_inner.value.clone())
    -        };
    -        let (mut p_ref, p_idx) = parents.pop().unwrap();
    -        let p_ptr = p_ref.as_ptr();
    -        if let Some(val) = val {
    -            match &p_ref.inner {
    -                NodeType::Branch(_) => {
    -                    // from: [p: Branch] -> [b (v)]x -> [Leaf]x
    -                    // to: [p: Branch] -> [Leaf (v)]
    -                    let leaf = self
    -                        .new_node(Node::new(NodeType::Leaf(LeafNode(PartialPath(Vec::new()), val))))?
    -                        .as_ptr();
    -                    p_ref
    -                        .write(|p| {
    -                            p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(leaf);
    -                            p.rehash()
    -                        })
    -                        .unwrap();
    -                }
    -                NodeType::Extension(n) => {
    -                    // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x
    -                    // to: P -> [Leaf (v)]
    -                    let leaf = self
    -                        .new_node(Node::new(NodeType::Leaf(LeafNode(
    -                            PartialPath(n.0.clone().into_inner()),
    -                            val,
    -                        ))))?
    -                        .as_ptr();
    -                    deleted.push(p_ptr);
    -                    self.set_parent(leaf, parents);
    -                }
    -                _ => unreachable!(),
    -            }
    -        } else {
    -            let (c_ptr, idx) = b_chd.unwrap();
    -            let mut c_ref = self.get_node(c_ptr)?;
    -            match &c_ref.inner {
    -                NodeType::Branch(_) => {
    -                    drop(c_ref);
    -                    match &p_ref.inner {
    -                        NodeType::Branch(_) => {
    -                            //                            ____[Branch]
    -                            //                           /
    -                            // from: [p: Branch] -> [b]x*
    -                            //                           \____[Leaf]x
    -                            // to: [p: Branch] -> [Ext] -> [Branch]
    -                            let ext = self
    -                                .new_node(Node::new(NodeType::Extension(ExtNode(PartialPath(vec![idx]), c_ptr))))?
    -                                .as_ptr();
    -                            self.set_parent(ext, &mut [(p_ref, p_idx)]);
    -                        }
    -                        NodeType::Extension(_) => {
    -                            //                         ____[Branch]
    -                            //                        /
    -                            // from: [p: Ext] -> [b]x*
    -                            //                        \____[Leaf]x
    -                            // to: [p: Ext] -> [Branch]
    -                            write_node!(
    -                                self,
    -                                p_ref,
    -                                |p| {
    -                                    let mut pp = p.inner.as_extension_mut().unwrap();
    -                                    pp.0 .0.push(idx);
    -                                    pp.1 = c_ptr;
    -                                    p.rehash();
    -                                },
    -                                parents,
    -                                deleted
    -                            );
    -                        }
    -                        _ => unreachable!(),
    -                    }
    -                }
    -                NodeType::Leaf(_) | NodeType::Extension(_) => {
    -                    #[allow(clippy::blocks_in_if_conditions)]
    -                    match &p_ref.inner {
    -                        NodeType::Branch(_) => {
    -                            //                            ____[Leaf/Ext]
    -                            //                           /
    -                            // from: [p: Branch] -> [b]x*
    -                            //                           \____[Leaf]x
    -                            // to: [p: Branch] -> [Leaf/Ext]
    -                            let c_ptr = if c_ref
    -                                .write(|c| {
    -                                    (match &mut c.inner {
    -                                        NodeType::Leaf(n) => &mut n.0,
    -                                        NodeType::Extension(n) => &mut n.0,
    -                                        _ => unreachable!(),
    -                                    })
    -                                    .0
    -                                    .insert(0, idx);
    -                                    c.rehash()
    -                                })
    -                                .is_none()
    -                            {
    -                                deleted.push(c_ptr);
    -                                self.new_node(c_ref.clone())?.as_ptr()
    -                            } else {
    -                                c_ptr
    -                            };
    -                            drop(c_ref);
    -                            p_ref
    -                                .write(|p| {
    -                                    p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(c_ptr);
    -                                    p.rehash()
    -                                })
    -                                .unwrap();
    -                        }
    -                        NodeType::Extension(n) => {
    -                            //                               ____[Leaf/Ext]
    -                            //                              /
    -                            // from: P -> [p: Ext]x -> [b]x*
    -                            //                              \____[Leaf]x
    -                            // to: P -> [p: Leaf/Ext]
    -                            deleted.push(p_ptr);
    -                            if !write_node!(
    -                                self,
    -                                c_ref,
    -                                |c| {
    -                                    let mut path = n.0.clone().into_inner();
    -                                    path.push(idx);
    -                                    let path0 = match &mut c.inner {
    -                                        NodeType::Leaf(n) => &mut n.0,
    -                                        NodeType::Extension(n) => &mut n.0,
    -                                        _ => unreachable!(),
    -                                    };
    -                                    path.extend(&**path0);
    -                                    *path0 = PartialPath(path);
    -                                    c.rehash()
    -                                },
    -                                parents,
    -                                deleted
    -                            ) {
    -                                drop(c_ref);
    -                                self.set_parent(c_ptr, parents);
    -                            }
    -                        }
    -                        _ => unreachable!(),
    -                    }
    -                }
    -            }
    -        }
    -        Ok(())
    -    }
    -
    -    fn after_remove_branch<'b>(
    -        &self, (c_ptr, idx): (ObjPtr<Node>, u8), parents: &mut Vec<(ObjRef<'b, Node>, u8)>,
    -        deleted: &mut Vec<ObjPtr<Node>>,
    -    ) -> Result<(), MerkleError> {
    -        // [b] -> [u] -> [c]
    -        let (mut b_ref, b_idx) = parents.pop().unwrap();
    -        let mut c_ref = self.get_node(c_ptr).unwrap();
    -        match &c_ref.inner {
    -            NodeType::Branch(_) => {
    -                drop(c_ref);
    -                let mut err = None;
    -                write_node!(
    -                    self,
    -                    b_ref,
    -                    |b| {
    -                        if let Err(e) = (|| {
    -                            match &mut b.inner {
    -                                NodeType::Branch(n) => {
    -                                    // from: [Branch] -> [Branch]x -> [Branch]
    -                                    // to: [Branch] -> [Ext] -> [Branch]
    -                                    n.chd[b_idx as usize] = Some(
    -                                        self.new_node(Node::new(NodeType::Extension(ExtNode(
    -                                            PartialPath(vec![idx]),
    -                                            c_ptr,
    -                                        ))))?
    -                                        .as_ptr(),
    -                                    );
    -                                }
    -                                NodeType::Extension(n) => {
    -                                    // from: [Ext] -> [Branch]x -> [Branch]
    -                                    // to: [Ext] -> [Branch]
    -                                    n.0 .0.push(idx);
    -                                    n.1 = c_ptr
    -                                }
    -                                _ => unreachable!(),
    -                            }
    -                            b.rehash();
    -                            Ok(())
    -                        })() {
    -                            err = Some(Err(e))
    -                        }
    -                    },
    -                    parents,
    -                    deleted
    -                );
    -                if let Some(e) = err {
    -                    return e
    -                }
    -            }
    -            NodeType::Leaf(_) | NodeType::Extension(_) => match &b_ref.inner {
    -                NodeType::Branch(_) => {
    -                    // from: [Branch] -> [Branch]x -> [Leaf/Ext]
    -                    // to: [Branch] -> [Leaf/Ext]
    -                    #[allow(clippy::blocks_in_if_conditions)]
    -                    let c_ptr = if c_ref
    -                        .write(|c| {
    -                            match &mut c.inner {
    -                                NodeType::Leaf(n) => &mut n.0,
    -                                NodeType::Extension(n) => &mut n.0,
    -                                _ => unreachable!(),
    -                            }
    -                            .0
    -                            .insert(0, idx);
    -                            c.rehash()
    -                        })
    -                        .is_none()
    -                    {
    -                        deleted.push(c_ptr);
    -                        self.new_node(c_ref.clone())?.as_ptr()
    -                    } else {
    -                        c_ptr
    -                    };
    -                    drop(c_ref);
    -                    b_ref
    -                        .write(|b| {
    -                            b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = Some(c_ptr);
    -                            b.rehash()
    -                        })
    -                        .unwrap();
    -                }
    -                NodeType::Extension(n) => {
    -                    // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext]
    -                    // to: P -> [Leaf/Ext]
    -                    #[allow(clippy::blocks_in_if_conditions)]
    -                    let c_ptr = if c_ref
    -                        .write(|c| {
    -                            let mut path = n.0.clone().into_inner();
    -                            path.push(idx);
    -                            let path0 = match &mut c.inner {
    -                                NodeType::Leaf(n) => &mut n.0,
    -                                NodeType::Extension(n) => &mut n.0,
    -                                _ => unreachable!(),
    -                            };
    -                            path.extend(&**path0);
    -                            *path0 = PartialPath(path);
    -                            c.rehash()
    -                        })
    -                        .is_none()
    -                    {
    -                        deleted.push(c_ptr);
    -                        self.new_node(c_ref.clone())?.as_ptr()
    -                    } else {
    -                        c_ptr
    -                    };
    -                    deleted.push(b_ref.as_ptr());
    -                    drop(c_ref);
    -                    self.set_parent(c_ptr, parents);
    -                }
    -                _ => unreachable!(),
    -            },
    -        }
    -        Ok(())
    -    }
    -
    -    pub fn remove<K: AsRef<[u8]>>(&mut self, key: K, root: ObjPtr<Node>) -> Result<Option<Vec<u8>>, MerkleError> {
    -        let mut chunks = vec![0];
    -        chunks.extend(to_nibbles(key.as_ref()));
    -
    -        if root.is_null() {
    -            return Ok(None)
    -        }
    -
    -        let mut deleted = Vec::new();
    -        let mut parents: Vec<(ObjRef<Node>, _)> = Vec::new();
    -        let mut u_ref = self.get_node(root)?;
    -        let mut nskip = 0;
    -        let mut found = None;
    -
    -        for (i, nib) in chunks.iter().enumerate() {
    -            if nskip > 0 {
    -                nskip -= 1;
    -                continue
    -            }
    -            let next_ptr = match &u_ref.inner {
    -                NodeType::Branch(n) => match n.chd[*nib as usize] {
    -                    Some(c) => c,
    -                    None => return Ok(None),
    -                },
    -                NodeType::Leaf(n) => {
    -                    if chunks[i..] != *n.0 {
    -                        return Ok(None)
    -                    }
    -                    found = Some(n.1.clone());
    -                    deleted.push(u_ref.as_ptr());
    -                    self.after_remove_leaf(&mut parents, &mut deleted)?;
    -                    break
    -                }
    -                NodeType::Extension(n) => {
    -                    let n_path = &*n.0;
    -                    let rem_path = &chunks[i..];
    -                    if rem_path < n_path || &rem_path[..n_path.len()] != n_path {
    -                        return Ok(None)
    -                    }
    -                    nskip = n_path.len() - 1;
    -                    n.1
    -                }
    -            };
    -
    -            parents.push((u_ref, *nib));
    -            u_ref = self.get_node(next_ptr)?;
    -        }
    -        if found.is_none() {
    -            match &u_ref.inner {
    -                NodeType::Branch(n) => {
    -                    if n.value.is_none() {
    -                        return Ok(None)
    -                    }
    -                    let (c_chd, _) = n.single_child();
    -                    u_ref
    -                        .write(|u| {
    -                            found = u.inner.as_branch_mut().unwrap().value.take();
    -                            u.rehash()
    -                        })
    -                        .unwrap();
    -                    if let Some((c_ptr, idx)) = c_chd {
    -                        deleted.push(u_ref.as_ptr());
    -                        self.after_remove_branch((c_ptr, idx), &mut parents, &mut deleted)?
    -                    }
    -                }
    -                NodeType::Leaf(n) => {
    -                    if n.0.len() > 0 {
    -                        return Ok(None)
    -                    }
    -                    found = Some(n.1.clone());
    -                    deleted.push(u_ref.as_ptr());
    -                    self.after_remove_leaf(&mut parents, &mut deleted)?
    -                }
    -                _ => (),
    -            }
    -        }
    -
    -        drop(u_ref);
    -
    -        for (mut r, _) in parents.into_iter().rev() {
    -            r.write(|u| u.rehash()).unwrap();
    -        }
    -
    -        for ptr in deleted.into_iter() {
    -            self.free_node(ptr)?;
    -        }
    -        Ok(found.map(|e| e.0))
    -    }
    -
    -    fn remove_tree_(&self, u: ObjPtr<Node>, deleted: &mut Vec<ObjPtr<Node>>) -> Result<(), MerkleError> {
    -        let u_ref = self.get_node(u)?;
    -        match &u_ref.inner {
    -            NodeType::Branch(n) => {
    -                for c in n.chd.iter().flatten() {
    -                    self.remove_tree_(*c, deleted)?
    -                }
    -            }
    -            NodeType::Leaf(_) => (),
    -            NodeType::Extension(n) => self.remove_tree_(n.1, deleted)?,
    -        }
    -        deleted.push(u);
    -        Ok(())
    -    }
    -
    -    pub fn remove_tree(&mut self, root: ObjPtr<Node>) -> Result<(), MerkleError> {
    -        let mut deleted = Vec::new();
    -        if root.is_null() {
    -            return Ok(())
    -        }
    -        self.remove_tree_(root, &mut deleted)?;
    -        for ptr in deleted.into_iter() {
    -            self.free_node(ptr)?;
    -        }
    -        Ok(())
    -    }
    -
    -    pub fn get_mut<K: AsRef<[u8]>>(&mut self, key: K, root: ObjPtr<Node>) -> Result<Option<RefMut>, MerkleError> {
    -        let mut chunks = vec![0];
    -        chunks.extend(to_nibbles(key.as_ref()));
    -        let mut parents = Vec::new();
    -
    -        if root.is_null() {
    -            return Ok(None)
    -        }
    -
    -        let mut u_ref = self.get_node(root)?;
    -        let mut nskip = 0;
    -
    -        for (i, nib) in chunks.iter().enumerate() {
    -            let u_ptr = u_ref.as_ptr();
    -            if nskip > 0 {
    -                nskip -= 1;
    -                continue
    -            }
    -            let next_ptr = match &u_ref.inner {
    -                NodeType::Branch(n) => match n.chd[*nib as usize] {
    -                    Some(c) => c,
    -                    None => return Ok(None),
    -                },
    -                NodeType::Leaf(n) => {
    -                    if chunks[i..] != *n.0 {
    -                        return Ok(None)
    -                    }
    -                    drop(u_ref);
    -                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
    -                }
    -                NodeType::Extension(n) => {
    -                    let n_path = &*n.0;
    -                    let rem_path = &chunks[i..];
    -                    if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path {
    -                        return Ok(None)
    -                    }
    -                    nskip = n_path.len() - 1;
    -                    n.1
    -                }
    -            };
    -            parents.push((u_ptr, *nib));
    -            u_ref = self.get_node(next_ptr)?;
    -        }
    -
    -        let u_ptr = u_ref.as_ptr();
    -        match &u_ref.inner {
    -            NodeType::Branch(n) => {
    -                if n.value.as_ref().is_some() {
    -                    drop(u_ref);
    -                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
    -                }
    -            }
    -            NodeType::Leaf(n) => {
    -                if n.0.len() == 0 {
    -                    drop(u_ref);
    -                    return Ok(Some(RefMut::new(u_ptr, parents, self)))
    -                }
    -            }
    -            _ => (),
    -        }
    -
    -        Ok(None)
    -    }
    -
    -    /// Constructs a merkle proof for key. The result contains all encoded nodes
    -    /// on the path to the value at key. The value itself is also included in the
    -    /// last node and can be retrieved by verifying the proof.
    -    ///
    -    /// If the trie does not contain a value for key, the returned proof contains
    -    /// all nodes of the longest existing prefix of the key, ending with the node
    -    /// that proves the absence of the key (at least the root node).
    -    pub fn prove<K, T>(&self, key: K, root: ObjPtr<Node>) -> Result<Proof, MerkleError>
    -    where
    -        K: AsRef<[u8]>,
    -        T: ValueTransformer,
    -    {
    -        let mut chunks = Vec::new();
    -        chunks.extend(to_nibbles(key.as_ref()));
    -
    -        let mut proofs: HashMap<[u8; 32], Vec<u8>> = HashMap::new();
    -        if root.is_null() {
    -            return Ok(Proof(proofs))
    -        }
    -
    -        // Skip the sentinel root
    -        let root = self
    -            .get_node(root)?
    -            .inner
    -            .as_branch()
    -            .ok_or(MerkleError::NotBranchNode)?
    -            .chd[0];
    -        let mut u_ref = match root {
    -            Some(root) => self.get_node(root)?,
    -            None => return Ok(Proof(proofs)),
    -        };
    -
    -        let mut nskip = 0;
    -        let mut nodes: Vec<ObjPtr<Node>> = Vec::new();
    -        for (i, nib) in chunks.iter().enumerate() {
    -            if nskip > 0 {
    -                nskip -= 1;
    -                continue
    -            }
    -            nodes.push(u_ref.as_ptr());
    -            let next_ptr: ObjPtr<Node> = match &u_ref.inner {
    -                NodeType::Branch(n) => match n.chd[*nib as usize] {
    -                    Some(c) => c,
    -                    None => break,
    -                },
    -                NodeType::Leaf(_) => break,
    -                NodeType::Extension(n) => {
    -                    let n_path = &*n.0;
    -                    let remaining_path = &chunks[i..];
    -                    if remaining_path.len() < n_path.len() || &remaining_path[..n_path.len()] != n_path {
    -                        break
    -                    } else {
    -                        nskip = n_path.len() - 1;
    -                        n.1
    -                    }
    -                }
    -            };
    -            u_ref = self.get_node(next_ptr)?;
    -        }
    -
    -        match &u_ref.inner {
    -            NodeType::Branch(n) => {
    -                if n.value.as_ref().is_some() {
    -                    nodes.push(u_ref.as_ptr());
    -                }
    -            }
    -            NodeType::Leaf(n) => {
    -                if n.0.len() == 0 {
    -                    nodes.push(u_ref.as_ptr());
    -                }
    -            }
    -            _ => (),
    -        }
    -
    -        drop(u_ref);
    -        // Get the hashes of the nodes.
    -        for node in nodes {
    -            let node = self.get_node(node)?;
    -            let rlp = <&[u8]>::clone(&node.get_eth_rlp::<T>(self.store.as_ref()));
    -            let hash: [u8; 32] = sha3::Keccak256::digest(rlp).into();
    -            proofs.insert(hash, rlp.to_vec());
    -        }
    -        Ok(Proof(proofs))
    -    }
    -
    -    pub fn get<K: AsRef<[u8]>>(&self, key: K, root: ObjPtr<Node>) -> Result<Option<Ref>, MerkleError> {
    -        let mut chunks = vec![0];
    -        chunks.extend(to_nibbles(key.as_ref()));
    -
    -        if root.is_null() {
    -            return Ok(None)
    -        }
    -
    -        let mut u_ref = self.get_node(root)?;
    -        let mut nskip = 0;
    -
    -        for (i, nib) in chunks.iter().enumerate() {
    -            if nskip > 0 {
    -                nskip -= 1;
    -                continue
    -            }
    -            let next_ptr = match &u_ref.inner {
    -                NodeType::Branch(n) => match n.chd[*nib as usize] {
    -                    Some(c) => c,
    -                    None => return Ok(None),
    -                },
    -                NodeType::Leaf(n) => {
    -                    if chunks[i..] != *n.0 {
    -                        return Ok(None)
    -                    }
    -                    return Ok(Some(Ref(u_ref)))
    -                }
    -                NodeType::Extension(n) => {
    -                    let n_path = &*n.0;
    -                    let rem_path = &chunks[i..];
    -                    if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path {
    -                        return Ok(None)
    -                    }
    -                    nskip = n_path.len() - 1;
    -                    n.1
    -                }
    -            };
    -            u_ref = self.get_node(next_ptr)?;
    -        }
    -
    -        match &u_ref.inner {
    -            NodeType::Branch(n) => {
    -                if n.value.as_ref().is_some() {
    -                    return Ok(Some(Ref(u_ref)))
    -                }
    -            }
    -            NodeType::Leaf(n) => {
    -                if n.0.len() == 0 {
    -                    return Ok(Some(Ref(u_ref)))
    -                }
    -            }
    -            _ => (),
    -        }
    -
    -        Ok(None)
    -    }
    -
    -    pub fn flush_dirty(&self) -> Option<()> {
    -        self.store.flush_dirty()
    -    }
    -}
    -
    -pub struct Ref<'a>(ObjRef<'a, Node>);
    -
    -pub struct RefMut<'a> {
    -    ptr: ObjPtr<Node>,
    -    parents: Vec<(ObjPtr<Node>, u8)>,
    -    merkle: &'a mut Merkle,
    -}
    -
    -impl<'a> std::ops::Deref for Ref<'a> {
    -    type Target = [u8];
    -    fn deref(&self) -> &[u8] {
    -        match &self.0.inner {
    -            NodeType::Branch(n) => n.value.as_ref().unwrap(),
    -            NodeType::Leaf(n) => &n.1,
    -            _ => unreachable!(),
    -        }
    -    }
    -}
    -
    -impl<'a> RefMut<'a> {
    -    fn new(ptr: ObjPtr<Node>, parents: Vec<(ObjPtr<Node>, u8)>, merkle: &'a mut Merkle) -> Self {
    -        Self { ptr, parents, merkle }
    -    }
    -
    -    pub fn get(&self) -> Ref {
    -        Ref(self.merkle.get_node(self.ptr).unwrap())
    -    }
    -
    -    pub fn write(&mut self, modify: impl FnOnce(&mut Vec<u8>)) -> Result<(), MerkleError> {
    -        let mut deleted = Vec::new();
    -        {
    -            let mut u_ref = self.merkle.get_node(self.ptr).unwrap();
    -            let mut parents: Vec<_> = self
    -                .parents
    -                .iter()
    -                .map(|(ptr, nib)| (self.merkle.get_node(*ptr).unwrap(), *nib))
    -                .collect();
    -            write_node!(
    -                self.merkle,
    -                u_ref,
    -                |u| {
    -                    modify(match &mut u.inner {
    -                        NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0,
    -                        NodeType::Leaf(n) => &mut n.1 .0,
    -                        _ => unreachable!(),
    -                    });
    -                    u.rehash()
    -                },
    -                &mut parents,
    -                &mut deleted
    -            );
    -        }
    -        for ptr in deleted.into_iter() {
    -            self.merkle.free_node(ptr)?;
    -        }
    -        Ok(())
    -    }
    -}
    -
    -pub trait ValueTransformer {
    -    fn transform(bytes: &[u8]) -> Vec<u8>;
    -}
    -
    -pub struct IdTrans;
    -
    -impl ValueTransformer for IdTrans {
    -    fn transform(bytes: &[u8]) -> Vec<u8> {
    -        bytes.to_vec()
    -    }
    -}
    -
    -pub fn to_nibbles(bytes: &[u8]) -> impl Iterator<Item = u8> + '_ {
    -    bytes.iter().flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter())
    -}
    -
    -pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator<Item = u8> + '_ {
    -    assert!(nibbles.len() & 1 == 0);
    -    nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1])
    -}
    -
    -#[test]
    -fn test_to_nibbles() {
    -    for (bytes, nibbles) in [
    -        (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]),
    -        (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]),
    -    ] {
    -        let n: Vec<_> = to_nibbles(&bytes).collect();
    -        assert_eq!(n, nibbles);
    -    }
    -}
    -
    -
    \ No newline at end of file diff --git a/docs/src/firewood/storage.rs.html b/docs/src/firewood/storage.rs.html deleted file mode 100644 index 1c9dc5c2b653..000000000000 --- a/docs/src/firewood/storage.rs.html +++ /dev/null @@ -1,2416 +0,0 @@ -storage.rs - source
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -20
    -21
    -22
    -23
    -24
    -25
    -26
    -27
    -28
    -29
    -30
    -31
    -32
    -33
    -34
    -35
    -36
    -37
    -38
    -39
    -40
    -41
    -42
    -43
    -44
    -45
    -46
    -47
    -48
    -49
    -50
    -51
    -52
    -53
    -54
    -55
    -56
    -57
    -58
    -59
    -60
    -61
    -62
    -63
    -64
    -65
    -66
    -67
    -68
    -69
    -70
    -71
    -72
    -73
    -74
    -75
    -76
    -77
    -78
    -79
    -80
    -81
    -82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    -93
    -94
    -95
    -96
    -97
    -98
    -99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    -180
    -181
    -182
    -183
    -184
    -185
    -186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    -200
    -201
    -202
    -203
    -204
    -205
    -206
    -207
    -208
    -209
    -210
    -211
    -212
    -213
    -214
    -215
    -216
    -217
    -218
    -219
    -220
    -221
    -222
    -223
    -224
    -225
    -226
    -227
    -228
    -229
    -230
    -231
    -232
    -233
    -234
    -235
    -236
    -237
    -238
    -239
    -240
    -241
    -242
    -243
    -244
    -245
    -246
    -247
    -248
    -249
    -250
    -251
    -252
    -253
    -254
    -255
    -256
    -257
    -258
    -259
    -260
    -261
    -262
    -263
    -264
    -265
    -266
    -267
    -268
    -269
    -270
    -271
    -272
    -273
    -274
    -275
    -276
    -277
    -278
    -279
    -280
    -281
    -282
    -283
    -284
    -285
    -286
    -287
    -288
    -289
    -290
    -291
    -292
    -293
    -294
    -295
    -296
    -297
    -298
    -299
    -300
    -301
    -302
    -303
    -304
    -305
    -306
    -307
    -308
    -309
    -310
    -311
    -312
    -313
    -314
    -315
    -316
    -317
    -318
    -319
    -320
    -321
    -322
    -323
    -324
    -325
    -326
    -327
    -328
    -329
    -330
    -331
    -332
    -333
    -334
    -335
    -336
    -337
    -338
    -339
    -340
    -341
    -342
    -343
    -344
    -345
    -346
    -347
    -348
    -349
    -350
    -351
    -352
    -353
    -354
    -355
    -356
    -357
    -358
    -359
    -360
    -361
    -362
    -363
    -364
    -365
    -366
    -367
    -368
    -369
    -370
    -371
    -372
    -373
    -374
    -375
    -376
    -377
    -378
    -379
    -380
    -381
    -382
    -383
    -384
    -385
    -386
    -387
    -388
    -389
    -390
    -391
    -392
    -393
    -394
    -395
    -396
    -397
    -398
    -399
    -400
    -401
    -402
    -403
    -404
    -405
    -406
    -407
    -408
    -409
    -410
    -411
    -412
    -413
    -414
    -415
    -416
    -417
    -418
    -419
    -420
    -421
    -422
    -423
    -424
    -425
    -426
    -427
    -428
    -429
    -430
    -431
    -432
    -433
    -434
    -435
    -436
    -437
    -438
    -439
    -440
    -441
    -442
    -443
    -444
    -445
    -446
    -447
    -448
    -449
    -450
    -451
    -452
    -453
    -454
    -455
    -456
    -457
    -458
    -459
    -460
    -461
    -462
    -463
    -464
    -465
    -466
    -467
    -468
    -469
    -470
    -471
    -472
    -473
    -474
    -475
    -476
    -477
    -478
    -479
    -480
    -481
    -482
    -483
    -484
    -485
    -486
    -487
    -488
    -489
    -490
    -491
    -492
    -493
    -494
    -495
    -496
    -497
    -498
    -499
    -500
    -501
    -502
    -503
    -504
    -505
    -506
    -507
    -508
    -509
    -510
    -511
    -512
    -513
    -514
    -515
    -516
    -517
    -518
    -519
    -520
    -521
    -522
    -523
    -524
    -525
    -526
    -527
    -528
    -529
    -530
    -531
    -532
    -533
    -534
    -535
    -536
    -537
    -538
    -539
    -540
    -541
    -542
    -543
    -544
    -545
    -546
    -547
    -548
    -549
    -550
    -551
    -552
    -553
    -554
    -555
    -556
    -557
    -558
    -559
    -560
    -561
    -562
    -563
    -564
    -565
    -566
    -567
    -568
    -569
    -570
    -571
    -572
    -573
    -574
    -575
    -576
    -577
    -578
    -579
    -580
    -581
    -582
    -583
    -584
    -585
    -586
    -587
    -588
    -589
    -590
    -591
    -592
    -593
    -594
    -595
    -596
    -597
    -598
    -599
    -600
    -601
    -602
    -603
    -604
    -605
    -606
    -607
    -608
    -609
    -610
    -611
    -612
    -613
    -614
    -615
    -616
    -617
    -618
    -619
    -620
    -621
    -622
    -623
    -624
    -625
    -626
    -627
    -628
    -629
    -630
    -631
    -632
    -633
    -634
    -635
    -636
    -637
    -638
    -639
    -640
    -641
    -642
    -643
    -644
    -645
    -646
    -647
    -648
    -649
    -650
    -651
    -652
    -653
    -654
    -655
    -656
    -657
    -658
    -659
    -660
    -661
    -662
    -663
    -664
    -665
    -666
    -667
    -668
    -669
    -670
    -671
    -672
    -673
    -674
    -675
    -676
    -677
    -678
    -679
    -680
    -681
    -682
    -683
    -684
    -685
    -686
    -687
    -688
    -689
    -690
    -691
    -692
    -693
    -694
    -695
    -696
    -697
    -698
    -699
    -700
    -701
    -702
    -703
    -704
    -705
    -706
    -707
    -708
    -709
    -710
    -711
    -712
    -713
    -714
    -715
    -716
    -717
    -718
    -719
    -720
    -721
    -722
    -723
    -724
    -725
    -726
    -727
    -728
    -729
    -730
    -731
    -732
    -733
    -734
    -735
    -736
    -737
    -738
    -739
    -740
    -741
    -742
    -743
    -744
    -745
    -746
    -747
    -748
    -749
    -750
    -751
    -752
    -753
    -754
    -755
    -756
    -757
    -758
    -759
    -760
    -761
    -762
    -763
    -764
    -765
    -766
    -767
    -768
    -769
    -770
    -771
    -772
    -773
    -774
    -775
    -776
    -777
    -778
    -779
    -780
    -781
    -782
    -783
    -784
    -785
    -786
    -787
    -788
    -789
    -790
    -791
    -792
    -793
    -794
    -795
    -796
    -797
    -798
    -799
    -800
    -801
    -802
    -803
    -804
    -805
    -806
    -807
    -808
    -809
    -810
    -811
    -812
    -813
    -814
    -815
    -816
    -817
    -818
    -819
    -820
    -821
    -822
    -823
    -824
    -825
    -826
    -827
    -828
    -829
    -830
    -831
    -832
    -833
    -834
    -835
    -836
    -837
    -838
    -839
    -840
    -841
    -842
    -843
    -844
    -845
    -846
    -847
    -848
    -849
    -850
    -851
    -852
    -853
    -854
    -855
    -856
    -857
    -858
    -859
    -860
    -861
    -862
    -863
    -864
    -865
    -866
    -867
    -868
    -869
    -870
    -871
    -872
    -873
    -874
    -875
    -876
    -877
    -878
    -879
    -880
    -881
    -882
    -883
    -884
    -885
    -886
    -887
    -888
    -889
    -890
    -891
    -892
    -893
    -894
    -895
    -896
    -897
    -898
    -899
    -900
    -901
    -902
    -903
    -904
    -905
    -906
    -907
    -908
    -909
    -910
    -911
    -912
    -913
    -914
    -915
    -916
    -917
    -918
    -919
    -920
    -921
    -922
    -923
    -924
    -925
    -926
    -927
    -928
    -929
    -930
    -931
    -932
    -933
    -934
    -935
    -936
    -937
    -938
    -939
    -940
    -941
    -942
    -943
    -944
    -945
    -946
    -947
    -948
    -949
    -950
    -951
    -952
    -953
    -954
    -955
    -956
    -957
    -958
    -959
    -960
    -961
    -962
    -963
    -964
    -965
    -966
    -967
    -968
    -969
    -970
    -971
    -972
    -973
    -974
    -975
    -976
    -977
    -978
    -979
    -980
    -981
    -982
    -983
    -984
    -985
    -986
    -987
    -988
    -989
    -990
    -991
    -992
    -993
    -994
    -995
    -996
    -997
    -998
    -999
    -1000
    -1001
    -1002
    -1003
    -1004
    -1005
    -1006
    -1007
    -1008
    -1009
    -1010
    -1011
    -1012
    -1013
    -1014
    -1015
    -1016
    -1017
    -1018
    -1019
    -1020
    -1021
    -1022
    -1023
    -1024
    -1025
    -1026
    -1027
    -1028
    -1029
    -1030
    -1031
    -1032
    -1033
    -1034
    -1035
    -1036
    -1037
    -1038
    -1039
    -1040
    -1041
    -1042
    -1043
    -1044
    -1045
    -1046
    -1047
    -1048
    -1049
    -1050
    -1051
    -1052
    -1053
    -1054
    -1055
    -1056
    -1057
    -1058
    -1059
    -1060
    -1061
    -1062
    -1063
    -1064
    -1065
    -1066
    -1067
    -1068
    -1069
    -1070
    -1071
    -1072
    -1073
    -1074
    -1075
    -1076
    -1077
    -1078
    -1079
    -1080
    -1081
    -1082
    -1083
    -1084
    -1085
    -1086
    -1087
    -1088
    -1089
    -1090
    -1091
    -1092
    -1093
    -1094
    -1095
    -1096
    -1097
    -1098
    -1099
    -1100
    -1101
    -1102
    -1103
    -1104
    -1105
    -1106
    -1107
    -1108
    -1109
    -1110
    -1111
    -1112
    -1113
    -1114
    -1115
    -1116
    -1117
    -1118
    -1119
    -1120
    -1121
    -1122
    -1123
    -1124
    -1125
    -1126
    -1127
    -1128
    -1129
    -1130
    -1131
    -1132
    -1133
    -1134
    -1135
    -1136
    -1137
    -1138
    -1139
    -1140
    -1141
    -1142
    -1143
    -1144
    -1145
    -1146
    -1147
    -1148
    -1149
    -1150
    -1151
    -1152
    -1153
    -1154
    -1155
    -1156
    -1157
    -1158
    -1159
    -1160
    -1161
    -1162
    -1163
    -1164
    -1165
    -1166
    -1167
    -1168
    -1169
    -1170
    -1171
    -1172
    -1173
    -1174
    -1175
    -1176
    -1177
    -1178
    -1179
    -1180
    -1181
    -1182
    -1183
    -1184
    -1185
    -1186
    -1187
    -1188
    -1189
    -1190
    -1191
    -1192
    -1193
    -1194
    -1195
    -1196
    -1197
    -1198
    -1199
    -1200
    -1201
    -1202
    -1203
    -1204
    -1205
    -1206
    -1207
    -
    // TODO: try to get rid of the use `RefCell` in this file
    -
    -use std::cell::{RefCell, RefMut};
    -use std::collections::HashMap;
    -use std::fmt;
    -use std::num::NonZeroUsize;
    -use std::ops::Deref;
    -use std::rc::Rc;
    -use std::sync::Arc;
    -
    -use aiofut::{AIOBuilder, AIOManager};
    -use growthring::{
    -    wal::{RecoverPolicy, WALLoader, WALWriter},
    -    WALStoreAIO,
    -};
    -use nix::fcntl::{flock, FlockArg};
    -use shale::{MemStore, MemView, SpaceID};
    -use tokio::sync::{mpsc, oneshot, Mutex, Semaphore};
    -use typed_builder::TypedBuilder;
    -
    -use crate::file::{Fd, File};
    -
    -pub(crate) const PAGE_SIZE_NBIT: u64 = 12;
    -pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT;
    -pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1;
    -
    -pub trait MemStoreR {
    -    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>>;
    -    fn id(&self) -> SpaceID;
    -}
    -
    -type Page = [u8; PAGE_SIZE as usize];
    -
    -#[derive(Debug)]
    -pub struct SpaceWrite {
    -    offset: u64,
    -    data: Box<[u8]>,
    -}
    -
    -#[derive(Debug)]
    -pub struct Ash {
    -    pub old: Vec<SpaceWrite>,
    -    pub new: Vec<Box<[u8]>>,
    -}
    -
    -impl Ash {
    -    fn new() -> Self {
    -        Self {
    -            old: Vec::new(),
    -            new: Vec::new(),
    -        }
    -    }
    -}
    -
    -#[derive(Debug)]
    -pub struct AshRecord(pub HashMap<SpaceID, Ash>);
    -
    -impl growthring::wal::Record for AshRecord {
    -    fn serialize(&self) -> growthring::wal::WALBytes {
    -        let mut bytes = Vec::new();
    -        bytes.extend((self.0.len() as u64).to_le_bytes());
    -        for (space_id, w) in self.0.iter() {
    -            bytes.extend((*space_id).to_le_bytes());
    -            bytes.extend((w.old.len() as u32).to_le_bytes());
    -            for (sw_old, sw_new) in w.old.iter().zip(w.new.iter()) {
    -                bytes.extend(sw_old.offset.to_le_bytes());
    -                bytes.extend((sw_old.data.len() as u64).to_le_bytes());
    -                bytes.extend(&*sw_old.data);
    -                bytes.extend(&**sw_new);
    -            }
    -        }
    -        bytes.into()
    -    }
    -}
    -
    -impl AshRecord {
    -    #[allow(clippy::boxed_local)]
    -    fn deserialize(raw: growthring::wal::WALBytes) -> Self {
    -        let mut r = &raw[..];
    -        let len = u64::from_le_bytes(r[..8].try_into().unwrap());
    -        r = &r[8..];
    -        let writes = (0..len)
    -            .map(|_| {
    -                let space_id = u8::from_le_bytes(r[..1].try_into().unwrap());
    -                let wlen = u32::from_le_bytes(r[1..5].try_into().unwrap());
    -                r = &r[5..];
    -                let mut old = Vec::new();
    -                let mut new = Vec::new();
    -                for _ in 0..wlen {
    -                    let offset = u64::from_le_bytes(r[..8].try_into().unwrap());
    -                    let data_len = u64::from_le_bytes(r[8..16].try_into().unwrap());
    -                    r = &r[16..];
    -                    let old_write = SpaceWrite {
    -                        offset,
    -                        data: r[..data_len as usize].into(),
    -                    };
    -                    r = &r[data_len as usize..];
    -                    let new_data: Box<[u8]> = r[..data_len as usize].into();
    -                    r = &r[data_len as usize..];
    -                    old.push(old_write);
    -                    new.push(new_data);
    -                }
    -                (space_id, Ash { old, new })
    -            })
    -            .collect();
    -        Self(writes)
    -    }
    -}
    -
    -/// Basic copy-on-write item in the linear storage space for multi-versioning.
    -pub struct DeltaPage(u64, Box<Page>);
    -
    -impl DeltaPage {
    -    #[inline(always)]
    -    fn offset(&self) -> u64 {
    -        self.0 << PAGE_SIZE_NBIT
    -    }
    -
    -    #[inline(always)]
    -    fn data(&self) -> &[u8] {
    -        self.1.as_ref()
    -    }
    -
    -    #[inline(always)]
    -    fn data_mut(&mut self) -> &mut [u8] {
    -        self.1.as_mut()
    -    }
    -}
    -
    -#[derive(Default)]
    -pub struct StoreDelta(Vec<DeltaPage>);
    -
    -impl fmt::Debug for StoreDelta {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
    -        write!(f, "<StoreDelta>")
    -    }
    -}
    -
    -impl Deref for StoreDelta {
    -    type Target = [DeltaPage];
    -    fn deref(&self) -> &[DeltaPage] {
    -        &self.0
    -    }
    -}
    -
    -impl StoreDelta {
    -    pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self {
    -        let mut deltas = Vec::new();
    -        let mut widx: Vec<_> = (0..writes.len()).filter(|i| writes[*i].data.len() > 0).collect();
    -        if widx.is_empty() {
    -            // the writes are all empty
    -            return Self(deltas)
    -        }
    -
    -        // sort by the starting point
    -        widx.sort_by_key(|i| writes[*i].offset);
    -
    -        let mut witer = widx.into_iter();
    -        let w0 = &writes[witer.next().unwrap()];
    -        let mut head = w0.offset >> PAGE_SIZE_NBIT;
    -        let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT;
    -
    -        macro_rules! create_dirty_pages {
    -            ($l: expr, $r: expr) => {
    -                for p in $l..=$r {
    -                    let off = p << PAGE_SIZE_NBIT;
    -                    deltas.push(DeltaPage(
    -                        p,
    -                        Box::new(src.get_slice(off, PAGE_SIZE).unwrap().try_into().unwrap()),
    -                    ));
    -                }
    -            };
    -        }
    -
    -        for i in witer {
    -            let w = &writes[i];
    -            let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT;
    -            let wp = w.offset >> PAGE_SIZE_NBIT;
    -            if wp > tail {
    -                // all following writes won't go back past w.offset, so the previous continous
    -                // write area is determined
    -                create_dirty_pages!(head, tail);
    -                head = wp;
    -            }
    -            tail = std::cmp::max(tail, ep)
    -        }
    -        create_dirty_pages!(head, tail);
    -
    -        let psize = PAGE_SIZE as usize;
    -        for w in writes.iter() {
    -            let mut l = 0;
    -            let mut r = deltas.len();
    -            while r - l > 1 {
    -                let mid = (l + r) >> 1;
    -                (*if w.offset < deltas[mid].offset() {
    -                    &mut r
    -                } else {
    -                    &mut l
    -                }) = mid;
    -            }
    -            let off = (w.offset - deltas[l].offset()) as usize;
    -            let len = std::cmp::min(psize - off, w.data.len());
    -            deltas[l].data_mut()[off..off + len].copy_from_slice(&w.data[..len]);
    -            let mut data = &w.data[len..];
    -            while data.len() >= psize {
    -                l += 1;
    -                deltas[l].data_mut().copy_from_slice(&data[..psize]);
    -                data = &data[psize..];
    -            }
    -            if !data.is_empty() {
    -                l += 1;
    -                deltas[l].data_mut()[..data.len()].copy_from_slice(data);
    -            }
    -        }
    -        Self(deltas)
    -    }
    -
    -    pub fn len(&self) -> usize {
    -        self.0.len()
    -    }
    -}
    -
    -pub struct StoreRev {
    -    prev: RefCell<Rc<dyn MemStoreR>>,
    -    delta: StoreDelta,
    -}
    -
    -impl fmt::Debug for StoreRev {
    -    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    -        write!(f, "<StoreRev")?;
    -        for d in self.delta.iter() {
    -            write!(f, " 0x{:x}", d.0)?;
    -        }
    -        writeln!(f, ">")
    -    }
    -}
    -
    -impl MemStoreR for StoreRev {
    -    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>> {
    -        let prev = self.prev.borrow();
    -        let mut start = offset;
    -        let end = start + length;
    -        let delta = &self.delta;
    -        let mut l = 0;
    -        let mut r = delta.len();
    -        // no dirty page, before or after all dirty pages
    -        if r == 0 {
    -            return prev.get_slice(start, end - start)
    -        }
    -        // otherwise, some dirty pages are covered by the range
    -        while r - l > 1 {
    -            let mid = (l + r) >> 1;
    -            (*if start < delta[mid].offset() { &mut r } else { &mut l }) = mid;
    -        }
    -        if start >= delta[l].offset() + PAGE_SIZE {
    -            l += 1
    -        }
    -        if l >= delta.len() || end < delta[l].offset() {
    -            return prev.get_slice(start, end - start)
    -        }
    -        let mut data = Vec::new();
    -        let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE);
    -        if start < delta[l].offset() {
    -            data.extend(prev.get_slice(start, delta[l].offset() - start)?);
    -            data.extend(&delta[l].data()[..p_off as usize]);
    -        } else {
    -            data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]);
    -        };
    -        start = delta[l].offset() + p_off;
    -        while start < end {
    -            l += 1;
    -            if l >= delta.len() || end < delta[l].offset() {
    -                data.extend(prev.get_slice(start, end - start)?);
    -                break
    -            }
    -            if delta[l].offset() > start {
    -                data.extend(prev.get_slice(start, delta[l].offset() - start)?);
    -            }
    -            if end < delta[l].offset() + PAGE_SIZE {
    -                data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]);
    -                break
    -            }
    -            data.extend(delta[l].data());
    -            start = delta[l].offset() + PAGE_SIZE;
    -        }
    -        assert!(data.len() == length as usize);
    -        Some(data)
    -    }
    -
    -    fn id(&self) -> SpaceID {
    -        self.prev.borrow().id()
    -    }
    -}
    -
    -#[derive(Clone, Debug)]
    -pub struct StoreRevShared(Rc<StoreRev>);
    -
    -impl StoreRevShared {
    -    pub fn from_ash(prev: Rc<dyn MemStoreR>, writes: &[SpaceWrite]) -> Self {
    -        let delta = StoreDelta::new(prev.as_ref(), writes);
    -        let prev = RefCell::new(prev);
    -        Self(Rc::new(StoreRev { prev, delta }))
    -    }
    -
    -    pub fn from_delta(prev: Rc<dyn MemStoreR>, delta: StoreDelta) -> Self {
    -        let prev = RefCell::new(prev);
    -        Self(Rc::new(StoreRev { prev, delta }))
    -    }
    -
    -    pub fn set_prev(&mut self, prev: Rc<dyn MemStoreR>) {
    -        *self.0.prev.borrow_mut() = prev
    -    }
    -
    -    pub fn inner(&self) -> &Rc<StoreRev> {
    -        &self.0
    -    }
    -}
    -
    -impl MemStore for StoreRevShared {
    -    fn get_view(&self, offset: u64, length: u64) -> Option<Box<dyn MemView>> {
    -        let data = self.0.get_slice(offset, length)?;
    -        Some(Box::new(StoreRef { data }))
    -    }
    -
    -    fn get_shared(&self) -> Option<Box<dyn Deref<Target = dyn MemStore>>> {
    -        Some(Box::new(StoreShared(self.clone())))
    -    }
    -
    -    fn write(&self, _offset: u64, _change: &[u8]) {
    -        // StoreRevShared is a read-only view version of MemStore
    -        // Writes could be induced by lazy hashing and we can just ignore those
    -    }
    -
    -    fn id(&self) -> SpaceID {
    -        <StoreRev as MemStoreR>::id(&self.0)
    -    }
    -}
    -
    -struct StoreRef {
    -    data: Vec<u8>,
    -}
    -
    -impl Deref for StoreRef {
    -    type Target = [u8];
    -    fn deref(&self) -> &[u8] {
    -        &self.data
    -    }
    -}
    -
    -impl MemView for StoreRef {}
    -
    -struct StoreShared<S: Clone + MemStore>(S);
    -
    -impl<S: Clone + MemStore + 'static> Deref for StoreShared<S> {
    -    type Target = dyn MemStore;
    -    fn deref(&self) -> &(dyn MemStore + 'static) {
    -        &self.0
    -    }
    -}
    -
    -struct StoreRevMutDelta {
    -    pages: HashMap<u64, Box<Page>>,
    -    plain: Ash,
    -}
    -
    -#[derive(Clone)]
    -pub struct StoreRevMut {
    -    prev: Rc<dyn MemStoreR>,
    -    deltas: Rc<RefCell<StoreRevMutDelta>>,
    -}
    -
    -impl StoreRevMut {
    -    pub fn new(prev: Rc<dyn MemStoreR>) -> Self {
    -        Self {
    -            prev,
    -            deltas: Rc::new(RefCell::new(StoreRevMutDelta {
    -                pages: HashMap::new(),
    -                plain: Ash::new(),
    -            })),
    -        }
    -    }
    -
    -    fn get_page_mut(&self, pid: u64) -> RefMut<[u8]> {
    -        let mut deltas = self.deltas.borrow_mut();
    -        if deltas.pages.get(&pid).is_none() {
    -            let page = Box::new(
    -                self.prev
    -                    .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE)
    -                    .unwrap()
    -                    .try_into()
    -                    .unwrap(),
    -            );
    -            deltas.pages.insert(pid, page);
    -        }
    -        RefMut::map(deltas, |e| &mut e.pages.get_mut(&pid).unwrap()[..])
    -    }
    -
    -    pub fn take_delta(&self) -> (StoreDelta, Ash) {
    -        let mut pages = Vec::new();
    -        let deltas = std::mem::replace(
    -            &mut *self.deltas.borrow_mut(),
    -            StoreRevMutDelta {
    -                pages: HashMap::new(),
    -                plain: Ash::new(),
    -            },
    -        );
    -        for (pid, page) in deltas.pages.into_iter() {
    -            pages.push(DeltaPage(pid, page));
    -        }
    -        pages.sort_by_key(|p| p.0);
    -        (StoreDelta(pages), deltas.plain)
    -    }
    -}
    -
    -impl MemStore for StoreRevMut {
    -    fn get_view(&self, offset: u64, length: u64) -> Option<Box<dyn MemView>> {
    -        let data = if length == 0 {
    -            Vec::new()
    -        } else {
    -            let end = offset + length - 1;
    -            let s_pid = offset >> PAGE_SIZE_NBIT;
    -            let s_off = (offset & PAGE_MASK) as usize;
    -            let e_pid = end >> PAGE_SIZE_NBIT;
    -            let e_off = (end & PAGE_MASK) as usize;
    -            let deltas = &self.deltas.borrow().pages;
    -            if s_pid == e_pid {
    -                match deltas.get(&s_pid) {
    -                    Some(p) => p[s_off..e_off + 1].to_vec(),
    -                    None => self.prev.get_slice(offset, length)?,
    -                }
    -            } else {
    -                let mut data = match deltas.get(&s_pid) {
    -                    Some(p) => p[s_off..].to_vec(),
    -                    None => self.prev.get_slice(offset, PAGE_SIZE - s_off as u64)?,
    -                };
    -                for p in s_pid + 1..e_pid {
    -                    match deltas.get(&p) {
    -                        Some(p) => data.extend(**p),
    -                        None => data.extend(&self.prev.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?),
    -                    };
    -                }
    -                match deltas.get(&e_pid) {
    -                    Some(p) => data.extend(&p[..e_off + 1]),
    -                    None => data.extend(self.prev.get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?),
    -                }
    -                data
    -            }
    -        };
    -        Some(Box::new(StoreRef { data }))
    -    }
    -
    -    fn get_shared(&self) -> Option<Box<dyn Deref<Target = dyn MemStore>>> {
    -        Some(Box::new(StoreShared(self.clone())))
    -    }
    -
    -    fn write(&self, offset: u64, mut change: &[u8]) {
    -        let length = change.len() as u64;
    -        let end = offset + length - 1;
    -        let s_pid = offset >> PAGE_SIZE_NBIT;
    -        let s_off = (offset & PAGE_MASK) as usize;
    -        let e_pid = end >> PAGE_SIZE_NBIT;
    -        let e_off = (end & PAGE_MASK) as usize;
    -        let mut old: Vec<u8> = Vec::new();
    -        let new: Box<[u8]> = change.into();
    -        if s_pid == e_pid {
    -            let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1];
    -            old.extend(&*slice);
    -            slice.copy_from_slice(change)
    -        } else {
    -            let len = PAGE_SIZE as usize - s_off;
    -            {
    -                let slice = &mut self.get_page_mut(s_pid)[s_off..];
    -                old.extend(&*slice);
    -                slice.copy_from_slice(&change[..len]);
    -            }
    -            change = &change[len..];
    -            for p in s_pid + 1..e_pid {
    -                let mut slice = self.get_page_mut(p);
    -                old.extend(&*slice);
    -                slice.copy_from_slice(&change[..PAGE_SIZE as usize]);
    -                change = &change[PAGE_SIZE as usize..];
    -            }
    -            let slice = &mut self.get_page_mut(e_pid)[..e_off + 1];
    -            old.extend(&*slice);
    -            slice.copy_from_slice(change);
    -        }
    -        let plain = &mut self.deltas.borrow_mut().plain;
    -        assert!(old.len() == new.len());
    -        plain.old.push(SpaceWrite {
    -            offset,
    -            data: old.into(),
    -        });
    -        plain.new.push(new);
    -    }
    -
    -    fn id(&self) -> SpaceID {
    -        self.prev.id()
    -    }
    -}
    -
    -#[cfg(test)]
    -#[derive(Clone)]
    -pub struct ZeroStore(Rc<()>);
    -
    -#[cfg(test)]
    -impl ZeroStore {
    -    pub fn new() -> Self {
    -        Self(Rc::new(()))
    -    }
    -}
    -
    -#[cfg(test)]
    -impl MemStoreR for ZeroStore {
    -    fn get_slice(&self, _: u64, length: u64) -> Option<Vec<u8>> {
    -        Some(vec![0; length as usize])
    -    }
    -
    -    fn id(&self) -> SpaceID {
    -        shale::INVALID_SPACE_ID
    -    }
    -}
    -
    -#[test]
    -fn test_from_ash() {
    -    use rand::{rngs::StdRng, Rng, SeedableRng};
    -    let mut rng = StdRng::seed_from_u64(42);
    -    let min = rng.gen_range(0..2 * PAGE_SIZE);
    -    let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE);
    -    for _ in 0..2000 {
    -        let n = 20;
    -        let mut canvas = Vec::new();
    -        canvas.resize((max - min) as usize, 0);
    -        let mut writes: Vec<_> = Vec::new();
    -        for _ in 0..n {
    -            let l = rng.gen_range(min..max);
    -            let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max));
    -            let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect();
    -            for (idx, byte) in (l..r).zip(data.iter()) {
    -                canvas[(idx - min) as usize] = *byte;
    -            }
    -            println!("[0x{l:x}, 0x{r:x})");
    -            writes.push(SpaceWrite { offset: l, data });
    -        }
    -        let z = Rc::new(ZeroStore::new());
    -        let rev = StoreRevShared::from_ash(z, &writes);
    -        println!("{rev:?}");
    -        assert_eq!(&**rev.get_view(min, max - min).unwrap(), &canvas);
    -        for _ in 0..2 * n {
    -            let l = rng.gen_range(min..max);
    -            let r = rng.gen_range(l + 1..max);
    -            assert_eq!(
    -                &**rev.get_view(l, r - l).unwrap(),
    -                &canvas[(l - min) as usize..(r - min) as usize]
    -            );
    -        }
    -    }
    -}
    -
    -#[derive(TypedBuilder)]
    -pub struct StoreConfig {
    -    ncached_pages: usize,
    -    ncached_files: usize,
    -    #[builder(default = 22)] // 4MB file by default
    -    file_nbit: u64,
    -    space_id: SpaceID,
    -    rootfd: Fd,
    -}
    -
    -struct CachedSpaceInner {
    -    cached_pages: lru::LruCache<u64, Box<Page>>,
    -    pinned_pages: HashMap<u64, (usize, Box<Page>)>,
    -    files: Arc<FilePool>,
    -    disk_buffer: DiskBufferRequester,
    -}
    -
    -#[derive(Clone)]
    -pub struct CachedSpace {
    -    inner: Rc<RefCell<CachedSpaceInner>>,
    -    space_id: SpaceID,
    -}
    -
    -impl CachedSpace {
    -    pub fn new(cfg: &StoreConfig) -> Result<Self, StoreError> {
    -        let space_id = cfg.space_id;
    -        let files = Arc::new(FilePool::new(cfg)?);
    -        Ok(Self {
    -            inner: Rc::new(RefCell::new(CachedSpaceInner {
    -                cached_pages: lru::LruCache::new(NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size")),
    -                pinned_pages: HashMap::new(),
    -                files,
    -                disk_buffer: DiskBufferRequester::default(),
    -            })),
    -            space_id,
    -        })
    -    }
    -
    -    /// Apply `delta` to the store and return the StoreDelta that can undo this change.
    -    pub fn update(&self, delta: &StoreDelta) -> Option<StoreDelta> {
    -        let mut pages = Vec::new();
    -        for DeltaPage(pid, page) in &delta.0 {
    -            let data = self.inner.borrow_mut().pin_page(self.space_id, *pid).ok()?;
    -            // save the original data
    -            pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap())));
    -            // apply the change
    -            data.copy_from_slice(page.as_ref());
    -        }
    -        Some(StoreDelta(pages))
    -    }
    -}
    -
    -impl CachedSpaceInner {
    -    fn fetch_page(&mut self, space_id: SpaceID, pid: u64) -> Result<Box<Page>, StoreError> {
    -        if let Some(p) = self.disk_buffer.get_page(space_id, pid) {
    -            return Ok(Box::new(*p))
    -        }
    -        let file_nbit = self.files.get_file_nbit();
    -        let file_size = 1 << file_nbit;
    -        let poff = pid << PAGE_SIZE_NBIT;
    -        let file = self.files.get_file(poff >> file_nbit)?;
    -        let mut page: Page = [0; PAGE_SIZE as usize];
    -        nix::sys::uio::pread(file.get_fd(), &mut page, (poff & (file_size - 1)) as nix::libc::off_t)
    -            .map_err(StoreError::System)?;
    -        Ok(Box::new(page))
    -    }
    -
    -    fn pin_page(&mut self, space_id: SpaceID, pid: u64) -> Result<&'static mut [u8], StoreError> {
    -        let base = match self.pinned_pages.get_mut(&pid) {
    -            Some(mut e) => {
    -                e.0 += 1;
    -                e.1.as_mut_ptr()
    -            }
    -            None => {
    -                let mut page = match self.cached_pages.pop(&pid) {
    -                    Some(p) => p,
    -                    None => self.fetch_page(space_id, pid)?,
    -                };
    -                let ptr = page.as_mut_ptr();
    -                self.pinned_pages.insert(pid, (1, page));
    -                ptr
    -            }
    -        };
    -        Ok(unsafe { std::slice::from_raw_parts_mut(base, PAGE_SIZE as usize) })
    -    }
    -
    -    fn unpin_page(&mut self, pid: u64) {
    -        use std::collections::hash_map::Entry::*;
    -        let page = match self.pinned_pages.entry(pid) {
    -            Occupied(mut e) => {
    -                let cnt = &mut e.get_mut().0;
    -                assert!(*cnt > 0);
    -                *cnt -= 1;
    -                if *cnt == 0 {
    -                    e.remove().1
    -                } else {
    -                    return
    -                }
    -            }
    -            _ => unreachable!(),
    -        };
    -        self.cached_pages.put(pid, page);
    -    }
    -}
    -
    -struct PageRef {
    -    pid: u64,
    -    data: &'static mut [u8],
    -    store: CachedSpace,
    -}
    -
    -impl std::ops::Deref for PageRef {
    -    type Target = [u8];
    -    fn deref(&self) -> &[u8] {
    -        self.data
    -    }
    -}
    -
    -impl std::ops::DerefMut for PageRef {
    -    fn deref_mut(&mut self) -> &mut [u8] {
    -        self.data
    -    }
    -}
    -
    -impl PageRef {
    -    fn new(pid: u64, store: &CachedSpace) -> Option<Self> {
    -        Some(Self {
    -            pid,
    -            data: store.inner.borrow_mut().pin_page(store.space_id, pid).ok()?,
    -            store: store.clone(),
    -        })
    -    }
    -}
    -
    -impl Drop for PageRef {
    -    fn drop(&mut self) {
    -        self.store.inner.borrow_mut().unpin_page(self.pid);
    -    }
    -}
    -
    -impl MemStoreR for CachedSpace {
    -    fn get_slice(&self, offset: u64, length: u64) -> Option<Vec<u8>> {
    -        if length == 0 {
    -            return Some(Default::default())
    -        }
    -        let end = offset + length - 1;
    -        let s_pid = offset >> PAGE_SIZE_NBIT;
    -        let s_off = (offset & PAGE_MASK) as usize;
    -        let e_pid = end >> PAGE_SIZE_NBIT;
    -        let e_off = (end & PAGE_MASK) as usize;
    -        if s_pid == e_pid {
    -            return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec())
    -        }
    -        let mut data: Vec<u8> = Vec::new();
    -        {
    -            data.extend(&PageRef::new(s_pid, self)?[s_off..]);
    -            for p in s_pid + 1..e_pid {
    -                data.extend(&PageRef::new(p, self)?[..]);
    -            }
    -            data.extend(&PageRef::new(e_pid, self)?[..e_off + 1]);
    -        }
    -        Some(data)
    -    }
    -
    -    fn id(&self) -> SpaceID {
    -        self.space_id
    -    }
    -}
    -
    -pub struct FilePool {
    -    files: parking_lot::Mutex<lru::LruCache<u64, Arc<File>>>,
    -    file_nbit: u64,
    -    rootfd: Fd,
    -}
    -
    -impl FilePool {
    -    fn new(cfg: &StoreConfig) -> Result<Self, StoreError> {
    -        let rootfd = cfg.rootfd;
    -        let file_nbit = cfg.file_nbit;
    -        let s = Self {
    -            files: parking_lot::Mutex::new(lru::LruCache::new(
    -                NonZeroUsize::new(cfg.ncached_files).expect("non-zero file num"),
    -            )),
    -            file_nbit,
    -            rootfd,
    -        };
    -        let f0 = s.get_file(0)?;
    -        if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() {
    -            return Err(StoreError::InitError("the store is busy".into()))
    -        }
    -        Ok(s)
    -    }
    -
    -    fn get_file(&self, fid: u64) -> Result<Arc<File>, StoreError> {
    -        let mut files = self.files.lock();
    -        let file_size = 1 << self.file_nbit;
    -        Ok(match files.get(&fid) {
    -            Some(f) => f.clone(),
    -            None => {
    -                files.put(
    -                    fid,
    -                    Arc::new(File::new(fid, file_size, self.rootfd).map_err(StoreError::System)?),
    -                );
    -                files.peek(&fid).unwrap().clone()
    -            }
    -        })
    -    }
    -
    -    fn get_file_nbit(&self) -> u64 {
    -        self.file_nbit
    -    }
    -}
    -
    -impl Drop for FilePool {
    -    fn drop(&mut self) {
    -        let f0 = self.get_file(0).unwrap();
    -        flock(f0.get_fd(), FlockArg::UnlockNonblock).ok();
    -        nix::unistd::close(self.rootfd).ok();
    -    }
    -}
    -
    -#[derive(Debug)]
    -pub struct BufferWrite {
    -    pub space_id: SpaceID,
    -    pub delta: StoreDelta,
    -}
    -
    -pub enum BufferCmd {
    -    InitWAL(Fd, String),
    -    WriteBatch(Vec<BufferWrite>, AshRecord),
    -    GetPage((SpaceID, u64), oneshot::Sender<Option<Arc<Page>>>),
    -    CollectAsh(usize, oneshot::Sender<Vec<AshRecord>>),
    -    RegCachedSpace(SpaceID, Arc<FilePool>),
    -    Shutdown,
    -}
    -
    -#[derive(TypedBuilder, Clone)]
    -pub struct WALConfig {
    -    #[builder(default = 22)] // 4MB WAL logs
    -    pub(crate) file_nbit: u64,
    -    #[builder(default = 15)] // 32KB
    -    pub(crate) block_nbit: u64,
    -    #[builder(default = 100)] // preserve a rolling window of 100 past commits
    -    pub(crate) max_revisions: u32,
    -}
    -
    -/// Config for the disk buffer.
    -#[derive(TypedBuilder, Clone)]
    -pub struct DiskBufferConfig {
    -    /// Maximum buffered disk buffer commands.
    -    #[builder(default = 4096)]
    -    pub(crate) max_buffered: usize,
    -    /// Maximum number of pending pages.
    -    #[builder(default = 65536)] // 256MB total size by default
    -    max_pending: usize,
    -    /// Maximum number of concurrent async I/O requests.
    -    #[builder(default = 1024)]
    -    max_aio_requests: u32,
    -    /// Maximum number of async I/O responses that it polls for at a time.
    -    #[builder(default = 128)]
    -    max_aio_response: u16,
    -    /// Maximum number of async I/O requests per submission.
    -    #[builder(default = 128)]
    -    max_aio_submit: usize,
    -    /// Maximum number of concurrent async I/O requests in WAL.
    -    #[builder(default = 256)]
    -    wal_max_aio_requests: usize,
    -    /// Maximum buffered WAL records.
    -    #[builder(default = 1024)]
    -    wal_max_buffered: usize,
    -    /// Maximum batched WAL records per write.
    -    #[builder(default = 4096)]
    -    wal_max_batch: usize,
    -}
    -
    -struct PendingPage {
    -    staging_data: Arc<Page>,
    -    file_nbit: u64,
    -    staging_notifiers: Vec<Rc<Semaphore>>,
    -    writing_notifiers: Vec<Rc<Semaphore>>,
    -}
    -
    -pub struct DiskBuffer {
    -    pending: HashMap<(SpaceID, u64), PendingPage>,
    -    inbound: mpsc::Receiver<BufferCmd>,
    -    fc_notifier: Option<oneshot::Sender<()>>,
    -    fc_blocker: Option<oneshot::Receiver<()>>,
    -    file_pools: [Option<Arc<FilePool>>; 255],
    -    aiomgr: AIOManager,
    -    local_pool: Rc<tokio::task::LocalSet>,
    -    task_id: u64,
    -    tasks: Rc<RefCell<HashMap<u64, Option<tokio::task::JoinHandle<()>>>>>,
    -    wal: Option<Rc<Mutex<WALWriter<WALStoreAIO>>>>,
    -    cfg: DiskBufferConfig,
    -    wal_cfg: WALConfig,
    -}
    -
    -impl DiskBuffer {
    -    pub fn new(inbound: mpsc::Receiver<BufferCmd>, cfg: &DiskBufferConfig, wal: &WALConfig) -> Option<Self> {
    -        const INIT: Option<Arc<FilePool>> = None;
    -        let aiomgr = AIOBuilder::default()
    -            .max_events(cfg.max_aio_requests)
    -            .max_nwait(cfg.max_aio_response)
    -            .max_nbatched(cfg.max_aio_submit)
    -            .build()
    -            .ok()?;
    -
    -        Some(Self {
    -            pending: HashMap::new(),
    -            cfg: cfg.clone(),
    -            inbound,
    -            fc_notifier: None,
    -            fc_blocker: None,
    -            file_pools: [INIT; 255],
    -            aiomgr,
    -            local_pool: Rc::new(tokio::task::LocalSet::new()),
    -            task_id: 0,
    -            tasks: Rc::new(RefCell::new(HashMap::new())),
    -            wal: None,
    -            wal_cfg: wal.clone(),
    -        })
    -    }
    -
    -    unsafe fn get_longlive_self(&mut self) -> &'static mut Self {
    -        std::mem::transmute::<&mut Self, &'static mut Self>(self)
    -    }
    -
    -    fn schedule_write(&mut self, page_key: (SpaceID, u64)) {
    -        let p = self.pending.get(&page_key).unwrap();
    -        let offset = page_key.1 << PAGE_SIZE_NBIT;
    -        let fid = offset >> p.file_nbit;
    -        let fmask = (1 << p.file_nbit) - 1;
    -        let file = self.file_pools[page_key.0 as usize]
    -            .as_ref()
    -            .unwrap()
    -            .get_file(fid)
    -            .unwrap();
    -        let fut = self
    -            .aiomgr
    -            .write(file.get_fd(), offset & fmask, Box::new(*p.staging_data), None);
    -        let s = unsafe { self.get_longlive_self() };
    -        self.start_task(async move {
    -            let (res, _) = fut.await;
    -            res.unwrap();
    -            s.finish_write(page_key);
    -        });
    -    }
    -
    -    fn finish_write(&mut self, page_key: (SpaceID, u64)) {
    -        use std::collections::hash_map::Entry::*;
    -        match self.pending.entry(page_key) {
    -            Occupied(mut e) => {
    -                let slot = e.get_mut();
    -                for notifier in std::mem::take(&mut slot.writing_notifiers) {
    -                    notifier.add_permits(1)
    -                }
    -                if slot.staging_notifiers.is_empty() {
    -                    e.remove();
    -                    if self.pending.len() < self.cfg.max_pending {
    -                        if let Some(notifier) = self.fc_notifier.take() {
    -                            notifier.send(()).unwrap();
    -                        }
    -                    }
    -                } else {
    -                    assert!(slot.writing_notifiers.is_empty());
    -                    std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers);
    -                    // write again
    -                    self.schedule_write(page_key);
    -                }
    -            }
    -            _ => unreachable!(),
    -        }
    -    }
    -
    -    async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), ()> {
    -        let mut aiobuilder = AIOBuilder::default();
    -        aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32);
    -        let aiomgr = aiobuilder.build().map_err(|_| ())?;
    -        let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)).map_err(|_| ())?;
    -        let mut loader = WALLoader::new();
    -        loader
    -            .file_nbit(self.wal_cfg.file_nbit)
    -            .block_nbit(self.wal_cfg.block_nbit)
    -            .recover_policy(RecoverPolicy::Strict);
    -        if self.wal.is_some() {
    -            // already initialized
    -            return Ok(())
    -        }
    -        let wal = loader
    -            .load(
    -                store,
    -                |raw, _| {
    -                    let batch = AshRecord::deserialize(raw);
    -                    for (space_id, Ash { old, new }) in batch.0 {
    -                        for (old, data) in old.into_iter().zip(new.into_iter()) {
    -                            let offset = old.offset;
    -                            let file_pool = self.file_pools[space_id as usize].as_ref().unwrap();
    -                            let file_nbit = file_pool.get_file_nbit();
    -                            let file_mask = (1 << file_nbit) - 1;
    -                            let fid = offset >> file_nbit;
    -                            nix::sys::uio::pwrite(
    -                                file_pool.get_file(fid).map_err(|_| ())?.get_fd(),
    -                                &data,
    -                                (offset & file_mask) as nix::libc::off_t,
    -                            )
    -                            .map_err(|_| ())?;
    -                        }
    -                    }
    -                    Ok(())
    -                },
    -                self.wal_cfg.max_revisions,
    -            )
    -            .await?;
    -        self.wal = Some(Rc::new(Mutex::new(wal)));
    -        Ok(())
    -    }
    -
    -    async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec<BufferWrite>, AshRecord)>) {
    -        use std::collections::hash_map::Entry::*;
    -        loop {
    -            let mut bwrites = Vec::new();
    -            let mut records = Vec::new();
    -
    -            if let Some((bw, ac)) = writes.recv().await {
    -                records.push(ac);
    -                bwrites.extend(bw);
    -            } else {
    -                break
    -            }
    -            while let Ok((bw, ac)) = writes.try_recv() {
    -                records.push(ac);
    -                bwrites.extend(bw);
    -                if records.len() >= self.cfg.wal_max_batch {
    -                    break
    -                }
    -            }
    -            // first write to WAL
    -            let ring_ids: Vec<_> = futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records))
    -                .await
    -                .into_iter()
    -                .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1)
    -                .collect();
    -            let sem = Rc::new(tokio::sync::Semaphore::new(0));
    -            let mut npermit = 0;
    -            for BufferWrite { space_id, delta } in bwrites {
    -                for w in delta.0 {
    -                    let page_key = (space_id, w.0);
    -                    match self.pending.entry(page_key) {
    -                        Occupied(mut e) => {
    -                            let e = e.get_mut();
    -                            e.staging_data = w.1.into();
    -                            e.staging_notifiers.push(sem.clone());
    -                            npermit += 1;
    -                        }
    -                        Vacant(e) => {
    -                            let file_nbit = self.file_pools[page_key.0 as usize].as_ref().unwrap().file_nbit;
    -                            e.insert(PendingPage {
    -                                staging_data: w.1.into(),
    -                                file_nbit,
    -                                staging_notifiers: Vec::new(),
    -                                writing_notifiers: vec![sem.clone()],
    -                            });
    -                            npermit += 1;
    -                            self.schedule_write(page_key);
    -                        }
    -                    }
    -                }
    -            }
    -            let wal = self.wal.as_ref().unwrap().clone();
    -            let max_revisions = self.wal_cfg.max_revisions;
    -            self.start_task(async move {
    -                let _ = sem.acquire_many(npermit).await.unwrap();
    -                wal.lock()
    -                    .await
    -                    .peel(ring_ids, max_revisions)
    -                    .await
    -                    .map_err(|_| "WAL errore while pruning")
    -                    .unwrap();
    -            });
    -            if self.pending.len() >= self.cfg.max_pending {
    -                let (tx, rx) = oneshot::channel();
    -                self.fc_notifier = Some(tx);
    -                self.fc_blocker = Some(rx);
    -            }
    -        }
    -    }
    -
    -    async fn process(&mut self, req: BufferCmd, wal_in: &mpsc::Sender<(Vec<BufferWrite>, AshRecord)>) -> bool {
    -        match req {
    -            BufferCmd::Shutdown => return false,
    -            BufferCmd::InitWAL(rootfd, waldir) => {
    -                if (self.init_wal(rootfd, waldir).await).is_err() {
    -                    panic!("cannot initialize from WAL")
    -                }
    -            }
    -            BufferCmd::GetPage(page_key, tx) => tx
    -                .send(self.pending.get(&page_key).map(|e| e.staging_data.clone()))
    -                .unwrap(),
    -            BufferCmd::WriteBatch(writes, wal_writes) => {
    -                wal_in.send((writes, wal_writes)).await.unwrap();
    -            }
    -            BufferCmd::CollectAsh(nrecords, tx) => {
    -                // wait to ensure writes are paused for WAL
    -                let ash = self
    -                    .wal
    -                    .as_ref()
    -                    .unwrap()
    -                    .clone()
    -                    .lock()
    -                    .await
    -                    .read_recent_records(nrecords, &RecoverPolicy::Strict)
    -                    .await
    -                    .unwrap()
    -                    .into_iter()
    -                    .map(AshRecord::deserialize)
    -                    .collect();
    -                tx.send(ash).unwrap();
    -            }
    -            BufferCmd::RegCachedSpace(space_id, files) => self.file_pools[space_id as usize] = Some(files),
    -        }
    -        true
    -    }
    -
    -    fn start_task<F: std::future::Future<Output = ()> + 'static>(&mut self, fut: F) {
    -        let task_id = self.task_id;
    -        self.task_id += 1;
    -        let tasks = self.tasks.clone();
    -        self.tasks.borrow_mut().insert(
    -            task_id,
    -            Some(self.local_pool.spawn_local(async move {
    -                fut.await;
    -                tasks.borrow_mut().remove(&task_id);
    -            })),
    -        );
    -    }
    -
    -    #[tokio::main(flavor = "current_thread")]
    -    pub async fn run(mut self) {
    -        let wal_in = {
    -            let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered);
    -            let s = unsafe { self.get_longlive_self() };
    -            self.start_task(s.run_wal_queue(rx));
    -            tx
    -        };
    -        self.local_pool
    -            .clone()
    -            .run_until(async {
    -                loop {
    -                    if let Some(fc) = self.fc_blocker.take() {
    -                        // flow control, wait until ready
    -                        fc.await.unwrap();
    -                    }
    -                    let req = self.inbound.recv().await.unwrap();
    -                    if !self.process(req, &wal_in).await {
    -                        break
    -                    }
    -                }
    -                drop(wal_in);
    -                let handles: Vec<_> = self
    -                    .tasks
    -                    .borrow_mut()
    -                    .iter_mut()
    -                    .map(|(_, task)| task.take().unwrap())
    -                    .collect();
    -                for h in handles {
    -                    h.await.unwrap();
    -                }
    -            })
    -            .await;
    -    }
    -}
    -
    -#[derive(Clone)]
    -pub struct DiskBufferRequester {
    -    sender: mpsc::Sender<BufferCmd>,
    -}
    -
    -impl Default for DiskBufferRequester {
    -    fn default() -> Self {
    -        Self {
    -            sender: mpsc::channel(1).0,
    -        }
    -    }
    -}
    -
    -impl DiskBufferRequester {
    -    pub fn new(sender: mpsc::Sender<BufferCmd>) -> Self {
    -        Self { sender }
    -    }
    -
    -    pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option<Arc<Page>> {
    -        let (resp_tx, resp_rx) = oneshot::channel();
    -        self.sender
    -            .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx))
    -            .ok()
    -            .unwrap();
    -        resp_rx.blocking_recv().unwrap()
    -    }
    -
    -    pub fn write(&self, page_batch: Vec<BufferWrite>, write_batch: AshRecord) {
    -        self.sender
    -            .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch))
    -            .ok()
    -            .unwrap()
    -    }
    -
    -    pub fn shutdown(&self) {
    -        self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap()
    -    }
    -
    -    pub fn init_wal(&self, waldir: &str, rootfd: Fd) {
    -        self.sender
    -            .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string()))
    -            .ok()
    -            .unwrap()
    -    }
    -
    -    pub fn collect_ash(&self, nrecords: usize) -> Vec<AshRecord> {
    -        let (resp_tx, resp_rx) = oneshot::channel();
    -        self.sender
    -            .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx))
    -            .ok()
    -            .unwrap();
    -        resp_rx.blocking_recv().unwrap()
    -    }
    -
    -    pub fn reg_cached_space(&self, space: &CachedSpace) {
    -        let mut inner = space.inner.borrow_mut();
    -        inner.disk_buffer = self.clone();
    -        self.sender
    -            .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone()))
    -            .ok()
    -            .unwrap()
    -    }
    -}
    -
    -#[derive(Debug, PartialEq)]
    -pub enum StoreError {
    -    System(nix::Error),
    -    InitError(String),
    -    // TODO: more error report from the DiskBuffer
    -    //WriterError,
    -}
    -
    -impl From<nix::Error> for StoreError {
    -    fn from(e: nix::Error) -> Self {
    -        StoreError::System(e)
    -    }
    -}
    -
    -
    \ No newline at end of file diff --git a/docs/storage.js b/docs/storage.js deleted file mode 100644 index 17c1da81f183..000000000000 --- a/docs/storage.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";const darkThemes=["dark","ayu"];window.currentTheme=document.getElementById("themeStyle");window.mainTheme=document.getElementById("mainThemeStyle");window.RUSTDOC_MOBILE_BREAKPOINT=700;const settingsDataset=(function(){const settingsElement=document.getElementById("default-settings");if(settingsElement===null){return null}const dataset=settingsElement.dataset;if(dataset===undefined){return null}return dataset})();function getSettingValue(settingName){const current=getCurrentValue(settingName);if(current!==null){return current}if(settingsDataset!==null){const def=settingsDataset[settingName.replace(/-/g,"_")];if(def!==undefined){return def}}return null}const localStoredTheme=getSettingValue("theme");const savedHref=[];function hasClass(elem,className){return elem&&elem.classList&&elem.classList.contains(className)}function addClass(elem,className){if(!elem||!elem.classList){return}elem.classList.add(className)}function removeClass(elem,className){if(!elem||!elem.classList){return}elem.classList.remove(className)}function onEach(arr,func,reversed){if(arr&&arr.length>0&&func){if(reversed){const length=arr.length;for(let i=length-1;i>=0;--i){if(func(arr[i])){return true}}}else{for(const elem of arr){if(func(elem)){return true}}}}return false}function onEachLazy(lazyArray,func,reversed){return onEach(Array.prototype.slice.call(lazyArray),func,reversed)}function updateLocalStorage(name,value){try{window.localStorage.setItem("rustdoc-"+name,value)}catch(e){}}function getCurrentValue(name){try{return window.localStorage.getItem("rustdoc-"+name)}catch(e){return null}}function switchTheme(styleElem,mainStyleElem,newTheme,saveTheme){const newHref=mainStyleElem.href.replace(/\/rustdoc([^/]*)\.css/,"/"+newTheme+"$1"+".css");if(saveTheme){updateLocalStorage("theme",newTheme)}if(styleElem.href===newHref){return}let found=false;if(savedHref.length===0){onEachLazy(document.getElementsByTagName("link"),el=>{savedHref.push(el.href)})}onEach(savedHref,el=>{if(el===newHref){found=true;return true}});if(found){styleElem.href=newHref}}function useSystemTheme(value){if(value===undefined){value=true}updateLocalStorage("use-system-theme",value);const toggle=document.getElementById("use-system-theme");if(toggle&&toggle instanceof HTMLInputElement){toggle.checked=value}}const updateSystemTheme=(function(){if(!window.matchMedia){return()=>{const cssTheme=getComputedStyle(document.documentElement).getPropertyValue("content");switchTheme(window.currentTheme,window.mainTheme,JSON.parse(cssTheme)||"light",true)}}const mql=window.matchMedia("(prefers-color-scheme: dark)");function handlePreferenceChange(mql){const use=theme=>{switchTheme(window.currentTheme,window.mainTheme,theme,true)};if(getSettingValue("use-system-theme")!=="false"){const lightTheme=getSettingValue("preferred-light-theme")||"light";const darkTheme=getSettingValue("preferred-dark-theme")||"dark";if(mql.matches){use(darkTheme)}else{use(lightTheme)}}else{use(getSettingValue("theme"))}}mql.addListener(handlePreferenceChange);return()=>{handlePreferenceChange(mql)}})();function switchToSavedTheme(){switchTheme(window.currentTheme,window.mainTheme,getSettingValue("theme")||"light",false)}if(getSettingValue("use-system-theme")!=="false"&&window.matchMedia){if(getSettingValue("use-system-theme")===null&&getSettingValue("preferred-dark-theme")===null&&darkThemes.indexOf(localStoredTheme)>=0){updateLocalStorage("preferred-dark-theme",localStoredTheme)}updateSystemTheme()}else{switchToSavedTheme()}if(getSettingValue("source-sidebar-show")==="true"){addClass(document.documentElement,"source-sidebar-expanded")}window.addEventListener("pageshow",ev=>{if(ev.persisted){setTimeout(switchToSavedTheme,0)}}) \ No newline at end of file diff --git a/docs/toggle-minus.svg b/docs/toggle-minus.svg deleted file mode 100644 index 73154788a0e8..000000000000 --- a/docs/toggle-minus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/toggle-plus.svg b/docs/toggle-plus.svg deleted file mode 100644 index 08b17033e164..000000000000 --- a/docs/toggle-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/wheel.svg b/docs/wheel.svg deleted file mode 100644 index 01da3b24c7c4..000000000000 --- a/docs/wheel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 9e38e03500120725469fe46d9a0f1795cc4f7529 Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 17 Apr 2023 19:16:05 -0400 Subject: [PATCH 0123/1053] docs: Add release notes (#27) --- README.md | 3 +++ RELEASE.md | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 RELEASE.md diff --git a/README.md b/README.md index a7dc06760e2c..a0e953d170ec 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,9 @@ use-cases. Try running them via the command-line, via `cargo run --release To integrate firewood into a custom VM or other project, see the [firewood-connection](./firewood-connection/README.md) for a straightforward way to use firewood via custom message-passing. +## Release +See the [release documentation](./RELEASE.md) for detailed information on how to release firewood. + ## CLI Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](fwdctl/README.md). diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000000..fb2d86e576ff --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,20 @@ +# Releasing firewood + +Releasing firewood is straightforward and can be done entirely in CI. + +Firewood is made up of several sub-projects in a workspace. Each project is in +its own crate and has an independent version. +* firewood +* firewood-growth-ring +* firewood-libaio +* firewood-shale + +To trigger a release, simply push a semver-compatible tag to the main branch, +for example `v0.0.1`. The CI will automatically publish a draft release which +consists of release notes and changes. Once this draft is approved, and the new +release exists, CI will then go and publish each of the sub-project crates to +crate.io +> Note: Only crates that had a version bump will be updated on crates.io. If a +> crate was changed, but the version was not bumped, it will not be updated on +> crates.io. Deploying a crate with the same version as previously will be a +> no-op. From d23b4eab11b84a09ec27f5cc85a2d2d07a4c14bc Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 18 Apr 2023 11:06:46 -0400 Subject: [PATCH 0124/1053] docs: Update CODEOWNERS (#28) Signed-off-by: Dan Sover --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index fea89b3e01f0..c80f371ca4d1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,3 @@ # CODEOWNERS -* @exdx @xinifinity @gyuho @hexfusion @rkuris +* @exdx @xinifinity @gyuho @hexfusion @rkuris @patrick-ogrady @StephenButtolph + From 6a04491cc2cd62637f7e171bdb588c70ed81bd06 Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 18 Apr 2023 13:30:55 -0400 Subject: [PATCH 0125/1053] docs: Add badges to README (#33) Signed-off-by: Dan Sover --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a0e953d170ec..065a58644871 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. +[![Latest version](https://img.shields.io/crates/v/firewood.svg)](https://crates.io/crates/firewood) +[![Ecosystem license](https://img.shields.io/badge/License-Ecosystem-blue.svg)](./LICENSE.md) + > :warning: firewood is alpha-level software and is not ready for production > use. Do not use firewood to store production data. See the > [license](./LICENSE.md) for more information regarding firewood usage. From 662b7949464baadfd32027fb7c277fc5318c81fe Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Tue, 18 Apr 2023 15:04:54 -0400 Subject: [PATCH 0126/1053] Breakout disk buffer from storage and add tests (#34) Signed-off-by: Sam Batschelet --- firewood-libaio/src/lib.rs | 16 +- firewood/Cargo.toml | 7 +- firewood/src/db.rs | 15 +- firewood/src/file.rs | 3 +- firewood/src/lib.rs | 2 +- firewood/src/storage/buffer.rs | 607 ++++++++++++++++++++ firewood/src/{storage.rs => storage/mod.rs} | 457 +-------------- 7 files changed, 638 insertions(+), 469 deletions(-) create mode 100644 firewood/src/storage/buffer.rs rename firewood/src/{storage.rs => storage/mod.rs} (61%) diff --git a/firewood-libaio/src/lib.rs b/firewood-libaio/src/lib.rs index 7c85dba1394a..82255a9a1803 100644 --- a/firewood-libaio/src/lib.rs +++ b/firewood-libaio/src/lib.rs @@ -52,7 +52,7 @@ const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; #[derive(Debug)] -pub enum Error { +pub enum AIOError { MaxEventsTooLarge, LowKernelRes, NotSupported, @@ -72,15 +72,15 @@ impl std::ops::Deref for AIOContext { } impl AIOContext { - fn new(maxevents: u32) -> Result { + fn new(maxevents: u32) -> Result { let mut ctx = std::ptr::null_mut(); unsafe { match abi::io_setup(maxevents as libc::c_int, &mut ctx) { 0 => Ok(()), - LIBAIO_EAGAIN => Err(Error::MaxEventsTooLarge), - LIBAIO_ENOMEM => Err(Error::LowKernelRes), - LIBAIO_ENOSYS => Err(Error::NotSupported), - _ => Err(Error::OtherError), + LIBAIO_EAGAIN => Err(AIOError::MaxEventsTooLarge), + LIBAIO_ENOMEM => Err(AIOError::LowKernelRes), + LIBAIO_ENOSYS => Err(AIOError::NotSupported), + _ => Err(AIOError::OtherError), } .map(|_| AIOContext(ctx)) } @@ -322,7 +322,7 @@ impl AIOBuilder { /// Build an AIOManager object based on the configuration (and auto-start the background IO /// scheduling thread). - pub fn build(&mut self) -> Result { + pub fn build(&mut self) -> Result { let (scheduler_in, scheduler_out) = new_batch_scheduler(self.max_nbatched); let (exit_s, exit_r) = crossbeam_channel::bounded(0); @@ -365,7 +365,7 @@ impl AIOManager { exit_r: crossbeam_channel::Receiver<()>, max_nwait: u16, timeout: Option, - ) -> Result<(), Error> { + ) -> Result<(), AIOError> { let n = self.notifier.clone(); self.listener = Some(std::thread::spawn(move || { let mut timespec = timeout.map(|sec: u32| libc::timespec { diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 423e42f55805..71f3739105cf 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -14,9 +14,10 @@ description = "Firewood is an embedded key-value store, optimized to store block license-file = "../LICENSE.md" homepage = "https://avalabs.org" [dependencies] -firewood-growth-ring = { version = "0.0.1", path = "../firewood-growth-ring", package = "firewood-growth-ring" } -firewood-libaio = {version = "0.0.1", path = "../firewood-libaio", package = "firewood-libaio" } -firewood-shale = { version = "0.0.1", path = "../firewood-shale", package = "firewood-shale" } +aquamarine = "0.3.1" +firewood-growth-ring = { version = "0.0.1", path = "../firewood-growth-ring" } +firewood-libaio = {version = "0.0.1", path = "../firewood-libaio" } +firewood-shale = { version = "0.0.1", path = "../firewood-shale" } enum-as-inner = "0.5.1" parking_lot = "0.12.1" rlp = "0.5.2" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4e0048a03465..3fd4c61feca1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -20,10 +20,12 @@ use crate::account::{Account, AccountRLP, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; use crate::proof::{Proof, ProofError}; +use crate::storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}; +pub use crate::storage::{buffer::DiskBufferConfig, WALConfig}; use crate::storage::{ - CachedSpace, DiskBuffer, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared, + AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared, + PAGE_SIZE_NBIT, }; -pub use crate::storage::{DiskBufferConfig, WALConfig}; const MERKLE_META_SPACE: SpaceID = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceID = 0x1; @@ -400,7 +402,7 @@ impl DBRev { struct DBInner { latest: DBRev, - disk_requester: crate::storage::DiskBufferRequester, + disk_requester: DiskBufferRequester, disk_thread: Option>, staging: Universe>, cached: Universe>, @@ -450,7 +452,7 @@ impl DB { if reset { // initialize DBParams if cfg.payload_file_nbit < cfg.payload_regn_nbit - || cfg.payload_regn_nbit < crate::storage::PAGE_SIZE_NBIT + || cfg.payload_regn_nbit < PAGE_SIZE_NBIT { return Err(DBError::InvalidParams); } @@ -539,7 +541,7 @@ impl DB { .max_revisions(cfg.wal.max_revisions) .build(); let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); - let disk_requester = crate::storage::DiskBufferRequester::new(sender); + let disk_requester = DiskBufferRequester::new(sender); let buffer = cfg.buffer.clone(); let disk_thread = Some(std::thread::spawn(move || { let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); @@ -1058,7 +1060,6 @@ impl WriteBatch { /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are /// either retained on disk or lost together during a crash. pub fn commit(mut self) { - use crate::storage::BufferWrite; let mut rev_inner = self.m.write(); if self.root_hash_recalc { rev_inner.latest.root_hash().ok(); @@ -1153,7 +1154,7 @@ impl WriteBatch { delta: blob_meta_pages, }, ], - crate::storage::AshRecord( + AshRecord( [ (MERKLE_META_SPACE, merkle_meta_plain), (MERKLE_PAYLOAD_SPACE, merkle_payload_plain), diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 8910fe33d41a..c81f53988567 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -6,8 +6,7 @@ pub(crate) use std::os::unix::io::RawFd as Fd; use std::path::Path; -extern crate firewood_growth_ring as growth_ring; -use growth_ring::oflags; +use growthring::oflags; use nix::errno::Errno; use nix::fcntl::{open, openat, OFlag}; use nix::sys::stat::Mode; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index ad127367fcc0..90c99bece402 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -201,7 +201,7 @@ pub(crate) mod file; pub mod merkle; pub mod merkle_util; pub mod proof; -pub(crate) mod storage; +pub mod storage; pub mod api; pub mod service; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs new file mode 100644 index 000000000000..302df06529e7 --- /dev/null +++ b/firewood/src/storage/buffer.rs @@ -0,0 +1,607 @@ +//! Disk buffer for staging in memory pages and flushing them to disk. +use std::fmt::Debug; +use std::rc::Rc; +use std::sync::Arc; +use std::{cell::RefCell, collections::HashMap}; + +use crate::storage::Fd; + +use super::{ + Ash, AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WALConfig, + PAGE_SIZE_NBIT, +}; + +use aiofut::{AIOBuilder, AIOError, AIOManager}; +use growthring::{ + wal::{RecoverPolicy, WALLoader, WALWriter}, + walerror::WALError, + WALStoreAIO, +}; +use shale::SpaceID; +use tokio::sync::oneshot::error::RecvError; +use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; +use typed_builder::TypedBuilder; + +#[derive(Debug)] +pub enum BufferCmd { + /// Initialize the WAL. + InitWAL(Fd, String), + /// Process a write batch against the underlying store. + WriteBatch(Vec, AshRecord), + /// Get a page from the disk buffer. + GetPage((SpaceID, u64), oneshot::Sender>>), + CollectAsh(usize, oneshot::Sender>), + /// Register a new space and add the files to a memory mapped pool. + RegCachedSpace(SpaceID, Arc), + /// Returns false if the + Shutdown, +} + +/// Config for the disk buffer. +#[derive(TypedBuilder, Clone, Debug)] +pub struct DiskBufferConfig { + /// Maximum buffered disk buffer commands. + #[builder(default = 4096)] + pub max_buffered: usize, + /// Maximum number of pending pages. + #[builder(default = 65536)] // 256MB total size by default + pub max_pending: usize, + /// Maximum number of concurrent async I/O requests. + #[builder(default = 1024)] + pub max_aio_requests: u32, + /// Maximum number of async I/O responses that it polls for at a time. + #[builder(default = 128)] + pub max_aio_response: u16, + /// Maximum number of async I/O requests per submission. + #[builder(default = 128)] + pub max_aio_submit: usize, + /// Maximum number of concurrent async I/O requests in WAL. + #[builder(default = 256)] + pub wal_max_aio_requests: usize, + /// Maximum buffered WAL records. + #[builder(default = 1024)] + pub wal_max_buffered: usize, + /// Maximum batched WAL records per write. + #[builder(default = 4096)] + pub wal_max_batch: usize, +} + +/// List of pages to write to disk. +#[derive(Debug)] +pub struct BufferWrite { + pub space_id: SpaceID, + pub delta: StoreDelta, +} + +#[derive(Debug)] +struct PendingPage { + staging_data: Arc, + file_nbit: u64, + staging_notifiers: Vec>, + writing_notifiers: Vec>, +} + +/// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] +/// and managing the persistance of pages. +pub struct DiskBuffer { + pending: HashMap<(SpaceID, u64), PendingPage>, + inbound: mpsc::Receiver, + fc_notifier: Option>, + fc_blocker: Option>, + file_pools: [Option>; 255], + aiomgr: AIOManager, + local_pool: Rc, + task_id: u64, + tasks: Rc>>>>, + wal: Option>>>, + cfg: DiskBufferConfig, + wal_cfg: WALConfig, +} + +impl DiskBuffer { + /// Create a new aio managed disk buffer. + pub fn new( + inbound: mpsc::Receiver, + cfg: &DiskBufferConfig, + wal: &WALConfig, + ) -> Result { + let aiomgr = AIOBuilder::default() + .max_events(cfg.max_aio_requests) + .max_nwait(cfg.max_aio_response) + .max_nbatched(cfg.max_aio_submit) + .build() + .map_err(|_| AIOError::OtherError)?; + + Ok(Self { + pending: HashMap::new(), + cfg: cfg.clone(), + inbound, + fc_notifier: None, + fc_blocker: None, + file_pools: std::array::from_fn(|_| None), + aiomgr, + local_pool: Rc::new(tokio::task::LocalSet::new()), + task_id: 0, + tasks: Rc::new(RefCell::new(HashMap::new())), + wal: None, + wal_cfg: wal.clone(), + }) + } + + unsafe fn get_longlive_self(&mut self) -> &'static mut Self { + std::mem::transmute::<&mut Self, &'static mut Self>(self) + } + + /// Add an pending pages to aio manager for processing by the local pool. + fn schedule_write(&mut self, page_key: (SpaceID, u64)) { + let p = self.pending.get(&page_key).unwrap(); + let offset = page_key.1 << PAGE_SIZE_NBIT; + let fid = offset >> p.file_nbit; + let fmask = (1 << p.file_nbit) - 1; + let file = self.file_pools[page_key.0 as usize] + .as_ref() + .unwrap() + .get_file(fid) + .unwrap(); + let fut = self.aiomgr.write( + file.get_fd(), + offset & fmask, + Box::new(*p.staging_data), + None, + ); + let s = unsafe { self.get_longlive_self() }; + self.start_task(async move { + let (res, _) = fut.await; + res.unwrap(); + s.finish_write(page_key); + }); + } + + fn finish_write(&mut self, page_key: (SpaceID, u64)) { + use std::collections::hash_map::Entry::*; + match self.pending.entry(page_key) { + Occupied(mut e) => { + let slot = e.get_mut(); + for notifier in std::mem::take(&mut slot.writing_notifiers) { + notifier.add_permits(1) + } + if slot.staging_notifiers.is_empty() { + e.remove(); + if self.pending.len() < self.cfg.max_pending { + if let Some(notifier) = self.fc_notifier.take() { + notifier.send(()).unwrap(); + } + } + } else { + assert!(slot.writing_notifiers.is_empty()); + std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers); + // write again + self.schedule_write(page_key); + } + } + _ => unreachable!(), + } + } + + /// Initialize the WAL subsystem if it does not exists and attempts to replay the WAL if exists. + async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), WALError> { + let mut aiobuilder = AIOBuilder::default(); + aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); + let aiomgr = aiobuilder.build().map_err(|_| WALError::Other)?; + let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)) + .map_err(|_| WALError::Other)?; + let mut loader = WALLoader::new(); + loader + .file_nbit(self.wal_cfg.file_nbit) + .block_nbit(self.wal_cfg.block_nbit) + .recover_policy(RecoverPolicy::Strict); + if self.wal.is_some() { + // already initialized + return Ok(()); + } + let wal = loader + .load( + store, + |raw, _| { + let batch = AshRecord::deserialize(raw); + for (space_id, Ash { old, new }) in batch.0 { + for (old, data) in old.into_iter().zip(new.into_iter()) { + let offset = old.offset; + let file_pool = self.file_pools[space_id as usize].as_ref().unwrap(); + let file_nbit = file_pool.get_file_nbit(); + let file_mask = (1 << file_nbit) - 1; + let fid = offset >> file_nbit; + nix::sys::uio::pwrite( + file_pool + .get_file(fid) + .map_err(|_| WALError::Other)? + .get_fd(), + &data, + (offset & file_mask) as nix::libc::off_t, + ) + .map_err(|_| WALError::Other)?; + } + } + Ok(()) + }, + self.wal_cfg.max_revisions, + ) + .await?; + self.wal = Some(Rc::new(Mutex::new(wal))); + Ok(()) + } + + async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec, AshRecord)>) { + use std::collections::hash_map::Entry::*; + loop { + let mut bwrites = Vec::new(); + let mut records = Vec::new(); + + if let Some((bw, ac)) = writes.recv().await { + records.push(ac); + bwrites.extend(bw); + } else { + break; + } + while let Ok((bw, ac)) = writes.try_recv() { + records.push(ac); + bwrites.extend(bw); + if records.len() >= self.cfg.wal_max_batch { + break; + } + } + // first write to WAL + let ring_ids: Vec<_> = + futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) + .await + .into_iter() + .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) + .collect(); + let sem = Rc::new(tokio::sync::Semaphore::new(0)); + let mut npermit = 0; + for BufferWrite { space_id, delta } in bwrites { + for w in delta.0 { + let page_key = (space_id, w.0); + match self.pending.entry(page_key) { + Occupied(mut e) => { + let e = e.get_mut(); + e.staging_data = w.1.into(); + e.staging_notifiers.push(sem.clone()); + npermit += 1; + } + Vacant(e) => { + let file_nbit = self.file_pools[page_key.0 as usize] + .as_ref() + .unwrap() + .file_nbit; + e.insert(PendingPage { + staging_data: w.1.into(), + file_nbit, + staging_notifiers: Vec::new(), + writing_notifiers: vec![sem.clone()], + }); + npermit += 1; + self.schedule_write(page_key); + } + } + } + } + let wal = self.wal.as_ref().unwrap().clone(); + let max_revisions = self.wal_cfg.max_revisions; + self.start_task(async move { + let _ = sem.acquire_many(npermit).await.unwrap(); + wal.lock() + .await + .peel(ring_ids, max_revisions) + .await + .map_err(|_| "WAL errore while pruning") + .unwrap(); + }); + if self.pending.len() >= self.cfg.max_pending { + let (tx, rx) = oneshot::channel(); + self.fc_notifier = Some(tx); + self.fc_blocker = Some(rx); + } + } + } + + async fn process( + &mut self, + req: BufferCmd, + wal_in: &mpsc::Sender<(Vec, AshRecord)>, + ) -> bool { + match req { + BufferCmd::Shutdown => return false, + BufferCmd::InitWAL(rootfd, waldir) => { + if (self.init_wal(rootfd, waldir).await).is_err() { + panic!("cannot initialize from WAL") + } + } + BufferCmd::GetPage(page_key, tx) => tx + .send(self.pending.get(&page_key).map(|e| e.staging_data.clone())) + .unwrap(), + BufferCmd::WriteBatch(writes, wal_writes) => { + wal_in.send((writes, wal_writes)).await.unwrap(); + } + BufferCmd::CollectAsh(nrecords, tx) => { + // wait to ensure writes are paused for WAL + let ash = self + .wal + .as_ref() + .unwrap() + .clone() + .lock() + .await + .read_recent_records(nrecords, &RecoverPolicy::Strict) + .await + .unwrap() + .into_iter() + .map(AshRecord::deserialize) + .collect(); + tx.send(ash).unwrap(); + } + BufferCmd::RegCachedSpace(space_id, files) => { + self.file_pools[space_id as usize] = Some(files) + } + } + true + } + + /// Processes an async AIOFuture request against the local pool. + fn start_task + 'static>(&mut self, fut: F) { + let task_id = self.task_id; + self.task_id += 1; + let tasks = self.tasks.clone(); + self.tasks.borrow_mut().insert( + task_id, + Some(self.local_pool.spawn_local(async move { + fut.await; + tasks.borrow_mut().remove(&task_id); + })), + ); + } + + #[tokio::main(flavor = "current_thread")] + pub async fn run(mut self) { + let wal_in = { + let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered); + let s = unsafe { self.get_longlive_self() }; + self.start_task(s.run_wal_queue(rx)); + tx + }; + self.local_pool + .clone() + .run_until(async { + loop { + if let Some(fc) = self.fc_blocker.take() { + // flow control, wait until ready + fc.await.unwrap(); + } + let req = self.inbound.recv().await.unwrap(); + if !self.process(req, &wal_in).await { + break; + } + } + drop(wal_in); + let handles: Vec<_> = self + .tasks + .borrow_mut() + .iter_mut() + .map(|(_, task)| task.take().unwrap()) + .collect(); + for h in handles { + h.await.unwrap(); + } + }) + .await; + } +} + +/// Communicates with the [`DiskBuffer`] over channels. +#[cfg_attr(doc, aquamarine::aquamarine)] +/// ```mermaid +/// graph LR +/// s([Caller]) --> a[[DiskBufferRequester]] +/// r[[DiskBuffer]] --> f([Disk]) +/// subgraph rustc[Thread] +/// r +/// end +/// subgraph rustc[Thread] +/// a -. BufferCmd .-> r +/// end +/// ``` +#[derive(Clone, Debug)] +pub struct DiskBufferRequester { + sender: mpsc::Sender, +} + +impl Default for DiskBufferRequester { + fn default() -> Self { + Self { + sender: mpsc::channel(1).0, + } + } +} + +impl DiskBufferRequester { + /// Create a new requester. + pub fn new(sender: mpsc::Sender) -> Self { + Self { sender } + } + + /// Get a page from the buffer. + pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.sender + .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) + .map_err(StoreError::Send) + .ok(); + resp_rx.blocking_recv().unwrap() + } + + /// Sends a batch of writes to the buffer. + pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { + self.sender + .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch)) + .map_err(StoreError::Send) + .ok(); + } + + pub fn shutdown(&self) { + self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap() + } + + /// Initialize the WAL. + pub fn init_wal(&self, waldir: &str, rootfd: Fd) { + self.sender + .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string())) + .map_err(StoreError::Send) + .ok(); + } + + /// Collect the last N records from the WAL. + pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.sender + .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx)) + .map_err(StoreError::Send) + .ok(); + resp_rx.blocking_recv().map_err(StoreError::Receive) + } + + /// Register a cached space to the buffer. + pub fn reg_cached_space(&self, space: &CachedSpace) { + let mut inner = space.inner.borrow_mut(); + inner.disk_buffer = self.clone(); + self.sender + .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone())) + .map_err(StoreError::Send) + .ok(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + file, + storage::{DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta}, + }; + use shale::MemStore; + + const STATE_SPACE: SpaceID = 0x0; + #[test] + fn test_buffer() { + let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let wal_cfg = WALConfig::builder().build(); + let disk_requester = init_buffer(buf_cfg, wal_cfg); + + let path = std::path::PathBuf::from(r"/tmp/firewood"); + let (root_db_fd, reset) = crate::file::open_dir(path, true).unwrap(); + + // file descriptor of the state directory + let state_fd = file::touch_dir("state", root_db_fd).unwrap(); + assert!(reset); + // create a new wal directory on top of root_db_fd + disk_requester.init_wal("wal", root_db_fd); + + // create a new state cache which tracks on disk state. + let state_cache = Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(1) + .ncached_files(1) + .space_id(STATE_SPACE) + .file_nbit(1) + .rootfd(state_fd) + .build(), + ) + .unwrap(), + ); + + // add an in memory cached space. this will allow us to write to the + // disk buffer then later persist the change to disk. + disk_requester.reg_cached_space(state_cache.as_ref()); + + // memory mapped store + let mut mut_store = StoreRevMut::new(state_cache.clone() as Rc); + + let change = b"this is a test"; + + // write to the in memory buffer not to disk + mut_store.write(0, change); + assert_eq!(mut_store.id(), STATE_SPACE); + + // mutate the in memory buffer. + let change = b"this is another test"; + + // write to the in memory buffer (ash) not yet to disk + mut_store.write(0, change); + assert_eq!(mut_store.id(), STATE_SPACE); + + // wal should have no records. + assert!(disk_requester.collect_ash(1).unwrap().is_empty()); + + // get RO view of the buffer from the beginning. + let view = mut_store.get_view(0, change.len() as u64).unwrap(); + assert_eq!(view.as_deref(), change); + + let (page_batch, write_batch) = create_batches(&mut_store); + + // create a mutation request to the disk buffer by passing the page and write batch. + let d1 = disk_requester.clone(); + std::thread::spawn(move || { + // wal is empty + assert!(d1.collect_ash(1).unwrap().is_empty()); + // page is not yet persisted to disk. + assert!(d1.get_page(STATE_SPACE, 0).is_none()); + d1.write(page_batch, write_batch); + // This is not ACID compliant write should not return before wal log is written to disk. + // If the sleep is removed the test will fail. + // TODO why is this so slow? + std::thread::sleep(std::time::Duration::from_millis(5)); + assert_eq!(d1.collect_ash(1).unwrap().len(), 1); + assert!(d1.get_page(STATE_SPACE, 0).is_some()); + }); + + // wait for thread to finish + std::thread::sleep(std::time::Duration::from_millis(5)); + // state has been written to disk in the above thread wal should have 1 record. + assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); + } + + fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WALConfig) -> DiskBufferRequester { + let (sender, inbound) = tokio::sync::mpsc::channel(buf_cfg.max_buffered); + let disk_requester = DiskBufferRequester::new(sender); + std::thread::spawn(move || { + let disk_buffer = DiskBuffer::new(inbound, &buf_cfg, &wal_cfg).unwrap(); + disk_buffer.run() + }); + disk_requester + } + + fn create_batches(rev_mut: &StoreRevMut) -> (Vec, AshRecord) { + let deltas = std::mem::replace( + &mut *rev_mut.deltas.borrow_mut(), + StoreRevMutDelta { + pages: HashMap::new(), + plain: Ash::new(), + }, + ); + + // create a list of delta pages from existing in memory data. + let mut pages = Vec::new(); + for (pid, page) in deltas.pages.into_iter() { + pages.push(DeltaPage(pid, page)); + } + pages.sort_by_key(|p| p.0); + + let page_batch = vec![BufferWrite { + space_id: STATE_SPACE, + delta: StoreDelta(pages), + }]; + + let write_batch = AshRecord([(STATE_SPACE, deltas.plain)].into()); + (page_batch, write_batch) + } +} diff --git a/firewood/src/storage.rs b/firewood/src/storage/mod.rs similarity index 61% rename from firewood/src/storage.rs rename to firewood/src/storage/mod.rs index 95dc6a44abb6..e660855c6652 100644 --- a/firewood/src/storage.rs +++ b/firewood/src/storage/mod.rs @@ -1,7 +1,5 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - // TODO: try to get rid of the use `RefCell` in this file +pub mod buffer; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; @@ -11,25 +9,17 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; -use firewood_libaio::{AIOBuilder, AIOManager}; - -extern crate firewood_growth_ring as growth_ring; -use growth_ring::{ - wal::{RecoverPolicy, WALError, WALLoader, WALWriter}, - WALStoreAIO, -}; - -use shale::{MemStore, MemView, SpaceID}; - use nix::fcntl::{flock, FlockArg}; +use shale::{MemStore, MemView, SpaceID}; use thiserror::Error; use tokio::sync::mpsc::error::SendError; use tokio::sync::oneshot::error::RecvError; -use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; use typed_builder::TypedBuilder; use crate::file::{Fd, File}; +use self::buffer::DiskBufferRequester; + pub(crate) const PAGE_SIZE_NBIT: u64 = 12; pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; @@ -49,6 +39,7 @@ pub enum StoreError { } pub trait MemStoreR: Debug { + /// Returns a slice of bytes from memory. fn get_slice(&self, offset: u64, length: u64) -> Option>; fn id(&self) -> SpaceID; } @@ -79,8 +70,8 @@ impl Ash { #[derive(Debug)] pub struct AshRecord(pub HashMap); -impl growth_ring::wal::Record for AshRecord { - fn serialize(&self) -> growth_ring::wal::WALBytes { +impl growthring::wal::Record for AshRecord { + fn serialize(&self) -> growthring::wal::WALBytes { let mut bytes = Vec::new(); bytes.extend((self.0.len() as u64).to_le_bytes()); for (space_id, w) in self.0.iter() { @@ -99,7 +90,7 @@ impl growth_ring::wal::Record for AshRecord { impl AshRecord { #[allow(clippy::boxed_local)] - fn deserialize(raw: growth_ring::wal::WALBytes) -> Self { + fn deserialize(raw: growthring::wal::WALBytes) -> Self { let mut r = &raw[..]; let len = u64::from_le_bytes(r[..8].try_into().unwrap()); r = &r[8..]; @@ -240,10 +231,6 @@ impl StoreDelta { } Self(deltas) } - - pub fn len(&self) -> usize { - self.0.len() - } } pub struct StoreRev { @@ -370,6 +357,7 @@ impl MemStore for StoreRevShared { } } +#[derive(Debug)] struct StoreRef { data: Vec, } @@ -854,22 +842,6 @@ impl Drop for FilePool { } } -#[derive(Debug)] -pub struct BufferWrite { - pub space_id: SpaceID, - pub delta: StoreDelta, -} - -#[derive(Debug)] -pub enum BufferCmd { - InitWAL(Fd, String), - WriteBatch(Vec, AshRecord), - GetPage((SpaceID, u64), oneshot::Sender>>), - CollectAsh(usize, oneshot::Sender>), - RegCachedSpace(SpaceID, Arc), - Shutdown, -} - #[derive(TypedBuilder, Clone, Debug)] pub struct WALConfig { #[builder(default = 22)] // 4MB WAL logs @@ -879,414 +851,3 @@ pub struct WALConfig { #[builder(default = 100)] // preserve a rolling window of 100 past commits pub max_revisions: u32, } - -/// Config for the disk buffer. -#[derive(TypedBuilder, Clone, Debug)] -pub struct DiskBufferConfig { - /// Maximum buffered disk buffer commands. - #[builder(default = 4096)] - pub max_buffered: usize, - /// Maximum number of pending pages. - #[builder(default = 65536)] // 256MB total size by default - pub max_pending: usize, - /// Maximum number of concurrent async I/O requests. - #[builder(default = 1024)] - pub max_aio_requests: u32, - /// Maximum number of async I/O responses that it polls for at a time. - #[builder(default = 128)] - pub max_aio_response: u16, - /// Maximum number of async I/O requests per submission. - #[builder(default = 128)] - pub max_aio_submit: usize, - /// Maximum number of concurrent async I/O requests in WAL. - #[builder(default = 256)] - pub wal_max_aio_requests: usize, - /// Maximum buffered WAL records. - #[builder(default = 1024)] - pub wal_max_buffered: usize, - /// Maximum batched WAL records per write. - #[builder(default = 4096)] - pub wal_max_batch: usize, -} - -struct PendingPage { - staging_data: Arc, - file_nbit: u64, - staging_notifiers: Vec>, - writing_notifiers: Vec>, -} - -pub struct DiskBuffer { - pending: HashMap<(SpaceID, u64), PendingPage>, - inbound: mpsc::Receiver, - fc_notifier: Option>, - fc_blocker: Option>, - file_pools: [Option>; 255], - aiomgr: AIOManager, - local_pool: Rc, - task_id: u64, - tasks: Rc>>>>, - wal: Option>>>, - cfg: DiskBufferConfig, - wal_cfg: WALConfig, -} - -impl DiskBuffer { - pub fn new( - inbound: mpsc::Receiver, - cfg: &DiskBufferConfig, - wal: &WALConfig, - ) -> Option { - const INIT: Option> = None; - let aiomgr = AIOBuilder::default() - .max_events(cfg.max_aio_requests) - .max_nwait(cfg.max_aio_response) - .max_nbatched(cfg.max_aio_submit) - .build() - .ok()?; - - Some(Self { - pending: HashMap::new(), - cfg: cfg.clone(), - inbound, - fc_notifier: None, - fc_blocker: None, - file_pools: [INIT; 255], - aiomgr, - local_pool: Rc::new(tokio::task::LocalSet::new()), - task_id: 0, - tasks: Rc::new(RefCell::new(HashMap::new())), - wal: None, - wal_cfg: wal.clone(), - }) - } - - unsafe fn get_longlive_self(&mut self) -> &'static mut Self { - std::mem::transmute::<&mut Self, &'static mut Self>(self) - } - - fn schedule_write(&mut self, page_key: (SpaceID, u64)) { - let p = self.pending.get(&page_key).unwrap(); - let offset = page_key.1 << PAGE_SIZE_NBIT; - let fid = offset >> p.file_nbit; - let fmask = (1 << p.file_nbit) - 1; - let file = self.file_pools[page_key.0 as usize] - .as_ref() - .unwrap() - .get_file(fid) - .unwrap(); - let fut = self.aiomgr.write( - file.get_fd(), - offset & fmask, - Box::new(*p.staging_data), - None, - ); - let s = unsafe { self.get_longlive_self() }; - self.start_task(async move { - let (res, _) = fut.await; - res.unwrap(); - s.finish_write(page_key); - }); - } - - fn finish_write(&mut self, page_key: (SpaceID, u64)) { - use std::collections::hash_map::Entry::*; - match self.pending.entry(page_key) { - Occupied(mut e) => { - let slot = e.get_mut(); - for notifier in std::mem::take(&mut slot.writing_notifiers) { - notifier.add_permits(1) - } - if slot.staging_notifiers.is_empty() { - e.remove(); - if self.pending.len() < self.cfg.max_pending { - if let Some(notifier) = self.fc_notifier.take() { - notifier.send(()).unwrap(); - } - } - } else { - assert!(slot.writing_notifiers.is_empty()); - std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers); - // write again - self.schedule_write(page_key); - } - } - _ => unreachable!(), - } - } - - async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), WALError> { - let mut aiobuilder = AIOBuilder::default(); - aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); - let aiomgr = aiobuilder.build().map_err(|_| WALError::Other)?; - let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)) - .map_err(|_| WALError::Other)?; - let mut loader = WALLoader::new(); - loader - .file_nbit(self.wal_cfg.file_nbit) - .block_nbit(self.wal_cfg.block_nbit) - .recover_policy(RecoverPolicy::Strict); - if self.wal.is_some() { - // already initialized - return Ok(()); - } - let wal = loader - .load( - store, - |raw, _| { - let batch = AshRecord::deserialize(raw); - for (space_id, Ash { old, new }) in batch.0 { - for (old, data) in old.into_iter().zip(new.into_iter()) { - let offset = old.offset; - let file_pool = self.file_pools[space_id as usize].as_ref().unwrap(); - let file_nbit = file_pool.get_file_nbit(); - let file_mask = (1 << file_nbit) - 1; - let fid = offset >> file_nbit; - nix::sys::uio::pwrite( - file_pool - .get_file(fid) - .map_err(|_| WALError::Other)? - .get_fd(), - &data, - (offset & file_mask) as nix::libc::off_t, - ) - .map_err(|_| WALError::Other)?; - } - } - Ok(()) - }, - self.wal_cfg.max_revisions, - ) - .await?; - self.wal = Some(Rc::new(Mutex::new(wal))); - Ok(()) - } - - async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec, AshRecord)>) { - use std::collections::hash_map::Entry::*; - loop { - let mut bwrites = Vec::new(); - let mut records = Vec::new(); - - if let Some((bw, ac)) = writes.recv().await { - records.push(ac); - bwrites.extend(bw); - } else { - break; - } - while let Ok((bw, ac)) = writes.try_recv() { - records.push(ac); - bwrites.extend(bw); - if records.len() >= self.cfg.wal_max_batch { - break; - } - } - // first write to WAL - let ring_ids: Vec<_> = - futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) - .await - .into_iter() - .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) - .collect(); - let sem = Rc::new(tokio::sync::Semaphore::new(0)); - let mut npermit = 0; - for BufferWrite { space_id, delta } in bwrites { - for w in delta.0 { - let page_key = (space_id, w.0); - match self.pending.entry(page_key) { - Occupied(mut e) => { - let e = e.get_mut(); - e.staging_data = w.1.into(); - e.staging_notifiers.push(sem.clone()); - npermit += 1; - } - Vacant(e) => { - let file_nbit = self.file_pools[page_key.0 as usize] - .as_ref() - .unwrap() - .file_nbit; - e.insert(PendingPage { - staging_data: w.1.into(), - file_nbit, - staging_notifiers: Vec::new(), - writing_notifiers: vec![sem.clone()], - }); - npermit += 1; - self.schedule_write(page_key); - } - } - } - } - let wal = self.wal.as_ref().unwrap().clone(); - let max_revisions = self.wal_cfg.max_revisions; - self.start_task(async move { - let _ = sem.acquire_many(npermit).await.unwrap(); - wal.lock() - .await - .peel(ring_ids, max_revisions) - .await - .map_err(|_| "WAL errore while pruning") - .unwrap(); - }); - if self.pending.len() >= self.cfg.max_pending { - let (tx, rx) = oneshot::channel(); - self.fc_notifier = Some(tx); - self.fc_blocker = Some(rx); - } - } - } - - async fn process( - &mut self, - req: BufferCmd, - wal_in: &mpsc::Sender<(Vec, AshRecord)>, - ) -> bool { - match req { - BufferCmd::Shutdown => return false, - BufferCmd::InitWAL(rootfd, waldir) => { - if (self.init_wal(rootfd, waldir).await).is_err() { - panic!("cannot initialize from WAL") - } - } - BufferCmd::GetPage(page_key, tx) => tx - .send(self.pending.get(&page_key).map(|e| e.staging_data.clone())) - .unwrap(), - BufferCmd::WriteBatch(writes, wal_writes) => { - wal_in.send((writes, wal_writes)).await.unwrap(); - } - BufferCmd::CollectAsh(nrecords, tx) => { - // wait to ensure writes are paused for WAL - let ash = self - .wal - .as_ref() - .unwrap() - .clone() - .lock() - .await - .read_recent_records(nrecords, &RecoverPolicy::Strict) - .await - .unwrap() - .into_iter() - .map(AshRecord::deserialize) - .collect(); - tx.send(ash).unwrap(); - } - BufferCmd::RegCachedSpace(space_id, files) => { - self.file_pools[space_id as usize] = Some(files) - } - } - true - } - - fn start_task + 'static>(&mut self, fut: F) { - let task_id = self.task_id; - self.task_id += 1; - let tasks = self.tasks.clone(); - self.tasks.borrow_mut().insert( - task_id, - Some(self.local_pool.spawn_local(async move { - fut.await; - tasks.borrow_mut().remove(&task_id); - })), - ); - } - - #[tokio::main(flavor = "current_thread")] - pub async fn run(mut self) { - let wal_in = { - let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered); - let s = unsafe { self.get_longlive_self() }; - self.start_task(s.run_wal_queue(rx)); - tx - }; - self.local_pool - .clone() - .run_until(async { - loop { - if let Some(fc) = self.fc_blocker.take() { - // flow control, wait until ready - fc.await.unwrap(); - } - let req = self.inbound.recv().await.unwrap(); - if !self.process(req, &wal_in).await { - break; - } - } - drop(wal_in); - let handles: Vec<_> = self - .tasks - .borrow_mut() - .iter_mut() - .map(|(_, task)| task.take().unwrap()) - .collect(); - for h in handles { - h.await.unwrap(); - } - }) - .await; - } -} - -#[derive(Clone, Debug)] -pub struct DiskBufferRequester { - sender: mpsc::Sender, -} - -impl Default for DiskBufferRequester { - fn default() -> Self { - Self { - sender: mpsc::channel(1).0, - } - } -} - -impl DiskBufferRequester { - pub fn new(sender: mpsc::Sender) -> Self { - Self { sender } - } - - pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option> { - let (resp_tx, resp_rx) = oneshot::channel(); - self.sender - .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) - .map_err(StoreError::Send) - .ok(); - resp_rx.blocking_recv().unwrap() - } - - pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { - self.sender - .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch)) - .map_err(StoreError::Send) - .ok(); - } - - pub fn shutdown(&self) { - self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap() - } - - pub fn init_wal(&self, waldir: &str, rootfd: Fd) { - self.sender - .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string())) - .map_err(StoreError::Send) - .ok(); - } - - pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { - let (resp_tx, resp_rx) = oneshot::channel(); - self.sender - .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx)) - .map_err(StoreError::Send) - .ok(); - resp_rx.blocking_recv().map_err(StoreError::Receive) - } - - pub fn reg_cached_space(&self, space: &CachedSpace) { - let mut inner = space.inner.borrow_mut(); - inner.disk_buffer = self.clone(); - self.sender - .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone())) - .map_err(StoreError::Send) - .ok(); - } -} From 0f07e36a9121634b38262e57511d2194377e7672 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Apr 2023 14:07:36 -0700 Subject: [PATCH 0127/1053] Add benchmarks and inline docs for merkle insert (#31) Also made the following changes: - Moved tests to #[cfg(test)] so they aren't in the final library - Renamed variables in the insert() method so it's easier to read: - chunks -> chunked_key - u_ref -> next_node - i -> key_nib_offset - u -> node - u_ptr -> node_ptr - nib -> key_nib Bench results: Running benches/hashops.rs running 3 tests test bench_dehydrate ... bench: 5 ns/iter (+/- 0) test bench_hydrate ... bench: 49 ns/iter (+/- 0) test bench_insert ... bench: 7,514 ns/iter (+/- 287) test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured --- firewood/Cargo.toml | 5 + firewood/benches/hashops.rs | 70 +++++++ firewood/src/merkle.rs | 365 +++++++++++++++++++++--------------- 3 files changed, 289 insertions(+), 151 deletions(-) create mode 100644 firewood/benches/hashops.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 71f3739105cf..c17a3ddf04c1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -44,9 +44,14 @@ assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.0.29" } +bencher = "0.1.5" [features] # proof API proof = [] # eth API eth = [] + +[[bench]] +name = "hashops" +harness = false diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs new file mode 100644 index 000000000000..2c9d1bc010f8 --- /dev/null +++ b/firewood/benches/hashops.rs @@ -0,0 +1,70 @@ +// hash benchmarks; run with 'cargo bench' +use std::ops::Deref; + +use bencher::{benchmark_group, benchmark_main, Bencher}; +use firewood::merkle::{Hash, Merkle, HASH_SIZE}; +use firewood_shale::{ + compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, PlainMem, +}; +use rand::{distributions::Alphanumeric, Rng, SeedableRng}; + +const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); + +fn bench_dehydrate(b: &mut Bencher) { + let mut to = [1u8; HASH_SIZE]; + b.iter(|| { + ZERO_HASH.dehydrate(&mut to); + }); +} + +fn bench_hydrate(b: &mut Bencher) { + let mut store = firewood_shale::PlainMem::new(HASH_SIZE as u64, 0u8); + store.write(0, ZERO_HASH.deref()); + + b.iter(|| { + Hash::hydrate(0, &store).unwrap(); + }); +} + +fn bench_insert(b: &mut Bencher) { + const TEST_MEM_SIZE: u64 = 20_000_000; + let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + + let merkle_payload_header_ref = MummyObj::ptr_to_obj( + &PlainMem::new(2 * firewood_shale::compact::CompactHeader::MSIZE, 9), + merkle_payload_header, + firewood_shale::compact::CompactHeader::MSIZE, + ) + .unwrap(); + + let store = firewood_shale::compact::CompactSpace::new( + PlainMem::new(TEST_MEM_SIZE, 0).into(), + PlainMem::new(TEST_MEM_SIZE, 1).into(), + merkle_payload_header_ref, + firewood_shale::ObjCache::new(1 << 20), + 4096, + 4096, + ) + .unwrap(); + let mut merkle = Merkle::new(Box::new(store)); + let mut root = ObjPtr::null(); + Merkle::init_root(&mut root, merkle.get_store()).unwrap(); + let mut rng = rand::rngs::StdRng::seed_from_u64(1234); + const KEY_LEN: usize = 4; + b.iter(|| { + // generate a random key + let k = (&mut rng) + .sample_iter(&Alphanumeric) + .take(KEY_LEN) + .collect::>(); + merkle.insert(k, vec![b'v'], root).unwrap(); + }); + #[cfg(trace)] + { + merkle.dump(root, &mut io::std::stdout().lock()).unwrap(); + println!("done\n---\n\n"); + } +} + +benchmark_group!(benches, bench_dehydrate, bench_hydrate, bench_insert); +benchmark_main!(benches); diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 11191611f0e9..b5a007cceb13 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -137,26 +137,6 @@ impl PartialPath { } } -#[test] -fn test_partial_path_encoding() { - let check = |steps: &[u8], term| { - let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term)); - assert_eq!(d.0, steps); - assert_eq!(t, term); - }; - for steps in [ - vec![0x1, 0x2, 0x3, 0x4], - vec![0x1, 0x2, 0x3], - vec![0x0, 0x1, 0x2], - vec![0x1, 0x2], - vec![0x1], - ] { - for term in [true, false] { - check(&steps, term) - } - } -} - #[derive(Debug, PartialEq, Eq, Clone)] pub struct Data(Vec); @@ -766,78 +746,6 @@ impl MummyItem for Node { } } -#[test] -fn test_merkle_node_encoding() { - let check = |node: Node| { - let mut bytes = Vec::new(); - bytes.resize(node.dehydrated_len() as usize, 0); - node.dehydrate(&mut bytes); - - let mut mem = shale::PlainMem::new(bytes.len() as u64, 0x0); - mem.write(0, &bytes); - println!("{bytes:?}"); - let node_ = Node::hydrate(0, &mem).unwrap(); - assert!(node == node_); - }; - let chd0 = [None; NBRANCH]; - let mut chd1 = chd0; - for node in chd1.iter_mut().take(NBRANCH / 2) { - *node = Some(ObjPtr::new_from_addr(0xa)); - } - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { - *rlp = Some(vec![0x1, 0x2, 0x3]); - } - for node in [ - Node::new_from_hash( - None, - None, - NodeType::Leaf(LeafNode( - PartialPath(vec![0x1, 0x2, 0x3]), - Data(vec![0x4, 0x5]), - )), - ), - Node::new_from_hash( - None, - None, - NodeType::Extension(ExtNode( - PartialPath(vec![0x1, 0x2, 0x3]), - ObjPtr::new_from_addr(0x42), - None, - )), - ), - Node::new_from_hash( - None, - None, - NodeType::Extension(ExtNode( - PartialPath(vec![0x1, 0x2, 0x3]), - ObjPtr::null(), - Some(vec![0x1, 0x2, 0x3]), - )), - ), - Node::new_from_hash( - None, - None, - NodeType::Branch(BranchNode { - chd: chd0, - value: Some(Data("hello, world!".as_bytes().to_vec())), - chd_eth_rlp: Default::default(), - }), - ), - Node::new_from_hash( - None, - None, - NodeType::Branch(BranchNode { - chd: chd1, - value: None, - chd_eth_rlp, - }), - ), - ] { - check(node); - } -} - macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let None = $r.write($modify) { @@ -1158,34 +1066,55 @@ impl Merkle { val: Vec, root: ObjPtr, ) -> Result<(), MerkleError> { + // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); - let mut chunks = vec![0]; - chunks.extend(to_nibbles(key.as_ref())); let mut parents = Vec::new(); - let mut u_ref = Some(self.get_node(root)?); + + // TODO: Explain why this always starts with a 0 chunk + // I think this may have to do with avoiding moving the root + let mut chunked_key = vec![0]; + chunked_key.extend(to_nibbles(key.as_ref())); + + let mut next_node = Some(self.get_node(root)?); let mut nskip = 0; + + // wrap the current value into an Option to indicate whether it has been + // inserted yet. If we haven't inserted it after we traverse the tree, we + // have to do some splitting let mut val = Some(val); - for (i, nib) in chunks.iter().enumerate() { + + // walk down the merkle tree starting from next_node, currently the root + for (key_nib_offset, key_nib) in chunked_key.iter().enumerate() { + // special handling for extension nodes if nskip > 0 { nskip -= 1; continue; } - let mut u = u_ref.take().unwrap(); - let u_ptr = u.as_ptr(); - let next_ptr = match &u.inner { - NodeType::Branch(n) => match n.chd[*nib as usize] { + // move the current node into node; next_node becomes None + // unwrap() is okay here since we are certain we have something + // in next_node at this point + let mut node = next_node.take().unwrap(); + let node_ptr = node.as_ptr(); + + let next_node_ptr = match &node.inner { + // For a Branch node, we look at the child pointer. If it points + // to another node, we walk down that. Otherwise, we can store our + // value as a leaf and we're done + NodeType::Branch(n) => match n.chd[*key_nib as usize] { Some(c) => c, None => { // insert the leaf to the empty slot + // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(chunks[i + 1..].to_vec()), + PartialPath(chunked_key[key_nib_offset + 1..].to_vec()), Data(val.take().unwrap()), ))))? .as_ptr(); - u.write(|u| { + // set the current child to point to this leaf + node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); - uu.chd[*nib as usize] = Some(leaf_ptr); + uu.chd[*key_nib as usize] = Some(leaf_ptr); u.rehash(); }) .unwrap(); @@ -1193,12 +1122,14 @@ impl Merkle { } }, NodeType::Leaf(n) => { + // we collided with another key; make a copy + // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); self.split( - u, + node, &mut parents, - &chunks[i..], + &chunked_key[key_nib_offset..], n_path, n_value, val.take().unwrap(), @@ -1211,30 +1142,36 @@ impl Merkle { let n_ptr = n.1; nskip = n_path.len() - 1; if let Some(v) = self.split( - u, + node, &mut parents, - &chunks[i..], + &chunked_key[key_nib_offset..], n_path, None, val.take().unwrap(), &mut deleted, )? { + // we couldn't split this, so we + // skip n_path items and follow the + // extension node's next pointer val = Some(v); - u = self.get_node(u_ptr)?; + node = self.get_node(node_ptr)?; n_ptr } else { + // successfully inserted break; } } }; - - parents.push((u, *nib)); - u_ref = Some(self.get_node(next_ptr)?); + // push another parent, and follow the next pointer + parents.push((node, *key_nib)); + next_node = Some(self.get_node(next_node_ptr)?); } if val.is_some() { + // we walked down the tree and reached the end of the key, + // but haven't inserted the value yet let mut info = None; let u_ptr = { - let mut u = u_ref.take().unwrap(); + let mut u = next_node.take().unwrap(); write_node!( self, u, @@ -1294,7 +1231,7 @@ impl Merkle { } } - drop(u_ref); + drop(next_node); for (mut r, _) in parents.into_iter().rev() { r.write(|u| u.rehash()).unwrap(); @@ -2015,19 +1952,24 @@ impl ValueTransformer for IdTrans { } } +// given a set of bytes, return a new iterator that returns a set of +// nibbles, high bits first, then low bits pub fn to_nibbles(bytes: &[u8]) -> impl Iterator + '_ { - bytes - .iter() - .flat_map(|b| [(b >> 4) & 0xf, b & 0xf].into_iter()) + bytes.iter().flat_map(|b| [b >> 4, b & 0xf]) } +// given a set of nibbles, take each pair and convert this back into bytes +// if an odd number of nibbles, in debug mode it panics. In release mode, +// the final nibble is dropped pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { - assert!(nibbles.len() & 1 == 0); + debug_assert_eq!(nibbles.len() & 1, 0); nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } +// compare two slices by comparing the bytes. A longer slice is greater +// than a shorter slice (assuming the leading bytes are equal) pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { - for (ai, bi) in a.iter().zip(b.iter()) { + for (ai, bi) in a.iter().zip(b) { match ai.cmp(bi) { cmp::Ordering::Equal => continue, ord => return ord, @@ -2038,40 +1980,161 @@ pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { a.len().cmp(&b.len()) } -#[test] -fn test_to_nibbles() { - for (bytes, nibbles) in [ - (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), - (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]), - ] { - let n: Vec<_> = to_nibbles(&bytes).collect(); - assert_eq!(n, nibbles); +#[cfg(test)] +mod test { + use super::*; + use std::ops::Deref; + + #[test] + fn test_to_nibbles() { + for (bytes, nibbles) in [ + (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), + (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]), + ] { + let n: Vec<_> = to_nibbles(&bytes).collect(); + assert_eq!(n, nibbles); + } } -} -#[test] -fn test_cmp() { - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x56]), - (vec![0xc0, 0xff], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_eq()); - } - - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x58]), - (vec![0xc0, 0xee], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_lt()); - } - - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x35, 0x56], vec![0x12, 0x34, 0x58]), - (vec![0xc0, 0xff, 0x33], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_gt()); + #[test] + fn test_cmp() { + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x56]), + (vec![0xc0, 0xff], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert!(n.is_eq()); + } + + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x58]), + (vec![0xc0, 0xee], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert!(n.is_lt()); + } + + for (bytes_a, bytes_b) in [ + (vec![0x12, 0x35, 0x56], vec![0x12, 0x34, 0x58]), + (vec![0xc0, 0xff, 0x33], vec![0xc0, 0xff]), + ] { + let n = compare(&bytes_a, &bytes_b); + assert!(n.is_gt()); + } + } + + const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); + + #[test] + fn test_hash_len() { + assert_eq!(HASH_SIZE, ZERO_HASH.dehydrated_len() as usize); + } + #[test] + fn test_dehydrate() { + let mut to = [1u8; HASH_SIZE]; + assert_eq!( + { + ZERO_HASH.dehydrate(&mut to); + &to + }, + ZERO_HASH.deref() + ); + } + + #[test] + fn test_hydrate() { + let mut store = shale::PlainMem::new(HASH_SIZE as u64, 0u8); + store.write(0, ZERO_HASH.deref()); + assert_eq!(Hash::hydrate(0, &store).unwrap(), ZERO_HASH); + } + #[test] + fn test_partial_path_encoding() { + let check = |steps: &[u8], term| { + let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term)); + assert_eq!(d.0, steps); + assert_eq!(t, term); + }; + for steps in [ + vec![0x1, 0x2, 0x3, 0x4], + vec![0x1, 0x2, 0x3], + vec![0x0, 0x1, 0x2], + vec![0x1, 0x2], + vec![0x1], + ] { + for term in [true, false] { + check(&steps, term) + } + } + } + #[test] + fn test_merkle_node_encoding() { + let check = |node: Node| { + let mut bytes = Vec::new(); + bytes.resize(node.dehydrated_len() as usize, 0); + node.dehydrate(&mut bytes); + + let mut mem = shale::PlainMem::new(bytes.len() as u64, 0x0); + mem.write(0, &bytes); + println!("{bytes:?}"); + let node_ = Node::hydrate(0, &mem).unwrap(); + assert!(node == node_); + }; + let chd0 = [None; NBRANCH]; + let mut chd1 = chd0; + for node in chd1.iter_mut().take(NBRANCH / 2) { + *node = Some(ObjPtr::new_from_addr(0xa)); + } + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { + *rlp = Some(vec![0x1, 0x2, 0x3]); + } + for node in [ + Node::new_from_hash( + None, + None, + NodeType::Leaf(LeafNode( + PartialPath(vec![0x1, 0x2, 0x3]), + Data(vec![0x4, 0x5]), + )), + ), + Node::new_from_hash( + None, + None, + NodeType::Extension(ExtNode( + PartialPath(vec![0x1, 0x2, 0x3]), + ObjPtr::new_from_addr(0x42), + None, + )), + ), + Node::new_from_hash( + None, + None, + NodeType::Extension(ExtNode( + PartialPath(vec![0x1, 0x2, 0x3]), + ObjPtr::null(), + Some(vec![0x1, 0x2, 0x3]), + )), + ), + Node::new_from_hash( + None, + None, + NodeType::Branch(BranchNode { + chd: chd0, + value: Some(Data("hello, world!".as_bytes().to_vec())), + chd_eth_rlp: Default::default(), + }), + ), + Node::new_from_hash( + None, + None, + NodeType::Branch(BranchNode { + chd: chd1, + value: None, + chd_eth_rlp, + }), + ), + ] { + check(node); + } } } From 7cbaa5247c81d97ffc0eb8c98a472f65e25aab2f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Apr 2023 14:33:16 -0700 Subject: [PATCH 0128/1053] rkuris/rename structs (#32) --- firewood-shale/benches/shale-bench.rs | 4 +- firewood-shale/src/compact.rs | 64 ++++++++-------- firewood-shale/src/lib.rs | 104 ++++++++++++++------------ firewood/benches/hashops.rs | 4 +- firewood/src/account.rs | 10 +-- firewood/src/db.rs | 20 ++--- firewood/src/dynamic_mem.rs | 14 ++-- firewood/src/lib.rs | 14 ++-- firewood/src/merkle.rs | 34 ++++----- firewood/src/merkle_util.rs | 4 +- firewood/src/storage/buffer.rs | 2 +- firewood/src/storage/mod.rs | 29 +++---- 12 files changed, 156 insertions(+), 147 deletions(-) diff --git a/firewood-shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs index 7e548d1fed3d..dc27b992d013 100644 --- a/firewood-shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -2,7 +2,7 @@ use bencher::{benchmark_group, benchmark_main, Bencher}; extern crate firewood_shale as shale; use rand::Rng; -use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr, PlainMem}; +use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, PlainMem, StoredView}; fn get_view(b: &mut Bencher) { const SIZE: u64 = 2_000_000; @@ -29,7 +29,7 @@ fn serialize(b: &mut Bencher) { b.iter(|| { let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); let _compact_header = - MummyObj::ptr_to_obj(&m, compact_header_obj, shale::compact::CompactHeader::MSIZE) + StoredView::ptr_to_obj(&m, compact_header_obj, shale::compact::CompactHeader::MSIZE) .unwrap(); }); } diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index bd7ee3de5dc0..9a935ebdca40 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -1,4 +1,4 @@ -use super::{MemStore, MummyItem, MummyObj, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore}; +use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; use std::cell::UnsafeCell; use std::io::{Cursor, Write}; use std::rc::Rc; @@ -20,11 +20,11 @@ impl CompactHeader { } } -impl MummyItem for CompactHeader { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for CompactHeader { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); let is_freed = raw.as_deref()[8] != 0; let desc_addr = u64::from_le_bytes(raw.as_deref()[9..17].try_into().unwrap()); @@ -55,11 +55,11 @@ impl CompactFooter { const MSIZE: u64 = 8; } -impl MummyItem for CompactFooter { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for CompactFooter { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); Ok(Self { payload_size }) } @@ -85,11 +85,11 @@ impl CompactDescriptor { const MSIZE: u64 = 16; } -impl MummyItem for CompactDescriptor { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for CompactDescriptor { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); Ok(Self { @@ -146,19 +146,19 @@ impl CompactSpaceHeader { fn into_fields(r: Obj) -> Result { Ok(CompactSpaceHeaderSliced { - meta_space_tail: MummyObj::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, - compact_space_tail: MummyObj::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, - base_addr: MummyObj::slice(&r, 16, 8, r.base_addr)?, - alloc_addr: MummyObj::slice(&r, 24, 8, r.alloc_addr)?, + meta_space_tail: StoredView::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, + compact_space_tail: StoredView::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, + base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, + alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, }) } } -impl MummyItem for CompactSpaceHeader { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for CompactSpaceHeader { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let meta_space_tail = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); let compact_space_tail = u64::from_le_bytes(raw.as_deref()[8..16].try_into().unwrap()); let base_addr = u64::from_le_bytes(raw.as_deref()[16..24].try_into().unwrap()); @@ -192,11 +192,11 @@ impl ObjPtrField { const MSIZE: u64 = 8; } -impl MummyItem for ObjPtrField { - fn hydrate(addr: u64, mem: &U) -> Result { +impl Storable for ObjPtrField { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( raw.as_deref().try_into().unwrap(), )))) @@ -232,11 +232,11 @@ impl U64Field { const MSIZE: u64 = 8; } -impl MummyItem for U64Field { - fn hydrate(addr: u64, mem: &U) -> Result { +impl Storable for U64Field { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) } @@ -262,7 +262,7 @@ impl std::ops::DerefMut for U64Field { } } -struct CompactSpaceInner { +struct CompactSpaceInner { meta_space: Rc, compact_space: Rc, header: CompactSpaceHeaderSliced, @@ -271,20 +271,20 @@ struct CompactSpaceInner { regn_nbit: u64, } -impl CompactSpaceInner { +impl CompactSpaceInner { fn get_descriptor( &self, ptr: ObjPtr, ) -> Result, ShaleError> { - MummyObj::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) + StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } - fn get_data_ref( + fn get_data_ref( &self, ptr: ObjPtr, len_limit: u64, ) -> Result, ShaleError> { - MummyObj::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) + StoredView::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) } fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { @@ -511,11 +511,11 @@ impl CompactSpaceInner { } } -pub struct CompactSpace { +pub struct CompactSpace { inner: UnsafeCell>, } -impl CompactSpace { +impl CompactSpace { pub fn new( meta_space: Rc, compact_space: Rc, @@ -538,7 +538,7 @@ impl CompactSpace { } } -impl ShaleStore for CompactSpace { +impl ShaleStore for CompactSpace { fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let inner = unsafe { &mut *self.inner.get() }; @@ -548,7 +548,7 @@ impl ShaleStore for CompactSpace { } else { inner.alloc_new(size)? }); - let mut u = inner.obj_cache.put(MummyObj::item_to_obj( + let mut u = inner.obj_cache.put(StoredView::item_to_obj( inner.compact_space.as_ref(), ptr.addr(), size, diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index 27d859a4c835..e0cf564223a4 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -13,7 +13,7 @@ pub mod util; #[derive(Debug)] pub enum ShaleError { - LinearMemStoreError, + LinearCachedStoreError, DecodeError, ObjRefAlreadyInUse, ObjPtrInvalid, @@ -42,7 +42,7 @@ impl std::fmt::Debug for DiskWrite { } /// A handle that pins and provides a readable access to a portion of the linear memory image. -pub trait MemView { +pub trait CachedView { type DerefReturn: Deref; fn as_deref(&self) -> Self::DerefReturn; } @@ -51,15 +51,18 @@ pub trait MemView { /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear /// persistent store. Reads could trigger disk reads to bring data into memory, but writes will /// *only* be visible in memory (it does not write back to the disk). -pub trait MemStore: Debug { +pub trait CachedStore: Debug { /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them /// directly accessible. - fn get_view(&self, offset: u64, length: u64) - -> Option>>>; + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>>; /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Option>>; + fn get_shared(&self) -> Option>>; /// Write the `change` to the portion of the linear space starting at `offset`. The change - /// should be immediately visible to all `MemView` associated to this linear space. + /// should be immediately visible to all `CachedView` associated to this linear space. fn write(&mut self, offset: u64, change: &[u8]); /// Returns the identifier of this storage space. fn id(&self) -> SpaceID; @@ -129,10 +132,10 @@ impl ObjPtr { pub trait TypedView: Deref { /// Get the offset of the initial byte in the linear space. fn get_offset(&self) -> u64; - /// Access it as a [MemStore] object. - fn get_mem_store(&self) -> &dyn MemStore; - /// Access it as a mutable MemStore object - fn get_mut_mem_store(&mut self) -> &mut dyn MemStore; + /// Access it as a [CachedStore] object. + fn get_mem_store(&self) -> &dyn CachedStore; + /// Access it as a mutable CachedStore object + fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore; /// Estimate the serialized length of the current type content. It should not be smaller than /// the actually length. fn estimate_mem_image(&self) -> Option; @@ -143,12 +146,12 @@ pub trait TypedView: Deref { /// could change. fn write(&mut self) -> &mut T; /// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto - /// reflected in the underlying [MemStore]). + /// reflected in the underlying [CachedStore]). fn is_mem_mapped(&self) -> bool; } /// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] -/// or [MummyObj::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. +/// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. /// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. pub struct Obj { @@ -189,7 +192,7 @@ impl Obj { let mut new_value = vec![0; new_value_len as usize]; self.value.write_mem_image(&mut new_value); let offset = self.value.get_offset(); - let bx: &mut dyn MemStore = self.value.get_mut_mem_store(); + let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); bx.write(offset, &new_value); } } @@ -270,10 +273,10 @@ pub trait ShaleStore { /// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient /// implementation could be directly transmuting to/from a POD struct. But sometimes necessary /// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. -pub trait MummyItem { +pub trait Storable { fn dehydrated_len(&self) -> u64; fn dehydrate(&self, to: &mut [u8]); - fn hydrate(addr: u64, mem: &T) -> Result + fn hydrate(addr: u64, mem: &T) -> Result where Self: Sized; fn is_mem_mapped(&self) -> bool { @@ -281,38 +284,37 @@ pub trait MummyItem { } } -pub fn to_dehydrated(item: &dyn MummyItem) -> Vec { +pub fn to_dehydrated(item: &dyn Storable) -> Vec { let mut buff = vec![0; item.dehydrated_len() as usize]; item.dehydrate(&mut buff); buff } -/// Reference implementation of [TypedView]. It takes any type that implements [MummyItem] and -/// should be useful for most applications. -pub struct MummyObj { +/// Reference implementation of [TypedView]. It takes any type that implements [Storable] +pub struct StoredView { decoded: T, - mem: Box>, + mem: Box>, offset: u64, len_limit: u64, } -impl Deref for MummyObj { +impl Deref for StoredView { type Target = T; fn deref(&self) -> &T { &self.decoded } } -impl TypedView for MummyObj { +impl TypedView for StoredView { fn get_offset(&self) -> u64 { self.offset } - fn get_mem_store(&self) -> &dyn MemStore { + fn get_mem_store(&self) -> &dyn CachedStore { &**self.mem } - fn get_mut_mem_store(&mut self) -> &mut dyn MemStore { + fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore { &mut **self.mem } @@ -337,14 +339,16 @@ impl TypedView for MummyObj { } } -impl MummyObj { +impl StoredView { #[inline(always)] - fn new(offset: u64, len_limit: u64, space: &U) -> Result { + fn new(offset: u64, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; Ok(Self { offset, decoded, - mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + mem: space + .get_shared() + .ok_or(ShaleError::LinearCachedStoreError)?, len_limit, }) } @@ -354,18 +358,20 @@ impl MummyObj { offset: u64, len_limit: u64, decoded: T, - space: &dyn MemStore, + space: &dyn CachedStore, ) -> Result { Ok(Self { offset, decoded, - mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + mem: space + .get_shared() + .ok_or(ShaleError::LinearCachedStoreError)?, len_limit, }) } #[inline(always)] - pub fn ptr_to_obj( + pub fn ptr_to_obj( store: &U, ptr: ObjPtr, len_limit: u64, @@ -379,7 +385,7 @@ impl MummyObj { #[inline(always)] pub fn item_to_obj( - store: &dyn MemStore, + store: &dyn CachedStore, addr: u64, len_limit: u64, decoded: T, @@ -390,22 +396,24 @@ impl MummyObj { } } -impl MummyObj { +impl StoredView { fn new_from_slice( offset: u64, len_limit: u64, decoded: T, - space: &dyn MemStore, + space: &dyn CachedStore, ) -> Result { Ok(Self { offset, decoded, - mem: space.get_shared().ok_or(ShaleError::LinearMemStoreError)?, + mem: space + .get_shared() + .ok_or(ShaleError::LinearCachedStoreError)?, len_limit, }) } - pub fn slice( + pub fn slice( s: &Obj, offset: u64, length: u64, @@ -415,7 +423,7 @@ impl MummyObj { if s.dirty.is_some() { return Err(ShaleError::SliceError); } - let r = Box::new(MummyObj::new_from_slice( + let r = Box::new(StoredView::new_from_slice( addr_, length, decoded, @@ -432,7 +440,7 @@ impl ObjPtr { const MSIZE: u64 = 8; } -impl MummyItem for ObjPtr { +impl Storable for ObjPtr { fn dehydrated_len(&self) -> u64 { Self::MSIZE } @@ -444,10 +452,10 @@ impl MummyItem for ObjPtr { .unwrap(); } - fn hydrate(addr: u64, mem: &U) -> Result { + fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let addrdyn = raw.deref(); let addrvec = addrdyn.as_deref(); Ok(Self::new_from_addr(u64::from_le_bytes( @@ -456,9 +464,9 @@ impl MummyItem for ObjPtr { } } -/// Purely volatile, vector-based implementation for [MemStore]. This is good for testing or trying +/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying /// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write -/// your own [MemStore] implementation. +/// your own [CachedStore] implementation. #[derive(Debug)] pub struct PlainMem { space: Rc>>, @@ -474,12 +482,12 @@ impl PlainMem { } } -impl MemStore for PlainMem { +impl CachedStore for PlainMem { fn get_view( &self, offset: u64, length: u64, - ) -> Option>>> { + ) -> Option>>> { let offset = offset as usize; let length = length as usize; if offset + length > self.space.borrow().len() { @@ -496,7 +504,7 @@ impl MemStore for PlainMem { } } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(PlainMemShared(Self { space: self.space.clone(), id: self.id, @@ -530,13 +538,13 @@ impl DerefMut for PlainMemShared { } impl Deref for PlainMemShared { - type Target = dyn MemStore; - fn deref(&self) -> &(dyn MemStore + 'static) { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { &self.0 } } -impl MemView for PlainMemView { +impl CachedView for PlainMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 2c9d1bc010f8..75a9affa6047 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use bencher::{benchmark_group, benchmark_main, Bencher}; use firewood::merkle::{Hash, Merkle, HASH_SIZE}; use firewood_shale::{ - compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, PlainMem, + compact::CompactSpaceHeader, CachedStore, ObjPtr, PlainMem, Storable, StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; @@ -30,7 +30,7 @@ fn bench_insert(b: &mut Bencher) { const TEST_MEM_SIZE: u64 = 20_000_000; let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let merkle_payload_header_ref = MummyObj::ptr_to_obj( + let merkle_payload_header_ref = StoredView::ptr_to_obj( &PlainMem::new(2 * firewood_shale::compact::CompactHeader::MSIZE, 9), merkle_payload_header, firewood_shale::compact::CompactHeader::MSIZE, diff --git a/firewood/src/account.rs b/firewood/src/account.rs index 3ed151c0062e..3842dd712375 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -6,7 +6,7 @@ use std::io::{Cursor, Write}; use crate::merkle::{Hash, Node, ValueTransformer}; use primitive_types::U256; -use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; +use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; pub struct Account { pub nonce: u64, @@ -98,16 +98,16 @@ pub enum Blob { Code(Vec), } -impl MummyItem for Blob { +impl Storable for Blob { // currently there is only one variant of Blob: Code - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, 4) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let len = u32::from_le_bytes(raw.as_deref()[..].try_into().unwrap()) as u64; let bytes = mem .get_view(addr + 4, len) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; Ok(Self::Code(bytes.as_deref())) } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 3fd4c61feca1..8e339f102060 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -13,7 +13,7 @@ use std::thread::JoinHandle; use bytemuck::{cast_slice, AnyBitPattern}; use parking_lot::{Mutex, RwLock}; use primitive_types::U256; -use shale::{compact::CompactSpaceHeader, MemStore, MummyItem, MummyObj, ObjPtr, SpaceID}; +use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceID, Storable, StoredView}; use typed_builder::TypedBuilder; use crate::account::{Account, AccountRLP, Blob, BlobStash}; @@ -194,11 +194,11 @@ impl DBHeader { } } -impl MummyItem for DBHeader { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for DBHeader { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(shale::ShaleError::LinearMemStoreError)?; + .ok_or(shale::ShaleError::LinearCachedStoreError)?; let acc_root = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); let kv_root = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); Ok(Self { @@ -613,14 +613,14 @@ impl DB { let blob_meta_ref = staging.blob.meta.as_ref(); ( - MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), - MummyObj::ptr_to_obj( + StoredView::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + StoredView::ptr_to_obj( merkle_meta_ref, merkle_payload_header, shale::compact::CompactHeader::MSIZE, ) .unwrap(), - MummyObj::ptr_to_obj( + StoredView::ptr_to_obj( blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE, @@ -816,14 +816,14 @@ impl DB { let blob_meta_ref = &space.blob.meta; ( - MummyObj::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), - MummyObj::ptr_to_obj( + StoredView::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + StoredView::ptr_to_obj( merkle_meta_ref, merkle_payload_header, shale::compact::CompactHeader::MSIZE, ) .unwrap(), - MummyObj::ptr_to_obj( + StoredView::ptr_to_obj( blob_meta_ref, blob_payload_header, shale::compact::CompactHeader::MSIZE, diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index 6d0dc03895cb..b44ae6bfc06b 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -9,7 +9,7 @@ use shale::*; pub type SpaceID = u8; -/// Purely volatile, dynamically allocated vector-based implementation for [MemStore]. This is similar to +/// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to /// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is /// not enough. #[derive(Debug)] @@ -31,12 +31,12 @@ impl DynamicMem { } } -impl MemStore for DynamicMem { +impl CachedStore for DynamicMem { fn get_view( &self, offset: u64, length: u64, - ) -> Option>>> { + ) -> Option>>> { let offset = offset as usize; let length = length as usize; let size = offset + length; @@ -54,7 +54,7 @@ impl MemStore for DynamicMem { })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(DynamicMemShared(Self { space: self.space.clone(), id: self.id, @@ -93,8 +93,8 @@ impl Deref for DynamicMemView { } impl Deref for DynamicMemShared { - type Target = dyn MemStore; - fn deref(&self) -> &(dyn MemStore + 'static) { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { &self.0 } } @@ -105,7 +105,7 @@ impl DerefMut for DynamicMemShared { } } -impl MemView for DynamicMemView { +impl CachedView for DynamicMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 90c99bece402..eeb958a45114 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -72,17 +72,17 @@ //! layout/representation of the data on disk from the actual logical data structure it retains: //! //! - Linear, memory-like space: the [shale](https://crates.io/crates/shale) crate from an academic -//! project (CedrusDB) code offers a `MemStore` abstraction for a (64-bit) byte-addressable space +//! project (CedrusDB) code offers a `CachedStore` abstraction for a (64-bit) byte-addressable space //! that abstracts away the intricate method that actually persists the in-memory data on the -//! secondary storage medium (e.g., hard drive). The implementor of `MemStore` will provide the -//! functions to give the user of `MemStore` an illusion that the user is operating upon a +//! secondary storage medium (e.g., hard drive). The implementor of `CachedStore` will provide the +//! functions to give the user of `CachedStore` an illusion that the user is operating upon a //! byte-addressable memory space. It is just a "magical" array of bytes one can view and change //! that is mirrored to the disk. In reality, the linear space will be chunked into files under a //! directory, but the user does not have to even know about this. //! //! - Persistent item storage stash: `ShaleStore` trait from `shale` defines a pool of typed //! objects that are persisted on disk but also made accessible in memory transparently. It is -//! built on top of `MemStore` by defining how "items" of the given type are laid out, allocated +//! built on top of `CachedStore` by defining how "items" of the given type are laid out, allocated //! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of //! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). //! @@ -99,7 +99,7 @@ //!

    //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (trie) is what the linear space (`MemStore`) keeps track of, that is, +//! state of the data structure (trie) is what the linear space (`CachedStore`) keeps track of, that is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the //! content of the linear space, the higher level data structure can be *uniquely* determined, there //! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) @@ -107,7 +107,7 @@ //! separate the logical data from its physical representation, greatly simplifies the storage //! management, and allows reusing the code. It is still a very versatile abstraction, as in theory //! any persistent data could be stored this way -- sometimes you need to swap in a different -//! `MemShale` or `MemStore` implementation, but without having to touch the code for the persisted +//! `MemShale` or `CachedStore` implementation, but without having to touch the code for the persisted //! data structure. //! //! ## Page-based Shadowing and Revisions @@ -117,7 +117,7 @@ //! space. The writes may overlap and some frequent writes are even done to the same spot in the //! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit //! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the -//! dirty pages in some `MemStore` instantiation (see `storage::StoreRevMut`). When a +//! dirty pages in some `CachedStore` instantiation (see `storage::StoreRevMut`). When a //! [`db::WriteBatch`] commits, both the recorded interval writes and the aggregated in-memory //! dirty pages induced by this write batch are taken out from the linear space. Although they are //! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b5a007cceb13..d3b34b5fc0cb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,7 +6,7 @@ use crate::proof::Proof; use enum_as_inner::EnumAsInner; use once_cell::unsync::OnceCell; use sha3::Digest; -use shale::{MemStore, MummyItem, ObjPtr, ObjRef, ShaleError, ShaleStore}; +use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; use std::cell::Cell; use std::cmp; @@ -57,11 +57,11 @@ impl std::ops::Deref for Hash { } } -impl MummyItem for Hash { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for Hash { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; Ok(Self( raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), )) @@ -477,13 +477,13 @@ impl Node { const ETH_RLP_LONG_BIT: u8 = 1 << 2; } -impl MummyItem for Node { - fn hydrate(addr: u64, mem: &T) -> Result { +impl Storable for Node { + fn hydrate(addr: u64, mem: &T) -> Result { let dec_err = |_| ShaleError::DecodeError; const META_SIZE: u64 = 32 + 1 + 1; let meta_raw = mem .get_view(addr, META_SIZE) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let attrs = meta_raw.as_deref()[32]; let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None @@ -502,7 +502,7 @@ impl MummyItem for Node { let branch_header_size = NBRANCH as u64 * 8 + 4; let node_raw = mem .get_view(addr + META_SIZE, branch_header_size) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let mut cur = Cursor::new(node_raw.as_deref()); let mut chd = [None; NBRANCH]; let mut buff = [0; 8]; @@ -522,7 +522,7 @@ impl MummyItem for Node { } else { Some(Data( mem.get_view(addr + META_SIZE + branch_header_size, raw_len) - .ok_or(ShaleError::LinearMemStoreError)? + .ok_or(ShaleError::LinearCachedStoreError)? .as_deref(), )) }; @@ -537,7 +537,7 @@ impl MummyItem for Node { let mut buff = [0_u8; 1]; let rlp_len_raw = mem .get_view(offset + cur_rlp_len, 1) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; cur = Cursor::new(rlp_len_raw.as_deref()); cur.read_exact(&mut buff) .map_err(|_| ShaleError::DecodeError)?; @@ -546,7 +546,7 @@ impl MummyItem for Node { if rlp_len != 0 { let rlp_raw = mem .get_view(offset + cur_rlp_len, rlp_len) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); *chd_rlp = Some(rlp); cur_rlp_len += rlp_len @@ -567,7 +567,7 @@ impl MummyItem for Node { let ext_header_size = 1 + 8; let node_raw = mem .get_view(addr + META_SIZE, ext_header_size) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 8]; cur.read_exact(&mut buff[..1]) @@ -578,7 +578,7 @@ impl MummyItem for Node { let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( &mem.get_view(addr + META_SIZE + ext_header_size, path_len) - .ok_or(ShaleError::LinearMemStoreError)? + .ok_or(ShaleError::LinearCachedStoreError)? .as_deref(), ) .collect(); @@ -587,7 +587,7 @@ impl MummyItem for Node { let mut buff = [0_u8; 1]; let rlp_len_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len, 1) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; cur = Cursor::new(rlp_len_raw.as_deref()); cur.read_exact(&mut buff) .map_err(|_| ShaleError::DecodeError)?; @@ -595,7 +595,7 @@ impl MummyItem for Node { let rlp: Option> = if rlp_len != 0 { let rlp_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len + 1, rlp_len) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; Some(rlp_raw.as_deref()[0..].to_vec()) } else { None @@ -611,7 +611,7 @@ impl MummyItem for Node { let leaf_header_size = 1 + 4; let node_raw = mem .get_view(addr + META_SIZE, leaf_header_size) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 4]; cur.read_exact(&mut buff[..1]) @@ -622,7 +622,7 @@ impl MummyItem for Node { let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) - .ok_or(ShaleError::LinearMemStoreError)?; + .ok_or(ShaleError::LinearCachedStoreError)?; let nibbles: Vec<_> = to_nibbles(&remainder.as_deref()[..path_len as usize]).collect(); let (path, _) = PartialPath::decode(nibbles); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 181479bf499e..25e8b3e98515 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -4,7 +4,7 @@ use crate::merkle::*; use crate::proof::Proof; use crate::{dynamic_mem::DynamicMem, proof::ProofError}; -use shale::{compact::CompactSpaceHeader, MemStore, MummyObj, ObjPtr}; +use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, StoredView}; use std::rc::Rc; use thiserror::Error; @@ -124,7 +124,7 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), ); let compact_header = - MummyObj::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); + StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); let mem_meta = Rc::new(dm); let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 302df06529e7..84d632c5c50c 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -487,7 +487,7 @@ mod tests { file, storage::{DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta}, }; - use shale::MemStore; + use shale::CachedStore; const STATE_SPACE: SpaceID = 0x0; #[test] diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index e660855c6652..2e48949b2b9a 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -9,8 +9,9 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; +use shale::{CachedStore, CachedView, SpaceID}; + use nix::fcntl::{flock, FlockArg}; -use shale::{MemStore, MemView, SpaceID}; use thiserror::Error; use tokio::sync::mpsc::error::SendError; use tokio::sync::oneshot::error::RecvError; @@ -333,22 +334,22 @@ impl StoreRevShared { } } -impl MemStore for StoreRevShared { +impl CachedStore for StoreRevShared { fn get_view( &self, offset: u64, length: u64, - ) -> Option>>> { + ) -> Option>>> { let data = self.0.get_slice(offset, length)?; Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(StoreShared(self.clone()))) } fn write(&mut self, _offset: u64, _change: &[u8]) { - // StoreRevShared is a read-only view version of MemStore + // StoreRevShared is a read-only view version of CachedStore // Writes could be induced by lazy hashing and we can just ignore those } @@ -369,7 +370,7 @@ impl Deref for StoreRef { } } -impl MemView for StoreRef { +impl CachedView for StoreRef { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { @@ -377,16 +378,16 @@ impl MemView for StoreRef { } } -struct StoreShared(S); +struct StoreShared(S); -impl Deref for StoreShared { - type Target = dyn MemStore; - fn deref(&self) -> &(dyn MemStore + 'static) { +impl Deref for StoreShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { &self.0 } } -impl DerefMut for StoreShared { +impl DerefMut for StoreShared { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -447,12 +448,12 @@ impl StoreRevMut { } } -impl MemStore for StoreRevMut { +impl CachedStore for StoreRevMut { fn get_view( &self, offset: u64, length: u64, - ) -> Option>>> { + ) -> Option>>> { let data = if length == 0 { Vec::new() } else { @@ -491,7 +492,7 @@ impl MemStore for StoreRevMut { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { + fn get_shared(&self) -> Option>> { Some(Box::new(StoreShared(self.clone()))) } From 59b2401ee491b07f832fc4f83c71ba525d86d8c8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 20 Apr 2023 11:40:33 -0700 Subject: [PATCH 0129/1053] Conditional compile additional eth-only code (#43) --- firewood/examples/dump.rs | 13 ++ firewood/examples/simple.rs | 10 + firewood/src/db.rs | 442 +++++++++++++++++++----------------- firewood/src/dynamic_mem.rs | 1 + firewood/src/lib.rs | 1 + firewood/src/proof.rs | 5 + 6 files changed, 265 insertions(+), 207 deletions(-) diff --git a/firewood/examples/dump.rs b/firewood/examples/dump.rs index 1b716b8bee08..93d1d3eaa8eb 100644 --- a/firewood/examples/dump.rs +++ b/firewood/examples/dump.rs @@ -1,10 +1,20 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#[cfg(not(feature = "eth"))] +fn main() { + println!("To run this example, you must enable to eth feature."); + println!("For more information on features, see:"); + println!("https://doc.rust-lang.org/cargo/reference/features.html"); +} + +#[cfg(feature = "eth")] use clap::{command, Arg, ArgMatches}; +#[cfg(feature = "eth")] use firewood::db::{DBConfig, DBError, WALConfig, DB}; /// cargo run --example dump benchmark_db/ +#[cfg(feature = "eth")] fn main() { let matches = command!() .arg( @@ -21,14 +31,17 @@ fn main() { ) .unwrap(); let mut stdout = std::io::stdout(); + println!("== Account Model =="); db.dump(&mut stdout).unwrap(); + println!("== Generic KV =="); db.kv_dump(&mut stdout).unwrap(); } /// Returns the provided INPUT db path if one is provided. /// Otherwise, instantiate a DB called simple_db and return the path. +#[cfg(feature = "eth")] fn get_db_path(matches: ArgMatches) -> Result { if let Some(m) = matches.get_one::("INPUT") { return Ok(m.to_string()); diff --git a/firewood/examples/simple.rs b/firewood/examples/simple.rs index 2630ed6bdc6a..1b71431b5206 100644 --- a/firewood/examples/simple.rs +++ b/firewood/examples/simple.rs @@ -1,8 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#[cfg(feature = "eth")] use firewood::db::{DBConfig, WALConfig, DB}; +#[cfg(feature = "eth")] fn print_states(db: &DB) { println!("======"); for account in ["ted", "alice"] { @@ -25,7 +27,15 @@ fn print_states(db: &DB) { } } +#[cfg(not(feature = "eth"))] +fn main() { + println!("To run this example, you must enable to eth feature."); + println!("For more information on features, see:"); + println!("https://doc.rust-lang.org/cargo/reference/features.html"); +} + /// cargo run --example simple +#[cfg(feature = "eth")] fn main() { let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8e339f102060..36f9b5a241d8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -12,10 +12,12 @@ use std::thread::JoinHandle; use bytemuck::{cast_slice, AnyBitPattern}; use parking_lot::{Mutex, RwLock}; +#[cfg(feature = "eth")] use primitive_types::U256; use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceID, Storable, StoredView}; use typed_builder::TypedBuilder; +#[cfg(feature = "eth")] use crate::account::{Account, AccountRLP, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; @@ -39,6 +41,7 @@ const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; pub enum DBError { InvalidParams, Merkle(MerkleError), + #[cfg(feature = "eth")] Blob(crate::account::BlobError), System(nix::Error), KeyNotFound, @@ -50,6 +53,7 @@ impl fmt::Display for DBError { match self { DBError::InvalidParams => write!(f, "invalid parameters provided"), DBError::Merkle(e) => write!(f, "merkle error: {e:?}"), + #[cfg(feature = "eth")] DBError::Blob(e) => write!(f, "storage error: {e:?}"), DBError::System(e) => write!(f, "system error: {e:?}"), DBError::KeyNotFound => write!(f, "not found"), @@ -263,6 +267,7 @@ impl Universe> { pub struct DBRev { header: shale::Obj, merkle: Merkle, + #[cfg(feature = "eth")] blob: BlobStash, } @@ -270,12 +275,19 @@ impl DBRev { fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); self.merkle.flush_dirty()?; - self.blob.flush_dirty() + #[cfg(feature = "eth")] + self.blob.flush_dirty()?; + Some(()) } + #[cfg(feature = "eth")] fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { (&mut self.header, &mut self.merkle, &mut self.blob) } + #[cfg(not(feature = "eth"))] + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { + (&mut self.header, &mut self.merkle) + } /// Get root hash of the generic key-value storage. pub fn kv_root_hash(&self) -> Result { @@ -300,6 +312,55 @@ impl DBRev { .map_err(DBError::Merkle) } + /// Provides a proof that a key is in the Trie. + pub fn prove>(&self, key: K) -> Result { + self.merkle + .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) + } + + /// Verifies a range proof is valid for a set of keys. + pub fn verify_range_proof, V: AsRef<[u8]>>( + &self, + proof: Proof, + first_key: K, + last_key: K, + keys: Vec, + values: Vec, + ) -> Result { + let hash: [u8; 32] = *self.kv_root_hash()?; + let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; + Ok(valid) + } + + /// Check if the account exists. + pub fn exist>(&self, key: K) -> Result { + Ok(match self.merkle.get(key, self.header.acc_root) { + Ok(r) => r.is_some(), + Err(e) => return Err(DBError::Merkle(e)), + }) + } +} + +#[cfg(feature = "eth")] +impl DBRev { + /// Get nonce of the account. + pub fn get_nonce>(&self, key: K) -> Result { + Ok(self.get_account(key)?.nonce) + } + + /// Get the state value indexed by `sub_key` in the account indexed by `key`. + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { + let root = self.get_account(key)?.root; + if root.is_null() { + return Ok(Vec::new()); + } + Ok(match self.merkle.get(sub_key, root) { + Ok(Some(v)) => v.to_vec(), + Ok(None) => Vec::new(), + Err(e) => return Err(DBError::Merkle(e)), + }) + } + /// Get root hash of the world state of all accounts. pub fn root_hash(&self) -> Result { self.merkle @@ -352,52 +413,6 @@ impl DBRev { Blob::Code(code) => code.clone(), }) } - - /// Provides a proof that a key is in the Trie. - pub fn prove>(&self, key: K) -> Result { - self.merkle - .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) - } - - /// Verifies a range proof is valid for a set of keys. - pub fn verify_range_proof, V: AsRef<[u8]>>( - &self, - proof: Proof, - first_key: K, - last_key: K, - keys: Vec, - values: Vec, - ) -> Result { - let hash: [u8; 32] = *self.kv_root_hash()?; - let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; - Ok(valid) - } - - /// Get nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { - Ok(self.get_account(key)?.nonce) - } - - /// Get the state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { - let root = self.get_account(key)?.root; - if root.is_null() { - return Ok(Vec::new()); - } - Ok(match self.merkle.get(sub_key, root) { - Ok(Some(v)) => v.to_vec(), - Ok(None) => Vec::new(), - Err(e) => return Err(DBError::Merkle(e)), - }) - } - - /// Check if the account exists. - pub fn exist>(&self, key: K) -> Result { - Ok(match self.merkle.get(key, self.header.acc_root) { - Ok(r) => r.is_some(), - Err(e) => return Err(DBError::Merkle(e)), - }) - } } struct DBInner { @@ -608,7 +623,7 @@ impl DB { ); } - let (mut db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { + let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = staging.merkle.meta.as_ref(); let blob_meta_ref = staging.blob.meta.as_ref(); @@ -639,6 +654,7 @@ impl DB { ) .unwrap(); + #[cfg(feature = "eth")] let blob_space = shale::compact::CompactSpace::new( staging.blob.meta.clone(), staging.blob.payload.clone(), @@ -666,6 +682,7 @@ impl DB { let mut latest = DBRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), + #[cfg(feature = "eth")] blob: BlobStash::new(Box::new(blob_space)), }; latest.flush_dirty().unwrap(); @@ -701,17 +718,6 @@ impl DB { pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { self.inner.read().latest.kv_dump(w) } - - /// Dump the Trie of the latest entire account model storage. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { - self.inner.read().latest.dump(w) - } - - /// Dump the Trie of the latest state storage under an account. - pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { - self.inner.read().latest.dump_account(key, w) - } - /// Get root hash of the latest generic key-value storage. pub fn kv_root_hash(&self) -> Result { self.inner.read().latest.kv_root_hash() @@ -725,37 +731,6 @@ impl DB { .kv_get(key) .ok_or(DBError::KeyNotFound) } - - /// Get root hash of the latest world state of all accounts. - pub fn root_hash(&self) -> Result { - self.inner.read().latest.root_hash() - } - - /// Get the latest balance of the account. - pub fn get_balance>(&self, key: K) -> Result { - self.inner.read().latest.get_balance(key) - } - - /// Get the latest code of the account. - pub fn get_code>(&self, key: K) -> Result, DBError> { - self.inner.read().latest.get_code(key) - } - - /// Get the latest nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { - self.inner.read().latest.get_nonce(key) - } - - /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { - self.inner.read().latest.get_state(key, sub_key) - } - - /// Check if the account exists in the latest world state. - pub fn exist>(&self, key: K) -> Result { - self.inner.read().latest.exist(key) - } - /// Get a handle that grants the access to some historical state of the entire DB. /// /// Note: There must be at least two committed batches in order for this function to return an older 'Revision', @@ -811,7 +786,7 @@ impl DB { let space = &revisions.inner[nback - 1]; - let (db_header_ref, merkle_payload_header_ref, blob_payload_header_ref) = { + let (db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = &space.merkle.meta; let blob_meta_ref = &space.blob.meta; @@ -842,6 +817,7 @@ impl DB { ) .unwrap(); + #[cfg(feature = "eth")] let blob_space = shale::compact::CompactSpace::new( Rc::new(space.blob.meta.clone()), Rc::new(space.blob.payload.clone()), @@ -855,11 +831,54 @@ impl DB { rev: DBRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), + #[cfg(feature = "eth")] blob: BlobStash::new(Box::new(blob_space)), }, }) } } +#[cfg(feature = "eth")] +impl DB { + /// Dump the Trie of the latest entire account model storage. + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + self.inner.read().latest.dump(w) + } + + /// Dump the Trie of the latest state storage under an account. + pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { + self.inner.read().latest.dump_account(key, w) + } + + /// Get root hash of the latest world state of all accounts. + pub fn root_hash(&self) -> Result { + self.inner.read().latest.root_hash() + } + + /// Get the latest balance of the account. + pub fn get_balance>(&self, key: K) -> Result { + self.inner.read().latest.get_balance(key) + } + + /// Get the latest code of the account. + pub fn get_code>(&self, key: K) -> Result, DBError> { + self.inner.read().latest.get_code(key) + } + + /// Get the latest nonce of the account. + pub fn get_nonce>(&self, key: K) -> Result { + self.inner.read().latest.get_nonce(key) + } + + /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { + self.inner.read().latest.get_state(key, sub_key) + } + + /// Check if the account exists in the latest world state. + pub fn exist>(&self, key: K) -> Result { + self.inner.read().latest.exist(key) + } +} /// Lock protected handle to a readable version of the DB. pub struct Revision { @@ -887,7 +906,10 @@ impl WriteBatch { /// Insert an item to the generic key-value storage. pub fn kv_insert>(self, key: K, val: Vec) -> Result { let mut rev = self.m.write(); + #[cfg(feature = "eth")] let (header, merkle, _) = rev.latest.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.latest.borrow_split(); merkle .insert(key, val, header.kv_root) .map_err(DBError::Merkle)?; @@ -899,14 +921,136 @@ impl WriteBatch { /// removed from the storage if it exists. pub fn kv_remove>(self, key: K) -> Result<(Self, Option>), DBError> { let mut rev = self.m.write(); + #[cfg(feature = "eth")] let (header, merkle, _) = rev.latest.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.latest.borrow_split(); let old_value = merkle .remove(key, header.kv_root) .map_err(DBError::Merkle)?; drop(rev); Ok((self, old_value)) } + /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root + /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. + pub fn no_root_hash(mut self) -> Self { + self.root_hash_recalc = false; + self + } + /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are + /// either retained on disk or lost together during a crash. + pub fn commit(mut self) { + let mut rev_inner = self.m.write(); + if self.root_hash_recalc { + #[cfg(feature = "eth")] + rev_inner.latest.root_hash().ok(); + rev_inner.latest.kv_root_hash().ok(); + } + // clear the staging layer and apply changes to the CachedSpace + rev_inner.latest.flush_dirty().unwrap(); + let (merkle_payload_pages, merkle_payload_plain) = + rev_inner.staging.merkle.payload.take_delta(); + let (merkle_meta_pages, merkle_meta_plain) = rev_inner.staging.merkle.meta.take_delta(); + let (blob_payload_pages, blob_payload_plain) = rev_inner.staging.blob.payload.take_delta(); + let (blob_meta_pages, blob_meta_plain) = rev_inner.staging.blob.meta.take_delta(); + + let old_merkle_meta_delta = rev_inner + .cached + .merkle + .meta + .update(&merkle_meta_pages) + .unwrap(); + let old_merkle_payload_delta = rev_inner + .cached + .merkle + .payload + .update(&merkle_payload_pages) + .unwrap(); + let old_blob_meta_delta = rev_inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); + let old_blob_payload_delta = rev_inner + .cached + .blob + .payload + .update(&blob_payload_pages) + .unwrap(); + + // update the rolling window of past revisions + let new_base = Universe { + merkle: SubUniverse::new( + StoreRevShared::from_delta( + rev_inner.cached.merkle.meta.clone(), + old_merkle_meta_delta, + ), + StoreRevShared::from_delta( + rev_inner.cached.merkle.payload.clone(), + old_merkle_payload_delta, + ), + ), + blob: SubUniverse::new( + StoreRevShared::from_delta(rev_inner.cached.blob.meta.clone(), old_blob_meta_delta), + StoreRevShared::from_delta( + rev_inner.cached.blob.payload.clone(), + old_blob_payload_delta, + ), + ), + }; + + let mut revisions = self.r.lock(); + if let Some(rev) = revisions.inner.front_mut() { + rev.merkle + .meta + .set_prev(new_base.merkle.meta.inner().clone()); + rev.merkle + .payload + .set_prev(new_base.merkle.payload.inner().clone()); + rev.blob.meta.set_prev(new_base.blob.meta.inner().clone()); + rev.blob + .payload + .set_prev(new_base.blob.payload.inner().clone()); + } + revisions.inner.push_front(new_base); + while revisions.inner.len() > revisions.max_revisions { + revisions.inner.pop_back(); + } + + self.committed = true; + + // schedule writes to the disk + rev_inner.disk_requester.write( + vec![ + BufferWrite { + space_id: rev_inner.staging.merkle.payload.id(), + delta: merkle_payload_pages, + }, + BufferWrite { + space_id: rev_inner.staging.merkle.meta.id(), + delta: merkle_meta_pages, + }, + BufferWrite { + space_id: rev_inner.staging.blob.payload.id(), + delta: blob_payload_pages, + }, + BufferWrite { + space_id: rev_inner.staging.blob.meta.id(), + delta: blob_meta_pages, + }, + ], + AshRecord( + [ + (MERKLE_META_SPACE, merkle_meta_plain), + (MERKLE_PAYLOAD_SPACE, merkle_payload_plain), + (BLOB_META_SPACE, blob_meta_plain), + (BLOB_PAYLOAD_SPACE, blob_payload_plain), + ] + .into(), + ), + ); + } +} + +#[cfg(feature = "eth")] +impl WriteBatch { fn change_account( &mut self, key: &[u8], @@ -1049,122 +1193,6 @@ impl WriteBatch { drop(rev); Ok(self) } - - /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root - /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. - pub fn no_root_hash(mut self) -> Self { - self.root_hash_recalc = false; - self - } - - /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are - /// either retained on disk or lost together during a crash. - pub fn commit(mut self) { - let mut rev_inner = self.m.write(); - if self.root_hash_recalc { - rev_inner.latest.root_hash().ok(); - rev_inner.latest.kv_root_hash().ok(); - } - // clear the staging layer and apply changes to the CachedSpace - rev_inner.latest.flush_dirty().unwrap(); - let (merkle_payload_pages, merkle_payload_plain) = - rev_inner.staging.merkle.payload.take_delta(); - let (merkle_meta_pages, merkle_meta_plain) = rev_inner.staging.merkle.meta.take_delta(); - let (blob_payload_pages, blob_payload_plain) = rev_inner.staging.blob.payload.take_delta(); - let (blob_meta_pages, blob_meta_plain) = rev_inner.staging.blob.meta.take_delta(); - - let old_merkle_meta_delta = rev_inner - .cached - .merkle - .meta - .update(&merkle_meta_pages) - .unwrap(); - let old_merkle_payload_delta = rev_inner - .cached - .merkle - .payload - .update(&merkle_payload_pages) - .unwrap(); - let old_blob_meta_delta = rev_inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); - let old_blob_payload_delta = rev_inner - .cached - .blob - .payload - .update(&blob_payload_pages) - .unwrap(); - - // update the rolling window of past revisions - let new_base = Universe { - merkle: SubUniverse::new( - StoreRevShared::from_delta( - rev_inner.cached.merkle.meta.clone(), - old_merkle_meta_delta, - ), - StoreRevShared::from_delta( - rev_inner.cached.merkle.payload.clone(), - old_merkle_payload_delta, - ), - ), - blob: SubUniverse::new( - StoreRevShared::from_delta(rev_inner.cached.blob.meta.clone(), old_blob_meta_delta), - StoreRevShared::from_delta( - rev_inner.cached.blob.payload.clone(), - old_blob_payload_delta, - ), - ), - }; - - let mut revisions = self.r.lock(); - if let Some(rev) = revisions.inner.front_mut() { - rev.merkle - .meta - .set_prev(new_base.merkle.meta.inner().clone()); - rev.merkle - .payload - .set_prev(new_base.merkle.payload.inner().clone()); - rev.blob.meta.set_prev(new_base.blob.meta.inner().clone()); - rev.blob - .payload - .set_prev(new_base.blob.payload.inner().clone()); - } - revisions.inner.push_front(new_base); - while revisions.inner.len() > revisions.max_revisions { - revisions.inner.pop_back(); - } - - self.committed = true; - - // schedule writes to the disk - rev_inner.disk_requester.write( - vec![ - BufferWrite { - space_id: rev_inner.staging.merkle.payload.id(), - delta: merkle_payload_pages, - }, - BufferWrite { - space_id: rev_inner.staging.merkle.meta.id(), - delta: merkle_meta_pages, - }, - BufferWrite { - space_id: rev_inner.staging.blob.payload.id(), - delta: blob_payload_pages, - }, - BufferWrite { - space_id: rev_inner.staging.blob.meta.id(), - delta: blob_meta_pages, - }, - ], - AshRecord( - [ - (MERKLE_META_SPACE, merkle_meta_plain), - (MERKLE_PAYLOAD_SPACE, merkle_payload_plain), - (BLOB_META_SPACE, blob_meta_plain), - (BLOB_PAYLOAD_SPACE, blob_payload_plain), - ] - .into(), - ), - ); - } } impl Drop for WriteBatch { diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs index b44ae6bfc06b..e3303e420b84 100644 --- a/firewood/src/dynamic_mem.rs +++ b/firewood/src/dynamic_mem.rs @@ -77,6 +77,7 @@ impl CachedStore for DynamicMem { } } +#[derive(Debug)] struct DynamicMemView { offset: usize, length: usize, diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index eeb958a45114..e01036a3761e 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -194,6 +194,7 @@ //! No change is required for other historical ghost space instances. Finally, we can phase out //! some very old ghost space to keep the size of the rolling window invariant. //! +#[cfg(feature = "eth")] pub(crate) mod account; pub mod db; pub mod dynamic_mem; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 74a9b98cd3ac..f45faa4079a4 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,7 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#[cfg(feature = "eth")] use crate::account::BlobError; + use crate::db::DBError; use crate::merkle::*; use crate::merkle_util::*; @@ -39,6 +41,7 @@ pub enum ProofError { EmptyRange, ForkLeft, ForkRight, + #[cfg(feature = "eth")] BlobStoreError(BlobError), SystemError(Errno), InvalidRootHash, @@ -59,6 +62,7 @@ impl From for ProofError { match d { DBError::InvalidParams => ProofError::InvalidProof, DBError::Merkle(e) => ProofError::InvalidNode(e), + #[cfg(feature = "eth")] DBError::Blob(e) => ProofError::BlobStoreError(e), DBError::System(e) => ProofError::SystemError(e), DBError::KeyNotFound => ProofError::InvalidEdgeKeys, @@ -86,6 +90,7 @@ impl fmt::Display for ProofError { ProofError::EmptyRange => write!(f, "empty range"), ProofError::ForkLeft => write!(f, "fork left"), ProofError::ForkRight => write!(f, "fork right"), + #[cfg(feature = "eth")] ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), ProofError::SystemError(e) => write!(f, "system error: {e:?}"), ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), From 772fc032c6851c34fca957f1f2f613bc6d6476b9 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 21 Apr 2023 15:34:03 -0400 Subject: [PATCH 0130/1053] buffer: Fix test flake (#44) Signed-off-by: Sam Batschelet --- firewood/src/storage/buffer.rs | 42 +++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 84d632c5c50c..94b2e58d4843 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -482,6 +482,12 @@ impl DiskBufferRequester { #[cfg(test)] mod tests { + use std::{ + path::{Path, PathBuf}, + thread, + time::Duration, + }; + use super::*; use crate::{ file, @@ -491,13 +497,19 @@ mod tests { const STATE_SPACE: SpaceID = 0x0; #[test] + #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer() { + let tmpdb = [ + &std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".to_string()), + "sender_api_test_db", + ]; + let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); let wal_cfg = WALConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); - let path = std::path::PathBuf::from(r"/tmp/firewood"); - let (root_db_fd, reset) = crate::file::open_dir(path, true).unwrap(); + let (root_db_fd, reset) = + crate::file::open_dir(&tmpdb.into_iter().collect::(), true).unwrap(); // file descriptor of the state directory let state_fd = file::touch_dir("state", root_db_fd).unwrap(); @@ -550,23 +562,27 @@ mod tests { // create a mutation request to the disk buffer by passing the page and write batch. let d1 = disk_requester.clone(); - std::thread::spawn(move || { + let write_thread_handle = std::thread::spawn(move || { // wal is empty assert!(d1.collect_ash(1).unwrap().is_empty()); // page is not yet persisted to disk. assert!(d1.get_page(STATE_SPACE, 0).is_none()); d1.write(page_batch, write_batch); - // This is not ACID compliant write should not return before wal log is written to disk. - // If the sleep is removed the test will fail. - // TODO why is this so slow? - std::thread::sleep(std::time::Duration::from_millis(5)); - assert_eq!(d1.collect_ash(1).unwrap().len(), 1); - assert!(d1.get_page(STATE_SPACE, 0).is_some()); }); - - // wait for thread to finish - std::thread::sleep(std::time::Duration::from_millis(5)); - // state has been written to disk in the above thread wal should have 1 record. + // wait for the write to complete. + write_thread_handle.join().unwrap(); + // This is not ACID compliant, write should not return before WAL log + // is written to disk. + assert!([ + &tmpdb.into_iter().collect::(), + "wal", + "00000000.log" + ] + .iter() + .collect::() + .exists()); + + // verify assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); } From 0ce7204e15a332ac771036a158a4326bf11a4cf6 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 21 Apr 2023 17:34:48 -0400 Subject: [PATCH 0131/1053] firewood-v0.0.2 (#23) Signed-off-by: Sam Batschelet --- firewood-growth-ring/Cargo.toml | 4 ++-- firewood-libaio/Cargo.toml | 2 +- firewood-shale/Cargo.toml | 2 +- firewood/Cargo.toml | 28 +++++++++++++++------------- fwdctl/Cargo.toml | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index 4825ac50160a..eeeeb40b06c6 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-growth-ring" -version = "0.0.1" +version = "0.0.2" edition = "2021" keywords = ["wal", "db", "futures"] license = "MIT" @@ -9,7 +9,7 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -firewood-libaio= { version = "0.0.1", path = "../firewood-libaio", package = "firewood-libaio" } +firewood-libaio= { version = "0.0.2", path = "../firewood-libaio", package = "firewood-libaio" } crc = "3.0.0" lru = "0.10.0" scan_fmt = "0.2.6" diff --git a/firewood-libaio/Cargo.toml b/firewood-libaio/Cargo.toml index 0e76dbcde5cd..1c119eae1a03 100644 --- a/firewood-libaio/Cargo.toml +++ b/firewood-libaio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-libaio" -version = "0.0.1" +version = "0.0.2" edition = "2021" keywords = ["libaio", "aio", "async", "futures"] license = "MIT" diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index beb1dc1d4b05..df30ccded546 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-shale" -version = "0.0.1" +version = "0.0.2" edition = "2021" description = "Useful abstraction and light-weight implemenation for a key-value store." license = "MIT" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index c17a3ddf04c1..2a34e183b475 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.1" +version = "0.0.2" edition = "2021" authors = [ "Ted Yin (@Determinant) ", @@ -13,27 +13,29 @@ authors = [ description = "Firewood is an embedded key-value store, optimized to store blockchain state." license-file = "../LICENSE.md" homepage = "https://avalabs.org" +readme = "../README.md" + [dependencies] aquamarine = "0.3.1" -firewood-growth-ring = { version = "0.0.1", path = "../firewood-growth-ring" } -firewood-libaio = {version = "0.0.1", path = "../firewood-libaio" } -firewood-shale = { version = "0.0.1", path = "../firewood-shale" } +async-trait = "0.1.57" +bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.5.1" -parking_lot = "0.12.1" -rlp = "0.5.2" -sha3 = "0.10.2" -once_cell = "1.13.1" +firewood-growth-ring = { version = "0.0.2", path = "../firewood-growth-ring" } +firewood-libaio = {version = "0.0.2", path = "../firewood-libaio" } +firewood-shale = { version = "0.0.2", path = "../firewood-shale" } +futures = "0.3.24" hex = "0.4.3" lru = "0.10.0" nix = "0.26.1" -typed-builder = "0.14.0" -tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } -futures = "0.3.24" +once_cell = "1.13.1" +parking_lot = "0.12.1" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } +rlp = "0.5.2" serde = { version = "1.0", features = ["derive"] } +sha3 = "0.10.2" thiserror = "1.0.38" -async-trait = "0.1.57" -bytemuck = { version = "1.13.1", features = ["derive"] } +tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } +typed-builder = "0.14.0" [dev-dependencies] criterion = "0.4.0" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index fdaeb2e2550f..8392f3474590 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "fwdctl" -version = "0.0.1" +version = "0.0.2" edition = "2021" [dependencies] -firewood = { version = "0.0.1", path = "../firewood" } +firewood = { version = "0.0.2", path = "../firewood" } clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" From a6fa2f913ae6848aa51fa9269def3ba5778abb86 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 21 Apr 2023 22:49:04 -0400 Subject: [PATCH 0132/1053] Move DynamicMemory to shale (#35) Signed-off-by: Sam Batschelet --- firewood-shale/benches/shale-bench.rs | 2 +- firewood-shale/src/cached.rs | 251 ++++++++++++++++++++++++++ firewood-shale/src/lib.rs | 92 +--------- firewood/benches/hashops.rs | 4 +- firewood/src/dynamic_mem.rs | 115 ------------ firewood/src/lib.rs | 1 - firewood/src/merkle.rs | 6 +- firewood/src/merkle_util.rs | 4 +- 8 files changed, 262 insertions(+), 213 deletions(-) create mode 100644 firewood-shale/src/cached.rs delete mode 100644 firewood/src/dynamic_mem.rs diff --git a/firewood-shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs index dc27b992d013..fd55f9ac988b 100644 --- a/firewood-shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -2,7 +2,7 @@ use bencher::{benchmark_group, benchmark_main, Bencher}; extern crate firewood_shale as shale; use rand::Rng; -use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, PlainMem, StoredView}; +use shale::{cached::PlainMem, compact::CompactSpaceHeader, CachedStore, ObjPtr, StoredView}; fn get_view(b: &mut Bencher) { const SIZE: u64 = 2_000_000; diff --git a/firewood-shale/src/cached.rs b/firewood-shale/src/cached.rs new file mode 100644 index 000000000000..4ca244ebbe38 --- /dev/null +++ b/firewood-shale/src/cached.rs @@ -0,0 +1,251 @@ +use std::borrow::BorrowMut; +use std::cell::{RefCell, UnsafeCell}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +use crate::{CachedStore, CachedView, SpaceID}; + +/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying +/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write +/// your own [CachedStore] implementation. +#[derive(Debug)] +pub struct PlainMem { + space: Rc>>, + id: SpaceID, +} + +impl PlainMem { + pub fn new(size: u64, id: SpaceID) -> Self { + let mut space: Vec = Vec::new(); + space.resize(size as usize, 0); + let space = Rc::new(RefCell::new(space)); + Self { space, id } + } +} + +impl CachedStore for PlainMem { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { + let offset = offset as usize; + let length = length as usize; + if offset + length > self.space.borrow().len() { + None + } else { + Some(Box::new(PlainMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(PlainMemShared(Self { + space: self.space.clone(), + id: self.id, + }))) + } + + fn write(&mut self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + let mut vect = self.space.deref().borrow_mut(); + vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); + } + + fn id(&self) -> SpaceID { + self.id + } +} + +#[derive(Debug)] +struct PlainMemView { + offset: usize, + length: usize, + mem: PlainMem, +} + +struct PlainMemShared(PlainMem); + +impl DerefMut for PlainMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.borrow_mut() + } +} + +impl Deref for PlainMemShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { + &self.0 + } +} + +impl CachedView for PlainMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() + } +} + +// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to +/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is +/// not enough. +#[derive(Debug)] +pub struct DynamicMem { + space: Rc>>, + id: SpaceID, +} + +impl DynamicMem { + pub fn new(size: u64, id: SpaceID) -> Self { + let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); + Self { space, id } + } + + #[allow(clippy::mut_from_ref)] + // TODO: Refactor this usage. + fn get_space_mut(&self) -> &mut Vec { + unsafe { &mut *self.space.get() } + } +} + +impl CachedStore for DynamicMem { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { + let offset = offset as usize; + let length = length as usize; + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size, 0); + } + Some(Box::new(DynamicMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + + fn get_shared(&self) -> Option>> { + Some(Box::new(DynamicMemShared(Self { + space: self.space.clone(), + id: self.id, + }))) + } + + fn write(&mut self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size, 0); + } + self.get_space_mut()[offset..offset + length].copy_from_slice(change) + } + + fn id(&self) -> SpaceID { + self.id + } +} + +#[derive(Debug)] +struct DynamicMemView { + offset: usize, + length: usize, + mem: DynamicMem, +} + +struct DynamicMemShared(DynamicMem); + +impl Deref for DynamicMemView { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.mem.get_space_mut()[self.offset..self.offset + self.length] + } +} + +impl Deref for DynamicMemShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { + &self.0 + } +} + +impl DerefMut for DynamicMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl CachedView for DynamicMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plain_mem() { + let mut view = PlainMemShared(PlainMem::new(2, 0)); + let mem = view.deref_mut(); + mem.write(0, &[1, 1]); + mem.write(0, &[1, 2]); + let r = mem.get_view(0, 2).unwrap().as_deref(); + assert_eq!(r, [1, 2]); + + // previous view not mutated by write + mem.write(0, &[1, 3]); + assert_eq!(r, [1, 2]); + let r = mem.get_view(0, 2).unwrap().as_deref(); + assert_eq!(r, [1, 3]); + + // create a view larger than capacity + assert!(mem.get_view(0, 4).is_none()) + } + + #[test] + #[should_panic(expected = "index 3 out of range for slice of length 2")] + fn test_plain_mem_panic() { + let mut view = PlainMemShared(PlainMem::new(2, 0)); + let mem = view.deref_mut(); + + // out of range + mem.write(1, &[7, 8]); + } + + #[test] + fn test_dynamic_mem() { + let mut view = DynamicMemShared(DynamicMem::new(2, 0)); + let mem = view.deref_mut(); + mem.write(0, &[1, 2]); + mem.write(0, &[3, 4]); + assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); + mem.get_shared().unwrap().write(0, &[5, 6]); + + // capacity is increased + mem.write(5, &[0; 10]); + + // get a view larger than recent growth + assert_eq!(mem.get_view(3, 20).unwrap().as_deref(), [0; 20]); + } +} diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index e0cf564223a4..4aebd3f284f4 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -1,5 +1,4 @@ -use std::borrow::BorrowMut; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::UnsafeCell; use std::collections::{HashMap, HashSet}; use std::fmt; use std::fmt::Debug; @@ -8,6 +7,7 @@ use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::rc::Rc; +pub mod cached; pub mod compact; pub mod util; @@ -464,94 +464,6 @@ impl Storable for ObjPtr { } } -/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying -/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write -/// your own [CachedStore] implementation. -#[derive(Debug)] -pub struct PlainMem { - space: Rc>>, - id: SpaceID, -} - -impl PlainMem { - pub fn new(size: u64, id: SpaceID) -> Self { - let mut space: Vec = Vec::new(); - space.resize(size as usize, 0); - let space = Rc::new(RefCell::new(space)); - Self { space, id } - } -} - -impl CachedStore for PlainMem { - fn get_view( - &self, - offset: u64, - length: u64, - ) -> Option>>> { - let offset = offset as usize; - let length = length as usize; - if offset + length > self.space.borrow().len() { - None - } else { - Some(Box::new(PlainMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - } - - fn get_shared(&self) -> Option>> { - Some(Box::new(PlainMemShared(Self { - space: self.space.clone(), - id: self.id, - }))) - } - - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; - let length = change.len(); - let mut vect = self.space.deref().borrow_mut(); - vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); - } - - fn id(&self) -> SpaceID { - self.id - } -} - -struct PlainMemView { - offset: usize, - length: usize, - mem: PlainMem, -} - -struct PlainMemShared(PlainMem); - -impl DerefMut for PlainMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.borrow_mut() - } -} - -impl Deref for PlainMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl CachedView for PlainMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() - } -} - struct ObjCacheInner { cached: lru::LruCache, Obj>, pinned: HashMap, bool>, diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 75a9affa6047..96538e96571c 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use bencher::{benchmark_group, benchmark_main, Bencher}; use firewood::merkle::{Hash, Merkle, HASH_SIZE}; use firewood_shale::{ - compact::CompactSpaceHeader, CachedStore, ObjPtr, PlainMem, Storable, StoredView, + cached::PlainMem, compact::CompactSpaceHeader, CachedStore, ObjPtr, Storable, StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; @@ -18,7 +18,7 @@ fn bench_dehydrate(b: &mut Bencher) { } fn bench_hydrate(b: &mut Bencher) { - let mut store = firewood_shale::PlainMem::new(HASH_SIZE as u64, 0u8); + let mut store = PlainMem::new(HASH_SIZE as u64, 0u8); store.write(0, ZERO_HASH.deref()); b.iter(|| { diff --git a/firewood/src/dynamic_mem.rs b/firewood/src/dynamic_mem.rs deleted file mode 100644 index e3303e420b84..000000000000 --- a/firewood/src/dynamic_mem.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::cell::UnsafeCell; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - -use shale::*; - -pub type SpaceID = u8; - -/// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to -/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is -/// not enough. -#[derive(Debug)] -pub struct DynamicMem { - space: Rc>>, - id: SpaceID, -} - -impl DynamicMem { - pub fn new(size: u64, id: SpaceID) -> Self { - let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); - Self { space, id } - } - - #[allow(clippy::mut_from_ref)] - // TODO: Refactor this usage. - fn get_space_mut(&self) -> &mut Vec { - unsafe { &mut *self.space.get() } - } -} - -impl CachedStore for DynamicMem { - fn get_view( - &self, - offset: u64, - length: u64, - ) -> Option>>> { - let offset = offset as usize; - let length = length as usize; - let size = offset + length; - // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); - } - Some(Box::new(DynamicMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - - fn get_shared(&self) -> Option>> { - Some(Box::new(DynamicMemShared(Self { - space: self.space.clone(), - id: self.id, - }))) - } - - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; - let length = change.len(); - let size = offset + length; - // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); - } - self.get_space_mut()[offset..offset + length].copy_from_slice(change) - } - - fn id(&self) -> SpaceID { - self.id - } -} - -#[derive(Debug)] -struct DynamicMemView { - offset: usize, - length: usize, - mem: DynamicMem, -} - -struct DynamicMemShared(DynamicMem); - -impl Deref for DynamicMemView { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.mem.get_space_mut()[self.offset..self.offset + self.length] - } -} - -impl Deref for DynamicMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl DerefMut for DynamicMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl CachedView for DynamicMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() - } -} diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index e01036a3761e..644923c0e939 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -197,7 +197,6 @@ #[cfg(feature = "eth")] pub(crate) mod account; pub mod db; -pub mod dynamic_mem; pub(crate) mod file; pub mod merkle; pub mod merkle_util; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d3b34b5fc0cb..50f7aacc78e5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1982,6 +1982,8 @@ pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { #[cfg(test)] mod test { + use shale::cached::PlainMem; + use super::*; use std::ops::Deref; @@ -2043,7 +2045,7 @@ mod test { #[test] fn test_hydrate() { - let mut store = shale::PlainMem::new(HASH_SIZE as u64, 0u8); + let mut store = PlainMem::new(HASH_SIZE as u64, 0u8); store.write(0, ZERO_HASH.deref()); assert_eq!(Hash::hydrate(0, &store).unwrap(), ZERO_HASH); } @@ -2073,7 +2075,7 @@ mod test { bytes.resize(node.dehydrated_len() as usize, 0); node.dehydrate(&mut bytes); - let mut mem = shale::PlainMem::new(bytes.len() as u64, 0x0); + let mut mem = PlainMem::new(bytes.len() as u64, 0x0); mem.write(0, &bytes); println!("{bytes:?}"); let node_ = Node::hydrate(0, &mem).unwrap(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 25e8b3e98515..5b4ab9574e10 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -2,8 +2,8 @@ // See the file LICENSE.md for licensing terms. use crate::merkle::*; -use crate::proof::Proof; -use crate::{dynamic_mem::DynamicMem, proof::ProofError}; +use crate::proof::{Proof, ProofError}; +use shale::cached::DynamicMem; use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, StoredView}; use std::rc::Rc; From 2a0c3053b3b01043a06a37db60fd7f6c2dfa2552 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 24 Apr 2023 07:46:45 -0700 Subject: [PATCH 0133/1053] CLI improvements (#55) --- fwdctl/Cargo.toml | 1 + fwdctl/src/create.rs | 12 ++---- fwdctl/src/delete.rs | 18 +++------ fwdctl/src/dump.rs | 15 ++----- fwdctl/src/get.rs | 14 +++---- fwdctl/src/insert.rs | 18 +++------ fwdctl/src/main.rs | 49 +++------------------- fwdctl/src/root.rs | 18 +++------ fwdctl/tests/cli.rs | 96 +++++++++++++++++++++++++++++++++++--------- 9 files changed, 115 insertions(+), 126 deletions(-) diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 8392f3474590..fa5254add555 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -14,3 +14,4 @@ log = "0.4.17" assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" +lazy_static = "1.4.0" diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index ee60196e1e4a..128d22cc7af5 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{Error, Result}; use clap::{value_parser, Args}; use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; use log; @@ -262,11 +262,7 @@ pub fn run(opts: &Options) -> Result<()> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - match DB::new::<&str>(opts.name.as_ref(), &db_config) { - Ok(_) => { - println!("created firewood database in {:?}", opts.name); - Ok(()) - } - Err(_) => Err(anyhow!("error creating database")), - } + DB::new::<&str>(opts.name.as_ref(), &db_config).map_err(Error::msg)?; + println!("created firewood database in {:?}", opts.name); + Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index f481502fde93..53b3ba588449 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Error, Result}; +use anyhow::{Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; @@ -29,18 +29,10 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db.as_str(), &cfg.build()) { - Ok(db) => db, - Err(_) => return Err(anyhow!("error opening database")), - }; - - if let Ok((wb, _)) = db - .new_writebatch() - .kv_remove(opts.key.clone()) - .map_err(Error::msg) - { - wb.commit() - } + let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + db.new_writebatch() + .kv_remove(&opts.key) + .map_err(Error::msg)?; println!("key {} deleted successfully", opts.key); Ok(()) } diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 0467243e45d8..8d1f81fb1b83 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; @@ -24,14 +24,7 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db.as_str(), &cfg.build()) { - Ok(db) => db, - Err(_) => return Err(anyhow!("db not available")), - }; - - let mut stdout = std::io::stdout(); - if db.kv_dump(&mut stdout).is_err() { - return Err(anyhow!("database dump not successful")); - } - Ok(()) + let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + db.kv_dump(&mut std::io::stdout().lock()) + .map_err(Error::msg) } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 39478e19cc25..f9817bcecaf1 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Error, Result}; use clap::Args; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{DBConfig, DBError, WALConfig, DB}; use log; use std::str; @@ -30,10 +30,7 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db.as_str(), &cfg.build()) { - Ok(db) => db, - Err(_) => return Err(anyhow!("db not available")), - }; + let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; match db.kv_get(opts.key.as_bytes()) { Ok(val) => { @@ -43,10 +40,11 @@ pub fn run(opts: &Options) -> Result<()> { }; println!("{:?}", s); if val.is_empty() { - return Err(anyhow!("no value found for key")); + bail!("no value found for key"); } Ok(()) } - Err(_) => Err(anyhow!("key not found")), + Err(DBError::KeyNotFound) => bail!("key not found"), + Err(e) => bail!(e), } } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index dad6c865367c..eabf1f6f9589 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; @@ -38,17 +38,11 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - let x = match db + let insertion_batch = db .new_writebatch() .kv_insert(opts.key.clone(), opts.value.bytes().collect()) - { - Ok(insertion) => { - insertion.commit(); - println!("{}", opts.key); - - Ok(()) - } - Err(_) => return Err(anyhow!("error inserting key/value pair into the database")), - }; - x + .map_err(Error::msg)?; + insertion_batch.commit(); + println!("{}", opts.key); + Ok(()) } diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index d4c8948438fc..6b6e9cb26253 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -3,7 +3,6 @@ use anyhow::Result; use clap::{Parser, Subcommand}; -use std::process; pub mod create; pub mod delete; @@ -56,47 +55,11 @@ fn main() -> Result<()> { ); match &cli.command { - Commands::Create(opts) => match create::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, - Commands::Insert(opts) => match insert::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, - Commands::Get(opts) => match get::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, - Commands::Delete(opts) => match delete::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, - Commands::Root(opts) => match root::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, - Commands::Dump(opts) => match dump::run(opts) { - Err(e) => { - eprintln!("{e}"); - process::exit(1) - } - Ok(_) => Ok(()), - }, + Commands::Create(opts) => create::run(opts), + Commands::Insert(opts) => insert::run(opts), + Commands::Get(opts) => get::run(opts), + Commands::Delete(opts) => delete::run(opts), + Commands::Root(opts) => root::run(opts), + Commands::Dump(opts) => dump::run(opts), } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 93c608930848..69bbe776726d 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{Error, Result}; use clap::Args; use firewood::db::{DBConfig, WALConfig, DB}; use log; @@ -26,17 +26,9 @@ pub fn run(opts: &Options) -> Result<()> { .truncate(false) .wal(WALConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db.as_str(), &cfg.build()) { - Ok(db) => db, - Err(_) => return Err(anyhow!("db not available")), - }; + let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; - match db.kv_root_hash() { - Ok(root) => { - // TODO: collect root into hex encoded string - println!("{:X?}", *root); - Ok(()) - } - Err(_) => Err(anyhow!("root hash not found")), - } + let root = db.kv_root_hash().map_err(Error::msg)?; + println!("{:X?}", *root); + Ok(()) } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 36e59a1f4ed0..c9812b90d3b2 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -1,16 +1,16 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; + use predicates::prelude::*; use serial_test::serial; + use std::fs::remove_dir_all; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); -const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; - // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { - if let Err(e) = remove_dir_all(FIREWOOD_TEST_DB_NAME) { + if let Err(e) = remove_dir_all(tmpdb::path()) { eprintln!("failed to delete testing dir: {e}"); return Err(anyhow!(e)); } @@ -38,7 +38,7 @@ fn fwdctl_prints_version() -> Result<()> { fn fwdctl_creates_database() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg(FIREWOOD_TEST_DB_NAME) + .arg(tmpdb::path()) .assert() .success(); @@ -53,7 +53,7 @@ fn fwdctl_insert_successful() -> Result<()> { // Create db Command::cargo_bin(PRG)? .arg("create") - .arg(FIREWOOD_TEST_DB_NAME) + .arg(tmpdb::path()) .assert() .success(); @@ -62,7 +62,8 @@ fn fwdctl_insert_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -78,7 +79,7 @@ fn fwdctl_get_successful() -> Result<()> { // Create db and insert data Command::cargo_bin(PRG)? .arg("create") - .args([FIREWOOD_TEST_DB_NAME]) + .args([tmpdb::path()]) .assert() .success(); @@ -86,7 +87,8 @@ fn fwdctl_get_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -95,7 +97,8 @@ fn fwdctl_get_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("get") .args(["year"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("2023")); @@ -110,7 +113,7 @@ fn fwdctl_get_successful() -> Result<()> { fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg(FIREWOOD_TEST_DB_NAME) + .arg(tmpdb::path()) .assert() .success(); @@ -118,7 +121,8 @@ fn fwdctl_delete_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -127,7 +131,8 @@ fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("delete") .args(["year"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("key year deleted successfully")); @@ -142,7 +147,7 @@ fn fwdctl_delete_successful() -> Result<()> { fn fwdctl_root_hash() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg(FIREWOOD_TEST_DB_NAME) + .arg(tmpdb::path()) .assert() .success(); @@ -150,7 +155,8 @@ fn fwdctl_root_hash() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -158,7 +164,8 @@ fn fwdctl_root_hash() -> Result<()> { // Get root Command::cargo_bin(PRG)? .arg("root") - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::is_empty().not()); @@ -173,7 +180,7 @@ fn fwdctl_root_hash() -> Result<()> { fn fwdctl_dump() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") - .arg(FIREWOOD_TEST_DB_NAME) + .arg(tmpdb::path()) .assert() .success(); @@ -181,7 +188,8 @@ fn fwdctl_dump() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db", FIREWOOD_TEST_DB_NAME]) + .args(["--db"]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("year")); @@ -189,7 +197,7 @@ fn fwdctl_dump() -> Result<()> { // Get root Command::cargo_bin(PRG)? .arg("dump") - .args([FIREWOOD_TEST_DB_NAME]) + .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::is_empty().not()); @@ -198,3 +206,55 @@ fn fwdctl_dump() -> Result<()> { Ok(()) } + +// A module to create a temporary database name for use in +// tests. The directory will be one of: +// - cargo's compile-time CARGO_TARGET_TMPDIR, if that exists +// - the value of the TMPDIR environment, if that is set, or +// - fallback to /tmp + +// using cargo's CARGO_TARGET_TMPDIR ensures that multiple runs +// of this in different directories will have different databases + +mod tmpdb { + use std::{ + ffi, + path::{Path, PathBuf}, + }; + + use lazy_static::lazy_static; + + const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; + const TARGET_TMP_DIR: Option<&str> = option_env!("CARGO_TARGET_TMPDIR"); + + #[derive(Debug)] + pub struct DbPath { + path: PathBuf, + } + + impl AsRef for DbPath { + fn as_ref(&self) -> &ffi::OsStr { + self.path.as_os_str() + } + } + impl AsRef for DbPath { + fn as_ref(&self) -> &Path { + &self.path + } + } + lazy_static! { + static ref DBPATH: DbPath = { + let path = [ + TARGET_TMP_DIR.unwrap_or(&std::env::var("TMPDIR").unwrap_or("/tmp".to_string())), + FIREWOOD_TEST_DB_NAME, + ] + .iter() + .collect::(); + DbPath { path } + }; + } + + pub fn path() -> &'static DbPath { + &DBPATH + } +} From 048e07255af4e4eec2f3d0710f52246a0509efda Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 25 Apr 2023 12:53:26 -0400 Subject: [PATCH 0134/1053] tests: Speed up slow unit tests (#58) --- firewood-growth-ring/tests/rand_fail.rs | 6 +++--- firewood/src/storage/mod.rs | 2 +- firewood/tests/db.rs | 2 +- firewood/tests/merkle.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/firewood-growth-ring/tests/rand_fail.rs b/firewood-growth-ring/tests/rand_fail.rs index ad4470dff9e0..7a24d230b88a 100644 --- a/firewood-growth-ring/tests/rand_fail.rs +++ b/firewood-growth-ring/tests/rand_fail.rs @@ -53,13 +53,13 @@ fn short_single_point_failure() { block_nbit: 5, file_nbit: 6, file_cache: 1000, - n: 100, + n: 10, m: 10, k: 10, - csize: 1000, + csize: 100, stroke_max_len: 10, stroke_max_col: 256, - stroke_max_n: 5, + stroke_max_n: 2, seed: 0, }; multi_point_failure(&[sim]); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 2e48949b2b9a..b93fdd465abc 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -569,7 +569,7 @@ fn test_from_ash() { let mut rng = StdRng::seed_from_u64(42); let min = rng.gen_range(0..2 * PAGE_SIZE); let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); - for _ in 0..2000 { + for _ in 0..20 { let n = 20; let mut canvas = Vec::new(); canvas.resize((max - min) as usize, 0); diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 59a528fe1aff..084718509b44 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -47,7 +47,7 @@ fn test_revisions() { for i in 0..10 { let db = DB::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); let mut dumped = VecDeque::new(); - for _ in 0..100 { + for _ in 0..10 { { let mut wb = db.new_writebatch(); let m = rng.borrow_mut().gen_range(1..20); diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 70961f4d2889..74296450090e 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -876,7 +876,7 @@ fn test_single_side_range_proof() -> Result<(), ProofError> { fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { for _ in 0..10 { let mut set = HashMap::new(); - for _ in 0..4096_u32 { + for _ in 0..1024_u32 { let key = rand::thread_rng().gen::<[u8; 32]>(); let val = rand::thread_rng().gen::<[u8; 20]>(); set.insert(key, val); From 2ffa063b4e7a0ff6253cdb10b627c595a5cefba1 Mon Sep 17 00:00:00 2001 From: exdx Date: Tue, 25 Apr 2023 15:25:23 -0400 Subject: [PATCH 0135/1053] ci: Add backtrace to e2e tests (#59) Signed-off-by: Dan Sover --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fc7432ec5a98..ddd4bd2c1f21 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -57,13 +57,13 @@ jobs: toolchain: stable override: true - name: Run simple example - run: cargo run --example simple + run: RUST_BACKTRACE=1 cargo run --example simple - name: Run dump example - run: cargo run --example dump + run: RUST_BACKTRACE=1 cargo run --example dump - name: Run benchmark example - run: cargo run --example benchmark -- --nbatch 100 --batch-size 1000 + run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - name: Run rev example - run: cargo run --example rev + run: RUST_BACKTRACE=1 cargo run --example rev docs: runs-on: ubuntu-latest From 991d9f8a0791d3e1de1f34f60e497aa0f46b9109 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Wed, 26 Apr 2023 14:23:32 -0400 Subject: [PATCH 0136/1053] shale: move benching to criterion (#61) Signed-off-by: Sam Batschelet --- firewood-shale/Cargo.toml | 3 +- firewood-shale/benches/shale-bench.rs | 98 +++++++++++++++++++++------ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index df30ccded546..48a55e5adfd9 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -12,7 +12,8 @@ hex = "0.4.3" lru = "0.10.0" [dev-dependencies] -bencher = "0.1.5" +criterion = { version = "0.4.0", features = ["html_reports"] } +pprof = { version = "0.11.1", features = ["flamegraph"] } rand = "0.8.5" [[bench]] diff --git a/firewood-shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs index fd55f9ac988b..56190f33b836 100644 --- a/firewood-shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -1,37 +1,97 @@ -use bencher::{benchmark_group, benchmark_main, Bencher}; - extern crate firewood_shale as shale; + +use criterion::{criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion}; +use pprof::ProfilerGuard; use rand::Rng; -use shale::{cached::PlainMem, compact::CompactSpaceHeader, CachedStore, ObjPtr, StoredView}; +use shale::{ + cached::{DynamicMem, PlainMem}, + compact::CompactSpaceHeader, + CachedStore, ObjPtr, StoredView, +}; +use std::{fs::File, os::raw::c_int, path::Path}; + +const BENCH_MEM_SIZE: u64 = 2_000_000; + +// To enable flamegraph output +// cargo bench --bench shale-bench -- --profile-time=N +pub struct FlamegraphProfiler<'a> { + frequency: c_int, + active_profiler: Option>, +} + +impl<'a> FlamegraphProfiler<'a> { + #[allow(dead_code)] + pub fn new(frequency: c_int) -> Self { + FlamegraphProfiler { + frequency, + active_profiler: None, + } + } +} + +impl<'a> Profiler for FlamegraphProfiler<'a> { + fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { + self.active_profiler = Some(ProfilerGuard::new(self.frequency).unwrap()); + } -fn get_view(b: &mut Bencher) { - const SIZE: u64 = 2_000_000; + fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { + std::fs::create_dir_all(benchmark_dir).unwrap(); + let flamegraph_path = benchmark_dir.join("flamegraph.svg"); + let flamegraph_file = File::create(&flamegraph_path) + .expect("File system error while creating flamegraph.svg"); + if let Some(profiler) = self.active_profiler.take() { + profiler + .report() + .build() + .unwrap() + .flamegraph(flamegraph_file) + .expect("Error writing flamegraph"); + } + } +} - let mut m = PlainMem::new(SIZE, 0); +fn get_view(b: &mut Bencher, mut cached: C) { let mut rng = rand::thread_rng(); b.iter(|| { let len = rng.gen_range(0..26); let rdata = &"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]; - let offset = rng.gen_range(0..SIZE - len as u64); + let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); + + cached.write(offset, &rdata); + let view = cached + .get_view(offset, rdata.len().try_into().unwrap()) + .unwrap(); - m.write(offset, &rdata); - let view = m.get_view(offset, rdata.len().try_into().unwrap()).unwrap(); + serialize(&cached); assert_eq!(view.as_deref(), rdata); }); } -fn serialize(b: &mut Bencher) { - const SIZE: u64 = 2_000_000; - let m = PlainMem::new(SIZE, 0); +fn serialize(m: &T) { + let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); + let _compact_header = + StoredView::ptr_to_obj(m, compact_header_obj, shale::compact::CompactHeader::MSIZE) + .unwrap(); +} - b.iter(|| { - let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); - let _compact_header = - StoredView::ptr_to_obj(&m, compact_header_obj, shale::compact::CompactHeader::MSIZE) - .unwrap(); +fn bench_cursors(c: &mut Criterion) { + let mut group = c.benchmark_group("shale-bench"); + group.bench_function("PlainMem", |b| { + let mem = PlainMem::new(BENCH_MEM_SIZE, 0); + get_view(b, mem) }); + group.bench_function("DynamicMem", |b| { + let mem = DynamicMem::new(BENCH_MEM_SIZE, 0); + get_view(b, mem) + }); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(FlamegraphProfiler::new(100)); + targets = bench_cursors } -benchmark_group!(benches, get_view, serialize); -benchmark_main!(benches); + +criterion_main!(benches); From 89dfad5c9b81c6a8846c78613d58e86acedd32c9 Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 26 Apr 2023 17:50:50 -0400 Subject: [PATCH 0137/1053] growth_ring: Refactor file operations to use a Path (#26) Signed-off-by: Dan Sover Co-authored-by: Ron Kuris --- firewood-growth-ring/examples/demo1.rs | 6 +- firewood-growth-ring/src/lib.rs | 135 +++++++++-------------- firewood-growth-ring/src/wal.rs | 20 +++- firewood-growth-ring/src/walerror.rs | 25 ++++- firewood-growth-ring/tests/common/mod.rs | 27 +++-- firewood-libaio/src/lib.rs | 2 +- firewood-shale/benches/shale-bench.rs | 6 +- firewood/src/db.rs | 36 +++--- firewood/src/file.rs | 108 ++++++++---------- firewood/src/proof.rs | 4 + firewood/src/storage/buffer.rs | 63 ++++++----- firewood/src/storage/mod.rs | 33 ++++-- firewood/tests/merkle.rs | 20 ++-- 13 files changed, 246 insertions(+), 239 deletions(-) diff --git a/firewood-growth-ring/examples/demo1.rs b/firewood-growth-ring/examples/demo1.rs index d9f3c38b0d53..9f9d0d8b0774 100644 --- a/firewood-growth-ring/examples/demo1.rs +++ b/firewood-growth-ring/examples/demo1.rs @@ -31,7 +31,7 @@ fn main() { let mut loader = WALLoader::new(); loader.file_nbit(9).block_nbit(8); - let store = WALStoreAIO::new(wal_dir, true, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, true, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -49,7 +49,7 @@ fn main() { ); } - let store = WALStoreAIO::new(wal_dir, false, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, false, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -63,7 +63,7 @@ fn main() { ); } - let store = WALStoreAIO::new(wal_dir, false, None, None).unwrap(); + let store = WALStoreAIO::new(wal_dir, false, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); let mut history = std::collections::VecDeque::new(); for _ in 0..3 { diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index 1640ce19f3c0..d8ab2be22e76 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -10,7 +10,7 @@ //! //! //! // Start with empty WAL (truncate = true). -//! let store = WALStoreAIO::new("./walfiles", true, None, None).unwrap(); +//! let store = WALStoreAIO::new("./walfiles", true, None).unwrap(); //! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); //! // Write a vector of records to WAL. //! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { @@ -20,7 +20,7 @@ //! //! //! // Load from WAL (truncate = false). -//! let store = WALStoreAIO::new("./walfiles", false, None, None).unwrap(); +//! let store = WALStoreAIO::new("./walfiles", false, None).unwrap(); //! let mut wal = block_on(loader.load(store, |payload, ringid| { //! // redo the operations in your application //! println!("recover(payload={}, ringid={:?})", @@ -37,7 +37,7 @@ //! block_on(wal.peel(ring_ids, 0)).unwrap(); //! // There will only be one remaining file in ./walfiles. //! -//! let store = WALStoreAIO::new("./walfiles", false, None, None).unwrap(); +//! let store = WALStoreAIO::new("./walfiles", false, None).unwrap(); //! let wal = block_on(loader.load(store, |payload, _| { //! println!("payload.len() = {}", payload.len()); //! Ok(()) @@ -54,12 +54,15 @@ use async_trait::async_trait; use firewood_libaio::{AIOBuilder, AIOManager}; use libc::off_t; #[cfg(target_os = "linux")] -use nix::fcntl::{fallocate, open, openat, FallocateFlags, OFlag}; +use nix::fcntl::{fallocate, FallocateFlags, OFlag}; #[cfg(not(target_os = "linux"))] use nix::fcntl::{open, openat, OFlag}; -use nix::sys::stat::Mode; -use nix::unistd::{close, ftruncate, mkdir, unlinkat, UnlinkatFlags}; +use nix::unistd::{close, ftruncate}; +use std::fs; +use std::os::fd::IntoRawFd; use std::os::unix::io::RawFd; +use std::os::unix::prelude::OpenOptionsExt; +use std::path::{Path, PathBuf}; use std::sync::Arc; use wal::{WALBytes, WALFile, WALPos, WALStore}; use walerror::WALError; @@ -69,18 +72,20 @@ pub struct WALFileAIO { aiomgr: Arc, } -#[allow(clippy::result_unit_err)] -// TODO: Refactor to return a meaningful error. impl WALFileAIO { - pub fn new(rootfd: RawFd, filename: &str, aiomgr: Arc) -> Result { - openat( - rootfd, - filename, - OFlag::O_CREAT | OFlag::O_RDWR, - Mode::S_IRUSR | Mode::S_IWUSR, - ) - .map(|fd| WALFileAIO { fd, aiomgr }) - .map_err(From::from) + pub fn new(root_dir: &Path, filename: &str, aiomgr: Arc) -> Result { + fs::OpenOptions::new() + .read(true) + .write(true) + .truncate(false) + .create(true) + .mode(0o600) + .open(root_dir.join(filename)) + .map(|f| { + let fd = f.into_raw_fd(); + WALFileAIO { fd, aiomgr } + }) + .map_err(|e| WALError::IOError(Arc::new(e))) } } @@ -120,7 +125,11 @@ impl WALFile for WALFileAIO { if nwrote == data.len() { Ok(()) } else { - Err(WALError::Other) + Err(WALError::Other(format!( + "partial write; wrote {nwrote} expected {} for fd {}", + data.len(), + self.fd + ))) } }) } @@ -133,62 +142,38 @@ impl WALFile for WALFileAIO { } pub struct WALStoreAIO { - rootfd: RawFd, + root_dir: PathBuf, aiomgr: Arc, } unsafe impl Send for WALStoreAIO {} impl WALStoreAIO { - #[allow(clippy::result_unit_err)] - pub fn new( - wal_dir: &str, + pub fn new>( + wal_dir: P, truncate: bool, - rootfd: Option, aiomgr: Option, - ) -> Result { - let aiomgr = Arc::new( - aiomgr - .ok_or(Err(())) - .or_else(|_: Result| AIOBuilder::default().build().or(Err(())))?, - ); + ) -> Result { + let aio = match aiomgr { + Some(aiomgr) => Arc::new(aiomgr), + None => Arc::new(AIOBuilder::default().build()?), + }; if truncate { - let _ = std::fs::remove_dir_all(wal_dir); - } - let walfd = match rootfd { - None => { - if let Err(e) = mkdir(wal_dir, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { - if truncate { - panic!("error while creating directory: {}", e) - } - } - match open(wal_dir, oflags(), Mode::empty()) { - Ok(fd) => fd, - Err(_) => panic!("error while opening the WAL directory"), + if let Err(e) = fs::remove_dir_all(&wal_dir) { + if e.kind() != std::io::ErrorKind::NotFound { + return Err(From::from(e)); } } - Some(fd) => { - let dirstr = std::ffi::CString::new(wal_dir).unwrap(); - let ret = unsafe { - libc::mkdirat( - fd, - dirstr.as_ptr(), - libc::S_IRUSR | libc::S_IWUSR | libc::S_IXUSR, - ) - }; - if ret != 0 && truncate { - panic!("error while creating directory") - } - match nix::fcntl::openat(fd, wal_dir, oflags(), Mode::empty()) { - Ok(fd) => fd, - Err(_) => panic!("error while opening the WAL directory"), - } - } - }; + fs::create_dir(&wal_dir)?; + } else if !wal_dir.as_ref().exists() { + // create WAL dir + fs::create_dir(&wal_dir)?; + } + Ok(WALStoreAIO { - rootfd: walfd, - aiomgr, + root_dir: wal_dir.as_ref().to_path_buf(), + aiomgr: aio, }) } } @@ -208,34 +193,20 @@ impl WALStore for WALStoreAIO { type FileNameIter = std::vec::IntoIter; async fn open_file(&self, filename: &str, _touch: bool) -> Result, WALError> { - let filename = filename.to_string(); - WALFileAIO::new(self.rootfd, &filename, self.aiomgr.clone()) + WALFileAIO::new(&self.root_dir, filename, self.aiomgr.clone()) .map(|f| Box::new(f) as Box) } async fn remove_file(&self, filename: String) -> Result<(), WALError> { - unlinkat( - Some(self.rootfd), - filename.as_str(), - UnlinkatFlags::NoRemoveDir, - ) - .map_err(From::from) + let file_to_remove = self.root_dir.join(filename); + fs::remove_file(file_to_remove).map_err(From::from) } fn enumerate_files(&self) -> Result { - let mut logfiles = Vec::new(); - for ent in nix::dir::Dir::openat(self.rootfd, "./", OFlag::empty(), Mode::empty()) - .unwrap() - .iter() - { - logfiles.push(ent.unwrap().file_name().to_str().unwrap().to_string()) + let mut filenames = Vec::new(); + for path in fs::read_dir(&self.root_dir)?.filter_map(|entry| entry.ok()) { + filenames.push(path.file_name().into_string().unwrap()); } - Ok(logfiles.into_iter()) - } -} - -impl Drop for WALStoreAIO { - fn drop(&mut self) { - nix::unistd::close(self.rootfd).ok(); + Ok(filenames.into_iter()) } } diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index f643698948ed..3eea6e92a9d6 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -701,7 +701,7 @@ impl WALWriter { rings.push(ring); } for ring in rings.into_iter().rev() { - let ring = ring.map_err(|_| WALError::Other)?; + let ring = ring.map_err(|_| WALError::Other("error mapping ring".to_string()))?; let (header, payload) = ring; let payload = payload.unwrap(); match header.rtype.try_into() { @@ -745,7 +745,11 @@ impl WALWriter { } Ok(WALRingType::Null) => break, Err(_) => match recover_policy { - RecoverPolicy::Strict => return Err(WALError::Other), + RecoverPolicy::Strict => { + return Err(WALError::Other( + "invalid ring type - strict recovery requested".to_string(), + )) + } RecoverPolicy::BestEffort => break 'outer, }, } @@ -818,7 +822,7 @@ impl WALLoader { Ok(true) } else { match p { - RecoverPolicy::Strict => Err(WALError::Other), + RecoverPolicy::Strict => Err(WALError::Other("invalid checksum".to_string())), RecoverPolicy::BestEffort => Ok(false), } } @@ -1153,7 +1157,9 @@ impl WALLoader { let (bytes, ring_id, _) = match res { Err(e) => { if e { - return Err(WALError::Other); + return Err(WALError::Other( + "error loading from storage".to_string(), + )); } else { break 'outer; } @@ -1171,7 +1177,8 @@ impl WALLoader { .collect() .await; for e in records.into_iter().rev() { - let (rec, _) = e.map_err(|_| WALError::Other)?; + let (rec, _) = + e.map_err(|_| WALError::Other("error decoding WALRingBlob".to_string()))?; if rec.rtype == WALRingType::Full as u8 || rec.rtype == WALRingType::Last as u8 { counter = rec.counter + 1; break 'outer; @@ -1194,7 +1201,8 @@ impl WALLoader { let stream = Self::read_rings(&f, false, self.block_nbit, &self.recover_policy); futures::pin_mut!(stream); while let Some(r) = stream.next().await { - last = Some(r.map_err(|_| WALError::Other)?); + last = + Some(r.map_err(|_| WALError::Other("error decoding WALRingBlob".to_string()))?); } if let Some((last_rec, _)) = last { if !counter_lt(last_rec.counter + keep_nrecords, counter) { diff --git a/firewood-growth-ring/src/walerror.rs b/firewood-growth-ring/src/walerror.rs index c22c8c4e6dfc..de1526c500c3 100644 --- a/firewood-growth-ring/src/walerror.rs +++ b/firewood-growth-ring/src/walerror.rs @@ -1,14 +1,23 @@ +use std::sync::Arc; + +use firewood_libaio::AIOError; use nix::errno::Errno; use thiserror::Error; #[derive(Clone, Debug, Error)] pub enum WALError { - #[error("an unclassified error has occurred")] - Other, + #[error("an unclassified error has occurred: {0}")] + Other(String), #[error("an OS error {0} has occurred")] UnixError(#[from] Errno), #[error("a checksum check has failed")] InvalidChecksum, + #[error("an I/O error has occurred")] + IOError(Arc), + #[error("lib AIO error has occurred")] + AIOError(AIOError), + #[error("WAL directory already exists")] + WALDirExists, } impl From for WALError { @@ -16,3 +25,15 @@ impl From for WALError { Self::UnixError(Errno::from_i32(value)) } } + +impl From for WALError { + fn from(err: std::io::Error) -> Self { + Self::IOError(Arc::new(err)) + } +} + +impl From for WALError { + fn from(err: AIOError) -> Self { + Self::AIOError(err) + } +} diff --git a/firewood-growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs index 6bde605136b0..268d26a1e4ac 100644 --- a/firewood-growth-ring/tests/common/mod.rs +++ b/firewood-growth-ring/tests/common/mod.rs @@ -40,7 +40,7 @@ pub struct WALFileEmul { impl WALFile for WALFileEmul { async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("allocate fgen next fail".to_string())); } let offset = offset as usize; if offset + length > self.file.borrow().len() { @@ -54,7 +54,7 @@ impl WALFile for WALFileEmul { async fn truncate(&self, length: usize) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("truncate fgen next fail".to_string())); } self.file.borrow_mut().resize(length, 0); Ok(()) @@ -62,7 +62,7 @@ impl WALFile for WALFileEmul { async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError> { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("write fgen next fail".to_string())); } let offset = offset as usize; self.file.borrow_mut()[offset..offset + data.len()].copy_from_slice(&data); @@ -71,7 +71,7 @@ impl WALFile for WALFileEmul { async fn read(&self, offset: WALPos, length: usize) -> Result, WALError> { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("read fgen next fail".to_string())); } let offset = offset as usize; @@ -128,7 +128,7 @@ where async fn open_file(&self, filename: &str, touch: bool) -> Result, WALError> { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("open_file fgen next fail".to_string())); } match self.state.borrow_mut().files.entry(filename.to_string()) { hash_map::Entry::Occupied(e) => Ok(Box::new(WALFileEmul { @@ -142,7 +142,7 @@ where fgen: self.fgen.clone(), })) } else { - Err(WALError::Other) + Err(WALError::Other("open_file not found".to_string())) } } } @@ -151,19 +151,21 @@ where async fn remove_file(&self, filename: String) -> Result<(), WALError> { //println!("remove_file(filename={})", filename); if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("remove_file fgen next fail".to_string())); } self.state .borrow_mut() .files .remove(&filename) - .ok_or(WALError::Other) + .ok_or(WALError::Other("remove_file not found".to_string())) .map(|_| ()) } fn enumerate_files(&self) -> Result { if self.fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other( + "enumerate_files fgen next fail".to_string(), + )); } let mut logfiles = Vec::new(); for (fname, _) in self.state.borrow().files.iter() { @@ -501,7 +503,7 @@ impl PaintingSim { WALStoreEmul::new(state, fgen.clone()), |_, _| { if fgen.next_fail() { - Err(WALError::Other) + Err(WALError::Other("run fgen fail".to_string())) } else { Ok(()) } @@ -529,7 +531,8 @@ impl PaintingSim { .zip(pss_.into_iter()) .map(|(r, ps)| -> Result<_, _> { ops.push(ps); - let (rec, rid) = futures::executor::block_on(r).map_err(|_| WALError::Other)?; + let (rec, rid) = futures::executor::block_on(r) + .map_err(|_| WALError::Other("paintstrokes executor error".to_string()))?; ringid_map.insert(rid, ops.len() - 1); Ok((rec, rid)) }) @@ -548,7 +551,7 @@ impl PaintingSim { for _ in 0..rng.gen_range(1..self.k) { // storage I/O could fail if fgen.next_fail() { - return Err(WALError::Other); + return Err(WALError::Other("run fgen fail: storage i/o".to_string())); } if let Some((fin_rid, _)) = canvas.rand_paint(&mut rng) { if let Some(rid) = fin_rid { diff --git a/firewood-libaio/src/lib.rs b/firewood-libaio/src/lib.rs index 82255a9a1803..7c0952d77fd6 100644 --- a/firewood-libaio/src/lib.rs +++ b/firewood-libaio/src/lib.rs @@ -51,7 +51,7 @@ const LIBAIO_EAGAIN: libc::c_int = -libc::EAGAIN; const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum AIOError { MaxEventsTooLarge, LowKernelRes, diff --git a/firewood-shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs index 56190f33b836..ffdbb0238c2a 100644 --- a/firewood-shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -37,8 +37,8 @@ impl<'a> Profiler for FlamegraphProfiler<'a> { fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { std::fs::create_dir_all(benchmark_dir).unwrap(); let flamegraph_path = benchmark_dir.join("flamegraph.svg"); - let flamegraph_file = File::create(&flamegraph_path) - .expect("File system error while creating flamegraph.svg"); + let flamegraph_file = + File::create(flamegraph_path).expect("File system error while creating flamegraph.svg"); if let Some(profiler) = self.active_profiler.take() { profiler .report() @@ -59,7 +59,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); - cached.write(offset, &rdata); + cached.write(offset, rdata); let view = cached .get_view(offset, rdata.len().try_into().unwrap()) .unwrap(); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 36f9b5a241d8..b01426675372 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -38,6 +38,7 @@ const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; #[derive(Debug)] +#[non_exhaustive] pub enum DBError { InvalidParams, Merkle(MerkleError), @@ -46,6 +47,7 @@ pub enum DBError { System(nix::Error), KeyNotFound, CreateError, + IO(std::io::Error), } impl fmt::Display for DBError { @@ -58,10 +60,17 @@ impl fmt::Display for DBError { DBError::System(e) => write!(f, "system error: {e:?}"), DBError::KeyNotFound => write!(f, "not found"), DBError::CreateError => write!(f, "database create error"), + DBError::IO(e) => write!(f, "I/O error: {e:?}"), } } } +impl From for DBError { + fn from(e: std::io::Error) -> Self { + DBError::IO(e) + } +} + impl Error for DBError {} /// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the @@ -450,18 +459,17 @@ impl DB { if cfg.truncate { let _ = std::fs::remove_dir_all(db_path.as_ref()); } - let (db_fd, reset) = file::open_dir(db_path, cfg.truncate).map_err(DBError::System)?; + let (db_path, reset) = file::open_dir(db_path, cfg.truncate)?; - let merkle_fd = file::touch_dir("merkle", db_fd).map_err(DBError::System)?; - let merkle_meta_fd = file::touch_dir("meta", merkle_fd).map_err(DBError::System)?; - let merkle_payload_fd = file::touch_dir("compact", merkle_fd).map_err(DBError::System)?; + let merkle_path = file::touch_dir("merkle", &db_path)?; + let merkle_meta_path = file::touch_dir("meta", &merkle_path)?; + let merkle_payload_path = file::touch_dir("compact", &merkle_path)?; - let blob_fd = file::touch_dir("blob", db_fd).map_err(DBError::System)?; - let blob_meta_fd = file::touch_dir("meta", blob_fd).map_err(DBError::System)?; - let blob_payload_fd = file::touch_dir("compact", blob_fd).map_err(DBError::System)?; + let blob_path = file::touch_dir("blob", &db_path)?; + let blob_meta_path = file::touch_dir("meta", &blob_path)?; + let blob_payload_path = file::touch_dir("compact", &blob_path)?; - let file0 = - crate::file::File::new(0, SPACE_RESERVED, merkle_meta_fd).map_err(DBError::System)?; + let file0 = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; let fd0 = file0.get_fd(); if reset { @@ -504,7 +512,7 @@ impl DB { .ncached_files(cfg.meta_ncached_files) .space_id(MERKLE_META_SPACE) .file_nbit(header.meta_file_nbit) - .rootfd(merkle_meta_fd) + .rootdir(merkle_meta_path) .build(), ) .unwrap(), @@ -516,7 +524,7 @@ impl DB { .ncached_files(cfg.payload_ncached_files) .space_id(MERKLE_PAYLOAD_SPACE) .file_nbit(header.payload_file_nbit) - .rootfd(merkle_payload_fd) + .rootdir(merkle_payload_path) .build(), ) .unwrap(), @@ -530,7 +538,7 @@ impl DB { .ncached_files(cfg.meta_ncached_files) .space_id(BLOB_META_SPACE) .file_nbit(header.meta_file_nbit) - .rootfd(blob_meta_fd) + .rootdir(blob_meta_path) .build(), ) .unwrap(), @@ -542,7 +550,7 @@ impl DB { .ncached_files(cfg.payload_ncached_files) .space_id(BLOB_PAYLOAD_SPACE) .file_nbit(header.payload_file_nbit) - .rootfd(blob_payload_fd) + .rootdir(blob_payload_path) .build(), ) .unwrap(), @@ -588,7 +596,7 @@ impl DB { }; // recover from WAL - disk_requester.init_wal("wal", db_fd); + disk_requester.init_wal("wal", db_path); // set up the storage layout diff --git a/firewood/src/file.rs b/firewood/src/file.rs index c81f53988567..2f8609d753ff 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -3,57 +3,53 @@ // Copied from CedrusDB +use std::os::fd::IntoRawFd; pub(crate) use std::os::unix::io::RawFd as Fd; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::{io::ErrorKind, os::unix::prelude::OpenOptionsExt}; -use growthring::oflags; -use nix::errno::Errno; -use nix::fcntl::{open, openat, OFlag}; -use nix::sys::stat::Mode; -use nix::unistd::{close, mkdir}; +use nix::unistd::close; pub struct File { fd: Fd, } impl File { - pub fn open_file(rootfd: Fd, fname: &str, truncate: bool) -> nix::Result { - openat( - rootfd, - fname, - (if truncate { - OFlag::O_TRUNC - } else { - OFlag::empty() - }) | OFlag::O_RDWR, - Mode::S_IRUSR | Mode::S_IWUSR, - ) + pub fn open_file(rootpath: PathBuf, fname: &str, truncate: bool) -> Result { + let mut filepath = rootpath; + filepath.push(fname); + Ok(std::fs::File::options() + .truncate(truncate) + .read(true) + .write(true) + .mode(0o600) + .open(filepath)? + .into_raw_fd()) } - pub fn create_file(rootfd: Fd, fname: &str) -> nix::Result { - openat( - rootfd, - fname, - OFlag::O_CREAT | OFlag::O_RDWR, - Mode::S_IRUSR | Mode::S_IWUSR, - ) + pub fn create_file(rootpath: PathBuf, fname: &str) -> Result { + let mut filepath = rootpath; + filepath.push(fname); + Ok(std::fs::File::options() + .create(true) + .read(true) + .write(true) + .mode(0o600) + .open(filepath)? + .into_raw_fd()) } fn _get_fname(fid: u64) -> String { format!("{fid:08x}.fw") } - pub fn new(fid: u64, flen: u64, rootfd: Fd) -> nix::Result { + pub fn new>(fid: u64, _flen: u64, rootdir: P) -> Result { let fname = Self::_get_fname(fid); - let fd = match Self::open_file(rootfd, &fname, false) { + let fd = match Self::open_file(rootdir.as_ref().to_path_buf(), &fname, false) { Ok(fd) => fd, - Err(e) => match e { - Errno::ENOENT => { - let fd = Self::create_file(rootfd, &fname)?; - nix::unistd::ftruncate(fd, flen as nix::libc::off_t)?; - fd - } - e => return Err(e), + Err(e) => match e.kind() { + ErrorKind::NotFound => Self::create_file(rootdir.as_ref().to_path_buf(), &fname)?, + _ => return Err(e), }, }; Ok(File { fd }) @@ -70,31 +66,28 @@ impl Drop for File { } } -pub fn touch_dir(dirname: &str, rootfd: Fd) -> Result { - use nix::sys::stat::mkdirat; - if mkdirat( - rootfd, - dirname, - Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR, - ) - .is_err() - { - let errno = nix::errno::from_i32(nix::errno::errno()); - if errno != nix::errno::Errno::EEXIST { - return Err(errno); +pub fn touch_dir(dirname: &str, rootdir: &Path) -> Result { + let path = rootdir.join(dirname); + if let Err(e) = std::fs::create_dir(&path) { + // ignore already-exists error + if e.kind() != ErrorKind::AlreadyExists { + return Err(e); } } - openat(rootfd, dirname, oflags(), Mode::empty()) + Ok(path) } -pub fn open_dir>(path: P, truncate: bool) -> Result<(Fd, bool), nix::Error> { +pub fn open_dir>( + path: P, + truncate: bool, +) -> Result<(PathBuf, bool), std::io::Error> { let mut reset_header = truncate; if truncate { let _ = std::fs::remove_dir_all(path.as_ref()); } - match mkdir(path.as_ref(), Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IXUSR) { + match std::fs::create_dir(path.as_ref()) { Err(e) => { - if truncate { + if truncate || e.kind() != ErrorKind::AlreadyExists { return Err(e); } } @@ -103,20 +96,5 @@ pub fn open_dir>(path: P, truncate: bool) -> Result<(Fd, bool), n reset_header = true } } - Ok(( - match open(path.as_ref(), oflags(), Mode::empty()) { - Ok(fd) => fd, - Err(e) => return Err(e), - }, - reset_header, - )) -} - -#[test] -/// This test simulates a filesystem error: for example the specified path -/// does not exist when creating a file. -fn test_create_file() { - if let Err(e) = File::create_file(0, "/badpath/baddir") { - assert_eq!(e.desc(), "No such file or directory") - } + Ok((PathBuf::from(path.as_ref()), reset_header)) } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index f45faa4079a4..558dd6dc50c5 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -67,6 +67,10 @@ impl From for ProofError { DBError::System(e) => ProofError::SystemError(e), DBError::KeyNotFound => ProofError::InvalidEdgeKeys, DBError::CreateError => ProofError::NoSuchNode, + // TODO: fix better by adding a new error to ProofError + DBError::IO(e) => { + ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) + } } } } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 94b2e58d4843..fdca8457b2f4 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,11 +1,10 @@ //! Disk buffer for staging in memory pages and flushing them to disk. use std::fmt::Debug; +use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; -use crate::storage::Fd; - use super::{ Ash, AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WALConfig, PAGE_SIZE_NBIT, @@ -25,7 +24,7 @@ use typed_builder::TypedBuilder; #[derive(Debug)] pub enum BufferCmd { /// Initialize the WAL. - InitWAL(Fd, String), + InitWAL(PathBuf, String), /// Process a write batch against the underlying store. WriteBatch(Vec, AshRecord), /// Get a page from the disk buffer. @@ -184,12 +183,12 @@ impl DiskBuffer { } /// Initialize the WAL subsystem if it does not exists and attempts to replay the WAL if exists. - async fn init_wal(&mut self, rootfd: Fd, waldir: String) -> Result<(), WALError> { + async fn init_wal(&mut self, rootpath: PathBuf, waldir: String) -> Result<(), WALError> { + let final_path = rootpath.clone().join(waldir.clone()); let mut aiobuilder = AIOBuilder::default(); aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); - let aiomgr = aiobuilder.build().map_err(|_| WALError::Other)?; - let store = WALStoreAIO::new(&waldir, false, Some(rootfd), Some(aiomgr)) - .map_err(|_| WALError::Other)?; + let aiomgr = aiobuilder.build()?; + let store = WALStoreAIO::new(final_path.clone(), false, Some(aiomgr))?; let mut loader = WALLoader::new(); loader .file_nbit(self.wal_cfg.file_nbit) @@ -214,12 +213,22 @@ impl DiskBuffer { nix::sys::uio::pwrite( file_pool .get_file(fid) - .map_err(|_| WALError::Other)? + .map_err(|e| { + WALError::Other(format!( + "file pool error: {:?} - final path {:?}", + e, final_path + )) + })? .get_fd(), &data, (offset & file_mask) as nix::libc::off_t, ) - .map_err(|_| WALError::Other)?; + .map_err(|e| { + WALError::Other(format!( + "wal loader error: {:?} - final path {:?}", + e, final_path + )) + })?; } } Ok(()) @@ -312,10 +321,15 @@ impl DiskBuffer { ) -> bool { match req { BufferCmd::Shutdown => return false, - BufferCmd::InitWAL(rootfd, waldir) => { - if (self.init_wal(rootfd, waldir).await).is_err() { - panic!("cannot initialize from WAL") - } + BufferCmd::InitWAL(root_path, waldir) => { + self.init_wal(root_path.clone(), waldir.clone()) + .await + .unwrap_or_else(|e| { + panic!( + "Initialize WAL in dir {:?} failed creating {:?}: {e:?}", + root_path, waldir + ) + }); } BufferCmd::GetPage(page_key, tx) => tx .send(self.pending.get(&page_key).map(|e| e.staging_data.clone())) @@ -452,9 +466,9 @@ impl DiskBufferRequester { } /// Initialize the WAL. - pub fn init_wal(&self, waldir: &str, rootfd: Fd) { + pub fn init_wal(&self, waldir: &str, rootpath: PathBuf) { self.sender - .blocking_send(BufferCmd::InitWAL(rootfd, waldir.to_string())) + .blocking_send(BufferCmd::InitWAL(rootpath, waldir.to_string())) .map_err(StoreError::Send) .ok(); } @@ -482,11 +496,7 @@ impl DiskBufferRequester { #[cfg(test)] mod tests { - use std::{ - path::{Path, PathBuf}, - thread, - time::Duration, - }; + use std::path::PathBuf; use super::*; use crate::{ @@ -508,14 +518,15 @@ mod tests { let wal_cfg = WALConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); - let (root_db_fd, reset) = - crate::file::open_dir(&tmpdb.into_iter().collect::(), true).unwrap(); + // TODO: Run the test in a separate standalone directory for concurrency reasons + let path = std::path::PathBuf::from(r"/tmp/firewood"); + let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); // file descriptor of the state directory - let state_fd = file::touch_dir("state", root_db_fd).unwrap(); + let state_path = file::touch_dir("state", &root_db_path).unwrap(); assert!(reset); // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", root_db_fd); + disk_requester.init_wal("wal", root_db_path); // create a new state cache which tracks on disk state. let state_cache = Rc::new( @@ -525,7 +536,7 @@ mod tests { .ncached_files(1) .space_id(STATE_SPACE) .file_nbit(1) - .rootfd(state_fd) + .rootdir(state_path) .build(), ) .unwrap(), @@ -536,7 +547,7 @@ mod tests { disk_requester.reg_cached_space(state_cache.as_ref()); // memory mapped store - let mut mut_store = StoreRevMut::new(state_cache.clone() as Rc); + let mut mut_store = StoreRevMut::new(state_cache as Rc); let change = b"this is a test"; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index b93fdd465abc..bed1fafa3fc5 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,8 +4,10 @@ pub mod buffer; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::fmt::{self, Debug}; +use std::io; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; @@ -17,7 +19,7 @@ use tokio::sync::mpsc::error::SendError; use tokio::sync::oneshot::error::RecvError; use typed_builder::TypedBuilder; -use crate::file::{Fd, File}; +use crate::file::File; use self::buffer::DiskBufferRequester; @@ -29,6 +31,8 @@ pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; pub enum StoreError { #[error("system error: `{0}`")] System(#[from] nix::Error), + #[error("io error: `{0}`")] + Io(Box), #[error("init error: `{0}`")] Init(String), // TODO: more error report from the DiskBuffer @@ -39,6 +43,12 @@ pub enum StoreError { Receive(#[from] RecvError), } +impl From for StoreError { + fn from(e: std::io::Error) -> Self { + StoreError::Io(Box::new(e)) + } +} + pub trait MemStoreR: Debug { /// Returns a slice of bytes from memory. fn get_slice(&self, offset: u64, length: u64) -> Option>; @@ -609,7 +619,7 @@ pub struct StoreConfig { #[builder(default = 22)] // 4MB file by default file_nbit: u64, space_id: SpaceID, - rootfd: Fd, + rootdir: PathBuf, } #[derive(Debug)] @@ -627,7 +637,7 @@ pub struct CachedSpace { } impl CachedSpace { - pub fn new(cfg: &StoreConfig) -> Result> { + pub fn new(cfg: &StoreConfig) -> Result> { let space_id = cfg.space_id; let files = Arc::new(FilePool::new(cfg)?); Ok(Self { @@ -662,7 +672,7 @@ impl CachedSpaceInner { &mut self, space_id: SpaceID, pid: u64, - ) -> Result, StoreError> { + ) -> Result, StoreError> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { return Ok(Box::new(*p)); } @@ -684,7 +694,7 @@ impl CachedSpaceInner { &mut self, space_id: SpaceID, pid: u64, - ) -> Result<&'static mut [u8], StoreError> { + ) -> Result<&'static mut [u8], StoreError> { let base = match self.pinned_pages.get_mut(&pid) { Some(mut e) => { e.0 += 1; @@ -794,19 +804,19 @@ impl MemStoreR for CachedSpace { pub struct FilePool { files: parking_lot::Mutex>>, file_nbit: u64, - rootfd: Fd, + rootdir: PathBuf, } impl FilePool { - fn new(cfg: &StoreConfig) -> Result> { - let rootfd = cfg.rootfd; + fn new(cfg: &StoreConfig) -> Result> { + let rootdir = &cfg.rootdir; let file_nbit = cfg.file_nbit; let s = Self { files: parking_lot::Mutex::new(lru::LruCache::new( NonZeroUsize::new(cfg.ncached_files).expect("non-zero file num"), )), file_nbit, - rootfd, + rootdir: rootdir.to_path_buf(), }; let f0 = s.get_file(0)?; if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() { @@ -815,7 +825,7 @@ impl FilePool { Ok(s) } - fn get_file(&self, fid: u64) -> Result, StoreError> { + fn get_file(&self, fid: u64) -> Result, StoreError> { let mut files = self.files.lock(); let file_size = 1 << self.file_nbit; Ok(match files.get(&fid) { @@ -823,7 +833,7 @@ impl FilePool { None => { files.put( fid, - Arc::new(File::new(fid, file_size, self.rootfd).map_err(StoreError::System)?), + Arc::new(File::new(fid, file_size, self.rootdir.clone())?), ); files.peek(&fid).unwrap().clone() } @@ -839,7 +849,6 @@ impl Drop for FilePool { fn drop(&mut self) { let f0 = self.get_file(0).unwrap(); flock(f0.get_fd(), FlockArg::UnlockNonblock).ok(); - nix::unistd::close(self.rootfd).ok(); } } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 74296450090e..3b9855e02395 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -316,7 +316,7 @@ fn test_bad_proof() -> Result<(), DataStoreError> { fn test_missing_key_proof() -> Result<(), DataStoreError> { let items = vec![("k", "v")]; let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - for key in vec!["a", "j", "l", "z"] { + for key in &["a", "j", "l", "z"] { let proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); assert!(proof.0.len() == 1); @@ -401,8 +401,8 @@ fn test_bad_range_proof() -> Result<(), ProofError> { let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); for i in start..end { - keys.push(items[i].0.clone()); - vals.push(items[i].1.clone()); + keys.push(*items[i].0); + vals.push(*items[i].1); } let test_case: u32 = rand::thread_rng().gen_range(0..6); @@ -445,13 +445,7 @@ fn test_bad_range_proof() -> Result<(), ProofError> { _ => unreachable!(), } assert!(merkle - .verify_range_proof( - &proof, - items[start].0.clone(), - items[end - 1].0.clone(), - keys, - vals - ) + .verify_range_proof(&proof, *items[start].0, *items[end - 1].0, keys, vals) .is_err()); } @@ -909,7 +903,7 @@ fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { fn test_both_sides_range_proof() -> Result<(), ProofError> { for _ in 0..10 { let mut set = HashMap::new(); - for _ in 0..4096 as u32 { + for _ in 0..4096_u32 { let key = rand::thread_rng().gen::<[u8; 32]>(); let val = rand::thread_rng().gen::<[u8; 20]>(); set.insert(key, val); @@ -1050,7 +1044,7 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { fn test_bloadted_range_proof() -> Result<(), ProofError> { // Use a small trie let mut items = Vec::new(); - for i in 0..100 as u32 { + for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; let mut data: [u8; 20] = [0; 20]; for (index, d) in i.to_be_bytes().iter().enumerate() { @@ -1067,7 +1061,7 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { let mut keys = Vec::new(); let mut vals = Vec::new(); for (i, item) in items.iter().enumerate() { - let cur_proof = merkle.prove(&item.0)?; + let cur_proof = merkle.prove(item.0)?; assert!(!cur_proof.0.is_empty()); proof.concat_proofs(cur_proof); if i == 50 { From 7dc1bfeddb9cb64e01ba0b7cd900eccb5c0bf24e Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 27 Apr 2023 12:01:24 -0700 Subject: [PATCH 0138/1053] Get the current committed state with `get_revision(0)` (#67) --- firewood/examples/rev.rs | 15 ++++++++++ firewood/src/db.rs | 60 ++++++++++++++++++++++++++++++------- firewood/src/storage/mod.rs | 5 ++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index daa95a097ca7..1e8a0c80b6a4 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -17,6 +17,15 @@ fn main() { println!("{}", hex::encode(*db.kv_root_hash().unwrap())); } db.kv_dump(&mut std::io::stdout()).unwrap(); + println!( + "{}", + hex::encode(*db.get_revision(0, None).unwrap().kv_root_hash().unwrap()) + ); + // The latest committed revision matches with the current state without dirty writes. + assert_eq!( + db.kv_root_hash().unwrap(), + db.get_revision(0, None).unwrap().kv_root_hash().unwrap() + ); println!( "{}", hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) @@ -53,6 +62,12 @@ fn main() { { let db = DB::new("rev_db", &cfg.truncate(false).build()).unwrap(); { + // The latest committed revision matches with the current state after replaying from WALs. + assert_eq!( + db.kv_root_hash().unwrap(), + db.get_revision(0, None).unwrap().kv_root_hash().unwrap() + ); + let rev = db.get_revision(1, None).unwrap(); println!("{}", hex::encode(*rev.kv_root_hash().unwrap())); rev.kv_dump(&mut std::io::stdout()).unwrap(); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b01426675372..59f4e39a5613 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -25,8 +25,8 @@ use crate::proof::{Proof, ProofError}; use crate::storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}; pub use crate::storage::{buffer::DiskBufferConfig, WALConfig}; use crate::storage::{ - AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreRevMut, StoreRevShared, - PAGE_SIZE_NBIT, + AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, + StoreRevShared, PAGE_SIZE_NBIT, }; const MERKLE_META_SPACE: SpaceID = 0x0; @@ -450,6 +450,7 @@ pub struct DB { pub struct DBRevInner { inner: VecDeque>, max_revisions: usize, + base: Universe, } impl DB { @@ -695,6 +696,17 @@ impl DB { }; latest.flush_dirty().unwrap(); + let base = Universe { + merkle: SubUniverse::new( + StoreRevShared::from_delta(cached.merkle.meta.clone(), StoreDelta::new_empty()), + StoreRevShared::from_delta(cached.merkle.payload.clone(), StoreDelta::new_empty()), + ), + blob: SubUniverse::new( + StoreRevShared::from_delta(cached.blob.meta.clone(), StoreDelta::new_empty()), + StoreRevShared::from_delta(cached.blob.payload.clone(), StoreDelta::new_empty()), + ), + }; + Ok(Self { inner: Arc::new(RwLock::new(DBInner { latest, @@ -706,6 +718,7 @@ impl DB { revisions: Arc::new(Mutex::new(DBRevInner { inner: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, + base, })), payload_regn_nbit: header.payload_regn_nbit, rev_cfg: cfg.rev.clone(), @@ -739,20 +752,17 @@ impl DB { .kv_get(key) .ok_or(DBError::KeyNotFound) } - /// Get a handle that grants the access to some historical state of the entire DB. - /// - /// Note: There must be at least two committed batches in order for this function to return an older 'Revision', - /// other than the latest state. + /// Get a handle that grants the access to any committed state of the entire DB. /// - /// The latest revision (nback) starts from 1, which is one behind the current state. - /// If nback equals 0, or is above the configured maximum number of revisions, this function returns None. + /// The latest revision (nback) starts from 0, which is the current state. + /// If nback equals is above the configured maximum number of revisions, this function returns None. /// It also returns None in the case where the nback is larger than the number of revisions available. pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { let mut revisions = self.revisions.lock(); let inner = self.inner.read(); let rlen = revisions.inner.len(); - if nback == 0 || nback > revisions.max_revisions { + if nback > revisions.max_revisions { return None; } if rlen < nback { @@ -778,7 +788,6 @@ impl DB { if revisions.inner.len() < nback { return None; } - drop(inner); // set up the storage layout let mut offset = std::mem::size_of::() as u64; @@ -792,7 +801,12 @@ impl DB { // Blob CompactSpaceHeader starts right in blob meta space let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let space = &revisions.inner[nback - 1]; + let space = if nback == 0 { + &revisions.base + } else { + &revisions.inner[nback - 1] + }; + drop(inner); let (db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = &space.merkle.meta; @@ -1022,6 +1036,30 @@ impl WriteBatch { revisions.inner.pop_back(); } + let base = Universe { + merkle: SubUniverse::new( + StoreRevShared::from_delta( + rev_inner.cached.merkle.meta.clone(), + StoreDelta::new_empty(), + ), + StoreRevShared::from_delta( + rev_inner.cached.merkle.payload.clone(), + StoreDelta::new_empty(), + ), + ), + blob: SubUniverse::new( + StoreRevShared::from_delta( + rev_inner.cached.blob.meta.clone(), + StoreDelta::new_empty(), + ), + StoreRevShared::from_delta( + rev_inner.cached.blob.payload.clone(), + StoreDelta::new_empty(), + ), + ), + }; + revisions.base = base; + self.committed = true; // schedule writes to the disk diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index bed1fafa3fc5..4d5109b417b8 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -170,6 +170,11 @@ impl Deref for StoreDelta { } impl StoreDelta { + pub fn new_empty() -> Self { + let deltas = Vec::new(); + Self(deltas) + } + pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { let mut deltas = Vec::new(); let mut widx: Vec<_> = (0..writes.len()) From f8b6b1cf5d0bfe9f1e15151e2306f6c28bc35fb4 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 27 Apr 2023 15:33:55 -0400 Subject: [PATCH 0139/1053] shale: Fix panic get_item on a dirty write (#66) Signed-off-by: Sam Batschelet --- firewood-shale/Cargo.toml | 1 + firewood-shale/src/compact.rs | 110 ++++++++++++++++++++++++++++++++++ firewood-shale/src/lib.rs | 11 ++-- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 48a55e5adfd9..9db09ad9a733 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -14,6 +14,7 @@ lru = "0.10.0" [dev-dependencies] criterion = { version = "0.4.0", features = ["html_reports"] } pprof = { version = "0.11.1", features = ["flamegraph"] } +sha3 = "0.10.7" rand = "0.8.5" [[bench]] diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index 9a935ebdca40..f04177c67114 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -584,3 +584,113 @@ impl ShaleStore for CompactSpace inner.obj_cache.flush_dirty() } } + +#[cfg(test)] +mod tests { + use sha3::Digest; + + use crate::{cached::DynamicMem, ObjCache}; + + use super::*; + + const HASH_SIZE: usize = 32; + const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); + + #[derive(PartialEq, Eq, Debug, Clone)] + pub struct Hash(pub [u8; HASH_SIZE]); + + impl Hash { + const MSIZE: u64 = 32; + } + + impl std::ops::Deref for Hash { + type Target = [u8; HASH_SIZE]; + fn deref(&self) -> &[u8; HASH_SIZE] { + &self.0 + } + } + + impl Storable for Hash { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::LinearCachedStoreError)?; + Ok(Self( + raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), + )) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) { + let mut cur = to; + cur.write_all(&self.0).unwrap() + } + } + + #[test] + fn test_space_item() { + const META_SIZE: u64 = 0x10000; + const COMPACT_SIZE: u64 = 0x10000; + const RESERVED: u64 = 0x1000; + + let mut dm = DynamicMem::new(META_SIZE, 0x0); + + // initialize compact space + let compact_header: ObjPtr = ObjPtr::new_from_addr(0x1); + dm.write( + compact_header.addr(), + &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)), + ); + let compact_header = + StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); + let mem_meta = Rc::new(dm); + let mem_payload = Rc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); + + let cache: ObjCache = ObjCache::new(1); + let space = + CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + + // initial write + let data = b"hello world"; + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(&data).into(); + let obj_ref = space.put_item(Hash(hash), 0).unwrap(); + assert_eq!(obj_ref.as_ptr().addr(), 4113); + // create hash ptr from address and attempt to read dirty write. + let hash_ref = space.get_item(ObjPtr::new_from_addr(4113)).unwrap(); + // read before flush results in zeroed hash + assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); + // not cached + assert!(obj_ref + .cache + .get_inner_mut() + .cached + .get(&ObjPtr::new_from_addr(4113)) + .is_none()); + // pinned + assert!(obj_ref + .cache + .get_inner_mut() + .pinned + .get(&ObjPtr::new_from_addr(4113)) + .is_some()); + // dirty + assert!(obj_ref + .cache + .get_inner_mut() + .dirty + .get(&ObjPtr::new_from_addr(4113)) + .is_some()); + drop(obj_ref); + // write is visible + assert_eq!( + space + .get_item(ObjPtr::new_from_addr(4113)) + .unwrap() + .as_ref(), + hash + ); + } +} diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index 4aebd3f284f4..c61ead23cc84 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -249,10 +249,13 @@ impl<'a, T> Drop for ObjRef<'a, T> { let mut inner = self.inner.take().unwrap(); let ptr = inner.as_ptr(); let cache = self.cache.get_inner_mut(); - if cache.pinned.remove(&ptr).unwrap() { - inner.dirty = None; - } else { - cache.cached.put(ptr, inner); + match cache.pinned.remove(&ptr) { + Some(true) => { + inner.dirty = None; + } + _ => { + cache.cached.put(ptr, inner); + } } } } From 503f4e5095737a75d14734a7ff60beb29fed9ff0 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 28 Apr 2023 15:41:17 -0400 Subject: [PATCH 0140/1053] shale: improve error handling (#70) Signed-off-by: Sam Batschelet --- firewood-shale/Cargo.toml | 1 + firewood-shale/benches/shale-bench.rs | 6 +- firewood-shale/src/cached.rs | 14 +-- firewood-shale/src/compact.rs | 133 ++++++++++++-------- firewood-shale/src/lib.rs | 91 ++++++++------ firewood/src/db.rs | 31 +++-- firewood/src/merkle.rs | 170 +++++++++++++++----------- firewood/src/merkle_util.rs | 3 +- firewood/src/proof.rs | 4 + firewood/src/storage/mod.rs | 8 +- 10 files changed, 283 insertions(+), 178 deletions(-) diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 9db09ad9a733..1e7efd9ade27 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -10,6 +10,7 @@ license = "MIT" [dependencies] hex = "0.4.3" lru = "0.10.0" +thiserror = "1.0.38" [dev-dependencies] criterion = { version = "0.4.0", features = ["html_reports"] } diff --git a/firewood-shale/benches/shale-bench.rs b/firewood-shale/benches/shale-bench.rs index ffdbb0238c2a..ca052fea9158 100644 --- a/firewood-shale/benches/shale-bench.rs +++ b/firewood-shale/benches/shale-bench.rs @@ -1,6 +1,8 @@ extern crate firewood_shale as shale; -use criterion::{criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion}; +use criterion::{ + black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, +}; use pprof::ProfilerGuard; use rand::Rng; use shale::{ @@ -55,7 +57,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { b.iter(|| { let len = rng.gen_range(0..26); - let rdata = &"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]; + let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); diff --git a/firewood-shale/src/cached.rs b/firewood-shale/src/cached.rs index 4ca244ebbe38..2d4c67eb1b6a 100644 --- a/firewood-shale/src/cached.rs +++ b/firewood-shale/src/cached.rs @@ -46,11 +46,11 @@ impl CachedStore for PlainMem { } } - fn get_shared(&self) -> Option>> { - Some(Box::new(PlainMemShared(Self { + fn get_shared(&self) -> Box> { + Box::new(PlainMemShared(Self { space: self.space.clone(), id: self.id, - }))) + })) } fn write(&mut self, offset: u64, change: &[u8]) { @@ -140,11 +140,11 @@ impl CachedStore for DynamicMem { })) } - fn get_shared(&self) -> Option>> { - Some(Box::new(DynamicMemShared(Self { + fn get_shared(&self) -> Box> { + Box::new(DynamicMemShared(Self { space: self.space.clone(), id: self.id, - }))) + })) } fn write(&mut self, offset: u64, change: &[u8]) { @@ -240,7 +240,7 @@ mod tests { mem.write(0, &[1, 2]); mem.write(0, &[3, 4]); assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); - mem.get_shared().unwrap().write(0, &[5, 6]); + mem.get_shared().write(0, &[5, 6]); // capacity is increased mem.write(5, &[0; 10]); diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index f04177c67114..7adc26ebe892 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -24,10 +24,15 @@ impl Storable for CompactHeader { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; - let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let payload_size = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); let is_freed = raw.as_deref()[8] != 0; - let desc_addr = u64::from_le_bytes(raw.as_deref()[9..17].try_into().unwrap()); + let desc_addr = + u64::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); Ok(Self { payload_size, is_freed, @@ -39,11 +44,12 @@ impl Storable for CompactHeader { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes()).unwrap(); - cur.write_all(&[if self.is_freed { 1 } else { 0 }]).unwrap(); - cur.write_all(&self.desc_addr.addr().to_le_bytes()).unwrap(); + cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; + cur.write_all(&self.desc_addr.addr().to_le_bytes())?; + Ok(()) } } @@ -59,7 +65,10 @@ impl Storable for CompactFooter { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); Ok(Self { payload_size }) } @@ -68,10 +77,9 @@ impl Storable for CompactFooter { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { - Cursor::new(to) - .write_all(&self.payload_size.to_le_bytes()) - .unwrap(); + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.payload_size.to_le_bytes())?; + Ok(()) } } @@ -89,9 +97,13 @@ impl Storable for CompactDescriptor { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; - let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); - let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let payload_size = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); Ok(Self { payload_size, haddr, @@ -102,10 +114,11 @@ impl Storable for CompactDescriptor { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes()).unwrap(); - cur.write_all(&self.haddr.to_le_bytes()).unwrap(); + cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&self.haddr.to_le_bytes())?; + Ok(()) } } @@ -158,11 +171,18 @@ impl Storable for CompactSpaceHeader { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; - let meta_space_tail = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); - let compact_space_tail = u64::from_le_bytes(raw.as_deref()[8..16].try_into().unwrap()); - let base_addr = u64::from_le_bytes(raw.as_deref()[16..24].try_into().unwrap()); - let alloc_addr = u64::from_le_bytes(raw.as_deref()[24..].try_into().unwrap()); + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let meta_space_tail = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let compact_space_tail = + u64::from_le_bytes(raw.as_deref()[8..16].try_into().expect("invalid slice")); + let base_addr = + u64::from_le_bytes(raw.as_deref()[16..24].try_into().expect("invalid slice")); + let alloc_addr = + u64::from_le_bytes(raw.as_deref()[24..].try_into().expect("invalid slice")); Ok(Self { meta_space_tail, compact_space_tail, @@ -175,14 +195,13 @@ impl Storable for CompactSpaceHeader { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.meta_space_tail.to_le_bytes()).unwrap(); - cur.write_all(&self.compact_space_tail.to_le_bytes()) - .unwrap(); - cur.write_all(&self.base_addr.addr().to_le_bytes()).unwrap(); - cur.write_all(&self.alloc_addr.addr().to_le_bytes()) - .unwrap(); + cur.write_all(&self.meta_space_tail.to_le_bytes())?; + cur.write_all(&self.compact_space_tail.to_le_bytes())?; + cur.write_all(&self.base_addr.addr().to_le_bytes())?; + cur.write_all(&self.alloc_addr.addr().to_le_bytes())?; + Ok(()) } } @@ -196,16 +215,19 @@ impl Storable for ObjPtrField { fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; - Ok(Self(ObjPtr::new_from_addr(u64::from_le_bytes( - raw.as_deref().try_into().unwrap(), - )))) - } - - fn dehydrate(&self, to: &mut [u8]) { - Cursor::new(to) - .write_all(&self.0.addr().to_le_bytes()) - .unwrap() + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let obj_ptr = ObjPtr::new_from_addr(u64::from_le_bytes( + <[u8; 8]>::try_from(&raw.as_deref()[0..8]).expect("invalid slice"), + )); + Ok(Self(obj_ptr)) + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0.addr().to_le_bytes())?; + Ok(()) } fn dehydrated_len(&self) -> u64 { @@ -236,7 +258,10 @@ impl Storable for U64Field { fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) } @@ -244,8 +269,9 @@ impl Storable for U64Field { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { - Cursor::new(to).write_all(&self.0.to_le_bytes()).unwrap() + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0.to_le_bytes())?; + Ok(()) } } @@ -570,7 +596,10 @@ impl ShaleStore for CompactSpace return Ok(r); } if ptr.addr() < CompactSpaceHeader::MSIZE { - return Err(ShaleError::ObjPtrInvalid); + return Err(ShaleError::InvalidAddressLength { + expected: CompactSpaceHeader::MSIZE, + found: ptr.addr(), + }); } let h = inner.get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))?; Ok(inner @@ -614,9 +643,14 @@ mod tests { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; Ok(Self( - raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), + raw.as_deref()[..Self::MSIZE as usize] + .try_into() + .expect("invalid slice"), )) } @@ -624,9 +658,10 @@ mod tests { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = to; - cur.write_all(&self.0).unwrap() + cur.write_all(&self.0)?; + Ok(()) } } @@ -642,7 +677,7 @@ mod tests { let compact_header: ObjPtr = ObjPtr::new_from_addr(0x1); dm.write( compact_header.addr(), - &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)), + &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)).unwrap(), ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index c61ead23cc84..5a895cb876ae 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -1,23 +1,37 @@ +use std::any::type_name; use std::cell::UnsafeCell; use std::collections::{HashMap, HashSet}; -use std::fmt; -use std::fmt::Debug; +use std::fmt::{self, Debug, Display, Formatter}; +use std::hash::Hash; +use std::hash::Hasher; use std::marker::PhantomData; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::rc::Rc; +use thiserror::Error; + pub mod cached; pub mod compact; pub mod util; -#[derive(Debug)] +#[derive(Debug, Error)] +#[non_exhaustive] pub enum ShaleError { - LinearCachedStoreError, - DecodeError, - ObjRefAlreadyInUse, - ObjPtrInvalid, - SliceError, + #[error("obj invalid: {addr:?} obj: {obj_type:?} error: {error:?}")] + InvalidObj { + addr: u64, + obj_type: &'static str, + error: &'static str, + }, + #[error("invalid address length expected: {expected:?} found: {found:?})")] + InvalidAddressLength { expected: u64, found: u64 }, + #[error("invalid node type")] + InvalidNodeType, + #[error("failed to create view: offset: {offset:?} size: {size:?}")] + InvalidCacheView { offset: u64, size: u64 }, + #[error("io error: {0}")] + Io(#[from] std::io::Error), } pub type SpaceID = u8; @@ -60,7 +74,7 @@ pub trait CachedStore: Debug { length: u64, ) -> Option>>>; /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Option>>; + fn get_shared(&self) -> Box>; /// Write the `change` to the portion of the linear space starting at `offset`. The change /// should be immediately visible to all `CachedView` associated to this linear space. fn write(&mut self, offset: u64, change: &[u8]); @@ -89,14 +103,14 @@ impl Clone for ObjPtr { } } -impl std::hash::Hash for ObjPtr { - fn hash(&self, state: &mut H) { +impl Hash for ObjPtr { + fn hash(&self, state: &mut H) { self.addr.hash(state) } } -impl fmt::Display for ObjPtr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for ObjPtr { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[ObjPtr addr={:08x}]", self.addr) } } @@ -141,7 +155,7 @@ pub trait TypedView: Deref { fn estimate_mem_image(&self) -> Option; /// Serialize the type content to the memory image. It defines how the current in-memory object /// of `T` should be represented in the linear storage space. - fn write_mem_image(&self, mem_image: &mut [u8]); + fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError>; /// Gain mutable access to the typed content. By changing it, its serialized bytes (and length) /// could change. fn write(&mut self) -> &mut T; @@ -190,7 +204,8 @@ impl Obj { if !self.value.is_mem_mapped() { if let Some(new_value_len) = self.dirty.take() { let mut new_value = vec![0; new_value_len as usize]; - self.value.write_mem_image(&mut new_value); + // TODO: log error + self.value.write_mem_image(&mut new_value).unwrap(); let offset = self.value.get_offset(); let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); bx.write(offset, &new_value); @@ -278,7 +293,7 @@ pub trait ShaleStore { /// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. pub trait Storable { fn dehydrated_len(&self) -> u64; - fn dehydrate(&self, to: &mut [u8]); + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>; fn hydrate(addr: u64, mem: &T) -> Result where Self: Sized; @@ -287,10 +302,10 @@ pub trait Storable { } } -pub fn to_dehydrated(item: &dyn Storable) -> Vec { +pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { let mut buff = vec![0; item.dehydrated_len() as usize]; - item.dehydrate(&mut buff); - buff + item.dehydrate(&mut buff)?; + Ok(buff) } /// Reference implementation of [TypedView]. It takes any type that implements [Storable] @@ -330,7 +345,7 @@ impl TypedView for StoredView { } } - fn write_mem_image(&self, mem_image: &mut [u8]) { + fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { self.decoded.dehydrate(mem_image) } @@ -349,9 +364,7 @@ impl StoredView { Ok(Self { offset, decoded, - mem: space - .get_shared() - .ok_or(ShaleError::LinearCachedStoreError)?, + mem: space.get_shared(), len_limit, }) } @@ -366,9 +379,7 @@ impl StoredView { Ok(Self { offset, decoded, - mem: space - .get_shared() - .ok_or(ShaleError::LinearCachedStoreError)?, + mem: space.get_shared(), len_limit, }) } @@ -409,9 +420,7 @@ impl StoredView { Ok(Self { offset, decoded, - mem: space - .get_shared() - .ok_or(ShaleError::LinearCachedStoreError)?, + mem: space.get_shared(), len_limit, }) } @@ -424,7 +433,11 @@ impl StoredView { ) -> Result, ShaleError> { let addr_ = s.value.get_offset() + offset; if s.dirty.is_some() { - return Err(ShaleError::SliceError); + return Err(ShaleError::InvalidObj { + addr: offset, + obj_type: type_name::(), + error: "dirty write", + }); } let r = Box::new(StoredView::new_from_slice( addr_, @@ -448,17 +461,19 @@ impl Storable for ObjPtr { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { use std::io::{Cursor, Write}; - Cursor::new(to) - .write_all(&self.addr().to_le_bytes()) - .unwrap(); + Cursor::new(to).write_all(&self.addr().to_le_bytes())?; + Ok(()) } fn hydrate(addr: u64, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; let addrdyn = raw.deref(); let addrvec = addrdyn.as_deref(); Ok(Self::new_from_addr(u64::from_le_bytes( @@ -497,7 +512,11 @@ impl ObjCache { let inner = &mut self.get_inner_mut(); if let Some(r) = inner.cached.pop(&ptr) { if inner.pinned.insert(ptr, false).is_some() { - return Err(ShaleError::ObjRefAlreadyInUse); + return Err(ShaleError::InvalidObj { + addr: ptr.addr(), + obj_type: type_name::(), + error: "address already in use", + }); } return Ok(Some(ObjRef { inner: Some(r), diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 59f4e39a5613..0aa3378d2c78 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,6 +14,7 @@ use bytemuck::{cast_slice, AnyBitPattern}; use parking_lot::{Mutex, RwLock}; #[cfg(feature = "eth")] use primitive_types::U256; +use shale::ShaleError; use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceID, Storable, StoredView}; use typed_builder::TypedBuilder; @@ -47,6 +48,7 @@ pub enum DBError { System(nix::Error), KeyNotFound, CreateError, + Shale(ShaleError), IO(std::io::Error), } @@ -61,6 +63,7 @@ impl fmt::Display for DBError { DBError::KeyNotFound => write!(f, "not found"), DBError::CreateError => write!(f, "database create error"), DBError::IO(e) => write!(f, "I/O error: {e:?}"), + DBError::Shale(e) => write!(f, "shale error: {e:?}"), } } } @@ -71,6 +74,12 @@ impl From for DBError { } } +impl From for DBError { + fn from(e: ShaleError) -> Self { + DBError::Shale(e) + } +} + impl Error for DBError {} /// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the @@ -211,9 +220,12 @@ impl Storable for DBHeader { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(shale::ShaleError::LinearCachedStoreError)?; - let acc_root = u64::from_le_bytes(raw.as_deref()[..8].try_into().unwrap()); - let kv_root = u64::from_le_bytes(raw.as_deref()[8..].try_into().unwrap()); + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let acc_root = u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let kv_root = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); Ok(Self { acc_root: ObjPtr::new_from_addr(acc_root), kv_root: ObjPtr::new_from_addr(kv_root), @@ -224,10 +236,11 @@ impl Storable for DBHeader { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.acc_root.addr().to_le_bytes()).unwrap(); - cur.write_all(&self.kv_root.addr().to_le_bytes()).unwrap(); + cur.write_all(&self.acc_root.addr().to_le_bytes())?; + cur.write_all(&self.kv_root.addr().to_le_bytes())?; + Ok(()) } } @@ -616,11 +629,11 @@ impl DB { &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, SPACE_RESERVED, - )), + ))?, ); initializer.write( db_header.addr(), - &shale::to_dehydrated(&DBHeader::new_empty()), + &shale::to_dehydrated(&DBHeader::new_empty())?, ); let initializer = Rc::::make_mut(&mut staging.blob.meta); initializer.write( @@ -628,7 +641,7 @@ impl DB { &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, SPACE_RESERVED, - )), + ))?, ); } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 50f7aacc78e5..ad8847fda63a 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -61,7 +61,10 @@ impl Storable for Hash { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; Ok(Self( raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), )) @@ -71,8 +74,8 @@ impl Storable for Hash { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) { - Cursor::new(to).write_all(&self.0).unwrap() + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0).map_err(ShaleError::Io) } } @@ -479,17 +482,21 @@ impl Node { impl Storable for Node { fn hydrate(addr: u64, mem: &T) -> Result { - let dec_err = |_| ShaleError::DecodeError; const META_SIZE: u64 = 32 + 1 + 1; let meta_raw = mem .get_view(addr, META_SIZE) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: META_SIZE, + })?; let attrs = meta_raw.as_deref()[32]; let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None } else { Some(Hash( - meta_raw.as_deref()[0..32].try_into().map_err(dec_err)?, + meta_raw.as_deref()[0..32] + .try_into() + .expect("invalid slice"), )) }; let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { @@ -500,29 +507,34 @@ impl Storable for Node { match meta_raw.as_deref()[33] { Self::BRANCH_NODE => { let branch_header_size = NBRANCH as u64 * 8 + 4; - let node_raw = mem - .get_view(addr + META_SIZE, branch_header_size) - .ok_or(ShaleError::LinearCachedStoreError)?; + let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: branch_header_size, + }, + )?; let mut cur = Cursor::new(node_raw.as_deref()); let mut chd = [None; NBRANCH]; let mut buff = [0; 8]; for chd in chd.iter_mut() { - cur.read_exact(&mut buff) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff)?; let addr = u64::from_le_bytes(buff); if addr != 0 { *chd = Some(ObjPtr::new_from_addr(addr)) } } - cur.read_exact(&mut buff[..4]) - .map_err(|_| ShaleError::DecodeError)?; - let raw_len = u32::from_le_bytes(buff[..4].try_into().map_err(dec_err)?) as u64; + cur.read_exact(&mut buff[..4])?; + let raw_len = + u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")) as u64; let value = if raw_len == u32::MAX as u64 { None } else { Some(Data( mem.get_view(addr + META_SIZE + branch_header_size, raw_len) - .ok_or(ShaleError::LinearCachedStoreError)? + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + branch_header_size, + size: raw_len, + })? .as_deref(), )) }; @@ -535,18 +547,23 @@ impl Storable for Node { let mut cur_rlp_len = 0; for chd_rlp in chd_eth_rlp.iter_mut() { let mut buff = [0_u8; 1]; - let rlp_len_raw = mem - .get_view(offset + cur_rlp_len, 1) - .ok_or(ShaleError::LinearCachedStoreError)?; + let rlp_len_raw = mem.get_view(offset + cur_rlp_len, 1).ok_or( + ShaleError::InvalidCacheView { + offset: offset + cur_rlp_len, + size: 1, + }, + )?; cur = Cursor::new(rlp_len_raw.as_deref()); - cur.read_exact(&mut buff) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff)?; let rlp_len = buff[0] as u64; cur_rlp_len += 1; if rlp_len != 0 { - let rlp_raw = mem - .get_view(offset + cur_rlp_len, rlp_len) - .ok_or(ShaleError::LinearCachedStoreError)?; + let rlp_raw = mem.get_view(offset + cur_rlp_len, rlp_len).ok_or( + ShaleError::InvalidCacheView { + offset: offset + cur_rlp_len, + size: rlp_len, + }, + )?; let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); *chd_rlp = Some(rlp); cur_rlp_len += rlp_len @@ -565,20 +582,24 @@ impl Storable for Node { } Self::EXT_NODE => { let ext_header_size = 1 + 8; - let node_raw = mem - .get_view(addr + META_SIZE, ext_header_size) - .ok_or(ShaleError::LinearCachedStoreError)?; + let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: ext_header_size, + }, + )?; let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 8]; - cur.read_exact(&mut buff[..1]) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff[..1])?; let path_len = buff[0] as u64; - cur.read_exact(&mut buff) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff)?; let ptr = u64::from_le_bytes(buff); let nibbles: Vec<_> = to_nibbles( &mem.get_view(addr + META_SIZE + ext_header_size, path_len) - .ok_or(ShaleError::LinearCachedStoreError)? + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size, + size: path_len, + })? .as_deref(), ) .collect(); @@ -587,15 +608,21 @@ impl Storable for Node { let mut buff = [0_u8; 1]; let rlp_len_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len, 1) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size + path_len, + size: 1, + })?; cur = Cursor::new(rlp_len_raw.as_deref()); - cur.read_exact(&mut buff) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff)?; let rlp_len = buff[0] as u64; let rlp: Option> = if rlp_len != 0 { let rlp_raw = mem .get_view(addr + META_SIZE + ext_header_size + path_len + 1, rlp_len) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size + path_len + 1, + size: rlp_len, + })?; + Some(rlp_raw.as_deref()[0..].to_vec()) } else { None @@ -609,20 +636,24 @@ impl Storable for Node { } Self::LEAF_NODE => { let leaf_header_size = 1 + 4; - let node_raw = mem - .get_view(addr + META_SIZE, leaf_header_size) - .ok_or(ShaleError::LinearCachedStoreError)?; + let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: leaf_header_size, + }, + )?; let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 4]; - cur.read_exact(&mut buff[..1]) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff[..1])?; let path_len = buff[0] as u64; - cur.read_exact(&mut buff) - .map_err(|_| ShaleError::DecodeError)?; + cur.read_exact(&mut buff)?; let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) - .ok_or(ShaleError::LinearCachedStoreError)?; + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + leaf_header_size, + size: path_len + data_len, + })?; let nibbles: Vec<_> = to_nibbles(&remainder.as_deref()[..path_len as usize]).collect(); let (path, _) = PartialPath::decode(nibbles); @@ -633,7 +664,7 @@ impl Storable for Node { NodeType::Leaf(LeafNode(path, value)), )) } - _ => Err(ShaleError::DecodeError), + _ => Err(ShaleError::InvalidNodeType), } } @@ -669,17 +700,17 @@ impl Storable for Node { } } - fn dehydrate(&self, to: &mut [u8]) { + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); let mut attrs = 0; attrs |= match self.root_hash.get() { Some(h) => { - cur.write_all(&h.0).unwrap(); + cur.write_all(&h.0)?; Node::ROOT_HASH_VALID_BIT } None => { - cur.write_all(&[0; 32]).unwrap(); + cur.write_all(&[0; 32])?; 0 } }; @@ -696,16 +727,15 @@ impl Storable for Node { cur.write_all(&match c { Some(p) => p.addr().to_le_bytes(), None => 0u64.to_le_bytes(), - }) - .unwrap(); + })?; } match &n.value { Some(val) => { - cur.write_all(&(val.len() as u32).to_le_bytes()).unwrap(); - cur.write_all(val).unwrap(); + cur.write_all(&(val.len() as u32).to_le_bytes())?; + cur.write_all(val)? } None => { - cur.write_all(&u32::MAX.to_le_bytes()).unwrap(); + cur.write_all(&u32::MAX.to_le_bytes())?; } } // Since child eth rlp will only be unset after initialization (only used for range proof), @@ -713,34 +743,34 @@ impl Storable for Node { for rlp in n.chd_eth_rlp.iter() { match rlp { Some(v) => { - cur.write_all(&[v.len() as u8]).unwrap(); - cur.write_all(v).unwrap(); - } - None => { - cur.write_all(&0u8.to_le_bytes()).unwrap(); + cur.write_all(&[v.len() as u8])?; + cur.write_all(v)? } + None => cur.write_all(&0u8.to_le_bytes())?, } } + Ok(()) } NodeType::Extension(n) => { - cur.write_all(&[Self::EXT_NODE]).unwrap(); + cur.write_all(&[Self::EXT_NODE])?; let path: Vec = from_nibbles(&n.0.encode(false)).collect(); - cur.write_all(&[path.len() as u8]).unwrap(); - cur.write_all(&n.1.addr().to_le_bytes()).unwrap(); - cur.write_all(&path).unwrap(); + cur.write_all(&[path.len() as u8])?; + cur.write_all(&n.1.addr().to_le_bytes())?; + cur.write_all(&path)?; if n.2.is_some() { let rlp = n.2.as_ref().unwrap(); - cur.write_all(&[rlp.len() as u8]).unwrap(); - cur.write_all(rlp).unwrap(); + cur.write_all(&[rlp.len() as u8])?; + cur.write_all(rlp)?; } + Ok(()) } NodeType::Leaf(n) => { - cur.write_all(&[Self::LEAF_NODE]).unwrap(); + cur.write_all(&[Self::LEAF_NODE])?; let path: Vec = from_nibbles(&n.0.encode(true)).collect(); - cur.write_all(&[path.len() as u8]).unwrap(); - cur.write_all(&(n.1.len() as u32).to_le_bytes()).unwrap(); - cur.write_all(&path).unwrap(); - cur.write_all(&n.1).unwrap(); + cur.write_all(&[path.len() as u8])?; + cur.write_all(&(n.1.len() as u32).to_le_bytes())?; + cur.write_all(&path)?; + cur.write_all(&n.1).map_err(ShaleError::Io) } } } @@ -2036,7 +2066,7 @@ mod test { let mut to = [1u8; HASH_SIZE]; assert_eq!( { - ZERO_HASH.dehydrate(&mut to); + ZERO_HASH.dehydrate(&mut to).unwrap(); &to }, ZERO_HASH.deref() @@ -2073,7 +2103,7 @@ mod test { let check = |node: Node| { let mut bytes = Vec::new(); bytes.resize(node.dehydrated_len() as usize, 0); - node.dehydrate(&mut bytes); + node.dehydrate(&mut bytes).unwrap(); let mut mem = PlainMem::new(bytes.len() as u64, 0x0); mem.write(0, &bytes); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 5b4ab9574e10..61013ca50176 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -121,7 +121,8 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { let compact_header: ObjPtr = ObjPtr::new_from_addr(0x0); dm.write( compact_header.addr(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)) + .unwrap(), ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 558dd6dc50c5..f94e2073980a 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -12,6 +12,7 @@ use nix::errno::Errno; use serde::{Deserialize, Serialize}; use sha3::Digest; use shale::ObjPtr; +use shale::ShaleError; use std::cmp::Ordering; use std::collections::HashMap; @@ -44,6 +45,7 @@ pub enum ProofError { #[cfg(feature = "eth")] BlobStoreError(BlobError), SystemError(Errno), + Shale(ShaleError), InvalidRootHash, } @@ -71,6 +73,7 @@ impl From for ProofError { DBError::IO(e) => { ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) } + DBError::Shale(e) => ProofError::Shale(e), } } } @@ -98,6 +101,7 @@ impl fmt::Display for ProofError { ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), ProofError::SystemError(e) => write!(f, "system error: {e:?}"), ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), + ProofError::Shale(e) => write!(f, "shale error: {e:?}"), } } } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 4d5109b417b8..90694d4d494d 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -359,8 +359,8 @@ impl CachedStore for StoreRevShared { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { - Some(Box::new(StoreShared(self.clone()))) + fn get_shared(&self) -> Box> { + Box::new(StoreShared(self.clone())) } fn write(&mut self, _offset: u64, _change: &[u8]) { @@ -507,8 +507,8 @@ impl CachedStore for StoreRevMut { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Option>> { - Some(Box::new(StoreShared(self.clone()))) + fn get_shared(&self) -> Box> { + Box::new(StoreShared(self.clone())) } fn write(&mut self, offset: u64, mut change: &[u8]) { From 9a0d08efaff0506d54232c74b4879eabea935891 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 2 May 2023 08:36:54 -0700 Subject: [PATCH 0141/1053] Add richardpringle to codeowners (#72) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index c80f371ca4d1..8abad2cf0466 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @exdx @xinifinity @gyuho @hexfusion @rkuris @patrick-ogrady @StephenButtolph +* @exdx @xinifinity @gyuho @hexfusion @rkuris @patrick-ogrady @StephenButtolph @richardpringle From e54455919f23463f800ddd40e575e9806f49a4a1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 2 May 2023 09:55:34 -0700 Subject: [PATCH 0142/1053] Update versions to 0.0.3 for everything (#71) --- Cargo.toml | 1 + RELEASE.md | 6 + cargo-update-all-revs/Cargo.toml | 23 ++++ cargo-update-all-revs/src/main.rs | 182 ++++++++++++++++++++++++++++++ firewood-growth-ring/Cargo.toml | 4 +- firewood-libaio/Cargo.toml | 2 +- firewood-shale/Cargo.toml | 2 +- firewood/Cargo.toml | 8 +- fwdctl/Cargo.toml | 4 +- 9 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 cargo-update-all-revs/Cargo.toml create mode 100644 cargo-update-all-revs/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 0742fcfcbda3..5a71afe9bc9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "cargo-update-all-revs", "firewood-growth-ring", "firewood-libaio", "firewood-shale", diff --git a/RELEASE.md b/RELEASE.md index fb2d86e576ff..2d08fdb9bf62 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,6 +9,12 @@ its own crate and has an independent version. * firewood-libaio * firewood-shale +There is a utility to ensure all versions are updated simultaneously in +cargo-update-all-revs. To use it to update to 0.0.4, for example: + + cargo install --path cargo-update-all-revs + cargo update-all-revs 0.0.4 + To trigger a release, simply push a semver-compatible tag to the main branch, for example `v0.0.1`. The CI will automatically publish a draft release which consists of release notes and changes. Once this draft is approved, and the new diff --git a/cargo-update-all-revs/Cargo.toml b/cargo-update-all-revs/Cargo.toml new file mode 100644 index 000000000000..18fa8b922e5e --- /dev/null +++ b/cargo-update-all-revs/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cargo-update-all-revs" +version = "0.0.3" +edition = "2021" +description = "Cargo plugin to update all versions in a workspace" +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" +readme = "../README.md" +authors = [ + "Ted Yin (@Determinant) ", + "Dan Sover (@exdx) ", + "Hao Hao (@haohao-os) ", + "Gyuho Lee (@gyuho) ", + "Sam Batschelet (@hexfusion) ", + "Ron Kuris (@rkuris) ", +] + +[dependencies] +anyhow = "1.0.71" +clap = { version = "4.2.5", features = ["derive"] } +serde = { version = "1.0.160", features = ["derive"] } +toml = "0.7.3" +toml_edit = { version = "0.19.8", features = ["serde"] } diff --git a/cargo-update-all-revs/src/main.rs b/cargo-update-all-revs/src/main.rs new file mode 100644 index 000000000000..63a05111da92 --- /dev/null +++ b/cargo-update-all-revs/src/main.rs @@ -0,0 +1,182 @@ +#![warn(clippy::all)] +#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +//! Release tool to update all versions of everything +//! inside the crate at the same time to the same version +use std::{collections::HashSet, fs, path::PathBuf}; + +use anyhow::{anyhow, bail, Context, Error}; +use clap::Parser; +use toml_edit::{Document, Formatted, InlineTable, Item, KeyMut, Value}; + +#[derive(Debug, Parser)] +struct Args { + /// how cargo invoked this; cargo chews up the first argument + /// so this should be completely ignored + #[clap(hide(true))] + _cargo_invoked_as: String, + /// Don't generate log entries + #[arg(short, long)] + quiet: bool, + + /// Don't write to the output file + #[arg(short, long)] + dryrun: bool, + + /// Fail if there were any differences + #[arg(short, long)] + check: bool, + + /// What the new version is supposed to be + newver: String, +} + +fn main() -> Result<(), Error> { + let cli = Args::parse(); + + // first read the top level Cargo.cli + let base = std::fs::read_to_string("Cargo.toml")?; + let doc = base.parse::()?; + // get the [workspace] section + let workspace = doc + .get("workspace") + .ok_or(anyhow!("No [workspace] section in top level"))?; + // find the members array inside the workspace + let members = workspace + .get("members") + .ok_or(anyhow!("No members in [workspace] section"))? + .as_array() + .ok_or(anyhow!("members must be an array"))?; + + // save these members into a hashmap for easy lookup later. We will + // only change [dependencies] that point to one of these, and we need + // to check each one to see if it's one we care about + let members_lookup = members + .iter() + .map(|v| v.as_str().expect("member wasn't a string").to_string()) + .collect::>(); + + let mut some_difference_found = false; + + // work on each subdirectory (each member of the workspace) + for member in members { + // calculate the path of the inner member + let inner_path: PathBuf = [member.as_str().unwrap(), "Cargo.toml"].iter().collect(); + // and load into a parsed yaml document + let inner = std::fs::read_to_string(&inner_path) + .context(format!("Can't read {}", inner_path.display()))?; + let mut inner = inner.parse::()?; + + // now find the [package] section + let package = inner.get_mut("package").ok_or(anyhow!(format!( + "no [package] section in {}", + inner_path.display() + )))?; + // which contains: version = "xxx"; mutable since we might change it + let version = package.get_mut("version"); + + // keep track of if we changed anything, to avoid unnecessary rewrites + let mut changed = false; + + // extract the value; we want a better error here in case we can't find + // it or if the version couldn't be parsed as a string + match version { + None => { + // TODO: We could just set the version... + bail!(format!("No version in {}", inner_path.display())) + } + Some(Item::Value(v)) => { + changed |= check_version(v, inner_path.display().to_string(), &cli); + } + Some(_) => bail!(format!( + "version in {} wasn't a string", + inner_path.display() + )), + } + + // now work on the [dependencies] section. We only care about + // dependencies with names that are one of the subdirectories + // we found when we parsed the members section at the top level + // so we filter using the hashset created earlier + // dependencies consist of a table of "name = { inline_table }" + // entries. We skip those that don't have that format (the short + // form of "name = version" for example) + if let Some(deps) = inner.get_mut("dependencies") { + if let Some(deps) = deps.as_table_mut() { + // build an iterator of K,V pairs for each dependency + // and do the filtering here for items in the members_lookup + for dep in deps + .iter_mut() + .filter(|dep| members_lookup.contains(dep.0.get())) + { + // call fixup_version for this dependency, which + // might make a change if the version was wrong + if let Some(inline_table) = dep.1.as_inline_table_mut() { + changed |= update_dep_ver(&dep.0, inline_table, &cli); + } + } + }; + } + if changed { + if !cli.quiet { + println!("{} was changed", inner_path.display()); + } + if !cli.dryrun { + fs::write(inner_path, inner.to_string())?; + } + } + some_difference_found |= changed; + } + if cli.check && some_difference_found { + bail!("There were differences") + } else { + if cli.check { + println!("All files had the correct version"); + } + Ok(()) + } +} + +/// Verify and/or update the version of a dependency +/// +/// Given a dependency and the table of attributes, check the +/// "version" attribute and make sure it matches what we expect +/// from the command line arguments +/// +/// * `key` - the name of this dependency +/// * `dep` - the table of K/V pairs describing the dependency +/// * `opts` - the command line arguments passed in +/// +/// Returns true if any changes were made +fn update_dep_ver(key: &KeyMut<'_>, dep: &mut InlineTable, opts: &Args) -> bool { + let v = dep.get_mut("version").unwrap(); + check_version(v, format!("dependency for {}", key.get()), opts) +} + +/// Check and/or set the version +/// +/// Check the version value provided and optionally +/// log and/or update it, based on the command line args +/// +/// Arguments: +/// +/// * `v` - the version to verify/change +/// * `source` - the text of where this version came from +/// * `opts` - the command line arguments +/// +/// Returns `true` if a change was made, `false` otherwise +fn check_version>(v: &mut Value, source: S, opts: &Args) -> bool { + if let Some(old) = v.as_str() { + if old != opts.newver { + if !opts.quiet { + println!( + "Version for {} was {old} want {} (fixed)", + source.as_ref(), + opts.newver + ); + } + *v = Value::String(Formatted::new(opts.newver.clone())); + return true; + } + } + false +} diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index eeeeb40b06c6..6202a2672bf4 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-growth-ring" -version = "0.0.2" +version = "0.0.3" edition = "2021" keywords = ["wal", "db", "futures"] license = "MIT" @@ -9,7 +9,7 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -firewood-libaio= { version = "0.0.2", path = "../firewood-libaio", package = "firewood-libaio" } +firewood-libaio= { version = "0.0.3", path = "../firewood-libaio", package = "firewood-libaio" } crc = "3.0.0" lru = "0.10.0" scan_fmt = "0.2.6" diff --git a/firewood-libaio/Cargo.toml b/firewood-libaio/Cargo.toml index 1c119eae1a03..5532cd3726e2 100644 --- a/firewood-libaio/Cargo.toml +++ b/firewood-libaio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-libaio" -version = "0.0.2" +version = "0.0.3" edition = "2021" keywords = ["libaio", "aio", "async", "futures"] license = "MIT" diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 1e7efd9ade27..2fe3af6a882a 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-shale" -version = "0.0.2" +version = "0.0.3" edition = "2021" description = "Useful abstraction and light-weight implemenation for a key-value store." license = "MIT" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 2a34e183b475..71877816b6c1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.2" +version = "0.0.3" edition = "2021" authors = [ "Ted Yin (@Determinant) ", @@ -20,9 +20,9 @@ aquamarine = "0.3.1" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.5.1" -firewood-growth-ring = { version = "0.0.2", path = "../firewood-growth-ring" } -firewood-libaio = {version = "0.0.2", path = "../firewood-libaio" } -firewood-shale = { version = "0.0.2", path = "../firewood-shale" } +firewood-growth-ring = { version = "0.0.3", path = "../firewood-growth-ring" } +firewood-libaio = {version = "0.0.3", path = "../firewood-libaio" } +firewood-shale = { version = "0.0.3", path = "../firewood-shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.10.0" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index fa5254add555..c57f8c130f6d 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "fwdctl" -version = "0.0.2" +version = "0.0.3" edition = "2021" [dependencies] -firewood = { version = "0.0.2", path = "../firewood" } +firewood = { version = "0.0.3", path = "../firewood" } clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" From de69ff5086cd9bdafa4f4c803140b27581614f05 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 2 May 2023 12:12:41 -0700 Subject: [PATCH 0143/1053] chore: refactor `rev.rs` (#74) --- firewood/examples/rev.rs | 190 +++++++++++++++++++++--------------- firewood/src/db.rs | 74 ++++++-------- firewood/src/merkle.rs | 8 +- firewood/src/storage/mod.rs | 5 - 4 files changed, 146 insertions(+), 131 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 1e8a0c80b6a4..bf5b29cfbc22 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -1,115 +1,143 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::{ + db::{DBConfig, Revision, WALConfig, DB}, + proof::Proof, +}; /// cargo run --example rev fn main() { let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); { - let db = DB::new("rev_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = DB::new("rev_db", &cfg.clone().truncate(true).build()) + .expect("db initiation should succeed"); let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; for (k, v) in items.iter() { db.new_writebatch() .kv_insert(k, v.as_bytes().to_vec()) .unwrap() .commit(); - println!("{}", hex::encode(*db.kv_root_hash().unwrap())); + + let root_hash = db + .kv_root_hash() + .expect("root-hash for current state should exist"); + println!("{root_hash:?}"); } db.kv_dump(&mut std::io::stdout()).unwrap(); - println!( - "{}", - hex::encode(*db.get_revision(0, None).unwrap().kv_root_hash().unwrap()) - ); - // The latest committed revision matches with the current state without dirty writes. - assert_eq!( - db.kv_root_hash().unwrap(), - db.get_revision(0, None).unwrap().kv_root_hash().unwrap() - ); - println!( - "{}", - hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) - ); - let root_hash = *db.get_revision(1, None).unwrap().kv_root_hash().unwrap(); - println!( - "{}", - hex::encode(*db.get_revision(2, None).unwrap().kv_root_hash().unwrap()) - ); - let write = db.new_writebatch().kv_insert("k", vec![b'v']).unwrap(); + let revision = db.get_revision(0, None).expect("revision-0 should exist"); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-0 should exist"); + println!("{revision_root_hash:?}"); + + let current_root_hash = db + .kv_root_hash() + .expect("root-hash for current state should exist"); + // The following is true as long as there are no dirty-writes. + assert_eq!(revision_root_hash, current_root_hash); + + let revision = db.get_revision(2, None).expect("revision-2 should exist"); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-2 should exist"); + println!("{revision_root_hash:?}"); // Get a revision while a batch is active. - println!( - "{}", - hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) - ); - assert_eq!( - root_hash, - *db.get_revision(1, None).unwrap().kv_root_hash().unwrap() - ); + let revision_root_hash = db + .get_revision(1, None) + .expect("revision-1 should exist") + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + println!("{revision_root_hash:?}"); + + let write = db.new_writebatch().kv_insert("k", vec![b'v']).unwrap(); + + let actual_revision_root_hash = db + .get_revision(1, None) + .expect("revision-1 should exist") + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + assert_eq!(revision_root_hash, actual_revision_root_hash); // Read the uncommitted value while the batch is still active. let val = db.kv_get("k").unwrap(); assert_eq!("v".as_bytes().to_vec(), val); write.commit(); - println!( - "{}", - hex::encode(*db.get_revision(1, None).unwrap().kv_root_hash().unwrap()) - ); + let new_revision_root_hash = db + .get_revision(1, None) + .expect("revision-1 should exist") + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + assert_ne!(revision_root_hash, new_revision_root_hash); + let val = db.kv_get("k").unwrap(); assert_eq!("v".as_bytes().to_vec(), val); } { - let db = DB::new("rev_db", &cfg.truncate(false).build()).unwrap(); + let db = + DB::new("rev_db", &cfg.truncate(false).build()).expect("db initiation should succeed"); { - // The latest committed revision matches with the current state after replaying from WALs. - assert_eq!( - db.kv_root_hash().unwrap(), - db.get_revision(0, None).unwrap().kv_root_hash().unwrap() - ); - - let rev = db.get_revision(1, None).unwrap(); - println!("{}", hex::encode(*rev.kv_root_hash().unwrap())); - rev.kv_dump(&mut std::io::stdout()).unwrap(); - - let mut items_rev_1 = vec![("dof", "verb"), ("doe", "reindeer")]; - items_rev_1.sort(); - let (keys, vals) = items_rev_1.clone().into_iter().unzip(); - - let mut proof = rev.prove(items_rev_1[0].0).unwrap(); - let end_proof = rev.prove(items_rev_1[items_rev_1.len() - 1].0).unwrap(); - proof.concat_proofs(end_proof); - - rev.verify_range_proof( - proof, - items_rev_1[0].0, - items_rev_1[items_rev_1.len() - 1].0, - keys, - vals, - ) - .unwrap(); + let revision = db.get_revision(0, None).expect("revision-0 should exist"); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-0 should exist"); + println!("{revision_root_hash:?}"); + + let current_root_hash = db + .kv_root_hash() + .expect("root-hash for current state should exist"); + // The following is true as long as the current state is fresh after replaying from WALs. + assert_eq!(revision_root_hash, current_root_hash); + + let revision = db.get_revision(1, None).expect("revision-1 should exist"); + revision.kv_dump(&mut std::io::stdout()).unwrap(); + + let mut items_rev = vec![("dof", "verb"), ("doe", "reindeer")]; + items_rev.sort(); + let (keys, vals) = items_rev.clone().into_iter().unzip(); + + let proof = build_proof(&revision, &items_rev); + revision + .verify_range_proof( + proof, + items_rev[0].0, + items_rev[items_rev.len() - 1].0, + keys, + vals, + ) + .unwrap(); } { - let rev = db.get_revision(2, None).unwrap(); - print!("{}", hex::encode(*rev.kv_root_hash().unwrap())); - rev.kv_dump(&mut std::io::stdout()).unwrap(); - - let mut items_rev_2 = vec![("dof", "verb")]; - items_rev_2.sort(); - let (keys, vals) = items_rev_2.clone().into_iter().unzip(); - - let mut proof = rev.prove(items_rev_2[0].0).unwrap(); - let end_proof = rev.prove(items_rev_2[items_rev_2.len() - 1].0).unwrap(); - proof.concat_proofs(end_proof); - - rev.verify_range_proof( - proof, - items_rev_2[0].0, - items_rev_2[items_rev_2.len() - 1].0, - keys, - vals, - ) - .unwrap(); + let revision = db.get_revision(2, None).expect("revision-2 should exist"); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-2 should exist"); + println!("{revision_root_hash:?}"); + revision.kv_dump(&mut std::io::stdout()).unwrap(); + + let mut items_rev = vec![("dof", "verb")]; + items_rev.sort(); + let (keys, vals) = items_rev.clone().into_iter().unzip(); + + let proof = build_proof(&revision, &items_rev); + revision + .verify_range_proof( + proof, + items_rev[0].0, + items_rev[items_rev.len() - 1].0, + keys, + vals, + ) + .unwrap(); } } } + +fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof { + let mut proof = revision.prove(items[0].0).unwrap(); + let end = revision.prove(items.last().unwrap().0).unwrap(); + proof.concat_proofs(end); + proof +} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 0aa3378d2c78..717bde4503e7 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -196,6 +196,23 @@ impl SubUniverse> { } } +fn get_sub_universe_from_deltas( + sub_universe: &SubUniverse>, + meta_delta: StoreDelta, + payload_delta: StoreDelta, +) -> SubUniverse { + SubUniverse::new( + StoreRevShared::from_delta(sub_universe.meta.clone(), meta_delta), + StoreRevShared::from_delta(sub_universe.payload.clone(), payload_delta), + ) +} + +fn get_sub_universe_from_empty_delta( + sub_universe: &SubUniverse>, +) -> SubUniverse { + get_sub_universe_from_deltas(sub_universe, StoreDelta::default(), StoreDelta::default()) +} + /// DB-wide metadata, it keeps track of the roots of the top-level tries. struct DBHeader { /// The root node of the account model storage. (Where the values are [Account] objects, which @@ -710,14 +727,8 @@ impl DB { latest.flush_dirty().unwrap(); let base = Universe { - merkle: SubUniverse::new( - StoreRevShared::from_delta(cached.merkle.meta.clone(), StoreDelta::new_empty()), - StoreRevShared::from_delta(cached.merkle.payload.clone(), StoreDelta::new_empty()), - ), - blob: SubUniverse::new( - StoreRevShared::from_delta(cached.blob.meta.clone(), StoreDelta::new_empty()), - StoreRevShared::from_delta(cached.blob.payload.clone(), StoreDelta::new_empty()), - ), + merkle: get_sub_universe_from_empty_delta(&cached.merkle), + blob: get_sub_universe_from_empty_delta(&cached.blob), }; Ok(Self { @@ -769,7 +780,7 @@ impl DB { /// /// The latest revision (nback) starts from 0, which is the current state. /// If nback equals is above the configured maximum number of revisions, this function returns None. - /// It also returns None in the case where the nback is larger than the number of revisions available. + /// Returns `None` if `nback` is greater than the configured maximum amount of revisions. pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { let mut revisions = self.revisions.lock(); let inner = self.inner.read(); @@ -1012,22 +1023,15 @@ impl WriteBatch { // update the rolling window of past revisions let new_base = Universe { - merkle: SubUniverse::new( - StoreRevShared::from_delta( - rev_inner.cached.merkle.meta.clone(), - old_merkle_meta_delta, - ), - StoreRevShared::from_delta( - rev_inner.cached.merkle.payload.clone(), - old_merkle_payload_delta, - ), + merkle: get_sub_universe_from_deltas( + &rev_inner.cached.merkle, + old_merkle_meta_delta, + old_merkle_payload_delta, ), - blob: SubUniverse::new( - StoreRevShared::from_delta(rev_inner.cached.blob.meta.clone(), old_blob_meta_delta), - StoreRevShared::from_delta( - rev_inner.cached.blob.payload.clone(), - old_blob_payload_delta, - ), + blob: get_sub_universe_from_deltas( + &rev_inner.cached.blob, + old_blob_meta_delta, + old_blob_payload_delta, ), }; @@ -1050,26 +1054,8 @@ impl WriteBatch { } let base = Universe { - merkle: SubUniverse::new( - StoreRevShared::from_delta( - rev_inner.cached.merkle.meta.clone(), - StoreDelta::new_empty(), - ), - StoreRevShared::from_delta( - rev_inner.cached.merkle.payload.clone(), - StoreDelta::new_empty(), - ), - ), - blob: SubUniverse::new( - StoreRevShared::from_delta( - rev_inner.cached.blob.meta.clone(), - StoreDelta::new_empty(), - ), - StoreRevShared::from_delta( - rev_inner.cached.blob.payload.clone(), - StoreDelta::new_empty(), - ), - ), + merkle: get_sub_universe_from_empty_delta(&rev_inner.cached.merkle), + blob: get_sub_universe_from_empty_delta(&rev_inner.cached.blob), }; revisions.base = base; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index ad8847fda63a..68a2b7b297ea 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -43,7 +43,7 @@ impl fmt::Display for MerkleError { impl Error for MerkleError {} -#[derive(PartialEq, Eq, Debug, Clone)] +#[derive(PartialEq, Eq, Clone)] pub struct Hash(pub [u8; HASH_SIZE]); impl Hash { @@ -79,6 +79,12 @@ impl Storable for Hash { } } +impl Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", hex::encode(self.0)) + } +} + /// PartialPath keeps a list of nibbles to represent a path on the Trie. #[derive(PartialEq, Eq, Clone)] pub struct PartialPath(Vec); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 90694d4d494d..90b16879d59d 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -170,11 +170,6 @@ impl Deref for StoreDelta { } impl StoreDelta { - pub fn new_empty() -> Self { - let deltas = Vec::new(); - Self(deltas) - } - pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { let mut deltas = Vec::new(); let mut widx: Vec<_> = (0..writes.len()) From 60ac2c7b1d5b626de89cb89d28c422b64189182f Mon Sep 17 00:00:00 2001 From: exdx Date: Wed, 3 May 2023 12:33:07 -0400 Subject: [PATCH 0144/1053] fix: Update release to cargo-workspace-version (#75) --- Cargo.toml | 1 - RELEASE.md | 26 +++-- cargo-update-all-revs/Cargo.toml | 23 ---- cargo-update-all-revs/src/main.rs | 182 ------------------------------ 4 files changed, 17 insertions(+), 215 deletions(-) delete mode 100644 cargo-update-all-revs/Cargo.toml delete mode 100644 cargo-update-all-revs/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 5a71afe9bc9c..0742fcfcbda3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "cargo-update-all-revs", "firewood-growth-ring", "firewood-libaio", "firewood-shale", diff --git a/RELEASE.md b/RELEASE.md index 2d08fdb9bf62..2356a7199540 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,18 +9,26 @@ its own crate and has an independent version. * firewood-libaio * firewood-shale -There is a utility to ensure all versions are updated simultaneously in -cargo-update-all-revs. To use it to update to 0.0.4, for example: +The first step in drafting a release is ensuring all crates within the firewood +project are using the version of the new release. There is a utility to ensure +all versions are updated simultaneously in `cargo-workspace-version`. To use it +to update to 0.0.4, for example: - cargo install --path cargo-update-all-revs - cargo update-all-revs 0.0.4 + $ cargo install cargo-workspace-version $ cargo workspace-version update +v0.0.4 + +See the [source code](https://github.com/ava-labs/cargo-workspace-version) for +more information on the tool. + +> ❗ Be sure to update the versions of all sub-projects before creating a new +> release. Open a PR with the updated versions and merge it before continuing to +> the next step. To trigger a release, simply push a semver-compatible tag to the main branch, for example `v0.0.1`. The CI will automatically publish a draft release which consists of release notes and changes. Once this draft is approved, and the new release exists, CI will then go and publish each of the sub-project crates to -crate.io -> Note: Only crates that had a version bump will be updated on crates.io. If a -> crate was changed, but the version was not bumped, it will not be updated on -> crates.io. Deploying a crate with the same version as previously will be a -> no-op. +crates.io. +> ❗ Publishing a crate with the same version as the existing version will +> result in a cargo error. Be sure to update all crates within the workspace +> before publishing to crates.io. diff --git a/cargo-update-all-revs/Cargo.toml b/cargo-update-all-revs/Cargo.toml deleted file mode 100644 index 18fa8b922e5e..000000000000 --- a/cargo-update-all-revs/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "cargo-update-all-revs" -version = "0.0.3" -edition = "2021" -description = "Cargo plugin to update all versions in a workspace" -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -readme = "../README.md" -authors = [ - "Ted Yin (@Determinant) ", - "Dan Sover (@exdx) ", - "Hao Hao (@haohao-os) ", - "Gyuho Lee (@gyuho) ", - "Sam Batschelet (@hexfusion) ", - "Ron Kuris (@rkuris) ", -] - -[dependencies] -anyhow = "1.0.71" -clap = { version = "4.2.5", features = ["derive"] } -serde = { version = "1.0.160", features = ["derive"] } -toml = "0.7.3" -toml_edit = { version = "0.19.8", features = ["serde"] } diff --git a/cargo-update-all-revs/src/main.rs b/cargo-update-all-revs/src/main.rs deleted file mode 100644 index 63a05111da92..000000000000 --- a/cargo-update-all-revs/src/main.rs +++ /dev/null @@ -1,182 +0,0 @@ -#![warn(clippy::all)] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] -//! Release tool to update all versions of everything -//! inside the crate at the same time to the same version -use std::{collections::HashSet, fs, path::PathBuf}; - -use anyhow::{anyhow, bail, Context, Error}; -use clap::Parser; -use toml_edit::{Document, Formatted, InlineTable, Item, KeyMut, Value}; - -#[derive(Debug, Parser)] -struct Args { - /// how cargo invoked this; cargo chews up the first argument - /// so this should be completely ignored - #[clap(hide(true))] - _cargo_invoked_as: String, - /// Don't generate log entries - #[arg(short, long)] - quiet: bool, - - /// Don't write to the output file - #[arg(short, long)] - dryrun: bool, - - /// Fail if there were any differences - #[arg(short, long)] - check: bool, - - /// What the new version is supposed to be - newver: String, -} - -fn main() -> Result<(), Error> { - let cli = Args::parse(); - - // first read the top level Cargo.cli - let base = std::fs::read_to_string("Cargo.toml")?; - let doc = base.parse::()?; - // get the [workspace] section - let workspace = doc - .get("workspace") - .ok_or(anyhow!("No [workspace] section in top level"))?; - // find the members array inside the workspace - let members = workspace - .get("members") - .ok_or(anyhow!("No members in [workspace] section"))? - .as_array() - .ok_or(anyhow!("members must be an array"))?; - - // save these members into a hashmap for easy lookup later. We will - // only change [dependencies] that point to one of these, and we need - // to check each one to see if it's one we care about - let members_lookup = members - .iter() - .map(|v| v.as_str().expect("member wasn't a string").to_string()) - .collect::>(); - - let mut some_difference_found = false; - - // work on each subdirectory (each member of the workspace) - for member in members { - // calculate the path of the inner member - let inner_path: PathBuf = [member.as_str().unwrap(), "Cargo.toml"].iter().collect(); - // and load into a parsed yaml document - let inner = std::fs::read_to_string(&inner_path) - .context(format!("Can't read {}", inner_path.display()))?; - let mut inner = inner.parse::()?; - - // now find the [package] section - let package = inner.get_mut("package").ok_or(anyhow!(format!( - "no [package] section in {}", - inner_path.display() - )))?; - // which contains: version = "xxx"; mutable since we might change it - let version = package.get_mut("version"); - - // keep track of if we changed anything, to avoid unnecessary rewrites - let mut changed = false; - - // extract the value; we want a better error here in case we can't find - // it or if the version couldn't be parsed as a string - match version { - None => { - // TODO: We could just set the version... - bail!(format!("No version in {}", inner_path.display())) - } - Some(Item::Value(v)) => { - changed |= check_version(v, inner_path.display().to_string(), &cli); - } - Some(_) => bail!(format!( - "version in {} wasn't a string", - inner_path.display() - )), - } - - // now work on the [dependencies] section. We only care about - // dependencies with names that are one of the subdirectories - // we found when we parsed the members section at the top level - // so we filter using the hashset created earlier - // dependencies consist of a table of "name = { inline_table }" - // entries. We skip those that don't have that format (the short - // form of "name = version" for example) - if let Some(deps) = inner.get_mut("dependencies") { - if let Some(deps) = deps.as_table_mut() { - // build an iterator of K,V pairs for each dependency - // and do the filtering here for items in the members_lookup - for dep in deps - .iter_mut() - .filter(|dep| members_lookup.contains(dep.0.get())) - { - // call fixup_version for this dependency, which - // might make a change if the version was wrong - if let Some(inline_table) = dep.1.as_inline_table_mut() { - changed |= update_dep_ver(&dep.0, inline_table, &cli); - } - } - }; - } - if changed { - if !cli.quiet { - println!("{} was changed", inner_path.display()); - } - if !cli.dryrun { - fs::write(inner_path, inner.to_string())?; - } - } - some_difference_found |= changed; - } - if cli.check && some_difference_found { - bail!("There were differences") - } else { - if cli.check { - println!("All files had the correct version"); - } - Ok(()) - } -} - -/// Verify and/or update the version of a dependency -/// -/// Given a dependency and the table of attributes, check the -/// "version" attribute and make sure it matches what we expect -/// from the command line arguments -/// -/// * `key` - the name of this dependency -/// * `dep` - the table of K/V pairs describing the dependency -/// * `opts` - the command line arguments passed in -/// -/// Returns true if any changes were made -fn update_dep_ver(key: &KeyMut<'_>, dep: &mut InlineTable, opts: &Args) -> bool { - let v = dep.get_mut("version").unwrap(); - check_version(v, format!("dependency for {}", key.get()), opts) -} - -/// Check and/or set the version -/// -/// Check the version value provided and optionally -/// log and/or update it, based on the command line args -/// -/// Arguments: -/// -/// * `v` - the version to verify/change -/// * `source` - the text of where this version came from -/// * `opts` - the command line arguments -/// -/// Returns `true` if a change was made, `false` otherwise -fn check_version>(v: &mut Value, source: S, opts: &Args) -> bool { - if let Some(old) = v.as_str() { - if old != opts.newver { - if !opts.quiet { - println!( - "Version for {} was {old} want {} (fixed)", - source.as_ref(), - opts.newver - ); - } - *v = Value::String(Formatted::new(opts.newver.clone())); - return true; - } - } - false -} From 306014e7f97be0989a9229afd8ac6b47b05fb771 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 4 May 2023 12:06:21 -0700 Subject: [PATCH 0145/1053] Add some terminology to the README.md (#77) --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 065a58644871..8b98d930ace8 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,40 @@ These crates will either be heavily modified or removed prior to the production launch of firewood. If they are retained, all changes made will be shared upstream. +## Termimology + +* `Revision` - A point-in-time state/version of the trie. This represents the entire + trie, including all `Key`/`Value`s at that point in time, and all `Node`s. +* `View` - This is a synonym for a `Revision`. +* `Node` - A node is a portion of a trie. A trie consists of nodes that are linked + together. Nodes can point to other nodes and/or contain `Key`/`Value` pairs. +* `Hash` - In this context, this refers to the merkle hash for a specific node. +* `Root Hash` - The hash of the root node for a specific revision. +* `Key` - Represents an individual byte array used to index into a trie. A `Key` + usually has a specific `Value`. +* `Value` - Represents a byte array for the value of a specific `Key`. Values can + contain 0-N bytes. In particular, a zero-length `Value` is valid. +* `Key Proof` - A proof that a `Key` exists within a specific revision of a trie. + This includes the hash for the node containing the `Key` as well as all parents. +* `Range Proof` - A proof that consists of two `Key Proof`s, one for the start of + the range, and one for the end of the range, as well as a list of all `Key`/`Value` + pairs in between the two. A `Range Proof` can be validated independently of an + actual database by constructing a trie from the `Key`/`Value`s provided. +* `Change Proof` - A proof that consists of a set of all changes between two + revisions. +* `Put` - An operation for a `Key`/`Value` pair. A put means "create if it doesn't + exist, or update it if it does. A put operation is how you add a `Value` for a + specific `Key`. +* `Delete` - A operation indicating that a `Key` that should be removed from the trie. +* `Batch Operation` - An operation of either `Put` or `Delete`. +* `Batch` - An ordered set of `Batch Operation`s. +* `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not + yet committed to the trie. In firewood's most recent API, a `Proposal` is required + to `Commit`. +* `Commit` - The operation of applying one or more `Proposal`s to the most recent + `Revision`. + + ## Roadmap ### Green Milestone This milestone will focus on additional code cleanup, including supporting From a83a96a37383edc45f4d9cfa9840414314663a2a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 17 May 2023 11:29:40 -0400 Subject: [PATCH 0146/1053] Rename types to use proper PascalCase (#79) --- firewood-growth-ring/examples/demo1.rs | 18 +- firewood-growth-ring/src/lib.rs | 76 ++--- firewood-growth-ring/src/wal.rs | 338 +++++++++++------------ firewood-growth-ring/src/walerror.rs | 18 +- firewood-growth-ring/tests/common/mod.rs | 120 ++++---- firewood-growth-ring/tests/rand_fail.rs | 4 +- firewood-libaio/src/abi.rs | 48 ++-- firewood-libaio/src/lib.rs | 192 ++++++------- firewood-libaio/tests/simple_test.rs | 6 +- firewood/examples/benchmark.rs | 6 +- firewood/examples/dump.rs | 12 +- firewood/examples/rev.rs | 10 +- firewood/examples/simple.rs | 12 +- firewood/src/account.rs | 4 +- firewood/src/api.rs | 42 +-- firewood/src/db.rs | 282 +++++++++---------- firewood/src/proof.rs | 22 +- firewood/src/sender.rs | 28 +- firewood/src/service/client.rs | 70 ++--- firewood/src/service/mod.rs | 24 +- firewood/src/service/server.rs | 12 +- firewood/src/storage/buffer.rs | 72 ++--- firewood/src/storage/mod.rs | 8 +- firewood/tests/db.rs | 16 +- fwdctl/src/create.rs | 14 +- fwdctl/src/delete.rs | 8 +- fwdctl/src/dump.rs | 8 +- fwdctl/src/get.rs | 10 +- fwdctl/src/insert.rs | 8 +- fwdctl/src/root.rs | 8 +- fwdctl/tests/cli.rs | 4 +- 31 files changed, 749 insertions(+), 751 deletions(-) diff --git a/firewood-growth-ring/examples/demo1.rs b/firewood-growth-ring/examples/demo1.rs index 9f9d0d8b0774..c9aa184984c8 100644 --- a/firewood-growth-ring/examples/demo1.rs +++ b/firewood-growth-ring/examples/demo1.rs @@ -1,12 +1,12 @@ use futures::executor::block_on; use growthring::{ - wal::{WALBytes, WALLoader, WALRingId, WALWriter}, - walerror::WALError, - WALStoreAIO, + wal::{WalBytes, WalLoader, WalRingId, WalWriter}, + walerror::WalError, + WalStoreAio, }; use rand::{seq::SliceRandom, Rng, SeedableRng}; -fn test(records: Vec, wal: &mut WALWriter) -> Vec { +fn test(records: Vec, wal: &mut WalWriter) -> Vec { let mut res = Vec::new(); for r in wal.grow(records).into_iter() { let ring_id = futures::executor::block_on(r).unwrap().1; @@ -16,7 +16,7 @@ fn test(records: Vec, wal: &mut WALWriter) -> Vec Result<(), WALError> { +fn recover(payload: WalBytes, ringid: WalRingId) -> Result<(), WalError> { println!( "recover(payload={}, ringid={:?}", std::str::from_utf8(&payload).unwrap(), @@ -28,10 +28,10 @@ fn recover(payload: WALBytes, ringid: WALRingId) -> Result<(), WALError> { fn main() { let wal_dir = "./wal_demo1"; let mut rng = rand::rngs::StdRng::seed_from_u64(0); - let mut loader = WALLoader::new(); + let mut loader = WalLoader::new(); loader.file_nbit(9).block_nbit(8); - let store = WALStoreAIO::new(wal_dir, true, None).unwrap(); + let store = WalStoreAio::new(wal_dir, true, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -49,7 +49,7 @@ fn main() { ); } - let store = WALStoreAIO::new(wal_dir, false, None).unwrap(); + let store = WalStoreAio::new(wal_dir, false, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -63,7 +63,7 @@ fn main() { ); } - let store = WALStoreAIO::new(wal_dir, false, None).unwrap(); + let store = WalStoreAio::new(wal_dir, false, None).unwrap(); let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); let mut history = std::collections::VecDeque::new(); for _ in 0..3 { diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index d8ab2be22e76..9a7dbd0ed352 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -3,14 +3,14 @@ //! # Examples //! //! ``` -//! use growthring::{WALStoreAIO, wal::WALLoader}; +//! use growthring::{WalStoreAio, wal::WalLoader}; //! use futures::executor::block_on; -//! let mut loader = WALLoader::new(); +//! let mut loader = WalLoader::new(); //! loader.file_nbit(9).block_nbit(8); //! //! //! // Start with empty WAL (truncate = true). -//! let store = WALStoreAIO::new("./walfiles", true, None).unwrap(); +//! let store = WalStoreAio::new("./walfiles", true, None).unwrap(); //! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); //! // Write a vector of records to WAL. //! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { @@ -20,7 +20,7 @@ //! //! //! // Load from WAL (truncate = false). -//! let store = WALStoreAIO::new("./walfiles", false, None).unwrap(); +//! let store = WalStoreAio::new("./walfiles", false, None).unwrap(); //! let mut wal = block_on(loader.load(store, |payload, ringid| { //! // redo the operations in your application //! println!("recover(payload={}, ringid={:?})", @@ -32,12 +32,12 @@ //! // Let's try to grow the WAL to create many files. //! let ring_ids = wal.grow((1..100).into_iter().map(|i| "a".repeat(i)).collect::>()) //! .into_iter().map(|f| block_on(f).unwrap().1).collect::>(); -//! // Then assume all these records are not longer needed. We can tell WALWriter by the `peel` +//! // Then assume all these records are not longer needed. We can tell WalWriter by the `peel` //! // method. //! block_on(wal.peel(ring_ids, 0)).unwrap(); //! // There will only be one remaining file in ./walfiles. //! -//! let store = WALStoreAIO::new("./walfiles", false, None).unwrap(); +//! let store = WalStoreAio::new("./walfiles", false, None).unwrap(); //! let wal = block_on(loader.load(store, |payload, _| { //! println!("payload.len() = {}", payload.len()); //! Ok(()) @@ -51,7 +51,7 @@ pub mod wal; pub mod walerror; use async_trait::async_trait; -use firewood_libaio::{AIOBuilder, AIOManager}; +use firewood_libaio::{AioBuilder, AioManager}; use libc::off_t; #[cfg(target_os = "linux")] use nix::fcntl::{fallocate, FallocateFlags, OFlag}; @@ -64,16 +64,16 @@ use std::os::unix::io::RawFd; use std::os::unix::prelude::OpenOptionsExt; use std::path::{Path, PathBuf}; use std::sync::Arc; -use wal::{WALBytes, WALFile, WALPos, WALStore}; -use walerror::WALError; +use wal::{WalBytes, WalFile, WalPos, WalStore}; +use walerror::WalError; -pub struct WALFileAIO { +pub struct WalFileAio { fd: RawFd, - aiomgr: Arc, + aiomgr: Arc, } -impl WALFileAIO { - pub fn new(root_dir: &Path, filename: &str, aiomgr: Arc) -> Result { +impl WalFileAio { + pub fn new(root_dir: &Path, filename: &str, aiomgr: Arc) -> Result { fs::OpenOptions::new() .read(true) .write(true) @@ -83,22 +83,22 @@ impl WALFileAIO { .open(root_dir.join(filename)) .map(|f| { let fd = f.into_raw_fd(); - WALFileAIO { fd, aiomgr } + WalFileAio { fd, aiomgr } }) - .map_err(|e| WALError::IOError(Arc::new(e))) + .map_err(|e| WalError::IOError(Arc::new(e))) } } -impl Drop for WALFileAIO { +impl Drop for WalFileAio { fn drop(&mut self) { close(self.fd).unwrap(); } } #[async_trait(?Send)] -impl WALFile for WALFileAIO { +impl WalFile for WalFileAio { #[cfg(target_os = "linux")] - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError> { + async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { // TODO: is there any async version of fallocate? return fallocate( self.fd, @@ -111,21 +111,21 @@ impl WALFile for WALFileAIO { } #[cfg(not(target_os = "linux"))] // TODO: macos support is possible here, but possibly unnecessary - async fn allocate(&self, _offset: WALPos, _length: usize) -> Result<(), WALError> { + async fn allocate(&self, _offset: WalPos, _length: usize) -> Result<(), WalError> { Ok(()) } - async fn truncate(&self, length: usize) -> Result<(), WALError> { + async fn truncate(&self, length: usize) -> Result<(), WalError> { ftruncate(self.fd, length as off_t).map_err(From::from) } - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError> { + async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { let (res, data) = self.aiomgr.write(self.fd, offset, data, None).await; res.map_err(Into::into).and_then(|nwrote| { if nwrote == data.len() { Ok(()) } else { - Err(WALError::Other(format!( + Err(WalError::Other(format!( "partial write; wrote {nwrote} expected {} for fd {}", data.len(), self.fd @@ -134,29 +134,29 @@ impl WALFile for WALFileAIO { }) } - async fn read(&self, offset: WALPos, length: usize) -> Result, WALError> { + async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { let (res, data) = self.aiomgr.read(self.fd, offset, length, None).await; res.map_err(From::from) .map(|nread| if nread == length { Some(data) } else { None }) } } -pub struct WALStoreAIO { +pub struct WalStoreAio { root_dir: PathBuf, - aiomgr: Arc, + aiomgr: Arc, } -unsafe impl Send for WALStoreAIO {} +unsafe impl Send for WalStoreAio {} -impl WALStoreAIO { +impl WalStoreAio { pub fn new>( wal_dir: P, truncate: bool, - aiomgr: Option, - ) -> Result { + aiomgr: Option, + ) -> Result { let aio = match aiomgr { Some(aiomgr) => Arc::new(aiomgr), - None => Arc::new(AIOBuilder::default().build()?), + None => Arc::new(AioBuilder::default().build()?), }; if truncate { @@ -167,11 +167,11 @@ impl WALStoreAIO { } fs::create_dir(&wal_dir)?; } else if !wal_dir.as_ref().exists() { - // create WAL dir + // create Wal dir fs::create_dir(&wal_dir)?; } - Ok(WALStoreAIO { + Ok(WalStoreAio { root_dir: wal_dir.as_ref().to_path_buf(), aiomgr: aio, }) @@ -189,20 +189,20 @@ pub fn oflags() -> OFlag { } #[async_trait(?Send)] -impl WALStore for WALStoreAIO { +impl WalStore for WalStoreAio { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, _touch: bool) -> Result, WALError> { - WALFileAIO::new(&self.root_dir, filename, self.aiomgr.clone()) - .map(|f| Box::new(f) as Box) + async fn open_file(&self, filename: &str, _touch: bool) -> Result, WalError> { + WalFileAio::new(&self.root_dir, filename, self.aiomgr.clone()) + .map(|f| Box::new(f) as Box) } - async fn remove_file(&self, filename: String) -> Result<(), WALError> { + async fn remove_file(&self, filename: String) -> Result<(), WalError> { let file_to_remove = self.root_dir.join(filename); fs::remove_file(file_to_remove).map_err(From::from) } - fn enumerate_files(&self) -> Result { + fn enumerate_files(&self) -> Result { let mut filenames = Vec::new(); for path in fs::read_dir(&self.root_dir)?.filter_map(|entry| entry.ok()) { filenames.push(path.file_name().into_string().unwrap()); diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index 3eea6e92a9d6..c49823ae59c5 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -13,11 +13,11 @@ use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::pin::Pin; -pub use crate::walerror::WALError; +pub use crate::walerror::WalError; const FILENAME_FMT: &str = r"[0-9a-f]+\.log"; -enum WALRingType { +enum WalRingType { #[allow(dead_code)] Null = 0x0, Full, @@ -27,7 +27,7 @@ enum WALRingType { } #[repr(packed)] -struct WALRingBlob { +struct WalRingBlob { counter: u32, crc32: u32, rsize: u32, @@ -35,29 +35,29 @@ struct WALRingBlob { // payload follows } -impl TryFrom for WALRingType { +impl TryFrom for WalRingType { type Error = (); fn try_from(v: u8) -> Result { match v { - x if x == WALRingType::Null as u8 => Ok(WALRingType::Null), - x if x == WALRingType::Full as u8 => Ok(WALRingType::Full), - x if x == WALRingType::First as u8 => Ok(WALRingType::First), - x if x == WALRingType::Middle as u8 => Ok(WALRingType::Middle), - x if x == WALRingType::Last as u8 => Ok(WALRingType::Last), + x if x == WalRingType::Null as u8 => Ok(WalRingType::Null), + x if x == WalRingType::Full as u8 => Ok(WalRingType::Full), + x if x == WalRingType::First as u8 => Ok(WalRingType::First), + x if x == WalRingType::Middle as u8 => Ok(WalRingType::Middle), + x if x == WalRingType::Last as u8 => Ok(WalRingType::Last), _ => Err(()), } } } -type WALFileId = u64; -pub type WALBytes = Box<[u8]>; -pub type WALPos = u64; +type WalFileId = u64; +pub type WalBytes = Box<[u8]>; +pub type WalPos = u64; -fn get_fid(fname: &str) -> WALFileId { - scan_fmt!(fname, "{x}.log", [hex WALFileId]).unwrap() +fn get_fid(fname: &str) -> WalFileId { + scan_fmt!(fname, "{x}.log", [hex WalFileId]).unwrap() } -fn get_fname(fid: WALFileId) -> String { +fn get_fname(fid: WalFileId) -> String { format!("{:08x}.log", fid) } @@ -99,30 +99,30 @@ const HEADER_SIZE: usize = std::mem::size_of::
    (); #[repr(C)] #[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] -pub struct WALRingId { - start: WALPos, - end: WALPos, +pub struct WalRingId { + start: WalPos, + end: WalPos, counter: u32, } -impl WALRingId { +impl WalRingId { pub fn empty_id() -> Self { - WALRingId { + WalRingId { start: 0, end: 0, counter: 0, } } - pub fn get_start(&self) -> WALPos { + pub fn get_start(&self) -> WalPos { self.start } - pub fn get_end(&self) -> WALPos { + pub fn get_end(&self) -> WalPos { self.end } } -impl Ord for WALRingId { - fn cmp(&self, other: &WALRingId) -> std::cmp::Ordering { +impl Ord for WalRingId { + fn cmp(&self, other: &WalRingId) -> std::cmp::Ordering { other .start .cmp(&self.start) @@ -130,90 +130,90 @@ impl Ord for WALRingId { } } -impl PartialOrd for WALRingId { - fn partial_cmp(&self, other: &WALRingId) -> Option { +impl PartialOrd for WalRingId { + fn partial_cmp(&self, other: &WalRingId) -> Option { Some(self.cmp(other)) } } pub trait Record { - fn serialize(&self) -> WALBytes; + fn serialize(&self) -> WalBytes; } -impl Record for WALBytes { - fn serialize(&self) -> WALBytes { +impl Record for WalBytes { + fn serialize(&self) -> WalBytes { self[..].into() } } impl Record for String { - fn serialize(&self) -> WALBytes { + fn serialize(&self) -> WalBytes { self.as_bytes().into() } } impl Record for &str { - fn serialize(&self) -> WALBytes { + fn serialize(&self) -> WalBytes { self.as_bytes().into() } } /// the state for a WAL writer -struct WALState { - /// the next position for a record, addressed in the entire WAL space - next: WALPos, +struct WalState { + /// the next position for a record, addressed in the entire Wal space + next: WalPos, /// number of bits for a file file_nbit: u64, - next_complete: WALRingId, + next_complete: WalRingId, counter: u32, - io_complete: BinaryHeap, - pending_removal: VecDeque<(WALFileId, u32)>, + io_complete: BinaryHeap, + pending_removal: VecDeque<(WalFileId, u32)>, } #[async_trait(?Send)] -pub trait WALFile { +pub trait WalFile { /// Initialize the file space in [offset, offset + length) to zero. - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError>; + async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError>; /// Write data with offset. We assume all previous `allocate`/`truncate` invocations are visible /// if ordered earlier (should be guaranteed by most OS). Additionally, the write caused /// by each invocation of this function should be _atomic_ (the entire single write should be /// all or nothing). - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError>; + async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError>; /// Read data with offset. Return `Ok(None)` when it reaches EOF. - async fn read(&self, offset: WALPos, length: usize) -> Result, WALError>; + async fn read(&self, offset: WalPos, length: usize) -> Result, WalError>; /// Truncate a file to a specified length. #[allow(clippy::result_unit_err)] - async fn truncate(&self, length: usize) -> Result<(), WALError>; + async fn truncate(&self, length: usize) -> Result<(), WalError>; } #[async_trait(?Send)] -pub trait WALStore { +pub trait WalStore { type FileNameIter: Iterator; /// Open a file given the filename, create the file if not exists when `touch` is `true`. - async fn open_file(&self, filename: &str, touch: bool) -> Result, WALError>; + async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError>; /// Unlink a file given the filename. - async fn remove_file(&self, filename: String) -> Result<(), WALError>; - /// Enumerate all WAL filenames. It should include all WAL files that are previously opened + async fn remove_file(&self, filename: String) -> Result<(), WalError>; + /// Enumerate all Wal filenames. It should include all Wal files that are previously opened /// (created) but not removed. The list could be unordered. #[allow(clippy::result_unit_err)] - fn enumerate_files(&self) -> Result; + fn enumerate_files(&self) -> Result; } -struct WALFileHandle<'a, F: WALStore> { - fid: WALFileId, - handle: &'a dyn WALFile, - pool: *const WALFilePool, +struct WalFileHandle<'a, F: WalStore> { + fid: WalFileId, + handle: &'a dyn WalFile, + pool: *const WalFilePool, } -impl<'a, F: WALStore> std::ops::Deref for WALFileHandle<'a, F> { - type Target = dyn WALFile + 'a; +impl<'a, F: WalStore> std::ops::Deref for WalFileHandle<'a, F> { + type Target = dyn WalFile + 'a; fn deref(&self) -> &Self::Target { self.handle } } -impl<'a, F: WALStore> Drop for WALFileHandle<'a, F> { +impl<'a, F: WalStore> Drop for WalFileHandle<'a, F> { fn drop(&mut self) { unsafe { (*self.pool).release_file(self.fid); @@ -223,33 +223,33 @@ impl<'a, F: WALStore> Drop for WALFileHandle<'a, F> { /// The middle layer that manages WAL file handles and invokes public trait functions to actually /// manipulate files and their contents. -struct WALFilePool { +struct WalFilePool { store: F, - header_file: Box, - handle_cache: RefCell>>, + header_file: Box, + handle_cache: RefCell>>, #[allow(clippy::type_complexity)] - handle_used: RefCell, usize)>>>, + handle_used: RefCell, usize)>>>, #[allow(clippy::type_complexity)] - last_write: UnsafeCell>>>>>, + last_write: UnsafeCell>>>>>, #[allow(clippy::type_complexity)] - last_peel: UnsafeCell>>>>>, + last_peel: UnsafeCell>>>>>, file_nbit: u64, file_size: u64, block_nbit: u64, } -impl WALFilePool { +impl WalFilePool { async fn new( store: F, file_nbit: u64, block_nbit: u64, cache_size: NonZeroUsize, - ) -> Result { + ) -> Result { let file_nbit = file_nbit; let block_nbit = block_nbit; let header_file = store.open_file("HEAD", true).await?; header_file.truncate(HEADER_SIZE).await?; - Ok(WALFilePool { + Ok(WalFilePool { store, header_file, handle_cache: RefCell::new(lru::LruCache::new(cache_size)), @@ -262,14 +262,14 @@ impl WALFilePool { }) } - async fn read_header(&self) -> Result { + async fn read_header(&self) -> Result { let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); let header: Header = cast_slice(&bytes)[0]; Ok(header) } - async fn write_header(&self, header: &Header) -> Result<(), WALError> { + async fn write_header(&self, header: &Header) -> Result<(), WalError> { let base = header as *const Header as usize as *const u8; let bytes = unsafe { std::slice::from_raw_parts(base, HEADER_SIZE) }; self.header_file.write(0, bytes.into()).await?; @@ -278,8 +278,8 @@ impl WALFilePool { #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to remove mutable reference from being awaited. - async fn get_file(&self, fid: u64, touch: bool) -> Result, WALError> { - let pool = self as *const WALFilePool; + async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { + let pool = self as *const WalFilePool; if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Vacant(e) => unsafe { @@ -287,7 +287,7 @@ impl WALFilePool { }, _ => unreachable!(), }; - Ok(WALFileHandle { fid, handle, pool }) + Ok(WalFileHandle { fid, handle, pool }) } else { let v = unsafe { &mut *match self.handle_used.borrow_mut().entry(fid) { @@ -300,7 +300,7 @@ impl WALFilePool { .get() }; v.1 += 1; - Ok(WALFileHandle { + Ok(WalFileHandle { fid, handle: &*v.0, pool, @@ -308,7 +308,7 @@ impl WALFilePool { } } - fn release_file(&self, fid: WALFileId) { + fn release_file(&self, fid: WalFileId) { match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Occupied(e) => { let v = unsafe { &mut *e.get().get() }; @@ -326,8 +326,8 @@ impl WALFilePool { #[allow(clippy::type_complexity)] fn write<'a>( &'a mut self, - writes: Vec<(WALPos, WALBytes)>, - ) -> Vec> + 'a>>> { + writes: Vec<(WalPos, WalBytes)>, + ) -> Vec> + 'a>>> { if writes.is_empty() { return Vec::new(); } @@ -352,7 +352,7 @@ impl WALFilePool { let alloc = async move { last_write.await?; let mut last_h: Option< - Pin, WALError>> + 'a>>, + Pin, WalError>> + 'a>>, > = None; for ((next_fid, wl), h) in meta.into_iter().zip(files.into_iter()) { if let Some(lh) = last_h.take() { @@ -403,15 +403,15 @@ impl WALFilePool { #[allow(clippy::type_complexity)] fn remove_files<'a>( &'a mut self, - state: &mut WALState, + state: &mut WalState, keep_nrecords: u32, - ) -> impl Future> + 'a { + ) -> impl Future> + 'a { let last_peel = unsafe { std::mem::replace(&mut *self.last_peel.get(), std::mem::MaybeUninit::uninit()) .assume_init() }; - let mut removes: Vec>>>> = Vec::new(); + let mut removes: Vec>>>> = Vec::new(); while state.pending_removal.len() > 1 { let (fid, counter) = state.pending_removal.front().unwrap(); if counter_lt(counter + keep_nrecords, state.counter) { @@ -448,23 +448,23 @@ impl WALFilePool { } } -pub struct WALWriter { - state: WALState, - file_pool: WALFilePool, - block_buffer: WALBytes, +pub struct WalWriter { + state: WalState, + file_pool: WalFilePool, + block_buffer: WalBytes, block_size: u32, msize: usize, } -unsafe impl Send for WALWriter where F: WALStore + Send {} +unsafe impl Send for WalWriter where F: WalStore + Send {} -impl WALWriter { - fn new(state: WALState, file_pool: WALFilePool) -> Self { +impl WalWriter { + fn new(state: WalState, file_pool: WalFilePool) -> Self { let mut b = Vec::new(); let block_size = 1 << file_pool.block_nbit as u32; - let msize = std::mem::size_of::(); + let msize = std::mem::size_of::(); b.resize(block_size as usize, 0); - WALWriter { + WalWriter { state, file_pool, block_buffer: b.into_boxed_slice(), @@ -473,16 +473,16 @@ impl WALWriter { } } - /// Submit a sequence of records to WAL. It returns a vector of futures, each of which - /// corresponds to one record. When a future resolves to `WALRingId`, it is guaranteed the + /// Submit a sequence of records to Wal. It returns a vector of futures, each of which + /// corresponds to one record. When a future resolves to `WalRingId`, it is guaranteed the /// record is already logged. Then, after finalizing the changes encoded by that record to - /// the persistent storage, the caller can recycle the WAL files by invoking the given - /// `peel` with the given `WALRingId`s. Note: each serialized record should contain at least 1 + /// the persistent storage, the caller can recycle the Wal files by invoking the given + /// `peel` with the given `WalRingId`s. Note: each serialized record should contain at least 1 /// byte (empty record payload will result in assertion failure). pub fn grow<'a, R: Record + 'a>( &'a mut self, records: Vec, - ) -> Vec> + 'a> { + ) -> Vec> + 'a> { let mut res = Vec::new(); let mut writes = Vec::new(); let msize = self.msize as u32; @@ -506,7 +506,7 @@ impl WALWriter { let blob = unsafe { &mut *self.block_buffer[bbuff_cur as usize..] .as_mut_ptr() - .cast::() + .cast::() }; bbuff_cur += msize; if d >= rsize { @@ -517,10 +517,10 @@ impl WALWriter { blob.rsize = rsize; let (rs, rt) = if let Some(rs) = ring_start.take() { self.state.counter += 1; - (rs, WALRingType::Last) + (rs, WalRingType::Last) } else { self.state.counter += 1; - (rs0, WALRingType::Full) + (rs0, WalRingType::Full) }; blob.rtype = rt as u8; self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] @@ -529,7 +529,7 @@ impl WALWriter { rsize = 0; let end = self.state.next + (bbuff_cur - bbuff_start) as u64; res.push(( - WALRingId { + WalRingId { start: rs, end, counter: blob.counter, @@ -543,10 +543,10 @@ impl WALWriter { blob.crc32 = CRC32.checksum(payload); blob.rsize = d; blob.rtype = if ring_start.is_some() { - WALRingType::Middle + WalRingType::Middle } else { ring_start = Some(rs0); - WALRingType::First + WalRingType::First } as u8; self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] .copy_from_slice(payload); @@ -621,15 +621,15 @@ impl WALWriter { .collect() } - /// Inform the `WALWriter` that some data writes are complete so that it could automatically - /// remove obsolete WAL files. The given list of `WALRingId` does not need to be ordered and - /// could be of arbitrary length. Use `0` for `keep_nrecords` if all obsolete WAL files + /// Inform the `WalWriter` that some data writes are complete so that it could automatically + /// remove obsolete Wal files. The given list of `WalRingId` does not need to be ordered and + /// could be of arbitrary length. Use `0` for `keep_nrecords` if all obsolete Wal files /// need to removed (the obsolete files do not affect the speed of recovery or correctness). - pub async fn peel>( + pub async fn peel>( &mut self, records: T, keep_nrecords: u32, - ) -> Result<(), WALError> { + ) -> Result<(), WalError> { let msize = self.msize as u64; let block_size = self.block_size as u64; let state = &mut self.state; @@ -672,12 +672,12 @@ impl WALWriter { &'a self, nrecords: usize, recover_policy: &RecoverPolicy, - ) -> Result, WALError> { + ) -> Result, WalError> { let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); let file_pool = &self.file_pool; let file_nbit = file_pool.file_nbit; let block_size = 1 << file_pool.block_nbit; - let msize = std::mem::size_of::(); + let msize = std::mem::size_of::(); let logfiles = sort_fids( file_nbit, @@ -693,7 +693,7 @@ impl WALWriter { let mut records = Vec::new(); 'outer: for (_, fid) in logfiles.into_iter().rev() { let f = file_pool.get_file(fid, false).await?; - let ring_stream = WALLoader::read_rings(&f, true, file_pool.block_nbit, recover_policy); + let ring_stream = WalLoader::read_rings(&f, true, file_pool.block_nbit, recover_policy); let mut off = fid << file_nbit; let mut rings = Vec::new(); futures::pin_mut!(ring_stream); @@ -701,21 +701,21 @@ impl WALWriter { rings.push(ring); } for ring in rings.into_iter().rev() { - let ring = ring.map_err(|_| WALError::Other("error mapping ring".to_string()))?; + let ring = ring.map_err(|_| WalError::Other("error mapping ring".to_string()))?; let (header, payload) = ring; let payload = payload.unwrap(); match header.rtype.try_into() { - Ok(WALRingType::Full) => { + Ok(WalRingType::Full) => { assert!(chunks.is_none()); - if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(WALError::InvalidChecksum); + if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { + return Err(WalError::InvalidChecksum); } off += header.rsize as u64; records.push(payload); } - Ok(WALRingType::First) => { - if !WALLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(WALError::InvalidChecksum); + Ok(WalRingType::First) => { + if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { + return Err(WalError::InvalidChecksum); } if let Some(mut chunks) = chunks.take() { chunks.push(payload); @@ -730,7 +730,7 @@ impl WALWriter { } off += header.rsize as u64; } - Ok(WALRingType::Middle) => { + Ok(WalRingType::Middle) => { if let Some(chunks) = &mut chunks { chunks.push(payload); } else { @@ -738,15 +738,15 @@ impl WALWriter { } off += header.rsize as u64; } - Ok(WALRingType::Last) => { + Ok(WalRingType::Last) => { assert!(chunks.is_none()); chunks = Some(vec![payload]); off += header.rsize as u64; } - Ok(WALRingType::Null) => break, + Ok(WalRingType::Null) => break, Err(_) => match recover_policy { RecoverPolicy::Strict => { - return Err(WALError::Other( + return Err(WalError::Other( "invalid ring type - strict recovery requested".to_string(), )) } @@ -774,16 +774,16 @@ pub enum RecoverPolicy { BestEffort, } -pub struct WALLoader { +pub struct WalLoader { file_nbit: u64, block_nbit: u64, cache_size: NonZeroUsize, recover_policy: RecoverPolicy, } -impl Default for WALLoader { +impl Default for WalLoader { fn default() -> Self { - WALLoader { + WalLoader { file_nbit: 22, // 4MB block_nbit: 15, // 32KB, cache_size: NonZeroUsize::new(16).unwrap(), @@ -792,7 +792,7 @@ impl Default for WALLoader { } } -impl WALLoader { +impl WalLoader { pub fn new() -> Self { Default::default() } @@ -817,36 +817,36 @@ impl WALLoader { self } - fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { + fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { if checksum == CRC32.checksum(data) { Ok(true) } else { match p { - RecoverPolicy::Strict => Err(WALError::Other("invalid checksum".to_string())), + RecoverPolicy::Strict => Err(WalError::Other("invalid checksum".to_string())), RecoverPolicy::BestEffort => Ok(false), } } } - fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { + fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { Self::verify_checksum_(data, checksum, &self.recover_policy) } #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to a more safe solution. - fn read_rings<'a, F: WALStore + 'a>( - file: &'a WALFileHandle<'a, F>, + fn read_rings<'a, F: WalStore + 'a>( + file: &'a WalFileHandle<'a, F>, read_payload: bool, block_nbit: u64, recover_policy: &'a RecoverPolicy, - ) -> impl futures::Stream), bool>> + 'a { + ) -> impl futures::Stream), bool>> + 'a { let block_size = 1 << block_nbit; - let msize = std::mem::size_of::(); + let msize = std::mem::size_of::(); - struct Vars<'a, F: WALStore> { + struct Vars<'a, F: WalStore> { done: bool, off: u64, - file: &'a WALFileHandle<'a, F>, + file: &'a WalFileHandle<'a, F>, } let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { @@ -905,8 +905,8 @@ impl WALLoader { None => _yield!(), }; v.off += msize as u64; - let header = unsafe { &*header_raw.as_ptr().cast::() }; - let header = WALRingBlob { + let header = unsafe { &*header_raw.as_ptr().cast::() }; + let header = WalRingBlob { counter: header.counter, crc32: header.crc32, rsize: header.rsize, @@ -914,21 +914,21 @@ impl WALLoader { }; let payload; match header.rtype.try_into() { - Ok(WALRingType::Full) - | Ok(WALRingType::First) - | Ok(WALRingType::Middle) - | Ok(WALRingType::Last) => { + Ok(WalRingType::Full) + | Ok(WalRingType::First) + | Ok(WalRingType::Middle) + | Ok(WalRingType::Last) => { payload = if read_payload { Some(check!(check!( v.file.read(v.off, header.rsize as usize).await ) - .ok_or(WALError::Other))) + .ok_or(WalError::Other))) } else { None }; v.off += header.rsize as u64; } - Ok(WALRingType::Null) => _yield!(), + Ok(WalRingType::Null) => _yield!(), Err(_) => match recover_policy { RecoverPolicy::Strict => die!(), RecoverPolicy::BestEffort => { @@ -943,21 +943,21 @@ impl WALLoader { } #[allow(clippy::await_holding_refcell_ref)] - fn read_records<'a, F: WALStore + 'a>( + fn read_records<'a, F: WalStore + 'a>( &'a self, - file: &'a WALFileHandle<'a, F>, - chunks: &'a mut Option<(Vec, WALPos)>, - ) -> impl futures::Stream> + 'a { + file: &'a WalFileHandle<'a, F>, + chunks: &'a mut Option<(Vec, WalPos)>, + ) -> impl futures::Stream> + 'a { let fid = file.fid; let file_nbit = self.file_nbit; let block_size = 1 << self.block_nbit; - let msize = std::mem::size_of::(); + let msize = std::mem::size_of::(); - struct Vars<'a, F: WALStore> { + struct Vars<'a, F: WalStore> { done: bool, - chunks: &'a mut Option<(Vec, WALPos)>, + chunks: &'a mut Option<(Vec, WalPos)>, off: u64, - file: &'a WALFileHandle<'a, F>, + file: &'a WalFileHandle<'a, F>, } let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { @@ -1019,13 +1019,13 @@ impl WALLoader { }; let ringid_start = (fid << file_nbit) + v.off; v.off += msize as u64; - let header = unsafe { &*header_raw.as_ptr().cast::() }; + let header = unsafe { &*header_raw.as_ptr().cast::() }; let rsize = header.rsize; match header.rtype.try_into() { - Ok(WALRingType::Full) => { + Ok(WalRingType::Full) => { assert!(v.chunks.is_none()); let payload = check!(check!(v.file.read(v.off, rsize as usize).await) - .ok_or(WALError::Other)); + .ok_or(WalError::Other)); // TODO: improve the behavior when CRC32 fails if !check!(self.verify_checksum(&payload, header.crc32)) { die!() @@ -1033,7 +1033,7 @@ impl WALLoader { v.off += rsize as u64; _yield!(( payload, - WALRingId { + WalRingId { start: ringid_start, end: (fid << file_nbit) + v.off, counter: header.counter @@ -1041,23 +1041,23 @@ impl WALLoader { header.counter )) } - Ok(WALRingType::First) => { + Ok(WalRingType::First) => { assert!(v.chunks.is_none()); let chunk = check!(check!(v.file.read(v.off, rsize as usize).await) - .ok_or(WALError::Other)); + .ok_or(WalError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } *v.chunks = Some((vec![chunk], ringid_start)); v.off += rsize as u64; } - Ok(WALRingType::Middle) => { + Ok(WalRingType::Middle) => { let Vars { chunks, off, file, .. } = &mut *v; if let Some((chunks, _)) = chunks { let chunk = check!(check!(file.read(*off, rsize as usize).await) - .ok_or(WALError::Other)); + .ok_or(WalError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } @@ -1065,13 +1065,13 @@ impl WALLoader { } // otherwise ignore the leftover *off += rsize as u64; } - Ok(WALRingType::Last) => { + Ok(WalRingType::Last) => { let v_off = v.off; v.off += rsize as u64; if let Some((mut chunks, ringid_start)) = v.chunks.take() { let chunk = check!(check!(v.file.read(v_off, rsize as usize).await) - .ok_or(WALError::Other)); + .ok_or(WalError::Other)); if !check!(self.verify_checksum(&chunk, header.crc32)) { die!() } @@ -1085,7 +1085,7 @@ impl WALLoader { } _yield!(( payload.into_boxed_slice(), - WALRingId { + WalRingId { start: ringid_start, end: (fid << file_nbit) + v.off, counter: header.counter, @@ -1094,7 +1094,7 @@ impl WALLoader { )) } } - Ok(WALRingType::Null) => _yield!(), + Ok(WalRingType::Null) => _yield!(), Err(_) => match self.recover_policy { RecoverPolicy::Strict => die!(), RecoverPolicy::BestEffort => { @@ -1109,19 +1109,19 @@ impl WALLoader { }) } - /// Recover by reading the WAL files. - pub async fn load Result<(), WALError>>( + /// Recover by reading the Wal files. + pub async fn load Result<(), WalError>>( &self, store: S, mut recover_func: F, keep_nrecords: u32, - ) -> Result, WALError> { - let msize = std::mem::size_of::(); + ) -> Result, WalError> { + let msize = std::mem::size_of::(); assert!(self.file_nbit > self.block_nbit); assert!(msize < 1 << self.block_nbit); let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); let mut file_pool = - WALFilePool::new(store, self.file_nbit, self.block_nbit, self.cache_size).await?; + WalFilePool::new(store, self.file_nbit, self.block_nbit, self.cache_size).await?; let logfiles = sort_fids( self.file_nbit, file_pool @@ -1136,7 +1136,7 @@ impl WALLoader { let mut chunks = None; let mut pre_skip = true; - let mut scanned: Vec<(String, WALFileHandle)> = Vec::new(); + let mut scanned: Vec<(String, WalFileHandle)> = Vec::new(); let mut counter = 0; // TODO: check for missing logfiles @@ -1157,7 +1157,7 @@ impl WALLoader { let (bytes, ring_id, _) = match res { Err(e) => { if e { - return Err(WALError::Other( + return Err(WalError::Other( "error loading from storage".to_string(), )); } else { @@ -1178,8 +1178,8 @@ impl WALLoader { .await; for e in records.into_iter().rev() { let (rec, _) = - e.map_err(|_| WALError::Other("error decoding WALRingBlob".to_string()))?; - if rec.rtype == WALRingType::Full as u8 || rec.rtype == WALRingType::Last as u8 { + e.map_err(|_| WalError::Other("error decoding WalRingBlob".to_string()))?; + if rec.rtype == WalRingType::Full as u8 || rec.rtype == WalRingType::Last as u8 { counter = rec.counter + 1; break 'outer; } @@ -1202,7 +1202,7 @@ impl WALLoader { futures::pin_mut!(stream); while let Some(r) = stream.next().await { last = - Some(r.map_err(|_| WALError::Other("error decoding WALRingBlob".to_string()))?); + Some(r.map_err(|_| WalError::Other("error decoding WalRingBlob".to_string()))?); } if let Some((last_rec, _)) = last { if !counter_lt(last_rec.counter + keep_nrecords, counter) { @@ -1221,13 +1221,13 @@ impl WALLoader { file_pool.reset(); let next = recover_fid << self.file_nbit; - let next_complete = WALRingId { + let next_complete = WalRingId { start: 0, end: next, counter, }; - Ok(WALWriter::new( - WALState { + Ok(WalWriter::new( + WalState { counter, next_complete, next, diff --git a/firewood-growth-ring/src/walerror.rs b/firewood-growth-ring/src/walerror.rs index de1526c500c3..0d6f6c5156f4 100644 --- a/firewood-growth-ring/src/walerror.rs +++ b/firewood-growth-ring/src/walerror.rs @@ -1,11 +1,11 @@ use std::sync::Arc; -use firewood_libaio::AIOError; +use firewood_libaio::AioError; use nix::errno::Errno; use thiserror::Error; #[derive(Clone, Debug, Error)] -pub enum WALError { +pub enum WalError { #[error("an unclassified error has occurred: {0}")] Other(String), #[error("an OS error {0} has occurred")] @@ -15,25 +15,25 @@ pub enum WALError { #[error("an I/O error has occurred")] IOError(Arc), #[error("lib AIO error has occurred")] - AIOError(AIOError), - #[error("WAL directory already exists")] - WALDirExists, + AIOError(AioError), + #[error("Wal directory already exists")] + WalDirExists, } -impl From for WALError { +impl From for WalError { fn from(value: i32) -> Self { Self::UnixError(Errno::from_i32(value)) } } -impl From for WALError { +impl From for WalError { fn from(err: std::io::Error) -> Self { Self::IOError(Arc::new(err)) } } -impl From for WALError { - fn from(err: AIOError) -> Self { +impl From for WalError { + fn from(err: AioError) -> Self { Self::AIOError(err) } } diff --git a/firewood-growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs index 268d26a1e4ac..7adae7f064fb 100644 --- a/firewood-growth-ring/tests/common/mod.rs +++ b/firewood-growth-ring/tests/common/mod.rs @@ -2,7 +2,7 @@ #[allow(dead_code)] use async_trait::async_trait; use futures::executor::block_on; -use growthring::wal::{WALBytes, WALError, WALFile, WALLoader, WALPos, WALRingId, WALStore}; +use growthring::wal::{WalBytes, WalError, WalFile, WalLoader, WalPos, WalRingId, WalStore}; use indexmap::{map::Entry, IndexMap}; use rand::Rng; use std::cell::RefCell; @@ -31,16 +31,16 @@ impl std::ops::Deref for FileContentEmul { } /// Emulate the a virtual file handle. -pub struct WALFileEmul { +pub struct WalFileEmul { file: Rc, fgen: Rc, } #[async_trait(?Send)] -impl WALFile for WALFileEmul { - async fn allocate(&self, offset: WALPos, length: usize) -> Result<(), WALError> { +impl WalFile for WalFileEmul { + async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { if self.fgen.next_fail() { - return Err(WALError::Other("allocate fgen next fail".to_string())); + return Err(WalError::Other("allocate fgen next fail".to_string())); } let offset = offset as usize; if offset + length > self.file.borrow().len() { @@ -52,26 +52,26 @@ impl WALFile for WALFileEmul { Ok(()) } - async fn truncate(&self, length: usize) -> Result<(), WALError> { + async fn truncate(&self, length: usize) -> Result<(), WalError> { if self.fgen.next_fail() { - return Err(WALError::Other("truncate fgen next fail".to_string())); + return Err(WalError::Other("truncate fgen next fail".to_string())); } self.file.borrow_mut().resize(length, 0); Ok(()) } - async fn write(&self, offset: WALPos, data: WALBytes) -> Result<(), WALError> { + async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { if self.fgen.next_fail() { - return Err(WALError::Other("write fgen next fail".to_string())); + return Err(WalError::Other("write fgen next fail".to_string())); } let offset = offset as usize; self.file.borrow_mut()[offset..offset + data.len()].copy_from_slice(&data); Ok(()) } - async fn read(&self, offset: WALPos, length: usize) -> Result, WALError> { + async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { if self.fgen.next_fail() { - return Err(WALError::Other("read fgen next fail".to_string())); + return Err(WalError::Other("read fgen next fail".to_string())); } let offset = offset as usize; @@ -86,84 +86,84 @@ impl WALFile for WALFileEmul { } } -pub struct WALStoreEmulState { +pub struct WalStoreEmulState { files: HashMap>, } -impl WALStoreEmulState { +impl WalStoreEmulState { pub fn new() -> Self { - WALStoreEmulState { + WalStoreEmulState { files: HashMap::new(), } } pub fn clone(&self) -> Self { - WALStoreEmulState { + WalStoreEmulState { files: self.files.clone(), } } } /// Emulate the persistent storage state. -pub struct WALStoreEmul<'a, G> +pub struct WalStoreEmul<'a, G> where G: FailGen, { - state: RefCell<&'a mut WALStoreEmulState>, + state: RefCell<&'a mut WalStoreEmulState>, fgen: Rc, } -impl<'a, G: FailGen> WALStoreEmul<'a, G> { - pub fn new(state: &'a mut WALStoreEmulState, fgen: Rc) -> Self { +impl<'a, G: FailGen> WalStoreEmul<'a, G> { + pub fn new(state: &'a mut WalStoreEmulState, fgen: Rc) -> Self { let state = RefCell::new(state); - WALStoreEmul { state, fgen } + WalStoreEmul { state, fgen } } } #[async_trait(?Send)] -impl<'a, G> WALStore for WALStoreEmul<'a, G> +impl<'a, G> WalStore for WalStoreEmul<'a, G> where G: 'static + FailGen, { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, touch: bool) -> Result, WALError> { + async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError> { if self.fgen.next_fail() { - return Err(WALError::Other("open_file fgen next fail".to_string())); + return Err(WalError::Other("open_file fgen next fail".to_string())); } match self.state.borrow_mut().files.entry(filename.to_string()) { - hash_map::Entry::Occupied(e) => Ok(Box::new(WALFileEmul { + hash_map::Entry::Occupied(e) => Ok(Box::new(WalFileEmul { file: e.get().clone(), fgen: self.fgen.clone(), })), hash_map::Entry::Vacant(e) => { if touch { - Ok(Box::new(WALFileEmul { + Ok(Box::new(WalFileEmul { file: e.insert(Rc::new(FileContentEmul::new())).clone(), fgen: self.fgen.clone(), })) } else { - Err(WALError::Other("open_file not found".to_string())) + Err(WalError::Other("open_file not found".to_string())) } } } } - async fn remove_file(&self, filename: String) -> Result<(), WALError> { + async fn remove_file(&self, filename: String) -> Result<(), WalError> { //println!("remove_file(filename={})", filename); if self.fgen.next_fail() { - return Err(WALError::Other("remove_file fgen next fail".to_string())); + return Err(WalError::Other("remove_file fgen next fail".to_string())); } self.state .borrow_mut() .files .remove(&filename) - .ok_or(WALError::Other("remove_file not found".to_string())) + .ok_or(WalError::Other("remove_file not found".to_string())) .map(|_| ()) } - fn enumerate_files(&self) -> Result { + fn enumerate_files(&self) -> Result { if self.fgen.next_fail() { - return Err(WALError::Other( + return Err(WalError::Other( "enumerate_files fgen next fail".to_string(), )); } @@ -232,7 +232,7 @@ impl PaintStrokes { PaintStrokes(Vec::new()) } - pub fn to_bytes(&self) -> WALBytes { + pub fn to_bytes(&self) -> WalBytes { let mut res: Vec = Vec::new(); let is = std::mem::size_of::(); let len = self.0.len() as u32; @@ -297,7 +297,7 @@ impl PaintStrokes { } impl growthring::wal::Record for PaintStrokes { - fn serialize(&self) -> WALBytes { + fn serialize(&self) -> WalBytes { self.to_bytes() } } @@ -321,8 +321,8 @@ fn test_paint_strokes() { } pub struct Canvas { - waiting: HashMap, - queue: IndexMap>, + waiting: HashMap, + queue: IndexMap>, canvas: Box<[u32]>, } @@ -351,21 +351,21 @@ impl Canvas { res } - fn get_waiting(&mut self, rid: WALRingId) -> &mut usize { + fn get_waiting(&mut self, rid: WalRingId) -> &mut usize { match self.waiting.entry(rid) { hash_map::Entry::Occupied(e) => e.into_mut(), hash_map::Entry::Vacant(e) => e.insert(0), } } - fn get_queued(&mut self, pos: u32) -> &mut VecDeque<(u32, WALRingId)> { + fn get_queued(&mut self, pos: u32) -> &mut VecDeque<(u32, WalRingId)> { match self.queue.entry(pos) { Entry::Occupied(e) => e.into_mut(), Entry::Vacant(e) => e.insert(VecDeque::new()), } } - pub fn prepaint(&mut self, strokes: &PaintStrokes, rid: &WALRingId) { + pub fn prepaint(&mut self, strokes: &PaintStrokes, rid: &WalRingId) { let rid = *rid; let mut nwait = 0; for (s, e, c) in strokes.0.iter() { @@ -379,8 +379,8 @@ impl Canvas { // TODO: allow customized scheduler /// Schedule to paint one position, randomly. It optionally returns a finished batch write - /// identified by its start position of WALRingId. - pub fn rand_paint(&mut self, rng: &mut R) -> Option<(Option, u32)> { + /// identified by its start position of WalRingId. + pub fn rand_paint(&mut self, rng: &mut R) -> Option<(Option, u32)> { if self.is_empty() { return None; } @@ -406,7 +406,7 @@ impl Canvas { self.queue.is_empty() } - pub fn paint(&mut self, pos: u32) -> Option { + pub fn paint(&mut self, pos: u32) -> Option { let q = self.queue.get_mut(&pos).unwrap(); let (c, rid) = q.pop_front().unwrap(); if q.is_empty() { @@ -448,7 +448,7 @@ fn test_canvas() { let mut canvas1 = Canvas::new(100); let mut canvas2 = Canvas::new(100); let canvas3 = Canvas::new(101); - let dummy = WALRingId::empty_id(); + let dummy = WalRingId::empty_id(); let s1 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); let s2 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); assert!(canvas1.is_same(&canvas2)); @@ -491,19 +491,19 @@ pub struct PaintingSim { impl PaintingSim { pub fn run( &self, - state: &mut WALStoreEmulState, + state: &mut WalStoreEmulState, canvas: &mut Canvas, - loader: WALLoader, + loader: WalLoader, ops: &mut Vec, - ringid_map: &mut HashMap, + ringid_map: &mut HashMap, fgen: Rc, - ) -> Result<(), WALError> { + ) -> Result<(), WalError> { let mut rng = ::seed_from_u64(self.seed); let mut wal = block_on(loader.load( - WALStoreEmul::new(state, fgen.clone()), + WalStoreEmul::new(state, fgen.clone()), |_, _| { if fgen.next_fail() { - Err(WALError::Other("run fgen fail".to_string())) + Err(WalError::Other("run fgen fail".to_string())) } else { Ok(()) } @@ -532,12 +532,12 @@ impl PaintingSim { .map(|(r, ps)| -> Result<_, _> { ops.push(ps); let (rec, rid) = futures::executor::block_on(r) - .map_err(|_| WALError::Other("paintstrokes executor error".to_string()))?; + .map_err(|_| WalError::Other("paintstrokes executor error".to_string()))?; ringid_map.insert(rid, ops.len() - 1); Ok((rec, rid)) }) - .collect::, WALError>>()?; - // finish appending to WAL + .collect::, WalError>>()?; + // finish appending to Wal /* for rid in rids.iter() { println!("got ringid: {:?}", rid); @@ -551,7 +551,7 @@ impl PaintingSim { for _ in 0..rng.gen_range(1..self.k) { // storage I/O could fail if fgen.next_fail() { - return Err(WALError::Other("run fgen fail: storage i/o".to_string())); + return Err(WalError::Other("run fgen fail: storage i/o".to_string())); } if let Some((fin_rid, _)) = canvas.rand_paint(&mut rng) { if let Some(rid) = fin_rid { @@ -567,8 +567,8 @@ impl PaintingSim { Ok(()) } - pub fn get_walloader(&self) -> WALLoader { - let mut loader = WALLoader::new(); + pub fn get_walloader(&self) -> WalLoader { + let mut loader = WalLoader::new(); loader .file_nbit(self.file_nbit) .block_nbit(self.block_nbit) @@ -576,7 +576,7 @@ impl PaintingSim { loader } - pub fn get_nticks(&self, state: &mut WALStoreEmulState) -> usize { + pub fn get_nticks(&self, state: &mut WalStoreEmulState) -> usize { let mut canvas = Canvas::new(self.csize); let mut ops: Vec = Vec::new(); let mut ringid_map = HashMap::new(); @@ -595,11 +595,11 @@ impl PaintingSim { pub fn check( &self, - state: &mut WALStoreEmulState, + state: &mut WalStoreEmulState, canvas: &mut Canvas, - wal: WALLoader, + wal: WalLoader, ops: &Vec, - ringid_map: &HashMap, + ringid_map: &HashMap, ) -> bool { if ops.is_empty() { return true; @@ -608,7 +608,7 @@ impl PaintingSim { let mut napplied = 0; canvas.clear_queued(); block_on(wal.load( - WALStoreEmul::new(state, Rc::new(ZeroFailGen)), + WalStoreEmul::new(state, Rc::new(ZeroFailGen)), |payload, ringid| { let s = PaintStrokes::from_bytes(&payload); canvas.prepaint(&s, &ringid); @@ -642,7 +642,7 @@ impl PaintingSim { break; } for i in i0..ops.len() { - canvas0.prepaint(&ops[i], &WALRingId::empty_id()); + canvas0.prepaint(&ops[i], &WalRingId::empty_id()); canvas0.paint_all(); if canvas.is_same(&canvas0) { break 'outer; diff --git a/firewood-growth-ring/tests/rand_fail.rs b/firewood-growth-ring/tests/rand_fail.rs index 7a24d230b88a..25fbf2e25863 100644 --- a/firewood-growth-ring/tests/rand_fail.rs +++ b/firewood-growth-ring/tests/rand_fail.rs @@ -4,7 +4,7 @@ mod common; use std::collections::HashMap; use std::rc::Rc; -fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WALStoreEmulState, f: usize) { +fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WalStoreEmulState, f: usize) { let sim = &sims[0]; // save the current state and start from there let mut state = state.clone(); @@ -44,7 +44,7 @@ fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WALStoreEm } fn multi_point_failure(sims: &[common::PaintingSim]) { - _multi_point_failure(sims, &common::WALStoreEmulState::new(), 1); + _multi_point_failure(sims, &common::WalStoreEmulState::new(), 1); } #[test] diff --git a/firewood-libaio/src/abi.rs b/firewood-libaio/src/abi.rs index 347a6065d350..995dbce0fed5 100644 --- a/firewood-libaio/src/abi.rs +++ b/firewood-libaio/src/abi.rs @@ -8,7 +8,7 @@ use std::default::Default; use std::mem::zeroed; #[repr(C)] -pub enum IOCmd { +pub enum IoCmd { PRead = 0, PWrite = 1, FSync = 2, @@ -26,7 +26,7 @@ pub const IOCB_FLAG_IOPRIO: u32 = 1 << 1; // Taken from linux/include/linux/aio_abi.h // This is a kernel ABI, so there should be no need to worry about it changing. #[repr(C)] -pub struct IOCb { +pub struct IoCb { pub aio_data: u64, // ends up in io_event.data // NOTE: the order of aio_key and aio_rw_flags could be byte-order depedent pub aio_key: u32, @@ -45,10 +45,10 @@ pub struct IOCb { pub aio_resfd: u32, } -impl Default for IOCb { - fn default() -> IOCb { - IOCb { - aio_lio_opcode: IOCmd::Noop as u16, +impl Default for IoCb { + fn default() -> IoCb { + IoCb { + aio_lio_opcode: IoCmd::Noop as u16, aio_fildes: (-1_i32) as u32, ..unsafe { zeroed() } } @@ -57,45 +57,45 @@ impl Default for IOCb { #[derive(Clone)] #[repr(C)] -pub struct IOEvent { +pub struct IoEvent { pub data: u64, pub obj: u64, pub res: i64, pub res2: i64, } -impl Default for IOEvent { - fn default() -> IOEvent { +impl Default for IoEvent { + fn default() -> IoEvent { unsafe { zeroed() } } } -pub enum IOContext {} -pub type IOContextPtr = *mut IOContext; +pub enum IoContext {} +pub type IoContextPtr = *mut IoContext; #[repr(C)] -pub struct IOVector { +pub struct IoVector { pub iov_base: *mut u8, pub iov_len: size_t, } #[link(name = "aio", kind = "static")] extern "C" { - pub fn io_queue_init(maxevents: c_int, ctxp: *mut IOContextPtr) -> c_int; - pub fn io_queue_release(ctx: IOContextPtr) -> c_int; - pub fn io_queue_run(ctx: IOContextPtr) -> c_int; - pub fn io_setup(maxevents: c_int, ctxp: *mut IOContextPtr) -> c_int; - pub fn io_destroy(ctx: IOContextPtr) -> c_int; - pub fn io_submit(ctx: IOContextPtr, nr: c_long, ios: *mut *mut IOCb) -> c_int; - pub fn io_cancel(ctx: IOContextPtr, iocb: *mut IOCb, evt: *mut IOEvent) -> c_int; + pub fn io_queue_init(maxevents: c_int, ctxp: *mut IoContextPtr) -> c_int; + pub fn io_queue_release(ctx: IoContextPtr) -> c_int; + pub fn io_queue_run(ctx: IoContextPtr) -> c_int; + pub fn io_setup(maxevents: c_int, ctxp: *mut IoContextPtr) -> c_int; + pub fn io_destroy(ctx: IoContextPtr) -> c_int; + pub fn io_submit(ctx: IoContextPtr, nr: c_long, ios: *mut *mut IoCb) -> c_int; + pub fn io_cancel(ctx: IoContextPtr, iocb: *mut IoCb, evt: *mut IoEvent) -> c_int; pub fn io_getevents( - ctx_id: IOContextPtr, + ctx_id: IoContextPtr, min_nr: c_long, nr: c_long, - events: *mut IOEvent, + events: *mut IoEvent, timeout: *mut timespec, ) -> c_int; - pub fn io_set_eventfd(iocb: *mut IOCb, eventfd: c_int); + pub fn io_set_eventfd(iocb: *mut IoCb, eventfd: c_int); } #[cfg(test)] @@ -105,7 +105,7 @@ mod test { #[test] fn test_sizes() { // Check against kernel ABI - assert!(size_of::() == 32); - assert!(size_of::() == 64); + assert!(size_of::() == 32); + assert!(size_of::() == 64); } } diff --git a/firewood-libaio/src/lib.rs b/firewood-libaio/src/lib.rs index 7c0952d77fd6..01b22c0bfd74 100644 --- a/firewood-libaio/src/lib.rs +++ b/firewood-libaio/src/lib.rs @@ -6,9 +6,9 @@ //! //! ```rust //! use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; -//! use aiofut::AIOBuilder; +//! use aiofut::AioBuilder; //! use std::os::unix::io::AsRawFd; -//! let mut aiomgr = AIOBuilder::default().build().unwrap(); +//! let mut aiomgr = AioBuilder::default().build().unwrap(); //! let file = std::fs::OpenOptions::new() //! .read(true) //! .write(true) @@ -35,7 +35,7 @@ //! ``` mod abi; -use abi::IOCb; +use abi::IoCb; use libc::time_t; use parking_lot::Mutex; use std::collections::{hash_map, HashMap}; @@ -52,7 +52,7 @@ const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; #[derive(Clone, Debug)] -pub enum AIOError { +pub enum AioError { MaxEventsTooLarge, LowKernelRes, NotSupported, @@ -60,34 +60,34 @@ pub enum AIOError { } // NOTE: I assume it io_context_t is thread-safe, no? -struct AIOContext(abi::IOContextPtr); -unsafe impl Sync for AIOContext {} -unsafe impl Send for AIOContext {} +struct AioContext(abi::IoContextPtr); +unsafe impl Sync for AioContext {} +unsafe impl Send for AioContext {} -impl std::ops::Deref for AIOContext { - type Target = abi::IOContextPtr; - fn deref(&self) -> &abi::IOContextPtr { +impl std::ops::Deref for AioContext { + type Target = abi::IoContextPtr; + fn deref(&self) -> &abi::IoContextPtr { &self.0 } } -impl AIOContext { - fn new(maxevents: u32) -> Result { +impl AioContext { + fn new(maxevents: u32) -> Result { let mut ctx = std::ptr::null_mut(); unsafe { match abi::io_setup(maxevents as libc::c_int, &mut ctx) { 0 => Ok(()), - LIBAIO_EAGAIN => Err(AIOError::MaxEventsTooLarge), - LIBAIO_ENOMEM => Err(AIOError::LowKernelRes), - LIBAIO_ENOSYS => Err(AIOError::NotSupported), - _ => Err(AIOError::OtherError), + LIBAIO_EAGAIN => Err(AioError::MaxEventsTooLarge), + LIBAIO_ENOMEM => Err(AioError::LowKernelRes), + LIBAIO_ENOSYS => Err(AioError::NotSupported), + _ => Err(AioError::OtherError), } - .map(|_| AIOContext(ctx)) + .map(|_| AioContext(ctx)) } } } -impl Drop for AIOContext { +impl Drop for AioContext { fn drop(&mut self) { unsafe { assert_eq!(abi::io_destroy(self.0), 0); @@ -96,14 +96,14 @@ impl Drop for AIOContext { } /// Represent the necessary data for an AIO operation. Memory-safe when moved. -pub struct AIO { +pub struct Aio { // hold the buffer used by iocb data: Option>, - iocb: AtomicPtr, + iocb: AtomicPtr, id: u64, } -impl AIO { +impl Aio { fn new( id: u64, fd: RawFd, @@ -111,9 +111,9 @@ impl AIO { data: Box<[u8]>, priority: u16, flags: u32, - opcode: abi::IOCmd, + opcode: abi::IoCmd, ) -> Self { - let mut iocb = Box::::default(); + let mut iocb = Box::::default(); iocb.aio_fildes = fd as u32; iocb.aio_lio_opcode = opcode as u16; iocb.aio_reqprio = priority; @@ -124,11 +124,11 @@ impl AIO { iocb.aio_data = id; let iocb = AtomicPtr::new(Box::into_raw(iocb)); let data = Some(data); - AIO { iocb, id, data } + Aio { iocb, id, data } } } -impl Drop for AIO { +impl Drop for Aio { fn drop(&mut self) { unsafe { drop(Box::from_raw(self.iocb.load(Ordering::Acquire))); @@ -138,23 +138,23 @@ impl Drop for AIO { /// The result of an AIO operation: the number of bytes written on success, /// or the errno on failure. -pub type AIOResult = (Result, Box<[u8]>); +pub type AioResult = (Result, Box<[u8]>); /// Represents a scheduled (future) asynchronous I/O operation, which gets executed (resolved) /// automatically. -pub struct AIOFuture { - notifier: Arc, +pub struct AioFuture { + notifier: Arc, aio_id: u64, } -impl AIOFuture { +impl AioFuture { pub fn get_id(&self) -> u64 { self.aio_id } } -impl std::future::Future for AIOFuture { - type Output = AIOResult; +impl std::future::Future for AioFuture { + type Output = AioResult; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> std::task::Poll { if let Some(ret) = self.notifier.poll(self.aio_id, cx.waker()) { std::task::Poll::Ready(ret) @@ -164,30 +164,30 @@ impl std::future::Future for AIOFuture { } } -impl Drop for AIOFuture { +impl Drop for AioFuture { fn drop(&mut self) { self.notifier.dropped(self.aio_id) } } #[allow(clippy::enum_variant_names)] -enum AIOState { - FutureInit(AIO, bool), - FuturePending(AIO, std::task::Waker, bool), - FutureDone(AIOResult), +enum AioState { + FutureInit(Aio, bool), + FuturePending(Aio, std::task::Waker, bool), + FutureDone(AioResult), } /// The state machine for finished AIO operations and wakes up the futures. -pub struct AIONotifier { - waiting: Mutex>, +pub struct AioNotifier { + waiting: Mutex>, npending: AtomicUsize, - io_ctx: AIOContext, + io_ctx: AioContext, #[cfg(feature = "emulated-failure")] emul_fail: Option, } -impl AIONotifier { - fn register_notify(&self, id: u64, state: AIOState) { +impl AioNotifier { + fn register_notify(&self, id: u64, state: AioState) { let mut waiting = self.waiting.lock(); assert!(waiting.insert(id, state).is_none()); } @@ -196,30 +196,30 @@ impl AIONotifier { let mut waiting = self.waiting.lock(); if let hash_map::Entry::Occupied(mut e) = waiting.entry(id) { match e.get_mut() { - AIOState::FutureInit(_, dropped) => *dropped = true, - AIOState::FuturePending(_, _, dropped) => *dropped = true, - AIOState::FutureDone(_) => { + AioState::FutureInit(_, dropped) => *dropped = true, + AioState::FuturePending(_, _, dropped) => *dropped = true, + AioState::FutureDone(_) => { e.remove(); } } } } - fn poll(&self, id: u64, waker: &std::task::Waker) -> Option { + fn poll(&self, id: u64, waker: &std::task::Waker) -> Option { let mut waiting = self.waiting.lock(); match waiting.entry(id) { hash_map::Entry::Occupied(e) => { let v = e.remove(); match v { - AIOState::FutureInit(aio, _) => { - waiting.insert(id, AIOState::FuturePending(aio, waker.clone(), false)); + AioState::FutureInit(aio, _) => { + waiting.insert(id, AioState::FuturePending(aio, waker.clone(), false)); None } - AIOState::FuturePending(aio, waker, dropped) => { - waiting.insert(id, AIOState::FuturePending(aio, waker, dropped)); + AioState::FuturePending(aio, waker, dropped) => { + waiting.insert(id, AioState::FuturePending(aio, waker, dropped)); None } - AIOState::FutureDone(res) => Some(res), + AioState::FutureDone(res) => Some(res), } } _ => unreachable!(), @@ -231,12 +231,12 @@ impl AIONotifier { self.npending.fetch_sub(1, Ordering::Relaxed); match w.entry(id) { hash_map::Entry::Occupied(e) => match e.remove() { - AIOState::FutureInit(mut aio, dropped) => { + AioState::FutureInit(mut aio, dropped) => { if !dropped { let data = aio.data.take().unwrap(); w.insert( id, - AIOState::FutureDone(if res >= 0 { + AioState::FutureDone(if res >= 0 { (Ok(res as usize), data) } else { (Err(-res as i32), data) @@ -244,12 +244,12 @@ impl AIONotifier { ); } } - AIOState::FuturePending(mut aio, waker, dropped) => { + AioState::FuturePending(mut aio, waker, dropped) => { if !dropped { let data = aio.data.take().unwrap(); w.insert( id, - AIOState::FutureDone(if res >= 0 { + AioState::FutureDone(if res >= 0 { (Ok(res as usize), data) } else { (Err(-res as i32), data) @@ -258,8 +258,8 @@ impl AIONotifier { waker.wake(); } } - AIOState::FutureDone(ret) => { - w.insert(id, AIOState::FutureDone(ret)); + AioState::FutureDone(ret) => { + w.insert(id, AioState::FutureDone(ret)); } }, _ => unreachable!(), @@ -267,7 +267,7 @@ impl AIONotifier { } } -pub struct AIOBuilder { +pub struct AioBuilder { max_events: u32, max_nwait: u16, max_nbatched: usize, @@ -276,9 +276,9 @@ pub struct AIOBuilder { emul_fail: Option, } -impl Default for AIOBuilder { +impl Default for AioBuilder { fn default() -> Self { - AIOBuilder { + AioBuilder { max_events: 128, max_nwait: 128, max_nbatched: 128, @@ -289,7 +289,7 @@ impl Default for AIOBuilder { } } -impl AIOBuilder { +impl AioBuilder { /// Maximum concurrent async IO operations. pub fn max_events(&mut self, v: u32) -> &mut Self { self.max_events = v; @@ -322,18 +322,18 @@ impl AIOBuilder { /// Build an AIOManager object based on the configuration (and auto-start the background IO /// scheduling thread). - pub fn build(&mut self) -> Result { + pub fn build(&mut self) -> Result { let (scheduler_in, scheduler_out) = new_batch_scheduler(self.max_nbatched); let (exit_s, exit_r) = crossbeam_channel::bounded(0); - let notifier = Arc::new(AIONotifier { - io_ctx: AIOContext::new(self.max_events)?, + let notifier = Arc::new(AioNotifier { + io_ctx: AioContext::new(self.max_events)?, waiting: Mutex::new(HashMap::new()), npending: AtomicUsize::new(0), #[cfg(feature = "emulated-failure")] emul_fail: self.emul_fail.as_ref().map(|ef| ef.clone()), }); - let mut aiomgr = AIOManager { + let mut aiomgr = AioManager { notifier, listener: None, scheduler_in, @@ -351,21 +351,21 @@ pub trait EmulatedFailure: Send { pub type EmulatedFailureShared = Arc>; /// Manager all AIOs. -pub struct AIOManager { - notifier: Arc, - scheduler_in: AIOBatchSchedulerIn, +pub struct AioManager { + notifier: Arc, + scheduler_in: AioBatchSchedulerIn, listener: Option>, exit_s: crossbeam_channel::Sender<()>, } -impl AIOManager { +impl AioManager { fn start( &mut self, - mut scheduler_out: AIOBatchSchedulerOut, + mut scheduler_out: AioBatchSchedulerOut, exit_r: crossbeam_channel::Receiver<()>, max_nwait: u16, timeout: Option, - ) -> Result<(), AIOError> { + ) -> Result<(), AioError> { let n = self.notifier.clone(); self.listener = Some(std::thread::spawn(move || { let mut timespec = timeout.map(|sec: u32| libc::timespec { @@ -398,7 +398,7 @@ impl AIOManager { continue; } // then block on any finishing aios - let mut events = vec![abi::IOEvent::default(); max_nwait as usize]; + let mut events = vec![abi::IoEvent::default(); max_nwait as usize]; let ret = unsafe { abi::io_getevents( *n.io_ctx, @@ -438,19 +438,19 @@ impl AIOManager { Ok(()) } - pub fn read(&self, fd: RawFd, offset: u64, length: usize, priority: Option) -> AIOFuture { + pub fn read(&self, fd: RawFd, offset: u64, length: usize, priority: Option) -> AioFuture { let priority = priority.unwrap_or(0); let mut data = Vec::new(); data.resize(length, 0); let data = data.into_boxed_slice(); - let aio = AIO::new( + let aio = Aio::new( self.scheduler_in.next_id(), fd, offset, data, priority, 0, - abi::IOCmd::PRead, + abi::IoCmd::PRead, ); self.scheduler_in.schedule(aio, &self.notifier) } @@ -461,16 +461,16 @@ impl AIOManager { offset: u64, data: Box<[u8]>, priority: Option, - ) -> AIOFuture { + ) -> AioFuture { let priority = priority.unwrap_or(0); - let aio = AIO::new( + let aio = Aio::new( self.scheduler_in.next_id(), fd, offset, data, priority, 0, - abi::IOCmd::PWrite, + abi::IoCmd::PWrite, ); self.scheduler_in.schedule(aio, &self.notifier) } @@ -480,9 +480,9 @@ impl AIOManager { let w = self.notifier.waiting.lock(); w.get(&aio_id).map(|state| { match state { - AIOState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), - AIOState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), - AIOState::FutureDone(res) => &res.1, + AioState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), + AioState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), + AioState::FutureDone(res) => &res.1, } .to_vec() }) @@ -494,32 +494,32 @@ impl AIOManager { } } -impl Drop for AIOManager { +impl Drop for AioManager { fn drop(&mut self) { self.exit_s.send(()).unwrap(); self.listener.take().unwrap().join().unwrap(); } } -pub struct AIOBatchSchedulerIn { - queue_in: crossbeam_channel::Sender>, +pub struct AioBatchSchedulerIn { + queue_in: crossbeam_channel::Sender>, last_id: std::cell::Cell, } -pub struct AIOBatchSchedulerOut { - queue_out: crossbeam_channel::Receiver>, +pub struct AioBatchSchedulerOut { + queue_out: crossbeam_channel::Receiver>, max_nbatched: usize, - leftover: Vec>, + leftover: Vec>, } -impl AIOBatchSchedulerIn { - fn schedule(&self, aio: AIO, notifier: &Arc) -> AIOFuture { - let fut = AIOFuture { +impl AioBatchSchedulerIn { + fn schedule(&self, aio: Aio, notifier: &Arc) -> AioFuture { + let fut = AioFuture { notifier: notifier.clone(), aio_id: aio.id, }; let iocb = aio.iocb.load(Ordering::Acquire); - notifier.register_notify(aio.id, AIOState::FutureInit(aio, false)); + notifier.register_notify(aio.id, AioState::FutureInit(aio, false)); self.queue_in.send(AtomicPtr::new(iocb)).unwrap(); notifier.npending.fetch_add(1, Ordering::Relaxed); fut @@ -532,14 +532,14 @@ impl AIOBatchSchedulerIn { } } -impl AIOBatchSchedulerOut { - fn get_receiver(&self) -> &crossbeam_channel::Receiver> { +impl AioBatchSchedulerOut { + fn get_receiver(&self) -> &crossbeam_channel::Receiver> { &self.queue_out } fn is_empty(&self) -> bool { self.leftover.len() == 0 } - fn submit(&mut self, notifier: &AIONotifier) -> usize { + fn submit(&mut self, notifier: &AioNotifier) -> usize { let mut quota = self.max_nbatched; let mut pending = self .leftover @@ -579,13 +579,13 @@ impl AIOBatchSchedulerOut { } /// Create the scheduler that submits AIOs in batches. -fn new_batch_scheduler(max_nbatched: usize) -> (AIOBatchSchedulerIn, AIOBatchSchedulerOut) { +fn new_batch_scheduler(max_nbatched: usize) -> (AioBatchSchedulerIn, AioBatchSchedulerOut) { let (queue_in, queue_out) = crossbeam_channel::unbounded(); - let bin = AIOBatchSchedulerIn { + let bin = AioBatchSchedulerIn { queue_in, last_id: std::cell::Cell::new(0), }; - let bout = AIOBatchSchedulerOut { + let bout = AioBatchSchedulerOut { queue_out, max_nbatched, leftover: Vec::new(), diff --git a/firewood-libaio/tests/simple_test.rs b/firewood-libaio/tests/simple_test.rs index d6504533119a..4fdaab0d126c 100644 --- a/firewood-libaio/tests/simple_test.rs +++ b/firewood-libaio/tests/simple_test.rs @@ -1,4 +1,4 @@ -use aiofut::AIOBuilder; +use aiofut::AioBuilder; use futures::executor::LocalPool; use futures::future::FutureExt; use futures::task::LocalSpawnExt; @@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd; #[test] fn simple1() { - let aiomgr = AIOBuilder::default().max_events(100).build().unwrap(); + let aiomgr = AioBuilder::default().max_events(100).build().unwrap(); let file = std::fs::OpenOptions::new() .read(true) .write(true) @@ -32,7 +32,7 @@ fn simple1() { #[test] fn simple2() { - let aiomgr = AIOBuilder::default().build().unwrap(); + let aiomgr = AioBuilder::default().build().unwrap(); let file = std::fs::OpenOptions::new() .read(true) .write(true) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 641f143d7dc1..08b473af6dcd 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -3,7 +3,7 @@ use clap::Parser; use criterion::Criterion; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -22,7 +22,7 @@ struct Args { fn main() { let args = Args::parse(); - let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); { use rand::{Rng, SeedableRng}; let mut c = Criterion::default(); @@ -50,7 +50,7 @@ fn main() { &workload, |b, workload| { b.iter(|| { - let db = DB::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = Db::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); for batch in workload.iter() { let mut wb = db.new_writebatch(); for (k, v) in batch { diff --git a/firewood/examples/dump.rs b/firewood/examples/dump.rs index 93d1d3eaa8eb..784ef3808771 100644 --- a/firewood/examples/dump.rs +++ b/firewood/examples/dump.rs @@ -11,7 +11,7 @@ fn main() { #[cfg(feature = "eth")] use clap::{command, Arg, ArgMatches}; #[cfg(feature = "eth")] -use firewood::db::{DBConfig, DBError, WALConfig, DB}; +use firewood::db::{Db, DbConfig, DbError, WalConfig}; /// cargo run --example dump benchmark_db/ #[cfg(feature = "eth")] @@ -25,9 +25,9 @@ fn main() { ) .get_matches(); let path = get_db_path(matches); - let db = DB::new( + let db = Db::new( path.unwrap().as_str(), - &DBConfig::builder().truncate(false).build(), + &DbConfig::builder().truncate(false).build(), ) .unwrap(); let mut stdout = std::io::stdout(); @@ -42,14 +42,14 @@ fn main() { /// Returns the provided INPUT db path if one is provided. /// Otherwise, instantiate a DB called simple_db and return the path. #[cfg(feature = "eth")] -fn get_db_path(matches: ArgMatches) -> Result { +fn get_db_path(matches: ArgMatches) -> Result { if let Some(m) = matches.get_one::("INPUT") { return Ok(m.to_string()); } // Build and provide a new db path - let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); - let db = DB::new("simple_db", &cfg.truncate(true).build()).unwrap(); + let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + let db = Db::new("simple_db", &cfg.truncate(true).build()).unwrap(); db.new_writebatch() .set_balance(b"ted", 10.into()) .unwrap() diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index bf5b29cfbc22..8a54a7066576 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -2,15 +2,15 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - db::{DBConfig, Revision, WALConfig, DB}, + db::{Db, DbConfig, Revision, WalConfig}, proof::Proof, }; /// cargo run --example rev fn main() { - let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); { - let db = DB::new("rev_db", &cfg.clone().truncate(true).build()) + let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) .expect("db initiation should succeed"); let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; for (k, v) in items.iter() { @@ -77,7 +77,7 @@ fn main() { } { let db = - DB::new("rev_db", &cfg.truncate(false).build()).expect("db initiation should succeed"); + Db::new("rev_db", &cfg.truncate(false).build()).expect("db initiation should succeed"); { let revision = db.get_revision(0, None).expect("revision-0 should exist"); let revision_root_hash = revision @@ -88,7 +88,7 @@ fn main() { let current_root_hash = db .kv_root_hash() .expect("root-hash for current state should exist"); - // The following is true as long as the current state is fresh after replaying from WALs. + // The following is true as long as the current state is fresh after replaying from Wals. assert_eq!(revision_root_hash, current_root_hash); let revision = db.get_revision(1, None).expect("revision-1 should exist"); diff --git a/firewood/examples/simple.rs b/firewood/examples/simple.rs index 1b71431b5206..bf1d73d720b4 100644 --- a/firewood/examples/simple.rs +++ b/firewood/examples/simple.rs @@ -2,10 +2,10 @@ // See the file LICENSE.md for licensing terms. #[cfg(feature = "eth")] -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; #[cfg(feature = "eth")] -fn print_states(db: &DB) { +fn print_states(db: &Db) { println!("======"); for account in ["ted", "alice"] { let addr = account.as_bytes(); @@ -37,9 +37,9 @@ fn main() { /// cargo run --example simple #[cfg(feature = "eth")] fn main() { - let cfg = DBConfig::builder().wal(WALConfig::builder().max_revisions(10).build()); + let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); { - let db = DB::new("simple_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = Db::new("simple_db", &cfg.clone().truncate(true).build()).unwrap(); db.new_writebatch() .set_balance(b"ted", 10.into()) .unwrap() @@ -54,7 +54,7 @@ fn main() { .commit(); } { - let db = DB::new("simple_db", &cfg.clone().truncate(false).build()).unwrap(); + let db = Db::new("simple_db", &cfg.clone().truncate(false).build()).unwrap(); print_states(&db); db.new_writebatch() .set_state(b"alice", b"z", b"999".to_vec()) @@ -63,7 +63,7 @@ fn main() { print_states(&db); } { - let db = DB::new("simple_db", &cfg.truncate(false).build()).unwrap(); + let db = Db::new("simple_db", &cfg.truncate(false).build()).unwrap(); print_states(&db); let mut stdout = std::io::stdout(); let mut acc = None; diff --git a/firewood/src/account.rs b/firewood/src/account.rs index 3842dd712375..c44be6f569ac 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -67,9 +67,9 @@ impl Account { } } -pub struct AccountRLP; +pub struct AccountRlp; -impl ValueTransformer for AccountRLP { +impl ValueTransformer for AccountRlp { fn transform(raw: &[u8]) -> Vec { let acc = Account::deserialize(raw); let mut stream = rlp::RlpStream::new_list(4); diff --git a/firewood/src/api.rs b/firewood/src/api.rs index ce87c2695cb1..f921fa013163 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -8,7 +8,7 @@ use crate::account::Account; #[cfg(feature = "eth")] use primitive_types::U256; -use crate::db::{DBError, DBRevConfig}; +use crate::db::{DbError, DbRevConfig}; use crate::merkle::Hash; #[cfg(feature = "proof")] use crate::{merkle::MerkleError, proof::Proof}; @@ -18,9 +18,9 @@ use async_trait::async_trait; pub type Nonce = u64; #[async_trait] -pub trait DB { +pub trait Db { async fn new_writebatch(&self) -> B; - async fn get_revision(&self, nback: usize, cfg: Option) -> Option; + async fn get_revision(&self, nback: usize, cfg: Option) -> Option; } #[async_trait] @@ -32,13 +32,13 @@ where self, key: K, val: V, - ) -> Result; + ) -> Result; /// Remove an item from the generic key-value storage. `val` will be set to the value that is /// removed from the storage if it exists. async fn kv_remove + Send + Sync>( self, key: K, - ) -> Result<(Self, Option>), DBError>; + ) -> Result<(Self, Option>), DbError>; /// Set balance of the account #[cfg(feature = "eth")] @@ -46,21 +46,21 @@ where self, key: K, balance: U256, - ) -> Result; + ) -> Result; /// Set code of the account #[cfg(feature = "eth")] async fn set_code + Send + Sync, V: AsRef<[u8]> + Send + Sync>( self, key: K, code: V, - ) -> Result; + ) -> Result; /// Set nonce of the account. #[cfg(feature = "eth")] async fn set_nonce + Send + Sync>( self, key: K, nonce: u64, - ) -> Result; + ) -> Result; /// Set the state value indexed by `sub_key` in the account indexed by `key`. #[cfg(feature = "eth")] async fn set_state< @@ -72,17 +72,17 @@ where key: K, sub_key: SK, val: V, - ) -> Result; + ) -> Result; /// Create an account. #[cfg(feature = "eth")] - async fn create_account + Send + Sync>(self, key: K) -> Result; + async fn create_account + Send + Sync>(self, key: K) -> Result; /// Delete an account. #[cfg(feature = "eth")] async fn delete_account + Send + Sync>( self, key: K, acc: &mut Option, - ) -> Result; + ) -> Result; /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. async fn no_root_hash(self) -> Self; @@ -97,12 +97,12 @@ pub trait Revision where Self: Sized, { - async fn kv_root_hash(&self) -> Result; - async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError>; + async fn kv_root_hash(&self) -> Result; + async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError>; - async fn kv_dump(&self, writer: W) -> Result<(), DBError>; - async fn root_hash(&self) -> Result; - async fn dump(&self, writer: W) -> Result<(), DBError>; + async fn kv_dump(&self, writer: W) -> Result<(), DbError>; + async fn root_hash(&self) -> Result; + async fn dump(&self, writer: W) -> Result<(), DbError>; #[cfg(feature = "proof")] async fn prove + Send + Sync>(&self, key: K) -> Result; @@ -116,21 +116,21 @@ where values: Vec, ); #[cfg(feature = "eth")] - async fn get_balance + Send + Sync>(&self, key: K) -> Result; + async fn get_balance + Send + Sync>(&self, key: K) -> Result; #[cfg(feature = "eth")] - async fn get_code + Send + Sync>(&self, key: K) -> Result, DBError>; + async fn get_code + Send + Sync>(&self, key: K) -> Result, DbError>; #[cfg(feature = "eth")] - async fn get_nonce + Send + Sync>(&self, key: K) -> Result; + async fn get_nonce + Send + Sync>(&self, key: K) -> Result; #[cfg(feature = "eth")] async fn get_state + Send + Sync>( &self, key: K, sub_key: K, - ) -> Result, DBError>; + ) -> Result, DbError>; #[cfg(feature = "eth")] async fn dump_account + Send + Sync>( &self, key: K, writer: W, - ) -> Result<(), DBError>; + ) -> Result<(), DbError>; } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 717bde4503e7..0b267c7b94c9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -19,12 +19,12 @@ use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceID, Storable, use typed_builder::TypedBuilder; #[cfg(feature = "eth")] -use crate::account::{Account, AccountRLP, Blob, BlobStash}; +use crate::account::{Account, AccountRlp, Blob, BlobStash}; use crate::file; use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; use crate::proof::{Proof, ProofError}; use crate::storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}; -pub use crate::storage::{buffer::DiskBufferConfig, WALConfig}; +pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use crate::storage::{ AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, PAGE_SIZE_NBIT, @@ -40,7 +40,7 @@ const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; #[derive(Debug)] #[non_exhaustive] -pub enum DBError { +pub enum DbError { InvalidParams, Merkle(MerkleError), #[cfg(feature = "eth")] @@ -52,42 +52,42 @@ pub enum DBError { IO(std::io::Error), } -impl fmt::Display for DBError { +impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - DBError::InvalidParams => write!(f, "invalid parameters provided"), - DBError::Merkle(e) => write!(f, "merkle error: {e:?}"), + DbError::InvalidParams => write!(f, "invalid parameters provided"), + DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), #[cfg(feature = "eth")] - DBError::Blob(e) => write!(f, "storage error: {e:?}"), - DBError::System(e) => write!(f, "system error: {e:?}"), - DBError::KeyNotFound => write!(f, "not found"), - DBError::CreateError => write!(f, "database create error"), - DBError::IO(e) => write!(f, "I/O error: {e:?}"), - DBError::Shale(e) => write!(f, "shale error: {e:?}"), + DbError::Blob(e) => write!(f, "storage error: {e:?}"), + DbError::System(e) => write!(f, "system error: {e:?}"), + DbError::KeyNotFound => write!(f, "not found"), + DbError::CreateError => write!(f, "database create error"), + DbError::IO(e) => write!(f, "I/O error: {e:?}"), + DbError::Shale(e) => write!(f, "shale error: {e:?}"), } } } -impl From for DBError { +impl From for DbError { fn from(e: std::io::Error) -> Self { - DBError::IO(e) + DbError::IO(e) } } -impl From for DBError { +impl From for DbError { fn from(e: ShaleError) -> Self { - DBError::Shale(e) + DbError::Shale(e) } } -impl Error for DBError {} +impl Error for DbError {} -/// DBParams contains the constants that are fixed upon the creation of the DB, this ensures the +/// DbParams contains the constants that are fixed upon the creation of the DB, this ensures the /// correct parameters are used when the DB is opened later (the parameters here will override the -/// parameters in [DBConfig] if the DB already exists). +/// parameters in [DbConfig] if the DB already exists). #[repr(C)] #[derive(Debug, Clone, Copy, AnyBitPattern)] -struct DBParams { +struct DbParams { magic: [u8; 16], meta_file_nbit: u64, payload_file_nbit: u64, @@ -98,7 +98,7 @@ struct DBParams { /// Config for accessing a version of the DB. #[derive(TypedBuilder, Clone, Debug)] -pub struct DBRevConfig { +pub struct DbRevConfig { /// Maximum cached Trie objects. #[builder(default = 1 << 20)] pub merkle_ncached_objs: usize, @@ -109,7 +109,7 @@ pub struct DBRevConfig { /// Database configuration. #[derive(TypedBuilder, Debug)] -pub struct DBConfig { +pub struct DbConfig { /// Maximum cached pages for the free list of the item stash. #[builder(default = 16384)] // 64M total size by default pub meta_ncached_pages: usize, @@ -143,14 +143,14 @@ pub struct DBConfig { #[builder(default = false)] pub truncate: bool, /// Config for accessing a version of the DB. - #[builder(default = DBRevConfig::builder().build())] - pub rev: DBRevConfig, + #[builder(default = DbRevConfig::builder().build())] + pub rev: DbRevConfig, /// Config for the disk buffer. #[builder(default = DiskBufferConfig::builder().build())] pub buffer: DiskBufferConfig, - /// Config for WAL. - #[builder(default = WALConfig::builder().build())] - pub wal: WALConfig, + /// Config for Wal. + #[builder(default = WalConfig::builder().build())] + pub wal: WalConfig, } /// Necessary linear space instances bundled for a `CompactSpace`. @@ -214,7 +214,7 @@ fn get_sub_universe_from_empty_delta( } /// DB-wide metadata, it keeps track of the roots of the top-level tries. -struct DBHeader { +struct DbHeader { /// The root node of the account model storage. (Where the values are [Account] objects, which /// may contain the root for the secondary trie.) acc_root: ObjPtr, @@ -222,7 +222,7 @@ struct DBHeader { kv_root: ObjPtr, } -impl DBHeader { +impl DbHeader { pub const MSIZE: u64 = 16; pub fn new_empty() -> Self { @@ -233,7 +233,7 @@ impl DBHeader { } } -impl Storable for DBHeader { +impl Storable for DbHeader { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) @@ -303,14 +303,14 @@ impl Universe> { } /// Some readable version of the DB. -pub struct DBRev { - header: shale::Obj, +pub struct DbRev { + header: shale::Obj, merkle: Merkle, #[cfg(feature = "eth")] blob: BlobStash, } -impl DBRev { +impl DbRev { fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); self.merkle.flush_dirty()?; @@ -320,19 +320,19 @@ impl DBRev { } #[cfg(feature = "eth")] - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { (&mut self.header, &mut self.merkle, &mut self.blob) } #[cfg(not(feature = "eth"))] - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { (&mut self.header, &mut self.merkle) } /// Get root hash of the generic key-value storage. - pub fn kv_root_hash(&self) -> Result { + pub fn kv_root_hash(&self) -> Result { self.merkle .root_hash::(self.header.kv_root) - .map_err(DBError::Merkle) + .map_err(DbError::Merkle) } /// Get a value associated with a key. @@ -345,10 +345,10 @@ impl DBRev { } /// Dump the Trie of the generic key-value storage. - pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.merkle .dump(self.header.kv_root, w) - .map_err(DBError::Merkle) + .map_err(DbError::Merkle) } /// Provides a proof that a key is in the Trie. @@ -372,23 +372,23 @@ impl DBRev { } /// Check if the account exists. - pub fn exist>(&self, key: K) -> Result { + pub fn exist>(&self, key: K) -> Result { Ok(match self.merkle.get(key, self.header.acc_root) { Ok(r) => r.is_some(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }) } } #[cfg(feature = "eth")] -impl DBRev { +impl DbRev { /// Get nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { + pub fn get_nonce>(&self, key: K) -> Result { Ok(self.get_account(key)?.nonce) } /// Get the state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DbError> { let root = self.get_account(key)?.root; if root.is_null() { return Ok(Vec::new()); @@ -396,73 +396,73 @@ impl DBRev { Ok(match self.merkle.get(sub_key, root) { Ok(Some(v)) => v.to_vec(), Ok(None) => Vec::new(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }) } /// Get root hash of the world state of all accounts. - pub fn root_hash(&self) -> Result { + pub fn root_hash(&self) -> Result { self.merkle - .root_hash::(self.header.acc_root) - .map_err(DBError::Merkle) + .root_hash::(self.header.acc_root) + .map_err(DbError::Merkle) } /// Dump the Trie of the entire account model storage. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.merkle .dump(self.header.acc_root, w) - .map_err(DBError::Merkle) + .map_err(DbError::Merkle) } - fn get_account>(&self, key: K) -> Result { + fn get_account>(&self, key: K) -> Result { Ok(match self.merkle.get(key, self.header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => Account::default(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }) } /// Dump the Trie of the state storage under an account. - pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DbError> { let acc = match self.merkle.get(key, self.header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes), Ok(None) => Account::default(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }; writeln!(w, "{acc:?}").unwrap(); if !acc.root.is_null() { - self.merkle.dump(acc.root, w).map_err(DBError::Merkle)?; + self.merkle.dump(acc.root, w).map_err(DbError::Merkle)?; } Ok(()) } /// Get balance of the account. - pub fn get_balance>(&self, key: K) -> Result { + pub fn get_balance>(&self, key: K) -> Result { Ok(self.get_account(key)?.balance) } /// Get code of the account. - pub fn get_code>(&self, key: K) -> Result, DBError> { + pub fn get_code>(&self, key: K) -> Result, DbError> { let code = self.get_account(key)?.code; if code.is_null() { return Ok(Vec::new()); } - let b = self.blob.get_blob(code).map_err(DBError::Blob)?; + let b = self.blob.get_blob(code).map_err(DbError::Blob)?; Ok(match &**b { Blob::Code(code) => code.clone(), }) } } -struct DBInner { - latest: DBRev, +struct DbInner { + latest: DbRev, disk_requester: DiskBufferRequester, disk_thread: Option>, staging: Universe>, cached: Universe>, } -impl Drop for DBInner { +impl Drop for DbInner { fn drop(&mut self) { self.disk_requester.shutdown(); self.disk_thread.take().map(JoinHandle::join); @@ -470,22 +470,22 @@ impl Drop for DBInner { } /// Firewood database handle. -pub struct DB { - inner: Arc>, - revisions: Arc>, +pub struct Db { + inner: Arc>, + revisions: Arc>, payload_regn_nbit: u64, - rev_cfg: DBRevConfig, + rev_cfg: DbRevConfig, } -pub struct DBRevInner { +pub struct DbRevInner { inner: VecDeque>, max_revisions: usize, base: Universe, } -impl DB { +impl Db { /// Open a database. - pub fn new>(db_path: P, cfg: &DBConfig) -> Result { + pub fn new>(db_path: P, cfg: &DbConfig) -> Result { // TODO: make sure all fds are released at the end if cfg.truncate { let _ = std::fs::remove_dir_all(db_path.as_ref()); @@ -504,17 +504,17 @@ impl DB { let fd0 = file0.get_fd(); if reset { - // initialize DBParams + // initialize dbparams if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < PAGE_SIZE_NBIT { - return Err(DBError::InvalidParams); + return Err(DbError::InvalidParams); } - nix::unistd::ftruncate(fd0, 0).map_err(DBError::System)?; - nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DBError::System)?; + nix::unistd::ftruncate(fd0, 0).map_err(DbError::System)?; + nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DbError::System)?; let mut magic = [0; 16]; magic[..MAGIC_STR.len()].copy_from_slice(MAGIC_STR); - let header = DBParams { + let header = DbParams { magic, meta_file_nbit: cfg.meta_file_nbit, payload_file_nbit: cfg.payload_file_nbit, @@ -523,15 +523,15 @@ impl DB { wal_block_nbit: cfg.wal.block_nbit, }; nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0) - .map_err(DBError::System)?; + .map_err(DbError::System)?; } - // read DBParams - let mut header_bytes = [0; std::mem::size_of::()]; - nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DBError::System)?; + // read DbParams + let mut header_bytes = [0; std::mem::size_of::()]; + nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; drop(file0); let mut offset = header_bytes.len() as u64; - let header: DBParams = cast_slice(&header_bytes)[0]; + let header: DbParams = cast_slice(&header_bytes)[0]; // setup disk buffer let cached = Universe { @@ -589,7 +589,7 @@ impl DB { ), }; - let wal = WALConfig::builder() + let wal = WalConfig::builder() .file_nbit(header.wal_file_nbit) .block_nbit(header.wal_block_nbit) .max_revisions(cfg.wal.max_revisions) @@ -626,13 +626,13 @@ impl DB { ), }; - // recover from WAL + // recover from Wal disk_requester.init_wal("wal", db_path); // set up the storage layout - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DBHeader::MSIZE; + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += DbHeader::MSIZE; let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += CompactSpaceHeader::MSIZE; assert!(offset <= SPACE_RESERVED); @@ -650,7 +650,7 @@ impl DB { ); initializer.write( db_header.addr(), - &shale::to_dehydrated(&DBHeader::new_empty())?, + &shale::to_dehydrated(&DbHeader::new_empty())?, ); let initializer = Rc::::make_mut(&mut staging.blob.meta); initializer.write( @@ -667,7 +667,7 @@ impl DB { let blob_meta_ref = staging.blob.meta.as_ref(); ( - StoredView::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), StoredView::ptr_to_obj( merkle_meta_ref, merkle_payload_header, @@ -715,10 +715,10 @@ impl DB { })(); }) .unwrap(); - err.map_err(DBError::Merkle)? + err.map_err(DbError::Merkle)? } - let mut latest = DBRev { + let mut latest = DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), #[cfg(feature = "eth")] @@ -732,14 +732,14 @@ impl DB { }; Ok(Self { - inner: Arc::new(RwLock::new(DBInner { + inner: Arc::new(RwLock::new(DbInner { latest, disk_thread, disk_requester, staging, cached, })), - revisions: Arc::new(Mutex::new(DBRevInner { + revisions: Arc::new(Mutex::new(DbRevInner { inner: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, base, @@ -760,28 +760,28 @@ impl DB { } /// Dump the Trie of the latest generic key-value storage. - pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.inner.read().latest.kv_dump(w) } /// Get root hash of the latest generic key-value storage. - pub fn kv_root_hash(&self) -> Result { + pub fn kv_root_hash(&self) -> Result { self.inner.read().latest.kv_root_hash() } /// Get a value in the kv store associated with a particular key. - pub fn kv_get>(&self, key: K) -> Result, DBError> { + pub fn kv_get>(&self, key: K) -> Result, DbError> { self.inner .read() .latest .kv_get(key) - .ok_or(DBError::KeyNotFound) + .ok_or(DbError::KeyNotFound) } /// Get a handle that grants the access to any committed state of the entire DB. /// /// The latest revision (nback) starts from 0, which is the current state. /// If nback equals is above the configured maximum number of revisions, this function returns None. /// Returns `None` if `nback` is greater than the configured maximum amount of revisions. - pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { + pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { let mut revisions = self.revisions.lock(); let inner = self.inner.read(); @@ -814,11 +814,11 @@ impl DB { } // set up the storage layout - let mut offset = std::mem::size_of::() as u64; - // DBHeader starts after DBParams in merkle meta space - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DBHeader::MSIZE; - // Merkle CompactHeader starts after DBHeader in merkle meta space + let mut offset = std::mem::size_of::() as u64; + // DbHeader starts after DbParams in merkle meta space + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += DbHeader::MSIZE; + // Merkle CompactHeader starts after DbHeader in merkle meta space let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += CompactSpaceHeader::MSIZE; assert!(offset <= SPACE_RESERVED); @@ -837,7 +837,7 @@ impl DB { let blob_meta_ref = &space.blob.meta; ( - StoredView::ptr_to_obj(merkle_meta_ref, db_header, DBHeader::MSIZE).unwrap(), + StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), StoredView::ptr_to_obj( merkle_meta_ref, merkle_payload_header, @@ -874,7 +874,7 @@ impl DB { ) .unwrap(); Some(Revision { - rev: DBRev { + rev: DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), #[cfg(feature = "eth")] @@ -884,56 +884,56 @@ impl DB { } } #[cfg(feature = "eth")] -impl DB { +impl Db { /// Dump the Trie of the latest entire account model storage. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.inner.read().latest.dump(w) } /// Dump the Trie of the latest state storage under an account. - pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DBError> { + pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DbError> { self.inner.read().latest.dump_account(key, w) } /// Get root hash of the latest world state of all accounts. - pub fn root_hash(&self) -> Result { + pub fn root_hash(&self) -> Result { self.inner.read().latest.root_hash() } /// Get the latest balance of the account. - pub fn get_balance>(&self, key: K) -> Result { + pub fn get_balance>(&self, key: K) -> Result { self.inner.read().latest.get_balance(key) } /// Get the latest code of the account. - pub fn get_code>(&self, key: K) -> Result, DBError> { + pub fn get_code>(&self, key: K) -> Result, DbError> { self.inner.read().latest.get_code(key) } /// Get the latest nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { + pub fn get_nonce>(&self, key: K) -> Result { self.inner.read().latest.get_nonce(key) } /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DBError> { + pub fn get_state>(&self, key: K, sub_key: K) -> Result, DbError> { self.inner.read().latest.get_state(key, sub_key) } /// Check if the account exists in the latest world state. - pub fn exist>(&self, key: K) -> Result { + pub fn exist>(&self, key: K) -> Result { self.inner.read().latest.exist(key) } } /// Lock protected handle to a readable version of the DB. pub struct Revision { - rev: DBRev, + rev: DbRev, } impl std::ops::Deref for Revision { - type Target = DBRev; - fn deref(&self) -> &DBRev { + type Target = DbRev; + fn deref(&self) -> &DbRev { &self.rev } } @@ -942,15 +942,15 @@ impl std::ops::Deref for Revision { /// because when an error occurs, the write batch will be automatically aborted so that the DB /// remains clean. pub struct WriteBatch { - m: Arc>, - r: Arc>, + m: Arc>, + r: Arc>, root_hash_recalc: bool, committed: bool, } impl WriteBatch { /// Insert an item to the generic key-value storage. - pub fn kv_insert>(self, key: K, val: Vec) -> Result { + pub fn kv_insert>(self, key: K, val: Vec) -> Result { let mut rev = self.m.write(); #[cfg(feature = "eth")] let (header, merkle, _) = rev.latest.borrow_split(); @@ -958,14 +958,14 @@ impl WriteBatch { let (header, merkle) = rev.latest.borrow_split(); merkle .insert(key, val, header.kv_root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; drop(rev); Ok(self) } /// Remove an item from the generic key-value storage. `val` will be set to the value that is /// removed from the storage if it exists. - pub fn kv_remove>(self, key: K) -> Result<(Self, Option>), DBError> { + pub fn kv_remove>(self, key: K) -> Result<(Self, Option>), DbError> { let mut rev = self.m.write(); #[cfg(feature = "eth")] let (header, merkle, _) = rev.latest.borrow_split(); @@ -973,7 +973,7 @@ impl WriteBatch { let (header, merkle) = rev.latest.borrow_split(); let old_value = merkle .remove(key, header.kv_root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; drop(rev); Ok((self, old_value)) } @@ -1099,8 +1099,8 @@ impl WriteBatch { fn change_account( &mut self, key: &[u8], - modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DBError>, - ) -> Result<(), DBError> { + modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DbError>, + ) -> Result<(), DbError> { let mut rev = self.m.write(); let (header, merkle, blob) = rev.latest.borrow_split(); match merkle.get_mut(key, header.acc_root) { @@ -1115,7 +1115,7 @@ impl WriteBatch { } *b = acc.serialize(); }) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; ret?; } Ok(None) => { @@ -1123,15 +1123,15 @@ impl WriteBatch { modify(&mut acc, blob)?; merkle .insert(key, acc.serialize(), header.acc_root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; } - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), } Ok(()) } /// Set balance of the account. - pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result { + pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result { self.change_account(key, |acc, _| { acc.balance = balance; Ok(()) @@ -1140,17 +1140,17 @@ impl WriteBatch { } /// Set code of the account. - pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result { + pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result { use sha3::Digest; self.change_account(key, |acc, blob_stash| { if !acc.code.is_null() { - blob_stash.free_blob(acc.code).map_err(DBError::Blob)?; + blob_stash.free_blob(acc.code).map_err(DbError::Blob)?; } acc.set_code( Hash(sha3::Keccak256::digest(code).into()), blob_stash .new_blob(Blob::Code(code.to_vec())) - .map_err(DBError::Blob)? + .map_err(DbError::Blob)? .as_ptr(), ); Ok(()) @@ -1159,7 +1159,7 @@ impl WriteBatch { } /// Set nonce of the account. - pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result { + pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result { self.change_account(key, |acc, _| { acc.nonce = nonce; Ok(()) @@ -1168,38 +1168,38 @@ impl WriteBatch { } /// Set the state value indexed by `sub_key` in the account indexed by `key`. - pub fn set_state(self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { + pub fn set_state(self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { let mut rev = self.m.write(); let (header, merkle, _) = rev.latest.borrow_split(); let mut acc = match merkle.get(key, header.acc_root) { Ok(Some(r)) => Account::deserialize(&r), Ok(None) => Account::default(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }; if acc.root.is_null() { - Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DBError::Merkle)?; + Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DbError::Merkle)?; } merkle .insert(sub_key, val, acc.root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; acc.root_hash = merkle .root_hash::(acc.root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; merkle .insert(key, acc.serialize(), header.acc_root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; drop(rev); Ok(self) } /// Create an account. - pub fn create_account(self, key: &[u8]) -> Result { + pub fn create_account(self, key: &[u8]) -> Result { let mut rev = self.m.write(); let (header, merkle, _) = rev.latest.borrow_split(); let old_balance = match merkle.get_mut(key, header.acc_root) { Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance, Ok(None) => U256::zero(), - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }; let acc = Account { balance: old_balance, @@ -1207,14 +1207,14 @@ impl WriteBatch { }; merkle .insert(key, acc.serialize(), header.acc_root) - .map_err(DBError::Merkle)?; + .map_err(DbError::Merkle)?; drop(rev); Ok(self) } /// Delete an account. - pub fn delete_account(self, key: &[u8], acc: &mut Option) -> Result { + pub fn delete_account(self, key: &[u8], acc: &mut Option) -> Result { let mut rev = self.m.write(); let (header, merkle, blob_stash) = rev.latest.borrow_split(); let mut a = match merkle.remove(key, header.acc_root) { @@ -1224,14 +1224,14 @@ impl WriteBatch { drop(rev); return Ok(self); } - Err(e) => return Err(DBError::Merkle(e)), + Err(e) => return Err(DbError::Merkle(e)), }; if !a.root.is_null() { - merkle.remove_tree(a.root).map_err(DBError::Merkle)?; + merkle.remove_tree(a.root).map_err(DbError::Merkle)?; a.root = ObjPtr::null(); } if !a.code.is_null() { - blob_stash.free_blob(a.code).map_err(DBError::Blob)?; + blob_stash.free_blob(a.code).map_err(DbError::Blob)?; a.code = ObjPtr::null(); } *acc = Some(a); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index f94e2073980a..6df91db334fe 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -4,7 +4,7 @@ #[cfg(feature = "eth")] use crate::account::BlobError; -use crate::db::DBError; +use crate::db::DbError; use crate::merkle::*; use crate::merkle_util::*; @@ -59,21 +59,21 @@ impl From for ProofError { } } -impl From for ProofError { - fn from(d: DBError) -> ProofError { +impl From for ProofError { + fn from(d: DbError) -> ProofError { match d { - DBError::InvalidParams => ProofError::InvalidProof, - DBError::Merkle(e) => ProofError::InvalidNode(e), + DbError::InvalidParams => ProofError::InvalidProof, + DbError::Merkle(e) => ProofError::InvalidNode(e), #[cfg(feature = "eth")] - DBError::Blob(e) => ProofError::BlobStoreError(e), - DBError::System(e) => ProofError::SystemError(e), - DBError::KeyNotFound => ProofError::InvalidEdgeKeys, - DBError::CreateError => ProofError::NoSuchNode, + DbError::Blob(e) => ProofError::BlobStoreError(e), + DbError::System(e) => ProofError::SystemError(e), + DbError::KeyNotFound => ProofError::InvalidEdgeKeys, + DbError::CreateError => ProofError::NoSuchNode, // TODO: fix better by adding a new error to ProofError - DBError::IO(e) => { + DbError::IO(e) => { ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) } - DBError::Shale(e) => ProofError::Shale(e), + DbError::Shale(e) => ProofError::Shale(e), } } } diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs index 5ac923646a11..73af6ee7d43c 100644 --- a/firewood/src/sender.rs +++ b/firewood/src/sender.rs @@ -3,12 +3,10 @@ use crate::api::DB; -pub struct Sender { - -} +pub struct Sender {} impl, V: AsRef<[u8]>> DB for Sender { - fn kv_root_hash(&self) -> Result { + fn kv_root_hash(&self) -> Result { todo!() } @@ -16,35 +14,35 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } - fn kv_dump(&self, writer: W) -> Result<(), crate::db::DBError> { + fn kv_dump(&self, writer: W) -> Result<(), crate::db::DbError> { todo!() } - fn root_hash(&self) -> Result { + fn root_hash(&self) -> Result { todo!() } - fn dump(&self, writer: W) -> Result<(), crate::db::DBError> { + fn dump(&self, writer: W) -> Result<(), crate::db::DbError> { todo!() } #[cfg(feature = "eth")] - fn get_account(&self, key: K) -> Result { + fn get_account(&self, key: K) -> Result { todo!() } #[cfg(feature = "eth")] - fn dump_account(&self, key: K, writer: W) -> Result<(), crate::db::DBError> { + fn dump_account(&self, key: K, writer: W) -> Result<(), crate::db::DbError> { todo!() } #[cfg(feature = "eth")] - fn get_balance(&self, key: K) -> Result { + fn get_balance(&self, key: K) -> Result { todo!() } #[cfg(feature = "eth")] - fn get_code(&self, key: K) -> Result, crate::db::DBError> { + fn get_code(&self, key: K) -> Result, crate::db::DbError> { todo!() } @@ -66,16 +64,16 @@ impl, V: AsRef<[u8]>> DB for Sender { } #[cfg(feature = "eth")] - fn get_nonce(&self, key: K) -> Result { + fn get_nonce(&self, key: K) -> Result { todo!() } #[cfg(feature = "eth")] - fn get_state(&self, key: K, sub_key: K) -> Result, crate::db::DBError> { + fn get_state(&self, key: K, sub_key: K) -> Result, crate::db::DbError> { todo!() } - fn exist(&self, key: K) -> Result { + fn exist(&self, key: K) -> Result { todo!() } -} \ No newline at end of file +} diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index 68b950ac701a..a3b06eda88e5 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -12,10 +12,10 @@ use std::{path::Path, thread}; use tokio::sync::{mpsc, oneshot}; use crate::api::Revision; -use crate::db::DBRevConfig; +use crate::db::DbRevConfig; use crate::{ api, - db::{DBConfig, DBError}, + db::{DbConfig, DbError}, merkle, }; use async_trait::async_trait; @@ -44,7 +44,7 @@ impl Drop for Connection { impl Connection { #[allow(dead_code)] - fn new>(path: P, cfg: DBConfig) -> Self { + fn new>(path: P, cfg: DbConfig) -> Self { let (sender, receiver) = mpsc::channel(1_000) as ( tokio::sync::mpsc::Sender, @@ -68,7 +68,7 @@ impl api::WriteBatch for BatchHandle { self, key: K, val: V, - ) -> Result { + ) -> Result { let (send, recv) = oneshot::channel(); let _ = self .sender @@ -81,14 +81,14 @@ impl api::WriteBatch for BatchHandle { .await; return match recv.await { Ok(_) => Ok(self), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } async fn kv_remove + Send + Sync>( self, key: K, - ) -> Result<(Self, Option>), DBError> { + ) -> Result<(Self, Option>), DbError> { let (send, recv) = oneshot::channel(); let _ = self .sender @@ -101,7 +101,7 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(v)) => Ok((self, v)), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } @@ -110,7 +110,7 @@ impl api::WriteBatch for BatchHandle { self, key: K, balance: primitive_types::U256, - ) -> Result { + ) -> Result { let (send, recv) = oneshot::channel(); let _ = self .sender @@ -124,12 +124,12 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(_)) => Ok(self), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } #[cfg(feature = "eth")] - async fn set_code(self, key: K, code: V) -> Result + async fn set_code(self, key: K, code: V) -> Result where K: AsRef<[u8]> + Send + Sync, V: AsRef<[u8]> + Send + Sync, @@ -147,7 +147,7 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(_)) => Ok(self), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } @@ -156,7 +156,7 @@ impl api::WriteBatch for BatchHandle { self, key: K, nonce: u64, - ) -> Result { + ) -> Result { let (send, recv) = oneshot::channel(); let _ = self .sender @@ -170,12 +170,12 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(_)) => Ok(self), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } #[cfg(feature = "eth")] - async fn set_state(self, key: K, sub_key: SK, state: S) -> Result + async fn set_state(self, key: K, sub_key: SK, state: S) -> Result where K: AsRef<[u8]> + Send + Sync, SK: AsRef<[u8]> + Send + Sync, @@ -195,11 +195,11 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(_)) => Ok(self), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } #[cfg(feature = "eth")] - async fn create_account + Send + Sync>(self, key: K) -> Result { + async fn create_account + Send + Sync>(self, key: K) -> Result { let (send, recv) = oneshot::channel(); let _ = self .sender @@ -212,7 +212,7 @@ impl api::WriteBatch for BatchHandle { return match recv.await { Ok(Ok(_)) => Ok(self), Ok(Err(e)) => Err(e), - Err(_e) => Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } @@ -221,7 +221,7 @@ impl api::WriteBatch for BatchHandle { self, _key: K, _acc: &mut Option, - ) -> Result { + ) -> Result { todo!() } @@ -249,7 +249,7 @@ impl api::WriteBatch for BatchHandle { .await; return match recv.await { Ok(_) => (), - Err(_e) => (), // Err(DBError::InvalidParams), // TODO: need a special error for comm failures + Err(_e) => (), // Err(DbError::InvalidParams), // TODO: need a special error for comm failures }; } } @@ -265,7 +265,7 @@ impl super::RevisionHandle { #[async_trait] impl Revision for super::RevisionHandle { - async fn kv_root_hash(&self) -> Result { + async fn kv_root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::RootHash { handle: self.id, @@ -275,7 +275,7 @@ impl Revision for super::RevisionHandle { recv.await.expect("Actor task has been killed") } - async fn kv_get + Send + Sync>(&self, key: K) -> Result, DBError> { + async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError> { let (send, recv) = oneshot::channel(); let _ = Request::RevRequest(RevRequest::Get { handle: self.id, @@ -311,7 +311,7 @@ impl Revision for super::RevisionHandle { ) { todo!() } - async fn root_hash(&self) -> Result { + async fn root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::RootHash { handle: self.id, @@ -321,7 +321,7 @@ impl Revision for super::RevisionHandle { recv.await.expect("channel failed") } - async fn dump(&self, _writer: W) -> Result<(), DBError> { + async fn dump(&self, _writer: W) -> Result<(), DbError> { todo!() } @@ -330,11 +330,11 @@ impl Revision for super::RevisionHandle { &self, _key: K, _writer: W, - ) -> Result<(), DBError> { + ) -> Result<(), DbError> { todo!() } - async fn kv_dump(&self, _writer: W) -> Result<(), DBError> { + async fn kv_dump(&self, _writer: W) -> Result<(), DbError> { unimplemented!(); } @@ -342,12 +342,12 @@ impl Revision for super::RevisionHandle { async fn get_balance + Send + Sync>( &self, _key: K, - ) -> Result { + ) -> Result { todo!() } #[cfg(feature = "eth")] - async fn get_code + Send + Sync>(&self, _key: K) -> Result, DBError> { + async fn get_code + Send + Sync>(&self, _key: K) -> Result, DbError> { todo!() } @@ -355,7 +355,7 @@ impl Revision for super::RevisionHandle { async fn get_nonce + Send + Sync>( &self, _key: K, - ) -> Result { + ) -> Result { todo!() } @@ -364,13 +364,13 @@ impl Revision for super::RevisionHandle { &self, _key: K, _sub_key: K, - ) -> Result, DBError> { + ) -> Result, DbError> { todo!() } } #[async_trait] -impl crate::api::DB for Connection +impl crate::api::Db for Connection where tokio::sync::mpsc::Sender: From>, { @@ -391,7 +391,7 @@ where } } - async fn get_revision(&self, nback: usize, cfg: Option) -> Option { + async fn get_revision(&self, nback: usize, cfg: Option) -> Option { let (send, recv) = oneshot::channel(); let msg = Request::NewRevision { nback, @@ -414,7 +414,7 @@ where #[cfg(test)] mod test { - use crate::{api::WriteBatch, api::DB, db::WALConfig}; + use crate::{api::Db, api::WriteBatch, db::WalConfig}; use std::path::PathBuf; use super::*; @@ -457,9 +457,9 @@ mod test { ); } - fn db_config() -> DBConfig { - DBConfig::builder() - .wal(WALConfig::builder().max_revisions(10).build()) + fn db_config() -> DbConfig { + DbConfig::builder() + .wal(WalConfig::builder().max_revisions(10).build()) .truncate(true) .build() } diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index e2fdd71ec855..e9ea1ee12f55 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -4,7 +4,7 @@ use tokio::sync::{mpsc, oneshot}; use crate::{ - db::{DBError, DBRevConfig}, + db::{DbError, DbRevConfig}, merkle, }; @@ -34,7 +34,7 @@ pub enum Request { }, NewRevision { nback: usize, - cfg: Option, + cfg: Option, respond_to: oneshot::Sender>, }, @@ -51,38 +51,38 @@ pub enum BatchRequest { KvRemove { handle: BatchId, key: OwnedKey, - respond_to: oneshot::Sender>, DBError>>, + respond_to: oneshot::Sender>, DbError>>, }, KvInsert { handle: BatchId, key: OwnedKey, val: OwnedKey, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, Commit { handle: BatchId, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, #[cfg(feature = "eth")] SetBalance { handle: BatchId, key: OwnedKey, balance: primitive_types::U256, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, #[cfg(feature = "eth")] SetCode { handle: BatchId, key: OwnedKey, code: OwnedVal, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, #[cfg(feature = "eth")] SetNonce { handle: BatchId, key: OwnedKey, nonce: u64, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, #[cfg(feature = "eth")] SetState { @@ -90,13 +90,13 @@ pub enum BatchRequest { key: OwnedKey, sub_key: OwnedVal, state: OwnedVal, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, #[cfg(feature = "eth")] CreateAccount { handle: BatchId, key: OwnedKey, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, NoRootHash { handle: BatchId, @@ -109,7 +109,7 @@ pub enum RevRequest { Get { handle: RevId, key: OwnedKey, - respond_to: oneshot::Sender, DBError>>, + respond_to: oneshot::Sender, DbError>>, }, #[cfg(feature = "proof")] Prove { @@ -119,7 +119,7 @@ pub enum RevRequest { }, RootHash { handle: RevId, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, Drop { handle: RevId, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 6a6f07abf544..4e46583343fd 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -8,14 +8,14 @@ use std::{ }; use tokio::sync::mpsc::Receiver; -use crate::db::{DBConfig, DBError, DB}; +use crate::db::{Db, DbConfig, DbError}; use super::{BatchId, BatchRequest, Request, RevId}; macro_rules! get_batch { ($active_batch: expr, $handle: ident, $lastid: ident, $respond_to: expr) => {{ if $handle != $lastid.load(Ordering::Relaxed) - 1 || $active_batch.is_none() { - let _ = $respond_to.send(Err(DBError::InvalidParams)); + let _ = $respond_to.send(Err(DbError::InvalidParams)); continue; } $active_batch.take().unwrap() @@ -26,7 +26,7 @@ macro_rules! get_rev { ($rev: ident, $handle: ident, $out: expr) => { match $rev.get(&$handle) { None => { - let _ = $out.send(Err(DBError::InvalidParams)); + let _ = $out.send(Err(DbError::InvalidParams)); continue; } Some(x) => x, @@ -37,8 +37,8 @@ macro_rules! get_rev { pub struct FirewoodService {} impl FirewoodService { - pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DBConfig) -> Self { - let db = DB::new(owned_path, &cfg).unwrap(); + pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DbConfig) -> Self { + let db = Db::new(owned_path, &cfg).unwrap(); let mut active_batch: Option = None; let mut revs = HashMap::::new(); let lastid = AtomicU32::new(0); @@ -77,7 +77,7 @@ impl FirewoodService { } => { let rev = get_rev!(revs, handle, respond_to); let msg = rev.kv_get(key); - let _ = respond_to.send(msg.map_or(Err(DBError::KeyNotFound), Ok)); + let _ = respond_to.send(msg.map_or(Err(DbError::KeyNotFound), Ok)); } #[cfg(feature = "proof")] super::RevRequest::Prove { diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index fdca8457b2f4..702038063876 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -6,15 +6,15 @@ use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; use super::{ - Ash, AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WALConfig, + Ash, AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT, }; -use aiofut::{AIOBuilder, AIOError, AIOManager}; +use aiofut::{AioBuilder, AioError, AioManager}; use growthring::{ - wal::{RecoverPolicy, WALLoader, WALWriter}, - walerror::WALError, - WALStoreAIO, + wal::{RecoverPolicy, WalLoader, WalWriter}, + walerror::WalError, + WalStoreAio, }; use shale::SpaceID; use tokio::sync::oneshot::error::RecvError; @@ -23,8 +23,8 @@ use typed_builder::TypedBuilder; #[derive(Debug)] pub enum BufferCmd { - /// Initialize the WAL. - InitWAL(PathBuf, String), + /// Initialize the Wal. + InitWal(PathBuf, String), /// Process a write batch against the underlying store. WriteBatch(Vec, AshRecord), /// Get a page from the disk buffer. @@ -54,13 +54,13 @@ pub struct DiskBufferConfig { /// Maximum number of async I/O requests per submission. #[builder(default = 128)] pub max_aio_submit: usize, - /// Maximum number of concurrent async I/O requests in WAL. + /// Maximum number of concurrent async I/O requests in Wal. #[builder(default = 256)] pub wal_max_aio_requests: usize, - /// Maximum buffered WAL records. + /// Maximum buffered Wal records. #[builder(default = 1024)] pub wal_max_buffered: usize, - /// Maximum batched WAL records per write. + /// Maximum batched Wal records per write. #[builder(default = 4096)] pub wal_max_batch: usize, } @@ -88,13 +88,13 @@ pub struct DiskBuffer { fc_notifier: Option>, fc_blocker: Option>, file_pools: [Option>; 255], - aiomgr: AIOManager, + aiomgr: AioManager, local_pool: Rc, task_id: u64, tasks: Rc>>>>, - wal: Option>>>, + wal: Option>>>, cfg: DiskBufferConfig, - wal_cfg: WALConfig, + wal_cfg: WalConfig, } impl DiskBuffer { @@ -102,14 +102,14 @@ impl DiskBuffer { pub fn new( inbound: mpsc::Receiver, cfg: &DiskBufferConfig, - wal: &WALConfig, - ) -> Result { - let aiomgr = AIOBuilder::default() + wal: &WalConfig, + ) -> Result { + let aiomgr = AioBuilder::default() .max_events(cfg.max_aio_requests) .max_nwait(cfg.max_aio_response) .max_nbatched(cfg.max_aio_submit) .build() - .map_err(|_| AIOError::OtherError)?; + .map_err(|_| AioError::OtherError)?; Ok(Self { pending: HashMap::new(), @@ -182,14 +182,14 @@ impl DiskBuffer { } } - /// Initialize the WAL subsystem if it does not exists and attempts to replay the WAL if exists. - async fn init_wal(&mut self, rootpath: PathBuf, waldir: String) -> Result<(), WALError> { + /// Initialize the Wal subsystem if it does not exists and attempts to replay the Wal if exists. + async fn init_wal(&mut self, rootpath: PathBuf, waldir: String) -> Result<(), WalError> { let final_path = rootpath.clone().join(waldir.clone()); - let mut aiobuilder = AIOBuilder::default(); + let mut aiobuilder = AioBuilder::default(); aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); let aiomgr = aiobuilder.build()?; - let store = WALStoreAIO::new(final_path.clone(), false, Some(aiomgr))?; - let mut loader = WALLoader::new(); + let store = WalStoreAio::new(final_path.clone(), false, Some(aiomgr))?; + let mut loader = WalLoader::new(); loader .file_nbit(self.wal_cfg.file_nbit) .block_nbit(self.wal_cfg.block_nbit) @@ -214,7 +214,7 @@ impl DiskBuffer { file_pool .get_file(fid) .map_err(|e| { - WALError::Other(format!( + WalError::Other(format!( "file pool error: {:?} - final path {:?}", e, final_path )) @@ -224,7 +224,7 @@ impl DiskBuffer { (offset & file_mask) as nix::libc::off_t, ) .map_err(|e| { - WALError::Other(format!( + WalError::Other(format!( "wal loader error: {:?} - final path {:?}", e, final_path )) @@ -259,12 +259,12 @@ impl DiskBuffer { break; } } - // first write to WAL + // first write to Wal let ring_ids: Vec<_> = futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) .await .into_iter() - .map(|ring| ring.map_err(|_| "WAL Error while writing").unwrap().1) + .map(|ring| ring.map_err(|_| "Wal Error while writing").unwrap().1) .collect(); let sem = Rc::new(tokio::sync::Semaphore::new(0)); let mut npermit = 0; @@ -303,7 +303,7 @@ impl DiskBuffer { .await .peel(ring_ids, max_revisions) .await - .map_err(|_| "WAL errore while pruning") + .map_err(|_| "Wal errore while pruning") .unwrap(); }); if self.pending.len() >= self.cfg.max_pending { @@ -321,12 +321,12 @@ impl DiskBuffer { ) -> bool { match req { BufferCmd::Shutdown => return false, - BufferCmd::InitWAL(root_path, waldir) => { + BufferCmd::InitWal(root_path, waldir) => { self.init_wal(root_path.clone(), waldir.clone()) .await .unwrap_or_else(|e| { panic!( - "Initialize WAL in dir {:?} failed creating {:?}: {e:?}", + "Initialize Wal in dir {:?} failed creating {:?}: {e:?}", root_path, waldir ) }); @@ -338,7 +338,7 @@ impl DiskBuffer { wal_in.send((writes, wal_writes)).await.unwrap(); } BufferCmd::CollectAsh(nrecords, tx) => { - // wait to ensure writes are paused for WAL + // wait to ensure writes are paused for Wal let ash = self .wal .as_ref() @@ -465,15 +465,15 @@ impl DiskBufferRequester { self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap() } - /// Initialize the WAL. + /// Initialize the Wal. pub fn init_wal(&self, waldir: &str, rootpath: PathBuf) { self.sender - .blocking_send(BufferCmd::InitWAL(rootpath, waldir.to_string())) + .blocking_send(BufferCmd::InitWal(rootpath, waldir.to_string())) .map_err(StoreError::Send) .ok(); } - /// Collect the last N records from the WAL. + /// Collect the last N records from the Wal. pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { let (resp_tx, resp_rx) = oneshot::channel(); self.sender @@ -515,7 +515,7 @@ mod tests { ]; let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); - let wal_cfg = WALConfig::builder().build(); + let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); // TODO: Run the test in a separate standalone directory for concurrency reasons @@ -582,7 +582,7 @@ mod tests { }); // wait for the write to complete. write_thread_handle.join().unwrap(); - // This is not ACID compliant, write should not return before WAL log + // This is not ACID compliant, write should not return before Wal log // is written to disk. assert!([ &tmpdb.into_iter().collect::(), @@ -597,7 +597,7 @@ mod tests { assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); } - fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WALConfig) -> DiskBufferRequester { + fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WalConfig) -> DiskBufferRequester { let (sender, inbound) = tokio::sync::mpsc::channel(buf_cfg.max_buffered); let disk_requester = DiskBufferRequester::new(sender); std::thread::spawn(move || { diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 90b16879d59d..0f86f801270b 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -82,7 +82,7 @@ impl Ash { pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { - fn serialize(&self) -> growthring::wal::WALBytes { + fn serialize(&self) -> growthring::wal::WalBytes { let mut bytes = Vec::new(); bytes.extend((self.0.len() as u64).to_le_bytes()); for (space_id, w) in self.0.iter() { @@ -101,7 +101,7 @@ impl growthring::wal::Record for AshRecord { impl AshRecord { #[allow(clippy::boxed_local)] - fn deserialize(raw: growthring::wal::WALBytes) -> Self { + fn deserialize(raw: growthring::wal::WalBytes) -> Self { let mut r = &raw[..]; let len = u64::from_le_bytes(r[..8].try_into().unwrap()); r = &r[8..]; @@ -853,8 +853,8 @@ impl Drop for FilePool { } #[derive(TypedBuilder, Clone, Debug)] -pub struct WALConfig { - #[builder(default = 22)] // 4MB WAL logs +pub struct WalConfig { + #[builder(default = 22)] // 4MB Wal logs pub file_nbit: u64, #[builder(default = 15)] // 32KB pub block_nbit: u64, diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 084718509b44..05bdbf37e94f 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,4 +1,4 @@ -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; use std::{collections::VecDeque, fs::remove_dir_all, path::Path}; macro_rules! kv_dump { @@ -12,7 +12,7 @@ macro_rules! kv_dump { #[test] fn test_revisions() { use rand::{rngs::StdRng, Rng, SeedableRng}; - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .meta_ncached_pages(1024) .meta_ncached_files(128) .payload_ncached_pages(1024) @@ -20,7 +20,7 @@ fn test_revisions() { .payload_file_nbit(16) .payload_regn_nbit(16) .wal( - WALConfig::builder() + WalConfig::builder() .file_nbit(15) .block_nbit(8) .max_revisions(10) @@ -45,7 +45,7 @@ fn test_revisions() { key }; for i in 0..10 { - let db = DB::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = Db::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); let mut dumped = VecDeque::new(); for _ in 0..10 { { @@ -73,7 +73,7 @@ fn test_revisions() { } } drop(db); - let db = DB::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); + let db = Db::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); for (j, _) in dumped.iter().enumerate().skip(1) { let rev = db.get_revision(j, None).unwrap(); let a = &kv_dump!(rev); @@ -89,7 +89,7 @@ fn test_revisions() { #[test] fn create_db_issue_proof() { - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .meta_ncached_pages(1024) .meta_ncached_files(128) .payload_ncached_pages(1024) @@ -97,14 +97,14 @@ fn create_db_issue_proof() { .payload_file_nbit(16) .payload_regn_nbit(16) .wal( - WALConfig::builder() + WalConfig::builder() .file_nbit(15) .block_nbit(8) .max_revisions(10) .build(), ); - let db = DB::new("test_db_proof", &cfg.truncate(true).build()).unwrap(); + let db = Db::new("test_db_proof", &cfg.truncate(true).build()).unwrap(); let mut wb = db.new_writebatch(); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 128d22cc7af5..07e1fa077ab4 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -3,7 +3,7 @@ use anyhow::{Error, Result}; use clap::{value_parser, Args}; -use firewood::db::{DBConfig, DBRevConfig, DiskBufferConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, DbRevConfig, DiskBufferConfig, WalConfig}; use log; #[derive(Args)] @@ -218,15 +218,15 @@ pub struct Options { long, required = false, default_value_t = 100, - value_name = "WAL_MAX_REVISIONS", + value_name = "Wal_MAX_REVISIONS", help = "Number of revisions to keep from the past. This preserves a rolling window of the past N commits to the database." )] max_revisions: u32, } -pub fn initialize_db_config(opts: &Options) -> DBConfig { - DBConfig { +pub fn initialize_db_config(opts: &Options) -> DbConfig { + DbConfig { meta_ncached_pages: opts.meta_ncached_pages, meta_ncached_files: opts.meta_ncached_files, meta_file_nbit: opts.meta_file_nbit, @@ -236,7 +236,7 @@ pub fn initialize_db_config(opts: &Options) -> DBConfig { payload_max_walk: opts.payload_max_walk, payload_regn_nbit: opts.payload_regn_nbit, truncate: opts.truncate, - rev: DBRevConfig { + rev: DbRevConfig { merkle_ncached_objs: opts.merkle_ncached_objs, blob_ncached_objs: opts.blob_ncached_objs, }, @@ -250,7 +250,7 @@ pub fn initialize_db_config(opts: &Options) -> DBConfig { wal_max_buffered: opts.wal_max_buffered, wal_max_batch: opts.wal_max_batch, }, - wal: WALConfig { + wal: WalConfig { file_nbit: opts.file_nbit, block_nbit: opts.block_nbit, max_revisions: opts.max_revisions, @@ -262,7 +262,7 @@ pub fn run(opts: &Options) -> Result<()> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - DB::new::<&str>(opts.name.as_ref(), &db_config).map_err(Error::msg)?; + Db::new::<&str>(opts.name.as_ref(), &db_config).map_err(Error::msg)?; println!("created firewood database in {:?}", opts.name); Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 53b3ba588449..1cbc76499f8b 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -3,7 +3,7 @@ use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; use log; #[derive(Debug, Args)] @@ -25,11 +25,11 @@ pub struct Options { pub fn run(opts: &Options) -> Result<()> { log::debug!("deleting key {:?}", opts); - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .truncate(false) - .wal(WALConfig::builder().max_revisions(10).build()); + .wal(WalConfig::builder().max_revisions(10).build()); - let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; db.new_writebatch() .kv_remove(&opts.key) .map_err(Error::msg)?; diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 8d1f81fb1b83..e1d690b4713c 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -3,7 +3,7 @@ use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; use log; #[derive(Debug, Args)] @@ -20,11 +20,11 @@ pub struct Options { pub fn run(opts: &Options) -> Result<()> { log::debug!("dump database {:?}", opts); - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .truncate(false) - .wal(WALConfig::builder().max_revisions(10).build()); + .wal(WalConfig::builder().max_revisions(10).build()); - let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; db.kv_dump(&mut std::io::stdout().lock()) .map_err(Error::msg) } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index f9817bcecaf1..ae314ad3d637 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, bail, Error, Result}; use clap::Args; -use firewood::db::{DBConfig, DBError, WALConfig, DB}; +use firewood::db::{Db, DbConfig, DbError, WalConfig}; use log; use std::str; @@ -26,11 +26,11 @@ pub struct Options { pub fn run(opts: &Options) -> Result<()> { log::debug!("get key value pair {:?}", opts); - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .truncate(false) - .wal(WALConfig::builder().max_revisions(10).build()); + .wal(WalConfig::builder().max_revisions(10).build()); - let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; match db.kv_get(opts.key.as_bytes()) { Ok(val) => { @@ -44,7 +44,7 @@ pub fn run(opts: &Options) -> Result<()> { } Ok(()) } - Err(DBError::KeyNotFound) => bail!("key not found"), + Err(DbError::KeyNotFound) => bail!("key not found"), Err(e) => bail!(e), } } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index eabf1f6f9589..28677411deb3 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Error, Result}; use clap::Args; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; use log; #[derive(Debug, Args)] @@ -29,11 +29,11 @@ pub struct Options { pub fn run(opts: &Options) -> Result<()> { log::debug!("inserting key value pair {:?}", opts); - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .truncate(false) - .wal(WALConfig::builder().max_revisions(10).build()); + .wal(WalConfig::builder().max_revisions(10).build()); - let db = match DB::new(opts.db.as_str(), &cfg.build()) { + let db = match Db::new(opts.db.as_str(), &cfg.build()) { Ok(db) => db, Err(_) => return Err(anyhow!("error opening database")), }; diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 69bbe776726d..1d76ea6c48fe 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -3,7 +3,7 @@ use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{DBConfig, WALConfig, DB}; +use firewood::db::{Db, DbConfig, WalConfig}; use log; use std::str; @@ -22,11 +22,11 @@ pub struct Options { pub fn run(opts: &Options) -> Result<()> { log::debug!("root hash {:?}", opts); - let cfg = DBConfig::builder() + let cfg = DbConfig::builder() .truncate(false) - .wal(WALConfig::builder().max_revisions(10).build()); + .wal(WalConfig::builder().max_revisions(10).build()); - let db = DB::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; let root = db.kv_root_hash().map_err(Error::msg)?; println!("{:X?}", *root); diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index c9812b90d3b2..7bc51923a2c6 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -243,7 +243,7 @@ mod tmpdb { } } lazy_static! { - static ref DBPATH: DbPath = { + static ref DB_PATH: DbPath = { let path = [ TARGET_TMP_DIR.unwrap_or(&std::env::var("TMPDIR").unwrap_or("/tmp".to_string())), FIREWOOD_TEST_DB_NAME, @@ -255,6 +255,6 @@ mod tmpdb { } pub fn path() -> &'static DbPath { - &DBPATH + &DB_PATH } } From 92cfab7a513d08855ebf0a2d3ceb805ea4f4a08d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 18 May 2023 14:32:54 -0700 Subject: [PATCH 0147/1053] Code cleanup: unused but constructed if expression (#86) --- firewood/src/merkle.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 68a2b7b297ea..17a1b82bdfd1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -211,28 +211,27 @@ impl BranchNode { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); if c_ref.get_eth_rlp_long::(store) { - let s = stream.append(&&(*c_ref.get_root_hash::(store))[..]); + stream.append(&&(*c_ref.get_root_hash::(store))[..]); if c_ref.lazy_dirty.get() { c_ref.write(|_| {}).unwrap(); c_ref.lazy_dirty.set(false) } - s } else { let c_rlp = &c_ref.get_eth_rlp::(store); - stream.append_raw(c_rlp, 1) + stream.append_raw(c_rlp, 1); } } None => { // Check if there is already a calculated rlp for the child, which // can happen when manually constructing a trie from proof. if self.chd_eth_rlp[i].is_none() { - stream.append_empty_data() + stream.append_empty_data(); } else { let v = self.chd_eth_rlp[i].clone().unwrap(); if v.len() == HASH_SIZE { - stream.append(&v) + stream.append(&v); } else { - stream.append_raw(&v, 1) + stream.append_raw(&v, 1); } } } From e04da8cec70aa5061186fc6ac86acb1c7c5bd388 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 18 May 2023 17:59:21 -0400 Subject: [PATCH 0148/1053] Turn to_nibbles into an extension method (#80) --- firewood/src/merkle.rs | 48 ++++++++++++++++++++++++------------------ firewood/src/proof.rs | 29 ++++++++++++++++++------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 17a1b82bdfd1..d53b46379bc0 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -599,15 +599,18 @@ impl Storable for Node { let path_len = buff[0] as u64; cur.read_exact(&mut buff)?; let ptr = u64::from_le_bytes(buff); - let nibbles: Vec<_> = to_nibbles( - &mem.get_view(addr + META_SIZE + ext_header_size, path_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size, - size: path_len, - })? - .as_deref(), - ) - .collect(); + + let nibbles: Vec = mem + .get_view(addr + META_SIZE + ext_header_size, path_len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size, + size: path_len, + })? + .as_deref() + .into_iter() + .flat_map(to_nibble_array) + .collect(); + let (path, _) = PartialPath::decode(nibbles); let mut buff = [0_u8; 1]; @@ -659,8 +662,14 @@ impl Storable for Node { offset: addr + META_SIZE + leaf_header_size, size: path_len + data_len, })?; - let nibbles: Vec<_> = - to_nibbles(&remainder.as_deref()[..path_len as usize]).collect(); + + let nibbles: Vec<_> = remainder + .as_deref() + .into_iter() + .take(path_len as usize) + .flat_map(to_nibble_array) + .collect(); + let (path, _) = PartialPath::decode(nibbles); let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); Ok(Self::new_from_hash( @@ -1108,7 +1117,7 @@ impl Merkle { // TODO: Explain why this always starts with a 0 chunk // I think this may have to do with avoiding moving the root let mut chunked_key = vec![0]; - chunked_key.extend(to_nibbles(key.as_ref())); + chunked_key.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut next_node = Some(self.get_node(root)?); let mut nskip = 0; @@ -1573,7 +1582,7 @@ impl Merkle { root: ObjPtr, ) -> Result>, MerkleError> { let mut chunks = vec![0]; - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); if root.is_null() { return Ok(None); @@ -1697,7 +1706,7 @@ impl Merkle { root: ObjPtr, ) -> Result, MerkleError> { let mut chunks = vec![0]; - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut parents = Vec::new(); if root.is_null() { @@ -1772,7 +1781,7 @@ impl Merkle { T: ValueTransformer, { let mut chunks = Vec::new(); - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut proofs: HashMap<[u8; HASH_SIZE], Vec> = HashMap::new(); if root.is_null() { @@ -1852,7 +1861,7 @@ impl Merkle { root: ObjPtr, ) -> Result, MerkleError> { let mut chunks = vec![0]; - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); if root.is_null() { return Ok(None); @@ -1987,10 +1996,9 @@ impl ValueTransformer for IdTrans { } } -// given a set of bytes, return a new iterator that returns a set of // nibbles, high bits first, then low bits -pub fn to_nibbles(bytes: &[u8]) -> impl Iterator + '_ { - bytes.iter().flat_map(|b| [b >> 4, b & 0xf]) +pub fn to_nibble_array(x: u8) -> [u8; 2] { + [x >> 4, x & 0b_0000_1111] } // given a set of nibbles, take each pair and convert this back into bytes @@ -2028,7 +2036,7 @@ mod test { (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]), ] { - let n: Vec<_> = to_nibbles(&bytes).collect(); + let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); assert_eq!(n, nibbles); } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 6df91db334fe..5dbaf3129405 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -130,7 +130,7 @@ impl Proof { root_hash: [u8; 32], ) -> Result>, ProofError> { let mut chunks = Vec::new(); - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut cur_key: &[u8] = &chunks; let mut cur_hash = root_hash; @@ -171,8 +171,14 @@ impl Proof { let size = rlp.item_count().unwrap(); match size { EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = - to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let cur_key_path: Vec<_> = rlp + .at(0) + .unwrap() + .as_val::>() + .unwrap() + .into_iter() + .flat_map(to_nibble_array) + .collect(); let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); @@ -377,7 +383,7 @@ impl Proof { let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; let mut chunks = Vec::new(); - chunks.extend(to_nibbles(key.as_ref())); + chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut cur_key: &[u8] = &chunks; let mut cur_hash = root_hash; @@ -570,8 +576,15 @@ impl Proof { let size = rlp.item_count().unwrap(); match size { EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = - to_nibbles(&rlp.at(0).unwrap().as_val::>().unwrap()).collect(); + let cur_key_path: Vec<_> = rlp + .at(0) + .unwrap() + .as_val::>() + .unwrap() + .into_iter() + .flat_map(to_nibble_array) + .collect(); + let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); @@ -695,10 +708,10 @@ fn unset_internal>( ) -> Result { // Add the sentinel root let mut left_chunks = vec![0]; - left_chunks.extend(to_nibbles(left.as_ref())); + left_chunks.extend(left.as_ref().iter().copied().flat_map(to_nibble_array)); // Add the sentinel root let mut right_chunks = vec![0]; - right_chunks.extend(to_nibbles(right.as_ref())); + right_chunks.extend(right.as_ref().iter().copied().flat_map(to_nibble_array)); let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; From 2db03af7861f88066dd226eebf698fa001f310d3 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 23 May 2023 12:54:48 -0400 Subject: [PATCH 0149/1053] Cleanup firewood-proof --- firewood/src/proof.rs | 179 ++++++++++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 68 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 5dbaf3129405..2e12fdd19504 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -78,6 +78,12 @@ impl From for ProofError { } } +impl From for ProofError { + fn from(_: rlp::DecoderError) -> ProofError { + ProofError::DecodeError + } +} + impl fmt::Display for ProofError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -391,13 +397,17 @@ impl Proof { let mut key_index = 0; let mut branch_index: u8 = 0; let mut iter = 0; + loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. - let (mut chd_ptr, sub_proof, size) = - self.decode_node(merkle, cur_key, cur_proof, false)?; + let (chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof, false)?; + + // I thinkn it's impossible for chd_ptr to be None... + let mut chd_ptr = Some(chd_ptr); + // Link the child to the parent based on the node type. match &u_ref.inner() { NodeType::Branch(n) => { @@ -467,7 +477,10 @@ impl Proof { let proof = proofs_map .get(&p.hash.unwrap()) .ok_or(ProofError::ProofNodeMissing)?; - (chd_ptr, _, _) = self.decode_node(merkle, cur_key, proof, true)?; + let (decoded_chd_ptr, _, _) = + self.decode_node(merkle, cur_key, proof, true)?; + + chd_ptr = Some(decoded_chd_ptr); // Link the child to the parent based on the node type. match &u_ref.inner() { @@ -564,23 +577,21 @@ impl Proof { /// /// * `end_node` - A boolean indicates whether this is the end node to decode, thus no `key` /// to be present. - #[allow(clippy::type_complexity)] fn decode_node( &self, merkle: &Merkle, key: &[u8], buf: &[u8], end_node: bool, - ) -> Result<(Option>, Option, usize), ProofError> { + ) -> Result<(ObjPtr, Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); - let size = rlp.item_count().unwrap(); + let size = rlp.item_count()?; + match size { EXT_NODE_SIZE => { let cur_key_path: Vec<_> = rlp - .at(0) - .unwrap() - .as_val::>() - .unwrap() + .at(0)? + .as_val::>()? .into_iter() .flat_map(to_nibble_array) .collect(); @@ -588,106 +599,138 @@ impl Proof { let (cur_key_path, term) = PartialPath::decode(cur_key_path); let cur_key = cur_key_path.into_inner(); - let rlp = rlp.at(1).unwrap(); + let rlp = rlp.at(1)?; + let data = if rlp.is_data() { - rlp.as_val::>().unwrap() + rlp.as_val::>()? } else { rlp.as_raw().to_vec() }; - let ext_ptr: Option>; - let subproof: Option; - if term { - ext_ptr = Some( - merkle - .new_node(Node::new(NodeType::Leaf(LeafNode::new( - cur_key.clone(), - data.clone(), - )))) - .map_err(|_| ProofError::DecodeError)? - .as_ptr(), - ); - subproof = Some(SubProof { - rlp: data, - hash: None, - }); - } else { - ext_ptr = Some( - merkle - .new_node(Node::new(NodeType::Extension(ExtNode::new( - cur_key.clone(), - ObjPtr::null(), - Some(data.clone()), - )))) - .map_err(|_| ProofError::DecodeError)? - .as_ptr(), - ); - subproof = self.generate_subproof(data)?; - } - // Check if the key of current node match with the given key. if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { + let ext_ptr = get_ext_ptr(merkle, term, Data(data), CurKey(cur_key))?; + return Ok((ext_ptr, None, 0)); } - Ok((ext_ptr, subproof, cur_key.len())) + + let subproof = if term { + Some(SubProof { + rlp: data.clone(), + hash: None, + }) + } else { + self.generate_subproof(data.clone())? + }; + + let cur_key_len = cur_key.len(); + + let ext_ptr = get_ext_ptr(merkle, term, Data(data), CurKey(cur_key))?; + + Ok((ext_ptr, subproof, cur_key_len)) } + BRANCH_NODE_SIZE => { + let data_rlp = rlp.at(NBRANCH)?; + // Extract the value of the branch node. - let mut value: Option> = None; - let data_rlp = rlp.at(NBRANCH).unwrap(); // Skip if rlp is empty data - if !data_rlp.is_empty() { + let value = if !data_rlp.is_empty() { let data = if data_rlp.is_data() { data_rlp.as_val::>().unwrap() } else { data_rlp.as_raw().to_vec() }; - value = Some(data); - } + + Some(data) + } else { + None + }; // Record rlp values of all children. let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - #[allow(clippy::needless_range_loop)] - for i in 0..NBRANCH { - let rlp = rlp.at(i).unwrap(); - // Skip if rlp is empty data - if !rlp.is_empty() { - let data = if rlp.is_data() { - rlp.as_val::>().unwrap() + + for (i, chd) in rlp.into_iter().take(NBRANCH).enumerate() { + if !chd.is_empty() { + // Skip if chd is empty data + let data = if chd.is_data() { + chd.as_val()? } else { - rlp.as_raw().to_vec() + chd.as_raw().to_vec() }; + chd_eth_rlp[i] = Some(data); } } - let chd = [None; NBRANCH]; - let t = NodeType::Branch(BranchNode::new(chd, value, chd_eth_rlp.clone())); - let branch_ptr = merkle - .new_node(Node::new(t)) - .map_err(|_| ProofError::ProofNodeMissing)?; + // If the node is the last one to be decoded, then no subproof to be extracted. if end_node { - return Ok((Some(branch_ptr.as_ptr()), None, 1)); - } else if key.is_empty() { + let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; + + return Ok((branch_ptr, None, 1)); + } + + if key.is_empty() { return Err(ProofError::NoSuchNode); } // Check if the subproof with the given key exist. let index = key[0] as usize; - let data: Vec = if chd_eth_rlp[index].is_none() { - return Ok((Some(branch_ptr.as_ptr()), None, 1)); - } else { - chd_eth_rlp[index].clone().unwrap() + + let Some(data) = chd_eth_rlp[index].clone() else { + let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; + + return Ok((branch_ptr, None, 1)); }; + + let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; let subproof = self.generate_subproof(data)?; - Ok((Some(branch_ptr.as_ptr()), subproof, 1)) + + Ok((branch_ptr, subproof, 1)) } + // RLP length can only be the two cases above. _ => Err(ProofError::DecodeError), } } } +struct CurKey(Vec); +struct Data(Vec); + +fn get_ext_ptr( + merkle: &Merkle, + term: bool, + Data(data): Data, + CurKey(cur_key): CurKey, +) -> Result, ProofError> { + let node = if term { + NodeType::Leaf(LeafNode::new(cur_key, data)) + } else { + NodeType::Extension(ExtNode::new(cur_key, ObjPtr::null(), Some(data))) + }; + + merkle + .new_node(Node::new(node)) + .map(|node| node.as_ptr()) + .map_err(|_| ProofError::DecodeError) +} + +fn build_branch_ptr( + merkle: &Merkle, + value: Option>, + chd_eth_rlp: [Option>; NBRANCH], +) -> Result, ProofError> { + let node = BranchNode::new([None; NBRANCH], value, chd_eth_rlp); + let node = NodeType::Branch(node); + let node = Node::new(node); + + merkle + .new_node(node) + .map_err(|_| ProofError::ProofNodeMissing) + .map(|node| node.as_ptr()) +} + // unset_internal removes all internal node references. // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. From 1aa5dcbf9b1934a2c33d09a46865accc80489289 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 24 May 2023 11:31:50 -0400 Subject: [PATCH 0150/1053] Remove unused imports (#89) --- firewood-growth-ring/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index 9a7dbd0ed352..e434e1ea1fba 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -53,10 +53,10 @@ pub mod walerror; use async_trait::async_trait; use firewood_libaio::{AioBuilder, AioManager}; use libc::off_t; +#[cfg(not(target_os = "linux"))] +use nix::fcntl::OFlag; #[cfg(target_os = "linux")] use nix::fcntl::{fallocate, FallocateFlags, OFlag}; -#[cfg(not(target_os = "linux"))] -use nix::fcntl::{open, openat, OFlag}; use nix::unistd::{close, ftruncate}; use std::fs; use std::os::fd::IntoRawFd; From 265b30422b44238be31296c7a1a5ccae6688d5c7 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 25 May 2023 13:42:19 -0400 Subject: [PATCH 0151/1053] Update CODEOWNERS to reflect active reviewers (#94) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 8abad2cf0466..975ac914b87b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @exdx @xinifinity @gyuho @hexfusion @rkuris @patrick-ogrady @StephenButtolph @richardpringle +* @xinifinity @rkuris @richardpringle From 64d2fac20613db9224bd4fa4da2af8d28d406b4f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 25 May 2023 14:35:59 -0400 Subject: [PATCH 0152/1053] Fallback to posix_fallocate (#90) --- firewood-growth-ring/src/lib.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index e434e1ea1fba..4857c85fba2d 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -55,9 +55,12 @@ use firewood_libaio::{AioBuilder, AioManager}; use libc::off_t; #[cfg(not(target_os = "linux"))] use nix::fcntl::OFlag; -#[cfg(target_os = "linux")] -use nix::fcntl::{fallocate, FallocateFlags, OFlag}; use nix::unistd::{close, ftruncate}; +#[cfg(target_os = "linux")] +use nix::{ + errno::Errno, + fcntl::{fallocate, posix_fallocate, FallocateFlags, OFlag}, +}; use std::fs; use std::os::fd::IntoRawFd; use std::os::unix::io::RawFd; @@ -99,16 +102,24 @@ impl Drop for WalFileAio { impl WalFile for WalFileAio { #[cfg(target_os = "linux")] async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { + let (offset, length) = (offset as off_t, length as off_t); // TODO: is there any async version of fallocate? - return fallocate( + fallocate( self.fd, FallocateFlags::FALLOC_FL_ZERO_RANGE, - offset as off_t, - length as off_t, + offset, + length, ) - .map(|_| ()) - .map_err(Into::into); + .or_else(|err| match err { + Errno::EOPNOTSUPP => posix_fallocate(self.fd, offset, length), + _ => { + eprintln!("fallocate failed with error: {err:?}"); + Err(err) + } + }) + .map_err(Into::into) } + #[cfg(not(target_os = "linux"))] // TODO: macos support is possible here, but possibly unnecessary async fn allocate(&self, _offset: WalPos, _length: usize) -> Result<(), WalError> { From 5e33f708bc62668e51b155949536095e91a1dcb4 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 25 May 2023 19:00:34 -0700 Subject: [PATCH 0153/1053] Update `Ash` with undo and redo (#92) --- firewood/src/db.rs | 10 ++--- firewood/src/storage/buffer.rs | 12 +++--- firewood/src/storage/mod.rs | 72 +++++++++++++++++++++------------- 3 files changed, 56 insertions(+), 38 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 0b267c7b94c9..15f88f3563cb 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -794,7 +794,7 @@ impl Db { let ashes = inner.disk_requester.collect_ash(nback).ok().unwrap(); for mut ash in ashes.into_iter().skip(rlen) { for (_, a) in ash.0.iter_mut() { - a.old.reverse() + a.undo.reverse() } let u = match revisions.inner.back() { @@ -802,10 +802,10 @@ impl Db { None => inner.cached.to_mem_store_r(), }; revisions.inner.push_back(u.rewind( - &ash.0[&MERKLE_META_SPACE].old, - &ash.0[&MERKLE_PAYLOAD_SPACE].old, - &ash.0[&BLOB_META_SPACE].old, - &ash.0[&BLOB_PAYLOAD_SPACE].old, + &ash.0[&MERKLE_META_SPACE].undo, + &ash.0[&MERKLE_PAYLOAD_SPACE].undo, + &ash.0[&BLOB_META_SPACE].undo, + &ash.0[&BLOB_PAYLOAD_SPACE].undo, )); } } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 702038063876..46eae2f36c55 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; use super::{ - Ash, AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WalConfig, + AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT, }; @@ -203,9 +203,9 @@ impl DiskBuffer { store, |raw, _| { let batch = AshRecord::deserialize(raw); - for (space_id, Ash { old, new }) in batch.0 { - for (old, data) in old.into_iter().zip(new.into_iter()) { - let offset = old.offset; + for (space_id, ash) in batch.0 { + for (undo, redo) in ash.iter() { + let offset = undo.offset; let file_pool = self.file_pools[space_id as usize].as_ref().unwrap(); let file_nbit = file_pool.get_file_nbit(); let file_mask = (1 << file_nbit) - 1; @@ -220,7 +220,7 @@ impl DiskBuffer { )) })? .get_fd(), - &data, + &redo.data, (offset & file_mask) as nix::libc::off_t, ) .map_err(|e| { @@ -501,7 +501,7 @@ mod tests { use super::*; use crate::{ file, - storage::{DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta}, + storage::{Ash, DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta}, }; use shale::CachedStore; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 0f86f801270b..5010037c0450 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -64,18 +64,25 @@ pub struct SpaceWrite { } #[derive(Debug)] +/// In memory representation of Write-ahead log with `undo` and `redo`. pub struct Ash { - pub old: Vec, - pub new: Vec>, + /// Deltas to undo the changes. + pub undo: Vec, + /// Deltas to replay the changes. + pub redo: Vec, } impl Ash { fn new() -> Self { Self { - old: Vec::new(), - new: Vec::new(), + undo: Vec::new(), + redo: Vec::new(), } } + + fn iter(&self) -> impl Iterator { + self.undo.iter().zip(self.redo.iter()) + } } #[derive(Debug)] @@ -87,12 +94,19 @@ impl growthring::wal::Record for AshRecord { bytes.extend((self.0.len() as u64).to_le_bytes()); for (space_id, w) in self.0.iter() { bytes.extend((*space_id).to_le_bytes()); - bytes.extend((w.old.len() as u32).to_le_bytes()); - for (sw_old, sw_new) in w.old.iter().zip(w.new.iter()) { - bytes.extend(sw_old.offset.to_le_bytes()); - bytes.extend((sw_old.data.len() as u64).to_le_bytes()); - bytes.extend(&*sw_old.data); - bytes.extend(&**sw_new); + bytes.extend( + (u32::try_from(w.undo.len()).expect("size of undo shoud be a `u32`")).to_le_bytes(), + ); + + for (sw_undo, sw_redo) in w.iter() { + bytes.extend(sw_undo.offset.to_le_bytes()); + bytes.extend( + (u64::try_from(sw_undo.data.len()) + .expect("length of undo data shoud be a `u64`")) + .to_le_bytes(), + ); + bytes.extend(&*sw_undo.data); + bytes.extend(&*sw_redo.data); } } bytes.into() @@ -110,23 +124,27 @@ impl AshRecord { let space_id = u8::from_le_bytes(r[..1].try_into().unwrap()); let wlen = u32::from_le_bytes(r[1..5].try_into().unwrap()); r = &r[5..]; - let mut old = Vec::new(); - let mut new = Vec::new(); + let mut undo = Vec::new(); + let mut redo = Vec::new(); for _ in 0..wlen { let offset = u64::from_le_bytes(r[..8].try_into().unwrap()); let data_len = u64::from_le_bytes(r[8..16].try_into().unwrap()); r = &r[16..]; - let old_write = SpaceWrite { + let undo_write = SpaceWrite { offset, data: r[..data_len as usize].into(), }; r = &r[data_len as usize..]; - let new_data: Box<[u8]> = r[..data_len as usize].into(); + // let new_data: Box<[u8]> = r[..data_len as usize].into(); + let redo_write = SpaceWrite { + offset, + data: r[..data_len as usize].into(), + }; r = &r[data_len as usize..]; - old.push(old_write); - new.push(new_data); + undo.push(undo_write); + redo.push(redo_write); } - (space_id, Ash { old, new }) + (space_id, Ash { undo, redo }) }) .collect(); Self(writes) @@ -513,37 +531,37 @@ impl CachedStore for StoreRevMut { let s_off = (offset & PAGE_MASK) as usize; let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; - let mut old: Vec = Vec::new(); - let new: Box<[u8]> = change.into(); + let mut undo: Vec = Vec::new(); + let data: Box<[u8]> = change.into(); if s_pid == e_pid { let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1]; - old.extend(&*slice); + undo.extend(&*slice); slice.copy_from_slice(change) } else { let len = PAGE_SIZE as usize - s_off; { let slice = &mut self.get_page_mut(s_pid)[s_off..]; - old.extend(&*slice); + undo.extend(&*slice); slice.copy_from_slice(&change[..len]); } change = &change[len..]; for p in s_pid + 1..e_pid { let mut slice = self.get_page_mut(p); - old.extend(&*slice); + undo.extend(&*slice); slice.copy_from_slice(&change[..PAGE_SIZE as usize]); change = &change[PAGE_SIZE as usize..]; } let slice = &mut self.get_page_mut(e_pid)[..e_off + 1]; - old.extend(&*slice); + undo.extend(&*slice); slice.copy_from_slice(change); } let plain = &mut self.deltas.borrow_mut().plain; - assert!(old.len() == new.len()); - plain.old.push(SpaceWrite { + assert!(undo.len() == data.len()); + plain.undo.push(SpaceWrite { offset, - data: old.into(), + data: undo.into(), }); - plain.new.push(new); + plain.redo.push(SpaceWrite { offset, data }); } fn id(&self) -> SpaceID { From 95bbc5d50b322605c9b97a5de65da8121a302a88 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 26 May 2023 11:55:30 -0400 Subject: [PATCH 0154/1053] no need for option (#82) --- firewood/src/proof.rs | 83 ++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 53 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 2e12fdd19504..218351c27154 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -396,17 +396,14 @@ impl Proof { let proofs_map = &self.0; let mut key_index = 0; let mut branch_index: u8 = 0; - let mut iter = 0; loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. - let (chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof, false)?; - - // I thinkn it's impossible for chd_ptr to be None... - let mut chd_ptr = Some(chd_ptr); + let (mut chd_ptr, sub_proof, size) = + self.decode_node(merkle, cur_key, cur_proof, false)?; // Link the child to the parent based on the node type. match &u_ref.inner() { @@ -414,14 +411,14 @@ impl Proof { match n.chd()[branch_index as usize] { // If the child already resolved, then use the existing node. Some(node) => { - chd_ptr = Some(node); + chd_ptr = node; } None => { // insert the leaf to the empty slot u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index as usize] = chd_ptr; + uu.chd_mut()[branch_index as usize] = Some(chd_ptr); }) .unwrap(); } @@ -434,36 +431,24 @@ impl Proof { u_ref .write(|u| { let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = if let Some(chd_p) = chd_ptr { - chd_p - } else { - ObjPtr::null() - } + *uu.chd_mut() = chd_ptr; }) .unwrap(); } else { - chd_ptr = Some(node); + chd_ptr = node; } } // We should not hit a leaf node as a parent. _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; - if chd_ptr.is_some() { - u_ref = merkle - .get_node(chd_ptr.unwrap()) - .map_err(|_| ProofError::DecodeError)?; - // If the new parent is a branch node, record the index to correctly link the next child to it. - if u_ref.inner().as_branch().is_some() { - branch_index = chunks[key_index]; - } - } else { - // Root node must be included in the proof. - if iter == 0 { - return Err(ProofError::ProofNodeMissing); - } + u_ref = merkle + .get_node(chd_ptr) + .map_err(|_| ProofError::DecodeError)?; + // If the new parent is a branch node, record the index to correctly link the next child to it. + if u_ref.inner().as_branch().is_some() { + branch_index = chunks[key_index]; } - iter += 1; key_index += size; match sub_proof { @@ -477,10 +462,8 @@ impl Proof { let proof = proofs_map .get(&p.hash.unwrap()) .ok_or(ProofError::ProofNodeMissing)?; - let (decoded_chd_ptr, _, _) = - self.decode_node(merkle, cur_key, proof, true)?; - chd_ptr = Some(decoded_chd_ptr); + chd_ptr = self.decode_node(merkle, cur_key, proof, true)?.0; // Link the child to the parent based on the node type. match &u_ref.inner() { @@ -493,7 +476,8 @@ impl Proof { u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index as usize] = chd_ptr; + uu.chd_mut()[branch_index as usize] = + Some(chd_ptr); }) .unwrap(); } @@ -506,11 +490,7 @@ impl Proof { u_ref .write(|u| { let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = if let Some(chd_p) = chd_ptr { - chd_p - } else { - ObjPtr::null() - } + *uu.chd_mut() = chd_ptr; }) .unwrap(); } @@ -524,26 +504,23 @@ impl Proof { }; } drop(u_ref); - if chd_ptr.is_some() { - let c_ref = merkle - .get_node(chd_ptr.unwrap()) - .map_err(|_| ProofError::DecodeError)?; - match &c_ref.inner() { - NodeType::Branch(n) => { - if let Some(v) = n.value() { - data = Some(v.deref().to_vec()); - } + let c_ref = merkle + .get_node(chd_ptr) + .map_err(|_| ProofError::DecodeError)?; + match &c_ref.inner() { + NodeType::Branch(n) => { + if let Some(v) = n.value() { + data = Some(v.deref().to_vec()); } - NodeType::Leaf(n) => { - // Return the value on the node only when the key matches exactly - // (e.g. the length path of subproof node is 0). - if p.hash.is_none() || (p.hash.is_some() && n.path().len() == 0) - { - data = Some(n.data().deref().to_vec()); - } + } + NodeType::Leaf(n) => { + // Return the value on the node only when the key matches exactly + // (e.g. the length path of subproof node is 0). + if p.hash.is_none() || (p.hash.is_some() && n.path().len() == 0) { + data = Some(n.data().deref().to_vec()); } - _ => (), } + _ => (), } return Ok(data); } From 289ea8dfe9e3af91676c728b9de7dd488cac0254 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 26 May 2023 12:35:57 -0400 Subject: [PATCH 0155/1053] Return a `Result<(), ObjWriteError>` for `Obj::write` and `ObjRef::write` instead of `Option<()>` (#83) --- compose-dev.yaml | 12 +++++ firewood-shale/src/compact.rs | 90 +++++++++++++++++++++-------------- firewood-shale/src/lib.rs | 24 ++++++++-- firewood/src/merkle.rs | 10 ++-- 4 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 compose-dev.yaml diff --git a/compose-dev.yaml b/compose-dev.yaml new file mode 100644 index 000000000000..a92f7012bbd8 --- /dev/null +++ b/compose-dev.yaml @@ -0,0 +1,12 @@ +services: + app: + entrypoint: + - sleep + - infinity + image: docker/dev-environments-default:stable-1 + init: true + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index 7adc26ebe892..268b797025ac 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -324,15 +324,20 @@ impl CompactSpaceInner { fn del_desc(&mut self, desc_addr: ObjPtr) -> Result<(), ShaleError> { let desc_size = CompactDescriptor::MSIZE; debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); - self.header.meta_space_tail.write(|r| **r -= desc_size); + self.header + .meta_space_tail + .write(|r| **r -= desc_size) + .unwrap(); + if desc_addr.addr != **self.header.meta_space_tail { let desc_last = self.get_descriptor(ObjPtr::new_from_addr(**self.header.meta_space_tail))?; let mut desc = self.get_descriptor(ObjPtr::new_from_addr(desc_addr.addr))?; - desc.write(|r| *r = *desc_last); + desc.write(|r| *r = *desc_last).unwrap(); let mut header = self.get_header(ObjPtr::new(desc.haddr))?; - header.write(|h| h.desc_addr = desc_addr); + header.write(|h| h.desc_addr = desc_addr).unwrap(); } + Ok(()) } @@ -340,7 +345,9 @@ impl CompactSpaceInner { let addr = **self.header.meta_space_tail; self.header .meta_space_tail - .write(|r| **r += CompactDescriptor::MSIZE); + .write(|r| **r += CompactDescriptor::MSIZE) + .unwrap(); + Ok(ObjPtr::new_from_addr(addr)) } @@ -404,7 +411,8 @@ impl CompactSpaceInner { desc.write(|d| { d.payload_size = payload_size; d.haddr = h; - }); + }) + .unwrap(); } let mut h = self.get_header(ObjPtr::new(h))?; let mut f = self.get_footer(ObjPtr::new(f))?; @@ -412,8 +420,10 @@ impl CompactSpaceInner { h.payload_size = payload_size; h.is_freed = true; h.desc_addr = desc_addr; - }); - f.write(|f| f.payload_size = payload_size); + }) + .unwrap(); + f.write(|f| f.payload_size = payload_size).unwrap(); + Ok(()) } @@ -447,7 +457,7 @@ impl CompactSpaceInner { let mut header = self.get_header(ObjPtr::new(desc_haddr))?; assert_eq!(header.payload_size, desc_payload_size); assert!(header.is_freed); - header.write(|h| h.is_freed = false); + header.write(|h| h.is_freed = false).unwrap(); } self.del_desc(ptr)?; true @@ -457,15 +467,17 @@ impl CompactSpaceInner { let mut lheader = self.get_header(ObjPtr::new(desc_haddr))?; assert_eq!(lheader.payload_size, desc_payload_size); assert!(lheader.is_freed); - lheader.write(|h| { - h.is_freed = false; - h.payload_size = length; - }); + lheader + .write(|h| { + h.is_freed = false; + h.payload_size = length; + }) + .unwrap(); } { let mut lfooter = self.get_footer(ObjPtr::new(desc_haddr + hsize + length))?; //assert!(lfooter.payload_size == desc_payload_size); - lfooter.write(|f| f.payload_size = length); + lfooter.write(|f| f.payload_size = length).unwrap(); } let offset = desc_haddr + hsize + length + fsize; @@ -473,23 +485,27 @@ impl CompactSpaceInner { let rdesc_addr = self.new_desc()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; - rdesc.write(|rd| { - rd.payload_size = rpayload_size; - rd.haddr = offset; - }); + rdesc + .write(|rd| { + rd.payload_size = rpayload_size; + rd.haddr = offset; + }) + .unwrap(); } { let mut rheader = self.get_header(ObjPtr::new(offset))?; - rheader.write(|rh| { - rh.is_freed = true; - rh.payload_size = rpayload_size; - rh.desc_addr = rdesc_addr; - }); + rheader + .write(|rh| { + rh.is_freed = true; + rh.payload_size = rpayload_size; + rh.desc_addr = rdesc_addr; + }) + .unwrap(); } { let mut rfooter = self.get_footer(ObjPtr::new(offset + hsize + rpayload_size))?; - rfooter.write(|f| f.payload_size = rpayload_size); + rfooter.write(|f| f.payload_size = rpayload_size).unwrap(); } self.del_desc(ptr)?; true @@ -497,7 +513,7 @@ impl CompactSpaceInner { false }; if exit { - self.header.alloc_addr.write(|r| *r = ptr); + self.header.alloc_addr.write(|r| *r = ptr).unwrap(); res = Some(desc_haddr + hsize); break; } @@ -516,23 +532,27 @@ impl CompactSpaceInner { let regn_size = 1 << self.regn_nbit; let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; let mut offset = **self.header.compact_space_tail; - self.header.compact_space_tail.write(|r| { - // an item is always fully in one region - let rem = regn_size - (offset & (regn_size - 1)); - if rem < total_length { - offset += rem; - **r += rem; - } - **r += total_length - }); + self.header + .compact_space_tail + .write(|r| { + // an item is always fully in one region + let rem = regn_size - (offset & (regn_size - 1)); + if rem < total_length { + offset += rem; + **r += rem; + } + **r += total_length + }) + .unwrap(); let mut h = self.get_header(ObjPtr::new(offset))?; let mut f = self.get_footer(ObjPtr::new(offset + CompactHeader::MSIZE + length))?; h.write(|h| { h.payload_size = length; h.is_freed = false; h.desc_addr = ObjPtr::new(0); - }); - f.write(|f| f.payload_size = length); + }) + .unwrap(); + f.write(|f| f.payload_size = length).unwrap(); Ok(offset + CompactHeader::MSIZE) } } diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index 5a895cb876ae..374a12a3f889 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -34,6 +34,13 @@ pub enum ShaleError { Io(#[from] std::io::Error), } +// TODO: +// this could probably included with ShaleError, +// but keeping it separate for now as Obj/ObjRef might change in the near future +#[derive(Debug, Error)] +#[error("write error")] +pub struct ObjWriteError; + pub type SpaceID = u8; pub const INVALID_SPACE_ID: SpaceID = 0xff; @@ -183,11 +190,16 @@ impl Obj { impl Obj { /// Write to the underlying object. Returns `Some(())` on success. #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Option<()> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { modify(self.value.write()); + // if `estimate_mem_image` gives overflow, the object will not be written - self.dirty = Some(self.value.estimate_mem_image()?); - Some(()) + self.dirty = match self.value.estimate_mem_image() { + Some(len) => Some(len), + None => return Err(ObjWriteError), + }; + + Ok(()) } #[inline(always)] @@ -244,11 +256,13 @@ impl<'a, T> ObjRef<'a, T> { } #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Option<()> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { let inner = self.inner.as_mut().unwrap(); inner.write(modify)?; + self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); - Some(()) + + Ok(()) } } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d53b46379bc0..bf4848bc95b7 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -792,7 +792,7 @@ impl Storable for Node { macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { - if let None = $r.write($modify) { + if let Err(_) = $r.write($modify) { let ptr = $self.new_node($r.clone())?.as_ptr(); $self.set_parent(ptr, $parents); $deleted.push($r.as_ptr()); @@ -1014,7 +1014,7 @@ impl Merkle { Some(Data(val)); b.rehash() }) - .is_none() + .is_err() { u.1 = self.new_node(b_ref.clone())?.as_ptr(); deleted.push(b_ref.as_ptr()); @@ -1407,7 +1407,7 @@ impl Merkle { .insert(0, idx); c.rehash() }) - .is_none() + .is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() @@ -1527,7 +1527,7 @@ impl Merkle { .insert(0, idx); c.rehash() }) - .is_none() + .is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() @@ -1559,7 +1559,7 @@ impl Merkle { *path0 = PartialPath(path); c.rehash() }) - .is_none() + .is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() From 3bd127853b03803a77cf346341442b65166e9c29 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 26 May 2023 17:02:02 -0400 Subject: [PATCH 0156/1053] test files in tmp (#95) --- firewood-growth-ring/src/lib.rs | 10 +++++----- firewood-libaio/src/lib.rs | 3 ++- firewood-libaio/tests/simple_test.rs | 9 +++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index 4857c85fba2d..e2fc50cf733a 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -10,7 +10,7 @@ //! //! //! // Start with empty WAL (truncate = true). -//! let store = WalStoreAio::new("./walfiles", true, None).unwrap(); +//! let store = WalStoreAio::new("/tmp/walfiles", true, None).unwrap(); //! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); //! // Write a vector of records to WAL. //! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { @@ -20,7 +20,7 @@ //! //! //! // Load from WAL (truncate = false). -//! let store = WalStoreAio::new("./walfiles", false, None).unwrap(); +//! let store = WalStoreAio::new("/tmp/walfiles", false, None).unwrap(); //! let mut wal = block_on(loader.load(store, |payload, ringid| { //! // redo the operations in your application //! println!("recover(payload={}, ringid={:?})", @@ -35,14 +35,14 @@ //! // Then assume all these records are not longer needed. We can tell WalWriter by the `peel` //! // method. //! block_on(wal.peel(ring_ids, 0)).unwrap(); -//! // There will only be one remaining file in ./walfiles. +//! // There will only be one remaining file in /tmp/walfiles. //! -//! let store = WalStoreAio::new("./walfiles", false, None).unwrap(); +//! let store = WalStoreAio::new("/tmp/walfiles", false, None).unwrap(); //! let wal = block_on(loader.load(store, |payload, _| { //! println!("payload.len() = {}", payload.len()); //! Ok(()) //! }, 0)).unwrap(); -//! // After each recovery, the ./walfiles is empty. +//! // After each recovery, the /tmp/walfiles is empty. //! ``` #[macro_use] diff --git a/firewood-libaio/src/lib.rs b/firewood-libaio/src/lib.rs index 01b22c0bfd74..37ce82def47c 100644 --- a/firewood-libaio/src/lib.rs +++ b/firewood-libaio/src/lib.rs @@ -14,7 +14,7 @@ //! .write(true) //! .create(true) //! .truncate(true) -//! .open("test") +//! .open("/tmp/test") //! .unwrap(); //! let fd = file.as_raw_fd(); //! // keep all returned futures in a vector @@ -32,6 +32,7 @@ //! spawner.spawn_local(h).unwrap(); //! } //! pool.run(); +//! # std::fs::remove_file("/tmp/test").ok(); //! ``` mod abi; diff --git a/firewood-libaio/tests/simple_test.rs b/firewood-libaio/tests/simple_test.rs index 4fdaab0d126c..3a11c5435c23 100644 --- a/firewood-libaio/tests/simple_test.rs +++ b/firewood-libaio/tests/simple_test.rs @@ -3,6 +3,11 @@ use futures::executor::LocalPool; use futures::future::FutureExt; use futures::task::LocalSpawnExt; use std::os::unix::io::AsRawFd; +use std::path::Path; + +fn tmp_dir() -> &'static Path { + Path::new(option_env!("CARGO_TARGET_TMPDIR").unwrap_or("/tmp")) +} #[test] fn simple1() { @@ -12,7 +17,7 @@ fn simple1() { .write(true) .create(true) .truncate(true) - .open("test") + .open(tmp_dir().join("test")) .unwrap(); let fd = file.as_raw_fd(); let ws = vec![(0, "hello"), (5, "world"), (2, "xxxx")] @@ -38,7 +43,7 @@ fn simple2() { .write(true) .create(true) .truncate(true) - .open("test2") + .open(tmp_dir().join("test2")) .unwrap(); let fd = file.as_raw_fd(); let ws = (0..4000) From ffd2b6a8a119acf7744395ce251fb74aa2ea8b13 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 29 May 2023 10:26:47 -0700 Subject: [PATCH 0157/1053] Update milestones (#100) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8b98d930ace8..69de29c29cf7 100644 --- a/README.md +++ b/README.md @@ -102,14 +102,18 @@ are uniquely identified by root hashes. operations do not see any changes. - [ ] Be able to read-your-write in a batch that is not committed. Uncommitted changes will not be shown to any other concurrent readers. +- [ ] Add some metrics framework to support timings and volume for future milestones ### Seasoned milestone This milestone will add support for proposals, including proposed future branches, with a cache to make committing these branches efficiently. +- [ ] Be able to support multiple proposed revisions against latest committed +version. - [ ] Be able to propose a batch against the existing committed revision, or propose a batch against any existing proposed revision. - [ ] Be able to quickly commit a batch that has been proposed. Note that this invalidates all other proposals that are not children of the committed proposed batch. +- [ ] Add metric reporting ### Dried milestone The focus of this milestone will be to support synchronization to other From 759abcada8f3ae25eccd536e2dae5939b6048053 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Mon, 29 May 2023 15:30:20 -0700 Subject: [PATCH 0158/1053] Verify pin a specific revision (#99) --- firewood/examples/rev.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 8a54a7066576..4a8207ffee74 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -44,9 +44,8 @@ fn main() { println!("{revision_root_hash:?}"); // Get a revision while a batch is active. - let revision_root_hash = db - .get_revision(1, None) - .expect("revision-1 should exist") + let revision = db.get_revision(1, None).expect("revision-1 should exist"); + let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-1 should exist"); println!("{revision_root_hash:?}"); @@ -71,9 +70,18 @@ fn main() { .kv_root_hash() .expect("root-hash for revision-1 should exist"); assert_ne!(revision_root_hash, new_revision_root_hash); - let val = db.kv_get("k").unwrap(); assert_eq!("v".as_bytes().to_vec(), val); + + // When reading a specific revision, after new commits the revision remains consistent. + let val = revision.kv_get("k"); + assert_eq!(None, val); + let val = revision.kv_get("dof").unwrap(); + assert_eq!("verb".as_bytes().to_vec(), val); + let actual_revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-2 should exist"); + assert_eq!(revision_root_hash, actual_revision_root_hash); } { let db = From 2f18f33de9544e4e68fb736126c745b9011d6219 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 11:15:14 -0400 Subject: [PATCH 0159/1053] build(deps): update criterion requirement from 0.4.0 to 0.5.1 (#96) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood-shale/Cargo.toml | 2 +- firewood/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 2fe3af6a882a..53092af1e707 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -13,7 +13,7 @@ lru = "0.10.0" thiserror = "1.0.38" [dev-dependencies] -criterion = { version = "0.4.0", features = ["html_reports"] } +criterion = { version = "0.5.1", features = ["html_reports"] } pprof = { version = "0.11.1", features = ["flamegraph"] } sha3 = "0.10.7" rand = "0.8.5" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 71877816b6c1..7d14ce91954e 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -38,7 +38,7 @@ tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } typed-builder = "0.14.0" [dev-dependencies] -criterion = "0.4.0" +criterion = "0.5.1" keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" From 17fdb5ee0a1f3f9ff2d3e2314878ee2f05109a72 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 30 May 2023 11:52:21 -0700 Subject: [PATCH 0160/1053] Mark resolved green milestone items done (#103) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69de29c29cf7..b68b5d8d45d3 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ reader and writer interfaces to have consistent read/write semantics. - [ ] Concurrent readers of pinned revisions while allowing additional batches to commit, to support parallel reads for the past consistent states. The revisions are uniquely identified by root hashes. -- [ ] Pin a reader to a specific revision, so that future commits or other +- [x] Pin a reader to a specific revision, so that future commits or other operations do not see any changes. -- [ ] Be able to read-your-write in a batch that is not committed. Uncommitted +- [x] Be able to read-your-write in a batch that is not committed. Uncommitted changes will not be shown to any other concurrent readers. - [ ] Add some metrics framework to support timings and volume for future milestones From 3d4d3899742eba6de35ccb4df6887ec762e846a1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 30 May 2023 14:28:27 -0700 Subject: [PATCH 0161/1053] Clippy fixes (#102) --- firewood-growth-ring/src/wal.rs | 2 +- firewood-growth-ring/tests/common/mod.rs | 19 +++---- firewood-shale/src/compact.rs | 2 +- firewood/benches/hashops.rs | 2 +- firewood/src/merkle.rs | 2 +- firewood/src/storage/mod.rs | 8 +-- firewood/tests/merkle.rs | 71 ++++++++++++------------ 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index c49823ae59c5..0925c3074281 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -608,7 +608,7 @@ impl WalWriter { .file_pool .write(writes) .into_iter() - .map(move |f| async move { f.await }.shared()) + .map(move |f| f.shared()) .collect(); res.into_iter() diff --git a/firewood-growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs index 7adae7f064fb..64e7404fa5a9 100644 --- a/firewood-growth-ring/tests/common/mod.rs +++ b/firewood-growth-ring/tests/common/mod.rs @@ -1,5 +1,4 @@ #[cfg(test)] -#[allow(dead_code)] use async_trait::async_trait; use futures::executor::block_on; use growthring::wal::{WalBytes, WalError, WalFile, WalLoader, WalPos, WalRingId, WalStore}; @@ -637,19 +636,17 @@ impl PaintingSim { let i0 = ops.len() - self.m; let mut canvas0 = canvas0.new_reference(&ops[..i0]); let mut res = None; - 'outer: loop { - if canvas.is_same(&canvas0) { - break; - } - for i in i0..ops.len() { - canvas0.prepaint(&ops[i], &WalRingId::empty_id()); - canvas0.paint_all(); - if canvas.is_same(&canvas0) { - break 'outer; + 'outer: { + if !canvas.is_same(&canvas0) { + for op in ops.iter().skip(i0) { + canvas0.prepaint(op, &WalRingId::empty_id()); + canvas0.paint_all(); + if canvas.is_same(&canvas0) { + break 'outer; + } } } res = Some(canvas0); - break; } res } diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index 268b797025ac..43d58ab62042 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -710,7 +710,7 @@ mod tests { // initial write let data = b"hello world"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(&data).into(); + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); let obj_ref = space.put_item(Hash(hash), 0).unwrap(); assert_eq!(obj_ref.as_ptr().addr(), 4113); // create hash ptr from address and attempt to read dirty write. diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 96538e96571c..8ff504e45152 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -13,7 +13,7 @@ const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); fn bench_dehydrate(b: &mut Bencher) { let mut to = [1u8; HASH_SIZE]; b.iter(|| { - ZERO_HASH.dehydrate(&mut to); + ZERO_HASH.dehydrate(&mut to).unwrap(); }); } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index bf4848bc95b7..a1b747e76de4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1375,7 +1375,7 @@ impl Merkle { self, p_ref, |p| { - let mut pp = p.inner.as_extension_mut().unwrap(); + let pp = p.inner.as_extension_mut().unwrap(); pp.0 .0.push(idx); pp.1 = c_ptr; p.rehash(); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 5010037c0450..e33de11626b0 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -574,8 +574,8 @@ impl CachedStore for StoreRevMut { pub struct ZeroStore(Rc<()>); #[cfg(test)] -impl ZeroStore { - pub fn new() -> Self { +impl Default for ZeroStore { + fn default() -> Self { Self(Rc::new(())) } } @@ -612,7 +612,7 @@ fn test_from_ash() { println!("[0x{l:x}, 0x{r:x})"); writes.push(SpaceWrite { offset: l, data }); } - let z = Rc::new(ZeroStore::new()); + let z = Rc::new(ZeroStore::default()); let rev = StoreRevShared::from_ash(z, &writes); println!("{rev:?}"); assert_eq!( @@ -714,7 +714,7 @@ impl CachedSpaceInner { pid: u64, ) -> Result<&'static mut [u8], StoreError> { let base = match self.pinned_pages.get_mut(&pid) { - Some(mut e) => { + Some(e) => { e.0 += 1; e.1.as_mut_ptr() } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 3b9855e02395..b9dd61cb8a9d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -365,9 +365,9 @@ fn test_range_proof() -> Result<(), ProofError> { let mut keys = Vec::new(); let mut vals = Vec::new(); - for i in start..end { - keys.push(&items[i].0); - vals.push(&items[i].1); + for item in items[start..end].iter() { + keys.push(&item.0); + vals.push(&item.1); } merkle.verify_range_proof(&proof, &items[start].0, &items[end - 1].0, keys, vals)?; @@ -400,9 +400,9 @@ fn test_bad_range_proof() -> Result<(), ProofError> { let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); - for i in start..end { - keys.push(*items[i].0); - vals.push(*items[i].1); + for item in items[start..end].iter() { + keys.push(*item.0); + vals.push(*item.1); } let test_case: u32 = rand::thread_rng().gen_range(0..6); @@ -496,9 +496,9 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); - for i in start..end { - keys.push(*items[i].0); - vals.push(*items[i].1); + for item in items[start..end].iter() { + keys.push(*item.0); + vals.push(*item.1); } merkle.verify_range_proof(&proof, first, last, keys, vals)?; @@ -544,9 +544,9 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); // Create gap - for i in start..end { - keys.push(*items[i].0); - vals.push(*items[i].1); + for item in items[start..end].iter() { + keys.push(*item.0); + vals.push(*item.1); } assert!(merkle .verify_range_proof(&proof, first, *items[end - 1].0, keys, vals) @@ -567,9 +567,9 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); // Create gap - for i in start..end { - keys.push(*items[i].0); - vals.push(*items[i].1); + for item in items[start..end].iter() { + keys.push(*item.0); + vals.push(*item.1); } assert!(merkle .verify_range_proof(&proof, *items[start].0, last, keys, vals) @@ -777,13 +777,15 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { let mut keys = Vec::new(); let mut vals = Vec::new(); - for i in first..last { - if i == (first + last) / 2 { - continue; - } - keys.push(&items[i].0); - vals.push(&items[i].1); - } + let middle = (first + last) / 2 - first; + items[first..last] + .iter() + .enumerate() + .filter(|(pos, _)| *pos != middle) + .for_each(|(_, item)| { + keys.push(&item.0); + vals.push(&item.1); + }); assert!(merkle .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) @@ -1005,17 +1007,18 @@ fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { #[test] fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { - let mut items = Vec::new(); - items.push(( - hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") - .expect("Decoding failed"), - hex::decode("02").expect("Decoding failed"), - )); - items.push(( - hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") - .expect("Decoding failed"), - hex::decode("03").expect("Decoding failed"), - )); + let items = vec![ + ( + hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("02").expect("Decoding failed"), + ), + ( + hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("03").expect("Decoding failed"), + ), + ]; let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") @@ -1029,7 +1032,7 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); - let item_iter = items.clone().into_iter(); + let item_iter = items.into_iter(); let keys = item_iter.clone().map(|item| item.0).collect(); let vals = item_iter.map(|item| item.1).collect(); From 9ce7938a53ed779187279ed71b3172f8ae1c7d55 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 30 May 2023 15:05:00 -0700 Subject: [PATCH 0162/1053] Framework for metrics (#101) --- firewood/Cargo.toml | 1 + firewood/src/db.rs | 9 +++++++++ firewood/tests/db.rs | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 7d14ce91954e..424ae26ad310 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -26,6 +26,7 @@ firewood-shale = { version = "0.0.3", path = "../firewood-shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.10.0" +metered = "0.9.0" nix = "0.26.1" once_cell = "1.13.1" parking_lot = "0.12.1" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 15f88f3563cb..14fb9c91badb 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use std::thread::JoinHandle; use bytemuck::{cast_slice, AnyBitPattern}; +use metered::{metered, HitCount}; use parking_lot::{Mutex, RwLock}; #[cfg(feature = "eth")] use primitive_types::U256; @@ -475,6 +476,7 @@ pub struct Db { revisions: Arc>, payload_regn_nbit: u64, rev_cfg: DbRevConfig, + metrics: Arc, } pub struct DbRevInner { @@ -483,6 +485,7 @@ pub struct DbRevInner { base: Universe, } +#[metered(registry = DbMetrics, visibility = pub)] impl Db { /// Open a database. pub fn new>(db_path: P, cfg: &DbConfig) -> Result { @@ -746,6 +749,7 @@ impl Db { })), payload_regn_nbit: header.payload_regn_nbit, rev_cfg: cfg.rev.clone(), + metrics: Arc::new(DbMetrics::default()), }) } @@ -769,6 +773,7 @@ impl Db { } /// Get a value in the kv store associated with a particular key. + #[measure([HitCount])] pub fn kv_get>(&self, key: K) -> Result, DbError> { self.inner .read() @@ -781,6 +786,7 @@ impl Db { /// The latest revision (nback) starts from 0, which is the current state. /// If nback equals is above the configured maximum number of revisions, this function returns None. /// Returns `None` if `nback` is greater than the configured maximum amount of revisions. + #[measure([HitCount])] pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { let mut revisions = self.revisions.lock(); let inner = self.inner.read(); @@ -882,6 +888,9 @@ impl Db { }, }) } + pub fn metrics(&self) -> Arc { + self.metrics.clone() + } } #[cfg(feature = "eth")] impl Db { diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 05bdbf37e94f..06bba2566eca 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -9,6 +9,29 @@ macro_rules! kv_dump { }}; } +#[test] +fn test_basic_metrics() { + let cfg = DbConfig::builder() + .meta_ncached_pages(1024) + .meta_ncached_files(128) + .payload_ncached_pages(1024) + .payload_ncached_files(128) + .payload_file_nbit(16) + .payload_regn_nbit(16) + .wal( + WalConfig::builder() + .file_nbit(15) + .block_nbit(8) + .max_revisions(10) + .build(), + ); + let db = Db::new("test_revisions_db2", &cfg.clone().truncate(true).build()).unwrap(); + let metrics = db.metrics(); + assert_eq!(metrics.kv_get.hit_count.get(), 0); + db.kv_get("a").ok(); + assert_eq!(metrics.kv_get.hit_count.get(), 1); +} + #[test] fn test_revisions() { use rand::{rngs::StdRng, Rng, SeedableRng}; From bc72b8bc3897ab328f48faf1786292879304f9d7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 31 May 2023 09:21:38 -0700 Subject: [PATCH 0163/1053] Use unzip instead of mut/push (#105) --- firewood/tests/merkle.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index b9dd61cb8a9d..ea243416b460 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -775,17 +775,13 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { assert!(!end_proof.0.is_empty()); proof.concat_proofs(end_proof); - let mut keys = Vec::new(); - let mut vals = Vec::new(); let middle = (first + last) / 2 - first; - items[first..last] + let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 4]>) = items[first..last] .iter() .enumerate() .filter(|(pos, _)| *pos != middle) - .for_each(|(_, item)| { - keys.push(&item.0); - vals.push(&item.1); - }); + .map(|(_, item)| (&item.0, &item.1)) + .unzip(); assert!(merkle .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) From 5a81f528b88ceefa68eb414fc71abfff6527ac0f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 31 May 2023 16:18:30 -0400 Subject: [PATCH 0164/1053] get rid of weird closure (#84) --- firewood/src/merkle.rs | 124 +++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index a1b747e76de4..17fa3ecc3588 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -997,31 +997,32 @@ impl Merkle { } None => { if rem_path.len() == n_path.len() { - let mut err = None; + let mut result = Ok(None); + write_node!( self, u_ref, |u| { - #[allow(clippy::blocks_in_if_conditions)] match &mut u.inner { NodeType::Leaf(u) => u.1 = Data(val), NodeType::Extension(u) => { - if let Err(e) = (|| { - let mut b_ref = self.get_node(u.1)?; - if b_ref - .write(|b| { - b.inner.as_branch_mut().unwrap().value = - Some(Data(val)); - b.rehash() - }) - .is_err() - { + let write_result = self.get_node(u.1).and_then(|mut b_ref| { + let write_result = b_ref.write(|b| { + b.inner.as_branch_mut().unwrap().value = + Some(Data(val)); + b.rehash() + }); + + if write_result.is_err() { u.1 = self.new_node(b_ref.clone())?.as_ptr(); deleted.push(b_ref.as_ptr()); } + Ok(()) - })() { - err = Some(Err(e)) + }); + + if let Err(e) = write_result { + result = Err(e); } } _ => unreachable!(), @@ -1031,8 +1032,10 @@ impl Merkle { parents, deleted ); - return err.unwrap_or_else(|| Ok(None)); + + return result; } + let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() { // key path is a prefix of the path to u u_ref @@ -1388,7 +1391,6 @@ impl Merkle { } } NodeType::Leaf(_) | NodeType::Extension(_) => { - #[allow(clippy::blocks_in_if_conditions)] match &p_ref.inner { NodeType::Branch(_) => { // ____[Leaf/Ext] @@ -1396,25 +1398,26 @@ impl Merkle { // from: [p: Branch] -> [b]x* // \____[Leaf]x // to: [p: Branch] -> [Leaf/Ext] - let c_ptr = if c_ref - .write(|c| { - (match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), - }) - .0 - .insert(0, idx); - c.rehash() - }) - .is_err() - { + let write_result = c_ref.write(|c| { + let partial_path = match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }; + + partial_path.0.insert(0, idx); + c.rehash() + }); + + let c_ptr = if write_result.is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { c_ptr }; + drop(c_ref); + p_ref .write(|p| { p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = @@ -1430,7 +1433,8 @@ impl Merkle { // \____[Leaf]x // to: P -> [p: Leaf/Ext] deleted.push(p_ptr); - if !write_node!( + + let write_failed = write_node!( self, c_ref, |c| { @@ -1447,7 +1451,9 @@ impl Merkle { }, parents, deleted - ) { + ); + + if !write_failed { drop(c_ref); self.set_parent(c_ptr, parents); } @@ -1515,20 +1521,17 @@ impl Merkle { NodeType::Branch(_) => { // from: [Branch] -> [Branch]x -> [Leaf/Ext] // to: [Branch] -> [Leaf/Ext] - #[allow(clippy::blocks_in_if_conditions)] - let c_ptr = if c_ref - .write(|c| { - match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), - } - .0 - .insert(0, idx); - c.rehash() - }) - .is_err() - { + let write_result = c_ref.write(|c| { + match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + } + .0 + .insert(0, idx); + c.rehash() + }); + if write_result.is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { @@ -1545,27 +1548,26 @@ impl Merkle { NodeType::Extension(n) => { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] - #[allow(clippy::blocks_in_if_conditions)] - let c_ptr = if c_ref - .write(|c| { - let mut path = n.0.clone().into_inner(); - path.push(idx); - let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, - _ => unreachable!(), - }; - path.extend(&**path0); - *path0 = PartialPath(path); - c.rehash() - }) - .is_err() - { + let write_result = c_ref.write(|c| { + let mut path = n.0.clone().into_inner(); + path.push(idx); + let path0 = match &mut c.inner { + NodeType::Leaf(n) => &mut n.0, + NodeType::Extension(n) => &mut n.0, + _ => unreachable!(), + }; + path.extend(&**path0); + *path0 = PartialPath(path); + c.rehash() + }); + + let c_ptr = if write_result.is_err() { deleted.push(c_ptr); self.new_node(c_ref.clone())?.as_ptr() } else { c_ptr }; + deleted.push(b_ref.as_ptr()); drop(c_ref); self.set_parent(c_ptr, parents); From ae3bf8b9618833b7ffe18f0ac11065c4b3bc5734 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 31 May 2023 16:43:21 -0400 Subject: [PATCH 0165/1053] migrate growth ring fd to tokio file (#88) --- firewood-growth-ring/Cargo.toml | 2 + firewood-growth-ring/src/lib.rs | 149 ++++++++++++++++++++------------ firewood-growth-ring/src/wal.rs | 1 - 3 files changed, 95 insertions(+), 57 deletions(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index 6202a2672bf4..b32f14a24ef5 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -20,11 +20,13 @@ nix = "0.26.2" libc = "0.2.133" bytemuck = {version = "1.13.1", features = ["derive"]} thiserror = "1.0.40" +tokio = { version = "1.28.1", features = ["fs"] } [dev-dependencies] hex = "0.4.3" rand = "0.8.5" indexmap = "1.9.1" +tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } [lib] name = "growthring" diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index e2fc50cf733a..d8e150793291 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -2,7 +2,7 @@ //! //! # Examples //! -//! ``` +//! ```no_run //! use growthring::{WalStoreAio, wal::WalLoader}; //! use futures::executor::block_on; //! let mut loader = WalLoader::new(); @@ -52,86 +52,53 @@ pub mod walerror; use async_trait::async_trait; use firewood_libaio::{AioBuilder, AioManager}; -use libc::off_t; -#[cfg(not(target_os = "linux"))] use nix::fcntl::OFlag; -use nix::unistd::{close, ftruncate}; -#[cfg(target_os = "linux")] -use nix::{ - errno::Errno, - fcntl::{fallocate, posix_fallocate, FallocateFlags, OFlag}, -}; use std::fs; -use std::os::fd::IntoRawFd; -use std::os::unix::io::RawFd; -use std::os::unix::prelude::OpenOptionsExt; +use std::os::fd::AsRawFd; use std::path::{Path, PathBuf}; use std::sync::Arc; +use tokio::fs::{File, OpenOptions}; use wal::{WalBytes, WalFile, WalPos, WalStore}; use walerror::WalError; pub struct WalFileAio { - fd: RawFd, - aiomgr: Arc, + file: File, + aio_manager: Arc, } impl WalFileAio { - pub fn new(root_dir: &Path, filename: &str, aiomgr: Arc) -> Result { - fs::OpenOptions::new() + async fn open_file>(path: P) -> Result { + OpenOptions::new() .read(true) .write(true) .truncate(false) .create(true) .mode(0o600) - .open(root_dir.join(filename)) - .map(|f| { - let fd = f.into_raw_fd(); - WalFileAio { fd, aiomgr } - }) - .map_err(|e| WalError::IOError(Arc::new(e))) + .open(path) + .await } -} -impl Drop for WalFileAio { - fn drop(&mut self) { - close(self.fd).unwrap(); + fn new(file: File, aio_manager: Arc) -> Self { + Self { file, aio_manager } } } #[async_trait(?Send)] impl WalFile for WalFileAio { - #[cfg(target_os = "linux")] async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { - let (offset, length) = (offset as off_t, length as off_t); - // TODO: is there any async version of fallocate? - fallocate( - self.fd, - FallocateFlags::FALLOC_FL_ZERO_RANGE, - offset, - length, - ) - .or_else(|err| match err { - Errno::EOPNOTSUPP => posix_fallocate(self.fd, offset, length), - _ => { - eprintln!("fallocate failed with error: {err:?}"); - Err(err) - } - }) - .map_err(Into::into) + self.file + .set_len(offset + length as u64) + .await + .map_err(Into::into) } - #[cfg(not(target_os = "linux"))] - // TODO: macos support is possible here, but possibly unnecessary - async fn allocate(&self, _offset: WalPos, _length: usize) -> Result<(), WalError> { - Ok(()) - } - - async fn truncate(&self, length: usize) -> Result<(), WalError> { - ftruncate(self.fd, length as off_t).map_err(From::from) + async fn truncate(&self, len: usize) -> Result<(), WalError> { + self.file.set_len(len as u64).await.map_err(Into::into) } async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { - let (res, data) = self.aiomgr.write(self.fd, offset, data, None).await; + let fd = self.file.as_raw_fd(); + let (res, data) = self.aio_manager.write(fd, offset, data, None).await; res.map_err(Into::into).and_then(|nwrote| { if nwrote == data.len() { Ok(()) @@ -139,14 +106,15 @@ impl WalFile for WalFileAio { Err(WalError::Other(format!( "partial write; wrote {nwrote} expected {} for fd {}", data.len(), - self.fd + fd ))) } }) } async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { - let (res, data) = self.aiomgr.read(self.fd, offset, length, None).await; + let fd = self.file.as_raw_fd(); + let (res, data) = self.aio_manager.read(fd, offset, length, None).await; res.map_err(From::from) .map(|nread| if nread == length { Some(data) } else { None }) } @@ -204,8 +172,11 @@ impl WalStore for WalStoreAio { type FileNameIter = std::vec::IntoIter; async fn open_file(&self, filename: &str, _touch: bool) -> Result, WalError> { - WalFileAio::new(&self.root_dir, filename, self.aiomgr.clone()) - .map(|f| Box::new(f) as Box) + let path = self.root_dir.join(filename); + + let file = WalFileAio::open_file(path).await?; + + Ok(Box::new(WalFileAio::new(file, self.aiomgr.clone()))) } async fn remove_file(&self, filename: String) -> Result<(), WalError> { @@ -221,3 +192,69 @@ impl WalStore for WalStoreAio { Ok(filenames.into_iter()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn truncation_makes_a_file_smaller() { + const HALF_LENGTH: usize = 512; + + let walfile_path = get_walfile_path(file!(), line!()); + + tokio::fs::remove_file(&walfile_path).await.ok(); + + let aio_manager = AioBuilder::default().build().unwrap(); + + let walfile = WalFileAio::open_file(walfile_path).await.unwrap(); + + let walfile_aio = WalFileAio::new(walfile, Arc::new(aio_manager)); + + let first_half = vec![1u8; HALF_LENGTH]; + let second_half = vec![2u8; HALF_LENGTH]; + + let data = first_half + .iter() + .copied() + .chain(second_half.iter().copied()) + .collect(); + + walfile_aio.write(0, data).await.unwrap(); + walfile_aio.truncate(HALF_LENGTH).await.unwrap(); + + let result = walfile_aio.read(0, HALF_LENGTH).await.unwrap(); + + assert_eq!(result, Some(first_half.into())) + } + + #[tokio::test] + async fn truncation_extends_a_file_with_zeros() { + const LENGTH: usize = 512; + + let walfile_path = get_walfile_path(file!(), line!()); + + tokio::fs::remove_file(&walfile_path).await.ok(); + + let aio_manager = AioBuilder::default().build().unwrap(); + + let walfile = WalFileAio::open_file(walfile_path).await.unwrap(); + + let walfile_aio = WalFileAio::new(walfile, Arc::new(aio_manager)); + + walfile_aio + .write(0, vec![1u8; LENGTH].into()) + .await + .unwrap(); + + walfile_aio.truncate(2 * LENGTH).await.unwrap(); + + let result = walfile_aio.read(LENGTH as u64, LENGTH).await.unwrap(); + + assert_eq!(result, Some(vec![0u8; LENGTH].into())) + } + + fn get_walfile_path(file: &str, line: u32) -> PathBuf { + Path::new("/tmp").join(format!("{}_{}", file.replace('/', "-"), line)) + } +} diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index 0925c3074281..c78d33374f1d 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -182,7 +182,6 @@ pub trait WalFile { /// Read data with offset. Return `Ok(None)` when it reaches EOF. async fn read(&self, offset: WalPos, length: usize) -> Result, WalError>; /// Truncate a file to a specified length. - #[allow(clippy::result_unit_err)] async fn truncate(&self, length: usize) -> Result<(), WalError>; } From b308173faf50e3fe2dd32eebaf7e0e0be8dd54de Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 31 May 2023 18:33:16 -0400 Subject: [PATCH 0166/1053] Add CI cacheing (#108) Signed-off-by: Richard Pringle --- .github/workflows/ci.yaml | 100 +++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ddd4bd2c1f21..c1ed267bfba0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ env: CARGO_TERM_COLOR: always jobs: - lint: + build: runs-on: ubuntu-latest steps: @@ -18,13 +18,40 @@ jobs: with: toolchain: stable override: true - components: rustfmt, clippy - - name: Lint - run: cargo fmt -- --check - - name: Clippy - run: cargo clippy -- -D warnings + - name: Cache Cargo intermediate products + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + # We can do this now because we use specific verison and update with Dependabot + # but if we make the deps any less specifc, we'll have to fix + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + # start from the previous set of cached dependencies + restore-keys: | + ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-deps- + # TODO: do a `cargo fetch` here first + - name: Check + run: cargo check --workspace --tests --examples --benches + - name: Build + run: cargo build --workspace --tests --examples --benches + - name: Save Build Deps + uses: actions/cache/save@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} - build: + lint: + needs: build runs-on: ubuntu-latest steps: @@ -33,10 +60,25 @@ jobs: with: toolchain: stable override: true - - name: Build - run: cargo build --verbose + components: rustfmt, clippy + - name: Restore Check Deps + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + - name: Format + run: cargo fmt -- --check + - name: Clippy + run: cargo clippy -- -D warnings test: + needs: build runs-on: ubuntu-latest steps: @@ -45,10 +87,22 @@ jobs: with: toolchain: stable override: true + - name: Restore Check Deps + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} - name: Run tests run: cargo test --verbose e2e: + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -56,16 +110,29 @@ jobs: with: toolchain: stable override: true + - name: Restore Check Deps + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} - name: Run simple example run: RUST_BACKTRACE=1 cargo run --example simple - name: Run dump example run: RUST_BACKTRACE=1 cargo run --example dump - - name: Run benchmark example - run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 + # benchmarks were not being done in --release mode, we can enable this again later + # - name: Run benchmark example + # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - name: Run rev example run: RUST_BACKTRACE=1 cargo run --example rev docs: + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -73,6 +140,17 @@ jobs: with: toolchain: stable override: true + - name: Restore Check Deps + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} - name: Lint intra docs links run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links - name: Lint missing crate-level docs From f7eb702534e3a15cb0be8cf9763f0a36b98a561d Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 31 May 2023 22:51:30 -0400 Subject: [PATCH 0167/1053] Unwind some growth-ring test nesting (#109) --- firewood-growth-ring/src/wal.rs | 8 ++- firewood-growth-ring/tests/common/mod.rs | 68 ++++++++++++------------ firewood-growth-ring/tests/rand_fail.rs | 47 ++++++++-------- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index c78d33374f1d..b850bd6b9f93 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -105,8 +105,14 @@ pub struct WalRingId { counter: u32, } +impl Default for WalRingId { + fn default() -> Self { + Self::empty_id() + } +} + impl WalRingId { - pub fn empty_id() -> Self { + pub const fn empty_id() -> Self { WalRingId { start: 0, end: 0, diff --git a/firewood-growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs index 64e7404fa5a9..402114ee6852 100644 --- a/firewood-growth-ring/tests/common/mod.rs +++ b/firewood-growth-ring/tests/common/mod.rs @@ -620,44 +620,44 @@ impl PaintingSim { .unwrap(); println!("last = {}/{}, applied = {}", last_idx, ops.len(), napplied); canvas.paint_all(); + // recover complete - let canvas0 = if last_idx > 0 { - let canvas0 = canvas.new_reference(&ops[..last_idx]); - if canvas.is_same(&canvas0) { - None - } else { - Some(canvas0) - } - } else { - let canvas0 = canvas.new_reference(&[]); - if canvas.is_same(&canvas0) { - None - } else { - let i0 = ops.len() - self.m; - let mut canvas0 = canvas0.new_reference(&ops[..i0]); - let mut res = None; - 'outer: { - if !canvas.is_same(&canvas0) { - for op in ops.iter().skip(i0) { - canvas0.prepaint(op, &WalRingId::empty_id()); - canvas0.paint_all(); - if canvas.is_same(&canvas0) { - break 'outer; - } - } - } - res = Some(canvas0); - } - res - } - }; - if let Some(canvas0) = canvas0 { + let start_ops = if last_idx > 0 { &ops[..last_idx] } else { &[] }; + let canvas0 = canvas.new_reference(start_ops); + + if canvas.is_same(&canvas0) { + return true; + } + + if last_idx > 0 { canvas.print(40); canvas0.print(40); - false - } else { - true + + return false; + } + + let (start_ops, end_ops) = ops.split_at(self.m); + let mut canvas0 = canvas0.new_reference(start_ops); + + if canvas.is_same(&canvas0) { + return true; } + + for op in end_ops { + const EMPTY_ID: WalRingId = WalRingId::empty_id(); + + canvas0.prepaint(op, &EMPTY_ID); + canvas0.paint_all(); + + if canvas.is_same(&canvas0) { + return true; + } + } + + canvas.print(40); + canvas0.print(40); + + false } pub fn new_canvas(&self) -> Canvas { diff --git a/firewood-growth-ring/tests/rand_fail.rs b/firewood-growth-ring/tests/rand_fail.rs index 25fbf2e25863..97435d012243 100644 --- a/firewood-growth-ring/tests/rand_fail.rs +++ b/firewood-growth-ring/tests/rand_fail.rs @@ -4,32 +4,37 @@ mod common; use std::collections::HashMap; use std::rc::Rc; -fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WalStoreEmulState, f: usize) { - let sim = &sims[0]; - // save the current state and start from there - let mut state = state.clone(); - let mut state0 = state.clone(); - let nticks = sim.get_nticks(&mut state0); - println!("fail = {}, nticks = {}", f, nticks); - for i in 0..nticks { - println!("fail = {}, pos = {}", f, i); - let mut canvas = sim.new_canvas(); - let mut ops: Vec = Vec::new(); - let mut ringid_map = HashMap::new(); - let fgen = common::SingleFailGen::new(i); - if sim - .run( +fn multi_point_failure(sims: &[common::PaintingSim]) { + fn track_recursion(sims: &[common::PaintingSim], state: &common::WalStoreEmulState, f: usize) { + let sim = &sims[0]; + // save the current state and start from there + let mut state = state.clone(); + let mut state0 = state.clone(); + let nticks = sim.get_nticks(&mut state0); + println!("fail = {f}, nticks = {nticks}"); + + for pos in 0..nticks { + println!("fail = {f}, pos = {pos}"); + let mut canvas = sim.new_canvas(); + let mut ops: Vec = Vec::new(); + let mut ringid_map = HashMap::new(); + let fgen = common::SingleFailGen::new(pos); + + let sim_result = sim.run( &mut state, &mut canvas, sim.get_walloader(), &mut ops, &mut ringid_map, Rc::new(fgen), - ) - .is_err() - { + ); + + if sim_result.is_ok() { + return; + } + if sims.len() > 1 { - _multi_point_failure(&sims[1..], &state, f + 1) + track_recursion(&sims[1..], &state, f + 1) } else { assert!(sim.check( &mut state, @@ -41,10 +46,8 @@ fn _multi_point_failure(sims: &[common::PaintingSim], state: &common::WalStoreEm } } } -} -fn multi_point_failure(sims: &[common::PaintingSim]) { - _multi_point_failure(sims, &common::WalStoreEmulState::new(), 1); + track_recursion(sims, &common::WalStoreEmulState::new(), 1); } #[test] From 7d6d9e5da40a13662bb56870a30b92be77ea0367 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 1 Jun 2023 11:39:15 -0400 Subject: [PATCH 0168/1053] Remove compose-dev.yml file (#111) --- .gitignore | 5 ++++- compose-dev.yaml | 12 ------------ 2 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 compose-dev.yaml diff --git a/.gitignore b/.gitignore index 08fa7a521f7f..4938d7e6cc1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ # Ignore VSCode directory .vscode -#### Below sections are auto-generated #### +compose-dev.yaml # ignore test databases *_db + +#### Below sections are auto-generated #### + # Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim # Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,vim diff --git a/compose-dev.yaml b/compose-dev.yaml deleted file mode 100644 index a92f7012bbd8..000000000000 --- a/compose-dev.yaml +++ /dev/null @@ -1,12 +0,0 @@ -services: - app: - entrypoint: - - sleep - - infinity - image: docker/dev-environments-default:stable-1 - init: true - volumes: - - type: bind - source: /var/run/docker.sock - target: /var/run/docker.sock - From ec97b1b5a70920331e7eb274df804292729d4636 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 1 Jun 2023 15:26:25 -0400 Subject: [PATCH 0169/1053] Add default branch caching (#112) --- .github/workflows/ci.yaml | 18 ++------- .github/workflows/default-branch-cache.yaml | 43 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/default-branch-cache.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c1ed267bfba0..a8da030154d1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,16 +39,6 @@ jobs: run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches - - name: Save Build Deps - uses: actions/cache/save@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} lint: needs: build @@ -71,7 +61,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - name: Format run: cargo fmt -- --check - name: Clippy @@ -97,7 +87,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - name: Run tests run: cargo test --verbose @@ -120,7 +110,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - name: Run simple example run: RUST_BACKTRACE=1 cargo run --example simple - name: Run dump example @@ -150,7 +140,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - name: Lint intra docs links run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links - name: Lint missing crate-level docs diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml new file mode 100644 index 000000000000..52bc37925043 --- /dev/null +++ b/.github/workflows/default-branch-cache.yaml @@ -0,0 +1,43 @@ +# because apparently caches are isolated across branches +name: default-branch-cache + +on: + push: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Cache Cargo intermediate products + id: cargo-cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + # We can do this now because we use specific verison and update with Dependabot + # but if we make the deps any less specifc, we'll have to fix + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + # start from the previous set of cached dependencies + restore-keys: | + ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-deps- + # TODO: do a `cargo fetch` here first + - name: Check + run: cargo check --workspace --tests --examples --benches + - name: Build + run: cargo build --workspace --tests --examples --benches From aa30f9477e6f46f9346f895974e3b9b70355aef5 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Jun 2023 11:30:53 -0400 Subject: [PATCH 0170/1053] Delete cache values associated with a merged branch (#114) --- .github/workflows/cache-cleanup.yaml | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/cache-cleanup.yaml diff --git a/.github/workflows/cache-cleanup.yaml b/.github/workflows/cache-cleanup.yaml new file mode 100644 index 000000000000..045241e13455 --- /dev/null +++ b/.github/workflows/cache-cleanup.yaml @@ -0,0 +1,34 @@ +name: cleanup caches by a branch +on: + pull_request: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From fe836fcbaf84049e5dc0aa69f6c14c5d61a4ed5f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Jun 2023 12:52:53 -0400 Subject: [PATCH 0171/1053] Always update actions-cache (#116) --- .github/workflows/ci.yaml | 28 +++++++++++++++------ .github/workflows/default-branch-cache.yaml | 14 +++++++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a8da030154d1..c206a85250a3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,15 +11,17 @@ env: jobs: build: runs-on: ubuntu-latest - + outputs: + cache-key: ${{ steps.cargo-cache.outputs.cache-primary-key }} steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - - name: Cache Cargo intermediate products - uses: actions/cache@v3 + - name: Restore Cargo Cache + id: cargo-cache + uses: actions/cache/restore@v3 with: path: | ~/.cargo/bin/ @@ -34,11 +36,21 @@ jobs: restore-keys: | ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- ${{ runner.os }}-deps- - # TODO: do a `cargo fetch` here first - name: Check run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches + # Always update the cache + - name: Save Cargo Cache + uses: actions/cache/save@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ steps.cargo-cache.outputs.cache-primary-key }} lint: needs: build @@ -61,7 +73,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + key: ${{ needs.build.outputs.cache-key }} - name: Format run: cargo fmt -- --check - name: Clippy @@ -87,7 +99,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + key: ${{ needs.build.outputs.cache-key }} - name: Run tests run: cargo test --verbose @@ -110,7 +122,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + key: ${{ needs.build.outputs.cache-key }} - name: Run simple example run: RUST_BACKTRACE=1 cargo run --example simple - name: Run dump example @@ -140,7 +152,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + key: ${{ needs.build.outputs.cache-key }} - name: Lint intra docs links run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links - name: Lint missing crate-level docs diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 52bc37925043..30038d2f028f 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -19,9 +19,9 @@ jobs: with: toolchain: stable override: true - - name: Cache Cargo intermediate products + - name: Restore Cargo Cache id: cargo-cache - uses: actions/cache@v3 + uses: actions/cache/restore@v3 with: path: | ~/.cargo/bin/ @@ -41,3 +41,13 @@ jobs: run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches + - name: Save Cargo Cache + uses: actions/cache/save@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ steps.cargo-cache.outputs.cache-primary-key }} From a6b26f5b346252228e6c5f8bb837c993d51e1995 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Jun 2023 14:58:39 -0400 Subject: [PATCH 0172/1053] always save actions cache (#117) --- .github/workflows/ci.yaml | 20 ++++++++++++++++++++ .github/workflows/default-branch-cache.yaml | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c206a85250a3..031edd41b50c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,6 +41,26 @@ jobs: - name: Build run: cargo build --workspace --tests --examples --benches # Always update the cache + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Save Cargo Cache uses: actions/cache/save@v3 with: diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 30038d2f028f..f2eda3b0d88e 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -41,6 +41,26 @@ jobs: run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches + - name: Delete old cache + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/head/${{ github.ref }}" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Save Cargo Cache uses: actions/cache/save@v3 with: From 7fcb6d517d1f5a637a1e7aca0da01feafe497bfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:03:44 -0400 Subject: [PATCH 0173/1053] build(deps): update enum-as-inner requirement from 0.5.1 to 0.6.0 (#107) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 424ae26ad310..fb43c5251db4 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -19,7 +19,7 @@ readme = "../README.md" aquamarine = "0.3.1" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } -enum-as-inner = "0.5.1" +enum-as-inner = "0.6.0" firewood-growth-ring = { version = "0.0.3", path = "../firewood-growth-ring" } firewood-libaio = {version = "0.0.3", path = "../firewood-libaio" } firewood-shale = { version = "0.0.3", path = "../firewood-shale" } From 4b2c02c145871b4e1b1d69e0e46a5ce8e16a3585 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 2 Jun 2023 14:02:28 -0700 Subject: [PATCH 0174/1053] Remove lazy_static and use the new OnceCell from rust 1.70.0 (#120) --- firewood/Cargo.toml | 1 - firewood/src/account.rs | 1 - firewood/src/merkle.rs | 11 +++++------ fwdctl/Cargo.toml | 1 - fwdctl/tests/cli.rs | 21 ++++++--------------- 5 files changed, 11 insertions(+), 24 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index fb43c5251db4..b61ebd18c902 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -28,7 +28,6 @@ hex = "0.4.3" lru = "0.10.0" metered = "0.9.0" nix = "0.26.1" -once_cell = "1.13.1" parking_lot = "0.12.1" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } rlp = "0.5.2" diff --git a/firewood/src/account.rs b/firewood/src/account.rs index c44be6f569ac..ac4ac4c8c0e5 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -19,7 +19,6 @@ pub struct Account { impl Account { pub fn empty_code() -> &'static Hash { - use once_cell::sync::OnceCell; static V: OnceCell = OnceCell::new(); V.get_or_init(|| { Hash( diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 17fa3ecc3588..76b20ae220ac 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -4,16 +4,16 @@ use crate::proof::Proof; use enum_as_inner::EnumAsInner; -use once_cell::unsync::OnceCell; use sha3::Digest; use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; -use std::cell::Cell; +use std::cell::{Cell, OnceCell}; use std::cmp; use std::collections::HashMap; use std::error::Error; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; +use std::sync::OnceLock; pub const NBRANCH: usize = 16; pub const HASH_SIZE: usize = 32; @@ -467,11 +467,11 @@ impl Node { fn new_from_hash(root_hash: Option, eth_rlp_long: Option, inner: NodeType) -> Self { Self { root_hash: match root_hash { - Some(h) => OnceCell::with_value(h), + Some(h) => OnceCell::from(h), None => OnceCell::new(), }, eth_rlp_long: match eth_rlp_long { - Some(b) => OnceCell::with_value(b), + Some(b) => OnceCell::from(b), None => OnceCell::new(), }, eth_rlp: OnceCell::new(), @@ -847,8 +847,7 @@ impl Merkle { } pub fn empty_root() -> &'static Hash { - use once_cell::sync::OnceCell; - static V: OnceCell = OnceCell::new(); + static V: OnceLock = OnceLock::new(); V.get_or_init(|| { Hash( hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index c57f8c130f6d..2c58686d5d93 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -14,4 +14,3 @@ log = "0.4.17" assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" -lazy_static = "1.4.0" diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 7bc51923a2c6..8fa0a9876204 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -222,8 +222,6 @@ mod tmpdb { path::{Path, PathBuf}, }; - use lazy_static::lazy_static; - const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; const TARGET_TMP_DIR: Option<&str> = option_env!("CARGO_TARGET_TMPDIR"); @@ -242,19 +240,12 @@ mod tmpdb { &self.path } } - lazy_static! { - static ref DB_PATH: DbPath = { - let path = [ - TARGET_TMP_DIR.unwrap_or(&std::env::var("TMPDIR").unwrap_or("/tmp".to_string())), - FIREWOOD_TEST_DB_NAME, - ] - .iter() - .collect::(); - DbPath { path } - }; - } + pub fn path() -> DbPath { + let path = Path::new( + TARGET_TMP_DIR.unwrap_or(&std::env::var("TMPDIR").unwrap_or("/tmp".to_string())), + ) + .join(FIREWOOD_TEST_DB_NAME); - pub fn path() -> &'static DbPath { - &DB_PATH + DbPath { path } } } From 198b02a8e9b29eb101aa46552d403a7282a01dff Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Jun 2023 19:36:36 -0400 Subject: [PATCH 0175/1053] use static dispatch (#124) --- firewood-growth-ring/examples/demo1.rs | 4 +- firewood-growth-ring/src/lib.rs | 119 ++++++++++++++++++++++- firewood-growth-ring/src/wal.rs | 94 ++++++++++-------- firewood-growth-ring/tests/common/mod.rs | 20 ++-- firewood/src/storage/buffer.rs | 3 +- 5 files changed, 187 insertions(+), 53 deletions(-) diff --git a/firewood-growth-ring/examples/demo1.rs b/firewood-growth-ring/examples/demo1.rs index c9aa184984c8..5ac22f788e89 100644 --- a/firewood-growth-ring/examples/demo1.rs +++ b/firewood-growth-ring/examples/demo1.rs @@ -2,11 +2,11 @@ use futures::executor::block_on; use growthring::{ wal::{WalBytes, WalLoader, WalRingId, WalWriter}, walerror::WalError, - WalStoreAio, + WalFileAio, WalStoreAio, }; use rand::{seq::SliceRandom, Rng, SeedableRng}; -fn test(records: Vec, wal: &mut WalWriter) -> Vec { +fn test(records: Vec, wal: &mut WalWriter) -> Vec { let mut res = Vec::new(); for r in wal.grow(records).into_iter() { let ring_id = futures::executor::block_on(r).unwrap().1; diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index d8e150793291..756cba48f113 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -168,15 +168,15 @@ pub fn oflags() -> OFlag { } #[async_trait(?Send)] -impl WalStore for WalStoreAio { +impl WalStore for WalStoreAio { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, _touch: bool) -> Result, WalError> { + async fn open_file(&self, filename: &str, _touch: bool) -> Result { let path = self.root_dir.join(filename); let file = WalFileAio::open_file(path).await?; - Ok(Box::new(WalFileAio::new(file, self.aiomgr.clone()))) + Ok(WalFileAio::new(file, self.aiomgr.clone())) } async fn remove_file(&self, filename: String) -> Result<(), WalError> { @@ -254,7 +254,118 @@ mod tests { assert_eq!(result, Some(vec![0u8; LENGTH].into())) } + #[tokio::test] + async fn write_and_read_full() { + let walfile = { + let walfile_path = get_walfile_path(file!(), line!()); + tokio::fs::remove_file(&walfile_path).await.ok(); + WalFileAio::open_file(walfile_path).await.unwrap() + }; + + let walfile_aio = { + let aio_manager = AioBuilder::default().build().unwrap(); + WalFileAio::new(walfile, Arc::new(aio_manager)) + }; + + let data: Vec = (0..=u8::MAX).collect(); + + walfile_aio.write(0, data.clone().into()).await.unwrap(); + + let result = walfile_aio.read(0, data.len()).await.unwrap(); + + assert_eq!(result, Some(data.into())); + } + + #[tokio::test] + async fn write_and_read_subset() { + let walfile = { + let walfile_path = get_walfile_path(file!(), line!()); + tokio::fs::remove_file(&walfile_path).await.ok(); + WalFileAio::open_file(walfile_path).await.unwrap() + }; + + let walfile_aio = { + let aio_manager = AioBuilder::default().build().unwrap(); + WalFileAio::new(walfile, Arc::new(aio_manager)) + }; + + let data: Vec = (0..=u8::MAX).collect(); + + walfile_aio.write(0, data.clone().into()).await.unwrap(); + + let mid = data.len() / 2; + + let (start, end) = data.split_at(mid); + + let read_start_result = walfile_aio.read(0, mid).await.unwrap(); + let read_end_result = walfile_aio.read(mid as u64, mid).await.unwrap(); + + assert_eq!(read_start_result, Some(start.into())); + assert_eq!(read_end_result, Some(end.into())); + } + + #[tokio::test] + async fn write_and_read_beyond_len() { + let walfile = { + let walfile_path = get_walfile_path(file!(), line!()); + tokio::fs::remove_file(&walfile_path).await.ok(); + WalFileAio::open_file(walfile_path).await.unwrap() + }; + + let walfile_aio = { + let aio_manager = AioBuilder::default().build().unwrap(); + WalFileAio::new(walfile, Arc::new(aio_manager)) + }; + + let data: Vec = (0..=u8::MAX).collect(); + + walfile_aio.write(0, data.clone().into()).await.unwrap(); + + let result = walfile_aio + .read((data.len() / 2) as u64, data.len()) + .await + .unwrap(); + + assert_eq!(result, None); + } + + #[tokio::test] + async fn write_at_offset() { + const OFFSET: u64 = 2; + + let walfile = { + let walfile_path = get_walfile_path(file!(), line!()); + tokio::fs::remove_file(&walfile_path).await.ok(); + WalFileAio::open_file(walfile_path).await.unwrap() + }; + + let walfile_aio = { + let aio_manager = AioBuilder::default().build().unwrap(); + WalFileAio::new(walfile, Arc::new(aio_manager)) + }; + + let data: Vec = (0..=u8::MAX).collect(); + + walfile_aio + .write(OFFSET, data.clone().into()) + .await + .unwrap(); + + let result = walfile_aio + .read(0, data.len() + OFFSET as usize) + .await + .unwrap(); + + let data: Vec<_> = std::iter::repeat(0) + .take(OFFSET as usize) + .chain(data) + .collect(); + + assert_eq!(result, Some(data.into())); + } + fn get_walfile_path(file: &str, line: u32) -> PathBuf { - Path::new("/tmp").join(format!("{}_{}", file.replace('/', "-"), line)) + let path = option_env!("CARGO_TARGET_TMPDIR").unwrap_or("/tmp"); + Path::new(path).join(format!("{}_{}", file.replace('/', "-"), line)) } } diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index b850bd6b9f93..680a5521a610 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -7,11 +7,14 @@ use futures::{ }; use std::cell::{RefCell, UnsafeCell}; -use std::collections::{hash_map, BinaryHeap, HashMap, VecDeque}; use std::convert::{TryFrom, TryInto}; use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::pin::Pin; +use std::{ + collections::{hash_map, BinaryHeap, HashMap, VecDeque}, + marker::PhantomData, +}; pub use crate::walerror::WalError; @@ -192,11 +195,11 @@ pub trait WalFile { } #[async_trait(?Send)] -pub trait WalStore { +pub trait WalStore { type FileNameIter: Iterator; /// Open a file given the filename, create the file if not exists when `touch` is `true`. - async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError>; + async fn open_file(&self, filename: &str, touch: bool) -> Result; /// Unlink a file given the filename. async fn remove_file(&self, filename: String) -> Result<(), WalError>; /// Enumerate all Wal filenames. It should include all Wal files that are previously opened @@ -205,20 +208,21 @@ pub trait WalStore { fn enumerate_files(&self) -> Result; } -struct WalFileHandle<'a, F: WalStore> { +struct WalFileHandle<'a, F: WalFile + 'static, S: WalStore> { fid: WalFileId, handle: &'a dyn WalFile, - pool: *const WalFilePool, + pool: *const WalFilePool, + wal_file: PhantomData, } -impl<'a, F: WalStore> std::ops::Deref for WalFileHandle<'a, F> { +impl<'a, F: WalFile, S: WalStore> std::ops::Deref for WalFileHandle<'a, F, S> { type Target = dyn WalFile + 'a; fn deref(&self) -> &Self::Target { self.handle } } -impl<'a, F: WalStore> Drop for WalFileHandle<'a, F> { +impl<'a, F: WalFile + 'static, S: WalStore> Drop for WalFileHandle<'a, F, S> { fn drop(&mut self) { unsafe { (*self.pool).release_file(self.fid); @@ -228,9 +232,9 @@ impl<'a, F: WalStore> Drop for WalFileHandle<'a, F> { /// The middle layer that manages WAL file handles and invokes public trait functions to actually /// manipulate files and their contents. -struct WalFilePool { - store: F, - header_file: Box, +struct WalFilePool> { + store: S, + header_file: F, handle_cache: RefCell>>, #[allow(clippy::type_complexity)] handle_used: RefCell, usize)>>>, @@ -243,9 +247,9 @@ struct WalFilePool { block_nbit: u64, } -impl WalFilePool { +impl> WalFilePool { async fn new( - store: F, + store: S, file_nbit: u64, block_nbit: u64, cache_size: NonZeroUsize, @@ -283,8 +287,8 @@ impl WalFilePool { #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to remove mutable reference from being awaited. - async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { - let pool = self as *const WalFilePool; + async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { + let pool = self as *const WalFilePool; if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Vacant(e) => unsafe { @@ -292,15 +296,20 @@ impl WalFilePool { }, _ => unreachable!(), }; - Ok(WalFileHandle { fid, handle, pool }) + Ok(WalFileHandle { + fid, + handle, + pool, + wal_file: PhantomData, + }) } else { let v = unsafe { &mut *match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => e.insert(UnsafeCell::new(( - self.store.open_file(&get_fname(fid), touch).await?, - 0, - ))), + hash_map::Entry::Vacant(e) => { + let file = self.store.open_file(&get_fname(fid), touch).await?; + e.insert(UnsafeCell::new((Box::new(file), 0))) + } } .get() }; @@ -309,6 +318,7 @@ impl WalFilePool { fid, handle: &*v.0, pool, + wal_file: PhantomData, }) } } @@ -357,7 +367,7 @@ impl WalFilePool { let alloc = async move { last_write.await?; let mut last_h: Option< - Pin, WalError>> + 'a>>, + Pin, WalError>> + 'a>>, > = None; for ((next_fid, wl), h) in meta.into_iter().zip(files.into_iter()) { if let Some(lh) = last_h.take() { @@ -384,18 +394,22 @@ impl WalFilePool { } Ok(()) }; + let mut res = Vec::new(); let mut prev = Box::pin(alloc) as Pin + 'a>>; + for (off, w) in writes.into_iter() { let f = self.get_file(off >> file_nbit, true); let w = (async move { prev.await?; - f.await?.write(off & (file_size - 1), w).await + let f = f.await?; + f.write(off & (file_size - 1), w).await }) .shared(); prev = Box::pin(w.clone()); res.push(Box::pin(w) as Pin + 'a>>) } + unsafe { (*self.last_write.get()) = MaybeUninit::new(std::mem::transmute::< Pin + 'a>>, @@ -453,18 +467,18 @@ impl WalFilePool { } } -pub struct WalWriter { +pub struct WalWriter> { state: WalState, - file_pool: WalFilePool, + file_pool: WalFilePool, block_buffer: WalBytes, block_size: u32, msize: usize, } -unsafe impl Send for WalWriter where F: WalStore + Send {} +unsafe impl Send for WalWriter where S: WalStore + Send {} -impl WalWriter { - fn new(state: WalState, file_pool: WalFilePool) -> Self { +impl> WalWriter { + fn new(state: WalState, file_pool: WalFilePool) -> Self { let mut b = Vec::new(); let block_size = 1 << file_pool.block_nbit as u32; let msize = std::mem::size_of::(); @@ -839,8 +853,8 @@ impl WalLoader { #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to a more safe solution. - fn read_rings<'a, F: WalStore + 'a>( - file: &'a WalFileHandle<'a, F>, + fn read_rings<'a, F: WalFile + 'static, S: WalStore + 'a>( + file: &'a WalFileHandle<'a, F, S>, read_payload: bool, block_nbit: u64, recover_policy: &'a RecoverPolicy, @@ -848,10 +862,10 @@ impl WalLoader { let block_size = 1 << block_nbit; let msize = std::mem::size_of::(); - struct Vars<'a, F: WalStore> { + struct Vars<'a, F: WalFile + 'static, S: WalStore> { done: bool, off: u64, - file: &'a WalFileHandle<'a, F>, + file: &'a WalFileHandle<'a, F, S>, } let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { @@ -948,9 +962,9 @@ impl WalLoader { } #[allow(clippy::await_holding_refcell_ref)] - fn read_records<'a, F: WalStore + 'a>( + fn read_records<'a, F: WalFile + 'static, S: WalStore + 'a>( &'a self, - file: &'a WalFileHandle<'a, F>, + file: &'a WalFileHandle<'a, F, S>, chunks: &'a mut Option<(Vec, WalPos)>, ) -> impl futures::Stream> + 'a { let fid = file.fid; @@ -958,11 +972,11 @@ impl WalLoader { let block_size = 1 << self.block_nbit; let msize = std::mem::size_of::(); - struct Vars<'a, F: WalStore> { + struct Vars<'a, F: WalFile + 'static, S: WalStore> { done: bool, chunks: &'a mut Option<(Vec, WalPos)>, off: u64, - file: &'a WalFileHandle<'a, F>, + file: &'a WalFileHandle<'a, F, S>, } let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { @@ -1115,12 +1129,16 @@ impl WalLoader { } /// Recover by reading the Wal files. - pub async fn load Result<(), WalError>>( + pub async fn load< + F: WalFile + 'static, + S: WalStore, + Func: FnMut(WalBytes, WalRingId) -> Result<(), WalError>, + >( &self, store: S, - mut recover_func: F, + mut recover_func: Func, keep_nrecords: u32, - ) -> Result, WalError> { + ) -> Result, WalError> { let msize = std::mem::size_of::(); assert!(self.file_nbit > self.block_nbit); assert!(msize < 1 << self.block_nbit); @@ -1141,7 +1159,7 @@ impl WalLoader { let mut chunks = None; let mut pre_skip = true; - let mut scanned: Vec<(String, WalFileHandle)> = Vec::new(); + let mut scanned: Vec<(String, WalFileHandle)> = Vec::new(); let mut counter = 0; // TODO: check for missing logfiles diff --git a/firewood-growth-ring/tests/common/mod.rs b/firewood-growth-ring/tests/common/mod.rs index 402114ee6852..a7580d67dd7f 100644 --- a/firewood-growth-ring/tests/common/mod.rs +++ b/firewood-growth-ring/tests/common/mod.rs @@ -119,27 +119,31 @@ impl<'a, G: FailGen> WalStoreEmul<'a, G> { } #[async_trait(?Send)] -impl<'a, G> WalStore for WalStoreEmul<'a, G> +impl<'a, G> WalStore> for WalStoreEmul<'a, G> where G: 'static + FailGen, { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError> { + async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError> { if self.fgen.next_fail() { return Err(WalError::Other("open_file fgen next fail".to_string())); } match self.state.borrow_mut().files.entry(filename.to_string()) { - hash_map::Entry::Occupied(e) => Ok(Box::new(WalFileEmul { - file: e.get().clone(), - fgen: self.fgen.clone(), - })), + hash_map::Entry::Occupied(e) => { + let file = WalFileEmul { + file: e.get().clone(), + fgen: self.fgen.clone(), + }; + Ok(file) + } hash_map::Entry::Vacant(e) => { if touch { - Ok(Box::new(WalFileEmul { + let file = WalFileEmul { file: e.insert(Rc::new(FileContentEmul::new())).clone(), fgen: self.fgen.clone(), - })) + }; + Ok(file) } else { Err(WalError::Other("open_file not found".to_string())) } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 46eae2f36c55..0e690e4d77f6 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -11,6 +11,7 @@ use super::{ }; use aiofut::{AioBuilder, AioError, AioManager}; +use growthring::WalFileAio; use growthring::{ wal::{RecoverPolicy, WalLoader, WalWriter}, walerror::WalError, @@ -92,7 +93,7 @@ pub struct DiskBuffer { local_pool: Rc, task_id: u64, tasks: Rc>>>>, - wal: Option>>>, + wal: Option>>>, cfg: DiskBufferConfig, wal_cfg: WalConfig, } From a4f4c805798ed9cde7f2c56c655b9196dfc04766 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Jun 2023 20:24:38 -0400 Subject: [PATCH 0176/1053] Test WAL read/write (#123) --- firewood-growth-ring/src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index 756cba48f113..d5a2cab6061c 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -201,7 +201,7 @@ mod tests { async fn truncation_makes_a_file_smaller() { const HALF_LENGTH: usize = 512; - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); @@ -232,7 +232,7 @@ mod tests { async fn truncation_extends_a_file_with_zeros() { const LENGTH: usize = 512; - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); @@ -257,7 +257,7 @@ mod tests { #[tokio::test] async fn write_and_read_full() { let walfile = { - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); WalFileAio::open_file(walfile_path).await.unwrap() }; @@ -279,7 +279,7 @@ mod tests { #[tokio::test] async fn write_and_read_subset() { let walfile = { - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); WalFileAio::open_file(walfile_path).await.unwrap() }; @@ -290,13 +290,10 @@ mod tests { }; let data: Vec = (0..=u8::MAX).collect(); - walfile_aio.write(0, data.clone().into()).await.unwrap(); let mid = data.len() / 2; - let (start, end) = data.split_at(mid); - let read_start_result = walfile_aio.read(0, mid).await.unwrap(); let read_end_result = walfile_aio.read(mid as u64, mid).await.unwrap(); @@ -307,7 +304,7 @@ mod tests { #[tokio::test] async fn write_and_read_beyond_len() { let walfile = { - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); WalFileAio::open_file(walfile_path).await.unwrap() }; @@ -334,7 +331,7 @@ mod tests { const OFFSET: u64 = 2; let walfile = { - let walfile_path = get_walfile_path(file!(), line!()); + let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); WalFileAio::open_file(walfile_path).await.unwrap() }; @@ -364,7 +361,7 @@ mod tests { assert_eq!(result, Some(data.into())); } - fn get_walfile_path(file: &str, line: u32) -> PathBuf { + fn get_temp_walfile_path(file: &str, line: u32) -> PathBuf { let path = option_env!("CARGO_TARGET_TMPDIR").unwrap_or("/tmp"); Path::new(path).join(format!("{}_{}", file.replace('/', "-"), line)) } From 73894f7a509d0c5ebb9944ffe8c6e25e36cc59f2 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:06:39 -0700 Subject: [PATCH 0177/1053] Ensure `StoreRevMut` can handle redo (#115) --- firewood/Cargo.toml | 1 + firewood/src/db.rs | 10 ++-- firewood/src/storage/buffer.rs | 101 +++++++++++++++++++++++++++++++-- firewood/src/storage/mod.rs | 80 ++++++++++++++------------ 4 files changed, 149 insertions(+), 43 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index b61ebd18c902..2111a10d95f3 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -47,6 +47,7 @@ predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.0.29" } bencher = "0.1.5" +tempdir = "0.3.7" [features] # proof API diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 14fb9c91badb..33651d82d8c8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1048,14 +1048,16 @@ impl WriteBatch { if let Some(rev) = revisions.inner.front_mut() { rev.merkle .meta - .set_prev(new_base.merkle.meta.inner().clone()); + .set_base_space(new_base.merkle.meta.inner().clone()); rev.merkle .payload - .set_prev(new_base.merkle.payload.inner().clone()); - rev.blob.meta.set_prev(new_base.blob.meta.inner().clone()); + .set_base_space(new_base.merkle.payload.inner().clone()); + rev.blob + .meta + .set_base_space(new_base.blob.meta.inner().clone()); rev.blob .payload - .set_prev(new_base.blob.payload.inner().clone()); + .set_base_space(new_base.blob.payload.inner().clone()); } revisions.inner.push_front(new_base); while revisions.inner.len() > revisions.max_revisions { diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 0e690e4d77f6..bf70f1420faf 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -497,19 +497,24 @@ impl DiskBufferRequester { #[cfg(test)] mod tests { - use std::path::PathBuf; + use sha3::Digest; + use std::path::{Path, PathBuf}; + use tempdir::TempDir; use super::*; use crate::{ file, - storage::{Ash, DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta}, + storage::{ + Ash, DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta, StoreRevShared, ZeroStore, + }, }; use shale::CachedStore; const STATE_SPACE: SpaceID = 0x0; + const HASH_SIZE: usize = 32; #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] - fn test_buffer() { + fn test_buffer_with_undo() { let tmpdb = [ &std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".to_string()), "sender_api_test_db", @@ -548,7 +553,7 @@ mod tests { disk_requester.reg_cached_space(state_cache.as_ref()); // memory mapped store - let mut mut_store = StoreRevMut::new(state_cache as Rc); + let mut mut_store = StoreRevMut::new(state_cache); let change = b"this is a test"; @@ -598,6 +603,94 @@ mod tests { assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); } + #[test] + fn test_buffer_with_redo() { + let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let wal_cfg = WalConfig::builder().build(); + let disk_requester = init_buffer(buf_cfg, wal_cfg); + + // TODO: Run the test in a separate standalone directory for concurrency reasons + let tmp_dir = TempDir::new("firewood").unwrap(); + let path = get_file_path(tmp_dir.path(), file!(), line!()); + let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); + + // file descriptor of the state directory + let state_path = file::touch_dir("state", &root_db_path).unwrap(); + assert!(reset); + // create a new wal directory on top of root_db_fd + disk_requester.init_wal("wal", root_db_path); + + // create a new state cache which tracks on disk state. + let state_cache = Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(1) + .ncached_files(1) + .space_id(STATE_SPACE) + .file_nbit(1) + .rootdir(state_path) + .build(), + ) + .unwrap(), + ); + + // add an in memory cached space. this will allow us to write to the + // disk buffer then later persist the change to disk. + disk_requester.reg_cached_space(state_cache.as_ref()); + + // memory mapped store + let mut mut_store = StoreRevMut::new(state_cache.clone()); + + // mutate the in memory buffer. + let data = b"this is another test"; + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(&data).into(); + + // write to the in memory buffer (ash) not yet to disk + mut_store.write(0, &hash); + assert_eq!(mut_store.id(), STATE_SPACE); + + // wal should have no records. + assert!(disk_requester.collect_ash(1).unwrap().is_empty()); + + // get RO view of the buffer from the beginning. + let view = mut_store.get_view(0, hash.len() as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + + // Commit the change. Take the delta from cached store, + // then apply changes to the CachedSpace. + let (redo_delta, wal) = mut_store.take_delta(); + state_cache.update(&redo_delta).unwrap(); + + // create a mutation request to the disk buffer by passing the page and write batch. + // wal is empty + assert!(disk_requester.collect_ash(1).unwrap().is_empty()); + // page is not yet persisted to disk. + assert!(disk_requester.get_page(STATE_SPACE, 0).is_none()); + disk_requester.write( + vec![BufferWrite { + space_id: STATE_SPACE, + delta: redo_delta, + }], + AshRecord([(STATE_SPACE, wal)].into()), + ); + + // verify + assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); + let ashes = disk_requester.collect_ash(1).unwrap(); + + // replay the redo from the wal + let shared_store = StoreRevShared::from_ash( + Rc::new(ZeroStore::default()), + &ashes[0].0[&STATE_SPACE].redo, + ); + let view = shared_store.get_view(0, hash.len() as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + } + + fn get_file_path(path: &Path, file: &str, line: u32) -> PathBuf { + path.join(format!("{}_{}", file.replace('/', "-"), line)) + } + fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WalConfig) -> DiskBufferRequester { let (sender, inbound) = tokio::sync::mpsc::channel(buf_cfg.max_buffered); let disk_requester = DiskBufferRequester::new(sender); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index e33de11626b0..443a758177c0 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -63,7 +63,7 @@ pub struct SpaceWrite { data: Box<[u8]>, } -#[derive(Debug)] +#[derive(Debug, Default)] /// In memory representation of Write-ahead log with `undo` and `redo`. pub struct Ash { /// Deltas to undo the changes. @@ -263,7 +263,7 @@ impl StoreDelta { } pub struct StoreRev { - prev: RefCell>, + base_space: RefCell>, delta: StoreDelta, } @@ -279,7 +279,7 @@ impl fmt::Debug for StoreRev { impl MemStoreR for StoreRev { fn get_slice(&self, offset: u64, length: u64) -> Option> { - let prev = self.prev.borrow(); + let base_space = self.base_space.borrow(); let mut start = offset; let end = start + length; let delta = &self.delta; @@ -287,7 +287,7 @@ impl MemStoreR for StoreRev { let mut r = delta.len(); // no dirty page, before or after all dirty pages if r == 0 { - return prev.get_slice(start, end - start); + return base_space.get_slice(start, end - start); } // otherwise, some dirty pages are covered by the range while r - l > 1 { @@ -302,12 +302,12 @@ impl MemStoreR for StoreRev { l += 1 } if l >= delta.len() || end < delta[l].offset() { - return prev.get_slice(start, end - start); + return base_space.get_slice(start, end - start); } let mut data = Vec::new(); let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE); if start < delta[l].offset() { - data.extend(prev.get_slice(start, delta[l].offset() - start)?); + data.extend(base_space.get_slice(start, delta[l].offset() - start)?); data.extend(&delta[l].data()[..p_off as usize]); } else { data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]); @@ -316,11 +316,11 @@ impl MemStoreR for StoreRev { while start < end { l += 1; if l >= delta.len() || end < delta[l].offset() { - data.extend(prev.get_slice(start, end - start)?); + data.extend(base_space.get_slice(start, end - start)?); break; } if delta[l].offset() > start { - data.extend(prev.get_slice(start, delta[l].offset() - start)?); + data.extend(base_space.get_slice(start, delta[l].offset() - start)?); } if end < delta[l].offset() + PAGE_SIZE { data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); @@ -334,7 +334,7 @@ impl MemStoreR for StoreRev { } fn id(&self) -> SpaceID { - self.prev.borrow().id() + self.base_space.borrow().id() } } @@ -342,19 +342,19 @@ impl MemStoreR for StoreRev { pub struct StoreRevShared(Rc); impl StoreRevShared { - pub fn from_ash(prev: Rc, writes: &[SpaceWrite]) -> Self { - let delta = StoreDelta::new(prev.as_ref(), writes); - let prev = RefCell::new(prev); - Self(Rc::new(StoreRev { prev, delta })) + pub fn from_ash(base_space: Rc, writes: &[SpaceWrite]) -> Self { + let delta = StoreDelta::new(base_space.as_ref(), writes); + let base_space = RefCell::new(base_space); + Self(Rc::new(StoreRev { base_space, delta })) } - pub fn from_delta(prev: Rc, delta: StoreDelta) -> Self { - let prev = RefCell::new(prev); - Self(Rc::new(StoreRev { prev, delta })) + pub fn from_delta(base_space: Rc, delta: StoreDelta) -> Self { + let base_space = RefCell::new(base_space); + Self(Rc::new(StoreRev { base_space, delta })) } - pub fn set_prev(&mut self, prev: Rc) { - *self.0.prev.borrow_mut() = prev + pub fn set_base_space(&mut self, base_space: Rc) { + *self.0.base_space.borrow_mut() = base_space } pub fn inner(&self) -> &Rc { @@ -421,26 +421,26 @@ impl DerefMut for StoreShared { } } -#[derive(Debug)] +#[derive(Debug, Default)] struct StoreRevMutDelta { pages: HashMap>, plain: Ash, } #[derive(Clone, Debug)] +/// A mutable revision of the store. The view is constucted by applying the `deltas` to the +/// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply +/// the changes. pub struct StoreRevMut { - prev: Rc, + base_space: Rc, deltas: Rc>, } impl StoreRevMut { - pub fn new(prev: Rc) -> Self { + pub fn new(base_space: Rc) -> Self { Self { - prev, - deltas: Rc::new(RefCell::new(StoreRevMutDelta { - pages: HashMap::new(), - plain: Ash::new(), - })), + base_space, + deltas: Default::default(), } } @@ -448,7 +448,7 @@ impl StoreRevMut { let mut deltas = self.deltas.borrow_mut(); if deltas.pages.get(&pid).is_none() { let page = Box::new( - self.prev + self.base_space .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) .unwrap() .try_into() @@ -494,23 +494,27 @@ impl CachedStore for StoreRevMut { if s_pid == e_pid { match deltas.get(&s_pid) { Some(p) => p[s_off..e_off + 1].to_vec(), - None => self.prev.get_slice(offset, length)?, + None => self.base_space.get_slice(offset, length)?, } } else { let mut data = match deltas.get(&s_pid) { Some(p) => p[s_off..].to_vec(), - None => self.prev.get_slice(offset, PAGE_SIZE - s_off as u64)?, + None => self + .base_space + .get_slice(offset, PAGE_SIZE - s_off as u64)?, }; for p in s_pid + 1..e_pid { match deltas.get(&p) { Some(p) => data.extend(**p), - None => data.extend(&self.prev.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?), + None => { + data.extend(&self.base_space.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?) + } }; } match deltas.get(&e_pid) { Some(p) => data.extend(&p[..e_off + 1]), None => data.extend( - self.prev + self.base_space .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, ), } @@ -532,40 +536,46 @@ impl CachedStore for StoreRevMut { let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; let mut undo: Vec = Vec::new(); - let data: Box<[u8]> = change.into(); + let redo: Box<[u8]> = change.into(); + if s_pid == e_pid { let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change) } else { let len = PAGE_SIZE as usize - s_off; + { let slice = &mut self.get_page_mut(s_pid)[s_off..]; undo.extend(&*slice); slice.copy_from_slice(&change[..len]); } + change = &change[len..]; + for p in s_pid + 1..e_pid { let mut slice = self.get_page_mut(p); undo.extend(&*slice); slice.copy_from_slice(&change[..PAGE_SIZE as usize]); change = &change[PAGE_SIZE as usize..]; } + let slice = &mut self.get_page_mut(e_pid)[..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change); } + let plain = &mut self.deltas.borrow_mut().plain; - assert!(undo.len() == data.len()); + assert!(undo.len() == redo.len()); plain.undo.push(SpaceWrite { offset, data: undo.into(), }); - plain.redo.push(SpaceWrite { offset, data }); + plain.redo.push(SpaceWrite { offset, data: redo }); } fn id(&self) -> SpaceID { - self.prev.id() + self.base_space.id() } } From c97219d04178ff96a1da0ddb80df3076554a0986 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 6 Jun 2023 16:22:06 -0400 Subject: [PATCH 0178/1053] migrate growth ring fd to tokio file (#122) --- firewood-growth-ring/Cargo.toml | 2 +- firewood-growth-ring/examples/demo1.rs | 10 +- firewood-growth-ring/src/lib.rs | 187 ++++++++++++------------- firewood/src/storage/buffer.rs | 9 +- firewood/tests/db.rs | 2 +- 5 files changed, 98 insertions(+), 112 deletions(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index b32f14a24ef5..680388cb0942 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -20,7 +20,7 @@ nix = "0.26.2" libc = "0.2.133" bytemuck = {version = "1.13.1", features = ["derive"]} thiserror = "1.0.40" -tokio = { version = "1.28.1", features = ["fs"] } +tokio = { version = "1.28.1", features = ["fs", "io-util", "sync"] } [dev-dependencies] hex = "0.4.3" diff --git a/firewood-growth-ring/examples/demo1.rs b/firewood-growth-ring/examples/demo1.rs index 5ac22f788e89..a75e89ff6933 100644 --- a/firewood-growth-ring/examples/demo1.rs +++ b/firewood-growth-ring/examples/demo1.rs @@ -2,11 +2,11 @@ use futures::executor::block_on; use growthring::{ wal::{WalBytes, WalLoader, WalRingId, WalWriter}, walerror::WalError, - WalFileAio, WalStoreAio, + WalFileImpl, WalStoreImpl, }; use rand::{seq::SliceRandom, Rng, SeedableRng}; -fn test(records: Vec, wal: &mut WalWriter) -> Vec { +fn test(records: Vec, wal: &mut WalWriter) -> Vec { let mut res = Vec::new(); for r in wal.grow(records).into_iter() { let ring_id = futures::executor::block_on(r).unwrap().1; @@ -31,7 +31,7 @@ fn main() { let mut loader = WalLoader::new(); loader.file_nbit(9).block_nbit(8); - let store = WalStoreAio::new(wal_dir, true, None).unwrap(); + let store = WalStoreImpl::new(wal_dir, true).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -49,7 +49,7 @@ fn main() { ); } - let store = WalStoreAio::new(wal_dir, false, None).unwrap(); + let store = WalStoreImpl::new(wal_dir, false).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { test( @@ -63,7 +63,7 @@ fn main() { ); } - let store = WalStoreAio::new(wal_dir, false, None).unwrap(); + let store = WalStoreImpl::new(wal_dir, false).unwrap(); let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); let mut history = std::collections::VecDeque::new(); for _ in 0..3 { diff --git a/firewood-growth-ring/src/lib.rs b/firewood-growth-ring/src/lib.rs index d5a2cab6061c..4e5c69070bb6 100644 --- a/firewood-growth-ring/src/lib.rs +++ b/firewood-growth-ring/src/lib.rs @@ -3,14 +3,14 @@ //! # Examples //! //! ```no_run -//! use growthring::{WalStoreAio, wal::WalLoader}; +//! use growthring::{WalStoreImpl, wal::WalLoader}; //! use futures::executor::block_on; //! let mut loader = WalLoader::new(); //! loader.file_nbit(9).block_nbit(8); //! //! //! // Start with empty WAL (truncate = true). -//! let store = WalStoreAio::new("/tmp/walfiles", true, None).unwrap(); +//! let store = WalStoreImpl::new("/tmp/walfiles", true).unwrap(); //! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); //! // Write a vector of records to WAL. //! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { @@ -20,7 +20,7 @@ //! //! //! // Load from WAL (truncate = false). -//! let store = WalStoreAio::new("/tmp/walfiles", false, None).unwrap(); +//! let store = WalStoreImpl::new("/tmp/walfiles", false).unwrap(); //! let mut wal = block_on(loader.load(store, |payload, ringid| { //! // redo the operations in your application //! println!("recover(payload={}, ringid={:?})", @@ -37,7 +37,7 @@ //! block_on(wal.peel(ring_ids, 0)).unwrap(); //! // There will only be one remaining file in /tmp/walfiles. //! -//! let store = WalStoreAio::new("/tmp/walfiles", false, None).unwrap(); +//! let store = WalStoreImpl::new("/tmp/walfiles", false).unwrap(); //! let wal = block_on(loader.load(store, |payload, _| { //! println!("payload.len() = {}", payload.len()); //! Ok(()) @@ -51,23 +51,22 @@ pub mod wal; pub mod walerror; use async_trait::async_trait; -use firewood_libaio::{AioBuilder, AioManager}; use nix::fcntl::OFlag; use std::fs; -use std::os::fd::AsRawFd; +use std::io::SeekFrom; use std::path::{Path, PathBuf}; -use std::sync::Arc; -use tokio::fs::{File, OpenOptions}; +use tokio::{ + fs::{File, OpenOptions}, + io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, + sync::Mutex, +}; use wal::{WalBytes, WalFile, WalPos, WalStore}; use walerror::WalError; -pub struct WalFileAio { - file: File, - aio_manager: Arc, -} +struct RawWalFile(File); -impl WalFileAio { - async fn open_file>(path: P) -> Result { +impl RawWalFile { + pub async fn open>(path: P) -> Result { OpenOptions::new() .read(true) .write(true) @@ -76,68 +75,73 @@ impl WalFileAio { .mode(0o600) .open(path) .await + .map(Self) } +} + +pub struct WalFileImpl { + file_mutex: Mutex, +} - fn new(file: File, aio_manager: Arc) -> Self { - Self { file, aio_manager } +impl From for WalFileImpl { + fn from(file: RawWalFile) -> Self { + let file = Mutex::new(file); + Self { file_mutex: file } } } #[async_trait(?Send)] -impl WalFile for WalFileAio { +impl WalFile for WalFileImpl { async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { - self.file + self.file_mutex + .lock() + .await + .0 .set_len(offset + length as u64) .await .map_err(Into::into) } async fn truncate(&self, len: usize) -> Result<(), WalError> { - self.file.set_len(len as u64).await.map_err(Into::into) + self.file_mutex + .lock() + .await + .0 + .set_len(len as u64) + .await + .map_err(Into::into) } async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { - let fd = self.file.as_raw_fd(); - let (res, data) = self.aio_manager.write(fd, offset, data, None).await; - res.map_err(Into::into).and_then(|nwrote| { - if nwrote == data.len() { - Ok(()) - } else { - Err(WalError::Other(format!( - "partial write; wrote {nwrote} expected {} for fd {}", - data.len(), - fd - ))) - } - }) + let file = &mut self.file_mutex.lock().await.0; + file.seek(SeekFrom::Start(offset)).await?; + + Ok(file.write_all(&data).await?) } async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { - let fd = self.file.as_raw_fd(); - let (res, data) = self.aio_manager.read(fd, offset, length, None).await; - res.map_err(From::from) - .map(|nread| if nread == length { Some(data) } else { None }) + let (result, bytes_read) = { + let mut result = Vec::with_capacity(length); + let file = &mut self.file_mutex.lock().await.0; + file.seek(SeekFrom::Start(offset)).await?; + let bytes_read = file.read_buf(&mut result).await?; + (result, bytes_read) + }; + + let result = Some(result) + .filter(|_| bytes_read == length) + .map(Vec::into_boxed_slice); + + Ok(result) } } -pub struct WalStoreAio { +pub struct WalStoreImpl { root_dir: PathBuf, - aiomgr: Arc, } -unsafe impl Send for WalStoreAio {} - -impl WalStoreAio { - pub fn new>( - wal_dir: P, - truncate: bool, - aiomgr: Option, - ) -> Result { - let aio = match aiomgr { - Some(aiomgr) => Arc::new(aiomgr), - None => Arc::new(AioBuilder::default().build()?), - }; - +impl WalStoreImpl { + pub fn new>(wal_dir: P, truncate: bool) -> Result { if truncate { if let Err(e) = fs::remove_dir_all(&wal_dir) { if e.kind() != std::io::ErrorKind::NotFound { @@ -150,9 +154,8 @@ impl WalStoreAio { fs::create_dir(&wal_dir)?; } - Ok(WalStoreAio { + Ok(WalStoreImpl { root_dir: wal_dir.as_ref().to_path_buf(), - aiomgr: aio, }) } } @@ -168,15 +171,15 @@ pub fn oflags() -> OFlag { } #[async_trait(?Send)] -impl WalStore for WalStoreAio { +impl WalStore for WalStoreImpl { type FileNameIter = std::vec::IntoIter; - async fn open_file(&self, filename: &str, _touch: bool) -> Result { + async fn open_file(&self, filename: &str, _touch: bool) -> Result { let path = self.root_dir.join(filename); - let file = WalFileAio::open_file(path).await?; + let file = RawWalFile::open(path).await?; - Ok(WalFileAio::new(file, self.aiomgr.clone())) + Ok(file.into()) } async fn remove_file(&self, filename: String) -> Result<(), WalError> { @@ -205,11 +208,9 @@ mod tests { tokio::fs::remove_file(&walfile_path).await.ok(); - let aio_manager = AioBuilder::default().build().unwrap(); - - let walfile = WalFileAio::open_file(walfile_path).await.unwrap(); + let walfile = RawWalFile::open(walfile_path).await.unwrap(); - let walfile_aio = WalFileAio::new(walfile, Arc::new(aio_manager)); + let walfile_impl = WalFileImpl::from(walfile); let first_half = vec![1u8; HALF_LENGTH]; let second_half = vec![2u8; HALF_LENGTH]; @@ -220,10 +221,10 @@ mod tests { .chain(second_half.iter().copied()) .collect(); - walfile_aio.write(0, data).await.unwrap(); - walfile_aio.truncate(HALF_LENGTH).await.unwrap(); + walfile_impl.write(0, data).await.unwrap(); + walfile_impl.truncate(HALF_LENGTH).await.unwrap(); - let result = walfile_aio.read(0, HALF_LENGTH).await.unwrap(); + let result = walfile_impl.read(0, HALF_LENGTH).await.unwrap(); assert_eq!(result, Some(first_half.into())) } @@ -236,20 +237,18 @@ mod tests { tokio::fs::remove_file(&walfile_path).await.ok(); - let aio_manager = AioBuilder::default().build().unwrap(); + let walfile = RawWalFile::open(walfile_path).await.unwrap(); - let walfile = WalFileAio::open_file(walfile_path).await.unwrap(); + let walfile_impl = WalFileImpl::from(walfile); - let walfile_aio = WalFileAio::new(walfile, Arc::new(aio_manager)); - - walfile_aio + walfile_impl .write(0, vec![1u8; LENGTH].into()) .await .unwrap(); - walfile_aio.truncate(2 * LENGTH).await.unwrap(); + walfile_impl.truncate(2 * LENGTH).await.unwrap(); - let result = walfile_aio.read(LENGTH as u64, LENGTH).await.unwrap(); + let result = walfile_impl.read(LENGTH as u64, LENGTH).await.unwrap(); assert_eq!(result, Some(vec![0u8; LENGTH].into())) } @@ -259,19 +258,16 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); - WalFileAio::open_file(walfile_path).await.unwrap() + RawWalFile::open(walfile_path).await.unwrap() }; - let walfile_aio = { - let aio_manager = AioBuilder::default().build().unwrap(); - WalFileAio::new(walfile, Arc::new(aio_manager)) - }; + let walfile_impl = WalFileImpl::from(walfile); let data: Vec = (0..=u8::MAX).collect(); - walfile_aio.write(0, data.clone().into()).await.unwrap(); + walfile_impl.write(0, data.clone().into()).await.unwrap(); - let result = walfile_aio.read(0, data.len()).await.unwrap(); + let result = walfile_impl.read(0, data.len()).await.unwrap(); assert_eq!(result, Some(data.into())); } @@ -281,21 +277,18 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); - WalFileAio::open_file(walfile_path).await.unwrap() + RawWalFile::open(walfile_path).await.unwrap() }; - let walfile_aio = { - let aio_manager = AioBuilder::default().build().unwrap(); - WalFileAio::new(walfile, Arc::new(aio_manager)) - }; + let walfile_impl = WalFileImpl::from(walfile); let data: Vec = (0..=u8::MAX).collect(); - walfile_aio.write(0, data.clone().into()).await.unwrap(); + walfile_impl.write(0, data.clone().into()).await.unwrap(); let mid = data.len() / 2; let (start, end) = data.split_at(mid); - let read_start_result = walfile_aio.read(0, mid).await.unwrap(); - let read_end_result = walfile_aio.read(mid as u64, mid).await.unwrap(); + let read_start_result = walfile_impl.read(0, mid).await.unwrap(); + let read_end_result = walfile_impl.read(mid as u64, mid).await.unwrap(); assert_eq!(read_start_result, Some(start.into())); assert_eq!(read_end_result, Some(end.into())); @@ -306,19 +299,16 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); - WalFileAio::open_file(walfile_path).await.unwrap() + RawWalFile::open(walfile_path).await.unwrap() }; - let walfile_aio = { - let aio_manager = AioBuilder::default().build().unwrap(); - WalFileAio::new(walfile, Arc::new(aio_manager)) - }; + let walfile_impl = WalFileImpl::from(walfile); let data: Vec = (0..=u8::MAX).collect(); - walfile_aio.write(0, data.clone().into()).await.unwrap(); + walfile_impl.write(0, data.clone().into()).await.unwrap(); - let result = walfile_aio + let result = walfile_impl .read((data.len() / 2) as u64, data.len()) .await .unwrap(); @@ -333,22 +323,19 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); - WalFileAio::open_file(walfile_path).await.unwrap() + RawWalFile::open(walfile_path).await.unwrap() }; - let walfile_aio = { - let aio_manager = AioBuilder::default().build().unwrap(); - WalFileAio::new(walfile, Arc::new(aio_manager)) - }; + let walfile_impl = WalFileImpl::from(walfile); let data: Vec = (0..=u8::MAX).collect(); - walfile_aio + walfile_impl .write(OFFSET, data.clone().into()) .await .unwrap(); - let result = walfile_aio + let result = walfile_impl .read(0, data.len() + OFFSET as usize) .await .unwrap(); diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index bf70f1420faf..db31599ebb2e 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -11,11 +11,11 @@ use super::{ }; use aiofut::{AioBuilder, AioError, AioManager}; -use growthring::WalFileAio; +use growthring::WalFileImpl; use growthring::{ wal::{RecoverPolicy, WalLoader, WalWriter}, walerror::WalError, - WalStoreAio, + WalStoreImpl, }; use shale::SpaceID; use tokio::sync::oneshot::error::RecvError; @@ -93,7 +93,7 @@ pub struct DiskBuffer { local_pool: Rc, task_id: u64, tasks: Rc>>>>, - wal: Option>>>, + wal: Option>>>, cfg: DiskBufferConfig, wal_cfg: WalConfig, } @@ -188,8 +188,7 @@ impl DiskBuffer { let final_path = rootpath.clone().join(waldir.clone()); let mut aiobuilder = AioBuilder::default(); aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); - let aiomgr = aiobuilder.build()?; - let store = WalStoreAio::new(final_path.clone(), false, Some(aiomgr))?; + let store = WalStoreImpl::new(final_path.clone(), false)?; let mut loader = WalLoader::new(); loader .file_nbit(self.wal_cfg.file_nbit) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 06bba2566eca..535d054c03c9 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -25,7 +25,7 @@ fn test_basic_metrics() { .max_revisions(10) .build(), ); - let db = Db::new("test_revisions_db2", &cfg.clone().truncate(true).build()).unwrap(); + let db = Db::new("test_revisions_db2", &cfg.truncate(true).build()).unwrap(); let metrics = db.metrics(); assert_eq!(metrics.kv_get.hit_count.get(), 0); db.kv_get("a").ok(); From 29483aa0bd84558f5d9c899f80089b739a85d6a3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 6 Jun 2023 14:06:20 -0700 Subject: [PATCH 0179/1053] Add note about metrics work (#106) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b68b5d8d45d3..a2d0fe76da3f 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,9 @@ are uniquely identified by root hashes. operations do not see any changes. - [x] Be able to read-your-write in a batch that is not committed. Uncommitted changes will not be shown to any other concurrent readers. -- [ ] Add some metrics framework to support timings and volume for future milestones +- [x] Add some metrics framework to support timings and volume for future milestones +To support this, a new method Db::metrics() returns an object that can be serialized +into prometheus metrics or json (it implements [serde::Serialize]) ### Seasoned milestone This milestone will add support for proposals, including proposed future From ef985053f85f60fa7c6856f6d360e4f0ca046593 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:31:29 -0700 Subject: [PATCH 0180/1053] chore: disable `test_buffer_with_redo` (#128) --- firewood/src/storage/buffer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index db31599ebb2e..9639e0d662d7 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -603,6 +603,7 @@ mod tests { } #[test] + #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer_with_redo() { let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); let wal_cfg = WalConfig::builder().build(); From 4d6413c0b08020fb2d7534a4be7042ff8df23871 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 6 Jun 2023 22:17:07 -0400 Subject: [PATCH 0181/1053] Fix main cache (#127) --- .github/workflows/default-branch-cache.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index f2eda3b0d88e..17b07dc06ac5 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -46,7 +46,7 @@ jobs: gh extension install actions/gh-actions-cache REPO=${{ github.repository }} - BRANCH="refs/head/${{ github.ref }}" + BRANCH="refs/heads/${{ github.ref }}" echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) From 7f0c7179a15bce043a82cb4a6fd7f3cc202ac2fa Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 7 Jun 2023 10:12:50 -0400 Subject: [PATCH 0182/1053] Another attempt to properly delete main caches (#129) --- .github/workflows/default-branch-cache.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 17b07dc06ac5..3c5ed0a4f3a1 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -46,7 +46,7 @@ jobs: gh extension install actions/gh-actions-cache REPO=${{ github.repository }} - BRANCH="refs/heads/${{ github.ref }}" + BRANCH=${{ github.ref }} echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) From a35095350380089c72f4f9c31b6f4b4ccd7f5d5e Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 7 Jun 2023 15:18:07 -0400 Subject: [PATCH 0183/1053] Cleanup benchmark code (#125) --- firewood/Cargo.toml | 2 +- firewood/examples/benchmark.rs | 86 +++++++++++++++++----------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 2111a10d95f3..cadacc1c4582 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -45,7 +45,7 @@ triehash = "0.8.4" assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" -clap = { version = "4.0.29" } +clap = { version = "4.3.1", features = ['derive'] } bencher = "0.1.5" tempdir = "0.3.7" diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 08b473af6dcd..24c9b14842a6 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -4,6 +4,7 @@ use clap::Parser; use criterion::Criterion; use firewood::db::{Db, DbConfig, WalConfig}; +use rand::{rngs::StdRng, Rng, SeedableRng}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -18,51 +19,52 @@ struct Args { no_root_hash: bool, } -/// cargo run --example benchmark -- --nbatch 100 --batch-size 1000 fn main() { let args = Args::parse(); let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - { - use rand::{Rng, SeedableRng}; - let mut c = Criterion::default(); - let mut group = c.benchmark_group("insert".to_string()); - let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed); - let nbatch = args.nbatch; - let batch_size = args.batch_size; - let total = nbatch * batch_size; - let root_hash = !args.no_root_hash; - let mut workload = Vec::new(); - for _ in 0..nbatch { - let mut batch: Vec<(Vec<_>, Vec<_>)> = Vec::new(); - for _ in 0..batch_size { - batch.push((rng.gen::<[u8; 32]>().into(), rng.gen::<[u8; 32]>().into())); - } - workload.push(batch); - } - println!("workload prepared"); - group - .sampling_mode(criterion::SamplingMode::Flat) - .sample_size(10); - group.throughput(criterion::Throughput::Elements(total as u64)); - group.bench_with_input( - format!("nbatch={nbatch} batch_size={batch_size}"), - &workload, - |b, workload| { - b.iter(|| { - let db = Db::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); - for batch in workload.iter() { - let mut wb = db.new_writebatch(); - for (k, v) in batch { - wb = wb.kv_insert(k, v.clone()).unwrap(); - } - if !root_hash { - wb = wb.no_root_hash(); - } - wb.commit(); + let mut c = Criterion::default(); + let mut group = c.benchmark_group("insert"); + let mut rng = StdRng::seed_from_u64(args.seed); + + let workload: Vec> = (0..args.nbatch) + .map(|_| { + (0..args.batch_size) + .map(|_| (rng.gen(), rng.gen())) + .collect() + }) + .collect(); + + println!("workload prepared"); + + group + .sampling_mode(criterion::SamplingMode::Flat) + .sample_size(10); + + let total = (args.nbatch * args.batch_size) as u64; + group.throughput(criterion::Throughput::Elements(total)); + + group.bench_with_input( + format!("nbatch={} batch_size={}", args.nbatch, args.batch_size), + &workload, + |b, workload| { + b.iter(|| { + let db = Db::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); + + for batch in workload.iter() { + let mut wb = db.new_writebatch(); + + for (k, v) in batch { + wb = wb.kv_insert(k, v.to_vec()).unwrap(); } - }) - }, - ); - } + + if args.no_root_hash { + wb = wb.no_root_hash(); + } + + wb.commit(); + } + }) + }, + ); } From a1ac06d5896ccb38dc96e4049e0f0e2920bc6791 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 7 Jun 2023 12:24:27 -0700 Subject: [PATCH 0184/1053] Fix clippy error (#130) --- firewood/src/storage/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 9639e0d662d7..bb480b78d658 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -643,7 +643,7 @@ mod tests { // mutate the in memory buffer. let data = b"this is another test"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(&data).into(); + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); // write to the in memory buffer (ash) not yet to disk mut_store.write(0, &hash); From 76893028d6f199166d0b4c1eed3cf532f558c6f2 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 7 Jun 2023 18:39:57 -0400 Subject: [PATCH 0185/1053] growth ring cleanup (#133) --- firewood-growth-ring/Cargo.toml | 1 - firewood-growth-ring/src/walerror.rs | 9 --------- 2 files changed, 10 deletions(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index 680388cb0942..a2fdf7a487be 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -9,7 +9,6 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -firewood-libaio= { version = "0.0.3", path = "../firewood-libaio", package = "firewood-libaio" } crc = "3.0.0" lru = "0.10.0" scan_fmt = "0.2.6" diff --git a/firewood-growth-ring/src/walerror.rs b/firewood-growth-ring/src/walerror.rs index 0d6f6c5156f4..6feb0b025ba6 100644 --- a/firewood-growth-ring/src/walerror.rs +++ b/firewood-growth-ring/src/walerror.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use firewood_libaio::AioError; use nix::errno::Errno; use thiserror::Error; @@ -14,8 +13,6 @@ pub enum WalError { InvalidChecksum, #[error("an I/O error has occurred")] IOError(Arc), - #[error("lib AIO error has occurred")] - AIOError(AioError), #[error("Wal directory already exists")] WalDirExists, } @@ -31,9 +28,3 @@ impl From for WalError { Self::IOError(Arc::new(err)) } } - -impl From for WalError { - fn from(err: AioError) -> Self { - Self::AIOError(err) - } -} From 3f80115d3c85c4465a1d9b4cb1ff6f60aa9f40f8 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 8 Jun 2023 14:56:40 -0400 Subject: [PATCH 0186/1053] Pages should always be boxed (#134) --- firewood-shale/src/cached.rs | 14 +++++------ firewood-shale/src/lib.rs | 10 ++++---- firewood/src/db.rs | 10 ++++---- firewood/src/storage/buffer.rs | 33 +++++++++++------------- firewood/src/storage/mod.rs | 46 ++++++++++++++++------------------ 5 files changed, 54 insertions(+), 59 deletions(-) diff --git a/firewood-shale/src/cached.rs b/firewood-shale/src/cached.rs index 2d4c67eb1b6a..0eea8c3dff36 100644 --- a/firewood-shale/src/cached.rs +++ b/firewood-shale/src/cached.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -use crate::{CachedStore, CachedView, SpaceID}; +use crate::{CachedStore, CachedView, SpaceId}; /// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying /// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write @@ -12,11 +12,11 @@ use crate::{CachedStore, CachedView, SpaceID}; #[derive(Debug)] pub struct PlainMem { space: Rc>>, - id: SpaceID, + id: SpaceId, } impl PlainMem { - pub fn new(size: u64, id: SpaceID) -> Self { + pub fn new(size: u64, id: SpaceId) -> Self { let mut space: Vec = Vec::new(); space.resize(size as usize, 0); let space = Rc::new(RefCell::new(space)); @@ -60,7 +60,7 @@ impl CachedStore for PlainMem { vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { self.id } } @@ -101,11 +101,11 @@ impl CachedView for PlainMemView { #[derive(Debug)] pub struct DynamicMem { space: Rc>>, - id: SpaceID, + id: SpaceId, } impl DynamicMem { - pub fn new(size: u64, id: SpaceID) -> Self { + pub fn new(size: u64, id: SpaceId) -> Self { let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); Self { space, id } } @@ -158,7 +158,7 @@ impl CachedStore for DynamicMem { self.get_space_mut()[offset..offset + length].copy_from_slice(change) } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { self.id } } diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index 374a12a3f889..a467082b75d9 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -41,11 +41,11 @@ pub enum ShaleError { #[error("write error")] pub struct ObjWriteError; -pub type SpaceID = u8; -pub const INVALID_SPACE_ID: SpaceID = 0xff; +pub type SpaceId = u8; +pub const INVALID_SPACE_ID: SpaceId = 0xff; pub struct DiskWrite { - pub space_id: SpaceID, + pub space_id: SpaceId, pub space_off: u64, pub data: Box<[u8]>, } @@ -86,7 +86,7 @@ pub trait CachedStore: Debug { /// should be immediately visible to all `CachedView` associated to this linear space. fn write(&mut self, offset: u64, change: &[u8]); /// Returns the identifier of this storage space. - fn id(&self) -> SpaceID; + fn id(&self) -> SpaceId; } /// Opaque typed pointer in the 64-bit virtual addressable space. @@ -203,7 +203,7 @@ impl Obj { } #[inline(always)] - pub fn get_space_id(&self) -> SpaceID { + pub fn get_space_id(&self) -> SpaceId { self.value.get_mem_store().id() } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 33651d82d8c8..da5ad42e0c2c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -16,7 +16,7 @@ use parking_lot::{Mutex, RwLock}; #[cfg(feature = "eth")] use primitive_types::U256; use shale::ShaleError; -use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceID, Storable, StoredView}; +use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceId, Storable, StoredView}; use typed_builder::TypedBuilder; #[cfg(feature = "eth")] @@ -31,10 +31,10 @@ use crate::storage::{ StoreRevShared, PAGE_SIZE_NBIT, }; -const MERKLE_META_SPACE: SpaceID = 0x0; -const MERKLE_PAYLOAD_SPACE: SpaceID = 0x1; -const BLOB_META_SPACE: SpaceID = 0x2; -const BLOB_PAYLOAD_SPACE: SpaceID = 0x3; +const MERKLE_META_SPACE: SpaceId = 0x0; +const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; +const BLOB_META_SPACE: SpaceId = 0x2; +const BLOB_PAYLOAD_SPACE: SpaceId = 0x3; const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index bb480b78d658..af1bade5d4fb 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -17,7 +17,7 @@ use growthring::{ walerror::WalError, WalStoreImpl, }; -use shale::SpaceID; +use shale::SpaceId; use tokio::sync::oneshot::error::RecvError; use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; use typed_builder::TypedBuilder; @@ -29,10 +29,10 @@ pub enum BufferCmd { /// Process a write batch against the underlying store. WriteBatch(Vec, AshRecord), /// Get a page from the disk buffer. - GetPage((SpaceID, u64), oneshot::Sender>>), + GetPage((SpaceId, u64), oneshot::Sender>), CollectAsh(usize, oneshot::Sender>), /// Register a new space and add the files to a memory mapped pool. - RegCachedSpace(SpaceID, Arc), + RegCachedSpace(SpaceId, Arc), /// Returns false if the Shutdown, } @@ -69,13 +69,13 @@ pub struct DiskBufferConfig { /// List of pages to write to disk. #[derive(Debug)] pub struct BufferWrite { - pub space_id: SpaceID, + pub space_id: SpaceId, pub delta: StoreDelta, } #[derive(Debug)] struct PendingPage { - staging_data: Arc, + staging_data: Page, file_nbit: u64, staging_notifiers: Vec>, writing_notifiers: Vec>, @@ -84,7 +84,7 @@ struct PendingPage { /// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] /// and managing the persistance of pages. pub struct DiskBuffer { - pending: HashMap<(SpaceID, u64), PendingPage>, + pending: HashMap<(SpaceId, u64), PendingPage>, inbound: mpsc::Receiver, fc_notifier: Option>, fc_blocker: Option>, @@ -133,7 +133,7 @@ impl DiskBuffer { } /// Add an pending pages to aio manager for processing by the local pool. - fn schedule_write(&mut self, page_key: (SpaceID, u64)) { + fn schedule_write(&mut self, page_key: (SpaceId, u64)) { let p = self.pending.get(&page_key).unwrap(); let offset = page_key.1 << PAGE_SIZE_NBIT; let fid = offset >> p.file_nbit; @@ -143,12 +143,9 @@ impl DiskBuffer { .unwrap() .get_file(fid) .unwrap(); - let fut = self.aiomgr.write( - file.get_fd(), - offset & fmask, - Box::new(*p.staging_data), - None, - ); + let fut = self + .aiomgr + .write(file.get_fd(), offset & fmask, p.staging_data.clone(), None); let s = unsafe { self.get_longlive_self() }; self.start_task(async move { let (res, _) = fut.await; @@ -157,7 +154,7 @@ impl DiskBuffer { }); } - fn finish_write(&mut self, page_key: (SpaceID, u64)) { + fn finish_write(&mut self, page_key: (SpaceId, u64)) { use std::collections::hash_map::Entry::*; match self.pending.entry(page_key) { Occupied(mut e) => { @@ -274,7 +271,7 @@ impl DiskBuffer { match self.pending.entry(page_key) { Occupied(mut e) => { let e = e.get_mut(); - e.staging_data = w.1.into(); + e.staging_data = w.1; e.staging_notifiers.push(sem.clone()); npermit += 1; } @@ -284,7 +281,7 @@ impl DiskBuffer { .unwrap() .file_nbit; e.insert(PendingPage { - staging_data: w.1.into(), + staging_data: w.1, file_nbit, staging_notifiers: Vec::new(), writing_notifiers: vec![sem.clone()], @@ -444,7 +441,7 @@ impl DiskBufferRequester { } /// Get a page from the buffer. - pub fn get_page(&self, space_id: SpaceID, pid: u64) -> Option> { + pub fn get_page(&self, space_id: SpaceId, pid: u64) -> Option { let (resp_tx, resp_rx) = oneshot::channel(); self.sender .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) @@ -509,7 +506,7 @@ mod tests { }; use shale::CachedStore; - const STATE_SPACE: SpaceID = 0x0; + const STATE_SPACE: SpaceId = 0x0; const HASH_SIZE: usize = 32; #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 443a758177c0..a3354346551e 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -11,7 +11,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; -use shale::{CachedStore, CachedView, SpaceID}; +use shale::{CachedStore, CachedView, SpaceId}; use nix::fcntl::{flock, FlockArg}; use thiserror::Error; @@ -52,10 +52,11 @@ impl From for StoreError { pub trait MemStoreR: Debug { /// Returns a slice of bytes from memory. fn get_slice(&self, offset: u64, length: u64) -> Option>; - fn id(&self) -> SpaceID; + fn id(&self) -> SpaceId; } -type Page = [u8; PAGE_SIZE as usize]; +// Page should be boxed as to not take up so much stack-space +type Page = Box<[u8; PAGE_SIZE as usize]>; #[derive(Debug)] pub struct SpaceWrite { @@ -86,7 +87,7 @@ impl Ash { } #[derive(Debug)] -pub struct AshRecord(pub HashMap); +pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { fn serialize(&self) -> growthring::wal::WalBytes { @@ -152,7 +153,7 @@ impl AshRecord { } /// Basic copy-on-write item in the linear storage space for multi-versioning. -pub struct DeltaPage(u64, Box); +pub struct DeltaPage(u64, Page); impl DeltaPage { #[inline(always)] @@ -333,7 +334,7 @@ impl MemStoreR for StoreRev { Some(data) } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { self.base_space.borrow().id() } } @@ -381,7 +382,7 @@ impl CachedStore for StoreRevShared { // Writes could be induced by lazy hashing and we can just ignore those } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { ::id(&self.0) } } @@ -423,7 +424,7 @@ impl DerefMut for StoreShared { #[derive(Debug, Default)] struct StoreRevMutDelta { - pages: HashMap>, + pages: HashMap, plain: Ash, } @@ -574,7 +575,7 @@ impl CachedStore for StoreRevMut { plain.redo.push(SpaceWrite { offset, data: redo }); } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { self.base_space.id() } } @@ -596,7 +597,7 @@ impl MemStoreR for ZeroStore { Some(vec![0; length as usize]) } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { shale::INVALID_SPACE_ID } } @@ -646,14 +647,14 @@ pub struct StoreConfig { ncached_files: usize, #[builder(default = 22)] // 4MB file by default file_nbit: u64, - space_id: SpaceID, + space_id: SpaceId, rootdir: PathBuf, } #[derive(Debug)] struct CachedSpaceInner { - cached_pages: lru::LruCache>, - pinned_pages: HashMap)>, + cached_pages: lru::LruCache, + pinned_pages: HashMap, files: Arc, disk_buffer: DiskBufferRequester, } @@ -661,7 +662,7 @@ struct CachedSpaceInner { #[derive(Clone, Debug)] pub struct CachedSpace { inner: Rc>, - space_id: SpaceID, + space_id: SpaceId, } impl CachedSpace { @@ -696,11 +697,7 @@ impl CachedSpace { } impl CachedSpaceInner { - fn fetch_page( - &mut self, - space_id: SpaceID, - pid: u64, - ) -> Result, StoreError> { + fn fetch_page(&mut self, space_id: SpaceId, pid: u64) -> Result> { if let Some(p) = self.disk_buffer.get_page(space_id, pid) { return Ok(Box::new(*p)); } @@ -708,19 +705,20 @@ impl CachedSpaceInner { let file_size = 1 << file_nbit; let poff = pid << PAGE_SIZE_NBIT; let file = self.files.get_file(poff >> file_nbit)?; - let mut page: Page = [0; PAGE_SIZE as usize]; + let mut page = Page::new([0; PAGE_SIZE as usize]); nix::sys::uio::pread( file.get_fd(), - &mut page, + page.deref_mut(), (poff & (file_size - 1)) as nix::libc::off_t, ) .map_err(StoreError::System)?; - Ok(Box::new(page)) + + Ok(page) } fn pin_page( &mut self, - space_id: SpaceID, + space_id: SpaceId, pid: u64, ) -> Result<&'static mut [u8], StoreError> { let base = match self.pinned_pages.get_mut(&pid) { @@ -823,7 +821,7 @@ impl MemStoreR for CachedSpace { Some(data) } - fn id(&self) -> SpaceID { + fn id(&self) -> SpaceId { self.space_id } } From d1a8aaf10cddff86e64dd2d36a2b91e18264dbb4 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 9 Jun 2023 10:46:54 -0700 Subject: [PATCH 0187/1053] feat: Identify a revision with root hash (#126) --- firewood/benches/hashops.rs | 10 +- firewood/examples/benchmark.rs | 6 - firewood/examples/rev.rs | 35 +++- firewood/src/api.rs | 11 +- firewood/src/db.rs | 304 ++++++++++++++++++++++----------- firewood/src/merkle.rs | 61 ++++--- firewood/src/merkle_util.rs | 2 +- firewood/src/service/client.rs | 48 +++--- firewood/src/service/mod.rs | 10 +- firewood/src/service/server.rs | 13 +- firewood/src/storage/mod.rs | 4 +- firewood/tests/db.rs | 48 +++--- fwdctl/src/create.rs | 31 ++++ 13 files changed, 357 insertions(+), 226 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 8ff504e45152..24f4f92cb21e 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -2,27 +2,27 @@ use std::ops::Deref; use bencher::{benchmark_group, benchmark_main, Bencher}; -use firewood::merkle::{Hash, Merkle, HASH_SIZE}; +use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; use firewood_shale::{ cached::PlainMem, compact::CompactSpaceHeader, CachedStore, ObjPtr, Storable, StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; -const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); +const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); fn bench_dehydrate(b: &mut Bencher) { - let mut to = [1u8; HASH_SIZE]; + let mut to = [1u8; TRIE_HASH_LEN]; b.iter(|| { ZERO_HASH.dehydrate(&mut to).unwrap(); }); } fn bench_hydrate(b: &mut Bencher) { - let mut store = PlainMem::new(HASH_SIZE as u64, 0u8); + let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, ZERO_HASH.deref()); b.iter(|| { - Hash::hydrate(0, &store).unwrap(); + TrieHash::hydrate(0, &store).unwrap(); }); } diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 24c9b14842a6..0ba02b9581e5 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -15,8 +15,6 @@ struct Args { batch_size: usize, #[arg(short, long, default_value_t = 0)] seed: u64, - #[arg(short, long, default_value_t = false)] - no_root_hash: bool, } fn main() { @@ -58,10 +56,6 @@ fn main() { wb = wb.kv_insert(k, v.to_vec()).unwrap(); } - if args.no_root_hash { - wb = wb.no_root_hash(); - } - wb.commit(); } }) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 4a8207ffee74..939799172d90 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -1,14 +1,18 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::collections::VecDeque; + use firewood::{ db::{Db, DbConfig, Revision, WalConfig}, + merkle::TrieHash, proof::Proof, }; /// cargo run --example rev fn main() { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + let mut hashes: VecDeque = VecDeque::new(); { let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) .expect("db initiation should succeed"); @@ -23,9 +27,12 @@ fn main() { .kv_root_hash() .expect("root-hash for current state should exist"); println!("{root_hash:?}"); + hashes.push_front(root_hash); } db.kv_dump(&mut std::io::stdout()).unwrap(); - let revision = db.get_revision(0, None).expect("revision-0 should exist"); + let revision = db + .get_revision(hashes[0].clone(), None) + .expect("revision-0 should exist"); let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-0 should exist"); @@ -37,14 +44,18 @@ fn main() { // The following is true as long as there are no dirty-writes. assert_eq!(revision_root_hash, current_root_hash); - let revision = db.get_revision(2, None).expect("revision-2 should exist"); + let revision = db + .get_revision(hashes[2].clone(), None) + .expect("revision-2 should exist"); let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-2 should exist"); println!("{revision_root_hash:?}"); // Get a revision while a batch is active. - let revision = db.get_revision(1, None).expect("revision-1 should exist"); + let revision = db + .get_revision(hashes[1].clone(), None) + .expect("revision-1 should exist"); let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-1 should exist"); @@ -53,7 +64,7 @@ fn main() { let write = db.new_writebatch().kv_insert("k", vec![b'v']).unwrap(); let actual_revision_root_hash = db - .get_revision(1, None) + .get_revision(hashes[1].clone(), None) .expect("revision-1 should exist") .kv_root_hash() .expect("root-hash for revision-1 should exist"); @@ -64,8 +75,10 @@ fn main() { assert_eq!("v".as_bytes().to_vec(), val); write.commit(); + hashes.push_front(db.kv_root_hash().expect("root-hash should exist")); + let new_revision_root_hash = db - .get_revision(1, None) + .get_revision(hashes[1].clone(), None) .expect("revision-1 should exist") .kv_root_hash() .expect("root-hash for revision-1 should exist"); @@ -87,7 +100,9 @@ fn main() { let db = Db::new("rev_db", &cfg.truncate(false).build()).expect("db initiation should succeed"); { - let revision = db.get_revision(0, None).expect("revision-0 should exist"); + let revision = db + .get_revision(hashes[0].clone(), None) + .expect("revision-0 should exist"); let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-0 should exist"); @@ -99,7 +114,9 @@ fn main() { // The following is true as long as the current state is fresh after replaying from Wals. assert_eq!(revision_root_hash, current_root_hash); - let revision = db.get_revision(1, None).expect("revision-1 should exist"); + let revision = db + .get_revision(hashes[1].clone(), None) + .expect("revision-1 should exist"); revision.kv_dump(&mut std::io::stdout()).unwrap(); let mut items_rev = vec![("dof", "verb"), ("doe", "reindeer")]; @@ -118,7 +135,9 @@ fn main() { .unwrap(); } { - let revision = db.get_revision(2, None).expect("revision-2 should exist"); + let revision = db + .get_revision(hashes[2].clone(), None) + .expect("revision-2 should exist"); let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-2 should exist"); diff --git a/firewood/src/api.rs b/firewood/src/api.rs index f921fa013163..9ac74a1d2742 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -9,7 +9,7 @@ use crate::account::Account; use primitive_types::U256; use crate::db::{DbError, DbRevConfig}; -use crate::merkle::Hash; +use crate::merkle::TrieHash; #[cfg(feature = "proof")] use crate::{merkle::MerkleError, proof::Proof}; @@ -20,7 +20,7 @@ pub type Nonce = u64; #[async_trait] pub trait Db { async fn new_writebatch(&self) -> B; - async fn get_revision(&self, nback: usize, cfg: Option) -> Option; + async fn get_revision(&self, root_hash: TrieHash, cfg: Option) -> Option; } #[async_trait] @@ -83,9 +83,6 @@ where key: K, acc: &mut Option, ) -> Result; - /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root - /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. - async fn no_root_hash(self) -> Self; /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are /// either retained on disk or lost together during a crash. @@ -97,11 +94,11 @@ pub trait Revision where Self: Sized, { - async fn kv_root_hash(&self) -> Result; + async fn kv_root_hash(&self) -> Result; async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError>; async fn kv_dump(&self, writer: W) -> Result<(), DbError>; - async fn root_hash(&self) -> Result; + async fn root_hash(&self) -> Result; async fn dump(&self, writer: W) -> Result<(), DbError>; #[cfg(feature = "proof")] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index da5ad42e0c2c..f9ed010ebd2f 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -22,19 +22,20 @@ use typed_builder::TypedBuilder; #[cfg(feature = "eth")] use crate::account::{Account, AccountRlp, Blob, BlobStash}; use crate::file; -use crate::merkle::{Hash, IdTrans, Merkle, MerkleError, Node}; +use crate::merkle::{IdTrans, Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}; use crate::proof::{Proof, ProofError}; use crate::storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}; pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use crate::storage::{ AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, - StoreRevShared, PAGE_SIZE_NBIT, + StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }; const MERKLE_META_SPACE: SpaceId = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; const BLOB_META_SPACE: SpaceId = 0x2; const BLOB_PAYLOAD_SPACE: SpaceId = 0x3; +const ROOT_HASH_SPACE: SpaceId = 0x4; const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; @@ -95,6 +96,7 @@ struct DbParams { payload_regn_nbit: u64, wal_file_nbit: u64, wal_block_nbit: u64, + root_hash_file_nbit: u64, } /// Config for accessing a version of the DB. @@ -139,6 +141,16 @@ pub struct DbConfig { /// partitioned into multiple regions. Just use the default value. #[builder(default = 22)] pub payload_regn_nbit: u64, + /// Maximum cached pages for the free list of the item stash. + #[builder(default = 16384)] // 64M total size by default + pub root_hash_ncached_pages: usize, + /// Maximum cached file descriptors for the free list of the item stash. + #[builder(default = 1024)] // 1K fds by default + pub root_hash_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + pub root_hash_file_nbit: u64, /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its /// existing contents will be lost. #[builder(default = false)] @@ -330,7 +342,7 @@ impl DbRev { } /// Get root hash of the generic key-value storage. - pub fn kv_root_hash(&self) -> Result { + pub fn kv_root_hash(&self) -> Result { self.merkle .root_hash::(self.header.kv_root) .map_err(DbError::Merkle) @@ -402,7 +414,7 @@ impl DbRev { } /// Get root hash of the world state of all accounts. - pub fn root_hash(&self) -> Result { + pub fn root_hash(&self) -> Result { self.merkle .root_hash::(self.header.acc_root) .map_err(DbError::Merkle) @@ -459,8 +471,9 @@ struct DbInner { latest: DbRev, disk_requester: DiskBufferRequester, disk_thread: Option>, - staging: Universe>, - cached: Universe>, + data_staging: Universe>, + data_cache: Universe>, + root_hash_staging: StoreRevMut, } impl Drop for DbInner { @@ -481,6 +494,7 @@ pub struct Db { pub struct DbRevInner { inner: VecDeque>, + root_hashes: VecDeque, max_revisions: usize, base: Universe, } @@ -503,6 +517,8 @@ impl Db { let blob_meta_path = file::touch_dir("meta", &blob_path)?; let blob_payload_path = file::touch_dir("compact", &blob_path)?; + let root_hash_path = file::touch_dir("root_hash", &db_path)?; + let file0 = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; let fd0 = file0.get_fd(); @@ -524,6 +540,7 @@ impl Db { payload_regn_nbit: cfg.payload_regn_nbit, wal_file_nbit: cfg.wal.file_nbit, wal_block_nbit: cfg.wal.block_nbit, + root_hash_file_nbit: cfg.root_hash_file_nbit, }; nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0) .map_err(DbError::System)?; @@ -537,7 +554,7 @@ impl Db { let header: DbParams = cast_slice(&header_bytes)[0]; // setup disk buffer - let cached = Universe { + let data_cache = Universe { merkle: SubUniverse::new( Rc::new( CachedSpace::new( @@ -592,6 +609,19 @@ impl Db { ), }; + let root_hash_cache = Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.root_hash_ncached_pages) + .ncached_files(cfg.root_hash_ncached_files) + .space_id(ROOT_HASH_SPACE) + .file_nbit(header.root_hash_file_nbit) + .rootdir(root_hash_path) + .build(), + ) + .unwrap(), + ); + let wal = WalConfig::builder() .file_nbit(header.wal_file_nbit) .block_nbit(header.wal_block_nbit) @@ -605,29 +635,31 @@ impl Db { disk_buffer.run() })); - disk_requester.reg_cached_space(cached.merkle.meta.as_ref()); - disk_requester.reg_cached_space(cached.merkle.payload.as_ref()); - disk_requester.reg_cached_space(cached.blob.meta.as_ref()); - disk_requester.reg_cached_space(cached.blob.payload.as_ref()); + disk_requester.reg_cached_space(data_cache.merkle.meta.as_ref()); + disk_requester.reg_cached_space(data_cache.merkle.payload.as_ref()); + disk_requester.reg_cached_space(data_cache.blob.meta.as_ref()); + disk_requester.reg_cached_space(data_cache.blob.payload.as_ref()); + disk_requester.reg_cached_space(root_hash_cache.as_ref()); - let mut staging = Universe { + let mut data_staging = Universe { merkle: SubUniverse::new( Rc::new(StoreRevMut::new( - cached.merkle.meta.clone() as Rc + data_cache.merkle.meta.clone() as Rc )), Rc::new(StoreRevMut::new( - cached.merkle.payload.clone() as Rc + data_cache.merkle.payload.clone() as Rc )), ), blob: SubUniverse::new( Rc::new(StoreRevMut::new( - cached.blob.meta.clone() as Rc + data_cache.blob.meta.clone() as Rc )), Rc::new(StoreRevMut::new( - cached.blob.payload.clone() as Rc + data_cache.blob.payload.clone() as Rc )), ), }; + let root_hash_staging = StoreRevMut::new(root_hash_cache); // recover from Wal disk_requester.init_wal("wal", db_path); @@ -643,7 +675,7 @@ impl Db { if reset { // initialize space headers - let initializer = Rc::::make_mut(&mut staging.merkle.meta); + let initializer = Rc::::make_mut(&mut data_staging.merkle.meta); initializer.write( merkle_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( @@ -655,7 +687,7 @@ impl Db { db_header.addr(), &shale::to_dehydrated(&DbHeader::new_empty())?, ); - let initializer = Rc::::make_mut(&mut staging.blob.meta); + let initializer = Rc::::make_mut(&mut data_staging.blob.meta); initializer.write( blob_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( @@ -666,8 +698,8 @@ impl Db { } let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { - let merkle_meta_ref = staging.merkle.meta.as_ref(); - let blob_meta_ref = staging.blob.meta.as_ref(); + let merkle_meta_ref = data_staging.merkle.meta.as_ref(); + let blob_meta_ref = data_staging.blob.meta.as_ref(); ( StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), @@ -687,8 +719,8 @@ impl Db { }; let merkle_space = shale::compact::CompactSpace::new( - staging.merkle.meta.clone(), - staging.merkle.payload.clone(), + data_staging.merkle.meta.clone(), + data_staging.merkle.payload.clone(), merkle_payload_header_ref, shale::ObjCache::new(cfg.rev.merkle_ncached_objs), cfg.payload_max_walk, @@ -730,8 +762,8 @@ impl Db { latest.flush_dirty().unwrap(); let base = Universe { - merkle: get_sub_universe_from_empty_delta(&cached.merkle), - blob: get_sub_universe_from_empty_delta(&cached.blob), + merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), + blob: get_sub_universe_from_empty_delta(&data_cache.blob), }; Ok(Self { @@ -739,11 +771,13 @@ impl Db { latest, disk_thread, disk_requester, - staging, - cached, + data_staging, + data_cache, + root_hash_staging, })), revisions: Arc::new(Mutex::new(DbRevInner { inner: VecDeque::new(), + root_hashes: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, base, })), @@ -758,7 +792,6 @@ impl Db { WriteBatch { m: Arc::clone(&self.inner), r: Arc::clone(&self.revisions), - root_hash_recalc: true, committed: false, } } @@ -768,7 +801,7 @@ impl Db { self.inner.read().latest.kv_dump(w) } /// Get root hash of the latest generic key-value storage. - pub fn kv_root_hash(&self) -> Result { + pub fn kv_root_hash(&self) -> Result { self.inner.read().latest.kv_root_hash() } @@ -781,23 +814,68 @@ impl Db { .kv_get(key) .ok_or(DbError::KeyNotFound) } - /// Get a handle that grants the access to any committed state of the entire DB. + /// Get a handle that grants the access to any committed state of the entire DB, + /// with a given root hash. If the given root hash matches with more than one + /// revisions, we use the most recent one as the trie are the same. /// - /// The latest revision (nback) starts from 0, which is the current state. - /// If nback equals is above the configured maximum number of revisions, this function returns None. - /// Returns `None` if `nback` is greater than the configured maximum amount of revisions. + /// If no revision with matching root hash found, returns None. #[measure([HitCount])] - pub fn get_revision(&self, nback: usize, cfg: Option) -> Option { + pub fn get_revision(&self, root_hash: TrieHash, cfg: Option) -> Option { let mut revisions = self.revisions.lock(); - let inner = self.inner.read(); + let inner_lock = self.inner.read(); + + // Find the revision index with the given root hash. + let (nback, found) = { + let mut nback = 0; + let mut found = false; + + for (i, r) in revisions.root_hashes.iter().enumerate() { + if *r == root_hash { + nback = i; + found = true; + break; + } + } - let rlen = revisions.inner.len(); - if nback > revisions.max_revisions { + if !found { + let rlen = revisions.root_hashes.len(); + if rlen < revisions.max_revisions { + let ashes = inner_lock + .disk_requester + .collect_ash(revisions.max_revisions) + .ok() + .unwrap(); + for (i, ash) in ashes.iter().skip(rlen).enumerate() { + // Replay the redo from the wal + let root_hash_store = StoreRevShared::from_ash( + Rc::new(ZeroStore::default()), + &ash.0[&ROOT_HASH_SPACE].redo, + ); + // No need the usage of `ShaleStore`, as this is just simple Hash value. + let r = root_hash_store + .get_view(0, TRIE_HASH_LEN as u64) + .unwrap() + .as_deref(); + let r = TrieHash(r[..TRIE_HASH_LEN].try_into().unwrap()); + if r == root_hash { + nback = i; + found = true; + break; + } + } + } + } + (nback, found) + }; + + if !found { return None; } + + let rlen = revisions.inner.len(); if rlen < nback { // TODO: Remove unwrap - let ashes = inner.disk_requester.collect_ash(nback).ok().unwrap(); + let ashes = inner_lock.disk_requester.collect_ash(nback).ok().unwrap(); for mut ash in ashes.into_iter().skip(rlen) { for (_, a) in ash.0.iter_mut() { a.undo.reverse() @@ -805,7 +883,7 @@ impl Db { let u = match revisions.inner.back() { Some(u) => u.to_mem_store_r(), - None => inner.cached.to_mem_store_r(), + None => inner_lock.data_cache.to_mem_store_r(), }; revisions.inner.push_back(u.rewind( &ash.0[&MERKLE_META_SPACE].undo, @@ -815,11 +893,8 @@ impl Db { )); } } - if revisions.inner.len() < nback { - return None; - } - // set up the storage layout + // Set up the storage layout let mut offset = std::mem::size_of::() as u64; // DbHeader starts after DbParams in merkle meta space let db_header: ObjPtr = ObjPtr::new_from_addr(offset); @@ -836,7 +911,8 @@ impl Db { } else { &revisions.inner[nback - 1] }; - drop(inner); + // Release the lock after we find the revision + drop(inner_lock); let (db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = &space.merkle.meta; @@ -905,7 +981,7 @@ impl Db { } /// Get root hash of the latest world state of all accounts. - pub fn root_hash(&self) -> Result { + pub fn root_hash(&self) -> Result { self.inner.read().latest.root_hash() } @@ -953,7 +1029,6 @@ impl std::ops::Deref for Revision { pub struct WriteBatch { m: Arc>, r: Arc>, - root_hash_recalc: bool, committed: bool, } @@ -986,118 +1061,138 @@ impl WriteBatch { drop(rev); Ok((self, old_value)) } - /// Do not rehash merkle roots upon commit. This will leave the recalculation of the dirty root - /// hashes to future invocation of `root_hash`, `kv_root_hash` or batch commits. - pub fn no_root_hash(mut self) -> Self { - self.root_hash_recalc = false; - self - } /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are /// either retained on disk or lost together during a crash. pub fn commit(mut self) { let mut rev_inner = self.m.write(); - if self.root_hash_recalc { - #[cfg(feature = "eth")] - rev_inner.latest.root_hash().ok(); - rev_inner.latest.kv_root_hash().ok(); - } + + #[cfg(feature = "eth")] + rev_inner.latest.root_hash().ok(); + + let kv_root_hash = rev_inner.latest.kv_root_hash().ok(); + let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); + // clear the staging layer and apply changes to the CachedSpace rev_inner.latest.flush_dirty().unwrap(); - let (merkle_payload_pages, merkle_payload_plain) = - rev_inner.staging.merkle.payload.take_delta(); - let (merkle_meta_pages, merkle_meta_plain) = rev_inner.staging.merkle.meta.take_delta(); - let (blob_payload_pages, blob_payload_plain) = rev_inner.staging.blob.payload.take_delta(); - let (blob_meta_pages, blob_meta_plain) = rev_inner.staging.blob.meta.take_delta(); - - let old_merkle_meta_delta = rev_inner - .cached + let (merkle_payload_redo, merkle_payload_wal) = + rev_inner.data_staging.merkle.payload.take_delta(); + let (merkle_meta_redo, merkle_meta_wal) = rev_inner.data_staging.merkle.meta.take_delta(); + let (blob_payload_redo, blob_payload_wal) = + rev_inner.data_staging.blob.payload.take_delta(); + let (blob_meta_redo, blob_meta_wal) = rev_inner.data_staging.blob.meta.take_delta(); + + let merkle_meta_undo = rev_inner + .data_cache .merkle .meta - .update(&merkle_meta_pages) + .update(&merkle_meta_redo) .unwrap(); - let old_merkle_payload_delta = rev_inner - .cached + let merkle_payload_undo = rev_inner + .data_cache .merkle .payload - .update(&merkle_payload_pages) + .update(&merkle_payload_redo) .unwrap(); - let old_blob_meta_delta = rev_inner.cached.blob.meta.update(&blob_meta_pages).unwrap(); - let old_blob_payload_delta = rev_inner - .cached + let blob_meta_undo = rev_inner + .data_cache + .blob + .meta + .update(&blob_meta_redo) + .unwrap(); + let blob_payload_undo = rev_inner + .data_cache .blob .payload - .update(&blob_payload_pages) + .update(&blob_payload_redo) .unwrap(); // update the rolling window of past revisions - let new_base = Universe { + let latest_past = Universe { merkle: get_sub_universe_from_deltas( - &rev_inner.cached.merkle, - old_merkle_meta_delta, - old_merkle_payload_delta, + &rev_inner.data_cache.merkle, + merkle_meta_undo, + merkle_payload_undo, ), blob: get_sub_universe_from_deltas( - &rev_inner.cached.blob, - old_blob_meta_delta, - old_blob_payload_delta, + &rev_inner.data_cache.blob, + blob_meta_undo, + blob_payload_undo, ), }; let mut revisions = self.r.lock(); + let max_revisions = revisions.max_revisions; if let Some(rev) = revisions.inner.front_mut() { rev.merkle .meta - .set_base_space(new_base.merkle.meta.inner().clone()); + .set_base_space(latest_past.merkle.meta.inner().clone()); rev.merkle .payload - .set_base_space(new_base.merkle.payload.inner().clone()); + .set_base_space(latest_past.merkle.payload.inner().clone()); rev.blob .meta - .set_base_space(new_base.blob.meta.inner().clone()); + .set_base_space(latest_past.blob.meta.inner().clone()); rev.blob .payload - .set_base_space(new_base.blob.payload.inner().clone()); + .set_base_space(latest_past.blob.payload.inner().clone()); } - revisions.inner.push_front(new_base); - while revisions.inner.len() > revisions.max_revisions { + revisions.inner.push_front(latest_past); + while revisions.inner.len() > max_revisions { revisions.inner.pop_back(); } let base = Universe { - merkle: get_sub_universe_from_empty_delta(&rev_inner.cached.merkle), - blob: get_sub_universe_from_empty_delta(&rev_inner.cached.blob), + merkle: get_sub_universe_from_empty_delta(&rev_inner.data_cache.merkle), + blob: get_sub_universe_from_empty_delta(&rev_inner.data_cache.blob), }; revisions.base = base; + // update the rolling window of root hashes + revisions.root_hashes.push_front(kv_root_hash.clone()); + if revisions.root_hashes.len() > max_revisions { + revisions + .root_hashes + .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); + } + + rev_inner.root_hash_staging.write(0, &kv_root_hash.0); + let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.take_delta(); + // let base_root_hash = StoreRevShared::from_delta(rev_inner.root_hash_cache, root_hash_redo); + self.committed = true; // schedule writes to the disk rev_inner.disk_requester.write( vec![ BufferWrite { - space_id: rev_inner.staging.merkle.payload.id(), - delta: merkle_payload_pages, + space_id: rev_inner.data_staging.merkle.payload.id(), + delta: merkle_payload_redo, + }, + BufferWrite { + space_id: rev_inner.data_staging.merkle.meta.id(), + delta: merkle_meta_redo, }, BufferWrite { - space_id: rev_inner.staging.merkle.meta.id(), - delta: merkle_meta_pages, + space_id: rev_inner.data_staging.blob.payload.id(), + delta: blob_payload_redo, }, BufferWrite { - space_id: rev_inner.staging.blob.payload.id(), - delta: blob_payload_pages, + space_id: rev_inner.data_staging.blob.meta.id(), + delta: blob_meta_redo, }, BufferWrite { - space_id: rev_inner.staging.blob.meta.id(), - delta: blob_meta_pages, + space_id: rev_inner.root_hash_staging.id(), + delta: root_hash_redo, }, ], AshRecord( [ - (MERKLE_META_SPACE, merkle_meta_plain), - (MERKLE_PAYLOAD_SPACE, merkle_payload_plain), - (BLOB_META_SPACE, blob_meta_plain), - (BLOB_PAYLOAD_SPACE, blob_payload_plain), + (MERKLE_META_SPACE, merkle_meta_wal), + (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), + (BLOB_META_SPACE, blob_meta_wal), + (BLOB_PAYLOAD_SPACE, blob_payload_wal), + (ROOT_HASH_SPACE, root_hash_wal), ] .into(), ), @@ -1158,7 +1253,7 @@ impl WriteBatch { blob_stash.free_blob(acc.code).map_err(DbError::Blob)?; } acc.set_code( - Hash(sha3::Keccak256::digest(code).into()), + TrieHash(sha3::Keccak256::digest(code).into()), blob_stash .new_blob(Blob::Code(code.to_vec())) .map_err(DbError::Blob)? @@ -1255,10 +1350,11 @@ impl Drop for WriteBatch { fn drop(&mut self) { if !self.committed { // drop the staging changes - self.m.read().staging.merkle.payload.take_delta(); - self.m.read().staging.merkle.meta.take_delta(); - self.m.read().staging.blob.payload.take_delta(); - self.m.read().staging.blob.meta.take_delta(); + self.m.read().data_staging.merkle.payload.take_delta(); + self.m.read().data_staging.merkle.meta.take_delta(); + self.m.read().data_staging.blob.payload.take_delta(); + self.m.read().data_staging.blob.meta.take_delta(); + self.m.read().root_hash_staging.take_delta(); } } } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 76b20ae220ac..7e999985bb35 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,7 +16,7 @@ use std::io::{Cursor, Read, Write}; use std::sync::OnceLock; pub const NBRANCH: usize = 16; -pub const HASH_SIZE: usize = 32; +pub const TRIE_HASH_LEN: usize = 32; #[derive(Debug)] pub enum MerkleError { @@ -44,20 +44,20 @@ impl fmt::Display for MerkleError { impl Error for MerkleError {} #[derive(PartialEq, Eq, Clone)] -pub struct Hash(pub [u8; HASH_SIZE]); +pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); -impl Hash { +impl TrieHash { const MSIZE: u64 = 32; } -impl std::ops::Deref for Hash { - type Target = [u8; HASH_SIZE]; - fn deref(&self) -> &[u8; HASH_SIZE] { +impl std::ops::Deref for TrieHash { + type Target = [u8; TRIE_HASH_LEN]; + fn deref(&self) -> &[u8; TRIE_HASH_LEN] { &self.0 } } -impl Storable for Hash { +impl Storable for TrieHash { fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) @@ -79,7 +79,7 @@ impl Storable for Hash { } } -impl Debug for Hash { +impl Debug for TrieHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", hex::encode(self.0)) } @@ -228,7 +228,7 @@ impl BranchNode { stream.append_empty_data(); } else { let v = self.chd_eth_rlp[i].clone().unwrap(); - if v.len() == HASH_SIZE { + if v.len() == TRIE_HASH_LEN { stream.append(&v); } else { stream.append_raw(&v, 1); @@ -339,7 +339,7 @@ impl ExtNode { stream.append_empty_data(); } else { let v = self.2.clone().unwrap(); - if v.len() == HASH_SIZE { + if v.len() == TRIE_HASH_LEN { stream.append(&v); } else { stream.append_raw(&v, 1); @@ -372,7 +372,7 @@ impl ExtNode { #[derive(PartialEq, Eq, Clone)] pub struct Node { - root_hash: OnceCell, + root_hash: OnceCell, eth_rlp_long: OnceCell, eth_rlp: OnceCell>, lazy_dirty: Cell, @@ -424,17 +424,17 @@ impl Node { .get_or_init(|| self.inner.calc_eth_rlp::(store)) } - fn get_root_hash(&self, store: &dyn ShaleStore) -> &Hash { + fn get_root_hash(&self, store: &dyn ShaleStore) -> &TrieHash { self.root_hash.get_or_init(|| { self.lazy_dirty.set(true); - Hash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) + TrieHash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) }) } fn get_eth_rlp_long(&self, store: &dyn ShaleStore) -> bool { *self.eth_rlp_long.get_or_init(|| { self.lazy_dirty.set(true); - self.get_eth_rlp::(store).len() >= HASH_SIZE + self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN }) } @@ -464,7 +464,11 @@ impl Node { &mut self.inner } - fn new_from_hash(root_hash: Option, eth_rlp_long: Option, inner: NodeType) -> Self { + fn new_from_hash( + root_hash: Option, + eth_rlp_long: Option, + inner: NodeType, + ) -> Self { Self { root_hash: match root_hash { Some(h) => OnceCell::from(h), @@ -498,7 +502,7 @@ impl Storable for Node { let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None } else { - Some(Hash( + Some(TrieHash( meta_raw.as_deref()[0..32] .try_into() .expect("invalid slice"), @@ -846,10 +850,10 @@ impl Merkle { self.store.as_ref() } - pub fn empty_root() -> &'static Hash { - static V: OnceLock = OnceLock::new(); + pub fn empty_root() -> &'static TrieHash { + static V: OnceLock = OnceLock::new(); V.get_or_init(|| { - Hash( + TrieHash( hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") .unwrap() .try_into() @@ -858,7 +862,10 @@ impl Merkle { }) } - pub fn root_hash(&self, root: ObjPtr) -> Result { + pub fn root_hash( + &self, + root: ObjPtr, + ) -> Result { let root = self .get_node(root)? .inner @@ -1784,7 +1791,7 @@ impl Merkle { let mut chunks = Vec::new(); chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); - let mut proofs: HashMap<[u8; HASH_SIZE], Vec> = HashMap::new(); + let mut proofs: HashMap<[u8; TRIE_HASH_LEN], Vec> = HashMap::new(); if root.is_null() { return Ok(Proof(proofs)); } @@ -1850,7 +1857,7 @@ impl Merkle { for node in nodes { let node = self.get_node(node)?; let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(rlp).into(); + let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(rlp).into(); proofs.insert(hash, rlp.to_vec()); } Ok(Proof(proofs)) @@ -2069,15 +2076,15 @@ mod test { } } - const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); + const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); #[test] fn test_hash_len() { - assert_eq!(HASH_SIZE, ZERO_HASH.dehydrated_len() as usize); + assert_eq!(TRIE_HASH_LEN, ZERO_HASH.dehydrated_len() as usize); } #[test] fn test_dehydrate() { - let mut to = [1u8; HASH_SIZE]; + let mut to = [1u8; TRIE_HASH_LEN]; assert_eq!( { ZERO_HASH.dehydrate(&mut to).unwrap(); @@ -2089,9 +2096,9 @@ mod test { #[test] fn test_hydrate() { - let mut store = PlainMem::new(HASH_SIZE as u64, 0u8); + let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, ZERO_HASH.deref()); - assert_eq!(Hash::hydrate(0, &store).unwrap(), ZERO_HASH); + assert_eq!(TrieHash::hydrate(0, &store).unwrap(), ZERO_HASH); } #[test] fn test_partial_path_encoding() { diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 61013ca50176..eb4378603458 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -69,7 +69,7 @@ impl MerkleSetup { &mut self.merkle } - pub fn root_hash(&self) -> Result { + pub fn root_hash(&self) -> Result { self.merkle .root_hash::(self.root) .map_err(|_err| DataStoreError::RootHashError) diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index a3b06eda88e5..2b160ca272bb 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -16,7 +16,7 @@ use crate::db::DbRevConfig; use crate::{ api, db::{DbConfig, DbError}, - merkle, + merkle::TrieHash, }; use async_trait::async_trait; @@ -225,19 +225,6 @@ impl api::WriteBatch for BatchHandle { todo!() } - async fn no_root_hash(self) -> Self { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::NoRootHash { - handle: self.id, - respond_to: send, - })) - .await; - let _ = recv.await; - self - } - async fn commit(self) { let (send, recv) = oneshot::channel(); let _ = self @@ -265,7 +252,7 @@ impl super::RevisionHandle { #[async_trait] impl Revision for super::RevisionHandle { - async fn kv_root_hash(&self) -> Result { + async fn kv_root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::RootHash { handle: self.id, @@ -311,7 +298,7 @@ impl Revision for super::RevisionHandle { ) { todo!() } - async fn root_hash(&self) -> Result { + async fn root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::RootHash { handle: self.id, @@ -391,10 +378,14 @@ where } } - async fn get_revision(&self, nback: usize, cfg: Option) -> Option { + async fn get_revision( + &self, + root_hash: TrieHash, + cfg: Option, + ) -> Option { let (send, recv) = oneshot::channel(); let msg = Request::NewRevision { - nback, + root_hash, cfg, respond_to: send, }; @@ -437,7 +428,6 @@ mod test { let batch = batch.set_nonce(key, 42).await.unwrap(); let batch = batch.set_state(key, b"subkey", b"state").await.unwrap(); let batch = batch.create_account(key).await.unwrap(); - let batch = batch.no_root_hash().await; } let (batch, oldvalue) = batch.kv_remove(key).await.unwrap(); assert_eq!(oldvalue, Some(b"val".to_vec())); @@ -446,15 +436,17 @@ mod test { let batch = batch.kv_insert(b"k2", b"val").await.unwrap(); batch.commit().await; - assert_ne!( - conn.get_revision(1, None) - .await - .unwrap() - .root_hash() - .await - .unwrap(), - merkle::Hash([0; 32]) - ); + // TODO: disable the assertion now, add a way to expose the current root hash either + // in the writebatch or the connection + // assert_ne!( + // conn.get_revision(Hash([0; 32]), None) + // .await + // .unwrap() + // .root_hash() + // .await + // .unwrap(), + // Hash([0; 32]) + // ); } fn db_config() -> DbConfig { diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index e9ea1ee12f55..70305e2df434 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -5,7 +5,7 @@ use tokio::sync::{mpsc, oneshot}; use crate::{ db::{DbError, DbRevConfig}, - merkle, + merkle::TrieHash, }; mod client; @@ -33,7 +33,7 @@ pub enum Request { respond_to: oneshot::Sender, }, NewRevision { - nback: usize, + root_hash: TrieHash, cfg: Option, respond_to: oneshot::Sender>, }, @@ -98,10 +98,6 @@ pub enum BatchRequest { key: OwnedKey, respond_to: oneshot::Sender>, }, - NoRootHash { - handle: BatchId, - respond_to: oneshot::Sender<()>, - }, } #[derive(Debug)] @@ -119,7 +115,7 @@ pub enum RevRequest { }, RootHash { handle: RevId, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender>, }, Drop { handle: RevId, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 4e46583343fd..0e86b7008d84 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -54,12 +54,12 @@ impl FirewoodService { let _ = respond_to.send(id); } Request::NewRevision { - nback, + root_hash, cfg, respond_to, } => { let id: RevId = lastid.fetch_add(1, Ordering::Relaxed); - let msg = match db.get_revision(nback, cfg) { + let msg = match db.get_revision(root_hash, cfg) { Some(rev) => { revs.insert(id, rev); Some(id) @@ -221,15 +221,6 @@ impl FirewoodService { }; respond_to.send(resp).unwrap(); } - BatchRequest::NoRootHash { handle, respond_to } => { - // TODO: there's no way to report an error back to the caller here - if handle == lastid.load(Ordering::Relaxed) - 1 { - if let Some(batch) = active_batch { - active_batch = Some(batch.no_root_hash()); - } - } - respond_to.send(()).unwrap(); - } }, } } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index a3354346551e..e18438feaf1e 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -580,18 +580,16 @@ impl CachedStore for StoreRevMut { } } -#[cfg(test)] #[derive(Clone, Debug)] +/// A zero-filled in memory store which can serve as a plain base to overlay deltas on top. pub struct ZeroStore(Rc<()>); -#[cfg(test)] impl Default for ZeroStore { fn default() -> Self { Self(Rc::new(())) } } -#[cfg(test)] impl MemStoreR for ZeroStore { fn get_slice(&self, _: u64, length: u64) -> Option> { Some(vec![0; length as usize]) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 535d054c03c9..cdbca140e301 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,4 +1,5 @@ use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::merkle::TrieHash; use std::{collections::VecDeque, fs::remove_dir_all, path::Path}; macro_rules! kv_dump { @@ -70,6 +71,7 @@ fn test_revisions() { for i in 0..10 { let db = Db::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); let mut dumped = VecDeque::new(); + let mut hashes: VecDeque = VecDeque::new(); for _ in 0..10 { { let mut wb = db.new_writebatch(); @@ -83,29 +85,36 @@ fn test_revisions() { } while dumped.len() > 10 { dumped.pop_back(); + hashes.pop_back(); } + let root_hash = db.kv_root_hash().unwrap(); + hashes.push_front(root_hash); dumped.push_front(kv_dump!(db)); - for (i, _) in dumped.iter().enumerate().skip(1) { - let rev = db.get_revision(i, None).unwrap(); - let a = &kv_dump!(rev); - let b = &dumped[i]; - if a != b { - print!("{a}\n{b}"); - panic!("not the same"); - } - } + dumped + .iter() + .zip(hashes.iter().cloned()) + .map(|(data, hash)| (data, db.get_revision(hash, None).unwrap())) + .map(|(data, rev)| (data, kv_dump!(rev))) + .for_each(|(b, a)| { + if &a != b { + print!("{a}\n{b}"); + panic!("not the same"); + } + }); } drop(db); let db = Db::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); - for (j, _) in dumped.iter().enumerate().skip(1) { - let rev = db.get_revision(j, None).unwrap(); - let a = &kv_dump!(rev); - let b = &dumped[j]; - if a != b { - print!("{a}\n{b}"); - panic!("not the same"); - } - } + dumped + .iter() + .zip(hashes.iter().cloned()) + .map(|(data, hash)| (data, db.get_revision(hash, None).unwrap())) + .map(|(data, rev)| (data, kv_dump!(rev))) + .for_each(|(b, a)| { + if &a != b { + print!("{a}\n{b}"); + panic!("not the same"); + } + }); println!("i = {i}"); } } @@ -142,6 +151,7 @@ fn create_db_issue_proof() { wb = wb.kv_insert(k.as_bytes(), v.as_bytes().to_vec()).unwrap(); } wb.commit(); + let root_hash = db.kv_root_hash().unwrap(); // Add second commit due to API restrictions let mut wb = db.new_writebatch(); @@ -152,7 +162,7 @@ fn create_db_issue_proof() { } wb.commit(); - let rev = db.get_revision(1, None).unwrap(); + let rev = db.get_revision(root_hash, None).unwrap(); let key = "doe".as_bytes(); let root_hash = rev.kv_root_hash(); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 07e1fa077ab4..27612515ae89 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -92,6 +92,34 @@ pub struct Options { )] pub payload_regn_nbit: u64, + #[arg( + long, + required = false, + default_value_t = 16384, + value_name = "ROOT_HASH_NCACHED_PAGES", + help = "Maximum cached pages for the free list of the item stash." + )] + pub root_hash_ncached_pages: usize, + + #[arg( + long, + required = false, + default_value_t = 1024, + value_name = "ROOT_HASH_NCACHED_FILES", + help = "Maximum cached file descriptors for the free list of the item stash." + )] + pub root_hash_ncached_files: usize, + + #[arg( + long, + required = false, + default_value_t = 22, + value_name = "ROOT_HASH_FILE_NBIT", + help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + the power of 2 for the file size." + )] + pub root_hash_file_nbit: u64, + #[arg( long, required = false, @@ -235,6 +263,9 @@ pub fn initialize_db_config(opts: &Options) -> DbConfig { payload_file_nbit: opts.payload_file_nbit, payload_max_walk: opts.payload_max_walk, payload_regn_nbit: opts.payload_regn_nbit, + root_hash_ncached_pages: opts.payload_ncached_pages, + root_hash_ncached_files: opts.root_hash_ncached_files, + root_hash_file_nbit: opts.root_hash_file_nbit, truncate: opts.truncate, rev: DbRevConfig { merkle_ncached_objs: opts.merkle_ncached_objs, From 9732fefac368ff7d8f85e52364964a4d8424b122 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 12 Jun 2023 13:02:46 -0400 Subject: [PATCH 0188/1053] Remove broken disk-buffer (#131) --- firewood/src/db.rs | 89 ++++++++++++++++++---------------- firewood/src/storage/buffer.rs | 33 +++++-------- firewood/src/storage/mod.rs | 61 +++++++++++++---------- 3 files changed, 93 insertions(+), 90 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f9ed010ebd2f..379e94429dce 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -553,6 +553,33 @@ impl Db { let mut offset = header_bytes.len() as u64; let header: DbParams = cast_slice(&header_bytes)[0]; + let wal = WalConfig::builder() + .file_nbit(header.wal_file_nbit) + .block_nbit(header.wal_block_nbit) + .max_revisions(cfg.wal.max_revisions) + .build(); + let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); + let disk_requester = DiskBufferRequester::new(sender); + let buffer = cfg.buffer.clone(); + let disk_thread = Some(std::thread::spawn(move || { + let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); + disk_buffer.run() + })); + + let root_hash_cache = Rc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.root_hash_ncached_pages) + .ncached_files(cfg.root_hash_ncached_files) + .space_id(ROOT_HASH_SPACE) + .file_nbit(header.root_hash_file_nbit) + .rootdir(root_hash_path) + .build(), + disk_requester.clone(), + ) + .unwrap(), + ); + // setup disk buffer let data_cache = Universe { merkle: SubUniverse::new( @@ -565,6 +592,7 @@ impl Db { .file_nbit(header.meta_file_nbit) .rootdir(merkle_meta_path) .build(), + disk_requester.clone(), ) .unwrap(), ), @@ -577,6 +605,7 @@ impl Db { .file_nbit(header.payload_file_nbit) .rootdir(merkle_payload_path) .build(), + disk_requester.clone(), ) .unwrap(), ), @@ -591,6 +620,7 @@ impl Db { .file_nbit(header.meta_file_nbit) .rootdir(blob_meta_path) .build(), + disk_requester.clone(), ) .unwrap(), ), @@ -603,60 +633,33 @@ impl Db { .file_nbit(header.payload_file_nbit) .rootdir(blob_payload_path) .build(), + disk_requester.clone(), ) .unwrap(), ), ), }; - let root_hash_cache = Rc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.root_hash_ncached_pages) - .ncached_files(cfg.root_hash_ncached_files) - .space_id(ROOT_HASH_SPACE) - .file_nbit(header.root_hash_file_nbit) - .rootdir(root_hash_path) - .build(), - ) - .unwrap(), - ); - - let wal = WalConfig::builder() - .file_nbit(header.wal_file_nbit) - .block_nbit(header.wal_block_nbit) - .max_revisions(cfg.wal.max_revisions) - .build(); - let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); - let disk_requester = DiskBufferRequester::new(sender); - let buffer = cfg.buffer.clone(); - let disk_thread = Some(std::thread::spawn(move || { - let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); - disk_buffer.run() - })); - - disk_requester.reg_cached_space(data_cache.merkle.meta.as_ref()); - disk_requester.reg_cached_space(data_cache.merkle.payload.as_ref()); - disk_requester.reg_cached_space(data_cache.blob.meta.as_ref()); - disk_requester.reg_cached_space(data_cache.blob.payload.as_ref()); - disk_requester.reg_cached_space(root_hash_cache.as_ref()); + [ + data_cache.merkle.meta.as_ref(), + data_cache.merkle.payload.as_ref(), + data_cache.blob.meta.as_ref(), + data_cache.blob.payload.as_ref(), + root_hash_cache.as_ref(), + ] + .into_iter() + .for_each(|cached_space| { + disk_requester.reg_cached_space(cached_space.id(), cached_space.clone_files()); + }); let mut data_staging = Universe { merkle: SubUniverse::new( - Rc::new(StoreRevMut::new( - data_cache.merkle.meta.clone() as Rc - )), - Rc::new(StoreRevMut::new( - data_cache.merkle.payload.clone() as Rc - )), + Rc::new(StoreRevMut::new(data_cache.merkle.meta.clone())), + Rc::new(StoreRevMut::new(data_cache.merkle.payload.clone())), ), blob: SubUniverse::new( - Rc::new(StoreRevMut::new( - data_cache.blob.meta.clone() as Rc - )), - Rc::new(StoreRevMut::new( - data_cache.blob.payload.clone() as Rc - )), + Rc::new(StoreRevMut::new(data_cache.blob.meta.clone())), + Rc::new(StoreRevMut::new(data_cache.blob.payload.clone())), ), }; let root_hash_staging = StoreRevMut::new(root_hash_cache); diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index af1bade5d4fb..131516309006 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -5,10 +5,7 @@ use std::rc::Rc; use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; -use super::{ - AshRecord, CachedSpace, FilePool, MemStoreR, Page, StoreDelta, StoreError, WalConfig, - PAGE_SIZE_NBIT, -}; +use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; use aiofut::{AioBuilder, AioError, AioManager}; use growthring::WalFileImpl; @@ -128,6 +125,7 @@ impl DiskBuffer { }) } + // TODO: fix this unsafe fn get_longlive_self(&mut self) -> &'static mut Self { std::mem::transmute::<&mut Self, &'static mut Self>(self) } @@ -426,14 +424,6 @@ pub struct DiskBufferRequester { sender: mpsc::Sender, } -impl Default for DiskBufferRequester { - fn default() -> Self { - Self { - sender: mpsc::channel(1).0, - } - } -} - impl DiskBufferRequester { /// Create a new requester. pub fn new(sender: mpsc::Sender) -> Self { @@ -441,10 +431,10 @@ impl DiskBufferRequester { } /// Get a page from the buffer. - pub fn get_page(&self, space_id: SpaceId, pid: u64) -> Option { + pub fn get_page(&self, space_id: SpaceId, page_id: u64) -> Option { let (resp_tx, resp_rx) = oneshot::channel(); self.sender - .blocking_send(BufferCmd::GetPage((space_id, pid), resp_tx)) + .blocking_send(BufferCmd::GetPage((space_id, page_id), resp_tx)) .map_err(StoreError::Send) .ok(); resp_rx.blocking_recv().unwrap() @@ -481,11 +471,9 @@ impl DiskBufferRequester { } /// Register a cached space to the buffer. - pub fn reg_cached_space(&self, space: &CachedSpace) { - let mut inner = space.inner.borrow_mut(); - inner.disk_buffer = self.clone(); + pub fn reg_cached_space(&self, space_id: SpaceId, files: Arc) { self.sender - .blocking_send(BufferCmd::RegCachedSpace(space.id(), inner.files.clone())) + .blocking_send(BufferCmd::RegCachedSpace(space_id, files)) .map_err(StoreError::Send) .ok(); } @@ -501,7 +489,8 @@ mod tests { use crate::{ file, storage::{ - Ash, DeltaPage, StoreConfig, StoreRevMut, StoreRevMutDelta, StoreRevShared, ZeroStore, + Ash, CachedSpace, DeltaPage, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, + StoreRevShared, ZeroStore, }, }; use shale::CachedStore; @@ -540,13 +529,14 @@ mod tests { .file_nbit(1) .rootdir(state_path) .build(), + disk_requester.clone(), ) .unwrap(), ); // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.as_ref()); + disk_requester.reg_cached_space(state_cache.id(), state_cache.inner.borrow().files.clone()); // memory mapped store let mut mut_store = StoreRevMut::new(state_cache); @@ -627,13 +617,14 @@ mod tests { .file_nbit(1) .rootdir(state_path) .build(), + disk_requester.clone(), ) .unwrap(), ); // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.as_ref()); + disk_requester.reg_cached_space(state_cache.id(), state_cache.clone_files()); // memory mapped store let mut mut_store = StoreRevMut::new(state_cache.clone()); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index e18438feaf1e..3c4b16fbbf43 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,7 +4,6 @@ pub mod buffer; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::fmt::{self, Debug}; -use std::io; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; @@ -654,7 +653,7 @@ struct CachedSpaceInner { cached_pages: lru::LruCache, pinned_pages: HashMap, files: Arc, - disk_buffer: DiskBufferRequester, + disk_requester: DiskBufferRequester, } #[derive(Clone, Debug)] @@ -664,7 +663,10 @@ pub struct CachedSpace { } impl CachedSpace { - pub fn new(cfg: &StoreConfig) -> Result> { + pub fn new( + cfg: &StoreConfig, + disk_requester: DiskBufferRequester, + ) -> Result> { let space_id = cfg.space_id; let files = Arc::new(FilePool::new(cfg)?); Ok(Self { @@ -674,12 +676,16 @@ impl CachedSpace { ), pinned_pages: HashMap::new(), files, - disk_buffer: DiskBufferRequester::default(), + disk_requester, })), space_id, }) } + pub fn clone_files(&self) -> Arc { + self.inner.borrow().files.clone() + } + /// Apply `delta` to the store and return the StoreDelta that can undo this change. pub fn update(&self, delta: &StoreDelta) -> Option { let mut pages = Vec::new(); @@ -695,25 +701,6 @@ impl CachedSpace { } impl CachedSpaceInner { - fn fetch_page(&mut self, space_id: SpaceId, pid: u64) -> Result> { - if let Some(p) = self.disk_buffer.get_page(space_id, pid) { - return Ok(Box::new(*p)); - } - let file_nbit = self.files.get_file_nbit(); - let file_size = 1 << file_nbit; - let poff = pid << PAGE_SIZE_NBIT; - let file = self.files.get_file(poff >> file_nbit)?; - let mut page = Page::new([0; PAGE_SIZE as usize]); - nix::sys::uio::pread( - file.get_fd(), - page.deref_mut(), - (poff & (file_size - 1)) as nix::libc::off_t, - ) - .map_err(StoreError::System)?; - - Ok(page) - } - fn pin_page( &mut self, space_id: SpaceId, @@ -725,15 +712,37 @@ impl CachedSpaceInner { e.1.as_mut_ptr() } None => { - let mut page = match self.cached_pages.pop(&pid) { - Some(p) => p, - None => self.fetch_page(space_id, pid)?, + let page = self + .cached_pages + .pop(&pid) + .or_else(|| self.disk_requester.get_page(space_id, pid)); + let mut page = match page { + Some(page) => page, + None => { + let file_nbit = self.files.get_file_nbit(); + let file_size = 1 << file_nbit; + let poff = pid << PAGE_SIZE_NBIT; + let file = self.files.get_file(poff >> file_nbit)?; + let mut page: Page = Page::new([0; PAGE_SIZE as usize]); + + nix::sys::uio::pread( + file.get_fd(), + page.deref_mut(), + (poff & (file_size - 1)) as nix::libc::off_t, + ) + .map_err(StoreError::System)?; + + page + } }; + let ptr = page.as_mut_ptr(); self.pinned_pages.insert(pid, (1, page)); + ptr } }; + Ok(unsafe { std::slice::from_raw_parts_mut(base, PAGE_SIZE as usize) }) } From 46d5486780658c53bbc32047c8d2a535ae06d851 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 14 Jun 2023 09:58:30 -0700 Subject: [PATCH 0189/1053] Improve concurrency for ShaleStore (#137) --- firewood-shale/src/compact.rs | 16 ++++-- firewood-shale/src/lib.rs | 30 +++++++++-- firewood/src/db.rs | 1 + firewood/src/merkle.rs | 98 +++++++++++++++++++++++------------ 4 files changed, 104 insertions(+), 41 deletions(-) diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index 43d58ab62042..2078adcd6d6b 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -1,8 +1,10 @@ use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; use std::cell::UnsafeCell; +use std::fmt::Debug; use std::io::{Cursor, Write}; use std::rc::Rc; +#[derive(Debug)] pub struct CompactHeader { payload_size: u64, is_freed: bool, @@ -53,6 +55,7 @@ impl Storable for CompactHeader { } } +#[derive(Debug)] struct CompactFooter { payload_size: u64, } @@ -83,7 +86,7 @@ impl Storable for CompactFooter { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] struct CompactDescriptor { payload_size: u64, haddr: u64, // pointer to the payload of freed space @@ -122,6 +125,7 @@ impl Storable for CompactDescriptor { } } +#[derive(Debug)] pub struct CompactSpaceHeader { meta_space_tail: u64, compact_space_tail: u64, @@ -168,7 +172,7 @@ impl CompactSpaceHeader { } impl Storable for CompactSpaceHeader { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: u64, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -248,6 +252,7 @@ impl std::ops::DerefMut for ObjPtrField { } } +#[derive(Debug)] struct U64Field(u64); impl U64Field { @@ -305,7 +310,7 @@ impl CompactSpaceInner { StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } - fn get_data_ref( + fn get_data_ref( &self, ptr: ObjPtr, len_limit: u64, @@ -557,6 +562,7 @@ impl CompactSpaceInner { } } +#[derive(Debug)] pub struct CompactSpace { inner: UnsafeCell>, } @@ -584,7 +590,9 @@ impl CompactSpace { } } -impl ShaleStore for CompactSpace { +impl ShaleStore + for CompactSpace +{ fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let inner = unsafe { &mut *self.inner.get() }; diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index a467082b75d9..d0c01a039ee3 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -91,6 +91,7 @@ pub trait CachedStore: Debug { /// Opaque typed pointer in the 64-bit virtual addressable space. #[repr(C)] +#[derive(Debug)] pub struct ObjPtr { pub(crate) addr: u64, phantom: PhantomData, @@ -150,7 +151,7 @@ impl ObjPtr { /// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object /// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to /// turn a `TypedView` into an [ObjRef]. -pub trait TypedView: Deref { +pub trait TypedView: std::fmt::Debug + Deref { /// Get the offset of the initial byte in the linear space. fn get_offset(&self) -> u64; /// Access it as a [CachedStore] object. @@ -175,6 +176,7 @@ pub trait TypedView: Deref { /// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. /// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. +#[derive(Debug)] pub struct Obj { value: Box>, dirty: Option, @@ -240,6 +242,7 @@ impl Deref for Obj { } /// User handle that offers read & write access to the stored [ShaleStore] item. +#[derive(Debug)] pub struct ObjRef<'a, T> { inner: Option>, cache: ObjCache, @@ -291,7 +294,7 @@ impl<'a, T> Drop for ObjRef<'a, T> { /// A persistent item storage backed by linear logical space. New items can be created and old /// items could be retrieved or dropped. -pub trait ShaleStore { +pub trait ShaleStore: Debug { /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; /// Allocate a new item. @@ -330,6 +333,22 @@ pub struct StoredView { len_limit: u64, } +impl Debug for StoredView { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let StoredView { + decoded, + offset, + len_limit, + mem: _, + } = self; + f.debug_struct("StoredView") + .field("decoded", decoded) + .field("offset", offset) + .field("len_limit", len_limit) + .finish() + } +} + impl Deref for StoredView { type Target = T; fn deref(&self) -> &T { @@ -337,7 +356,7 @@ impl Deref for StoredView { } } -impl TypedView for StoredView { +impl TypedView for StoredView { fn get_offset(&self) -> u64 { self.offset } @@ -371,7 +390,7 @@ impl TypedView for StoredView { } } -impl StoredView { +impl StoredView { #[inline(always)] fn new(offset: u64, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; @@ -439,7 +458,7 @@ impl StoredView { }) } - pub fn slice( + pub fn slice( s: &Obj, offset: u64, length: u64, @@ -503,6 +522,7 @@ struct ObjCacheInner { } /// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. +#[derive(Debug)] pub struct ObjCache(Rc>>); impl ObjCache { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 379e94429dce..4d4581b9e863 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -227,6 +227,7 @@ fn get_sub_universe_from_empty_delta( } /// DB-wide metadata, it keeps track of the roots of the top-level tries. +#[derive(Debug)] struct DbHeader { /// The root node of the account model storage. (Where the values are [Account] objects, which /// may contain the root for the secondary trie.) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7e999985bb35..74b57a64a7bb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -7,12 +7,12 @@ use enum_as_inner::EnumAsInner; use sha3::Digest; use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; -use std::cell::{Cell, OnceCell}; use std::cmp; use std::collections::HashMap; use std::error::Error; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::OnceLock; pub const NBRANCH: usize = 16; @@ -212,9 +212,10 @@ impl BranchNode { let mut c_ref = store.get_item(*c).unwrap(); if c_ref.get_eth_rlp_long::(store) { stream.append(&&(*c_ref.get_root_hash::(store))[..]); - if c_ref.lazy_dirty.get() { + // See struct docs for ordering requirements + if c_ref.lazy_dirty.load(Ordering::Relaxed) { c_ref.write(|_| {}).unwrap(); - c_ref.lazy_dirty.set(false) + c_ref.lazy_dirty.store(false, Ordering::Relaxed) } } else { let c_rlp = &c_ref.get_eth_rlp::(store); @@ -325,9 +326,9 @@ impl ExtNode { stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); if r.get_eth_rlp_long::(store) { stream.append(&&(*r.get_root_hash::(store))[..]); - if r.lazy_dirty.get() { + if r.lazy_dirty.load(Ordering::Relaxed) { r.write(|_| {}).unwrap(); - r.lazy_dirty.set(false) + r.lazy_dirty.store(false, Ordering::Relaxed); } } else { stream.append_raw(r.get_eth_rlp::(store), 1); @@ -370,15 +371,47 @@ impl ExtNode { } } -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug)] pub struct Node { - root_hash: OnceCell, - eth_rlp_long: OnceCell, - eth_rlp: OnceCell>, - lazy_dirty: Cell, + root_hash: OnceLock, + eth_rlp_long: OnceLock, + eth_rlp: OnceLock>, + // lazy_dirty is an atomicbool, but only writers ever set it + // Therefore, we can always use Relaxed ordering. It's atomic + // just to ensure Sync + Send. + lazy_dirty: AtomicBool, inner: NodeType, } +impl Eq for Node {} +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + let Node { + root_hash, + eth_rlp_long, + eth_rlp, + lazy_dirty, + inner, + } = self; + *root_hash == other.root_hash + && *eth_rlp_long == other.eth_rlp_long + && *eth_rlp == other.eth_rlp + && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) + && *inner == other.inner + } +} +impl Clone for Node { + fn clone(&self) -> Self { + Self { + root_hash: self.root_hash.clone(), + eth_rlp_long: self.eth_rlp_long.clone(), + eth_rlp: self.eth_rlp.clone(), + lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), + inner: self.inner.clone(), + } + } +} + #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] pub enum NodeType { Branch(BranchNode), @@ -402,18 +435,18 @@ impl Node { const LEAF_NODE: u8 = 0x2; fn max_branch_node_size() -> u64 { - let max_size: OnceCell = OnceCell::new(); + let max_size: OnceLock = OnceLock::new(); *max_size.get_or_init(|| { Self { - root_hash: OnceCell::new(), - eth_rlp_long: OnceCell::new(), - eth_rlp: OnceCell::new(), + root_hash: OnceLock::new(), + eth_rlp_long: OnceLock::new(), + eth_rlp: OnceLock::new(), inner: NodeType::Branch(BranchNode { chd: [Some(ObjPtr::null()); NBRANCH], value: Some(Data(Vec::new())), chd_eth_rlp: Default::default(), }), - lazy_dirty: Cell::new(false), + lazy_dirty: AtomicBool::new(false), } .dehydrated_len() }) @@ -426,31 +459,31 @@ impl Node { fn get_root_hash(&self, store: &dyn ShaleStore) -> &TrieHash { self.root_hash.get_or_init(|| { - self.lazy_dirty.set(true); + self.lazy_dirty.store(true, Ordering::Relaxed); TrieHash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) }) } fn get_eth_rlp_long(&self, store: &dyn ShaleStore) -> bool { *self.eth_rlp_long.get_or_init(|| { - self.lazy_dirty.set(true); + self.lazy_dirty.store(true, Ordering::Relaxed); self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN }) } fn rehash(&mut self) { - self.eth_rlp = OnceCell::new(); - self.eth_rlp_long = OnceCell::new(); - self.root_hash = OnceCell::new(); + self.eth_rlp = OnceLock::new(); + self.eth_rlp_long = OnceLock::new(); + self.root_hash = OnceLock::new(); } pub fn new(inner: NodeType) -> Self { let mut s = Self { - root_hash: OnceCell::new(), - eth_rlp_long: OnceCell::new(), - eth_rlp: OnceCell::new(), + root_hash: OnceLock::new(), + eth_rlp_long: OnceLock::new(), + eth_rlp: OnceLock::new(), inner, - lazy_dirty: Cell::new(false), + lazy_dirty: AtomicBool::new(false), }; s.rehash(); s @@ -471,16 +504,16 @@ impl Node { ) -> Self { Self { root_hash: match root_hash { - Some(h) => OnceCell::from(h), - None => OnceCell::new(), + Some(h) => OnceLock::from(h), + None => OnceLock::new(), }, eth_rlp_long: match eth_rlp_long { - Some(b) => OnceCell::from(b), - None => OnceCell::new(), + Some(b) => OnceLock::from(b), + None => OnceLock::new(), }, - eth_rlp: OnceCell::new(), + eth_rlp: OnceLock::new(), inner, - lazy_dirty: Cell::new(false), + lazy_dirty: AtomicBool::new(false), } } @@ -807,6 +840,7 @@ macro_rules! write_node { }; } +#[derive(Debug)] pub struct Merkle { store: Box>, } @@ -875,9 +909,9 @@ impl Merkle { Ok(if let Some(root) = root { let mut node = self.get_node(root)?; let res = node.get_root_hash::(self.store.as_ref()).clone(); - if node.lazy_dirty.get() { + if node.lazy_dirty.load(Ordering::Relaxed) { node.write(|_| {}).unwrap(); - node.lazy_dirty.set(false) + node.lazy_dirty.store(false, Ordering::Relaxed); } res } else { From 9157b00463afab3c3b9de64769342241211ea9fc Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 14 Jun 2023 13:34:06 -0400 Subject: [PATCH 0190/1053] Use dependency injection in DiskBuffer (#136) --- firewood-growth-ring/src/wal.rs | 36 ++ firewood/src/storage/buffer.rs | 710 ++++++++++++++++++++------------ 2 files changed, 480 insertions(+), 266 deletions(-) diff --git a/firewood-growth-ring/src/wal.rs b/firewood-growth-ring/src/wal.rs index 680a5521a610..9769240db015 100644 --- a/firewood-growth-ring/src/wal.rs +++ b/firewood-growth-ring/src/wal.rs @@ -431,8 +431,10 @@ impl> WalFilePool { }; let mut removes: Vec>>>> = Vec::new(); + while state.pending_removal.len() > 1 { let (fid, counter) = state.pending_removal.front().unwrap(); + if counter_lt(counter + keep_nrecords, state.counter) { removes.push(self.store.remove_file(get_fname(*fid)) as Pin + 'a>>); @@ -441,19 +443,24 @@ impl> WalFilePool { break; } } + let p = async move { last_peel.await.ok(); + for r in removes.into_iter() { r.await.ok(); } + Ok(()) } .shared(); + unsafe { (*self.last_peel.get()) = MaybeUninit::new(std::mem::transmute( Box::pin(p.clone()) as Pin + 'a>> )) } + p } @@ -517,23 +524,29 @@ impl> WalWriter { let mut rsize = rec.len() as u32; let mut ring_start = None; assert!(rsize > 0); + while rsize > 0 { let remain = self.block_size - bbuff_cur; + if remain > msize { let d = remain - msize; let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; + let blob = unsafe { &mut *self.block_buffer[bbuff_cur as usize..] .as_mut_ptr() .cast::() }; + bbuff_cur += msize; + if d >= rsize { // the remaining rec fits in the block let payload = rec; blob.counter = self.state.counter; blob.crc32 = CRC32.checksum(payload); blob.rsize = rsize; + let (rs, rt) = if let Some(rs) = ring_start.take() { self.state.counter += 1; (rs, WalRingType::Last) @@ -541,12 +554,14 @@ impl> WalWriter { self.state.counter += 1; (rs0, WalRingType::Full) }; + blob.rtype = rt as u8; self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] .copy_from_slice(payload); bbuff_cur += rsize; rsize = 0; let end = self.state.next + (bbuff_cur - bbuff_start) as u64; + res.push(( WalRingId { start: rs, @@ -561,12 +576,14 @@ impl> WalWriter { blob.counter = self.state.counter; blob.crc32 = CRC32.checksum(payload); blob.rsize = d; + blob.rtype = if ring_start.is_some() { WalRingType::Middle } else { ring_start = Some(rs0); WalRingType::First } as u8; + self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] .copy_from_slice(payload); bbuff_cur += d; @@ -577,6 +594,7 @@ impl> WalWriter { // add padding space by moving the point to the end of the block bbuff_cur = self.block_size; } + if bbuff_cur == self.block_size { writes.push(( self.state.next, @@ -590,6 +608,7 @@ impl> WalWriter { } } } + if bbuff_cur > bbuff_start { writes.push(( self.state.next, @@ -597,26 +616,34 @@ impl> WalWriter { .to_vec() .into_boxed_slice(), )); + self.state.next += (bbuff_cur - bbuff_start) as u64; } // mark the block info for each record let mut i = 0; + 'outer: for (j, (off, w)) in writes.iter().enumerate() { let blk_s = *off; let blk_e = blk_s + w.len() as u64; + while res[i].0.end <= blk_s { i += 1; + if i >= res.len() { break 'outer; } } + while res[i].0.start < blk_e { res[i].1.push(j); + if res[i].0.end >= blk_e { break; } + i += 1; + if i >= res.len() { break 'outer; } @@ -652,19 +679,25 @@ impl> WalWriter { let msize = self.msize as u64; let block_size = self.block_size as u64; let state = &mut self.state; + for rec in records.as_ref() { state.io_complete.push(*rec); } + while let Some(s) = state.io_complete.peek().map(|&e| e.start) { if s != state.next_complete.end { break; } + let mut m = state.io_complete.pop().unwrap(); let block_remain = block_size - (m.end & (block_size - 1)); + if block_remain <= msize { m.end += block_remain } + let fid = m.start >> state.file_nbit; + match state.pending_removal.back_mut() { Some(l) => { if l.0 == fid { @@ -677,9 +710,12 @@ impl> WalWriter { } None => state.pending_removal.push_back((fid, m.counter)), } + state.next_complete = m; } + self.file_pool.remove_files(state, keep_nrecords).await.ok(); + Ok(()) } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 131516309006..83ff97dd6052 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,13 +1,19 @@ //! Disk buffer for staging in memory pages and flushing them to disk. use std::fmt::Debug; -use std::path::PathBuf; +use std::ops::IndexMut; +use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering::Relaxed; use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; +use crate::storage::DeltaPage; + use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; use aiofut::{AioBuilder, AioError, AioManager}; +use futures::future::join_all; use growthring::WalFileImpl; use growthring::{ wal::{RecoverPolicy, WalLoader, WalWriter}, @@ -74,23 +80,34 @@ pub struct BufferWrite { struct PendingPage { staging_data: Page, file_nbit: u64, - staging_notifiers: Vec>, - writing_notifiers: Vec>, + notifiers: Notifiers, +} + +#[derive(Debug)] +struct Notifiers { + staging: Vec>, + writing: Vec>, +} + +impl Notifiers { + /// adds one permit to earch semaphore clone and leaves an empty `Vec` for `writing` + fn drain_writing(&mut self) { + std::mem::take(&mut self.writing) + .into_iter() + .for_each(|notifier| notifier.add_permits(1)) + } + + /// takes all staging semaphores and moves them to writing, leaving an `Vec` for `staging` + fn staging_to_writing(&mut self) { + self.writing = std::mem::take(&mut self.staging); + } } /// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] /// and managing the persistance of pages. pub struct DiskBuffer { - pending: HashMap<(SpaceId, u64), PendingPage>, inbound: mpsc::Receiver, - fc_notifier: Option>, - fc_blocker: Option>, - file_pools: [Option>; 255], aiomgr: AioManager, - local_pool: Rc, - task_id: u64, - tasks: Rc>>>>, - wal: Option>>>, cfg: DiskBufferConfig, wal_cfg: WalConfig, } @@ -110,302 +127,463 @@ impl DiskBuffer { .map_err(|_| AioError::OtherError)?; Ok(Self { - pending: HashMap::new(), cfg: cfg.clone(), inbound, - fc_notifier: None, - fc_blocker: None, - file_pools: std::array::from_fn(|_| None), aiomgr, - local_pool: Rc::new(tokio::task::LocalSet::new()), - task_id: 0, - tasks: Rc::new(RefCell::new(HashMap::new())), - wal: None, wal_cfg: wal.clone(), }) } - // TODO: fix this - unsafe fn get_longlive_self(&mut self) -> &'static mut Self { - std::mem::transmute::<&mut Self, &'static mut Self>(self) + #[tokio::main(flavor = "current_thread")] + pub async fn run(self) { + // TODO: call local_pool.await to make sure that all pending futures finish instead of using the `tasks` HashMap. + static TASK_ID: AtomicU64 = AtomicU64::new(0); + + let mut inbound = self.inbound; + let aiomgr = Rc::new(self.aiomgr); + let cfg = self.cfg; + let wal_cfg = self.wal_cfg; + + let pending = Rc::new(RefCell::new(HashMap::new())); + let file_pools = Rc::new(RefCell::new(std::array::from_fn(|_| None))); + let local_pool = Rc::new(tokio::task::LocalSet::new()); + let tasks = Rc::new(RefCell::new(HashMap::new())); + + let max = WalQueueMax { + batch: cfg.wal_max_batch, + revisions: wal_cfg.max_revisions, + pending: cfg.max_pending, + }; + + let (wal_in, writes) = mpsc::channel(cfg.wal_max_buffered); + + let mut writes = Some(writes); + let mut wal = None; + + let notifier = Rc::new(tokio::sync::Notify::new()); + + local_pool + .run_until(async { + loop { + let pending_len = pending.borrow().len(); + if pending_len >= cfg.max_pending { + notifier.notified().await; + } + + let req = inbound.recv().await.unwrap(); + + let process_result = process( + pending.clone(), + notifier.clone(), + file_pools.clone(), + aiomgr.clone(), + local_pool.clone(), + tasks.clone(), + &mut wal, + &cfg, + &wal_cfg, + req, + max, + wal_in.clone(), + &TASK_ID, + &mut writes, + ) + .await; + + if !process_result { + break; + } + } + + drop(wal_in); + + let handles: Vec<_> = tasks + .borrow_mut() + .iter_mut() + .map(|(_, task)| task.take().unwrap()) + .collect(); + + for h in handles { + h.await.unwrap(); + } + }) + .await; } +} +#[derive(Clone, Copy)] +struct WalQueueMax { + batch: usize, + revisions: u32, + pending: usize, +} + +/// Processes an async AIOFuture request against the local pool. +fn start_task + 'static>( + task_id: &AtomicU64, + tasks: Rc>>>>, + local_pool: Rc, + fut: F, +) { + let key = task_id.fetch_add(1, Relaxed); + let handle = local_pool.spawn_local({ + let tasks = tasks.clone(); + + async move { + fut.await; + tasks.borrow_mut().remove(&key); + } + }); + + tasks.borrow_mut().insert(key, handle.into()); +} - /// Add an pending pages to aio manager for processing by the local pool. - fn schedule_write(&mut self, page_key: (SpaceId, u64)) { - let p = self.pending.get(&page_key).unwrap(); +/// Add an pending pages to aio manager for processing by the local pool. +#[allow(clippy::too_many_arguments)] +fn schedule_write( + pending: Rc>>, + fc_notifier: Rc, + file_pools: Rc>; 255]>>, + aiomgr: Rc, + local_pool: Rc, + task_id: &'static AtomicU64, + tasks: Rc>>>>, + max: WalQueueMax, + page_key: (SpaceId, u64), +) { + use std::collections::hash_map::Entry::*; + + let fut = { + let pending = pending.borrow(); + let p = pending.get(&page_key).unwrap(); let offset = page_key.1 << PAGE_SIZE_NBIT; let fid = offset >> p.file_nbit; let fmask = (1 << p.file_nbit) - 1; - let file = self.file_pools[page_key.0 as usize] + let file = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() .get_file(fid) .unwrap(); - let fut = self - .aiomgr - .write(file.get_fd(), offset & fmask, p.staging_data.clone(), None); - let s = unsafe { self.get_longlive_self() }; - self.start_task(async move { + aiomgr.write(file.get_fd(), offset & fmask, p.staging_data.clone(), None) + }; + + let task = { + let tasks = tasks.clone(); + let local_pool = local_pool.clone(); + + async move { let (res, _) = fut.await; res.unwrap(); - s.finish_write(page_key); - }); - } - fn finish_write(&mut self, page_key: (SpaceId, u64)) { - use std::collections::hash_map::Entry::*; - match self.pending.entry(page_key) { - Occupied(mut e) => { - let slot = e.get_mut(); - for notifier in std::mem::take(&mut slot.writing_notifiers) { - notifier.add_permits(1) - } - if slot.staging_notifiers.is_empty() { - e.remove(); - if self.pending.len() < self.cfg.max_pending { - if let Some(notifier) = self.fc_notifier.take() { - notifier.send(()).unwrap(); + let pending_len = pending.borrow().len(); + + let write_again = match pending.borrow_mut().entry(page_key) { + Occupied(mut e) => { + let slot = e.get_mut(); + + slot.notifiers.drain_writing(); + + // if staging is empty, all we need to do is notify any potential waiters + if slot.notifiers.staging.is_empty() { + e.remove(); + + if pending_len < max.pending { + fc_notifier.notify_waiters(); } + + false + } else { + // if `staging` is not empty, move all semaphors to `writing` and recurse + // to schedule the new writes. + slot.notifiers.staging_to_writing(); + + true } - } else { - assert!(slot.writing_notifiers.is_empty()); - std::mem::swap(&mut slot.writing_notifiers, &mut slot.staging_notifiers); - // write again - self.schedule_write(page_key); } + _ => unreachable!(), + }; + + if write_again { + schedule_write( + pending, + fc_notifier, + file_pools, + aiomgr, + local_pool, + task_id, + tasks, + max, + page_key, + ); } - _ => unreachable!(), } - } + }; - /// Initialize the Wal subsystem if it does not exists and attempts to replay the Wal if exists. - async fn init_wal(&mut self, rootpath: PathBuf, waldir: String) -> Result<(), WalError> { - let final_path = rootpath.clone().join(waldir.clone()); - let mut aiobuilder = AioBuilder::default(); - aiobuilder.max_events(self.cfg.wal_max_aio_requests as u32); - let store = WalStoreImpl::new(final_path.clone(), false)?; - let mut loader = WalLoader::new(); - loader - .file_nbit(self.wal_cfg.file_nbit) - .block_nbit(self.wal_cfg.block_nbit) - .recover_policy(RecoverPolicy::Strict); - if self.wal.is_some() { - // already initialized - return Ok(()); - } - let wal = loader - .load( - store, - |raw, _| { - let batch = AshRecord::deserialize(raw); - for (space_id, ash) in batch.0 { - for (undo, redo) in ash.iter() { - let offset = undo.offset; - let file_pool = self.file_pools[space_id as usize].as_ref().unwrap(); - let file_nbit = file_pool.get_file_nbit(); - let file_mask = (1 << file_nbit) - 1; - let fid = offset >> file_nbit; - nix::sys::uio::pwrite( - file_pool - .get_file(fid) - .map_err(|e| { - WalError::Other(format!( - "file pool error: {:?} - final path {:?}", - e, final_path - )) - })? - .get_fd(), - &redo.data, - (offset & file_mask) as nix::libc::off_t, - ) - .map_err(|e| { - WalError::Other(format!( - "wal loader error: {:?} - final path {:?}", - e, final_path - )) - })?; - } + start_task(task_id, tasks, local_pool, task) +} + +/// Initialize the Wal subsystem if it does not exists and attempts to replay the Wal if exists. +async fn init_wal( + file_pools: &Rc>; 255]>>, + cfg: &DiskBufferConfig, + wal_cfg: &WalConfig, + rootpath: &Path, + waldir: &str, +) -> Result>>, WalError> { + let final_path = rootpath.join(waldir); + let mut aiobuilder = AioBuilder::default(); + aiobuilder.max_events(cfg.wal_max_aio_requests as u32); + let store = WalStoreImpl::new(final_path.clone(), false)?; + + let mut loader = WalLoader::new(); + loader + .file_nbit(wal_cfg.file_nbit) + .block_nbit(wal_cfg.block_nbit) + .recover_policy(RecoverPolicy::Strict); + + let wal = loader + .load( + store, + |raw, _| { + let batch = AshRecord::deserialize(raw); + + for (space_id, ash) in batch.0 { + for (undo, redo) in ash.iter() { + let offset = undo.offset; + let file_pools = file_pools.borrow(); + let file_pool = file_pools[space_id as usize].as_ref().unwrap(); + let file_nbit = file_pool.get_file_nbit(); + let file_mask = (1 << file_nbit) - 1; + let fid = offset >> file_nbit; + + nix::sys::uio::pwrite( + file_pool + .get_file(fid) + .map_err(|e| { + WalError::Other(format!( + "file pool error: {:?} - final path {:?}", + e, final_path + )) + })? + .get_fd(), + &redo.data, + (offset & file_mask) as nix::libc::off_t, + ) + .map_err(|e| { + WalError::Other(format!( + "wal loader error: {:?} - final path {:?}", + e, final_path + )) + })?; } - Ok(()) - }, - self.wal_cfg.max_revisions, - ) - .await?; - self.wal = Some(Rc::new(Mutex::new(wal))); - Ok(()) - } + } + + Ok(()) + }, + wal_cfg.max_revisions, + ) + .await?; + + Ok(Rc::new(Mutex::new(wal))) +} + +#[allow(clippy::too_many_arguments)] +async fn run_wal_queue( + task_id: &'static AtomicU64, + max: WalQueueMax, + wal: Rc>>, + pending: Rc>>, + tasks: Rc>>>>, + local_pool: Rc, + file_pools: Rc>; 255]>>, + mut writes: mpsc::Receiver<(Vec, AshRecord)>, + fc_notifier: Rc, + aiomgr: Rc, +) { + use std::collections::hash_map::Entry::*; + + loop { + let mut bwrites = Vec::new(); + let mut records = Vec::new(); + let wal = wal.clone(); + + if let Some((bw, ac)) = writes.recv().await { + records.push(ac); + bwrites.extend(bw); + } else { + break; + } - async fn run_wal_queue(&mut self, mut writes: mpsc::Receiver<(Vec, AshRecord)>) { - use std::collections::hash_map::Entry::*; - loop { - let mut bwrites = Vec::new(); - let mut records = Vec::new(); + while let Ok((bw, ac)) = writes.try_recv() { + records.push(ac); + bwrites.extend(bw); - if let Some((bw, ac)) = writes.recv().await { - records.push(ac); - bwrites.extend(bw); - } else { + if records.len() >= max.batch { break; } - while let Ok((bw, ac)) = writes.try_recv() { - records.push(ac); - bwrites.extend(bw); - if records.len() >= self.cfg.wal_max_batch { - break; - } - } - // first write to Wal - let ring_ids: Vec<_> = - futures::future::join_all(self.wal.as_ref().unwrap().lock().await.grow(records)) - .await - .into_iter() - .map(|ring| ring.map_err(|_| "Wal Error while writing").unwrap().1) - .collect(); - let sem = Rc::new(tokio::sync::Semaphore::new(0)); - let mut npermit = 0; - for BufferWrite { space_id, delta } in bwrites { - for w in delta.0 { - let page_key = (space_id, w.0); - match self.pending.entry(page_key) { - Occupied(mut e) => { - let e = e.get_mut(); - e.staging_data = w.1; - e.staging_notifiers.push(sem.clone()); - npermit += 1; - } - Vacant(e) => { - let file_nbit = self.file_pools[page_key.0 as usize] - .as_ref() - .unwrap() - .file_nbit; - e.insert(PendingPage { - staging_data: w.1, - file_nbit, - staging_notifiers: Vec::new(), - writing_notifiers: vec![sem.clone()], - }); - npermit += 1; - self.schedule_write(page_key); - } + } + + // first write to Wal + let ring_ids = join_all(wal.clone().lock().await.grow(records)) + .await + .into_iter() + .map(|ring| ring.map_err(|_| "Wal Error while writing").unwrap().1) + .collect::>(); + let sem = Rc::new(tokio::sync::Semaphore::new(0)); + let mut npermit = 0; + + for BufferWrite { space_id, delta } in bwrites { + for DeltaPage(page_id, page) in delta.0 { + let page_key = (space_id, page_id); + + let should_write = match pending.borrow_mut().entry(page_key) { + Occupied(mut e) => { + let e = e.get_mut(); + e.staging_data = page; + e.notifiers.staging.push(sem.clone()); + + false } + Vacant(e) => { + let file_nbit = file_pools.borrow()[page_key.0 as usize] + .as_ref() + .unwrap() + .file_nbit; + + e.insert(PendingPage { + staging_data: page, + file_nbit, + notifiers: { + let semaphore = sem.clone(); + Notifiers { + staging: Vec::new(), + writing: vec![semaphore], + } + }, + }); + + true + } + }; + + if should_write { + schedule_write( + pending.clone(), + fc_notifier.clone(), + file_pools.clone(), + aiomgr.clone(), + local_pool.clone(), + task_id, + tasks.clone(), + max, + page_key, + ); } - } - let wal = self.wal.as_ref().unwrap().clone(); - let max_revisions = self.wal_cfg.max_revisions; - self.start_task(async move { - let _ = sem.acquire_many(npermit).await.unwrap(); - wal.lock() - .await - .peel(ring_ids, max_revisions) - .await - .map_err(|_| "Wal errore while pruning") - .unwrap(); - }); - if self.pending.len() >= self.cfg.max_pending { - let (tx, rx) = oneshot::channel(); - self.fc_notifier = Some(tx); - self.fc_blocker = Some(rx); - } - } - } - async fn process( - &mut self, - req: BufferCmd, - wal_in: &mpsc::Sender<(Vec, AshRecord)>, - ) -> bool { - match req { - BufferCmd::Shutdown => return false, - BufferCmd::InitWal(root_path, waldir) => { - self.init_wal(root_path.clone(), waldir.clone()) - .await - .unwrap_or_else(|e| { - panic!( - "Initialize Wal in dir {:?} failed creating {:?}: {e:?}", - root_path, waldir - ) - }); - } - BufferCmd::GetPage(page_key, tx) => tx - .send(self.pending.get(&page_key).map(|e| e.staging_data.clone())) - .unwrap(), - BufferCmd::WriteBatch(writes, wal_writes) => { - wal_in.send((writes, wal_writes)).await.unwrap(); - } - BufferCmd::CollectAsh(nrecords, tx) => { - // wait to ensure writes are paused for Wal - let ash = self - .wal - .as_ref() - .unwrap() - .clone() - .lock() - .await - .read_recent_records(nrecords, &RecoverPolicy::Strict) - .await - .unwrap() - .into_iter() - .map(AshRecord::deserialize) - .collect(); - tx.send(ash).unwrap(); - } - BufferCmd::RegCachedSpace(space_id, files) => { - self.file_pools[space_id as usize] = Some(files) + npermit += 1; } } - true - } - /// Processes an async AIOFuture request against the local pool. - fn start_task + 'static>(&mut self, fut: F) { - let task_id = self.task_id; - self.task_id += 1; - let tasks = self.tasks.clone(); - self.tasks.borrow_mut().insert( - task_id, - Some(self.local_pool.spawn_local(async move { - fut.await; - tasks.borrow_mut().remove(&task_id); - })), - ); - } + let task = async move { + let _ = sem.acquire_many(npermit).await.unwrap(); - #[tokio::main(flavor = "current_thread")] - pub async fn run(mut self) { - let wal_in = { - let (tx, rx) = mpsc::channel(self.cfg.wal_max_buffered); - let s = unsafe { self.get_longlive_self() }; - self.start_task(s.run_wal_queue(rx)); - tx + wal.lock() + .await + .peel(ring_ids, max.revisions) + .await + .map_err(|_| "Wal errore while pruning") + .unwrap() }; - self.local_pool - .clone() - .run_until(async { - loop { - if let Some(fc) = self.fc_blocker.take() { - // flow control, wait until ready - fc.await.unwrap(); - } - let req = self.inbound.recv().await.unwrap(); - if !self.process(req, &wal_in).await { - break; - } - } - drop(wal_in); - let handles: Vec<_> = self - .tasks - .borrow_mut() - .iter_mut() - .map(|(_, task)| task.take().unwrap()) - .collect(); - for h in handles { - h.await.unwrap(); - } - }) - .await; + + start_task(task_id, tasks.clone(), local_pool.clone(), task); } } +#[allow(clippy::too_many_arguments)] +async fn process( + pending: Rc>>, + fc_notifier: Rc, + file_pools: Rc>; 255]>>, + aiomgr: Rc, + local_pool: Rc, + tasks: Rc>>>>, + wal: &mut Option>>>, + cfg: &DiskBufferConfig, + wal_cfg: &WalConfig, + req: BufferCmd, + max: WalQueueMax, + wal_in: mpsc::Sender<(Vec, AshRecord)>, + task_id: &'static AtomicU64, + writes: &mut Option, AshRecord)>>, +) -> bool { + match req { + BufferCmd::Shutdown => return false, + BufferCmd::InitWal(rootpath, waldir) => { + let initialized_wal = init_wal(&file_pools, cfg, wal_cfg, &rootpath, &waldir) + .await + .unwrap_or_else(|e| { + panic!( + "Initialize Wal in dir {:?} failed creating {:?}: {e:?}", + rootpath, waldir + ) + }); + + let writes = writes.take().unwrap(); + + let task = run_wal_queue( + task_id, + max, + initialized_wal.clone(), + pending, + tasks.clone(), + local_pool.clone(), + file_pools.clone(), + writes, + fc_notifier, + aiomgr, + ); + + start_task(task_id, tasks, local_pool, task); + + wal.replace(initialized_wal); + } + BufferCmd::GetPage(page_key, tx) => tx + .send( + pending + .borrow() + .get(&page_key) + .map(|e| e.staging_data.clone()), + ) + .unwrap(), + BufferCmd::WriteBatch(writes, wal_writes) => { + wal_in.send((writes, wal_writes)).await.unwrap(); + } + BufferCmd::CollectAsh(nrecords, tx) => { + // wait to ensure writes are paused for Wal + let ash = wal + .as_ref() + .unwrap() + .lock() + .await + .read_recent_records(nrecords, &RecoverPolicy::Strict) + .await + .unwrap() + .into_iter() + .map(AshRecord::deserialize) + .collect(); + tx.send(ash).unwrap(); + } + BufferCmd::RegCachedSpace(space_id, files) => { + file_pools + .borrow_mut() + .as_mut_slice() + .index_mut(space_id as usize) + .replace(files); + } + } + + true +} + /// Communicates with the [`DiskBuffer`] over channels. #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid From afd8196cf7db16ba5c29b24f6852d17bc4354d2a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 14 Jun 2023 17:29:15 -0400 Subject: [PATCH 0191/1053] disk buffer async (#138) --- firewood/src/storage/buffer.rs | 212 +++++++++++++-------------------- 1 file changed, 81 insertions(+), 131 deletions(-) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 83ff97dd6052..d7077a0eaf7c 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,28 +1,31 @@ //! Disk buffer for staging in memory pages and flushing them to disk. -use std::fmt::Debug; -use std::ops::IndexMut; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering::Relaxed; -use std::sync::Arc; -use std::{cell::RefCell, collections::HashMap}; - -use crate::storage::DeltaPage; - use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; - +use crate::storage::DeltaPage; use aiofut::{AioBuilder, AioError, AioManager}; use futures::future::join_all; -use growthring::WalFileImpl; use growthring::{ wal::{RecoverPolicy, WalLoader, WalWriter}, walerror::WalError, - WalStoreImpl, + WalFileImpl, WalStoreImpl, }; use shale::SpaceId; -use tokio::sync::oneshot::error::RecvError; -use tokio::sync::{mpsc, oneshot, Mutex, Semaphore}; +use std::{ + cell::RefCell, + collections::HashMap, + fmt::Debug, + ops::IndexMut, + path::{Path, PathBuf}, + rc::Rc, + sync::Arc, +}; +use tokio::{ + sync::{ + mpsc, + oneshot::{self, error::RecvError}, + Mutex, Notify, Semaphore, + }, + task, +}; use typed_builder::TypedBuilder; #[derive(Debug)] @@ -136,18 +139,14 @@ impl DiskBuffer { #[tokio::main(flavor = "current_thread")] pub async fn run(self) { - // TODO: call local_pool.await to make sure that all pending futures finish instead of using the `tasks` HashMap. - static TASK_ID: AtomicU64 = AtomicU64::new(0); - let mut inbound = self.inbound; let aiomgr = Rc::new(self.aiomgr); let cfg = self.cfg; let wal_cfg = self.wal_cfg; - let pending = Rc::new(RefCell::new(HashMap::new())); + let pending_writes = Rc::new(RefCell::new(HashMap::new())); let file_pools = Rc::new(RefCell::new(std::array::from_fn(|_| None))); - let local_pool = Rc::new(tokio::task::LocalSet::new()); - let tasks = Rc::new(RefCell::new(HashMap::new())); + let local_pool = tokio::task::LocalSet::new(); let max = WalQueueMax { batch: cfg.wal_max_batch, @@ -160,56 +159,47 @@ impl DiskBuffer { let mut writes = Some(writes); let mut wal = None; - let notifier = Rc::new(tokio::sync::Notify::new()); + let notifier = Rc::new(Notify::new()); local_pool - .run_until(async { + // everything needs to be moved into this future in order to be properly dropped + .run_until(async move { loop { - let pending_len = pending.borrow().len(); + // can't hold the borrowed `pending_writes` across the .await point inside the if-statement + let pending_len = pending_writes.borrow().len(); + if pending_len >= cfg.max_pending { notifier.notified().await; } - let req = inbound.recv().await.unwrap(); - + // process the the request let process_result = process( - pending.clone(), + pending_writes.clone(), notifier.clone(), file_pools.clone(), aiomgr.clone(), - local_pool.clone(), - tasks.clone(), &mut wal, - &cfg, &wal_cfg, - req, + inbound.recv().await.unwrap(), max, wal_in.clone(), - &TASK_ID, &mut writes, ) .await; + // stop handling new requests and exit the loop if !process_result { break; } } - - drop(wal_in); - - let handles: Vec<_> = tasks - .borrow_mut() - .iter_mut() - .map(|(_, task)| task.take().unwrap()) - .collect(); - - for h in handles { - h.await.unwrap(); - } }) .await; + + // when finished process all requests, wait for any pending-futures to complete + local_pool.await; } } + #[derive(Clone, Copy)] struct WalQueueMax { batch: usize, @@ -217,36 +207,12 @@ struct WalQueueMax { pending: usize, } -/// Processes an async AIOFuture request against the local pool. -fn start_task + 'static>( - task_id: &AtomicU64, - tasks: Rc>>>>, - local_pool: Rc, - fut: F, -) { - let key = task_id.fetch_add(1, Relaxed); - let handle = local_pool.spawn_local({ - let tasks = tasks.clone(); - - async move { - fut.await; - tasks.borrow_mut().remove(&key); - } - }); - - tasks.borrow_mut().insert(key, handle.into()); -} - /// Add an pending pages to aio manager for processing by the local pool. -#[allow(clippy::too_many_arguments)] fn schedule_write( pending: Rc>>, - fc_notifier: Rc, + fc_notifier: Rc, file_pools: Rc>; 255]>>, aiomgr: Rc, - local_pool: Rc, - task_id: &'static AtomicU64, - tasks: Rc>>>>, max: WalQueueMax, page_key: (SpaceId, u64), ) { @@ -267,9 +233,6 @@ fn schedule_write( }; let task = { - let tasks = tasks.clone(); - let local_pool = local_pool.clone(); - async move { let (res, _) = fut.await; res.unwrap(); @@ -287,7 +250,7 @@ fn schedule_write( e.remove(); if pending_len < max.pending { - fc_notifier.notify_waiters(); + fc_notifier.notify_one(); } false @@ -303,43 +266,22 @@ fn schedule_write( }; if write_again { - schedule_write( - pending, - fc_notifier, - file_pools, - aiomgr, - local_pool, - task_id, - tasks, - max, - page_key, - ); + schedule_write(pending, fc_notifier, file_pools, aiomgr, max, page_key); } } }; - start_task(task_id, tasks, local_pool, task) + task::spawn_local(task); } /// Initialize the Wal subsystem if it does not exists and attempts to replay the Wal if exists. async fn init_wal( file_pools: &Rc>; 255]>>, - cfg: &DiskBufferConfig, - wal_cfg: &WalConfig, - rootpath: &Path, - waldir: &str, + store: WalStoreImpl, + loader: WalLoader, + max_revisions: u32, + final_path: &Path, ) -> Result>>, WalError> { - let final_path = rootpath.join(waldir); - let mut aiobuilder = AioBuilder::default(); - aiobuilder.max_events(cfg.wal_max_aio_requests as u32); - let store = WalStoreImpl::new(final_path.clone(), false)?; - - let mut loader = WalLoader::new(); - loader - .file_nbit(wal_cfg.file_nbit) - .block_nbit(wal_cfg.block_nbit) - .recover_policy(RecoverPolicy::Strict); - let wal = loader .load( store, @@ -379,24 +321,20 @@ async fn init_wal( Ok(()) }, - wal_cfg.max_revisions, + max_revisions, ) .await?; Ok(Rc::new(Mutex::new(wal))) } -#[allow(clippy::too_many_arguments)] async fn run_wal_queue( - task_id: &'static AtomicU64, max: WalQueueMax, wal: Rc>>, pending: Rc>>, - tasks: Rc>>>>, - local_pool: Rc, file_pools: Rc>; 255]>>, mut writes: mpsc::Receiver<(Vec, AshRecord)>, - fc_notifier: Rc, + fc_notifier: Rc, aiomgr: Rc, ) { use std::collections::hash_map::Entry::*; @@ -471,9 +409,6 @@ async fn run_wal_queue( fc_notifier.clone(), file_pools.clone(), aiomgr.clone(), - local_pool.clone(), - task_id, - tasks.clone(), max, page_key, ); @@ -494,57 +429,72 @@ async fn run_wal_queue( .unwrap() }; - start_task(task_id, tasks.clone(), local_pool.clone(), task); + task::spawn_local(task); } + + // if this function breaks for any reason, make sure there is no one waiting for staged writes. + fc_notifier.notify_one(); +} + +fn panic_on_intialization_failure_with<'a, T>( + rootpath: &'a Path, + waldir: &'a str, +) -> impl Fn(WalError) -> T + 'a { + move |e| panic!("Initialize Wal in dir {rootpath:?} failed creating {waldir:?}: {e:?}") } #[allow(clippy::too_many_arguments)] async fn process( pending: Rc>>, - fc_notifier: Rc, + fc_notifier: Rc, file_pools: Rc>; 255]>>, aiomgr: Rc, - local_pool: Rc, - tasks: Rc>>>>, wal: &mut Option>>>, - cfg: &DiskBufferConfig, wal_cfg: &WalConfig, req: BufferCmd, max: WalQueueMax, wal_in: mpsc::Sender<(Vec, AshRecord)>, - task_id: &'static AtomicU64, writes: &mut Option, AshRecord)>>, ) -> bool { match req { BufferCmd::Shutdown => return false, BufferCmd::InitWal(rootpath, waldir) => { - let initialized_wal = init_wal(&file_pools, cfg, wal_cfg, &rootpath, &waldir) - .await - .unwrap_or_else(|e| { - panic!( - "Initialize Wal in dir {:?} failed creating {:?}: {e:?}", - rootpath, waldir - ) - }); + let final_path = rootpath.join(&waldir); + + let store = WalStoreImpl::new(final_path.clone(), false) + .unwrap_or_else(panic_on_intialization_failure_with(&rootpath, &waldir)); + + let mut loader = WalLoader::new(); + loader + .file_nbit(wal_cfg.file_nbit) + .block_nbit(wal_cfg.block_nbit) + .recover_policy(RecoverPolicy::Strict); + + let initialized_wal = init_wal( + &file_pools, + store, + loader, + wal_cfg.max_revisions, + final_path.as_path(), + ) + .await + .unwrap_or_else(panic_on_intialization_failure_with(&rootpath, &waldir)); + + wal.replace(initialized_wal.clone()); let writes = writes.take().unwrap(); let task = run_wal_queue( - task_id, max, - initialized_wal.clone(), + initialized_wal, pending, - tasks.clone(), - local_pool.clone(), file_pools.clone(), writes, fc_notifier, aiomgr, ); - start_task(task_id, tasks, local_pool, task); - - wal.replace(initialized_wal); + task::spawn_local(task); } BufferCmd::GetPage(page_key, tx) => tx .send( From 885111d500f96a4936e7f07f5a8e78dbed4de705 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 14 Jun 2023 18:02:01 -0400 Subject: [PATCH 0192/1053] Cleanup test directories (#139) --- firewood/tests/db.rs | 49 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index cdbca140e301..8fdf354f82ad 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,7 +1,15 @@ -use firewood::db::{Db, DbConfig, WalConfig}; -use firewood::merkle::TrieHash; -use std::{collections::VecDeque, fs::remove_dir_all, path::Path}; +use firewood::{ + db::{Db as PersistedDb, DbConfig, DbError, WalConfig}, + merkle::TrieHash, +}; +use std::{ + collections::VecDeque, + fs::remove_dir_all, + ops::{Deref, DerefMut}, + path::Path, +}; +// TODO: use a trait macro_rules! kv_dump { ($e: ident) => {{ let mut s = Vec::new(); @@ -10,6 +18,23 @@ macro_rules! kv_dump { }}; } +struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); + +impl<'a, P: AsRef + ?Sized> Db<'a, P> { + fn new(path: &'a P, cfg: &DbConfig) -> Result { + PersistedDb::new(path, cfg).map(|db| Self(db, path)) + } +} + +impl + ?Sized> Drop for Db<'_, P> { + fn drop(&mut self) { + // if you're using absolute paths, you have to clean up after yourself + if self.1.as_ref().is_relative() { + remove_dir_all(self.1).expect("should be able to remove db-directory"); + } + } +} + #[test] fn test_basic_metrics() { let cfg = DbConfig::builder() @@ -69,7 +94,8 @@ fn test_revisions() { key }; for i in 0..10 { - let db = Db::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = + PersistedDb::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); let mut dumped = VecDeque::new(); let mut hashes: VecDeque = VecDeque::new(); for _ in 0..10 { @@ -182,13 +208,18 @@ fn create_db_issue_proof() { println!("Error: {}", e); // TODO do type assertion on error } +} - fwdctl_delete_db("test_db_proof"); +impl + ?Sized> Deref for Db<'_, P> { + type Target = PersistedDb; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -// Removes the firewood database on disk -fn fwdctl_delete_db>(path: P) { - if let Err(e) = remove_dir_all(path) { - eprintln!("failed to delete testing dir: {e}"); +impl + ?Sized> DerefMut for Db<'_, P> { + fn deref_mut(&mut self) -> &mut PersistedDb { + &mut self.0 } } From bcecaecbec580b1be461ce26580e084549a5d1c6 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 15 Jun 2023 11:50:27 -0400 Subject: [PATCH 0193/1053] Iterator::position FTW? (#140) --- firewood/src/db.rs | 72 +++++++++++++++++------------------------- firewood/src/merkle.rs | 9 ++++-- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4d4581b9e863..f2478ba938e5 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -829,52 +829,38 @@ impl Db { let inner_lock = self.inner.read(); // Find the revision index with the given root hash. - let (nback, found) = { - let mut nback = 0; - let mut found = false; - - for (i, r) in revisions.root_hashes.iter().enumerate() { - if *r == root_hash { - nback = i; - found = true; - break; - } - } + let mut nback = revisions.root_hashes.iter().position(|r| r == &root_hash); + let rlen = revisions.root_hashes.len(); + + if nback.is_none() && rlen < revisions.max_revisions { + let ashes = inner_lock + .disk_requester + .collect_ash(revisions.max_revisions) + .ok() + .unwrap(); - if !found { - let rlen = revisions.root_hashes.len(); - if rlen < revisions.max_revisions { - let ashes = inner_lock - .disk_requester - .collect_ash(revisions.max_revisions) - .ok() - .unwrap(); - for (i, ash) in ashes.iter().skip(rlen).enumerate() { - // Replay the redo from the wal - let root_hash_store = StoreRevShared::from_ash( - Rc::new(ZeroStore::default()), - &ash.0[&ROOT_HASH_SPACE].redo, - ); - // No need the usage of `ShaleStore`, as this is just simple Hash value. - let r = root_hash_store - .get_view(0, TRIE_HASH_LEN as u64) - .unwrap() - .as_deref(); - let r = TrieHash(r[..TRIE_HASH_LEN].try_into().unwrap()); - if r == root_hash { - nback = i; - found = true; - break; - } - } - } - } - (nback, found) - }; + nback = ashes + .iter() + .skip(rlen) + .map(|ash| { + StoreRevShared::from_ash( + Rc::new(ZeroStore::default()), + &ash.0[&ROOT_HASH_SPACE].redo, + ) + }) + .map(|root_hash_store| { + root_hash_store + .get_view(0, TRIE_HASH_LEN as u64) + .unwrap() + .as_deref() + }) + .map(|data| TrieHash(data[..TRIE_HASH_LEN].try_into().unwrap())) + .position(|trie_hash| trie_hash == root_hash); + } - if !found { + let Some(nback) = nback else { return None; - } + }; let rlen = revisions.inner.len(); if rlen < nback { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 74b57a64a7bb..f40d551036e2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use std::error::Error; use std::fmt::{self, Debug}; use std::io::{Cursor, Read, Write}; +use std::iter; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::OnceLock; @@ -1902,13 +1903,15 @@ impl Merkle { key: K, root: ObjPtr, ) -> Result, MerkleError> { - let mut chunks = vec![0]; - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); - + // TODO: Make this NonNull> or something similar if root.is_null() { return Ok(None); } + let chunks: Vec = iter::once(0) + .chain(key.as_ref().iter().copied().flat_map(to_nibble_array)) + .collect(); + let mut u_ref = self.get_node(root)?; let mut nskip = 0; From 6347ed3ecd47954df845290f7a1133fe1bbe7d2d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 15 Jun 2023 10:10:18 -0700 Subject: [PATCH 0194/1053] New proposed API (#42) --- firewood/src/lib.rs | 2 + firewood/src/v2/api.rs | 181 +++++++++++++++++++++++++++++++++++++++++ firewood/src/v2/db.rs | 99 ++++++++++++++++++++++ firewood/src/v2/mod.rs | 2 + 4 files changed, 284 insertions(+) create mode 100644 firewood/src/v2/api.rs create mode 100644 firewood/src/v2/db.rs create mode 100644 firewood/src/v2/mod.rs diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 644923c0e939..dcd2a28ec4f1 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -206,4 +206,6 @@ pub mod storage; pub mod api; pub mod service; +pub mod v2; + extern crate firewood_shale as shale; diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs new file mode 100644 index 000000000000..2b1d36f4d0d1 --- /dev/null +++ b/firewood/src/v2/api.rs @@ -0,0 +1,181 @@ +use std::{collections::HashMap, fmt::Debug, sync::Weak}; + +use async_trait::async_trait; + +/// A KeyType is something that can be cast to a u8 reference, +/// and can be sent and shared across threads. References with +/// lifetimes are not allowed (hence 'static) +pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug + 'static {} + +/// A ValueType is the same as a [KeyType]. However, these could +/// be a different type from the [KeyType] on a given API call. +/// For example, you might insert {key: "key", value: vec!\[0u8\]} +/// This also means that the type of all the keys for a single +/// API call must be the same, as well as the type of all values +/// must be the same. +pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug + 'static {} + +/// The type and size of a single HashKey +/// These are 256-bit hashes that are used for a variety of reasons: +/// - They identify a version of the datastore at a specific point +/// in time +/// - They are used to provide integrity at different points in a +/// proof +pub type HashKey = [u8; 32]; + +/// A key/value pair operation. Only put (upsert) and delete are +/// supported +#[derive(Debug)] +pub enum BatchOp { + Put { key: K, value: V }, + Delete { key: K }, +} + +/// A list of operations to consist of a batch that +/// can be proposed +pub type Batch = Vec>; + +/// A convenience implementation to convert a vector of key/value +/// pairs into a batch of insert operations +pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch { + value + .into_iter() + .map(|(key, value)| BatchOp::Put { key, value }) + .collect() +} + +/// Errors returned through the API +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + /// A given hash key is not available in the database + HashNotFound { + provided: HashKey, + }, + /// Incorrect root hash for commit + IncorrectRootHash { + provided: HashKey, + current: HashKey, + }, + /// Key not found + KeyNotFound, + IO(std::io::Error), +} + +/// A range proof, consisting of a proof of the first key and the last key, +/// and a vector of all key/value pairs +#[derive(Debug)] +pub struct RangeProof { + pub first_key: Proof, + pub last_key: Proof, + pub middle: Vec<(K, V)>, +} + +/// A proof that a single key is present +#[derive(Debug)] +pub struct Proof(pub HashMap); + +/// The database interface, which includes a type for a static view of +/// the database (the DbView). The most common implementation of the DbView +/// is the api::DbView trait defined next. +#[async_trait] +pub trait Db { + type Historical: DbView; + type Proposal: DbView + Proposal; + + /// Get a reference to a specific view based on a hash + /// + /// # Arguments + /// + /// - `hash` - Identifies the revision for the view + async fn revision(&self, hash: HashKey) -> Result, Error>; + + /// Get the hash of the most recently committed version + async fn root_hash(&self) -> Result; + + /// Propose a change to the database via a batch + /// + /// This proposal assumes it is based off the most recently + /// committed transaction + /// + /// # Arguments + /// + /// * `data` - A batch consisting of [BatchOp::Put] and + /// [BatchOp::Delete] operations to apply + /// + async fn propose( + &mut self, + data: Batch, + ) -> Result; +} + +/// A view of the database at a specific time. These are wrapped with +/// a Weak reference when fetching via a call to [Db::revision], as these +/// can disappear because they became too old. +/// +/// You only need a DbView if you need to read from a snapshot at a given +/// root. Don't hold a strong reference to the DbView as it prevents older +/// views from being cleaned up. +/// +/// A [Proposal] requires implementing DbView +#[async_trait] +pub trait DbView { + /// Get the hash for the current DbView + async fn hash(&self) -> Result; + + /// Get the value of a specific key + async fn val(&self, key: K) -> Result; + + /// Obtain a proof for a single key + async fn single_key_proof(&self, key: K) -> Result, Error>; + + /// Obtain a range proof over a set of keys + /// + /// # Arguments + /// + /// * `first_key` - If None, start at the lowest key + /// * `last_key` - If None, continue to the end of the database + /// * `limit` - The maximum number of keys in the range proof + /// + async fn range_proof( + &self, + first_key: Option, + last_key: Option, + limit: usize, + ) -> Result, Error>; +} + +/// A proposal for a new revision of the database. +/// +/// A proposal may be committed, which consumes the +/// [Proposal] and return the generic type T, which +/// is the same thing you get if you call [Db::root_hash] +/// immediately after committing, and then call +/// [Db::revision] with the returned revision. +/// +/// A proposal type must also implement everything in a +/// [DbView], which means you can fetch values from it or +/// obtain proofs. +#[async_trait] +pub trait Proposal: DbView { + /// Commit this revision + /// + /// # Return value + /// + /// * A weak reference to a new historical view + async fn commit(self) -> Result, Error>; + /// Propose a new revision on top of an existing proposal + /// + /// # Arguments + /// + /// * `data` - the batch changes to apply + /// + /// # Return value + /// + /// A weak reference to a new proposal + /// + async fn propose( + &self, + data: Batch, + ) -> Result, Error>; +} diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs new file mode 100644 index 000000000000..c0bc5488339a --- /dev/null +++ b/firewood/src/v2/db.rs @@ -0,0 +1,99 @@ +use std::sync::Weak; + +use async_trait::async_trait; + +use crate::v2::api::{self, Batch, KeyType, ValueType}; + +struct Db; + +#[async_trait] +impl api::Db for Db { + type Historical = DbView; + type Proposal = Proposal; + + async fn revision(&self, _hash: api::HashKey) -> Result, api::Error> { + todo!() + } + + async fn root_hash(&self) -> Result { + todo!() + } + + async fn propose( + &mut self, + _data: Batch, + ) -> Result { + todo!() + } +} + +struct DbView; + +#[async_trait] +impl api::DbView for DbView { + async fn hash(&self) -> Result { + todo!() + } + + async fn val(&self, _key: K) -> Result { + todo!() + } + + async fn single_key_proof( + &self, + _key: K, + ) -> Result, api::Error> { + todo!() + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result, api::Error> { + todo!() + } +} + +struct Proposal; + +#[async_trait] +impl api::DbView for Proposal { + async fn hash(&self) -> Result { + todo!() + } + + async fn val(&self, _key: K) -> Result { + todo!() + } + + async fn single_key_proof( + &self, + _key: K, + ) -> Result, api::Error> { + todo!() + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result, api::Error> { + todo!() + } +} + +#[async_trait] +impl api::Proposal for Proposal { + async fn propose( + &self, + _data: Batch, + ) -> Result, api::Error> { + todo!() + } + async fn commit(self) -> Result, api::Error> { + todo!() + } +} diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs new file mode 100644 index 000000000000..037782038990 --- /dev/null +++ b/firewood/src/v2/mod.rs @@ -0,0 +1,2 @@ +pub mod api; +pub mod db; From 28bc80e2b79131a53b8fd98da8d1e7e04fee88b5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 16 Jun 2023 13:54:42 -0700 Subject: [PATCH 0195/1053] Basic implementation for proposals (#142) --- firewood/src/v2/api.rs | 7 +- firewood/src/v2/db.rs | 232 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 221 insertions(+), 18 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 2b1d36f4d0d1..198f7d2625d8 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; /// and can be sent and shared across threads. References with /// lifetimes are not allowed (hence 'static) pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug + 'static {} +impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} /// A ValueType is the same as a [KeyType]. However, these could /// be a different type from the [KeyType] on a given API call. @@ -14,6 +15,7 @@ pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug + 'static {} /// API call must be the same, as well as the type of all values /// must be the same. pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug + 'static {} +impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} /// The type and size of a single HashKey /// These are 256-bit hashes that are used for a variety of reasons: @@ -60,6 +62,7 @@ pub enum Error { /// Key not found KeyNotFound, IO(std::io::Error), + InvalidProposal, } /// A range proof, consisting of a proof of the first key and the last key, @@ -106,7 +109,7 @@ pub trait Db { async fn propose( &mut self, data: Batch, - ) -> Result; + ) -> Result, Error>; } /// A view of the database at a specific time. These are wrapped with @@ -124,7 +127,7 @@ pub trait DbView { async fn hash(&self) -> Result; /// Get the value of a specific key - async fn val(&self, key: K) -> Result; + async fn val(&self, key: K) -> Result, Error>; /// Obtain a proof for a single key async fn single_key_proof(&self, key: K) -> Result, Error>; diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index c0bc5488339a..d4dda01b9c64 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,10 +1,18 @@ -use std::sync::Weak; +use std::{ + borrow::Borrow, + collections::BTreeMap, + fmt::Debug, + sync::{Arc, Mutex, RwLock, Weak}, +}; use async_trait::async_trait; use crate::v2::api::{self, Batch, KeyType, ValueType}; -struct Db; +#[derive(Debug, Default)] +pub struct Db { + latest_cache: Mutex>>, +} #[async_trait] impl api::Db for Db { @@ -21,13 +29,34 @@ impl api::Db for Db { async fn propose( &mut self, - _data: Batch, - ) -> Result { - todo!() + data: Batch, + ) -> Result, api::Error> { + let mut dbview_latest_cache_guard = self.latest_cache.lock().unwrap(); + if dbview_latest_cache_guard.is_none() { + // TODO: actually get the latest dbview + *dbview_latest_cache_guard = Some(Arc::new(DbView { + proposals: RwLock::new(vec![]), + })); + }; + let mut proposal_guard = dbview_latest_cache_guard + .as_ref() + .unwrap() + .proposals + .write() + .unwrap(); + let proposal = Arc::new(Proposal::new( + ProposalBase::View(dbview_latest_cache_guard.clone().unwrap()), + data, + )); + proposal_guard.push(proposal.clone()); + Ok(Arc::downgrade(&proposal)) } } -struct DbView; +#[derive(Debug)] +pub struct DbView { + proposals: RwLock>>, +} #[async_trait] impl api::DbView for DbView { @@ -35,7 +64,7 @@ impl api::DbView for DbView { todo!() } - async fn val(&self, _key: K) -> Result { + async fn val(&self, _key: K) -> Result, api::Error> { todo!() } @@ -56,7 +85,51 @@ impl api::DbView for DbView { } } -struct Proposal; +#[derive(Clone, Debug)] +enum ProposalBase { + Proposal(Arc), + View(Arc), +} + +#[derive(Clone, Debug)] +enum KeyOp { + Put(V), + Delete, +} + +#[derive(Debug)] +pub struct Proposal { + base: ProposalBase, + delta: BTreeMap, KeyOp>>, + children: RwLock>>, +} +impl Clone for Proposal { + fn clone(&self) -> Self { + Self { + base: self.base.clone(), + delta: self.delta.clone(), + children: RwLock::new(vec![]), + } + } +} +impl Proposal { + fn new(base: ProposalBase, batch: Batch) -> Self { + let delta = batch + .iter() + .map(|op| match op { + api::BatchOp::Put { key, value } => { + (key.as_ref().to_vec(), KeyOp::Put(value.as_ref().to_vec())) + } + api::BatchOp::Delete { key } => (key.as_ref().to_vec(), KeyOp::Delete), + }) + .collect(); + Self { + base, + delta, + children: RwLock::new(vec![]), + } + } +} #[async_trait] impl api::DbView for Proposal { @@ -64,8 +137,20 @@ impl api::DbView for Proposal { todo!() } - async fn val(&self, _key: K) -> Result { - todo!() + async fn val(&self, key: K) -> Result, api::Error> { + // see if this key is in this proposal + match self.delta.get(key.as_ref()) { + Some(change) => match change { + // key in proposal, check for Put or Delete + KeyOp::Put(val) => Ok(val.clone()), + KeyOp::Delete => Err(api::Error::KeyNotFound), // key was deleted in this proposal + }, + None => match &self.base { + // key not in this proposal, so delegate to base + ProposalBase::Proposal(p) => p.val(key).await, + ProposalBase::View(view) => view.val(key).await, + }, + } } async fn single_key_proof( @@ -75,12 +160,12 @@ impl api::DbView for Proposal { todo!() } - async fn range_proof( + async fn range_proof( &self, - _first_key: Option, - _last_key: Option, + _first_key: Option, + _last_key: Option, _limit: usize, - ) -> Result, api::Error> { + ) -> Result, api::Error> { todo!() } } @@ -89,11 +174,126 @@ impl api::DbView for Proposal { impl api::Proposal for Proposal { async fn propose( &self, - _data: Batch, + data: Batch, ) -> Result, api::Error> { - todo!() + // find the Arc for this base proposal from the parent + let children_guard = match &self.base { + ProposalBase::Proposal(p) => p.children.read().unwrap(), + ProposalBase::View(v) => v.proposals.read().unwrap(), + }; + let arc = children_guard + .iter() + .find(|&c| std::ptr::eq(c.borrow() as *const _, self as *const _)); + + if arc.is_none() { + return Err(api::Error::InvalidProposal); + } + let proposal = Arc::new(Proposal::new( + ProposalBase::Proposal(arc.unwrap().clone()), + data, + )); + self.children.write().unwrap().push(proposal.clone()); + Ok(Arc::downgrade(&proposal)) } async fn commit(self) -> Result, api::Error> { todo!() } } + +impl std::ops::Add for Proposal { + type Output = Arc; + + fn add(self, rhs: Self) -> Self::Output { + let mut delta = self.delta.clone(); + delta.extend(rhs.delta); + let proposal = Proposal { + base: self.base, + delta, + children: RwLock::new(Vec::new()), + }; + Arc::new(proposal) + } +} +impl std::ops::Add for &Proposal { + type Output = Arc; + + fn add(self, rhs: Self) -> Self::Output { + let mut delta = self.delta.clone(); + delta.extend(rhs.delta.clone()); + let proposal = Proposal { + base: self.base.clone(), + delta, + children: RwLock::new(Vec::new()), + }; + Arc::new(proposal) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::v2::api::Db as _; + use crate::v2::api::DbView as _; + use crate::v2::api::Proposal; + use api::BatchOp; + #[tokio::test] + async fn test_basic_proposal() -> Result<(), crate::v2::api::Error> { + let mut db = Db::default(); + let batch = vec![ + BatchOp::Put { + key: b"k", + value: b"v", + }, + BatchOp::Delete { key: b"z" }, + ]; + let proposal = db.propose(batch).await?.upgrade().unwrap(); + assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); + assert!(matches!( + proposal.val(b"z").await.unwrap_err(), + crate::v2::api::Error::KeyNotFound + )); + Ok(()) + } + #[tokio::test] + async fn test_nested_proposal() -> Result<(), crate::v2::api::Error> { + let mut db = Db::default(); + + // create proposal1 which adds key "k" with value "v" and deletes "z" + let batch = vec![ + BatchOp::Put { + key: b"k", + value: b"v", + }, + BatchOp::Delete { key: b"z" }, + ]; + let proposal1 = db.propose(batch).await?.upgrade().unwrap(); + // create proposal2 which adds key "z" with value "undo" + let proposal2 = proposal1 + .propose(vec![BatchOp::Put { + key: b"z", + value: "undo", + }]) + .await? + .upgrade() + .unwrap(); + // both proposals still have (k,v) + assert_eq!(proposal1.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal2.val(b"k").await.unwrap(), b"v"); + // only proposal1 doesn't have z + assert!(matches!( + proposal1.val(b"z").await.unwrap_err(), + crate::v2::api::Error::KeyNotFound + )); + // proposal2 has z with value "undo" + assert_eq!(proposal2.val(b"z").await.unwrap(), b"undo"); + + // create a proposal3 by adding the two proposals together, keeping the originals + let proposal3: Arc = proposal1.as_ref() + proposal2.as_ref(); + assert_eq!(proposal3.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal3.val(b"z").await.unwrap(), b"undo"); + + // now consume proposal1 and proposal2 + + Ok(()) + } +} From f3de2523ff7bb07da40631da1cbfe17bb7990ad2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 17 Jun 2023 07:31:09 -0700 Subject: [PATCH 0196/1053] Add some blank lines (Richardification) (#143) --- firewood/src/v2/api.rs | 13 +++++++++---- firewood/src/v2/db.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 198f7d2625d8..86c431784117 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,22 +2,24 @@ use std::{collections::HashMap, fmt::Debug, sync::Weak}; use async_trait::async_trait; -/// A KeyType is something that can be cast to a u8 reference, +/// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with /// lifetimes are not allowed (hence 'static) pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug + 'static {} + impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} -/// A ValueType is the same as a [KeyType]. However, these could -/// be a different type from the [KeyType] on a given API call. +/// A `ValueType` is the same as a `KeyType`. However, these could +/// be a different type from the `KeyType` on a given API call. /// For example, you might insert {key: "key", value: vec!\[0u8\]} /// This also means that the type of all the keys for a single /// API call must be the same, as well as the type of all values /// must be the same. pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug + 'static {} + impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} -/// The type and size of a single HashKey +/// The type and size of a single hash key /// These are 256-bit hashes that are used for a variety of reasons: /// - They identify a version of the datastore at a specific point /// in time @@ -39,6 +41,7 @@ pub type Batch = Vec>; /// A convenience implementation to convert a vector of key/value /// pairs into a batch of insert operations +#[must_use] pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch { value .into_iter() @@ -84,6 +87,7 @@ pub struct Proof(pub HashMap); #[async_trait] pub trait Db { type Historical: DbView; + type Proposal: DbView + Proposal; /// Get a reference to a specific view based on a hash @@ -167,6 +171,7 @@ pub trait Proposal: DbView { /// /// * A weak reference to a new historical view async fn commit(self) -> Result, Error>; + /// Propose a new revision on top of an existing proposal /// /// # Arguments diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index d4dda01b9c64..122274e4d334 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -17,6 +17,7 @@ pub struct Db { #[async_trait] impl api::Db for Db { type Historical = DbView; + type Proposal = Proposal; async fn revision(&self, _hash: api::HashKey) -> Result, api::Error> { @@ -32,23 +33,28 @@ impl api::Db for Db { data: Batch, ) -> Result, api::Error> { let mut dbview_latest_cache_guard = self.latest_cache.lock().unwrap(); + if dbview_latest_cache_guard.is_none() { // TODO: actually get the latest dbview *dbview_latest_cache_guard = Some(Arc::new(DbView { proposals: RwLock::new(vec![]), })); }; + let mut proposal_guard = dbview_latest_cache_guard .as_ref() .unwrap() .proposals .write() .unwrap(); + let proposal = Arc::new(Proposal::new( ProposalBase::View(dbview_latest_cache_guard.clone().unwrap()), data, )); + proposal_guard.push(proposal.clone()); + Ok(Arc::downgrade(&proposal)) } } @@ -103,6 +109,7 @@ pub struct Proposal { delta: BTreeMap, KeyOp>>, children: RwLock>>, } + impl Clone for Proposal { fn clone(&self) -> Self { Self { @@ -112,10 +119,11 @@ impl Clone for Proposal { } } } + impl Proposal { fn new(base: ProposalBase, batch: Batch) -> Self { let delta = batch - .iter() + .into_iter() .map(|op| match op { api::BatchOp::Put { key, value } => { (key.as_ref().to_vec(), KeyOp::Put(value.as_ref().to_vec())) @@ -123,6 +131,7 @@ impl Proposal { api::BatchOp::Delete { key } => (key.as_ref().to_vec(), KeyOp::Delete), }) .collect(); + Self { base, delta, @@ -181,6 +190,7 @@ impl api::Proposal for Proposal { ProposalBase::Proposal(p) => p.children.read().unwrap(), ProposalBase::View(v) => v.proposals.read().unwrap(), }; + let arc = children_guard .iter() .find(|&c| std::ptr::eq(c.borrow() as *const _, self as *const _)); @@ -188,13 +198,17 @@ impl api::Proposal for Proposal { if arc.is_none() { return Err(api::Error::InvalidProposal); } + let proposal = Arc::new(Proposal::new( ProposalBase::Proposal(arc.unwrap().clone()), data, )); + self.children.write().unwrap().push(proposal.clone()); + Ok(Arc::downgrade(&proposal)) } + async fn commit(self) -> Result, api::Error> { todo!() } @@ -205,26 +219,33 @@ impl std::ops::Add for Proposal { fn add(self, rhs: Self) -> Self::Output { let mut delta = self.delta.clone(); + delta.extend(rhs.delta); + let proposal = Proposal { base: self.base, delta, children: RwLock::new(Vec::new()), }; + Arc::new(proposal) } } + impl std::ops::Add for &Proposal { type Output = Arc; fn add(self, rhs: Self) -> Self::Output { let mut delta = self.delta.clone(); + delta.extend(rhs.delta.clone()); + let proposal = Proposal { base: self.base.clone(), delta, children: RwLock::new(Vec::new()), }; + Arc::new(proposal) } } @@ -236,9 +257,11 @@ mod test { use crate::v2::api::DbView as _; use crate::v2::api::Proposal; use api::BatchOp; + #[tokio::test] async fn test_basic_proposal() -> Result<(), crate::v2::api::Error> { let mut db = Db::default(); + let batch = vec![ BatchOp::Put { key: b"k", @@ -246,14 +269,19 @@ mod test { }, BatchOp::Delete { key: b"z" }, ]; + let proposal = db.propose(batch).await?.upgrade().unwrap(); + assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); + assert!(matches!( proposal.val(b"z").await.unwrap_err(), crate::v2::api::Error::KeyNotFound )); + Ok(()) } + #[tokio::test] async fn test_nested_proposal() -> Result<(), crate::v2::api::Error> { let mut db = Db::default(); @@ -266,7 +294,9 @@ mod test { }, BatchOp::Delete { key: b"z" }, ]; + let proposal1 = db.propose(batch).await?.upgrade().unwrap(); + // create proposal2 which adds key "z" with value "undo" let proposal2 = proposal1 .propose(vec![BatchOp::Put { From 8fe79e590d0d34f20b2ec86aa5891c113daa9502 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 20 Jun 2023 19:58:48 -0700 Subject: [PATCH 0197/1053] Remove dyn for MemStoreR (#144) --- firewood/src/db.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f2478ba938e5..65802c30d85d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -179,7 +179,7 @@ impl SubUniverse { } impl SubUniverse { - fn to_mem_store_r(&self) -> SubUniverse> { + fn to_mem_store_r(&self) -> SubUniverse> { SubUniverse { meta: self.meta.inner().clone(), payload: self.payload.inner().clone(), @@ -187,7 +187,7 @@ impl SubUniverse { } } -impl SubUniverse> { +impl SubUniverse> { fn rewind( &self, meta_writes: &[SpaceWrite], @@ -201,7 +201,7 @@ impl SubUniverse> { } impl SubUniverse> { - fn to_mem_store_r(&self) -> SubUniverse> { + fn to_mem_store_r(&self) -> SubUniverse> { SubUniverse { meta: self.meta.clone(), payload: self.payload.clone(), @@ -282,7 +282,7 @@ struct Universe { } impl Universe { - fn to_mem_store_r(&self) -> Universe> { + fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), blob: self.blob.to_mem_store_r(), @@ -291,7 +291,7 @@ impl Universe { } impl Universe> { - fn to_mem_store_r(&self) -> Universe> { + fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), blob: self.blob.to_mem_store_r(), @@ -299,7 +299,7 @@ impl Universe> { } } -impl Universe> { +impl Universe> { fn rewind( &self, merkle_meta_writes: &[SpaceWrite], @@ -872,15 +872,20 @@ impl Db { } let u = match revisions.inner.back() { - Some(u) => u.to_mem_store_r(), - None => inner_lock.data_cache.to_mem_store_r(), + Some(u) => u.to_mem_store_r().rewind( + &ash.0[&MERKLE_META_SPACE].undo, + &ash.0[&MERKLE_PAYLOAD_SPACE].undo, + &ash.0[&BLOB_META_SPACE].undo, + &ash.0[&BLOB_PAYLOAD_SPACE].undo, + ), + None => inner_lock.data_cache.to_mem_store_r().rewind( + &ash.0[&MERKLE_META_SPACE].undo, + &ash.0[&MERKLE_PAYLOAD_SPACE].undo, + &ash.0[&BLOB_META_SPACE].undo, + &ash.0[&BLOB_PAYLOAD_SPACE].undo, + ), }; - revisions.inner.push_back(u.rewind( - &ash.0[&MERKLE_META_SPACE].undo, - &ash.0[&MERKLE_PAYLOAD_SPACE].undo, - &ash.0[&BLOB_META_SPACE].undo, - &ash.0[&BLOB_PAYLOAD_SPACE].undo, - )); + revisions.inner.push_back(u); } } From 0b9b539972b30a8dbac1496d7c4a138141f9c748 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 21 Jun 2023 19:22:02 -0400 Subject: [PATCH 0198/1053] Remove re-implementation of cmp on slice (#145) --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 80 ++---- firewood/src/proof.rs | 514 ++++++++++++++++++++------------------- firewood/tests/merkle.rs | 13 +- 4 files changed, 295 insertions(+), 313 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index cadacc1c4582..338a8e27deec 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -48,6 +48,7 @@ serial_test = "2.0.0" clap = { version = "4.3.1", features = ['derive'] } bencher = "0.1.5" tempdir = "0.3.7" +test-case = "3.1.0" [features] # proof API diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index f40d551036e2..1aa1029876b9 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,19 +2,20 @@ // See the file LICENSE.md for licensing terms. use crate::proof::Proof; - use enum_as_inner::EnumAsInner; use sha3::Digest; use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; - -use std::cmp; -use std::collections::HashMap; -use std::error::Error; -use std::fmt::{self, Debug}; -use std::io::{Cursor, Read, Write}; -use std::iter; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::OnceLock; +use std::{ + collections::HashMap, + error::Error, + fmt::{self, Debug}, + io::{Cursor, Read, Write}, + iter, + sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock, + }, +}; pub const NBRANCH: usize = 16; pub const TRIE_HASH_LEN: usize = 32; @@ -2054,63 +2055,18 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } -// compare two slices by comparing the bytes. A longer slice is greater -// than a shorter slice (assuming the leading bytes are equal) -pub fn compare(a: &[u8], b: &[u8]) -> cmp::Ordering { - for (ai, bi) in a.iter().zip(b) { - match ai.cmp(bi) { - cmp::Ordering::Equal => continue, - ord => return ord, - } - } - - // If every single element was equal, compare length - a.len().cmp(&b.len()) -} - #[cfg(test)] mod test { - use shale::cached::PlainMem; - use super::*; + use shale::cached::PlainMem; use std::ops::Deref; + use test_case::test_case; - #[test] - fn test_to_nibbles() { - for (bytes, nibbles) in [ - (vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), - (vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf]), - ] { - let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); - assert_eq!(n, nibbles); - } - } - - #[test] - fn test_cmp() { - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x56]), - (vec![0xc0, 0xff], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_eq()); - } - - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x34, 0x56], vec![0x12, 0x34, 0x58]), - (vec![0xc0, 0xee], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_lt()); - } - - for (bytes_a, bytes_b) in [ - (vec![0x12, 0x35, 0x56], vec![0x12, 0x34, 0x58]), - (vec![0xc0, 0xff, 0x33], vec![0xc0, 0xff]), - ] { - let n = compare(&bytes_a, &bytes_b); - assert!(n.is_gt()); - } + #[test_case(vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] + #[test_case(vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf])] + fn test_to_nibbles(bytes: Vec, nibbles: Vec) { + let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); + assert_eq!(n, nibbles); } const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 218351c27154..8b7af6899760 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -142,28 +142,23 @@ impl Proof { let mut cur_hash = root_hash; let proofs_map = &self.0; let mut index = 0; + loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; let (sub_proof, size) = self.locate_subproof(cur_key, cur_proof)?; index += size; + match sub_proof { + // Return when reaching the end of the key. + Some(p) if index == chunks.len() => return Ok(Some(p.rlp)), + // The trie doesn't contain the key. + Some(SubProof { hash: None, .. }) | None => return Ok(None), Some(p) => { - // Return when reaching the end of the key. - if index == chunks.len() { - return Ok(Some(p.rlp)); - } - - // The trie doesn't contain the key. - if p.hash.is_none() { - return Ok(None); - } cur_hash = p.hash.unwrap(); cur_key = &chunks[index..]; } - // The trie doesn't contain the key. - None => return Ok(None), } } } @@ -174,8 +169,9 @@ impl Proof { buf: &[u8], ) -> Result<(Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); - let size = rlp.item_count().unwrap(); - match size { + + // TODO: handle error in match statement instead of unwrapping + match rlp.item_count().unwrap() { EXT_NODE_SIZE => { let cur_key_path: Vec<_> = rlp .at(0) @@ -189,6 +185,7 @@ impl Proof { let cur_key = cur_key_path.into_inner(); let rlp = rlp.at(1).unwrap(); + let data = if rlp.is_data() { rlp.as_val::>().unwrap() } else { @@ -199,6 +196,7 @@ impl Proof { if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { return Ok((None, 0)); } + if term { Ok(( Some(SubProof { @@ -209,43 +207,47 @@ impl Proof { )) } else { self.generate_subproof(data) - .map(|subproof| (subproof, cur_key.len())) + .map(|subproof| (Some(subproof), cur_key.len())) } } + + BRANCH_NODE_SIZE if key.is_empty() => Err(ProofError::NoSuchNode), + BRANCH_NODE_SIZE => { - if key.is_empty() { - return Err(ProofError::NoSuchNode); - } let index = key[0]; let rlp = rlp.at(index as usize).unwrap(); + let data = if rlp.is_data() { rlp.as_val::>().unwrap() } else { rlp.as_raw().to_vec() }; - self.generate_subproof(data).map(|subproof| (subproof, 1)) + + self.generate_subproof(data) + .map(|subproof| (Some(subproof), 1)) } + _ => Err(ProofError::DecodeError), } } - fn generate_subproof(&self, data: Vec) -> Result, ProofError> { - let data_len = data.len(); - match data_len { - 32 => { - let sub_hash: &[u8] = &data; - let sub_hash = sub_hash.try_into().unwrap(); - Ok(Some(SubProof { + fn generate_subproof(&self, data: Vec) -> Result { + match data.len() { + 0..=31 => { + let sub_hash = sha3::Keccak256::digest(&data).into(); + Ok(SubProof { rlp: data, hash: Some(sub_hash), - })) + }) } - 0..=31 => { - let sub_hash = sha3::Keccak256::digest(&data).into(); - Ok(Some(SubProof { + 32 => { + let sub_hash: &[u8] = &data; + let sub_hash = sub_hash.try_into().unwrap(); + + Ok(SubProof { rlp: data, hash: Some(sub_hash), - })) + }) } _ => Err(ProofError::DecodeError), } @@ -268,69 +270,65 @@ impl Proof { } // Ensure the received batch is monotonic increasing and contains no deletions - for n in 0..(keys.len() as i32 - 1) { - if compare(keys[n as usize].as_ref(), keys[(n + 1) as usize].as_ref()).is_ge() { - return Err(ProofError::NonMonotonicIncreaseRange); - } + if !keys.windows(2).all(|w| w[0].as_ref() < w[1].as_ref()) { + return Err(ProofError::NonMonotonicIncreaseRange); } - for v in vals.iter() { - if v.as_ref().is_empty() { - return Err(ProofError::RangeHasDeletion); - } + if !vals.iter().all(|v| !v.as_ref().is_empty()) { + return Err(ProofError::RangeHasDeletion); } // Use in-memory merkle let mut merkle_setup = new_merkle(0x10000, 0x10000); + // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if self.0.is_empty() { for (index, k) in keys.iter().enumerate() { merkle_setup.insert(k, vals[index].as_ref().to_vec())?; } + let merkle_root = &*merkle_setup.root_hash()?; - if merkle_root != &root_hash { - return Err(ProofError::InvalidProof); - } - return Ok(false); + + return if merkle_root == &root_hash { + Ok(false) + } else { + Err(ProofError::InvalidProof) + }; } + // Special case when there is a provided edge proof but zero key/value pairs, // ensure there are no more accounts / slots in the trie. if keys.is_empty() { - if (self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?) - .is_some() - { - // No more entries should be available. - return Err(ProofError::InvalidData); - } - return Ok(false); + let proof_to_path = + self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; + return match proof_to_path { + Some(_) => Err(ProofError::InvalidData), + None => Ok(false), + }; } // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. - if keys.len() == 1 && compare(first_key.as_ref(), last_key.as_ref()).is_eq() { + if keys.len() == 1 && first_key.as_ref() == last_key.as_ref() { let data = self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, false)?; - if compare(first_key.as_ref(), keys[0].as_ref()).is_ne() { + return if first_key.as_ref() != keys[0].as_ref() { // correct proof but invalid key - return Err(ProofError::InvalidEdgeKeys); - } - - return data.map_or_else( - || Err(ProofError::InvalidData), - |d| { - if compare(&d, vals[0].as_ref()).is_ne() { - Err(ProofError::InvalidData) - } else { - Ok(true) - } - }, - ); + Err(ProofError::InvalidEdgeKeys) + } else { + match data { + Some(val) if val == vals[0].as_ref() => Ok(true), + None => Ok(false), + _ => Err(ProofError::InvalidData), + } + }; } + // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. - if compare(first_key.as_ref(), last_key.as_ref()).is_ge() { + if first_key.as_ref() >= last_key.as_ref() { return Err(ProofError::InvalidEdgeKeys); } @@ -353,22 +351,24 @@ impl Proof { // be re-filled(or re-constructed) by the given leaves range. let fork_at_root = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; + // If the fork point is the root, the trie should be empty, start with a new one. if fork_at_root { merkle_setup = new_merkle(0x100000, 0x100000); } - for (i, _) in keys.iter().enumerate() { - merkle_setup.insert(keys[i].as_ref(), vals[i].as_ref().to_vec())?; + for (key, val) in keys.iter().zip(vals.iter()) { + merkle_setup.insert(key.as_ref(), val.as_ref().to_vec())?; } // Calculate the hash let merkle_root = &*merkle_setup.root_hash()?; - if merkle_root != &root_hash { - return Err(ProofError::InvalidProof); - } - Ok(true) + if merkle_root == &root_hash { + Ok(true) + } else { + Err(ProofError::InvalidProof) + } } /// proofToPath converts a merkle proof to trie node path. The main purpose of @@ -395,7 +395,7 @@ impl Proof { let mut cur_hash = root_hash; let proofs_map = &self.0; let mut key_index = 0; - let mut branch_index: u8 = 0; + let mut branch_index = 0; loop { let cur_proof = proofs_map @@ -407,26 +407,26 @@ impl Proof { // Link the child to the parent based on the node type. match &u_ref.inner() { - NodeType::Branch(n) => { - match n.chd()[branch_index as usize] { - // If the child already resolved, then use the existing node. - Some(node) => { - chd_ptr = node; - } - None => { - // insert the leaf to the empty slot - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index as usize] = Some(chd_ptr); - }) - .unwrap(); - } - }; - } + NodeType::Branch(n) => match n.chd()[branch_index] { + // If the child already resolved, then use the existing node. + Some(node) => { + chd_ptr = node; + } + None => { + // insert the leaf to the empty slot + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[branch_index] = Some(chd_ptr); + }) + .unwrap(); + } + }, + NodeType::Extension(_) => { // If the child already resolved, then use the existing node. let node = u_ref.inner().as_extension().unwrap().chd(); + if node.is_null() { u_ref .write(|u| { @@ -438,6 +438,7 @@ impl Proof { chd_ptr = node; } } + // We should not hit a leaf node as a parent. _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; @@ -445,56 +446,66 @@ impl Proof { u_ref = merkle .get_node(chd_ptr) .map_err(|_| ProofError::DecodeError)?; + // If the new parent is a branch node, record the index to correctly link the next child to it. if u_ref.inner().as_branch().is_some() { - branch_index = chunks[key_index]; + branch_index = chunks[key_index] as usize; } key_index += size; + match sub_proof { + // The trie doesn't contain the key. It's possible + // the proof is a non-existing proof, but at least + // we can prove all resolved nodes are correct, it's + // enough for us to prove range. + None => { + return allow_non_existent_node + .then_some(None) + .ok_or(ProofError::NodeNotInTrie); + } Some(p) => { // Return when reaching the end of the key. if key_index == chunks.len() { cur_key = &chunks[key_index..]; let mut data = None; + // Decode the last subproof to get the value. - if p.hash.is_some() { - let proof = proofs_map - .get(&p.hash.unwrap()) - .ok_or(ProofError::ProofNodeMissing)?; + if let Some(p_hash) = p.hash.as_ref() { + let proof = + proofs_map.get(p_hash).ok_or(ProofError::ProofNodeMissing)?; chd_ptr = self.decode_node(merkle, cur_key, proof, true)?.0; // Link the child to the parent based on the node type. match &u_ref.inner() { - NodeType::Branch(n) => { - match n.chd()[branch_index as usize] { - // If the child already resolved, then use the existing node. - Some(_) => {} - None => { - // insert the leaf to the empty slot - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index as usize] = - Some(chd_ptr); - }) - .unwrap(); - } - }; + NodeType::Branch(n) if n.chd()[branch_index].is_none() => { + // insert the leaf to the empty slot + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[branch_index] = Some(chd_ptr); + }) + .unwrap(); } - NodeType::Extension(_) => { - // If the child already resolved, then use the existing node. - let node = u_ref.inner().as_extension().unwrap().chd(); - if node.is_null() { - u_ref - .write(|u| { - let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = chd_ptr; - }) - .unwrap(); - } + + // If the child already resolved, then use the existing node. + NodeType::Branch(_) => {} + + NodeType::Extension(_) + if u_ref.inner().as_extension().unwrap().chd().is_null() => + { + u_ref + .write(|u| { + let uu = u.inner_mut().as_extension_mut().unwrap(); + *uu.chd_mut() = chd_ptr; + }) + .unwrap(); } + + // If the child already resolved, then use the existing node. + NodeType::Extension(_) => {} + // We should not hit a leaf node as a parent. _ => { return Err(ProofError::InvalidNode( @@ -503,10 +514,13 @@ impl Proof { } }; } + drop(u_ref); + let c_ref = merkle .get_node(chd_ptr) .map_err(|_| ProofError::DecodeError)?; + match &c_ref.inner() { NodeType::Branch(n) => { if let Some(v) = n.value() { @@ -522,28 +536,22 @@ impl Proof { } _ => (), } + return Ok(data); } // The trie doesn't contain the key. if p.hash.is_none() { - if allow_non_existent_node { - return Ok(None); - } - return Err(ProofError::NodeNotInTrie); - } + return if allow_non_existent_node { + Ok(None) + } else { + Err(ProofError::NodeNotInTrie) + }; + }; + cur_hash = p.hash.unwrap(); cur_key = &chunks[key_index..]; } - // The trie doesn't contain the key. It's possible - // the proof is a non-existing proof, but at least - // we can prove all resolved nodes are correct, it's - // enough for us to prove range. - None => { - return allow_non_existent_node - .then_some(None) - .ok_or(ProofError::NodeNotInTrie); - } } } } @@ -597,7 +605,7 @@ impl Proof { hash: None, }) } else { - self.generate_subproof(data.clone())? + self.generate_subproof(data.clone()).map(Some)? }; let cur_key_len = cur_key.len(); @@ -663,7 +671,7 @@ impl Proof { let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; let subproof = self.generate_subproof(data)?; - Ok((branch_ptr, subproof, 1)) + Ok((branch_ptr, Some(subproof), 1)) } // RLP length can only be the two cases above. @@ -748,56 +756,63 @@ fn unset_internal>( // stop here and the forkpoint is the fullnode. let left_node = n.chd()[left_chunks[index] as usize]; let right_node = n.chd()[right_chunks[index] as usize]; - if left_node.is_none() - || right_node.is_none() - || left_node.unwrap() != right_node.unwrap() - { - break; - } + + match (left_node.as_ref(), right_node.as_ref()) { + (None, _) | (_, None) => break, + (left, right) if left != right => break, + _ => (), + }; + parent = u_ref.as_ptr(); u_ref = merkle .get_node(left_node.unwrap()) .map_err(|_| ProofError::DecodeError)?; index += 1; } + NodeType::Extension(n) => { // If either the key of left proof or right proof doesn't match with // shortnode, stop here and the forkpoint is the shortnode. let cur_key = n.path().clone().into_inner(); - if left_chunks.len() - index < cur_key.len() { - fork_left = compare(&left_chunks[index..], &cur_key) + + fork_left = if left_chunks.len() - index < cur_key.len() { + left_chunks[index..].cmp(&cur_key) } else { - fork_left = compare(&left_chunks[index..index + cur_key.len()], &cur_key) - } + left_chunks[index..index + cur_key.len()].cmp(&cur_key) + }; - if right_chunks.len() - index < cur_key.len() { - fork_right = compare(&right_chunks[index..], &cur_key) + fork_right = if right_chunks.len() - index < cur_key.len() { + right_chunks[index..].cmp(&cur_key) } else { - fork_right = compare(&right_chunks[index..index + cur_key.len()], &cur_key) - } + right_chunks[index..index + cur_key.len()].cmp(&cur_key) + }; if !fork_left.is_eq() || !fork_right.is_eq() { break; } + parent = u_ref.as_ptr(); u_ref = merkle .get_node(n.chd()) .map_err(|_| ProofError::DecodeError)?; index += cur_key.len(); } + NodeType::Leaf(n) => { let cur_key = n.path(); - if left_chunks.len() - index < cur_key.len() { - fork_left = compare(&left_chunks[index..], cur_key) + + fork_left = if left_chunks.len() - index < cur_key.len() { + left_chunks[index..].cmp(cur_key) } else { - fork_left = compare(&left_chunks[index..index + cur_key.len()], cur_key) - } + left_chunks[index..index + cur_key.len()].cmp(cur_key) + }; - if right_chunks.len() - index < cur_key.len() { - fork_right = compare(&right_chunks[index..], cur_key) + fork_right = if right_chunks.len() - index < cur_key.len() { + right_chunks[index..].cmp(cur_key) } else { - fork_right = compare(&right_chunks[index..index + cur_key.len()], cur_key) - } + right_chunks[index..index + cur_key.len()].cmp(cur_key) + }; + break; } } @@ -818,12 +833,14 @@ fn unset_internal>( }) .unwrap(); } + let p = u_ref.as_ptr(); drop(u_ref); unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; Ok(false) } + NodeType::Extension(n) => { // There can have these five scenarios: // - both proofs are less than the trie path => no valid range @@ -833,17 +850,21 @@ fn unset_internal>( // - right proof points to the shortnode, but left proof is less let node = n.chd(); let cur_key = n.path().clone().into_inner(); + if fork_left.is_lt() && fork_right.is_lt() { return Err(ProofError::EmptyRange); } + if fork_left.is_gt() && fork_right.is_gt() { return Err(ProofError::EmptyRange); } + if fork_left.is_ne() && fork_right.is_ne() { // The fork point is root node, unset the entire trie if parent.is_null() { return Ok(true); } + let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; @@ -854,10 +875,13 @@ fn unset_internal>( pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; }) .unwrap(); + return Ok(false); } + let p = u_ref.as_ptr(); drop(u_ref); + // Only one proof points to non-existent key. if fork_right.is_ne() { unset_node_ref( @@ -868,8 +892,10 @@ fn unset_internal>( cur_key.len(), false, )?; + return Ok(false); } + if fork_left.is_ne() { unset_node_ref( merkle, @@ -879,20 +905,26 @@ fn unset_internal>( cur_key.len(), true, )?; + return Ok(false); } + Ok(false) } + NodeType::Leaf(_) => { if fork_left.is_lt() && fork_right.is_lt() { return Err(ProofError::EmptyRange); } + if fork_left.is_gt() && fork_right.is_gt() { return Err(ProofError::EmptyRange); } + let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; + if fork_left.is_ne() && fork_right.is_ne() { p_ref .write(|p| match p.inner_mut() { @@ -924,6 +956,7 @@ fn unset_internal>( }) .unwrap(); } + Ok(false) } } @@ -965,89 +998,79 @@ fn unset_node_ref>( match &u_ref.inner() { NodeType::Branch(n) => { - let node = n.chd()[chunks[index] as usize]; - if remove_left { - for i in 0..chunks[index] { - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[i as usize] = None; - uu.chd_eth_rlp_mut()[i as usize] = None; - }) - .unwrap(); - } + let child_index = chunks[index] as usize; + + let node = n.chd()[child_index]; + + let iter = if remove_left { + 0..child_index } else { - for i in chunks[index] + 1..16 { - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[i as usize] = None; - uu.chd_eth_rlp_mut()[i as usize] = None; - }) - .unwrap(); - } + child_index + 1..16 + }; + + for i in iter { + u_ref + .write(|u| { + let uu = u.inner_mut().as_branch_mut().unwrap(); + uu.chd_mut()[i] = None; + uu.chd_eth_rlp_mut()[i] = None; + }) + .unwrap(); } drop(u_ref); - return unset_node_ref(merkle, p, node, key, index + 1, remove_left); + + unset_node_ref(merkle, p, node, key, index + 1, remove_left) + } + + NodeType::Extension(n) if chunks[index..].starts_with(n.path()) => { + let node = Some(n.chd()); + unset_node_ref(merkle, p, node, key, index + n.path().len(), remove_left) } + NodeType::Extension(n) => { let cur_key = n.path(); - let node = n.chd(); - if !(chunks[index..]).starts_with(cur_key) { - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - // Find the fork point, it's an non-existent branch. - if remove_left { - if compare(cur_key, &chunks[index..]).is_lt() { - // The key of fork shortnode is less than the path - // (it belongs to the range), unset the entire - // branch. The parent must be a fullnode. - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; - }) - .unwrap(); - } - //else { - // The key of fork shortnode is greater than the - // path(it doesn't belong to the range), keep - // it with the cached hash available. - //} - } else if compare(cur_key, &chunks[index..]).is_gt() { - // The key of fork shortnode is greater than the - // path(it belongs to the range), unset the entrie - // branch. The parent must be a fullnode. Otherwise the - // key is not part of the range and should remain in the - // cached hash. - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; - }) - .unwrap(); - } - return Ok(()); - } + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; - return unset_node_ref( - merkle, - p, - Some(node), - key, - index + cur_key.len(), - remove_left, + // Find the fork point, it's a non-existent branch. + // + // for (true, Ordering::Less) + // The key of fork shortnode is less than the path + // (it belongs to the range), unset the entire + // branch. The parent must be a fullnode. + // + // for (false, Ordering::Greater) + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. Otherwise the + // key is not part of the range and should remain in the + // cached hash. + let should_unset_entire_branch = matches!( + (remove_left, cur_key.cmp(&chunks[index..])), + (true, Ordering::Less) | (false, Ordering::Greater) ); + + if should_unset_entire_branch { + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + pp.chd_mut()[chunks[index - 1] as usize] = None; + pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + }) + .unwrap(); + } + + Ok(()) } + NodeType::Leaf(n) => { let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; let cur_key = n.path(); + // Similar to branch node, we need to compare the path to see if the node // needs to be unset. if !(chunks[index..]).starts_with(cur_key) { @@ -1056,30 +1079,33 @@ fn unset_node_ref>( p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + let index = chunks[index - 1] as usize; + pp.chd_mut()[index] = None; + pp.chd_eth_rlp_mut()[index] = None; }) .expect("node write failure"); } _ => (), } - return Ok(()); + } else { + p_ref + .write(|p| match p.inner_mut() { + NodeType::Extension(n) => { + *n.chd_mut() = ObjPtr::null(); + *n.chd_eth_rlp_mut() = None; + } + NodeType::Branch(n) => { + let index = chunks[index - 1] as usize; + + n.chd_mut()[index] = None; + n.chd_eth_rlp_mut()[index] = None; + } + _ => {} + }) + .expect("node write failure"); } - p_ref - .write(|p| match p.inner_mut() { - NodeType::Extension(n) => { - *n.chd_mut() = ObjPtr::null(); - *n.chd_eth_rlp_mut() = None; - } - NodeType::Branch(n) => { - n.chd_mut()[chunks[index - 1] as usize] = None; - n.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; - } - _ => {} - }) - .expect("node write failure"); + + Ok(()) } } - - Ok(()) } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index ea243416b460..e874b8a78a98 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,5 +1,4 @@ use firewood::{ - merkle::compare, merkle_util::*, proof::{Proof, ProofError}, }; @@ -221,7 +220,7 @@ fn test_one_element_proof() -> Result<(), DataStoreError> { let val = merkle.verify_proof(key, &proof)?; assert!(val.is_some()); - assert!(compare(val.unwrap().as_ref(), "v".as_ref()).is_eq()); + assert_eq!(&val.unwrap(), b"v"); Ok(()) } @@ -239,7 +238,7 @@ fn test_proof() -> Result<(), DataStoreError> { assert!(!proof.0.is_empty()); let val = merkle.verify_proof(key, &proof)?; assert!(val.is_some()); - assert!(compare(val.unwrap().as_ref(), vals[i].as_ref()).is_eq()); + assert_eq!(val.unwrap(), vals[i]); } Ok(()) @@ -471,20 +470,20 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { // Short circuit if the decreased key is same with the previous key let first = decrease_key(items[start].0); - if start != 0 && compare(first.as_ref(), items[start - 1].0.as_ref()).is_eq() { + if start != 0 && first.as_ref() == items[start - 1].0.as_ref() { continue; } // Short circuit if the decreased key is underflow - if compare(first.as_ref(), items[start].0.as_ref()).is_gt() { + if first.as_ref() > items[start].0.as_ref() { continue; } // Short circuit if the increased key is same with the next key let last = increase_key(items[end - 1].0); - if end != items.len() && compare(last.as_ref(), items[end].0.as_ref()).is_eq() { + if end != items.len() && last.as_ref() == items[end].0.as_ref() { continue; } // Short circuit if the increased key is overflow - if compare(last.as_ref(), items[end - 1].0.as_ref()).is_lt() { + if last.as_ref() < items[end - 1].0.as_ref() { continue; } From 4887adc78d86d01264dd746c3713854be4c179dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:47:54 -0400 Subject: [PATCH 0199/1053] build(deps): update indexmap requirement from 1.9.1 to 2.0.0 (#147) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood-growth-ring/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index a2fdf7a487be..b9218f1b0426 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -24,7 +24,7 @@ tokio = { version = "1.28.1", features = ["fs", "io-util", "sync"] } [dev-dependencies] hex = "0.4.3" rand = "0.8.5" -indexmap = "1.9.1" +indexmap = "2.0.0" tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } [lib] From 0285c42bbdba6f6fea08ca2a0c3b70922ab7dbc4 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 29 Jun 2023 16:07:48 -0400 Subject: [PATCH 0200/1053] Make Revisions Thread Safe (#148) --- Cargo.toml | 1 + firewood-shale/src/cached.rs | 63 +- firewood-shale/src/compact.rs | 91 ++- firewood-shale/src/lib.rs | 115 +-- firewood/benches/hashops.rs | 11 +- firewood/examples/rev.rs | 322 +++++--- firewood/src/account.rs | 8 +- firewood/src/db.rs | 279 ++++--- firewood/src/merkle.rs | 59 +- firewood/src/merkle_util.rs | 37 +- firewood/src/proof.rs | 25 +- firewood/src/service/server.rs | 6 +- firewood/src/storage/buffer.rs | 10 +- firewood/src/storage/mod.rs | 188 ++--- firewood/tests/db.rs | 18 +- firewood/tests/merkle.rs | 8 +- shale-archive/Cargo.toml | 23 + shale-archive/LICENSE | 22 + shale-archive/benches/shale-bench.rs | 99 +++ shale-archive/src/cached.rs | 251 ++++++ shale-archive/src/compact.rs | 759 ++++++++++++++++++ shale-archive/src/lib.rs | 599 ++++++++++++++ {firewood-shale => shale-archive}/src/util.rs | 0 23 files changed, 2447 insertions(+), 547 deletions(-) create mode 100644 shale-archive/Cargo.toml create mode 100644 shale-archive/LICENSE create mode 100644 shale-archive/benches/shale-bench.rs create mode 100644 shale-archive/src/cached.rs create mode 100644 shale-archive/src/compact.rs create mode 100644 shale-archive/src/lib.rs rename {firewood-shale => shale-archive}/src/util.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 0742fcfcbda3..508571625719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "firewood-growth-ring", "firewood-libaio", "firewood-shale", + "shale-archive", "firewood", "fwdctl", ] diff --git a/firewood-shale/src/cached.rs b/firewood-shale/src/cached.rs index 0eea8c3dff36..63b8e02b5791 100644 --- a/firewood-shale/src/cached.rs +++ b/firewood-shale/src/cached.rs @@ -1,17 +1,17 @@ -use std::borrow::BorrowMut; -use std::cell::{RefCell, UnsafeCell}; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - -use crate::{CachedStore, CachedView, SpaceId}; +use crate::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use std::{ + borrow::BorrowMut, + fmt::Debug, + ops::{Deref, DerefMut}, + sync::{Arc, RwLock}, +}; /// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying /// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write /// your own [CachedStore] implementation. #[derive(Debug)] pub struct PlainMem { - space: Rc>>, + space: Arc>>, id: SpaceId, } @@ -19,7 +19,7 @@ impl PlainMem { pub fn new(size: u64, id: SpaceId) -> Self { let mut space: Vec = Vec::new(); space.resize(size as usize, 0); - let space = Rc::new(RefCell::new(space)); + let space = Arc::new(RwLock::new(space)); Self { space, id } } } @@ -32,7 +32,7 @@ impl CachedStore for PlainMem { ) -> Option>>> { let offset = offset as usize; let length = length as usize; - if offset + length > self.space.borrow().len() { + if offset + length > self.space.read().unwrap().len() { None } else { Some(Box::new(PlainMemView { @@ -46,7 +46,7 @@ impl CachedStore for PlainMem { } } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(PlainMemShared(Self { space: self.space.clone(), id: self.id, @@ -56,7 +56,7 @@ impl CachedStore for PlainMem { fn write(&mut self, offset: u64, change: &[u8]) { let offset = offset as usize; let length = change.len(); - let mut vect = self.space.deref().borrow_mut(); + let mut vect = self.space.deref().write().unwrap(); vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); } @@ -91,7 +91,7 @@ impl CachedView for PlainMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { - self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() + self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } @@ -100,21 +100,15 @@ impl CachedView for PlainMemView { /// not enough. #[derive(Debug)] pub struct DynamicMem { - space: Rc>>, + space: Arc>>, id: SpaceId, } impl DynamicMem { pub fn new(size: u64, id: SpaceId) -> Self { - let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); + let space = Arc::new(RwLock::new(vec![0; size as usize])); Self { space, id } } - - #[allow(clippy::mut_from_ref)] - // TODO: Refactor this usage. - fn get_space_mut(&self) -> &mut Vec { - unsafe { &mut *self.space.get() } - } } impl CachedStore for DynamicMem { @@ -126,10 +120,13 @@ impl CachedStore for DynamicMem { let offset = offset as usize; let length = length as usize; let size = offset + length; + let mut space = self.space.write().unwrap(); + // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); + if size > space.len() { + space.resize(size, 0); } + Some(Box::new(DynamicMemView { offset, length, @@ -140,7 +137,7 @@ impl CachedStore for DynamicMem { })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(DynamicMemShared(Self { space: self.space.clone(), id: self.id, @@ -151,11 +148,14 @@ impl CachedStore for DynamicMem { let offset = offset as usize; let length = change.len(); let size = offset + length; + + let mut space = self.space.write().unwrap(); + // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); + if size > space.len() { + space.resize(size, 0); } - self.get_space_mut()[offset..offset + length].copy_from_slice(change) + space[offset..offset + length].copy_from_slice(change) } fn id(&self) -> SpaceId { @@ -172,13 +172,6 @@ struct DynamicMemView { struct DynamicMemShared(DynamicMem); -impl Deref for DynamicMemView { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.mem.get_space_mut()[self.offset..self.offset + self.length] - } -} - impl Deref for DynamicMemShared { type Target = dyn CachedStore; fn deref(&self) -> &(dyn CachedStore + 'static) { @@ -196,7 +189,7 @@ impl CachedView for DynamicMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { - self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() + self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } diff --git a/firewood-shale/src/compact.rs b/firewood-shale/src/compact.rs index 2078adcd6d6b..f37a3f871026 100644 --- a/firewood-shale/src/compact.rs +++ b/firewood-shale/src/compact.rs @@ -1,8 +1,8 @@ use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; -use std::cell::UnsafeCell; use std::fmt::Debug; use std::io::{Cursor, Write}; -use std::rc::Rc; +use std::ops::DerefMut; +use std::sync::{Arc, RwLock}; #[derive(Debug)] pub struct CompactHeader { @@ -293,16 +293,16 @@ impl std::ops::DerefMut for U64Field { } } -struct CompactSpaceInner { - meta_space: Rc, - compact_space: Rc, +struct CompactSpaceInner { + meta_space: Arc, + compact_space: Arc, header: CompactSpaceHeaderSliced, obj_cache: super::ObjCache, alloc_max_walk: u64, regn_nbit: u64, } -impl CompactSpaceInner { +impl CompactSpaceInner { fn get_descriptor( &self, ptr: ObjPtr, @@ -310,7 +310,7 @@ impl CompactSpaceInner { StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } - fn get_data_ref( + fn get_data_ref( &self, ptr: ObjPtr, len_limit: u64, @@ -562,22 +562,21 @@ impl CompactSpaceInner { } } -#[derive(Debug)] -pub struct CompactSpace { - inner: UnsafeCell>, +pub struct CompactSpace { + inner: RwLock>, } -impl CompactSpace { +impl CompactSpace { pub fn new( - meta_space: Rc, - compact_space: Rc, + meta_space: Arc, + compact_space: Arc, header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, regn_nbit: u64, ) -> Result { let cs = CompactSpace { - inner: UnsafeCell::new(CompactSpaceInner { + inner: RwLock::new(CompactSpaceInner { meta_space, compact_space, header: CompactSpaceHeader::into_fields(header)?, @@ -590,53 +589,71 @@ impl CompactSpace { } } -impl ShaleStore +impl ShaleStore for CompactSpace { - fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { + fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; - let inner = unsafe { &mut *self.inner.get() }; - let ptr: ObjPtr = - ObjPtr::new_from_addr(if let Some(addr) = inner.alloc_from_freed(size)? { + let ptr: ObjPtr = { + let mut inner = self.inner.write().unwrap(); + let addr = if let Some(addr) = inner.alloc_from_freed(size)? { addr } else { inner.alloc_new(size)? - }); - let mut u = inner.obj_cache.put(StoredView::item_to_obj( + }; + + ObjPtr::new_from_addr(addr) + }; + let inner = self.inner.read().unwrap(); + + let mut u: ObjRef<'a, T> = inner.obj_cache.put(StoredView::item_to_obj( inner.compact_space.as_ref(), ptr.addr(), size, item, )?); + u.write(|_| {}).unwrap(); + Ok(u) } fn free_item(&mut self, ptr: ObjPtr) -> Result<(), ShaleError> { - let inner = self.inner.get_mut(); + let mut inner = self.inner.write().unwrap(); inner.obj_cache.pop(ptr); inner.free(ptr.addr()) } - fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError> { - let inner = unsafe { &*self.inner.get() }; - if let Some(r) = inner.obj_cache.get(ptr)? { - return Ok(r); - } + fn get_item(&self, ptr: ObjPtr) -> Result, ShaleError> { + let inner = { + let inner = self.inner.read().unwrap(); + let obj_ref = inner + .obj_cache + .get(inner.obj_cache.lock().deref_mut(), ptr)?; + + if let Some(obj_ref) = obj_ref { + return Ok(obj_ref); + } + + inner + }; + if ptr.addr() < CompactSpaceHeader::MSIZE { return Err(ShaleError::InvalidAddressLength { expected: CompactSpaceHeader::MSIZE, found: ptr.addr(), }); } - let h = inner.get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))?; - Ok(inner - .obj_cache - .put(inner.get_data_ref(ptr, h.payload_size)?)) + + let payload_size = inner + .get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))? + .payload_size; + + Ok(inner.obj_cache.put(inner.get_data_ref(ptr, payload_size)?)) } fn flush_dirty(&self) -> Option<()> { - let inner = unsafe { &mut *self.inner.get() }; + let mut inner = self.inner.write().unwrap(); inner.header.flush_dirty(); inner.obj_cache.flush_dirty() } @@ -709,8 +726,8 @@ mod tests { ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); - let mem_meta = Rc::new(dm); - let mem_payload = Rc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); + let mem_meta = Arc::new(dm); + let mem_payload = Arc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); let cache: ObjCache = ObjCache::new(1); let space = @@ -728,21 +745,21 @@ mod tests { // not cached assert!(obj_ref .cache - .get_inner_mut() + .lock() .cached .get(&ObjPtr::new_from_addr(4113)) .is_none()); // pinned assert!(obj_ref .cache - .get_inner_mut() + .lock() .pinned .get(&ObjPtr::new_from_addr(4113)) .is_some()); // dirty assert!(obj_ref .cache - .get_inner_mut() + .lock() .dirty .get(&ObjPtr::new_from_addr(4113)) .is_some()); diff --git a/firewood-shale/src/lib.rs b/firewood-shale/src/lib.rs index d0c01a039ee3..524ea269a2de 100644 --- a/firewood-shale/src/lib.rs +++ b/firewood-shale/src/lib.rs @@ -1,5 +1,4 @@ use std::any::type_name; -use std::cell::UnsafeCell; use std::collections::{HashMap, HashSet}; use std::fmt::{self, Debug, Display, Formatter}; use std::hash::Hash; @@ -7,13 +6,12 @@ use std::hash::Hasher; use std::marker::PhantomData; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; -use std::rc::Rc; +use std::sync::{Arc, RwLock, RwLockWriteGuard}; use thiserror::Error; pub mod cached; pub mod compact; -pub mod util; #[derive(Debug, Error)] #[non_exhaustive] @@ -68,11 +66,15 @@ pub trait CachedView { fn as_deref(&self) -> Self::DerefReturn; } +pub trait SendSyncDerefMut: DerefMut + Send + Sync {} + +impl SendSyncDerefMut for T {} + /// In-memory store that offers access to intervals from a linear byte space, which is usually /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear /// persistent store. Reads could trigger disk reads to bring data into memory, but writes will /// *only* be visible in memory (it does not write back to the disk). -pub trait CachedStore: Debug { +pub trait CachedStore: Debug + Send + Sync { /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them /// directly accessible. fn get_view( @@ -81,7 +83,7 @@ pub trait CachedStore: Debug { length: u64, ) -> Option>>>; /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Box>; + fn get_shared(&self) -> Box>; /// Write the `change` to the portion of the linear space starting at `offset`. The change /// should be immediately visible to all `CachedView` associated to this linear space. fn write(&mut self, offset: u64, change: &[u8]); @@ -151,7 +153,9 @@ impl ObjPtr { /// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object /// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to /// turn a `TypedView` into an [ObjRef]. -pub trait TypedView: std::fmt::Debug + Deref { +pub trait TypedView: + std::fmt::Debug + Deref + Send + Sync +{ /// Get the offset of the initial byte in the linear space. fn get_offset(&self) -> u64; /// Access it as a [CachedStore] object. @@ -177,19 +181,17 @@ pub trait TypedView: std::fmt::Debug + Deref { /// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. #[derive(Debug)] -pub struct Obj { +pub struct Obj { value: Box>, dirty: Option, } -impl Obj { +impl Obj { #[inline(always)] pub fn as_ptr(&self) -> ObjPtr { ObjPtr::::new(self.value.get_offset()) } -} -impl Obj { /// Write to the underlying object. Returns `Some(())` on success. #[inline] pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { @@ -228,13 +230,13 @@ impl Obj { } } -impl Drop for Obj { +impl Drop for Obj { fn drop(&mut self) { self.flush_dirty() } } -impl Deref for Obj { +impl Deref for Obj { type Target = T; fn deref(&self) -> &T { &self.value @@ -242,14 +244,13 @@ impl Deref for Obj { } /// User handle that offers read & write access to the stored [ShaleStore] item. -#[derive(Debug)] -pub struct ObjRef<'a, T> { +pub struct ObjRef<'a, T: Send + Sync> { inner: Option>, cache: ObjCache, _life: PhantomData<&'a mut ()>, } -impl<'a, T> ObjRef<'a, T> { +impl<'a, T: Send + Sync> ObjRef<'a, T> { pub fn to_longlive(mut self) -> ObjRef<'static, T> { ObjRef { inner: self.inner.take(), @@ -263,24 +264,24 @@ impl<'a, T> ObjRef<'a, T> { let inner = self.inner.as_mut().unwrap(); inner.write(modify)?; - self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); + self.cache.lock().dirty.insert(inner.as_ptr()); Ok(()) } } -impl<'a, T> Deref for ObjRef<'a, T> { +impl<'a, T: Send + Sync> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { self.inner.as_ref().unwrap() } } -impl<'a, T> Drop for ObjRef<'a, T> { +impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> { fn drop(&mut self) { let mut inner = self.inner.take().unwrap(); let ptr = inner.as_ptr(); - let cache = self.cache.get_inner_mut(); + let mut cache = self.cache.lock(); match cache.pinned.remove(&ptr) { Some(true) => { inner.dirty = None; @@ -294,7 +295,7 @@ impl<'a, T> Drop for ObjRef<'a, T> { /// A persistent item storage backed by linear logical space. New items can be created and old /// items could be retrieved or dropped. -pub trait ShaleStore: Debug { +pub trait ShaleStore { /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; /// Allocate a new item. @@ -326,9 +327,9 @@ pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { } /// Reference implementation of [TypedView]. It takes any type that implements [Storable] -pub struct StoredView { +pub struct StoredView { decoded: T, - mem: Box>, + mem: Box>, offset: u64, len_limit: u64, } @@ -356,7 +357,7 @@ impl Deref for StoredView { } } -impl TypedView for StoredView { +impl TypedView for StoredView { fn get_offset(&self) -> u64 { self.offset } @@ -390,7 +391,7 @@ impl TypedView for StoredView { } } -impl StoredView { +impl StoredView { #[inline(always)] fn new(offset: u64, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; @@ -443,7 +444,7 @@ impl StoredView { } } -impl StoredView { +impl StoredView { fn new_from_slice( offset: u64, len_limit: u64, @@ -458,7 +459,7 @@ impl StoredView { }) } - pub fn slice( + pub fn slice( s: &Obj, offset: u64, length: u64, @@ -515,56 +516,72 @@ impl Storable for ObjPtr { } } -struct ObjCacheInner { +pub struct ObjCacheInner { cached: lru::LruCache, Obj>, pinned: HashMap, bool>, dirty: HashSet>, } /// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. -#[derive(Debug)] -pub struct ObjCache(Rc>>); +pub struct ObjCache(Arc>>); -impl ObjCache { +impl ObjCache { pub fn new(capacity: usize) -> Self { - Self(Rc::new(UnsafeCell::new(ObjCacheInner { + Self(Arc::new(RwLock::new(ObjCacheInner { cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), pinned: HashMap::new(), dirty: HashSet::new(), }))) } - #[inline(always)] - #[allow(clippy::mut_from_ref)] - // TODO: Refactor this function - fn get_inner_mut(&self) -> &mut ObjCacheInner { - unsafe { &mut *self.0.get() } + fn lock(&self) -> RwLockWriteGuard> { + self.0.write().unwrap() } #[inline(always)] - pub fn get(&'_ self, ptr: ObjPtr) -> Result>, ShaleError> { - let inner = &mut self.get_inner_mut(); + pub fn get<'a>( + &self, + inner: &mut ObjCacheInner, + ptr: ObjPtr, + ) -> Result>, ShaleError> { if let Some(r) = inner.cached.pop(&ptr) { - if inner.pinned.insert(ptr, false).is_some() { - return Err(ShaleError::InvalidObj { - addr: ptr.addr(), - obj_type: type_name::(), - error: "address already in use", - }); - } + // insert and set to `false` if you can + // When using `get` in parallel, one should not `write` to the same address + inner + .pinned + .entry(ptr) + .and_modify(|is_pinned| *is_pinned = false) + .or_insert(false); + + // return if inner.pinned.insert(ptr, false).is_some() { + // Err(ShaleError::InvalidObj { + // addr: ptr.addr(), + // obj_type: type_name::(), + // error: "address already in use", + // }) + // } else { + // Ok(Some(ObjRef { + // inner: Some(r), + // cache: Self(self.0.clone()), + // _life: PhantomData, + // })) + // }; + + // always return instead of the code above return Ok(Some(ObjRef { inner: Some(r), cache: Self(self.0.clone()), _life: PhantomData, })); } + Ok(None) } #[inline(always)] - pub fn put(&'_ self, inner: Obj) -> ObjRef<'_, T> { + pub fn put<'a>(&self, inner: Obj) -> ObjRef<'a, T> { let ptr = inner.as_ptr(); - self.get_inner_mut().pinned.insert(ptr, false); + self.lock().pinned.insert(ptr, false); ObjRef { inner: Some(inner), cache: Self(self.0.clone()), @@ -574,7 +591,7 @@ impl ObjCache { #[inline(always)] pub fn pop(&self, ptr: ObjPtr) { - let inner = self.get_inner_mut(); + let mut inner = self.lock(); if let Some(f) = inner.pinned.get_mut(&ptr) { *f = true } @@ -585,7 +602,7 @@ impl ObjCache { } pub fn flush_dirty(&self) -> Option<()> { - let inner = self.get_inner_mut(); + let mut inner = self.lock(); if !inner.pinned.is_empty() { return None; } diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 24f4f92cb21e..53eddbbf2509 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -2,9 +2,14 @@ use std::ops::Deref; use bencher::{benchmark_group, benchmark_main, Bencher}; -use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; +use firewood::{ + merkle::{Merkle, Node, TrieHash, TRIE_HASH_LEN}, + storage::StoreRevMut, +}; use firewood_shale::{ - cached::PlainMem, compact::CompactSpaceHeader, CachedStore, ObjPtr, Storable, StoredView, + cached::PlainMem, + compact::{CompactSpace, CompactSpaceHeader}, + CachedStore, ObjPtr, Storable, StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; @@ -48,7 +53,7 @@ fn bench_insert(b: &mut Bencher) { .unwrap(); let mut merkle = Merkle::new(Box::new(store)); let mut root = ObjPtr::null(); - Merkle::init_root(&mut root, merkle.get_store()).unwrap(); + Merkle::>::init_root(&mut root, merkle.get_store()).unwrap(); let mut rng = rand::rngs::StdRng::seed_from_u64(1234); const KEY_LEN: usize = 4; b.iter(|| { diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 939799172d90..2631ba87d4a9 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -1,143 +1,84 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::collections::VecDeque; +use std::{collections::VecDeque, path::Path}; use firewood::{ - db::{Db, DbConfig, Revision, WalConfig}, - merkle::TrieHash, + db::{Db, DbConfig, Revision, WalConfig, WriteBatch}, + merkle::{Node, TrieHash}, proof::Proof, + storage::{StoreRevMut, StoreRevShared}, }; +use firewood_shale::compact::CompactSpace; + +type Store = CompactSpace; +type SharedStore = CompactSpace; /// cargo run --example rev fn main() { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - let mut hashes: VecDeque = VecDeque::new(); - { - let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) - .expect("db initiation should succeed"); - let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; - for (k, v) in items.iter() { - db.new_writebatch() - .kv_insert(k, v.as_bytes().to_vec()) - .unwrap() - .commit(); - - let root_hash = db - .kv_root_hash() - .expect("root-hash for current state should exist"); - println!("{root_hash:?}"); - hashes.push_front(root_hash); - } - db.kv_dump(&mut std::io::stdout()).unwrap(); - let revision = db - .get_revision(hashes[0].clone(), None) - .expect("revision-0 should exist"); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-0 should exist"); - println!("{revision_root_hash:?}"); - - let current_root_hash = db - .kv_root_hash() - .expect("root-hash for current state should exist"); - // The following is true as long as there are no dirty-writes. - assert_eq!(revision_root_hash, current_root_hash); - - let revision = db - .get_revision(hashes[2].clone(), None) - .expect("revision-2 should exist"); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-2 should exist"); - println!("{revision_root_hash:?}"); - - // Get a revision while a batch is active. - let revision = db - .get_revision(hashes[1].clone(), None) - .expect("revision-1 should exist"); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - println!("{revision_root_hash:?}"); - - let write = db.new_writebatch().kv_insert("k", vec![b'v']).unwrap(); - - let actual_revision_root_hash = db - .get_revision(hashes[1].clone(), None) - .expect("revision-1 should exist") - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - assert_eq!(revision_root_hash, actual_revision_root_hash); - - // Read the uncommitted value while the batch is still active. - let val = db.kv_get("k").unwrap(); - assert_eq!("v".as_bytes().to_vec(), val); - - write.commit(); - hashes.push_front(db.kv_root_hash().expect("root-hash should exist")); - - let new_revision_root_hash = db - .get_revision(hashes[1].clone(), None) - .expect("revision-1 should exist") - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - assert_ne!(revision_root_hash, new_revision_root_hash); - let val = db.kv_get("k").unwrap(); - assert_eq!("v".as_bytes().to_vec(), val); - - // When reading a specific revision, after new commits the revision remains consistent. - let val = revision.kv_get("k"); - assert_eq!(None, val); - let val = revision.kv_get("dof").unwrap(); - assert_eq!("verb".as_bytes().to_vec(), val); - let actual_revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-2 should exist"); - assert_eq!(revision_root_hash, actual_revision_root_hash); - } - { - let db = - Db::new("rev_db", &cfg.truncate(false).build()).expect("db initiation should succeed"); - { - let revision = db - .get_revision(hashes[0].clone(), None) - .expect("revision-0 should exist"); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-0 should exist"); - println!("{revision_root_hash:?}"); - let current_root_hash = db - .kv_root_hash() - .expect("root-hash for current state should exist"); - // The following is true as long as the current state is fresh after replaying from Wals. - assert_eq!(revision_root_hash, current_root_hash); + let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) + .expect("db initiation should succeed"); + let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; + + let mut revision_tracker = RevisionTracker::new(db); + + revision_tracker.create_revisions(items.into_iter()); - let revision = db - .get_revision(hashes[1].clone(), None) - .expect("revision-1 should exist"); + revision_tracker.db.kv_dump(&mut std::io::stdout()).unwrap(); + + verify_root_hashes(&mut revision_tracker); + + let revision_tracker = revision_tracker.with_new_db("rev_db", &cfg.truncate(false).build()); + + let revision = revision_tracker.get_revision(0); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-0 should exist"); + println!("{revision_root_hash:?}"); + + let current_root_hash = revision_tracker + .db + .kv_root_hash() + .expect("root-hash for current state should exist"); + // The following is true as long as the current state is fresh after replaying from Wals. + assert_eq!(revision_root_hash, current_root_hash); + + let rev1 = revision_tracker.get_revision(1); + let rev2 = revision_tracker.get_revision(2); + + std::thread::scope(move |scope| { + scope.spawn(|| { + let revision = rev1; revision.kv_dump(&mut std::io::stdout()).unwrap(); let mut items_rev = vec![("dof", "verb"), ("doe", "reindeer")]; items_rev.sort(); - let (keys, vals) = items_rev.clone().into_iter().unzip(); - let proof = build_proof(&revision, &items_rev); - revision - .verify_range_proof( - proof, - items_rev[0].0, - items_rev[items_rev.len() - 1].0, - keys, - vals, - ) - .unwrap(); - } - { - let revision = db - .get_revision(hashes[2].clone(), None) - .expect("revision-2 should exist"); + let items = items_rev; + std::thread::scope(|scope| { + for _ in 0..3 { + scope.spawn(|| { + let (keys, vals) = items.iter().cloned().unzip(); + + let proof = build_proof(&revision, &items); + revision + .verify_range_proof( + proof, + items.first().unwrap().0, + items.last().unwrap().0, + keys, + vals, + ) + .unwrap(); + }); + } + }) + }); + + scope.spawn(|| { + let revision = rev2; let revision_root_hash = revision .kv_root_hash() .expect("root-hash for revision-2 should exist"); @@ -146,25 +87,152 @@ fn main() { let mut items_rev = vec![("dof", "verb")]; items_rev.sort(); - let (keys, vals) = items_rev.clone().into_iter().unzip(); + let items = items_rev; + + let (keys, vals) = items.iter().cloned().unzip(); - let proof = build_proof(&revision, &items_rev); + let proof = build_proof(&revision, &items); revision .verify_range_proof( proof, - items_rev[0].0, - items_rev[items_rev.len() - 1].0, + items.first().unwrap().0, + items.last().unwrap().0, keys, vals, ) .unwrap(); + }); + }); +} + +struct RevisionTracker { + hashes: VecDeque, + db: Db, +} + +impl RevisionTracker { + fn new(db: Db) -> Self { + Self { + hashes: VecDeque::new(), + db, } } + + fn create_revisions(&mut self, iter: impl Iterator) + where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + { + iter.for_each(|(k, v)| self.create_revision(k, v)); + } + + fn create_revision(&mut self, k: K, v: V) + where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + { + self.db + .new_writebatch() + .kv_insert(k, v.as_ref().to_vec()) + .unwrap() + .commit(); + let hash = self.db.kv_root_hash().expect("root-hash should exist"); + self.hashes.push_front(hash); + } + + fn commit_batch(&mut self, batch: WriteBatch) { + batch.commit(); + let hash = self.db.kv_root_hash().expect("root-hash should exist"); + self.hashes.push_front(hash); + } + + fn with_new_db(self, path: impl AsRef, cfg: &DbConfig) -> Self { + let hashes = { + // must name db variable to explictly drop + let Self { hashes, db: _db } = self; + hashes + }; + + let db = Db::new(path, cfg).expect("db initiation should succeed"); + + Self { hashes, db } + } + + fn get_revision(&self, index: usize) -> Revision { + self.db + .get_revision(&self.hashes[index], None) + .expect(&format!("revision-{index} should exist")) + } } -fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof { +fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof { let mut proof = revision.prove(items[0].0).unwrap(); let end = revision.prove(items.last().unwrap().0).unwrap(); proof.concat_proofs(end); proof } + +fn verify_root_hashes(revision_tracker: &mut RevisionTracker) { + let revision = revision_tracker.get_revision(0); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-0 should exist"); + println!("{revision_root_hash:?}"); + + let current_root_hash = revision_tracker + .db + .kv_root_hash() + .expect("root-hash for current state should exist"); + + // The following is true as long as there are no dirty-writes. + assert_eq!(revision_root_hash, current_root_hash); + + let revision = revision_tracker.get_revision(2); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-2 should exist"); + println!("{revision_root_hash:?}"); + + // Get a revision while a batch is active. + let revision = revision_tracker.get_revision(1); + let revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + println!("{revision_root_hash:?}"); + + let write = revision_tracker + .db + .new_writebatch() + .kv_insert("k", vec![b'v']) + .unwrap(); + + let actual_revision_root_hash = revision_tracker + .get_revision(1) + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + assert_eq!(revision_root_hash, actual_revision_root_hash); + + // Read the uncommitted value while the batch is still active. + let val = revision_tracker.db.kv_get("k").unwrap(); + assert_eq!("v".as_bytes().to_vec(), val); + + revision_tracker.commit_batch(write); + + let new_revision_root_hash = revision_tracker + .get_revision(1) + .kv_root_hash() + .expect("root-hash for revision-1 should exist"); + assert_ne!(revision_root_hash, new_revision_root_hash); + let val = revision_tracker.db.kv_get("k").unwrap(); + assert_eq!("v".as_bytes().to_vec(), val); + + // When reading a specific revision, after new commits the revision remains consistent. + let val = revision.kv_get("k"); + assert_eq!(None, val); + let val = revision.kv_get("dof").unwrap(); + assert_eq!("verb".as_bytes().to_vec(), val); + let actual_revision_root_hash = revision + .kv_root_hash() + .expect("root-hash for revision-2 should exist"); + assert_eq!(revision_root_hash, actual_revision_root_hash); +} diff --git a/firewood/src/account.rs b/firewood/src/account.rs index ac4ac4c8c0e5..84c4633e0407 100644 --- a/firewood/src/account.rs +++ b/firewood/src/account.rs @@ -132,12 +132,12 @@ pub enum BlobError { Shale(ShaleError), } -pub struct BlobStash { - store: Box>, +pub struct BlobStash { + store: Box, } -impl BlobStash { - pub fn new(store: Box>) -> Self { +impl + Send + Sync> BlobStash { + pub fn new(store: Box) -> Self { Self { store } } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 65802c30d85d..c53f33497dda 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,35 +1,39 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::collections::VecDeque; -use std::error::Error; -use std::fmt; -use std::io::{Cursor, Write}; -use std::path::Path; -use std::rc::Rc; -use std::sync::Arc; -use std::thread::JoinHandle; - -use bytemuck::{cast_slice, AnyBitPattern}; -use metered::{metered, HitCount}; -use parking_lot::{Mutex, RwLock}; -#[cfg(feature = "eth")] -use primitive_types::U256; -use shale::ShaleError; -use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, SpaceId, Storable, StoredView}; -use typed_builder::TypedBuilder; - #[cfg(feature = "eth")] use crate::account::{Account, AccountRlp, Blob, BlobStash}; -use crate::file; -use crate::merkle::{IdTrans, Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}; -use crate::proof::{Proof, ProofError}; -use crate::storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}; pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use crate::storage::{ AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }; +use crate::{ + file, + merkle::{IdTrans, Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, + proof::{Proof, ProofError}, + storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, +}; +use bytemuck::{cast_slice, AnyBitPattern}; +use metered::{metered, HitCount}; +use parking_lot::{Mutex, RwLock}; +#[cfg(feature = "eth")] +use primitive_types::U256; +use shale::compact::CompactSpace; +use shale::ShaleStore; +use shale::{ + compact::CompactSpaceHeader, CachedStore, ObjPtr, ShaleError, SpaceId, Storable, StoredView, +}; +use std::{ + collections::VecDeque, + error::Error, + fmt, + io::{Cursor, Write}, + path::Path, + sync::Arc, + thread::JoinHandle, +}; +use typed_builder::TypedBuilder; const MERKLE_META_SPACE: SpaceId = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; @@ -179,7 +183,7 @@ impl SubUniverse { } impl SubUniverse { - fn to_mem_store_r(&self) -> SubUniverse> { + fn to_mem_store_r(&self) -> SubUniverse> { SubUniverse { meta: self.meta.inner().clone(), payload: self.payload.inner().clone(), @@ -187,7 +191,7 @@ impl SubUniverse { } } -impl SubUniverse> { +impl SubUniverse> { fn rewind( &self, meta_writes: &[SpaceWrite], @@ -200,8 +204,8 @@ impl SubUniverse> { } } -impl SubUniverse> { - fn to_mem_store_r(&self) -> SubUniverse> { +impl SubUniverse> { + fn to_mem_store_r(&self) -> SubUniverse> { SubUniverse { meta: self.meta.clone(), payload: self.payload.clone(), @@ -210,7 +214,7 @@ impl SubUniverse> { } fn get_sub_universe_from_deltas( - sub_universe: &SubUniverse>, + sub_universe: &SubUniverse>, meta_delta: StoreDelta, payload_delta: StoreDelta, ) -> SubUniverse { @@ -221,7 +225,7 @@ fn get_sub_universe_from_deltas( } fn get_sub_universe_from_empty_delta( - sub_universe: &SubUniverse>, + sub_universe: &SubUniverse>, ) -> SubUniverse { get_sub_universe_from_deltas(sub_universe, StoreDelta::default(), StoreDelta::default()) } @@ -282,7 +286,7 @@ struct Universe { } impl Universe { - fn to_mem_store_r(&self) -> Universe> { + fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), blob: self.blob.to_mem_store_r(), @@ -290,8 +294,8 @@ impl Universe { } } -impl Universe> { - fn to_mem_store_r(&self) -> Universe> { +impl Universe> { + fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), blob: self.blob.to_mem_store_r(), @@ -299,7 +303,7 @@ impl Universe> { } } -impl Universe> { +impl Universe> { fn rewind( &self, merkle_meta_writes: &[SpaceWrite], @@ -317,14 +321,14 @@ impl Universe> { } /// Some readable version of the DB. -pub struct DbRev { +pub struct DbRev { header: shale::Obj, - merkle: Merkle, + merkle: Merkle, #[cfg(feature = "eth")] - blob: BlobStash, + blob: BlobStash, } -impl DbRev { +impl + Send + Sync> DbRev { fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); self.merkle.flush_dirty()?; @@ -334,11 +338,11 @@ impl DbRev { } #[cfg(feature = "eth")] - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { (&mut self.header, &mut self.merkle, &mut self.blob) } #[cfg(not(feature = "eth"))] - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { (&mut self.header, &mut self.merkle) } @@ -395,7 +399,7 @@ impl DbRev { } #[cfg(feature = "eth")] -impl DbRev { +impl + Send + Sync> DbRev { /// Get nonce of the account. pub fn get_nonce>(&self, key: K) -> Result { Ok(self.get_account(key)?.nonce) @@ -468,16 +472,16 @@ impl DbRev { } } -struct DbInner { - latest: DbRev, +struct DbInner { + latest: DbRev, disk_requester: DiskBufferRequester, disk_thread: Option>, - data_staging: Universe>, - data_cache: Universe>, + data_staging: Universe>, + data_cache: Universe>, root_hash_staging: StoreRevMut, } -impl Drop for DbInner { +impl Drop for DbInner { fn drop(&mut self) { self.disk_requester.shutdown(); self.disk_thread.take().map(JoinHandle::join); @@ -485,8 +489,8 @@ impl Drop for DbInner { } /// Firewood database handle. -pub struct Db { - inner: Arc>, +pub struct Db { + inner: Arc>>, revisions: Arc>, payload_regn_nbit: u64, rev_cfg: DbRevConfig, @@ -500,8 +504,8 @@ pub struct DbRevInner { base: Universe, } -#[metered(registry = DbMetrics, visibility = pub)] -impl Db { +// #[metered(registry = DbMetrics, visibility = pub)] +impl Db> { /// Open a database. pub fn new>(db_path: P, cfg: &DbConfig) -> Result { // TODO: make sure all fds are released at the end @@ -543,8 +547,16 @@ impl Db { wal_block_nbit: cfg.wal.block_nbit, root_hash_file_nbit: cfg.root_hash_file_nbit, }; - nix::sys::uio::pwrite(fd0, &shale::util::get_raw_bytes(&header), 0) - .map_err(DbError::System)?; + + let header_bytes = unsafe { + std::slice::from_raw_parts( + &header as *const DbParams as *const u8, + std::mem::size_of::(), + ) + .to_vec() + }; + + nix::sys::uio::pwrite(fd0, &header_bytes, 0).map_err(DbError::System)?; } // read DbParams @@ -567,7 +579,7 @@ impl Db { disk_buffer.run() })); - let root_hash_cache = Rc::new( + let root_hash_cache = Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.root_hash_ncached_pages) @@ -584,7 +596,7 @@ impl Db { // setup disk buffer let data_cache = Universe { merkle: SubUniverse::new( - Rc::new( + Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.meta_ncached_pages) @@ -597,7 +609,7 @@ impl Db { ) .unwrap(), ), - Rc::new( + Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.payload_ncached_pages) @@ -612,7 +624,7 @@ impl Db { ), ), blob: SubUniverse::new( - Rc::new( + Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.meta_ncached_pages) @@ -625,7 +637,7 @@ impl Db { ) .unwrap(), ), - Rc::new( + Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.payload_ncached_pages) @@ -653,21 +665,6 @@ impl Db { disk_requester.reg_cached_space(cached_space.id(), cached_space.clone_files()); }); - let mut data_staging = Universe { - merkle: SubUniverse::new( - Rc::new(StoreRevMut::new(data_cache.merkle.meta.clone())), - Rc::new(StoreRevMut::new(data_cache.merkle.payload.clone())), - ), - blob: SubUniverse::new( - Rc::new(StoreRevMut::new(data_cache.blob.meta.clone())), - Rc::new(StoreRevMut::new(data_cache.blob.payload.clone())), - ), - }; - let root_hash_staging = StoreRevMut::new(root_hash_cache); - - // recover from Wal - disk_requester.init_wal("wal", db_path); - // set up the storage layout let db_header: ObjPtr = ObjPtr::new_from_addr(offset); @@ -677,22 +674,23 @@ impl Db { assert!(offset <= SPACE_RESERVED); let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + let mut data_staging_merkle_meta = StoreRevMut::new(data_cache.merkle.meta.clone()); + let mut data_staging_blob_meta = StoreRevMut::new(data_cache.blob.meta.clone()); + if reset { // initialize space headers - let initializer = Rc::::make_mut(&mut data_staging.merkle.meta); - initializer.write( + data_staging_merkle_meta.write( merkle_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, SPACE_RESERVED, ))?, ); - initializer.write( + data_staging_merkle_meta.write( db_header.addr(), &shale::to_dehydrated(&DbHeader::new_empty())?, ); - let initializer = Rc::::make_mut(&mut data_staging.blob.meta); - initializer.write( + data_staging_blob_meta.write( blob_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, @@ -701,6 +699,21 @@ impl Db { ); } + let data_staging = Universe { + merkle: SubUniverse::new( + Arc::new(data_staging_merkle_meta), + Arc::new(StoreRevMut::new(data_cache.merkle.payload.clone())), + ), + blob: SubUniverse::new( + Arc::new(data_staging_blob_meta), + Arc::new(StoreRevMut::new(data_cache.blob.payload.clone())), + ), + }; + let root_hash_staging = StoreRevMut::new(root_hash_cache); + + // recover from Wal + disk_requester.init_wal("wal", db_path); + let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = data_staging.merkle.meta.as_ref(); let blob_meta_ref = data_staging.blob.meta.as_ref(); @@ -749,8 +762,14 @@ impl Db { db_header_ref .write(|r| { err = (|| { - Merkle::init_root(&mut r.acc_root, &merkle_space)?; - Merkle::init_root(&mut r.kv_root, &merkle_space) + Merkle::>::init_root( + &mut r.acc_root, + &merkle_space, + )?; + Merkle::>::init_root( + &mut r.kv_root, + &merkle_space, + ) })(); }) .unwrap(); @@ -791,45 +810,22 @@ impl Db { }) } - /// Create a write batch. - pub fn new_writebatch(&self) -> WriteBatch { - WriteBatch { - m: Arc::clone(&self.inner), - r: Arc::clone(&self.revisions), - committed: false, - } - } - - /// Dump the Trie of the latest generic key-value storage. - pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.inner.read().latest.kv_dump(w) - } - /// Get root hash of the latest generic key-value storage. - pub fn kv_root_hash(&self) -> Result { - self.inner.read().latest.kv_root_hash() - } - - /// Get a value in the kv store associated with a particular key. - #[measure([HitCount])] - pub fn kv_get>(&self, key: K) -> Result, DbError> { - self.inner - .read() - .latest - .kv_get(key) - .ok_or(DbError::KeyNotFound) - } /// Get a handle that grants the access to any committed state of the entire DB, /// with a given root hash. If the given root hash matches with more than one /// revisions, we use the most recent one as the trie are the same. /// /// If no revision with matching root hash found, returns None. - #[measure([HitCount])] - pub fn get_revision(&self, root_hash: TrieHash, cfg: Option) -> Option { + // #[measure([HitCount])] + pub fn get_revision( + &self, + root_hash: &TrieHash, + cfg: Option, + ) -> Option>> { let mut revisions = self.revisions.lock(); let inner_lock = self.inner.read(); // Find the revision index with the given root hash. - let mut nback = revisions.root_hashes.iter().position(|r| r == &root_hash); + let mut nback = revisions.root_hashes.iter().position(|r| r == root_hash); let rlen = revisions.root_hashes.len(); if nback.is_none() && rlen < revisions.max_revisions { @@ -844,7 +840,7 @@ impl Db { .skip(rlen) .map(|ash| { StoreRevShared::from_ash( - Rc::new(ZeroStore::default()), + Arc::new(ZeroStore::default()), &ash.0[&ROOT_HASH_SPACE].redo, ) }) @@ -855,7 +851,7 @@ impl Db { .as_deref() }) .map(|data| TrieHash(data[..TRIE_HASH_LEN].try_into().unwrap())) - .position(|trie_hash| trie_hash == root_hash); + .position(|trie_hash| &trie_hash == root_hash); } let Some(nback) = nback else { @@ -931,8 +927,8 @@ impl Db { }; let merkle_space = shale::compact::CompactSpace::new( - Rc::new(space.merkle.meta.clone()), - Rc::new(space.merkle.payload.clone()), + Arc::new(space.merkle.meta.clone()), + Arc::new(space.merkle.payload.clone()), merkle_payload_header_ref, shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).merkle_ncached_objs), 0, @@ -942,14 +938,15 @@ impl Db { #[cfg(feature = "eth")] let blob_space = shale::compact::CompactSpace::new( - Rc::new(space.blob.meta.clone()), - Rc::new(space.blob.payload.clone()), + Arc::new(space.blob.meta.clone()), + Arc::new(space.blob.payload.clone()), blob_payload_header_ref, shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).blob_ncached_objs), 0, self.payload_regn_nbit, ) .unwrap(); + Some(Revision { rev: DbRev { header: db_header_ref, @@ -959,12 +956,44 @@ impl Db { }, }) } +} + +#[metered(registry = DbMetrics, visibility = pub)] +impl + Send + Sync> Db { + /// Create a write batch. + pub fn new_writebatch(&self) -> WriteBatch { + WriteBatch { + m: Arc::clone(&self.inner), + r: Arc::clone(&self.revisions), + committed: false, + } + } + + /// Dump the Trie of the latest generic key-value storage. + pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { + self.inner.read().latest.kv_dump(w) + } + /// Get root hash of the latest generic key-value storage. + pub fn kv_root_hash(&self) -> Result { + self.inner.read().latest.kv_root_hash() + } + + /// Get a value in the kv store associated with a particular key. + #[measure(HitCount)] + pub fn kv_get>(&self, key: K) -> Result, DbError> { + self.inner + .read() + .latest + .kv_get(key) + .ok_or(DbError::KeyNotFound) + } + pub fn metrics(&self) -> Arc { self.metrics.clone() } } #[cfg(feature = "eth")] -impl Db { +impl + Send + Sync> Db { /// Dump the Trie of the latest entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.inner.read().latest.dump(w) @@ -1007,13 +1036,13 @@ impl Db { } /// Lock protected handle to a readable version of the DB. -pub struct Revision { - rev: DbRev, +pub struct Revision { + rev: DbRev, } -impl std::ops::Deref for Revision { - type Target = DbRev; - fn deref(&self) -> &DbRev { +impl std::ops::Deref for Revision { + type Target = DbRev; + fn deref(&self) -> &DbRev { &self.rev } } @@ -1021,13 +1050,13 @@ impl std::ops::Deref for Revision { /// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself /// because when an error occurs, the write batch will be automatically aborted so that the DB /// remains clean. -pub struct WriteBatch { - m: Arc>, +pub struct WriteBatch { + m: Arc>>, r: Arc>, committed: bool, } -impl WriteBatch { +impl + Send + Sync> WriteBatch { /// Insert an item to the generic key-value storage. pub fn kv_insert>(self, key: K, val: Vec) -> Result { let mut rev = self.m.write(); @@ -1196,11 +1225,11 @@ impl WriteBatch { } #[cfg(feature = "eth")] -impl WriteBatch { +impl + Send + Sync> WriteBatch { fn change_account( &mut self, key: &[u8], - modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DbError>, + modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DbError>, ) -> Result<(), DbError> { let mut rev = self.m.write(); let (header, merkle, blob) = rev.latest.borrow_split(); @@ -1341,7 +1370,7 @@ impl WriteBatch { } } -impl Drop for WriteBatch { +impl Drop for WriteBatch { fn drop(&mut self) { if !self.committed { // drop the staging changes diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1aa1029876b9..a4673aab3988 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -206,21 +206,21 @@ impl BranchNode { (only_chd, has_chd) } - fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { let mut stream = rlp::RlpStream::new_list(17); for (i, c) in self.chd.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); - if c_ref.get_eth_rlp_long::(store) { - stream.append(&&(*c_ref.get_root_hash::(store))[..]); + if c_ref.get_eth_rlp_long::(store) { + stream.append(&&(*c_ref.get_root_hash::(store))[..]); // See struct docs for ordering requirements if c_ref.lazy_dirty.load(Ordering::Relaxed) { c_ref.write(|_| {}).unwrap(); c_ref.lazy_dirty.store(false, Ordering::Relaxed) } } else { - let c_rlp = &c_ref.get_eth_rlp::(store); + let c_rlp = &c_ref.get_eth_rlp::(store); stream.append_raw(c_rlp, 1); } } @@ -321,19 +321,19 @@ impl Debug for ExtNode { } impl ExtNode { - fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { let mut stream = rlp::RlpStream::new_list(2); if !self.1.is_null() { let mut r = store.get_item(self.1).unwrap(); stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); - if r.get_eth_rlp_long::(store) { - stream.append(&&(*r.get_root_hash::(store))[..]); + if r.get_eth_rlp_long::(store) { + stream.append(&&(*r.get_root_hash::(store))[..]); if r.lazy_dirty.load(Ordering::Relaxed) { r.write(|_| {}).unwrap(); r.lazy_dirty.store(false, Ordering::Relaxed); } } else { - stream.append_raw(r.get_eth_rlp::(store), 1); + stream.append_raw(r.get_eth_rlp::(store), 1); } } else { // Check if there is already a caclucated rlp for the child, which @@ -422,11 +422,11 @@ pub enum NodeType { } impl NodeType { - fn calc_eth_rlp(&self, store: &dyn ShaleStore) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { match &self { NodeType::Leaf(n) => n.calc_eth_rlp::(), - NodeType::Extension(n) => n.calc_eth_rlp::(store), - NodeType::Branch(n) => n.calc_eth_rlp::(store), + NodeType::Extension(n) => n.calc_eth_rlp::(store), + NodeType::Branch(n) => n.calc_eth_rlp::(store), } } } @@ -454,22 +454,22 @@ impl Node { }) } - fn get_eth_rlp(&self, store: &dyn ShaleStore) -> &[u8] { + fn get_eth_rlp>(&self, store: &S) -> &[u8] { self.eth_rlp - .get_or_init(|| self.inner.calc_eth_rlp::(store)) + .get_or_init(|| self.inner.calc_eth_rlp::(store)) } - fn get_root_hash(&self, store: &dyn ShaleStore) -> &TrieHash { + fn get_root_hash>(&self, store: &S) -> &TrieHash { self.root_hash.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - TrieHash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) + TrieHash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) }) } - fn get_eth_rlp_long(&self, store: &dyn ShaleStore) -> bool { + fn get_eth_rlp_long>(&self, store: &S) -> bool { *self.eth_rlp_long.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN + self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN }) } @@ -842,12 +842,11 @@ macro_rules! write_node { }; } -#[derive(Debug)] -pub struct Merkle { - store: Box>, +pub struct Merkle { + store: Box, } -impl Merkle { +impl + Send + Sync> Merkle { pub fn get_node(&self, ptr: ObjPtr) -> Result, MerkleError> { self.store.get_item(ptr).map_err(MerkleError::Shale) } @@ -859,8 +858,8 @@ impl Merkle { } } -impl Merkle { - pub fn new(store: Box>) -> Self { +impl + Send + Sync> Merkle { + pub fn new(store: Box) -> Self { Self { store } } @@ -910,7 +909,7 @@ impl Merkle { .chd[0]; Ok(if let Some(root) = root { let mut node = self.get_node(root)?; - let res = node.get_root_hash::(self.store.as_ref()).clone(); + let res = node.get_root_hash::(self.store.as_ref()).clone(); if node.lazy_dirty.load(Ordering::Relaxed) { node.write(|_| {}).unwrap(); node.lazy_dirty.store(false, Ordering::Relaxed); @@ -1748,7 +1747,7 @@ impl Merkle { &mut self, key: K, root: ObjPtr, - ) -> Result, MerkleError> { + ) -> Result>, MerkleError> { let mut chunks = vec![0]; chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); let mut parents = Vec::new(); @@ -1892,7 +1891,7 @@ impl Merkle { // Get the hashes of the nodes. for node in nodes { let node = self.get_node(node)?; - let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); + let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(rlp).into(); proofs.insert(hash, rlp.to_vec()); } @@ -1969,10 +1968,10 @@ impl Merkle { pub struct Ref<'a>(ObjRef<'a, Node>); -pub struct RefMut<'a> { +pub struct RefMut<'a, S> { ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, - merkle: &'a mut Merkle, + merkle: &'a mut Merkle, } impl<'a> std::ops::Deref for Ref<'a> { @@ -1986,8 +1985,8 @@ impl<'a> std::ops::Deref for Ref<'a> { } } -impl<'a> RefMut<'a> { - fn new(ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, merkle: &'a mut Merkle) -> Self { +impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { + fn new(ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, merkle: &'a mut Merkle) -> Self { Self { ptr, parents, diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index eb4378603458..186623a6a62b 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,12 +1,16 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::*; -use crate::proof::{Proof, ProofError}; -use shale::cached::DynamicMem; -use shale::{compact::CompactSpaceHeader, CachedStore, ObjPtr, StoredView}; -use std::rc::Rc; - +use crate::{ + merkle::*, + proof::{Proof, ProofError}, +}; +use shale::{ + cached::DynamicMem, + compact::{CompactSpace, CompactSpaceHeader}, + CachedStore, ObjPtr, ShaleStore, StoredView, +}; +use std::sync::Arc; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -31,12 +35,12 @@ pub enum DataStoreError { ProofEmptyKeyValuesError, } -pub struct MerkleSetup { +pub struct MerkleSetup { root: ObjPtr, - merkle: Merkle, + merkle: Merkle, } -impl MerkleSetup { +impl + Send + Sync> MerkleSetup { pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { self.merkle .insert(key, val, self.root) @@ -55,7 +59,7 @@ impl MerkleSetup { .map_err(|_err| DataStoreError::GetError) } - pub fn get_mut>(&mut self, key: K) -> Result, DataStoreError> { + pub fn get_mut>(&mut self, key: K) -> Result>, DataStoreError> { self.merkle .get_mut(key, self.root) .map_err(|_err| DataStoreError::GetError) @@ -65,7 +69,7 @@ impl MerkleSetup { self.root } - pub fn get_merkle_mut(&mut self) -> &mut Merkle { + pub fn get_merkle_mut(&mut self) -> &mut Merkle { &mut self.merkle } @@ -113,7 +117,10 @@ impl MerkleSetup { } } -pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { +pub fn new_merkle( + meta_size: u64, + compact_size: u64, +) -> MerkleSetup> { const RESERVED: u64 = 0x1000; assert!(meta_size > RESERVED); assert!(compact_size > RESERVED); @@ -126,15 +133,15 @@ pub fn new_merkle(meta_size: u64, compact_size: u64) -> MerkleSetup { ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); - let mem_meta = Rc::new(dm); - let mem_payload = Rc::new(DynamicMem::new(compact_size, 0x1)); + let mem_meta = Arc::new(dm); + let mem_payload = Arc::new(DynamicMem::new(compact_size, 0x1)); let cache = shale::ObjCache::new(1); let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); let mut root = ObjPtr::null(); - Merkle::init_root(&mut root, &space).unwrap(); + Merkle::>::init_root(&mut root, &space).unwrap(); MerkleSetup { root, merkle: Merkle::new(Box::new(space)), diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8b7af6899760..c5f83d34d5b6 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use sha3::Digest; use shale::ObjPtr; use shale::ShaleError; +use shale::ShaleStore; use std::cmp::Ordering; use std::collections::HashMap; @@ -376,11 +377,11 @@ impl Proof { /// necessary nodes will be resolved and leave the remaining as hashnode. /// /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path>( + fn proof_to_path, S: ShaleStore + Send + Sync>( &self, key: K, root_hash: [u8; 32], - merkle_setup: &mut MerkleSetup, + merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, ) -> Result>, ProofError> { // Start with the sentinel root @@ -562,9 +563,9 @@ impl Proof { /// /// * `end_node` - A boolean indicates whether this is the end node to decode, thus no `key` /// to be present. - fn decode_node( + fn decode_node + Send + Sync>( &self, - merkle: &Merkle, + merkle: &Merkle, key: &[u8], buf: &[u8], end_node: bool, @@ -683,8 +684,8 @@ impl Proof { struct CurKey(Vec); struct Data(Vec); -fn get_ext_ptr( - merkle: &Merkle, +fn get_ext_ptr + Send + Sync>( + merkle: &Merkle, term: bool, Data(data): Data, CurKey(cur_key): CurKey, @@ -701,8 +702,8 @@ fn get_ext_ptr( .map_err(|_| ProofError::DecodeError) } -fn build_branch_ptr( - merkle: &Merkle, +fn build_branch_ptr + Send + Sync>( + merkle: &Merkle, value: Option>, chd_eth_rlp: [Option>; NBRANCH], ) -> Result, ProofError> { @@ -729,8 +730,8 @@ fn build_branch_ptr( // // The return value indicates if the fork point is root node. If so, unset the // entire trie. -fn unset_internal>( - merkle_setup: &mut MerkleSetup, +fn unset_internal, S: ShaleStore + Send + Sync>( + merkle_setup: &mut MerkleSetup, left: K, right: K, ) -> Result { @@ -974,8 +975,8 @@ fn unset_internal>( // keep the entire branch and return. // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -fn unset_node_ref>( - merkle: &Merkle, +fn unset_node_ref, S: ShaleStore + Send + Sync>( + merkle: &Merkle, parent: ObjPtr, node: Option>, key: K, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 0e86b7008d84..515a71370b64 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -39,8 +39,8 @@ pub struct FirewoodService {} impl FirewoodService { pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DbConfig) -> Self { let db = Db::new(owned_path, &cfg).unwrap(); - let mut active_batch: Option = None; - let mut revs = HashMap::::new(); + let mut active_batch = None; + let mut revs = HashMap::new(); let lastid = AtomicU32::new(0); loop { let msg = match receiver.blocking_recv() { @@ -59,7 +59,7 @@ impl FirewoodService { respond_to, } => { let id: RevId = lastid.fetch_add(1, Ordering::Relaxed); - let msg = match db.get_revision(root_hash, cfg) { + let msg = match db.get_revision(&root_hash, cfg) { Some(rev) => { revs.insert(id, rev); Some(id) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index d7077a0eaf7c..487f3c825687 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -648,7 +648,7 @@ mod tests { disk_requester.init_wal("wal", root_db_path); // create a new state cache which tracks on disk state. - let state_cache = Rc::new( + let state_cache = Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(1) @@ -664,7 +664,7 @@ mod tests { // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.id(), state_cache.inner.borrow().files.clone()); + disk_requester.reg_cached_space(state_cache.id(), state_cache.inner.read().files.clone()); // memory mapped store let mut mut_store = StoreRevMut::new(state_cache); @@ -736,7 +736,7 @@ mod tests { disk_requester.init_wal("wal", root_db_path); // create a new state cache which tracks on disk state. - let state_cache = Rc::new( + let state_cache = Arc::new( CachedSpace::new( &StoreConfig::builder() .ncached_pages(1) @@ -796,7 +796,7 @@ mod tests { // replay the redo from the wal let shared_store = StoreRevShared::from_ash( - Rc::new(ZeroStore::default()), + Arc::new(ZeroStore::default()), &ashes[0].0[&STATE_SPACE].redo, ); let view = shared_store.get_view(0, hash.len() as u64).unwrap(); @@ -819,7 +819,7 @@ mod tests { fn create_batches(rev_mut: &StoreRevMut) -> (Vec, AshRecord) { let deltas = std::mem::replace( - &mut *rev_mut.deltas.borrow_mut(), + &mut *rev_mut.deltas.write(), StoreRevMutDelta { pages: HashMap::new(), plain: Ash::new(), diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 3c4b16fbbf43..d5e853b29396 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -1,26 +1,22 @@ // TODO: try to get rid of the use `RefCell` in this file -pub mod buffer; - -use std::cell::{RefCell, RefMut}; -use std::collections::HashMap; -use std::fmt::{self, Debug}; -use std::num::NonZeroUsize; -use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; -use std::rc::Rc; -use std::sync::Arc; - -use shale::{CachedStore, CachedView, SpaceId}; - +use self::buffer::DiskBufferRequester; +use crate::file::File; use nix::fcntl::{flock, FlockArg}; +use parking_lot::RwLock; +use shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use std::{ + collections::HashMap, + fmt::{self, Debug}, + num::NonZeroUsize, + ops::{Deref, DerefMut}, + path::PathBuf, + sync::Arc, +}; use thiserror::Error; -use tokio::sync::mpsc::error::SendError; -use tokio::sync::oneshot::error::RecvError; +use tokio::sync::{mpsc::error::SendError, oneshot::error::RecvError}; use typed_builder::TypedBuilder; -use crate::file::File; - -use self::buffer::DiskBufferRequester; +pub mod buffer; pub(crate) const PAGE_SIZE_NBIT: u64 = 12; pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; @@ -48,7 +44,7 @@ impl From for StoreError { } } -pub trait MemStoreR: Debug { +pub trait MemStoreR: Debug + Send + Sync { /// Returns a slice of bytes from memory. fn get_slice(&self, offset: u64, length: u64) -> Option>; fn id(&self) -> SpaceId; @@ -90,26 +86,35 @@ pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { fn serialize(&self) -> growthring::wal::WalBytes { - let mut bytes = Vec::new(); - bytes.extend((self.0.len() as u64).to_le_bytes()); - for (space_id, w) in self.0.iter() { - bytes.extend((*space_id).to_le_bytes()); - bytes.extend( - (u32::try_from(w.undo.len()).expect("size of undo shoud be a `u32`")).to_le_bytes(), - ); + let len_bytes = (self.0.len() as u64).to_le_bytes(); + let ash_record_bytes = self + .0 + .iter() + .map(|(space_id, w)| { + let space_id_bytes = space_id.to_le_bytes(); + let undo_len = u32::try_from(w.undo.len()).expect("size of undo shoud be a `u32`"); + let ash_bytes = w.iter().flat_map(|(undo, redo)| { + let undo_data_len = u64::try_from(undo.data.len()) + .expect("length of undo data shoud be a `u64`"); + + undo.offset + .to_le_bytes() + .into_iter() + .chain(undo_data_len.to_le_bytes().into_iter()) + .chain(undo.data.iter().copied()) + .chain(redo.data.iter().copied()) + }); + + (space_id_bytes, undo_len, ash_bytes) + }) + .flat_map(|(space_id_bytes, undo_len, ash_bytes)| { + space_id_bytes + .into_iter() + .chain(undo_len.to_le_bytes().into_iter()) + .chain(ash_bytes) + }); - for (sw_undo, sw_redo) in w.iter() { - bytes.extend(sw_undo.offset.to_le_bytes()); - bytes.extend( - (u64::try_from(sw_undo.data.len()) - .expect("length of undo data shoud be a `u64`")) - .to_le_bytes(), - ); - bytes.extend(&*sw_undo.data); - bytes.extend(&*sw_redo.data); - } - } - bytes.into() + len_bytes.into_iter().chain(ash_record_bytes).collect() } } @@ -263,7 +268,7 @@ impl StoreDelta { } pub struct StoreRev { - base_space: RefCell>, + base_space: RwLock>, delta: StoreDelta, } @@ -279,7 +284,7 @@ impl fmt::Debug for StoreRev { impl MemStoreR for StoreRev { fn get_slice(&self, offset: u64, length: u64) -> Option> { - let base_space = self.base_space.borrow(); + let base_space = self.base_space.read(); let mut start = offset; let end = start + length; let delta = &self.delta; @@ -334,30 +339,30 @@ impl MemStoreR for StoreRev { } fn id(&self) -> SpaceId { - self.base_space.borrow().id() + self.base_space.read().id() } } #[derive(Clone, Debug)] -pub struct StoreRevShared(Rc); +pub struct StoreRevShared(Arc); impl StoreRevShared { - pub fn from_ash(base_space: Rc, writes: &[SpaceWrite]) -> Self { + pub fn from_ash(base_space: Arc, writes: &[SpaceWrite]) -> Self { let delta = StoreDelta::new(base_space.as_ref(), writes); - let base_space = RefCell::new(base_space); - Self(Rc::new(StoreRev { base_space, delta })) + let base_space = RwLock::new(base_space); + Self(Arc::new(StoreRev { base_space, delta })) } - pub fn from_delta(base_space: Rc, delta: StoreDelta) -> Self { - let base_space = RefCell::new(base_space); - Self(Rc::new(StoreRev { base_space, delta })) + pub fn from_delta(base_space: Arc, delta: StoreDelta) -> Self { + let base_space = RwLock::new(base_space); + Self(Arc::new(StoreRev { base_space, delta })) } - pub fn set_base_space(&mut self, base_space: Rc) { - *self.0.base_space.borrow_mut() = base_space + pub fn set_base_space(&mut self, base_space: Arc) { + *self.0.base_space.write() = base_space } - pub fn inner(&self) -> &Rc { + pub fn inner(&self) -> &Arc { &self.0 } } @@ -372,7 +377,7 @@ impl CachedStore for StoreRevShared { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(StoreShared(self.clone())) } @@ -432,37 +437,36 @@ struct StoreRevMutDelta { /// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply /// the changes. pub struct StoreRevMut { - base_space: Rc, - deltas: Rc>, + base_space: Arc, + deltas: Arc>, } impl StoreRevMut { - pub fn new(base_space: Rc) -> Self { + pub fn new(base_space: Arc) -> Self { Self { base_space, deltas: Default::default(), } } - fn get_page_mut(&self, pid: u64) -> RefMut<[u8]> { - let mut deltas = self.deltas.borrow_mut(); - if deltas.pages.get(&pid).is_none() { - let page = Box::new( + fn get_page_mut<'a>(&self, deltas: &'a mut StoreRevMutDelta, pid: u64) -> &'a mut [u8] { + let page = deltas.pages.entry(pid).or_insert_with(|| { + Box::new( self.base_space .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) .unwrap() .try_into() .unwrap(), - ); - deltas.pages.insert(pid, page); - } - RefMut::map(deltas, |e| &mut e.pages.get_mut(&pid).unwrap()[..]) + ) + }); + + page.as_mut() } pub fn take_delta(&self) -> (StoreDelta, Ash) { let mut pages = Vec::new(); let deltas = std::mem::replace( - &mut *self.deltas.borrow_mut(), + &mut *self.deltas.write(), StoreRevMutDelta { pages: HashMap::new(), plain: Ash::new(), @@ -490,7 +494,7 @@ impl CachedStore for StoreRevMut { let s_off = (offset & PAGE_MASK) as usize; let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; - let deltas = &self.deltas.borrow().pages; + let deltas = &self.deltas.read().pages; if s_pid == e_pid { match deltas.get(&s_pid) { Some(p) => p[s_off..e_off + 1].to_vec(), @@ -524,7 +528,7 @@ impl CachedStore for StoreRevMut { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(StoreShared(self.clone())) } @@ -539,33 +543,36 @@ impl CachedStore for StoreRevMut { let redo: Box<[u8]> = change.into(); if s_pid == e_pid { - let slice = &mut self.get_page_mut(s_pid)[s_off..e_off + 1]; + let mut deltas = self.deltas.write(); + let slice = &mut self.get_page_mut(deltas.deref_mut(), s_pid)[s_off..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change) } else { let len = PAGE_SIZE as usize - s_off; { - let slice = &mut self.get_page_mut(s_pid)[s_off..]; + let mut deltas = self.deltas.write(); + let slice = &mut self.get_page_mut(deltas.deref_mut(), s_pid)[s_off..]; undo.extend(&*slice); slice.copy_from_slice(&change[..len]); } change = &change[len..]; + let mut deltas = self.deltas.write(); for p in s_pid + 1..e_pid { - let mut slice = self.get_page_mut(p); + let slice = self.get_page_mut(deltas.deref_mut(), p); undo.extend(&*slice); slice.copy_from_slice(&change[..PAGE_SIZE as usize]); change = &change[PAGE_SIZE as usize..]; } - let slice = &mut self.get_page_mut(e_pid)[..e_off + 1]; + let slice = &mut self.get_page_mut(deltas.deref_mut(), e_pid)[..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change); } - let plain = &mut self.deltas.borrow_mut().plain; + let plain = &mut self.deltas.write().plain; assert!(undo.len() == redo.len()); plain.undo.push(SpaceWrite { offset, @@ -581,11 +588,11 @@ impl CachedStore for StoreRevMut { #[derive(Clone, Debug)] /// A zero-filled in memory store which can serve as a plain base to overlay deltas on top. -pub struct ZeroStore(Rc<()>); +pub struct ZeroStore(Arc<()>); impl Default for ZeroStore { fn default() -> Self { - Self(Rc::new(())) + Self(Arc::new(())) } } @@ -620,7 +627,7 @@ fn test_from_ash() { println!("[0x{l:x}, 0x{r:x})"); writes.push(SpaceWrite { offset: l, data }); } - let z = Rc::new(ZeroStore::default()); + let z = Arc::new(ZeroStore::default()); let rev = StoreRevShared::from_ash(z, &writes); println!("{rev:?}"); assert_eq!( @@ -658,7 +665,7 @@ struct CachedSpaceInner { #[derive(Clone, Debug)] pub struct CachedSpace { - inner: Rc>, + inner: Arc>, space_id: SpaceId, } @@ -670,7 +677,7 @@ impl CachedSpace { let space_id = cfg.space_id; let files = Arc::new(FilePool::new(cfg)?); Ok(Self { - inner: Rc::new(RefCell::new(CachedSpaceInner { + inner: Arc::new(RwLock::new(CachedSpaceInner { cached_pages: lru::LruCache::new( NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size"), ), @@ -683,14 +690,14 @@ impl CachedSpace { } pub fn clone_files(&self) -> Arc { - self.inner.borrow().files.clone() + self.inner.read().files.clone() } /// Apply `delta` to the store and return the StoreDelta that can undo this change. pub fn update(&self, delta: &StoreDelta) -> Option { let mut pages = Vec::new(); for DeltaPage(pid, page) in &delta.0 { - let data = self.inner.borrow_mut().pin_page(self.space_id, *pid).ok()?; + let data = self.inner.write().pin_page(self.space_id, *pid).ok()?; // save the original data pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap()))); // apply the change @@ -788,11 +795,7 @@ impl PageRef { fn new(pid: u64, store: &CachedSpace) -> Option { Some(Self { pid, - data: store - .inner - .borrow_mut() - .pin_page(store.space_id, pid) - .ok()?, + data: store.inner.write().pin_page(store.space_id, pid).ok()?, store: store.clone(), }) } @@ -800,7 +803,7 @@ impl PageRef { impl Drop for PageRef { fn drop(&mut self) { - self.store.inner.borrow_mut().unpin_page(self.pid); + self.store.inner.write().unpin_page(self.pid); } } @@ -860,17 +863,18 @@ impl FilePool { fn get_file(&self, fid: u64) -> Result, StoreError> { let mut files = self.files.lock(); - let file_size = 1 << self.file_nbit; - Ok(match files.get(&fid) { + + let file = match files.get(&fid) { Some(f) => f.clone(), None => { - files.put( - fid, - Arc::new(File::new(fid, file_size, self.rootdir.clone())?), - ); - files.peek(&fid).unwrap().clone() + let file_size = 1 << self.file_nbit; + let file = Arc::new(File::new(fid, file_size, &self.rootdir)?); + files.put(fid, file.clone()); + file } - }) + }; + + Ok(file) } fn get_file_nbit(&self) -> u64 { diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 8fdf354f82ad..8970957852b4 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,7 +1,9 @@ use firewood::{ db::{Db as PersistedDb, DbConfig, DbError, WalConfig}, - merkle::TrieHash, + merkle::{Node, TrieHash}, + storage::StoreRevMut, }; +use firewood_shale::compact::CompactSpace; use std::{ collections::VecDeque, fs::remove_dir_all, @@ -18,7 +20,9 @@ macro_rules! kv_dump { }}; } -struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); +type Store = CompactSpace; + +struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); impl<'a, P: AsRef + ?Sized> Db<'a, P> { fn new(path: &'a P, cfg: &DbConfig) -> Result { @@ -119,7 +123,7 @@ fn test_revisions() { dumped .iter() .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(hash, None).unwrap())) + .map(|(data, hash)| (data, db.get_revision(&hash, None).unwrap())) .map(|(data, rev)| (data, kv_dump!(rev))) .for_each(|(b, a)| { if &a != b { @@ -133,7 +137,7 @@ fn test_revisions() { dumped .iter() .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(hash, None).unwrap())) + .map(|(data, hash)| (data, db.get_revision(&hash, None).unwrap())) .map(|(data, rev)| (data, kv_dump!(rev))) .for_each(|(b, a)| { if &a != b { @@ -188,7 +192,7 @@ fn create_db_issue_proof() { } wb.commit(); - let rev = db.get_revision(root_hash, None).unwrap(); + let rev = db.get_revision(&root_hash, None).unwrap(); let key = "doe".as_bytes(); let root_hash = rev.kv_root_hash(); @@ -211,7 +215,7 @@ fn create_db_issue_proof() { } impl + ?Sized> Deref for Db<'_, P> { - type Target = PersistedDb; + type Target = PersistedDb; fn deref(&self) -> &Self::Target { &self.0 @@ -219,7 +223,7 @@ impl + ?Sized> Deref for Db<'_, P> { } impl + ?Sized> DerefMut for Db<'_, P> { - fn deref_mut(&mut self) -> &mut PersistedDb { + fn deref_mut(&mut self) -> &mut PersistedDb { &mut self.0 } } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index e874b8a78a98..7830005574ae 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,17 +1,19 @@ use firewood::{ + merkle::Node, merkle_util::*, proof::{Proof, ProofError}, }; - +use firewood_shale::{cached::DynamicMem, compact::CompactSpace}; +use rand::Rng; use std::collections::HashMap; -use rand::Rng; +type Store = CompactSpace; fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( items: Vec<(K, V)>, meta_size: u64, compact_size: u64, -) -> Result { +) -> Result, DataStoreError> { let mut merkle = new_merkle(meta_size, compact_size); for (k, v) in items.iter() { merkle.insert(k, v.as_ref().to_vec())?; diff --git a/shale-archive/Cargo.toml b/shale-archive/Cargo.toml new file mode 100644 index 000000000000..01850e9578ed --- /dev/null +++ b/shale-archive/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "shale-archive" +version = "0.0.3" +edition = "2021" +description = "Useful abstraction and light-weight implemenation for a key-value store." +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex = "0.4.3" +lru = "0.10.0" +thiserror = "1.0.38" + +[dev-dependencies] +criterion = { version = "0.5.1", features = ["html_reports"] } +pprof = { version = "0.11.1", features = ["flamegraph"] } +sha3 = "0.10.7" +rand = "0.8.5" + +[[bench]] +name = "shale-bench" +harness = false diff --git a/shale-archive/LICENSE b/shale-archive/LICENSE new file mode 100644 index 000000000000..81a1d4cf1dfb --- /dev/null +++ b/shale-archive/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019 Maofan (Ted) Yin +Copyright (c) 2019 Cornell University + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shale-archive/benches/shale-bench.rs b/shale-archive/benches/shale-bench.rs new file mode 100644 index 000000000000..01db3f6dd87c --- /dev/null +++ b/shale-archive/benches/shale-bench.rs @@ -0,0 +1,99 @@ +use shale_archive as shale; + +use criterion::{ + black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, +}; +use pprof::ProfilerGuard; +use rand::Rng; +use shale::{ + cached::{DynamicMem, PlainMem}, + compact::CompactSpaceHeader, + CachedStore, ObjPtr, StoredView, +}; +use std::{fs::File, os::raw::c_int, path::Path}; + +const BENCH_MEM_SIZE: u64 = 2_000_000; + +// To enable flamegraph output +// cargo bench --bench shale-bench -- --profile-time=N +pub struct FlamegraphProfiler<'a> { + frequency: c_int, + active_profiler: Option>, +} + +impl<'a> FlamegraphProfiler<'a> { + #[allow(dead_code)] + pub fn new(frequency: c_int) -> Self { + FlamegraphProfiler { + frequency, + active_profiler: None, + } + } +} + +impl<'a> Profiler for FlamegraphProfiler<'a> { + fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { + self.active_profiler = Some(ProfilerGuard::new(self.frequency).unwrap()); + } + + fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { + std::fs::create_dir_all(benchmark_dir).unwrap(); + let flamegraph_path = benchmark_dir.join("flamegraph.svg"); + let flamegraph_file = + File::create(flamegraph_path).expect("File system error while creating flamegraph.svg"); + if let Some(profiler) = self.active_profiler.take() { + profiler + .report() + .build() + .unwrap() + .flamegraph(flamegraph_file) + .expect("Error writing flamegraph"); + } + } +} + +fn get_view(b: &mut Bencher, mut cached: C) { + let mut rng = rand::thread_rng(); + + b.iter(|| { + let len = rng.gen_range(0..26); + let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); + + let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); + + cached.write(offset, rdata); + let view = cached + .get_view(offset, rdata.len().try_into().unwrap()) + .unwrap(); + + serialize(&cached); + assert_eq!(view.as_deref(), rdata); + }); +} + +fn serialize(m: &T) { + let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); + let _compact_header = + StoredView::ptr_to_obj(m, compact_header_obj, shale::compact::CompactHeader::MSIZE) + .unwrap(); +} + +fn bench_cursors(c: &mut Criterion) { + let mut group = c.benchmark_group("shale-bench"); + group.bench_function("PlainMem", |b| { + let mem = PlainMem::new(BENCH_MEM_SIZE, 0); + get_view(b, mem) + }); + group.bench_function("DynamicMem", |b| { + let mem = DynamicMem::new(BENCH_MEM_SIZE, 0); + get_view(b, mem) + }); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(FlamegraphProfiler::new(100)); + targets = bench_cursors +} + +criterion_main!(benches); diff --git a/shale-archive/src/cached.rs b/shale-archive/src/cached.rs new file mode 100644 index 000000000000..0eea8c3dff36 --- /dev/null +++ b/shale-archive/src/cached.rs @@ -0,0 +1,251 @@ +use std::borrow::BorrowMut; +use std::cell::{RefCell, UnsafeCell}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +use crate::{CachedStore, CachedView, SpaceId}; + +/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying +/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write +/// your own [CachedStore] implementation. +#[derive(Debug)] +pub struct PlainMem { + space: Rc>>, + id: SpaceId, +} + +impl PlainMem { + pub fn new(size: u64, id: SpaceId) -> Self { + let mut space: Vec = Vec::new(); + space.resize(size as usize, 0); + let space = Rc::new(RefCell::new(space)); + Self { space, id } + } +} + +impl CachedStore for PlainMem { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { + let offset = offset as usize; + let length = length as usize; + if offset + length > self.space.borrow().len() { + None + } else { + Some(Box::new(PlainMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + } + + fn get_shared(&self) -> Box> { + Box::new(PlainMemShared(Self { + space: self.space.clone(), + id: self.id, + })) + } + + fn write(&mut self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + let mut vect = self.space.deref().borrow_mut(); + vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); + } + + fn id(&self) -> SpaceId { + self.id + } +} + +#[derive(Debug)] +struct PlainMemView { + offset: usize, + length: usize, + mem: PlainMem, +} + +struct PlainMemShared(PlainMem); + +impl DerefMut for PlainMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.borrow_mut() + } +} + +impl Deref for PlainMemShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { + &self.0 + } +} + +impl CachedView for PlainMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() + } +} + +// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to +/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is +/// not enough. +#[derive(Debug)] +pub struct DynamicMem { + space: Rc>>, + id: SpaceId, +} + +impl DynamicMem { + pub fn new(size: u64, id: SpaceId) -> Self { + let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); + Self { space, id } + } + + #[allow(clippy::mut_from_ref)] + // TODO: Refactor this usage. + fn get_space_mut(&self) -> &mut Vec { + unsafe { &mut *self.space.get() } + } +} + +impl CachedStore for DynamicMem { + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>> { + let offset = offset as usize; + let length = length as usize; + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size, 0); + } + Some(Box::new(DynamicMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + + fn get_shared(&self) -> Box> { + Box::new(DynamicMemShared(Self { + space: self.space.clone(), + id: self.id, + })) + } + + fn write(&mut self, offset: u64, change: &[u8]) { + let offset = offset as usize; + let length = change.len(); + let size = offset + length; + // Increase the size if the request range exceeds the current limit. + if size > self.get_space_mut().len() { + self.get_space_mut().resize(size, 0); + } + self.get_space_mut()[offset..offset + length].copy_from_slice(change) + } + + fn id(&self) -> SpaceId { + self.id + } +} + +#[derive(Debug)] +struct DynamicMemView { + offset: usize, + length: usize, + mem: DynamicMem, +} + +struct DynamicMemShared(DynamicMem); + +impl Deref for DynamicMemView { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.mem.get_space_mut()[self.offset..self.offset + self.length] + } +} + +impl Deref for DynamicMemShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { + &self.0 + } +} + +impl DerefMut for DynamicMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl CachedView for DynamicMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plain_mem() { + let mut view = PlainMemShared(PlainMem::new(2, 0)); + let mem = view.deref_mut(); + mem.write(0, &[1, 1]); + mem.write(0, &[1, 2]); + let r = mem.get_view(0, 2).unwrap().as_deref(); + assert_eq!(r, [1, 2]); + + // previous view not mutated by write + mem.write(0, &[1, 3]); + assert_eq!(r, [1, 2]); + let r = mem.get_view(0, 2).unwrap().as_deref(); + assert_eq!(r, [1, 3]); + + // create a view larger than capacity + assert!(mem.get_view(0, 4).is_none()) + } + + #[test] + #[should_panic(expected = "index 3 out of range for slice of length 2")] + fn test_plain_mem_panic() { + let mut view = PlainMemShared(PlainMem::new(2, 0)); + let mem = view.deref_mut(); + + // out of range + mem.write(1, &[7, 8]); + } + + #[test] + fn test_dynamic_mem() { + let mut view = DynamicMemShared(DynamicMem::new(2, 0)); + let mem = view.deref_mut(); + mem.write(0, &[1, 2]); + mem.write(0, &[3, 4]); + assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); + mem.get_shared().write(0, &[5, 6]); + + // capacity is increased + mem.write(5, &[0; 10]); + + // get a view larger than recent growth + assert_eq!(mem.get_view(3, 20).unwrap().as_deref(), [0; 20]); + } +} diff --git a/shale-archive/src/compact.rs b/shale-archive/src/compact.rs new file mode 100644 index 000000000000..2078adcd6d6b --- /dev/null +++ b/shale-archive/src/compact.rs @@ -0,0 +1,759 @@ +use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; +use std::cell::UnsafeCell; +use std::fmt::Debug; +use std::io::{Cursor, Write}; +use std::rc::Rc; + +#[derive(Debug)] +pub struct CompactHeader { + payload_size: u64, + is_freed: bool, + desc_addr: ObjPtr, +} + +impl CompactHeader { + pub const MSIZE: u64 = 17; + pub fn is_freed(&self) -> bool { + self.is_freed + } + + pub fn payload_size(&self) -> u64 { + self.payload_size + } +} + +impl Storable for CompactHeader { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let payload_size = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let is_freed = raw.as_deref()[8] != 0; + let desc_addr = + u64::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); + Ok(Self { + payload_size, + is_freed, + desc_addr: ObjPtr::new(desc_addr), + }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + let mut cur = Cursor::new(to); + cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; + cur.write_all(&self.desc_addr.addr().to_le_bytes())?; + Ok(()) + } +} + +#[derive(Debug)] +struct CompactFooter { + payload_size: u64, +} + +impl CompactFooter { + const MSIZE: u64 = 8; +} + +impl Storable for CompactFooter { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); + Ok(Self { payload_size }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.payload_size.to_le_bytes())?; + Ok(()) + } +} + +#[derive(Clone, Copy, Debug)] +struct CompactDescriptor { + payload_size: u64, + haddr: u64, // pointer to the payload of freed space +} + +impl CompactDescriptor { + const MSIZE: u64 = 16; +} + +impl Storable for CompactDescriptor { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let payload_size = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); + Ok(Self { + payload_size, + haddr, + }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + let mut cur = Cursor::new(to); + cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&self.haddr.to_le_bytes())?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct CompactSpaceHeader { + meta_space_tail: u64, + compact_space_tail: u64, + base_addr: ObjPtr, + alloc_addr: ObjPtr, +} + +struct CompactSpaceHeaderSliced { + meta_space_tail: Obj, + compact_space_tail: Obj, + base_addr: Obj>, + alloc_addr: Obj>, +} + +impl CompactSpaceHeaderSliced { + fn flush_dirty(&mut self) { + self.meta_space_tail.flush_dirty(); + self.compact_space_tail.flush_dirty(); + self.base_addr.flush_dirty(); + self.alloc_addr.flush_dirty(); + } +} + +impl CompactSpaceHeader { + pub const MSIZE: u64 = 32; + + pub fn new(meta_base: u64, compact_base: u64) -> Self { + Self { + meta_space_tail: meta_base, + compact_space_tail: compact_base, + base_addr: ObjPtr::new_from_addr(meta_base), + alloc_addr: ObjPtr::new_from_addr(meta_base), + } + } + + fn into_fields(r: Obj) -> Result { + Ok(CompactSpaceHeaderSliced { + meta_space_tail: StoredView::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, + compact_space_tail: StoredView::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, + base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, + alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, + }) + } +} + +impl Storable for CompactSpaceHeader { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let meta_space_tail = + u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let compact_space_tail = + u64::from_le_bytes(raw.as_deref()[8..16].try_into().expect("invalid slice")); + let base_addr = + u64::from_le_bytes(raw.as_deref()[16..24].try_into().expect("invalid slice")); + let alloc_addr = + u64::from_le_bytes(raw.as_deref()[24..].try_into().expect("invalid slice")); + Ok(Self { + meta_space_tail, + compact_space_tail, + base_addr: ObjPtr::new_from_addr(base_addr), + alloc_addr: ObjPtr::new_from_addr(alloc_addr), + }) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + let mut cur = Cursor::new(to); + cur.write_all(&self.meta_space_tail.to_le_bytes())?; + cur.write_all(&self.compact_space_tail.to_le_bytes())?; + cur.write_all(&self.base_addr.addr().to_le_bytes())?; + cur.write_all(&self.alloc_addr.addr().to_le_bytes())?; + Ok(()) + } +} + +struct ObjPtrField(ObjPtr); + +impl ObjPtrField { + const MSIZE: u64 = 8; +} + +impl Storable for ObjPtrField { + fn hydrate(addr: u64, mem: &U) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let obj_ptr = ObjPtr::new_from_addr(u64::from_le_bytes( + <[u8; 8]>::try_from(&raw.as_deref()[0..8]).expect("invalid slice"), + )); + Ok(Self(obj_ptr)) + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0.addr().to_le_bytes())?; + Ok(()) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } +} + +impl std::ops::Deref for ObjPtrField { + type Target = ObjPtr; + fn deref(&self) -> &ObjPtr { + &self.0 + } +} + +impl std::ops::DerefMut for ObjPtrField { + fn deref_mut(&mut self) -> &mut ObjPtr { + &mut self.0 + } +} + +#[derive(Debug)] +struct U64Field(u64); + +impl U64Field { + const MSIZE: u64 = 8; +} + +impl Storable for U64Field { + fn hydrate(addr: u64, mem: &U) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0.to_le_bytes())?; + Ok(()) + } +} + +impl std::ops::Deref for U64Field { + type Target = u64; + fn deref(&self) -> &u64 { + &self.0 + } +} + +impl std::ops::DerefMut for U64Field { + fn deref_mut(&mut self) -> &mut u64 { + &mut self.0 + } +} + +struct CompactSpaceInner { + meta_space: Rc, + compact_space: Rc, + header: CompactSpaceHeaderSliced, + obj_cache: super::ObjCache, + alloc_max_walk: u64, + regn_nbit: u64, +} + +impl CompactSpaceInner { + fn get_descriptor( + &self, + ptr: ObjPtr, + ) -> Result, ShaleError> { + StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) + } + + fn get_data_ref( + &self, + ptr: ObjPtr, + len_limit: u64, + ) -> Result, ShaleError> { + StoredView::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) + } + + fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { + self.get_data_ref::(ptr, CompactHeader::MSIZE) + } + + fn get_footer(&self, ptr: ObjPtr) -> Result, ShaleError> { + self.get_data_ref::(ptr, CompactFooter::MSIZE) + } + + fn del_desc(&mut self, desc_addr: ObjPtr) -> Result<(), ShaleError> { + let desc_size = CompactDescriptor::MSIZE; + debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); + self.header + .meta_space_tail + .write(|r| **r -= desc_size) + .unwrap(); + + if desc_addr.addr != **self.header.meta_space_tail { + let desc_last = + self.get_descriptor(ObjPtr::new_from_addr(**self.header.meta_space_tail))?; + let mut desc = self.get_descriptor(ObjPtr::new_from_addr(desc_addr.addr))?; + desc.write(|r| *r = *desc_last).unwrap(); + let mut header = self.get_header(ObjPtr::new(desc.haddr))?; + header.write(|h| h.desc_addr = desc_addr).unwrap(); + } + + Ok(()) + } + + fn new_desc(&mut self) -> Result, ShaleError> { + let addr = **self.header.meta_space_tail; + self.header + .meta_space_tail + .write(|r| **r += CompactDescriptor::MSIZE) + .unwrap(); + + Ok(ObjPtr::new_from_addr(addr)) + } + + fn free(&mut self, addr: u64) -> Result<(), ShaleError> { + let hsize = CompactHeader::MSIZE; + let fsize = CompactFooter::MSIZE; + let regn_size = 1 << self.regn_nbit; + + let mut offset = addr - hsize; + let header_payload_size = { + let header = self.get_header(ObjPtr::new(offset))?; + assert!(!header.is_freed); + header.payload_size + }; + let mut h = offset; + let mut payload_size = header_payload_size; + + if offset & (regn_size - 1) > 0 { + // merge with lower data segment + offset -= fsize; + let (pheader_is_freed, pheader_payload_size, pheader_desc_addr) = { + let pfooter = self.get_footer(ObjPtr::new(offset))?; + offset -= pfooter.payload_size + hsize; + let pheader = self.get_header(ObjPtr::new(offset))?; + (pheader.is_freed, pheader.payload_size, pheader.desc_addr) + }; + if pheader_is_freed { + h = offset; + payload_size += hsize + fsize + pheader_payload_size; + self.del_desc(pheader_desc_addr)?; + } + } + + offset = addr + header_payload_size; + let mut f = offset; + + if offset + fsize < **self.header.compact_space_tail + && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize + { + // merge with higher data segment + offset += fsize; + let (nheader_is_freed, nheader_payload_size, nheader_desc_addr) = { + let nheader = self.get_header(ObjPtr::new(offset))?; + (nheader.is_freed, nheader.payload_size, nheader.desc_addr) + }; + if nheader_is_freed { + offset += hsize + nheader_payload_size; + f = offset; + { + let nfooter = self.get_footer(ObjPtr::new(offset))?; + assert!(nheader_payload_size == nfooter.payload_size); + } + payload_size += hsize + fsize + nheader_payload_size; + self.del_desc(nheader_desc_addr)?; + } + } + + let desc_addr = self.new_desc()?; + { + let mut desc = self.get_descriptor(desc_addr)?; + desc.write(|d| { + d.payload_size = payload_size; + d.haddr = h; + }) + .unwrap(); + } + let mut h = self.get_header(ObjPtr::new(h))?; + let mut f = self.get_footer(ObjPtr::new(f))?; + h.write(|h| { + h.payload_size = payload_size; + h.is_freed = true; + h.desc_addr = desc_addr; + }) + .unwrap(); + f.write(|f| f.payload_size = payload_size).unwrap(); + + Ok(()) + } + + fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { + let tail = **self.header.meta_space_tail; + if tail == self.header.base_addr.addr { + return Ok(None); + } + + let hsize = CompactHeader::MSIZE; + let fsize = CompactFooter::MSIZE; + let dsize = CompactDescriptor::MSIZE; + + let mut old_alloc_addr = *self.header.alloc_addr; + + if old_alloc_addr.addr >= tail { + old_alloc_addr = *self.header.base_addr; + } + + let mut ptr = old_alloc_addr; + let mut res: Option = None; + for _ in 0..self.alloc_max_walk { + assert!(ptr.addr < tail); + let (desc_payload_size, desc_haddr) = { + let desc = self.get_descriptor(ptr)?; + (desc.payload_size, desc.haddr) + }; + let exit = if desc_payload_size == length { + // perfect match + { + let mut header = self.get_header(ObjPtr::new(desc_haddr))?; + assert_eq!(header.payload_size, desc_payload_size); + assert!(header.is_freed); + header.write(|h| h.is_freed = false).unwrap(); + } + self.del_desc(ptr)?; + true + } else if desc_payload_size > length + hsize + fsize { + // able to split + { + let mut lheader = self.get_header(ObjPtr::new(desc_haddr))?; + assert_eq!(lheader.payload_size, desc_payload_size); + assert!(lheader.is_freed); + lheader + .write(|h| { + h.is_freed = false; + h.payload_size = length; + }) + .unwrap(); + } + { + let mut lfooter = self.get_footer(ObjPtr::new(desc_haddr + hsize + length))?; + //assert!(lfooter.payload_size == desc_payload_size); + lfooter.write(|f| f.payload_size = length).unwrap(); + } + + let offset = desc_haddr + hsize + length + fsize; + let rpayload_size = desc_payload_size - length - fsize - hsize; + let rdesc_addr = self.new_desc()?; + { + let mut rdesc = self.get_descriptor(rdesc_addr)?; + rdesc + .write(|rd| { + rd.payload_size = rpayload_size; + rd.haddr = offset; + }) + .unwrap(); + } + { + let mut rheader = self.get_header(ObjPtr::new(offset))?; + rheader + .write(|rh| { + rh.is_freed = true; + rh.payload_size = rpayload_size; + rh.desc_addr = rdesc_addr; + }) + .unwrap(); + } + { + let mut rfooter = + self.get_footer(ObjPtr::new(offset + hsize + rpayload_size))?; + rfooter.write(|f| f.payload_size = rpayload_size).unwrap(); + } + self.del_desc(ptr)?; + true + } else { + false + }; + if exit { + self.header.alloc_addr.write(|r| *r = ptr).unwrap(); + res = Some(desc_haddr + hsize); + break; + } + ptr.addr += dsize; + if ptr.addr >= tail { + ptr = *self.header.base_addr; + } + if ptr == old_alloc_addr { + break; + } + } + Ok(res) + } + + fn alloc_new(&mut self, length: u64) -> Result { + let regn_size = 1 << self.regn_nbit; + let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; + let mut offset = **self.header.compact_space_tail; + self.header + .compact_space_tail + .write(|r| { + // an item is always fully in one region + let rem = regn_size - (offset & (regn_size - 1)); + if rem < total_length { + offset += rem; + **r += rem; + } + **r += total_length + }) + .unwrap(); + let mut h = self.get_header(ObjPtr::new(offset))?; + let mut f = self.get_footer(ObjPtr::new(offset + CompactHeader::MSIZE + length))?; + h.write(|h| { + h.payload_size = length; + h.is_freed = false; + h.desc_addr = ObjPtr::new(0); + }) + .unwrap(); + f.write(|f| f.payload_size = length).unwrap(); + Ok(offset + CompactHeader::MSIZE) + } +} + +#[derive(Debug)] +pub struct CompactSpace { + inner: UnsafeCell>, +} + +impl CompactSpace { + pub fn new( + meta_space: Rc, + compact_space: Rc, + header: Obj, + obj_cache: super::ObjCache, + alloc_max_walk: u64, + regn_nbit: u64, + ) -> Result { + let cs = CompactSpace { + inner: UnsafeCell::new(CompactSpaceInner { + meta_space, + compact_space, + header: CompactSpaceHeader::into_fields(header)?, + obj_cache, + alloc_max_walk, + regn_nbit, + }), + }; + Ok(cs) + } +} + +impl ShaleStore + for CompactSpace +{ + fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { + let size = item.dehydrated_len() + extra; + let inner = unsafe { &mut *self.inner.get() }; + let ptr: ObjPtr = + ObjPtr::new_from_addr(if let Some(addr) = inner.alloc_from_freed(size)? { + addr + } else { + inner.alloc_new(size)? + }); + let mut u = inner.obj_cache.put(StoredView::item_to_obj( + inner.compact_space.as_ref(), + ptr.addr(), + size, + item, + )?); + u.write(|_| {}).unwrap(); + Ok(u) + } + + fn free_item(&mut self, ptr: ObjPtr) -> Result<(), ShaleError> { + let inner = self.inner.get_mut(); + inner.obj_cache.pop(ptr); + inner.free(ptr.addr()) + } + + fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError> { + let inner = unsafe { &*self.inner.get() }; + if let Some(r) = inner.obj_cache.get(ptr)? { + return Ok(r); + } + if ptr.addr() < CompactSpaceHeader::MSIZE { + return Err(ShaleError::InvalidAddressLength { + expected: CompactSpaceHeader::MSIZE, + found: ptr.addr(), + }); + } + let h = inner.get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))?; + Ok(inner + .obj_cache + .put(inner.get_data_ref(ptr, h.payload_size)?)) + } + + fn flush_dirty(&self) -> Option<()> { + let inner = unsafe { &mut *self.inner.get() }; + inner.header.flush_dirty(); + inner.obj_cache.flush_dirty() + } +} + +#[cfg(test)] +mod tests { + use sha3::Digest; + + use crate::{cached::DynamicMem, ObjCache}; + + use super::*; + + const HASH_SIZE: usize = 32; + const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); + + #[derive(PartialEq, Eq, Debug, Clone)] + pub struct Hash(pub [u8; HASH_SIZE]); + + impl Hash { + const MSIZE: u64 = 32; + } + + impl std::ops::Deref for Hash { + type Target = [u8; HASH_SIZE]; + fn deref(&self) -> &[u8; HASH_SIZE] { + &self.0 + } + } + + impl Storable for Hash { + fn hydrate(addr: u64, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + Ok(Self( + raw.as_deref()[..Self::MSIZE as usize] + .try_into() + .expect("invalid slice"), + )) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + let mut cur = to; + cur.write_all(&self.0)?; + Ok(()) + } + } + + #[test] + fn test_space_item() { + const META_SIZE: u64 = 0x10000; + const COMPACT_SIZE: u64 = 0x10000; + const RESERVED: u64 = 0x1000; + + let mut dm = DynamicMem::new(META_SIZE, 0x0); + + // initialize compact space + let compact_header: ObjPtr = ObjPtr::new_from_addr(0x1); + dm.write( + compact_header.addr(), + &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)).unwrap(), + ); + let compact_header = + StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); + let mem_meta = Rc::new(dm); + let mem_payload = Rc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); + + let cache: ObjCache = ObjCache::new(1); + let space = + CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + + // initial write + let data = b"hello world"; + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); + let obj_ref = space.put_item(Hash(hash), 0).unwrap(); + assert_eq!(obj_ref.as_ptr().addr(), 4113); + // create hash ptr from address and attempt to read dirty write. + let hash_ref = space.get_item(ObjPtr::new_from_addr(4113)).unwrap(); + // read before flush results in zeroed hash + assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); + // not cached + assert!(obj_ref + .cache + .get_inner_mut() + .cached + .get(&ObjPtr::new_from_addr(4113)) + .is_none()); + // pinned + assert!(obj_ref + .cache + .get_inner_mut() + .pinned + .get(&ObjPtr::new_from_addr(4113)) + .is_some()); + // dirty + assert!(obj_ref + .cache + .get_inner_mut() + .dirty + .get(&ObjPtr::new_from_addr(4113)) + .is_some()); + drop(obj_ref); + // write is visible + assert_eq!( + space + .get_item(ObjPtr::new_from_addr(4113)) + .unwrap() + .as_ref(), + hash + ); + } +} diff --git a/shale-archive/src/lib.rs b/shale-archive/src/lib.rs new file mode 100644 index 000000000000..d0c01a039ee3 --- /dev/null +++ b/shale-archive/src/lib.rs @@ -0,0 +1,599 @@ +use std::any::type_name; +use std::cell::UnsafeCell; +use std::collections::{HashMap, HashSet}; +use std::fmt::{self, Debug, Display, Formatter}; +use std::hash::Hash; +use std::hash::Hasher; +use std::marker::PhantomData; +use std::num::NonZeroUsize; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +use thiserror::Error; + +pub mod cached; +pub mod compact; +pub mod util; + +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum ShaleError { + #[error("obj invalid: {addr:?} obj: {obj_type:?} error: {error:?}")] + InvalidObj { + addr: u64, + obj_type: &'static str, + error: &'static str, + }, + #[error("invalid address length expected: {expected:?} found: {found:?})")] + InvalidAddressLength { expected: u64, found: u64 }, + #[error("invalid node type")] + InvalidNodeType, + #[error("failed to create view: offset: {offset:?} size: {size:?}")] + InvalidCacheView { offset: u64, size: u64 }, + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} + +// TODO: +// this could probably included with ShaleError, +// but keeping it separate for now as Obj/ObjRef might change in the near future +#[derive(Debug, Error)] +#[error("write error")] +pub struct ObjWriteError; + +pub type SpaceId = u8; +pub const INVALID_SPACE_ID: SpaceId = 0xff; + +pub struct DiskWrite { + pub space_id: SpaceId, + pub space_off: u64, + pub data: Box<[u8]>, +} + +impl std::fmt::Debug for DiskWrite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "[Disk space=0x{:02x} offset=0x{:04x} data=0x{}", + self.space_id, + self.space_off, + hex::encode(&self.data) + ) + } +} + +/// A handle that pins and provides a readable access to a portion of the linear memory image. +pub trait CachedView { + type DerefReturn: Deref; + fn as_deref(&self) -> Self::DerefReturn; +} + +/// In-memory store that offers access to intervals from a linear byte space, which is usually +/// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear +/// persistent store. Reads could trigger disk reads to bring data into memory, but writes will +/// *only* be visible in memory (it does not write back to the disk). +pub trait CachedStore: Debug { + /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them + /// directly accessible. + fn get_view( + &self, + offset: u64, + length: u64, + ) -> Option>>>; + /// Returns a handle that allows shared access to the store. + fn get_shared(&self) -> Box>; + /// Write the `change` to the portion of the linear space starting at `offset`. The change + /// should be immediately visible to all `CachedView` associated to this linear space. + fn write(&mut self, offset: u64, change: &[u8]); + /// Returns the identifier of this storage space. + fn id(&self) -> SpaceId; +} + +/// Opaque typed pointer in the 64-bit virtual addressable space. +#[repr(C)] +#[derive(Debug)] +pub struct ObjPtr { + pub(crate) addr: u64, + phantom: PhantomData, +} + +impl std::cmp::PartialEq for ObjPtr { + fn eq(&self, other: &ObjPtr) -> bool { + self.addr == other.addr + } +} + +impl Eq for ObjPtr {} +impl Copy for ObjPtr {} +impl Clone for ObjPtr { + fn clone(&self) -> Self { + *self + } +} + +impl Hash for ObjPtr { + fn hash(&self, state: &mut H) { + self.addr.hash(state) + } +} + +impl Display for ObjPtr { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[ObjPtr addr={:08x}]", self.addr) + } +} + +impl ObjPtr { + pub fn null() -> Self { + Self::new(0) + } + pub fn is_null(&self) -> bool { + self.addr == 0 + } + pub fn addr(&self) -> u64 { + self.addr + } + + #[inline(always)] + pub(crate) fn new(addr: u64) -> Self { + ObjPtr { + addr, + phantom: PhantomData, + } + } + + #[inline(always)] + pub fn new_from_addr(addr: u64) -> Self { + Self::new(addr) + } +} + +/// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object +/// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to +/// turn a `TypedView` into an [ObjRef]. +pub trait TypedView: std::fmt::Debug + Deref { + /// Get the offset of the initial byte in the linear space. + fn get_offset(&self) -> u64; + /// Access it as a [CachedStore] object. + fn get_mem_store(&self) -> &dyn CachedStore; + /// Access it as a mutable CachedStore object + fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore; + /// Estimate the serialized length of the current type content. It should not be smaller than + /// the actually length. + fn estimate_mem_image(&self) -> Option; + /// Serialize the type content to the memory image. It defines how the current in-memory object + /// of `T` should be represented in the linear storage space. + fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError>; + /// Gain mutable access to the typed content. By changing it, its serialized bytes (and length) + /// could change. + fn write(&mut self) -> &mut T; + /// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto + /// reflected in the underlying [CachedStore]). + fn is_mem_mapped(&self) -> bool; +} + +/// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] +/// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. +/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] +/// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. +#[derive(Debug)] +pub struct Obj { + value: Box>, + dirty: Option, +} + +impl Obj { + #[inline(always)] + pub fn as_ptr(&self) -> ObjPtr { + ObjPtr::::new(self.value.get_offset()) + } +} + +impl Obj { + /// Write to the underlying object. Returns `Some(())` on success. + #[inline] + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { + modify(self.value.write()); + + // if `estimate_mem_image` gives overflow, the object will not be written + self.dirty = match self.value.estimate_mem_image() { + Some(len) => Some(len), + None => return Err(ObjWriteError), + }; + + Ok(()) + } + + #[inline(always)] + pub fn get_space_id(&self) -> SpaceId { + self.value.get_mem_store().id() + } + + #[inline(always)] + pub fn from_typed_view(value: Box>) -> Self { + Obj { value, dirty: None } + } + + pub fn flush_dirty(&mut self) { + if !self.value.is_mem_mapped() { + if let Some(new_value_len) = self.dirty.take() { + let mut new_value = vec![0; new_value_len as usize]; + // TODO: log error + self.value.write_mem_image(&mut new_value).unwrap(); + let offset = self.value.get_offset(); + let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); + bx.write(offset, &new_value); + } + } + } +} + +impl Drop for Obj { + fn drop(&mut self) { + self.flush_dirty() + } +} + +impl Deref for Obj { + type Target = T; + fn deref(&self) -> &T { + &self.value + } +} + +/// User handle that offers read & write access to the stored [ShaleStore] item. +#[derive(Debug)] +pub struct ObjRef<'a, T> { + inner: Option>, + cache: ObjCache, + _life: PhantomData<&'a mut ()>, +} + +impl<'a, T> ObjRef<'a, T> { + pub fn to_longlive(mut self) -> ObjRef<'static, T> { + ObjRef { + inner: self.inner.take(), + cache: ObjCache(self.cache.0.clone()), + _life: PhantomData, + } + } + + #[inline] + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { + let inner = self.inner.as_mut().unwrap(); + inner.write(modify)?; + + self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); + + Ok(()) + } +} + +impl<'a, T> Deref for ObjRef<'a, T> { + type Target = Obj; + fn deref(&self) -> &Obj { + self.inner.as_ref().unwrap() + } +} + +impl<'a, T> Drop for ObjRef<'a, T> { + fn drop(&mut self) { + let mut inner = self.inner.take().unwrap(); + let ptr = inner.as_ptr(); + let cache = self.cache.get_inner_mut(); + match cache.pinned.remove(&ptr) { + Some(true) => { + inner.dirty = None; + } + _ => { + cache.cached.put(ptr, inner); + } + } + } +} + +/// A persistent item storage backed by linear logical space. New items can be created and old +/// items could be retrieved or dropped. +pub trait ShaleStore: Debug { + /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. + fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; + /// Allocate a new item. + fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; + /// Free an item and recycle its space when applicable. + fn free_item(&mut self, item: ObjPtr) -> Result<(), ShaleError>; + /// Flush all dirty writes. + fn flush_dirty(&self) -> Option<()>; +} + +/// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient +/// implementation could be directly transmuting to/from a POD struct. But sometimes necessary +/// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. +pub trait Storable { + fn dehydrated_len(&self) -> u64; + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>; + fn hydrate(addr: u64, mem: &T) -> Result + where + Self: Sized; + fn is_mem_mapped(&self) -> bool { + false + } +} + +pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { + let mut buff = vec![0; item.dehydrated_len() as usize]; + item.dehydrate(&mut buff)?; + Ok(buff) +} + +/// Reference implementation of [TypedView]. It takes any type that implements [Storable] +pub struct StoredView { + decoded: T, + mem: Box>, + offset: u64, + len_limit: u64, +} + +impl Debug for StoredView { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let StoredView { + decoded, + offset, + len_limit, + mem: _, + } = self; + f.debug_struct("StoredView") + .field("decoded", decoded) + .field("offset", offset) + .field("len_limit", len_limit) + .finish() + } +} + +impl Deref for StoredView { + type Target = T; + fn deref(&self) -> &T { + &self.decoded + } +} + +impl TypedView for StoredView { + fn get_offset(&self) -> u64 { + self.offset + } + + fn get_mem_store(&self) -> &dyn CachedStore { + &**self.mem + } + + fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore { + &mut **self.mem + } + + fn estimate_mem_image(&self) -> Option { + let len = self.decoded.dehydrated_len(); + if len > self.len_limit { + None + } else { + Some(len) + } + } + + fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { + self.decoded.dehydrate(mem_image) + } + + fn write(&mut self) -> &mut T { + &mut self.decoded + } + fn is_mem_mapped(&self) -> bool { + self.decoded.is_mem_mapped() + } +} + +impl StoredView { + #[inline(always)] + fn new(offset: u64, len_limit: u64, space: &U) -> Result { + let decoded = T::hydrate(offset, space)?; + Ok(Self { + offset, + decoded, + mem: space.get_shared(), + len_limit, + }) + } + + #[inline(always)] + fn from_hydrated( + offset: u64, + len_limit: u64, + decoded: T, + space: &dyn CachedStore, + ) -> Result { + Ok(Self { + offset, + decoded, + mem: space.get_shared(), + len_limit, + }) + } + + #[inline(always)] + pub fn ptr_to_obj( + store: &U, + ptr: ObjPtr, + len_limit: u64, + ) -> Result, ShaleError> { + Ok(Obj::from_typed_view(Box::new(Self::new( + ptr.addr(), + len_limit, + store, + )?))) + } + + #[inline(always)] + pub fn item_to_obj( + store: &dyn CachedStore, + addr: u64, + len_limit: u64, + decoded: T, + ) -> Result, ShaleError> { + Ok(Obj::from_typed_view(Box::new(Self::from_hydrated( + addr, len_limit, decoded, store, + )?))) + } +} + +impl StoredView { + fn new_from_slice( + offset: u64, + len_limit: u64, + decoded: T, + space: &dyn CachedStore, + ) -> Result { + Ok(Self { + offset, + decoded, + mem: space.get_shared(), + len_limit, + }) + } + + pub fn slice( + s: &Obj, + offset: u64, + length: u64, + decoded: U, + ) -> Result, ShaleError> { + let addr_ = s.value.get_offset() + offset; + if s.dirty.is_some() { + return Err(ShaleError::InvalidObj { + addr: offset, + obj_type: type_name::(), + error: "dirty write", + }); + } + let r = Box::new(StoredView::new_from_slice( + addr_, + length, + decoded, + s.value.get_mem_store(), + )?); + Ok(Obj { + value: r, + dirty: None, + }) + } +} + +impl ObjPtr { + const MSIZE: u64 = 8; +} + +impl Storable for ObjPtr { + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + use std::io::{Cursor, Write}; + Cursor::new(to).write_all(&self.addr().to_le_bytes())?; + Ok(()) + } + + fn hydrate(addr: u64, mem: &U) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let addrdyn = raw.deref(); + let addrvec = addrdyn.as_deref(); + Ok(Self::new_from_addr(u64::from_le_bytes( + addrvec.try_into().unwrap(), + ))) + } +} + +struct ObjCacheInner { + cached: lru::LruCache, Obj>, + pinned: HashMap, bool>, + dirty: HashSet>, +} + +/// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. +#[derive(Debug)] +pub struct ObjCache(Rc>>); + +impl ObjCache { + pub fn new(capacity: usize) -> Self { + Self(Rc::new(UnsafeCell::new(ObjCacheInner { + cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), + pinned: HashMap::new(), + dirty: HashSet::new(), + }))) + } + + #[inline(always)] + #[allow(clippy::mut_from_ref)] + // TODO: Refactor this function + fn get_inner_mut(&self) -> &mut ObjCacheInner { + unsafe { &mut *self.0.get() } + } + + #[inline(always)] + pub fn get(&'_ self, ptr: ObjPtr) -> Result>, ShaleError> { + let inner = &mut self.get_inner_mut(); + if let Some(r) = inner.cached.pop(&ptr) { + if inner.pinned.insert(ptr, false).is_some() { + return Err(ShaleError::InvalidObj { + addr: ptr.addr(), + obj_type: type_name::(), + error: "address already in use", + }); + } + return Ok(Some(ObjRef { + inner: Some(r), + cache: Self(self.0.clone()), + _life: PhantomData, + })); + } + Ok(None) + } + + #[inline(always)] + pub fn put(&'_ self, inner: Obj) -> ObjRef<'_, T> { + let ptr = inner.as_ptr(); + self.get_inner_mut().pinned.insert(ptr, false); + ObjRef { + inner: Some(inner), + cache: Self(self.0.clone()), + _life: PhantomData, + } + } + + #[inline(always)] + pub fn pop(&self, ptr: ObjPtr) { + let inner = self.get_inner_mut(); + if let Some(f) = inner.pinned.get_mut(&ptr) { + *f = true + } + if let Some(mut r) = inner.cached.pop(&ptr) { + r.dirty = None + } + inner.dirty.remove(&ptr); + } + + pub fn flush_dirty(&self) -> Option<()> { + let inner = self.get_inner_mut(); + if !inner.pinned.is_empty() { + return None; + } + for ptr in std::mem::take(&mut inner.dirty) { + if let Some(r) = inner.cached.peek_mut(&ptr) { + r.flush_dirty() + } + } + Some(()) + } +} diff --git a/firewood-shale/src/util.rs b/shale-archive/src/util.rs similarity index 100% rename from firewood-shale/src/util.rs rename to shale-archive/src/util.rs From 3a188f0ee4930c808bd159e0bf960f607ba02571 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:29:54 -0700 Subject: [PATCH 0201/1053] Mark green milestone complete (#151) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2d0fe76da3f..dd8e7f71b666 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ upstream. This milestone will focus on additional code cleanup, including supporting concurrent access to a specific revision, as well as cleaning up the basic reader and writer interfaces to have consistent read/write semantics. -- [ ] Concurrent readers of pinned revisions while allowing additional batches +- [x] Concurrent readers of pinned revisions while allowing additional batches to commit, to support parallel reads for the past consistent states. The revisions are uniquely identified by root hashes. - [x] Pin a reader to a specific revision, so that future commits or other From e2e75ced49033e52f0a2423b6b55742e27df236c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 18:32:38 -0700 Subject: [PATCH 0202/1053] build(deps): update pprof requirement from 0.11.1 to 0.12.0 (#152) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood-shale/Cargo.toml | 2 +- shale-archive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 53092af1e707..9fcf0d9c125e 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -14,7 +14,7 @@ thiserror = "1.0.38" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.11.1", features = ["flamegraph"] } +pprof = { version = "0.12.0", features = ["flamegraph"] } sha3 = "0.10.7" rand = "0.8.5" diff --git a/shale-archive/Cargo.toml b/shale-archive/Cargo.toml index 01850e9578ed..0ee8c84a1a51 100644 --- a/shale-archive/Cargo.toml +++ b/shale-archive/Cargo.toml @@ -14,7 +14,7 @@ thiserror = "1.0.38" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.11.1", features = ["flamegraph"] } +pprof = { version = "0.12.0", features = ["flamegraph"] } sha3 = "0.10.7" rand = "0.8.5" From a2352491edc52242c6bc19d4de6b823afed07bbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:04:30 -0400 Subject: [PATCH 0203/1053] build(deps): update typed-builder requirement from 0.14.0 to 0.15.0 (#153) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 338a8e27deec..1d2a4d814459 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } -typed-builder = "0.14.0" +typed-builder = "0.15.0" [dev-dependencies] criterion = "0.5.1" From 8fe2593b159d82ee13723562f6ca6e08f4c14cef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:10:59 -0400 Subject: [PATCH 0204/1053] build(deps): update lru requirement from 0.10.0 to 0.11.0 (#155) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood-growth-ring/Cargo.toml | 2 +- firewood-shale/Cargo.toml | 2 +- firewood/Cargo.toml | 2 +- shale-archive/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firewood-growth-ring/Cargo.toml b/firewood-growth-ring/Cargo.toml index b9218f1b0426..ca468c79e492 100644 --- a/firewood-growth-ring/Cargo.toml +++ b/firewood-growth-ring/Cargo.toml @@ -10,7 +10,7 @@ description = "Simple and modular write-ahead-logging implementation." [dependencies] crc = "3.0.0" -lru = "0.10.0" +lru = "0.11.0" scan_fmt = "0.2.6" regex = "1.6.0" async-trait = "0.1.57" diff --git a/firewood-shale/Cargo.toml b/firewood-shale/Cargo.toml index 9fcf0d9c125e..c0a3fb0421c6 100644 --- a/firewood-shale/Cargo.toml +++ b/firewood-shale/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" [dependencies] hex = "0.4.3" -lru = "0.10.0" +lru = "0.11.0" thiserror = "1.0.38" [dev-dependencies] diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 1d2a4d814459..e47fcd18b58c 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -25,7 +25,7 @@ firewood-libaio = {version = "0.0.3", path = "../firewood-libaio" } firewood-shale = { version = "0.0.3", path = "../firewood-shale" } futures = "0.3.24" hex = "0.4.3" -lru = "0.10.0" +lru = "0.11.0" metered = "0.9.0" nix = "0.26.1" parking_lot = "0.12.1" diff --git a/shale-archive/Cargo.toml b/shale-archive/Cargo.toml index 0ee8c84a1a51..5ba5f85b133e 100644 --- a/shale-archive/Cargo.toml +++ b/shale-archive/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" [dependencies] hex = "0.4.3" -lru = "0.10.0" +lru = "0.11.0" thiserror = "1.0.38" [dev-dependencies] From 1e1fab9799f68db524b043ad046949d2a6cfe76a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 12 Jul 2023 12:36:18 -0400 Subject: [PATCH 0205/1053] Remove shale-archive (#156) --- Cargo.toml | 1 - shale-archive/Cargo.toml | 23 - shale-archive/LICENSE | 22 - shale-archive/benches/shale-bench.rs | 99 ---- shale-archive/src/cached.rs | 251 --------- shale-archive/src/compact.rs | 759 --------------------------- shale-archive/src/lib.rs | 599 --------------------- shale-archive/src/util.rs | 6 - 8 files changed, 1760 deletions(-) delete mode 100644 shale-archive/Cargo.toml delete mode 100644 shale-archive/LICENSE delete mode 100644 shale-archive/benches/shale-bench.rs delete mode 100644 shale-archive/src/cached.rs delete mode 100644 shale-archive/src/compact.rs delete mode 100644 shale-archive/src/lib.rs delete mode 100644 shale-archive/src/util.rs diff --git a/Cargo.toml b/Cargo.toml index 508571625719..0742fcfcbda3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "firewood-growth-ring", "firewood-libaio", "firewood-shale", - "shale-archive", "firewood", "fwdctl", ] diff --git a/shale-archive/Cargo.toml b/shale-archive/Cargo.toml deleted file mode 100644 index 5ba5f85b133e..000000000000 --- a/shale-archive/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "shale-archive" -version = "0.0.3" -edition = "2021" -description = "Useful abstraction and light-weight implemenation for a key-value store." -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -hex = "0.4.3" -lru = "0.11.0" -thiserror = "1.0.38" - -[dev-dependencies] -criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.12.0", features = ["flamegraph"] } -sha3 = "0.10.7" -rand = "0.8.5" - -[[bench]] -name = "shale-bench" -harness = false diff --git a/shale-archive/LICENSE b/shale-archive/LICENSE deleted file mode 100644 index 81a1d4cf1dfb..000000000000 --- a/shale-archive/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2019 Maofan (Ted) Yin -Copyright (c) 2019 Cornell University - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/shale-archive/benches/shale-bench.rs b/shale-archive/benches/shale-bench.rs deleted file mode 100644 index 01db3f6dd87c..000000000000 --- a/shale-archive/benches/shale-bench.rs +++ /dev/null @@ -1,99 +0,0 @@ -use shale_archive as shale; - -use criterion::{ - black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, -}; -use pprof::ProfilerGuard; -use rand::Rng; -use shale::{ - cached::{DynamicMem, PlainMem}, - compact::CompactSpaceHeader, - CachedStore, ObjPtr, StoredView, -}; -use std::{fs::File, os::raw::c_int, path::Path}; - -const BENCH_MEM_SIZE: u64 = 2_000_000; - -// To enable flamegraph output -// cargo bench --bench shale-bench -- --profile-time=N -pub struct FlamegraphProfiler<'a> { - frequency: c_int, - active_profiler: Option>, -} - -impl<'a> FlamegraphProfiler<'a> { - #[allow(dead_code)] - pub fn new(frequency: c_int) -> Self { - FlamegraphProfiler { - frequency, - active_profiler: None, - } - } -} - -impl<'a> Profiler for FlamegraphProfiler<'a> { - fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { - self.active_profiler = Some(ProfilerGuard::new(self.frequency).unwrap()); - } - - fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { - std::fs::create_dir_all(benchmark_dir).unwrap(); - let flamegraph_path = benchmark_dir.join("flamegraph.svg"); - let flamegraph_file = - File::create(flamegraph_path).expect("File system error while creating flamegraph.svg"); - if let Some(profiler) = self.active_profiler.take() { - profiler - .report() - .build() - .unwrap() - .flamegraph(flamegraph_file) - .expect("Error writing flamegraph"); - } - } -} - -fn get_view(b: &mut Bencher, mut cached: C) { - let mut rng = rand::thread_rng(); - - b.iter(|| { - let len = rng.gen_range(0..26); - let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); - - let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); - - cached.write(offset, rdata); - let view = cached - .get_view(offset, rdata.len().try_into().unwrap()) - .unwrap(); - - serialize(&cached); - assert_eq!(view.as_deref(), rdata); - }); -} - -fn serialize(m: &T) { - let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); - let _compact_header = - StoredView::ptr_to_obj(m, compact_header_obj, shale::compact::CompactHeader::MSIZE) - .unwrap(); -} - -fn bench_cursors(c: &mut Criterion) { - let mut group = c.benchmark_group("shale-bench"); - group.bench_function("PlainMem", |b| { - let mem = PlainMem::new(BENCH_MEM_SIZE, 0); - get_view(b, mem) - }); - group.bench_function("DynamicMem", |b| { - let mem = DynamicMem::new(BENCH_MEM_SIZE, 0); - get_view(b, mem) - }); -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(FlamegraphProfiler::new(100)); - targets = bench_cursors -} - -criterion_main!(benches); diff --git a/shale-archive/src/cached.rs b/shale-archive/src/cached.rs deleted file mode 100644 index 0eea8c3dff36..000000000000 --- a/shale-archive/src/cached.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::borrow::BorrowMut; -use std::cell::{RefCell, UnsafeCell}; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - -use crate::{CachedStore, CachedView, SpaceId}; - -/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying -/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write -/// your own [CachedStore] implementation. -#[derive(Debug)] -pub struct PlainMem { - space: Rc>>, - id: SpaceId, -} - -impl PlainMem { - pub fn new(size: u64, id: SpaceId) -> Self { - let mut space: Vec = Vec::new(); - space.resize(size as usize, 0); - let space = Rc::new(RefCell::new(space)); - Self { space, id } - } -} - -impl CachedStore for PlainMem { - fn get_view( - &self, - offset: u64, - length: u64, - ) -> Option>>> { - let offset = offset as usize; - let length = length as usize; - if offset + length > self.space.borrow().len() { - None - } else { - Some(Box::new(PlainMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - } - - fn get_shared(&self) -> Box> { - Box::new(PlainMemShared(Self { - space: self.space.clone(), - id: self.id, - })) - } - - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; - let length = change.len(); - let mut vect = self.space.deref().borrow_mut(); - vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); - } - - fn id(&self) -> SpaceId { - self.id - } -} - -#[derive(Debug)] -struct PlainMemView { - offset: usize, - length: usize, - mem: PlainMem, -} - -struct PlainMemShared(PlainMem); - -impl DerefMut for PlainMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.borrow_mut() - } -} - -impl Deref for PlainMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl CachedView for PlainMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - self.mem.space.borrow()[self.offset..self.offset + self.length].to_vec() - } -} - -// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to -/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is -/// not enough. -#[derive(Debug)] -pub struct DynamicMem { - space: Rc>>, - id: SpaceId, -} - -impl DynamicMem { - pub fn new(size: u64, id: SpaceId) -> Self { - let space = Rc::new(UnsafeCell::new(vec![0; size as usize])); - Self { space, id } - } - - #[allow(clippy::mut_from_ref)] - // TODO: Refactor this usage. - fn get_space_mut(&self) -> &mut Vec { - unsafe { &mut *self.space.get() } - } -} - -impl CachedStore for DynamicMem { - fn get_view( - &self, - offset: u64, - length: u64, - ) -> Option>>> { - let offset = offset as usize; - let length = length as usize; - let size = offset + length; - // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); - } - Some(Box::new(DynamicMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - - fn get_shared(&self) -> Box> { - Box::new(DynamicMemShared(Self { - space: self.space.clone(), - id: self.id, - })) - } - - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; - let length = change.len(); - let size = offset + length; - // Increase the size if the request range exceeds the current limit. - if size > self.get_space_mut().len() { - self.get_space_mut().resize(size, 0); - } - self.get_space_mut()[offset..offset + length].copy_from_slice(change) - } - - fn id(&self) -> SpaceId { - self.id - } -} - -#[derive(Debug)] -struct DynamicMemView { - offset: usize, - length: usize, - mem: DynamicMem, -} - -struct DynamicMemShared(DynamicMem); - -impl Deref for DynamicMemView { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.mem.get_space_mut()[self.offset..self.offset + self.length] - } -} - -impl Deref for DynamicMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl DerefMut for DynamicMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl CachedView for DynamicMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - self.mem.get_space_mut()[self.offset..self.offset + self.length].to_vec() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plain_mem() { - let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = view.deref_mut(); - mem.write(0, &[1, 1]); - mem.write(0, &[1, 2]); - let r = mem.get_view(0, 2).unwrap().as_deref(); - assert_eq!(r, [1, 2]); - - // previous view not mutated by write - mem.write(0, &[1, 3]); - assert_eq!(r, [1, 2]); - let r = mem.get_view(0, 2).unwrap().as_deref(); - assert_eq!(r, [1, 3]); - - // create a view larger than capacity - assert!(mem.get_view(0, 4).is_none()) - } - - #[test] - #[should_panic(expected = "index 3 out of range for slice of length 2")] - fn test_plain_mem_panic() { - let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = view.deref_mut(); - - // out of range - mem.write(1, &[7, 8]); - } - - #[test] - fn test_dynamic_mem() { - let mut view = DynamicMemShared(DynamicMem::new(2, 0)); - let mem = view.deref_mut(); - mem.write(0, &[1, 2]); - mem.write(0, &[3, 4]); - assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); - mem.get_shared().write(0, &[5, 6]); - - // capacity is increased - mem.write(5, &[0; 10]); - - // get a view larger than recent growth - assert_eq!(mem.get_view(3, 20).unwrap().as_deref(), [0; 20]); - } -} diff --git a/shale-archive/src/compact.rs b/shale-archive/src/compact.rs deleted file mode 100644 index 2078adcd6d6b..000000000000 --- a/shale-archive/src/compact.rs +++ /dev/null @@ -1,759 +0,0 @@ -use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; -use std::cell::UnsafeCell; -use std::fmt::Debug; -use std::io::{Cursor, Write}; -use std::rc::Rc; - -#[derive(Debug)] -pub struct CompactHeader { - payload_size: u64, - is_freed: bool, - desc_addr: ObjPtr, -} - -impl CompactHeader { - pub const MSIZE: u64 = 17; - pub fn is_freed(&self) -> bool { - self.is_freed - } - - pub fn payload_size(&self) -> u64 { - self.payload_size - } -} - -impl Storable for CompactHeader { - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let payload_size = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let is_freed = raw.as_deref()[8] != 0; - let desc_addr = - u64::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); - Ok(Self { - payload_size, - is_freed, - desc_addr: ObjPtr::new(desc_addr), - }) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes())?; - cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; - cur.write_all(&self.desc_addr.addr().to_le_bytes())?; - Ok(()) - } -} - -#[derive(Debug)] -struct CompactFooter { - payload_size: u64, -} - -impl CompactFooter { - const MSIZE: u64 = 8; -} - -impl Storable for CompactFooter { - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); - Ok(Self { payload_size }) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.payload_size.to_le_bytes())?; - Ok(()) - } -} - -#[derive(Clone, Copy, Debug)] -struct CompactDescriptor { - payload_size: u64, - haddr: u64, // pointer to the payload of freed space -} - -impl CompactDescriptor { - const MSIZE: u64 = 16; -} - -impl Storable for CompactDescriptor { - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let payload_size = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); - Ok(Self { - payload_size, - haddr, - }) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes())?; - cur.write_all(&self.haddr.to_le_bytes())?; - Ok(()) - } -} - -#[derive(Debug)] -pub struct CompactSpaceHeader { - meta_space_tail: u64, - compact_space_tail: u64, - base_addr: ObjPtr, - alloc_addr: ObjPtr, -} - -struct CompactSpaceHeaderSliced { - meta_space_tail: Obj, - compact_space_tail: Obj, - base_addr: Obj>, - alloc_addr: Obj>, -} - -impl CompactSpaceHeaderSliced { - fn flush_dirty(&mut self) { - self.meta_space_tail.flush_dirty(); - self.compact_space_tail.flush_dirty(); - self.base_addr.flush_dirty(); - self.alloc_addr.flush_dirty(); - } -} - -impl CompactSpaceHeader { - pub const MSIZE: u64 = 32; - - pub fn new(meta_base: u64, compact_base: u64) -> Self { - Self { - meta_space_tail: meta_base, - compact_space_tail: compact_base, - base_addr: ObjPtr::new_from_addr(meta_base), - alloc_addr: ObjPtr::new_from_addr(meta_base), - } - } - - fn into_fields(r: Obj) -> Result { - Ok(CompactSpaceHeaderSliced { - meta_space_tail: StoredView::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, - compact_space_tail: StoredView::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, - base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, - alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, - }) - } -} - -impl Storable for CompactSpaceHeader { - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let meta_space_tail = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let compact_space_tail = - u64::from_le_bytes(raw.as_deref()[8..16].try_into().expect("invalid slice")); - let base_addr = - u64::from_le_bytes(raw.as_deref()[16..24].try_into().expect("invalid slice")); - let alloc_addr = - u64::from_le_bytes(raw.as_deref()[24..].try_into().expect("invalid slice")); - Ok(Self { - meta_space_tail, - compact_space_tail, - base_addr: ObjPtr::new_from_addr(base_addr), - alloc_addr: ObjPtr::new_from_addr(alloc_addr), - }) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.meta_space_tail.to_le_bytes())?; - cur.write_all(&self.compact_space_tail.to_le_bytes())?; - cur.write_all(&self.base_addr.addr().to_le_bytes())?; - cur.write_all(&self.alloc_addr.addr().to_le_bytes())?; - Ok(()) - } -} - -struct ObjPtrField(ObjPtr); - -impl ObjPtrField { - const MSIZE: u64 = 8; -} - -impl Storable for ObjPtrField { - fn hydrate(addr: u64, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let obj_ptr = ObjPtr::new_from_addr(u64::from_le_bytes( - <[u8; 8]>::try_from(&raw.as_deref()[0..8]).expect("invalid slice"), - )); - Ok(Self(obj_ptr)) - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0.addr().to_le_bytes())?; - Ok(()) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } -} - -impl std::ops::Deref for ObjPtrField { - type Target = ObjPtr; - fn deref(&self) -> &ObjPtr { - &self.0 - } -} - -impl std::ops::DerefMut for ObjPtrField { - fn deref_mut(&mut self) -> &mut ObjPtr { - &mut self.0 - } -} - -#[derive(Debug)] -struct U64Field(u64); - -impl U64Field { - const MSIZE: u64 = 8; -} - -impl Storable for U64Field { - fn hydrate(addr: u64, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0.to_le_bytes())?; - Ok(()) - } -} - -impl std::ops::Deref for U64Field { - type Target = u64; - fn deref(&self) -> &u64 { - &self.0 - } -} - -impl std::ops::DerefMut for U64Field { - fn deref_mut(&mut self) -> &mut u64 { - &mut self.0 - } -} - -struct CompactSpaceInner { - meta_space: Rc, - compact_space: Rc, - header: CompactSpaceHeaderSliced, - obj_cache: super::ObjCache, - alloc_max_walk: u64, - regn_nbit: u64, -} - -impl CompactSpaceInner { - fn get_descriptor( - &self, - ptr: ObjPtr, - ) -> Result, ShaleError> { - StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) - } - - fn get_data_ref( - &self, - ptr: ObjPtr, - len_limit: u64, - ) -> Result, ShaleError> { - StoredView::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) - } - - fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactHeader::MSIZE) - } - - fn get_footer(&self, ptr: ObjPtr) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactFooter::MSIZE) - } - - fn del_desc(&mut self, desc_addr: ObjPtr) -> Result<(), ShaleError> { - let desc_size = CompactDescriptor::MSIZE; - debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); - self.header - .meta_space_tail - .write(|r| **r -= desc_size) - .unwrap(); - - if desc_addr.addr != **self.header.meta_space_tail { - let desc_last = - self.get_descriptor(ObjPtr::new_from_addr(**self.header.meta_space_tail))?; - let mut desc = self.get_descriptor(ObjPtr::new_from_addr(desc_addr.addr))?; - desc.write(|r| *r = *desc_last).unwrap(); - let mut header = self.get_header(ObjPtr::new(desc.haddr))?; - header.write(|h| h.desc_addr = desc_addr).unwrap(); - } - - Ok(()) - } - - fn new_desc(&mut self) -> Result, ShaleError> { - let addr = **self.header.meta_space_tail; - self.header - .meta_space_tail - .write(|r| **r += CompactDescriptor::MSIZE) - .unwrap(); - - Ok(ObjPtr::new_from_addr(addr)) - } - - fn free(&mut self, addr: u64) -> Result<(), ShaleError> { - let hsize = CompactHeader::MSIZE; - let fsize = CompactFooter::MSIZE; - let regn_size = 1 << self.regn_nbit; - - let mut offset = addr - hsize; - let header_payload_size = { - let header = self.get_header(ObjPtr::new(offset))?; - assert!(!header.is_freed); - header.payload_size - }; - let mut h = offset; - let mut payload_size = header_payload_size; - - if offset & (regn_size - 1) > 0 { - // merge with lower data segment - offset -= fsize; - let (pheader_is_freed, pheader_payload_size, pheader_desc_addr) = { - let pfooter = self.get_footer(ObjPtr::new(offset))?; - offset -= pfooter.payload_size + hsize; - let pheader = self.get_header(ObjPtr::new(offset))?; - (pheader.is_freed, pheader.payload_size, pheader.desc_addr) - }; - if pheader_is_freed { - h = offset; - payload_size += hsize + fsize + pheader_payload_size; - self.del_desc(pheader_desc_addr)?; - } - } - - offset = addr + header_payload_size; - let mut f = offset; - - if offset + fsize < **self.header.compact_space_tail - && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize - { - // merge with higher data segment - offset += fsize; - let (nheader_is_freed, nheader_payload_size, nheader_desc_addr) = { - let nheader = self.get_header(ObjPtr::new(offset))?; - (nheader.is_freed, nheader.payload_size, nheader.desc_addr) - }; - if nheader_is_freed { - offset += hsize + nheader_payload_size; - f = offset; - { - let nfooter = self.get_footer(ObjPtr::new(offset))?; - assert!(nheader_payload_size == nfooter.payload_size); - } - payload_size += hsize + fsize + nheader_payload_size; - self.del_desc(nheader_desc_addr)?; - } - } - - let desc_addr = self.new_desc()?; - { - let mut desc = self.get_descriptor(desc_addr)?; - desc.write(|d| { - d.payload_size = payload_size; - d.haddr = h; - }) - .unwrap(); - } - let mut h = self.get_header(ObjPtr::new(h))?; - let mut f = self.get_footer(ObjPtr::new(f))?; - h.write(|h| { - h.payload_size = payload_size; - h.is_freed = true; - h.desc_addr = desc_addr; - }) - .unwrap(); - f.write(|f| f.payload_size = payload_size).unwrap(); - - Ok(()) - } - - fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { - let tail = **self.header.meta_space_tail; - if tail == self.header.base_addr.addr { - return Ok(None); - } - - let hsize = CompactHeader::MSIZE; - let fsize = CompactFooter::MSIZE; - let dsize = CompactDescriptor::MSIZE; - - let mut old_alloc_addr = *self.header.alloc_addr; - - if old_alloc_addr.addr >= tail { - old_alloc_addr = *self.header.base_addr; - } - - let mut ptr = old_alloc_addr; - let mut res: Option = None; - for _ in 0..self.alloc_max_walk { - assert!(ptr.addr < tail); - let (desc_payload_size, desc_haddr) = { - let desc = self.get_descriptor(ptr)?; - (desc.payload_size, desc.haddr) - }; - let exit = if desc_payload_size == length { - // perfect match - { - let mut header = self.get_header(ObjPtr::new(desc_haddr))?; - assert_eq!(header.payload_size, desc_payload_size); - assert!(header.is_freed); - header.write(|h| h.is_freed = false).unwrap(); - } - self.del_desc(ptr)?; - true - } else if desc_payload_size > length + hsize + fsize { - // able to split - { - let mut lheader = self.get_header(ObjPtr::new(desc_haddr))?; - assert_eq!(lheader.payload_size, desc_payload_size); - assert!(lheader.is_freed); - lheader - .write(|h| { - h.is_freed = false; - h.payload_size = length; - }) - .unwrap(); - } - { - let mut lfooter = self.get_footer(ObjPtr::new(desc_haddr + hsize + length))?; - //assert!(lfooter.payload_size == desc_payload_size); - lfooter.write(|f| f.payload_size = length).unwrap(); - } - - let offset = desc_haddr + hsize + length + fsize; - let rpayload_size = desc_payload_size - length - fsize - hsize; - let rdesc_addr = self.new_desc()?; - { - let mut rdesc = self.get_descriptor(rdesc_addr)?; - rdesc - .write(|rd| { - rd.payload_size = rpayload_size; - rd.haddr = offset; - }) - .unwrap(); - } - { - let mut rheader = self.get_header(ObjPtr::new(offset))?; - rheader - .write(|rh| { - rh.is_freed = true; - rh.payload_size = rpayload_size; - rh.desc_addr = rdesc_addr; - }) - .unwrap(); - } - { - let mut rfooter = - self.get_footer(ObjPtr::new(offset + hsize + rpayload_size))?; - rfooter.write(|f| f.payload_size = rpayload_size).unwrap(); - } - self.del_desc(ptr)?; - true - } else { - false - }; - if exit { - self.header.alloc_addr.write(|r| *r = ptr).unwrap(); - res = Some(desc_haddr + hsize); - break; - } - ptr.addr += dsize; - if ptr.addr >= tail { - ptr = *self.header.base_addr; - } - if ptr == old_alloc_addr { - break; - } - } - Ok(res) - } - - fn alloc_new(&mut self, length: u64) -> Result { - let regn_size = 1 << self.regn_nbit; - let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; - let mut offset = **self.header.compact_space_tail; - self.header - .compact_space_tail - .write(|r| { - // an item is always fully in one region - let rem = regn_size - (offset & (regn_size - 1)); - if rem < total_length { - offset += rem; - **r += rem; - } - **r += total_length - }) - .unwrap(); - let mut h = self.get_header(ObjPtr::new(offset))?; - let mut f = self.get_footer(ObjPtr::new(offset + CompactHeader::MSIZE + length))?; - h.write(|h| { - h.payload_size = length; - h.is_freed = false; - h.desc_addr = ObjPtr::new(0); - }) - .unwrap(); - f.write(|f| f.payload_size = length).unwrap(); - Ok(offset + CompactHeader::MSIZE) - } -} - -#[derive(Debug)] -pub struct CompactSpace { - inner: UnsafeCell>, -} - -impl CompactSpace { - pub fn new( - meta_space: Rc, - compact_space: Rc, - header: Obj, - obj_cache: super::ObjCache, - alloc_max_walk: u64, - regn_nbit: u64, - ) -> Result { - let cs = CompactSpace { - inner: UnsafeCell::new(CompactSpaceInner { - meta_space, - compact_space, - header: CompactSpaceHeader::into_fields(header)?, - obj_cache, - alloc_max_walk, - regn_nbit, - }), - }; - Ok(cs) - } -} - -impl ShaleStore - for CompactSpace -{ - fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError> { - let size = item.dehydrated_len() + extra; - let inner = unsafe { &mut *self.inner.get() }; - let ptr: ObjPtr = - ObjPtr::new_from_addr(if let Some(addr) = inner.alloc_from_freed(size)? { - addr - } else { - inner.alloc_new(size)? - }); - let mut u = inner.obj_cache.put(StoredView::item_to_obj( - inner.compact_space.as_ref(), - ptr.addr(), - size, - item, - )?); - u.write(|_| {}).unwrap(); - Ok(u) - } - - fn free_item(&mut self, ptr: ObjPtr) -> Result<(), ShaleError> { - let inner = self.inner.get_mut(); - inner.obj_cache.pop(ptr); - inner.free(ptr.addr()) - } - - fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError> { - let inner = unsafe { &*self.inner.get() }; - if let Some(r) = inner.obj_cache.get(ptr)? { - return Ok(r); - } - if ptr.addr() < CompactSpaceHeader::MSIZE { - return Err(ShaleError::InvalidAddressLength { - expected: CompactSpaceHeader::MSIZE, - found: ptr.addr(), - }); - } - let h = inner.get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))?; - Ok(inner - .obj_cache - .put(inner.get_data_ref(ptr, h.payload_size)?)) - } - - fn flush_dirty(&self) -> Option<()> { - let inner = unsafe { &mut *self.inner.get() }; - inner.header.flush_dirty(); - inner.obj_cache.flush_dirty() - } -} - -#[cfg(test)] -mod tests { - use sha3::Digest; - - use crate::{cached::DynamicMem, ObjCache}; - - use super::*; - - const HASH_SIZE: usize = 32; - const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); - - #[derive(PartialEq, Eq, Debug, Clone)] - pub struct Hash(pub [u8; HASH_SIZE]); - - impl Hash { - const MSIZE: u64 = 32; - } - - impl std::ops::Deref for Hash { - type Target = [u8; HASH_SIZE]; - fn deref(&self) -> &[u8; HASH_SIZE] { - &self.0 - } - } - - impl Storable for Hash { - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - Ok(Self( - raw.as_deref()[..Self::MSIZE as usize] - .try_into() - .expect("invalid slice"), - )) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = to; - cur.write_all(&self.0)?; - Ok(()) - } - } - - #[test] - fn test_space_item() { - const META_SIZE: u64 = 0x10000; - const COMPACT_SIZE: u64 = 0x10000; - const RESERVED: u64 = 0x1000; - - let mut dm = DynamicMem::new(META_SIZE, 0x0); - - // initialize compact space - let compact_header: ObjPtr = ObjPtr::new_from_addr(0x1); - dm.write( - compact_header.addr(), - &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)).unwrap(), - ); - let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); - let mem_meta = Rc::new(dm); - let mem_payload = Rc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); - - let cache: ObjCache = ObjCache::new(1); - let space = - CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); - - // initial write - let data = b"hello world"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - let obj_ref = space.put_item(Hash(hash), 0).unwrap(); - assert_eq!(obj_ref.as_ptr().addr(), 4113); - // create hash ptr from address and attempt to read dirty write. - let hash_ref = space.get_item(ObjPtr::new_from_addr(4113)).unwrap(); - // read before flush results in zeroed hash - assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); - // not cached - assert!(obj_ref - .cache - .get_inner_mut() - .cached - .get(&ObjPtr::new_from_addr(4113)) - .is_none()); - // pinned - assert!(obj_ref - .cache - .get_inner_mut() - .pinned - .get(&ObjPtr::new_from_addr(4113)) - .is_some()); - // dirty - assert!(obj_ref - .cache - .get_inner_mut() - .dirty - .get(&ObjPtr::new_from_addr(4113)) - .is_some()); - drop(obj_ref); - // write is visible - assert_eq!( - space - .get_item(ObjPtr::new_from_addr(4113)) - .unwrap() - .as_ref(), - hash - ); - } -} diff --git a/shale-archive/src/lib.rs b/shale-archive/src/lib.rs deleted file mode 100644 index d0c01a039ee3..000000000000 --- a/shale-archive/src/lib.rs +++ /dev/null @@ -1,599 +0,0 @@ -use std::any::type_name; -use std::cell::UnsafeCell; -use std::collections::{HashMap, HashSet}; -use std::fmt::{self, Debug, Display, Formatter}; -use std::hash::Hash; -use std::hash::Hasher; -use std::marker::PhantomData; -use std::num::NonZeroUsize; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - -use thiserror::Error; - -pub mod cached; -pub mod compact; -pub mod util; - -#[derive(Debug, Error)] -#[non_exhaustive] -pub enum ShaleError { - #[error("obj invalid: {addr:?} obj: {obj_type:?} error: {error:?}")] - InvalidObj { - addr: u64, - obj_type: &'static str, - error: &'static str, - }, - #[error("invalid address length expected: {expected:?} found: {found:?})")] - InvalidAddressLength { expected: u64, found: u64 }, - #[error("invalid node type")] - InvalidNodeType, - #[error("failed to create view: offset: {offset:?} size: {size:?}")] - InvalidCacheView { offset: u64, size: u64 }, - #[error("io error: {0}")] - Io(#[from] std::io::Error), -} - -// TODO: -// this could probably included with ShaleError, -// but keeping it separate for now as Obj/ObjRef might change in the near future -#[derive(Debug, Error)] -#[error("write error")] -pub struct ObjWriteError; - -pub type SpaceId = u8; -pub const INVALID_SPACE_ID: SpaceId = 0xff; - -pub struct DiskWrite { - pub space_id: SpaceId, - pub space_off: u64, - pub data: Box<[u8]>, -} - -impl std::fmt::Debug for DiskWrite { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "[Disk space=0x{:02x} offset=0x{:04x} data=0x{}", - self.space_id, - self.space_off, - hex::encode(&self.data) - ) - } -} - -/// A handle that pins and provides a readable access to a portion of the linear memory image. -pub trait CachedView { - type DerefReturn: Deref; - fn as_deref(&self) -> Self::DerefReturn; -} - -/// In-memory store that offers access to intervals from a linear byte space, which is usually -/// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear -/// persistent store. Reads could trigger disk reads to bring data into memory, but writes will -/// *only* be visible in memory (it does not write back to the disk). -pub trait CachedStore: Debug { - /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them - /// directly accessible. - fn get_view( - &self, - offset: u64, - length: u64, - ) -> Option>>>; - /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Box>; - /// Write the `change` to the portion of the linear space starting at `offset`. The change - /// should be immediately visible to all `CachedView` associated to this linear space. - fn write(&mut self, offset: u64, change: &[u8]); - /// Returns the identifier of this storage space. - fn id(&self) -> SpaceId; -} - -/// Opaque typed pointer in the 64-bit virtual addressable space. -#[repr(C)] -#[derive(Debug)] -pub struct ObjPtr { - pub(crate) addr: u64, - phantom: PhantomData, -} - -impl std::cmp::PartialEq for ObjPtr { - fn eq(&self, other: &ObjPtr) -> bool { - self.addr == other.addr - } -} - -impl Eq for ObjPtr {} -impl Copy for ObjPtr {} -impl Clone for ObjPtr { - fn clone(&self) -> Self { - *self - } -} - -impl Hash for ObjPtr { - fn hash(&self, state: &mut H) { - self.addr.hash(state) - } -} - -impl Display for ObjPtr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[ObjPtr addr={:08x}]", self.addr) - } -} - -impl ObjPtr { - pub fn null() -> Self { - Self::new(0) - } - pub fn is_null(&self) -> bool { - self.addr == 0 - } - pub fn addr(&self) -> u64 { - self.addr - } - - #[inline(always)] - pub(crate) fn new(addr: u64) -> Self { - ObjPtr { - addr, - phantom: PhantomData, - } - } - - #[inline(always)] - pub fn new_from_addr(addr: u64) -> Self { - Self::new(addr) - } -} - -/// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object -/// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to -/// turn a `TypedView` into an [ObjRef]. -pub trait TypedView: std::fmt::Debug + Deref { - /// Get the offset of the initial byte in the linear space. - fn get_offset(&self) -> u64; - /// Access it as a [CachedStore] object. - fn get_mem_store(&self) -> &dyn CachedStore; - /// Access it as a mutable CachedStore object - fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore; - /// Estimate the serialized length of the current type content. It should not be smaller than - /// the actually length. - fn estimate_mem_image(&self) -> Option; - /// Serialize the type content to the memory image. It defines how the current in-memory object - /// of `T` should be represented in the linear storage space. - fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError>; - /// Gain mutable access to the typed content. By changing it, its serialized bytes (and length) - /// could change. - fn write(&mut self) -> &mut T; - /// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto - /// reflected in the underlying [CachedStore]). - fn is_mem_mapped(&self) -> bool; -} - -/// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] -/// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. -/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] -/// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. -#[derive(Debug)] -pub struct Obj { - value: Box>, - dirty: Option, -} - -impl Obj { - #[inline(always)] - pub fn as_ptr(&self) -> ObjPtr { - ObjPtr::::new(self.value.get_offset()) - } -} - -impl Obj { - /// Write to the underlying object. Returns `Some(())` on success. - #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { - modify(self.value.write()); - - // if `estimate_mem_image` gives overflow, the object will not be written - self.dirty = match self.value.estimate_mem_image() { - Some(len) => Some(len), - None => return Err(ObjWriteError), - }; - - Ok(()) - } - - #[inline(always)] - pub fn get_space_id(&self) -> SpaceId { - self.value.get_mem_store().id() - } - - #[inline(always)] - pub fn from_typed_view(value: Box>) -> Self { - Obj { value, dirty: None } - } - - pub fn flush_dirty(&mut self) { - if !self.value.is_mem_mapped() { - if let Some(new_value_len) = self.dirty.take() { - let mut new_value = vec![0; new_value_len as usize]; - // TODO: log error - self.value.write_mem_image(&mut new_value).unwrap(); - let offset = self.value.get_offset(); - let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); - bx.write(offset, &new_value); - } - } - } -} - -impl Drop for Obj { - fn drop(&mut self) { - self.flush_dirty() - } -} - -impl Deref for Obj { - type Target = T; - fn deref(&self) -> &T { - &self.value - } -} - -/// User handle that offers read & write access to the stored [ShaleStore] item. -#[derive(Debug)] -pub struct ObjRef<'a, T> { - inner: Option>, - cache: ObjCache, - _life: PhantomData<&'a mut ()>, -} - -impl<'a, T> ObjRef<'a, T> { - pub fn to_longlive(mut self) -> ObjRef<'static, T> { - ObjRef { - inner: self.inner.take(), - cache: ObjCache(self.cache.0.clone()), - _life: PhantomData, - } - } - - #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { - let inner = self.inner.as_mut().unwrap(); - inner.write(modify)?; - - self.cache.get_inner_mut().dirty.insert(inner.as_ptr()); - - Ok(()) - } -} - -impl<'a, T> Deref for ObjRef<'a, T> { - type Target = Obj; - fn deref(&self) -> &Obj { - self.inner.as_ref().unwrap() - } -} - -impl<'a, T> Drop for ObjRef<'a, T> { - fn drop(&mut self) { - let mut inner = self.inner.take().unwrap(); - let ptr = inner.as_ptr(); - let cache = self.cache.get_inner_mut(); - match cache.pinned.remove(&ptr) { - Some(true) => { - inner.dirty = None; - } - _ => { - cache.cached.put(ptr, inner); - } - } - } -} - -/// A persistent item storage backed by linear logical space. New items can be created and old -/// items could be retrieved or dropped. -pub trait ShaleStore: Debug { - /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. - fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; - /// Allocate a new item. - fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; - /// Free an item and recycle its space when applicable. - fn free_item(&mut self, item: ObjPtr) -> Result<(), ShaleError>; - /// Flush all dirty writes. - fn flush_dirty(&self) -> Option<()>; -} - -/// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient -/// implementation could be directly transmuting to/from a POD struct. But sometimes necessary -/// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. -pub trait Storable { - fn dehydrated_len(&self) -> u64; - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>; - fn hydrate(addr: u64, mem: &T) -> Result - where - Self: Sized; - fn is_mem_mapped(&self) -> bool { - false - } -} - -pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { - let mut buff = vec![0; item.dehydrated_len() as usize]; - item.dehydrate(&mut buff)?; - Ok(buff) -} - -/// Reference implementation of [TypedView]. It takes any type that implements [Storable] -pub struct StoredView { - decoded: T, - mem: Box>, - offset: u64, - len_limit: u64, -} - -impl Debug for StoredView { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let StoredView { - decoded, - offset, - len_limit, - mem: _, - } = self; - f.debug_struct("StoredView") - .field("decoded", decoded) - .field("offset", offset) - .field("len_limit", len_limit) - .finish() - } -} - -impl Deref for StoredView { - type Target = T; - fn deref(&self) -> &T { - &self.decoded - } -} - -impl TypedView for StoredView { - fn get_offset(&self) -> u64 { - self.offset - } - - fn get_mem_store(&self) -> &dyn CachedStore { - &**self.mem - } - - fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore { - &mut **self.mem - } - - fn estimate_mem_image(&self) -> Option { - let len = self.decoded.dehydrated_len(); - if len > self.len_limit { - None - } else { - Some(len) - } - } - - fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { - self.decoded.dehydrate(mem_image) - } - - fn write(&mut self) -> &mut T { - &mut self.decoded - } - fn is_mem_mapped(&self) -> bool { - self.decoded.is_mem_mapped() - } -} - -impl StoredView { - #[inline(always)] - fn new(offset: u64, len_limit: u64, space: &U) -> Result { - let decoded = T::hydrate(offset, space)?; - Ok(Self { - offset, - decoded, - mem: space.get_shared(), - len_limit, - }) - } - - #[inline(always)] - fn from_hydrated( - offset: u64, - len_limit: u64, - decoded: T, - space: &dyn CachedStore, - ) -> Result { - Ok(Self { - offset, - decoded, - mem: space.get_shared(), - len_limit, - }) - } - - #[inline(always)] - pub fn ptr_to_obj( - store: &U, - ptr: ObjPtr, - len_limit: u64, - ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Box::new(Self::new( - ptr.addr(), - len_limit, - store, - )?))) - } - - #[inline(always)] - pub fn item_to_obj( - store: &dyn CachedStore, - addr: u64, - len_limit: u64, - decoded: T, - ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Box::new(Self::from_hydrated( - addr, len_limit, decoded, store, - )?))) - } -} - -impl StoredView { - fn new_from_slice( - offset: u64, - len_limit: u64, - decoded: T, - space: &dyn CachedStore, - ) -> Result { - Ok(Self { - offset, - decoded, - mem: space.get_shared(), - len_limit, - }) - } - - pub fn slice( - s: &Obj, - offset: u64, - length: u64, - decoded: U, - ) -> Result, ShaleError> { - let addr_ = s.value.get_offset() + offset; - if s.dirty.is_some() { - return Err(ShaleError::InvalidObj { - addr: offset, - obj_type: type_name::(), - error: "dirty write", - }); - } - let r = Box::new(StoredView::new_from_slice( - addr_, - length, - decoded, - s.value.get_mem_store(), - )?); - Ok(Obj { - value: r, - dirty: None, - }) - } -} - -impl ObjPtr { - const MSIZE: u64 = 8; -} - -impl Storable for ObjPtr { - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - use std::io::{Cursor, Write}; - Cursor::new(to).write_all(&self.addr().to_le_bytes())?; - Ok(()) - } - - fn hydrate(addr: u64, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let addrdyn = raw.deref(); - let addrvec = addrdyn.as_deref(); - Ok(Self::new_from_addr(u64::from_le_bytes( - addrvec.try_into().unwrap(), - ))) - } -} - -struct ObjCacheInner { - cached: lru::LruCache, Obj>, - pinned: HashMap, bool>, - dirty: HashSet>, -} - -/// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. -#[derive(Debug)] -pub struct ObjCache(Rc>>); - -impl ObjCache { - pub fn new(capacity: usize) -> Self { - Self(Rc::new(UnsafeCell::new(ObjCacheInner { - cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), - pinned: HashMap::new(), - dirty: HashSet::new(), - }))) - } - - #[inline(always)] - #[allow(clippy::mut_from_ref)] - // TODO: Refactor this function - fn get_inner_mut(&self) -> &mut ObjCacheInner { - unsafe { &mut *self.0.get() } - } - - #[inline(always)] - pub fn get(&'_ self, ptr: ObjPtr) -> Result>, ShaleError> { - let inner = &mut self.get_inner_mut(); - if let Some(r) = inner.cached.pop(&ptr) { - if inner.pinned.insert(ptr, false).is_some() { - return Err(ShaleError::InvalidObj { - addr: ptr.addr(), - obj_type: type_name::(), - error: "address already in use", - }); - } - return Ok(Some(ObjRef { - inner: Some(r), - cache: Self(self.0.clone()), - _life: PhantomData, - })); - } - Ok(None) - } - - #[inline(always)] - pub fn put(&'_ self, inner: Obj) -> ObjRef<'_, T> { - let ptr = inner.as_ptr(); - self.get_inner_mut().pinned.insert(ptr, false); - ObjRef { - inner: Some(inner), - cache: Self(self.0.clone()), - _life: PhantomData, - } - } - - #[inline(always)] - pub fn pop(&self, ptr: ObjPtr) { - let inner = self.get_inner_mut(); - if let Some(f) = inner.pinned.get_mut(&ptr) { - *f = true - } - if let Some(mut r) = inner.cached.pop(&ptr) { - r.dirty = None - } - inner.dirty.remove(&ptr); - } - - pub fn flush_dirty(&self) -> Option<()> { - let inner = self.get_inner_mut(); - if !inner.pinned.is_empty() { - return None; - } - for ptr in std::mem::take(&mut inner.dirty) { - if let Some(r) = inner.cached.peek_mut(&ptr) { - r.flush_dirty() - } - } - Some(()) - } -} diff --git a/shale-archive/src/util.rs b/shale-archive/src/util.rs deleted file mode 100644 index e532ddebf679..000000000000 --- a/shale-archive/src/util.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[allow(dead_code)] -pub fn get_raw_bytes(data: &T) -> Vec { - unsafe { - std::slice::from_raw_parts(data as *const T as *const u8, std::mem::size_of::()).to_vec() - } -} From 4a75319ecb9dc28e3ed035767fb43650899b64a6 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:15:08 -0700 Subject: [PATCH 0206/1053] Add mermaid diagram for proposal (#154) --- firewood/src/v2/db.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 122274e4d334..827d3d7e3034 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -9,6 +9,16 @@ use async_trait::async_trait; use crate::v2::api::{self, Batch, KeyType, ValueType}; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// ```mermaid +/// graph LR +/// RevRootHash --> DBRevID +/// RevHeight --> DBRevID +/// DBRevID -- Identify --> DbRev +/// Db/Proposal -- propose with batch --> Proposal +/// Proposal -- translate --> DbRev +/// DB -- commit proposal --> DB +/// ``` #[derive(Debug, Default)] pub struct Db { latest_cache: Mutex>>, From d59863c3fa7658f8af2584d80eb090bd107ee27b Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 18 Jul 2023 13:32:48 -0400 Subject: [PATCH 0207/1053] Fix Shale-doc link (#158) --- firewood-shale/src/cached.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood-shale/src/cached.rs b/firewood-shale/src/cached.rs index 63b8e02b5791..cba6f7117644 100644 --- a/firewood-shale/src/cached.rs +++ b/firewood-shale/src/cached.rs @@ -7,7 +7,7 @@ use std::{ }; /// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying -/// out stuff (persistent data structures) built on [ShaleStore] in memory, without having to write +/// out stuff (persistent data structures) built on [ShaleStore](crate::ShaleStore) in memory, without having to write /// your own [CachedStore] implementation. #[derive(Debug)] pub struct PlainMem { From 9aff915e886a8ce0cff58daa3af0ff7994204021 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 20 Jul 2023 08:54:40 -0700 Subject: [PATCH 0208/1053] Make proposals owned by API caller (#161) --- firewood/src/v2/api.rs | 18 ++++++--- firewood/src/v2/db.rs | 83 +++++++++++------------------------------- 2 files changed, 34 insertions(+), 67 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 86c431784117..4af5d84366a7 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,4 +1,8 @@ -use std::{collections::HashMap, fmt::Debug, sync::Weak}; +use std::{ + collections::HashMap, + fmt::Debug, + sync::{Arc, Weak}, +}; use async_trait::async_trait; @@ -111,9 +115,9 @@ pub trait Db { /// [BatchOp::Delete] operations to apply /// async fn propose( - &mut self, + self: Arc, data: Batch, - ) -> Result, Error>; + ) -> Result, Error>; } /// A view of the database at a specific time. These are wrapped with @@ -165,12 +169,14 @@ pub trait DbView { /// obtain proofs. #[async_trait] pub trait Proposal: DbView { + type Proposal: DbView + Proposal; + /// Commit this revision /// /// # Return value /// /// * A weak reference to a new historical view - async fn commit(self) -> Result, Error>; + async fn commit(self) -> Result; /// Propose a new revision on top of an existing proposal /// @@ -183,7 +189,7 @@ pub trait Proposal: DbView { /// A weak reference to a new proposal /// async fn propose( - &self, + self: Arc, data: Batch, - ) -> Result, Error>; + ) -> Result, Error>; } diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 827d3d7e3034..5ecca8fd0804 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,8 +1,7 @@ use std::{ - borrow::Borrow, collections::BTreeMap, fmt::Debug, - sync::{Arc, Mutex, RwLock, Weak}, + sync::{Arc, Mutex, Weak}, }; use async_trait::async_trait; @@ -39,40 +38,27 @@ impl api::Db for Db { } async fn propose( - &mut self, + self: Arc, data: Batch, - ) -> Result, api::Error> { + ) -> Result, api::Error> { let mut dbview_latest_cache_guard = self.latest_cache.lock().unwrap(); if dbview_latest_cache_guard.is_none() { // TODO: actually get the latest dbview - *dbview_latest_cache_guard = Some(Arc::new(DbView { - proposals: RwLock::new(vec![]), - })); + *dbview_latest_cache_guard = Some(Arc::new(DbView {})); }; - let mut proposal_guard = dbview_latest_cache_guard - .as_ref() - .unwrap() - .proposals - .write() - .unwrap(); - - let proposal = Arc::new(Proposal::new( + let proposal = Proposal::new( ProposalBase::View(dbview_latest_cache_guard.clone().unwrap()), data, - )); - - proposal_guard.push(proposal.clone()); + ); - Ok(Arc::downgrade(&proposal)) + Ok(Arc::new(proposal)) } } #[derive(Debug)] -pub struct DbView { - proposals: RwLock>>, -} +pub struct DbView; #[async_trait] impl api::DbView for DbView { @@ -117,7 +103,6 @@ enum KeyOp { pub struct Proposal { base: ProposalBase, delta: BTreeMap, KeyOp>>, - children: RwLock>>, } impl Clone for Proposal { @@ -125,7 +110,6 @@ impl Clone for Proposal { Self { base: self.base.clone(), delta: self.delta.clone(), - children: RwLock::new(vec![]), } } } @@ -142,11 +126,7 @@ impl Proposal { }) .collect(); - Self { - base, - delta, - children: RwLock::new(vec![]), - } + Self { base, delta } } } @@ -191,35 +171,19 @@ impl api::DbView for Proposal { #[async_trait] impl api::Proposal for Proposal { + type Proposal = Proposal; + async fn propose( - &self, + self: Arc, data: Batch, - ) -> Result, api::Error> { + ) -> Result, api::Error> { // find the Arc for this base proposal from the parent - let children_guard = match &self.base { - ProposalBase::Proposal(p) => p.children.read().unwrap(), - ProposalBase::View(v) => v.proposals.read().unwrap(), - }; - - let arc = children_guard - .iter() - .find(|&c| std::ptr::eq(c.borrow() as *const _, self as *const _)); - - if arc.is_none() { - return Err(api::Error::InvalidProposal); - } - - let proposal = Arc::new(Proposal::new( - ProposalBase::Proposal(arc.unwrap().clone()), - data, - )); - - self.children.write().unwrap().push(proposal.clone()); + let proposal = Proposal::new(ProposalBase::Proposal(self), data); - Ok(Arc::downgrade(&proposal)) + Ok(Arc::new(proposal)) } - async fn commit(self) -> Result, api::Error> { + async fn commit(self) -> Result { todo!() } } @@ -235,7 +199,6 @@ impl std::ops::Add for Proposal { let proposal = Proposal { base: self.base, delta, - children: RwLock::new(Vec::new()), }; Arc::new(proposal) @@ -253,7 +216,6 @@ impl std::ops::Add for &Proposal { let proposal = Proposal { base: self.base.clone(), delta, - children: RwLock::new(Vec::new()), }; Arc::new(proposal) @@ -270,7 +232,7 @@ mod test { #[tokio::test] async fn test_basic_proposal() -> Result<(), crate::v2::api::Error> { - let mut db = Db::default(); + let db = Arc::new(Db::default()); let batch = vec![ BatchOp::Put { @@ -280,7 +242,7 @@ mod test { BatchOp::Delete { key: b"z" }, ]; - let proposal = db.propose(batch).await?.upgrade().unwrap(); + let proposal = db.propose(batch).await?; assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); @@ -294,7 +256,7 @@ mod test { #[tokio::test] async fn test_nested_proposal() -> Result<(), crate::v2::api::Error> { - let mut db = Db::default(); + let db = Arc::new(Db::default()); // create proposal1 which adds key "k" with value "v" and deletes "z" let batch = vec![ @@ -305,17 +267,16 @@ mod test { BatchOp::Delete { key: b"z" }, ]; - let proposal1 = db.propose(batch).await?.upgrade().unwrap(); + let proposal1 = db.propose(batch).await?; // create proposal2 which adds key "z" with value "undo" let proposal2 = proposal1 + .clone() .propose(vec![BatchOp::Put { key: b"z", value: "undo", }]) - .await? - .upgrade() - .unwrap(); + .await?; // both proposals still have (k,v) assert_eq!(proposal1.val(b"k").await.unwrap(), b"v"); assert_eq!(proposal2.val(b"k").await.unwrap(), b"v"); From c2111b88fa8a4670ba5cebe153e27eda6e3fb9bc Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 20 Jul 2023 09:12:37 -0700 Subject: [PATCH 0209/1053] Redefine Revision and View (#160) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd8e7f71b666..c24a9e2756b0 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,10 @@ upstream. ## Termimology -* `Revision` - A point-in-time state/version of the trie. This represents the entire - trie, including all `Key`/`Value`s at that point in time, and all `Node`s. -* `View` - This is a synonym for a `Revision`. +* `Revision` - A historical point-in-time state/version of the trie. This + represents the entire trie, including all `Key`/`Value`s at that point + in time, and all `Node`s. +* `View` - This is the interface to read from a `Revision` or a `Proposal`. * `Node` - A node is a portion of a trie. A trie consists of nodes that are linked together. Nodes can point to other nodes and/or contain `Key`/`Value` pairs. * `Hash` - In this context, this refers to the merkle hash for a specific node. From bd6399cf833813e56c06a0bcdc779c1f7a3bd434 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 20 Jul 2023 18:01:14 -0400 Subject: [PATCH 0210/1053] Remove directory stutter naming (#162) --- Cargo.toml | 6 +++--- firewood/Cargo.toml | 6 +++--- {firewood-growth-ring => growth-ring}/Cargo.toml | 0 {firewood-growth-ring => growth-ring}/README.rst | 0 {firewood-growth-ring => growth-ring}/examples/.gitignore | 0 {firewood-growth-ring => growth-ring}/examples/demo1.rs | 0 {firewood-growth-ring => growth-ring}/src/lib.rs | 0 {firewood-growth-ring => growth-ring}/src/wal.rs | 0 {firewood-growth-ring => growth-ring}/src/walerror.rs | 0 {firewood-growth-ring => growth-ring}/tests/common/mod.rs | 0 {firewood-growth-ring => growth-ring}/tests/rand_fail.rs | 0 {firewood-libaio => libaio}/Cargo.toml | 0 {firewood-libaio => libaio}/build.rs | 0 {firewood-libaio => libaio}/libaio/.gitignore | 0 {firewood-libaio => libaio}/libaio/COPYING | 0 {firewood-libaio => libaio}/libaio/Makefile | 0 {firewood-libaio => libaio}/libaio/aio_ring.h | 0 {firewood-libaio => libaio}/libaio/compat-0_1.c | 0 {firewood-libaio => libaio}/libaio/io_cancel.c | 0 {firewood-libaio => libaio}/libaio/io_destroy.c | 0 {firewood-libaio => libaio}/libaio/io_getevents.c | 0 {firewood-libaio => libaio}/libaio/io_pgetevents.c | 0 {firewood-libaio => libaio}/libaio/io_queue_init.c | 0 {firewood-libaio => libaio}/libaio/io_queue_release.c | 0 {firewood-libaio => libaio}/libaio/io_queue_run.c | 0 {firewood-libaio => libaio}/libaio/io_queue_wait.c | 0 {firewood-libaio => libaio}/libaio/io_setup.c | 0 {firewood-libaio => libaio}/libaio/io_submit.c | 0 {firewood-libaio => libaio}/libaio/libaio.h | 0 {firewood-libaio => libaio}/libaio/libaio.map | 0 {firewood-libaio => libaio}/libaio/raw_syscall.c | 0 {firewood-libaio => libaio}/libaio/syscall-alpha.h | 0 {firewood-libaio => libaio}/libaio/syscall-arm.h | 0 {firewood-libaio => libaio}/libaio/syscall-generic.h | 0 {firewood-libaio => libaio}/libaio/syscall-i386.h | 0 {firewood-libaio => libaio}/libaio/syscall-ia64.h | 0 {firewood-libaio => libaio}/libaio/syscall-ppc.h | 0 {firewood-libaio => libaio}/libaio/syscall-s390.h | 0 {firewood-libaio => libaio}/libaio/syscall-sparc.h | 0 {firewood-libaio => libaio}/libaio/syscall-x86_64.h | 0 {firewood-libaio => libaio}/libaio/syscall.h | 0 {firewood-libaio => libaio}/libaio/vsys_def.h | 0 {firewood-libaio => libaio}/src/abi.rs | 0 {firewood-libaio => libaio}/src/lib.rs | 0 {firewood-libaio => libaio}/tests/simple_test.rs | 0 {firewood-shale => shale}/Cargo.toml | 0 {firewood-shale => shale}/LICENSE | 0 {firewood-shale => shale}/benches/shale-bench.rs | 0 {firewood-shale => shale}/src/cached.rs | 0 {firewood-shale => shale}/src/compact.rs | 0 {firewood-shale => shale}/src/lib.rs | 0 51 files changed, 6 insertions(+), 6 deletions(-) rename {firewood-growth-ring => growth-ring}/Cargo.toml (100%) rename {firewood-growth-ring => growth-ring}/README.rst (100%) rename {firewood-growth-ring => growth-ring}/examples/.gitignore (100%) rename {firewood-growth-ring => growth-ring}/examples/demo1.rs (100%) rename {firewood-growth-ring => growth-ring}/src/lib.rs (100%) rename {firewood-growth-ring => growth-ring}/src/wal.rs (100%) rename {firewood-growth-ring => growth-ring}/src/walerror.rs (100%) rename {firewood-growth-ring => growth-ring}/tests/common/mod.rs (100%) rename {firewood-growth-ring => growth-ring}/tests/rand_fail.rs (100%) rename {firewood-libaio => libaio}/Cargo.toml (100%) rename {firewood-libaio => libaio}/build.rs (100%) rename {firewood-libaio => libaio}/libaio/.gitignore (100%) rename {firewood-libaio => libaio}/libaio/COPYING (100%) rename {firewood-libaio => libaio}/libaio/Makefile (100%) rename {firewood-libaio => libaio}/libaio/aio_ring.h (100%) rename {firewood-libaio => libaio}/libaio/compat-0_1.c (100%) rename {firewood-libaio => libaio}/libaio/io_cancel.c (100%) rename {firewood-libaio => libaio}/libaio/io_destroy.c (100%) rename {firewood-libaio => libaio}/libaio/io_getevents.c (100%) rename {firewood-libaio => libaio}/libaio/io_pgetevents.c (100%) rename {firewood-libaio => libaio}/libaio/io_queue_init.c (100%) rename {firewood-libaio => libaio}/libaio/io_queue_release.c (100%) rename {firewood-libaio => libaio}/libaio/io_queue_run.c (100%) rename {firewood-libaio => libaio}/libaio/io_queue_wait.c (100%) rename {firewood-libaio => libaio}/libaio/io_setup.c (100%) rename {firewood-libaio => libaio}/libaio/io_submit.c (100%) rename {firewood-libaio => libaio}/libaio/libaio.h (100%) rename {firewood-libaio => libaio}/libaio/libaio.map (100%) rename {firewood-libaio => libaio}/libaio/raw_syscall.c (100%) rename {firewood-libaio => libaio}/libaio/syscall-alpha.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-arm.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-generic.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-i386.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-ia64.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-ppc.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-s390.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-sparc.h (100%) rename {firewood-libaio => libaio}/libaio/syscall-x86_64.h (100%) rename {firewood-libaio => libaio}/libaio/syscall.h (100%) rename {firewood-libaio => libaio}/libaio/vsys_def.h (100%) rename {firewood-libaio => libaio}/src/abi.rs (100%) rename {firewood-libaio => libaio}/src/lib.rs (100%) rename {firewood-libaio => libaio}/tests/simple_test.rs (100%) rename {firewood-shale => shale}/Cargo.toml (100%) rename {firewood-shale => shale}/LICENSE (100%) rename {firewood-shale => shale}/benches/shale-bench.rs (100%) rename {firewood-shale => shale}/src/cached.rs (100%) rename {firewood-shale => shale}/src/compact.rs (100%) rename {firewood-shale => shale}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 0742fcfcbda3..a1d8190aee07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "firewood-growth-ring", - "firewood-libaio", - "firewood-shale", + "growth-ring", + "libaio", + "shale", "firewood", "fwdctl", ] diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index e47fcd18b58c..079643b8f7cc 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -20,9 +20,9 @@ aquamarine = "0.3.1" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" -firewood-growth-ring = { version = "0.0.3", path = "../firewood-growth-ring" } -firewood-libaio = {version = "0.0.3", path = "../firewood-libaio" } -firewood-shale = { version = "0.0.3", path = "../firewood-shale" } +firewood-growth-ring = { version = "0.0.3", path = "../growth-ring" } +firewood-libaio = {version = "0.0.3", path = "../libaio" } +firewood-shale = { version = "0.0.3", path = "../shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.11.0" diff --git a/firewood-growth-ring/Cargo.toml b/growth-ring/Cargo.toml similarity index 100% rename from firewood-growth-ring/Cargo.toml rename to growth-ring/Cargo.toml diff --git a/firewood-growth-ring/README.rst b/growth-ring/README.rst similarity index 100% rename from firewood-growth-ring/README.rst rename to growth-ring/README.rst diff --git a/firewood-growth-ring/examples/.gitignore b/growth-ring/examples/.gitignore similarity index 100% rename from firewood-growth-ring/examples/.gitignore rename to growth-ring/examples/.gitignore diff --git a/firewood-growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs similarity index 100% rename from firewood-growth-ring/examples/demo1.rs rename to growth-ring/examples/demo1.rs diff --git a/firewood-growth-ring/src/lib.rs b/growth-ring/src/lib.rs similarity index 100% rename from firewood-growth-ring/src/lib.rs rename to growth-ring/src/lib.rs diff --git a/firewood-growth-ring/src/wal.rs b/growth-ring/src/wal.rs similarity index 100% rename from firewood-growth-ring/src/wal.rs rename to growth-ring/src/wal.rs diff --git a/firewood-growth-ring/src/walerror.rs b/growth-ring/src/walerror.rs similarity index 100% rename from firewood-growth-ring/src/walerror.rs rename to growth-ring/src/walerror.rs diff --git a/firewood-growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs similarity index 100% rename from firewood-growth-ring/tests/common/mod.rs rename to growth-ring/tests/common/mod.rs diff --git a/firewood-growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs similarity index 100% rename from firewood-growth-ring/tests/rand_fail.rs rename to growth-ring/tests/rand_fail.rs diff --git a/firewood-libaio/Cargo.toml b/libaio/Cargo.toml similarity index 100% rename from firewood-libaio/Cargo.toml rename to libaio/Cargo.toml diff --git a/firewood-libaio/build.rs b/libaio/build.rs similarity index 100% rename from firewood-libaio/build.rs rename to libaio/build.rs diff --git a/firewood-libaio/libaio/.gitignore b/libaio/libaio/.gitignore similarity index 100% rename from firewood-libaio/libaio/.gitignore rename to libaio/libaio/.gitignore diff --git a/firewood-libaio/libaio/COPYING b/libaio/libaio/COPYING similarity index 100% rename from firewood-libaio/libaio/COPYING rename to libaio/libaio/COPYING diff --git a/firewood-libaio/libaio/Makefile b/libaio/libaio/Makefile similarity index 100% rename from firewood-libaio/libaio/Makefile rename to libaio/libaio/Makefile diff --git a/firewood-libaio/libaio/aio_ring.h b/libaio/libaio/aio_ring.h similarity index 100% rename from firewood-libaio/libaio/aio_ring.h rename to libaio/libaio/aio_ring.h diff --git a/firewood-libaio/libaio/compat-0_1.c b/libaio/libaio/compat-0_1.c similarity index 100% rename from firewood-libaio/libaio/compat-0_1.c rename to libaio/libaio/compat-0_1.c diff --git a/firewood-libaio/libaio/io_cancel.c b/libaio/libaio/io_cancel.c similarity index 100% rename from firewood-libaio/libaio/io_cancel.c rename to libaio/libaio/io_cancel.c diff --git a/firewood-libaio/libaio/io_destroy.c b/libaio/libaio/io_destroy.c similarity index 100% rename from firewood-libaio/libaio/io_destroy.c rename to libaio/libaio/io_destroy.c diff --git a/firewood-libaio/libaio/io_getevents.c b/libaio/libaio/io_getevents.c similarity index 100% rename from firewood-libaio/libaio/io_getevents.c rename to libaio/libaio/io_getevents.c diff --git a/firewood-libaio/libaio/io_pgetevents.c b/libaio/libaio/io_pgetevents.c similarity index 100% rename from firewood-libaio/libaio/io_pgetevents.c rename to libaio/libaio/io_pgetevents.c diff --git a/firewood-libaio/libaio/io_queue_init.c b/libaio/libaio/io_queue_init.c similarity index 100% rename from firewood-libaio/libaio/io_queue_init.c rename to libaio/libaio/io_queue_init.c diff --git a/firewood-libaio/libaio/io_queue_release.c b/libaio/libaio/io_queue_release.c similarity index 100% rename from firewood-libaio/libaio/io_queue_release.c rename to libaio/libaio/io_queue_release.c diff --git a/firewood-libaio/libaio/io_queue_run.c b/libaio/libaio/io_queue_run.c similarity index 100% rename from firewood-libaio/libaio/io_queue_run.c rename to libaio/libaio/io_queue_run.c diff --git a/firewood-libaio/libaio/io_queue_wait.c b/libaio/libaio/io_queue_wait.c similarity index 100% rename from firewood-libaio/libaio/io_queue_wait.c rename to libaio/libaio/io_queue_wait.c diff --git a/firewood-libaio/libaio/io_setup.c b/libaio/libaio/io_setup.c similarity index 100% rename from firewood-libaio/libaio/io_setup.c rename to libaio/libaio/io_setup.c diff --git a/firewood-libaio/libaio/io_submit.c b/libaio/libaio/io_submit.c similarity index 100% rename from firewood-libaio/libaio/io_submit.c rename to libaio/libaio/io_submit.c diff --git a/firewood-libaio/libaio/libaio.h b/libaio/libaio/libaio.h similarity index 100% rename from firewood-libaio/libaio/libaio.h rename to libaio/libaio/libaio.h diff --git a/firewood-libaio/libaio/libaio.map b/libaio/libaio/libaio.map similarity index 100% rename from firewood-libaio/libaio/libaio.map rename to libaio/libaio/libaio.map diff --git a/firewood-libaio/libaio/raw_syscall.c b/libaio/libaio/raw_syscall.c similarity index 100% rename from firewood-libaio/libaio/raw_syscall.c rename to libaio/libaio/raw_syscall.c diff --git a/firewood-libaio/libaio/syscall-alpha.h b/libaio/libaio/syscall-alpha.h similarity index 100% rename from firewood-libaio/libaio/syscall-alpha.h rename to libaio/libaio/syscall-alpha.h diff --git a/firewood-libaio/libaio/syscall-arm.h b/libaio/libaio/syscall-arm.h similarity index 100% rename from firewood-libaio/libaio/syscall-arm.h rename to libaio/libaio/syscall-arm.h diff --git a/firewood-libaio/libaio/syscall-generic.h b/libaio/libaio/syscall-generic.h similarity index 100% rename from firewood-libaio/libaio/syscall-generic.h rename to libaio/libaio/syscall-generic.h diff --git a/firewood-libaio/libaio/syscall-i386.h b/libaio/libaio/syscall-i386.h similarity index 100% rename from firewood-libaio/libaio/syscall-i386.h rename to libaio/libaio/syscall-i386.h diff --git a/firewood-libaio/libaio/syscall-ia64.h b/libaio/libaio/syscall-ia64.h similarity index 100% rename from firewood-libaio/libaio/syscall-ia64.h rename to libaio/libaio/syscall-ia64.h diff --git a/firewood-libaio/libaio/syscall-ppc.h b/libaio/libaio/syscall-ppc.h similarity index 100% rename from firewood-libaio/libaio/syscall-ppc.h rename to libaio/libaio/syscall-ppc.h diff --git a/firewood-libaio/libaio/syscall-s390.h b/libaio/libaio/syscall-s390.h similarity index 100% rename from firewood-libaio/libaio/syscall-s390.h rename to libaio/libaio/syscall-s390.h diff --git a/firewood-libaio/libaio/syscall-sparc.h b/libaio/libaio/syscall-sparc.h similarity index 100% rename from firewood-libaio/libaio/syscall-sparc.h rename to libaio/libaio/syscall-sparc.h diff --git a/firewood-libaio/libaio/syscall-x86_64.h b/libaio/libaio/syscall-x86_64.h similarity index 100% rename from firewood-libaio/libaio/syscall-x86_64.h rename to libaio/libaio/syscall-x86_64.h diff --git a/firewood-libaio/libaio/syscall.h b/libaio/libaio/syscall.h similarity index 100% rename from firewood-libaio/libaio/syscall.h rename to libaio/libaio/syscall.h diff --git a/firewood-libaio/libaio/vsys_def.h b/libaio/libaio/vsys_def.h similarity index 100% rename from firewood-libaio/libaio/vsys_def.h rename to libaio/libaio/vsys_def.h diff --git a/firewood-libaio/src/abi.rs b/libaio/src/abi.rs similarity index 100% rename from firewood-libaio/src/abi.rs rename to libaio/src/abi.rs diff --git a/firewood-libaio/src/lib.rs b/libaio/src/lib.rs similarity index 100% rename from firewood-libaio/src/lib.rs rename to libaio/src/lib.rs diff --git a/firewood-libaio/tests/simple_test.rs b/libaio/tests/simple_test.rs similarity index 100% rename from firewood-libaio/tests/simple_test.rs rename to libaio/tests/simple_test.rs diff --git a/firewood-shale/Cargo.toml b/shale/Cargo.toml similarity index 100% rename from firewood-shale/Cargo.toml rename to shale/Cargo.toml diff --git a/firewood-shale/LICENSE b/shale/LICENSE similarity index 100% rename from firewood-shale/LICENSE rename to shale/LICENSE diff --git a/firewood-shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs similarity index 100% rename from firewood-shale/benches/shale-bench.rs rename to shale/benches/shale-bench.rs diff --git a/firewood-shale/src/cached.rs b/shale/src/cached.rs similarity index 100% rename from firewood-shale/src/cached.rs rename to shale/src/cached.rs diff --git a/firewood-shale/src/compact.rs b/shale/src/compact.rs similarity index 100% rename from firewood-shale/src/compact.rs rename to shale/src/compact.rs diff --git a/firewood-shale/src/lib.rs b/shale/src/lib.rs similarity index 100% rename from firewood-shale/src/lib.rs rename to shale/src/lib.rs From 8577810a40077a4a149074f0913f324fd0403dd4 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 24 Jul 2023 19:57:07 -0400 Subject: [PATCH 0211/1053] Add Shale refactor to seasoned milestone (#164) Signed-off-by: Richard Pringle --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c24a9e2756b0..082f9aee39a1 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ propose a batch against any existing proposed revision. - [ ] Be able to quickly commit a batch that has been proposed. Note that this invalidates all other proposals that are not children of the committed proposed batch. - [ ] Add metric reporting +- [ ] Refactor `Shale` to be more idiomatic ### Dried milestone The focus of this milestone will be to support synchronization to other From 3b399c1e994138fd9b6bea74772a16c4a4536f48 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 26 Jul 2023 13:40:59 -0700 Subject: [PATCH 0212/1053] Implement EmptyDb (#159) Signed-off-by: Richard Pringle Co-authored-by: Richard Pringle --- firewood/src/v2/api.rs | 20 ++- firewood/src/v2/db.rs | 266 +++++-------------------------------- firewood/src/v2/emptydb.rs | 149 +++++++++++++++++++++ firewood/src/v2/mod.rs | 2 + firewood/src/v2/propose.rs | 161 ++++++++++++++++++++++ 5 files changed, 350 insertions(+), 248 deletions(-) create mode 100644 firewood/src/v2/emptydb.rs create mode 100644 firewood/src/v2/propose.rs diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 4af5d84366a7..65efcf05edaa 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,8 +1,4 @@ -use std::{ - collections::HashMap, - fmt::Debug, - sync::{Arc, Weak}, -}; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; @@ -99,7 +95,7 @@ pub trait Db { /// # Arguments /// /// - `hash` - Identifies the revision for the view - async fn revision(&self, hash: HashKey) -> Result, Error>; + async fn revision(&self, hash: HashKey) -> Result, Error>; /// Get the hash of the most recently committed version async fn root_hash(&self) -> Result; @@ -115,9 +111,9 @@ pub trait Db { /// [BatchOp::Delete] operations to apply /// async fn propose( - self: Arc, + &self, data: Batch, - ) -> Result, Error>; + ) -> Result; } /// A view of the database at a specific time. These are wrapped with @@ -175,8 +171,8 @@ pub trait Proposal: DbView { /// /// # Return value /// - /// * A weak reference to a new historical view - async fn commit(self) -> Result; + /// * A reference to a new historical view + async fn commit(self: Arc) -> Result, Error>; /// Propose a new revision on top of an existing proposal /// @@ -186,10 +182,10 @@ pub trait Proposal: DbView { /// /// # Return value /// - /// A weak reference to a new proposal + /// A reference to a new proposal /// async fn propose( self: Arc, data: Batch, - ) -> Result, Error>; + ) -> Result; } diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 5ecca8fd0804..23dece760f45 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,13 +1,13 @@ -use std::{ - collections::BTreeMap, - fmt::Debug, - sync::{Arc, Mutex, Weak}, -}; +use std::{fmt::Debug, ops::DerefMut, sync::Arc}; + +use tokio::sync::Mutex; use async_trait::async_trait; use crate::v2::api::{self, Batch, KeyType, ValueType}; +use super::propose; + #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid /// graph LR @@ -19,17 +19,22 @@ use crate::v2::api::{self, Batch, KeyType, ValueType}; /// DB -- commit proposal --> DB /// ``` #[derive(Debug, Default)] -pub struct Db { - latest_cache: Mutex>>, +pub struct Db { + latest_cache: Mutex>>, } #[async_trait] -impl api::Db for Db { - type Historical = DbView; +impl api::Db for Db +where + T: api::DbView, + T: Send + Sync, + T: Default, +{ + type Historical = T; - type Proposal = Proposal; + type Proposal = propose::Proposal; - async fn revision(&self, _hash: api::HashKey) -> Result, api::Error> { + async fn revision(&self, _hash: api::HashKey) -> Result, api::Error> { todo!() } @@ -38,26 +43,27 @@ impl api::Db for Db { } async fn propose( - self: Arc, + &self, data: Batch, - ) -> Result, api::Error> { - let mut dbview_latest_cache_guard = self.latest_cache.lock().unwrap(); - - if dbview_latest_cache_guard.is_none() { - // TODO: actually get the latest dbview - *dbview_latest_cache_guard = Some(Arc::new(DbView {})); + ) -> Result { + let mut dbview_latest_cache_guard = self.latest_cache.lock().await; + + let revision = match dbview_latest_cache_guard.deref_mut() { + Some(revision) => revision.clone(), + revision => { + let default_revision = Arc::new(T::default()); + *revision = Some(default_revision.clone()); + default_revision + } }; - let proposal = Proposal::new( - ProposalBase::View(dbview_latest_cache_guard.clone().unwrap()), - data, - ); + let proposal = Self::Proposal::new(propose::ProposalBase::View(revision), data); - Ok(Arc::new(proposal)) + Ok(proposal) } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct DbView; #[async_trait] @@ -86,215 +92,3 @@ impl api::DbView for DbView { todo!() } } - -#[derive(Clone, Debug)] -enum ProposalBase { - Proposal(Arc), - View(Arc), -} - -#[derive(Clone, Debug)] -enum KeyOp { - Put(V), - Delete, -} - -#[derive(Debug)] -pub struct Proposal { - base: ProposalBase, - delta: BTreeMap, KeyOp>>, -} - -impl Clone for Proposal { - fn clone(&self) -> Self { - Self { - base: self.base.clone(), - delta: self.delta.clone(), - } - } -} - -impl Proposal { - fn new(base: ProposalBase, batch: Batch) -> Self { - let delta = batch - .into_iter() - .map(|op| match op { - api::BatchOp::Put { key, value } => { - (key.as_ref().to_vec(), KeyOp::Put(value.as_ref().to_vec())) - } - api::BatchOp::Delete { key } => (key.as_ref().to_vec(), KeyOp::Delete), - }) - .collect(); - - Self { base, delta } - } -} - -#[async_trait] -impl api::DbView for Proposal { - async fn hash(&self) -> Result { - todo!() - } - - async fn val(&self, key: K) -> Result, api::Error> { - // see if this key is in this proposal - match self.delta.get(key.as_ref()) { - Some(change) => match change { - // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(val.clone()), - KeyOp::Delete => Err(api::Error::KeyNotFound), // key was deleted in this proposal - }, - None => match &self.base { - // key not in this proposal, so delegate to base - ProposalBase::Proposal(p) => p.val(key).await, - ProposalBase::View(view) => view.val(key).await, - }, - } - } - - async fn single_key_proof( - &self, - _key: K, - ) -> Result, api::Error> { - todo!() - } - - async fn range_proof( - &self, - _first_key: Option, - _last_key: Option, - _limit: usize, - ) -> Result, api::Error> { - todo!() - } -} - -#[async_trait] -impl api::Proposal for Proposal { - type Proposal = Proposal; - - async fn propose( - self: Arc, - data: Batch, - ) -> Result, api::Error> { - // find the Arc for this base proposal from the parent - let proposal = Proposal::new(ProposalBase::Proposal(self), data); - - Ok(Arc::new(proposal)) - } - - async fn commit(self) -> Result { - todo!() - } -} - -impl std::ops::Add for Proposal { - type Output = Arc; - - fn add(self, rhs: Self) -> Self::Output { - let mut delta = self.delta.clone(); - - delta.extend(rhs.delta); - - let proposal = Proposal { - base: self.base, - delta, - }; - - Arc::new(proposal) - } -} - -impl std::ops::Add for &Proposal { - type Output = Arc; - - fn add(self, rhs: Self) -> Self::Output { - let mut delta = self.delta.clone(); - - delta.extend(rhs.delta.clone()); - - let proposal = Proposal { - base: self.base.clone(), - delta, - }; - - Arc::new(proposal) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::v2::api::Db as _; - use crate::v2::api::DbView as _; - use crate::v2::api::Proposal; - use api::BatchOp; - - #[tokio::test] - async fn test_basic_proposal() -> Result<(), crate::v2::api::Error> { - let db = Arc::new(Db::default()); - - let batch = vec![ - BatchOp::Put { - key: b"k", - value: b"v", - }, - BatchOp::Delete { key: b"z" }, - ]; - - let proposal = db.propose(batch).await?; - - assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); - - assert!(matches!( - proposal.val(b"z").await.unwrap_err(), - crate::v2::api::Error::KeyNotFound - )); - - Ok(()) - } - - #[tokio::test] - async fn test_nested_proposal() -> Result<(), crate::v2::api::Error> { - let db = Arc::new(Db::default()); - - // create proposal1 which adds key "k" with value "v" and deletes "z" - let batch = vec![ - BatchOp::Put { - key: b"k", - value: b"v", - }, - BatchOp::Delete { key: b"z" }, - ]; - - let proposal1 = db.propose(batch).await?; - - // create proposal2 which adds key "z" with value "undo" - let proposal2 = proposal1 - .clone() - .propose(vec![BatchOp::Put { - key: b"z", - value: "undo", - }]) - .await?; - // both proposals still have (k,v) - assert_eq!(proposal1.val(b"k").await.unwrap(), b"v"); - assert_eq!(proposal2.val(b"k").await.unwrap(), b"v"); - // only proposal1 doesn't have z - assert!(matches!( - proposal1.val(b"z").await.unwrap_err(), - crate::v2::api::Error::KeyNotFound - )); - // proposal2 has z with value "undo" - assert_eq!(proposal2.val(b"z").await.unwrap(), b"undo"); - - // create a proposal3 by adding the two proposals together, keeping the originals - let proposal3: Arc = proposal1.as_ref() + proposal2.as_ref(); - assert_eq!(proposal3.val(b"k").await.unwrap(), b"v"); - assert_eq!(proposal3.val(b"z").await.unwrap(), b"undo"); - - // now consume proposal1 and proposal2 - - Ok(()) - } -} diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs new file mode 100644 index 000000000000..c27919ac14ef --- /dev/null +++ b/firewood/src/v2/emptydb.rs @@ -0,0 +1,149 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, Proof, RangeProof, ValueType}; +use super::propose::{Proposal, ProposalBase}; + +/// An EmptyDb is a simple implementation of api::Db +/// that doesn't store any data. It contains a single +/// HistoricalImpl that has no keys or values +#[derive(Debug, Default)] +struct EmptyDb { + root: Arc, +} + +/// HistoricalImpl is always empty, and there is only one, +/// since nothing can be committed to an EmptyDb. +#[derive(Debug, Default)] +struct HistoricalImpl; + +/// This is the hash of the [EmptyDb] root +const ROOT_HASH: [u8; 32] = [0; 32]; + +#[async_trait] +impl Db for EmptyDb { + type Historical = HistoricalImpl; + + type Proposal = Proposal; + + async fn revision(&self, hash_key: HashKey) -> Result, Error> { + if hash_key == ROOT_HASH { + Ok(self.root.clone()) + } else { + Err(Error::HashNotFound { provided: hash_key }) + } + } + + async fn root_hash(&self) -> Result { + Ok(ROOT_HASH) + } + + async fn propose(&self, data: Batch) -> Result + where + K: KeyType, + V: ValueType, + { + Ok(Proposal::new(ProposalBase::View(self.root.clone()), data)) + } +} + +#[async_trait] +impl DbView for HistoricalImpl { + async fn hash(&self) -> Result { + Ok(ROOT_HASH) + } + + async fn val(&self, _key: K) -> Result, Error> { + Err(Error::KeyNotFound) + } + + async fn single_key_proof(&self, _key: K) -> Result, Error> { + Err(Error::KeyNotFound) + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result, Error> { + Err(Error::KeyNotFound) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::v2::api::{BatchOp, Proposal}; + + #[tokio::test] + async fn basic_proposal() -> Result<(), Error> { + let db = Arc::new(EmptyDb::default()); + + let batch = vec![ + BatchOp::Put { + key: b"k", + value: b"v", + }, + BatchOp::Delete { key: b"z" }, + ]; + + let proposal = db.propose(batch).await?; + + assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); + + assert!(matches!( + proposal.val(b"z").await.unwrap_err(), + Error::KeyNotFound + )); + + Ok(()) + } + + #[tokio::test] + async fn nested_proposal() -> Result<(), Error> { + let db = Arc::new(EmptyDb::default()); + + // create proposal1 which adds key "k" with value "v" and deletes "z" + let batch = vec![ + BatchOp::Put { + key: b"k", + value: b"v", + }, + BatchOp::Delete { key: b"z" }, + ]; + + let proposal1 = Arc::new(db.propose(batch).await?); + + // create proposal2 which adds key "z" with value "undo" + let proposal2 = proposal1 + .clone() + .propose(vec![BatchOp::Put { + key: b"z", + value: "undo", + }]) + .await?; + let proposal2 = Arc::new(proposal2); + // both proposals still have (k,v) + assert_eq!(proposal1.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal2.val(b"k").await.unwrap(), b"v"); + // only proposal1 doesn't have z + assert!(matches!( + proposal1.val(b"z").await.unwrap_err(), + Error::KeyNotFound + )); + // proposal2 has z with value "undo" + assert_eq!(proposal2.val(b"z").await.unwrap(), b"undo"); + + // create a proposal3 by adding the two proposals together, keeping the originals + let proposal3 = proposal1.as_ref() + proposal2.as_ref(); + assert_eq!(proposal3.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal3.val(b"z").await.unwrap(), b"undo"); + + // now consume proposal1 and proposal2 + proposal2.commit().await?; + + Ok(()) + } +} diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 037782038990..0a05e5d24d76 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -1,2 +1,4 @@ pub mod api; pub mod db; +pub mod emptydb; +pub mod propose; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs new file mode 100644 index 000000000000..d11ddc74a270 --- /dev/null +++ b/firewood/src/v2/propose.rs @@ -0,0 +1,161 @@ +use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; + +use async_trait::async_trait; + +use crate::v2::api; + +use super::api::{KeyType, ValueType}; + +#[derive(Clone, Debug)] +pub(crate) enum KeyOp { + Put(V), + Delete, +} + +#[derive(Debug)] +pub(crate) enum ProposalBase { + Proposal(Arc>), + View(Arc), +} + +// Implement Clone because T doesn't need to be Clone +// so an automatically derived Clone won't work +impl Clone for ProposalBase { + fn clone(&self) -> Self { + match self { + Self::Proposal(arg0) => Self::Proposal(arg0.clone()), + Self::View(arg0) => Self::View(arg0.clone()), + } + } +} + +#[derive(Debug)] +pub struct Proposal { + pub(crate) base: ProposalBase, + pub(crate) delta: BTreeMap, KeyOp>>, +} + +// Implement Clone because T doesn't need to be Clone +// so an automatically derived Clone won't work +impl Clone for Proposal { + fn clone(&self) -> Self { + Self { + base: self.base.clone(), + delta: self.delta.clone(), + } + } +} + +impl Proposal { + pub(crate) fn new( + base: ProposalBase, + batch: api::Batch, + ) -> Self { + let delta = batch + .into_iter() + .map(|op| match op { + api::BatchOp::Put { key, value } => { + (key.as_ref().to_vec(), KeyOp::Put(value.as_ref().to_vec())) + } + api::BatchOp::Delete { key } => (key.as_ref().to_vec(), KeyOp::Delete), + }) + .collect(); + + Self { base, delta } + } +} + +#[async_trait] +impl api::DbView for Proposal { + async fn hash(&self) -> Result { + todo!() + } + + async fn val(&self, key: K) -> Result, api::Error> { + // see if this key is in this proposal + match self.delta.get(key.as_ref()) { + Some(change) => match change { + // key in proposal, check for Put or Delete + KeyOp::Put(val) => Ok(val.clone()), + KeyOp::Delete => Err(api::Error::KeyNotFound), // key was deleted in this proposal + }, + None => match &self.base { + // key not in this proposal, so delegate to base + ProposalBase::Proposal(p) => p.val(key).await, + ProposalBase::View(view) => view.val(key).await, + }, + } + } + + async fn single_key_proof( + &self, + _key: K, + ) -> Result, api::Error> { + todo!() + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result, api::Error> { + todo!() + } +} + +#[async_trait] +impl api::Proposal for Proposal { + type Proposal = Proposal; + + async fn propose( + self: Arc, + data: api::Batch, + ) -> Result { + // find the Arc for this base proposal from the parent + Ok(Proposal::new(ProposalBase::Proposal(self), data)) + } + + async fn commit(self: Arc) -> Result, api::Error> { + // TODO: commit should modify the db; this will only work for + // emptydb at the moment + match &self.base { + ProposalBase::Proposal(base) => base.clone().commit().await, + ProposalBase::View(v) => Ok(v.clone()), + } + } +} + +impl std::ops::Add for Proposal { + type Output = Arc>; + + fn add(self, rhs: Self) -> Self::Output { + let mut delta = self.delta.clone(); + + delta.extend(rhs.delta); + + let proposal = Proposal { + base: self.base, + delta, + }; + + Arc::new(proposal) + } +} + +impl std::ops::Add for &Proposal { + type Output = Arc>; + + fn add(self, rhs: Self) -> Self::Output { + let mut delta = self.delta.clone(); + + delta.extend(rhs.delta.clone()); + + let proposal = Proposal { + base: self.base.clone(), + delta, + }; + + Arc::new(proposal) + } +} From d9aa6f03b46fbd143582062a92ab60dfdc5b0357 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 27 Jul 2023 08:41:53 -0700 Subject: [PATCH 0213/1053] Add mermaid diagram for proposals (#169) --- firewood/src/v2/propose.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index d11ddc74a270..ae03fb3d47cd 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -29,6 +29,37 @@ impl Clone for ProposalBase { } } +/// A proposal is created either from the [[crate::v2::api::Db]] object +/// or from another proposal. Proposals are owned by the +/// caller. A proposal can only be committed if it has a +/// base of the current revision of the [[crate::v2::api::Db]]. +#[cfg_attr(doc, aquamarine::aquamarine)] +/// ```mermaid +/// graph LR +/// subgraph historical +/// direction BT +/// PH1 --> R1((R1)) +/// PH2 --> R1 +/// PH3 --> PH2 +/// end +/// R1 ~~~|"proposals on R1
    may not be committed"| R1 +/// subgraph committed_head +/// direction BT +/// R2 ~~~|"proposals on R2
    may be committed"| R2 +/// PC4 --> R2((R2)) +/// PC6 --> PC5 +/// PC5 --> R2 +/// PC6 ~~~|"Committing PC6
    creates two revisions"| PC6 +/// end +/// subgraph new_committing +/// direction BT +/// PN --> R3((R3)) +/// R3 ~~~|"R3 does not yet exist"| R3 +/// PN ~~~|"this proposal
    is committing"
    --
    could be
    PC4 or PC5| PN +/// end +/// historical ==> committed_head +/// committed_head ==> new_committing +/// ``` #[derive(Debug)] pub struct Proposal { pub(crate) base: ProposalBase, From 8dbb079a1e13d57ccd34e9727c16f7f941d4753d Mon Sep 17 00:00:00 2001 From: exdx Date: Thu, 27 Jul 2023 13:30:27 -0400 Subject: [PATCH 0214/1053] api/v2: Update hash fn to root_hash (#170) Signed-off-by: Dan Sover --- firewood/src/v2/api.rs | 4 ++-- firewood/src/v2/db.rs | 2 +- firewood/src/v2/emptydb.rs | 2 +- firewood/src/v2/propose.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 65efcf05edaa..8ccb1c19ca69 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -127,8 +127,8 @@ pub trait Db { /// A [Proposal] requires implementing DbView #[async_trait] pub trait DbView { - /// Get the hash for the current DbView - async fn hash(&self) -> Result; + /// Get the root hash for the current DbView + async fn root_hash(&self) -> Result; /// Get the value of a specific key async fn val(&self, key: K) -> Result, Error>; diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 23dece760f45..61c5b911121b 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -68,7 +68,7 @@ pub struct DbView; #[async_trait] impl api::DbView for DbView { - async fn hash(&self) -> Result { + async fn root_hash(&self) -> Result { todo!() } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index c27919ac14ef..487912db4ac0 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -50,7 +50,7 @@ impl Db for EmptyDb { #[async_trait] impl DbView for HistoricalImpl { - async fn hash(&self) -> Result { + async fn root_hash(&self) -> Result { Ok(ROOT_HASH) } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index ae03fb3d47cd..a3ac1b30caa1 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -98,7 +98,7 @@ impl Proposal { #[async_trait] impl api::DbView for Proposal { - async fn hash(&self) -> Result { + async fn root_hash(&self) -> Result { todo!() } From 6a6329071eca51dcd7e0d5bda929504f8551ed49 Mon Sep 17 00:00:00 2001 From: Cesar <137245636+nytzuga@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:28:30 -0400 Subject: [PATCH 0215/1053] Return Option instead of erroring with api::Error:::KeyNotFound (#171) --- firewood/src/v2/api.rs | 11 ++++++----- firewood/src/v2/db.rs | 6 +++--- firewood/src/v2/emptydb.rs | 37 +++++++++++++++++-------------------- firewood/src/v2/propose.rs | 10 +++++----- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 8ccb1c19ca69..3f6d343fc200 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -62,8 +62,6 @@ pub enum Error { provided: HashKey, current: HashKey, }, - /// Key not found - KeyNotFound, IO(std::io::Error), InvalidProposal, } @@ -131,10 +129,13 @@ pub trait DbView { async fn root_hash(&self) -> Result; /// Get the value of a specific key - async fn val(&self, key: K) -> Result, Error>; + async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key - async fn single_key_proof(&self, key: K) -> Result, Error>; + async fn single_key_proof( + &self, + key: K, + ) -> Result>, Error>; /// Obtain a range proof over a set of keys /// @@ -149,7 +150,7 @@ pub trait DbView { first_key: Option, last_key: Option, limit: usize, - ) -> Result, Error>; + ) -> Result>, Error>; } /// A proposal for a new revision of the database. diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 61c5b911121b..a84be67b91ab 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -72,14 +72,14 @@ impl api::DbView for DbView { todo!() } - async fn val(&self, _key: K) -> Result, api::Error> { + async fn val(&self, _key: K) -> Result>, api::Error> { todo!() } async fn single_key_proof( &self, _key: K, - ) -> Result, api::Error> { + ) -> Result>, api::Error> { todo!() } @@ -88,7 +88,7 @@ impl api::DbView for DbView { _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result, api::Error> { + ) -> Result>, api::Error> { todo!() } } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 487912db4ac0..45f3d6dbc162 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -54,12 +54,15 @@ impl DbView for HistoricalImpl { Ok(ROOT_HASH) } - async fn val(&self, _key: K) -> Result, Error> { - Err(Error::KeyNotFound) + async fn val(&self, _key: K) -> Result>, Error> { + Ok(None) } - async fn single_key_proof(&self, _key: K) -> Result, Error> { - Err(Error::KeyNotFound) + async fn single_key_proof( + &self, + _key: K, + ) -> Result>, Error> { + Ok(None) } async fn range_proof( @@ -67,8 +70,8 @@ impl DbView for HistoricalImpl { _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result, Error> { - Err(Error::KeyNotFound) + ) -> Result>, Error> { + Ok(None) } } @@ -91,12 +94,9 @@ mod tests { let proposal = db.propose(batch).await?; - assert_eq!(proposal.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal.val(b"k").await.unwrap().unwrap(), b"v"); - assert!(matches!( - proposal.val(b"z").await.unwrap_err(), - Error::KeyNotFound - )); + assert!(matches!(proposal.val(b"z").await.unwrap(), None)); Ok(()) } @@ -126,20 +126,17 @@ mod tests { .await?; let proposal2 = Arc::new(proposal2); // both proposals still have (k,v) - assert_eq!(proposal1.val(b"k").await.unwrap(), b"v"); - assert_eq!(proposal2.val(b"k").await.unwrap(), b"v"); + assert_eq!(proposal1.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(proposal2.val(b"k").await.unwrap().unwrap(), b"v"); // only proposal1 doesn't have z - assert!(matches!( - proposal1.val(b"z").await.unwrap_err(), - Error::KeyNotFound - )); + assert!(matches!(proposal1.val(b"z").await.unwrap(), None)); // proposal2 has z with value "undo" - assert_eq!(proposal2.val(b"z").await.unwrap(), b"undo"); + assert_eq!(proposal2.val(b"z").await.unwrap().unwrap(), b"undo"); // create a proposal3 by adding the two proposals together, keeping the originals let proposal3 = proposal1.as_ref() + proposal2.as_ref(); - assert_eq!(proposal3.val(b"k").await.unwrap(), b"v"); - assert_eq!(proposal3.val(b"z").await.unwrap(), b"undo"); + assert_eq!(proposal3.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(proposal3.val(b"z").await.unwrap().unwrap(), b"undo"); // now consume proposal1 and proposal2 proposal2.commit().await?; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index a3ac1b30caa1..099697b3a0b0 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -102,13 +102,13 @@ impl api::DbView for Proposal { todo!() } - async fn val(&self, key: K) -> Result, api::Error> { + async fn val(&self, key: K) -> Result>, api::Error> { // see if this key is in this proposal match self.delta.get(key.as_ref()) { Some(change) => match change { // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(val.clone()), - KeyOp::Delete => Err(api::Error::KeyNotFound), // key was deleted in this proposal + KeyOp::Put(val) => Ok(Some(val.clone())), + KeyOp::Delete => Ok(None), // key was deleted in this proposal }, None => match &self.base { // key not in this proposal, so delegate to base @@ -121,7 +121,7 @@ impl api::DbView for Proposal { async fn single_key_proof( &self, _key: K, - ) -> Result, api::Error> { + ) -> Result>, api::Error> { todo!() } @@ -130,7 +130,7 @@ impl api::DbView for Proposal { _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result, api::Error> { + ) -> Result>, api::Error> { todo!() } } From 01be0910080a0ca282cb491a2f6c3c540495cd3a Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:59:02 -0700 Subject: [PATCH 0216/1053] chore: verify concurrent committing write batches (#172) --- firewood/examples/rev.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 2631ba87d4a9..2f1c59878514 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -22,6 +22,25 @@ fn main() { .expect("db initiation should succeed"); let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; + std::thread::scope(|scope| { + scope.spawn(|| { + db.new_writebatch() + .kv_insert("k1", "v1".into()) + .unwrap() + .commit(); + }); + + scope.spawn(|| { + db.new_writebatch() + .kv_insert("k2", "v2".into()) + .unwrap() + .commit(); + }); + }); + + assert_eq!("v1".as_bytes().to_vec(), db.kv_get("k1").unwrap()); + assert_eq!("v2".as_bytes().to_vec(), db.kv_get("k2").unwrap()); + let mut revision_tracker = RevisionTracker::new(db); revision_tracker.create_revisions(items.into_iter()); From 6b60e958332f9c10e1d32f6927b0deb1e731cf94 Mon Sep 17 00:00:00 2001 From: Cesar <137245636+nytzuga@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:52:30 -0400 Subject: [PATCH 0217/1053] Return &[u8] from val trait (Fixes #165) (#173) --- firewood/src/v2/api.rs | 2 +- firewood/src/v2/db.rs | 2 +- firewood/src/v2/emptydb.rs | 2 +- firewood/src/v2/propose.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 3f6d343fc200..bf69bc66f990 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -129,7 +129,7 @@ pub trait DbView { async fn root_hash(&self) -> Result; /// Get the value of a specific key - async fn val(&self, key: K) -> Result>, Error>; + async fn val(&self, key: K) -> Result, Error>; /// Obtain a proof for a single key async fn single_key_proof( diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index a84be67b91ab..5df02441eb77 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -72,7 +72,7 @@ impl api::DbView for DbView { todo!() } - async fn val(&self, _key: K) -> Result>, api::Error> { + async fn val(&self, _key: K) -> Result, api::Error> { todo!() } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 45f3d6dbc162..f3d9b36839b4 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -54,7 +54,7 @@ impl DbView for HistoricalImpl { Ok(ROOT_HASH) } - async fn val(&self, _key: K) -> Result>, Error> { + async fn val(&self, _key: K) -> Result, Error> { Ok(None) } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 099697b3a0b0..5b8d3422db4b 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -102,12 +102,12 @@ impl api::DbView for Proposal { todo!() } - async fn val(&self, key: K) -> Result>, api::Error> { + async fn val(&self, key: K) -> Result, api::Error> { // see if this key is in this proposal match self.delta.get(key.as_ref()) { Some(change) => match change { // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(Some(val.clone())), + KeyOp::Put(val) => Ok(Some(val)), KeyOp::Delete => Ok(None), // key was deleted in this proposal }, None => match &self.base { From 912f27d732d990febfc634667d40efbbcf275f5a Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 2 Aug 2023 09:58:09 -0700 Subject: [PATCH 0218/1053] chore: remove redundant code (#174) --- firewood/src/db.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c53f33497dda..bc14f262efbd 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1182,7 +1182,6 @@ impl + Send + Sync> WriteBatch { rev_inner.root_hash_staging.write(0, &kv_root_hash.0); let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.take_delta(); - // let base_root_hash = StoreRevShared::from_delta(rev_inner.root_hash_cache, root_hash_redo); self.committed = true; From 45cd6e63f51bfcbc90e94ca714c3d8122a318b5a Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:27:37 -0700 Subject: [PATCH 0219/1053] feat: supports chains of `StoreRevMut` (#175) Co-authored-by: Ron Kuris --- firewood/src/storage/buffer.rs | 99 ++++++++++++++++++++++++++++++++ firewood/src/storage/mod.rs | 101 +++++++++++++++++++++++---------- firewood/tests/db.rs | 9 +-- 3 files changed, 176 insertions(+), 33 deletions(-) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 487f3c825687..49918e67e8bb 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -803,6 +803,105 @@ mod tests { assert_eq!(view.as_deref(), hash); } + #[test] + fn test_multi_stores() { + let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let wal_cfg = WalConfig::builder().build(); + let disk_requester = init_buffer(buf_cfg, wal_cfg); + + // TODO: Run the test in a separate standalone directory for concurrency reasons + let tmp_dir = TempDir::new("firewood").unwrap(); + let path = get_file_path(tmp_dir.path(), file!(), line!()); + let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); + + // file descriptor of the state directory + let state_path = file::touch_dir("state", &root_db_path).unwrap(); + assert!(reset); + // create a new wal directory on top of root_db_fd + disk_requester.init_wal("wal", root_db_path); + + // create a new state cache which tracks on disk state. + let state_cache = Arc::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(1) + .ncached_files(1) + .space_id(STATE_SPACE) + .file_nbit(1) + .rootdir(state_path) + .build(), + disk_requester.clone(), + ) + .unwrap(), + ); + + // add an in memory cached space. this will allow us to write to the + // disk buffer then later persist the change to disk. + disk_requester.reg_cached_space(state_cache.id(), state_cache.clone_files()); + + // memory mapped store + let mut store = StoreRevMut::new(state_cache.clone()); + + // mutate the in memory buffer. + let data = b"this is a test"; + let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); + store.write(0, &hash); + assert_eq!(store.id(), STATE_SPACE); + + let another_data = b"this is another test"; + let another_hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(another_data).into(); + + // mutate the in memory buffer in another StoreRev new from the above. + let mut another_store = StoreRevMut::new_from_other(&store); + another_store.write(32, &another_hash); + assert_eq!(another_store.id(), STATE_SPACE); + + // wal should have no records. + assert!(disk_requester.collect_ash(1).unwrap().is_empty()); + + // get RO view of the buffer from the beginning. Both stores should have the same view. + let view = store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + + let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + + // get RO view of the buffer from the second hash. Only the new store shoulde see the value. + let view = another_store.get_view(32, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), another_hash); + let empty: [u8; HASH_SIZE] = [0; HASH_SIZE]; + let view = store.get_view(32, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), empty); + + // Overwrite the value from the beginning in the new store. Only the new store shoulde see the change. + another_store.write(0, &another_hash); + let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), another_hash); + let view = store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + + // Commit the change. Take the delta from both stores. + let (redo_delta, wal) = store.take_delta(); + assert_eq!(1, redo_delta.0.len()); + assert_eq!(1, wal.undo.len()); + + let (another_redo_delta, another_wal) = another_store.take_delta(); + assert_eq!(1, another_redo_delta.0.len()); + assert_eq!(2, another_wal.undo.len()); + + // Verify after the changes been applied to underlying CachedSpace, + // the newly created stores should see the previous changes. + state_cache.update(&redo_delta).unwrap(); + let store = StoreRevMut::new(state_cache.clone()); + let view = store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), hash); + + state_cache.update(&another_redo_delta).unwrap(); + let another_store = StoreRevMut::new(state_cache); + let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); + assert_eq!(view.as_deref(), another_hash); + } + fn get_file_path(path: &Path, file: &str, line: u32) -> PathBuf { path.join(format!("{}_{}", file.replace('/', "-"), line)) } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index d5e853b29396..783933b746fc 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -53,13 +53,13 @@ pub trait MemStoreR: Debug + Send + Sync { // Page should be boxed as to not take up so much stack-space type Page = Box<[u8; PAGE_SIZE as usize]>; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SpaceWrite { offset: u64, data: Box<[u8]>, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] /// In memory representation of Write-ahead log with `undo` and `redo`. pub struct Ash { /// Deltas to undo the changes. @@ -432,13 +432,33 @@ struct StoreRevMutDelta { plain: Ash, } +impl Clone for StoreRevMutDelta { + fn clone(&self) -> Self { + let mut pages = HashMap::new(); + for (pid, page) in self.pages.iter() { + let mut data = Box::new([0u8; PAGE_SIZE as usize]); + data.copy_from_slice(page.as_ref()); + pages.insert(*pid, data); + } + + Self { + pages, + plain: self.plain.clone(), + } + } +} + #[derive(Clone, Debug)] /// A mutable revision of the store. The view is constucted by applying the `deltas` to the /// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply -/// the changes. +/// the changes. `StoreRevMut` supports basing on top of another `StoreRevMut`, by chaining +/// `prev_deltas` (from based `StoreRevMut`) with current `deltas` from itself . In this way, +/// callers can create a new `StoreRevMut` from an existing one without actually committing +/// the mutations to the base space. pub struct StoreRevMut { base_space: Arc, deltas: Arc>, + prev_deltas: Arc>, } impl StoreRevMut { @@ -446,37 +466,56 @@ impl StoreRevMut { Self { base_space, deltas: Default::default(), + prev_deltas: Default::default(), } } - fn get_page_mut<'a>(&self, deltas: &'a mut StoreRevMutDelta, pid: u64) -> &'a mut [u8] { - let page = deltas.pages.entry(pid).or_insert_with(|| { - Box::new( - self.base_space - .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) - .unwrap() - .try_into() - .unwrap(), - ) - }); + pub fn new_from_other(other: &StoreRevMut) -> Self { + Self { + base_space: other.base_space.clone(), + deltas: Default::default(), + prev_deltas: other.deltas.clone(), + } + } + + fn get_page_mut<'a>( + &self, + deltas: &'a mut StoreRevMutDelta, + prev_deltas: &StoreRevMutDelta, + pid: u64, + ) -> &'a mut [u8] { + let page = deltas + .pages + .entry(pid) + .or_insert_with(|| match prev_deltas.pages.get(&pid) { + Some(p) => Box::new(*p.as_ref()), + None => Box::new( + self.base_space + .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) + .unwrap() + .try_into() + .unwrap(), + ), + }); page.as_mut() } pub fn take_delta(&self) -> (StoreDelta, Ash) { - let mut pages = Vec::new(); - let deltas = std::mem::replace( - &mut *self.deltas.write(), - StoreRevMutDelta { - pages: HashMap::new(), - plain: Ash::new(), - }, - ); - for (pid, page) in deltas.pages.into_iter() { - pages.push(DeltaPage(pid, page)); - } + let mut guard = self.deltas.write(); + let mut pages: Vec = guard + .pages + .iter() + .map(|page| DeltaPage(*page.0, page.1.clone())) + .collect(); pages.sort_by_key(|p| p.0); - (StoreDelta(pages), deltas.plain) + let cloned_plain = guard.plain.clone(); + // TODO: remove this line, since we don't know why this works + *guard = StoreRevMutDelta { + pages: HashMap::new(), + plain: Ash::new(), + }; + (StoreDelta(pages), cloned_plain) } } @@ -544,7 +583,8 @@ impl CachedStore for StoreRevMut { if s_pid == e_pid { let mut deltas = self.deltas.write(); - let slice = &mut self.get_page_mut(deltas.deref_mut(), s_pid)[s_off..e_off + 1]; + let slice = &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid) + [s_off..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change) } else { @@ -552,7 +592,9 @@ impl CachedStore for StoreRevMut { { let mut deltas = self.deltas.write(); - let slice = &mut self.get_page_mut(deltas.deref_mut(), s_pid)[s_off..]; + let slice = + &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid) + [s_off..]; undo.extend(&*slice); slice.copy_from_slice(&change[..len]); } @@ -561,13 +603,14 @@ impl CachedStore for StoreRevMut { let mut deltas = self.deltas.write(); for p in s_pid + 1..e_pid { - let slice = self.get_page_mut(deltas.deref_mut(), p); + let slice = self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), p); undo.extend(&*slice); slice.copy_from_slice(&change[..PAGE_SIZE as usize]); change = &change[PAGE_SIZE as usize..]; } - let slice = &mut self.get_page_mut(deltas.deref_mut(), e_pid)[..e_off + 1]; + let slice = &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), e_pid) + [..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change); } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 8970957852b4..1d90fc40b27b 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -139,10 +139,11 @@ fn test_revisions() { .zip(hashes.iter().cloned()) .map(|(data, hash)| (data, db.get_revision(&hash, None).unwrap())) .map(|(data, rev)| (data, kv_dump!(rev))) - .for_each(|(b, a)| { - if &a != b { - print!("{a}\n{b}"); - panic!("not the same"); + .for_each(|(previous_dump, after_reopen_dump)| { + if &after_reopen_dump != previous_dump { + panic!( + "not the same: pass {i}:\n{after_reopen_dump}\n--------\n{previous_dump}" + ); } }); println!("i = {i}"); From 011ef1bb6be123b35f908466ef62a80a525a651e Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:15:42 -0700 Subject: [PATCH 0220/1053] chore: remove unused clone for `StoreRevMutDelta` (#178) --- firewood/src/storage/buffer.rs | 1 - firewood/src/storage/mod.rs | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 49918e67e8bb..3e1f1b50753b 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -809,7 +809,6 @@ mod tests { let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); - // TODO: Run the test in a separate standalone directory for concurrency reasons let tmp_dir = TempDir::new("firewood").unwrap(); let path = get_file_path(tmp_dir.path(), file!(), line!()); let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 783933b746fc..070bbfb1290f 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -432,22 +432,6 @@ struct StoreRevMutDelta { plain: Ash, } -impl Clone for StoreRevMutDelta { - fn clone(&self) -> Self { - let mut pages = HashMap::new(); - for (pid, page) in self.pages.iter() { - let mut data = Box::new([0u8; PAGE_SIZE as usize]); - data.copy_from_slice(page.as_ref()); - pages.insert(*pid, data); - } - - Self { - pages, - plain: self.plain.clone(), - } - } -} - #[derive(Clone, Debug)] /// A mutable revision of the store. The view is constucted by applying the `deltas` to the /// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply From c3d164ad97763829475c1ed6ca801fe31cdc4e2f Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:29:47 -0700 Subject: [PATCH 0221/1053] chore: abstract out mutable store creation (#176) --- firewood/src/db.rs | 129 +++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index bc14f262efbd..00d7ca5a2a6a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -44,6 +44,11 @@ const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; +type Store = ( + Universe>, + DbRev>, +); + #[derive(Debug)] #[non_exhaustive] pub enum DbError { @@ -115,7 +120,7 @@ pub struct DbRevConfig { } /// Database configuration. -#[derive(TypedBuilder, Debug)] +#[derive(Clone, TypedBuilder, Debug)] pub struct DbConfig { /// Maximum cached pages for the free list of the item stash. #[builder(default = 16384)] // 64M total size by default @@ -563,12 +568,12 @@ impl Db> { let mut header_bytes = [0; std::mem::size_of::()]; nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; drop(file0); - let mut offset = header_bytes.len() as u64; - let header: DbParams = cast_slice(&header_bytes)[0]; + let offset = header_bytes.len() as u64; + let params: DbParams = cast_slice(&header_bytes)[0]; let wal = WalConfig::builder() - .file_nbit(header.wal_file_nbit) - .block_nbit(header.wal_block_nbit) + .file_nbit(params.wal_file_nbit) + .block_nbit(params.wal_block_nbit) .max_revisions(cfg.wal.max_revisions) .build(); let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); @@ -585,7 +590,7 @@ impl Db> { .ncached_pages(cfg.root_hash_ncached_pages) .ncached_files(cfg.root_hash_ncached_files) .space_id(ROOT_HASH_SPACE) - .file_nbit(header.root_hash_file_nbit) + .file_nbit(params.root_hash_file_nbit) .rootdir(root_hash_path) .build(), disk_requester.clone(), @@ -602,7 +607,7 @@ impl Db> { .ncached_pages(cfg.meta_ncached_pages) .ncached_files(cfg.meta_ncached_files) .space_id(MERKLE_META_SPACE) - .file_nbit(header.meta_file_nbit) + .file_nbit(params.meta_file_nbit) .rootdir(merkle_meta_path) .build(), disk_requester.clone(), @@ -615,7 +620,7 @@ impl Db> { .ncached_pages(cfg.payload_ncached_pages) .ncached_files(cfg.payload_ncached_files) .space_id(MERKLE_PAYLOAD_SPACE) - .file_nbit(header.payload_file_nbit) + .file_nbit(params.payload_file_nbit) .rootdir(merkle_payload_path) .build(), disk_requester.clone(), @@ -630,7 +635,7 @@ impl Db> { .ncached_pages(cfg.meta_ncached_pages) .ncached_files(cfg.meta_ncached_files) .space_id(BLOB_META_SPACE) - .file_nbit(header.meta_file_nbit) + .file_nbit(params.meta_file_nbit) .rootdir(blob_meta_path) .build(), disk_requester.clone(), @@ -643,7 +648,7 @@ impl Db> { .ncached_pages(cfg.payload_ncached_pages) .ncached_files(cfg.payload_ncached_files) .space_id(BLOB_PAYLOAD_SPACE) - .file_nbit(header.payload_file_nbit) + .file_nbit(params.payload_file_nbit) .rootdir(blob_payload_path) .build(), disk_requester.clone(), @@ -665,8 +670,48 @@ impl Db> { disk_requester.reg_cached_space(cached_space.id(), cached_space.clone_files()); }); - // set up the storage layout + // recover from Wal + disk_requester.init_wal("wal", db_path); + + let root_hash_staging = StoreRevMut::new(root_hash_cache); + let (data_staging, mut latest) = Db::new_store(&data_cache, reset, offset, cfg, ¶ms)?; + latest.flush_dirty().unwrap(); + + let base = Universe { + merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), + blob: get_sub_universe_from_empty_delta(&data_cache.blob), + }; + + Ok(Self { + inner: Arc::new(RwLock::new(DbInner { + latest, + disk_thread, + disk_requester, + data_staging, + data_cache, + root_hash_staging, + })), + revisions: Arc::new(Mutex::new(DbRevInner { + inner: VecDeque::new(), + root_hashes: VecDeque::new(), + max_revisions: cfg.wal.max_revisions as usize, + base, + })), + payload_regn_nbit: params.payload_regn_nbit, + rev_cfg: cfg.rev.clone(), + metrics: Arc::new(DbMetrics::default()), + }) + } + /// Create a new mutable store and an alterable revision of the DB on top. + fn new_store( + cached_space: &Universe>, + reset: bool, + offset: u64, + cfg: &DbConfig, + params: &DbParams, + ) -> Result { + let mut offset = offset; let db_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += DbHeader::MSIZE; let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); @@ -674,23 +719,23 @@ impl Db> { assert!(offset <= SPACE_RESERVED); let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let mut data_staging_merkle_meta = StoreRevMut::new(data_cache.merkle.meta.clone()); - let mut data_staging_blob_meta = StoreRevMut::new(data_cache.blob.meta.clone()); + let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); + let mut blob_meta_store = StoreRevMut::new(cached_space.blob.meta.clone()); if reset { // initialize space headers - data_staging_merkle_meta.write( + merkle_meta_store.write( merkle_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, SPACE_RESERVED, ))?, ); - data_staging_merkle_meta.write( + merkle_meta_store.write( db_header.addr(), &shale::to_dehydrated(&DbHeader::new_empty())?, ); - data_staging_blob_meta.write( + blob_meta_store.write( blob_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( SPACE_RESERVED, @@ -699,24 +744,20 @@ impl Db> { ); } - let data_staging = Universe { + let store = Universe { merkle: SubUniverse::new( - Arc::new(data_staging_merkle_meta), - Arc::new(StoreRevMut::new(data_cache.merkle.payload.clone())), + Arc::new(merkle_meta_store), + Arc::new(StoreRevMut::new(cached_space.merkle.payload.clone())), ), blob: SubUniverse::new( - Arc::new(data_staging_blob_meta), - Arc::new(StoreRevMut::new(data_cache.blob.payload.clone())), + Arc::new(blob_meta_store), + Arc::new(StoreRevMut::new(cached_space.blob.payload.clone())), ), }; - let root_hash_staging = StoreRevMut::new(root_hash_cache); - - // recover from Wal - disk_requester.init_wal("wal", db_path); let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { - let merkle_meta_ref = data_staging.merkle.meta.as_ref(); - let blob_meta_ref = data_staging.blob.meta.as_ref(); + let merkle_meta_ref = store.merkle.meta.as_ref(); + let blob_meta_ref = store.blob.meta.as_ref(); ( StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), @@ -736,12 +777,12 @@ impl Db> { }; let merkle_space = shale::compact::CompactSpace::new( - data_staging.merkle.meta.clone(), - data_staging.merkle.payload.clone(), + store.merkle.meta.clone(), + store.merkle.payload.clone(), merkle_payload_header_ref, shale::ObjCache::new(cfg.rev.merkle_ncached_objs), cfg.payload_max_walk, - header.payload_regn_nbit, + params.payload_regn_nbit, ) .unwrap(); @@ -776,38 +817,14 @@ impl Db> { err.map_err(DbError::Merkle)? } - let mut latest = DbRev { + let rev = DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), #[cfg(feature = "eth")] blob: BlobStash::new(Box::new(blob_space)), }; - latest.flush_dirty().unwrap(); - let base = Universe { - merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), - blob: get_sub_universe_from_empty_delta(&data_cache.blob), - }; - - Ok(Self { - inner: Arc::new(RwLock::new(DbInner { - latest, - disk_thread, - disk_requester, - data_staging, - data_cache, - root_hash_staging, - })), - revisions: Arc::new(Mutex::new(DbRevInner { - inner: VecDeque::new(), - root_hashes: VecDeque::new(), - max_revisions: cfg.wal.max_revisions as usize, - base, - })), - payload_regn_nbit: header.payload_regn_nbit, - rev_cfg: cfg.rev.clone(), - metrics: Arc::new(DbMetrics::default()), - }) + Ok((store, rev)) } /// Get a handle that grants the access to any committed state of the entire DB, From 441763197388dd9ad563248d19cc3ca8daa45163 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Aug 2023 10:49:48 -0700 Subject: [PATCH 0222/1053] Rkuris/cleanup delta (#179) Co-authored-by: Hao Hao --- firewood/src/db.rs | 26 ++++++++++++++------------ firewood/src/storage/buffer.rs | 8 ++++---- firewood/src/storage/mod.rs | 25 +++++++++---------------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 00d7ca5a2a6a..e26f1d67b4d8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1117,12 +1117,14 @@ impl + Send + Sync> WriteBatch { // clear the staging layer and apply changes to the CachedSpace rev_inner.latest.flush_dirty().unwrap(); let (merkle_payload_redo, merkle_payload_wal) = - rev_inner.data_staging.merkle.payload.take_delta(); - let (merkle_meta_redo, merkle_meta_wal) = rev_inner.data_staging.merkle.meta.take_delta(); - let (blob_payload_redo, blob_payload_wal) = - rev_inner.data_staging.blob.payload.take_delta(); - let (blob_meta_redo, blob_meta_wal) = rev_inner.data_staging.blob.meta.take_delta(); - + rev_inner.data_staging.merkle.payload.delta(); + rev_inner.data_staging.merkle.payload.reset_deltas(); + let (merkle_meta_redo, merkle_meta_wal) = rev_inner.data_staging.merkle.meta.delta(); + rev_inner.data_staging.merkle.meta.reset_deltas(); + let (blob_payload_redo, blob_payload_wal) = rev_inner.data_staging.blob.payload.delta(); + rev_inner.data_staging.blob.payload.reset_deltas(); + let (blob_meta_redo, blob_meta_wal) = rev_inner.data_staging.blob.meta.delta(); + rev_inner.data_staging.blob.meta.reset_deltas(); let merkle_meta_undo = rev_inner .data_cache .merkle @@ -1198,7 +1200,7 @@ impl + Send + Sync> WriteBatch { } rev_inner.root_hash_staging.write(0, &kv_root_hash.0); - let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.take_delta(); + let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); self.committed = true; @@ -1390,11 +1392,11 @@ impl Drop for WriteBatch { fn drop(&mut self) { if !self.committed { // drop the staging changes - self.m.read().data_staging.merkle.payload.take_delta(); - self.m.read().data_staging.merkle.meta.take_delta(); - self.m.read().data_staging.blob.payload.take_delta(); - self.m.read().data_staging.blob.meta.take_delta(); - self.m.read().root_hash_staging.take_delta(); + self.m.read().data_staging.merkle.payload.reset_deltas(); + self.m.read().data_staging.merkle.meta.reset_deltas(); + self.m.read().data_staging.blob.payload.reset_deltas(); + self.m.read().data_staging.blob.meta.reset_deltas(); + self.m.read().root_hash_staging.reset_deltas(); } } } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 3e1f1b50753b..af1b80f3fcc2 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -774,7 +774,7 @@ mod tests { // Commit the change. Take the delta from cached store, // then apply changes to the CachedSpace. - let (redo_delta, wal) = mut_store.take_delta(); + let (redo_delta, wal) = mut_store.delta(); state_cache.update(&redo_delta).unwrap(); // create a mutation request to the disk buffer by passing the page and write batch. @@ -880,11 +880,11 @@ mod tests { assert_eq!(view.as_deref(), hash); // Commit the change. Take the delta from both stores. - let (redo_delta, wal) = store.take_delta(); + let (redo_delta, wal) = store.delta(); assert_eq!(1, redo_delta.0.len()); assert_eq!(1, wal.undo.len()); - let (another_redo_delta, another_wal) = another_store.take_delta(); + let (another_redo_delta, another_wal) = another_store.delta(); assert_eq!(1, another_redo_delta.0.len()); assert_eq!(2, another_wal.undo.len()); @@ -920,7 +920,7 @@ mod tests { &mut *rev_mut.deltas.write(), StoreRevMutDelta { pages: HashMap::new(), - plain: Ash::new(), + plain: Ash::default(), }, ); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 070bbfb1290f..c1fa4a6b29a1 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -69,13 +69,6 @@ pub struct Ash { } impl Ash { - fn new() -> Self { - Self { - undo: Vec::new(), - redo: Vec::new(), - } - } - fn iter(&self) -> impl Iterator { self.undo.iter().zip(self.redo.iter()) } @@ -485,21 +478,21 @@ impl StoreRevMut { page.as_mut() } - pub fn take_delta(&self) -> (StoreDelta, Ash) { - let mut guard = self.deltas.write(); + #[must_use] + pub fn delta(&self) -> (StoreDelta, Ash) { + let guard = self.deltas.read(); let mut pages: Vec = guard .pages .iter() .map(|page| DeltaPage(*page.0, page.1.clone())) .collect(); pages.sort_by_key(|p| p.0); - let cloned_plain = guard.plain.clone(); - // TODO: remove this line, since we don't know why this works - *guard = StoreRevMutDelta { - pages: HashMap::new(), - plain: Ash::new(), - }; - (StoreDelta(pages), cloned_plain) + (StoreDelta(pages), guard.plain.clone()) + } + pub fn reset_deltas(&self) { + let mut guard = self.deltas.write(); + guard.plain = Ash::default(); + guard.pages = HashMap::new(); } } From 87837bf664d71e40f85617f6b7d4d79019deb93c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Aug 2023 11:12:05 -0700 Subject: [PATCH 0223/1053] Modularize database initialization (#65) --- firewood/examples/rev.rs | 2 +- firewood/src/config.rs | 69 +++++++++++++++++++++++++++++++++ firewood/src/db.rs | 71 +--------------------------------- firewood/src/lib.rs | 1 + firewood/src/storage/buffer.rs | 29 +++++++------- 5 files changed, 88 insertions(+), 84 deletions(-) create mode 100644 firewood/src/config.rs diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 2f1c59878514..b7d808232b0b 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -180,7 +180,7 @@ impl RevisionTracker { fn get_revision(&self, index: usize) -> Revision { self.db .get_revision(&self.hashes[index], None) - .expect(&format!("revision-{index} should exist")) + .unwrap_or_else(|| panic!("revision-{index} should exist")) } } diff --git a/firewood/src/config.rs b/firewood/src/config.rs new file mode 100644 index 000000000000..2c8d76acdac3 --- /dev/null +++ b/firewood/src/config.rs @@ -0,0 +1,69 @@ +pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; +use typed_builder::TypedBuilder; + +/// Database configuration. +#[derive(TypedBuilder, Debug)] +pub struct DbConfig { + /// Maximum cached pages for the free list of the item stash. + #[builder(default = 16384)] // 64M total size by default + pub meta_ncached_pages: usize, + /// Maximum cached file descriptors for the free list of the item stash. + #[builder(default = 1024)] // 1K fds by default + pub meta_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + pub meta_file_nbit: u64, + /// Maximum cached pages for the item stash. This is the low-level cache used by the linear + /// space that holds Trie nodes and account objects. + #[builder(default = 262144)] // 1G total size by default + pub payload_ncached_pages: usize, + /// Maximum cached file descriptors for the item stash. + #[builder(default = 1024)] // 1K fds by default + pub payload_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + pub payload_file_nbit: u64, + /// Maximum steps of walk to recycle a freed item. + #[builder(default = 10)] + pub payload_max_walk: u64, + /// Region size in bits (should be not greater than `payload_file_nbit`). One file is + /// partitioned into multiple regions. Just use the default value. + #[builder(default = 22)] + pub payload_regn_nbit: u64, + /// Maximum cached pages for the free list of the item stash. + #[builder(default = 16384)] // 64M total size by default + pub root_hash_ncached_pages: usize, + /// Maximum cached file descriptors for the free list of the item stash. + #[builder(default = 1024)] // 1K fds by default + pub root_hash_ncached_files: usize, + /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to + /// the power of 2 for the file size. + #[builder(default = 22)] // 4MB file by default + pub root_hash_file_nbit: u64, + /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its + /// existing contents will be lost. + #[builder(default = false)] + pub truncate: bool, + /// Config for accessing a version of the DB. + #[builder(default = DbRevConfig::builder().build())] + pub rev: DbRevConfig, + /// Config for the disk buffer. + #[builder(default = DiskBufferConfig::builder().build())] + pub buffer: DiskBufferConfig, + /// Config for Wal. + #[builder(default = WalConfig::builder().build())] + pub wal: WalConfig, +} + +/// Config for accessing a version of the DB. +#[derive(TypedBuilder, Clone, Debug)] +pub struct DbRevConfig { + /// Maximum cached Trie objects. + #[builder(default = 1 << 20)] + pub merkle_ncached_objs: usize, + /// Maximum cached Blob (currently just `Account`) objects. + #[builder(default = 4096)] + pub blob_ncached_objs: usize, +} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e26f1d67b4d8..5c7a446cdfc4 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -3,6 +3,7 @@ #[cfg(feature = "eth")] use crate::account::{Account, AccountRlp, Blob, BlobStash}; +pub use crate::config::{DbConfig, DbRevConfig}; pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use crate::storage::{ AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, @@ -33,7 +34,6 @@ use std::{ sync::Arc, thread::JoinHandle, }; -use typed_builder::TypedBuilder; const MERKLE_META_SPACE: SpaceId = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; @@ -108,73 +108,6 @@ struct DbParams { root_hash_file_nbit: u64, } -/// Config for accessing a version of the DB. -#[derive(TypedBuilder, Clone, Debug)] -pub struct DbRevConfig { - /// Maximum cached Trie objects. - #[builder(default = 1 << 20)] - pub merkle_ncached_objs: usize, - /// Maximum cached Blob (currently just `Account`) objects. - #[builder(default = 4096)] - pub blob_ncached_objs: usize, -} - -/// Database configuration. -#[derive(Clone, TypedBuilder, Debug)] -pub struct DbConfig { - /// Maximum cached pages for the free list of the item stash. - #[builder(default = 16384)] // 64M total size by default - pub meta_ncached_pages: usize, - /// Maximum cached file descriptors for the free list of the item stash. - #[builder(default = 1024)] // 1K fds by default - pub meta_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub meta_file_nbit: u64, - /// Maximum cached pages for the item stash. This is the low-level cache used by the linear - /// space that holds Trie nodes and account objects. - #[builder(default = 262144)] // 1G total size by default - pub payload_ncached_pages: usize, - /// Maximum cached file descriptors for the item stash. - #[builder(default = 1024)] // 1K fds by default - pub payload_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub payload_file_nbit: u64, - /// Maximum steps of walk to recycle a freed item. - #[builder(default = 10)] - pub payload_max_walk: u64, - /// Region size in bits (should be not greater than `payload_file_nbit`). One file is - /// partitioned into multiple regions. Just use the default value. - #[builder(default = 22)] - pub payload_regn_nbit: u64, - /// Maximum cached pages for the free list of the item stash. - #[builder(default = 16384)] // 64M total size by default - pub root_hash_ncached_pages: usize, - /// Maximum cached file descriptors for the free list of the item stash. - #[builder(default = 1024)] // 1K fds by default - pub root_hash_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub root_hash_file_nbit: u64, - /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its - /// existing contents will be lost. - #[builder(default = false)] - pub truncate: bool, - /// Config for accessing a version of the DB. - #[builder(default = DbRevConfig::builder().build())] - pub rev: DbRevConfig, - /// Config for the disk buffer. - #[builder(default = DiskBufferConfig::builder().build())] - pub buffer: DiskBufferConfig, - /// Config for Wal. - #[builder(default = WalConfig::builder().build())] - pub wal: WalConfig, -} - /// Necessary linear space instances bundled for a `CompactSpace`. struct SubUniverse { meta: T, @@ -671,7 +604,7 @@ impl Db> { }); // recover from Wal - disk_requester.init_wal("wal", db_path); + disk_requester.init_wal("wal", &db_path); let root_hash_staging = StoreRevMut::new(root_hash_cache); let (data_staging, mut latest) = Db::new_store(&data_cache, reset, offset, cfg, ¶ms)?; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index dcd2a28ec4f1..3a0b48d550c0 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -204,6 +204,7 @@ pub mod proof; pub mod storage; pub mod api; +pub(crate) mod config; pub mod service; pub mod v2; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index af1b80f3fcc2..1f111ba9f58f 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,4 +1,11 @@ //! Disk buffer for staging in memory pages and flushing them to disk. +use std::fmt::Debug; +use std::ops::IndexMut; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::sync::Arc; +use std::{cell::RefCell, collections::HashMap}; + use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; use crate::storage::DeltaPage; use aiofut::{AioBuilder, AioError, AioManager}; @@ -9,15 +16,6 @@ use growthring::{ WalFileImpl, WalStoreImpl, }; use shale::SpaceId; -use std::{ - cell::RefCell, - collections::HashMap, - fmt::Debug, - ops::IndexMut, - path::{Path, PathBuf}, - rc::Rc, - sync::Arc, -}; use tokio::{ sync::{ mpsc, @@ -581,9 +579,12 @@ impl DiskBufferRequester { } /// Initialize the Wal. - pub fn init_wal(&self, waldir: &str, rootpath: PathBuf) { + pub fn init_wal(&self, waldir: &str, rootpath: &Path) { self.sender - .blocking_send(BufferCmd::InitWal(rootpath, waldir.to_string())) + .blocking_send(BufferCmd::InitWal( + rootpath.to_path_buf(), + waldir.to_string(), + )) .map_err(StoreError::Send) .ok(); } @@ -645,7 +646,7 @@ mod tests { let state_path = file::touch_dir("state", &root_db_path).unwrap(); assert!(reset); // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", root_db_path); + disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. let state_cache = Arc::new( @@ -733,7 +734,7 @@ mod tests { let state_path = file::touch_dir("state", &root_db_path).unwrap(); assert!(reset); // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", root_db_path); + disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. let state_cache = Arc::new( @@ -817,7 +818,7 @@ mod tests { let state_path = file::touch_dir("state", &root_db_path).unwrap(); assert!(reset); // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", root_db_path); + disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. let state_cache = Arc::new( From e36f99b9df065cc0262b33b6d18466c1dde1cb7b Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:12:03 -0700 Subject: [PATCH 0224/1053] feat: add proposal (#181) --- firewood/examples/rev.rs | 6 +- firewood/src/config.rs | 2 +- firewood/src/db.rs | 606 +++++++++++++++++++++++++++++------- firewood/src/proof.rs | 1 + firewood/src/storage/mod.rs | 35 ++- firewood/tests/db.rs | 109 ++++++- 6 files changed, 632 insertions(+), 127 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index b7d808232b0b..1abcda0eacfa 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -126,11 +126,11 @@ fn main() { struct RevisionTracker { hashes: VecDeque, - db: Db, + db: Db, } impl RevisionTracker { - fn new(db: Db) -> Self { + fn new(db: Db) -> Self { Self { hashes: VecDeque::new(), db, @@ -159,7 +159,7 @@ impl RevisionTracker { self.hashes.push_front(hash); } - fn commit_batch(&mut self, batch: WriteBatch) { + fn commit_batch(&mut self, batch: WriteBatch) { batch.commit(); let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); diff --git a/firewood/src/config.rs b/firewood/src/config.rs index 2c8d76acdac3..90a114d9c871 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -2,7 +2,7 @@ pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use typed_builder::TypedBuilder; /// Database configuration. -#[derive(TypedBuilder, Debug)] +#[derive(Clone, TypedBuilder, Debug)] pub struct DbConfig { /// Maximum cached pages for the free list of the item stash. #[builder(default = 16384)] // 64M total size by default diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 5c7a446cdfc4..82339f8771a0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -44,10 +44,8 @@ const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; -type Store = ( - Universe>, - DbRev>, -); +type Store = CompactSpace; +type SharedStore = CompactSpace; #[derive(Debug)] #[non_exhaustive] @@ -61,6 +59,7 @@ pub enum DbError { CreateError, Shale(ShaleError), IO(std::io::Error), + InvalidProposal, } impl fmt::Display for DbError { @@ -75,6 +74,7 @@ impl fmt::Display for DbError { DbError::CreateError => write!(f, "database create error"), DbError::IO(e) => write!(f, "I/O error: {e:?}"), DbError::Shale(e) => write!(f, "shale error: {e:?}"), + DbError::InvalidProposal => write!(f, "invalid proposal"), } } } @@ -108,6 +108,7 @@ struct DbParams { root_hash_file_nbit: u64, } +#[derive(Clone)] /// Necessary linear space instances bundled for a `CompactSpace`. struct SubUniverse { meta: T, @@ -129,6 +130,15 @@ impl SubUniverse { } } +impl SubUniverse> { + fn new_from_other(&self) -> SubUniverse> { + SubUniverse { + meta: Arc::new(StoreRevMut::new_from_other(self.meta.as_ref())), + payload: Arc::new(StoreRevMut::new_from_other(self.payload.as_ref())), + } + } +} + impl SubUniverse> { fn rewind( &self, @@ -217,6 +227,7 @@ impl Storable for DbHeader { } } +#[derive(Clone)] /// Necessary linear space instances bundled for the state of the entire DB. struct Universe { merkle: SubUniverse, @@ -232,6 +243,15 @@ impl Universe { } } +impl Universe> { + fn new_from_other(&self) -> Universe> { + Universe { + merkle: self.merkle.new_from_other(), + blob: self.blob.new_from_other(), + } + } +} + impl Universe> { fn to_mem_store_r(&self) -> Universe> { Universe { @@ -426,24 +446,26 @@ impl Drop for DbInner { } } +pub struct DbRevInner { + inner: VecDeque>, + root_hashes: VecDeque, + max_revisions: usize, + base: Universe, + base_revision: Arc>, +} + /// Firewood database handle. -pub struct Db { +pub struct Db { inner: Arc>>, - revisions: Arc>, + revisions: Arc>>, payload_regn_nbit: u64, rev_cfg: DbRevConfig, metrics: Arc, -} - -pub struct DbRevInner { - inner: VecDeque>, - root_hashes: VecDeque, - max_revisions: usize, - base: Universe, + cfg: DbConfig, } // #[metered(registry = DbMetrics, visibility = pub)] -impl Db> { +impl Db { /// Open a database. pub fn new>(db_path: P, cfg: &DbConfig) -> Result { // TODO: make sure all fds are released at the end @@ -501,7 +523,6 @@ impl Db> { let mut header_bytes = [0; std::mem::size_of::()]; nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; drop(file0); - let offset = header_bytes.len() as u64; let params: DbParams = cast_slice(&header_bytes)[0]; let wal = WalConfig::builder() @@ -607,13 +628,20 @@ impl Db> { disk_requester.init_wal("wal", &db_path); let root_hash_staging = StoreRevMut::new(root_hash_cache); - let (data_staging, mut latest) = Db::new_store(&data_cache, reset, offset, cfg, ¶ms)?; + let (data_staging, mut latest) = + Db::new_store(&data_cache, reset, params.payload_regn_nbit, cfg)?; latest.flush_dirty().unwrap(); let base = Universe { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), blob: get_sub_universe_from_empty_delta(&data_cache.blob), }; + let base_revision = Db::new_revision( + &base, + params.payload_regn_nbit, + cfg.payload_max_walk, + &cfg.rev, + )?; Ok(Self { inner: Arc::new(RwLock::new(DbInner { @@ -629,10 +657,12 @@ impl Db> { root_hashes: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, base, + base_revision: Arc::new(base_revision), })), payload_regn_nbit: params.payload_regn_nbit, rev_cfg: cfg.rev.clone(), metrics: Arc::new(DbMetrics::default()), + cfg: cfg.clone(), }) } @@ -640,11 +670,10 @@ impl Db> { fn new_store( cached_space: &Universe>, reset: bool, - offset: u64, + payload_regn_nbit: u64, cfg: &DbConfig, - params: &DbParams, - ) -> Result { - let mut offset = offset; + ) -> Result<(Universe>, DbRev), DbError> { + let mut offset = std::mem::size_of::() as u64; let db_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += DbHeader::MSIZE; let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); @@ -688,6 +717,114 @@ impl Db> { ), }; + let mut rev = + Db::new_revision_arc(&store, payload_regn_nbit, cfg.payload_max_walk, &cfg.rev)?; + rev.flush_dirty().unwrap(); + + Ok((store, rev)) + } + + fn new_revision( + store: &Universe, + payload_regn_nbit: u64, + payload_max_walk: u64, + cfg: &DbRevConfig, + ) -> Result>, DbError> { + // Set up the storage layout + let mut offset = std::mem::size_of::() as u64; + // DbHeader starts after DbParams in merkle meta space + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += DbHeader::MSIZE; + // Merkle CompactHeader starts after DbHeader in merkle meta space + let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + // Blob CompactSpaceHeader starts right in blob meta space + let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + + let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { + let merkle_meta_ref = &store.merkle.meta; + let blob_meta_ref = &store.blob.meta; + + ( + StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), + StoredView::ptr_to_obj( + merkle_meta_ref, + merkle_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), + StoredView::ptr_to_obj( + blob_meta_ref, + blob_payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(), + ) + }; + + let merkle_space = shale::compact::CompactSpace::new( + Arc::new(store.merkle.meta.clone()), + Arc::new(store.merkle.payload.clone()), + merkle_payload_header_ref, + shale::ObjCache::new(cfg.merkle_ncached_objs), + payload_max_walk, + payload_regn_nbit, + ) + .unwrap(); + + #[cfg(feature = "eth")] + let blob_space = shale::compact::CompactSpace::new( + Arc::new(store.blob.meta.clone()), + Arc::new(store.blob.payload.clone()), + blob_payload_header_ref, + shale::ObjCache::new(cfg.blob_ncached_objs), + payload_max_walk, + payload_regn_nbit, + ) + .unwrap(); + + if db_header_ref.acc_root.is_null() { + let mut err = Ok(()); + // create the sentinel node + db_header_ref + .write(|r| { + err = (|| { + Merkle::::init_root(&mut r.acc_root, &merkle_space)?; + Merkle::::init_root(&mut r.kv_root, &merkle_space) + })(); + }) + .unwrap(); + err.map_err(DbError::Merkle)? + } + + Ok(DbRev { + header: db_header_ref, + merkle: Merkle::new(Box::new(merkle_space)), + #[cfg(feature = "eth")] + blob: BlobStash::new(Box::new(blob_space)), + }) + } + + // TODO: can we only have one version of `new_revision`? + fn new_revision_arc( + store: &Universe>, + payload_regn_nbit: u64, + payload_max_walk: u64, + cfg: &DbRevConfig, + ) -> Result>, DbError> { + // Set up the storage layout + let mut offset = std::mem::size_of::() as u64; + // DbHeader starts after DbParams in merkle meta space + let db_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += DbHeader::MSIZE; + // Merkle CompactHeader starts after DbHeader in merkle meta space + let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); + offset += CompactSpaceHeader::MSIZE; + assert!(offset <= SPACE_RESERVED); + // Blob CompactSpaceHeader starts right in blob meta space + let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { let merkle_meta_ref = store.merkle.meta.as_ref(); let blob_meta_ref = store.blob.meta.as_ref(); @@ -713,20 +850,20 @@ impl Db> { store.merkle.meta.clone(), store.merkle.payload.clone(), merkle_payload_header_ref, - shale::ObjCache::new(cfg.rev.merkle_ncached_objs), - cfg.payload_max_walk, - params.payload_regn_nbit, + shale::ObjCache::new(cfg.merkle_ncached_objs), + payload_max_walk, + payload_regn_nbit, ) .unwrap(); #[cfg(feature = "eth")] let blob_space = shale::compact::CompactSpace::new( - staging.blob.meta.clone(), - staging.blob.payload.clone(), + Arc::new(store.blob.meta.clone()), + Arc::new(store.blob.payload.clone()), blob_payload_header_ref, - shale::ObjCache::new(cfg.rev.blob_ncached_objs), - cfg.payload_max_walk, - header.payload_regn_nbit, + shale::ObjCache::new(cfg.blob_ncached_objs), + payload_max_walk, + payload_regn_nbit, ) .unwrap(); @@ -736,28 +873,71 @@ impl Db> { db_header_ref .write(|r| { err = (|| { - Merkle::>::init_root( - &mut r.acc_root, - &merkle_space, - )?; - Merkle::>::init_root( - &mut r.kv_root, - &merkle_space, - ) + Merkle::::init_root(&mut r.acc_root, &merkle_space)?; + Merkle::::init_root(&mut r.kv_root, &merkle_space) })(); }) .unwrap(); err.map_err(DbError::Merkle)? } - let rev = DbRev { + Ok(DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), #[cfg(feature = "eth")] blob: BlobStash::new(Box::new(blob_space)), - }; + }) + } - Ok((store, rev)) + /// Create a proposal. + pub fn new_proposal>( + &self, + data: Batch, + ) -> Result, DbError> { + let (store, mut rev) = Db::new_store( + &self.inner.read().data_cache, + false, + self.payload_regn_nbit, + &self.cfg, + )?; + data.into_iter().try_for_each(|op| -> Result<(), DbError> { + match op { + BatchOp::Put { key, value } => { + #[cfg(feature = "eth")] + let (header, merkle, _) = rev.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.borrow_split(); + merkle + .insert(key, value, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + BatchOp::Delete { key } => { + #[cfg(feature = "eth")] + let (header, merkle, _) = rev.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.borrow_split(); + merkle + .remove(key, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + } + })?; + rev.flush_dirty().unwrap(); + + // TODO: remove `WriteBatch`. Currently `WriteBatch` + // and `Proposal` cannot be used at the same time. + let parent = ProposalBase::View(Arc::clone(&self.revisions.lock().base_revision)); + Ok(Proposal { + m: Arc::clone(&self.inner), + r: Arc::clone(&self.revisions), + cfg: self.cfg.clone(), + rev, + store, + committed: Arc::new(Mutex::new(false)), + parent, + }) } /// Get a handle that grants the access to any committed state of the entire DB, @@ -770,7 +950,7 @@ impl Db> { &self, root_hash: &TrieHash, cfg: Option, - ) -> Option>> { + ) -> Option> { let mut revisions = self.revisions.lock(); let inner_lock = self.inner.read(); @@ -835,18 +1015,6 @@ impl Db> { } } - // Set up the storage layout - let mut offset = std::mem::size_of::() as u64; - // DbHeader starts after DbParams in merkle meta space - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DbHeader::MSIZE; - // Merkle CompactHeader starts after DbHeader in merkle meta space - let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); - // Blob CompactSpaceHeader starts right in blob meta space - let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let space = if nback == 0 { &revisions.base } else { @@ -855,63 +1023,17 @@ impl Db> { // Release the lock after we find the revision drop(inner_lock); - let (db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { - let merkle_meta_ref = &space.merkle.meta; - let blob_meta_ref = &space.blob.meta; - - ( - StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), - StoredView::ptr_to_obj( - merkle_meta_ref, - merkle_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - StoredView::ptr_to_obj( - blob_meta_ref, - blob_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - ) - }; - - let merkle_space = shale::compact::CompactSpace::new( - Arc::new(space.merkle.meta.clone()), - Arc::new(space.merkle.payload.clone()), - merkle_payload_header_ref, - shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).merkle_ncached_objs), - 0, - self.payload_regn_nbit, - ) - .unwrap(); - - #[cfg(feature = "eth")] - let blob_space = shale::compact::CompactSpace::new( - Arc::new(space.blob.meta.clone()), - Arc::new(space.blob.payload.clone()), - blob_payload_header_ref, - shale::ObjCache::new(cfg.as_ref().unwrap_or(&self.rev_cfg).blob_ncached_objs), - 0, - self.payload_regn_nbit, - ) - .unwrap(); - + let cfg = cfg.as_ref().unwrap_or(&self.rev_cfg); Some(Revision { - rev: DbRev { - header: db_header_ref, - merkle: Merkle::new(Box::new(merkle_space)), - #[cfg(feature = "eth")] - blob: BlobStash::new(Box::new(blob_space)), - }, + rev: Db::new_revision(space, self.payload_regn_nbit, 0, cfg).unwrap(), }) } } #[metered(registry = DbMetrics, visibility = pub)] -impl + Send + Sync> Db { +impl + Send + Sync, T> Db { /// Create a write batch. - pub fn new_writebatch(&self) -> WriteBatch { + pub fn new_writebatch(&self) -> WriteBatch { WriteBatch { m: Arc::clone(&self.inner), r: Arc::clone(&self.revisions), @@ -943,7 +1065,7 @@ impl + Send + Sync> Db { } } #[cfg(feature = "eth")] -impl + Send + Sync> Db { +impl + Send + Sync, T: ShaleStore + Send + Sync> Db { /// Dump the Trie of the latest entire account model storage. pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.inner.read().latest.dump(w) @@ -997,16 +1119,273 @@ impl std::ops::Deref for Revision { } } +/// A key/value pair operation. Only put (upsert) and delete are +/// supported +#[derive(Debug)] +pub enum BatchOp { + Put { key: K, value: Vec }, + Delete { key: K }, +} + +/// A list of operations to consist of a batch that +/// can be proposed +pub type Batch = Vec>; + +pub struct Proposal { + // State of the Db + m: Arc>>, + r: Arc>>, + cfg: DbConfig, + + // State of the proposal + rev: DbRev, + store: Universe>, + committed: Arc>, + + parent: ProposalBase, +} + +pub enum ProposalBase { + Proposal(Arc>), + View(Arc>), +} + +impl Proposal { + pub fn propose>( + self: Arc, + data: Batch, + ) -> Result, DbError> { + let store = self.store.new_from_other(); + + let m = Arc::clone(&self.m); + let r = Arc::clone(&self.r); + let cfg = self.cfg.clone(); + let mut rev = Db::new_revision_arc( + &store, + cfg.payload_regn_nbit, + cfg.payload_max_walk, + &cfg.rev, + )?; + data.into_iter().try_for_each(|op| -> Result<(), DbError> { + match op { + BatchOp::Put { key, value } => { + #[cfg(feature = "eth")] + let (header, merkle, _) = rev.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.borrow_split(); + merkle + .insert(key, value, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + BatchOp::Delete { key } => { + #[cfg(feature = "eth")] + let (header, merkle, _) = rev.borrow_split(); + #[cfg(not(feature = "eth"))] + let (header, merkle) = rev.borrow_split(); + merkle + .remove(key, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + } + })?; + rev.flush_dirty().unwrap(); + + let parent = ProposalBase::Proposal(self); + + Ok(Proposal { + m, + r, + cfg, + rev, + store, + committed: Arc::new(Mutex::new(false)), + parent, + }) + } + + pub fn commit(&self) -> Result<(), DbError> { + let mut committed = self.committed.lock(); + if *committed { + return Ok(()); + } + + if let ProposalBase::Proposal(p) = &self.parent { + p.commit()?; + }; + + // Check for if it can be committed + let mut revisions = self.r.lock(); + let committed_root_hash = revisions.base_revision.kv_root_hash().ok(); + let committed_root_hash = + committed_root_hash.expect("committed_root_hash should not be none"); + match &self.parent { + ProposalBase::Proposal(p) => { + let parent_root_hash = p.rev.kv_root_hash().ok(); + let parent_root_hash = + parent_root_hash.expect("parent_root_hash should not be none"); + if parent_root_hash != committed_root_hash { + return Err(DbError::InvalidProposal); + } + } + ProposalBase::View(p) => { + let parent_root_hash = p.kv_root_hash().ok(); + let parent_root_hash = + parent_root_hash.expect("parent_root_hash should not be none"); + if parent_root_hash != committed_root_hash { + return Err(DbError::InvalidProposal); + } + } + }; + + #[cfg(feature = "eth")] + self.rev.root_hash().ok(); + + let kv_root_hash = self.rev.kv_root_hash().ok(); + let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); + + // clear the staging layer and apply changes to the CachedSpace + let (merkle_payload_redo, merkle_payload_wal) = self.store.merkle.payload.delta(); + let (merkle_meta_redo, merkle_meta_wal) = self.store.merkle.meta.delta(); + let (blob_payload_redo, blob_payload_wal) = self.store.blob.payload.delta(); + let (blob_meta_redo, blob_meta_wal) = self.store.blob.meta.delta(); + + let mut rev_inner = self.m.write(); + let merkle_meta_undo = rev_inner + .data_cache + .merkle + .meta + .update(&merkle_meta_redo) + .unwrap(); + let merkle_payload_undo = rev_inner + .data_cache + .merkle + .payload + .update(&merkle_payload_redo) + .unwrap(); + let blob_meta_undo = rev_inner + .data_cache + .blob + .meta + .update(&blob_meta_redo) + .unwrap(); + let blob_payload_undo = rev_inner + .data_cache + .blob + .payload + .update(&blob_payload_redo) + .unwrap(); + + // update the rolling window of past revisions + let latest_past = Universe { + merkle: get_sub_universe_from_deltas( + &rev_inner.data_cache.merkle, + merkle_meta_undo, + merkle_payload_undo, + ), + blob: get_sub_universe_from_deltas( + &rev_inner.data_cache.blob, + blob_meta_undo, + blob_payload_undo, + ), + }; + + let max_revisions = revisions.max_revisions; + if let Some(rev) = revisions.inner.front_mut() { + rev.merkle + .meta + .set_base_space(latest_past.merkle.meta.inner().clone()); + rev.merkle + .payload + .set_base_space(latest_past.merkle.payload.inner().clone()); + rev.blob + .meta + .set_base_space(latest_past.blob.meta.inner().clone()); + rev.blob + .payload + .set_base_space(latest_past.blob.payload.inner().clone()); + } + revisions.inner.push_front(latest_past); + while revisions.inner.len() > max_revisions { + revisions.inner.pop_back(); + } + + let base = Universe { + merkle: get_sub_universe_from_empty_delta(&rev_inner.data_cache.merkle), + blob: get_sub_universe_from_empty_delta(&rev_inner.data_cache.blob), + }; + let base_revision = Db::new_revision(&base, 0, self.cfg.payload_max_walk, &self.cfg.rev)?; + revisions.base = base; + revisions.base_revision = Arc::new(base_revision); + + // update the rolling window of root hashes + revisions.root_hashes.push_front(kv_root_hash.clone()); + if revisions.root_hashes.len() > max_revisions { + revisions + .root_hashes + .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); + } + + rev_inner.root_hash_staging.write(0, &kv_root_hash.0); + let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); + + // schedule writes to the disk + rev_inner.disk_requester.write( + vec![ + BufferWrite { + space_id: rev_inner.data_staging.merkle.payload.id(), + delta: merkle_payload_redo, + }, + BufferWrite { + space_id: rev_inner.data_staging.merkle.meta.id(), + delta: merkle_meta_redo, + }, + BufferWrite { + space_id: rev_inner.data_staging.blob.payload.id(), + delta: blob_payload_redo, + }, + BufferWrite { + space_id: rev_inner.data_staging.blob.meta.id(), + delta: blob_meta_redo, + }, + BufferWrite { + space_id: rev_inner.root_hash_staging.id(), + delta: root_hash_redo, + }, + ], + AshRecord( + [ + (MERKLE_META_SPACE, merkle_meta_wal), + (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), + (BLOB_META_SPACE, blob_meta_wal), + (BLOB_PAYLOAD_SPACE, blob_payload_wal), + (ROOT_HASH_SPACE, root_hash_wal), + ] + .into(), + ), + ); + *committed = true; + Ok(()) + } +} + +impl + Send + Sync, T: ShaleStore + Send + Sync> Proposal { + pub fn get_revision(&self) -> &DbRev { + &self.rev + } +} + /// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself /// because when an error occurs, the write batch will be automatically aborted so that the DB /// remains clean. -pub struct WriteBatch { +pub struct WriteBatch { m: Arc>>, - r: Arc>, + r: Arc>>, committed: bool, } -impl + Send + Sync> WriteBatch { +impl + Send + Sync, T> WriteBatch { /// Insert an item to the generic key-value storage. pub fn kv_insert>(self, key: K, val: Vec) -> Result { let mut rev = self.m.write(); @@ -1321,7 +1700,7 @@ impl + Send + Sync> WriteBatch { } } -impl Drop for WriteBatch { +impl Drop for WriteBatch { fn drop(&mut self) { if !self.committed { // drop the staging changes @@ -1333,3 +1712,16 @@ impl Drop for WriteBatch { } } } + +impl Drop for Proposal { + fn drop(&mut self) { + if !*self.committed.lock() { + // drop the staging changes + self.m.read().data_staging.merkle.payload.reset_deltas(); + self.m.read().data_staging.merkle.meta.reset_deltas(); + self.m.read().data_staging.blob.payload.reset_deltas(); + self.m.read().data_staging.blob.meta.reset_deltas(); + self.m.read().root_hash_staging.reset_deltas(); + } + } +} diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index c5f83d34d5b6..1d53ddb53050 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -75,6 +75,7 @@ impl From for ProofError { ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) } DbError::Shale(e) => ProofError::Shale(e), + DbError::InvalidProposal => ProofError::InvalidProof, } } } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index c1fa4a6b29a1..39610b95143d 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -511,32 +511,45 @@ impl CachedStore for StoreRevMut { let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; let deltas = &self.deltas.read().pages; + let prev_deltas = &self.prev_deltas.read().pages; if s_pid == e_pid { match deltas.get(&s_pid) { Some(p) => p[s_off..e_off + 1].to_vec(), - None => self.base_space.get_slice(offset, length)?, + None => match prev_deltas.get(&s_pid) { + Some(p) => p[s_off..e_off + 1].to_vec(), + None => self.base_space.get_slice(offset, length)?, + }, } } else { let mut data = match deltas.get(&s_pid) { Some(p) => p[s_off..].to_vec(), - None => self - .base_space - .get_slice(offset, PAGE_SIZE - s_off as u64)?, + None => match prev_deltas.get(&s_pid) { + Some(p) => p[s_off..].to_vec(), + None => self + .base_space + .get_slice(offset, PAGE_SIZE - s_off as u64)?, + }, }; for p in s_pid + 1..e_pid { match deltas.get(&p) { Some(p) => data.extend(**p), - None => { - data.extend(&self.base_space.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?) - } + None => match prev_deltas.get(&p) { + Some(p) => data.extend(**p), + None => data.extend( + &self.base_space.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?, + ), + }, }; } match deltas.get(&e_pid) { Some(p) => data.extend(&p[..e_off + 1]), - None => data.extend( - self.base_space - .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, - ), + None => match prev_deltas.get(&e_pid) { + Some(p) => data.extend(&p[..e_off + 1]), + None => data.extend( + self.base_space + .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, + ), + }, } data } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 1d90fc40b27b..bf691ec7e8f6 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,7 +1,7 @@ use firewood::{ - db::{Db as PersistedDb, DbConfig, DbError, WalConfig}, + db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, merkle::{Node, TrieHash}, - storage::StoreRevMut, + storage::{StoreRevMut, StoreRevShared}, }; use firewood_shale::compact::CompactSpace; use std::{ @@ -9,6 +9,7 @@ use std::{ fs::remove_dir_all, ops::{Deref, DerefMut}, path::Path, + sync::Arc, }; // TODO: use a trait @@ -21,8 +22,9 @@ macro_rules! kv_dump { } type Store = CompactSpace; +type SharedStore = CompactSpace; -struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); +struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); impl<'a, P: AsRef + ?Sized> Db<'a, P> { fn new(path: &'a P, cfg: &DbConfig) -> Result { @@ -216,7 +218,7 @@ fn create_db_issue_proof() { } impl + ?Sized> Deref for Db<'_, P> { - type Target = PersistedDb; + type Target = PersistedDb; fn deref(&self) -> &Self::Target { &self.0 @@ -224,7 +226,104 @@ impl + ?Sized> Deref for Db<'_, P> { } impl + ?Sized> DerefMut for Db<'_, P> { - fn deref_mut(&mut self) -> &mut PersistedDb { + fn deref_mut(&mut self) -> &mut PersistedDb { &mut self.0 } } + +#[test] +fn db_proposal() { + let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + + let db = Db::new("test_db_proposal", &cfg.clone().truncate(true).build()) + .expect("db initiation should succeed"); + + let batch = vec![ + BatchOp::Put { + key: b"k", + value: "v".as_bytes().to_vec(), + }, + BatchOp::Delete { key: b"z" }, + ]; + + let proposal = Arc::new(db.new_proposal(batch).unwrap()); + let rev = proposal.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + + let batch_2 = vec![BatchOp::Put { + key: b"k2", + value: "v2".as_bytes().to_vec(), + }]; + let proposal_2 = proposal.clone().propose(batch_2).unwrap(); + let rev = proposal_2.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + let val = rev.kv_get(b"k2"); + assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); + + proposal.commit().unwrap(); + proposal_2.commit().unwrap(); + + std::thread::scope(|scope| { + scope.spawn(|| { + let another_batch = vec![BatchOp::Put { + key: b"another_k", + value: "another_v".as_bytes().to_vec(), + }]; + let another_proposal = proposal.clone().propose(another_batch).unwrap(); + let rev = another_proposal.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + let val = rev.kv_get(b"another_k"); + assert_eq!(val.unwrap(), "another_v".as_bytes().to_vec()); + // The proposal is invalid and cannot be committed + assert!(another_proposal.commit().is_err()); + }); + + scope.spawn(|| { + let another_batch = vec![BatchOp::Put { + key: b"another_k_1", + value: "another_v_1".as_bytes().to_vec(), + }]; + let another_proposal = proposal.clone().propose(another_batch).unwrap(); + let rev = another_proposal.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + let val = rev.kv_get(b"another_k_1"); + assert_eq!(val.unwrap(), "another_v_1".as_bytes().to_vec()); + }); + }); + + // Recusrive commit + + let batch = vec![BatchOp::Put { + key: b"k3", + value: "v3".as_bytes().to_vec(), + }]; + let proposal = Arc::new(db.new_proposal(batch).unwrap()); + let rev = proposal.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + let val = rev.kv_get(b"k2"); + assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); + let val = rev.kv_get(b"k3"); + assert_eq!(val.unwrap(), "v3".as_bytes().to_vec()); + + let batch_2 = vec![BatchOp::Put { + key: b"k4", + value: "v4".as_bytes().to_vec(), + }]; + let proposal_2 = proposal.clone().propose(batch_2).unwrap(); + let rev = proposal_2.get_revision(); + let val = rev.kv_get(b"k"); + assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + let val = rev.kv_get(b"k2"); + assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); + let val = rev.kv_get(b"k3"); + assert_eq!(val.unwrap(), "v3".as_bytes().to_vec()); + let val = rev.kv_get(b"k4"); + assert_eq!(val.unwrap(), "v4".as_bytes().to_vec()); + + proposal_2.commit().unwrap(); +} From c6be2e3ecbacfaf73b28da71999b6e07b7ebb2ae Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:56:47 -0700 Subject: [PATCH 0225/1053] Update README.md with proposal implementation (#182) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 082f9aee39a1..e3bcd5c8a356 100644 --- a/README.md +++ b/README.md @@ -110,12 +110,13 @@ into prometheus metrics or json (it implements [serde::Serialize]) ### Seasoned milestone This milestone will add support for proposals, including proposed future branches, with a cache to make committing these branches efficiently. -- [ ] Be able to support multiple proposed revisions against latest committed +- [x] Be able to support multiple proposed revisions against latest committed version. -- [ ] Be able to propose a batch against the existing committed revision, or +- [x] Be able to propose a batch against the existing committed revision, or propose a batch against any existing proposed revision. -- [ ] Be able to quickly commit a batch that has been proposed. Note that this -invalidates all other proposals that are not children of the committed proposed batch. +- [x] Commit a batch that has been proposed will invalidate all other proposals +that are not children of the committed proposed batch. +- [ ] Be able to quickly commit a batch that has been proposed. - [ ] Add metric reporting - [ ] Refactor `Shale` to be more idiomatic From bde806f9c63574fed01518b660c124cbc2cd9d8c Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:54:24 -0700 Subject: [PATCH 0226/1053] chore: proposal test cleanup (#184) --- firewood/tests/db.rs | 78 +++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index bf691ec7e8f6..a979cc97b15e 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -231,8 +231,15 @@ impl + ?Sized> DerefMut for Db<'_, P> { } } +macro_rules! assert_val { + ($rev: ident, $key:literal, $expected_val:literal) => { + let actual = $rev.kv_get($key.as_bytes()).unwrap(); + assert_eq!(actual, $expected_val.as_bytes().to_vec()); + }; +} + #[test] -fn db_proposal() { +fn db_proposal() -> Result<(), DbError> { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); let db = Db::new("test_db_proposal", &cfg.clone().truncate(true).build()) @@ -246,52 +253,47 @@ fn db_proposal() { BatchOp::Delete { key: b"z" }, ]; - let proposal = Arc::new(db.new_proposal(batch).unwrap()); + let proposal = Arc::new(db.new_proposal(batch)?); let rev = proposal.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); + assert_val!(rev, "k", "v"); let batch_2 = vec![BatchOp::Put { key: b"k2", value: "v2".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose(batch_2).unwrap(); + let proposal_2 = proposal.clone().propose(batch_2)?; let rev = proposal_2.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); - let val = rev.kv_get(b"k2"); - assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); + assert_val!(rev, "k", "v"); + assert_val!(rev, "k2", "v2"); - proposal.commit().unwrap(); - proposal_2.commit().unwrap(); + proposal.commit()?; + proposal_2.commit()?; std::thread::scope(|scope| { - scope.spawn(|| { + scope.spawn(|| -> Result<(), DbError> { let another_batch = vec![BatchOp::Put { key: b"another_k", value: "another_v".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose(another_batch).unwrap(); + let another_proposal = proposal.clone().propose(another_batch)?; let rev = another_proposal.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); - let val = rev.kv_get(b"another_k"); - assert_eq!(val.unwrap(), "another_v".as_bytes().to_vec()); + assert_val!(rev, "k", "v"); + assert_val!(rev, "another_k", "another_v"); // The proposal is invalid and cannot be committed assert!(another_proposal.commit().is_err()); + Ok(()) }); - scope.spawn(|| { + scope.spawn(|| -> Result<(), DbError> { let another_batch = vec![BatchOp::Put { key: b"another_k_1", value: "another_v_1".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose(another_batch).unwrap(); + let another_proposal = proposal.clone().propose(another_batch)?; let rev = another_proposal.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); - let val = rev.kv_get(b"another_k_1"); - assert_eq!(val.unwrap(), "another_v_1".as_bytes().to_vec()); + assert_val!(rev, "k", "v"); + assert_val!(rev, "another_k_1", "another_v_1"); + Ok(()) }); }); @@ -301,29 +303,23 @@ fn db_proposal() { key: b"k3", value: "v3".as_bytes().to_vec(), }]; - let proposal = Arc::new(db.new_proposal(batch).unwrap()); + let proposal = Arc::new(db.new_proposal(batch)?); let rev = proposal.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); - let val = rev.kv_get(b"k2"); - assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); - let val = rev.kv_get(b"k3"); - assert_eq!(val.unwrap(), "v3".as_bytes().to_vec()); + assert_val!(rev, "k", "v"); + assert_val!(rev, "k2", "v2"); + assert_val!(rev, "k3", "v3"); let batch_2 = vec![BatchOp::Put { key: b"k4", value: "v4".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose(batch_2).unwrap(); + let proposal_2 = proposal.clone().propose(batch_2)?; let rev = proposal_2.get_revision(); - let val = rev.kv_get(b"k"); - assert_eq!(val.unwrap(), "v".as_bytes().to_vec()); - let val = rev.kv_get(b"k2"); - assert_eq!(val.unwrap(), "v2".as_bytes().to_vec()); - let val = rev.kv_get(b"k3"); - assert_eq!(val.unwrap(), "v3".as_bytes().to_vec()); - let val = rev.kv_get(b"k4"); - assert_eq!(val.unwrap(), "v4".as_bytes().to_vec()); - - proposal_2.commit().unwrap(); + assert_val!(rev, "k", "v"); + assert_val!(rev, "k2", "v2"); + assert_val!(rev, "k3", "v3"); + assert_val!(rev, "k4", "v4"); + + proposal_2.commit()?; + Ok(()) } From a119f14da648085f681a5bc6bd69eb57582f6766 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 9 Aug 2023 17:44:56 -0400 Subject: [PATCH 0227/1053] Open-options instead of a truncate bool (#185) --- firewood/src/db.rs | 9 ++++++++- firewood/src/file.rs | 37 ++++++++++++++++++++-------------- firewood/src/storage/buffer.rs | 6 +++--- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 82339f8771a0..f6ce2bc3e7d3 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -472,7 +472,14 @@ impl Db { if cfg.truncate { let _ = std::fs::remove_dir_all(db_path.as_ref()); } - let (db_path, reset) = file::open_dir(db_path, cfg.truncate)?; + + let open_options = if cfg.truncate { + file::Options::Truncate + } else { + file::Options::NoTruncate + }; + + let (db_path, reset) = file::open_dir(db_path, open_options)?; let merkle_path = file::touch_dir("merkle", &db_path)?; let merkle_meta_path = file::touch_dir("meta", &merkle_path)?; diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 2f8609d753ff..a23f43dbd068 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -14,12 +14,22 @@ pub struct File { fd: Fd, } +#[derive(PartialEq, Eq)] +pub enum Options { + Truncate, + NoTruncate, +} + impl File { - pub fn open_file(rootpath: PathBuf, fname: &str, truncate: bool) -> Result { + pub fn open_file( + rootpath: PathBuf, + fname: &str, + options: Options, + ) -> Result { let mut filepath = rootpath; filepath.push(fname); Ok(std::fs::File::options() - .truncate(truncate) + .truncate(options == Options::Truncate) .read(true) .write(true) .mode(0o600) @@ -45,7 +55,8 @@ impl File { pub fn new>(fid: u64, _flen: u64, rootdir: P) -> Result { let fname = Self::_get_fname(fid); - let fd = match Self::open_file(rootdir.as_ref().to_path_buf(), &fname, false) { + let fd = match Self::open_file(rootdir.as_ref().to_path_buf(), &fname, Options::NoTruncate) + { Ok(fd) => fd, Err(e) => match e.kind() { ErrorKind::NotFound => Self::create_file(rootdir.as_ref().to_path_buf(), &fname)?, @@ -79,22 +90,18 @@ pub fn touch_dir(dirname: &str, rootdir: &Path) -> Result>( path: P, - truncate: bool, + options: Options, ) -> Result<(PathBuf, bool), std::io::Error> { - let mut reset_header = truncate; + let truncate = options == Options::Truncate; + if truncate { let _ = std::fs::remove_dir_all(path.as_ref()); } + match std::fs::create_dir(path.as_ref()) { - Err(e) => { - if truncate || e.kind() != ErrorKind::AlreadyExists { - return Err(e); - } - } - Ok(_) => { - // the DB did not exist - reset_header = true - } + Err(e) if truncate || e.kind() != ErrorKind::AlreadyExists => Err(e), + // the DB already exists + Err(_) => Ok((path.as_ref().to_path_buf(), false)), + Ok(_) => Ok((path.as_ref().to_path_buf(), true)), } - Ok((PathBuf::from(path.as_ref()), reset_header)) } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 1f111ba9f58f..f2eeeedb9c41 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -640,7 +640,7 @@ mod tests { // TODO: Run the test in a separate standalone directory for concurrency reasons let path = std::path::PathBuf::from(r"/tmp/firewood"); - let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); + let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory let state_path = file::touch_dir("state", &root_db_path).unwrap(); @@ -728,7 +728,7 @@ mod tests { // TODO: Run the test in a separate standalone directory for concurrency reasons let tmp_dir = TempDir::new("firewood").unwrap(); let path = get_file_path(tmp_dir.path(), file!(), line!()); - let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); + let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory let state_path = file::touch_dir("state", &root_db_path).unwrap(); @@ -812,7 +812,7 @@ mod tests { let tmp_dir = TempDir::new("firewood").unwrap(); let path = get_file_path(tmp_dir.path(), file!(), line!()); - let (root_db_path, reset) = crate::file::open_dir(path, true).unwrap(); + let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory let state_path = file::touch_dir("state", &root_db_path).unwrap(); From 541c7d04a9bc155d08c74a2a3d61451f7b57a4a7 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 10 Aug 2023 08:43:56 -0700 Subject: [PATCH 0228/1053] Update Seasoned milestone (#189) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3bcd5c8a356..d132fa805074 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,6 @@ propose a batch against any existing proposed revision. - [x] Commit a batch that has been proposed will invalidate all other proposals that are not children of the committed proposed batch. - [ ] Be able to quickly commit a batch that has been proposed. -- [ ] Add metric reporting -- [ ] Refactor `Shale` to be more idiomatic ### Dried milestone The focus of this milestone will be to support synchronization to other @@ -130,6 +128,8 @@ verify the correctness of the data. corresponding range proofs that verify the correctness of the data. - [ ] Enforce limits on the size of the range proof as well as keys to make synchronization easier for clients. +- [ ] Add metric reporting +- [ ] Refactor `Shale` to be more idiomatic ## Build Firewood currently is Linux-only, as it has a dependency on the asynchronous From bd07742902e993f004a34cf1ac353542579ecea7 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 10 Aug 2023 08:59:46 -0700 Subject: [PATCH 0229/1053] chore: add comments for `Proposal` (#186) Co-authored-by: Richard Pringle --- firewood/src/db.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f6ce2bc3e7d3..59772675d07e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1138,6 +1138,11 @@ pub enum BatchOp { /// can be proposed pub type Batch = Vec>; +/// An atomic batch of changes proposed against the latest committed revision, +/// or any existing [Proposal]. Multiple proposals can be created against the +/// latest committed revision at the same time. [Proposal] is immutable meaning +/// the internal batch cannot be altered after creation. Committing a proposal +/// invalidates all other proposals that are not children of the committed one. pub struct Proposal { // State of the Db m: Arc>>, @@ -1158,6 +1163,8 @@ pub enum ProposalBase { } impl Proposal { + // Propose a new proposal from this proposal. The new proposal will be + // the child of it. pub fn propose>( self: Arc, data: Batch, @@ -1212,6 +1219,8 @@ impl Proposal { }) } + /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are + /// either retained on disk or lost together during a crash. pub fn commit(&self) -> Result<(), DbError> { let mut committed = self.committed.lock(); if *committed { From d14a8381841bfbdecc4622cc19955b69713d54c0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 10 Aug 2023 09:34:40 -0700 Subject: [PATCH 0230/1053] Rkuris/db refactor (#180) --- firewood/src/db.rs | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 59772675d07e..44f143e52e13 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -42,7 +42,7 @@ const BLOB_PAYLOAD_SPACE: SpaceId = 0x3; const ROOT_HASH_SPACE: SpaceId = 0x4; const SPACE_RESERVED: u64 = 0x1000; -const MAGIC_STR: &[u8; 13] = b"firewood v0.1"; +const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; type Store = CompactSpace; type SharedStore = CompactSpace; @@ -503,10 +503,8 @@ impl Db { } nix::unistd::ftruncate(fd0, 0).map_err(DbError::System)?; nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DbError::System)?; - let mut magic = [0; 16]; - magic[..MAGIC_STR.len()].copy_from_slice(MAGIC_STR); let header = DbParams { - magic, + magic: *MAGIC_STR, meta_file_nbit: cfg.meta_file_nbit, payload_file_nbit: cfg.payload_file_nbit, payload_regn_nbit: cfg.payload_regn_nbit, @@ -746,29 +744,22 @@ impl Db { let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += CompactSpaceHeader::MSIZE; assert!(offset <= SPACE_RESERVED); - // Blob CompactSpaceHeader starts right in blob meta space - let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); - let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { - let merkle_meta_ref = &store.merkle.meta; - let blob_meta_ref = &store.blob.meta; + let mut db_header_ref = + StoredView::ptr_to_obj(&store.merkle.meta, db_header, DbHeader::MSIZE).unwrap(); - ( - StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), - StoredView::ptr_to_obj( - merkle_meta_ref, - merkle_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - StoredView::ptr_to_obj( - blob_meta_ref, - blob_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - ) - }; + let merkle_payload_header_ref = StoredView::ptr_to_obj( + &store.merkle.meta, + merkle_payload_header, + shale::compact::CompactHeader::MSIZE, + )?; + + #[cfg(feature = "eth")] + let blob_payload_header_ref = StoredView::ptr_to_obj( + &store.blob.meta, + blob_payload_header, + shale::compact::CompactHeader::MSIZE, + )?; let merkle_space = shale::compact::CompactSpace::new( Arc::new(store.merkle.meta.clone()), From 09ddc1aa19ff5939b1072cd70f751c456649dbd3 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 10 Aug 2023 18:20:18 -0700 Subject: [PATCH 0231/1053] chore: deprecate `WriteBatch` and use `Proposal` instead (#188) --- firewood/examples/benchmark.rs | 14 +- firewood/examples/rev.rs | 60 ++-- firewood/src/api.rs | 69 +---- firewood/src/db.rs | 487 ++++----------------------------- firewood/src/lib.rs | 2 +- firewood/src/service/client.rs | 255 +---------------- firewood/src/service/mod.rs | 64 ----- firewood/src/service/server.rs | 141 +--------- firewood/tests/db.rs | 49 ++-- fwdctl/src/delete.rs | 10 +- fwdctl/src/insert.rs | 14 +- 11 files changed, 131 insertions(+), 1034 deletions(-) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 0ba02b9581e5..f1e3ccca8e6e 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -3,7 +3,7 @@ use clap::Parser; use criterion::Criterion; -use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; use rand::{rngs::StdRng, Rng, SeedableRng}; #[derive(Parser, Debug)] @@ -50,13 +50,17 @@ fn main() { let db = Db::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); for batch in workload.iter() { - let mut wb = db.new_writebatch(); + let mut wb = Vec::new(); for (k, v) in batch { - wb = wb.kv_insert(k, v.to_vec()).unwrap(); + let write = BatchOp::Put { + key: k, + value: v.to_vec(), + }; + wb.push(write); } - - wb.commit(); + let proposal = db.new_proposal(wb).unwrap(); + proposal.commit().unwrap(); } }) }, diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 1abcda0eacfa..c5fc7cfa9415 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -4,7 +4,7 @@ use std::{collections::VecDeque, path::Path}; use firewood::{ - db::{Db, DbConfig, Revision, WalConfig, WriteBatch}, + db::{BatchOp, Db, DbConfig, Proposal, Revision, WalConfig}, merkle::{Node, TrieHash}, proof::Proof, storage::{StoreRevMut, StoreRevShared}, @@ -22,25 +22,6 @@ fn main() { .expect("db initiation should succeed"); let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; - std::thread::scope(|scope| { - scope.spawn(|| { - db.new_writebatch() - .kv_insert("k1", "v1".into()) - .unwrap() - .commit(); - }); - - scope.spawn(|| { - db.new_writebatch() - .kv_insert("k2", "v2".into()) - .unwrap() - .commit(); - }); - }); - - assert_eq!("v1".as_bytes().to_vec(), db.kv_get("k1").unwrap()); - assert_eq!("v2".as_bytes().to_vec(), db.kv_get("k2").unwrap()); - let mut revision_tracker = RevisionTracker::new(db); revision_tracker.create_revisions(items.into_iter()); @@ -126,11 +107,11 @@ fn main() { struct RevisionTracker { hashes: VecDeque, - db: Db, + db: Db, } impl RevisionTracker { - fn new(db: Db) -> Self { + fn new(db: Db) -> Self { Self { hashes: VecDeque::new(), db, @@ -150,17 +131,19 @@ impl RevisionTracker { K: AsRef<[u8]>, V: AsRef<[u8]>, { - self.db - .new_writebatch() - .kv_insert(k, v.as_ref().to_vec()) - .unwrap() - .commit(); + let batch = vec![BatchOp::Put { + key: k, + value: v.as_ref().to_vec(), + }]; + let proposal = self.db.new_proposal(batch).unwrap(); + proposal.commit().unwrap(); + let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); } - fn commit_batch(&mut self, batch: WriteBatch) { - batch.commit(); + fn commit_proposal(&mut self, proposal: Proposal) { + proposal.commit().unwrap(); let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); } @@ -203,7 +186,7 @@ fn verify_root_hashes(revision_tracker: &mut RevisionTracker) { .kv_root_hash() .expect("root-hash for current state should exist"); - // The following is true as long as there are no dirty-writes. + // The following should always hold. assert_eq!(revision_root_hash, current_root_hash); let revision = revision_tracker.get_revision(2); @@ -219,11 +202,11 @@ fn verify_root_hashes(revision_tracker: &mut RevisionTracker) { .expect("root-hash for revision-1 should exist"); println!("{revision_root_hash:?}"); - let write = revision_tracker - .db - .new_writebatch() - .kv_insert("k", vec![b'v']) - .unwrap(); + let batch = vec![BatchOp::Put { + key: "k", + value: vec![b'v'], + }]; + let proposal = revision_tracker.db.new_proposal(batch).unwrap(); let actual_revision_root_hash = revision_tracker .get_revision(1) @@ -231,11 +214,10 @@ fn verify_root_hashes(revision_tracker: &mut RevisionTracker) { .expect("root-hash for revision-1 should exist"); assert_eq!(revision_root_hash, actual_revision_root_hash); - // Read the uncommitted value while the batch is still active. - let val = revision_tracker.db.kv_get("k").unwrap(); - assert_eq!("v".as_bytes().to_vec(), val); + // Uncommitted changes of proposal cannot be seen from db. + assert!(revision_tracker.db.kv_get("k").is_err()); - revision_tracker.commit_batch(write); + revision_tracker.commit_proposal(proposal); let new_revision_root_hash = revision_tracker .get_revision(1) diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 9ac74a1d2742..294887fa52c9 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -18,77 +18,10 @@ use async_trait::async_trait; pub type Nonce = u64; #[async_trait] -pub trait Db { - async fn new_writebatch(&self) -> B; +pub trait Db { async fn get_revision(&self, root_hash: TrieHash, cfg: Option) -> Option; } -#[async_trait] -pub trait WriteBatch -where - Self: Sized, -{ - async fn kv_insert + Send + Sync, V: AsRef<[u8]> + Send + Sync>( - self, - key: K, - val: V, - ) -> Result; - /// Remove an item from the generic key-value storage. `val` will be set to the value that is - /// removed from the storage if it exists. - async fn kv_remove + Send + Sync>( - self, - key: K, - ) -> Result<(Self, Option>), DbError>; - - /// Set balance of the account - #[cfg(feature = "eth")] - async fn set_balance + Send + Sync>( - self, - key: K, - balance: U256, - ) -> Result; - /// Set code of the account - #[cfg(feature = "eth")] - async fn set_code + Send + Sync, V: AsRef<[u8]> + Send + Sync>( - self, - key: K, - code: V, - ) -> Result; - /// Set nonce of the account. - #[cfg(feature = "eth")] - async fn set_nonce + Send + Sync>( - self, - key: K, - nonce: u64, - ) -> Result; - /// Set the state value indexed by `sub_key` in the account indexed by `key`. - #[cfg(feature = "eth")] - async fn set_state< - K: AsRef<[u8]> + Send + Sync, - SK: AsRef<[u8]> + Send + Sync, - V: AsRef<[u8]> + Send + Sync, - >( - self, - key: K, - sub_key: SK, - val: V, - ) -> Result; - /// Create an account. - #[cfg(feature = "eth")] - async fn create_account + Send + Sync>(self, key: K) -> Result; - /// Delete an account. - #[cfg(feature = "eth")] - async fn delete_account + Send + Sync>( - self, - key: K, - acc: &mut Option, - ) -> Result; - - /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are - /// either retained on disk or lost together during a crash. - async fn commit(self); -} - #[async_trait] pub trait Revision where diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 44f143e52e13..ff8a3a6924f0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -430,16 +430,16 @@ impl + Send + Sync> DbRev { } } -struct DbInner { - latest: DbRev, +struct DbInner { disk_requester: DiskBufferRequester, disk_thread: Option>, - data_staging: Universe>, - data_cache: Universe>, + cached_space: Universe>, + // Whether to reset the store headers when creating a new store on top of the cached space. + reset_store_headers: bool, root_hash_staging: StoreRevMut, } -impl Drop for DbInner { +impl Drop for DbInner { fn drop(&mut self) { self.disk_requester.shutdown(); self.disk_thread.take().map(JoinHandle::join); @@ -455,17 +455,16 @@ pub struct DbRevInner { } /// Firewood database handle. -pub struct Db { - inner: Arc>>, - revisions: Arc>>, +pub struct Db { + inner: Arc>, + revisions: Arc>>, payload_regn_nbit: u64, - rev_cfg: DbRevConfig, metrics: Arc, cfg: DbConfig, } // #[metered(registry = DbMetrics, visibility = pub)] -impl Db { +impl Db { /// Open a database. pub fn new>(db_path: P, cfg: &DbConfig) -> Result { // TODO: make sure all fds are released at the end @@ -633,9 +632,7 @@ impl Db { disk_requester.init_wal("wal", &db_path); let root_hash_staging = StoreRevMut::new(root_hash_cache); - let (data_staging, mut latest) = - Db::new_store(&data_cache, reset, params.payload_regn_nbit, cfg)?; - latest.flush_dirty().unwrap(); + let reset_headers = reset; let base = Universe { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), @@ -650,11 +647,10 @@ impl Db { Ok(Self { inner: Arc::new(RwLock::new(DbInner { - latest, disk_thread, disk_requester, - data_staging, - data_cache, + cached_space: data_cache, + reset_store_headers: reset_headers, root_hash_staging, })), revisions: Arc::new(Mutex::new(DbRevInner { @@ -665,7 +661,6 @@ impl Db { base_revision: Arc::new(base_revision), })), payload_regn_nbit: params.payload_regn_nbit, - rev_cfg: cfg.rev.clone(), metrics: Arc::new(DbMetrics::default()), cfg: cfg.clone(), }) @@ -674,7 +669,7 @@ impl Db { /// Create a new mutable store and an alterable revision of the DB on top. fn new_store( cached_space: &Universe>, - reset: bool, + reset_store_headers: bool, payload_regn_nbit: u64, cfg: &DbConfig, ) -> Result<(Universe>, DbRev), DbError> { @@ -689,8 +684,8 @@ impl Db { let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); let mut blob_meta_store = StoreRevMut::new(cached_space.blob.meta.clone()); - if reset { - // initialize space headers + if reset_store_headers { + // initialize store headers merkle_meta_store.write( merkle_payload_header.addr(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( @@ -892,12 +887,20 @@ impl Db { &self, data: Batch, ) -> Result, DbError> { + let mut inner = self.inner.write(); + let reset_store_headers = inner.reset_store_headers; let (store, mut rev) = Db::new_store( - &self.inner.read().data_cache, - false, + &inner.cached_space, + reset_store_headers, self.payload_regn_nbit, &self.cfg, )?; + + // Flip the reset flag after reseting the store headers. + if reset_store_headers { + inner.reset_store_headers = false; + } + data.into_iter().try_for_each(|op| -> Result<(), DbError> { match op { BatchOp::Put { key, value } => { @@ -924,8 +927,6 @@ impl Db { })?; rev.flush_dirty().unwrap(); - // TODO: remove `WriteBatch`. Currently `WriteBatch` - // and `Proposal` cannot be used at the same time. let parent = ProposalBase::View(Arc::clone(&self.revisions.lock().base_revision)); Ok(Proposal { m: Arc::clone(&self.inner), @@ -1002,7 +1003,7 @@ impl Db { &ash.0[&BLOB_META_SPACE].undo, &ash.0[&BLOB_PAYLOAD_SPACE].undo, ), - None => inner_lock.data_cache.to_mem_store_r().rewind( + None => inner_lock.cached_space.to_mem_store_r().rewind( &ash.0[&MERKLE_META_SPACE].undo, &ash.0[&MERKLE_PAYLOAD_SPACE].undo, &ash.0[&BLOB_META_SPACE].undo, @@ -1021,7 +1022,7 @@ impl Db { // Release the lock after we find the revision drop(inner_lock); - let cfg = cfg.as_ref().unwrap_or(&self.rev_cfg); + let cfg = cfg.as_ref().unwrap_or(&self.cfg.rev); Some(Revision { rev: Db::new_revision(space, self.payload_regn_nbit, 0, cfg).unwrap(), }) @@ -1029,31 +1030,22 @@ impl Db { } #[metered(registry = DbMetrics, visibility = pub)] -impl + Send + Sync, T> Db { - /// Create a write batch. - pub fn new_writebatch(&self) -> WriteBatch { - WriteBatch { - m: Arc::clone(&self.inner), - r: Arc::clone(&self.revisions), - committed: false, - } - } - +impl + Send + Sync> Db { /// Dump the Trie of the latest generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.inner.read().latest.kv_dump(w) + self.revisions.lock().base_revision.kv_dump(w) } /// Get root hash of the latest generic key-value storage. pub fn kv_root_hash(&self) -> Result { - self.inner.read().latest.kv_root_hash() + self.revisions.lock().base_revision.kv_root_hash() } /// Get a value in the kv store associated with a particular key. #[measure(HitCount)] pub fn kv_get>(&self, key: K) -> Result, DbError> { - self.inner - .read() - .latest + self.revisions + .lock() + .base_revision .kv_get(key) .ok_or(DbError::KeyNotFound) } @@ -1062,48 +1054,6 @@ impl + Send + Sync, T> Db { self.metrics.clone() } } -#[cfg(feature = "eth")] -impl + Send + Sync, T: ShaleStore + Send + Sync> Db { - /// Dump the Trie of the latest entire account model storage. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.inner.read().latest.dump(w) - } - - /// Dump the Trie of the latest state storage under an account. - pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DbError> { - self.inner.read().latest.dump_account(key, w) - } - - /// Get root hash of the latest world state of all accounts. - pub fn root_hash(&self) -> Result { - self.inner.read().latest.root_hash() - } - - /// Get the latest balance of the account. - pub fn get_balance>(&self, key: K) -> Result { - self.inner.read().latest.get_balance(key) - } - - /// Get the latest code of the account. - pub fn get_code>(&self, key: K) -> Result, DbError> { - self.inner.read().latest.get_code(key) - } - - /// Get the latest nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { - self.inner.read().latest.get_nonce(key) - } - - /// Get the latest state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DbError> { - self.inner.read().latest.get_state(key, sub_key) - } - - /// Check if the account exists in the latest world state. - pub fn exist>(&self, key: K) -> Result { - self.inner.read().latest.exist(key) - } -} /// Lock protected handle to a readable version of the DB. pub struct Revision { @@ -1136,7 +1086,7 @@ pub type Batch = Vec>; /// invalidates all other proposals that are not children of the committed one. pub struct Proposal { // State of the Db - m: Arc>>, + m: Arc>, r: Arc>>, cfg: DbConfig, @@ -1260,25 +1210,25 @@ impl Proposal { let mut rev_inner = self.m.write(); let merkle_meta_undo = rev_inner - .data_cache + .cached_space .merkle .meta .update(&merkle_meta_redo) .unwrap(); let merkle_payload_undo = rev_inner - .data_cache + .cached_space .merkle .payload .update(&merkle_payload_redo) .unwrap(); let blob_meta_undo = rev_inner - .data_cache + .cached_space .blob .meta .update(&blob_meta_redo) .unwrap(); let blob_payload_undo = rev_inner - .data_cache + .cached_space .blob .payload .update(&blob_payload_redo) @@ -1287,12 +1237,12 @@ impl Proposal { // update the rolling window of past revisions let latest_past = Universe { merkle: get_sub_universe_from_deltas( - &rev_inner.data_cache.merkle, + &rev_inner.cached_space.merkle, merkle_meta_undo, merkle_payload_undo, ), blob: get_sub_universe_from_deltas( - &rev_inner.data_cache.blob, + &rev_inner.cached_space.blob, blob_meta_undo, blob_payload_undo, ), @@ -1319,8 +1269,8 @@ impl Proposal { } let base = Universe { - merkle: get_sub_universe_from_empty_delta(&rev_inner.data_cache.merkle), - blob: get_sub_universe_from_empty_delta(&rev_inner.data_cache.blob), + merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), + blob: get_sub_universe_from_empty_delta(&rev_inner.cached_space.blob), }; let base_revision = Db::new_revision(&base, 0, self.cfg.payload_max_walk, &self.cfg.rev)?; revisions.base = base; @@ -1341,19 +1291,19 @@ impl Proposal { rev_inner.disk_requester.write( vec![ BufferWrite { - space_id: rev_inner.data_staging.merkle.payload.id(), + space_id: self.store.merkle.payload.id(), delta: merkle_payload_redo, }, BufferWrite { - space_id: rev_inner.data_staging.merkle.meta.id(), + space_id: self.store.merkle.meta.id(), delta: merkle_meta_redo, }, BufferWrite { - space_id: rev_inner.data_staging.blob.payload.id(), + space_id: self.store.blob.payload.id(), delta: blob_payload_redo, }, BufferWrite { - space_id: rev_inner.data_staging.blob.meta.id(), + space_id: self.store.blob.meta.id(), delta: blob_meta_redo, }, BufferWrite { @@ -1383,351 +1333,14 @@ impl + Send + Sync, T: ShaleStore + Send + Sync> Propo } } -/// An atomic batch of changes made to the DB. Each operation on a [WriteBatch] will move itself -/// because when an error occurs, the write batch will be automatically aborted so that the DB -/// remains clean. -pub struct WriteBatch { - m: Arc>>, - r: Arc>>, - committed: bool, -} - -impl + Send + Sync, T> WriteBatch { - /// Insert an item to the generic key-value storage. - pub fn kv_insert>(self, key: K, val: Vec) -> Result { - let mut rev = self.m.write(); - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.latest.borrow_split(); - #[cfg(not(feature = "eth"))] - let (header, merkle) = rev.latest.borrow_split(); - merkle - .insert(key, val, header.kv_root) - .map_err(DbError::Merkle)?; - drop(rev); - Ok(self) - } - - /// Remove an item from the generic key-value storage. `val` will be set to the value that is - /// removed from the storage if it exists. - pub fn kv_remove>(self, key: K) -> Result<(Self, Option>), DbError> { - let mut rev = self.m.write(); - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.latest.borrow_split(); - #[cfg(not(feature = "eth"))] - let (header, merkle) = rev.latest.borrow_split(); - let old_value = merkle - .remove(key, header.kv_root) - .map_err(DbError::Merkle)?; - drop(rev); - Ok((self, old_value)) - } - - /// Persist all changes to the DB. The atomicity of the [WriteBatch] guarantees all changes are - /// either retained on disk or lost together during a crash. - pub fn commit(mut self) { - let mut rev_inner = self.m.write(); - - #[cfg(feature = "eth")] - rev_inner.latest.root_hash().ok(); - - let kv_root_hash = rev_inner.latest.kv_root_hash().ok(); - let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); - - // clear the staging layer and apply changes to the CachedSpace - rev_inner.latest.flush_dirty().unwrap(); - let (merkle_payload_redo, merkle_payload_wal) = - rev_inner.data_staging.merkle.payload.delta(); - rev_inner.data_staging.merkle.payload.reset_deltas(); - let (merkle_meta_redo, merkle_meta_wal) = rev_inner.data_staging.merkle.meta.delta(); - rev_inner.data_staging.merkle.meta.reset_deltas(); - let (blob_payload_redo, blob_payload_wal) = rev_inner.data_staging.blob.payload.delta(); - rev_inner.data_staging.blob.payload.reset_deltas(); - let (blob_meta_redo, blob_meta_wal) = rev_inner.data_staging.blob.meta.delta(); - rev_inner.data_staging.blob.meta.reset_deltas(); - let merkle_meta_undo = rev_inner - .data_cache - .merkle - .meta - .update(&merkle_meta_redo) - .unwrap(); - let merkle_payload_undo = rev_inner - .data_cache - .merkle - .payload - .update(&merkle_payload_redo) - .unwrap(); - let blob_meta_undo = rev_inner - .data_cache - .blob - .meta - .update(&blob_meta_redo) - .unwrap(); - let blob_payload_undo = rev_inner - .data_cache - .blob - .payload - .update(&blob_payload_redo) - .unwrap(); - - // update the rolling window of past revisions - let latest_past = Universe { - merkle: get_sub_universe_from_deltas( - &rev_inner.data_cache.merkle, - merkle_meta_undo, - merkle_payload_undo, - ), - blob: get_sub_universe_from_deltas( - &rev_inner.data_cache.blob, - blob_meta_undo, - blob_payload_undo, - ), - }; - - let mut revisions = self.r.lock(); - let max_revisions = revisions.max_revisions; - if let Some(rev) = revisions.inner.front_mut() { - rev.merkle - .meta - .set_base_space(latest_past.merkle.meta.inner().clone()); - rev.merkle - .payload - .set_base_space(latest_past.merkle.payload.inner().clone()); - rev.blob - .meta - .set_base_space(latest_past.blob.meta.inner().clone()); - rev.blob - .payload - .set_base_space(latest_past.blob.payload.inner().clone()); - } - revisions.inner.push_front(latest_past); - while revisions.inner.len() > max_revisions { - revisions.inner.pop_back(); - } - - let base = Universe { - merkle: get_sub_universe_from_empty_delta(&rev_inner.data_cache.merkle), - blob: get_sub_universe_from_empty_delta(&rev_inner.data_cache.blob), - }; - revisions.base = base; - - // update the rolling window of root hashes - revisions.root_hashes.push_front(kv_root_hash.clone()); - if revisions.root_hashes.len() > max_revisions { - revisions - .root_hashes - .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); - } - - rev_inner.root_hash_staging.write(0, &kv_root_hash.0); - let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); - - self.committed = true; - - // schedule writes to the disk - rev_inner.disk_requester.write( - vec![ - BufferWrite { - space_id: rev_inner.data_staging.merkle.payload.id(), - delta: merkle_payload_redo, - }, - BufferWrite { - space_id: rev_inner.data_staging.merkle.meta.id(), - delta: merkle_meta_redo, - }, - BufferWrite { - space_id: rev_inner.data_staging.blob.payload.id(), - delta: blob_payload_redo, - }, - BufferWrite { - space_id: rev_inner.data_staging.blob.meta.id(), - delta: blob_meta_redo, - }, - BufferWrite { - space_id: rev_inner.root_hash_staging.id(), - delta: root_hash_redo, - }, - ], - AshRecord( - [ - (MERKLE_META_SPACE, merkle_meta_wal), - (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), - (BLOB_META_SPACE, blob_meta_wal), - (BLOB_PAYLOAD_SPACE, blob_payload_wal), - (ROOT_HASH_SPACE, root_hash_wal), - ] - .into(), - ), - ); - } -} - -#[cfg(feature = "eth")] -impl + Send + Sync> WriteBatch { - fn change_account( - &mut self, - key: &[u8], - modify: impl FnOnce(&mut Account, &mut BlobStash) -> Result<(), DbError>, - ) -> Result<(), DbError> { - let mut rev = self.m.write(); - let (header, merkle, blob) = rev.latest.borrow_split(); - match merkle.get_mut(key, header.acc_root) { - Ok(Some(mut bytes)) => { - let mut ret = Ok(()); - bytes - .write(|b| { - let mut acc = Account::deserialize(b); - ret = modify(&mut acc, blob); - if ret.is_err() { - return; - } - *b = acc.serialize(); - }) - .map_err(DbError::Merkle)?; - ret?; - } - Ok(None) => { - let mut acc = Account::default(); - modify(&mut acc, blob)?; - merkle - .insert(key, acc.serialize(), header.acc_root) - .map_err(DbError::Merkle)?; - } - Err(e) => return Err(DbError::Merkle(e)), - } - Ok(()) - } - - /// Set balance of the account. - pub fn set_balance(mut self, key: &[u8], balance: U256) -> Result { - self.change_account(key, |acc, _| { - acc.balance = balance; - Ok(()) - })?; - Ok(self) - } - - /// Set code of the account. - pub fn set_code(mut self, key: &[u8], code: &[u8]) -> Result { - use sha3::Digest; - self.change_account(key, |acc, blob_stash| { - if !acc.code.is_null() { - blob_stash.free_blob(acc.code).map_err(DbError::Blob)?; - } - acc.set_code( - TrieHash(sha3::Keccak256::digest(code).into()), - blob_stash - .new_blob(Blob::Code(code.to_vec())) - .map_err(DbError::Blob)? - .as_ptr(), - ); - Ok(()) - })?; - Ok(self) - } - - /// Set nonce of the account. - pub fn set_nonce(mut self, key: &[u8], nonce: u64) -> Result { - self.change_account(key, |acc, _| { - acc.nonce = nonce; - Ok(()) - })?; - Ok(self) - } - - /// Set the state value indexed by `sub_key` in the account indexed by `key`. - pub fn set_state(self, key: &[u8], sub_key: &[u8], val: Vec) -> Result { - let mut rev = self.m.write(); - let (header, merkle, _) = rev.latest.borrow_split(); - let mut acc = match merkle.get(key, header.acc_root) { - Ok(Some(r)) => Account::deserialize(&r), - Ok(None) => Account::default(), - Err(e) => return Err(DbError::Merkle(e)), - }; - if acc.root.is_null() { - Merkle::init_root(&mut acc.root, merkle.get_store()).map_err(DbError::Merkle)?; - } - merkle - .insert(sub_key, val, acc.root) - .map_err(DbError::Merkle)?; - acc.root_hash = merkle - .root_hash::(acc.root) - .map_err(DbError::Merkle)?; - merkle - .insert(key, acc.serialize(), header.acc_root) - .map_err(DbError::Merkle)?; - drop(rev); - Ok(self) - } - - /// Create an account. - pub fn create_account(self, key: &[u8]) -> Result { - let mut rev = self.m.write(); - let (header, merkle, _) = rev.latest.borrow_split(); - let old_balance = match merkle.get_mut(key, header.acc_root) { - Ok(Some(bytes)) => Account::deserialize(&bytes.get()).balance, - Ok(None) => U256::zero(), - Err(e) => return Err(DbError::Merkle(e)), - }; - let acc = Account { - balance: old_balance, - ..Default::default() - }; - merkle - .insert(key, acc.serialize(), header.acc_root) - .map_err(DbError::Merkle)?; - - drop(rev); - Ok(self) - } - - /// Delete an account. - pub fn delete_account(self, key: &[u8], acc: &mut Option) -> Result { - let mut rev = self.m.write(); - let (header, merkle, blob_stash) = rev.latest.borrow_split(); - let mut a = match merkle.remove(key, header.acc_root) { - Ok(Some(bytes)) => Account::deserialize(&bytes), - Ok(None) => { - *acc = None; - drop(rev); - return Ok(self); - } - Err(e) => return Err(DbError::Merkle(e)), - }; - if !a.root.is_null() { - merkle.remove_tree(a.root).map_err(DbError::Merkle)?; - a.root = ObjPtr::null(); - } - if !a.code.is_null() { - blob_stash.free_blob(a.code).map_err(DbError::Blob)?; - a.code = ObjPtr::null(); - } - *acc = Some(a); - drop(rev); - Ok(self) - } -} - -impl Drop for WriteBatch { - fn drop(&mut self) { - if !self.committed { - // drop the staging changes - self.m.read().data_staging.merkle.payload.reset_deltas(); - self.m.read().data_staging.merkle.meta.reset_deltas(); - self.m.read().data_staging.blob.payload.reset_deltas(); - self.m.read().data_staging.blob.meta.reset_deltas(); - self.m.read().root_hash_staging.reset_deltas(); - } - } -} - impl Drop for Proposal { fn drop(&mut self) { if !*self.committed.lock() { // drop the staging changes - self.m.read().data_staging.merkle.payload.reset_deltas(); - self.m.read().data_staging.merkle.meta.reset_deltas(); - self.m.read().data_staging.blob.payload.reset_deltas(); - self.m.read().data_staging.blob.meta.reset_deltas(); + self.store.merkle.payload.reset_deltas(); + self.store.merkle.meta.reset_deltas(); + self.store.blob.payload.reset_deltas(); + self.store.blob.meta.reset_deltas(); self.m.read().root_hash_staging.reset_deltas(); } } diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 3a0b48d550c0..f39f9c4d3486 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -118,7 +118,7 @@ //! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit //! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the //! dirty pages in some `CachedStore` instantiation (see `storage::StoreRevMut`). When a -//! [`db::WriteBatch`] commits, both the recorded interval writes and the aggregated in-memory +//! [`db::Proposal`] commits, both the recorded interval writes and the aggregated in-memory //! dirty pages induced by this write batch are taken out from the linear space. Although they are //! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, //! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index 2b160ca272bb..fcddca26e3d9 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -14,14 +14,13 @@ use tokio::sync::{mpsc, oneshot}; use crate::api::Revision; use crate::db::DbRevConfig; use crate::{ - api, db::{DbConfig, DbError}, merkle::TrieHash, }; use async_trait::async_trait; use super::server::FirewoodService; -use super::{BatchHandle, BatchRequest, Request, RevRequest, RevisionHandle}; +use super::{Request, RevRequest, RevisionHandle}; /// A `Connection` represents a connection to the thread running firewood /// The type specified is how you want to refer to your key values; this is @@ -62,185 +61,6 @@ impl Connection { } } -#[async_trait] -impl api::WriteBatch for BatchHandle { - async fn kv_insert + Send + Sync, V: AsRef<[u8]> + Send + Sync>( - self, - key: K, - val: V, - ) -> Result { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::KvInsert { - handle: self.id, - key: key.as_ref().to_vec(), - val: val.as_ref().to_vec(), - respond_to: send, - })) - .await; - return match recv.await { - Ok(_) => Ok(self), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - async fn kv_remove + Send + Sync>( - self, - key: K, - ) -> Result<(Self, Option>), DbError> { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::KvRemove { - handle: self.id, - key: key.as_ref().to_vec(), - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(v)) => Ok((self, v)), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - #[cfg(feature = "eth")] - async fn set_balance + Send + Sync>( - self, - key: K, - balance: primitive_types::U256, - ) -> Result { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::SetBalance { - handle: self.id, - key: key.as_ref().to_vec(), - balance, - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(_)) => Ok(self), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - #[cfg(feature = "eth")] - async fn set_code(self, key: K, code: V) -> Result - where - K: AsRef<[u8]> + Send + Sync, - V: AsRef<[u8]> + Send + Sync, - { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::SetCode { - handle: self.id, - key: key.as_ref().to_vec(), - code: code.as_ref().to_vec(), - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(_)) => Ok(self), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - #[cfg(feature = "eth")] - async fn set_nonce + Send + Sync>( - self, - key: K, - nonce: u64, - ) -> Result { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::SetNonce { - handle: self.id, - key: key.as_ref().to_vec(), - nonce, - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(_)) => Ok(self), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - #[cfg(feature = "eth")] - async fn set_state(self, key: K, sub_key: SK, state: S) -> Result - where - K: AsRef<[u8]> + Send + Sync, - SK: AsRef<[u8]> + Send + Sync, - S: AsRef<[u8]> + Send + Sync, - { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::SetState { - handle: self.id, - key: key.as_ref().to_vec(), - sub_key: sub_key.as_ref().to_vec(), - state: state.as_ref().to_vec(), - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(_)) => Ok(self), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - #[cfg(feature = "eth")] - async fn create_account + Send + Sync>(self, key: K) -> Result { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::CreateAccount { - handle: self.id, - key: key.as_ref().to_vec(), - respond_to: send, - })) - .await; - return match recv.await { - Ok(Ok(_)) => Ok(self), - Ok(Err(e)) => Err(e), - Err(_e) => Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } - - #[cfg(feature = "eth")] - async fn delete_account + Send + Sync>( - self, - _key: K, - _acc: &mut Option, - ) -> Result { - todo!() - } - - async fn commit(self) { - let (send, recv) = oneshot::channel(); - let _ = self - .sender - .send(Request::BatchRequest(BatchRequest::Commit { - handle: self.id, - respond_to: send, - })) - .await; - return match recv.await { - Ok(_) => (), - Err(_e) => (), // Err(DbError::InvalidParams), // TODO: need a special error for comm failures - }; - } -} - impl super::RevisionHandle { pub async fn close(self) { let _ = self @@ -357,27 +177,10 @@ impl Revision for super::RevisionHandle { } #[async_trait] -impl crate::api::Db for Connection +impl crate::api::Db for Connection where tokio::sync::mpsc::Sender: From>, { - async fn new_writebatch(&self) -> BatchHandle { - let (send, recv) = oneshot::channel(); - let msg = Request::NewBatch { respond_to: send }; - self.sender - .as_ref() - .unwrap() - .send(msg) - .await - .expect("channel failed"); - let id = recv.await; - let id = id.unwrap(); - BatchHandle { - sender: self.sender.as_ref().unwrap().clone(), - id, - } - } - async fn get_revision( &self, root_hash: TrieHash, @@ -403,56 +206,4 @@ where } } -#[cfg(test)] -mod test { - use crate::{api::Db, api::WriteBatch, db::WalConfig}; - use std::path::PathBuf; - - use super::*; - #[tokio::test] - async fn sender_api() { - let key = b"key"; - // test using a subdirectory of CARGO_TARGET_DIR which is - // cleaned up on `cargo clean` - let tmpdb = [ - &std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".to_string()), - "sender_api_test_db", - ]; - let tmpdb = tmpdb.into_iter().collect::(); - let conn = Connection::new(tmpdb, db_config()); - let batch = conn.new_writebatch().await; - let batch = batch.kv_insert(key, b"val").await.unwrap(); - #[cfg(feature = "eth")] - { - let batch = batch.set_code(key, b"code").await.unwrap(); - let batch = batch.set_nonce(key, 42).await.unwrap(); - let batch = batch.set_state(key, b"subkey", b"state").await.unwrap(); - let batch = batch.create_account(key).await.unwrap(); - } - let (batch, oldvalue) = batch.kv_remove(key).await.unwrap(); - assert_eq!(oldvalue, Some(b"val".to_vec())); - batch.commit().await; - let batch = conn.new_writebatch().await; - let batch = batch.kv_insert(b"k2", b"val").await.unwrap(); - batch.commit().await; - - // TODO: disable the assertion now, add a way to expose the current root hash either - // in the writebatch or the connection - // assert_ne!( - // conn.get_revision(Hash([0; 32]), None) - // .await - // .unwrap() - // .root_hash() - // .await - // .unwrap(), - // Hash([0; 32]) - // ); - } - - fn db_config() -> DbConfig { - DbConfig::builder() - .wal(WalConfig::builder().max_revisions(10).build()) - .truncate(true) - .build() - } -} +// TODO: add a meaningful test with `Proposal` and `Revisions`. diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index 70305e2df434..0db87a86ab8c 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -14,12 +14,6 @@ mod server; pub type BatchId = u32; pub type RevId = u32; -#[derive(Debug)] -pub struct BatchHandle { - sender: mpsc::Sender, - id: u32, -} - #[derive(Debug)] pub struct RevisionHandle { sender: mpsc::Sender, @@ -29,16 +23,12 @@ pub struct RevisionHandle { /// Client side request object #[derive(Debug)] pub enum Request { - NewBatch { - respond_to: oneshot::Sender, - }, NewRevision { root_hash: TrieHash, cfg: Option, respond_to: oneshot::Sender>, }, - BatchRequest(BatchRequest), RevRequest(RevRequest), } @@ -46,60 +36,6 @@ type OwnedKey = Vec; #[allow(dead_code)] type OwnedVal = Vec; -#[derive(Debug)] -pub enum BatchRequest { - KvRemove { - handle: BatchId, - key: OwnedKey, - respond_to: oneshot::Sender>, DbError>>, - }, - KvInsert { - handle: BatchId, - key: OwnedKey, - val: OwnedKey, - respond_to: oneshot::Sender>, - }, - Commit { - handle: BatchId, - respond_to: oneshot::Sender>, - }, - #[cfg(feature = "eth")] - SetBalance { - handle: BatchId, - key: OwnedKey, - balance: primitive_types::U256, - respond_to: oneshot::Sender>, - }, - #[cfg(feature = "eth")] - SetCode { - handle: BatchId, - key: OwnedKey, - code: OwnedVal, - respond_to: oneshot::Sender>, - }, - #[cfg(feature = "eth")] - SetNonce { - handle: BatchId, - key: OwnedKey, - nonce: u64, - respond_to: oneshot::Sender>, - }, - #[cfg(feature = "eth")] - SetState { - handle: BatchId, - key: OwnedKey, - sub_key: OwnedVal, - state: OwnedVal, - respond_to: oneshot::Sender>, - }, - #[cfg(feature = "eth")] - CreateAccount { - handle: BatchId, - key: OwnedKey, - respond_to: oneshot::Sender>, - }, -} - #[derive(Debug)] pub enum RevRequest { Get { diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 515a71370b64..278cc753016f 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -10,17 +10,7 @@ use tokio::sync::mpsc::Receiver; use crate::db::{Db, DbConfig, DbError}; -use super::{BatchId, BatchRequest, Request, RevId}; - -macro_rules! get_batch { - ($active_batch: expr, $handle: ident, $lastid: ident, $respond_to: expr) => {{ - if $handle != $lastid.load(Ordering::Relaxed) - 1 || $active_batch.is_none() { - let _ = $respond_to.send(Err(DbError::InvalidParams)); - continue; - } - $active_batch.take().unwrap() - }}; -} +use super::{Request, RevId}; macro_rules! get_rev { ($rev: ident, $handle: ident, $out: expr) => { @@ -39,7 +29,6 @@ pub struct FirewoodService {} impl FirewoodService { pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DbConfig) -> Self { let db = Db::new(owned_path, &cfg).unwrap(); - let mut active_batch = None; let mut revs = HashMap::new(); let lastid = AtomicU32::new(0); loop { @@ -48,11 +37,6 @@ impl FirewoodService { None => break, }; match msg { - Request::NewBatch { respond_to } => { - let id: BatchId = lastid.fetch_add(1, Ordering::Relaxed); - active_batch = Some(db.new_writebatch()); - let _ = respond_to.send(id); - } Request::NewRevision { root_hash, cfg, @@ -99,129 +83,6 @@ impl FirewoodService { revs.remove(&handle); } }, - Request::BatchRequest(req) => match req { - BatchRequest::Commit { handle, respond_to } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - batch.commit(); - let _ = respond_to.send(Ok(())); - } - BatchRequest::KvInsert { - handle, - key, - val, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.kv_insert(key, val) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - BatchRequest::KvRemove { - handle, - key, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.kv_remove(key) { - Ok(v) => { - active_batch = Some(v.0); - Ok(v.1) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - #[cfg(feature = "eth")] - BatchRequest::SetBalance { - handle, - key, - balance, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.set_balance(key.as_ref(), balance) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - #[cfg(feature = "eth")] - BatchRequest::SetCode { - handle, - key, - code, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.set_code(key.as_ref(), code.as_ref()) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - #[cfg(feature = "eth")] - BatchRequest::SetNonce { - handle, - key, - nonce, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.set_nonce(key.as_ref(), nonce) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - #[cfg(feature = "eth")] - BatchRequest::SetState { - handle, - key, - sub_key, - state, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.set_state(key.as_ref(), sub_key.as_ref(), state) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - #[cfg(feature = "eth")] - BatchRequest::CreateAccount { - handle, - key, - respond_to, - } => { - let batch = get_batch!(active_batch, handle, lastid, respond_to); - let resp = match batch.create_account(key.as_ref()) { - Ok(v) => { - active_batch = Some(v); - Ok(()) - } - Err(e) => Err(e), - }; - respond_to.send(resp).unwrap(); - } - }, } } FirewoodService {} diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index a979cc97b15e..d7399b2760fd 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,7 +1,7 @@ use firewood::{ db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, merkle::{Node, TrieHash}, - storage::{StoreRevMut, StoreRevShared}, + storage::StoreRevShared, }; use firewood_shale::compact::CompactSpace; use std::{ @@ -16,15 +16,15 @@ use std::{ macro_rules! kv_dump { ($e: ident) => {{ let mut s = Vec::new(); + $e.kv_root_hash().unwrap(); $e.kv_dump(&mut s).unwrap(); String::from_utf8(s).unwrap() }}; } -type Store = CompactSpace; type SharedStore = CompactSpace; -struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); +struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); impl<'a, P: AsRef + ?Sized> Db<'a, P> { fn new(path: &'a P, cfg: &DbConfig) -> Result { @@ -106,14 +106,19 @@ fn test_revisions() { let mut hashes: VecDeque = VecDeque::new(); for _ in 0..10 { { - let mut wb = db.new_writebatch(); + let mut batch = Vec::new(); let m = rng.borrow_mut().gen_range(1..20); for _ in 0..m { let key = keygen(); let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - wb = wb.kv_insert(key, val.to_vec()).unwrap(); + let write = BatchOp::Put { + key, + value: val.to_vec(), + }; + batch.push(write); } - wb.commit(); + let proposal = db.new_proposal(batch).unwrap(); + proposal.commit().unwrap(); } while dumped.len() > 10 { dumped.pop_back(); @@ -171,8 +176,6 @@ fn create_db_issue_proof() { let db = Db::new("test_db_proof", &cfg.truncate(true).build()).unwrap(); - let mut wb = db.new_writebatch(); - let items = vec![ ("d", "verb"), ("do", "verb"), @@ -180,20 +183,30 @@ fn create_db_issue_proof() { ("e", "coin"), ]; + let mut batch = Vec::new(); for (k, v) in items { - wb = wb.kv_insert(k.as_bytes(), v.as_bytes().to_vec()).unwrap(); + let write = BatchOp::Put { + key: k.as_bytes(), + value: v.as_bytes().to_vec(), + }; + batch.push(write); } - wb.commit(); + let proposal = db.new_proposal(batch).unwrap(); + proposal.commit().unwrap(); + let root_hash = db.kv_root_hash().unwrap(); - // Add second commit due to API restrictions - let mut wb = db.new_writebatch(); + // Add second commit + let mut batch = Vec::new(); for (k, v) in Vec::from([("x", "two")]).iter() { - wb = wb - .kv_insert(k.to_string().as_bytes(), v.as_bytes().to_vec()) - .unwrap(); + let write = BatchOp::Put { + key: k.to_string().as_bytes().to_vec(), + value: v.as_bytes().to_vec(), + }; + batch.push(write); } - wb.commit(); + let proposal = db.new_proposal(batch).unwrap(); + proposal.commit().unwrap(); let rev = db.get_revision(&root_hash, None).unwrap(); let key = "doe".as_bytes(); @@ -218,7 +231,7 @@ fn create_db_issue_proof() { } impl + ?Sized> Deref for Db<'_, P> { - type Target = PersistedDb; + type Target = PersistedDb; fn deref(&self) -> &Self::Target { &self.0 @@ -226,7 +239,7 @@ impl + ?Sized> Deref for Db<'_, P> { } impl + ?Sized> DerefMut for Db<'_, P> { - fn deref_mut(&mut self) -> &mut PersistedDb { + fn deref_mut(&mut self) -> &mut PersistedDb { &mut self.0 } } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 1cbc76499f8b..fa8f16d03767 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -3,7 +3,7 @@ use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; use log; #[derive(Debug, Args)] @@ -30,9 +30,11 @@ pub fn run(opts: &Options) -> Result<()> { .wal(WalConfig::builder().max_revisions(10).build()); let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; - db.new_writebatch() - .kv_remove(&opts.key) - .map_err(Error::msg)?; + + let batch = vec![BatchOp::Delete { key: &opts.key }]; + let proposal = db.new_proposal(batch).map_err(Error::msg)?; + proposal.commit().map_err(Error::msg)?; + println!("key {} deleted successfully", opts.key); Ok(()) } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 28677411deb3..815685e948c0 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Error, Result}; use clap::Args; -use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; use log; #[derive(Debug, Args)] @@ -38,11 +38,13 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - let insertion_batch = db - .new_writebatch() - .kv_insert(opts.key.clone(), opts.value.bytes().collect()) - .map_err(Error::msg)?; - insertion_batch.commit(); + let batch = vec![BatchOp::Put { + key: &opts.key, + value: opts.value.bytes().collect(), + }]; + let proposal = db.new_proposal(batch).map_err(Error::msg)?; + proposal.commit().map_err(Error::msg)?; + println!("{}", opts.key); Ok(()) } From 9d57c4018ae523494772f216e91b105eb928e9c3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 11 Aug 2023 00:10:25 +0000 Subject: [PATCH 0232/1053] Remove eth feature --- firewood/Cargo.toml | 2 - firewood/examples/dump.rs | 66 -------------- firewood/examples/simple.rs | 83 ----------------- firewood/src/account.rs | 172 ------------------------------------ firewood/src/api.rs | 23 ----- firewood/src/db.rs | 139 ----------------------------- firewood/src/lib.rs | 2 - firewood/src/proof.rs | 9 -- firewood/src/sender.rs | 30 ------- 9 files changed, 526 deletions(-) delete mode 100644 firewood/examples/dump.rs delete mode 100644 firewood/examples/simple.rs delete mode 100644 firewood/src/account.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 079643b8f7cc..23e37a3a6b41 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -53,8 +53,6 @@ test-case = "3.1.0" [features] # proof API proof = [] -# eth API -eth = [] [[bench]] name = "hashops" diff --git a/firewood/examples/dump.rs b/firewood/examples/dump.rs deleted file mode 100644 index 784ef3808771..000000000000 --- a/firewood/examples/dump.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#[cfg(not(feature = "eth"))] -fn main() { - println!("To run this example, you must enable to eth feature."); - println!("For more information on features, see:"); - println!("https://doc.rust-lang.org/cargo/reference/features.html"); -} - -#[cfg(feature = "eth")] -use clap::{command, Arg, ArgMatches}; -#[cfg(feature = "eth")] -use firewood::db::{Db, DbConfig, DbError, WalConfig}; - -/// cargo run --example dump benchmark_db/ -#[cfg(feature = "eth")] -fn main() { - let matches = command!() - .arg( - Arg::new("INPUT") - .help("db path name") - .required(false) - .index(1), - ) - .get_matches(); - let path = get_db_path(matches); - let db = Db::new( - path.unwrap().as_str(), - &DbConfig::builder().truncate(false).build(), - ) - .unwrap(); - let mut stdout = std::io::stdout(); - - println!("== Account Model =="); - db.dump(&mut stdout).unwrap(); - - println!("== Generic KV =="); - db.kv_dump(&mut stdout).unwrap(); -} - -/// Returns the provided INPUT db path if one is provided. -/// Otherwise, instantiate a DB called simple_db and return the path. -#[cfg(feature = "eth")] -fn get_db_path(matches: ArgMatches) -> Result { - if let Some(m) = matches.get_one::("INPUT") { - return Ok(m.to_string()); - } - - // Build and provide a new db path - let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new("simple_db", &cfg.truncate(true).build()).unwrap(); - db.new_writebatch() - .set_balance(b"ted", 10.into()) - .unwrap() - .set_code(b"ted", b"smart contract byte code here!") - .unwrap() - .set_nonce(b"ted", 10086) - .unwrap() - .set_state(b"ted", b"x", b"1".to_vec()) - .unwrap() - .set_state(b"ted", b"y", b"2".to_vec()) - .unwrap() - .commit(); - Ok("simple_db".to_string()) -} diff --git a/firewood/examples/simple.rs b/firewood/examples/simple.rs deleted file mode 100644 index bf1d73d720b4..000000000000 --- a/firewood/examples/simple.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#[cfg(feature = "eth")] -use firewood::db::{Db, DbConfig, WalConfig}; - -#[cfg(feature = "eth")] -fn print_states(db: &Db) { - println!("======"); - for account in ["ted", "alice"] { - let addr = account.as_bytes(); - println!("{account}.balance = {}", db.get_balance(addr).unwrap()); - println!("{account}.nonce = {}", db.get_nonce(addr).unwrap()); - println!( - "{}.code = {}", - account, - std::str::from_utf8(&db.get_code(addr).unwrap()).unwrap() - ); - for state_key in ["x", "y", "z"] { - println!( - "{}.state.{} = {}", - account, - state_key, - std::str::from_utf8(&db.get_state(addr, state_key.as_bytes()).unwrap()).unwrap() - ); - } - } -} - -#[cfg(not(feature = "eth"))] -fn main() { - println!("To run this example, you must enable to eth feature."); - println!("For more information on features, see:"); - println!("https://doc.rust-lang.org/cargo/reference/features.html"); -} - -/// cargo run --example simple -#[cfg(feature = "eth")] -fn main() { - let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - { - let db = Db::new("simple_db", &cfg.clone().truncate(true).build()).unwrap(); - db.new_writebatch() - .set_balance(b"ted", 10.into()) - .unwrap() - .set_code(b"ted", b"smart contract byte code here!") - .unwrap() - .set_nonce(b"ted", 10086) - .unwrap() - .set_state(b"ted", b"x", b"1".to_vec()) - .unwrap() - .set_state(b"ted", b"y", b"2".to_vec()) - .unwrap() - .commit(); - } - { - let db = Db::new("simple_db", &cfg.clone().truncate(false).build()).unwrap(); - print_states(&db); - db.new_writebatch() - .set_state(b"alice", b"z", b"999".to_vec()) - .unwrap() - .commit(); - print_states(&db); - } - { - let db = Db::new("simple_db", &cfg.truncate(false).build()).unwrap(); - print_states(&db); - let mut stdout = std::io::stdout(); - let mut acc = None; - db.dump(&mut stdout).unwrap(); - db.dump_account(b"ted", &mut stdout).unwrap(); - db.new_writebatch() - .delete_account(b"ted", &mut acc) - .unwrap(); - assert!(acc.is_some()); - print_states(&db); - db.dump_account(b"ted", &mut stdout).unwrap(); - db.new_writebatch() - .delete_account(b"nobody", &mut acc) - .unwrap(); - assert!(acc.is_none()); - } -} diff --git a/firewood/src/account.rs b/firewood/src/account.rs deleted file mode 100644 index 84c4633e0407..000000000000 --- a/firewood/src/account.rs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::fmt; -use std::io::{Cursor, Write}; - -use crate::merkle::{Hash, Node, ValueTransformer}; -use primitive_types::U256; -use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; - -pub struct Account { - pub nonce: u64, - pub balance: U256, - pub root: ObjPtr, - pub code: ObjPtr, - pub root_hash: Hash, - pub code_hash: Hash, -} - -impl Account { - pub fn empty_code() -> &'static Hash { - static V: OnceCell = OnceCell::new(); - V.get_or_init(|| { - Hash( - hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") - .unwrap() - .try_into() - .unwrap(), - ) - }) - } - - pub fn serialize(&self) -> Vec { - let mut buff = Vec::new(); - buff.extend(self.nonce.to_le_bytes()); - buff.resize(40, 0); - self.balance.to_big_endian(&mut buff[8..40]); - buff.extend((self.root.addr()).to_le_bytes()); - buff.extend((self.code.addr()).to_le_bytes()); - buff.extend(self.root_hash.0); - buff.extend(self.code_hash.0); - buff - } - - pub fn deserialize(raw: &[u8]) -> Self { - let nonce = u64::from_le_bytes(raw[..8].try_into().unwrap()); - let balance = U256::from_big_endian(&raw[8..40]); - let root = u64::from_le_bytes(raw[40..48].try_into().unwrap()); - let code = u64::from_le_bytes(raw[48..56].try_into().unwrap()); - let root_hash = Hash(raw[56..88].try_into().unwrap()); - let code_hash = Hash(raw[88..].try_into().unwrap()); - - Self { - nonce, - balance, - root: ObjPtr::new_from_addr(root), - code: ObjPtr::new_from_addr(code), - root_hash, - code_hash, - } - } - - pub fn set_code(&mut self, code_hash: Hash, code: ObjPtr) { - self.code_hash = code_hash; - self.code = code; - } -} - -pub struct AccountRlp; - -impl ValueTransformer for AccountRlp { - fn transform(raw: &[u8]) -> Vec { - let acc = Account::deserialize(raw); - let mut stream = rlp::RlpStream::new_list(4); - stream.append(&acc.nonce); - stream.append(&acc.balance); - stream.append(&&acc.root_hash[..]); - stream.append(&&acc.code_hash[..]); - stream.out().into() - } -} - -impl Default for Account { - fn default() -> Self { - Account { - nonce: 0, - balance: U256::zero(), - root: ObjPtr::null(), - code: ObjPtr::null(), - root_hash: crate::merkle::Merkle::empty_root().clone(), - code_hash: Self::empty_code().clone(), - } - } -} - -pub enum Blob { - Code(Vec), -} - -impl Storable for Blob { - // currently there is only one variant of Blob: Code - fn hydrate(addr: u64, mem: &T) -> Result { - let raw = mem - .get_view(addr, 4) - .ok_or(ShaleError::LinearCachedStoreError)?; - let len = u32::from_le_bytes(raw.as_deref()[..].try_into().unwrap()) as u64; - let bytes = mem - .get_view(addr + 4, len) - .ok_or(ShaleError::LinearCachedStoreError)?; - Ok(Self::Code(bytes.as_deref())) - } - - fn dehydrated_len(&self) -> u64 { - match self { - Self::Code(code) => 4 + code.len() as u64, - } - } - - fn dehydrate(&self, to: &mut [u8]) { - match self { - Self::Code(code) => { - let mut cur = Cursor::new(to); - cur.write_all(&(code.len() as u32).to_le_bytes()).unwrap(); - cur.write_all(code).unwrap(); - } - } - } -} - -#[derive(Debug)] -pub enum BlobError { - Shale(ShaleError), -} - -pub struct BlobStash { - store: Box, -} - -impl + Send + Sync> BlobStash { - pub fn new(store: Box) -> Self { - Self { store } - } - - pub fn get_blob(&self, ptr: ObjPtr) -> Result, BlobError> { - self.store.get_item(ptr).map_err(BlobError::Shale) - } - - pub fn new_blob(&self, item: Blob) -> Result, BlobError> { - self.store.put_item(item, 0).map_err(BlobError::Shale) - } - - pub fn free_blob(&mut self, ptr: ObjPtr) -> Result<(), BlobError> { - self.store.free_item(ptr).map_err(BlobError::Shale) - } - - pub fn flush_dirty(&self) -> Option<()> { - self.store.flush_dirty() - } -} - -impl fmt::Debug for Account { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "", - self.balance, - self.nonce, - hex::encode(*self.code_hash), - hex::encode(*self.root_hash) - ) - } -} diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 294887fa52c9..80eed118e204 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -3,11 +3,6 @@ use std::io::Write; -#[cfg(feature = "eth")] -use crate::account::Account; -#[cfg(feature = "eth")] -use primitive_types::U256; - use crate::db::{DbError, DbRevConfig}; use crate::merkle::TrieHash; #[cfg(feature = "proof")] @@ -45,22 +40,4 @@ where keys: Vec, values: Vec, ); - #[cfg(feature = "eth")] - async fn get_balance + Send + Sync>(&self, key: K) -> Result; - #[cfg(feature = "eth")] - async fn get_code + Send + Sync>(&self, key: K) -> Result, DbError>; - #[cfg(feature = "eth")] - async fn get_nonce + Send + Sync>(&self, key: K) -> Result; - #[cfg(feature = "eth")] - async fn get_state + Send + Sync>( - &self, - key: K, - sub_key: K, - ) -> Result, DbError>; - #[cfg(feature = "eth")] - async fn dump_account + Send + Sync>( - &self, - key: K, - writer: W, - ) -> Result<(), DbError>; } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ff8a3a6924f0..e4ebcd0cf18a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,8 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#[cfg(feature = "eth")] -use crate::account::{Account, AccountRlp, Blob, BlobStash}; pub use crate::config::{DbConfig, DbRevConfig}; pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use crate::storage::{ @@ -18,8 +16,6 @@ use crate::{ use bytemuck::{cast_slice, AnyBitPattern}; use metered::{metered, HitCount}; use parking_lot::{Mutex, RwLock}; -#[cfg(feature = "eth")] -use primitive_types::U256; use shale::compact::CompactSpace; use shale::ShaleStore; use shale::{ @@ -52,8 +48,6 @@ type SharedStore = CompactSpace; pub enum DbError { InvalidParams, Merkle(MerkleError), - #[cfg(feature = "eth")] - Blob(crate::account::BlobError), System(nix::Error), KeyNotFound, CreateError, @@ -67,8 +61,6 @@ impl fmt::Display for DbError { match self { DbError::InvalidParams => write!(f, "invalid parameters provided"), DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), - #[cfg(feature = "eth")] - DbError::Blob(e) => write!(f, "storage error: {e:?}"), DbError::System(e) => write!(f, "system error: {e:?}"), DbError::KeyNotFound => write!(f, "not found"), DbError::CreateError => write!(f, "database create error"), @@ -282,24 +274,15 @@ impl Universe> { pub struct DbRev { header: shale::Obj, merkle: Merkle, - #[cfg(feature = "eth")] - blob: BlobStash, } impl + Send + Sync> DbRev { fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); self.merkle.flush_dirty()?; - #[cfg(feature = "eth")] - self.blob.flush_dirty()?; Some(()) } - #[cfg(feature = "eth")] - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle, &mut BlobStash) { - (&mut self.header, &mut self.merkle, &mut self.blob) - } - #[cfg(not(feature = "eth"))] fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { (&mut self.header, &mut self.merkle) } @@ -356,80 +339,6 @@ impl + Send + Sync> DbRev { } } -#[cfg(feature = "eth")] -impl + Send + Sync> DbRev { - /// Get nonce of the account. - pub fn get_nonce>(&self, key: K) -> Result { - Ok(self.get_account(key)?.nonce) - } - - /// Get the state value indexed by `sub_key` in the account indexed by `key`. - pub fn get_state>(&self, key: K, sub_key: K) -> Result, DbError> { - let root = self.get_account(key)?.root; - if root.is_null() { - return Ok(Vec::new()); - } - Ok(match self.merkle.get(sub_key, root) { - Ok(Some(v)) => v.to_vec(), - Ok(None) => Vec::new(), - Err(e) => return Err(DbError::Merkle(e)), - }) - } - - /// Get root hash of the world state of all accounts. - pub fn root_hash(&self) -> Result { - self.merkle - .root_hash::(self.header.acc_root) - .map_err(DbError::Merkle) - } - - /// Dump the Trie of the entire account model storage. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.merkle - .dump(self.header.acc_root, w) - .map_err(DbError::Merkle) - } - - fn get_account>(&self, key: K) -> Result { - Ok(match self.merkle.get(key, self.header.acc_root) { - Ok(Some(bytes)) => Account::deserialize(&bytes), - Ok(None) => Account::default(), - Err(e) => return Err(DbError::Merkle(e)), - }) - } - - /// Dump the Trie of the state storage under an account. - pub fn dump_account>(&self, key: K, w: &mut dyn Write) -> Result<(), DbError> { - let acc = match self.merkle.get(key, self.header.acc_root) { - Ok(Some(bytes)) => Account::deserialize(&bytes), - Ok(None) => Account::default(), - Err(e) => return Err(DbError::Merkle(e)), - }; - writeln!(w, "{acc:?}").unwrap(); - if !acc.root.is_null() { - self.merkle.dump(acc.root, w).map_err(DbError::Merkle)?; - } - Ok(()) - } - - /// Get balance of the account. - pub fn get_balance>(&self, key: K) -> Result { - Ok(self.get_account(key)?.balance) - } - - /// Get code of the account. - pub fn get_code>(&self, key: K) -> Result, DbError> { - let code = self.get_account(key)?.code; - if code.is_null() { - return Ok(Vec::new()); - } - let b = self.blob.get_blob(code).map_err(DbError::Blob)?; - Ok(match &**b { - Blob::Code(code) => code.clone(), - }) - } -} - struct DbInner { disk_requester: DiskBufferRequester, disk_thread: Option>, @@ -749,13 +658,6 @@ impl Db { shale::compact::CompactHeader::MSIZE, )?; - #[cfg(feature = "eth")] - let blob_payload_header_ref = StoredView::ptr_to_obj( - &store.blob.meta, - blob_payload_header, - shale::compact::CompactHeader::MSIZE, - )?; - let merkle_space = shale::compact::CompactSpace::new( Arc::new(store.merkle.meta.clone()), Arc::new(store.merkle.payload.clone()), @@ -766,17 +668,6 @@ impl Db { ) .unwrap(); - #[cfg(feature = "eth")] - let blob_space = shale::compact::CompactSpace::new( - Arc::new(store.blob.meta.clone()), - Arc::new(store.blob.payload.clone()), - blob_payload_header_ref, - shale::ObjCache::new(cfg.blob_ncached_objs), - payload_max_walk, - payload_regn_nbit, - ) - .unwrap(); - if db_header_ref.acc_root.is_null() { let mut err = Ok(()); // create the sentinel node @@ -794,8 +685,6 @@ impl Db { Ok(DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), - #[cfg(feature = "eth")] - blob: BlobStash::new(Box::new(blob_space)), }) } @@ -849,17 +738,6 @@ impl Db { ) .unwrap(); - #[cfg(feature = "eth")] - let blob_space = shale::compact::CompactSpace::new( - Arc::new(store.blob.meta.clone()), - Arc::new(store.blob.payload.clone()), - blob_payload_header_ref, - shale::ObjCache::new(cfg.blob_ncached_objs), - payload_max_walk, - payload_regn_nbit, - ) - .unwrap(); - if db_header_ref.acc_root.is_null() { let mut err = Ok(()); // create the sentinel node @@ -877,8 +755,6 @@ impl Db { Ok(DbRev { header: db_header_ref, merkle: Merkle::new(Box::new(merkle_space)), - #[cfg(feature = "eth")] - blob: BlobStash::new(Box::new(blob_space)), }) } @@ -904,9 +780,6 @@ impl Db { data.into_iter().try_for_each(|op| -> Result<(), DbError> { match op { BatchOp::Put { key, value } => { - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.borrow_split(); - #[cfg(not(feature = "eth"))] let (header, merkle) = rev.borrow_split(); merkle .insert(key, value, header.kv_root) @@ -914,9 +787,6 @@ impl Db { Ok(()) } BatchOp::Delete { key } => { - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.borrow_split(); - #[cfg(not(feature = "eth"))] let (header, merkle) = rev.borrow_split(); merkle .remove(key, header.kv_root) @@ -1124,9 +994,6 @@ impl Proposal { data.into_iter().try_for_each(|op| -> Result<(), DbError> { match op { BatchOp::Put { key, value } => { - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.borrow_split(); - #[cfg(not(feature = "eth"))] let (header, merkle) = rev.borrow_split(); merkle .insert(key, value, header.kv_root) @@ -1134,9 +1001,6 @@ impl Proposal { Ok(()) } BatchOp::Delete { key } => { - #[cfg(feature = "eth")] - let (header, merkle, _) = rev.borrow_split(); - #[cfg(not(feature = "eth"))] let (header, merkle) = rev.borrow_split(); merkle .remove(key, header.kv_root) @@ -1196,9 +1060,6 @@ impl Proposal { } }; - #[cfg(feature = "eth")] - self.rev.root_hash().ok(); - let kv_root_hash = self.rev.kv_root_hash().ok(); let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index f39f9c4d3486..02dc372a996a 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -194,8 +194,6 @@ //! No change is required for other historical ghost space instances. Finally, we can phase out //! some very old ghost space to keep the size of the rolling window invariant. //! -#[cfg(feature = "eth")] -pub(crate) mod account; pub mod db; pub(crate) mod file; pub mod merkle; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 1d53ddb53050..5c9e773e9730 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,9 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#[cfg(feature = "eth")] -use crate::account::BlobError; - use crate::db::DbError; use crate::merkle::*; use crate::merkle_util::*; @@ -43,8 +40,6 @@ pub enum ProofError { EmptyRange, ForkLeft, ForkRight, - #[cfg(feature = "eth")] - BlobStoreError(BlobError), SystemError(Errno), Shale(ShaleError), InvalidRootHash, @@ -65,8 +60,6 @@ impl From for ProofError { match d { DbError::InvalidParams => ProofError::InvalidProof, DbError::Merkle(e) => ProofError::InvalidNode(e), - #[cfg(feature = "eth")] - DbError::Blob(e) => ProofError::BlobStoreError(e), DbError::System(e) => ProofError::SystemError(e), DbError::KeyNotFound => ProofError::InvalidEdgeKeys, DbError::CreateError => ProofError::NoSuchNode, @@ -105,8 +98,6 @@ impl fmt::Display for ProofError { ProofError::EmptyRange => write!(f, "empty range"), ProofError::ForkLeft => write!(f, "fork left"), ProofError::ForkRight => write!(f, "fork right"), - #[cfg(feature = "eth")] - ProofError::BlobStoreError(e) => write!(f, "blob store error: {e:?}"), ProofError::SystemError(e) => write!(f, "system error: {e:?}"), ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), ProofError::Shale(e) => write!(f, "shale error: {e:?}"), diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs index 73af6ee7d43c..7f7efac21d5f 100644 --- a/firewood/src/sender.rs +++ b/firewood/src/sender.rs @@ -26,26 +26,6 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } - #[cfg(feature = "eth")] - fn get_account(&self, key: K) -> Result { - todo!() - } - - #[cfg(feature = "eth")] - fn dump_account(&self, key: K, writer: W) -> Result<(), crate::db::DbError> { - todo!() - } - - #[cfg(feature = "eth")] - fn get_balance(&self, key: K) -> Result { - todo!() - } - - #[cfg(feature = "eth")] - fn get_code(&self, key: K) -> Result, crate::db::DbError> { - todo!() - } - #[cfg(feature = "proof")] fn prove(&self, key: K) -> Result { todo!() @@ -63,16 +43,6 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } - #[cfg(feature = "eth")] - fn get_nonce(&self, key: K) -> Result { - todo!() - } - - #[cfg(feature = "eth")] - fn get_state(&self, key: K, sub_key: K) -> Result, crate::db::DbError> { - todo!() - } - fn exist(&self, key: K) -> Result { todo!() } From 9c28d3c3743fec83b58016717a6ba5edca1b4fd2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 11 Aug 2023 16:07:03 +0000 Subject: [PATCH 0233/1053] Fix ci scripts --- .github/workflows/ci.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 031edd41b50c..e74a657c6abf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -143,10 +143,6 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ needs.build.outputs.cache-key }} - - name: Run simple example - run: RUST_BACKTRACE=1 cargo run --example simple - - name: Run dump example - run: RUST_BACKTRACE=1 cargo run --example dump # benchmarks were not being done in --release mode, we can enable this again later # - name: Run benchmark example # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 From 89204f1c2862b2bfeb46eb493b0aca2d93a4bba5 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 11 Aug 2023 14:14:19 -0400 Subject: [PATCH 0234/1053] Remove new_revision_arc method (#187) --- firewood/src/db.rs | 262 +++++++++++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 106 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e4ebcd0cf18a..2569d3d2c22c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,31 +1,33 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -pub use crate::config::{DbConfig, DbRevConfig}; -pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; -use crate::storage::{ - AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, - StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, +pub use crate::{ + config::{DbConfig, DbRevConfig}, + storage::{buffer::DiskBufferConfig, WalConfig}, }; use crate::{ file, merkle::{IdTrans, Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, proof::{Proof, ProofError}, - storage::buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, + storage::{ + buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, + AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, + StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, + }, }; use bytemuck::{cast_slice, AnyBitPattern}; use metered::{metered, HitCount}; use parking_lot::{Mutex, RwLock}; -use shale::compact::CompactSpace; -use shale::ShaleStore; use shale::{ - compact::CompactSpaceHeader, CachedStore, ObjPtr, ShaleError, SpaceId, Storable, StoredView, + compact::{CompactSpace, CompactSpaceHeader}, + CachedStore, Obj, ObjPtr, ShaleError, ShaleStore, SpaceId, Storable, StoredView, }; use std::{ collections::VecDeque, error::Error, fmt, io::{Cursor, Write}, + mem::size_of, path::Path, sync::Arc, thread::JoinHandle, @@ -374,6 +376,8 @@ pub struct Db { // #[metered(registry = DbMetrics, visibility = pub)] impl Db { + const PARAM_SIZE: u64 = size_of::() as u64; + /// Open a database. pub fn new>(db_path: P, cfg: &DbConfig) -> Result { // TODO: make sure all fds are released at the end @@ -424,7 +428,7 @@ impl Db { let header_bytes = unsafe { std::slice::from_raw_parts( &header as *const DbParams as *const u8, - std::mem::size_of::(), + size_of::(), ) .to_vec() }; @@ -433,7 +437,7 @@ impl Db { } // read DbParams - let mut header_bytes = [0; std::mem::size_of::()]; + let mut header_bytes = [0; size_of::()]; nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; drop(file0); let params: DbParams = cast_slice(&header_bytes)[0]; @@ -547,8 +551,24 @@ impl Db { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), blob: get_sub_universe_from_empty_delta(&data_cache.blob), }; + + let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; + + let merkle_payload_header_ref = + Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; + + let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + let base_revision = Db::new_revision( - &base, + header_refs, + (base.merkle.meta.clone(), base.merkle.payload.clone()), + (base.blob.meta.clone(), base.blob.payload.clone()), params.payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -582,7 +602,7 @@ impl Db { payload_regn_nbit: u64, cfg: &DbConfig, ) -> Result<(Universe>, DbRev), DbError> { - let mut offset = std::mem::size_of::() as u64; + let mut offset = Db::PARAM_SIZE; let db_header: ObjPtr = ObjPtr::new_from_addr(offset); offset += DbHeader::MSIZE; let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); @@ -626,111 +646,78 @@ impl Db { ), }; - let mut rev = - Db::new_revision_arc(&store, payload_regn_nbit, cfg.payload_max_walk, &cfg.rev)?; - rev.flush_dirty().unwrap(); + let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; - Ok((store, rev)) - } + let merkle_payload_header_ref = Db::get_payload_header_ref( + store.merkle.meta.as_ref(), + Db::PARAM_SIZE + DbHeader::MSIZE, + )?; - fn new_revision( - store: &Universe, - payload_regn_nbit: u64, - payload_max_walk: u64, - cfg: &DbRevConfig, - ) -> Result>, DbError> { - // Set up the storage layout - let mut offset = std::mem::size_of::() as u64; - // DbHeader starts after DbParams in merkle meta space - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DbHeader::MSIZE; - // Merkle CompactHeader starts after DbHeader in merkle meta space - let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); + let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; - let mut db_header_ref = - StoredView::ptr_to_obj(&store.merkle.meta, db_header, DbHeader::MSIZE).unwrap(); + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); - let merkle_payload_header_ref = StoredView::ptr_to_obj( - &store.merkle.meta, - merkle_payload_header, - shale::compact::CompactHeader::MSIZE, + let mut rev: DbRev> = Db::new_revision( + header_refs, + (store.merkle.meta.clone(), store.merkle.payload.clone()), + (store.blob.meta.clone(), store.blob.payload.clone()), + payload_regn_nbit, + cfg.payload_max_walk, + &cfg.rev, )?; + rev.flush_dirty().unwrap(); - let merkle_space = shale::compact::CompactSpace::new( - Arc::new(store.merkle.meta.clone()), - Arc::new(store.merkle.payload.clone()), - merkle_payload_header_ref, - shale::ObjCache::new(cfg.merkle_ncached_objs), - payload_max_walk, - payload_regn_nbit, - ) - .unwrap(); + Ok((store, rev)) + } - if db_header_ref.acc_root.is_null() { - let mut err = Ok(()); - // create the sentinel node - db_header_ref - .write(|r| { - err = (|| { - Merkle::::init_root(&mut r.acc_root, &merkle_space)?; - Merkle::::init_root(&mut r.kv_root, &merkle_space) - })(); - }) - .unwrap(); - err.map_err(DbError::Merkle)? - } + fn get_payload_header_ref( + meta_ref: &K, + header_offset: u64, + ) -> Result, DbError> { + let payload_header = ObjPtr::::new_from_addr(header_offset); + StoredView::ptr_to_obj( + meta_ref, + payload_header, + shale::compact::CompactHeader::MSIZE, + ) + .map_err(Into::into) + } - Ok(DbRev { - header: db_header_ref, - merkle: Merkle::new(Box::new(merkle_space)), - }) + fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { + let db_header = ObjPtr::::new_from_addr(Db::PARAM_SIZE); + StoredView::ptr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) } - // TODO: can we only have one version of `new_revision`? - fn new_revision_arc( - store: &Universe>, + fn new_revision>>( + header_refs: ( + Obj, + Obj, + Obj, + ), + merkle: (T, T), + _blob: (T, T), payload_regn_nbit: u64, payload_max_walk: u64, cfg: &DbRevConfig, ) -> Result>, DbError> { - // Set up the storage layout - let mut offset = std::mem::size_of::() as u64; - // DbHeader starts after DbParams in merkle meta space - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DbHeader::MSIZE; - // Merkle CompactHeader starts after DbHeader in merkle meta space - let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); - // Blob CompactSpaceHeader starts right in blob meta space - let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + // TODO: This should be a compile time check + const DB_OFFSET: u64 = Db::PARAM_SIZE; + let merkle_offset = DB_OFFSET + DbHeader::MSIZE; + assert!(merkle_offset + CompactSpaceHeader::MSIZE <= SPACE_RESERVED); - let (mut db_header_ref, merkle_payload_header_ref, _blob_payload_header_ref) = { - let merkle_meta_ref = store.merkle.meta.as_ref(); - let blob_meta_ref = store.blob.meta.as_ref(); + let mut db_header_ref = header_refs.0; + let merkle_payload_header_ref = header_refs.1; - ( - StoredView::ptr_to_obj(merkle_meta_ref, db_header, DbHeader::MSIZE).unwrap(), - StoredView::ptr_to_obj( - merkle_meta_ref, - merkle_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - StoredView::ptr_to_obj( - blob_meta_ref, - blob_payload_header, - shale::compact::CompactHeader::MSIZE, - ) - .unwrap(), - ) - }; + let merkle_meta = merkle.0.into(); + let merkle_payload = merkle.1.into(); let merkle_space = shale::compact::CompactSpace::new( - store.merkle.meta.clone(), - store.merkle.payload.clone(), + merkle_meta, + merkle_payload, merkle_payload_header_ref, shale::ObjCache::new(cfg.merkle_ncached_objs), payload_max_walk, @@ -893,9 +880,33 @@ impl Db { drop(inner_lock); let cfg = cfg.as_ref().unwrap_or(&self.cfg.rev); - Some(Revision { - rev: Db::new_revision(space, self.payload_regn_nbit, 0, cfg).unwrap(), - }) + + let db_header_ref = Db::get_db_header_ref(&space.merkle.meta).unwrap(); + + let merkle_payload_header_ref = + Db::get_payload_header_ref(&space.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) + .unwrap(); + + let blob_payload_header_ref = Db::get_payload_header_ref(&space.blob.meta, 0).unwrap(); + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + + Revision { + rev: Db::new_revision( + header_refs, + (space.merkle.meta.clone(), space.merkle.payload.clone()), + (space.blob.meta.clone(), space.blob.payload.clone()), + self.payload_regn_nbit, + 0, + cfg, + ) + .unwrap(), + } + .into() } } @@ -985,8 +996,26 @@ impl Proposal { let m = Arc::clone(&self.m); let r = Arc::clone(&self.r); let cfg = self.cfg.clone(); - let mut rev = Db::new_revision_arc( - &store, + + let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; + + let merkle_payload_header_ref = Db::get_payload_header_ref( + store.merkle.meta.as_ref(), + Db::PARAM_SIZE + DbHeader::MSIZE, + )?; + + let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + + let mut rev = Db::new_revision( + header_refs, + (store.merkle.meta.clone(), store.merkle.payload.clone()), + (store.blob.meta.clone(), store.blob.payload.clone()), cfg.payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -1133,7 +1162,28 @@ impl Proposal { merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), blob: get_sub_universe_from_empty_delta(&rev_inner.cached_space.blob), }; - let base_revision = Db::new_revision(&base, 0, self.cfg.payload_max_walk, &self.cfg.rev)?; + + let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; + + let merkle_payload_header_ref = + Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; + + let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + + let base_revision = Db::new_revision( + header_refs, + (base.merkle.meta.clone(), base.merkle.payload.clone()), + (base.blob.meta.clone(), base.blob.payload.clone()), + 0, + self.cfg.payload_max_walk, + &self.cfg.rev, + )?; revisions.base = base; revisions.base_revision = Arc::new(base_revision); From e2572477c687076b09a2662d233f067f6e7bbccc Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 15 Aug 2023 12:24:26 -0700 Subject: [PATCH 0235/1053] Add #[derive(Debug)] in a few missing places (#193) --- firewood/src/db.rs | 8 ++++++-- firewood/src/merkle.rs | 1 + shale/src/compact.rs | 3 +++ shale/src/lib.rs | 6 ++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 2569d3d2c22c..c1a9d1cb8c57 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -102,7 +102,7 @@ struct DbParams { root_hash_file_nbit: u64, } -#[derive(Clone)] +#[derive(Clone, Debug)] /// Necessary linear space instances bundled for a `CompactSpace`. struct SubUniverse { meta: T, @@ -221,7 +221,7 @@ impl Storable for DbHeader { } } -#[derive(Clone)] +#[derive(Clone, Debug)] /// Necessary linear space instances bundled for the state of the entire DB. struct Universe { merkle: SubUniverse, @@ -273,6 +273,7 @@ impl Universe> { } /// Some readable version of the DB. +#[derive(Debug)] pub struct DbRev { header: shale::Obj, merkle: Merkle, @@ -341,6 +342,7 @@ impl + Send + Sync> DbRev { } } +#[derive(Debug)] struct DbInner { disk_requester: DiskBufferRequester, disk_thread: Option>, @@ -357,6 +359,7 @@ impl Drop for DbInner { } } +#[derive(Debug)] pub struct DbRevInner { inner: VecDeque>, root_hashes: VecDeque, @@ -366,6 +369,7 @@ pub struct DbRevInner { } /// Firewood database handle. +#[derive(Debug)] pub struct Db { inner: Arc>, revisions: Arc>>, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index a4673aab3988..09d1338ff6c7 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -842,6 +842,7 @@ macro_rules! write_node { }; } +#[derive(Debug)] pub struct Merkle { store: Box, } diff --git a/shale/src/compact.rs b/shale/src/compact.rs index f37a3f871026..408cb3e08b6f 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -133,6 +133,7 @@ pub struct CompactSpaceHeader { alloc_addr: ObjPtr, } +#[derive(Debug)] struct CompactSpaceHeaderSliced { meta_space_tail: Obj, compact_space_tail: Obj, @@ -293,6 +294,7 @@ impl std::ops::DerefMut for U64Field { } } +#[derive(Debug)] struct CompactSpaceInner { meta_space: Arc, compact_space: Arc, @@ -562,6 +564,7 @@ impl CompactSpaceInner { } } +#[derive(Debug)] pub struct CompactSpace { inner: RwLock>, } diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 524ea269a2de..adb8c081abd1 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -516,14 +516,16 @@ impl Storable for ObjPtr { } } -pub struct ObjCacheInner { +#[derive(Debug)] +pub struct ObjCacheInner { cached: lru::LruCache, Obj>, pinned: HashMap, bool>, dirty: HashSet>, } /// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. -pub struct ObjCache(Arc>>); +#[derive(Debug)] +pub struct ObjCache(Arc>>); impl ObjCache { pub fn new(capacity: usize) -> Self { From 450f382c0b183d6174775b13d8ca44a50112a92d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 15 Aug 2023 13:13:15 -0700 Subject: [PATCH 0236/1053] Remove get_revision configuration option (#194) --- firewood/examples/rev.rs | 2 +- firewood/src/api.rs | 4 ++-- firewood/src/db.rs | 10 ++-------- firewood/src/service/client.rs | 9 ++------- firewood/src/service/mod.rs | 6 +----- firewood/src/service/server.rs | 3 +-- firewood/tests/db.rs | 6 +++--- 7 files changed, 12 insertions(+), 28 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index c5fc7cfa9415..0faac609bc2f 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -162,7 +162,7 @@ impl RevisionTracker { fn get_revision(&self, index: usize) -> Revision { self.db - .get_revision(&self.hashes[index], None) + .get_revision(&self.hashes[index]) .unwrap_or_else(|| panic!("revision-{index} should exist")) } } diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 80eed118e204..353a65952be8 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -3,7 +3,7 @@ use std::io::Write; -use crate::db::{DbError, DbRevConfig}; +use crate::db::DbError; use crate::merkle::TrieHash; #[cfg(feature = "proof")] use crate::{merkle::MerkleError, proof::Proof}; @@ -14,7 +14,7 @@ pub type Nonce = u64; #[async_trait] pub trait Db { - async fn get_revision(&self, root_hash: TrieHash, cfg: Option) -> Option; + async fn get_revision(&self, root_hash: TrieHash) -> Option; } #[async_trait] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c1a9d1cb8c57..f587f3632505 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -806,11 +806,7 @@ impl Db { /// /// If no revision with matching root hash found, returns None. // #[measure([HitCount])] - pub fn get_revision( - &self, - root_hash: &TrieHash, - cfg: Option, - ) -> Option> { + pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { let mut revisions = self.revisions.lock(); let inner_lock = self.inner.read(); @@ -883,8 +879,6 @@ impl Db { // Release the lock after we find the revision drop(inner_lock); - let cfg = cfg.as_ref().unwrap_or(&self.cfg.rev); - let db_header_ref = Db::get_db_header_ref(&space.merkle.meta).unwrap(); let merkle_payload_header_ref = @@ -906,7 +900,7 @@ impl Db { (space.blob.meta.clone(), space.blob.payload.clone()), self.payload_regn_nbit, 0, - cfg, + &self.cfg.rev, ) .unwrap(), } diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index fcddca26e3d9..c3f44dd9cca1 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -12,7 +12,7 @@ use std::{path::Path, thread}; use tokio::sync::{mpsc, oneshot}; use crate::api::Revision; -use crate::db::DbRevConfig; + use crate::{ db::{DbConfig, DbError}, merkle::TrieHash, @@ -181,15 +181,10 @@ impl crate::api::Db for Connection where tokio::sync::mpsc::Sender: From>, { - async fn get_revision( - &self, - root_hash: TrieHash, - cfg: Option, - ) -> Option { + async fn get_revision(&self, root_hash: TrieHash) -> Option { let (send, recv) = oneshot::channel(); let msg = Request::NewRevision { root_hash, - cfg, respond_to: send, }; self.sender diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index 0db87a86ab8c..47ac8ff347e9 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -3,10 +3,7 @@ use tokio::sync::{mpsc, oneshot}; -use crate::{ - db::{DbError, DbRevConfig}, - merkle::TrieHash, -}; +use crate::{db::DbError, merkle::TrieHash}; mod client; mod server; @@ -25,7 +22,6 @@ pub struct RevisionHandle { pub enum Request { NewRevision { root_hash: TrieHash, - cfg: Option, respond_to: oneshot::Sender>, }, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 278cc753016f..779efce3ee43 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -39,11 +39,10 @@ impl FirewoodService { match msg { Request::NewRevision { root_hash, - cfg, respond_to, } => { let id: RevId = lastid.fetch_add(1, Ordering::Relaxed); - let msg = match db.get_revision(&root_hash, cfg) { + let msg = match db.get_revision(&root_hash) { Some(rev) => { revs.insert(id, rev); Some(id) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index d7399b2760fd..dd512128dfa6 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -130,7 +130,7 @@ fn test_revisions() { dumped .iter() .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(&hash, None).unwrap())) + .map(|(data, hash)| (data, db.get_revision(&hash).unwrap())) .map(|(data, rev)| (data, kv_dump!(rev))) .for_each(|(b, a)| { if &a != b { @@ -144,7 +144,7 @@ fn test_revisions() { dumped .iter() .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(&hash, None).unwrap())) + .map(|(data, hash)| (data, db.get_revision(&hash).unwrap())) .map(|(data, rev)| (data, kv_dump!(rev))) .for_each(|(previous_dump, after_reopen_dump)| { if &after_reopen_dump != previous_dump { @@ -208,7 +208,7 @@ fn create_db_issue_proof() { let proposal = db.new_proposal(batch).unwrap(); proposal.commit().unwrap(); - let rev = db.get_revision(&root_hash, None).unwrap(); + let rev = db.get_revision(&root_hash).unwrap(); let key = "doe".as_bytes(); let root_hash = rev.kv_root_hash(); From 62b2b0d731d719d3a70d94a1408a89e06bc4e667 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Aug 2023 11:36:21 -0700 Subject: [PATCH 0237/1053] Emptydb is a test, so only compile when testing (#195) --- firewood/src/v2/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 0a05e5d24d76..6e23b8fe11fb 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -1,4 +1,6 @@ pub mod api; pub mod db; -pub mod emptydb; pub mod propose; + +#[cfg(test)] +pub mod emptydb; From 424354194b1529984e0fcf3f8f98421d40135f52 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 17 Aug 2023 09:15:32 -0700 Subject: [PATCH 0238/1053] Cleanup: Remove generics on Db (#196) --- firewood/examples/rev.rs | 4 ++-- firewood/src/db.rs | 8 ++++---- firewood/tests/db.rs | 13 +++++-------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 0faac609bc2f..19107d8300a2 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -107,11 +107,11 @@ fn main() { struct RevisionTracker { hashes: VecDeque, - db: Db, + db: Db, } impl RevisionTracker { - fn new(db: Db) -> Self { + fn new(db: Db) -> Self { Self { hashes: VecDeque::new(), db, diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f587f3632505..619da819c78a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -370,16 +370,16 @@ pub struct DbRevInner { /// Firewood database handle. #[derive(Debug)] -pub struct Db { +pub struct Db { inner: Arc>, - revisions: Arc>>, + revisions: Arc>>, payload_regn_nbit: u64, metrics: Arc, cfg: DbConfig, } // #[metered(registry = DbMetrics, visibility = pub)] -impl Db { +impl Db { const PARAM_SIZE: u64 = size_of::() as u64; /// Open a database. @@ -909,7 +909,7 @@ impl Db { } #[metered(registry = DbMetrics, visibility = pub)] -impl + Send + Sync> Db { +impl Db { /// Dump the Trie of the latest generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.revisions.lock().base_revision.kv_dump(w) diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index dd512128dfa6..e83805bbefc6 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,9 +1,8 @@ use firewood::{ db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, - merkle::{Node, TrieHash}, - storage::StoreRevShared, + merkle::TrieHash, }; -use firewood_shale::compact::CompactSpace; + use std::{ collections::VecDeque, fs::remove_dir_all, @@ -22,9 +21,7 @@ macro_rules! kv_dump { }}; } -type SharedStore = CompactSpace; - -struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); +struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); impl<'a, P: AsRef + ?Sized> Db<'a, P> { fn new(path: &'a P, cfg: &DbConfig) -> Result { @@ -231,7 +228,7 @@ fn create_db_issue_proof() { } impl + ?Sized> Deref for Db<'_, P> { - type Target = PersistedDb; + type Target = PersistedDb; fn deref(&self) -> &Self::Target { &self.0 @@ -239,7 +236,7 @@ impl + ?Sized> Deref for Db<'_, P> { } impl + ?Sized> DerefMut for Db<'_, P> { - fn deref_mut(&mut self) -> &mut PersistedDb { + fn deref_mut(&mut self) -> &mut PersistedDb { &mut self.0 } } From 383eb4d3eab9173f358ae6e883ddd6fb4d63953b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 17 Aug 2023 09:48:59 -0700 Subject: [PATCH 0239/1053] Cleanup: remove generics for Proposal (#197) --- firewood/examples/rev.rs | 5 ++--- firewood/src/db.rs | 32 +++++++++++++------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 19107d8300a2..70ce7cdfb2ab 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -7,11 +7,10 @@ use firewood::{ db::{BatchOp, Db, DbConfig, Proposal, Revision, WalConfig}, merkle::{Node, TrieHash}, proof::Proof, - storage::{StoreRevMut, StoreRevShared}, + storage::StoreRevShared, }; use firewood_shale::compact::CompactSpace; -type Store = CompactSpace; type SharedStore = CompactSpace; /// cargo run --example rev @@ -142,7 +141,7 @@ impl RevisionTracker { self.hashes.push_front(hash); } - fn commit_proposal(&mut self, proposal: Proposal) { + fn commit_proposal(&mut self, proposal: Proposal) { proposal.commit().unwrap(); let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 619da819c78a..0cf1e70af913 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -750,10 +750,7 @@ impl Db { } /// Create a proposal. - pub fn new_proposal>( - &self, - data: Batch, - ) -> Result, DbError> { + pub fn new_proposal>(&self, data: Batch) -> Result { let mut inner = self.inner.write(); let reset_store_headers = inner.reset_store_headers; let (store, mut rev) = Db::new_store( @@ -963,32 +960,29 @@ pub type Batch = Vec>; /// latest committed revision at the same time. [Proposal] is immutable meaning /// the internal batch cannot be altered after creation. Committing a proposal /// invalidates all other proposals that are not children of the committed one. -pub struct Proposal { +pub struct Proposal { // State of the Db m: Arc>, - r: Arc>>, + r: Arc>>, cfg: DbConfig, // State of the proposal - rev: DbRev, + rev: DbRev, store: Universe>, committed: Arc>, - parent: ProposalBase, + parent: ProposalBase, } -pub enum ProposalBase { - Proposal(Arc>), - View(Arc>), +pub enum ProposalBase { + Proposal(Arc), + View(Arc>), } -impl Proposal { +impl Proposal { // Propose a new proposal from this proposal. The new proposal will be // the child of it. - pub fn propose>( - self: Arc, - data: Batch, - ) -> Result, DbError> { + pub fn propose>(self: Arc, data: Batch) -> Result { let store = self.store.new_from_other(); let m = Arc::clone(&self.m); @@ -1236,13 +1230,13 @@ impl Proposal { } } -impl + Send + Sync, T: ShaleStore + Send + Sync> Proposal { - pub fn get_revision(&self) -> &DbRev { +impl Proposal { + pub fn get_revision(&self) -> &DbRev { &self.rev } } -impl Drop for Proposal { +impl Drop for Proposal { fn drop(&mut self) { if !*self.committed.lock() { // drop the staging changes From d3e4582f4681ccbb48cbc32544b9408ae32d48b9 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 17 Aug 2023 09:58:49 -0700 Subject: [PATCH 0240/1053] Add all dependendencies to dependabot config (#198) --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c707b8e846b..80cf2aec8ccf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,5 @@ updates: time: "05:00" timezone: America/Los_Angeles open-pull-requests-limit: 10 + allow: + dependency-type: all From 17acc48d54251fc982b4f94c275729f73e447682 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 17 Aug 2023 10:57:10 -0700 Subject: [PATCH 0241/1053] dependabot: Use quotes around all (#200) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 80cf2aec8ccf..f3876f5ba928 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,4 +13,4 @@ updates: timezone: America/Los_Angeles open-pull-requests-limit: 10 allow: - dependency-type: all + dependency-type: "all" From bc999b3552f88b4eddb337901ea7f611a8954e6b Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 18 Aug 2023 13:28:12 -0400 Subject: [PATCH 0242/1053] Fix dependabot (#203) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f3876f5ba928..cc945b270cea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,4 +13,4 @@ updates: timezone: America/Los_Angeles open-pull-requests-limit: 10 allow: - dependency-type: "all" + - dependency-type: all From 1e7ce75364cc6cdb5592167fe8582188e0964c51 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 18 Aug 2023 16:13:26 -0400 Subject: [PATCH 0243/1053] Simplify PlainMem initialization (#201) --- shale/src/cached.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shale/src/cached.rs b/shale/src/cached.rs index cba6f7117644..cdf3453228bf 100644 --- a/shale/src/cached.rs +++ b/shale/src/cached.rs @@ -17,9 +17,8 @@ pub struct PlainMem { impl PlainMem { pub fn new(size: u64, id: SpaceId) -> Self { - let mut space: Vec = Vec::new(); - space.resize(size as usize, 0); - let space = Arc::new(RwLock::new(space)); + // TODO: this could cause problems on a 32-bit system + let space = Arc::new(RwLock::new(vec![0; size as usize])); Self { space, id } } } From 5d57d07b9721ca0dc5926ca533d9de12425ae547 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Aug 2023 10:10:51 -0700 Subject: [PATCH 0244/1053] Rename ObjPtr to DiskAddress (#204) --- firewood/benches/hashops.rs | 9 +- firewood/src/db.rs | 58 ++++---- firewood/src/merkle.rs | 153 ++++++++++--------- firewood/src/merkle_util.rs | 30 ++-- firewood/src/proof.rs | 20 +-- firewood/src/storage/mod.rs | 68 +++++---- shale/benches/shale-bench.rs | 15 +- shale/src/cached.rs | 16 +- shale/src/compact.rs | 279 +++++++++++++++++------------------ shale/src/disk_address.rs | 184 +++++++++++++++++++++++ shale/src/lib.rs | 145 ++++-------------- 11 files changed, 549 insertions(+), 428 deletions(-) create mode 100644 shale/src/disk_address.rs diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 53eddbbf2509..954b1ea1eaad 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -7,9 +7,8 @@ use firewood::{ storage::StoreRevMut, }; use firewood_shale::{ - cached::PlainMem, - compact::{CompactSpace, CompactSpaceHeader}, - CachedStore, ObjPtr, Storable, StoredView, + cached::PlainMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, Storable, + StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; @@ -33,7 +32,7 @@ fn bench_hydrate(b: &mut Bencher) { fn bench_insert(b: &mut Bencher) { const TEST_MEM_SIZE: u64 = 20_000_000; - let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + let merkle_payload_header = DiskAddress::null(); let merkle_payload_header_ref = StoredView::ptr_to_obj( &PlainMem::new(2 * firewood_shale::compact::CompactHeader::MSIZE, 9), @@ -52,7 +51,7 @@ fn bench_insert(b: &mut Bencher) { ) .unwrap(); let mut merkle = Merkle::new(Box::new(store)); - let mut root = ObjPtr::null(); + let mut root = DiskAddress::null(); Merkle::>::init_root(&mut root, merkle.get_store()).unwrap(); let mut rng = rand::rngs::StdRng::seed_from_u64(1234); const KEY_LEN: usize = 4; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 0cf1e70af913..e48defc4a050 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -20,7 +20,8 @@ use metered::{metered, HitCount}; use parking_lot::{Mutex, RwLock}; use shale::{ compact::{CompactSpace, CompactSpaceHeader}, - CachedStore, Obj, ObjPtr, ShaleError, ShaleStore, SpaceId, Storable, StoredView, + disk_address::DiskAddress, + CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, }; use std::{ collections::VecDeque, @@ -28,6 +29,7 @@ use std::{ fmt, io::{Cursor, Write}, mem::size_of, + num::NonZeroUsize, path::Path, sync::Arc, thread::JoinHandle, @@ -177,9 +179,9 @@ fn get_sub_universe_from_empty_delta( struct DbHeader { /// The root node of the account model storage. (Where the values are [Account] objects, which /// may contain the root for the secondary trie.) - acc_root: ObjPtr, + acc_root: DiskAddress, /// The root node of the generic key-value store. - kv_root: ObjPtr, + kv_root: DiskAddress, } impl DbHeader { @@ -187,25 +189,23 @@ impl DbHeader { pub fn new_empty() -> Self { Self { - acc_root: ObjPtr::null(), - kv_root: ObjPtr::null(), + acc_root: DiskAddress::null(), + kv_root: DiskAddress::null(), } } } impl Storable for DbHeader { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: Self::MSIZE, })?; - let acc_root = u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let kv_root = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); Ok(Self { - acc_root: ObjPtr::new_from_addr(acc_root), - kv_root: ObjPtr::new_from_addr(kv_root), + acc_root: raw.as_deref()[..8].into(), + kv_root: raw.as_deref()[8..].into(), }) } @@ -215,8 +215,8 @@ impl Storable for DbHeader { fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.acc_root.addr().to_le_bytes())?; - cur.write_all(&self.kv_root.addr().to_le_bytes())?; + cur.write_all(&self.acc_root.to_le_bytes())?; + cur.write_all(&self.kv_root.to_le_bytes())?; Ok(()) } } @@ -561,7 +561,7 @@ impl Db { let merkle_payload_header_ref = Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; + let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, DbHeader::MSIZE)?; let header_refs = ( db_header_ref, @@ -606,13 +606,13 @@ impl Db { payload_regn_nbit: u64, cfg: &DbConfig, ) -> Result<(Universe>, DbRev), DbError> { - let mut offset = Db::PARAM_SIZE; - let db_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += DbHeader::MSIZE; - let merkle_payload_header: ObjPtr = ObjPtr::new_from_addr(offset); - offset += CompactSpaceHeader::MSIZE; - assert!(offset <= SPACE_RESERVED); - let blob_payload_header: ObjPtr = ObjPtr::new_from_addr(0); + let mut offset = Db::PARAM_SIZE as usize; + let db_header: DiskAddress = DiskAddress::from(offset); + offset += DbHeader::MSIZE as usize; + let merkle_payload_header: DiskAddress = DiskAddress::from(offset); + offset += CompactSpaceHeader::MSIZE as usize; + assert!(offset <= SPACE_RESERVED as usize); + let blob_payload_header: DiskAddress = DiskAddress::null(); let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); let mut blob_meta_store = StoreRevMut::new(cached_space.blob.meta.clone()); @@ -620,21 +620,21 @@ impl Db { if reset_store_headers { // initialize store headers merkle_meta_store.write( - merkle_payload_header.addr(), + merkle_payload_header.into(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( - SPACE_RESERVED, - SPACE_RESERVED, + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), ))?, ); merkle_meta_store.write( - db_header.addr(), + db_header.into(), &shale::to_dehydrated(&DbHeader::new_empty())?, ); blob_meta_store.write( - blob_payload_header.addr(), + blob_payload_header.into(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( - SPACE_RESERVED, - SPACE_RESERVED, + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), ))?, ); } @@ -682,7 +682,7 @@ impl Db { meta_ref: &K, header_offset: u64, ) -> Result, DbError> { - let payload_header = ObjPtr::::new_from_addr(header_offset); + let payload_header = DiskAddress::from(header_offset as usize); StoredView::ptr_to_obj( meta_ref, payload_header, @@ -692,7 +692,7 @@ impl Db { } fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { - let db_header = ObjPtr::::new_from_addr(Db::PARAM_SIZE); + let db_header = DiskAddress::from(Db::PARAM_SIZE as usize); StoredView::ptr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 09d1338ff6c7..4d1eb41a73da 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -4,7 +4,7 @@ use crate::proof::Proof; use enum_as_inner::EnumAsInner; use sha3::Digest; -use shale::{CachedStore, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable}; +use shale::{disk_address::DiskAddress, CachedStore, ObjRef, ShaleError, ShaleStore, Storable}; use std::{ collections::HashMap, error::Error, @@ -60,7 +60,7 @@ impl std::ops::Deref for TrieHash { } impl Storable for TrieHash { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -160,7 +160,7 @@ impl std::ops::Deref for Data { #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { - chd: [Option>; NBRANCH], + chd: [Option; NBRANCH], value: Option, chd_eth_rlp: [Option>; NBRANCH], } @@ -170,7 +170,7 @@ impl Debug for BranchNode { write!(f, "[Branch")?; for (i, c) in self.chd.iter().enumerate() { if let Some(c) = c { - write!(f, " ({i:x} {c})")?; + write!(f, " ({i:x} {c:?})")?; } } for (i, c) in self.chd_eth_rlp.iter().enumerate() { @@ -190,7 +190,7 @@ impl Debug for BranchNode { } impl BranchNode { - fn single_child(&self) -> (Option<(ObjPtr, u8)>, bool) { + fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { let mut has_chd = false; let mut only_chd = None; for (i, c) in self.chd.iter().enumerate() { @@ -248,7 +248,7 @@ impl BranchNode { } pub fn new( - chd: [Option>; NBRANCH], + chd: [Option; NBRANCH], value: Option>, chd_eth_rlp: [Option>; NBRANCH], ) -> Self { @@ -263,11 +263,11 @@ impl BranchNode { &self.value } - pub fn chd(&self) -> &[Option>; NBRANCH] { + pub fn chd(&self) -> &[Option; NBRANCH] { &self.chd } - pub fn chd_mut(&mut self) -> &mut [Option>; NBRANCH] { + pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { &mut self.chd } @@ -312,11 +312,11 @@ impl LeafNode { } #[derive(PartialEq, Eq, Clone)] -pub struct ExtNode(PartialPath, ObjPtr, Option>); +pub struct ExtNode(PartialPath, DiskAddress, Option>); impl Debug for ExtNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Extension {:?} {} {:?}]", self.0, self.1, self.2) + write!(f, "[Extension {:?} {:?} {:?}]", self.0, self.1, self.2) } } @@ -352,7 +352,7 @@ impl ExtNode { stream.out().into() } - pub fn new(path: Vec, chd: ObjPtr, chd_eth_rlp: Option>) -> Self { + pub fn new(path: Vec, chd: DiskAddress, chd_eth_rlp: Option>) -> Self { ExtNode(PartialPath(path), chd, chd_eth_rlp) } @@ -360,11 +360,11 @@ impl ExtNode { &self.0 } - pub fn chd(&self) -> ObjPtr { + pub fn chd(&self) -> DiskAddress { self.1 } - pub fn chd_mut(&mut self) -> &mut ObjPtr { + pub fn chd_mut(&mut self) -> &mut DiskAddress { &mut self.1 } @@ -415,6 +415,7 @@ impl Clone for Node { } #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] +#[allow(clippy::large_enum_variant)] pub enum NodeType { Branch(BranchNode), Leaf(LeafNode), @@ -444,7 +445,7 @@ impl Node { eth_rlp_long: OnceLock::new(), eth_rlp: OnceLock::new(), inner: NodeType::Branch(BranchNode { - chd: [Some(ObjPtr::null()); NBRANCH], + chd: [Some(DiskAddress::null()); NBRANCH], value: Some(Data(Vec::new())), chd_eth_rlp: Default::default(), }), @@ -525,14 +526,14 @@ impl Node { } impl Storable for Node { - fn hydrate(addr: u64, mem: &T) -> Result { - const META_SIZE: u64 = 32 + 1 + 1; - let meta_raw = mem - .get_view(addr, META_SIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: META_SIZE, - })?; + fn hydrate(addr: usize, mem: &T) -> Result { + const META_SIZE: usize = 32 + 1 + 1; + let meta_raw = + mem.get_view(addr, META_SIZE as u64) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: META_SIZE as u64, + })?; let attrs = meta_raw.as_deref()[32]; let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None @@ -562,9 +563,9 @@ impl Storable for Node { let mut buff = [0; 8]; for chd in chd.iter_mut() { cur.read_exact(&mut buff)?; - let addr = u64::from_le_bytes(buff); + let addr = usize::from_le_bytes(buff); if addr != 0 { - *chd = Some(ObjPtr::new_from_addr(addr)) + *chd = Some(DiskAddress::from(addr)) } } cur.read_exact(&mut buff[..4])?; @@ -574,9 +575,9 @@ impl Storable for Node { None } else { Some(Data( - mem.get_view(addr + META_SIZE + branch_header_size, raw_len) + mem.get_view(addr + META_SIZE + branch_header_size as usize, raw_len) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + branch_header_size, + offset: addr + META_SIZE + branch_header_size as usize, size: raw_len, })? .as_deref(), @@ -584,9 +585,9 @@ impl Storable for Node { }; let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); let offset = if raw_len == u32::MAX as u64 { - addr + META_SIZE + branch_header_size + addr + META_SIZE + branch_header_size as usize } else { - addr + META_SIZE + branch_header_size + raw_len + addr + META_SIZE + branch_header_size as usize + raw_len as usize }; let mut cur_rlp_len = 0; for chd_rlp in chd_eth_rlp.iter_mut() { @@ -610,7 +611,7 @@ impl Storable for Node { )?; let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); *chd_rlp = Some(rlp); - cur_rlp_len += rlp_len + cur_rlp_len += rlp_len as usize } } @@ -640,9 +641,9 @@ impl Storable for Node { let ptr = u64::from_le_bytes(buff); let nibbles: Vec = mem - .get_view(addr + META_SIZE + ext_header_size, path_len) + .get_view(addr + META_SIZE + ext_header_size as usize, path_len) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size, + offset: addr + META_SIZE + ext_header_size as usize, size: path_len, })? .as_deref() @@ -654,9 +655,12 @@ impl Storable for Node { let mut buff = [0_u8; 1]; let rlp_len_raw = mem - .get_view(addr + META_SIZE + ext_header_size + path_len, 1) + .get_view( + addr + META_SIZE + ext_header_size as usize + path_len as usize, + 1, + ) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size + path_len, + offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, size: 1, })?; cur = Cursor::new(rlp_len_raw.as_deref()); @@ -664,9 +668,16 @@ impl Storable for Node { let rlp_len = buff[0] as u64; let rlp: Option> = if rlp_len != 0 { let rlp_raw = mem - .get_view(addr + META_SIZE + ext_header_size + path_len + 1, rlp_len) + .get_view( + addr + META_SIZE + ext_header_size as usize + path_len as usize + 1, + rlp_len, + ) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size + path_len + 1, + offset: addr + + META_SIZE + + ext_header_size as usize + + path_len as usize + + 1, size: rlp_len, })?; @@ -678,7 +689,7 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, eth_rlp_long, - NodeType::Extension(ExtNode(path, ObjPtr::new_from_addr(ptr), rlp)), + NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), rlp)), )) } Self::LEAF_NODE => { @@ -696,9 +707,12 @@ impl Storable for Node { cur.read_exact(&mut buff)?; let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem - .get_view(addr + META_SIZE + leaf_header_size, path_len + data_len) + .get_view( + addr + META_SIZE + leaf_header_size as usize, + path_len + data_len, + ) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + leaf_header_size, + offset: addr + META_SIZE + leaf_header_size as usize, size: path_len + data_len, })?; @@ -778,7 +792,7 @@ impl Storable for Node { cur.write_all(&[Self::BRANCH_NODE]).unwrap(); for c in n.chd.iter() { cur.write_all(&match c { - Some(p) => p.addr().to_le_bytes(), + Some(p) => p.to_le_bytes(), None => 0u64.to_le_bytes(), })?; } @@ -808,7 +822,7 @@ impl Storable for Node { cur.write_all(&[Self::EXT_NODE])?; let path: Vec = from_nibbles(&n.0.encode(false)).collect(); cur.write_all(&[path.len() as u8])?; - cur.write_all(&n.1.addr().to_le_bytes())?; + cur.write_all(&n.1.to_le_bytes())?; cur.write_all(&path)?; if n.2.is_some() { let rlp = n.2.as_ref().unwrap(); @@ -848,13 +862,13 @@ pub struct Merkle { } impl + Send + Sync> Merkle { - pub fn get_node(&self, ptr: ObjPtr) -> Result, MerkleError> { + pub fn get_node(&self, ptr: DiskAddress) -> Result, MerkleError> { self.store.get_item(ptr).map_err(MerkleError::Shale) } pub fn new_node(&self, item: Node) -> Result, MerkleError> { self.store.put_item(item, 0).map_err(MerkleError::Shale) } - fn free_node(&mut self, ptr: ObjPtr) -> Result<(), MerkleError> { + fn free_node(&mut self, ptr: DiskAddress) -> Result<(), MerkleError> { self.store.free_item(ptr).map_err(MerkleError::Shale) } } @@ -865,7 +879,7 @@ impl + Send + Sync> Merkle { } pub fn init_root( - root: &mut ObjPtr, + root: &mut DiskAddress, store: &dyn ShaleStore, ) -> Result<(), MerkleError> { *root = store @@ -900,7 +914,7 @@ impl + Send + Sync> Merkle { pub fn root_hash( &self, - root: ObjPtr, + root: DiskAddress, ) -> Result { let root = self .get_node(root)? @@ -921,12 +935,11 @@ impl + Send + Sync> Merkle { }) } - fn dump_(&self, u: ObjPtr, w: &mut dyn Write) -> Result<(), MerkleError> { + fn dump_(&self, u: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { let u_ref = self.get_node(u)?; write!( w, - "{} => {}: ", - u, + "{u:?} => {}: ", match u_ref.root_hash.get() { Some(h) => hex::encode(**h), None => "".to_string(), @@ -949,7 +962,7 @@ impl + Send + Sync> Merkle { Ok(()) } - pub fn dump(&self, root: ObjPtr, w: &mut dyn Write) -> Result<(), MerkleError> { + pub fn dump(&self, root: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { if root.is_null() { write!(w, "").map_err(MerkleError::Format)?; } else { @@ -958,7 +971,7 @@ impl + Send + Sync> Merkle { Ok(()) } - fn set_parent(&self, new_chd: ObjPtr, parents: &mut [(ObjRef<'_, Node>, u8)]) { + fn set_parent(&self, new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { let (p_ref, idx) = parents.last_mut().unwrap(); p_ref .write(|p| { @@ -981,7 +994,7 @@ impl + Send + Sync> Merkle { n_path: Vec, n_value: Option, val: Vec, - deleted: &mut Vec>, + deleted: &mut Vec, ) -> Result>, MerkleError> { let u_ptr = u_ref.as_ptr(); let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) { @@ -1153,7 +1166,7 @@ impl + Send + Sync> Merkle { &mut self, key: K, val: Vec, - root: ObjPtr, + root: DiskAddress, ) -> Result<(), MerkleError> { // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); @@ -1335,7 +1348,7 @@ impl + Send + Sync> Merkle { fn after_remove_leaf( &self, parents: &mut Vec<(ObjRef<'_, Node>, u8)>, - deleted: &mut Vec>, + deleted: &mut Vec, ) -> Result<(), MerkleError> { let (b_chd, val) = { let (mut b_ref, b_idx) = parents.pop().unwrap(); @@ -1510,9 +1523,9 @@ impl + Send + Sync> Merkle { fn after_remove_branch( &self, - (c_ptr, idx): (ObjPtr, u8), + (c_ptr, idx): (DiskAddress, u8), parents: &mut Vec<(ObjRef<'_, Node>, u8)>, - deleted: &mut Vec>, + deleted: &mut Vec, ) -> Result<(), MerkleError> { // [b] -> [u] -> [c] let (mut b_ref, b_idx) = parents.pop().unwrap(); @@ -1623,7 +1636,7 @@ impl + Send + Sync> Merkle { pub fn remove>( &mut self, key: K, - root: ObjPtr, + root: DiskAddress, ) -> Result>, MerkleError> { let mut chunks = vec![0]; chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); @@ -1715,8 +1728,8 @@ impl + Send + Sync> Merkle { fn remove_tree_( &self, - u: ObjPtr, - deleted: &mut Vec>, + u: DiskAddress, + deleted: &mut Vec, ) -> Result<(), MerkleError> { let u_ref = self.get_node(u)?; match &u_ref.inner { @@ -1732,7 +1745,7 @@ impl + Send + Sync> Merkle { Ok(()) } - pub fn remove_tree(&mut self, root: ObjPtr) -> Result<(), MerkleError> { + pub fn remove_tree(&mut self, root: DiskAddress) -> Result<(), MerkleError> { let mut deleted = Vec::new(); if root.is_null() { return Ok(()); @@ -1747,7 +1760,7 @@ impl + Send + Sync> Merkle { pub fn get_mut>( &mut self, key: K, - root: ObjPtr, + root: DiskAddress, ) -> Result>, MerkleError> { let mut chunks = vec![0]; chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); @@ -1819,7 +1832,7 @@ impl + Send + Sync> Merkle { /// If the trie does not contain a value for key, the returned proof contains /// all nodes of the longest existing prefix of the key, ending with the node /// that proves the absence of the key (at least the root node). - pub fn prove(&self, key: K, root: ObjPtr) -> Result + pub fn prove(&self, key: K, root: DiskAddress) -> Result where K: AsRef<[u8]>, T: ValueTransformer, @@ -1845,14 +1858,14 @@ impl + Send + Sync> Merkle { }; let mut nskip = 0; - let mut nodes: Vec> = Vec::new(); + let mut nodes: Vec = Vec::new(); for (i, nib) in chunks.iter().enumerate() { if nskip > 0 { nskip -= 1; continue; } nodes.push(u_ref.as_ptr()); - let next_ptr: ObjPtr = match &u_ref.inner { + let next_ptr: DiskAddress = match &u_ref.inner { NodeType::Branch(n) => match n.chd[*nib as usize] { Some(c) => c, None => break, @@ -1902,9 +1915,9 @@ impl + Send + Sync> Merkle { pub fn get>( &self, key: K, - root: ObjPtr, + root: DiskAddress, ) -> Result, MerkleError> { - // TODO: Make this NonNull> or something similar + // TODO: Make this NonNull or something similar if root.is_null() { return Ok(None); } @@ -1970,8 +1983,8 @@ impl + Send + Sync> Merkle { pub struct Ref<'a>(ObjRef<'a, Node>); pub struct RefMut<'a, S> { - ptr: ObjPtr, - parents: Vec<(ObjPtr, u8)>, + ptr: DiskAddress, + parents: Vec<(DiskAddress, u8)>, merkle: &'a mut Merkle, } @@ -1987,7 +2000,7 @@ impl<'a> std::ops::Deref for Ref<'a> { } impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { - fn new(ptr: ObjPtr, parents: Vec<(ObjPtr, u8)>, merkle: &'a mut Merkle) -> Self { + fn new(ptr: DiskAddress, parents: Vec<(DiskAddress, u8)>, merkle: &'a mut Merkle) -> Self { Self { ptr, parents, @@ -2128,7 +2141,7 @@ mod test { let chd0 = [None; NBRANCH]; let mut chd1 = chd0; for node in chd1.iter_mut().take(NBRANCH / 2) { - *node = Some(ObjPtr::new_from_addr(0xa)); + *node = Some(DiskAddress::from(0xa)); } let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { @@ -2148,7 +2161,7 @@ mod test { None, NodeType::Extension(ExtNode( PartialPath(vec![0x1, 0x2, 0x3]), - ObjPtr::new_from_addr(0x42), + DiskAddress::from(0x42), None, )), ), @@ -2157,7 +2170,7 @@ mod test { None, NodeType::Extension(ExtNode( PartialPath(vec![0x1, 0x2, 0x3]), - ObjPtr::null(), + DiskAddress::null(), Some(vec![0x1, 0x2, 0x3]), )), ), diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 186623a6a62b..4d6f4cc801c2 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -6,11 +6,10 @@ use crate::{ proof::{Proof, ProofError}, }; use shale::{ - cached::DynamicMem, - compact::{CompactSpace, CompactSpaceHeader}, - CachedStore, ObjPtr, ShaleStore, StoredView, + cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, + StoredView, }; -use std::sync::Arc; +use std::{num::NonZeroUsize, sync::Arc}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -36,7 +35,7 @@ pub enum DataStoreError { } pub struct MerkleSetup { - root: ObjPtr, + root: DiskAddress, merkle: Merkle, } @@ -65,7 +64,7 @@ impl + Send + Sync> MerkleSetup { .map_err(|_err| DataStoreError::GetError) } - pub fn get_root(&self) -> ObjPtr { + pub fn get_root(&self) -> DiskAddress { self.root } @@ -121,15 +120,18 @@ pub fn new_merkle( meta_size: u64, compact_size: u64, ) -> MerkleSetup> { - const RESERVED: u64 = 0x1000; - assert!(meta_size > RESERVED); - assert!(compact_size > RESERVED); + const RESERVED: usize = 0x1000; + assert!(meta_size as usize > RESERVED); + assert!(compact_size as usize > RESERVED); let mut dm = DynamicMem::new(meta_size, 0); - let compact_header: ObjPtr = ObjPtr::new_from_addr(0x0); + let compact_header = DiskAddress::null(); dm.write( - compact_header.addr(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new(RESERVED, RESERVED)) - .unwrap(), + compact_header.into(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + NonZeroUsize::new(RESERVED).unwrap(), + NonZeroUsize::new(RESERVED).unwrap(), + )) + .unwrap(), ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); @@ -140,7 +142,7 @@ pub fn new_merkle( let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); - let mut root = ObjPtr::null(); + let mut root = DiskAddress::null(); Merkle::>::init_root(&mut root, &space).unwrap(); MerkleSetup { root, diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 5c9e773e9730..e76692839c43 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -8,7 +8,7 @@ use crate::merkle_util::*; use nix::errno::Errno; use serde::{Deserialize, Serialize}; use sha3::Digest; -use shale::ObjPtr; +use shale::disk_address::DiskAddress; use shale::ShaleError; use shale::ShaleStore; @@ -561,7 +561,7 @@ impl Proof { key: &[u8], buf: &[u8], end_node: bool, - ) -> Result<(ObjPtr, Option, usize), ProofError> { + ) -> Result<(DiskAddress, Option, usize), ProofError> { let rlp = rlp::Rlp::new(buf); let size = rlp.item_count()?; @@ -681,11 +681,11 @@ fn get_ext_ptr + Send + Sync>( term: bool, Data(data): Data, CurKey(cur_key): CurKey, -) -> Result, ProofError> { +) -> Result { let node = if term { NodeType::Leaf(LeafNode::new(cur_key, data)) } else { - NodeType::Extension(ExtNode::new(cur_key, ObjPtr::null(), Some(data))) + NodeType::Extension(ExtNode::new(cur_key, DiskAddress::null(), Some(data))) }; merkle @@ -698,7 +698,7 @@ fn build_branch_ptr + Send + Sync>( merkle: &Merkle, value: Option>, chd_eth_rlp: [Option>; NBRANCH], -) -> Result, ProofError> { +) -> Result { let node = BranchNode::new([None; NBRANCH], value, chd_eth_rlp); let node = NodeType::Branch(node); let node = Node::new(node); @@ -736,7 +736,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( let root = merkle_setup.get_root(); let merkle = merkle_setup.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; - let mut parent = ObjPtr::null(); + let mut parent = DiskAddress::null(); let mut fork_left: Ordering = Ordering::Equal; let mut fork_right: Ordering = Ordering::Equal; @@ -922,7 +922,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( p_ref .write(|p| match p.inner_mut() { NodeType::Extension(n) => { - *n.chd_mut() = ObjPtr::null(); + *n.chd_mut() = DiskAddress::null(); *n.chd_eth_rlp_mut() = None; } NodeType::Branch(n) => { @@ -969,8 +969,8 @@ fn unset_internal, S: ShaleStore + Send + Sync>( // unset the entire branch. fn unset_node_ref, S: ShaleStore + Send + Sync>( merkle: &Merkle, - parent: ObjPtr, - node: Option>, + parent: DiskAddress, + node: Option, key: K, index: usize, remove_left: bool, @@ -1084,7 +1084,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( p_ref .write(|p| match p.inner_mut() { NodeType::Extension(n) => { - *n.chd_mut() = ObjPtr::null(); + *n.chd_mut() = DiskAddress::null(); *n.chd_eth_rlp_mut() = None; } NodeType::Branch(n) => { diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 39610b95143d..7e69a2e9fc25 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -363,10 +363,10 @@ impl StoreRevShared { impl CachedStore for StoreRevShared { fn get_view( &self, - offset: u64, + offset: usize, length: u64, ) -> Option>>> { - let data = self.0.get_slice(offset, length)?; + let data = self.0.get_slice(offset as u64, length)?; Some(Box::new(StoreRef { data })) } @@ -374,7 +374,7 @@ impl CachedStore for StoreRevShared { Box::new(StoreShared(self.clone())) } - fn write(&mut self, _offset: u64, _change: &[u8]) { + fn write(&mut self, _offset: usize, _change: &[u8]) { // StoreRevShared is a read-only view version of CachedStore // Writes could be induced by lazy hashing and we can just ignore those } @@ -499,17 +499,17 @@ impl StoreRevMut { impl CachedStore for StoreRevMut { fn get_view( &self, - offset: u64, + offset: usize, length: u64, ) -> Option>>> { let data = if length == 0 { Vec::new() } else { - let end = offset + length - 1; - let s_pid = offset >> PAGE_SIZE_NBIT; - let s_off = (offset & PAGE_MASK) as usize; - let e_pid = end >> PAGE_SIZE_NBIT; - let e_off = (end & PAGE_MASK) as usize; + let end = offset + length as usize - 1; + let s_pid = (offset >> PAGE_SIZE_NBIT) as u64; + let s_off = offset & PAGE_MASK as usize; + let e_pid = (end >> PAGE_SIZE_NBIT) as u64; + let e_off = end & PAGE_MASK as usize; let deltas = &self.deltas.read().pages; let prev_deltas = &self.prev_deltas.read().pages; if s_pid == e_pid { @@ -517,7 +517,7 @@ impl CachedStore for StoreRevMut { Some(p) => p[s_off..e_off + 1].to_vec(), None => match prev_deltas.get(&s_pid) { Some(p) => p[s_off..e_off + 1].to_vec(), - None => self.base_space.get_slice(offset, length)?, + None => self.base_space.get_slice(offset as u64, length)?, }, } } else { @@ -527,7 +527,7 @@ impl CachedStore for StoreRevMut { Some(p) => p[s_off..].to_vec(), None => self .base_space - .get_slice(offset, PAGE_SIZE - s_off as u64)?, + .get_slice(offset as u64, PAGE_SIZE - s_off as u64)?, }, }; for p in s_pid + 1..e_pid { @@ -561,20 +561,21 @@ impl CachedStore for StoreRevMut { Box::new(StoreShared(self.clone())) } - fn write(&mut self, offset: u64, mut change: &[u8]) { + fn write(&mut self, offset: usize, mut change: &[u8]) { let length = change.len() as u64; - let end = offset + length - 1; + let end = offset + length as usize - 1; let s_pid = offset >> PAGE_SIZE_NBIT; - let s_off = (offset & PAGE_MASK) as usize; + let s_off = offset & PAGE_MASK as usize; let e_pid = end >> PAGE_SIZE_NBIT; - let e_off = (end & PAGE_MASK) as usize; + let e_off = end & PAGE_MASK as usize; let mut undo: Vec = Vec::new(); let redo: Box<[u8]> = change.into(); if s_pid == e_pid { let mut deltas = self.deltas.write(); - let slice = &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid) - [s_off..e_off + 1]; + let slice = + &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid as u64) + [s_off..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change) } else { @@ -582,9 +583,11 @@ impl CachedStore for StoreRevMut { { let mut deltas = self.deltas.write(); - let slice = - &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid) - [s_off..]; + let slice = &mut self.get_page_mut( + deltas.deref_mut(), + &self.prev_deltas.read(), + s_pid as u64, + )[s_off..]; undo.extend(&*slice); slice.copy_from_slice(&change[..len]); } @@ -593,14 +596,16 @@ impl CachedStore for StoreRevMut { let mut deltas = self.deltas.write(); for p in s_pid + 1..e_pid { - let slice = self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), p); + let slice = + self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), p as u64); undo.extend(&*slice); slice.copy_from_slice(&change[..PAGE_SIZE as usize]); change = &change[PAGE_SIZE as usize..]; } - let slice = &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), e_pid) - [..e_off + 1]; + let slice = + &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), e_pid as u64) + [..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change); } @@ -608,10 +613,13 @@ impl CachedStore for StoreRevMut { let plain = &mut self.deltas.write().plain; assert!(undo.len() == redo.len()); plain.undo.push(SpaceWrite { - offset, + offset: offset as u64, data: undo.into(), }); - plain.redo.push(SpaceWrite { offset, data: redo }); + plain.redo.push(SpaceWrite { + offset: offset as u64, + data: redo, + }); } fn id(&self) -> SpaceId { @@ -664,14 +672,20 @@ fn test_from_ash() { let rev = StoreRevShared::from_ash(z, &writes); println!("{rev:?}"); assert_eq!( - rev.get_view(min, max - min).as_deref().unwrap().as_deref(), + rev.get_view(min as usize, max - min) + .as_deref() + .unwrap() + .as_deref(), canvas ); for _ in 0..2 * n { let l = rng.gen_range(min..max); let r = rng.gen_range(l + 1..max); assert_eq!( - rev.get_view(l, r - l).as_deref().unwrap().as_deref(), + rev.get_view(l as usize, r - l) + .as_deref() + .unwrap() + .as_deref(), canvas[(l - min) as usize..(r - min) as usize] ); } diff --git a/shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs index ca052fea9158..a651cbc70897 100644 --- a/shale/benches/shale-bench.rs +++ b/shale/benches/shale-bench.rs @@ -8,11 +8,12 @@ use rand::Rng; use shale::{ cached::{DynamicMem, PlainMem}, compact::CompactSpaceHeader, - CachedStore, ObjPtr, StoredView, + disk_address::DiskAddress, + CachedStore, Obj, StoredView, }; use std::{fs::File, os::raw::c_int, path::Path}; -const BENCH_MEM_SIZE: u64 = 2_000_000; +const BENCH_MEM_SIZE: usize = 2_000_000; // To enable flamegraph output // cargo bench --bench shale-bench -- --profile-time=N @@ -59,7 +60,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { let len = rng.gen_range(0..26); let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); - let offset = rng.gen_range(0..BENCH_MEM_SIZE - len as u64); + let offset = rng.gen_range(0..BENCH_MEM_SIZE - len); cached.write(offset, rdata); let view = cached @@ -72,8 +73,8 @@ fn get_view(b: &mut Bencher, mut cached: C) { } fn serialize(m: &T) { - let compact_header_obj: ObjPtr = ObjPtr::new_from_addr(0x0); - let _compact_header = + let compact_header_obj: DiskAddress = DiskAddress::from(0x0); + let _: Obj = StoredView::ptr_to_obj(m, compact_header_obj, shale::compact::CompactHeader::MSIZE) .unwrap(); } @@ -81,11 +82,11 @@ fn serialize(m: &T) { fn bench_cursors(c: &mut Criterion) { let mut group = c.benchmark_group("shale-bench"); group.bench_function("PlainMem", |b| { - let mem = PlainMem::new(BENCH_MEM_SIZE, 0); + let mem = PlainMem::new(BENCH_MEM_SIZE as u64, 0); get_view(b, mem) }); group.bench_function("DynamicMem", |b| { - let mem = DynamicMem::new(BENCH_MEM_SIZE, 0); + let mem = DynamicMem::new(BENCH_MEM_SIZE as u64, 0); get_view(b, mem) }); } diff --git a/shale/src/cached.rs b/shale/src/cached.rs index cdf3453228bf..8963ce665cfa 100644 --- a/shale/src/cached.rs +++ b/shale/src/cached.rs @@ -26,10 +26,10 @@ impl PlainMem { impl CachedStore for PlainMem { fn get_view( &self, - offset: u64, + offset: usize, length: u64, ) -> Option>>> { - let offset = offset as usize; + let offset = offset; let length = length as usize; if offset + length > self.space.read().unwrap().len() { None @@ -52,8 +52,8 @@ impl CachedStore for PlainMem { })) } - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; + fn write(&mut self, offset: usize, change: &[u8]) { + let offset = offset; let length = change.len(); let mut vect = self.space.deref().write().unwrap(); vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); @@ -113,10 +113,10 @@ impl DynamicMem { impl CachedStore for DynamicMem { fn get_view( &self, - offset: u64, + offset: usize, length: u64, ) -> Option>>> { - let offset = offset as usize; + let offset = offset; let length = length as usize; let size = offset + length; let mut space = self.space.write().unwrap(); @@ -143,8 +143,8 @@ impl CachedStore for DynamicMem { })) } - fn write(&mut self, offset: u64, change: &[u8]) { - let offset = offset as usize; + fn write(&mut self, offset: usize, change: &[u8]) { + let offset = offset; let length = change.len(); let size = offset + length; diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 408cb3e08b6f..6324d1968663 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -1,6 +1,8 @@ -use super::{CachedStore, Obj, ObjPtr, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; +use super::disk_address::DiskAddress; +use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; use std::fmt::Debug; use std::io::{Cursor, Write}; +use std::num::NonZeroUsize; use std::ops::DerefMut; use std::sync::{Arc, RwLock}; @@ -8,7 +10,7 @@ use std::sync::{Arc, RwLock}; pub struct CompactHeader { payload_size: u64, is_freed: bool, - desc_addr: ObjPtr, + desc_addr: DiskAddress, } impl CompactHeader { @@ -23,7 +25,7 @@ impl CompactHeader { } impl Storable for CompactHeader { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -34,11 +36,11 @@ impl Storable for CompactHeader { u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); let is_freed = raw.as_deref()[8] != 0; let desc_addr = - u64::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); + usize::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); Ok(Self { payload_size, is_freed, - desc_addr: ObjPtr::new(desc_addr), + desc_addr: DiskAddress(NonZeroUsize::new(desc_addr)), }) } @@ -50,7 +52,7 @@ impl Storable for CompactHeader { let mut cur = Cursor::new(to); cur.write_all(&self.payload_size.to_le_bytes())?; cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; - cur.write_all(&self.desc_addr.addr().to_le_bytes())?; + cur.write_all(&self.desc_addr.to_le_bytes())?; Ok(()) } } @@ -65,7 +67,7 @@ impl CompactFooter { } impl Storable for CompactFooter { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -89,7 +91,7 @@ impl Storable for CompactFooter { #[derive(Clone, Copy, Debug)] struct CompactDescriptor { payload_size: u64, - haddr: u64, // pointer to the payload of freed space + haddr: usize, // disk address of the free space } impl CompactDescriptor { @@ -97,7 +99,7 @@ impl CompactDescriptor { } impl Storable for CompactDescriptor { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -106,7 +108,7 @@ impl Storable for CompactDescriptor { })?; let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let haddr = u64::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); + let haddr = usize::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); Ok(Self { payload_size, haddr, @@ -127,18 +129,18 @@ impl Storable for CompactDescriptor { #[derive(Debug)] pub struct CompactSpaceHeader { - meta_space_tail: u64, - compact_space_tail: u64, - base_addr: ObjPtr, - alloc_addr: ObjPtr, + meta_space_tail: DiskAddress, + compact_space_tail: DiskAddress, + base_addr: DiskAddress, + alloc_addr: DiskAddress, } #[derive(Debug)] struct CompactSpaceHeaderSliced { - meta_space_tail: Obj, - compact_space_tail: Obj, - base_addr: Obj>, - alloc_addr: Obj>, + meta_space_tail: Obj, + compact_space_tail: Obj, + base_addr: Obj, + alloc_addr: Obj, } impl CompactSpaceHeaderSliced { @@ -153,19 +155,19 @@ impl CompactSpaceHeaderSliced { impl CompactSpaceHeader { pub const MSIZE: u64 = 32; - pub fn new(meta_base: u64, compact_base: u64) -> Self { + pub fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { - meta_space_tail: meta_base, - compact_space_tail: compact_base, - base_addr: ObjPtr::new_from_addr(meta_base), - alloc_addr: ObjPtr::new_from_addr(meta_base), + meta_space_tail: DiskAddress::new(meta_base), + compact_space_tail: DiskAddress::new(compact_base), + base_addr: DiskAddress::new(meta_base), + alloc_addr: DiskAddress::new(meta_base), } } fn into_fields(r: Obj) -> Result { Ok(CompactSpaceHeaderSliced { - meta_space_tail: StoredView::slice(&r, 0, 8, U64Field(r.meta_space_tail))?, - compact_space_tail: StoredView::slice(&r, 8, 8, U64Field(r.compact_space_tail))?, + meta_space_tail: StoredView::slice(&r, 0, 8, r.meta_space_tail)?, + compact_space_tail: StoredView::slice(&r, 8, 8, r.compact_space_tail)?, base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, }) @@ -173,26 +175,22 @@ impl CompactSpaceHeader { } impl Storable for CompactSpaceHeader { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: Self::MSIZE, })?; - let meta_space_tail = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); - let compact_space_tail = - u64::from_le_bytes(raw.as_deref()[8..16].try_into().expect("invalid slice")); - let base_addr = - u64::from_le_bytes(raw.as_deref()[16..24].try_into().expect("invalid slice")); - let alloc_addr = - u64::from_le_bytes(raw.as_deref()[24..].try_into().expect("invalid slice")); + let meta_space_tail = raw.as_deref()[..8].into(); + let compact_space_tail = raw.as_deref()[8..16].into(); + let base_addr = raw.as_deref()[16..24].into(); + let alloc_addr = raw.as_deref()[24..].into(); Ok(Self { meta_space_tail, compact_space_tail, - base_addr: ObjPtr::new_from_addr(base_addr), - alloc_addr: ObjPtr::new_from_addr(alloc_addr), + base_addr, + alloc_addr, }) } @@ -204,34 +202,32 @@ impl Storable for CompactSpaceHeader { let mut cur = Cursor::new(to); cur.write_all(&self.meta_space_tail.to_le_bytes())?; cur.write_all(&self.compact_space_tail.to_le_bytes())?; - cur.write_all(&self.base_addr.addr().to_le_bytes())?; - cur.write_all(&self.alloc_addr.addr().to_le_bytes())?; + cur.write_all(&self.base_addr.to_le_bytes())?; + cur.write_all(&self.alloc_addr.to_le_bytes())?; Ok(()) } } -struct ObjPtrField(ObjPtr); +struct ObjPtrField(DiskAddress); -impl ObjPtrField { +impl ObjPtrField { const MSIZE: u64 = 8; } -impl Storable for ObjPtrField { - fn hydrate(addr: u64, mem: &U) -> Result { +impl Storable for ObjPtrField { + fn hydrate(addr: usize, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: Self::MSIZE, })?; - let obj_ptr = ObjPtr::new_from_addr(u64::from_le_bytes( - <[u8; 8]>::try_from(&raw.as_deref()[0..8]).expect("invalid slice"), - )); + let obj_ptr = raw.as_deref()[0..8].into(); Ok(Self(obj_ptr)) } fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0.addr().to_le_bytes())?; + Cursor::new(to).write_all(&self.to_le_bytes())?; Ok(()) } @@ -240,15 +236,15 @@ impl Storable for ObjPtrField { } } -impl std::ops::Deref for ObjPtrField { - type Target = ObjPtr; - fn deref(&self) -> &ObjPtr { +impl std::ops::Deref for ObjPtrField { + type Target = DiskAddress; + fn deref(&self) -> &DiskAddress { &self.0 } } -impl std::ops::DerefMut for ObjPtrField { - fn deref_mut(&mut self) -> &mut ObjPtr { +impl std::ops::DerefMut for ObjPtrField { + fn deref_mut(&mut self) -> &mut DiskAddress { &mut self.0 } } @@ -261,7 +257,7 @@ impl U64Field { } impl Storable for U64Field { - fn hydrate(addr: u64, mem: &U) -> Result { + fn hydrate(addr: usize, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -305,57 +301,54 @@ struct CompactSpaceInner { } impl CompactSpaceInner { - fn get_descriptor( - &self, - ptr: ObjPtr, - ) -> Result, ShaleError> { + fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } fn get_data_ref( &self, - ptr: ObjPtr, + ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { StoredView::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) } - fn get_header(&self, ptr: ObjPtr) -> Result, ShaleError> { + fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { self.get_data_ref::(ptr, CompactHeader::MSIZE) } - fn get_footer(&self, ptr: ObjPtr) -> Result, ShaleError> { + fn get_footer(&self, ptr: DiskAddress) -> Result, ShaleError> { self.get_data_ref::(ptr, CompactFooter::MSIZE) } - fn del_desc(&mut self, desc_addr: ObjPtr) -> Result<(), ShaleError> { + fn del_desc(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { let desc_size = CompactDescriptor::MSIZE; - debug_assert!((desc_addr.addr - self.header.base_addr.addr) % desc_size == 0); + // TODO: subtracting two disk addresses is only used here, probably can rewrite this + // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); self.header .meta_space_tail - .write(|r| **r -= desc_size) + .write(|r| *r -= desc_size as usize) .unwrap(); - if desc_addr.addr != **self.header.meta_space_tail { - let desc_last = - self.get_descriptor(ObjPtr::new_from_addr(**self.header.meta_space_tail))?; - let mut desc = self.get_descriptor(ObjPtr::new_from_addr(desc_addr.addr))?; + if desc_addr != DiskAddress(**self.header.meta_space_tail) { + let desc_last = self.get_descriptor(**self.header.meta_space_tail.value)?; + let mut desc = self.get_descriptor(desc_addr)?; desc.write(|r| *r = *desc_last).unwrap(); - let mut header = self.get_header(ObjPtr::new(desc.haddr))?; + let mut header = self.get_header(desc.haddr.into())?; header.write(|h| h.desc_addr = desc_addr).unwrap(); } Ok(()) } - fn new_desc(&mut self) -> Result, ShaleError> { + fn new_desc(&mut self) -> Result { let addr = **self.header.meta_space_tail; self.header .meta_space_tail - .write(|r| **r += CompactDescriptor::MSIZE) + .write(|r| *r += CompactDescriptor::MSIZE as usize) .unwrap(); - Ok(ObjPtr::new_from_addr(addr)) + Ok(DiskAddress(addr)) } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { @@ -365,7 +358,7 @@ impl CompactSpaceInner { let mut offset = addr - hsize; let header_payload_size = { - let header = self.get_header(ObjPtr::new(offset))?; + let header = self.get_header(DiskAddress::from(offset as usize))?; assert!(!header.is_freed); header.payload_size }; @@ -376,9 +369,9 @@ impl CompactSpaceInner { // merge with lower data segment offset -= fsize; let (pheader_is_freed, pheader_payload_size, pheader_desc_addr) = { - let pfooter = self.get_footer(ObjPtr::new(offset))?; + let pfooter = self.get_footer(DiskAddress::from(offset as usize))?; offset -= pfooter.payload_size + hsize; - let pheader = self.get_header(ObjPtr::new(offset))?; + let pheader = self.get_header(DiskAddress::from(offset as usize))?; (pheader.is_freed, pheader.payload_size, pheader.desc_addr) }; if pheader_is_freed { @@ -391,20 +384,20 @@ impl CompactSpaceInner { offset = addr + header_payload_size; let mut f = offset; - if offset + fsize < **self.header.compact_space_tail + if offset + fsize < self.header.compact_space_tail.unwrap().get() as u64 && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize { // merge with higher data segment offset += fsize; let (nheader_is_freed, nheader_payload_size, nheader_desc_addr) = { - let nheader = self.get_header(ObjPtr::new(offset))?; + let nheader = self.get_header(DiskAddress::from(offset as usize))?; (nheader.is_freed, nheader.payload_size, nheader.desc_addr) }; if nheader_is_freed { offset += hsize + nheader_payload_size; f = offset; { - let nfooter = self.get_footer(ObjPtr::new(offset))?; + let nfooter = self.get_footer(DiskAddress::from(offset as usize))?; assert!(nheader_payload_size == nfooter.payload_size); } payload_size += hsize + fsize + nheader_payload_size; @@ -417,12 +410,12 @@ impl CompactSpaceInner { let mut desc = self.get_descriptor(desc_addr)?; desc.write(|d| { d.payload_size = payload_size; - d.haddr = h; + d.haddr = h as usize; }) .unwrap(); } - let mut h = self.get_header(ObjPtr::new(h))?; - let mut f = self.get_footer(ObjPtr::new(f))?; + let mut h = self.get_header(DiskAddress::from(h as usize))?; + let mut f = self.get_footer(DiskAddress::from(f as usize))?; h.write(|h| { h.payload_size = payload_size; h.is_freed = true; @@ -435,44 +428,44 @@ impl CompactSpaceInner { } fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { - let tail = **self.header.meta_space_tail; - if tail == self.header.base_addr.addr { + let tail = *self.header.meta_space_tail; + if tail == *self.header.base_addr { return Ok(None); } - let hsize = CompactHeader::MSIZE; - let fsize = CompactFooter::MSIZE; - let dsize = CompactDescriptor::MSIZE; + let hsize = CompactHeader::MSIZE as usize; + let fsize = CompactFooter::MSIZE as usize; + let dsize = CompactDescriptor::MSIZE as usize; let mut old_alloc_addr = *self.header.alloc_addr; - if old_alloc_addr.addr >= tail { + if old_alloc_addr >= tail { old_alloc_addr = *self.header.base_addr; } let mut ptr = old_alloc_addr; let mut res: Option = None; for _ in 0..self.alloc_max_walk { - assert!(ptr.addr < tail); + assert!(ptr < tail); let (desc_payload_size, desc_haddr) = { let desc = self.get_descriptor(ptr)?; - (desc.payload_size, desc.haddr) + (desc.payload_size as usize, desc.haddr) }; - let exit = if desc_payload_size == length { + let exit = if desc_payload_size == length as usize { // perfect match { - let mut header = self.get_header(ObjPtr::new(desc_haddr))?; - assert_eq!(header.payload_size, desc_payload_size); + let mut header = self.get_header(DiskAddress::from(desc_haddr))?; + assert_eq!(header.payload_size as usize, desc_payload_size); assert!(header.is_freed); header.write(|h| h.is_freed = false).unwrap(); } self.del_desc(ptr)?; true - } else if desc_payload_size > length + hsize + fsize { + } else if desc_payload_size > length as usize + hsize + fsize { // able to split { - let mut lheader = self.get_header(ObjPtr::new(desc_haddr))?; - assert_eq!(lheader.payload_size, desc_payload_size); + let mut lheader = self.get_header(DiskAddress::from(desc_haddr))?; + assert_eq!(lheader.payload_size as usize, desc_payload_size); assert!(lheader.is_freed); lheader .write(|h| { @@ -482,37 +475,40 @@ impl CompactSpaceInner { .unwrap(); } { - let mut lfooter = self.get_footer(ObjPtr::new(desc_haddr + hsize + length))?; + let mut lfooter = + self.get_footer(DiskAddress::from(desc_haddr + hsize + length as usize))?; //assert!(lfooter.payload_size == desc_payload_size); lfooter.write(|f| f.payload_size = length).unwrap(); } - let offset = desc_haddr + hsize + length + fsize; - let rpayload_size = desc_payload_size - length - fsize - hsize; + let offset = desc_haddr + hsize + length as usize + fsize; + let rpayload_size = desc_payload_size - length as usize - fsize - hsize; let rdesc_addr = self.new_desc()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; rdesc .write(|rd| { - rd.payload_size = rpayload_size; + rd.payload_size = rpayload_size as u64; rd.haddr = offset; }) .unwrap(); } { - let mut rheader = self.get_header(ObjPtr::new(offset))?; + let mut rheader = self.get_header(DiskAddress::from(offset))?; rheader .write(|rh| { rh.is_freed = true; - rh.payload_size = rpayload_size; + rh.payload_size = rpayload_size as u64; rh.desc_addr = rdesc_addr; }) .unwrap(); } { let mut rfooter = - self.get_footer(ObjPtr::new(offset + hsize + rpayload_size))?; - rfooter.write(|f| f.payload_size = rpayload_size).unwrap(); + self.get_footer(DiskAddress::from(offset + hsize + rpayload_size))?; + rfooter + .write(|f| f.payload_size = rpayload_size as u64) + .unwrap(); } self.del_desc(ptr)?; true @@ -521,11 +517,11 @@ impl CompactSpaceInner { }; if exit { self.header.alloc_addr.write(|r| *r = ptr).unwrap(); - res = Some(desc_haddr + hsize); + res = Some((desc_haddr + hsize) as u64); break; } - ptr.addr += dsize; - if ptr.addr >= tail { + ptr += dsize; + if ptr >= tail { ptr = *self.header.base_addr; } if ptr == old_alloc_addr { @@ -538,29 +534,29 @@ impl CompactSpaceInner { fn alloc_new(&mut self, length: u64) -> Result { let regn_size = 1 << self.regn_nbit; let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; - let mut offset = **self.header.compact_space_tail; + let mut offset = *self.header.compact_space_tail; self.header .compact_space_tail .write(|r| { // an item is always fully in one region - let rem = regn_size - (offset & (regn_size - 1)); - if rem < total_length { + let rem = regn_size - (offset & (regn_size - 1)).get(); + if rem < total_length as usize { offset += rem; - **r += rem; + *r += rem; } - **r += total_length + *r += total_length as usize }) .unwrap(); - let mut h = self.get_header(ObjPtr::new(offset))?; - let mut f = self.get_footer(ObjPtr::new(offset + CompactHeader::MSIZE + length))?; + let mut h = self.get_header(offset)?; + let mut f = self.get_footer(offset + CompactHeader::MSIZE as usize + length as usize)?; h.write(|h| { h.payload_size = length; h.is_freed = false; - h.desc_addr = ObjPtr::new(0); + h.desc_addr = DiskAddress::null(); }) .unwrap(); f.write(|f| f.payload_size = length).unwrap(); - Ok(offset + CompactHeader::MSIZE) + Ok((offset + CompactHeader::MSIZE as usize).0.unwrap().get() as u64) } } @@ -597,7 +593,7 @@ impl { fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; - let ptr: ObjPtr = { + let ptr: DiskAddress = { let mut inner = self.inner.write().unwrap(); let addr = if let Some(addr) = inner.alloc_from_freed(size)? { addr @@ -605,13 +601,13 @@ impl inner.alloc_new(size)? }; - ObjPtr::new_from_addr(addr) + DiskAddress::from(addr as usize) }; let inner = self.inner.read().unwrap(); let mut u: ObjRef<'a, T> = inner.obj_cache.put(StoredView::item_to_obj( inner.compact_space.as_ref(), - ptr.addr(), + ptr.into(), size, item, )?); @@ -621,13 +617,13 @@ impl Ok(u) } - fn free_item(&mut self, ptr: ObjPtr) -> Result<(), ShaleError> { + fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { let mut inner = self.inner.write().unwrap(); inner.obj_cache.pop(ptr); - inner.free(ptr.addr()) + inner.free(ptr.unwrap().get() as u64) } - fn get_item(&self, ptr: ObjPtr) -> Result, ShaleError> { + fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { let inner = { let inner = self.inner.read().unwrap(); let obj_ref = inner @@ -641,15 +637,15 @@ impl inner }; - if ptr.addr() < CompactSpaceHeader::MSIZE { + if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { return Err(ShaleError::InvalidAddressLength { - expected: CompactSpaceHeader::MSIZE, - found: ptr.addr(), + expected: DiskAddress::from(CompactSpaceHeader::MSIZE as usize), + found: ptr.0.unwrap().get() as u64, }); } let payload_size = inner - .get_header(ObjPtr::new(ptr.addr() - CompactHeader::MSIZE))? + .get_header(ptr - CompactHeader::MSIZE as usize)? .payload_size; Ok(inner.obj_cache.put(inner.get_data_ref(ptr, payload_size)?)) @@ -688,7 +684,7 @@ mod tests { } impl Storable for Hash { - fn hydrate(addr: u64, mem: &T) -> Result { + fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -715,22 +711,26 @@ mod tests { #[test] fn test_space_item() { - const META_SIZE: u64 = 0x10000; - const COMPACT_SIZE: u64 = 0x10000; - const RESERVED: u64 = 0x1000; + let meta_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); + let compact_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); + let reserved: DiskAddress = 0x1000.into(); - let mut dm = DynamicMem::new(META_SIZE, 0x0); + let mut dm = DynamicMem::new(meta_size.get() as u64, 0x0); // initialize compact space - let compact_header: ObjPtr = ObjPtr::new_from_addr(0x1); + let compact_header = DiskAddress::from(0x1); dm.write( - compact_header.addr(), - &crate::to_dehydrated(&CompactSpaceHeader::new(RESERVED, RESERVED)).unwrap(), + compact_header.unwrap().get(), + &crate::to_dehydrated(&CompactSpaceHeader::new( + reserved.0.unwrap(), + reserved.0.unwrap(), + )) + .unwrap(), ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); let mem_meta = Arc::new(dm); - let mem_payload = Arc::new(DynamicMem::new(COMPACT_SIZE, 0x1)); + let mem_payload = Arc::new(DynamicMem::new(compact_size.get() as u64, 0x1)); let cache: ObjCache = ObjCache::new(1); let space = @@ -740,9 +740,9 @@ mod tests { let data = b"hello world"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); let obj_ref = space.put_item(Hash(hash), 0).unwrap(); - assert_eq!(obj_ref.as_ptr().addr(), 4113); + assert_eq!(obj_ref.as_ptr(), DiskAddress::from(4113)); // create hash ptr from address and attempt to read dirty write. - let hash_ref = space.get_item(ObjPtr::new_from_addr(4113)).unwrap(); + let hash_ref = space.get_item(DiskAddress::from(4113)).unwrap(); // read before flush results in zeroed hash assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); // not cached @@ -750,29 +750,26 @@ mod tests { .cache .lock() .cached - .get(&ObjPtr::new_from_addr(4113)) + .get(&DiskAddress::from(4113)) .is_none()); // pinned assert!(obj_ref .cache .lock() .pinned - .get(&ObjPtr::new_from_addr(4113)) + .get(&DiskAddress::from(4113)) .is_some()); // dirty assert!(obj_ref .cache .lock() .dirty - .get(&ObjPtr::new_from_addr(4113)) + .get(&DiskAddress::from(4113)) .is_some()); drop(obj_ref); // write is visible assert_eq!( - space - .get_item(ObjPtr::new_from_addr(4113)) - .unwrap() - .as_ref(), + space.get_item(DiskAddress::from(4113)).unwrap().as_ref(), hash ); } diff --git a/shale/src/disk_address.rs b/shale/src/disk_address.rs new file mode 100644 index 000000000000..7eb039d84fc4 --- /dev/null +++ b/shale/src/disk_address.rs @@ -0,0 +1,184 @@ +use std::hash::Hash; +use std::num::NonZeroUsize; +use std::ops::{Deref, DerefMut}; + +use crate::{CachedStore, ShaleError, Storable}; + +/// The virtual disk address of an object +#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq)] +pub struct DiskAddress(pub Option); + +impl Deref for DiskAddress { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DiskAddress { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl DiskAddress { + /// Return a None DiskAddress + pub fn null() -> Self { + DiskAddress(None) + } + + /// Indicate whether the DiskAddress is null + pub fn is_null(&self) -> bool { + self.is_none() + } + + /// Convert a NonZeroUsize to a DiskAddress + pub fn new(addr: NonZeroUsize) -> Self { + DiskAddress(Some(addr)) + } + + /// Get the little endian bytes for a DiskAddress for storage + pub fn to_le_bytes(&self) -> [u8; 8] { + self.0.map(|v| v.get()).unwrap_or_default().to_le_bytes() + } + + /// Get the inner usize, using 0 if None + pub fn get(&self) -> usize { + self.0.map(|v| v.get()).unwrap_or_default() + } +} + +/// Convert from a usize to a DiskAddress +impl From for DiskAddress { + fn from(value: usize) -> Self { + DiskAddress(NonZeroUsize::new(value)) + } +} + +/// Convert from a serialized le_bytes to a DiskAddress +impl From<[u8; 8]> for DiskAddress { + fn from(value: [u8; 8]) -> Self { + Self::from(usize::from_le_bytes(value)) + } +} + +/// Convert from a slice of bytes to a DiskAddress +/// panics if the slice isn't 8 bytes; used for +/// serialization from disk +impl From<&[u8]> for DiskAddress { + fn from(value: &[u8]) -> Self { + let bytes: [u8; 8] = value.try_into().unwrap(); + bytes.into() + } +} + +/// Convert a DiskAddress into a usize +/// TODO: panic if the DiskAddress is None +impl From for usize { + fn from(value: DiskAddress) -> usize { + value.get() + } +} + +/// Add two disk addresses; +/// TODO: panic if either are null +impl std::ops::Add for DiskAddress { + type Output = DiskAddress; + + fn add(self, rhs: DiskAddress) -> Self::Output { + self + rhs.get() + } +} + +/// Add a usize to a DiskAddress +/// TODO: panic if the DiskAddress is null +impl std::ops::Add for DiskAddress { + type Output = DiskAddress; + + fn add(self, rhs: usize) -> Self::Output { + (self.get() + rhs).into() + } +} + +/// subtract one disk address from another +/// TODO: panic if either are null +impl std::ops::Sub for DiskAddress { + type Output = DiskAddress; + + fn sub(self, rhs: DiskAddress) -> Self::Output { + self - rhs.get() + } +} + +/// subtract a usize from a diskaddress +/// panic if the DiskAddress is null +impl std::ops::Sub for DiskAddress { + type Output = DiskAddress; + + fn sub(self, rhs: usize) -> Self::Output { + (self.get() - rhs).into() + } +} + +impl std::ops::AddAssign for DiskAddress { + fn add_assign(&mut self, rhs: DiskAddress) { + *self = *self + rhs; + } +} + +impl std::ops::AddAssign for DiskAddress { + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +} + +impl std::ops::SubAssign for DiskAddress { + fn sub_assign(&mut self, rhs: DiskAddress) { + *self = *self - rhs; + } +} + +impl std::ops::SubAssign for DiskAddress { + fn sub_assign(&mut self, rhs: usize) { + *self = *self - rhs; + } +} + +impl std::ops::BitAnd for DiskAddress { + type Output = DiskAddress; + + fn bitand(self, rhs: usize) -> Self::Output { + (self.get() & rhs).into() + } +} + +impl DiskAddress { + const MSIZE: u64 = 8; +} + +impl Storable for DiskAddress { + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + use std::io::{Cursor, Write}; + Cursor::new(to).write_all(&self.0.unwrap().get().to_le_bytes())?; + Ok(()) + } + + fn hydrate(addr: usize, mem: &U) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + let addrdyn = raw.deref(); + let addrvec = addrdyn.as_deref(); + Ok(Self(NonZeroUsize::new(usize::from_le_bytes( + addrvec.try_into().unwrap(), + )))) + } +} diff --git a/shale/src/lib.rs b/shale/src/lib.rs index adb8c081abd1..1e8bc1d05163 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -1,8 +1,7 @@ +use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; -use std::fmt::{self, Debug, Display, Formatter}; -use std::hash::Hash; -use std::hash::Hasher; +use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; @@ -12,22 +11,23 @@ use thiserror::Error; pub mod cached; pub mod compact; +pub mod disk_address; #[derive(Debug, Error)] #[non_exhaustive] pub enum ShaleError { #[error("obj invalid: {addr:?} obj: {obj_type:?} error: {error:?}")] InvalidObj { - addr: u64, + addr: usize, obj_type: &'static str, error: &'static str, }, #[error("invalid address length expected: {expected:?} found: {found:?})")] - InvalidAddressLength { expected: u64, found: u64 }, + InvalidAddressLength { expected: DiskAddress, found: u64 }, #[error("invalid node type")] InvalidNodeType, #[error("failed to create view: offset: {offset:?} size: {size:?}")] - InvalidCacheView { offset: u64, size: u64 }, + InvalidCacheView { offset: usize, size: u64 }, #[error("io error: {0}")] Io(#[from] std::io::Error), } @@ -79,77 +79,18 @@ pub trait CachedStore: Debug + Send + Sync { /// directly accessible. fn get_view( &self, - offset: u64, + offset: usize, length: u64, ) -> Option>>>; /// Returns a handle that allows shared access to the store. fn get_shared(&self) -> Box>; /// Write the `change` to the portion of the linear space starting at `offset`. The change /// should be immediately visible to all `CachedView` associated to this linear space. - fn write(&mut self, offset: u64, change: &[u8]); + fn write(&mut self, offset: usize, change: &[u8]); /// Returns the identifier of this storage space. fn id(&self) -> SpaceId; } -/// Opaque typed pointer in the 64-bit virtual addressable space. -#[repr(C)] -#[derive(Debug)] -pub struct ObjPtr { - pub(crate) addr: u64, - phantom: PhantomData, -} - -impl std::cmp::PartialEq for ObjPtr { - fn eq(&self, other: &ObjPtr) -> bool { - self.addr == other.addr - } -} - -impl Eq for ObjPtr {} -impl Copy for ObjPtr {} -impl Clone for ObjPtr { - fn clone(&self) -> Self { - *self - } -} - -impl Hash for ObjPtr { - fn hash(&self, state: &mut H) { - self.addr.hash(state) - } -} - -impl Display for ObjPtr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[ObjPtr addr={:08x}]", self.addr) - } -} - -impl ObjPtr { - pub fn null() -> Self { - Self::new(0) - } - pub fn is_null(&self) -> bool { - self.addr == 0 - } - pub fn addr(&self) -> u64 { - self.addr - } - - #[inline(always)] - pub(crate) fn new(addr: u64) -> Self { - ObjPtr { - addr, - phantom: PhantomData, - } - } - - #[inline(always)] - pub fn new_from_addr(addr: u64) -> Self { - Self::new(addr) - } -} - /// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object /// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to /// turn a `TypedView` into an [ObjRef]. @@ -157,7 +98,7 @@ pub trait TypedView: std::fmt::Debug + Deref + Send + Sync { /// Get the offset of the initial byte in the linear space. - fn get_offset(&self) -> u64; + fn get_offset(&self) -> usize; /// Access it as a [CachedStore] object. fn get_mem_store(&self) -> &dyn CachedStore; /// Access it as a mutable CachedStore object @@ -188,8 +129,8 @@ pub struct Obj { impl Obj { #[inline(always)] - pub fn as_ptr(&self) -> ObjPtr { - ObjPtr::::new(self.value.get_offset()) + pub fn as_ptr(&self) -> DiskAddress { + DiskAddress(NonZeroUsize::new(self.value.get_offset())) } /// Write to the underlying object. Returns `Some(())` on success. @@ -297,11 +238,11 @@ impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> { /// items could be retrieved or dropped. pub trait ShaleStore { /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. - fn get_item(&'_ self, ptr: ObjPtr) -> Result, ShaleError>; + fn get_item(&'_ self, ptr: DiskAddress) -> Result, ShaleError>; /// Allocate a new item. fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; /// Free an item and recycle its space when applicable. - fn free_item(&mut self, item: ObjPtr) -> Result<(), ShaleError>; + fn free_item(&mut self, item: DiskAddress) -> Result<(), ShaleError>; /// Flush all dirty writes. fn flush_dirty(&self) -> Option<()>; } @@ -312,7 +253,7 @@ pub trait ShaleStore { pub trait Storable { fn dehydrated_len(&self) -> u64; fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>; - fn hydrate(addr: u64, mem: &T) -> Result + fn hydrate(addr: usize, mem: &T) -> Result where Self: Sized; fn is_mem_mapped(&self) -> bool { @@ -330,7 +271,7 @@ pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { pub struct StoredView { decoded: T, mem: Box>, - offset: u64, + offset: usize, len_limit: u64, } @@ -358,7 +299,7 @@ impl Deref for StoredView { } impl TypedView for StoredView { - fn get_offset(&self) -> u64 { + fn get_offset(&self) -> usize { self.offset } @@ -393,7 +334,7 @@ impl TypedView for StoredView { impl StoredView { #[inline(always)] - fn new(offset: u64, len_limit: u64, space: &U) -> Result { + fn new(offset: usize, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; Ok(Self { offset, @@ -405,7 +346,7 @@ impl StoredView { #[inline(always)] fn from_hydrated( - offset: u64, + offset: usize, len_limit: u64, decoded: T, space: &dyn CachedStore, @@ -421,11 +362,11 @@ impl StoredView { #[inline(always)] pub fn ptr_to_obj( store: &U, - ptr: ObjPtr, + ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { Ok(Obj::from_typed_view(Box::new(Self::new( - ptr.addr(), + ptr.get(), len_limit, store, )?))) @@ -434,7 +375,7 @@ impl StoredView { #[inline(always)] pub fn item_to_obj( store: &dyn CachedStore, - addr: u64, + addr: usize, len_limit: u64, decoded: T, ) -> Result, ShaleError> { @@ -446,7 +387,7 @@ impl StoredView { impl StoredView { fn new_from_slice( - offset: u64, + offset: usize, len_limit: u64, decoded: T, space: &dyn CachedStore, @@ -461,7 +402,7 @@ impl StoredView { pub fn slice( s: &Obj, - offset: u64, + offset: usize, length: u64, decoded: U, ) -> Result, ShaleError> { @@ -486,41 +427,11 @@ impl StoredView { } } -impl ObjPtr { - const MSIZE: u64 = 8; -} - -impl Storable for ObjPtr { - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - use std::io::{Cursor, Write}; - Cursor::new(to).write_all(&self.addr().to_le_bytes())?; - Ok(()) - } - - fn hydrate(addr: u64, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let addrdyn = raw.deref(); - let addrvec = addrdyn.as_deref(); - Ok(Self::new_from_addr(u64::from_le_bytes( - addrvec.try_into().unwrap(), - ))) - } -} - #[derive(Debug)] pub struct ObjCacheInner { - cached: lru::LruCache, Obj>, - pinned: HashMap, bool>, - dirty: HashSet>, + cached: lru::LruCache>, + pinned: HashMap, + dirty: HashSet, } /// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. @@ -544,7 +455,7 @@ impl ObjCache { pub fn get<'a>( &self, inner: &mut ObjCacheInner, - ptr: ObjPtr, + ptr: DiskAddress, ) -> Result>, ShaleError> { if let Some(r) = inner.cached.pop(&ptr) { // insert and set to `false` if you can @@ -592,7 +503,7 @@ impl ObjCache { } #[inline(always)] - pub fn pop(&self, ptr: ObjPtr) { + pub fn pop(&self, ptr: DiskAddress) { let mut inner = self.lock(); if let Some(f) = inner.pinned.get_mut(&ptr) { *f = true From 4485c8911cdad6a231d20e91ef58c0edcab7e807 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 21 Aug 2023 16:40:39 -0400 Subject: [PATCH 0245/1053] Init root from Merkle (#205) --- firewood/benches/hashops.rs | 11 ++++------- firewood/src/db.rs | 8 +++++--- firewood/src/merkle.rs | 8 +++----- firewood/src/merkle_util.rs | 10 +++++----- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 954b1ea1eaad..b9993aaa3ac8 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -2,13 +2,9 @@ use std::ops::Deref; use bencher::{benchmark_group, benchmark_main, Bencher}; -use firewood::{ - merkle::{Merkle, Node, TrieHash, TRIE_HASH_LEN}, - storage::StoreRevMut, -}; +use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; use firewood_shale::{ - cached::PlainMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, Storable, - StoredView, + cached::PlainMem, disk_address::DiskAddress, CachedStore, Storable, StoredView, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; @@ -52,7 +48,8 @@ fn bench_insert(b: &mut Bencher) { .unwrap(); let mut merkle = Merkle::new(Box::new(store)); let mut root = DiskAddress::null(); - Merkle::>::init_root(&mut root, merkle.get_store()).unwrap(); + merkle.init_root(&mut root).unwrap(); + let mut rng = rand::rngs::StdRng::seed_from_u64(1234); const KEY_LEN: usize = 4; b.iter(|| { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e48defc4a050..c850f9092c3c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -729,14 +729,16 @@ impl Db { ) .unwrap(); + let merkle = Merkle::new(Box::new(merkle_space)); + if db_header_ref.acc_root.is_null() { let mut err = Ok(()); // create the sentinel node db_header_ref .write(|r| { err = (|| { - Merkle::::init_root(&mut r.acc_root, &merkle_space)?; - Merkle::::init_root(&mut r.kv_root, &merkle_space) + merkle.init_root(&mut r.acc_root)?; + merkle.init_root(&mut r.kv_root) })(); }) .unwrap(); @@ -745,7 +747,7 @@ impl Db { Ok(DbRev { header: db_header_ref, - merkle: Merkle::new(Box::new(merkle_space)), + merkle, }) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4d1eb41a73da..d528bc996eda 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -878,11 +878,9 @@ impl + Send + Sync> Merkle { Self { store } } - pub fn init_root( - root: &mut DiskAddress, - store: &dyn ShaleStore, - ) -> Result<(), MerkleError> { - *root = store + pub fn init_root(&self, root: &mut DiskAddress) -> Result<(), MerkleError> { + *root = self + .store .put_item( Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 4d6f4cc801c2..f4995a5e951b 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -143,9 +143,9 @@ pub fn new_merkle( shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); let mut root = DiskAddress::null(); - Merkle::>::init_root(&mut root, &space).unwrap(); - MerkleSetup { - root, - merkle: Merkle::new(Box::new(space)), - } + + let merkle = Merkle::new(Box::new(space)); + merkle.init_root(&mut root).unwrap(); + + MerkleSetup { root, merkle } } From 906034d95f65982a5d717a7a77c17341d276a810 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Aug 2023 16:11:11 -0700 Subject: [PATCH 0246/1053] Implement Nibbles to reduce memory allocations (#207) --- firewood/src/lib.rs | 1 + firewood/src/merkle.rs | 54 +++++++----- firewood/src/nibbles.rs | 190 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 firewood/src/nibbles.rs diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 02dc372a996a..103bc37a94f6 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -203,6 +203,7 @@ pub mod storage; pub mod api; pub(crate) mod config; +pub mod nibbles; pub mod service; pub mod v2; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d528bc996eda..b29b49adc95f 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::proof::Proof; +use crate::{nibbles::Nibbles, proof::Proof}; use enum_as_inner::EnumAsInner; use sha3::Digest; use shale::{disk_address::DiskAddress, CachedStore, ObjRef, ShaleError, ShaleStore, Storable}; @@ -1170,10 +1170,10 @@ impl + Send + Sync> Merkle { let mut deleted = Vec::new(); let mut parents = Vec::new(); - // TODO: Explain why this always starts with a 0 chunk - // I think this may have to do with avoiding moving the root - let mut chunked_key = vec![0]; - chunked_key.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + // we use Nibbles::<1> so that 1 zero nibble is at the front + // this is for the sentinel node, which avoids moving the root + // and always only has one child + let key_nibbles = Nibbles::<1>(key.as_ref()); let mut next_node = Some(self.get_node(root)?); let mut nskip = 0; @@ -1184,7 +1184,7 @@ impl + Send + Sync> Merkle { let mut val = Some(val); // walk down the merkle tree starting from next_node, currently the root - for (key_nib_offset, key_nib) in chunked_key.iter().enumerate() { + for (key_nib_offset, key_nib) in key_nibbles.iter().enumerate() { // special handling for extension nodes if nskip > 0 { nskip -= 1; @@ -1200,21 +1200,21 @@ impl + Send + Sync> Merkle { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done - NodeType::Branch(n) => match n.chd[*key_nib as usize] { + NodeType::Branch(n) => match n.chd[key_nib as usize] { Some(c) => c, None => { // insert the leaf to the empty slot // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(chunked_key[key_nib_offset + 1..].to_vec()), + PartialPath(key_nibbles.skip(key_nib_offset + 1).iter().collect()), Data(val.take().unwrap()), ))))? .as_ptr(); // set the current child to point to this leaf node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); - uu.chd[*key_nib as usize] = Some(leaf_ptr); + uu.chd[key_nib as usize] = Some(leaf_ptr); u.rehash(); }) .unwrap(); @@ -1226,10 +1226,11 @@ impl + Send + Sync> Merkle { // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); + let rem_path = key_nibbles.skip(key_nib_offset).iter().collect::>(); self.split( node, &mut parents, - &chunked_key[key_nib_offset..], + &rem_path, n_path, n_value, val.take().unwrap(), @@ -1241,10 +1242,12 @@ impl + Send + Sync> Merkle { let n_path = n.0.to_vec(); let n_ptr = n.1; nskip = n_path.len() - 1; + let rem_path = key_nibbles.skip(key_nib_offset).iter().collect::>(); + if let Some(v) = self.split( node, &mut parents, - &chunked_key[key_nib_offset..], + &rem_path, n_path, None, val.take().unwrap(), @@ -1263,7 +1266,7 @@ impl + Send + Sync> Merkle { } }; // push another parent, and follow the next pointer - parents.push((node, *key_nib)); + parents.push((node, key_nib)); next_node = Some(self.get_node(next_node_ptr)?); } if val.is_some() { @@ -1835,8 +1838,7 @@ impl + Send + Sync> Merkle { K: AsRef<[u8]>, T: ValueTransformer, { - let mut chunks = Vec::new(); - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + let key_nibbles = Nibbles::<0>(key.as_ref()); let mut proofs: HashMap<[u8; TRIE_HASH_LEN], Vec> = HashMap::new(); if root.is_null() { @@ -1857,29 +1859,37 @@ impl + Send + Sync> Merkle { let mut nskip = 0; let mut nodes: Vec = Vec::new(); - for (i, nib) in chunks.iter().enumerate() { + for (i, nib) in key_nibbles.iter().enumerate() { if nskip > 0 { nskip -= 1; continue; } nodes.push(u_ref.as_ptr()); let next_ptr: DiskAddress = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[*nib as usize] { + NodeType::Branch(n) => match n.chd[nib as usize] { Some(c) => c, None => break, }, NodeType::Leaf(_) => break, NodeType::Extension(n) => { + // the key passed in must match the entire remainder of this + // extension node, otherwise we break out let n_path = &*n.0; - let remaining_path = &chunks[i..]; - if remaining_path.len() < n_path.len() - || &remaining_path[..n_path.len()] != n_path + let remaining_path = key_nibbles.skip(i); + if remaining_path.len() < n_path.len() { + // all bytes aren't there + break; + } + if !remaining_path + .iter() + .take(n_path.len()) + .eq(n_path.iter().cloned()) { + // contents aren't the same break; - } else { - nskip = n_path.len() - 1; - n.1 } + nskip = n_path.len() - 1; + n.1 } }; u_ref = self.get_node(next_ptr)?; diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs new file mode 100644 index 000000000000..93d23605dde0 --- /dev/null +++ b/firewood/src/nibbles.rs @@ -0,0 +1,190 @@ +use std::ops::Index; + +static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + +/// Nibbles is a newtype that contains only a reference to a [u8], and produces +/// nibbles. Nibbles can be indexed using nib\[x\] or you can get an iterator +/// with iter() +/// +/// Nibbles can be constructed with a number of leading zeroes. This is used +/// in firewood because there is a sentinel node, so we always want the first +/// byte to be 0 +/// +/// When creating a Nibbles object, use the syntax `Nibbles::(r)` where +/// `N` is the number of leading zero bytes you need and `r` is a reference to +/// a [u8] +/// +/// # Examples +/// +/// ``` +/// # use firewood::nibbles; +/// # fn main() { +/// let nib = nibbles::Nibbles::<0>(&[0x56, 0x78]); +/// assert_eq!(nib.iter().collect::>(), [0x5, 0x6, 0x7, 0x8]); +/// +/// // nibbles can be efficiently advanced without rendering the +/// // intermediate values +/// assert_eq!(nib.skip(3).iter().collect::>(), [0x8]); +/// +/// // nibbles can also be indexed +/// +/// assert_eq!(nib[1], 0x6); +/// # } +/// ``` +#[derive(Debug)] +pub struct Nibbles<'a, const LEADING_ZEROES: usize>(pub &'a [u8]); + +impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROES> { + type Output = u8; + + fn index(&self, index: usize) -> &Self::Output { + match index { + _ if index < LEADING_ZEROES => &NIBBLES[0], + _ if (index - LEADING_ZEROES) % 2 == 0 => { + &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] >> 4) as usize] + } + _ => &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] & 0xf) as usize], + } + } +} + +impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { + #[must_use] + pub fn iter(&self) -> NibblesIterator<'_, LEADING_ZEROES> { + NibblesIterator { data: self, pos: 0 } + } + + /// Efficently skip some values + #[must_use] + pub fn skip(&self, at: usize) -> NibblesSlice<'a> { + assert!(at >= LEADING_ZEROES, "Cannot split before LEADING_ZEROES (requested split at {at} is less than the {LEADING_ZEROES} leading zero(es)"); + NibblesSlice { + skipfirst: (at - LEADING_ZEROES) % 2 != 0, + nibbles: Nibbles(&self.0[(at - LEADING_ZEROES) / 2..]), + } + } + + #[must_use] + pub fn len(&self) -> usize { + LEADING_ZEROES + 2 * self.0.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + LEADING_ZEROES == 0 && self.0.is_empty() + } +} + +/// NibblesSlice is created by [Nibbles::skip]. This is +/// used to create an interator that starts at some particular +/// nibble +#[derive(Debug)] +pub struct NibblesSlice<'a> { + nibbles: Nibbles<'a, 0>, + skipfirst: bool, +} + +impl<'a> NibblesSlice<'a> { + /// Returns an iterator over this subset of nibbles + pub fn iter(&self) -> NibblesIterator<'_, 0> { + let pos = if self.skipfirst { 1 } else { 0 }; + NibblesIterator { + data: &self.nibbles, + pos, + } + } + + pub fn len(&self) -> usize { + self.nibbles.len() - if self.skipfirst { 1 } else { 0 } + } + + pub fn is_empty(&self) -> bool { + self.len() > 0 + } +} + +/// An interator returned by [Nibbles::iter] or [NibblesSlice::iter]. +/// See their documentation for details. +#[derive(Debug)] +pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { + data: &'a Nibbles<'a, LEADING_ZEROES>, + pos: usize, +} + +impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_ZEROES> { + type Item = u8; + + fn next(&mut self) -> Option { + let result = if self.pos >= LEADING_ZEROES + self.data.0.len() * 2 { + None + } else { + Some(self.data[self.pos]) + }; + self.pos += 1; + result + } +} + +#[cfg(test)] +mod test { + use super::*; + static TEST_BYTES: [u8; 4] = [0xdeu8, 0xad, 0xbe, 0xef]; + #[test] + fn happy_regular_nibbles() { + let nib = Nibbles::<0>(&TEST_BYTES); + let expected = [0xdu8, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + for v in expected.into_iter().enumerate() { + assert_eq!(nib[v.0], v.1, "{v:?}"); + } + } + #[test] + fn leadingzero_nibbles_index() { + let nib = Nibbles::<1>(&TEST_BYTES); + let expected = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + for v in expected.into_iter().enumerate() { + assert_eq!(nib[v.0], v.1, "{v:?}"); + } + } + #[test] + fn leading_zero_nibbles_iter() { + let nib = Nibbles::<1>(&TEST_BYTES); + let expected: [u8; 9] = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + expected.into_iter().eq(nib.iter()); + } + + #[test] + fn skip_zero() { + let nib = Nibbles::<0>(&TEST_BYTES); + let slice = nib.skip(0); + assert!(nib.iter().eq(slice.iter())); + } + #[test] + fn skip_one() { + let nib = Nibbles::<0>(&TEST_BYTES); + let slice = nib.skip(1); + assert!(nib.iter().skip(1).eq(slice.iter())); + } + #[test] + fn skip_skips_zeroes() { + let nib = Nibbles::<1>(&TEST_BYTES); + let slice = nib.skip(1); + assert!(nib.iter().skip(1).eq(slice.iter())); + } + #[test] + #[should_panic] + fn test_out_of_bounds_panics() { + let nib = Nibbles::<0>(&TEST_BYTES); + let _ = nib[8]; + } + #[test] + fn test_last_nibble() { + let nib = Nibbles::<0>(&TEST_BYTES); + assert_eq!(nib[7], 0xf); + } + #[test] + #[should_panic] + fn test_skip_before_zeroes_panics() { + let nib = Nibbles::<1>(&TEST_BYTES); + let _ = nib.skip(0); + } +} From a981b6bceae067093a51f2c76bf0080cc8a68d08 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Aug 2023 10:56:26 -0400 Subject: [PATCH 0247/1053] Nibbles improvements (#208) --- firewood/src/merkle.rs | 16 +++---- firewood/src/nibbles.rs | 102 ++++++++++++++++++---------------------- 2 files changed, 51 insertions(+), 67 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b29b49adc95f..fa50c0a6687e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1207,7 +1207,7 @@ impl + Send + Sync> Merkle { // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(key_nibbles.skip(key_nib_offset + 1).iter().collect()), + PartialPath(key_nibbles.iter().skip(key_nib_offset + 1).collect()), Data(val.take().unwrap()), ))))? .as_ptr(); @@ -1226,7 +1226,7 @@ impl + Send + Sync> Merkle { // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); - let rem_path = key_nibbles.skip(key_nib_offset).iter().collect::>(); + let rem_path = key_nibbles.iter().skip(key_nib_offset).collect::>(); self.split( node, &mut parents, @@ -1242,7 +1242,7 @@ impl + Send + Sync> Merkle { let n_path = n.0.to_vec(); let n_ptr = n.1; nskip = n_path.len() - 1; - let rem_path = key_nibbles.skip(key_nib_offset).iter().collect::>(); + let rem_path = key_nibbles.iter().skip(key_nib_offset).collect::>(); if let Some(v) = self.split( node, @@ -1875,16 +1875,12 @@ impl + Send + Sync> Merkle { // the key passed in must match the entire remainder of this // extension node, otherwise we break out let n_path = &*n.0; - let remaining_path = key_nibbles.skip(i); - if remaining_path.len() < n_path.len() { + let remaining_path = key_nibbles.iter().skip(i); + if remaining_path.size_hint().0 < n_path.len() { // all bytes aren't there break; } - if !remaining_path - .iter() - .take(n_path.len()) - .eq(n_path.iter().cloned()) - { + if !remaining_path.take(n_path.len()).eq(n_path.iter().cloned()) { // contents aren't the same break; } diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 93d23605dde0..6610ff60b7af 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -24,7 +24,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// /// // nibbles can be efficiently advanced without rendering the /// // intermediate values -/// assert_eq!(nib.skip(3).iter().collect::>(), [0x8]); +/// assert_eq!(nib.iter().skip(3).collect::>(), [0x8]); /// /// // nibbles can also be indexed /// @@ -54,16 +54,6 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { NibblesIterator { data: self, pos: 0 } } - /// Efficently skip some values - #[must_use] - pub fn skip(&self, at: usize) -> NibblesSlice<'a> { - assert!(at >= LEADING_ZEROES, "Cannot split before LEADING_ZEROES (requested split at {at} is less than the {LEADING_ZEROES} leading zero(es)"); - NibblesSlice { - skipfirst: (at - LEADING_ZEROES) % 2 != 0, - nibbles: Nibbles(&self.0[(at - LEADING_ZEROES) / 2..]), - } - } - #[must_use] pub fn len(&self) -> usize { LEADING_ZEROES + 2 * self.0.len() @@ -75,35 +65,7 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { } } -/// NibblesSlice is created by [Nibbles::skip]. This is -/// used to create an interator that starts at some particular -/// nibble -#[derive(Debug)] -pub struct NibblesSlice<'a> { - nibbles: Nibbles<'a, 0>, - skipfirst: bool, -} - -impl<'a> NibblesSlice<'a> { - /// Returns an iterator over this subset of nibbles - pub fn iter(&self) -> NibblesIterator<'_, 0> { - let pos = if self.skipfirst { 1 } else { 0 }; - NibblesIterator { - data: &self.nibbles, - pos, - } - } - - pub fn len(&self) -> usize { - self.nibbles.len() - if self.skipfirst { 1 } else { 0 } - } - - pub fn is_empty(&self) -> bool { - self.len() > 0 - } -} - -/// An interator returned by [Nibbles::iter] or [NibblesSlice::iter]. +/// An interator returned by [Nibbles::iter] /// See their documentation for details. #[derive(Debug)] pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { @@ -123,12 +85,23 @@ impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_Z self.pos += 1; result } + + fn nth(&mut self, n: usize) -> Option { + self.pos += n; + self.next() + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.data.len() - self.pos; + (remaining, Some(remaining)) + } } #[cfg(test)] mod test { - use super::*; + use super::Nibbles; static TEST_BYTES: [u8; 4] = [0xdeu8, 0xad, 0xbe, 0xef]; + #[test] fn happy_regular_nibbles() { let nib = Nibbles::<0>(&TEST_BYTES); @@ -137,6 +110,7 @@ mod test { assert_eq!(nib[v.0], v.1, "{v:?}"); } } + #[test] fn leadingzero_nibbles_index() { let nib = Nibbles::<1>(&TEST_BYTES); @@ -155,36 +129,50 @@ mod test { #[test] fn skip_zero() { let nib = Nibbles::<0>(&TEST_BYTES); - let slice = nib.skip(0); - assert!(nib.iter().eq(slice.iter())); - } - #[test] - fn skip_one() { - let nib = Nibbles::<0>(&TEST_BYTES); - let slice = nib.skip(1); - assert!(nib.iter().skip(1).eq(slice.iter())); + assert!(nib.iter().skip(0).eq(nib.iter())); } + #[test] fn skip_skips_zeroes() { - let nib = Nibbles::<1>(&TEST_BYTES); - let slice = nib.skip(1); - assert!(nib.iter().skip(1).eq(slice.iter())); + let nib1 = Nibbles::<1>(&TEST_BYTES); + let nib0 = Nibbles::<0>(&TEST_BYTES); + assert!(nib1.iter().skip(1).eq(nib0.iter())); } + #[test] #[should_panic] - fn test_out_of_bounds_panics() { + fn out_of_bounds_panics() { let nib = Nibbles::<0>(&TEST_BYTES); let _ = nib[8]; } + + #[test] + fn skip_before_zeroes() { + let nib = Nibbles::<1>(&TEST_BYTES); + assert!(nib.iter().skip(0).eq(nib.iter())); + } + #[test] - fn test_last_nibble() { + fn last_nibble() { let nib = Nibbles::<0>(&TEST_BYTES); assert_eq!(nib[7], 0xf); } + + #[test] + fn size_hint_0() { + let nib = Nibbles::<0>(&TEST_BYTES); + let mut nib_iter = nib.iter(); + assert_eq!((8, Some(8)), nib_iter.size_hint()); + let _ = nib_iter.next(); + assert_eq!((7, Some(7)), nib_iter.size_hint()); + } + #[test] - #[should_panic] - fn test_skip_before_zeroes_panics() { + fn size_hint_1() { let nib = Nibbles::<1>(&TEST_BYTES); - let _ = nib.skip(0); + let mut nib_iter = nib.iter(); + assert_eq!((9, Some(9)), nib_iter.size_hint()); + let _ = nib_iter.next(); + assert_eq!((8, Some(8)), nib_iter.size_hint()); } } From da6bef32e71f119036cb18d5a5e59e75888630ae Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 22 Aug 2023 12:27:52 -0400 Subject: [PATCH 0248/1053] Return DiskAddress instead of mutating (#206) --- firewood/benches/hashops.rs | 3 +-- firewood/src/db.rs | 5 +++-- firewood/src/merkle.rs | 10 ++++------ firewood/src/merkle_util.rs | 3 +-- shale/src/compact.rs | 37 ++++++++++++++++++++----------------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index b9993aaa3ac8..a285fc304ade 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -47,8 +47,7 @@ fn bench_insert(b: &mut Bencher) { ) .unwrap(); let mut merkle = Merkle::new(Box::new(store)); - let mut root = DiskAddress::null(); - merkle.init_root(&mut root).unwrap(); + let root = merkle.init_root().unwrap(); let mut rng = rand::rngs::StdRng::seed_from_u64(1234); const KEY_LEN: usize = 4; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c850f9092c3c..13edd5bc4d50 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -737,8 +737,9 @@ impl Db { db_header_ref .write(|r| { err = (|| { - merkle.init_root(&mut r.acc_root)?; - merkle.init_root(&mut r.kv_root) + r.acc_root = merkle.init_root()?; + r.kv_root = merkle.init_root()?; + Ok(()) })(); }) .unwrap(); diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fa50c0a6687e..7ea1c157c557 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -878,9 +878,8 @@ impl + Send + Sync> Merkle { Self { store } } - pub fn init_root(&self, root: &mut DiskAddress) -> Result<(), MerkleError> { - *root = self - .store + pub fn init_root(&self) -> Result { + self.store .put_item( Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], @@ -889,9 +888,8 @@ impl + Send + Sync> Merkle { })), Node::max_branch_node_size(), ) - .map_err(MerkleError::Shale)? - .as_ptr(); - Ok(()) + .map_err(MerkleError::Shale) + .map(|node| node.as_ptr()) } pub fn get_store(&self) -> &dyn ShaleStore { diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index f4995a5e951b..ed7daf12a652 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -142,10 +142,9 @@ pub fn new_merkle( let space = shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); - let mut root = DiskAddress::null(); let merkle = Merkle::new(Box::new(space)); - merkle.init_root(&mut root).unwrap(); + let root = merkle.init_root().unwrap(); MerkleSetup { root, merkle } } diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 6324d1968663..7f1ad4816218 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -558,6 +558,16 @@ impl CompactSpaceInner { f.write(|f| f.payload_size = length).unwrap(); Ok((offset + CompactHeader::MSIZE as usize).0.unwrap().get() as u64) } + + fn alloc(&mut self, length: u64) -> Result { + self.alloc_from_freed(length).and_then(|addr| { + if let Some(addr) = addr { + Ok(addr) + } else { + self.alloc_new(length) + } + }) + } } #[derive(Debug)] @@ -591,27 +601,20 @@ impl CompactSpace { impl ShaleStore for CompactSpace { - fn put_item<'a>(&'a self, item: T, extra: u64) -> Result, ShaleError> { + fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; - let ptr: DiskAddress = { - let mut inner = self.inner.write().unwrap(); - let addr = if let Some(addr) = inner.alloc_from_freed(size)? { - addr - } else { - inner.alloc_new(size)? - }; + let addr = self.inner.write().unwrap().alloc(size)?; - DiskAddress::from(addr as usize) - }; - let inner = self.inner.read().unwrap(); + let mut u = { + let inner = self.inner.read().unwrap(); + let compact_space = inner.compact_space.as_ref(); + let view = + StoredView::item_to_obj(compact_space, addr.try_into().unwrap(), size, item)?; - let mut u: ObjRef<'a, T> = inner.obj_cache.put(StoredView::item_to_obj( - inner.compact_space.as_ref(), - ptr.into(), - size, - item, - )?); + inner.obj_cache.put(view) + }; + // should this use a `?` instead of `unwrap`? u.write(|_| {}).unwrap(); Ok(u) From 107e0441e9082c373411a6255ffb79de0e3dfee7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Aug 2023 15:42:24 -0400 Subject: [PATCH 0249/1053] Make Nibbles newtype wrapped non-pub (#209) --- firewood/src/merkle.rs | 4 ++-- firewood/src/nibbles.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7ea1c157c557..f65ba2594bfa 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1171,7 +1171,7 @@ impl + Send + Sync> Merkle { // we use Nibbles::<1> so that 1 zero nibble is at the front // this is for the sentinel node, which avoids moving the root // and always only has one child - let key_nibbles = Nibbles::<1>(key.as_ref()); + let key_nibbles = Nibbles::<1>::new(key.as_ref()); let mut next_node = Some(self.get_node(root)?); let mut nskip = 0; @@ -1836,7 +1836,7 @@ impl + Send + Sync> Merkle { K: AsRef<[u8]>, T: ValueTransformer, { - let key_nibbles = Nibbles::<0>(key.as_ref()); + let key_nibbles = Nibbles::<0>::new(key.as_ref()); let mut proofs: HashMap<[u8; TRIE_HASH_LEN], Vec> = HashMap::new(); if root.is_null() { diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 6610ff60b7af..5f65caf060e6 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -19,7 +19,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// ``` /// # use firewood::nibbles; /// # fn main() { -/// let nib = nibbles::Nibbles::<0>(&[0x56, 0x78]); +/// let nib = nibbles::Nibbles::<0>::new(&[0x56, 0x78]); /// assert_eq!(nib.iter().collect::>(), [0x5, 0x6, 0x7, 0x8]); /// /// // nibbles can be efficiently advanced without rendering the @@ -32,7 +32,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// # } /// ``` #[derive(Debug)] -pub struct Nibbles<'a, const LEADING_ZEROES: usize>(pub &'a [u8]); +pub struct Nibbles<'a, const LEADING_ZEROES: usize>(&'a [u8]); impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROES> { type Output = u8; @@ -63,6 +63,10 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { pub fn is_empty(&self) -> bool { LEADING_ZEROES == 0 && self.0.is_empty() } + + pub fn new(inner: &'a [u8]) -> Self { + Nibbles(inner) + } } /// An interator returned by [Nibbles::iter] From 14150fac1093976288997ad1906320cd33a589d3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Aug 2023 18:27:43 -0400 Subject: [PATCH 0250/1053] merkle::get: use Nibbles (#210) --- firewood/src/merkle.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index f65ba2594bfa..4814f51a7512 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -10,7 +10,6 @@ use std::{ error::Error, fmt::{self, Debug}, io::{Cursor, Read, Write}, - iter, sync::{ atomic::{AtomicBool, Ordering}, OnceLock, @@ -1919,38 +1918,38 @@ impl + Send + Sync> Merkle { key: K, root: DiskAddress, ) -> Result, MerkleError> { - // TODO: Make this NonNull or something similar if root.is_null() { return Ok(None); } - let chunks: Vec = iter::once(0) - .chain(key.as_ref().iter().copied().flat_map(to_nibble_array)) - .collect(); + let key_nibbles = Nibbles::<1>::new(key.as_ref()); let mut u_ref = self.get_node(root)?; let mut nskip = 0; - for (i, nib) in chunks.iter().enumerate() { + for (i, nib) in key_nibbles.iter().enumerate() { if nskip > 0 { nskip -= 1; continue; } let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[*nib as usize] { + NodeType::Branch(n) => match n.chd[nib as usize] { Some(c) => c, None => return Ok(None), }, NodeType::Leaf(n) => { - if chunks[i..] != *n.0 { + if !key_nibbles.iter().skip(i).eq(n.0.iter().cloned()) { return Ok(None); } return Ok(Some(Ref(u_ref))); } NodeType::Extension(n) => { let n_path = &*n.0; - let rem_path = &chunks[i..]; - if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { + let rem_path = key_nibbles.iter().skip(i); + if rem_path.size_hint().0 < n_path.len() { + return Ok(None); + } + if !rem_path.take(n_path.len()).eq(n_path.iter().cloned()) { return Ok(None); } nskip = n_path.len() - 1; From f3804445a9bdb5b6ccf90a5626f8037235bb7992 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 23 Aug 2023 12:38:56 -0400 Subject: [PATCH 0251/1053] Improve benchmarking (#202) --- firewood/Cargo.toml | 2 +- firewood/benches/hashops.rs | 160 ++++++++++++++++++++++----------- firewood/examples/benchmark.rs | 8 +- shale/benches/shale-bench.rs | 48 +++++----- 4 files changed, 135 insertions(+), 83 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 23e37a3a6b41..608dd97f6910 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -46,9 +46,9 @@ assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.3.1", features = ['derive'] } -bencher = "0.1.5" tempdir = "0.3.7" test-case = "3.1.0" +pprof = { version = "0.12.1", features = ["flamegraph"] } [features] # proof API diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index a285fc304ade..708ea58f7119 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -1,70 +1,128 @@ // hash benchmarks; run with 'cargo bench' -use std::ops::Deref; -use bencher::{benchmark_group, benchmark_main, Bencher}; +use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; use firewood_shale::{ - cached::PlainMem, disk_address::DiskAddress, CachedStore, Storable, StoredView, + cached::PlainMem, + compact::{CompactHeader, CompactSpace}, + disk_address::DiskAddress, + CachedStore, ObjCache, Storable, StoredView, }; -use rand::{distributions::Alphanumeric, Rng, SeedableRng}; +use pprof::ProfilerGuard; +use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; +use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path}; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); -fn bench_dehydrate(b: &mut Bencher) { - let mut to = [1u8; TRIE_HASH_LEN]; - b.iter(|| { - ZERO_HASH.dehydrate(&mut to).unwrap(); - }); +// To enable flamegraph output +// cargo bench --bench shale-bench -- --profile-time=N +enum FlamegraphProfiler { + Init(c_int), + Active(ProfilerGuard<'static>), +} + +fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { + |_| panic!("Error on file `{}`", path.display()) +} + +impl Profiler for FlamegraphProfiler { + fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { + if let Self::Init(frequency) = self { + let guard = ProfilerGuard::new(*frequency).unwrap(); + *self = Self::Active(guard); + } + } + + fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { + std::fs::create_dir_all(benchmark_dir).unwrap(); + let filename = "firewood-flamegraph.svg"; + let flamegraph_path = benchmark_dir.join(filename); + let flamegraph_file = + File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); + + if let Self::Active(profiler) = self { + profiler + .report() + .build() + .unwrap() + .flamegraph(flamegraph_file) + .unwrap_or_else(file_error_panic(&flamegraph_path)); + } + } } -fn bench_hydrate(b: &mut Bencher) { +fn bench_trie_hash(criterion: &mut Criterion) { + let mut to = [1u8; TRIE_HASH_LEN]; let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, ZERO_HASH.deref()); - b.iter(|| { - TrieHash::hydrate(0, &store).unwrap(); - }); + criterion + .benchmark_group("TrieHash") + .bench_function("dehydrate", |b| { + b.iter(|| ZERO_HASH.dehydrate(&mut to).unwrap()); + }) + .bench_function("hydrate", |b| { + b.iter(|| TrieHash::hydrate(0, &store).unwrap()); + }); } -fn bench_insert(b: &mut Bencher) { +fn bench_merkle(criterion: &mut Criterion) { const TEST_MEM_SIZE: u64 = 20_000_000; - let merkle_payload_header = DiskAddress::null(); - - let merkle_payload_header_ref = StoredView::ptr_to_obj( - &PlainMem::new(2 * firewood_shale::compact::CompactHeader::MSIZE, 9), - merkle_payload_header, - firewood_shale::compact::CompactHeader::MSIZE, - ) - .unwrap(); - - let store = firewood_shale::compact::CompactSpace::new( - PlainMem::new(TEST_MEM_SIZE, 0).into(), - PlainMem::new(TEST_MEM_SIZE, 1).into(), - merkle_payload_header_ref, - firewood_shale::ObjCache::new(1 << 20), - 4096, - 4096, - ) - .unwrap(); - let mut merkle = Merkle::new(Box::new(store)); - let root = merkle.init_root().unwrap(); - - let mut rng = rand::rngs::StdRng::seed_from_u64(1234); const KEY_LEN: usize = 4; - b.iter(|| { - // generate a random key - let k = (&mut rng) - .sample_iter(&Alphanumeric) - .take(KEY_LEN) - .collect::>(); - merkle.insert(k, vec![b'v'], root).unwrap(); - }); - #[cfg(trace)] - { - merkle.dump(root, &mut io::std::stdout().lock()).unwrap(); - println!("done\n---\n\n"); - } + let mut rng = StdRng::seed_from_u64(1234); + + criterion + .benchmark_group("Merkle") + .sample_size(30) + .bench_function("insert", |b| { + b.iter_batched( + || { + let merkle_payload_header = DiskAddress::from(0); + + let merkle_payload_header_ref = StoredView::ptr_to_obj( + &PlainMem::new(2 * CompactHeader::MSIZE, 9), + merkle_payload_header, + CompactHeader::MSIZE, + ) + .unwrap(); + + let store = CompactSpace::new( + PlainMem::new(TEST_MEM_SIZE, 0).into(), + PlainMem::new(TEST_MEM_SIZE, 1).into(), + merkle_payload_header_ref, + ObjCache::new(1 << 20), + 4096, + 4096, + ) + .unwrap(); + + let merkle = Merkle::new(Box::new(store)); + let root = merkle.init_root().unwrap(); + + let keys: Vec> = repeat_with(|| { + (&mut rng) + .sample_iter(&Alphanumeric) + .take(KEY_LEN) + .collect() + }) + .take(N) + .collect(); + + (merkle, root, keys) + }, + |(mut merkle, root, keys)| { + keys.into_iter() + .for_each(|key| merkle.insert(key, vec![b'v'], root).unwrap()) + }, + BatchSize::SmallInput, + ); + }); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); + targets = bench_trie_hash, bench_merkle::<1> } -benchmark_group!(benches, bench_dehydrate, bench_hydrate, bench_insert); -benchmark_main!(benches); +criterion_main!(benches); diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index f1e3ccca8e6e..17a2a7006d10 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use clap::Parser; -use criterion::Criterion; +use criterion::{Criterion, SamplingMode, Throughput}; use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -35,12 +35,10 @@ fn main() { println!("workload prepared"); - group - .sampling_mode(criterion::SamplingMode::Flat) - .sample_size(10); + group.sampling_mode(SamplingMode::Flat).sample_size(10); let total = (args.nbatch * args.batch_size) as u64; - group.throughput(criterion::Throughput::Elements(total)); + group.throughput(Throughput::Elements(total)); group.bench_with_input( format!("nbatch={} batch_size={}", args.nbatch, args.batch_size), diff --git a/shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs index a651cbc70897..584d2a206ab3 100644 --- a/shale/benches/shale-bench.rs +++ b/shale/benches/shale-bench.rs @@ -1,54 +1,51 @@ -extern crate firewood_shale as shale; - use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; -use pprof::ProfilerGuard; -use rand::Rng; -use shale::{ +use firewood_shale::{ cached::{DynamicMem, PlainMem}, - compact::CompactSpaceHeader, + compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, CachedStore, Obj, StoredView, }; +use pprof::ProfilerGuard; +use rand::Rng; use std::{fs::File, os::raw::c_int, path::Path}; const BENCH_MEM_SIZE: usize = 2_000_000; // To enable flamegraph output // cargo bench --bench shale-bench -- --profile-time=N -pub struct FlamegraphProfiler<'a> { - frequency: c_int, - active_profiler: Option>, +enum FlamegraphProfiler { + Init(c_int), + Active(ProfilerGuard<'static>), } -impl<'a> FlamegraphProfiler<'a> { - #[allow(dead_code)] - pub fn new(frequency: c_int) -> Self { - FlamegraphProfiler { - frequency, - active_profiler: None, - } - } +fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { + |_| panic!("Error on file `{}`", path.display()) } -impl<'a> Profiler for FlamegraphProfiler<'a> { +impl Profiler for FlamegraphProfiler { fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { - self.active_profiler = Some(ProfilerGuard::new(self.frequency).unwrap()); + if let Self::Init(frequency) = self { + let guard = ProfilerGuard::new(*frequency).unwrap(); + *self = Self::Active(guard); + } } fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { std::fs::create_dir_all(benchmark_dir).unwrap(); - let flamegraph_path = benchmark_dir.join("flamegraph.svg"); + let filename = "shale-flamegraph.svg"; + let flamegraph_path = benchmark_dir.join(filename); let flamegraph_file = - File::create(flamegraph_path).expect("File system error while creating flamegraph.svg"); - if let Some(profiler) = self.active_profiler.take() { + File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); + + if let Self::Active(profiler) = self { profiler .report() .build() .unwrap() .flamegraph(flamegraph_file) - .expect("Error writing flamegraph"); + .unwrap_or_else(file_error_panic(&flamegraph_path)); } } } @@ -75,8 +72,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); let _: Obj = - StoredView::ptr_to_obj(m, compact_header_obj, shale::compact::CompactHeader::MSIZE) - .unwrap(); + StoredView::ptr_to_obj(m, compact_header_obj, CompactHeader::MSIZE).unwrap(); } fn bench_cursors(c: &mut Criterion) { @@ -93,7 +89,7 @@ fn bench_cursors(c: &mut Criterion) { criterion_group! { name = benches; - config = Criterion::default().with_profiler(FlamegraphProfiler::new(100)); + config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); targets = bench_cursors } From b6aa24baca9f9abf78086d847e5d33ac4a78628d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 23 Aug 2023 12:50:49 -0400 Subject: [PATCH 0252/1053] Cleanup: variable renames (#211) --- firewood/src/proof.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index e76692839c43..fbbd980b3c84 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -128,10 +128,10 @@ impl Proof { key: K, root_hash: [u8; 32], ) -> Result>, ProofError> { - let mut chunks = Vec::new(); - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + let mut key_nibbles = Vec::new(); + key_nibbles.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); - let mut cur_key: &[u8] = &chunks; + let mut remaining_key_nibbles: &[u8] = &key_nibbles; let mut cur_hash = root_hash; let proofs_map = &self.0; let mut index = 0; @@ -140,17 +140,17 @@ impl Proof { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; - let (sub_proof, size) = self.locate_subproof(cur_key, cur_proof)?; + let (sub_proof, size) = self.locate_subproof(remaining_key_nibbles, cur_proof)?; index += size; match sub_proof { // Return when reaching the end of the key. - Some(p) if index == chunks.len() => return Ok(Some(p.rlp)), + Some(p) if index == key_nibbles.len() => return Ok(Some(p.rlp)), // The trie doesn't contain the key. Some(SubProof { hash: None, .. }) | None => return Ok(None), Some(p) => { cur_hash = p.hash.unwrap(); - cur_key = &chunks[index..]; + remaining_key_nibbles = &key_nibbles[index..]; } } } @@ -158,15 +158,14 @@ impl Proof { fn locate_subproof( &self, - key: &[u8], - buf: &[u8], + key_nibbles: &[u8], + rlp_encoded_node: &[u8], ) -> Result<(Option, usize), ProofError> { - let rlp = rlp::Rlp::new(buf); + let rlp = rlp::Rlp::new(rlp_encoded_node); - // TODO: handle error in match statement instead of unwrapping - match rlp.item_count().unwrap() { - EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = rlp + match rlp.item_count() { + Ok(EXT_NODE_SIZE) => { + let decoded_key_nibbles: Vec<_> = rlp .at(0) .unwrap() .as_val::>() @@ -174,7 +173,7 @@ impl Proof { .into_iter() .flat_map(to_nibble_array) .collect(); - let (cur_key_path, term) = PartialPath::decode(cur_key_path); + let (cur_key_path, term) = PartialPath::decode(decoded_key_nibbles); let cur_key = cur_key_path.into_inner(); let rlp = rlp.at(1).unwrap(); @@ -186,7 +185,7 @@ impl Proof { }; // Check if the key of current node match with the given key. - if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { + if key_nibbles.len() < cur_key.len() || key_nibbles[..cur_key.len()] != cur_key { return Ok((None, 0)); } @@ -204,10 +203,10 @@ impl Proof { } } - BRANCH_NODE_SIZE if key.is_empty() => Err(ProofError::NoSuchNode), + Ok(BRANCH_NODE_SIZE) if key_nibbles.is_empty() => Err(ProofError::NoSuchNode), - BRANCH_NODE_SIZE => { - let index = key[0]; + Ok(BRANCH_NODE_SIZE) => { + let index = key_nibbles[0]; let rlp = rlp.at(index as usize).unwrap(); let data = if rlp.is_data() { From 9f7e6d535ddb49f170e3cc5153e8e4e14057430f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 24 Aug 2023 09:59:28 -0400 Subject: [PATCH 0253/1053] cleanups (#212) --- RELEASE.md | 6 +- firewood/Cargo.toml | 6 +- firewood/benches/hashops.rs | 6 +- firewood/examples/rev.rs | 2 +- firewood/src/lib.rs | 2 - firewood/src/merkle.rs | 833 +--------------------------- firewood/src/merkle/node.rs | 731 ++++++++++++++++++++++++ firewood/src/merkle/partial_path.rs | 62 +++ firewood/src/merkle/trie_hash.rs | 49 ++ firewood/src/merkle_util.rs | 2 +- firewood/src/proof.rs | 23 +- firewood/tests/merkle.rs | 4 +- fwdctl/tests/cli.rs | 2 - growth-ring/Cargo.toml | 2 +- libaio/Cargo.toml | 2 +- shale/Cargo.toml | 2 +- shale/benches/shale-bench.rs | 6 +- 17 files changed, 885 insertions(+), 855 deletions(-) create mode 100644 firewood/src/merkle/node.rs create mode 100644 firewood/src/merkle/partial_path.rs create mode 100644 firewood/src/merkle/trie_hash.rs diff --git a/RELEASE.md b/RELEASE.md index 2356a7199540..62d020e6fe7b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,9 +5,9 @@ Releasing firewood is straightforward and can be done entirely in CI. Firewood is made up of several sub-projects in a workspace. Each project is in its own crate and has an independent version. * firewood -* firewood-growth-ring -* firewood-libaio -* firewood-shale +* growth-ring +* libaio +* shale The first step in drafting a release is ensuring all crates within the firewood project are using the version of the new release. There is a utility to ensure diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 608dd97f6910..0273641a8cf0 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -20,9 +20,9 @@ aquamarine = "0.3.1" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" -firewood-growth-ring = { version = "0.0.3", path = "../growth-ring" } -firewood-libaio = {version = "0.0.3", path = "../libaio" } -firewood-shale = { version = "0.0.3", path = "../shale" } +growth-ring = { version = "0.0.3", path = "../growth-ring" } +libaio = {version = "0.0.3", path = "../libaio" } +shale = { version = "0.0.3", path = "../shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.11.0" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 708ea58f7119..0e6359f440ca 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -2,14 +2,14 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; -use firewood_shale::{ +use pprof::ProfilerGuard; +use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; +use shale::{ cached::PlainMem, compact::{CompactHeader, CompactSpace}, disk_address::DiskAddress, CachedStore, ObjCache, Storable, StoredView, }; -use pprof::ProfilerGuard; -use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path}; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 70ce7cdfb2ab..5ebd75d7893d 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -9,7 +9,7 @@ use firewood::{ proof::Proof, storage::StoreRevShared, }; -use firewood_shale::compact::CompactSpace; +use shale::compact::CompactSpace; type SharedStore = CompactSpace; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 103bc37a94f6..478f6c841d85 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -207,5 +207,3 @@ pub mod nibbles; pub mod service; pub mod v2; - -extern crate firewood_shale as shale; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4814f51a7512..188458da166d 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,22 +2,25 @@ // See the file LICENSE.md for licensing terms. use crate::{nibbles::Nibbles, proof::Proof}; -use enum_as_inner::EnumAsInner; use sha3::Digest; -use shale::{disk_address::DiskAddress, CachedStore, ObjRef, ShaleError, ShaleStore, Storable}; +use shale::{disk_address::DiskAddress, ObjRef, ShaleError, ShaleStore}; use std::{ collections::HashMap, error::Error, fmt::{self, Debug}, - io::{Cursor, Read, Write}, - sync::{ - atomic::{AtomicBool, Ordering}, - OnceLock, - }, + io::Write, + sync::{atomic::Ordering, OnceLock}, }; -pub const NBRANCH: usize = 16; -pub const TRIE_HASH_LEN: usize = 32; +mod node; +mod partial_path; +mod trie_hash; + +pub use node::{ + BranchNode, Data, ExtNode, IdTrans, LeafNode, Node, NodeType, ValueTransformer, NBRANCH, +}; +pub use partial_path::PartialPath; +pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; #[derive(Debug)] pub enum MerkleError { @@ -42,806 +45,9 @@ impl fmt::Display for MerkleError { } } +// TODO: use thiserror impl Error for MerkleError {} -#[derive(PartialEq, Eq, Clone)] -pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); - -impl TrieHash { - const MSIZE: u64 = 32; -} - -impl std::ops::Deref for TrieHash { - type Target = [u8; TRIE_HASH_LEN]; - fn deref(&self) -> &[u8; TRIE_HASH_LEN] { - &self.0 - } -} - -impl Storable for TrieHash { - fn hydrate(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - Ok(Self( - raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), - )) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0).map_err(ShaleError::Io) - } -} - -impl Debug for TrieHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", hex::encode(self.0)) - } -} - -/// PartialPath keeps a list of nibbles to represent a path on the Trie. -#[derive(PartialEq, Eq, Clone)] -pub struct PartialPath(Vec); - -impl Debug for PartialPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - for nib in self.0.iter() { - write!(f, "{:x}", *nib & 0xf)?; - } - Ok(()) - } -} - -impl std::ops::Deref for PartialPath { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl PartialPath { - pub fn into_inner(self) -> Vec { - self.0 - } - - fn encode(&self, term: bool) -> Vec { - let odd_len = (self.0.len() & 1) as u8; - let flags = if term { 2 } else { 0 } + odd_len; - let mut res = if odd_len == 1 { - vec![flags] - } else { - vec![flags, 0x0] - }; - res.extend(&self.0); - res - } - - pub fn decode>(raw: R) -> (Self, bool) { - let raw = raw.as_ref(); - let term = raw[0] > 1; - let odd_len = raw[0] & 1; - ( - Self(if odd_len == 1 { - raw[1..].to_vec() - } else { - raw[2..].to_vec() - }), - term, - ) - } - - fn dehydrated_len(&self) -> u64 { - let len = self.0.len() as u64; - if len & 1 == 1 { - (len + 1) >> 1 - } else { - (len >> 1) + 1 - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Data(Vec); - -impl std::ops::Deref for Data { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct BranchNode { - chd: [Option; NBRANCH], - value: Option, - chd_eth_rlp: [Option>; NBRANCH], -} - -impl Debug for BranchNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Branch")?; - for (i, c) in self.chd.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {c:?})")?; - } - } - for (i, c) in self.chd_eth_rlp.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {:?})", c)?; - } - } - write!( - f, - " v={}]", - match &self.value { - Some(v) => hex::encode(&**v), - None => "nil".to_string(), - } - ) - } -} - -impl BranchNode { - fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { - let mut has_chd = false; - let mut only_chd = None; - for (i, c) in self.chd.iter().enumerate() { - if c.is_some() { - has_chd = true; - if only_chd.is_some() { - only_chd = None; - break; - } - only_chd = (*c).map(|e| (e, i as u8)) - } - } - (only_chd, has_chd) - } - - fn calc_eth_rlp>(&self, store: &S) -> Vec { - let mut stream = rlp::RlpStream::new_list(17); - for (i, c) in self.chd.iter().enumerate() { - match c { - Some(c) => { - let mut c_ref = store.get_item(*c).unwrap(); - if c_ref.get_eth_rlp_long::(store) { - stream.append(&&(*c_ref.get_root_hash::(store))[..]); - // See struct docs for ordering requirements - if c_ref.lazy_dirty.load(Ordering::Relaxed) { - c_ref.write(|_| {}).unwrap(); - c_ref.lazy_dirty.store(false, Ordering::Relaxed) - } - } else { - let c_rlp = &c_ref.get_eth_rlp::(store); - stream.append_raw(c_rlp, 1); - } - } - None => { - // Check if there is already a calculated rlp for the child, which - // can happen when manually constructing a trie from proof. - if self.chd_eth_rlp[i].is_none() { - stream.append_empty_data(); - } else { - let v = self.chd_eth_rlp[i].clone().unwrap(); - if v.len() == TRIE_HASH_LEN { - stream.append(&v); - } else { - stream.append_raw(&v, 1); - } - } - } - }; - } - match &self.value { - Some(val) => stream.append(&val.to_vec()), - None => stream.append_empty_data(), - }; - stream.out().into() - } - - pub fn new( - chd: [Option; NBRANCH], - value: Option>, - chd_eth_rlp: [Option>; NBRANCH], - ) -> Self { - BranchNode { - chd, - value: value.map(Data), - chd_eth_rlp, - } - } - - pub fn value(&self) -> &Option { - &self.value - } - - pub fn chd(&self) -> &[Option; NBRANCH] { - &self.chd - } - - pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { - &mut self.chd - } - - pub fn chd_eth_rlp(&self) -> &[Option>; NBRANCH] { - &self.chd_eth_rlp - } - - pub fn chd_eth_rlp_mut(&mut self) -> &mut [Option>; NBRANCH] { - &mut self.chd_eth_rlp - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct LeafNode(PartialPath, Data); - -impl Debug for LeafNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1)) - } -} - -impl LeafNode { - fn calc_eth_rlp(&self) -> Vec { - rlp::encode_list::, _>(&[ - from_nibbles(&self.0.encode(true)).collect(), - T::transform(&self.1), - ]) - .into() - } - - pub fn new(path: Vec, data: Vec) -> Self { - LeafNode(PartialPath(path), Data(data)) - } - - pub fn path(&self) -> &PartialPath { - &self.0 - } - - pub fn data(&self) -> &Data { - &self.1 - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct ExtNode(PartialPath, DiskAddress, Option>); - -impl Debug for ExtNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Extension {:?} {:?} {:?}]", self.0, self.1, self.2) - } -} - -impl ExtNode { - fn calc_eth_rlp>(&self, store: &S) -> Vec { - let mut stream = rlp::RlpStream::new_list(2); - if !self.1.is_null() { - let mut r = store.get_item(self.1).unwrap(); - stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); - if r.get_eth_rlp_long::(store) { - stream.append(&&(*r.get_root_hash::(store))[..]); - if r.lazy_dirty.load(Ordering::Relaxed) { - r.write(|_| {}).unwrap(); - r.lazy_dirty.store(false, Ordering::Relaxed); - } - } else { - stream.append_raw(r.get_eth_rlp::(store), 1); - } - } else { - // Check if there is already a caclucated rlp for the child, which - // can happen when manually constructing a trie from proof. - if self.2.is_none() { - stream.append_empty_data(); - } else { - let v = self.2.clone().unwrap(); - if v.len() == TRIE_HASH_LEN { - stream.append(&v); - } else { - stream.append_raw(&v, 1); - } - } - } - stream.out().into() - } - - pub fn new(path: Vec, chd: DiskAddress, chd_eth_rlp: Option>) -> Self { - ExtNode(PartialPath(path), chd, chd_eth_rlp) - } - - pub fn path(&self) -> &PartialPath { - &self.0 - } - - pub fn chd(&self) -> DiskAddress { - self.1 - } - - pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.1 - } - - pub fn chd_eth_rlp_mut(&mut self) -> &mut Option> { - &mut self.2 - } -} - -#[derive(Debug)] -pub struct Node { - root_hash: OnceLock, - eth_rlp_long: OnceLock, - eth_rlp: OnceLock>, - // lazy_dirty is an atomicbool, but only writers ever set it - // Therefore, we can always use Relaxed ordering. It's atomic - // just to ensure Sync + Send. - lazy_dirty: AtomicBool, - inner: NodeType, -} - -impl Eq for Node {} -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - let Node { - root_hash, - eth_rlp_long, - eth_rlp, - lazy_dirty, - inner, - } = self; - *root_hash == other.root_hash - && *eth_rlp_long == other.eth_rlp_long - && *eth_rlp == other.eth_rlp - && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) - && *inner == other.inner - } -} -impl Clone for Node { - fn clone(&self) -> Self { - Self { - root_hash: self.root_hash.clone(), - eth_rlp_long: self.eth_rlp_long.clone(), - eth_rlp: self.eth_rlp.clone(), - lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), - inner: self.inner.clone(), - } - } -} - -#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] -#[allow(clippy::large_enum_variant)] -pub enum NodeType { - Branch(BranchNode), - Leaf(LeafNode), - Extension(ExtNode), -} - -impl NodeType { - fn calc_eth_rlp>(&self, store: &S) -> Vec { - match &self { - NodeType::Leaf(n) => n.calc_eth_rlp::(), - NodeType::Extension(n) => n.calc_eth_rlp::(store), - NodeType::Branch(n) => n.calc_eth_rlp::(store), - } - } -} - -impl Node { - const BRANCH_NODE: u8 = 0x0; - const EXT_NODE: u8 = 0x1; - const LEAF_NODE: u8 = 0x2; - - fn max_branch_node_size() -> u64 { - let max_size: OnceLock = OnceLock::new(); - *max_size.get_or_init(|| { - Self { - root_hash: OnceLock::new(), - eth_rlp_long: OnceLock::new(), - eth_rlp: OnceLock::new(), - inner: NodeType::Branch(BranchNode { - chd: [Some(DiskAddress::null()); NBRANCH], - value: Some(Data(Vec::new())), - chd_eth_rlp: Default::default(), - }), - lazy_dirty: AtomicBool::new(false), - } - .dehydrated_len() - }) - } - - fn get_eth_rlp>(&self, store: &S) -> &[u8] { - self.eth_rlp - .get_or_init(|| self.inner.calc_eth_rlp::(store)) - } - - fn get_root_hash>(&self, store: &S) -> &TrieHash { - self.root_hash.get_or_init(|| { - self.lazy_dirty.store(true, Ordering::Relaxed); - TrieHash(sha3::Keccak256::digest(self.get_eth_rlp::(store)).into()) - }) - } - - fn get_eth_rlp_long>(&self, store: &S) -> bool { - *self.eth_rlp_long.get_or_init(|| { - self.lazy_dirty.store(true, Ordering::Relaxed); - self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN - }) - } - - fn rehash(&mut self) { - self.eth_rlp = OnceLock::new(); - self.eth_rlp_long = OnceLock::new(); - self.root_hash = OnceLock::new(); - } - - pub fn new(inner: NodeType) -> Self { - let mut s = Self { - root_hash: OnceLock::new(), - eth_rlp_long: OnceLock::new(), - eth_rlp: OnceLock::new(), - inner, - lazy_dirty: AtomicBool::new(false), - }; - s.rehash(); - s - } - - pub fn inner(&self) -> &NodeType { - &self.inner - } - - pub fn inner_mut(&mut self) -> &mut NodeType { - &mut self.inner - } - - fn new_from_hash( - root_hash: Option, - eth_rlp_long: Option, - inner: NodeType, - ) -> Self { - Self { - root_hash: match root_hash { - Some(h) => OnceLock::from(h), - None => OnceLock::new(), - }, - eth_rlp_long: match eth_rlp_long { - Some(b) => OnceLock::from(b), - None => OnceLock::new(), - }, - eth_rlp: OnceLock::new(), - inner, - lazy_dirty: AtomicBool::new(false), - } - } - - const ROOT_HASH_VALID_BIT: u8 = 1 << 0; - const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1; - const ETH_RLP_LONG_BIT: u8 = 1 << 2; -} - -impl Storable for Node { - fn hydrate(addr: usize, mem: &T) -> Result { - const META_SIZE: usize = 32 + 1 + 1; - let meta_raw = - mem.get_view(addr, META_SIZE as u64) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: META_SIZE as u64, - })?; - let attrs = meta_raw.as_deref()[32]; - let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { - None - } else { - Some(TrieHash( - meta_raw.as_deref()[0..32] - .try_into() - .expect("invalid slice"), - )) - }; - let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { - None - } else { - Some(attrs & Node::ETH_RLP_LONG_BIT != 0) - }; - match meta_raw.as_deref()[33] { - Self::BRANCH_NODE => { - let branch_header_size = NBRANCH as u64 * 8 + 4; - let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + META_SIZE, - size: branch_header_size, - }, - )?; - let mut cur = Cursor::new(node_raw.as_deref()); - let mut chd = [None; NBRANCH]; - let mut buff = [0; 8]; - for chd in chd.iter_mut() { - cur.read_exact(&mut buff)?; - let addr = usize::from_le_bytes(buff); - if addr != 0 { - *chd = Some(DiskAddress::from(addr)) - } - } - cur.read_exact(&mut buff[..4])?; - let raw_len = - u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")) as u64; - let value = if raw_len == u32::MAX as u64 { - None - } else { - Some(Data( - mem.get_view(addr + META_SIZE + branch_header_size as usize, raw_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + branch_header_size as usize, - size: raw_len, - })? - .as_deref(), - )) - }; - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - let offset = if raw_len == u32::MAX as u64 { - addr + META_SIZE + branch_header_size as usize - } else { - addr + META_SIZE + branch_header_size as usize + raw_len as usize - }; - let mut cur_rlp_len = 0; - for chd_rlp in chd_eth_rlp.iter_mut() { - let mut buff = [0_u8; 1]; - let rlp_len_raw = mem.get_view(offset + cur_rlp_len, 1).ok_or( - ShaleError::InvalidCacheView { - offset: offset + cur_rlp_len, - size: 1, - }, - )?; - cur = Cursor::new(rlp_len_raw.as_deref()); - cur.read_exact(&mut buff)?; - let rlp_len = buff[0] as u64; - cur_rlp_len += 1; - if rlp_len != 0 { - let rlp_raw = mem.get_view(offset + cur_rlp_len, rlp_len).ok_or( - ShaleError::InvalidCacheView { - offset: offset + cur_rlp_len, - size: rlp_len, - }, - )?; - let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); - *chd_rlp = Some(rlp); - cur_rlp_len += rlp_len as usize - } - } - - Ok(Self::new_from_hash( - root_hash, - eth_rlp_long, - NodeType::Branch(BranchNode { - chd, - value, - chd_eth_rlp, - }), - )) - } - Self::EXT_NODE => { - let ext_header_size = 1 + 8; - let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + META_SIZE, - size: ext_header_size, - }, - )?; - let mut cur = Cursor::new(node_raw.as_deref()); - let mut buff = [0; 8]; - cur.read_exact(&mut buff[..1])?; - let path_len = buff[0] as u64; - cur.read_exact(&mut buff)?; - let ptr = u64::from_le_bytes(buff); - - let nibbles: Vec = mem - .get_view(addr + META_SIZE + ext_header_size as usize, path_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size as usize, - size: path_len, - })? - .as_deref() - .into_iter() - .flat_map(to_nibble_array) - .collect(); - - let (path, _) = PartialPath::decode(nibbles); - - let mut buff = [0_u8; 1]; - let rlp_len_raw = mem - .get_view( - addr + META_SIZE + ext_header_size as usize + path_len as usize, - 1, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, - size: 1, - })?; - cur = Cursor::new(rlp_len_raw.as_deref()); - cur.read_exact(&mut buff)?; - let rlp_len = buff[0] as u64; - let rlp: Option> = if rlp_len != 0 { - let rlp_raw = mem - .get_view( - addr + META_SIZE + ext_header_size as usize + path_len as usize + 1, - rlp_len, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr - + META_SIZE - + ext_header_size as usize - + path_len as usize - + 1, - size: rlp_len, - })?; - - Some(rlp_raw.as_deref()[0..].to_vec()) - } else { - None - }; - - Ok(Self::new_from_hash( - root_hash, - eth_rlp_long, - NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), rlp)), - )) - } - Self::LEAF_NODE => { - let leaf_header_size = 1 + 4; - let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + META_SIZE, - size: leaf_header_size, - }, - )?; - let mut cur = Cursor::new(node_raw.as_deref()); - let mut buff = [0; 4]; - cur.read_exact(&mut buff[..1])?; - let path_len = buff[0] as u64; - cur.read_exact(&mut buff)?; - let data_len = u32::from_le_bytes(buff) as u64; - let remainder = mem - .get_view( - addr + META_SIZE + leaf_header_size as usize, - path_len + data_len, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + leaf_header_size as usize, - size: path_len + data_len, - })?; - - let nibbles: Vec<_> = remainder - .as_deref() - .into_iter() - .take(path_len as usize) - .flat_map(to_nibble_array) - .collect(); - - let (path, _) = PartialPath::decode(nibbles); - let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); - Ok(Self::new_from_hash( - root_hash, - eth_rlp_long, - NodeType::Leaf(LeafNode(path, value)), - )) - } - _ => Err(ShaleError::InvalidNodeType), - } - } - - fn dehydrated_len(&self) -> u64 { - 32 + 1 - + 1 - + match &self.inner { - NodeType::Branch(n) => { - let mut rlp_len = 0; - for rlp in n.chd_eth_rlp.iter() { - rlp_len += match rlp { - Some(v) => 1 + v.len() as u64, - None => 1, - } - } - NBRANCH as u64 * 8 - + 4 - + match &n.value { - Some(val) => val.len() as u64, - None => 0, - } - + rlp_len - } - NodeType::Extension(n) => { - 1 + 8 - + n.0.dehydrated_len() - + match &n.2 { - Some(v) => 1 + v.len() as u64, - None => 1, - } - } - NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, - } - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - - let mut attrs = 0; - attrs |= match self.root_hash.get() { - Some(h) => { - cur.write_all(&h.0)?; - Node::ROOT_HASH_VALID_BIT - } - None => { - cur.write_all(&[0; 32])?; - 0 - } - }; - attrs |= match self.eth_rlp_long.get() { - Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT), - None => 0, - }; - cur.write_all(&[attrs]).unwrap(); - - match &self.inner { - NodeType::Branch(n) => { - cur.write_all(&[Self::BRANCH_NODE]).unwrap(); - for c in n.chd.iter() { - cur.write_all(&match c { - Some(p) => p.to_le_bytes(), - None => 0u64.to_le_bytes(), - })?; - } - match &n.value { - Some(val) => { - cur.write_all(&(val.len() as u32).to_le_bytes())?; - cur.write_all(val)? - } - None => { - cur.write_all(&u32::MAX.to_le_bytes())?; - } - } - // Since child eth rlp will only be unset after initialization (only used for range proof), - // it is fine to encode its value adjacent to other fields. Same for extention node. - for rlp in n.chd_eth_rlp.iter() { - match rlp { - Some(v) => { - cur.write_all(&[v.len() as u8])?; - cur.write_all(v)? - } - None => cur.write_all(&0u8.to_le_bytes())?, - } - } - Ok(()) - } - NodeType::Extension(n) => { - cur.write_all(&[Self::EXT_NODE])?; - let path: Vec = from_nibbles(&n.0.encode(false)).collect(); - cur.write_all(&[path.len() as u8])?; - cur.write_all(&n.1.to_le_bytes())?; - cur.write_all(&path)?; - if n.2.is_some() { - let rlp = n.2.as_ref().unwrap(); - cur.write_all(&[rlp.len() as u8])?; - cur.write_all(rlp)?; - } - Ok(()) - } - NodeType::Leaf(n) => { - cur.write_all(&[Self::LEAF_NODE])?; - let path: Vec = from_nibbles(&n.0.encode(true)).collect(); - cur.write_all(&[path.len() as u8])?; - cur.write_all(&(n.1.len() as u32).to_le_bytes())?; - cur.write_all(&path)?; - cur.write_all(&n.1).map_err(ShaleError::Io) - } - } - } -} - macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let Err(_) = $r.write($modify) { @@ -2044,18 +1250,6 @@ impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { } } -pub trait ValueTransformer { - fn transform(bytes: &[u8]) -> Vec; -} - -pub struct IdTrans; - -impl ValueTransformer for IdTrans { - fn transform(bytes: &[u8]) -> Vec { - bytes.to_vec() - } -} - // nibbles, high bits first, then low bits pub fn to_nibble_array(x: u8) -> [u8; 2] { [x >> 4, x & 0b_0000_1111] @@ -2073,6 +1267,7 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { mod test { use super::*; use shale::cached::PlainMem; + use shale::{CachedStore, Storable}; use std::ops::Deref; use test_case::test_case; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs new file mode 100644 index 000000000000..b91f58eaa120 --- /dev/null +++ b/firewood/src/merkle/node.rs @@ -0,0 +1,731 @@ +use enum_as_inner::EnumAsInner; +use sha3::{Digest, Keccak256}; +use shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; +use std::{ + fmt::{self, Debug}, + io::{Cursor, Read, Write}, + sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock, + }, +}; + +use crate::merkle::to_nibble_array; + +use super::{from_nibbles, PartialPath, TrieHash, TRIE_HASH_LEN}; + +pub const NBRANCH: usize = 16; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Data(pub(super) Vec); + +impl std::ops::Deref for Data { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +pub trait ValueTransformer { + fn transform(bytes: &[u8]) -> Vec; +} + +pub struct IdTrans; + +impl ValueTransformer for IdTrans { + fn transform(bytes: &[u8]) -> Vec { + bytes.to_vec() + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct BranchNode { + pub(super) chd: [Option; NBRANCH], + pub(super) value: Option, + pub(super) chd_eth_rlp: [Option>; NBRANCH], +} + +impl Debug for BranchNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Branch")?; + for (i, c) in self.chd.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {c:?})")?; + } + } + for (i, c) in self.chd_eth_rlp.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {:?})", c)?; + } + } + write!( + f, + " v={}]", + match &self.value { + Some(v) => hex::encode(&**v), + None => "nil".to_string(), + } + ) + } +} + +impl BranchNode { + pub(super) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { + let mut has_chd = false; + let mut only_chd = None; + for (i, c) in self.chd.iter().enumerate() { + if c.is_some() { + has_chd = true; + if only_chd.is_some() { + only_chd = None; + break; + } + only_chd = (*c).map(|e| (e, i as u8)) + } + } + (only_chd, has_chd) + } + + fn calc_eth_rlp>(&self, store: &S) -> Vec { + let mut stream = rlp::RlpStream::new_list(17); + for (i, c) in self.chd.iter().enumerate() { + match c { + Some(c) => { + let mut c_ref = store.get_item(*c).unwrap(); + if c_ref.get_eth_rlp_long::(store) { + stream.append(&&(*c_ref.get_root_hash::(store))[..]); + // See struct docs for ordering requirements + if c_ref.lazy_dirty.load(Ordering::Relaxed) { + c_ref.write(|_| {}).unwrap(); + c_ref.lazy_dirty.store(false, Ordering::Relaxed) + } + } else { + let c_rlp = &c_ref.get_eth_rlp::(store); + stream.append_raw(c_rlp, 1); + } + } + None => { + // Check if there is already a calculated rlp for the child, which + // can happen when manually constructing a trie from proof. + if self.chd_eth_rlp[i].is_none() { + stream.append_empty_data(); + } else { + let v = self.chd_eth_rlp[i].clone().unwrap(); + if v.len() == TRIE_HASH_LEN { + stream.append(&v); + } else { + stream.append_raw(&v, 1); + } + } + } + }; + } + match &self.value { + Some(val) => stream.append(&val.to_vec()), + None => stream.append_empty_data(), + }; + stream.out().into() + } + + pub fn new( + chd: [Option; NBRANCH], + value: Option>, + chd_eth_rlp: [Option>; NBRANCH], + ) -> Self { + BranchNode { + chd, + value: value.map(Data), + chd_eth_rlp, + } + } + + pub fn value(&self) -> &Option { + &self.value + } + + pub fn chd(&self) -> &[Option; NBRANCH] { + &self.chd + } + + pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { + &mut self.chd + } + + pub fn chd_eth_rlp(&self) -> &[Option>; NBRANCH] { + &self.chd_eth_rlp + } + + pub fn chd_eth_rlp_mut(&mut self) -> &mut [Option>; NBRANCH] { + &mut self.chd_eth_rlp + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct LeafNode(pub(super) PartialPath, pub(super) Data); + +impl Debug for LeafNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1)) + } +} + +impl LeafNode { + fn calc_eth_rlp(&self) -> Vec { + rlp::encode_list::, _>(&[ + from_nibbles(&self.0.encode(true)).collect(), + T::transform(&self.1), + ]) + .into() + } + + pub fn new(path: Vec, data: Vec) -> Self { + LeafNode(PartialPath(path), Data(data)) + } + + pub fn path(&self) -> &PartialPath { + &self.0 + } + + pub fn data(&self) -> &Data { + &self.1 + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct ExtNode( + pub(super) PartialPath, + pub(super) DiskAddress, + pub(super) Option>, +); + +impl Debug for ExtNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "[Extension {:?} {:?} {:?}]", self.0, self.1, self.2) + } +} + +impl ExtNode { + fn calc_eth_rlp>(&self, store: &S) -> Vec { + let mut stream = rlp::RlpStream::new_list(2); + if !self.1.is_null() { + let mut r = store.get_item(self.1).unwrap(); + stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); + if r.get_eth_rlp_long::(store) { + stream.append(&&(*r.get_root_hash::(store))[..]); + if r.lazy_dirty.load(Ordering::Relaxed) { + r.write(|_| {}).unwrap(); + r.lazy_dirty.store(false, Ordering::Relaxed); + } + } else { + stream.append_raw(r.get_eth_rlp::(store), 1); + } + } else { + // Check if there is already a caclucated rlp for the child, which + // can happen when manually constructing a trie from proof. + if self.2.is_none() { + stream.append_empty_data(); + } else { + let v = self.2.clone().unwrap(); + if v.len() == TRIE_HASH_LEN { + stream.append(&v); + } else { + stream.append_raw(&v, 1); + } + } + } + stream.out().into() + } + + pub fn new(path: Vec, chd: DiskAddress, chd_eth_rlp: Option>) -> Self { + ExtNode(PartialPath(path), chd, chd_eth_rlp) + } + + pub fn path(&self) -> &PartialPath { + &self.0 + } + + pub fn chd(&self) -> DiskAddress { + self.1 + } + + pub fn chd_mut(&mut self) -> &mut DiskAddress { + &mut self.1 + } + + pub fn chd_eth_rlp_mut(&mut self) -> &mut Option> { + &mut self.2 + } +} + +#[derive(Debug)] +pub struct Node { + pub(super) root_hash: OnceLock, + eth_rlp_long: OnceLock, + eth_rlp: OnceLock>, + // lazy_dirty is an atomicbool, but only writers ever set it + // Therefore, we can always use Relaxed ordering. It's atomic + // just to ensure Sync + Send. + pub(super) lazy_dirty: AtomicBool, + pub(super) inner: NodeType, +} + +impl Eq for Node {} +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + let Node { + root_hash, + eth_rlp_long, + eth_rlp, + lazy_dirty, + inner, + } = self; + *root_hash == other.root_hash + && *eth_rlp_long == other.eth_rlp_long + && *eth_rlp == other.eth_rlp + && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) + && *inner == other.inner + } +} +impl Clone for Node { + fn clone(&self) -> Self { + Self { + root_hash: self.root_hash.clone(), + eth_rlp_long: self.eth_rlp_long.clone(), + eth_rlp: self.eth_rlp.clone(), + lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), + inner: self.inner.clone(), + } + } +} + +#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] +#[allow(clippy::large_enum_variant)] +pub enum NodeType { + Branch(BranchNode), + Leaf(LeafNode), + Extension(ExtNode), +} + +impl NodeType { + fn calc_eth_rlp>(&self, store: &S) -> Vec { + match &self { + NodeType::Leaf(n) => n.calc_eth_rlp::(), + NodeType::Extension(n) => n.calc_eth_rlp::(store), + NodeType::Branch(n) => n.calc_eth_rlp::(store), + } + } +} + +impl Node { + const BRANCH_NODE: u8 = 0x0; + const EXT_NODE: u8 = 0x1; + const LEAF_NODE: u8 = 0x2; + + pub(super) fn max_branch_node_size() -> u64 { + let max_size: OnceLock = OnceLock::new(); + *max_size.get_or_init(|| { + Self { + root_hash: OnceLock::new(), + eth_rlp_long: OnceLock::new(), + eth_rlp: OnceLock::new(), + inner: NodeType::Branch(BranchNode { + chd: [Some(DiskAddress::null()); NBRANCH], + value: Some(Data(Vec::new())), + chd_eth_rlp: Default::default(), + }), + lazy_dirty: AtomicBool::new(false), + } + .dehydrated_len() + }) + } + + pub(super) fn get_eth_rlp>(&self, store: &S) -> &[u8] { + self.eth_rlp + .get_or_init(|| self.inner.calc_eth_rlp::(store)) + } + + pub(super) fn get_root_hash>( + &self, + store: &S, + ) -> &TrieHash { + self.root_hash.get_or_init(|| { + self.lazy_dirty.store(true, Ordering::Relaxed); + TrieHash(Keccak256::digest(self.get_eth_rlp::(store)).into()) + }) + } + + fn get_eth_rlp_long>(&self, store: &S) -> bool { + *self.eth_rlp_long.get_or_init(|| { + self.lazy_dirty.store(true, Ordering::Relaxed); + self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN + }) + } + + pub(super) fn rehash(&mut self) { + self.eth_rlp = OnceLock::new(); + self.eth_rlp_long = OnceLock::new(); + self.root_hash = OnceLock::new(); + } + + pub fn new(inner: NodeType) -> Self { + let mut s = Self { + root_hash: OnceLock::new(), + eth_rlp_long: OnceLock::new(), + eth_rlp: OnceLock::new(), + inner, + lazy_dirty: AtomicBool::new(false), + }; + s.rehash(); + s + } + + pub fn inner(&self) -> &NodeType { + &self.inner + } + + pub fn inner_mut(&mut self) -> &mut NodeType { + &mut self.inner + } + + pub(super) fn new_from_hash( + root_hash: Option, + eth_rlp_long: Option, + inner: NodeType, + ) -> Self { + Self { + root_hash: match root_hash { + Some(h) => OnceLock::from(h), + None => OnceLock::new(), + }, + eth_rlp_long: match eth_rlp_long { + Some(b) => OnceLock::from(b), + None => OnceLock::new(), + }, + eth_rlp: OnceLock::new(), + inner, + lazy_dirty: AtomicBool::new(false), + } + } + + const ROOT_HASH_VALID_BIT: u8 = 1 << 0; + const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1; + const ETH_RLP_LONG_BIT: u8 = 1 << 2; +} + +impl Storable for Node { + fn hydrate(addr: usize, mem: &T) -> Result { + const META_SIZE: usize = 32 + 1 + 1; + let meta_raw = + mem.get_view(addr, META_SIZE as u64) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: META_SIZE as u64, + })?; + let attrs = meta_raw.as_deref()[32]; + let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { + None + } else { + Some(TrieHash( + meta_raw.as_deref()[0..32] + .try_into() + .expect("invalid slice"), + )) + }; + let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { + None + } else { + Some(attrs & Node::ETH_RLP_LONG_BIT != 0) + }; + match meta_raw.as_deref()[33] { + Self::BRANCH_NODE => { + let branch_header_size = NBRANCH as u64 * 8 + 4; + let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: branch_header_size, + }, + )?; + let mut cur = Cursor::new(node_raw.as_deref()); + let mut chd = [None; NBRANCH]; + let mut buff = [0; 8]; + for chd in chd.iter_mut() { + cur.read_exact(&mut buff)?; + let addr = usize::from_le_bytes(buff); + if addr != 0 { + *chd = Some(DiskAddress::from(addr)) + } + } + cur.read_exact(&mut buff[..4])?; + let raw_len = + u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")) as u64; + let value = if raw_len == u32::MAX as u64 { + None + } else { + Some(Data( + mem.get_view(addr + META_SIZE + branch_header_size as usize, raw_len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + branch_header_size as usize, + size: raw_len, + })? + .as_deref(), + )) + }; + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + let offset = if raw_len == u32::MAX as u64 { + addr + META_SIZE + branch_header_size as usize + } else { + addr + META_SIZE + branch_header_size as usize + raw_len as usize + }; + let mut cur_rlp_len = 0; + for chd_rlp in chd_eth_rlp.iter_mut() { + let mut buff = [0_u8; 1]; + let rlp_len_raw = mem.get_view(offset + cur_rlp_len, 1).ok_or( + ShaleError::InvalidCacheView { + offset: offset + cur_rlp_len, + size: 1, + }, + )?; + cur = Cursor::new(rlp_len_raw.as_deref()); + cur.read_exact(&mut buff)?; + let rlp_len = buff[0] as u64; + cur_rlp_len += 1; + if rlp_len != 0 { + let rlp_raw = mem.get_view(offset + cur_rlp_len, rlp_len).ok_or( + ShaleError::InvalidCacheView { + offset: offset + cur_rlp_len, + size: rlp_len, + }, + )?; + let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); + *chd_rlp = Some(rlp); + cur_rlp_len += rlp_len as usize + } + } + + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Branch(BranchNode { + chd, + value, + chd_eth_rlp, + }), + )) + } + Self::EXT_NODE => { + let ext_header_size = 1 + 8; + let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: ext_header_size, + }, + )?; + let mut cur = Cursor::new(node_raw.as_deref()); + let mut buff = [0; 8]; + cur.read_exact(&mut buff[..1])?; + let path_len = buff[0] as u64; + cur.read_exact(&mut buff)?; + let ptr = u64::from_le_bytes(buff); + + let nibbles: Vec = mem + .get_view(addr + META_SIZE + ext_header_size as usize, path_len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size as usize, + size: path_len, + })? + .as_deref() + .into_iter() + .flat_map(to_nibble_array) + .collect(); + + let (path, _) = PartialPath::decode(nibbles); + + let mut buff = [0_u8; 1]; + let rlp_len_raw = mem + .get_view( + addr + META_SIZE + ext_header_size as usize + path_len as usize, + 1, + ) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, + size: 1, + })?; + cur = Cursor::new(rlp_len_raw.as_deref()); + cur.read_exact(&mut buff)?; + let rlp_len = buff[0] as u64; + let rlp: Option> = if rlp_len != 0 { + let rlp_raw = mem + .get_view( + addr + META_SIZE + ext_header_size as usize + path_len as usize + 1, + rlp_len, + ) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + + META_SIZE + + ext_header_size as usize + + path_len as usize + + 1, + size: rlp_len, + })?; + + Some(rlp_raw.as_deref()[0..].to_vec()) + } else { + None + }; + + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), rlp)), + )) + } + Self::LEAF_NODE => { + let leaf_header_size = 1 + 4; + let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( + ShaleError::InvalidCacheView { + offset: addr + META_SIZE, + size: leaf_header_size, + }, + )?; + let mut cur = Cursor::new(node_raw.as_deref()); + let mut buff = [0; 4]; + cur.read_exact(&mut buff[..1])?; + let path_len = buff[0] as u64; + cur.read_exact(&mut buff)?; + let data_len = u32::from_le_bytes(buff) as u64; + let remainder = mem + .get_view( + addr + META_SIZE + leaf_header_size as usize, + path_len + data_len, + ) + .ok_or(ShaleError::InvalidCacheView { + offset: addr + META_SIZE + leaf_header_size as usize, + size: path_len + data_len, + })?; + + let nibbles: Vec<_> = remainder + .as_deref() + .into_iter() + .take(path_len as usize) + .flat_map(to_nibble_array) + .collect(); + + let (path, _) = PartialPath::decode(nibbles); + let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); + Ok(Self::new_from_hash( + root_hash, + eth_rlp_long, + NodeType::Leaf(LeafNode(path, value)), + )) + } + _ => Err(ShaleError::InvalidNodeType), + } + } + + fn dehydrated_len(&self) -> u64 { + 32 + 1 + + 1 + + match &self.inner { + NodeType::Branch(n) => { + let mut rlp_len = 0; + for rlp in n.chd_eth_rlp.iter() { + rlp_len += match rlp { + Some(v) => 1 + v.len() as u64, + None => 1, + } + } + NBRANCH as u64 * 8 + + 4 + + match &n.value { + Some(val) => val.len() as u64, + None => 0, + } + + rlp_len + } + NodeType::Extension(n) => { + 1 + 8 + + n.0.dehydrated_len() + + match &n.2 { + Some(v) => 1 + v.len() as u64, + None => 1, + } + } + NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, + } + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + let mut cur = Cursor::new(to); + + let mut attrs = 0; + attrs |= match self.root_hash.get() { + Some(h) => { + cur.write_all(&h.0)?; + Node::ROOT_HASH_VALID_BIT + } + None => { + cur.write_all(&[0; 32])?; + 0 + } + }; + attrs |= match self.eth_rlp_long.get() { + Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT), + None => 0, + }; + cur.write_all(&[attrs]).unwrap(); + + match &self.inner { + NodeType::Branch(n) => { + cur.write_all(&[Self::BRANCH_NODE]).unwrap(); + for c in n.chd.iter() { + cur.write_all(&match c { + Some(p) => p.to_le_bytes(), + None => 0u64.to_le_bytes(), + })?; + } + match &n.value { + Some(val) => { + cur.write_all(&(val.len() as u32).to_le_bytes())?; + cur.write_all(val)? + } + None => { + cur.write_all(&u32::MAX.to_le_bytes())?; + } + } + // Since child eth rlp will only be unset after initialization (only used for range proof), + // it is fine to encode its value adjacent to other fields. Same for extention node. + for rlp in n.chd_eth_rlp.iter() { + match rlp { + Some(v) => { + cur.write_all(&[v.len() as u8])?; + cur.write_all(v)? + } + None => cur.write_all(&0u8.to_le_bytes())?, + } + } + Ok(()) + } + NodeType::Extension(n) => { + cur.write_all(&[Self::EXT_NODE])?; + let path: Vec = from_nibbles(&n.0.encode(false)).collect(); + cur.write_all(&[path.len() as u8])?; + cur.write_all(&n.1.to_le_bytes())?; + cur.write_all(&path)?; + if n.2.is_some() { + let rlp = n.2.as_ref().unwrap(); + cur.write_all(&[rlp.len() as u8])?; + cur.write_all(rlp)?; + } + Ok(()) + } + NodeType::Leaf(n) => { + cur.write_all(&[Self::LEAF_NODE])?; + let path: Vec = from_nibbles(&n.0.encode(true)).collect(); + cur.write_all(&[path.len() as u8])?; + cur.write_all(&(n.1.len() as u32).to_le_bytes())?; + cur.write_all(&path)?; + cur.write_all(&n.1).map_err(ShaleError::Io) + } + } + } +} diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs new file mode 100644 index 000000000000..634a02995fe2 --- /dev/null +++ b/firewood/src/merkle/partial_path.rs @@ -0,0 +1,62 @@ +use std::fmt::{self, Debug}; + +/// PartialPath keeps a list of nibbles to represent a path on the Trie. +#[derive(PartialEq, Eq, Clone)] +pub struct PartialPath(pub Vec); + +impl Debug for PartialPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for nib in self.0.iter() { + write!(f, "{:x}", *nib & 0xf)?; + } + Ok(()) + } +} + +impl std::ops::Deref for PartialPath { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl PartialPath { + pub fn into_inner(self) -> Vec { + self.0 + } + + pub(super) fn encode(&self, term: bool) -> Vec { + let odd_len = (self.0.len() & 1) as u8; + let flags = if term { 2 } else { 0 } + odd_len; + let mut res = if odd_len == 1 { + vec![flags] + } else { + vec![flags, 0x0] + }; + res.extend(&self.0); + res + } + + pub fn decode>(raw: R) -> (Self, bool) { + let raw = raw.as_ref(); + let term = raw[0] > 1; + let odd_len = raw[0] & 1; + ( + Self(if odd_len == 1 { + raw[1..].to_vec() + } else { + raw[2..].to_vec() + }), + term, + ) + } + + pub(super) fn dehydrated_len(&self) -> u64 { + let len = self.0.len() as u64; + if len & 1 == 1 { + (len + 1) >> 1 + } else { + (len >> 1) + 1 + } + } +} diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs new file mode 100644 index 000000000000..922d843bf771 --- /dev/null +++ b/firewood/src/merkle/trie_hash.rs @@ -0,0 +1,49 @@ +use shale::{CachedStore, ShaleError, Storable}; +use std::{ + fmt::{self, Debug}, + io::{Cursor, Write}, +}; + +pub const TRIE_HASH_LEN: usize = 32; + +#[derive(PartialEq, Eq, Clone)] +pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); + +impl TrieHash { + const MSIZE: u64 = 32; +} + +impl std::ops::Deref for TrieHash { + type Target = [u8; TRIE_HASH_LEN]; + fn deref(&self) -> &[u8; TRIE_HASH_LEN] { + &self.0 + } +} + +impl Storable for TrieHash { + fn hydrate(addr: usize, mem: &T) -> Result { + let raw = mem + .get_view(addr, Self::MSIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::MSIZE, + })?; + Ok(Self( + raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), + )) + } + + fn dehydrated_len(&self) -> u64 { + Self::MSIZE + } + + fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + Cursor::new(to).write_all(&self.0).map_err(ShaleError::Io) + } +} + +impl Debug for TrieHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", hex::encode(self.0)) + } +} diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index ed7daf12a652..7dfbad720615 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::{ - merkle::*, + merkle::{IdTrans, Merkle, Node, Ref, RefMut, TrieHash}, proof::{Proof, ProofError}, }; use shale::{ diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index fbbd980b3c84..5bdeead49fcf 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,22 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::db::DbError; -use crate::merkle::*; -use crate::merkle_util::*; - +use crate::{ + db::DbError, + merkle::{ + to_nibble_array, BranchNode, ExtNode, LeafNode, Merkle, MerkleError, Node, NodeType, + PartialPath, NBRANCH, + }, + merkle_util::{new_merkle, DataStoreError, MerkleSetup}, +}; use nix::errno::Errno; use serde::{Deserialize, Serialize}; use sha3::Digest; -use shale::disk_address::DiskAddress; -use shale::ShaleError; -use shale::ShaleStore; - -use std::cmp::Ordering; -use std::collections::HashMap; -use std::error::Error; -use std::fmt; -use std::ops::Deref; +use shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; +use std::{cmp::Ordering, collections::HashMap, error::Error, fmt, ops::Deref}; /// Hash -> RLP encoding map #[derive(Debug, Serialize, Deserialize)] diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 7830005574ae..8154af744a2d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,10 +1,10 @@ use firewood::{ merkle::Node, - merkle_util::*, + merkle_util::{new_merkle, DataStoreError, MerkleSetup}, proof::{Proof, ProofError}, }; -use firewood_shale::{cached::DynamicMem, compact::CompactSpace}; use rand::Rng; +use shale::{cached::DynamicMem, compact::CompactSpace}; use std::collections::HashMap; type Store = CompactSpace; diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 8fa0a9876204..f4eae562a924 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -1,9 +1,7 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; - use predicates::prelude::*; use serial_test::serial; - use std::fs::remove_dir_all; const PRG: &str = "fwdctl"; diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index ca468c79e492..f17abd50b219 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "firewood-growth-ring" +name = "growth-ring" version = "0.0.3" edition = "2021" keywords = ["wal", "db", "futures"] diff --git a/libaio/Cargo.toml b/libaio/Cargo.toml index 5532cd3726e2..a81d858f2502 100644 --- a/libaio/Cargo.toml +++ b/libaio/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "firewood-libaio" +name = "libaio" version = "0.0.3" edition = "2021" keywords = ["libaio", "aio", "async", "futures"] diff --git a/shale/Cargo.toml b/shale/Cargo.toml index c0a3fb0421c6..fe75adaa0fcd 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "firewood-shale" +name = "shale" version = "0.0.3" edition = "2021" description = "Useful abstraction and light-weight implemenation for a key-value store." diff --git a/shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs index 584d2a206ab3..1d2bbb396f93 100644 --- a/shale/benches/shale-bench.rs +++ b/shale/benches/shale-bench.rs @@ -1,14 +1,14 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; -use firewood_shale::{ +use pprof::ProfilerGuard; +use rand::Rng; +use shale::{ cached::{DynamicMem, PlainMem}, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, CachedStore, Obj, StoredView, }; -use pprof::ProfilerGuard; -use rand::Rng; use std::{fs::File, os::raw::c_int, path::Path}; const BENCH_MEM_SIZE: usize = 2_000_000; From 2c6810a81a60c1a656020ef4155580aefffe6691 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 24 Aug 2023 12:55:55 -0400 Subject: [PATCH 0254/1053] Clippy fixes for latest version of Rust (#216) --- firewood/src/storage/mod.rs | 4 ++-- firewood/src/v2/emptydb.rs | 4 ++-- firewood/tests/merkle.rs | 12 +++--------- growth-ring/src/wal.rs | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 7e69a2e9fc25..73163e26c6e8 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -93,7 +93,7 @@ impl growthring::wal::Record for AshRecord { undo.offset .to_le_bytes() .into_iter() - .chain(undo_data_len.to_le_bytes().into_iter()) + .chain(undo_data_len.to_le_bytes()) .chain(undo.data.iter().copied()) .chain(redo.data.iter().copied()) }); @@ -103,7 +103,7 @@ impl growthring::wal::Record for AshRecord { .flat_map(|(space_id_bytes, undo_len, ash_bytes)| { space_id_bytes .into_iter() - .chain(undo_len.to_le_bytes().into_iter()) + .chain(undo_len.to_le_bytes()) .chain(ash_bytes) }); diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index f3d9b36839b4..31cd93212b88 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -96,7 +96,7 @@ mod tests { assert_eq!(proposal.val(b"k").await.unwrap().unwrap(), b"v"); - assert!(matches!(proposal.val(b"z").await.unwrap(), None)); + assert!(proposal.val(b"z").await.unwrap().is_none()); Ok(()) } @@ -129,7 +129,7 @@ mod tests { assert_eq!(proposal1.val(b"k").await.unwrap().unwrap(), b"v"); assert_eq!(proposal2.val(b"k").await.unwrap().unwrap(), b"v"); // only proposal1 doesn't have z - assert!(matches!(proposal1.val(b"z").await.unwrap(), None)); + assert!(proposal1.val(b"z").await.unwrap().is_none()); // proposal2 has z with value "undo" assert_eq!(proposal2.val(b"z").await.unwrap().unwrap(), b"undo"); diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 8154af744a2d..84093d017279 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -730,7 +730,7 @@ fn test_empty_range_proof() -> Result<(), ProofError> { items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let cases = vec![(items.len() - 1, false)]; + let cases = [(items.len() - 1, false)]; for (_, c) in cases.iter().enumerate() { let first = increase_key(items[c.0].0); let proof = merkle.prove(first)?; @@ -940,10 +940,7 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { let mid_index = items.len() / 2; let key = increase_key(items[mid_index - 1].0); let empty_data: [u8; 20] = [0; 20]; - items.splice( - mid_index..mid_index, - vec![(&key, &empty_data)].iter().cloned(), - ); + items.splice(mid_index..mid_index, [(&key, &empty_data)].iter().cloned()); let start = 1; let end = items.len() - 1; @@ -978,10 +975,7 @@ fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { let mid_index = items.len() / 2; let key = increase_key(items[mid_index - 1].0); let empty_data: [u8; 20] = [0; 20]; - items.splice( - mid_index..mid_index, - vec![(&key, &empty_data)].iter().cloned(), - ); + items.splice(mid_index..mid_index, [(&key, &empty_data)].iter().cloned()); let start = 0; let end = items.len() - 1; diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 9769240db015..eee2101b4228 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -658,7 +658,7 @@ impl> WalWriter { .collect(); res.into_iter() - .zip(records.into_iter()) + .zip(records) .map(|((ringid, blks), rec)| { future::try_join_all(blks.into_iter().map(|idx| writes[idx].clone())) .or_else(|_| future::ready(Err(()))) From dbac196fd1358dd7fe97c39d7e847c3279f16ae5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 24 Aug 2023 13:15:32 -0400 Subject: [PATCH 0255/1053] Remove ValueTransformer + IdTrans (#214) --- firewood/src/db.rs | 7 ++--- firewood/src/merkle.rs | 16 ++++------- firewood/src/merkle/node.rs | 55 ++++++++++++++----------------------- firewood/src/merkle_util.rs | 6 ++-- growth-ring/src/wal.rs | 2 +- 5 files changed, 32 insertions(+), 54 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 13edd5bc4d50..c76d86f084e5 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -7,7 +7,7 @@ pub use crate::{ }; use crate::{ file, - merkle::{IdTrans, Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, + merkle::{Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, proof::{Proof, ProofError}, storage::{ buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, @@ -293,7 +293,7 @@ impl + Send + Sync> DbRev { /// Get root hash of the generic key-value storage. pub fn kv_root_hash(&self) -> Result { self.merkle - .root_hash::(self.header.kv_root) + .root_hash(self.header.kv_root) .map_err(DbError::Merkle) } @@ -315,8 +315,7 @@ impl + Send + Sync> DbRev { /// Provides a proof that a key is in the Trie. pub fn prove>(&self, key: K) -> Result { - self.merkle - .prove::<&[u8], IdTrans>(key.as_ref(), self.header.kv_root) + self.merkle.prove(key, self.header.kv_root) } /// Verifies a range proof is valid for a set of keys. diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 188458da166d..d4516993142d 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,9 +16,7 @@ mod node; mod partial_path; mod trie_hash; -pub use node::{ - BranchNode, Data, ExtNode, IdTrans, LeafNode, Node, NodeType, ValueTransformer, NBRANCH, -}; +pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; pub use partial_path::PartialPath; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; @@ -113,10 +111,7 @@ impl + Send + Sync> Merkle { }) } - pub fn root_hash( - &self, - root: DiskAddress, - ) -> Result { + pub fn root_hash(&self, root: DiskAddress) -> Result { let root = self .get_node(root)? .inner @@ -125,7 +120,7 @@ impl + Send + Sync> Merkle { .chd[0]; Ok(if let Some(root) = root { let mut node = self.get_node(root)?; - let res = node.get_root_hash::(self.store.as_ref()).clone(); + let res = node.get_root_hash::(self.store.as_ref()).clone(); if node.lazy_dirty.load(Ordering::Relaxed) { node.write(|_| {}).unwrap(); node.lazy_dirty.store(false, Ordering::Relaxed); @@ -1036,10 +1031,9 @@ impl + Send + Sync> Merkle { /// If the trie does not contain a value for key, the returned proof contains /// all nodes of the longest existing prefix of the key, ending with the node /// that proves the absence of the key (at least the root node). - pub fn prove(&self, key: K, root: DiskAddress) -> Result + pub fn prove>(&self, key: K, root: DiskAddress) -> Result where K: AsRef<[u8]>, - T: ValueTransformer, { let key_nibbles = Nibbles::<0>::new(key.as_ref()); @@ -1112,7 +1106,7 @@ impl + Send + Sync> Merkle { // Get the hashes of the nodes. for node in nodes { let node = self.get_node(node)?; - let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); + let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(rlp).into(); proofs.insert(hash, rlp.to_vec()); } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index b91f58eaa120..17c8a9d98a74 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -26,18 +26,6 @@ impl std::ops::Deref for Data { } } -pub trait ValueTransformer { - fn transform(bytes: &[u8]) -> Vec; -} - -pub struct IdTrans; - -impl ValueTransformer for IdTrans { - fn transform(bytes: &[u8]) -> Vec { - bytes.to_vec() - } -} - #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { pub(super) chd: [Option; NBRANCH], @@ -86,21 +74,21 @@ impl BranchNode { (only_chd, has_chd) } - fn calc_eth_rlp>(&self, store: &S) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { let mut stream = rlp::RlpStream::new_list(17); for (i, c) in self.chd.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); - if c_ref.get_eth_rlp_long::(store) { - stream.append(&&(*c_ref.get_root_hash::(store))[..]); + if c_ref.get_eth_rlp_long::(store) { + stream.append(&&(*c_ref.get_root_hash::(store))[..]); // See struct docs for ordering requirements if c_ref.lazy_dirty.load(Ordering::Relaxed) { c_ref.write(|_| {}).unwrap(); c_ref.lazy_dirty.store(false, Ordering::Relaxed) } } else { - let c_rlp = &c_ref.get_eth_rlp::(store); + let c_rlp = &c_ref.get_eth_rlp::(store); stream.append_raw(c_rlp, 1); } } @@ -170,10 +158,10 @@ impl Debug for LeafNode { } impl LeafNode { - fn calc_eth_rlp(&self) -> Vec { + fn calc_eth_rlp(&self) -> Vec { rlp::encode_list::, _>(&[ from_nibbles(&self.0.encode(true)).collect(), - T::transform(&self.1), + self.1.to_vec(), ]) .into() } @@ -205,19 +193,19 @@ impl Debug for ExtNode { } impl ExtNode { - fn calc_eth_rlp>(&self, store: &S) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { let mut stream = rlp::RlpStream::new_list(2); if !self.1.is_null() { let mut r = store.get_item(self.1).unwrap(); stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); - if r.get_eth_rlp_long::(store) { - stream.append(&&(*r.get_root_hash::(store))[..]); + if r.get_eth_rlp_long(store) { + stream.append(&&(*r.get_root_hash(store))[..]); if r.lazy_dirty.load(Ordering::Relaxed) { r.write(|_| {}).unwrap(); r.lazy_dirty.store(false, Ordering::Relaxed); } } else { - stream.append_raw(r.get_eth_rlp::(store), 1); + stream.append_raw(r.get_eth_rlp(store), 1); } } else { // Check if there is already a caclucated rlp for the child, which @@ -307,11 +295,11 @@ pub enum NodeType { } impl NodeType { - fn calc_eth_rlp>(&self, store: &S) -> Vec { + fn calc_eth_rlp>(&self, store: &S) -> Vec { match &self { - NodeType::Leaf(n) => n.calc_eth_rlp::(), - NodeType::Extension(n) => n.calc_eth_rlp::(store), - NodeType::Branch(n) => n.calc_eth_rlp::(store), + NodeType::Leaf(n) => n.calc_eth_rlp(), + NodeType::Extension(n) => n.calc_eth_rlp(store), + NodeType::Branch(n) => n.calc_eth_rlp(store), } } } @@ -339,25 +327,22 @@ impl Node { }) } - pub(super) fn get_eth_rlp>(&self, store: &S) -> &[u8] { + pub(super) fn get_eth_rlp>(&self, store: &S) -> &[u8] { self.eth_rlp - .get_or_init(|| self.inner.calc_eth_rlp::(store)) + .get_or_init(|| self.inner.calc_eth_rlp::(store)) } - pub(super) fn get_root_hash>( - &self, - store: &S, - ) -> &TrieHash { + pub(super) fn get_root_hash>(&self, store: &S) -> &TrieHash { self.root_hash.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - TrieHash(Keccak256::digest(self.get_eth_rlp::(store)).into()) + TrieHash(Keccak256::digest(self.get_eth_rlp::(store)).into()) }) } - fn get_eth_rlp_long>(&self, store: &S) -> bool { + fn get_eth_rlp_long>(&self, store: &S) -> bool { *self.eth_rlp_long.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - self.get_eth_rlp::(store).len() >= TRIE_HASH_LEN + self.get_eth_rlp(store).len() >= TRIE_HASH_LEN }) } diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 7dfbad720615..39c94ce19314 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::{ - merkle::{IdTrans, Merkle, Node, Ref, RefMut, TrieHash}, + merkle::{Merkle, Node, Ref, RefMut, TrieHash}, proof::{Proof, ProofError}, }; use shale::{ @@ -74,7 +74,7 @@ impl + Send + Sync> MerkleSetup { pub fn root_hash(&self) -> Result { self.merkle - .root_hash::(self.root) + .root_hash(self.root) .map_err(|_err| DataStoreError::RootHashError) } @@ -88,7 +88,7 @@ impl + Send + Sync> MerkleSetup { pub fn prove>(&self, key: K) -> Result { self.merkle - .prove::(key, self.root) + .prove(key, self.root) .map_err(|_err| DataStoreError::ProofError) } diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index eee2101b4228..8b7de8c29ba5 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -369,7 +369,7 @@ impl> WalFilePool { let mut last_h: Option< Pin, WalError>> + 'a>>, > = None; - for ((next_fid, wl), h) in meta.into_iter().zip(files.into_iter()) { + for ((next_fid, wl), h) in meta.into_iter().zip(files) { if let Some(lh) = last_h.take() { if next_fid != fid { lh.await? From 88b731e058fd539cad716924291547c87d74f1ef Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 24 Aug 2023 14:30:57 -0400 Subject: [PATCH 0256/1053] Use v2::api::Proof over proof::Proof (#215) --- firewood/examples/rev.rs | 4 ++-- firewood/src/db.rs | 12 ++++++------ firewood/src/merkle.rs | 6 +++--- firewood/src/merkle_util.rs | 13 +++++++------ firewood/src/proof.rs | 34 ++++++++++++++++++++-------------- firewood/src/v2/api.rs | 4 +++- firewood/tests/db.rs | 2 +- firewood/tests/merkle.rs | 5 +++-- 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 5ebd75d7893d..93fe3097d377 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -6,8 +6,8 @@ use std::{collections::VecDeque, path::Path}; use firewood::{ db::{BatchOp, Db, DbConfig, Proposal, Revision, WalConfig}, merkle::{Node, TrieHash}, - proof::Proof, storage::StoreRevShared, + v2::api::Proof, }; use shale::compact::CompactSpace; @@ -166,7 +166,7 @@ impl RevisionTracker { } } -fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof { +fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof> { let mut proof = revision.prove(items[0].0).unwrap(); let end = revision.prove(items.last().unwrap().0).unwrap(); proof.concat_proofs(end); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c76d86f084e5..30520a57511b 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,12 +8,13 @@ pub use crate::{ use crate::{ file, merkle::{Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, - proof::{Proof, ProofError}, + proof::ProofError, storage::{ buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }, + v2::api::Proof, }; use bytemuck::{cast_slice, AnyBitPattern}; use metered::{metered, HitCount}; @@ -313,15 +314,14 @@ impl + Send + Sync> DbRev { .map_err(DbError::Merkle) } - /// Provides a proof that a key is in the Trie. - pub fn prove>(&self, key: K) -> Result { - self.merkle.prove(key, self.header.kv_root) + pub fn prove>(&self, key: K) -> Result>, MerkleError> { + self.merkle.prove::(key, self.header.kv_root) } /// Verifies a range proof is valid for a set of keys. - pub fn verify_range_proof, V: AsRef<[u8]>>( + pub fn verify_range_proof + Send, K: AsRef<[u8]>, V: AsRef<[u8]>>( &self, - proof: Proof, + proof: Proof, first_key: K, last_key: K, keys: Vec, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d4516993142d..64b32df1c885 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{nibbles::Nibbles, proof::Proof}; +use crate::{nibbles::Nibbles, v2::api::Proof}; use sha3::Digest; use shale::{disk_address::DiskAddress, ObjRef, ShaleError, ShaleStore}; use std::{ @@ -1031,13 +1031,13 @@ impl + Send + Sync> Merkle { /// If the trie does not contain a value for key, the returned proof contains /// all nodes of the longest existing prefix of the key, ending with the node /// that proves the absence of the key (at least the root node). - pub fn prove>(&self, key: K, root: DiskAddress) -> Result + pub fn prove(&self, key: K, root: DiskAddress) -> Result>, MerkleError> where K: AsRef<[u8]>, { let key_nibbles = Nibbles::<0>::new(key.as_ref()); - let mut proofs: HashMap<[u8; TRIE_HASH_LEN], Vec> = HashMap::new(); + let mut proofs = HashMap::new(); if root.is_null() { return Ok(Proof(proofs)); } diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 39c94ce19314..a3b5fa4659ed 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -3,7 +3,8 @@ use crate::{ merkle::{Merkle, Node, Ref, RefMut, TrieHash}, - proof::{Proof, ProofError}, + proof::ProofError, + v2::api::Proof, }; use shale::{ cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, @@ -86,16 +87,16 @@ impl + Send + Sync> MerkleSetup { String::from_utf8(s).map_err(|_err| DataStoreError::UTF8Error) } - pub fn prove>(&self, key: K) -> Result { + pub fn prove>(&self, key: K) -> Result>, DataStoreError> { self.merkle .prove(key, self.root) .map_err(|_err| DataStoreError::ProofError) } - pub fn verify_proof>( + pub fn verify_proof + Send, K: AsRef<[u8]>>( &self, key: K, - proof: &Proof, + proof: &Proof, ) -> Result>, DataStoreError> { let hash: [u8; 32] = *self.root_hash()?; proof @@ -103,9 +104,9 @@ impl + Send + Sync> MerkleSetup { .map_err(|_err| DataStoreError::ProofVerificationError) } - pub fn verify_range_proof, V: AsRef<[u8]>>( + pub fn verify_range_proof + Send, K: AsRef<[u8]>, V: AsRef<[u8]>>( &self, - proof: &Proof, + proof: &Proof, first_key: K, last_key: K, keys: Vec, diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 5bdeead49fcf..6e94961c446f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -10,14 +10,17 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; use nix::errno::Errno; -use serde::{Deserialize, Serialize}; use sha3::Digest; -use shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; -use std::{cmp::Ordering, collections::HashMap, error::Error, fmt, ops::Deref}; +use shale::disk_address::DiskAddress; +use shale::ShaleError; +use shale::ShaleStore; -/// Hash -> RLP encoding map -#[derive(Debug, Serialize, Deserialize)] -pub struct Proof(pub HashMap<[u8; 32], Vec>); +use std::cmp::Ordering; +use std::error::Error; +use std::fmt; +use std::ops::Deref; + +use crate::v2::api::Proof; #[derive(Debug)] pub enum ProofError { @@ -116,10 +119,12 @@ pub struct SubProof { hash: Option<[u8; 32]>, } -impl Proof { +impl + Send> Proof { /// verify_proof checks merkle proofs. The given proof must contain the value for /// key in a trie with the given root hash. VerifyProof returns an error if the /// proof contains invalid trie nodes or the wrong value. + /// + /// The generic N represents the storage for the node data pub fn verify_proof>( &self, key: K, @@ -137,7 +142,8 @@ impl Proof { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; - let (sub_proof, size) = self.locate_subproof(remaining_key_nibbles, cur_proof)?; + let (sub_proof, size) = + self.locate_subproof(remaining_key_nibbles, cur_proof.as_ref())?; index += size; match sub_proof { @@ -242,7 +248,7 @@ impl Proof { } } - pub fn concat_proofs(&mut self, other: Proof) { + pub fn concat_proofs(&mut self, other: Proof) { self.0.extend(other.0) } @@ -290,7 +296,7 @@ impl Proof { // ensure there are no more accounts / slots in the trie. if keys.is_empty() { let proof_to_path = - self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; + self.proof_to_path(first_key, root_hash, &mut merkle_setup, true)?; return match proof_to_path { Some(_) => Err(ProofError::InvalidData), None => Ok(false), @@ -365,9 +371,9 @@ impl Proof { /// necessary nodes will be resolved and leave the remaining as hashnode. /// /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path, S: ShaleStore + Send + Sync>( + fn proof_to_path, S: ShaleStore + Send + Sync>( &self, - key: K, + key: KV, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, @@ -392,7 +398,7 @@ impl Proof { .ok_or(ProofError::ProofNodeMissing)?; // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. let (mut chd_ptr, sub_proof, size) = - self.decode_node(merkle, cur_key, cur_proof, false)?; + self.decode_node(merkle, cur_key, cur_proof.as_ref(), false)?; // Link the child to the parent based on the node type. match &u_ref.inner() { @@ -464,7 +470,7 @@ impl Proof { let proof = proofs_map.get(p_hash).ok_or(ProofError::ProofNodeMissing)?; - chd_ptr = self.decode_node(merkle, cur_key, proof, true)?.0; + chd_ptr = self.decode_node(merkle, cur_key, proof.as_ref(), true)?.0; // Link the child to the parent based on the node type. match &u_ref.inner() { diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index bf69bc66f990..4e119eb17272 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -76,8 +76,10 @@ pub struct RangeProof { } /// A proof that a single key is present +/// +/// The generic N represents the storage for the node data #[derive(Debug)] -pub struct Proof(pub HashMap); +pub struct Proof(pub HashMap); /// The database interface, which includes a type for a static view of /// the database (the DbView). The most common implementation of the DbView diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index e83805bbefc6..dfb867936b1f 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -209,7 +209,7 @@ fn create_db_issue_proof() { let key = "doe".as_bytes(); let root_hash = rev.kv_root_hash(); - match rev.prove(key) { + match rev.prove::<&[u8]>(key) { Ok(proof) => { let verification = proof.verify_proof(key, *root_hash.unwrap()).unwrap(); assert!(verification.is_some()); diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 84093d017279..57db390164ac 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,7 +1,8 @@ use firewood::{ merkle::Node, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, - proof::{Proof, ProofError}, + proof::ProofError, + v2::api::Proof, }; use rand::Rng; use shale::{cached::DynamicMem, compact::CompactSpace}; @@ -679,7 +680,7 @@ fn test_all_elements_proof() -> Result<(), ProofError> { let keys: Vec<&[u8; 32]> = item_iter.clone().map(|item| item.0).collect(); let vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); - let empty_proof = Proof(HashMap::new()); + let empty_proof = Proof(HashMap::<[u8; 32], Vec>::new()); let empty_key: [u8; 32] = [0; 32]; merkle.verify_range_proof( &empty_proof, From 413d6e416fb68a4ccbd2d3bf834f59ed42456fcf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 24 Aug 2023 15:36:47 -0400 Subject: [PATCH 0257/1053] Switch proof.rs to thiserror (#218) --- firewood/src/proof.rs | 107 +++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 65 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 6e94961c446f..c324d7559d4f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,6 +1,16 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::cmp::Ordering; +use std::ops::Deref; + +use nix::errno::Errno; +use sha3::Digest; +use shale::disk_address::DiskAddress; +use shale::ShaleError; +use shale::ShaleStore; +use thiserror::Error; + use crate::{ db::DbError, merkle::{ @@ -8,40 +18,48 @@ use crate::{ PartialPath, NBRANCH, }, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, + v2::api::Proof, }; -use nix::errno::Errno; -use sha3::Digest; -use shale::disk_address::DiskAddress; -use shale::ShaleError; -use shale::ShaleStore; - -use std::cmp::Ordering; -use std::error::Error; -use std::fmt; -use std::ops::Deref; -use crate::v2::api::Proof; - -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ProofError { - DecodeError, + #[error("decoding error")] + DecodeError(#[from] rlp::DecoderError), + #[error("no such node")] NoSuchNode, + #[error("proof node missing")] ProofNodeMissing, + #[error("inconsistent proof data")] InconsistentProofData, + #[error("non-monotonic range increase")] NonMonotonicIncreaseRange, + #[error("range has deletion")] RangeHasDeletion, + #[error("invalid data")] InvalidData, + #[error("invalid proof")] InvalidProof, + #[error("invalid edge keys")] InvalidEdgeKeys, + #[error("inconsisent edge keys")] InconsistentEdgeKeys, + #[error("node insertion error")] NodesInsertionError, + #[error("node not in trie")] NodeNotInTrie, - InvalidNode(MerkleError), + #[error("invalid node {0:?}")] + InvalidNode(#[from] MerkleError), + #[error("empty range")] EmptyRange, + #[error("fork left")] ForkLeft, + #[error("fork right")] ForkRight, + #[error("system error: {0:?}")] SystemError(Errno), + #[error("shale error: {0:?}")] Shale(ShaleError), + #[error("invalid root hash")] InvalidRootHash, } @@ -73,40 +91,6 @@ impl From for ProofError { } } -impl From for ProofError { - fn from(_: rlp::DecoderError) -> ProofError { - ProofError::DecodeError - } -} - -impl fmt::Display for ProofError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ProofError::DecodeError => write!(f, "decoding"), - ProofError::NoSuchNode => write!(f, "no such node"), - ProofError::ProofNodeMissing => write!(f, "proof node missing"), - ProofError::InconsistentProofData => write!(f, "inconsistent proof data"), - ProofError::NonMonotonicIncreaseRange => write!(f, "nonmonotonic range increase"), - ProofError::RangeHasDeletion => write!(f, "range has deletion"), - ProofError::InvalidData => write!(f, "invalid data"), - ProofError::InvalidProof => write!(f, "invalid proof"), - ProofError::InvalidEdgeKeys => write!(f, "invalid edge keys"), - ProofError::InconsistentEdgeKeys => write!(f, "inconsistent edge keys"), - ProofError::NodesInsertionError => write!(f, "node insertion error"), - ProofError::NodeNotInTrie => write!(f, "node not in trie"), - ProofError::InvalidNode(e) => write!(f, "invalid node: {e:?}"), - ProofError::EmptyRange => write!(f, "empty range"), - ProofError::ForkLeft => write!(f, "fork left"), - ProofError::ForkRight => write!(f, "fork right"), - ProofError::SystemError(e) => write!(f, "system error: {e:?}"), - ProofError::InvalidRootHash => write!(f, "invalid root hash provided"), - ProofError::Shale(e) => write!(f, "shale error: {e:?}"), - } - } -} - -impl Error for ProofError {} - const EXT_NODE_SIZE: usize = 2; const BRANCH_NODE_SIZE: usize = 17; @@ -222,7 +206,8 @@ impl + Send> Proof { .map(|subproof| (Some(subproof), 1)) } - _ => Err(ProofError::DecodeError), + Ok(_) => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), + Err(e) => Err(ProofError::DecodeError(e)), } } @@ -244,7 +229,7 @@ impl + Send> Proof { hash: Some(sub_hash), }) } - _ => Err(ProofError::DecodeError), + _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), } } @@ -438,9 +423,7 @@ impl + Send> Proof { _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; - u_ref = merkle - .get_node(chd_ptr) - .map_err(|_| ProofError::DecodeError)?; + u_ref = merkle.get_node(chd_ptr)?; // If the new parent is a branch node, record the index to correctly link the next child to it. if u_ref.inner().as_branch().is_some() { @@ -512,9 +495,7 @@ impl + Send> Proof { drop(u_ref); - let c_ref = merkle - .get_node(chd_ptr) - .map_err(|_| ProofError::DecodeError)?; + let c_ref = merkle.get_node(chd_ptr)?; match &c_ref.inner() { NodeType::Branch(n) => { @@ -670,7 +651,7 @@ impl + Send> Proof { } // RLP length can only be the two cases above. - _ => Err(ProofError::DecodeError), + _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), } } } @@ -693,7 +674,7 @@ fn get_ext_ptr + Send + Sync>( merkle .new_node(Node::new(node)) .map(|node| node.as_ptr()) - .map_err(|_| ProofError::DecodeError) + .map_err(ProofError::InvalidNode) } fn build_branch_ptr + Send + Sync>( @@ -759,9 +740,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( }; parent = u_ref.as_ptr(); - u_ref = merkle - .get_node(left_node.unwrap()) - .map_err(|_| ProofError::DecodeError)?; + u_ref = merkle.get_node(left_node.unwrap())?; index += 1; } @@ -787,9 +766,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( } parent = u_ref.as_ptr(); - u_ref = merkle - .get_node(n.chd()) - .map_err(|_| ProofError::DecodeError)?; + u_ref = merkle.get_node(n.chd())?; index += cur_key.len(); } From 1512fe626497554d3fca39b26b9f5a9fc45be8d2 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 24 Aug 2023 22:41:20 -0700 Subject: [PATCH 0258/1053] Cleanup of `rev.rs` (#217) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/examples/rev.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 93fe3097d377..44ce44f14d17 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -4,7 +4,7 @@ use std::{collections::VecDeque, path::Path}; use firewood::{ - db::{BatchOp, Db, DbConfig, Proposal, Revision, WalConfig}, + db::{BatchOp, Db, DbConfig, DbError, Proposal, Revision, WalConfig}, merkle::{Node, TrieHash}, storage::StoreRevShared, v2::api::Proof, @@ -14,7 +14,7 @@ use shale::compact::CompactSpace; type SharedStore = CompactSpace; /// cargo run --example rev -fn main() { +fn main() -> Result<(), DbError> { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) @@ -23,9 +23,9 @@ fn main() { let mut revision_tracker = RevisionTracker::new(db); - revision_tracker.create_revisions(items.into_iter()); + revision_tracker.create_revisions(items.into_iter())?; - revision_tracker.db.kv_dump(&mut std::io::stdout()).unwrap(); + revision_tracker.db.kv_dump(&mut std::io::stdout())?; verify_root_hashes(&mut revision_tracker); @@ -102,6 +102,7 @@ fn main() { .unwrap(); }); }); + Ok(()) } struct RevisionTracker { @@ -117,15 +118,15 @@ impl RevisionTracker { } } - fn create_revisions(&mut self, iter: impl Iterator) + fn create_revisions(&mut self, iter: impl Iterator) -> Result<(), DbError> where K: AsRef<[u8]>, V: AsRef<[u8]>, { - iter.for_each(|(k, v)| self.create_revision(k, v)); + iter.map(|(k, v)| self.create_revision(k, v)).collect() } - fn create_revision(&mut self, k: K, v: V) + fn create_revision(&mut self, k: K, v: V) -> Result<(), DbError> where K: AsRef<[u8]>, V: AsRef<[u8]>, @@ -134,11 +135,11 @@ impl RevisionTracker { key: k, value: v.as_ref().to_vec(), }]; - let proposal = self.db.new_proposal(batch).unwrap(); - proposal.commit().unwrap(); + self.db.new_proposal(batch)?.commit()?; let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); + Ok(()) } fn commit_proposal(&mut self, proposal: Proposal) { From f0f88c962e625283f68ab74582ca27277c4ee782 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 25 Aug 2023 12:57:59 -0400 Subject: [PATCH 0259/1053] Additional clippy lints from latest nightly (#219) --- Cargo.toml | 1 + firewood/src/merkle.rs | 3 +-- firewood/src/nibbles.rs | 12 ------------ firewood/src/storage/mod.rs | 3 +-- growth-ring/src/wal.rs | 6 ++---- growth-ring/tests/common/mod.rs | 6 ++---- libaio/src/lib.rs | 8 +++----- shale/src/cached.rs | 4 ---- 8 files changed, 10 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1d8190aee07..8ede346b7d51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "firewood", "fwdctl", ] +resolver = "2" [profile.release] debug = true diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 64b32df1c885..c4bf139a0ea4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1318,8 +1318,7 @@ mod test { #[test] fn test_merkle_node_encoding() { let check = |node: Node| { - let mut bytes = Vec::new(); - bytes.resize(node.dehydrated_len() as usize, 0); + let mut bytes = vec![0; node.dehydrated_len() as usize]; node.dehydrate(&mut bytes).unwrap(); let mut mem = PlainMem::new(bytes.len() as u64, 0x0); diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 5f65caf060e6..7837a8e1acb7 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -130,12 +130,6 @@ mod test { expected.into_iter().eq(nib.iter()); } - #[test] - fn skip_zero() { - let nib = Nibbles::<0>(&TEST_BYTES); - assert!(nib.iter().skip(0).eq(nib.iter())); - } - #[test] fn skip_skips_zeroes() { let nib1 = Nibbles::<1>(&TEST_BYTES); @@ -150,12 +144,6 @@ mod test { let _ = nib[8]; } - #[test] - fn skip_before_zeroes() { - let nib = Nibbles::<1>(&TEST_BYTES); - assert!(nib.iter().skip(0).eq(nib.iter())); - } - #[test] fn last_nibble() { let nib = Nibbles::<0>(&TEST_BYTES); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 73163e26c6e8..b79f9170262d 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -655,8 +655,7 @@ fn test_from_ash() { let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); for _ in 0..20 { let n = 20; - let mut canvas = Vec::new(); - canvas.resize((max - min) as usize, 0); + let mut canvas = vec![0; (max - min) as usize]; let mut writes: Vec<_> = Vec::new(); for _ in 0..n { let l = rng.gen_range(min..max); diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 8b7de8c29ba5..7208102d129e 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -254,8 +254,6 @@ impl> WalFilePool { block_nbit: u64, cache_size: NonZeroUsize, ) -> Result { - let file_nbit = file_nbit; - let block_nbit = block_nbit; let header_file = store.open_file("HEAD", true).await?; header_file.truncate(HEADER_SIZE).await?; Ok(WalFilePool { @@ -1131,8 +1129,8 @@ impl WalLoader { die!() } chunks.push(chunk); - let mut payload = Vec::new(); - payload.resize(chunks.iter().fold(0, |acc, v| acc + v.len()), 0); + let mut payload = + vec![0; chunks.iter().fold(0, |acc, v| acc + v.len())]; let mut ps = &mut payload[..]; for c in chunks { ps[..c.len()].copy_from_slice(&c); diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index a7580d67dd7f..fe21dd29992c 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -331,10 +331,8 @@ pub struct Canvas { impl Canvas { pub fn new(size: usize) -> Self { - let mut canvas = Vec::new(); - // fill the backgroudn color 0 - canvas.resize(size, 0); - let canvas = canvas.into_boxed_slice(); + let canvas = vec![0; size].into_boxed_slice(); + // fill the background color 0 Canvas { waiting: HashMap::new(), queue: IndexMap::new(), diff --git a/libaio/src/lib.rs b/libaio/src/lib.rs index 37ce82def47c..2bb8a8021fbf 100644 --- a/libaio/src/lib.rs +++ b/libaio/src/lib.rs @@ -441,9 +441,7 @@ impl AioManager { pub fn read(&self, fd: RawFd, offset: u64, length: usize, priority: Option) -> AioFuture { let priority = priority.unwrap_or(0); - let mut data = Vec::new(); - data.resize(length, 0); - let data = data.into_boxed_slice(); + let data = vec![0; length].into_boxed_slice(); let aio = Aio::new( self.scheduler_in.next_id(), fd, @@ -481,8 +479,8 @@ impl AioManager { let w = self.notifier.waiting.lock(); w.get(&aio_id).map(|state| { match state { - AioState::FutureInit(aio, _) => &**aio.data.as_ref().unwrap(), - AioState::FuturePending(aio, _, _) => &**aio.data.as_ref().unwrap(), + AioState::FutureInit(aio, _) => aio.data.as_ref().unwrap(), + AioState::FuturePending(aio, _, _) => aio.data.as_ref().unwrap(), AioState::FutureDone(res) => &res.1, } .to_vec() diff --git a/shale/src/cached.rs b/shale/src/cached.rs index 8963ce665cfa..15b7edf624a8 100644 --- a/shale/src/cached.rs +++ b/shale/src/cached.rs @@ -29,7 +29,6 @@ impl CachedStore for PlainMem { offset: usize, length: u64, ) -> Option>>> { - let offset = offset; let length = length as usize; if offset + length > self.space.read().unwrap().len() { None @@ -53,7 +52,6 @@ impl CachedStore for PlainMem { } fn write(&mut self, offset: usize, change: &[u8]) { - let offset = offset; let length = change.len(); let mut vect = self.space.deref().write().unwrap(); vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); @@ -116,7 +114,6 @@ impl CachedStore for DynamicMem { offset: usize, length: u64, ) -> Option>>> { - let offset = offset; let length = length as usize; let size = offset + length; let mut space = self.space.write().unwrap(); @@ -144,7 +141,6 @@ impl CachedStore for DynamicMem { } fn write(&mut self, offset: usize, change: &[u8]) { - let offset = offset; let length = change.len(); let size = offset + length; From 07386f8ff5f46ea2f5f98b42b2f0e987b65ac4b2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 25 Aug 2023 13:13:57 -0400 Subject: [PATCH 0260/1053] Merkle: use thiserror (#221) --- firewood/src/merkle.rs | 46 ++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index c4bf139a0ea4..28994e6d7e84 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,11 +6,10 @@ use sha3::Digest; use shale::{disk_address::DiskAddress, ObjRef, ShaleError, ShaleStore}; use std::{ collections::HashMap, - error::Error, - fmt::{self, Debug}, io::Write, sync::{atomic::Ordering, OnceLock}, }; +use thiserror::Error; mod node; mod partial_path; @@ -20,32 +19,22 @@ pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; pub use partial_path::PartialPath; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum MerkleError { - Shale(ShaleError), + #[error("merkle datastore error: {0:?}")] + Shale(#[from] ShaleError), + #[error("read only")] ReadOnly, + #[error("node not a branch node")] NotBranchNode, - Format(std::io::Error), + #[error("format error: {0:?}")] + Format(#[from] std::io::Error), + #[error("parent should not be a leaf branch")] ParentLeafBranch, + #[error("removing internal node references failed")] UnsetInternal, } -impl fmt::Display for MerkleError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - MerkleError::Shale(e) => write!(f, "merkle datastore error: {e:?}"), - MerkleError::ReadOnly => write!(f, "error: read only"), - MerkleError::NotBranchNode => write!(f, "error: node is not a branch node"), - MerkleError::Format(e) => write!(f, "format error: {e:?}"), - MerkleError::ParentLeafBranch => write!(f, "parent should not be a leaf branch"), - MerkleError::UnsetInternal => write!(f, "removing internal node references failed"), - } - } -} - -// TODO: use thiserror -impl Error for MerkleError {} - macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let Err(_) = $r.write($modify) { @@ -66,13 +55,13 @@ pub struct Merkle { impl + Send + Sync> Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result, MerkleError> { - self.store.get_item(ptr).map_err(MerkleError::Shale) + self.store.get_item(ptr).map_err(Into::into) } pub fn new_node(&self, item: Node) -> Result, MerkleError> { - self.store.put_item(item, 0).map_err(MerkleError::Shale) + self.store.put_item(item, 0).map_err(Into::into) } fn free_node(&mut self, ptr: DiskAddress) -> Result<(), MerkleError> { - self.store.free_item(ptr).map_err(MerkleError::Shale) + self.store.free_item(ptr).map_err(Into::into) } } @@ -140,18 +129,17 @@ impl + Send + Sync> Merkle { Some(h) => hex::encode(**h), None => "".to_string(), } - ) - .map_err(MerkleError::Format)?; + )?; match &u_ref.inner { NodeType::Branch(n) => { - writeln!(w, "{n:?}").map_err(MerkleError::Format)?; + writeln!(w, "{n:?}")?; for c in n.chd.iter().flatten() { self.dump_(*c, w)? } } NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), NodeType::Extension(n) => { - writeln!(w, "{n:?}").map_err(MerkleError::Format)?; + writeln!(w, "{n:?}")?; self.dump_(n.1, w)? } } @@ -160,7 +148,7 @@ impl + Send + Sync> Merkle { pub fn dump(&self, root: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { if root.is_null() { - write!(w, "").map_err(MerkleError::Format)?; + write!(w, "")?; } else { self.dump_(root, w)?; }; From 7e9dc91e0489bb940f314405fda1b7a55ca12244 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 25 Aug 2023 17:26:04 -0400 Subject: [PATCH 0261/1053] Rkuris/nibbles iterative improvement (#220) Signed-off-by: Ron Kuris --- firewood/src/nibbles.rs | 64 +++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 7837a8e1acb7..eef36f3db418 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -1,4 +1,4 @@ -use std::ops::Index; +use std::{iter::FusedIterator, ops::Index}; static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; @@ -29,6 +29,9 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// // nibbles can also be indexed /// /// assert_eq!(nib[1], 0x6); +/// +/// // or reversed +/// assert_eq!(nib.iter().rev().next(), Some(0x8)); /// # } /// ``` #[derive(Debug)] @@ -51,7 +54,11 @@ impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROE impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { #[must_use] pub fn iter(&self) -> NibblesIterator<'_, LEADING_ZEROES> { - NibblesIterator { data: self, pos: 0 } + NibblesIterator { + data: self, + head: 0, + tail: self.len(), + } } #[must_use] @@ -71,36 +78,60 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { /// An interator returned by [Nibbles::iter] /// See their documentation for details. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { data: &'a Nibbles<'a, LEADING_ZEROES>, - pos: usize, + head: usize, + tail: usize, } +impl<'a, const LEADING_ZEROES: usize> FusedIterator for NibblesIterator<'a, LEADING_ZEROES> {} + impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_ZEROES> { type Item = u8; fn next(&mut self) -> Option { - let result = if self.pos >= LEADING_ZEROES + self.data.0.len() * 2 { - None - } else { - Some(self.data[self.pos]) - }; - self.pos += 1; + if self.is_empty() { + return None; + } + let result = Some(self.data[self.head]); + self.head += 1; result } fn nth(&mut self, n: usize) -> Option { - self.pos += n; + self.head += std::cmp::min(n, self.tail - self.head); self.next() } fn size_hint(&self) -> (usize, Option) { - let remaining = self.data.len() - self.pos; + let remaining = self.tail - self.head; (remaining, Some(remaining)) } } +impl<'a, const LEADING_ZEROES: usize> NibblesIterator<'a, LEADING_ZEROES> { + #[inline(always)] + fn is_empty(&self) -> bool { + self.head == self.tail + } +} + +impl<'a, const LEADING_ZEROES: usize> DoubleEndedIterator for NibblesIterator<'a, LEADING_ZEROES> { + fn next_back(&mut self) -> Option { + if self.is_empty() { + return None; + } + self.tail -= 1; + Some(self.data[self.tail]) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.tail -= std::cmp::min(n, self.tail - self.head); + self.next_back() + } +} + #[cfg(test)] mod test { use super::Nibbles; @@ -167,4 +198,13 @@ mod test { let _ = nib_iter.next(); assert_eq!((8, Some(8)), nib_iter.size_hint()); } + + #[test] + fn backwards() { + let nib = Nibbles::<1>(&TEST_BYTES); + let nib_iter = nib.iter().rev(); + let expected = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd, 0x0]; + + assert!(nib_iter.eq(expected)); + } } From 99d97f70f63e880f1436db8831549d7384e4f9b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 10:53:00 -0400 Subject: [PATCH 0262/1053] build(deps): update typed-builder requirement from 0.15.0 to 0.16.0 (#222) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 0273641a8cf0..aad6434e0e98 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } -typed-builder = "0.15.0" +typed-builder = "0.16.0" [dev-dependencies] criterion = "0.5.1" From 42111f17e1f08d3d38bb3dcc981dafcec04af4e1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 28 Aug 2023 09:57:29 -0700 Subject: [PATCH 0263/1053] Upgrade nix (#226) --- firewood/Cargo.toml | 2 +- firewood/src/db.rs | 3 ++- firewood/src/file.rs | 29 +++++++++++++---------------- firewood/src/storage/buffer.rs | 10 ++++++++-- firewood/src/storage/mod.rs | 7 ++++--- growth-ring/Cargo.toml | 2 +- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index aad6434e0e98..989e7401d069 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -27,7 +27,7 @@ futures = "0.3.24" hex = "0.4.3" lru = "0.11.0" metered = "0.9.0" -nix = "0.26.1" +nix = {version = "0.27.1", features = ["fs", "uio"]} parking_lot = "0.12.1" primitive-types = { version = "0.12.0", features = ["impl-rlp"] } rlp = "0.5.2" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 30520a57511b..c8a84af1cbe8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -31,6 +31,7 @@ use std::{ io::{Cursor, Write}, mem::size_of, num::NonZeroUsize, + os::fd::AsFd, path::Path, sync::Arc, thread::JoinHandle, @@ -407,7 +408,7 @@ impl Db { let root_hash_path = file::touch_dir("root_hash", &db_path)?; let file0 = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; - let fd0 = file0.get_fd(); + let fd0 = file0.as_fd(); if reset { // initialize dbparams diff --git a/firewood/src/file.rs b/firewood/src/file.rs index a23f43dbd068..96e93c9bdf6f 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -3,15 +3,14 @@ // Copied from CedrusDB -use std::os::fd::IntoRawFd; -pub(crate) use std::os::unix::io::RawFd as Fd; +use std::ops::Deref; +use std::os::fd::OwnedFd; + use std::path::{Path, PathBuf}; use std::{io::ErrorKind, os::unix::prelude::OpenOptionsExt}; -use nix::unistd::close; - pub struct File { - fd: Fd, + fd: OwnedFd, } #[derive(PartialEq, Eq)] @@ -25,7 +24,7 @@ impl File { rootpath: PathBuf, fname: &str, options: Options, - ) -> Result { + ) -> Result { let mut filepath = rootpath; filepath.push(fname); Ok(std::fs::File::options() @@ -34,10 +33,10 @@ impl File { .write(true) .mode(0o600) .open(filepath)? - .into_raw_fd()) + .into()) } - pub fn create_file(rootpath: PathBuf, fname: &str) -> Result { + pub fn create_file(rootpath: PathBuf, fname: &str) -> Result { let mut filepath = rootpath; filepath.push(fname); Ok(std::fs::File::options() @@ -46,7 +45,7 @@ impl File { .write(true) .mode(0o600) .open(filepath)? - .into_raw_fd()) + .into()) } fn _get_fname(fid: u64) -> String { @@ -65,15 +64,13 @@ impl File { }; Ok(File { fd }) } - - pub fn get_fd(&self) -> Fd { - self.fd - } } -impl Drop for File { - fn drop(&mut self) { - close(self.fd).unwrap(); +impl Deref for File { + type Target = OwnedFd; + + fn deref(&self) -> &Self::Target { + &self.fd } } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index f2eeeedb9c41..8ed871d927df 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,6 +1,7 @@ //! Disk buffer for staging in memory pages and flushing them to disk. use std::fmt::Debug; use std::ops::IndexMut; +use std::os::fd::{AsFd, AsRawFd}; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::Arc; @@ -227,7 +228,12 @@ fn schedule_write( .unwrap() .get_file(fid) .unwrap(); - aiomgr.write(file.get_fd(), offset & fmask, p.staging_data.clone(), None) + aiomgr.write( + file.as_raw_fd(), + offset & fmask, + p.staging_data.clone(), + None, + ) }; let task = { @@ -304,7 +310,7 @@ async fn init_wal( e, final_path )) })? - .get_fd(), + .as_fd(), &redo.data, (offset & file_mask) as nix::libc::off_t, ) diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index b79f9170262d..e535c1706ace 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -9,6 +9,7 @@ use std::{ fmt::{self, Debug}, num::NonZeroUsize, ops::{Deref, DerefMut}, + os::fd::{AsFd, AsRawFd}, path::PathBuf, sync::Arc, }; @@ -779,7 +780,7 @@ impl CachedSpaceInner { let mut page: Page = Page::new([0; PAGE_SIZE as usize]); nix::sys::uio::pread( - file.get_fd(), + file.as_fd(), page.deref_mut(), (poff & (file_size - 1)) as nix::libc::off_t, ) @@ -901,7 +902,7 @@ impl FilePool { rootdir: rootdir.to_path_buf(), }; let f0 = s.get_file(0)?; - if flock(f0.get_fd(), FlockArg::LockExclusiveNonblock).is_err() { + if flock(f0.as_raw_fd(), FlockArg::LockExclusiveNonblock).is_err() { return Err(StoreError::Init("the store is busy".into())); } Ok(s) @@ -931,7 +932,7 @@ impl FilePool { impl Drop for FilePool { fn drop(&mut self) { let f0 = self.get_file(0).unwrap(); - flock(f0.get_fd(), FlockArg::UnlockNonblock).ok(); + flock(f0.as_raw_fd(), FlockArg::UnlockNonblock).ok(); } } diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index f17abd50b219..5de0781f236d 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -15,7 +15,7 @@ scan_fmt = "0.2.6" regex = "1.6.0" async-trait = "0.1.57" futures = "0.3.24" -nix = "0.26.2" +nix = {version = "0.27.1", features = ["fs", "uio"]} libc = "0.2.133" bytemuck = {version = "1.13.1", features = ["derive"]} thiserror = "1.0.40" From 0e48de6bbfc0d0ecbe32659224c765d55ff9af16 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 28 Aug 2023 10:16:25 -0700 Subject: [PATCH 0264/1053] Add node type to api + proofs (#225) --- firewood/src/v2/api.rs | 12 ++++++------ firewood/src/v2/db.rs | 4 ++-- firewood/src/v2/emptydb.rs | 4 ++-- firewood/src/v2/propose.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 4e119eb17272..74444e99240b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -69,9 +69,9 @@ pub enum Error { /// A range proof, consisting of a proof of the first key and the last key, /// and a vector of all key/value pairs #[derive(Debug)] -pub struct RangeProof { - pub first_key: Proof, - pub last_key: Proof, +pub struct RangeProof { + pub first_key: Proof, + pub last_key: Proof, pub middle: Vec<(K, V)>, } @@ -79,7 +79,7 @@ pub struct RangeProof { /// /// The generic N represents the storage for the node data #[derive(Debug)] -pub struct Proof(pub HashMap); +pub struct Proof(pub HashMap); /// The database interface, which includes a type for a static view of /// the database (the DbView). The most common implementation of the DbView @@ -147,12 +147,12 @@ pub trait DbView { /// * `last_key` - If None, continue to the end of the database /// * `limit` - The maximum number of keys in the range proof /// - async fn range_proof( + async fn range_proof( &self, first_key: Option, last_key: Option, limit: usize, - ) -> Result>, Error>; + ) -> Result>, Error>; } /// A proposal for a new revision of the database. diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 5df02441eb77..31b21eb575db 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -83,12 +83,12 @@ impl api::DbView for DbView { todo!() } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result>, api::Error> { + ) -> Result>, api::Error> { todo!() } } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 31cd93212b88..8b6e07debd26 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -65,12 +65,12 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result>, Error> { + ) -> Result>, Error> { Ok(None) } } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 5b8d3422db4b..4520173a257d 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -125,12 +125,12 @@ impl api::DbView for Proposal { todo!() } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, _limit: usize, - ) -> Result>, api::Error> { + ) -> Result>, api::Error> { todo!() } } From 016669fd90f0e13492a64ccfd6f8e336510fb79e Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 29 Aug 2023 12:57:34 -0400 Subject: [PATCH 0265/1053] Make EmptyDb useable (#228) --- firewood/src/v2/emptydb.rs | 21 +++++++++++---------- firewood/src/v2/mod.rs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 8b6e07debd26..8d7a3d5bd84a 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -8,15 +8,13 @@ use super::propose::{Proposal, ProposalBase}; /// An EmptyDb is a simple implementation of api::Db /// that doesn't store any data. It contains a single /// HistoricalImpl that has no keys or values -#[derive(Debug, Default)] -struct EmptyDb { - root: Arc, -} +#[derive(Debug)] +pub struct EmptyDb; /// HistoricalImpl is always empty, and there is only one, /// since nothing can be committed to an EmptyDb. -#[derive(Debug, Default)] -struct HistoricalImpl; +#[derive(Debug)] +pub struct HistoricalImpl; /// This is the hash of the [EmptyDb] root const ROOT_HASH: [u8; 32] = [0; 32]; @@ -29,7 +27,7 @@ impl Db for EmptyDb { async fn revision(&self, hash_key: HashKey) -> Result, Error> { if hash_key == ROOT_HASH { - Ok(self.root.clone()) + Ok(HistoricalImpl.into()) } else { Err(Error::HashNotFound { provided: hash_key }) } @@ -44,7 +42,10 @@ impl Db for EmptyDb { K: KeyType, V: ValueType, { - Ok(Proposal::new(ProposalBase::View(self.root.clone()), data)) + Ok(Proposal::new( + ProposalBase::View(HistoricalImpl.into()), + data, + )) } } @@ -82,7 +83,7 @@ mod tests { #[tokio::test] async fn basic_proposal() -> Result<(), Error> { - let db = Arc::new(EmptyDb::default()); + let db = Arc::new(EmptyDb); let batch = vec![ BatchOp::Put { @@ -103,7 +104,7 @@ mod tests { #[tokio::test] async fn nested_proposal() -> Result<(), Error> { - let db = Arc::new(EmptyDb::default()); + let db = Arc::new(EmptyDb); // create proposal1 which adds key "k" with value "v" and deletes "z" let batch = vec![ diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 6e23b8fe11fb..c84826759421 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -2,5 +2,5 @@ pub mod api; pub mod db; pub mod propose; -#[cfg(test)] +// #[cfg(test)] pub mod emptydb; From 464dc4e3ecd90a594f2ae4154ec104bcc49311a8 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 29 Aug 2023 15:11:13 -0400 Subject: [PATCH 0266/1053] Use thiserror for v2::api::Error (#229) --- firewood/src/v2/api.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 74444e99240b..9776179f82e8 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -50,19 +50,21 @@ pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch Date: Tue, 29 Aug 2023 16:49:42 -0400 Subject: [PATCH 0267/1053] Initial implementation of the rpcdb grpc-service (#230) --- .github/workflows/ci.yaml | 5 + Cargo.toml | 5 +- rpc/Cargo.toml | 26 ++++ rpc/build.rs | 6 + rpc/proto/rpcdb/rpcdb.proto | 127 ++++++++++++++++++ rpc/proto/sync/sync.proto | 166 +++++++++++++++++++++++ rpc/src/bin/client.rs | 3 + rpc/src/bin/server.rs | 258 ++++++++++++++++++++++++++++++++++++ rpc/src/lib.rs | 7 + 9 files changed, 601 insertions(+), 2 deletions(-) create mode 100644 rpc/Cargo.toml create mode 100644 rpc/build.rs create mode 100644 rpc/proto/rpcdb/rpcdb.proto create mode 100644 rpc/proto/sync/sync.proto create mode 100644 rpc/src/bin/client.rs create mode 100644 rpc/src/bin/server.rs create mode 100644 rpc/src/lib.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e74a657c6abf..846ea09ec739 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,6 +19,7 @@ jobs: with: toolchain: stable override: true + - uses: arduino/setup-protoc@v2 - name: Restore Cargo Cache id: cargo-cache uses: actions/cache/restore@v3 @@ -83,6 +84,7 @@ jobs: toolchain: stable override: true components: rustfmt, clippy + - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore uses: actions/cache/restore@v3 @@ -109,6 +111,7 @@ jobs: with: toolchain: stable override: true + - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore uses: actions/cache/restore@v3 @@ -132,6 +135,7 @@ jobs: with: toolchain: stable override: true + - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore uses: actions/cache/restore@v3 @@ -158,6 +162,7 @@ jobs: with: toolchain: stable override: true + - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore uses: actions/cache/restore@v3 diff --git a/Cargo.toml b/Cargo.toml index 8ede346b7d51..8fd718676b60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] members = [ + "firewood", + "fwdctl", "growth-ring", "libaio", + "rpc", "shale", - "firewood", - "fwdctl", ] resolver = "2" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml new file mode 100644 index 000000000000..16f4bd0749ec --- /dev/null +++ b/rpc/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rpc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "server" +test = false +bench = false + +[[bin]] +name = "client" +test = false +bench = false + +[dependencies] +firewood = { version = "0.0.3", path = "../firewood" } +prost = "0.11.9" +thiserror = "1.0.47" +tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } +tonic = { version = "0.9.2", features = ["tls"] } + +[build-dependencies] +tonic-build = "0.9.2" diff --git a/rpc/build.rs b/rpc/build.rs new file mode 100644 index 000000000000..54a51676e660 --- /dev/null +++ b/rpc/build.rs @@ -0,0 +1,6 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/sync/sync.proto")?; + tonic_build::compile_protos("proto/rpcdb/rpcdb.proto")?; + + Ok(()) +} diff --git a/rpc/proto/rpcdb/rpcdb.proto b/rpc/proto/rpcdb/rpcdb.proto new file mode 100644 index 000000000000..420d2b7a89fc --- /dev/null +++ b/rpc/proto/rpcdb/rpcdb.proto @@ -0,0 +1,127 @@ +syntax = "proto3"; + +package rpcdb; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/ava-labs/avalanchego/proto/pb/rpcdb"; + +service Database { + rpc Has(HasRequest) returns (HasResponse); + rpc Get(GetRequest) returns (GetResponse); + rpc Put(PutRequest) returns (PutResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Compact(CompactRequest) returns (CompactResponse); + rpc Close(CloseRequest) returns (CloseResponse); + rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse); + rpc WriteBatch(WriteBatchRequest) returns (WriteBatchResponse); + rpc NewIteratorWithStartAndPrefix(NewIteratorWithStartAndPrefixRequest) returns (NewIteratorWithStartAndPrefixResponse); + rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); + rpc IteratorError(IteratorErrorRequest) returns (IteratorErrorResponse); + rpc IteratorRelease(IteratorReleaseRequest) returns (IteratorReleaseResponse); +} + +enum Error { + // ERROR_UNSPECIFIED is used to indicate that no error occurred. + ERROR_UNSPECIFIED = 0; + ERROR_CLOSED = 1; + ERROR_NOT_FOUND = 2; +} + +message HasRequest { + bytes key = 1; +} + +message HasResponse { + bool has = 1; + Error err = 2; +} + +message GetRequest { + bytes key = 1; +} + +message GetResponse { + bytes value = 1; + Error err = 2; +} + +message PutRequest { + bytes key = 1; + bytes value = 2; +} + +message PutResponse { + Error err = 1; +} + +message DeleteRequest { + bytes key = 1; +} + +message DeleteResponse { + Error err = 1; +} + +message CompactRequest { + bytes start = 1; + bytes limit = 2; +} + +message CompactResponse { + Error err = 1; +} + +message CloseRequest {} + +message CloseResponse { + Error err = 1; +} + +message WriteBatchRequest { + repeated PutRequest puts = 1; + repeated DeleteRequest deletes = 2; +} + +message WriteBatchResponse { + Error err = 1; +} + +message NewIteratorRequest {} + +message NewIteratorWithStartAndPrefixRequest { + bytes start = 1; + bytes prefix = 2; +} + +message NewIteratorWithStartAndPrefixResponse { + uint64 id = 1; +} + +message IteratorNextRequest { + uint64 id = 1; +} + +message IteratorNextResponse { + repeated PutRequest data = 1; +} + +message IteratorErrorRequest { + uint64 id = 1; +} + +message IteratorErrorResponse { + Error err = 1; +} + +message IteratorReleaseRequest { + uint64 id = 1; +} + +message IteratorReleaseResponse { + Error err = 1; +} + +message HealthCheckResponse { + bytes details = 1; +} diff --git a/rpc/proto/sync/sync.proto b/rpc/proto/sync/sync.proto new file mode 100644 index 000000000000..e1c1ccd22ec4 --- /dev/null +++ b/rpc/proto/sync/sync.proto @@ -0,0 +1,166 @@ +syntax = "proto3"; + +package sync; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/ava-labs/avalanchego/proto/pb/sync"; + +// Request represents a request for information during syncing. +message Request { + oneof message { + SyncGetRangeProofRequest range_proof_request = 1; + SyncGetChangeProofRequest change_proof_request = 2; + } +} + +// The interface required by an x/sync/SyncManager for syncing. +// Note this service definition only exists for use in tests. +// A database shouldn't expose this over the internet, as it +// allows for reading/writing to the database. +service DB { + rpc GetMerkleRoot(google.protobuf.Empty) returns (GetMerkleRootResponse); + + rpc GetProof(GetProofRequest) returns (GetProofResponse); + + rpc GetChangeProof(GetChangeProofRequest) returns (GetChangeProofResponse); + rpc VerifyChangeProof(VerifyChangeProofRequest) returns (VerifyChangeProofResponse); + rpc CommitChangeProof(CommitChangeProofRequest) returns (google.protobuf.Empty); + + rpc GetRangeProof(GetRangeProofRequest) returns (GetRangeProofResponse); + rpc CommitRangeProof(CommitRangeProofRequest) returns (google.protobuf.Empty); +} + +message GetMerkleRootResponse { + bytes root_hash = 1; +} + +message GetProofRequest { + bytes key = 1; +} + +message GetProofResponse { + Proof proof = 1; +} + +message Proof { + bytes key = 1; + MaybeBytes value = 2; + repeated ProofNode proof = 3; +} + +// For use in sync client, which has a restriction on the size of +// the response. GetChangeProof in the DB service doesn't. +message SyncGetChangeProofRequest { + bytes start_root_hash = 1; + bytes end_root_hash = 2; + MaybeBytes start_key = 3; + MaybeBytes end_key = 4; + uint32 key_limit = 5; + uint32 bytes_limit = 6; +} + +message SyncGetChangeProofResponse { + oneof response { + ChangeProof change_proof = 1; + RangeProof range_proof = 2; + } +} + +message GetChangeProofRequest { + bytes start_root_hash = 1; + bytes end_root_hash = 2; + MaybeBytes start_key = 3; + MaybeBytes end_key = 4; + uint32 key_limit = 5; +} + +message GetChangeProofResponse { + oneof response { + ChangeProof change_proof = 1; + // True iff server errored with merkledb.ErrInsufficientHistory. + bool root_not_present = 2; + } +} + +message VerifyChangeProofRequest { + ChangeProof proof = 1; + MaybeBytes start_key = 2; + MaybeBytes end_key = 3; + bytes expected_root_hash = 4; +} + +message VerifyChangeProofResponse { + // If empty, there was no error. + string error = 1; +} + +message CommitChangeProofRequest { + ChangeProof proof = 1; +} + +// For use in sync client, which has a restriction on the size of +// the response. GetRangeProof in the DB service doesn't. +message SyncGetRangeProofRequest { + bytes root_hash = 1; + MaybeBytes start_key = 2; + MaybeBytes end_key = 3; + uint32 key_limit = 4; + uint32 bytes_limit = 5; +} + +message GetRangeProofRequest { + bytes root_hash = 1; + MaybeBytes start_key = 2; + MaybeBytes end_key = 3; + uint32 key_limit = 4; +} + +message GetRangeProofResponse { + RangeProof proof = 1; +} + +message CommitRangeProofRequest { + MaybeBytes start_key = 1; + RangeProof range_proof = 2; +} + +message ChangeProof { + repeated ProofNode start_proof = 1; + repeated ProofNode end_proof = 2; + repeated KeyChange key_changes = 3; +} + +message RangeProof { + repeated ProofNode start = 1; + repeated ProofNode end = 2; + repeated KeyValue key_values = 3; +} + +message ProofNode { + SerializedPath key = 1; + MaybeBytes value_or_hash = 2; + map children = 3; +} + +message KeyChange { + bytes key = 1; + MaybeBytes value = 2; +} + +message SerializedPath { + uint64 nibble_length = 1; + bytes value = 2; +} + +message MaybeBytes { + bytes value = 1; + // If false, this is None. + // Otherwise this is Some. + bool is_nothing = 2; +} + +message KeyValue { + bytes key = 1; + bytes value = 2; +} diff --git a/rpc/src/bin/client.rs b/rpc/src/bin/client.rs new file mode 100644 index 000000000000..12b90dcd53ce --- /dev/null +++ b/rpc/src/bin/client.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello from {}", file!()); +} diff --git a/rpc/src/bin/server.rs b/rpc/src/bin/server.rs new file mode 100644 index 000000000000..2fc8473d89b7 --- /dev/null +++ b/rpc/src/bin/server.rs @@ -0,0 +1,258 @@ +use firewood::v2::{ + api::{BatchOp, Db, DbView, Error as DbError, Proposal}, + emptydb::{EmptyDb, HistoricalImpl}, +}; +use rpc::rpcdb::{ + database_server::{self, DatabaseServer}, + CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, + GetRequest, GetResponse, HasRequest, HasResponse, HealthCheckResponse, IteratorErrorRequest, + IteratorErrorResponse, IteratorNextRequest, IteratorNextResponse, IteratorReleaseRequest, + IteratorReleaseResponse, NewIteratorWithStartAndPrefixRequest, + NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, + WriteBatchResponse, +}; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; +use tokio::sync::Mutex; +use tonic::{async_trait, transport::Server, Request, Response, Status}; + +struct DatabaseService { + db: EmptyDb, + iterators: Arc>, +} + +impl DatabaseService { + fn new() -> Self { + let db = EmptyDb; + let iterators = Arc::new(Mutex::new(Iterators::default())); + + Self { db, iterators } + } + + async fn revision(&self) -> Result, DbError> { + let root_hash = self.db.root_hash().await?; + self.db.revision(root_hash).await + } +} + +// TODO: implement Iterator +struct Iter; + +#[derive(Default)] +struct Iterators { + map: HashMap, + next_id: AtomicU64, +} + +impl Iterators { + fn insert(&mut self, iter: Iter) -> u64 { + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + self.map.insert(id, iter); + id + } + + fn _get(&self, id: u64) -> Option<&Iter> { + self.map.get(&id) + } + + fn remove(&mut self, id: u64) { + self.map.remove(&id); + } +} + +#[async_trait] +impl database_server::Database for DatabaseService { + async fn has(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.revision().await.into_status_result()?; + + let val = revision.val(key).await.into_status_result()?; + + let response = HasResponse { + has: val.is_some(), + ..Default::default() + }; + + Ok(Response::new(response)) + } + + async fn get(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.revision().await.into_status_result()?; + + let value = revision + .val(key) + .await + .into_status_result()? + .map(|v| v.to_vec()); + + let Some(value) = value else { + return Err(Status::not_found("key not found")); + }; + + let response = GetResponse { + value, + ..Default::default() + }; + + Ok(Response::new(response)) + } + + async fn put(&self, request: Request) -> Result, Status> { + let PutRequest { key, value } = request.into_inner(); + let batch = BatchOp::Put { key, value }; + let proposal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); + let _ = proposal.commit().await.into_status_result()?; + + Ok(Response::new(PutResponse::default())) + } + + async fn delete( + &self, + request: Request, + ) -> Result, Status> { + let DeleteRequest { key } = request.into_inner(); + let batch = BatchOp::<_, Vec>::Delete { key }; + let propoal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); + let _ = propoal.commit().await.into_status_result()?; + + Ok(Response::new(DeleteResponse::default())) + } + + async fn compact( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("compact not implemented")) + } + + async fn close( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("close not implemented")) + } + + async fn health_check( + &self, + _request: Request<()>, + ) -> Result, Status> { + // TODO: why is the response a Vec? + Ok(Response::new(HealthCheckResponse::default())) + } + + async fn write_batch( + &self, + request: Request, + ) -> Result, Status> { + let WriteBatchRequest { puts, deletes } = request.into_inner(); + let batch = puts + .into_iter() + .map(from_put_request) + .chain(deletes.into_iter().map(from_delete_request)) + .collect(); + let proposal = Arc::new(self.db.propose(batch).await.into_status_result()?); + let _ = proposal.commit().await.into_status_result()?; + + Ok(Response::new(WriteBatchResponse::default())) + } + + async fn new_iterator_with_start_and_prefix( + &self, + request: Request, + ) -> Result, Status> { + let NewIteratorWithStartAndPrefixRequest { + start: _, + prefix: _, + } = request.into_inner(); + + // TODO: create the actual iterator + let id = { + let mut iters = self.iterators.lock().await; + iters.insert(Iter) + }; + + Ok(Response::new(NewIteratorWithStartAndPrefixResponse { id })) + } + + async fn iterator_next( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("iterator_next not implemented")) + } + + async fn iterator_error( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("iterator_error not implemented")) + } + + async fn iterator_release( + &self, + request: Request, + ) -> Result, Status> { + let IteratorReleaseRequest { id } = request.into_inner(); + + { + let mut iters = self.iterators.lock().await; + iters.remove(id); + } + + Ok(Response::new(IteratorReleaseResponse::default())) + } +} + +trait IntoStatusResult { + fn into_status_result(self) -> Result; +} + +impl IntoStatusResult for Result { + fn into_status_result(self) -> Result { + self.map_err(|err| match err { + DbError::HashNotFound { provided: _ } => todo!(), + DbError::IncorrectRootHash { + provided: _, + current: _, + } => todo!(), + DbError::IO(_) => todo!(), + DbError::InvalidProposal => todo!(), + _ => todo!(), + }) + } +} + +fn from_put_request(request: PutRequest) -> BatchOp, Vec> { + BatchOp::Put { + key: request.key, + value: request.value, + } +} + +fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { + BatchOp::Delete { key: request.key } +} + +// TODO: use clap to parse command line input to run the server +#[tokio::main] +async fn main() { + let addr = "[::1]:10000".parse().unwrap(); + + println!("Database-Server listening on: {}", addr); + + let svc = DatabaseService::new(); + let svc = DatabaseServer::new(svc); + + // TODO: graceful shutdown + Server::builder() + .add_service(svc) + .serve(addr) + .await + .unwrap(); +} diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs new file mode 100644 index 000000000000..4fe46ff36371 --- /dev/null +++ b/rpc/src/lib.rs @@ -0,0 +1,7 @@ +pub mod sync { + tonic::include_proto!("sync"); +} + +pub mod rpcdb { + tonic::include_proto!("rpcdb"); +} From 1dedd068cb20df15e4570d2e52ad069438668e15 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:38:39 -0700 Subject: [PATCH 0268/1053] Update `README.md` to remove broken links (#231) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index d132fa805074..2749953a5bea 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,6 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. -To integrate firewood into a custom VM or other project, see the [firewood-connection](./firewood-connection/README.md) for a straightforward way to use firewood via custom message-passing. - ## Release See the [release documentation](./RELEASE.md) for detailed information on how to release firewood. From 9f10bc004bf941246d7bdf611195d78a0afd1154 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 31 Aug 2023 10:43:05 -0400 Subject: [PATCH 0269/1053] Use protoc to setup default-cache (#233) --- .github/workflows/default-branch-cache.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 3c5ed0a4f3a1..2afa44b4c6b6 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -19,6 +19,7 @@ jobs: with: toolchain: stable override: true + - uses: arduino/setup-protoc@v2 - name: Restore Cargo Cache id: cargo-cache uses: actions/cache/restore@v3 From 5b0ed02981b2e6a0233c49677819c00332112159 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 31 Aug 2023 11:02:52 -0400 Subject: [PATCH 0270/1053] Sync grpc implementation (#232) --- rpc/src/bin/server.rs | 250 ++---------------------------------- rpc/src/lib.rs | 4 + rpc/src/service.rs | 80 ++++++++++++ rpc/src/service/database.rs | 167 ++++++++++++++++++++++++ rpc/src/service/db.rs | 101 +++++++++++++++ 5 files changed, 360 insertions(+), 242 deletions(-) create mode 100644 rpc/src/service.rs create mode 100644 rpc/src/service/database.rs create mode 100644 rpc/src/service/db.rs diff --git a/rpc/src/bin/server.rs b/rpc/src/bin/server.rs index 2fc8473d89b7..c4eed2d28821 100644 --- a/rpc/src/bin/server.rs +++ b/rpc/src/bin/server.rs @@ -1,243 +1,9 @@ -use firewood::v2::{ - api::{BatchOp, Db, DbView, Error as DbError, Proposal}, - emptydb::{EmptyDb, HistoricalImpl}, +use rpc::{ + rpcdb::database_server::DatabaseServer as RpcServer, sync::db_server::DbServer as SyncServer, + DatabaseService, }; -use rpc::rpcdb::{ - database_server::{self, DatabaseServer}, - CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, - GetRequest, GetResponse, HasRequest, HasResponse, HealthCheckResponse, IteratorErrorRequest, - IteratorErrorResponse, IteratorNextRequest, IteratorNextResponse, IteratorReleaseRequest, - IteratorReleaseResponse, NewIteratorWithStartAndPrefixRequest, - NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, - WriteBatchResponse, -}; -use std::{ - collections::HashMap, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, -}; -use tokio::sync::Mutex; -use tonic::{async_trait, transport::Server, Request, Response, Status}; - -struct DatabaseService { - db: EmptyDb, - iterators: Arc>, -} - -impl DatabaseService { - fn new() -> Self { - let db = EmptyDb; - let iterators = Arc::new(Mutex::new(Iterators::default())); - - Self { db, iterators } - } - - async fn revision(&self) -> Result, DbError> { - let root_hash = self.db.root_hash().await?; - self.db.revision(root_hash).await - } -} - -// TODO: implement Iterator -struct Iter; - -#[derive(Default)] -struct Iterators { - map: HashMap, - next_id: AtomicU64, -} - -impl Iterators { - fn insert(&mut self, iter: Iter) -> u64 { - let id = self.next_id.fetch_add(1, Ordering::Relaxed); - self.map.insert(id, iter); - id - } - - fn _get(&self, id: u64) -> Option<&Iter> { - self.map.get(&id) - } - - fn remove(&mut self, id: u64) { - self.map.remove(&id); - } -} - -#[async_trait] -impl database_server::Database for DatabaseService { - async fn has(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.revision().await.into_status_result()?; - - let val = revision.val(key).await.into_status_result()?; - - let response = HasResponse { - has: val.is_some(), - ..Default::default() - }; - - Ok(Response::new(response)) - } - - async fn get(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.revision().await.into_status_result()?; - - let value = revision - .val(key) - .await - .into_status_result()? - .map(|v| v.to_vec()); - - let Some(value) = value else { - return Err(Status::not_found("key not found")); - }; - - let response = GetResponse { - value, - ..Default::default() - }; - - Ok(Response::new(response)) - } - - async fn put(&self, request: Request) -> Result, Status> { - let PutRequest { key, value } = request.into_inner(); - let batch = BatchOp::Put { key, value }; - let proposal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); - let _ = proposal.commit().await.into_status_result()?; - - Ok(Response::new(PutResponse::default())) - } - - async fn delete( - &self, - request: Request, - ) -> Result, Status> { - let DeleteRequest { key } = request.into_inner(); - let batch = BatchOp::<_, Vec>::Delete { key }; - let propoal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); - let _ = propoal.commit().await.into_status_result()?; - - Ok(Response::new(DeleteResponse::default())) - } - - async fn compact( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("compact not implemented")) - } - - async fn close( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("close not implemented")) - } - - async fn health_check( - &self, - _request: Request<()>, - ) -> Result, Status> { - // TODO: why is the response a Vec? - Ok(Response::new(HealthCheckResponse::default())) - } - - async fn write_batch( - &self, - request: Request, - ) -> Result, Status> { - let WriteBatchRequest { puts, deletes } = request.into_inner(); - let batch = puts - .into_iter() - .map(from_put_request) - .chain(deletes.into_iter().map(from_delete_request)) - .collect(); - let proposal = Arc::new(self.db.propose(batch).await.into_status_result()?); - let _ = proposal.commit().await.into_status_result()?; - - Ok(Response::new(WriteBatchResponse::default())) - } - - async fn new_iterator_with_start_and_prefix( - &self, - request: Request, - ) -> Result, Status> { - let NewIteratorWithStartAndPrefixRequest { - start: _, - prefix: _, - } = request.into_inner(); - - // TODO: create the actual iterator - let id = { - let mut iters = self.iterators.lock().await; - iters.insert(Iter) - }; - - Ok(Response::new(NewIteratorWithStartAndPrefixResponse { id })) - } - - async fn iterator_next( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("iterator_next not implemented")) - } - - async fn iterator_error( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("iterator_error not implemented")) - } - - async fn iterator_release( - &self, - request: Request, - ) -> Result, Status> { - let IteratorReleaseRequest { id } = request.into_inner(); - - { - let mut iters = self.iterators.lock().await; - iters.remove(id); - } - - Ok(Response::new(IteratorReleaseResponse::default())) - } -} - -trait IntoStatusResult { - fn into_status_result(self) -> Result; -} - -impl IntoStatusResult for Result { - fn into_status_result(self) -> Result { - self.map_err(|err| match err { - DbError::HashNotFound { provided: _ } => todo!(), - DbError::IncorrectRootHash { - provided: _, - current: _, - } => todo!(), - DbError::IO(_) => todo!(), - DbError::InvalidProposal => todo!(), - _ => todo!(), - }) - } -} - -fn from_put_request(request: PutRequest) -> BatchOp, Vec> { - BatchOp::Put { - key: request.key, - value: request.value, - } -} - -fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { - BatchOp::Delete { key: request.key } -} +use std::sync::Arc; +use tonic::transport::Server; // TODO: use clap to parse command line input to run the server #[tokio::main] @@ -246,12 +12,12 @@ async fn main() { println!("Database-Server listening on: {}", addr); - let svc = DatabaseService::new(); - let svc = DatabaseServer::new(svc); + let svc = Arc::new(DatabaseService::default()); // TODO: graceful shutdown Server::builder() - .add_service(svc) + .add_service(RpcServer::from_arc(svc.clone())) + .add_service(SyncServer::from_arc(svc)) .serve(addr) .await .unwrap(); diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 4fe46ff36371..7eac8c0a9545 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -5,3 +5,7 @@ pub mod sync { pub mod rpcdb { tonic::include_proto!("rpcdb"); } + +pub mod service; + +pub use service::Database as DatabaseService; diff --git a/rpc/src/service.rs b/rpc/src/service.rs new file mode 100644 index 000000000000..d48d6994ae60 --- /dev/null +++ b/rpc/src/service.rs @@ -0,0 +1,80 @@ +use firewood::v2::{ + api::{Db, Error}, + emptydb::{EmptyDb, HistoricalImpl}, +}; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; +use tokio::sync::Mutex; +use tonic::Status; + +pub mod database; +pub mod db; + +trait IntoStatusResultExt { + fn into_status_result(self) -> Result; +} + +impl IntoStatusResultExt for Result { + fn into_status_result(self) -> Result { + self.map_err(|err| match err { + Error::HashNotFound { provided: _ } => todo!(), + Error::IncorrectRootHash { + provided: _, + current: _, + } => todo!(), + Error::IO(_) => todo!(), + Error::InvalidProposal => todo!(), + _ => todo!(), + }) + } +} +pub struct Database { + db: EmptyDb, + iterators: Arc>, +} + +impl Default for Database { + fn default() -> Self { + Self { + db: EmptyDb, + iterators: Default::default(), + } + } +} + +impl Database { + async fn revision(&self) -> Result, Error> { + let root_hash = self.db.root_hash().await?; + self.db.revision(root_hash).await + } +} + +// TODO: implement Iterator +struct Iter; + +#[derive(Default)] +struct Iterators { + map: HashMap, + next_id: AtomicU64, +} + +impl Iterators { + fn insert(&mut self, iter: Iter) -> u64 { + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + self.map.insert(id, iter); + id + } + + fn _get(&self, id: u64) -> Option<&Iter> { + self.map.get(&id) + } + + fn remove(&mut self, id: u64) { + self.map.remove(&id); + } +} diff --git a/rpc/src/service/database.rs b/rpc/src/service/database.rs new file mode 100644 index 000000000000..13d4b7878776 --- /dev/null +++ b/rpc/src/service/database.rs @@ -0,0 +1,167 @@ +use super::{Database as DatabaseService, IntoStatusResultExt, Iter}; +use crate::rpcdb::{ + database_server::Database, CloseRequest, CloseResponse, CompactRequest, CompactResponse, + DeleteRequest, DeleteResponse, GetRequest, GetResponse, HasRequest, HasResponse, + HealthCheckResponse, IteratorErrorRequest, IteratorErrorResponse, IteratorNextRequest, + IteratorNextResponse, IteratorReleaseRequest, IteratorReleaseResponse, + NewIteratorWithStartAndPrefixRequest, NewIteratorWithStartAndPrefixResponse, PutRequest, + PutResponse, WriteBatchRequest, WriteBatchResponse, +}; +use firewood::v2::api::{BatchOp, Db, DbView, Proposal}; +use std::sync::Arc; +use tonic::{async_trait, Request, Response, Status}; + +#[async_trait] +impl Database for DatabaseService { + async fn has(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.revision().await.into_status_result()?; + + let val = revision.val(key).await.into_status_result()?; + + let response = HasResponse { + has: val.is_some(), + ..Default::default() + }; + + Ok(Response::new(response)) + } + + async fn get(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.revision().await.into_status_result()?; + + let value = revision + .val(key) + .await + .into_status_result()? + .map(|v| v.to_vec()); + + let Some(value) = value else { + return Err(Status::not_found("key not found")); + }; + + let response = GetResponse { + value, + ..Default::default() + }; + + Ok(Response::new(response)) + } + + async fn put(&self, request: Request) -> Result, Status> { + let PutRequest { key, value } = request.into_inner(); + let batch = BatchOp::Put { key, value }; + let proposal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); + let _ = proposal.commit().await.into_status_result()?; + + Ok(Response::new(PutResponse::default())) + } + + async fn delete( + &self, + request: Request, + ) -> Result, Status> { + let DeleteRequest { key } = request.into_inner(); + let batch = BatchOp::<_, Vec>::Delete { key }; + let propoal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); + let _ = propoal.commit().await.into_status_result()?; + + Ok(Response::new(DeleteResponse::default())) + } + + async fn compact( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("compact not implemented")) + } + + async fn close( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("close not implemented")) + } + + async fn health_check( + &self, + _request: Request<()>, + ) -> Result, Status> { + // TODO: why is the response a Vec? + Ok(Response::new(HealthCheckResponse::default())) + } + + async fn write_batch( + &self, + request: Request, + ) -> Result, Status> { + let WriteBatchRequest { puts, deletes } = request.into_inner(); + let batch = puts + .into_iter() + .map(from_put_request) + .chain(deletes.into_iter().map(from_delete_request)) + .collect(); + let proposal = Arc::new(self.db.propose(batch).await.into_status_result()?); + let _ = proposal.commit().await.into_status_result()?; + + Ok(Response::new(WriteBatchResponse::default())) + } + + async fn new_iterator_with_start_and_prefix( + &self, + request: Request, + ) -> Result, Status> { + let NewIteratorWithStartAndPrefixRequest { + start: _, + prefix: _, + } = request.into_inner(); + + // TODO: create the actual iterator + let id = { + let mut iters = self.iterators.lock().await; + iters.insert(Iter) + }; + + Ok(Response::new(NewIteratorWithStartAndPrefixResponse { id })) + } + + async fn iterator_next( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("iterator_next not implemented")) + } + + async fn iterator_error( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("iterator_error not implemented")) + } + + async fn iterator_release( + &self, + request: Request, + ) -> Result, Status> { + let IteratorReleaseRequest { id } = request.into_inner(); + + { + let mut iters = self.iterators.lock().await; + iters.remove(id); + } + + Ok(Response::new(IteratorReleaseResponse::default())) + } +} + +fn from_put_request(request: PutRequest) -> BatchOp, Vec> { + BatchOp::Put { + key: request.key, + value: request.value, + } +} + +fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { + BatchOp::Delete { key: request.key } +} diff --git a/rpc/src/service/db.rs b/rpc/src/service/db.rs new file mode 100644 index 000000000000..b9ce5fc83999 --- /dev/null +++ b/rpc/src/service/db.rs @@ -0,0 +1,101 @@ +use super::{Database, IntoStatusResultExt}; +use crate::sync::{ + db_server::Db as DbServerTrait, CommitChangeProofRequest, CommitRangeProofRequest, + GetChangeProofRequest, GetChangeProofResponse, GetMerkleRootResponse, GetProofRequest, + GetProofResponse, GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, + VerifyChangeProofResponse, +}; +use firewood::v2::api::Db; +use tonic::{async_trait, Request, Response, Status}; + +#[async_trait] +impl DbServerTrait for Database { + async fn get_merkle_root( + &self, + _request: Request<()>, + ) -> Result, Status> { + let root_hash = self.db.root_hash().await.into_status_result()?.to_vec(); + + let response = GetMerkleRootResponse { root_hash }; + + Ok(Response::new(response)) + } + + async fn get_proof( + &self, + request: Request, + ) -> Result, Status> { + let GetProofRequest { key: _ } = request.into_inner(); + let _revision = self.revision().await.into_status_result()?; + + todo!() + } + + async fn get_change_proof( + &self, + request: Request, + ) -> Result, Status> { + let GetChangeProofRequest { + start_root_hash: _, + end_root_hash: _, + start_key: _, + end_key: _, + key_limit: _, + } = request.into_inner(); + + let _revision = self.revision().await.into_status_result()?; + + todo!() + } + + async fn verify_change_proof( + &self, + request: Request, + ) -> Result, Status> { + let VerifyChangeProofRequest { + proof: _, + start_key: _, + end_key: _, + expected_root_hash: _, + } = request.into_inner(); + + let _revision = self.revision().await.into_status_result()?; + + todo!() + } + + async fn commit_change_proof( + &self, + request: Request, + ) -> Result, Status> { + let CommitChangeProofRequest { proof: _ } = request.into_inner(); + + todo!() + } + + async fn get_range_proof( + &self, + request: Request, + ) -> Result, Status> { + let GetRangeProofRequest { + root_hash: _, + start_key: _, + end_key: _, + key_limit: _, + } = request.into_inner(); + + todo!() + } + + async fn commit_range_proof( + &self, + request: Request, + ) -> Result, Status> { + let CommitRangeProofRequest { + start_key: _, + range_proof: _, + } = request.into_inner(); + + todo!() + } +} From 055e580e1e16ab8e2e60f1e75db156d66144f048 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:50:06 -0700 Subject: [PATCH 0271/1053] Update README.md with correct badges (#234) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2749953a5bea..a670743df3a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. -[![Latest version](https://img.shields.io/crates/v/firewood.svg)](https://crates.io/crates/firewood) +![Github Actions](https://github.com/ava-labs/firewood/actions/workflows/ci.yaml/badge.svg) [![Ecosystem license](https://img.shields.io/badge/License-Ecosystem-blue.svg)](./LICENSE.md) > :warning: firewood is alpha-level software and is not ready for production From 43c4492f1f14ee61190dc6f01398d8791054566d Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:56:42 -0700 Subject: [PATCH 0272/1053] Update README.md to remove support of eth account store (#236) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> Co-authored-by: Richard Pringle --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a670743df3a3..1b079e5202d0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ it is not built on top of a generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses the tree structure as the index on disk. Thus, there is no additional “emulation” of the logical trie to flatten out the data structure to feed into the underlying DB that is unaware of the -data being stored. +data being stored. It provides generic trie storage for arbitrary keys and +values. Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees atomicity and durability in the database, but also offers @@ -33,14 +34,6 @@ store back in memory. While running the store, new changes will also contribute to the configured window of changes (at batch granularity) to access any past versions with no additional cost at all. -Firewood provides two isolated storage spaces which can be both or selectively -used the user. The account model portion of the storage offers something very similar -to StateDB in geth, which captures the address-“state key” style of two-level access for -an account’s (smart contract’s) state. Therefore, it takes minimal effort to -delegate all state storage from an EVM implementation to firewood. The other -portion of the storage supports generic trie storage for arbitrary keys and -values. When unused, there is no additional cost. - ## License firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). From 76222b6b7818b07c48a641d12e06f35c37fbc538 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:06:42 -0700 Subject: [PATCH 0273/1053] Update fwdctl `README.md` (#237) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> Co-authored-by: Richard Pringle --- fwdctl/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fwdctl/README.md b/fwdctl/README.md index 77934328b68f..3696747e424a 100644 --- a/fwdctl/README.md +++ b/fwdctl/README.md @@ -2,11 +2,6 @@ `fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. -> Note: firewood has two separate storage areas in the database. One is a generic key-value store. -The other is EVM-based account model storage, based on Merkle-Patricia Tries. The generic key-value -store is being written to by the cli currently. Support for commands that use the account based storage -will be supported in a future release of firewood. - ## Building locally *Note: fwdctl is linux-only* ``` From 6b2fd2f6a8b2d205830ebae0462b2d19de67fd1f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 31 Aug 2023 21:01:57 -0400 Subject: [PATCH 0274/1053] Use the standard-library temp_dir function (#238) --- firewood/examples/rev.rs | 7 +++++-- firewood/src/storage/buffer.rs | 10 +++++----- fwdctl/tests/cli.rs | 36 +++++++++------------------------- growth-ring/src/lib.rs | 6 ++++-- libaio/tests/simple_test.rs | 13 ++++++------ 5 files changed, 29 insertions(+), 43 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 44ce44f14d17..91402ddee165 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -118,12 +118,15 @@ impl RevisionTracker { } } - fn create_revisions(&mut self, iter: impl Iterator) -> Result<(), DbError> + fn create_revisions( + &mut self, + mut iter: impl Iterator, + ) -> Result<(), DbError> where K: AsRef<[u8]>, V: AsRef<[u8]>, { - iter.map(|(k, v)| self.create_revision(k, v)).collect() + iter.try_for_each(|(k, v)| self.create_revision(k, v)) } fn create_revision(&mut self, k: K, v: V) -> Result<(), DbError> diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 8ed871d927df..f360bdb5a80e 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -635,17 +635,17 @@ mod tests { #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer_with_undo() { - let tmpdb = [ - &std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".to_string()), - "sender_api_test_db", - ]; + let temp_dir = option_env!("CARGO_TARGET_DIR") + .map(PathBuf::from) + .unwrap_or(std::env::temp_dir()); + let tmpdb = [temp_dir.to_str().unwrap(), "sender_api_test_db"]; let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); // TODO: Run the test in a separate standalone directory for concurrency reasons - let path = std::path::PathBuf::from(r"/tmp/firewood"); + let path = temp_dir.join("firewood"); let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index f4eae562a924..111f3cb39ca1 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -2,10 +2,11 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; -use std::fs::remove_dir_all; +use std::{fs::remove_dir_all, path::PathBuf}; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); + // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { if let Err(e) = remove_dir_all(tmpdb::path()) { @@ -215,35 +216,16 @@ fn fwdctl_dump() -> Result<()> { // of this in different directories will have different databases mod tmpdb { - use std::{ - ffi, - path::{Path, PathBuf}, - }; + use super::*; const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; const TARGET_TMP_DIR: Option<&str> = option_env!("CARGO_TARGET_TMPDIR"); - #[derive(Debug)] - pub struct DbPath { - path: PathBuf, - } - - impl AsRef for DbPath { - fn as_ref(&self) -> &ffi::OsStr { - self.path.as_os_str() - } - } - impl AsRef for DbPath { - fn as_ref(&self) -> &Path { - &self.path - } - } - pub fn path() -> DbPath { - let path = Path::new( - TARGET_TMP_DIR.unwrap_or(&std::env::var("TMPDIR").unwrap_or("/tmp".to_string())), - ) - .join(FIREWOOD_TEST_DB_NAME); - - DbPath { path } + pub fn path() -> PathBuf { + TARGET_TMP_DIR + .map(PathBuf::from) + .or_else(|| std::env::var("TMPDIR").ok().map(PathBuf::from)) + .unwrap_or(std::env::temp_dir()) + .join(FIREWOOD_TEST_DB_NAME) } } diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 4e5c69070bb6..0880434b9faa 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -349,7 +349,9 @@ mod tests { } fn get_temp_walfile_path(file: &str, line: u32) -> PathBuf { - let path = option_env!("CARGO_TARGET_TMPDIR").unwrap_or("/tmp"); - Path::new(path).join(format!("{}_{}", file.replace('/', "-"), line)) + let path = option_env!("CARGO_TARGET_TMPDIR") + .map(PathBuf::from) + .unwrap_or(std::env::temp_dir()); + path.join(format!("{}_{}", file.replace('/', "-"), line)) } } diff --git a/libaio/tests/simple_test.rs b/libaio/tests/simple_test.rs index 3a11c5435c23..86eea76387ff 100644 --- a/libaio/tests/simple_test.rs +++ b/libaio/tests/simple_test.rs @@ -1,12 +1,11 @@ use aiofut::AioBuilder; -use futures::executor::LocalPool; -use futures::future::FutureExt; -use futures::task::LocalSpawnExt; -use std::os::unix::io::AsRawFd; -use std::path::Path; +use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; +use std::{os::unix::io::AsRawFd, path::PathBuf}; -fn tmp_dir() -> &'static Path { - Path::new(option_env!("CARGO_TARGET_TMPDIR").unwrap_or("/tmp")) +fn tmp_dir() -> PathBuf { + option_env!("CARGO_TARGET_TMPDIR") + .map(PathBuf::from) + .unwrap_or(std::env::temp_dir()) } #[test] From 5c5ba8291ed7b989ae90fef8899e370d1cf135b8 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 31 Aug 2023 21:10:57 -0400 Subject: [PATCH 0275/1053] Move benchmark example to benches (#239) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/benches/hashops.rs | 49 +++++++++++++++++++++++-- firewood/examples/benchmark.rs | 66 ---------------------------------- 2 files changed, 47 insertions(+), 68 deletions(-) delete mode 100644 firewood/examples/benchmark.rs diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 0e6359f440ca..33acc5da4358 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -1,7 +1,11 @@ // hash benchmarks; run with 'cargo bench' use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; -use firewood::merkle::{Merkle, TrieHash, TRIE_HASH_LEN}; +use firewood::{ + db::{BatchOp, Db, DbConfig}, + merkle::{Merkle, TrieHash, TRIE_HASH_LEN}, + storage::WalConfig, +}; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use shale::{ @@ -119,10 +123,51 @@ fn bench_merkle(criterion: &mut Criterion) { }); } +fn bench_db(criterion: &mut Criterion) { + const KEY_LEN: usize = 4; + let mut rng = StdRng::seed_from_u64(1234); + + criterion + .benchmark_group("Db") + .sample_size(30) + .bench_function("commit", |b| { + b.iter_batched( + || { + let cfg = + DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + + let batch_ops: Vec<_> = repeat_with(|| { + (&mut rng) + .sample_iter(&Alphanumeric) + .take(KEY_LEN) + .collect() + }) + .map(|key: Vec<_>| BatchOp::Put { + key, + value: vec![b'v'], + }) + .take(N) + .collect(); + let db_path = dbg!(std::env::temp_dir()); + let db_path = db_path.join("benchmark_db"); + + let db = Db::new(db_path, &cfg.clone().truncate(true).build()).unwrap(); + + (db, batch_ops) + }, + |(db, batch_ops)| { + let proposal = db.new_proposal(batch_ops).unwrap(); + proposal.commit().unwrap(); + }, + BatchSize::SmallInput, + ); + }); +} + criterion_group! { name = benches; config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); - targets = bench_trie_hash, bench_merkle::<1> + targets = bench_trie_hash, bench_merkle::<3>, bench_db::<100> } criterion_main!(benches); diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs deleted file mode 100644 index 17a2a7006d10..000000000000 --- a/firewood/examples/benchmark.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use clap::Parser; -use criterion::{Criterion, SamplingMode, Throughput}; -use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; -use rand::{rngs::StdRng, Rng, SeedableRng}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(long)] - nbatch: usize, - #[arg(short, long)] - batch_size: usize, - #[arg(short, long, default_value_t = 0)] - seed: u64, -} - -fn main() { - let args = Args::parse(); - - let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - let mut c = Criterion::default(); - let mut group = c.benchmark_group("insert"); - let mut rng = StdRng::seed_from_u64(args.seed); - - let workload: Vec> = (0..args.nbatch) - .map(|_| { - (0..args.batch_size) - .map(|_| (rng.gen(), rng.gen())) - .collect() - }) - .collect(); - - println!("workload prepared"); - - group.sampling_mode(SamplingMode::Flat).sample_size(10); - - let total = (args.nbatch * args.batch_size) as u64; - group.throughput(Throughput::Elements(total)); - - group.bench_with_input( - format!("nbatch={} batch_size={}", args.nbatch, args.batch_size), - &workload, - |b, workload| { - b.iter(|| { - let db = Db::new("benchmark_db", &cfg.clone().truncate(true).build()).unwrap(); - - for batch in workload.iter() { - let mut wb = Vec::new(); - - for (k, v) in batch { - let write = BatchOp::Put { - key: k, - value: v.to_vec(), - }; - wb.push(write); - } - let proposal = db.new_proposal(wb).unwrap(); - proposal.commit().unwrap(); - } - }) - }, - ); -} From 688b2839e95ecc1541662273df253c45890a68a3 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:04:16 -0700 Subject: [PATCH 0276/1053] chore: inline doc clean up (#240) --- firewood/src/lib.rs | 18 +++++------------- shale/src/lib.rs | 4 ++-- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 478f6c841d85..c17677eeada4 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -16,7 +16,7 @@ //! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses //! the tree structure as the index on disk. Thus, there is no additional "emulation" of the //! logical trie to flatten out the data structure to feed into the underlying DB that is unaware -//! of the data being stored. +//! of the data being stored. It provides generic trie storage for arbitrary keys and values. //! //! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees //! atomicity and durability in the database, but also offers "reversibility": some portion @@ -25,13 +25,6 @@ //! will also contribute to the configured window of changes (at batch granularity) to access any past //! versions with no additional cost at all. //! -//! Firewood provides two isolated storage spaces which can be both or selectively used the user. -//! The account model portion of the storage offers something very similar to `StateDB` in geth, -//! which captures the address-"state key" style of two-level access for an account's (smart contract's) state. -//! Therefore, it takes minimal effort to delegate all state storage from an EVM implementation to firewood. The other -//! portion of the storage supports generic trie storage for arbitrary keys and values. When unused, -//! there is no additional cost. -//! //! # Design Philosophy & Overview //! //! With some on-going academic research efforts and increasing demand of faster local storage @@ -86,8 +79,7 @@ //! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of //! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). //! -//! - Data structure: in Firewood, one or more tries are maintained by invoking -//! `ShaleStore` (see `src/merkle.rs`; another stash for code objects is in `src/account.rs`). +//! - Data structure: in Firewood, one trie is maintained by invoking `ShaleStore` (see `src/merkle.rs`). //! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or //! persisted on disk. It is as if they're just in memory, which makes it much easier to write //! and maintain the code. @@ -107,7 +99,7 @@ //! separate the logical data from its physical representation, greatly simplifies the storage //! management, and allows reusing the code. It is still a very versatile abstraction, as in theory //! any persistent data could be stored this way -- sometimes you need to swap in a different -//! `MemShale` or `CachedStore` implementation, but without having to touch the code for the persisted +//! `ShaleStore` or `CachedStore` implementation, but without having to touch the code for the persisted //! data structure. //! //! ## Page-based Shadowing and Revisions @@ -142,11 +134,11 @@ //! //! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` space that //! overlays atop `CachedSpace`, so all dirty pages during the current write batch will be -//! exactly captured in `StoreRevMut` (see `StoreRevMut::take_delta`). +//! exactly captured in `StoreRevMut` (see `StoreRevMut::delta`). //! //! - Finally: //! -//! - Abort: when the write batch is dropped without invoking `db::WriteBatch::commit`, all in-memory +//! - Abort: when the write batch is dropped without invoking `db::Proposal::commit`, all in-memory //! changes will be discarded, the dirty pages from `StoreRevMut` will be dropped and the merkle //! will "revert" back to its original state without actually having to rollback anything. //! diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 1e8bc1d05163..83c8685b45ad 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -119,7 +119,7 @@ pub trait TypedView: /// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] /// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. -/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [ObjPtr] +/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [DiskAddress] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. #[derive(Debug)] pub struct Obj { @@ -237,7 +237,7 @@ impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> { /// A persistent item storage backed by linear logical space. New items can be created and old /// items could be retrieved or dropped. pub trait ShaleStore { - /// Dereference [ObjPtr] to a unique handle that allows direct access to the item in memory. + /// Dereference [DiskAddress] to a unique handle that allows direct access to the item in memory. fn get_item(&'_ self, ptr: DiskAddress) -> Result, ShaleError>; /// Allocate a new item. fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; From ca87a2be49a1bdfd73efcaafb150d83152148af9 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 1 Sep 2023 16:04:55 -0400 Subject: [PATCH 0277/1053] Break out proposal module from db (#244) --- firewood/src/db.rs | 323 ++---------------------------------- firewood/src/db/proposal.rs | 318 +++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+), 313 deletions(-) create mode 100644 firewood/src/db/proposal.rs diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c8a84af1cbe8..7bc2b3967718 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -10,9 +10,9 @@ use crate::{ merkle::{Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, proof::ProofError, storage::{ - buffer::{BufferWrite, DiskBuffer, DiskBufferRequester}, - AshRecord, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, - StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, + buffer::{DiskBuffer, DiskBufferRequester}, + CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, + ZeroStore, PAGE_SIZE_NBIT, }, v2::api::Proof, }; @@ -37,6 +37,12 @@ use std::{ thread::JoinHandle, }; +mod proposal; + +pub use proposal::{Batch, BatchOp, Proposal}; + +use self::proposal::ProposalBase; + const MERKLE_META_SPACE: SpaceId = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; const BLOB_META_SPACE: SpaceId = 0x2; @@ -378,7 +384,7 @@ pub struct Db { cfg: DbConfig, } -// #[metered(registry = DbMetrics, visibility = pub)] +#[metered(registry = DbMetrics, visibility = pub)] impl Db { const PARAM_SIZE: u64 = size_of::() as u64; @@ -906,10 +912,7 @@ impl Db { } .into() } -} -#[metered(registry = DbMetrics, visibility = pub)] -impl Db { /// Dump the Trie of the latest generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.revisions.lock().base_revision.kv_dump(w) @@ -945,309 +948,3 @@ impl std::ops::Deref for Revision { &self.rev } } - -/// A key/value pair operation. Only put (upsert) and delete are -/// supported -#[derive(Debug)] -pub enum BatchOp { - Put { key: K, value: Vec }, - Delete { key: K }, -} - -/// A list of operations to consist of a batch that -/// can be proposed -pub type Batch = Vec>; - -/// An atomic batch of changes proposed against the latest committed revision, -/// or any existing [Proposal]. Multiple proposals can be created against the -/// latest committed revision at the same time. [Proposal] is immutable meaning -/// the internal batch cannot be altered after creation. Committing a proposal -/// invalidates all other proposals that are not children of the committed one. -pub struct Proposal { - // State of the Db - m: Arc>, - r: Arc>>, - cfg: DbConfig, - - // State of the proposal - rev: DbRev, - store: Universe>, - committed: Arc>, - - parent: ProposalBase, -} - -pub enum ProposalBase { - Proposal(Arc), - View(Arc>), -} - -impl Proposal { - // Propose a new proposal from this proposal. The new proposal will be - // the child of it. - pub fn propose>(self: Arc, data: Batch) -> Result { - let store = self.store.new_from_other(); - - let m = Arc::clone(&self.m); - let r = Arc::clone(&self.r); - let cfg = self.cfg.clone(); - - let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; - - let merkle_payload_header_ref = Db::get_payload_header_ref( - store.merkle.meta.as_ref(), - Db::PARAM_SIZE + DbHeader::MSIZE, - )?; - - let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); - - let mut rev = Db::new_revision( - header_refs, - (store.merkle.meta.clone(), store.merkle.payload.clone()), - (store.blob.meta.clone(), store.blob.payload.clone()), - cfg.payload_regn_nbit, - cfg.payload_max_walk, - &cfg.rev, - )?; - data.into_iter().try_for_each(|op| -> Result<(), DbError> { - match op { - BatchOp::Put { key, value } => { - let (header, merkle) = rev.borrow_split(); - merkle - .insert(key, value, header.kv_root) - .map_err(DbError::Merkle)?; - Ok(()) - } - BatchOp::Delete { key } => { - let (header, merkle) = rev.borrow_split(); - merkle - .remove(key, header.kv_root) - .map_err(DbError::Merkle)?; - Ok(()) - } - } - })?; - rev.flush_dirty().unwrap(); - - let parent = ProposalBase::Proposal(self); - - Ok(Proposal { - m, - r, - cfg, - rev, - store, - committed: Arc::new(Mutex::new(false)), - parent, - }) - } - - /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are - /// either retained on disk or lost together during a crash. - pub fn commit(&self) -> Result<(), DbError> { - let mut committed = self.committed.lock(); - if *committed { - return Ok(()); - } - - if let ProposalBase::Proposal(p) = &self.parent { - p.commit()?; - }; - - // Check for if it can be committed - let mut revisions = self.r.lock(); - let committed_root_hash = revisions.base_revision.kv_root_hash().ok(); - let committed_root_hash = - committed_root_hash.expect("committed_root_hash should not be none"); - match &self.parent { - ProposalBase::Proposal(p) => { - let parent_root_hash = p.rev.kv_root_hash().ok(); - let parent_root_hash = - parent_root_hash.expect("parent_root_hash should not be none"); - if parent_root_hash != committed_root_hash { - return Err(DbError::InvalidProposal); - } - } - ProposalBase::View(p) => { - let parent_root_hash = p.kv_root_hash().ok(); - let parent_root_hash = - parent_root_hash.expect("parent_root_hash should not be none"); - if parent_root_hash != committed_root_hash { - return Err(DbError::InvalidProposal); - } - } - }; - - let kv_root_hash = self.rev.kv_root_hash().ok(); - let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); - - // clear the staging layer and apply changes to the CachedSpace - let (merkle_payload_redo, merkle_payload_wal) = self.store.merkle.payload.delta(); - let (merkle_meta_redo, merkle_meta_wal) = self.store.merkle.meta.delta(); - let (blob_payload_redo, blob_payload_wal) = self.store.blob.payload.delta(); - let (blob_meta_redo, blob_meta_wal) = self.store.blob.meta.delta(); - - let mut rev_inner = self.m.write(); - let merkle_meta_undo = rev_inner - .cached_space - .merkle - .meta - .update(&merkle_meta_redo) - .unwrap(); - let merkle_payload_undo = rev_inner - .cached_space - .merkle - .payload - .update(&merkle_payload_redo) - .unwrap(); - let blob_meta_undo = rev_inner - .cached_space - .blob - .meta - .update(&blob_meta_redo) - .unwrap(); - let blob_payload_undo = rev_inner - .cached_space - .blob - .payload - .update(&blob_payload_redo) - .unwrap(); - - // update the rolling window of past revisions - let latest_past = Universe { - merkle: get_sub_universe_from_deltas( - &rev_inner.cached_space.merkle, - merkle_meta_undo, - merkle_payload_undo, - ), - blob: get_sub_universe_from_deltas( - &rev_inner.cached_space.blob, - blob_meta_undo, - blob_payload_undo, - ), - }; - - let max_revisions = revisions.max_revisions; - if let Some(rev) = revisions.inner.front_mut() { - rev.merkle - .meta - .set_base_space(latest_past.merkle.meta.inner().clone()); - rev.merkle - .payload - .set_base_space(latest_past.merkle.payload.inner().clone()); - rev.blob - .meta - .set_base_space(latest_past.blob.meta.inner().clone()); - rev.blob - .payload - .set_base_space(latest_past.blob.payload.inner().clone()); - } - revisions.inner.push_front(latest_past); - while revisions.inner.len() > max_revisions { - revisions.inner.pop_back(); - } - - let base = Universe { - merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), - blob: get_sub_universe_from_empty_delta(&rev_inner.cached_space.blob), - }; - - let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; - - let merkle_payload_header_ref = - Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - - let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); - - let base_revision = Db::new_revision( - header_refs, - (base.merkle.meta.clone(), base.merkle.payload.clone()), - (base.blob.meta.clone(), base.blob.payload.clone()), - 0, - self.cfg.payload_max_walk, - &self.cfg.rev, - )?; - revisions.base = base; - revisions.base_revision = Arc::new(base_revision); - - // update the rolling window of root hashes - revisions.root_hashes.push_front(kv_root_hash.clone()); - if revisions.root_hashes.len() > max_revisions { - revisions - .root_hashes - .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); - } - - rev_inner.root_hash_staging.write(0, &kv_root_hash.0); - let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); - - // schedule writes to the disk - rev_inner.disk_requester.write( - vec![ - BufferWrite { - space_id: self.store.merkle.payload.id(), - delta: merkle_payload_redo, - }, - BufferWrite { - space_id: self.store.merkle.meta.id(), - delta: merkle_meta_redo, - }, - BufferWrite { - space_id: self.store.blob.payload.id(), - delta: blob_payload_redo, - }, - BufferWrite { - space_id: self.store.blob.meta.id(), - delta: blob_meta_redo, - }, - BufferWrite { - space_id: rev_inner.root_hash_staging.id(), - delta: root_hash_redo, - }, - ], - AshRecord( - [ - (MERKLE_META_SPACE, merkle_meta_wal), - (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), - (BLOB_META_SPACE, blob_meta_wal), - (BLOB_PAYLOAD_SPACE, blob_payload_wal), - (ROOT_HASH_SPACE, root_hash_wal), - ] - .into(), - ), - ); - *committed = true; - Ok(()) - } -} - -impl Proposal { - pub fn get_revision(&self) -> &DbRev { - &self.rev - } -} - -impl Drop for Proposal { - fn drop(&mut self) { - if !*self.committed.lock() { - // drop the staging changes - self.store.merkle.payload.reset_deltas(); - self.store.merkle.meta.reset_deltas(); - self.store.blob.payload.reset_deltas(); - self.store.blob.meta.reset_deltas(); - self.m.read().root_hash_staging.reset_deltas(); - } - } -} diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs new file mode 100644 index 000000000000..984e91c84db7 --- /dev/null +++ b/firewood/src/db/proposal.rs @@ -0,0 +1,318 @@ +use super::{ + get_sub_universe_from_deltas, get_sub_universe_from_empty_delta, Db, DbConfig, DbError, + DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, BLOB_META_SPACE, + BLOB_PAYLOAD_SPACE, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, +}; +use crate::{ + merkle::{TrieHash, TRIE_HASH_LEN}, + storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, +}; +use parking_lot::{Mutex, RwLock}; +use shale::CachedStore; +use std::sync::Arc; + +/// A key/value pair operation. Only put (upsert) and delete are +/// supported +#[derive(Debug)] +pub enum BatchOp { + Put { key: K, value: Vec }, + Delete { key: K }, +} + +/// A list of operations to consist of a batch that +/// can be proposed +pub type Batch = Vec>; + +/// An atomic batch of changes proposed against the latest committed revision, +/// or any existing [Proposal]. Multiple proposals can be created against the +/// latest committed revision at the same time. [Proposal] is immutable meaning +/// the internal batch cannot be altered after creation. Committing a proposal +/// invalidates all other proposals that are not children of the committed one. +pub struct Proposal { + // State of the Db + pub(super) m: Arc>, + pub(super) r: Arc>>, + pub(super) cfg: DbConfig, + + // State of the proposal + pub(super) rev: DbRev, + pub(super) store: Universe>, + pub(super) committed: Arc>, + + pub(super) parent: ProposalBase, +} + +pub enum ProposalBase { + Proposal(Arc), + View(Arc>), +} + +impl Proposal { + // Propose a new proposal from this proposal. The new proposal will be + // the child of it. + pub fn propose>(self: Arc, data: Batch) -> Result { + let store = self.store.new_from_other(); + + let m = Arc::clone(&self.m); + let r = Arc::clone(&self.r); + let cfg = self.cfg.clone(); + + let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; + + let merkle_payload_header_ref = Db::get_payload_header_ref( + store.merkle.meta.as_ref(), + Db::PARAM_SIZE + DbHeader::MSIZE, + )?; + + let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + + let mut rev = Db::new_revision( + header_refs, + (store.merkle.meta.clone(), store.merkle.payload.clone()), + (store.blob.meta.clone(), store.blob.payload.clone()), + cfg.payload_regn_nbit, + cfg.payload_max_walk, + &cfg.rev, + )?; + data.into_iter().try_for_each(|op| -> Result<(), DbError> { + match op { + BatchOp::Put { key, value } => { + let (header, merkle) = rev.borrow_split(); + merkle + .insert(key, value, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + BatchOp::Delete { key } => { + let (header, merkle) = rev.borrow_split(); + merkle + .remove(key, header.kv_root) + .map_err(DbError::Merkle)?; + Ok(()) + } + } + })?; + rev.flush_dirty().unwrap(); + + let parent = ProposalBase::Proposal(self); + + Ok(Proposal { + m, + r, + cfg, + rev, + store, + committed: Arc::new(Mutex::new(false)), + parent, + }) + } + + /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are + /// either retained on disk or lost together during a crash. + pub fn commit(&self) -> Result<(), DbError> { + let mut committed = self.committed.lock(); + if *committed { + return Ok(()); + } + + if let ProposalBase::Proposal(p) = &self.parent { + p.commit()?; + }; + + // Check for if it can be committed + let mut revisions = self.r.lock(); + let committed_root_hash = revisions.base_revision.kv_root_hash().ok(); + let committed_root_hash = + committed_root_hash.expect("committed_root_hash should not be none"); + match &self.parent { + ProposalBase::Proposal(p) => { + let parent_root_hash = p.rev.kv_root_hash().ok(); + let parent_root_hash = + parent_root_hash.expect("parent_root_hash should not be none"); + if parent_root_hash != committed_root_hash { + return Err(DbError::InvalidProposal); + } + } + ProposalBase::View(p) => { + let parent_root_hash = p.kv_root_hash().ok(); + let parent_root_hash = + parent_root_hash.expect("parent_root_hash should not be none"); + if parent_root_hash != committed_root_hash { + return Err(DbError::InvalidProposal); + } + } + }; + + let kv_root_hash = self.rev.kv_root_hash().ok(); + let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); + + // clear the staging layer and apply changes to the CachedSpace + let (merkle_payload_redo, merkle_payload_wal) = self.store.merkle.payload.delta(); + let (merkle_meta_redo, merkle_meta_wal) = self.store.merkle.meta.delta(); + let (blob_payload_redo, blob_payload_wal) = self.store.blob.payload.delta(); + let (blob_meta_redo, blob_meta_wal) = self.store.blob.meta.delta(); + + let mut rev_inner = self.m.write(); + let merkle_meta_undo = rev_inner + .cached_space + .merkle + .meta + .update(&merkle_meta_redo) + .unwrap(); + let merkle_payload_undo = rev_inner + .cached_space + .merkle + .payload + .update(&merkle_payload_redo) + .unwrap(); + let blob_meta_undo = rev_inner + .cached_space + .blob + .meta + .update(&blob_meta_redo) + .unwrap(); + let blob_payload_undo = rev_inner + .cached_space + .blob + .payload + .update(&blob_payload_redo) + .unwrap(); + + // update the rolling window of past revisions + let latest_past = Universe { + merkle: get_sub_universe_from_deltas( + &rev_inner.cached_space.merkle, + merkle_meta_undo, + merkle_payload_undo, + ), + blob: get_sub_universe_from_deltas( + &rev_inner.cached_space.blob, + blob_meta_undo, + blob_payload_undo, + ), + }; + + let max_revisions = revisions.max_revisions; + if let Some(rev) = revisions.inner.front_mut() { + rev.merkle + .meta + .set_base_space(latest_past.merkle.meta.inner().clone()); + rev.merkle + .payload + .set_base_space(latest_past.merkle.payload.inner().clone()); + rev.blob + .meta + .set_base_space(latest_past.blob.meta.inner().clone()); + rev.blob + .payload + .set_base_space(latest_past.blob.payload.inner().clone()); + } + revisions.inner.push_front(latest_past); + while revisions.inner.len() > max_revisions { + revisions.inner.pop_back(); + } + + let base = Universe { + merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), + blob: get_sub_universe_from_empty_delta(&rev_inner.cached_space.blob), + }; + + let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; + + let merkle_payload_header_ref = + Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; + + let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; + + let header_refs = ( + db_header_ref, + merkle_payload_header_ref, + blob_payload_header_ref, + ); + + let base_revision = Db::new_revision( + header_refs, + (base.merkle.meta.clone(), base.merkle.payload.clone()), + (base.blob.meta.clone(), base.blob.payload.clone()), + 0, + self.cfg.payload_max_walk, + &self.cfg.rev, + )?; + revisions.base = base; + revisions.base_revision = Arc::new(base_revision); + + // update the rolling window of root hashes + revisions.root_hashes.push_front(kv_root_hash.clone()); + if revisions.root_hashes.len() > max_revisions { + revisions + .root_hashes + .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); + } + + rev_inner.root_hash_staging.write(0, &kv_root_hash.0); + let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); + + // schedule writes to the disk + rev_inner.disk_requester.write( + vec![ + BufferWrite { + space_id: self.store.merkle.payload.id(), + delta: merkle_payload_redo, + }, + BufferWrite { + space_id: self.store.merkle.meta.id(), + delta: merkle_meta_redo, + }, + BufferWrite { + space_id: self.store.blob.payload.id(), + delta: blob_payload_redo, + }, + BufferWrite { + space_id: self.store.blob.meta.id(), + delta: blob_meta_redo, + }, + BufferWrite { + space_id: rev_inner.root_hash_staging.id(), + delta: root_hash_redo, + }, + ], + AshRecord( + [ + (MERKLE_META_SPACE, merkle_meta_wal), + (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), + (BLOB_META_SPACE, blob_meta_wal), + (BLOB_PAYLOAD_SPACE, blob_payload_wal), + (ROOT_HASH_SPACE, root_hash_wal), + ] + .into(), + ), + ); + *committed = true; + Ok(()) + } +} + +impl Proposal { + pub fn get_revision(&self) -> &DbRev { + &self.rev + } +} + +impl Drop for Proposal { + fn drop(&mut self) { + if !*self.committed.lock() { + // drop the staging changes + self.store.merkle.payload.reset_deltas(); + self.store.merkle.meta.reset_deltas(); + self.store.blob.payload.reset_deltas(); + self.store.blob.meta.reset_deltas(); + self.m.read().root_hash_staging.reset_deltas(); + } + } +} From 77f5805bb3ccf4fa5babb7fa41272dc1418585be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 23:02:45 -0700 Subject: [PATCH 0278/1053] build(deps): update tonic-build requirement from 0.9.2 to 0.10.0 (#247) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rpc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 16f4bd0749ec..21c468462efa 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -23,4 +23,4 @@ tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.9.2", features = ["tls"] } [build-dependencies] -tonic-build = "0.9.2" +tonic-build = "0.10.0" From 5fcdaeee3d23f8171a7c4be337ecb75a994618c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:23:48 -0400 Subject: [PATCH 0279/1053] build(deps): update prost requirement from 0.11.9 to 0.12.0 (#246) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rpc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 21c468462efa..16df5ebe414c 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -17,10 +17,10 @@ bench = false [dependencies] firewood = { version = "0.0.3", path = "../firewood" } -prost = "0.11.9" +prost = "0.12.0" thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.9.2", features = ["tls"] } +tonic = { version = "0.10.0", features = ["tls"] } [build-dependencies] tonic-build = "0.10.0" From 9880cdf47deb7ee32ed022a16bf24fb0221413aa Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 5 Sep 2023 11:30:31 -0700 Subject: [PATCH 0280/1053] chore: remove unused blob in db (#245) --- firewood/src/config.rs | 3 - firewood/src/db.rs | 107 ++---------------------------------- firewood/src/db/proposal.rs | 60 ++------------------ fwdctl/src/create.rs | 1 - 4 files changed, 10 insertions(+), 161 deletions(-) diff --git a/firewood/src/config.rs b/firewood/src/config.rs index 90a114d9c871..f4bfcf658782 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -63,7 +63,4 @@ pub struct DbRevConfig { /// Maximum cached Trie objects. #[builder(default = 1 << 20)] pub merkle_ncached_objs: usize, - /// Maximum cached Blob (currently just `Account`) objects. - #[builder(default = 4096)] - pub blob_ncached_objs: usize, } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7bc2b3967718..528610c2d480 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -45,9 +45,7 @@ use self::proposal::ProposalBase; const MERKLE_META_SPACE: SpaceId = 0x0; const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; -const BLOB_META_SPACE: SpaceId = 0x2; -const BLOB_PAYLOAD_SPACE: SpaceId = 0x3; -const ROOT_HASH_SPACE: SpaceId = 0x4; +const ROOT_HASH_SPACE: SpaceId = 0x2; const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; @@ -233,14 +231,12 @@ impl Storable for DbHeader { /// Necessary linear space instances bundled for the state of the entire DB. struct Universe { merkle: SubUniverse, - blob: SubUniverse, } impl Universe { fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), - blob: self.blob.to_mem_store_r(), } } } @@ -249,7 +245,6 @@ impl Universe> { fn new_from_other(&self) -> Universe> { Universe { merkle: self.merkle.new_from_other(), - blob: self.blob.new_from_other(), } } } @@ -258,7 +253,6 @@ impl Universe> { fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), - blob: self.blob.to_mem_store_r(), } } } @@ -268,14 +262,11 @@ impl Universe> { &self, merkle_meta_writes: &[SpaceWrite], merkle_payload_writes: &[SpaceWrite], - blob_meta_writes: &[SpaceWrite], - blob_payload_writes: &[SpaceWrite], ) -> Universe { Universe { merkle: self .merkle .rewind(merkle_meta_writes, merkle_payload_writes), - blob: self.blob.rewind(blob_meta_writes, blob_payload_writes), } } } @@ -338,14 +329,6 @@ impl + Send + Sync> DbRev { let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; Ok(valid) } - - /// Check if the account exists. - pub fn exist>(&self, key: K) -> Result { - Ok(match self.merkle.get(key, self.header.acc_root) { - Ok(r) => r.is_some(), - Err(e) => return Err(DbError::Merkle(e)), - }) - } } #[derive(Debug)] @@ -407,10 +390,6 @@ impl Db { let merkle_meta_path = file::touch_dir("meta", &merkle_path)?; let merkle_payload_path = file::touch_dir("compact", &merkle_path)?; - let blob_path = file::touch_dir("blob", &db_path)?; - let blob_meta_path = file::touch_dir("meta", &blob_path)?; - let blob_payload_path = file::touch_dir("compact", &blob_path)?; - let root_hash_path = file::touch_dir("root_hash", &db_path)?; let file0 = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; @@ -509,41 +488,11 @@ impl Db { .unwrap(), ), ), - blob: SubUniverse::new( - Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.meta_ncached_pages) - .ncached_files(cfg.meta_ncached_files) - .space_id(BLOB_META_SPACE) - .file_nbit(params.meta_file_nbit) - .rootdir(blob_meta_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ), - Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.payload_ncached_pages) - .ncached_files(cfg.payload_ncached_files) - .space_id(BLOB_PAYLOAD_SPACE) - .file_nbit(params.payload_file_nbit) - .rootdir(blob_payload_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ), - ), }; [ data_cache.merkle.meta.as_ref(), data_cache.merkle.payload.as_ref(), - data_cache.blob.meta.as_ref(), - data_cache.blob.payload.as_ref(), root_hash_cache.as_ref(), ] .into_iter() @@ -559,7 +508,6 @@ impl Db { let base = Universe { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), - blob: get_sub_universe_from_empty_delta(&data_cache.blob), }; let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; @@ -567,18 +515,11 @@ impl Db { let merkle_payload_header_ref = Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, DbHeader::MSIZE)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); + let header_refs = (db_header_ref, merkle_payload_header_ref); let base_revision = Db::new_revision( header_refs, (base.merkle.meta.clone(), base.merkle.payload.clone()), - (base.blob.meta.clone(), base.blob.payload.clone()), params.payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -618,10 +559,8 @@ impl Db { let merkle_payload_header: DiskAddress = DiskAddress::from(offset); offset += CompactSpaceHeader::MSIZE as usize; assert!(offset <= SPACE_RESERVED as usize); - let blob_payload_header: DiskAddress = DiskAddress::null(); let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); - let mut blob_meta_store = StoreRevMut::new(cached_space.blob.meta.clone()); if reset_store_headers { // initialize store headers @@ -636,13 +575,6 @@ impl Db { db_header.into(), &shale::to_dehydrated(&DbHeader::new_empty())?, ); - blob_meta_store.write( - blob_payload_header.into(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - ))?, - ); } let store = Universe { @@ -650,10 +582,6 @@ impl Db { Arc::new(merkle_meta_store), Arc::new(StoreRevMut::new(cached_space.merkle.payload.clone())), ), - blob: SubUniverse::new( - Arc::new(blob_meta_store), - Arc::new(StoreRevMut::new(cached_space.blob.payload.clone())), - ), }; let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; @@ -663,18 +591,11 @@ impl Db { Db::PARAM_SIZE + DbHeader::MSIZE, )?; - let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); + let header_refs = (db_header_ref, merkle_payload_header_ref); let mut rev: DbRev> = Db::new_revision( header_refs, (store.merkle.meta.clone(), store.merkle.payload.clone()), - (store.blob.meta.clone(), store.blob.payload.clone()), payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -703,13 +624,8 @@ impl Db { } fn new_revision>>( - header_refs: ( - Obj, - Obj, - Obj, - ), + header_refs: (Obj, Obj), merkle: (T, T), - _blob: (T, T), payload_regn_nbit: u64, payload_max_walk: u64, cfg: &DbRevConfig, @@ -737,7 +653,7 @@ impl Db { let merkle = Merkle::new(Box::new(merkle_space)); - if db_header_ref.acc_root.is_null() { + if db_header_ref.kv_root.is_null() { let mut err = Ok(()); // create the sentinel node db_header_ref @@ -863,14 +779,10 @@ impl Db { Some(u) => u.to_mem_store_r().rewind( &ash.0[&MERKLE_META_SPACE].undo, &ash.0[&MERKLE_PAYLOAD_SPACE].undo, - &ash.0[&BLOB_META_SPACE].undo, - &ash.0[&BLOB_PAYLOAD_SPACE].undo, ), None => inner_lock.cached_space.to_mem_store_r().rewind( &ash.0[&MERKLE_META_SPACE].undo, &ash.0[&MERKLE_PAYLOAD_SPACE].undo, - &ash.0[&BLOB_META_SPACE].undo, - &ash.0[&BLOB_PAYLOAD_SPACE].undo, ), }; revisions.inner.push_back(u); @@ -891,19 +803,12 @@ impl Db { Db::get_payload_header_ref(&space.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) .unwrap(); - let blob_payload_header_ref = Db::get_payload_header_ref(&space.blob.meta, 0).unwrap(); - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); + let header_refs = (db_header_ref, merkle_payload_header_ref); Revision { rev: Db::new_revision( header_refs, (space.merkle.meta.clone(), space.merkle.payload.clone()), - (space.blob.meta.clone(), space.blob.payload.clone()), self.payload_regn_nbit, 0, &self.cfg.rev, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 984e91c84db7..debece604210 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -1,7 +1,7 @@ use super::{ get_sub_universe_from_deltas, get_sub_universe_from_empty_delta, Db, DbConfig, DbError, - DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, BLOB_META_SPACE, - BLOB_PAYLOAD_SPACE, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, + DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, MERKLE_META_SPACE, + MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, @@ -64,18 +64,11 @@ impl Proposal { Db::PARAM_SIZE + DbHeader::MSIZE, )?; - let blob_payload_header_ref = Db::get_payload_header_ref(store.blob.meta.as_ref(), 0)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); + let header_refs = (db_header_ref, merkle_payload_header_ref); let mut rev = Db::new_revision( header_refs, (store.merkle.meta.clone(), store.merkle.payload.clone()), - (store.blob.meta.clone(), store.blob.payload.clone()), cfg.payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -155,8 +148,6 @@ impl Proposal { // clear the staging layer and apply changes to the CachedSpace let (merkle_payload_redo, merkle_payload_wal) = self.store.merkle.payload.delta(); let (merkle_meta_redo, merkle_meta_wal) = self.store.merkle.meta.delta(); - let (blob_payload_redo, blob_payload_wal) = self.store.blob.payload.delta(); - let (blob_meta_redo, blob_meta_wal) = self.store.blob.meta.delta(); let mut rev_inner = self.m.write(); let merkle_meta_undo = rev_inner @@ -171,18 +162,6 @@ impl Proposal { .payload .update(&merkle_payload_redo) .unwrap(); - let blob_meta_undo = rev_inner - .cached_space - .blob - .meta - .update(&blob_meta_redo) - .unwrap(); - let blob_payload_undo = rev_inner - .cached_space - .blob - .payload - .update(&blob_payload_redo) - .unwrap(); // update the rolling window of past revisions let latest_past = Universe { @@ -191,11 +170,6 @@ impl Proposal { merkle_meta_undo, merkle_payload_undo, ), - blob: get_sub_universe_from_deltas( - &rev_inner.cached_space.blob, - blob_meta_undo, - blob_payload_undo, - ), }; let max_revisions = revisions.max_revisions; @@ -206,12 +180,6 @@ impl Proposal { rev.merkle .payload .set_base_space(latest_past.merkle.payload.inner().clone()); - rev.blob - .meta - .set_base_space(latest_past.blob.meta.inner().clone()); - rev.blob - .payload - .set_base_space(latest_past.blob.payload.inner().clone()); } revisions.inner.push_front(latest_past); while revisions.inner.len() > max_revisions { @@ -220,7 +188,6 @@ impl Proposal { let base = Universe { merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), - blob: get_sub_universe_from_empty_delta(&rev_inner.cached_space.blob), }; let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; @@ -228,18 +195,11 @@ impl Proposal { let merkle_payload_header_ref = Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - let blob_payload_header_ref = Db::get_payload_header_ref(&base.blob.meta, 0)?; - - let header_refs = ( - db_header_ref, - merkle_payload_header_ref, - blob_payload_header_ref, - ); + let header_refs = (db_header_ref, merkle_payload_header_ref); let base_revision = Db::new_revision( header_refs, (base.merkle.meta.clone(), base.merkle.payload.clone()), - (base.blob.meta.clone(), base.blob.payload.clone()), 0, self.cfg.payload_max_walk, &self.cfg.rev, @@ -269,14 +229,6 @@ impl Proposal { space_id: self.store.merkle.meta.id(), delta: merkle_meta_redo, }, - BufferWrite { - space_id: self.store.blob.payload.id(), - delta: blob_payload_redo, - }, - BufferWrite { - space_id: self.store.blob.meta.id(), - delta: blob_meta_redo, - }, BufferWrite { space_id: rev_inner.root_hash_staging.id(), delta: root_hash_redo, @@ -286,8 +238,6 @@ impl Proposal { [ (MERKLE_META_SPACE, merkle_meta_wal), (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), - (BLOB_META_SPACE, blob_meta_wal), - (BLOB_PAYLOAD_SPACE, blob_payload_wal), (ROOT_HASH_SPACE, root_hash_wal), ] .into(), @@ -310,8 +260,6 @@ impl Drop for Proposal { // drop the staging changes self.store.merkle.payload.reset_deltas(); self.store.merkle.meta.reset_deltas(); - self.store.blob.payload.reset_deltas(); - self.store.blob.meta.reset_deltas(); self.m.read().root_hash_staging.reset_deltas(); } } diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 27612515ae89..1ae83c3b792b 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -269,7 +269,6 @@ pub fn initialize_db_config(opts: &Options) -> DbConfig { truncate: opts.truncate, rev: DbRevConfig { merkle_ncached_objs: opts.merkle_ncached_objs, - blob_ncached_objs: opts.blob_ncached_objs, }, buffer: DiskBufferConfig { max_buffered: opts.max_buffered, From 5e2d746016bfc5079609d56da7b0663e28bb93de Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 5 Sep 2023 18:34:40 -0400 Subject: [PATCH 0281/1053] docs and accounts (#241) --- .github/workflows/ci.yaml | 7 ++----- firewood/src/db.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 846ea09ec739..689f2c67a6b4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -126,7 +126,7 @@ jobs: - name: Run tests run: cargo test --verbose - e2e: + examples: needs: build runs-on: ubuntu-latest steps: @@ -174,7 +174,4 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ needs.build.outputs.cache-key }} - - name: Lint intra docs links - run: cargo rustdoc -p firewood --lib -- -D rustdoc::broken-intra-doc-links - - name: Lint missing crate-level docs - run: cargo rustdoc -p firewood --lib -- -D rustdoc::missing-crate-level-docs + - run: RUSTDOCFLAGS="-D warnings" cargo doc --all --document-private-items --no-deps diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 528610c2d480..d8f633a8757a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -183,7 +183,7 @@ fn get_sub_universe_from_empty_delta( /// DB-wide metadata, it keeps track of the roots of the top-level tries. #[derive(Debug)] struct DbHeader { - /// The root node of the account model storage. (Where the values are [Account] objects, which + /// The root node of the account model storage. (Where the values are `Account` objects, which /// may contain the root for the secondary trie.) acc_root: DiskAddress, /// The root node of the generic key-value store. From 32bcdae982f57df9470453a3cc138ad4957c7a62 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Sep 2023 09:25:20 -0700 Subject: [PATCH 0282/1053] Clean up remants of eth (#249) --- firewood/src/db.rs | 71 ++++++++++++++++++++++----------------- shale/Cargo.toml | 1 + shale/src/compact.rs | 3 +- shale/src/disk_address.rs | 5 ++- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d8f633a8757a..597f2c612505 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -99,7 +99,7 @@ impl Error for DbError {} /// correct parameters are used when the DB is opened later (the parameters here will override the /// parameters in [DbConfig] if the DB already exists). #[repr(C)] -#[derive(Debug, Clone, Copy, AnyBitPattern)] +#[derive(Debug, Clone, Copy, AnyBitPattern, bytemuck::NoUninit)] struct DbParams { magic: [u8; 16], meta_file_nbit: u64, @@ -180,22 +180,18 @@ fn get_sub_universe_from_empty_delta( get_sub_universe_from_deltas(sub_universe, StoreDelta::default(), StoreDelta::default()) } -/// DB-wide metadata, it keeps track of the roots of the top-level tries. -#[derive(Debug)] +/// mutable DB-wide metadata, it keeps track of the root of the top-level trie. +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::NoUninit)] struct DbHeader { - /// The root node of the account model storage. (Where the values are `Account` objects, which - /// may contain the root for the secondary trie.) - acc_root: DiskAddress, - /// The root node of the generic key-value store. kv_root: DiskAddress, } impl DbHeader { - pub const MSIZE: u64 = 16; + pub const MSIZE: u64 = std::mem::size_of::() as u64; pub fn new_empty() -> Self { Self { - acc_root: DiskAddress::null(), kv_root: DiskAddress::null(), } } @@ -210,8 +206,7 @@ impl Storable for DbHeader { size: Self::MSIZE, })?; Ok(Self { - acc_root: raw.as_deref()[..8].into(), - kv_root: raw.as_deref()[8..].into(), + kv_root: raw.as_deref().as_slice().into(), }) } @@ -221,7 +216,6 @@ impl Storable for DbHeader { fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.acc_root.to_le_bytes())?; cur.write_all(&self.kv_root.to_le_bytes())?; Ok(()) } @@ -404,23 +398,41 @@ impl Db { } nix::unistd::ftruncate(fd0, 0).map_err(DbError::System)?; nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DbError::System)?; - let header = DbParams { - magic: *MAGIC_STR, - meta_file_nbit: cfg.meta_file_nbit, - payload_file_nbit: cfg.payload_file_nbit, - payload_regn_nbit: cfg.payload_regn_nbit, - wal_file_nbit: cfg.wal.file_nbit, - wal_block_nbit: cfg.wal.block_nbit, - root_hash_file_nbit: cfg.root_hash_file_nbit, - }; - - let header_bytes = unsafe { - std::slice::from_raw_parts( - &header as *const DbParams as *const u8, - size_of::(), - ) - .to_vec() - }; + + // The header consists of three parts: + // DbParams + // DbHeader (just a pointer to the sentinel) + // CompactSpaceHeader for future allocations + let header_bytes: Vec = { + let params = DbParams { + magic: *MAGIC_STR, + meta_file_nbit: cfg.meta_file_nbit, + payload_file_nbit: cfg.payload_file_nbit, + payload_regn_nbit: cfg.payload_regn_nbit, + wal_file_nbit: cfg.wal.file_nbit, + wal_block_nbit: cfg.wal.block_nbit, + root_hash_file_nbit: cfg.root_hash_file_nbit, + }; + let bytes = bytemuck::bytes_of(¶ms).to_vec(); + bytes.into_iter() + } + .chain({ + // compute the DbHeader as bytes + let hdr = DbHeader::new_empty(); + // clippy thinks to_vec isn't necessary, but it actually is :( + #[allow(clippy::unnecessary_to_owned)] + bytemuck::bytes_of(&hdr).to_vec().into_iter() + }) + .chain({ + // write out the CompactSpaceHeader + let csh = CompactSpaceHeader::new( + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + ); + #[allow(clippy::unnecessary_to_owned)] + bytemuck::bytes_of(&csh).to_vec().into_iter() + }) + .collect(); nix::sys::uio::pwrite(fd0, &header_bytes, 0).map_err(DbError::System)?; } @@ -659,7 +671,6 @@ impl Db { db_header_ref .write(|r| { err = (|| { - r.acc_root = merkle.init_root()?; r.kv_root = merkle.init_root()?; Ok(()) })(); diff --git a/shale/Cargo.toml b/shale/Cargo.toml index fe75adaa0fcd..acf120027d67 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" hex = "0.4.3" lru = "0.11.0" thiserror = "1.0.38" +bytemuck = { version = "1.13.1", features = ["derive"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 7f1ad4816218..495c3304fa2a 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -127,7 +127,8 @@ impl Storable for CompactDescriptor { } } -#[derive(Debug)] +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::NoUninit)] pub struct CompactSpaceHeader { meta_space_tail: DiskAddress, compact_space_tail: DiskAddress, diff --git a/shale/src/disk_address.rs b/shale/src/disk_address.rs index 7eb039d84fc4..1b7e78a4594b 100644 --- a/shale/src/disk_address.rs +++ b/shale/src/disk_address.rs @@ -2,10 +2,13 @@ use std::hash::Hash; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; +use bytemuck::NoUninit; + use crate::{CachedStore, ShaleError, Storable}; /// The virtual disk address of an object -#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq)] +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq, NoUninit)] pub struct DiskAddress(pub Option); impl Deref for DiskAddress { From 3f51676f082758d4ea3bac0e68e524800851edc8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Sep 2023 14:01:16 -0700 Subject: [PATCH 0283/1053] Remove some allocations during initialization (#250) --- firewood/src/db.rs | 81 +++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 597f2c612505..b2c31f257fcc 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -31,7 +31,7 @@ use std::{ io::{Cursor, Write}, mem::size_of, num::NonZeroUsize, - os::fd::AsFd, + os::fd::{AsFd, BorrowedFd}, path::Path, sync::Arc, thread::JoinHandle, @@ -396,45 +396,7 @@ impl Db { { return Err(DbError::InvalidParams); } - nix::unistd::ftruncate(fd0, 0).map_err(DbError::System)?; - nix::unistd::ftruncate(fd0, 1 << cfg.meta_file_nbit).map_err(DbError::System)?; - - // The header consists of three parts: - // DbParams - // DbHeader (just a pointer to the sentinel) - // CompactSpaceHeader for future allocations - let header_bytes: Vec = { - let params = DbParams { - magic: *MAGIC_STR, - meta_file_nbit: cfg.meta_file_nbit, - payload_file_nbit: cfg.payload_file_nbit, - payload_regn_nbit: cfg.payload_regn_nbit, - wal_file_nbit: cfg.wal.file_nbit, - wal_block_nbit: cfg.wal.block_nbit, - root_hash_file_nbit: cfg.root_hash_file_nbit, - }; - let bytes = bytemuck::bytes_of(¶ms).to_vec(); - bytes.into_iter() - } - .chain({ - // compute the DbHeader as bytes - let hdr = DbHeader::new_empty(); - // clippy thinks to_vec isn't necessary, but it actually is :( - #[allow(clippy::unnecessary_to_owned)] - bytemuck::bytes_of(&hdr).to_vec().into_iter() - }) - .chain({ - // write out the CompactSpaceHeader - let csh = CompactSpaceHeader::new( - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - ); - #[allow(clippy::unnecessary_to_owned)] - bytemuck::bytes_of(&csh).to_vec().into_iter() - }) - .collect(); - - nix::sys::uio::pwrite(fd0, &header_bytes, 0).map_err(DbError::System)?; + Self::initialize_header_on_disk(cfg, fd0)?; } // read DbParams @@ -558,6 +520,45 @@ impl Db { }) } + fn initialize_header_on_disk(cfg: &DbConfig, fd0: BorrowedFd) -> Result<(), DbError> { + // The header consists of three parts: + // DbParams + // DbHeader (just a pointer to the sentinel) + // CompactSpaceHeader for future allocations + let (params, hdr, csh); + let header_bytes: Vec = { + params = DbParams { + magic: *MAGIC_STR, + meta_file_nbit: cfg.meta_file_nbit, + payload_file_nbit: cfg.payload_file_nbit, + payload_regn_nbit: cfg.payload_regn_nbit, + wal_file_nbit: cfg.wal.file_nbit, + wal_block_nbit: cfg.wal.block_nbit, + root_hash_file_nbit: cfg.root_hash_file_nbit, + }; + let bytes = bytemuck::bytes_of(¶ms); + bytes.iter() + } + .chain({ + // compute the DbHeader as bytes + hdr = DbHeader::new_empty(); + bytemuck::bytes_of(&hdr) + }) + .chain({ + // write out the CompactSpaceHeader + csh = CompactSpaceHeader::new( + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + ); + bytemuck::bytes_of(&csh) + }) + .copied() + .collect(); + + nix::sys::uio::pwrite(fd0, &header_bytes, 0).map_err(DbError::System)?; + Ok(()) + } + /// Create a new mutable store and an alterable revision of the DB on top. fn new_store( cached_space: &Universe>, From 6683a0fdf9a291e4a0c81cf0e3fa63fc5a95507a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Sep 2023 11:50:37 -0700 Subject: [PATCH 0284/1053] Add workflow for github pages (#251) --- .github/workflows/ci.yaml | 3 +- .github/workflows/gh-pages.yaml | 78 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/gh-pages.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 689f2c67a6b4..f2df4bbb7f34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,6 +20,7 @@ jobs: toolchain: stable override: true - uses: arduino/setup-protoc@v2 + # caution: this is the same restore as in gh-pages.yaml - name: Restore Cargo Cache id: cargo-cache uses: actions/cache/restore@v3 @@ -174,4 +175,4 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ needs.build.outputs.cache-key }} - - run: RUSTDOCFLAGS="-D warnings" cargo doc --all --document-private-items --no-deps + - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml new file mode 100644 index 000000000000..b855098d92d2 --- /dev/null +++ b/.github/workflows/gh-pages.yaml @@ -0,0 +1,78 @@ +name: gh-pages + +on: + push: + branches: + - "main" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: arduino/setup-protoc@v2 + # caution: this is the same restore as in ci.yaml + - name: Restore Cargo Cache + id: cargo-cache + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + # We can do this now because we use specific verison and update with Dependabot + # but if we make the deps any less specifc, we'll have to fix + key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} + # start from the previous set of cached dependencies + restore-keys: | + ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-deps- + - name: Build + run: cargo doc --document-private-items --no-deps + - name: Set up _site redirect to firewood + run: | + rm -fr _site + mkdir _site + echo "" > _site/index.html + - name: Copy doc files to _site + run: | + cp -rv target/doc/* ./_site + cp -rv docs/assets ./_site + - uses: actions/upload-artifact@v3 + with: + name: pages + path: _site + if-no-files-found: error + deploy: + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Download pages artifact + uses: actions/download-artifact@v3 + with: + name: pages + path: . + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: . + - name: Deploy to GitHub pages + id: deployment + uses: actions/deploy-pages@v2 From 9890b60c2fb03fe307791a8ac2c55419ded17416 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Sep 2023 12:50:35 -0700 Subject: [PATCH 0285/1053] Fix test shutdown race (#254) --- firewood/src/storage/buffer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index f360bdb5a80e..3fbcd6ca1d5f 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -906,6 +906,7 @@ mod tests { let another_store = StoreRevMut::new(state_cache); let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), another_hash); + disk_requester.shutdown(); } fn get_file_path(path: &Path, file: &str, line: u32) -> PathBuf { From 48014bf7256509f61fb6317e73e7896e21a4aef9 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 7 Sep 2023 17:31:33 -0400 Subject: [PATCH 0286/1053] Remove tempdir as it is not maintained (#252) --- firewood/Cargo.toml | 1 - firewood/src/storage/buffer.rs | 43 +++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 989e7401d069..139a2b0d6661 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -46,7 +46,6 @@ assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.3.1", features = ['derive'] } -tempdir = "0.3.7" test-case = "3.1.0" pprof = { version = "0.12.1", features = ["flamegraph"] } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 3fbcd6ca1d5f..f4173273e175 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -618,7 +618,6 @@ impl DiskBufferRequester { mod tests { use sha3::Digest; use std::path::{Path, PathBuf}; - use tempdir::TempDir; use super::*; use crate::{ @@ -632,21 +631,26 @@ mod tests { const STATE_SPACE: SpaceId = 0x0; const HASH_SIZE: usize = 32; + + fn get_tmp_dir() -> PathBuf { + option_env!("CARGO_TARGET_TMPDIR") + .map(Into::into) + .unwrap_or(std::env::temp_dir()) + .join("firewood") + } + #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer_with_undo() { - let temp_dir = option_env!("CARGO_TARGET_DIR") - .map(PathBuf::from) - .unwrap_or(std::env::temp_dir()); - let tmpdb = [temp_dir.to_str().unwrap(), "sender_api_test_db"]; + let temp_dir = get_tmp_dir(); let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); // TODO: Run the test in a separate standalone directory for concurrency reasons - let path = temp_dir.join("firewood"); - let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); + let (root_db_path, reset) = + file::open_dir(temp_dir.as_path(), file::Options::Truncate).unwrap(); // file descriptor of the state directory let state_path = file::touch_dir("state", &root_db_path).unwrap(); @@ -711,14 +715,14 @@ mod tests { write_thread_handle.join().unwrap(); // This is not ACID compliant, write should not return before Wal log // is written to disk. - assert!([ - &tmpdb.into_iter().collect::(), - "wal", - "00000000.log" - ] - .iter() - .collect::() - .exists()); + + let log_file = temp_dir + .parent() + .unwrap() + .join("sender_api_test_db") + .join("wal") + .join("00000000.log"); + assert!(log_file.exists()); // verify assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); @@ -732,8 +736,8 @@ mod tests { let disk_requester = init_buffer(buf_cfg, wal_cfg); // TODO: Run the test in a separate standalone directory for concurrency reasons - let tmp_dir = TempDir::new("firewood").unwrap(); - let path = get_file_path(tmp_dir.path(), file!(), line!()); + let tmp_dir = get_tmp_dir(); + let path = get_file_path(tmp_dir.as_path(), file!(), line!()); let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory @@ -816,8 +820,9 @@ mod tests { let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); - let tmp_dir = TempDir::new("firewood").unwrap(); - let path = get_file_path(tmp_dir.path(), file!(), line!()); + let tmp_dir = get_tmp_dir(); + let path = get_file_path(tmp_dir.as_path(), file!(), line!()); + std::fs::create_dir_all(&path).unwrap(); let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); // file descriptor of the state directory From e91b4f0cf21d38d4112a27b943d9ce4be592e757 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 8 Sep 2023 10:46:34 -0700 Subject: [PATCH 0287/1053] actions-rs/toolchain -> dtolnay/rust-toolchain (#256) --- .github/workflows/ci.yaml | 26 ++++----------------- .github/workflows/default-branch-cache.yaml | 5 +--- .github/workflows/gh-pages.yaml | 5 +--- .github/workflows/publish.yaml | 5 +--- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2df4bbb7f34..17e67397c7fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,10 +15,7 @@ jobs: cache-key: ${{ steps.cargo-cache.outputs.cache-primary-key }} steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 # caution: this is the same restore as in gh-pages.yaml - name: Restore Cargo Cache @@ -80,11 +77,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - components: rustfmt, clippy + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore @@ -108,10 +101,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore @@ -132,10 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore @@ -159,10 +146,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - name: Restore Check Deps id: cache-build-deps-restore diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 2afa44b4c6b6..7e18c7f2e65d 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -15,10 +15,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - name: Restore Cargo Cache id: cargo-cache diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index b855098d92d2..416db804d596 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -13,10 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 # caution: this is the same restore as in ci.yaml - name: Restore Cargo Cache diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 27e4d7afde45..89171dcd4d1b 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -12,10 +12,7 @@ jobs: if: "startsWith(github.event.release.tag_name, 'v')" steps: - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - name: publish shale crate continue-on-error: true run: | From 035d9ed9699af31fd0c0d05bb9730a344674e7d3 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 14 Sep 2023 17:59:15 -0700 Subject: [PATCH 0288/1053] Update README.md about milestone changes (#259) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b079e5202d0..cfd8abd7712f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ version. propose a batch against any existing proposed revision. - [x] Commit a batch that has been proposed will invalidate all other proposals that are not children of the committed proposed batch. -- [ ] Be able to quickly commit a batch that has been proposed. +- [x] Be able to quickly commit a batch that has been proposed. ### Dried milestone The focus of this milestone will be to support synchronization to other @@ -121,6 +121,8 @@ verify the correctness of the data. corresponding range proofs that verify the correctness of the data. - [ ] Enforce limits on the size of the range proof as well as keys to make synchronization easier for clients. +- [ ] MerkleDB root hash in parity for seamless transition between MerkleDB +and firewood. - [ ] Add metric reporting - [ ] Refactor `Shale` to be more idiomatic From b1167120809fd03c81ac3502e2722aa7e5ee5946 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 19 Sep 2023 16:44:26 -0400 Subject: [PATCH 0289/1053] Ignore .DS_Store files (#260) --- .gitignore | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4938d7e6cc1a..13fc140abf9f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,41 @@ compose-dev.yaml #### Below sections are auto-generated #### -# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim -# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,vim +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,vim,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud ### Rust ### # Generated by Cargo @@ -67,4 +100,4 @@ tags .history .ionide -# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,vim,macos From 7eff5cf0fdbf0a7ce673108f7041f93e193ceb10 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 19 Sep 2023 19:00:21 -0700 Subject: [PATCH 0290/1053] chore: add license header to firewood files (#262) --- LICENSE.md | 87 ++++++++++++++++------------- firewood/benches/hashops.rs | 3 + firewood/src/config.rs | 3 + firewood/src/db/proposal.rs | 3 + firewood/src/merkle/node.rs | 3 + firewood/src/merkle/partial_path.rs | 3 + firewood/src/merkle/trie_hash.rs | 3 + firewood/src/nibbles.rs | 3 + firewood/src/storage/buffer.rs | 3 + firewood/src/storage/mod.rs | 3 + firewood/src/v2/api.rs | 3 + firewood/src/v2/db.rs | 3 + firewood/src/v2/emptydb.rs | 3 + firewood/src/v2/mod.rs | 3 + firewood/src/v2/propose.rs | 3 + firewood/tests/db.rs | 3 + firewood/tests/merkle.rs | 3 + fwdctl/tests/cli.rs | 3 + growth-ring/README.md | 3 + growth-ring/README.rst | 11 ---- growth-ring/examples/demo1.rs | 3 + growth-ring/src/lib.rs | 3 + growth-ring/src/wal.rs | 3 + growth-ring/src/walerror.rs | 3 + growth-ring/tests/common/mod.rs | 3 + growth-ring/tests/rand_fail.rs | 3 + libaio/README.md | 3 + libaio/build.rs | 3 + libaio/src/abi.rs | 3 + libaio/src/lib.rs | 2 + libaio/tests/simple_test.rs | 3 + rpc/build.rs | 3 + rpc/src/bin/client.rs | 3 + rpc/src/bin/server.rs | 3 + rpc/src/lib.rs | 3 + rpc/src/service.rs | 3 + rpc/src/service/database.rs | 3 + rpc/src/service/db.rs | 3 + shale/LICENSE | 22 -------- shale/README.md | 3 + shale/benches/shale-bench.rs | 3 + shale/src/cached.rs | 3 + shale/src/compact.rs | 3 + shale/src/disk_address.rs | 3 + shale/src/lib.rs | 3 + 45 files changed, 172 insertions(+), 73 deletions(-) create mode 100644 growth-ring/README.md delete mode 100644 growth-ring/README.rst create mode 100644 libaio/README.md delete mode 100644 shale/LICENSE create mode 100644 shale/README.md diff --git a/LICENSE.md b/LICENSE.md index 8f543d3df535..fbc13b0da963 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,59 +1,66 @@ -© 2023 Ava Labs, Inc. +Copyright (C) 2023, Ava Labs, Inc. All rights reserved. Ecosystem License +Version: 1.1 -Subject to the terms herein, Ava Labs, Inc. (**“Ava Labs”**) hereby grants you a -limited, royalty-free, worldwide, non-sublicensable, -non-transferable,non-exclusive license to use, copy, modify, create derivative -works based on,and redistribute the Software, in source code, binary, or any -other form,including any modifications or derivative works of the Software -(collectively,**“Licensed Software”**), in each case subject to this Ecosystem -License (**“License”**). +Subject to the terms herein, Ava Labs, Inc. (**“Ava Labs”**) hereby grants you +a limited, royalty-free, worldwide, non-sublicensable, non-transferable, +non-exclusive license to use, copy, modify, create derivative works based on, +and redistribute the Software, in source code, binary, or any other form, +including any modifications or derivative works of the Software (collectively, +**“Licensed Software”**), in each case subject to this Ecosystem License +(**“License”**). This License applies to all copies, modifications, derivative works, and any -other form or usage of the Licensed Software. You will include and display this -License, without modification, with all uses of the Licensed Software,regardless -of form. - -You will use the Licensed Software solely in connection with the Avalanche -Public Blockchain platform and associated blockchains, comprised exclusively of -the Avalanche X-Chain, C-Chain, P-Chain and any subnets linked to the -P-Chain(“Avalanche Authorized Platform”). This License does not permit use of -the Licensed Software in connection with any forks of the Avalanche Authorized -Platform or in any manner not operationally connected to the Avalanche -Authorized Platform. Ava Labs may publicly announce changes or additions to the -Avalanche Authorized Platform, which may expand or modify usage of the Licensed -Software. Upon such announcement, the Avalanche Authorized Platform will be -deemed to be the then-current iteration of such platform. +other form or usage of the Licensed Software. You will include and display +this License, without modification, with all uses of the Licensed Software, +regardless of form. + +You will use the Licensed Software solely (i) in connection with the Avalanche +Public Blockchain platform, having a NetworkID of 1 (Mainnet) or 5 (Fuji), and +associated blockchains, comprised exclusively of the Avalanche X-Chain, +C-Chain, P-Chain and any subnets linked to the P-Chain (“Avalanche Authorized +Platform”) or (ii) for non-production, testing or research purposes within the +Avalanche ecosystem, in each case, without any commercial application +(“Non-Commercial Use”); provided that this License does not permit use of the +Licensed Software in connection with (a) any forks of the Avalanche Authorized +Platform or (b) in any manner not operationally connected to the Avalanche +Authorized Platform other than, for the avoidance of doubt, the limited +exception for Non-Commercial Use. Ava Labs may publicly announce changes or +additions to the Avalanche Authorized Platform, which may expand or modify +usage of the Licensed Software. Upon such announcement, the Avalanche +Authorized Platform will be deemed to be the then-current iteration of such +platform. You hereby acknowledge and agree to the terms set forth at www.avalabs.org/important-notice. -If you use the Licensed Software in violation of this License, this License will -automatically terminate and Ava Labs reserves all rights to seek any remedy for -such violation. +If you use the Licensed Software in violation of this License, this License +will automatically terminate and Ava Labs reserves all rights to seek any +remedy for such violation. Except for uses explicitly permitted in this License, Ava Labs retains all rights in the Licensed Software, including without limitation the ability to modify it. -Except as required or explicitly permitted by this License, you will not use any -Ava Labs names, logos, or trademarks without Ava Labs’ prior written consent. +Except as required or explicitly permitted by this License, you will not use +any Ava Labs names, logos, or trademarks without Ava Labs’ prior written +consent. You may use this License for software other than the “Licensed Software” -specified above, as long as the only change to this License is the definition of -the term “Licensed Software.” +specified above, as long as the only change to this License is the definition +of the term “Licensed Software.” -The Licensed Software may reference third party components. You acknowledge and -agree that these third party components may be governed by a separate license or -terms and that you will comply with them. +The Licensed Software may reference third party components. You acknowledge +and agree that these third party components may be governed by a separate +license or terms and that you will comply with them. -**TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSED SOFTWARE IS PROVIDED ON -AN “AS IS” BASIS, AND AVA LABS EXPRESSLY DISCLAIMS AND EXCLUDES ALL +**TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSED SOFTWARE IS PROVIDED +ON AN “AS IS” BASIS, AND AVA LABS EXPRESSLY DISCLAIMS AND EXCLUDES ALL REPRESENTATIONS, WARRANTIES AND OTHER TERMS AND CONDITIONS, WHETHER EXPRESS OR -IMPLIED, INCLUDING WITHOUT LIMITATION BY OPERATION OF LAW OR BY CUSTOM, STATUTE -OR OTHERWISE, AND INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTY, TERM, OR -CONDITION OF NON-INFRINGEMENT, MERCHANTABILITY, TITLE, OR FITNESS FOR PARTICULAR -PURPOSE. YOU USE THE LICENSED SOFTWARE AT YOUR OWN RISK. AVA LABS EXPRESSLY -DISCLAIMS ALL LIABILITY (INCLUDING FOR ALL DIRECT, CONSEQUENTIAL OR OTHER -DAMAGES OR LOSSES) RELATED TO ANY USE OF THE LICENSED SOFTWARE.** +IMPLIED, INCLUDING WITHOUT LIMITATION BY OPERATION OF LAW OR BY CUSTOM, +STATUTE OR OTHERWISE, AND INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTY, +TERM, OR CONDITION OF NON-INFRINGEMENT, MERCHANTABILITY, TITLE, OR FITNESS FOR +PARTICULAR PURPOSE. YOU USE THE LICENSED SOFTWARE AT YOUR OWN RISK. AVA LABS +EXPRESSLY DISCLAIMS ALL LIABILITY (INCLUDING FOR ALL DIRECT, CONSEQUENTIAL OR +OTHER DAMAGES OR LOSSES) RELATED TO ANY USE OF THE LICENSED SOFTWARE.** \ No newline at end of file diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 33acc5da4358..bebac2ab134c 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // hash benchmarks; run with 'cargo bench' use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; diff --git a/firewood/src/config.rs b/firewood/src/config.rs index f4bfcf658782..6a13e4ffd613 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; use typed_builder::TypedBuilder; diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index debece604210..befc1a6ad534 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use super::{ get_sub_universe_from_deltas, get_sub_universe_from_empty_delta, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, MERKLE_META_SPACE, diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 17c8a9d98a74..7d96096522e0 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use enum_as_inner::EnumAsInner; use sha3::{Digest, Keccak256}; use shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs index 634a02995fe2..b20394353348 100644 --- a/firewood/src/merkle/partial_path.rs +++ b/firewood/src/merkle/partial_path.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::fmt::{self, Debug}; /// PartialPath keeps a list of nibbles to represent a path on the Trie. diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 922d843bf771..1f64788ae759 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use shale::{CachedStore, ShaleError, Storable}; use std::{ fmt::{self, Debug}, diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index eef36f3db418..aa98a9c11615 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::{iter::FusedIterator, ops::Index}; static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index f4173273e175..7f7de2996ce1 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + //! Disk buffer for staging in memory pages and flushing them to disk. use std::fmt::Debug; use std::ops::IndexMut; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index e535c1706ace..d59e47a949d4 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // TODO: try to get rid of the use `RefCell` in this file use self::buffer::DiskBufferRequester; use crate::file::File; diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 9776179f82e8..51fea04e0a90 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::{collections::HashMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 31b21eb575db..dfa342242cd5 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::{fmt::Debug, ops::DerefMut, sync::Arc}; use tokio::sync::Mutex; diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 8d7a3d5bd84a..c7c8839b02df 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::sync::Arc; use async_trait::async_trait; diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index c84826759421..454b1d3cfeca 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + pub mod api; pub mod db; pub mod propose; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 4520173a257d..f2cb1f6c7fad 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index dfb867936b1f..715c388c521a 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use firewood::{ db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, merkle::TrieHash, diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 57db390164ac..6fbbd5b3623c 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use firewood::{ merkle::Node, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 111f3cb39ca1..4b92aad534bd 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; diff --git a/growth-ring/README.md b/growth-ring/README.md new file mode 100644 index 000000000000..a6f1035573b8 --- /dev/null +++ b/growth-ring/README.md @@ -0,0 +1,3 @@ +# growth-ring + +This crate was forked from [`growth-ring`](https://github.com/Determinant/growth-ring) at commit [`8a1d4b9`](https://github.com/Determinant/growth-ring/commit/8a1d4b9d3a06449b02127645c2110accc9d5390d), under MIT license. \ No newline at end of file diff --git a/growth-ring/README.rst b/growth-ring/README.rst deleted file mode 100644 index ff61f443f439..000000000000 --- a/growth-ring/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -growth-ring -=========== - -.. image:: https://travis-ci.com/ava-labs/growth-ring.svg?token=EbLxqxy3qxjHrZKkyoP4&branch=master - :target: https://travis-ci.com/Determinant/growth-ring - -Documentation -------------- -- Latest_ - -.. _Latest: https://docs.rs/growth-ring diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs index a75e89ff6933..f7dfcd720228 100644 --- a/growth-ring/examples/demo1.rs +++ b/growth-ring/examples/demo1.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use futures::executor::block_on; use growthring::{ wal::{WalBytes, WalLoader, WalRingId, WalWriter}, diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 0880434b9faa..3a6e58d4c0f6 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. +// //! Simple and modular write-ahead-logging implementation. //! //! # Examples diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 7208102d129e..622477c3742f 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; use futures::{ diff --git a/growth-ring/src/walerror.rs b/growth-ring/src/walerror.rs index 6feb0b025ba6..508dd146274a 100644 --- a/growth-ring/src/walerror.rs +++ b/growth-ring/src/walerror.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::sync::Arc; use nix::errno::Errno; diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index fe21dd29992c..16c110c24362 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + #[cfg(test)] use async_trait::async_trait; use futures::executor::block_on; diff --git a/growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs index 97435d012243..3557f53845dd 100644 --- a/growth-ring/tests/rand_fail.rs +++ b/growth-ring/tests/rand_fail.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + #[cfg(test)] mod common; diff --git a/libaio/README.md b/libaio/README.md new file mode 100644 index 000000000000..e245c1808d48 --- /dev/null +++ b/libaio/README.md @@ -0,0 +1,3 @@ +# libaio + +This crate was forked from [`libaio-futures`](https://github.com/Determinant/libaio-futures) at commit [`61861e2`](https://github.com/Determinant/libaio-futures/commit/61861e20cee14211d03a1b895721b27fce148aef), under MIT license. \ No newline at end of file diff --git a/libaio/build.rs b/libaio/build.rs index fd58353f4749..ec369eef8626 100644 --- a/libaio/build.rs +++ b/libaio/build.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::env; fn main() { diff --git a/libaio/src/abi.rs b/libaio/src/abi.rs index 995dbce0fed5..b4784bd5cbac 100644 --- a/libaio/src/abi.rs +++ b/libaio/src/abi.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // libaio ABI adapted from: // https://raw.githubusercontent.com/jsgf/libaio-rust/master/src/aioabi.rs #![allow(dead_code)] diff --git a/libaio/src/lib.rs b/libaio/src/lib.rs index 2bb8a8021fbf..c3332e860155 100644 --- a/libaio/src/lib.rs +++ b/libaio/src/lib.rs @@ -1,3 +1,5 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. //! Straightforward Linux AIO using Futures/async/await. //! //! # Example diff --git a/libaio/tests/simple_test.rs b/libaio/tests/simple_test.rs index 86eea76387ff..e01b24254d22 100644 --- a/libaio/tests/simple_test.rs +++ b/libaio/tests/simple_test.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use aiofut::AioBuilder; use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; use std::{os::unix::io::AsRawFd, path::PathBuf}; diff --git a/rpc/build.rs b/rpc/build.rs index 54a51676e660..beb3f015046a 100644 --- a/rpc/build.rs +++ b/rpc/build.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + fn main() -> Result<(), Box> { tonic_build::compile_protos("proto/sync/sync.proto")?; tonic_build::compile_protos("proto/rpcdb/rpcdb.proto")?; diff --git a/rpc/src/bin/client.rs b/rpc/src/bin/client.rs index 12b90dcd53ce..2fe2d056c828 100644 --- a/rpc/src/bin/client.rs +++ b/rpc/src/bin/client.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + fn main() { println!("Hello from {}", file!()); } diff --git a/rpc/src/bin/server.rs b/rpc/src/bin/server.rs index c4eed2d28821..80ef8039593b 100644 --- a/rpc/src/bin/server.rs +++ b/rpc/src/bin/server.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use rpc::{ rpcdb::database_server::DatabaseServer as RpcServer, sync::db_server::DbServer as SyncServer, DatabaseService, diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 7eac8c0a9545..7a06133519ea 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + pub mod sync { tonic::include_proto!("sync"); } diff --git a/rpc/src/service.rs b/rpc/src/service.rs index d48d6994ae60..256484de9ff9 100644 --- a/rpc/src/service.rs +++ b/rpc/src/service.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use firewood::v2::{ api::{Db, Error}, emptydb::{EmptyDb, HistoricalImpl}, diff --git a/rpc/src/service/database.rs b/rpc/src/service/database.rs index 13d4b7878776..d3ac2cce01c2 100644 --- a/rpc/src/service/database.rs +++ b/rpc/src/service/database.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use super::{Database as DatabaseService, IntoStatusResultExt, Iter}; use crate::rpcdb::{ database_server::Database, CloseRequest, CloseResponse, CompactRequest, CompactResponse, diff --git a/rpc/src/service/db.rs b/rpc/src/service/db.rs index b9ce5fc83999..54d3ae40902d 100644 --- a/rpc/src/service/db.rs +++ b/rpc/src/service/db.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use super::{Database, IntoStatusResultExt}; use crate::sync::{ db_server::Db as DbServerTrait, CommitChangeProofRequest, CommitRangeProofRequest, diff --git a/shale/LICENSE b/shale/LICENSE deleted file mode 100644 index 81a1d4cf1dfb..000000000000 --- a/shale/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2019 Maofan (Ted) Yin -Copyright (c) 2019 Cornell University - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/shale/README.md b/shale/README.md new file mode 100644 index 000000000000..96396377c416 --- /dev/null +++ b/shale/README.md @@ -0,0 +1,3 @@ +# shale + +This crate was forked from [`shale`](https://github.com/Determinant/shale) at commit [`caa6d75`](https://github.com/Determinant/shale/commit/caa6d7543d253e2172c51a65d226d65d232a9b9a), under MIT license. \ No newline at end of file diff --git a/shale/benches/shale-bench.rs b/shale/benches/shale-bench.rs index 1d2bbb396f93..e29006cc5ac0 100644 --- a/shale/benches/shale-bench.rs +++ b/shale/benches/shale-bench.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; diff --git a/shale/src/cached.rs b/shale/src/cached.rs index 15b7edf624a8..b98a2dc29039 100644 --- a/shale/src/cached.rs +++ b/shale/src/cached.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use std::{ borrow::BorrowMut, diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 495c3304fa2a..a5ca3c1553e5 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use super::disk_address::DiskAddress; use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; use std::fmt::Debug; diff --git a/shale/src/disk_address.rs b/shale/src/disk_address.rs index 1b7e78a4594b..571a6d3eab2e 100644 --- a/shale/src/disk_address.rs +++ b/shale/src/disk_address.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::hash::Hash; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 83c8685b45ad..84656cc6aee3 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; From b4eea41371cfd87e782b5b09c61def6aca89fdf1 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 20 Sep 2023 18:31:59 -0400 Subject: [PATCH 0291/1053] Firewood should start with an uppercase letter (#264) --- firewood/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index c17677eeada4..e8045acdec77 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -8,12 +8,12 @@ //! into past state. It does not copy-on-write the state trie to generate an ever //! growing forest of tries like other databases, but instead keeps one latest version of the trie index on disk //! and apply in-place updates to it. This ensures that the database size is small and stable -//! during the course of running firewood. Firewood was first conceived to provide a very fast +//! during the course of running Firewood. Firewood was first conceived to provide a very fast //! storage layer for the EVM but could be used on any blockchain that requires authenticated state. //! //! Firewood is a robust database implemented from the ground up to directly store trie nodes and //! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a -//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, firewood directly uses +//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, Firewood directly uses //! the tree structure as the index on disk. Thus, there is no additional "emulation" of the //! logical trie to flatten out the data structure to feed into the underlying DB that is unaware //! of the data being stored. It provides generic trie storage for arbitrary keys and values. @@ -48,7 +48,7 @@ //! persisted and available to the blockchain system as that's what matters for most of the time. //! While one can still keep some volatile state versions in memory for mutation and VM //! execution, the final commit to some state works on a singleton so the indexed merkle tries -//! may be typically updated in place. It is also possible (e.g., firewood) to allow some +//! may be typically updated in place. It is also possible (e.g., Firewood) to allow some //! infrequent access to historical versions with higher cost, and/or allow fast access to //! versions of the store within certain limited recency. This style of storage is useful for //! the blockchain systems where only (or mostly) the latest state is required and data footprint @@ -56,7 +56,7 @@ //! directly participate in the consensus and vote for the blocks, for example, can largely //! benefit from such a design. //! -//! In firewood, we take a closer look at the second regime and have come up with a simple but +//! In Firewood, we take a closer look at the second regime and have come up with a simple but //! robust architecture that fulfills the need for such blockchain storage. //! //! ## Storage Model @@ -77,7 +77,7 @@ //! objects that are persisted on disk but also made accessible in memory transparently. It is //! built on top of `CachedStore` by defining how "items" of the given type are laid out, allocated //! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of -//! basic implementation in `shale` crate, but one can always define his/her own `ShaleStore`). +//! basic implementation in `shale` crate, but one can always define their own `ShaleStore`). //! //! - Data structure: in Firewood, one trie is maintained by invoking `ShaleStore` (see `src/merkle.rs`). //! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or From 39c726e0133931b8e288649a2d5a6332625c6f7c Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:14:28 -0700 Subject: [PATCH 0292/1053] Remove crates information in README.md (#265) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index cfd8abd7712f..526f37af9c3d 100644 --- a/README.md +++ b/README.md @@ -38,17 +38,6 @@ versions with no additional cost at all. firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). -## Vendored Crates -The following crates are vendored in this repository to allow for making -modifications without requiring upstream approval: -* [`growth-ring`](https://github.com/Determinant/growth-ring) -* [`libaio-futures`](https://github.com/Determinant/libaio-futures) -* [`shale`](https://github.com/Determinant/shale) - -These crates will either be heavily modified or removed prior to the production -launch of firewood. If they are retained, all changes made will be shared -upstream. - ## Termimology * `Revision` - A historical point-in-time state/version of the trie. This From 41547008131a3aee2c1df1eb3662d7ac6be3fdcc Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 21 Sep 2023 18:59:57 -0400 Subject: [PATCH 0293/1053] Use nibbles more (#267) --- firewood/src/merkle.rs | 28 +++++---- firewood/src/merkle/node.rs | 4 +- firewood/src/merkle/partial_path.rs | 32 +++++++---- firewood/src/nibbles.rs | 33 ++++++----- firewood/src/proof.rs | 88 ++++++++++++++--------------- 5 files changed, 101 insertions(+), 84 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 28994e6d7e84..5f251a50c7d8 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -370,7 +370,7 @@ impl + Send + Sync> Merkle { let mut val = Some(val); // walk down the merkle tree starting from next_node, currently the root - for (key_nib_offset, key_nib) in key_nibbles.iter().enumerate() { + for (key_nib_offset, key_nib) in key_nibbles.into_iter().enumerate() { // special handling for extension nodes if nskip > 0 { nskip -= 1; @@ -393,7 +393,9 @@ impl + Send + Sync> Merkle { // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(key_nibbles.iter().skip(key_nib_offset + 1).collect()), + PartialPath( + key_nibbles.into_iter().skip(key_nib_offset + 1).collect(), + ), Data(val.take().unwrap()), ))))? .as_ptr(); @@ -412,7 +414,10 @@ impl + Send + Sync> Merkle { // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); - let rem_path = key_nibbles.iter().skip(key_nib_offset).collect::>(); + let rem_path = key_nibbles + .into_iter() + .skip(key_nib_offset) + .collect::>(); self.split( node, &mut parents, @@ -428,7 +433,10 @@ impl + Send + Sync> Merkle { let n_path = n.0.to_vec(); let n_ptr = n.1; nskip = n_path.len() - 1; - let rem_path = key_nibbles.iter().skip(key_nib_offset).collect::>(); + let rem_path = key_nibbles + .into_iter() + .skip(key_nib_offset) + .collect::>(); if let Some(v) = self.split( node, @@ -1044,7 +1052,7 @@ impl + Send + Sync> Merkle { let mut nskip = 0; let mut nodes: Vec = Vec::new(); - for (i, nib) in key_nibbles.iter().enumerate() { + for (i, nib) in key_nibbles.into_iter().enumerate() { if nskip > 0 { nskip -= 1; continue; @@ -1060,7 +1068,7 @@ impl + Send + Sync> Merkle { // the key passed in must match the entire remainder of this // extension node, otherwise we break out let n_path = &*n.0; - let remaining_path = key_nibbles.iter().skip(i); + let remaining_path = key_nibbles.into_iter().skip(i); if remaining_path.size_hint().0 < n_path.len() { // all bytes aren't there break; @@ -1115,7 +1123,7 @@ impl + Send + Sync> Merkle { let mut u_ref = self.get_node(root)?; let mut nskip = 0; - for (i, nib) in key_nibbles.iter().enumerate() { + for (i, nib) in key_nibbles.into_iter().enumerate() { if nskip > 0 { nskip -= 1; continue; @@ -1126,14 +1134,14 @@ impl + Send + Sync> Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - if !key_nibbles.iter().skip(i).eq(n.0.iter().cloned()) { + if !key_nibbles.into_iter().skip(i).eq(n.0.iter().cloned()) { return Ok(None); } return Ok(Some(Ref(u_ref))); } NodeType::Extension(n) => { let n_path = &*n.0; - let rem_path = key_nibbles.iter().skip(i); + let rem_path = key_nibbles.into_iter().skip(i); if rem_path.size_hint().0 < n_path.len() { return Ok(None); } @@ -1287,7 +1295,7 @@ mod test { #[test] fn test_partial_path_encoding() { let check = |steps: &[u8], term| { - let (d, t) = PartialPath::decode(PartialPath(steps.to_vec()).encode(term)); + let (d, t) = PartialPath::decode(&PartialPath(steps.to_vec()).encode(term)); assert_eq!(d.0, steps); assert_eq!(t, term); }; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 7d96096522e0..cb1a6c5162f1 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -526,7 +526,7 @@ impl Storable for Node { .flat_map(to_nibble_array) .collect(); - let (path, _) = PartialPath::decode(nibbles); + let (path, _) = PartialPath::decode(&nibbles); let mut buff = [0_u8; 1]; let rlp_len_raw = mem @@ -598,7 +598,7 @@ impl Storable for Node { .flat_map(to_nibble_array) .collect(); - let (path, _) = PartialPath::decode(nibbles); + let (path, _) = PartialPath::decode(&nibbles); let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); Ok(Self::new_from_hash( root_hash, diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs index b20394353348..b7aa70315425 100644 --- a/firewood/src/merkle/partial_path.rs +++ b/firewood/src/merkle/partial_path.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::nibbles::NibblesIterator; use std::fmt::{self, Debug}; /// PartialPath keeps a list of nibbles to represent a path on the Trie. @@ -40,18 +41,25 @@ impl PartialPath { res } - pub fn decode>(raw: R) -> (Self, bool) { - let raw = raw.as_ref(); - let term = raw[0] > 1; - let odd_len = raw[0] & 1; - ( - Self(if odd_len == 1 { - raw[1..].to_vec() - } else { - raw[2..].to_vec() - }), - term, - ) + // TODO: remove all non `Nibbles` usages and delete this function. + // I also think `PartialPath` could probably borrow instead of own data. + // + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn decode(raw: &[u8]) -> (Self, bool) { + let prefix = raw[0]; + let is_odd = (prefix & 1) as usize; + let decoded = raw.iter().skip(1).skip(1 - is_odd).copied().collect(); + + (Self(decoded), prefix > 1) + } + + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { + let prefix = nibbles.next().unwrap(); + let is_odd = (prefix & 1) as usize; + let decoded = nibbles.skip(1 - is_odd).collect(); + + (Self(decoded), prefix > 1) } pub(super) fn dehydrated_len(&self) -> u64 { diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index aa98a9c11615..d3b356fdba5d 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -7,7 +7,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// Nibbles is a newtype that contains only a reference to a [u8], and produces /// nibbles. Nibbles can be indexed using nib\[x\] or you can get an iterator -/// with iter() +/// with `into_iter()` /// /// Nibbles can be constructed with a number of leading zeroes. This is used /// in firewood because there is a sentinel node, so we always want the first @@ -23,21 +23,21 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// # use firewood::nibbles; /// # fn main() { /// let nib = nibbles::Nibbles::<0>::new(&[0x56, 0x78]); -/// assert_eq!(nib.iter().collect::>(), [0x5, 0x6, 0x7, 0x8]); +/// assert_eq!(nib.into_iter().collect::>(), [0x5, 0x6, 0x7, 0x8]); /// /// // nibbles can be efficiently advanced without rendering the /// // intermediate values -/// assert_eq!(nib.iter().skip(3).collect::>(), [0x8]); +/// assert_eq!(nib.into_iter().skip(3).collect::>(), [0x8]); /// /// // nibbles can also be indexed /// /// assert_eq!(nib[1], 0x6); /// /// // or reversed -/// assert_eq!(nib.iter().rev().next(), Some(0x8)); +/// assert_eq!(nib.into_iter().rev().next(), Some(0x8)); /// # } /// ``` -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct Nibbles<'a, const LEADING_ZEROES: usize>(&'a [u8]); impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROES> { @@ -54,16 +54,21 @@ impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROE } } -impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { +impl<'a, const LEADING_ZEROES: usize> IntoIterator for Nibbles<'a, LEADING_ZEROES> { + type Item = u8; + type IntoIter = NibblesIterator<'a, LEADING_ZEROES>; + #[must_use] - pub fn iter(&self) -> NibblesIterator<'_, LEADING_ZEROES> { + fn into_iter(self) -> Self::IntoIter { NibblesIterator { data: self, head: 0, tail: self.len(), } } +} +impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { #[must_use] pub fn len(&self) -> usize { LEADING_ZEROES + 2 * self.0.len() @@ -79,11 +84,11 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { } } -/// An interator returned by [Nibbles::iter] +/// An interator returned by [Nibbles::into_iter] /// See their documentation for details. #[derive(Clone, Debug)] pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { - data: &'a Nibbles<'a, LEADING_ZEROES>, + data: Nibbles<'a, LEADING_ZEROES>, head: usize, tail: usize, } @@ -161,14 +166,14 @@ mod test { fn leading_zero_nibbles_iter() { let nib = Nibbles::<1>(&TEST_BYTES); let expected: [u8; 9] = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; - expected.into_iter().eq(nib.iter()); + expected.into_iter().eq(nib.into_iter()); } #[test] fn skip_skips_zeroes() { let nib1 = Nibbles::<1>(&TEST_BYTES); let nib0 = Nibbles::<0>(&TEST_BYTES); - assert!(nib1.iter().skip(1).eq(nib0.iter())); + assert!(nib1.into_iter().skip(1).eq(nib0.into_iter())); } #[test] @@ -187,7 +192,7 @@ mod test { #[test] fn size_hint_0() { let nib = Nibbles::<0>(&TEST_BYTES); - let mut nib_iter = nib.iter(); + let mut nib_iter = nib.into_iter(); assert_eq!((8, Some(8)), nib_iter.size_hint()); let _ = nib_iter.next(); assert_eq!((7, Some(7)), nib_iter.size_hint()); @@ -196,7 +201,7 @@ mod test { #[test] fn size_hint_1() { let nib = Nibbles::<1>(&TEST_BYTES); - let mut nib_iter = nib.iter(); + let mut nib_iter = nib.into_iter(); assert_eq!((9, Some(9)), nib_iter.size_hint()); let _ = nib_iter.next(); assert_eq!((8, Some(8)), nib_iter.size_hint()); @@ -205,7 +210,7 @@ mod test { #[test] fn backwards() { let nib = Nibbles::<1>(&TEST_BYTES); - let nib_iter = nib.iter().rev(); + let nib_iter = nib.into_iter().rev(); let expected = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd, 0x0]; assert!(nib_iter.eq(expected)); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index c324d7559d4f..8d8c485ae200 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -11,6 +11,8 @@ use shale::ShaleError; use shale::ShaleStore; use thiserror::Error; +use crate::nibbles::Nibbles; +use crate::nibbles::NibblesIterator; use crate::{ db::DbError, merkle::{ @@ -114,53 +116,45 @@ impl + Send> Proof { key: K, root_hash: [u8; 32], ) -> Result>, ProofError> { - let mut key_nibbles = Vec::new(); - key_nibbles.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + let mut key_nibbles = Nibbles::<0>::new(key.as_ref()).into_iter(); - let mut remaining_key_nibbles: &[u8] = &key_nibbles; let mut cur_hash = root_hash; let proofs_map = &self.0; - let mut index = 0; loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; - let (sub_proof, size) = - self.locate_subproof(remaining_key_nibbles, cur_proof.as_ref())?; - index += size; + let (sub_proof, traversed_nibbles) = + self.locate_subproof(key_nibbles, cur_proof.as_ref())?; + key_nibbles = traversed_nibbles; - match sub_proof { + cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(p) if index == key_nibbles.len() => return Ok(Some(p.rlp)), + Some(p) if key_nibbles.size_hint().0 == 0 => return Ok(Some(p.rlp)), // The trie doesn't contain the key. - Some(SubProof { hash: None, .. }) | None => return Ok(None), - Some(p) => { - cur_hash = p.hash.unwrap(); - remaining_key_nibbles = &key_nibbles[index..]; - } - } + Some(SubProof { + hash: Some(hash), .. + }) => hash, + _ => return Ok(None), + }; } } - fn locate_subproof( + fn locate_subproof<'a>( &self, - key_nibbles: &[u8], + mut key_nibbles: NibblesIterator<'a, 0>, rlp_encoded_node: &[u8], - ) -> Result<(Option, usize), ProofError> { + ) -> Result<(Option, NibblesIterator<'a, 0>), ProofError> { let rlp = rlp::Rlp::new(rlp_encoded_node); match rlp.item_count() { Ok(EXT_NODE_SIZE) => { - let decoded_key_nibbles: Vec<_> = rlp - .at(0) - .unwrap() - .as_val::>() - .unwrap() - .into_iter() - .flat_map(to_nibble_array) - .collect(); - let (cur_key_path, term) = PartialPath::decode(decoded_key_nibbles); + let decoded_key = rlp.at(0).unwrap().as_val::>().unwrap(); + let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); + + let (cur_key_path, term) = + PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); let rlp = rlp.at(1).unwrap(); @@ -171,30 +165,32 @@ impl + Send> Proof { rlp.as_raw().to_vec() }; - // Check if the key of current node match with the given key. - if key_nibbles.len() < cur_key.len() || key_nibbles[..cur_key.len()] != cur_key { - return Ok((None, 0)); + // Check if the key of current node match with the given key + // and consume the current-key portion of the nibbles-iterator + let does_not_match = key_nibbles.size_hint().0 < cur_key.len() + || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); + + if does_not_match { + return Ok((None, Nibbles::<0>::new(&[]).into_iter())); } - if term { - Ok(( - Some(SubProof { - rlp: data, - hash: None, - }), - cur_key.len(), - )) + let sub_proof = if term { + SubProof { + rlp: data, + hash: None, + } } else { - self.generate_subproof(data) - .map(|subproof| (Some(subproof), cur_key.len())) - } + self.generate_subproof(data)? + }; + + Ok((sub_proof.into(), key_nibbles)) } - Ok(BRANCH_NODE_SIZE) if key_nibbles.is_empty() => Err(ProofError::NoSuchNode), + Ok(BRANCH_NODE_SIZE) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), Ok(BRANCH_NODE_SIZE) => { - let index = key_nibbles[0]; - let rlp = rlp.at(index as usize).unwrap(); + let index = key_nibbles.next().unwrap() as usize; + let rlp = rlp.at(index).unwrap(); let data = if rlp.is_data() { rlp.as_val::>().unwrap() @@ -203,7 +199,7 @@ impl + Send> Proof { }; self.generate_subproof(data) - .map(|subproof| (Some(subproof), 1)) + .map(|subproof| (Some(subproof), key_nibbles)) } Ok(_) => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), @@ -557,7 +553,7 @@ impl + Send> Proof { .flat_map(to_nibble_array) .collect(); - let (cur_key_path, term) = PartialPath::decode(cur_key_path); + let (cur_key_path, term) = PartialPath::decode(&cur_key_path); let cur_key = cur_key_path.into_inner(); let rlp = rlp.at(1)?; From 4c9beaabf96e1e7b0303fa219577747a61d9edd5 Mon Sep 17 00:00:00 2001 From: exdx Date: Mon, 25 Sep 2023 15:33:37 -0400 Subject: [PATCH 0294/1053] Update README.md (#270) --- .github/workflows/ci.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 17e67397c7fc..79d393c59fad 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,7 +1,7 @@ name: ci on: - pull_request: + push: branches: - "*" diff --git a/README.md b/README.md index 526f37af9c3d..c890ef3a5b62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. -![Github Actions](https://github.com/ava-labs/firewood/actions/workflows/ci.yaml/badge.svg) +![Github Actions](https://github.com/ava-labs/firewood/actions/workflows/ci.yaml/badge.svg?branch=main) [![Ecosystem license](https://img.shields.io/badge/License-Ecosystem-blue.svg)](./LICENSE.md) > :warning: firewood is alpha-level software and is not ready for production From 063e5a754d07d2b12562db4313f28cc5a54d8675 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 25 Sep 2023 16:22:17 -0400 Subject: [PATCH 0295/1053] Revert "Update README.md (#270)" (#272) Co-authored-by: exdx --- .github/workflows/ci.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 79d393c59fad..e18c5371dbbd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,9 +1,10 @@ name: ci on: + pull_request: + branches: ['*'] push: - branches: - - "*" + branches: [main] env: CARGO_TERM_COLOR: always @@ -39,7 +40,7 @@ jobs: run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches - # Always update the cache + # Always update the cache - name: Cleanup run: | gh extension install actions/gh-actions-cache From 9f6cc49fddec66abd211d07497f49e622ff0d389 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 25 Sep 2023 17:48:11 -0400 Subject: [PATCH 0296/1053] Generalize Node Encoding Scheme (#269) Co-authored-by: hhao --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 87 ++++++++++++++++++- firewood/src/merkle/node.rs | 165 +++++++++++++++++++++++++++++------- firewood/src/nibbles.rs | 2 +- firewood/src/proof.rs | 164 ++++++++++++++++++++++------------- firewood/tests/merkle.rs | 19 +---- 6 files changed, 329 insertions(+), 109 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 139a2b0d6661..70c67c5629b1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -36,6 +36,7 @@ sha3 = "0.10.2" thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } typed-builder = "0.16.0" +bincode = "1.3.3" [dev-dependencies] criterion = "0.5.1" diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5f251a50c7d8..cee523654cf9 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -15,6 +15,7 @@ mod node; mod partial_path; mod trie_hash; +pub(crate) use node::Encoded; pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; pub use partial_path::PartialPath; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; @@ -1256,9 +1257,10 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { #[cfg(test)] mod test { use super::*; - use shale::cached::PlainMem; + use shale::cached::{DynamicMem, PlainMem}; use shale::{CachedStore, Storable}; use std::ops::Deref; + use std::sync::Arc; use test_case::test_case; #[test_case(vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] @@ -1381,4 +1383,87 @@ mod test { check(node); } } + #[test] + fn test_encode() { + const RESERVED: usize = 0x1000; + + let mut dm = shale::cached::DynamicMem::new(0x10000, 0); + let compact_header = DiskAddress::null(); + dm.write( + compact_header.into(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + std::num::NonZeroUsize::new(RESERVED).unwrap(), + std::num::NonZeroUsize::new(RESERVED).unwrap(), + )) + .unwrap(), + ); + let compact_header = shale::StoredView::ptr_to_obj( + &dm, + compact_header, + shale::compact::CompactHeader::MSIZE, + ) + .unwrap(); + let mem_meta = Arc::new(dm); + let mem_payload = Arc::new(DynamicMem::new(0x10000, 0x1)); + + let cache = shale::ObjCache::new(1); + let space = + shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("CompactSpace init fail"); + + let store = Box::new(space); + let merkle = Merkle::new(store); + + { + let chd = Node::new(NodeType::Leaf(LeafNode( + PartialPath(vec![0x1, 0x2, 0x3]), + Data(vec![0x4, 0x5]), + ))); + let chd_ref = merkle.new_node(chd.clone()).unwrap(); + let chd_rlp = chd_ref.get_eth_rlp(merkle.store.as_ref()); + let new_chd = Node::new(NodeType::decode(chd_rlp).unwrap()); + let new_chd_rlp = new_chd.get_eth_rlp(merkle.store.as_ref()); + assert_eq!(chd_rlp, new_chd_rlp); + + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + chd_eth_rlp[0] = Some(chd_rlp.to_vec()); + let node = Node::new(NodeType::Branch(BranchNode { + chd: [None; NBRANCH], + value: Some(Data("value1".as_bytes().to_vec())), + chd_eth_rlp, + })); + + let node_ref = merkle.new_node(node.clone()).unwrap(); + + let r = node_ref.get_eth_rlp(merkle.store.as_ref()); + let new_node = Node::new(NodeType::decode(r).unwrap()); + let new_rlp = new_node.get_eth_rlp(merkle.store.as_ref()); + assert_eq!(r, new_rlp); + } + + { + let chd = Node::new(NodeType::Branch(BranchNode { + chd: [None; NBRANCH], + value: Some(Data("value1".as_bytes().to_vec())), + chd_eth_rlp: Default::default(), + })); + let chd_ref = merkle.new_node(chd.clone()).unwrap(); + let chd_rlp = chd_ref.get_eth_rlp(merkle.store.as_ref()); + let new_chd = Node::new(NodeType::decode(chd_rlp).unwrap()); + let new_chd_rlp = new_chd.get_eth_rlp(merkle.store.as_ref()); + assert_eq!(chd_rlp, new_chd_rlp); + + let node = Node::new(NodeType::Extension(ExtNode( + PartialPath(vec![0x1, 0x2, 0x3]), + DiskAddress::null(), + Some(chd_rlp.to_vec()), + ))); + let node_ref = merkle.new_node(node.clone()).unwrap(); + + let r = node_ref.get_eth_rlp(merkle.store.as_ref()); + let new_node = Node::new(NodeType::decode(r).unwrap()); + let new_rlp = new_node.get_eth_rlp(merkle.store.as_ref()); + assert_eq!(r, new_rlp); + } + } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index cb1a6c5162f1..380c1116c1b9 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,7 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use bincode::{Error, Options}; use enum_as_inner::EnumAsInner; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; use shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; use std::{ @@ -14,11 +16,15 @@ use std::{ }; use crate::merkle::to_nibble_array; +use crate::nibbles::Nibbles; use super::{from_nibbles, PartialPath, TrieHash, TRIE_HASH_LEN}; pub const NBRANCH: usize = 16; +const EXT_NODE_SIZE: usize = 2; +const BRANCH_NODE_SIZE: usize = 17; + #[derive(Debug, PartialEq, Eq, Clone)] pub struct Data(pub(super) Vec); @@ -29,6 +35,28 @@ impl std::ops::Deref for Data { } } +#[derive(Serialize, Deserialize, Debug)] +pub(crate) enum Encoded { + Raw(T), + Data(T), +} + +impl Default for Encoded> { + fn default() -> Self { + // This is the default serialized empty vector + Encoded::Data(vec![0]) + } +} + +impl> Encoded { + pub fn decode(self) -> Result { + match self { + Encoded::Raw(raw) => Ok(raw), + Encoded::Data(data) => bincode::DefaultOptions::new().deserialize(data.as_ref()), + } + } +} + #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { pub(super) chd: [Option; NBRANCH], @@ -77,14 +105,41 @@ impl BranchNode { (only_chd, has_chd) } + pub fn decode(buf: &[u8]) -> Result { + let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + + // we've already validated the size, that's why we can safely unwrap + let data = items.pop().unwrap().decode()?; + // Extract the value of the branch node and set to None if it's an empty Vec + let value = Some(data).filter(|data| !data.is_empty()); + + // Record rlp values of all children. + let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + + // we popped the last element, so their should only be NBRANCH items left + for (i, chd) in items.into_iter().enumerate() { + let data = chd.decode()?; + chd_eth_rlp[i] = Some(data).filter(|data| !data.is_empty()); + } + + Ok(BranchNode::new([None; NBRANCH], value, chd_eth_rlp)) + } + fn calc_eth_rlp>(&self, store: &S) -> Vec { - let mut stream = rlp::RlpStream::new_list(17); + let mut list = <[Encoded>; NBRANCH + 1]>::default(); + for (i, c) in self.chd.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); + if c_ref.get_eth_rlp_long::(store) { - stream.append(&&(*c_ref.get_root_hash::(store))[..]); + list[i] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*c_ref.get_root_hash::(store))[..]) + .unwrap(), + ); + // See struct docs for ordering requirements if c_ref.lazy_dirty.load(Ordering::Relaxed) { c_ref.write(|_| {}).unwrap(); @@ -92,30 +147,31 @@ impl BranchNode { } } else { let c_rlp = &c_ref.get_eth_rlp::(store); - stream.append_raw(c_rlp, 1); + list[i] = Encoded::Raw(c_rlp.to_vec()); } } None => { // Check if there is already a calculated rlp for the child, which // can happen when manually constructing a trie from proof. - if self.chd_eth_rlp[i].is_none() { - stream.append_empty_data(); - } else { - let v = self.chd_eth_rlp[i].clone().unwrap(); + if let Some(v) = &self.chd_eth_rlp[i] { if v.len() == TRIE_HASH_LEN { - stream.append(&v); + list[i] = + Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); } else { - stream.append_raw(&v, 1); + list[i] = Encoded::Raw(v.clone()); } } } }; } - match &self.value { - Some(val) => stream.append(&val.to_vec()), - None => stream.append_empty_data(), - }; - stream.out().into() + + if let Some(Data(val)) = &self.value { + list[NBRANCH] = Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); + } + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() } pub fn new( @@ -162,11 +218,15 @@ impl Debug for LeafNode { impl LeafNode { fn calc_eth_rlp(&self) -> Vec { - rlp::encode_list::, _>(&[ - from_nibbles(&self.0.encode(true)).collect(), - self.1.to_vec(), - ]) - .into() + bincode::DefaultOptions::new() + .serialize( + [ + Encoded::Raw(from_nibbles(&self.0.encode(true)).collect()), + Encoded::Raw(self.1.to_vec()), + ] + .as_slice(), + ) + .unwrap() } pub fn new(path: Vec, data: Vec) -> Self { @@ -197,34 +257,45 @@ impl Debug for ExtNode { impl ExtNode { fn calc_eth_rlp>(&self, store: &S) -> Vec { - let mut stream = rlp::RlpStream::new_list(2); + let mut list = <[Encoded>; 2]>::default(); + list[0] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&from_nibbles(&self.0.encode(false)).collect::>()) + .unwrap(), + ); + if !self.1.is_null() { let mut r = store.get_item(self.1).unwrap(); - stream.append(&from_nibbles(&self.0.encode(false)).collect::>()); + if r.get_eth_rlp_long(store) { - stream.append(&&(*r.get_root_hash(store))[..]); + list[1] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*r.get_root_hash(store))[..]) + .unwrap(), + ); + if r.lazy_dirty.load(Ordering::Relaxed) { r.write(|_| {}).unwrap(); r.lazy_dirty.store(false, Ordering::Relaxed); } } else { - stream.append_raw(r.get_eth_rlp(store), 1); + list[1] = Encoded::Raw(r.get_eth_rlp(store).to_vec()); } } else { // Check if there is already a caclucated rlp for the child, which // can happen when manually constructing a trie from proof. - if self.2.is_none() { - stream.append_empty_data(); - } else { - let v = self.2.clone().unwrap(); + if let Some(v) = &self.2 { if v.len() == TRIE_HASH_LEN { - stream.append(&v); + list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); } else { - stream.append_raw(&v, 1); + list[1] = Encoded::Raw(v.clone()); } } } - stream.out().into() + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() } pub fn new(path: Vec, chd: DiskAddress, chd_eth_rlp: Option>) -> Self { @@ -298,13 +369,45 @@ pub enum NodeType { } impl NodeType { - fn calc_eth_rlp>(&self, store: &S) -> Vec { + pub fn calc_eth_rlp>(&self, store: &S) -> Vec { match &self { NodeType::Leaf(n) => n.calc_eth_rlp(), NodeType::Extension(n) => n.calc_eth_rlp(store), NodeType::Branch(n) => n.calc_eth_rlp(store), } } + + pub fn decode(buf: &[u8]) -> Result { + let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + + match items.len() { + EXT_NODE_SIZE => { + let mut items = items.into_iter(); + let decoded_key: Vec = items.next().unwrap().decode()?; + + let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); + + let (cur_key_path, term) = + PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); + let cur_key = cur_key_path.into_inner(); + let data: Vec = items.next().unwrap().decode()?; + + if term { + Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) + } else { + Ok(NodeType::Extension(ExtNode::new( + cur_key, + DiskAddress::null(), + Some(data), + ))) + } + } + BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?)), + size => Err(Box::new(bincode::ErrorKind::Custom(format!( + "invalid size: {size}" + )))), + } + } } impl Node { diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index d3b356fdba5d..d918fb5172b5 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -166,7 +166,7 @@ mod test { fn leading_zero_nibbles_iter() { let nib = Nibbles::<1>(&TEST_BYTES); let expected: [u8; 9] = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; - expected.into_iter().eq(nib.into_iter()); + expected.into_iter().eq(nib); } #[test] diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8d8c485ae200..b66a23366b0f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use std::ops::Deref; +use bincode::Options; use nix::errno::Errno; use sha3::Digest; use shale::disk_address::DiskAddress; @@ -11,6 +12,7 @@ use shale::ShaleError; use shale::ShaleStore; use thiserror::Error; +use crate::merkle::Encoded; use crate::nibbles::Nibbles; use crate::nibbles::NibblesIterator; use crate::{ @@ -26,7 +28,8 @@ use crate::{ #[derive(Debug, Error)] pub enum ProofError { #[error("decoding error")] - DecodeError(#[from] rlp::DecoderError), + // DecodeError(#[from] rlp::DecoderError), + DecodeError(#[from] bincode::Error), #[error("no such node")] NoSuchNode, #[error("proof node missing")] @@ -146,24 +149,36 @@ impl + Send> Proof { mut key_nibbles: NibblesIterator<'a, 0>, rlp_encoded_node: &[u8], ) -> Result<(Option, NibblesIterator<'a, 0>), ProofError> { - let rlp = rlp::Rlp::new(rlp_encoded_node); + // let rlp = rlp::Rlp::new(rlp_encoded_node); + let items: Vec>> = bincode::DefaultOptions::new() + .deserialize(rlp_encoded_node) + .map_err(ProofError::DecodeError)?; + + // match rlp.item_count() { + match items.len() { + // Ok(EXT_NODE_SIZE) => { + // [Encoded>; 2] + // 0 is always Encoded::Data + // 1 could be either + EXT_NODE_SIZE => { + // let decoded_key = rlp.at(0).unwrap().as_val::>().unwrap(); + let mut items = items.into_iter(); + let decoded_key: Vec = items.next().unwrap().decode()?; - match rlp.item_count() { - Ok(EXT_NODE_SIZE) => { - let decoded_key = rlp.at(0).unwrap().as_val::>().unwrap(); let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); let (cur_key_path, term) = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); - let rlp = rlp.at(1).unwrap(); + // let rlp = rlp.at(1).unwrap(); + // let data = if rlp.is_data() { + // rlp.as_val::>().unwrap() + // } else { + // rlp.as_raw().to_vec() + // }; - let data = if rlp.is_data() { - rlp.as_val::>().unwrap() - } else { - rlp.as_raw().to_vec() - }; + let data: Vec = items.next().unwrap().decode()?; // Check if the key of current node match with the given key // and consume the current-key portion of the nibbles-iterator @@ -186,24 +201,21 @@ impl + Send> Proof { Ok((sub_proof.into(), key_nibbles)) } - Ok(BRANCH_NODE_SIZE) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), + BRANCH_NODE_SIZE if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), - Ok(BRANCH_NODE_SIZE) => { + BRANCH_NODE_SIZE => { let index = key_nibbles.next().unwrap() as usize; - let rlp = rlp.at(index).unwrap(); - let data = if rlp.is_data() { - rlp.as_val::>().unwrap() - } else { - rlp.as_raw().to_vec() - }; + // consume items returning the item at index + let data: Vec = items.into_iter().nth(index).unwrap().decode()?; self.generate_subproof(data) .map(|subproof| (Some(subproof), key_nibbles)) } - Ok(_) => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), - Err(e) => Err(ProofError::DecodeError(e)), + size => Err(ProofError::DecodeError(Box::new( + bincode::ErrorKind::Custom(format!("invalid size: {size}")), + ))), } } @@ -216,6 +228,7 @@ impl + Send> Proof { hash: Some(sub_hash), }) } + 32 => { let sub_hash: &[u8] = &data; let sub_hash = sub_hash.try_into().unwrap(); @@ -225,7 +238,10 @@ impl + Send> Proof { hash: Some(sub_hash), }) } - _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), + + len => Err(ProofError::DecodeError(Box::new( + bincode::ErrorKind::Custom(format!("invalid proof length: {len}")), + ))), } } @@ -541,14 +557,26 @@ impl + Send> Proof { buf: &[u8], end_node: bool, ) -> Result<(DiskAddress, Option, usize), ProofError> { - let rlp = rlp::Rlp::new(buf); - let size = rlp.item_count()?; + // let rlp = rlp::Rlp::new(buf); + // let size = rlp.item_count()?; + + let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + let size = items.len(); match size { EXT_NODE_SIZE => { - let cur_key_path: Vec<_> = rlp - .at(0)? - .as_val::>()? + // let cur_key_path: Vec<_> = rlp + // .at(0)? + // .as_val::>()? + // .into_iter() + // .flat_map(to_nibble_array) + // .collect(); + let mut items = items.into_iter(); + + let cur_key_path: Vec = items + .next() + .unwrap() + .decode()? .into_iter() .flat_map(to_nibble_array) .collect(); @@ -556,13 +584,15 @@ impl + Send> Proof { let (cur_key_path, term) = PartialPath::decode(&cur_key_path); let cur_key = cur_key_path.into_inner(); - let rlp = rlp.at(1)?; + // let rlp = rlp.at(1)?; - let data = if rlp.is_data() { - rlp.as_val::>()? - } else { - rlp.as_raw().to_vec() - }; + // let data = if rlp.is_data() { + // rlp.as_val::>()? + // } else { + // rlp.as_raw().to_vec() + // }; + + let data: Vec = items.next().unwrap().decode()?; // Check if the key of current node match with the given key. if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { @@ -588,36 +618,47 @@ impl + Send> Proof { } BRANCH_NODE_SIZE => { - let data_rlp = rlp.at(NBRANCH)?; - - // Extract the value of the branch node. - // Skip if rlp is empty data - let value = if !data_rlp.is_empty() { - let data = if data_rlp.is_data() { - data_rlp.as_val::>().unwrap() - } else { - data_rlp.as_raw().to_vec() - }; - - Some(data) - } else { - None - }; + // let data_rlp = rlp.at(NBRANCH)?; + + // // Extract the value of the branch node. + // // Skip if rlp is empty data + // let value = if !data_rlp.is_empty() { + // let data = if data_rlp.is_data() { + // data_rlp.as_val::>().unwrap() + // } else { + // data_rlp.as_raw().to_vec() + // }; + + // Some(data) + // } else { + // None + // }; + + // we've already validated the size, that's why we can safely unwrap + let data = items.pop().unwrap().decode()?; + // Extract the value of the branch node and set to None if it's an empty Vec + let value = Some(data).filter(|data| !data.is_empty()); // Record rlp values of all children. let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - for (i, chd) in rlp.into_iter().take(NBRANCH).enumerate() { - if !chd.is_empty() { - // Skip if chd is empty data - let data = if chd.is_data() { - chd.as_val()? - } else { - chd.as_raw().to_vec() - }; - - chd_eth_rlp[i] = Some(data); - } + // for (i, chd) in rlp.into_iter().take(NBRANCH).enumerate() { + // if !chd.is_empty() { + // // Skip if chd is empty data + // let data = if chd.is_data() { + // chd.as_val()? + // } else { + // chd.as_raw().to_vec() + // }; + + // chd_eth_rlp[i] = Some(data); + // } + // } + + // we popped the last element, so their should only be NBRANCH items left + for (i, chd) in items.into_iter().enumerate() { + let data = chd.decode()?; + chd_eth_rlp[i] = Some(data).filter(|data| !data.is_empty()); } // If the node is the last one to be decoded, then no subproof to be extracted. @@ -647,7 +688,10 @@ impl + Send> Proof { } // RLP length can only be the two cases above. - _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), + // _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), + _ => Err(ProofError::DecodeError(Box::new( + bincode::ErrorKind::Custom(String::from("")), + ))), } } } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 6fbbd5b3623c..0686e8932690 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -19,24 +19,11 @@ fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Cl compact_size: u64, ) -> Result, DataStoreError> { let mut merkle = new_merkle(meta_size, compact_size); + for (k, v) in items.iter() { merkle.insert(k, v.as_ref().to_vec())?; } - let merkle_root = merkle.root_hash().unwrap(); - let items_copy = items.clone(); - let reference_root = triehash::trie_root::(items); - println!( - "ours: {}, correct: {}", - hex::encode(merkle_root.0), - hex::encode(reference_root) - ); - if merkle_root.0 != reference_root { - for (k, v) in items_copy { - println!("{} => {}", hex::encode(k), hex::encode(v)); - } - println!("{:?}", merkle.dump()?); - panic!(); - } + Ok(merkle) } @@ -233,7 +220,7 @@ fn test_one_element_proof() -> Result<(), DataStoreError> { #[test] fn test_proof() -> Result<(), DataStoreError> { - let set = generate_random_data(500); + let set = generate_random_data(1); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; From 357be7b1cbe026ff71e4c8d63390c60cd395976f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 25 Sep 2023 14:56:29 -0700 Subject: [PATCH 0297/1053] Remove proof feature (#273) Co-authored-by: Richard Pringle --- firewood/src/api.rs | 14 ++++++-------- firewood/src/sender.rs | 2 -- firewood/src/service/client.rs | 35 ++++++++++++++++------------------ firewood/src/service/mod.rs | 19 ++++++++++-------- firewood/src/service/server.rs | 18 ++++++++--------- 5 files changed, 42 insertions(+), 46 deletions(-) diff --git a/firewood/src/api.rs b/firewood/src/api.rs index 353a65952be8..0dad80b05c52 100644 --- a/firewood/src/api.rs +++ b/firewood/src/api.rs @@ -4,21 +4,21 @@ use std::io::Write; use crate::db::DbError; +use crate::merkle::MerkleError; use crate::merkle::TrieHash; -#[cfg(feature = "proof")] -use crate::{merkle::MerkleError, proof::Proof}; +use crate::v2::api::Proof; use async_trait::async_trait; pub type Nonce = u64; #[async_trait] -pub trait Db { +pub trait Db, N: Send> { async fn get_revision(&self, root_hash: TrieHash) -> Option; } #[async_trait] -pub trait Revision +pub trait Revision where Self: Sized, { @@ -29,12 +29,10 @@ where async fn root_hash(&self) -> Result; async fn dump(&self, writer: W) -> Result<(), DbError>; - #[cfg(feature = "proof")] - async fn prove + Send + Sync>(&self, key: K) -> Result; - #[cfg(feature = "proof")] + async fn prove + Send + Sync>(&self, key: K) -> Result, MerkleError>; async fn verify_range_proof + Send + Sync>( &self, - proof: Proof, + proof: Proof, first_key: K, last_key: K, keys: Vec, diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs index 7f7efac21d5f..6dc2ea025ac6 100644 --- a/firewood/src/sender.rs +++ b/firewood/src/sender.rs @@ -26,12 +26,10 @@ impl, V: AsRef<[u8]>> DB for Sender { todo!() } - #[cfg(feature = "proof")] fn prove(&self, key: K) -> Result { todo!() } - #[cfg(feature = "proof")] fn verify_range_proof( &self, proof: crate::proof::Proof, diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs index c3f44dd9cca1..b1d7b38ac015 100644 --- a/firewood/src/service/client.rs +++ b/firewood/src/service/client.rs @@ -13,6 +13,8 @@ use tokio::sync::{mpsc, oneshot}; use crate::api::Revision; +use crate::merkle::MerkleError; +use crate::v2::api::Proof; use crate::{ db::{DbConfig, DbError}, merkle::TrieHash, @@ -26,12 +28,12 @@ use super::{Request, RevRequest, RevisionHandle}; /// The type specified is how you want to refer to your key values; this is /// something like `Vec` or `&[u8]` #[derive(Debug)] -pub struct Connection { - sender: Option>, +pub struct Connection { + sender: Option>>, handle: Option>, } -impl Drop for Connection { +impl Drop for Connection { fn drop(&mut self) { drop(take(&mut self.sender)); take(&mut self.handle) @@ -41,13 +43,13 @@ impl Drop for Connection { } } -impl Connection { +impl Connection { #[allow(dead_code)] fn new>(path: P, cfg: DbConfig) -> Self { let (sender, receiver) = mpsc::channel(1_000) as ( - tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Receiver, + tokio::sync::mpsc::Sender>, + tokio::sync::mpsc::Receiver>, ); let owned_path = path.as_ref().to_path_buf(); let handle = thread::Builder::new() @@ -61,7 +63,7 @@ impl Connection { } } -impl super::RevisionHandle { +impl super::RevisionHandle { pub async fn close(self) { let _ = self .sender @@ -71,7 +73,7 @@ impl super::RevisionHandle { } #[async_trait] -impl Revision for super::RevisionHandle { +impl Revision for super::RevisionHandle { async fn kv_root_hash(&self) -> Result { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::RootHash { @@ -84,7 +86,7 @@ impl Revision for super::RevisionHandle { async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError> { let (send, recv) = oneshot::channel(); - let _ = Request::RevRequest(RevRequest::Get { + let _ = Request::RevRequest::(RevRequest::Get { handle: self.id, key: key.as_ref().to_vec(), respond_to: send, @@ -92,11 +94,7 @@ impl Revision for super::RevisionHandle { recv.await.expect("Actor task has been killed") } - #[cfg(feature = "proof")] - async fn prove + Send + Sync>( - &self, - key: K, - ) -> Result { + async fn prove + Send + Sync>(&self, key: K) -> Result, MerkleError> { let (send, recv) = oneshot::channel(); let msg = Request::RevRequest(RevRequest::Prove { handle: self.id, @@ -107,10 +105,9 @@ impl Revision for super::RevisionHandle { recv.await.expect("channel failed") } - #[cfg(feature = "proof")] async fn verify_range_proof + Send + Sync>( &self, - _proof: crate::proof::Proof, + _proof: Proof, _first_key: K, _last_key: K, _keys: Vec, @@ -177,11 +174,11 @@ impl Revision for super::RevisionHandle { } #[async_trait] -impl crate::api::Db for Connection +impl crate::api::Db, N> for Connection where - tokio::sync::mpsc::Sender: From>, + tokio::sync::mpsc::Sender>: From>>, { - async fn get_revision(&self, root_hash: TrieHash) -> Option { + async fn get_revision(&self, root_hash: TrieHash) -> Option> { let (send, recv) = oneshot::channel(); let msg = Request::NewRevision { root_hash, diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs index 47ac8ff347e9..77587d744384 100644 --- a/firewood/src/service/mod.rs +++ b/firewood/src/service/mod.rs @@ -3,7 +3,11 @@ use tokio::sync::{mpsc, oneshot}; -use crate::{db::DbError, merkle::TrieHash}; +use crate::{ + db::DbError, + merkle::{MerkleError, TrieHash}, + v2::api::Proof, +}; mod client; mod server; @@ -12,20 +16,20 @@ pub type BatchId = u32; pub type RevId = u32; #[derive(Debug)] -pub struct RevisionHandle { - sender: mpsc::Sender, +pub struct RevisionHandle { + sender: mpsc::Sender>, id: u32, } /// Client side request object #[derive(Debug)] -pub enum Request { +pub enum Request { NewRevision { root_hash: TrieHash, respond_to: oneshot::Sender>, }, - RevRequest(RevRequest), + RevRequest(RevRequest), } type OwnedKey = Vec; @@ -33,17 +37,16 @@ type OwnedKey = Vec; type OwnedVal = Vec; #[derive(Debug)] -pub enum RevRequest { +pub enum RevRequest { Get { handle: RevId, key: OwnedKey, respond_to: oneshot::Sender, DbError>>, }, - #[cfg(feature = "proof")] Prove { handle: RevId, key: OwnedKey, - respond_to: oneshot::Sender>, + respond_to: oneshot::Sender, MerkleError>>, }, RootHash { handle: RevId, diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs index 779efce3ee43..043f0d76bfe4 100644 --- a/firewood/src/service/server.rs +++ b/firewood/src/service/server.rs @@ -27,7 +27,11 @@ macro_rules! get_rev { pub struct FirewoodService {} impl FirewoodService { - pub fn new(mut receiver: Receiver, owned_path: PathBuf, cfg: DbConfig) -> Self { + pub fn new( + mut receiver: Receiver>, + owned_path: PathBuf, + cfg: DbConfig, + ) -> Self { let db = Db::new(owned_path, &cfg).unwrap(); let mut revs = HashMap::new(); let lastid = AtomicU32::new(0); @@ -62,16 +66,12 @@ impl FirewoodService { let msg = rev.kv_get(key); let _ = respond_to.send(msg.map_or(Err(DbError::KeyNotFound), Ok)); } - #[cfg(feature = "proof")] super::RevRequest::Prove { - handle, - key, - respond_to, + handle: _handle, + key: _key, + respond_to: _respond_to, } => { - let msg = revs - .get(&handle) - .map_or(Err(MerkleError::UnsetInternal), |r| r.prove(key)); - let _ = respond_to.send(msg); + todo!() } super::RevRequest::RootHash { handle, respond_to } => { let rev = get_rev!(revs, handle, respond_to); From 78bb11d1039a6fe0c763520912ddc3c29e5f36f5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 25 Sep 2023 16:04:56 -0700 Subject: [PATCH 0298/1053] Remove obsolete diagram (#274) --- docs/assets/three-layers.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/assets/three-layers.svg diff --git a/docs/assets/three-layers.svg b/docs/assets/three-layers.svg deleted file mode 100644 index 8681bef2aa4a..000000000000 --- a/docs/assets/three-layers.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 0c24cd30d86c6ddc37346c9f7acf4e2cf3e1309d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Sep 2023 09:40:15 -0700 Subject: [PATCH 0299/1053] README updates (#275) --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c890ef3a5b62..cf66b6b92a82 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ versions with no additional cost at all. firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). +## Architecture Diagram + +![architecture diagram](./docs/assets/architecture.svg) + ## Termimology * `Revision` - A historical point-in-time state/version of the trie. This @@ -74,6 +78,12 @@ firewood is licensed by the Ecosystem License. For more information, see the ## Roadmap + +**LEGEND** +- [ ] Not started +- [ ] :runner: In progress +- [x] Complete + ### Green Milestone This milestone will focus on additional code cleanup, including supporting concurrent access to a specific revision, as well as cleaning up the basic @@ -99,11 +109,14 @@ propose a batch against any existing proposed revision. - [x] Commit a batch that has been proposed will invalidate all other proposals that are not children of the committed proposed batch. - [x] Be able to quickly commit a batch that has been proposed. +- [x] Remove RLP encoding ### Dried milestone The focus of this milestone will be to support synchronization to other instances to replicate the state. A synchronization library should also be developed for this milestone. +- [ ] :runner: Add support for Ava Labs generic test tool via grpc client +- [ ] :runner: Pluggable encoding for nodes, for optional compatibilty with merkledb - [ ] Support replicating the full state with corresponding range proofs that verify the correctness of the data. - [ ] Support replicating the delta state from the last sync point with @@ -113,15 +126,15 @@ corresponding range proofs that verify the correctness of the data. - [ ] MerkleDB root hash in parity for seamless transition between MerkleDB and firewood. - [ ] Add metric reporting -- [ ] Refactor `Shale` to be more idiomatic +- [ ] Migrate to a fully async interface, consider tokio\_uring, monoio, etc +- [ ] Refactor `Shale` to be more idiomatic, consider rearchitecting it ## Build Firewood currently is Linux-only, as it has a dependency on the asynchronous I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not able to successfully emulate the syscalls `libaio` relies on, so Linux or a -Linux VM must be used to run firewood. It is encouraged to enhance the project -with I/O supports for other OSes, such as OSX (where `kqueue` needs to be used -for async I/O) and Windows. Please contact us if you're interested in such contribution. +Linux VM must be used to run firewood. We intend to migrate to io\_uring which +should allow for this emulation. ## Run There are several examples, in the examples directory, that simulate real world From 481dac3b95179662d07ed1156238c60e2ef48416 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 26 Sep 2023 13:04:23 -0400 Subject: [PATCH 0300/1053] Rebase of #271 (#276) Co-authored-by: Ron Kuris Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/Cargo.toml | 2 - firewood/src/merkle.rs | 62 +++++------ firewood/src/merkle/node.rs | 198 ++++++++++++++++++------------------ firewood/src/proof.rs | 131 +++++++----------------- 4 files changed, 164 insertions(+), 229 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 70c67c5629b1..a4cf1f58d9f0 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -29,8 +29,6 @@ lru = "0.11.0" metered = "0.9.0" nix = {version = "0.27.1", features = ["fs", "uio"]} parking_lot = "0.12.1" -primitive-types = { version = "0.12.0", features = ["impl-rlp"] } -rlp = "0.5.2" serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index cee523654cf9..4c7c73df7244 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -77,7 +77,7 @@ impl + Send + Sync> Merkle { Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], value: None, - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), })), Node::max_branch_node_size(), ) @@ -221,7 +221,7 @@ impl + Send + Sync> Merkle { let t = NodeType::Branch(BranchNode { chd, value: None, - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), }); let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); if idx > 0 { @@ -325,7 +325,7 @@ impl + Send + Sync> Merkle { .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: v, - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), })))? .as_ptr(); if !prefix.is_empty() { @@ -522,7 +522,7 @@ impl + Send + Sync> Merkle { .new_node(Node::new(NodeType::Branch(BranchNode { chd, value: Some(Data(val.take().unwrap())), - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), })))? .as_ptr(); self.set_parent(branch, &mut parents); @@ -1103,9 +1103,9 @@ impl + Send + Sync> Merkle { // Get the hashes of the nodes. for node in nodes { let node = self.get_node(node)?; - let rlp = <&[u8]>::clone(&node.get_eth_rlp::(self.store.as_ref())); - let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(rlp).into(); - proofs.insert(hash, rlp.to_vec()); + let encoded = <&[u8]>::clone(&node.get_encoded::(self.store.as_ref())); + let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(encoded).into(); + proofs.insert(hash, encoded.to_vec()); } Ok(Proof(proofs)) } @@ -1330,9 +1330,9 @@ mod test { for node in chd1.iter_mut().take(NBRANCH / 2) { *node = Some(DiskAddress::from(0xa)); } - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - for rlp in chd_eth_rlp.iter_mut().take(NBRANCH / 2) { - *rlp = Some(vec![0x1, 0x2, 0x3]); + let mut chd_encoded: [Option>; NBRANCH] = Default::default(); + for encoded in chd_encoded.iter_mut().take(NBRANCH / 2) { + *encoded = Some(vec![0x1, 0x2, 0x3]); } for node in [ Node::new_from_hash( @@ -1367,7 +1367,7 @@ mod test { NodeType::Branch(BranchNode { chd: chd0, value: Some(Data("hello, world!".as_bytes().to_vec())), - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), }), ), Node::new_from_hash( @@ -1376,7 +1376,7 @@ mod test { NodeType::Branch(BranchNode { chd: chd1, value: None, - chd_eth_rlp, + chd_encoded, }), ), ] { @@ -1420,50 +1420,50 @@ mod test { Data(vec![0x4, 0x5]), ))); let chd_ref = merkle.new_node(chd.clone()).unwrap(); - let chd_rlp = chd_ref.get_eth_rlp(merkle.store.as_ref()); - let new_chd = Node::new(NodeType::decode(chd_rlp).unwrap()); - let new_chd_rlp = new_chd.get_eth_rlp(merkle.store.as_ref()); - assert_eq!(chd_rlp, new_chd_rlp); + let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); + let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); + let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); + assert_eq!(chd_encoded, new_chd_encoded); - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - chd_eth_rlp[0] = Some(chd_rlp.to_vec()); + let mut chd_encoded: [Option>; NBRANCH] = Default::default(); + chd_encoded[0] = Some(new_chd_encoded.to_vec()); let node = Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], value: Some(Data("value1".as_bytes().to_vec())), - chd_eth_rlp, + chd_encoded, })); let node_ref = merkle.new_node(node.clone()).unwrap(); - let r = node_ref.get_eth_rlp(merkle.store.as_ref()); + let r = node_ref.get_encoded(merkle.store.as_ref()); let new_node = Node::new(NodeType::decode(r).unwrap()); - let new_rlp = new_node.get_eth_rlp(merkle.store.as_ref()); - assert_eq!(r, new_rlp); + let new_encoded = new_node.get_encoded(merkle.store.as_ref()); + assert_eq!(r, new_encoded); } { let chd = Node::new(NodeType::Branch(BranchNode { chd: [None; NBRANCH], value: Some(Data("value1".as_bytes().to_vec())), - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), })); let chd_ref = merkle.new_node(chd.clone()).unwrap(); - let chd_rlp = chd_ref.get_eth_rlp(merkle.store.as_ref()); - let new_chd = Node::new(NodeType::decode(chd_rlp).unwrap()); - let new_chd_rlp = new_chd.get_eth_rlp(merkle.store.as_ref()); - assert_eq!(chd_rlp, new_chd_rlp); + let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); + let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); + let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); + assert_eq!(chd_encoded, new_chd_encoded); let node = Node::new(NodeType::Extension(ExtNode( PartialPath(vec![0x1, 0x2, 0x3]), DiskAddress::null(), - Some(chd_rlp.to_vec()), + Some(chd_encoded.to_vec()), ))); let node_ref = merkle.new_node(node.clone()).unwrap(); - let r = node_ref.get_eth_rlp(merkle.store.as_ref()); + let r = node_ref.get_encoded(merkle.store.as_ref()); let new_node = Node::new(NodeType::decode(r).unwrap()); - let new_rlp = new_node.get_eth_rlp(merkle.store.as_ref()); - assert_eq!(r, new_rlp); + let new_encoded = new_node.get_encoded(merkle.store.as_ref()); + assert_eq!(r, new_encoded); } } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 380c1116c1b9..6c91741bec94 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -61,7 +61,7 @@ impl> Encoded { pub struct BranchNode { pub(super) chd: [Option; NBRANCH], pub(super) value: Option, - pub(super) chd_eth_rlp: [Option>; NBRANCH], + pub(super) chd_encoded: [Option>; NBRANCH], } impl Debug for BranchNode { @@ -72,7 +72,7 @@ impl Debug for BranchNode { write!(f, " ({i:x} {c:?})")?; } } - for (i, c) in self.chd_eth_rlp.iter().enumerate() { + for (i, c) in self.chd_encoded.iter().enumerate() { if let Some(c) = c { write!(f, " ({i:x} {:?})", c)?; } @@ -113,19 +113,19 @@ impl BranchNode { // Extract the value of the branch node and set to None if it's an empty Vec let value = Some(data).filter(|data| !data.is_empty()); - // Record rlp values of all children. - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + // encode all children. + let mut chd_encoded: [Option>; NBRANCH] = Default::default(); // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { let data = chd.decode()?; - chd_eth_rlp[i] = Some(data).filter(|data| !data.is_empty()); + chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); } - Ok(BranchNode::new([None; NBRANCH], value, chd_eth_rlp)) + Ok(BranchNode::new([None; NBRANCH], value, chd_encoded)) } - fn calc_eth_rlp>(&self, store: &S) -> Vec { + fn encode>(&self, store: &S) -> Vec { let mut list = <[Encoded>; NBRANCH + 1]>::default(); for (i, c) in self.chd.iter().enumerate() { @@ -133,7 +133,7 @@ impl BranchNode { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); - if c_ref.get_eth_rlp_long::(store) { + if c_ref.is_encoded_big::(store) { list[i] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&&(*c_ref.get_root_hash::(store))[..]) @@ -146,14 +146,14 @@ impl BranchNode { c_ref.lazy_dirty.store(false, Ordering::Relaxed) } } else { - let c_rlp = &c_ref.get_eth_rlp::(store); - list[i] = Encoded::Raw(c_rlp.to_vec()); + let child_encoded = &c_ref.get_encoded::(store); + list[i] = Encoded::Raw(child_encoded.to_vec()); } } None => { - // Check if there is already a calculated rlp for the child, which + // Check if there is already a calculated encoded value for the child, which // can happen when manually constructing a trie from proof. - if let Some(v) = &self.chd_eth_rlp[i] { + if let Some(v) = &self.chd_encoded[i] { if v.len() == TRIE_HASH_LEN { list[i] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); @@ -177,12 +177,12 @@ impl BranchNode { pub fn new( chd: [Option; NBRANCH], value: Option>, - chd_eth_rlp: [Option>; NBRANCH], + chd_encoded: [Option>; NBRANCH], ) -> Self { BranchNode { chd, value: value.map(Data), - chd_eth_rlp, + chd_encoded, } } @@ -198,12 +198,12 @@ impl BranchNode { &mut self.chd } - pub fn chd_eth_rlp(&self) -> &[Option>; NBRANCH] { - &self.chd_eth_rlp + pub fn chd_encode(&self) -> &[Option>; NBRANCH] { + &self.chd_encoded } - pub fn chd_eth_rlp_mut(&mut self) -> &mut [Option>; NBRANCH] { - &mut self.chd_eth_rlp + pub fn chd_encoded_mut(&mut self) -> &mut [Option>; NBRANCH] { + &mut self.chd_encoded } } @@ -217,7 +217,7 @@ impl Debug for LeafNode { } impl LeafNode { - fn calc_eth_rlp(&self) -> Vec { + fn encode(&self) -> Vec { bincode::DefaultOptions::new() .serialize( [ @@ -256,7 +256,7 @@ impl Debug for ExtNode { } impl ExtNode { - fn calc_eth_rlp>(&self, store: &S) -> Vec { + fn encode>(&self, store: &S) -> Vec { let mut list = <[Encoded>; 2]>::default(); list[0] = Encoded::Data( bincode::DefaultOptions::new() @@ -267,7 +267,7 @@ impl ExtNode { if !self.1.is_null() { let mut r = store.get_item(self.1).unwrap(); - if r.get_eth_rlp_long(store) { + if r.is_encoded_big(store) { list[1] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&&(*r.get_root_hash(store))[..]) @@ -279,10 +279,10 @@ impl ExtNode { r.lazy_dirty.store(false, Ordering::Relaxed); } } else { - list[1] = Encoded::Raw(r.get_eth_rlp(store).to_vec()); + list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); } } else { - // Check if there is already a caclucated rlp for the child, which + // Check if there is already a caclucated encoded value for the child, which // can happen when manually constructing a trie from proof. if let Some(v) = &self.2 { if v.len() == TRIE_HASH_LEN { @@ -298,8 +298,8 @@ impl ExtNode { .unwrap() } - pub fn new(path: Vec, chd: DiskAddress, chd_eth_rlp: Option>) -> Self { - ExtNode(PartialPath(path), chd, chd_eth_rlp) + pub fn new(path: Vec, chd: DiskAddress, chd_encoded: Option>) -> Self { + ExtNode(PartialPath(path), chd, chd_encoded) } pub fn path(&self) -> &PartialPath { @@ -314,7 +314,7 @@ impl ExtNode { &mut self.1 } - pub fn chd_eth_rlp_mut(&mut self) -> &mut Option> { + pub fn chd_encoded_mut(&mut self) -> &mut Option> { &mut self.2 } } @@ -322,8 +322,8 @@ impl ExtNode { #[derive(Debug)] pub struct Node { pub(super) root_hash: OnceLock, - eth_rlp_long: OnceLock, - eth_rlp: OnceLock>, + is_encoded_big: OnceLock, + encoded: OnceLock>, // lazy_dirty is an atomicbool, but only writers ever set it // Therefore, we can always use Relaxed ordering. It's atomic // just to ensure Sync + Send. @@ -336,14 +336,14 @@ impl PartialEq for Node { fn eq(&self, other: &Self) -> bool { let Node { root_hash, - eth_rlp_long, - eth_rlp, + is_encoded_big, + encoded, lazy_dirty, inner, } = self; *root_hash == other.root_hash - && *eth_rlp_long == other.eth_rlp_long - && *eth_rlp == other.eth_rlp + && *is_encoded_big == other.is_encoded_big + && *encoded == other.encoded && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) && *inner == other.inner } @@ -352,8 +352,8 @@ impl Clone for Node { fn clone(&self) -> Self { Self { root_hash: self.root_hash.clone(), - eth_rlp_long: self.eth_rlp_long.clone(), - eth_rlp: self.eth_rlp.clone(), + is_encoded_big: self.is_encoded_big.clone(), + encoded: self.encoded.clone(), lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), inner: self.inner.clone(), } @@ -369,11 +369,11 @@ pub enum NodeType { } impl NodeType { - pub fn calc_eth_rlp>(&self, store: &S) -> Vec { + pub fn encode>(&self, store: &S) -> Vec { match &self { - NodeType::Leaf(n) => n.calc_eth_rlp(), - NodeType::Extension(n) => n.calc_eth_rlp(store), - NodeType::Branch(n) => n.calc_eth_rlp(store), + NodeType::Leaf(n) => n.encode(), + NodeType::Extension(n) => n.encode(store), + NodeType::Branch(n) => n.encode(store), } } @@ -420,12 +420,12 @@ impl Node { *max_size.get_or_init(|| { Self { root_hash: OnceLock::new(), - eth_rlp_long: OnceLock::new(), - eth_rlp: OnceLock::new(), + is_encoded_big: OnceLock::new(), + encoded: OnceLock::new(), inner: NodeType::Branch(BranchNode { chd: [Some(DiskAddress::null()); NBRANCH], value: Some(Data(Vec::new())), - chd_eth_rlp: Default::default(), + chd_encoded: Default::default(), }), lazy_dirty: AtomicBool::new(false), } @@ -433,36 +433,35 @@ impl Node { }) } - pub(super) fn get_eth_rlp>(&self, store: &S) -> &[u8] { - self.eth_rlp - .get_or_init(|| self.inner.calc_eth_rlp::(store)) + pub(super) fn get_encoded>(&self, store: &S) -> &[u8] { + self.encoded.get_or_init(|| self.inner.encode::(store)) } pub(super) fn get_root_hash>(&self, store: &S) -> &TrieHash { self.root_hash.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - TrieHash(Keccak256::digest(self.get_eth_rlp::(store)).into()) + TrieHash(Keccak256::digest(self.get_encoded::(store)).into()) }) } - fn get_eth_rlp_long>(&self, store: &S) -> bool { - *self.eth_rlp_long.get_or_init(|| { + fn is_encoded_big>(&self, store: &S) -> bool { + *self.is_encoded_big.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); - self.get_eth_rlp(store).len() >= TRIE_HASH_LEN + self.get_encoded(store).len() >= TRIE_HASH_LEN }) } pub(super) fn rehash(&mut self) { - self.eth_rlp = OnceLock::new(); - self.eth_rlp_long = OnceLock::new(); + self.encoded = OnceLock::new(); + self.is_encoded_big = OnceLock::new(); self.root_hash = OnceLock::new(); } pub fn new(inner: NodeType) -> Self { let mut s = Self { root_hash: OnceLock::new(), - eth_rlp_long: OnceLock::new(), - eth_rlp: OnceLock::new(), + is_encoded_big: OnceLock::new(), + encoded: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), }; @@ -480,7 +479,7 @@ impl Node { pub(super) fn new_from_hash( root_hash: Option, - eth_rlp_long: Option, + encoded_big: Option, inner: NodeType, ) -> Self { Self { @@ -488,19 +487,20 @@ impl Node { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, - eth_rlp_long: match eth_rlp_long { + is_encoded_big: match encoded_big { Some(b) => OnceLock::from(b), None => OnceLock::new(), }, - eth_rlp: OnceLock::new(), + encoded: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), } } const ROOT_HASH_VALID_BIT: u8 = 1 << 0; - const ETH_RLP_LONG_VALID_BIT: u8 = 1 << 1; - const ETH_RLP_LONG_BIT: u8 = 1 << 2; + // TODO: why are these different? + const IS_ENCODED_BIG_VALID: u8 = 1 << 1; + const LONG_BIT: u8 = 1 << 2; } impl Storable for Node { @@ -522,10 +522,10 @@ impl Storable for Node { .expect("invalid slice"), )) }; - let eth_rlp_long = if attrs & Node::ETH_RLP_LONG_VALID_BIT == 0 { + let encoded_big = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { None } else { - Some(attrs & Node::ETH_RLP_LONG_BIT != 0) + Some(attrs & Node::LONG_BIT != 0) }; match meta_raw.as_deref()[33] { Self::BRANCH_NODE => { @@ -561,45 +561,45 @@ impl Storable for Node { .as_deref(), )) }; - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); + let mut chd_encoded: [Option>; NBRANCH] = Default::default(); let offset = if raw_len == u32::MAX as u64 { addr + META_SIZE + branch_header_size as usize } else { addr + META_SIZE + branch_header_size as usize + raw_len as usize }; - let mut cur_rlp_len = 0; - for chd_rlp in chd_eth_rlp.iter_mut() { + let mut cur_encoded_len = 0; + for chd_encoded in chd_encoded.iter_mut() { let mut buff = [0_u8; 1]; - let rlp_len_raw = mem.get_view(offset + cur_rlp_len, 1).ok_or( + let len_raw = mem.get_view(offset + cur_encoded_len, 1).ok_or( ShaleError::InvalidCacheView { - offset: offset + cur_rlp_len, + offset: offset + cur_encoded_len, size: 1, }, )?; - cur = Cursor::new(rlp_len_raw.as_deref()); + cur = Cursor::new(len_raw.as_deref()); cur.read_exact(&mut buff)?; - let rlp_len = buff[0] as u64; - cur_rlp_len += 1; - if rlp_len != 0 { - let rlp_raw = mem.get_view(offset + cur_rlp_len, rlp_len).ok_or( + let len = buff[0] as u64; + cur_encoded_len += 1; + if len != 0 { + let encoded_raw = mem.get_view(offset + cur_encoded_len, len).ok_or( ShaleError::InvalidCacheView { - offset: offset + cur_rlp_len, - size: rlp_len, + offset: offset + cur_encoded_len, + size: len, }, )?; - let rlp: Vec = rlp_raw.as_deref()[0..].to_vec(); - *chd_rlp = Some(rlp); - cur_rlp_len += rlp_len as usize + let encoded: Vec = encoded_raw.as_deref()[0..].to_vec(); + *chd_encoded = Some(encoded); + cur_encoded_len += len as usize } } Ok(Self::new_from_hash( root_hash, - eth_rlp_long, + encoded_big, NodeType::Branch(BranchNode { chd, value, - chd_eth_rlp, + chd_encoded, }), )) } @@ -632,7 +632,7 @@ impl Storable for Node { let (path, _) = PartialPath::decode(&nibbles); let mut buff = [0_u8; 1]; - let rlp_len_raw = mem + let encoded_len_raw = mem .get_view( addr + META_SIZE + ext_header_size as usize + path_len as usize, 1, @@ -641,14 +641,14 @@ impl Storable for Node { offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, size: 1, })?; - cur = Cursor::new(rlp_len_raw.as_deref()); + cur = Cursor::new(encoded_len_raw.as_deref()); cur.read_exact(&mut buff)?; - let rlp_len = buff[0] as u64; - let rlp: Option> = if rlp_len != 0 { - let rlp_raw = mem + let encoded_len = buff[0] as u64; + let encoded: Option> = if encoded_len != 0 { + let emcoded_raw = mem .get_view( addr + META_SIZE + ext_header_size as usize + path_len as usize + 1, - rlp_len, + encoded_len, ) .ok_or(ShaleError::InvalidCacheView { offset: addr @@ -656,18 +656,18 @@ impl Storable for Node { + ext_header_size as usize + path_len as usize + 1, - size: rlp_len, + size: encoded_len, })?; - Some(rlp_raw.as_deref()[0..].to_vec()) + Some(emcoded_raw.as_deref()[0..].to_vec()) } else { None }; Ok(Self::new_from_hash( root_hash, - eth_rlp_long, - NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), rlp)), + encoded_big, + NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), encoded)), )) } Self::LEAF_NODE => { @@ -705,7 +705,7 @@ impl Storable for Node { let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); Ok(Self::new_from_hash( root_hash, - eth_rlp_long, + encoded_big, NodeType::Leaf(LeafNode(path, value)), )) } @@ -718,9 +718,9 @@ impl Storable for Node { + 1 + match &self.inner { NodeType::Branch(n) => { - let mut rlp_len = 0; - for rlp in n.chd_eth_rlp.iter() { - rlp_len += match rlp { + let mut encoded_len = 0; + for emcoded in n.chd_encoded.iter() { + encoded_len += match emcoded { Some(v) => 1 + v.len() as u64, None => 1, } @@ -731,7 +731,7 @@ impl Storable for Node { Some(val) => val.len() as u64, None => 0, } - + rlp_len + + encoded_len } NodeType::Extension(n) => { 1 + 8 @@ -759,8 +759,8 @@ impl Storable for Node { 0 } }; - attrs |= match self.eth_rlp_long.get() { - Some(b) => (if *b { Node::ETH_RLP_LONG_BIT } else { 0 } | Node::ETH_RLP_LONG_VALID_BIT), + attrs |= match self.is_encoded_big.get() { + Some(b) => (if *b { Node::LONG_BIT } else { 0 } | Node::IS_ENCODED_BIG_VALID), None => 0, }; cur.write_all(&[attrs]).unwrap(); @@ -783,10 +783,10 @@ impl Storable for Node { cur.write_all(&u32::MAX.to_le_bytes())?; } } - // Since child eth rlp will only be unset after initialization (only used for range proof), + // Since child encoding will only be unset after initialization (only used for range proof), // it is fine to encode its value adjacent to other fields. Same for extention node. - for rlp in n.chd_eth_rlp.iter() { - match rlp { + for encoded in n.chd_encoded.iter() { + match encoded { Some(v) => { cur.write_all(&[v.len() as u8])?; cur.write_all(v)? @@ -803,9 +803,9 @@ impl Storable for Node { cur.write_all(&n.1.to_le_bytes())?; cur.write_all(&path)?; if n.2.is_some() { - let rlp = n.2.as_ref().unwrap(); - cur.write_all(&[rlp.len() as u8])?; - cur.write_all(rlp)?; + let encoded = n.2.as_ref().unwrap(); + cur.write_all(&[encoded.len() as u8])?; + cur.write_all(encoded)?; } Ok(()) } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index b66a23366b0f..a43aecc67964 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -28,7 +28,6 @@ use crate::{ #[derive(Debug, Error)] pub enum ProofError { #[error("decoding error")] - // DecodeError(#[from] rlp::DecoderError), DecodeError(#[from] bincode::Error), #[error("no such node")] NoSuchNode, @@ -99,12 +98,12 @@ impl From for ProofError { const EXT_NODE_SIZE: usize = 2; const BRANCH_NODE_SIZE: usize = 17; -/// SubProof contains the RLP encoding and the hash value of a node that maps +/// SubProof contains the encoded value and the hash value of a node that maps /// to a single proof step. If reaches an end step during proof verification, -/// the hash value will be none, and the RLP encoding will be the value of the +/// the hash value will be none, and the encoded value will be the value of the /// node. pub struct SubProof { - rlp: Vec, + encoded: Vec, hash: Option<[u8; 32]>, } @@ -134,7 +133,7 @@ impl + Send> Proof { cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(p) if key_nibbles.size_hint().0 == 0 => return Ok(Some(p.rlp)), + Some(p) if key_nibbles.size_hint().0 == 0 => return Ok(Some(p.encoded)), // The trie doesn't contain the key. Some(SubProof { hash: Some(hash), .. @@ -147,21 +146,14 @@ impl + Send> Proof { fn locate_subproof<'a>( &self, mut key_nibbles: NibblesIterator<'a, 0>, - rlp_encoded_node: &[u8], + encoded_node: &[u8], ) -> Result<(Option, NibblesIterator<'a, 0>), ProofError> { - // let rlp = rlp::Rlp::new(rlp_encoded_node); let items: Vec>> = bincode::DefaultOptions::new() - .deserialize(rlp_encoded_node) + .deserialize(encoded_node) .map_err(ProofError::DecodeError)?; - // match rlp.item_count() { match items.len() { - // Ok(EXT_NODE_SIZE) => { - // [Encoded>; 2] - // 0 is always Encoded::Data - // 1 could be either EXT_NODE_SIZE => { - // let decoded_key = rlp.at(0).unwrap().as_val::>().unwrap(); let mut items = items.into_iter(); let decoded_key: Vec = items.next().unwrap().decode()?; @@ -171,13 +163,6 @@ impl + Send> Proof { PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); - // let rlp = rlp.at(1).unwrap(); - // let data = if rlp.is_data() { - // rlp.as_val::>().unwrap() - // } else { - // rlp.as_raw().to_vec() - // }; - let data: Vec = items.next().unwrap().decode()?; // Check if the key of current node match with the given key @@ -191,7 +176,7 @@ impl + Send> Proof { let sub_proof = if term { SubProof { - rlp: data, + encoded: data, hash: None, } } else { @@ -224,7 +209,7 @@ impl + Send> Proof { 0..=31 => { let sub_hash = sha3::Keccak256::digest(&data).into(); Ok(SubProof { - rlp: data, + encoded: data, hash: Some(sub_hash), }) } @@ -234,7 +219,7 @@ impl + Send> Proof { let sub_hash = sub_hash.try_into().unwrap(); Ok(SubProof { - rlp: data, + encoded: data, hash: Some(sub_hash), }) } @@ -339,7 +324,7 @@ impl + Send> Proof { // proof is also allowed. self.proof_to_path(last_key.as_ref(), root_hash, &mut merkle_setup, true)?; - // Remove all internal calcuated RLP values. All the removed parts should + // Remove all internal caculated values. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. let fork_at_root = unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; @@ -544,7 +529,7 @@ impl + Send> Proof { } } - /// Decode the RLP value to generate the corresponding type of node, and locate the subproof. + /// Decode the value to generate the corresponding type of node, and locate the subproof. /// /// # Arguments /// @@ -557,20 +542,11 @@ impl + Send> Proof { buf: &[u8], end_node: bool, ) -> Result<(DiskAddress, Option, usize), ProofError> { - // let rlp = rlp::Rlp::new(buf); - // let size = rlp.item_count()?; - let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; let size = items.len(); match size { EXT_NODE_SIZE => { - // let cur_key_path: Vec<_> = rlp - // .at(0)? - // .as_val::>()? - // .into_iter() - // .flat_map(to_nibble_array) - // .collect(); let mut items = items.into_iter(); let cur_key_path: Vec = items @@ -584,14 +560,6 @@ impl + Send> Proof { let (cur_key_path, term) = PartialPath::decode(&cur_key_path); let cur_key = cur_key_path.into_inner(); - // let rlp = rlp.at(1)?; - - // let data = if rlp.is_data() { - // rlp.as_val::>()? - // } else { - // rlp.as_raw().to_vec() - // }; - let data: Vec = items.next().unwrap().decode()?; // Check if the key of current node match with the given key. @@ -603,7 +571,7 @@ impl + Send> Proof { let subproof = if term { Some(SubProof { - rlp: data.clone(), + encoded: data.clone(), hash: None, }) } else { @@ -618,52 +586,23 @@ impl + Send> Proof { } BRANCH_NODE_SIZE => { - // let data_rlp = rlp.at(NBRANCH)?; - - // // Extract the value of the branch node. - // // Skip if rlp is empty data - // let value = if !data_rlp.is_empty() { - // let data = if data_rlp.is_data() { - // data_rlp.as_val::>().unwrap() - // } else { - // data_rlp.as_raw().to_vec() - // }; - - // Some(data) - // } else { - // None - // }; - // we've already validated the size, that's why we can safely unwrap let data = items.pop().unwrap().decode()?; // Extract the value of the branch node and set to None if it's an empty Vec let value = Some(data).filter(|data| !data.is_empty()); - // Record rlp values of all children. - let mut chd_eth_rlp: [Option>; NBRANCH] = Default::default(); - - // for (i, chd) in rlp.into_iter().take(NBRANCH).enumerate() { - // if !chd.is_empty() { - // // Skip if chd is empty data - // let data = if chd.is_data() { - // chd.as_val()? - // } else { - // chd.as_raw().to_vec() - // }; - - // chd_eth_rlp[i] = Some(data); - // } - // } + // Record encoded values of all children. + let mut chd_encoded: [Option>; NBRANCH] = Default::default(); // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { let data = chd.decode()?; - chd_eth_rlp[i] = Some(data).filter(|data| !data.is_empty()); + chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); } // If the node is the last one to be decoded, then no subproof to be extracted. if end_node { - let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; + let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; return Ok((branch_ptr, None, 1)); } @@ -675,20 +614,18 @@ impl + Send> Proof { // Check if the subproof with the given key exist. let index = key[0] as usize; - let Some(data) = chd_eth_rlp[index].clone() else { - let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; + let Some(data) = chd_encoded[index].clone() else { + let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; return Ok((branch_ptr, None, 1)); }; - let branch_ptr = build_branch_ptr(merkle, value, chd_eth_rlp)?; + let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; let subproof = self.generate_subproof(data)?; Ok((branch_ptr, Some(subproof), 1)) } - // RLP length can only be the two cases above. - // _ => Err(ProofError::DecodeError(rlp::DecoderError::RlpInvalidLength)), _ => Err(ProofError::DecodeError(Box::new( bincode::ErrorKind::Custom(String::from("")), ))), @@ -720,9 +657,9 @@ fn get_ext_ptr + Send + Sync>( fn build_branch_ptr + Send + Sync>( merkle: &Merkle, value: Option>, - chd_eth_rlp: [Option>; NBRANCH], + chd_encoded: [Option>; NBRANCH], ) -> Result { - let node = BranchNode::new([None; NBRANCH], value, chd_eth_rlp); + let node = BranchNode::new([None; NBRANCH], value, chd_encoded); let node = NodeType::Branch(node); let node = Node::new(node); @@ -736,7 +673,7 @@ fn build_branch_ptr + Send + Sync>( // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. // -// It's the key step for range proof. The precalucated RLP value of all internal +// It's the key step for range proof. The precalucated encoded value of all internal // nodes should be removed. But if the proof is valid, // the missing children will be filled, otherwise it will be thrown anyway. // @@ -835,13 +772,13 @@ fn unset_internal, S: ShaleStore + Send + Sync>( let left_node = n.chd()[left_chunks[index] as usize]; let right_node = n.chd()[right_chunks[index] as usize]; - // unset all internal nodes calculated RLP value in the forkpoint + // unset all internal nodes calculated encoded value in the forkpoint for i in left_chunks[index] + 1..right_chunks[index] { u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); uu.chd_mut()[i as usize] = None; - uu.chd_eth_rlp_mut()[i as usize] = None; + uu.chd_encoded_mut()[i as usize] = None; }) .unwrap(); } @@ -884,7 +821,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_mut()[left_chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; }) .unwrap(); @@ -942,11 +879,11 @@ fn unset_internal, S: ShaleStore + Send + Sync>( .write(|p| match p.inner_mut() { NodeType::Extension(n) => { *n.chd_mut() = DiskAddress::null(); - *n.chd_eth_rlp_mut() = None; + *n.chd_encoded_mut() = None; } NodeType::Branch(n) => { n.chd_mut()[left_chunks[index - 1] as usize] = None; - n.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + n.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; } _ => {} }) @@ -956,7 +893,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_mut()[left_chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[left_chunks[index - 1] as usize] = None; + pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; }) .unwrap(); } else if fork_left.is_ne() { @@ -964,7 +901,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_mut()[right_chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[right_chunks[index - 1] as usize] = None; + pp.chd_encoded_mut()[right_chunks[index - 1] as usize] = None; }) .unwrap(); } @@ -1025,7 +962,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); uu.chd_mut()[i] = None; - uu.chd_eth_rlp_mut()[i] = None; + uu.chd_encoded_mut()[i] = None; }) .unwrap(); } @@ -1069,7 +1006,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_eth_rlp_mut()[chunks[index - 1] as usize] = None; + pp.chd_encoded_mut()[chunks[index - 1] as usize] = None; }) .unwrap(); } @@ -1093,7 +1030,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); let index = chunks[index - 1] as usize; pp.chd_mut()[index] = None; - pp.chd_eth_rlp_mut()[index] = None; + pp.chd_encoded_mut()[index] = None; }) .expect("node write failure"); } @@ -1104,13 +1041,13 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( .write(|p| match p.inner_mut() { NodeType::Extension(n) => { *n.chd_mut() = DiskAddress::null(); - *n.chd_eth_rlp_mut() = None; + *n.chd_encoded_mut() = None; } NodeType::Branch(n) => { let index = chunks[index - 1] as usize; n.chd_mut()[index] = None; - n.chd_eth_rlp_mut()[index] = None; + n.chd_encoded_mut()[index] = None; } _ => {} }) From 74a7fde71a860f566a3e422ce587c3f3ba767caf Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:16:19 -0700 Subject: [PATCH 0301/1053] chore: revert back `test_proof` changes accidentally changed (#279) --- firewood/tests/merkle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 0686e8932690..6eaa5ac434cd 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -220,7 +220,7 @@ fn test_one_element_proof() -> Result<(), DataStoreError> { #[test] fn test_proof() -> Result<(), DataStoreError> { - let set = generate_random_data(1); + let set = generate_random_data(500); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; From f1254e434e09c5b0f7c045faf4d8815043601d91 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 26 Sep 2023 12:24:03 -0700 Subject: [PATCH 0302/1053] [README] Final Tweaks (#278) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 61 +++++++++++++++-------------- docs/assets/architecture.svg | 2 +- firewood/src/lib.rs | 74 ++++++++++++++++++------------------ 3 files changed, 67 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index cf66b6b92a82..bc11d1da4b08 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ -# Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. +# Firewood: Compaction-Less Database Optimized for Efficiently Storing Recent Merkleized Blockchain State ![Github Actions](https://github.com/ava-labs/firewood/actions/workflows/ci.yaml/badge.svg?branch=main) [![Ecosystem license](https://img.shields.io/badge/License-Ecosystem-blue.svg)](./LICENSE.md) -> :warning: firewood is alpha-level software and is not ready for production -> use. Do not use firewood to store production data. See the -> [license](./LICENSE.md) for more information regarding firewood usage. - -Firewood is an embedded key-value store, optimized to store blockchain state. -It prioritizes access to latest state, by providing extremely fast reads, but -also provides a limited view into past state. It does not copy-on-write the -state trie to generate an ever growing forest of tries like other databases, -but instead keeps one latest version of the trie index on disk and apply -in-place updates to it. This ensures that the database size is small and stable -during the course of running firewood. Firewood was first conceived to provide +> :warning: Firewood is alpha-level software and is not ready for production +> use. The Firewood API and on-disk state representation may change with +> little to no warning. + +Firewood is an embedded key-value store, optimized to store recent Merkleized blockchain +state with minimal overhead. Firewood is implemented from the ground up to directly +store trie nodes on-disk. Unlike most of state management approaches in the field, +it is not built on top of a generic KV store such as LevelDB/RocksDB. Firewood, like a +B+-tree based database, directly uses the trie structure as the index on-disk. Thus, +there is no additional “emulation” of the logical trie to flatten out the data structure +to feed into the underlying database that is unaware of the data being stored. The convenient +byproduct of this approach is that iteration is still fast (for serving state sync queries) +but compaction is not required to maintain the index. Firewood was first conceived to provide a very fast storage layer for the EVM but could be used on any blockchain that requires authenticated state. -Firewood is a robust database implemented from the ground up to directly store -trie nodes and user data. Unlike most (if not all) of the solutions in the field, -it is not built on top of a generic KV store such as LevelDB/RocksDB. Like a -B+-tree based store, firewood directly uses the tree structure as the index on -disk. Thus, there is no additional “emulation” of the logical trie to flatten -out the data structure to feed into the underlying DB that is unaware of the -data being stored. It provides generic trie storage for arbitrary keys and -values. +Firewood only attempts to store the latest state on-disk and will actively clean up +unused state when state diffs are committed. To avoid reference counting trie nodes, +Firewood does not copy-on-write (COW) the state trie and instead keeps +one latest version of the trie index on disk and applies in-place updates to it. +Firewood keeps some configurable number of previous states in memory to power +state sync (which may occur at a few roots behind the current state). Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees atomicity and durability in the database, but also offers @@ -34,13 +34,9 @@ store back in memory. While running the store, new changes will also contribute to the configured window of changes (at batch granularity) to access any past versions with no additional cost at all. -## License -firewood is licensed by the Ecosystem License. For more information, see the -[LICENSE file](./LICENSE.md). - ## Architecture Diagram -![architecture diagram](./docs/assets/architecture.svg) +![architecture diagram](./docs/assets/architecture.svg) ## Termimology @@ -71,12 +67,11 @@ firewood is licensed by the Ecosystem License. For more information, see the * `Batch Operation` - An operation of either `Put` or `Delete`. * `Batch` - An ordered set of `Batch Operation`s. * `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not - yet committed to the trie. In firewood's most recent API, a `Proposal` is required + yet committed to the trie. In Firewood's most recent API, a `Proposal` is required to `Commit`. * `Commit` - The operation of applying one or more `Proposal`s to the most recent `Revision`. - ## Roadmap **LEGEND** @@ -124,7 +119,7 @@ corresponding range proofs that verify the correctness of the data. - [ ] Enforce limits on the size of the range proof as well as keys to make synchronization easier for clients. - [ ] MerkleDB root hash in parity for seamless transition between MerkleDB -and firewood. +and Firewood. - [ ] Add metric reporting - [ ] Migrate to a fully async interface, consider tokio\_uring, monoio, etc - [ ] Refactor `Shale` to be more idiomatic, consider rearchitecting it @@ -133,7 +128,7 @@ and firewood. Firewood currently is Linux-only, as it has a dependency on the asynchronous I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not able to successfully emulate the syscalls `libaio` relies on, so Linux or a -Linux VM must be used to run firewood. We intend to migrate to io\_uring which +Linux VM must be used to run Firewood. We intend to migrate to io\_uring which should allow for this emulation. ## Run @@ -142,12 +137,16 @@ use-cases. Try running them via the command-line, via `cargo run --release --example simple`. ## Release -See the [release documentation](./RELEASE.md) for detailed information on how to release firewood. +See the [release documentation](./RELEASE.md) for detailed information on how to release Firewood. ## CLI -Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a firewood database. For more information, see the [fwdctl README](fwdctl/README.md). +Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a Firewood database. For more information, see the [fwdctl README](fwdctl/README.md). ## Test ``` cargo test --release ``` + +## License +Firewood is licensed by the Ecosystem License. For more information, see the +[LICENSE file](./LICENSE.md). diff --git a/docs/assets/architecture.svg b/docs/assets/architecture.svg index deeddcfad964..e57a3025d58a 100644 --- a/docs/assets/architecture.svg +++ b/docs/assets/architecture.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index e8045acdec77..6e36e6ca4df0 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -1,28 +1,33 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -//! # Firewood: non-archival blockchain key-value store with hyper-fast recent state retrieval. -//! -//! Firewood is an embedded key-value store, optimized to store blockchain state. It prioritizes -//! access to latest state, by providing extremely fast reads, but also provides a limited view -//! into past state. It does not copy-on-write the state trie to generate an ever -//! growing forest of tries like other databases, but instead keeps one latest version of the trie index on disk -//! and apply in-place updates to it. This ensures that the database size is small and stable -//! during the course of running Firewood. Firewood was first conceived to provide a very fast -//! storage layer for the EVM but could be used on any blockchain that requires authenticated state. -//! -//! Firewood is a robust database implemented from the ground up to directly store trie nodes and -//! user data. Unlike most (if not all) of the solutions in the field, it is not built on top of a -//! generic KV store such as LevelDB/RocksDB. Like a B+-tree based store, Firewood directly uses -//! the tree structure as the index on disk. Thus, there is no additional "emulation" of the -//! logical trie to flatten out the data structure to feed into the underlying DB that is unaware -//! of the data being stored. It provides generic trie storage for arbitrary keys and values. -//! -//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL guarantees -//! atomicity and durability in the database, but also offers "reversibility": some portion -//! of the old WAL can be optionally kept around to allow a fast in-memory rollback to recover -//! some past versions of the entire store back in memory. While running the store, new changes -//! will also contribute to the configured window of changes (at batch granularity) to access any past +//! #Firewood: Compaction-Less Database Optimized for Efficiently Storing Recent Merkleized Blockchain State +//! +//! Firewood is an embedded key-value store, optimized to store recent Merkleized blockchain +//! state with minimal overhead. Firewood is implemented from the ground up to directly +//! store trie nodes on-disk. Unlike most of state management approaches in the field, +//! it is not built on top of a generic KV store such as LevelDB/RocksDB. Firewood, like a +//! B+-tree based database, directly uses the trie structure as the index on-disk. Thus, +//! there is no additional “emulation” of the logical trie to flatten out the data structure +//! to feed into the underlying database that is unaware of the data being stored. The convenient +//! byproduct of this approach is that iteration is still fast (for serving state sync queries) +//! but compaction is not required to maintain the index. Firewood was first conceived to provide +//! a very fast storage layer for the EVM but could be used on any blockchain that +//! requires authenticated state. +//! +//! Firewood only attempts to store the latest state on-disk and will actively clean up +//! unused state when state diffs are committed. To avoid reference counting trie nodes, +//! Firewood does not copy-on-write (COW) the state trie and instead keeps +//! one latest version of the trie index on disk and applies in-place updates to it. +//! Firewood keeps some configurable number of previous states in memory to power +//! state sync (which may occur at a few roots behind the current state). +//! +//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL +//! guarantees atomicity and durability in the database, but also offers +//! “reversibility”: some portion of the old WAL can be optionally kept around to +//! allow a fast in-memory rollback to recover some past versions of the entire +//! store back in memory. While running the store, new changes will also contribute +//! to the configured window of changes (at batch granularity) to access any past //! versions with no additional cost at all. //! //! # Design Philosophy & Overview @@ -38,7 +43,7 @@ //! well-executed plan for this is to make sure the performance degradation is reasonable or //! well-contained with respect to the ever-increasing size of the index. This design is useful //! for nodes which serve as the backend for some indexing service (e.g., chain explorer) or as a -//! query portal to some user agent (e.g., wallet apps). Blockchains with poor finality may also +//! query portal to some user agent (e.g., wallet apps). Blockchains with delayed finality may also //! need this because the "canonical" branch of the chain could switch (but not necessarily a //! practical concern nowadays) to a different fork at times. //! @@ -64,11 +69,10 @@ //! Firewood is built by three layers of abstractions that totally decouple the //! layout/representation of the data on disk from the actual logical data structure it retains: //! -//! - Linear, memory-like space: the [shale](https://crates.io/crates/shale) crate from an academic -//! project (CedrusDB) code offers a `CachedStore` abstraction for a (64-bit) byte-addressable space -//! that abstracts away the intricate method that actually persists the in-memory data on the -//! secondary storage medium (e.g., hard drive). The implementor of `CachedStore` will provide the -//! functions to give the user of `CachedStore` an illusion that the user is operating upon a +//! - Linear, memory-like space: the `shale` crate offers a `CachedStore` abstraction for a +//! (64-bit) byte-addressable space that abstracts away the intricate method that actually persists +//! the in-memory data on the secondary storage medium (e.g., hard drive). The implementor of `CachedStore` +//! provides the functions to give the user of `CachedStore` an illusion that the user is operating upon a //! byte-addressable memory space. It is just a "magical" array of bytes one can view and change //! that is mirrored to the disk. In reality, the linear space will be chunked into files under a //! directory, but the user does not have to even know about this. @@ -84,12 +88,6 @@ //! persisted on disk. It is as if they're just in memory, which makes it much easier to write //! and maintain the code. //! -//! The three layers are depicted as follows: -//! -//!

    -//! -//!

    -//! //! Given the abstraction, one can easily realize the fact that the actual data that affect the //! state of the data structure (trie) is what the linear space (`CachedStore`) keeps track of, that is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the @@ -114,10 +112,10 @@ //! dirty pages induced by this write batch are taken out from the linear space. Although they are //! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, //! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL -//! subsystem (supported by [growthring](https://crates.io/crates/growth-ring)). After the -//! WAL record is written (one record per write batch), the dirty pages are then pushed to the -//! on-disk linear space to mirror the change by some asynchronous, out-of-order file writes. See -//! the `BufferCmd::WriteBatch` part of `DiskBuffer::process` for the detailed logic. +//! subsystem (supported by growthring). After the WAL record is written (one record per write batch), +//! the dirty pages are then pushed to the on-disk linear space to mirror the change by some +//! asynchronous, out-of-order file writes. See the `BufferCmd::WriteBatch` part of `DiskBuffer::process` +//! for the detailed logic. //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! From 5b2dc05e347682934157427310189b1a72a9c8cd Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Sep 2023 12:41:31 -0700 Subject: [PATCH 0303/1053] cargo workspace-version update v0.0.4 (#280) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/Cargo.toml | 8 ++++---- fwdctl/Cargo.toml | 4 ++-- growth-ring/Cargo.toml | 4 ++-- libaio/Cargo.toml | 4 ++-- rpc/Cargo.toml | 4 ++-- shale/Cargo.toml | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index a4cf1f58d9f0..7f026966df0b 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.3" +version = "0.0.4" edition = "2021" authors = [ "Ted Yin (@Determinant) ", @@ -20,9 +20,9 @@ aquamarine = "0.3.1" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" -growth-ring = { version = "0.0.3", path = "../growth-ring" } -libaio = {version = "0.0.3", path = "../libaio" } -shale = { version = "0.0.3", path = "../shale" } +growth-ring = { version = "0.0.4", path = "../growth-ring" } +libaio = {version = "0.0.4", path = "../libaio" } +shale = { version = "0.0.4", path = "../shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.11.0" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 2c58686d5d93..dd01f83f5492 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "fwdctl" -version = "0.0.3" +version = "0.0.4" edition = "2021" [dependencies] -firewood = { version = "0.0.3", path = "../firewood" } +firewood = { version = "0.0.4", path = "../firewood" } clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 5de0781f236d..05c3f159416c 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "growth-ring" -version = "0.0.3" +version = "0.0.4" edition = "2021" keywords = ["wal", "db", "futures"] -license = "MIT" +license = "../LICENSE.md" description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/libaio/Cargo.toml b/libaio/Cargo.toml index a81d858f2502..cce43b716754 100644 --- a/libaio/Cargo.toml +++ b/libaio/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "libaio" -version = "0.0.3" +version = "0.0.4" edition = "2021" keywords = ["libaio", "aio", "async", "futures"] -license = "MIT" +license = "../LICENSE.md" description = "Straightforward Linux AIO using Futures/async/await." [features] diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 16df5ebe414c..3bec6bb0e844 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rpc" -version = "0.1.0" +version = "0.0.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,7 +16,7 @@ test = false bench = false [dependencies] -firewood = { version = "0.0.3", path = "../firewood" } +firewood = { version = "0.0.4", path = "../firewood" } prost = "0.12.0" thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } diff --git a/shale/Cargo.toml b/shale/Cargo.toml index acf120027d67..5d9ba1499ff6 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "shale" -version = "0.0.3" +version = "0.0.4" edition = "2021" description = "Useful abstraction and light-weight implemenation for a key-value store." -license = "MIT" +license = "../LICENSE.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 4eb4bbbb0321f947b5a78a3d77abfc5bbe435249 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Sep 2023 14:02:59 -0700 Subject: [PATCH 0304/1053] Add a license check for each file to CI (#263) --- .github/check-license-headers.yaml | 38 ++++++++++++++++++++++++++++++ .github/license-header.txt | 2 ++ .github/workflows/ci.yaml | 6 +++++ 3 files changed, 46 insertions(+) create mode 100644 .github/check-license-headers.yaml create mode 100644 .github/license-header.txt diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml new file mode 100644 index 000000000000..9837b102655a --- /dev/null +++ b/.github/check-license-headers.yaml @@ -0,0 +1,38 @@ +[ + { + "include": [ + "**/**/*.rs" + ], + "exclude": [ + "target/**", + "*/LICENSE*", + "LICENSE.md", + "RELEASE.md", + "rpc/**", + "README*", + "*/README*", + "Cargo.toml", + "*/Cargo.toml", + "libaio/**", + "docs/**", + "CODEOWNERS" + ], + "license": "./.github/license-header.txt" + }, + { + "include": [ + "target/**", + "*/LICENSE*", + "LICENSE.md", + "RELEASE.md", + "rpc/**", + "README*", + "*/README*", + "Cargo.toml", + "*/Cargo.toml", + "libaio/**", + "docs/**", + "CODEOWNERS" + ], + } +] diff --git a/.github/license-header.txt b/.github/license-header.txt new file mode 100644 index 000000000000..bb72d440a583 --- /dev/null +++ b/.github/license-header.txt @@ -0,0 +1,2 @@ +// Copyright (C) %year%, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e18c5371dbbd..e26b9094deed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,6 +80,12 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 + - name: Check license headers + uses: viperproject/check-license-header@v2 + with: + path: . + config: .github/check-license-headers.yaml + strict: true - name: Restore Check Deps id: cache-build-deps-restore uses: actions/cache/restore@v3 From 84bb8d51026ff80c6ecffdcb30fea5cb1e1df399 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Sep 2023 17:00:55 -0700 Subject: [PATCH 0305/1053] Don't publish to crates.io yet (#281) --- .github/workflows/publish.yaml | 35 ---------------------------------- RELEASE.md | 11 +++-------- 2 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml deleted file mode 100644 index 89171dcd4d1b..000000000000 --- a/.github/workflows/publish.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: publish - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - publish-firewood-crate: - name: firewood-lib - runs-on: ubuntu-latest - if: "startsWith(github.event.release.tag_name, 'v')" - steps: - - uses: actions/checkout@v1 - - uses: dtolnay/rust-toolchain@stable - - name: publish shale crate - continue-on-error: true - run: | - cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish -p firewood-shale - - name: publish libaio crate - continue-on-error: true - run: | - cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish -p firewood-libaio - - name: publish growth-ring crate - continue-on-error: true - run: | - cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish -p firewood-growth-ring - - name: publish firewood crate - continue-on-error: false - run: | - cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish -p firewood diff --git a/RELEASE.md b/RELEASE.md index 62d020e6fe7b..2c41c04507f0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -15,7 +15,7 @@ all versions are updated simultaneously in `cargo-workspace-version`. To use it to update to 0.0.4, for example: $ cargo install cargo-workspace-version $ cargo workspace-version update -v0.0.4 +v0.0.5 See the [source code](https://github.com/ava-labs/cargo-workspace-version) for more information on the tool. @@ -25,10 +25,5 @@ more information on the tool. > the next step. To trigger a release, simply push a semver-compatible tag to the main branch, -for example `v0.0.1`. The CI will automatically publish a draft release which -consists of release notes and changes. Once this draft is approved, and the new -release exists, CI will then go and publish each of the sub-project crates to -crates.io. -> ❗ Publishing a crate with the same version as the existing version will -> result in a cargo error. Be sure to update all crates within the workspace -> before publishing to crates.io. +for example `v0.0.5`. The CI will automatically publish a draft release which +consists of release notes and changes. From 9d33614d7d743ef0be542d43387f1dadcdbafd6b Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 26 Sep 2023 17:22:53 -0700 Subject: [PATCH 0306/1053] [firewood] lib header nit (#282) --- firewood/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 6e36e6ca4df0..2007dfcded7e 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -//! #Firewood: Compaction-Less Database Optimized for Efficiently Storing Recent Merkleized Blockchain State +//! # Firewood: Compaction-Less Database Optimized for Efficiently Storing Recent Merkleized Blockchain State //! //! Firewood is an embedded key-value store, optimized to store recent Merkleized blockchain //! state with minimal overhead. Firewood is implemented from the ground up to directly From fefab0576eb2b38c36e8afe3d1aee3596043840e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 08:39:37 -0700 Subject: [PATCH 0307/1053] build(deps): update pprof requirement from 0.12.1 to 0.13.0 (#283) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- shale/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 7f026966df0b..5cd04eb9cf34 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -46,7 +46,7 @@ predicates = "3.0.1" serial_test = "2.0.0" clap = { version = "4.3.1", features = ['derive'] } test-case = "3.1.0" -pprof = { version = "0.12.1", features = ["flamegraph"] } +pprof = { version = "0.13.0", features = ["flamegraph"] } [features] # proof API diff --git a/shale/Cargo.toml b/shale/Cargo.toml index 5d9ba1499ff6..f427370f7291 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -15,7 +15,7 @@ bytemuck = { version = "1.13.1", features = ["derive"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.12.0", features = ["flamegraph"] } +pprof = { version = "0.13.0", features = ["flamegraph"] } sha3 = "0.10.7" rand = "0.8.5" From b124809ccc724c82a74f6fb3944700ba2304e31d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 27 Sep 2023 09:46:47 -0700 Subject: [PATCH 0308/1053] Correct some spelling errors in README.md (#284) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bc11d1da4b08..813191fdc21e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ versions with no additional cost at all. ![architecture diagram](./docs/assets/architecture.svg) -## Termimology +## Terminology * `Revision` - A historical point-in-time state/version of the trie. This represents the entire trie, including all `Key`/`Value`s at that point @@ -111,7 +111,7 @@ The focus of this milestone will be to support synchronization to other instances to replicate the state. A synchronization library should also be developed for this milestone. - [ ] :runner: Add support for Ava Labs generic test tool via grpc client -- [ ] :runner: Pluggable encoding for nodes, for optional compatibilty with merkledb +- [ ] :runner: Pluggable encoding for nodes, for optional compatibility with MerkleDB - [ ] Support replicating the full state with corresponding range proofs that verify the correctness of the data. - [ ] Support replicating the delta state from the last sync point with From c7a96781a3c54b86b33fc96cb1d11b8074ffacab Mon Sep 17 00:00:00 2001 From: JacobEverly <112036223+JacobEverly@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:18:54 +0200 Subject: [PATCH 0309/1053] Update README.md (#286) Signed-off-by: JacobEverly <112036223+JacobEverly@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 813191fdc21e..f68868705f13 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ versions with no additional cost at all. * `Put` - An operation for a `Key`/`Value` pair. A put means "create if it doesn't exist, or update it if it does. A put operation is how you add a `Value` for a specific `Key`. -* `Delete` - A operation indicating that a `Key` that should be removed from the trie. +* `Delete` - An operation indicating that a `Key` that should be removed from the trie. * `Batch Operation` - An operation of either `Put` or `Delete`. * `Batch` - An ordered set of `Batch Operation`s. * `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not From cfc5aa5cbd690a9f82bd27973c871c7751805d55 Mon Sep 17 00:00:00 2001 From: Angel Leon Date: Wed, 27 Sep 2023 19:14:49 -0600 Subject: [PATCH 0310/1053] Nibbles nits (#285) Signed-off-by: JacobEverly <112036223+JacobEverly@users.noreply.github.com> --- firewood/src/nibbles.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index d918fb5172b5..8de0865eb42d 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -84,7 +84,7 @@ impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { } } -/// An interator returned by [Nibbles::into_iter] +/// An iterator returned by [Nibbles::into_iter] /// See their documentation for details. #[derive(Clone, Debug)] pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { @@ -107,15 +107,15 @@ impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_Z result } - fn nth(&mut self, n: usize) -> Option { - self.head += std::cmp::min(n, self.tail - self.head); - self.next() - } - fn size_hint(&self) -> (usize, Option) { let remaining = self.tail - self.head; (remaining, Some(remaining)) } + + fn nth(&mut self, n: usize) -> Option { + self.head += std::cmp::min(n, self.tail - self.head); + self.next() + } } impl<'a, const LEADING_ZEROES: usize> NibblesIterator<'a, LEADING_ZEROES> { @@ -155,7 +155,7 @@ mod test { } #[test] - fn leadingzero_nibbles_index() { + fn leading_zero_nibbles_index() { let nib = Nibbles::<1>(&TEST_BYTES); let expected = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; for v in expected.into_iter().enumerate() { From 44850d12c9f46adc76acf220cda4cefe3d137d8c Mon Sep 17 00:00:00 2001 From: Angel Leon Date: Wed, 27 Sep 2023 21:15:22 -0600 Subject: [PATCH 0311/1053] Corrected various typographical errors in code comments (#288) --- firewood/examples/rev.rs | 2 +- firewood/src/db.rs | 2 +- firewood/src/proof.rs | 4 ++-- firewood/src/storage/buffer.rs | 10 +++++----- firewood/src/storage/mod.rs | 4 ++-- firewood/tests/db.rs | 2 +- firewood/tests/merkle.rs | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index 91402ddee165..ce4463b2d19d 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -153,7 +153,7 @@ impl RevisionTracker { fn with_new_db(self, path: impl AsRef, cfg: &DbConfig) -> Self { let hashes = { - // must name db variable to explictly drop + // must name db variable to explicitly drop let Self { hashes, db: _db } = self; hashes }; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b2c31f257fcc..f8bf18222a8c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -697,7 +697,7 @@ impl Db { &self.cfg, )?; - // Flip the reset flag after reseting the store headers. + // Flip the reset flag after resetting the store headers. if reset_store_headers { inner.reset_store_headers = false; } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index a43aecc67964..f5f25ea24d9a 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -378,7 +378,7 @@ impl + Send> Proof { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; - // TODO(Hao): (Optimization) If a node is alreay decode we don't need to decode again. + // TODO(Hao): (Optimization) If a node is already decode we don't need to decode again. let (mut chd_ptr, sub_proof, size) = self.decode_node(merkle, cur_key, cur_proof.as_ref(), false)?; @@ -673,7 +673,7 @@ fn build_branch_ptr + Send + Sync>( // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. // -// It's the key step for range proof. The precalucated encoded value of all internal +// It's the key step for range proof. The precalculated encoded value of all internal // nodes should be removed. But if the proof is valid, // the missing children will be filled, otherwise it will be thrown anyway. // diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 7f7de2996ce1..8e4bcdbb4f20 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -109,7 +109,7 @@ impl Notifiers { } /// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] -/// and managing the persistance of pages. +/// and managing the persistence of pages. pub struct DiskBuffer { inbound: mpsc::Receiver, aiomgr: AioManager, @@ -262,7 +262,7 @@ fn schedule_write( false } else { - // if `staging` is not empty, move all semaphors to `writing` and recurse + // if `staging` is not empty, move all semaphores to `writing` and recurse // to schedule the new writes. slot.notifiers.staging_to_writing(); @@ -432,7 +432,7 @@ async fn run_wal_queue( .await .peel(ring_ids, max.revisions) .await - .map_err(|_| "Wal errore while pruning") + .map_err(|_| "Wal errored while pruning") .unwrap() }; @@ -880,14 +880,14 @@ mod tests { let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), hash); - // get RO view of the buffer from the second hash. Only the new store shoulde see the value. + // get RO view of the buffer from the second hash. Only the new store should see the value. let view = another_store.get_view(32, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), another_hash); let empty: [u8; HASH_SIZE] = [0; HASH_SIZE]; let view = store.get_view(32, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), empty); - // Overwrite the value from the beginning in the new store. Only the new store shoulde see the change. + // Overwrite the value from the beginning in the new store. Only the new store should see the change. another_store.write(0, &another_hash); let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), another_hash); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index d59e47a949d4..e0ceceb34fae 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -225,7 +225,7 @@ impl StoreDelta { let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; let wp = w.offset >> PAGE_SIZE_NBIT; if wp > tail { - // all following writes won't go back past w.offset, so the previous continous + // all following writes won't go back past w.offset, so the previous continuous // write area is determined create_dirty_pages!(head, tail); head = wp; @@ -430,7 +430,7 @@ struct StoreRevMutDelta { } #[derive(Clone, Debug)] -/// A mutable revision of the store. The view is constucted by applying the `deltas` to the +/// A mutable revision of the store. The view is constructed by applying the `deltas` to the /// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply /// the changes. `StoreRevMut` supports basing on top of another `StoreRevMut`, by chaining /// `prev_deltas` (from based `StoreRevMut`) with current `deltas` from itself . In this way, diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 715c388c521a..d69f073e607c 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -310,7 +310,7 @@ fn db_proposal() -> Result<(), DbError> { }); }); - // Recusrive commit + // Recursive commit let batch = vec![BatchOp::Put { key: b"k3", diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 6eaa5ac434cd..6cf879035d5d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -1025,7 +1025,7 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { #[test] // Tests a malicious proof, where the proof is more or less the -// whole trie. This is to match correpsonding test in geth. +// whole trie. This is to match corresponding test in geth. fn test_bloadted_range_proof() -> Result<(), ProofError> { // Use a small trie let mut items = Vec::new(); From 1c6016de26f3ccc41b3f11b6ec13120cf4dc5e38 Mon Sep 17 00:00:00 2001 From: Angel Leon Date: Thu, 28 Sep 2023 09:14:01 -0600 Subject: [PATCH 0312/1053] Refactor `buffer.rs` for better readability and maintainability (#287) Co-authored-by: Richard Pringle --- firewood/src/db.rs | 77 ++++++++++++++++------------------ firewood/src/storage/buffer.rs | 71 ++++++++++++++----------------- 2 files changed, 69 insertions(+), 79 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f8bf18222a8c..600702174194 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -418,49 +418,46 @@ impl Db { disk_buffer.run() })); - let root_hash_cache = Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.root_hash_ncached_pages) - .ncached_files(cfg.root_hash_ncached_files) - .space_id(ROOT_HASH_SPACE) - .file_nbit(params.root_hash_file_nbit) - .rootdir(root_hash_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ); + let root_hash_cache: Arc = CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.root_hash_ncached_pages) + .ncached_files(cfg.root_hash_ncached_files) + .space_id(ROOT_HASH_SPACE) + .file_nbit(params.root_hash_file_nbit) + .rootdir(root_hash_path) + .build(), + disk_requester.clone(), + ) + .unwrap() + .into(); // setup disk buffer let data_cache = Universe { - merkle: SubUniverse::new( - Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.meta_ncached_pages) - .ncached_files(cfg.meta_ncached_files) - .space_id(MERKLE_META_SPACE) - .file_nbit(params.meta_file_nbit) - .rootdir(merkle_meta_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ), - Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(cfg.payload_ncached_pages) - .ncached_files(cfg.payload_ncached_files) - .space_id(MERKLE_PAYLOAD_SPACE) - .file_nbit(params.payload_file_nbit) - .rootdir(merkle_payload_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ), + merkle: SubUniverse::>::new( + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.meta_ncached_pages) + .ncached_files(cfg.meta_ncached_files) + .space_id(MERKLE_META_SPACE) + .file_nbit(params.meta_file_nbit) + .rootdir(merkle_meta_path) + .build(), + disk_requester.clone(), + ) + .unwrap() + .into(), + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(cfg.payload_ncached_pages) + .ncached_files(cfg.payload_ncached_files) + .space_id(MERKLE_PAYLOAD_SPACE) + .file_nbit(params.payload_file_nbit) + .rootdir(merkle_payload_path) + .build(), + disk_requester.clone(), + ) + .unwrap() + .into(), ), }; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 8e4bcdbb4f20..99f703c5409e 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -642,6 +642,24 @@ mod tests { .join("firewood") } + fn new_cached_space_for_test( + state_path: PathBuf, + disk_requester: DiskBufferRequester, + ) -> Arc { + CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(1) + .ncached_files(1) + .space_id(STATE_SPACE) + .file_nbit(1) + .rootdir(state_path) + .build(), + disk_requester, + ) + .unwrap() + .into() + } + #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer_with_undo() { @@ -662,19 +680,7 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache = Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(1) - .ncached_files(1) - .space_id(STATE_SPACE) - .file_nbit(1) - .rootdir(state_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ); + let state_cache = new_cached_space_for_test(state_path, disk_requester.clone()); // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. @@ -750,19 +756,7 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache = Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(1) - .ncached_files(1) - .space_id(STATE_SPACE) - .file_nbit(1) - .rootdir(state_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ); + let state_cache = new_cached_space_for_test(state_path, disk_requester.clone()); // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. @@ -835,19 +829,18 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache = Arc::new( - CachedSpace::new( - &StoreConfig::builder() - .ncached_pages(1) - .ncached_files(1) - .space_id(STATE_SPACE) - .file_nbit(1) - .rootdir(state_path) - .build(), - disk_requester.clone(), - ) - .unwrap(), - ); + let state_cache: Arc = CachedSpace::new( + &StoreConfig::builder() + .ncached_pages(1) + .ncached_files(1) + .space_id(STATE_SPACE) + .file_nbit(1) + .rootdir(state_path) + .build(), + disk_requester.clone(), + ) + .unwrap() + .into(); // add an in memory cached space. this will allow us to write to the // disk buffer then later persist the change to disk. From 646a04a10ecc67ccd4f82a1ef1ea8321e1bbf5e3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 28 Sep 2023 12:07:49 -0700 Subject: [PATCH 0313/1053] 1/3 Implement improved API implementation for database (#227) --- firewood/src/db.rs | 42 ++++++++++++++++++++++++++++++++++++-- firewood/src/v2/api.rs | 2 +- firewood/src/v2/db.rs | 2 +- firewood/src/v2/emptydb.rs | 2 +- firewood/src/v2/propose.rs | 4 ++-- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 600702174194..cbab34d65ef2 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,8 +14,9 @@ use crate::{ CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }, - v2::api::Proof, + v2::api::{self, Proof}, }; +use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; use metered::{metered, HitCount}; use parking_lot::{Mutex, RwLock}; @@ -28,7 +29,7 @@ use std::{ collections::VecDeque, error::Error, fmt, - io::{Cursor, Write}, + io::{Cursor, ErrorKind, Write}, mem::size_of, num::NonZeroUsize, os::fd::{AsFd, BorrowedFd}, @@ -272,6 +273,43 @@ pub struct DbRev { merkle: Merkle, } +#[async_trait] +impl + Send + Sync> api::DbView for DbRev { + async fn root_hash(&self) -> Result { + self.merkle + .root_hash(self.header.kv_root) + .map(|h| *h) + .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) + } + + async fn val(&self, key: K) -> Result>, api::Error> { + let obj_ref = self.merkle.get(key, self.header.kv_root); + match obj_ref { + Err(e) => Err(api::Error::IO(std::io::Error::new(ErrorKind::Other, e))), + Ok(obj) => match obj { + None => Ok(None), + Some(inner) => Ok(Some(inner.as_ref().to_owned())), + }, + } + } + + async fn single_key_proof + Send>( + &self, + _key: K, + ) -> Result>, api::Error> { + todo!() + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result>, api::Error> { + todo!() + } +} + impl + Send + Sync> DbRev { fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 51fea04e0a90..7ce46a4a9e85 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -136,7 +136,7 @@ pub trait DbView { async fn root_hash(&self) -> Result; /// Get the value of a specific key - async fn val(&self, key: K) -> Result, Error>; + async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key async fn single_key_proof( diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index dfa342242cd5..ad5e4ac760f4 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -75,7 +75,7 @@ impl api::DbView for DbView { todo!() } - async fn val(&self, _key: K) -> Result, api::Error> { + async fn val(&self, _key: K) -> Result>, api::Error> { todo!() } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index c7c8839b02df..677f6de85750 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -58,7 +58,7 @@ impl DbView for HistoricalImpl { Ok(ROOT_HASH) } - async fn val(&self, _key: K) -> Result, Error> { + async fn val(&self, _key: K) -> Result>, Error> { Ok(None) } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index f2cb1f6c7fad..6467a6920764 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -105,12 +105,12 @@ impl api::DbView for Proposal { todo!() } - async fn val(&self, key: K) -> Result, api::Error> { + async fn val(&self, key: K) -> Result>, api::Error> { // see if this key is in this proposal match self.delta.get(key.as_ref()) { Some(change) => match change { // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(Some(val)), + KeyOp::Put(val) => Ok(Some(val.to_owned())), KeyOp::Delete => Ok(None), // key was deleted in this proposal }, None => match &self.base { From 403f7d84e2f8ac0d2d35267ab5fa26027ce8c48b Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 28 Sep 2023 18:18:07 -0400 Subject: [PATCH 0314/1053] Remove 'static lifetime on key-type trait (#289) --- firewood/src/v2/api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 7ce46a4a9e85..47fc01fdf3be 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -8,9 +8,9 @@ use async_trait::async_trait; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with /// lifetimes are not allowed (hence 'static) -pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug + 'static {} +pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug {} -impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} +impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// A `ValueType` is the same as a `KeyType`. However, these could /// be a different type from the `KeyType` on a given API call. From 6f29a0057ef7c5bcd94ed4ca914c2a95fd18a8a6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 28 Sep 2023 16:10:34 -0700 Subject: [PATCH 0315/1053] 2/3 Implement v2::api for the real DB (#292) --- firewood/benches/hashops.rs | 2 +- firewood/examples/rev.rs | 10 ++--- firewood/src/db.rs | 48 ++++++++++++++++++---- firewood/src/db/proposal.rs | 82 +++++++++++++++++++++++++++++-------- firewood/src/v2/api.rs | 8 ++-- firewood/src/v2/db.rs | 23 +++++++++-- firewood/src/v2/emptydb.rs | 5 +-- firewood/src/v2/propose.rs | 4 +- firewood/tests/db.rs | 22 +++++----- fwdctl/src/delete.rs | 6 ++- fwdctl/src/insert.rs | 6 +-- 11 files changed, 158 insertions(+), 58 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bebac2ab134c..bd69004ac94b 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -160,7 +160,7 @@ fn bench_db(criterion: &mut Criterion) { }, |(db, batch_ops)| { let proposal = db.new_proposal(batch_ops).unwrap(); - proposal.commit().unwrap(); + proposal.commit_sync().unwrap(); }, BatchSize::SmallInput, ); diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs index ce4463b2d19d..aa0fd917cc01 100644 --- a/firewood/examples/rev.rs +++ b/firewood/examples/rev.rs @@ -7,7 +7,7 @@ use firewood::{ db::{BatchOp, Db, DbConfig, DbError, Proposal, Revision, WalConfig}, merkle::{Node, TrieHash}, storage::StoreRevShared, - v2::api::Proof, + v2::api::{KeyType, Proof, ValueType}, }; use shale::compact::CompactSpace; @@ -118,7 +118,7 @@ impl RevisionTracker { } } - fn create_revisions( + fn create_revisions( &mut self, mut iter: impl Iterator, ) -> Result<(), DbError> @@ -129,7 +129,7 @@ impl RevisionTracker { iter.try_for_each(|(k, v)| self.create_revision(k, v)) } - fn create_revision(&mut self, k: K, v: V) -> Result<(), DbError> + fn create_revision(&mut self, k: K, v: V) -> Result<(), DbError> where K: AsRef<[u8]>, V: AsRef<[u8]>, @@ -138,7 +138,7 @@ impl RevisionTracker { key: k, value: v.as_ref().to_vec(), }]; - self.db.new_proposal(batch)?.commit()?; + self.db.new_proposal(batch)?.commit_sync()?; let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); @@ -146,7 +146,7 @@ impl RevisionTracker { } fn commit_proposal(&mut self, proposal: Proposal) { - proposal.commit().unwrap(); + proposal.commit_sync().unwrap(); let hash = self.db.kv_root_hash().expect("root-hash should exist"); self.hashes.push_front(hash); } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index cbab34d65ef2..ea59c503e52c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,7 +14,7 @@ use crate::{ CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }, - v2::api::{self, Proof}, + v2::api::{self, HashKey, KeyType, Proof, ValueType}, }; use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; @@ -293,11 +293,14 @@ impl + Send + Sync> api::DbView for DbRev { } } - async fn single_key_proof + Send>( + async fn single_key_proof( &self, - _key: K, - ) -> Result>, api::Error> { - todo!() + key: K, + ) -> Result>>, api::Error> { + self.merkle + .prove(key, self.header.kv_root) + .map(Some) + .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } async fn range_proof( @@ -380,6 +383,34 @@ impl Drop for DbInner { } } +#[async_trait] +impl api::Db for Db { + type Historical = DbRev; + + type Proposal = Proposal; + + async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { + if let Some(rev) = self.get_revision(&TrieHash(root_hash)) { + Ok(Arc::new(rev.rev)) + } else { + Err(api::Error::HashNotFound { + provided: root_hash, + }) + } + } + + async fn root_hash(&self) -> Result { + self.kv_root_hash().map(|hash| hash.0).map_err(Into::into) + } + + async fn propose( + &self, + batch: api::Batch, + ) -> Result { + self.new_proposal(batch).map_err(Into::into) + } +} + #[derive(Debug)] pub struct DbRevInner { inner: VecDeque>, @@ -722,7 +753,10 @@ impl Db { } /// Create a proposal. - pub fn new_proposal>(&self, data: Batch) -> Result { + pub fn new_proposal( + &self, + data: Batch, + ) -> Result { let mut inner = self.inner.write(); let reset_store_headers = inner.reset_store_headers; let (store, mut rev) = Db::new_store( @@ -742,7 +776,7 @@ impl Db { BatchOp::Put { key, value } => { let (header, merkle) = rev.borrow_split(); merkle - .insert(key, value, header.kv_root) + .insert(key, value.as_ref().to_vec(), header.kv_root) .map_err(DbError::Merkle)?; Ok(()) } diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index befc1a6ad534..c202e3c0154f 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -9,22 +9,14 @@ use super::{ use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, + v2::api::{self, KeyType, ValueType}, }; +use async_trait::async_trait; use parking_lot::{Mutex, RwLock}; use shale::CachedStore; -use std::sync::Arc; - -/// A key/value pair operation. Only put (upsert) and delete are -/// supported -#[derive(Debug)] -pub enum BatchOp { - Put { key: K, value: Vec }, - Delete { key: K }, -} +use std::{io::ErrorKind, sync::Arc}; -/// A list of operations to consist of a batch that -/// can be proposed -pub type Batch = Vec>; +pub use crate::v2::api::{Batch, BatchOp}; /// An atomic batch of changes proposed against the latest committed revision, /// or any existing [Proposal]. Multiple proposals can be created against the @@ -50,10 +42,29 @@ pub enum ProposalBase { View(Arc>), } +#[async_trait] +impl crate::v2::api::Proposal for Proposal { + type Proposal = Proposal; + + async fn commit(self: Arc) -> Result, api::Error> { + todo!() + } + + async fn propose( + self: Arc, + data: api::Batch, + ) -> Result { + self.propose_sync(data).map_err(Into::into) + } +} + impl Proposal { // Propose a new proposal from this proposal. The new proposal will be // the child of it. - pub fn propose>(self: Arc, data: Batch) -> Result { + pub fn propose_sync( + self: Arc, + data: Batch, + ) -> Result { let store = self.store.new_from_other(); let m = Arc::clone(&self.m); @@ -81,7 +92,7 @@ impl Proposal { BatchOp::Put { key, value } => { let (header, merkle) = rev.borrow_split(); merkle - .insert(key, value, header.kv_root) + .insert(key, value.as_ref().to_vec(), header.kv_root) .map_err(DbError::Merkle)?; Ok(()) } @@ -111,14 +122,14 @@ impl Proposal { /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are /// either retained on disk or lost together during a crash. - pub fn commit(&self) -> Result<(), DbError> { + pub fn commit_sync(&self) -> Result<(), DbError> { let mut committed = self.committed.lock(); if *committed { return Ok(()); } if let ProposalBase::Proposal(p) = &self.parent { - p.commit()?; + p.commit_sync()?; }; // Check for if it can be committed @@ -257,6 +268,45 @@ impl Proposal { } } +#[async_trait] +impl api::DbView for Proposal { + async fn root_hash(&self) -> Result { + self.get_revision() + .kv_root_hash() + .map(|hash| hash.0) + .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) + } + async fn val(&self, key: K) -> Result>, api::Error> + where + K: api::KeyType, + { + // TODO: pass back errors from kv_get + Ok(self.get_revision().kv_get(key)) + } + + async fn single_key_proof(&self, key: K) -> Result>>, api::Error> + where + K: api::KeyType, + { + self.get_revision() + .prove(key) + .map(Some) + .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: usize, + ) -> Result>, api::Error> + where + K: api::KeyType, + { + todo!() + } +} + impl Drop for Proposal { fn drop(&mut self) { if !*self.committed.lock() { diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 47fc01fdf3be..38df5dc5b062 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -69,6 +69,9 @@ pub enum Error { #[error("Invalid proposal")] InvalidProposal, + + #[error("Internal error")] + InternalError(Box), } /// A range proof, consisting of a proof of the first key and the last key, @@ -139,10 +142,7 @@ pub trait DbView { async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key - async fn single_key_proof( - &self, - key: K, - ) -> Result>, Error>; + async fn single_key_proof(&self, key: K) -> Result>>, Error>; /// Obtain a range proof over a set of keys /// diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index ad5e4ac760f4..89853eb86da4 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -7,7 +7,10 @@ use tokio::sync::Mutex; use async_trait::async_trait; -use crate::v2::api::{self, Batch, KeyType, ValueType}; +use crate::{ + db::DbError, + v2::api::{self, Batch, KeyType, ValueType}, +}; use super::propose; @@ -26,6 +29,20 @@ pub struct Db { latest_cache: Mutex>>, } +impl From for api::Error { + fn from(value: DbError) -> Self { + match value { + DbError::InvalidParams => api::Error::InternalError(value.into()), + DbError::Merkle(e) => api::Error::InternalError(e.into()), + DbError::System(e) => api::Error::IO(e.into()), + DbError::KeyNotFound | DbError::CreateError => api::Error::InternalError(value.into()), + DbError::Shale(e) => api::Error::InternalError(e.into()), + DbError::IO(e) => api::Error::IO(e), + DbError::InvalidProposal => api::Error::InvalidProposal, + } + } +} + #[async_trait] impl api::Db for Db where @@ -79,10 +96,10 @@ impl api::DbView for DbView { todo!() } - async fn single_key_proof( + async fn single_key_proof( &self, _key: K, - ) -> Result>, api::Error> { + ) -> Result>>, api::Error> { todo!() } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 677f6de85750..762504aad005 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -62,10 +62,7 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn single_key_proof( - &self, - _key: K, - ) -> Result>, Error> { + async fn single_key_proof(&self, _key: K) -> Result>>, Error> { Ok(None) } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 6467a6920764..a6e53d0cb353 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -121,10 +121,10 @@ impl api::DbView for Proposal { } } - async fn single_key_proof( + async fn single_key_proof( &self, _key: K, - ) -> Result>, api::Error> { + ) -> Result>>, api::Error> { todo!() } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index d69f073e607c..e522fcac2a17 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -118,7 +118,7 @@ fn test_revisions() { batch.push(write); } let proposal = db.new_proposal(batch).unwrap(); - proposal.commit().unwrap(); + proposal.commit_sync().unwrap(); } while dumped.len() > 10 { dumped.pop_back(); @@ -192,7 +192,7 @@ fn create_db_issue_proof() { batch.push(write); } let proposal = db.new_proposal(batch).unwrap(); - proposal.commit().unwrap(); + proposal.commit_sync().unwrap(); let root_hash = db.kv_root_hash().unwrap(); @@ -206,7 +206,7 @@ fn create_db_issue_proof() { batch.push(write); } let proposal = db.new_proposal(batch).unwrap(); - proposal.commit().unwrap(); + proposal.commit_sync().unwrap(); let rev = db.get_revision(&root_hash).unwrap(); let key = "doe".as_bytes(); @@ -274,13 +274,13 @@ fn db_proposal() -> Result<(), DbError> { key: b"k2", value: "v2".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose(batch_2)?; + let proposal_2 = proposal.clone().propose_sync(batch_2)?; let rev = proposal_2.get_revision(); assert_val!(rev, "k", "v"); assert_val!(rev, "k2", "v2"); - proposal.commit()?; - proposal_2.commit()?; + proposal.commit_sync()?; + proposal_2.commit_sync()?; std::thread::scope(|scope| { scope.spawn(|| -> Result<(), DbError> { @@ -288,12 +288,12 @@ fn db_proposal() -> Result<(), DbError> { key: b"another_k", value: "another_v".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose(another_batch)?; + let another_proposal = proposal.clone().propose_sync(another_batch)?; let rev = another_proposal.get_revision(); assert_val!(rev, "k", "v"); assert_val!(rev, "another_k", "another_v"); // The proposal is invalid and cannot be committed - assert!(another_proposal.commit().is_err()); + assert!(another_proposal.commit_sync().is_err()); Ok(()) }); @@ -302,7 +302,7 @@ fn db_proposal() -> Result<(), DbError> { key: b"another_k_1", value: "another_v_1".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose(another_batch)?; + let another_proposal = proposal.clone().propose_sync(another_batch)?; let rev = another_proposal.get_revision(); assert_val!(rev, "k", "v"); assert_val!(rev, "another_k_1", "another_v_1"); @@ -326,13 +326,13 @@ fn db_proposal() -> Result<(), DbError> { key: b"k4", value: "v4".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose(batch_2)?; + let proposal_2 = proposal.clone().propose_sync(batch_2)?; let rev = proposal_2.get_revision(); assert_val!(rev, "k", "v"); assert_val!(rev, "k2", "v2"); assert_val!(rev, "k3", "v3"); assert_val!(rev, "k4", "v4"); - proposal_2.commit()?; + proposal_2.commit_sync()?; Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index fa8f16d03767..0c2520cfa644 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -31,9 +31,11 @@ pub fn run(opts: &Options) -> Result<()> { let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; - let batch = vec![BatchOp::Delete { key: &opts.key }]; + let batch: Vec> = vec![BatchOp::Delete { + key: opts.key.clone(), + }]; let proposal = db.new_proposal(batch).map_err(Error::msg)?; - proposal.commit().map_err(Error::msg)?; + proposal.commit_sync().map_err(Error::msg)?; println!("key {} deleted successfully", opts.key); Ok(()) diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 815685e948c0..804557de1f96 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -38,12 +38,12 @@ pub fn run(opts: &Options) -> Result<()> { Err(_) => return Err(anyhow!("error opening database")), }; - let batch = vec![BatchOp::Put { - key: &opts.key, + let batch: Vec, Vec>> = vec![BatchOp::Put { + key: opts.key.clone().into(), value: opts.value.bytes().collect(), }]; let proposal = db.new_proposal(batch).map_err(Error::msg)?; - proposal.commit().map_err(Error::msg)?; + proposal.commit_sync().map_err(Error::msg)?; println!("{}", opts.key); Ok(()) From 07a6eef96e02f1c08e27f174f452d724847468ef Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 28 Sep 2023 19:21:25 -0400 Subject: [PATCH 0316/1053] Untangling lifetimes and borrows (#291) Signed-off-by: Richard Pringle --- shale/src/compact.rs | 50 ++++++++++++++++++++++++-------------------- shale/src/lib.rs | 42 +++++++++++++------------------------ 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index a5ca3c1553e5..2557e0c19189 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -1,12 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::ObjCache; + use super::disk_address::DiskAddress; use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; use std::fmt::Debug; use std::io::{Cursor, Write}; use std::num::NonZeroUsize; -use std::ops::DerefMut; use std::sync::{Arc, RwLock}; #[derive(Debug)] @@ -295,16 +296,15 @@ impl std::ops::DerefMut for U64Field { } #[derive(Debug)] -struct CompactSpaceInner { +struct CompactSpaceInner { meta_space: Arc, compact_space: Arc, header: CompactSpaceHeaderSliced, - obj_cache: super::ObjCache, alloc_max_walk: u64, regn_nbit: u64, } -impl CompactSpaceInner { +impl CompactSpaceInner { fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } @@ -576,7 +576,8 @@ impl CompactSpaceInner { #[derive(Debug)] pub struct CompactSpace { - inner: RwLock>, + inner: RwLock>, + obj_cache: ObjCache, } impl CompactSpace { @@ -593,10 +594,10 @@ impl CompactSpace { meta_space, compact_space, header: CompactSpaceHeader::into_fields(header)?, - obj_cache, alloc_max_walk, regn_nbit, }), + obj_cache, }; Ok(cs) } @@ -609,40 +610,40 @@ impl let size = item.dehydrated_len() + extra; let addr = self.inner.write().unwrap().alloc(size)?; - let mut u = { + let obj = { let inner = self.inner.read().unwrap(); let compact_space = inner.compact_space.as_ref(); let view = StoredView::item_to_obj(compact_space, addr.try_into().unwrap(), size, item)?; - inner.obj_cache.put(view) + self.obj_cache.put(view) }; + let cache = &self.obj_cache; + + let mut obj_ref = ObjRef::new(Some(obj), cache); + // should this use a `?` instead of `unwrap`? - u.write(|_| {}).unwrap(); + obj_ref.write(|_| {}).unwrap(); - Ok(u) + Ok(obj_ref) } fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { let mut inner = self.inner.write().unwrap(); - inner.obj_cache.pop(ptr); + self.obj_cache.pop(ptr); inner.free(ptr.unwrap().get() as u64) } fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { - let inner = { - let inner = self.inner.read().unwrap(); - let obj_ref = inner - .obj_cache - .get(inner.obj_cache.lock().deref_mut(), ptr)?; + let obj = self.obj_cache.get(ptr)?; - if let Some(obj_ref) = obj_ref { - return Ok(obj_ref); - } + let inner = self.inner.read().unwrap(); + let cache = &self.obj_cache; - inner - }; + if let Some(obj) = obj { + return Ok(ObjRef::new(Some(obj), cache)); + } if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { return Err(ShaleError::InvalidAddressLength { @@ -654,14 +655,17 @@ impl let payload_size = inner .get_header(ptr - CompactHeader::MSIZE as usize)? .payload_size; + let obj = self.obj_cache.put(inner.get_data_ref(ptr, payload_size)?); + let cache = &self.obj_cache; - Ok(inner.obj_cache.put(inner.get_data_ref(ptr, payload_size)?)) + Ok(ObjRef::new(Some(obj), cache)) } fn flush_dirty(&self) -> Option<()> { let mut inner = self.inner.write().unwrap(); inner.header.flush_dirty(); - inner.obj_cache.flush_dirty() + // hold the write lock to ensure that both cache and header are flushed in-sync + self.obj_cache.flush_dirty() } } diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 84656cc6aee3..6b95c458e18d 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -5,7 +5,6 @@ use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; use std::fmt::{self, Debug, Formatter}; -use std::marker::PhantomData; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::sync::{Arc, RwLock, RwLockWriteGuard}; @@ -190,17 +189,12 @@ impl Deref for Obj { /// User handle that offers read & write access to the stored [ShaleStore] item. pub struct ObjRef<'a, T: Send + Sync> { inner: Option>, - cache: ObjCache, - _life: PhantomData<&'a mut ()>, + cache: &'a ObjCache, } impl<'a, T: Send + Sync> ObjRef<'a, T> { - pub fn to_longlive(mut self) -> ObjRef<'static, T> { - ObjRef { - inner: self.inner.take(), - cache: ObjCache(self.cache.0.clone()), - _life: PhantomData, - } + fn new(inner: Option>, cache: &'a ObjCache) -> Self { + Self { inner, cache } } #[inline] @@ -455,12 +449,10 @@ impl ObjCache { } #[inline(always)] - pub fn get<'a>( - &self, - inner: &mut ObjCacheInner, - ptr: DiskAddress, - ) -> Result>, ShaleError> { - if let Some(r) = inner.cached.pop(&ptr) { + fn get(&self, ptr: DiskAddress) -> Result>, ShaleError> { + let mut inner = self.0.write().unwrap(); + + let obj_ref = inner.cached.pop(&ptr).map(|r| { // insert and set to `false` if you can // When using `get` in parallel, one should not `write` to the same address inner @@ -469,6 +461,8 @@ impl ObjCache { .and_modify(|is_pinned| *is_pinned = false) .or_insert(false); + // if we need to re-enable this code, it has to return from the outer function + // // return if inner.pinned.insert(ptr, false).is_some() { // Err(ShaleError::InvalidObj { // addr: ptr.addr(), @@ -484,25 +478,17 @@ impl ObjCache { // }; // always return instead of the code above - return Ok(Some(ObjRef { - inner: Some(r), - cache: Self(self.0.clone()), - _life: PhantomData, - })); - } + r + }); - Ok(None) + Ok(obj_ref) } #[inline(always)] - pub fn put<'a>(&self, inner: Obj) -> ObjRef<'a, T> { + fn put(&self, inner: Obj) -> Obj { let ptr = inner.as_ptr(); self.lock().pinned.insert(ptr, false); - ObjRef { - inner: Some(inner), - cache: Self(self.0.clone()), - _life: PhantomData, - } + inner } #[inline(always)] From f5006a9eea1a4901d93ab106cab460020ccd3c55 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:46:26 -0700 Subject: [PATCH 0317/1053] chore: use `decode` in single key proof verification (#295) --- firewood/src/merkle/node.rs | 4 +++ firewood/src/proof.rs | 66 +++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 6c91741bec94..baf515b76147 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -310,6 +310,10 @@ impl ExtNode { self.1 } + pub fn chd_encoded(&self) -> Option<&[u8]> { + self.2.as_deref() + } + pub fn chd_mut(&mut self) -> &mut DiskAddress { &mut self.1 } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index f5f25ea24d9a..ef0ff47e9a2a 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -127,8 +127,8 @@ impl + Send> Proof { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; - let (sub_proof, traversed_nibbles) = - self.locate_subproof(key_nibbles, cur_proof.as_ref())?; + let node = NodeType::decode(cur_proof.as_ref())?; + let (sub_proof, traversed_nibbles) = self.locate_subproof(key_nibbles, node)?; key_nibbles = traversed_nibbles; cur_hash = match sub_proof { @@ -146,25 +146,11 @@ impl + Send> Proof { fn locate_subproof<'a>( &self, mut key_nibbles: NibblesIterator<'a, 0>, - encoded_node: &[u8], + node: NodeType, ) -> Result<(Option, NibblesIterator<'a, 0>), ProofError> { - let items: Vec>> = bincode::DefaultOptions::new() - .deserialize(encoded_node) - .map_err(ProofError::DecodeError)?; - - match items.len() { - EXT_NODE_SIZE => { - let mut items = items.into_iter(); - let decoded_key: Vec = items.next().unwrap().decode()?; - - let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); - - let (cur_key_path, term) = - PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); - let cur_key = cur_key_path.into_inner(); - - let data: Vec = items.next().unwrap().decode()?; - + match node { + NodeType::Leaf(n) => { + let cur_key = &n.path().0; // Check if the key of current node match with the given key // and consume the current-key portion of the nibbles-iterator let does_not_match = key_nibbles.size_hint().0 < cur_key.len() @@ -174,33 +160,41 @@ impl + Send> Proof { return Ok((None, Nibbles::<0>::new(&[]).into_iter())); } - let sub_proof = if term { - SubProof { - encoded: data, - hash: None, - } - } else { - self.generate_subproof(data)? + let encoded = n.data().to_vec(); + + let sub_proof = SubProof { + encoded, + hash: None, }; Ok((sub_proof.into(), key_nibbles)) } + NodeType::Extension(n) => { + let cur_key = &n.path().0; + // Check if the key of current node match with the given key + // and consume the current-key portion of the nibbles-iterator + let does_not_match = key_nibbles.size_hint().0 < cur_key.len() + || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); - BRANCH_NODE_SIZE if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), + if does_not_match { + return Ok((None, Nibbles::<0>::new(&[]).into_iter())); + } + let data = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); + let sub_proof = self.generate_subproof(data)?; - BRANCH_NODE_SIZE => { + Ok((sub_proof.into(), key_nibbles)) + } + NodeType::Branch(_) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), + NodeType::Branch(n) => { let index = key_nibbles.next().unwrap() as usize; - // consume items returning the item at index - let data: Vec = items.into_iter().nth(index).unwrap().decode()?; - + let data = n.chd_encode()[index] + .as_ref() + .ok_or(ProofError::InvalidData)? + .to_vec(); self.generate_subproof(data) .map(|subproof| (Some(subproof), key_nibbles)) } - - size => Err(ProofError::DecodeError(Box::new( - bincode::ErrorKind::Custom(format!("invalid size: {size}")), - ))), } } From a4aa277c3c2956af050166a82350d9c3a0c0496f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 29 Sep 2023 18:09:43 -0400 Subject: [PATCH 0318/1053] Move methods to functions that don't reference self (#296) --- firewood/src/proof.rs | 168 +++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index ef0ff47e9a2a..df30cfc11378 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -102,7 +102,7 @@ const BRANCH_NODE_SIZE: usize = 17; /// to a single proof step. If reaches an end step during proof verification, /// the hash value will be none, and the encoded value will be the value of the /// node. -pub struct SubProof { +struct SubProof { encoded: Vec, hash: Option<[u8; 32]>, } @@ -128,7 +128,7 @@ impl + Send> Proof { .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; let node = NodeType::decode(cur_proof.as_ref())?; - let (sub_proof, traversed_nibbles) = self.locate_subproof(key_nibbles, node)?; + let (sub_proof, traversed_nibbles) = locate_subproof(key_nibbles, node)?; key_nibbles = traversed_nibbles; cur_hash = match sub_proof { @@ -143,87 +143,6 @@ impl + Send> Proof { } } - fn locate_subproof<'a>( - &self, - mut key_nibbles: NibblesIterator<'a, 0>, - node: NodeType, - ) -> Result<(Option, NibblesIterator<'a, 0>), ProofError> { - match node { - NodeType::Leaf(n) => { - let cur_key = &n.path().0; - // Check if the key of current node match with the given key - // and consume the current-key portion of the nibbles-iterator - let does_not_match = key_nibbles.size_hint().0 < cur_key.len() - || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); - - if does_not_match { - return Ok((None, Nibbles::<0>::new(&[]).into_iter())); - } - - let encoded = n.data().to_vec(); - - let sub_proof = SubProof { - encoded, - hash: None, - }; - - Ok((sub_proof.into(), key_nibbles)) - } - NodeType::Extension(n) => { - let cur_key = &n.path().0; - // Check if the key of current node match with the given key - // and consume the current-key portion of the nibbles-iterator - let does_not_match = key_nibbles.size_hint().0 < cur_key.len() - || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); - - if does_not_match { - return Ok((None, Nibbles::<0>::new(&[]).into_iter())); - } - let data = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); - let sub_proof = self.generate_subproof(data)?; - - Ok((sub_proof.into(), key_nibbles)) - } - NodeType::Branch(_) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), - NodeType::Branch(n) => { - let index = key_nibbles.next().unwrap() as usize; - // consume items returning the item at index - let data = n.chd_encode()[index] - .as_ref() - .ok_or(ProofError::InvalidData)? - .to_vec(); - self.generate_subproof(data) - .map(|subproof| (Some(subproof), key_nibbles)) - } - } - } - - fn generate_subproof(&self, data: Vec) -> Result { - match data.len() { - 0..=31 => { - let sub_hash = sha3::Keccak256::digest(&data).into(); - Ok(SubProof { - encoded: data, - hash: Some(sub_hash), - }) - } - - 32 => { - let sub_hash: &[u8] = &data; - let sub_hash = sub_hash.try_into().unwrap(); - - Ok(SubProof { - encoded: data, - hash: Some(sub_hash), - }) - } - - len => Err(ProofError::DecodeError(Box::new( - bincode::ErrorKind::Custom(format!("invalid proof length: {len}")), - ))), - } - } - pub fn concat_proofs(&mut self, other: Proof) { self.0.extend(other.0) } @@ -569,7 +488,7 @@ impl + Send> Proof { hash: None, }) } else { - self.generate_subproof(data.clone()).map(Some)? + generate_subproof(data.clone()).map(Some)? }; let cur_key_len = cur_key.len(); @@ -615,7 +534,7 @@ impl + Send> Proof { }; let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; - let subproof = self.generate_subproof(data)?; + let subproof = generate_subproof(data)?; Ok((branch_ptr, Some(subproof), 1)) } @@ -627,6 +546,85 @@ impl + Send> Proof { } } +fn locate_subproof( + mut key_nibbles: NibblesIterator<'_, 0>, + node: NodeType, +) -> Result<(Option, NibblesIterator<'_, 0>), ProofError> { + match node { + NodeType::Leaf(n) => { + let cur_key = &n.path().0; + // Check if the key of current node match with the given key + // and consume the current-key portion of the nibbles-iterator + let does_not_match = key_nibbles.size_hint().0 < cur_key.len() + || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); + + if does_not_match { + return Ok((None, Nibbles::<0>::new(&[]).into_iter())); + } + + let encoded = n.data().to_vec(); + + let sub_proof = SubProof { + encoded, + hash: None, + }; + + Ok((sub_proof.into(), key_nibbles)) + } + NodeType::Extension(n) => { + let cur_key = &n.path().0; + // Check if the key of current node match with the given key + // and consume the current-key portion of the nibbles-iterator + let does_not_match = key_nibbles.size_hint().0 < cur_key.len() + || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); + + if does_not_match { + return Ok((None, Nibbles::<0>::new(&[]).into_iter())); + } + let data = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); + let sub_proof = generate_subproof(data)?; + + Ok((sub_proof.into(), key_nibbles)) + } + NodeType::Branch(_) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), + NodeType::Branch(n) => { + let index = key_nibbles.next().unwrap() as usize; + // consume items returning the item at index + let data = n.chd_encode()[index] + .as_ref() + .ok_or(ProofError::InvalidData)? + .to_vec(); + generate_subproof(data).map(|subproof| (Some(subproof), key_nibbles)) + } + } +} + +fn generate_subproof(data: Vec) -> Result { + match data.len() { + 0..=31 => { + let sub_hash = sha3::Keccak256::digest(&data).into(); + Ok(SubProof { + encoded: data, + hash: Some(sub_hash), + }) + } + + 32 => { + let sub_hash: &[u8] = &data; + let sub_hash = sub_hash.try_into().unwrap(); + + Ok(SubProof { + encoded: data, + hash: Some(sub_hash), + }) + } + + len => Err(ProofError::DecodeError(Box::new( + bincode::ErrorKind::Custom(format!("invalid proof length: {len}")), + ))), + } +} + struct CurKey(Vec); struct Data(Vec); From e518190ea0ebaec29072f4738201bc7f34f974f1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 30 Sep 2023 09:12:40 -0700 Subject: [PATCH 0319/1053] Remove proof feature from Cargo.toml (#300) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 5cd04eb9cf34..8507654174be 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -48,10 +48,6 @@ clap = { version = "4.3.1", features = ['derive'] } test-case = "3.1.0" pprof = { version = "0.13.0", features = ["flamegraph"] } -[features] -# proof API -proof = [] - [[bench]] name = "hashops" harness = false From 77cbfb35fa66611ed8f050724af59c3180a5cf24 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 2 Oct 2023 09:49:49 -0700 Subject: [PATCH 0320/1053] Renames for readability (#301) --- firewood/src/merkle/node.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index baf515b76147..0a024e85e9d3 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -133,7 +133,7 @@ impl BranchNode { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); - if c_ref.is_encoded_big::(store) { + if c_ref.is_encoded_longer_than_hash_len::(store) { list[i] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&&(*c_ref.get_root_hash::(store))[..]) @@ -267,7 +267,7 @@ impl ExtNode { if !self.1.is_null() { let mut r = store.get_item(self.1).unwrap(); - if r.is_encoded_big(store) { + if r.is_encoded_longer_than_hash_len(store) { list[1] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&&(*r.get_root_hash(store))[..]) @@ -326,7 +326,7 @@ impl ExtNode { #[derive(Debug)] pub struct Node { pub(super) root_hash: OnceLock, - is_encoded_big: OnceLock, + is_encoded_longer_than_hash_len: OnceLock, encoded: OnceLock>, // lazy_dirty is an atomicbool, but only writers ever set it // Therefore, we can always use Relaxed ordering. It's atomic @@ -340,13 +340,13 @@ impl PartialEq for Node { fn eq(&self, other: &Self) -> bool { let Node { root_hash, - is_encoded_big, + is_encoded_longer_than_hash_len, encoded, lazy_dirty, inner, } = self; *root_hash == other.root_hash - && *is_encoded_big == other.is_encoded_big + && *is_encoded_longer_than_hash_len == other.is_encoded_longer_than_hash_len && *encoded == other.encoded && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) && *inner == other.inner @@ -356,7 +356,7 @@ impl Clone for Node { fn clone(&self) -> Self { Self { root_hash: self.root_hash.clone(), - is_encoded_big: self.is_encoded_big.clone(), + is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), encoded: self.encoded.clone(), lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), inner: self.inner.clone(), @@ -424,7 +424,7 @@ impl Node { *max_size.get_or_init(|| { Self { root_hash: OnceLock::new(), - is_encoded_big: OnceLock::new(), + is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner: NodeType::Branch(BranchNode { chd: [Some(DiskAddress::null()); NBRANCH], @@ -448,8 +448,8 @@ impl Node { }) } - fn is_encoded_big>(&self, store: &S) -> bool { - *self.is_encoded_big.get_or_init(|| { + fn is_encoded_longer_than_hash_len>(&self, store: &S) -> bool { + *self.is_encoded_longer_than_hash_len.get_or_init(|| { self.lazy_dirty.store(true, Ordering::Relaxed); self.get_encoded(store).len() >= TRIE_HASH_LEN }) @@ -457,14 +457,14 @@ impl Node { pub(super) fn rehash(&mut self) { self.encoded = OnceLock::new(); - self.is_encoded_big = OnceLock::new(); + self.is_encoded_longer_than_hash_len = OnceLock::new(); self.root_hash = OnceLock::new(); } pub fn new(inner: NodeType) -> Self { let mut s = Self { root_hash: OnceLock::new(), - is_encoded_big: OnceLock::new(), + is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), @@ -483,7 +483,7 @@ impl Node { pub(super) fn new_from_hash( root_hash: Option, - encoded_big: Option, + encoded_longer_than_hash_len: Option, inner: NodeType, ) -> Self { Self { @@ -491,7 +491,7 @@ impl Node { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, - is_encoded_big: match encoded_big { + is_encoded_longer_than_hash_len: match encoded_longer_than_hash_len { Some(b) => OnceLock::from(b), None => OnceLock::new(), }, @@ -526,7 +526,7 @@ impl Storable for Node { .expect("invalid slice"), )) }; - let encoded_big = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { + let is_encoded_longer_than_hash_len = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { None } else { Some(attrs & Node::LONG_BIT != 0) @@ -599,7 +599,7 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, - encoded_big, + is_encoded_longer_than_hash_len, NodeType::Branch(BranchNode { chd, value, @@ -670,7 +670,7 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, - encoded_big, + is_encoded_longer_than_hash_len, NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), encoded)), )) } @@ -709,7 +709,7 @@ impl Storable for Node { let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); Ok(Self::new_from_hash( root_hash, - encoded_big, + is_encoded_longer_than_hash_len, NodeType::Leaf(LeafNode(path, value)), )) } @@ -763,7 +763,7 @@ impl Storable for Node { 0 } }; - attrs |= match self.is_encoded_big.get() { + attrs |= match self.is_encoded_longer_than_hash_len.get() { Some(b) => (if *b { Node::LONG_BIT } else { 0 } | Node::IS_ENCODED_BIG_VALID), None => 0, }; From 87f5599757bf6c596e9a8bb3a80fe2bdd53826ff Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 2 Oct 2023 13:14:06 -0400 Subject: [PATCH 0321/1053] Remove line about Docker in README (#302) --- README.md | 82 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index f68868705f13..f3a884401c71 100644 --- a/README.md +++ b/README.md @@ -40,113 +40,123 @@ versions with no additional cost at all. ## Terminology -* `Revision` - A historical point-in-time state/version of the trie. This - represents the entire trie, including all `Key`/`Value`s at that point - in time, and all `Node`s. -* `View` - This is the interface to read from a `Revision` or a `Proposal`. -* `Node` - A node is a portion of a trie. A trie consists of nodes that are linked +- `Revision` - A historical point-in-time state/version of the trie. This + represents the entire trie, including all `Key`/`Value`s at that point + in time, and all `Node`s. +- `View` - This is the interface to read from a `Revision` or a `Proposal`. +- `Node` - A node is a portion of a trie. A trie consists of nodes that are linked together. Nodes can point to other nodes and/or contain `Key`/`Value` pairs. -* `Hash` - In this context, this refers to the merkle hash for a specific node. -* `Root Hash` - The hash of the root node for a specific revision. -* `Key` - Represents an individual byte array used to index into a trie. A `Key` +- `Hash` - In this context, this refers to the merkle hash for a specific node. +- `Root Hash` - The hash of the root node for a specific revision. +- `Key` - Represents an individual byte array used to index into a trie. A `Key` usually has a specific `Value`. -* `Value` - Represents a byte array for the value of a specific `Key`. Values can +- `Value` - Represents a byte array for the value of a specific `Key`. Values can contain 0-N bytes. In particular, a zero-length `Value` is valid. -* `Key Proof` - A proof that a `Key` exists within a specific revision of a trie. +- `Key Proof` - A proof that a `Key` exists within a specific revision of a trie. This includes the hash for the node containing the `Key` as well as all parents. -* `Range Proof` - A proof that consists of two `Key Proof`s, one for the start of +- `Range Proof` - A proof that consists of two `Key Proof`s, one for the start of the range, and one for the end of the range, as well as a list of all `Key`/`Value` pairs in between the two. A `Range Proof` can be validated independently of an actual database by constructing a trie from the `Key`/`Value`s provided. -* `Change Proof` - A proof that consists of a set of all changes between two +- `Change Proof` - A proof that consists of a set of all changes between two revisions. -* `Put` - An operation for a `Key`/`Value` pair. A put means "create if it doesn't +- `Put` - An operation for a `Key`/`Value` pair. A put means "create if it doesn't exist, or update it if it does. A put operation is how you add a `Value` for a specific `Key`. -* `Delete` - An operation indicating that a `Key` that should be removed from the trie. -* `Batch Operation` - An operation of either `Put` or `Delete`. -* `Batch` - An ordered set of `Batch Operation`s. -* `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not +- `Delete` - An operation indicating that a `Key` that should be removed from the trie. +- `Batch Operation` - An operation of either `Put` or `Delete`. +- `Batch` - An ordered set of `Batch Operation`s. +- `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not yet committed to the trie. In Firewood's most recent API, a `Proposal` is required to `Commit`. -* `Commit` - The operation of applying one or more `Proposal`s to the most recent +- `Commit` - The operation of applying one or more `Proposal`s to the most recent `Revision`. ## Roadmap **LEGEND** + - [ ] Not started - [ ] :runner: In progress - [x] Complete ### Green Milestone + This milestone will focus on additional code cleanup, including supporting concurrent access to a specific revision, as well as cleaning up the basic reader and writer interfaces to have consistent read/write semantics. + - [x] Concurrent readers of pinned revisions while allowing additional batches -to commit, to support parallel reads for the past consistent states. The revisions -are uniquely identified by root hashes. + to commit, to support parallel reads for the past consistent states. The revisions + are uniquely identified by root hashes. - [x] Pin a reader to a specific revision, so that future commits or other -operations do not see any changes. + operations do not see any changes. - [x] Be able to read-your-write in a batch that is not committed. Uncommitted -changes will not be shown to any other concurrent readers. + changes will not be shown to any other concurrent readers. - [x] Add some metrics framework to support timings and volume for future milestones -To support this, a new method Db::metrics() returns an object that can be serialized -into prometheus metrics or json (it implements [serde::Serialize]) + To support this, a new method Db::metrics() returns an object that can be serialized + into prometheus metrics or json (it implements [serde::Serialize]) ### Seasoned milestone + This milestone will add support for proposals, including proposed future branches, with a cache to make committing these branches efficiently. + - [x] Be able to support multiple proposed revisions against latest committed -version. + version. - [x] Be able to propose a batch against the existing committed revision, or -propose a batch against any existing proposed revision. + propose a batch against any existing proposed revision. - [x] Commit a batch that has been proposed will invalidate all other proposals -that are not children of the committed proposed batch. + that are not children of the committed proposed batch. - [x] Be able to quickly commit a batch that has been proposed. - [x] Remove RLP encoding ### Dried milestone + The focus of this milestone will be to support synchronization to other instances to replicate the state. A synchronization library should also be developed for this milestone. + - [ ] :runner: Add support for Ava Labs generic test tool via grpc client - [ ] :runner: Pluggable encoding for nodes, for optional compatibility with MerkleDB - [ ] Support replicating the full state with corresponding range proofs that -verify the correctness of the data. + verify the correctness of the data. - [ ] Support replicating the delta state from the last sync point with -corresponding range proofs that verify the correctness of the data. + corresponding range proofs that verify the correctness of the data. - [ ] Enforce limits on the size of the range proof as well as keys to make - synchronization easier for clients. + synchronization easier for clients. - [ ] MerkleDB root hash in parity for seamless transition between MerkleDB -and Firewood. + and Firewood. - [ ] Add metric reporting -- [ ] Migrate to a fully async interface, consider tokio\_uring, monoio, etc +- [ ] Migrate to a fully async interface, consider tokio_uring, monoio, etc - [ ] Refactor `Shale` to be more idiomatic, consider rearchitecting it ## Build + Firewood currently is Linux-only, as it has a dependency on the asynchronous -I/O provided by the Linux kernel (see `libaio`). Unfortunately, Docker is not -able to successfully emulate the syscalls `libaio` relies on, so Linux or a -Linux VM must be used to run Firewood. We intend to migrate to io\_uring which -should allow for this emulation. +I/O provided by the Linux kernel (see `libaio`). ## Run + There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. ## Release + See the [release documentation](./RELEASE.md) for detailed information on how to release Firewood. ## CLI + Firewood comes with a CLI tool called `fwdctl` that enables one to create and interact with a local instance of a Firewood database. For more information, see the [fwdctl README](fwdctl/README.md). ## Test + ``` cargo test --release ``` ## License + Firewood is licensed by the Ecosystem License. For more information, see the [LICENSE file](./LICENSE.md). From 772035e2350b26f23e3384a1aad2714fc3b162eb Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:22:30 -0700 Subject: [PATCH 0322/1053] chore: use `decode` in range proof verification (#303) --- firewood/src/merkle.rs | 1 - firewood/src/proof.rs | 153 +++++++++-------------------------------- 2 files changed, 33 insertions(+), 121 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4c7c73df7244..77b7b43b0564 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -15,7 +15,6 @@ mod node; mod partial_path; mod trie_hash; -pub(crate) use node::Encoded; pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; pub use partial_path::PartialPath; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index df30cfc11378..5473ed728592 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -4,7 +4,6 @@ use std::cmp::Ordering; use std::ops::Deref; -use bincode::Options; use nix::errno::Errno; use sha3::Digest; use shale::disk_address::DiskAddress; @@ -12,15 +11,11 @@ use shale::ShaleError; use shale::ShaleStore; use thiserror::Error; -use crate::merkle::Encoded; use crate::nibbles::Nibbles; use crate::nibbles::NibblesIterator; use crate::{ db::DbError, - merkle::{ - to_nibble_array, BranchNode, ExtNode, LeafNode, Merkle, MerkleError, Node, NodeType, - PartialPath, NBRANCH, - }, + merkle::{to_nibble_array, Merkle, MerkleError, Node, NodeType}, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, v2::api::Proof, }; @@ -95,9 +90,6 @@ impl From for ProofError { } } -const EXT_NODE_SIZE: usize = 2; -const BRANCH_NODE_SIZE: usize = 17; - /// SubProof contains the encoded value and the hash value of a node that maps /// to a single proof step. If reaches an end step during proof verification, /// the hash value will be none, and the encoded value will be the value of the @@ -455,93 +447,50 @@ impl + Send> Proof { buf: &[u8], end_node: bool, ) -> Result<(DiskAddress, Option, usize), ProofError> { - let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; - let size = items.len(); - - match size { - EXT_NODE_SIZE => { - let mut items = items.into_iter(); - - let cur_key_path: Vec = items - .next() - .unwrap() - .decode()? - .into_iter() - .flat_map(to_nibble_array) - .collect(); - - let (cur_key_path, term) = PartialPath::decode(&cur_key_path); - let cur_key = cur_key_path.into_inner(); - - let data: Vec = items.next().unwrap().decode()?; - + let node = NodeType::decode(buf)?; + let new_node = merkle + .new_node(Node::new(node)) + .map_err(ProofError::InvalidNode)?; + let addr = new_node.as_ptr(); + match new_node.inner() { + NodeType::Leaf(n) => { + let cur_key = &n.path().0; // Check if the key of current node match with the given key. - if key.len() < cur_key.len() || key[..cur_key.len()] != cur_key { - let ext_ptr = get_ext_ptr(merkle, term, Data(data), CurKey(cur_key))?; - - return Ok((ext_ptr, None, 0)); + if key.len() < cur_key.len() || &key[..cur_key.len()] != cur_key { + return Ok((addr, None, 0)); } - let subproof = if term { - Some(SubProof { - encoded: data.clone(), - hash: None, - }) - } else { - generate_subproof(data.clone()).map(Some)? - }; - - let cur_key_len = cur_key.len(); - - let ext_ptr = get_ext_ptr(merkle, term, Data(data), CurKey(cur_key))?; - - Ok((ext_ptr, subproof, cur_key_len)) + let subproof = Some(SubProof { + encoded: n.data().to_vec(), + hash: None, + }); + Ok((addr, subproof, cur_key.len())) } + NodeType::Extension(n) => { + let cur_key = &n.path().0; - BRANCH_NODE_SIZE => { - // we've already validated the size, that's why we can safely unwrap - let data = items.pop().unwrap().decode()?; - // Extract the value of the branch node and set to None if it's an empty Vec - let value = Some(data).filter(|data| !data.is_empty()); - - // Record encoded values of all children. - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); - - // we popped the last element, so their should only be NBRANCH items left - for (i, chd) in items.into_iter().enumerate() { - let data = chd.decode()?; - chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); - } - - // If the node is the last one to be decoded, then no subproof to be extracted. - if end_node { - let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; - - return Ok((branch_ptr, None, 1)); + // Check if the key of current node match with the given key. + if key.len() < cur_key.len() || &key[..cur_key.len()] != cur_key { + return Ok((addr, None, 0)); } - if key.is_empty() { - return Err(ProofError::NoSuchNode); - } + let encoded = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); + let subproof = generate_subproof(encoded).map(Some)?; + Ok((addr, subproof, cur_key.len())) + } + // If the node is the last one to be decoded, then no subproof to be extracted. + NodeType::Branch(_) if end_node => Ok((addr, None, 1)), + NodeType::Branch(_) if key.is_empty() => Err(ProofError::NoSuchNode), + NodeType::Branch(n) => { // Check if the subproof with the given key exist. let index = key[0] as usize; - - let Some(data) = chd_encoded[index].clone() else { - let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; - - return Ok((branch_ptr, None, 1)); + let Some(data) = &n.chd_encode()[index] else { + return Ok((addr, None, 1)); }; - - let branch_ptr = build_branch_ptr(merkle, value, chd_encoded)?; - let subproof = generate_subproof(data)?; - - Ok((branch_ptr, Some(subproof), 1)) + let subproof = generate_subproof(data.to_vec())?; + Ok((addr, Some(subproof), 1)) } - - _ => Err(ProofError::DecodeError(Box::new( - bincode::ErrorKind::Custom(String::from("")), - ))), } } } @@ -625,42 +574,6 @@ fn generate_subproof(data: Vec) -> Result { } } -struct CurKey(Vec); -struct Data(Vec); - -fn get_ext_ptr + Send + Sync>( - merkle: &Merkle, - term: bool, - Data(data): Data, - CurKey(cur_key): CurKey, -) -> Result { - let node = if term { - NodeType::Leaf(LeafNode::new(cur_key, data)) - } else { - NodeType::Extension(ExtNode::new(cur_key, DiskAddress::null(), Some(data))) - }; - - merkle - .new_node(Node::new(node)) - .map(|node| node.as_ptr()) - .map_err(ProofError::InvalidNode) -} - -fn build_branch_ptr + Send + Sync>( - merkle: &Merkle, - value: Option>, - chd_encoded: [Option>; NBRANCH], -) -> Result { - let node = BranchNode::new([None; NBRANCH], value, chd_encoded); - let node = NodeType::Branch(node); - let node = Node::new(node); - - merkle - .new_node(node) - .map_err(|_| ProofError::ProofNodeMissing) - .map(|node| node.as_ptr()) -} - // unset_internal removes all internal node references. // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. From f1b351e633bc114abb130e2629393fbffa564877 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:35:21 -0400 Subject: [PATCH 0323/1053] build(deps): update lru requirement from 0.11.0 to 0.12.0 (#306) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- growth-ring/Cargo.toml | 2 +- shale/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8507654174be..00fed7e68294 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -25,7 +25,7 @@ libaio = {version = "0.0.4", path = "../libaio" } shale = { version = "0.0.4", path = "../shale" } futures = "0.3.24" hex = "0.4.3" -lru = "0.11.0" +lru = "0.12.0" metered = "0.9.0" nix = {version = "0.27.1", features = ["fs", "uio"]} parking_lot = "0.12.1" diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 05c3f159416c..1ba541bcf5e7 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -10,7 +10,7 @@ description = "Simple and modular write-ahead-logging implementation." [dependencies] crc = "3.0.0" -lru = "0.11.0" +lru = "0.12.0" scan_fmt = "0.2.6" regex = "1.6.0" async-trait = "0.1.57" diff --git a/shale/Cargo.toml b/shale/Cargo.toml index f427370f7291..33f0bda4e53a 100644 --- a/shale/Cargo.toml +++ b/shale/Cargo.toml @@ -9,7 +9,7 @@ license = "../LICENSE.md" [dependencies] hex = "0.4.3" -lru = "0.11.0" +lru = "0.12.0" thiserror = "1.0.38" bytemuck = { version = "1.13.1", features = ["derive"] } From a4c94af0ac1f149ed628b49b444133026d43a9db Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Oct 2023 10:15:35 -0700 Subject: [PATCH 0324/1053] Switch to unbounded channel (#304) --- firewood/src/db.rs | 2 +- firewood/src/storage/buffer.rs | 31 ++++++++++++++----------------- fwdctl/src/create.rs | 11 ----------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ea59c503e52c..bf813f802144 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -479,7 +479,7 @@ impl Db { .block_nbit(params.wal_block_nbit) .max_revisions(cfg.wal.max_revisions) .build(); - let (sender, inbound) = tokio::sync::mpsc::channel(cfg.buffer.max_buffered); + let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); let disk_requester = DiskBufferRequester::new(sender); let buffer = cfg.buffer.clone(); let disk_thread = Some(std::thread::spawn(move || { diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 99f703c5409e..9951a4aa92a9 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -48,9 +48,6 @@ pub enum BufferCmd { /// Config for the disk buffer. #[derive(TypedBuilder, Clone, Debug)] pub struct DiskBufferConfig { - /// Maximum buffered disk buffer commands. - #[builder(default = 4096)] - pub max_buffered: usize, /// Maximum number of pending pages. #[builder(default = 65536)] // 256MB total size by default pub max_pending: usize, @@ -111,7 +108,7 @@ impl Notifiers { /// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] /// and managing the persistence of pages. pub struct DiskBuffer { - inbound: mpsc::Receiver, + inbound: mpsc::UnboundedReceiver, aiomgr: AioManager, cfg: DiskBufferConfig, wal_cfg: WalConfig, @@ -120,7 +117,7 @@ pub struct DiskBuffer { impl DiskBuffer { /// Create a new aio managed disk buffer. pub fn new( - inbound: mpsc::Receiver, + inbound: mpsc::UnboundedReceiver, cfg: &DiskBufferConfig, wal: &WalConfig, ) -> Result { @@ -556,12 +553,12 @@ async fn process( /// ``` #[derive(Clone, Debug)] pub struct DiskBufferRequester { - sender: mpsc::Sender, + sender: mpsc::UnboundedSender, } impl DiskBufferRequester { /// Create a new requester. - pub fn new(sender: mpsc::Sender) -> Self { + pub fn new(sender: mpsc::UnboundedSender) -> Self { Self { sender } } @@ -569,7 +566,7 @@ impl DiskBufferRequester { pub fn get_page(&self, space_id: SpaceId, page_id: u64) -> Option { let (resp_tx, resp_rx) = oneshot::channel(); self.sender - .blocking_send(BufferCmd::GetPage((space_id, page_id), resp_tx)) + .send(BufferCmd::GetPage((space_id, page_id), resp_tx)) .map_err(StoreError::Send) .ok(); resp_rx.blocking_recv().unwrap() @@ -578,19 +575,19 @@ impl DiskBufferRequester { /// Sends a batch of writes to the buffer. pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { self.sender - .blocking_send(BufferCmd::WriteBatch(page_batch, write_batch)) + .send(BufferCmd::WriteBatch(page_batch, write_batch)) .map_err(StoreError::Send) .ok(); } pub fn shutdown(&self) { - self.sender.blocking_send(BufferCmd::Shutdown).ok().unwrap() + self.sender.send(BufferCmd::Shutdown).ok().unwrap() } /// Initialize the Wal. pub fn init_wal(&self, waldir: &str, rootpath: &Path) { self.sender - .blocking_send(BufferCmd::InitWal( + .send(BufferCmd::InitWal( rootpath.to_path_buf(), waldir.to_string(), )) @@ -602,7 +599,7 @@ impl DiskBufferRequester { pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { let (resp_tx, resp_rx) = oneshot::channel(); self.sender - .blocking_send(BufferCmd::CollectAsh(nrecords, resp_tx)) + .send(BufferCmd::CollectAsh(nrecords, resp_tx)) .map_err(StoreError::Send) .ok(); resp_rx.blocking_recv().map_err(StoreError::Receive) @@ -611,7 +608,7 @@ impl DiskBufferRequester { /// Register a cached space to the buffer. pub fn reg_cached_space(&self, space_id: SpaceId, files: Arc) { self.sender - .blocking_send(BufferCmd::RegCachedSpace(space_id, files)) + .send(BufferCmd::RegCachedSpace(space_id, files)) .map_err(StoreError::Send) .ok(); } @@ -665,7 +662,7 @@ mod tests { fn test_buffer_with_undo() { let temp_dir = get_tmp_dir(); - let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let buf_cfg = DiskBufferConfig::builder().build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); @@ -740,7 +737,7 @@ mod tests { #[test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] fn test_buffer_with_redo() { - let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let buf_cfg = DiskBufferConfig::builder().build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); @@ -813,7 +810,7 @@ mod tests { #[test] fn test_multi_stores() { - let buf_cfg = DiskBufferConfig::builder().max_buffered(1).build(); + let buf_cfg = DiskBufferConfig::builder().build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); @@ -915,7 +912,7 @@ mod tests { } fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WalConfig) -> DiskBufferRequester { - let (sender, inbound) = tokio::sync::mpsc::channel(buf_cfg.max_buffered); + let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); let disk_requester = DiskBufferRequester::new(sender); std::thread::spawn(move || { let disk_buffer = DiskBuffer::new(inbound, &buf_cfg, &wal_cfg).unwrap(); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 1ae83c3b792b..10bb562d585d 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -150,16 +150,6 @@ pub struct Options { )] blob_ncached_objs: usize, - /// Disk Buffer options - #[arg( - long, - required = false, - default_value_t = 4096, - value_name = "DISK_BUFFER_MAX_BUFFERED", - help = "Maximum buffered disk buffer." - )] - max_buffered: usize, - #[arg( long, required = false, @@ -271,7 +261,6 @@ pub fn initialize_db_config(opts: &Options) -> DbConfig { merkle_ncached_objs: opts.merkle_ncached_objs, }, buffer: DiskBufferConfig { - max_buffered: opts.max_buffered, max_pending: opts.max_pending, max_aio_requests: opts.max_aio_requests, max_aio_response: opts.max_aio_response, From 63dadbca7394d6400ccde03dfb05eaa871545163 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:27:03 -0700 Subject: [PATCH 0325/1053] chore: naming the elements of `ExtNode` (#305) Co-authored-by: Richard Pringle --- firewood/src/merkle.rs | 133 ++++++++++++++++++------------------ firewood/src/merkle/node.rs | 61 ++++++++++------- 2 files changed, 105 insertions(+), 89 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 77b7b43b0564..31b518b03d15 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -140,7 +140,7 @@ impl + Send + Sync> Merkle { NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), NodeType::Extension(n) => { writeln!(w, "{n:?}")?; - self.dump_(n.1, w)? + self.dump_(n.chd(), w)? } } Ok(()) @@ -161,7 +161,7 @@ impl + Send + Sync> Merkle { .write(|p| { match &mut p.inner { NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd), - NodeType::Extension(pp) => pp.1 = new_chd, + NodeType::Extension(pp) => *pp.chd_mut() = new_chd, _ => unreachable!(), } p.rehash(); @@ -191,7 +191,7 @@ impl + Send + Sync> Merkle { .write(|u| { (*match &mut u.inner { NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => &mut u.0, + NodeType::Extension(u) => u.path_mut(), _ => unreachable!(), }) = PartialPath(n_path[idx + 1..].to_vec()); u.rehash(); @@ -207,9 +207,9 @@ impl + Send + Sync> Merkle { chd[rem_path[idx] as usize] = Some(leaf_ptr); chd[n_path[idx] as usize] = Some(match &u_ref.inner { NodeType::Extension(u) => { - if u.0.len() == 0 { + if u.path().len() == 0 { deleted.push(u_ptr); - u.1 + u.chd() } else { u_ptr } @@ -224,8 +224,8 @@ impl + Send + Sync> Merkle { }); let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); if idx > 0 { - self.new_node(Node::new(NodeType::Extension(ExtNode( - PartialPath(rem_path[..idx].to_vec()), + self.new_node(Node::new(NodeType::Extension(ExtNode::new( + rem_path[..idx].to_vec(), branch_ptr, None, ))))? @@ -245,21 +245,23 @@ impl + Send + Sync> Merkle { match &mut u.inner { NodeType::Leaf(u) => u.1 = Data(val), NodeType::Extension(u) => { - let write_result = self.get_node(u.1).and_then(|mut b_ref| { - let write_result = b_ref.write(|b| { - b.inner.as_branch_mut().unwrap().value = - Some(Data(val)); - b.rehash() + let write_result = + self.get_node(u.chd()).and_then(|mut b_ref| { + let write_result = b_ref.write(|b| { + b.inner.as_branch_mut().unwrap().value = + Some(Data(val)); + b.rehash() + }); + + if write_result.is_err() { + *u.chd_mut() = + self.new_node(b_ref.clone())?.as_ptr(); + deleted.push(b_ref.as_ptr()); + } + + Ok(()) }); - if write_result.is_err() { - u.1 = self.new_node(b_ref.clone())?.as_ptr(); - deleted.push(b_ref.as_ptr()); - } - - Ok(()) - }); - if let Err(e) = write_result { result = Err(e); } @@ -281,7 +283,7 @@ impl + Send + Sync> Merkle { .write(|u| { (*match &mut u.inner { NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => &mut u.0, + NodeType::Extension(u) => u.path_mut(), _ => unreachable!(), }) = PartialPath(n_path[rem_path.len() + 1..].to_vec()); u.rehash(); @@ -290,9 +292,9 @@ impl + Send + Sync> Merkle { ( match &u_ref.inner { NodeType::Extension(u) => { - if u.0.len() == 0 { + if u.path().len() == 0 { deleted.push(u_ptr); - u.1 + u.chd() } else { u_ptr } @@ -328,8 +330,8 @@ impl + Send + Sync> Merkle { })))? .as_ptr(); if !prefix.is_empty() { - self.new_node(Node::new(NodeType::Extension(ExtNode( - PartialPath(prefix.to_vec()), + self.new_node(Node::new(NodeType::Extension(ExtNode::new( + prefix.to_vec(), branch_ptr, None, ))))? @@ -430,8 +432,8 @@ impl + Send + Sync> Merkle { break; } NodeType::Extension(n) => { - let n_path = n.0.to_vec(); - let n_ptr = n.1; + let n_path = n.path().to_vec(); + let n_ptr = n.chd(); nskip = n_path.len() - 1; let rem_path = key_nibbles .into_iter() @@ -490,14 +492,14 @@ impl + Send + Sync> Merkle { } } NodeType::Extension(n) => { - let idx = n.0[0]; - let more = if n.0.len() > 1 { - n.0 = PartialPath(n.0[1..].to_vec()); + let idx = n.path()[0]; + let more = if n.path().len() > 1 { + *n.path_mut() = PartialPath(n.path()[1..].to_vec()); true } else { false }; - Some((idx, more, Some(n.1))) + Some((idx, more, Some(n.chd()))) } }; u.rehash() @@ -587,7 +589,7 @@ impl + Send + Sync> Merkle { // to: P -> [Leaf (v)] let leaf = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(n.0.clone().into_inner()), + PartialPath(n.path().clone().into_inner()), val, ))))? .as_ptr(); @@ -610,8 +612,8 @@ impl + Send + Sync> Merkle { // \____[Leaf]x // to: [p: Branch] -> [Ext] -> [Branch] let ext = self - .new_node(Node::new(NodeType::Extension(ExtNode( - PartialPath(vec![idx]), + .new_node(Node::new(NodeType::Extension(ExtNode::new( + vec![idx], c_ptr, None, ))))? @@ -629,8 +631,8 @@ impl + Send + Sync> Merkle { p_ref, |p| { let pp = p.inner.as_extension_mut().unwrap(); - pp.0 .0.push(idx); - pp.1 = c_ptr; + pp.path_mut().0.push(idx); + *pp.chd_mut() = c_ptr; p.rehash(); }, parents, @@ -651,7 +653,7 @@ impl + Send + Sync> Merkle { let write_result = c_ref.write(|c| { let partial_path = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, + NodeType::Extension(n) => n.path_mut(), _ => unreachable!(), }; @@ -688,11 +690,11 @@ impl + Send + Sync> Merkle { self, c_ref, |c| { - let mut path = n.0.clone().into_inner(); + let mut path = n.path().clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, + NodeType::Extension(n) => n.path_mut(), _ => unreachable!(), }; path.extend(&**path0); @@ -738,19 +740,18 @@ impl + Send + Sync> Merkle { NodeType::Branch(n) => { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] - n.chd[b_idx as usize] = - Some( - self.new_node(Node::new(NodeType::Extension( - ExtNode(PartialPath(vec![idx]), c_ptr, None), - )))? - .as_ptr(), - ); + n.chd[b_idx as usize] = Some( + self.new_node(Node::new(NodeType::Extension( + ExtNode::new(vec![idx], c_ptr, None), + )))? + .as_ptr(), + ); } NodeType::Extension(n) => { // from: [Ext] -> [Branch]x -> [Branch] // to: [Ext] -> [Branch] - n.0 .0.push(idx); - n.1 = c_ptr + n.path_mut().0.push(idx); + *n.chd_mut() = c_ptr } _ => unreachable!(), } @@ -774,7 +775,7 @@ impl + Send + Sync> Merkle { let write_result = c_ref.write(|c| { match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, + NodeType::Extension(n) => n.path_mut(), _ => unreachable!(), } .0 @@ -799,11 +800,11 @@ impl + Send + Sync> Merkle { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] let write_result = c_ref.write(|c| { - let mut path = n.0.clone().into_inner(); + let mut path = n.path().clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => &mut n.0, + NodeType::Extension(n) => n.path_mut(), _ => unreachable!(), }; path.extend(&**path0); @@ -866,13 +867,13 @@ impl + Send + Sync> Merkle { break; } NodeType::Extension(n) => { - let n_path = &*n.0; + let n_path = &*n.path().0; let rem_path = &chunks[i..]; if rem_path < n_path || &rem_path[..n_path.len()] != n_path { return Ok(None); } nskip = n_path.len() - 1; - n.1 + n.chd() } }; @@ -934,7 +935,7 @@ impl + Send + Sync> Merkle { } } NodeType::Leaf(_) => (), - NodeType::Extension(n) => self.remove_tree_(n.1, deleted)?, + NodeType::Extension(n) => self.remove_tree_(n.chd(), deleted)?, } deleted.push(u); Ok(()) @@ -987,13 +988,13 @@ impl + Send + Sync> Merkle { return Ok(Some(RefMut::new(u_ptr, parents, self))); } NodeType::Extension(n) => { - let n_path = &*n.0; + let n_path = &*n.path().0; let rem_path = &chunks[i..]; if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { return Ok(None); } nskip = n_path.len() - 1; - n.1 + n.chd() } }; parents.push((u_ptr, *nib)); @@ -1067,7 +1068,7 @@ impl + Send + Sync> Merkle { NodeType::Extension(n) => { // the key passed in must match the entire remainder of this // extension node, otherwise we break out - let n_path = &*n.0; + let n_path = n.path(); let remaining_path = key_nibbles.into_iter().skip(i); if remaining_path.size_hint().0 < n_path.len() { // all bytes aren't there @@ -1078,7 +1079,7 @@ impl + Send + Sync> Merkle { break; } nskip = n_path.len() - 1; - n.1 + n.chd() } }; u_ref = self.get_node(next_ptr)?; @@ -1140,7 +1141,7 @@ impl + Send + Sync> Merkle { return Ok(Some(Ref(u_ref))); } NodeType::Extension(n) => { - let n_path = &*n.0; + let n_path = n.path(); let rem_path = key_nibbles.into_iter().skip(i); if rem_path.size_hint().0 < n_path.len() { return Ok(None); @@ -1149,7 +1150,7 @@ impl + Send + Sync> Merkle { return Ok(None); } nskip = n_path.len() - 1; - n.1 + n.chd() } }; u_ref = self.get_node(next_ptr)?; @@ -1345,8 +1346,8 @@ mod test { Node::new_from_hash( None, None, - NodeType::Extension(ExtNode( - PartialPath(vec![0x1, 0x2, 0x3]), + NodeType::Extension(ExtNode::new( + vec![0x1, 0x2, 0x3], DiskAddress::from(0x42), None, )), @@ -1354,8 +1355,8 @@ mod test { Node::new_from_hash( None, None, - NodeType::Extension(ExtNode( - PartialPath(vec![0x1, 0x2, 0x3]), + NodeType::Extension(ExtNode::new( + vec![0x1, 0x2, 0x3], DiskAddress::null(), Some(vec![0x1, 0x2, 0x3]), )), @@ -1452,8 +1453,8 @@ mod test { let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); assert_eq!(chd_encoded, new_chd_encoded); - let node = Node::new(NodeType::Extension(ExtNode( - PartialPath(vec![0x1, 0x2, 0x3]), + let node = Node::new(NodeType::Extension(ExtNode::new( + vec![0x1, 0x2, 0x3], DiskAddress::null(), Some(chd_encoded.to_vec()), ))); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 0a024e85e9d3..472fd48b0bf8 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -243,15 +243,19 @@ impl LeafNode { } #[derive(PartialEq, Eq, Clone)] -pub struct ExtNode( - pub(super) PartialPath, - pub(super) DiskAddress, - pub(super) Option>, -); +pub struct ExtNode { + path: PartialPath, + chd: DiskAddress, + chd_encoded: Option>, +} impl Debug for ExtNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Extension {:?} {:?} {:?}]", self.0, self.1, self.2) + write!( + f, + "[Extension {:?} {:?} {:?}]", + self.path, self.chd, self.chd_encoded + ) } } @@ -260,12 +264,12 @@ impl ExtNode { let mut list = <[Encoded>; 2]>::default(); list[0] = Encoded::Data( bincode::DefaultOptions::new() - .serialize(&from_nibbles(&self.0.encode(false)).collect::>()) + .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) .unwrap(), ); - if !self.1.is_null() { - let mut r = store.get_item(self.1).unwrap(); + if !self.chd.is_null() { + let mut r = store.get_item(self.chd).unwrap(); if r.is_encoded_longer_than_hash_len(store) { list[1] = Encoded::Data( @@ -284,7 +288,7 @@ impl ExtNode { } else { // Check if there is already a caclucated encoded value for the child, which // can happen when manually constructing a trie from proof. - if let Some(v) = &self.2 { + if let Some(v) = &self.chd_encoded { if v.len() == TRIE_HASH_LEN { list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); } else { @@ -299,27 +303,35 @@ impl ExtNode { } pub fn new(path: Vec, chd: DiskAddress, chd_encoded: Option>) -> Self { - ExtNode(PartialPath(path), chd, chd_encoded) + ExtNode { + path: PartialPath(path), + chd, + chd_encoded, + } } pub fn path(&self) -> &PartialPath { - &self.0 + &self.path + } + + pub fn path_mut(&mut self) -> &mut PartialPath { + &mut self.path } pub fn chd(&self) -> DiskAddress { - self.1 + self.chd } pub fn chd_encoded(&self) -> Option<&[u8]> { - self.2.as_deref() + self.chd_encoded.as_deref() } pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.1 + &mut self.chd } pub fn chd_encoded_mut(&mut self) -> &mut Option> { - &mut self.2 + &mut self.chd_encoded } } @@ -671,7 +683,11 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, - NodeType::Extension(ExtNode(path, DiskAddress::from(ptr as usize), encoded)), + NodeType::Extension(ExtNode { + path, + chd: DiskAddress::from(ptr as usize), + chd_encoded: encoded, + }), )) } Self::LEAF_NODE => { @@ -739,8 +755,8 @@ impl Storable for Node { } NodeType::Extension(n) => { 1 + 8 - + n.0.dehydrated_len() - + match &n.2 { + + n.path.dehydrated_len() + + match n.chd_encoded() { Some(v) => 1 + v.len() as u64, None => 1, } @@ -802,12 +818,11 @@ impl Storable for Node { } NodeType::Extension(n) => { cur.write_all(&[Self::EXT_NODE])?; - let path: Vec = from_nibbles(&n.0.encode(false)).collect(); + let path: Vec = from_nibbles(&n.path.encode(false)).collect(); cur.write_all(&[path.len() as u8])?; - cur.write_all(&n.1.to_le_bytes())?; + cur.write_all(&n.chd.to_le_bytes())?; cur.write_all(&path)?; - if n.2.is_some() { - let encoded = n.2.as_ref().unwrap(); + if let Some(encoded) = n.chd_encoded() { cur.write_all(&[encoded.len() as u8])?; cur.write_all(encoded)?; } From 655a7917f7c2490e3bf8fdb0434efeaf2721a956 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 5 Oct 2023 17:14:40 -0400 Subject: [PATCH 0326/1053] Rely on mutable iterators instead of offsets (#307) --- firewood/src/merkle.rs | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 31b518b03d15..5dd58df69b3a 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -7,6 +7,7 @@ use shale::{disk_address::DiskAddress, ObjRef, ShaleError, ShaleStore}; use std::{ collections::HashMap, io::Write, + iter::once, sync::{atomic::Ordering, OnceLock}, }; use thiserror::Error; @@ -53,7 +54,7 @@ pub struct Merkle { store: Box, } -impl + Send + Sync> Merkle { +impl> Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result, MerkleError> { self.store.get_item(ptr).map_err(Into::into) } @@ -358,11 +359,6 @@ impl + Send + Sync> Merkle { let mut deleted = Vec::new(); let mut parents = Vec::new(); - // we use Nibbles::<1> so that 1 zero nibble is at the front - // this is for the sentinel node, which avoids moving the root - // and always only has one child - let key_nibbles = Nibbles::<1>::new(key.as_ref()); - let mut next_node = Some(self.get_node(root)?); let mut nskip = 0; @@ -371,13 +367,23 @@ impl + Send + Sync> Merkle { // have to do some splitting let mut val = Some(val); + // we use Nibbles::<1> so that 1 zero nibble is at the front + // this is for the sentinel node, which avoids moving the root + // and always only has one child + let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); + // walk down the merkle tree starting from next_node, currently the root - for (key_nib_offset, key_nib) in key_nibbles.into_iter().enumerate() { + loop { + let Some(key_nib) = key_nibbles.next() else { + break; + }; + // special handling for extension nodes if nskip > 0 { nskip -= 1; continue; } + // move the current node into node; next_node becomes None // unwrap() is okay here since we are certain we have something // in next_node at this point @@ -395,9 +401,7 @@ impl + Send + Sync> Merkle { // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath( - key_nibbles.into_iter().skip(key_nib_offset + 1).collect(), - ), + PartialPath(key_nibbles.collect()), Data(val.take().unwrap()), ))))? .as_ptr(); @@ -408,18 +412,17 @@ impl + Send + Sync> Merkle { u.rehash(); }) .unwrap(); + break; } }, + NodeType::Leaf(n) => { // we collided with another key; make a copy // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); - let rem_path = key_nibbles - .into_iter() - .skip(key_nib_offset) - .collect::>(); + let rem_path = once(key_nib).chain(key_nibbles).collect::>(); self.split( node, &mut parents, @@ -429,16 +432,15 @@ impl + Send + Sync> Merkle { val.take().unwrap(), &mut deleted, )?; + break; } + NodeType::Extension(n) => { let n_path = n.path().to_vec(); let n_ptr = n.chd(); nskip = n_path.len() - 1; - let rem_path = key_nibbles - .into_iter() - .skip(key_nib_offset) - .collect::>(); + let rem_path = once(key_nib).chain(key_nibbles.clone()).collect::>(); if let Some(v) = self.split( node, @@ -461,10 +463,12 @@ impl + Send + Sync> Merkle { } } }; + // push another parent, and follow the next pointer parents.push((node, key_nib)); next_node = Some(self.get_node(next_node_ptr)?); } + if val.is_some() { // we walked down the tree and reached the end of the key, // but haven't inserted the value yet @@ -539,6 +543,7 @@ impl + Send + Sync> Merkle { for ptr in deleted.into_iter() { self.free_node(ptr)? } + Ok(()) } From 404a07097b269cf386f349c2644b177c32dc1c57 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 6 Oct 2023 10:21:09 -0400 Subject: [PATCH 0327/1053] Use iterators instead of continue (#309) --- firewood/src/merkle.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5dd58df69b3a..224596cb23cd 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -360,7 +360,6 @@ impl + Send + Sync> Merkle { let mut parents = Vec::new(); let mut next_node = Some(self.get_node(root)?); - let mut nskip = 0; // wrap the current value into an Option to indicate whether it has been // inserted yet. If we haven't inserted it after we traverse the tree, we @@ -378,12 +377,6 @@ impl + Send + Sync> Merkle { break; }; - // special handling for extension nodes - if nskip > 0 { - nskip -= 1; - continue; - } - // move the current node into node; next_node becomes None // unwrap() is okay here since we are certain we have something // in next_node at this point @@ -439,8 +432,8 @@ impl + Send + Sync> Merkle { NodeType::Extension(n) => { let n_path = n.path().to_vec(); let n_ptr = n.chd(); - nskip = n_path.len() - 1; let rem_path = once(key_nib).chain(key_nibbles.clone()).collect::>(); + let n_path_len = n_path.len(); if let Some(v) = self.split( node, @@ -451,6 +444,10 @@ impl + Send + Sync> Merkle { val.take().unwrap(), &mut deleted, )? { + (0..n_path_len).skip(1).for_each(|_| { + key_nibbles.next(); + }); + // we couldn't split this, so we // skip n_path items and follow the // extension node's next pointer From de377f1d7621fa98f1e6102a998faafa389c0fed Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 6 Oct 2023 08:21:33 -0700 Subject: [PATCH 0328/1053] chore: Remove the getter pattern over `ExtNode` (#310) Co-authored-by: Richard Pringle --- firewood/src/merkle.rs | 110 ++++++++++++++++++------------------ firewood/src/merkle/node.rs | 63 ++++++++------------- firewood/src/proof.rs | 14 ++--- 3 files changed, 87 insertions(+), 100 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 224596cb23cd..ce1d18b2e6f5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -192,7 +192,7 @@ impl + Send + Sync> Merkle { .write(|u| { (*match &mut u.inner { NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => u.path_mut(), + NodeType::Extension(u) => &mut u.path, _ => unreachable!(), }) = PartialPath(n_path[idx + 1..].to_vec()); u.rehash(); @@ -208,7 +208,7 @@ impl + Send + Sync> Merkle { chd[rem_path[idx] as usize] = Some(leaf_ptr); chd[n_path[idx] as usize] = Some(match &u_ref.inner { NodeType::Extension(u) => { - if u.path().len() == 0 { + if u.path.len() == 0 { deleted.push(u_ptr); u.chd() } else { @@ -225,11 +225,11 @@ impl + Send + Sync> Merkle { }); let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); if idx > 0 { - self.new_node(Node::new(NodeType::Extension(ExtNode::new( - rem_path[..idx].to_vec(), - branch_ptr, - None, - ))))? + self.new_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(rem_path[..idx].to_vec()), + child: branch_ptr, + child_encoded: None, + })))? .as_ptr() } else { branch_ptr @@ -284,7 +284,7 @@ impl + Send + Sync> Merkle { .write(|u| { (*match &mut u.inner { NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => u.path_mut(), + NodeType::Extension(u) => &mut u.path, _ => unreachable!(), }) = PartialPath(n_path[rem_path.len() + 1..].to_vec()); u.rehash(); @@ -293,7 +293,7 @@ impl + Send + Sync> Merkle { ( match &u_ref.inner { NodeType::Extension(u) => { - if u.path().len() == 0 { + if u.path.len() == 0 { deleted.push(u_ptr); u.chd() } else { @@ -331,11 +331,11 @@ impl + Send + Sync> Merkle { })))? .as_ptr(); if !prefix.is_empty() { - self.new_node(Node::new(NodeType::Extension(ExtNode::new( - prefix.to_vec(), - branch_ptr, - None, - ))))? + self.new_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(prefix.to_vec()), + child: branch_ptr, + child_encoded: None, + })))? .as_ptr() } else { branch_ptr @@ -430,7 +430,7 @@ impl + Send + Sync> Merkle { } NodeType::Extension(n) => { - let n_path = n.path().to_vec(); + let n_path = n.path.to_vec(); let n_ptr = n.chd(); let rem_path = once(key_nib).chain(key_nibbles.clone()).collect::>(); let n_path_len = n_path.len(); @@ -493,9 +493,9 @@ impl + Send + Sync> Merkle { } } NodeType::Extension(n) => { - let idx = n.path()[0]; - let more = if n.path().len() > 1 { - *n.path_mut() = PartialPath(n.path()[1..].to_vec()); + let idx = n.path[0]; + let more = if n.path.len() > 1 { + n.path = PartialPath(n.path[1..].to_vec()); true } else { false @@ -591,7 +591,7 @@ impl + Send + Sync> Merkle { // to: P -> [Leaf (v)] let leaf = self .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(n.path().clone().into_inner()), + PartialPath(n.path.clone().into_inner()), val, ))))? .as_ptr(); @@ -614,11 +614,11 @@ impl + Send + Sync> Merkle { // \____[Leaf]x // to: [p: Branch] -> [Ext] -> [Branch] let ext = self - .new_node(Node::new(NodeType::Extension(ExtNode::new( - vec![idx], - c_ptr, - None, - ))))? + .new_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(vec![idx]), + child: c_ptr, + child_encoded: None, + })))? .as_ptr(); self.set_parent(ext, &mut [(p_ref, p_idx)]); } @@ -633,7 +633,7 @@ impl + Send + Sync> Merkle { p_ref, |p| { let pp = p.inner.as_extension_mut().unwrap(); - pp.path_mut().0.push(idx); + pp.path.0.push(idx); *pp.chd_mut() = c_ptr; p.rehash(); }, @@ -655,7 +655,7 @@ impl + Send + Sync> Merkle { let write_result = c_ref.write(|c| { let partial_path = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => n.path_mut(), + NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -692,11 +692,11 @@ impl + Send + Sync> Merkle { self, c_ref, |c| { - let mut path = n.path().clone().into_inner(); + let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => n.path_mut(), + NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; path.extend(&**path0); @@ -743,16 +743,18 @@ impl + Send + Sync> Merkle { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] n.chd[b_idx as usize] = Some( - self.new_node(Node::new(NodeType::Extension( - ExtNode::new(vec![idx], c_ptr, None), - )))? + self.new_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(vec![idx]), + child: c_ptr, + child_encoded: None, + })))? .as_ptr(), ); } NodeType::Extension(n) => { // from: [Ext] -> [Branch]x -> [Branch] // to: [Ext] -> [Branch] - n.path_mut().0.push(idx); + n.path.0.push(idx); *n.chd_mut() = c_ptr } _ => unreachable!(), @@ -777,7 +779,7 @@ impl + Send + Sync> Merkle { let write_result = c_ref.write(|c| { match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => n.path_mut(), + NodeType::Extension(n) => &mut n.path, _ => unreachable!(), } .0 @@ -802,11 +804,11 @@ impl + Send + Sync> Merkle { // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] // to: P -> [Leaf/Ext] let write_result = c_ref.write(|c| { - let mut path = n.path().clone().into_inner(); + let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { NodeType::Leaf(n) => &mut n.0, - NodeType::Extension(n) => n.path_mut(), + NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; path.extend(&**path0); @@ -869,7 +871,7 @@ impl + Send + Sync> Merkle { break; } NodeType::Extension(n) => { - let n_path = &*n.path().0; + let n_path = &*n.path.0; let rem_path = &chunks[i..]; if rem_path < n_path || &rem_path[..n_path.len()] != n_path { return Ok(None); @@ -990,7 +992,7 @@ impl + Send + Sync> Merkle { return Ok(Some(RefMut::new(u_ptr, parents, self))); } NodeType::Extension(n) => { - let n_path = &*n.path().0; + let n_path = &*n.path.0; let rem_path = &chunks[i..]; if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { return Ok(None); @@ -1070,7 +1072,7 @@ impl + Send + Sync> Merkle { NodeType::Extension(n) => { // the key passed in must match the entire remainder of this // extension node, otherwise we break out - let n_path = n.path(); + let n_path = &n.path; let remaining_path = key_nibbles.into_iter().skip(i); if remaining_path.size_hint().0 < n_path.len() { // all bytes aren't there @@ -1143,7 +1145,7 @@ impl + Send + Sync> Merkle { return Ok(Some(Ref(u_ref))); } NodeType::Extension(n) => { - let n_path = n.path(); + let n_path = &n.path; let rem_path = key_nibbles.into_iter().skip(i); if rem_path.size_hint().0 < n_path.len() { return Ok(None); @@ -1348,20 +1350,20 @@ mod test { Node::new_from_hash( None, None, - NodeType::Extension(ExtNode::new( - vec![0x1, 0x2, 0x3], - DiskAddress::from(0x42), - None, - )), + NodeType::Extension(ExtNode { + path: PartialPath(vec![0x1, 0x2, 0x3]), + child: DiskAddress::from(0x42), + child_encoded: None, + }), ), Node::new_from_hash( None, None, - NodeType::Extension(ExtNode::new( - vec![0x1, 0x2, 0x3], - DiskAddress::null(), - Some(vec![0x1, 0x2, 0x3]), - )), + NodeType::Extension(ExtNode { + path: PartialPath(vec![0x1, 0x2, 0x3]), + child: DiskAddress::null(), + child_encoded: Some(vec![0x1, 0x2, 0x3]), + }), ), Node::new_from_hash( None, @@ -1455,11 +1457,11 @@ mod test { let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); assert_eq!(chd_encoded, new_chd_encoded); - let node = Node::new(NodeType::Extension(ExtNode::new( - vec![0x1, 0x2, 0x3], - DiskAddress::null(), - Some(chd_encoded.to_vec()), - ))); + let node = Node::new(NodeType::Extension(ExtNode { + path: PartialPath(vec![0x1, 0x2, 0x3]), + child: DiskAddress::null(), + child_encoded: Some(chd_encoded.to_vec()), + })); let node_ref = merkle.new_node(node.clone()).unwrap(); let r = node_ref.get_encoded(merkle.store.as_ref()); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 472fd48b0bf8..81967b572778 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -244,18 +244,19 @@ impl LeafNode { #[derive(PartialEq, Eq, Clone)] pub struct ExtNode { - path: PartialPath, - chd: DiskAddress, - chd_encoded: Option>, + pub(crate) path: PartialPath, + pub(crate) child: DiskAddress, + pub(crate) child_encoded: Option>, } impl Debug for ExtNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "[Extension {:?} {:?} {:?}]", - self.path, self.chd, self.chd_encoded - ) + let Self { + path, + child, + child_encoded, + } = self; + write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) } } @@ -268,8 +269,8 @@ impl ExtNode { .unwrap(), ); - if !self.chd.is_null() { - let mut r = store.get_item(self.chd).unwrap(); + if !self.child.is_null() { + let mut r = store.get_item(self.child).unwrap(); if r.is_encoded_longer_than_hash_len(store) { list[1] = Encoded::Data( @@ -288,7 +289,7 @@ impl ExtNode { } else { // Check if there is already a caclucated encoded value for the child, which // can happen when manually constructing a trie from proof. - if let Some(v) = &self.chd_encoded { + if let Some(v) = &self.child_encoded { if v.len() == TRIE_HASH_LEN { list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); } else { @@ -302,36 +303,20 @@ impl ExtNode { .unwrap() } - pub fn new(path: Vec, chd: DiskAddress, chd_encoded: Option>) -> Self { - ExtNode { - path: PartialPath(path), - chd, - chd_encoded, - } - } - - pub fn path(&self) -> &PartialPath { - &self.path - } - - pub fn path_mut(&mut self) -> &mut PartialPath { - &mut self.path - } - pub fn chd(&self) -> DiskAddress { - self.chd + self.child } pub fn chd_encoded(&self) -> Option<&[u8]> { - self.chd_encoded.as_deref() + self.child_encoded.as_deref() } pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.chd + &mut self.child } pub fn chd_encoded_mut(&mut self) -> &mut Option> { - &mut self.chd_encoded + &mut self.child_encoded } } @@ -411,11 +396,11 @@ impl NodeType { if term { Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) } else { - Ok(NodeType::Extension(ExtNode::new( - cur_key, - DiskAddress::null(), - Some(data), - ))) + Ok(NodeType::Extension(ExtNode { + path: PartialPath(cur_key), + child: DiskAddress::null(), + child_encoded: Some(data), + })) } } BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?)), @@ -685,8 +670,8 @@ impl Storable for Node { is_encoded_longer_than_hash_len, NodeType::Extension(ExtNode { path, - chd: DiskAddress::from(ptr as usize), - chd_encoded: encoded, + child: DiskAddress::from(ptr as usize), + child_encoded: encoded, }), )) } @@ -820,7 +805,7 @@ impl Storable for Node { cur.write_all(&[Self::EXT_NODE])?; let path: Vec = from_nibbles(&n.path.encode(false)).collect(); cur.write_all(&[path.len() as u8])?; - cur.write_all(&n.chd.to_le_bytes())?; + cur.write_all(&n.child.to_le_bytes())?; cur.write_all(&path)?; if let Some(encoded) = n.chd_encoded() { cur.write_all(&[encoded.len() as u8])?; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 5473ed728592..50b1a3015d6f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -467,7 +467,7 @@ impl + Send> Proof { Ok((addr, subproof, cur_key.len())) } NodeType::Extension(n) => { - let cur_key = &n.path().0; + let cur_key = &n.path.0; // Check if the key of current node match with the given key. if key.len() < cur_key.len() || &key[..cur_key.len()] != cur_key { @@ -521,7 +521,7 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } NodeType::Extension(n) => { - let cur_key = &n.path().0; + let cur_key = &n.path.0; // Check if the key of current node match with the given key // and consume the current-key portion of the nibbles-iterator let does_not_match = key_nibbles.size_hint().0 < cur_key.len() @@ -629,7 +629,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( NodeType::Extension(n) => { // If either the key of left proof or right proof doesn't match with // shortnode, stop here and the forkpoint is the shortnode. - let cur_key = n.path().clone().into_inner(); + let cur_key = n.path.clone().into_inner(); fork_left = if left_chunks.len() - index < cur_key.len() { left_chunks[index..].cmp(&cur_key) @@ -703,7 +703,7 @@ fn unset_internal, S: ShaleStore + Send + Sync>( // - left proof points to the shortnode, but right proof is greater // - right proof points to the shortnode, but left proof is less let node = n.chd(); - let cur_key = n.path().clone().into_inner(); + let cur_key = n.path.clone().into_inner(); if fork_left.is_lt() && fork_right.is_lt() { return Err(ProofError::EmptyRange); @@ -877,13 +877,13 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( unset_node_ref(merkle, p, node, key, index + 1, remove_left) } - NodeType::Extension(n) if chunks[index..].starts_with(n.path()) => { + NodeType::Extension(n) if chunks[index..].starts_with(&n.path) => { let node = Some(n.chd()); - unset_node_ref(merkle, p, node, key, index + n.path().len(), remove_left) + unset_node_ref(merkle, p, node, key, index + n.path.len(), remove_left) } NodeType::Extension(n) => { - let cur_key = n.path(); + let cur_key = &n.path; let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; From 4fd35a4c38e530d253000b3fe920d9ef0dbed1a9 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Oct 2023 08:43:22 -0700 Subject: [PATCH 0329/1053] API implementation part 3 (#308) --- firewood/Cargo.toml | 2 +- firewood/src/db.rs | 4 ++- firewood/src/db/proposal.rs | 7 ++-- firewood/src/v2/api.rs | 12 +++---- firewood/src/v2/propose.rs | 6 ++-- firewood/tests/v2api.rs | 70 +++++++++++++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 firewood/tests/v2api.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 00fed7e68294..829f05f0ec7a 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -32,7 +32,7 @@ parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" -tokio = { version = "1.21.1", features = ["rt", "sync", "macros"] } +tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.16.0" bincode = "1.3.3" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index bf813f802144..b129391e3ea9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -37,6 +37,7 @@ use std::{ sync::Arc, thread::JoinHandle, }; +use tokio::task::block_in_place; mod proposal; @@ -390,7 +391,8 @@ impl api::Db for Db { type Proposal = Proposal; async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { - if let Some(rev) = self.get_revision(&TrieHash(root_hash)) { + let rev = block_in_place(|| self.get_revision(&TrieHash(root_hash))); + if let Some(rev) = rev { Ok(Arc::new(rev.rev)) } else { Err(api::Error::HashNotFound { diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index c202e3c0154f..1af6f20371be 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -15,6 +15,7 @@ use async_trait::async_trait; use parking_lot::{Mutex, RwLock}; use shale::CachedStore; use std::{io::ErrorKind, sync::Arc}; +use tokio::task::block_in_place; pub use crate::v2::api::{Batch, BatchOp}; @@ -43,11 +44,11 @@ pub enum ProposalBase { } #[async_trait] -impl crate::v2::api::Proposal for Proposal { +impl crate::v2::api::Proposal for Proposal { type Proposal = Proposal; - async fn commit(self: Arc) -> Result, api::Error> { - todo!() + async fn commit(self: Arc) -> Result<(), api::Error> { + block_in_place(|| self.commit_sync().map_err(Into::into)) } async fn propose( diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 38df5dc5b062..dbc9ff4e0523 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -96,7 +96,7 @@ pub struct Proof(pub HashMap); pub trait Db { type Historical: DbView; - type Proposal: DbView + Proposal; + type Proposal: DbView + Proposal; /// Get a reference to a specific view based on a hash /// @@ -172,15 +172,11 @@ pub trait DbView { /// [DbView], which means you can fetch values from it or /// obtain proofs. #[async_trait] -pub trait Proposal: DbView { - type Proposal: DbView + Proposal; +pub trait Proposal: DbView { + type Proposal: DbView + Proposal; /// Commit this revision - /// - /// # Return value - /// - /// * A reference to a new historical view - async fn commit(self: Arc) -> Result, Error>; + async fn commit(self: Arc) -> Result<(), Error>; /// Propose a new revision on top of an existing proposal /// diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index a6e53d0cb353..d00e8ee2ac29 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -139,7 +139,7 @@ impl api::DbView for Proposal { } #[async_trait] -impl api::Proposal for Proposal { +impl api::Proposal for Proposal { type Proposal = Proposal; async fn propose( @@ -150,12 +150,12 @@ impl api::Proposal for Proposal { Ok(Proposal::new(ProposalBase::Proposal(self), data)) } - async fn commit(self: Arc) -> Result, api::Error> { + async fn commit(self: Arc) -> Result<(), api::Error> { // TODO: commit should modify the db; this will only work for // emptydb at the moment match &self.base { ProposalBase::Proposal(base) => base.clone().commit().await, - ProposalBase::View(v) => Ok(v.clone()), + ProposalBase::View(_) => Ok(()), } } } diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs new file mode 100644 index 000000000000..69b35e73b574 --- /dev/null +++ b/firewood/tests/v2api.rs @@ -0,0 +1,70 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::{error::Error, path::PathBuf, sync::Arc}; + +use firewood::{ + db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, + v2::api::{Db, DbView, Proposal}, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn smoke() -> Result<(), Box> { + let cfg = DbConfig::builder() + .meta_ncached_pages(1024) + .meta_ncached_files(128) + .payload_ncached_pages(1024) + .payload_ncached_files(128) + .payload_file_nbit(16) + .payload_regn_nbit(16) + .wal( + WalConfig::builder() + .file_nbit(15) + .block_nbit(8) + .max_revisions(10) + .build(), + ) + .truncate(true) + .build(); + let db = Arc::new(testdb(cfg).await?); + let empty_hash = db.root_hash().await?; + assert_ne!(empty_hash, [0; 32]); + + // insert a single key/value + let (key, value) = (b"smoke", b"test"); + let batch_put = BatchOp::Put { key, value }; + let proposal: Arc = db.propose(vec![batch_put]).await?.into(); + proposal.commit().await?; + + // ensure the latest hash is different + let latest = db.root_hash().await?; + assert_ne!(empty_hash, latest); + + // fetch the view of the latest + let view = db.revision(latest).await.unwrap(); + + // check that the key/value is there + let got_value = view.val(key).await.unwrap().unwrap(); + assert_eq!(got_value, value); + + // TODO: also fetch view of empty; this currently does not work, as you can't reference + // the empty hash + // let empty_view = db.revision(empty_hash).await.unwrap(); + // let value = empty_view.val(b"smoke").await.unwrap(); + // assert_eq!(value, None); + + Ok(()) +} + +async fn testdb(cfg: DbConfig) -> Result { + let tmpdbpath = tmp_dir().join("testdb"); + tokio::task::spawn_blocking(move || PersistedDb::new(tmpdbpath, &cfg)) + .await + .unwrap() +} + +fn tmp_dir() -> PathBuf { + option_env!("CARGO_TARGET_TMPDIR") + .map(PathBuf::from) + .unwrap_or(std::env::temp_dir()) +} From f0f6a78685b68fbf1a6adae6053f1d69cb4beaf2 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 6 Oct 2023 17:41:08 -0400 Subject: [PATCH 0330/1053] Cleanup merkle-insert code (#311) --- firewood/src/merkle.rs | 170 +++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 76 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index ce1d18b2e6f5..83dc9dc56d21 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -3,7 +3,7 @@ use crate::{nibbles::Nibbles, v2::api::Proof}; use sha3::Digest; -use shale::{disk_address::DiskAddress, ObjRef, ShaleError, ShaleStore}; +use shale::{disk_address::DiskAddress, ObjRef, ObjWriteError, ShaleError, ShaleStore}; use std::{ collections::HashMap, io::Write, @@ -34,13 +34,15 @@ pub enum MerkleError { ParentLeafBranch, #[error("removing internal node references failed")] UnsetInternal, + #[error("error updating nodes: {0}")] + WriteError(#[from] ObjWriteError), } macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let Err(_) = $r.write($modify) { let ptr = $self.new_node($r.clone())?.as_ptr(); - $self.set_parent(ptr, $parents); + set_parent(ptr, $parents); $deleted.push($r.as_ptr()); true } else { @@ -156,20 +158,6 @@ impl + Send + Sync> Merkle { Ok(()) } - fn set_parent(&self, new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { - let (p_ref, idx) = parents.last_mut().unwrap(); - p_ref - .write(|p| { - match &mut p.inner { - NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd), - NodeType::Extension(pp) => *pp.chd_mut() = new_chd, - _ => unreachable!(), - } - p.rehash(); - }) - .unwrap(); - } - #[allow(clippy::too_many_arguments)] fn split<'b>( &self, @@ -345,7 +333,7 @@ impl + Send + Sync> Merkle { // observation: // - leaf/extension node can only be the child of a branch node // - branch node can only be the child of a branch/extension node - self.set_parent(new_chd, parents); + set_parent(new_chd, parents); Ok(None) } @@ -355,16 +343,30 @@ impl + Send + Sync> Merkle { val: Vec, root: DiskAddress, ) -> Result<(), MerkleError> { + let (parents, deleted) = self.insert_and_return_updates(key, val, root)?; + + for mut r in parents { + r.write(|u| u.rehash())?; + } + + for ptr in deleted { + self.free_node(ptr)? + } + + Ok(()) + } + + fn insert_and_return_updates>( + &self, + key: K, + mut val: Vec, + root: DiskAddress, + ) -> Result<(impl Iterator>, Vec), MerkleError> { // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); let mut parents = Vec::new(); - let mut next_node = Some(self.get_node(root)?); - - // wrap the current value into an Option to indicate whether it has been - // inserted yet. If we haven't inserted it after we traverse the tree, we - // have to do some splitting - let mut val = Some(val); + let mut next_node = None; // we use Nibbles::<1> so that 1 zero nibble is at the front // this is for the sentinel node, which avoids moving the root @@ -372,41 +374,42 @@ impl + Send + Sync> Merkle { let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); // walk down the merkle tree starting from next_node, currently the root - loop { - let Some(key_nib) = key_nibbles.next() else { - break; - }; - - // move the current node into node; next_node becomes None - // unwrap() is okay here since we are certain we have something - // in next_node at this point - let mut node = next_node.take().unwrap(); + // return None if the value is inserted + let next_node_and_val = loop { + let mut node = next_node + .take() + .map(Ok) + .unwrap_or_else(|| self.get_node(root))?; let node_ptr = node.as_ptr(); - let next_node_ptr = match &node.inner { + let Some(current_nibble) = key_nibbles.next() else { + break Some((node, val)); + }; + + let (node, next_node_ptr) = match &node.inner { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done - NodeType::Branch(n) => match n.chd[key_nib as usize] { - Some(c) => c, + NodeType::Branch(n) => match n.chd[current_nibble as usize] { + Some(c) => (node, c), None => { // insert the leaf to the empty slot // create a new leaf let leaf_ptr = self .new_node(Node::new(NodeType::Leaf(LeafNode( PartialPath(key_nibbles.collect()), - Data(val.take().unwrap()), + Data(val), ))))? .as_ptr(); // set the current child to point to this leaf node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); - uu.chd[key_nib as usize] = Some(leaf_ptr); + uu.chd[current_nibble as usize] = Some(leaf_ptr); u.rehash(); }) .unwrap(); - break; + break None; } }, @@ -415,24 +418,27 @@ impl + Send + Sync> Merkle { // of the stored key to pass into split let n_path = n.0.to_vec(); let n_value = Some(n.1.clone()); - let rem_path = once(key_nib).chain(key_nibbles).collect::>(); + let rem_path = once(current_nibble).chain(key_nibbles).collect::>(); + self.split( node, &mut parents, &rem_path, n_path, n_value, - val.take().unwrap(), + val, &mut deleted, )?; - break; + break None; } NodeType::Extension(n) => { let n_path = n.path.to_vec(); let n_ptr = n.chd(); - let rem_path = once(key_nib).chain(key_nibbles.clone()).collect::>(); + let rem_path = once(current_nibble) + .chain(key_nibbles.clone()) + .collect::>(); let n_path_len = n_path.len(); if let Some(v) = self.split( @@ -441,7 +447,7 @@ impl + Send + Sync> Merkle { &rem_path, n_path, None, - val.take().unwrap(), + val, &mut deleted, )? { (0..n_path_len).skip(1).for_each(|_| { @@ -451,45 +457,46 @@ impl + Send + Sync> Merkle { // we couldn't split this, so we // skip n_path items and follow the // extension node's next pointer - val = Some(v); - node = self.get_node(node_ptr)?; - n_ptr + val = v; + + (self.get_node(node_ptr)?, n_ptr) } else { // successfully inserted - break; + break None; } } }; // push another parent, and follow the next pointer - parents.push((node, key_nib)); - next_node = Some(self.get_node(next_node_ptr)?); - } + parents.push((node, current_nibble)); + next_node = self.get_node(next_node_ptr)?.into(); + }; - if val.is_some() { + if let Some((mut node, val)) = next_node_and_val { // we walked down the tree and reached the end of the key, // but haven't inserted the value yet let mut info = None; let u_ptr = { - let mut u = next_node.take().unwrap(); write_node!( self, - u, + node, |u| { info = match &mut u.inner { NodeType::Branch(n) => { - n.value = Some(Data(val.take().unwrap())); + n.value = Some(Data(val)); None } NodeType::Leaf(n) => { if n.0.len() == 0 { - n.1 = Data(val.take().unwrap()); + n.1 = Data(val); + None } else { let idx = n.0[0]; n.0 = PartialPath(n.0[1..].to_vec()); u.rehash(); - Some((idx, true, None)) + + Some((idx, true, None, val)) } } NodeType::Extension(n) => { @@ -500,48 +507,45 @@ impl + Send + Sync> Merkle { } else { false }; - Some((idx, more, Some(n.chd()))) + + Some((idx, more, Some(n.chd()), val)) } }; + u.rehash() }, &mut parents, &mut deleted ); - u.as_ptr() + + node.as_ptr() }; - if let Some((idx, more, ext)) = info { + if let Some((idx, more, ext, val)) = info { let mut chd = [None; NBRANCH]; + let c_ptr = if more { u_ptr } else { deleted.push(u_ptr); ext.unwrap() }; + chd[idx as usize] = Some(c_ptr); + let branch = self .new_node(Node::new(NodeType::Branch(BranchNode { chd, - value: Some(Data(val.take().unwrap())), + value: Some(Data(val)), chd_encoded: Default::default(), })))? .as_ptr(); - self.set_parent(branch, &mut parents); - } - } - - drop(next_node); - - for (mut r, _) in parents.into_iter().rev() { - r.write(|u| u.rehash()).unwrap(); - } - for ptr in deleted.into_iter() { - self.free_node(ptr)? + set_parent(branch, &mut parents); + } } - Ok(()) + Ok((parents.into_iter().rev().map(|(node, _)| node), deleted)) } fn after_remove_leaf( @@ -596,7 +600,7 @@ impl + Send + Sync> Merkle { ))))? .as_ptr(); deleted.push(p_ptr); - self.set_parent(leaf, parents); + set_parent(leaf, parents); } _ => unreachable!(), } @@ -620,7 +624,7 @@ impl + Send + Sync> Merkle { child_encoded: None, })))? .as_ptr(); - self.set_parent(ext, &mut [(p_ref, p_idx)]); + set_parent(ext, &mut [(p_ref, p_idx)]); } NodeType::Extension(_) => { // ____[Branch] @@ -709,7 +713,7 @@ impl + Send + Sync> Merkle { if !write_failed { drop(c_ref); - self.set_parent(c_ptr, parents); + set_parent(c_ptr, parents); } } _ => unreachable!(), @@ -825,7 +829,7 @@ impl + Send + Sync> Merkle { deleted.push(b_ref.as_ptr()); drop(c_ref); - self.set_parent(c_ptr, parents); + set_parent(c_ptr, parents); } _ => unreachable!(), }, @@ -1182,6 +1186,20 @@ impl + Send + Sync> Merkle { } } +fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { + let (p_ref, idx) = parents.last_mut().unwrap(); + p_ref + .write(|p| { + match &mut p.inner { + NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd), + NodeType::Extension(pp) => *pp.chd_mut() = new_chd, + _ => unreachable!(), + } + p.rehash(); + }) + .unwrap(); +} + pub struct Ref<'a>(ObjRef<'a, Node>); pub struct RefMut<'a, S> { From ab47b31bd4faf449b3d1d7251d22d5ca7da17fc8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Oct 2023 11:46:39 -0700 Subject: [PATCH 0331/1053] Simplify test (#312) --- firewood/tests/v2api.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index 69b35e73b574..afd9a7493697 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -4,28 +4,13 @@ use std::{error::Error, path::PathBuf, sync::Arc}; use firewood::{ - db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, + db::{BatchOp, Db as PersistedDb, DbConfig, DbError}, v2::api::{Db, DbView, Proposal}, }; #[tokio::test(flavor = "multi_thread")] async fn smoke() -> Result<(), Box> { - let cfg = DbConfig::builder() - .meta_ncached_pages(1024) - .meta_ncached_files(128) - .payload_ncached_pages(1024) - .payload_ncached_files(128) - .payload_file_nbit(16) - .payload_regn_nbit(16) - .wal( - WalConfig::builder() - .file_nbit(15) - .block_nbit(8) - .max_revisions(10) - .build(), - ) - .truncate(true) - .build(); + let cfg = DbConfig::builder().truncate(true).build(); let db = Arc::new(testdb(cfg).await?); let empty_hash = db.root_hash().await?; assert_ne!(empty_hash, [0; 32]); From bd882188ed0c476ee23e1e69d191f4be91383d90 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:59:56 -0700 Subject: [PATCH 0332/1053] chore: proof cleanup (#316) --- firewood/src/proof.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 50b1a3015d6f..b813ec3fab05 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -454,23 +454,21 @@ impl + Send> Proof { let addr = new_node.as_ptr(); match new_node.inner() { NodeType::Leaf(n) => { - let cur_key = &n.path().0; - // Check if the key of current node match with the given key. - if key.len() < cur_key.len() || &key[..cur_key.len()] != cur_key { + let cur_key = n.path().0.as_ref(); + if !key.contains_other(cur_key) { return Ok((addr, None, 0)); } - let subproof = Some(SubProof { + let subproof = SubProof { encoded: n.data().to_vec(), hash: None, - }); - Ok((addr, subproof, cur_key.len())) + }; + Ok((addr, subproof.into(), cur_key.len())) } NodeType::Extension(n) => { - let cur_key = &n.path.0; + let cur_key = n.path.0.as_ref(); - // Check if the key of current node match with the given key. - if key.len() < cur_key.len() || &key[..cur_key.len()] != cur_key { + if !key.contains_other(cur_key) { return Ok((addr, None, 0)); } @@ -963,3 +961,16 @@ fn unset_node_ref, S: ShaleStore + Send + Sync>( } } } + +pub trait ContainsOtherExt { + fn contains_other(&self, other: Self) -> bool; +} + +impl ContainsOtherExt for &[T] +where + [T]: PartialEq<[T]>, +{ + fn contains_other(&self, other: Self) -> bool { + self.len() >= other.len() && &self[..other.len()] == other + } +} From 5c2e0cbf4448170f733bf77db3d8d1fae7e686b0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 14 Oct 2023 15:29:03 -0700 Subject: [PATCH 0333/1053] Use bincode for serialization (#319) --- firewood/src/storage/mod.rs | 70 ++++--------------------------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index e0ceceb34fae..6779e99aa605 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -6,6 +6,7 @@ use self::buffer::DiskBufferRequester; use crate::file::File; use nix::fcntl::{flock, FlockArg}; use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; use shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use std::{ collections::HashMap, @@ -57,13 +58,13 @@ pub trait MemStoreR: Debug + Send + Sync { // Page should be boxed as to not take up so much stack-space type Page = Box<[u8; PAGE_SIZE as usize]>; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct SpaceWrite { offset: u64, data: Box<[u8]>, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] /// In memory representation of Write-ahead log with `undo` and `redo`. pub struct Ash { /// Deltas to undo the changes. @@ -78,78 +79,19 @@ impl Ash { } } -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { fn serialize(&self) -> growthring::wal::WalBytes { - let len_bytes = (self.0.len() as u64).to_le_bytes(); - let ash_record_bytes = self - .0 - .iter() - .map(|(space_id, w)| { - let space_id_bytes = space_id.to_le_bytes(); - let undo_len = u32::try_from(w.undo.len()).expect("size of undo shoud be a `u32`"); - let ash_bytes = w.iter().flat_map(|(undo, redo)| { - let undo_data_len = u64::try_from(undo.data.len()) - .expect("length of undo data shoud be a `u64`"); - - undo.offset - .to_le_bytes() - .into_iter() - .chain(undo_data_len.to_le_bytes()) - .chain(undo.data.iter().copied()) - .chain(redo.data.iter().copied()) - }); - - (space_id_bytes, undo_len, ash_bytes) - }) - .flat_map(|(space_id_bytes, undo_len, ash_bytes)| { - space_id_bytes - .into_iter() - .chain(undo_len.to_le_bytes()) - .chain(ash_bytes) - }); - - len_bytes.into_iter().chain(ash_record_bytes).collect() + bincode::serialize(self).unwrap().into() } } impl AshRecord { #[allow(clippy::boxed_local)] fn deserialize(raw: growthring::wal::WalBytes) -> Self { - let mut r = &raw[..]; - let len = u64::from_le_bytes(r[..8].try_into().unwrap()); - r = &r[8..]; - let writes = (0..len) - .map(|_| { - let space_id = u8::from_le_bytes(r[..1].try_into().unwrap()); - let wlen = u32::from_le_bytes(r[1..5].try_into().unwrap()); - r = &r[5..]; - let mut undo = Vec::new(); - let mut redo = Vec::new(); - for _ in 0..wlen { - let offset = u64::from_le_bytes(r[..8].try_into().unwrap()); - let data_len = u64::from_le_bytes(r[8..16].try_into().unwrap()); - r = &r[16..]; - let undo_write = SpaceWrite { - offset, - data: r[..data_len as usize].into(), - }; - r = &r[data_len as usize..]; - // let new_data: Box<[u8]> = r[..data_len as usize].into(); - let redo_write = SpaceWrite { - offset, - data: r[..data_len as usize].into(), - }; - r = &r[data_len as usize..]; - undo.push(undo_write); - redo.push(redo_write); - } - (space_id, Ash { undo, redo }) - }) - .collect(); - Self(writes) + bincode::deserialize(raw.as_ref()).unwrap() } } From 1d836ad4846afdf77b512eba05d99908645d7d1c Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 16 Oct 2023 10:35:44 -0400 Subject: [PATCH 0334/1053] Cleanup split logic (#313) --- firewood/src/merkle.rs | 427 +++++++++++++++++++----------------- firewood/src/merkle/node.rs | 74 ++++--- firewood/src/proof.rs | 2 +- 3 files changed, 276 insertions(+), 227 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 83dc9dc56d21..3bacd663cc02 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -5,10 +5,11 @@ use crate::{nibbles::Nibbles, v2::api::Proof}; use sha3::Digest; use shale::{disk_address::DiskAddress, ObjRef, ObjWriteError, ShaleError, ShaleStore}; use std::{ + cmp::Ordering, collections::HashMap, io::Write, iter::once, - sync::{atomic::Ordering, OnceLock}, + sync::{atomic::Ordering::Relaxed, OnceLock}, }; use thiserror::Error; @@ -41,7 +42,7 @@ pub enum MerkleError { macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let Err(_) = $r.write($modify) { - let ptr = $self.new_node($r.clone())?.as_ptr(); + let ptr = $self.put_node($r.clone())?.as_ptr(); set_parent(ptr, $parents); $deleted.push($r.as_ptr()); true @@ -60,9 +61,11 @@ impl> Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result, MerkleError> { self.store.get_item(ptr).map_err(Into::into) } - pub fn new_node(&self, item: Node) -> Result, MerkleError> { - self.store.put_item(item, 0).map_err(Into::into) + + pub fn put_node(&self, node: Node) -> Result, MerkleError> { + self.store.put_item(node, 0).map_err(Into::into) } + fn free_node(&mut self, ptr: DiskAddress) -> Result<(), MerkleError> { self.store.free_item(ptr).map_err(Into::into) } @@ -77,9 +80,9 @@ impl + Send + Sync> Merkle { self.store .put_item( Node::new(NodeType::Branch(BranchNode { - chd: [None; NBRANCH], + children: [None; NBRANCH], value: None, - chd_encoded: Default::default(), + children_encoded: Default::default(), })), Node::max_branch_node_size(), ) @@ -109,13 +112,13 @@ impl + Send + Sync> Merkle { .inner .as_branch() .ok_or(MerkleError::NotBranchNode)? - .chd[0]; + .children[0]; Ok(if let Some(root) = root { let mut node = self.get_node(root)?; let res = node.get_root_hash::(self.store.as_ref()).clone(); - if node.lazy_dirty.load(Ordering::Relaxed) { + if node.lazy_dirty.load(Relaxed) { node.write(|_| {}).unwrap(); - node.lazy_dirty.store(false, Ordering::Relaxed); + node.lazy_dirty.store(false, Relaxed); } res } else { @@ -136,7 +139,7 @@ impl + Send + Sync> Merkle { match &u_ref.inner { NodeType::Branch(n) => { writeln!(w, "{n:?}")?; - for c in n.chd.iter().flatten() { + for c in n.children.iter().flatten() { self.dump_(*c, w)? } } @@ -161,179 +164,207 @@ impl + Send + Sync> Merkle { #[allow(clippy::too_many_arguments)] fn split<'b>( &self, - mut u_ref: ObjRef<'b, Node>, + mut node_to_split: ObjRef<'b, Node>, parents: &mut [(ObjRef<'b, Node>, u8)], - rem_path: &[u8], + insert_path: &[u8], n_path: Vec, n_value: Option, val: Vec, deleted: &mut Vec, ) -> Result>, MerkleError> { - let u_ptr = u_ref.as_ptr(); - let new_chd = match rem_path.iter().zip(n_path.iter()).position(|(a, b)| a != b) { - Some(idx) => { - // _ [u (new path)] - // / - // [parent] (-> [ExtNode (common prefix)]) -> [branch]* - // \_ [leaf (with val)] - u_ref - .write(|u| { - (*match &mut u.inner { - NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => &mut u.path, - _ => unreachable!(), - }) = PartialPath(n_path[idx + 1..].to_vec()); - u.rehash(); - }) - .unwrap(); - let leaf_ptr = self - .new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(rem_path[idx + 1..].to_vec()), - Data(val), - ))))? - .as_ptr(); - let mut chd = [None; NBRANCH]; - chd[rem_path[idx] as usize] = Some(leaf_ptr); - chd[n_path[idx] as usize] = Some(match &u_ref.inner { - NodeType::Extension(u) => { - if u.path.len() == 0 { - deleted.push(u_ptr); - u.chd() - } else { - u_ptr - } - } - _ => u_ptr, - }); - drop(u_ref); - let t = NodeType::Branch(BranchNode { - chd, - value: None, - chd_encoded: Default::default(), - }); - let branch_ptr = self.new_node(Node::new(t))?.as_ptr(); - if idx > 0 { - self.new_node(Node::new(NodeType::Extension(ExtNode { - path: PartialPath(rem_path[..idx].to_vec()), - child: branch_ptr, - child_encoded: None, - })))? - .as_ptr() - } else { - branch_ptr + let node_to_split_address = node_to_split.as_ptr(); + let split_index = insert_path + .iter() + .zip(n_path.iter()) + .position(|(a, b)| a != b); + + let new_child_address = if let Some(idx) = split_index { + // paths diverge + let new_split_node_path = n_path.split_at(idx + 1).1; + let (matching_path, new_node_path) = insert_path.split_at(idx + 1); + + node_to_split.write(|node| { + // TODO: handle unwrap better + let path = node.inner.path_mut().unwrap(); + + *path = PartialPath(new_split_node_path.to_vec()); + + node.rehash(); + })?; + + let new_node = Node::leaf(PartialPath(new_node_path.to_vec()), Data(val)); + let leaf_address = self.put_node(new_node)?.as_ptr(); + + let mut chd = [None; NBRANCH]; + + let last_matching_nibble = matching_path[idx]; + chd[last_matching_nibble as usize] = Some(leaf_address); + + let address = match &node_to_split.inner { + NodeType::Extension(u) if u.path.len() == 0 => { + deleted.push(node_to_split_address); + u.chd() } - } - None => { - if rem_path.len() == n_path.len() { - let mut result = Ok(None); - - write_node!( - self, - u_ref, - |u| { - match &mut u.inner { - NodeType::Leaf(u) => u.1 = Data(val), - NodeType::Extension(u) => { - let write_result = - self.get_node(u.chd()).and_then(|mut b_ref| { - let write_result = b_ref.write(|b| { - b.inner.as_branch_mut().unwrap().value = - Some(Data(val)); - b.rehash() - }); + _ => node_to_split_address, + }; + + chd[n_path[idx] as usize] = Some(address); - if write_result.is_err() { - *u.chd_mut() = - self.new_node(b_ref.clone())?.as_ptr(); - deleted.push(b_ref.as_ptr()); - } + let new_branch = Node::branch(BranchNode { + children: chd, + value: None, + children_encoded: Default::default(), + }); - Ok(()) - }); + let new_branch_address = self.put_node(new_branch)?.as_ptr(); - if let Err(e) = write_result { - result = Err(e); + if idx > 0 { + self.put_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(matching_path[..idx].to_vec()), + child: new_branch_address, + child_encoded: None, + })))? + .as_ptr() + } else { + new_branch_address + } + } else { + // paths do not diverge + let (leaf_address, prefix, idx, value) = + match (insert_path.len().cmp(&n_path.len()), n_value) { + // no node-value means this is an extension node and we can therefore continue walking the tree + (Ordering::Greater, None) => return Ok(Some(val)), + + // if the paths are equal, we overwrite the data + (Ordering::Equal, _) => { + let mut result = Ok(None); + + write_node!( + self, + node_to_split, + |u| { + match &mut u.inner { + NodeType::Leaf(u) => u.1 = Data(val), + NodeType::Extension(u) => { + let write_result = + self.get_node(u.chd()).and_then(|mut b_ref| { + b_ref + .write(|b| { + let branch = + b.inner.as_branch_mut().unwrap(); + branch.value = Some(Data(val)); + + b.rehash() + }) + // if writing fails, delete the child? + .or_else(|_| { + let node = self.put_node(b_ref.clone())?; + + let child = u.chd_mut(); + *child = node.as_ptr(); + + deleted.push(b_ref.as_ptr()); + + Ok(()) + }) + }); + + if let Err(e) = write_result { + result = Err(e); + } } + NodeType::Branch(_) => unreachable!(), } - _ => unreachable!(), - } - u.rehash(); - }, - parents, - deleted - ); - return result; - } + u.rehash(); + }, + parents, + deleted + ); - let (leaf_ptr, prefix, idx, v) = if rem_path.len() < n_path.len() { - // key path is a prefix of the path to u - u_ref - .write(|u| { - (*match &mut u.inner { - NodeType::Leaf(u) => &mut u.0, - NodeType::Extension(u) => &mut u.path, - _ => unreachable!(), - }) = PartialPath(n_path[rem_path.len() + 1..].to_vec()); - u.rehash(); - }) - .unwrap(); - ( - match &u_ref.inner { - NodeType::Extension(u) => { - if u.path.len() == 0 { - deleted.push(u_ptr); - u.chd() - } else { - u_ptr - } + return result; + } + + // if the node-path is greater than the insert path + (Ordering::Less, _) => { + // key path is a prefix of the path to u + node_to_split + .write(|u| { + // TODO: handle unwraps better + let path = u.inner.path_mut().unwrap(); + *path = PartialPath(n_path[insert_path.len() + 1..].to_vec()); + + u.rehash(); + }) + .unwrap(); + + let leaf_address = match &node_to_split.inner { + NodeType::Extension(u) if u.path.len() == 0 => { + deleted.push(node_to_split_address); + u.chd() } - _ => u_ptr, - }, - rem_path, - n_path[rem_path.len()], - Some(Data(val)), - ) - } else { - // key path extends the path to u - if n_value.is_none() { - // this case does not apply to an extension node, resume the tree walk - return Ok(Some(val)); + _ => node_to_split_address, + }; + + ( + leaf_address, + insert_path, + n_path[insert_path.len()] as usize, + Data(val).into(), + ) + } + // insert path is greather than the path of the leaf + (Ordering::Greater, Some(n_value)) => { + let leaf = Node::leaf( + PartialPath(insert_path[n_path.len() + 1..].to_vec()), + Data(val), + ); + + let leaf_address = self.put_node(leaf)?.as_ptr(); + + deleted.push(node_to_split_address); + + ( + leaf_address, + n_path.as_slice(), + insert_path[n_path.len()] as usize, + n_value.into(), + ) } - let leaf = self.new_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(rem_path[n_path.len() + 1..].to_vec()), - Data(val), - ))))?; - deleted.push(u_ptr); - (leaf.as_ptr(), &n_path[..], rem_path[n_path.len()], n_value) }; - drop(u_ref); - // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] - let mut chd = [None; NBRANCH]; - chd[idx as usize] = Some(leaf_ptr); - let branch_ptr = self - .new_node(Node::new(NodeType::Branch(BranchNode { - chd, - value: v, - chd_encoded: Default::default(), - })))? - .as_ptr(); - if !prefix.is_empty() { - self.new_node(Node::new(NodeType::Extension(ExtNode { - path: PartialPath(prefix.to_vec()), - child: branch_ptr, - child_encoded: None, - })))? - .as_ptr() - } else { - branch_ptr - } + + // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] + let mut children = [None; NBRANCH]; + + children[idx] = leaf_address.into(); + + let branch_address = self + .put_node(Node::branch(BranchNode { + children, + value, + children_encoded: Default::default(), + }))? + .as_ptr(); + + if !prefix.is_empty() { + self.put_node(Node::new(NodeType::Extension(ExtNode { + path: PartialPath(prefix.to_vec()), + child: branch_address, + child_encoded: None, + })))? + .as_ptr() + } else { + branch_address } }; + // observation: // - leaf/extension node can only be the child of a branch node // - branch node can only be the child of a branch/extension node - set_parent(new_chd, parents); + // ^^^ I think a leaf can end up being the child of an extension node + // ^^^ maybe just on delete though? I'm not sure, removing extension-nodes anyway + set_parent(new_child_address, parents); + Ok(None) } @@ -390,13 +421,13 @@ impl + Send + Sync> Merkle { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done - NodeType::Branch(n) => match n.chd[current_nibble as usize] { + NodeType::Branch(n) => match n.children[current_nibble as usize] { Some(c) => (node, c), None => { // insert the leaf to the empty slot // create a new leaf let leaf_ptr = self - .new_node(Node::new(NodeType::Leaf(LeafNode( + .put_node(Node::new(NodeType::Leaf(LeafNode( PartialPath(key_nibbles.collect()), Data(val), ))))? @@ -404,7 +435,7 @@ impl + Send + Sync> Merkle { // set the current child to point to this leaf node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); - uu.chd[current_nibble as usize] = Some(leaf_ptr); + uu.children[current_nibble as usize] = Some(leaf_ptr); u.rehash(); }) .unwrap(); @@ -534,10 +565,10 @@ impl + Send + Sync> Merkle { chd[idx as usize] = Some(c_ptr); let branch = self - .new_node(Node::new(NodeType::Branch(BranchNode { - chd, + .put_node(Node::new(NodeType::Branch(BranchNode { + children: chd, value: Some(Data(val)), - chd_encoded: Default::default(), + children_encoded: Default::default(), })))? .as_ptr(); @@ -558,7 +589,7 @@ impl + Send + Sync> Merkle { // the immediate parent of a leaf must be a branch b_ref .write(|b| { - b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = None; + b.inner.as_branch_mut().unwrap().children[b_idx as usize] = None; b.rehash() }) .unwrap(); @@ -578,14 +609,14 @@ impl + Send + Sync> Merkle { // from: [p: Branch] -> [b (v)]x -> [Leaf]x // to: [p: Branch] -> [Leaf (v)] let leaf = self - .new_node(Node::new(NodeType::Leaf(LeafNode( + .put_node(Node::new(NodeType::Leaf(LeafNode( PartialPath(Vec::new()), val, ))))? .as_ptr(); p_ref .write(|p| { - p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = Some(leaf); + p.inner.as_branch_mut().unwrap().children[p_idx as usize] = Some(leaf); p.rehash() }) .unwrap(); @@ -594,7 +625,7 @@ impl + Send + Sync> Merkle { // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x // to: P -> [Leaf (v)] let leaf = self - .new_node(Node::new(NodeType::Leaf(LeafNode( + .put_node(Node::new(NodeType::Leaf(LeafNode( PartialPath(n.path.clone().into_inner()), val, ))))? @@ -618,7 +649,7 @@ impl + Send + Sync> Merkle { // \____[Leaf]x // to: [p: Branch] -> [Ext] -> [Branch] let ext = self - .new_node(Node::new(NodeType::Extension(ExtNode { + .put_node(Node::new(NodeType::Extension(ExtNode { path: PartialPath(vec![idx]), child: c_ptr, child_encoded: None, @@ -669,7 +700,7 @@ impl + Send + Sync> Merkle { let c_ptr = if write_result.is_err() { deleted.push(c_ptr); - self.new_node(c_ref.clone())?.as_ptr() + self.put_node(c_ref.clone())?.as_ptr() } else { c_ptr }; @@ -678,7 +709,7 @@ impl + Send + Sync> Merkle { p_ref .write(|p| { - p.inner.as_branch_mut().unwrap().chd[p_idx as usize] = + p.inner.as_branch_mut().unwrap().children[p_idx as usize] = Some(c_ptr); p.rehash() }) @@ -746,8 +777,8 @@ impl + Send + Sync> Merkle { NodeType::Branch(n) => { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] - n.chd[b_idx as usize] = Some( - self.new_node(Node::new(NodeType::Extension(ExtNode { + n.children[b_idx as usize] = Some( + self.put_node(Node::new(NodeType::Extension(ExtNode { path: PartialPath(vec![idx]), child: c_ptr, child_encoded: None, @@ -792,14 +823,14 @@ impl + Send + Sync> Merkle { }); if write_result.is_err() { deleted.push(c_ptr); - self.new_node(c_ref.clone())?.as_ptr() + self.put_node(c_ref.clone())?.as_ptr() } else { c_ptr }; drop(c_ref); b_ref .write(|b| { - b.inner.as_branch_mut().unwrap().chd[b_idx as usize] = Some(c_ptr); + b.inner.as_branch_mut().unwrap().children[b_idx as usize] = Some(c_ptr); b.rehash() }) .unwrap(); @@ -822,7 +853,7 @@ impl + Send + Sync> Merkle { let c_ptr = if write_result.is_err() { deleted.push(c_ptr); - self.new_node(c_ref.clone())?.as_ptr() + self.put_node(c_ref.clone())?.as_ptr() } else { c_ptr }; @@ -861,7 +892,7 @@ impl + Send + Sync> Merkle { continue; } let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[*nib as usize] { + NodeType::Branch(n) => match n.children[*nib as usize] { Some(c) => c, None => return Ok(None), }, @@ -938,7 +969,7 @@ impl + Send + Sync> Merkle { let u_ref = self.get_node(u)?; match &u_ref.inner { NodeType::Branch(n) => { - for c in n.chd.iter().flatten() { + for c in n.children.iter().flatten() { self.remove_tree_(*c, deleted)? } } @@ -984,7 +1015,7 @@ impl + Send + Sync> Merkle { continue; } let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[*nib as usize] { + NodeType::Branch(n) => match n.children[*nib as usize] { Some(c) => c, None => return Ok(None), }, @@ -1053,7 +1084,7 @@ impl + Send + Sync> Merkle { .inner .as_branch() .ok_or(MerkleError::NotBranchNode)? - .chd[0]; + .children[0]; let mut u_ref = match root { Some(root) => self.get_node(root)?, None => return Ok(Proof(proofs)), @@ -1068,7 +1099,7 @@ impl + Send + Sync> Merkle { } nodes.push(u_ref.as_ptr()); let next_ptr: DiskAddress = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[nib as usize] { + NodeType::Branch(n) => match n.children[nib as usize] { Some(c) => c, None => break, }, @@ -1138,7 +1169,7 @@ impl + Send + Sync> Merkle { continue; } let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.chd[nib as usize] { + NodeType::Branch(n) => match n.children[nib as usize] { Some(c) => c, None => return Ok(None), }, @@ -1191,7 +1222,7 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { p_ref .write(|p| { match &mut p.inner { - NodeType::Branch(pp) => pp.chd[*idx as usize] = Some(new_chd), + NodeType::Branch(pp) => pp.children[*idx as usize] = Some(new_chd), NodeType::Extension(pp) => *pp.chd_mut() = new_chd, _ => unreachable!(), } @@ -1387,18 +1418,18 @@ mod test { None, None, NodeType::Branch(BranchNode { - chd: chd0, + children: chd0, value: Some(Data("hello, world!".as_bytes().to_vec())), - chd_encoded: Default::default(), + children_encoded: Default::default(), }), ), Node::new_from_hash( None, None, NodeType::Branch(BranchNode { - chd: chd1, + children: chd1, value: None, - chd_encoded, + children_encoded: chd_encoded, }), ), ] { @@ -1441,7 +1472,7 @@ mod test { PartialPath(vec![0x1, 0x2, 0x3]), Data(vec![0x4, 0x5]), ))); - let chd_ref = merkle.new_node(chd.clone()).unwrap(); + let chd_ref = merkle.put_node(chd.clone()).unwrap(); let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); @@ -1450,12 +1481,12 @@ mod test { let mut chd_encoded: [Option>; NBRANCH] = Default::default(); chd_encoded[0] = Some(new_chd_encoded.to_vec()); let node = Node::new(NodeType::Branch(BranchNode { - chd: [None; NBRANCH], + children: [None; NBRANCH], value: Some(Data("value1".as_bytes().to_vec())), - chd_encoded, + children_encoded: chd_encoded, })); - let node_ref = merkle.new_node(node.clone()).unwrap(); + let node_ref = merkle.put_node(node.clone()).unwrap(); let r = node_ref.get_encoded(merkle.store.as_ref()); let new_node = Node::new(NodeType::decode(r).unwrap()); @@ -1465,11 +1496,11 @@ mod test { { let chd = Node::new(NodeType::Branch(BranchNode { - chd: [None; NBRANCH], + children: [None; NBRANCH], value: Some(Data("value1".as_bytes().to_vec())), - chd_encoded: Default::default(), + children_encoded: Default::default(), })); - let chd_ref = merkle.new_node(chd.clone()).unwrap(); + let chd_ref = merkle.put_node(chd.clone()).unwrap(); let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); @@ -1480,7 +1511,7 @@ mod test { child: DiskAddress::null(), child_encoded: Some(chd_encoded.to_vec()), })); - let node_ref = merkle.new_node(node.clone()).unwrap(); + let node_ref = merkle.put_node(node.clone()).unwrap(); let r = node_ref.get_encoded(merkle.store.as_ref()); let new_node = Node::new(NodeType::decode(r).unwrap()); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 81967b572778..f4aacbcc4bcd 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -59,20 +59,20 @@ impl> Encoded { #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { - pub(super) chd: [Option; NBRANCH], + pub(super) children: [Option; NBRANCH], pub(super) value: Option, - pub(super) chd_encoded: [Option>; NBRANCH], + pub(super) children_encoded: [Option>; NBRANCH], } impl Debug for BranchNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "[Branch")?; - for (i, c) in self.chd.iter().enumerate() { + for (i, c) in self.children.iter().enumerate() { if let Some(c) = c { write!(f, " ({i:x} {c:?})")?; } } - for (i, c) in self.chd_encoded.iter().enumerate() { + for (i, c) in self.children_encoded.iter().enumerate() { if let Some(c) = c { write!(f, " ({i:x} {:?})", c)?; } @@ -92,7 +92,7 @@ impl BranchNode { pub(super) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { let mut has_chd = false; let mut only_chd = None; - for (i, c) in self.chd.iter().enumerate() { + for (i, c) in self.children.iter().enumerate() { if c.is_some() { has_chd = true; if only_chd.is_some() { @@ -128,7 +128,7 @@ impl BranchNode { fn encode>(&self, store: &S) -> Vec { let mut list = <[Encoded>; NBRANCH + 1]>::default(); - for (i, c) in self.chd.iter().enumerate() { + for (i, c) in self.children.iter().enumerate() { match c { Some(c) => { let mut c_ref = store.get_item(*c).unwrap(); @@ -153,7 +153,7 @@ impl BranchNode { None => { // Check if there is already a calculated encoded value for the child, which // can happen when manually constructing a trie from proof. - if let Some(v) = &self.chd_encoded[i] { + if let Some(v) = &self.children_encoded[i] { if v.len() == TRIE_HASH_LEN { list[i] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); @@ -180,9 +180,9 @@ impl BranchNode { chd_encoded: [Option>; NBRANCH], ) -> Self { BranchNode { - chd, + children: chd, value: value.map(Data), - chd_encoded, + children_encoded: chd_encoded, } } @@ -191,19 +191,19 @@ impl BranchNode { } pub fn chd(&self) -> &[Option; NBRANCH] { - &self.chd + &self.children } pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { - &mut self.chd + &mut self.children } pub fn chd_encode(&self) -> &[Option>; NBRANCH] { - &self.chd_encoded + &self.children_encoded } pub fn chd_encoded_mut(&mut self) -> &mut [Option>; NBRANCH] { - &mut self.chd_encoded + &mut self.children_encoded } } @@ -370,14 +370,6 @@ pub enum NodeType { } impl NodeType { - pub fn encode>(&self, store: &S) -> Vec { - match &self { - NodeType::Leaf(n) => n.encode(), - NodeType::Extension(n) => n.encode(store), - NodeType::Branch(n) => n.encode(store), - } - } - pub fn decode(buf: &[u8]) -> Result { let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; @@ -409,6 +401,24 @@ impl NodeType { )))), } } + + pub fn encode>(&self, store: &S) -> Vec { + match &self { + NodeType::Leaf(n) => n.encode(), + NodeType::Extension(n) => n.encode(store), + NodeType::Branch(n) => n.encode(store), + } + } + + pub fn path_mut(&mut self) -> Option<&mut PartialPath> { + let path = match self { + NodeType::Branch(_) => return None, + NodeType::Leaf(node) => &mut node.0, + NodeType::Extension(node) => &mut node.path, + }; + + path.into() + } } impl Node { @@ -424,9 +434,9 @@ impl Node { is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner: NodeType::Branch(BranchNode { - chd: [Some(DiskAddress::null()); NBRANCH], + children: [Some(DiskAddress::null()); NBRANCH], value: Some(Data(Vec::new())), - chd_encoded: Default::default(), + children_encoded: Default::default(), }), lazy_dirty: AtomicBool::new(false), } @@ -470,6 +480,14 @@ impl Node { s } + pub fn branch(node: BranchNode) -> Self { + Self::new(NodeType::Branch(node)) + } + + pub fn leaf(path: PartialPath, data: Data) -> Self { + Self::new(NodeType::Leaf(LeafNode(path, data))) + } + pub fn inner(&self) -> &NodeType { &self.inner } @@ -598,9 +616,9 @@ impl Storable for Node { root_hash, is_encoded_longer_than_hash_len, NodeType::Branch(BranchNode { - chd, + children: chd, value, - chd_encoded, + children_encoded: chd_encoded, }), )) } @@ -724,7 +742,7 @@ impl Storable for Node { + match &self.inner { NodeType::Branch(n) => { let mut encoded_len = 0; - for emcoded in n.chd_encoded.iter() { + for emcoded in n.children_encoded.iter() { encoded_len += match emcoded { Some(v) => 1 + v.len() as u64, None => 1, @@ -773,7 +791,7 @@ impl Storable for Node { match &self.inner { NodeType::Branch(n) => { cur.write_all(&[Self::BRANCH_NODE]).unwrap(); - for c in n.chd.iter() { + for c in n.children.iter() { cur.write_all(&match c { Some(p) => p.to_le_bytes(), None => 0u64.to_le_bytes(), @@ -790,7 +808,7 @@ impl Storable for Node { } // Since child encoding will only be unset after initialization (only used for range proof), // it is fine to encode its value adjacent to other fields. Same for extention node. - for encoded in n.chd_encoded.iter() { + for encoded in n.children_encoded.iter() { match encoded { Some(v) => { cur.write_all(&[v.len() as u8])?; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index b813ec3fab05..6f89f8377665 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -449,7 +449,7 @@ impl + Send> Proof { ) -> Result<(DiskAddress, Option, usize), ProofError> { let node = NodeType::decode(buf)?; let new_node = merkle - .new_node(Node::new(node)) + .put_node(Node::new(node)) .map_err(ProofError::InvalidNode)?; let addr = new_node.as_ptr(); match new_node.inner() { From d1a7c4bb80ca5b0364fc238adae971f3fc9e7c9b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 16 Oct 2023 09:54:20 -0700 Subject: [PATCH 0335/1053] Fix up example, use new async interface (#314) --- .github/workflows/ci.yaml | 4 +- firewood/examples/insert.rs | 80 ++++++++++++ firewood/examples/rev.rs | 242 ------------------------------------ firewood/src/db.rs | 2 +- firewood/src/db/proposal.rs | 2 +- 5 files changed, 84 insertions(+), 246 deletions(-) create mode 100644 firewood/examples/insert.rs delete mode 100644 firewood/examples/rev.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e26b9094deed..2388117db346 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -145,8 +145,8 @@ jobs: # benchmarks were not being done in --release mode, we can enable this again later # - name: Run benchmark example # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - - name: Run rev example - run: RUST_BACKTRACE=1 cargo run --example rev + - name: Run insert example + run: RUST_BACKTRACE=1 cargo run --example insert docs: needs: build diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs new file mode 100644 index 000000000000..a297aab69c3e --- /dev/null +++ b/firewood/examples/insert.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// This example isn't an actual benchmark, it's just an example of how to +// insert some random keys using the front-end API. + +use clap::Parser; +use std::{error::Error, ops::RangeInclusive, sync::Arc, time::Instant}; + +use firewood::{ + db::{Batch, BatchOp, Db, DbConfig}, + v2::api::{Db as DbApi, Proposal}, +}; +use rand::{distributions::Alphanumeric, Rng}; + +#[derive(Parser, Debug)] +struct Args { + #[arg(short, long, default_value = "1-64", value_parser = string_to_range)] + keylen: RangeInclusive, + #[arg(short, long, default_value = "32", value_parser = string_to_range)] + datalen: RangeInclusive, + #[arg(short, long, default_value_t = 1)] + batch_keys: usize, + #[arg(short, long, default_value_t = 100)] + inserts: usize, +} + +fn string_to_range(input: &str) -> Result, Box> { + //::Err> { + let parts: Vec<&str> = input.split('-').collect(); + match parts.len() { + 1 => Ok(input.parse()?..=input.parse()?), + 2 => Ok(parts[0].parse()?..=parts[1].parse()?), + _ => Err("Too many dashes in input string".into()), + } +} + +/// cargo run --release --example insert +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), Box> { + let cfg = DbConfig::builder().truncate(true).build(); + + let args = Args::parse(); + + let db = tokio::task::spawn_blocking(move || { + Db::new("rev_db", &cfg).expect("db initiation should succeed") + }) + .await + .unwrap(); + + let keys = args.batch_keys; + let start = Instant::now(); + + for _ in 0..args.inserts { + let keylen = rand::thread_rng().gen_range(args.keylen.clone()); + let datalen = rand::thread_rng().gen_range(args.datalen.clone()); + let batch: Batch, Vec> = (0..keys) + .map(|_| { + ( + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(keylen as usize) + .collect::>(), + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(datalen as usize) + .collect::>(), + ) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect(); + let proposal: Arc = db.propose(batch).await.unwrap().into(); + proposal.commit().await?; + } + + let duration = start.elapsed(); + println!("Generated and inserted {keys} in {duration:?}"); + + Ok(()) +} diff --git a/firewood/examples/rev.rs b/firewood/examples/rev.rs deleted file mode 100644 index aa0fd917cc01..000000000000 --- a/firewood/examples/rev.rs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::{collections::VecDeque, path::Path}; - -use firewood::{ - db::{BatchOp, Db, DbConfig, DbError, Proposal, Revision, WalConfig}, - merkle::{Node, TrieHash}, - storage::StoreRevShared, - v2::api::{KeyType, Proof, ValueType}, -}; -use shale::compact::CompactSpace; - -type SharedStore = CompactSpace; - -/// cargo run --example rev -fn main() -> Result<(), DbError> { - let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - - let db = Db::new("rev_db", &cfg.clone().truncate(true).build()) - .expect("db initiation should succeed"); - let items = vec![("dof", "verb"), ("doe", "reindeer"), ("dog", "puppy")]; - - let mut revision_tracker = RevisionTracker::new(db); - - revision_tracker.create_revisions(items.into_iter())?; - - revision_tracker.db.kv_dump(&mut std::io::stdout())?; - - verify_root_hashes(&mut revision_tracker); - - let revision_tracker = revision_tracker.with_new_db("rev_db", &cfg.truncate(false).build()); - - let revision = revision_tracker.get_revision(0); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-0 should exist"); - println!("{revision_root_hash:?}"); - - let current_root_hash = revision_tracker - .db - .kv_root_hash() - .expect("root-hash for current state should exist"); - // The following is true as long as the current state is fresh after replaying from Wals. - assert_eq!(revision_root_hash, current_root_hash); - - let rev1 = revision_tracker.get_revision(1); - let rev2 = revision_tracker.get_revision(2); - - std::thread::scope(move |scope| { - scope.spawn(|| { - let revision = rev1; - revision.kv_dump(&mut std::io::stdout()).unwrap(); - - let mut items_rev = vec![("dof", "verb"), ("doe", "reindeer")]; - items_rev.sort(); - - let items = items_rev; - std::thread::scope(|scope| { - for _ in 0..3 { - scope.spawn(|| { - let (keys, vals) = items.iter().cloned().unzip(); - - let proof = build_proof(&revision, &items); - revision - .verify_range_proof( - proof, - items.first().unwrap().0, - items.last().unwrap().0, - keys, - vals, - ) - .unwrap(); - }); - } - }) - }); - - scope.spawn(|| { - let revision = rev2; - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-2 should exist"); - println!("{revision_root_hash:?}"); - revision.kv_dump(&mut std::io::stdout()).unwrap(); - - let mut items_rev = vec![("dof", "verb")]; - items_rev.sort(); - let items = items_rev; - - let (keys, vals) = items.iter().cloned().unzip(); - - let proof = build_proof(&revision, &items); - revision - .verify_range_proof( - proof, - items.first().unwrap().0, - items.last().unwrap().0, - keys, - vals, - ) - .unwrap(); - }); - }); - Ok(()) -} - -struct RevisionTracker { - hashes: VecDeque, - db: Db, -} - -impl RevisionTracker { - fn new(db: Db) -> Self { - Self { - hashes: VecDeque::new(), - db, - } - } - - fn create_revisions( - &mut self, - mut iter: impl Iterator, - ) -> Result<(), DbError> - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - { - iter.try_for_each(|(k, v)| self.create_revision(k, v)) - } - - fn create_revision(&mut self, k: K, v: V) -> Result<(), DbError> - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - { - let batch = vec![BatchOp::Put { - key: k, - value: v.as_ref().to_vec(), - }]; - self.db.new_proposal(batch)?.commit_sync()?; - - let hash = self.db.kv_root_hash().expect("root-hash should exist"); - self.hashes.push_front(hash); - Ok(()) - } - - fn commit_proposal(&mut self, proposal: Proposal) { - proposal.commit_sync().unwrap(); - let hash = self.db.kv_root_hash().expect("root-hash should exist"); - self.hashes.push_front(hash); - } - - fn with_new_db(self, path: impl AsRef, cfg: &DbConfig) -> Self { - let hashes = { - // must name db variable to explicitly drop - let Self { hashes, db: _db } = self; - hashes - }; - - let db = Db::new(path, cfg).expect("db initiation should succeed"); - - Self { hashes, db } - } - - fn get_revision(&self, index: usize) -> Revision { - self.db - .get_revision(&self.hashes[index]) - .unwrap_or_else(|| panic!("revision-{index} should exist")) - } -} - -fn build_proof(revision: &Revision, items: &[(&str, &str)]) -> Proof> { - let mut proof = revision.prove(items[0].0).unwrap(); - let end = revision.prove(items.last().unwrap().0).unwrap(); - proof.concat_proofs(end); - proof -} - -fn verify_root_hashes(revision_tracker: &mut RevisionTracker) { - let revision = revision_tracker.get_revision(0); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-0 should exist"); - println!("{revision_root_hash:?}"); - - let current_root_hash = revision_tracker - .db - .kv_root_hash() - .expect("root-hash for current state should exist"); - - // The following should always hold. - assert_eq!(revision_root_hash, current_root_hash); - - let revision = revision_tracker.get_revision(2); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-2 should exist"); - println!("{revision_root_hash:?}"); - - // Get a revision while a batch is active. - let revision = revision_tracker.get_revision(1); - let revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - println!("{revision_root_hash:?}"); - - let batch = vec![BatchOp::Put { - key: "k", - value: vec![b'v'], - }]; - let proposal = revision_tracker.db.new_proposal(batch).unwrap(); - - let actual_revision_root_hash = revision_tracker - .get_revision(1) - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - assert_eq!(revision_root_hash, actual_revision_root_hash); - - // Uncommitted changes of proposal cannot be seen from db. - assert!(revision_tracker.db.kv_get("k").is_err()); - - revision_tracker.commit_proposal(proposal); - - let new_revision_root_hash = revision_tracker - .get_revision(1) - .kv_root_hash() - .expect("root-hash for revision-1 should exist"); - assert_ne!(revision_root_hash, new_revision_root_hash); - let val = revision_tracker.db.kv_get("k").unwrap(); - assert_eq!("v".as_bytes().to_vec(), val); - - // When reading a specific revision, after new commits the revision remains consistent. - let val = revision.kv_get("k"); - assert_eq!(None, val); - let val = revision.kv_get("dof").unwrap(); - assert_eq!("verb".as_bytes().to_vec(), val); - let actual_revision_root_hash = revision - .kv_root_hash() - .expect("root-hash for revision-2 should exist"); - assert_eq!(revision_root_hash, actual_revision_root_hash); -} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b129391e3ea9..1ac78e193d57 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -409,7 +409,7 @@ impl api::Db for Db { &self, batch: api::Batch, ) -> Result { - self.new_proposal(batch).map_err(Into::into) + block_in_place(|| self.new_proposal(batch)).map_err(Into::into) } } diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 1af6f20371be..42ee06f9f30d 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -55,7 +55,7 @@ impl crate::v2::api::Proposal for Proposal { self: Arc, data: api::Batch, ) -> Result { - self.propose_sync(data).map_err(Into::into) + block_in_place(|| self.propose_sync(data)).map_err(Into::into) } } From d43b95f408b36b20d40c9accccccb17b70e00c4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:04:29 -0700 Subject: [PATCH 0336/1053] build(deps): update typed-builder requirement from 0.16.0 to 0.17.0 (#320) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 829f05f0ec7a..0b613a04e5de 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } -typed-builder = "0.16.0" +typed-builder = "0.17.0" bincode = "1.3.3" [dev-dependencies] From d31113c57c50498eb4acf36d7a7ce03ed1dff572 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 17 Oct 2023 09:38:26 -0700 Subject: [PATCH 0337/1053] Add verify option to insert example (#321) --- firewood/examples/insert.rs | 47 ++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index a297aab69c3e..a65e8357e01c 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,11 +5,11 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::{error::Error, ops::RangeInclusive, sync::Arc, time::Instant}; +use std::{collections::HashMap, error::Error, ops::RangeInclusive, sync::Arc, time::Instant}; use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, - v2::api::{Db as DbApi, Proposal}, + v2::api::{Db as DbApi, DbView, Proposal}, }; use rand::{distributions::Alphanumeric, Rng}; @@ -23,6 +23,8 @@ struct Args { batch_keys: usize, #[arg(short, long, default_value_t = 100)] inserts: usize, + #[arg(short, long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] + read_verify_percent: u16, } fn string_to_range(input: &str) -> Result, Box> { @@ -69,12 +71,51 @@ async fn main() -> Result<(), Box> { }) .map(|(key, value)| BatchOp::Put { key, value }) .collect(); + + let verify = get_keys_to_verify(&batch, args.read_verify_percent); + let proposal: Arc = db.propose(batch).await.unwrap().into(); proposal.commit().await?; + verify_keys(&db, verify).await?; } let duration = start.elapsed(); - println!("Generated and inserted {keys} in {duration:?}"); + println!( + "Generated and inserted {} batches of size {keys} in {duration:?}", + args.inserts + ); + + Ok(()) +} +fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec> { + if pct == 0 { + HashMap::new() + } else { + batch + .iter() + .filter(|_last_key| rand::thread_rng().gen_range(0..=(100 - pct)) == 0) + .map(|op| { + if let BatchOp::Put { key, value } = op { + (key.clone(), value.clone()) + } else { + unreachable!() + } + }) + .collect() + } +} + +async fn verify_keys( + db: &Db, + verify: HashMap, Vec>, +) -> Result<(), firewood::v2::api::Error> { + if !verify.is_empty() { + let hash = db.root_hash().await?; + let revision = db.revision(hash).await?; + for (key, value) in verify { + assert_eq!(*value, revision.val(key).await?.unwrap()); + } + } Ok(()) } From 2eea64c588636ac54ed25047aad58f91d5b4fe91 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 17 Oct 2023 13:03:34 -0700 Subject: [PATCH 0338/1053] Rkuris/dead code cleanup (#322) Co-authored-by: Richard Pringle --- firewood/src/lib.rs | 1 - firewood/src/service/client.rs | 201 --------------------------------- firewood/src/service/mod.rs | 58 ---------- firewood/src/service/server.rs | 89 --------------- firewood/src/v2/db.rs | 30 ----- 5 files changed, 379 deletions(-) delete mode 100644 firewood/src/service/client.rs delete mode 100644 firewood/src/service/mod.rs delete mode 100644 firewood/src/service/server.rs diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 2007dfcded7e..24682eda735c 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -194,6 +194,5 @@ pub mod storage; pub mod api; pub(crate) mod config; pub mod nibbles; -pub mod service; pub mod v2; diff --git a/firewood/src/service/client.rs b/firewood/src/service/client.rs deleted file mode 100644 index b1d7b38ac015..000000000000 --- a/firewood/src/service/client.rs +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -/// Client side connection structure -/// -/// A connection is used to send messages to the firewood thread. -use std::fmt::Debug; -use std::mem::take; -use std::thread::JoinHandle; -use std::{path::Path, thread}; - -use tokio::sync::{mpsc, oneshot}; - -use crate::api::Revision; - -use crate::merkle::MerkleError; -use crate::v2::api::Proof; -use crate::{ - db::{DbConfig, DbError}, - merkle::TrieHash, -}; -use async_trait::async_trait; - -use super::server::FirewoodService; -use super::{Request, RevRequest, RevisionHandle}; - -/// A `Connection` represents a connection to the thread running firewood -/// The type specified is how you want to refer to your key values; this is -/// something like `Vec` or `&[u8]` -#[derive(Debug)] -pub struct Connection { - sender: Option>>, - handle: Option>, -} - -impl Drop for Connection { - fn drop(&mut self) { - drop(take(&mut self.sender)); - take(&mut self.handle) - .unwrap() - .join() - .expect("Couldn't join with the firewood thread"); - } -} - -impl Connection { - #[allow(dead_code)] - fn new>(path: P, cfg: DbConfig) -> Self { - let (sender, receiver) = mpsc::channel(1_000) - as ( - tokio::sync::mpsc::Sender>, - tokio::sync::mpsc::Receiver>, - ); - let owned_path = path.as_ref().to_path_buf(); - let handle = thread::Builder::new() - .name("firewood-receiver".to_owned()) - .spawn(move || FirewoodService::new(receiver, owned_path, cfg)) - .expect("thread creation failed"); - Self { - sender: Some(sender), - handle: Some(handle), - } - } -} - -impl super::RevisionHandle { - pub async fn close(self) { - let _ = self - .sender - .send(Request::RevRequest(RevRequest::Drop { handle: self.id })) - .await; - } -} - -#[async_trait] -impl Revision for super::RevisionHandle { - async fn kv_root_hash(&self) -> Result { - let (send, recv) = oneshot::channel(); - let msg = Request::RevRequest(RevRequest::RootHash { - handle: self.id, - respond_to: send, - }); - let _ = self.sender.send(msg).await; - recv.await.expect("Actor task has been killed") - } - - async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError> { - let (send, recv) = oneshot::channel(); - let _ = Request::RevRequest::(RevRequest::Get { - handle: self.id, - key: key.as_ref().to_vec(), - respond_to: send, - }); - recv.await.expect("Actor task has been killed") - } - - async fn prove + Send + Sync>(&self, key: K) -> Result, MerkleError> { - let (send, recv) = oneshot::channel(); - let msg = Request::RevRequest(RevRequest::Prove { - handle: self.id, - key: key.as_ref().to_vec(), - respond_to: send, - }); - self.sender.send(msg).await.expect("channel failed"); - recv.await.expect("channel failed") - } - - async fn verify_range_proof + Send + Sync>( - &self, - _proof: Proof, - _first_key: K, - _last_key: K, - _keys: Vec, - _values: Vec, - ) { - todo!() - } - async fn root_hash(&self) -> Result { - let (send, recv) = oneshot::channel(); - let msg = Request::RevRequest(RevRequest::RootHash { - handle: self.id, - respond_to: send, - }); - self.sender.send(msg).await.expect("channel failed"); - recv.await.expect("channel failed") - } - - async fn dump(&self, _writer: W) -> Result<(), DbError> { - todo!() - } - - #[cfg(feature = "eth")] - async fn dump_account + Send + Sync>( - &self, - _key: K, - _writer: W, - ) -> Result<(), DbError> { - todo!() - } - - async fn kv_dump(&self, _writer: W) -> Result<(), DbError> { - unimplemented!(); - } - - #[cfg(feature = "eth")] - async fn get_balance + Send + Sync>( - &self, - _key: K, - ) -> Result { - todo!() - } - - #[cfg(feature = "eth")] - async fn get_code + Send + Sync>(&self, _key: K) -> Result, DbError> { - todo!() - } - - #[cfg(feature = "eth")] - async fn get_nonce + Send + Sync>( - &self, - _key: K, - ) -> Result { - todo!() - } - - #[cfg(feature = "eth")] - async fn get_state + Send + Sync>( - &self, - _key: K, - _sub_key: K, - ) -> Result, DbError> { - todo!() - } -} - -#[async_trait] -impl crate::api::Db, N> for Connection -where - tokio::sync::mpsc::Sender>: From>>, -{ - async fn get_revision(&self, root_hash: TrieHash) -> Option> { - let (send, recv) = oneshot::channel(); - let msg = Request::NewRevision { - root_hash, - respond_to: send, - }; - self.sender - .as_ref() - .unwrap() - .send(msg) - .await - .expect("channel failed"); - let id = recv.await.unwrap(); - id.map(|id| RevisionHandle { - sender: self.sender.as_ref().unwrap().clone(), - id, - }) - } -} - -// TODO: add a meaningful test with `Proposal` and `Revisions`. diff --git a/firewood/src/service/mod.rs b/firewood/src/service/mod.rs deleted file mode 100644 index 77587d744384..000000000000 --- a/firewood/src/service/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use tokio::sync::{mpsc, oneshot}; - -use crate::{ - db::DbError, - merkle::{MerkleError, TrieHash}, - v2::api::Proof, -}; - -mod client; -mod server; - -pub type BatchId = u32; -pub type RevId = u32; - -#[derive(Debug)] -pub struct RevisionHandle { - sender: mpsc::Sender>, - id: u32, -} - -/// Client side request object -#[derive(Debug)] -pub enum Request { - NewRevision { - root_hash: TrieHash, - respond_to: oneshot::Sender>, - }, - - RevRequest(RevRequest), -} - -type OwnedKey = Vec; -#[allow(dead_code)] -type OwnedVal = Vec; - -#[derive(Debug)] -pub enum RevRequest { - Get { - handle: RevId, - key: OwnedKey, - respond_to: oneshot::Sender, DbError>>, - }, - Prove { - handle: RevId, - key: OwnedKey, - respond_to: oneshot::Sender, MerkleError>>, - }, - RootHash { - handle: RevId, - respond_to: oneshot::Sender>, - }, - Drop { - handle: RevId, - }, -} diff --git a/firewood/src/service/server.rs b/firewood/src/service/server.rs deleted file mode 100644 index 043f0d76bfe4..000000000000 --- a/firewood/src/service/server.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::{ - collections::HashMap, - path::PathBuf, - sync::atomic::{AtomicU32, Ordering}, -}; -use tokio::sync::mpsc::Receiver; - -use crate::db::{Db, DbConfig, DbError}; - -use super::{Request, RevId}; - -macro_rules! get_rev { - ($rev: ident, $handle: ident, $out: expr) => { - match $rev.get(&$handle) { - None => { - let _ = $out.send(Err(DbError::InvalidParams)); - continue; - } - Some(x) => x, - } - }; -} -#[derive(Copy, Debug, Clone)] -pub struct FirewoodService {} - -impl FirewoodService { - pub fn new( - mut receiver: Receiver>, - owned_path: PathBuf, - cfg: DbConfig, - ) -> Self { - let db = Db::new(owned_path, &cfg).unwrap(); - let mut revs = HashMap::new(); - let lastid = AtomicU32::new(0); - loop { - let msg = match receiver.blocking_recv() { - Some(msg) => msg, - None => break, - }; - match msg { - Request::NewRevision { - root_hash, - respond_to, - } => { - let id: RevId = lastid.fetch_add(1, Ordering::Relaxed); - let msg = match db.get_revision(&root_hash) { - Some(rev) => { - revs.insert(id, rev); - Some(id) - } - None => None, - }; - let _ = respond_to.send(msg); - } - - Request::RevRequest(req) => match req { - super::RevRequest::Get { - handle, - key, - respond_to, - } => { - let rev = get_rev!(revs, handle, respond_to); - let msg = rev.kv_get(key); - let _ = respond_to.send(msg.map_or(Err(DbError::KeyNotFound), Ok)); - } - super::RevRequest::Prove { - handle: _handle, - key: _key, - respond_to: _respond_to, - } => { - todo!() - } - super::RevRequest::RootHash { handle, respond_to } => { - let rev = get_rev!(revs, handle, respond_to); - let msg = rev.kv_root_hash(); - let _ = respond_to.send(msg); - } - super::RevRequest::Drop { handle } => { - revs.remove(&handle); - } - }, - } - } - FirewoodService {} - } -} diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 89853eb86da4..a210d130201a 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -82,33 +82,3 @@ where Ok(proposal) } } - -#[derive(Debug, Default)] -pub struct DbView; - -#[async_trait] -impl api::DbView for DbView { - async fn root_hash(&self) -> Result { - todo!() - } - - async fn val(&self, _key: K) -> Result>, api::Error> { - todo!() - } - - async fn single_key_proof( - &self, - _key: K, - ) -> Result>>, api::Error> { - todo!() - } - - async fn range_proof( - &self, - _first_key: Option, - _last_key: Option, - _limit: usize, - ) -> Result>, api::Error> { - todo!() - } -} From 39d911e93344263829ee875f274edba10246bb6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:18:39 -0700 Subject: [PATCH 0339/1053] build(deps): update typed-builder requirement from 0.17.0 to 0.18.0 (#324) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 0b613a04e5de..9efa74ddc630 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.2" thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } -typed-builder = "0.17.0" +typed-builder = "0.18.0" bincode = "1.3.3" [dev-dependencies] From fc7fb021fdff3d254bb1bb01c895e985877af7bd Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 20 Oct 2023 14:42:07 -0400 Subject: [PATCH 0340/1053] Cleanup insert example verification (#325) --- firewood/examples/insert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index a65e8357e01c..56dc7e021e25 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -114,7 +114,7 @@ async fn verify_keys( let hash = db.root_hash().await?; let revision = db.revision(hash).await?; for (key, value) in verify { - assert_eq!(*value, revision.val(key).await?.unwrap()); + assert_eq!(Some(value), revision.val(key).await?); } } Ok(()) From 854e801c0eb0bb1b021171437c87bddc9c1cd9a8 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 24 Oct 2023 12:52:40 -0400 Subject: [PATCH 0341/1053] Insert test (#328) Signed-off-by: Richard Pringle Co-authored-by: Ron Kuris --- firewood/src/merkle.rs | 277 +++++++++------------------- firewood/src/merkle/node.rs | 105 +++++++++-- firewood/src/merkle/partial_path.rs | 18 ++ firewood/src/merkle/trie_hash.rs | 37 ++-- firewood/src/proof.rs | 2 +- 5 files changed, 222 insertions(+), 217 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 3bacd663cc02..04d43f8707bb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -79,11 +79,11 @@ impl + Send + Sync> Merkle { pub fn init_root(&self) -> Result { self.store .put_item( - Node::new(NodeType::Branch(BranchNode { + Node::branch(BranchNode { children: [None; NBRANCH], value: None, children_encoded: Default::default(), - })), + }), Node::max_branch_node_size(), ) .map_err(MerkleError::Shale) @@ -219,7 +219,7 @@ impl + Send + Sync> Merkle { let new_branch_address = self.put_node(new_branch)?.as_ptr(); if idx > 0 { - self.put_node(Node::new(NodeType::Extension(ExtNode { + self.put_node(Node::from(NodeType::Extension(ExtNode { path: PartialPath(matching_path[..idx].to_vec()), child: new_branch_address, child_encoded: None, @@ -347,7 +347,7 @@ impl + Send + Sync> Merkle { .as_ptr(); if !prefix.is_empty() { - self.put_node(Node::new(NodeType::Extension(ExtNode { + self.put_node(Node::from(NodeType::Extension(ExtNode { path: PartialPath(prefix.to_vec()), child: branch_address, child_encoded: None, @@ -427,10 +427,7 @@ impl + Send + Sync> Merkle { // insert the leaf to the empty slot // create a new leaf let leaf_ptr = self - .put_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(key_nibbles.collect()), - Data(val), - ))))? + .put_node(Node::leaf(PartialPath(key_nibbles.collect()), Data(val)))? .as_ptr(); // set the current child to point to this leaf node.write(|u| { @@ -565,11 +562,11 @@ impl + Send + Sync> Merkle { chd[idx as usize] = Some(c_ptr); let branch = self - .put_node(Node::new(NodeType::Branch(BranchNode { + .put_node(Node::branch(BranchNode { children: chd, value: Some(Data(val)), children_encoded: Default::default(), - })))? + }))? .as_ptr(); set_parent(branch, &mut parents); @@ -609,10 +606,7 @@ impl + Send + Sync> Merkle { // from: [p: Branch] -> [b (v)]x -> [Leaf]x // to: [p: Branch] -> [Leaf (v)] let leaf = self - .put_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(Vec::new()), - val, - ))))? + .put_node(Node::leaf(PartialPath(Vec::new()), val))? .as_ptr(); p_ref .write(|p| { @@ -625,10 +619,7 @@ impl + Send + Sync> Merkle { // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x // to: P -> [Leaf (v)] let leaf = self - .put_node(Node::new(NodeType::Leaf(LeafNode( - PartialPath(n.path.clone().into_inner()), - val, - ))))? + .put_node(Node::leaf(PartialPath(n.path.clone().into_inner()), val))? .as_ptr(); deleted.push(p_ptr); set_parent(leaf, parents); @@ -649,7 +640,7 @@ impl + Send + Sync> Merkle { // \____[Leaf]x // to: [p: Branch] -> [Ext] -> [Branch] let ext = self - .put_node(Node::new(NodeType::Extension(ExtNode { + .put_node(Node::from(NodeType::Extension(ExtNode { path: PartialPath(vec![idx]), child: c_ptr, child_encoded: None, @@ -778,7 +769,7 @@ impl + Send + Sync> Merkle { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] n.children[b_idx as usize] = Some( - self.put_node(Node::new(NodeType::Extension(ExtNode { + self.put_node(Node::from(NodeType::Extension(ExtNode { path: PartialPath(vec![idx]), child: c_ptr, child_encoded: None, @@ -1308,136 +1299,21 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { } #[cfg(test)] -mod test { +mod tests { use super::*; - use shale::cached::{DynamicMem, PlainMem}; - use shale::{CachedStore, Storable}; - use std::ops::Deref; + use node::tests::{extension, leaf}; + use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; use std::sync::Arc; use test_case::test_case; #[test_case(vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] #[test_case(vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf])] - fn test_to_nibbles(bytes: Vec, nibbles: Vec) { + fn to_nibbles(bytes: Vec, nibbles: Vec) { let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); assert_eq!(n, nibbles); } - const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); - - #[test] - fn test_hash_len() { - assert_eq!(TRIE_HASH_LEN, ZERO_HASH.dehydrated_len() as usize); - } - #[test] - fn test_dehydrate() { - let mut to = [1u8; TRIE_HASH_LEN]; - assert_eq!( - { - ZERO_HASH.dehydrate(&mut to).unwrap(); - &to - }, - ZERO_HASH.deref() - ); - } - - #[test] - fn test_hydrate() { - let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); - store.write(0, ZERO_HASH.deref()); - assert_eq!(TrieHash::hydrate(0, &store).unwrap(), ZERO_HASH); - } - #[test] - fn test_partial_path_encoding() { - let check = |steps: &[u8], term| { - let (d, t) = PartialPath::decode(&PartialPath(steps.to_vec()).encode(term)); - assert_eq!(d.0, steps); - assert_eq!(t, term); - }; - for steps in [ - vec![0x1, 0x2, 0x3, 0x4], - vec![0x1, 0x2, 0x3], - vec![0x0, 0x1, 0x2], - vec![0x1, 0x2], - vec![0x1], - ] { - for term in [true, false] { - check(&steps, term) - } - } - } - #[test] - fn test_merkle_node_encoding() { - let check = |node: Node| { - let mut bytes = vec![0; node.dehydrated_len() as usize]; - node.dehydrate(&mut bytes).unwrap(); - - let mut mem = PlainMem::new(bytes.len() as u64, 0x0); - mem.write(0, &bytes); - println!("{bytes:?}"); - let node_ = Node::hydrate(0, &mem).unwrap(); - assert!(node == node_); - }; - let chd0 = [None; NBRANCH]; - let mut chd1 = chd0; - for node in chd1.iter_mut().take(NBRANCH / 2) { - *node = Some(DiskAddress::from(0xa)); - } - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); - for encoded in chd_encoded.iter_mut().take(NBRANCH / 2) { - *encoded = Some(vec![0x1, 0x2, 0x3]); - } - for node in [ - Node::new_from_hash( - None, - None, - NodeType::Leaf(LeafNode( - PartialPath(vec![0x1, 0x2, 0x3]), - Data(vec![0x4, 0x5]), - )), - ), - Node::new_from_hash( - None, - None, - NodeType::Extension(ExtNode { - path: PartialPath(vec![0x1, 0x2, 0x3]), - child: DiskAddress::from(0x42), - child_encoded: None, - }), - ), - Node::new_from_hash( - None, - None, - NodeType::Extension(ExtNode { - path: PartialPath(vec![0x1, 0x2, 0x3]), - child: DiskAddress::null(), - child_encoded: Some(vec![0x1, 0x2, 0x3]), - }), - ), - Node::new_from_hash( - None, - None, - NodeType::Branch(BranchNode { - children: chd0, - value: Some(Data("hello, world!".as_bytes().to_vec())), - children_encoded: Default::default(), - }), - ), - Node::new_from_hash( - None, - None, - NodeType::Branch(BranchNode { - children: chd1, - value: None, - children_encoded: chd_encoded, - }), - ), - ] { - check(node); - } - } - #[test] - fn test_encode() { + fn create_test_merkle() -> Merkle> { const RESERVED: usize = 0x1000; let mut dm = shale::cached::DynamicMem::new(0x10000, 0); @@ -1465,58 +1341,81 @@ mod test { .expect("CompactSpace init fail"); let store = Box::new(space); - let merkle = Merkle::new(store); + Merkle::new(store) + } - { - let chd = Node::new(NodeType::Leaf(LeafNode( - PartialPath(vec![0x1, 0x2, 0x3]), - Data(vec![0x4, 0x5]), - ))); - let chd_ref = merkle.put_node(chd.clone()).unwrap(); - let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); - let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); - let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); - assert_eq!(chd_encoded, new_chd_encoded); - - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); - chd_encoded[0] = Some(new_chd_encoded.to_vec()); - let node = Node::new(NodeType::Branch(BranchNode { - children: [None; NBRANCH], - value: Some(Data("value1".as_bytes().to_vec())), - children_encoded: chd_encoded, - })); - - let node_ref = merkle.put_node(node.clone()).unwrap(); - - let r = node_ref.get_encoded(merkle.store.as_ref()); - let new_node = Node::new(NodeType::decode(r).unwrap()); - let new_encoded = new_node.get_encoded(merkle.store.as_ref()); - assert_eq!(r, new_encoded); + fn branch(value: Vec, encoded_child: Option>) -> Node { + let children = Default::default(); + let value = Some(value).map(Data); + let mut children_encoded = <[Option>; NBRANCH]>::default(); + + if let Some(child) = encoded_child { + children_encoded[0] = Some(child); } - { - let chd = Node::new(NodeType::Branch(BranchNode { - children: [None; NBRANCH], - value: Some(Data("value1".as_bytes().to_vec())), - children_encoded: Default::default(), - })); - let chd_ref = merkle.put_node(chd.clone()).unwrap(); - let chd_encoded = chd_ref.get_encoded(merkle.store.as_ref()); - let new_chd = Node::new(NodeType::decode(chd_encoded).unwrap()); - let new_chd_encoded = new_chd.get_encoded(merkle.store.as_ref()); - assert_eq!(chd_encoded, new_chd_encoded); - - let node = Node::new(NodeType::Extension(ExtNode { - path: PartialPath(vec![0x1, 0x2, 0x3]), - child: DiskAddress::null(), - child_encoded: Some(chd_encoded.to_vec()), - })); - let node_ref = merkle.put_node(node.clone()).unwrap(); - - let r = node_ref.get_encoded(merkle.store.as_ref()); - let new_node = Node::new(NodeType::decode(r).unwrap()); - let new_encoded = new_node.get_encoded(merkle.store.as_ref()); - assert_eq!(r, new_encoded); + Node::branch(BranchNode { + children, + value, + children_encoded, + }) + } + + #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] + #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with value")] + #[test_case(branch(b"value".to_vec(), None); "branch without value")] + #[test_case(extension(vec![1, 2, 3], DiskAddress::null(), vec![4, 5].into()) ; "extension without child address")] + fn encode_(node: Node) { + let merkle = create_test_merkle(); + + let node_ref = merkle.put_node(node).unwrap(); + let encoded = node_ref.get_encoded(merkle.store.as_ref()); + let new_node = Node::from(NodeType::decode(encoded).unwrap()); + let new_node_encoded = new_node.get_encoded(merkle.store.as_ref()); + + assert_eq!(encoded, new_node_encoded); + } + + #[test] + fn insert_and_retrieve() { + let key = b"hello"; + let val = b"world"; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(key, val.to_vec(), root).unwrap(); + + let fetched_val = merkle.get(key, root).unwrap(); + + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + #[test] + fn insert_and_retrieve_multiple() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + // insert values + for key_val in u8::MIN..=u8::MAX { + let key = vec![key_val]; + let val = vec![key_val]; + + merkle.insert(&key, val.clone(), root).unwrap(); + + let fetched_val = merkle.get(&key, root).unwrap(); + + // make sure the value was inserted + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + // make sure none of the previous values were forgotten after initial insert + for key_val in u8::MIN..=u8::MAX { + let key = vec![key_val]; + let val = vec![key_val]; + + let fetched_val = merkle.get(&key, root).unwrap(); + + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index f4aacbcc4bcd..f40e74cd53db 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -421,6 +421,20 @@ impl NodeType { } } +impl From for Node { + fn from(inner: NodeType) -> Self { + let mut s = Self { + root_hash: OnceLock::new(), + is_encoded_longer_than_hash_len: OnceLock::new(), + encoded: OnceLock::new(), + inner, + lazy_dirty: AtomicBool::new(false), + }; + s.rehash(); + s + } +} + impl Node { const BRANCH_NODE: u8 = 0x0; const EXT_NODE: u8 = 0x1; @@ -468,24 +482,12 @@ impl Node { self.root_hash = OnceLock::new(); } - pub fn new(inner: NodeType) -> Self { - let mut s = Self { - root_hash: OnceLock::new(), - is_encoded_longer_than_hash_len: OnceLock::new(), - encoded: OnceLock::new(), - inner, - lazy_dirty: AtomicBool::new(false), - }; - s.rehash(); - s - } - pub fn branch(node: BranchNode) -> Self { - Self::new(NodeType::Branch(node)) + Self::from(NodeType::Branch(node)) } pub fn leaf(path: PartialPath, data: Data) -> Self { - Self::new(NodeType::Leaf(LeafNode(path, data))) + Self::from(NodeType::Leaf(LeafNode(path, data))) } pub fn inner(&self) -> &NodeType { @@ -842,3 +844,78 @@ impl Storable for Node { } } } + +#[cfg(test)] +pub(super) mod tests { + use std::array::from_fn; + + use super::*; + use shale::cached::PlainMem; + use test_case::test_case; + + pub fn leaf(path: Vec, data: Vec) -> Node { + Node::leaf(PartialPath(path), Data(data)) + } + + pub fn branch( + repeated_disk_address: usize, + value: Option>, + repeated_encoded_child: Option>, + ) -> Node { + let children: [Option; NBRANCH] = from_fn(|i| { + if i < NBRANCH / 2 { + DiskAddress::from(repeated_disk_address).into() + } else { + None + } + }); + + let children_encoded = repeated_encoded_child + .map(|child| { + from_fn(|i| { + if i < NBRANCH / 2 { + child.clone().into() + } else { + None + } + }) + }) + .unwrap_or_default(); + + Node::branch(BranchNode { + children, + value: value.map(Data), + children_encoded, + }) + } + + pub fn extension( + path: Vec, + child_address: DiskAddress, + child_encoded: Option>, + ) -> Node { + Node::from(NodeType::Extension(ExtNode { + path: PartialPath(path), + child: child_address, + child_encoded, + })) + } + + #[test_case(leaf(vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf_node")] + #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with child address")] + #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::null(), vec![0x01, 0x02, 0x03].into()) ; "extension without child address")] + #[test_case(branch(0x0a, b"hello world".to_vec().into(), None); "branch with data")] + #[test_case(branch(0x0a, None, vec![0x01, 0x02, 0x03].into()); "branch without data")] + fn test_encoding(node: Node) { + let mut bytes = vec![0; node.dehydrated_len() as usize]; + + node.dehydrate(&mut bytes).unwrap(); + + let mut mem = PlainMem::new(node.dehydrated_len(), 0x00); + mem.write(0, &bytes); + + let hydrated_node = Node::hydrate(0, &mem).unwrap(); + + assert_eq!(node, hydrated_node); + } +} diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs index b7aa70315425..eb2bd94d8101 100644 --- a/firewood/src/merkle/partial_path.rs +++ b/firewood/src/merkle/partial_path.rs @@ -71,3 +71,21 @@ impl PartialPath { } } } + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(&[1, 2, 3, 4], true)] + #[test_case(&[1, 2, 3], false)] + #[test_case(&[0, 1, 2], false)] + #[test_case(&[1, 2], true)] + #[test_case(&[1], true)] + fn test_encoding(steps: &[u8], term: bool) { + let path = PartialPath(steps.to_vec()).encode(term); + let (decoded, decoded_term) = PartialPath::decode(&path); + assert_eq!(&decoded.0, &steps); + assert_eq!(decoded_term, term); + } +} diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 1f64788ae759..c9777fb1dda7 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -4,18 +4,15 @@ use shale::{CachedStore, ShaleError, Storable}; use std::{ fmt::{self, Debug}, - io::{Cursor, Write}, + io::Write, }; pub const TRIE_HASH_LEN: usize = 32; +const U64_TRIE_HASH_LEN: u64 = TRIE_HASH_LEN as u64; #[derive(PartialEq, Eq, Clone)] pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); -impl TrieHash { - const MSIZE: u64 = 32; -} - impl std::ops::Deref for TrieHash { type Target = [u8; TRIE_HASH_LEN]; fn deref(&self) -> &[u8; TRIE_HASH_LEN] { @@ -26,22 +23,21 @@ impl std::ops::Deref for TrieHash { impl Storable for TrieHash { fn hydrate(addr: usize, mem: &T) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, U64_TRIE_HASH_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: U64_TRIE_HASH_LEN, })?; - Ok(Self( - raw.as_deref()[..Self::MSIZE as usize].try_into().unwrap(), - )) + + Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) } fn dehydrated_len(&self) -> u64 { - Self::MSIZE + U64_TRIE_HASH_LEN } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0).map_err(ShaleError::Io) + fn dehydrate(&self, mut to: &mut [u8]) -> Result<(), ShaleError> { + to.write_all(&self.0).map_err(ShaleError::Io) } } @@ -50,3 +46,18 @@ impl Debug for TrieHash { write!(f, "{}", hex::encode(self.0)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dehydrate() { + let zero_hash = TrieHash([0u8; TRIE_HASH_LEN]); + + let mut to = [1u8; TRIE_HASH_LEN]; + zero_hash.dehydrate(&mut to).unwrap(); + + assert_eq!(&to, &zero_hash.0); + } +} diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 6f89f8377665..c76765797baf 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -449,7 +449,7 @@ impl + Send> Proof { ) -> Result<(DiskAddress, Option, usize), ProofError> { let node = NodeType::decode(buf)?; let new_node = merkle - .put_node(Node::new(node)) + .put_node(Node::from(node)) .map_err(ProofError::InvalidNode)?; let addr = new_node.as_ptr(); match new_node.inner() { From 24657c4bc66e4cedc4d92ae6e554a689fcdc134a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 24 Oct 2023 23:40:29 -0400 Subject: [PATCH 0342/1053] Check all features (#330) --- .github/workflows/ci.yaml | 8 ++++---- libaio/src/lib.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2388117db346..5bb909de956c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,9 +37,9 @@ jobs: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- ${{ runner.os }}-deps- - name: Check - run: cargo check --workspace --tests --examples --benches + run: cargo check --workspace --tests --examples --benches --all-features - name: Build - run: cargo build --workspace --tests --examples --benches + run: cargo build --workspace --tests --examples --benches --all-features # Always update the cache - name: Cleanup run: | @@ -100,7 +100,7 @@ jobs: - name: Format run: cargo fmt -- --check - name: Clippy - run: cargo clippy -- -D warnings + run: cargo clippy --tests --examples --benches --all-features -- -D warnings test: needs: build @@ -122,7 +122,7 @@ jobs: target/ key: ${{ needs.build.outputs.cache-key }} - name: Run tests - run: cargo test --verbose + run: cargo test --all-features --verbose examples: needs: build diff --git a/libaio/src/lib.rs b/libaio/src/lib.rs index c3332e860155..24776c5317fe 100644 --- a/libaio/src/lib.rs +++ b/libaio/src/lib.rs @@ -334,7 +334,7 @@ impl AioBuilder { waiting: Mutex::new(HashMap::new()), npending: AtomicUsize::new(0), #[cfg(feature = "emulated-failure")] - emul_fail: self.emul_fail.as_ref().map(|ef| ef.clone()), + emul_fail: self.emul_fail.as_ref().cloned(), }); let mut aiomgr = AioManager { notifier, @@ -433,7 +433,7 @@ impl AioManager { res = e } } - n.finish(ev.data as u64, res); + n.finish(ev.data, res); } } } From 2680695b9ced6eb46124b313fe075c179a18d42b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 25 Oct 2023 14:56:41 -0700 Subject: [PATCH 0343/1053] More dead code cleanup (#331) --- firewood/src/api.rs | 41 ----------------------------------------- firewood/src/lib.rs | 1 - 2 files changed, 42 deletions(-) delete mode 100644 firewood/src/api.rs diff --git a/firewood/src/api.rs b/firewood/src/api.rs deleted file mode 100644 index 0dad80b05c52..000000000000 --- a/firewood/src/api.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::io::Write; - -use crate::db::DbError; -use crate::merkle::MerkleError; -use crate::merkle::TrieHash; -use crate::v2::api::Proof; - -use async_trait::async_trait; - -pub type Nonce = u64; - -#[async_trait] -pub trait Db, N: Send> { - async fn get_revision(&self, root_hash: TrieHash) -> Option; -} - -#[async_trait] -pub trait Revision -where - Self: Sized, -{ - async fn kv_root_hash(&self) -> Result; - async fn kv_get + Send + Sync>(&self, key: K) -> Result, DbError>; - - async fn kv_dump(&self, writer: W) -> Result<(), DbError>; - async fn root_hash(&self) -> Result; - async fn dump(&self, writer: W) -> Result<(), DbError>; - - async fn prove + Send + Sync>(&self, key: K) -> Result, MerkleError>; - async fn verify_range_proof + Send + Sync>( - &self, - proof: Proof, - first_key: K, - last_key: K, - keys: Vec, - values: Vec, - ); -} diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 24682eda735c..9c7b09548cb9 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -191,7 +191,6 @@ pub mod merkle_util; pub mod proof; pub mod storage; -pub mod api; pub(crate) mod config; pub mod nibbles; From 1ed24f389fa4e1633d5e8dd7a5384641883d65d2 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 26 Oct 2023 14:04:32 -0400 Subject: [PATCH 0344/1053] More merkle.rs cleanup (#329) --- firewood/src/db.rs | 6 +- firewood/src/merkle.rs | 348 ++++++++++++++++++++++------------------- shale/src/lib.rs | 5 + 3 files changed, 192 insertions(+), 167 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 1ac78e193d57..f5f324fe6093 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -32,6 +32,7 @@ use std::{ io::{Cursor, ErrorKind, Write}, mem::size_of, num::NonZeroUsize, + ops::Deref, os::fd::{AsFd, BorrowedFd}, path::Path, sync::Arc, @@ -287,10 +288,7 @@ impl + Send + Sync> api::DbView for DbRev { let obj_ref = self.merkle.get(key, self.header.kv_root); match obj_ref { Err(e) => Err(api::Error::IO(std::io::Error::new(ErrorKind::Other, e))), - Ok(obj) => match obj { - None => Ok(None), - Some(inner) => Ok(Some(inner.as_ref().to_owned())), - }, + Ok(obj) => Ok(obj.map(|inner| inner.deref().to_owned())), } } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 04d43f8707bb..0ca162a7bf01 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -3,7 +3,7 @@ use crate::{nibbles::Nibbles, v2::api::Proof}; use sha3::Digest; -use shale::{disk_address::DiskAddress, ObjRef, ObjWriteError, ShaleError, ShaleStore}; +use shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use std::{ cmp::Ordering, collections::HashMap, @@ -21,6 +21,10 @@ pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; pub use partial_path::PartialPath; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; +type ObjRef<'a> = shale::ObjRef<'a, Node>; +type ParentRefs<'a> = Vec<(ObjRef<'a>, u8)>; +type ParentAddresses = Vec<(DiskAddress, u8)>; + #[derive(Debug, Error)] pub enum MerkleError { #[error("merkle datastore error: {0:?}")] @@ -58,11 +62,11 @@ pub struct Merkle { } impl> Merkle { - pub fn get_node(&self, ptr: DiskAddress) -> Result, MerkleError> { + pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) } - pub fn put_node(&self, node: Node) -> Result, MerkleError> { + pub fn put_node(&self, node: Node) -> Result { self.store.put_item(node, 0).map_err(Into::into) } @@ -162,10 +166,10 @@ impl + Send + Sync> Merkle { } #[allow(clippy::too_many_arguments)] - fn split<'b>( + fn split( &self, - mut node_to_split: ObjRef<'b, Node>, - parents: &mut [(ObjRef<'b, Node>, u8)], + mut node_to_split: ObjRef, + parents: &mut [(ObjRef, u8)], insert_path: &[u8], n_path: Vec, n_value: Option, @@ -392,7 +396,7 @@ impl + Send + Sync> Merkle { key: K, mut val: Vec, root: DiskAddress, - ) -> Result<(impl Iterator>, Vec), MerkleError> { + ) -> Result<(impl Iterator, Vec), MerkleError> { // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); let mut parents = Vec::new(); @@ -578,7 +582,7 @@ impl + Send + Sync> Merkle { fn after_remove_leaf( &self, - parents: &mut Vec<(ObjRef<'_, Node>, u8)>, + parents: &mut ParentRefs, deleted: &mut Vec, ) -> Result<(), MerkleError> { let (b_chd, val) = { @@ -749,7 +753,7 @@ impl + Send + Sync> Merkle { fn after_remove_branch( &self, (c_ptr, idx): (DiskAddress, u8), - parents: &mut Vec<(ObjRef<'_, Node>, u8)>, + parents: &mut ParentRefs, deleted: &mut Vec, ) -> Result<(), MerkleError> { // [b] -> [u] -> [c] @@ -864,83 +868,47 @@ impl + Send + Sync> Merkle { key: K, root: DiskAddress, ) -> Result>, MerkleError> { - let mut chunks = vec![0]; - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); - if root.is_null() { return Ok(None); } - let mut deleted = Vec::new(); - let mut parents: Vec<(ObjRef, _)> = Vec::new(); - let mut u_ref = self.get_node(root)?; - let mut nskip = 0; - let mut found = None; + let (found, parents, deleted) = { + let (node_ref, mut parents) = + self.get_node_and_parents_by_key(self.get_node(root)?, key)?; - for (i, nib) in chunks.iter().enumerate() { - if nskip > 0 { - nskip -= 1; - continue; - } - let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.children[*nib as usize] { - Some(c) => c, - None => return Ok(None), - }, - NodeType::Leaf(n) => { - if chunks[i..] != *n.0 { - return Ok(None); - } - found = Some(n.1.clone()); - deleted.push(u_ref.as_ptr()); - self.after_remove_leaf(&mut parents, &mut deleted)?; - break; - } - NodeType::Extension(n) => { - let n_path = &*n.path.0; - let rem_path = &chunks[i..]; - if rem_path < n_path || &rem_path[..n_path.len()] != n_path { - return Ok(None); - } - nskip = n_path.len() - 1; - n.chd() - } + let Some(mut node_ref) = node_ref else { + return Ok(None); }; + let mut deleted = Vec::new(); + let mut found = None; - parents.push((u_ref, *nib)); - u_ref = self.get_node(next_ptr)?; - } - if found.is_none() { - match &u_ref.inner { + match &node_ref.inner { NodeType::Branch(n) => { - if n.value.is_none() { - return Ok(None); - } let (c_chd, _) = n.single_child(); - u_ref + + node_ref .write(|u| { found = u.inner.as_branch_mut().unwrap().value.take(); u.rehash() }) .unwrap(); + if let Some((c_ptr, idx)) = c_chd { - deleted.push(u_ref.as_ptr()); + deleted.push(node_ref.as_ptr()); self.after_remove_branch((c_ptr, idx), &mut parents, &mut deleted)? } } + NodeType::Leaf(n) => { - if n.0.len() > 0 { - return Ok(None); - } found = Some(n.1.clone()); - deleted.push(u_ref.as_ptr()); + deleted.push(node_ref.as_ptr()); self.after_remove_leaf(&mut parents, &mut deleted)? } _ => (), - } - } + }; - drop(u_ref); + (found, parents, deleted) + }; for (mut r, _) in parents.into_iter().rev() { r.write(|u| u.rehash()).unwrap(); @@ -949,6 +917,7 @@ impl + Send + Sync> Merkle { for ptr in deleted.into_iter() { self.free_node(ptr)?; } + Ok(found.map(|e| e.0)) } @@ -983,72 +952,118 @@ impl + Send + Sync> Merkle { Ok(()) } - pub fn get_mut>( - &mut self, + fn get_node_by_key<'a, K: AsRef<[u8]>>( + &'a self, + node_ref: ObjRef<'a>, key: K, - root: DiskAddress, - ) -> Result>, MerkleError> { - let mut chunks = vec![0]; - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + ) -> Result>, MerkleError> { + self.get_node_by_key_with_callback(node_ref, key, |_, _| {}) + } + + fn get_node_and_parents_by_key<'a, K: AsRef<[u8]>>( + &'a self, + node_ref: ObjRef<'a>, + key: K, + ) -> Result<(Option>, ParentRefs<'a>), MerkleError> { let mut parents = Vec::new(); + let node_ref = self.get_node_by_key_with_callback(node_ref, key, |node_ref, nib| { + parents.push((node_ref, nib)); + })?; - if root.is_null() { - return Ok(None); - } + Ok((node_ref, parents)) + } - let mut u_ref = self.get_node(root)?; - let mut nskip = 0; + fn get_node_and_parent_addresses_by_key<'a, K: AsRef<[u8]>>( + &'a self, + node_ref: ObjRef<'a>, + key: K, + ) -> Result<(Option>, ParentAddresses), MerkleError> { + let mut parents = Vec::new(); + let node_ref = self.get_node_by_key_with_callback(node_ref, key, |node_ref, nib| { + parents.push((node_ref.into_ptr(), nib)); + })?; - for (i, nib) in chunks.iter().enumerate() { - let u_ptr = u_ref.as_ptr(); - if nskip > 0 { - nskip -= 1; - continue; - } - let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.children[*nib as usize] { + Ok((node_ref, parents)) + } + + fn get_node_by_key_with_callback<'a, K: AsRef<[u8]>>( + &'a self, + mut node_ref: ObjRef<'a>, + key: K, + mut loop_callback: impl FnMut(ObjRef<'a>, u8), + ) -> Result>, MerkleError> { + let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); + + loop { + let Some(nib) = key_nibbles.next() else { + break; + }; + + let next_ptr = match &node_ref.inner { + NodeType::Branch(n) => match n.children[nib as usize] { Some(c) => c, None => return Ok(None), }, NodeType::Leaf(n) => { - if chunks[i..] != *n.0 { - return Ok(None); - } - drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))); + let node_ref = if once(nib).chain(key_nibbles).eq(n.0.iter().copied()) { + Some(node_ref) + } else { + None + }; + + return Ok(node_ref); } NodeType::Extension(n) => { - let n_path = &*n.path.0; - let rem_path = &chunks[i..]; - if rem_path.len() < n_path.len() || &rem_path[..n_path.len()] != n_path { + let mut n_path_iter = n.path.iter().copied(); + + if n_path_iter.next() != Some(nib) { return Ok(None); } - nskip = n_path.len() - 1; + + let path_matches = n_path_iter + .map(Some) + .all(|n_path_nibble| key_nibbles.next() == n_path_nibble); + + if !path_matches { + return Ok(None); + } + n.chd() } }; - parents.push((u_ptr, *nib)); - u_ref = self.get_node(next_ptr)?; + + loop_callback(node_ref, nib); + + node_ref = self.get_node(next_ptr)?; } - let u_ptr = u_ref.as_ptr(); - match &u_ref.inner { - NodeType::Branch(n) => { - if n.value.as_ref().is_some() { - drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))); - } - } - NodeType::Leaf(n) => { - if n.0.len() == 0 { - drop(u_ref); - return Ok(Some(RefMut::new(u_ptr, parents, self))); - } - } - _ => (), + // when we're done iterating over nibbles, check if the node we're at has a value + let node_ref = match &node_ref.inner { + NodeType::Branch(n) if n.value.as_ref().is_some() => Some(node_ref), + NodeType::Leaf(n) if n.0.len() == 0 => Some(node_ref), + _ => None, + }; + + Ok(node_ref) + } + + pub fn get_mut>( + &mut self, + key: K, + root: DiskAddress, + ) -> Result>, MerkleError> { + if root.is_null() { + return Ok(None); } - Ok(None) + let (ptr, parents) = { + let root_node = self.get_node(root)?; + let (node_ref, parents) = self.get_node_and_parent_addresses_by_key(root_node, key)?; + + (node_ref.map(|n| n.into_ptr()), parents) + }; + + Ok(ptr.map(|ptr| RefMut::new(ptr, parents, self))) } /// Constructs a merkle proof for key. The result contains all encoded nodes @@ -1083,6 +1098,8 @@ impl + Send + Sync> Merkle { let mut nskip = 0; let mut nodes: Vec = Vec::new(); + + // TODO: use get_node_by_key (and write proper unit test) for (i, nib) in key_nibbles.into_iter().enumerate() { if nskip > 0 { nskip -= 1; @@ -1149,58 +1166,10 @@ impl + Send + Sync> Merkle { return Ok(None); } - let key_nibbles = Nibbles::<1>::new(key.as_ref()); - - let mut u_ref = self.get_node(root)?; - let mut nskip = 0; + let root_node = self.get_node(root)?; + let node_ref = self.get_node_by_key(root_node, key)?; - for (i, nib) in key_nibbles.into_iter().enumerate() { - if nskip > 0 { - nskip -= 1; - continue; - } - let next_ptr = match &u_ref.inner { - NodeType::Branch(n) => match n.children[nib as usize] { - Some(c) => c, - None => return Ok(None), - }, - NodeType::Leaf(n) => { - if !key_nibbles.into_iter().skip(i).eq(n.0.iter().cloned()) { - return Ok(None); - } - return Ok(Some(Ref(u_ref))); - } - NodeType::Extension(n) => { - let n_path = &n.path; - let rem_path = key_nibbles.into_iter().skip(i); - if rem_path.size_hint().0 < n_path.len() { - return Ok(None); - } - if !rem_path.take(n_path.len()).eq(n_path.iter().cloned()) { - return Ok(None); - } - nskip = n_path.len() - 1; - n.chd() - } - }; - u_ref = self.get_node(next_ptr)?; - } - - match &u_ref.inner { - NodeType::Branch(n) => { - if n.value.as_ref().is_some() { - return Ok(Some(Ref(u_ref))); - } - } - NodeType::Leaf(n) => { - if n.0.len() == 0 { - return Ok(Some(Ref(u_ref))); - } - } - _ => (), - } - - Ok(None) + Ok(node_ref.map(Ref)) } pub fn flush_dirty(&self) -> Option<()> { @@ -1208,7 +1177,7 @@ impl + Send + Sync> Merkle { } } -fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { +fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { let (p_ref, idx) = parents.last_mut().unwrap(); p_ref .write(|p| { @@ -1222,11 +1191,11 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef<'_, Node>, u8)]) { .unwrap(); } -pub struct Ref<'a>(ObjRef<'a, Node>); +pub struct Ref<'a>(ObjRef<'a>); pub struct RefMut<'a, S> { ptr: DiskAddress, - parents: Vec<(DiskAddress, u8)>, + parents: ParentAddresses, merkle: &'a mut Merkle, } @@ -1242,7 +1211,7 @@ impl<'a> std::ops::Deref for Ref<'a> { } impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { - fn new(ptr: DiskAddress, parents: Vec<(DiskAddress, u8)>, merkle: &'a mut Merkle) -> Self { + fn new(ptr: DiskAddress, parents: ParentAddresses, merkle: &'a mut Merkle) -> Self { Self { ptr, parents, @@ -1418,4 +1387,57 @@ mod tests { assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } } + + #[test] + fn remove_one() { + let key = b"hello"; + let val = b"world"; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(key, val.to_vec(), root).unwrap(); + + assert_eq!( + merkle.get(key, root).unwrap().as_deref(), + val.as_slice().into() + ); + + let removed_val = merkle.remove(key, root).unwrap(); + assert_eq!(removed_val.as_deref(), val.as_slice().into()); + + let fetched_val = merkle.get(key, root).unwrap(); + assert!(fetched_val.is_none()); + } + + #[test] + fn remove_many() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + // insert values + for key_val in u8::MIN..=u8::MAX { + let key = &[key_val]; + let val = &[key_val]; + + merkle.insert(key, val.to_vec(), root).unwrap(); + + let fetched_val = merkle.get(key, root).unwrap(); + + // make sure the value was inserted + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + // remove values + for key_val in u8::MIN..=u8::MAX { + let key = &[key_val]; + let val = &[key_val]; + + let removed_val = merkle.remove(key, root).unwrap(); + assert_eq!(removed_val.as_deref(), val.as_slice().into()); + + let fetched_val = merkle.get(key, root).unwrap(); + assert!(fetched_val.is_none()); + } + } } diff --git a/shale/src/lib.rs b/shale/src/lib.rs index 6b95c458e18d..beeb7514710b 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -206,11 +206,16 @@ impl<'a, T: Send + Sync> ObjRef<'a, T> { Ok(()) } + + pub fn into_ptr(self) -> DiskAddress { + self.deref().as_ptr() + } } impl<'a, T: Send + Sync> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { + // TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix self.inner.as_ref().unwrap() } } From 20d6110d7557ac2c021f5726390ced374424c9d7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 26 Oct 2023 11:38:51 -0700 Subject: [PATCH 0345/1053] Make Db::new async, force use of new API (#323) Signed-off-by: Ron Kuris Signed-off-by: Ron Kuris Co-authored-by: Richard Pringle --- firewood/Cargo.toml | 2 +- firewood/benches/hashops.rs | 70 +++++---- firewood/examples/insert.rs | 14 +- firewood/src/db.rs | 56 ++++--- firewood/src/file.rs | 9 +- firewood/src/lib.rs | 2 +- firewood/src/storage/buffer.rs | 23 +-- firewood/src/v2/api.rs | 2 +- firewood/src/v2/db.rs | 1 + firewood/tests/db.rs | 275 +++++++++++++++++---------------- firewood/tests/v2api.rs | 14 +- fwdctl/Cargo.toml | 1 + fwdctl/src/create.rs | 10 +- fwdctl/src/delete.rs | 16 +- fwdctl/src/dump.rs | 16 +- fwdctl/src/get.rs | 31 ++-- fwdctl/src/insert.rs | 19 +-- fwdctl/src/main.rs | 17 +- fwdctl/src/root.rs | 15 +- 19 files changed, 307 insertions(+), 286 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 9efa74ddc630..89db5ed8b5e1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -37,7 +37,7 @@ typed-builder = "0.18.0" bincode = "1.3.3" [dev-dependencies] -criterion = "0.5.1" +criterion = {version = "0.5.1", features = ["async_tokio"]} keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bd69004ac94b..76771d943904 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -5,9 +5,10 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::{ - db::{BatchOp, Db, DbConfig}, + db::{BatchOp, DbConfig}, merkle::{Merkle, TrieHash, TRIE_HASH_LEN}, storage::WalConfig, + v2::api::{Db, Proposal}, }; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; @@ -17,7 +18,7 @@ use shale::{ disk_address::DiskAddress, CachedStore, ObjCache, Storable, StoredView, }; -use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path}; +use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path, sync::Arc}; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); @@ -134,36 +135,41 @@ fn bench_db(criterion: &mut Criterion) { .benchmark_group("Db") .sample_size(30) .bench_function("commit", |b| { - b.iter_batched( - || { - let cfg = - DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - - let batch_ops: Vec<_> = repeat_with(|| { - (&mut rng) - .sample_iter(&Alphanumeric) - .take(KEY_LEN) - .collect() - }) - .map(|key: Vec<_>| BatchOp::Put { - key, - value: vec![b'v'], - }) - .take(N) - .collect(); - let db_path = dbg!(std::env::temp_dir()); - let db_path = db_path.join("benchmark_db"); - - let db = Db::new(db_path, &cfg.clone().truncate(true).build()).unwrap(); - - (db, batch_ops) - }, - |(db, batch_ops)| { - let proposal = db.new_proposal(batch_ops).unwrap(); - proposal.commit_sync().unwrap(); - }, - BatchSize::SmallInput, - ); + b.to_async(tokio::runtime::Runtime::new().unwrap()) + .iter_batched( + || { + let batch_ops: Vec<_> = repeat_with(|| { + (&mut rng) + .sample_iter(&Alphanumeric) + .take(KEY_LEN) + .collect() + }) + .map(|key: Vec<_>| BatchOp::Put { + key, + value: vec![b'v'], + }) + .take(N) + .collect(); + batch_ops + }, + |batch_ops| async { + let db_path = dbg!(std::env::temp_dir()); + let db_path = db_path.join("benchmark_db"); + let cfg = + DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + + let db = + firewood::db::Db::new(db_path, &cfg.clone().truncate(true).build()) + .await + .unwrap(); + + Arc::new(db.propose(batch_ops).await.unwrap()) + .commit() + .await + .unwrap() + }, + BatchSize::SmallInput, + ); }); } diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 56dc7e021e25..985813b9b01e 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -9,7 +9,7 @@ use std::{collections::HashMap, error::Error, ops::RangeInclusive, sync::Arc, ti use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, - v2::api::{Db as DbApi, DbView, Proposal}, + v2::api::{Db as _, DbView, Proposal}, }; use rand::{distributions::Alphanumeric, Rng}; @@ -44,11 +44,9 @@ async fn main() -> Result<(), Box> { let args = Args::parse(); - let db = tokio::task::spawn_blocking(move || { - Db::new("rev_db", &cfg).expect("db initiation should succeed") - }) - .await - .unwrap(); + let db = Db::new("rev_db", &cfg) + .await + .expect("db initiation should succeed"); let keys = args.batch_keys; let start = Instant::now(); @@ -74,7 +72,7 @@ async fn main() -> Result<(), Box> { let verify = get_keys_to_verify(&batch, args.read_verify_percent); - let proposal: Arc = db.propose(batch).await.unwrap().into(); + let proposal = Arc::new(db.propose(batch).await.unwrap()); proposal.commit().await?; verify_keys(&db, verify).await?; } @@ -107,7 +105,7 @@ fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec>, ) -> Result<(), firewood::v2::api::Error> { if !verify.is_empty() { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f5f324fe6093..ea79ec38355a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -4,6 +4,7 @@ pub use crate::{ config::{DbConfig, DbRevConfig}, storage::{buffer::DiskBufferConfig, WalConfig}, + v2::api::{Batch, BatchOp, Proposal}, }; use crate::{ file, @@ -18,7 +19,7 @@ use crate::{ }; use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; -use metered::{metered, HitCount}; +use metered::metered; use parking_lot::{Mutex, RwLock}; use shale::{ compact::{CompactSpace, CompactSpaceHeader}, @@ -42,8 +43,6 @@ use tokio::task::block_in_place; mod proposal; -pub use proposal::{Batch, BatchOp, Proposal}; - use self::proposal::ProposalBase; const MERKLE_META_SPACE: SpaceId = 0x0; @@ -278,8 +277,7 @@ pub struct DbRev { #[async_trait] impl + Send + Sync> api::DbView for DbRev { async fn root_hash(&self) -> Result { - self.merkle - .root_hash(self.header.kv_root) + block_in_place(|| self.merkle.root_hash(self.header.kv_root)) .map(|h| *h) .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } @@ -386,7 +384,7 @@ impl Drop for DbInner { impl api::Db for Db { type Historical = DbRev; - type Proposal = Proposal; + type Proposal = proposal::Proposal; async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { let rev = block_in_place(|| self.get_revision(&TrieHash(root_hash))); @@ -400,7 +398,9 @@ impl api::Db for Db { } async fn root_hash(&self) -> Result { - self.kv_root_hash().map(|hash| hash.0).map_err(Into::into) + block_in_place(|| self.kv_root_hash()) + .map(|hash| hash.0) + .map_err(Into::into) } async fn propose( @@ -434,13 +434,17 @@ pub struct Db { impl Db { const PARAM_SIZE: u64 = size_of::() as u64; - /// Open a database. - pub fn new>(db_path: P, cfg: &DbConfig) -> Result { - // TODO: make sure all fds are released at the end + pub async fn new>(db_path: P, cfg: &DbConfig) -> Result { if cfg.truncate { - let _ = std::fs::remove_dir_all(db_path.as_ref()); + let _ = tokio::fs::remove_dir_all(db_path.as_ref()).await; } + block_in_place(|| Db::new_internal(db_path, cfg.clone())) + .map_err(|e| api::Error::InternalError(e.into())) + } + + /// Open a database. + fn new_internal>(db_path: P, cfg: DbConfig) -> Result { let open_options = if cfg.truncate { file::Options::Truncate } else { @@ -465,7 +469,7 @@ impl Db { { return Err(DbError::InvalidParams); } - Self::initialize_header_on_disk(cfg, fd0)?; + Self::initialize_header_on_disk(&cfg, fd0)?; } // read DbParams @@ -482,10 +486,12 @@ impl Db { let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); let disk_requester = DiskBufferRequester::new(sender); let buffer = cfg.buffer.clone(); - let disk_thread = Some(std::thread::spawn(move || { - let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); - disk_buffer.run() - })); + let disk_thread = block_in_place(|| { + Some(std::thread::spawn(move || { + let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); + disk_buffer.run() + })) + }); let root_hash_cache: Arc = CachedSpace::new( &StoreConfig::builder() @@ -753,10 +759,10 @@ impl Db { } /// Create a proposal. - pub fn new_proposal( + pub(crate) fn new_proposal( &self, data: Batch, - ) -> Result { + ) -> Result { let mut inner = self.inner.write(); let reset_store_headers = inner.reset_store_headers; let (store, mut rev) = Db::new_store( @@ -792,7 +798,7 @@ impl Db { rev.flush_dirty().unwrap(); let parent = ProposalBase::View(Arc::clone(&self.revisions.lock().base_revision)); - Ok(Proposal { + Ok(proposal::Proposal { m: Arc::clone(&self.inner), r: Arc::clone(&self.revisions), cfg: self.cfg.clone(), @@ -904,20 +910,10 @@ impl Db { self.revisions.lock().base_revision.kv_dump(w) } /// Get root hash of the latest generic key-value storage. - pub fn kv_root_hash(&self) -> Result { + pub(crate) fn kv_root_hash(&self) -> Result { self.revisions.lock().base_revision.kv_root_hash() } - /// Get a value in the kv store associated with a particular key. - #[measure(HitCount)] - pub fn kv_get>(&self, key: K) -> Result, DbError> { - self.revisions - .lock() - .base_revision - .kv_get(key) - .ok_or(DbError::KeyNotFound) - } - pub fn metrics(&self) -> Arc { self.metrics.clone() } diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 96e93c9bdf6f..84d81dff0526 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -3,6 +3,7 @@ // Copied from CedrusDB +use std::fs::{create_dir, remove_dir_all}; use std::ops::Deref; use std::os::fd::OwnedFd; @@ -74,7 +75,7 @@ impl Deref for File { } } -pub fn touch_dir(dirname: &str, rootdir: &Path) -> Result { +pub(crate) fn touch_dir(dirname: &str, rootdir: &Path) -> Result { let path = rootdir.join(dirname); if let Err(e) = std::fs::create_dir(&path) { // ignore already-exists error @@ -85,17 +86,17 @@ pub fn touch_dir(dirname: &str, rootdir: &Path) -> Result>( +pub(crate) fn open_dir>( path: P, options: Options, ) -> Result<(PathBuf, bool), std::io::Error> { let truncate = options == Options::Truncate; if truncate { - let _ = std::fs::remove_dir_all(path.as_ref()); + let _ = remove_dir_all(path.as_ref()); } - match std::fs::create_dir(path.as_ref()) { + match create_dir(path.as_ref()) { Err(e) if truncate || e.kind() != ErrorKind::AlreadyExists => Err(e), // the DB already exists Err(_) => Ok((path.as_ref().to_path_buf(), false)), diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 9c7b09548cb9..e9da7c75960a 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -191,7 +191,7 @@ pub mod merkle_util; pub mod proof; pub mod storage; -pub(crate) mod config; +pub mod config; pub mod nibbles; pub mod v2; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 9951a4aa92a9..2b50e26e99e7 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -618,6 +618,7 @@ impl DiskBufferRequester { mod tests { use sha3::Digest; use std::path::{Path, PathBuf}; + use tokio::task::block_in_place; use super::*; use crate::{ @@ -657,9 +658,9 @@ mod tests { .into() } - #[test] + #[tokio::test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] - fn test_buffer_with_undo() { + async fn test_buffer_with_undo() { let temp_dir = get_tmp_dir(); let buf_cfg = DiskBufferConfig::builder().build(); @@ -734,9 +735,9 @@ mod tests { assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); } - #[test] + #[tokio::test] #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] - fn test_buffer_with_redo() { + async fn test_buffer_with_redo() { let buf_cfg = DiskBufferConfig::builder().build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); @@ -808,8 +809,8 @@ mod tests { assert_eq!(view.as_deref(), hash); } - #[test] - fn test_multi_stores() { + #[tokio::test(flavor = "multi_thread")] + async fn test_multi_stores() { let buf_cfg = DiskBufferConfig::builder().build(); let wal_cfg = WalConfig::builder().build(); let disk_requester = init_buffer(buf_cfg, wal_cfg); @@ -849,7 +850,7 @@ mod tests { // mutate the in memory buffer. let data = b"this is a test"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - store.write(0, &hash); + block_in_place(|| store.write(0, &hash)); assert_eq!(store.id(), STATE_SPACE); let another_data = b"this is another test"; @@ -857,11 +858,13 @@ mod tests { // mutate the in memory buffer in another StoreRev new from the above. let mut another_store = StoreRevMut::new_from_other(&store); - another_store.write(32, &another_hash); + block_in_place(|| another_store.write(32, &another_hash)); assert_eq!(another_store.id(), STATE_SPACE); // wal should have no records. - assert!(disk_requester.collect_ash(1).unwrap().is_empty()); + assert!(block_in_place(|| disk_requester.collect_ash(1)) + .unwrap() + .is_empty()); // get RO view of the buffer from the beginning. Both stores should have the same view. let view = store.get_view(0, HASH_SIZE as u64).unwrap(); @@ -904,7 +907,7 @@ mod tests { let another_store = StoreRevMut::new(state_cache); let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), another_hash); - disk_requester.shutdown(); + block_in_place(|| disk_requester.shutdown()); } fn get_file_path(path: &Path, file: &str, line: u32) -> PathBuf { diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index dbc9ff4e0523..0ddacbe71648 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -172,7 +172,7 @@ pub trait DbView { /// [DbView], which means you can fetch values from it or /// obtain proofs. #[async_trait] -pub trait Proposal: DbView { +pub trait Proposal: DbView + Send + Sync { type Proposal: DbView + Proposal; /// Commit this revision diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index a210d130201a..edfbe1ee6c13 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -49,6 +49,7 @@ where T: api::DbView, T: Send + Sync, T: Default, + T: 'static, { type Historical = T; diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index e522fcac2a17..f8bab4f317e8 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -2,47 +2,24 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - db::{BatchOp, Db as PersistedDb, DbConfig, DbError, WalConfig}, - merkle::TrieHash, + db::{Db, DbConfig, WalConfig}, + v2::api::{self, BatchOp, Db as _, DbView, Proposal}, }; +use tokio::task::block_in_place; -use std::{ - collections::VecDeque, - fs::remove_dir_all, - ops::{Deref, DerefMut}, - path::Path, - sync::Arc, -}; +use std::{collections::VecDeque, env::temp_dir, path::PathBuf, sync::Arc}; // TODO: use a trait macro_rules! kv_dump { ($e: ident) => {{ let mut s = Vec::new(); - $e.kv_root_hash().unwrap(); $e.kv_dump(&mut s).unwrap(); String::from_utf8(s).unwrap() }}; } -struct Db<'a, P: AsRef + ?Sized>(PersistedDb, &'a P); - -impl<'a, P: AsRef + ?Sized> Db<'a, P> { - fn new(path: &'a P, cfg: &DbConfig) -> Result { - PersistedDb::new(path, cfg).map(|db| Self(db, path)) - } -} - -impl + ?Sized> Drop for Db<'_, P> { - fn drop(&mut self) { - // if you're using absolute paths, you have to clean up after yourself - if self.1.as_ref().is_relative() { - remove_dir_all(self.1).expect("should be able to remove db-directory"); - } - } -} - -#[test] -fn test_basic_metrics() { +#[tokio::test(flavor = "multi_thread")] +async fn test_basic_metrics() { let cfg = DbConfig::builder() .meta_ncached_pages(1024) .meta_ncached_files(128) @@ -57,15 +34,40 @@ fn test_basic_metrics() { .max_revisions(10) .build(), ); - let db = Db::new("test_revisions_db2", &cfg.truncate(true).build()).unwrap(); - let metrics = db.metrics(); - assert_eq!(metrics.kv_get.hit_count.get(), 0); - db.kv_get("a").ok(); - assert_eq!(metrics.kv_get.hit_count.get(), 1); + + let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or(temp_dir().into()) + .into(); + tmpdir.push("/tmp/test_basic_metrics"); + + let db = firewood::db::Db::new(tmpdir, &cfg.truncate(true).build()) + .await + .unwrap(); + // let metrics = db.metrics(); + // TODO: kv_get is no longer a valid metric, and DbRev has no access to Db.metrics (yet) + //assert_eq!(metrics.kv_get.hit_count.get(), 0); + + // TODO: we can't fetch the revision for the empty tree, so insert a single value + Arc::new( + db.propose(vec![BatchOp::Put { + key: b"a", + value: b"b", + }]) + .await + .unwrap(), + ) + .commit() + .await + .unwrap(); + + let root = db.root_hash().await.unwrap(); + let rev = db.revision(root).await.unwrap(); + rev.val("a").await.ok().unwrap().unwrap(); + //assert_eq!(metrics.val.hit_count.get(), 1); } -#[test] -fn test_revisions() { +#[tokio::test(flavor = "multi_thread")] +async fn test_revisions() { use rand::{rngs::StdRng, Rng, SeedableRng}; let cfg = DbConfig::builder() .meta_ncached_pages(1024) @@ -99,11 +101,18 @@ fn test_revisions() { .collect(); key }; + + let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or(temp_dir().into()) + .into(); + tmpdir.push("/tmp/test_revisions"); + for i in 0..10 { - let db = - PersistedDb::new("test_revisions_db", &cfg.clone().truncate(true).build()).unwrap(); + let db = Db::new(&tmpdir, &cfg.clone().truncate(true).build()) + .await + .unwrap(); let mut dumped = VecDeque::new(); - let mut hashes: VecDeque = VecDeque::new(); + let mut hashes: VecDeque = VecDeque::new(); for _ in 0..10 { { let mut batch = Vec::new(); @@ -117,48 +126,40 @@ fn test_revisions() { }; batch.push(write); } - let proposal = db.new_proposal(batch).unwrap(); - proposal.commit_sync().unwrap(); + let proposal = Arc::new(db.propose(batch).await.unwrap()); + proposal.commit().await.unwrap(); } while dumped.len() > 10 { dumped.pop_back(); hashes.pop_back(); } - let root_hash = db.kv_root_hash().unwrap(); + let root_hash = db.root_hash().await.unwrap(); hashes.push_front(root_hash); dumped.push_front(kv_dump!(db)); - dumped - .iter() - .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(&hash).unwrap())) - .map(|(data, rev)| (data, kv_dump!(rev))) - .for_each(|(b, a)| { - if &a != b { - print!("{a}\n{b}"); - panic!("not the same"); - } - }); + for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { + let rev = db.revision(hash).await.unwrap(); + assert_eq!(rev.root_hash().await.unwrap(), hash); + assert_eq!(kv_dump!(rev), *dump, "not the same: Pass {i}"); + } } drop(db); - let db = Db::new("test_revisions_db", &cfg.clone().truncate(false).build()).unwrap(); - dumped - .iter() - .zip(hashes.iter().cloned()) - .map(|(data, hash)| (data, db.get_revision(&hash).unwrap())) - .map(|(data, rev)| (data, kv_dump!(rev))) - .for_each(|(previous_dump, after_reopen_dump)| { - if &after_reopen_dump != previous_dump { - panic!( - "not the same: pass {i}:\n{after_reopen_dump}\n--------\n{previous_dump}" - ); - } - }); - println!("i = {i}"); + let db = Db::new(tmpdir.clone(), &cfg.clone().truncate(false).build()) + .await + .unwrap(); + for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { + let rev = db.revision(hash).await.unwrap(); + rev.root_hash().await.unwrap(); + assert_eq!( + *dump, + block_in_place(|| kv_dump!(rev)), + "not the same: pass {i}" + ); + } } } -#[test] -fn create_db_issue_proof() { +#[tokio::test(flavor = "multi_thread")] +async fn create_db_issue_proof() { let cfg = DbConfig::builder() .meta_ncached_pages(1024) .meta_ncached_files(128) @@ -174,7 +175,14 @@ fn create_db_issue_proof() { .build(), ); - let db = Db::new("test_db_proof", &cfg.truncate(true).build()).unwrap(); + let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or(temp_dir().into()) + .into(); + tmpdir.push("/tmp/test_db_proof"); + + let db = firewood::db::Db::new(tmpdir, &cfg.truncate(true).build()) + .await + .unwrap(); let items = vec![ ("d", "verb"), @@ -191,10 +199,10 @@ fn create_db_issue_proof() { }; batch.push(write); } - let proposal = db.new_proposal(batch).unwrap(); - proposal.commit_sync().unwrap(); + let proposal = Arc::new(db.propose(batch).await.unwrap()); + proposal.commit().await.unwrap(); - let root_hash = db.kv_root_hash().unwrap(); + let root_hash = db.root_hash().await.unwrap(); // Add second commit let mut batch = Vec::new(); @@ -205,16 +213,16 @@ fn create_db_issue_proof() { }; batch.push(write); } - let proposal = db.new_proposal(batch).unwrap(); - proposal.commit_sync().unwrap(); + let proposal = Arc::new(db.propose(batch).await.unwrap()); + proposal.commit().await.unwrap(); - let rev = db.get_revision(&root_hash).unwrap(); + let rev = db.revision(root_hash).await.unwrap(); let key = "doe".as_bytes(); - let root_hash = rev.kv_root_hash(); + let root_hash = rev.root_hash().await.unwrap(); - match rev.prove::<&[u8]>(key) { + match rev.single_key_proof(key).await { Ok(proof) => { - let verification = proof.verify_proof(key, *root_hash.unwrap()).unwrap(); + let verification = proof.unwrap().verify_proof(key, root_hash).unwrap(); assert!(verification.is_some()); } Err(e) => { @@ -224,38 +232,30 @@ fn create_db_issue_proof() { let missing_key = "dog".as_bytes(); // The proof for the missing key will return the path to the missing key - if let Err(e) = rev.prove(missing_key) { + if let Err(e) = rev.single_key_proof(missing_key).await { println!("Error: {}", e); // TODO do type assertion on error } } -impl + ?Sized> Deref for Db<'_, P> { - type Target = PersistedDb; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl + ?Sized> DerefMut for Db<'_, P> { - fn deref_mut(&mut self) -> &mut PersistedDb { - &mut self.0 - } -} - macro_rules! assert_val { ($rev: ident, $key:literal, $expected_val:literal) => { - let actual = $rev.kv_get($key.as_bytes()).unwrap(); + let actual = $rev.val($key.as_bytes()).await.unwrap().unwrap(); assert_eq!(actual, $expected_val.as_bytes().to_vec()); }; } -#[test] -fn db_proposal() -> Result<(), DbError> { +#[tokio::test(flavor = "multi_thread")] +async fn db_proposal() -> Result<(), api::Error> { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new("test_db_proposal", &cfg.clone().truncate(true).build()) + let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or(temp_dir().into()) + .into(); + tmpdir.push("/tmp/test_db_proposal"); + + let db = firewood::db::Db::new(tmpdir, &cfg.clone().truncate(true).build()) + .await .expect("db initiation should succeed"); let batch = vec![ @@ -266,49 +266,52 @@ fn db_proposal() -> Result<(), DbError> { BatchOp::Delete { key: b"z" }, ]; - let proposal = Arc::new(db.new_proposal(batch)?); - let rev = proposal.get_revision(); - assert_val!(rev, "k", "v"); + let proposal = Arc::new(db.propose(batch).await?); + assert_val!(proposal, "k", "v"); let batch_2 = vec![BatchOp::Put { key: b"k2", value: "v2".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose_sync(batch_2)?; - let rev = proposal_2.get_revision(); - assert_val!(rev, "k", "v"); - assert_val!(rev, "k2", "v2"); + let proposal_2 = Arc::new(proposal.clone().propose(batch_2).await?); + assert_val!(proposal_2, "k", "v"); + assert_val!(proposal_2, "k2", "v2"); - proposal.commit_sync()?; - proposal_2.commit_sync()?; + proposal.clone().commit().await?; + proposal_2.commit().await?; - std::thread::scope(|scope| { - scope.spawn(|| -> Result<(), DbError> { + let t1 = tokio::spawn({ + let proposal = proposal.clone(); + async move { let another_batch = vec![BatchOp::Put { - key: b"another_k", - value: "another_v".as_bytes().to_vec(), + key: b"another_k_t1", + value: "another_v_t1".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose_sync(another_batch)?; + let another_proposal = proposal.clone().propose(another_batch).await.unwrap(); let rev = another_proposal.get_revision(); assert_val!(rev, "k", "v"); - assert_val!(rev, "another_k", "another_v"); + assert_val!(rev, "another_k_t1", "another_v_t1"); // The proposal is invalid and cannot be committed - assert!(another_proposal.commit_sync().is_err()); - Ok(()) - }); - - scope.spawn(|| -> Result<(), DbError> { + assert!(Arc::new(another_proposal).commit().await.is_err()); + } + }); + let t2 = tokio::spawn({ + let proposal = proposal.clone(); + async move { let another_batch = vec![BatchOp::Put { - key: b"another_k_1", - value: "another_v_1".as_bytes().to_vec(), + key: b"another_k_t2", + value: "another_v_t2".as_bytes().to_vec(), }]; - let another_proposal = proposal.clone().propose_sync(another_batch)?; + let another_proposal = proposal.clone().propose(another_batch).await.unwrap(); let rev = another_proposal.get_revision(); assert_val!(rev, "k", "v"); - assert_val!(rev, "another_k_1", "another_v_1"); - Ok(()) - }); + assert_val!(rev, "another_k_t2", "another_v_t2"); + assert!(Arc::new(another_proposal).commit().await.is_err()); + } }); + let (first, second) = tokio::join!(t1, t2); + first.unwrap(); + second.unwrap(); // Recursive commit @@ -316,23 +319,21 @@ fn db_proposal() -> Result<(), DbError> { key: b"k3", value: "v3".as_bytes().to_vec(), }]; - let proposal = Arc::new(db.new_proposal(batch)?); - let rev = proposal.get_revision(); - assert_val!(rev, "k", "v"); - assert_val!(rev, "k2", "v2"); - assert_val!(rev, "k3", "v3"); + let proposal = Arc::new(db.propose(batch).await?); + assert_val!(proposal, "k", "v"); + assert_val!(proposal, "k2", "v2"); + assert_val!(proposal, "k3", "v3"); let batch_2 = vec![BatchOp::Put { key: b"k4", value: "v4".as_bytes().to_vec(), }]; - let proposal_2 = proposal.clone().propose_sync(batch_2)?; - let rev = proposal_2.get_revision(); - assert_val!(rev, "k", "v"); - assert_val!(rev, "k2", "v2"); - assert_val!(rev, "k3", "v3"); - assert_val!(rev, "k4", "v4"); - - proposal_2.commit_sync()?; + let proposal_2 = Arc::new(proposal.clone().propose(batch_2).await?); + assert_val!(proposal_2, "k", "v"); + assert_val!(proposal_2, "k2", "v2"); + assert_val!(proposal_2, "k3", "v3"); + assert_val!(proposal_2, "k4", "v4"); + + proposal_2.commit().await?; Ok(()) } diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index afd9a7493697..98ca03096f63 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -1,15 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{error::Error, path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use firewood::{ - db::{BatchOp, Db as PersistedDb, DbConfig, DbError}, + db::{BatchOp, Db as PersistedDb, DbConfig}, v2::api::{Db, DbView, Proposal}, }; #[tokio::test(flavor = "multi_thread")] -async fn smoke() -> Result<(), Box> { +async fn smoke() -> Result<(), Box> { let cfg = DbConfig::builder().truncate(true).build(); let db = Arc::new(testdb(cfg).await?); let empty_hash = db.root_hash().await?; @@ -18,7 +18,7 @@ async fn smoke() -> Result<(), Box> { // insert a single key/value let (key, value) = (b"smoke", b"test"); let batch_put = BatchOp::Put { key, value }; - let proposal: Arc = db.propose(vec![batch_put]).await?.into(); + let proposal = Arc::new(db.propose(vec![batch_put]).await?); proposal.commit().await?; // ensure the latest hash is different @@ -41,11 +41,9 @@ async fn smoke() -> Result<(), Box> { Ok(()) } -async fn testdb(cfg: DbConfig) -> Result { +async fn testdb(cfg: DbConfig) -> Result { let tmpdbpath = tmp_dir().join("testdb"); - tokio::task::spawn_blocking(move || PersistedDb::new(tmpdbpath, &cfg)) - .await - .unwrap() + PersistedDb::new(tmpdbpath, &cfg).await } fn tmp_dir() -> PathBuf { diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index dd01f83f5492..433892546295 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -9,6 +9,7 @@ clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" env_logger = "0.10.0" log = "0.4.17" +tokio = { version = "1.33.0", features = ["full"] } [dev-dependencies] assert_cmd = "2.0.7" diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 10bb562d585d..d52ed1127687 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -1,9 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{Error, Result}; use clap::{value_parser, Args}; -use firewood::db::{Db, DbConfig, DbRevConfig, DiskBufferConfig, WalConfig}; +use firewood::{ + db::{Db, DbConfig, DbRevConfig, DiskBufferConfig, WalConfig}, + v2::api, +}; use log; #[derive(Args)] @@ -277,11 +279,11 @@ pub fn initialize_db_config(opts: &Options) -> DbConfig { } } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - Db::new::<&str>(opts.name.as_ref(), &db_config).map_err(Error::msg)?; + Db::new(opts.name.clone(), &db_config).await?; println!("created firewood database in {:?}", opts.name); Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 0c2520cfa644..50bba1b294af 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -1,9 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{Error, Result}; +use std::sync::Arc; + use clap::Args; -use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; +use firewood::{ + db::{BatchOp, Db, DbConfig, WalConfig}, + v2::api::{self, Db as _, Proposal}, +}; use log; #[derive(Debug, Args)] @@ -23,19 +27,19 @@ pub struct Options { pub db: String, } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("deleting key {:?}", opts); let cfg = DbConfig::builder() .truncate(false) .wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.clone(), &cfg.build()).await?; let batch: Vec> = vec![BatchOp::Delete { key: opts.key.clone(), }]; - let proposal = db.new_proposal(batch).map_err(Error::msg)?; - proposal.commit_sync().map_err(Error::msg)?; + let proposal = Arc::new(db.propose(batch).await?); + proposal.commit().await?; println!("key {} deleted successfully", opts.key); Ok(()) diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index e1d690b4713c..3a0bc2f321d0 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,10 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::{ + db::{Db, DbConfig, WalConfig}, + v2::api::{self}, +}; use log; +use tokio::task::block_in_place; #[derive(Debug, Args)] pub struct Options { @@ -18,13 +21,14 @@ pub struct Options { pub db: String, } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {:?}", opts); let cfg = DbConfig::builder() .truncate(false) .wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; - db.kv_dump(&mut std::io::stdout().lock()) - .map_err(Error::msg) + let db = Db::new(opts.db.clone(), &cfg.build()).await?; + Ok(block_in_place(|| { + db.kv_dump(&mut std::io::stdout().lock()) + })?) } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index ae314ad3d637..6a637ac4de85 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -1,9 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, bail, Error, Result}; use clap::Args; -use firewood::db::{Db, DbConfig, DbError, WalConfig}; +use firewood::{ + db::{Db, DbConfig, WalConfig}, + v2::api::{self, Db as _, DbView}, +}; use log; use std::str; @@ -24,27 +26,26 @@ pub struct Options { pub db: String, } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("get key value pair {:?}", opts); let cfg = DbConfig::builder() .truncate(false) .wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.clone(), &cfg.build()).await?; - match db.kv_get(opts.key.as_bytes()) { - Ok(val) => { - let s = match str::from_utf8(&val) { - Ok(v) => v, - Err(e) => return Err(anyhow!("Invalid UTF-8 sequence: {}", e)), - }; + let rev = db.revision(db.root_hash().await?).await?; + + match rev.val(opts.key.as_bytes()).await { + Ok(Some(val)) => { + let s = String::from_utf8_lossy(val.as_ref()); println!("{:?}", s); - if val.is_empty() { - bail!("no value found for key"); - } Ok(()) } - Err(DbError::KeyNotFound) => bail!("key not found"), - Err(e) => bail!(e), + Ok(None) => { + eprintln!("Key '{}' not found", opts.key); + Ok(()) + } + Err(e) => Err(e), } } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 804557de1f96..84e6ef2b7da6 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -1,9 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Error, Result}; +use std::sync::Arc; + use clap::Args; -use firewood::db::{BatchOp, Db, DbConfig, WalConfig}; +use firewood::{ + db::{BatchOp, Db, DbConfig, WalConfig}, + v2::api::{self, Db as _, Proposal}, +}; use log; #[derive(Debug, Args)] @@ -27,23 +31,20 @@ pub struct Options { pub db: String, } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("inserting key value pair {:?}", opts); let cfg = DbConfig::builder() .truncate(false) .wal(WalConfig::builder().max_revisions(10).build()); - let db = match Db::new(opts.db.as_str(), &cfg.build()) { - Ok(db) => db, - Err(_) => return Err(anyhow!("error opening database")), - }; + let db = Db::new(opts.db.clone(), &cfg.build()).await?; let batch: Vec, Vec>> = vec![BatchOp::Put { key: opts.key.clone().into(), value: opts.value.bytes().collect(), }]; - let proposal = db.new_proposal(batch).map_err(Error::msg)?; - proposal.commit_sync().map_err(Error::msg)?; + let proposal = Arc::new(db.propose(batch).await?); + proposal.commit().await?; println!("{}", opts.key); Ok(()) diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 6b6e9cb26253..fca9d6d873b3 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -1,8 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::Result; use clap::{Parser, Subcommand}; +use firewood::v2::api; pub mod create; pub mod delete; @@ -46,7 +46,8 @@ enum Commands { Dump(dump::Options), } -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<(), api::Error> { let cli = Cli::parse(); env_logger::init_from_env( @@ -55,11 +56,11 @@ fn main() -> Result<()> { ); match &cli.command { - Commands::Create(opts) => create::run(opts), - Commands::Insert(opts) => insert::run(opts), - Commands::Get(opts) => get::run(opts), - Commands::Delete(opts) => delete::run(opts), - Commands::Root(opts) => root::run(opts), - Commands::Dump(opts) => dump::run(opts), + Commands::Create(opts) => create::run(opts).await, + Commands::Insert(opts) => insert::run(opts).await, + Commands::Get(opts) => get::run(opts).await, + Commands::Delete(opts) => delete::run(opts).await, + Commands::Root(opts) => root::run(opts).await, + Commands::Dump(opts) => dump::run(opts).await, } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 1d76ea6c48fe..119a6b4635ce 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -1,9 +1,12 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{Error, Result}; use clap::Args; -use firewood::db::{Db, DbConfig, WalConfig}; +use firewood::v2::api::Db as _; +use firewood::{ + db::{Db, DbConfig, WalConfig}, + v2::api, +}; use log; use std::str; @@ -20,15 +23,15 @@ pub struct Options { pub db: String, } -pub fn run(opts: &Options) -> Result<()> { +pub async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("root hash {:?}", opts); let cfg = DbConfig::builder() .truncate(false) .wal(WalConfig::builder().max_revisions(10).build()); - let db = Db::new(opts.db.as_str(), &cfg.build()).map_err(Error::msg)?; + let db = Db::new(opts.db.clone(), &cfg.build()).await?; - let root = db.kv_root_hash().map_err(Error::msg)?; - println!("{:X?}", *root); + let root = db.root_hash().await?; + println!("{:X?}", root); Ok(()) } From 4b240fac3fbfc257a75d175613245e86990d415d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Oct 2023 09:31:56 -0700 Subject: [PATCH 0346/1053] Refactor tests (#332) --- firewood/tests/common/mod.rs | 72 ++++++++++++++++++++++++++++++++++++ firewood/tests/db.rs | 61 +++++++++++++++--------------- firewood/tests/v2api.rs | 26 ++++++------- 3 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 firewood/tests/common/mod.rs diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs new file mode 100644 index 000000000000..6d66c087d2f9 --- /dev/null +++ b/firewood/tests/common/mod.rs @@ -0,0 +1,72 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::{env::temp_dir, fs::remove_dir_all, ops::Deref, path::PathBuf}; + +use firewood::db::{Db, DbConfig}; +use typed_builder::TypedBuilder; + +#[derive(Clone, Debug, TypedBuilder)] +pub struct TestDbCreator { + #[builder(setter(into))] + test_name: String, + #[builder(default, setter(into))] + path: Option, + #[builder(default = DbConfig::builder().truncate(true).build())] + cfg: DbConfig, +} + +pub struct TestDb { + creator: TestDbCreator, + preserve_on_drop: bool, + db: Db, +} + +impl TestDbCreator { + pub async fn create(self) -> TestDb { + let path = self.path.clone().unwrap_or_else(|| { + let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or(temp_dir().into()) + .into(); + if path.join("tmp").is_dir() { + path.push("tmp"); + } + path.join(&self.test_name) + }); + let mut creator = self.clone(); + creator.path = path.clone().into(); + let db = Db::new(&path, &self.cfg).await.unwrap(); + TestDb { + creator, + db, + preserve_on_drop: false, + } + } +} + +impl Deref for TestDb { + type Target = Db; + + fn deref(&self) -> &Self::Target { + &self.db + } +} + +impl TestDb { + /// reopen the database, consuming the old TestDb and giving you a new one + pub async fn reopen(mut self) -> Self { + let mut creator = self.creator.clone(); + self.preserve_on_drop = true; + drop(self); + creator.cfg.truncate = false; + creator.create().await + } +} + +impl Drop for TestDb { + fn drop(&mut self) { + if !self.preserve_on_drop { + remove_dir_all(self.creator.path.as_ref().unwrap()).unwrap(); + } + } +} diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index f8bab4f317e8..2fcc0e20f453 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -2,13 +2,16 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - db::{Db, DbConfig, WalConfig}, + db::{DbConfig, WalConfig}, v2::api::{self, BatchOp, Db as _, DbView, Proposal}, }; use tokio::task::block_in_place; use std::{collections::VecDeque, env::temp_dir, path::PathBuf, sync::Arc}; +mod common; +use common::TestDbCreator; + // TODO: use a trait macro_rules! kv_dump { ($e: ident) => {{ @@ -35,14 +38,13 @@ async fn test_basic_metrics() { .build(), ); - let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(temp_dir().into()) - .into(); - tmpdir.push("/tmp/test_basic_metrics"); + let db = TestDbCreator::builder() + .cfg(cfg.build()) + .test_name("test_basic_metrics") + .build() + .create() + .await; - let db = firewood::db::Db::new(tmpdir, &cfg.truncate(true).build()) - .await - .unwrap(); // let metrics = db.metrics(); // TODO: kv_get is no longer a valid metric, and DbRev has no access to Db.metrics (yet) //assert_eq!(metrics.kv_get.hit_count.get(), 0); @@ -82,7 +84,8 @@ async fn test_revisions() { .block_nbit(8) .max_revisions(10) .build(), - ); + ) + .build(); let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); let max_len0 = 8; @@ -102,15 +105,13 @@ async fn test_revisions() { key }; - let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(temp_dir().into()) - .into(); - tmpdir.push("/tmp/test_revisions"); - for i in 0..10 { - let db = Db::new(&tmpdir, &cfg.clone().truncate(true).build()) - .await - .unwrap(); + let db = TestDbCreator::builder() + .cfg(cfg.clone()) + .test_name("test_revisions") + .build() + .create() + .await; let mut dumped = VecDeque::new(); let mut hashes: VecDeque = VecDeque::new(); for _ in 0..10 { @@ -142,10 +143,8 @@ async fn test_revisions() { assert_eq!(kv_dump!(rev), *dump, "not the same: Pass {i}"); } } - drop(db); - let db = Db::new(tmpdir.clone(), &cfg.clone().truncate(false).build()) - .await - .unwrap(); + + let db = db.reopen().await; for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { let rev = db.revision(hash).await.unwrap(); rev.root_hash().await.unwrap(); @@ -247,16 +246,16 @@ macro_rules! assert_val { #[tokio::test(flavor = "multi_thread")] async fn db_proposal() -> Result<(), api::Error> { - let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - - let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(temp_dir().into()) - .into(); - tmpdir.push("/tmp/test_db_proposal"); - - let db = firewood::db::Db::new(tmpdir, &cfg.clone().truncate(true).build()) - .await - .expect("db initiation should succeed"); + let cfg = DbConfig::builder() + .wal(WalConfig::builder().max_revisions(10).build()) + .build(); + + let db = TestDbCreator::builder() + .cfg(cfg) + .test_name("db_proposal") + .build() + .create() + .await; let batch = vec![ BatchOp::Put { diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index 98ca03096f63..4c90cbf5fd3d 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -1,17 +1,26 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; use firewood::{ - db::{BatchOp, Db as PersistedDb, DbConfig}, + db::{BatchOp, DbConfig}, v2::api::{Db, DbView, Proposal}, }; +pub mod common; +use common::TestDbCreator; + #[tokio::test(flavor = "multi_thread")] async fn smoke() -> Result<(), Box> { let cfg = DbConfig::builder().truncate(true).build(); - let db = Arc::new(testdb(cfg).await?); + let db = TestDbCreator::builder() + .cfg(cfg) + .test_name("smoke") + .build() + .create() + .await; + let empty_hash = db.root_hash().await?; assert_ne!(empty_hash, [0; 32]); @@ -40,14 +49,3 @@ async fn smoke() -> Result<(), Box> { Ok(()) } - -async fn testdb(cfg: DbConfig) -> Result { - let tmpdbpath = tmp_dir().join("testdb"); - PersistedDb::new(tmpdbpath, &cfg).await -} - -fn tmp_dir() -> PathBuf { - option_env!("CARGO_TARGET_TMPDIR") - .map(PathBuf::from) - .unwrap_or(std::env::temp_dir()) -} From 1bafa213269710edc1b8e877c048999c20875ee1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Oct 2023 14:26:58 -0700 Subject: [PATCH 0347/1053] Shale: Remove dead code (#333) --- shale/src/compact.rs | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 2557e0c19189..65937a0d801b 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -213,47 +213,6 @@ impl Storable for CompactSpaceHeader { } } -struct ObjPtrField(DiskAddress); - -impl ObjPtrField { - const MSIZE: u64 = 8; -} - -impl Storable for ObjPtrField { - fn hydrate(addr: usize, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let obj_ptr = raw.as_deref()[0..8].into(); - Ok(Self(obj_ptr)) - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.to_le_bytes())?; - Ok(()) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } -} - -impl std::ops::Deref for ObjPtrField { - type Target = DiskAddress; - fn deref(&self) -> &DiskAddress { - &self.0 - } -} - -impl std::ops::DerefMut for ObjPtrField { - fn deref_mut(&mut self) -> &mut DiskAddress { - &mut self.0 - } -} - #[derive(Debug)] struct U64Field(u64); From 8b97e1eba1e97302bacb8faff789d2d77e04c6b4 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Oct 2023 22:09:51 -0700 Subject: [PATCH 0348/1053] Remove unused U64Field implementation for Storable (#334) Signed-off-by: Richard Pringle Co-authored-by: Richard Pringle --- shale/src/compact.rs | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 65937a0d801b..098f72864907 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -213,47 +213,6 @@ impl Storable for CompactSpaceHeader { } } -#[derive(Debug)] -struct U64Field(u64); - -impl U64Field { - const MSIZE: u64 = 8; -} - -impl Storable for U64Field { - fn hydrate(addr: usize, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - Ok(Self(u64::from_le_bytes(raw.as_deref().try_into().unwrap()))) - } - - fn dehydrated_len(&self) -> u64 { - Self::MSIZE - } - - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.0.to_le_bytes())?; - Ok(()) - } -} - -impl std::ops::Deref for U64Field { - type Target = u64; - fn deref(&self) -> &u64 { - &self.0 - } -} - -impl std::ops::DerefMut for U64Field { - fn deref_mut(&mut self) -> &mut u64 { - &mut self.0 - } -} - #[derive(Debug)] struct CompactSpaceInner { meta_space: Arc, From 19edacf84ec77b1dc14679ab7a1ca68e806f4c59 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 31 Oct 2023 07:02:31 -0700 Subject: [PATCH 0349/1053] Remove MSIZE from DiskAddress (#335) Co-authored-by: Richard Pringle --- shale/src/disk_address.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shale/src/disk_address.rs b/shale/src/disk_address.rs index 571a6d3eab2e..6dd83c417336 100644 --- a/shale/src/disk_address.rs +++ b/shale/src/disk_address.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use std::hash::Hash; +use std::mem::size_of; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; @@ -160,7 +161,7 @@ impl std::ops::BitAnd for DiskAddress { } impl DiskAddress { - const MSIZE: u64 = 8; + const MSIZE: u64 = size_of::() as u64; } impl Storable for DiskAddress { From 28665e40ee5a53923d88aa67ac6feab0ab09aa9b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 1 Nov 2023 10:04:32 -0700 Subject: [PATCH 0350/1053] Remove TypedView trait (#336) --- shale/src/compact.rs | 10 +++---- shale/src/lib.rs | 66 ++++++++++++++------------------------------ 2 files changed, 24 insertions(+), 52 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 098f72864907..2f6c21ff6a67 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -227,7 +227,7 @@ impl CompactSpaceInner { StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) } - fn get_data_ref( + fn get_data_ref( &self, ptr: DiskAddress, len_limit: u64, @@ -493,12 +493,12 @@ impl CompactSpaceInner { } #[derive(Debug)] -pub struct CompactSpace { +pub struct CompactSpace { inner: RwLock>, obj_cache: ObjCache, } -impl CompactSpace { +impl CompactSpace { pub fn new( meta_space: Arc, compact_space: Arc, @@ -521,9 +521,7 @@ impl CompactSpace { } } -impl ShaleStore - for CompactSpace -{ +impl ShaleStore for CompactSpace { fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.dehydrated_len() + extra; let addr = self.inner.write().unwrap().alloc(size)?; diff --git a/shale/src/lib.rs b/shale/src/lib.rs index beeb7514710b..ba761d35fbe2 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -93,43 +93,17 @@ pub trait CachedStore: Debug + Send + Sync { fn id(&self) -> SpaceId; } -/// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object -/// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to -/// turn a `TypedView` into an [ObjRef]. -pub trait TypedView: - std::fmt::Debug + Deref + Send + Sync -{ - /// Get the offset of the initial byte in the linear space. - fn get_offset(&self) -> usize; - /// Access it as a [CachedStore] object. - fn get_mem_store(&self) -> &dyn CachedStore; - /// Access it as a mutable CachedStore object - fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore; - /// Estimate the serialized length of the current type content. It should not be smaller than - /// the actually length. - fn estimate_mem_image(&self) -> Option; - /// Serialize the type content to the memory image. It defines how the current in-memory object - /// of `T` should be represented in the linear storage space. - fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError>; - /// Gain mutable access to the typed content. By changing it, its serialized bytes (and length) - /// could change. - fn write(&mut self) -> &mut T; - /// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto - /// reflected in the underlying [CachedStore]). - fn is_mem_mapped(&self) -> bool; -} - /// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] /// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. /// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [DiskAddress] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. #[derive(Debug)] -pub struct Obj { - value: Box>, +pub struct Obj { + value: Box>, dirty: Option, } -impl Obj { +impl Obj { #[inline(always)] pub fn as_ptr(&self) -> DiskAddress { DiskAddress(NonZeroUsize::new(self.value.get_offset())) @@ -155,7 +129,7 @@ impl Obj { } #[inline(always)] - pub fn from_typed_view(value: Box>) -> Self { + pub fn from_typed_view(value: Box>) -> Self { Obj { value, dirty: None } } @@ -173,13 +147,13 @@ impl Obj { } } -impl Drop for Obj { +impl Drop for Obj { fn drop(&mut self) { self.flush_dirty() } } -impl Deref for Obj { +impl Deref for Obj { type Target = T; fn deref(&self) -> &T { &self.value @@ -187,12 +161,12 @@ impl Deref for Obj { } /// User handle that offers read & write access to the stored [ShaleStore] item. -pub struct ObjRef<'a, T: Send + Sync> { +pub struct ObjRef<'a, T: Storable> { inner: Option>, cache: &'a ObjCache, } -impl<'a, T: Send + Sync> ObjRef<'a, T> { +impl<'a, T: Storable> ObjRef<'a, T> { fn new(inner: Option>, cache: &'a ObjCache) -> Self { Self { inner, cache } } @@ -212,7 +186,7 @@ impl<'a, T: Send + Sync> ObjRef<'a, T> { } } -impl<'a, T: Send + Sync> Deref for ObjRef<'a, T> { +impl<'a, T: Storable> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { // TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix @@ -220,7 +194,7 @@ impl<'a, T: Send + Sync> Deref for ObjRef<'a, T> { } } -impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> { +impl<'a, T: Storable> Drop for ObjRef<'a, T> { fn drop(&mut self) { let mut inner = self.inner.take().unwrap(); let ptr = inner.as_ptr(); @@ -238,7 +212,7 @@ impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> { /// A persistent item storage backed by linear logical space. New items can be created and old /// items could be retrieved or dropped. -pub trait ShaleStore { +pub trait ShaleStore { /// Dereference [DiskAddress] to a unique handle that allows direct access to the item in memory. fn get_item(&'_ self, ptr: DiskAddress) -> Result, ShaleError>; /// Allocate a new item. @@ -269,7 +243,7 @@ pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { Ok(buff) } -/// Reference implementation of [TypedView]. It takes any type that implements [Storable] +/// A stored view of any [Storable] pub struct StoredView { decoded: T, mem: Box>, @@ -277,7 +251,7 @@ pub struct StoredView { len_limit: u64, } -impl Debug for StoredView { +impl Debug for StoredView { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let StoredView { decoded, @@ -300,7 +274,7 @@ impl Deref for StoredView { } } -impl TypedView for StoredView { +impl StoredView { fn get_offset(&self) -> usize { self.offset } @@ -334,7 +308,7 @@ impl TypedView for StoredView { } } -impl StoredView { +impl StoredView { #[inline(always)] fn new(offset: usize, len_limit: u64, space: &U) -> Result { let decoded = T::hydrate(offset, space)?; @@ -387,7 +361,7 @@ impl StoredView { } } -impl StoredView { +impl StoredView { fn new_from_slice( offset: usize, len_limit: u64, @@ -402,7 +376,7 @@ impl StoredView { }) } - pub fn slice( + pub fn slice( s: &Obj, offset: usize, length: u64, @@ -430,7 +404,7 @@ impl StoredView { } #[derive(Debug)] -pub struct ObjCacheInner { +pub struct ObjCacheInner { cached: lru::LruCache>, pinned: HashMap, dirty: HashSet, @@ -438,9 +412,9 @@ pub struct ObjCacheInner { /// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. #[derive(Debug)] -pub struct ObjCache(Arc>>); +pub struct ObjCache(Arc>>); -impl ObjCache { +impl ObjCache { pub fn new(capacity: usize) -> Self { Self(Arc::new(RwLock::new(ObjCacheInner { cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), From 371896283c33df3d9e6f50a185bd6a7a0415b377 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 1 Nov 2023 12:51:02 -0700 Subject: [PATCH 0351/1053] More shale cleanups (#337) --- shale/src/compact.rs | 2 +- shale/src/lib.rs | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/shale/src/compact.rs b/shale/src/compact.rs index 2f6c21ff6a67..55cd99752836 100644 --- a/shale/src/compact.rs +++ b/shale/src/compact.rs @@ -253,7 +253,7 @@ impl CompactSpaceInner { .unwrap(); if desc_addr != DiskAddress(**self.header.meta_space_tail) { - let desc_last = self.get_descriptor(**self.header.meta_space_tail.value)?; + let desc_last = self.get_descriptor(*self.header.meta_space_tail.value)?; let mut desc = self.get_descriptor(desc_addr)?; desc.write(|r| *r = *desc_last).unwrap(); let mut header = self.get_header(desc.haddr.into())?; diff --git a/shale/src/lib.rs b/shale/src/lib.rs index ba761d35fbe2..54be4f052695 100644 --- a/shale/src/lib.rs +++ b/shale/src/lib.rs @@ -99,7 +99,7 @@ pub trait CachedStore: Debug + Send + Sync { /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. #[derive(Debug)] pub struct Obj { - value: Box>, + value: StoredView, dirty: Option, } @@ -109,7 +109,7 @@ impl Obj { DiskAddress(NonZeroUsize::new(self.value.get_offset())) } - /// Write to the underlying object. Returns `Some(())` on success. + /// Write to the underlying object. Returns `Ok(())` on success. #[inline] pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { modify(self.value.write()); @@ -129,7 +129,7 @@ impl Obj { } #[inline(always)] - pub fn from_typed_view(value: Box>) -> Self { + pub fn from_typed_view(value: StoredView) -> Self { Obj { value, dirty: None } } @@ -341,11 +341,11 @@ impl StoredView { ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Box::new(Self::new( + Ok(Obj::from_typed_view(Self::new( ptr.get(), len_limit, store, - )?))) + )?)) } #[inline(always)] @@ -355,9 +355,9 @@ impl StoredView { len_limit: u64, decoded: T, ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Box::new(Self::from_hydrated( + Ok(Obj::from_typed_view(Self::from_hydrated( addr, len_limit, decoded, store, - )?))) + )?)) } } @@ -390,12 +390,7 @@ impl StoredView { error: "dirty write", }); } - let r = Box::new(StoredView::new_from_slice( - addr_, - length, - decoded, - s.value.get_mem_store(), - )?); + let r = StoredView::new_from_slice(addr_, length, decoded, s.value.get_mem_store())?; Ok(Obj { value: r, dirty: None, From e65d24aa2a6b1fd30c649968e47a87d731cbc012 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 1 Nov 2023 13:42:05 -0700 Subject: [PATCH 0352/1053] Move shale into firewood as a module (#338) --- .github/check-license-headers.yaml | 4 ++-- Cargo.toml | 1 - firewood/Cargo.toml | 1 - firewood/benches/hashops.rs | 12 +++++----- {shale => firewood}/benches/shale-bench.rs | 6 ++--- firewood/src/db.rs | 11 +++++---- firewood/src/db/proposal.rs | 2 +- firewood/src/lib.rs | 2 ++ firewood/src/merkle.rs | 2 +- firewood/src/merkle/node.rs | 4 ++-- firewood/src/merkle/trie_hash.rs | 2 +- firewood/src/merkle_util.rs | 8 +++---- firewood/src/proof.rs | 4 +--- firewood/src/shale/README.md | 3 +++ {shale/src => firewood/src/shale}/cached.rs | 4 ++-- {shale/src => firewood/src/shale}/compact.rs | 6 ++--- .../src/shale}/disk_address.rs | 2 +- shale/src/lib.rs => firewood/src/shale/mod.rs | 0 firewood/src/storage/buffer.rs | 4 ++-- firewood/src/storage/mod.rs | 2 +- firewood/tests/merkle.rs | 3 ++- shale/Cargo.toml | 24 ------------------- shale/README.md | 3 --- 23 files changed, 43 insertions(+), 67 deletions(-) rename {shale => firewood}/benches/shale-bench.rs (99%) create mode 100644 firewood/src/shale/README.md rename {shale/src => firewood/src/shale}/cached.rs (98%) rename {shale/src => firewood/src/shale}/compact.rs (99%) rename {shale/src => firewood/src/shale}/disk_address.rs (98%) rename shale/src/lib.rs => firewood/src/shale/mod.rs (100%) delete mode 100644 shale/Cargo.toml delete mode 100644 shale/README.md diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 9837b102655a..e4e9826b2398 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -10,7 +10,7 @@ "RELEASE.md", "rpc/**", "README*", - "*/README*", + "**/README*", "Cargo.toml", "*/Cargo.toml", "libaio/**", @@ -27,7 +27,7 @@ "RELEASE.md", "rpc/**", "README*", - "*/README*", + "**/README*", "Cargo.toml", "*/Cargo.toml", "libaio/**", diff --git a/Cargo.toml b/Cargo.toml index 8fd718676b60..942c4d3c06e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "growth-ring", "libaio", "rpc", - "shale", ] resolver = "2" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 89db5ed8b5e1..dcbdb0f8caa1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,7 +22,6 @@ bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" growth-ring = { version = "0.0.4", path = "../growth-ring" } libaio = {version = "0.0.4", path = "../libaio" } -shale = { version = "0.0.4", path = "../shale" } futures = "0.3.24" hex = "0.4.3" lru = "0.12.0" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 76771d943904..eb16740ad914 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -7,17 +7,17 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, use firewood::{ db::{BatchOp, DbConfig}, merkle::{Merkle, TrieHash, TRIE_HASH_LEN}, + shale::{ + cached::PlainMem, + compact::{CompactHeader, CompactSpace}, + disk_address::DiskAddress, + CachedStore, ObjCache, Storable, StoredView, + }, storage::WalConfig, v2::api::{Db, Proposal}, }; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; -use shale::{ - cached::PlainMem, - compact::{CompactHeader, CompactSpace}, - disk_address::DiskAddress, - CachedStore, ObjCache, Storable, StoredView, -}; use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path, sync::Arc}; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); diff --git a/shale/benches/shale-bench.rs b/firewood/benches/shale-bench.rs similarity index 99% rename from shale/benches/shale-bench.rs rename to firewood/benches/shale-bench.rs index e29006cc5ac0..0fb6a8e749f2 100644 --- a/shale/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -4,14 +4,14 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; -use pprof::ProfilerGuard; -use rand::Rng; -use shale::{ +use firewood::shale::{ cached::{DynamicMem, PlainMem}, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, CachedStore, Obj, StoredView, }; +use pprof::ProfilerGuard; +use rand::Rng; use std::{fs::File, os::raw::c_int, path::Path}; const BENCH_MEM_SIZE: usize = 2_000_000; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ea79ec38355a..1cc14ab63f4c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,6 +1,12 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::shale::{ + self, + compact::{CompactSpace, CompactSpaceHeader}, + disk_address::DiskAddress, + CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, +}; pub use crate::{ config::{DbConfig, DbRevConfig}, storage::{buffer::DiskBufferConfig, WalConfig}, @@ -21,11 +27,6 @@ use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; use metered::metered; use parking_lot::{Mutex, RwLock}; -use shale::{ - compact::{CompactSpace, CompactSpaceHeader}, - disk_address::DiskAddress, - CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, -}; use std::{ collections::VecDeque, error::Error, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 42ee06f9f30d..bfe6caa7e1d4 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -6,6 +6,7 @@ use super::{ DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; +use crate::shale::CachedStore; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, @@ -13,7 +14,6 @@ use crate::{ }; use async_trait::async_trait; use parking_lot::{Mutex, RwLock}; -use shale::CachedStore; use std::{io::ErrorKind, sync::Arc}; use tokio::task::block_in_place; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index e9da7c75960a..d932522607fb 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -193,5 +193,7 @@ pub mod storage; pub mod config; pub mod nibbles; +// TODO: shale should not be pub, but there are integration test dependencies :( +pub mod shale; pub mod v2; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 0ca162a7bf01..f1121ce7e05e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::{nibbles::Nibbles, v2::api::Proof}; use sha3::Digest; -use shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use std::{ cmp::Ordering, collections::HashMap, diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index f40e74cd53db..38ff1fc8b5c4 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,11 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; use bincode::{Error, Options}; use enum_as_inner::EnumAsInner; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; -use shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; use std::{ fmt::{self, Debug}, io::{Cursor, Read, Write}, @@ -850,7 +850,7 @@ pub(super) mod tests { use std::array::from_fn; use super::*; - use shale::cached::PlainMem; + use crate::shale::cached::PlainMem; use test_case::test_case; pub fn leaf(path: Vec, data: Vec) -> Node { diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index c9777fb1dda7..5f3e98aa9286 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use shale::{CachedStore, ShaleError, Storable}; +use crate::shale::{CachedStore, ShaleError, Storable}; use std::{ fmt::{self, Debug}, io::Write, diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index a3b5fa4659ed..3e9fcd3a1c2c 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,15 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::shale::{ + self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, + ShaleStore, StoredView, +}; use crate::{ merkle::{Merkle, Node, Ref, RefMut, TrieHash}, proof::ProofError, v2::api::Proof, }; -use shale::{ - cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, - StoredView, -}; use std::{num::NonZeroUsize, sync::Arc}; use thiserror::Error; diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index c76765797baf..8a566a737015 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -4,11 +4,9 @@ use std::cmp::Ordering; use std::ops::Deref; +use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; use nix::errno::Errno; use sha3::Digest; -use shale::disk_address::DiskAddress; -use shale::ShaleError; -use shale::ShaleStore; use thiserror::Error; use crate::nibbles::Nibbles; diff --git a/firewood/src/shale/README.md b/firewood/src/shale/README.md new file mode 100644 index 000000000000..361804102e36 --- /dev/null +++ b/firewood/src/shale/README.md @@ -0,0 +1,3 @@ +# shale + +This directory was forked from [`shale`](https://github.com/Determinant/shale) at commit [`caa6d75`](https://github.com/Determinant/shale/commit/caa6d7543d253e2172c51a65d226d65d232a9b9a), under MIT license. diff --git a/shale/src/cached.rs b/firewood/src/shale/cached.rs similarity index 98% rename from shale/src/cached.rs rename to firewood/src/shale/cached.rs index b98a2dc29039..05263d83c7c7 100644 --- a/shale/src/cached.rs +++ b/firewood/src/shale/cached.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use crate::shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use std::{ borrow::BorrowMut, fmt::Debug, @@ -10,7 +10,7 @@ use std::{ }; /// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying -/// out stuff (persistent data structures) built on [ShaleStore](crate::ShaleStore) in memory, without having to write +/// out stuff (persistent data structures) built on [ShaleStore](super::ShaleStore) in memory, without having to write /// your own [CachedStore] implementation. #[derive(Debug)] pub struct PlainMem { diff --git a/shale/src/compact.rs b/firewood/src/shale/compact.rs similarity index 99% rename from shale/src/compact.rs rename to firewood/src/shale/compact.rs index 55cd99752836..f2d1acf9ebe7 100644 --- a/shale/src/compact.rs +++ b/firewood/src/shale/compact.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::ObjCache; +use crate::shale::ObjCache; use super::disk_address::DiskAddress; use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; @@ -589,7 +589,7 @@ impl ShaleStore for Comp mod tests { use sha3::Digest; - use crate::{cached::DynamicMem, ObjCache}; + use crate::shale::{self, cached::DynamicMem, ObjCache}; use super::*; @@ -648,7 +648,7 @@ mod tests { let compact_header = DiskAddress::from(0x1); dm.write( compact_header.unwrap().get(), - &crate::to_dehydrated(&CompactSpaceHeader::new( + &shale::to_dehydrated(&CompactSpaceHeader::new( reserved.0.unwrap(), reserved.0.unwrap(), )) diff --git a/shale/src/disk_address.rs b/firewood/src/shale/disk_address.rs similarity index 98% rename from shale/src/disk_address.rs rename to firewood/src/shale/disk_address.rs index 6dd83c417336..dade387d315a 100644 --- a/shale/src/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut}; use bytemuck::NoUninit; -use crate::{CachedStore, ShaleError, Storable}; +use crate::shale::{CachedStore, ShaleError, Storable}; /// The virtual disk address of an object #[repr(C)] diff --git a/shale/src/lib.rs b/firewood/src/shale/mod.rs similarity index 100% rename from shale/src/lib.rs rename to firewood/src/shale/mod.rs diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 2b50e26e99e7..b1fdfaa67815 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; +use crate::shale::SpaceId; use crate::storage::DeltaPage; use aiofut::{AioBuilder, AioError, AioManager}; use futures::future::join_all; @@ -19,7 +20,6 @@ use growthring::{ walerror::WalError, WalFileImpl, WalStoreImpl, }; -use shale::SpaceId; use tokio::{ sync::{ mpsc, @@ -621,6 +621,7 @@ mod tests { use tokio::task::block_in_place; use super::*; + use crate::shale::CachedStore; use crate::{ file, storage::{ @@ -628,7 +629,6 @@ mod tests { StoreRevShared, ZeroStore, }, }; - use shale::CachedStore; const STATE_SPACE: SpaceId = 0x0; const HASH_SIZE: usize = 32; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 6779e99aa605..21575e89dffe 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,10 +4,10 @@ // TODO: try to get rid of the use `RefCell` in this file use self::buffer::DiskBufferRequester; use crate::file::File; +use crate::shale::{self, CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use nix::fcntl::{flock, FlockArg}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use std::{ collections::HashMap, fmt::{self, Debug}, diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 6cf879035d5d..353e169ba2fc 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -5,10 +5,11 @@ use firewood::{ merkle::Node, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, proof::ProofError, + // TODO: we should not be using shale from an integration test + shale::{cached::DynamicMem, compact::CompactSpace}, v2::api::Proof, }; use rand::Rng; -use shale::{cached::DynamicMem, compact::CompactSpace}; use std::collections::HashMap; type Store = CompactSpace; diff --git a/shale/Cargo.toml b/shale/Cargo.toml deleted file mode 100644 index 33f0bda4e53a..000000000000 --- a/shale/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "shale" -version = "0.0.4" -edition = "2021" -description = "Useful abstraction and light-weight implemenation for a key-value store." -license = "../LICENSE.md" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -hex = "0.4.3" -lru = "0.12.0" -thiserror = "1.0.38" -bytemuck = { version = "1.13.1", features = ["derive"] } - -[dev-dependencies] -criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.13.0", features = ["flamegraph"] } -sha3 = "0.10.7" -rand = "0.8.5" - -[[bench]] -name = "shale-bench" -harness = false diff --git a/shale/README.md b/shale/README.md deleted file mode 100644 index 96396377c416..000000000000 --- a/shale/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# shale - -This crate was forked from [`shale`](https://github.com/Determinant/shale) at commit [`caa6d75`](https://github.com/Determinant/shale/commit/caa6d7543d253e2172c51a65d226d65d232a9b9a), under MIT license. \ No newline at end of file From 8fb67174f1f056d549e91d91fd9bae721094feb2 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 1 Nov 2023 18:01:11 -0400 Subject: [PATCH 0353/1053] Enable manual trigger of default-cache build (#339) Signed-off-by: Richard Pringle --- .github/workflows/default-branch-cache.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 7e18c7f2e65d..80f08891e7df 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -2,6 +2,7 @@ name: default-branch-cache on: + workflow_dispatch: push: branches: - main From b33a9c1c30b3f7cf2ce805e7289a6098691e9873 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 2 Nov 2023 10:52:02 -0700 Subject: [PATCH 0354/1053] Rename hydrate/dehydrate to serialize/deserialize (#340) --- firewood/benches/hashops.rs | 4 ++-- firewood/src/db.rs | 6 +++--- firewood/src/merkle/node.rs | 16 +++++++-------- firewood/src/merkle/trie_hash.rs | 8 ++++---- firewood/src/shale/compact.rs | 32 +++++++++++++++--------------- firewood/src/shale/disk_address.rs | 6 +++--- firewood/src/shale/mod.rs | 16 +++++++-------- 7 files changed, 44 insertions(+), 44 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index eb16740ad914..93dc9b3d4752 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -67,10 +67,10 @@ fn bench_trie_hash(criterion: &mut Criterion) { criterion .benchmark_group("TrieHash") .bench_function("dehydrate", |b| { - b.iter(|| ZERO_HASH.dehydrate(&mut to).unwrap()); + b.iter(|| ZERO_HASH.serialize(&mut to).unwrap()); }) .bench_function("hydrate", |b| { - b.iter(|| TrieHash::hydrate(0, &store).unwrap()); + b.iter(|| TrieHash::deserialize(0, &store).unwrap()); }); } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 1cc14ab63f4c..de1ff059ccab 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -201,7 +201,7 @@ impl DbHeader { } impl Storable for DbHeader { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -213,11 +213,11 @@ impl Storable for DbHeader { }) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); cur.write_all(&self.kv_root.to_le_bytes())?; Ok(()) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 38ff1fc8b5c4..b048bb2a55de 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -454,7 +454,7 @@ impl Node { }), lazy_dirty: AtomicBool::new(false), } - .dehydrated_len() + .serialized_len() }) } @@ -525,7 +525,7 @@ impl Node { } impl Storable for Node { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { const META_SIZE: usize = 32 + 1 + 1; let meta_raw = mem.get_view(addr, META_SIZE as u64) @@ -738,7 +738,7 @@ impl Storable for Node { } } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { 32 + 1 + 1 + match &self.inner { @@ -770,7 +770,7 @@ impl Storable for Node { } } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); let mut attrs = 0; @@ -907,14 +907,14 @@ pub(super) mod tests { #[test_case(branch(0x0a, b"hello world".to_vec().into(), None); "branch with data")] #[test_case(branch(0x0a, None, vec![0x01, 0x02, 0x03].into()); "branch without data")] fn test_encoding(node: Node) { - let mut bytes = vec![0; node.dehydrated_len() as usize]; + let mut bytes = vec![0; node.serialized_len() as usize]; - node.dehydrate(&mut bytes).unwrap(); + node.serialize(&mut bytes).unwrap(); - let mut mem = PlainMem::new(node.dehydrated_len(), 0x00); + let mut mem = PlainMem::new(node.serialized_len(), 0x00); mem.write(0, &bytes); - let hydrated_node = Node::hydrate(0, &mem).unwrap(); + let hydrated_node = Node::deserialize(0, &mem).unwrap(); assert_eq!(node, hydrated_node); } diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 5f3e98aa9286..557da0c587c5 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -21,7 +21,7 @@ impl std::ops::Deref for TrieHash { } impl Storable for TrieHash { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, U64_TRIE_HASH_LEN) .ok_or(ShaleError::InvalidCacheView { @@ -32,11 +32,11 @@ impl Storable for TrieHash { Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { U64_TRIE_HASH_LEN } - fn dehydrate(&self, mut to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, mut to: &mut [u8]) -> Result<(), ShaleError> { to.write_all(&self.0).map_err(ShaleError::Io) } } @@ -56,7 +56,7 @@ mod tests { let zero_hash = TrieHash([0u8; TRIE_HASH_LEN]); let mut to = [1u8; TRIE_HASH_LEN]; - zero_hash.dehydrate(&mut to).unwrap(); + zero_hash.serialize(&mut to).unwrap(); assert_eq!(&to, &zero_hash.0); } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index f2d1acf9ebe7..96df8c7d2673 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -29,7 +29,7 @@ impl CompactHeader { } impl Storable for CompactHeader { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -48,11 +48,11 @@ impl Storable for CompactHeader { }) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); cur.write_all(&self.payload_size.to_le_bytes())?; cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; @@ -71,7 +71,7 @@ impl CompactFooter { } impl Storable for CompactFooter { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -82,11 +82,11 @@ impl Storable for CompactFooter { Ok(Self { payload_size }) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { Cursor::new(to).write_all(&self.payload_size.to_le_bytes())?; Ok(()) } @@ -103,7 +103,7 @@ impl CompactDescriptor { } impl Storable for CompactDescriptor { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -119,11 +119,11 @@ impl Storable for CompactDescriptor { }) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); cur.write_all(&self.payload_size.to_le_bytes())?; cur.write_all(&self.haddr.to_le_bytes())?; @@ -180,7 +180,7 @@ impl CompactSpaceHeader { } impl Storable for CompactSpaceHeader { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -199,11 +199,11 @@ impl Storable for CompactSpaceHeader { }) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); cur.write_all(&self.meta_space_tail.to_le_bytes())?; cur.write_all(&self.compact_space_tail.to_le_bytes())?; @@ -523,7 +523,7 @@ impl CompactSpace { impl ShaleStore for CompactSpace { fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { - let size = item.dehydrated_len() + extra; + let size = item.serialized_len() + extra; let addr = self.inner.write().unwrap().alloc(size)?; let obj = { @@ -611,7 +611,7 @@ mod tests { } impl Storable for Hash { - fn hydrate(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -625,11 +625,11 @@ mod tests { )) } - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = to; cur.write_all(&self.0)?; Ok(()) diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index dade387d315a..969b917f46e6 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -165,17 +165,17 @@ impl DiskAddress { } impl Storable for DiskAddress { - fn dehydrated_len(&self) -> u64 { + fn serialized_len(&self) -> u64 { Self::MSIZE } - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError> { + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { use std::io::{Cursor, Write}; Cursor::new(to).write_all(&self.0.unwrap().get().to_le_bytes())?; Ok(()) } - fn hydrate(addr: usize, mem: &U) -> Result { + fn deserialize(addr: usize, mem: &U) -> Result { let raw = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 54be4f052695..7d1d549d61ff 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -227,9 +227,9 @@ pub trait ShaleStore { /// implementation could be directly transmuting to/from a POD struct. But sometimes necessary /// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. pub trait Storable { - fn dehydrated_len(&self) -> u64; - fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>; - fn hydrate(addr: usize, mem: &T) -> Result + fn serialized_len(&self) -> u64; + fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError>; + fn deserialize(addr: usize, mem: &T) -> Result where Self: Sized; fn is_mem_mapped(&self) -> bool { @@ -238,8 +238,8 @@ pub trait Storable { } pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { - let mut buff = vec![0; item.dehydrated_len() as usize]; - item.dehydrate(&mut buff)?; + let mut buff = vec![0; item.serialized_len() as usize]; + item.serialize(&mut buff)?; Ok(buff) } @@ -288,7 +288,7 @@ impl StoredView { } fn estimate_mem_image(&self) -> Option { - let len = self.decoded.dehydrated_len(); + let len = self.decoded.serialized_len(); if len > self.len_limit { None } else { @@ -297,7 +297,7 @@ impl StoredView { } fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { - self.decoded.dehydrate(mem_image) + self.decoded.serialize(mem_image) } fn write(&mut self) -> &mut T { @@ -311,7 +311,7 @@ impl StoredView { impl StoredView { #[inline(always)] fn new(offset: usize, len_limit: u64, space: &U) -> Result { - let decoded = T::hydrate(offset, space)?; + let decoded = T::deserialize(offset, space)?; Ok(Self { offset, decoded, From 1989a96d6f9d18a06edd4cb769aca828add9fb78 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 15 Nov 2023 09:22:11 -0800 Subject: [PATCH 0355/1053] Rkuris/streaming iterator from start (#346) --- firewood/src/db.rs | 25 ++- firewood/src/merkle.rs | 335 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 6 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index de1ff059ccab..7c8c8a152ca9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,12 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::shale::{ - self, - compact::{CompactSpace, CompactSpaceHeader}, - disk_address::DiskAddress, - CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, -}; pub use crate::{ config::{DbConfig, DbRevConfig}, storage::{buffer::DiskBufferConfig, WalConfig}, @@ -23,8 +17,18 @@ use crate::{ }, v2::api::{self, HashKey, KeyType, Proof, ValueType}, }; +use crate::{ + merkle, + shale::{ + self, + compact::{CompactSpace, CompactSpaceHeader}, + disk_address::DiskAddress, + CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, + }, +}; use async_trait::async_trait; use bytemuck::{cast_slice, AnyBitPattern}; + use metered::metered; use parking_lot::{Mutex, RwLock}; use std::{ @@ -312,6 +316,15 @@ impl + Send + Sync> api::DbView for DbRev { } impl + Send + Sync> DbRev { + pub fn stream( + &self, + start_key: Option, + ) -> Result, api::Error> { + self.merkle + .get_iter(start_key, self.header.kv_root) + .map_err(|e| api::Error::InternalError(e.into())) + } + fn flush_dirty(&mut self) -> Option<()> { self.header.flush_dirty(); self.merkle.flush_dirty()?; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index f1121ce7e05e..32625192eac1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,7 +2,9 @@ // See the file LICENSE.md for licensing terms. use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; +use crate::v2::api; use crate::{nibbles::Nibbles, v2::api::Proof}; +use futures::Stream; use sha3::Digest; use std::{ cmp::Ordering, @@ -10,6 +12,7 @@ use std::{ io::Write, iter::once, sync::{atomic::Ordering::Relaxed, OnceLock}, + task::Poll, }; use thiserror::Error; @@ -1175,6 +1178,296 @@ impl + Send + Sync> Merkle { pub fn flush_dirty(&self) -> Option<()> { self.store.flush_dirty() } + + pub(crate) fn get_iter>( + &self, + key: Option, + root: DiskAddress, + ) -> Result, MerkleError> { + Ok(MerkleKeyValueStream { + key_state: IteratorState::new(key), + merkle_root: root, + merkle: self, + }) + } +} + +enum IteratorState<'a> { + /// Start iterating at the beginning of the trie, + /// returning the lowest key/value pair first + StartAtBeginning, + /// Start iterating at the specified key + StartAtKey(Vec), + /// Continue iterating after the given last_node and parents + Iterating { + last_node: ObjRef<'a>, + parents: Vec<(ObjRef<'a>, u8)>, + }, +} +impl IteratorState<'_> { + fn new>(starting: Option) -> Self { + match starting { + None => Self::StartAtBeginning, + Some(key) => Self::StartAtKey(key.as_ref().to_vec()), + } + } +} + +// The default state is to start at the beginning +impl<'a> Default for IteratorState<'a> { + fn default() -> Self { + Self::StartAtBeginning + } +} + +/// A MerkleKeyValueStream iterates over keys/values for a merkle trie. +/// This iterator is not fused. If you read past the None value, you start +/// over at the beginning. If you need a fused iterator, consider using +/// std::iter::fuse +pub struct MerkleKeyValueStream<'a, S> { + key_state: IteratorState<'a>, + merkle_root: DiskAddress, + merkle: &'a Merkle, +} + +impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyValueStream<'a, S> { + type Item = Result<(Vec, Vec), api::Error>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll> { + // Note that this sets the key_state to StartAtBeginning temporarily + let found_key = match std::mem::take(&mut self.key_state) { + IteratorState::StartAtBeginning => { + let root_node = self + .merkle + .get_node(self.merkle_root) + .map_err(|e| api::Error::InternalError(e.into()))?; + let mut last_node = root_node; + let mut parents = vec![]; + let leaf = loop { + match last_node.inner() { + NodeType::Branch(branch) => { + let Some((leftmost_position, leftmost_address)) = branch + .children + .iter() + .enumerate() + .filter_map(|(i, addr)| addr.map(|addr| (i, addr))) + .next() + else { + // we already exhausted the branch node. This happens with an empty trie + // ... or a corrupt one + return if parents.is_empty() { + // empty trie + Poll::Ready(None) + } else { + // branch with NO children, not at the top + Poll::Ready(Some(Err(api::Error::InternalError(Box::new( + MerkleError::ParentLeafBranch, + ))))) + }; + }; + + let next = self + .merkle + .get_node(leftmost_address) + .map_err(|e| api::Error::InternalError(e.into()))?; + + parents.push((last_node, leftmost_position as u8)); + + last_node = next; + } + NodeType::Leaf(leaf) => break leaf, + NodeType::Extension(_) => todo!(), + } + }; + + // last_node should have a leaf; compute the key and value + let current_key = key_from_parents_and_leaf(&parents, leaf); + + self.key_state = IteratorState::Iterating { last_node, parents }; + + current_key + } + IteratorState::StartAtKey(key) => { + // TODO: support finding the next key after K + let root_node = self + .merkle + .get_node(self.merkle_root) + .map_err(|e| api::Error::InternalError(e.into()))?; + + let (found_node, parents) = self + .merkle + .get_node_and_parents_by_key(root_node, &key) + .map_err(|e| api::Error::InternalError(e.into()))?; + + let Some(last_node) = found_node else { + return Poll::Ready(None); + }; + + let returned_key_value = match last_node.inner() { + NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), + NodeType::Leaf(leaf) => (key, leaf.1.to_vec()), + NodeType::Extension(_) => todo!(), + }; + + self.key_state = IteratorState::Iterating { last_node, parents }; + + return Poll::Ready(Some(Ok(returned_key_value))); + } + IteratorState::Iterating { + last_node, + mut parents, + } => { + match last_node.inner() { + NodeType::Branch(branch) => { + // previously rendered the value from a branch node, so walk down to the first available child + let Some((child_position, child_address)) = branch + .children + .iter() + .enumerate() + .filter_map(|(child_position, &addr)| { + addr.map(|addr| (child_position, addr)) + }) + .next() + else { + // Branch node with no children? + return Poll::Ready(Some(Err(api::Error::InternalError(Box::new( + MerkleError::ParentLeafBranch, + ))))); + }; + + parents.push((last_node, child_position as u8)); // remember where we walked down from + + let current_node = self + .merkle + .get_node(child_address) + .map_err(|e| api::Error::InternalError(e.into()))?; + + let found_key = key_from_parents(&parents); + + self.key_state = IteratorState::Iterating { + // continue iterating from here + last_node: current_node, + parents, + }; + + found_key + } + NodeType::Leaf(leaf) => { + let mut next = parents.pop().map(|(node, position)| (node, Some(position))); + loop { + match next { + None => return Poll::Ready(None), + Some((parent, child_position)) => { + // Assume all parents are branch nodes + let children = parent.inner().as_branch().unwrap().chd(); + + // we use wrapping_add here because the value might be u8::MAX indicating that + // we want to go down branch + let start_position = + child_position.map(|pos| pos + 1).unwrap_or_default(); + + let Some((found_position, found_address)) = children + .iter() + .enumerate() + .skip(start_position as usize) + .filter_map(|(offset, addr)| { + addr.map(|addr| (offset as u8, addr)) + }) + .next() + else { + next = parents + .pop() + .map(|(node, position)| (node, Some(position))); + continue; + }; + + // we push (node, None) which will start at the beginning of the next branch node + let child = self + .merkle + .get_node(found_address) + .map(|node| (node, None)) + .map_err(|e| api::Error::InternalError(e.into()))?; + + // stop_descending if: + // - on a branch and it has a value; OR + // - on a leaf + let stop_descending = match child.0.inner() { + NodeType::Branch(branch) => branch.value.is_some(), + NodeType::Leaf(_) => true, + NodeType::Extension(_) => todo!(), + }; + + next = Some(child); + + parents.push((parent, found_position)); + + if stop_descending { + break; + } + } + } + } + // recompute current_key + // TODO: Can we keep current_key updated as we walk the tree instead of building it from the top all the time? + let current_key = key_from_parents_and_leaf(&parents, leaf); + + self.key_state = IteratorState::Iterating { + last_node: next.unwrap().0, + parents, + }; + + current_key + } + + NodeType::Extension(_) => todo!(), + } + } + }; + + // figure out the value to return from the state + // if we get here, we're sure to have something to return + // TODO: It's possible to return a reference to the data since the last_node is + // saved in the iterator + let return_value = match &self.key_state { + IteratorState::Iterating { + last_node, + parents: _, + } => { + let value = match last_node.inner() { + NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), + NodeType::Leaf(leaf) => leaf.1.to_vec(), + NodeType::Extension(_) => todo!(), + }; + + (found_key, value) + } + _ => unreachable!(), + }; + + Poll::Ready(Some(Ok(return_value))) + } +} + +/// Compute a key from a set of parents +fn key_from_parents(parents: &[(ObjRef, u8)]) -> Vec { + parents[1..] + .chunks_exact(2) + .map(|parents| (parents[0].1 << 4) + parents[1].1) + .collect::>() +} +fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec { + let mut iter = parents[1..] + .iter() + .map(|parent| parent.1) + .chain(leaf.0.to_vec()); + let mut data = Vec::with_capacity(iter.size_hint().0); + while let (Some(hi), Some(lo)) = (iter.next(), iter.next()) { + data.push((hi << 4) + lo); + } + data } fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { @@ -1270,6 +1563,7 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { #[cfg(test)] mod tests { use super::*; + use futures::StreamExt; use node::tests::{extension, leaf}; use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; use std::sync::Arc; @@ -1388,6 +1682,47 @@ mod tests { } } + #[tokio::test] + async fn iterate_empty() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let mut it = merkle.get_iter(Some(b"x"), root).unwrap(); + let next = it.next().await; + assert!(next.is_none()) + } + + #[test_case(Some(&[u8::MIN]); "Starting at first key")] + #[test_case(None; "No start specified")] + #[test_case(Some(&[128u8]); "Starting in middle")] + #[test_case(Some(&[u8::MAX]); "Starting at last key")] + #[tokio::test] + async fn iterate_many(start: Option<&[u8]>) { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + // insert all values from u8::MIN to u8::MAX, with the key and value the same + for k in u8::MIN..=u8::MAX { + merkle.insert([k], vec![k], root).unwrap(); + } + + let mut it = merkle.get_iter(start, root).unwrap(); + // we iterate twice because we should get a None then start over + for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { + let next = it.next().await.unwrap().unwrap(); + assert_eq!(next.0, next.1,); + assert_eq!(next.1, vec![k]); + } + assert!(it.next().await.is_none()); + + // ensure that reading past the end returns all the values + for k in u8::MIN..=u8::MAX { + let next = it.next().await.unwrap().unwrap(); + assert_eq!(next.0, next.1); + assert_eq!(next.1, vec![k]); + } + assert!(it.next().await.is_none()); + } + #[test] fn remove_one() { let key = b"hello"; From 175e08bca851ee8af0863d6ed7393a47614ddcf6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 16 Nov 2023 08:44:31 -0800 Subject: [PATCH 0356/1053] 1/3: kv_dump should be done with the iterator (#347) --- fwdctl/Cargo.toml | 1 + fwdctl/src/dump.rs | 25 ++++++++++++++++++++----- fwdctl/tests/cli.rs | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 433892546295..1236f9ce159b 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -10,6 +10,7 @@ anyhow = "1.0.66" env_logger = "0.10.0" log = "0.4.17" tokio = { version = "1.33.0", features = ["full"] } +futures-util = "0.3.29" [dev-dependencies] assert_cmd = "2.0.7" diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 3a0bc2f321d0..9320943de4ac 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,13 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::borrow::Cow; + use clap::Args; use firewood::{ db::{Db, DbConfig, WalConfig}, - v2::api::{self}, + v2::api::{self, Db as _}, }; +use futures_util::StreamExt; use log; -use tokio::task::block_in_place; #[derive(Debug, Args)] pub struct Options { @@ -28,7 +30,20 @@ pub async fn run(opts: &Options) -> Result<(), api::Error> { .wal(WalConfig::builder().max_revisions(10).build()); let db = Db::new(opts.db.clone(), &cfg.build()).await?; - Ok(block_in_place(|| { - db.kv_dump(&mut std::io::stdout().lock()) - })?) + let latest_hash = db.root_hash().await?; + let latest_rev = db.revision(latest_hash).await?; + let mut stream = latest_rev.stream::>(None)?; + loop { + match stream.next().await { + None => break, + Some(Ok((key, value))) => { + println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)) + } + Some(Err(e)) => return Err(e), + } + } + Ok(()) +} +fn u8_to_string(data: &[u8]) -> Cow<'_, str> { + String::from_utf8_lossy(data) } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 4b92aad534bd..89c4d4f51991 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -202,7 +202,7 @@ fn fwdctl_dump() -> Result<()> { .args([tmpdb::path()]) .assert() .success() - .stdout(predicate::str::is_empty().not()); + .stdout(predicate::str::contains("2023")); fwdctl_delete_db().map_err(|e| anyhow!(e))?; From 55f976af2c552df8b4338d3fb4e9dc3a7f5e3bec Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 17 Nov 2023 17:32:29 -0800 Subject: [PATCH 0357/1053] Fix new clippy lint (rust 1.74.0) (#355) --- firewood/src/merkle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 32625192eac1..69bb641be06b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1609,7 +1609,7 @@ mod tests { fn branch(value: Vec, encoded_child: Option>) -> Node { let children = Default::default(); - let value = Some(value).map(Data); + let value = Some(Data(value)); let mut children_encoded = <[Option>; NBRANCH]>::default(); if let Some(child) = encoded_child { From 3f35b3008e030519993d01e3146583b3dc3e85cf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 20 Nov 2023 07:07:24 -0800 Subject: [PATCH 0358/1053] Rkuris/range proofs (#345) --- firewood/src/db.rs | 19 ++++--- firewood/src/db/proposal.rs | 6 +- firewood/src/merkle.rs | 86 +++++++++++++++++++++++++---- firewood/src/merkle/range_proof.rs | 88 ++++++++++++++++++++++++++++++ firewood/src/v2/api.rs | 19 ++++--- firewood/src/v2/db.rs | 10 ++-- firewood/src/v2/emptydb.rs | 6 +- firewood/src/v2/propose.rs | 6 +- 8 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 firewood/src/merkle/range_proof.rs diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7c8c8a152ca9..91459ae4d6c1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -305,13 +305,16 @@ impl + Send + Sync> api::DbView for DbRev { .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } - async fn range_proof( + async fn range_proof( &self, - _first_key: Option, - _last_key: Option, - _limit: usize, - ) -> Result>, api::Error> { - todo!() + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result, Vec>>, api::Error> { + self.merkle + .range_proof(self.header.kv_root, first_key, last_key, limit) + .await + .map_err(|e| api::Error::InternalError(Box::new(e))) } } @@ -322,7 +325,7 @@ impl + Send + Sync> DbRev { ) -> Result, api::Error> { self.merkle .get_iter(start_key, self.header.kv_root) - .map_err(|e| api::Error::InternalError(e.into())) + .map_err(|e| api::Error::InternalError(Box::new(e))) } fn flush_dirty(&mut self) -> Option<()> { @@ -454,7 +457,7 @@ impl Db { } block_in_place(|| Db::new_internal(db_path, cfg.clone())) - .map_err(|e| api::Error::InternalError(e.into())) + .map_err(|e| api::Error::InternalError(Box::new(e))) } /// Open a database. diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index bfe6caa7e1d4..8afd099ec0cc 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -295,12 +295,12 @@ impl api::DbView for Proposal { .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, - _limit: usize, - ) -> Result>, api::Error> + _limit: Option, + ) -> Result, Vec>>, api::Error> where K: api::KeyType, { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 69bb641be06b..30c683f248d5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,6 +1,5 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. - use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::v2::api; use crate::{nibbles::Nibbles, v2::api::Proof}; @@ -18,6 +17,7 @@ use thiserror::Error; mod node; mod partial_path; +pub(super) mod range_proof; mod trie_hash; pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; @@ -1243,7 +1243,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let root_node = self .merkle .get_node(self.merkle_root) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; let mut last_node = root_node; let mut parents = vec![]; let leaf = loop { @@ -1272,7 +1272,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let next = self .merkle .get_node(leftmost_address) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; parents.push((last_node, leftmost_position as u8)); @@ -1295,12 +1295,12 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let root_node = self .merkle .get_node(self.merkle_root) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; let (found_node, parents) = self .merkle .get_node_and_parents_by_key(root_node, &key) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; let Some(last_node) = found_node else { return Poll::Ready(None); @@ -1343,7 +1343,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let current_node = self .merkle .get_node(child_address) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; let found_key = key_from_parents(&parents); @@ -1389,7 +1389,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal .merkle .get_node(found_address) .map(|node| (node, None)) - .map_err(|e| api::Error::InternalError(e.into()))?; + .map_err(|e| api::Error::InternalError(Box::new(e)))?; // stop_descending if: // - on a branch and it has a value; OR @@ -1569,9 +1569,9 @@ mod tests { use std::sync::Arc; use test_case::test_case; - #[test_case(vec![0x12, 0x34, 0x56], vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] - #[test_case(vec![0xc0, 0xff], vec![0xc, 0x0, 0xf, 0xf])] - fn to_nibbles(bytes: Vec, nibbles: Vec) { + #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] + #[test_case(vec![0xc0, 0xff], &[0xc, 0x0, 0xf, 0xf])] + fn to_nibbles(bytes: Vec, nibbles: &[u8]) { let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); assert_eq!(n, nibbles); } @@ -1688,7 +1688,7 @@ mod tests { let root = merkle.init_root().unwrap(); let mut it = merkle.get_iter(Some(b"x"), root).unwrap(); let next = it.next().await; - assert!(next.is_none()) + assert!(next.is_none()); } #[test_case(Some(&[u8::MIN]); "Starting at first key")] @@ -1747,7 +1747,7 @@ mod tests { #[test] fn remove_many() { - let mut merkle = create_test_merkle(); + let mut merkle: Merkle> = create_test_merkle(); let root = merkle.init_root().unwrap(); // insert values @@ -1775,4 +1775,66 @@ mod tests { assert!(fetched_val.is_none()); } } + + #[tokio::test] + async fn empty_range_proof() { + let merkle: Merkle> = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + assert!(merkle + .range_proof::<&[u8]>(root, None, None, None) + .await + .unwrap() + .is_none()); + } + + #[tokio::test] + async fn full_range_proof() { + let mut merkle: Merkle> = create_test_merkle(); + let root = merkle.init_root().unwrap(); + // insert values + for key_val in u8::MIN..=u8::MAX { + let key = &[key_val]; + let val = &[key_val]; + + merkle.insert(key, val.to_vec(), root).unwrap(); + } + merkle.flush_dirty(); + + let rangeproof = merkle + .range_proof::<&[u8]>(root, None, None, None) + .await + .unwrap() + .unwrap(); + assert_eq!(rangeproof.middle.len(), (u8::MAX - 1).into()); + assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); + let left_proof = merkle.prove([u8::MIN], root).unwrap(); + let right_proof = merkle.prove([u8::MAX], root).unwrap(); + assert_eq!(rangeproof.first_key_proof.0, left_proof.0); + assert_eq!(rangeproof.last_key_proof.0, right_proof.0); + } + + #[tokio::test] + async fn single_value_range_proof() { + const RANDOM_KEY: u8 = 42; + + let mut merkle: Merkle> = create_test_merkle(); + let root = merkle.init_root().unwrap(); + // insert values + for key_val in u8::MIN..=u8::MAX { + let key = &[key_val]; + let val = &[key_val]; + + merkle.insert(key, val.to_vec(), root).unwrap(); + } + merkle.flush_dirty(); + + let rangeproof = merkle + .range_proof(root, Some([RANDOM_KEY]), None, Some(1)) + .await + .unwrap() + .unwrap(); + assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); + assert_eq!(rangeproof.middle.len(), 0); + } } diff --git a/firewood/src/merkle/range_proof.rs b/firewood/src/merkle/range_proof.rs new file mode 100644 index 000000000000..2a1dd1da52cf --- /dev/null +++ b/firewood/src/merkle/range_proof.rs @@ -0,0 +1,88 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::future::ready; + +use futures::{StreamExt as _, TryStreamExt as _}; + +use crate::{ + shale::{disk_address::DiskAddress, ShaleStore}, + v2::api, +}; + +use super::{Merkle, Node}; + +impl + Send + Sync> Merkle { + pub(crate) async fn range_proof( + &self, + root: DiskAddress, + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result, Vec>>, api::Error> { + // limit of 0 is always an empty RangeProof + if let Some(0) = limit { + return Ok(None); + } + + let mut stream = self + .get_iter(first_key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + // fetch the first key from the stream + let first_result = stream.next().await; + + // transpose the Option> to Result, E> + // If this is an error, the ? operator will return it + let Some((key, _)) = first_result.transpose()? else { + // nothing returned, either the trie is empty or the key wasn't found + return Ok(None); + }; + + let first_key_proof = self + .prove(key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let limit = limit.map(|old_limit| old_limit - 1); + + // we stop streaming if either we hit the limit or the key returned was larger + // than the largest key requested + let mut middle = stream + .take(limit.unwrap_or(usize::MAX)) + .take_while(|kv_result| { + // no last key asked for, so keep going + let Some(last_key) = last_key.as_ref() else { + return ready(true); + }; + + // return the error if there was one + let Ok(kv) = kv_result else { + return ready(true); + }; + + // keep going if the key returned is less than the last key requested + ready(kv.0.as_slice() <= last_key.as_ref()) + }) + .try_collect::, Vec)>>() + .await?; + + // remove the last key from middle and do a proof on it + let last_key_proof = match middle.pop() { + None => { + return Ok(Some(api::RangeProof { + first_key_proof: first_key_proof.clone(), + middle: vec![], + last_key_proof: first_key_proof, + })) + } + Some((last_key, _)) => self + .prove(last_key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?, + }; + + Ok(Some(api::RangeProof { + first_key_proof, + middle, + last_key_proof, + })) + } +} diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 0ddacbe71648..4321c005bc84 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -71,22 +71,25 @@ pub enum Error { InvalidProposal, #[error("Internal error")] - InternalError(Box), + InternalError(Box), + + #[error("Range too small")] + RangeTooSmall, } /// A range proof, consisting of a proof of the first key and the last key, /// and a vector of all key/value pairs #[derive(Debug)] -pub struct RangeProof { - pub first_key: Proof, - pub last_key: Proof, +pub struct RangeProof { + pub first_key_proof: Proof>, + pub last_key_proof: Proof>, pub middle: Vec<(K, V)>, } /// A proof that a single key is present /// /// The generic N represents the storage for the node data -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Proof(pub HashMap); /// The database interface, which includes a type for a static view of @@ -152,12 +155,12 @@ pub trait DbView { /// * `last_key` - If None, continue to the end of the database /// * `limit` - The maximum number of keys in the range proof /// - async fn range_proof( + async fn range_proof( &self, first_key: Option, last_key: Option, - limit: usize, - ) -> Result>, Error>; + limit: Option, + ) -> Result, Vec>>, Error>; } /// A proposal for a new revision of the database. diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index edfbe1ee6c13..3e6378159ff4 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -32,11 +32,13 @@ pub struct Db { impl From for api::Error { fn from(value: DbError) -> Self { match value { - DbError::InvalidParams => api::Error::InternalError(value.into()), - DbError::Merkle(e) => api::Error::InternalError(e.into()), + DbError::InvalidParams => api::Error::InternalError(Box::new(value)), + DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), DbError::System(e) => api::Error::IO(e.into()), - DbError::KeyNotFound | DbError::CreateError => api::Error::InternalError(value.into()), - DbError::Shale(e) => api::Error::InternalError(e.into()), + DbError::KeyNotFound | DbError::CreateError => { + api::Error::InternalError(Box::new(value)) + } + DbError::Shale(e) => api::Error::InternalError(Box::new(e)), DbError::IO(e) => api::Error::IO(e), DbError::InvalidProposal => api::Error::InvalidProposal, } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 762504aad005..746373ab126b 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -66,12 +66,12 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, - _limit: usize, - ) -> Result>, Error> { + _limit: Option, + ) -> Result, Vec>>, Error> { Ok(None) } } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index d00e8ee2ac29..053a6a059b4b 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -128,12 +128,12 @@ impl api::DbView for Proposal { todo!() } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, - _limit: usize, - ) -> Result>, api::Error> { + _limit: Option, + ) -> Result, Vec>>, api::Error> { todo!() } } From 9690aa4b645fabd4ebab9676140e9b43a4a08198 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 20 Nov 2023 10:09:24 -0800 Subject: [PATCH 0359/1053] README updates (#357) --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f3a884401c71..f3b77743f2d0 100644 --- a/README.md +++ b/README.md @@ -117,18 +117,19 @@ The focus of this milestone will be to support synchronization to other instances to replicate the state. A synchronization library should also be developed for this milestone. -- [ ] :runner: Add support for Ava Labs generic test tool via grpc client +- [x] Migrate to a fully async interface - [ ] :runner: Pluggable encoding for nodes, for optional compatibility with MerkleDB -- [ ] Support replicating the full state with corresponding range proofs that +- [ ] :runner: MerkleDB root hash in parity for seamless transition between MerkleDB + and Firewood. +- [ ] :runner: Support replicating the full state with corresponding range proofs that verify the correctness of the data. -- [ ] Support replicating the delta state from the last sync point with - corresponding range proofs that verify the correctness of the data. +- [ ] Pluggable IO subsystem (tokio\_uring, monoio, etc) +- [ ] Add metric reporting - [ ] Enforce limits on the size of the range proof as well as keys to make synchronization easier for clients. -- [ ] MerkleDB root hash in parity for seamless transition between MerkleDB - and Firewood. -- [ ] Add metric reporting -- [ ] Migrate to a fully async interface, consider tokio_uring, monoio, etc +- [ ] Add support for Ava Labs generic test tool via grpc client +- [ ] Support replicating the delta state from the last sync point with + corresponding change proofs that verify the correctness of the data. - [ ] Refactor `Shale` to be more idiomatic, consider rearchitecting it ## Build From 8c4bd6412bfb888d136235259decc84d7eaed2cf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 20 Nov 2023 14:03:03 -0800 Subject: [PATCH 0360/1053] Enable lints for discussion (#356) --- fwdctl/Cargo.toml | 9 +++++++++ fwdctl/src/create.rs | 4 ++-- fwdctl/src/delete.rs | 2 +- fwdctl/src/dump.rs | 4 ++-- fwdctl/src/get.rs | 4 ++-- fwdctl/src/insert.rs | 2 +- fwdctl/src/root.rs | 4 ++-- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 1236f9ce159b..e91ccf12023f 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -16,3 +16,12 @@ futures-util = "0.3.29" assert_cmd = "2.0.7" predicates = "3.0.1" serial_test = "2.0.0" + +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" +missing_const_for_fn = "warn" diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index d52ed1127687..51d3ed25186b 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -245,7 +245,7 @@ pub struct Options { max_revisions: u32, } -pub fn initialize_db_config(opts: &Options) -> DbConfig { +pub(super) const fn initialize_db_config(opts: &Options) -> DbConfig { DbConfig { meta_ncached_pages: opts.meta_ncached_pages, meta_ncached_files: opts.meta_ncached_files, @@ -279,7 +279,7 @@ pub fn initialize_db_config(opts: &Options) -> DbConfig { } } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db_config = initialize_db_config(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 50bba1b294af..c47f5a9b4b20 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -27,7 +27,7 @@ pub struct Options { pub db: String, } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("deleting key {:?}", opts); let cfg = DbConfig::builder() .truncate(false) diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 9320943de4ac..3ae12358aaf8 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -23,7 +23,7 @@ pub struct Options { pub db: String, } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {:?}", opts); let cfg = DbConfig::builder() .truncate(false) @@ -37,7 +37,7 @@ pub async fn run(opts: &Options) -> Result<(), api::Error> { match stream.next().await { None => break, Some(Ok((key, value))) => { - println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)) + println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); } Some(Err(e)) => return Err(e), } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 6a637ac4de85..a94c437d1c7d 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -26,7 +26,7 @@ pub struct Options { pub db: String, } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("get key value pair {:?}", opts); let cfg = DbConfig::builder() .truncate(false) @@ -39,7 +39,7 @@ pub async fn run(opts: &Options) -> Result<(), api::Error> { match rev.val(opts.key.as_bytes()).await { Ok(Some(val)) => { let s = String::from_utf8_lossy(val.as_ref()); - println!("{:?}", s); + println!("{s:?}"); Ok(()) } Ok(None) => { diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 84e6ef2b7da6..8cb56b8f7a93 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -31,7 +31,7 @@ pub struct Options { pub db: String, } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("inserting key value pair {:?}", opts); let cfg = DbConfig::builder() .truncate(false) diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 119a6b4635ce..973d446165e9 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -23,7 +23,7 @@ pub struct Options { pub db: String, } -pub async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("root hash {:?}", opts); let cfg = DbConfig::builder() .truncate(false) @@ -32,6 +32,6 @@ pub async fn run(opts: &Options) -> Result<(), api::Error> { let db = Db::new(opts.db.clone(), &cfg.build()).await?; let root = db.root_hash().await?; - println!("{:X?}", root); + println!("{root:X?}"); Ok(()) } From 03ae1a23aea1f8524b261ac4b59c0b4faedfdb35 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 20 Nov 2023 18:11:43 -0500 Subject: [PATCH 0361/1053] Add proof verification test (#359) --- firewood/src/merkle.rs | 32 ++++++++++++++++++++++++++++++++ firewood/src/merkle/node.rs | 1 + firewood/src/merkle_util.rs | 2 +- firewood/src/proof.rs | 19 +++++++++++++++---- firewood/tests/db.rs | 2 +- 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 30c683f248d5..4f2f77c713c9 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1837,4 +1837,36 @@ mod tests { assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); assert_eq!(rangeproof.middle.len(), 0); } + + #[test] + fn shared_path_proof() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let key1 = b"key1"; + let value1 = b"1"; + merkle.insert(key1, value1.to_vec(), root).unwrap(); + + let key2 = b"key2"; + let value2 = b"2"; + merkle.insert(key2, value2.to_vec(), root).unwrap(); + + let root_hash = merkle.root_hash(root).unwrap(); + + let verified = { + let key = key1; + let proof = merkle.prove(key, root).unwrap(); + proof.verify(key, root_hash.0).unwrap() + }; + + assert_eq!(verified, Some(value1.to_vec())); + + let verified = { + let key = key2; + let proof = merkle.prove(key, root).unwrap(); + proof.verify(key, root_hash.0).unwrap() + }; + + assert_eq!(verified, Some(value2.to_vec())); + } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index b048bb2a55de..604b38d2eb42 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -376,6 +376,7 @@ impl NodeType { match items.len() { EXT_NODE_SIZE => { let mut items = items.into_iter(); + let decoded_key: Vec = items.next().unwrap().decode()?; let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 3e9fcd3a1c2c..f7bad0a89f55 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -100,7 +100,7 @@ impl + Send + Sync> MerkleSetup { ) -> Result>, DataStoreError> { let hash: [u8; 32] = *self.root_hash()?; proof - .verify_proof(key, hash) + .verify(key, hash) .map_err(|_err| DataStoreError::ProofVerificationError) } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8a566a737015..fa50404a6a51 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -92,6 +92,8 @@ impl From for ProofError { /// to a single proof step. If reaches an end step during proof verification, /// the hash value will be none, and the encoded value will be the value of the /// node. + +#[derive(Debug)] struct SubProof { encoded: Vec, hash: Option<[u8; 32]>, @@ -103,7 +105,7 @@ impl + Send> Proof { /// proof contains invalid trie nodes or the wrong value. /// /// The generic N represents the storage for the node data - pub fn verify_proof>( + pub fn verify>( &self, key: K, root_hash: [u8; 32], @@ -113,24 +115,33 @@ impl + Send> Proof { let mut cur_hash = root_hash; let proofs_map = &self.0; - loop { + let value_node = loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; + let node = NodeType::decode(cur_proof.as_ref())?; let (sub_proof, traversed_nibbles) = locate_subproof(key_nibbles, node)?; key_nibbles = traversed_nibbles; cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(p) if key_nibbles.size_hint().0 == 0 => return Ok(Some(p.encoded)), + Some(p) if key_nibbles.size_hint().0 == 0 => break p.encoded, // The trie doesn't contain the key. Some(SubProof { hash: Some(hash), .. }) => hash, _ => return Ok(None), }; - } + }; + + let value = match NodeType::decode(&value_node) { + Ok(NodeType::Branch(branch)) => branch.value().as_ref().map(|v| v.to_vec()), + Ok(NodeType::Leaf(leaf)) => leaf.data().to_vec().into(), + _ => return Ok(value_node.into()), + }; + + Ok(value) } pub fn concat_proofs(&mut self, other: Proof) { diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 2fcc0e20f453..83524b13628c 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -221,7 +221,7 @@ async fn create_db_issue_proof() { match rev.single_key_proof(key).await { Ok(proof) => { - let verification = proof.unwrap().verify_proof(key, root_hash).unwrap(); + let verification = proof.unwrap().verify(key, root_hash).unwrap(); assert!(verification.is_some()); } Err(e) => { From bb3673ba86189945284afb49bdb8e4625b7fbc3d Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 21 Nov 2023 13:26:37 -0500 Subject: [PATCH 0362/1053] Move range-proof-builder (#361) --- firewood/src/merkle.rs | 77 +++++++++++++++++++++++++- firewood/src/merkle/range_proof.rs | 88 ------------------------------ 2 files changed, 75 insertions(+), 90 deletions(-) delete mode 100644 firewood/src/merkle/range_proof.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4f2f77c713c9..5ab27623d700 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -3,8 +3,9 @@ use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::v2::api; use crate::{nibbles::Nibbles, v2::api::Proof}; -use futures::Stream; +use futures::{Stream, StreamExt, TryStreamExt}; use sha3::Digest; +use std::future::ready; use std::{ cmp::Ordering, collections::HashMap, @@ -17,7 +18,6 @@ use thiserror::Error; mod node; mod partial_path; -pub(super) mod range_proof; mod trie_hash; pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; @@ -1190,6 +1190,79 @@ impl + Send + Sync> Merkle { merkle: self, }) } + + pub(super) async fn range_proof( + &self, + root: DiskAddress, + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result, Vec>>, api::Error> { + // limit of 0 is always an empty RangeProof + if let Some(0) = limit { + return Ok(None); + } + + let mut stream = self + .get_iter(first_key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + // fetch the first key from the stream + let first_result = stream.next().await; + + // transpose the Option> to Result, E> + // If this is an error, the ? operator will return it + let Some((key, _)) = first_result.transpose()? else { + // nothing returned, either the trie is empty or the key wasn't found + return Ok(None); + }; + + let first_key_proof = self + .prove(key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let limit = limit.map(|old_limit| old_limit - 1); + + // we stop streaming if either we hit the limit or the key returned was larger + // than the largest key requested + let mut middle = stream + .take(limit.unwrap_or(usize::MAX)) + .take_while(|kv_result| { + // no last key asked for, so keep going + let Some(last_key) = last_key.as_ref() else { + return ready(true); + }; + + // return the error if there was one + let Ok(kv) = kv_result else { + return ready(true); + }; + + // keep going if the key returned is less than the last key requested + ready(kv.0.as_slice() <= last_key.as_ref()) + }) + .try_collect::, Vec)>>() + .await?; + + // remove the last key from middle and do a proof on it + let last_key_proof = match middle.pop() { + None => { + return Ok(Some(api::RangeProof { + first_key_proof: first_key_proof.clone(), + middle: vec![], + last_key_proof: first_key_proof, + })) + } + Some((last_key, _)) => self + .prove(last_key, root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?, + }; + + Ok(Some(api::RangeProof { + first_key_proof, + middle, + last_key_proof, + })) + } } enum IteratorState<'a> { diff --git a/firewood/src/merkle/range_proof.rs b/firewood/src/merkle/range_proof.rs deleted file mode 100644 index 2a1dd1da52cf..000000000000 --- a/firewood/src/merkle/range_proof.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::future::ready; - -use futures::{StreamExt as _, TryStreamExt as _}; - -use crate::{ - shale::{disk_address::DiskAddress, ShaleStore}, - v2::api, -}; - -use super::{Merkle, Node}; - -impl + Send + Sync> Merkle { - pub(crate) async fn range_proof( - &self, - root: DiskAddress, - first_key: Option, - last_key: Option, - limit: Option, - ) -> Result, Vec>>, api::Error> { - // limit of 0 is always an empty RangeProof - if let Some(0) = limit { - return Ok(None); - } - - let mut stream = self - .get_iter(first_key, root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - // fetch the first key from the stream - let first_result = stream.next().await; - - // transpose the Option> to Result, E> - // If this is an error, the ? operator will return it - let Some((key, _)) = first_result.transpose()? else { - // nothing returned, either the trie is empty or the key wasn't found - return Ok(None); - }; - - let first_key_proof = self - .prove(key, root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let limit = limit.map(|old_limit| old_limit - 1); - - // we stop streaming if either we hit the limit or the key returned was larger - // than the largest key requested - let mut middle = stream - .take(limit.unwrap_or(usize::MAX)) - .take_while(|kv_result| { - // no last key asked for, so keep going - let Some(last_key) = last_key.as_ref() else { - return ready(true); - }; - - // return the error if there was one - let Ok(kv) = kv_result else { - return ready(true); - }; - - // keep going if the key returned is less than the last key requested - ready(kv.0.as_slice() <= last_key.as_ref()) - }) - .try_collect::, Vec)>>() - .await?; - - // remove the last key from middle and do a proof on it - let last_key_proof = match middle.pop() { - None => { - return Ok(Some(api::RangeProof { - first_key_proof: first_key_proof.clone(), - middle: vec![], - last_key_proof: first_key_proof, - })) - } - Some((last_key, _)) => self - .prove(last_key, root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?, - }; - - Ok(Some(api::RangeProof { - first_key_proof, - middle, - last_key_proof, - })) - } -} From 9e1fd97c4e5bc861373952d128d0c120bae62e0a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 13:54:34 -0500 Subject: [PATCH 0363/1053] split node files (#360) --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 110 ++++--- firewood/src/merkle/node.rs | 386 +++++------------------ firewood/src/merkle/node/branch.rs | 184 +++++++++++ firewood/src/merkle/node/extension.rs | 94 ++++++ firewood/src/merkle/node/leaf.rs | 75 +++++ firewood/src/merkle/node/partial_path.rs | 129 ++++++++ firewood/src/merkle/partial_path.rs | 91 ------ firewood/src/shale/mod.rs | 2 +- 9 files changed, 628 insertions(+), 444 deletions(-) create mode 100644 firewood/src/merkle/node/branch.rs create mode 100644 firewood/src/merkle/node/extension.rs create mode 100644 firewood/src/merkle/node/leaf.rs create mode 100644 firewood/src/merkle/node/partial_path.rs delete mode 100644 firewood/src/merkle/partial_path.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index dcbdb0f8caa1..8ae8ea51f25b 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,6 +34,7 @@ thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.18.0" bincode = "1.3.3" +bitflags = "2.4.1" [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5ab27623d700..10fb47869f7b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -17,11 +17,9 @@ use std::{ use thiserror::Error; mod node; -mod partial_path; mod trie_hash; -pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; -pub use partial_path::PartialPath; +pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath, MAX_CHILDREN}; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; @@ -87,7 +85,8 @@ impl + Send + Sync> Merkle { self.store .put_item( Node::branch(BranchNode { - children: [None; NBRANCH], + // path: vec![].into(), + children: [None; MAX_CHILDREN], value: None, children_encoded: Default::default(), }), @@ -192,7 +191,7 @@ impl + Send + Sync> Merkle { node_to_split.write(|node| { // TODO: handle unwrap better - let path = node.inner.path_mut().unwrap(); + let path = node.inner.path_mut(); *path = PartialPath(new_split_node_path.to_vec()); @@ -202,7 +201,7 @@ impl + Send + Sync> Merkle { let new_node = Node::leaf(PartialPath(new_node_path.to_vec()), Data(val)); let leaf_address = self.put_node(new_node)?.as_ptr(); - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let last_matching_nibble = matching_path[idx]; chd[last_matching_nibble as usize] = Some(leaf_address); @@ -218,6 +217,7 @@ impl + Send + Sync> Merkle { chd[n_path[idx] as usize] = Some(address); let new_branch = Node::branch(BranchNode { + // path: PartialPath(matching_path[..idx].to_vec()), children: chd, value: None, children_encoded: Default::default(), @@ -251,7 +251,7 @@ impl + Send + Sync> Merkle { node_to_split, |u| { match &mut u.inner { - NodeType::Leaf(u) => u.1 = Data(val), + NodeType::Leaf(u) => u.data = Data(val), NodeType::Extension(u) => { let write_result = self.get_node(u.chd()).and_then(|mut b_ref| { @@ -298,7 +298,7 @@ impl + Send + Sync> Merkle { node_to_split .write(|u| { // TODO: handle unwraps better - let path = u.inner.path_mut().unwrap(); + let path = u.inner.path_mut(); *path = PartialPath(n_path[insert_path.len() + 1..].to_vec()); u.rehash(); @@ -306,6 +306,7 @@ impl + Send + Sync> Merkle { .unwrap(); let leaf_address = match &node_to_split.inner { + // TODO: handle BranchNode case NodeType::Extension(u) if u.path.len() == 0 => { deleted.push(node_to_split_address); u.chd() @@ -341,7 +342,7 @@ impl + Send + Sync> Merkle { }; // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] - let mut children = [None; NBRANCH]; + let mut children = [None; MAX_CHILDREN]; children[idx] = leaf_address.into(); @@ -428,31 +429,11 @@ impl + Send + Sync> Merkle { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done - NodeType::Branch(n) => match n.children[current_nibble as usize] { - Some(c) => (node, c), - None => { - // insert the leaf to the empty slot - // create a new leaf - let leaf_ptr = self - .put_node(Node::leaf(PartialPath(key_nibbles.collect()), Data(val)))? - .as_ptr(); - // set the current child to point to this leaf - node.write(|u| { - let uu = u.inner.as_branch_mut().unwrap(); - uu.children[current_nibble as usize] = Some(leaf_ptr); - u.rehash(); - }) - .unwrap(); - - break None; - } - }, - NodeType::Leaf(n) => { // we collided with another key; make a copy // of the stored key to pass into split - let n_path = n.0.to_vec(); - let n_value = Some(n.1.clone()); + let n_path = n.path.to_vec(); + let n_value = Some(n.data.clone()); let rem_path = once(current_nibble).chain(key_nibbles).collect::>(); self.split( @@ -468,6 +449,31 @@ impl + Send + Sync> Merkle { break None; } + NodeType::Branch(n) => { + match n.children[current_nibble as usize] { + Some(c) => (node, c), + None => { + // insert the leaf to the empty slot + // create a new leaf + let leaf_ptr = self + .put_node(Node::leaf( + PartialPath(key_nibbles.collect()), + Data(val), + ))? + .as_ptr(); + // set the current child to point to this leaf + node.write(|u| { + let uu = u.inner.as_branch_mut().unwrap(); + uu.children[current_nibble as usize] = Some(leaf_ptr); + u.rehash(); + }) + .unwrap(); + + break None; + } + } + } + NodeType::Extension(n) => { let n_path = n.path.to_vec(); let n_ptr = n.chd(); @@ -522,13 +528,13 @@ impl + Send + Sync> Merkle { None } NodeType::Leaf(n) => { - if n.0.len() == 0 { - n.1 = Data(val); + if n.path.len() == 0 { + n.data = Data(val); None } else { - let idx = n.0[0]; - n.0 = PartialPath(n.0[1..].to_vec()); + let idx = n.path[0]; + n.path = PartialPath(n.path[1..].to_vec()); u.rehash(); Some((idx, true, None, val)) @@ -557,7 +563,7 @@ impl + Send + Sync> Merkle { }; if let Some((idx, more, ext, val)) = info { - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let c_ptr = if more { u_ptr @@ -570,6 +576,7 @@ impl + Send + Sync> Merkle { let branch = self .put_node(Node::branch(BranchNode { + // path: vec![].into(), children: chd, value: Some(Data(val)), children_encoded: Default::default(), @@ -687,7 +694,7 @@ impl + Send + Sync> Merkle { // to: [p: Branch] -> [Leaf/Ext] let write_result = c_ref.write(|c| { let partial_path = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -728,7 +735,7 @@ impl + Send + Sync> Merkle { let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -811,7 +818,7 @@ impl + Send + Sync> Merkle { // to: [Branch] -> [Leaf/Ext] let write_result = c_ref.write(|c| { match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), } @@ -840,7 +847,7 @@ impl + Send + Sync> Merkle { let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -903,7 +910,7 @@ impl + Send + Sync> Merkle { } NodeType::Leaf(n) => { - found = Some(n.1.clone()); + found = Some(n.data.clone()); deleted.push(node_ref.as_ptr()); self.after_remove_leaf(&mut parents, &mut deleted)? } @@ -1008,7 +1015,7 @@ impl + Send + Sync> Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - let node_ref = if once(nib).chain(key_nibbles).eq(n.0.iter().copied()) { + let node_ref = if once(nib).chain(key_nibbles).eq(n.path.iter().copied()) { Some(node_ref) } else { None @@ -1043,7 +1050,7 @@ impl + Send + Sync> Merkle { // when we're done iterating over nibbles, check if the node we're at has a value let node_ref = match &node_ref.inner { NodeType::Branch(n) if n.value.as_ref().is_some() => Some(node_ref), - NodeType::Leaf(n) if n.0.len() == 0 => Some(node_ref), + NodeType::Leaf(n) if n.path.len() == 0 => Some(node_ref), _ => None, }; @@ -1142,7 +1149,7 @@ impl + Send + Sync> Merkle { } } NodeType::Leaf(n) => { - if n.0.len() == 0 { + if n.path.len() == 0 { nodes.push(u_ref.as_ptr()); } } @@ -1381,7 +1388,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let returned_key_value = match last_node.inner() { NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), - NodeType::Leaf(leaf) => (key, leaf.1.to_vec()), + NodeType::Leaf(leaf) => (key, leaf.data.to_vec()), NodeType::Extension(_) => todo!(), }; @@ -1511,7 +1518,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal } => { let value = match last_node.inner() { NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), - NodeType::Leaf(leaf) => leaf.1.to_vec(), + NodeType::Leaf(leaf) => leaf.data.to_vec(), NodeType::Extension(_) => todo!(), }; @@ -1535,7 +1542,7 @@ fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec std::ops::Deref for Ref<'a> { fn deref(&self) -> &[u8] { match &self.0.inner { NodeType::Branch(n) => n.value.as_ref().unwrap(), - NodeType::Leaf(n) => &n.1, + NodeType::Leaf(n) => &n.data, _ => unreachable!(), } } @@ -1604,7 +1611,7 @@ impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { |u| { modify(match &mut u.inner { NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, - NodeType::Leaf(n) => &mut n.1 .0, + NodeType::Leaf(n) => &mut n.data.0, _ => unreachable!(), }); u.rehash() @@ -1683,13 +1690,14 @@ mod tests { fn branch(value: Vec, encoded_child: Option>) -> Node { let children = Default::default(); let value = Some(Data(value)); - let mut children_encoded = <[Option>; NBRANCH]>::default(); + let mut children_encoded = <[Option>; MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { children_encoded[0] = Some(child); } Node::branch(BranchNode { + // path: vec![].into(), children, value, children_encoded, @@ -1712,7 +1720,7 @@ mod tests { } #[test] - fn insert_and_retrieve() { + fn insert_and_retrieve_one() { let key = b"hello"; let val = b"world"; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 604b38d2eb42..0b2453e46fb0 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -3,11 +3,12 @@ use crate::shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; use bincode::{Error, Options}; +use bitflags::bitflags; use enum_as_inner::EnumAsInner; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; use std::{ - fmt::{self, Debug}, + fmt::Debug, io::{Cursor, Read, Write}, sync::{ atomic::{AtomicBool, Ordering}, @@ -15,15 +16,28 @@ use std::{ }, }; +mod branch; +mod extension; +mod leaf; +mod partial_path; + +pub use branch::{BranchNode, MAX_CHILDREN, SIZE as BRANCH_NODE_SIZE}; +pub use extension::{ExtNode, SIZE as EXTENSION_NODE_SIZE}; +pub use leaf::LeafNode; +pub use partial_path::PartialPath; + use crate::merkle::to_nibble_array; use crate::nibbles::Nibbles; -use super::{from_nibbles, PartialPath, TrieHash, TRIE_HASH_LEN}; - -pub const NBRANCH: usize = 16; +use super::{from_nibbles, TrieHash, TRIE_HASH_LEN}; -const EXT_NODE_SIZE: usize = 2; -const BRANCH_NODE_SIZE: usize = 17; +bitflags! { + // should only ever be the size of a nibble + struct Flags: u8 { + const TERMINAL = 0b0010; + const ODD_LEN = 0b0001; + } +} #[derive(Debug, PartialEq, Eq, Clone)] pub struct Data(pub(super) Vec); @@ -35,8 +49,14 @@ impl std::ops::Deref for Data { } } +impl From> for Data { + fn from(v: Vec) -> Self { + Self(v) + } +} + #[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Encoded { +enum Encoded { Raw(T), Data(T), } @@ -57,269 +77,6 @@ impl> Encoded { } } -#[derive(PartialEq, Eq, Clone)] -pub struct BranchNode { - pub(super) children: [Option; NBRANCH], - pub(super) value: Option, - pub(super) children_encoded: [Option>; NBRANCH], -} - -impl Debug for BranchNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Branch")?; - for (i, c) in self.children.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {c:?})")?; - } - } - for (i, c) in self.children_encoded.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {:?})", c)?; - } - } - write!( - f, - " v={}]", - match &self.value { - Some(v) => hex::encode(&**v), - None => "nil".to_string(), - } - ) - } -} - -impl BranchNode { - pub(super) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { - let mut has_chd = false; - let mut only_chd = None; - for (i, c) in self.children.iter().enumerate() { - if c.is_some() { - has_chd = true; - if only_chd.is_some() { - only_chd = None; - break; - } - only_chd = (*c).map(|e| (e, i as u8)) - } - } - (only_chd, has_chd) - } - - pub fn decode(buf: &[u8]) -> Result { - let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; - - // we've already validated the size, that's why we can safely unwrap - let data = items.pop().unwrap().decode()?; - // Extract the value of the branch node and set to None if it's an empty Vec - let value = Some(data).filter(|data| !data.is_empty()); - - // encode all children. - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); - - // we popped the last element, so their should only be NBRANCH items left - for (i, chd) in items.into_iter().enumerate() { - let data = chd.decode()?; - chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); - } - - Ok(BranchNode::new([None; NBRANCH], value, chd_encoded)) - } - - fn encode>(&self, store: &S) -> Vec { - let mut list = <[Encoded>; NBRANCH + 1]>::default(); - - for (i, c) in self.children.iter().enumerate() { - match c { - Some(c) => { - let mut c_ref = store.get_item(*c).unwrap(); - - if c_ref.is_encoded_longer_than_hash_len::(store) { - list[i] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*c_ref.get_root_hash::(store))[..]) - .unwrap(), - ); - - // See struct docs for ordering requirements - if c_ref.lazy_dirty.load(Ordering::Relaxed) { - c_ref.write(|_| {}).unwrap(); - c_ref.lazy_dirty.store(false, Ordering::Relaxed) - } - } else { - let child_encoded = &c_ref.get_encoded::(store); - list[i] = Encoded::Raw(child_encoded.to_vec()); - } - } - None => { - // Check if there is already a calculated encoded value for the child, which - // can happen when manually constructing a trie from proof. - if let Some(v) = &self.children_encoded[i] { - if v.len() == TRIE_HASH_LEN { - list[i] = - Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); - } else { - list[i] = Encoded::Raw(v.clone()); - } - } - } - }; - } - - if let Some(Data(val)) = &self.value { - list[NBRANCH] = Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); - } - - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .unwrap() - } - - pub fn new( - chd: [Option; NBRANCH], - value: Option>, - chd_encoded: [Option>; NBRANCH], - ) -> Self { - BranchNode { - children: chd, - value: value.map(Data), - children_encoded: chd_encoded, - } - } - - pub fn value(&self) -> &Option { - &self.value - } - - pub fn chd(&self) -> &[Option; NBRANCH] { - &self.children - } - - pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { - &mut self.children - } - - pub fn chd_encode(&self) -> &[Option>; NBRANCH] { - &self.children_encoded - } - - pub fn chd_encoded_mut(&mut self) -> &mut [Option>; NBRANCH] { - &mut self.children_encoded - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct LeafNode(pub(super) PartialPath, pub(super) Data); - -impl Debug for LeafNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1)) - } -} - -impl LeafNode { - fn encode(&self) -> Vec { - bincode::DefaultOptions::new() - .serialize( - [ - Encoded::Raw(from_nibbles(&self.0.encode(true)).collect()), - Encoded::Raw(self.1.to_vec()), - ] - .as_slice(), - ) - .unwrap() - } - - pub fn new(path: Vec, data: Vec) -> Self { - LeafNode(PartialPath(path), Data(data)) - } - - pub fn path(&self) -> &PartialPath { - &self.0 - } - - pub fn data(&self) -> &Data { - &self.1 - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct ExtNode { - pub(crate) path: PartialPath, - pub(crate) child: DiskAddress, - pub(crate) child_encoded: Option>, -} - -impl Debug for ExtNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let Self { - path, - child, - child_encoded, - } = self; - write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) - } -} - -impl ExtNode { - fn encode>(&self, store: &S) -> Vec { - let mut list = <[Encoded>; 2]>::default(); - list[0] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) - .unwrap(), - ); - - if !self.child.is_null() { - let mut r = store.get_item(self.child).unwrap(); - - if r.is_encoded_longer_than_hash_len(store) { - list[1] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*r.get_root_hash(store))[..]) - .unwrap(), - ); - - if r.lazy_dirty.load(Ordering::Relaxed) { - r.write(|_| {}).unwrap(); - r.lazy_dirty.store(false, Ordering::Relaxed); - } - } else { - list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); - } - } else { - // Check if there is already a caclucated encoded value for the child, which - // can happen when manually constructing a trie from proof. - if let Some(v) = &self.child_encoded { - if v.len() == TRIE_HASH_LEN { - list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); - } else { - list[1] = Encoded::Raw(v.clone()); - } - } - } - - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .unwrap() - } - - pub fn chd(&self) -> DiskAddress { - self.child - } - - pub fn chd_encoded(&self) -> Option<&[u8]> { - self.child_encoded.as_deref() - } - - pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.child - } - - pub fn chd_encoded_mut(&mut self) -> &mut Option> { - &mut self.child_encoded - } -} - #[derive(Debug)] pub struct Node { pub(super) root_hash: OnceLock, @@ -333,6 +90,7 @@ pub struct Node { } impl Eq for Node {} + impl PartialEq for Node { fn eq(&self, other: &Self) -> bool { let Node { @@ -349,6 +107,7 @@ impl PartialEq for Node { && *inner == other.inner } } + impl Clone for Node { fn clone(&self) -> Self { Self { @@ -374,7 +133,7 @@ impl NodeType { let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; match items.len() { - EXT_NODE_SIZE => { + EXTENSION_NODE_SIZE => { let mut items = items.into_iter(); let decoded_key: Vec = items.next().unwrap().decode()?; @@ -383,6 +142,7 @@ impl NodeType { let (cur_key_path, term) = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); + let cur_key = cur_key_path.into_inner(); let data: Vec = items.next().unwrap().decode()?; @@ -396,6 +156,7 @@ impl NodeType { })) } } + // TODO: add path BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?)), size => Err(Box::new(bincode::ErrorKind::Custom(format!( "invalid size: {size}" @@ -411,14 +172,12 @@ impl NodeType { } } - pub fn path_mut(&mut self) -> Option<&mut PartialPath> { - let path = match self { - NodeType::Branch(_) => return None, - NodeType::Leaf(node) => &mut node.0, + pub fn path_mut(&mut self) -> &mut PartialPath { + match self { + NodeType::Branch(_u) => todo!(), + NodeType::Leaf(node) => &mut node.path, NodeType::Extension(node) => &mut node.path, - }; - - path.into() + } } } @@ -449,7 +208,8 @@ impl Node { is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner: NodeType::Branch(BranchNode { - children: [Some(DiskAddress::null()); NBRANCH], + // path: vec![].into(), + children: [Some(DiskAddress::null()); MAX_CHILDREN], value: Some(Data(Vec::new())), children_encoded: Default::default(), }), @@ -488,7 +248,7 @@ impl Node { } pub fn leaf(path: PartialPath, data: Data) -> Self { - Self::from(NodeType::Leaf(LeafNode(path, data))) + Self::from(NodeType::Leaf(LeafNode { path, data })) } pub fn inner(&self) -> &NodeType { @@ -527,31 +287,37 @@ impl Node { impl Storable for Node { fn deserialize(addr: usize, mem: &T) -> Result { - const META_SIZE: usize = 32 + 1 + 1; + const META_SIZE: usize = TRIE_HASH_LEN + 1 + 1; + let meta_raw = mem.get_view(addr, META_SIZE as u64) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: META_SIZE as u64, })?; - let attrs = meta_raw.as_deref()[32]; + + let attrs = meta_raw.as_deref()[TRIE_HASH_LEN]; + let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None } else { Some(TrieHash( - meta_raw.as_deref()[0..32] + meta_raw.as_deref()[..TRIE_HASH_LEN] .try_into() .expect("invalid slice"), )) }; + let is_encoded_longer_than_hash_len = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { None } else { Some(attrs & Node::LONG_BIT != 0) }; + match meta_raw.as_deref()[33] { Self::BRANCH_NODE => { - let branch_header_size = NBRANCH as u64 * 8 + 4; + // TODO: add path + let branch_header_size = MAX_CHILDREN as u64 * 8 + 4; let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( ShaleError::InvalidCacheView { offset: addr + META_SIZE, @@ -559,7 +325,7 @@ impl Storable for Node { }, )?; let mut cur = Cursor::new(node_raw.as_deref()); - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let mut buff = [0; 8]; for chd in chd.iter_mut() { cur.read_exact(&mut buff)?; @@ -583,7 +349,7 @@ impl Storable for Node { .as_deref(), )) }; - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); + let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); let offset = if raw_len == u32::MAX as u64 { addr + META_SIZE + branch_header_size as usize } else { @@ -619,12 +385,14 @@ impl Storable for Node { root_hash, is_encoded_longer_than_hash_len, NodeType::Branch(BranchNode { + // path: vec![].into(), children: chd, value, children_encoded: chd_encoded, }), )) } + Self::EXT_NODE => { let ext_header_size = 1 + 8; let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( @@ -663,9 +431,12 @@ impl Storable for Node { offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, size: 1, })?; + cur = Cursor::new(encoded_len_raw.as_deref()); cur.read_exact(&mut buff)?; + let encoded_len = buff[0] as u64; + let encoded: Option> = if encoded_len != 0 { let emcoded_raw = mem .get_view( @@ -686,7 +457,7 @@ impl Storable for Node { None }; - Ok(Self::new_from_hash( + let node = Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, NodeType::Extension(ExtNode { @@ -694,8 +465,11 @@ impl Storable for Node { child: DiskAddress::from(ptr as usize), child_encoded: encoded, }), - )) + ); + + Ok(node) } + Self::LEAF_NODE => { let leaf_header_size = 1 + 4; let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( @@ -704,11 +478,15 @@ impl Storable for Node { size: leaf_header_size, }, )?; + let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 4]; cur.read_exact(&mut buff[..1])?; + let path_len = buff[0] as u64; + cur.read_exact(&mut buff)?; + let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view( @@ -728,12 +506,15 @@ impl Storable for Node { .collect(); let (path, _) = PartialPath::decode(&nibbles); - let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); - Ok(Self::new_from_hash( + let data = Data(remainder.as_deref()[path_len as usize..].to_vec()); + + let node = Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, - NodeType::Leaf(LeafNode(path, value)), - )) + NodeType::Leaf(LeafNode { path, data }), + ); + + Ok(node) } _ => Err(ShaleError::InvalidNodeType), } @@ -744,6 +525,7 @@ impl Storable for Node { + 1 + match &self.inner { NodeType::Branch(n) => { + // TODO: add path let mut encoded_len = 0; for emcoded in n.children_encoded.iter() { encoded_len += match emcoded { @@ -751,7 +533,7 @@ impl Storable for Node { None => 1, } } - NBRANCH as u64 * 8 + MAX_CHILDREN as u64 * 8 + 4 + match &n.value { Some(val) => val.len() as u64, @@ -761,13 +543,13 @@ impl Storable for Node { } NodeType::Extension(n) => { 1 + 8 - + n.path.dehydrated_len() + + n.path.serialized_len() + match n.chd_encoded() { Some(v) => 1 + v.len() as u64, None => 1, } } - NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, + NodeType::Leaf(n) => 1 + 4 + n.path.serialized_len() + n.data.len() as u64, } } @@ -793,6 +575,7 @@ impl Storable for Node { match &self.inner { NodeType::Branch(n) => { + // TODO: add path cur.write_all(&[Self::BRANCH_NODE]).unwrap(); for c in n.children.iter() { cur.write_all(&match c { @@ -836,11 +619,11 @@ impl Storable for Node { } NodeType::Leaf(n) => { cur.write_all(&[Self::LEAF_NODE])?; - let path: Vec = from_nibbles(&n.0.encode(true)).collect(); + let path: Vec = from_nibbles(&n.path.encode(true)).collect(); cur.write_all(&[path.len() as u8])?; - cur.write_all(&(n.1.len() as u32).to_le_bytes())?; + cur.write_all(&(n.data.len() as u32).to_le_bytes())?; cur.write_all(&path)?; - cur.write_all(&n.1).map_err(ShaleError::Io) + cur.write_all(&n.data).map_err(ShaleError::Io) } } } @@ -863,8 +646,8 @@ pub(super) mod tests { value: Option>, repeated_encoded_child: Option>, ) -> Node { - let children: [Option; NBRANCH] = from_fn(|i| { - if i < NBRANCH / 2 { + let children: [Option; MAX_CHILDREN] = from_fn(|i| { + if i < MAX_CHILDREN / 2 { DiskAddress::from(repeated_disk_address).into() } else { None @@ -874,7 +657,7 @@ pub(super) mod tests { let children_encoded = repeated_encoded_child .map(|child| { from_fn(|i| { - if i < NBRANCH / 2 { + if i < MAX_CHILDREN / 2 { child.clone().into() } else { None @@ -884,6 +667,7 @@ pub(super) mod tests { .unwrap_or_default(); Node::branch(BranchNode { + // path: vec![].into(), children, value: value.map(Data), children_encoded, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs new file mode 100644 index 000000000000..9a6469511b82 --- /dev/null +++ b/firewood/src/merkle/node/branch.rs @@ -0,0 +1,184 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::{Data, Encoded, Node}; +use crate::{ + merkle::{PartialPath, TRIE_HASH_LEN}, + shale::DiskAddress, + shale::ShaleStore, +}; +use bincode::{Error, Options}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + sync::atomic::Ordering, +}; + +pub const MAX_CHILDREN: usize = 16; +pub const SIZE: usize = MAX_CHILDREN + 1; + +#[derive(PartialEq, Eq, Clone)] +pub struct BranchNode { + // pub(crate) path: PartialPath, + pub(crate) children: [Option; MAX_CHILDREN], + pub(crate) value: Option, + pub(crate) children_encoded: [Option>; MAX_CHILDREN], +} + +impl Debug for BranchNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "[Branch")?; + // write!(f, " path={:?}", self.path)?; + + for (i, c) in self.children.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {c:?})")?; + } + } + + for (i, c) in self.children_encoded.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {:?})", c)?; + } + } + + write!( + f, + " v={}]", + match &self.value { + Some(v) => hex::encode(&**v), + None => "nil".to_string(), + } + ) + } +} + +impl BranchNode { + pub fn new( + _path: PartialPath, + chd: [Option; MAX_CHILDREN], + value: Option>, + chd_encoded: [Option>; MAX_CHILDREN], + ) -> Self { + BranchNode { + // path, + children: chd, + value: value.map(Data), + children_encoded: chd_encoded, + } + } + + pub fn value(&self) -> &Option { + &self.value + } + + pub fn chd(&self) -> &[Option; MAX_CHILDREN] { + &self.children + } + + pub fn chd_mut(&mut self) -> &mut [Option; MAX_CHILDREN] { + &mut self.children + } + + pub fn chd_encode(&self) -> &[Option>; MAX_CHILDREN] { + &self.children_encoded + } + + pub fn chd_encoded_mut(&mut self) -> &mut [Option>; MAX_CHILDREN] { + &mut self.children_encoded + } + + pub(crate) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { + let mut has_chd = false; + let mut only_chd = None; + for (i, c) in self.children.iter().enumerate() { + if c.is_some() { + has_chd = true; + if only_chd.is_some() { + only_chd = None; + break; + } + only_chd = (*c).map(|e| (e, i as u8)) + } + } + (only_chd, has_chd) + } + + pub(super) fn decode(buf: &[u8]) -> Result { + let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + + // we've already validated the size, that's why we can safely unwrap + let data = items.pop().unwrap().decode()?; + // Extract the value of the branch node and set to None if it's an empty Vec + let value = Some(data).filter(|data| !data.is_empty()); + + // encode all children. + let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); + + // we popped the last element, so their should only be NBRANCH items left + for (i, chd) in items.into_iter().enumerate() { + let data = chd.decode()?; + chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); + } + + // TODO: add path + let path = Vec::new().into(); + + Ok(BranchNode::new( + path, + [None; MAX_CHILDREN], + value, + chd_encoded, + )) + } + + pub(super) fn encode>(&self, store: &S) -> Vec { + // TODO: add path to encoded node + let mut list = <[Encoded>; MAX_CHILDREN + 1]>::default(); + + for (i, c) in self.children.iter().enumerate() { + match c { + Some(c) => { + let mut c_ref = store.get_item(*c).unwrap(); + + if c_ref.is_encoded_longer_than_hash_len::(store) { + list[i] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*c_ref.get_root_hash::(store))[..]) + .unwrap(), + ); + + // See struct docs for ordering requirements + if c_ref.lazy_dirty.load(Ordering::Relaxed) { + c_ref.write(|_| {}).unwrap(); + c_ref.lazy_dirty.store(false, Ordering::Relaxed) + } + } else { + let child_encoded = &c_ref.get_encoded::(store); + list[i] = Encoded::Raw(child_encoded.to_vec()); + } + } + None => { + // Check if there is already a calculated encoded value for the child, which + // can happen when manually constructing a trie from proof. + if let Some(v) = &self.children_encoded[i] { + if v.len() == TRIE_HASH_LEN { + list[i] = + Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); + } else { + list[i] = Encoded::Raw(v.clone()); + } + } + } + }; + } + + if let Some(Data(val)) = &self.value { + list[MAX_CHILDREN] = + Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); + } + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() + } +} diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs new file mode 100644 index 000000000000..28f9549cd243 --- /dev/null +++ b/firewood/src/merkle/node/extension.rs @@ -0,0 +1,94 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use bincode::Options; + +use super::{Encoded, Node}; +use crate::{ + merkle::{from_nibbles, PartialPath, TRIE_HASH_LEN}, + shale::{DiskAddress, ShaleStore}, +}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + sync::atomic::Ordering, +}; + +pub const SIZE: usize = 2; + +#[derive(PartialEq, Eq, Clone)] +pub struct ExtNode { + pub(crate) path: PartialPath, + pub(crate) child: DiskAddress, + pub(crate) child_encoded: Option>, +} + +impl Debug for ExtNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + let Self { + path, + child, + child_encoded, + } = self; + write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) + } +} + +impl ExtNode { + pub(super) fn encode>(&self, store: &S) -> Vec { + let mut list = <[Encoded>; 2]>::default(); + list[0] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) + .unwrap(), + ); + + if !self.child.is_null() { + let mut r = store.get_item(self.child).unwrap(); + + if r.is_encoded_longer_than_hash_len(store) { + list[1] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*r.get_root_hash(store))[..]) + .unwrap(), + ); + + if r.lazy_dirty.load(Ordering::Relaxed) { + r.write(|_| {}).unwrap(); + r.lazy_dirty.store(false, Ordering::Relaxed); + } + } else { + list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); + } + } else { + // Check if there is already a caclucated encoded value for the child, which + // can happen when manually constructing a trie from proof. + if let Some(v) = &self.child_encoded { + if v.len() == TRIE_HASH_LEN { + list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); + } else { + list[1] = Encoded::Raw(v.clone()); + } + } + } + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() + } + + pub fn chd(&self) -> DiskAddress { + self.child + } + + pub fn chd_encoded(&self) -> Option<&[u8]> { + self.child_encoded.as_deref() + } + + pub fn chd_mut(&mut self) -> &mut DiskAddress { + &mut self.child + } + + pub fn chd_encoded_mut(&mut self) -> &mut Option> { + &mut self.child_encoded + } +} diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs new file mode 100644 index 000000000000..0418ab888bcb --- /dev/null +++ b/firewood/src/merkle/node/leaf.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt::{Debug, Error as FmtError, Formatter}; + +use bincode::Options; + +use super::{Data, Encoded}; +use crate::merkle::{from_nibbles, PartialPath}; + +#[derive(PartialEq, Eq, Clone)] +pub struct LeafNode { + pub(crate) path: PartialPath, + pub(crate) data: Data, +} + +impl Debug for LeafNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "[Leaf {:?} {}]", self.path, hex::encode(&*self.data)) + } +} + +impl LeafNode { + pub fn new, D: Into>(path: P, data: D) -> Self { + Self { + path: path.into(), + data: data.into(), + } + } + + pub fn path(&self) -> &PartialPath { + &self.path + } + + pub fn data(&self) -> &Data { + &self.data + } + + pub(super) fn encode(&self) -> Vec { + bincode::DefaultOptions::new() + .serialize( + [ + Encoded::Raw(from_nibbles(&self.path.encode(true)).collect()), + Encoded::Raw(self.data.to_vec()), + ] + .as_slice(), + ) + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // these tests will fail if the encoding mechanism changes and should be updated accordingly + #[test_case(0b10 << 4, vec![0x12, 0x34], vec![1, 2, 3, 4]; "even length")] + // first nibble is part of the prefix + #[test_case((0b11 << 4) + 2, vec![0x34], vec![2, 3, 4]; "odd length")] + fn encode_regression_test(prefix: u8, path: Vec, nibbles: Vec) { + let data = vec![5, 6, 7, 8]; + + let serialized_path = [vec![prefix], path.clone()].concat(); + // 0 represents Encoded::Raw + let serialized_path = [vec![0, serialized_path.len() as u8], serialized_path].concat(); + let serialized_data = [vec![0, data.len() as u8], data.clone()].concat(); + + let serialized = [vec![2], serialized_path, serialized_data].concat(); + + let node = LeafNode::new(nibbles, data.clone()); + + assert_eq!(node.encode(), serialized); + } +} diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs new file mode 100644 index 000000000000..d213dd05d826 --- /dev/null +++ b/firewood/src/merkle/node/partial_path.rs @@ -0,0 +1,129 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::Flags; +use crate::nibbles::NibblesIterator; +use std::{ + fmt::{self, Debug}, + iter::once, +}; + +// TODO: use smallvec +/// PartialPath keeps a list of nibbles to represent a path on the Trie. +#[derive(PartialEq, Eq, Clone)] +pub struct PartialPath(pub Vec); + +impl Debug for PartialPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for nib in self.0.iter() { + write!(f, "{:x}", *nib & 0xf)?; + } + Ok(()) + } +} + +impl std::ops::Deref for PartialPath { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl From> for PartialPath { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl PartialPath { + pub fn into_inner(self) -> Vec { + self.0 + } + + pub(super) fn encode(&self, is_terminal: bool) -> Vec { + let mut flags = Flags::empty(); + + if is_terminal { + flags.insert(Flags::TERMINAL); + } + + let has_odd_len = self.0.len() & 1 == 1; + + let extra_byte = if has_odd_len { + flags.insert(Flags::ODD_LEN); + + None + } else { + Some(0) + }; + + once(flags.bits()) + .chain(extra_byte) + .chain(self.0.iter().copied()) + .collect() + } + + // TODO: remove all non `Nibbles` usages and delete this function. + // I also think `PartialPath` could probably borrow instead of own data. + // + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn decode(raw: &[u8]) -> (Self, bool) { + let mut raw = raw.iter().copied(); + let flags = Flags::from_bits_retain(raw.next().unwrap_or_default()); + + if !flags.contains(Flags::ODD_LEN) { + let _ = raw.next(); + } + + (Self(raw.collect()), flags.contains(Flags::TERMINAL)) + } + + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { + let flags = Flags::from_bits_retain(nibbles.next().unwrap_or_default()); + + if !flags.contains(Flags::ODD_LEN) { + let _ = nibbles.next(); + } + + (Self(nibbles.collect()), flags.contains(Flags::TERMINAL)) + } + + pub(super) fn serialized_len(&self) -> u64 { + let len = self.0.len(); + + // if len is even the prefix takes an extra byte + // otherwise is combined with the first nibble + let len = if len & 1 == 1 { + (len + 1) / 2 + } else { + len / 2 + 1 + }; + + len as u64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ops::Deref; + use test_case::test_case; + + #[test_case(&[1, 2, 3, 4], true)] + #[test_case(&[1, 2, 3], false)] + #[test_case(&[0, 1, 2], false)] + #[test_case(&[1, 2], true)] + #[test_case(&[1], true)] + fn test_encoding(steps: &[u8], term: bool) { + let path = PartialPath(steps.to_vec()); + let encoded = path.encode(term); + + assert_eq!(encoded.len(), path.serialized_len() as usize * 2); + + let (decoded, decoded_term) = PartialPath::decode(&encoded); + + assert_eq!(&decoded.deref(), &steps); + assert_eq!(decoded_term, term); + } +} diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs deleted file mode 100644 index eb2bd94d8101..000000000000 --- a/firewood/src/merkle/partial_path.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::nibbles::NibblesIterator; -use std::fmt::{self, Debug}; - -/// PartialPath keeps a list of nibbles to represent a path on the Trie. -#[derive(PartialEq, Eq, Clone)] -pub struct PartialPath(pub Vec); - -impl Debug for PartialPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - for nib in self.0.iter() { - write!(f, "{:x}", *nib & 0xf)?; - } - Ok(()) - } -} - -impl std::ops::Deref for PartialPath { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl PartialPath { - pub fn into_inner(self) -> Vec { - self.0 - } - - pub(super) fn encode(&self, term: bool) -> Vec { - let odd_len = (self.0.len() & 1) as u8; - let flags = if term { 2 } else { 0 } + odd_len; - let mut res = if odd_len == 1 { - vec![flags] - } else { - vec![flags, 0x0] - }; - res.extend(&self.0); - res - } - - // TODO: remove all non `Nibbles` usages and delete this function. - // I also think `PartialPath` could probably borrow instead of own data. - // - /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn decode(raw: &[u8]) -> (Self, bool) { - let prefix = raw[0]; - let is_odd = (prefix & 1) as usize; - let decoded = raw.iter().skip(1).skip(1 - is_odd).copied().collect(); - - (Self(decoded), prefix > 1) - } - - /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { - let prefix = nibbles.next().unwrap(); - let is_odd = (prefix & 1) as usize; - let decoded = nibbles.skip(1 - is_odd).collect(); - - (Self(decoded), prefix > 1) - } - - pub(super) fn dehydrated_len(&self) -> u64 { - let len = self.0.len() as u64; - if len & 1 == 1 { - (len + 1) >> 1 - } else { - (len >> 1) + 1 - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - #[test_case(&[1, 2, 3, 4], true)] - #[test_case(&[1, 2, 3], false)] - #[test_case(&[0, 1, 2], false)] - #[test_case(&[1, 2], true)] - #[test_case(&[1], true)] - fn test_encoding(steps: &[u8], term: bool) { - let path = PartialPath(steps.to_vec()).encode(term); - let (decoded, decoded_term) = PartialPath::decode(&path); - assert_eq!(&decoded.0, &steps); - assert_eq!(decoded_term, term); - } -} diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 7d1d549d61ff..1a2d69a92fb7 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use disk_address::DiskAddress; +pub(crate) use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; use std::fmt::{self, Debug, Formatter}; From c08dfcb628dfc14ccf9e8c8a7bf5e45113b87164 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 14:26:23 -0500 Subject: [PATCH 0364/1053] Box the branch-node (#367) --- firewood/src/merkle/node.rs | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 0b2453e46fb0..bcf165f4e870 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -121,9 +121,8 @@ impl Clone for Node { } #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] -#[allow(clippy::large_enum_variant)] pub enum NodeType { - Branch(BranchNode), + Branch(Box), Leaf(LeafNode), Extension(ExtNode), } @@ -157,7 +156,7 @@ impl NodeType { } } // TODO: add path - BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?)), + BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), size => Err(Box::new(bincode::ErrorKind::Custom(format!( "invalid size: {size}" )))), @@ -207,12 +206,15 @@ impl Node { root_hash: OnceLock::new(), is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), - inner: NodeType::Branch(BranchNode { - // path: vec![].into(), - children: [Some(DiskAddress::null()); MAX_CHILDREN], - value: Some(Data(Vec::new())), - children_encoded: Default::default(), - }), + inner: NodeType::Branch( + BranchNode { + // path: vec![].into(), + children: [Some(DiskAddress::null()); MAX_CHILDREN], + value: Some(Data(Vec::new())), + children_encoded: Default::default(), + } + .into(), + ), lazy_dirty: AtomicBool::new(false), } .serialized_len() @@ -243,8 +245,8 @@ impl Node { self.root_hash = OnceLock::new(); } - pub fn branch(node: BranchNode) -> Self { - Self::from(NodeType::Branch(node)) + pub fn branch>>(node: B) -> Self { + Self::from(NodeType::Branch(node.into())) } pub fn leaf(path: PartialPath, data: Data) -> Self { @@ -384,12 +386,15 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, - NodeType::Branch(BranchNode { - // path: vec![].into(), - children: chd, - value, - children_encoded: chd_encoded, - }), + NodeType::Branch( + BranchNode { + // path: vec![].into(), + children: chd, + value, + children_encoded: chd_encoded, + } + .into(), + ), )) } From fe13e4c831c15455b4bb40de530cda31543c00ae Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 16:40:24 -0500 Subject: [PATCH 0365/1053] Rename Node Constructors (#368) --- firewood/src/merkle.rs | 30 ++++++++++++++++++------------ firewood/src/merkle/node.rs | 10 +++++----- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 10fb47869f7b..c0e28f319d4c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -84,7 +84,7 @@ impl + Send + Sync> Merkle { pub fn init_root(&self) -> Result { self.store .put_item( - Node::branch(BranchNode { + Node::from_branch(BranchNode { // path: vec![].into(), children: [None; MAX_CHILDREN], value: None, @@ -198,7 +198,10 @@ impl + Send + Sync> Merkle { node.rehash(); })?; - let new_node = Node::leaf(PartialPath(new_node_path.to_vec()), Data(val)); + let new_node = Node::from_leaf(LeafNode::new( + PartialPath(new_node_path.to_vec()), + Data(val), + )); let leaf_address = self.put_node(new_node)?.as_ptr(); let mut chd = [None; MAX_CHILDREN]; @@ -216,7 +219,7 @@ impl + Send + Sync> Merkle { chd[n_path[idx] as usize] = Some(address); - let new_branch = Node::branch(BranchNode { + let new_branch = Node::from_branch(BranchNode { // path: PartialPath(matching_path[..idx].to_vec()), children: chd, value: None, @@ -323,10 +326,10 @@ impl + Send + Sync> Merkle { } // insert path is greather than the path of the leaf (Ordering::Greater, Some(n_value)) => { - let leaf = Node::leaf( + let leaf = Node::from_leaf(LeafNode::new( PartialPath(insert_path[n_path.len() + 1..].to_vec()), Data(val), - ); + )); let leaf_address = self.put_node(leaf)?.as_ptr(); @@ -347,7 +350,7 @@ impl + Send + Sync> Merkle { children[idx] = leaf_address.into(); let branch_address = self - .put_node(Node::branch(BranchNode { + .put_node(Node::from_branch(BranchNode { children, value, children_encoded: Default::default(), @@ -456,10 +459,10 @@ impl + Send + Sync> Merkle { // insert the leaf to the empty slot // create a new leaf let leaf_ptr = self - .put_node(Node::leaf( + .put_node(Node::from_leaf(LeafNode::new( PartialPath(key_nibbles.collect()), Data(val), - ))? + )))? .as_ptr(); // set the current child to point to this leaf node.write(|u| { @@ -575,7 +578,7 @@ impl + Send + Sync> Merkle { chd[idx as usize] = Some(c_ptr); let branch = self - .put_node(Node::branch(BranchNode { + .put_node(Node::from_branch(BranchNode { // path: vec![].into(), children: chd, value: Some(Data(val)), @@ -620,7 +623,7 @@ impl + Send + Sync> Merkle { // from: [p: Branch] -> [b (v)]x -> [Leaf]x // to: [p: Branch] -> [Leaf (v)] let leaf = self - .put_node(Node::leaf(PartialPath(Vec::new()), val))? + .put_node(Node::from_leaf(LeafNode::new(PartialPath(Vec::new()), val)))? .as_ptr(); p_ref .write(|p| { @@ -633,7 +636,10 @@ impl + Send + Sync> Merkle { // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x // to: P -> [Leaf (v)] let leaf = self - .put_node(Node::leaf(PartialPath(n.path.clone().into_inner()), val))? + .put_node(Node::from_leaf(LeafNode::new( + PartialPath(n.path.clone().into_inner()), + val, + )))? .as_ptr(); deleted.push(p_ptr); set_parent(leaf, parents); @@ -1696,7 +1702,7 @@ mod tests { children_encoded[0] = Some(child); } - Node::branch(BranchNode { + Node::from_branch(BranchNode { // path: vec![].into(), children, value, diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index bcf165f4e870..7821aa84da16 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -245,12 +245,12 @@ impl Node { self.root_hash = OnceLock::new(); } - pub fn branch>>(node: B) -> Self { + pub fn from_branch>>(node: B) -> Self { Self::from(NodeType::Branch(node.into())) } - pub fn leaf(path: PartialPath, data: Data) -> Self { - Self::from(NodeType::Leaf(LeafNode { path, data })) + pub fn from_leaf(leaf: LeafNode) -> Self { + Self::from(NodeType::Leaf(leaf)) } pub fn inner(&self) -> &NodeType { @@ -643,7 +643,7 @@ pub(super) mod tests { use test_case::test_case; pub fn leaf(path: Vec, data: Vec) -> Node { - Node::leaf(PartialPath(path), Data(data)) + Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) } pub fn branch( @@ -671,7 +671,7 @@ pub(super) mod tests { }) .unwrap_or_default(); - Node::branch(BranchNode { + Node::from_branch(BranchNode { // path: vec![].into(), children, value: value.map(Data), From 48902864dba6f589f6e6f25b89a5534176530dfc Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 17:41:49 -0500 Subject: [PATCH 0366/1053] Centralize Node Atomics (#369) --- firewood/src/merkle.rs | 11 +-- firewood/src/merkle/node.rs | 108 ++++++++++++++------------ firewood/src/merkle/node/branch.rs | 9 +-- firewood/src/merkle/node/extension.rs | 9 +-- firewood/src/merkle/node/leaf.rs | 3 + 5 files changed, 71 insertions(+), 69 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index c0e28f319d4c..a3d06b791c35 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -5,13 +5,8 @@ use crate::v2::api; use crate::{nibbles::Nibbles, v2::api::Proof}; use futures::{Stream, StreamExt, TryStreamExt}; use sha3::Digest; -use std::future::ready; use std::{ - cmp::Ordering, - collections::HashMap, - io::Write, - iter::once, - sync::{atomic::Ordering::Relaxed, OnceLock}, + cmp::Ordering, collections::HashMap, future::ready, io::Write, iter::once, sync::OnceLock, task::Poll, }; use thiserror::Error; @@ -122,9 +117,9 @@ impl + Send + Sync> Merkle { Ok(if let Some(root) = root { let mut node = self.get_node(root)?; let res = node.get_root_hash::(self.store.as_ref()).clone(); - if node.lazy_dirty.load(Relaxed) { + if node.is_dirty() { node.write(|_| {}).unwrap(); - node.lazy_dirty.store(false, Relaxed); + node.set_dirty(false); } res } else { diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 7821aa84da16..5ce90ac6b987 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -77,49 +77,6 @@ impl> Encoded { } } -#[derive(Debug)] -pub struct Node { - pub(super) root_hash: OnceLock, - is_encoded_longer_than_hash_len: OnceLock, - encoded: OnceLock>, - // lazy_dirty is an atomicbool, but only writers ever set it - // Therefore, we can always use Relaxed ordering. It's atomic - // just to ensure Sync + Send. - pub(super) lazy_dirty: AtomicBool, - pub(super) inner: NodeType, -} - -impl Eq for Node {} - -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - let Node { - root_hash, - is_encoded_longer_than_hash_len, - encoded, - lazy_dirty, - inner, - } = self; - *root_hash == other.root_hash - && *is_encoded_longer_than_hash_len == other.is_encoded_longer_than_hash_len - && *encoded == other.encoded - && (*lazy_dirty).load(Ordering::Relaxed) == other.lazy_dirty.load(Ordering::Relaxed) - && *inner == other.inner - } -} - -impl Clone for Node { - fn clone(&self) -> Self { - Self { - root_hash: self.root_hash.clone(), - is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), - encoded: self.encoded.clone(), - lazy_dirty: AtomicBool::new(self.lazy_dirty.load(Ordering::Relaxed)), - inner: self.inner.clone(), - } - } -} - #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] pub enum NodeType { Branch(Box), @@ -180,6 +137,51 @@ impl NodeType { } } +#[derive(Debug)] +pub struct Node { + pub(super) root_hash: OnceLock, + is_encoded_longer_than_hash_len: OnceLock, + encoded: OnceLock>, + // lazy_dirty is an atomicbool, but only writers ever set it + // Therefore, we can always use Relaxed ordering. It's atomic + // just to ensure Sync + Send. + lazy_dirty: AtomicBool, + pub(super) inner: NodeType, +} + +impl Eq for Node {} + +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + let is_dirty = self.is_dirty(); + + let Node { + root_hash, + is_encoded_longer_than_hash_len, + encoded, + lazy_dirty: _, + inner, + } = self; + *root_hash == other.root_hash + && *is_encoded_longer_than_hash_len == other.is_encoded_longer_than_hash_len + && *encoded == other.encoded + && is_dirty == other.is_dirty() + && *inner == other.inner + } +} + +impl Clone for Node { + fn clone(&self) -> Self { + Self { + root_hash: self.root_hash.clone(), + is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), + encoded: self.encoded.clone(), + lazy_dirty: AtomicBool::new(self.is_dirty()), + inner: self.inner.clone(), + } + } +} + impl From for Node { fn from(inner: NodeType) -> Self { let mut s = Self { @@ -199,6 +201,11 @@ impl Node { const EXT_NODE: u8 = 0x1; const LEAF_NODE: u8 = 0x2; + const ROOT_HASH_VALID_BIT: u8 = 1 << 0; + // TODO: why are these different? + const IS_ENCODED_BIG_VALID: u8 = 1 << 1; + const LONG_BIT: u8 = 1 << 2; + pub(super) fn max_branch_node_size() -> u64 { let max_size: OnceLock = OnceLock::new(); *max_size.get_or_init(|| { @@ -227,14 +234,14 @@ impl Node { pub(super) fn get_root_hash>(&self, store: &S) -> &TrieHash { self.root_hash.get_or_init(|| { - self.lazy_dirty.store(true, Ordering::Relaxed); + self.set_dirty(true); TrieHash(Keccak256::digest(self.get_encoded::(store)).into()) }) } fn is_encoded_longer_than_hash_len>(&self, store: &S) -> bool { *self.is_encoded_longer_than_hash_len.get_or_init(|| { - self.lazy_dirty.store(true, Ordering::Relaxed); + self.set_dirty(true); self.get_encoded(store).len() >= TRIE_HASH_LEN }) } @@ -281,10 +288,13 @@ impl Node { } } - const ROOT_HASH_VALID_BIT: u8 = 1 << 0; - // TODO: why are these different? - const IS_ENCODED_BIG_VALID: u8 = 1 << 1; - const LONG_BIT: u8 = 1 << 2; + pub(super) fn is_dirty(&self) -> bool { + self.lazy_dirty.load(Ordering::Relaxed) + } + + pub(super) fn set_dirty(&self, is_dirty: bool) { + self.lazy_dirty.store(is_dirty, Ordering::Relaxed) + } } impl Storable for Node { diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 9a6469511b82..fc48a7981035 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -8,10 +8,7 @@ use crate::{ shale::ShaleStore, }; use bincode::{Error, Options}; -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - sync::atomic::Ordering, -}; +use std::fmt::{Debug, Error as FmtError, Formatter}; pub const MAX_CHILDREN: usize = 16; pub const SIZE: usize = MAX_CHILDREN + 1; @@ -148,9 +145,9 @@ impl BranchNode { ); // See struct docs for ordering requirements - if c_ref.lazy_dirty.load(Ordering::Relaxed) { + if c_ref.is_dirty() { c_ref.write(|_| {}).unwrap(); - c_ref.lazy_dirty.store(false, Ordering::Relaxed) + c_ref.set_dirty(false); } } else { let child_encoded = &c_ref.get_encoded::(store); diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index 28f9549cd243..8387f884dc40 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -8,10 +8,7 @@ use crate::{ merkle::{from_nibbles, PartialPath, TRIE_HASH_LEN}, shale::{DiskAddress, ShaleStore}, }; -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - sync::atomic::Ordering, -}; +use std::fmt::{Debug, Error as FmtError, Formatter}; pub const SIZE: usize = 2; @@ -52,9 +49,9 @@ impl ExtNode { .unwrap(), ); - if r.lazy_dirty.load(Ordering::Relaxed) { + if r.is_dirty() { r.write(|_| {}).unwrap(); - r.lazy_dirty.store(false, Ordering::Relaxed); + r.set_dirty(false); } } else { list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 0418ab888bcb..58b55d31e4c1 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -8,6 +8,9 @@ use bincode::Options; use super::{Data, Encoded}; use crate::merkle::{from_nibbles, PartialPath}; +// will be needed when extension node is removed +// pub const SIZE: usize = 2; + #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { pub(crate) path: PartialPath, From ec8a2d195e4c837458182c4fc3e7cfecbf043706 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 18:23:02 -0500 Subject: [PATCH 0367/1053] Use leaf-node size (#370) --- firewood/src/merkle/node.rs | 6 +++--- firewood/src/merkle/node/extension.rs | 2 -- firewood/src/merkle/node/leaf.rs | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 5ce90ac6b987..43fbeafa168e 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -22,8 +22,8 @@ mod leaf; mod partial_path; pub use branch::{BranchNode, MAX_CHILDREN, SIZE as BRANCH_NODE_SIZE}; -pub use extension::{ExtNode, SIZE as EXTENSION_NODE_SIZE}; -pub use leaf::LeafNode; +pub use extension::ExtNode; +pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; pub use partial_path::PartialPath; use crate::merkle::to_nibble_array; @@ -89,7 +89,7 @@ impl NodeType { let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; match items.len() { - EXTENSION_NODE_SIZE => { + LEAF_NODE_SIZE => { let mut items = items.into_iter(); let decoded_key: Vec = items.next().unwrap().decode()?; diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index 8387f884dc40..fcbcc4e7437e 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -10,8 +10,6 @@ use crate::{ }; use std::fmt::{Debug, Error as FmtError, Formatter}; -pub const SIZE: usize = 2; - #[derive(PartialEq, Eq, Clone)] pub struct ExtNode { pub(crate) path: PartialPath, diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 58b55d31e4c1..475f62a007a7 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -8,8 +8,7 @@ use bincode::Options; use super::{Data, Encoded}; use crate::merkle::{from_nibbles, PartialPath}; -// will be needed when extension node is removed -// pub const SIZE: usize = 2; +pub const SIZE: usize = 2; #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { From 0267229b6f7a09ca283981c2ed4a96728f44fbb2 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 19:15:04 -0500 Subject: [PATCH 0368/1053] Use bitflags for leaf-node attributes (#371) --- firewood/src/merkle/node.rs | 78 +++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 43fbeafa168e..a7fe8bf344b3 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -196,15 +196,23 @@ impl From for Node { } } +bitflags! { + struct NodeAttributes: u8 { + const ROOT_HASH_VALID = 0b001; + const IS_ENCODED_BIG_VALID = 0b010; + const LONG = 0b100; + } +} + impl Node { - const BRANCH_NODE: u8 = 0x0; - const EXT_NODE: u8 = 0x1; - const LEAF_NODE: u8 = 0x2; + const BRANCH_NODE: u8 = 0; + const LEAF_NODE: u8 = 1; + const EXT_NODE: u8 = 2; - const ROOT_HASH_VALID_BIT: u8 = 1 << 0; + const ROOT_HASH_VALID_BIT: u8 = 0b01; // TODO: why are these different? - const IS_ENCODED_BIG_VALID: u8 = 1 << 1; - const LONG_BIT: u8 = 1 << 2; + const IS_ENCODED_BIG_VALID: u8 = 0b10; + const LONG_BIT: u8 = 0b100; pub(super) fn max_branch_node_size() -> u64 { let max_size: OnceLock = OnceLock::new(); @@ -297,6 +305,36 @@ impl Node { } } +mod type_id { + use crate::shale::ShaleError; + + const BRANCH: u8 = 0; + const LEAF: u8 = 1; + const EXTENSION: u8 = 2; + + #[repr(u8)] + pub enum NodeTypeId { + Branch = BRANCH, + Leaf = LEAF, + Extension = EXTENSION, + } + + impl TryFrom for NodeTypeId { + type Error = ShaleError; + + fn try_from(value: u8) -> Result { + match value { + BRANCH => Ok(Self::Branch), + LEAF => Ok(Self::Leaf), + EXTENSION => Ok(Self::Extension), + _ => Err(ShaleError::InvalidNodeType), + } + } + } +} + +use type_id::NodeTypeId; + impl Storable for Node { fn deserialize(addr: usize, mem: &T) -> Result { const META_SIZE: usize = TRIE_HASH_LEN + 1 + 1; @@ -308,26 +346,27 @@ impl Storable for Node { size: META_SIZE as u64, })?; - let attrs = meta_raw.as_deref()[TRIE_HASH_LEN]; + let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); - let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { - None - } else { + let root_hash = if attrs.contains(NodeAttributes::ROOT_HASH_VALID) { Some(TrieHash( meta_raw.as_deref()[..TRIE_HASH_LEN] .try_into() .expect("invalid slice"), )) - }; - - let is_encoded_longer_than_hash_len = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { - None } else { - Some(attrs & Node::LONG_BIT != 0) + None }; - match meta_raw.as_deref()[33] { - Self::BRANCH_NODE => { + let is_encoded_longer_than_hash_len = + if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { + Some(attrs.contains(NodeAttributes::LONG)) + } else { + None + }; + + match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { + NodeTypeId::Branch => { // TODO: add path let branch_header_size = MAX_CHILDREN as u64 * 8 + 4; let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( @@ -408,7 +447,7 @@ impl Storable for Node { )) } - Self::EXT_NODE => { + NodeTypeId::Extension => { let ext_header_size = 1 + 8; let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( ShaleError::InvalidCacheView { @@ -485,7 +524,7 @@ impl Storable for Node { Ok(node) } - Self::LEAF_NODE => { + NodeTypeId::Leaf => { let leaf_header_size = 1 + 4; let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( ShaleError::InvalidCacheView { @@ -531,7 +570,6 @@ impl Storable for Node { Ok(node) } - _ => Err(ShaleError::InvalidNodeType), } } From b2b4da89947b98ca7a4b4ddb7453847a01a22c06 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 19:23:22 -0500 Subject: [PATCH 0369/1053] Use more strict types in node-(de)serialization (#372) --- firewood/src/merkle/node.rs | 161 ++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 63 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index a7fe8bf344b3..b0d5c4540e60 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -10,6 +10,7 @@ use sha3::{Digest, Keccak256}; use std::{ fmt::Debug, io::{Cursor, Read, Write}, + mem::size_of, sync::{ atomic::{AtomicBool, Ordering}, OnceLock, @@ -205,15 +206,6 @@ bitflags! { } impl Node { - const BRANCH_NODE: u8 = 0; - const LEAF_NODE: u8 = 1; - const EXT_NODE: u8 = 2; - - const ROOT_HASH_VALID_BIT: u8 = 0b01; - // TODO: why are these different? - const IS_ENCODED_BIG_VALID: u8 = 0b10; - const LONG_BIT: u8 = 0b100; - pub(super) fn max_branch_node_size() -> u64 { let max_size: OnceLock = OnceLock::new(); *max_size.get_or_init(|| { @@ -305,6 +297,17 @@ impl Node { } } +#[repr(C)] +struct Meta { + root_hash: [u8; TRIE_HASH_LEN], + attrs: NodeAttributes, + is_encoded_longer_than_hash_len: Option, +} + +impl Meta { + const SIZE: usize = size_of::(); +} + mod type_id { use crate::shale::ShaleError; @@ -337,13 +340,11 @@ use type_id::NodeTypeId; impl Storable for Node { fn deserialize(addr: usize, mem: &T) -> Result { - const META_SIZE: usize = TRIE_HASH_LEN + 1 + 1; - let meta_raw = - mem.get_view(addr, META_SIZE as u64) + mem.get_view(addr, Meta::SIZE as u64) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: META_SIZE as u64, + size: Meta::SIZE as u64, })?; let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); @@ -368,16 +369,19 @@ impl Storable for Node { match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { NodeTypeId::Branch => { // TODO: add path + // TODO: figure out what this size is? let branch_header_size = MAX_CHILDREN as u64 * 8 + 4; - let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( + let node_raw = mem.get_view(addr + Meta::SIZE, branch_header_size).ok_or( ShaleError::InvalidCacheView { - offset: addr + META_SIZE, + offset: addr + Meta::SIZE, size: branch_header_size, }, )?; + let mut cur = Cursor::new(node_raw.as_deref()); let mut chd = [None; MAX_CHILDREN]; let mut buff = [0; 8]; + for chd in chd.iter_mut() { cur.read_exact(&mut buff)?; let addr = usize::from_le_bytes(buff); @@ -385,28 +389,35 @@ impl Storable for Node { *chd = Some(DiskAddress::from(addr)) } } + cur.read_exact(&mut buff[..4])?; + let raw_len = u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")) as u64; + let value = if raw_len == u32::MAX as u64 { None } else { Some(Data( - mem.get_view(addr + META_SIZE + branch_header_size as usize, raw_len) + mem.get_view(addr + Meta::SIZE + branch_header_size as usize, raw_len) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + branch_header_size as usize, + offset: addr + Meta::SIZE + branch_header_size as usize, size: raw_len, })? .as_deref(), )) }; + let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); + let offset = if raw_len == u32::MAX as u64 { - addr + META_SIZE + branch_header_size as usize + addr + Meta::SIZE + branch_header_size as usize } else { - addr + META_SIZE + branch_header_size as usize + raw_len as usize + addr + Meta::SIZE + branch_header_size as usize + raw_len as usize }; + let mut cur_encoded_len = 0; + for chd_encoded in chd_encoded.iter_mut() { let mut buff = [0_u8; 1]; let len_raw = mem.get_view(offset + cur_encoded_len, 1).ok_or( @@ -415,10 +426,13 @@ impl Storable for Node { size: 1, }, )?; + cur = Cursor::new(len_raw.as_deref()); cur.read_exact(&mut buff)?; + let len = buff[0] as u64; cur_encoded_len += 1; + if len != 0 { let encoded_raw = mem.get_view(offset + cur_encoded_len, len).ok_or( ShaleError::InvalidCacheView { @@ -426,46 +440,53 @@ impl Storable for Node { size: len, }, )?; + let encoded: Vec = encoded_raw.as_deref()[0..].to_vec(); *chd_encoded = Some(encoded); cur_encoded_len += len as usize } } + let inner = NodeType::Branch( + BranchNode { + // path: vec![].into(), + children: chd, + value, + children_encoded: chd_encoded, + } + .into(), + ); + Ok(Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, - NodeType::Branch( - BranchNode { - // path: vec![].into(), - children: chd, - value, - children_encoded: chd_encoded, - } - .into(), - ), + inner, )) } NodeTypeId::Extension => { let ext_header_size = 1 + 8; - let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( + + let node_raw = mem.get_view(addr + Meta::SIZE, ext_header_size).ok_or( ShaleError::InvalidCacheView { - offset: addr + META_SIZE, + offset: addr + Meta::SIZE, size: ext_header_size, }, )?; + let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 8]; + cur.read_exact(&mut buff[..1])?; let path_len = buff[0] as u64; + cur.read_exact(&mut buff)?; let ptr = u64::from_le_bytes(buff); let nibbles: Vec = mem - .get_view(addr + META_SIZE + ext_header_size as usize, path_len) + .get_view(addr + Meta::SIZE + ext_header_size as usize, path_len) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size as usize, + offset: addr + Meta::SIZE + ext_header_size as usize, size: path_len, })? .as_deref() @@ -476,13 +497,14 @@ impl Storable for Node { let (path, _) = PartialPath::decode(&nibbles); let mut buff = [0_u8; 1]; + let encoded_len_raw = mem .get_view( - addr + META_SIZE + ext_header_size as usize + path_len as usize, + addr + Meta::SIZE + ext_header_size as usize + path_len as usize, 1, ) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, + offset: addr + Meta::SIZE + ext_header_size as usize + path_len as usize, size: 1, })?; @@ -494,12 +516,12 @@ impl Storable for Node { let encoded: Option> = if encoded_len != 0 { let emcoded_raw = mem .get_view( - addr + META_SIZE + ext_header_size as usize + path_len as usize + 1, + addr + Meta::SIZE + ext_header_size as usize + path_len as usize + 1, encoded_len, ) .ok_or(ShaleError::InvalidCacheView { offset: addr - + META_SIZE + + Meta::SIZE + ext_header_size as usize + path_len as usize + 1, @@ -511,24 +533,22 @@ impl Storable for Node { None }; - let node = Self::new_from_hash( - root_hash, - is_encoded_longer_than_hash_len, - NodeType::Extension(ExtNode { - path, - child: DiskAddress::from(ptr as usize), - child_encoded: encoded, - }), - ); + let inner = NodeType::Extension(ExtNode { + path, + child: DiskAddress::from(ptr as usize), + child_encoded: encoded, + }); + + let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); Ok(node) } NodeTypeId::Leaf => { let leaf_header_size = 1 + 4; - let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( + let node_raw = mem.get_view(addr + Meta::SIZE, leaf_header_size).ok_or( ShaleError::InvalidCacheView { - offset: addr + META_SIZE, + offset: addr + Meta::SIZE, size: leaf_header_size, }, )?; @@ -544,11 +564,11 @@ impl Storable for Node { let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view( - addr + META_SIZE + leaf_header_size as usize, + addr + Meta::SIZE + leaf_header_size as usize, path_len + data_len, ) .ok_or(ShaleError::InvalidCacheView { - offset: addr + META_SIZE + leaf_header_size as usize, + offset: addr + Meta::SIZE + leaf_header_size as usize, size: path_len + data_len, })?; @@ -574,8 +594,7 @@ impl Storable for Node { } fn serialized_len(&self) -> u64 { - 32 + 1 - + 1 + Meta::SIZE as u64 + match &self.inner { NodeType::Branch(n) => { // TODO: add path @@ -609,33 +628,39 @@ impl Storable for Node { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - let mut attrs = 0; - attrs |= match self.root_hash.get() { + let mut attrs = match self.root_hash.get() { Some(h) => { cur.write_all(&h.0)?; - Node::ROOT_HASH_VALID_BIT + NodeAttributes::ROOT_HASH_VALID } None => { cur.write_all(&[0; 32])?; - 0 + NodeAttributes::empty() } }; - attrs |= match self.is_encoded_longer_than_hash_len.get() { - Some(b) => (if *b { Node::LONG_BIT } else { 0 } | Node::IS_ENCODED_BIG_VALID), - None => 0, - }; - cur.write_all(&[attrs]).unwrap(); + + if let Some(&b) = self.is_encoded_longer_than_hash_len.get() { + attrs.insert(if b { + NodeAttributes::LONG + } else { + NodeAttributes::IS_ENCODED_BIG_VALID + }); + } + + cur.write_all(&[attrs.bits()]).unwrap(); match &self.inner { NodeType::Branch(n) => { // TODO: add path - cur.write_all(&[Self::BRANCH_NODE]).unwrap(); + cur.write_all(&[type_id::NodeTypeId::Branch as u8]).unwrap(); + for c in n.children.iter() { cur.write_all(&match c { Some(p) => p.to_le_bytes(), None => 0u64.to_le_bytes(), })?; } + match &n.value { Some(val) => { cur.write_all(&(val.len() as u32).to_le_bytes())?; @@ -645,6 +670,7 @@ impl Storable for Node { cur.write_all(&u32::MAX.to_le_bytes())?; } } + // Since child encoding will only be unset after initialization (only used for range proof), // it is fine to encode its value adjacent to other fields. Same for extention node. for encoded in n.children_encoded.iter() { @@ -656,23 +682,32 @@ impl Storable for Node { None => cur.write_all(&0u8.to_le_bytes())?, } } + Ok(()) } + NodeType::Extension(n) => { - cur.write_all(&[Self::EXT_NODE])?; + cur.write_all(&[type_id::NodeTypeId::Extension as u8])?; + let path: Vec = from_nibbles(&n.path.encode(false)).collect(); + cur.write_all(&[path.len() as u8])?; cur.write_all(&n.child.to_le_bytes())?; cur.write_all(&path)?; + if let Some(encoded) = n.chd_encoded() { cur.write_all(&[encoded.len() as u8])?; cur.write_all(encoded)?; } + Ok(()) } + NodeType::Leaf(n) => { - cur.write_all(&[Self::LEAF_NODE])?; + cur.write_all(&[type_id::NodeTypeId::Leaf as u8])?; + let path: Vec = from_nibbles(&n.path.encode(true)).collect(); + cur.write_all(&[path.len() as u8])?; cur.write_all(&(n.data.len() as u32).to_le_bytes())?; cur.write_all(&path)?; From 67fc20b6c8e0d06724656f5a800ddc6448bcf779 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 27 Nov 2023 14:33:31 -0800 Subject: [PATCH 0370/1053] Growth ring const function checks (#383) --- growth-ring/Cargo.toml | 3 +++ growth-ring/src/wal.rs | 6 +++--- growth-ring/tests/common/mod.rs | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 1ba541bcf5e7..80d58437f861 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -31,3 +31,6 @@ tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } name = "growthring" path = "src/lib.rs" crate-type = ["dylib", "rlib", "staticlib"] + +[lints.clippy] +missing_const_for_fn = "warn" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 622477c3742f..71ee1ed30885 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -86,7 +86,7 @@ fn sort_fids(file_nbit: u64, mut fids: Vec) -> Vec<(u8, u64)> { } } -fn counter_lt(a: u32, b: u32) -> bool { +const fn counter_lt(a: u32, b: u32) -> bool { if u32::abs_diff(a, b) > u32::MAX / 2 { b < a } else { @@ -125,10 +125,10 @@ impl WalRingId { counter: 0, } } - pub fn get_start(&self) -> WalPos { + pub const fn get_start(&self) -> WalPos { self.start } - pub fn get_end(&self) -> WalPos { + pub const fn get_end(&self) -> WalPos { self.end } } diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 16c110c24362..3c5955a4f082 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -20,7 +20,7 @@ pub trait FailGen { struct FileContentEmul(RefCell>); impl FileContentEmul { - pub fn new() -> Self { + pub const fn new() -> Self { FileContentEmul(RefCell::new(Vec::new())) } } @@ -187,7 +187,7 @@ pub struct SingleFailGen { } impl SingleFailGen { - pub fn new(fail_point: usize) -> Self { + pub const fn new(fail_point: usize) -> Self { SingleFailGen { cnt: std::cell::Cell::new(0), fail_point, @@ -214,7 +214,7 @@ impl FailGen for ZeroFailGen { pub struct CountFailGen(std::cell::Cell); impl CountFailGen { - pub fn new() -> Self { + pub const fn new() -> Self { CountFailGen(std::cell::Cell::new(0)) } pub fn get_count(&self) -> usize { @@ -234,7 +234,7 @@ impl FailGen for CountFailGen { pub struct PaintStrokes(Vec<(u32, u32, u32)>); impl PaintStrokes { - pub fn new() -> Self { + pub const fn new() -> Self { PaintStrokes(Vec::new()) } From 19f81ae397653a14febafdd44cd219611a899840 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 07:53:21 -0800 Subject: [PATCH 0371/1053] Growth ring file handling improvements (#381) --- growth-ring/Cargo.toml | 1 + growth-ring/src/lib.rs | 6 ++-- growth-ring/src/wal.rs | 59 ++++++++++++++++++++++++++------- growth-ring/tests/common/mod.rs | 5 +-- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 80d58437f861..7a3ae56fe268 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -26,6 +26,7 @@ hex = "0.4.3" rand = "0.8.5" indexmap = "2.0.0" tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } +test-case = "3.1.0" [lib] name = "growthring" diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 3a6e58d4c0f6..94debb137d78 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -48,8 +48,6 @@ //! // After each recovery, the /tmp/walfiles is empty. //! ``` -#[macro_use] -extern crate scan_fmt; pub mod wal; pub mod walerror; @@ -175,7 +173,7 @@ pub fn oflags() -> OFlag { #[async_trait(?Send)] impl WalStore for WalStoreImpl { - type FileNameIter = std::vec::IntoIter; + type FileNameIter = std::vec::IntoIter; async fn open_file(&self, filename: &str, _touch: bool) -> Result { let path = self.root_dir.join(filename); @@ -193,7 +191,7 @@ impl WalStore for WalStoreImpl { fn enumerate_files(&self) -> Result { let mut filenames = Vec::new(); for path in fs::read_dir(&self.root_dir)?.filter_map(|entry| entry.ok()) { - filenames.push(path.file_name().into_string().unwrap()); + filenames.push(path.path()); } Ok(filenames.into_iter()) } diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 71ee1ed30885..738a79a82a59 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -9,11 +9,15 @@ use futures::{ Future, }; -use std::cell::{RefCell, UnsafeCell}; use std::convert::{TryFrom, TryInto}; use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::pin::Pin; +use std::{ + cell::{RefCell, UnsafeCell}, + ffi::OsStr, + path::{Path, PathBuf}, +}; use std::{ collections::{hash_map, BinaryHeap, HashMap, VecDeque}, marker::PhantomData, @@ -21,8 +25,6 @@ use std::{ pub use crate::walerror::WalError; -const FILENAME_FMT: &str = r"[0-9a-f]+\.log"; - enum WalRingType { #[allow(dead_code)] Null = 0x0, @@ -59,8 +61,23 @@ type WalFileId = u64; pub type WalBytes = Box<[u8]>; pub type WalPos = u64; -fn get_fid(fname: &str) -> WalFileId { - scan_fmt!(fname, "{x}.log", [hex WalFileId]).unwrap() +// convert XXXXXX.log into number from the XXXXXX (in hex) +fn get_fid(fname: &Path) -> Result { + let wal_err: WalError = WalError::Other("not a log file".to_string()); + + if fname.extension() != Some(OsStr::new("log")) { + return Err(wal_err); + } + + u64::from_str_radix( + fname + .file_stem() + .unwrap_or(OsStr::new("")) + .to_str() + .unwrap_or(""), + 16, + ) + .map_err(|_| wal_err) } fn get_fname(fid: WalFileId) -> String { @@ -199,7 +216,7 @@ pub trait WalFile { #[async_trait(?Send)] pub trait WalStore { - type FileNameIter: Iterator; + type FileNameIter: Iterator; /// Open a file given the filename, create the file if not exists when `touch` is `true`. async fn open_file(&self, filename: &str, touch: bool) -> Result; @@ -729,7 +746,6 @@ impl> WalWriter { nrecords: usize, recover_policy: &RecoverPolicy, ) -> Result, WalError> { - let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); let file_pool = &self.file_pool; let file_nbit = file_pool.file_nbit; let block_size = 1 << file_pool.block_nbit; @@ -740,8 +756,7 @@ impl> WalWriter { file_pool .store .enumerate_files()? - .filter(|f| filename_fmt.is_match(f)) - .map(|s| get_fid(&s)) + .flat_map(|s| get_fid(&s)) .collect(), ); @@ -1179,7 +1194,6 @@ impl WalLoader { let msize = std::mem::size_of::(); assert!(self.file_nbit > self.block_nbit); assert!(msize < 1 << self.block_nbit); - let filename_fmt = regex::Regex::new(FILENAME_FMT).unwrap(); let mut file_pool = WalFilePool::new(store, self.file_nbit, self.block_nbit, self.cache_size).await?; let logfiles = sort_fids( @@ -1187,8 +1201,7 @@ impl WalLoader { file_pool .store .enumerate_files()? - .filter(|f| filename_fmt.is_match(f)) - .map(|s| get_fid(&s)) + .flat_map(|s| get_fid(&s)) .collect(), ); @@ -1301,3 +1314,25 @@ impl WalLoader { } pub const CRC32: crc::Crc = crc::Crc::::new(&crc::CRC_32_CKSUM); + +#[cfg(test)] +mod test { + use super::*; + use test_case::test_case; + + #[test_case("foo", Err("not a log file"); "no log extension")] + #[test_case("foo.log", Err("not a log file"); "invalid digit found in string")] + #[test_case("0000001.log", Ok(1); "happy path")] + #[test_case("1.log", Ok(1); "no leading zeroes")] + + fn test_get_fid(input: &str, expected: Result) { + let got = get_fid(Path::new(input)); + match expected { + Err(has) => { + let err = got.err().unwrap().to_string(); + assert!(err.contains(has), "{:?}", err) + } + Ok(val) => assert_eq!(got.unwrap(), val), + } + } +} diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 3c5955a4f082..d461c8216290 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -11,6 +11,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::collections::{hash_map, HashMap}; use std::convert::TryInto; +use std::path::PathBuf; use std::rc::Rc; pub trait FailGen { @@ -126,7 +127,7 @@ impl<'a, G> WalStore> for WalStoreEmul<'a, G> where G: 'static + FailGen, { - type FileNameIter = std::vec::IntoIter; + type FileNameIter = std::vec::IntoIter; async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError> { if self.fgen.next_fail() { @@ -175,7 +176,7 @@ where } let mut logfiles = Vec::new(); for (fname, _) in self.state.borrow().files.iter() { - logfiles.push(fname.clone()) + logfiles.push(fname.into()) } Ok(logfiles.into_iter()) } From 06c465e601fa4fad39ccbb42d40fa83c88876769 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 09:12:26 -0800 Subject: [PATCH 0372/1053] Growth ring unwrap cleanups (#382) --- growth-ring/examples/demo1.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs index f7dfcd720228..474ef25a6289 100644 --- a/growth-ring/examples/demo1.rs +++ b/growth-ring/examples/demo1.rs @@ -9,26 +9,29 @@ use growthring::{ }; use rand::{seq::SliceRandom, Rng, SeedableRng}; -fn test(records: Vec, wal: &mut WalWriter) -> Vec { +fn test( + records: Vec, + wal: &mut WalWriter, +) -> Result, ()> { let mut res = Vec::new(); for r in wal.grow(records).into_iter() { - let ring_id = futures::executor::block_on(r).unwrap().1; + let ring_id = futures::executor::block_on(r)?.1; println!("got ring id: {:?}", ring_id); res.push(ring_id); } - res + Ok(res) } fn recover(payload: WalBytes, ringid: WalRingId) -> Result<(), WalError> { println!( "recover(payload={}, ringid={:?}", - std::str::from_utf8(&payload).unwrap(), + std::str::from_utf8(&payload).map_err(|e| WalError::Other(e.to_string()))?, ringid ); Ok(()) } -fn main() { +fn main() -> Result<(), WalError> { let wal_dir = "./wal_demo1"; let mut rng = rand::rngs::StdRng::seed_from_u64(0); let mut loader = WalLoader::new(); @@ -37,7 +40,7 @@ fn main() { let store = WalStoreImpl::new(wal_dir, true).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { - test( + let _ = test( ["hi", "hello", "lol"] .iter() .map(|s| s.to_string()) @@ -46,7 +49,7 @@ fn main() { ); } for _ in 0..3 { - test( + let _ = test( vec!["a".repeat(10), "b".repeat(100), "c".repeat(1000)], &mut wal, ); @@ -55,7 +58,7 @@ fn main() { let store = WalStoreImpl::new(wal_dir, false).unwrap(); let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); for _ in 0..3 { - test( + let _ = test( vec![ "a".repeat(10), "b".repeat(100), @@ -81,7 +84,10 @@ fn main() { } records.push(rec) } - for id in test(records, &mut wal).iter() { + for id in test(records, &mut wal) + .map_err(|_| WalError::Other("test failed".to_string()))? + .iter() + { ids.push(*id) } } @@ -100,4 +106,6 @@ fn main() { assert_eq!(std::str::from_utf8(&rec).unwrap(), &ans); println!("{}", std::str::from_utf8(&rec).unwrap()); } + + Ok(()) } From 408a46880884a3206985c3308d572e956ba35814 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 09:24:22 -0800 Subject: [PATCH 0373/1053] Dead code removal (#385) --- growth-ring/src/lib.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 94debb137d78..2df79968d513 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -52,7 +52,6 @@ pub mod wal; pub mod walerror; use async_trait::async_trait; -use nix::fcntl::OFlag; use std::fs; use std::io::SeekFrom; use std::path::{Path, PathBuf}; @@ -161,16 +160,6 @@ impl WalStoreImpl { } } -/// Return OS specific open flags for opening files -/// TODO: Switch to a rust idiomatic directory scanning approach -/// TODO: This shouldn't need to escape growth-ring (no pub) -pub fn oflags() -> OFlag { - #[cfg(target_os = "linux")] - return OFlag::O_DIRECTORY | OFlag::O_PATH; - #[cfg(not(target_os = "linux"))] - return OFlag::O_DIRECTORY; -} - #[async_trait(?Send)] impl WalStore for WalStoreImpl { type FileNameIter = std::vec::IntoIter; From 72921d03b6d8460b8567c6c0bcd6da5f88f16644 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 09:41:37 -0800 Subject: [PATCH 0374/1053] Unwrap warning addition (#384) --- growth-ring/Cargo.toml | 1 + growth-ring/examples/demo1.rs | 21 ++++++++++----------- growth-ring/src/lib.rs | 23 +++++++++++++++++++++++ growth-ring/src/wal.rs | 8 ++++++++ growth-ring/tests/common/mod.rs | 11 +++++++++++ 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 7a3ae56fe268..603c85caca37 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -34,4 +34,5 @@ path = "src/lib.rs" crate-type = ["dylib", "rlib", "staticlib"] [lints.clippy] +unwrap_used = "warn" missing_const_for_fn = "warn" diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs index 474ef25a6289..0c4616f9c7f4 100644 --- a/growth-ring/examples/demo1.rs +++ b/growth-ring/examples/demo1.rs @@ -37,8 +37,8 @@ fn main() -> Result<(), WalError> { let mut loader = WalLoader::new(); loader.file_nbit(9).block_nbit(8); - let store = WalStoreImpl::new(wal_dir, true).unwrap(); - let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); + let store = WalStoreImpl::new(wal_dir, true)?; + let mut wal = block_on(loader.load(store, recover, 0))?; for _ in 0..3 { let _ = test( ["hi", "hello", "lol"] @@ -55,8 +55,8 @@ fn main() -> Result<(), WalError> { ); } - let store = WalStoreImpl::new(wal_dir, false).unwrap(); - let mut wal = block_on(loader.load(store, recover, 0)).unwrap(); + let store = WalStoreImpl::new(wal_dir, false)?; + let mut wal = block_on(loader.load(store, recover, 0))?; for _ in 0..3 { let _ = test( vec![ @@ -69,8 +69,8 @@ fn main() -> Result<(), WalError> { ); } - let store = WalStoreImpl::new(wal_dir, false).unwrap(); - let mut wal = block_on(loader.load(store, recover, 100)).unwrap(); + let store = WalStoreImpl::new(wal_dir, false)?; + let mut wal = block_on(loader.load(store, recover, 100))?; let mut history = std::collections::VecDeque::new(); for _ in 0..3 { let mut ids = Vec::new(); @@ -94,17 +94,16 @@ fn main() -> Result<(), WalError> { ids.shuffle(&mut rng); for e in ids.chunks(20) { println!("peel(20)"); - futures::executor::block_on(wal.peel(e, 100)).unwrap(); + futures::executor::block_on(wal.peel(e, 100))?; } } for (rec, ans) in - block_on(wal.read_recent_records(100, &growthring::wal::RecoverPolicy::Strict)) - .unwrap() + block_on(wal.read_recent_records(100, &growthring::wal::RecoverPolicy::Strict))? .into_iter() .zip(history.into_iter().rev()) { - assert_eq!(std::str::from_utf8(&rec).unwrap(), &ans); - println!("{}", std::str::from_utf8(&rec).unwrap()); + assert_eq!(&String::from_utf8_lossy(&rec), &ans); + println!("{}", String::from_utf8_lossy(&rec)); } Ok(()) diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs index 2df79968d513..b27844f69399 100644 --- a/growth-ring/src/lib.rs +++ b/growth-ring/src/lib.rs @@ -179,6 +179,7 @@ impl WalStore for WalStoreImpl { fn enumerate_files(&self) -> Result { let mut filenames = Vec::new(); + #[allow(clippy::unwrap_used)] for path in fs::read_dir(&self.root_dir)?.filter_map(|entry| entry.ok()) { filenames.push(path.path()); } @@ -198,6 +199,7 @@ mod tests { tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] let walfile = RawWalFile::open(walfile_path).await.unwrap(); let walfile_impl = WalFileImpl::from(walfile); @@ -211,9 +213,12 @@ mod tests { .chain(second_half.iter().copied()) .collect(); + #[allow(clippy::unwrap_used)] walfile_impl.write(0, data).await.unwrap(); + #[allow(clippy::unwrap_used)] walfile_impl.truncate(HALF_LENGTH).await.unwrap(); + #[allow(clippy::unwrap_used)] let result = walfile_impl.read(0, HALF_LENGTH).await.unwrap(); assert_eq!(result, Some(first_half.into())) @@ -227,17 +232,21 @@ mod tests { tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] let walfile = RawWalFile::open(walfile_path).await.unwrap(); let walfile_impl = WalFileImpl::from(walfile); + #[allow(clippy::unwrap_used)] walfile_impl .write(0, vec![1u8; LENGTH].into()) .await .unwrap(); + #[allow(clippy::unwrap_used)] walfile_impl.truncate(2 * LENGTH).await.unwrap(); + #[allow(clippy::unwrap_used)] let result = walfile_impl.read(LENGTH as u64, LENGTH).await.unwrap(); assert_eq!(result, Some(vec![0u8; LENGTH].into())) @@ -248,6 +257,7 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] RawWalFile::open(walfile_path).await.unwrap() }; @@ -255,8 +265,10 @@ mod tests { let data: Vec = (0..=u8::MAX).collect(); + #[allow(clippy::unwrap_used)] walfile_impl.write(0, data.clone().into()).await.unwrap(); + #[allow(clippy::unwrap_used)] let result = walfile_impl.read(0, data.len()).await.unwrap(); assert_eq!(result, Some(data.into())); @@ -267,17 +279,21 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] RawWalFile::open(walfile_path).await.unwrap() }; let walfile_impl = WalFileImpl::from(walfile); let data: Vec = (0..=u8::MAX).collect(); + #[allow(clippy::unwrap_used)] walfile_impl.write(0, data.clone().into()).await.unwrap(); let mid = data.len() / 2; let (start, end) = data.split_at(mid); + #[allow(clippy::unwrap_used)] let read_start_result = walfile_impl.read(0, mid).await.unwrap(); + #[allow(clippy::unwrap_used)] let read_end_result = walfile_impl.read(mid as u64, mid).await.unwrap(); assert_eq!(read_start_result, Some(start.into())); @@ -289,6 +305,7 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] RawWalFile::open(walfile_path).await.unwrap() }; @@ -296,8 +313,10 @@ mod tests { let data: Vec = (0..=u8::MAX).collect(); + #[allow(clippy::unwrap_used)] walfile_impl.write(0, data.clone().into()).await.unwrap(); + #[allow(clippy::unwrap_used)] let result = walfile_impl .read((data.len() / 2) as u64, data.len()) .await @@ -313,6 +332,7 @@ mod tests { let walfile = { let walfile_path = get_temp_walfile_path(file!(), line!()); tokio::fs::remove_file(&walfile_path).await.ok(); + #[allow(clippy::unwrap_used)] RawWalFile::open(walfile_path).await.unwrap() }; @@ -320,11 +340,13 @@ mod tests { let data: Vec = (0..=u8::MAX).collect(); + #[allow(clippy::unwrap_used)] walfile_impl .write(OFFSET, data.clone().into()) .await .unwrap(); + #[allow(clippy::unwrap_used)] let result = walfile_impl .read(0, data.len() + OFFSET as usize) .await @@ -338,6 +360,7 @@ mod tests { assert_eq!(result, Some(data.into())); } + #[allow(clippy::unwrap_used)] fn get_temp_walfile_path(file: &str, line: u32) -> PathBuf { let path = option_env!("CARGO_TARGET_TMPDIR") .map(PathBuf::from) diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 738a79a82a59..aa20681ea657 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -289,8 +289,10 @@ impl> WalFilePool { }) } + #[allow(clippy::unwrap_used)] async fn read_header(&self) -> Result { let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); + #[allow(clippy::unwrap_used)] let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); let header: Header = cast_slice(&bytes)[0]; Ok(header) @@ -450,6 +452,7 @@ impl> WalFilePool { let mut removes: Vec>>>> = Vec::new(); + #[allow(clippy::unwrap_used)] while state.pending_removal.len() > 1 { let (fid, counter) = state.pending_removal.front().unwrap(); @@ -707,6 +710,7 @@ impl> WalWriter { break; } + #[allow(clippy::unwrap_used)] let mut m = state.io_complete.pop().unwrap(); let block_remain = block_size - (m.end & (block_size - 1)); @@ -741,6 +745,7 @@ impl> WalWriter { self.file_pool.in_use_len() } + #[allow(clippy::unwrap_used)] pub async fn read_recent_records<'a>( &'a self, nrecords: usize, @@ -774,6 +779,7 @@ impl> WalWriter { for ring in rings.into_iter().rev() { let ring = ring.map_err(|_| WalError::Other("error mapping ring".to_string()))?; let (header, payload) = ring; + #[allow(clippy::unwrap_used)] let payload = payload.unwrap(); match header.rtype.try_into() { Ok(WalRingType::Full) => { @@ -853,6 +859,7 @@ pub struct WalLoader { } impl Default for WalLoader { + #[allow(clippy::unwrap_used)] fn default() -> Self { WalLoader { file_nbit: 22, // 4MB @@ -1316,6 +1323,7 @@ impl WalLoader { pub const CRC32: crc::Crc = crc::Crc::::new(&crc::CRC_32_CKSUM); #[cfg(test)] +#[allow(clippy::unwrap_used)] mod test { use super::*; use test_case::test_case; diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index d461c8216290..4f308b35527f 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -261,15 +261,19 @@ impl PaintStrokes { assert!(raw.len() & 3 == 0); let is = std::mem::size_of::(); let (len_raw, mut rest) = raw.split_at(is); + #[allow(clippy::unwrap_used)] let len = u32::from_le_bytes(len_raw.try_into().unwrap()); let mut res = Vec::new(); for _ in 0..len { let (s_raw, rest1) = rest.split_at(is); let (e_raw, rest2) = rest1.split_at(is); let (c_raw, rest3) = rest2.split_at(is); + #[allow(clippy::unwrap_used)] res.push(( u32::from_le_bytes(s_raw.try_into().unwrap()), + #[allow(clippy::unwrap_used)] u32::from_le_bytes(e_raw.try_into().unwrap()), + #[allow(clippy::unwrap_used)] u32::from_le_bytes(c_raw.try_into().unwrap()), )); rest = rest3 @@ -390,6 +394,7 @@ impl Canvas { return None; } let idx = rng.gen_range(0..self.queue.len()); + #[allow(clippy::unwrap_used)] let (pos, _) = self.queue.get_index(idx).unwrap(); let pos = *pos; Some((self.paint(pos), pos)) @@ -400,6 +405,7 @@ impl Canvas { self.waiting.clear(); } + #[allow(clippy::unwrap_used)] pub fn paint_all(&mut self) { for (pos, q) in self.queue.iter() { self.canvas[*pos as usize] = q.back().unwrap().0; @@ -411,8 +417,10 @@ impl Canvas { self.queue.is_empty() } + #[allow(clippy::unwrap_used)] pub fn paint(&mut self, pos: u32) -> Option { let q = self.queue.get_mut(&pos).unwrap(); + #[allow(clippy::unwrap_used)] let (c, rid) = q.pop_front().unwrap(); if q.is_empty() { self.queue.remove(&pos); @@ -574,6 +582,7 @@ impl PaintingSim { pub fn get_walloader(&self) -> WalLoader { let mut loader = WalLoader::new(); + #[allow(clippy::unwrap_used)] loader .file_nbit(self.file_nbit) .block_nbit(self.block_nbit) @@ -586,6 +595,7 @@ impl PaintingSim { let mut ops: Vec = Vec::new(); let mut ringid_map = HashMap::new(); let fgen = Rc::new(CountFailGen::new()); + #[allow(clippy::unwrap_used)] self.run( state, &mut canvas, @@ -612,6 +622,7 @@ impl PaintingSim { let mut last_idx = 0; let mut napplied = 0; canvas.clear_queued(); + #[allow(clippy::unwrap_used)] block_on(wal.load( WalStoreEmul::new(state, Rc::new(ZeroFailGen)), |payload, ringid| { From 52cc9a023c66f41eae268582ad66b53869af7183 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 12:51:12 -0800 Subject: [PATCH 0375/1053] Expose NibblesIterator::is_empty (#389) --- firewood/src/nibbles.rs | 37 ++++++++++++++++++++++++++++++++++++- firewood/src/proof.rs | 4 ++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 8de0865eb42d..0f6152b06eaf 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -120,7 +120,7 @@ impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_Z impl<'a, const LEADING_ZEROES: usize> NibblesIterator<'a, LEADING_ZEROES> { #[inline(always)] - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.head == self.tail } } @@ -215,4 +215,39 @@ mod test { assert!(nib_iter.eq(expected)); } + + #[test] + fn empty() { + let nib = Nibbles::<0>(&[]); + assert!(nib.is_empty()); + let it = nib.into_iter(); + assert!(it.is_empty()); + assert_eq!(it.size_hint().0, 0); + } + + #[test] + fn not_empty_because_of_leading_nibble() { + let nib = Nibbles::<1>(&[]); + assert!(!nib.is_empty()); + let mut it = nib.into_iter(); + assert!(!it.is_empty()); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next(), Some(0)); + assert!(it.is_empty()); + assert_eq!(it.size_hint(), (0, Some(0))); + } + #[test] + fn not_empty_because_of_data() { + let nib = Nibbles::<0>(&[1]); + assert!(!nib.is_empty()); + let mut it = nib.into_iter(); + assert!(!it.is_empty()); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(it.next(), Some(0)); + assert!(!it.is_empty()); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next(), Some(1)); + assert!(it.is_empty()); + assert_eq!(it.size_hint(), (0, Some(0))); + } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index fa50404a6a51..b5b571c696e2 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -126,7 +126,7 @@ impl + Send> Proof { cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(p) if key_nibbles.size_hint().0 == 0 => break p.encoded, + Some(p) if key_nibbles.is_empty() => break p.encoded, // The trie doesn't contain the key. Some(SubProof { hash: Some(hash), .. @@ -542,7 +542,7 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } - NodeType::Branch(_) if key_nibbles.size_hint().0 == 0 => Err(ProofError::NoSuchNode), + NodeType::Branch(_) if key_nibbles.is_empty() => Err(ProofError::NoSuchNode), NodeType::Branch(n) => { let index = key_nibbles.next().unwrap() as usize; // consume items returning the item at index From ae01a4a9940d9e729ea42c0bafc79f44fa33bb2f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 14:47:57 -0800 Subject: [PATCH 0376/1053] Range proof middles should contain all (#387) --- firewood/src/merkle.rs | 46 +++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index a3d06b791c35..eb53b8a3abf4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1220,39 +1220,43 @@ impl + Send + Sync> Merkle { // transpose the Option> to Result, E> // If this is an error, the ? operator will return it - let Some((key, _)) = first_result.transpose()? else { + let Some((first_key, first_data)) = first_result.transpose()? else { // nothing returned, either the trie is empty or the key wasn't found return Ok(None); }; let first_key_proof = self - .prove(key, root) + .prove(&first_key, root) .map_err(|e| api::Error::InternalError(Box::new(e)))?; let limit = limit.map(|old_limit| old_limit - 1); + let mut middle = vec![(first_key, first_data)]; + // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested - let mut middle = stream - .take(limit.unwrap_or(usize::MAX)) - .take_while(|kv_result| { - // no last key asked for, so keep going - let Some(last_key) = last_key.as_ref() else { - return ready(true); - }; + middle.extend( + stream + .take(limit.unwrap_or(usize::MAX)) + .take_while(|kv_result| { + // no last key asked for, so keep going + let Some(last_key) = last_key.as_ref() else { + return ready(true); + }; - // return the error if there was one - let Ok(kv) = kv_result else { - return ready(true); - }; + // return the error if there was one + let Ok(kv) = kv_result else { + return ready(true); + }; - // keep going if the key returned is less than the last key requested - ready(kv.0.as_slice() <= last_key.as_ref()) - }) - .try_collect::, Vec)>>() - .await?; + // keep going if the key returned is less than the last key requested + ready(kv.0.as_slice() <= last_key.as_ref()) + }) + .try_collect::, Vec)>>() + .await?, + ); // remove the last key from middle and do a proof on it - let last_key_proof = match middle.pop() { + let last_key_proof = match middle.last() { None => { return Ok(Some(api::RangeProof { first_key_proof: first_key_proof.clone(), @@ -1888,7 +1892,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(rangeproof.middle.len(), (u8::MAX - 1).into()); + assert_eq!(rangeproof.middle.len(), u8::MAX as usize + 1); assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); let left_proof = merkle.prove([u8::MIN], root).unwrap(); let right_proof = merkle.prove([u8::MAX], root).unwrap(); @@ -1917,7 +1921,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - assert_eq!(rangeproof.middle.len(), 0); + assert_eq!(rangeproof.middle.len(), 1); } #[test] From 80a09f63acb4773df81fd3a0d8578fbfa01bd527 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 28 Nov 2023 15:08:38 -0800 Subject: [PATCH 0377/1053] Remove test for edge key length differences (#388) --- firewood/src/proof.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index b5b571c696e2..e101ff2fa8c0 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -38,8 +38,6 @@ pub enum ProofError { InvalidProof, #[error("invalid edge keys")] InvalidEdgeKeys, - #[error("inconsisent edge keys")] - InconsistentEdgeKeys, #[error("node insertion error")] NodesInsertionError, #[error("node not in trie")] @@ -223,11 +221,6 @@ impl + Send> Proof { return Err(ProofError::InvalidEdgeKeys); } - // TODO(Hao): different length edge keys should be supported - if first_key.as_ref().len() != last_key.as_ref().len() { - return Err(ProofError::InconsistentEdgeKeys); - } - // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. From 4a4f4bab181a561cc84cc786c2f12d83b8761b48 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 28 Nov 2023 23:38:02 -0500 Subject: [PATCH 0378/1053] Delegate branch-storage logic to the branch mod (#373) --- firewood/src/merkle.rs | 12 ++-- firewood/src/merkle/node.rs | 74 ++++++----------------- firewood/src/merkle/node/branch.rs | 94 +++++++++++++++++++++++++----- firewood/src/shale/disk_address.rs | 6 +- 4 files changed, 106 insertions(+), 80 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index eb53b8a3abf4..56d6cd1b9399 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -14,7 +14,7 @@ use thiserror::Error; mod node; mod trie_hash; -pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath, MAX_CHILDREN}; +pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath}; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; @@ -81,7 +81,7 @@ impl + Send + Sync> Merkle { .put_item( Node::from_branch(BranchNode { // path: vec![].into(), - children: [None; MAX_CHILDREN], + children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), }), @@ -199,7 +199,7 @@ impl + Send + Sync> Merkle { )); let leaf_address = self.put_node(new_node)?.as_ptr(); - let mut chd = [None; MAX_CHILDREN]; + let mut chd = [None; BranchNode::MAX_CHILDREN]; let last_matching_nibble = matching_path[idx]; chd[last_matching_nibble as usize] = Some(leaf_address); @@ -340,7 +340,7 @@ impl + Send + Sync> Merkle { }; // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] - let mut children = [None; MAX_CHILDREN]; + let mut children = [None; BranchNode::MAX_CHILDREN]; children[idx] = leaf_address.into(); @@ -561,7 +561,7 @@ impl + Send + Sync> Merkle { }; if let Some((idx, more, ext, val)) = info { - let mut chd = [None; MAX_CHILDREN]; + let mut chd = [None; BranchNode::MAX_CHILDREN]; let c_ptr = if more { u_ptr @@ -1695,7 +1695,7 @@ mod tests { fn branch(value: Vec, encoded_child: Option>) -> Node { let children = Default::default(); let value = Some(Data(value)); - let mut children_encoded = <[Option>; MAX_CHILDREN]>::default(); + let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { children_encoded[0] = Some(child); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index b0d5c4540e60..938e994aad8f 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -22,7 +22,7 @@ mod extension; mod leaf; mod partial_path; -pub use branch::{BranchNode, MAX_CHILDREN, SIZE as BRANCH_NODE_SIZE}; +pub use branch::BranchNode; pub use extension::ExtNode; pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; pub use partial_path::PartialPath; @@ -114,7 +114,7 @@ impl NodeType { } } // TODO: add path - BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), + BranchNode::MSIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), size => Err(Box::new(bincode::ErrorKind::Custom(format!( "invalid size: {size}" )))), @@ -216,7 +216,7 @@ impl Node { inner: NodeType::Branch( BranchNode { // path: vec![].into(), - children: [Some(DiskAddress::null()); MAX_CHILDREN], + children: [Some(DiskAddress::null()); BranchNode::MAX_CHILDREN], value: Some(Data(Vec::new())), children_encoded: Default::default(), } @@ -370,7 +370,7 @@ impl Storable for Node { NodeTypeId::Branch => { // TODO: add path // TODO: figure out what this size is? - let branch_header_size = MAX_CHILDREN as u64 * 8 + 4; + let branch_header_size = BranchNode::MAX_CHILDREN as u64 * 8 + 4; let node_raw = mem.get_view(addr + Meta::SIZE, branch_header_size).ok_or( ShaleError::InvalidCacheView { offset: addr + Meta::SIZE, @@ -379,7 +379,7 @@ impl Storable for Node { )?; let mut cur = Cursor::new(node_raw.as_deref()); - let mut chd = [None; MAX_CHILDREN]; + let mut chd = [None; BranchNode::MAX_CHILDREN]; let mut buff = [0; 8]; for chd in chd.iter_mut() { @@ -392,12 +392,13 @@ impl Storable for Node { cur.read_exact(&mut buff[..4])?; - let raw_len = - u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")) as u64; + let raw_len = u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")); - let value = if raw_len == u32::MAX as u64 { + let value = if raw_len == u32::MAX { None } else { + let raw_len = raw_len as u64; + Some(Data( mem.get_view(addr + Meta::SIZE + branch_header_size as usize, raw_len) .ok_or(ShaleError::InvalidCacheView { @@ -408,9 +409,10 @@ impl Storable for Node { )) }; - let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); + let mut chd_encoded: [Option>; BranchNode::MAX_CHILDREN] = + Default::default(); - let offset = if raw_len == u32::MAX as u64 { + let offset = if raw_len == u32::MAX { addr + Meta::SIZE + branch_header_size as usize } else { addr + Meta::SIZE + branch_header_size as usize + raw_len as usize @@ -598,20 +600,7 @@ impl Storable for Node { + match &self.inner { NodeType::Branch(n) => { // TODO: add path - let mut encoded_len = 0; - for emcoded in n.children_encoded.iter() { - encoded_len += match emcoded { - Some(v) => 1 + v.len() as u64, - None => 1, - } - } - MAX_CHILDREN as u64 * 8 - + 4 - + match &n.value { - Some(val) => val.len() as u64, - None => 0, - } - + encoded_len + n.serialized_len() } NodeType::Extension(n) => { 1 + 8 @@ -654,36 +643,9 @@ impl Storable for Node { // TODO: add path cur.write_all(&[type_id::NodeTypeId::Branch as u8]).unwrap(); - for c in n.children.iter() { - cur.write_all(&match c { - Some(p) => p.to_le_bytes(), - None => 0u64.to_le_bytes(), - })?; - } + let pos = cur.position() as usize; - match &n.value { - Some(val) => { - cur.write_all(&(val.len() as u32).to_le_bytes())?; - cur.write_all(val)? - } - None => { - cur.write_all(&u32::MAX.to_le_bytes())?; - } - } - - // Since child encoding will only be unset after initialization (only used for range proof), - // it is fine to encode its value adjacent to other fields. Same for extention node. - for encoded in n.children_encoded.iter() { - match encoded { - Some(v) => { - cur.write_all(&[v.len() as u8])?; - cur.write_all(v)? - } - None => cur.write_all(&0u8.to_le_bytes())?, - } - } - - Ok(()) + n.serialize(&mut cur.get_mut()[pos..]) } NodeType::Extension(n) => { @@ -734,8 +696,8 @@ pub(super) mod tests { value: Option>, repeated_encoded_child: Option>, ) -> Node { - let children: [Option; MAX_CHILDREN] = from_fn(|i| { - if i < MAX_CHILDREN / 2 { + let children: [Option; BranchNode::MAX_CHILDREN] = from_fn(|i| { + if i < BranchNode::MAX_CHILDREN / 2 { DiskAddress::from(repeated_disk_address).into() } else { None @@ -745,7 +707,7 @@ pub(super) mod tests { let children_encoded = repeated_encoded_child .map(|child| { from_fn(|i| { - if i < MAX_CHILDREN / 2 { + if i < BranchNode::MAX_CHILDREN / 2 { child.clone().into() } else { None diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index fc48a7981035..ea4f8ca2e608 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -4,14 +4,21 @@ use super::{Data, Encoded, Node}; use crate::{ merkle::{PartialPath, TRIE_HASH_LEN}, - shale::DiskAddress, shale::ShaleStore, + shale::{DiskAddress, Storable}, }; use bincode::{Error, Options}; -use std::fmt::{Debug, Error as FmtError, Formatter}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + io::{Cursor, Write}, + mem::size_of, + ops::Deref, +}; + +pub type DataLen = u32; +pub type EncodedChildLen = u8; -pub const MAX_CHILDREN: usize = 16; -pub const SIZE: usize = MAX_CHILDREN + 1; +const MAX_CHILDREN: usize = 16; #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { @@ -50,11 +57,14 @@ impl Debug for BranchNode { } impl BranchNode { + pub const MAX_CHILDREN: usize = MAX_CHILDREN; + pub const MSIZE: usize = Self::MAX_CHILDREN + 1; + pub fn new( _path: PartialPath, - chd: [Option; MAX_CHILDREN], + chd: [Option; Self::MAX_CHILDREN], value: Option>, - chd_encoded: [Option>; MAX_CHILDREN], + chd_encoded: [Option>; Self::MAX_CHILDREN], ) -> Self { BranchNode { // path, @@ -68,19 +78,19 @@ impl BranchNode { &self.value } - pub fn chd(&self) -> &[Option; MAX_CHILDREN] { + pub fn chd(&self) -> &[Option; Self::MAX_CHILDREN] { &self.children } - pub fn chd_mut(&mut self) -> &mut [Option; MAX_CHILDREN] { + pub fn chd_mut(&mut self) -> &mut [Option; Self::MAX_CHILDREN] { &mut self.children } - pub fn chd_encode(&self) -> &[Option>; MAX_CHILDREN] { + pub fn chd_encode(&self) -> &[Option>; Self::MAX_CHILDREN] { &self.children_encoded } - pub fn chd_encoded_mut(&mut self) -> &mut [Option>; MAX_CHILDREN] { + pub fn chd_encoded_mut(&mut self) -> &mut [Option>; Self::MAX_CHILDREN] { &mut self.children_encoded } @@ -109,7 +119,7 @@ impl BranchNode { let value = Some(data).filter(|data| !data.is_empty()); // encode all children. - let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); + let mut chd_encoded: [Option>; Self::MAX_CHILDREN] = Default::default(); // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { @@ -122,7 +132,7 @@ impl BranchNode { Ok(BranchNode::new( path, - [None; MAX_CHILDREN], + [None; Self::MAX_CHILDREN], value, chd_encoded, )) @@ -130,7 +140,7 @@ impl BranchNode { pub(super) fn encode>(&self, store: &S) -> Vec { // TODO: add path to encoded node - let mut list = <[Encoded>; MAX_CHILDREN + 1]>::default(); + let mut list = <[Encoded>; Self::MAX_CHILDREN + 1]>::default(); for (i, c) in self.children.iter().enumerate() { match c { @@ -170,7 +180,7 @@ impl BranchNode { } if let Some(Data(val)) = &self.value { - list[MAX_CHILDREN] = + list[Self::MAX_CHILDREN] = Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); } @@ -179,3 +189,59 @@ impl BranchNode { .unwrap() } } + +impl Storable for BranchNode { + fn serialized_len(&self) -> u64 { + let children_len = Self::MAX_CHILDREN as u64 * DiskAddress::MSIZE; + let data_len = optional_data_len::(self.value.as_deref()); + let children_encoded_len = self.children_encoded.iter().fold(0, |len, child| { + len + optional_data_len::(child.as_ref()) + }); + + children_len + data_len + children_encoded_len + } + + fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { + let mut cursor = Cursor::new(to); + + for child in &self.children { + let bytes = child.map(|addr| addr.to_le_bytes()).unwrap_or_default(); + cursor.write_all(&bytes)?; + } + + let (value_len, value) = self + .value + .as_ref() + .map(|val| (val.len() as DataLen, val.deref())) + .unwrap_or((DataLen::MAX, &[])); + + cursor.write_all(&value_len.to_le_bytes())?; + cursor.write_all(value)?; + + for child_encoded in &self.children_encoded { + let (child_len, child) = child_encoded + .as_ref() + .map(|child| (child.len() as EncodedChildLen, child.as_slice())) + .unwrap_or((EncodedChildLen::MIN, &[])); + + cursor.write_all(&child_len.to_le_bytes())?; + cursor.write_all(child)?; + } + + Ok(()) + } + + fn deserialize( + _addr: usize, + _mem: &T, + ) -> Result + where + Self: Sized, + { + todo!() + } +} + +fn optional_data_len>(data: Option) -> u64 { + size_of::() as u64 + data.as_ref().map_or(0, |data| data.as_ref().len() as u64) +} diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 969b917f46e6..5b75b98fbf19 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -30,6 +30,8 @@ impl DerefMut for DiskAddress { } impl DiskAddress { + pub(crate) const MSIZE: u64 = size_of::() as u64; + /// Return a None DiskAddress pub fn null() -> Self { DiskAddress(None) @@ -160,10 +162,6 @@ impl std::ops::BitAnd for DiskAddress { } } -impl DiskAddress { - const MSIZE: u64 = size_of::() as u64; -} - impl Storable for DiskAddress { fn serialized_len(&self) -> u64 { Self::MSIZE From 61420a3135c7f592d1c3becfd076e19db4dfeaaa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 29 Nov 2023 09:06:04 -0800 Subject: [PATCH 0379/1053] Range proofs can contain zero length data (#386) --- firewood/src/proof.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index e101ff2fa8c0..b0883758e98a 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -30,8 +30,6 @@ pub enum ProofError { InconsistentProofData, #[error("non-monotonic range increase")] NonMonotonicIncreaseRange, - #[error("range has deletion")] - RangeHasDeletion, #[error("invalid data")] InvalidData, #[error("invalid proof")] @@ -163,10 +161,6 @@ impl + Send> Proof { return Err(ProofError::NonMonotonicIncreaseRange); } - if !vals.iter().all(|v| !v.as_ref().is_empty()) { - return Err(ProofError::RangeHasDeletion); - } - // Use in-memory merkle let mut merkle_setup = new_merkle(0x10000, 0x10000); From 31a57de9ce71a99132733c946e4880af72fc677f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 29 Nov 2023 14:54:56 -0500 Subject: [PATCH 0380/1053] Deserialize branch (#374) --- firewood/src/merkle/node.rs | 99 ++------------------------- firewood/src/merkle/node/branch.rs | 103 ++++++++++++++++++++++++++--- 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 938e994aad8f..789a772b7680 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -340,13 +340,17 @@ use type_id::NodeTypeId; impl Storable for Node { fn deserialize(addr: usize, mem: &T) -> Result { + let mut offset = addr; + let meta_raw = - mem.get_view(addr, Meta::SIZE as u64) + mem.get_view(offset, Meta::SIZE as u64) .ok_or(ShaleError::InvalidCacheView { - offset: addr, + offset, size: Meta::SIZE as u64, })?; + offset += Meta::SIZE; + let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); let root_hash = if attrs.contains(NodeAttributes::ROOT_HASH_VALID) { @@ -368,96 +372,7 @@ impl Storable for Node { match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { NodeTypeId::Branch => { - // TODO: add path - // TODO: figure out what this size is? - let branch_header_size = BranchNode::MAX_CHILDREN as u64 * 8 + 4; - let node_raw = mem.get_view(addr + Meta::SIZE, branch_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE, - size: branch_header_size, - }, - )?; - - let mut cur = Cursor::new(node_raw.as_deref()); - let mut chd = [None; BranchNode::MAX_CHILDREN]; - let mut buff = [0; 8]; - - for chd in chd.iter_mut() { - cur.read_exact(&mut buff)?; - let addr = usize::from_le_bytes(buff); - if addr != 0 { - *chd = Some(DiskAddress::from(addr)) - } - } - - cur.read_exact(&mut buff[..4])?; - - let raw_len = u32::from_le_bytes(buff[..4].try_into().expect("invalid slice")); - - let value = if raw_len == u32::MAX { - None - } else { - let raw_len = raw_len as u64; - - Some(Data( - mem.get_view(addr + Meta::SIZE + branch_header_size as usize, raw_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE + branch_header_size as usize, - size: raw_len, - })? - .as_deref(), - )) - }; - - let mut chd_encoded: [Option>; BranchNode::MAX_CHILDREN] = - Default::default(); - - let offset = if raw_len == u32::MAX { - addr + Meta::SIZE + branch_header_size as usize - } else { - addr + Meta::SIZE + branch_header_size as usize + raw_len as usize - }; - - let mut cur_encoded_len = 0; - - for chd_encoded in chd_encoded.iter_mut() { - let mut buff = [0_u8; 1]; - let len_raw = mem.get_view(offset + cur_encoded_len, 1).ok_or( - ShaleError::InvalidCacheView { - offset: offset + cur_encoded_len, - size: 1, - }, - )?; - - cur = Cursor::new(len_raw.as_deref()); - cur.read_exact(&mut buff)?; - - let len = buff[0] as u64; - cur_encoded_len += 1; - - if len != 0 { - let encoded_raw = mem.get_view(offset + cur_encoded_len, len).ok_or( - ShaleError::InvalidCacheView { - offset: offset + cur_encoded_len, - size: len, - }, - )?; - - let encoded: Vec = encoded_raw.as_deref()[0..].to_vec(); - *chd_encoded = Some(encoded); - cur_encoded_len += len as usize - } - } - - let inner = NodeType::Branch( - BranchNode { - // path: vec![].into(), - children: chd, - value, - children_encoded: chd_encoded, - } - .into(), - ); + let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); Ok(Self::new_from_hash( root_hash, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index ea4f8ca2e608..b3ef6fa020ff 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -4,13 +4,13 @@ use super::{Data, Encoded, Node}; use crate::{ merkle::{PartialPath, TRIE_HASH_LEN}, - shale::ShaleStore, shale::{DiskAddress, Storable}, + shale::{ShaleError, ShaleStore}, }; use bincode::{Error, Options}; use std::{ fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Write}, + io::{Cursor, Read, Write}, mem::size_of, ops::Deref, }; @@ -232,13 +232,98 @@ impl Storable for BranchNode { } fn deserialize( - _addr: usize, - _mem: &T, - ) -> Result - where - Self: Sized, - { - todo!() + mut addr: usize, + mem: &T, + ) -> Result { + const DATA_LEN_SIZE: usize = size_of::(); + const BRANCH_HEADER_SIZE: u64 = + BranchNode::MAX_CHILDREN as u64 * DiskAddress::MSIZE + DATA_LEN_SIZE as u64; + + let node_raw = + mem.get_view(addr, BRANCH_HEADER_SIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: BRANCH_HEADER_SIZE, + })?; + + addr += BRANCH_HEADER_SIZE as usize; + + let mut cursor = Cursor::new(node_raw.as_deref()); + let mut children = [None; BranchNode::MAX_CHILDREN]; + let mut buf = [0u8; DiskAddress::MSIZE as usize]; + + for child in &mut children { + cursor.read_exact(&mut buf)?; + *child = Some(usize::from_le_bytes(buf)) + .filter(|addr| *addr != 0) + .map(DiskAddress::from); + } + + let raw_len = { + let mut buf = [0; DATA_LEN_SIZE]; + cursor.read_exact(&mut buf)?; + Some(DataLen::from_le_bytes(buf)) + .filter(|len| *len != DataLen::MAX) + .map(|len| len as u64) + }; + + let value = match raw_len { + Some(len) => { + let data = mem + .get_view(addr, len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: len, + })?; + + addr += len as usize; + + Some(Data(data.as_deref())) + } + None => None, + }; + + let mut children_encoded: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + + for child in &mut children_encoded { + const ENCODED_CHILD_LEN_SIZE: u64 = size_of::() as u64; + + let len = mem + .get_view(addr, ENCODED_CHILD_LEN_SIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: ENCODED_CHILD_LEN_SIZE, + })? + .as_deref()[0] as u64; + + addr += ENCODED_CHILD_LEN_SIZE as usize; + + if len == 0 { + continue; + } + + let encoded = mem + .get_view(addr, len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: len, + })? + .as_deref(); + + addr += len as usize; + + *child = Some(encoded); + } + + let node = BranchNode { + // TODO: add path + // path: Vec::new().into(), + children, + value, + children_encoded, + }; + + Ok(node) } } From e36b312aaa1c7ddc5a099938b0115d3dc071150d Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 29 Nov 2023 18:00:41 -0500 Subject: [PATCH 0381/1053] Implement Storable for LeafNode (#376) --- firewood/src/merkle/node.rs | 79 +++------------------- firewood/src/merkle/node/extension.rs | 53 ++++++++++++++- firewood/src/merkle/node/leaf.rs | 95 ++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 73 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 789a772b7680..ea290318a23f 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -30,7 +30,7 @@ pub use partial_path::PartialPath; use crate::merkle::to_nibble_array; use crate::nibbles::Nibbles; -use super::{from_nibbles, TrieHash, TRIE_HASH_LEN}; +use super::{TrieHash, TRIE_HASH_LEN}; bitflags! { // should only ever be the size of a nibble @@ -462,48 +462,8 @@ impl Storable for Node { } NodeTypeId::Leaf => { - let leaf_header_size = 1 + 4; - let node_raw = mem.get_view(addr + Meta::SIZE, leaf_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE, - size: leaf_header_size, - }, - )?; - - let mut cur = Cursor::new(node_raw.as_deref()); - let mut buff = [0; 4]; - cur.read_exact(&mut buff[..1])?; - - let path_len = buff[0] as u64; - - cur.read_exact(&mut buff)?; - - let data_len = u32::from_le_bytes(buff) as u64; - let remainder = mem - .get_view( - addr + Meta::SIZE + leaf_header_size as usize, - path_len + data_len, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE + leaf_header_size as usize, - size: path_len + data_len, - })?; - - let nibbles: Vec<_> = remainder - .as_deref() - .into_iter() - .take(path_len as usize) - .flat_map(to_nibble_array) - .collect(); - - let (path, _) = PartialPath::decode(&nibbles); - let data = Data(remainder.as_deref()[path_len as usize..].to_vec()); - - let node = Self::new_from_hash( - root_hash, - is_encoded_longer_than_hash_len, - NodeType::Leaf(LeafNode { path, data }), - ); + let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); + let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); Ok(node) } @@ -517,15 +477,8 @@ impl Storable for Node { // TODO: add path n.serialized_len() } - NodeType::Extension(n) => { - 1 + 8 - + n.path.serialized_len() - + match n.chd_encoded() { - Some(v) => 1 + v.len() as u64, - None => 1, - } - } - NodeType::Leaf(n) => 1 + 4 + n.path.serialized_len() + n.data.len() as u64, + NodeType::Extension(n) => n.serialized_len(), + NodeType::Leaf(n) => n.serialized_len(), } } @@ -556,7 +509,7 @@ impl Storable for Node { match &self.inner { NodeType::Branch(n) => { // TODO: add path - cur.write_all(&[type_id::NodeTypeId::Branch as u8]).unwrap(); + cur.write_all(&[type_id::NodeTypeId::Branch as u8])?; let pos = cur.position() as usize; @@ -566,29 +519,17 @@ impl Storable for Node { NodeType::Extension(n) => { cur.write_all(&[type_id::NodeTypeId::Extension as u8])?; - let path: Vec = from_nibbles(&n.path.encode(false)).collect(); - - cur.write_all(&[path.len() as u8])?; - cur.write_all(&n.child.to_le_bytes())?; - cur.write_all(&path)?; - - if let Some(encoded) = n.chd_encoded() { - cur.write_all(&[encoded.len() as u8])?; - cur.write_all(encoded)?; - } + let pos = cur.position() as usize; - Ok(()) + n.serialize(&mut cur.get_mut()[pos..]) } NodeType::Leaf(n) => { cur.write_all(&[type_id::NodeTypeId::Leaf as u8])?; - let path: Vec = from_nibbles(&n.path.encode(true)).collect(); + let pos = cur.position() as usize; - cur.write_all(&[path.len() as u8])?; - cur.write_all(&(n.data.len() as u32).to_le_bytes())?; - cur.write_all(&path)?; - cur.write_all(&n.data).map_err(ShaleError::Io) + n.serialize(&mut cur.get_mut()[pos..]) } } } diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index fcbcc4e7437e..d321268ff50c 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -6,9 +6,15 @@ use bincode::Options; use super::{Encoded, Node}; use crate::{ merkle::{from_nibbles, PartialPath, TRIE_HASH_LEN}, - shale::{DiskAddress, ShaleStore}, + shale::{DiskAddress, ShaleStore, Storable}, }; -use std::fmt::{Debug, Error as FmtError, Formatter}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + io::{Cursor, Write}, + mem::size_of, +}; + +type DataLen = u8; #[derive(PartialEq, Eq, Clone)] pub struct ExtNode { @@ -87,3 +93,46 @@ impl ExtNode { &mut self.child_encoded } } + +impl Storable for ExtNode { + fn serialized_len(&self) -> u64 { + let path_len_size = size_of::() as u64; + let path_len = self.path.serialized_len(); + let child_len = DiskAddress::MSIZE; + let encoded_len_size = size_of::() as u64; + let encoded_len = self + .child_encoded + .as_ref() + .map(|v| v.len() as u64) + .unwrap_or(0); + + path_len_size + path_len + child_len + encoded_len_size + encoded_len + } + + fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { + let mut cursor = Cursor::new(to); + + let path: Vec = from_nibbles(&self.path.encode(false)).collect(); + + cursor.write_all(&[path.len() as DataLen])?; + cursor.write_all(&self.child.to_le_bytes())?; + cursor.write_all(&path)?; + + if let Some(encoded) = self.chd_encoded() { + cursor.write_all(&[encoded.len() as DataLen])?; + cursor.write_all(encoded)?; + } + + Ok(()) + } + + fn deserialize( + _addr: usize, + _mem: &T, + ) -> Result + where + Self: Sized, + { + todo!() + } +} diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 475f62a007a7..6235b2e2e1e5 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -1,15 +1,25 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt::{Debug, Error as FmtError, Formatter}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + io::{Cursor, Read, Write}, + mem::size_of, +}; use bincode::Options; use super::{Data, Encoded}; -use crate::merkle::{from_nibbles, PartialPath}; +use crate::{ + merkle::{from_nibbles, to_nibble_array, PartialPath}, + shale::{ShaleError::InvalidCacheView, Storable}, +}; pub const SIZE: usize = 2; +type PathLen = u8; +type DataLen = u32; + #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { pub(crate) path: PartialPath, @@ -23,6 +33,9 @@ impl Debug for LeafNode { } impl LeafNode { + const PATH_LEN_SIZE: u64 = size_of::() as u64; + const DATA_LEN_SIZE: u64 = size_of::() as u64; + pub fn new, D: Into>(path: P, data: D) -> Self { Self { path: path.into(), @@ -51,6 +64,84 @@ impl LeafNode { } } +impl Storable for LeafNode { + fn serialized_len(&self) -> u64 { + let path_len_size = size_of::() as u64; + let path_len = self.path.serialized_len(); + let data_len_size = size_of::() as u64; + let data_len = self.data.len() as u64; + + path_len_size + path_len + data_len_size + data_len + } + + fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { + let mut cursor = Cursor::new(to); + + let path: Vec = from_nibbles(&self.path.encode(true)).collect(); + + cursor.write_all(&[path.len() as PathLen])?; + + let data_len = self.data.len() as DataLen; + cursor.write_all(&data_len.to_le_bytes())?; + + cursor.write_all(&path)?; + cursor.write_all(&self.data)?; + + Ok(()) + } + + fn deserialize( + mut offset: usize, + mem: &T, + ) -> Result + where + Self: Sized, + { + let header_size = Self::PATH_LEN_SIZE + Self::DATA_LEN_SIZE; + + let node_header_raw = mem + .get_view(offset, header_size) + .ok_or(InvalidCacheView { + offset, + size: header_size, + })? + .as_deref(); + + offset += header_size as usize; + + let mut cursor = Cursor::new(node_header_raw); + let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; + + let path_len = { + let buf = &mut buf[..Self::PATH_LEN_SIZE as usize]; + cursor.read_exact(buf)?; + buf[0] as u64 + }; + + let data_len = { + cursor.read_exact(buf.as_mut())?; + DataLen::from_le_bytes(buf) as u64 + }; + + let size = path_len + data_len; + let remainder = mem + .get_view(offset, size) + .ok_or(InvalidCacheView { offset, size })? + .as_deref(); + + let (path, data) = remainder.split_at(path_len as usize); + + let path = { + let nibbles: Vec = path.iter().copied().flat_map(to_nibble_array).collect(); + PartialPath::decode(&nibbles).0 + }; + + let data = Data(data.to_vec()); + + Ok(Self::new(path, data)) + } +} + #[cfg(test)] mod tests { use super::*; From 8799910035817e0574d02f516fbb73f0d52b907b Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 30 Nov 2023 13:26:59 -0500 Subject: [PATCH 0382/1053] Finish deserialization of extension-node (#377) --- firewood/src/merkle/node.rs | 82 +-------------------- firewood/src/merkle/node/branch.rs | 12 ++- firewood/src/merkle/node/extension.rs | 101 +++++++++++++++++++++++--- firewood/src/merkle/node/leaf.rs | 8 +- 4 files changed, 109 insertions(+), 94 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index ea290318a23f..3f16141772ec 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -9,7 +9,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; use std::{ fmt::Debug, - io::{Cursor, Read, Write}, + io::{Cursor, Write}, mem::size_of, sync::{ atomic::{AtomicBool, Ordering}, @@ -27,7 +27,6 @@ pub use extension::ExtNode; pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; pub use partial_path::PartialPath; -use crate::merkle::to_nibble_array; use crate::nibbles::Nibbles; use super::{TrieHash, TRIE_HASH_LEN}; @@ -339,9 +338,7 @@ mod type_id { use type_id::NodeTypeId; impl Storable for Node { - fn deserialize(addr: usize, mem: &T) -> Result { - let mut offset = addr; - + fn deserialize(mut offset: usize, mem: &T) -> Result { let meta_raw = mem.get_view(offset, Meta::SIZE as u64) .ok_or(ShaleError::InvalidCacheView { @@ -382,80 +379,7 @@ impl Storable for Node { } NodeTypeId::Extension => { - let ext_header_size = 1 + 8; - - let node_raw = mem.get_view(addr + Meta::SIZE, ext_header_size).ok_or( - ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE, - size: ext_header_size, - }, - )?; - - let mut cur = Cursor::new(node_raw.as_deref()); - let mut buff = [0; 8]; - - cur.read_exact(&mut buff[..1])?; - let path_len = buff[0] as u64; - - cur.read_exact(&mut buff)?; - let ptr = u64::from_le_bytes(buff); - - let nibbles: Vec = mem - .get_view(addr + Meta::SIZE + ext_header_size as usize, path_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE + ext_header_size as usize, - size: path_len, - })? - .as_deref() - .into_iter() - .flat_map(to_nibble_array) - .collect(); - - let (path, _) = PartialPath::decode(&nibbles); - - let mut buff = [0_u8; 1]; - - let encoded_len_raw = mem - .get_view( - addr + Meta::SIZE + ext_header_size as usize + path_len as usize, - 1, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr + Meta::SIZE + ext_header_size as usize + path_len as usize, - size: 1, - })?; - - cur = Cursor::new(encoded_len_raw.as_deref()); - cur.read_exact(&mut buff)?; - - let encoded_len = buff[0] as u64; - - let encoded: Option> = if encoded_len != 0 { - let emcoded_raw = mem - .get_view( - addr + Meta::SIZE + ext_header_size as usize + path_len as usize + 1, - encoded_len, - ) - .ok_or(ShaleError::InvalidCacheView { - offset: addr - + Meta::SIZE - + ext_header_size as usize - + path_len as usize - + 1, - size: encoded_len, - })?; - - Some(emcoded_raw.as_deref()[0..].to_vec()) - } else { - None - }; - - let inner = NodeType::Extension(ExtNode { - path, - child: DiskAddress::from(ptr as usize), - child_encoded: encoded, - }); - + let inner = NodeType::Extension(ExtNode::deserialize(offset, mem)?); let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); Ok(node) diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index b3ef6fa020ff..0f3995c2129e 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -288,13 +288,21 @@ impl Storable for BranchNode { for child in &mut children_encoded { const ENCODED_CHILD_LEN_SIZE: u64 = size_of::() as u64; - let len = mem + let len_raw = mem .get_view(addr, ENCODED_CHILD_LEN_SIZE) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: ENCODED_CHILD_LEN_SIZE, })? - .as_deref()[0] as u64; + .as_deref(); + + let mut cursor = Cursor::new(len_raw); + + let len = { + let mut buf = [0; ENCODED_CHILD_LEN_SIZE as usize]; + cursor.read_exact(buf.as_mut())?; + EncodedChildLen::from_le_bytes(buf) as u64 + }; addr += ENCODED_CHILD_LEN_SIZE as usize; diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index d321268ff50c..9cc017ab5448 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -5,15 +5,16 @@ use bincode::Options; use super::{Encoded, Node}; use crate::{ - merkle::{from_nibbles, PartialPath, TRIE_HASH_LEN}, - shale::{DiskAddress, ShaleStore, Storable}, + merkle::{from_nibbles, to_nibble_array, PartialPath, TRIE_HASH_LEN}, + shale::{DiskAddress, ShaleError::InvalidCacheView, ShaleStore, Storable}, }; use std::{ fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Write}, + io::{Cursor, Read, Write}, mem::size_of, }; +type PathLen = u8; type DataLen = u8; #[derive(PartialEq, Eq, Clone)] @@ -35,6 +36,9 @@ impl Debug for ExtNode { } impl ExtNode { + const PATH_LEN_SIZE: u64 = size_of::() as u64; + const DATA_LEN_SIZE: u64 = size_of::() as u64; + pub(super) fn encode>(&self, store: &S) -> Vec { let mut list = <[Encoded>; 2]>::default(); list[0] = Encoded::Data( @@ -96,10 +100,13 @@ impl ExtNode { impl Storable for ExtNode { fn serialized_len(&self) -> u64 { - let path_len_size = size_of::() as u64; + let path_len_size = Self::PATH_LEN_SIZE; let path_len = self.path.serialized_len(); let child_len = DiskAddress::MSIZE; - let encoded_len_size = size_of::() as u64; + // TODO: + // this seems wrong to always include this byte even if there isn't a child + // but it matches the original implementation + let encoded_len_size = Self::DATA_LEN_SIZE; let encoded_len = self .child_encoded .as_ref() @@ -114,7 +121,7 @@ impl Storable for ExtNode { let path: Vec = from_nibbles(&self.path.encode(false)).collect(); - cursor.write_all(&[path.len() as DataLen])?; + cursor.write_all(&[path.len() as PathLen])?; cursor.write_all(&self.child.to_le_bytes())?; cursor.write_all(&path)?; @@ -127,12 +134,88 @@ impl Storable for ExtNode { } fn deserialize( - _addr: usize, - _mem: &T, + mut offset: usize, + mem: &T, ) -> Result where Self: Sized, { - todo!() + let header_size = Self::PATH_LEN_SIZE + DiskAddress::MSIZE; + + let path_and_disk_address = mem + .get_view(offset, header_size) + .ok_or(InvalidCacheView { + offset, + size: header_size, + })? + .as_deref(); + + offset += header_size as usize; + + let mut cursor = Cursor::new(path_and_disk_address); + let mut buf = [0u8; DiskAddress::MSIZE as usize]; + + let path_len = { + let buf = &mut buf[..Self::PATH_LEN_SIZE as usize]; + cursor.read_exact(buf)?; + buf[0] as u64 + }; + + let disk_address = { + cursor.read_exact(buf.as_mut())?; + DiskAddress::from(u64::from_le_bytes(buf) as usize) + }; + + let path = mem + .get_view(offset, path_len) + .ok_or(InvalidCacheView { + offset, + size: path_len, + })? + .as_deref(); + + offset += path_len as usize; + + let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); + + let path = PartialPath::decode(&path).0; + + let encoded_len_raw = mem + .get_view(offset, Self::DATA_LEN_SIZE) + .ok_or(InvalidCacheView { + offset, + size: Self::DATA_LEN_SIZE, + })? + .as_deref(); + + offset += Self::DATA_LEN_SIZE as usize; + + let mut cursor = Cursor::new(encoded_len_raw); + + let encoded_len = { + let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; + cursor.read_exact(buf.as_mut())?; + DataLen::from_le_bytes(buf) as u64 + }; + + let encoded = if encoded_len != 0 { + let encoded = mem + .get_view(offset, encoded_len) + .ok_or(InvalidCacheView { + offset, + size: encoded_len, + })? + .as_deref(); + + encoded.into() + } else { + None + }; + + Ok(ExtNode { + path, + child: disk_address, + child_encoded: encoded, + }) } } diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 6235b2e2e1e5..42826d110016 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -110,15 +110,15 @@ impl Storable for LeafNode { offset += header_size as usize; let mut cursor = Cursor::new(node_header_raw); - let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; let path_len = { - let buf = &mut buf[..Self::PATH_LEN_SIZE as usize]; - cursor.read_exact(buf)?; - buf[0] as u64 + let mut buf = [0u8; Self::PATH_LEN_SIZE as usize]; + cursor.read_exact(buf.as_mut())?; + PathLen::from_le_bytes(buf) as u64 }; let data_len = { + let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; cursor.read_exact(buf.as_mut())?; DataLen::from_le_bytes(buf) as u64 }; From 7c1c7a79f1db9d4a38cddc72741aee2178ff7b7c Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 30 Nov 2023 17:53:11 -0500 Subject: [PATCH 0383/1053] Move proof to be a sub-module of merkle (#378) --- firewood/src/db.rs | 5 ++--- firewood/src/db/proposal.rs | 3 ++- firewood/src/lib.rs | 1 - firewood/src/merkle.rs | 4 +++- firewood/src/{ => merkle}/proof.rs | 9 ++++++++- firewood/src/merkle_util.rs | 6 +----- firewood/src/v2/api.rs | 10 ++-------- firewood/src/v2/emptydb.rs | 11 ++++++----- firewood/src/v2/propose.rs | 4 ++-- firewood/tests/merkle.rs | 4 +--- 10 files changed, 27 insertions(+), 30 deletions(-) rename firewood/src/{ => merkle}/proof.rs (99%) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 91459ae4d6c1..f30e54c15525 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,14 +8,13 @@ pub use crate::{ }; use crate::{ file, - merkle::{Merkle, MerkleError, Node, TrieHash, TRIE_HASH_LEN}, - proof::ProofError, + merkle::{Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, storage::{ buffer::{DiskBuffer, DiskBufferRequester}, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, ZeroStore, PAGE_SIZE_NBIT, }, - v2::api::{self, HashKey, KeyType, Proof, ValueType}, + v2::api::{self, HashKey, KeyType, ValueType}, }; use crate::{ merkle, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 8afd099ec0cc..869b751c9e08 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -6,6 +6,7 @@ use super::{ DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; +use crate::merkle::Proof; use crate::shale::CachedStore; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, @@ -285,7 +286,7 @@ impl api::DbView for Proposal { Ok(self.get_revision().kv_get(key)) } - async fn single_key_proof(&self, key: K) -> Result>>, api::Error> + async fn single_key_proof(&self, key: K) -> Result>>, api::Error> where K: api::KeyType, { diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index d932522607fb..8052d2b54cf3 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -188,7 +188,6 @@ pub mod db; pub(crate) mod file; pub mod merkle; pub mod merkle_util; -pub mod proof; pub mod storage; pub mod config; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 56d6cd1b9399..d3c1c55b1e0e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,8 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::nibbles::Nibbles; use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::v2::api; -use crate::{nibbles::Nibbles, v2::api::Proof}; use futures::{Stream, StreamExt, TryStreamExt}; use sha3::Digest; use std::{ @@ -12,9 +12,11 @@ use std::{ use thiserror::Error; mod node; +pub mod proof; mod trie_hash; pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath}; +pub use proof::{Proof, ProofError}; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; diff --git a/firewood/src/proof.rs b/firewood/src/merkle/proof.rs similarity index 99% rename from firewood/src/proof.rs rename to firewood/src/merkle/proof.rs index b0883758e98a..8c59f889a441 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -2,9 +2,11 @@ // See the file LICENSE.md for licensing terms. use std::cmp::Ordering; +use std::collections::HashMap; use std::ops::Deref; use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; +use crate::v2::api::HashKey; use nix::errno::Errno; use sha3::Digest; use thiserror::Error; @@ -15,7 +17,6 @@ use crate::{ db::DbError, merkle::{to_nibble_array, Merkle, MerkleError, Node, NodeType}, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, - v2::api::Proof, }; #[derive(Debug, Error)] @@ -84,6 +85,12 @@ impl From for ProofError { } } +/// A proof that a single key is present +/// +/// The generic N represents the storage for the node data +#[derive(Clone, Debug)] +pub struct Proof(pub HashMap); + /// SubProof contains the encoded value and the hash value of a node that maps /// to a single proof step. If reaches an end step during proof verification, /// the hash value will be none, and the encoded value will be the value of the diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index f7bad0a89f55..c307f01f450f 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,15 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::merkle::{Merkle, Node, Proof, ProofError, Ref, RefMut, TrieHash}; use crate::shale::{ self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, StoredView, }; -use crate::{ - merkle::{Merkle, Node, Ref, RefMut, TrieHash}, - proof::ProofError, - v2::api::Proof, -}; use std::{num::NonZeroUsize, sync::Arc}; use thiserror::Error; diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 4321c005bc84..f0787d7855c9 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{collections::HashMap, fmt::Debug, sync::Arc}; - +pub use crate::merkle::Proof; use async_trait::async_trait; +use std::{fmt::Debug, sync::Arc}; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with @@ -86,12 +86,6 @@ pub struct RangeProof { pub middle: Vec<(K, V)>, } -/// A proof that a single key is present -/// -/// The generic N represents the storage for the node data -#[derive(Clone, Debug)] -pub struct Proof(pub HashMap); - /// The database interface, which includes a type for a static view of /// the database (the DbView). The most common implementation of the DbView /// is the api::DbView trait defined next. diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 746373ab126b..762e0b73a7e0 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,12 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::Arc; - +use super::{ + api::{Batch, Db, DbView, Error, HashKey, KeyType, RangeProof, ValueType}, + propose::{Proposal, ProposalBase}, +}; +use crate::merkle::Proof; use async_trait::async_trait; - -use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, Proof, RangeProof, ValueType}; -use super::propose::{Proposal, ProposalBase}; +use std::sync::Arc; /// An EmptyDb is a simple implementation of api::Db /// that doesn't store any data. It contains a single diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 053a6a059b4b..dda3f1d0f227 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -5,7 +5,7 @@ use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; -use crate::v2::api; +use crate::{merkle::Proof, v2::api}; use super::api::{KeyType, ValueType}; @@ -124,7 +124,7 @@ impl api::DbView for Proposal { async fn single_key_proof( &self, _key: K, - ) -> Result>>, api::Error> { + ) -> Result>>, api::Error> { todo!() } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 353e169ba2fc..bebeb68618fb 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -2,12 +2,10 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - merkle::Node, + merkle::{Node, Proof, ProofError}, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, - proof::ProofError, // TODO: we should not be using shale from an integration test shale::{cached::DynamicMem, compact::CompactSpace}, - v2::api::Proof, }; use rand::Rng; use std::collections::HashMap; From 3f71366b69dcedfa5f709486095cb7a19f82e33b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 30 Nov 2023 17:46:42 -0800 Subject: [PATCH 0384/1053] growth-ring: Add remaining lint checks (#397) --- growth-ring/Cargo.toml | 2 ++ growth-ring/src/wal.rs | 31 +++++++++++++++++++++++++------ growth-ring/tests/common/mod.rs | 1 + growth-ring/tests/rand_fail.rs | 1 + 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 603c85caca37..fe056d62cb0a 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -35,4 +35,6 @@ crate-type = ["dylib", "rlib", "staticlib"] [lints.clippy] unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" missing_const_for_fn = "warn" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index aa20681ea657..9e322526e86c 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -289,13 +289,17 @@ impl> WalFilePool { }) } - #[allow(clippy::unwrap_used)] async fn read_header(&self) -> Result { - let bytes = self.header_file.read(0, HEADER_SIZE).await?.unwrap(); - #[allow(clippy::unwrap_used)] - let bytes: [u8; HEADER_SIZE] = (&*bytes).try_into().unwrap(); - let header: Header = cast_slice(&bytes)[0]; - Ok(header) + let bytes = self + .header_file + .read(0, HEADER_SIZE) + .await? + .ok_or(WalError::Other("EOF".to_string()))?; + let slice = cast_slice::<_, Header>(&bytes); + slice + .get(0) + .copied() + .ok_or(WalError::Other("short read".to_string())) } async fn write_header(&self, header: &Header) -> Result<(), WalError> { @@ -376,8 +380,11 @@ impl> WalFilePool { for &(fid, _) in meta.iter() { files.push(Box::pin(self.get_file(fid, true)) as Pin + 'a>>) } + #[allow(clippy::indexing_slicing)] let mut fid = writes[0].0 >> file_nbit; + #[allow(clippy::indexing_slicing)] let mut alloc_start = writes[0].0 & (self.file_size - 1); + #[allow(clippy::indexing_slicing)] let mut alloc_end = alloc_start + writes[0].1.len() as u64; let last_write = unsafe { std::mem::replace(&mut *self.last_write.get(), std::mem::MaybeUninit::uninit()) @@ -549,11 +556,13 @@ impl> WalWriter { while rsize > 0 { let remain = self.block_size - bbuff_cur; + #[allow(clippy::indexing_slicing)] // TODO: remove this to reduce scope if remain > msize { let d = remain - msize; let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; let blob = unsafe { + #[allow(clippy::indexing_slicing)] &mut *self.block_buffer[bbuff_cur as usize..] .as_mut_ptr() .cast::() @@ -577,6 +586,7 @@ impl> WalWriter { }; blob.rtype = rt as u8; + #[allow(clippy::indexing_slicing)] self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] .copy_from_slice(payload); bbuff_cur += rsize; @@ -593,6 +603,7 @@ impl> WalWriter { )); } else { // the remaining block can only accommodate partial rec + #[allow(clippy::indexing_slicing)] let payload = &rec[..d as usize]; blob.counter = self.state.counter; blob.crc32 = CRC32.checksum(payload); @@ -605,10 +616,12 @@ impl> WalWriter { WalRingType::First } as u8; + #[allow(clippy::indexing_slicing)] self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] .copy_from_slice(payload); bbuff_cur += d; rsize -= d; + // TODO: not allowed: #[allow(clippy::indexing_slicing)] rec = &rec[d as usize..]; } } else { @@ -617,6 +630,7 @@ impl> WalWriter { } if bbuff_cur == self.block_size { + #[allow(clippy::indexing_slicing)] writes.push(( self.state.next, self.block_buffer[bbuff_start as usize..] @@ -631,6 +645,7 @@ impl> WalWriter { } if bbuff_cur > bbuff_start { + #[allow(clippy::indexing_slicing)] writes.push(( self.state.next, self.block_buffer[bbuff_start as usize..bbuff_cur as usize] @@ -648,6 +663,7 @@ impl> WalWriter { let blk_s = *off; let blk_e = blk_s + w.len() as u64; + #[allow(clippy::indexing_slicing)] while res[i].0.end <= blk_s { i += 1; @@ -656,6 +672,7 @@ impl> WalWriter { } } + #[allow(clippy::indexing_slicing)] while res[i].0.start < blk_e { res[i].1.push(j); @@ -681,6 +698,7 @@ impl> WalWriter { res.into_iter() .zip(records) .map(|((ringid, blks), rec)| { + #[allow(clippy::indexing_slicing)] future::try_join_all(blks.into_iter().map(|idx| writes[idx].clone())) .or_else(|_| future::ready(Err(()))) .and_then(move |_| future::ready(Ok((rec, ringid)))) @@ -1157,6 +1175,7 @@ impl WalLoader { let mut payload = vec![0; chunks.iter().fold(0, |acc, v| acc + v.len())]; let mut ps = &mut payload[..]; + #[allow(clippy::indexing_slicing)] for c in chunks { ps[..c.len()].copy_from_slice(&c); ps = &mut ps[c.len()..]; diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 4f308b35527f..0f9b2e0c0158 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![allow(clippy::indexing_slicing)] #[cfg(test)] use async_trait::async_trait; use futures::executor::block_on; diff --git a/growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs index 3557f53845dd..dcfecaec06c1 100644 --- a/growth-ring/tests/rand_fail.rs +++ b/growth-ring/tests/rand_fail.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![allow(clippy::indexing_slicing)] #[cfg(test)] mod common; From a773750385aeabe63a51b64afca0593b4c479f4d Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 30 Nov 2023 23:27:16 -0500 Subject: [PATCH 0385/1053] Add shared-path-on-insert test (#379) --- firewood/src/merkle.rs | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d3c1c55b1e0e..fb771afd93de 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1957,4 +1957,48 @@ mod tests { assert_eq!(verified, Some(value2.to_vec())); } + + // this was a specific failing case + #[test] + fn shared_path_on_insert() { + type Bytes = &'static [u8]; + let pairs: Vec<(Bytes, Bytes)> = vec![ + ( + &[1, 1, 46, 82, 67, 218], + &[23, 252, 128, 144, 235, 202, 124, 243], + ), + ( + &[1, 0, 0, 1, 1, 0, 63, 80], + &[99, 82, 31, 213, 180, 196, 49, 242], + ), + ( + &[0, 0, 0, 169, 176, 15], + &[105, 211, 176, 51, 231, 182, 74, 207], + ), + ( + &[1, 0, 0, 0, 53, 57, 93], + &[234, 139, 214, 220, 172, 38, 168, 164], + ), + ]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + for (key, val) in &pairs { + let val = val.to_vec(); + merkle.insert(key, val.clone(), root).unwrap(); + + let fetched_val = merkle.get(key, root).unwrap(); + + // make sure the value was inserted + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + for (key, val) in pairs { + let fetched_val = merkle.get(key, root).unwrap(); + + // make sure the value was inserted + assert_eq!(fetched_val.as_deref(), val.into()); + } + } } From fedc673d6e61588d8129865ca6ec6f3e3b4bc182 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 1 Dec 2023 13:18:44 -0800 Subject: [PATCH 0386/1053] Remove some unsafe code (#400) --- growth-ring/src/wal.rs | 44 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 9e322526e86c..188a7f898a6b 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use async_trait::async_trait; -use bytemuck::{cast_slice, AnyBitPattern}; +use bytemuck::{cast_slice, AnyBitPattern, NoUninit}; use futures::{ future::{self, FutureExt, TryFutureExt}, stream::StreamExt, @@ -34,7 +34,8 @@ enum WalRingType { Last, } -#[repr(packed)] +#[repr(C, packed)] +#[derive(NoUninit, Copy, Clone, Debug, AnyBitPattern)] struct WalRingBlob { counter: u32, crc32: u32, @@ -111,8 +112,8 @@ const fn counter_lt(a: u32, b: u32) -> bool { } } -#[repr(C)] -#[derive(Debug, Clone, Copy, AnyBitPattern)] +#[repr(transparent)] +#[derive(Debug, Clone, Copy, AnyBitPattern, NoUninit)] struct Header { /// all preceding files ( { struct WalFileHandle<'a, F: WalFile + 'static, S: WalStore> { fid: WalFileId, handle: &'a dyn WalFile, - pool: *const WalFilePool, + pool: &'a WalFilePool, wal_file: PhantomData, } @@ -244,9 +245,7 @@ impl<'a, F: WalFile, S: WalStore> std::ops::Deref for WalFileHandle<'a, F, S> impl<'a, F: WalFile + 'static, S: WalStore> Drop for WalFileHandle<'a, F, S> { fn drop(&mut self) { - unsafe { - (*self.pool).release_file(self.fid); - } + (self.pool).release_file(self.fid); } } @@ -302,17 +301,16 @@ impl> WalFilePool { .ok_or(WalError::Other("short read".to_string())) } - async fn write_header(&self, header: &Header) -> Result<(), WalError> { - let base = header as *const Header as usize as *const u8; - let bytes = unsafe { std::slice::from_raw_parts(base, HEADER_SIZE) }; - self.header_file.write(0, bytes.into()).await?; + async fn write_header(&self, header: Header) -> Result<(), WalError> { + self.header_file + .write(0, cast_slice(&[header]).into()) + .await?; Ok(()) } #[allow(clippy::await_holding_refcell_ref)] // TODO: Refactor to remove mutable reference from being awaited. async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { - let pool = self as *const WalFilePool; if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Vacant(e) => unsafe { @@ -323,7 +321,7 @@ impl> WalFilePool { Ok(WalFileHandle { fid, handle, - pool, + pool: self, wal_file: PhantomData, }) } else { @@ -341,7 +339,7 @@ impl> WalFilePool { Ok(WalFileHandle { fid, handle: &*v.0, - pool, + pool: self, wal_file: PhantomData, }) } @@ -510,8 +508,6 @@ pub struct WalWriter> { msize: usize, } -unsafe impl Send for WalWriter where S: WalStore + Send {} - impl> WalWriter { fn new(state: WalState, file_pool: WalFilePool) -> Self { let mut b = Vec::new(); @@ -1001,13 +997,9 @@ impl WalLoader { None => _yield!(), }; v.off += msize as u64; - let header = unsafe { &*header_raw.as_ptr().cast::() }; - let header = WalRingBlob { - counter: header.counter, - crc32: header.crc32, - rsize: header.rsize, - rtype: header.rtype, - }; + let header: &[WalRingBlob] = cast_slice(&header_raw); + let header = *header.get(0)?; + let payload; match header.rtype.try_into() { Ok(WalRingType::Full) @@ -1115,7 +1107,7 @@ impl WalLoader { }; let ringid_start = (fid << file_nbit) + v.off; v.off += msize as u64; - let header = unsafe { &*header_raw.as_ptr().cast::() }; + let header: WalRingBlob = *cast_slice(&header_raw).get(0)?; let rsize = header.rsize; match header.rtype.try_into() { Ok(WalRingType::Full) => { @@ -1292,7 +1284,7 @@ impl WalLoader { None => 0, }; - file_pool.write_header(&Header { recover_fid }).await?; + file_pool.write_header(Header { recover_fid }).await?; let mut skip_remove = false; for (fname, f) in scanned.into_iter() { From 11285ca18f89c3a5218dfe6ec6dc45f0c8cd698c Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 1 Dec 2023 16:42:02 -0500 Subject: [PATCH 0387/1053] Centralize trie-traversal for proof generation (#380) --- firewood/src/merkle.rs | 327 ++++++++++++++++++++++------- firewood/src/merkle/node.rs | 6 + firewood/src/merkle/node/branch.rs | 7 + firewood/src/merkle/proof.rs | 95 ++++----- firewood/tests/merkle.rs | 17 -- 5 files changed, 314 insertions(+), 138 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fb771afd93de..fd36c93eeb95 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -970,7 +970,7 @@ impl + Send + Sync> Merkle { node_ref: ObjRef<'a>, key: K, ) -> Result>, MerkleError> { - self.get_node_by_key_with_callback(node_ref, key, |_, _| {}) + self.get_node_by_key_with_callbacks(node_ref, key, |_, _| {}, |_, _| {}) } fn get_node_and_parents_by_key<'a, K: AsRef<[u8]>>( @@ -979,9 +979,14 @@ impl + Send + Sync> Merkle { key: K, ) -> Result<(Option>, ParentRefs<'a>), MerkleError> { let mut parents = Vec::new(); - let node_ref = self.get_node_by_key_with_callback(node_ref, key, |node_ref, nib| { - parents.push((node_ref, nib)); - })?; + let node_ref = self.get_node_by_key_with_callbacks( + node_ref, + key, + |_, _| {}, + |node_ref, nib| { + parents.push((node_ref, nib)); + }, + )?; Ok((node_ref, parents)) } @@ -992,18 +997,24 @@ impl + Send + Sync> Merkle { key: K, ) -> Result<(Option>, ParentAddresses), MerkleError> { let mut parents = Vec::new(); - let node_ref = self.get_node_by_key_with_callback(node_ref, key, |node_ref, nib| { - parents.push((node_ref.into_ptr(), nib)); - })?; + let node_ref = self.get_node_by_key_with_callbacks( + node_ref, + key, + |_, _| {}, + |node_ref, nib| { + parents.push((node_ref.into_ptr(), nib)); + }, + )?; Ok((node_ref, parents)) } - fn get_node_by_key_with_callback<'a, K: AsRef<[u8]>>( + fn get_node_by_key_with_callbacks<'a, K: AsRef<[u8]>>( &'a self, mut node_ref: ObjRef<'a>, key: K, - mut loop_callback: impl FnMut(ObjRef<'a>, u8), + mut start_loop_callback: impl FnMut(DiskAddress, u8), + mut end_loop_callback: impl FnMut(ObjRef<'a>, u8), ) -> Result>, MerkleError> { let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); @@ -1012,6 +1023,8 @@ impl + Send + Sync> Merkle { break; }; + start_loop_callback(node_ref.as_ptr(), nib); + let next_ptr = match &node_ref.inner { NodeType::Branch(n) => match n.children[nib as usize] { Some(c) => c, @@ -1045,7 +1058,7 @@ impl + Send + Sync> Merkle { } }; - loop_callback(node_ref, nib); + end_loop_callback(node_ref, nib); node_ref = self.get_node(next_ptr)?; } @@ -1090,78 +1103,28 @@ impl + Send + Sync> Merkle { where K: AsRef<[u8]>, { - let key_nibbles = Nibbles::<0>::new(key.as_ref()); - let mut proofs = HashMap::new(); if root.is_null() { return Ok(Proof(proofs)); } - // Skip the sentinel root - let root = self - .get_node(root)? - .inner - .as_branch() - .ok_or(MerkleError::NotBranchNode)? - .children[0]; - let mut u_ref = match root { - Some(root) => self.get_node(root)?, - None => return Ok(Proof(proofs)), - }; + let root_node = self.get_node(root)?; - let mut nskip = 0; - let mut nodes: Vec = Vec::new(); + let mut nodes = Vec::new(); - // TODO: use get_node_by_key (and write proper unit test) - for (i, nib) in key_nibbles.into_iter().enumerate() { - if nskip > 0 { - nskip -= 1; - continue; - } - nodes.push(u_ref.as_ptr()); - let next_ptr: DiskAddress = match &u_ref.inner { - NodeType::Branch(n) => match n.children[nib as usize] { - Some(c) => c, - None => break, - }, - NodeType::Leaf(_) => break, - NodeType::Extension(n) => { - // the key passed in must match the entire remainder of this - // extension node, otherwise we break out - let n_path = &n.path; - let remaining_path = key_nibbles.into_iter().skip(i); - if remaining_path.size_hint().0 < n_path.len() { - // all bytes aren't there - break; - } - if !remaining_path.take(n_path.len()).eq(n_path.iter().cloned()) { - // contents aren't the same - break; - } - nskip = n_path.len() - 1; - n.chd() - } - }; - u_ref = self.get_node(next_ptr)?; - } + let node = self.get_node_by_key_with_callbacks( + root_node, + key, + |node, _| nodes.push(node), + |_, _| {}, + )?; - match &u_ref.inner { - NodeType::Branch(n) => { - if n.value.as_ref().is_some() { - nodes.push(u_ref.as_ptr()); - } - } - NodeType::Leaf(n) => { - if n.path.len() == 0 { - nodes.push(u_ref.as_ptr()); - } - } - _ => (), + if let Some(node) = node { + nodes.push(node.as_ptr()); } - drop(u_ref); // Get the hashes of the nodes. - for node in nodes { + for node in nodes.into_iter().skip(1) { let node = self.get_node(node)?; let encoded = <&[u8]>::clone(&node.get_encoded::(self.store.as_ref())); let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(encoded).into(); @@ -1864,6 +1827,16 @@ mod tests { } } + #[test] + fn get_empty_proof() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let proof = merkle.prove(b"any-key", root).unwrap(); + + assert!(proof.0.is_empty()); + } + #[tokio::test] async fn empty_range_proof() { let merkle: Merkle> = create_test_merkle(); @@ -2001,4 +1974,216 @@ mod tests { assert_eq!(fetched_val.as_deref(), val.into()); } } + + #[test] + fn overwrite_leaf() { + let key = vec![0x00]; + let val = vec![1]; + let overwrite = vec![2]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + merkle.insert(&key, overwrite.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(overwrite.as_slice()) + ); + } + + #[test] + fn new_leaf_is_a_child_of_the_old_leaf() { + let key = vec![0xff]; + let val = vec![1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = vec![2]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key_2, val_2.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + } + + #[test] + fn old_leaf_is_a_child_of_the_new_leaf() { + let key = vec![0xff, 0x00]; + let val = vec![1]; + let key_2 = vec![0xff]; + let val_2 = vec![2]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key_2, val_2.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + } + + #[test] + fn new_leaf_is_sibling_of_old_leaf() { + let key = vec![0xff]; + let val = vec![1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = vec![2]; + let key_3 = vec![0xff, 0x0f]; + let val_3 = vec![3]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key_2, val_2.clone(), root).unwrap(); + merkle.insert(&key_3, val_3.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + + assert_eq!( + merkle.get(&key_3, root).unwrap().as_deref(), + Some(val_3.as_slice()) + ); + } + + #[test] + fn old_branch_is_a_child_of_new_branch() { + let key = vec![0xff, 0xf0]; + let val = vec![1]; + let key_2 = vec![0xff, 0xf0, 0x00]; + let val_2 = vec![2]; + let key_3 = vec![0xff]; + let val_3 = vec![3]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key_2, val_2.clone(), root).unwrap(); + merkle.insert(&key_3, val_3.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + + assert_eq!( + merkle.get(&key_3, root).unwrap().as_deref(), + Some(val_3.as_slice()) + ); + } + + #[test] + fn overlapping_branch_insert() { + let key = vec![0xff]; + let val = vec![1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = vec![2]; + + let overwrite = vec![3]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key_2, val_2.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(val.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + + merkle.insert(&key, overwrite.clone(), root).unwrap(); + + assert_eq!( + merkle.get(&key, root).unwrap().as_deref(), + Some(overwrite.as_slice()) + ); + + assert_eq!( + merkle.get(&key_2, root).unwrap().as_deref(), + Some(val_2.as_slice()) + ); + } + + #[test] + fn single_key_proof_with_one_node() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let key = b"key"; + let value = b"value"; + + merkle.insert(key, value.to_vec(), root).unwrap(); + let root_hash = merkle.root_hash(root).unwrap(); + + let proof = merkle.prove(key, root).unwrap(); + + let verified = proof.verify(key, root_hash.0).unwrap(); + + assert_eq!(verified, Some(value.to_vec())); + } + + #[test] + fn two_key_proof_without_shared_path() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let key1 = &[0x00]; + let key2 = &[0xff]; + + merkle.insert(key1, key1.to_vec(), root).unwrap(); + merkle.insert(key2, key2.to_vec(), root).unwrap(); + + let root_hash = merkle.root_hash(root).unwrap(); + + let verified = { + let proof = merkle.prove(key1, root).unwrap(); + proof.verify(key1, root_hash.0).unwrap() + }; + + assert_eq!(verified.as_deref(), Some(key1.as_slice())); + } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 3f16141772ec..ffa466623c65 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -55,6 +55,12 @@ impl From> for Data { } } +impl Data { + pub fn into_inner(self) -> Vec { + self.0 + } +} + #[derive(Serialize, Deserialize, Debug)] enum Encoded { Raw(T), diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 0f3995c2129e..39ad8e419bfb 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -164,6 +164,13 @@ impl BranchNode { list[i] = Encoded::Raw(child_encoded.to_vec()); } } + + // TODO: + // we need a better solution for this. This is only used for reconstructing a + // merkle-tree in memory. The proper way to do it is to abstract a trait for nodes + // but that's a heavy lift. + // TODO: + // change the data-structure children: [(Option, Option>); Self::MAX_CHILDREN] None => { // Check if there is already a calculated encoded value for the child, which // can happen when manually constructing a trie from proof. diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 8c59f889a441..8caba21f9e0f 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -19,6 +19,8 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; +use super::TRIE_HASH_LEN; + #[derive(Debug, Error)] pub enum ProofError { #[error("decoding error")] @@ -91,15 +93,14 @@ impl From for ProofError { #[derive(Clone, Debug)] pub struct Proof(pub HashMap); -/// SubProof contains the encoded value and the hash value of a node that maps +/// `SubProof` contains the value or the hash of a node that maps /// to a single proof step. If reaches an end step during proof verification, -/// the hash value will be none, and the encoded value will be the value of the -/// node. +/// the `SubProof` should be the `Value` variant. #[derive(Debug)] -struct SubProof { - encoded: Vec, - hash: Option<[u8; 32]>, +enum SubProof { + Data(Vec), + Hash([u8; TRIE_HASH_LEN]), } impl + Send> Proof { @@ -118,33 +119,24 @@ impl + Send> Proof { let mut cur_hash = root_hash; let proofs_map = &self.0; - let value_node = loop { + loop { let cur_proof = proofs_map .get(&cur_hash) .ok_or(ProofError::ProofNodeMissing)?; let node = NodeType::decode(cur_proof.as_ref())?; + // TODO: I think this will currently fail if the key is &[]; let (sub_proof, traversed_nibbles) = locate_subproof(key_nibbles, node)?; key_nibbles = traversed_nibbles; cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(p) if key_nibbles.is_empty() => break p.encoded, + Some(SubProof::Data(value)) if key_nibbles.is_empty() => return Ok(Some(value)), // The trie doesn't contain the key. - Some(SubProof { - hash: Some(hash), .. - }) => hash, + Some(SubProof::Hash(hash)) => hash, _ => return Ok(None), }; - }; - - let value = match NodeType::decode(&value_node) { - Ok(NodeType::Branch(branch)) => branch.value().as_ref().map(|v| v.to_vec()), - Ok(NodeType::Leaf(leaf)) => leaf.data().to_vec().into(), - _ => return Ok(value_node.into()), - }; - - Ok(value) + } } pub fn concat_proofs(&mut self, other: Proof) { @@ -354,14 +346,16 @@ impl + Send> Proof { let mut data = None; // Decode the last subproof to get the value. - if let Some(p_hash) = p.hash.as_ref() { - let proof = - proofs_map.get(p_hash).ok_or(ProofError::ProofNodeMissing)?; + if let SubProof::Hash(p_hash) = p { + let proof = proofs_map + .get(&p_hash) + .ok_or(ProofError::ProofNodeMissing)?; chd_ptr = self.decode_node(merkle, cur_key, proof.as_ref(), true)?.0; // Link the child to the parent based on the node type. match &u_ref.inner() { + // TODO: add path NodeType::Branch(n) if n.chd()[branch_index].is_none() => { // insert the leaf to the empty slot u_ref @@ -411,8 +405,12 @@ impl + Send> Proof { NodeType::Leaf(n) => { // Return the value on the node only when the key matches exactly // (e.g. the length path of subproof node is 0). - if p.hash.is_none() || (p.hash.is_some() && n.path().len() == 0) { - data = Some(n.data().deref().to_vec()); + match p { + SubProof::Data(_) => data = Some(n.data().deref().to_vec()), + SubProof::Hash(_) if n.path().len() == 0 => { + data = Some(n.data().deref().to_vec()) + } + _ => (), } } _ => (), @@ -422,7 +420,7 @@ impl + Send> Proof { } // The trie doesn't contain the key. - if p.hash.is_none() { + let SubProof::Hash(hash) = p else { return if allow_non_existent_node { Ok(None) } else { @@ -430,7 +428,7 @@ impl + Send> Proof { }; }; - cur_hash = p.hash.unwrap(); + cur_hash = hash; cur_key = &chunks[key_index..]; } } @@ -455,17 +453,17 @@ impl + Send> Proof { .put_node(Node::from(node)) .map_err(ProofError::InvalidNode)?; let addr = new_node.as_ptr(); + match new_node.inner() { NodeType::Leaf(n) => { let cur_key = n.path().0.as_ref(); + if !key.contains_other(cur_key) { return Ok((addr, None, 0)); } - let subproof = SubProof { - encoded: n.data().to_vec(), - hash: None, - }; + let subproof = SubProof::Data(n.data().to_vec()); + Ok((addr, subproof.into(), cur_key.len())) } NodeType::Extension(n) => { @@ -512,12 +510,9 @@ fn locate_subproof( return Ok((None, Nibbles::<0>::new(&[]).into_iter())); } - let encoded = n.data().to_vec(); + let encoded: Vec = n.data().to_vec(); - let sub_proof = SubProof { - encoded, - hash: None, - }; + let sub_proof = SubProof::Data(encoded); Ok((sub_proof.into(), key_nibbles)) } @@ -536,9 +531,15 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } - NodeType::Branch(_) if key_nibbles.is_empty() => Err(ProofError::NoSuchNode), NodeType::Branch(n) => { - let index = key_nibbles.next().unwrap() as usize; + let Some(index) = key_nibbles.next().map(|nib| nib as usize) else { + let encoded = n.value; + + let sub_proof = encoded.map(|encoded| SubProof::Data(encoded.into_inner())); + + return Ok((sub_proof, key_nibbles)); + }; + // consume items returning the item at index let data = n.chd_encode()[index] .as_ref() @@ -549,24 +550,18 @@ fn locate_subproof( } } -fn generate_subproof(data: Vec) -> Result { - match data.len() { +fn generate_subproof(encoded: Vec) -> Result { + match encoded.len() { 0..=31 => { - let sub_hash = sha3::Keccak256::digest(&data).into(); - Ok(SubProof { - encoded: data, - hash: Some(sub_hash), - }) + let sub_hash = sha3::Keccak256::digest(&encoded).into(); + Ok(SubProof::Hash(sub_hash)) } 32 => { - let sub_hash: &[u8] = &data; + let sub_hash: &[u8] = &encoded; let sub_hash = sub_hash.try_into().unwrap(); - Ok(SubProof { - encoded: data, - hash: Some(sub_hash), - }) + Ok(SubProof::Hash(sub_hash)) } len => Err(ProofError::DecodeError(Box::new( diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index bebeb68618fb..0796d217eb57 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -200,23 +200,6 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { Ok(()) } -#[test] -fn test_one_element_proof() -> Result<(), DataStoreError> { - let items = vec![("k", "v")]; - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - let key = "k"; - - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - assert!(proof.0.len() == 1); - - let val = merkle.verify_proof(key, &proof)?; - assert!(val.is_some()); - assert_eq!(&val.unwrap(), b"v"); - - Ok(()) -} - #[test] fn test_proof() -> Result<(), DataStoreError> { let set = generate_random_data(500); From 628fb7ec713a1c838b7634d5e54bf65f9603b210 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 1 Dec 2023 16:52:40 -0500 Subject: [PATCH 0388/1053] Cleanup `insert_and_return_updates` method (#391) --- firewood/src/merkle.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fd36c93eeb95..aabba695ed65 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -405,27 +405,21 @@ impl + Send + Sync> Merkle { let mut deleted = Vec::new(); let mut parents = Vec::new(); - let mut next_node = None; - // we use Nibbles::<1> so that 1 zero nibble is at the front // this is for the sentinel node, which avoids moving the root // and always only has one child let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); + let mut node = self.get_node(root)?; + // walk down the merkle tree starting from next_node, currently the root // return None if the value is inserted let next_node_and_val = loop { - let mut node = next_node - .take() - .map(Ok) - .unwrap_or_else(|| self.get_node(root))?; - let node_ptr = node.as_ptr(); - let Some(current_nibble) = key_nibbles.next() else { break Some((node, val)); }; - let (node, next_node_ptr) = match &node.inner { + let (node_ref, next_node_ptr) = match &node.inner { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done @@ -481,6 +475,7 @@ impl + Send + Sync> Merkle { .chain(key_nibbles.clone()) .collect::>(); let n_path_len = n_path.len(); + let node_ptr = node.as_ptr(); if let Some(v) = self.split( node, @@ -509,8 +504,8 @@ impl + Send + Sync> Merkle { }; // push another parent, and follow the next pointer - parents.push((node, current_nibble)); - next_node = self.get_node(next_node_ptr)?.into(); + parents.push((node_ref, current_nibble)); + node = self.get_node(next_node_ptr)?; }; if let Some((mut node, val)) = next_node_and_val { From 95f37a22edf2feea44cbf79ad2b544a24de7dbcf Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 1 Dec 2023 16:59:17 -0500 Subject: [PATCH 0389/1053] Don't ignore results in tests (#393) --- firewood/tests/merkle.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 0796d217eb57..cd5c957b401d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -27,8 +27,7 @@ fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Cl } #[test] -#[allow(unused_must_use)] -fn test_root_hash_simple_insertions() { +fn test_root_hash_simple_insertions() -> Result<(), DataStoreError> { let items = vec![ ("do", "verb"), ("doe", "reindeer"), @@ -37,13 +36,15 @@ fn test_root_hash_simple_insertions() { ("horse", "stallion"), ("ddd", "ok"), ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000).unwrap(); - merkle.dump(); + let merkle = merkle_build_test(items, 0x10000, 0x10000)?; + + merkle.dump()?; + + Ok(()) } #[test] -#[allow(unused_must_use)] -fn test_root_hash_fuzz_insertions() { +fn test_root_hash_fuzz_insertions() -> Result<(), DataStoreError> { use rand::{rngs::StdRng, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); let max_len0 = 8; @@ -62,14 +63,17 @@ fn test_root_hash_fuzz_insertions() { .collect(); key }; + for _ in 0..10 { let mut items = Vec::new(); for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.push((keygen(), val)); } - merkle_build_test(items, 0x1000000, 0x1000000); + merkle_build_test(items, 0x1000000, 0x1000000)?; } + + Ok(()) } #[test] From dfb33e73f09dd2ae0bef3a039cb2eeb8b018d5e5 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:40:14 -0800 Subject: [PATCH 0390/1053] Pluggable encoding for nodes (#317) --- firewood/src/db.rs | 8 +- firewood/src/merkle.rs | 132 ++++++++++++++++++---- firewood/src/merkle/node.rs | 205 ++++++++++++++++++++++++++++++++++- firewood/src/merkle/proof.rs | 18 +-- firewood/src/merkle_util.rs | 17 +-- firewood/tests/merkle.rs | 4 +- 6 files changed, 337 insertions(+), 47 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f30e54c15525..6107d6cef5aa 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,7 +8,7 @@ pub use crate::{ }; use crate::{ file, - merkle::{Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, + merkle::{Bincode, Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, storage::{ buffer::{DiskBuffer, DiskBufferRequester}, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, @@ -275,7 +275,7 @@ impl Universe> { #[derive(Debug)] pub struct DbRev { header: shale::Obj, - merkle: Merkle, + merkle: Merkle, } #[async_trait] @@ -321,7 +321,7 @@ impl + Send + Sync> DbRev { pub fn stream( &self, start_key: Option, - ) -> Result, api::Error> { + ) -> Result, api::Error> { self.merkle .get_iter(start_key, self.header.kv_root) .map_err(|e| api::Error::InternalError(Box::new(e))) @@ -333,7 +333,7 @@ impl + Send + Sync> DbRev { Some(()) } - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { (&mut self.header, &mut self.merkle) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index aabba695ed65..64c685716b6c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,8 +6,8 @@ use crate::v2::api; use futures::{Stream, StreamExt, TryStreamExt}; use sha3::Digest; use std::{ - cmp::Ordering, collections::HashMap, future::ready, io::Write, iter::once, sync::OnceLock, - task::Poll, + cmp::Ordering, collections::HashMap, future::ready, io::Write, iter::once, marker::PhantomData, + sync::OnceLock, task::Poll, }; use thiserror::Error; @@ -15,7 +15,10 @@ mod node; pub mod proof; mod trie_hash; -pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath}; +pub use node::{ + BinarySerde, Bincode, BranchNode, Data, EncodedNode, EncodedNodeType, ExtNode, LeafNode, Node, + NodeType, PartialPath, +}; pub use proof::{Proof, ProofError}; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; @@ -39,6 +42,8 @@ pub enum MerkleError { UnsetInternal, #[error("error updating nodes: {0}")] WriteError(#[from] ObjWriteError), + #[error("merkle serde error: {0}")] + BinarySerdeError(String), } macro_rules! write_node { @@ -55,11 +60,12 @@ macro_rules! write_node { } #[derive(Debug)] -pub struct Merkle { +pub struct Merkle { store: Box, + phantom: PhantomData, } -impl> Merkle { +impl, T> Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) } @@ -73,11 +79,72 @@ impl> Merkle { } } -impl + Send + Sync> Merkle { +impl<'de, S, T> Merkle +where + S: ShaleStore + Send + Sync, + T: BinarySerde, + EncodedNode: serde::Serialize + serde::Deserialize<'de>, +{ pub fn new(store: Box) -> Self { - Self { store } + Self { + store, + phantom: PhantomData, + } + } + + // TODO: use `encode` / `decode` instead of `node.encode` / `node.decode` after extention node removal. + #[allow(dead_code)] + fn encode(&self, node: &ObjRef) -> Result, MerkleError> { + let encoded = match node.inner() { + NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), + NodeType::Branch(n) => { + // pair up DiskAddresses with encoded children and pick the right one + let encoded_children = n.chd().iter().zip(n.children_encoded.iter()); + let children = encoded_children + .map(|(child_addr, encoded_child)| { + child_addr + // if there's a child disk address here, get the encoded bytes + .map(|addr| self.get_node(addr).and_then(|node| self.encode(&node))) + // or look for the pre-fetched bytes + .or_else(|| encoded_child.as_ref().map(|child| Ok(child.to_vec()))) + .transpose() + }) + .collect::>>, MerkleError>>()? + .try_into() + .expect("MAX_CHILDREN will always be yielded"); + + EncodedNode::new(EncodedNodeType::Branch { + children, + value: n.value.clone(), + }) + } + + NodeType::Extension(_) => todo!(), + }; + + Bincode::serialize(&encoded).map_err(|e| MerkleError::BinarySerdeError(e.to_string())) } + #[allow(dead_code)] + fn decode(&self, buf: &'de [u8]) -> Result { + let encoded: EncodedNode = + T::deserialize(buf).map_err(|e| MerkleError::BinarySerdeError(e.to_string()))?; + + match encoded.node { + EncodedNodeType::Leaf(leaf) => Ok(NodeType::Leaf(leaf)), + EncodedNodeType::Branch { children, value } => { + let path = Vec::new().into(); + let value = value.map(|v| v.0); + Ok(NodeType::Branch( + BranchNode::new(path, [None; BranchNode::MAX_CHILDREN], value, *children) + .into(), + )) + } + } + } +} + +impl + Send + Sync, T> Merkle { pub fn init_root(&self) -> Result { self.store .put_item( @@ -1072,7 +1139,7 @@ impl + Send + Sync> Merkle { &mut self, key: K, root: DiskAddress, - ) -> Result>, MerkleError> { + ) -> Result>, MerkleError> { if root.is_null() { return Ok(None); } @@ -1151,7 +1218,7 @@ impl + Send + Sync> Merkle { &self, key: Option, root: DiskAddress, - ) -> Result, MerkleError> { + ) -> Result, MerkleError> { Ok(MerkleKeyValueStream { key_state: IteratorState::new(key), merkle_root: root, @@ -1269,13 +1336,15 @@ impl<'a> Default for IteratorState<'a> { /// This iterator is not fused. If you read past the None value, you start /// over at the beginning. If you need a fused iterator, consider using /// std::iter::fuse -pub struct MerkleKeyValueStream<'a, S> { +pub struct MerkleKeyValueStream<'a, S, T> { key_state: IteratorState<'a>, merkle_root: DiskAddress, - merkle: &'a Merkle, + merkle: &'a Merkle, } -impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyValueStream<'a, S> { +impl<'a, S: shale::ShaleStore + Send + Sync, T> Stream + for MerkleKeyValueStream<'a, S, T> +{ type Item = Result<(Vec, Vec), api::Error>; fn poll_next( @@ -1531,10 +1600,10 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { pub struct Ref<'a>(ObjRef<'a>); -pub struct RefMut<'a, S> { +pub struct RefMut<'a, S, T> { ptr: DiskAddress, parents: ParentAddresses, - merkle: &'a mut Merkle, + merkle: &'a mut Merkle, } impl<'a> std::ops::Deref for Ref<'a> { @@ -1548,8 +1617,8 @@ impl<'a> std::ops::Deref for Ref<'a> { } } -impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { - fn new(ptr: DiskAddress, parents: ParentAddresses, merkle: &'a mut Merkle) -> Self { +impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { + fn new(ptr: DiskAddress, parents: ParentAddresses, merkle: &'a mut Merkle) -> Self { Self { ptr, parents, @@ -1621,7 +1690,7 @@ mod tests { assert_eq!(n, nibbles); } - fn create_test_merkle() -> Merkle> { + fn create_test_merkle() -> Merkle, Bincode> { const RESERVED: usize = 0x1000; let mut dm = shale::cached::DynamicMem::new(0x10000, 0); @@ -1669,9 +1738,11 @@ mod tests { }) } + #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with value")] - #[test_case(branch(b"value".to_vec(), None); "branch without value")] + #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(b"value".to_vec(), None); "branch without chd")] + #[test_case(branch(Vec::new(), None); "branch without value and chd")] #[test_case(extension(vec![1, 2, 3], DiskAddress::null(), vec![4, 5].into()) ; "extension without child address")] fn encode_(node: Node) { let merkle = create_test_merkle(); @@ -1684,6 +1755,21 @@ mod tests { assert_eq!(encoded, new_node_encoded); } + #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] + #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] + #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(b"value".to_vec(), None); "branch without chd")] + #[test_case(branch(Vec::new(), None); "branch without value and chd")] + fn node_encode_decode_(node: Node) { + let merkle = create_test_merkle(); + let node_ref = merkle.put_node(node.clone()).unwrap(); + let encoded = merkle.encode(&node_ref).unwrap(); + let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); + let new_node_hash = new_node.get_root_hash(merkle.store.as_ref()); + let expected_hash = node.get_root_hash(merkle.store.as_ref()); + assert_eq!(new_node_hash, expected_hash); + } + #[test] fn insert_and_retrieve_one() { let key = b"hello"; @@ -1793,7 +1879,7 @@ mod tests { #[test] fn remove_many() { - let mut merkle: Merkle> = create_test_merkle(); + let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); // insert values @@ -1834,7 +1920,7 @@ mod tests { #[tokio::test] async fn empty_range_proof() { - let merkle: Merkle> = create_test_merkle(); + let merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); assert!(merkle @@ -1846,7 +1932,7 @@ mod tests { #[tokio::test] async fn full_range_proof() { - let mut merkle: Merkle> = create_test_merkle(); + let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { @@ -1874,7 +1960,7 @@ mod tests { async fn single_value_range_proof() { const RANDOM_KEY: u8 = 42; - let mut merkle: Merkle> = create_test_merkle(); + let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index ffa466623c65..5e6e9bc2d3e8 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,15 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; +use crate::{ + merkle::from_nibbles, + shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}, +}; use bincode::{Error, Options}; use bitflags::bitflags; use enum_as_inner::EnumAsInner; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, ser::SerializeSeq, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; use std::{ fmt::Debug, io::{Cursor, Write}, + marker::PhantomData, mem::size_of, sync::{ atomic::{AtomicBool, Ordering}, @@ -465,6 +469,203 @@ impl Storable for Node { } } +pub struct EncodedNode { + pub(crate) node: EncodedNodeType, + pub(crate) phantom: PhantomData, +} + +impl EncodedNode { + pub fn new(node: EncodedNodeType) -> Self { + Self { + node, + phantom: PhantomData, + } + } +} +pub enum EncodedNodeType { + Leaf(LeafNode), + Branch { + children: Box<[Option>; BranchNode::MAX_CHILDREN]>, + value: Option, + }, +} + +// Note that the serializer passed in should always be the same type as T in EncodedNode. +impl Serialize for EncodedNode { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::Error; + + match &self.node { + EncodedNodeType::Leaf(n) => { + let list = [ + Encoded::Raw(from_nibbles(&n.path.encode(true)).collect()), + Encoded::Raw(n.data.to_vec()), + ]; + let mut seq = serializer.serialize_seq(Some(list.len()))?; + for e in list { + seq.serialize_element(&e)?; + } + seq.end() + } + EncodedNodeType::Branch { children, value } => { + let mut list = <[Encoded>; BranchNode::MAX_CHILDREN + 1]>::default(); + + for (i, c) in children + .iter() + .enumerate() + .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))) + { + if c.len() >= TRIE_HASH_LEN { + let serialized_hash = Bincode::serialize(&Keccak256::digest(c).to_vec()) + .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; + list[i] = Encoded::Data(serialized_hash); + } else { + list[i] = Encoded::Raw(c.to_vec()); + } + } + if let Some(Data(val)) = &value { + let serialized_val = Bincode::serialize(val) + .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; + list[BranchNode::MAX_CHILDREN] = Encoded::Data(serialized_val); + } + + let mut seq = serializer.serialize_seq(Some(list.len()))?; + for e in list { + seq.serialize_element(&e)?; + } + seq.end() + } + } + } +} + +impl<'de> Deserialize<'de> for EncodedNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let items: Vec>> = Deserialize::deserialize(deserializer)?; + let len = items.len(); + match len { + LEAF_NODE_SIZE => { + let mut items = items.into_iter(); + let Some(Encoded::Raw(path)) = items.next() else { + return Err(D::Error::custom( + "incorrect encoded type for leaf node path", + )); + }; + let Some(Encoded::Raw(data)) = items.next() else { + return Err(D::Error::custom( + "incorrect encoded type for leaf node data", + )); + }; + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + let node = EncodedNodeType::Leaf(LeafNode { + path, + data: Data(data), + }); + Ok(Self { + node, + phantom: PhantomData, + }) + } + BranchNode::MSIZE => { + let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + let mut value: Option = Default::default(); + let len = items.len(); + + for (i, chd) in items.into_iter().enumerate() { + if i == len - 1 { + let data = match chd { + Encoded::Raw(data) => Err(D::Error::custom(format!( + "incorrect encoded type for branch node value {:?}", + data + )))?, + Encoded::Data(data) => Bincode::deserialize(data.as_ref()) + .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, + }; + // Extract the value of the branch node and set to None if it's an empty Vec + value = Some(Data(data)).filter(|data| !data.is_empty()); + } else { + let chd = match chd { + Encoded::Raw(chd) => chd, + Encoded::Data(chd) => Bincode::deserialize(chd.as_ref()) + .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, + }; + children[i] = Some(chd).filter(|chd| !chd.is_empty()); + } + } + let node = EncodedNodeType::Branch { + children: children.into(), + value, + }; + Ok(Self { + node, + phantom: PhantomData, + }) + } + size => Err(D::Error::custom(format!("invalid size: {size}"))), + } + } +} + +pub trait BinarySerde { + type SerializeError: serde::ser::Error; + type DeserializeError: serde::de::Error; + + fn new() -> Self; + + fn serialize(t: &T) -> Result, Self::SerializeError> + where + Self: Sized, + { + Self::new().serialize_impl(t) + } + + fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result + where + Self: Sized, + { + Self::new().deserialize_impl(bytes) + } + + fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError>; + fn deserialize_impl<'de, T: Deserialize<'de>>( + &self, + bytes: &'de [u8], + ) -> Result; +} + +pub struct Bincode(pub bincode::DefaultOptions); + +impl Debug for Bincode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "[bincode::DefaultOptions]") + } +} + +impl BinarySerde for Bincode { + type SerializeError = bincode::Error; + type DeserializeError = Self::SerializeError; + + fn new() -> Self { + Self(bincode::DefaultOptions::new()) + } + + fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError> { + self.0.serialize(t) + } + + fn deserialize_impl<'de, T: Deserialize<'de>>( + &self, + bytes: &'de [u8], + ) -> Result { + self.0.deserialize(bytes) + } +} + #[cfg(test)] pub(super) mod tests { use std::array::from_fn; diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 8caba21f9e0f..aafc2360aa8d 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -19,7 +19,7 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; -use super::TRIE_HASH_LEN; +use super::{BinarySerde, TRIE_HASH_LEN}; #[derive(Debug, Error)] pub enum ProofError { @@ -253,11 +253,11 @@ impl + Send> Proof { /// necessary nodes will be resolved and leave the remaining as hashnode. /// /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path, S: ShaleStore + Send + Sync>( + fn proof_to_path, S: ShaleStore + Send + Sync, T: BinarySerde>( &self, key: KV, root_hash: [u8; 32], - merkle_setup: &mut MerkleSetup, + merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, ) -> Result>, ProofError> { // Start with the sentinel root @@ -441,9 +441,9 @@ impl + Send> Proof { /// /// * `end_node` - A boolean indicates whether this is the end node to decode, thus no `key` /// to be present. - fn decode_node + Send + Sync>( + fn decode_node + Send + Sync, T: BinarySerde>( &self, - merkle: &Merkle, + merkle: &Merkle, key: &[u8], buf: &[u8], end_node: bool, @@ -583,8 +583,8 @@ fn generate_subproof(encoded: Vec) -> Result { // // The return value indicates if the fork point is root node. If so, unset the // entire trie. -fn unset_internal, S: ShaleStore + Send + Sync>( - merkle_setup: &mut MerkleSetup, +fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySerde>( + merkle_setup: &mut MerkleSetup, left: K, right: K, ) -> Result { @@ -824,8 +824,8 @@ fn unset_internal, S: ShaleStore + Send + Sync>( // keep the entire branch and return. // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -fn unset_node_ref, S: ShaleStore + Send + Sync>( - merkle: &Merkle, +fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySerde>( + merkle: &Merkle, parent: DiskAddress, node: Option, key: K, diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index c307f01f450f..fe63a0952e6e 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::{Merkle, Node, Proof, ProofError, Ref, RefMut, TrieHash}; +use crate::merkle::{BinarySerde, Bincode, Merkle, Node, Proof, ProofError, Ref, RefMut, TrieHash}; use crate::shale::{ self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, StoredView, @@ -31,12 +31,12 @@ pub enum DataStoreError { ProofEmptyKeyValuesError, } -pub struct MerkleSetup { +pub struct MerkleSetup { root: DiskAddress, - merkle: Merkle, + merkle: Merkle, } -impl + Send + Sync> MerkleSetup { +impl + Send + Sync, T: BinarySerde> MerkleSetup { pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { self.merkle .insert(key, val, self.root) @@ -55,7 +55,10 @@ impl + Send + Sync> MerkleSetup { .map_err(|_err| DataStoreError::GetError) } - pub fn get_mut>(&mut self, key: K) -> Result>, DataStoreError> { + pub fn get_mut>( + &mut self, + key: K, + ) -> Result>, DataStoreError> { self.merkle .get_mut(key, self.root) .map_err(|_err| DataStoreError::GetError) @@ -65,7 +68,7 @@ impl + Send + Sync> MerkleSetup { self.root } - pub fn get_merkle_mut(&mut self) -> &mut Merkle { + pub fn get_merkle_mut(&mut self) -> &mut Merkle { &mut self.merkle } @@ -116,7 +119,7 @@ impl + Send + Sync> MerkleSetup { pub fn new_merkle( meta_size: u64, compact_size: u64, -) -> MerkleSetup> { +) -> MerkleSetup, Bincode> { const RESERVED: usize = 0x1000; assert!(meta_size as usize > RESERVED); assert!(compact_size as usize > RESERVED); diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index cd5c957b401d..f9f2a4209286 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - merkle::{Node, Proof, ProofError}, + merkle::{Bincode, Node, Proof, ProofError}, merkle_util::{new_merkle, DataStoreError, MerkleSetup}, // TODO: we should not be using shale from an integration test shale::{cached::DynamicMem, compact::CompactSpace}, @@ -16,7 +16,7 @@ fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Cl items: Vec<(K, V)>, meta_size: u64, compact_size: u64, -) -> Result, DataStoreError> { +) -> Result, DataStoreError> { let mut merkle = new_merkle(meta_size, compact_size); for (k, v) in items.iter() { From af8ed64e07d1b7cbf7f4e9d8505d6c340ae742b0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 4 Dec 2023 10:47:03 -0800 Subject: [PATCH 0391/1053] Prefer bytemuck::Pod over Uninit (#405) --- firewood/src/db.rs | 6 +++--- firewood/src/shale/compact.rs | 3 ++- firewood/src/shale/disk_address.rs | 6 +++--- growth-ring/src/wal.rs | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6107d6cef5aa..282cf5a18517 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -26,7 +26,7 @@ use crate::{ }, }; use async_trait::async_trait; -use bytemuck::{cast_slice, AnyBitPattern}; +use bytemuck::{cast_slice, Pod, Zeroable}; use metered::metered; use parking_lot::{Mutex, RwLock}; @@ -105,7 +105,7 @@ impl Error for DbError {} /// correct parameters are used when the DB is opened later (the parameters here will override the /// parameters in [DbConfig] if the DB already exists). #[repr(C)] -#[derive(Debug, Clone, Copy, AnyBitPattern, bytemuck::NoUninit)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] struct DbParams { magic: [u8; 16], meta_file_nbit: u64, @@ -188,7 +188,7 @@ fn get_sub_universe_from_empty_delta( /// mutable DB-wide metadata, it keeps track of the root of the top-level trie. #[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::NoUninit)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] struct DbHeader { kv_root: DiskAddress, } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 96df8c7d2673..8d8b0fac37e4 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -5,6 +5,7 @@ use crate::shale::ObjCache; use super::disk_address::DiskAddress; use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; +use bytemuck::{Pod, Zeroable}; use std::fmt::Debug; use std::io::{Cursor, Write}; use std::num::NonZeroUsize; @@ -132,7 +133,7 @@ impl Storable for CompactDescriptor { } #[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::NoUninit)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct CompactSpaceHeader { meta_space_tail: DiskAddress, compact_space_tail: DiskAddress, diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 5b75b98fbf19..332c88bf4e72 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -6,13 +6,13 @@ use std::mem::size_of; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; -use bytemuck::NoUninit; +use bytemuck::{Pod, Zeroable}; use crate::shale::{CachedStore, ShaleError, Storable}; /// The virtual disk address of an object -#[repr(C)] -#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq, NoUninit)] +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq, Pod, Zeroable)] pub struct DiskAddress(pub Option); impl Deref for DiskAddress { diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 188a7f898a6b..4140c6c6cbad 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use async_trait::async_trait; -use bytemuck::{cast_slice, AnyBitPattern, NoUninit}; +use bytemuck::{cast_slice, Pod, Zeroable}; use futures::{ future::{self, FutureExt, TryFutureExt}, stream::StreamExt, @@ -35,7 +35,7 @@ enum WalRingType { } #[repr(C, packed)] -#[derive(NoUninit, Copy, Clone, Debug, AnyBitPattern)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] struct WalRingBlob { counter: u32, crc32: u32, @@ -113,7 +113,7 @@ const fn counter_lt(a: u32, b: u32) -> bool { } #[repr(transparent)] -#[derive(Debug, Clone, Copy, AnyBitPattern, NoUninit)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] struct Header { /// all preceding files ( Date: Mon, 4 Dec 2023 10:53:37 -0800 Subject: [PATCH 0392/1053] Rkuris/deny new unsafe growth ring (#401) Signed-off-by: Ron Kuris --- growth-ring/Cargo.toml | 3 +++ growth-ring/src/wal.rs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index fe056d62cb0a..3985f40b5e71 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -33,6 +33,9 @@ name = "growthring" path = "src/lib.rs" crate-type = ["dylib", "rlib", "staticlib"] +[lints.rust] +unsafe_code = "deny" + [lints.clippy] unwrap_used = "warn" indexing_slicing = "warn" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 4140c6c6cbad..d509bbfe99d6 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -313,6 +313,7 @@ impl> WalFilePool { async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { let handle = match self.handle_used.borrow_mut().entry(fid) { + #[allow(unsafe_code)] hash_map::Entry::Vacant(e) => unsafe { &*(*e.insert(UnsafeCell::new((h, 1))).get()).0 }, @@ -325,6 +326,7 @@ impl> WalFilePool { wal_file: PhantomData, }) } else { + #[allow(unsafe_code)] let v = unsafe { &mut *match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Occupied(e) => e.into_mut(), @@ -348,6 +350,7 @@ impl> WalFilePool { fn release_file(&self, fid: WalFileId) { match self.handle_used.borrow_mut().entry(fid) { hash_map::Entry::Occupied(e) => { + #[allow(unsafe_code)] let v = unsafe { &mut *e.get().get() }; v.1 -= 1; if v.1 == 0 { @@ -384,6 +387,7 @@ impl> WalFilePool { let mut alloc_start = writes[0].0 & (self.file_size - 1); #[allow(clippy::indexing_slicing)] let mut alloc_end = alloc_start + writes[0].1.len() as u64; + #[allow(unsafe_code)] let last_write = unsafe { std::mem::replace(&mut *self.last_write.get(), std::mem::MaybeUninit::uninit()) .assume_init() @@ -435,6 +439,7 @@ impl> WalFilePool { res.push(Box::pin(w) as Pin + 'a>>) } + #[allow(unsafe_code)] unsafe { (*self.last_write.get()) = MaybeUninit::new(std::mem::transmute::< Pin + 'a>>, @@ -450,6 +455,7 @@ impl> WalFilePool { state: &mut WalState, keep_nrecords: u32, ) -> impl Future> + 'a { + #[allow(unsafe_code)] let last_peel = unsafe { std::mem::replace(&mut *self.last_peel.get(), std::mem::MaybeUninit::uninit()) .assume_init() @@ -481,6 +487,7 @@ impl> WalFilePool { } .shared(); + #[allow(unsafe_code)] unsafe { (*self.last_peel.get()) = MaybeUninit::new(std::mem::transmute( Box::pin(p.clone()) as Pin + 'a>> @@ -557,6 +564,7 @@ impl> WalWriter { let d = remain - msize; let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; + #[allow(unsafe_code)] let blob = unsafe { #[allow(clippy::indexing_slicing)] &mut *self.block_buffer[bbuff_cur as usize..] From 7e9fa1947a30dc34dd67f027e538aab91f55bcc8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 4 Dec 2023 15:26:59 -0800 Subject: [PATCH 0393/1053] Rename rpc -> grpc-testtool (#407) --- .github/check-license-headers.yaml | 4 ++-- Cargo.toml | 2 +- {rpc => grpc-testtool}/Cargo.toml | 0 {rpc => grpc-testtool}/build.rs | 0 {rpc => grpc-testtool}/proto/rpcdb/rpcdb.proto | 0 {rpc => grpc-testtool}/proto/sync/sync.proto | 0 {rpc => grpc-testtool}/src/bin/client.rs | 0 {rpc => grpc-testtool}/src/bin/server.rs | 0 {rpc => grpc-testtool}/src/lib.rs | 0 {rpc => grpc-testtool}/src/service.rs | 0 {rpc => grpc-testtool}/src/service/database.rs | 0 {rpc => grpc-testtool}/src/service/db.rs | 0 12 files changed, 3 insertions(+), 3 deletions(-) rename {rpc => grpc-testtool}/Cargo.toml (100%) rename {rpc => grpc-testtool}/build.rs (100%) rename {rpc => grpc-testtool}/proto/rpcdb/rpcdb.proto (100%) rename {rpc => grpc-testtool}/proto/sync/sync.proto (100%) rename {rpc => grpc-testtool}/src/bin/client.rs (100%) rename {rpc => grpc-testtool}/src/bin/server.rs (100%) rename {rpc => grpc-testtool}/src/lib.rs (100%) rename {rpc => grpc-testtool}/src/service.rs (100%) rename {rpc => grpc-testtool}/src/service/database.rs (100%) rename {rpc => grpc-testtool}/src/service/db.rs (100%) diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index e4e9826b2398..7f3076a2dd7a 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -8,7 +8,7 @@ "*/LICENSE*", "LICENSE.md", "RELEASE.md", - "rpc/**", + "grpc-testtool/**", "README*", "**/README*", "Cargo.toml", @@ -25,7 +25,7 @@ "*/LICENSE*", "LICENSE.md", "RELEASE.md", - "rpc/**", + "grpc-testtool/**", "README*", "**/README*", "Cargo.toml", diff --git a/Cargo.toml b/Cargo.toml index 942c4d3c06e7..018f2a39f377 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "fwdctl", "growth-ring", "libaio", - "rpc", + "grpc-testtool", ] resolver = "2" diff --git a/rpc/Cargo.toml b/grpc-testtool/Cargo.toml similarity index 100% rename from rpc/Cargo.toml rename to grpc-testtool/Cargo.toml diff --git a/rpc/build.rs b/grpc-testtool/build.rs similarity index 100% rename from rpc/build.rs rename to grpc-testtool/build.rs diff --git a/rpc/proto/rpcdb/rpcdb.proto b/grpc-testtool/proto/rpcdb/rpcdb.proto similarity index 100% rename from rpc/proto/rpcdb/rpcdb.proto rename to grpc-testtool/proto/rpcdb/rpcdb.proto diff --git a/rpc/proto/sync/sync.proto b/grpc-testtool/proto/sync/sync.proto similarity index 100% rename from rpc/proto/sync/sync.proto rename to grpc-testtool/proto/sync/sync.proto diff --git a/rpc/src/bin/client.rs b/grpc-testtool/src/bin/client.rs similarity index 100% rename from rpc/src/bin/client.rs rename to grpc-testtool/src/bin/client.rs diff --git a/rpc/src/bin/server.rs b/grpc-testtool/src/bin/server.rs similarity index 100% rename from rpc/src/bin/server.rs rename to grpc-testtool/src/bin/server.rs diff --git a/rpc/src/lib.rs b/grpc-testtool/src/lib.rs similarity index 100% rename from rpc/src/lib.rs rename to grpc-testtool/src/lib.rs diff --git a/rpc/src/service.rs b/grpc-testtool/src/service.rs similarity index 100% rename from rpc/src/service.rs rename to grpc-testtool/src/service.rs diff --git a/rpc/src/service/database.rs b/grpc-testtool/src/service/database.rs similarity index 100% rename from rpc/src/service/database.rs rename to grpc-testtool/src/service/database.rs diff --git a/rpc/src/service/db.rs b/grpc-testtool/src/service/db.rs similarity index 100% rename from rpc/src/service/db.rs rename to grpc-testtool/src/service/db.rs From 68e62dec0fc0cb435cfc3ec9ab0960b0960a8630 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 5 Dec 2023 12:30:41 -0800 Subject: [PATCH 0394/1053] Bring grpc-testtool up to the lint standard (#409) --- grpc-testtool/Cargo.toml | 9 +++++++++ grpc-testtool/src/bin/server.rs | 4 ++-- grpc-testtool/src/lib.rs | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 3bec6bb0e844..ff8143345a52 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -24,3 +24,12 @@ tonic = { version = "0.10.0", features = ["tls"] } [build-dependencies] tonic-build = "0.10.0" + +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" +missing_const_for_fn = "warn" diff --git a/grpc-testtool/src/bin/server.rs b/grpc-testtool/src/bin/server.rs index 80ef8039593b..372e586206df 100644 --- a/grpc-testtool/src/bin/server.rs +++ b/grpc-testtool/src/bin/server.rs @@ -10,7 +10,8 @@ use tonic::transport::Server; // TODO: use clap to parse command line input to run the server #[tokio::main] -async fn main() { +async fn main() -> Result<(), tonic::transport::Error> { + #[allow(clippy::unwrap_used)] let addr = "[::1]:10000".parse().unwrap(); println!("Database-Server listening on: {}", addr); @@ -23,5 +24,4 @@ async fn main() { .add_service(SyncServer::from_arc(svc)) .serve(addr) .await - .unwrap(); } diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 7a06133519ea..5521e7aca417 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -2,10 +2,14 @@ // See the file LICENSE.md for licensing terms. pub mod sync { + #![allow(clippy::unwrap_used)] + #![allow(clippy::missing_const_for_fn)] tonic::include_proto!("sync"); } pub mod rpcdb { + #![allow(clippy::unwrap_used)] + #![allow(clippy::missing_const_for_fn)] tonic::include_proto!("rpcdb"); } From 068beda1a9737c2ab73fa2c4fbf1690186c15160 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Dec 2023 13:19:17 -0500 Subject: [PATCH 0395/1053] Rkuris/grpc make process server (#413) --- grpc-testtool/Cargo.toml | 3 +- grpc-testtool/build.rs | 6 + .../proto/io/prometheus/client/metrics.proto | 154 ++++++++++++++++++ .../proto/process-server/process-server.proto | 16 ++ .../src/bin/{server.rs => process-server.rs} | 0 grpc-testtool/src/lib.rs | 16 ++ 6 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 grpc-testtool/proto/io/prometheus/client/metrics.proto create mode 100644 grpc-testtool/proto/process-server/process-server.proto rename grpc-testtool/src/bin/{server.rs => process-server.rs} (100%) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index ff8143345a52..106e55fd6cfe 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] -name = "server" +name = "process-server" test = false bench = false @@ -21,6 +21,7 @@ prost = "0.12.0" thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.10.0", features = ["tls"] } +prost-types = "0.12.0" [build-dependencies] tonic-build = "0.10.0" diff --git a/grpc-testtool/build.rs b/grpc-testtool/build.rs index beb3f015046a..5246f5c57166 100644 --- a/grpc-testtool/build.rs +++ b/grpc-testtool/build.rs @@ -4,6 +4,12 @@ fn main() -> Result<(), Box> { tonic_build::compile_protos("proto/sync/sync.proto")?; tonic_build::compile_protos("proto/rpcdb/rpcdb.proto")?; + tonic_build::compile_protos("proto/io/prometheus/client/metrics.proto")?; + + tonic_build::configure().compile( + &["proto/process-server/process-server.proto"], // protos + &["proto"], // includes + )?; Ok(()) } diff --git a/grpc-testtool/proto/io/prometheus/client/metrics.proto b/grpc-testtool/proto/io/prometheus/client/metrics.proto new file mode 100644 index 000000000000..31595d8e73dd --- /dev/null +++ b/grpc-testtool/proto/io/prometheus/client/metrics.proto @@ -0,0 +1,154 @@ +// Copyright 2013 Prometheus Team +// 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. + +syntax = "proto2"; + +package io.prometheus.client; +option java_package = "io.prometheus.client"; +option go_package = "github.com/prometheus/client_model/go;io_prometheus_client"; + +import "google/protobuf/timestamp.proto"; + +message LabelPair { + optional string name = 1; + optional string value = 2; +} + +enum MetricType { + // COUNTER must use the Metric field "counter". + COUNTER = 0; + // GAUGE must use the Metric field "gauge". + GAUGE = 1; + // SUMMARY must use the Metric field "summary". + SUMMARY = 2; + // UNTYPED must use the Metric field "untyped". + UNTYPED = 3; + // HISTOGRAM must use the Metric field "histogram". + HISTOGRAM = 4; + // GAUGE_HISTOGRAM must use the Metric field "histogram". + GAUGE_HISTOGRAM = 5; +} + +message Gauge { + optional double value = 1; +} + +message Counter { + optional double value = 1; + optional Exemplar exemplar = 2; + + optional google.protobuf.Timestamp created_timestamp = 3; +} + +message Quantile { + optional double quantile = 1; + optional double value = 2; +} + +message Summary { + optional uint64 sample_count = 1; + optional double sample_sum = 2; + repeated Quantile quantile = 3; + + optional google.protobuf.Timestamp created_timestamp = 4; +} + +message Untyped { + optional double value = 1; +} + +message Histogram { + optional uint64 sample_count = 1; + optional double sample_count_float = 4; // Overrides sample_count if > 0. + optional double sample_sum = 2; + // Buckets for the conventional histogram. + repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional. + + optional google.protobuf.Timestamp created_timestamp = 15; + + // Everything below here is for native histograms (also known as sparse histograms). + // Native histograms are an experimental feature without stability guarantees. + + // schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8. + // They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and + // then each power of two is divided into 2^n logarithmic buckets. + // Or in other words, each bucket boundary is the previous boundary times 2^(2^-n). + // In the future, more bucket schemas may be added using numbers < -4 or > 8. + optional sint32 schema = 5; + optional double zero_threshold = 6; // Breadth of the zero bucket. + optional uint64 zero_count = 7; // Count in zero bucket. + optional double zero_count_float = 8; // Overrides sb_zero_count if > 0. + + // Negative buckets for the native histogram. + repeated BucketSpan negative_span = 9; + // Use either "negative_delta" or "negative_count", the former for + // regular histograms with integer counts, the latter for float + // histograms. + repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). + repeated double negative_count = 11; // Absolute count of each bucket. + + // Positive buckets for the native histogram. + // Use a no-op span (offset 0, length 0) for a native histogram without any + // observations yet and with a zero_threshold of 0. Otherwise, it would be + // indistinguishable from a classic histogram. + repeated BucketSpan positive_span = 12; + // Use either "positive_delta" or "positive_count", the former for + // regular histograms with integer counts, the latter for float + // histograms. + repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). + repeated double positive_count = 14; // Absolute count of each bucket. +} + +// A Bucket of a conventional histogram, each of which is treated as +// an individual counter-like time series by Prometheus. +message Bucket { + optional uint64 cumulative_count = 1; // Cumulative in increasing order. + optional double cumulative_count_float = 4; // Overrides cumulative_count if > 0. + optional double upper_bound = 2; // Inclusive. + optional Exemplar exemplar = 3; +} + +// A BucketSpan defines a number of consecutive buckets in a native +// histogram with their offset. Logically, it would be more +// straightforward to include the bucket counts in the Span. However, +// the protobuf representation is more compact in the way the data is +// structured here (with all the buckets in a single array separate +// from the Spans). +message BucketSpan { + optional sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative). + optional uint32 length = 2; // Length of consecutive buckets. +} + +message Exemplar { + repeated LabelPair label = 1; + optional double value = 2; + optional google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style. +} + +message Metric { + repeated LabelPair label = 1; + optional Gauge gauge = 2; + optional Counter counter = 3; + optional Summary summary = 4; + optional Untyped untyped = 5; + optional Histogram histogram = 7; + optional int64 timestamp_ms = 6; +} + +message MetricFamily { + optional string name = 1; + optional string help = 2; + optional MetricType type = 3; + repeated Metric metric = 4; + optional string unit = 5; +} diff --git a/grpc-testtool/proto/process-server/process-server.proto b/grpc-testtool/proto/process-server/process-server.proto new file mode 100644 index 000000000000..494ca694c2ac --- /dev/null +++ b/grpc-testtool/proto/process-server/process-server.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package process; + +import "google/protobuf/empty.proto"; +import "io/prometheus/client/metrics.proto"; + +option go_package = "github.com/ava-labs/merkledb-tester/proto/pb/process"; + +service ProcessServerService { + rpc Metrics(google.protobuf.Empty) returns (MetricsResponse); +} + +message MetricsResponse { + repeated io.prometheus.client.MetricFamily metric_families = 1; +} diff --git a/grpc-testtool/src/bin/server.rs b/grpc-testtool/src/bin/process-server.rs similarity index 100% rename from grpc-testtool/src/bin/server.rs rename to grpc-testtool/src/bin/process-server.rs diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 5521e7aca417..8189048b0101 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -1,6 +1,16 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +pub mod io { + pub mod prometheus { + pub mod client { + #![allow(clippy::unwrap_used)] + #![allow(clippy::missing_const_for_fn)] + tonic::include_proto!("io.prometheus.client"); + } + } +} + pub mod sync { #![allow(clippy::unwrap_used)] #![allow(clippy::missing_const_for_fn)] @@ -13,6 +23,12 @@ pub mod rpcdb { tonic::include_proto!("rpcdb"); } +pub mod process_server { + #![allow(clippy::unwrap_used)] + #![allow(clippy::missing_const_for_fn)] + tonic::include_proto!("process"); +} + pub mod service; pub use service::Database as DatabaseService; From d7b157cbcd0f775db894cdac2e96564232c71c88 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Dec 2023 14:09:01 -0500 Subject: [PATCH 0396/1053] More improvements for grpc service (#415) --- grpc-testtool/Cargo.toml | 5 ++ grpc-testtool/README.md | 21 ++++++ grpc-testtool/src/bin/process-server.rs | 89 ++++++++++++++++++++++--- grpc-testtool/src/service.rs | 4 ++ grpc-testtool/src/service/process.rs | 14 ++++ 5 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 grpc-testtool/README.md create mode 100644 grpc-testtool/src/service/process.rs diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 106e55fd6cfe..5d3bd3d681e0 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -22,6 +22,11 @@ thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.10.0", features = ["tls"] } prost-types = "0.12.0" +clap = { version = "4.4.11", features = ["derive"] } +tempdir = "0.3.7" +log = "0.4.20" +env_logger = "0.10.1" +chrono = "0.4.31" [build-dependencies] tonic-build = "0.10.0" diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md new file mode 100644 index 000000000000..4733395b5c7c --- /dev/null +++ b/grpc-testtool/README.md @@ -0,0 +1,21 @@ +# Firewood process-server implementation + +This service is a plug-in for the test tool orchestrator (currently closed source). +The test tool is used for both performance and correctness testing, especially for the syncer. + +```mermaid +sequenceDiagram +Orchestrator->>ProcessServer: Startup (via command line) +ProcessServer->>Firewood: Open or create database +Orchestrator->>+ProcessServer: GRPC request +ProcessServer->>+Firewood: Native API Call +Firewood->>-ProcessServer: Response +ProcessServer->>-Orchestrator: Response +``` + +There are 3 RPC specs that must be implemented: + +1. The rpcdb proto, which supports simple operations like Has, Get, Put, Delete, and some iterators. +2. The sync proto, which supports retrieving range and change proofs +3. The process-server proto, which currently only retrieves metrics + diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index 372e586206df..2128374a292f 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -1,27 +1,96 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use chrono::Local; +use clap::Parser; +use env_logger::{Builder, Target}; +use log::{info, LevelFilter}; use rpc::{ + process_server::process_server_service_server::ProcessServerServiceServer, rpcdb::database_server::DatabaseServer as RpcServer, sync::db_server::DbServer as SyncServer, DatabaseService, }; -use std::sync::Arc; +use std::{ + error::Error, + io::Write, + net::{IpAddr::V4, Ipv4Addr}, + path::PathBuf, + sync::Arc, +}; +use tempdir::TempDir; use tonic::transport::Server; -// TODO: use clap to parse command line input to run the server +/// A GRPC server that can be plugged into the generic testing framework for merkledb + +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +struct Opts { + #[arg(short = 'g', long, default_value_t = 10000)] + //// Port gRPC server listens on + grpc_port: u16, + + #[arg(short = 'G', long = "gatewayPort", default_value_t = 10001)] + /// Port gRPC gateway server, which HTTP requests can be made to + _gateway_port: u16, + + #[arg(short = 'l', long = "logDir", default_value = temp_path())] + log_dir: PathBuf, + + #[arg(short = 'L', long = "LogLevelStr", default_value_t = LevelFilter::Info)] + log_level: LevelFilter, + + #[arg(short = 'b', long = "branch-factor")] + _branch_factor: Option, +} + +fn temp_path() -> clap::builder::OsStr { + let tmpdir = TempDir::new("process-server").expect("unable to create temporary directory"); + // we leak because we want the temporary directory to stick around forever (emulating what happens in golang) + Box::leak(Box::new(tmpdir.into_path())).as_os_str().into() +} + #[tokio::main] -async fn main() -> Result<(), tonic::transport::Error> { - #[allow(clippy::unwrap_used)] - let addr = "[::1]:10000".parse().unwrap(); +async fn main() -> Result<(), Box> { + // parse command line options + let args = Opts::parse(); + + // set up the log file + let logfile = std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(args.log_dir.join("server.log"))?; + + // configure the logger + Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{} [{}] - {}", + Local::now().format("%Y-%m-%dT%H:%M:%S"), + record.level(), + record.args() + ) + }) + .format_target(true) + .target(Target::Pipe(Box::new(logfile))) + .filter(None, args.log_level) + .init(); - println!("Database-Server listening on: {}", addr); + // log to the file and to stderr + eprintln!("Database-Server listening on: {}", args.grpc_port); + info!("Starting up: Listening on {}", args.grpc_port); let svc = Arc::new(DatabaseService::default()); // TODO: graceful shutdown - Server::builder() + Ok(Server::builder() .add_service(RpcServer::from_arc(svc.clone())) - .add_service(SyncServer::from_arc(svc)) - .serve(addr) - .await + .add_service(SyncServer::from_arc(svc.clone())) + .add_service(ProcessServerServiceServer::from_arc(svc.clone())) + .serve(std::net::SocketAddr::new( + V4(Ipv4Addr::LOCALHOST), + args.grpc_port, + )) + .await?) } diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index 256484de9ff9..cf79cebdab7d 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -17,6 +17,7 @@ use tonic::Status; pub mod database; pub mod db; +pub mod process; trait IntoStatusResultExt { fn into_status_result(self) -> Result; @@ -81,3 +82,6 @@ impl Iterators { self.map.remove(&id); } } + +#[derive(Debug)] +pub struct ProcessServer; diff --git a/grpc-testtool/src/service/process.rs b/grpc-testtool/src/service/process.rs new file mode 100644 index 000000000000..b98e7f7589de --- /dev/null +++ b/grpc-testtool/src/service/process.rs @@ -0,0 +1,14 @@ +use tonic::{async_trait, Request, Response, Status}; + +use crate::process_server::process_server_service_server::ProcessServerService as ProcessTrait; + +use crate::process_server::MetricsResponse; + +#[async_trait] +impl ProcessTrait for super::Database { + async fn metrics(&self, _request: Request<()>) -> Result, Status> { + Err(Status::unimplemented("Metrics not yet supported")) + // TODO: collect the metrics here + // Ok(Response::new(MetricsResponse::default())) + } +} From 9c2d9a7298abde49b01dae4ff454c7ec41b7ef2c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Dec 2023 14:09:37 -0500 Subject: [PATCH 0397/1053] Update process server proto (#417) --- grpc-testtool/Cargo.toml | 1 - grpc-testtool/build.rs | 7 +- .../proto/io/prometheus/client/metrics.proto | 154 ------------------ .../proto/process-server/process-server.proto | 3 +- grpc-testtool/src/lib.rs | 10 -- 5 files changed, 2 insertions(+), 173 deletions(-) delete mode 100644 grpc-testtool/proto/io/prometheus/client/metrics.proto diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 5d3bd3d681e0..cf85f673c68b 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -21,7 +21,6 @@ prost = "0.12.0" thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.10.0", features = ["tls"] } -prost-types = "0.12.0" clap = { version = "4.4.11", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" diff --git a/grpc-testtool/build.rs b/grpc-testtool/build.rs index 5246f5c57166..1c3c17ee3ea2 100644 --- a/grpc-testtool/build.rs +++ b/grpc-testtool/build.rs @@ -4,12 +4,7 @@ fn main() -> Result<(), Box> { tonic_build::compile_protos("proto/sync/sync.proto")?; tonic_build::compile_protos("proto/rpcdb/rpcdb.proto")?; - tonic_build::compile_protos("proto/io/prometheus/client/metrics.proto")?; - - tonic_build::configure().compile( - &["proto/process-server/process-server.proto"], // protos - &["proto"], // includes - )?; + tonic_build::compile_protos("proto/process-server/process-server.proto")?; Ok(()) } diff --git a/grpc-testtool/proto/io/prometheus/client/metrics.proto b/grpc-testtool/proto/io/prometheus/client/metrics.proto deleted file mode 100644 index 31595d8e73dd..000000000000 --- a/grpc-testtool/proto/io/prometheus/client/metrics.proto +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2013 Prometheus Team -// 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. - -syntax = "proto2"; - -package io.prometheus.client; -option java_package = "io.prometheus.client"; -option go_package = "github.com/prometheus/client_model/go;io_prometheus_client"; - -import "google/protobuf/timestamp.proto"; - -message LabelPair { - optional string name = 1; - optional string value = 2; -} - -enum MetricType { - // COUNTER must use the Metric field "counter". - COUNTER = 0; - // GAUGE must use the Metric field "gauge". - GAUGE = 1; - // SUMMARY must use the Metric field "summary". - SUMMARY = 2; - // UNTYPED must use the Metric field "untyped". - UNTYPED = 3; - // HISTOGRAM must use the Metric field "histogram". - HISTOGRAM = 4; - // GAUGE_HISTOGRAM must use the Metric field "histogram". - GAUGE_HISTOGRAM = 5; -} - -message Gauge { - optional double value = 1; -} - -message Counter { - optional double value = 1; - optional Exemplar exemplar = 2; - - optional google.protobuf.Timestamp created_timestamp = 3; -} - -message Quantile { - optional double quantile = 1; - optional double value = 2; -} - -message Summary { - optional uint64 sample_count = 1; - optional double sample_sum = 2; - repeated Quantile quantile = 3; - - optional google.protobuf.Timestamp created_timestamp = 4; -} - -message Untyped { - optional double value = 1; -} - -message Histogram { - optional uint64 sample_count = 1; - optional double sample_count_float = 4; // Overrides sample_count if > 0. - optional double sample_sum = 2; - // Buckets for the conventional histogram. - repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional. - - optional google.protobuf.Timestamp created_timestamp = 15; - - // Everything below here is for native histograms (also known as sparse histograms). - // Native histograms are an experimental feature without stability guarantees. - - // schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8. - // They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and - // then each power of two is divided into 2^n logarithmic buckets. - // Or in other words, each bucket boundary is the previous boundary times 2^(2^-n). - // In the future, more bucket schemas may be added using numbers < -4 or > 8. - optional sint32 schema = 5; - optional double zero_threshold = 6; // Breadth of the zero bucket. - optional uint64 zero_count = 7; // Count in zero bucket. - optional double zero_count_float = 8; // Overrides sb_zero_count if > 0. - - // Negative buckets for the native histogram. - repeated BucketSpan negative_span = 9; - // Use either "negative_delta" or "negative_count", the former for - // regular histograms with integer counts, the latter for float - // histograms. - repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). - repeated double negative_count = 11; // Absolute count of each bucket. - - // Positive buckets for the native histogram. - // Use a no-op span (offset 0, length 0) for a native histogram without any - // observations yet and with a zero_threshold of 0. Otherwise, it would be - // indistinguishable from a classic histogram. - repeated BucketSpan positive_span = 12; - // Use either "positive_delta" or "positive_count", the former for - // regular histograms with integer counts, the latter for float - // histograms. - repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). - repeated double positive_count = 14; // Absolute count of each bucket. -} - -// A Bucket of a conventional histogram, each of which is treated as -// an individual counter-like time series by Prometheus. -message Bucket { - optional uint64 cumulative_count = 1; // Cumulative in increasing order. - optional double cumulative_count_float = 4; // Overrides cumulative_count if > 0. - optional double upper_bound = 2; // Inclusive. - optional Exemplar exemplar = 3; -} - -// A BucketSpan defines a number of consecutive buckets in a native -// histogram with their offset. Logically, it would be more -// straightforward to include the bucket counts in the Span. However, -// the protobuf representation is more compact in the way the data is -// structured here (with all the buckets in a single array separate -// from the Spans). -message BucketSpan { - optional sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative). - optional uint32 length = 2; // Length of consecutive buckets. -} - -message Exemplar { - repeated LabelPair label = 1; - optional double value = 2; - optional google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style. -} - -message Metric { - repeated LabelPair label = 1; - optional Gauge gauge = 2; - optional Counter counter = 3; - optional Summary summary = 4; - optional Untyped untyped = 5; - optional Histogram histogram = 7; - optional int64 timestamp_ms = 6; -} - -message MetricFamily { - optional string name = 1; - optional string help = 2; - optional MetricType type = 3; - repeated Metric metric = 4; - optional string unit = 5; -} diff --git a/grpc-testtool/proto/process-server/process-server.proto b/grpc-testtool/proto/process-server/process-server.proto index 494ca694c2ac..6aa8f55d41d4 100644 --- a/grpc-testtool/proto/process-server/process-server.proto +++ b/grpc-testtool/proto/process-server/process-server.proto @@ -3,7 +3,6 @@ syntax = "proto3"; package process; import "google/protobuf/empty.proto"; -import "io/prometheus/client/metrics.proto"; option go_package = "github.com/ava-labs/merkledb-tester/proto/pb/process"; @@ -12,5 +11,5 @@ service ProcessServerService { } message MetricsResponse { - repeated io.prometheus.client.MetricFamily metric_families = 1; + string metrics = 1; } diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 8189048b0101..85ba10e8f00f 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -1,16 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -pub mod io { - pub mod prometheus { - pub mod client { - #![allow(clippy::unwrap_used)] - #![allow(clippy::missing_const_for_fn)] - tonic::include_proto!("io.prometheus.client"); - } - } -} - pub mod sync { #![allow(clippy::unwrap_used)] #![allow(clippy::missing_const_for_fn)] From ec4547e31e4f38e039c8b69c4f1e953dddf2b5bb Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Dec 2023 14:42:33 -0500 Subject: [PATCH 0398/1053] Write panics to the log (#419) --- grpc-testtool/Cargo.toml | 1 + grpc-testtool/src/bin/process-server.rs | 30 +++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index cf85f673c68b..e883f47bbb03 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -26,6 +26,7 @@ tempdir = "0.3.7" log = "0.4.20" env_logger = "0.10.1" chrono = "0.4.31" +log-panics = { version = "2.1.0", features = ["backtrace", "with-backtrace"] } [build-dependencies] tonic-build = "0.10.0" diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index 2128374a292f..d11e4d2188fe 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -29,18 +29,30 @@ struct Opts { //// Port gRPC server listens on grpc_port: u16, - #[arg(short = 'G', long = "gatewayPort", default_value_t = 10001)] + #[arg(short = 'G', long, default_value_t = 10001)] /// Port gRPC gateway server, which HTTP requests can be made to _gateway_port: u16, - #[arg(short = 'l', long = "logDir", default_value = temp_path())] + #[arg(short = 'l', long, default_value = temp_path())] log_dir: PathBuf, - #[arg(short = 'L', long = "LogLevelStr", default_value_t = LevelFilter::Info)] + #[arg(short = 'L', long, default_value_t = LevelFilter::Info)] log_level: LevelFilter, - #[arg(short = 'b', long = "branch-factor")] + #[arg(short = 'b', long)] _branch_factor: Option, + + #[arg(short = 'p', long)] + _process_name: String, + + #[arg(short = 'd')] + db_dir: PathBuf, + + #[arg(short = 'H')] + _history_length: Option, + + #[arg(short = 'N')] + _node_cache_size: Option, } fn temp_path() -> clap::builder::OsStr { @@ -54,6 +66,12 @@ async fn main() -> Result<(), Box> { // parse command line options let args = Opts::parse(); + match std::fs::create_dir_all(&args.log_dir) { + Ok(()) => {} + Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(e) => panic!("Unable to create directory\n{}", e), + } + // set up the log file let logfile = std::fs::OpenOptions::new() .create(true) @@ -77,6 +95,10 @@ async fn main() -> Result<(), Box> { .filter(None, args.log_level) .init(); + log_panics::Config::new() + .backtrace_mode(log_panics::BacktraceMode::Unresolved) + .install_panic_hook(); + // log to the file and to stderr eprintln!("Database-Server listening on: {}", args.grpc_port); info!("Starting up: Listening on {}", args.grpc_port); From 90b98e303758d554456c26aeea24ff1c35a02354 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Dec 2023 16:32:03 -0500 Subject: [PATCH 0399/1053] grpc-testtool: Finish error handler mapper (#421) --- grpc-testtool/src/service.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index cf79cebdab7d..d5cf20ec0d5b 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -24,16 +24,16 @@ trait IntoStatusResultExt { } impl IntoStatusResultExt for Result { + // We map errors from bad arguments into Status::invalid_argument; all other errors are Status::internal errors fn into_status_result(self) -> Result { self.map_err(|err| match err { - Error::HashNotFound { provided: _ } => todo!(), - Error::IncorrectRootHash { - provided: _, - current: _, - } => todo!(), - Error::IO(_) => todo!(), - Error::InvalidProposal => todo!(), - _ => todo!(), + Error::IncorrectRootHash { .. } | Error::HashNotFound { .. } | Error::RangeTooSmall => { + Status::invalid_argument(err.to_string()) + } + Error::IO { .. } | Error::InternalError { .. } | Error::InvalidProposal => { + Status::internal(err.to_string()) + } + _ => Status::internal(err.to_string()), }) } } From a82a03d8c0bb7d7f6cae4ec89c89ea1e759bc491 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Dec 2023 16:40:54 -0500 Subject: [PATCH 0400/1053] grpc-testtool: Switch from EmptyDB to Db (#422) --- grpc-testtool/src/bin/process-server.rs | 2 +- grpc-testtool/src/service.rs | 43 +++++++++++++++++-------- grpc-testtool/src/service/database.rs | 4 +-- grpc-testtool/src/service/db.rs | 6 ++-- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index d11e4d2188fe..a0288c3a0b1e 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -103,7 +103,7 @@ async fn main() -> Result<(), Box> { eprintln!("Database-Server listening on: {}", args.grpc_port); info!("Starting up: Listening on {}", args.grpc_port); - let svc = Arc::new(DatabaseService::default()); + let svc = Arc::new(DatabaseService::new(args.db_dir).await?); // TODO: graceful shutdown Ok(Server::builder() diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index d5cf20ec0d5b..dd7920c17374 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -1,12 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::{ - api::{Db, Error}, - emptydb::{EmptyDb, HistoricalImpl}, -}; +use firewood::db::Db; +use firewood::v2::{api::Db as _, api::Error}; + +use std::path::Path; use std::{ collections::HashMap, + ops::Deref, sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -38,23 +39,39 @@ impl IntoStatusResultExt for Result { } } pub struct Database { - db: EmptyDb, + db: Db, iterators: Arc>, } -impl Default for Database { - fn default() -> Self { - Self { - db: EmptyDb, +impl Database { + pub async fn new>(path: P) -> Result { + // try to create the parents for this directory, but it's okay if it fails; it will get caught in Db::new + std::fs::create_dir_all(&path).ok(); + // TODO: truncate should be false + // see https://github.com/ava-labs/firewood/issues/418 + let cfg = firewood::config::DbConfig::builder().truncate(true).build(); + + let db = Db::new(path, &cfg).await?; + + Ok(Self { + db, iterators: Default::default(), - } + }) + } +} + +impl Deref for Database { + type Target = Db; + + fn deref(&self) -> &Self::Target { + &self.db } } impl Database { - async fn revision(&self) -> Result, Error> { - let root_hash = self.db.root_hash().await?; - self.db.revision(root_hash).await + async fn latest(&self) -> Result::Historical>, Error> { + let root_hash = self.root_hash().await?; + self.revision(root_hash).await } } diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index d3ac2cce01c2..7fc08ba05362 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -18,7 +18,7 @@ use tonic::{async_trait, Request, Response, Status}; impl Database for DatabaseService { async fn has(&self, request: Request) -> Result, Status> { let key = request.into_inner().key; - let revision = self.revision().await.into_status_result()?; + let revision = self.latest().await.into_status_result()?; let val = revision.val(key).await.into_status_result()?; @@ -32,7 +32,7 @@ impl Database for DatabaseService { async fn get(&self, request: Request) -> Result, Status> { let key = request.into_inner().key; - let revision = self.revision().await.into_status_result()?; + let revision = self.latest().await.into_status_result()?; let value = revision .val(key) diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index 54d3ae40902d..1eb836498cfc 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -29,7 +29,7 @@ impl DbServerTrait for Database { request: Request, ) -> Result, Status> { let GetProofRequest { key: _ } = request.into_inner(); - let _revision = self.revision().await.into_status_result()?; + let _revision = self.latest().await.into_status_result()?; todo!() } @@ -46,7 +46,7 @@ impl DbServerTrait for Database { key_limit: _, } = request.into_inner(); - let _revision = self.revision().await.into_status_result()?; + let _revision = self.latest().await.into_status_result()?; todo!() } @@ -62,7 +62,7 @@ impl DbServerTrait for Database { expected_root_hash: _, } = request.into_inner(); - let _revision = self.revision().await.into_status_result()?; + let _revision = self.latest().await.into_status_result()?; todo!() } From f47a28eb6c313493fd05c9315e14a41c043c58af Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 8 Dec 2023 16:28:29 -0500 Subject: [PATCH 0401/1053] Rename revision() to latest() (#420) Signed-off-by: Ron Kuris From 3071ca39ba8477f5b0ea7059f3652d489c491379 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 11 Dec 2023 11:23:53 -0500 Subject: [PATCH 0402/1053] Orchestrator interface changes (#423) --- grpc-testtool/Cargo.toml | 5 +- grpc-testtool/src/bin/process-server.rs | 93 ++++++++++++------------- grpc-testtool/src/service.rs | 15 ++-- grpc-testtool/src/service/db.rs | 7 ++ 4 files changed, 66 insertions(+), 54 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index e883f47bbb03..5f2129716c60 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -21,12 +21,15 @@ prost = "0.12.0" thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.10.0", features = ["tls"] } +tracing = { version = "0.1.16" } +tracing-subscriber = { version = "0.3", features = ["tracing-log", "fmt", "env-filter"] } clap = { version = "4.4.11", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" env_logger = "0.10.1" chrono = "0.4.31" -log-panics = { version = "2.1.0", features = ["backtrace", "with-backtrace"] } +serde_json = "1.0.108" +serde = { version = "1.0.193", features = ["derive"] } [build-dependencies] tonic-build = "0.10.0" diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index a0288c3a0b1e..1e55c4a975ef 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -3,62 +3,65 @@ use chrono::Local; use clap::Parser; -use env_logger::{Builder, Target}; +use env_logger::Builder; use log::{info, LevelFilter}; use rpc::{ process_server::process_server_service_server::ProcessServerServiceServer, rpcdb::database_server::DatabaseServer as RpcServer, sync::db_server::DbServer as SyncServer, DatabaseService, }; +use serde::Deserialize; use std::{ error::Error, io::Write, net::{IpAddr::V4, Ipv4Addr}, path::PathBuf, + str::FromStr, sync::Arc, }; -use tempdir::TempDir; use tonic::transport::Server; +#[derive(Clone, Debug, Deserialize)] +struct Options { + #[serde(default = "Options::history_length_default")] + history_length: u32, +} + +impl Options { + // used in two cases: + // serde deserializes Options and there was no history_length + // OR + // Options was not present + const fn history_length_default() -> u32 { + 100 + } +} + +impl FromStr for Options { + type Err = String; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s).map_err(|e| format!("error parsing options: {}", e)) + } +} + /// A GRPC server that can be plugged into the generic testing framework for merkledb #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] struct Opts { - #[arg(short = 'g', long, default_value_t = 10000)] + #[arg(short, long)] //// Port gRPC server listens on grpc_port: u16, - #[arg(short = 'G', long, default_value_t = 10001)] - /// Port gRPC gateway server, which HTTP requests can be made to - _gateway_port: u16, - - #[arg(short = 'l', long, default_value = temp_path())] - log_dir: PathBuf, - - #[arg(short = 'L', long, default_value_t = LevelFilter::Info)] - log_level: LevelFilter, - - #[arg(short = 'b', long)] - _branch_factor: Option, - - #[arg(short = 'p', long)] - _process_name: String, - - #[arg(short = 'd')] + #[arg(short, long)] db_dir: PathBuf, - #[arg(short = 'H')] - _history_length: Option, - - #[arg(short = 'N')] - _node_cache_size: Option, -} + #[arg(short, long, default_value_t = LevelFilter::Info)] + log_level: LevelFilter, -fn temp_path() -> clap::builder::OsStr { - let tmpdir = TempDir::new("process-server").expect("unable to create temporary directory"); - // we leak because we want the temporary directory to stick around forever (emulating what happens in golang) - Box::leak(Box::new(tmpdir.into_path())).as_os_str().into() + #[arg(short, long)] + config: Option, } #[tokio::main] @@ -66,19 +69,6 @@ async fn main() -> Result<(), Box> { // parse command line options let args = Opts::parse(); - match std::fs::create_dir_all(&args.log_dir) { - Ok(()) => {} - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} - Err(e) => panic!("Unable to create directory\n{}", e), - } - - // set up the log file - let logfile = std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(true) - .open(args.log_dir.join("server.log"))?; - // configure the logger Builder::new() .format(|buf, record| { @@ -91,22 +81,27 @@ async fn main() -> Result<(), Box> { ) }) .format_target(true) - .target(Target::Pipe(Box::new(logfile))) .filter(None, args.log_level) .init(); - log_panics::Config::new() - .backtrace_mode(log_panics::BacktraceMode::Unresolved) - .install_panic_hook(); + tracing_subscriber::fmt::init(); // log to the file and to stderr - eprintln!("Database-Server listening on: {}", args.grpc_port); info!("Starting up: Listening on {}", args.grpc_port); - let svc = Arc::new(DatabaseService::new(args.db_dir).await?); + let svc = Arc::new( + DatabaseService::new( + args.db_dir, + args.config + .map(|o| o.history_length) + .unwrap_or_else(Options::history_length_default), + ) + .await?, + ); // TODO: graceful shutdown Ok(Server::builder() + .trace_fn(|_m| tracing::debug_span!("process-server")) .add_service(RpcServer::from_arc(svc.clone())) .add_service(SyncServer::from_arc(svc.clone())) .add_service(ProcessServerServiceServer::from_arc(svc.clone())) diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index dd7920c17374..b82ba676aba7 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -1,7 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::db::Db; +use firewood::db::{Db, DbConfig}; +use firewood::storage::WalConfig; use firewood::v2::{api::Db as _, api::Error}; use std::path::Path; @@ -38,18 +39,23 @@ impl IntoStatusResultExt for Result { }) } } + +#[derive(Debug)] pub struct Database { db: Db, iterators: Arc>, } impl Database { - pub async fn new>(path: P) -> Result { + pub async fn new>(path: P, history_length: u32) -> Result { // try to create the parents for this directory, but it's okay if it fails; it will get caught in Db::new std::fs::create_dir_all(&path).ok(); // TODO: truncate should be false // see https://github.com/ava-labs/firewood/issues/418 - let cfg = firewood::config::DbConfig::builder().truncate(true).build(); + let cfg = DbConfig::builder() + .wal(WalConfig::builder().max_revisions(history_length).build()) + .truncate(true) + .build(); let db = Db::new(path, &cfg).await?; @@ -76,9 +82,10 @@ impl Database { } // TODO: implement Iterator +#[derive(Debug)] struct Iter; -#[derive(Default)] +#[derive(Default, Debug)] struct Iterators { map: HashMap, next_id: AtomicU64, diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index 1eb836498cfc..9f49a02fd12b 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -13,6 +13,7 @@ use tonic::{async_trait, Request, Response, Status}; #[async_trait] impl DbServerTrait for Database { + #[tracing::instrument(level = "trace")] async fn get_merkle_root( &self, _request: Request<()>, @@ -24,6 +25,7 @@ impl DbServerTrait for Database { Ok(Response::new(response)) } + #[tracing::instrument(level = "trace")] async fn get_proof( &self, request: Request, @@ -34,6 +36,7 @@ impl DbServerTrait for Database { todo!() } + #[tracing::instrument(level = "trace")] async fn get_change_proof( &self, request: Request, @@ -51,6 +54,7 @@ impl DbServerTrait for Database { todo!() } + #[tracing::instrument(level = "trace")] async fn verify_change_proof( &self, request: Request, @@ -67,6 +71,7 @@ impl DbServerTrait for Database { todo!() } + #[tracing::instrument(level = "trace")] async fn commit_change_proof( &self, request: Request, @@ -76,6 +81,7 @@ impl DbServerTrait for Database { todo!() } + #[tracing::instrument(level = "trace")] async fn get_range_proof( &self, request: Request, @@ -90,6 +96,7 @@ impl DbServerTrait for Database { todo!() } + #[tracing::instrument(level = "trace")] async fn commit_range_proof( &self, request: Request, From 5597f5092c85f472aca215b050e0d522ea20fcb5 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:16:46 -0800 Subject: [PATCH 0403/1053] Update milestone (#427) Signed-off-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3b77743f2d0..1ae15479f19d 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ instances to replicate the state. A synchronization library should also be developed for this milestone. - [x] Migrate to a fully async interface -- [ ] :runner: Pluggable encoding for nodes, for optional compatibility with MerkleDB +- [x] Pluggable encoding for nodes, for optional compatibility with MerkleDB - [ ] :runner: MerkleDB root hash in parity for seamless transition between MerkleDB and Firewood. - [ ] :runner: Support replicating the full state with corresponding range proofs that From 6bd2e5288bf1983d96647232375f442eea375749 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 11 Dec 2023 15:35:09 -0500 Subject: [PATCH 0404/1053] Add more iterator tests (#429) --- firewood/src/merkle.rs | 331 +-------------------------- firewood/src/merkle/stream.rs | 415 ++++++++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+), 325 deletions(-) create mode 100644 firewood/src/merkle/stream.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 64c685716b6c..7e64c3f023dd 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -3,16 +3,17 @@ use crate::nibbles::Nibbles; use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::v2::api; -use futures::{Stream, StreamExt, TryStreamExt}; +use futures::{StreamExt, TryStreamExt}; use sha3::Digest; use std::{ cmp::Ordering, collections::HashMap, future::ready, io::Write, iter::once, marker::PhantomData, - sync::OnceLock, task::Poll, + sync::OnceLock, }; use thiserror::Error; mod node; pub mod proof; +mod stream; mod trie_hash; pub use node::{ @@ -20,6 +21,8 @@ pub use node::{ NodeType, PartialPath, }; pub use proof::{Proof, ProofError}; +use stream::IteratorState; +pub use stream::MerkleKeyValueStream; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; @@ -1304,286 +1307,6 @@ impl + Send + Sync, T> Merkle { } } -enum IteratorState<'a> { - /// Start iterating at the beginning of the trie, - /// returning the lowest key/value pair first - StartAtBeginning, - /// Start iterating at the specified key - StartAtKey(Vec), - /// Continue iterating after the given last_node and parents - Iterating { - last_node: ObjRef<'a>, - parents: Vec<(ObjRef<'a>, u8)>, - }, -} -impl IteratorState<'_> { - fn new>(starting: Option) -> Self { - match starting { - None => Self::StartAtBeginning, - Some(key) => Self::StartAtKey(key.as_ref().to_vec()), - } - } -} - -// The default state is to start at the beginning -impl<'a> Default for IteratorState<'a> { - fn default() -> Self { - Self::StartAtBeginning - } -} - -/// A MerkleKeyValueStream iterates over keys/values for a merkle trie. -/// This iterator is not fused. If you read past the None value, you start -/// over at the beginning. If you need a fused iterator, consider using -/// std::iter::fuse -pub struct MerkleKeyValueStream<'a, S, T> { - key_state: IteratorState<'a>, - merkle_root: DiskAddress, - merkle: &'a Merkle, -} - -impl<'a, S: shale::ShaleStore + Send + Sync, T> Stream - for MerkleKeyValueStream<'a, S, T> -{ - type Item = Result<(Vec, Vec), api::Error>; - - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll> { - // Note that this sets the key_state to StartAtBeginning temporarily - let found_key = match std::mem::take(&mut self.key_state) { - IteratorState::StartAtBeginning => { - let root_node = self - .merkle - .get_node(self.merkle_root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let mut last_node = root_node; - let mut parents = vec![]; - let leaf = loop { - match last_node.inner() { - NodeType::Branch(branch) => { - let Some((leftmost_position, leftmost_address)) = branch - .children - .iter() - .enumerate() - .filter_map(|(i, addr)| addr.map(|addr| (i, addr))) - .next() - else { - // we already exhausted the branch node. This happens with an empty trie - // ... or a corrupt one - return if parents.is_empty() { - // empty trie - Poll::Ready(None) - } else { - // branch with NO children, not at the top - Poll::Ready(Some(Err(api::Error::InternalError(Box::new( - MerkleError::ParentLeafBranch, - ))))) - }; - }; - - let next = self - .merkle - .get_node(leftmost_address) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - parents.push((last_node, leftmost_position as u8)); - - last_node = next; - } - NodeType::Leaf(leaf) => break leaf, - NodeType::Extension(_) => todo!(), - } - }; - - // last_node should have a leaf; compute the key and value - let current_key = key_from_parents_and_leaf(&parents, leaf); - - self.key_state = IteratorState::Iterating { last_node, parents }; - - current_key - } - IteratorState::StartAtKey(key) => { - // TODO: support finding the next key after K - let root_node = self - .merkle - .get_node(self.merkle_root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let (found_node, parents) = self - .merkle - .get_node_and_parents_by_key(root_node, &key) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let Some(last_node) = found_node else { - return Poll::Ready(None); - }; - - let returned_key_value = match last_node.inner() { - NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), - NodeType::Leaf(leaf) => (key, leaf.data.to_vec()), - NodeType::Extension(_) => todo!(), - }; - - self.key_state = IteratorState::Iterating { last_node, parents }; - - return Poll::Ready(Some(Ok(returned_key_value))); - } - IteratorState::Iterating { - last_node, - mut parents, - } => { - match last_node.inner() { - NodeType::Branch(branch) => { - // previously rendered the value from a branch node, so walk down to the first available child - let Some((child_position, child_address)) = branch - .children - .iter() - .enumerate() - .filter_map(|(child_position, &addr)| { - addr.map(|addr| (child_position, addr)) - }) - .next() - else { - // Branch node with no children? - return Poll::Ready(Some(Err(api::Error::InternalError(Box::new( - MerkleError::ParentLeafBranch, - ))))); - }; - - parents.push((last_node, child_position as u8)); // remember where we walked down from - - let current_node = self - .merkle - .get_node(child_address) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let found_key = key_from_parents(&parents); - - self.key_state = IteratorState::Iterating { - // continue iterating from here - last_node: current_node, - parents, - }; - - found_key - } - NodeType::Leaf(leaf) => { - let mut next = parents.pop().map(|(node, position)| (node, Some(position))); - loop { - match next { - None => return Poll::Ready(None), - Some((parent, child_position)) => { - // Assume all parents are branch nodes - let children = parent.inner().as_branch().unwrap().chd(); - - // we use wrapping_add here because the value might be u8::MAX indicating that - // we want to go down branch - let start_position = - child_position.map(|pos| pos + 1).unwrap_or_default(); - - let Some((found_position, found_address)) = children - .iter() - .enumerate() - .skip(start_position as usize) - .filter_map(|(offset, addr)| { - addr.map(|addr| (offset as u8, addr)) - }) - .next() - else { - next = parents - .pop() - .map(|(node, position)| (node, Some(position))); - continue; - }; - - // we push (node, None) which will start at the beginning of the next branch node - let child = self - .merkle - .get_node(found_address) - .map(|node| (node, None)) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - // stop_descending if: - // - on a branch and it has a value; OR - // - on a leaf - let stop_descending = match child.0.inner() { - NodeType::Branch(branch) => branch.value.is_some(), - NodeType::Leaf(_) => true, - NodeType::Extension(_) => todo!(), - }; - - next = Some(child); - - parents.push((parent, found_position)); - - if stop_descending { - break; - } - } - } - } - // recompute current_key - // TODO: Can we keep current_key updated as we walk the tree instead of building it from the top all the time? - let current_key = key_from_parents_and_leaf(&parents, leaf); - - self.key_state = IteratorState::Iterating { - last_node: next.unwrap().0, - parents, - }; - - current_key - } - - NodeType::Extension(_) => todo!(), - } - } - }; - - // figure out the value to return from the state - // if we get here, we're sure to have something to return - // TODO: It's possible to return a reference to the data since the last_node is - // saved in the iterator - let return_value = match &self.key_state { - IteratorState::Iterating { - last_node, - parents: _, - } => { - let value = match last_node.inner() { - NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), - NodeType::Leaf(leaf) => leaf.data.to_vec(), - NodeType::Extension(_) => todo!(), - }; - - (found_key, value) - } - _ => unreachable!(), - }; - - Poll::Ready(Some(Ok(return_value))) - } -} - -/// Compute a key from a set of parents -fn key_from_parents(parents: &[(ObjRef, u8)]) -> Vec { - parents[1..] - .chunks_exact(2) - .map(|parents| (parents[0].1 << 4) + parents[1].1) - .collect::>() -} -fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec { - let mut iter = parents[1..] - .iter() - .map(|parent| parent.1) - .chain(leaf.path.to_vec()); - let mut data = Vec::with_capacity(iter.size_hint().0); - while let (Some(hi), Some(lo)) = (iter.next(), iter.next()) { - data.push((hi << 4) + lo); - } - data -} - fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { let (p_ref, idx) = parents.last_mut().unwrap(); p_ref @@ -1677,7 +1400,6 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { #[cfg(test)] mod tests { use super::*; - use futures::StreamExt; use node::tests::{extension, leaf}; use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; use std::sync::Arc; @@ -1690,7 +1412,7 @@ mod tests { assert_eq!(n, nibbles); } - fn create_test_merkle() -> Merkle, Bincode> { + pub(super) fn create_test_merkle() -> Merkle, Bincode> { const RESERVED: usize = 0x1000; let mut dm = shale::cached::DynamicMem::new(0x10000, 0); @@ -1814,47 +1536,6 @@ mod tests { } } - #[tokio::test] - async fn iterate_empty() { - let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - let mut it = merkle.get_iter(Some(b"x"), root).unwrap(); - let next = it.next().await; - assert!(next.is_none()); - } - - #[test_case(Some(&[u8::MIN]); "Starting at first key")] - #[test_case(None; "No start specified")] - #[test_case(Some(&[128u8]); "Starting in middle")] - #[test_case(Some(&[u8::MAX]); "Starting at last key")] - #[tokio::test] - async fn iterate_many(start: Option<&[u8]>) { - let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - - // insert all values from u8::MIN to u8::MAX, with the key and value the same - for k in u8::MIN..=u8::MAX { - merkle.insert([k], vec![k], root).unwrap(); - } - - let mut it = merkle.get_iter(start, root).unwrap(); - // we iterate twice because we should get a None then start over - for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { - let next = it.next().await.unwrap().unwrap(); - assert_eq!(next.0, next.1,); - assert_eq!(next.1, vec![k]); - } - assert!(it.next().await.is_none()); - - // ensure that reading past the end returns all the values - for k in u8::MIN..=u8::MAX { - let next = it.next().await.unwrap().unwrap(); - assert_eq!(next.0, next.1); - assert_eq!(next.1, vec![k]); - } - assert!(it.next().await.is_none()); - } - #[test] fn remove_one() { let key = b"hello"; diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs new file mode 100644 index 000000000000..4186d7e22b4d --- /dev/null +++ b/firewood/src/merkle/stream.rs @@ -0,0 +1,415 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::{node::Node, LeafNode, Merkle, MerkleError, NodeType, ObjRef}; +use crate::{ + shale::{DiskAddress, ShaleStore}, + v2::api, +}; +use futures::Stream; +use std::task::Poll; + +pub(super) enum IteratorState<'a> { + /// Start iterating at the beginning of the trie, + /// returning the lowest key/value pair first + StartAtBeginning, + /// Start iterating at the specified key + StartAtKey(Vec), + /// Continue iterating after the given last_node and parents + Iterating { + last_node: ObjRef<'a>, + parents: Vec<(ObjRef<'a>, u8)>, + }, +} +impl IteratorState<'_> { + pub(super) fn new>(starting: Option) -> Self { + match starting { + None => Self::StartAtBeginning, + Some(key) => Self::StartAtKey(key.as_ref().to_vec()), + } + } +} + +// The default state is to start at the beginning +impl<'a> Default for IteratorState<'a> { + fn default() -> Self { + Self::StartAtBeginning + } +} + +/// A MerkleKeyValueStream iterates over keys/values for a merkle trie. +/// This iterator is not fused. If you read past the None value, you start +/// over at the beginning. If you need a fused iterator, consider using +/// std::iter::fuse +pub struct MerkleKeyValueStream<'a, S, T> { + pub(super) key_state: IteratorState<'a>, + pub(super) merkle_root: DiskAddress, + pub(super) merkle: &'a Merkle, +} + +impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<'a, S, T> { + type Item = Result<(Vec, Vec), api::Error>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll> { + // Note that this sets the key_state to StartAtBeginning temporarily + let found_key = match std::mem::take(&mut self.key_state) { + IteratorState::StartAtBeginning => { + let root_node = self + .merkle + .get_node(self.merkle_root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let mut last_node = root_node; + let mut parents = vec![]; + let leaf = loop { + match last_node.inner() { + NodeType::Branch(branch) => { + let Some((leftmost_position, leftmost_address)) = branch + .children + .iter() + .enumerate() + .filter_map(|(i, addr)| addr.map(|addr| (i, addr))) + .next() + else { + // we already exhausted the branch node. This happens with an empty trie + // ... or a corrupt one + return if parents.is_empty() { + // empty trie + Poll::Ready(None) + } else { + // branch with NO children, not at the top + Poll::Ready(Some(Err(api::Error::InternalError(Box::new( + MerkleError::ParentLeafBranch, + ))))) + }; + }; + + let next = self + .merkle + .get_node(leftmost_address) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + parents.push((last_node, leftmost_position as u8)); + + last_node = next; + } + NodeType::Leaf(leaf) => break leaf, + NodeType::Extension(_) => todo!(), + } + }; + + // last_node should have a leaf; compute the key and value + let current_key = key_from_parents_and_leaf(&parents, leaf); + + self.key_state = IteratorState::Iterating { last_node, parents }; + + current_key + } + IteratorState::StartAtKey(key) => { + // TODO: support finding the next key after K + let root_node = self + .merkle + .get_node(self.merkle_root) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + let (found_node, parents) = self + .merkle + .get_node_and_parents_by_key(root_node, &key) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + let Some(last_node) = found_node else { + return Poll::Ready(None); + }; + + let returned_key_value = match last_node.inner() { + NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), + NodeType::Leaf(leaf) => (key, leaf.data.to_vec()), + NodeType::Extension(_) => todo!(), + }; + + self.key_state = IteratorState::Iterating { last_node, parents }; + + return Poll::Ready(Some(Ok(returned_key_value))); + } + IteratorState::Iterating { + last_node, + mut parents, + } => { + match last_node.inner() { + NodeType::Branch(branch) => { + // previously rendered the value from a branch node, so walk down to the first available child + let Some((child_position, child_address)) = branch + .children + .iter() + .enumerate() + .filter_map(|(child_position, &addr)| { + addr.map(|addr| (child_position, addr)) + }) + .next() + else { + // Branch node with no children? + return Poll::Ready(Some(Err(api::Error::InternalError(Box::new( + MerkleError::ParentLeafBranch, + ))))); + }; + + parents.push((last_node, child_position as u8)); // remember where we walked down from + + let current_node = self + .merkle + .get_node(child_address) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + let found_key = key_from_parents(&parents); + + self.key_state = IteratorState::Iterating { + // continue iterating from here + last_node: current_node, + parents, + }; + + found_key + } + NodeType::Leaf(leaf) => { + let mut next = parents.pop().map(|(node, position)| (node, Some(position))); + loop { + match next { + None => return Poll::Ready(None), + Some((parent, child_position)) => { + // Assume all parents are branch nodes + let children = parent.inner().as_branch().unwrap().chd(); + + // we use wrapping_add here because the value might be u8::MAX indicating that + // we want to go down branch + let start_position = + child_position.map(|pos| pos + 1).unwrap_or_default(); + + let Some((found_position, found_address)) = children + .iter() + .enumerate() + .skip(start_position as usize) + .filter_map(|(offset, addr)| { + addr.map(|addr| (offset as u8, addr)) + }) + .next() + else { + next = parents + .pop() + .map(|(node, position)| (node, Some(position))); + continue; + }; + + // we push (node, None) which will start at the beginning of the next branch node + let child = self + .merkle + .get_node(found_address) + .map(|node| (node, None)) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + // stop_descending if: + // - on a branch and it has a value; OR + // - on a leaf + let stop_descending = match child.0.inner() { + NodeType::Branch(branch) => branch.value.is_some(), + NodeType::Leaf(_) => true, + NodeType::Extension(_) => todo!(), + }; + + next = Some(child); + + parents.push((parent, found_position)); + + if stop_descending { + break; + } + } + } + } + // recompute current_key + // TODO: Can we keep current_key updated as we walk the tree instead of building it from the top all the time? + let current_key = key_from_parents_and_leaf(&parents, leaf); + + self.key_state = IteratorState::Iterating { + last_node: next.unwrap().0, + parents, + }; + + current_key + } + + NodeType::Extension(_) => todo!(), + } + } + }; + + // figure out the value to return from the state + // if we get here, we're sure to have something to return + // TODO: It's possible to return a reference to the data since the last_node is + // saved in the iterator + let return_value = match &self.key_state { + IteratorState::Iterating { + last_node, + parents: _, + } => { + let value = match last_node.inner() { + NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), + NodeType::Leaf(leaf) => leaf.data.to_vec(), + NodeType::Extension(_) => todo!(), + }; + + (found_key, value) + } + _ => unreachable!(), + }; + + Poll::Ready(Some(Ok(return_value))) + } +} + +/// Compute a key from a set of parents +fn key_from_parents(parents: &[(ObjRef, u8)]) -> Vec { + parents[1..] + .chunks_exact(2) + .map(|parents| (parents[0].1 << 4) + parents[1].1) + .collect::>() +} +fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec { + let mut iter = parents[1..] + .iter() + .map(|parent| parent.1) + .chain(leaf.path.to_vec()); + let mut data = Vec::with_capacity(iter.size_hint().0); + while let (Some(hi), Some(lo)) = (iter.next(), iter.next()) { + data.push((hi << 4) + lo); + } + data +} + +#[cfg(test)] +use super::tests::create_test_merkle; + +#[cfg(test)] +mod tests { + use super::*; + use futures::StreamExt; + use test_case::test_case; + + #[tokio::test] + async fn iterate_empty() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let mut it = merkle.get_iter(Some(b"x"), root).unwrap(); + let next = it.next().await; + assert!(next.is_none()); + } + + #[test_case(Some(&[u8::MIN]); "Starting at first key")] + #[test_case(None; "No start specified")] + #[test_case(Some(&[128u8]); "Starting in middle")] + #[test_case(Some(&[u8::MAX]); "Starting at last key")] + #[tokio::test] + async fn iterate_many(start: Option<&[u8]>) { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + // insert all values from u8::MIN to u8::MAX, with the key and value the same + for k in u8::MIN..=u8::MAX { + merkle.insert([k], vec![k], root).unwrap(); + } + + let mut it = merkle.get_iter(start, root).unwrap(); + // we iterate twice because we should get a None then start over + for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { + let next = it.next().await.map(|kv| { + let (k, v) = kv.unwrap(); + assert_eq!(k, v); + k + }); + + assert_eq!(next, Some(vec![k])); + } + + assert!(it.next().await.is_none()); + } + + #[ignore] + #[tokio::test] + async fn get_branch_and_leaf() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let first_leaf = &[0x00, 0x00]; + let second_leaf = &[0x00, 0x0f]; + let branch = &[0x00]; + + merkle + .insert(first_leaf, first_leaf.to_vec(), root) + .unwrap(); + merkle + .insert(second_leaf, second_leaf.to_vec(), root) + .unwrap(); + + merkle.insert(branch, branch.to_vec(), root).unwrap(); + + let mut stream = merkle.get_iter(None::<&[u8]>, root).unwrap(); + + assert_eq!( + stream.next().await.unwrap().unwrap(), + (branch.to_vec(), branch.to_vec()) + ); + + assert_eq!( + stream.next().await.unwrap().unwrap(), + (first_leaf.to_vec(), first_leaf.to_vec()) + ); + + assert_eq!( + stream.next().await.unwrap().unwrap(), + (second_leaf.to_vec(), second_leaf.to_vec()) + ); + } + + #[ignore] + #[tokio::test] + async fn start_at_key_not_in_trie() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let first_key = 0x00; + let intermediate = 0x80; + + assert!(first_key < intermediate); + + let key_values = vec![ + vec![first_key], + vec![intermediate, intermediate], + vec![intermediate, intermediate, intermediate], + ]; + assert!(key_values[0] < key_values[1]); + assert!(key_values[1] < key_values[2]); + + for key in key_values.iter() { + merkle.insert(key, key.to_vec(), root).unwrap(); + } + + let mut stream = merkle.get_iter(Some([intermediate]), root).unwrap(); + + let first_expected = key_values[1].as_slice(); + let first = stream.next().await.unwrap().unwrap(); + + assert_eq!(first.0, first.1); + assert_eq!(first.0, first_expected); + + let second_expected = key_values[2].as_slice(); + let second = stream.next().await.unwrap().unwrap(); + + assert_eq!(second.0, second.1); + assert_eq!(second.0, second_expected); + + let done = stream.next().await; + + assert!(done.is_none()); + } +} From d3889839faccbcea03a0ab7d68c80ef99ec71724 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:31:21 -0800 Subject: [PATCH 0405/1053] Add Merkledb encoding schema (#425) --- firewood/benches/hashops.rs | 4 +- firewood/src/merkle.rs | 57 ++++++++++++---- firewood/src/merkle/node.rs | 133 +++++++++++++++++++++++++++++++++--- 3 files changed, 169 insertions(+), 25 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 93dc9b3d4752..a03429117ad7 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -6,7 +6,7 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::{ db::{BatchOp, DbConfig}, - merkle::{Merkle, TrieHash, TRIE_HASH_LEN}, + merkle::{MerkleWithEncoder, TrieHash, TRIE_HASH_LEN}, shale::{ cached::PlainMem, compact::{CompactHeader, CompactSpace}, @@ -104,7 +104,7 @@ fn bench_merkle(criterion: &mut Criterion) { ) .unwrap(); - let merkle = Merkle::new(Box::new(store)); + let merkle = MerkleWithEncoder::new(Box::new(store)); let root = merkle.init_root().unwrap(); let keys: Vec> = repeat_with(|| { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7e64c3f023dd..f567c193f8d2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,7 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; -use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; +use crate::shale::{ + self, cached::PlainMem, compact::CompactSpace, disk_address::DiskAddress, ObjWriteError, + ShaleError, ShaleStore, +}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use sha3::Digest; @@ -28,6 +31,7 @@ pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; type ParentRefs<'a> = Vec<(ObjRef<'a>, u8)>; type ParentAddresses = Vec<(DiskAddress, u8)>; +pub type MerkleWithEncoder = Merkle, Bincode>; #[derive(Debug, Error)] pub enum MerkleError { @@ -1399,6 +1403,8 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { #[cfg(test)] mod tests { + use crate::merkle::node::PlainCodec; + use super::*; use node::tests::{extension, leaf}; use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; @@ -1412,7 +1418,11 @@ mod tests { assert_eq!(n, nibbles); } - pub(super) fn create_test_merkle() -> Merkle, Bincode> { + fn create_generic_test_merkle<'de, T>() -> Merkle, T> + where + T: BinarySerde, + EncodedNode: serde::Serialize + serde::Deserialize<'de>, + { const RESERVED: usize = 0x1000; let mut dm = shale::cached::DynamicMem::new(0x10000, 0); @@ -1443,9 +1453,13 @@ mod tests { Merkle::new(store) } - fn branch(value: Vec, encoded_child: Option>) -> Node { + pub(super) fn create_test_merkle() -> Merkle, Bincode> { + create_generic_test_merkle::() + } + + fn branch(value: Option>, encoded_child: Option>) -> Node { let children = Default::default(); - let value = Some(Data(value)); + let value = value.map(Data); let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { @@ -1462,9 +1476,9 @@ mod tests { #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(b"value".to_vec(), None); "branch without chd")] - #[test_case(branch(Vec::new(), None); "branch without value and chd")] + #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(Some(b"value".to_vec()), None); "branch without chd")] + #[test_case(branch(None, None); "branch without value and chd")] #[test_case(extension(vec![1, 2, 3], DiskAddress::null(), vec![4, 5].into()) ; "extension without child address")] fn encode_(node: Node) { let merkle = create_test_merkle(); @@ -1479,17 +1493,32 @@ mod tests { #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(b"value".to_vec(), vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(b"value".to_vec(), None); "branch without chd")] - #[test_case(branch(Vec::new(), None); "branch without value and chd")] - fn node_encode_decode_(node: Node) { + #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(Some(b"value".to_vec()), None); "branch without chd")] + #[test_case(branch(None, None); "branch without value and chd")] + fn node_encode_decode(node: Node) { let merkle = create_test_merkle(); let node_ref = merkle.put_node(node.clone()).unwrap(); + let encoded = merkle.encode(&node_ref).unwrap(); let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); - let new_node_hash = new_node.get_root_hash(merkle.store.as_ref()); - let expected_hash = node.get_root_hash(merkle.store.as_ref()); - assert_eq!(new_node_hash, expected_hash); + + assert_eq!(node, new_node); + } + + #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] + #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] + #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(Some(b"value".to_vec()), Some(Vec::new())); "branch with empty chd")] + #[test_case(branch(Some(Vec::new()), vec![1, 2, 3].into()); "branch with empty value")] + fn node_encode_decode_plain(node: Node) { + let merkle = create_generic_test_merkle::(); + let node_ref = merkle.put_node(node.clone()).unwrap(); + + let encoded = merkle.encode(&node_ref).unwrap(); + let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); + + assert_eq!(node, new_node); } #[test] diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 5e6e9bc2d3e8..74ac9336dc98 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -8,7 +8,11 @@ use crate::{ use bincode::{Error, Options}; use bitflags::bitflags; use enum_as_inner::EnumAsInner; -use serde::{de::DeserializeOwned, ser::SerializeSeq, Deserialize, Serialize}; +use serde::{ + de::DeserializeOwned, + ser::{SerializeSeq, SerializeTuple}, + Deserialize, Serialize, +}; use sha3::{Digest, Keccak256}; use std::{ fmt::Debug, @@ -482,6 +486,7 @@ impl EncodedNode { } } } + pub enum EncodedNodeType { Leaf(LeafNode), Branch { @@ -490,6 +495,91 @@ pub enum EncodedNodeType { }, } +// TODO: probably can merge with `EncodedNodeType`. +#[derive(Debug, Deserialize)] +struct EncodedBranchNode { + chd: Vec<(u64, Vec)>, + data: Option>, + path: Vec, +} + +// Note that the serializer passed in should always be the same type as T in EncodedNode. +impl Serialize for EncodedNode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let n = match &self.node { + EncodedNodeType::Leaf(n) => { + let data = Some(n.data.to_vec()); + let chd: Vec<(u64, Vec)> = Default::default(); + let path = from_nibbles(&n.path.encode(true)).collect(); + EncodedBranchNode { chd, data, path } + } + EncodedNodeType::Branch { children, value } => { + let chd: Vec<(u64, Vec)> = children + .iter() + .enumerate() + .filter_map(|(i, c)| c.as_ref().map(|c| (i as u64, c))) + .map(|(i, c)| { + if c.len() >= TRIE_HASH_LEN { + (i, Keccak256::digest(c).to_vec()) + } else { + (i, c.to_vec()) + } + }) + .collect(); + + let data = value.as_ref().map(|v| v.0.to_vec()); + EncodedBranchNode { + chd, + data, + path: Vec::new(), + } + } + }; + + let mut s = serializer.serialize_tuple(3)?; + s.serialize_element(&n.chd)?; + s.serialize_element(&n.data)?; + s.serialize_element(&n.path)?; + s.end() + } +} + +impl<'de> Deserialize<'de> for EncodedNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let node: EncodedBranchNode = Deserialize::deserialize(deserializer)?; + if node.chd.is_empty() { + let data = if let Some(d) = node.data { + Data(d) + } else { + Data(Vec::new()) + }; + + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&node.path).into_iter()).0; + let node = EncodedNodeType::Leaf(LeafNode { path, data }); + Ok(Self::new(node)) + } else { + let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + let value = node.data.map(Data); + + for (i, chd) in node.chd { + children[i as usize] = Some(chd); + } + + let node = EncodedNodeType::Branch { + children: children.into(), + value, + }; + Ok(Self::new(node)) + } + } +} + // Note that the serializer passed in should always be the same type as T in EncodedNode. impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result { @@ -566,10 +656,7 @@ impl<'de> Deserialize<'de> for EncodedNode { path, data: Data(data), }); - Ok(Self { - node, - phantom: PhantomData, - }) + Ok(Self::new(node)) } BranchNode::MSIZE => { let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); @@ -601,10 +688,7 @@ impl<'de> Deserialize<'de> for EncodedNode { children: children.into(), value, }; - Ok(Self { - node, - phantom: PhantomData, - }) + Ok(Self::new(node)) } size => Err(D::Error::custom(format!("invalid size: {size}"))), } @@ -666,6 +750,37 @@ impl BinarySerde for Bincode { } } +pub struct PlainCodec(pub bincode::DefaultOptions); + +impl Debug for PlainCodec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "PlainCodec") + } +} + +impl BinarySerde for PlainCodec { + type SerializeError = bincode::Error; + type DeserializeError = Self::SerializeError; + + fn new() -> Self { + Self(bincode::DefaultOptions::new()) + } + + fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError> { + // Serializes the object directly into a Writer without include the length. + let mut writer = Vec::new(); + self.0.serialize_into(&mut writer, t)?; + Ok(writer) + } + + fn deserialize_impl<'de, T: Deserialize<'de>>( + &self, + bytes: &'de [u8], + ) -> Result { + self.0.deserialize(bytes) + } +} + #[cfg(test)] pub(super) mod tests { use std::array::from_fn; From b03a225bf158ef5da920670ccda549f33acb90e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:43:08 -0500 Subject: [PATCH 0406/1053] build(deps): update aquamarine requirement from 0.3.1 to 0.4.0 (#434) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8ae8ea51f25b..85de87d3f054 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -16,7 +16,7 @@ homepage = "https://avalabs.org" readme = "../README.md" [dependencies] -aquamarine = "0.3.1" +aquamarine = "0.4.0" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" From e107c6e40e1814c31e364dba418fef6a8811db80 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 13 Dec 2023 14:59:51 -0800 Subject: [PATCH 0407/1053] Update grpc-testtool README with specifics (#428) --- grpc-testtool/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index 4733395b5c7c..003f72d1b4ac 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -19,3 +19,29 @@ There are 3 RPC specs that must be implemented: 2. The sync proto, which supports retrieving range and change proofs 3. The process-server proto, which currently only retrieves metrics +# Running + +To run a single test and make sure things are working, first check out and build the go and rust code. +These have to be in the same directory. See the corresponding README for specific build requirements. + +```sh +BASE=$HOME +cd $BASE && git clone git@github.com:ava-labs/merkledb-tester.git +cd $BASE && git clone git@github.com:ava-labs/firewood.git +``` + +Then, build the rust process server and symlink it to where the testtool expects it: + +```sh +cd $BASE/firewood +cargo build --release +ln -sf $BASE/firewood/target/release/process-server $BASE/merkledb-tester/process/process-server +``` + +Then, run the test you want: + +```sh +cd $BASE/merkledb-tester +go test -timeout 2m -run TestConsistency github.com/ava-labs/merkledb-tester/tests/consistency -v +go test -timeout 2m -run TestPutPerformance github.com/ava-labs/merkledb-tester/tests/performance -v +``` From dd65b1117a37650640ed62e93fe524583d9a946e Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 14 Dec 2023 22:40:27 -0800 Subject: [PATCH 0408/1053] Make encode test generic to cover all schemas (#436) --- firewood/src/merkle.rs | 38 ++++++++++++++++--------------------- firewood/src/merkle/node.rs | 2 ++ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index f567c193f8d2..81648f568a60 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1491,28 +1491,22 @@ mod tests { assert_eq!(encoded, new_node_encoded); } - #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] - #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(Some(b"value".to_vec()), None); "branch without chd")] - #[test_case(branch(None, None); "branch without value and chd")] - fn node_encode_decode(node: Node) { - let merkle = create_test_merkle(); - let node_ref = merkle.put_node(node.clone()).unwrap(); - - let encoded = merkle.encode(&node_ref).unwrap(); - let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); - - assert_eq!(node, new_node); - } - - #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] - #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(Some(b"value".to_vec()), Some(Vec::new())); "branch with empty chd")] - #[test_case(branch(Some(Vec::new()), vec![1, 2, 3].into()); "branch with empty value")] - fn node_encode_decode_plain(node: Node) { - let merkle = create_generic_test_merkle::(); + #[test_case(Bincode::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with Bincode")] + #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with Bincode")] + #[test_case(Bincode::new(), branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd with Bincode")] + #[test_case(Bincode::new(), branch(Some(b"value".to_vec()), None); "branch without chd with Bincode")] + #[test_case(Bincode::new(), branch(None, None); "branch without value and chd with Bincode")] + #[test_case(PlainCodec::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with PlainCodec")] + #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with PlainCodec")] + #[test_case(PlainCodec::new(), branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd with PlainCodec")] + #[test_case(PlainCodec::new(), branch(Some(b"value".to_vec()), Some(Vec::new())); "branch with empty chd with PlainCodec")] + #[test_case(PlainCodec::new(), branch(Some(Vec::new()), vec![1, 2, 3].into()); "branch with empty value with PlainCodec")] + fn node_encode_decode(_codec: T, node: Node) + where + T: BinarySerde, + for<'de> EncodedNode: serde::Serialize + serde::Deserialize<'de>, + { + let merkle = create_generic_test_merkle::(); let node_ref = merkle.put_node(node.clone()).unwrap(); let encoded = merkle.encode(&node_ref).unwrap(); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 74ac9336dc98..e3c672a69eb8 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -722,6 +722,7 @@ pub trait BinarySerde { ) -> Result; } +#[derive(Default)] pub struct Bincode(pub bincode::DefaultOptions); impl Debug for Bincode { @@ -750,6 +751,7 @@ impl BinarySerde for Bincode { } } +#[derive(Default)] pub struct PlainCodec(pub bincode::DefaultOptions); impl Debug for PlainCodec { From 57e8698ef2b070833e3d324aacedc574b722239b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 15 Dec 2023 10:50:06 -0800 Subject: [PATCH 0409/1053] Add nibbles_into_bytes for an arbitrary iterator (#435) Co-authored-by: Richard Pringle --- firewood/src/merkle/stream.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 4186d7e22b4d..5a8fdc430e8f 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -287,11 +287,27 @@ fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec { + fn nibbles_into_bytes(&mut self) -> Vec { + let mut data = Vec::with_capacity(self.size_hint().0 / 2); + + while let (Some(hi), Some(lo)) = (self.next(), self.next()) { + data.push((hi << 4) + lo); + } + + data + } +} +impl> IntoBytes for T {} + #[cfg(test)] use super::tests::create_test_merkle; #[cfg(test)] mod tests { + use crate::nibbles::Nibbles; + use super::*; use futures::StreamExt; use test_case::test_case; @@ -412,4 +428,21 @@ mod tests { assert!(done.is_none()); } + + #[test] + fn remaining_bytes() { + let data = &[1]; + let nib: Nibbles<'_, 0> = Nibbles::<0>::new(data); + let mut it = nib.into_iter(); + assert_eq!(it.nibbles_into_bytes(), data.to_vec()); + } + + #[test] + fn remaining_bytes_off() { + let data = &[1]; + let nib: Nibbles<'_, 0> = Nibbles::<0>::new(data); + let mut it = nib.into_iter(); + it.next(); + assert_eq!(it.nibbles_into_bytes(), vec![]); + } } From 17e53d267e398c95a5ea22666314cecb9b1391b6 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 15 Dec 2023 15:52:48 -0500 Subject: [PATCH 0410/1053] Handle Extension-nodes in Iterator (#430) --- firewood/src/db.rs | 14 +- firewood/src/merkle.rs | 32 +- firewood/src/merkle/stream.rs | 861 ++++++++++++++++++++++++---------- fwdctl/src/dump.rs | 2 +- 4 files changed, 636 insertions(+), 273 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 282cf5a18517..ad05f24ee153 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -318,13 +318,15 @@ impl + Send + Sync> api::DbView for DbRev { } impl + Send + Sync> DbRev { - pub fn stream( + pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { + self.merkle.iter(self.header.kv_root) + } + + pub fn stream_from( &self, - start_key: Option, - ) -> Result, api::Error> { - self.merkle - .get_iter(start_key, self.header.kv_root) - .map_err(|e| api::Error::InternalError(Box::new(e))) + start_key: Box<[u8]>, + ) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { + self.merkle.iter_from(self.header.kv_root, start_key) } fn flush_dirty(&mut self) -> Option<()> { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 81648f568a60..7d9216d3a58d 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -24,7 +24,6 @@ pub use node::{ NodeType, PartialPath, }; pub use proof::{Proof, ProofError}; -use stream::IteratorState; pub use stream::MerkleKeyValueStream; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; @@ -1221,16 +1220,16 @@ impl + Send + Sync, T> Merkle { self.store.flush_dirty() } - pub(crate) fn get_iter>( + pub(crate) fn iter(&self, root: DiskAddress) -> MerkleKeyValueStream<'_, S, T> { + MerkleKeyValueStream::new(self, root) + } + + pub(crate) fn iter_from( &self, - key: Option, root: DiskAddress, - ) -> Result, MerkleError> { - Ok(MerkleKeyValueStream { - key_state: IteratorState::new(key), - merkle_root: root, - merkle: self, - }) + key: Box<[u8]>, + ) -> MerkleKeyValueStream<'_, S, T> { + MerkleKeyValueStream::from_key(self, root, key) } pub(super) async fn range_proof( @@ -1241,13 +1240,15 @@ impl + Send + Sync, T> Merkle { limit: Option, ) -> Result, Vec>>, api::Error> { // limit of 0 is always an empty RangeProof - if let Some(0) = limit { + if limit == Some(0) { return Ok(None); } - let mut stream = self - .get_iter(first_key, root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let mut stream = match first_key { + // TODO: fix the call-site to force the caller to do the allocation + Some(key) => self.iter_from(root, key.as_ref().to_vec().into_boxed_slice()), + None => self.iter(root), + }; // fetch the first key from the stream let first_result = stream.next().await; @@ -1264,7 +1265,7 @@ impl + Send + Sync, T> Merkle { .map_err(|e| api::Error::InternalError(Box::new(e)))?; let limit = limit.map(|old_limit| old_limit - 1); - let mut middle = vec![(first_key, first_data)]; + let mut middle = vec![(first_key.into_vec(), first_data)]; // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested @@ -1283,8 +1284,9 @@ impl + Send + Sync, T> Merkle { }; // keep going if the key returned is less than the last key requested - ready(kv.0.as_slice() <= last_key.as_ref()) + ready(&*kv.0 <= last_key.as_ref()) }) + .map(|kv_result| kv_result.map(|(k, v)| (k.into_vec(), v))) .try_collect::, Vec)>>() .await?, ); diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 5a8fdc430e8f..748c0b5de4a7 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -1,290 +1,398 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{node::Node, LeafNode, Merkle, MerkleError, NodeType, ObjRef}; +use super::{node::Node, BranchNode, Merkle, NodeType, ObjRef}; use crate::{ shale::{DiskAddress, ShaleStore}, v2::api, }; -use futures::Stream; +use futures::{stream::FusedStream, Stream}; +use helper_types::{Either, MustUse}; use std::task::Poll; -pub(super) enum IteratorState<'a> { - /// Start iterating at the beginning of the trie, - /// returning the lowest key/value pair first - StartAtBeginning, +type Key = Box<[u8]>; +type Value = Vec; + +enum IteratorState<'a> { /// Start iterating at the specified key - StartAtKey(Vec), - /// Continue iterating after the given last_node and parents + StartAtKey(Key), + /// Continue iterating after the last node in the `visited_node_path` Iterating { - last_node: ObjRef<'a>, - parents: Vec<(ObjRef<'a>, u8)>, + visited_node_path: Vec<(ObjRef<'a>, u8)>, }, } + impl IteratorState<'_> { - pub(super) fn new>(starting: Option) -> Self { - match starting { - None => Self::StartAtBeginning, - Some(key) => Self::StartAtKey(key.as_ref().to_vec()), - } + fn new() -> Self { + Self::StartAtKey(vec![].into_boxed_slice()) } -} -// The default state is to start at the beginning -impl<'a> Default for IteratorState<'a> { - fn default() -> Self { - Self::StartAtBeginning + fn with_key(key: Key) -> Self { + Self::StartAtKey(key) } } /// A MerkleKeyValueStream iterates over keys/values for a merkle trie. -/// This iterator is not fused. If you read past the None value, you start -/// over at the beginning. If you need a fused iterator, consider using -/// std::iter::fuse pub struct MerkleKeyValueStream<'a, S, T> { - pub(super) key_state: IteratorState<'a>, - pub(super) merkle_root: DiskAddress, - pub(super) merkle: &'a Merkle, + key_state: IteratorState<'a>, + merkle_root: DiskAddress, + merkle: &'a Merkle, +} + +impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleKeyValueStream<'a, S, T> { + fn is_terminated(&self) -> bool { + matches!(&self.key_state, IteratorState::Iterating { visited_node_path } if visited_node_path.is_empty()) + } +} + +impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { + pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress) -> Self { + let key_state = IteratorState::new(); + + Self { + merkle, + key_state, + merkle_root, + } + } + + pub(super) fn from_key(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { + let key_state = IteratorState::with_key(key); + + Self { + merkle, + key_state, + merkle_root, + } + } } impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<'a, S, T> { - type Item = Result<(Vec, Vec), api::Error>; + type Item = Result<(Key, Value), api::Error>; fn poll_next( mut self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> Poll> { - // Note that this sets the key_state to StartAtBeginning temporarily - let found_key = match std::mem::take(&mut self.key_state) { - IteratorState::StartAtBeginning => { - let root_node = self - .merkle - .get_node(self.merkle_root) + // destructuring is necessary here because we need mutable access to `key_state` + // at the same time as immutable access to `merkle` + let Self { + key_state, + merkle_root, + merkle, + } = &mut *self; + + match key_state { + IteratorState::StartAtKey(key) => { + let root_node = merkle + .get_node(*merkle_root) .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let mut last_node = root_node; - let mut parents = vec![]; - let leaf = loop { - match last_node.inner() { - NodeType::Branch(branch) => { - let Some((leftmost_position, leftmost_address)) = branch - .children - .iter() - .enumerate() - .filter_map(|(i, addr)| addr.map(|addr| (i, addr))) - .next() - else { - // we already exhausted the branch node. This happens with an empty trie - // ... or a corrupt one - return if parents.is_empty() { - // empty trie - Poll::Ready(None) - } else { - // branch with NO children, not at the top - Poll::Ready(Some(Err(api::Error::InternalError(Box::new( - MerkleError::ParentLeafBranch, - ))))) - }; - }; - - let next = self - .merkle - .get_node(leftmost_address) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - parents.push((last_node, leftmost_position as u8)); - - last_node = next; - } - NodeType::Leaf(leaf) => break leaf, - NodeType::Extension(_) => todo!(), + + // traverse the trie along each nibble until we find a node with a value + // TODO: merkle.iter_by_key(key) will simplify this entire code-block. + let (found_node, mut visited_node_path) = { + let mut visited_node_path = vec![]; + + let found_node = merkle + .get_node_by_key_with_callbacks( + root_node, + &key, + |node_addr, i| visited_node_path.push((node_addr, i)), + |_, _| {}, + ) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + let mut visited_node_path = visited_node_path + .into_iter() + .map(|(node, pos)| merkle.get_node(node).map(|node| (node, pos))) + .collect::, _>>() + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + let last_visited_node_not_branch = visited_node_path + .last() + .map(|(node, _)| { + matches!(node.inner(), NodeType::Leaf(_) | NodeType::Extension(_)) + }) + .unwrap_or_default(); + + // we only want branch in the visited node-path to start + if last_visited_node_not_branch { + visited_node_path.pop(); } + + (found_node, visited_node_path) }; - // last_node should have a leaf; compute the key and value - let current_key = key_from_parents_and_leaf(&parents, leaf); + if let Some(found_node) = found_node { + let value = match found_node.inner() { + NodeType::Branch(branch) => branch.value.as_ref(), + NodeType::Leaf(leaf) => Some(&leaf.data), + NodeType::Extension(_) => None, + }; + + let next_result = value.map(|value| { + let value = value.to_vec(); + + Ok((std::mem::take(key), value)) + }); - self.key_state = IteratorState::Iterating { last_node, parents }; + visited_node_path.push((found_node, 0)); - current_key + self.key_state = IteratorState::Iterating { visited_node_path }; + + return Poll::Ready(next_result); + } + + self.key_state = IteratorState::Iterating { visited_node_path }; + + self.poll_next(_cx) } - IteratorState::StartAtKey(key) => { - // TODO: support finding the next key after K - let root_node = self - .merkle - .get_node(self.merkle_root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let (found_node, parents) = self - .merkle - .get_node_and_parents_by_key(root_node, &key) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; + IteratorState::Iterating { visited_node_path } => { + let next = find_next_result(merkle, visited_node_path) + .map_err(|e| api::Error::InternalError(Box::new(e))) + .transpose(); - let Some(last_node) = found_node else { - return Poll::Ready(None); - }; + Poll::Ready(next) + } + } + } +} + +enum NodeRef<'a> { + New(ObjRef<'a>), + Visited(ObjRef<'a>), +} + +#[derive(Debug)] +enum InnerNode<'a> { + New(&'a NodeType), + Visited(&'a NodeType), +} + +impl<'a> NodeRef<'a> { + fn inner(&self) -> InnerNode<'_> { + match self { + Self::New(node) => InnerNode::New(node.inner()), + Self::Visited(node) => InnerNode::Visited(node.inner()), + } + } + + fn into_node(self) -> ObjRef<'a> { + match self { + Self::New(node) => node, + Self::Visited(node) => node, + } + } +} + +fn find_next_result<'a, S: ShaleStore, T>( + merkle: &'a Merkle, + visited_path: &mut Vec<(ObjRef<'a>, u8)>, +) -> Result, super::MerkleError> { + let next = find_next_node_with_data(merkle, visited_path)?.map(|(next_node, value)| { + let partial_path = match next_node.inner() { + NodeType::Leaf(leaf) => leaf.path.iter().copied(), + NodeType::Extension(extension) => extension.path.iter().copied(), + _ => [].iter().copied(), + }; + + let key = key_from_nibble_iter(nibble_iter_from_parents(visited_path).chain(partial_path)); + + visited_path.push((next_node, 0)); + + (key, value) + }); + + Ok(next) +} + +fn find_next_node_with_data<'a, S: ShaleStore, T>( + merkle: &'a Merkle, + visited_path: &mut Vec<(ObjRef<'a>, u8)>, +) -> Result, Vec)>, super::MerkleError> { + use InnerNode::*; + + let Some((visited_parent, visited_pos)) = visited_path.pop() else { + return Ok(None); + }; + + let mut node = NodeRef::Visited(visited_parent); + let mut pos = visited_pos; + let mut first_loop = true; + + loop { + match node.inner() { + New(NodeType::Leaf(leaf)) => { + let value = leaf.data.to_vec(); + return Ok(Some((node.into_node(), value))); + } - let returned_key_value = match last_node.inner() { - NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), - NodeType::Leaf(leaf) => (key, leaf.data.to_vec()), - NodeType::Extension(_) => todo!(), + Visited(NodeType::Leaf(_)) | Visited(NodeType::Extension(_)) => { + let Some((next_parent, next_pos)) = visited_path.pop() else { + return Ok(None); }; - self.key_state = IteratorState::Iterating { last_node, parents }; + node = NodeRef::Visited(next_parent); + pos = next_pos; + } + + New(NodeType::Extension(extension)) => { + let child = merkle.get_node(extension.chd())?; - return Poll::Ready(Some(Ok(returned_key_value))); + pos = 0; + visited_path.push((node.into_node(), pos)); + + node = NodeRef::New(child); } - IteratorState::Iterating { - last_node, - mut parents, - } => { - match last_node.inner() { - NodeType::Branch(branch) => { - // previously rendered the value from a branch node, so walk down to the first available child - let Some((child_position, child_address)) = branch - .children - .iter() - .enumerate() - .filter_map(|(child_position, &addr)| { - addr.map(|addr| (child_position, addr)) - }) - .next() - else { - // Branch node with no children? - return Poll::Ready(Some(Err(api::Error::InternalError(Box::new( - MerkleError::ParentLeafBranch, - ))))); - }; - - parents.push((last_node, child_position as u8)); // remember where we walked down from - - let current_node = self - .merkle - .get_node(child_address) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let found_key = key_from_parents(&parents); - - self.key_state = IteratorState::Iterating { - // continue iterating from here - last_node: current_node, - parents, - }; - - found_key - } - NodeType::Leaf(leaf) => { - let mut next = parents.pop().map(|(node, position)| (node, Some(position))); - loop { - match next { - None => return Poll::Ready(None), - Some((parent, child_position)) => { - // Assume all parents are branch nodes - let children = parent.inner().as_branch().unwrap().chd(); - - // we use wrapping_add here because the value might be u8::MAX indicating that - // we want to go down branch - let start_position = - child_position.map(|pos| pos + 1).unwrap_or_default(); - - let Some((found_position, found_address)) = children - .iter() - .enumerate() - .skip(start_position as usize) - .filter_map(|(offset, addr)| { - addr.map(|addr| (offset as u8, addr)) - }) - .next() - else { - next = parents - .pop() - .map(|(node, position)| (node, Some(position))); - continue; - }; - - // we push (node, None) which will start at the beginning of the next branch node - let child = self - .merkle - .get_node(found_address) - .map(|node| (node, None)) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - // stop_descending if: - // - on a branch and it has a value; OR - // - on a leaf - let stop_descending = match child.0.inner() { - NodeType::Branch(branch) => branch.value.is_some(), - NodeType::Leaf(_) => true, - NodeType::Extension(_) => todo!(), - }; - - next = Some(child); - - parents.push((parent, found_position)); - - if stop_descending { - break; - } - } - } - } - // recompute current_key - // TODO: Can we keep current_key updated as we walk the tree instead of building it from the top all the time? - let current_key = key_from_parents_and_leaf(&parents, leaf); - - self.key_state = IteratorState::Iterating { - last_node: next.unwrap().0, - parents, - }; - - current_key - } - NodeType::Extension(_) => todo!(), + Visited(NodeType::Branch(branch)) => { + // if the first node that we check is a visited branch, that means that the branch had a value + // and we need to visit the first child, for all other cases, we need to visit the next child + let compare_op = if first_loop { + ::ge // >= + } else { + ::gt + }; + + let children = get_children_iter(branch) + .filter(move |(_, child_pos)| compare_op(child_pos, &pos)); + + let found_next_node = + next_node(merkle, children, visited_path, &mut node, &mut pos)?; + + if !found_next_node { + return Ok(None); } } - }; - // figure out the value to return from the state - // if we get here, we're sure to have something to return - // TODO: It's possible to return a reference to the data since the last_node is - // saved in the iterator - let return_value = match &self.key_state { - IteratorState::Iterating { - last_node, - parents: _, - } => { - let value = match last_node.inner() { - NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), - NodeType::Leaf(leaf) => leaf.data.to_vec(), - NodeType::Extension(_) => todo!(), - }; + New(NodeType::Branch(branch)) => { + if let Some(value) = branch.value.as_ref() { + let value = value.to_vec(); + return Ok(Some((node.into_node(), value))); + } + + let children = get_children_iter(branch); - (found_key, value) + let found_next_node = + next_node(merkle, children, visited_path, &mut node, &mut pos)?; + + if !found_next_node { + return Ok(None); + } } - _ => unreachable!(), - }; + } - Poll::Ready(Some(Ok(return_value))) + first_loop = false; } } -/// Compute a key from a set of parents -fn key_from_parents(parents: &[(ObjRef, u8)]) -> Vec { - parents[1..] - .chunks_exact(2) - .map(|parents| (parents[0].1 << 4) + parents[1].1) - .collect::>() +fn get_children_iter(branch: &BranchNode) -> impl Iterator { + branch + .children + .into_iter() + .enumerate() + .filter_map(|(pos, child_addr)| child_addr.map(|child_addr| (child_addr, pos as u8))) } -fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec { - let mut iter = parents[1..] + +/// This function is a little complicated because we need to be able to early return from the parent +/// when we return `false`. `MustUse` forces the caller to check the inner value of `Result::Ok`. +/// It also replaces `node` +fn next_node<'a, S, T, Iter>( + merkle: &'a Merkle, + mut children: Iter, + parents: &mut Vec<(ObjRef<'a>, u8)>, + node: &mut NodeRef<'a>, + pos: &mut u8, +) -> Result, super::MerkleError> +where + Iter: Iterator, + S: ShaleStore, +{ + if let Some((child_addr, child_pos)) = children.next() { + let child = merkle.get_node(child_addr)?; + + *pos = child_pos; + let node = std::mem::replace(node, NodeRef::New(child)); + parents.push((node.into_node(), *pos)); + } else { + let Some((next_parent, next_pos)) = parents.pop() else { + return Ok(false.into()); + }; + + *node = NodeRef::Visited(next_parent); + *pos = next_pos; + } + + Ok(true.into()) +} + +/// create an iterator over the key-nibbles from all parents _excluding_ the sentinal node. +fn nibble_iter_from_parents<'a>(parents: &'a [(ObjRef, u8)]) -> impl Iterator + 'a { + parents .iter() - .map(|parent| parent.1) - .chain(leaf.path.to_vec()); - let mut data = Vec::with_capacity(iter.size_hint().0); - while let (Some(hi), Some(lo)) = (iter.next(), iter.next()) { + .skip(1) // always skip the sentinal node + .flat_map(|(parent, child_nibble)| match parent.inner() { + NodeType::Branch(_) => Either::Left(std::iter::once(*child_nibble)), + NodeType::Extension(extension) => Either::Right(extension.path.iter().copied()), + NodeType::Leaf(leaf) => Either::Right(leaf.path.iter().copied()), + }) +} + +fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { + let mut data = Vec::with_capacity(nibbles.size_hint().0 / 2); + + while let (Some(hi), Some(lo)) = (nibbles.next(), nibbles.next()) { data.push((hi << 4) + lo); } - data + + data.into_boxed_slice() +} + +mod helper_types { + use std::ops::Not; + + /// Enums enable stack-based dynamic-dispatch as opposed to heap-based `Box`. + /// This helps us with match arms that return different types that implement the same trait. + /// It's possible that [rust-lang/rust#63065](https://github.com/rust-lang/rust/issues/63065) will make this unnecessary. + /// + /// And this can be replaced by the `either` crate from crates.io if we ever need more functionality. + pub(super) enum Either { + Left(T), + Right(U), + } + + impl Iterator for Either + where + T: Iterator, + U: Iterator, + { + type Item = T::Item; + + fn next(&mut self) -> Option { + match self { + Self::Left(left) => left.next(), + Self::Right(right) => right.next(), + } + } + } + + #[must_use] + pub(super) struct MustUse(T); + + impl From for MustUse { + fn from(t: T) -> Self { + Self(t) + } + } + + impl Not for MustUse { + type Output = T::Output; + + fn not(self) -> Self::Output { + self.0.not() + } + } } // CAUTION: only use with nibble iterators @@ -316,9 +424,8 @@ mod tests { async fn iterate_empty() { let merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); - let mut it = merkle.get_iter(Some(b"x"), root).unwrap(); - let next = it.next().await; - assert!(next.is_none()); + let stream = merkle.iter_from(root, b"x".to_vec().into_boxed_slice()); + check_stream_is_done(stream).await; } #[test_case(Some(&[u8::MIN]); "Starting at first key")] @@ -335,22 +442,78 @@ mod tests { merkle.insert([k], vec![k], root).unwrap(); } - let mut it = merkle.get_iter(start, root).unwrap(); + let mut stream = match start { + Some(start) => merkle.iter_from(root, start.to_vec().into_boxed_slice()), + None => merkle.iter(root), + }; + // we iterate twice because we should get a None then start over for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { - let next = it.next().await.map(|kv| { + let next = stream.next().await.map(|kv| { let (k, v) = kv.unwrap(); - assert_eq!(k, v); + assert_eq!(&*k, &*v); k }); - assert_eq!(next, Some(vec![k])); + assert_eq!(next, Some(vec![k].into_boxed_slice())); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn fused_empty() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + check_stream_is_done(merkle.iter(root)).await; + } + + #[tokio::test] + async fn fused_full() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let last = vec![0x00, 0x00, 0x00]; + + let mut key_values = vec![vec![0x00], vec![0x00, 0x00], last.clone()]; + + // branchs with paths (or extensions) will be present as well as leaves with siblings + for kv in u8::MIN..=u8::MAX { + let mut last = last.clone(); + last.push(kv); + key_values.push(last); + } + + for kv in key_values.iter() { + merkle.insert(kv, kv.clone(), root).unwrap(); } - assert!(it.next().await.is_none()); + let mut stream = merkle.iter(root); + + for kv in key_values.iter() { + let next = stream.next().await.unwrap().unwrap(); + assert_eq!(&*next.0, &*next.1); + assert_eq!(&next.1, kv); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn root_with_empty_data() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let key = vec![].into_boxed_slice(); + let value = vec![0x00]; + + merkle.insert(&key, value.clone(), root).unwrap(); + + let mut stream = merkle.iter(root); + + assert_eq!(stream.next().await.unwrap().unwrap(), (key, value)); } - #[ignore] #[tokio::test] async fn get_branch_and_leaf() { let mut merkle = create_test_merkle(); @@ -369,25 +532,27 @@ mod tests { merkle.insert(branch, branch.to_vec(), root).unwrap(); - let mut stream = merkle.get_iter(None::<&[u8]>, root).unwrap(); + let mut stream = merkle.iter(root); assert_eq!( stream.next().await.unwrap().unwrap(), - (branch.to_vec(), branch.to_vec()) + (branch.to_vec().into_boxed_slice(), branch.to_vec()) ); assert_eq!( stream.next().await.unwrap().unwrap(), - (first_leaf.to_vec(), first_leaf.to_vec()) + (first_leaf.to_vec().into_boxed_slice(), first_leaf.to_vec()) ); assert_eq!( stream.next().await.unwrap().unwrap(), - (second_leaf.to_vec(), second_leaf.to_vec()) + ( + second_leaf.to_vec().into_boxed_slice(), + second_leaf.to_vec() + ) ); } - #[ignore] #[tokio::test] async fn start_at_key_not_in_trie() { let mut merkle = create_test_merkle(); @@ -410,23 +575,217 @@ mod tests { merkle.insert(key, key.to_vec(), root).unwrap(); } - let mut stream = merkle.get_iter(Some([intermediate]), root).unwrap(); + let mut stream = merkle.iter_from(root, vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); let first = stream.next().await.unwrap().unwrap(); - assert_eq!(first.0, first.1); - assert_eq!(first.0, first_expected); + assert_eq!(&*first.0, &*first.1); + assert_eq!(first.1, first_expected); let second_expected = key_values[2].as_slice(); let second = stream.next().await.unwrap().unwrap(); - assert_eq!(second.0, second.1); - assert_eq!(second.0, second_expected); + assert_eq!(&*second.0, &*second.1); + assert_eq!(second.1, second_expected); + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_on_branch_with_no_value() { + let sibling_path = 0x00; + let branch_path = 0x0f; + let children = 0..=0x0f; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + children.clone().for_each(|child_path| { + let key = vec![sibling_path, child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + }); + + let mut keys: Vec<_> = children + .map(|child_path| { + let key = vec![branch_path, child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + + key + }) + .collect(); + + keys.sort(); + + let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); + let keys = &keys[start..]; + + let mut stream = merkle.iter_from(root, vec![branch_path].into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_on_branch_with_value() { + let sibling_path = 0x00; + let branch_path = 0x0f; + let branch_key = vec![branch_path]; + + let children = (0..=0xf).map(|val| (val << 4) + val); // 0x00, 0x11, ... 0xff + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle + .insert(&branch_key, branch_key.clone(), root) + .unwrap(); + + children.clone().for_each(|child_path| { + let key = vec![sibling_path, child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + }); - let done = stream.next().await; + let mut keys: Vec<_> = children + .map(|child_path| { + let key = vec![branch_path, child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + + key + }) + .chain(Some(branch_key.clone())) + .collect(); + + keys.sort(); + + let start = keys.iter().position(|key| key == &branch_key).unwrap(); + let keys = &keys[start..]; + + let mut stream = merkle.iter_from(root, branch_key.into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_on_extension() { + let missing = 0x0a; + let children = (0..=0x0f).filter(|x| *x != missing); + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let keys: Vec<_> = children + .map(|child_path| { + let key = vec![child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + + key + }) + .collect(); + + let keys = &keys[(missing as usize)..]; + + let mut stream = merkle.iter_from(root, vec![missing].into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_between_siblings() { + let missing = 0xaa; + let children = (0..=0xf) + .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff + .filter(|x| *x != missing); + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let keys: Vec<_> = children + .map(|child_path| { + let key = vec![child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + + key + }) + .collect(); + + let keys = &keys[((missing >> 4) as usize)..]; + + let mut stream = merkle.iter_from(root, vec![missing].into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_greater_than_all_others() { + let greatest = 0xff; + let children = (0..=0xf) + .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff + .filter(|x| *x != greatest); + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let keys: Vec<_> = children + .map(|child_path| { + let key = vec![child_path]; + + merkle.insert(&key, key.clone(), root).unwrap(); + + key + }) + .collect(); + + let keys = &keys[((greatest >> 4) as usize)..]; + + let mut stream = merkle.iter_from(root, vec![greatest].into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } - assert!(done.is_none()); + async fn check_stream_is_done(mut stream: S) + where + S: FusedStream + Unpin, + { + assert!(stream.next().await.is_none()); + assert!(stream.is_terminated()); } #[test] diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 3ae12358aaf8..3468589939df 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -32,7 +32,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db = Db::new(opts.db.clone(), &cfg.build()).await?; let latest_hash = db.root_hash().await?; let latest_rev = db.revision(latest_hash).await?; - let mut stream = latest_rev.stream::>(None)?; + let mut stream = latest_rev.stream(); loop { match stream.next().await { None => break, From 33d4795584469650d03c462e5709b4a383ea07f8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 18 Dec 2023 09:42:49 -0800 Subject: [PATCH 0411/1053] Update testing instructions (#441) --- grpc-testtool/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index 003f72d1b4ac..074f173b90b8 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -41,7 +41,6 @@ ln -sf $BASE/firewood/target/release/process-server $BASE/merkledb-tester/proces Then, run the test you want: ```sh -cd $BASE/merkledb-tester -go test -timeout 2m -run TestConsistency github.com/ava-labs/merkledb-tester/tests/consistency -v -go test -timeout 2m -run TestPutPerformance github.com/ava-labs/merkledb-tester/tests/performance -v +cd $BASE/merkledb-tester/tests +go test -timeout 2m -run TestAll/Performance github.com/ava-labs/merkledb-tester/tests -v -count=1 ``` From d83e9527d0fa0910540c7a91e9b002d68e2a41a4 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 18 Dec 2023 16:10:30 -0500 Subject: [PATCH 0412/1053] fix readme instructions to run merkledb-tester (#445) --- grpc-testtool/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index 074f173b90b8..d1047359d2b1 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -42,5 +42,5 @@ Then, run the test you want: ```sh cd $BASE/merkledb-tester/tests -go test -timeout 2m -run TestAll/Performance github.com/ava-labs/merkledb-tester/tests -v -count=1 +go test -timeout 5m github.com/ava-labs/merkledb-tester/... -v -count=1 ``` From 675798d2a8c0b3b86d1abee5343e5b8d70f31b9f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 18 Dec 2023 20:30:55 -0800 Subject: [PATCH 0413/1053] Add environment vars to instructions (#446) --- grpc-testtool/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index d1047359d2b1..c3f2ecdd8138 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -41,6 +41,9 @@ ln -sf $BASE/firewood/target/release/process-server $BASE/merkledb-tester/proces Then, run the test you want: ```sh +export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +export CGO_ENABLED=1 +export GOPROXY=https://proxy.golang.org cd $BASE/merkledb-tester/tests go test -timeout 5m github.com/ava-labs/merkledb-tester/... -v -count=1 ``` From 11b3b69cde0df44c4174303e68b193c338c2a121 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 19 Dec 2023 12:09:16 -0500 Subject: [PATCH 0414/1053] Add docker instructions (#437) Co-authored-by: Ron Kuris --- README.docker.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 README.docker.md diff --git a/README.docker.md b/README.docker.md new file mode 100644 index 000000000000..07628ee5bfb1 --- /dev/null +++ b/README.docker.md @@ -0,0 +1,82 @@ +# Docker on Mac Compatibility + +Note: +Docker compatiblitiy is a work in progress. Please PR any changes here if you find a better way to do this. + +## Steps + +### Step 1 + +Install docker-desktop ([guide](https://docs.docker.com/desktop/install/mac-install/)) + +### Step 2 + +Setup a dev-environment ([guide](https://docs.docker.com/desktop/dev-environments/set-up/#set-up-a-dev-environment)) + +Here, you want to specifically pick a local-directory (the repo's directory) + +![image](https://github.com/ava-labs/firewood/assets/3286504/83d6b66d-19e3-4b59-bc73-f67cf68d7329) + +This is best because you can still do all your `git` stuff from the host. + +### Step 3 + +You will need the `Dev Containers` VSCODE extension, authored by Microsoft for this next step. + +Open your dev-environment with VSCODE. Until you do this, the volume might not be properly mounted. If you (dear reader) know of a better way to do this, please open a PR. VSCODE is very useful for its step-by-step debugger, but other than that, you can run whatever IDE you would like in the host environment and just open a shell in the container to run the tests. + +![image](https://github.com/ava-labs/firewood/assets/3286504/88c981cb-42b9-4b99-acec-fbca31cca652) + +### Step 4 + +Open a terminal in vscode OR exec into the container directly as follows + +```sh +# you don't need to do this if you open the terminal from vscode +# the container name here is "firewood-app-1", you should be able to see this in docker-desktop +docker exec -it --privileged -u root firewood-app-1 zsh +``` + +Once you're in the terminal you'll want to install the Rust toolset. You can find instructions [here](https://rustup.rs/) + +**!!! IMPORTANT !!!** + +Make sure you read the output of any commands that you run. `rustup` will likely ask you to `source` a file to add some tools to your `PATH`. + +You'll also need to install all the regular linux dependencies (if there is anything from this list that's missing, please add to this README) + +```sh +apt update +apt install vim +apt install build-essential +apt install protobuf-compiler +``` + +### Step 5 + +**!!! IMPORTANT !!!** + +You need to create a separate `CARGO_TARGET_DIR` that isn't volume mounted onto the host. `VirtioFS` (the default file-system) has some concurrency issues when dealing with sequential writes and reads to a volume that is mounted to the host. You can put a directory here for example: `/root/target`. + +For step-by-step debugging and development directly in the container, you will also **need to make sure that `rust-analyzer` is configured to point to the new target-directory instead of just default**. + +There are a couple of places where this can be setup. If you're a `zsh` user, you should add `export CARGO_TARGET_DIR=/root/target` to either `/root/.zshrc` or `/root/.bashrc`. +After adding the line, don't forget to `source` the file to make sure your current session is updated. + +### Step 6 + +Navigate to `/com.docker.devenvironments.code ` and run `cargo test`. If it worked, you are most of the way there! If it did not work, there are a couple of common issues. If the code will not compile, it's possible that your target directory isn't set up properly. Check inside `/root/target` to see if there are any build artifacts. If not, you might need to call `source ~/.zshrc` again (sub in whatever your preferred shell is). + +Now for vscode, you need to configure your `rust-analyzer` in the "remote-environment" (the Docker container). There are a couple of places to do this. First, you want to open `/root/.vscode-server/Machine/settings.json` and make sure that you have the following entry: + +```json +{ + "rust-analyzer.cargo.extraEnv": { + "CARGO_TARGET_DIR": "/root/target" + } +} +``` + +Then, you want to make sure that the terminal that's being used by the vscode instance (for the host system) is the same as your preferred terminal in the container to make sure that things work as expected. [Here are the docs](https://code.visualstudio.com/docs/terminal/profiles) to help you with setting up the proper profile. + +And that should be enough to get your started! Feel free to open an issue if you need any help debugging. From 21f877a1118f56f611f2cde4c8eafb0a75e4d0f5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 20 Dec 2023 14:38:45 -0800 Subject: [PATCH 0415/1053] Allow for a seed (#453) --- firewood/examples/insert.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 985813b9b01e..997ffac039aa 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,13 +5,16 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::{collections::HashMap, error::Error, ops::RangeInclusive, sync::Arc, time::Instant}; +use std::{ + borrow::BorrowMut as _, collections::HashMap, error::Error, ops::RangeInclusive, sync::Arc, + time::Instant, +}; use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, v2::api::{Db as _, DbView, Proposal}, }; -use rand::{distributions::Alphanumeric, Rng}; +use rand::{distributions::Alphanumeric, Rng, SeedableRng}; #[derive(Parser, Debug)] struct Args { @@ -25,6 +28,8 @@ struct Args { inserts: usize, #[arg(short, long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, + #[arg(short, long)] + seed: Option, } fn string_to_range(input: &str) -> Result, Box> { @@ -51,19 +56,25 @@ async fn main() -> Result<(), Box> { let keys = args.batch_keys; let start = Instant::now(); + let mut rng = if let Some(seed) = args.seed { + rand::rngs::StdRng::seed_from_u64(seed) + } else { + rand::rngs::StdRng::from_entropy() + }; + for _ in 0..args.inserts { - let keylen = rand::thread_rng().gen_range(args.keylen.clone()); - let datalen = rand::thread_rng().gen_range(args.datalen.clone()); + let keylen = rng.gen_range(args.keylen.clone()); + let datalen = rng.gen_range(args.datalen.clone()); let batch: Batch, Vec> = (0..keys) .map(|_| { ( - rand::thread_rng() + rng.borrow_mut() .sample_iter(&Alphanumeric) - .take(keylen as usize) + .take(keylen) .collect::>(), - rand::thread_rng() + rng.borrow_mut() .sample_iter(&Alphanumeric) - .take(datalen as usize) + .take(datalen) .collect::>(), ) }) From a68b5f8577d3058d9589414c13cda8e204bc741a Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:11:22 -0800 Subject: [PATCH 0416/1053] Only call `Obj.flush_dirty` in `drop` if it is actually dirty (#450) --- firewood/src/shale/mod.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 1a2d69a92fb7..542bff424d45 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -134,15 +134,18 @@ impl Obj { } pub fn flush_dirty(&mut self) { - if !self.value.is_mem_mapped() { - if let Some(new_value_len) = self.dirty.take() { - let mut new_value = vec![0; new_value_len as usize]; - // TODO: log error - self.value.write_mem_image(&mut new_value).unwrap(); - let offset = self.value.get_offset(); - let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); - bx.write(offset, &new_value); - } + // faster than calling `self.dirty.take()` on a `None` + if self.dirty.is_none() { + return; + } + + if let Some(new_value_len) = self.dirty.take() { + let mut new_value = vec![0; new_value_len as usize]; + // TODO: log error + self.value.write_mem_image(&mut new_value).unwrap(); + let offset = self.value.get_offset(); + let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); + bx.write(offset, &new_value); } } } @@ -232,9 +235,6 @@ pub trait Storable { fn deserialize(addr: usize, mem: &T) -> Result where Self: Sized; - fn is_mem_mapped(&self) -> bool { - false - } } pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { @@ -303,9 +303,6 @@ impl StoredView { fn write(&mut self) -> &mut T { &mut self.decoded } - fn is_mem_mapped(&self) -> bool { - self.decoded.is_mem_mapped() - } } impl StoredView { From 8501dee51af8de6bde8ae4da8c0526467581399e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 20 Dec 2023 19:49:46 -0800 Subject: [PATCH 0417/1053] Remove cache for is_longer_than_hash_len (#452) --- firewood/src/merkle/node.rs | 43 ++++++------------------------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index e3c672a69eb8..caf057ceb65e 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -154,7 +154,6 @@ impl NodeType { #[derive(Debug)] pub struct Node { pub(super) root_hash: OnceLock, - is_encoded_longer_than_hash_len: OnceLock, encoded: OnceLock>, // lazy_dirty is an atomicbool, but only writers ever set it // Therefore, we can always use Relaxed ordering. It's atomic @@ -171,13 +170,11 @@ impl PartialEq for Node { let Node { root_hash, - is_encoded_longer_than_hash_len, encoded, lazy_dirty: _, inner, } = self; *root_hash == other.root_hash - && *is_encoded_longer_than_hash_len == other.is_encoded_longer_than_hash_len && *encoded == other.encoded && is_dirty == other.is_dirty() && *inner == other.inner @@ -188,7 +185,6 @@ impl Clone for Node { fn clone(&self) -> Self { Self { root_hash: self.root_hash.clone(), - is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), encoded: self.encoded.clone(), lazy_dirty: AtomicBool::new(self.is_dirty()), inner: self.inner.clone(), @@ -200,7 +196,6 @@ impl From for Node { fn from(inner: NodeType) -> Self { let mut s = Self { root_hash: OnceLock::new(), - is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), @@ -224,7 +219,6 @@ impl Node { *max_size.get_or_init(|| { Self { root_hash: OnceLock::new(), - is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner: NodeType::Branch( BranchNode { @@ -253,15 +247,11 @@ impl Node { } fn is_encoded_longer_than_hash_len>(&self, store: &S) -> bool { - *self.is_encoded_longer_than_hash_len.get_or_init(|| { - self.set_dirty(true); - self.get_encoded(store).len() >= TRIE_HASH_LEN - }) + self.get_encoded(store).len() >= TRIE_HASH_LEN } pub(super) fn rehash(&mut self) { self.encoded = OnceLock::new(); - self.is_encoded_longer_than_hash_len = OnceLock::new(); self.root_hash = OnceLock::new(); } @@ -281,20 +271,12 @@ impl Node { &mut self.inner } - pub(super) fn new_from_hash( - root_hash: Option, - encoded_longer_than_hash_len: Option, - inner: NodeType, - ) -> Self { + pub(super) fn new_from_hash(root_hash: Option, inner: NodeType) -> Self { Self { root_hash: match root_hash { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, - is_encoded_longer_than_hash_len: match encoded_longer_than_hash_len { - Some(b) => OnceLock::from(b), - None => OnceLock::new(), - }, encoded: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), @@ -374,34 +356,23 @@ impl Storable for Node { None }; - let is_encoded_longer_than_hash_len = - if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { - Some(attrs.contains(NodeAttributes::LONG)) - } else { - None - }; - match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { NodeTypeId::Branch => { let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); - Ok(Self::new_from_hash( - root_hash, - is_encoded_longer_than_hash_len, - inner, - )) + Ok(Self::new_from_hash(root_hash, inner)) } NodeTypeId::Extension => { let inner = NodeType::Extension(ExtNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); + let node = Self::new_from_hash(root_hash, inner); Ok(node) } NodeTypeId::Leaf => { let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); + let node = Self::new_from_hash(root_hash, inner); Ok(node) } @@ -434,8 +405,8 @@ impl Storable for Node { } }; - if let Some(&b) = self.is_encoded_longer_than_hash_len.get() { - attrs.insert(if b { + if let Some(b) = self.encoded.get() { + attrs.insert(if b.len() > TRIE_HASH_LEN { NodeAttributes::LONG } else { NodeAttributes::IS_ENCODED_BIG_VALID From 50aae3a3a215efa95685d3ba1cc592f539431e48 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:11:45 -0800 Subject: [PATCH 0418/1053] `Proposal.commit` consume the underlying `DbRev` directly to the base committed revision (#456) --- firewood/benches/hashops.rs | 4 +- firewood/src/db.rs | 43 ++++++++++------- firewood/src/db/proposal.rs | 91 ++++++++++++++--------------------- firewood/src/merkle.rs | 16 ++++-- firewood/src/merkle_util.rs | 6 +-- firewood/src/shale/compact.rs | 44 +++++++++++++---- firewood/src/storage/mod.rs | 16 ++++++ firewood/tests/db.rs | 1 + 8 files changed, 129 insertions(+), 92 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index a03429117ad7..d797c01f85aa 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -95,8 +95,8 @@ fn bench_merkle(criterion: &mut Criterion) { .unwrap(); let store = CompactSpace::new( - PlainMem::new(TEST_MEM_SIZE, 0).into(), - PlainMem::new(TEST_MEM_SIZE, 1).into(), + PlainMem::new(TEST_MEM_SIZE, 0), + PlainMem::new(TEST_MEM_SIZE, 1), merkle_payload_header_ref, ObjCache::new(1 << 20), 4096, diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ad05f24ee153..7da7af0338b2 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -56,8 +56,8 @@ const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; -type Store = CompactSpace; -type SharedStore = CompactSpace; +pub type MutStore = CompactSpace; +pub type SharedStore = CompactSpace; #[derive(Debug)] #[non_exhaustive] @@ -138,11 +138,11 @@ impl SubUniverse { } } -impl SubUniverse> { - fn new_from_other(&self) -> SubUniverse> { +impl SubUniverse { + fn new_from_other(&self) -> SubUniverse { SubUniverse { - meta: Arc::new(StoreRevMut::new_from_other(self.meta.as_ref())), - payload: Arc::new(StoreRevMut::new_from_other(self.payload.as_ref())), + meta: StoreRevMut::new_from_other(&self.meta), + payload: StoreRevMut::new_from_other(&self.payload), } } } @@ -241,8 +241,8 @@ impl Universe { } } -impl Universe> { - fn new_from_other(&self) -> Universe> { +impl Universe { + fn new_from_other(&self) -> Universe { Universe { merkle: self.merkle.new_from_other(), } @@ -381,6 +381,15 @@ impl + Send + Sync> DbRev { } } +impl DbRev { + pub fn into_shared(self) -> DbRev { + DbRev { + header: self.header, + merkle: self.merkle.into(), + } + } +} + #[derive(Debug)] struct DbInner { disk_requester: DiskBufferRequester, @@ -462,7 +471,7 @@ impl Db { } /// Open a database. - fn new_internal>(db_path: P, cfg: DbConfig) -> Result { + pub fn new_internal>(db_path: P, cfg: DbConfig) -> Result { let open_options = if cfg.truncate { file::Options::Truncate } else { @@ -655,7 +664,7 @@ impl Db { reset_store_headers: bool, payload_regn_nbit: u64, cfg: &DbConfig, - ) -> Result<(Universe>, DbRev), DbError> { + ) -> Result<(Universe, DbRev), DbError> { let mut offset = Db::PARAM_SIZE as usize; let db_header: DiskAddress = DiskAddress::from(offset); offset += DbHeader::MSIZE as usize; @@ -682,17 +691,15 @@ impl Db { let store = Universe { merkle: SubUniverse::new( - Arc::new(merkle_meta_store), - Arc::new(StoreRevMut::new(cached_space.merkle.payload.clone())), + merkle_meta_store, + StoreRevMut::new(cached_space.merkle.payload.clone()), ), }; - let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; + let db_header_ref = Db::get_db_header_ref(&store.merkle.meta)?; - let merkle_payload_header_ref = Db::get_payload_header_ref( - store.merkle.meta.as_ref(), - Db::PARAM_SIZE + DbHeader::MSIZE, - )?; + let merkle_payload_header_ref = + Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; let header_refs = (db_header_ref, merkle_payload_header_ref); @@ -726,7 +733,7 @@ impl Db { StoredView::ptr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) } - fn new_revision>>( + fn new_revision>( header_refs: (Obj, Obj), merkle: (T, T), payload_regn_nbit: u64, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 869b751c9e08..12cd4601dcb1 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -2,9 +2,8 @@ // See the file LICENSE.md for licensing terms. use super::{ - get_sub_universe_from_deltas, get_sub_universe_from_empty_delta, Db, DbConfig, DbError, - DbHeader, DbInner, DbRev, DbRevInner, SharedStore, Store, Universe, MERKLE_META_SPACE, - MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, + get_sub_universe_from_deltas, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, + MutStore, SharedStore, Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; use crate::merkle::Proof; use crate::shale::CachedStore; @@ -32,8 +31,8 @@ pub struct Proposal { pub(super) cfg: DbConfig, // State of the proposal - pub(super) rev: DbRev, - pub(super) store: Universe>, + pub(super) rev: DbRev, + pub(super) store: Universe, pub(super) committed: Arc>, pub(super) parent: ProposalBase, @@ -49,7 +48,8 @@ impl crate::v2::api::Proposal for Proposal { type Proposal = Proposal; async fn commit(self: Arc) -> Result<(), api::Error> { - block_in_place(|| self.commit_sync().map_err(Into::into)) + let proposal = Arc::::into_inner(self).unwrap(); + block_in_place(|| proposal.commit_sync().map_err(Into::into)) } async fn propose( @@ -73,12 +73,10 @@ impl Proposal { let r = Arc::clone(&self.r); let cfg = self.cfg.clone(); - let db_header_ref = Db::get_db_header_ref(store.merkle.meta.as_ref())?; + let db_header_ref = Db::get_db_header_ref(&store.merkle.meta)?; - let merkle_payload_header_ref = Db::get_payload_header_ref( - store.merkle.meta.as_ref(), - Db::PARAM_SIZE + DbHeader::MSIZE, - )?; + let merkle_payload_header_ref = + Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; let header_refs = (db_header_ref, merkle_payload_header_ref); @@ -124,22 +122,33 @@ impl Proposal { /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are /// either retained on disk or lost together during a crash. - pub fn commit_sync(&self) -> Result<(), DbError> { - let mut committed = self.committed.lock(); + pub fn commit_sync(self) -> Result<(), DbError> { + let Self { + m, + r, + cfg: _, + rev, + store, + committed, + parent, + } = self; + + let mut committed = committed.lock(); if *committed { return Ok(()); } - if let ProposalBase::Proposal(p) = &self.parent { - p.commit_sync()?; - }; + if let ProposalBase::Proposal(_p) = parent { + // p.commit_sync()?; + todo!(); + } // Check for if it can be committed - let mut revisions = self.r.lock(); + let mut revisions = r.lock(); let committed_root_hash = revisions.base_revision.kv_root_hash().ok(); let committed_root_hash = committed_root_hash.expect("committed_root_hash should not be none"); - match &self.parent { + match &parent { ProposalBase::Proposal(p) => { let parent_root_hash = p.rev.kv_root_hash().ok(); let parent_root_hash = @@ -158,14 +167,14 @@ impl Proposal { } }; - let kv_root_hash = self.rev.kv_root_hash().ok(); + let kv_root_hash = rev.kv_root_hash().ok(); let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); // clear the staging layer and apply changes to the CachedSpace - let (merkle_payload_redo, merkle_payload_wal) = self.store.merkle.payload.delta(); - let (merkle_meta_redo, merkle_meta_wal) = self.store.merkle.meta.delta(); + let (merkle_payload_redo, merkle_payload_wal) = store.merkle.payload.delta(); + let (merkle_meta_redo, merkle_meta_wal) = store.merkle.meta.delta(); - let mut rev_inner = self.m.write(); + let mut rev_inner = m.write(); let merkle_meta_undo = rev_inner .cached_space .merkle @@ -202,26 +211,7 @@ impl Proposal { revisions.inner.pop_back(); } - let base = Universe { - merkle: get_sub_universe_from_empty_delta(&rev_inner.cached_space.merkle), - }; - - let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; - - let merkle_payload_header_ref = - Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - - let header_refs = (db_header_ref, merkle_payload_header_ref); - - let base_revision = Db::new_revision( - header_refs, - (base.merkle.meta.clone(), base.merkle.payload.clone()), - 0, - self.cfg.payload_max_walk, - &self.cfg.rev, - )?; - revisions.base = base; - revisions.base_revision = Arc::new(base_revision); + revisions.base_revision = Arc::new(rev.into_shared()); // update the rolling window of root hashes revisions.root_hashes.push_front(kv_root_hash.clone()); @@ -238,11 +228,11 @@ impl Proposal { rev_inner.disk_requester.write( vec![ BufferWrite { - space_id: self.store.merkle.payload.id(), + space_id: store.merkle.payload.id(), delta: merkle_payload_redo, }, BufferWrite { - space_id: self.store.merkle.meta.id(), + space_id: store.merkle.meta.id(), delta: merkle_meta_redo, }, BufferWrite { @@ -265,7 +255,7 @@ impl Proposal { } impl Proposal { - pub fn get_revision(&self) -> &DbRev { + pub fn get_revision(&self) -> &DbRev { &self.rev } } @@ -308,14 +298,3 @@ impl api::DbView for Proposal { todo!() } } - -impl Drop for Proposal { - fn drop(&mut self) { - if !*self.committed.lock() { - // drop the staging changes - self.store.merkle.payload.reset_deltas(); - self.store.merkle.meta.reset_deltas(); - self.m.read().root_hash_staging.reset_deltas(); - } - } -} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7d9216d3a58d..6fea50f1d10a 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,3 +1,4 @@ +use crate::db::{MutStore, SharedStore}; // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; @@ -71,6 +72,16 @@ pub struct Merkle { phantom: PhantomData, } +impl Merkle { + pub fn into(self) -> Merkle { + let store = self.store.into_shared(); + Merkle { + store: Box::new(store), + phantom: PhantomData, + } + } +} + impl, T> Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) @@ -1410,7 +1421,6 @@ mod tests { use super::*; use node::tests::{extension, leaf}; use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; - use std::sync::Arc; use test_case::test_case; #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] @@ -1443,8 +1453,8 @@ mod tests { shale::compact::CompactHeader::MSIZE, ) .unwrap(); - let mem_meta = Arc::new(dm); - let mem_payload = Arc::new(DynamicMem::new(0x10000, 0x1)); + let mem_meta = dm; + let mem_payload = DynamicMem::new(0x10000, 0x1); let cache = shale::ObjCache::new(1); let space = diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index fe63a0952e6e..c8becf025df4 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -6,7 +6,7 @@ use crate::shale::{ self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleStore, StoredView, }; -use std::{num::NonZeroUsize, sync::Arc}; +use std::num::NonZeroUsize; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -135,8 +135,8 @@ pub fn new_merkle( ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); - let mem_meta = Arc::new(dm); - let mem_payload = Arc::new(DynamicMem::new(compact_size, 0x1)); + let mem_meta = dm; + let mem_payload = DynamicMem::new(compact_size, 0x1); let cache = shale::ObjCache::new(1); let space = diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 8d8b0fac37e4..e8a97331f181 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -1,7 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::merkle::Node; use crate::shale::ObjCache; +use crate::storage::{StoreRevMut, StoreRevShared}; use super::disk_address::DiskAddress; use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; @@ -9,7 +11,7 @@ use bytemuck::{Pod, Zeroable}; use std::fmt::Debug; use std::io::{Cursor, Write}; use std::num::NonZeroUsize; -use std::sync::{Arc, RwLock}; +use std::sync::RwLock; #[derive(Debug)] pub struct CompactHeader { @@ -216,16 +218,28 @@ impl Storable for CompactSpaceHeader { #[derive(Debug)] struct CompactSpaceInner { - meta_space: Arc, - compact_space: Arc, + meta_space: M, + compact_space: M, header: CompactSpaceHeaderSliced, alloc_max_walk: u64, regn_nbit: u64, } +impl CompactSpaceInner { + pub fn into_shared(self) -> CompactSpaceInner { + CompactSpaceInner { + meta_space: self.meta_space.into_shared(), + compact_space: self.compact_space.into_shared(), + header: self.header, + alloc_max_walk: self.alloc_max_walk, + regn_nbit: self.regn_nbit, + } + } +} + impl CompactSpaceInner { fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE) + StoredView::ptr_to_obj(&self.meta_space, ptr, CompactDescriptor::MSIZE) } fn get_data_ref( @@ -233,7 +247,7 @@ impl CompactSpaceInner { ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - StoredView::ptr_to_obj(self.compact_space.as_ref(), ptr, len_limit) + StoredView::ptr_to_obj(&self.compact_space, ptr, len_limit) } fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { @@ -501,8 +515,8 @@ pub struct CompactSpace { impl CompactSpace { pub fn new( - meta_space: Arc, - compact_space: Arc, + meta_space: M, + compact_space: M, header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, @@ -522,6 +536,16 @@ impl CompactSpace { } } +impl CompactSpace { + pub fn into_shared(self) -> CompactSpace { + let inner = self.inner.into_inner().unwrap(); + CompactSpace { + inner: RwLock::new(inner.into_shared()), + obj_cache: self.obj_cache, + } + } +} + impl ShaleStore for CompactSpace { fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; @@ -529,7 +553,7 @@ impl ShaleStore for Comp let obj = { let inner = self.inner.read().unwrap(); - let compact_space = inner.compact_space.as_ref(); + let compact_space = &inner.compact_space; let view = StoredView::item_to_obj(compact_space, addr.try_into().unwrap(), size, item)?; @@ -657,8 +681,8 @@ mod tests { ); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); - let mem_meta = Arc::new(dm); - let mem_payload = Arc::new(DynamicMem::new(compact_size.get() as u64, 0x1)); + let mem_meta = dm; + let mem_payload = DynamicMem::new(compact_size.get() as u64, 0x1); let cache: ObjCache = ObjCache::new(1); let space = diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 21575e89dffe..4cbb18e5e32d 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -401,6 +401,22 @@ impl StoreRevMut { } } + pub fn into_shared(self) -> StoreRevShared { + let mut pages = Vec::new(); + let deltas = std::mem::take(&mut *self.deltas.write()); + for (pid, page) in deltas.pages.into_iter() { + pages.push(DeltaPage(pid, page)); + } + pages.sort_by_key(|p| p.0); + let delta = StoreDelta(pages); + + let rev = Arc::new(StoreRev { + base_space: RwLock::new(self.base_space), + delta, + }); + StoreRevShared(rev) + } + fn get_page_mut<'a>( &self, deltas: &'a mut StoreRevMutDelta, diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 83524b13628c..0fba4f854f7c 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -244,6 +244,7 @@ macro_rules! assert_val { }; } +#[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] #[tokio::test(flavor = "multi_thread")] async fn db_proposal() -> Result<(), api::Error> { let cfg = DbConfig::builder() From c893ac77a8415b7746b782cc4cf21a26aa172f53 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Dec 2023 09:28:48 -0800 Subject: [PATCH 0419/1053] unwrap warning project --- firewood/Cargo.toml | 3 + firewood/benches/hashops.rs | 12 ++++ firewood/benches/shale-bench.rs | 6 ++ firewood/examples/insert.rs | 1 + firewood/src/config.rs | 1 - firewood/src/db.rs | 18 +++++- firewood/src/db/proposal.rs | 4 ++ firewood/src/merkle.rs | 28 ++++++++++ firewood/src/merkle/node.rs | 5 ++ firewood/src/merkle/node/branch.rs | 6 ++ firewood/src/merkle/node/extension.rs | 6 +- firewood/src/merkle/node/leaf.rs | 2 + firewood/src/merkle/proof.rs | 15 ++++- firewood/src/merkle/stream.rs | 1 + firewood/src/merkle/trie_hash.rs | 3 +- firewood/src/merkle_util.rs | 4 ++ firewood/src/shale/cached.rs | 8 +++ firewood/src/shale/compact.rs | 31 +++++++++++ firewood/src/shale/disk_address.rs | 3 + firewood/src/shale/mod.rs | 6 ++ firewood/src/storage/buffer.rs | 18 ++++++ firewood/src/storage/mod.rs | 79 +++++++++++++++------------ firewood/src/v2/emptydb.rs | 1 + firewood/src/v2/propose.rs | 2 +- firewood/tests/common/mod.rs | 2 + firewood/tests/db.rs | 4 ++ firewood/tests/merkle.rs | 3 + firewood/tests/v2api.rs | 1 + 28 files changed, 233 insertions(+), 40 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 85de87d3f054..37ba5d8b8aef 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -51,3 +51,6 @@ pprof = { version = "0.13.0", features = ["flamegraph"] } [[bench]] name = "hashops" harness = false + +[lints.clippy] +unwrap_used = "warn" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index d797c01f85aa..bce54c489670 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -34,6 +34,7 @@ fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { } impl Profiler for FlamegraphProfiler { + #[allow(clippy::unwrap_used)] fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { if let Self::Init(frequency) = self { let guard = ProfilerGuard::new(*frequency).unwrap(); @@ -41,13 +42,16 @@ impl Profiler for FlamegraphProfiler { } } + #[allow(clippy::unwrap_used)] fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { std::fs::create_dir_all(benchmark_dir).unwrap(); let filename = "firewood-flamegraph.svg"; let flamegraph_path = benchmark_dir.join(filename); + #[allow(clippy::unwrap_used)] let flamegraph_file = File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); + #[allow(clippy::unwrap_used)] if let Self::Active(profiler) = self { profiler .report() @@ -64,6 +68,7 @@ fn bench_trie_hash(criterion: &mut Criterion) { let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, ZERO_HASH.deref()); + #[allow(clippy::unwrap_used)] criterion .benchmark_group("TrieHash") .bench_function("dehydrate", |b| { @@ -87,6 +92,7 @@ fn bench_merkle(criterion: &mut Criterion) { || { let merkle_payload_header = DiskAddress::from(0); + #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = StoredView::ptr_to_obj( &PlainMem::new(2 * CompactHeader::MSIZE, 9), merkle_payload_header, @@ -94,6 +100,7 @@ fn bench_merkle(criterion: &mut Criterion) { ) .unwrap(); + #[allow(clippy::unwrap_used)] let store = CompactSpace::new( PlainMem::new(TEST_MEM_SIZE, 0), PlainMem::new(TEST_MEM_SIZE, 1), @@ -105,6 +112,7 @@ fn bench_merkle(criterion: &mut Criterion) { .unwrap(); let merkle = MerkleWithEncoder::new(Box::new(store)); + #[allow(clippy::unwrap_used)] let root = merkle.init_root().unwrap(); let keys: Vec> = repeat_with(|| { @@ -118,6 +126,7 @@ fn bench_merkle(criterion: &mut Criterion) { (merkle, root, keys) }, + #[allow(clippy::unwrap_used)] |(mut merkle, root, keys)| { keys.into_iter() .for_each(|key| merkle.insert(key, vec![b'v'], root).unwrap()) @@ -131,6 +140,7 @@ fn bench_db(criterion: &mut Criterion) { const KEY_LEN: usize = 4; let mut rng = StdRng::seed_from_u64(1234); + #[allow(clippy::unwrap_used)] criterion .benchmark_group("Db") .sample_size(30) @@ -158,11 +168,13 @@ fn bench_db(criterion: &mut Criterion) { let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); + #[allow(clippy::unwrap_used)] let db = firewood::db::Db::new(db_path, &cfg.clone().truncate(true).build()) .await .unwrap(); + #[allow(clippy::unwrap_used)] Arc::new(db.propose(batch_ops).await.unwrap()) .commit() .await diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 0fb6a8e749f2..5ee0417fa326 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -28,6 +28,7 @@ fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { } impl Profiler for FlamegraphProfiler { + #[allow(clippy::unwrap_used)] fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { if let Self::Init(frequency) = self { let guard = ProfilerGuard::new(*frequency).unwrap(); @@ -35,13 +36,16 @@ impl Profiler for FlamegraphProfiler { } } + #[allow(clippy::unwrap_used)] fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { std::fs::create_dir_all(benchmark_dir).unwrap(); let filename = "shale-flamegraph.svg"; let flamegraph_path = benchmark_dir.join(filename); + #[allow(clippy::unwrap_used)] let flamegraph_file = File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); + #[allow(clippy::unwrap_used)] if let Self::Active(profiler) = self { profiler .report() @@ -63,6 +67,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { let offset = rng.gen_range(0..BENCH_MEM_SIZE - len); cached.write(offset, rdata); + #[allow(clippy::unwrap_used)] let view = cached .get_view(offset, rdata.len().try_into().unwrap()) .unwrap(); @@ -74,6 +79,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); + #[allow(clippy::unwrap_used)] let _: Obj = StoredView::ptr_to_obj(m, compact_header_obj, CompactHeader::MSIZE).unwrap(); } diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 997ffac039aa..7ec4657eaa67 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -83,6 +83,7 @@ async fn main() -> Result<(), Box> { let verify = get_keys_to_verify(&batch, args.read_verify_percent); + #[allow(clippy::unwrap_used)] let proposal = Arc::new(db.propose(batch).await.unwrap()); proposal.commit().await?; verify_keys(&db, verify).await?; diff --git a/firewood/src/config.rs b/firewood/src/config.rs index 6a13e4ffd613..dd4e3e0bd3c0 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -1,4 +1,3 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7da7af0338b2..10c73ab7fd6a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -513,6 +513,7 @@ impl Db { let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); let disk_requester = DiskBufferRequester::new(sender); let buffer = cfg.buffer.clone(); + #[allow(clippy::unwrap_used)] let disk_thread = block_in_place(|| { Some(std::thread::spawn(move || { let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); @@ -520,6 +521,7 @@ impl Db { })) }); + #[allow(clippy::unwrap_used)] let root_hash_cache: Arc = CachedSpace::new( &StoreConfig::builder() .ncached_pages(cfg.root_hash_ncached_pages) @@ -534,6 +536,7 @@ impl Db { .into(); // setup disk buffer + #[allow(clippy::unwrap_used)] let data_cache = Universe { merkle: SubUniverse::>::new( CachedSpace::new( @@ -625,6 +628,7 @@ impl Db { // DbHeader (just a pointer to the sentinel) // CompactSpaceHeader for future allocations let (params, hdr, csh); + #[allow(clippy::unwrap_used)] let header_bytes: Vec = { params = DbParams { magic: *MAGIC_STR, @@ -647,6 +651,7 @@ impl Db { // write out the CompactSpaceHeader csh = CompactSpaceHeader::new( NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + #[allow(clippy::unwrap_used)] NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), ); bytemuck::bytes_of(&csh) @@ -676,10 +681,12 @@ impl Db { if reset_store_headers { // initialize store headers + #[allow(clippy::unwrap_used)] merkle_meta_store.write( merkle_payload_header.into(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + #[allow(clippy::unwrap_used)] NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), ))?, ); @@ -710,6 +717,7 @@ impl Db { cfg.payload_max_walk, &cfg.rev, )?; + #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); Ok((store, rev)) @@ -751,6 +759,7 @@ impl Db { let merkle_meta = merkle.0.into(); let merkle_payload = merkle.1.into(); + #[allow(clippy::unwrap_used)] let merkle_space = shale::compact::CompactSpace::new( merkle_meta, merkle_payload, @@ -766,6 +775,7 @@ impl Db { if db_header_ref.kv_root.is_null() { let mut err = Ok(()); // create the sentinel node + #[allow(clippy::unwrap_used)] db_header_ref .write(|r| { err = (|| { @@ -820,6 +830,7 @@ impl Db { } } })?; + #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); let parent = ProposalBase::View(Arc::clone(&self.revisions.lock().base_revision)); @@ -848,6 +859,7 @@ impl Db { let mut nback = revisions.root_hashes.iter().position(|r| r == root_hash); let rlen = revisions.root_hashes.len(); + #[allow(clippy::unwrap_used)] if nback.is_none() && rlen < revisions.max_revisions { let ashes = inner_lock .disk_requester @@ -867,7 +879,7 @@ impl Db { .map(|root_hash_store| { root_hash_store .get_view(0, TRIE_HASH_LEN as u64) - .unwrap() + .expect("get view failed") .as_deref() }) .map(|data| TrieHash(data[..TRIE_HASH_LEN].try_into().unwrap())) @@ -881,6 +893,7 @@ impl Db { let rlen = revisions.inner.len(); if rlen < nback { // TODO: Remove unwrap + #[allow(clippy::unwrap_used)] let ashes = inner_lock.disk_requester.collect_ash(nback).ok().unwrap(); for mut ash in ashes.into_iter().skip(rlen) { for (_, a) in ash.0.iter_mut() { @@ -909,14 +922,17 @@ impl Db { // Release the lock after we find the revision drop(inner_lock); + #[allow(clippy::unwrap_used)] let db_header_ref = Db::get_db_header_ref(&space.merkle.meta).unwrap(); + #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = Db::get_payload_header_ref(&space.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) .unwrap(); let header_refs = (db_header_ref, merkle_payload_header_ref); + #[allow(clippy::unwrap_used)] Revision { rev: Db::new_revision( header_refs, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 12cd4601dcb1..950c7346aec8 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -47,6 +47,7 @@ pub enum ProposalBase { impl crate::v2::api::Proposal for Proposal { type Proposal = Proposal; + #[allow(clippy::unwrap_used)] async fn commit(self: Arc) -> Result<(), api::Error> { let proposal = Arc::::into_inner(self).unwrap(); block_in_place(|| proposal.commit_sync().map_err(Into::into)) @@ -105,6 +106,7 @@ impl Proposal { } } })?; + #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); let parent = ProposalBase::Proposal(self); @@ -175,12 +177,14 @@ impl Proposal { let (merkle_meta_redo, merkle_meta_wal) = store.merkle.meta.delta(); let mut rev_inner = m.write(); + #[allow(clippy::unwrap_used)] let merkle_meta_undo = rev_inner .cached_space .merkle .meta .update(&merkle_meta_redo) .unwrap(); + #[allow(clippy::unwrap_used)] let merkle_payload_undo = rev_inner .cached_space .merkle diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 6fea50f1d10a..c9b5c5453f5e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -183,6 +183,7 @@ impl + Send + Sync, T> Merkle { pub fn empty_root() -> &'static TrieHash { static V: OnceLock = OnceLock::new(); + #[allow(clippy::unwrap_used)] V.get_or_init(|| { TrieHash( hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") @@ -203,6 +204,7 @@ impl + Send + Sync, T> Merkle { Ok(if let Some(root) = root { let mut node = self.get_node(root)?; let res = node.get_root_hash::(self.store.as_ref()).clone(); + #[allow(clippy::unwrap_used)] if node.is_dirty() { node.write(|_| {}).unwrap(); node.set_dirty(false); @@ -230,6 +232,7 @@ impl + Send + Sync, T> Merkle { self.dump_(*c, w)? } } + #[allow(clippy::unwrap_used)] NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), NodeType::Extension(n) => { writeln!(w, "{n:?}")?; @@ -337,6 +340,7 @@ impl + Send + Sync, T> Merkle { match &mut u.inner { NodeType::Leaf(u) => u.data = Data(val), NodeType::Extension(u) => { + #[allow(clippy::unwrap_used)] let write_result = self.get_node(u.chd()).and_then(|mut b_ref| { b_ref @@ -379,6 +383,7 @@ impl + Send + Sync, T> Merkle { // if the node-path is greater than the insert path (Ordering::Less, _) => { // key path is a prefix of the path to u + #[allow(clippy::unwrap_used)] node_to_split .write(|u| { // TODO: handle unwraps better @@ -540,6 +545,7 @@ impl + Send + Sync, T> Merkle { )))? .as_ptr(); // set the current child to point to this leaf + #[allow(clippy::unwrap_used)] node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); uu.children[current_nibble as usize] = Some(leaf_ptr); @@ -648,6 +654,7 @@ impl + Send + Sync, T> Merkle { u_ptr } else { deleted.push(u_ptr); + #[allow(clippy::unwrap_used)] ext.unwrap() }; @@ -669,6 +676,7 @@ impl + Send + Sync, T> Merkle { Ok((parents.into_iter().rev().map(|(node, _)| node), deleted)) } + #[allow(clippy::unwrap_used)] fn after_remove_leaf( &self, parents: &mut ParentRefs, @@ -677,12 +685,14 @@ impl + Send + Sync, T> Merkle { let (b_chd, val) = { let (mut b_ref, b_idx) = parents.pop().unwrap(); // the immediate parent of a leaf must be a branch + #[allow(clippy::unwrap_used)] b_ref .write(|b| { b.inner.as_branch_mut().unwrap().children[b_idx as usize] = None; b.rehash() }) .unwrap(); + #[allow(clippy::unwrap_used)] let b_inner = b_ref.inner.as_branch().unwrap(); let (b_chd, has_chd) = b_inner.single_child(); if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() { @@ -691,6 +701,7 @@ impl + Send + Sync, T> Merkle { deleted.push(b_ref.as_ptr()); (b_chd, b_inner.value.clone()) }; + #[allow(clippy::unwrap_used)] let (mut p_ref, p_idx) = parents.pop().unwrap(); let p_ptr = p_ref.as_ptr(); if let Some(val) = val { @@ -701,6 +712,7 @@ impl + Send + Sync, T> Merkle { let leaf = self .put_node(Node::from_leaf(LeafNode::new(PartialPath(Vec::new()), val)))? .as_ptr(); + #[allow(clippy::unwrap_used)] p_ref .write(|p| { p.inner.as_branch_mut().unwrap().children[p_idx as usize] = Some(leaf); @@ -723,6 +735,7 @@ impl + Send + Sync, T> Merkle { _ => unreachable!(), } } else { + #[allow(clippy::unwrap_used)] let (c_ptr, idx) = b_chd.unwrap(); let mut c_ref = self.get_node(c_ptr)?; match &c_ref.inner { @@ -794,6 +807,7 @@ impl + Send + Sync, T> Merkle { drop(c_ref); + #[allow(clippy::unwrap_used)] p_ref .write(|p| { p.inner.as_branch_mut().unwrap().children[p_idx as usize] = @@ -849,7 +863,9 @@ impl + Send + Sync, T> Merkle { deleted: &mut Vec, ) -> Result<(), MerkleError> { // [b] -> [u] -> [c] + #[allow(clippy::unwrap_used)] let (mut b_ref, b_idx) = parents.pop().unwrap(); + #[allow(clippy::unwrap_used)] let mut c_ref = self.get_node(c_ptr).unwrap(); match &c_ref.inner { NodeType::Branch(_) => { @@ -915,6 +931,7 @@ impl + Send + Sync, T> Merkle { c_ptr }; drop(c_ref); + #[allow(clippy::unwrap_used)] b_ref .write(|b| { b.inner.as_branch_mut().unwrap().children[b_idx as usize] = Some(c_ptr); @@ -978,6 +995,7 @@ impl + Send + Sync, T> Merkle { NodeType::Branch(n) => { let (c_chd, _) = n.single_child(); + #[allow(clippy::unwrap_used)] node_ref .write(|u| { found = u.inner.as_branch_mut().unwrap().value.take(); @@ -1002,6 +1020,7 @@ impl + Send + Sync, T> Merkle { (found, parents, deleted) }; + #[allow(clippy::unwrap_used)] for (mut r, _) in parents.into_iter().rev() { r.write(|u| u.rehash()).unwrap(); } @@ -1280,6 +1299,7 @@ impl + Send + Sync, T> Merkle { // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested + #[allow(clippy::unwrap_used)] middle.extend( stream .take(limit.unwrap_or(usize::MAX)) @@ -1325,7 +1345,9 @@ impl + Send + Sync, T> Merkle { } fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { + #[allow(clippy::unwrap_used)] let (p_ref, idx) = parents.last_mut().unwrap(); + #[allow(clippy::unwrap_used)] p_ref .write(|p| { match &mut p.inner { @@ -1348,6 +1370,7 @@ pub struct RefMut<'a, S, T> { impl<'a> std::ops::Deref for Ref<'a> { type Target = [u8]; + #[allow(clippy::unwrap_used)] fn deref(&self) -> &[u8] { match &self.0.inner { NodeType::Branch(n) => n.value.as_ref().unwrap(), @@ -1366,14 +1389,17 @@ impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { } } + #[allow(clippy::unwrap_used)] pub fn get(&self) -> Ref { Ref(self.merkle.get_node(self.ptr).unwrap()) } pub fn write(&mut self, modify: impl FnOnce(&mut Vec)) -> Result<(), MerkleError> { let mut deleted = Vec::new(); + #[allow(clippy::unwrap_used)] { let mut u_ref = self.merkle.get_node(self.ptr).unwrap(); + #[allow(clippy::unwrap_used)] let mut parents: Vec<_> = self .parents .iter() @@ -1383,6 +1409,7 @@ impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { self.merkle, u_ref, |u| { + #[allow(clippy::unwrap_used)] modify(match &mut u.inner { NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, NodeType::Leaf(n) => &mut n.data.0, @@ -1415,6 +1442,7 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use crate::merkle::node::PlainCodec; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index caf057ceb65e..628a4911d278 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -106,6 +106,7 @@ impl NodeType { LEAF_NODE_SIZE => { let mut items = items.into_iter(); + #[allow(clippy::unwrap_used)] let decoded_key: Vec = items.next().unwrap().decode()?; let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); @@ -114,6 +115,7 @@ impl NodeType { PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); + #[allow(clippy::unwrap_used)] let data: Vec = items.next().unwrap().decode()?; if term { @@ -413,6 +415,7 @@ impl Storable for Node { }); } + #[allow(clippy::unwrap_used)] cur.write_all(&[attrs.bits()]).unwrap(); match &self.inner { @@ -819,11 +822,13 @@ pub(super) mod tests { fn test_encoding(node: Node) { let mut bytes = vec![0; node.serialized_len() as usize]; + #[allow(clippy::unwrap_used)] node.serialize(&mut bytes).unwrap(); let mut mem = PlainMem::new(node.serialized_len(), 0x00); mem.write(0, &bytes); + #[allow(clippy::unwrap_used)] let hydrated_node = Node::deserialize(0, &mem).unwrap(); assert_eq!(node, hydrated_node); diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 39ad8e419bfb..a6edab6f69f8 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -114,6 +114,7 @@ impl BranchNode { let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; // we've already validated the size, that's why we can safely unwrap + #[allow(clippy::unwrap_used)] let data = items.pop().unwrap().decode()?; // Extract the value of the branch node and set to None if it's an empty Vec let value = Some(data).filter(|data| !data.is_empty()); @@ -145,8 +146,10 @@ impl BranchNode { for (i, c) in self.children.iter().enumerate() { match c { Some(c) => { + #[allow(clippy::unwrap_used)] let mut c_ref = store.get_item(*c).unwrap(); + #[allow(clippy::unwrap_used)] if c_ref.is_encoded_longer_than_hash_len::(store) { list[i] = Encoded::Data( bincode::DefaultOptions::new() @@ -175,6 +178,7 @@ impl BranchNode { // Check if there is already a calculated encoded value for the child, which // can happen when manually constructing a trie from proof. if let Some(v) = &self.children_encoded[i] { + #[allow(clippy::unwrap_used)] if v.len() == TRIE_HASH_LEN { list[i] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); @@ -186,11 +190,13 @@ impl BranchNode { }; } + #[allow(clippy::unwrap_used)] if let Some(Data(val)) = &self.value { list[Self::MAX_CHILDREN] = Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); } + #[allow(clippy::unwrap_used)] bincode::DefaultOptions::new() .serialize(list.as_slice()) .unwrap() diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index 9cc017ab5448..a05e475312ee 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -44,12 +44,14 @@ impl ExtNode { list[0] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) - .unwrap(), + .expect("serialization failed"), ); if !self.child.is_null() { + #[allow(clippy::unwrap_used)] let mut r = store.get_item(self.child).unwrap(); + #[allow(clippy::unwrap_used)] if r.is_encoded_longer_than_hash_len(store) { list[1] = Encoded::Data( bincode::DefaultOptions::new() @@ -67,6 +69,7 @@ impl ExtNode { } else { // Check if there is already a caclucated encoded value for the child, which // can happen when manually constructing a trie from proof. + #[allow(clippy::unwrap_used)] if let Some(v) = &self.child_encoded { if v.len() == TRIE_HASH_LEN { list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); @@ -76,6 +79,7 @@ impl ExtNode { } } + #[allow(clippy::unwrap_used)] bincode::DefaultOptions::new() .serialize(list.as_slice()) .unwrap() diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 42826d110016..5b346a0ee980 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -52,6 +52,7 @@ impl LeafNode { } pub(super) fn encode(&self) -> Vec { + #[allow(clippy::unwrap_used)] bincode::DefaultOptions::new() .serialize( [ @@ -143,6 +144,7 @@ impl Storable for LeafNode { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; use test_case::test_case; diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index aafc2360aa8d..d279fb371cea 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -78,6 +78,7 @@ impl From for ProofError { DbError::KeyNotFound => ProofError::InvalidEdgeKeys, DbError::CreateError => ProofError::NoSuchNode, // TODO: fix better by adding a new error to ProofError + #[allow(clippy::unwrap_used)] DbError::IO(e) => { ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) } @@ -291,6 +292,7 @@ impl + Send> Proof { } None => { // insert the leaf to the empty slot + #[allow(clippy::unwrap_used)] u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); @@ -302,8 +304,10 @@ impl + Send> Proof { NodeType::Extension(_) => { // If the child already resolved, then use the existing node. + #[allow(clippy::unwrap_used)] let node = u_ref.inner().as_extension().unwrap().chd(); + #[allow(clippy::unwrap_used)] if node.is_null() { u_ref .write(|u| { @@ -358,6 +362,7 @@ impl + Send> Proof { // TODO: add path NodeType::Branch(n) if n.chd()[branch_index].is_none() => { // insert the leaf to the empty slot + #[allow(clippy::unwrap_used)] u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); @@ -369,6 +374,7 @@ impl + Send> Proof { // If the child already resolved, then use the existing node. NodeType::Branch(_) => {} + #[allow(clippy::unwrap_used)] NodeType::Extension(_) if u_ref.inner().as_extension().unwrap().chd().is_null() => { @@ -559,6 +565,7 @@ fn generate_subproof(encoded: Vec) -> Result { 32 => { let sub_hash: &[u8] = &encoded; + #[allow(clippy::unwrap_used)] let sub_hash = sub_hash.try_into().unwrap(); Ok(SubProof::Hash(sub_hash)) @@ -618,7 +625,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe }; parent = u_ref.as_ptr(); - u_ref = merkle.get_node(left_node.unwrap())?; + u_ref = merkle.get_node(left_node.expect("left_node none"))?; index += 1; } @@ -674,6 +681,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe let right_node = n.chd()[right_chunks[index] as usize]; // unset all internal nodes calculated encoded value in the forkpoint + #[allow(clippy::unwrap_used)] for i in left_chunks[index] + 1..right_chunks[index] { u_ref .write(|u| { @@ -718,6 +726,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe let mut p_ref = merkle .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; + #[allow(clippy::unwrap_used)] p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); @@ -775,6 +784,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe .get_node(parent) .map_err(|_| ProofError::NoSuchNode)?; + #[allow(clippy::unwrap_used)] if fork_left.is_ne() && fork_right.is_ne() { p_ref .write(|p| match p.inner_mut() { @@ -841,6 +851,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe let mut chunks = Vec::new(); chunks.extend(key.as_ref()); + #[allow(clippy::unwrap_used)] let mut u_ref = merkle .get_node(node.unwrap()) .map_err(|_| ProofError::NoSuchNode)?; @@ -858,6 +869,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe child_index + 1..16 }; + #[allow(clippy::unwrap_used)] for i in iter { u_ref .write(|u| { @@ -902,6 +914,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe (true, Ordering::Less) | (false, Ordering::Greater) ); + #[allow(clippy::unwrap_used)] if should_unset_entire_branch { p_ref .write(|p| { diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 748c0b5de4a7..b6150ecbe285 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -413,6 +413,7 @@ impl> IntoBytes for T {} use super::tests::create_test_merkle; #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use crate::nibbles::Nibbles; diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 557da0c587c5..e6e5b4a697fb 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -28,7 +28,7 @@ impl Storable for TrieHash { offset: addr, size: U64_TRIE_HASH_LEN, })?; - + #[allow(clippy::unwrap_used)] Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) } @@ -56,6 +56,7 @@ mod tests { let zero_hash = TrieHash([0u8; TRIE_HASH_LEN]); let mut to = [1u8; TRIE_HASH_LEN]; + #[allow(clippy::unwrap_used)] zero_hash.serialize(&mut to).unwrap(); assert_eq!(&to, &zero_hash.0); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index c8becf025df4..879d394a883d 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -125,14 +125,17 @@ pub fn new_merkle( assert!(compact_size as usize > RESERVED); let mut dm = DynamicMem::new(meta_size, 0); let compact_header = DiskAddress::null(); + #[allow(clippy::unwrap_used)] dm.write( compact_header.into(), &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( NonZeroUsize::new(RESERVED).unwrap(), + #[allow(clippy::unwrap_used)] NonZeroUsize::new(RESERVED).unwrap(), )) .unwrap(), ); + #[allow(clippy::unwrap_used)] let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); let mem_meta = dm; @@ -144,6 +147,7 @@ pub fn new_merkle( .expect("CompactSpace init fail"); let merkle = Merkle::new(Box::new(space)); + #[allow(clippy::unwrap_used)] let root = merkle.init_root().unwrap(); MerkleSetup { root, merkle } diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 05263d83c7c7..7f9dcf5c7a7a 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -33,6 +33,7 @@ impl CachedStore for PlainMem { length: u64, ) -> Option>>> { let length = length as usize; + #[allow(clippy::unwrap_used)] if offset + length > self.space.read().unwrap().len() { None } else { @@ -56,6 +57,7 @@ impl CachedStore for PlainMem { fn write(&mut self, offset: usize, change: &[u8]) { let length = change.len(); + #[allow(clippy::unwrap_used)] let mut vect = self.space.deref().write().unwrap(); vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); } @@ -91,6 +93,7 @@ impl CachedView for PlainMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { + #[allow(clippy::unwrap_used)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } @@ -119,6 +122,7 @@ impl CachedStore for DynamicMem { ) -> Option>>> { let length = length as usize; let size = offset + length; + #[allow(clippy::unwrap_used)] let mut space = self.space.write().unwrap(); // Increase the size if the request range exceeds the current limit. @@ -147,6 +151,7 @@ impl CachedStore for DynamicMem { let length = change.len(); let size = offset + length; + #[allow(clippy::unwrap_used)] let mut space = self.space.write().unwrap(); // Increase the size if the request range exceeds the current limit. @@ -187,11 +192,13 @@ impl CachedView for DynamicMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { + #[allow(clippy::unwrap_used)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; @@ -201,6 +208,7 @@ mod tests { let mem = view.deref_mut(); mem.write(0, &[1, 1]); mem.write(0, &[1, 2]); + #[allow(clippy::unwrap_used)] let r = mem.get_view(0, 2).unwrap().as_deref(); assert_eq!(r, [1, 2]); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index e8a97331f181..35dee2dfc6c0 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -81,6 +81,7 @@ impl Storable for CompactFooter { offset: addr, size: Self::MSIZE, })?; + #[allow(clippy::unwrap_used)] let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); Ok(Self { payload_size }) } @@ -262,6 +263,7 @@ impl CompactSpaceInner { let desc_size = CompactDescriptor::MSIZE; // TODO: subtracting two disk addresses is only used here, probably can rewrite this // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); + #[allow(clippy::unwrap_used)] self.header .meta_space_tail .write(|r| *r -= desc_size as usize) @@ -270,8 +272,10 @@ impl CompactSpaceInner { if desc_addr != DiskAddress(**self.header.meta_space_tail) { let desc_last = self.get_descriptor(*self.header.meta_space_tail.value)?; let mut desc = self.get_descriptor(desc_addr)?; + #[allow(clippy::unwrap_used)] desc.write(|r| *r = *desc_last).unwrap(); let mut header = self.get_header(desc.haddr.into())?; + #[allow(clippy::unwrap_used)] header.write(|h| h.desc_addr = desc_addr).unwrap(); } @@ -280,6 +284,7 @@ impl CompactSpaceInner { fn new_desc(&mut self) -> Result { let addr = **self.header.meta_space_tail; + #[allow(clippy::unwrap_used)] self.header .meta_space_tail .write(|r| *r += CompactDescriptor::MSIZE as usize) @@ -321,6 +326,7 @@ impl CompactSpaceInner { offset = addr + header_payload_size; let mut f = offset; + #[allow(clippy::unwrap_used)] if offset + fsize < self.header.compact_space_tail.unwrap().get() as u64 && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize { @@ -345,6 +351,7 @@ impl CompactSpaceInner { let desc_addr = self.new_desc()?; { let mut desc = self.get_descriptor(desc_addr)?; + #[allow(clippy::unwrap_used)] desc.write(|d| { d.payload_size = payload_size; d.haddr = h as usize; @@ -353,12 +360,14 @@ impl CompactSpaceInner { } let mut h = self.get_header(DiskAddress::from(h as usize))?; let mut f = self.get_footer(DiskAddress::from(f as usize))?; + #[allow(clippy::unwrap_used)] h.write(|h| { h.payload_size = payload_size; h.is_freed = true; h.desc_addr = desc_addr; }) .unwrap(); + #[allow(clippy::unwrap_used)] f.write(|f| f.payload_size = payload_size).unwrap(); Ok(()) @@ -394,6 +403,7 @@ impl CompactSpaceInner { let mut header = self.get_header(DiskAddress::from(desc_haddr))?; assert_eq!(header.payload_size as usize, desc_payload_size); assert!(header.is_freed); + #[allow(clippy::unwrap_used)] header.write(|h| h.is_freed = false).unwrap(); } self.del_desc(ptr)?; @@ -404,6 +414,7 @@ impl CompactSpaceInner { let mut lheader = self.get_header(DiskAddress::from(desc_haddr))?; assert_eq!(lheader.payload_size as usize, desc_payload_size); assert!(lheader.is_freed); + #[allow(clippy::unwrap_used)] lheader .write(|h| { h.is_freed = false; @@ -415,6 +426,7 @@ impl CompactSpaceInner { let mut lfooter = self.get_footer(DiskAddress::from(desc_haddr + hsize + length as usize))?; //assert!(lfooter.payload_size == desc_payload_size); + #[allow(clippy::unwrap_used)] lfooter.write(|f| f.payload_size = length).unwrap(); } @@ -423,6 +435,7 @@ impl CompactSpaceInner { let rdesc_addr = self.new_desc()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; + #[allow(clippy::unwrap_used)] rdesc .write(|rd| { rd.payload_size = rpayload_size as u64; @@ -432,6 +445,7 @@ impl CompactSpaceInner { } { let mut rheader = self.get_header(DiskAddress::from(offset))?; + #[allow(clippy::unwrap_used)] rheader .write(|rh| { rh.is_freed = true; @@ -443,6 +457,7 @@ impl CompactSpaceInner { { let mut rfooter = self.get_footer(DiskAddress::from(offset + hsize + rpayload_size))?; + #[allow(clippy::unwrap_used)] rfooter .write(|f| f.payload_size = rpayload_size as u64) .unwrap(); @@ -452,6 +467,7 @@ impl CompactSpaceInner { } else { false }; + #[allow(clippy::unwrap_used)] if exit { self.header.alloc_addr.write(|r| *r = ptr).unwrap(); res = Some((desc_haddr + hsize) as u64); @@ -472,6 +488,7 @@ impl CompactSpaceInner { let regn_size = 1 << self.regn_nbit; let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; let mut offset = *self.header.compact_space_tail; + #[allow(clippy::unwrap_used)] self.header .compact_space_tail .write(|r| { @@ -486,13 +503,16 @@ impl CompactSpaceInner { .unwrap(); let mut h = self.get_header(offset)?; let mut f = self.get_footer(offset + CompactHeader::MSIZE as usize + length as usize)?; + #[allow(clippy::unwrap_used)] h.write(|h| { h.payload_size = length; h.is_freed = false; h.desc_addr = DiskAddress::null(); }) .unwrap(); + #[allow(clippy::unwrap_used)] f.write(|f| f.payload_size = length).unwrap(); + #[allow(clippy::unwrap_used)] Ok((offset + CompactHeader::MSIZE as usize).0.unwrap().get() as u64) } @@ -537,6 +557,7 @@ impl CompactSpace { } impl CompactSpace { + #[allow(clippy::unwrap_used)] pub fn into_shared(self) -> CompactSpace { let inner = self.inner.into_inner().unwrap(); CompactSpace { @@ -549,11 +570,14 @@ impl CompactSpace { impl ShaleStore for CompactSpace { fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; + #[allow(clippy::unwrap_used)] let addr = self.inner.write().unwrap().alloc(size)?; + #[allow(clippy::unwrap_used)] let obj = { let inner = self.inner.read().unwrap(); let compact_space = &inner.compact_space; + #[allow(clippy::unwrap_used)] let view = StoredView::item_to_obj(compact_space, addr.try_into().unwrap(), size, item)?; @@ -565,20 +589,24 @@ impl ShaleStore for Comp let mut obj_ref = ObjRef::new(Some(obj), cache); // should this use a `?` instead of `unwrap`? + #[allow(clippy::unwrap_used)] obj_ref.write(|_| {}).unwrap(); Ok(obj_ref) } + #[allow(clippy::unwrap_used)] fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { let mut inner = self.inner.write().unwrap(); self.obj_cache.pop(ptr); + #[allow(clippy::unwrap_used)] inner.free(ptr.unwrap().get() as u64) } fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { let obj = self.obj_cache.get(ptr)?; + #[allow(clippy::unwrap_used)] let inner = self.inner.read().unwrap(); let cache = &self.obj_cache; @@ -586,6 +614,7 @@ impl ShaleStore for Comp return Ok(ObjRef::new(Some(obj), cache)); } + #[allow(clippy::unwrap_used)] if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { return Err(ShaleError::InvalidAddressLength { expected: DiskAddress::from(CompactSpaceHeader::MSIZE as usize), @@ -602,6 +631,7 @@ impl ShaleStore for Comp Ok(ObjRef::new(Some(obj), cache)) } + #[allow(clippy::unwrap_used)] fn flush_dirty(&self) -> Option<()> { let mut inner = self.inner.write().unwrap(); inner.header.flush_dirty(); @@ -611,6 +641,7 @@ impl ShaleStore for Comp } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use sha3::Digest; diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 332c88bf4e72..8764a224e984 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -77,6 +77,7 @@ impl From<[u8; 8]> for DiskAddress { /// serialization from disk impl From<&[u8]> for DiskAddress { fn from(value: &[u8]) -> Self { + #[allow(clippy::unwrap_used)] let bytes: [u8; 8] = value.try_into().unwrap(); bytes.into() } @@ -169,6 +170,7 @@ impl Storable for DiskAddress { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { use std::io::{Cursor, Write}; + #[allow(clippy::unwrap_used)] Cursor::new(to).write_all(&self.0.unwrap().get().to_le_bytes())?; Ok(()) } @@ -182,6 +184,7 @@ impl Storable for DiskAddress { })?; let addrdyn = raw.deref(); let addrvec = addrdyn.as_deref(); + #[allow(clippy::unwrap_used)] Ok(Self(NonZeroUsize::new(usize::from_le_bytes( addrvec.try_into().unwrap(), )))) diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 542bff424d45..d2f4efdc405b 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -142,6 +142,7 @@ impl Obj { if let Some(new_value_len) = self.dirty.take() { let mut new_value = vec![0; new_value_len as usize]; // TODO: log error + #[allow(clippy::unwrap_used)] self.value.write_mem_image(&mut new_value).unwrap(); let offset = self.value.get_offset(); let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); @@ -176,6 +177,7 @@ impl<'a, T: Storable> ObjRef<'a, T> { #[inline] pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { + #[allow(clippy::unwrap_used)] let inner = self.inner.as_mut().unwrap(); inner.write(modify)?; @@ -193,12 +195,14 @@ impl<'a, T: Storable> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { // TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix + #[allow(clippy::unwrap_used)] self.inner.as_ref().unwrap() } } impl<'a, T: Storable> Drop for ObjRef<'a, T> { fn drop(&mut self) { + #[allow(clippy::unwrap_used)] let mut inner = self.inner.take().unwrap(); let ptr = inner.as_ptr(); let mut cache = self.cache.lock(); @@ -416,11 +420,13 @@ impl ObjCache { } fn lock(&self) -> RwLockWriteGuard> { + #[allow(clippy::unwrap_used)] self.0.write().unwrap() } #[inline(always)] fn get(&self, ptr: DiskAddress) -> Result>, ShaleError> { + #[allow(clippy::unwrap_used)] let mut inner = self.0.write().unwrap(); let obj_ref = inner.cached.pop(&ptr).map(|r| { diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index b1fdfaa67815..a84ba45d8108 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -172,6 +172,7 @@ impl DiskBuffer { } // process the the request + #[allow(clippy::unwrap_used)] let process_result = process( pending_writes.clone(), notifier.clone(), @@ -219,10 +220,12 @@ fn schedule_write( let fut = { let pending = pending.borrow(); + #[allow(clippy::unwrap_used)] let p = pending.get(&page_key).unwrap(); let offset = page_key.1 << PAGE_SIZE_NBIT; let fid = offset >> p.file_nbit; let fmask = (1 << p.file_nbit) - 1; + #[allow(clippy::unwrap_used)] let file = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -239,6 +242,7 @@ fn schedule_write( let task = { async move { let (res, _) = fut.await; + #[allow(clippy::unwrap_used)] res.unwrap(); let pending_len = pending.borrow().len(); @@ -296,6 +300,7 @@ async fn init_wal( for (undo, redo) in ash.iter() { let offset = undo.offset; let file_pools = file_pools.borrow(); + #[allow(clippy::unwrap_used)] let file_pool = file_pools[space_id as usize].as_ref().unwrap(); let file_nbit = file_pool.get_file_nbit(); let file_mask = (1 << file_nbit) - 1; @@ -365,6 +370,7 @@ async fn run_wal_queue( } // first write to Wal + #[allow(clippy::unwrap_used)] let ring_ids = join_all(wal.clone().lock().await.grow(records)) .await .into_iter() @@ -386,6 +392,7 @@ async fn run_wal_queue( false } Vacant(e) => { + #[allow(clippy::unwrap_used)] let file_nbit = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -423,8 +430,10 @@ async fn run_wal_queue( } let task = async move { + #[allow(clippy::unwrap_used)] let _ = sem.acquire_many(npermit).await.unwrap(); + #[allow(clippy::unwrap_used)] wal.lock() .await .peel(ring_ids, max.revisions) @@ -486,6 +495,7 @@ async fn process( wal.replace(initialized_wal.clone()); + #[allow(clippy::unwrap_used)] let writes = writes.take().unwrap(); let task = run_wal_queue( @@ -500,6 +510,8 @@ async fn process( task::spawn_local(task); } + + #[allow(clippy::unwrap_used)] BufferCmd::GetPage(page_key, tx) => tx .send( pending @@ -509,10 +521,12 @@ async fn process( ) .unwrap(), BufferCmd::WriteBatch(writes, wal_writes) => { + #[allow(clippy::unwrap_used)] wal_in.send((writes, wal_writes)).await.unwrap(); } BufferCmd::CollectAsh(nrecords, tx) => { // wait to ensure writes are paused for Wal + #[allow(clippy::unwrap_used)] let ash = wal .as_ref() .unwrap() @@ -524,6 +538,7 @@ async fn process( .into_iter() .map(AshRecord::deserialize) .collect(); + #[allow(clippy::unwrap_used)] tx.send(ash).unwrap(); } BufferCmd::RegCachedSpace(space_id, files) => { @@ -569,6 +584,7 @@ impl DiskBufferRequester { .send(BufferCmd::GetPage((space_id, page_id), resp_tx)) .map_err(StoreError::Send) .ok(); + #[allow(clippy::unwrap_used)] resp_rx.blocking_recv().unwrap() } @@ -581,6 +597,7 @@ impl DiskBufferRequester { } pub fn shutdown(&self) { + #[allow(clippy::unwrap_used)] self.sender.send(BufferCmd::Shutdown).ok().unwrap() } @@ -615,6 +632,7 @@ impl DiskBufferRequester { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use sha3::Digest; use std::path::{Path, PathBuf}; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 4cbb18e5e32d..8d8d31f691c4 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -84,6 +84,7 @@ pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { fn serialize(&self) -> growthring::wal::WalBytes { + #[allow(clippy::unwrap_used)] bincode::serialize(self).unwrap().into() } } @@ -91,6 +92,7 @@ impl growthring::wal::Record for AshRecord { impl AshRecord { #[allow(clippy::boxed_local)] fn deserialize(raw: growthring::wal::WalBytes) -> Self { + #[allow(clippy::unwrap_used)] bincode::deserialize(raw.as_ref()).unwrap() } } @@ -146,6 +148,7 @@ impl StoreDelta { widx.sort_by_key(|i| writes[*i].offset); let mut witer = widx.into_iter(); + #[allow(clippy::unwrap_used)] let w0 = &writes[witer.next().unwrap()]; let mut head = w0.offset >> PAGE_SIZE_NBIT; let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; @@ -423,6 +426,7 @@ impl StoreRevMut { prev_deltas: &StoreRevMutDelta, pid: u64, ) -> &'a mut [u8] { + #[allow(clippy::unwrap_used)] let page = deltas .pages .entry(pid) @@ -609,46 +613,51 @@ impl MemStoreR for ZeroStore { } } -#[test] -fn test_from_ash() { - use rand::{rngs::StdRng, Rng, SeedableRng}; - let mut rng = StdRng::seed_from_u64(42); - let min = rng.gen_range(0..2 * PAGE_SIZE); - let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); - for _ in 0..20 { - let n = 20; - let mut canvas = vec![0; (max - min) as usize]; - let mut writes: Vec<_> = Vec::new(); - for _ in 0..n { - let l = rng.gen_range(min..max); - let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max)); - let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect(); - for (idx, byte) in (l..r).zip(data.iter()) { - canvas[(idx - min) as usize] = *byte; +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod test { + use super::*; + #[test] + fn test_from_ash() { + use rand::{rngs::StdRng, Rng, SeedableRng}; + let mut rng = StdRng::seed_from_u64(42); + let min = rng.gen_range(0..2 * PAGE_SIZE); + let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); + for _ in 0..20 { + let n = 20; + let mut canvas = vec![0; (max - min) as usize]; + let mut writes: Vec<_> = Vec::new(); + for _ in 0..n { + let l = rng.gen_range(min..max); + let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max)); + let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect(); + for (idx, byte) in (l..r).zip(data.iter()) { + canvas[(idx - min) as usize] = *byte; + } + println!("[0x{l:x}, 0x{r:x})"); + writes.push(SpaceWrite { offset: l, data }); } - println!("[0x{l:x}, 0x{r:x})"); - writes.push(SpaceWrite { offset: l, data }); - } - let z = Arc::new(ZeroStore::default()); - let rev = StoreRevShared::from_ash(z, &writes); - println!("{rev:?}"); - assert_eq!( - rev.get_view(min as usize, max - min) - .as_deref() - .unwrap() - .as_deref(), - canvas - ); - for _ in 0..2 * n { - let l = rng.gen_range(min..max); - let r = rng.gen_range(l + 1..max); + let z = Arc::new(ZeroStore::default()); + let rev = StoreRevShared::from_ash(z, &writes); + println!("{rev:?}"); assert_eq!( - rev.get_view(l as usize, r - l) + rev.get_view(min as usize, max - min) .as_deref() .unwrap() .as_deref(), - canvas[(l - min) as usize..(r - min) as usize] + canvas ); + for _ in 0..2 * n { + let l = rng.gen_range(min..max); + let r = rng.gen_range(l + 1..max); + assert_eq!( + rev.get_view(l as usize, r - l) + .as_deref() + .unwrap() + .as_deref(), + canvas[(l - min) as usize..(r - min) as usize] + ); + } } } } @@ -707,6 +716,7 @@ impl CachedSpace { for DeltaPage(pid, page) in &delta.0 { let data = self.inner.write().pin_page(self.space_id, *pid).ok()?; // save the original data + #[allow(clippy::unwrap_used)] pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap()))); // apply the change data.copy_from_slice(page.as_ref()); @@ -892,6 +902,7 @@ impl FilePool { impl Drop for FilePool { fn drop(&mut self) { + #[allow(clippy::unwrap_used)] let f0 = self.get_file(0).unwrap(); flock(f0.as_raw_fd(), FlockArg::UnlockNonblock).ok(); } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 762e0b73a7e0..79d952f762b1 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -78,6 +78,7 @@ impl DbView for HistoricalImpl { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; use crate::v2::api::{BatchOp, Proposal}; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index dda3f1d0f227..f3a01a53da61 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -134,7 +134,7 @@ impl api::DbView for Proposal { _last_key: Option, _limit: Option, ) -> Result, Vec>>, api::Error> { - todo!() + todo!(); } } diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs index 6d66c087d2f9..36c1c5d8d8c4 100644 --- a/firewood/tests/common/mod.rs +++ b/firewood/tests/common/mod.rs @@ -23,6 +23,7 @@ pub struct TestDb { } impl TestDbCreator { + #[allow(clippy::unwrap_used)] pub async fn create(self) -> TestDb { let path = self.path.clone().unwrap_or_else(|| { let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") @@ -66,6 +67,7 @@ impl TestDb { impl Drop for TestDb { fn drop(&mut self) { if !self.preserve_on_drop { + #[allow(clippy::unwrap_used)] remove_dir_all(self.creator.path.as_ref().unwrap()).unwrap(); } } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index 0fba4f854f7c..dc15398f409f 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -22,6 +22,7 @@ macro_rules! kv_dump { } #[tokio::test(flavor = "multi_thread")] +#[allow(clippy::unwrap_used)] async fn test_basic_metrics() { let cfg = DbConfig::builder() .meta_ncached_pages(1024) @@ -69,6 +70,7 @@ async fn test_basic_metrics() { } #[tokio::test(flavor = "multi_thread")] +#[allow(clippy::unwrap_used)] async fn test_revisions() { use rand::{rngs::StdRng, Rng, SeedableRng}; let cfg = DbConfig::builder() @@ -158,6 +160,7 @@ async fn test_revisions() { } #[tokio::test(flavor = "multi_thread")] +#[allow(clippy::unwrap_used)] async fn create_db_issue_proof() { let cfg = DbConfig::builder() .meta_ncached_pages(1024) @@ -246,6 +249,7 @@ macro_rules! assert_val { #[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] #[tokio::test(flavor = "multi_thread")] +#[allow(clippy::unwrap_used)] async fn db_proposal() -> Result<(), api::Error> { let cfg = DbConfig::builder() .wal(WalConfig::builder().max_revisions(10).build()) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index f9f2a4209286..f75fd2902630 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -77,6 +77,7 @@ fn test_root_hash_fuzz_insertions() -> Result<(), DataStoreError> { } #[test] +#[allow(clippy::unwrap_used)] fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { use rand::{rngs::StdRng, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -148,6 +149,7 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { } #[test] +#[allow(clippy::unwrap_used)] fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -205,6 +207,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { } #[test] +#[allow(clippy::unwrap_used)] fn test_proof() -> Result<(), DataStoreError> { let set = generate_random_data(500); let mut items = Vec::from_iter(set.iter()); diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index 4c90cbf5fd3d..99f4b3e624ba 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -12,6 +12,7 @@ pub mod common; use common::TestDbCreator; #[tokio::test(flavor = "multi_thread")] +#[allow(clippy::unwrap_used)] async fn smoke() -> Result<(), Box> { let cfg = DbConfig::builder().truncate(true).build(); let db = TestDbCreator::builder() From d7a0a13fa5429f3ae4fd7d5b42a843099ad22de8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 27 Dec 2023 08:48:15 -0800 Subject: [PATCH 0420/1053] Remove missing header --- firewood/src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/firewood/src/config.rs b/firewood/src/config.rs index dd4e3e0bd3c0..6a13e4ffd613 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -1,3 +1,4 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; From d33f663e4b0a4073be7f3a2bfbe289595d5782ad Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 2 Jan 2024 09:42:30 -0800 Subject: [PATCH 0421/1053] explicit deref methods removal (#458) --- firewood/Cargo.toml | 3 + firewood/benches/hashops.rs | 4 +- firewood/benches/shale-bench.rs | 1 + firewood/examples/insert.rs | 1 + firewood/src/db.rs | 16 +++- firewood/src/db/proposal.rs | 4 +- firewood/src/merkle.rs | 60 +++++++++++---- firewood/src/merkle/node.rs | 22 ++++-- firewood/src/merkle/node/branch.rs | 32 ++++---- firewood/src/merkle/node/extension.rs | 3 +- firewood/src/merkle/node/leaf.rs | 4 +- firewood/src/merkle/node/partial_path.rs | 3 +- firewood/src/merkle/proof.rs | 92 +++++++++++++++++----- firewood/src/merkle/stream.rs | 2 + firewood/src/merkle/trie_hash.rs | 2 + firewood/src/merkle_util.rs | 2 +- firewood/src/nibbles.rs | 18 +++-- firewood/src/shale/cached.rs | 11 ++- firewood/src/shale/compact.rs | 16 +++- firewood/src/shale/disk_address.rs | 6 +- firewood/src/shale/mod.rs | 8 +- firewood/src/storage/buffer.rs | 6 +- firewood/src/storage/mod.rs | 97 +++++++++++++++++------- firewood/src/v2/db.rs | 58 +------------- firewood/src/v2/propose.rs | 4 +- firewood/tests/merkle.rs | 38 +++++++--- growth-ring/src/wal.rs | 6 +- 27 files changed, 334 insertions(+), 185 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 37ba5d8b8aef..f3a9042f7a6b 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -54,3 +54,6 @@ harness = false [lints.clippy] unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" +missing_const_for_fn = "warn" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bce54c489670..0bb4b50d3380 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -18,7 +18,7 @@ use firewood::{ }; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; -use std::{fs::File, iter::repeat_with, ops::Deref, os::raw::c_int, path::Path, sync::Arc}; +use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path, sync::Arc}; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); @@ -66,7 +66,7 @@ impl Profiler for FlamegraphProfiler { fn bench_trie_hash(criterion: &mut Criterion) { let mut to = [1u8; TRIE_HASH_LEN]; let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); - store.write(0, ZERO_HASH.deref()); + store.write(0, &*ZERO_HASH); #[allow(clippy::unwrap_used)] criterion diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 5ee0417fa326..b17160c99e85 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -62,6 +62,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { b.iter(|| { let len = rng.gen_range(0..26); + #[allow(clippy::indexing_slicing)] let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); let offset = rng.gen_range(0..BENCH_MEM_SIZE - len); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 7ec4657eaa67..5e5276d6ff67 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -35,6 +35,7 @@ struct Args { fn string_to_range(input: &str) -> Result, Box> { //::Err> { let parts: Vec<&str> = input.split('-').collect(); + #[allow(clippy::indexing_slicing)] match parts.len() { 1 => Ok(input.parse()?..=input.parse()?), 2 => Ok(parts[0].parse()?..=parts[1].parse()?), diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 10c73ab7fd6a..dba39b00ba25 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -124,7 +124,7 @@ struct SubUniverse { } impl SubUniverse { - fn new(meta: T, payload: T) -> Self { + const fn new(meta: T, payload: T) -> Self { Self { meta, payload } } } @@ -196,7 +196,7 @@ struct DbHeader { impl DbHeader { pub const MSIZE: u64 = std::mem::size_of::() as u64; - pub fn new_empty() -> Self { + pub const fn new_empty() -> Self { Self { kv_root: DiskAddress::null(), } @@ -503,6 +503,7 @@ impl Db { let mut header_bytes = [0; size_of::()]; nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; drop(file0); + #[allow(clippy::indexing_slicing)] let params: DbParams = cast_slice(&header_bytes)[0]; let wal = WalConfig::builder() @@ -867,12 +868,14 @@ impl Db { .ok() .unwrap(); - nback = ashes + #[allow(clippy::indexing_slicing)] + (nback = ashes .iter() .skip(rlen) .map(|ash| { StoreRevShared::from_ash( Arc::new(ZeroStore::default()), + #[allow(clippy::indexing_slicing)] &ash.0[&ROOT_HASH_SPACE].redo, ) }) @@ -883,7 +886,7 @@ impl Db { .as_deref() }) .map(|data| TrieHash(data[..TRIE_HASH_LEN].try_into().unwrap())) - .position(|trie_hash| &trie_hash == root_hash); + .position(|trie_hash| &trie_hash == root_hash)); } let Some(nback) = nback else { @@ -902,11 +905,15 @@ impl Db { let u = match revisions.inner.back() { Some(u) => u.to_mem_store_r().rewind( + #[allow(clippy::indexing_slicing)] &ash.0[&MERKLE_META_SPACE].undo, + #[allow(clippy::indexing_slicing)] &ash.0[&MERKLE_PAYLOAD_SPACE].undo, ), None => inner_lock.cached_space.to_mem_store_r().rewind( + #[allow(clippy::indexing_slicing)] &ash.0[&MERKLE_META_SPACE].undo, + #[allow(clippy::indexing_slicing)] &ash.0[&MERKLE_PAYLOAD_SPACE].undo, ), }; @@ -917,6 +924,7 @@ impl Db { let space = if nback == 0 { &revisions.base } else { + #[allow(clippy::indexing_slicing)] &revisions.inner[nback - 1] }; // Release the lock after we find the revision diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 950c7346aec8..33c6f25a9696 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -259,7 +259,7 @@ impl Proposal { } impl Proposal { - pub fn get_revision(&self) -> &DbRev { + pub const fn get_revision(&self) -> &DbRev { &self.rev } } @@ -299,6 +299,6 @@ impl api::DbView for Proposal { where K: api::KeyType, { - todo!() + todo!(); } } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index c9b5c5453f5e..8c3b0b5d63b1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -290,8 +290,10 @@ impl + Send + Sync, T> Merkle { let mut chd = [None; BranchNode::MAX_CHILDREN]; + #[allow(clippy::indexing_slicing)] let last_matching_nibble = matching_path[idx]; - chd[last_matching_nibble as usize] = Some(leaf_address); + #[allow(clippy::indexing_slicing)] + (chd[last_matching_nibble as usize] = Some(leaf_address)); let address = match &node_to_split.inner { NodeType::Extension(u) if u.path.len() == 0 => { @@ -301,7 +303,8 @@ impl + Send + Sync, T> Merkle { _ => node_to_split_address, }; - chd[n_path[idx] as usize] = Some(address); + #[allow(clippy::indexing_slicing)] + (chd[n_path[idx] as usize] = Some(address)); let new_branch = Node::from_branch(BranchNode { // path: PartialPath(matching_path[..idx].to_vec()), @@ -314,6 +317,7 @@ impl + Send + Sync, T> Merkle { if idx > 0 { self.put_node(Node::from(NodeType::Extension(ExtNode { + #[allow(clippy::indexing_slicing)] path: PartialPath(matching_path[..idx].to_vec()), child: new_branch_address, child_encoded: None, @@ -388,7 +392,8 @@ impl + Send + Sync, T> Merkle { .write(|u| { // TODO: handle unwraps better let path = u.inner.path_mut(); - *path = PartialPath(n_path[insert_path.len() + 1..].to_vec()); + #[allow(clippy::indexing_slicing)] + (*path = PartialPath(n_path[insert_path.len() + 1..].to_vec())); u.rehash(); }) @@ -403,6 +408,7 @@ impl + Send + Sync, T> Merkle { _ => node_to_split_address, }; + #[allow(clippy::indexing_slicing)] ( leaf_address, insert_path, @@ -413,6 +419,7 @@ impl + Send + Sync, T> Merkle { // insert path is greather than the path of the leaf (Ordering::Greater, Some(n_value)) => { let leaf = Node::from_leaf(LeafNode::new( + #[allow(clippy::indexing_slicing)] PartialPath(insert_path[n_path.len() + 1..].to_vec()), Data(val), )); @@ -421,6 +428,7 @@ impl + Send + Sync, T> Merkle { deleted.push(node_to_split_address); + #[allow(clippy::indexing_slicing)] ( leaf_address, n_path.as_slice(), @@ -433,7 +441,8 @@ impl + Send + Sync, T> Merkle { // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] let mut children = [None; BranchNode::MAX_CHILDREN]; - children[idx] = leaf_address.into(); + #[allow(clippy::indexing_slicing)] + (children[idx] = leaf_address.into()); let branch_address = self .put_node(Node::from_branch(BranchNode { @@ -533,6 +542,7 @@ impl + Send + Sync, T> Merkle { } NodeType::Branch(n) => { + #[allow(clippy::indexing_slicing)] match n.children[current_nibble as usize] { Some(c) => (node, c), None => { @@ -548,7 +558,8 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::unwrap_used)] node.write(|u| { let uu = u.inner.as_branch_mut().unwrap(); - uu.children[current_nibble as usize] = Some(leaf_ptr); + #[allow(clippy::indexing_slicing)] + (uu.children[current_nibble as usize] = Some(leaf_ptr)); u.rehash(); }) .unwrap(); @@ -618,17 +629,21 @@ impl + Send + Sync, T> Merkle { None } else { + #[allow(clippy::indexing_slicing)] let idx = n.path[0]; - n.path = PartialPath(n.path[1..].to_vec()); + #[allow(clippy::indexing_slicing)] + (n.path = PartialPath(n.path[1..].to_vec())); u.rehash(); Some((idx, true, None, val)) } } NodeType::Extension(n) => { + #[allow(clippy::indexing_slicing)] let idx = n.path[0]; let more = if n.path.len() > 1 { - n.path = PartialPath(n.path[1..].to_vec()); + #[allow(clippy::indexing_slicing)] + (n.path = PartialPath(n.path[1..].to_vec())); true } else { false @@ -658,7 +673,8 @@ impl + Send + Sync, T> Merkle { ext.unwrap() }; - chd[idx as usize] = Some(c_ptr); + #[allow(clippy::indexing_slicing)] + (chd[idx as usize] = Some(c_ptr)); let branch = self .put_node(Node::from_branch(BranchNode { @@ -688,7 +704,8 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::unwrap_used)] b_ref .write(|b| { - b.inner.as_branch_mut().unwrap().children[b_idx as usize] = None; + #[allow(clippy::indexing_slicing)] + (b.inner.as_branch_mut().unwrap().children[b_idx as usize] = None); b.rehash() }) .unwrap(); @@ -715,7 +732,9 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::unwrap_used)] p_ref .write(|p| { - p.inner.as_branch_mut().unwrap().children[p_idx as usize] = Some(leaf); + #[allow(clippy::indexing_slicing)] + (p.inner.as_branch_mut().unwrap().children[p_idx as usize] = + Some(leaf)); p.rehash() }) .unwrap(); @@ -810,8 +829,9 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::unwrap_used)] p_ref .write(|p| { - p.inner.as_branch_mut().unwrap().children[p_idx as usize] = - Some(c_ptr); + #[allow(clippy::indexing_slicing)] + (p.inner.as_branch_mut().unwrap().children[p_idx as usize] = + Some(c_ptr)); p.rehash() }) .unwrap(); @@ -880,14 +900,15 @@ impl + Send + Sync, T> Merkle { NodeType::Branch(n) => { // from: [Branch] -> [Branch]x -> [Branch] // to: [Branch] -> [Ext] -> [Branch] - n.children[b_idx as usize] = Some( + #[allow(clippy::indexing_slicing)] + (n.children[b_idx as usize] = Some( self.put_node(Node::from(NodeType::Extension(ExtNode { path: PartialPath(vec![idx]), child: c_ptr, child_encoded: None, })))? .as_ptr(), - ); + )); } NodeType::Extension(n) => { // from: [Ext] -> [Branch]x -> [Branch] @@ -934,7 +955,9 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::unwrap_used)] b_ref .write(|b| { - b.inner.as_branch_mut().unwrap().children[b_idx as usize] = Some(c_ptr); + #[allow(clippy::indexing_slicing)] + (b.inner.as_branch_mut().unwrap().children[b_idx as usize] = + Some(c_ptr)); b.rehash() }) .unwrap(); @@ -1124,6 +1147,7 @@ impl + Send + Sync, T> Merkle { start_loop_callback(node_ref.as_ptr(), nib); let next_ptr = match &node_ref.inner { + #[allow(clippy::indexing_slicing)] NodeType::Branch(n) => match n.children[nib as usize] { Some(c) => c, None => return Ok(None), @@ -1351,6 +1375,7 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { p_ref .write(|p| { match &mut p.inner { + #[allow(clippy::indexing_slicing)] NodeType::Branch(pp) => pp.children[*idx as usize] = Some(new_chd), NodeType::Extension(pp) => *pp.chd_mut() = new_chd, _ => unreachable!(), @@ -1429,7 +1454,7 @@ impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { } // nibbles, high bits first, then low bits -pub fn to_nibble_array(x: u8) -> [u8; 2] { +pub const fn to_nibble_array(x: u8) -> [u8; 2] { [x >> 4, x & 0b_0000_1111] } @@ -1438,11 +1463,14 @@ pub fn to_nibble_array(x: u8) -> [u8; 2] { // the final nibble is dropped pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { debug_assert_eq!(nibbles.len() & 1, 0); + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use crate::merkle::node::PlainCodec; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 628a4911d278..f7a48fb5304f 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -265,7 +265,7 @@ impl Node { Self::from(NodeType::Leaf(leaf)) } - pub fn inner(&self) -> &NodeType { + pub const fn inner(&self) -> &NodeType { &self.inner } @@ -346,10 +346,12 @@ impl Storable for Node { offset += Meta::SIZE; + #[allow(clippy::indexing_slicing)] let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); let root_hash = if attrs.contains(NodeAttributes::ROOT_HASH_VALID) { Some(TrieHash( + #[allow(clippy::indexing_slicing)] meta_raw.as_deref()[..TRIE_HASH_LEN] .try_into() .expect("invalid slice"), @@ -358,6 +360,7 @@ impl Storable for Node { None }; + #[allow(clippy::indexing_slicing)] match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { NodeTypeId::Branch => { let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); @@ -425,6 +428,7 @@ impl Storable for Node { let pos = cur.position() as usize; + #[allow(clippy::indexing_slicing)] n.serialize(&mut cur.get_mut()[pos..]) } @@ -433,6 +437,7 @@ impl Storable for Node { let pos = cur.position() as usize; + #[allow(clippy::indexing_slicing)] n.serialize(&mut cur.get_mut()[pos..]) } @@ -441,6 +446,7 @@ impl Storable for Node { let pos = cur.position() as usize; + #[allow(clippy::indexing_slicing)] n.serialize(&mut cur.get_mut()[pos..]) } } @@ -453,7 +459,7 @@ pub struct EncodedNode { } impl EncodedNode { - pub fn new(node: EncodedNodeType) -> Self { + pub const fn new(node: EncodedNodeType) -> Self { Self { node, phantom: PhantomData, @@ -542,7 +548,8 @@ impl<'de> Deserialize<'de> for EncodedNode { let value = node.data.map(Data); for (i, chd) in node.chd { - children[i as usize] = Some(chd); + #[allow(clippy::indexing_slicing)] + (children[i as usize] = Some(chd)); } let node = EncodedNodeType::Branch { @@ -582,9 +589,11 @@ impl Serialize for EncodedNode { if c.len() >= TRIE_HASH_LEN { let serialized_hash = Bincode::serialize(&Keccak256::digest(c).to_vec()) .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; - list[i] = Encoded::Data(serialized_hash); + #[allow(clippy::indexing_slicing)] + (list[i] = Encoded::Data(serialized_hash)); } else { - list[i] = Encoded::Raw(c.to_vec()); + #[allow(clippy::indexing_slicing)] + (list[i] = Encoded::Raw(c.to_vec())); } } if let Some(Data(val)) = &value { @@ -655,7 +664,8 @@ impl<'de> Deserialize<'de> for EncodedNode { Encoded::Data(chd) => Bincode::deserialize(chd.as_ref()) .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, }; - children[i] = Some(chd).filter(|chd| !chd.is_empty()); + #[allow(clippy::indexing_slicing)] + (children[i] = Some(chd).filter(|chd| !chd.is_empty())); } } let node = EncodedNodeType::Branch { diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index a6edab6f69f8..145ddee0980f 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -12,7 +12,6 @@ use std::{ fmt::{Debug, Error as FmtError, Formatter}, io::{Cursor, Read, Write}, mem::size_of, - ops::Deref, }; pub type DataLen = u32; @@ -74,11 +73,11 @@ impl BranchNode { } } - pub fn value(&self) -> &Option { + pub const fn value(&self) -> &Option { &self.value } - pub fn chd(&self) -> &[Option; Self::MAX_CHILDREN] { + pub const fn chd(&self) -> &[Option; Self::MAX_CHILDREN] { &self.children } @@ -86,7 +85,7 @@ impl BranchNode { &mut self.children } - pub fn chd_encode(&self) -> &[Option>; Self::MAX_CHILDREN] { + pub const fn chd_encode(&self) -> &[Option>; Self::MAX_CHILDREN] { &self.children_encoded } @@ -125,7 +124,8 @@ impl BranchNode { // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { let data = chd.decode()?; - chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); + #[allow(clippy::indexing_slicing)] + (chd_encoded[i] = Some(data).filter(|data| !data.is_empty())); } // TODO: add path @@ -151,11 +151,12 @@ impl BranchNode { #[allow(clippy::unwrap_used)] if c_ref.is_encoded_longer_than_hash_len::(store) { - list[i] = Encoded::Data( + #[allow(clippy::indexing_slicing)] + (list[i] = Encoded::Data( bincode::DefaultOptions::new() .serialize(&&(*c_ref.get_root_hash::(store))[..]) .unwrap(), - ); + )); // See struct docs for ordering requirements if c_ref.is_dirty() { @@ -164,7 +165,8 @@ impl BranchNode { } } else { let child_encoded = &c_ref.get_encoded::(store); - list[i] = Encoded::Raw(child_encoded.to_vec()); + #[allow(clippy::indexing_slicing)] + (list[i] = Encoded::Raw(child_encoded.to_vec())); } } @@ -177,13 +179,17 @@ impl BranchNode { None => { // Check if there is already a calculated encoded value for the child, which // can happen when manually constructing a trie from proof. + #[allow(clippy::indexing_slicing)] if let Some(v) = &self.children_encoded[i] { - #[allow(clippy::unwrap_used)] if v.len() == TRIE_HASH_LEN { - list[i] = - Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); + #[allow(clippy::indexing_slicing)] + #[allow(clippy::unwrap_used)] + (list[i] = Encoded::Data( + bincode::DefaultOptions::new().serialize(v).unwrap(), + )); } else { - list[i] = Encoded::Raw(v.clone()); + #[allow(clippy::indexing_slicing)] + (list[i] = Encoded::Raw(v.clone())); } } } @@ -225,7 +231,7 @@ impl Storable for BranchNode { let (value_len, value) = self .value .as_ref() - .map(|val| (val.len() as DataLen, val.deref())) + .map(|val| (val.len() as DataLen, &**val)) .unwrap_or((DataLen::MAX, &[])); cursor.write_all(&value_len.to_le_bytes())?; diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs index a05e475312ee..dd8f33bbf7d1 100644 --- a/firewood/src/merkle/node/extension.rs +++ b/firewood/src/merkle/node/extension.rs @@ -85,7 +85,7 @@ impl ExtNode { .unwrap() } - pub fn chd(&self) -> DiskAddress { + pub const fn chd(&self) -> DiskAddress { self.child } @@ -159,6 +159,7 @@ impl Storable for ExtNode { let mut cursor = Cursor::new(path_and_disk_address); let mut buf = [0u8; DiskAddress::MSIZE as usize]; + #[allow(clippy::indexing_slicing)] let path_len = { let buf = &mut buf[..Self::PATH_LEN_SIZE as usize]; cursor.read_exact(buf)?; diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 5b346a0ee980..5b058a66174c 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -43,11 +43,11 @@ impl LeafNode { } } - pub fn path(&self) -> &PartialPath { + pub const fn path(&self) -> &PartialPath { &self.path } - pub fn data(&self) -> &Data { + pub const fn data(&self) -> &Data { &self.data } diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs index d213dd05d826..d8f9904e8844 100644 --- a/firewood/src/merkle/node/partial_path.rs +++ b/firewood/src/merkle/node/partial_path.rs @@ -107,7 +107,6 @@ impl PartialPath { #[cfg(test)] mod tests { use super::*; - use std::ops::Deref; use test_case::test_case; #[test_case(&[1, 2, 3, 4], true)] @@ -123,7 +122,7 @@ mod tests { let (decoded, decoded_term) = PartialPath::decode(&encoded); - assert_eq!(&decoded.deref(), &steps); + assert_eq!(&&*decoded, &steps); assert_eq!(decoded_term, term); } } diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index d279fb371cea..ad79a329749d 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -157,6 +157,8 @@ impl + Send> Proof { } // Ensure the received batch is monotonic increasing and contains no deletions + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] if !keys.windows(2).all(|w| w[0].as_ref() < w[1].as_ref()) { return Err(ProofError::NonMonotonicIncreaseRange); } @@ -168,6 +170,7 @@ impl + Send> Proof { // to be the whole leaf-set in the trie. if self.0.is_empty() { for (index, k) in keys.iter().enumerate() { + #[allow(clippy::indexing_slicing)] merkle_setup.insert(k, vals[index].as_ref().to_vec())?; } @@ -197,11 +200,13 @@ impl + Send> Proof { let data = self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, false)?; + #[allow(clippy::indexing_slicing)] return if first_key.as_ref() != keys[0].as_ref() { // correct proof but invalid key Err(ProofError::InvalidEdgeKeys) } else { match data { + #[allow(clippy::indexing_slicing)] Some(val) if val == vals[0].as_ref() => Ok(true), None => Ok(false), _ => Err(ProofError::InvalidData), @@ -285,6 +290,7 @@ impl + Send> Proof { // Link the child to the parent based on the node type. match &u_ref.inner() { + #[allow(clippy::indexing_slicing)] NodeType::Branch(n) => match n.chd()[branch_index] { // If the child already resolved, then use the existing node. Some(node) => { @@ -296,7 +302,8 @@ impl + Send> Proof { u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index] = Some(chd_ptr); + #[allow(clippy::indexing_slicing)] + (uu.chd_mut()[branch_index] = Some(chd_ptr)); }) .unwrap(); } @@ -328,7 +335,8 @@ impl + Send> Proof { // If the new parent is a branch node, record the index to correctly link the next child to it. if u_ref.inner().as_branch().is_some() { - branch_index = chunks[key_index] as usize; + #[allow(clippy::indexing_slicing)] + (branch_index = chunks[key_index] as usize); } key_index += size; @@ -346,7 +354,8 @@ impl + Send> Proof { Some(p) => { // Return when reaching the end of the key. if key_index == chunks.len() { - cur_key = &chunks[key_index..]; + #[allow(clippy::indexing_slicing)] + (cur_key = &chunks[key_index..]); let mut data = None; // Decode the last subproof to get the value. @@ -360,13 +369,15 @@ impl + Send> Proof { // Link the child to the parent based on the node type. match &u_ref.inner() { // TODO: add path + #[allow(clippy::indexing_slicing)] NodeType::Branch(n) if n.chd()[branch_index].is_none() => { // insert the leaf to the empty slot #[allow(clippy::unwrap_used)] u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[branch_index] = Some(chd_ptr); + #[allow(clippy::indexing_slicing)] + (uu.chd_mut()[branch_index] = Some(chd_ptr)); }) .unwrap(); } @@ -435,7 +446,8 @@ impl + Send> Proof { }; cur_hash = hash; - cur_key = &chunks[key_index..]; + #[allow(clippy::indexing_slicing)] + (cur_key = &chunks[key_index..]); } } } @@ -489,7 +501,9 @@ impl + Send> Proof { NodeType::Branch(_) if key.is_empty() => Err(ProofError::NoSuchNode), NodeType::Branch(n) => { // Check if the subproof with the given key exist. + #[allow(clippy::indexing_slicing)] let index = key[0] as usize; + #[allow(clippy::indexing_slicing)] let Some(data) = &n.chd_encode()[index] else { return Ok((addr, None, 1)); }; @@ -547,6 +561,7 @@ fn locate_subproof( }; // consume items returning the item at index + #[allow(clippy::indexing_slicing)] let data = n.chd_encode()[index] .as_ref() .ok_or(ProofError::InvalidData)? @@ -615,7 +630,11 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe NodeType::Branch(n) => { // If either the node pointed by left proof or right proof is nil, // stop here and the forkpoint is the fullnode. + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] let left_node = n.chd()[left_chunks[index] as usize]; + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] let right_node = n.chd()[right_chunks[index] as usize]; match (left_node.as_ref(), right_node.as_ref()) { @@ -635,14 +654,18 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe let cur_key = n.path.clone().into_inner(); fork_left = if left_chunks.len() - index < cur_key.len() { + #[allow(clippy::indexing_slicing)] left_chunks[index..].cmp(&cur_key) } else { + #[allow(clippy::indexing_slicing)] left_chunks[index..index + cur_key.len()].cmp(&cur_key) }; fork_right = if right_chunks.len() - index < cur_key.len() { + #[allow(clippy::indexing_slicing)] right_chunks[index..].cmp(&cur_key) } else { + #[allow(clippy::indexing_slicing)] right_chunks[index..index + cur_key.len()].cmp(&cur_key) }; @@ -659,14 +682,18 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe let cur_key = n.path(); fork_left = if left_chunks.len() - index < cur_key.len() { + #[allow(clippy::indexing_slicing)] left_chunks[index..].cmp(cur_key) } else { + #[allow(clippy::indexing_slicing)] left_chunks[index..index + cur_key.len()].cmp(cur_key) }; fork_right = if right_chunks.len() - index < cur_key.len() { + #[allow(clippy::indexing_slicing)] right_chunks[index..].cmp(cur_key) } else { + #[allow(clippy::indexing_slicing)] right_chunks[index..index + cur_key.len()].cmp(cur_key) }; @@ -677,24 +704,34 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe match &u_ref.inner() { NodeType::Branch(n) => { + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] let left_node = n.chd()[left_chunks[index] as usize]; + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] let right_node = n.chd()[right_chunks[index] as usize]; // unset all internal nodes calculated encoded value in the forkpoint #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] for i in left_chunks[index] + 1..right_chunks[index] { u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[i as usize] = None; - uu.chd_encoded_mut()[i as usize] = None; + #[allow(clippy::indexing_slicing)] + (uu.chd_mut()[i as usize] = None); + #[allow(clippy::indexing_slicing)] + (uu.chd_encoded_mut()[i as usize] = None); }) .unwrap(); } let p = u_ref.as_ptr(); drop(u_ref); + #[allow(clippy::indexing_slicing)] unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; + #[allow(clippy::indexing_slicing)] unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; Ok(false) } @@ -730,8 +767,10 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[left_chunks[index - 1] as usize] = None; - pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; + #[allow(clippy::indexing_slicing)] + (pp.chd_mut()[left_chunks[index - 1] as usize] = None); + #[allow(clippy::indexing_slicing)] + (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); }) .unwrap(); @@ -747,6 +786,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe merkle, p, Some(node), + #[allow(clippy::indexing_slicing)] &left_chunks[index..], cur_key.len(), false, @@ -760,6 +800,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe merkle, p, Some(node), + #[allow(clippy::indexing_slicing)] &right_chunks[index..], cur_key.len(), true, @@ -793,8 +834,10 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe *n.chd_encoded_mut() = None; } NodeType::Branch(n) => { - n.chd_mut()[left_chunks[index - 1] as usize] = None; - n.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; + #[allow(clippy::indexing_slicing)] + (n.chd_mut()[left_chunks[index - 1] as usize] = None); + #[allow(clippy::indexing_slicing)] + (n.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); } _ => {} }) @@ -803,16 +846,20 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[left_chunks[index - 1] as usize] = None; - pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None; + #[allow(clippy::indexing_slicing)] + (pp.chd_mut()[left_chunks[index - 1] as usize] = None); + #[allow(clippy::indexing_slicing)] + (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); }) .unwrap(); } else if fork_left.is_ne() { p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[right_chunks[index - 1] as usize] = None; - pp.chd_encoded_mut()[right_chunks[index - 1] as usize] = None; + #[allow(clippy::indexing_slicing)] + (pp.chd_mut()[right_chunks[index - 1] as usize] = None); + #[allow(clippy::indexing_slicing)] + (pp.chd_encoded_mut()[right_chunks[index - 1] as usize] = None); }) .unwrap(); } @@ -859,8 +906,10 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe match &u_ref.inner() { NodeType::Branch(n) => { + #[allow(clippy::indexing_slicing)] let child_index = chunks[index] as usize; + #[allow(clippy::indexing_slicing)] let node = n.chd()[child_index]; let iter = if remove_left { @@ -874,8 +923,10 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe u_ref .write(|u| { let uu = u.inner_mut().as_branch_mut().unwrap(); - uu.chd_mut()[i] = None; - uu.chd_encoded_mut()[i] = None; + #[allow(clippy::indexing_slicing)] + (uu.chd_mut()[i] = None); + #[allow(clippy::indexing_slicing)] + (uu.chd_encoded_mut()[i] = None); }) .unwrap(); } @@ -885,6 +936,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe unset_node_ref(merkle, p, node, key, index + 1, remove_left) } + #[allow(clippy::indexing_slicing)] NodeType::Extension(n) if chunks[index..].starts_with(&n.path) => { let node = Some(n.chd()); unset_node_ref(merkle, p, node, key, index + n.path.len(), remove_left) @@ -909,12 +961,14 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe // branch. The parent must be a fullnode. Otherwise the // key is not part of the range and should remain in the // cached hash. + #[allow(clippy::indexing_slicing)] let should_unset_entire_branch = matches!( (remove_left, cur_key.cmp(&chunks[index..])), (true, Ordering::Less) | (false, Ordering::Greater) ); #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] if should_unset_entire_branch { p_ref .write(|p| { @@ -936,7 +990,9 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe // Similar to branch node, we need to compare the path to see if the node // needs to be unset. + #[allow(clippy::indexing_slicing)] if !(chunks[index..]).starts_with(cur_key) { + #[allow(clippy::indexing_slicing)] match (cur_key.cmp(&chunks[index..]), remove_left) { (Ordering::Greater, false) | (Ordering::Less, true) => { p_ref @@ -958,6 +1014,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe *n.chd_encoded_mut() = None; } NodeType::Branch(n) => { + #[allow(clippy::indexing_slicing)] let index = chunks[index - 1] as usize; n.chd_mut()[index] = None; @@ -981,6 +1038,7 @@ impl ContainsOtherExt for &[T] where [T]: PartialEq<[T]>, { + #[allow(clippy::indexing_slicing)] fn contains_other(&self, other: Self) -> bool { self.len() >= other.len() && &self[..other.len()] == other } diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index b6150ecbe285..9aca12ad4e9f 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -414,6 +414,7 @@ use super::tests::create_test_merkle; #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use crate::nibbles::Nibbles; @@ -449,6 +450,7 @@ mod tests { }; // we iterate twice because we should get a None then start over + #[allow(clippy::indexing_slicing)] for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { let next = stream.next().await.map(|kv| { let (k, v) = kv.unwrap(); diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index e6e5b4a697fb..3dff124d45b5 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -29,6 +29,7 @@ impl Storable for TrieHash { size: U64_TRIE_HASH_LEN, })?; #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) } @@ -48,6 +49,7 @@ impl Debug for TrieHash { } #[cfg(test)] +#[allow(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 879d394a883d..df2a1164a1e7 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -64,7 +64,7 @@ impl + Send + Sync, T: BinarySerde> MerkleSetup { .map_err(|_err| DataStoreError::GetError) } - pub fn get_root(&self) -> DiskAddress { + pub const fn get_root(&self) -> DiskAddress { self.root } diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 0f6152b06eaf..2649a771ed1c 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -46,9 +46,14 @@ impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROE fn index(&self, index: usize) -> &Self::Output { match index { _ if index < LEADING_ZEROES => &NIBBLES[0], - _ if (index - LEADING_ZEROES) % 2 == 0 => { + _ if (index - LEADING_ZEROES) % 2 == 0 => + { + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] >> 4) as usize] } + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] _ => &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] & 0xf) as usize], } } @@ -70,16 +75,16 @@ impl<'a, const LEADING_ZEROES: usize> IntoIterator for Nibbles<'a, LEADING_ZEROE impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { #[must_use] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { LEADING_ZEROES + 2 * self.0.len() } #[must_use] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { LEADING_ZEROES == 0 && self.0.is_empty() } - pub fn new(inner: &'a [u8]) -> Self { + pub const fn new(inner: &'a [u8]) -> Self { Nibbles(inner) } } @@ -102,6 +107,7 @@ impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_Z if self.is_empty() { return None; } + #[allow(clippy::indexing_slicing)] let result = Some(self.data[self.head]); self.head += 1; result @@ -120,7 +126,7 @@ impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_Z impl<'a, const LEADING_ZEROES: usize> NibblesIterator<'a, LEADING_ZEROES> { #[inline(always)] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.head == self.tail } } @@ -131,6 +137,7 @@ impl<'a, const LEADING_ZEROES: usize> DoubleEndedIterator for NibblesIterator<'a return None; } self.tail -= 1; + #[allow(clippy::indexing_slicing)] Some(self.data[self.tail]) } @@ -141,6 +148,7 @@ impl<'a, const LEADING_ZEROES: usize> DoubleEndedIterator for NibblesIterator<'a } #[cfg(test)] +#[allow(clippy::indexing_slicing)] mod test { use super::Nibbles; static TEST_BYTES: [u8; 4] = [0xdeu8, 0xad, 0xbe, 0xef]; diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 7f9dcf5c7a7a..18ec3ecbf995 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -59,6 +59,7 @@ impl CachedStore for PlainMem { let length = change.len(); #[allow(clippy::unwrap_used)] let mut vect = self.space.deref().write().unwrap(); + #[allow(clippy::indexing_slicing)] vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); } @@ -94,6 +95,7 @@ impl CachedView for PlainMemView { fn as_deref(&self) -> Self::DerefReturn { #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } @@ -158,6 +160,7 @@ impl CachedStore for DynamicMem { if size > space.len() { space.resize(size, 0); } + #[allow(clippy::indexing_slicing)] space[offset..offset + length].copy_from_slice(change) } @@ -193,19 +196,21 @@ impl CachedView for DynamicMemView { fn as_deref(&self) -> Self::DerefReturn { #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use super::*; #[test] fn test_plain_mem() { let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = view.deref_mut(); + let mem = &mut *view; mem.write(0, &[1, 1]); mem.write(0, &[1, 2]); #[allow(clippy::unwrap_used)] @@ -226,7 +231,7 @@ mod tests { #[should_panic(expected = "index 3 out of range for slice of length 2")] fn test_plain_mem_panic() { let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = view.deref_mut(); + let mem = &mut *view; // out of range mem.write(1, &[7, 8]); @@ -235,7 +240,7 @@ mod tests { #[test] fn test_dynamic_mem() { let mut view = DynamicMemShared(DynamicMem::new(2, 0)); - let mem = view.deref_mut(); + let mem = &mut *view; mem.write(0, &[1, 2]); mem.write(0, &[3, 4]); assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 35dee2dfc6c0..8fc1a02c3a46 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -22,11 +22,11 @@ pub struct CompactHeader { impl CompactHeader { pub const MSIZE: u64 = 17; - pub fn is_freed(&self) -> bool { + pub const fn is_freed(&self) -> bool { self.is_freed } - pub fn payload_size(&self) -> u64 { + pub const fn payload_size(&self) -> u64 { self.payload_size } } @@ -39,9 +39,12 @@ impl Storable for CompactHeader { offset: addr, size: Self::MSIZE, })?; + #[allow(clippy::indexing_slicing)] let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + #[allow(clippy::indexing_slicing)] let is_freed = raw.as_deref()[8] != 0; + #[allow(clippy::indexing_slicing)] let desc_addr = usize::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); Ok(Self { @@ -114,8 +117,10 @@ impl Storable for CompactDescriptor { offset: addr, size: Self::MSIZE, })?; + #[allow(clippy::indexing_slicing)] let payload_size = u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + #[allow(clippy::indexing_slicing)] let haddr = usize::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); Ok(Self { payload_size, @@ -164,7 +169,7 @@ impl CompactSpaceHeaderSliced { impl CompactSpaceHeader { pub const MSIZE: u64 = 32; - pub fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { + pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { meta_space_tail: DiskAddress::new(meta_base), compact_space_tail: DiskAddress::new(compact_base), @@ -191,9 +196,13 @@ impl Storable for CompactSpaceHeader { offset: addr, size: Self::MSIZE, })?; + #[allow(clippy::indexing_slicing)] let meta_space_tail = raw.as_deref()[..8].into(); + #[allow(clippy::indexing_slicing)] let compact_space_tail = raw.as_deref()[8..16].into(); + #[allow(clippy::indexing_slicing)] let base_addr = raw.as_deref()[16..24].into(); + #[allow(clippy::indexing_slicing)] let alloc_addr = raw.as_deref()[24..].into(); Ok(Self { meta_space_tail, @@ -642,6 +651,7 @@ impl ShaleStore for Comp #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use sha3::Digest; diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 8764a224e984..679bde29ae27 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -33,7 +33,7 @@ impl DiskAddress { pub(crate) const MSIZE: u64 = size_of::() as u64; /// Return a None DiskAddress - pub fn null() -> Self { + pub const fn null() -> Self { DiskAddress(None) } @@ -43,7 +43,7 @@ impl DiskAddress { } /// Convert a NonZeroUsize to a DiskAddress - pub fn new(addr: NonZeroUsize) -> Self { + pub const fn new(addr: NonZeroUsize) -> Self { DiskAddress(Some(addr)) } @@ -182,7 +182,7 @@ impl Storable for DiskAddress { offset: addr, size: Self::MSIZE, })?; - let addrdyn = raw.deref(); + let addrdyn = &*raw; let addrvec = addrdyn.as_deref(); #[allow(clippy::unwrap_used)] Ok(Self(NonZeroUsize::new(usize::from_le_bytes( diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index d2f4efdc405b..a070c411506b 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -105,7 +105,7 @@ pub struct Obj { impl Obj { #[inline(always)] - pub fn as_ptr(&self) -> DiskAddress { + pub const fn as_ptr(&self) -> DiskAddress { DiskAddress(NonZeroUsize::new(self.value.get_offset())) } @@ -129,7 +129,7 @@ impl Obj { } #[inline(always)] - pub fn from_typed_view(value: StoredView) -> Self { + pub const fn from_typed_view(value: StoredView) -> Self { Obj { value, dirty: None } } @@ -171,7 +171,7 @@ pub struct ObjRef<'a, T: Storable> { } impl<'a, T: Storable> ObjRef<'a, T> { - fn new(inner: Option>, cache: &'a ObjCache) -> Self { + const fn new(inner: Option>, cache: &'a ObjCache) -> Self { Self { inner, cache } } @@ -279,7 +279,7 @@ impl Deref for StoredView { } impl StoredView { - fn get_offset(&self) -> usize { + const fn get_offset(&self) -> usize { self.offset } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index a84ba45d8108..d691cfe53c7a 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -226,6 +226,7 @@ fn schedule_write( let fid = offset >> p.file_nbit; let fmask = (1 << p.file_nbit) - 1; #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] let file = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -301,6 +302,7 @@ async fn init_wal( let offset = undo.offset; let file_pools = file_pools.borrow(); #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] let file_pool = file_pools[space_id as usize].as_ref().unwrap(); let file_nbit = file_pool.get_file_nbit(); let file_mask = (1 << file_nbit) - 1; @@ -393,6 +395,7 @@ async fn run_wal_queue( } Vacant(e) => { #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] let file_nbit = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -573,7 +576,7 @@ pub struct DiskBufferRequester { impl DiskBufferRequester { /// Create a new requester. - pub fn new(sender: mpsc::UnboundedSender) -> Self { + pub const fn new(sender: mpsc::UnboundedSender) -> Self { Self { sender } } @@ -633,6 +636,7 @@ impl DiskBufferRequester { #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use sha3::Digest; use std::path::{Path, PathBuf}; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 8d8d31f691c4..24898b9bec9e 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -102,7 +102,7 @@ pub struct DeltaPage(u64, Page); impl DeltaPage { #[inline(always)] - fn offset(&self) -> u64 { + const fn offset(&self) -> u64 { self.0 << PAGE_SIZE_NBIT } @@ -136,6 +136,7 @@ impl Deref for StoreDelta { impl StoreDelta { pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { let mut deltas = Vec::new(); + #[allow(clippy::indexing_slicing)] let mut widx: Vec<_> = (0..writes.len()) .filter(|i| writes[*i].data.len() > 0) .collect(); @@ -145,10 +146,12 @@ impl StoreDelta { } // sort by the starting point + #[allow(clippy::indexing_slicing)] widx.sort_by_key(|i| writes[*i].offset); let mut witer = widx.into_iter(); #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing)] let w0 = &writes[witer.next().unwrap()]; let mut head = w0.offset >> PAGE_SIZE_NBIT; let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; @@ -166,6 +169,7 @@ impl StoreDelta { } for i in witer { + #[allow(clippy::indexing_slicing)] let w = &writes[i]; let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; let wp = w.offset >> PAGE_SIZE_NBIT; @@ -185,23 +189,31 @@ impl StoreDelta { let mut r = deltas.len(); while r - l > 1 { let mid = (l + r) >> 1; - (*if w.offset < deltas[mid].offset() { + #[allow(clippy::indexing_slicing)] + ((*if w.offset < deltas[mid].offset() { &mut r } else { &mut l - }) = mid; + }) = mid); } + #[allow(clippy::indexing_slicing)] let off = (w.offset - deltas[l].offset()) as usize; let len = std::cmp::min(psize - off, w.data.len()); + #[allow(clippy::indexing_slicing)] deltas[l].data_mut()[off..off + len].copy_from_slice(&w.data[..len]); + #[allow(clippy::indexing_slicing)] let mut data = &w.data[len..]; while data.len() >= psize { l += 1; + #[allow(clippy::indexing_slicing)] deltas[l].data_mut().copy_from_slice(&data[..psize]); - data = &data[psize..]; + #[allow(clippy::indexing_slicing)] + (data = &data[psize..]); } if !data.is_empty() { l += 1; + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] deltas[l].data_mut()[..data.len()].copy_from_slice(data); } } @@ -239,42 +251,63 @@ impl MemStoreR for StoreRev { // otherwise, some dirty pages are covered by the range while r - l > 1 { let mid = (l + r) >> 1; - (*if start < delta[mid].offset() { + #[allow(clippy::indexing_slicing)] + ((*if start < delta[mid].offset() { &mut r } else { &mut l - }) = mid; + }) = mid); } + #[allow(clippy::indexing_slicing)] if start >= delta[l].offset() + PAGE_SIZE { l += 1 } + #[allow(clippy::indexing_slicing)] if l >= delta.len() || end < delta[l].offset() { return base_space.get_slice(start, end - start); } let mut data = Vec::new(); + #[allow(clippy::indexing_slicing)] let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE); + #[allow(clippy::indexing_slicing)] if start < delta[l].offset() { + #[allow(clippy::indexing_slicing)] data.extend(base_space.get_slice(start, delta[l].offset() - start)?); + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[..p_off as usize]); } else { + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]); }; - start = delta[l].offset() + p_off; + #[allow(clippy::indexing_slicing)] + (start = delta[l].offset() + p_off); while start < end { l += 1; + #[allow(clippy::indexing_slicing)] if l >= delta.len() || end < delta[l].offset() { data.extend(base_space.get_slice(start, end - start)?); break; } + #[allow(clippy::indexing_slicing)] if delta[l].offset() > start { + #[allow(clippy::indexing_slicing)] data.extend(base_space.get_slice(start, delta[l].offset() - start)?); } + #[allow(clippy::indexing_slicing)] if end < delta[l].offset() + PAGE_SIZE { + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); break; } + #[allow(clippy::indexing_slicing)] data.extend(delta[l].data()); - start = delta[l].offset() + PAGE_SIZE; + #[allow(clippy::indexing_slicing)] + (start = delta[l].offset() + PAGE_SIZE); } assert!(data.len() == length as usize); Some(data) @@ -304,7 +337,7 @@ impl StoreRevShared { *self.0.base_space.write() = base_space } - pub fn inner(&self) -> &Arc { + pub const fn inner(&self) -> &Arc { &self.0 } } @@ -480,16 +513,20 @@ impl CachedStore for StoreRevMut { let prev_deltas = &self.prev_deltas.read().pages; if s_pid == e_pid { match deltas.get(&s_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..e_off + 1].to_vec(), None => match prev_deltas.get(&s_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..e_off + 1].to_vec(), None => self.base_space.get_slice(offset as u64, length)?, }, } } else { let mut data = match deltas.get(&s_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..].to_vec(), None => match prev_deltas.get(&s_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..].to_vec(), None => self .base_space @@ -508,8 +545,10 @@ impl CachedStore for StoreRevMut { }; } match deltas.get(&e_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => data.extend(&p[..e_off + 1]), None => match prev_deltas.get(&e_pid) { + #[allow(clippy::indexing_slicing)] Some(p) => data.extend(&p[..e_off + 1]), None => data.extend( self.base_space @@ -539,9 +578,9 @@ impl CachedStore for StoreRevMut { if s_pid == e_pid { let mut deltas = self.deltas.write(); - let slice = - &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), s_pid as u64) - [s_off..e_off + 1]; + #[allow(clippy::indexing_slicing)] + let slice = &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), s_pid as u64) + [s_off..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change) } else { @@ -549,29 +588,31 @@ impl CachedStore for StoreRevMut { { let mut deltas = self.deltas.write(); - let slice = &mut self.get_page_mut( - deltas.deref_mut(), - &self.prev_deltas.read(), - s_pid as u64, - )[s_off..]; + #[allow(clippy::indexing_slicing)] + let slice = + &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), s_pid as u64) + [s_off..]; undo.extend(&*slice); + #[allow(clippy::indexing_slicing)] slice.copy_from_slice(&change[..len]); } - change = &change[len..]; + #[allow(clippy::indexing_slicing)] + (change = &change[len..]); let mut deltas = self.deltas.write(); for p in s_pid + 1..e_pid { - let slice = - self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), p as u64); + let slice = self.get_page_mut(&mut deltas, &self.prev_deltas.read(), p as u64); undo.extend(&*slice); + #[allow(clippy::indexing_slicing)] slice.copy_from_slice(&change[..PAGE_SIZE as usize]); - change = &change[PAGE_SIZE as usize..]; + #[allow(clippy::indexing_slicing)] + (change = &change[PAGE_SIZE as usize..]); } - let slice = - &mut self.get_page_mut(deltas.deref_mut(), &self.prev_deltas.read(), e_pid as u64) - [..e_off + 1]; + #[allow(clippy::indexing_slicing)] + let slice = &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), e_pid as u64) + [..e_off + 1]; undo.extend(&*slice); slice.copy_from_slice(change); } @@ -615,6 +656,7 @@ impl MemStoreR for ZeroStore { #[cfg(test)] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod test { use super::*; #[test] @@ -752,7 +794,7 @@ impl CachedSpaceInner { nix::sys::uio::pread( file.as_fd(), - page.deref_mut(), + &mut *page, (poff & (file_size - 1)) as nix::libc::off_t, ) .map_err(StoreError::System)?; @@ -836,14 +878,17 @@ impl MemStoreR for CachedSpace { let e_pid = end >> PAGE_SIZE_NBIT; let e_off = (end & PAGE_MASK) as usize; if s_pid == e_pid { + #[allow(clippy::indexing_slicing)] return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec()); } let mut data: Vec = Vec::new(); { + #[allow(clippy::indexing_slicing)] data.extend(&PageRef::new(s_pid, self)?[s_off..]); for p in s_pid + 1..e_pid { data.extend(&PageRef::new(p, self)?[..]); } + #[allow(clippy::indexing_slicing)] data.extend(&PageRef::new(e_pid, self)?[..e_off + 1]); } Some(data) @@ -895,7 +940,7 @@ impl FilePool { Ok(file) } - fn get_file_nbit(&self) -> u64 { + const fn get_file_nbit(&self) -> u64 { self.file_nbit } } diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 3e6378159ff4..feeefb003895 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,18 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{fmt::Debug, ops::DerefMut, sync::Arc}; - -use tokio::sync::Mutex; - -use async_trait::async_trait; - -use crate::{ - db::DbError, - v2::api::{self, Batch, KeyType, ValueType}, -}; - -use super::propose; +use crate::{db::DbError, v2::api}; #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid @@ -24,10 +13,6 @@ use super::propose; /// Proposal -- translate --> DbRev /// DB -- commit proposal --> DB /// ``` -#[derive(Debug, Default)] -pub struct Db { - latest_cache: Mutex>>, -} impl From for api::Error { fn from(value: DbError) -> Self { @@ -44,44 +29,3 @@ impl From for api::Error { } } } - -#[async_trait] -impl api::Db for Db -where - T: api::DbView, - T: Send + Sync, - T: Default, - T: 'static, -{ - type Historical = T; - - type Proposal = propose::Proposal; - - async fn revision(&self, _hash: api::HashKey) -> Result, api::Error> { - todo!() - } - - async fn root_hash(&self) -> Result { - todo!() - } - - async fn propose( - &self, - data: Batch, - ) -> Result { - let mut dbview_latest_cache_guard = self.latest_cache.lock().await; - - let revision = match dbview_latest_cache_guard.deref_mut() { - Some(revision) => revision.clone(), - revision => { - let default_revision = Arc::new(T::default()); - *revision = Some(default_revision.clone()); - default_revision - } - }; - - let proposal = Self::Proposal::new(propose::ProposalBase::View(revision), data); - - Ok(proposal) - } -} diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index f3a01a53da61..cf08df42ba26 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -102,7 +102,7 @@ impl Proposal { #[async_trait] impl api::DbView for Proposal { async fn root_hash(&self) -> Result { - todo!() + todo!(); } async fn val(&self, key: K) -> Result>, api::Error> { @@ -125,7 +125,7 @@ impl api::DbView for Proposal { &self, _key: K, ) -> Result>>, api::Error> { - todo!() + todo!(); } async fn range_proof( diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index f75fd2902630..2aebcddc1c6d 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -208,6 +208,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { #[test] #[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] fn test_proof() -> Result<(), DataStoreError> { let set = generate_random_data(500); let mut items = Vec::from_iter(set.iter()); @@ -278,7 +279,7 @@ fn test_bad_proof() -> Result<(), DataStoreError> { let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let (keys, _): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - for (_, key) in keys.iter().enumerate() { + for key in keys.iter() { let mut proof = merkle.prove(key)?; assert!(!proof.0.is_empty()); @@ -324,6 +325,7 @@ fn test_empty_tree_proof() -> Result<(), DataStoreError> { #[test] // Tests normal range proof with both edge proofs as the existent proof. // The test cases are generated randomly. +#[allow(clippy::indexing_slicing)] fn test_range_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -359,6 +361,7 @@ fn test_range_proof() -> Result<(), ProofError> { #[test] // Tests a few cases which the proof is wrong. // The prover is expected to detect the error. +#[allow(clippy::indexing_slicing)] fn test_bad_range_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -436,6 +439,7 @@ fn test_bad_range_proof() -> Result<(), ProofError> { #[test] // Tests normal range proof with two non-existent proofs. // The test cases are generated randomly. +#[allow(clippy::indexing_slicing)] fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -504,6 +508,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { // Tests such scenarios: // - There exists a gap between the first element and the left edge proof // - There exists a gap between the last element and the right edge proof +#[allow(clippy::indexing_slicing)] fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -562,6 +567,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> #[test] // Tests the proof with only one element. The first edge proof can be existent one or // non-existent one. +#[allow(clippy::indexing_slicing)] fn test_one_element_range_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -649,6 +655,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { #[test] // Tests the range proof with all elements. // The edge proofs can be nil. +#[allow(clippy::indexing_slicing)] fn test_all_elements_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -704,6 +711,7 @@ fn test_all_elements_proof() -> Result<(), ProofError> { #[test] // Tests the range proof with "no" element. The first edge proof must // be a non-existent proof. +#[allow(clippy::indexing_slicing)] fn test_empty_range_proof() -> Result<(), ProofError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -711,7 +719,7 @@ fn test_empty_range_proof() -> Result<(), ProofError> { let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; let cases = [(items.len() - 1, false)]; - for (_, c) in cases.iter().enumerate() { + for c in cases.iter() { let first = increase_key(items[c.0].0); let proof = merkle.prove(first)?; assert!(!proof.0.is_empty()); @@ -735,6 +743,7 @@ fn test_empty_range_proof() -> Result<(), ProofError> { #[test] // Focuses on the small trie with embedded nodes. If the gapped // node is embedded in the trie, it should be detected too. +#[allow(clippy::indexing_slicing)] fn test_gapped_range_proof() -> Result<(), ProofError> { let mut items = Vec::new(); // Sorted entries @@ -773,6 +782,7 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { #[test] // Tests the element is not in the range covered by proofs. +#[allow(clippy::indexing_slicing)] fn test_same_side_proof() -> Result<(), DataStoreError> { let set = generate_random_data(4096); let mut items = Vec::from_iter(set.iter()); @@ -812,6 +822,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { } #[test] +#[allow(clippy::indexing_slicing)] // Tests the range starts from zero. fn test_single_side_range_proof() -> Result<(), ProofError> { for _ in 0..10 { @@ -845,6 +856,7 @@ fn test_single_side_range_proof() -> Result<(), ProofError> { } #[test] +#[allow(clippy::indexing_slicing)] // Tests the range ends with 0xffff...fff. fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { for _ in 0..10 { @@ -907,6 +919,7 @@ fn test_both_sides_range_proof() -> Result<(), ProofError> { } #[test] +#[allow(clippy::indexing_slicing)] // Tests normal range proof with both edge proofs // as the existent proof, but with an extra empty value included, which is a // noop technically, but practically should be rejected. @@ -942,6 +955,7 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { } #[test] +#[allow(clippy::indexing_slicing)] // Tests the range proof with all elements, // but with an extra empty value included, which is a noop technically, but // practically should be rejected. @@ -1013,6 +1027,7 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { } #[test] +#[allow(clippy::indexing_slicing)] // Tests a malicious proof, where the proof is more or less the // whole trie. This is to match corresponding test in geth. fn test_bloadted_range_proof() -> Result<(), ProofError> { @@ -1049,6 +1064,7 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { Ok(()) } +#[allow(clippy::indexing_slicing)] fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); for i in 0..100_u32 { @@ -1077,11 +1093,10 @@ fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { fn increase_key(key: &[u8; 32]) -> [u8; 32] { let mut new_key = *key; - for i in (0..key.len()).rev() { - if new_key[i] == 0xff { - new_key[i] = 0x00; - } else { - new_key[i] += 1; + for ch in new_key.iter_mut().rev() { + let overflow; + (*ch, overflow) = ch.overflowing_add(1); + if !overflow { break; } } @@ -1090,11 +1105,10 @@ fn increase_key(key: &[u8; 32]) -> [u8; 32] { fn decrease_key(key: &[u8; 32]) -> [u8; 32] { let mut new_key = *key; - for i in (0..key.len()).rev() { - if new_key[i] == 0x00 { - new_key[i] = 0xff; - } else { - new_key[i] -= 1; + for ch in new_key.iter_mut().rev() { + let overflow; + (*ch, overflow) = ch.overflowing_sub(1); + if !overflow { break; } } diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index d509bbfe99d6..05179716cac3 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -296,7 +296,7 @@ impl> WalFilePool { .ok_or(WalError::Other("EOF".to_string()))?; let slice = cast_slice::<_, Header>(&bytes); slice - .get(0) + .first() .copied() .ok_or(WalError::Other("short read".to_string())) } @@ -1006,7 +1006,7 @@ impl WalLoader { }; v.off += msize as u64; let header: &[WalRingBlob] = cast_slice(&header_raw); - let header = *header.get(0)?; + let header = *header.first()?; let payload; match header.rtype.try_into() { @@ -1115,7 +1115,7 @@ impl WalLoader { }; let ringid_start = (fid << file_nbit) + v.off; v.off += msize as u64; - let header: WalRingBlob = *cast_slice(&header_raw).get(0)?; + let header: WalRingBlob = *cast_slice(&header_raw).first()?; let rsize = header.rsize; match header.rtype.try_into() { Ok(WalRingType::Full) => { From 36a562c39fe27b005323045b7018f336958de701 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 2 Jan 2024 11:04:42 -0800 Subject: [PATCH 0422/1053] Fix upcoming clippy lint (#459) --- firewood/src/db.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index dba39b00ba25..ace4a75939dc 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -889,9 +889,7 @@ impl Db { .position(|trie_hash| &trie_hash == root_hash)); } - let Some(nback) = nback else { - return None; - }; + let nback = nback?; let rlen = revisions.inner.len(); if rlen < nback { From fab15d751e7b49c323da134e951c7f2d1504b50b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 2 Jan 2024 11:34:54 -0800 Subject: [PATCH 0423/1053] Delay block in place (#454) --- firewood/src/db.rs | 11 +++++------ firewood/src/db/proposal.rs | 2 +- firewood/src/storage/buffer.rs | 5 +++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ace4a75939dc..bf6bffb02ab6 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -281,7 +281,8 @@ pub struct DbRev { #[async_trait] impl + Send + Sync> api::DbView for DbRev { async fn root_hash(&self) -> Result { - block_in_place(|| self.merkle.root_hash(self.header.kv_root)) + self.merkle + .root_hash(self.header.kv_root) .map(|h| *h) .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } @@ -414,7 +415,7 @@ impl api::Db for Db { type Proposal = proposal::Proposal; async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { - let rev = block_in_place(|| self.get_revision(&TrieHash(root_hash))); + let rev = self.get_revision(&TrieHash(root_hash)); if let Some(rev) = rev { Ok(Arc::new(rev.rev)) } else { @@ -425,16 +426,14 @@ impl api::Db for Db { } async fn root_hash(&self) -> Result { - block_in_place(|| self.kv_root_hash()) - .map(|hash| hash.0) - .map_err(Into::into) + self.kv_root_hash().map(|hash| hash.0).map_err(Into::into) } async fn propose( &self, batch: api::Batch, ) -> Result { - block_in_place(|| self.new_proposal(batch)).map_err(Into::into) + self.new_proposal(batch).map_err(Into::into) } } diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 33c6f25a9696..a7c560aa010b 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -57,7 +57,7 @@ impl crate::v2::api::Proposal for Proposal { self: Arc, data: api::Batch, ) -> Result { - block_in_place(|| self.propose_sync(data)).map_err(Into::into) + self.propose_sync(data).map_err(Into::into) } } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index d691cfe53c7a..5a6e23323e62 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -20,6 +20,7 @@ use growthring::{ walerror::WalError, WalFileImpl, WalStoreImpl, }; +use tokio::task::block_in_place; use tokio::{ sync::{ mpsc, @@ -588,7 +589,7 @@ impl DiskBufferRequester { .map_err(StoreError::Send) .ok(); #[allow(clippy::unwrap_used)] - resp_rx.blocking_recv().unwrap() + block_in_place(move || resp_rx.blocking_recv().unwrap()) } /// Sends a batch of writes to the buffer. @@ -622,7 +623,7 @@ impl DiskBufferRequester { .send(BufferCmd::CollectAsh(nrecords, resp_tx)) .map_err(StoreError::Send) .ok(); - resp_rx.blocking_recv().map_err(StoreError::Receive) + block_in_place(|| resp_rx.blocking_recv().map_err(StoreError::Receive)) } /// Register a cached space to the buffer. From 113ff2fdd443509c5fef707717490e1941fe1f50 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:21:24 -0800 Subject: [PATCH 0424/1053] Rename the arguments name for better clarification in `insert` example (#463) --- firewood/examples/insert.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 5e5276d6ff67..18d987fa48ef 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -23,9 +23,9 @@ struct Args { #[arg(short, long, default_value = "32", value_parser = string_to_range)] datalen: RangeInclusive, #[arg(short, long, default_value_t = 1)] - batch_keys: usize, + batch_size: usize, #[arg(short, long, default_value_t = 100)] - inserts: usize, + number_of_batches: usize, #[arg(short, long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, #[arg(short, long)] @@ -54,7 +54,7 @@ async fn main() -> Result<(), Box> { .await .expect("db initiation should succeed"); - let keys = args.batch_keys; + let keys = args.batch_size; let start = Instant::now(); let mut rng = if let Some(seed) = args.seed { @@ -63,7 +63,7 @@ async fn main() -> Result<(), Box> { rand::rngs::StdRng::from_entropy() }; - for _ in 0..args.inserts { + for _ in 0..args.number_of_batches { let keylen = rng.gen_range(args.keylen.clone()); let datalen = rng.gen_range(args.datalen.clone()); let batch: Batch, Vec> = (0..keys) @@ -93,7 +93,7 @@ async fn main() -> Result<(), Box> { let duration = start.elapsed(); println!( "Generated and inserted {} batches of size {keys} in {duration:?}", - args.inserts + args.number_of_batches ); Ok(()) From 61c1236507d2bce1a4ec5b8d4c002bd741c5692c Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 3 Jan 2024 18:13:54 -0500 Subject: [PATCH 0425/1053] Consolidate attributes (#466) --- firewood/src/merkle.rs | 4 +--- firewood/src/merkle/node/branch.rs | 3 +-- firewood/src/merkle/proof.rs | 12 ++---------- firewood/src/merkle/stream.rs | 3 +-- firewood/src/merkle/trie_hash.rs | 3 +-- firewood/src/nibbles.rs | 2 -- firewood/src/shale/cached.rs | 9 +++------ firewood/src/shale/compact.rs | 3 +-- firewood/src/storage/buffer.rs | 12 ++++-------- firewood/src/storage/mod.rs | 12 ++---------- firewood/tests/merkle.rs | 3 +-- grpc-testtool/src/lib.rs | 9 +++------ 12 files changed, 20 insertions(+), 55 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 8c3b0b5d63b1..8868560ed0ed 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1464,13 +1464,11 @@ pub const fn to_nibble_array(x: u8) -> [u8; 2] { pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { debug_assert_eq!(nibbles.len() & 1, 0); #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use crate::merkle::node::PlainCodec; diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 145ddee0980f..19b15116f4fc 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -182,8 +182,7 @@ impl BranchNode { #[allow(clippy::indexing_slicing)] if let Some(v) = &self.children_encoded[i] { if v.len() == TRIE_HASH_LEN { - #[allow(clippy::indexing_slicing)] - #[allow(clippy::unwrap_used)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] (list[i] = Encoded::Data( bincode::DefaultOptions::new().serialize(v).unwrap(), )); diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index ad79a329749d..a312ef0d805a 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -158,7 +158,6 @@ impl + Send> Proof { // Ensure the received batch is monotonic increasing and contains no deletions #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] if !keys.windows(2).all(|w| w[0].as_ref() < w[1].as_ref()) { return Err(ProofError::NonMonotonicIncreaseRange); } @@ -631,10 +630,8 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe // If either the node pointed by left proof or right proof is nil, // stop here and the forkpoint is the fullnode. #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] let left_node = n.chd()[left_chunks[index] as usize]; #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] let right_node = n.chd()[right_chunks[index] as usize]; match (left_node.as_ref(), right_node.as_ref()) { @@ -704,17 +701,13 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe match &u_ref.inner() { NodeType::Branch(n) => { - #[allow(clippy::indexing_slicing)] #[allow(clippy::indexing_slicing)] let left_node = n.chd()[left_chunks[index] as usize]; #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] let right_node = n.chd()[right_chunks[index] as usize]; // unset all internal nodes calculated encoded value in the forkpoint - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] for i in left_chunks[index] + 1..right_chunks[index] { u_ref .write(|u| { @@ -967,8 +960,7 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe (true, Ordering::Less) | (false, Ordering::Greater) ); - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] if should_unset_entire_branch { p_ref .write(|p| { diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 9aca12ad4e9f..c4faa0cf8b69 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -413,8 +413,7 @@ impl> IntoBytes for T {} use super::tests::create_test_merkle; #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use crate::nibbles::Nibbles; diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 3dff124d45b5..2f89105ead93 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -28,8 +28,7 @@ impl Storable for TrieHash { offset: addr, size: U64_TRIE_HASH_LEN, })?; - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) } diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs index 2649a771ed1c..0f2138c39faf 100644 --- a/firewood/src/nibbles.rs +++ b/firewood/src/nibbles.rs @@ -48,12 +48,10 @@ impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROE _ if index < LEADING_ZEROES => &NIBBLES[0], _ if (index - LEADING_ZEROES) % 2 == 0 => { - #[allow(clippy::indexing_slicing)] #[allow(clippy::indexing_slicing)] &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] >> 4) as usize] } #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] _ => &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] & 0xf) as usize], } } diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 18ec3ecbf995..94882e3bb97e 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -94,8 +94,7 @@ impl CachedView for PlainMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } @@ -195,15 +194,13 @@ impl CachedView for DynamicMemView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 8fc1a02c3a46..619c5ad0302b 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -650,8 +650,7 @@ impl ShaleStore for Comp } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use sha3::Digest; diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 5a6e23323e62..c4ada62342db 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -226,8 +226,7 @@ fn schedule_write( let offset = page_key.1 << PAGE_SIZE_NBIT; let fid = offset >> p.file_nbit; let fmask = (1 << p.file_nbit) - 1; - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::unwrap_used, clippy::indexing_slicing)] let file = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -302,8 +301,7 @@ async fn init_wal( for (undo, redo) in ash.iter() { let offset = undo.offset; let file_pools = file_pools.borrow(); - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::unwrap_used, clippy::indexing_slicing)] let file_pool = file_pools[space_id as usize].as_ref().unwrap(); let file_nbit = file_pool.get_file_nbit(); let file_mask = (1 << file_nbit) - 1; @@ -395,8 +393,7 @@ async fn run_wal_queue( false } Vacant(e) => { - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::unwrap_used, clippy::indexing_slicing)] let file_nbit = file_pools.borrow()[page_key.0 as usize] .as_ref() .unwrap() @@ -636,8 +633,7 @@ impl DiskBufferRequester { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { use sha3::Digest; use std::path::{Path, PathBuf}; diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 24898b9bec9e..7709208f317b 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -150,8 +150,7 @@ impl StoreDelta { widx.sort_by_key(|i| writes[*i].offset); let mut witer = widx.into_iter(); - #[allow(clippy::unwrap_used)] - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] let w0 = &writes[witer.next().unwrap()]; let mut head = w0.offset >> PAGE_SIZE_NBIT; let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; @@ -213,7 +212,6 @@ impl StoreDelta { if !data.is_empty() { l += 1; #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] deltas[l].data_mut()[..data.len()].copy_from_slice(data); } } @@ -274,11 +272,8 @@ impl MemStoreR for StoreRev { #[allow(clippy::indexing_slicing)] data.extend(base_space.get_slice(start, delta[l].offset() - start)?); #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[..p_off as usize]); } else { - #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]); }; @@ -298,8 +293,6 @@ impl MemStoreR for StoreRev { } #[allow(clippy::indexing_slicing)] if end < delta[l].offset() + PAGE_SIZE { - #[allow(clippy::indexing_slicing)] - #[allow(clippy::indexing_slicing)] #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); break; @@ -655,8 +648,7 @@ impl MemStoreR for ZeroStore { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::unwrap_used, clippy::indexing_slicing)] mod test { use super::*; #[test] diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 2aebcddc1c6d..62b1ad00665f 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -207,8 +207,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { } #[test] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[allow(clippy::unwrap_used, clippy::indexing_slicing)] fn test_proof() -> Result<(), DataStoreError> { let set = generate_random_data(500); let mut items = Vec::from_iter(set.iter()); diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 85ba10e8f00f..3819ac80a61f 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -2,20 +2,17 @@ // See the file LICENSE.md for licensing terms. pub mod sync { - #![allow(clippy::unwrap_used)] - #![allow(clippy::missing_const_for_fn)] + #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] tonic::include_proto!("sync"); } pub mod rpcdb { - #![allow(clippy::unwrap_used)] - #![allow(clippy::missing_const_for_fn)] + #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] tonic::include_proto!("rpcdb"); } pub mod process_server { - #![allow(clippy::unwrap_used)] - #![allow(clippy::missing_const_for_fn)] + #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] tonic::include_proto!("process"); } From 0e8ab96098a23624c4fa2e64e10892b2712a041b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 3 Jan 2024 15:20:40 -0800 Subject: [PATCH 0426/1053] Add Dan Laine as a code owner (#467) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 975ac914b87b..b34e810c2fcf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @xinifinity @rkuris @richardpringle +* @xinifinity @rkuris @richardpringle @danlaine From 6e8c00d01ad4174c3d3add1e08dfc00e0d59ead1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 4 Jan 2024 09:55:26 -0800 Subject: [PATCH 0427/1053] Remove unnecessary wrapper around Revision (#455) --- firewood/src/db.rs | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index bf6bffb02ab6..d6e78962044a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -417,7 +417,7 @@ impl api::Db for Db { async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { let rev = self.get_revision(&TrieHash(root_hash)); if let Some(rev) = rev { - Ok(Arc::new(rev.rev)) + Ok(Arc::new(rev)) } else { Err(api::Error::HashNotFound { provided: root_hash, @@ -851,7 +851,7 @@ impl Db { /// /// If no revision with matching root hash found, returns None. // #[measure([HitCount])] - pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { + pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { let mut revisions = self.revisions.lock(); let inner_lock = self.inner.read(); @@ -938,16 +938,14 @@ impl Db { let header_refs = (db_header_ref, merkle_payload_header_ref); #[allow(clippy::unwrap_used)] - Revision { - rev: Db::new_revision( - header_refs, - (space.merkle.meta.clone(), space.merkle.payload.clone()), - self.payload_regn_nbit, - 0, - &self.cfg.rev, - ) - .unwrap(), - } + Db::new_revision( + header_refs, + (space.merkle.meta.clone(), space.merkle.payload.clone()), + self.payload_regn_nbit, + 0, + &self.cfg.rev, + ) + .unwrap() .into() } @@ -964,15 +962,3 @@ impl Db { self.metrics.clone() } } - -/// Lock protected handle to a readable version of the DB. -pub struct Revision { - rev: DbRev, -} - -impl std::ops::Deref for Revision { - type Target = DbRev; - fn deref(&self) -> &DbRev { - &self.rev - } -} From 7286cb101a59f6281856f341da877486419803a6 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:25:17 -0800 Subject: [PATCH 0428/1053] Idiomatic conversion between `MutStore` and `SharedStore` (#468) --- firewood/src/db.rs | 10 +++++----- firewood/src/db/proposal.rs | 2 +- firewood/src/merkle.rs | 6 +++--- firewood/src/shale/compact.rs | 24 ++++++++++++------------ firewood/src/storage/mod.rs | 34 ++++++++++++++++++---------------- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d6e78962044a..93c5506adfd0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -382,11 +382,11 @@ impl + Send + Sync> DbRev { } } -impl DbRev { - pub fn into_shared(self) -> DbRev { +impl From> for DbRev { + fn from(value: DbRev) -> Self { DbRev { - header: self.header, - merkle: self.merkle.into(), + header: value.header, + merkle: value.merkle.into(), } } } @@ -470,7 +470,7 @@ impl Db { } /// Open a database. - pub fn new_internal>(db_path: P, cfg: DbConfig) -> Result { + fn new_internal>(db_path: P, cfg: DbConfig) -> Result { let open_options = if cfg.truncate { file::Options::Truncate } else { diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index a7c560aa010b..60d94cc31928 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -215,7 +215,7 @@ impl Proposal { revisions.inner.pop_back(); } - revisions.base_revision = Arc::new(rev.into_shared()); + revisions.base_revision = Arc::new(rev.into()); // update the rolling window of root hashes revisions.root_hashes.push_front(kv_root_hash.clone()); diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 8868560ed0ed..7e3800c8e1ad 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -72,9 +72,9 @@ pub struct Merkle { phantom: PhantomData, } -impl Merkle { - pub fn into(self) -> Merkle { - let store = self.store.into_shared(); +impl From> for Merkle { + fn from(value: Merkle) -> Self { + let store = value.store.into(); Merkle { store: Box::new(store), phantom: PhantomData, diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 619c5ad0302b..bac868e6e882 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -235,14 +235,14 @@ struct CompactSpaceInner { regn_nbit: u64, } -impl CompactSpaceInner { - pub fn into_shared(self) -> CompactSpaceInner { +impl From> for CompactSpaceInner { + fn from(value: CompactSpaceInner) -> CompactSpaceInner { CompactSpaceInner { - meta_space: self.meta_space.into_shared(), - compact_space: self.compact_space.into_shared(), - header: self.header, - alloc_max_walk: self.alloc_max_walk, - regn_nbit: self.regn_nbit, + meta_space: value.meta_space.into(), + compact_space: value.compact_space.into(), + header: value.header, + alloc_max_walk: value.alloc_max_walk, + regn_nbit: value.regn_nbit, } } } @@ -565,13 +565,13 @@ impl CompactSpace { } } -impl CompactSpace { +impl From>> for CompactSpace { #[allow(clippy::unwrap_used)] - pub fn into_shared(self) -> CompactSpace { - let inner = self.inner.into_inner().unwrap(); + fn from(value: Box>) -> Self { + let inner = value.inner.into_inner().unwrap(); CompactSpace { - inner: RwLock::new(inner.into_shared()), - obj_cache: self.obj_cache, + inner: RwLock::new(inner.into()), + obj_cache: value.obj_cache, } } } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 7709208f317b..cd6648e9cc06 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -359,6 +359,24 @@ impl CachedStore for StoreRevShared { } } +impl From for StoreRevShared { + fn from(value: StoreRevMut) -> Self { + let mut pages = Vec::new(); + let deltas = std::mem::take(&mut *value.deltas.write()); + for (pid, page) in deltas.pages.into_iter() { + pages.push(DeltaPage(pid, page)); + } + pages.sort_by_key(|p| p.0); + let delta = StoreDelta(pages); + + let rev = Arc::new(StoreRev { + base_space: RwLock::new(value.base_space), + delta, + }); + StoreRevShared(rev) + } +} + #[derive(Debug)] struct StoreRef { data: Vec, @@ -430,22 +448,6 @@ impl StoreRevMut { } } - pub fn into_shared(self) -> StoreRevShared { - let mut pages = Vec::new(); - let deltas = std::mem::take(&mut *self.deltas.write()); - for (pid, page) in deltas.pages.into_iter() { - pages.push(DeltaPage(pid, page)); - } - pages.sort_by_key(|p| p.0); - let delta = StoreDelta(pages); - - let rev = Arc::new(StoreRev { - base_space: RwLock::new(self.base_space), - delta, - }); - StoreRevShared(rev) - } - fn get_page_mut<'a>( &self, deltas: &'a mut StoreRevMutDelta, From 5c9ced2e364bc0d4ecf95be087a40345bf06e3aa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 5 Jan 2024 07:39:34 -0800 Subject: [PATCH 0429/1053] Change new_store method to use self (#470) --- firewood/src/db.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 93c5506adfd0..e4dcfa1c9c36 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -665,10 +665,9 @@ impl Db { /// Create a new mutable store and an alterable revision of the DB on top. fn new_store( + &self, cached_space: &Universe>, reset_store_headers: bool, - payload_regn_nbit: u64, - cfg: &DbConfig, ) -> Result<(Universe, DbRev), DbError> { let mut offset = Db::PARAM_SIZE as usize; let db_header: DiskAddress = DiskAddress::from(offset); @@ -713,9 +712,9 @@ impl Db { let mut rev: DbRev> = Db::new_revision( header_refs, (store.merkle.meta.clone(), store.merkle.payload.clone()), - payload_regn_nbit, - cfg.payload_max_walk, - &cfg.rev, + self.payload_regn_nbit, + self.cfg.payload_max_walk, + &self.cfg.rev, )?; #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); @@ -800,12 +799,7 @@ impl Db { ) -> Result { let mut inner = self.inner.write(); let reset_store_headers = inner.reset_store_headers; - let (store, mut rev) = Db::new_store( - &inner.cached_space, - reset_store_headers, - self.payload_regn_nbit, - &self.cfg, - )?; + let (store, mut rev) = self.new_store(&inner.cached_space, reset_store_headers)?; // Flip the reset flag after resetting the store headers. if reset_store_headers { From aa39d2fb7c5459dc754555cd8194c53322e1a727 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 8 Jan 2024 06:38:05 -0800 Subject: [PATCH 0430/1053] Make ObjRef Debug (#475) --- firewood/src/merkle.rs | 2 +- firewood/src/shale/compact.rs | 4 +++- firewood/src/shale/mod.rs | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7e3800c8e1ad..50457e7c0cb4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -177,7 +177,7 @@ impl + Send + Sync, T> Merkle { .map(|node| node.as_ptr()) } - pub fn get_store(&self) -> &dyn ShaleStore { + pub fn get_store(&self) -> &S { self.store.as_ref() } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index bac868e6e882..35d3f8b7ec1a 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -576,7 +576,9 @@ impl From>> for CompactSpace ShaleStore for CompactSpace { +impl ShaleStore + for CompactSpace +{ fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; #[allow(clippy::unwrap_used)] diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index a070c411506b..97f914e22809 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -165,12 +165,13 @@ impl Deref for Obj { } /// User handle that offers read & write access to the stored [ShaleStore] item. +#[derive(Debug)] pub struct ObjRef<'a, T: Storable> { inner: Option>, cache: &'a ObjCache, } -impl<'a, T: Storable> ObjRef<'a, T> { +impl<'a, T: Storable + Debug> ObjRef<'a, T> { const fn new(inner: Option>, cache: &'a ObjCache) -> Self { Self { inner, cache } } @@ -191,7 +192,7 @@ impl<'a, T: Storable> ObjRef<'a, T> { } } -impl<'a, T: Storable> Deref for ObjRef<'a, T> { +impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { // TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix @@ -219,7 +220,7 @@ impl<'a, T: Storable> Drop for ObjRef<'a, T> { /// A persistent item storage backed by linear logical space. New items can be created and old /// items could be retrieved or dropped. -pub trait ShaleStore { +pub trait ShaleStore { /// Dereference [DiskAddress] to a unique handle that allows direct access to the item in memory. fn get_item(&'_ self, ptr: DiskAddress) -> Result, ShaleError>; /// Allocate a new item. From 98144343fadd9f2e5314f7de40e7bc27bb42183b Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 8 Jan 2024 10:55:52 -0500 Subject: [PATCH 0431/1053] Make range proof generation error on invalid bounds (#465) --- firewood/src/merkle.rs | 29 +++++++++++++++++++++++++++++ firewood/src/v2/api.rs | 7 +++++++ 2 files changed, 36 insertions(+) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 50457e7c0cb4..2469ed15d63f 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1293,6 +1293,15 @@ impl + Send + Sync, T> Merkle { last_key: Option, limit: Option, ) -> Result, Vec>>, api::Error> { + if let (Some(k1), Some(k2)) = (&first_key, &last_key) { + if k1.as_ref() > k2.as_ref() { + return Err(api::Error::InvalidRange { + first_key: k1.as_ref().to_vec(), + last_key: k2.as_ref().to_vec(), + }); + } + } + // limit of 0 is always an empty RangeProof if limit == Some(0) { return Ok(None); @@ -1700,6 +1709,26 @@ mod tests { .is_none()); } + #[tokio::test] + async fn range_proof_invalid_bounds() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let start_key = &[0x01]; + let end_key = &[0x00]; + + match merkle + .range_proof::<&[u8]>(root, Some(start_key), Some(end_key), Some(1)) + .await + { + Err(api::Error::InvalidRange { + first_key, + last_key, + }) if first_key == start_key && last_key == end_key => (), + Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), + _ => panic!("expected InvalidRange error"), + } + } + #[tokio::test] async fn full_range_proof() { let mut merkle = create_test_merkle(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index f0787d7855c9..866ccadd68dc 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -64,6 +64,13 @@ pub enum Error { #[error("Incorrect root hash for commit: {provided:?} != {current:?}")] IncorrectRootHash { provided: HashKey, current: HashKey }, + /// Invalid range + #[error("Invalid range: {first_key:?} > {last_key:?}")] + InvalidRange { + first_key: Vec, + last_key: Vec, + }, + #[error("IO error: {0}")] IO(std::io::Error), From fbe03e9ea1e31b76fdb43fcd747237fd39579594 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:23:34 +0000 Subject: [PATCH 0432/1053] build(deps): update serial_test requirement from 2.0.0 to 3.0.0 (#477) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- firewood/Cargo.toml | 1 - fwdctl/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index f3a9042f7a6b..78a90252ecf1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -43,7 +43,6 @@ rand = "0.8.5" triehash = "0.8.4" assert_cmd = "2.0.7" predicates = "3.0.1" -serial_test = "2.0.0" clap = { version = "4.3.1", features = ['derive'] } test-case = "3.1.0" pprof = { version = "0.13.0", features = ["flamegraph"] } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index e91ccf12023f..fc4d8e762e58 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -15,7 +15,7 @@ futures-util = "0.3.29" [dev-dependencies] assert_cmd = "2.0.7" predicates = "3.0.1" -serial_test = "2.0.0" +serial_test = "3.0.0" [lints.rust] unsafe_code = "deny" From 3cff03324dbbd489da7cf3294da6098ab625f2c5 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:44:10 -0800 Subject: [PATCH 0433/1053] Persist `is_encoded_longer_than_hash_len` property of a Node (#474) --- firewood/src/db.rs | 4 +++ firewood/src/db/proposal.rs | 18 ++++++------ firewood/src/merkle/node.rs | 57 ++++++++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e4dcfa1c9c36..57d3d90107a0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -824,6 +824,9 @@ impl Db { } } })?; + + // Calculated the root hash before flushing so it can be persisted. + let root_hash = rev.kv_root_hash()?; #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); @@ -835,6 +838,7 @@ impl Db { rev, store, committed: Arc::new(Mutex::new(false)), + root_hash, parent, }) } diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 60d94cc31928..e54a9039eca3 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -34,6 +34,7 @@ pub struct Proposal { pub(super) rev: DbRev, pub(super) store: Universe, pub(super) committed: Arc>, + pub(super) root_hash: TrieHash, pub(super) parent: ProposalBase, } @@ -106,6 +107,9 @@ impl Proposal { } } })?; + + // Calculated the root hash before flushing so it can be persisted. + let hash = rev.kv_root_hash()?; #[allow(clippy::unwrap_used)] rev.flush_dirty().unwrap(); @@ -118,6 +122,7 @@ impl Proposal { rev, store, committed: Arc::new(Mutex::new(false)), + root_hash: hash, parent, }) } @@ -132,6 +137,7 @@ impl Proposal { rev, store, committed, + root_hash: hash, parent, } = self; @@ -152,10 +158,7 @@ impl Proposal { committed_root_hash.expect("committed_root_hash should not be none"); match &parent { ProposalBase::Proposal(p) => { - let parent_root_hash = p.rev.kv_root_hash().ok(); - let parent_root_hash = - parent_root_hash.expect("parent_root_hash should not be none"); - if parent_root_hash != committed_root_hash { + if p.root_hash != committed_root_hash { return Err(DbError::InvalidProposal); } } @@ -169,9 +172,6 @@ impl Proposal { } }; - let kv_root_hash = rev.kv_root_hash().ok(); - let kv_root_hash = kv_root_hash.expect("kv_root_hash should not be none"); - // clear the staging layer and apply changes to the CachedSpace let (merkle_payload_redo, merkle_payload_wal) = store.merkle.payload.delta(); let (merkle_meta_redo, merkle_meta_wal) = store.merkle.meta.delta(); @@ -218,14 +218,14 @@ impl Proposal { revisions.base_revision = Arc::new(rev.into()); // update the rolling window of root hashes - revisions.root_hashes.push_front(kv_root_hash.clone()); + revisions.root_hashes.push_front(hash.clone()); if revisions.root_hashes.len() > max_revisions { revisions .root_hashes .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); } - rev_inner.root_hash_staging.write(0, &kv_root_hash.0); + rev_inner.root_hash_staging.write(0, &hash.0); let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); // schedule writes to the disk diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index f7a48fb5304f..0255454318ca 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -157,6 +157,7 @@ impl NodeType { pub struct Node { pub(super) root_hash: OnceLock, encoded: OnceLock>, + is_encoded_longer_than_hash_len: OnceLock, // lazy_dirty is an atomicbool, but only writers ever set it // Therefore, we can always use Relaxed ordering. It's atomic // just to ensure Sync + Send. @@ -173,6 +174,7 @@ impl PartialEq for Node { let Node { root_hash, encoded, + is_encoded_longer_than_hash_len: _, lazy_dirty: _, inner, } = self; @@ -187,6 +189,7 @@ impl Clone for Node { fn clone(&self) -> Self { Self { root_hash: self.root_hash.clone(), + is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), encoded: self.encoded.clone(), lazy_dirty: AtomicBool::new(self.is_dirty()), inner: self.inner.clone(), @@ -199,6 +202,7 @@ impl From for Node { let mut s = Self { root_hash: OnceLock::new(), encoded: OnceLock::new(), + is_encoded_longer_than_hash_len: OnceLock::new(), inner, lazy_dirty: AtomicBool::new(false), }; @@ -222,6 +226,7 @@ impl Node { Self { root_hash: OnceLock::new(), encoded: OnceLock::new(), + is_encoded_longer_than_hash_len: OnceLock::new(), inner: NodeType::Branch( BranchNode { // path: vec![].into(), @@ -249,7 +254,9 @@ impl Node { } fn is_encoded_longer_than_hash_len>(&self, store: &S) -> bool { - self.get_encoded(store).len() >= TRIE_HASH_LEN + *self + .is_encoded_longer_than_hash_len + .get_or_init(|| self.get_encoded(store).len() >= TRIE_HASH_LEN) } pub(super) fn rehash(&mut self) { @@ -273,13 +280,21 @@ impl Node { &mut self.inner } - pub(super) fn new_from_hash(root_hash: Option, inner: NodeType) -> Self { + pub(super) fn new_from_hash( + root_hash: Option, + is_encoded_longer_than_hash_len: Option, + inner: NodeType, + ) -> Self { Self { root_hash: match root_hash { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, encoded: OnceLock::new(), + is_encoded_longer_than_hash_len: match is_encoded_longer_than_hash_len { + Some(v) => OnceLock::from(v), + None => OnceLock::new(), + }, inner, lazy_dirty: AtomicBool::new(false), } @@ -298,7 +313,6 @@ impl Node { struct Meta { root_hash: [u8; TRIE_HASH_LEN], attrs: NodeAttributes, - is_encoded_longer_than_hash_len: Option, } impl Meta { @@ -360,24 +374,46 @@ impl Storable for Node { None }; + let is_encoded_longer_than_hash_len = + if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { + Some(false) + } else if attrs.contains(NodeAttributes::LONG) { + Some(true) + } else { + None + }; + + let meta_raw = mem + .get_view(offset, 1_u64) + .ok_or(ShaleError::InvalidCacheView { + offset, + size: 1_u64, + })?; + + offset += 1; + #[allow(clippy::indexing_slicing)] - match meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()? { + match meta_raw.as_deref()[0].try_into()? { NodeTypeId::Branch => { let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); - Ok(Self::new_from_hash(root_hash, inner)) + Ok(Self::new_from_hash( + root_hash, + is_encoded_longer_than_hash_len, + inner, + )) } NodeTypeId::Extension => { let inner = NodeType::Extension(ExtNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, inner); + let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); Ok(node) } NodeTypeId::Leaf => { let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, inner); + let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); Ok(node) } @@ -386,6 +422,7 @@ impl Storable for Node { fn serialized_len(&self) -> u64 { Meta::SIZE as u64 + + 1 + match &self.inner { NodeType::Branch(n) => { // TODO: add path @@ -416,6 +453,12 @@ impl Storable for Node { } else { NodeAttributes::IS_ENCODED_BIG_VALID }); + } else if let Some(b) = self.is_encoded_longer_than_hash_len.get() { + attrs.insert(if *b { + NodeAttributes::LONG + } else { + NodeAttributes::IS_ENCODED_BIG_VALID + }); } #[allow(clippy::unwrap_used)] From 4f1fb19e9d8b58802043ae5c1625b22aed219683 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 8 Jan 2024 19:30:25 -0500 Subject: [PATCH 0434/1053] Add type-id to node::Meta struct for deserializing (#480) --- firewood/src/merkle/node.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 0255454318ca..bef9f999d0f5 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -313,6 +313,7 @@ impl Node { struct Meta { root_hash: [u8; TRIE_HASH_LEN], attrs: NodeAttributes, + type_id: NodeTypeId, } impl Meta { @@ -374,6 +375,9 @@ impl Storable for Node { None }; + #[allow(clippy::indexing_slicing)] + let type_id: NodeTypeId = meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()?; + let is_encoded_longer_than_hash_len = if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { Some(false) @@ -383,17 +387,7 @@ impl Storable for Node { None }; - let meta_raw = mem - .get_view(offset, 1_u64) - .ok_or(ShaleError::InvalidCacheView { - offset, - size: 1_u64, - })?; - - offset += 1; - - #[allow(clippy::indexing_slicing)] - match meta_raw.as_deref()[0].try_into()? { + match type_id { NodeTypeId::Branch => { let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); From f679a3f3c3947de1f3e71cdb61ee79e206d52a9f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 9 Jan 2024 10:35:58 -0500 Subject: [PATCH 0435/1053] Use nibbles iterator in proof code (#460) --- firewood/src/merkle/proof.rs | 355 +++++++++++----------------------- firewood/src/merkle_util.rs | 2 +- firewood/src/shale/compact.rs | 2 +- 3 files changed, 119 insertions(+), 240 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index a312ef0d805a..6774cf641812 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -3,7 +3,6 @@ use std::cmp::Ordering; use std::collections::HashMap; -use std::ops::Deref; use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; use crate::v2::api::HashKey; @@ -19,7 +18,7 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; -use super::{BinarySerde, TRIE_HASH_LEN}; +use super::{BinarySerde, ObjRef, TRIE_HASH_LEN}; #[derive(Debug, Error)] pub enum ProofError { @@ -57,6 +56,8 @@ pub enum ProofError { Shale(ShaleError), #[error("invalid root hash")] InvalidRootHash, + #[error("failed writing the node")] + WriteError, } impl From for ProofError { @@ -258,261 +259,150 @@ impl + Send> Proof { /// necessary nodes will be resolved and leave the remaining as hashnode. /// /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path, S: ShaleStore + Send + Sync, T: BinarySerde>( + fn proof_to_path, S: ShaleStore + Send + Sync, T: BinarySerde>( &self, - key: KV, + key: K, root_hash: [u8; 32], merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, ) -> Result>, ProofError> { // Start with the sentinel root - let root = merkle_setup.get_root(); + let sentinel = merkle_setup.get_sentinel_address(); let merkle = merkle_setup.get_merkle_mut(); - let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; + let mut parent_node_ref = merkle + .get_node(sentinel) + .map_err(|_| ProofError::NoSuchNode)?; - let mut chunks = Vec::new(); - chunks.extend(key.as_ref().iter().copied().flat_map(to_nibble_array)); + let mut key_nibbles = Nibbles::<0>::new(key.as_ref()).into_iter(); - let mut cur_key: &[u8] = &chunks; - let mut cur_hash = root_hash; + let mut child_hash = root_hash; let proofs_map = &self.0; - let mut key_index = 0; - let mut branch_index = 0; - - loop { - let cur_proof = proofs_map - .get(&cur_hash) - .ok_or(ProofError::ProofNodeMissing)?; - // TODO(Hao): (Optimization) If a node is already decode we don't need to decode again. - let (mut chd_ptr, sub_proof, size) = - self.decode_node(merkle, cur_key, cur_proof.as_ref(), false)?; + let mut child_index = 0; + let sub_proof = loop { // Link the child to the parent based on the node type. - match &u_ref.inner() { + // if a child is already linked, use it instead + let child_node = match &parent_node_ref.inner() { #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) => match n.chd()[branch_index] { + NodeType::Branch(n) => match n.chd()[child_index] { // If the child already resolved, then use the existing node. - Some(node) => { - chd_ptr = node; - } + Some(node) => merkle.get_node(node)?, None => { + let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; + // insert the leaf to the empty slot - #[allow(clippy::unwrap_used)] - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); + parent_node_ref + .write(|node| { #[allow(clippy::indexing_slicing)] - (uu.chd_mut()[branch_index] = Some(chd_ptr)); + let node = node + .inner_mut() + .as_branch_mut() + .expect("parent_node_ref is a branch"); + node.chd_mut()[child_index] = Some(child_node.as_ptr()); }) - .unwrap(); + .map_err(|_| ProofError::WriteError)?; + + child_node } }, - NodeType::Extension(_) => { - // If the child already resolved, then use the existing node. - #[allow(clippy::unwrap_used)] - let node = u_ref.inner().as_extension().unwrap().chd(); - - #[allow(clippy::unwrap_used)] - if node.is_null() { - u_ref - .write(|u| { - let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = chd_ptr; - }) - .unwrap(); - } else { - chd_ptr = node; - } + NodeType::Extension(n) if n.chd().is_null() => { + let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; + + parent_node_ref + .write(|node| { + let node = node + .inner_mut() + .as_extension_mut() + .expect("parent_node_ref is an extension"); + *node.chd_mut() = child_node.as_ptr(); + }) + .map_err(|_| ProofError::WriteError)?; + + child_node } + NodeType::Extension(n) => merkle.get_node(n.chd())?, + // We should not hit a leaf node as a parent. _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; - u_ref = merkle.get_node(chd_ptr)?; - - // If the new parent is a branch node, record the index to correctly link the next child to it. - if u_ref.inner().as_branch().is_some() { - #[allow(clippy::indexing_slicing)] - (branch_index = chunks[key_index] as usize); - } - - key_index += size; - - match sub_proof { - // The trie doesn't contain the key. It's possible - // the proof is a non-existing proof, but at least - // we can prove all resolved nodes are correct, it's - // enough for us to prove range. - None => { - return allow_non_existent_node - .then_some(None) - .ok_or(ProofError::NodeNotInTrie); + // find the encoded subproof of the child if the partial-path and nibbles match + let encoded_sub_proof = match child_node.inner() { + NodeType::Leaf(n) => { + break n + .path + .iter() + .copied() + .eq(key_nibbles) // all nibbles have to match + .then(|| n.data().to_vec()); } - Some(p) => { - // Return when reaching the end of the key. - if key_index == chunks.len() { - #[allow(clippy::indexing_slicing)] - (cur_key = &chunks[key_index..]); - let mut data = None; - - // Decode the last subproof to get the value. - if let SubProof::Hash(p_hash) = p { - let proof = proofs_map - .get(&p_hash) - .ok_or(ProofError::ProofNodeMissing)?; - chd_ptr = self.decode_node(merkle, cur_key, proof.as_ref(), true)?.0; + NodeType::Extension(n) => { + let paths_match = n + .path + .iter() + .copied() + .all(|nibble| Some(nibble) == key_nibbles.next()); - // Link the child to the parent based on the node type. - match &u_ref.inner() { - // TODO: add path - #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) if n.chd()[branch_index].is_none() => { - // insert the leaf to the empty slot - #[allow(clippy::unwrap_used)] - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - #[allow(clippy::indexing_slicing)] - (uu.chd_mut()[branch_index] = Some(chd_ptr)); - }) - .unwrap(); - } - - // If the child already resolved, then use the existing node. - NodeType::Branch(_) => {} - - #[allow(clippy::unwrap_used)] - NodeType::Extension(_) - if u_ref.inner().as_extension().unwrap().chd().is_null() => - { - u_ref - .write(|u| { - let uu = u.inner_mut().as_extension_mut().unwrap(); - *uu.chd_mut() = chd_ptr; - }) - .unwrap(); - } - - // If the child already resolved, then use the existing node. - NodeType::Extension(_) => {} - - // We should not hit a leaf node as a parent. - _ => { - return Err(ProofError::InvalidNode( - MerkleError::ParentLeafBranch, - )) - } - }; - } - - drop(u_ref); - - let c_ref = merkle.get_node(chd_ptr)?; - - match &c_ref.inner() { - NodeType::Branch(n) => { - if let Some(v) = n.value() { - data = Some(v.deref().to_vec()); - } - } - NodeType::Leaf(n) => { - // Return the value on the node only when the key matches exactly - // (e.g. the length path of subproof node is 0). - match p { - SubProof::Data(_) => data = Some(n.data().deref().to_vec()), - SubProof::Hash(_) if n.path().len() == 0 => { - data = Some(n.data().deref().to_vec()) - } - _ => (), - } - } - _ => (), - } - - return Ok(data); + if !paths_match { + break None; } - // The trie doesn't contain the key. - let SubProof::Hash(hash) = p else { - return if allow_non_existent_node { - Ok(None) - } else { - Err(ProofError::NodeNotInTrie) - }; - }; - - cur_hash = hash; - #[allow(clippy::indexing_slicing)] - (cur_key = &chunks[key_index..]); + Some(n.chd_encoded().ok_or(ProofError::InvalidData)?) } - } - } - } - - /// Decode the value to generate the corresponding type of node, and locate the subproof. - /// - /// # Arguments - /// - /// * `end_node` - A boolean indicates whether this is the end node to decode, thus no `key` - /// to be present. - fn decode_node + Send + Sync, T: BinarySerde>( - &self, - merkle: &Merkle, - key: &[u8], - buf: &[u8], - end_node: bool, - ) -> Result<(DiskAddress, Option, usize), ProofError> { - let node = NodeType::decode(buf)?; - let new_node = merkle - .put_node(Node::from(node)) - .map_err(ProofError::InvalidNode)?; - let addr = new_node.as_ptr(); - - match new_node.inner() { - NodeType::Leaf(n) => { - let cur_key = n.path().0.as_ref(); - if !key.contains_other(cur_key) { - return Ok((addr, None, 0)); - } + NodeType::Branch(n) => { + if let Some(index) = key_nibbles.next() { + let subproof = n + .chd_encode() + .get(index as usize) + .and_then(|inner| inner.as_ref()) + .map(|data| &**data); - let subproof = SubProof::Data(n.data().to_vec()); + child_index = index as usize; - Ok((addr, subproof.into(), cur_key.len())) - } - NodeType::Extension(n) => { - let cur_key = n.path.0.as_ref(); + subproof + } else { + break n.value.as_ref().map(|data| data.to_vec()); + } + } + }; - if !key.contains_other(cur_key) { - return Ok((addr, None, 0)); + match encoded_sub_proof { + None => break None, + Some(encoded) => { + let hash = generate_subproof_hash(encoded)?; + child_hash = hash; } + } - let encoded = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); - let subproof = generate_subproof(encoded).map(Some)?; + parent_node_ref = child_node; + }; - Ok((addr, subproof, cur_key.len())) - } - // If the node is the last one to be decoded, then no subproof to be extracted. - NodeType::Branch(_) if end_node => Ok((addr, None, 1)), - NodeType::Branch(_) if key.is_empty() => Err(ProofError::NoSuchNode), - NodeType::Branch(n) => { - // Check if the subproof with the given key exist. - #[allow(clippy::indexing_slicing)] - let index = key[0] as usize; - #[allow(clippy::indexing_slicing)] - let Some(data) = &n.chd_encode()[index] else { - return Ok((addr, None, 1)); - }; - let subproof = generate_subproof(data.to_vec())?; - Ok((addr, Some(subproof), 1)) - } + match sub_proof { + Some(data) => Ok(Some(data)), + None if allow_non_existent_node => Ok(None), + None => Err(ProofError::NodeNotInTrie), } } } +fn decode_subproof<'a, S: ShaleStore, T, N: AsRef<[u8]>>( + merkle: &'a Merkle, + proofs_map: &HashMap<[u8; 32], N>, + child_hash: &[u8; 32], +) -> Result, ProofError> { + let child_proof = proofs_map + .get(child_hash) + .ok_or(ProofError::ProofNodeMissing)?; + let child_node = NodeType::decode(child_proof.as_ref())?; + merkle + .put_node(Node::from(child_node)) + .map_err(ProofError::InvalidNode) +} + fn locate_subproof( mut key_nibbles: NibblesIterator<'_, 0>, node: NodeType, @@ -545,7 +435,7 @@ fn locate_subproof( if does_not_match { return Ok((None, Nibbles::<0>::new(&[]).into_iter())); } - let data = n.chd_encoded().ok_or(ProofError::InvalidData)?.to_vec(); + let data = n.chd_encoded().ok_or(ProofError::InvalidData)?; let sub_proof = generate_subproof(data)?; Ok((sub_proof.into(), key_nibbles)) @@ -563,26 +453,25 @@ fn locate_subproof( #[allow(clippy::indexing_slicing)] let data = n.chd_encode()[index] .as_ref() - .ok_or(ProofError::InvalidData)? - .to_vec(); + .ok_or(ProofError::InvalidData)?; generate_subproof(data).map(|subproof| (Some(subproof), key_nibbles)) } } } -fn generate_subproof(encoded: Vec) -> Result { +fn generate_subproof_hash(encoded: &[u8]) -> Result<[u8; 32], ProofError> { match encoded.len() { 0..=31 => { - let sub_hash = sha3::Keccak256::digest(&encoded).into(); - Ok(SubProof::Hash(sub_hash)) + let sub_hash = sha3::Keccak256::digest(encoded).into(); + Ok(sub_hash) } 32 => { - let sub_hash: &[u8] = &encoded; - #[allow(clippy::unwrap_used)] - let sub_hash = sub_hash.try_into().unwrap(); + let sub_hash = encoded + .try_into() + .expect("slice length checked in match arm"); - Ok(SubProof::Hash(sub_hash)) + Ok(sub_hash) } len => Err(ProofError::DecodeError(Box::new( @@ -591,6 +480,10 @@ fn generate_subproof(encoded: Vec) -> Result { } } +fn generate_subproof(encoded: &[u8]) -> Result { + Ok(SubProof::Hash(generate_subproof_hash(encoded)?)) +} + // unset_internal removes all internal node references. // It should be called after a trie is constructed with two edge paths. Also // the given boundary keys must be the one used to construct the edge paths. @@ -615,7 +508,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe // Add the sentinel root let mut right_chunks = vec![0]; right_chunks.extend(right.as_ref().iter().copied().flat_map(to_nibble_array)); - let root = merkle_setup.get_root(); + let root = merkle_setup.get_sentinel_address(); let merkle = merkle_setup.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; let mut parent = DiskAddress::null(); @@ -1021,17 +914,3 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe } } } - -pub trait ContainsOtherExt { - fn contains_other(&self, other: Self) -> bool; -} - -impl ContainsOtherExt for &[T] -where - [T]: PartialEq<[T]>, -{ - #[allow(clippy::indexing_slicing)] - fn contains_other(&self, other: Self) -> bool { - self.len() >= other.len() && &self[..other.len()] == other - } -} diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index df2a1164a1e7..328ae03cdb60 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -64,7 +64,7 @@ impl + Send + Sync, T: BinarySerde> MerkleSetup { .map_err(|_err| DataStoreError::GetError) } - pub const fn get_root(&self) -> DiskAddress { + pub const fn get_sentinel_address(&self) -> DiskAddress { self.root } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 35d3f8b7ec1a..544f58103341 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -629,7 +629,7 @@ impl Sh if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { return Err(ShaleError::InvalidAddressLength { expected: DiskAddress::from(CompactSpaceHeader::MSIZE as usize), - found: ptr.0.unwrap().get() as u64, + found: ptr.0.map(|inner| inner.get()).unwrap_or_default() as u64, }); } From 4cd0b792636a675a48d2e28b71f9ace834369c19 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 9 Jan 2024 13:34:14 -0500 Subject: [PATCH 0436/1053] Rename ObjRef type-alias (#483) --- firewood/src/merkle.rs | 38 +++++++++++++++++------------------ firewood/src/merkle/proof.rs | 4 ++-- firewood/src/merkle/stream.rs | 20 +++++++++--------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 2469ed15d63f..8266659a245b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -28,8 +28,8 @@ pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; -type ObjRef<'a> = shale::ObjRef<'a, Node>; -type ParentRefs<'a> = Vec<(ObjRef<'a>, u8)>; +type NodeObjRef<'a> = shale::ObjRef<'a, Node>; +type ParentRefs<'a> = Vec<(NodeObjRef<'a>, u8)>; type ParentAddresses = Vec<(DiskAddress, u8)>; pub type MerkleWithEncoder = Merkle, Bincode>; @@ -83,11 +83,11 @@ impl From> for Merkle { } impl, T> Merkle { - pub fn get_node(&self, ptr: DiskAddress) -> Result { + pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) } - pub fn put_node(&self, node: Node) -> Result { + pub fn put_node(&self, node: Node) -> Result { self.store.put_item(node, 0).map_err(Into::into) } @@ -111,7 +111,7 @@ where // TODO: use `encode` / `decode` instead of `node.encode` / `node.decode` after extention node removal. #[allow(dead_code)] - fn encode(&self, node: &ObjRef) -> Result, MerkleError> { + fn encode(&self, node: &NodeObjRef) -> Result, MerkleError> { let encoded = match node.inner() { NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), NodeType::Branch(n) => { @@ -254,8 +254,8 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::too_many_arguments)] fn split( &self, - mut node_to_split: ObjRef, - parents: &mut [(ObjRef, u8)], + mut node_to_split: NodeObjRef, + parents: &mut [(NodeObjRef, u8)], insert_path: &[u8], n_path: Vec, n_value: Option, @@ -498,7 +498,7 @@ impl + Send + Sync, T> Merkle { key: K, mut val: Vec, root: DiskAddress, - ) -> Result<(impl Iterator, Vec), MerkleError> { + ) -> Result<(impl Iterator, Vec), MerkleError> { // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); let mut parents = Vec::new(); @@ -1088,17 +1088,17 @@ impl + Send + Sync, T> Merkle { fn get_node_by_key<'a, K: AsRef<[u8]>>( &'a self, - node_ref: ObjRef<'a>, + node_ref: NodeObjRef<'a>, key: K, - ) -> Result>, MerkleError> { + ) -> Result>, MerkleError> { self.get_node_by_key_with_callbacks(node_ref, key, |_, _| {}, |_, _| {}) } fn get_node_and_parents_by_key<'a, K: AsRef<[u8]>>( &'a self, - node_ref: ObjRef<'a>, + node_ref: NodeObjRef<'a>, key: K, - ) -> Result<(Option>, ParentRefs<'a>), MerkleError> { + ) -> Result<(Option>, ParentRefs<'a>), MerkleError> { let mut parents = Vec::new(); let node_ref = self.get_node_by_key_with_callbacks( node_ref, @@ -1114,9 +1114,9 @@ impl + Send + Sync, T> Merkle { fn get_node_and_parent_addresses_by_key<'a, K: AsRef<[u8]>>( &'a self, - node_ref: ObjRef<'a>, + node_ref: NodeObjRef<'a>, key: K, - ) -> Result<(Option>, ParentAddresses), MerkleError> { + ) -> Result<(Option>, ParentAddresses), MerkleError> { let mut parents = Vec::new(); let node_ref = self.get_node_by_key_with_callbacks( node_ref, @@ -1132,11 +1132,11 @@ impl + Send + Sync, T> Merkle { fn get_node_by_key_with_callbacks<'a, K: AsRef<[u8]>>( &'a self, - mut node_ref: ObjRef<'a>, + mut node_ref: NodeObjRef<'a>, key: K, mut start_loop_callback: impl FnMut(DiskAddress, u8), - mut end_loop_callback: impl FnMut(ObjRef<'a>, u8), - ) -> Result>, MerkleError> { + mut end_loop_callback: impl FnMut(NodeObjRef<'a>, u8), + ) -> Result>, MerkleError> { let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); loop { @@ -1377,7 +1377,7 @@ impl + Send + Sync, T> Merkle { } } -fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { +fn set_parent(new_chd: DiskAddress, parents: &mut [(NodeObjRef, u8)]) { #[allow(clippy::unwrap_used)] let (p_ref, idx) = parents.last_mut().unwrap(); #[allow(clippy::unwrap_used)] @@ -1394,7 +1394,7 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(ObjRef, u8)]) { .unwrap(); } -pub struct Ref<'a>(ObjRef<'a>); +pub struct Ref<'a>(NodeObjRef<'a>); pub struct RefMut<'a, S, T> { ptr: DiskAddress, diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 6774cf641812..c8a72368fefa 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -18,7 +18,7 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; -use super::{BinarySerde, ObjRef, TRIE_HASH_LEN}; +use super::{BinarySerde, NodeObjRef, TRIE_HASH_LEN}; #[derive(Debug, Error)] pub enum ProofError { @@ -393,7 +393,7 @@ fn decode_subproof<'a, S: ShaleStore, T, N: AsRef<[u8]>>( merkle: &'a Merkle, proofs_map: &HashMap<[u8; 32], N>, child_hash: &[u8; 32], -) -> Result, ProofError> { +) -> Result, ProofError> { let child_proof = proofs_map .get(child_hash) .ok_or(ProofError::ProofNodeMissing)?; diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index c4faa0cf8b69..8eea2f680f8c 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{node::Node, BranchNode, Merkle, NodeType, ObjRef}; +use super::{node::Node, BranchNode, Merkle, NodeObjRef, NodeType}; use crate::{ shale::{DiskAddress, ShaleStore}, v2::api, @@ -18,7 +18,7 @@ enum IteratorState<'a> { StartAtKey(Key), /// Continue iterating after the last node in the `visited_node_path` Iterating { - visited_node_path: Vec<(ObjRef<'a>, u8)>, + visited_node_path: Vec<(NodeObjRef<'a>, u8)>, }, } @@ -160,8 +160,8 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' } enum NodeRef<'a> { - New(ObjRef<'a>), - Visited(ObjRef<'a>), + New(NodeObjRef<'a>), + Visited(NodeObjRef<'a>), } #[derive(Debug)] @@ -178,7 +178,7 @@ impl<'a> NodeRef<'a> { } } - fn into_node(self) -> ObjRef<'a> { + fn into_node(self) -> NodeObjRef<'a> { match self { Self::New(node) => node, Self::Visited(node) => node, @@ -188,7 +188,7 @@ impl<'a> NodeRef<'a> { fn find_next_result<'a, S: ShaleStore, T>( merkle: &'a Merkle, - visited_path: &mut Vec<(ObjRef<'a>, u8)>, + visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, ) -> Result, super::MerkleError> { let next = find_next_node_with_data(merkle, visited_path)?.map(|(next_node, value)| { let partial_path = match next_node.inner() { @@ -209,8 +209,8 @@ fn find_next_result<'a, S: ShaleStore, T>( fn find_next_node_with_data<'a, S: ShaleStore, T>( merkle: &'a Merkle, - visited_path: &mut Vec<(ObjRef<'a>, u8)>, -) -> Result, Vec)>, super::MerkleError> { + visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, +) -> Result, Vec)>, super::MerkleError> { use InnerNode::*; let Some((visited_parent, visited_pos)) = visited_path.pop() else { @@ -301,7 +301,7 @@ fn get_children_iter(branch: &BranchNode) -> impl Iterator( merkle: &'a Merkle, mut children: Iter, - parents: &mut Vec<(ObjRef<'a>, u8)>, + parents: &mut Vec<(NodeObjRef<'a>, u8)>, node: &mut NodeRef<'a>, pos: &mut u8, ) -> Result, super::MerkleError> @@ -328,7 +328,7 @@ where } /// create an iterator over the key-nibbles from all parents _excluding_ the sentinal node. -fn nibble_iter_from_parents<'a>(parents: &'a [(ObjRef, u8)]) -> impl Iterator + 'a { +fn nibble_iter_from_parents<'a>(parents: &'a [(NodeObjRef, u8)]) -> impl Iterator + 'a { parents .iter() .skip(1) // always skip the sentinal node From c8383e1ceab0399cb5437ec0e44c04256753670e Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 9 Jan 2024 13:41:30 -0500 Subject: [PATCH 0437/1053] Use `HashKey` alias instead of plain array (#484) --- firewood/src/merkle/proof.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index c8a72368fefa..bd892235eb5c 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -18,7 +18,7 @@ use crate::{ merkle_util::{new_merkle, DataStoreError, MerkleSetup}, }; -use super::{BinarySerde, NodeObjRef, TRIE_HASH_LEN}; +use super::{BinarySerde, NodeObjRef}; #[derive(Debug, Error)] pub enum ProofError { @@ -102,7 +102,7 @@ pub struct Proof(pub HashMap); #[derive(Debug)] enum SubProof { Data(Vec), - Hash([u8; TRIE_HASH_LEN]), + Hash(HashKey), } impl + Send> Proof { @@ -114,7 +114,7 @@ impl + Send> Proof { pub fn verify>( &self, key: K, - root_hash: [u8; 32], + root_hash: HashKey, ) -> Result>, ProofError> { let mut key_nibbles = Nibbles::<0>::new(key.as_ref()).into_iter(); @@ -147,7 +147,7 @@ impl + Send> Proof { pub fn verify_range_proof, V: AsRef<[u8]>>( &self, - root_hash: [u8; 32], + root_hash: HashKey, first_key: K, last_key: K, keys: Vec, @@ -262,7 +262,7 @@ impl + Send> Proof { fn proof_to_path, S: ShaleStore + Send + Sync, T: BinarySerde>( &self, key: K, - root_hash: [u8; 32], + root_hash: HashKey, merkle_setup: &mut MerkleSetup, allow_non_existent_node: bool, ) -> Result>, ProofError> { @@ -391,8 +391,8 @@ impl + Send> Proof { fn decode_subproof<'a, S: ShaleStore, T, N: AsRef<[u8]>>( merkle: &'a Merkle, - proofs_map: &HashMap<[u8; 32], N>, - child_hash: &[u8; 32], + proofs_map: &HashMap, + child_hash: &HashKey, ) -> Result, ProofError> { let child_proof = proofs_map .get(child_hash) @@ -459,7 +459,7 @@ fn locate_subproof( } } -fn generate_subproof_hash(encoded: &[u8]) -> Result<[u8; 32], ProofError> { +fn generate_subproof_hash(encoded: &[u8]) -> Result { match encoded.len() { 0..=31 => { let sub_hash = sha3::Keccak256::digest(encoded).into(); From 7548cdd5e6df84468ec38220171804c812a8451a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 9 Jan 2024 13:47:38 -0500 Subject: [PATCH 0438/1053] Only use an iterator instead of an index (#482) --- firewood/src/merkle/proof.rs | 57 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index bd892235eb5c..5278e1e0b6b6 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -273,38 +273,43 @@ impl + Send> Proof { .get_node(sentinel) .map_err(|_| ProofError::NoSuchNode)?; - let mut key_nibbles = Nibbles::<0>::new(key.as_ref()).into_iter(); + let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter().peekable(); let mut child_hash = root_hash; let proofs_map = &self.0; - let mut child_index = 0; let sub_proof = loop { // Link the child to the parent based on the node type. // if a child is already linked, use it instead let child_node = match &parent_node_ref.inner() { #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) => match n.chd()[child_index] { - // If the child already resolved, then use the existing node. - Some(node) => merkle.get_node(node)?, - None => { - let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; - - // insert the leaf to the empty slot - parent_node_ref - .write(|node| { - #[allow(clippy::indexing_slicing)] - let node = node - .inner_mut() - .as_branch_mut() - .expect("parent_node_ref is a branch"); - node.chd_mut()[child_index] = Some(child_node.as_ptr()); - }) - .map_err(|_| ProofError::WriteError)?; - - child_node + NodeType::Branch(n) => { + let Some(child_index) = key_nibbles.next().map(usize::from) else { + break None; + }; + + match n.chd()[child_index] { + // If the child already resolved, then use the existing node. + Some(node) => merkle.get_node(node)?, + None => { + let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; + + // insert the leaf to the empty slot + parent_node_ref + .write(|node| { + #[allow(clippy::indexing_slicing)] + let node = node + .inner_mut() + .as_branch_mut() + .expect("parent_node_ref must be a branch"); + node.chd_mut()[child_index] = Some(child_node.as_ptr()); + }) + .map_err(|_| ProofError::WriteError)?; + + child_node + } } - }, + } NodeType::Extension(n) if n.chd().is_null() => { let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; @@ -314,7 +319,7 @@ impl + Send> Proof { let node = node .inner_mut() .as_extension_mut() - .expect("parent_node_ref is an extension"); + .expect("parent_node_ref must be an extension"); *node.chd_mut() = child_node.as_ptr(); }) .map_err(|_| ProofError::WriteError)?; @@ -354,15 +359,13 @@ impl + Send> Proof { } NodeType::Branch(n) => { - if let Some(index) = key_nibbles.next() { + if let Some(index) = key_nibbles.peek() { let subproof = n .chd_encode() - .get(index as usize) + .get(*index as usize) .and_then(|inner| inner.as_ref()) .map(|data| &**data); - child_index = index as usize; - subproof } else { break n.value.as_ref().map(|data| data.to_vec()); From 09fbaade73ec47def7a97114c02e2ce3d5e59c36 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 9 Jan 2024 14:50:36 -0500 Subject: [PATCH 0439/1053] Simplify proof-to-path errors (#485) --- firewood/src/merkle/proof.rs | 44 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 5278e1e0b6b6..9dc90f4de196 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use std::collections::HashMap; +use crate::shale::ObjWriteError; use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; use crate::v2::api::HashKey; use nix::errno::Errno; @@ -56,8 +57,8 @@ pub enum ProofError { Shale(ShaleError), #[error("invalid root hash")] InvalidRootHash, - #[error("failed writing the node")] - WriteError, + #[error("{0}")] + WriteError(#[from] ObjWriteError), } impl From for ProofError { @@ -295,16 +296,14 @@ impl + Send> Proof { let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; // insert the leaf to the empty slot - parent_node_ref - .write(|node| { - #[allow(clippy::indexing_slicing)] - let node = node - .inner_mut() - .as_branch_mut() - .expect("parent_node_ref must be a branch"); - node.chd_mut()[child_index] = Some(child_node.as_ptr()); - }) - .map_err(|_| ProofError::WriteError)?; + parent_node_ref.write(|node| { + #[allow(clippy::indexing_slicing)] + let node = node + .inner_mut() + .as_branch_mut() + .expect("parent_node_ref must be a branch"); + node.chd_mut()[child_index] = Some(child_node.as_ptr()); + })?; child_node } @@ -314,15 +313,13 @@ impl + Send> Proof { NodeType::Extension(n) if n.chd().is_null() => { let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; - parent_node_ref - .write(|node| { - let node = node - .inner_mut() - .as_extension_mut() - .expect("parent_node_ref must be an extension"); - *node.chd_mut() = child_node.as_ptr(); - }) - .map_err(|_| ProofError::WriteError)?; + parent_node_ref.write(|node| { + let node = node + .inner_mut() + .as_extension_mut() + .expect("parent_node_ref must be an extension"); + *node.chd_mut() = child_node.as_ptr(); + })?; child_node } @@ -401,9 +398,8 @@ fn decode_subproof<'a, S: ShaleStore, T, N: AsRef<[u8]>>( .get(child_hash) .ok_or(ProofError::ProofNodeMissing)?; let child_node = NodeType::decode(child_proof.as_ref())?; - merkle - .put_node(Node::from(child_node)) - .map_err(ProofError::InvalidNode) + let node = merkle.put_node(Node::from(child_node))?; + Ok(node) } fn locate_subproof( From c7a092afd865aede3f75400e3ed5ffabbcc70534 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:26:33 -0800 Subject: [PATCH 0440/1053] Fix `serialized_len` in Node (#486) --- firewood/src/merkle/node.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index bef9f999d0f5..f41c02c9d011 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -416,7 +416,6 @@ impl Storable for Node { fn serialized_len(&self) -> u64 { Meta::SIZE as u64 - + 1 + match &self.inner { NodeType::Branch(n) => { // TODO: add path From 14f4562ad942f03604d1d0ff67969e4c8da95e7e Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:32:34 -0800 Subject: [PATCH 0441/1053] Clippy Warning fix with latest rust upgrade (#488) --- firewood/src/storage/mod.rs | 10 ++-------- growth-ring/tests/common/mod.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index cd6648e9cc06..3f621da4882e 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -629,15 +629,9 @@ impl CachedStore for StoreRevMut { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] /// A zero-filled in memory store which can serve as a plain base to overlay deltas on top. -pub struct ZeroStore(Arc<()>); - -impl Default for ZeroStore { - fn default() -> Self { - Self(Arc::new(())) - } -} +pub struct ZeroStore(()); impl MemStoreR for ZeroStore { fn get_slice(&self, _: u64, length: u64) -> Option> { diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index 0f9b2e0c0158..a80083950a55 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -614,7 +614,7 @@ impl PaintingSim { state: &mut WalStoreEmulState, canvas: &mut Canvas, wal: WalLoader, - ops: &Vec, + ops: &[PaintStrokes], ringid_map: &HashMap, ) -> bool { if ops.is_empty() { From 1394b2614a22b7fd8096762d10c9ac947b8ff285 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:03:42 -0800 Subject: [PATCH 0442/1053] Persist the encoded value of a Node if its length is less than the hash (#487) --- firewood/src/merkle/node.rs | 125 +++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index f41c02c9d011..0901906b2c5b 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -261,6 +261,7 @@ impl Node { pub(super) fn rehash(&mut self) { self.encoded = OnceLock::new(); + self.is_encoded_longer_than_hash_len = OnceLock::new(); self.root_hash = OnceLock::new(); } @@ -282,6 +283,7 @@ impl Node { pub(super) fn new_from_hash( root_hash: Option, + encoded: Option>, is_encoded_longer_than_hash_len: Option, inner: NodeType, ) -> Self { @@ -290,7 +292,10 @@ impl Node { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, - encoded: OnceLock::new(), + encoded: match encoded { + Some(e) => OnceLock::from(e), + None => OnceLock::new(), + }, is_encoded_longer_than_hash_len: match is_encoded_longer_than_hash_len { Some(v) => OnceLock::from(v), None => OnceLock::new(), @@ -309,10 +314,14 @@ impl Node { } } +const ENCODED_MAX_LEN: usize = TRIE_HASH_LEN; + #[repr(C)] struct Meta { root_hash: [u8; TRIE_HASH_LEN], attrs: NodeAttributes, + encoded_len: [u8; size_of::()], + encoded: [u8; ENCODED_MAX_LEN], type_id: NodeTypeId, } @@ -375,8 +384,27 @@ impl Storable for Node { None }; + let mut start_index = TRIE_HASH_LEN + 1; + let end_index = start_index + size_of::(); + #[allow(clippy::indexing_slicing)] + let encoded_len = u64::from_le_bytes( + meta_raw.as_deref()[start_index..end_index] + .try_into() + .expect("invalid slice"), + ); + + start_index = end_index; + let mut encoded: Option> = None; + if encoded_len > 0 { + #[allow(clippy::indexing_slicing)] + let value: Vec = + meta_raw.as_deref()[start_index..start_index + encoded_len as usize].into(); + encoded = Some(value); + } + + start_index += ENCODED_MAX_LEN; #[allow(clippy::indexing_slicing)] - let type_id: NodeTypeId = meta_raw.as_deref()[TRIE_HASH_LEN + 1].try_into()?; + let type_id: NodeTypeId = meta_raw.as_deref()[start_index].try_into()?; let is_encoded_longer_than_hash_len = if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { @@ -393,6 +421,7 @@ impl Storable for Node { Ok(Self::new_from_hash( root_hash, + encoded, is_encoded_longer_than_hash_len, inner, )) @@ -400,14 +429,16 @@ impl Storable for Node { NodeTypeId::Extension => { let inner = NodeType::Extension(ExtNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); + let node = + Self::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner); Ok(node) } NodeTypeId::Leaf => { let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); - let node = Self::new_from_hash(root_hash, is_encoded_longer_than_hash_len, inner); + let node = + Self::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner); Ok(node) } @@ -440,10 +471,14 @@ impl Storable for Node { } }; + let mut encoded_len: u64 = 0; + let mut encoded = None; if let Some(b) = self.encoded.get() { attrs.insert(if b.len() > TRIE_HASH_LEN { NodeAttributes::LONG } else { + encoded_len = b.len() as u64; + encoded = Some(b); NodeAttributes::IS_ENCODED_BIG_VALID }); } else if let Some(b) = self.is_encoded_longer_than_hash_len.get() { @@ -457,6 +492,15 @@ impl Storable for Node { #[allow(clippy::unwrap_used)] cur.write_all(&[attrs.bits()]).unwrap(); + cur.write_all(&encoded_len.to_le_bytes())?; + if let Some(encoded) = encoded { + cur.write_all(encoded)?; + let remaining_len = ENCODED_MAX_LEN - encoded_len as usize; + cur.write_all(&vec![0; remaining_len])?; + } else { + cur.write_all(&[0; ENCODED_MAX_LEN])?; + } + match &self.inner { NodeType::Branch(n) => { // TODO: add path @@ -815,11 +859,22 @@ pub(super) mod tests { Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) } - pub fn branch( + pub fn leaf_from_hash( + root_hash: Option, + encoded: Option>, + is_encoded_longer_than_hash_len: Option, + path: Vec, + data: Vec, + ) -> Node { + let inner = NodeType::Leaf(LeafNode::new(PartialPath(path), Data(data))); + Node::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner) + } + + fn new_branch( repeated_disk_address: usize, value: Option>, repeated_encoded_child: Option>, - ) -> Node { + ) -> BranchNode { let children: [Option; BranchNode::MAX_CHILDREN] = from_fn(|i| { if i < BranchNode::MAX_CHILDREN / 2 { DiskAddress::from(repeated_disk_address).into() @@ -840,12 +895,42 @@ pub(super) mod tests { }) .unwrap_or_default(); - Node::from_branch(BranchNode { + BranchNode { // path: vec![].into(), children, value: value.map(Data), children_encoded, - }) + } + } + + pub fn branch( + repeated_disk_address: usize, + value: Option>, + repeated_encoded_child: Option>, + ) -> Node { + Node::from_branch(new_branch( + repeated_disk_address, + value, + repeated_encoded_child, + )) + } + + pub fn branch_with_hash( + root_hash: Option, + encoded: Option>, + is_encoded_longer_than_hash_len: Option, + repeated_disk_address: usize, + value: Option>, + repeated_encoded_child: Option>, + ) -> Node { + Node::new_from_hash( + root_hash, + encoded, + is_encoded_longer_than_hash_len, + NodeType::Branch( + new_branch(repeated_disk_address, value, repeated_encoded_child).into(), + ), + ) } pub fn extension( @@ -860,11 +945,33 @@ pub(super) mod tests { })) } - #[test_case(leaf(vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf_node")] + pub fn extension_from_hash( + root_hash: Option, + encoded: Option>, + is_encoded_longer_than_hash_len: Option, + path: Vec, + child_address: DiskAddress, + child_encoded: Option>, + ) -> Node { + let inner = NodeType::Extension(ExtNode { + path: PartialPath(path), + child: child_address, + child_encoded, + }); + Node::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner) + } + + #[test_case(leaf(vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node")] + #[test_case(leaf_from_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node with encoded")] + #[test_case(leaf_from_hash(Some(TrieHash([0x01; 32])), None, Some(true), vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node with hash")] #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with child address")] #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::null(), vec![0x01, 0x02, 0x03].into()) ; "extension without child address")] + #[test_case(extension_from_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with encoded")] + #[test_case(extension_from_hash(Some(TrieHash([0x01; 32])), None, Some(true), vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with hash")] #[test_case(branch(0x0a, b"hello world".to_vec().into(), None); "branch with data")] #[test_case(branch(0x0a, None, vec![0x01, 0x02, 0x03].into()); "branch without data")] + #[test_case(branch_with_hash(Some(TrieHash([0x01; 32])), None, Some(true), 0x0a, b"hello world".to_vec().into(), None); "branch with hash value")] + #[test_case(branch_with_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), 0x0a, b"hello world".to_vec().into(), None); "branch with encoded")] fn test_encoding(node: Node) { let mut bytes = vec![0; node.serialized_len() as usize]; From 54f8e2b37c14a8210ec5d87a2c25e5df072f3524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:29:28 -0500 Subject: [PATCH 0443/1053] build(deps): update aquamarine requirement from 0.4.0 to 0.5.0 (#496) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 78a90252ecf1..f2b077b30961 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -16,7 +16,7 @@ homepage = "https://avalabs.org" readme = "../README.md" [dependencies] -aquamarine = "0.4.0" +aquamarine = "0.5.0" async-trait = "0.1.57" bytemuck = { version = "1.13.1", features = ["derive"] } enum-as-inner = "0.6.0" From 7973ccf219b0a401a880096fcb5e6b1dc461848d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 12 Jan 2024 12:07:59 -0600 Subject: [PATCH 0444/1053] PlainMem is only for testing (#493) --- firewood/benches/hashops.rs | 14 +++-- firewood/benches/shale-bench.rs | 4 +- firewood/src/merkle.rs | 6 +- firewood/src/shale/cached.rs | 100 +++----------------------------- firewood/src/shale/mod.rs | 2 + firewood/src/shale/plainmem.rs | 99 +++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 106 deletions(-) create mode 100644 firewood/src/shale/plainmem.rs diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 0bb4b50d3380..0809d59fed77 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -6,9 +6,9 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::{ db::{BatchOp, DbConfig}, - merkle::{MerkleWithEncoder, TrieHash, TRIE_HASH_LEN}, + merkle::{Bincode, Merkle, Node, TrieHash, TRIE_HASH_LEN}, shale::{ - cached::PlainMem, + cached::DynamicMem, compact::{CompactHeader, CompactSpace}, disk_address::DiskAddress, CachedStore, ObjCache, Storable, StoredView, @@ -20,6 +20,8 @@ use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path, sync::Arc}; +pub type MerkleWithEncoder = Merkle, Bincode>; + const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); // To enable flamegraph output @@ -65,7 +67,7 @@ impl Profiler for FlamegraphProfiler { fn bench_trie_hash(criterion: &mut Criterion) { let mut to = [1u8; TRIE_HASH_LEN]; - let mut store = PlainMem::new(TRIE_HASH_LEN as u64, 0u8); + let mut store = DynamicMem::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, &*ZERO_HASH); #[allow(clippy::unwrap_used)] @@ -94,7 +96,7 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = StoredView::ptr_to_obj( - &PlainMem::new(2 * CompactHeader::MSIZE, 9), + &DynamicMem::new(2 * CompactHeader::MSIZE, 9), merkle_payload_header, CompactHeader::MSIZE, ) @@ -102,8 +104,8 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let store = CompactSpace::new( - PlainMem::new(TEST_MEM_SIZE, 0), - PlainMem::new(TEST_MEM_SIZE, 1), + DynamicMem::new(TEST_MEM_SIZE, 0), + DynamicMem::new(TEST_MEM_SIZE, 1), merkle_payload_header_ref, ObjCache::new(1 << 20), 4096, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index b17160c99e85..a7649882d3a9 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -5,7 +5,7 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; use firewood::shale::{ - cached::{DynamicMem, PlainMem}, + cached::DynamicMem, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, CachedStore, Obj, StoredView, @@ -88,7 +88,7 @@ fn serialize(m: &T) { fn bench_cursors(c: &mut Criterion) { let mut group = c.benchmark_group("shale-bench"); group.bench_function("PlainMem", |b| { - let mem = PlainMem::new(BENCH_MEM_SIZE as u64, 0); + let mem = DynamicMem::new(BENCH_MEM_SIZE as u64, 0); get_view(b, mem) }); group.bench_function("DynamicMem", |b| { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 8266659a245b..7c9414ab7950 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,10 +2,7 @@ use crate::db::{MutStore, SharedStore}; // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; -use crate::shale::{ - self, cached::PlainMem, compact::CompactSpace, disk_address::DiskAddress, ObjWriteError, - ShaleError, ShaleStore, -}; +use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use sha3::Digest; @@ -31,7 +28,6 @@ pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type NodeObjRef<'a> = shale::ObjRef<'a, Node>; type ParentRefs<'a> = Vec<(NodeObjRef<'a>, u8)>; type ParentAddresses = Vec<(DiskAddress, u8)>; -pub type MerkleWithEncoder = Merkle, Bincode>; #[derive(Debug, Error)] pub enum MerkleError { diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 94882e3bb97e..0beeaa8dfbc9 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -3,105 +3,18 @@ use crate::shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; use std::{ - borrow::BorrowMut, fmt::Debug, ops::{Deref, DerefMut}, sync::{Arc, RwLock}, }; -/// Purely volatile, vector-based implementation for [CachedStore]. This is good for testing or trying -/// out stuff (persistent data structures) built on [ShaleStore](super::ShaleStore) in memory, without having to write -/// your own [CachedStore] implementation. -#[derive(Debug)] -pub struct PlainMem { - space: Arc>>, - id: SpaceId, -} - -impl PlainMem { - pub fn new(size: u64, id: SpaceId) -> Self { - // TODO: this could cause problems on a 32-bit system - let space = Arc::new(RwLock::new(vec![0; size as usize])); - Self { space, id } - } -} - -impl CachedStore for PlainMem { - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>> { - let length = length as usize; - #[allow(clippy::unwrap_used)] - if offset + length > self.space.read().unwrap().len() { - None - } else { - Some(Box::new(PlainMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - } - - fn get_shared(&self) -> Box> { - Box::new(PlainMemShared(Self { - space: self.space.clone(), - id: self.id, - })) - } - - fn write(&mut self, offset: usize, change: &[u8]) { - let length = change.len(); - #[allow(clippy::unwrap_used)] - let mut vect = self.space.deref().write().unwrap(); - #[allow(clippy::indexing_slicing)] - vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); - } - - fn id(&self) -> SpaceId { - self.id - } -} - -#[derive(Debug)] -struct PlainMemView { - offset: usize, - length: usize, - mem: PlainMem, -} - -struct PlainMemShared(PlainMem); - -impl DerefMut for PlainMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.borrow_mut() - } -} - -impl Deref for PlainMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl CachedView for PlainMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() - } -} +#[cfg(test)] +pub use crate::shale::plainmem::PlainMem; -// Purely volatile, dynamically allocated vector-based implementation for [CachedStore]. This is similar to -/// [PlainMem]. The only difference is, when [write] dynamically allocate more space if original space is -/// not enough. +// Purely volatile, dynamically allocated vector-based implementation for +// [CachedStore]. This is similar to PlainMem (in testing). The only +// difference is, when [write] dynamically allocate more space if original +// space is not enough. #[derive(Debug)] pub struct DynamicMem { space: Arc>>, @@ -203,6 +116,7 @@ impl CachedView for DynamicMemView { #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; + use crate::shale::plainmem::PlainMemShared; #[test] fn test_plain_mem() { diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 97f914e22809..29a933f21875 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -14,6 +14,8 @@ use thiserror::Error; pub mod cached; pub mod compact; pub mod disk_address; +#[cfg(test)] +pub mod plainmem; #[derive(Debug, Error)] #[non_exhaustive] diff --git a/firewood/src/shale/plainmem.rs b/firewood/src/shale/plainmem.rs new file mode 100644 index 000000000000..c6bdf2e6d145 --- /dev/null +++ b/firewood/src/shale/plainmem.rs @@ -0,0 +1,99 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::{ + borrow::BorrowMut, + ops::{Deref, DerefMut}, + sync::{Arc, RwLock}, +}; + +use super::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; + +/// in-memory vector-based implementation for [CachedStore] for testing +// built on [ShaleStore](super::ShaleStore) in memory, without having to write +/// your own [CachedStore] implementation. +#[derive(Debug)] +pub struct PlainMem { + space: Arc>>, + id: SpaceId, +} + +impl PlainMem { + pub fn new(size: u64, id: SpaceId) -> Self { + // TODO: this could cause problems on a 32-bit system + let space = Arc::new(RwLock::new(vec![0; size as usize])); + Self { space, id } + } +} +impl CachedStore for PlainMem { + fn get_view( + &self, + offset: usize, + length: u64, + ) -> Option>>> { + let length = length as usize; + #[allow(clippy::unwrap_used)] + if offset + length > self.space.read().unwrap().len() { + None + } else { + Some(Box::new(PlainMemView { + offset, + length, + mem: Self { + space: self.space.clone(), + id: self.id, + }, + })) + } + } + + fn get_shared(&self) -> Box> { + Box::new(PlainMemShared(Self { + space: self.space.clone(), + id: self.id, + })) + } + + fn write(&mut self, offset: usize, change: &[u8]) { + let length = change.len(); + #[allow(clippy::unwrap_used)] + let mut vect = self.space.deref().write().unwrap(); + #[allow(clippy::indexing_slicing)] + vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); + } + + fn id(&self) -> SpaceId { + self.id + } +} + +#[derive(Debug)] +struct PlainMemView { + offset: usize, + length: usize, + mem: PlainMem, +} + +pub struct PlainMemShared(pub PlainMem); + +impl DerefMut for PlainMemShared { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.borrow_mut() + } +} + +impl Deref for PlainMemShared { + type Target = dyn CachedStore; + fn deref(&self) -> &(dyn CachedStore + 'static) { + &self.0 + } +} + +impl CachedView for PlainMemView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] + self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() + } +} From d460e25b0a87e68c6c585613f37c75b34ab00452 Mon Sep 17 00:00:00 2001 From: xinifinity <113067541+xinifinity@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:55:26 -0800 Subject: [PATCH 0445/1053] Use fast crc32 crate instead in growth-ring (#494) --- growth-ring/Cargo.toml | 2 +- growth-ring/src/wal.rs | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 3985f40b5e71..3770f1d9bf89 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -9,7 +9,6 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -crc = "3.0.0" lru = "0.12.0" scan_fmt = "0.2.6" regex = "1.6.0" @@ -20,6 +19,7 @@ libc = "0.2.133" bytemuck = {version = "1.13.1", features = ["derive"]} thiserror = "1.0.40" tokio = { version = "1.28.1", features = ["fs", "io-util", "sync"] } +crc32fast = "1.3.2" [dev-dependencies] hex = "0.4.3" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 05179716cac3..ee3f4c601d61 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use bytemuck::{cast_slice, Pod, Zeroable}; +use crc32fast::Hasher; use futures::{ future::{self, FutureExt, TryFutureExt}, stream::StreamExt, @@ -578,7 +579,11 @@ impl> WalWriter { // the remaining rec fits in the block let payload = rec; blob.counter = self.state.counter; - blob.crc32 = CRC32.checksum(payload); + + let mut hasher = Hasher::new(); + hasher.update(payload); + blob.crc32 = hasher.finalize(); + blob.rsize = rsize; let (rs, rt) = if let Some(rs) = ring_start.take() { @@ -610,7 +615,11 @@ impl> WalWriter { #[allow(clippy::indexing_slicing)] let payload = &rec[..d as usize]; blob.counter = self.state.counter; - blob.crc32 = CRC32.checksum(payload); + + let mut hasher = Hasher::new(); + hasher.update(payload); + blob.crc32 = hasher.finalize(); + blob.rsize = d; blob.rtype = if ring_start.is_some() { @@ -918,7 +927,11 @@ impl WalLoader { } fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { - if checksum == CRC32.checksum(data) { + let mut hasher = Hasher::new(); + hasher.update(data); + let expected_checksum = hasher.finalize(); + + if checksum == expected_checksum { Ok(true) } else { match p { @@ -1339,8 +1352,6 @@ impl WalLoader { } } -pub const CRC32: crc::Crc = crc::Crc::::new(&crc::CRC_32_CKSUM); - #[cfg(test)] #[allow(clippy::unwrap_used)] mod test { From 89e740d7f1213ad14c42cf8b3e7a7398e81e59cd Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 12 Jan 2024 14:55:56 -0500 Subject: [PATCH 0446/1053] Simplify path advancement in proof verification (#497) --- firewood/src/merkle/proof.rs | 44 +++++++++--------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 9dc90f4de196..02281a5ea9f5 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -537,54 +537,32 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe index += 1; } + #[allow(clippy::indexing_slicing)] NodeType::Extension(n) => { // If either the key of left proof or right proof doesn't match with // shortnode, stop here and the forkpoint is the shortnode. - let cur_key = n.path.clone().into_inner(); - - fork_left = if left_chunks.len() - index < cur_key.len() { - #[allow(clippy::indexing_slicing)] - left_chunks[index..].cmp(&cur_key) - } else { - #[allow(clippy::indexing_slicing)] - left_chunks[index..index + cur_key.len()].cmp(&cur_key) - }; + let path = &*n.path; - fork_right = if right_chunks.len() - index < cur_key.len() { - #[allow(clippy::indexing_slicing)] - right_chunks[index..].cmp(&cur_key) - } else { - #[allow(clippy::indexing_slicing)] - right_chunks[index..index + cur_key.len()].cmp(&cur_key) - }; + [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] + .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) + .map(|key| key.cmp(path)); if !fork_left.is_eq() || !fork_right.is_eq() { break; } parent = u_ref.as_ptr(); + index += path.len(); u_ref = merkle.get_node(n.chd())?; - index += cur_key.len(); } + #[allow(clippy::indexing_slicing)] NodeType::Leaf(n) => { - let cur_key = n.path(); - - fork_left = if left_chunks.len() - index < cur_key.len() { - #[allow(clippy::indexing_slicing)] - left_chunks[index..].cmp(cur_key) - } else { - #[allow(clippy::indexing_slicing)] - left_chunks[index..index + cur_key.len()].cmp(cur_key) - }; + let path = &*n.path; - fork_right = if right_chunks.len() - index < cur_key.len() { - #[allow(clippy::indexing_slicing)] - right_chunks[index..].cmp(cur_key) - } else { - #[allow(clippy::indexing_slicing)] - right_chunks[index..index + cur_key.len()].cmp(cur_key) - }; + [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] + .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) + .map(|key| key.cmp(path)); break; } From eb416aa3ade85fce6903aaacfad2c18aca36e67d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 15 Jan 2024 14:11:35 -0600 Subject: [PATCH 0447/1053] Logging in firewood, initial version (#489) --- README.md | 6 ++++++ firewood/Cargo.toml | 5 +++++ firewood/src/db.rs | 5 +++++ firewood/src/lib.rs | 1 + firewood/src/logger.rs | 26 ++++++++++++++++++++++++++ firewood/src/merkle/node.rs | 8 ++++++-- firewood/src/shale/compact.rs | 4 ++++ 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 firewood/src/logger.rs diff --git a/README.md b/README.md index 1ae15479f19d..408cc564fa9d 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,12 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. +## Logging + +If you want logging, enable the `logging` feature flag, and then set RUST\_LOG accordingly. +See the documentation for [env\_logger](https://docs.rs/env_logger/latest/env_logger/) for specifics. +We currently have very few logging statements, but this is useful for print-style debugging. + ## Release See the [release documentation](./RELEASE.md) for detailed information on how to release Firewood. diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index f2b077b30961..04f1bb343283 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,6 +35,11 @@ tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thre typed-builder = "0.18.0" bincode = "1.3.3" bitflags = "2.4.1" +env_logger = { version = "0.10.1", optional = true } +log = { version = "0.4.20", optional = true } + +[features] +logger = ["dep:env_logger", "log"] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 57d3d90107a0..013f24c8c23d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -465,6 +465,11 @@ impl Db { let _ = tokio::fs::remove_dir_all(db_path.as_ref()).await; } + #[cfg(feature = "logger")] + // initialize the logger, but ignore if this fails. This could fail because the calling + // library already initialized the logger or if you're opening a second database + let _ = env_logger::try_init(); + block_in_place(|| Db::new_internal(db_path, cfg.clone())) .map_err(|e| api::Error::InternalError(Box::new(e))) } diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 8052d2b54cf3..7fb1212b0ac9 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -195,4 +195,5 @@ pub mod nibbles; // TODO: shale should not be pub, but there are integration test dependencies :( pub mod shale; +pub mod logger; pub mod v2; diff --git a/firewood/src/logger.rs b/firewood/src/logger.rs new file mode 100644 index 000000000000..2f36a73699d8 --- /dev/null +++ b/firewood/src/logger.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// Supports making the logging operations a true runtime no-op +// Since we're a library, we can't really use the logging level +// static shortcut + +#[cfg(feature = "logger")] +pub use log::{debug, error, info, trace, warn}; + +#[cfg(not(feature = "logger"))] +pub use noop_logger::{debug, error, info, trace, warn}; + +#[cfg(not(feature = "logger"))] +mod noop_logger { + #[macro_export] + macro_rules! noop { + ($(target: $a:expr,)? $b:tt) => {}; + } + + pub use noop as debug; + pub use noop as error; + pub use noop as info; + pub use noop as trace; + pub use noop as warn; +} diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 0901906b2c5b..8e736e7a5301 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::logger::trace; use crate::{ merkle::from_nibbles, shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}, @@ -360,7 +361,7 @@ mod type_id { use type_id::NodeTypeId; impl Storable for Node { - fn deserialize(mut offset: usize, mem: &T) -> Result { + fn deserialize(offset: usize, mem: &T) -> Result { let meta_raw = mem.get_view(offset, Meta::SIZE as u64) .ok_or(ShaleError::InvalidCacheView { @@ -368,7 +369,9 @@ impl Storable for Node { size: Meta::SIZE as u64, })?; - offset += Meta::SIZE; + trace!("[{mem:p}] Deserializing node at {offset}"); + + let offset = offset + Meta::SIZE; #[allow(clippy::indexing_slicing)] let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); @@ -458,6 +461,7 @@ impl Storable for Node { } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { + trace!("[{self:p}] Serializing node"); let mut cur = Cursor::new(to); let mut attrs = match self.root_hash.get() { diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 544f58103341..9b0f4b148a9a 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -13,6 +13,8 @@ use std::io::{Cursor, Write}; use std::num::NonZeroUsize; use std::sync::RwLock; +use crate::logger::trace; + #[derive(Debug)] pub struct CompactHeader { payload_size: u64, @@ -584,6 +586,8 @@ impl Sh #[allow(clippy::unwrap_used)] let addr = self.inner.write().unwrap().alloc(size)?; + trace!("{self:p} put_item at {addr} size {size}"); + #[allow(clippy::unwrap_used)] let obj = { let inner = self.inner.read().unwrap(); From e99595e65a90910b6c245f6ea14181bccf4e3a45 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 15 Jan 2024 15:25:00 -0600 Subject: [PATCH 0448/1053] Add CONTRIBUTING.md draft (#490) --- .github/check-license-headers.yaml | 6 ++- CONTRIBUTING.md | 61 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 7f3076a2dd7a..90c67096bc93 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -15,7 +15,8 @@ "*/Cargo.toml", "libaio/**", "docs/**", - "CODEOWNERS" + "CODEOWNERS", + "CONTRIBUTING.md" ], "license": "./.github/license-header.txt" }, @@ -32,7 +33,8 @@ "*/Cargo.toml", "libaio/**", "docs/**", - "CODEOWNERS" + "CODEOWNERS", + "CONTRIBUTING.md" ], } ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..8fda78eb9371 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Welcome contributors! + +We are eager for contributions and happy you found yourself here. +Please read through this document to familiarize yourself with our +guidelines for contributing to firewood. + +# Table of Contents + + * [Quick Links](#Quick Links) + * [Testing](#Testing) + * [How to submit changes](#How to submit changes) + * [Where can I ask for help?](#Where can I ask for help) + +# [Quick Links] + + * [Setting up docker](README.docker.md) + * [Auto-generated documentation](https://ava-labs.github.io/firewood/firewood/) + * [Issue tracker](https://github.com/ava-labs/firewood/issues) + +# [Testing] + +After submitting a PR, we'll run all the tests and verify your code meets our submission guidelines. To ensure it's more likely to pass these checks, you should run the following commands locally: + + cargo fmt + cargo test + cargo clippy + cargo doc --no-deps + +Resolve any warnings or errors before making your PR. + +# [How to submit changes] + +To create a PR, fork firewood, and use github to create the PR. We typically prioritize reviews in the middle of our the next work day, +so you should expect a response during the week within 24 hours. + +# [How to report a bug] + +Please use the [issue tracker](https://github.com/ava-labs/firewood/issues) for reporting issues. + +# [First time fixes for contributors] + +The [issue tracker](https://github.com/ava-labs/firewood/issues) typically has some issues tagged for first-time contributors. If not, +please reach out. We hope you work on an easy task before tackling a harder one. + +# [How to request an enhancement] + +Just like bugs, please use the [issue tracker](https://github.com/ava-labs/firewood/issues) for requesting enhancements. Please tag the issue with the "enhancement" tag. + +# [Style Guide / Coding Conventions] + +We generally follow the same rules that `cargo fmt` and `cargo clippy` will report as warnings, with a few notable exceptions as documented in the associated Cargo.toml file. + +By default, we prohibit bare `unwrap` calls and index dereferencing, as there are usually better ways to write this code. In the case where you can't, please use `expect` with a message explaining why it would be a bug, which we currently allow. For more information on our motivation, please read this great article on unwrap: [Using unwrap() in Rust is Okay](https://blog.burntsushi.net/unwrap) by [Andrew Gallant](https://blog.burntsushi.net). + +# [Where can I ask for help]? + +Please reach out on X (formerly twitter) @rkuris for help or questions! + +# Thank you + +We'd like to extend a pre-emptive "thank you" for reading through this and submitting your first contribution! From bdb82d704b8a305023185b42221cc146ed6ebdf9 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 15 Jan 2024 18:25:05 -0500 Subject: [PATCH 0449/1053] Fix a stream edge case (#498) --- firewood/src/merkle/stream.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 8eea2f680f8c..773b1a51995b 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -102,24 +102,12 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' ) .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let mut visited_node_path = visited_node_path + let visited_node_path = visited_node_path .into_iter() .map(|(node, pos)| merkle.get_node(node).map(|node| (node, pos))) .collect::, _>>() .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let last_visited_node_not_branch = visited_node_path - .last() - .map(|(node, _)| { - matches!(node.inner(), NodeType::Leaf(_) | NodeType::Extension(_)) - }) - .unwrap_or_default(); - - // we only want branch in the visited node-path to start - if last_visited_node_not_branch { - visited_node_path.pop(); - } - (found_node, visited_node_path) }; @@ -143,6 +131,13 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' return Poll::Ready(next_result); } + let found_key = nibble_iter_from_parents(&visited_node_path); + let found_key = key_from_nibble_iter(found_key); + + if found_key > *key { + visited_node_path.pop(); + } + self.key_state = IteratorState::Iterating { visited_node_path }; self.poll_next(_cx) @@ -750,7 +745,19 @@ mod tests { } #[tokio::test] - async fn start_at_key_greater_than_all_others() { + async fn start_at_key_greater_than_all_others_leaf() { + let key = vec![0x00]; + let greater_key = vec![0xff]; + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + merkle.insert(key.clone(), key, root).unwrap(); + let stream = merkle.iter_from(root, greater_key.into_boxed_slice()); + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_greater_than_all_others_branch() { let greatest = 0xff; let children = (0..=0xf) .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff From 058d7ca1e3bea9aea297287187de8daf7b1f2329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 07:02:48 -0700 Subject: [PATCH 0450/1053] build(deps): update env_logger requirement from 0.10.1 to 0.11.0 (#502) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- fwdctl/Cargo.toml | 2 +- grpc-testtool/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 04f1bb343283..1ac94733fb41 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,7 +35,7 @@ tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thre typed-builder = "0.18.0" bincode = "1.3.3" bitflags = "2.4.1" -env_logger = { version = "0.10.1", optional = true } +env_logger = { version = "0.11.0", optional = true } log = { version = "0.4.20", optional = true } [features] diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index fc4d8e762e58..195651e62301 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" firewood = { version = "0.0.4", path = "../firewood" } clap = { version = "4.0.29", features = ["cargo", "derive"] } anyhow = "1.0.66" -env_logger = "0.10.0" +env_logger = "0.11.0" log = "0.4.17" tokio = { version = "1.33.0", features = ["full"] } futures-util = "0.3.29" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 5f2129716c60..5089989c0428 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -26,7 +26,7 @@ tracing-subscriber = { version = "0.3", features = ["tracing-log", "fmt", "env-f clap = { version = "4.4.11", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" -env_logger = "0.10.1" +env_logger = "0.11.0" chrono = "0.4.31" serde_json = "1.0.108" serde = { version = "1.0.193", features = ["derive"] } From ebabba916089a5762a60fc28269ae7550535167f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 29 Jan 2024 14:33:10 -0500 Subject: [PATCH 0451/1053] Fix clippy (#506) --- growth-ring/Cargo.toml | 2 +- growth-ring/tests/common/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 3770f1d9bf89..e9becab2ae19 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -24,7 +24,7 @@ crc32fast = "1.3.2" [dev-dependencies] hex = "0.4.3" rand = "0.8.5" -indexmap = "2.0.0" +indexmap = "2.2.1" tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } test-case = "3.1.0" diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index a80083950a55..e601d5b8565c 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -424,7 +424,7 @@ impl Canvas { #[allow(clippy::unwrap_used)] let (c, rid) = q.pop_front().unwrap(); if q.is_empty() { - self.queue.remove(&pos); + self.queue.swap_remove(&pos); } self.canvas[pos as usize] = c; if let Some(cnt) = self.waiting.get_mut(&rid) { From de9bd5746cc75d7c5f816994b1f0f6c60f9b156c Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 31 Jan 2024 13:31:39 -0500 Subject: [PATCH 0452/1053] update instructions for running tests (#510) --- grpc-testtool/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index c3f2ecdd8138..650f3c24e0ee 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -44,6 +44,6 @@ Then, run the test you want: export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" export CGO_ENABLED=1 export GOPROXY=https://proxy.golang.org -cd $BASE/merkledb-tester/tests -go test -timeout 5m github.com/ava-labs/merkledb-tester/... -v -count=1 +cd $BASE/merkledb-tester +./scripts/test.sh ``` From b2f14dce22821fb243c835671d0c0e3fe099bc04 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 1 Feb 2024 13:53:00 -0500 Subject: [PATCH 0453/1053] Use test-matrix for testing encoding for storage (#509) --- firewood/src/db/proposal.rs | 2 +- firewood/src/merkle.rs | 24 ++- firewood/src/merkle/node.rs | 268 ++++++++++++++++++------------- firewood/src/merkle/trie_hash.rs | 2 +- 4 files changed, 175 insertions(+), 121 deletions(-) diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index e54a9039eca3..ecf18ec1181f 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -218,7 +218,7 @@ impl Proposal { revisions.base_revision = Arc::new(rev.into()); // update the rolling window of root hashes - revisions.root_hashes.push_front(hash.clone()); + revisions.root_hashes.push_front(hash); if revisions.root_hashes.len() > max_revisions { revisions .root_hashes diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7c9414ab7950..f8b6fb0f12f1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -199,7 +199,7 @@ impl + Send + Sync, T> Merkle { .children[0]; Ok(if let Some(root) = root { let mut node = self.get_node(root)?; - let res = node.get_root_hash::(self.store.as_ref()).clone(); + let res = *node.get_root_hash::(self.store.as_ref()); #[allow(clippy::unwrap_used)] if node.is_dirty() { node.write(|_| {}).unwrap(); @@ -207,7 +207,7 @@ impl + Send + Sync, T> Merkle { } res } else { - Self::empty_root().clone() + *Self::empty_root() }) } @@ -1475,13 +1475,27 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use crate::merkle::node::PlainCodec; - use super::*; - use node::tests::{extension, leaf}; + use crate::merkle::node::PlainCodec; use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; use test_case::test_case; + fn extension( + path: Vec, + child_address: DiskAddress, + child_encoded: Option>, + ) -> Node { + Node::from(NodeType::Extension(ExtNode { + path: PartialPath(path), + child: child_address, + child_encoded, + })) + } + + fn leaf(path: Vec, data: Vec) -> Node { + Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) + } + #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] #[test_case(vec![0xc0, 0xff], &[0xc, 0x0, 0xf, 0xf])] fn to_nibbles(bytes: Vec, nibbles: &[u8]) { diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 8e736e7a5301..c3c77cc0ec5c 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -293,7 +293,7 @@ impl Node { Some(h) => OnceLock::from(h), None => OnceLock::new(), }, - encoded: match encoded { + encoded: match encoded.filter(|encoded| !encoded.is_empty()) { Some(e) => OnceLock::from(e), None => OnceLock::new(), }, @@ -852,142 +852,182 @@ impl BinarySerde for PlainCodec { } #[cfg(test)] -pub(super) mod tests { - use std::array::from_fn; - +mod tests { use super::*; use crate::shale::cached::PlainMem; - use test_case::test_case; + use std::iter::repeat; + use test_case::test_matrix; + + #[test_matrix( + [Nil, [0x00; TRIE_HASH_LEN]], + [Nil, vec![], vec![0x01], (0..TRIE_HASH_LEN as u8).collect::>(), (0..33).collect::>()], + [Nil, false, true] + )] + fn cached_node_data( + root_hash: impl Into>, + encoded: impl Into>>, + is_encoded_longer_than_hash_len: impl Into>, + ) { + let leaf = NodeType::Leaf(LeafNode::new(PartialPath(vec![1, 2, 3]), Data(vec![4, 5]))); + let branch = NodeType::Branch(Box::new(BranchNode { + // path: vec![].into(), + children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], + value: Some(Data(vec![1, 2, 3])), + children_encoded: std::array::from_fn(|_| Some(vec![1])), + })); + let extension = NodeType::Extension(ExtNode { + path: PartialPath(vec![1, 2, 3]), + child: DiskAddress::from(1), + child_encoded: Some(vec![1, 2, 3]), + }); + + let root_hash = root_hash.into().map(TrieHash); + let encoded = encoded.into(); + let is_encoded_longer_than_hash_len = is_encoded_longer_than_hash_len.into(); + + let node = Node::new_from_hash( + root_hash, + encoded.clone(), + is_encoded_longer_than_hash_len, + leaf, + ); + + check_node_encoding(node); + + let node = Node::new_from_hash( + root_hash, + encoded.clone(), + is_encoded_longer_than_hash_len, + branch, + ); + + check_node_encoding(node); + + let node = Node::new_from_hash( + root_hash, + encoded.clone(), + is_encoded_longer_than_hash_len, + extension, + ); - pub fn leaf(path: Vec, data: Vec) -> Node { - Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) + check_node_encoding(node); } - pub fn leaf_from_hash( - root_hash: Option, - encoded: Option>, - is_encoded_longer_than_hash_len: Option, - path: Vec, - data: Vec, - ) -> Node { - let inner = NodeType::Leaf(LeafNode::new(PartialPath(path), Data(data))); - Node::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner) - } - - fn new_branch( - repeated_disk_address: usize, - value: Option>, - repeated_encoded_child: Option>, - ) -> BranchNode { - let children: [Option; BranchNode::MAX_CHILDREN] = from_fn(|i| { - if i < BranchNode::MAX_CHILDREN / 2 { - DiskAddress::from(repeated_disk_address).into() - } else { + #[test_matrix( + (0..0, 0..15, 0..16, 0..31, 0..32), + [0..0, 0..16, 0..32] + )] + fn leaf_node>(path: Iter, data: Iter) { + let node = Node::from_leaf(LeafNode::new( + PartialPath(path.map(|x| x & 0xf).collect()), + Data(data.collect()), + )); + + check_node_encoding(node); + } + + #[test_matrix( + [vec![], vec![1,0,0,0,0,0,0,1], vec![1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], repeat(1).take(16).collect()], + [Nil, 0, 15], + [ + std::array::from_fn(|_| None), + std::array::from_fn(|_| Some(vec![1])), + [Some(vec![1]), None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(vec![1])], + std::array::from_fn(|_| Some(vec![1; 32])), + std::array::from_fn(|_| Some(vec![1; 33])) + ] + )] + fn branch_encoding( + children: Vec, + value: impl Into>, + children_encoded: [Option>; BranchNode::MAX_CHILDREN], + ) { + let mut children = children.into_iter().map(|x| { + if x == 0 { None + } else { + Some(DiskAddress::from(x)) } }); - let children_encoded = repeated_encoded_child - .map(|child| { - from_fn(|i| { - if i < BranchNode::MAX_CHILDREN / 2 { - child.clone().into() - } else { - None - } - }) - }) - .unwrap_or_default(); + let children = std::array::from_fn(|_| children.next().flatten()); + + let value = value + .into() + .map(|x| Data(std::iter::repeat(x).take(x as usize).collect())); - BranchNode { + let node = Node::from_branch(BranchNode { // path: vec![].into(), children, - value: value.map(Data), - children_encoded, - } - } - - pub fn branch( - repeated_disk_address: usize, - value: Option>, - repeated_encoded_child: Option>, - ) -> Node { - Node::from_branch(new_branch( - repeated_disk_address, value, - repeated_encoded_child, - )) - } + children_encoded, + }); - pub fn branch_with_hash( - root_hash: Option, - encoded: Option>, - is_encoded_longer_than_hash_len: Option, - repeated_disk_address: usize, - value: Option>, - repeated_encoded_child: Option>, - ) -> Node { - Node::new_from_hash( - root_hash, - encoded, - is_encoded_longer_than_hash_len, - NodeType::Branch( - new_branch(repeated_disk_address, value, repeated_encoded_child).into(), - ), - ) + check_node_encoding(node); } - pub fn extension( - path: Vec, - child_address: DiskAddress, - child_encoded: Option>, - ) -> Node { - Node::from(NodeType::Extension(ExtNode { - path: PartialPath(path), - child: child_address, - child_encoded, - })) + #[test_matrix( + [0..1, 0..16], + [DiskAddress::null(), DiskAddress::from(1)], + [Nil, 1, 32, 33] + )] + fn extension_encoding>( + path: Iter, + child: DiskAddress, + child_encoded: impl Into>, + ) { + let node = Node::from(NodeType::Extension(ExtNode { + path: PartialPath(path.map(|x| x & 0xf).collect()), + child, + child_encoded: child_encoded + .into() + .map(|x| repeat(x as u8).take(x).collect()), + })); + + check_node_encoding(node); } - pub fn extension_from_hash( - root_hash: Option, - encoded: Option>, - is_encoded_longer_than_hash_len: Option, - path: Vec, - child_address: DiskAddress, - child_encoded: Option>, - ) -> Node { - let inner = NodeType::Extension(ExtNode { - path: PartialPath(path), - child: child_address, - child_encoded, - }); - Node::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner) - } - - #[test_case(leaf(vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node")] - #[test_case(leaf_from_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node with encoded")] - #[test_case(leaf_from_hash(Some(TrieHash([0x01; 32])), None, Some(true), vec![0x01, 0x02, 0x03], vec![0x04, 0x05]); "leaf node with hash")] - #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with child address")] - #[test_case(extension(vec![0x01, 0x02, 0x03], DiskAddress::null(), vec![0x01, 0x02, 0x03].into()) ; "extension without child address")] - #[test_case(extension_from_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with encoded")] - #[test_case(extension_from_hash(Some(TrieHash([0x01; 32])), None, Some(true), vec![0x01, 0x02, 0x03], DiskAddress::from(0x42), None); "extension with hash")] - #[test_case(branch(0x0a, b"hello world".to_vec().into(), None); "branch with data")] - #[test_case(branch(0x0a, None, vec![0x01, 0x02, 0x03].into()); "branch without data")] - #[test_case(branch_with_hash(Some(TrieHash([0x01; 32])), None, Some(true), 0x0a, b"hello world".to_vec().into(), None); "branch with hash value")] - #[test_case(branch_with_hash(None, Some(vec![0x01, 0x02, 0x03]), Some(false), 0x0a, b"hello world".to_vec().into(), None); "branch with encoded")] - fn test_encoding(node: Node) { - let mut bytes = vec![0; node.serialized_len() as usize]; + fn check_node_encoding(node: Node) { + let serialized_len = node.serialized_len(); - #[allow(clippy::unwrap_used)] - node.serialize(&mut bytes).unwrap(); + let mut bytes = vec![0; serialized_len as usize]; + node.serialize(&mut bytes).expect("node should serialize"); - let mut mem = PlainMem::new(node.serialized_len(), 0x00); + let mut mem = PlainMem::new(serialized_len, 0); mem.write(0, &bytes); - #[allow(clippy::unwrap_used)] - let hydrated_node = Node::deserialize(0, &mem).unwrap(); + let mut hydrated_node = Node::deserialize(0, &mem).expect("node should deserialize"); + + let encoded = node + .encoded + .get() + .filter(|encoded| encoded.len() >= TRIE_HASH_LEN); + + match encoded { + // long-encoded won't be serialized + Some(encoded) if hydrated_node.encoded.get().is_none() => { + hydrated_node.encoded = OnceLock::from(encoded.clone()); + } + _ => (), + } assert_eq!(node, hydrated_node); } + + struct Nil; + + macro_rules! impl_nil_for { + // match a comma separated list of types + ($($t:ty),* $(,)?) => { + $( + impl From for Option<$t> { + fn from(_val: Nil) -> Self { + None + } + } + )* + }; + } + + impl_nil_for!([u8; 32], Vec, usize, u8, bool); } diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 2f89105ead93..08195e249b0e 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -10,7 +10,7 @@ use std::{ pub const TRIE_HASH_LEN: usize = 32; const U64_TRIE_HASH_LEN: u64 = TRIE_HASH_LEN as u64; -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Clone, Copy)] pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); impl std::ops::Deref for TrieHash { From 5d76b5893360781527880c5b0ae736dcbbcbdf0f Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 1 Feb 2024 14:03:18 -0500 Subject: [PATCH 0454/1053] Simplify node-attribute flags for storage (#505) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/src/merkle/node.rs | 47 +++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index c3c77cc0ec5c..c8627db3ec31 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -214,9 +214,9 @@ impl From for Node { bitflags! { struct NodeAttributes: u8 { - const ROOT_HASH_VALID = 0b001; - const IS_ENCODED_BIG_VALID = 0b010; - const LONG = 0b100; + const HAS_ROOT_HASH = 0b001; + const ENCODED_LENGTH_IS_KNOWN = 0b010; + const ENCODED_IS_LONG = 0b110; } } @@ -315,7 +315,7 @@ impl Node { } } -const ENCODED_MAX_LEN: usize = TRIE_HASH_LEN; +const ENCODED_MAX_LEN: usize = TRIE_HASH_LEN - 1; #[repr(C)] struct Meta { @@ -376,7 +376,7 @@ impl Storable for Node { #[allow(clippy::indexing_slicing)] let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); - let root_hash = if attrs.contains(NodeAttributes::ROOT_HASH_VALID) { + let root_hash = if attrs.contains(NodeAttributes::HAS_ROOT_HASH) { Some(TrieHash( #[allow(clippy::indexing_slicing)] meta_raw.as_deref()[..TRIE_HASH_LEN] @@ -410,10 +410,8 @@ impl Storable for Node { let type_id: NodeTypeId = meta_raw.as_deref()[start_index].try_into()?; let is_encoded_longer_than_hash_len = - if attrs.contains(NodeAttributes::IS_ENCODED_BIG_VALID) { - Some(false) - } else if attrs.contains(NodeAttributes::LONG) { - Some(true) + if attrs.contains(NodeAttributes::ENCODED_LENGTH_IS_KNOWN) { + attrs.contains(NodeAttributes::ENCODED_IS_LONG).into() } else { None }; @@ -467,7 +465,7 @@ impl Storable for Node { let mut attrs = match self.root_hash.get() { Some(h) => { cur.write_all(&h.0)?; - NodeAttributes::ROOT_HASH_VALID + NodeAttributes::HAS_ROOT_HASH } None => { cur.write_all(&[0; 32])?; @@ -475,31 +473,28 @@ impl Storable for Node { } }; - let mut encoded_len: u64 = 0; - let mut encoded = None; - if let Some(b) = self.encoded.get() { - attrs.insert(if b.len() > TRIE_HASH_LEN { - NodeAttributes::LONG - } else { - encoded_len = b.len() as u64; - encoded = Some(b); - NodeAttributes::IS_ENCODED_BIG_VALID - }); - } else if let Some(b) = self.is_encoded_longer_than_hash_len.get() { - attrs.insert(if *b { - NodeAttributes::LONG + let encoded = self + .encoded + .get() + .filter(|encoded| encoded.len() < TRIE_HASH_LEN); + + let encoded_len = encoded.map(Vec::len).unwrap_or(0); + + if let Some(&is_encoded_longer_than_hash_len) = self.is_encoded_longer_than_hash_len.get() { + attrs.insert(if is_encoded_longer_than_hash_len { + NodeAttributes::ENCODED_IS_LONG } else { - NodeAttributes::IS_ENCODED_BIG_VALID + NodeAttributes::ENCODED_LENGTH_IS_KNOWN }); } #[allow(clippy::unwrap_used)] cur.write_all(&[attrs.bits()]).unwrap(); + cur.write_all(&(encoded_len as u64).to_le_bytes())?; - cur.write_all(&encoded_len.to_le_bytes())?; if let Some(encoded) = encoded { cur.write_all(encoded)?; - let remaining_len = ENCODED_MAX_LEN - encoded_len as usize; + let remaining_len = ENCODED_MAX_LEN - encoded_len; cur.write_all(&vec![0; remaining_len])?; } else { cur.write_all(&[0; ENCODED_MAX_LEN])?; From 30df44e67ca7679ec1653fed2c728a111579a5a4 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Thu, 1 Feb 2024 16:40:30 -0500 Subject: [PATCH 0455/1053] use bytemuck for metadata (#500) --- firewood/Cargo.toml | 2 +- firewood/src/merkle/node.rs | 154 ++++++++++------------- firewood/src/merkle/node/leaf.rs | 87 +++++++------ firewood/src/merkle/node/partial_path.rs | 22 ++-- firewood/src/shale/mod.rs | 2 + 5 files changed, 124 insertions(+), 143 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 1ac94733fb41..8159de00886d 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,7 +34,7 @@ thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.18.0" bincode = "1.3.3" -bitflags = "2.4.1" +bitflags = { version = "2.4.1", features = ["bytemuck"] } env_logger = { version = "0.11.0", optional = true } log = { version = "0.4.20", optional = true } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index c8627db3ec31..076aa4cb7f79 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -1,13 +1,14 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::logger::trace; use crate::{ + logger::trace, merkle::from_nibbles, shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}, }; use bincode::{Error, Options}; use bitflags::bitflags; +use bytemuck::{CheckedBitPattern, NoUninit, Pod, Zeroable}; use enum_as_inner::EnumAsInner; use serde::{ de::DeserializeOwned, @@ -213,6 +214,8 @@ impl From for Node { } bitflags! { + #[derive(Debug, Default, Clone, Copy, Pod, Zeroable)] + #[repr(transparent)] struct NodeAttributes: u8 { const HAS_ROOT_HASH = 0b001; const ENCODED_LENGTH_IS_KNOWN = 0b010; @@ -315,14 +318,13 @@ impl Node { } } -const ENCODED_MAX_LEN: usize = TRIE_HASH_LEN - 1; - -#[repr(C)] +#[derive(Clone, Copy, CheckedBitPattern, NoUninit)] +#[repr(C, packed)] struct Meta { root_hash: [u8; TRIE_HASH_LEN], attrs: NodeAttributes, - encoded_len: [u8; size_of::()], - encoded: [u8; ENCODED_MAX_LEN], + encoded_len: u64, + encoded: [u8; TRIE_HASH_LEN], type_id: NodeTypeId, } @@ -331,28 +333,31 @@ impl Meta { } mod type_id { + use super::{CheckedBitPattern, NoUninit, NodeType}; use crate::shale::ShaleError; - const BRANCH: u8 = 0; - const LEAF: u8 = 1; - const EXTENSION: u8 = 2; - + #[derive(Clone, Copy, CheckedBitPattern, NoUninit)] #[repr(u8)] pub enum NodeTypeId { - Branch = BRANCH, - Leaf = LEAF, - Extension = EXTENSION, + Branch = 0, + Leaf = 1, + Extension = 2, } impl TryFrom for NodeTypeId { type Error = ShaleError; fn try_from(value: u8) -> Result { - match value { - BRANCH => Ok(Self::Branch), - LEAF => Ok(Self::Leaf), - EXTENSION => Ok(Self::Extension), - _ => Err(ShaleError::InvalidNodeType), + bytemuck::checked::try_cast::<_, Self>(value).map_err(|_| ShaleError::InvalidNodeType) + } + } + + impl From<&NodeType> for NodeTypeId { + fn from(node_type: &NodeType) -> Self { + match node_type { + NodeType::Branch(_) => NodeTypeId::Branch, + NodeType::Leaf(_) => NodeTypeId::Leaf, + NodeType::Extension(_) => NodeTypeId::Extension, } } } @@ -368,46 +373,34 @@ impl Storable for Node { offset, size: Meta::SIZE as u64, })?; + let meta_raw = meta_raw.as_deref(); + + let meta = bytemuck::checked::try_from_bytes::(&meta_raw) + .map_err(|_| ShaleError::InvalidNodeMeta)?; + + let Meta { + root_hash, + attrs, + encoded_len, + encoded, + type_id, + } = *meta; trace!("[{mem:p}] Deserializing node at {offset}"); let offset = offset + Meta::SIZE; - #[allow(clippy::indexing_slicing)] - let attrs = NodeAttributes::from_bits_retain(meta_raw.as_deref()[TRIE_HASH_LEN]); - let root_hash = if attrs.contains(NodeAttributes::HAS_ROOT_HASH) { - Some(TrieHash( - #[allow(clippy::indexing_slicing)] - meta_raw.as_deref()[..TRIE_HASH_LEN] - .try_into() - .expect("invalid slice"), - )) + Some(TrieHash(root_hash)) } else { None }; - let mut start_index = TRIE_HASH_LEN + 1; - let end_index = start_index + size_of::(); - #[allow(clippy::indexing_slicing)] - let encoded_len = u64::from_le_bytes( - meta_raw.as_deref()[start_index..end_index] - .try_into() - .expect("invalid slice"), - ); - - start_index = end_index; - let mut encoded: Option> = None; - if encoded_len > 0 { - #[allow(clippy::indexing_slicing)] - let value: Vec = - meta_raw.as_deref()[start_index..start_index + encoded_len as usize].into(); - encoded = Some(value); - } - - start_index += ENCODED_MAX_LEN; - #[allow(clippy::indexing_slicing)] - let type_id: NodeTypeId = meta_raw.as_deref()[start_index].try_into()?; + let encoded = if encoded_len > 0 { + Some(encoded.iter().take(encoded_len as usize).copied().collect()) + } else { + None + }; let is_encoded_longer_than_hash_len = if attrs.contains(NodeAttributes::ENCODED_LENGTH_IS_KNOWN) { @@ -449,10 +442,7 @@ impl Storable for Node { fn serialized_len(&self) -> u64 { Meta::SIZE as u64 + match &self.inner { - NodeType::Branch(n) => { - // TODO: add path - n.serialized_len() - } + NodeType::Branch(n) => n.serialized_len(), NodeType::Extension(n) => n.serialized_len(), NodeType::Leaf(n) => n.serialized_len(), } @@ -460,17 +450,11 @@ impl Storable for Node { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { trace!("[{self:p}] Serializing node"); - let mut cur = Cursor::new(to); + let mut cursor = Cursor::new(to); - let mut attrs = match self.root_hash.get() { - Some(h) => { - cur.write_all(&h.0)?; - NodeAttributes::HAS_ROOT_HASH - } - None => { - cur.write_all(&[0; 32])?; - NodeAttributes::empty() - } + let (mut attrs, root_hash) = match self.root_hash.get() { + Some(hash) => (NodeAttributes::HAS_ROOT_HASH, hash.0), + None => Default::default(), }; let encoded = self @@ -478,7 +462,7 @@ impl Storable for Node { .get() .filter(|encoded| encoded.len() < TRIE_HASH_LEN); - let encoded_len = encoded.map(Vec::len).unwrap_or(0); + let encoded_len = encoded.map(Vec::len).unwrap_or(0) as u64; if let Some(&is_encoded_longer_than_hash_len) = self.is_encoded_longer_than_hash_len.get() { attrs.insert(if is_encoded_longer_than_hash_len { @@ -488,45 +472,43 @@ impl Storable for Node { }); } - #[allow(clippy::unwrap_used)] - cur.write_all(&[attrs.bits()]).unwrap(); - cur.write_all(&(encoded_len as u64).to_le_bytes())?; + let encoded = std::array::from_fn({ + let mut encoded = encoded.into_iter().flatten().copied(); + move |_| encoded.next().unwrap_or(0) + }); - if let Some(encoded) = encoded { - cur.write_all(encoded)?; - let remaining_len = ENCODED_MAX_LEN - encoded_len; - cur.write_all(&vec![0; remaining_len])?; - } else { - cur.write_all(&[0; ENCODED_MAX_LEN])?; - } + let type_id = NodeTypeId::from(&self.inner); + + let meta = Meta { + root_hash, + attrs, + encoded_len, + encoded, + type_id, + }; + + cursor.write_all(bytemuck::bytes_of(&meta))?; match &self.inner { NodeType::Branch(n) => { - // TODO: add path - cur.write_all(&[type_id::NodeTypeId::Branch as u8])?; - - let pos = cur.position() as usize; + let pos = cursor.position() as usize; #[allow(clippy::indexing_slicing)] - n.serialize(&mut cur.get_mut()[pos..]) + n.serialize(&mut cursor.get_mut()[pos..]) } NodeType::Extension(n) => { - cur.write_all(&[type_id::NodeTypeId::Extension as u8])?; - - let pos = cur.position() as usize; + let pos = cursor.position() as usize; #[allow(clippy::indexing_slicing)] - n.serialize(&mut cur.get_mut()[pos..]) + n.serialize(&mut cursor.get_mut()[pos..]) } NodeType::Leaf(n) => { - cur.write_all(&[type_id::NodeTypeId::Leaf as u8])?; - - let pos = cur.position() as usize; + let pos = cursor.position() as usize; #[allow(clippy::indexing_slicing)] - n.serialize(&mut cur.get_mut()[pos..]) + n.serialize(&mut cursor.get_mut()[pos..]) } } } diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 5b058a66174c..552caf3cccfc 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -1,19 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Read, Write}, - mem::size_of, -}; - -use bincode::Options; - use super::{Data, Encoded}; use crate::{ - merkle::{from_nibbles, to_nibble_array, PartialPath}, + merkle::{from_nibbles, PartialPath}, + nibbles::Nibbles, shale::{ShaleError::InvalidCacheView, Storable}, }; +use bincode::Options; +use bytemuck::{Pod, Zeroable}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + io::{Cursor, Write}, + mem::size_of, +}; pub const SIZE: usize = 2; @@ -33,9 +33,6 @@ impl Debug for LeafNode { } impl LeafNode { - const PATH_LEN_SIZE: u64 = size_of::() as u64; - const DATA_LEN_SIZE: u64 = size_of::() as u64; - pub fn new, D: Into>(path: P, data: D) -> Self { Self { path: path.into(), @@ -65,66 +62,68 @@ impl LeafNode { } } +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +struct Meta { + path_len: PathLen, + data_len: DataLen, +} + +impl Meta { + const SIZE: usize = size_of::(); +} + impl Storable for LeafNode { fn serialized_len(&self) -> u64 { - let path_len_size = size_of::() as u64; + let meta_len = size_of::() as u64; let path_len = self.path.serialized_len(); - let data_len_size = size_of::() as u64; let data_len = self.data.len() as u64; - path_len_size + path_len + data_len_size + data_len + meta_len + path_len + data_len } fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path: Vec = from_nibbles(&self.path.encode(true)).collect(); + let path = &self.path.encode(true); + let path = from_nibbles(path); + let data = &self.data; - cursor.write_all(&[path.len() as PathLen])?; + let path_len = self.path.serialized_len() as PathLen; + let data_len = data.len() as DataLen; - let data_len = self.data.len() as DataLen; - cursor.write_all(&data_len.to_le_bytes())?; + let meta = Meta { path_len, data_len }; + + cursor.write_all(bytemuck::bytes_of(&meta))?; + + for nibble in path { + cursor.write_all(&[nibble])?; + } - cursor.write_all(&path)?; - cursor.write_all(&self.data)?; + cursor.write_all(data)?; Ok(()) } fn deserialize( - mut offset: usize, + offset: usize, mem: &T, ) -> Result where Self: Sized, { - let header_size = Self::PATH_LEN_SIZE + Self::DATA_LEN_SIZE; - let node_header_raw = mem - .get_view(offset, header_size) + .get_view(offset, Meta::SIZE as u64) .ok_or(InvalidCacheView { offset, - size: header_size, + size: Meta::SIZE as u64, })? .as_deref(); - offset += header_size as usize; - - let mut cursor = Cursor::new(node_header_raw); - - let path_len = { - let mut buf = [0u8; Self::PATH_LEN_SIZE as usize]; - cursor.read_exact(buf.as_mut())?; - PathLen::from_le_bytes(buf) as u64 - }; - - let data_len = { - let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; - cursor.read_exact(buf.as_mut())?; - DataLen::from_le_bytes(buf) as u64 - }; + let offset = offset + Meta::SIZE; + let Meta { path_len, data_len } = *bytemuck::from_bytes(&node_header_raw); + let size = path_len as u64 + data_len as u64; - let size = path_len + data_len; let remainder = mem .get_view(offset, size) .ok_or(InvalidCacheView { offset, size })? @@ -133,8 +132,8 @@ impl Storable for LeafNode { let (path, data) = remainder.split_at(path_len as usize); let path = { - let nibbles: Vec = path.iter().copied().flat_map(to_nibble_array).collect(); - PartialPath::decode(&nibbles).0 + let nibbles = Nibbles::<0>::new(path).into_iter(); + PartialPath::from_nibbles(nibbles).0 }; let data = Data(data.to_vec()); diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs index d8f9904e8844..4646ea5ca643 100644 --- a/firewood/src/merkle/node/partial_path.rs +++ b/firewood/src/merkle/node/partial_path.rs @@ -68,25 +68,23 @@ impl PartialPath { // /// returns a tuple of the decoded partial path and whether the path is terminal pub fn decode(raw: &[u8]) -> (Self, bool) { - let mut raw = raw.iter().copied(); - let flags = Flags::from_bits_retain(raw.next().unwrap_or_default()); - - if !flags.contains(Flags::ODD_LEN) { - let _ = raw.next(); - } - - (Self(raw.collect()), flags.contains(Flags::TERMINAL)) + Self::from_iter(raw.iter().copied()) } /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { - let flags = Flags::from_bits_retain(nibbles.next().unwrap_or_default()); + pub fn from_nibbles(nibbles: NibblesIterator<'_, N>) -> (Self, bool) { + Self::from_iter(nibbles) + } + + /// Assumes all bytes are nibbles, prefer to use `from_nibbles` instead. + fn from_iter>(mut iter: Iter) -> (Self, bool) { + let flags = Flags::from_bits_retain(iter.next().unwrap_or_default()); if !flags.contains(Flags::ODD_LEN) { - let _ = nibbles.next(); + let _ = iter.next(); } - (Self(nibbles.collect()), flags.contains(Flags::TERMINAL)) + (Self(iter.collect()), flags.contains(Flags::TERMINAL)) } pub(super) fn serialized_len(&self) -> u64 { diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 29a933f21875..fc519d9b5187 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -30,6 +30,8 @@ pub enum ShaleError { InvalidAddressLength { expected: DiskAddress, found: u64 }, #[error("invalid node type")] InvalidNodeType, + #[error("invalid node metadata")] + InvalidNodeMeta, #[error("failed to create view: offset: {offset:?} size: {size:?}")] InvalidCacheView { offset: usize, size: u64 }, #[error("io error: {0}")] From 7713f2d101566b8586940ae112d9d6dba6c6e356 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 2 Feb 2024 14:23:17 -0500 Subject: [PATCH 0456/1053] Add path to Branch-Nodes (don't use Extension-Nodes) (#344) --- firewood/src/db/proposal.rs | 4 +- firewood/src/merkle.rs | 786 ++++++++++++++++++++--- firewood/src/merkle/node.rs | 165 +++-- firewood/src/merkle/node/branch.rs | 85 ++- firewood/src/merkle/node/partial_path.rs | 2 +- firewood/src/merkle/proof.rs | 184 +++++- firewood/src/merkle/stream.rs | 158 ++++- firewood/src/merkle_util.rs | 13 +- firewood/src/shale/compact.rs | 12 +- firewood/src/shale/mod.rs | 1 + firewood/tests/merkle.rs | 147 +++-- 11 files changed, 1271 insertions(+), 286 deletions(-) diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index ecf18ec1181f..e842c1087c9a 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -10,15 +10,13 @@ use crate::shale::CachedStore; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, - v2::api::{self, KeyType, ValueType}, + v2::api::{self, Batch, BatchOp, KeyType, ValueType}, }; use async_trait::async_trait; use parking_lot::{Mutex, RwLock}; use std::{io::ErrorKind, sync::Arc}; use tokio::task::block_in_place; -pub use crate::v2::api::{Batch, BatchOp}; - /// An atomic batch of changes proposed against the latest committed revision, /// or any existing [Proposal]. Multiple proposals can be created against the /// latest committed revision at the same time. [Proposal] is immutable meaning diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index f8b6fb0f12f1..25a9aea33eb3 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -110,7 +110,10 @@ where fn encode(&self, node: &NodeObjRef) -> Result, MerkleError> { let encoded = match node.inner() { NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), + NodeType::Branch(n) => { + let path = n.path.clone(); + // pair up DiskAddresses with encoded children and pick the right one let encoded_children = n.chd().iter().zip(n.children_encoded.iter()); let children = encoded_children @@ -127,6 +130,7 @@ where .expect("MAX_CHILDREN will always be yielded"); EncodedNode::new(EncodedNodeType::Branch { + path, children, value: n.value.clone(), }) @@ -145,13 +149,19 @@ where match encoded.node { EncodedNodeType::Leaf(leaf) => Ok(NodeType::Leaf(leaf)), - EncodedNodeType::Branch { children, value } => { - let path = Vec::new().into(); + EncodedNodeType::Branch { + path, + children, + value, + } => { + let path = PartialPath::decode(&path).0; let value = value.map(|v| v.0); - Ok(NodeType::Branch( + let branch = NodeType::Branch( BranchNode::new(path, [None; BranchNode::MAX_CHILDREN], value, *children) .into(), - )) + ); + + Ok(branch) } } } @@ -162,7 +172,7 @@ impl + Send + Sync, T> Merkle { self.store .put_item( Node::from_branch(BranchNode { - // path: vec![].into(), + path: vec![].into(), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -190,9 +200,9 @@ impl + Send + Sync, T> Merkle { }) } - pub fn root_hash(&self, root: DiskAddress) -> Result { + pub fn root_hash(&self, sentinel: DiskAddress) -> Result { let root = self - .get_node(root)? + .get_node(sentinel)? .inner .as_branch() .ok_or(MerkleError::NotBranchNode)? @@ -213,14 +223,14 @@ impl + Send + Sync, T> Merkle { fn dump_(&self, u: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { let u_ref = self.get_node(u)?; - write!( - w, - "{u:?} => {}: ", - match u_ref.root_hash.get() { - Some(h) => hex::encode(**h), - None => "".to_string(), - } - )?; + + let hash = match u_ref.root_hash.get() { + Some(h) => h, + None => u_ref.get_root_hash::(self.store.as_ref()), + }; + + write!(w, "{u:?} => {}: ", hex::encode(**hash))?; + match &u_ref.inner { NodeType::Branch(n) => { writeln!(w, "{n:?}")?; @@ -235,6 +245,7 @@ impl + Send + Sync, T> Merkle { self.dump_(n.chd(), w)? } } + Ok(()) } @@ -247,23 +258,25 @@ impl + Send + Sync, T> Merkle { Ok(()) } + // TODO: replace `split` with a `split_at` function. Handle the logic for matching paths in `insert` instead. #[allow(clippy::too_many_arguments)] - fn split( - &self, - mut node_to_split: NodeObjRef, - parents: &mut [(NodeObjRef, u8)], + fn split<'a>( + &'a self, + mut node_to_split: NodeObjRef<'a>, + parents: &mut [(NodeObjRef<'a>, u8)], insert_path: &[u8], n_path: Vec, n_value: Option, val: Vec, deleted: &mut Vec, - ) -> Result>, MerkleError> { + ) -> Result, Vec)>, MerkleError> { let node_to_split_address = node_to_split.as_ptr(); let split_index = insert_path .iter() .zip(n_path.iter()) .position(|(a, b)| a != b); + #[allow(clippy::indexing_slicing)] let new_child_address = if let Some(idx) = split_index { // paths diverge let new_split_node_path = n_path.split_at(idx + 1).1; @@ -286,10 +299,8 @@ impl + Send + Sync, T> Merkle { let mut chd = [None; BranchNode::MAX_CHILDREN]; - #[allow(clippy::indexing_slicing)] let last_matching_nibble = matching_path[idx]; - #[allow(clippy::indexing_slicing)] - (chd[last_matching_nibble as usize] = Some(leaf_address)); + chd[last_matching_nibble as usize] = Some(leaf_address); let address = match &node_to_split.inner { NodeType::Extension(u) if u.path.len() == 0 => { @@ -299,35 +310,22 @@ impl + Send + Sync, T> Merkle { _ => node_to_split_address, }; - #[allow(clippy::indexing_slicing)] - (chd[n_path[idx] as usize] = Some(address)); + chd[n_path[idx] as usize] = Some(address); let new_branch = Node::from_branch(BranchNode { - // path: PartialPath(matching_path[..idx].to_vec()), + path: PartialPath(matching_path[..idx].to_vec()), children: chd, value: None, children_encoded: Default::default(), }); - let new_branch_address = self.put_node(new_branch)?.as_ptr(); - - if idx > 0 { - self.put_node(Node::from(NodeType::Extension(ExtNode { - #[allow(clippy::indexing_slicing)] - path: PartialPath(matching_path[..idx].to_vec()), - child: new_branch_address, - child_encoded: None, - })))? - .as_ptr() - } else { - new_branch_address - } + self.put_node(new_branch)?.as_ptr() } else { // paths do not diverge let (leaf_address, prefix, idx, value) = match (insert_path.len().cmp(&n_path.len()), n_value) { // no node-value means this is an extension node and we can therefore continue walking the tree - (Ordering::Greater, None) => return Ok(Some(val)), + (Ordering::Greater, None) => return Ok(Some((node_to_split, val))), // if the paths are equal, we overwrite the data (Ordering::Equal, _) => { @@ -368,7 +366,9 @@ impl + Send + Sync, T> Merkle { result = Err(e); } } - NodeType::Branch(_) => unreachable!(), + NodeType::Branch(u) => { + u.value = Some(Data(val)); + } } u.rehash(); @@ -440,24 +440,13 @@ impl + Send + Sync, T> Merkle { #[allow(clippy::indexing_slicing)] (children[idx] = leaf_address.into()); - let branch_address = self - .put_node(Node::from_branch(BranchNode { - children, - value, - children_encoded: Default::default(), - }))? - .as_ptr(); - - if !prefix.is_empty() { - self.put_node(Node::from(NodeType::Extension(ExtNode { - path: PartialPath(prefix.to_vec()), - child: branch_address, - child_encoded: None, - })))? - .as_ptr() - } else { - branch_address - } + self.put_node(Node::from_branch(BranchNode { + path: PartialPath(prefix.to_vec()), + children, + value, + children_encoded: Default::default(), + }))? + .as_ptr() }; // observation: @@ -509,7 +498,7 @@ impl + Send + Sync, T> Merkle { // walk down the merkle tree starting from next_node, currently the root // return None if the value is inserted let next_node_and_val = loop { - let Some(current_nibble) = key_nibbles.next() else { + let Some(mut next_nibble) = key_nibbles.next() else { break Some((node, val)); }; @@ -518,28 +507,138 @@ impl + Send + Sync, T> Merkle { // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done NodeType::Leaf(n) => { - // we collided with another key; make a copy - // of the stored key to pass into split - let n_path = n.path.to_vec(); - let n_value = Some(n.data.clone()); - let rem_path = once(current_nibble).chain(key_nibbles).collect::>(); + // TODO: avoid extra allocation + let key_remainder = once(next_nibble) + .chain(key_nibbles.clone()) + .collect::>(); - self.split( - node, - &mut parents, - &rem_path, - n_path, - n_value, - val, - &mut deleted, - )?; + let overlap = PrefixOverlap::from(&n.path, &key_remainder); + + #[allow(clippy::indexing_slicing)] + match (overlap.unique_a.len(), overlap.unique_b.len()) { + // same node, overwrite the data + (0, 0) => { + node.write(|node| { + node.inner.set_data(Data(val)); + node.rehash(); + })?; + } + + // new node is a child of the old node + (0, _) => { + let (new_leaf_index, new_leaf_path) = { + let (index, path) = overlap.unique_b.split_at(1); + (index[0], path.to_vec()) + }; + + let new_leaf = Node::from_leaf(LeafNode::new( + PartialPath(new_leaf_path), + Data(val), + )); + + let new_leaf = self.put_node(new_leaf)?.as_ptr(); + + let mut children = [None; BranchNode::MAX_CHILDREN]; + children[new_leaf_index as usize] = Some(new_leaf); + + let new_branch = BranchNode { + path: PartialPath(overlap.shared.to_vec()), + children, + value: n.data.clone().into(), + children_encoded: Default::default(), + }; + + let new_branch = Node::from_branch(new_branch); + + let new_branch = self.put_node(new_branch)?.as_ptr(); + + set_parent(new_branch, &mut parents); + + deleted.push(node.as_ptr()); + } + + // old node is a child of the new node + (_, 0) => { + let (old_leaf_index, old_leaf_path) = { + let (index, path) = overlap.unique_a.split_at(1); + (index[0], path.to_vec()) + }; + + let new_branch_path = overlap.shared.to_vec(); + + node.write(move |old_leaf| { + *old_leaf.inner.path_mut() = PartialPath(old_leaf_path.to_vec()); + old_leaf.rehash(); + })?; + + let old_leaf = node.as_ptr(); + + let mut new_branch = BranchNode { + path: PartialPath(new_branch_path), + children: [None; BranchNode::MAX_CHILDREN], + value: Some(val.into()), + children_encoded: Default::default(), + }; + + new_branch.children[old_leaf_index as usize] = Some(old_leaf); + + let node = Node::from_branch(new_branch); + let node = self.put_node(node)?.as_ptr(); + + set_parent(node, &mut parents); + } + + // nodes are siblings + _ => { + let (old_leaf_index, old_leaf_path) = { + let (index, path) = overlap.unique_a.split_at(1); + (index[0], path.to_vec()) + }; + + let (new_leaf_index, new_leaf_path) = { + let (index, path) = overlap.unique_b.split_at(1); + (index[0], path.to_vec()) + }; + + let new_branch_path = overlap.shared.to_vec(); + + node.write(move |old_leaf| { + *old_leaf.inner.path_mut() = PartialPath(old_leaf_path.to_vec()); + old_leaf.rehash(); + })?; + + let new_leaf = Node::from_leaf(LeafNode::new( + PartialPath(new_leaf_path), + Data(val), + )); + + let old_leaf = node.as_ptr(); + + let new_leaf = self.put_node(new_leaf)?.as_ptr(); + + let mut new_branch = BranchNode { + path: PartialPath(new_branch_path), + children: [None; BranchNode::MAX_CHILDREN], + value: None, + children_encoded: Default::default(), + }; + + new_branch.children[old_leaf_index as usize] = Some(old_leaf); + new_branch.children[new_leaf_index as usize] = Some(new_leaf); + + let node = Node::from_branch(new_branch); + let node = self.put_node(node)?.as_ptr(); + + set_parent(node, &mut parents); + } + } break None; } - NodeType::Branch(n) => { + NodeType::Branch(n) if n.path.len() == 0 => { #[allow(clippy::indexing_slicing)] - match n.children[current_nibble as usize] { + match n.children[next_nibble as usize] { Some(c) => (node, c), None => { // insert the leaf to the empty slot @@ -550,15 +649,152 @@ impl + Send + Sync, T> Merkle { Data(val), )))? .as_ptr(); + // set the current child to point to this leaf - #[allow(clippy::unwrap_used)] - node.write(|u| { - let uu = u.inner.as_branch_mut().unwrap(); - #[allow(clippy::indexing_slicing)] - (uu.children[current_nibble as usize] = Some(leaf_ptr)); - u.rehash(); - }) - .unwrap(); + #[allow(clippy::indexing_slicing)] + node.write(|node| { + node.as_branch_mut().children[next_nibble as usize] = + Some(leaf_ptr); + node.rehash(); + })?; + + break None; + } + } + } + + NodeType::Branch(n) => { + // TODO: avoid extra allocation + let key_remainder = once(next_nibble) + .chain(key_nibbles.clone()) + .collect::>(); + + let overlap = PrefixOverlap::from(&n.path, &key_remainder); + + #[allow(clippy::indexing_slicing)] + match (overlap.unique_a.len(), overlap.unique_b.len()) { + // same node, overwrite the data + (0, 0) => { + node.write(|node| { + node.inner.set_data(Data(val)); + node.rehash(); + })?; + + break None; + } + + // new node is a child of the old node + (0, _) => { + let (new_leaf_index, new_leaf_path) = { + let (index, path) = overlap.unique_b.split_at(1); + (index[0], path) + }; + + (0..overlap.shared.len()).for_each(|_| { + key_nibbles.next(); + }); + + next_nibble = new_leaf_index; + + match n.children[next_nibble as usize] { + Some(ptr) => (node, ptr), + None => { + let new_leaf = Node::from_leaf(LeafNode::new( + PartialPath(new_leaf_path.to_vec()), + Data(val), + )); + + let new_leaf = self.put_node(new_leaf)?.as_ptr(); + + #[allow(clippy::indexing_slicing)] + node.write(|node| { + node.as_branch_mut().children[next_nibble as usize] = + Some(new_leaf); + node.rehash(); + })?; + + break None; + } + } + } + + // old node is a child of the new node + (_, 0) => { + let (old_branch_index, old_branch_path) = { + let (index, path) = overlap.unique_a.split_at(1); + (index[0], path.to_vec()) + }; + + let new_branch_path = overlap.shared.to_vec(); + + node.write(move |old_branch| { + *old_branch.inner.path_mut() = + PartialPath(old_branch_path.to_vec()); + old_branch.rehash(); + })?; + + let old_branch = node.as_ptr(); + + let mut new_branch = BranchNode { + path: PartialPath(new_branch_path), + children: [None; BranchNode::MAX_CHILDREN], + value: Some(val.into()), + children_encoded: Default::default(), + }; + + new_branch.children[old_branch_index as usize] = Some(old_branch); + + let node = Node::from_branch(new_branch); + let node = self.put_node(node)?.as_ptr(); + + set_parent(node, &mut parents); + + break None; + } + + // nodes are siblings + _ => { + let (old_branch_index, old_branch_path) = { + let (index, path) = overlap.unique_a.split_at(1); + (index[0], path.to_vec()) + }; + + let (new_leaf_index, new_leaf_path) = { + let (index, path) = overlap.unique_b.split_at(1); + (index[0], path.to_vec()) + }; + + let new_branch_path = overlap.shared.to_vec(); + + node.write(move |old_branch| { + *old_branch.inner.path_mut() = + PartialPath(old_branch_path.to_vec()); + old_branch.rehash(); + })?; + + let new_leaf = Node::from_leaf(LeafNode::new( + PartialPath(new_leaf_path), + Data(val), + )); + + let old_branch = node.as_ptr(); + + let new_leaf = self.put_node(new_leaf)?.as_ptr(); + + let mut new_branch = BranchNode { + path: PartialPath(new_branch_path), + children: [None; BranchNode::MAX_CHILDREN], + value: None, + children_encoded: Default::default(), + }; + + new_branch.children[old_branch_index as usize] = Some(old_branch); + new_branch.children[new_leaf_index as usize] = Some(new_leaf); + + let node = Node::from_branch(new_branch); + let node = self.put_node(node)?.as_ptr(); + + set_parent(node, &mut parents); break None; } @@ -568,13 +804,12 @@ impl + Send + Sync, T> Merkle { NodeType::Extension(n) => { let n_path = n.path.to_vec(); let n_ptr = n.chd(); - let rem_path = once(current_nibble) + let rem_path = once(next_nibble) .chain(key_nibbles.clone()) .collect::>(); let n_path_len = n_path.len(); - let node_ptr = node.as_ptr(); - if let Some(v) = self.split( + if let Some((node, v)) = self.split( node, &mut parents, &rem_path, @@ -592,7 +827,7 @@ impl + Send + Sync, T> Merkle { // extension node's next pointer val = v; - (self.get_node(node_ptr)?, n_ptr) + (node, n_ptr) } else { // successfully inserted break None; @@ -601,7 +836,7 @@ impl + Send + Sync, T> Merkle { }; // push another parent, and follow the next pointer - parents.push((node_ref, current_nibble)); + parents.push((node_ref, next_nibble)); node = self.get_node(next_node_ptr)?; }; @@ -674,7 +909,7 @@ impl + Send + Sync, T> Merkle { let branch = self .put_node(Node::from_branch(BranchNode { - // path: vec![].into(), + path: vec![].into(), children: chd, value: Some(Data(val)), children_encoded: Default::default(), @@ -1000,6 +1235,183 @@ impl + Send + Sync, T> Merkle { return Ok(None); } + let mut deleted = Vec::new(); + + let data = { + let (node, mut parents) = + self.get_node_and_parents_by_key(self.get_node(root)?, key)?; + + let Some(mut node) = node else { + return Ok(None); + }; + + let data = match &node.inner { + NodeType::Branch(branch) => { + let data = branch.value.clone(); + let children = branch.children; + + if data.is_none() { + return Ok(None); + } + + let children: Vec<_> = children + .iter() + .enumerate() + .filter_map(|(i, child)| child.map(|child| (i, child))) + .collect(); + + // don't change the sentinal node + if children.len() == 1 && !parents.is_empty() { + let branch_path = &branch.path.0; + + #[allow(clippy::indexing_slicing)] + let (child_index, child) = children[0]; + let mut child = self.get_node(child)?; + + child.write(|child| { + let child_path = child.inner.path_mut(); + let path = branch_path + .iter() + .copied() + .chain(once(child_index as u8)) + .chain(child_path.0.iter().copied()) + .collect(); + *child_path = PartialPath(path); + + child.rehash(); + })?; + + set_parent(child.as_ptr(), &mut parents); + + deleted.push(node.as_ptr()); + } else { + node.write(|node| { + node.as_branch_mut().value = None; + node.rehash(); + })? + } + + data + } + + NodeType::Leaf(n) => { + let data = Some(n.data.clone()); + + // TODO: handle unwrap better + deleted.push(node.as_ptr()); + + let (mut parent, child_index) = parents.pop().expect("parents is never empty"); + + #[allow(clippy::indexing_slicing)] + parent.write(|parent| { + parent.as_branch_mut().children[child_index as usize] = None; + })?; + + let branch = parent + .inner + .as_branch() + .expect("parents are always branch nodes"); + + let children: Vec<_> = branch + .children + .iter() + .enumerate() + .filter_map(|(i, child)| child.map(|child| (i, child))) + .collect(); + + match (children.len(), &branch.value, !parents.is_empty()) { + // node is invalid, all single-child nodes should have data + (1, None, true) => { + let parent_path = &branch.path.0; + + #[allow(clippy::indexing_slicing)] + let (child_index, child) = children[0]; + let child = self.get_node(child)?; + + // TODO: + // there's an optimization here for when the paths are the same length + // and that clone isn't great but ObjRef causes problems + // we can't write directly to the child because we could be changing its size + let new_child = match child.inner.clone() { + NodeType::Branch(mut child) => { + let path = parent_path + .iter() + .copied() + .chain(once(child_index as u8)) + .chain(child.path.0.iter().copied()) + .collect(); + + child.path = PartialPath(path); + + Node::from_branch(child) + } + NodeType::Leaf(mut child) => { + let path = parent_path + .iter() + .copied() + .chain(once(child_index as u8)) + .chain(child.path.0.iter().copied()) + .collect(); + + child.path = PartialPath(path); + + Node::from_leaf(child) + } + NodeType::Extension(_) => todo!(), + }; + + let child = self.put_node(new_child)?.as_ptr(); + + set_parent(child, &mut parents); + + deleted.push(parent.as_ptr()); + } + + // branch nodes shouldn't have no children + (0, Some(data), true) => { + let leaf = Node::from_leaf(LeafNode::new( + PartialPath(branch.path.0.clone()), + data.clone(), + )); + + let leaf = self.put_node(leaf)?.as_ptr(); + set_parent(leaf, &mut parents); + + deleted.push(parent.as_ptr()); + } + + _ => parent.write(|parent| parent.rehash())?, + } + + data + } + + NodeType::Extension(_) => todo!(), + }; + + for (mut parent, _) in parents { + parent.write(|u| u.rehash())?; + } + + data + }; + + for ptr in deleted.into_iter() { + self.free_node(ptr)?; + } + + Ok(data.map(|data| data.0)) + } + + pub fn remove_old>( + &mut self, + key: K, + root: DiskAddress, + ) -> Result>, MerkleError> { + if root.is_null() { + return Ok(None); + } + let (found, parents, deleted) = { let (node_ref, mut parents) = self.get_node_and_parents_by_key(self.get_node(root)?, key)?; @@ -1136,7 +1548,7 @@ impl + Send + Sync, T> Merkle { let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); loop { - let Some(nib) = key_nibbles.next() else { + let Some(mut nib) = key_nibbles.next() else { break; }; @@ -1144,10 +1556,41 @@ impl + Send + Sync, T> Merkle { let next_ptr = match &node_ref.inner { #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) => match n.children[nib as usize] { + NodeType::Branch(n) if n.path.len() == 0 => match n.children[nib as usize] { Some(c) => c, None => return Ok(None), }, + NodeType::Branch(n) => { + let mut n_path_iter = n.path.iter().copied(); + + if n_path_iter.next() != Some(nib) { + return Ok(None); + } + + let path_matches = n_path_iter + .map(Some) + .all(|n_path_nibble| key_nibbles.next() == n_path_nibble); + + if !path_matches { + return Ok(None); + } + + nib = if let Some(nib) = key_nibbles.next() { + nib + } else { + return Ok(if n.value.is_some() { + Some(node_ref) + } else { + None + }); + }; + + #[allow(clippy::indexing_slicing)] + match n.children[nib as usize] { + Some(c) => c, + None => return Ok(None), + } + } NodeType::Leaf(n) => { let node_ref = if once(nib).chain(key_nibbles).eq(n.path.iter().copied()) { Some(node_ref) @@ -1183,7 +1626,9 @@ impl + Send + Sync, T> Merkle { // when we're done iterating over nibbles, check if the node we're at has a value let node_ref = match &node_ref.inner { - NodeType::Branch(n) if n.value.as_ref().is_some() => Some(node_ref), + NodeType::Branch(n) if n.value.as_ref().is_some() && n.path.is_empty() => { + Some(node_ref) + } NodeType::Leaf(n) if n.path.len() == 0 => Some(node_ref), _ => None, }; @@ -1472,6 +1917,41 @@ pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } +/// The [`PrefixOverlap`] type represents the _shared_ and _unique_ parts of two potentially overlapping slices. +/// As the type-name implies, the `shared` property only constitues a shared *prefix*. +/// The `unique_*` properties, [`unique_a`][`PrefixOverlap::unique_a`] and [`unique_b`][`PrefixOverlap::unique_b`] +/// are set based on the argument order passed into the [`from`][`PrefixOverlap::from`] constructor. +#[derive(Debug)] +struct PrefixOverlap<'a, T> { + shared: &'a [T], + unique_a: &'a [T], + unique_b: &'a [T], +} + +impl<'a, T: PartialEq> PrefixOverlap<'a, T> { + fn from(a: &'a [T], b: &'a [T]) -> Self { + let mut split_index = 0; + + #[allow(clippy::indexing_slicing)] + for i in 0..std::cmp::min(a.len(), b.len()) { + if a[i] != b[i] { + break; + } + + split_index += 1; + } + + let (shared, unique_a) = a.split_at(split_index); + let (_, unique_b) = b.split_at(split_index); + + Self { + shared, + unique_a, + unique_b, + } + } +} + #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { @@ -1542,9 +2022,36 @@ mod tests { create_generic_test_merkle::() } - fn branch(value: Option>, encoded_child: Option>) -> Node { + fn branch(path: &[u8], value: &[u8], encoded_child: Option>) -> Node { + let (path, value) = (path.to_vec(), value.to_vec()); + let path = Nibbles::<0>::new(&path); + let path = PartialPath(path.into_iter().collect()); + + let children = Default::default(); + // TODO: Properly test empty data as a value + let value = Some(Data(value)); + let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); + + if let Some(child) = encoded_child { + children_encoded[0] = Some(child); + } + + Node::from_branch(BranchNode { + path, + children, + value, + children_encoded, + }) + } + + fn branch_without_data(path: &[u8], encoded_child: Option>) -> Node { + let path = path.to_vec(); + let path = Nibbles::<0>::new(&path); + let path = PartialPath(path.into_iter().collect()); + let children = Default::default(); - let value = value.map(Data); + // TODO: Properly test empty data as a value + let value = None; let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { @@ -1552,7 +2059,7 @@ mod tests { } Node::from_branch(BranchNode { - // path: vec![].into(), + path, children, value, children_encoded, @@ -1561,11 +2068,19 @@ mod tests { #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(Some(b"value".to_vec()), None); "branch without chd")] - #[test_case(branch(None, None); "branch without value and chd")] + #[test_case(branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd")] + #[test_case(branch(b"", b"value", None); "branch without chd")] + #[test_case(branch_without_data(b"", None); "branch without value and chd")] + #[test_case(branch(b"", b"", None); "branch without path value or children")] + #[test_case(branch(b"", b"value", None) ; "branch with value")] + #[test_case(branch(&[2], b"", None); "branch with path")] + #[test_case(branch(b"", b"", vec![1, 2, 3].into()); "branch with children")] + #[test_case(branch(&[2], b"value", None); "branch with path and value")] + #[test_case(branch(b"", b"value", vec![1, 2, 3].into()); "branch with value and children")] + #[test_case(branch(&[2], b"", vec![1, 2, 3].into()); "branch with path and children")] + #[test_case(branch(&[2], b"value", vec![1, 2, 3].into()); "branch with path value and children")] #[test_case(extension(vec![1, 2, 3], DiskAddress::null(), vec![4, 5].into()) ; "extension without child address")] - fn encode_(node: Node) { + fn encode(node: Node) { let merkle = create_test_merkle(); let node_ref = merkle.put_node(node).unwrap(); @@ -1578,14 +2093,14 @@ mod tests { #[test_case(Bincode::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with Bincode")] #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with Bincode")] - #[test_case(Bincode::new(), branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd with Bincode")] - #[test_case(Bincode::new(), branch(Some(b"value".to_vec()), None); "branch without chd with Bincode")] - #[test_case(Bincode::new(), branch(None, None); "branch without value and chd with Bincode")] + #[test_case(Bincode::new(), branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd with Bincode")] + #[test_case(Bincode::new(), branch(b"", b"value", None); "branch without chd with Bincode")] + #[test_case(Bincode::new(), branch_without_data(b"", None); "branch without value and chd with Bincode")] #[test_case(PlainCodec::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with PlainCodec")] #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with PlainCodec")] - #[test_case(PlainCodec::new(), branch(Some(b"value".to_vec()), vec![1, 2, 3].into()) ; "branch with chd with PlainCodec")] - #[test_case(PlainCodec::new(), branch(Some(b"value".to_vec()), Some(Vec::new())); "branch with empty chd with PlainCodec")] - #[test_case(PlainCodec::new(), branch(Some(Vec::new()), vec![1, 2, 3].into()); "branch with empty value with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"", b"value", Some(Vec::new())); "branch with empty chd with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"", b"", vec![1, 2, 3].into()); "branch with empty value with PlainCodec")] fn node_encode_decode(_codec: T, node: Node) where T: BinarySerde, @@ -1644,6 +2159,60 @@ mod tests { } } + #[test] + fn long_insert_and_retrieve_multiple() { + let key_val: Vec<(&'static [u8], _)> = vec![ + ( + &[0, 0, 0, 1, 0, 101, 151, 236], + [16, 15, 159, 195, 34, 101, 227, 73], + ), + ( + &[0, 0, 1, 107, 198, 92, 205], + [26, 147, 21, 200, 138, 106, 137, 218], + ), + (&[0, 1, 0, 1, 0, 56], [194, 147, 168, 193, 19, 226, 51, 204]), + (&[1, 90], [101, 38, 25, 65, 181, 79, 88, 223]), + ( + &[1, 1, 1, 0, 0, 0, 1, 59], + [105, 173, 182, 126, 67, 166, 166, 196], + ), + ( + &[0, 1, 0, 0, 1, 1, 55, 33, 38, 194], + [90, 140, 160, 53, 230, 100, 237, 236], + ), + ( + &[1, 1, 0, 1, 249, 46, 69], + [16, 104, 134, 6, 57, 46, 200, 35], + ), + ( + &[1, 1, 0, 1, 0, 0, 1, 33, 163], + [95, 97, 187, 124, 198, 28, 75, 226], + ), + ( + &[1, 1, 0, 1, 0, 57, 156], + [184, 18, 69, 29, 96, 252, 188, 58], + ), + (&[1, 0, 1, 1, 0, 218], [155, 38, 43, 54, 93, 134, 73, 209]), + ]; + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + for (key, val) in &key_val { + merkle.insert(key, val.to_vec(), root).unwrap(); + + let fetched_val = merkle.get(key, root).unwrap(); + + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + for (key, val) in key_val { + let fetched_val = merkle.get(key, root).unwrap(); + + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + } + #[test] fn remove_one() { let key = b"hello"; @@ -1689,7 +2258,10 @@ mod tests { let key = &[key_val]; let val = &[key_val]; - let removed_val = merkle.remove(key, root).unwrap(); + let Ok(removed_val) = merkle.remove(key, root) else { + panic!("({key_val}, {key_val}) missing"); + }; + assert_eq!(removed_val.as_deref(), val.as_slice().into()); let fetched_val = merkle.get(key, root).unwrap(); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 076aa4cb7f79..3e3ac0f7a5cc 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -91,6 +91,13 @@ impl> Encoded { Encoded::Data(data) => bincode::DefaultOptions::new().deserialize(data.as_ref()), } } + + pub fn deserialize(self) -> Result { + match self { + Encoded::Raw(raw) => Ok(raw), + Encoded::Data(data) => De::deserialize(data.as_ref()), + } + } } #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] @@ -148,11 +155,19 @@ impl NodeType { pub fn path_mut(&mut self) -> &mut PartialPath { match self { - NodeType::Branch(_u) => todo!(), + NodeType::Branch(u) => &mut u.path, NodeType::Leaf(node) => &mut node.path, NodeType::Extension(node) => &mut node.path, } } + + pub fn set_data(&mut self, data: Data) { + match self { + NodeType::Branch(u) => u.value = Some(data), + NodeType::Leaf(node) => node.data = data, + NodeType::Extension(_) => (), + } + } } #[derive(Debug)] @@ -233,7 +248,7 @@ impl Node { is_encoded_longer_than_hash_len: OnceLock::new(), inner: NodeType::Branch( BranchNode { - // path: vec![].into(), + path: vec![].into(), children: [Some(DiskAddress::null()); BranchNode::MAX_CHILDREN], value: Some(Data(Vec::new())), children_encoded: Default::default(), @@ -316,6 +331,12 @@ impl Node { pub(super) fn set_dirty(&self, is_dirty: bool) { self.lazy_dirty.store(is_dirty, Ordering::Relaxed) } + + pub(crate) fn as_branch_mut(&mut self) -> &mut Box { + self.inner_mut() + .as_branch_mut() + .expect("must be a branch node") + } } #[derive(Clone, Copy, CheckedBitPattern, NoUninit)] @@ -531,6 +552,7 @@ impl EncodedNode { pub enum EncodedNodeType { Leaf(LeafNode), Branch { + path: PartialPath, children: Box<[Option>; BranchNode::MAX_CHILDREN]>, value: Option, }, @@ -550,14 +572,19 @@ impl Serialize for EncodedNode { where S: serde::Serializer, { - let n = match &self.node { + let (chd, data, path) = match &self.node { EncodedNodeType::Leaf(n) => { - let data = Some(n.data.to_vec()); + let data = Some(&*n.data); let chd: Vec<(u64, Vec)> = Default::default(); - let path = from_nibbles(&n.path.encode(true)).collect(); - EncodedBranchNode { chd, data, path } + let path: Vec<_> = from_nibbles(&n.path.encode(true)).collect(); + (chd, data, path) } - EncodedNodeType::Branch { children, value } => { + + EncodedNodeType::Branch { + path, + children, + value, + } => { let chd: Vec<(u64, Vec)> = children .iter() .enumerate() @@ -571,19 +598,20 @@ impl Serialize for EncodedNode { }) .collect(); - let data = value.as_ref().map(|v| v.0.to_vec()); - EncodedBranchNode { - chd, - data, - path: Vec::new(), - } + let data = value.as_deref(); + + let path = from_nibbles(&path.encode(false)).collect(); + + (chd, data, path) } }; let mut s = serializer.serialize_tuple(3)?; - s.serialize_element(&n.chd)?; - s.serialize_element(&n.data)?; - s.serialize_element(&n.path)?; + + s.serialize_element(&chd)?; + s.serialize_element(&data)?; + s.serialize_element(&path)?; + s.end() } } @@ -593,30 +621,35 @@ impl<'de> Deserialize<'de> for EncodedNode { where D: serde::Deserializer<'de>, { - let node: EncodedBranchNode = Deserialize::deserialize(deserializer)?; - if node.chd.is_empty() { - let data = if let Some(d) = node.data { + let EncodedBranchNode { chd, data, path } = Deserialize::deserialize(deserializer)?; + + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + + if chd.is_empty() { + let data = if let Some(d) = data { Data(d) } else { Data(Vec::new()) }; - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&node.path).into_iter()).0; let node = EncodedNodeType::Leaf(LeafNode { path, data }); + Ok(Self::new(node)) } else { let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - let value = node.data.map(Data); + let value = data.map(Data); - for (i, chd) in node.chd { - #[allow(clippy::indexing_slicing)] - (children[i as usize] = Some(chd)); + #[allow(clippy::indexing_slicing)] + for (i, chd) in chd { + children[i as usize] = Some(chd); } let node = EncodedNodeType::Branch { + path, children: children.into(), value, }; + Ok(Self::new(node)) } } @@ -639,34 +672,50 @@ impl Serialize for EncodedNode { } seq.end() } - EncodedNodeType::Branch { children, value } => { - let mut list = <[Encoded>; BranchNode::MAX_CHILDREN + 1]>::default(); - for (i, c) in children + EncodedNodeType::Branch { + path, + children, + value, + } => { + let mut list = <[Encoded>; BranchNode::MAX_CHILDREN + 2]>::default(); + let children = children .iter() .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))) - { - if c.len() >= TRIE_HASH_LEN { - let serialized_hash = Bincode::serialize(&Keccak256::digest(c).to_vec()) - .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; - #[allow(clippy::indexing_slicing)] - (list[i] = Encoded::Data(serialized_hash)); + .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))); + + #[allow(clippy::indexing_slicing)] + for (i, child) in children { + if child.len() >= TRIE_HASH_LEN { + let serialized_hash = + Bincode::serialize(&Keccak256::digest(child).to_vec()) + .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; + list[i] = Encoded::Data(serialized_hash); } else { - #[allow(clippy::indexing_slicing)] - (list[i] = Encoded::Raw(c.to_vec())); + list[i] = Encoded::Raw(child.to_vec()); } } - if let Some(Data(val)) = &value { + + list[BranchNode::MAX_CHILDREN] = if let Some(Data(val)) = &value { let serialized_val = Bincode::serialize(val) .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; - list[BranchNode::MAX_CHILDREN] = Encoded::Data(serialized_val); - } + + Encoded::Data(serialized_val) + } else { + Encoded::default() + }; + + let serialized_path = Bincode::serialize(&path.encode(false)) + .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; + + list[BranchNode::MAX_CHILDREN + 1] = Encoded::Data(serialized_path); let mut seq = serializer.serialize_seq(Some(list.len()))?; + for e in list { seq.serialize_element(&e)?; } + seq.end() } } @@ -680,8 +729,9 @@ impl<'de> Deserialize<'de> for EncodedNode { { use serde::de::Error; - let items: Vec>> = Deserialize::deserialize(deserializer)?; + let mut items: Vec>> = Deserialize::deserialize(deserializer)?; let len = items.len(); + match len { LEAF_NODE_SIZE => { let mut items = items.into_iter(); @@ -702,10 +752,25 @@ impl<'de> Deserialize<'de> for EncodedNode { }); Ok(Self::new(node)) } + BranchNode::MSIZE => { + let path = items + .pop() + .unwrap_or_default() + .deserialize::() + .map_err(D::Error::custom)?; + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + + let mut value = items + .pop() + .unwrap_or_default() + .deserialize::() + .map_err(D::Error::custom) + .map(Data) + .map(Some)? + .filter(|data| !data.is_empty()); + let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - let mut value: Option = Default::default(); - let len = items.len(); for (i, chd) in items.into_iter().enumerate() { if i == len - 1 { @@ -729,11 +794,17 @@ impl<'de> Deserialize<'de> for EncodedNode { (children[i] = Some(chd).filter(|chd| !chd.is_empty())); } } + let node = EncodedNodeType::Branch { + path, children: children.into(), value, }; - Ok(Self::new(node)) + + Ok(Self { + node, + phantom: PhantomData, + }) } size => Err(D::Error::custom(format!("invalid size: {size}"))), } @@ -847,7 +918,7 @@ mod tests { ) { let leaf = NodeType::Leaf(LeafNode::new(PartialPath(vec![1, 2, 3]), Data(vec![4, 5]))); let branch = NodeType::Branch(Box::new(BranchNode { - // path: vec![].into(), + path: vec![].into(), children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], value: Some(Data(vec![1, 2, 3])), children_encoded: std::array::from_fn(|_| Some(vec![1])), @@ -904,6 +975,7 @@ mod tests { } #[test_matrix( + [&[], &[0xf], &[0xf, 0xf]], [vec![], vec![1,0,0,0,0,0,0,1], vec![1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], repeat(1).take(16).collect()], [Nil, 0, 15], [ @@ -915,10 +987,13 @@ mod tests { ] )] fn branch_encoding( + path: &[u8], children: Vec, value: impl Into>, children_encoded: [Option>; BranchNode::MAX_CHILDREN], ) { + let path = PartialPath(path.iter().copied().map(|x| x & 0xf).collect()); + let mut children = children.into_iter().map(|x| { if x == 0 { None @@ -934,7 +1009,7 @@ mod tests { .map(|x| Data(std::iter::repeat(x).take(x as usize).collect())); let node = Node::from_branch(BranchNode { - // path: vec![].into(), + path, children, value, children_encoded, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 19b15116f4fc..53b8bac6e710 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -3,17 +3,19 @@ use super::{Data, Encoded, Node}; use crate::{ - merkle::{PartialPath, TRIE_HASH_LEN}, - shale::{DiskAddress, Storable}, - shale::{ShaleError, ShaleStore}, + merkle::{from_nibbles, to_nibble_array, PartialPath, TRIE_HASH_LEN}, + nibbles::Nibbles, + shale::{DiskAddress, ShaleError, ShaleStore, Storable}, }; use bincode::{Error, Options}; +use serde::de::Error as DeError; use std::{ fmt::{Debug, Error as FmtError, Formatter}, io::{Cursor, Read, Write}, mem::size_of, }; +type PathLen = u8; pub type DataLen = u32; pub type EncodedChildLen = u8; @@ -21,7 +23,7 @@ const MAX_CHILDREN: usize = 16; #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { - // pub(crate) path: PartialPath, + pub(crate) path: PartialPath, pub(crate) children: [Option; MAX_CHILDREN], pub(crate) value: Option, pub(crate) children_encoded: [Option>; MAX_CHILDREN], @@ -30,7 +32,7 @@ pub struct BranchNode { impl Debug for BranchNode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "[Branch")?; - // write!(f, " path={:?}", self.path)?; + write!(f, r#" path="{:?}""#, self.path)?; for (i, c) in self.children.iter().enumerate() { if let Some(c) = c { @@ -57,16 +59,16 @@ impl Debug for BranchNode { impl BranchNode { pub const MAX_CHILDREN: usize = MAX_CHILDREN; - pub const MSIZE: usize = Self::MAX_CHILDREN + 1; + pub const MSIZE: usize = Self::MAX_CHILDREN + 2; pub fn new( - _path: PartialPath, + path: PartialPath, chd: [Option; Self::MAX_CHILDREN], value: Option>, chd_encoded: [Option>; Self::MAX_CHILDREN], ) -> Self { BranchNode { - // path, + path, children: chd, value: value.map(Data), children_encoded: chd_encoded, @@ -112,6 +114,13 @@ impl BranchNode { pub(super) fn decode(buf: &[u8]) -> Result { let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + let path = items + .pop() + .ok_or(Error::custom("Invalid Branch Node"))? + .decode()?; + let path = Nibbles::<0>::new(&path); + let (path, _term) = PartialPath::from_nibbles(path.into_iter()); + // we've already validated the size, that's why we can safely unwrap #[allow(clippy::unwrap_used)] let data = items.pop().unwrap().decode()?; @@ -128,9 +137,6 @@ impl BranchNode { (chd_encoded[i] = Some(data).filter(|data| !data.is_empty())); } - // TODO: add path - let path = Vec::new().into(); - Ok(BranchNode::new( path, [None; Self::MAX_CHILDREN], @@ -140,8 +146,8 @@ impl BranchNode { } pub(super) fn encode>(&self, store: &S) -> Vec { - // TODO: add path to encoded node - let mut list = <[Encoded>; Self::MAX_CHILDREN + 1]>::default(); + // path + children + value + let mut list = <[Encoded>; Self::MSIZE]>::default(); for (i, c) in self.children.iter().enumerate() { match c { @@ -202,9 +208,17 @@ impl BranchNode { } #[allow(clippy::unwrap_used)] + let path = from_nibbles(&self.path.encode(false)).collect::>(); + + list[Self::MAX_CHILDREN + 1] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&path) + .expect("serializing raw bytes to always succeed"), + ); + bincode::DefaultOptions::new() .serialize(list.as_slice()) - .unwrap() + .expect("serializing `Encoded` to always succeed") } } @@ -215,13 +229,19 @@ impl Storable for BranchNode { let children_encoded_len = self.children_encoded.iter().fold(0, |len, child| { len + optional_data_len::(child.as_ref()) }); + let path_len_size = size_of::() as u64; + let path_len = self.path.serialized_len(); - children_len + data_len + children_encoded_len + children_len + data_len + children_encoded_len + path_len_size + path_len } fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); + let path: Vec = from_nibbles(&self.path.encode(false)).collect(); + cursor.write_all(&[path.len() as PathLen])?; + cursor.write_all(&path)?; + for child in &self.children { let bytes = child.map(|addr| addr.to_le_bytes()).unwrap_or_default(); cursor.write_all(&bytes)?; @@ -253,10 +273,42 @@ impl Storable for BranchNode { mut addr: usize, mem: &T, ) -> Result { + const PATH_LEN_SIZE: u64 = size_of::() as u64; const DATA_LEN_SIZE: usize = size_of::(); const BRANCH_HEADER_SIZE: u64 = BranchNode::MAX_CHILDREN as u64 * DiskAddress::MSIZE + DATA_LEN_SIZE as u64; + let path_len = mem + .get_view(addr, PATH_LEN_SIZE) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: PATH_LEN_SIZE, + })? + .as_deref(); + + addr += PATH_LEN_SIZE as usize; + + let path_len = { + let mut buf = [0u8; PATH_LEN_SIZE as usize]; + let mut cursor = Cursor::new(path_len); + cursor.read_exact(buf.as_mut())?; + + PathLen::from_le_bytes(buf) as u64 + }; + + let path = mem + .get_view(addr, path_len) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: path_len, + })? + .as_deref(); + + addr += path_len as usize; + + let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); + let path = PartialPath::decode(&path).0; + let node_raw = mem.get_view(addr, BRANCH_HEADER_SIZE) .ok_or(ShaleError::InvalidCacheView { @@ -342,8 +394,7 @@ impl Storable for BranchNode { } let node = BranchNode { - // TODO: add path - // path: Vec::new().into(), + path, children, value, children_encoded, diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs index 4646ea5ca643..1e1d5a74ce6f 100644 --- a/firewood/src/merkle/node/partial_path.rs +++ b/firewood/src/merkle/node/partial_path.rs @@ -40,7 +40,7 @@ impl PartialPath { self.0 } - pub(super) fn encode(&self, is_terminal: bool) -> Vec { + pub(crate) fn encode(&self, is_terminal: bool) -> Vec { let mut flags = Flags::empty(); if is_terminal { diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 02281a5ea9f5..4eaf0890be1a 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -142,7 +142,7 @@ impl + Send> Proof { } } - pub fn concat_proofs(&mut self, other: Proof) { + pub fn extend(&mut self, other: Proof) { self.0.extend(other.0) } @@ -356,6 +356,16 @@ impl + Send> Proof { } NodeType::Branch(n) => { + let paths_match = n + .path + .iter() + .copied() + .all(|nibble| Some(nibble) == key_nibbles.next()); + + if !paths_match { + break None; + } + if let Some(index) = key_nibbles.peek() { let subproof = n .chd_encode() @@ -440,6 +450,17 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } NodeType::Branch(n) => { + let partial_path = &n.path.0; + + let does_not_match = key_nibbles.size_hint().0 < partial_path.len() + || !partial_path + .iter() + .all(|val| key_nibbles.next() == Some(*val)); + + if does_not_match { + return Ok((None, Nibbles::<0>::new(&[]).into_iter())); + } + let Some(index) = key_nibbles.next().map(|nib| nib as usize) else { let encoded = n.value; @@ -512,13 +533,30 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; let mut parent = DiskAddress::null(); - let mut fork_left: Ordering = Ordering::Equal; - let mut fork_right: Ordering = Ordering::Equal; + let mut fork_left = Ordering::Equal; + let mut fork_right = Ordering::Equal; let mut index = 0; loop { match &u_ref.inner() { + #[allow(clippy::indexing_slicing)] NodeType::Branch(n) => { + // If either the key of left proof or right proof doesn't match with + // stop here, this is the forkpoint. + let path = &*n.path; + + if !path.is_empty() { + [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] + .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) + .map(|key| key.cmp(path)); + + if !fork_left.is_eq() || !fork_right.is_eq() { + break; + } + + index += path.len(); + } + // If either the node pointed by left proof or right proof is nil, // stop here and the forkpoint is the fullnode. #[allow(clippy::indexing_slicing)] @@ -571,6 +609,61 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe match &u_ref.inner() { NodeType::Branch(n) => { + if fork_left.is_lt() && fork_right.is_lt() { + return Err(ProofError::EmptyRange); + } + + if fork_left.is_gt() && fork_right.is_gt() { + return Err(ProofError::EmptyRange); + } + + if fork_left.is_ne() && fork_right.is_ne() { + // The fork point is root node, unset the entire trie + if parent.is_null() { + return Ok(true); + } + + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; + #[allow(clippy::unwrap_used)] + p_ref + .write(|p| { + let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); + #[allow(clippy::indexing_slicing)] + (pp.chd_mut()[left_chunks[index - 1] as usize] = None); + #[allow(clippy::indexing_slicing)] + (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); + }) + .unwrap(); + + return Ok(false); + } + + let p = u_ref.as_ptr(); + index += n.path.len(); + + // Only one proof points to non-existent key. + if fork_right.is_ne() { + #[allow(clippy::indexing_slicing)] + let left_node = n.chd()[left_chunks[index] as usize]; + + drop(u_ref); + #[allow(clippy::indexing_slicing)] + unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; + return Ok(false); + } + + if fork_left.is_ne() { + #[allow(clippy::indexing_slicing)] + let right_node = n.chd()[right_chunks[index] as usize]; + + drop(u_ref); + #[allow(clippy::indexing_slicing)] + unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; + return Ok(false); + }; + #[allow(clippy::indexing_slicing)] let left_node = n.chd()[left_chunks[index] as usize]; #[allow(clippy::indexing_slicing)] @@ -590,12 +683,14 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe .unwrap(); } - let p = u_ref.as_ptr(); drop(u_ref); + #[allow(clippy::indexing_slicing)] unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; + #[allow(clippy::indexing_slicing)] unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; + Ok(false) } @@ -606,9 +701,6 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe // - left proof is less and right proof is greater => valid range, unset the shortnode entirely // - left proof points to the shortnode, but right proof is greater // - right proof points to the shortnode, but left proof is less - let node = n.chd(); - let cur_key = n.path.clone().into_inner(); - if fork_left.is_lt() && fork_right.is_lt() { return Err(ProofError::EmptyRange); } @@ -640,6 +732,9 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe return Ok(false); } + let node = n.chd(); + let index = n.path.len(); + let p = u_ref.as_ptr(); drop(u_ref); @@ -651,7 +746,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe Some(node), #[allow(clippy::indexing_slicing)] &left_chunks[index..], - cur_key.len(), + index, false, )?; @@ -665,7 +760,7 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe Some(node), #[allow(clippy::indexing_slicing)] &right_chunks[index..], - cur_key.len(), + index, true, )?; @@ -752,27 +847,29 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe index: usize, remove_left: bool, ) -> Result<(), ProofError> { - if node.is_none() { + let Some(node) = node else { // If the node is nil, then it's a child of the fork point // fullnode(it's a non-existent branch). return Ok(()); - } + }; let mut chunks = Vec::new(); chunks.extend(key.as_ref()); #[allow(clippy::unwrap_used)] - let mut u_ref = merkle - .get_node(node.unwrap()) - .map_err(|_| ProofError::NoSuchNode)?; + let mut u_ref = merkle.get_node(node).map_err(|_| ProofError::NoSuchNode)?; let p = u_ref.as_ptr(); + if index >= chunks.len() { + return Err(ProofError::InvalidProof); + } + + #[allow(clippy::indexing_slicing)] match &u_ref.inner() { - NodeType::Branch(n) => { - #[allow(clippy::indexing_slicing)] + NodeType::Branch(n) if chunks[index..].starts_with(&n.path) => { + let index = index + n.path.len(); let child_index = chunks[index] as usize; - #[allow(clippy::indexing_slicing)] let node = n.chd()[child_index]; let iter = if remove_left { @@ -799,6 +896,52 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe unset_node_ref(merkle, p, node, key, index + 1, remove_left) } + NodeType::Branch(n) => { + let cur_key = &n.path; + + // Find the fork point, it's a non-existent branch. + // + // for (true, Ordering::Less) + // The key of fork shortnode is less than the path + // (it belongs to the range), unset the entire + // branch. The parent must be a fullnode. + // + // for (false, Ordering::Greater) + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. Otherwise the + // key is not part of the range and should remain in the + // cached hash. + #[allow(clippy::indexing_slicing)] + let should_unset_entire_branch = matches!( + (remove_left, cur_key.cmp(&chunks[index..])), + (true, Ordering::Less) | (false, Ordering::Greater) + ); + + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] + if should_unset_entire_branch { + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; + + p_ref + .write(|p| match p.inner_mut() { + NodeType::Branch(pp) => { + pp.chd_mut()[chunks[index - 1] as usize] = None; + pp.chd_encoded_mut()[chunks[index - 1] as usize] = None; + } + NodeType::Extension(n) => { + *n.chd_mut() = DiskAddress::null(); + *n.chd_encoded_mut() = None; + } + NodeType::Leaf(_) => (), + }) + .unwrap(); + } + + Ok(()) + } + #[allow(clippy::indexing_slicing)] NodeType::Extension(n) if chunks[index..].starts_with(&n.path) => { let node = Some(n.chd()); @@ -807,9 +950,6 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe NodeType::Extension(n) => { let cur_key = &n.path; - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; // Find the fork point, it's a non-existent branch. // @@ -832,6 +972,10 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe #[allow(clippy::indexing_slicing, clippy::unwrap_used)] if should_unset_entire_branch { + let mut p_ref = merkle + .get_node(parent) + .map_err(|_| ProofError::NoSuchNode)?; + p_ref .write(|p| { let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 773b1a51995b..6cd2c7f2a2c1 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -3,6 +3,7 @@ use super::{node::Node, BranchNode, Merkle, NodeObjRef, NodeType}; use crate::{ + nibbles::Nibbles, shale::{DiskAddress, ShaleStore}, v2::api, }; @@ -18,6 +19,7 @@ enum IteratorState<'a> { StartAtKey(Key), /// Continue iterating after the last node in the `visited_node_path` Iterating { + check_child_nibble: bool, visited_node_path: Vec<(NodeObjRef<'a>, u8)>, }, } @@ -41,7 +43,7 @@ pub struct MerkleKeyValueStream<'a, S, T> { impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleKeyValueStream<'a, S, T> { fn is_terminated(&self) -> bool { - matches!(&self.key_state, IteratorState::Iterating { visited_node_path } if visited_node_path.is_empty()) + matches!(&self.key_state, IteratorState::Iterating { visited_node_path, .. } if visited_node_path.is_empty()) } } @@ -88,6 +90,8 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' .get_node(*merkle_root) .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let mut check_child_nibble = false; + // traverse the trie along each nibble until we find a node with a value // TODO: merkle.iter_by_key(key) will simplify this entire code-block. let (found_node, mut visited_node_path) = { @@ -97,14 +101,51 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' .get_node_by_key_with_callbacks( root_node, &key, - |node_addr, i| visited_node_path.push((node_addr, i)), + |node_addr, _| visited_node_path.push(node_addr), |_, _| {}, ) .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let mut nibbles = Nibbles::<1>::new(key).into_iter(); + let visited_node_path = visited_node_path .into_iter() - .map(|(node, pos)| merkle.get_node(node).map(|node| (node, pos))) + .map(|node| merkle.get_node(node)) + .map(|node_result| { + let nibbles = &mut nibbles; + + node_result + .map(|node| match node.inner() { + NodeType::Branch(branch) => { + let mut partial_path_iter = branch.path.iter(); + let next_nibble = nibbles + .map(|nibble| (Some(nibble), partial_path_iter.next())) + .find(|(a, b)| a.as_ref() != *b); + + match next_nibble { + // this case will be hit by all but the last nodes + // unless there is a deviation between the key and the path + None | Some((None, _)) => None, + + Some((Some(key_nibble), Some(path_nibble))) => { + check_child_nibble = key_nibble < *path_nibble; + None + } + + // path is subset of the key + Some((Some(nibble), None)) => { + check_child_nibble = true; + Some((node, nibble)) + } + } + } + NodeType::Leaf(_) => Some((node, 0)), + NodeType::Extension(_) => Some((node, 0)), + }) + .transpose() + }) + .take_while(|node| node.is_some()) + .flatten() .collect::, _>>() .map_err(|e| api::Error::InternalError(Box::new(e)))?; @@ -113,7 +154,10 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' if let Some(found_node) = found_node { let value = match found_node.inner() { - NodeType::Branch(branch) => branch.value.as_ref(), + NodeType::Branch(branch) => { + check_child_nibble = true; + branch.value.as_ref() + } NodeType::Leaf(leaf) => Some(&leaf.data), NodeType::Extension(_) => None, }; @@ -126,7 +170,10 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' visited_node_path.push((found_node, 0)); - self.key_state = IteratorState::Iterating { visited_node_path }; + self.key_state = IteratorState::Iterating { + check_child_nibble, + visited_node_path, + }; return Poll::Ready(next_result); } @@ -135,16 +182,23 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' let found_key = key_from_nibble_iter(found_key); if found_key > *key { + check_child_nibble = false; visited_node_path.pop(); } - self.key_state = IteratorState::Iterating { visited_node_path }; + self.key_state = IteratorState::Iterating { + check_child_nibble, + visited_node_path, + }; self.poll_next(_cx) } - IteratorState::Iterating { visited_node_path } => { - let next = find_next_result(merkle, visited_node_path) + IteratorState::Iterating { + check_child_nibble, + visited_node_path, + } => { + let next = find_next_result(merkle, visited_node_path, check_child_nibble) .map_err(|e| api::Error::InternalError(Box::new(e))) .transpose(); @@ -184,20 +238,27 @@ impl<'a> NodeRef<'a> { fn find_next_result<'a, S: ShaleStore, T>( merkle: &'a Merkle, visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, + check_child_nibble: &mut bool, ) -> Result, super::MerkleError> { - let next = find_next_node_with_data(merkle, visited_path)?.map(|(next_node, value)| { - let partial_path = match next_node.inner() { - NodeType::Leaf(leaf) => leaf.path.iter().copied(), - NodeType::Extension(extension) => extension.path.iter().copied(), - _ => [].iter().copied(), - }; + let next = find_next_node_with_data(merkle, visited_path, *check_child_nibble)?.map( + |(next_node, value)| { + let partial_path = match next_node.inner() { + NodeType::Leaf(leaf) => leaf.path.iter().copied(), + NodeType::Extension(extension) => extension.path.iter().copied(), + NodeType::Branch(branch) => branch.path.iter().copied(), + }; - let key = key_from_nibble_iter(nibble_iter_from_parents(visited_path).chain(partial_path)); + // always check the child for branch nodes with data + *check_child_nibble = next_node.inner().is_branch(); - visited_path.push((next_node, 0)); + let key = + key_from_nibble_iter(nibble_iter_from_parents(visited_path).chain(partial_path)); - (key, value) - }); + visited_path.push((next_node, 0)); + + (key, value) + }, + ); Ok(next) } @@ -205,6 +266,7 @@ fn find_next_result<'a, S: ShaleStore, T>( fn find_next_node_with_data<'a, S: ShaleStore, T>( merkle: &'a Merkle, visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, + check_child_nibble: bool, ) -> Result, Vec)>, super::MerkleError> { use InnerNode::*; @@ -244,7 +306,7 @@ fn find_next_node_with_data<'a, S: ShaleStore, T>( Visited(NodeType::Branch(branch)) => { // if the first node that we check is a visited branch, that means that the branch had a value // and we need to visit the first child, for all other cases, we need to visit the next child - let compare_op = if first_loop { + let compare_op = if first_loop && check_child_nibble { ::ge // >= } else { ::gt @@ -328,7 +390,13 @@ fn nibble_iter_from_parents<'a>(parents: &'a [(NodeObjRef, u8)]) -> impl Iterato .iter() .skip(1) // always skip the sentinal node .flat_map(|(parent, child_nibble)| match parent.inner() { - NodeType::Branch(_) => Either::Left(std::iter::once(*child_nibble)), + NodeType::Branch(branch) => Either::Left( + branch + .path + .iter() + .copied() + .chain(std::iter::once(*child_nibble)), + ), NodeType::Extension(extension) => Either::Right(extension.path.iter().copied()), NodeType::Leaf(leaf) => Either::Right(leaf.path.iter().copied()), }) @@ -711,6 +779,56 @@ mod tests { check_stream_is_done(stream).await; } + #[tokio::test] + async fn start_at_key_overlapping_with_extension_but_greater() { + let start_key = 0x0a; + let shared_path = 0x09; + // 0x0900, 0x0901, ... 0x0a0f + // path extension is 0x090 + let children = (0..=0x0f).map(|val| vec![shared_path, val]); + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + children.for_each(|key| { + merkle.insert(&key, key.clone(), root).unwrap(); + }); + + let stream = merkle.iter_from(root, vec![start_key].into_boxed_slice()); + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn start_at_key_overlapping_with_extension_but_smaller() { + let start_key = 0x00; + let shared_path = 0x09; + // 0x0900, 0x0901, ... 0x0a0f + // path extension is 0x090 + let children = (0..=0x0f).map(|val| vec![shared_path, val]); + + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + let keys: Vec<_> = children + .map(|key| { + merkle.insert(&key, key.clone(), root).unwrap(); + key + }) + .collect(); + + let mut stream = merkle.iter_from(root, vec![start_key].into_boxed_slice()); + + for key in keys { + let next = stream.next().await.unwrap().unwrap(); + + assert_eq!(&*next.0, &*next.1); + assert_eq!(&*next.0, key); + } + + check_stream_is_done(stream).await; + } + #[tokio::test] async fn start_at_key_between_siblings() { let missing = 0xaa; diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 328ae03cdb60..fd53908be8e5 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -1,10 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::{BinarySerde, Bincode, Merkle, Node, Proof, ProofError, Ref, RefMut, TrieHash}; -use crate::shale::{ - self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, - ShaleStore, StoredView, +use crate::{ + merkle::{ + proof::{Proof, ProofError}, + BinarySerde, Bincode, Merkle, Node, Ref, RefMut, TrieHash, + }, + shale::{ + self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, + ShaleStore, StoredView, + }, }; use std::num::NonZeroUsize; use thiserror::Error; diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 9b0f4b148a9a..15306bceb954 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::logger::trace; use crate::merkle::Node; use crate::shale::ObjCache; use crate::storage::{StoreRevMut, StoreRevShared}; @@ -13,17 +14,18 @@ use std::io::{Cursor, Write}; use std::num::NonZeroUsize; use std::sync::RwLock; -use crate::logger::trace; +type PayLoadSize = u64; #[derive(Debug)] pub struct CompactHeader { - payload_size: u64, + payload_size: PayLoadSize, is_freed: bool, desc_addr: DiskAddress, } impl CompactHeader { pub const MSIZE: u64 = 17; + pub const fn is_freed(&self) -> bool { self.is_freed } @@ -71,11 +73,11 @@ impl Storable for CompactHeader { #[derive(Debug)] struct CompactFooter { - payload_size: u64, + payload_size: PayLoadSize, } impl CompactFooter { - const MSIZE: u64 = 8; + const MSIZE: u64 = std::mem::size_of::() as u64; } impl Storable for CompactFooter { @@ -103,7 +105,7 @@ impl Storable for CompactFooter { #[derive(Clone, Copy, Debug)] struct CompactDescriptor { - payload_size: u64, + payload_size: PayLoadSize, haddr: usize, // disk address of the free space } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index fc519d9b5187..81fbd3b1e224 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -318,6 +318,7 @@ impl StoredView { #[inline(always)] fn new(offset: usize, len_limit: u64, space: &U) -> Result { let decoded = T::deserialize(offset, space)?; + Ok(Self { offset, decoded, diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 62b1ad00665f..34f314c676a6 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -8,17 +8,19 @@ use firewood::{ shale::{cached::DynamicMem, compact::CompactSpace}, }; use rand::Rng; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Write}; type Store = CompactSpace; -fn merkle_build_test + std::cmp::Ord + Clone, V: AsRef<[u8]> + Clone>( +fn merkle_build_test< + K: AsRef<[u8]> + std::cmp::Ord + Clone + std::fmt::Debug, + V: AsRef<[u8]> + Clone, +>( items: Vec<(K, V)>, meta_size: u64, compact_size: u64, ) -> Result, DataStoreError> { let mut merkle = new_merkle(meta_size, compact_size); - for (k, v) in items.iter() { merkle.insert(k, v.as_ref().to_vec())?; } @@ -66,10 +68,12 @@ fn test_root_hash_fuzz_insertions() -> Result<(), DataStoreError> { for _ in 0..10 { let mut items = Vec::new(); + for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.push((keygen(), val)); } + merkle_build_test(items, 0x1000000, 0x1000000)?; } @@ -97,54 +101,51 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { .collect(); key }; - for i in 0..10 { - let mut items = std::collections::HashMap::new(); - for _ in 0..10 { - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - items.insert(keygen(), val); - } - let mut items: Vec<_> = items.into_iter().collect(); + + for _ in 0..10 { + let mut items: Vec<_> = (0..10) + .map(|_| keygen()) + .map(|key| { + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + (key, val) + }) + .collect(); + items.sort(); + let mut merkle = new_merkle(0x100000, 0x100000); + let mut hashes = Vec::new(); - let mut dumps = Vec::new(); + for (k, v) in items.iter() { - dumps.push(merkle.dump()); + hashes.push((merkle.root_hash()?, merkle.dump()?)); merkle.insert(k, v.to_vec())?; - hashes.push(merkle.root_hash()); } - hashes.pop(); - println!("----"); - let mut prev_dump = merkle.dump()?; - for (((k, _), h), d) in items - .iter() - .rev() - .zip(hashes.iter().rev()) - .zip(dumps.iter().rev()) - { + + let mut new_hashes = Vec::new(); + + for (k, _) in items.iter().rev() { + let before = merkle.dump()?; merkle.remove(k)?; - let h0 = merkle.root_hash()?.0; - if h.as_ref().unwrap().0 != h0 { - for (k, _) in items.iter() { - println!("{}", hex::encode(k)); - } - println!( - "{} != {}", - hex::encode(**h.as_ref().unwrap()), - hex::encode(h0) - ); - println!("== before {} ===", hex::encode(k)); - print!("{prev_dump}"); - println!("== after {} ===", hex::encode(k)); - print!("{}", merkle.dump()?); - println!("== should be ==="); - print!("{:?}", d); - panic!(); - } - prev_dump = merkle.dump()?; + new_hashes.push((merkle.root_hash()?, k, before, merkle.dump()?)); + } + + hashes.reverse(); + + for i in 0..hashes.len() { + #[allow(clippy::indexing_slicing)] + let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; + #[allow(clippy::indexing_slicing)] + let (expected_hash, expected_dump) = &hashes[i]; + let key = key.iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "{:02x}", b); + s + }); + + assert_eq!(new_hash, expected_hash, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_dump}\n"); } - println!("i = {i}"); } + Ok(()) } @@ -169,38 +170,56 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { .collect(); key }; + for i in 0..10 { let mut items = std::collections::HashMap::new(); + for _ in 0..10 { let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); items.insert(keygen(), val); } + let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); items_ordered.sort(); items_ordered.shuffle(&mut *rng.borrow_mut()); let mut merkle = new_merkle(0x100000, 0x100000); + for (k, v) in items.iter() { merkle.insert(k, v.to_vec())?; } + + for (k, v) in items.iter() { + assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); + assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); + } + for (k, _) in items_ordered.into_iter() { assert!(merkle.get(&k)?.is_some()); assert!(merkle.get_mut(&k)?.is_some()); + merkle.remove(&k)?; + assert!(merkle.get(&k)?.is_none()); assert!(merkle.get_mut(&k)?.is_none()); + items.remove(&k); + for (k, v) in items.iter() { assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); } + let h = triehash::trie_root::, _, _>( items.iter().collect(), ); + let h0 = merkle.root_hash()?; + if h[..] != *h0 { println!("{} != {}", hex::encode(h), hex::encode(*h0)); } } + println!("i = {i}"); } Ok(()) @@ -343,7 +362,7 @@ fn test_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let mut keys = Vec::new(); let mut vals = Vec::new(); @@ -379,7 +398,7 @@ fn test_bad_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); @@ -476,7 +495,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let mut keys: Vec<[u8; 32]> = Vec::new(); let mut vals: Vec<[u8; 20]> = Vec::new(); @@ -495,7 +514,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; @@ -523,7 +542,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); start = 105; // Gap created let mut keys: Vec<[u8; 32]> = Vec::new(); @@ -546,7 +565,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); end = 195; // Capped slice let mut keys: Vec<[u8; 32]> = Vec::new(); @@ -593,7 +612,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[start].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof( &proof, @@ -609,7 +628,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof( &proof, @@ -624,7 +643,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof( &proof, @@ -644,7 +663,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(key)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof(&proof, first, key, vec![key], vec![val])?; @@ -683,7 +702,7 @@ fn test_all_elements_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof( &proof, @@ -700,7 +719,7 @@ fn test_all_elements_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; @@ -762,7 +781,7 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[last - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let middle = (first + last) / 2 - first; let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 4]>) = items[first..last] @@ -797,7 +816,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); assert!(merkle .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) @@ -811,7 +830,7 @@ fn test_same_side_proof() -> Result<(), DataStoreError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(last)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); assert!(merkle .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) @@ -842,7 +861,7 @@ fn test_single_side_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[case].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let item_iter = items.clone().into_iter().take(case + 1); let keys = item_iter.clone().map(|item| *item.0).collect(); @@ -876,7 +895,7 @@ fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(end)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let item_iter = items.clone().into_iter().skip(case); let keys = item_iter.clone().map(|item| item.0).collect(); @@ -909,7 +928,7 @@ fn test_both_sides_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(end)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); merkle.verify_range_proof(&proof, &start, &end, keys, vals)?; @@ -941,7 +960,7 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end - 1].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let item_iter = items.clone().into_iter().skip(start).take(end - start); let keys = item_iter.clone().map(|item| item.0).collect(); @@ -977,7 +996,7 @@ fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(items[end].0)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let item_iter = items.clone().into_iter(); let keys = item_iter.clone().map(|item| item.0).collect(); @@ -1014,7 +1033,7 @@ fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { assert!(!proof.0.is_empty()); let end_proof = merkle.prove(&end)?; assert!(!end_proof.0.is_empty()); - proof.concat_proofs(end_proof); + proof.extend(end_proof); let item_iter = items.into_iter(); let keys = item_iter.clone().map(|item| item.0).collect(); @@ -1051,7 +1070,7 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { for (i, item) in items.iter().enumerate() { let cur_proof = merkle.prove(item.0)?; assert!(!cur_proof.0.is_empty()); - proof.concat_proofs(cur_proof); + proof.extend(cur_proof); if i == 50 { keys.push(item.0); vals.push(item.1); From c5630888e40c033c087613023c4834212fa05466 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 5 Feb 2024 10:00:39 -0800 Subject: [PATCH 0457/1053] Remove tracing subscriber (#515) --- grpc-testtool/Cargo.toml | 1 - grpc-testtool/src/bin/process-server.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 5089989c0428..2aab4de19b9a 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -22,7 +22,6 @@ thiserror = "1.0.47" tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.10.0", features = ["tls"] } tracing = { version = "0.1.16" } -tracing-subscriber = { version = "0.3", features = ["tracing-log", "fmt", "env-filter"] } clap = { version = "4.4.11", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index 1e55c4a975ef..9f12043eec27 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -84,7 +84,7 @@ async fn main() -> Result<(), Box> { .filter(None, args.log_level) .init(); - tracing_subscriber::fmt::init(); + // tracing_subscriber::fmt::init(); // log to the file and to stderr info!("Starting up: Listening on {}", args.grpc_port); From 5e8bfb6c48278a3df557d6348788fc3ce93ca8bc Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 7 Feb 2024 12:26:32 -0800 Subject: [PATCH 0458/1053] Add an upgrade check to CI (#519) --- .github/dependabot.yml | 9 +++++---- .github/workflows/ci.yaml | 6 +++++- firewood/Cargo.toml | 28 ++++++++++++++-------------- fwdctl/Cargo.toml | 16 ++++++++-------- growth-ring/Cargo.toml | 22 +++++++++++----------- grpc-testtool/Cargo.toml | 22 +++++++++++----------- libaio/Cargo.toml | 6 +++--- 7 files changed, 57 insertions(+), 52 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cc945b270cea..e12ea7338987 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,12 +5,13 @@ version: 2 updates: - - package-ecosystem: cargo + - package-ecosystem: "cargo" + versioning-strategy: "increase" directory: "/" schedule: - interval: daily + interval: "daily" time: "05:00" - timezone: America/Los_Angeles + timezone: "America/Los_Angeles" open-pull-requests-limit: 10 allow: - - dependency-type: all + - dependency-type: "all" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5bb909de956c..565d849e9476 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - # We can do this now because we use specific verison and update with Dependabot + # We can do this now because we use specific version and update with Dependabot # but if we make the deps any less specifc, we'll have to fix key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} # start from the previous set of cached dependencies @@ -101,6 +101,10 @@ jobs: run: cargo fmt -- --check - name: Clippy run: cargo clippy --tests --examples --benches --all-features -- -D warnings + - name: Cargo Edit + run: cargo install cargo-edit + - name: Cargo upgrade + run: cargo upgrade --locked test: needs: build diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8159de00886d..24b40d6605f2 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -17,25 +17,25 @@ readme = "../README.md" [dependencies] aquamarine = "0.5.0" -async-trait = "0.1.57" -bytemuck = { version = "1.13.1", features = ["derive"] } +async-trait = "0.1.77" +bytemuck = { version = "1.14.2", features = ["derive"] } enum-as-inner = "0.6.0" growth-ring = { version = "0.0.4", path = "../growth-ring" } libaio = {version = "0.0.4", path = "../libaio" } -futures = "0.3.24" +futures = "0.3.30" hex = "0.4.3" -lru = "0.12.0" +lru = "0.12.2" metered = "0.9.0" nix = {version = "0.27.1", features = ["fs", "uio"]} parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } -sha3 = "0.10.2" -thiserror = "1.0.38" -tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } -typed-builder = "0.18.0" +sha3 = "0.10.8" +thiserror = "1.0.56" +tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } +typed-builder = "0.18.1" bincode = "1.3.3" -bitflags = { version = "2.4.1", features = ["bytemuck"] } -env_logger = { version = "0.11.0", optional = true } +bitflags = { version = "2.4.2", features = ["bytemuck"] } +env_logger = { version = "0.11.1", optional = true } log = { version = "0.4.20", optional = true } [features] @@ -46,10 +46,10 @@ criterion = {version = "0.5.1", features = ["async_tokio"]} keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" -assert_cmd = "2.0.7" -predicates = "3.0.1" -clap = { version = "4.3.1", features = ['derive'] } -test-case = "3.1.0" +assert_cmd = "2.0.13" +predicates = "3.1.0" +clap = { version = "4.4.18", features = ['derive'] } +test-case = "3.3.1" pprof = { version = "0.13.0", features = ["flamegraph"] } [[bench]] diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 195651e62301..fea4992f56e3 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -5,16 +5,16 @@ edition = "2021" [dependencies] firewood = { version = "0.0.4", path = "../firewood" } -clap = { version = "4.0.29", features = ["cargo", "derive"] } -anyhow = "1.0.66" -env_logger = "0.11.0" -log = "0.4.17" -tokio = { version = "1.33.0", features = ["full"] } -futures-util = "0.3.29" +clap = { version = "4.4.18", features = ["cargo", "derive"] } +anyhow = "1.0.79" +env_logger = "0.11.1" +log = "0.4.20" +tokio = { version = "1.36.0", features = ["full"] } +futures-util = "0.3.30" [dev-dependencies] -assert_cmd = "2.0.7" -predicates = "3.0.1" +assert_cmd = "2.0.13" +predicates = "3.1.0" serial_test = "3.0.0" [lints.rust] diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index e9becab2ae19..1907784d8666 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -9,24 +9,24 @@ description = "Simple and modular write-ahead-logging implementation." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lru = "0.12.0" +lru = "0.12.2" scan_fmt = "0.2.6" -regex = "1.6.0" -async-trait = "0.1.57" -futures = "0.3.24" +regex = "1.10.3" +async-trait = "0.1.77" +futures = "0.3.30" nix = {version = "0.27.1", features = ["fs", "uio"]} -libc = "0.2.133" -bytemuck = {version = "1.13.1", features = ["derive"]} -thiserror = "1.0.40" -tokio = { version = "1.28.1", features = ["fs", "io-util", "sync"] } +libc = "0.2.153" +bytemuck = {version = "1.14.2", features = ["derive"]} +thiserror = "1.0.56" +tokio = { version = "1.36.0", features = ["fs", "io-util", "sync"] } crc32fast = "1.3.2" [dev-dependencies] hex = "0.4.3" rand = "0.8.5" -indexmap = "2.2.1" -tokio = { version = "1.28.1", features = ["tokio-macros", "rt", "macros"] } -test-case = "3.1.0" +indexmap = "2.2.2" +tokio = { version = "1.36.0", features = ["tokio-macros", "rt", "macros"] } +test-case = "3.3.1" [lib] name = "growthring" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 2aab4de19b9a..4249a819d3d4 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,21 +17,21 @@ bench = false [dependencies] firewood = { version = "0.0.4", path = "../firewood" } -prost = "0.12.0" -thiserror = "1.0.47" -tokio = { version = "1.32.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.10.0", features = ["tls"] } -tracing = { version = "0.1.16" } -clap = { version = "4.4.11", features = ["derive"] } +prost = "0.12.3" +thiserror = "1.0.56" +tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } +tonic = { version = "0.10.2", features = ["tls"] } +tracing = { version = "0.1.40" } +clap = { version = "4.4.18", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" -env_logger = "0.11.0" -chrono = "0.4.31" -serde_json = "1.0.108" -serde = { version = "1.0.193", features = ["derive"] } +env_logger = "0.11.1" +chrono = "0.4.33" +serde_json = "1.0.113" +serde = { version = "1.0.196", features = ["derive"] } [build-dependencies] -tonic-build = "0.10.0" +tonic-build = "0.10.2" [lints.rust] unsafe_code = "deny" diff --git a/libaio/Cargo.toml b/libaio/Cargo.toml index cce43b716754..181c4cd89218 100644 --- a/libaio/Cargo.toml +++ b/libaio/Cargo.toml @@ -10,12 +10,12 @@ description = "Straightforward Linux AIO using Futures/async/await." emulated-failure = [] [dependencies] -libc = "0.2.133" +libc = "0.2.153" parking_lot = "0.12.1" -crossbeam-channel = "0.5.6" +crossbeam-channel = "0.5.11" [dev-dependencies] -futures = "0.3.24" +futures = "0.3.30" [lib] name = "aiofut" From 47d94e393e8426eac1bc901033a66346fd2b3e74 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 9 Feb 2024 10:54:50 -0800 Subject: [PATCH 0459/1053] Make upgrade check a separate step (#524) --- .github/workflows/ci.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 565d849e9476..98dc6ad51e79 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -101,6 +101,25 @@ jobs: run: cargo fmt -- --check - name: Clippy run: cargo clippy --tests --examples --benches --all-features -- -D warnings + + upgrades: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Restore Check Deps + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ needs.build.outputs.cache-key }} + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable - name: Cargo Edit run: cargo install cargo-edit - name: Cargo upgrade From 42fdc837a0ddf0e9e03fa99fbd20b9e9bc2ee3c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:19:36 -0800 Subject: [PATCH 0460/1053] build(deps): update tonic-build requirement from 0.10.2 to 0.11.0 (#522) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- grpc-testtool/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 4249a819d3d4..e14c73bb23e8 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -31,7 +31,7 @@ serde_json = "1.0.113" serde = { version = "1.0.196", features = ["derive"] } [build-dependencies] -tonic-build = "0.10.2" +tonic-build = "0.11.0" [lints.rust] unsafe_code = "deny" From 616b0005d00901ddb15b325df8ce3491ff9bd367 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:31:59 -0800 Subject: [PATCH 0461/1053] build(deps): update tonic requirement from 0.10.2 to 0.11.0 (#523) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- grpc-testtool/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index e14c73bb23e8..752761c68097 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -20,7 +20,7 @@ firewood = { version = "0.0.4", path = "../firewood" } prost = "0.12.3" thiserror = "1.0.56" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.10.2", features = ["tls"] } +tonic = { version = "0.11.0", features = ["tls"] } tracing = { version = "0.1.40" } clap = { version = "4.4.18", features = ["derive"] } tempdir = "0.3.7" From 6d663a7d2873c78e601eb84017839366be25bb90 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 9 Feb 2024 15:36:55 -0500 Subject: [PATCH 0462/1053] update clap dependency (#521) Co-authored-by: Ron Kuris --- firewood/Cargo.toml | 2 +- fwdctl/Cargo.toml | 2 +- grpc-testtool/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 24b40d6605f2..137dd388cbd0 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -48,7 +48,7 @@ rand = "0.8.5" triehash = "0.8.4" assert_cmd = "2.0.13" predicates = "3.1.0" -clap = { version = "4.4.18", features = ['derive'] } +clap = { version = "4.5.0", features = ['derive'] } test-case = "3.3.1" pprof = { version = "0.13.0", features = ["flamegraph"] } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index fea4992f56e3..365d68b95349 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] firewood = { version = "0.0.4", path = "../firewood" } -clap = { version = "4.4.18", features = ["cargo", "derive"] } +clap = { version = "4.5.0", features = ["cargo", "derive"] } anyhow = "1.0.79" env_logger = "0.11.1" log = "0.4.20" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 752761c68097..6ebbbf022cce 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -22,7 +22,7 @@ thiserror = "1.0.56" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.11.0", features = ["tls"] } tracing = { version = "0.1.40" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.0", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" env_logger = "0.11.1" From 51dcd432daa6b84c9ceb3b4a448c560767061f3e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 12 Feb 2024 07:03:36 -0800 Subject: [PATCH 0463/1053] Remove versioning-strategy: increase (#525) --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e12ea7338987..df3af69a5c14 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,6 @@ version: 2 updates: - package-ecosystem: "cargo" - versioning-strategy: "increase" directory: "/" schedule: interval: "daily" From 9bbe19b2e54a7cf3298ad3788732319cbf480d58 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 12 Feb 2024 18:39:20 -0500 Subject: [PATCH 0464/1053] update deps (#526) Co-authored-by: Richard Pringle --- firewood/Cargo.toml | 2 +- growth-ring/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 137dd388cbd0..d34ee1ce7492 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -18,7 +18,7 @@ readme = "../README.md" [dependencies] aquamarine = "0.5.0" async-trait = "0.1.77" -bytemuck = { version = "1.14.2", features = ["derive"] } +bytemuck = { version = "1.14.3", features = ["derive"] } enum-as-inner = "0.6.0" growth-ring = { version = "0.0.4", path = "../growth-ring" } libaio = {version = "0.0.4", path = "../libaio" } diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 1907784d8666..9cec56446d06 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -16,7 +16,7 @@ async-trait = "0.1.77" futures = "0.3.30" nix = {version = "0.27.1", features = ["fs", "uio"]} libc = "0.2.153" -bytemuck = {version = "1.14.2", features = ["derive"]} +bytemuck = {version = "1.14.3", features = ["derive"]} thiserror = "1.0.56" tokio = { version = "1.36.0", features = ["fs", "io-util", "sync"] } crc32fast = "1.3.2" From 94155c2a2be037c8b846022eed0b1ebca0587e06 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 13 Feb 2024 08:52:05 -0500 Subject: [PATCH 0465/1053] Implement iterator over nodes; refactor key-value iterator (#517) Co-authored-by: Ron Kuris --- firewood/src/db.rs | 5 +- firewood/src/merkle.rs | 10 +- firewood/src/merkle/stream.rs | 1035 ++++++++++++++++++++------------- firewood/src/v2/api.rs | 8 + 4 files changed, 640 insertions(+), 418 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 013f24c8c23d..b278b564a341 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -320,14 +320,15 @@ impl + Send + Sync> api::DbView for DbRev { impl + Send + Sync> DbRev { pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { - self.merkle.iter(self.header.kv_root) + self.merkle.key_value_iter(self.header.kv_root) } pub fn stream_from( &self, start_key: Box<[u8]>, ) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { - self.merkle.iter_from(self.header.kv_root, start_key) + self.merkle + .key_value_iter_from_key(self.header.kv_root, start_key) } fn flush_dirty(&mut self) -> Option<()> { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 25a9aea33eb3..56f89a9a0ee0 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1715,11 +1715,11 @@ impl + Send + Sync, T> Merkle { self.store.flush_dirty() } - pub(crate) fn iter(&self, root: DiskAddress) -> MerkleKeyValueStream<'_, S, T> { + pub(crate) fn key_value_iter(&self, root: DiskAddress) -> MerkleKeyValueStream<'_, S, T> { MerkleKeyValueStream::new(self, root) } - pub(crate) fn iter_from( + pub(crate) fn key_value_iter_from_key( &self, root: DiskAddress, key: Box<[u8]>, @@ -1750,8 +1750,10 @@ impl + Send + Sync, T> Merkle { let mut stream = match first_key { // TODO: fix the call-site to force the caller to do the allocation - Some(key) => self.iter_from(root, key.as_ref().to_vec().into_boxed_slice()), - None => self.iter(root), + Some(key) => { + self.key_value_iter_from_key(root, key.as_ref().to_vec().into_boxed_slice()) + } + None => self.key_value_iter(root), }; // fetch the first key from the stream diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 6cd2c7f2a2c1..e035ef69bc9f 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -7,399 +7,434 @@ use crate::{ shale::{DiskAddress, ShaleStore}, v2::api, }; -use futures::{stream::FusedStream, Stream}; -use helper_types::{Either, MustUse}; +use futures::{stream::FusedStream, Stream, StreamExt}; use std::task::Poll; +use std::{cmp::Ordering, iter::once}; type Key = Box<[u8]>; type Value = Vec; -enum IteratorState<'a> { - /// Start iterating at the specified key - StartAtKey(Key), - /// Continue iterating after the last node in the `visited_node_path` - Iterating { - check_child_nibble: bool, - visited_node_path: Vec<(NodeObjRef<'a>, u8)>, +/// Represents an ongoing iteration over a node and its children. +enum IterationNode<'a> { + /// This node has not been returned yet. + Unvisited { + /// The key (as nibbles) of this node. + key: Key, + node: NodeObjRef<'a>, + }, + /// This node has been returned. Track which child to visit next. + Visited { + /// The key (as nibbles) of this node. + key: Key, + /// Returns the non-empty children of this node and their positions + /// in the node's children array. + children_iter: Box + Send>, }, } -impl IteratorState<'_> { - fn new() -> Self { - Self::StartAtKey(vec![].into_boxed_slice()) - } +enum NodeStreamState<'a> { + /// The iterator state is lazily initialized when poll_next is called + /// for the first time. The iteration start key is stored here. + StartFromKey(Key), + Iterating { + /// Each element is a node that will be visited (i.e. returned) + /// or has been visited but has unvisited children. + /// On each call to poll_next we pop the next element. + /// If it's unvisited, we visit it. + /// If it's visited, we push its next child onto this stack. + iter_stack: Vec>, + }, +} - fn with_key(key: Key) -> Self { - Self::StartAtKey(key) +impl NodeStreamState<'_> { + fn new(key: Key) -> Self { + Self::StartFromKey(key) } } -/// A MerkleKeyValueStream iterates over keys/values for a merkle trie. -pub struct MerkleKeyValueStream<'a, S, T> { - key_state: IteratorState<'a>, +pub struct MerkleNodeStream<'a, S, T> { + state: NodeStreamState<'a>, merkle_root: DiskAddress, merkle: &'a Merkle, } -impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleKeyValueStream<'a, S, T> { +impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleNodeStream<'a, S, T> { fn is_terminated(&self) -> bool { - matches!(&self.key_state, IteratorState::Iterating { visited_node_path, .. } if visited_node_path.is_empty()) + // The top of `iter_stack` is the next node to return. + // If `iter_stack` is empty, there are no more nodes to visit. + matches!(&self.state, NodeStreamState::Iterating { iter_stack } if iter_stack.is_empty()) } } -impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { - pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress) -> Self { - let key_state = IteratorState::new(); - +impl<'a, S, T> MerkleNodeStream<'a, S, T> { + /// Returns a new iterator that will iterate over all the nodes in `merkle` + /// with keys greater than or equal to `key`. + pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { Self { - merkle, - key_state, + state: NodeStreamState::new(key), merkle_root, - } - } - - pub(super) fn from_key(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { - let key_state = IteratorState::with_key(key); - - Self { merkle, - key_state, - merkle_root, } } } -impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<'a, S, T> { - type Item = Result<(Key, Value), api::Error>; +impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S, T> { + type Item = Result<(Key, NodeObjRef<'a>), api::Error>; fn poll_next( mut self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, ) -> Poll> { - // destructuring is necessary here because we need mutable access to `key_state` - // at the same time as immutable access to `merkle` + // destructuring is necessary here because we need mutable access to `state` + // at the same time as immutable access to `merkle`. let Self { - key_state, + state, merkle_root, merkle, } = &mut *self; - match key_state { - IteratorState::StartAtKey(key) => { - let root_node = merkle - .get_node(*merkle_root) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let mut check_child_nibble = false; - - // traverse the trie along each nibble until we find a node with a value - // TODO: merkle.iter_by_key(key) will simplify this entire code-block. - let (found_node, mut visited_node_path) = { - let mut visited_node_path = vec![]; - - let found_node = merkle - .get_node_by_key_with_callbacks( - root_node, - &key, - |node_addr, _| visited_node_path.push(node_addr), - |_, _| {}, - ) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - let mut nibbles = Nibbles::<1>::new(key).into_iter(); - - let visited_node_path = visited_node_path - .into_iter() - .map(|node| merkle.get_node(node)) - .map(|node_result| { - let nibbles = &mut nibbles; - - node_result - .map(|node| match node.inner() { - NodeType::Branch(branch) => { - let mut partial_path_iter = branch.path.iter(); - let next_nibble = nibbles - .map(|nibble| (Some(nibble), partial_path_iter.next())) - .find(|(a, b)| a.as_ref() != *b); - - match next_nibble { - // this case will be hit by all but the last nodes - // unless there is a deviation between the key and the path - None | Some((None, _)) => None, - - Some((Some(key_nibble), Some(path_nibble))) => { - check_child_nibble = key_nibble < *path_nibble; - None - } - - // path is subset of the key - Some((Some(nibble), None)) => { - check_child_nibble = true; - Some((node, nibble)) - } - } - } - NodeType::Leaf(_) => Some((node, 0)), - NodeType::Extension(_) => Some((node, 0)), - }) - .transpose() - }) - .take_while(|node| node.is_some()) - .flatten() - .collect::, _>>() - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - - (found_node, visited_node_path) - }; - - if let Some(found_node) = found_node { - let value = match found_node.inner() { - NodeType::Branch(branch) => { - check_child_nibble = true; - branch.value.as_ref() + match state { + NodeStreamState::StartFromKey(key) => { + self.state = get_iterator_intial_state(merkle, *merkle_root, key)?; + self.poll_next(_cx) + } + NodeStreamState::Iterating { iter_stack } => { + while let Some(mut iter_node) = iter_stack.pop() { + match iter_node { + IterationNode::Unvisited { key, node } => { + match node.inner() { + NodeType::Branch(branch) => { + // `node` is a branch node. Visit its children next. + iter_stack.push(IterationNode::Visited { + key: key.clone(), + children_iter: Box::new(as_enumerated_children_iter( + branch, + )), + }); + } + NodeType::Leaf(_) => {} + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") + } + } + + let key = key_from_nibble_iter(key.iter().copied().skip(1)); + return Poll::Ready(Some(Ok((key, node)))); + } + IterationNode::Visited { + ref key, + ref mut children_iter, + } => { + // We returned `node` already. Visit its next child. + let Some((pos, child_addr)) = children_iter.next() else { + // We visited all this node's descendants. Go back to its parent. + continue; + }; + + let child = merkle.get_node(child_addr)?; + + let partial_path = match child.inner() { + NodeType::Branch(branch) => branch.path.iter().copied(), + NodeType::Leaf(leaf) => leaf.path.iter().copied(), + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") + } + }; + + // The child's key is its parent's key, followed by the child's index, + // followed by the child's partial path (if any). + let child_key: Box<[u8]> = key + .iter() + .copied() + .chain(once(pos)) + .chain(partial_path) + .collect(); + + // There may be more children of this node to visit. + iter_stack.push(iter_node); + + iter_stack.push(IterationNode::Unvisited { + key: child_key, + node: child, + }); + return self.poll_next(_cx); } - NodeType::Leaf(leaf) => Some(&leaf.data), - NodeType::Extension(_) => None, - }; + } + } + Poll::Ready(None) + } + } + } +} - let next_result = value.map(|value| { - let value = value.to_vec(); +/// Returns the initial state for an iterator over the given `merkle` with root `root_node` +/// which starts at `key`. +fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( + merkle: &'a Merkle, + root_node: DiskAddress, + key: &[u8], +) -> Result, api::Error> { + // Invariant: `node`'s key is a prefix of `key`. + let mut node = merkle.get_node(root_node)?; - Ok((std::mem::take(key), value)) - }); + // Invariant: [matched_key_nibbles] is the key of `node` at the start + // of each loop iteration. + let mut matched_key_nibbles = vec![]; - visited_node_path.push((found_node, 0)); + let mut unmatched_key_nibbles = Nibbles::<1>::new(key).into_iter(); - self.key_state = IteratorState::Iterating { - check_child_nibble, - visited_node_path, - }; + let mut iter_stack: Vec = vec![]; - return Poll::Ready(next_result); + loop { + // `next_unmatched_key_nibble` is the first nibble after `matched_key_nibbles`. + let Some(next_unmatched_key_nibble) = unmatched_key_nibbles.next() else { + // The invariant tells us `node` is a prefix of `key`. + // There is no more `key` left so `node` must be at `key`. + // Visit and return `node` first. + match &node.inner { + NodeType::Branch(_) | NodeType::Leaf(_) => { + iter_stack.push(IterationNode::Unvisited { + key: Box::from(matched_key_nibbles), + node, + }); } - - let found_key = nibble_iter_from_parents(&visited_node_path); - let found_key = key_from_nibble_iter(found_key); - - if found_key > *key { - check_child_nibble = false; - visited_node_path.pop(); + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") } + } - self.key_state = IteratorState::Iterating { - check_child_nibble, - visited_node_path, + return Ok(NodeStreamState::Iterating { iter_stack }); + }; + + match &node.inner { + NodeType::Branch(branch) => { + // The next nibble in `key` is `next_unmatched_key_nibble`, + // so all children of `node` with a position > `next_unmatched_key_nibble` + // should be visited since they are after `key`. + iter_stack.push(IterationNode::Visited { + key: matched_key_nibbles.iter().copied().collect(), + children_iter: Box::new( + as_enumerated_children_iter(branch) + .filter(move |(pos, _)| *pos > next_unmatched_key_nibble), + ), + }); + + // Figure out if the child at `next_unmatched_key_nibble` is a prefix of `key`. + // (i.e. if we should run this loop body again) + #[allow(clippy::indexing_slicing)] + let Some(child_addr) = branch.children[next_unmatched_key_nibble as usize] else { + // There is no child at `next_unmatched_key_nibble`. + // We'll visit `node`'s first child at index > `next_unmatched_key_nibble` + // first (if it exists). + return Ok(NodeStreamState::Iterating { iter_stack }); }; - self.poll_next(_cx) - } + matched_key_nibbles.push(next_unmatched_key_nibble); - IteratorState::Iterating { - check_child_nibble, - visited_node_path, - } => { - let next = find_next_result(merkle, visited_node_path, check_child_nibble) - .map_err(|e| api::Error::InternalError(Box::new(e))) - .transpose(); + let child = merkle.get_node(child_addr)?; - Poll::Ready(next) + let partial_key = match child.inner() { + NodeType::Branch(branch) => &branch.path, + NodeType::Leaf(leaf) => &leaf.path, + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") + } + }; + + let (comparison, new_unmatched_key_nibbles) = + compare_partial_path(partial_key.iter(), unmatched_key_nibbles); + unmatched_key_nibbles = new_unmatched_key_nibbles; + + match comparison { + Ordering::Less => { + // `child` is before `key`. + return Ok(NodeStreamState::Iterating { iter_stack }); + } + Ordering::Equal => { + // `child` is a prefix of `key`. + matched_key_nibbles.extend(partial_key.iter().copied()); + node = child; + } + Ordering::Greater => { + // `child` is after `key`. + let key = matched_key_nibbles + .iter() + .chain(partial_key.iter()) + .copied() + .collect(); + iter_stack.push(IterationNode::Unvisited { key, node: child }); + + return Ok(NodeStreamState::Iterating { iter_stack }); + } + } } - } + NodeType::Leaf(leaf) => { + if compare_partial_path(leaf.path.iter(), unmatched_key_nibbles).0 + == Ordering::Greater + { + // `child` is after `key`. + let key = matched_key_nibbles + .iter() + .chain(leaf.path.iter()) + .copied() + .collect(); + iter_stack.push(IterationNode::Unvisited { key, node }); + } + return Ok(NodeStreamState::Iterating { iter_stack }); + } + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") + } + }; } } -enum NodeRef<'a> { - New(NodeObjRef<'a>), - Visited(NodeObjRef<'a>), -} - -#[derive(Debug)] -enum InnerNode<'a> { - New(&'a NodeType), - Visited(&'a NodeType), +enum MerkleKeyValueStreamState<'a, S, T> { + /// The iterator state is lazily initialized when poll_next is called + /// for the first time. The iteration start key is stored here. + Uninitialized(Key), + /// The iterator works by iterating over the nodes in the merkle trie + /// and returning the key-value pairs for nodes that have values. + Initialized { + node_iter: MerkleNodeStream<'a, S, T>, + }, } -impl<'a> NodeRef<'a> { - fn inner(&self) -> InnerNode<'_> { - match self { - Self::New(node) => InnerNode::New(node.inner()), - Self::Visited(node) => InnerNode::Visited(node.inner()), - } +impl<'a, S, T> MerkleKeyValueStreamState<'a, S, T> { + /// Returns a new iterator that will iterate over all the key-value pairs in `merkle`. + fn new() -> Self { + Self::Uninitialized(Box::new([])) } - fn into_node(self) -> NodeObjRef<'a> { - match self { - Self::New(node) => node, - Self::Visited(node) => node, - } + /// Returns a new iterator that will iterate over all the key-value pairs in `merkle` + /// with keys greater than or equal to `key`. + fn with_key(key: Key) -> Self { + Self::Uninitialized(key) } } -fn find_next_result<'a, S: ShaleStore, T>( +pub struct MerkleKeyValueStream<'a, S, T> { + state: MerkleKeyValueStreamState<'a, S, T>, + merkle_root: DiskAddress, merkle: &'a Merkle, - visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, - check_child_nibble: &mut bool, -) -> Result, super::MerkleError> { - let next = find_next_node_with_data(merkle, visited_path, *check_child_nibble)?.map( - |(next_node, value)| { - let partial_path = match next_node.inner() { - NodeType::Leaf(leaf) => leaf.path.iter().copied(), - NodeType::Extension(extension) => extension.path.iter().copied(), - NodeType::Branch(branch) => branch.path.iter().copied(), - }; - - // always check the child for branch nodes with data - *check_child_nibble = next_node.inner().is_branch(); - - let key = - key_from_nibble_iter(nibble_iter_from_parents(visited_path).chain(partial_path)); - - visited_path.push((next_node, 0)); - - (key, value) - }, - ); - - Ok(next) } -fn find_next_node_with_data<'a, S: ShaleStore, T>( - merkle: &'a Merkle, - visited_path: &mut Vec<(NodeObjRef<'a>, u8)>, - check_child_nibble: bool, -) -> Result, Vec)>, super::MerkleError> { - use InnerNode::*; - - let Some((visited_parent, visited_pos)) = visited_path.pop() else { - return Ok(None); - }; - - let mut node = NodeRef::Visited(visited_parent); - let mut pos = visited_pos; - let mut first_loop = true; - - loop { - match node.inner() { - New(NodeType::Leaf(leaf)) => { - let value = leaf.data.to_vec(); - return Ok(Some((node.into_node(), value))); - } - - Visited(NodeType::Leaf(_)) | Visited(NodeType::Extension(_)) => { - let Some((next_parent, next_pos)) = visited_path.pop() else { - return Ok(None); - }; - - node = NodeRef::Visited(next_parent); - pos = next_pos; - } - - New(NodeType::Extension(extension)) => { - let child = merkle.get_node(extension.chd())?; - - pos = 0; - visited_path.push((node.into_node(), pos)); +impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleKeyValueStream<'a, S, T> { + fn is_terminated(&self) -> bool { + matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) + } +} - node = NodeRef::New(child); - } +impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { + pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress) -> Self { + Self { + state: MerkleKeyValueStreamState::new(), + merkle_root, + merkle, + } + } - Visited(NodeType::Branch(branch)) => { - // if the first node that we check is a visited branch, that means that the branch had a value - // and we need to visit the first child, for all other cases, we need to visit the next child - let compare_op = if first_loop && check_child_nibble { - ::ge // >= - } else { - ::gt - }; + pub(super) fn from_key(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { + Self { + state: MerkleKeyValueStreamState::with_key(key), + merkle_root, + merkle, + } + } +} - let children = get_children_iter(branch) - .filter(move |(_, child_pos)| compare_op(child_pos, &pos)); +impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<'a, S, T> { + type Item = Result<(Key, Value), api::Error>; - let found_next_node = - next_node(merkle, children, visited_path, &mut node, &mut pos)?; + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll> { + // destructuring is necessary here because we need mutable access to `key_state` + // at the same time as immutable access to `merkle` + let Self { + state, + merkle_root, + merkle, + } = &mut *self; - if !found_next_node { - return Ok(None); - } + match state { + MerkleKeyValueStreamState::Uninitialized(key) => { + let iter = MerkleNodeStream::new(merkle, *merkle_root, key.clone()); + self.state = MerkleKeyValueStreamState::Initialized { node_iter: iter }; + self.poll_next(_cx) } - - New(NodeType::Branch(branch)) => { - if let Some(value) = branch.value.as_ref() { - let value = value.to_vec(); - return Ok(Some((node.into_node(), value))); - } - - let children = get_children_iter(branch); - - let found_next_node = - next_node(merkle, children, visited_path, &mut node, &mut pos)?; - - if !found_next_node { - return Ok(None); + MerkleKeyValueStreamState::Initialized { node_iter: iter } => { + match iter.poll_next_unpin(_cx) { + Poll::Ready(node) => match node { + Some(Ok((key, node))) => match node.inner() { + NodeType::Branch(branch) => { + let Some(value) = branch.value.as_ref() else { + // This node doesn't have a value to return. + // Continue to the next node. + return self.poll_next(_cx); + }; + + let value = value.to_vec(); + Poll::Ready(Some(Ok((key, value)))) + } + NodeType::Leaf(leaf) => { + let value = leaf.data.to_vec(); + Poll::Ready(Some(Ok((key, value)))) + } + NodeType::Extension(_) => { + unreachable!("extension nodes shouldn't exist") + } + }, + Some(Err(e)) => Poll::Ready(Some(Err(e))), + None => Poll::Ready(None), + }, + Poll::Pending => Poll::Pending, } } } - - first_loop = false; } } -fn get_children_iter(branch: &BranchNode) -> impl Iterator { - branch - .children - .into_iter() - .enumerate() - .filter_map(|(pos, child_addr)| child_addr.map(|child_addr| (child_addr, pos as u8))) -} - -/// This function is a little complicated because we need to be able to early return from the parent -/// when we return `false`. `MustUse` forces the caller to check the inner value of `Result::Ok`. -/// It also replaces `node` -fn next_node<'a, S, T, Iter>( - merkle: &'a Merkle, - mut children: Iter, - parents: &mut Vec<(NodeObjRef<'a>, u8)>, - node: &mut NodeRef<'a>, - pos: &mut u8, -) -> Result, super::MerkleError> +/// Takes in an iterator over a node's partial path and an iterator over the +/// unmatched portion of a key. +/// The first returned element is: +/// * [Ordering::Less] if the node is before the key. +/// * [Ordering::Equal] if the node is a prefix of the key. +/// * [Ordering::Greater] if the node is after the key. +/// The second returned element is the unmatched portion of the key after the +/// partial path has been matched. +fn compare_partial_path<'a, I1, I2>( + partial_path_iter: I1, + mut unmatched_key_nibbles_iter: I2, +) -> (Ordering, I2) where - Iter: Iterator, - S: ShaleStore, + I1: Iterator, + I2: Iterator, { - if let Some((child_addr, child_pos)) = children.next() { - let child = merkle.get_node(child_addr)?; - - *pos = child_pos; - let node = std::mem::replace(node, NodeRef::New(child)); - parents.push((node.into_node(), *pos)); - } else { - let Some((next_parent, next_pos)) = parents.pop() else { - return Ok(false.into()); + for next_partial_path_nibble in partial_path_iter { + let Some(next_key_nibble) = unmatched_key_nibbles_iter.next() else { + return (Ordering::Greater, unmatched_key_nibbles_iter); }; - *node = NodeRef::Visited(next_parent); - *pos = next_pos; + match next_partial_path_nibble.cmp(&next_key_nibble) { + Ordering::Less => return (Ordering::Less, unmatched_key_nibbles_iter), + Ordering::Greater => return (Ordering::Greater, unmatched_key_nibbles_iter), + Ordering::Equal => {} + } } - Ok(true.into()) + (Ordering::Equal, unmatched_key_nibbles_iter) } -/// create an iterator over the key-nibbles from all parents _excluding_ the sentinal node. -fn nibble_iter_from_parents<'a>(parents: &'a [(NodeObjRef, u8)]) -> impl Iterator + 'a { - parents - .iter() - .skip(1) // always skip the sentinal node - .flat_map(|(parent, child_nibble)| match parent.inner() { - NodeType::Branch(branch) => Either::Left( - branch - .path - .iter() - .copied() - .chain(std::iter::once(*child_nibble)), - ), - NodeType::Extension(extension) => Either::Right(extension.path.iter().copied()), - NodeType::Leaf(leaf) => Either::Right(leaf.path.iter().copied()), - }) +/// Returns an iterator that returns (`pos`,`child_addr`) for each non-empty child of `branch`, +/// where `pos` is the position of the child in `branch`'s children array. +fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator { + branch + .children + .into_iter() + .enumerate() + .filter_map(|(pos, child_addr)| child_addr.map(|child_addr| (pos as u8, child_addr))) } fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { @@ -412,83 +447,214 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { data.into_boxed_slice() } -mod helper_types { - use std::ops::Not; +#[cfg(test)] +use super::tests::create_test_merkle; - /// Enums enable stack-based dynamic-dispatch as opposed to heap-based `Box`. - /// This helps us with match arms that return different types that implement the same trait. - /// It's possible that [rust-lang/rust#63065](https://github.com/rust-lang/rust/issues/63065) will make this unnecessary. - /// - /// And this can be replaced by the `either` crate from crates.io if we ever need more functionality. - pub(super) enum Either { - Left(T), - Right(U), - } +#[cfg(test)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] +mod tests { + use crate::{ + merkle::Bincode, + shale::{cached::DynamicMem, compact::CompactSpace}, + }; - impl Iterator for Either - where - T: Iterator, - U: Iterator, - { - type Item = T::Item; + use super::*; + use futures::StreamExt; + use test_case::test_case; - fn next(&mut self) -> Option { - match self { - Self::Left(left) => left.next(), - Self::Right(right) => right.next(), - } + impl + Send + Sync, T> Merkle { + pub(crate) fn node_iter(&self, root: DiskAddress) -> MerkleNodeStream<'_, S, T> { + MerkleNodeStream::new(self, root, Box::new([])) + } + + pub(crate) fn node_iter_from( + &self, + root: DiskAddress, + key: Box<[u8]>, + ) -> MerkleNodeStream<'_, S, T> { + MerkleNodeStream::new(self, root, key) } } - #[must_use] - pub(super) struct MustUse(T); + #[tokio::test] + async fn key_value_iterate_empty() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let stream = merkle.key_value_iter_from_key(root, b"x".to_vec().into_boxed_slice()); + check_stream_is_done(stream).await; + } - impl From for MustUse { - fn from(t: T) -> Self { - Self(t) - } + #[tokio::test] + async fn node_iterate_empty() { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let stream = merkle.node_iter(root); + check_stream_is_done(stream).await; } - impl Not for MustUse { - type Output = T::Output; + #[tokio::test] + async fn node_iterate_root_only() { + let mut merkle = create_test_merkle(); + + let root = merkle.init_root().unwrap(); - fn not(self) -> Self::Output { - self.0.not() - } + merkle.insert(vec![0x00], vec![0x00], root).unwrap(); + + let mut stream = merkle.node_iter(root); + + let (key, node) = stream.next().await.unwrap().unwrap(); + + assert_eq!(key, vec![0x00].into_boxed_slice()); + assert_eq!(node.inner().as_leaf().unwrap().data.to_vec(), vec![0x00]); + + check_stream_is_done(stream).await; } -} -// CAUTION: only use with nibble iterators -trait IntoBytes: Iterator { - fn nibbles_into_bytes(&mut self) -> Vec { - let mut data = Vec::with_capacity(self.size_hint().0 / 2); + /// Returns a new [Merkle] with the following structure: + /// sentinel + /// | 0 + /// 00 <-- branch with no value + /// 0/ D| \F + /// 00 0D0 F <-- leaf with no partial path + /// 0/ \F + /// 1 F + /// + /// Note the 0000 branch has no value and the F0F0 + /// The number next to each branch is the position of the child in the branch's children array. + fn created_populated_merkle() -> (Merkle, Bincode>, DiskAddress) + { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); - while let (Some(hi), Some(lo)) = (self.next(), self.next()) { - data.push((hi << 4) + lo); - } + merkle + .insert(vec![0x00, 0x00, 0x00], vec![0x00, 0x00, 0x00], root) + .unwrap(); + merkle + .insert( + vec![0x00, 0x00, 0x00, 0x01], + vec![0x00, 0x00, 0x00, 0x01], + root, + ) + .unwrap(); + merkle + .insert( + vec![0x00, 0x00, 0x00, 0xFF], + vec![0x00, 0x00, 0x00, 0xFF], + root, + ) + .unwrap(); + merkle + .insert(vec![0x00, 0xD0, 0xD0], vec![0x00, 0xD0, 0xD0], root) + .unwrap(); + merkle + .insert(vec![0x00, 0xFF], vec![0x00, 0xFF], root) + .unwrap(); + (merkle, root) + } + + #[tokio::test] + async fn node_iterator_no_start_key() { + let (merkle, root) = created_populated_merkle(); + + let mut stream = merkle.node_iter(root); + + // Covers case of branch with no value + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00].into_boxed_slice()); + let node = node.inner().as_branch().unwrap(); + assert!(node.value.is_none()); + assert_eq!(node.path.to_vec(), vec![0x00, 0x00]); + + // Covers case of branch with value + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); + let node = node.inner().as_branch().unwrap(); + assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); + assert_eq!(node.path.to_vec(), vec![0x00, 0x00, 0x00]); + + // Covers case of leaf with partial path + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); + let node = node.inner().as_leaf().unwrap(); + assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); + assert_eq!(node.path.to_vec(), vec![0x01]); + + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); + let node = node.inner().as_leaf().unwrap(); + assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); + assert_eq!(node.path.to_vec(), vec![0x0F]); + + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + let node = node.inner().as_leaf().unwrap(); + assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xD0, 0xD0]); + assert_eq!(node.path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO + + // Covers case of leaf with no partial path + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); + let node = node.inner().as_leaf().unwrap(); + assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xFF]); + assert_eq!(node.path.to_vec(), vec![0x0F]); - data + check_stream_is_done(stream).await; } -} -impl> IntoBytes for T {} -#[cfg(test)] -use super::tests::create_test_merkle; + #[tokio::test] + async fn node_iterator_start_key_between_nodes() { + let (merkle, root) = created_populated_merkle(); -#[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] -mod tests { - use crate::nibbles::Nibbles; + let mut stream = merkle.node_iter_from(root, vec![0x00, 0x00, 0x01].into_boxed_slice()); - use super::*; - use futures::StreamExt; - use test_case::test_case; + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + assert_eq!( + node.inner().as_leaf().unwrap().clone().data.to_vec(), + vec![0x00, 0xD0, 0xD0] + ); + + // Covers case of leaf with no partial path + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); + assert_eq!( + node.inner().as_leaf().unwrap().clone().data.to_vec(), + vec![0x00, 0xFF] + ); + + check_stream_is_done(stream).await; + } #[tokio::test] - async fn iterate_empty() { - let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - let stream = merkle.iter_from(root, b"x".to_vec().into_boxed_slice()); + async fn node_iterator_start_key_on_node() { + let (merkle, root) = created_populated_merkle(); + + let mut stream = merkle.node_iter_from(root, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + assert_eq!( + node.inner().as_leaf().unwrap().clone().data.to_vec(), + vec![0x00, 0xD0, 0xD0] + ); + + // Covers case of leaf with no partial path + let (key, node) = stream.next().await.unwrap().unwrap(); + assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); + assert_eq!( + node.inner().as_leaf().unwrap().clone().data.to_vec(), + vec![0x00, 0xFF] + ); + + check_stream_is_done(stream).await; + } + + #[tokio::test] + async fn node_iterator_start_key_after_last_key() { + let (merkle, root) = created_populated_merkle(); + + let stream = merkle.node_iter_from(root, vec![0xFF].into_boxed_slice()); + check_stream_is_done(stream).await; } @@ -497,7 +663,7 @@ mod tests { #[test_case(Some(&[128u8]); "Starting in middle")] #[test_case(Some(&[u8::MAX]); "Starting at last key")] #[tokio::test] - async fn iterate_many(start: Option<&[u8]>) { + async fn key_value_iterate_many(start: Option<&[u8]>) { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -507,8 +673,8 @@ mod tests { } let mut stream = match start { - Some(start) => merkle.iter_from(root, start.to_vec().into_boxed_slice()), - None => merkle.iter(root), + Some(start) => merkle.key_value_iter_from_key(root, start.to_vec().into_boxed_slice()), + None => merkle.key_value_iter(root), }; // we iterate twice because we should get a None then start over @@ -527,14 +693,75 @@ mod tests { } #[tokio::test] - async fn fused_empty() { + async fn key_value_fused_empty() { let merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); - check_stream_is_done(merkle.iter(root)).await; + check_stream_is_done(merkle.key_value_iter(root)).await; + } + + #[tokio::test] + async fn key_value_table_test() { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + // Insert key-values in reverse order to ensure iterator + // doesn't just return the keys in insertion order. + for i in (0..=u8::MAX).rev() { + for j in (0..=u8::MAX).rev() { + let key = vec![i, j]; + let value = vec![i, j]; + + merkle.insert(key, value, root).unwrap(); + } + } + + // Test with no start key + let mut stream = merkle.key_value_iter(root); + for i in 0..=u8::MAX { + for j in 0..=u8::MAX { + let expected_key = vec![i, j]; + let expected_value = vec![i, j]; + + assert_eq!( + stream.next().await.unwrap().unwrap(), + (expected_key.into_boxed_slice(), expected_value), + "i: {}, j: {}", + i, + j, + ); + } + } + check_stream_is_done(stream).await; + + // Test with start key + for i in 0..=u8::MAX { + let mut stream = merkle.key_value_iter_from_key(root, vec![i].into_boxed_slice()); + for j in 0..=u8::MAX { + let expected_key = vec![i, j]; + let expected_value = vec![i, j]; + assert_eq!( + stream.next().await.unwrap().unwrap(), + (expected_key.into_boxed_slice(), expected_value), + "i: {}, j: {}", + i, + j, + ); + } + if i == u8::MAX { + check_stream_is_done(stream).await; + } else { + assert_eq!( + stream.next().await.unwrap().unwrap(), + (vec![i + 1, 0].into_boxed_slice(), vec![i + 1, 0]), + "i: {}", + i, + ); + } + } } #[tokio::test] - async fn fused_full() { + async fn key_value_fused_full() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -553,7 +780,7 @@ mod tests { merkle.insert(kv, kv.clone(), root).unwrap(); } - let mut stream = merkle.iter(root); + let mut stream = merkle.key_value_iter(root); for kv in key_values.iter() { let next = stream.next().await.unwrap().unwrap(); @@ -565,7 +792,7 @@ mod tests { } #[tokio::test] - async fn root_with_empty_data() { + async fn key_value_root_with_empty_data() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -574,13 +801,13 @@ mod tests { merkle.insert(&key, value.clone(), root).unwrap(); - let mut stream = merkle.iter(root); + let mut stream = merkle.key_value_iter(root); assert_eq!(stream.next().await.unwrap().unwrap(), (key, value)); } #[tokio::test] - async fn get_branch_and_leaf() { + async fn key_value_get_branch_and_leaf() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -597,7 +824,7 @@ mod tests { merkle.insert(branch, branch.to_vec(), root).unwrap(); - let mut stream = merkle.iter(root); + let mut stream = merkle.key_value_iter(root); assert_eq!( stream.next().await.unwrap().unwrap(), @@ -619,7 +846,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_not_in_trie() { + async fn key_value_start_at_key_not_in_trie() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -640,7 +867,8 @@ mod tests { merkle.insert(key, key.to_vec(), root).unwrap(); } - let mut stream = merkle.iter_from(root, vec![intermediate].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(root, vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); let first = stream.next().await.unwrap().unwrap(); @@ -658,7 +886,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_on_branch_with_no_value() { + async fn key_value_start_at_key_on_branch_with_no_value() { let sibling_path = 0x00; let branch_path = 0x0f; let children = 0..=0x0f; @@ -687,7 +915,7 @@ mod tests { let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.iter_from(root, vec![branch_path].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, vec![branch_path].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -700,7 +928,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_on_branch_with_value() { + async fn key_value_start_at_key_on_branch_with_value() { let sibling_path = 0x00; let branch_path = 0x0f; let branch_key = vec![branch_path]; @@ -736,7 +964,7 @@ mod tests { let start = keys.iter().position(|key| key == &branch_key).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.iter_from(root, branch_key.into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, branch_key.into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -749,7 +977,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_on_extension() { + async fn key_value_start_at_key_on_extension() { let missing = 0x0a; let children = (0..=0x0f).filter(|x| *x != missing); let mut merkle = create_test_merkle(); @@ -767,7 +995,7 @@ mod tests { let keys = &keys[(missing as usize)..]; - let mut stream = merkle.iter_from(root, vec![missing].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -780,7 +1008,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_overlapping_with_extension_but_greater() { + async fn key_value_start_at_key_overlapping_with_extension_but_greater() { let start_key = 0x0a; let shared_path = 0x09; // 0x0900, 0x0901, ... 0x0a0f @@ -794,13 +1022,13 @@ mod tests { merkle.insert(&key, key.clone(), root).unwrap(); }); - let stream = merkle.iter_from(root, vec![start_key].into_boxed_slice()); + let stream = merkle.key_value_iter_from_key(root, vec![start_key].into_boxed_slice()); check_stream_is_done(stream).await; } #[tokio::test] - async fn start_at_key_overlapping_with_extension_but_smaller() { + async fn key_value_start_at_key_overlapping_with_extension_but_smaller() { let start_key = 0x00; let shared_path = 0x09; // 0x0900, 0x0901, ... 0x0a0f @@ -817,7 +1045,7 @@ mod tests { }) .collect(); - let mut stream = merkle.iter_from(root, vec![start_key].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, vec![start_key].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -830,7 +1058,7 @@ mod tests { } #[tokio::test] - async fn start_at_key_between_siblings() { + async fn key_value_start_at_key_between_siblings() { let missing = 0xaa; let children = (0..=0xf) .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff @@ -850,7 +1078,7 @@ mod tests { let keys = &keys[((missing >> 4) as usize)..]; - let mut stream = merkle.iter_from(root, vec![missing].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -863,19 +1091,19 @@ mod tests { } #[tokio::test] - async fn start_at_key_greater_than_all_others_leaf() { + async fn key_value_start_at_key_greater_than_all_others_leaf() { let key = vec![0x00]; let greater_key = vec![0xff]; let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); merkle.insert(key.clone(), key, root).unwrap(); - let stream = merkle.iter_from(root, greater_key.into_boxed_slice()); + let stream = merkle.key_value_iter_from_key(root, greater_key.into_boxed_slice()); check_stream_is_done(stream).await; } #[tokio::test] - async fn start_at_key_greater_than_all_others_branch() { + async fn key_value_start_at_key_greater_than_all_others_branch() { let greatest = 0xff; let children = (0..=0xf) .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff @@ -895,7 +1123,7 @@ mod tests { let keys = &keys[((greatest >> 4) as usize)..]; - let mut stream = merkle.iter_from(root, vec![greatest].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(root, vec![greatest].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -914,21 +1142,4 @@ mod tests { assert!(stream.next().await.is_none()); assert!(stream.is_terminated()); } - - #[test] - fn remaining_bytes() { - let data = &[1]; - let nib: Nibbles<'_, 0> = Nibbles::<0>::new(data); - let mut it = nib.into_iter(); - assert_eq!(it.nibbles_into_bytes(), data.to_vec()); - } - - #[test] - fn remaining_bytes_off() { - let data = &[1]; - let nib: Nibbles<'_, 0> = Nibbles::<0>::new(data); - let mut it = nib.into_iter(); - it.next(); - assert_eq!(it.nibbles_into_bytes(), vec![]); - } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 866ccadd68dc..a6a413d266db 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::merkle::MerkleError; pub use crate::merkle::Proof; use async_trait::async_trait; use std::{fmt::Debug, sync::Arc}; @@ -84,6 +85,13 @@ pub enum Error { RangeTooSmall, } +impl From for Error { + fn from(err: MerkleError) -> Self { + // TODO: do a better job + Error::InternalError(Box::new(err)) + } +} + /// A range proof, consisting of a proof of the first key and the last key, /// and a vector of all key/value pairs #[derive(Debug)] From 7be647115739324f09a2ac2ee006b94e148c3acf Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 15 Feb 2024 11:21:37 -0500 Subject: [PATCH 0466/1053] add support for start key argument in fwdctl dump command (#476) Co-authored-by: Ron Kuris --- fwdctl/src/dump.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 3468589939df..a86a6f862200 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -21,6 +21,16 @@ pub struct Options { help = "Name of the database" )] pub db: String, + + /// The key to start dumping from (if no key is provided, start from the beginning). + /// Defaults to None. + #[arg( + required = false, + value_name = "START_KEY", + value_parser = key_parser, + help = "Start dumping from this key (inclusive)." + )] + pub start_key: Option>, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { @@ -32,7 +42,8 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db = Db::new(opts.db.clone(), &cfg.build()).await?; let latest_hash = db.root_hash().await?; let latest_rev = db.revision(latest_hash).await?; - let mut stream = latest_rev.stream(); + let start_key = opts.start_key.clone().unwrap_or(Box::new([])); + let mut stream = latest_rev.stream_from(start_key); loop { match stream.next().await { None => break, @@ -44,6 +55,11 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { } Ok(()) } + fn u8_to_string(data: &[u8]) -> Cow<'_, str> { String::from_utf8_lossy(data) } + +fn key_parser(s: &str) -> Result, std::io::Error> { + return Ok(Box::from(s.as_bytes())); +} From dd7ef9dd099d807398bed2cc51c7610d66f22e29 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 16 Feb 2024 05:45:10 -0800 Subject: [PATCH 0467/1053] Cargo upgrades (#531) --- firewood/Cargo.toml | 4 ++-- fwdctl/Cargo.toml | 2 +- growth-ring/Cargo.toml | 6 +++--- grpc-testtool/Cargo.toml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d34ee1ce7492..bcab84ea03a4 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -30,12 +30,12 @@ nix = {version = "0.27.1", features = ["fs", "uio"]} parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.8" -thiserror = "1.0.56" +thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.18.1" bincode = "1.3.3" bitflags = { version = "2.4.2", features = ["bytemuck"] } -env_logger = { version = "0.11.1", optional = true } +env_logger = { version = "0.11.2", optional = true } log = { version = "0.4.20", optional = true } [features] diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 365d68b95349..3c05ebfcc203 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" firewood = { version = "0.0.4", path = "../firewood" } clap = { version = "4.5.0", features = ["cargo", "derive"] } anyhow = "1.0.79" -env_logger = "0.11.1" +env_logger = "0.11.2" log = "0.4.20" tokio = { version = "1.36.0", features = ["full"] } futures-util = "0.3.30" diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 9cec56446d06..13ed5b6f684d 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -17,14 +17,14 @@ futures = "0.3.30" nix = {version = "0.27.1", features = ["fs", "uio"]} libc = "0.2.153" bytemuck = {version = "1.14.3", features = ["derive"]} -thiserror = "1.0.56" +thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["fs", "io-util", "sync"] } -crc32fast = "1.3.2" +crc32fast = "1.4.0" [dev-dependencies] hex = "0.4.3" rand = "0.8.5" -indexmap = "2.2.2" +indexmap = "2.2.3" tokio = { version = "1.36.0", features = ["tokio-macros", "rt", "macros"] } test-case = "3.3.1" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 6ebbbf022cce..4d686ca7b743 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -18,15 +18,15 @@ bench = false [dependencies] firewood = { version = "0.0.4", path = "../firewood" } prost = "0.12.3" -thiserror = "1.0.56" +thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.11.0", features = ["tls"] } tracing = { version = "0.1.40" } clap = { version = "4.5.0", features = ["derive"] } tempdir = "0.3.7" log = "0.4.20" -env_logger = "0.11.1" -chrono = "0.4.33" +env_logger = "0.11.2" +chrono = "0.4.34" serde_json = "1.0.113" serde = { version = "1.0.196", features = ["derive"] } From 3c69b74a1baf4654c871e09cbcc004d78a685fe7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 16 Feb 2024 13:37:25 -0800 Subject: [PATCH 0468/1053] Improve build.rs (#535) --- grpc-testtool/build.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/grpc-testtool/build.rs b/grpc-testtool/build.rs index 1c3c17ee3ea2..f546f152e04e 100644 --- a/grpc-testtool/build.rs +++ b/grpc-testtool/build.rs @@ -1,10 +1,24 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::path::PathBuf; + fn main() -> Result<(), Box> { - tonic_build::compile_protos("proto/sync/sync.proto")?; - tonic_build::compile_protos("proto/rpcdb/rpcdb.proto")?; - tonic_build::compile_protos("proto/process-server/process-server.proto")?; + // we want to import these proto files + let import_protos = ["sync", "rpcdb", "process-server"]; + + let protos: Box<[PathBuf]> = import_protos + .into_iter() + .map(|proto| PathBuf::from(format!("proto/{proto}/{proto}.proto"))) + .collect(); + + // go through each proto and build it, also let cargo know we rerun this if the file changes + for proto in protos.iter() { + tonic_build::compile_protos(proto)?; + + // this improves recompile times; we only rerun tonic if any of these files change + println!("cargo:rerun-if-changed={}", proto.display()); + } Ok(()) } From bf3b97ebd7557d6b48b2433e6f1c0ecb442dd7bb Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 16 Feb 2024 14:02:36 -0800 Subject: [PATCH 0469/1053] Take out the updates step for now (#536) --- .github/workflows/ci.yaml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 98dc6ad51e79..04b2a67b4090 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -102,29 +102,6 @@ jobs: - name: Clippy run: cargo clippy --tests --examples --benches --all-features -- -D warnings - upgrades: - needs: build - runs-on: ubuntu-latest - - steps: - - name: Restore Check Deps - id: cache-build-deps-restore - uses: actions/cache/restore@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - name: Cargo Edit - run: cargo install cargo-edit - - name: Cargo upgrade - run: cargo upgrade --locked - test: needs: build runs-on: ubuntu-latest From 1f9c2717926f3d93b5d434301d5746abb3ba2689 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 16 Feb 2024 15:27:34 -0800 Subject: [PATCH 0470/1053] Add some rust-based benchmarks (#528) Co-authored-by: Dan Laine --- grpc-testtool/Cargo.toml | 8 ++ grpc-testtool/README.md | 30 +---- grpc-testtool/benches/insert.rs | 230 ++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 grpc-testtool/benches/insert.rs diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 4d686ca7b743..f7a22fe29820 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -33,9 +33,17 @@ serde = { version = "1.0.196", features = ["derive"] } [build-dependencies] tonic-build = "0.11.0" +[dev-dependencies] +criterion = {version = "0.5.1", features = ["async_tokio"]} +rand = "0.8.5" + [lints.rust] unsafe_code = "deny" +[[bench]] +name = "insert" +harness = false + [lints.clippy] unwrap_used = "warn" indexing_slicing = "warn" diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index 650f3c24e0ee..7ff503e0a7e7 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -21,29 +21,9 @@ There are 3 RPC specs that must be implemented: # Running -To run a single test and make sure things are working, first check out and build the go and rust code. -These have to be in the same directory. See the corresponding README for specific build requirements. +To test the release version of firewood, just run `RUST_MIN_STACK=7000000 cargo bench`. If you make some changes and then +run it again, it will give you a report showing how much it sped up or slowed down. -```sh -BASE=$HOME -cd $BASE && git clone git@github.com:ava-labs/merkledb-tester.git -cd $BASE && git clone git@github.com:ava-labs/firewood.git -``` - -Then, build the rust process server and symlink it to where the testtool expects it: - -```sh -cd $BASE/firewood -cargo build --release -ln -sf $BASE/firewood/target/release/process-server $BASE/merkledb-tester/process/process-server -``` - -Then, run the test you want: - -```sh -export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" -export CGO_ENABLED=1 -export GOPROXY=https://proxy.golang.org -cd $BASE/merkledb-tester -./scripts/test.sh -``` +If you want to run this against merkledb, first build the process-server following the instructions in +the [merkledb-tester](https://github.com/ava-labs/merkledb-tester) directory, then modify your PATH so +that `process-server` from the merkledbexecutable is found first, then run `cargo bench`. diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs new file mode 100644 index 000000000000..7168c6feedf4 --- /dev/null +++ b/grpc-testtool/benches/insert.rs @@ -0,0 +1,230 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use rand::{distributions::Alphanumeric, Rng, SeedableRng}; +use std::{ + borrow::BorrowMut as _, cell::RefCell, env, fs::remove_dir_all, net::TcpStream, + os::unix::process::CommandExt, path::PathBuf, thread::sleep, time::Duration, +}; + +use rpc::rpcdb::{self, PutRequest, WriteBatchRequest}; +pub use rpc::service::Database as DatabaseService; + +use rpcdb::database_client::DatabaseClient; + +use std::process::Command; + +/// The directory where the database will be created +const TESTDIR: &str = "/tmp/benchdb"; +/// The port to use for testing +const TESTPORT: u16 = 5000; +/// The URI to connect to; this better match the TESTPORT +const TESTURI: &str = "http://localhost:5000"; +/// Retry timeouts (in seconds); we want this long for processes +/// to start and exit +const RETRY_TIMEOUT_SEC: u32 = 5; + +/// Merkledb configuration options; these are ignored by the rust side +/// These were chosen to be as close to the defaults for firewood as +/// possible. NodeCacheSize is a guess. +const MERKLEDB_OPTIONAL_CONFIGURATIONS: &str = r#" + { + "BranchFactor":16, + "ProcessName":"bench", + "HistoryLength":100, + "NodeCacheSize":1000 + } +"#; + +/// Clean up anything that might be left from prior runs +fn stop_everything() -> Result<(), std::io::Error> { + // kill all process servers + Command::new("killall").arg("process-server").output()?; + + // wait for them to die + retry("process-server wouldn't die", || { + process_server_pids().is_empty() + }); + + // remove test directory, ignoring any errors + let _ = remove_dir_all(TESTDIR); + + Ok(()) +} +fn reset_everything() -> Result<(), std::io::Error> { + stop_everything()?; + // find the process server + let process_server = process_server_path().expect("Can't find process-server on path"); + eprintln!("Using process-server {}", process_server.display()); + + // spawn a new one; use a separate thread to avoid zombies + std::thread::spawn(|| { + Command::new(process_server) + .arg("--grpc-port") + .arg(TESTPORT.to_string()) + .arg("--db-dir") + .arg(TESTDIR) + .arg("--config") + .arg(MERKLEDB_OPTIONAL_CONFIGURATIONS) + .process_group(0) + .spawn() + .expect("unable to start process-server") + .wait() + }); + + // wait for it to accept connections + retry("couldn't connect to process-server", || { + TcpStream::connect(format!("localhost:{TESTPORT}")).is_ok() + }); + + Ok(()) +} + +/// Poll a function until it returns true +/// +/// Pass in a message and a closure to execute. Panics if it times out. +/// +/// # Arguments +/// +/// * `msg` - The message to render if it panics +/// * `t` - The test closure +fn retry bool>(msg: &str, t: TEST) { + const TEST_INTERVAL_MS: u32 = 50; + for _ in 0..=(RETRY_TIMEOUT_SEC * 1000 / TEST_INTERVAL_MS) { + sleep(Duration::from_millis(TEST_INTERVAL_MS as u64)); + if t() { + return; + } + } + panic!("{msg} timed out after {RETRY_TIMEOUT_SEC} second(s)"); +} + +/// Return a list of process IDs for any running process-server processes +fn process_server_pids() -> Vec { + // Basically we do `ps -eo pid=,comm=` which removes the header from ps + // and gives us just the pid and the command, then we look for process-server + // TODO: we match "123456 process-server-something-else", which isn't ideal + let cmd = Command::new("ps") + .arg("-eo") + .arg("pid=,comm=") + .output() + .expect("Can't run ps"); + String::from_utf8_lossy(&cmd.stdout) + .lines() + .filter_map(|line| { + line.trim_start().find(" process-server").map(|pos| { + str::parse(line.trim_start().get(0..pos).unwrap_or_default()).unwrap_or_default() + }) + }) + .collect() +} + +/// Finds the first process-server on the path, or in some predefined locations +/// +/// If the process-server isn't on the path, look for it in a target directory +/// As a last resort, we check the parent in case you're running from the +/// grpc-testtool directory, or in the current directory +fn process_server_path() -> Option { + const OTHER_PLACES_TO_LOOK: &str = ":target/release:../target/release:."; + + env::var_os("PATH").and_then(|mut paths| { + paths.push(OTHER_PLACES_TO_LOOK); + env::split_paths(&paths) + .filter_map(|dir| { + let full_path = dir.join("process-server"); + if full_path.is_file() { + Some(full_path) + } else { + None + } + }) + .next() + }) +} + +/// The actual insert benchmark +fn insert( + criterion: &mut Criterion, +) { + // we save the tokio runtime because the client is only valid from within the + // same runtime + let runtime = tokio::runtime::Runtime::new().expect("tokio startup"); + + // clean up anything that was running before, and make sure we have an empty directory + // to run the tests in + reset_everything().expect("unable to reset everything"); + + // We want a consistent seed, but we need different values for each batch, so we + // reseed each time we compute more data from the next seed value upwards + let seed = RefCell::new(0); + + let client = runtime + .block_on(DatabaseClient::connect(TESTURI)) + .expect("connection succeeded"); + + criterion.bench_with_input( + BenchmarkId::new("insert", BATCHSIZE), + &BATCHSIZE, + |b, &s| { + b.to_async(&runtime).iter_batched( + || { + // seed a new random number generator to generate data + // each time we call this, we increase the seed by 1 + // this gives us different but consistently different random data + let seed = { + let mut inner = seed.borrow_mut(); + *inner += 1; + *inner + }; + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + + // generate the put request, which is BATCHSIZE PutRequest objects with random keys/values + let put_requests: Vec = (0..s) + .map(|_| { + ( + rng.borrow_mut() + .sample_iter(&Alphanumeric) + .take(KEYLEN) + .collect::>(), + rng.borrow_mut() + .sample_iter(&Alphanumeric) + .take(DATALEN) + .collect::>(), + ) + }) + .map(|(key, value)| PutRequest { key, value }) + .collect(); + + // wrap it into a tonic::Request to pass to the execution routine + let req = tonic::Request::new(WriteBatchRequest { + puts: put_requests, + deletes: vec![], + }); + + // hand back the client and the request contents to the benchmark executor + (client.clone(), req) + }, + // this part is actually timed + |(mut client, req)| async move { + client + .write_batch(req) + .await + .expect("batch insert succeeds"); + }, + // I have no idea what this does, but the docs seem to say you almost always want this + BatchSize::SmallInput, + ) + }, + ); + + stop_everything().expect("unable to stop the process server"); +} + +criterion_group! { + name = benches; + config = Criterion::default().sample_size(20); + targets = insert::<1, 32, 32>, insert::<20, 32, 32>, insert::<10000, 32, 32> +} + +criterion_main!(benches); From 2389abd2948f693f3fed150b14eb8ab14c6cc1c3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 17 Feb 2024 06:58:58 -0800 Subject: [PATCH 0471/1053] Switch to strum's from_repr (#541) --- growth-ring/Cargo.toml | 1 + growth-ring/src/wal.rs | 61 +++++++++++++++++------------------------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 13ed5b6f684d..7471953211f9 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -20,6 +20,7 @@ bytemuck = {version = "1.14.3", features = ["derive"]} thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["fs", "io-util", "sync"] } crc32fast = "1.4.0" +strum_macros = "0.26.1" [dev-dependencies] hex = "0.4.3" diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index ee3f4c601d61..0a128f78c7e6 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -10,7 +10,6 @@ use futures::{ Future, }; -use std::convert::{TryFrom, TryInto}; use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::pin::Pin; @@ -24,10 +23,12 @@ use std::{ marker::PhantomData, }; +use strum_macros::FromRepr; + pub use crate::walerror::WalError; +#[derive(Debug, FromRepr)] enum WalRingType { - #[allow(dead_code)] Null = 0x0, Full, First, @@ -45,20 +46,6 @@ struct WalRingBlob { // payload follows } -impl TryFrom for WalRingType { - type Error = (); - fn try_from(v: u8) -> Result { - match v { - x if x == WalRingType::Null as u8 => Ok(WalRingType::Null), - x if x == WalRingType::Full as u8 => Ok(WalRingType::Full), - x if x == WalRingType::First as u8 => Ok(WalRingType::First), - x if x == WalRingType::Middle as u8 => Ok(WalRingType::Middle), - x if x == WalRingType::Last as u8 => Ok(WalRingType::Last), - _ => Err(()), - } - } -} - type WalFileId = u64; pub type WalBytes = Box<[u8]>; pub type WalPos = u64; @@ -812,8 +799,8 @@ impl> WalWriter { let (header, payload) = ring; #[allow(clippy::unwrap_used)] let payload = payload.unwrap(); - match header.rtype.try_into() { - Ok(WalRingType::Full) => { + match WalRingType::from_repr(header.rtype as usize) { + Some(WalRingType::Full) => { assert!(chunks.is_none()); if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { return Err(WalError::InvalidChecksum); @@ -821,7 +808,7 @@ impl> WalWriter { off += header.rsize as u64; records.push(payload); } - Ok(WalRingType::First) => { + Some(WalRingType::First) => { if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { return Err(WalError::InvalidChecksum); } @@ -838,7 +825,7 @@ impl> WalWriter { } off += header.rsize as u64; } - Ok(WalRingType::Middle) => { + Some(WalRingType::Middle) => { if let Some(chunks) = &mut chunks { chunks.push(payload); } else { @@ -846,13 +833,13 @@ impl> WalWriter { } off += header.rsize as u64; } - Ok(WalRingType::Last) => { + Some(WalRingType::Last) => { assert!(chunks.is_none()); chunks = Some(vec![payload]); off += header.rsize as u64; } - Ok(WalRingType::Null) => break, - Err(_) => match recover_policy { + Some(WalRingType::Null) => break, + None => match recover_policy { RecoverPolicy::Strict => { return Err(WalError::Other( "invalid ring type - strict recovery requested".to_string(), @@ -1022,11 +1009,11 @@ impl WalLoader { let header = *header.first()?; let payload; - match header.rtype.try_into() { - Ok(WalRingType::Full) - | Ok(WalRingType::First) - | Ok(WalRingType::Middle) - | Ok(WalRingType::Last) => { + match WalRingType::from_repr(header.rtype as usize) { + Some(WalRingType::Full) + | Some(WalRingType::First) + | Some(WalRingType::Middle) + | Some(WalRingType::Last) => { payload = if read_payload { Some(check!(check!( v.file.read(v.off, header.rsize as usize).await @@ -1037,8 +1024,8 @@ impl WalLoader { }; v.off += header.rsize as u64; } - Ok(WalRingType::Null) => _yield!(), - Err(_) => match recover_policy { + Some(WalRingType::Null) => _yield!(), + None => match recover_policy { RecoverPolicy::Strict => die!(), RecoverPolicy::BestEffort => { v.done = true; @@ -1130,8 +1117,8 @@ impl WalLoader { v.off += msize as u64; let header: WalRingBlob = *cast_slice(&header_raw).first()?; let rsize = header.rsize; - match header.rtype.try_into() { - Ok(WalRingType::Full) => { + match WalRingType::from_repr(header.rtype as usize) { + Some(WalRingType::Full) => { assert!(v.chunks.is_none()); let payload = check!(check!(v.file.read(v.off, rsize as usize).await) .ok_or(WalError::Other)); @@ -1150,7 +1137,7 @@ impl WalLoader { header.counter )) } - Ok(WalRingType::First) => { + Some(WalRingType::First) => { assert!(v.chunks.is_none()); let chunk = check!(check!(v.file.read(v.off, rsize as usize).await) .ok_or(WalError::Other)); @@ -1160,7 +1147,7 @@ impl WalLoader { *v.chunks = Some((vec![chunk], ringid_start)); v.off += rsize as u64; } - Ok(WalRingType::Middle) => { + Some(WalRingType::Middle) => { let Vars { chunks, off, file, .. } = &mut *v; @@ -1174,7 +1161,7 @@ impl WalLoader { } // otherwise ignore the leftover *off += rsize as u64; } - Ok(WalRingType::Last) => { + Some(WalRingType::Last) => { let v_off = v.off; v.off += rsize as u64; if let Some((mut chunks, ringid_start)) = v.chunks.take() { @@ -1204,8 +1191,8 @@ impl WalLoader { )) } } - Ok(WalRingType::Null) => _yield!(), - Err(_) => match self.recover_policy { + Some(WalRingType::Null) => _yield!(), + None => match self.recover_policy { RecoverPolicy::Strict => die!(), RecoverPolicy::BestEffort => { v.done = true; From fc19c6d8682f2a531aa995d6c2a67d23d3a2cbf9 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 19 Feb 2024 09:22:08 -0500 Subject: [PATCH 0472/1053] Path iterator (#530) Co-authored-by: Ron Kuris --- firewood/src/merkle.rs | 13 ++ firewood/src/merkle/stream.rs | 263 +++++++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 6 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 56f89a9a0ee0..305e8abc31e6 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -25,10 +25,15 @@ pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; +use self::stream::PathIterator; + type NodeObjRef<'a> = shale::ObjRef<'a, Node>; type ParentRefs<'a> = Vec<(NodeObjRef<'a>, u8)>; type ParentAddresses = Vec<(DiskAddress, u8)>; +type Key = Box<[u8]>; +type Value = Vec; + #[derive(Debug, Error)] pub enum MerkleError { #[error("merkle datastore error: {0:?}")] @@ -1715,6 +1720,14 @@ impl + Send + Sync, T> Merkle { self.store.flush_dirty() } + pub fn path_iter<'a, 'b>( + &'a self, + sentinel_node: NodeObjRef<'a>, + key: &'b [u8], + ) -> PathIterator<'_, 'b, S, T> { + PathIterator::new(self, sentinel_node, key) + } + pub(crate) fn key_value_iter(&self, root: DiskAddress) -> MerkleKeyValueStream<'_, S, T> { MerkleKeyValueStream::new(self, root) } diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index e035ef69bc9f..9f407e570a6d 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{node::Node, BranchNode, Merkle, NodeObjRef, NodeType}; +use super::{node::Node, BranchNode, Key, Merkle, MerkleError, NodeObjRef, NodeType, Value}; use crate::{ - nibbles::Nibbles, + nibbles::{Nibbles, NibblesIterator}, shale::{DiskAddress, ShaleStore}, v2::api, }; @@ -11,9 +11,6 @@ use futures::{stream::FusedStream, Stream, StreamExt}; use std::task::Poll; use std::{cmp::Ordering, iter::once}; -type Key = Box<[u8]>; -type Value = Vec; - /// Represents an ongoing iteration over a node and its children. enum IterationNode<'a> { /// This node has not been returned yet. @@ -177,7 +174,7 @@ fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( // Invariant: `node`'s key is a prefix of `key`. let mut node = merkle.get_node(root_node)?; - // Invariant: [matched_key_nibbles] is the key of `node` at the start + // Invariant: `matched_key_nibbles` is the key of `node` at the start // of each loop iteration. let mut matched_key_nibbles = vec![]; @@ -396,6 +393,138 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' } } +enum PathIteratorState<'a> { + Iterating { + /// The key, as nibbles, of the node at `address`, without the + /// node's partial path (if any) at the end. + /// Invariant: If this node has a parent, the parent's key is a + /// prefix of the key we're traversing to. + /// Note the node at `address` may not have a key which is a + /// prefix of the key we're traversing to. + matched_key: Vec, + unmatched_key: NibblesIterator<'a, 0>, + address: DiskAddress, + }, + Exhausted, +} + +/// Iterates over all nodes on the path to a given key starting from the root. +/// All nodes are branch nodes except possibly the last, which may be a leaf. +/// If the key is in the trie, the last node is the one at the given key. +/// Otherwise, the last node proves the non-existence of the key. +/// Specifically, if during the traversal, we encounter: +/// * A branch node with no child at the index of the next nibble in the key, +/// then the branch node proves the non-existence of the key. +/// * A node (either branch or leaf) whose partial path doesn't match the +/// remaining unmatched key, the node proves the non-existence of the key. +/// Note that thi means that the last node's key isn't necessarily a prefix of +/// the key we're traversing to. +pub struct PathIterator<'a, 'b, S, T> { + state: PathIteratorState<'b>, + merkle: &'a Merkle, +} + +impl<'a, 'b, S: ShaleStore + Send + Sync, T> PathIterator<'a, 'b, S, T> { + pub(super) fn new( + merkle: &'a Merkle, + sentinel_node: NodeObjRef<'a>, + key: &'b [u8], + ) -> Self { + let root = match sentinel_node.inner() { + NodeType::Branch(branch) => match branch.children[0] { + Some(root) => root, + None => { + return Self { + state: PathIteratorState::Exhausted, + merkle, + } + } + }, + _ => unreachable!("sentinel node is not a branch"), + }; + + Self { + merkle, + state: PathIteratorState::Iterating { + matched_key: vec![], + unmatched_key: Nibbles::new(key).into_iter(), + address: root, + }, + } + } +} + +impl<'a, 'b, S: ShaleStore + Send + Sync, T> Iterator for PathIterator<'a, 'b, S, T> { + type Item = Result<(Key, NodeObjRef<'a>), MerkleError>; + + fn next(&mut self) -> Option { + // destructuring is necessary here because we need mutable access to `state` + // at the same time as immutable access to `merkle`. + let Self { state, merkle } = &mut *self; + + match state { + PathIteratorState::Exhausted => None, + PathIteratorState::Iterating { + matched_key, + unmatched_key, + address, + } => { + let node = match merkle.get_node(*address) { + Ok(node) => node, + Err(e) => return Some(Err(e)), + }; + + let partial_path = match node.inner() { + NodeType::Branch(branch) => &branch.path, + NodeType::Leaf(leaf) => &leaf.path, + _ => unreachable!("extension nodes shouldn't exist"), + }; + + let (comparison, unmatched_key) = + compare_partial_path(partial_path.iter(), unmatched_key); + + matched_key.extend(partial_path.iter()); + let node_key = matched_key.clone().into_boxed_slice(); + + match comparison { + Ordering::Less | Ordering::Greater => { + self.state = PathIteratorState::Exhausted; + Some(Ok((node_key, node))) + } + Ordering::Equal => match node.inner() { + NodeType::Extension(_) => unreachable!(), + NodeType::Leaf(_) => { + self.state = PathIteratorState::Exhausted; + Some(Ok((node_key, node))) + } + NodeType::Branch(branch) => { + let Some(next_unmatched_key_nibble) = unmatched_key.next() else { + // There's no more key to match. We're done. + self.state = PathIteratorState::Exhausted; + return Some(Ok((node_key, node))); + }; + + #[allow(clippy::indexing_slicing)] + let Some(child) = branch.children[next_unmatched_key_nibble as usize] else { + // There's no child at the index of the next nibble in the key. + // The node we're traversing to isn't in the trie. + self.state = PathIteratorState::Exhausted; + return Some(Ok((node_key, node))); + }; + + matched_key.push(next_unmatched_key_nibble); + + *address = child; + + Some(Ok((node_key, node))) + } + }, + } + } + } + } +} + /// Takes in an iterator over a node's partial path and an iterator over the /// unmatched portion of a key. /// The first returned element is: @@ -476,6 +605,128 @@ mod tests { } } + #[test_case(&[]; "empty key")] + #[test_case(&[1]; "non-empty key")] + #[tokio::test] + async fn path_iterate_empty_merkle_empty_key(key: &[u8]) { + let merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + let sentinel_node = merkle.get_node(root).unwrap(); + let mut stream = merkle.path_iter(sentinel_node, key); + assert!(stream.next().is_none()); + } + + #[test_case(&[]; "empty key")] + #[test_case(&[13]; "prefix of singleton key")] + #[test_case(&[13, 37]; "match singleton key")] + #[test_case(&[13, 37,1]; "suffix of singleton key")] + #[test_case(&[255]; "no key nibbles match singleton key")] + #[tokio::test] + async fn path_iterate_singleton_merkle(key: &[u8]) { + let mut merkle = create_test_merkle(); + let root = merkle.init_root().unwrap(); + + merkle.insert(vec![0x13, 0x37], vec![0x42], root).unwrap(); + + let sentinel_node = merkle.get_node(root).unwrap(); + + let mut stream = merkle.path_iter(sentinel_node, key); + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + + assert_eq!(key, vec![0x01, 0x03, 0x03, 0x07].into_boxed_slice()); + assert_eq!(node.inner().as_leaf().unwrap().data, vec![0x42].into()); + + assert!(stream.next().is_none()); + } + + #[test_case(&[0x00, 0x00, 0x00, 0xFF]; "leaf key")] + #[test_case(&[0x00, 0x00, 0x00, 0xF3]; "leaf sibling key")] + #[test_case(&[0x00, 0x00, 0x00, 0xFF, 0x01]; "past leaf key")] + #[tokio::test] + async fn path_iterate_non_singleton_merkle_seek_leaf(key: &[u8]) { + let (merkle, root) = created_populated_merkle(); + + let sentinel_node = merkle.get_node(root).unwrap(); + + let mut stream = merkle.path_iter(sentinel_node, key); + + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + assert_eq!(key, vec![0x00, 0x00].into_boxed_slice()); + assert!(node.inner().as_branch().unwrap().value.is_none()); + + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + assert_eq!( + key, + vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() + ); + assert_eq!( + node.inner().as_branch().unwrap().value, + Some(vec![0x00, 0x00, 0x00].into()), + ); + + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + assert_eq!( + key, + vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice() + ); + assert_eq!( + node.inner().as_leaf().unwrap().data, + vec![0x00, 0x00, 0x00, 0x0FF].into(), + ); + + assert!(stream.next().is_none()); + } + + #[tokio::test] + async fn path_iterate_non_singleton_merkle_seek_branch() { + let (merkle, root) = created_populated_merkle(); + + let key = &[0x00, 0x00, 0x00]; + + let sentinel_node = merkle.get_node(root).unwrap(); + let mut stream = merkle.path_iter(sentinel_node, key); + + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + assert_eq!(key, vec![0x00, 0x00].into_boxed_slice()); + assert!(node.inner().as_branch().unwrap().value.is_none()); + + let (key, node) = match stream.next() { + Some(Ok((key, node))) => (key, node), + Some(Err(e)) => panic!("{:?}", e), + None => panic!("unexpected end of iterator"), + }; + assert_eq!( + key, + vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() + ); + assert_eq!( + node.inner().as_branch().unwrap().value, + Some(vec![0x00, 0x00, 0x00].into()), + ); + + assert!(stream.next().is_none()); + } + #[tokio::test] async fn key_value_iterate_empty() { let merkle = create_test_merkle(); From b1eaeb550a27f5839f176aeb685db511a3f679ee Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 19 Feb 2024 13:34:54 -0500 Subject: [PATCH 0473/1053] Add proto file for service exposing views/proposals (#529) Co-authored-by: Ron Kuris --- grpc-testtool/proto/merkle/merkle.proto | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 grpc-testtool/proto/merkle/merkle.proto diff --git a/grpc-testtool/proto/merkle/merkle.proto b/grpc-testtool/proto/merkle/merkle.proto new file mode 100644 index 000000000000..7519288c77ce --- /dev/null +++ b/grpc-testtool/proto/merkle/merkle.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +package merkle; + +import "google/protobuf/empty.proto"; + +service Merkle { + rpc NewProposal(NewProposalRequest) returns (NewProposalResponse); + rpc ProposalCommit(ProposalCommitRequest) returns (ProposalCommitResponse); + + rpc NewView(NewViewRequest) returns (NewViewResponse); + // The methods below may be called with a view ID that corresponds to either a (committable) proposal + // or (non-committable) historical view. + rpc ViewHas(ViewHasRequest) returns (ViewHasResponse); + rpc ViewGet(ViewGetRequest) returns (ViewGetResponse); + rpc ViewNewIteratorWithStartAndPrefix(ViewNewIteratorWithStartAndPrefixRequest) returns (ViewNewIteratorWithStartAndPrefixResponse); + rpc ViewRelease(ViewReleaseRequest) returns (google.protobuf.Empty); +} + +message NewProposalRequest { + // If not given, the parent view is the current database revision. + optional uint32 parent_view_id = 1; + repeated PutRequest puts = 2; + // The keys being deleted. + repeated bytes deletes = 3; +} + +message NewProposalResponse { + uint32 proposal_id = 1; +} + +message ProposalCommitRequest { + uint32 proposal_id = 1; +} + +message ProposalCommitResponse { + CommitError err = 1; +} + +message NewViewRequest { + bytes root_id = 1; +} + +message NewViewResponse { + uint32 view_id = 1; +} + +message ViewHasRequest { + uint32 view_id = 1; + bytes key = 2; +} + +message ViewHasResponse { + bool has = 1; +} + +message ViewGetRequest { + uint32 view_id = 1; + bytes key = 2; +} + +message ViewGetResponse { + bytes value = 1; + GetError err = 2; +} + +message ViewNewIteratorWithStartAndPrefixRequest { + uint32 view_id = 1; + bytes start = 2; + bytes prefix = 3; +} + +message ViewNewIteratorWithStartAndPrefixResponse { + uint32 iterator_id = 1; +} + +message ViewReleaseRequest { + uint32 view_id = 1; +} + +// TODO import this from the rpcdb package. +message PutRequest { + bytes key = 1; + bytes value = 2; +} + +enum GetError { + // ERROR_UNSPECIFIED_GET is used to indicate that no error occurred. + ERROR_UNSPECIFIED_GET = 0; + ERROR_CLOSED_GET = 1; + ERROR_NOT_FOUND = 2; +} + +enum CommitError { + // ERROR_UNSPECIFIED_COMMIT is used to indicate that no error occurred. + ERROR_UNSPECIFIED_COMMIT = 0; + ERROR_CLOSED_COMMIT = 1; + ERROR_INVALID = 2; + ERROR_COMMITTED = 3; + ERROR_PARENT_NOT_DATABASE = 4; + ERROR_NON_PROPOSAL_ID = 5; +} From 2e6480dbe00598da0537d04e5459c3271bbc6ca6 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 19 Feb 2024 13:48:08 -0500 Subject: [PATCH 0474/1053] Use path iterator in `merkle.prove` and `merkle.get_node_by_key` (#534) Co-authored-by: Ron Kuris --- firewood/src/merkle.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 305e8abc31e6..18c15863a341 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1504,7 +1504,21 @@ impl + Send + Sync, T> Merkle { node_ref: NodeObjRef<'a>, key: K, ) -> Result>, MerkleError> { - self.get_node_by_key_with_callbacks(node_ref, key, |_, _| {}, |_, _| {}) + let key = key.as_ref(); + let path_iter = self.path_iter(node_ref, key); + + match path_iter.last() { + None => Ok(None), + Some(Err(e)) => Err(e), + Some(Ok((node_key, node))) => { + let key_nibbles = Nibbles::<0>::new(key).into_iter(); + if key_nibbles.eq(node_key.iter().copied()) { + Ok(Some(node)) + } else { + Ok(None) + } + } + } } fn get_node_and_parents_by_key<'a, K: AsRef<[u8]>>( @@ -1676,24 +1690,16 @@ impl + Send + Sync, T> Merkle { return Ok(Proof(proofs)); } - let root_node = self.get_node(root)?; - - let mut nodes = Vec::new(); + let sentinel_node = self.get_node(root)?; - let node = self.get_node_by_key_with_callbacks( - root_node, - key, - |node, _| nodes.push(node), - |_, _| {}, - )?; + let path_iter = self.path_iter(sentinel_node, key.as_ref()); - if let Some(node) = node { - nodes.push(node.as_ptr()); - } + let nodes = path_iter + .map(|result| result.map(|(_, node)| node)) + .collect::, MerkleError>>()?; // Get the hashes of the nodes. - for node in nodes.into_iter().skip(1) { - let node = self.get_node(node)?; + for node in nodes.into_iter() { let encoded = <&[u8]>::clone(&node.get_encoded::(self.store.as_ref())); let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(encoded).into(); proofs.insert(hash, encoded.to_vec()); From 90aa00a434c2a617ec0a8ae98bbcf87374614f18 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 19 Feb 2024 16:18:52 -0800 Subject: [PATCH 0475/1053] Name the diskbuffer thread (#540) Co-authored-by: Dan Laine --- firewood/src/db.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b278b564a341..88e2a8b3257a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -520,12 +520,14 @@ impl Db { let disk_requester = DiskBufferRequester::new(sender); let buffer = cfg.buffer.clone(); #[allow(clippy::unwrap_used)] - let disk_thread = block_in_place(|| { - Some(std::thread::spawn(move || { - let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).unwrap(); - disk_buffer.run() - })) - }); + let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).expect("DiskBuffer::new"); + + let disk_thread = Some( + std::thread::Builder::new() + .name("DiskBuffer".to_string()) + .spawn(move || disk_buffer.run()) + .expect("thread spawn should succeed"), + ); #[allow(clippy::unwrap_used)] let root_hash_cache: Arc = CachedSpace::new( From 01eb6e0ff03b944af0a4b8a38494ad55db43253a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 19 Feb 2024 16:34:30 -0800 Subject: [PATCH 0476/1053] Removed the only dbg! macro in firewood (#539) --- firewood/benches/hashops.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 0809d59fed77..ed4e26b0c24a 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -165,7 +165,7 @@ fn bench_db(criterion: &mut Criterion) { batch_ops }, |batch_ops| async { - let db_path = dbg!(std::env::temp_dir()); + let db_path = std::env::temp_dir(); let db_path = db_path.join("benchmark_db"); let cfg = DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); From c1a0bd37c42255701ab09c439221b2e4205ad1ef Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 20 Feb 2024 08:55:31 -0500 Subject: [PATCH 0477/1053] remove dead code `remove_old` and helpers (#542) --- firewood/src/merkle.rs | 363 ----------------------------- firewood/src/merkle/node/branch.rs | 16 -- 2 files changed, 379 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 18c15863a341..9b40aa2ecc37 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -928,309 +928,6 @@ impl + Send + Sync, T> Merkle { Ok((parents.into_iter().rev().map(|(node, _)| node), deleted)) } - #[allow(clippy::unwrap_used)] - fn after_remove_leaf( - &self, - parents: &mut ParentRefs, - deleted: &mut Vec, - ) -> Result<(), MerkleError> { - let (b_chd, val) = { - let (mut b_ref, b_idx) = parents.pop().unwrap(); - // the immediate parent of a leaf must be a branch - #[allow(clippy::unwrap_used)] - b_ref - .write(|b| { - #[allow(clippy::indexing_slicing)] - (b.inner.as_branch_mut().unwrap().children[b_idx as usize] = None); - b.rehash() - }) - .unwrap(); - #[allow(clippy::unwrap_used)] - let b_inner = b_ref.inner.as_branch().unwrap(); - let (b_chd, has_chd) = b_inner.single_child(); - if (has_chd && (b_chd.is_none() || b_inner.value.is_some())) || parents.is_empty() { - return Ok(()); - } - deleted.push(b_ref.as_ptr()); - (b_chd, b_inner.value.clone()) - }; - #[allow(clippy::unwrap_used)] - let (mut p_ref, p_idx) = parents.pop().unwrap(); - let p_ptr = p_ref.as_ptr(); - if let Some(val) = val { - match &p_ref.inner { - NodeType::Branch(_) => { - // from: [p: Branch] -> [b (v)]x -> [Leaf]x - // to: [p: Branch] -> [Leaf (v)] - let leaf = self - .put_node(Node::from_leaf(LeafNode::new(PartialPath(Vec::new()), val)))? - .as_ptr(); - #[allow(clippy::unwrap_used)] - p_ref - .write(|p| { - #[allow(clippy::indexing_slicing)] - (p.inner.as_branch_mut().unwrap().children[p_idx as usize] = - Some(leaf)); - p.rehash() - }) - .unwrap(); - } - NodeType::Extension(n) => { - // from: P -> [p: Ext]x -> [b (v)]x -> [leaf]x - // to: P -> [Leaf (v)] - let leaf = self - .put_node(Node::from_leaf(LeafNode::new( - PartialPath(n.path.clone().into_inner()), - val, - )))? - .as_ptr(); - deleted.push(p_ptr); - set_parent(leaf, parents); - } - _ => unreachable!(), - } - } else { - #[allow(clippy::unwrap_used)] - let (c_ptr, idx) = b_chd.unwrap(); - let mut c_ref = self.get_node(c_ptr)?; - match &c_ref.inner { - NodeType::Branch(_) => { - drop(c_ref); - match &p_ref.inner { - NodeType::Branch(_) => { - // ____[Branch] - // / - // from: [p: Branch] -> [b]x* - // \____[Leaf]x - // to: [p: Branch] -> [Ext] -> [Branch] - let ext = self - .put_node(Node::from(NodeType::Extension(ExtNode { - path: PartialPath(vec![idx]), - child: c_ptr, - child_encoded: None, - })))? - .as_ptr(); - set_parent(ext, &mut [(p_ref, p_idx)]); - } - NodeType::Extension(_) => { - // ____[Branch] - // / - // from: [p: Ext] -> [b]x* - // \____[Leaf]x - // to: [p: Ext] -> [Branch] - write_node!( - self, - p_ref, - |p| { - let pp = p.inner.as_extension_mut().unwrap(); - pp.path.0.push(idx); - *pp.chd_mut() = c_ptr; - p.rehash(); - }, - parents, - deleted - ); - } - _ => unreachable!(), - } - } - NodeType::Leaf(_) | NodeType::Extension(_) => { - match &p_ref.inner { - NodeType::Branch(_) => { - // ____[Leaf/Ext] - // / - // from: [p: Branch] -> [b]x* - // \____[Leaf]x - // to: [p: Branch] -> [Leaf/Ext] - let write_result = c_ref.write(|c| { - let partial_path = match &mut c.inner { - NodeType::Leaf(n) => &mut n.path, - NodeType::Extension(n) => &mut n.path, - _ => unreachable!(), - }; - - partial_path.0.insert(0, idx); - c.rehash() - }); - - let c_ptr = if write_result.is_err() { - deleted.push(c_ptr); - self.put_node(c_ref.clone())?.as_ptr() - } else { - c_ptr - }; - - drop(c_ref); - - #[allow(clippy::unwrap_used)] - p_ref - .write(|p| { - #[allow(clippy::indexing_slicing)] - (p.inner.as_branch_mut().unwrap().children[p_idx as usize] = - Some(c_ptr)); - p.rehash() - }) - .unwrap(); - } - NodeType::Extension(n) => { - // ____[Leaf/Ext] - // / - // from: P -> [p: Ext]x -> [b]x* - // \____[Leaf]x - // to: P -> [p: Leaf/Ext] - deleted.push(p_ptr); - - let write_failed = write_node!( - self, - c_ref, - |c| { - let mut path = n.path.clone().into_inner(); - path.push(idx); - let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.path, - NodeType::Extension(n) => &mut n.path, - _ => unreachable!(), - }; - path.extend(&**path0); - *path0 = PartialPath(path); - c.rehash() - }, - parents, - deleted - ); - - if !write_failed { - drop(c_ref); - set_parent(c_ptr, parents); - } - } - _ => unreachable!(), - } - } - } - } - Ok(()) - } - - fn after_remove_branch( - &self, - (c_ptr, idx): (DiskAddress, u8), - parents: &mut ParentRefs, - deleted: &mut Vec, - ) -> Result<(), MerkleError> { - // [b] -> [u] -> [c] - #[allow(clippy::unwrap_used)] - let (mut b_ref, b_idx) = parents.pop().unwrap(); - #[allow(clippy::unwrap_used)] - let mut c_ref = self.get_node(c_ptr).unwrap(); - match &c_ref.inner { - NodeType::Branch(_) => { - drop(c_ref); - let mut err = None; - write_node!( - self, - b_ref, - |b| { - if let Err(e) = (|| { - match &mut b.inner { - NodeType::Branch(n) => { - // from: [Branch] -> [Branch]x -> [Branch] - // to: [Branch] -> [Ext] -> [Branch] - #[allow(clippy::indexing_slicing)] - (n.children[b_idx as usize] = Some( - self.put_node(Node::from(NodeType::Extension(ExtNode { - path: PartialPath(vec![idx]), - child: c_ptr, - child_encoded: None, - })))? - .as_ptr(), - )); - } - NodeType::Extension(n) => { - // from: [Ext] -> [Branch]x -> [Branch] - // to: [Ext] -> [Branch] - n.path.0.push(idx); - *n.chd_mut() = c_ptr - } - _ => unreachable!(), - } - b.rehash(); - Ok(()) - })() { - err = Some(Err(e)) - } - }, - parents, - deleted - ); - if let Some(e) = err { - return e; - } - } - NodeType::Leaf(_) | NodeType::Extension(_) => match &b_ref.inner { - NodeType::Branch(_) => { - // from: [Branch] -> [Branch]x -> [Leaf/Ext] - // to: [Branch] -> [Leaf/Ext] - let write_result = c_ref.write(|c| { - match &mut c.inner { - NodeType::Leaf(n) => &mut n.path, - NodeType::Extension(n) => &mut n.path, - _ => unreachable!(), - } - .0 - .insert(0, idx); - c.rehash() - }); - if write_result.is_err() { - deleted.push(c_ptr); - self.put_node(c_ref.clone())?.as_ptr() - } else { - c_ptr - }; - drop(c_ref); - #[allow(clippy::unwrap_used)] - b_ref - .write(|b| { - #[allow(clippy::indexing_slicing)] - (b.inner.as_branch_mut().unwrap().children[b_idx as usize] = - Some(c_ptr)); - b.rehash() - }) - .unwrap(); - } - NodeType::Extension(n) => { - // from: P -> [Ext] -> [Branch]x -> [Leaf/Ext] - // to: P -> [Leaf/Ext] - let write_result = c_ref.write(|c| { - let mut path = n.path.clone().into_inner(); - path.push(idx); - let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.path, - NodeType::Extension(n) => &mut n.path, - _ => unreachable!(), - }; - path.extend(&**path0); - *path0 = PartialPath(path); - c.rehash() - }); - - let c_ptr = if write_result.is_err() { - deleted.push(c_ptr); - self.put_node(c_ref.clone())?.as_ptr() - } else { - c_ptr - }; - - deleted.push(b_ref.as_ptr()); - drop(c_ref); - set_parent(c_ptr, parents); - } - _ => unreachable!(), - }, - } - Ok(()) - } - pub fn remove>( &mut self, key: K, @@ -1408,66 +1105,6 @@ impl + Send + Sync, T> Merkle { Ok(data.map(|data| data.0)) } - pub fn remove_old>( - &mut self, - key: K, - root: DiskAddress, - ) -> Result>, MerkleError> { - if root.is_null() { - return Ok(None); - } - - let (found, parents, deleted) = { - let (node_ref, mut parents) = - self.get_node_and_parents_by_key(self.get_node(root)?, key)?; - - let Some(mut node_ref) = node_ref else { - return Ok(None); - }; - let mut deleted = Vec::new(); - let mut found = None; - - match &node_ref.inner { - NodeType::Branch(n) => { - let (c_chd, _) = n.single_child(); - - #[allow(clippy::unwrap_used)] - node_ref - .write(|u| { - found = u.inner.as_branch_mut().unwrap().value.take(); - u.rehash() - }) - .unwrap(); - - if let Some((c_ptr, idx)) = c_chd { - deleted.push(node_ref.as_ptr()); - self.after_remove_branch((c_ptr, idx), &mut parents, &mut deleted)? - } - } - - NodeType::Leaf(n) => { - found = Some(n.data.clone()); - deleted.push(node_ref.as_ptr()); - self.after_remove_leaf(&mut parents, &mut deleted)? - } - _ => (), - }; - - (found, parents, deleted) - }; - - #[allow(clippy::unwrap_used)] - for (mut r, _) in parents.into_iter().rev() { - r.write(|u| u.rehash()).unwrap(); - } - - for ptr in deleted.into_iter() { - self.free_node(ptr)?; - } - - Ok(found.map(|e| e.0)) - } - fn remove_tree_( &self, u: DiskAddress, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 53b8bac6e710..2b05b715679f 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -95,22 +95,6 @@ impl BranchNode { &mut self.children_encoded } - pub(crate) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { - let mut has_chd = false; - let mut only_chd = None; - for (i, c) in self.children.iter().enumerate() { - if c.is_some() { - has_chd = true; - if only_chd.is_some() { - only_chd = None; - break; - } - only_chd = (*c).map(|e| (e, i as u8)) - } - } - (only_chd, has_chd) - } - pub(super) fn decode(buf: &[u8]) -> Result { let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; From b3f6e28fe09ee5b8caaff9dd08025c0f5db4a36a Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 20 Feb 2024 15:01:22 -0500 Subject: [PATCH 0478/1053] update merkle proto definition (#546) --- grpc-testtool/proto/merkle/merkle.proto | 83 +++++++++++++------------ 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/grpc-testtool/proto/merkle/merkle.proto b/grpc-testtool/proto/merkle/merkle.proto index 7519288c77ce..94261d3ea099 100644 --- a/grpc-testtool/proto/merkle/merkle.proto +++ b/grpc-testtool/proto/merkle/merkle.proto @@ -4,49 +4,52 @@ package merkle; import "google/protobuf/empty.proto"; +// Methods on this service return status code NOT_FOUND if a requested +// view, iterator or root hash is not found. service Merkle { - rpc NewProposal(NewProposalRequest) returns (NewProposalResponse); - rpc ProposalCommit(ProposalCommitRequest) returns (ProposalCommitResponse); + rpc NewProposal(NewProposalRequest) returns (NewProposalResponse); + rpc ProposalCommit(ProposalCommitRequest) returns (google.protobuf.Empty); - rpc NewView(NewViewRequest) returns (NewViewResponse); - // The methods below may be called with a view ID that corresponds to either a (committable) proposal - // or (non-committable) historical view. - rpc ViewHas(ViewHasRequest) returns (ViewHasResponse); - rpc ViewGet(ViewGetRequest) returns (ViewGetResponse); - rpc ViewNewIteratorWithStartAndPrefix(ViewNewIteratorWithStartAndPrefixRequest) returns (ViewNewIteratorWithStartAndPrefixResponse); - rpc ViewRelease(ViewReleaseRequest) returns (google.protobuf.Empty); + rpc NewView(NewViewRequest) returns (NewViewResponse); + // The methods below may be called with an ID that corresponds to either a (committable) proposal + // or (non-committable) historical view. + rpc ViewHas(ViewHasRequest) returns (ViewHasResponse); + rpc ViewGet(ViewGetRequest) returns (ViewGetResponse); + rpc ViewNewIteratorWithStartAndPrefix(ViewNewIteratorWithStartAndPrefixRequest) returns (ViewNewIteratorWithStartAndPrefixResponse); + // Returns status code NOT_FOUND when the iterator is done. + rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); + rpc IteratorError(IteratorErrorRequest) returns (google.protobuf.Empty); + // Iterator can't be used (even to check error) after release. + rpc IteratorRelease(IteratorReleaseRequest) returns (google.protobuf.Empty); + rpc ViewRelease(ViewReleaseRequest) returns (google.protobuf.Empty); } message NewProposalRequest { // If not given, the parent view is the current database revision. - optional uint32 parent_view_id = 1; + optional uint32 parent_id = 1; repeated PutRequest puts = 2; // The keys being deleted. repeated bytes deletes = 3; } message NewProposalResponse { - uint32 proposal_id = 1; + uint32 id = 1; } message ProposalCommitRequest { - uint32 proposal_id = 1; -} - -message ProposalCommitResponse { - CommitError err = 1; + uint32 id = 1; } message NewViewRequest { - bytes root_id = 1; + bytes root_hash = 1; } message NewViewResponse { - uint32 view_id = 1; + uint32 id = 1; } message ViewHasRequest { - uint32 view_id = 1; + uint32 id = 1; bytes key = 2; } @@ -55,27 +58,42 @@ message ViewHasResponse { } message ViewGetRequest { - uint32 view_id = 1; + uint32 id = 1; bytes key = 2; } message ViewGetResponse { bytes value = 1; - GetError err = 2; } message ViewNewIteratorWithStartAndPrefixRequest { - uint32 view_id = 1; + uint32 id = 1; bytes start = 2; bytes prefix = 3; } message ViewNewIteratorWithStartAndPrefixResponse { - uint32 iterator_id = 1; + uint32 id = 1; +} + +message IteratorNextRequest { + uint32 id = 1; +} + +message IteratorNextResponse { + PutRequest data = 1; +} + +message IteratorErrorRequest { + uint32 id = 1; +} + +message IteratorReleaseRequest { + uint32 id = 1; } message ViewReleaseRequest { - uint32 view_id = 1; + uint32 id = 1; } // TODO import this from the rpcdb package. @@ -83,20 +101,3 @@ message PutRequest { bytes key = 1; bytes value = 2; } - -enum GetError { - // ERROR_UNSPECIFIED_GET is used to indicate that no error occurred. - ERROR_UNSPECIFIED_GET = 0; - ERROR_CLOSED_GET = 1; - ERROR_NOT_FOUND = 2; -} - -enum CommitError { - // ERROR_UNSPECIFIED_COMMIT is used to indicate that no error occurred. - ERROR_UNSPECIFIED_COMMIT = 0; - ERROR_CLOSED_COMMIT = 1; - ERROR_INVALID = 2; - ERROR_COMMITTED = 3; - ERROR_PARENT_NOT_DATABASE = 4; - ERROR_NON_PROPOSAL_ID = 5; -} From f7d7414b8e8713206887898606576d2d28ee2400 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 20 Feb 2024 20:21:16 +0000 Subject: [PATCH 0479/1053] Box over vec for io (#543) --- firewood/src/db/proposal.rs | 4 ++-- firewood/src/storage/buffer.rs | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index e842c1087c9a..009d0ee39a08 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -228,7 +228,7 @@ impl Proposal { // schedule writes to the disk rev_inner.disk_requester.write( - vec![ + Box::new([ BufferWrite { space_id: store.merkle.payload.id(), delta: merkle_payload_redo, @@ -241,7 +241,7 @@ impl Proposal { space_id: rev_inner.root_hash_staging.id(), delta: root_hash_redo, }, - ], + ]), AshRecord( [ (MERKLE_META_SPACE, merkle_meta_wal), diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index c4ada62342db..8621ecf8d000 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -31,12 +31,14 @@ use tokio::{ }; use typed_builder::TypedBuilder; +type BufferWrites = Box<[BufferWrite]>; + #[derive(Debug)] pub enum BufferCmd { /// Initialize the Wal. InitWal(PathBuf, String), /// Process a write batch against the underlying store. - WriteBatch(Vec, AshRecord), + WriteBatch(BufferWrites, AshRecord), /// Get a page from the disk buffer. GetPage((SpaceId, u64), oneshot::Sender>), CollectAsh(usize, oneshot::Sender>), @@ -343,7 +345,7 @@ async fn run_wal_queue( wal: Rc>>, pending: Rc>>, file_pools: Rc>; 255]>>, - mut writes: mpsc::Receiver<(Vec, AshRecord)>, + mut writes: mpsc::Receiver<(BufferWrites, AshRecord)>, fc_notifier: Rc, aiomgr: Rc, ) { @@ -356,14 +358,14 @@ async fn run_wal_queue( if let Some((bw, ac)) = writes.recv().await { records.push(ac); - bwrites.extend(bw); + bwrites.extend(bw.into_vec()); } else { break; } while let Ok((bw, ac)) = writes.try_recv() { records.push(ac); - bwrites.extend(bw); + bwrites.extend(bw.into_vec()); if records.len() >= max.batch { break; @@ -467,8 +469,8 @@ async fn process( wal_cfg: &WalConfig, req: BufferCmd, max: WalQueueMax, - wal_in: mpsc::Sender<(Vec, AshRecord)>, - writes: &mut Option, AshRecord)>>, + wal_in: mpsc::Sender<(BufferWrites, AshRecord)>, + writes: &mut Option>, ) -> bool { match req { BufferCmd::Shutdown => return false, @@ -590,7 +592,7 @@ impl DiskBufferRequester { } /// Sends a batch of writes to the buffer. - pub fn write(&self, page_batch: Vec, write_batch: AshRecord) { + pub fn write(&self, page_batch: BufferWrites, write_batch: AshRecord) { self.sender .send(BufferCmd::WriteBatch(page_batch, write_batch)) .map_err(StoreError::Send) @@ -808,10 +810,10 @@ mod tests { // page is not yet persisted to disk. assert!(disk_requester.get_page(STATE_SPACE, 0).is_none()); disk_requester.write( - vec![BufferWrite { + Box::new([BufferWrite { space_id: STATE_SPACE, delta: redo_delta, - }], + }]), AshRecord([(STATE_SPACE, wal)].into()), ); @@ -943,7 +945,7 @@ mod tests { disk_requester } - fn create_batches(rev_mut: &StoreRevMut) -> (Vec, AshRecord) { + fn create_batches(rev_mut: &StoreRevMut) -> (BufferWrites, AshRecord) { let deltas = std::mem::replace( &mut *rev_mut.deltas.write(), StoreRevMutDelta { @@ -959,10 +961,10 @@ mod tests { } pages.sort_by_key(|p| p.0); - let page_batch = vec![BufferWrite { + let page_batch = Box::new([BufferWrite { space_id: STATE_SPACE, delta: StoreDelta(pages), - }]; + }]); let write_batch = AshRecord([(STATE_SPACE, deltas.plain)].into()); (page_batch, write_batch) From 56e2d2d92ddf1b60dda97640a5b8bdb765dc5af8 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 21 Feb 2024 09:06:07 -0500 Subject: [PATCH 0480/1053] Update or insert nodes (#545) Signed-off-by: Richard Pringle Co-authored-by: Ron Kuris --- firewood/src/merkle.rs | 258 ++++++++++++++++++++++++++++++----- firewood/src/merkle/node.rs | 8 ++ firewood/src/merkle/proof.rs | 4 +- firewood/src/shale/mod.rs | 10 +- 4 files changed, 236 insertions(+), 44 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 9b40aa2ecc37..168b44500108 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,7 +2,7 @@ use crate::db::{MutStore, SharedStore}; // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; -use crate::shale::{self, disk_address::DiskAddress, ObjWriteError, ShaleError, ShaleStore}; +use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError, ShaleStore}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use sha3::Digest; @@ -49,7 +49,7 @@ pub enum MerkleError { #[error("removing internal node references failed")] UnsetInternal, #[error("error updating nodes: {0}")] - WriteError(#[from] ObjWriteError), + WriteError(#[from] ObjWriteSizeError), #[error("merkle serde error: {0}")] BinarySerdeError(String), } @@ -523,10 +523,11 @@ impl + Send + Sync, T> Merkle { match (overlap.unique_a.len(), overlap.unique_b.len()) { // same node, overwrite the data (0, 0) => { - node.write(|node| { - node.inner.set_data(Data(val)); - node.rehash(); - })?; + self.update_data_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + Data(val), + )?; } // new node is a child of the old node @@ -571,12 +572,13 @@ impl + Send + Sync, T> Merkle { let new_branch_path = overlap.shared.to_vec(); - node.write(move |old_leaf| { - *old_leaf.inner.path_mut() = PartialPath(old_leaf_path.to_vec()); - old_leaf.rehash(); - })?; - - let old_leaf = node.as_ptr(); + let old_leaf = self + .update_path_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + PartialPath(old_leaf_path.to_vec()), + )? + .as_ptr(); let mut new_branch = BranchNode { path: PartialPath(new_branch_path), @@ -607,18 +609,19 @@ impl + Send + Sync, T> Merkle { let new_branch_path = overlap.shared.to_vec(); - node.write(move |old_leaf| { - *old_leaf.inner.path_mut() = PartialPath(old_leaf_path.to_vec()); - old_leaf.rehash(); - })?; + let old_leaf = self + .update_path_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + PartialPath(old_leaf_path.to_vec()), + )? + .as_ptr(); let new_leaf = Node::from_leaf(LeafNode::new( PartialPath(new_leaf_path), Data(val), )); - let old_leaf = node.as_ptr(); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { @@ -680,11 +683,11 @@ impl + Send + Sync, T> Merkle { match (overlap.unique_a.len(), overlap.unique_b.len()) { // same node, overwrite the data (0, 0) => { - node.write(|node| { - node.inner.set_data(Data(val)); - node.rehash(); - })?; - + self.update_data_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + Data(val), + )?; break None; } @@ -732,13 +735,13 @@ impl + Send + Sync, T> Merkle { let new_branch_path = overlap.shared.to_vec(); - node.write(move |old_branch| { - *old_branch.inner.path_mut() = - PartialPath(old_branch_path.to_vec()); - old_branch.rehash(); - })?; - - let old_branch = node.as_ptr(); + let old_branch = self + .update_path_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + PartialPath(old_branch_path.to_vec()), + )? + .as_ptr(); let mut new_branch = BranchNode { path: PartialPath(new_branch_path), @@ -771,19 +774,19 @@ impl + Send + Sync, T> Merkle { let new_branch_path = overlap.shared.to_vec(); - node.write(move |old_branch| { - *old_branch.inner.path_mut() = - PartialPath(old_branch_path.to_vec()); - old_branch.rehash(); - })?; + let old_branch = self + .update_path_and_move_node_if_larger( + (&mut parents, &mut deleted), + node, + PartialPath(old_branch_path.to_vec()), + )? + .as_ptr(); let new_leaf = Node::from_leaf(LeafNode::new( PartialPath(new_leaf_path), Data(val), )); - let old_branch = node.as_ptr(); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { @@ -1474,6 +1477,56 @@ impl + Send + Sync, T> Merkle { last_key_proof, })) } + + /// Try to update the [NodeObjRef]'s path in-place. If the update fails because the node can no longer fit at its old address, + /// then the old address is marked for deletion and the [Node] (with its update) is inserted at a new address. + fn update_path_and_move_node_if_larger<'a>( + &'a self, + (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), + mut node: NodeObjRef<'a>, + path: PartialPath, + ) -> Result, MerkleError> { + let write_result = node.write(|node| { + node.inner_mut().set_path(path); + node.rehash(); + }); + + self.move_node_if_write_failed((parents, to_delete), node, write_result) + } + + /// Try to update the [NodeObjRef]'s data/value in-place. If the update fails because the node can no longer fit at its old address, + /// then the old address is marked for deletion and the [Node] (with its update) is inserted at a new address. + fn update_data_and_move_node_if_larger<'a>( + &'a self, + (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), + mut node: NodeObjRef<'a>, + data: Data, + ) -> Result { + let write_result = node.write(|node| { + node.inner_mut().set_data(data); + node.rehash(); + }); + + self.move_node_if_write_failed((parents, to_delete), node, write_result) + } + + /// Checks if the `write_result` is an [ObjWriteSizeError]. If it is, then the `node` is moved to a new address and the old address is marked for deletion. + fn move_node_if_write_failed<'a>( + &'a self, + (parents, deleted): (&mut [(NodeObjRef, u8)], &mut Vec), + mut node: NodeObjRef<'a>, + write_result: Result<(), ObjWriteSizeError>, + ) -> Result, MerkleError> { + if let Err(ObjWriteSizeError) = write_result { + let old_node_address = node.as_ptr(); + node = self.put_node(node.clone())?; + deleted.push(old_node_address); + + set_parent(node.as_ptr(), parents); + } + + Ok(node) + } } fn set_parent(new_chd: DiskAddress, parents: &mut [(NodeObjRef, u8)]) { @@ -2270,6 +2323,7 @@ mod tests { } #[test] + #[ignore] fn single_key_proof_with_one_node() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -2306,4 +2360,134 @@ mod tests { assert_eq!(verified.as_deref(), Some(key1.as_slice())); } + + #[test] + #[ignore] + fn update_leaf_with_larger_path() -> Result<(), MerkleError> { + let path = vec![0x00]; + let data = vec![0x00]; + + let double_path = path + .clone() + .into_iter() + .chain(path.clone()) + .collect::>(); + + let node = Node::from_leaf(LeafNode { + path: PartialPath::from(path), + data: Data(data.clone()), + }); + + check_node_update(node, double_path, data) + } + + #[test] + #[ignore] + fn update_leaf_with_larger_data() -> Result<(), MerkleError> { + let path = vec![0x00]; + let data = vec![0x00]; + + let double_data = data + .clone() + .into_iter() + .chain(data.clone()) + .collect::>(); + + let node = Node::from_leaf(LeafNode { + path: PartialPath::from(path.clone()), + data: Data(data), + }); + + check_node_update(node, path, double_data) + } + + #[test] + #[ignore] + fn update_branch_with_larger_path() -> Result<(), MerkleError> { + let path = vec![0x00]; + let data = vec![0x00]; + + let double_path = path + .clone() + .into_iter() + .chain(path.clone()) + .collect::>(); + + let node = Node::from_branch(BranchNode { + path: PartialPath::from(path.clone()), + children: Default::default(), + value: Some(Data(data.clone())), + children_encoded: Default::default(), + }); + + check_node_update(node, double_path, data) + } + + #[test] + #[ignore] + fn update_branch_with_larger_data() -> Result<(), MerkleError> { + let path = vec![0x00]; + let data = vec![0x00]; + + let double_data = data + .clone() + .into_iter() + .chain(data.clone()) + .collect::>(); + + let node = Node::from_branch(BranchNode { + path: PartialPath::from(path.clone()), + children: Default::default(), + value: Some(Data(data)), + children_encoded: Default::default(), + }); + + check_node_update(node, path, double_data) + } + + fn check_node_update( + node: Node, + new_path: Vec, + new_data: Vec, + ) -> Result<(), MerkleError> { + let merkle = create_test_merkle(); + let root = merkle.init_root()?; + let root = merkle.get_node(root)?; + + let mut node_ref = merkle.put_node(node)?; + let addr = node_ref.as_ptr(); + + // make sure that doubling the path length will fail on a normal write + let write_result = node_ref.write(|node| { + node.inner_mut().set_path(PartialPath(new_path.clone())); + node.inner_mut().set_data(Data(new_data.clone())); + node.rehash(); + }); + + assert!(matches!(write_result, Err(ObjWriteSizeError))); + + let mut to_delete = vec![]; + // could be any branch node, convenient to use the root. + let mut parents = vec![(root, 0)]; + + let node = merkle.update_path_and_move_node_if_larger( + (&mut parents, &mut to_delete), + node_ref, + PartialPath(new_path.clone()), + )?; + + assert_ne!(node.as_ptr(), addr); + assert_eq!(&to_delete[0], &addr); + + let (path, data) = match node.inner() { + NodeType::Leaf(leaf) => (&leaf.path, Some(&leaf.data)), + NodeType::Branch(branch) => (&branch.path, branch.value.as_ref()), + _ => unreachable!(), + }; + + assert_eq!(path, &PartialPath(new_path)); + assert_eq!(data, Some(&Data(new_data))); + + Ok(()) + } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 3e3ac0f7a5cc..5df8321e867e 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -161,6 +161,14 @@ impl NodeType { } } + pub fn set_path(&mut self, path: PartialPath) { + match self { + NodeType::Branch(u) => u.path = path, + NodeType::Leaf(node) => node.path = path, + NodeType::Extension(node) => node.path = path, + } + } + pub fn set_data(&mut self, data: Data) { match self { NodeType::Branch(u) => u.value = Some(data), diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 4eaf0890be1a..83a29b4a4199 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use std::collections::HashMap; -use crate::shale::ObjWriteError; +use crate::shale::ObjWriteSizeError; use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; use crate::v2::api::HashKey; use nix::errno::Errno; @@ -58,7 +58,7 @@ pub enum ProofError { #[error("invalid root hash")] InvalidRootHash, #[error("{0}")] - WriteError(#[from] ObjWriteError), + WriteError(#[from] ObjWriteSizeError), } impl From for ProofError { diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 81fbd3b1e224..1d3cb79af61b 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -42,8 +42,8 @@ pub enum ShaleError { // this could probably included with ShaleError, // but keeping it separate for now as Obj/ObjRef might change in the near future #[derive(Debug, Error)] -#[error("write error")] -pub struct ObjWriteError; +#[error("object cannot be written in the space provided")] +pub struct ObjWriteSizeError; pub type SpaceId = u8; pub const INVALID_SPACE_ID: SpaceId = 0xff; @@ -115,13 +115,13 @@ impl Obj { /// Write to the underlying object. Returns `Ok(())` on success. #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { modify(self.value.write()); // if `estimate_mem_image` gives overflow, the object will not be written self.dirty = match self.value.estimate_mem_image() { Some(len) => Some(len), - None => return Err(ObjWriteError), + None => return Err(ObjWriteSizeError), }; Ok(()) @@ -181,7 +181,7 @@ impl<'a, T: Storable + Debug> ObjRef<'a, T> { } #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteError> { + pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { #[allow(clippy::unwrap_used)] let inner = self.inner.as_mut().unwrap(); inner.write(modify)?; From b7f000f21e19f9fc2e2525236f0fe3a4d8a4be46 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 21 Feb 2024 11:37:26 -0500 Subject: [PATCH 0481/1053] Add `into_inner` method so we don't have to `clone` (#548) --- firewood/src/merkle.rs | 7 +---- firewood/src/shale/compact.rs | 6 ++--- firewood/src/shale/mod.rs | 49 +++++++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 168b44500108..d54dcb0a5633 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1519,7 +1519,7 @@ impl + Send + Sync, T> Merkle { ) -> Result, MerkleError> { if let Err(ObjWriteSizeError) = write_result { let old_node_address = node.as_ptr(); - node = self.put_node(node.clone())?; + node = self.put_node(node.into_inner())?; deleted.push(old_node_address); set_parent(node.as_ptr(), parents); @@ -2323,7 +2323,6 @@ mod tests { } #[test] - #[ignore] fn single_key_proof_with_one_node() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); @@ -2362,7 +2361,6 @@ mod tests { } #[test] - #[ignore] fn update_leaf_with_larger_path() -> Result<(), MerkleError> { let path = vec![0x00]; let data = vec![0x00]; @@ -2382,7 +2380,6 @@ mod tests { } #[test] - #[ignore] fn update_leaf_with_larger_data() -> Result<(), MerkleError> { let path = vec![0x00]; let data = vec![0x00]; @@ -2402,7 +2399,6 @@ mod tests { } #[test] - #[ignore] fn update_branch_with_larger_path() -> Result<(), MerkleError> { let path = vec![0x00]; let data = vec![0x00]; @@ -2424,7 +2420,6 @@ mod tests { } #[test] - #[ignore] fn update_branch_with_larger_data() -> Result<(), MerkleError> { let path = vec![0x00]; let data = vec![0x00]; diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 15306bceb954..b71a3f5f03d8 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -603,7 +603,7 @@ impl Sh let cache = &self.obj_cache; - let mut obj_ref = ObjRef::new(Some(obj), cache); + let mut obj_ref = ObjRef::new(obj, cache); // should this use a `?` instead of `unwrap`? #[allow(clippy::unwrap_used)] @@ -628,7 +628,7 @@ impl Sh let cache = &self.obj_cache; if let Some(obj) = obj { - return Ok(ObjRef::new(Some(obj), cache)); + return Ok(ObjRef::new(obj, cache)); } #[allow(clippy::unwrap_used)] @@ -645,7 +645,7 @@ impl Sh let obj = self.obj_cache.put(inner.get_data_ref(ptr, payload_size)?); let cache = &self.obj_cache; - Ok(ObjRef::new(Some(obj), cache)) + Ok(ObjRef::new(obj, cache)) } #[allow(clippy::unwrap_used)] diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 1d3cb79af61b..a7b762c13f17 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -11,6 +11,8 @@ use std::sync::{Arc, RwLock, RwLockWriteGuard}; use thiserror::Error; +use crate::merkle::{LeafNode, Node, PartialPath}; + pub mod cached; pub mod compact; pub mod disk_address; @@ -155,6 +157,17 @@ impl Obj { } } +impl Obj { + pub fn into_inner(mut self) -> Node { + let empty_node = LeafNode { + path: PartialPath(Vec::new()), + data: Vec::new().into(), + }; + + std::mem::replace(&mut self.value.decoded, Node::from_leaf(empty_node)) + } +} + impl Drop for Obj { fn drop(&mut self) { self.flush_dirty() @@ -171,12 +184,15 @@ impl Deref for Obj { /// User handle that offers read & write access to the stored [ShaleStore] item. #[derive(Debug)] pub struct ObjRef<'a, T: Storable> { + /// WARNING: + /// [Self::inner] should only set to [None] when consuming [Self] or inside [Drop::drop]. inner: Option>, cache: &'a ObjCache, } impl<'a, T: Storable + Debug> ObjRef<'a, T> { - const fn new(inner: Option>, cache: &'a ObjCache) -> Self { + const fn new(inner: Obj, cache: &'a ObjCache) -> Self { + let inner = Some(inner); Self { inner, cache } } @@ -196,6 +212,17 @@ impl<'a, T: Storable + Debug> ObjRef<'a, T> { } } +impl<'a> ObjRef<'a, Node> { + /// # Panics: + /// if inner is not set + pub fn into_inner(mut self) -> Node { + self.inner + .take() + .expect("inner should already be set") + .into_inner() + } +} + impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { @@ -207,16 +234,16 @@ impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { impl<'a, T: Storable> Drop for ObjRef<'a, T> { fn drop(&mut self) { - #[allow(clippy::unwrap_used)] - let mut inner = self.inner.take().unwrap(); - let ptr = inner.as_ptr(); - let mut cache = self.cache.lock(); - match cache.pinned.remove(&ptr) { - Some(true) => { - inner.dirty = None; - } - _ => { - cache.cached.put(ptr, inner); + if let Some(mut inner) = self.inner.take() { + let ptr = inner.as_ptr(); + let mut cache = self.cache.lock(); + match cache.pinned.remove(&ptr) { + Some(true) => { + inner.dirty = None; + } + _ => { + cache.cached.put(ptr, inner); + } } } } From 7786a891b19ac4cb7ce93ce051b9ec034a7d5d3d Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 21 Feb 2024 13:04:45 -0500 Subject: [PATCH 0482/1053] Use `Key` type instead of `Box<[u8]>` (#550) --- firewood/src/db.rs | 7 ++----- firewood/src/merkle.rs | 4 ++-- firewood/src/merkle/stream.rs | 4 ++-- fwdctl/src/dump.rs | 6 +++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 88e2a8b3257a..5677b885cd44 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,7 +8,7 @@ pub use crate::{ }; use crate::{ file, - merkle::{Bincode, Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, + merkle::{Bincode, Key, Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, storage::{ buffer::{DiskBuffer, DiskBufferRequester}, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, @@ -323,10 +323,7 @@ impl + Send + Sync> DbRev { self.merkle.key_value_iter(self.header.kv_root) } - pub fn stream_from( - &self, - start_key: Box<[u8]>, - ) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { + pub fn stream_from(&self, start_key: Key) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { self.merkle .key_value_iter_from_key(self.header.kv_root, start_key) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d54dcb0a5633..2aafb36b6885 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -31,7 +31,7 @@ type NodeObjRef<'a> = shale::ObjRef<'a, Node>; type ParentRefs<'a> = Vec<(NodeObjRef<'a>, u8)>; type ParentAddresses = Vec<(DiskAddress, u8)>; -type Key = Box<[u8]>; +pub type Key = Box<[u8]>; type Value = Vec; #[derive(Debug, Error)] @@ -1381,7 +1381,7 @@ impl + Send + Sync, T> Merkle { pub(crate) fn key_value_iter_from_key( &self, root: DiskAddress, - key: Box<[u8]>, + key: Key, ) -> MerkleKeyValueStream<'_, S, T> { MerkleKeyValueStream::from_key(self, root, key) } diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 9f407e570a6d..abfee7b38f65 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -140,7 +140,7 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S // The child's key is its parent's key, followed by the child's index, // followed by the child's partial path (if any). - let child_key: Box<[u8]> = key + let child_key: Key = key .iter() .copied() .chain(once(pos)) @@ -599,7 +599,7 @@ mod tests { pub(crate) fn node_iter_from( &self, root: DiskAddress, - key: Box<[u8]>, + key: Key, ) -> MerkleNodeStream<'_, S, T> { MerkleNodeStream::new(self, root, key) } diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index a86a6f862200..928574b4bf03 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,15 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::borrow::Cow; - use clap::Args; use firewood::{ db::{Db, DbConfig, WalConfig}, + merkle::Key, v2::api::{self, Db as _}, }; use futures_util::StreamExt; use log; +use std::borrow::Cow; #[derive(Debug, Args)] pub struct Options { @@ -30,7 +30,7 @@ pub struct Options { value_parser = key_parser, help = "Start dumping from this key (inclusive)." )] - pub start_key: Option>, + pub start_key: Option, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { From 3eacc026cb919485545ac948dc6284362e9b31e6 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 21 Feb 2024 13:41:12 -0500 Subject: [PATCH 0483/1053] Remove extension nodes (#552) --- firewood/src/merkle.rs | 300 +------------------------- firewood/src/merkle/node.rs | 73 +------ firewood/src/merkle/node/extension.rs | 226 ------------------- firewood/src/merkle/proof.rs | 208 +----------------- firewood/src/merkle/stream.rs | 20 -- 5 files changed, 10 insertions(+), 817 deletions(-) delete mode 100644 firewood/src/merkle/node/extension.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 2aafb36b6885..31dc75c61e84 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -7,8 +7,7 @@ use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use sha3::Digest; use std::{ - cmp::Ordering, collections::HashMap, future::ready, io::Write, iter::once, marker::PhantomData, - sync::OnceLock, + collections::HashMap, future::ready, io::Write, iter::once, marker::PhantomData, sync::OnceLock, }; use thiserror::Error; @@ -18,8 +17,8 @@ mod stream; mod trie_hash; pub use node::{ - BinarySerde, Bincode, BranchNode, Data, EncodedNode, EncodedNodeType, ExtNode, LeafNode, Node, - NodeType, PartialPath, + BinarySerde, Bincode, BranchNode, Data, EncodedNode, EncodedNodeType, LeafNode, Node, NodeType, + PartialPath, }; pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; @@ -140,8 +139,6 @@ where value: n.value.clone(), }) } - - NodeType::Extension(_) => todo!(), }; Bincode::serialize(&encoded).map_err(|e| MerkleError::BinarySerdeError(e.to_string())) @@ -245,10 +242,6 @@ impl + Send + Sync, T> Merkle { } #[allow(clippy::unwrap_used)] NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), - NodeType::Extension(n) => { - writeln!(w, "{n:?}")?; - self.dump_(n.chd(), w)? - } } Ok(()) @@ -263,207 +256,6 @@ impl + Send + Sync, T> Merkle { Ok(()) } - // TODO: replace `split` with a `split_at` function. Handle the logic for matching paths in `insert` instead. - #[allow(clippy::too_many_arguments)] - fn split<'a>( - &'a self, - mut node_to_split: NodeObjRef<'a>, - parents: &mut [(NodeObjRef<'a>, u8)], - insert_path: &[u8], - n_path: Vec, - n_value: Option, - val: Vec, - deleted: &mut Vec, - ) -> Result, Vec)>, MerkleError> { - let node_to_split_address = node_to_split.as_ptr(); - let split_index = insert_path - .iter() - .zip(n_path.iter()) - .position(|(a, b)| a != b); - - #[allow(clippy::indexing_slicing)] - let new_child_address = if let Some(idx) = split_index { - // paths diverge - let new_split_node_path = n_path.split_at(idx + 1).1; - let (matching_path, new_node_path) = insert_path.split_at(idx + 1); - - node_to_split.write(|node| { - // TODO: handle unwrap better - let path = node.inner.path_mut(); - - *path = PartialPath(new_split_node_path.to_vec()); - - node.rehash(); - })?; - - let new_node = Node::from_leaf(LeafNode::new( - PartialPath(new_node_path.to_vec()), - Data(val), - )); - let leaf_address = self.put_node(new_node)?.as_ptr(); - - let mut chd = [None; BranchNode::MAX_CHILDREN]; - - let last_matching_nibble = matching_path[idx]; - chd[last_matching_nibble as usize] = Some(leaf_address); - - let address = match &node_to_split.inner { - NodeType::Extension(u) if u.path.len() == 0 => { - deleted.push(node_to_split_address); - u.chd() - } - _ => node_to_split_address, - }; - - chd[n_path[idx] as usize] = Some(address); - - let new_branch = Node::from_branch(BranchNode { - path: PartialPath(matching_path[..idx].to_vec()), - children: chd, - value: None, - children_encoded: Default::default(), - }); - - self.put_node(new_branch)?.as_ptr() - } else { - // paths do not diverge - let (leaf_address, prefix, idx, value) = - match (insert_path.len().cmp(&n_path.len()), n_value) { - // no node-value means this is an extension node and we can therefore continue walking the tree - (Ordering::Greater, None) => return Ok(Some((node_to_split, val))), - - // if the paths are equal, we overwrite the data - (Ordering::Equal, _) => { - let mut result = Ok(None); - - write_node!( - self, - node_to_split, - |u| { - match &mut u.inner { - NodeType::Leaf(u) => u.data = Data(val), - NodeType::Extension(u) => { - #[allow(clippy::unwrap_used)] - let write_result = - self.get_node(u.chd()).and_then(|mut b_ref| { - b_ref - .write(|b| { - let branch = - b.inner.as_branch_mut().unwrap(); - branch.value = Some(Data(val)); - - b.rehash() - }) - // if writing fails, delete the child? - .or_else(|_| { - let node = self.put_node(b_ref.clone())?; - - let child = u.chd_mut(); - *child = node.as_ptr(); - - deleted.push(b_ref.as_ptr()); - - Ok(()) - }) - }); - - if let Err(e) = write_result { - result = Err(e); - } - } - NodeType::Branch(u) => { - u.value = Some(Data(val)); - } - } - - u.rehash(); - }, - parents, - deleted - ); - - return result; - } - - // if the node-path is greater than the insert path - (Ordering::Less, _) => { - // key path is a prefix of the path to u - #[allow(clippy::unwrap_used)] - node_to_split - .write(|u| { - // TODO: handle unwraps better - let path = u.inner.path_mut(); - #[allow(clippy::indexing_slicing)] - (*path = PartialPath(n_path[insert_path.len() + 1..].to_vec())); - - u.rehash(); - }) - .unwrap(); - - let leaf_address = match &node_to_split.inner { - // TODO: handle BranchNode case - NodeType::Extension(u) if u.path.len() == 0 => { - deleted.push(node_to_split_address); - u.chd() - } - _ => node_to_split_address, - }; - - #[allow(clippy::indexing_slicing)] - ( - leaf_address, - insert_path, - n_path[insert_path.len()] as usize, - Data(val).into(), - ) - } - // insert path is greather than the path of the leaf - (Ordering::Greater, Some(n_value)) => { - let leaf = Node::from_leaf(LeafNode::new( - #[allow(clippy::indexing_slicing)] - PartialPath(insert_path[n_path.len() + 1..].to_vec()), - Data(val), - )); - - let leaf_address = self.put_node(leaf)?.as_ptr(); - - deleted.push(node_to_split_address); - - #[allow(clippy::indexing_slicing)] - ( - leaf_address, - n_path.as_slice(), - insert_path[n_path.len()] as usize, - n_value.into(), - ) - } - }; - - // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] - let mut children = [None; BranchNode::MAX_CHILDREN]; - - #[allow(clippy::indexing_slicing)] - (children[idx] = leaf_address.into()); - - self.put_node(Node::from_branch(BranchNode { - path: PartialPath(prefix.to_vec()), - children, - value, - children_encoded: Default::default(), - }))? - .as_ptr() - }; - - // observation: - // - leaf/extension node can only be the child of a branch node - // - branch node can only be the child of a branch/extension node - // ^^^ I think a leaf can end up being the child of an extension node - // ^^^ maybe just on delete though? I'm not sure, removing extension-nodes anyway - set_parent(new_child_address, parents); - - Ok(None) - } - pub fn insert>( &mut self, key: K, @@ -486,7 +278,7 @@ impl + Send + Sync, T> Merkle { fn insert_and_return_updates>( &self, key: K, - mut val: Vec, + val: Vec, root: DiskAddress, ) -> Result<(impl Iterator, Vec), MerkleError> { // as we split a node, we need to track deleted nodes and parents @@ -808,39 +600,6 @@ impl + Send + Sync, T> Merkle { } } } - - NodeType::Extension(n) => { - let n_path = n.path.to_vec(); - let n_ptr = n.chd(); - let rem_path = once(next_nibble) - .chain(key_nibbles.clone()) - .collect::>(); - let n_path_len = n_path.len(); - - if let Some((node, v)) = self.split( - node, - &mut parents, - &rem_path, - n_path, - None, - val, - &mut deleted, - )? { - (0..n_path_len).skip(1).for_each(|_| { - key_nibbles.next(); - }); - - // we couldn't split this, so we - // skip n_path items and follow the - // extension node's next pointer - val = v; - - (node, n_ptr) - } else { - // successfully inserted - break None; - } - } }; // push another parent, and follow the next pointer @@ -877,19 +636,6 @@ impl + Send + Sync, T> Merkle { Some((idx, true, None, val)) } } - NodeType::Extension(n) => { - #[allow(clippy::indexing_slicing)] - let idx = n.path[0]; - let more = if n.path.len() > 1 { - #[allow(clippy::indexing_slicing)] - (n.path = PartialPath(n.path[1..].to_vec())); - true - } else { - false - }; - - Some((idx, more, Some(n.chd()), val)) - } }; u.rehash() @@ -1062,7 +808,6 @@ impl + Send + Sync, T> Merkle { Node::from_leaf(child) } - NodeType::Extension(_) => todo!(), }; let child = self.put_node(new_child)?.as_ptr(); @@ -1090,8 +835,6 @@ impl + Send + Sync, T> Merkle { data } - - NodeType::Extension(_) => todo!(), }; for (mut parent, _) in parents { @@ -1121,7 +864,6 @@ impl + Send + Sync, T> Merkle { } } NodeType::Leaf(_) => (), - NodeType::Extension(n) => self.remove_tree_(n.chd(), deleted)?, } deleted.push(u); Ok(()) @@ -1259,23 +1001,6 @@ impl + Send + Sync, T> Merkle { return Ok(node_ref); } - NodeType::Extension(n) => { - let mut n_path_iter = n.path.iter().copied(); - - if n_path_iter.next() != Some(nib) { - return Ok(None); - } - - let path_matches = n_path_iter - .map(Some) - .all(|n_path_nibble| key_nibbles.next() == n_path_nibble); - - if !path_matches { - return Ok(None); - } - - n.chd() - } }; end_loop_callback(node_ref, nib); @@ -1538,7 +1263,6 @@ fn set_parent(new_chd: DiskAddress, parents: &mut [(NodeObjRef, u8)]) { match &mut p.inner { #[allow(clippy::indexing_slicing)] NodeType::Branch(pp) => pp.children[*idx as usize] = Some(new_chd), - NodeType::Extension(pp) => *pp.chd_mut() = new_chd, _ => unreachable!(), } p.rehash(); @@ -1561,7 +1285,6 @@ impl<'a> std::ops::Deref for Ref<'a> { match &self.0.inner { NodeType::Branch(n) => n.value.as_ref().unwrap(), NodeType::Leaf(n) => &n.data, - _ => unreachable!(), } } } @@ -1599,7 +1322,6 @@ impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { modify(match &mut u.inner { NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, NodeType::Leaf(n) => &mut n.data.0, - _ => unreachable!(), }); u.rehash() }, @@ -1671,18 +1393,6 @@ mod tests { use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; use test_case::test_case; - fn extension( - path: Vec, - child_address: DiskAddress, - child_encoded: Option>, - ) -> Node { - Node::from(NodeType::Extension(ExtNode { - path: PartialPath(path), - child: child_address, - child_encoded, - })) - } - fn leaf(path: Vec, data: Vec) -> Node { Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) } @@ -1790,7 +1500,6 @@ mod tests { #[test_case(branch(b"", b"value", vec![1, 2, 3].into()); "branch with value and children")] #[test_case(branch(&[2], b"", vec![1, 2, 3].into()); "branch with path and children")] #[test_case(branch(&[2], b"value", vec![1, 2, 3].into()); "branch with path value and children")] - #[test_case(extension(vec![1, 2, 3], DiskAddress::null(), vec![4, 5].into()) ; "extension without child address")] fn encode(node: Node) { let merkle = create_test_merkle(); @@ -2477,7 +2186,6 @@ mod tests { let (path, data) = match node.inner() { NodeType::Leaf(leaf) => (&leaf.path, Some(&leaf.data)), NodeType::Branch(branch) => (&branch.path, branch.value.as_ref()), - _ => unreachable!(), }; assert_eq!(path, &PartialPath(new_path)); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 5df8321e867e..7a79a6620081 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -28,12 +28,10 @@ use std::{ }; mod branch; -mod extension; mod leaf; mod partial_path; pub use branch::BranchNode; -pub use extension::ExtNode; pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; pub use partial_path::PartialPath; @@ -104,7 +102,6 @@ impl> Encoded { pub enum NodeType { Branch(Box), Leaf(LeafNode), - Extension(ExtNode), } impl NodeType { @@ -120,22 +117,13 @@ impl NodeType { let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); - let (cur_key_path, term) = - PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); + let cur_key_path = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()).0; let cur_key = cur_key_path.into_inner(); #[allow(clippy::unwrap_used)] let data: Vec = items.next().unwrap().decode()?; - if term { - Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) - } else { - Ok(NodeType::Extension(ExtNode { - path: PartialPath(cur_key), - child: DiskAddress::null(), - child_encoded: Some(data), - })) - } + Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) } // TODO: add path BranchNode::MSIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), @@ -148,7 +136,6 @@ impl NodeType { pub fn encode>(&self, store: &S) -> Vec { match &self { NodeType::Leaf(n) => n.encode(), - NodeType::Extension(n) => n.encode(store), NodeType::Branch(n) => n.encode(store), } } @@ -157,7 +144,6 @@ impl NodeType { match self { NodeType::Branch(u) => &mut u.path, NodeType::Leaf(node) => &mut node.path, - NodeType::Extension(node) => &mut node.path, } } @@ -165,7 +151,6 @@ impl NodeType { match self { NodeType::Branch(u) => u.path = path, NodeType::Leaf(node) => node.path = path, - NodeType::Extension(node) => node.path = path, } } @@ -173,7 +158,6 @@ impl NodeType { match self { NodeType::Branch(u) => u.value = Some(data), NodeType::Leaf(node) => node.data = data, - NodeType::Extension(_) => (), } } } @@ -370,7 +354,6 @@ mod type_id { pub enum NodeTypeId { Branch = 0, Leaf = 1, - Extension = 2, } impl TryFrom for NodeTypeId { @@ -386,7 +369,6 @@ mod type_id { match node_type { NodeType::Branch(_) => NodeTypeId::Branch, NodeType::Leaf(_) => NodeTypeId::Leaf, - NodeType::Extension(_) => NodeTypeId::Extension, } } } @@ -450,14 +432,6 @@ impl Storable for Node { )) } - NodeTypeId::Extension => { - let inner = NodeType::Extension(ExtNode::deserialize(offset, mem)?); - let node = - Self::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner); - - Ok(node) - } - NodeTypeId::Leaf => { let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); let node = @@ -472,7 +446,6 @@ impl Storable for Node { Meta::SIZE as u64 + match &self.inner { NodeType::Branch(n) => n.serialized_len(), - NodeType::Extension(n) => n.serialized_len(), NodeType::Leaf(n) => n.serialized_len(), } } @@ -526,13 +499,6 @@ impl Storable for Node { n.serialize(&mut cursor.get_mut()[pos..]) } - NodeType::Extension(n) => { - let pos = cursor.position() as usize; - - #[allow(clippy::indexing_slicing)] - n.serialize(&mut cursor.get_mut()[pos..]) - } - NodeType::Leaf(n) => { let pos = cursor.position() as usize; @@ -931,11 +897,6 @@ mod tests { value: Some(Data(vec![1, 2, 3])), children_encoded: std::array::from_fn(|_| Some(vec![1])), })); - let extension = NodeType::Extension(ExtNode { - path: PartialPath(vec![1, 2, 3]), - child: DiskAddress::from(1), - child_encoded: Some(vec![1, 2, 3]), - }); let root_hash = root_hash.into().map(TrieHash); let encoded = encoded.into(); @@ -958,15 +919,6 @@ mod tests { ); check_node_encoding(node); - - let node = Node::new_from_hash( - root_hash, - encoded.clone(), - is_encoded_longer_than_hash_len, - extension, - ); - - check_node_encoding(node); } #[test_matrix( @@ -1026,27 +978,6 @@ mod tests { check_node_encoding(node); } - #[test_matrix( - [0..1, 0..16], - [DiskAddress::null(), DiskAddress::from(1)], - [Nil, 1, 32, 33] - )] - fn extension_encoding>( - path: Iter, - child: DiskAddress, - child_encoded: impl Into>, - ) { - let node = Node::from(NodeType::Extension(ExtNode { - path: PartialPath(path.map(|x| x & 0xf).collect()), - child, - child_encoded: child_encoded - .into() - .map(|x| repeat(x as u8).take(x).collect()), - })); - - check_node_encoding(node); - } - fn check_node_encoding(node: Node) { let serialized_len = node.serialized_len(); diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs deleted file mode 100644 index dd8f33bbf7d1..000000000000 --- a/firewood/src/merkle/node/extension.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use bincode::Options; - -use super::{Encoded, Node}; -use crate::{ - merkle::{from_nibbles, to_nibble_array, PartialPath, TRIE_HASH_LEN}, - shale::{DiskAddress, ShaleError::InvalidCacheView, ShaleStore, Storable}, -}; -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Read, Write}, - mem::size_of, -}; - -type PathLen = u8; -type DataLen = u8; - -#[derive(PartialEq, Eq, Clone)] -pub struct ExtNode { - pub(crate) path: PartialPath, - pub(crate) child: DiskAddress, - pub(crate) child_encoded: Option>, -} - -impl Debug for ExtNode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - let Self { - path, - child, - child_encoded, - } = self; - write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) - } -} - -impl ExtNode { - const PATH_LEN_SIZE: u64 = size_of::() as u64; - const DATA_LEN_SIZE: u64 = size_of::() as u64; - - pub(super) fn encode>(&self, store: &S) -> Vec { - let mut list = <[Encoded>; 2]>::default(); - list[0] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) - .expect("serialization failed"), - ); - - if !self.child.is_null() { - #[allow(clippy::unwrap_used)] - let mut r = store.get_item(self.child).unwrap(); - - #[allow(clippy::unwrap_used)] - if r.is_encoded_longer_than_hash_len(store) { - list[1] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*r.get_root_hash(store))[..]) - .unwrap(), - ); - - if r.is_dirty() { - r.write(|_| {}).unwrap(); - r.set_dirty(false); - } - } else { - list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); - } - } else { - // Check if there is already a caclucated encoded value for the child, which - // can happen when manually constructing a trie from proof. - #[allow(clippy::unwrap_used)] - if let Some(v) = &self.child_encoded { - if v.len() == TRIE_HASH_LEN { - list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); - } else { - list[1] = Encoded::Raw(v.clone()); - } - } - } - - #[allow(clippy::unwrap_used)] - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .unwrap() - } - - pub const fn chd(&self) -> DiskAddress { - self.child - } - - pub fn chd_encoded(&self) -> Option<&[u8]> { - self.child_encoded.as_deref() - } - - pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.child - } - - pub fn chd_encoded_mut(&mut self) -> &mut Option> { - &mut self.child_encoded - } -} - -impl Storable for ExtNode { - fn serialized_len(&self) -> u64 { - let path_len_size = Self::PATH_LEN_SIZE; - let path_len = self.path.serialized_len(); - let child_len = DiskAddress::MSIZE; - // TODO: - // this seems wrong to always include this byte even if there isn't a child - // but it matches the original implementation - let encoded_len_size = Self::DATA_LEN_SIZE; - let encoded_len = self - .child_encoded - .as_ref() - .map(|v| v.len() as u64) - .unwrap_or(0); - - path_len_size + path_len + child_len + encoded_len_size + encoded_len - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { - let mut cursor = Cursor::new(to); - - let path: Vec = from_nibbles(&self.path.encode(false)).collect(); - - cursor.write_all(&[path.len() as PathLen])?; - cursor.write_all(&self.child.to_le_bytes())?; - cursor.write_all(&path)?; - - if let Some(encoded) = self.chd_encoded() { - cursor.write_all(&[encoded.len() as DataLen])?; - cursor.write_all(encoded)?; - } - - Ok(()) - } - - fn deserialize( - mut offset: usize, - mem: &T, - ) -> Result - where - Self: Sized, - { - let header_size = Self::PATH_LEN_SIZE + DiskAddress::MSIZE; - - let path_and_disk_address = mem - .get_view(offset, header_size) - .ok_or(InvalidCacheView { - offset, - size: header_size, - })? - .as_deref(); - - offset += header_size as usize; - - let mut cursor = Cursor::new(path_and_disk_address); - let mut buf = [0u8; DiskAddress::MSIZE as usize]; - - #[allow(clippy::indexing_slicing)] - let path_len = { - let buf = &mut buf[..Self::PATH_LEN_SIZE as usize]; - cursor.read_exact(buf)?; - buf[0] as u64 - }; - - let disk_address = { - cursor.read_exact(buf.as_mut())?; - DiskAddress::from(u64::from_le_bytes(buf) as usize) - }; - - let path = mem - .get_view(offset, path_len) - .ok_or(InvalidCacheView { - offset, - size: path_len, - })? - .as_deref(); - - offset += path_len as usize; - - let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); - - let path = PartialPath::decode(&path).0; - - let encoded_len_raw = mem - .get_view(offset, Self::DATA_LEN_SIZE) - .ok_or(InvalidCacheView { - offset, - size: Self::DATA_LEN_SIZE, - })? - .as_deref(); - - offset += Self::DATA_LEN_SIZE as usize; - - let mut cursor = Cursor::new(encoded_len_raw); - - let encoded_len = { - let mut buf = [0u8; Self::DATA_LEN_SIZE as usize]; - cursor.read_exact(buf.as_mut())?; - DataLen::from_le_bytes(buf) as u64 - }; - - let encoded = if encoded_len != 0 { - let encoded = mem - .get_view(offset, encoded_len) - .ok_or(InvalidCacheView { - offset, - size: encoded_len, - })? - .as_deref(); - - encoded.into() - } else { - None - }; - - Ok(ExtNode { - path, - child: disk_address, - child_encoded: encoded, - }) - } -} diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 83a29b4a4199..b2ee4568655c 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -310,22 +310,6 @@ impl + Send> Proof { } } - NodeType::Extension(n) if n.chd().is_null() => { - let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; - - parent_node_ref.write(|node| { - let node = node - .inner_mut() - .as_extension_mut() - .expect("parent_node_ref must be an extension"); - *node.chd_mut() = child_node.as_ptr(); - })?; - - child_node - } - - NodeType::Extension(n) => merkle.get_node(n.chd())?, - // We should not hit a leaf node as a parent. _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; @@ -341,20 +325,6 @@ impl + Send> Proof { .then(|| n.data().to_vec()); } - NodeType::Extension(n) => { - let paths_match = n - .path - .iter() - .copied() - .all(|nibble| Some(nibble) == key_nibbles.next()); - - if !paths_match { - break None; - } - - Some(n.chd_encoded().ok_or(ProofError::InvalidData)?) - } - NodeType::Branch(n) => { let paths_match = n .path @@ -434,21 +404,6 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } - NodeType::Extension(n) => { - let cur_key = &n.path.0; - // Check if the key of current node match with the given key - // and consume the current-key portion of the nibbles-iterator - let does_not_match = key_nibbles.size_hint().0 < cur_key.len() - || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); - - if does_not_match { - return Ok((None, Nibbles::<0>::new(&[]).into_iter())); - } - let data = n.chd_encoded().ok_or(ProofError::InvalidData)?; - let sub_proof = generate_subproof(data)?; - - Ok((sub_proof.into(), key_nibbles)) - } NodeType::Branch(n) => { let partial_path = &n.path.0; @@ -575,25 +530,6 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe index += 1; } - #[allow(clippy::indexing_slicing)] - NodeType::Extension(n) => { - // If either the key of left proof or right proof doesn't match with - // shortnode, stop here and the forkpoint is the shortnode. - let path = &*n.path; - - [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] - .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) - .map(|key| key.cmp(path)); - - if !fork_left.is_eq() || !fork_right.is_eq() { - break; - } - - parent = u_ref.as_ptr(); - index += path.len(); - u_ref = merkle.get_node(n.chd())?; - } - #[allow(clippy::indexing_slicing)] NodeType::Leaf(n) => { let path = &*n.path; @@ -694,82 +630,6 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe Ok(false) } - NodeType::Extension(n) => { - // There can have these five scenarios: - // - both proofs are less than the trie path => no valid range - // - both proofs are greater than the trie path => no valid range - // - left proof is less and right proof is greater => valid range, unset the shortnode entirely - // - left proof points to the shortnode, but right proof is greater - // - right proof points to the shortnode, but left proof is less - if fork_left.is_lt() && fork_right.is_lt() { - return Err(ProofError::EmptyRange); - } - - if fork_left.is_gt() && fork_right.is_gt() { - return Err(ProofError::EmptyRange); - } - - if fork_left.is_ne() && fork_right.is_ne() { - // The fork point is root node, unset the entire trie - if parent.is_null() { - return Ok(true); - } - - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - #[allow(clippy::unwrap_used)] - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - #[allow(clippy::indexing_slicing)] - (pp.chd_mut()[left_chunks[index - 1] as usize] = None); - #[allow(clippy::indexing_slicing)] - (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); - }) - .unwrap(); - - return Ok(false); - } - - let node = n.chd(); - let index = n.path.len(); - - let p = u_ref.as_ptr(); - drop(u_ref); - - // Only one proof points to non-existent key. - if fork_right.is_ne() { - unset_node_ref( - merkle, - p, - Some(node), - #[allow(clippy::indexing_slicing)] - &left_chunks[index..], - index, - false, - )?; - - return Ok(false); - } - - if fork_left.is_ne() { - unset_node_ref( - merkle, - p, - Some(node), - #[allow(clippy::indexing_slicing)] - &right_chunks[index..], - index, - true, - )?; - - return Ok(false); - } - - Ok(false) - } - NodeType::Leaf(_) => { if fork_left.is_lt() && fork_right.is_lt() { return Err(ProofError::EmptyRange); @@ -786,18 +646,13 @@ fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySe #[allow(clippy::unwrap_used)] if fork_left.is_ne() && fork_right.is_ne() { p_ref - .write(|p| match p.inner_mut() { - NodeType::Extension(n) => { - *n.chd_mut() = DiskAddress::null(); - *n.chd_encoded_mut() = None; - } - NodeType::Branch(n) => { + .write(|p| { + if let NodeType::Branch(n) = p.inner_mut() { #[allow(clippy::indexing_slicing)] (n.chd_mut()[left_chunks[index - 1] as usize] = None); #[allow(clippy::indexing_slicing)] (n.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); } - _ => {} }) .unwrap(); } else if fork_right.is_ne() { @@ -930,10 +785,6 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe pp.chd_mut()[chunks[index - 1] as usize] = None; pp.chd_encoded_mut()[chunks[index - 1] as usize] = None; } - NodeType::Extension(n) => { - *n.chd_mut() = DiskAddress::null(); - *n.chd_encoded_mut() = None; - } NodeType::Leaf(_) => (), }) .unwrap(); @@ -942,52 +793,6 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe Ok(()) } - #[allow(clippy::indexing_slicing)] - NodeType::Extension(n) if chunks[index..].starts_with(&n.path) => { - let node = Some(n.chd()); - unset_node_ref(merkle, p, node, key, index + n.path.len(), remove_left) - } - - NodeType::Extension(n) => { - let cur_key = &n.path; - - // Find the fork point, it's a non-existent branch. - // - // for (true, Ordering::Less) - // The key of fork shortnode is less than the path - // (it belongs to the range), unset the entire - // branch. The parent must be a fullnode. - // - // for (false, Ordering::Greater) - // The key of fork shortnode is greater than the - // path(it belongs to the range), unset the entrie - // branch. The parent must be a fullnode. Otherwise the - // key is not part of the range and should remain in the - // cached hash. - #[allow(clippy::indexing_slicing)] - let should_unset_entire_branch = matches!( - (remove_left, cur_key.cmp(&chunks[index..])), - (true, Ordering::Less) | (false, Ordering::Greater) - ); - - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - if should_unset_entire_branch { - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_encoded_mut()[chunks[index - 1] as usize] = None; - }) - .unwrap(); - } - - Ok(()) - } - NodeType::Leaf(n) => { let mut p_ref = merkle .get_node(parent) @@ -1014,19 +819,14 @@ fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySe } } else { p_ref - .write(|p| match p.inner_mut() { - NodeType::Extension(n) => { - *n.chd_mut() = DiskAddress::null(); - *n.chd_encoded_mut() = None; - } - NodeType::Branch(n) => { + .write(|p| { + if let NodeType::Branch(n) = p.inner_mut() { #[allow(clippy::indexing_slicing)] let index = chunks[index - 1] as usize; n.chd_mut()[index] = None; n.chd_encoded_mut()[index] = None; } - _ => {} }) .expect("node write failure"); } diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index abfee7b38f65..69a12230b7b6 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -110,9 +110,6 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S }); } NodeType::Leaf(_) => {} - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } } let key = key_from_nibble_iter(key.iter().copied().skip(1)); @@ -133,9 +130,6 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S let partial_path = match child.inner() { NodeType::Branch(branch) => branch.path.iter().copied(), NodeType::Leaf(leaf) => leaf.path.iter().copied(), - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } }; // The child's key is its parent's key, followed by the child's index, @@ -195,9 +189,6 @@ fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( node, }); } - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } } return Ok(NodeStreamState::Iterating { iter_stack }); @@ -233,9 +224,6 @@ fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( let partial_key = match child.inner() { NodeType::Branch(branch) => &branch.path, NodeType::Leaf(leaf) => &leaf.path, - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } }; let (comparison, new_unmatched_key_nibbles) = @@ -279,9 +267,6 @@ fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( } return Ok(NodeStreamState::Iterating { iter_stack }); } - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } }; } } @@ -379,9 +364,6 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<' let value = leaf.data.to_vec(); Poll::Ready(Some(Ok((key, value)))) } - NodeType::Extension(_) => { - unreachable!("extension nodes shouldn't exist") - } }, Some(Err(e)) => Poll::Ready(Some(Err(e))), None => Poll::Ready(None), @@ -477,7 +459,6 @@ impl<'a, 'b, S: ShaleStore + Send + Sync, T> Iterator for PathIterator<'a, let partial_path = match node.inner() { NodeType::Branch(branch) => &branch.path, NodeType::Leaf(leaf) => &leaf.path, - _ => unreachable!("extension nodes shouldn't exist"), }; let (comparison, unmatched_key) = @@ -492,7 +473,6 @@ impl<'a, 'b, S: ShaleStore + Send + Sync, T> Iterator for PathIterator<'a, Some(Ok((node_key, node))) } Ordering::Equal => match node.inner() { - NodeType::Extension(_) => unreachable!(), NodeType::Leaf(_) => { self.state = PathIteratorState::Exhausted; Some(Ok((node_key, node))) From cb9e98adc54990aee03bb5eec64794e834b6108e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 21 Feb 2024 19:42:49 +0000 Subject: [PATCH 0484/1053] Remove Option from ObjPtr (#551) Signed-off-by: Richard Pringle Co-authored-by: Richard Pringle --- firewood/src/shale/mod.rs | 66 ++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index a7b762c13f17..efcc574ca0bc 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -5,6 +5,7 @@ pub(crate) use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; use std::fmt::{self, Debug, Formatter}; +use std::mem::ManuallyDrop; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::sync::{Arc, RwLock, RwLockWriteGuard}; @@ -184,25 +185,23 @@ impl Deref for Obj { /// User handle that offers read & write access to the stored [ShaleStore] item. #[derive(Debug)] pub struct ObjRef<'a, T: Storable> { - /// WARNING: - /// [Self::inner] should only set to [None] when consuming [Self] or inside [Drop::drop]. - inner: Option>, + inner: ManuallyDrop>, cache: &'a ObjCache, } impl<'a, T: Storable + Debug> ObjRef<'a, T> { const fn new(inner: Obj, cache: &'a ObjCache) -> Self { - let inner = Some(inner); - Self { inner, cache } + Self { + inner: ManuallyDrop::new(inner), + cache, + } } #[inline] pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { - #[allow(clippy::unwrap_used)] - let inner = self.inner.as_mut().unwrap(); - inner.write(modify)?; + self.inner.write(modify)?; - self.cache.lock().dirty.insert(inner.as_ptr()); + self.cache.lock().dirty.insert(self.inner.as_ptr()); Ok(()) } @@ -213,37 +212,48 @@ impl<'a, T: Storable + Debug> ObjRef<'a, T> { } impl<'a> ObjRef<'a, Node> { - /// # Panics: - /// if inner is not set pub fn into_inner(mut self) -> Node { - self.inner - .take() - .expect("inner should already be set") - .into_inner() + // Safety: okay because we'll never be touching "self.inner" again + let b = unsafe { ManuallyDrop::take(&mut self.inner) }; + + // Safety: safe because self.cache: + // - is valid for both reads and writes. + // - is properly aligned + // - is nonnull + // - upholds invariant T + // - does not have a manual drop() implementation + // - is not accessed after drop_in_place and is not Copy + unsafe { std::ptr::drop_in_place(&mut self.cache) }; + + // we have dropped or moved everything out of self, so we can forget it + std::mem::forget(self); + + b.into_inner() } } impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { type Target = Obj; fn deref(&self) -> &Obj { - // TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix - #[allow(clippy::unwrap_used)] - self.inner.as_ref().unwrap() + &self.inner } } impl<'a, T: Storable> Drop for ObjRef<'a, T> { fn drop(&mut self) { - if let Some(mut inner) = self.inner.take() { - let ptr = inner.as_ptr(); - let mut cache = self.cache.lock(); - match cache.pinned.remove(&ptr) { - Some(true) => { - inner.dirty = None; - } - _ => { - cache.cached.put(ptr, inner); - } + let ptr = self.inner.as_ptr(); + let mut cache = self.cache.lock(); + match cache.pinned.remove(&ptr) { + Some(true) => { + self.inner.dirty = None; + // SAFETY: self.inner will have completed it's destructor + // so it must not be referenced after this line, and it isn't + unsafe { ManuallyDrop::drop(&mut self.inner) }; + } + _ => { + // SAFETY: safe because self.inner is not referenced after this line + let b = unsafe { ManuallyDrop::take(&mut self.inner) }; + cache.cached.put(ptr, b); } } } From 5053794d3c38a8c02af1b15d761eab0f0fd42f4f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 26 Feb 2024 10:31:36 +0000 Subject: [PATCH 0485/1053] merkle.proto cleanups (#562) --- grpc-testtool/proto/merkle/merkle.proto | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/grpc-testtool/proto/merkle/merkle.proto b/grpc-testtool/proto/merkle/merkle.proto index 94261d3ea099..a894d5376284 100644 --- a/grpc-testtool/proto/merkle/merkle.proto +++ b/grpc-testtool/proto/merkle/merkle.proto @@ -7,16 +7,22 @@ import "google/protobuf/empty.proto"; // Methods on this service return status code NOT_FOUND if a requested // view, iterator or root hash is not found. service Merkle { + // --- Proposals --- rpc NewProposal(NewProposalRequest) returns (NewProposalResponse); rpc ProposalCommit(ProposalCommitRequest) returns (google.protobuf.Empty); + // --- Views --- rpc NewView(NewViewRequest) returns (NewViewResponse); + + // --- Reads --- // The methods below may be called with an ID that corresponds to either a (committable) proposal // or (non-committable) historical view. rpc ViewHas(ViewHasRequest) returns (ViewHasResponse); rpc ViewGet(ViewGetRequest) returns (ViewGetResponse); + + // --- Iterators --- rpc ViewNewIteratorWithStartAndPrefix(ViewNewIteratorWithStartAndPrefixRequest) returns (ViewNewIteratorWithStartAndPrefixResponse); - // Returns status code NOT_FOUND when the iterator is done. + // Returns status code OUT_OF_RANGE when the iterator is done rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); rpc IteratorError(IteratorErrorRequest) returns (google.protobuf.Empty); // Iterator can't be used (even to check error) after release. @@ -67,17 +73,17 @@ message ViewGetResponse { } message ViewNewIteratorWithStartAndPrefixRequest { - uint32 id = 1; + uint64 id = 1; bytes start = 2; bytes prefix = 3; } message ViewNewIteratorWithStartAndPrefixResponse { - uint32 id = 1; + uint64 id = 1; } message IteratorNextRequest { - uint32 id = 1; + uint64 id = 1; } message IteratorNextResponse { @@ -85,11 +91,11 @@ message IteratorNextResponse { } message IteratorErrorRequest { - uint32 id = 1; + uint64 id = 1; } message IteratorReleaseRequest { - uint32 id = 1; + uint64 id = 1; } message ViewReleaseRequest { From 9f92521182d68cf97af0e5d4a7e781c0ebb7fcb9 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 26 Feb 2024 18:37:47 +0000 Subject: [PATCH 0486/1053] MerkleKeyValueStream and friends should implement Debug (#561) --- firewood/src/merkle/stream.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 69a12230b7b6..0bbb9795b4c4 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -29,6 +29,23 @@ enum IterationNode<'a> { }, } +impl<'a> std::fmt::Debug for IterationNode<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Unvisited { key, node } => f + .debug_struct("Unvisited") + .field("key", key) + .field("node", node) + .finish(), + Self::Visited { + key, + children_iter: _, + } => f.debug_struct("Visited").field("key", key).finish(), + } + } +} + +#[derive(Debug)] enum NodeStreamState<'a> { /// The iterator state is lazily initialized when poll_next is called /// for the first time. The iteration start key is stored here. @@ -49,6 +66,7 @@ impl NodeStreamState<'_> { } } +#[derive(Debug)] pub struct MerkleNodeStream<'a, S, T> { state: NodeStreamState<'a>, merkle_root: DiskAddress, @@ -271,6 +289,7 @@ fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( } } +#[derive(Debug)] enum MerkleKeyValueStreamState<'a, S, T> { /// The iterator state is lazily initialized when poll_next is called /// for the first time. The iteration start key is stored here. @@ -295,6 +314,7 @@ impl<'a, S, T> MerkleKeyValueStreamState<'a, S, T> { } } +#[derive(Debug)] pub struct MerkleKeyValueStream<'a, S, T> { state: MerkleKeyValueStreamState<'a, S, T>, merkle_root: DiskAddress, From 03c5b858417bbdc17295dea2b7621e7a5cb8afb9 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 26 Feb 2024 16:04:35 -0500 Subject: [PATCH 0487/1053] Fix `bincode` encoding of partial paths (#560) --- firewood/src/merkle/node.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 7a79a6620081..f7452165c907 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -523,6 +523,7 @@ impl EncodedNode { } } +#[derive(Debug, PartialEq)] pub enum EncodedNodeType { Leaf(LeafNode), Branch { @@ -679,10 +680,9 @@ impl Serialize for EncodedNode { Encoded::default() }; - let serialized_path = Bincode::serialize(&path.encode(false)) - .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; + let serialized_path = from_nibbles(&path.encode(true)).collect(); - list[BranchNode::MAX_CHILDREN + 1] = Encoded::Data(serialized_path); + list[BranchNode::MAX_CHILDREN + 1] = Encoded::Raw(serialized_path); let mut seq = serializer.serialize_seq(Some(list.len()))?; @@ -878,7 +878,7 @@ mod tests { use super::*; use crate::shale::cached::PlainMem; use std::iter::repeat; - use test_case::test_matrix; + use test_case::{test_case, test_matrix}; #[test_matrix( [Nil, [0x00; TRIE_HASH_LEN]], @@ -934,6 +934,30 @@ mod tests { check_node_encoding(node); } + #[test_case(&[])] + #[test_case(&[0x00])] + #[test_case(&[0x0F])] + #[test_case(&[0x00, 0x00])] + #[test_case(&[0x01, 0x02])] + #[test_case(&[0x00,0x0F])] + #[test_case(&[0x0F,0x0F])] + #[test_case(&[0x0F,0x01,0x0F])] + fn encoded_branch_node_bincode_serialize(path_nibbles: &[u8]) -> Result<(), Error> { + let node = EncodedNode::::new(EncodedNodeType::Branch { + path: PartialPath(path_nibbles.to_vec()), + children: Default::default(), + value: Some(Data(vec![1, 2, 3, 4])), + }); + + let node_bytes = Bincode::serialize(&node)?; + + let deserialized_node: EncodedNode = Bincode::deserialize(&node_bytes)?; + + assert_eq!(&node.node, &deserialized_node.node); + + Ok(()) + } + #[test_matrix( [&[], &[0xf], &[0xf, 0xf]], [vec![], vec![1,0,0,0,0,0,0,1], vec![1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], repeat(1).take(16).collect()], From 11f210000e9437144e96edd4bc590b53b2757e71 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 26 Feb 2024 23:34:37 +0000 Subject: [PATCH 0488/1053] Add the iterator to the public async API (#544) Signed-off-by: Ron Kuris Co-authored-by: Dan Laine --- firewood/src/db.rs | 19 +++++++++++++++++- firewood/src/db/proposal.rs | 18 ++++++++++++++++- firewood/src/v2/api.rs | 28 ++++++++++++++++++++++++++ firewood/src/v2/emptydb.rs | 40 +++++++++++++++++++++++++++++++++++++ firewood/src/v2/propose.rs | 11 ++++++++++ 5 files changed, 114 insertions(+), 2 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 5677b885cd44..3f096cbc89cd 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,7 +8,10 @@ pub use crate::{ }; use crate::{ file, - merkle::{Bincode, Key, Merkle, MerkleError, Node, Proof, ProofError, TrieHash, TRIE_HASH_LEN}, + merkle::{ + Bincode, Key, Merkle, MerkleError, MerkleKeyValueStream, Node, Proof, ProofError, TrieHash, + TRIE_HASH_LEN, + }, storage::{ buffer::{DiskBuffer, DiskBufferRequester}, CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, @@ -280,6 +283,8 @@ pub struct DbRev { #[async_trait] impl + Send + Sync> api::DbView for DbRev { + type Stream<'a> = MerkleKeyValueStream<'a, S, Bincode> where Self: 'a; + async fn root_hash(&self) -> Result { self.merkle .root_hash(self.header.kv_root) @@ -316,6 +321,18 @@ impl + Send + Sync> api::DbView for DbRev { .await .map_err(|e| api::Error::InternalError(Box::new(e))) } + + fn iter_option( + &self, + first_key: Option, + ) -> Result, api::Error> { + Ok(match first_key { + None => self.merkle.key_value_iter(self.header.kv_root), + Some(key) => self + .merkle + .key_value_iter_from_key(self.header.kv_root, key.as_ref().into()), + }) + } } impl + Send + Sync> DbRev { diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 009d0ee39a08..5b9711a7131e 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -5,8 +5,10 @@ use super::{ get_sub_universe_from_deltas, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, MutStore, SharedStore, Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; -use crate::merkle::Proof; +use crate::merkle::{Bincode, MerkleKeyValueStream, Node, Proof}; +use crate::shale::compact::CompactSpace; use crate::shale::CachedStore; +use crate::storage; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, @@ -264,6 +266,8 @@ impl Proposal { #[async_trait] impl api::DbView for Proposal { + type Stream<'a> = MerkleKeyValueStream<'a, CompactSpace, Bincode>; + async fn root_hash(&self) -> Result { self.get_revision() .kv_root_hash() @@ -299,4 +303,16 @@ impl api::DbView for Proposal { { todo!(); } + + fn iter_option( + &self, + first_key: Option, + ) -> Result, api::Error> { + let rev = self.get_revision(); + let iter = match first_key { + None => rev.stream(), + Some(first_key) => rev.stream_from(first_key.as_ref().into()), + }; + Ok(iter) + } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index a6a413d266db..165fe9113f34 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -4,6 +4,7 @@ use crate::merkle::MerkleError; pub use crate::merkle::Proof; use async_trait::async_trait; +use futures::Stream; use std::{fmt::Debug, sync::Arc}; /// A `KeyType` is something that can be xcast to a u8 reference, @@ -147,6 +148,10 @@ pub trait Db { /// A [Proposal] requires implementing DbView #[async_trait] pub trait DbView { + type Stream<'a>: Stream, Vec), Error>> + where + Self: 'a; + /// Get the root hash for the current DbView async fn root_hash(&self) -> Result; @@ -170,6 +175,29 @@ pub trait DbView { last_key: Option, limit: Option, ) -> Result, Vec>>, Error>; + + /// Obtain a stream over the keys/values of this view, using an optional starting point + /// + /// # Arguments + /// + /// * `first_key` - If None, start at the lowest key + /// + /// # Note + /// + /// If you always want to start at the beginning, [DbView::iter] is easier to use + /// If you always provide a key, [DbView::iter_from] is easier to use + /// + fn iter_option(&self, first_key: Option) -> Result, Error>; + + /// Obtain a stream over the keys/values of this view, starting from the beginning + fn iter(&self) -> Result, Error> { + self.iter_option(Option::>::None) + } + + /// Obtain a stream over the key/values, starting at a specific key + fn iter_from(&self, first_key: K) -> Result, Error> { + self.iter_option(Some(first_key)) + } } /// A proposal for a new revision of the database. diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 79d952f762b1..4fb65dc7d8b0 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -7,6 +7,7 @@ use super::{ }; use crate::merkle::Proof; use async_trait::async_trait; +use futures::Stream; use std::sync::Arc; /// An EmptyDb is a simple implementation of api::Db @@ -55,6 +56,8 @@ impl Db for EmptyDb { #[async_trait] impl DbView for HistoricalImpl { + type Stream<'a> = EmptyStreamer; + async fn root_hash(&self) -> Result { Ok(ROOT_HASH) } @@ -75,11 +78,30 @@ impl DbView for HistoricalImpl { ) -> Result, Vec>>, Error> { Ok(None) } + + fn iter_option(&self, _first_key: Option) -> Result { + Ok(EmptyStreamer {}) + } +} + +pub struct EmptyStreamer; + +impl Stream for EmptyStreamer { + type Item = Result<(Box<[u8]>, Vec), Error>; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(None) + } } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { + use futures::StreamExt; + use super::*; use crate::v2::api::{BatchOp, Proposal}; @@ -146,4 +168,22 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn empty_streamer() -> Result<(), Error> { + let emptydb = EmptyDb {}; + let rev = emptydb.revision(ROOT_HASH).await?; + let mut iter = rev.iter()?; + assert!(iter.next().await.is_none()); + Ok(()) + } + + #[tokio::test] + async fn empty_streamer_start_at() -> Result<(), Error> { + let emptydb = EmptyDb {}; + let rev = emptydb.revision(ROOT_HASH).await?; + let mut iter = rev.iter_from(b"ignored")?; + assert!(iter.next().await.is_none()); + Ok(()) + } } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index cf08df42ba26..95b015f99b80 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -4,6 +4,7 @@ use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; +use futures::stream::Empty; use crate::{merkle::Proof, v2::api}; @@ -101,6 +102,9 @@ impl Proposal { #[async_trait] impl api::DbView for Proposal { + // TODO: Replace with the correct stream type for an in-memory proposal implementation + type Stream<'a> = Empty, Vec), api::Error>> where T: 'a; + async fn root_hash(&self) -> Result { todo!(); } @@ -136,6 +140,13 @@ impl api::DbView for Proposal { ) -> Result, Vec>>, api::Error> { todo!(); } + + fn iter_option( + &self, + _first_key: Option, + ) -> Result, api::Error> { + todo!(); + } } #[async_trait] From 6639e345a015cbdbda44280c9f9b705b51a96243 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 27 Feb 2024 09:34:19 -0500 Subject: [PATCH 0489/1053] rename MerkleSetup to InMemoryMerkle (#564) --- firewood/src/db.rs | 3 +- firewood/src/merkle/proof.rs | 66 ++++++++++++++++---------- firewood/src/merkle_util.rs | 92 +++++++++++++++++++----------------- firewood/tests/merkle.rs | 16 +++---- 4 files changed, 97 insertions(+), 80 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 3f096cbc89cd..ed2410e70b71 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -392,7 +392,8 @@ impl + Send + Sync> DbRev { values: Vec, ) -> Result { let hash: [u8; 32] = *self.kv_root_hash()?; - let valid = proof.verify_range_proof(hash, first_key, last_key, keys, values)?; + let valid = + proof.verify_range_proof::(hash, first_key, last_key, keys, values)?; Ok(valid) } } diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index b2ee4568655c..c51f4267de1f 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -16,10 +16,10 @@ use crate::nibbles::NibblesIterator; use crate::{ db::DbError, merkle::{to_nibble_array, Merkle, MerkleError, Node, NodeType}, - merkle_util::{new_merkle, DataStoreError, MerkleSetup}, + merkle_util::{DataStoreError, InMemoryMerkle}, }; -use super::{BinarySerde, NodeObjRef}; +use super::{BinarySerde, EncodedNode, NodeObjRef}; #[derive(Debug, Error)] pub enum ProofError { @@ -146,14 +146,20 @@ impl + Send> Proof { self.0.extend(other.0) } - pub fn verify_range_proof, V: AsRef<[u8]>>( + pub fn verify_range_proof( &self, root_hash: HashKey, first_key: K, last_key: K, keys: Vec, vals: Vec, - ) -> Result { + ) -> Result + where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + T: BinarySerde, + EncodedNode: serde::Serialize + serde::de::DeserializeOwned, + { if keys.len() != vals.len() { return Err(ProofError::InconsistentProofData); } @@ -165,17 +171,17 @@ impl + Send> Proof { } // Use in-memory merkle - let mut merkle_setup = new_merkle(0x10000, 0x10000); + let mut in_mem_merkle = InMemoryMerkle::new(0x10000, 0x10000); // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if self.0.is_empty() { for (index, k) in keys.iter().enumerate() { #[allow(clippy::indexing_slicing)] - merkle_setup.insert(k, vals[index].as_ref().to_vec())?; + in_mem_merkle.insert(k, vals[index].as_ref().to_vec())?; } - let merkle_root = &*merkle_setup.root_hash()?; + let merkle_root = &*in_mem_merkle.root_hash()?; return if merkle_root == &root_hash { Ok(false) @@ -188,7 +194,7 @@ impl + Send> Proof { // ensure there are no more accounts / slots in the trie. if keys.is_empty() { let proof_to_path = - self.proof_to_path(first_key, root_hash, &mut merkle_setup, true)?; + self.proof_to_path(first_key, root_hash, &mut in_mem_merkle, true)?; return match proof_to_path { Some(_) => Err(ProofError::InvalidData), None => Ok(false), @@ -199,7 +205,7 @@ impl + Send> Proof { // In this case, we can't construct two edge paths. So handle it here. if keys.len() == 1 && first_key.as_ref() == last_key.as_ref() { let data = - self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, false)?; + self.proof_to_path(first_key.as_ref(), root_hash, &mut in_mem_merkle, false)?; #[allow(clippy::indexing_slicing)] return if first_key.as_ref() != keys[0].as_ref() { @@ -224,29 +230,29 @@ impl + Send> Proof { // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - self.proof_to_path(first_key.as_ref(), root_hash, &mut merkle_setup, true)?; + self.proof_to_path(first_key.as_ref(), root_hash, &mut in_mem_merkle, true)?; // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. - self.proof_to_path(last_key.as_ref(), root_hash, &mut merkle_setup, true)?; + self.proof_to_path(last_key.as_ref(), root_hash, &mut in_mem_merkle, true)?; // Remove all internal caculated values. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. let fork_at_root = - unset_internal(&mut merkle_setup, first_key.as_ref(), last_key.as_ref())?; + unset_internal(&mut in_mem_merkle, first_key.as_ref(), last_key.as_ref())?; // If the fork point is the root, the trie should be empty, start with a new one. if fork_at_root { - merkle_setup = new_merkle(0x100000, 0x100000); + in_mem_merkle = InMemoryMerkle::new(0x100000, 0x100000); } for (key, val) in keys.iter().zip(vals.iter()) { - merkle_setup.insert(key.as_ref(), val.as_ref().to_vec())?; + in_mem_merkle.insert(key.as_ref(), val.as_ref().to_vec())?; } // Calculate the hash - let merkle_root = &*merkle_setup.root_hash()?; + let merkle_root = &*in_mem_merkle.root_hash()?; if merkle_root == &root_hash { Ok(true) @@ -260,16 +266,21 @@ impl + Send> Proof { /// necessary nodes will be resolved and leave the remaining as hashnode. /// /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path, S: ShaleStore + Send + Sync, T: BinarySerde>( + fn proof_to_path( &self, key: K, root_hash: HashKey, - merkle_setup: &mut MerkleSetup, + in_mem_merkle: &mut InMemoryMerkle, allow_non_existent_node: bool, - ) -> Result>, ProofError> { + ) -> Result>, ProofError> + where + K: AsRef<[u8]>, + T: BinarySerde, + EncodedNode: serde::Serialize + serde::de::DeserializeOwned, + { // Start with the sentinel root - let sentinel = merkle_setup.get_sentinel_address(); - let merkle = merkle_setup.get_merkle_mut(); + let sentinel = in_mem_merkle.get_sentinel_address(); + let merkle = in_mem_merkle.get_merkle_mut(); let mut parent_node_ref = merkle .get_node(sentinel) .map_err(|_| ProofError::NoSuchNode)?; @@ -472,19 +483,24 @@ fn generate_subproof(encoded: &[u8]) -> Result { // // The return value indicates if the fork point is root node. If so, unset the // entire trie. -fn unset_internal, S: ShaleStore + Send + Sync, T: BinarySerde>( - merkle_setup: &mut MerkleSetup, +fn unset_internal( + in_mem_merkle: &mut InMemoryMerkle, left: K, right: K, -) -> Result { +) -> Result +where + K: AsRef<[u8]>, + T: BinarySerde, + EncodedNode: serde::Serialize + serde::de::DeserializeOwned, +{ // Add the sentinel root let mut left_chunks = vec![0]; left_chunks.extend(left.as_ref().iter().copied().flat_map(to_nibble_array)); // Add the sentinel root let mut right_chunks = vec![0]; right_chunks.extend(right.as_ref().iter().copied().flat_map(to_nibble_array)); - let root = merkle_setup.get_sentinel_address(); - let merkle = merkle_setup.get_merkle_mut(); + let root = in_mem_merkle.get_sentinel_address(); + let merkle = in_mem_merkle.get_merkle_mut(); let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; let mut parent = DiskAddress::null(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index fd53908be8e5..c64cea4d1961 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -4,11 +4,11 @@ use crate::{ merkle::{ proof::{Proof, ProofError}, - BinarySerde, Bincode, Merkle, Node, Ref, RefMut, TrieHash, + BinarySerde, EncodedNode, Merkle, Node, Ref, RefMut, TrieHash, }, shale::{ self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, - ShaleStore, StoredView, + StoredView, }, }; use std::num::NonZeroUsize; @@ -36,12 +36,53 @@ pub enum DataStoreError { ProofEmptyKeyValuesError, } -pub struct MerkleSetup { +type InMemoryStore = CompactSpace; + +pub struct InMemoryMerkle { root: DiskAddress, - merkle: Merkle, + merkle: Merkle, } -impl + Send + Sync, T: BinarySerde> MerkleSetup { +impl InMemoryMerkle +where + T: BinarySerde, + EncodedNode: serde::Serialize + serde::de::DeserializeOwned, +{ + pub fn new(meta_size: u64, compact_size: u64) -> Self { + const RESERVED: usize = 0x1000; + assert!(meta_size as usize > RESERVED); + assert!(compact_size as usize > RESERVED); + let mut dm = DynamicMem::new(meta_size, 0); + let compact_header = DiskAddress::null(); + #[allow(clippy::unwrap_used)] + dm.write( + compact_header.into(), + &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + NonZeroUsize::new(RESERVED).unwrap(), + #[allow(clippy::unwrap_used)] + NonZeroUsize::new(RESERVED).unwrap(), + )) + .unwrap(), + ); + #[allow(clippy::unwrap_used)] + let compact_header = + StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE) + .unwrap(); + let mem_meta = dm; + let mem_payload = DynamicMem::new(compact_size, 0x1); + + let cache = shale::ObjCache::new(1); + let space = + shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("CompactSpace init fail"); + + let merkle = Merkle::new(Box::new(space)); + #[allow(clippy::unwrap_used)] + let root = merkle.init_root().unwrap(); + + InMemoryMerkle { root, merkle } + } + pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { self.merkle .insert(key, val, self.root) @@ -63,7 +104,7 @@ impl + Send + Sync, T: BinarySerde> MerkleSetup { pub fn get_mut>( &mut self, key: K, - ) -> Result>, DataStoreError> { + ) -> Result>, DataStoreError> { self.merkle .get_mut(key, self.root) .map_err(|_err| DataStoreError::GetError) @@ -73,7 +114,7 @@ impl + Send + Sync, T: BinarySerde> MerkleSetup { self.root } - pub fn get_merkle_mut(&mut self) -> &mut Merkle { + pub fn get_merkle_mut(&mut self) -> &mut Merkle { &mut self.merkle } @@ -120,40 +161,3 @@ impl + Send + Sync, T: BinarySerde> MerkleSetup { proof.verify_range_proof(hash, first_key, last_key, keys, vals) } } - -pub fn new_merkle( - meta_size: u64, - compact_size: u64, -) -> MerkleSetup, Bincode> { - const RESERVED: usize = 0x1000; - assert!(meta_size as usize > RESERVED); - assert!(compact_size as usize > RESERVED); - let mut dm = DynamicMem::new(meta_size, 0); - let compact_header = DiskAddress::null(); - #[allow(clippy::unwrap_used)] - dm.write( - compact_header.into(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( - NonZeroUsize::new(RESERVED).unwrap(), - #[allow(clippy::unwrap_used)] - NonZeroUsize::new(RESERVED).unwrap(), - )) - .unwrap(), - ); - #[allow(clippy::unwrap_used)] - let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE).unwrap(); - let mem_meta = dm; - let mem_payload = DynamicMem::new(compact_size, 0x1); - - let cache = shale::ObjCache::new(1); - let space = - shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("CompactSpace init fail"); - - let merkle = Merkle::new(Box::new(space)); - #[allow(clippy::unwrap_used)] - let root = merkle.init_root().unwrap(); - - MerkleSetup { root, merkle } -} diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 34f314c676a6..a19ffd87626c 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -2,16 +2,12 @@ // See the file LICENSE.md for licensing terms. use firewood::{ - merkle::{Bincode, Node, Proof, ProofError}, - merkle_util::{new_merkle, DataStoreError, MerkleSetup}, - // TODO: we should not be using shale from an integration test - shale::{cached::DynamicMem, compact::CompactSpace}, + merkle::{Bincode, Proof, ProofError}, + merkle_util::{DataStoreError, InMemoryMerkle}, }; use rand::Rng; use std::{collections::HashMap, fmt::Write}; -type Store = CompactSpace; - fn merkle_build_test< K: AsRef<[u8]> + std::cmp::Ord + Clone + std::fmt::Debug, V: AsRef<[u8]> + Clone, @@ -19,8 +15,8 @@ fn merkle_build_test< items: Vec<(K, V)>, meta_size: u64, compact_size: u64, -) -> Result, DataStoreError> { - let mut merkle = new_merkle(meta_size, compact_size); +) -> Result, DataStoreError> { + let mut merkle = InMemoryMerkle::new(meta_size, compact_size); for (k, v) in items.iter() { merkle.insert(k, v.as_ref().to_vec())?; } @@ -113,7 +109,7 @@ fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { items.sort(); - let mut merkle = new_merkle(0x100000, 0x100000); + let mut merkle: InMemoryMerkle = InMemoryMerkle::new(0x100000, 0x100000); let mut hashes = Vec::new(); @@ -182,7 +178,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); items_ordered.sort(); items_ordered.shuffle(&mut *rng.borrow_mut()); - let mut merkle = new_merkle(0x100000, 0x100000); + let mut merkle: InMemoryMerkle = InMemoryMerkle::new(0x100000, 0x100000); for (k, v) in items.iter() { merkle.insert(k, v.to_vec())?; From 8ca10819b7c2dc28bb0c2faadca87fe208785985 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 27 Feb 2024 19:12:32 +0000 Subject: [PATCH 0490/1053] Prevent mutations to DbRev (#566) --- firewood/src/db.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ed2410e70b71..edeb9a38e7e4 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -345,16 +345,6 @@ impl + Send + Sync> DbRev { .key_value_iter_from_key(self.header.kv_root, start_key) } - fn flush_dirty(&mut self) -> Option<()> { - self.header.flush_dirty(); - self.merkle.flush_dirty()?; - Some(()) - } - - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { - (&mut self.header, &mut self.merkle) - } - /// Get root hash of the generic key-value storage. pub fn kv_root_hash(&self) -> Result { self.merkle @@ -398,6 +388,23 @@ impl + Send + Sync> DbRev { } } +impl DbRev { + fn borrow_split( + &mut self, + ) -> ( + &mut shale::Obj, + &mut Merkle, Bincode>, + ) { + (&mut self.header, &mut self.merkle) + } + + fn flush_dirty(&mut self) -> Option<()> { + self.header.flush_dirty(); + self.merkle.flush_dirty()?; + Some(()) + } +} + impl From> for DbRev { fn from(value: DbRev) -> Self { DbRev { From 957f8c7b4b40a7f4519acd2c0b64d9bb6bb3418e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 27 Feb 2024 14:51:04 -0500 Subject: [PATCH 0491/1053] replace NodeObjRef with NodeType as merkle.encode arg (#565) --- firewood/src/merkle.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 31dc75c61e84..023358c495ea 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -111,8 +111,8 @@ where // TODO: use `encode` / `decode` instead of `node.encode` / `node.decode` after extention node removal. #[allow(dead_code)] - fn encode(&self, node: &NodeObjRef) -> Result, MerkleError> { - let encoded = match node.inner() { + fn encode(&self, node: &NodeType) -> Result, MerkleError> { + let encoded = match node { NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), NodeType::Branch(n) => { @@ -124,7 +124,10 @@ where .map(|(child_addr, encoded_child)| { child_addr // if there's a child disk address here, get the encoded bytes - .map(|addr| self.get_node(addr).and_then(|node| self.encode(&node))) + .map(|addr| { + self.get_node(addr) + .and_then(|node| self.encode(node.inner())) + }) // or look for the pre-fetched bytes .or_else(|| encoded_child.as_ref().map(|child| Ok(child.to_vec()))) .transpose() @@ -1529,7 +1532,7 @@ mod tests { let merkle = create_generic_test_merkle::(); let node_ref = merkle.put_node(node.clone()).unwrap(); - let encoded = merkle.encode(&node_ref).unwrap(); + let encoded = merkle.encode(node_ref.inner()).unwrap(); let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); assert_eq!(node, new_node); From 9b49f694f6c35a06bc60a8d2565d93ad32604d63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:12:11 -0800 Subject: [PATCH 0492/1053] build(deps): update nix requirement from 0.27.1 to 0.28.0 (#563) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- firewood/Cargo.toml | 2 +- firewood/src/file.rs | 14 +++++++++++++- firewood/src/merkle/proof.rs | 2 +- firewood/src/storage/mod.rs | 18 ++++++------------ growth-ring/Cargo.toml | 2 +- growth-ring/src/walerror.rs | 2 +- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index bcab84ea03a4..60cafb5bad7a 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -26,7 +26,7 @@ futures = "0.3.30" hex = "0.4.3" lru = "0.12.2" metered = "0.9.0" -nix = {version = "0.27.1", features = ["fs", "uio"]} +nix = {version = "0.28.0", features = ["fs", "uio"]} parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } sha3 = "0.10.8" diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 84d81dff0526..0f9a7705b752 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -5,15 +5,27 @@ use std::fs::{create_dir, remove_dir_all}; use std::ops::Deref; -use std::os::fd::OwnedFd; +use std::os::fd::{AsRawFd, OwnedFd}; use std::path::{Path, PathBuf}; use std::{io::ErrorKind, os::unix::prelude::OpenOptionsExt}; +use nix::fcntl::Flockable; + pub struct File { fd: OwnedFd, } +impl AsRawFd for File { + fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd { + self.fd.as_raw_fd() + } +} + +// SAFETY: Docs for Flockable say it's safe if T is not Clone, +// and File is not clone +unsafe impl Flockable for File {} + #[derive(PartialEq, Eq)] pub enum Options { Truncate, diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index c51f4267de1f..a9c4d9052771 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -82,7 +82,7 @@ impl From for ProofError { // TODO: fix better by adding a new error to ProofError #[allow(clippy::unwrap_used)] DbError::IO(e) => { - ProofError::SystemError(nix::errno::Errno::from_i32(e.raw_os_error().unwrap())) + ProofError::SystemError(nix::errno::Errno::from_raw(e.raw_os_error().unwrap())) } DbError::Shale(e) => ProofError::Shale(e), DbError::InvalidProposal => ProofError::InvalidProof, diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 3f621da4882e..7ffbf9a1ea98 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -5,7 +5,7 @@ use self::buffer::DiskBufferRequester; use crate::file::File; use crate::shale::{self, CachedStore, CachedView, SendSyncDerefMut, SpaceId}; -use nix::fcntl::{flock, FlockArg}; +use nix::fcntl::{Flock, FlockArg}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::{ @@ -13,7 +13,7 @@ use std::{ fmt::{self, Debug}, num::NonZeroUsize, ops::{Deref, DerefMut}, - os::fd::{AsFd, AsRawFd}, + os::fd::AsFd, path::PathBuf, sync::Arc, }; @@ -906,8 +906,10 @@ impl FilePool { rootdir: rootdir.to_path_buf(), }; let f0 = s.get_file(0)?; - if flock(f0.as_raw_fd(), FlockArg::LockExclusiveNonblock).is_err() { - return Err(StoreError::Init("the store is busy".into())); + if let Some(inner) = Arc::::into_inner(f0) { + // first open of this file, acquire the lock + Flock::lock(inner, FlockArg::LockExclusiveNonblock) + .map_err(|_| StoreError::Init("the store is busy".into()))?; } Ok(s) } @@ -933,14 +935,6 @@ impl FilePool { } } -impl Drop for FilePool { - fn drop(&mut self) { - #[allow(clippy::unwrap_used)] - let f0 = self.get_file(0).unwrap(); - flock(f0.as_raw_fd(), FlockArg::UnlockNonblock).ok(); - } -} - #[derive(TypedBuilder, Clone, Debug)] pub struct WalConfig { #[builder(default = 22)] // 4MB Wal logs diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml index 7471953211f9..ab38d269227a 100644 --- a/growth-ring/Cargo.toml +++ b/growth-ring/Cargo.toml @@ -14,7 +14,7 @@ scan_fmt = "0.2.6" regex = "1.10.3" async-trait = "0.1.77" futures = "0.3.30" -nix = {version = "0.27.1", features = ["fs", "uio"]} +nix = {version = "0.28.0", features = ["fs", "uio"]} libc = "0.2.153" bytemuck = {version = "1.14.3", features = ["derive"]} thiserror = "1.0.57" diff --git a/growth-ring/src/walerror.rs b/growth-ring/src/walerror.rs index 508dd146274a..6900f0ba2d8c 100644 --- a/growth-ring/src/walerror.rs +++ b/growth-ring/src/walerror.rs @@ -22,7 +22,7 @@ pub enum WalError { impl From for WalError { fn from(value: i32) -> Self { - Self::UnixError(Errno::from_i32(value)) + Self::UnixError(Errno::from_raw(value)) } } From 983765c5df0cfae700c32060de26727e649e0737 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 28 Feb 2024 09:34:35 -0500 Subject: [PATCH 0493/1053] remove dead code from bincode deserialize of EncodedNode (#567) --- firewood/src/merkle/node.rs | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index f7452165c907..6bc5eb17a9fb 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -735,7 +735,7 @@ impl<'de> Deserialize<'de> for EncodedNode { .map_err(D::Error::custom)?; let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; - let mut value = items + let value = items .pop() .unwrap_or_default() .deserialize::() @@ -747,26 +747,13 @@ impl<'de> Deserialize<'de> for EncodedNode { let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); for (i, chd) in items.into_iter().enumerate() { - if i == len - 1 { - let data = match chd { - Encoded::Raw(data) => Err(D::Error::custom(format!( - "incorrect encoded type for branch node value {:?}", - data - )))?, - Encoded::Data(data) => Bincode::deserialize(data.as_ref()) - .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, - }; - // Extract the value of the branch node and set to None if it's an empty Vec - value = Some(Data(data)).filter(|data| !data.is_empty()); - } else { - let chd = match chd { - Encoded::Raw(chd) => chd, - Encoded::Data(chd) => Bincode::deserialize(chd.as_ref()) - .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, - }; - #[allow(clippy::indexing_slicing)] - (children[i] = Some(chd).filter(|chd| !chd.is_empty())); - } + let chd = match chd { + Encoded::Raw(chd) => chd, + Encoded::Data(chd) => Bincode::deserialize(chd.as_ref()) + .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, + }; + #[allow(clippy::indexing_slicing)] + (children[i] = Some(chd).filter(|chd| !chd.is_empty())); } let node = EncodedNodeType::Branch { From 3530accecf386a131e5c92030cdc37d81b47117a Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 28 Feb 2024 09:47:04 -0500 Subject: [PATCH 0494/1053] remove `Encoded` type (#568) --- firewood/src/merkle/node.rs | 98 ++++++++---------------------- firewood/src/merkle/node/branch.rs | 48 +++++---------- firewood/src/merkle/node/leaf.rs | 11 ++-- 3 files changed, 43 insertions(+), 114 deletions(-) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 6bc5eb17a9fb..6cb6f033360a 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -11,7 +11,6 @@ use bitflags::bitflags; use bytemuck::{CheckedBitPattern, NoUninit, Pod, Zeroable}; use enum_as_inner::EnumAsInner; use serde::{ - de::DeserializeOwned, ser::{SerializeSeq, SerializeTuple}, Deserialize, Serialize, }; @@ -25,6 +24,7 @@ use std::{ atomic::{AtomicBool, Ordering}, OnceLock, }, + vec, }; mod branch; @@ -69,35 +69,6 @@ impl Data { } } -#[derive(Serialize, Deserialize, Debug)] -enum Encoded { - Raw(T), - Data(T), -} - -impl Default for Encoded> { - fn default() -> Self { - // This is the default serialized empty vector - Encoded::Data(vec![0]) - } -} - -impl> Encoded { - pub fn decode(self) -> Result { - match self { - Encoded::Raw(raw) => Ok(raw), - Encoded::Data(data) => bincode::DefaultOptions::new().deserialize(data.as_ref()), - } - } - - pub fn deserialize(self) -> Result { - match self { - Encoded::Raw(raw) => Ok(raw), - Encoded::Data(data) => De::deserialize(data.as_ref()), - } - } -} - #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] pub enum NodeType { Branch(Box), @@ -106,14 +77,14 @@ pub enum NodeType { impl NodeType { pub fn decode(buf: &[u8]) -> Result { - let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + let items: Vec> = bincode::DefaultOptions::new().deserialize(buf)?; match items.len() { LEAF_NODE_SIZE => { let mut items = items.into_iter(); #[allow(clippy::unwrap_used)] - let decoded_key: Vec = items.next().unwrap().decode()?; + let decoded_key: Vec = items.next().unwrap(); let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); @@ -121,7 +92,7 @@ impl NodeType { let cur_key = cur_key_path.into_inner(); #[allow(clippy::unwrap_used)] - let data: Vec = items.next().unwrap().decode()?; + let data: Vec = items.next().unwrap(); Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) } @@ -633,13 +604,11 @@ impl<'de> Deserialize<'de> for EncodedNode { // Note that the serializer passed in should always be the same type as T in EncodedNode. impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result { - use serde::ser::Error; - match &self.node { EncodedNodeType::Leaf(n) => { let list = [ - Encoded::Raw(from_nibbles(&n.path.encode(true)).collect()), - Encoded::Raw(n.data.to_vec()), + from_nibbles(&n.path.encode(true)).collect(), + n.data.to_vec(), ]; let mut seq = serializer.serialize_seq(Some(list.len()))?; for e in list { @@ -653,7 +622,7 @@ impl Serialize for EncodedNode { children, value, } => { - let mut list = <[Encoded>; BranchNode::MAX_CHILDREN + 2]>::default(); + let mut list = <[Vec; BranchNode::MAX_CHILDREN + 2]>::default(); let children = children .iter() .enumerate() @@ -662,27 +631,19 @@ impl Serialize for EncodedNode { #[allow(clippy::indexing_slicing)] for (i, child) in children { if child.len() >= TRIE_HASH_LEN { - let serialized_hash = - Bincode::serialize(&Keccak256::digest(child).to_vec()) - .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; - list[i] = Encoded::Data(serialized_hash); + let serialized_hash = Keccak256::digest(child).to_vec(); + list[i] = serialized_hash; } else { - list[i] = Encoded::Raw(child.to_vec()); + list[i] = child.to_vec(); } } - list[BranchNode::MAX_CHILDREN] = if let Some(Data(val)) = &value { - let serialized_val = Bincode::serialize(val) - .map_err(|e| S::Error::custom(format!("bincode error: {e}")))?; - - Encoded::Data(serialized_val) - } else { - Encoded::default() - }; + if let Some(Data(val)) = &value { + list[BranchNode::MAX_CHILDREN] = val.clone(); + } let serialized_path = from_nibbles(&path.encode(true)).collect(); - - list[BranchNode::MAX_CHILDREN + 1] = Encoded::Raw(serialized_path); + list[BranchNode::MAX_CHILDREN + 1] = serialized_path; let mut seq = serializer.serialize_seq(Some(list.len()))?; @@ -703,18 +664,18 @@ impl<'de> Deserialize<'de> for EncodedNode { { use serde::de::Error; - let mut items: Vec>> = Deserialize::deserialize(deserializer)?; + let mut items: Vec> = Deserialize::deserialize(deserializer)?; let len = items.len(); match len { LEAF_NODE_SIZE => { let mut items = items.into_iter(); - let Some(Encoded::Raw(path)) = items.next() else { + let Some(path) = items.next() else { return Err(D::Error::custom( "incorrect encoded type for leaf node path", )); }; - let Some(Encoded::Raw(data)) = items.next() else { + let Some(data) = items.next() else { return Err(D::Error::custom( "incorrect encoded type for leaf node data", )); @@ -728,30 +689,19 @@ impl<'de> Deserialize<'de> for EncodedNode { } BranchNode::MSIZE => { - let path = items - .pop() - .unwrap_or_default() - .deserialize::() - .map_err(D::Error::custom)?; + let path = items.pop().expect("length was checked above"); let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; - let value = items - .pop() - .unwrap_or_default() - .deserialize::() - .map_err(D::Error::custom) - .map(Data) - .map(Some)? - .filter(|data| !data.is_empty()); + let value = items.pop().expect("length was checked above"); + let value = if value.is_empty() { + None + } else { + Some(Data(value)) + }; let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); for (i, chd) in items.into_iter().enumerate() { - let chd = match chd { - Encoded::Raw(chd) => chd, - Encoded::Data(chd) => Bincode::deserialize(chd.as_ref()) - .map_err(|e| D::Error::custom(format!("bincode error: {e}")))?, - }; #[allow(clippy::indexing_slicing)] (children[i] = Some(chd).filter(|chd| !chd.is_empty())); } diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 2b05b715679f..749c02586029 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Data, Encoded, Node}; +use super::{Data, Node}; use crate::{ - merkle::{from_nibbles, to_nibble_array, PartialPath, TRIE_HASH_LEN}, + merkle::{from_nibbles, to_nibble_array, PartialPath}, nibbles::Nibbles, shale::{DiskAddress, ShaleError, ShaleStore, Storable}, }; @@ -96,18 +96,15 @@ impl BranchNode { } pub(super) fn decode(buf: &[u8]) -> Result { - let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + let mut items: Vec> = bincode::DefaultOptions::new().deserialize(buf)?; - let path = items - .pop() - .ok_or(Error::custom("Invalid Branch Node"))? - .decode()?; + let path = items.pop().ok_or(Error::custom("Invalid Branch Node"))?; let path = Nibbles::<0>::new(&path); let (path, _term) = PartialPath::from_nibbles(path.into_iter()); // we've already validated the size, that's why we can safely unwrap #[allow(clippy::unwrap_used)] - let data = items.pop().unwrap().decode()?; + let data = items.pop().unwrap(); // Extract the value of the branch node and set to None if it's an empty Vec let value = Some(data).filter(|data| !data.is_empty()); @@ -116,9 +113,8 @@ impl BranchNode { // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { - let data = chd.decode()?; #[allow(clippy::indexing_slicing)] - (chd_encoded[i] = Some(data).filter(|data| !data.is_empty())); + (chd_encoded[i] = Some(chd).filter(|data| !data.is_empty())); } Ok(BranchNode::new( @@ -131,7 +127,7 @@ impl BranchNode { pub(super) fn encode>(&self, store: &S) -> Vec { // path + children + value - let mut list = <[Encoded>; Self::MSIZE]>::default(); + let mut list = <[Vec; Self::MSIZE]>::default(); for (i, c) in self.children.iter().enumerate() { match c { @@ -142,11 +138,7 @@ impl BranchNode { #[allow(clippy::unwrap_used)] if c_ref.is_encoded_longer_than_hash_len::(store) { #[allow(clippy::indexing_slicing)] - (list[i] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*c_ref.get_root_hash::(store))[..]) - .unwrap(), - )); + (list[i] = c_ref.get_root_hash::(store).to_vec()); // See struct docs for ordering requirements if c_ref.is_dirty() { @@ -154,9 +146,9 @@ impl BranchNode { c_ref.set_dirty(false); } } else { - let child_encoded = &c_ref.get_encoded::(store); + let child_encoded = c_ref.get_encoded::(store); #[allow(clippy::indexing_slicing)] - (list[i] = Encoded::Raw(child_encoded.to_vec())); + (list[i] = child_encoded.to_vec()); } } @@ -171,15 +163,8 @@ impl BranchNode { // can happen when manually constructing a trie from proof. #[allow(clippy::indexing_slicing)] if let Some(v) = &self.children_encoded[i] { - if v.len() == TRIE_HASH_LEN { - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - (list[i] = Encoded::Data( - bincode::DefaultOptions::new().serialize(v).unwrap(), - )); - } else { - #[allow(clippy::indexing_slicing)] - (list[i] = Encoded::Raw(v.clone())); - } + #[allow(clippy::indexing_slicing)] + (list[i] = v.clone()); } } }; @@ -187,18 +172,13 @@ impl BranchNode { #[allow(clippy::unwrap_used)] if let Some(Data(val)) = &self.value { - list[Self::MAX_CHILDREN] = - Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); + list[Self::MAX_CHILDREN] = val.clone(); } #[allow(clippy::unwrap_used)] let path = from_nibbles(&self.path.encode(false)).collect::>(); - list[Self::MAX_CHILDREN + 1] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&path) - .expect("serializing raw bytes to always succeed"), - ); + list[Self::MAX_CHILDREN + 1] = path; bincode::DefaultOptions::new() .serialize(list.as_slice()) diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 552caf3cccfc..c38a20328dbc 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Data, Encoded}; +use super::Data; use crate::{ merkle::{from_nibbles, PartialPath}, nibbles::Nibbles, @@ -53,8 +53,8 @@ impl LeafNode { bincode::DefaultOptions::new() .serialize( [ - Encoded::Raw(from_nibbles(&self.path.encode(true)).collect()), - Encoded::Raw(self.data.to_vec()), + from_nibbles(&self.path.encode(true)).collect(), + self.data.to_vec(), ] .as_slice(), ) @@ -156,9 +156,8 @@ mod tests { let data = vec![5, 6, 7, 8]; let serialized_path = [vec![prefix], path.clone()].concat(); - // 0 represents Encoded::Raw - let serialized_path = [vec![0, serialized_path.len() as u8], serialized_path].concat(); - let serialized_data = [vec![0, data.len() as u8], data.clone()].concat(); + let serialized_path = [vec![serialized_path.len() as u8], serialized_path].concat(); + let serialized_data = [vec![data.len() as u8], data.clone()].concat(); let serialized = [vec![2], serialized_path, serialized_data].concat(); From 4d874245b1e05faabc807650637d16f97bbe6a23 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 1 Mar 2024 14:58:35 -0500 Subject: [PATCH 0495/1053] remove terminal flag from PartialPath (#571) --- firewood/src/merkle.rs | 2 +- firewood/src/merkle/node.rs | 20 ++++++--------- firewood/src/merkle/node/branch.rs | 8 +++--- firewood/src/merkle/node/leaf.rs | 13 ++++++---- firewood/src/merkle/node/partial_path.rs | 31 ++++++++++-------------- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 023358c495ea..c20e54d77b39 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -159,7 +159,7 @@ where children, value, } => { - let path = PartialPath::decode(&path).0; + let path = PartialPath::decode(&path); let value = value.map(|v| v.0); let branch = NodeType::Branch( BranchNode::new(path, [None; BranchNode::MAX_CHILDREN], value, *children) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 6cb6f033360a..c743ac8855c6 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -42,7 +42,6 @@ use super::{TrieHash, TRIE_HASH_LEN}; bitflags! { // should only ever be the size of a nibble struct Flags: u8 { - const TERMINAL = 0b0010; const ODD_LEN = 0b0001; } } @@ -88,7 +87,7 @@ impl NodeType { let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); - let cur_key_path = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()).0; + let cur_key_path = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); #[allow(clippy::unwrap_used)] @@ -522,7 +521,7 @@ impl Serialize for EncodedNode { EncodedNodeType::Leaf(n) => { let data = Some(&*n.data); let chd: Vec<(u64, Vec)> = Default::default(); - let path: Vec<_> = from_nibbles(&n.path.encode(true)).collect(); + let path: Vec<_> = from_nibbles(&n.path.encode()).collect(); (chd, data, path) } @@ -546,7 +545,7 @@ impl Serialize for EncodedNode { let data = value.as_deref(); - let path = from_nibbles(&path.encode(false)).collect(); + let path = from_nibbles(&path.encode()).collect(); (chd, data, path) } @@ -569,7 +568,7 @@ impl<'de> Deserialize<'de> for EncodedNode { { let EncodedBranchNode { chd, data, path } = Deserialize::deserialize(deserializer)?; - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); if chd.is_empty() { let data = if let Some(d) = data { @@ -606,10 +605,7 @@ impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result { match &self.node { EncodedNodeType::Leaf(n) => { - let list = [ - from_nibbles(&n.path.encode(true)).collect(), - n.data.to_vec(), - ]; + let list = [from_nibbles(&n.path.encode()).collect(), n.data.to_vec()]; let mut seq = serializer.serialize_seq(Some(list.len()))?; for e in list { seq.serialize_element(&e)?; @@ -642,7 +638,7 @@ impl Serialize for EncodedNode { list[BranchNode::MAX_CHILDREN] = val.clone(); } - let serialized_path = from_nibbles(&path.encode(true)).collect(); + let serialized_path = from_nibbles(&path.encode()).collect(); list[BranchNode::MAX_CHILDREN + 1] = serialized_path; let mut seq = serializer.serialize_seq(Some(list.len()))?; @@ -680,7 +676,7 @@ impl<'de> Deserialize<'de> for EncodedNode { "incorrect encoded type for leaf node data", )); }; - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let node = EncodedNodeType::Leaf(LeafNode { path, data: Data(data), @@ -690,7 +686,7 @@ impl<'de> Deserialize<'de> for EncodedNode { BranchNode::MSIZE => { let path = items.pop().expect("length was checked above"); - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()).0; + let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let value = items.pop().expect("length was checked above"); let value = if value.is_empty() { diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 749c02586029..62e1941fad2c 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -100,7 +100,7 @@ impl BranchNode { let path = items.pop().ok_or(Error::custom("Invalid Branch Node"))?; let path = Nibbles::<0>::new(&path); - let (path, _term) = PartialPath::from_nibbles(path.into_iter()); + let path = PartialPath::from_nibbles(path.into_iter()); // we've already validated the size, that's why we can safely unwrap #[allow(clippy::unwrap_used)] @@ -176,7 +176,7 @@ impl BranchNode { } #[allow(clippy::unwrap_used)] - let path = from_nibbles(&self.path.encode(false)).collect::>(); + let path = from_nibbles(&self.path.encode()).collect::>(); list[Self::MAX_CHILDREN + 1] = path; @@ -202,7 +202,7 @@ impl Storable for BranchNode { fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path: Vec = from_nibbles(&self.path.encode(false)).collect(); + let path: Vec = from_nibbles(&self.path.encode()).collect(); cursor.write_all(&[path.len() as PathLen])?; cursor.write_all(&path)?; @@ -271,7 +271,7 @@ impl Storable for BranchNode { addr += path_len as usize; let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); - let path = PartialPath::decode(&path).0; + let path = PartialPath::decode(&path); let node_raw = mem.get_view(addr, BRANCH_HEADER_SIZE) diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index c38a20328dbc..905cf2b4c049 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -53,7 +53,7 @@ impl LeafNode { bincode::DefaultOptions::new() .serialize( [ - from_nibbles(&self.path.encode(true)).collect(), + from_nibbles(&self.path.encode()).collect(), self.data.to_vec(), ] .as_slice(), @@ -85,7 +85,7 @@ impl Storable for LeafNode { fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path = &self.path.encode(true); + let path = &self.path.encode(); let path = from_nibbles(path); let data = &self.data; @@ -149,9 +149,12 @@ mod tests { use test_case::test_case; // these tests will fail if the encoding mechanism changes and should be updated accordingly - #[test_case(0b10 << 4, vec![0x12, 0x34], vec![1, 2, 3, 4]; "even length")] - // first nibble is part of the prefix - #[test_case((0b11 << 4) + 2, vec![0x34], vec![2, 3, 4]; "odd length")] + // + // Even length so ODD_LEN flag is not set so flag byte is 0b0000_0000 + #[test_case(0x00, vec![0x12, 0x34], vec![1, 2, 3, 4]; "even length")] + // Odd length so ODD_LEN flag is set so flag byte is 0b0000_0001 + // This is combined with the first nibble of the path (0b0000_0010) to become 0b0001_0010 + #[test_case(0b0001_0010, vec![0x34], vec![2, 3, 4]; "odd length")] fn encode_regression_test(prefix: u8, path: Vec, nibbles: Vec) { let data = vec![5, 6, 7, 8]; diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs index 1e1d5a74ce6f..fe1583b3d7c5 100644 --- a/firewood/src/merkle/node/partial_path.rs +++ b/firewood/src/merkle/node/partial_path.rs @@ -40,13 +40,9 @@ impl PartialPath { self.0 } - pub(crate) fn encode(&self, is_terminal: bool) -> Vec { + pub(crate) fn encode(&self) -> Vec { let mut flags = Flags::empty(); - if is_terminal { - flags.insert(Flags::TERMINAL); - } - let has_odd_len = self.0.len() & 1 == 1; let extra_byte = if has_odd_len { @@ -67,24 +63,24 @@ impl PartialPath { // I also think `PartialPath` could probably borrow instead of own data. // /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn decode(raw: &[u8]) -> (Self, bool) { + pub fn decode(raw: &[u8]) -> Self { Self::from_iter(raw.iter().copied()) } /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn from_nibbles(nibbles: NibblesIterator<'_, N>) -> (Self, bool) { + pub fn from_nibbles(nibbles: NibblesIterator<'_, N>) -> Self { Self::from_iter(nibbles) } /// Assumes all bytes are nibbles, prefer to use `from_nibbles` instead. - fn from_iter>(mut iter: Iter) -> (Self, bool) { + fn from_iter>(mut iter: Iter) -> Self { let flags = Flags::from_bits_retain(iter.next().unwrap_or_default()); if !flags.contains(Flags::ODD_LEN) { let _ = iter.next(); } - (Self(iter.collect()), flags.contains(Flags::TERMINAL)) + Self(iter.collect()) } pub(super) fn serialized_len(&self) -> u64 { @@ -107,20 +103,19 @@ mod tests { use super::*; use test_case::test_case; - #[test_case(&[1, 2, 3, 4], true)] - #[test_case(&[1, 2, 3], false)] - #[test_case(&[0, 1, 2], false)] - #[test_case(&[1, 2], true)] - #[test_case(&[1], true)] - fn test_encoding(steps: &[u8], term: bool) { + #[test_case(&[1, 2, 3, 4])] + #[test_case(&[1, 2, 3])] + #[test_case(&[0, 1, 2])] + #[test_case(&[1, 2])] + #[test_case(&[1])] + fn test_encoding(steps: &[u8]) { let path = PartialPath(steps.to_vec()); - let encoded = path.encode(term); + let encoded = path.encode(); assert_eq!(encoded.len(), path.serialized_len() as usize * 2); - let (decoded, decoded_term) = PartialPath::decode(&encoded); + let decoded = PartialPath::decode(&encoded); assert_eq!(&&*decoded, &steps); - assert_eq!(decoded_term, term); } } From fc2c1d26b88f0df2b0014fdbbc1bbd175a01d438 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 4 Mar 2024 19:50:28 +0000 Subject: [PATCH 0496/1053] Use the typed encoder, not always Bincode (#573) --- firewood/src/merkle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index c20e54d77b39..70196ec0e967 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -144,7 +144,7 @@ where } }; - Bincode::serialize(&encoded).map_err(|e| MerkleError::BinarySerdeError(e.to_string())) + T::serialize(&encoded).map_err(|e| MerkleError::BinarySerdeError(e.to_string())) } #[allow(dead_code)] From 9a5b381dd2ae82f590568b1c735c55c8d145090e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 4 Mar 2024 21:33:41 +0000 Subject: [PATCH 0497/1053] Make the merkle tests idempotent (#572) --- firewood/tests/merkle.rs | 50 ++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index a19ffd87626c..14843e03b34c 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -5,7 +5,7 @@ use firewood::{ merkle::{Bincode, Proof, ProofError}, merkle_util::{DataStoreError, InMemoryMerkle}, }; -use rand::Rng; +use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng as _}; use std::{collections::HashMap, fmt::Write}; fn merkle_build_test< @@ -224,7 +224,7 @@ fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { #[test] #[allow(clippy::unwrap_used, clippy::indexing_slicing)] fn test_proof() -> Result<(), DataStoreError> { - let set = generate_random_data(500); + let set = fixed_and_pseudorandom_data(500); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -287,7 +287,7 @@ fn test_proof_end_with_branch() -> Result<(), DataStoreError> { #[test] fn test_bad_proof() -> Result<(), DataStoreError> { - let set = generate_random_data(800); + let set = fixed_and_pseudorandom_data(800); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -341,7 +341,7 @@ fn test_empty_tree_proof() -> Result<(), DataStoreError> { // The test cases are generated randomly. #[allow(clippy::indexing_slicing)] fn test_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -377,7 +377,7 @@ fn test_range_proof() -> Result<(), ProofError> { // The prover is expected to detect the error. #[allow(clippy::indexing_slicing)] fn test_bad_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -455,7 +455,7 @@ fn test_bad_range_proof() -> Result<(), ProofError> { // The test cases are generated randomly. #[allow(clippy::indexing_slicing)] fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -524,7 +524,7 @@ fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { // - There exists a gap between the last element and the right edge proof #[allow(clippy::indexing_slicing)] fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -583,7 +583,7 @@ fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> // non-existent one. #[allow(clippy::indexing_slicing)] fn test_one_element_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -671,7 +671,7 @@ fn test_one_element_range_proof() -> Result<(), ProofError> { // The edge proofs can be nil. #[allow(clippy::indexing_slicing)] fn test_all_elements_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -727,7 +727,7 @@ fn test_all_elements_proof() -> Result<(), ProofError> { // be a non-existent proof. #[allow(clippy::indexing_slicing)] fn test_empty_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -798,7 +798,7 @@ fn test_gapped_range_proof() -> Result<(), ProofError> { // Tests the element is not in the range covered by proofs. #[allow(clippy::indexing_slicing)] fn test_same_side_proof() -> Result<(), DataStoreError> { - let set = generate_random_data(4096); + let set = fixed_and_pseudorandom_data(4096); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -938,7 +938,7 @@ fn test_both_sides_range_proof() -> Result<(), ProofError> { // as the existent proof, but with an extra empty value included, which is a // noop technically, but practically should be rejected. fn test_empty_value_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(512); + let set = fixed_and_pseudorandom_data(512); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -974,7 +974,7 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { // but with an extra empty value included, which is a noop technically, but // practically should be rejected. fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { - let set = generate_random_data(512); + let set = fixed_and_pseudorandom_data(512); let mut items = Vec::from_iter(set.iter()); items.sort(); let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -1078,8 +1078,10 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { Ok(()) } +// generate pseudorandom data, but prefix it with some known data +// The number of fixed data points is 100; you specify how much random data you want #[allow(clippy::indexing_slicing)] -fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { +fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; @@ -1097,9 +1099,23 @@ fn generate_random_data(n: u32) -> HashMap<[u8; 32], [u8; 20]> { items.insert(more_key, data); } - for _ in 0..n { - let key = rand::thread_rng().gen::<[u8; 32]>(); - let val = rand::thread_rng().gen::<[u8; 20]>(); + // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| thread_rng().gen()); + + // the test framework will only render this in verbose mode or if the test fails + // to re-run the test when it fails, just specify the seed instead of randomly + // selecting one + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let mut r = StdRng::seed_from_u64(seed); + for _ in 0..random_count { + let key = r.gen::<[u8; 32]>(); + let val = r.gen::<[u8; 20]>(); items.insert(key, val); } items From 5f63b818d1837a8f82942a8e805e3abda0601b09 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Mar 2024 15:26:28 -0800 Subject: [PATCH 0498/1053] Make sure free space has a writeable CachedStore (#574) --- firewood/benches/hashops.rs | 2 +- firewood/benches/shale-bench.rs | 2 +- firewood/src/db.rs | 23 ++++++++++++++--------- firewood/src/db/proposal.rs | 2 +- firewood/src/merkle.rs | 3 ++- firewood/src/merkle/node.rs | 2 +- firewood/src/merkle_util.rs | 3 ++- firewood/src/shale/cached.rs | 28 ++++++++++++++++++---------- firewood/src/shale/compact.rs | 3 ++- firewood/src/shale/mod.rs | 12 ++++++++++-- firewood/src/shale/plainmem.rs | 9 +++++++-- firewood/src/storage/buffer.rs | 12 ++++++------ firewood/src/storage/mod.rs | 27 ++++++++++++++++++++++++--- 13 files changed, 89 insertions(+), 39 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index ed4e26b0c24a..50b558359160 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -68,7 +68,7 @@ impl Profiler for FlamegraphProfiler { fn bench_trie_hash(criterion: &mut Criterion) { let mut to = [1u8; TRIE_HASH_LEN]; let mut store = DynamicMem::new(TRIE_HASH_LEN as u64, 0u8); - store.write(0, &*ZERO_HASH); + store.write(0, &*ZERO_HASH).expect("write should succeed"); #[allow(clippy::unwrap_used)] criterion diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index a7649882d3a9..73870e45b660 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -67,7 +67,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { let offset = rng.gen_range(0..BENCH_MEM_SIZE - len); - cached.write(offset, rdata); + cached.write(offset, rdata).expect("write should succeed"); #[allow(clippy::unwrap_used)] let view = cached .get_view(offset, rdata.len().try_into().unwrap()) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index edeb9a38e7e4..ffc37a681b36 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -406,7 +406,8 @@ impl DbRev { } impl From> for DbRev { - fn from(value: DbRev) -> Self { + fn from(mut value: DbRev) -> Self { + value.flush_dirty(); DbRev { header: value.header, merkle: value.merkle.into(), @@ -616,16 +617,20 @@ impl Db { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), }; - let db_header_ref = Db::get_db_header_ref(&base.merkle.meta)?; + // convert the base merkle objects into writable ones + let meta: StoreRevMut = base.merkle.meta.clone().into(); + let payload: StoreRevMut = base.merkle.payload.clone().into(); + // get references to the DbHeader and the CompactSpaceHeader + // for free space management + let db_header_ref = Db::get_db_header_ref(&meta)?; let merkle_payload_header_ref = - Db::get_payload_header_ref(&base.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - + Db::get_payload_header_ref(&meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; let header_refs = (db_header_ref, merkle_payload_header_ref); - let base_revision = Db::new_revision( + let base_revision = Db::new_revision::( header_refs, - (base.merkle.meta.clone(), base.merkle.payload.clone()), + (meta, payload), params.payload_regn_nbit, cfg.payload_max_walk, &cfg.rev, @@ -644,7 +649,7 @@ impl Db { root_hashes: VecDeque::new(), max_revisions: cfg.wal.max_revisions as usize, base, - base_revision: Arc::new(base_revision), + base_revision: Arc::new(base_revision.into()), })), payload_regn_nbit: params.payload_regn_nbit, metrics: Arc::new(DbMetrics::default()), @@ -718,11 +723,11 @@ impl Db { #[allow(clippy::unwrap_used)] NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), ))?, - ); + )?; merkle_meta_store.write( db_header.into(), &shale::to_dehydrated(&DbHeader::new_empty())?, - ); + )?; } let store = Universe { diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 5b9711a7131e..1b01be3420ad 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -225,7 +225,7 @@ impl Proposal { .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); } - rev_inner.root_hash_staging.write(0, &hash.0); + rev_inner.root_hash_staging.write(0, &hash.0)?; let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); // schedule writes to the disk diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 70196ec0e967..77d252e8bb0b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1423,7 +1423,8 @@ mod tests { std::num::NonZeroUsize::new(RESERVED).unwrap(), )) .unwrap(), - ); + ) + .unwrap(); let compact_header = shale::StoredView::ptr_to_obj( &dm, compact_header, diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index c743ac8855c6..7cf6b89ad210 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -942,7 +942,7 @@ mod tests { node.serialize(&mut bytes).expect("node should serialize"); let mut mem = PlainMem::new(serialized_len, 0); - mem.write(0, &bytes); + mem.write(0, &bytes).expect("write should succed"); let mut hydrated_node = Node::deserialize(0, &mem).expect("node should deserialize"); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index c64cea4d1961..59086632a832 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -63,7 +63,8 @@ where NonZeroUsize::new(RESERVED).unwrap(), )) .unwrap(), - ); + ) + .expect("write should succeed"); #[allow(clippy::unwrap_used)] let compact_header = StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE) diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 0beeaa8dfbc9..28f282d2c53a 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -11,6 +11,8 @@ use std::{ #[cfg(test)] pub use crate::shale::plainmem::PlainMem; +use super::ShaleError; + // Purely volatile, dynamically allocated vector-based implementation for // [CachedStore]. This is similar to PlainMem (in testing). The only // difference is, when [write] dynamically allocate more space if original @@ -61,7 +63,7 @@ impl CachedStore for DynamicMem { })) } - fn write(&mut self, offset: usize, change: &[u8]) { + fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError> { let length = change.len(); let size = offset + length; @@ -73,12 +75,18 @@ impl CachedStore for DynamicMem { space.resize(size, 0); } #[allow(clippy::indexing_slicing)] - space[offset..offset + length].copy_from_slice(change) + space[offset..offset + length].copy_from_slice(change); + + Ok(()) } fn id(&self) -> SpaceId { self.id } + + fn is_writeable(&self) -> bool { + true + } } #[derive(Debug)] @@ -122,14 +130,14 @@ mod tests { fn test_plain_mem() { let mut view = PlainMemShared(PlainMem::new(2, 0)); let mem = &mut *view; - mem.write(0, &[1, 1]); - mem.write(0, &[1, 2]); + mem.write(0, &[1, 1]).unwrap(); + mem.write(0, &[1, 2]).unwrap(); #[allow(clippy::unwrap_used)] let r = mem.get_view(0, 2).unwrap().as_deref(); assert_eq!(r, [1, 2]); // previous view not mutated by write - mem.write(0, &[1, 3]); + mem.write(0, &[1, 3]).unwrap(); assert_eq!(r, [1, 2]); let r = mem.get_view(0, 2).unwrap().as_deref(); assert_eq!(r, [1, 3]); @@ -145,20 +153,20 @@ mod tests { let mem = &mut *view; // out of range - mem.write(1, &[7, 8]); + mem.write(1, &[7, 8]).unwrap(); } #[test] fn test_dynamic_mem() { let mut view = DynamicMemShared(DynamicMem::new(2, 0)); let mem = &mut *view; - mem.write(0, &[1, 2]); - mem.write(0, &[3, 4]); + mem.write(0, &[1, 2]).unwrap(); + mem.write(0, &[3, 4]).unwrap(); assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); - mem.get_shared().write(0, &[5, 6]); + mem.get_shared().write(0, &[5, 6]).unwrap(); // capacity is increased - mem.write(5, &[0; 10]); + mem.write(5, &[0; 10]).unwrap(); // get a view larger than recent growth assert_eq!(mem.get_view(3, 20).unwrap().as_deref(), [0; 20]); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index b71a3f5f03d8..7317bbe225a5 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -726,7 +726,8 @@ mod tests { reserved.0.unwrap(), )) .unwrap(), - ); + ) + .unwrap(); let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); let mem_meta = dm; diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index efcc574ca0bc..badfeef05ab6 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -39,6 +39,8 @@ pub enum ShaleError { InvalidCacheView { offset: usize, size: u64 }, #[error("io error: {0}")] Io(#[from] std::io::Error), + #[error("Write on immutable cache")] + ImmutableWrite, } // TODO: @@ -95,9 +97,12 @@ pub trait CachedStore: Debug + Send + Sync { fn get_shared(&self) -> Box>; /// Write the `change` to the portion of the linear space starting at `offset`. The change /// should be immediately visible to all `CachedView` associated to this linear space. - fn write(&mut self, offset: usize, change: &[u8]); + fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError>; /// Returns the identifier of this storage space. fn id(&self) -> SpaceId; + + /// Returns whether or not this store is writable + fn is_writeable(&self) -> bool; } /// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] @@ -127,6 +132,9 @@ impl Obj { None => return Err(ObjWriteSizeError), }; + // catch writes that cannot be flushed early during debugging + debug_assert!(self.value.get_mem_store().is_writeable()); + Ok(()) } @@ -153,7 +161,7 @@ impl Obj { self.value.write_mem_image(&mut new_value).unwrap(); let offset = self.value.get_offset(); let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); - bx.write(offset, &new_value); + bx.write(offset, &new_value).expect("write should succeed"); } } } diff --git a/firewood/src/shale/plainmem.rs b/firewood/src/shale/plainmem.rs index c6bdf2e6d145..edea011436cb 100644 --- a/firewood/src/shale/plainmem.rs +++ b/firewood/src/shale/plainmem.rs @@ -7,7 +7,7 @@ use std::{ sync::{Arc, RwLock}, }; -use super::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use super::{CachedStore, CachedView, SendSyncDerefMut, ShaleError, SpaceId}; /// in-memory vector-based implementation for [CachedStore] for testing // built on [ShaleStore](super::ShaleStore) in memory, without having to write @@ -54,17 +54,22 @@ impl CachedStore for PlainMem { })) } - fn write(&mut self, offset: usize, change: &[u8]) { + fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError> { let length = change.len(); #[allow(clippy::unwrap_used)] let mut vect = self.space.deref().write().unwrap(); #[allow(clippy::indexing_slicing)] vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); + Ok(()) } fn id(&self) -> SpaceId { self.id } + + fn is_writeable(&self) -> bool { + true + } } #[derive(Debug)] diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 8621ecf8d000..75111f3babf2 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -711,14 +711,14 @@ mod tests { let change = b"this is a test"; // write to the in memory buffer not to disk - mut_store.write(0, change); + mut_store.write(0, change).unwrap(); assert_eq!(mut_store.id(), STATE_SPACE); // mutate the in memory buffer. let change = b"this is another test"; // write to the in memory buffer (ash) not yet to disk - mut_store.write(0, change); + mut_store.write(0, change).unwrap(); assert_eq!(mut_store.id(), STATE_SPACE); // wal should have no records. @@ -789,7 +789,7 @@ mod tests { let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); // write to the in memory buffer (ash) not yet to disk - mut_store.write(0, &hash); + mut_store.write(0, &hash).unwrap(); assert_eq!(mut_store.id(), STATE_SPACE); // wal should have no records. @@ -871,7 +871,7 @@ mod tests { // mutate the in memory buffer. let data = b"this is a test"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - block_in_place(|| store.write(0, &hash)); + block_in_place(|| store.write(0, &hash)).unwrap(); assert_eq!(store.id(), STATE_SPACE); let another_data = b"this is another test"; @@ -879,7 +879,7 @@ mod tests { // mutate the in memory buffer in another StoreRev new from the above. let mut another_store = StoreRevMut::new_from_other(&store); - block_in_place(|| another_store.write(32, &another_hash)); + block_in_place(|| another_store.write(32, &another_hash)).unwrap(); assert_eq!(another_store.id(), STATE_SPACE); // wal should have no records. @@ -902,7 +902,7 @@ mod tests { assert_eq!(view.as_deref(), empty); // Overwrite the value from the beginning in the new store. Only the new store should see the change. - another_store.write(0, &another_hash); + another_store.write(0, &another_hash).unwrap(); let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); assert_eq!(view.as_deref(), another_hash); let view = store.get_view(0, HASH_SIZE as u64).unwrap(); diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index 7ffbf9a1ea98..ba316d9adc35 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,7 +4,7 @@ // TODO: try to get rid of the use `RefCell` in this file use self::buffer::DiskBufferRequester; use crate::file::File; -use crate::shale::{self, CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use crate::shale::{self, CachedStore, CachedView, SendSyncDerefMut, ShaleError, SpaceId}; use nix::fcntl::{Flock, FlockArg}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -349,14 +349,19 @@ impl CachedStore for StoreRevShared { Box::new(StoreShared(self.clone())) } - fn write(&mut self, _offset: usize, _change: &[u8]) { + fn write(&mut self, _offset: usize, _change: &[u8]) -> Result<(), ShaleError> { // StoreRevShared is a read-only view version of CachedStore // Writes could be induced by lazy hashing and we can just ignore those + Err(ShaleError::ImmutableWrite) } fn id(&self) -> SpaceId { ::id(&self.0) } + + fn is_writeable(&self) -> bool { + false + } } impl From for StoreRevShared { @@ -431,6 +436,16 @@ pub struct StoreRevMut { prev_deltas: Arc>, } +impl From for StoreRevMut { + fn from(value: StoreRevShared) -> Self { + StoreRevMut { + base_space: value.0.base_space.read().clone(), + deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), + prev_deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), + } + } +} + impl StoreRevMut { pub fn new(base_space: Arc) -> Self { Self { @@ -561,7 +576,7 @@ impl CachedStore for StoreRevMut { Box::new(StoreShared(self.clone())) } - fn write(&mut self, offset: usize, mut change: &[u8]) { + fn write(&mut self, offset: usize, mut change: &[u8]) -> Result<(), ShaleError> { let length = change.len() as u64; let end = offset + length as usize - 1; let s_pid = offset >> PAGE_SIZE_NBIT; @@ -622,11 +637,17 @@ impl CachedStore for StoreRevMut { offset: offset as u64, data: redo, }); + + Ok(()) } fn id(&self) -> SpaceId { self.base_space.id() } + + fn is_writeable(&self) -> bool { + true + } } #[derive(Clone, Debug, Default)] From 5126830ed670ff20ae082d5c39bdad098c0c0c3d Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 7 Mar 2024 15:18:12 -0500 Subject: [PATCH 0499/1053] shale naming cleanups (#582) --- firewood/src/db.rs | 2 +- firewood/src/shale/compact.rs | 34 +++++++------- firewood/src/shale/mod.rs | 88 +++++++++++++++++------------------ 3 files changed, 61 insertions(+), 63 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ffc37a681b36..c1377c02d2d2 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -811,7 +811,7 @@ impl Db { // create the sentinel node #[allow(clippy::unwrap_used)] db_header_ref - .write(|r| { + .modify(|r| { err = (|| { r.kv_root = merkle.init_root()?; Ok(()) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 7317bbe225a5..104de584e177 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -279,17 +279,17 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] self.header .meta_space_tail - .write(|r| *r -= desc_size as usize) + .modify(|r| *r -= desc_size as usize) .unwrap(); if desc_addr != DiskAddress(**self.header.meta_space_tail) { let desc_last = self.get_descriptor(*self.header.meta_space_tail.value)?; let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] - desc.write(|r| *r = *desc_last).unwrap(); + desc.modify(|r| *r = *desc_last).unwrap(); let mut header = self.get_header(desc.haddr.into())?; #[allow(clippy::unwrap_used)] - header.write(|h| h.desc_addr = desc_addr).unwrap(); + header.modify(|h| h.desc_addr = desc_addr).unwrap(); } Ok(()) @@ -300,7 +300,7 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] self.header .meta_space_tail - .write(|r| *r += CompactDescriptor::MSIZE as usize) + .modify(|r| *r += CompactDescriptor::MSIZE as usize) .unwrap(); Ok(DiskAddress(addr)) @@ -365,7 +365,7 @@ impl CompactSpaceInner { { let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] - desc.write(|d| { + desc.modify(|d| { d.payload_size = payload_size; d.haddr = h as usize; }) @@ -374,14 +374,14 @@ impl CompactSpaceInner { let mut h = self.get_header(DiskAddress::from(h as usize))?; let mut f = self.get_footer(DiskAddress::from(f as usize))?; #[allow(clippy::unwrap_used)] - h.write(|h| { + h.modify(|h| { h.payload_size = payload_size; h.is_freed = true; h.desc_addr = desc_addr; }) .unwrap(); #[allow(clippy::unwrap_used)] - f.write(|f| f.payload_size = payload_size).unwrap(); + f.modify(|f| f.payload_size = payload_size).unwrap(); Ok(()) } @@ -417,7 +417,7 @@ impl CompactSpaceInner { assert_eq!(header.payload_size as usize, desc_payload_size); assert!(header.is_freed); #[allow(clippy::unwrap_used)] - header.write(|h| h.is_freed = false).unwrap(); + header.modify(|h| h.is_freed = false).unwrap(); } self.del_desc(ptr)?; true @@ -429,7 +429,7 @@ impl CompactSpaceInner { assert!(lheader.is_freed); #[allow(clippy::unwrap_used)] lheader - .write(|h| { + .modify(|h| { h.is_freed = false; h.payload_size = length; }) @@ -440,7 +440,7 @@ impl CompactSpaceInner { self.get_footer(DiskAddress::from(desc_haddr + hsize + length as usize))?; //assert!(lfooter.payload_size == desc_payload_size); #[allow(clippy::unwrap_used)] - lfooter.write(|f| f.payload_size = length).unwrap(); + lfooter.modify(|f| f.payload_size = length).unwrap(); } let offset = desc_haddr + hsize + length as usize + fsize; @@ -450,7 +450,7 @@ impl CompactSpaceInner { let mut rdesc = self.get_descriptor(rdesc_addr)?; #[allow(clippy::unwrap_used)] rdesc - .write(|rd| { + .modify(|rd| { rd.payload_size = rpayload_size as u64; rd.haddr = offset; }) @@ -460,7 +460,7 @@ impl CompactSpaceInner { let mut rheader = self.get_header(DiskAddress::from(offset))?; #[allow(clippy::unwrap_used)] rheader - .write(|rh| { + .modify(|rh| { rh.is_freed = true; rh.payload_size = rpayload_size as u64; rh.desc_addr = rdesc_addr; @@ -472,7 +472,7 @@ impl CompactSpaceInner { self.get_footer(DiskAddress::from(offset + hsize + rpayload_size))?; #[allow(clippy::unwrap_used)] rfooter - .write(|f| f.payload_size = rpayload_size as u64) + .modify(|f| f.payload_size = rpayload_size as u64) .unwrap(); } self.del_desc(ptr)?; @@ -482,7 +482,7 @@ impl CompactSpaceInner { }; #[allow(clippy::unwrap_used)] if exit { - self.header.alloc_addr.write(|r| *r = ptr).unwrap(); + self.header.alloc_addr.modify(|r| *r = ptr).unwrap(); res = Some((desc_haddr + hsize) as u64); break; } @@ -504,7 +504,7 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] self.header .compact_space_tail - .write(|r| { + .modify(|r| { // an item is always fully in one region let rem = regn_size - (offset & (regn_size - 1)).get(); if rem < total_length as usize { @@ -517,14 +517,14 @@ impl CompactSpaceInner { let mut h = self.get_header(offset)?; let mut f = self.get_footer(offset + CompactHeader::MSIZE as usize + length as usize)?; #[allow(clippy::unwrap_used)] - h.write(|h| { + h.modify(|h| { h.payload_size = length; h.is_freed = false; h.desc_addr = DiskAddress::null(); }) .unwrap(); #[allow(clippy::unwrap_used)] - f.write(|f| f.payload_size = length).unwrap(); + f.modify(|f| f.payload_size = length).unwrap(); #[allow(clippy::unwrap_used)] Ok((offset + CompactHeader::MSIZE as usize).0.unwrap().get() as u64) } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index badfeef05ab6..d74f6ffb8e72 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -54,8 +54,7 @@ pub type SpaceId = u8; pub const INVALID_SPACE_ID: SpaceId = 0xff; pub struct DiskWrite { - pub space_id: SpaceId, - pub space_off: u64, + pub offset: u64, pub data: Box<[u8]>, } @@ -63,9 +62,8 @@ impl std::fmt::Debug for DiskWrite { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( f, - "[Disk space=0x{:02x} offset=0x{:04x} data=0x{}", - self.space_id, - self.space_off, + "[Disk offset=0x{:04x} data=0x{}", + self.offset, hex::encode(&self.data) ) } @@ -105,13 +103,14 @@ pub trait CachedStore: Debug + Send + Sync { fn is_writeable(&self) -> bool; } -/// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view] +/// A wrapper of `StoredView` to enable writes. The direct construction (by [Obj::from_stored_view] /// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. /// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [DiskAddress] /// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. #[derive(Debug)] pub struct Obj { value: StoredView, + /// None if the object isn't dirty, otherwise the length of the serialized object. dirty: Option, } @@ -121,13 +120,13 @@ impl Obj { DiskAddress(NonZeroUsize::new(self.value.get_offset())) } - /// Write to the underlying object. Returns `Ok(())` on success. + /// Modifies the value of this object and marks it as dirty. #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { - modify(self.value.write()); + pub fn modify(&mut self, modify_func: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { + modify_func(self.value.mut_item_ref()); - // if `estimate_mem_image` gives overflow, the object will not be written - self.dirty = match self.value.estimate_mem_image() { + // if `serialized_len` gives overflow, the object will not be written + self.dirty = match self.value.serialized_len() { Some(len) => Some(len), None => return Err(ObjWriteSizeError), }; @@ -139,12 +138,7 @@ impl Obj { } #[inline(always)] - pub fn get_space_id(&self) -> SpaceId { - self.value.get_mem_store().id() - } - - #[inline(always)] - pub const fn from_typed_view(value: StoredView) -> Self { + pub const fn from_stored_view(value: StoredView) -> Self { Obj { value, dirty: None } } @@ -158,7 +152,7 @@ impl Obj { let mut new_value = vec![0; new_value_len as usize]; // TODO: log error #[allow(clippy::unwrap_used)] - self.value.write_mem_image(&mut new_value).unwrap(); + self.value.serialize(&mut new_value).unwrap(); let offset = self.value.get_offset(); let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); bx.write(offset, &new_value).expect("write should succeed"); @@ -173,7 +167,7 @@ impl Obj { data: Vec::new().into(), }; - std::mem::replace(&mut self.value.decoded, Node::from_leaf(empty_node)) + std::mem::replace(&mut self.value.item, Node::from_leaf(empty_node)) } } @@ -207,7 +201,7 @@ impl<'a, T: Storable + Debug> ObjRef<'a, T> { #[inline] pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { - self.inner.write(modify)?; + self.inner.modify(modify)?; self.cache.lock().dirty.insert(self.inner.as_ptr()); @@ -292,29 +286,32 @@ pub trait Storable { } pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { - let mut buff = vec![0; item.serialized_len() as usize]; - item.serialize(&mut buff)?; - Ok(buff) + let mut buf = vec![0; item.serialized_len() as usize]; + item.serialize(&mut buf)?; + Ok(buf) } /// A stored view of any [Storable] pub struct StoredView { - decoded: T, + /// The item this stores. + item: T, mem: Box>, offset: usize, + /// If the serialized length of `item` is greater than this, + /// `serialized_len` will return `None`. len_limit: u64, } impl Debug for StoredView { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let StoredView { - decoded, + item, offset, len_limit, mem: _, } = self; f.debug_struct("StoredView") - .field("decoded", decoded) + .field("item", item) .field("offset", offset) .field("len_limit", len_limit) .finish() @@ -324,7 +321,7 @@ impl Debug for StoredView { impl Deref for StoredView { type Target = T; fn deref(&self) -> &T { - &self.decoded + &self.item } } @@ -341,8 +338,9 @@ impl StoredView { &mut **self.mem } - fn estimate_mem_image(&self) -> Option { - let len = self.decoded.serialized_len(); + /// Returns the serialized length of the item if it's less than the limit, otherwise `None`. + fn serialized_len(&self) -> Option { + let len = self.item.serialized_len(); if len > self.len_limit { None } else { @@ -350,23 +348,23 @@ impl StoredView { } } - fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { - self.decoded.serialize(mem_image) + fn serialize(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { + self.item.serialize(mem_image) } - fn write(&mut self) -> &mut T { - &mut self.decoded + fn mut_item_ref(&mut self) -> &mut T { + &mut self.item } } impl StoredView { #[inline(always)] fn new(offset: usize, len_limit: u64, space: &U) -> Result { - let decoded = T::deserialize(offset, space)?; + let item = T::deserialize(offset, space)?; Ok(Self { offset, - decoded, + item, mem: space.get_shared(), len_limit, }) @@ -376,12 +374,12 @@ impl StoredView { fn from_hydrated( offset: usize, len_limit: u64, - decoded: T, + item: T, space: &dyn CachedStore, ) -> Result { Ok(Self { offset, - decoded, + item, mem: space.get_shared(), len_limit, }) @@ -393,7 +391,7 @@ impl StoredView { ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Self::new( + Ok(Obj::from_stored_view(Self::new( ptr.get(), len_limit, store, @@ -405,10 +403,10 @@ impl StoredView { store: &dyn CachedStore, addr: usize, len_limit: u64, - decoded: T, + item: T, ) -> Result, ShaleError> { - Ok(Obj::from_typed_view(Self::from_hydrated( - addr, len_limit, decoded, store, + Ok(Obj::from_stored_view(Self::from_hydrated( + addr, len_limit, item, store, )?)) } } @@ -417,12 +415,12 @@ impl StoredView { fn new_from_slice( offset: usize, len_limit: u64, - decoded: T, + item: T, space: &dyn CachedStore, ) -> Result { Ok(Self { offset, - decoded, + item, mem: space.get_shared(), len_limit, }) @@ -432,7 +430,7 @@ impl StoredView { s: &Obj, offset: usize, length: u64, - decoded: U, + item: U, ) -> Result, ShaleError> { let addr_ = s.value.get_offset() + offset; if s.dirty.is_some() { @@ -442,7 +440,7 @@ impl StoredView { error: "dirty write", }); } - let r = StoredView::new_from_slice(addr_, length, decoded, s.value.get_mem_store())?; + let r = StoredView::new_from_slice(addr_, length, item, s.value.get_mem_store())?; Ok(Obj { value: r, dirty: None, From d8c7f92997c6434499396010554f8167adeb4dca Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 7 Mar 2024 17:01:12 -0500 Subject: [PATCH 0500/1053] clean up new_internal (#580) --- firewood/src/db.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c1377c02d2d2..65a61e4f5094 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -506,44 +506,45 @@ impl Db { file::Options::NoTruncate }; - let (db_path, reset) = file::open_dir(db_path, open_options)?; + let (db_path, reset_store_headers) = file::open_dir(db_path, open_options)?; let merkle_path = file::touch_dir("merkle", &db_path)?; let merkle_meta_path = file::touch_dir("meta", &merkle_path)?; let merkle_payload_path = file::touch_dir("compact", &merkle_path)?; - let root_hash_path = file::touch_dir("root_hash", &db_path)?; - let file0 = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; - let fd0 = file0.as_fd(); + let meta_file = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; + let meta_fd = meta_file.as_fd(); - if reset { - // initialize dbparams + if reset_store_headers { + // initialize DbParams if cfg.payload_file_nbit < cfg.payload_regn_nbit || cfg.payload_regn_nbit < PAGE_SIZE_NBIT { return Err(DbError::InvalidParams); } - Self::initialize_header_on_disk(&cfg, fd0)?; + Self::initialize_header_on_disk(&cfg, meta_fd)?; } // read DbParams let mut header_bytes = [0; size_of::()]; - nix::sys::uio::pread(fd0, &mut header_bytes, 0).map_err(DbError::System)?; - drop(file0); + nix::sys::uio::pread(meta_fd, &mut header_bytes, 0).map_err(DbError::System)?; + drop(meta_file); #[allow(clippy::indexing_slicing)] let params: DbParams = cast_slice(&header_bytes)[0]; - let wal = WalConfig::builder() + let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); + let disk_requester = DiskBufferRequester::new(sender); + + let wal_config = WalConfig::builder() .file_nbit(params.wal_file_nbit) .block_nbit(params.wal_block_nbit) .max_revisions(cfg.wal.max_revisions) .build(); - let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); - let disk_requester = DiskBufferRequester::new(sender); - let buffer = cfg.buffer.clone(); + #[allow(clippy::unwrap_used)] - let disk_buffer = DiskBuffer::new(inbound, &buffer, &wal).expect("DiskBuffer::new"); + let disk_buffer = + DiskBuffer::new(inbound, &cfg.buffer, &wal_config).expect("DiskBuffer::new"); let disk_thread = Some( std::thread::Builder::new() @@ -552,6 +553,7 @@ impl Db { .expect("thread spawn should succeed"), ); + // set up caches #[allow(clippy::unwrap_used)] let root_hash_cache: Arc = CachedSpace::new( &StoreConfig::builder() @@ -566,7 +568,6 @@ impl Db { .unwrap() .into(); - // setup disk buffer #[allow(clippy::unwrap_used)] let data_cache = Universe { merkle: SubUniverse::>::new( @@ -610,9 +611,6 @@ impl Db { // recover from Wal disk_requester.init_wal("wal", &db_path); - let root_hash_staging = StoreRevMut::new(root_hash_cache); - let reset_headers = reset; - let base = Universe { merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), }; @@ -641,8 +639,8 @@ impl Db { disk_thread, disk_requester, cached_space: data_cache, - reset_store_headers: reset_headers, - root_hash_staging, + reset_store_headers, + root_hash_staging: StoreRevMut::new(root_hash_cache), })), revisions: Arc::new(Mutex::new(DbRevInner { inner: VecDeque::new(), From 243d00b6f30f8a4d12a5bb9b530865ae463c5a23 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 8 Mar 2024 09:52:47 -0500 Subject: [PATCH 0501/1053] nit: move clippy pragma closer to usage (#578) Signed-off-by: Dan Laine Co-authored-by: Ron Kuris --- firewood/src/db.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 65a61e4f5094..09b432ab06ae 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -661,7 +661,6 @@ impl Db { // DbHeader (just a pointer to the sentinel) // CompactSpaceHeader for future allocations let (params, hdr, csh); - #[allow(clippy::unwrap_used)] let header_bytes: Vec = { params = DbParams { magic: *MAGIC_STR, @@ -682,11 +681,9 @@ impl Db { }) .chain({ // write out the CompactSpaceHeader - csh = CompactSpaceHeader::new( - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - #[allow(clippy::unwrap_used)] - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), - ); + let space_reserved = + NonZeroUsize::new(SPACE_RESERVED as usize).expect("SPACE_RESERVED is non-zero"); + csh = CompactSpaceHeader::new(space_reserved, space_reserved); bytemuck::bytes_of(&csh) }) .copied() From 7fb14f940291d43b03d72bd3c53b19d5ef3c5a96 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 8 Mar 2024 10:35:08 -0500 Subject: [PATCH 0502/1053] dont panic on error during disk buffer creation (#579) --- firewood/src/db.rs | 6 ++++-- firewood/src/merkle/proof.rs | 4 ++++ firewood/src/v2/db.rs | 1 + libaio/Cargo.toml | 1 + libaio/src/lib.rs | 7 ++++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 09b432ab06ae..b385569c6dae 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -28,6 +28,7 @@ use crate::{ CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, }, }; +use aiofut::AioError; use async_trait::async_trait; use bytemuck::{cast_slice, Pod, Zeroable}; @@ -65,6 +66,7 @@ pub type SharedStore = CompactSpace; #[derive(Debug)] #[non_exhaustive] pub enum DbError { + Aio(AioError), InvalidParams, Merkle(MerkleError), System(nix::Error), @@ -78,6 +80,7 @@ pub enum DbError { impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + DbError::Aio(e) => write!(f, "aio error: {e:?}"), DbError::InvalidParams => write!(f, "invalid parameters provided"), DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), DbError::System(e) => write!(f, "system error: {e:?}"), @@ -542,9 +545,8 @@ impl Db { .max_revisions(cfg.wal.max_revisions) .build(); - #[allow(clippy::unwrap_used)] let disk_buffer = - DiskBuffer::new(inbound, &cfg.buffer, &wal_config).expect("DiskBuffer::new"); + DiskBuffer::new(inbound, &cfg.buffer, &wal_config).map_err(DbError::Aio)?; let disk_thread = Some( std::thread::Builder::new() diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index a9c4d9052771..7b4dba66dbae 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::shale::ObjWriteSizeError; use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; use crate::v2::api::HashKey; +use aiofut::AioError; use nix::errno::Errno; use sha3::Digest; use thiserror::Error; @@ -23,6 +24,8 @@ use super::{BinarySerde, EncodedNode, NodeObjRef}; #[derive(Debug, Error)] pub enum ProofError { + #[error("aio error: {0:?}")] + AioError(AioError), #[error("decoding error")] DecodeError(#[from] bincode::Error), #[error("no such node")] @@ -74,6 +77,7 @@ impl From for ProofError { impl From for ProofError { fn from(d: DbError) -> ProofError { match d { + DbError::Aio(e) => ProofError::AioError(e), DbError::InvalidParams => ProofError::InvalidProof, DbError::Merkle(e) => ProofError::InvalidNode(e), DbError::System(e) => ProofError::SystemError(e), diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index feeefb003895..252ff157f122 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -17,6 +17,7 @@ use crate::{db::DbError, v2::api}; impl From for api::Error { fn from(value: DbError) -> Self { match value { + DbError::Aio(e) => api::Error::InternalError(Box::new(e)), DbError::InvalidParams => api::Error::InternalError(Box::new(value)), DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), DbError::System(e) => api::Error::IO(e.into()), diff --git a/libaio/Cargo.toml b/libaio/Cargo.toml index 181c4cd89218..bde24dcdd0a8 100644 --- a/libaio/Cargo.toml +++ b/libaio/Cargo.toml @@ -13,6 +13,7 @@ emulated-failure = [] libc = "0.2.153" parking_lot = "0.12.1" crossbeam-channel = "0.5.11" +thiserror = "1.0.57" [dev-dependencies] futures = "0.3.30" diff --git a/libaio/src/lib.rs b/libaio/src/lib.rs index 24776c5317fe..59bf35e89808 100644 --- a/libaio/src/lib.rs +++ b/libaio/src/lib.rs @@ -49,16 +49,21 @@ use std::sync::{ atomic::{AtomicPtr, AtomicUsize, Ordering}, Arc, }; +use thiserror::Error; const LIBAIO_EAGAIN: libc::c_int = -libc::EAGAIN; const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum AioError { + #[error("maxevents is too large")] MaxEventsTooLarge, + #[error("low kernel resources")] LowKernelRes, + #[error("not supported")] NotSupported, + #[error("other aio error")] OtherError, } From 37cea2e947896ff6c676127b3d0f1e7160d05ac5 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 11 Mar 2024 10:06:39 -0400 Subject: [PATCH 0503/1053] remove plainmem (#583) --- firewood/benches/hashops.rs | 12 ++-- firewood/benches/shale-bench.rs | 10 +-- firewood/src/merkle.rs | 10 +-- firewood/src/merkle/node.rs | 4 +- firewood/src/merkle/stream.rs | 8 ++- firewood/src/merkle_util.rs | 10 +-- firewood/src/shale/cached.rs | 66 ++++++-------------- firewood/src/shale/compact.rs | 6 +- firewood/src/shale/mod.rs | 2 - firewood/src/shale/plainmem.rs | 104 -------------------------------- 10 files changed, 46 insertions(+), 186 deletions(-) delete mode 100644 firewood/src/shale/plainmem.rs diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 50b558359160..8d6f8ef6d448 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -8,7 +8,7 @@ use firewood::{ db::{BatchOp, DbConfig}, merkle::{Bincode, Merkle, Node, TrieHash, TRIE_HASH_LEN}, shale::{ - cached::DynamicMem, + cached::InMemLinearStore, compact::{CompactHeader, CompactSpace}, disk_address::DiskAddress, CachedStore, ObjCache, Storable, StoredView, @@ -20,7 +20,7 @@ use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path, sync::Arc}; -pub type MerkleWithEncoder = Merkle, Bincode>; +pub type MerkleWithEncoder = Merkle, Bincode>; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); @@ -67,7 +67,7 @@ impl Profiler for FlamegraphProfiler { fn bench_trie_hash(criterion: &mut Criterion) { let mut to = [1u8; TRIE_HASH_LEN]; - let mut store = DynamicMem::new(TRIE_HASH_LEN as u64, 0u8); + let mut store = InMemLinearStore::new(TRIE_HASH_LEN as u64, 0u8); store.write(0, &*ZERO_HASH).expect("write should succeed"); #[allow(clippy::unwrap_used)] @@ -96,7 +96,7 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = StoredView::ptr_to_obj( - &DynamicMem::new(2 * CompactHeader::MSIZE, 9), + &InMemLinearStore::new(2 * CompactHeader::MSIZE, 9), merkle_payload_header, CompactHeader::MSIZE, ) @@ -104,8 +104,8 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let store = CompactSpace::new( - DynamicMem::new(TEST_MEM_SIZE, 0), - DynamicMem::new(TEST_MEM_SIZE, 1), + InMemLinearStore::new(TEST_MEM_SIZE, 0), + InMemLinearStore::new(TEST_MEM_SIZE, 1), merkle_payload_header_ref, ObjCache::new(1 << 20), 4096, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 73870e45b660..186136070b01 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -5,7 +5,7 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; use firewood::shale::{ - cached::DynamicMem, + cached::InMemLinearStore, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, CachedStore, Obj, StoredView, @@ -87,12 +87,8 @@ fn serialize(m: &T) { fn bench_cursors(c: &mut Criterion) { let mut group = c.benchmark_group("shale-bench"); - group.bench_function("PlainMem", |b| { - let mem = DynamicMem::new(BENCH_MEM_SIZE as u64, 0); - get_view(b, mem) - }); - group.bench_function("DynamicMem", |b| { - let mem = DynamicMem::new(BENCH_MEM_SIZE as u64, 0); + group.bench_function("InMemLinearStore", |b| { + let mem = InMemLinearStore::new(BENCH_MEM_SIZE as u64, 0); get_view(b, mem) }); } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 77d252e8bb0b..fd5de16f5655 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1393,7 +1393,7 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { mod tests { use super::*; use crate::merkle::node::PlainCodec; - use shale::{cached::DynamicMem, compact::CompactSpace, CachedStore}; + use shale::{cached::InMemLinearStore, compact::CompactSpace, CachedStore}; use test_case::test_case; fn leaf(path: Vec, data: Vec) -> Node { @@ -1407,14 +1407,14 @@ mod tests { assert_eq!(n, nibbles); } - fn create_generic_test_merkle<'de, T>() -> Merkle, T> + fn create_generic_test_merkle<'de, T>() -> Merkle, T> where T: BinarySerde, EncodedNode: serde::Serialize + serde::Deserialize<'de>, { const RESERVED: usize = 0x1000; - let mut dm = shale::cached::DynamicMem::new(0x10000, 0); + let mut dm = shale::cached::InMemLinearStore::new(0x10000, 0); let compact_header = DiskAddress::null(); dm.write( compact_header.into(), @@ -1432,7 +1432,7 @@ mod tests { ) .unwrap(); let mem_meta = dm; - let mem_payload = DynamicMem::new(0x10000, 0x1); + let mem_payload = InMemLinearStore::new(0x10000, 0x1); let cache = shale::ObjCache::new(1); let space = @@ -1443,7 +1443,7 @@ mod tests { Merkle::new(store) } - pub(super) fn create_test_merkle() -> Merkle, Bincode> { + pub(super) fn create_test_merkle() -> Merkle, Bincode> { create_generic_test_merkle::() } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 7cf6b89ad210..a9108c40eed7 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -809,7 +809,7 @@ impl BinarySerde for PlainCodec { #[cfg(test)] mod tests { use super::*; - use crate::shale::cached::PlainMem; + use crate::shale::cached::InMemLinearStore; use std::iter::repeat; use test_case::{test_case, test_matrix}; @@ -941,7 +941,7 @@ mod tests { let mut bytes = vec![0; serialized_len as usize]; node.serialize(&mut bytes).expect("node should serialize"); - let mut mem = PlainMem::new(serialized_len, 0); + let mut mem = InMemLinearStore::new(serialized_len, 0); mem.write(0, &bytes).expect("write should succed"); let mut hydrated_node = Node::deserialize(0, &mem).expect("node should deserialize"); diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 0bbb9795b4c4..b6b76ed30396 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -584,7 +584,7 @@ use super::tests::create_test_merkle; mod tests { use crate::{ merkle::Bincode, - shale::{cached::DynamicMem, compact::CompactSpace}, + shale::{cached::InMemLinearStore, compact::CompactSpace}, }; use super::*; @@ -772,8 +772,10 @@ mod tests { /// /// Note the 0000 branch has no value and the F0F0 /// The number next to each branch is the position of the child in the branch's children array. - fn created_populated_merkle() -> (Merkle, Bincode>, DiskAddress) - { + fn created_populated_merkle() -> ( + Merkle, Bincode>, + DiskAddress, + ) { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 59086632a832..cb0bb6236c15 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -7,8 +7,8 @@ use crate::{ BinarySerde, EncodedNode, Merkle, Node, Ref, RefMut, TrieHash, }, shale::{ - self, cached::DynamicMem, compact::CompactSpace, disk_address::DiskAddress, CachedStore, - StoredView, + self, cached::InMemLinearStore, compact::CompactSpace, disk_address::DiskAddress, + CachedStore, StoredView, }, }; use std::num::NonZeroUsize; @@ -36,7 +36,7 @@ pub enum DataStoreError { ProofEmptyKeyValuesError, } -type InMemoryStore = CompactSpace; +type InMemoryStore = CompactSpace; pub struct InMemoryMerkle { root: DiskAddress, @@ -52,7 +52,7 @@ where const RESERVED: usize = 0x1000; assert!(meta_size as usize > RESERVED); assert!(compact_size as usize > RESERVED); - let mut dm = DynamicMem::new(meta_size, 0); + let mut dm = InMemLinearStore::new(meta_size, 0); let compact_header = DiskAddress::null(); #[allow(clippy::unwrap_used)] dm.write( @@ -70,7 +70,7 @@ where StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE) .unwrap(); let mem_meta = dm; - let mem_payload = DynamicMem::new(compact_size, 0x1); + let mem_payload = InMemLinearStore::new(compact_size, 0x1); let cache = shale::ObjCache::new(1); let space = diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 28f282d2c53a..65064104b5eb 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -8,29 +8,24 @@ use std::{ sync::{Arc, RwLock}, }; -#[cfg(test)] -pub use crate::shale::plainmem::PlainMem; - use super::ShaleError; // Purely volatile, dynamically allocated vector-based implementation for -// [CachedStore]. This is similar to PlainMem (in testing). The only -// difference is, when [write] dynamically allocate more space if original -// space is not enough. +// [CachedStore]. Allocates more space on `write` if original size isn't enough. #[derive(Debug)] -pub struct DynamicMem { +pub struct InMemLinearStore { space: Arc>>, id: SpaceId, } -impl DynamicMem { +impl InMemLinearStore { pub fn new(size: u64, id: SpaceId) -> Self { let space = Arc::new(RwLock::new(vec![0; size as usize])); Self { space, id } } } -impl CachedStore for DynamicMem { +impl CachedStore for InMemLinearStore { fn get_view( &self, offset: usize, @@ -46,7 +41,7 @@ impl CachedStore for DynamicMem { space.resize(size, 0); } - Some(Box::new(DynamicMemView { + Some(Box::new(InMemLinearStoreView { offset, length, mem: Self { @@ -57,7 +52,7 @@ impl CachedStore for DynamicMem { } fn get_shared(&self) -> Box> { - Box::new(DynamicMemShared(Self { + Box::new(InMemLinearStoreShared(Self { space: self.space.clone(), id: self.id, })) @@ -89,29 +84,33 @@ impl CachedStore for DynamicMem { } } +/// A range within an in-memory linear byte store. #[derive(Debug)] -struct DynamicMemView { +struct InMemLinearStoreView { + /// The start of the range. offset: usize, + /// The length of the range. length: usize, - mem: DynamicMem, + /// The underlying store. + mem: InMemLinearStore, } -struct DynamicMemShared(DynamicMem); +struct InMemLinearStoreShared(InMemLinearStore); -impl Deref for DynamicMemShared { +impl Deref for InMemLinearStoreShared { type Target = dyn CachedStore; fn deref(&self) -> &(dyn CachedStore + 'static) { &self.0 } } -impl DerefMut for DynamicMemShared { +impl DerefMut for InMemLinearStoreShared { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl CachedView for DynamicMemView { +impl CachedView for InMemLinearStoreView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { @@ -124,41 +123,10 @@ impl CachedView for DynamicMemView { #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; - use crate::shale::plainmem::PlainMemShared; - - #[test] - fn test_plain_mem() { - let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = &mut *view; - mem.write(0, &[1, 1]).unwrap(); - mem.write(0, &[1, 2]).unwrap(); - #[allow(clippy::unwrap_used)] - let r = mem.get_view(0, 2).unwrap().as_deref(); - assert_eq!(r, [1, 2]); - - // previous view not mutated by write - mem.write(0, &[1, 3]).unwrap(); - assert_eq!(r, [1, 2]); - let r = mem.get_view(0, 2).unwrap().as_deref(); - assert_eq!(r, [1, 3]); - - // create a view larger than capacity - assert!(mem.get_view(0, 4).is_none()) - } - - #[test] - #[should_panic(expected = "index 3 out of range for slice of length 2")] - fn test_plain_mem_panic() { - let mut view = PlainMemShared(PlainMem::new(2, 0)); - let mem = &mut *view; - - // out of range - mem.write(1, &[7, 8]).unwrap(); - } #[test] fn test_dynamic_mem() { - let mut view = DynamicMemShared(DynamicMem::new(2, 0)); + let mut view = InMemLinearStoreShared(InMemLinearStore::new(2, 0)); let mem = &mut *view; mem.write(0, &[1, 2]).unwrap(); mem.write(0, &[3, 4]).unwrap(); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 104de584e177..9cc82ce6a493 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -662,7 +662,7 @@ impl Sh mod tests { use sha3::Digest; - use crate::shale::{self, cached::DynamicMem, ObjCache}; + use crate::shale::{self, cached::InMemLinearStore, ObjCache}; use super::*; @@ -715,7 +715,7 @@ mod tests { let compact_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); let reserved: DiskAddress = 0x1000.into(); - let mut dm = DynamicMem::new(meta_size.get() as u64, 0x0); + let mut dm = InMemLinearStore::new(meta_size.get() as u64, 0x0); // initialize compact space let compact_header = DiskAddress::from(0x1); @@ -731,7 +731,7 @@ mod tests { let compact_header = StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); let mem_meta = dm; - let mem_payload = DynamicMem::new(compact_size.get() as u64, 0x1); + let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); let cache: ObjCache = ObjCache::new(1); let space = diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index d74f6ffb8e72..b5971ddf7bdf 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -17,8 +17,6 @@ use crate::merkle::{LeafNode, Node, PartialPath}; pub mod cached; pub mod compact; pub mod disk_address; -#[cfg(test)] -pub mod plainmem; #[derive(Debug, Error)] #[non_exhaustive] diff --git a/firewood/src/shale/plainmem.rs b/firewood/src/shale/plainmem.rs deleted file mode 100644 index edea011436cb..000000000000 --- a/firewood/src/shale/plainmem.rs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::{ - borrow::BorrowMut, - ops::{Deref, DerefMut}, - sync::{Arc, RwLock}, -}; - -use super::{CachedStore, CachedView, SendSyncDerefMut, ShaleError, SpaceId}; - -/// in-memory vector-based implementation for [CachedStore] for testing -// built on [ShaleStore](super::ShaleStore) in memory, without having to write -/// your own [CachedStore] implementation. -#[derive(Debug)] -pub struct PlainMem { - space: Arc>>, - id: SpaceId, -} - -impl PlainMem { - pub fn new(size: u64, id: SpaceId) -> Self { - // TODO: this could cause problems on a 32-bit system - let space = Arc::new(RwLock::new(vec![0; size as usize])); - Self { space, id } - } -} -impl CachedStore for PlainMem { - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>> { - let length = length as usize; - #[allow(clippy::unwrap_used)] - if offset + length > self.space.read().unwrap().len() { - None - } else { - Some(Box::new(PlainMemView { - offset, - length, - mem: Self { - space: self.space.clone(), - id: self.id, - }, - })) - } - } - - fn get_shared(&self) -> Box> { - Box::new(PlainMemShared(Self { - space: self.space.clone(), - id: self.id, - })) - } - - fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError> { - let length = change.len(); - #[allow(clippy::unwrap_used)] - let mut vect = self.space.deref().write().unwrap(); - #[allow(clippy::indexing_slicing)] - vect.as_mut_slice()[offset..offset + length].copy_from_slice(change); - Ok(()) - } - - fn id(&self) -> SpaceId { - self.id - } - - fn is_writeable(&self) -> bool { - true - } -} - -#[derive(Debug)] -struct PlainMemView { - offset: usize, - length: usize, - mem: PlainMem, -} - -pub struct PlainMemShared(pub PlainMem); - -impl DerefMut for PlainMemShared { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.borrow_mut() - } -} - -impl Deref for PlainMemShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { - &self.0 - } -} - -impl CachedView for PlainMemView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() - } -} From 8f6b8b7ef38d05a0d3ff278bb940afc981398f33 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 11 Mar 2024 16:36:10 -0400 Subject: [PATCH 0504/1053] rename compact_space to data_space (#585) --- firewood/src/shale/compact.rs | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 9cc82ce6a493..4b514110c23f 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -148,7 +148,7 @@ impl Storable for CompactDescriptor { #[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct CompactSpaceHeader { meta_space_tail: DiskAddress, - compact_space_tail: DiskAddress, + data_space_tail: DiskAddress, base_addr: DiskAddress, alloc_addr: DiskAddress, } @@ -156,7 +156,7 @@ pub struct CompactSpaceHeader { #[derive(Debug)] struct CompactSpaceHeaderSliced { meta_space_tail: Obj, - compact_space_tail: Obj, + data_space_tail: Obj, base_addr: Obj, alloc_addr: Obj, } @@ -164,7 +164,7 @@ struct CompactSpaceHeaderSliced { impl CompactSpaceHeaderSliced { fn flush_dirty(&mut self) { self.meta_space_tail.flush_dirty(); - self.compact_space_tail.flush_dirty(); + self.data_space_tail.flush_dirty(); self.base_addr.flush_dirty(); self.alloc_addr.flush_dirty(); } @@ -176,7 +176,7 @@ impl CompactSpaceHeader { pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { meta_space_tail: DiskAddress::new(meta_base), - compact_space_tail: DiskAddress::new(compact_base), + data_space_tail: DiskAddress::new(compact_base), base_addr: DiskAddress::new(meta_base), alloc_addr: DiskAddress::new(meta_base), } @@ -185,7 +185,7 @@ impl CompactSpaceHeader { fn into_fields(r: Obj) -> Result { Ok(CompactSpaceHeaderSliced { meta_space_tail: StoredView::slice(&r, 0, 8, r.meta_space_tail)?, - compact_space_tail: StoredView::slice(&r, 8, 8, r.compact_space_tail)?, + data_space_tail: StoredView::slice(&r, 8, 8, r.data_space_tail)?, base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, }) @@ -203,14 +203,14 @@ impl Storable for CompactSpaceHeader { #[allow(clippy::indexing_slicing)] let meta_space_tail = raw.as_deref()[..8].into(); #[allow(clippy::indexing_slicing)] - let compact_space_tail = raw.as_deref()[8..16].into(); + let data_space_tail = raw.as_deref()[8..16].into(); #[allow(clippy::indexing_slicing)] let base_addr = raw.as_deref()[16..24].into(); #[allow(clippy::indexing_slicing)] let alloc_addr = raw.as_deref()[24..].into(); Ok(Self { meta_space_tail, - compact_space_tail, + data_space_tail, base_addr, alloc_addr, }) @@ -223,7 +223,7 @@ impl Storable for CompactSpaceHeader { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); cur.write_all(&self.meta_space_tail.to_le_bytes())?; - cur.write_all(&self.compact_space_tail.to_le_bytes())?; + cur.write_all(&self.data_space_tail.to_le_bytes())?; cur.write_all(&self.base_addr.to_le_bytes())?; cur.write_all(&self.alloc_addr.to_le_bytes())?; Ok(()) @@ -233,7 +233,7 @@ impl Storable for CompactSpaceHeader { #[derive(Debug)] struct CompactSpaceInner { meta_space: M, - compact_space: M, + data_space: M, header: CompactSpaceHeaderSliced, alloc_max_walk: u64, regn_nbit: u64, @@ -243,7 +243,7 @@ impl From> for CompactSpaceInner fn from(value: CompactSpaceInner) -> CompactSpaceInner { CompactSpaceInner { meta_space: value.meta_space.into(), - compact_space: value.compact_space.into(), + data_space: value.data_space.into(), header: value.header, alloc_max_walk: value.alloc_max_walk, regn_nbit: value.regn_nbit, @@ -261,7 +261,7 @@ impl CompactSpaceInner { ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.compact_space, ptr, len_limit) + StoredView::ptr_to_obj(&self.data_space, ptr, len_limit) } fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { @@ -340,7 +340,7 @@ impl CompactSpaceInner { let mut f = offset; #[allow(clippy::unwrap_used)] - if offset + fsize < self.header.compact_space_tail.unwrap().get() as u64 + if offset + fsize < self.header.data_space_tail.unwrap().get() as u64 && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize { // merge with higher data segment @@ -500,10 +500,10 @@ impl CompactSpaceInner { fn alloc_new(&mut self, length: u64) -> Result { let regn_size = 1 << self.regn_nbit; let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; - let mut offset = *self.header.compact_space_tail; + let mut offset = *self.header.data_space_tail; #[allow(clippy::unwrap_used)] self.header - .compact_space_tail + .data_space_tail .modify(|r| { // an item is always fully in one region let rem = regn_size - (offset & (regn_size - 1)).get(); @@ -549,7 +549,7 @@ pub struct CompactSpace { impl CompactSpace { pub fn new( meta_space: M, - compact_space: M, + data_space: M, header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, @@ -558,7 +558,7 @@ impl CompactSpace { let cs = CompactSpace { inner: RwLock::new(CompactSpaceInner { meta_space, - compact_space, + data_space, header: CompactSpaceHeader::into_fields(header)?, alloc_max_walk, regn_nbit, @@ -593,10 +593,9 @@ impl Sh #[allow(clippy::unwrap_used)] let obj = { let inner = self.inner.read().unwrap(); - let compact_space = &inner.compact_space; + let data_space = &inner.data_space; #[allow(clippy::unwrap_used)] - let view = - StoredView::item_to_obj(compact_space, addr.try_into().unwrap(), size, item)?; + let view = StoredView::item_to_obj(data_space, addr.try_into().unwrap(), size, item)?; self.obj_cache.put(view) }; From ff3be92f22b74ac4c5c5d18a88eee1519e12db84 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 11 Mar 2024 16:44:29 -0400 Subject: [PATCH 0505/1053] remove unused type DiskWrite (#584) --- firewood/src/shale/mod.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index b5971ddf7bdf..ea7f4339b728 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -51,22 +51,6 @@ pub struct ObjWriteSizeError; pub type SpaceId = u8; pub const INVALID_SPACE_ID: SpaceId = 0xff; -pub struct DiskWrite { - pub offset: u64, - pub data: Box<[u8]>, -} - -impl std::fmt::Debug for DiskWrite { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "[Disk offset=0x{:04x} data=0x{}", - self.offset, - hex::encode(&self.data) - ) - } -} - /// A handle that pins and provides a readable access to a portion of the linear memory image. pub trait CachedView { type DerefReturn: Deref; From c9fa316ab7470fd6b7496bb4d36979cce5d9250e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 12 Mar 2024 09:07:21 -0400 Subject: [PATCH 0506/1053] remove `ShaleStore` trait (#586) --- firewood/benches/hashops.rs | 6 ++-- firewood/src/db.rs | 52 ++++++++++++------------------ firewood/src/db/proposal.rs | 17 +++++----- firewood/src/lib.rs | 15 ++++----- firewood/src/merkle.rs | 49 ++++++++++++++-------------- firewood/src/merkle/node.rs | 17 ++++++---- firewood/src/merkle/node/branch.rs | 10 +++--- firewood/src/merkle/proof.rs | 8 ++--- firewood/src/merkle/stream.rs | 30 +++++++---------- firewood/src/merkle_util.rs | 17 ++++------ firewood/src/shale/compact.rs | 18 +++++------ firewood/src/shale/mod.rs | 20 ++---------- 12 files changed, 110 insertions(+), 149 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 8d6f8ef6d448..cdebb7c9a1c6 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -6,7 +6,7 @@ use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; use firewood::{ db::{BatchOp, DbConfig}, - merkle::{Bincode, Merkle, Node, TrieHash, TRIE_HASH_LEN}, + merkle::{Bincode, Merkle, TrieHash, TRIE_HASH_LEN}, shale::{ cached::InMemLinearStore, compact::{CompactHeader, CompactSpace}, @@ -20,7 +20,7 @@ use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path, sync::Arc}; -pub type MerkleWithEncoder = Merkle, Bincode>; +pub type MerkleWithEncoder = Merkle; const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); @@ -113,7 +113,7 @@ fn bench_merkle(criterion: &mut Criterion) { ) .unwrap(); - let merkle = MerkleWithEncoder::new(Box::new(store)); + let merkle = MerkleWithEncoder::new(store); #[allow(clippy::unwrap_used)] let root = merkle.init_root().unwrap(); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b385569c6dae..b8363d1d4174 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -9,7 +9,7 @@ pub use crate::{ use crate::{ file, merkle::{ - Bincode, Key, Merkle, MerkleError, MerkleKeyValueStream, Node, Proof, ProofError, TrieHash, + Bincode, Key, Merkle, MerkleError, MerkleKeyValueStream, Proof, ProofError, TrieHash, TRIE_HASH_LEN, }, storage::{ @@ -22,10 +22,8 @@ use crate::{ use crate::{ merkle, shale::{ - self, - compact::{CompactSpace, CompactSpaceHeader}, - disk_address::DiskAddress, - CachedStore, Obj, ShaleError, ShaleStore, SpaceId, Storable, StoredView, + self, compact::CompactSpaceHeader, disk_address::DiskAddress, CachedStore, Obj, ShaleError, + SpaceId, Storable, StoredView, }, }; use aiofut::AioError; @@ -60,9 +58,6 @@ const SPACE_RESERVED: u64 = 0x1000; const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; -pub type MutStore = CompactSpace; -pub type SharedStore = CompactSpace; - #[derive(Debug)] #[non_exhaustive] pub enum DbError { @@ -279,14 +274,14 @@ impl Universe> { /// Some readable version of the DB. #[derive(Debug)] -pub struct DbRev { +pub struct DbRev { header: shale::Obj, - merkle: Merkle, + merkle: Merkle, } #[async_trait] -impl + Send + Sync> api::DbView for DbRev { - type Stream<'a> = MerkleKeyValueStream<'a, S, Bincode> where Self: 'a; +impl api::DbView for DbRev { + type Stream<'a> = MerkleKeyValueStream<'a, T, Bincode> where Self: 'a; async fn root_hash(&self) -> Result { self.merkle @@ -338,12 +333,12 @@ impl + Send + Sync> api::DbView for DbRev { } } -impl + Send + Sync> DbRev { - pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { +impl DbRev { + pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { self.merkle.key_value_iter(self.header.kv_root) } - pub fn stream_from(&self, start_key: Key) -> merkle::MerkleKeyValueStream<'_, S, Bincode> { + pub fn stream_from(&self, start_key: Key) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { self.merkle .key_value_iter_from_key(self.header.kv_root, start_key) } @@ -391,13 +386,8 @@ impl + Send + Sync> DbRev { } } -impl DbRev { - fn borrow_split( - &mut self, - ) -> ( - &mut shale::Obj, - &mut Merkle, Bincode>, - ) { +impl DbRev { + fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { (&mut self.header, &mut self.merkle) } @@ -408,8 +398,8 @@ impl DbRev { } } -impl From> for DbRev { - fn from(mut value: DbRev) -> Self { +impl From> for DbRev { + fn from(mut value: DbRev) -> Self { value.flush_dirty(); DbRev { header: value.header, @@ -437,7 +427,7 @@ impl Drop for DbInner { #[async_trait] impl api::Db for Db { - type Historical = DbRev; + type Historical = DbRev; type Proposal = proposal::Proposal; @@ -477,7 +467,7 @@ pub struct DbRevInner { #[derive(Debug)] pub struct Db { inner: Arc>, - revisions: Arc>>, + revisions: Arc>>, payload_regn_nbit: u64, metrics: Arc, cfg: DbConfig, @@ -700,7 +690,7 @@ impl Db { &self, cached_space: &Universe>, reset_store_headers: bool, - ) -> Result<(Universe, DbRev), DbError> { + ) -> Result<(Universe, DbRev), DbError> { let mut offset = Db::PARAM_SIZE as usize; let db_header: DiskAddress = DiskAddress::from(offset); offset += DbHeader::MSIZE as usize; @@ -741,7 +731,7 @@ impl Db { let header_refs = (db_header_ref, merkle_payload_header_ref); - let mut rev: DbRev> = Db::new_revision( + let mut rev: DbRev = Db::new_revision( header_refs, (store.merkle.meta.clone(), store.merkle.payload.clone()), self.payload_regn_nbit, @@ -778,7 +768,7 @@ impl Db { payload_regn_nbit: u64, payload_max_walk: u64, cfg: &DbRevConfig, - ) -> Result>, DbError> { + ) -> Result, DbError> { // TODO: This should be a compile time check const DB_OFFSET: u64 = Db::PARAM_SIZE; let merkle_offset = DB_OFFSET + DbHeader::MSIZE; @@ -801,7 +791,7 @@ impl Db { ) .unwrap(); - let merkle = Merkle::new(Box::new(merkle_space)); + let merkle = Merkle::new(merkle_space); if db_header_ref.kv_root.is_null() { let mut err = Ok(()); @@ -881,7 +871,7 @@ impl Db { /// /// If no revision with matching root hash found, returns None. // #[measure([HitCount])] - pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { + pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { let mut revisions = self.revisions.lock(); let inner_lock = self.inner.read(); diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 1b01be3420ad..cad50e44bcd7 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -3,12 +3,11 @@ use super::{ get_sub_universe_from_deltas, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, - MutStore, SharedStore, Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, + Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; -use crate::merkle::{Bincode, MerkleKeyValueStream, Node, Proof}; -use crate::shale::compact::CompactSpace; +use crate::merkle::{Bincode, MerkleKeyValueStream, Proof}; use crate::shale::CachedStore; -use crate::storage; +use crate::storage::StoreRevShared; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, @@ -27,11 +26,11 @@ use tokio::task::block_in_place; pub struct Proposal { // State of the Db pub(super) m: Arc>, - pub(super) r: Arc>>, + pub(super) r: Arc>>, pub(super) cfg: DbConfig, // State of the proposal - pub(super) rev: DbRev, + pub(super) rev: DbRev, pub(super) store: Universe, pub(super) committed: Arc>, pub(super) root_hash: TrieHash, @@ -41,7 +40,7 @@ pub struct Proposal { pub enum ProposalBase { Proposal(Arc), - View(Arc>), + View(Arc>), } #[async_trait] @@ -259,14 +258,14 @@ impl Proposal { } impl Proposal { - pub const fn get_revision(&self) -> &DbRev { + pub const fn get_revision(&self) -> &DbRev { &self.rev } } #[async_trait] impl api::DbView for Proposal { - type Stream<'a> = MerkleKeyValueStream<'a, CompactSpace, Bincode>; + type Stream<'a> = MerkleKeyValueStream<'a, StoreRevMut, Bincode>; async fn root_hash(&self) -> Result { self.get_revision() diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 7fb1212b0ac9..d72b76721ca9 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -77,19 +77,17 @@ //! that is mirrored to the disk. In reality, the linear space will be chunked into files under a //! directory, but the user does not have to even know about this. //! -//! - Persistent item storage stash: `ShaleStore` trait from `shale` defines a pool of typed -//! objects that are persisted on disk but also made accessible in memory transparently. It is -//! built on top of `CachedStore` by defining how "items" of the given type are laid out, allocated -//! and recycled throughout their life cycles (there is a disk-friendly, malloc-style kind of -//! basic implementation in `shale` crate, but one can always define their own `ShaleStore`). +//! - Persistent item storage stash: `CompactStore` in `shale` defines a pool of typed objects that are +//! persisted on disk but also made accessible in memory transparently. It is built on top of `CachedStore` +//! and defines how "items" of a given type are laid out, allocated and recycled throughout their lifecycles. //! -//! - Data structure: in Firewood, one trie is maintained by invoking `ShaleStore` (see `src/merkle.rs`). +//! - Data structure: in Firewood, one trie is maintained by invoking `CompactStore` (see `src/merkle.rs`). //! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or //! persisted on disk. It is as if they're just in memory, which makes it much easier to write //! and maintain the code. //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (trie) is what the linear space (`CachedStore`) keeps track of, that is, +//! state of the data structure (trie) is what the linear space (`CachedStore`) keeps track of. That is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the //! content of the linear space, the higher level data structure can be *uniquely* determined, there //! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) @@ -97,8 +95,7 @@ //! separate the logical data from its physical representation, greatly simplifies the storage //! management, and allows reusing the code. It is still a very versatile abstraction, as in theory //! any persistent data could be stored this way -- sometimes you need to swap in a different -//! `ShaleStore` or `CachedStore` implementation, but without having to touch the code for the persisted -//! data structure. +//! `CachedStore` implementation, but without having to touch the code for the persisted data structure. //! //! ## Page-based Shadowing and Revisions //! diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fd5de16f5655..412afa16e031 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,8 +1,10 @@ -use crate::db::{MutStore, SharedStore}; // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; -use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError, ShaleStore}; +use crate::shale::compact::CompactSpace; +use crate::shale::CachedStore; +use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError}; +use crate::storage::{StoreRevMut, StoreRevShared}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use sha3::Digest; @@ -68,21 +70,21 @@ macro_rules! write_node { #[derive(Debug)] pub struct Merkle { - store: Box, + store: CompactSpace, phantom: PhantomData, } -impl From> for Merkle { - fn from(value: Merkle) -> Self { +impl From> for Merkle { + fn from(value: Merkle) -> Self { let store = value.store.into(); Merkle { - store: Box::new(store), + store, phantom: PhantomData, } } } -impl, T> Merkle { +impl Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) } @@ -98,11 +100,11 @@ impl, T> Merkle { impl<'de, S, T> Merkle where - S: ShaleStore + Send + Sync, + S: CachedStore, T: BinarySerde, EncodedNode: serde::Serialize + serde::Deserialize<'de>, { - pub fn new(store: Box) -> Self { + pub const fn new(store: CompactSpace) -> Self { Self { store, phantom: PhantomData, @@ -172,7 +174,7 @@ where } } -impl + Send + Sync, T> Merkle { +impl Merkle { pub fn init_root(&self) -> Result { self.store .put_item( @@ -188,10 +190,6 @@ impl + Send + Sync, T> Merkle { .map(|node| node.as_ptr()) } - pub fn get_store(&self) -> &S { - self.store.as_ref() - } - pub fn empty_root() -> &'static TrieHash { static V: OnceLock = OnceLock::new(); #[allow(clippy::unwrap_used)] @@ -214,7 +212,7 @@ impl + Send + Sync, T> Merkle { .children[0]; Ok(if let Some(root) = root { let mut node = self.get_node(root)?; - let res = *node.get_root_hash::(self.store.as_ref()); + let res = *node.get_root_hash(&self.store); #[allow(clippy::unwrap_used)] if node.is_dirty() { node.write(|_| {}).unwrap(); @@ -231,7 +229,7 @@ impl + Send + Sync, T> Merkle { let hash = match u_ref.root_hash.get() { Some(h) => h, - None => u_ref.get_root_hash::(self.store.as_ref()), + None => u_ref.get_root_hash(&self.store), }; write!(w, "{u:?} => {}: ", hex::encode(**hash))?; @@ -1068,7 +1066,7 @@ impl + Send + Sync, T> Merkle { // Get the hashes of the nodes. for node in nodes.into_iter() { - let encoded = <&[u8]>::clone(&node.get_encoded::(self.store.as_ref())); + let encoded = node.get_encoded(&self.store); let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(encoded).into(); proofs.insert(hash, encoded.to_vec()); } @@ -1292,7 +1290,7 @@ impl<'a> std::ops::Deref for Ref<'a> { } } -impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { +impl<'a, S, T> RefMut<'a, S, T> { fn new(ptr: DiskAddress, parents: ParentAddresses, merkle: &'a mut Merkle) -> Self { Self { ptr, @@ -1300,7 +1298,9 @@ impl<'a, S: ShaleStore + Send + Sync, T> RefMut<'a, S, T> { merkle, } } +} +impl<'a, S: CachedStore, T> RefMut<'a, S, T> { #[allow(clippy::unwrap_used)] pub fn get(&self) -> Ref { Ref(self.merkle.get_node(self.ptr).unwrap()) @@ -1393,7 +1393,7 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { mod tests { use super::*; use crate::merkle::node::PlainCodec; - use shale::{cached::InMemLinearStore, compact::CompactSpace, CachedStore}; + use shale::{cached::InMemLinearStore, CachedStore}; use test_case::test_case; fn leaf(path: Vec, data: Vec) -> Node { @@ -1407,7 +1407,7 @@ mod tests { assert_eq!(n, nibbles); } - fn create_generic_test_merkle<'de, T>() -> Merkle, T> + fn create_generic_test_merkle<'de, T>() -> Merkle where T: BinarySerde, EncodedNode: serde::Serialize + serde::Deserialize<'de>, @@ -1439,11 +1439,10 @@ mod tests { shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); - let store = Box::new(space); - Merkle::new(store) + Merkle::new(space) } - pub(super) fn create_test_merkle() -> Merkle, Bincode> { + pub(super) fn create_test_merkle() -> Merkle { create_generic_test_merkle::() } @@ -1508,9 +1507,9 @@ mod tests { let merkle = create_test_merkle(); let node_ref = merkle.put_node(node).unwrap(); - let encoded = node_ref.get_encoded(merkle.store.as_ref()); + let encoded = node_ref.get_encoded(&merkle.store); let new_node = Node::from(NodeType::decode(encoded).unwrap()); - let new_node_encoded = new_node.get_encoded(merkle.store.as_ref()); + let new_node_encoded = new_node.get_encoded(&merkle.store); assert_eq!(encoded, new_node_encoded); } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index a9108c40eed7..9177c9db854a 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -4,7 +4,7 @@ use crate::{ logger::trace, merkle::from_nibbles, - shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}, + shale::{compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleError, Storable}, }; use bincode::{Error, Options}; use bitflags::bitflags; @@ -103,7 +103,7 @@ impl NodeType { } } - pub fn encode>(&self, store: &S) -> Vec { + pub fn encode(&self, store: &CompactSpace) -> Vec { match &self { NodeType::Leaf(n) => n.encode(), NodeType::Branch(n) => n.encode(store), @@ -223,18 +223,21 @@ impl Node { }) } - pub(super) fn get_encoded>(&self, store: &S) -> &[u8] { - self.encoded.get_or_init(|| self.inner.encode::(store)) + pub(super) fn get_encoded(&self, store: &CompactSpace) -> &[u8] { + self.encoded.get_or_init(|| self.inner.encode(store)) } - pub(super) fn get_root_hash>(&self, store: &S) -> &TrieHash { + pub(super) fn get_root_hash(&self, store: &CompactSpace) -> &TrieHash { self.root_hash.get_or_init(|| { self.set_dirty(true); - TrieHash(Keccak256::digest(self.get_encoded::(store)).into()) + TrieHash(Keccak256::digest(self.get_encoded(store)).into()) }) } - fn is_encoded_longer_than_hash_len>(&self, store: &S) -> bool { + fn is_encoded_longer_than_hash_len( + &self, + store: &CompactSpace, + ) -> bool { *self .is_encoded_longer_than_hash_len .get_or_init(|| self.get_encoded(store).len() >= TRIE_HASH_LEN) diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 62e1941fad2c..da1b5341a390 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -5,7 +5,7 @@ use super::{Data, Node}; use crate::{ merkle::{from_nibbles, to_nibble_array, PartialPath}, nibbles::Nibbles, - shale::{DiskAddress, ShaleError, ShaleStore, Storable}, + shale::{compact::CompactSpace, CachedStore, DiskAddress, ShaleError, Storable}, }; use bincode::{Error, Options}; use serde::de::Error as DeError; @@ -125,7 +125,7 @@ impl BranchNode { )) } - pub(super) fn encode>(&self, store: &S) -> Vec { + pub(super) fn encode(&self, store: &CompactSpace) -> Vec { // path + children + value let mut list = <[Vec; Self::MSIZE]>::default(); @@ -136,9 +136,9 @@ impl BranchNode { let mut c_ref = store.get_item(*c).unwrap(); #[allow(clippy::unwrap_used)] - if c_ref.is_encoded_longer_than_hash_len::(store) { + if c_ref.is_encoded_longer_than_hash_len(store) { #[allow(clippy::indexing_slicing)] - (list[i] = c_ref.get_root_hash::(store).to_vec()); + (list[i] = c_ref.get_root_hash(store).to_vec()); // See struct docs for ordering requirements if c_ref.is_dirty() { @@ -146,7 +146,7 @@ impl BranchNode { c_ref.set_dirty(false); } } else { - let child_encoded = c_ref.get_encoded::(store); + let child_encoded = c_ref.get_encoded(store); #[allow(clippy::indexing_slicing)] (list[i] = child_encoded.to_vec()); } diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 7b4dba66dbae..4709e5987ce6 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -4,8 +4,8 @@ use std::cmp::Ordering; use std::collections::HashMap; -use crate::shale::ObjWriteSizeError; -use crate::shale::{disk_address::DiskAddress, ShaleError, ShaleStore}; +use crate::shale::{disk_address::DiskAddress, ShaleError}; +use crate::shale::{CachedStore, ObjWriteSizeError}; use crate::v2::api::HashKey; use aiofut::AioError; use nix::errno::Errno; @@ -384,7 +384,7 @@ impl + Send> Proof { } } -fn decode_subproof<'a, S: ShaleStore, T, N: AsRef<[u8]>>( +fn decode_subproof<'a, S: CachedStore, T, N: AsRef<[u8]>>( merkle: &'a Merkle, proofs_map: &HashMap, child_hash: &HashKey, @@ -714,7 +714,7 @@ where // keep the entire branch and return. // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -fn unset_node_ref, S: ShaleStore + Send + Sync, T: BinarySerde>( +fn unset_node_ref, S: CachedStore, T: BinarySerde>( merkle: &Merkle, parent: DiskAddress, node: Option, diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index b6b76ed30396..6f8ce6cca038 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -1,10 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{node::Node, BranchNode, Key, Merkle, MerkleError, NodeObjRef, NodeType, Value}; +use super::{BranchNode, Key, Merkle, MerkleError, NodeObjRef, NodeType, Value}; use crate::{ nibbles::{Nibbles, NibblesIterator}, - shale::{DiskAddress, ShaleStore}, + shale::{CachedStore, DiskAddress}, v2::api, }; use futures::{stream::FusedStream, Stream, StreamExt}; @@ -73,7 +73,7 @@ pub struct MerkleNodeStream<'a, S, T> { merkle: &'a Merkle, } -impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleNodeStream<'a, S, T> { +impl<'a, S: CachedStore, T> FusedStream for MerkleNodeStream<'a, S, T> { fn is_terminated(&self) -> bool { // The top of `iter_stack` is the next node to return. // If `iter_stack` is empty, there are no more nodes to visit. @@ -93,7 +93,7 @@ impl<'a, S, T> MerkleNodeStream<'a, S, T> { } } -impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S, T> { +impl<'a, S: CachedStore, T> Stream for MerkleNodeStream<'a, S, T> { type Item = Result<(Key, NodeObjRef<'a>), api::Error>; fn poll_next( @@ -178,7 +178,7 @@ impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleNodeStream<'a, S /// Returns the initial state for an iterator over the given `merkle` with root `root_node` /// which starts at `key`. -fn get_iterator_intial_state<'a, S: ShaleStore + Send + Sync, T>( +fn get_iterator_intial_state<'a, S: CachedStore, T>( merkle: &'a Merkle, root_node: DiskAddress, key: &[u8], @@ -321,7 +321,7 @@ pub struct MerkleKeyValueStream<'a, S, T> { merkle: &'a Merkle, } -impl<'a, S: ShaleStore + Send + Sync, T> FusedStream for MerkleKeyValueStream<'a, S, T> { +impl<'a, S: CachedStore, T> FusedStream for MerkleKeyValueStream<'a, S, T> { fn is_terminated(&self) -> bool { matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) } @@ -345,7 +345,7 @@ impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { } } -impl<'a, S: ShaleStore + Send + Sync, T> Stream for MerkleKeyValueStream<'a, S, T> { +impl<'a, S: CachedStore, T> Stream for MerkleKeyValueStream<'a, S, T> { type Item = Result<(Key, Value), api::Error>; fn poll_next( @@ -426,7 +426,7 @@ pub struct PathIterator<'a, 'b, S, T> { merkle: &'a Merkle, } -impl<'a, 'b, S: ShaleStore + Send + Sync, T> PathIterator<'a, 'b, S, T> { +impl<'a, 'b, S: CachedStore, T> PathIterator<'a, 'b, S, T> { pub(super) fn new( merkle: &'a Merkle, sentinel_node: NodeObjRef<'a>, @@ -456,7 +456,7 @@ impl<'a, 'b, S: ShaleStore + Send + Sync, T> PathIterator<'a, 'b, S, T> { } } -impl<'a, 'b, S: ShaleStore + Send + Sync, T> Iterator for PathIterator<'a, 'b, S, T> { +impl<'a, 'b, S: CachedStore, T> Iterator for PathIterator<'a, 'b, S, T> { type Item = Result<(Key, NodeObjRef<'a>), MerkleError>; fn next(&mut self) -> Option { @@ -582,16 +582,13 @@ use super::tests::create_test_merkle; #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use crate::{ - merkle::Bincode, - shale::{cached::InMemLinearStore, compact::CompactSpace}, - }; + use crate::{merkle::Bincode, shale::cached::InMemLinearStore}; use super::*; use futures::StreamExt; use test_case::test_case; - impl + Send + Sync, T> Merkle { + impl Merkle { pub(crate) fn node_iter(&self, root: DiskAddress) -> MerkleNodeStream<'_, S, T> { MerkleNodeStream::new(self, root, Box::new([])) } @@ -772,10 +769,7 @@ mod tests { /// /// Note the 0000 branch has no value and the F0F0 /// The number next to each branch is the position of the child in the branch's children array. - fn created_populated_merkle() -> ( - Merkle, Bincode>, - DiskAddress, - ) { + fn created_populated_merkle() -> (Merkle, DiskAddress) { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index cb0bb6236c15..897958d6bae6 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -4,12 +4,9 @@ use crate::{ merkle::{ proof::{Proof, ProofError}, - BinarySerde, EncodedNode, Merkle, Node, Ref, RefMut, TrieHash, - }, - shale::{ - self, cached::InMemLinearStore, compact::CompactSpace, disk_address::DiskAddress, - CachedStore, StoredView, + BinarySerde, EncodedNode, Merkle, Ref, RefMut, TrieHash, }, + shale::{self, cached::InMemLinearStore, disk_address::DiskAddress, CachedStore, StoredView}, }; use std::num::NonZeroUsize; use thiserror::Error; @@ -36,11 +33,9 @@ pub enum DataStoreError { ProofEmptyKeyValuesError, } -type InMemoryStore = CompactSpace; - pub struct InMemoryMerkle { root: DiskAddress, - merkle: Merkle, + merkle: Merkle, } impl InMemoryMerkle @@ -77,7 +72,7 @@ where shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("CompactSpace init fail"); - let merkle = Merkle::new(Box::new(space)); + let merkle = Merkle::new(space); #[allow(clippy::unwrap_used)] let root = merkle.init_root().unwrap(); @@ -105,7 +100,7 @@ where pub fn get_mut>( &mut self, key: K, - ) -> Result>, DataStoreError> { + ) -> Result>, DataStoreError> { self.merkle .get_mut(key, self.root) .map_err(|_err| DataStoreError::GetError) @@ -115,7 +110,7 @@ where self.root } - pub fn get_merkle_mut(&mut self) -> &mut Merkle { + pub fn get_merkle_mut(&mut self) -> &mut Merkle { &mut self.merkle } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 4b514110c23f..6a97a55b89f1 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -7,7 +7,7 @@ use crate::shale::ObjCache; use crate::storage::{StoreRevMut, StoreRevShared}; use super::disk_address::DiskAddress; -use super::{CachedStore, Obj, ObjRef, ShaleError, ShaleStore, Storable, StoredView}; +use super::{CachedStore, Obj, ObjRef, ShaleError, Storable, StoredView}; use bytemuck::{Pod, Zeroable}; use std::fmt::Debug; use std::io::{Cursor, Write}; @@ -569,9 +569,9 @@ impl CompactSpace { } } -impl From>> for CompactSpace { +impl From> for CompactSpace { #[allow(clippy::unwrap_used)] - fn from(value: Box>) -> Self { + fn from(value: CompactSpace) -> Self { let inner = value.inner.into_inner().unwrap(); CompactSpace { inner: RwLock::new(inner.into()), @@ -580,10 +580,8 @@ impl From>> for CompactSpace ShaleStore - for CompactSpace -{ - fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { +impl CompactSpace { + pub(crate) fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; #[allow(clippy::unwrap_used)] let addr = self.inner.write().unwrap().alloc(size)?; @@ -612,14 +610,14 @@ impl Sh } #[allow(clippy::unwrap_used)] - fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { + pub(crate) fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { let mut inner = self.inner.write().unwrap(); self.obj_cache.pop(ptr); #[allow(clippy::unwrap_used)] inner.free(ptr.unwrap().get() as u64) } - fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { + pub(crate) fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { let obj = self.obj_cache.get(ptr)?; #[allow(clippy::unwrap_used)] @@ -648,7 +646,7 @@ impl Sh } #[allow(clippy::unwrap_used)] - fn flush_dirty(&self) -> Option<()> { + pub(crate) fn flush_dirty(&self) -> Option<()> { let mut inner = self.inner.write().unwrap(); inner.header.flush_dirty(); // hold the write lock to ensure that both cache and header are flushed in-sync diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index ea7f4339b728..f0aa7b516668 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -87,8 +87,7 @@ pub trait CachedStore: Debug + Send + Sync { /// A wrapper of `StoredView` to enable writes. The direct construction (by [Obj::from_stored_view] /// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. -/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [DiskAddress] -/// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access. +/// headers/metadata at bootstrap) stored at a given [DiskAddress]. #[derive(Debug)] pub struct Obj { value: StoredView, @@ -166,7 +165,7 @@ impl Deref for Obj { } } -/// User handle that offers read & write access to the stored [ShaleStore] item. +/// User handle that offers read & write access to the stored items. #[derive(Debug)] pub struct ObjRef<'a, T: Storable> { inner: ManuallyDrop>, @@ -243,19 +242,6 @@ impl<'a, T: Storable> Drop for ObjRef<'a, T> { } } -/// A persistent item storage backed by linear logical space. New items can be created and old -/// items could be retrieved or dropped. -pub trait ShaleStore { - /// Dereference [DiskAddress] to a unique handle that allows direct access to the item in memory. - fn get_item(&'_ self, ptr: DiskAddress) -> Result, ShaleError>; - /// Allocate a new item. - fn put_item(&'_ self, item: T, extra: u64) -> Result, ShaleError>; - /// Free an item and recycle its space when applicable. - fn free_item(&mut self, item: DiskAddress) -> Result<(), ShaleError>; - /// Flush all dirty writes. - fn flush_dirty(&self) -> Option<()>; -} - /// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient /// implementation could be directly transmuting to/from a POD struct. But sometimes necessary /// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. @@ -437,7 +423,7 @@ pub struct ObjCacheInner { dirty: HashSet, } -/// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s. +/// [ObjRef] pool that is used by [compact::CompactSpace] to construct [ObjRef]s. #[derive(Debug)] pub struct ObjCache(Arc>>); From e762c0e8166bd8ab99d109c060360fba5ca2f50c Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 12 Mar 2024 13:43:35 -0400 Subject: [PATCH 0507/1053] rename node `path` fields to `partial_path` (#587) --- firewood/src/merkle.rs | 79 ++++++++++++++++-------------- firewood/src/merkle/node.rs | 30 +++++++----- firewood/src/merkle/node/branch.rs | 16 +++--- firewood/src/merkle/node/leaf.rs | 23 +++++---- firewood/src/merkle/proof.rs | 18 +++---- firewood/src/merkle/stream.rs | 28 +++++------ firewood/src/shale/mod.rs | 2 +- 7 files changed, 106 insertions(+), 90 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 412afa16e031..d8c0442967a2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -118,7 +118,7 @@ where NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), NodeType::Branch(n) => { - let path = n.path.clone(); + let path = n.partial_path.clone(); // pair up DiskAddresses with encoded children and pick the right one let encoded_children = n.chd().iter().zip(n.children_encoded.iter()); @@ -179,7 +179,7 @@ impl Merkle { self.store .put_item( Node::from_branch(BranchNode { - path: vec![].into(), + partial_path: vec![].into(), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -310,7 +310,7 @@ impl Merkle { .chain(key_nibbles.clone()) .collect::>(); - let overlap = PrefixOverlap::from(&n.path, &key_remainder); + let overlap = PrefixOverlap::from(&n.partial_path, &key_remainder); #[allow(clippy::indexing_slicing)] match (overlap.unique_a.len(), overlap.unique_b.len()) { @@ -341,7 +341,7 @@ impl Merkle { children[new_leaf_index as usize] = Some(new_leaf); let new_branch = BranchNode { - path: PartialPath(overlap.shared.to_vec()), + partial_path: PartialPath(overlap.shared.to_vec()), children, value: n.data.clone().into(), children_encoded: Default::default(), @@ -374,7 +374,7 @@ impl Merkle { .as_ptr(); let mut new_branch = BranchNode { - path: PartialPath(new_branch_path), + partial_path: PartialPath(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: Some(val.into()), children_encoded: Default::default(), @@ -418,7 +418,7 @@ impl Merkle { let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { - path: PartialPath(new_branch_path), + partial_path: PartialPath(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -437,7 +437,7 @@ impl Merkle { break None; } - NodeType::Branch(n) if n.path.len() == 0 => { + NodeType::Branch(n) if n.partial_path.len() == 0 => { #[allow(clippy::indexing_slicing)] match n.children[next_nibble as usize] { Some(c) => (node, c), @@ -470,7 +470,7 @@ impl Merkle { .chain(key_nibbles.clone()) .collect::>(); - let overlap = PrefixOverlap::from(&n.path, &key_remainder); + let overlap = PrefixOverlap::from(&n.partial_path, &key_remainder); #[allow(clippy::indexing_slicing)] match (overlap.unique_a.len(), overlap.unique_b.len()) { @@ -537,7 +537,7 @@ impl Merkle { .as_ptr(); let mut new_branch = BranchNode { - path: PartialPath(new_branch_path), + partial_path: PartialPath(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: Some(val.into()), children_encoded: Default::default(), @@ -583,7 +583,7 @@ impl Merkle { let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { - path: PartialPath(new_branch_path), + partial_path: PartialPath(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -623,15 +623,15 @@ impl Merkle { None } NodeType::Leaf(n) => { - if n.path.len() == 0 { + if n.partial_path.len() == 0 { n.data = Data(val); None } else { #[allow(clippy::indexing_slicing)] - let idx = n.path[0]; + let idx = n.partial_path[0]; #[allow(clippy::indexing_slicing)] - (n.path = PartialPath(n.path[1..].to_vec())); + (n.partial_path = PartialPath(n.partial_path[1..].to_vec())); u.rehash(); Some((idx, true, None, val)) @@ -664,7 +664,7 @@ impl Merkle { let branch = self .put_node(Node::from_branch(BranchNode { - path: vec![].into(), + partial_path: vec![].into(), children: chd, value: Some(Data(val)), children_encoded: Default::default(), @@ -714,7 +714,7 @@ impl Merkle { // don't change the sentinal node if children.len() == 1 && !parents.is_empty() { - let branch_path = &branch.path.0; + let branch_path = &branch.partial_path.0; #[allow(clippy::indexing_slicing)] let (child_index, child) = children[0]; @@ -774,7 +774,7 @@ impl Merkle { match (children.len(), &branch.value, !parents.is_empty()) { // node is invalid, all single-child nodes should have data (1, None, true) => { - let parent_path = &branch.path.0; + let parent_path = &branch.partial_path.0; #[allow(clippy::indexing_slicing)] let (child_index, child) = children[0]; @@ -790,10 +790,10 @@ impl Merkle { .iter() .copied() .chain(once(child_index as u8)) - .chain(child.path.0.iter().copied()) + .chain(child.partial_path.0.iter().copied()) .collect(); - child.path = PartialPath(path); + child.partial_path = PartialPath(path); Node::from_branch(child) } @@ -802,10 +802,10 @@ impl Merkle { .iter() .copied() .chain(once(child_index as u8)) - .chain(child.path.0.iter().copied()) + .chain(child.partial_path.0.iter().copied()) .collect(); - child.path = PartialPath(path); + child.partial_path = PartialPath(path); Node::from_leaf(child) } @@ -821,7 +821,7 @@ impl Merkle { // branch nodes shouldn't have no children (0, Some(data), true) => { let leaf = Node::from_leaf(LeafNode::new( - PartialPath(branch.path.0.clone()), + PartialPath(branch.partial_path.0.clone()), data.clone(), )); @@ -958,12 +958,14 @@ impl Merkle { let next_ptr = match &node_ref.inner { #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) if n.path.len() == 0 => match n.children[nib as usize] { - Some(c) => c, - None => return Ok(None), - }, + NodeType::Branch(n) if n.partial_path.len() == 0 => { + match n.children[nib as usize] { + Some(c) => c, + None => return Ok(None), + } + } NodeType::Branch(n) => { - let mut n_path_iter = n.path.iter().copied(); + let mut n_path_iter = n.partial_path.iter().copied(); if n_path_iter.next() != Some(nib) { return Ok(None); @@ -994,7 +996,10 @@ impl Merkle { } } NodeType::Leaf(n) => { - let node_ref = if once(nib).chain(key_nibbles).eq(n.path.iter().copied()) { + let node_ref = if once(nib) + .chain(key_nibbles) + .eq(n.partial_path.iter().copied()) + { Some(node_ref) } else { None @@ -1011,10 +1016,10 @@ impl Merkle { // when we're done iterating over nibbles, check if the node we're at has a value let node_ref = match &node_ref.inner { - NodeType::Branch(n) if n.value.as_ref().is_some() && n.path.is_empty() => { + NodeType::Branch(n) if n.value.as_ref().is_some() && n.partial_path.is_empty() => { Some(node_ref) } - NodeType::Leaf(n) if n.path.len() == 0 => Some(node_ref), + NodeType::Leaf(n) if n.partial_path.len() == 0 => Some(node_ref), _ => None, }; @@ -1461,7 +1466,7 @@ mod tests { } Node::from_branch(BranchNode { - path, + partial_path: path, children, value, children_encoded, @@ -1483,7 +1488,7 @@ mod tests { } Node::from_branch(BranchNode { - path, + partial_path: path, children, value, children_encoded, @@ -2084,7 +2089,7 @@ mod tests { .collect::>(); let node = Node::from_leaf(LeafNode { - path: PartialPath::from(path), + partial_path: PartialPath::from(path), data: Data(data.clone()), }); @@ -2103,7 +2108,7 @@ mod tests { .collect::>(); let node = Node::from_leaf(LeafNode { - path: PartialPath::from(path.clone()), + partial_path: PartialPath::from(path.clone()), data: Data(data), }); @@ -2122,7 +2127,7 @@ mod tests { .collect::>(); let node = Node::from_branch(BranchNode { - path: PartialPath::from(path.clone()), + partial_path: PartialPath::from(path.clone()), children: Default::default(), value: Some(Data(data.clone())), children_encoded: Default::default(), @@ -2143,7 +2148,7 @@ mod tests { .collect::>(); let node = Node::from_branch(BranchNode { - path: PartialPath::from(path.clone()), + partial_path: PartialPath::from(path.clone()), children: Default::default(), value: Some(Data(data)), children_encoded: Default::default(), @@ -2187,8 +2192,8 @@ mod tests { assert_eq!(&to_delete[0], &addr); let (path, data) = match node.inner() { - NodeType::Leaf(leaf) => (&leaf.path, Some(&leaf.data)), - NodeType::Branch(branch) => (&branch.path, branch.value.as_ref()), + NodeType::Leaf(leaf) => (&leaf.partial_path, Some(&leaf.data)), + NodeType::Branch(branch) => (&branch.partial_path, branch.value.as_ref()), }; assert_eq!(path, &PartialPath(new_path)); diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 9177c9db854a..1b03a3c06062 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -112,15 +112,15 @@ impl NodeType { pub fn path_mut(&mut self) -> &mut PartialPath { match self { - NodeType::Branch(u) => &mut u.path, - NodeType::Leaf(node) => &mut node.path, + NodeType::Branch(u) => &mut u.partial_path, + NodeType::Leaf(node) => &mut node.partial_path, } } pub fn set_path(&mut self, path: PartialPath) { match self { - NodeType::Branch(u) => u.path = path, - NodeType::Leaf(node) => node.path = path, + NodeType::Branch(u) => u.partial_path = path, + NodeType::Leaf(node) => node.partial_path = path, } } @@ -210,7 +210,7 @@ impl Node { is_encoded_longer_than_hash_len: OnceLock::new(), inner: NodeType::Branch( BranchNode { - path: vec![].into(), + partial_path: vec![].into(), children: [Some(DiskAddress::null()); BranchNode::MAX_CHILDREN], value: Some(Data(Vec::new())), children_encoded: Default::default(), @@ -524,7 +524,7 @@ impl Serialize for EncodedNode { EncodedNodeType::Leaf(n) => { let data = Some(&*n.data); let chd: Vec<(u64, Vec)> = Default::default(); - let path: Vec<_> = from_nibbles(&n.path.encode()).collect(); + let path: Vec<_> = from_nibbles(&n.partial_path.encode()).collect(); (chd, data, path) } @@ -580,7 +580,10 @@ impl<'de> Deserialize<'de> for EncodedNode { Data(Vec::new()) }; - let node = EncodedNodeType::Leaf(LeafNode { path, data }); + let node = EncodedNodeType::Leaf(LeafNode { + partial_path: path, + data, + }); Ok(Self::new(node)) } else { @@ -608,7 +611,10 @@ impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result { match &self.node { EncodedNodeType::Leaf(n) => { - let list = [from_nibbles(&n.path.encode()).collect(), n.data.to_vec()]; + let list = [ + from_nibbles(&n.partial_path.encode()).collect(), + n.data.to_vec(), + ]; let mut seq = serializer.serialize_seq(Some(list.len()))?; for e in list { seq.serialize_element(&e)?; @@ -681,7 +687,7 @@ impl<'de> Deserialize<'de> for EncodedNode { }; let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let node = EncodedNodeType::Leaf(LeafNode { - path, + partial_path: path, data: Data(data), }); Ok(Self::new(node)) @@ -828,7 +834,7 @@ mod tests { ) { let leaf = NodeType::Leaf(LeafNode::new(PartialPath(vec![1, 2, 3]), Data(vec![4, 5]))); let branch = NodeType::Branch(Box::new(BranchNode { - path: vec![].into(), + partial_path: vec![].into(), children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], value: Some(Data(vec![1, 2, 3])), children_encoded: std::array::from_fn(|_| Some(vec![1])), @@ -912,7 +918,7 @@ mod tests { value: impl Into>, children_encoded: [Option>; BranchNode::MAX_CHILDREN], ) { - let path = PartialPath(path.iter().copied().map(|x| x & 0xf).collect()); + let partial_path = PartialPath(path.iter().copied().map(|x| x & 0xf).collect()); let mut children = children.into_iter().map(|x| { if x == 0 { @@ -929,7 +935,7 @@ mod tests { .map(|x| Data(std::iter::repeat(x).take(x as usize).collect())); let node = Node::from_branch(BranchNode { - path, + partial_path, children, value, children_encoded, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index da1b5341a390..dbccbff4a4da 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -23,7 +23,7 @@ const MAX_CHILDREN: usize = 16; #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { - pub(crate) path: PartialPath, + pub(crate) partial_path: PartialPath, pub(crate) children: [Option; MAX_CHILDREN], pub(crate) value: Option, pub(crate) children_encoded: [Option>; MAX_CHILDREN], @@ -32,7 +32,7 @@ pub struct BranchNode { impl Debug for BranchNode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "[Branch")?; - write!(f, r#" path="{:?}""#, self.path)?; + write!(f, r#" path="{:?}""#, self.partial_path)?; for (i, c) in self.children.iter().enumerate() { if let Some(c) = c { @@ -62,13 +62,13 @@ impl BranchNode { pub const MSIZE: usize = Self::MAX_CHILDREN + 2; pub fn new( - path: PartialPath, + partial_path: PartialPath, chd: [Option; Self::MAX_CHILDREN], value: Option>, chd_encoded: [Option>; Self::MAX_CHILDREN], ) -> Self { BranchNode { - path, + partial_path, children: chd, value: value.map(Data), children_encoded: chd_encoded, @@ -176,7 +176,7 @@ impl BranchNode { } #[allow(clippy::unwrap_used)] - let path = from_nibbles(&self.path.encode()).collect::>(); + let path = from_nibbles(&self.partial_path.encode()).collect::>(); list[Self::MAX_CHILDREN + 1] = path; @@ -194,7 +194,7 @@ impl Storable for BranchNode { len + optional_data_len::(child.as_ref()) }); let path_len_size = size_of::() as u64; - let path_len = self.path.serialized_len(); + let path_len = self.partial_path.serialized_len(); children_len + data_len + children_encoded_len + path_len_size + path_len } @@ -202,7 +202,7 @@ impl Storable for BranchNode { fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path: Vec = from_nibbles(&self.path.encode()).collect(); + let path: Vec = from_nibbles(&self.partial_path.encode()).collect(); cursor.write_all(&[path.len() as PathLen])?; cursor.write_all(&path)?; @@ -358,7 +358,7 @@ impl Storable for BranchNode { } let node = BranchNode { - path, + partial_path: path, children, value, children_encoded, diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 905cf2b4c049..d18d06eaf90e 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -22,26 +22,31 @@ type DataLen = u32; #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { - pub(crate) path: PartialPath, + pub(crate) partial_path: PartialPath, pub(crate) data: Data, } impl Debug for LeafNode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - write!(f, "[Leaf {:?} {}]", self.path, hex::encode(&*self.data)) + write!( + f, + "[Leaf {:?} {}]", + self.partial_path, + hex::encode(&*self.data) + ) } } impl LeafNode { - pub fn new, D: Into>(path: P, data: D) -> Self { + pub fn new, D: Into>(partial_path: P, data: D) -> Self { Self { - path: path.into(), + partial_path: partial_path.into(), data: data.into(), } } pub const fn path(&self) -> &PartialPath { - &self.path + &self.partial_path } pub const fn data(&self) -> &Data { @@ -53,7 +58,7 @@ impl LeafNode { bincode::DefaultOptions::new() .serialize( [ - from_nibbles(&self.path.encode()).collect(), + from_nibbles(&self.partial_path.encode()).collect(), self.data.to_vec(), ] .as_slice(), @@ -76,7 +81,7 @@ impl Meta { impl Storable for LeafNode { fn serialized_len(&self) -> u64 { let meta_len = size_of::() as u64; - let path_len = self.path.serialized_len(); + let path_len = self.partial_path.serialized_len(); let data_len = self.data.len() as u64; meta_len + path_len + data_len @@ -85,11 +90,11 @@ impl Storable for LeafNode { fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path = &self.path.encode(); + let path = &self.partial_path.encode(); let path = from_nibbles(path); let data = &self.data; - let path_len = self.path.serialized_len() as PathLen; + let path_len = self.partial_path.serialized_len() as PathLen; let data_len = data.len() as DataLen; let meta = Meta { path_len, data_len }; diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 4709e5987ce6..5f9f5fe46ff3 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -333,7 +333,7 @@ impl + Send> Proof { let encoded_sub_proof = match child_node.inner() { NodeType::Leaf(n) => { break n - .path + .partial_path .iter() .copied() .eq(key_nibbles) // all nibbles have to match @@ -342,7 +342,7 @@ impl + Send> Proof { NodeType::Branch(n) => { let paths_match = n - .path + .partial_path .iter() .copied() .all(|nibble| Some(nibble) == key_nibbles.next()); @@ -420,7 +420,7 @@ fn locate_subproof( Ok((sub_proof.into(), key_nibbles)) } NodeType::Branch(n) => { - let partial_path = &n.path.0; + let partial_path = &n.partial_path.0; let does_not_match = key_nibbles.size_hint().0 < partial_path.len() || !partial_path @@ -518,7 +518,7 @@ where NodeType::Branch(n) => { // If either the key of left proof or right proof doesn't match with // stop here, this is the forkpoint. - let path = &*n.path; + let path = &*n.partial_path; if !path.is_empty() { [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] @@ -552,7 +552,7 @@ where #[allow(clippy::indexing_slicing)] NodeType::Leaf(n) => { - let path = &*n.path; + let path = &*n.partial_path; [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) @@ -597,7 +597,7 @@ where } let p = u_ref.as_ptr(); - index += n.path.len(); + index += n.partial_path.len(); // Only one proof points to non-existent key. if fork_right.is_ne() { @@ -741,8 +741,8 @@ fn unset_node_ref, S: CachedStore, T: BinarySerde>( #[allow(clippy::indexing_slicing)] match &u_ref.inner() { - NodeType::Branch(n) if chunks[index..].starts_with(&n.path) => { - let index = index + n.path.len(); + NodeType::Branch(n) if chunks[index..].starts_with(&n.partial_path) => { + let index = index + n.partial_path.len(); let child_index = chunks[index] as usize; let node = n.chd()[child_index]; @@ -772,7 +772,7 @@ fn unset_node_ref, S: CachedStore, T: BinarySerde>( } NodeType::Branch(n) => { - let cur_key = &n.path; + let cur_key = &n.partial_path; // Find the fork point, it's a non-existent branch. // diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 6f8ce6cca038..18ad4fc62d55 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -146,8 +146,8 @@ impl<'a, S: CachedStore, T> Stream for MerkleNodeStream<'a, S, T> { let child = merkle.get_node(child_addr)?; let partial_path = match child.inner() { - NodeType::Branch(branch) => branch.path.iter().copied(), - NodeType::Leaf(leaf) => leaf.path.iter().copied(), + NodeType::Branch(branch) => branch.partial_path.iter().copied(), + NodeType::Leaf(leaf) => leaf.partial_path.iter().copied(), }; // The child's key is its parent's key, followed by the child's index, @@ -240,8 +240,8 @@ fn get_iterator_intial_state<'a, S: CachedStore, T>( let child = merkle.get_node(child_addr)?; let partial_key = match child.inner() { - NodeType::Branch(branch) => &branch.path, - NodeType::Leaf(leaf) => &leaf.path, + NodeType::Branch(branch) => &branch.partial_path, + NodeType::Leaf(leaf) => &leaf.partial_path, }; let (comparison, new_unmatched_key_nibbles) = @@ -272,13 +272,13 @@ fn get_iterator_intial_state<'a, S: CachedStore, T>( } } NodeType::Leaf(leaf) => { - if compare_partial_path(leaf.path.iter(), unmatched_key_nibbles).0 + if compare_partial_path(leaf.partial_path.iter(), unmatched_key_nibbles).0 == Ordering::Greater { // `child` is after `key`. let key = matched_key_nibbles .iter() - .chain(leaf.path.iter()) + .chain(leaf.partial_path.iter()) .copied() .collect(); iter_stack.push(IterationNode::Unvisited { key, node }); @@ -477,8 +477,8 @@ impl<'a, 'b, S: CachedStore, T> Iterator for PathIterator<'a, 'b, S, T> { }; let partial_path = match node.inner() { - NodeType::Branch(branch) => &branch.path, - NodeType::Leaf(leaf) => &leaf.path, + NodeType::Branch(branch) => &branch.partial_path, + NodeType::Leaf(leaf) => &leaf.partial_path, }; let (comparison, unmatched_key) = @@ -810,40 +810,40 @@ mod tests { assert_eq!(key, vec![0x00].into_boxed_slice()); let node = node.inner().as_branch().unwrap(); assert!(node.value.is_none()); - assert_eq!(node.path.to_vec(), vec![0x00, 0x00]); + assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00]); // Covers case of branch with value let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); let node = node.inner().as_branch().unwrap(); assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); - assert_eq!(node.path.to_vec(), vec![0x00, 0x00, 0x00]); + assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00, 0x00]); // Covers case of leaf with partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); - assert_eq!(node.path.to_vec(), vec![0x01]); + assert_eq!(node.partial_path.to_vec(), vec![0x01]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); - assert_eq!(node.path.to_vec(), vec![0x0F]); + assert_eq!(node.partial_path.to_vec(), vec![0x0F]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xD0, 0xD0]); - assert_eq!(node.path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO + assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO // Covers case of leaf with no partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xFF]); - assert_eq!(node.path.to_vec(), vec![0x0F]); + assert_eq!(node.partial_path.to_vec(), vec![0x0F]); check_stream_is_done(stream).await; } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index f0aa7b516668..8e2c70239768 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -144,7 +144,7 @@ impl Obj { impl Obj { pub fn into_inner(mut self) -> Node { let empty_node = LeafNode { - path: PartialPath(Vec::new()), + partial_path: PartialPath(Vec::new()), data: Vec::new().into(), }; From 00965182e9184d3ac8ade914bab8be703fccd21f Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 14 Mar 2024 12:16:20 -0400 Subject: [PATCH 0508/1053] rename `PartialPath` to `Path` (#588) --- firewood/src/merkle.rs | 84 +++++++++---------- firewood/src/merkle/node.rs | 36 ++++---- firewood/src/merkle/node/branch.rs | 14 ++-- firewood/src/merkle/node/leaf.rs | 14 ++-- .../merkle/node/{partial_path.rs => path.rs} | 23 ++--- firewood/src/shale/mod.rs | 4 +- 6 files changed, 85 insertions(+), 90 deletions(-) rename firewood/src/merkle/node/{partial_path.rs => path.rs} (80%) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d8c0442967a2..23f76c6cbceb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -20,7 +20,7 @@ mod trie_hash; pub use node::{ BinarySerde, Bincode, BranchNode, Data, EncodedNode, EncodedNodeType, LeafNode, Node, NodeType, - PartialPath, + Path, }; pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; @@ -161,7 +161,7 @@ where children, value, } => { - let path = PartialPath::decode(&path); + let path = Path::decode(&path); let value = value.map(|v| v.0); let branch = NodeType::Branch( BranchNode::new(path, [None; BranchNode::MAX_CHILDREN], value, *children) @@ -330,10 +330,8 @@ impl Merkle { (index[0], path.to_vec()) }; - let new_leaf = Node::from_leaf(LeafNode::new( - PartialPath(new_leaf_path), - Data(val), - )); + let new_leaf = + Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); let new_leaf = self.put_node(new_leaf)?.as_ptr(); @@ -341,7 +339,7 @@ impl Merkle { children[new_leaf_index as usize] = Some(new_leaf); let new_branch = BranchNode { - partial_path: PartialPath(overlap.shared.to_vec()), + partial_path: Path(overlap.shared.to_vec()), children, value: n.data.clone().into(), children_encoded: Default::default(), @@ -369,12 +367,12 @@ impl Merkle { .update_path_and_move_node_if_larger( (&mut parents, &mut deleted), node, - PartialPath(old_leaf_path.to_vec()), + Path(old_leaf_path.to_vec()), )? .as_ptr(); let mut new_branch = BranchNode { - partial_path: PartialPath(new_branch_path), + partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: Some(val.into()), children_encoded: Default::default(), @@ -406,19 +404,17 @@ impl Merkle { .update_path_and_move_node_if_larger( (&mut parents, &mut deleted), node, - PartialPath(old_leaf_path.to_vec()), + Path(old_leaf_path.to_vec()), )? .as_ptr(); - let new_leaf = Node::from_leaf(LeafNode::new( - PartialPath(new_leaf_path), - Data(val), - )); + let new_leaf = + Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { - partial_path: PartialPath(new_branch_path), + partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -446,7 +442,7 @@ impl Merkle { // create a new leaf let leaf_ptr = self .put_node(Node::from_leaf(LeafNode::new( - PartialPath(key_nibbles.collect()), + Path(key_nibbles.collect()), Data(val), )))? .as_ptr(); @@ -501,7 +497,7 @@ impl Merkle { Some(ptr) => (node, ptr), None => { let new_leaf = Node::from_leaf(LeafNode::new( - PartialPath(new_leaf_path.to_vec()), + Path(new_leaf_path.to_vec()), Data(val), )); @@ -532,12 +528,12 @@ impl Merkle { .update_path_and_move_node_if_larger( (&mut parents, &mut deleted), node, - PartialPath(old_branch_path.to_vec()), + Path(old_branch_path.to_vec()), )? .as_ptr(); let mut new_branch = BranchNode { - partial_path: PartialPath(new_branch_path), + partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: Some(val.into()), children_encoded: Default::default(), @@ -571,19 +567,17 @@ impl Merkle { .update_path_and_move_node_if_larger( (&mut parents, &mut deleted), node, - PartialPath(old_branch_path.to_vec()), + Path(old_branch_path.to_vec()), )? .as_ptr(); - let new_leaf = Node::from_leaf(LeafNode::new( - PartialPath(new_leaf_path), - Data(val), - )); + let new_leaf = + Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); let new_leaf = self.put_node(new_leaf)?.as_ptr(); let mut new_branch = BranchNode { - partial_path: PartialPath(new_branch_path), + partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], value: None, children_encoded: Default::default(), @@ -631,7 +625,7 @@ impl Merkle { #[allow(clippy::indexing_slicing)] let idx = n.partial_path[0]; #[allow(clippy::indexing_slicing)] - (n.partial_path = PartialPath(n.partial_path[1..].to_vec())); + (n.partial_path = Path(n.partial_path[1..].to_vec())); u.rehash(); Some((idx, true, None, val)) @@ -728,7 +722,7 @@ impl Merkle { .chain(once(child_index as u8)) .chain(child_path.0.iter().copied()) .collect(); - *child_path = PartialPath(path); + *child_path = Path(path); child.rehash(); })?; @@ -793,7 +787,7 @@ impl Merkle { .chain(child.partial_path.0.iter().copied()) .collect(); - child.partial_path = PartialPath(path); + child.partial_path = Path(path); Node::from_branch(child) } @@ -805,7 +799,7 @@ impl Merkle { .chain(child.partial_path.0.iter().copied()) .collect(); - child.partial_path = PartialPath(path); + child.partial_path = Path(path); Node::from_leaf(child) } @@ -821,7 +815,7 @@ impl Merkle { // branch nodes shouldn't have no children (0, Some(data), true) => { let leaf = Node::from_leaf(LeafNode::new( - PartialPath(branch.partial_path.0.clone()), + Path(branch.partial_path.0.clone()), data.clone(), )); @@ -1215,7 +1209,7 @@ impl Merkle { &'a self, (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), mut node: NodeObjRef<'a>, - path: PartialPath, + path: Path, ) -> Result, MerkleError> { let write_result = node.write(|node| { node.inner_mut().set_path(path); @@ -1349,10 +1343,10 @@ pub const fn to_nibble_array(x: u8) -> [u8; 2] { [x >> 4, x & 0b_0000_1111] } -// given a set of nibbles, take each pair and convert this back into bytes -// if an odd number of nibbles, in debug mode it panics. In release mode, -// the final nibble is dropped -pub fn from_nibbles(nibbles: &[u8]) -> impl Iterator + '_ { +/// Returns an iterator where each element is the result of combining +/// 2 nibbles of `nibbles`. If `nibbles` is odd length, panics in +/// debug mode and drops the final nibble in release mode. +pub fn nibbles_to_bytes_iter(nibbles: &[u8]) -> impl Iterator + '_ { debug_assert_eq!(nibbles.len() & 1, 0); #[allow(clippy::indexing_slicing)] nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) @@ -1402,7 +1396,7 @@ mod tests { use test_case::test_case; fn leaf(path: Vec, data: Vec) -> Node { - Node::from_leaf(LeafNode::new(PartialPath(path), Data(data))) + Node::from_leaf(LeafNode::new(Path(path), Data(data))) } #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] @@ -1454,7 +1448,7 @@ mod tests { fn branch(path: &[u8], value: &[u8], encoded_child: Option>) -> Node { let (path, value) = (path.to_vec(), value.to_vec()); let path = Nibbles::<0>::new(&path); - let path = PartialPath(path.into_iter().collect()); + let path = Path(path.into_iter().collect()); let children = Default::default(); // TODO: Properly test empty data as a value @@ -1476,7 +1470,7 @@ mod tests { fn branch_without_data(path: &[u8], encoded_child: Option>) -> Node { let path = path.to_vec(); let path = Nibbles::<0>::new(&path); - let path = PartialPath(path.into_iter().collect()); + let path = Path(path.into_iter().collect()); let children = Default::default(); // TODO: Properly test empty data as a value @@ -2089,7 +2083,7 @@ mod tests { .collect::>(); let node = Node::from_leaf(LeafNode { - partial_path: PartialPath::from(path), + partial_path: Path::from(path), data: Data(data.clone()), }); @@ -2108,7 +2102,7 @@ mod tests { .collect::>(); let node = Node::from_leaf(LeafNode { - partial_path: PartialPath::from(path.clone()), + partial_path: Path::from(path.clone()), data: Data(data), }); @@ -2127,7 +2121,7 @@ mod tests { .collect::>(); let node = Node::from_branch(BranchNode { - partial_path: PartialPath::from(path.clone()), + partial_path: Path::from(path.clone()), children: Default::default(), value: Some(Data(data.clone())), children_encoded: Default::default(), @@ -2148,7 +2142,7 @@ mod tests { .collect::>(); let node = Node::from_branch(BranchNode { - partial_path: PartialPath::from(path.clone()), + partial_path: Path::from(path.clone()), children: Default::default(), value: Some(Data(data)), children_encoded: Default::default(), @@ -2171,7 +2165,7 @@ mod tests { // make sure that doubling the path length will fail on a normal write let write_result = node_ref.write(|node| { - node.inner_mut().set_path(PartialPath(new_path.clone())); + node.inner_mut().set_path(Path(new_path.clone())); node.inner_mut().set_data(Data(new_data.clone())); node.rehash(); }); @@ -2185,7 +2179,7 @@ mod tests { let node = merkle.update_path_and_move_node_if_larger( (&mut parents, &mut to_delete), node_ref, - PartialPath(new_path.clone()), + Path(new_path.clone()), )?; assert_ne!(node.as_ptr(), addr); @@ -2196,7 +2190,7 @@ mod tests { NodeType::Branch(branch) => (&branch.partial_path, branch.value.as_ref()), }; - assert_eq!(path, &PartialPath(new_path)); + assert_eq!(path, &Path(new_path)); assert_eq!(data, Some(&Data(new_data))); Ok(()) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 1b03a3c06062..03531b13b4b1 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -3,7 +3,7 @@ use crate::{ logger::trace, - merkle::from_nibbles, + merkle::nibbles_to_bytes_iter, shale::{compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleError, Storable}, }; use bincode::{Error, Options}; @@ -29,11 +29,11 @@ use std::{ mod branch; mod leaf; -mod partial_path; +mod path; pub use branch::BranchNode; pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; -pub use partial_path::PartialPath; +pub use path::Path; use crate::nibbles::Nibbles; @@ -87,7 +87,7 @@ impl NodeType { let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); - let cur_key_path = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); + let cur_key_path = Path::from_nibbles(decoded_key_nibbles.into_iter()); let cur_key = cur_key_path.into_inner(); #[allow(clippy::unwrap_used)] @@ -110,14 +110,14 @@ impl NodeType { } } - pub fn path_mut(&mut self) -> &mut PartialPath { + pub fn path_mut(&mut self) -> &mut Path { match self { NodeType::Branch(u) => &mut u.partial_path, NodeType::Leaf(node) => &mut node.partial_path, } } - pub fn set_path(&mut self, path: PartialPath) { + pub fn set_path(&mut self, path: Path) { match self { NodeType::Branch(u) => u.partial_path = path, NodeType::Leaf(node) => node.partial_path = path, @@ -500,7 +500,7 @@ impl EncodedNode { pub enum EncodedNodeType { Leaf(LeafNode), Branch { - path: PartialPath, + path: Path, children: Box<[Option>; BranchNode::MAX_CHILDREN]>, value: Option, }, @@ -524,7 +524,7 @@ impl Serialize for EncodedNode { EncodedNodeType::Leaf(n) => { let data = Some(&*n.data); let chd: Vec<(u64, Vec)> = Default::default(); - let path: Vec<_> = from_nibbles(&n.partial_path.encode()).collect(); + let path: Vec<_> = nibbles_to_bytes_iter(&n.partial_path.encode()).collect(); (chd, data, path) } @@ -548,7 +548,7 @@ impl Serialize for EncodedNode { let data = value.as_deref(); - let path = from_nibbles(&path.encode()).collect(); + let path = nibbles_to_bytes_iter(&path.encode()).collect(); (chd, data, path) } @@ -571,7 +571,7 @@ impl<'de> Deserialize<'de> for EncodedNode { { let EncodedBranchNode { chd, data, path } = Deserialize::deserialize(deserializer)?; - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); + let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); if chd.is_empty() { let data = if let Some(d) = data { @@ -612,7 +612,7 @@ impl Serialize for EncodedNode { match &self.node { EncodedNodeType::Leaf(n) => { let list = [ - from_nibbles(&n.partial_path.encode()).collect(), + nibbles_to_bytes_iter(&n.partial_path.encode()).collect(), n.data.to_vec(), ]; let mut seq = serializer.serialize_seq(Some(list.len()))?; @@ -647,7 +647,7 @@ impl Serialize for EncodedNode { list[BranchNode::MAX_CHILDREN] = val.clone(); } - let serialized_path = from_nibbles(&path.encode()).collect(); + let serialized_path = nibbles_to_bytes_iter(&path.encode()).collect(); list[BranchNode::MAX_CHILDREN + 1] = serialized_path; let mut seq = serializer.serialize_seq(Some(list.len()))?; @@ -685,7 +685,7 @@ impl<'de> Deserialize<'de> for EncodedNode { "incorrect encoded type for leaf node data", )); }; - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); + let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let node = EncodedNodeType::Leaf(LeafNode { partial_path: path, data: Data(data), @@ -695,7 +695,7 @@ impl<'de> Deserialize<'de> for EncodedNode { BranchNode::MSIZE => { let path = items.pop().expect("length was checked above"); - let path = PartialPath::from_nibbles(Nibbles::<0>::new(&path).into_iter()); + let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let value = items.pop().expect("length was checked above"); let value = if value.is_empty() { @@ -832,7 +832,7 @@ mod tests { encoded: impl Into>>, is_encoded_longer_than_hash_len: impl Into>, ) { - let leaf = NodeType::Leaf(LeafNode::new(PartialPath(vec![1, 2, 3]), Data(vec![4, 5]))); + let leaf = NodeType::Leaf(LeafNode::new(Path(vec![1, 2, 3]), Data(vec![4, 5]))); let branch = NodeType::Branch(Box::new(BranchNode { partial_path: vec![].into(), children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], @@ -869,7 +869,7 @@ mod tests { )] fn leaf_node>(path: Iter, data: Iter) { let node = Node::from_leaf(LeafNode::new( - PartialPath(path.map(|x| x & 0xf).collect()), + Path(path.map(|x| x & 0xf).collect()), Data(data.collect()), )); @@ -886,7 +886,7 @@ mod tests { #[test_case(&[0x0F,0x01,0x0F])] fn encoded_branch_node_bincode_serialize(path_nibbles: &[u8]) -> Result<(), Error> { let node = EncodedNode::::new(EncodedNodeType::Branch { - path: PartialPath(path_nibbles.to_vec()), + path: Path(path_nibbles.to_vec()), children: Default::default(), value: Some(Data(vec![1, 2, 3, 4])), }); @@ -918,7 +918,7 @@ mod tests { value: impl Into>, children_encoded: [Option>; BranchNode::MAX_CHILDREN], ) { - let partial_path = PartialPath(path.iter().copied().map(|x| x & 0xf).collect()); + let partial_path = Path(path.iter().copied().map(|x| x & 0xf).collect()); let mut children = children.into_iter().map(|x| { if x == 0 { diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index dbccbff4a4da..060bd665e194 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -3,7 +3,7 @@ use super::{Data, Node}; use crate::{ - merkle::{from_nibbles, to_nibble_array, PartialPath}, + merkle::{nibbles_to_bytes_iter, to_nibble_array, Path}, nibbles::Nibbles, shale::{compact::CompactSpace, CachedStore, DiskAddress, ShaleError, Storable}, }; @@ -23,7 +23,7 @@ const MAX_CHILDREN: usize = 16; #[derive(PartialEq, Eq, Clone)] pub struct BranchNode { - pub(crate) partial_path: PartialPath, + pub(crate) partial_path: Path, pub(crate) children: [Option; MAX_CHILDREN], pub(crate) value: Option, pub(crate) children_encoded: [Option>; MAX_CHILDREN], @@ -62,7 +62,7 @@ impl BranchNode { pub const MSIZE: usize = Self::MAX_CHILDREN + 2; pub fn new( - partial_path: PartialPath, + partial_path: Path, chd: [Option; Self::MAX_CHILDREN], value: Option>, chd_encoded: [Option>; Self::MAX_CHILDREN], @@ -100,7 +100,7 @@ impl BranchNode { let path = items.pop().ok_or(Error::custom("Invalid Branch Node"))?; let path = Nibbles::<0>::new(&path); - let path = PartialPath::from_nibbles(path.into_iter()); + let path = Path::from_nibbles(path.into_iter()); // we've already validated the size, that's why we can safely unwrap #[allow(clippy::unwrap_used)] @@ -176,7 +176,7 @@ impl BranchNode { } #[allow(clippy::unwrap_used)] - let path = from_nibbles(&self.partial_path.encode()).collect::>(); + let path = nibbles_to_bytes_iter(&self.partial_path.encode()).collect::>(); list[Self::MAX_CHILDREN + 1] = path; @@ -202,7 +202,7 @@ impl Storable for BranchNode { fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { let mut cursor = Cursor::new(to); - let path: Vec = from_nibbles(&self.partial_path.encode()).collect(); + let path: Vec = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); cursor.write_all(&[path.len() as PathLen])?; cursor.write_all(&path)?; @@ -271,7 +271,7 @@ impl Storable for BranchNode { addr += path_len as usize; let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); - let path = PartialPath::decode(&path); + let path = Path::decode(&path); let node_raw = mem.get_view(addr, BRANCH_HEADER_SIZE) diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index d18d06eaf90e..1dcc5f1ac270 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -3,7 +3,7 @@ use super::Data; use crate::{ - merkle::{from_nibbles, PartialPath}, + merkle::{nibbles_to_bytes_iter, Path}, nibbles::Nibbles, shale::{ShaleError::InvalidCacheView, Storable}, }; @@ -22,7 +22,7 @@ type DataLen = u32; #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { - pub(crate) partial_path: PartialPath, + pub(crate) partial_path: Path, pub(crate) data: Data, } @@ -38,14 +38,14 @@ impl Debug for LeafNode { } impl LeafNode { - pub fn new, D: Into>(partial_path: P, data: D) -> Self { + pub fn new, D: Into>(partial_path: P, data: D) -> Self { Self { partial_path: partial_path.into(), data: data.into(), } } - pub const fn path(&self) -> &PartialPath { + pub const fn path(&self) -> &Path { &self.partial_path } @@ -58,7 +58,7 @@ impl LeafNode { bincode::DefaultOptions::new() .serialize( [ - from_nibbles(&self.partial_path.encode()).collect(), + nibbles_to_bytes_iter(&self.partial_path.encode()).collect(), self.data.to_vec(), ] .as_slice(), @@ -91,7 +91,7 @@ impl Storable for LeafNode { let mut cursor = Cursor::new(to); let path = &self.partial_path.encode(); - let path = from_nibbles(path); + let path = nibbles_to_bytes_iter(path); let data = &self.data; let path_len = self.partial_path.serialized_len() as PathLen; @@ -138,7 +138,7 @@ impl Storable for LeafNode { let path = { let nibbles = Nibbles::<0>::new(path).into_iter(); - PartialPath::from_nibbles(nibbles).0 + Path::from_nibbles(nibbles).0 }; let data = Data(data.to_vec()); diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/path.rs similarity index 80% rename from firewood/src/merkle/node/partial_path.rs rename to firewood/src/merkle/node/path.rs index fe1583b3d7c5..f83888a18bce 100644 --- a/firewood/src/merkle/node/partial_path.rs +++ b/firewood/src/merkle/node/path.rs @@ -9,11 +9,12 @@ use std::{ }; // TODO: use smallvec -/// PartialPath keeps a list of nibbles to represent a path on the Trie. +/// Path is part or all of a node's path in the trie. +/// Each element is a nibble. #[derive(PartialEq, Eq, Clone)] -pub struct PartialPath(pub Vec); +pub struct Path(pub Vec); -impl Debug for PartialPath { +impl Debug for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { for nib in self.0.iter() { write!(f, "{:x}", *nib & 0xf)?; @@ -22,20 +23,20 @@ impl Debug for PartialPath { } } -impl std::ops::Deref for PartialPath { +impl std::ops::Deref for Path { type Target = [u8]; fn deref(&self) -> &[u8] { &self.0 } } -impl From> for PartialPath { +impl From> for Path { fn from(value: Vec) -> Self { Self(value) } } -impl PartialPath { +impl Path { pub fn into_inner(self) -> Vec { self.0 } @@ -60,14 +61,14 @@ impl PartialPath { } // TODO: remove all non `Nibbles` usages and delete this function. - // I also think `PartialPath` could probably borrow instead of own data. + // I also think `Path` could probably borrow instead of own data. // - /// returns a tuple of the decoded partial path and whether the path is terminal + /// Returns the decoded path. pub fn decode(raw: &[u8]) -> Self { Self::from_iter(raw.iter().copied()) } - /// returns a tuple of the decoded partial path and whether the path is terminal + /// Returns the decoded path. pub fn from_nibbles(nibbles: NibblesIterator<'_, N>) -> Self { Self::from_iter(nibbles) } @@ -109,12 +110,12 @@ mod tests { #[test_case(&[1, 2])] #[test_case(&[1])] fn test_encoding(steps: &[u8]) { - let path = PartialPath(steps.to_vec()); + let path = Path(steps.to_vec()); let encoded = path.encode(); assert_eq!(encoded.len(), path.serialized_len() as usize * 2); - let decoded = PartialPath::decode(&encoded); + let decoded = Path::decode(&encoded); assert_eq!(&&*decoded, &steps); } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 8e2c70239768..da506eaa7442 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -12,7 +12,7 @@ use std::sync::{Arc, RwLock, RwLockWriteGuard}; use thiserror::Error; -use crate::merkle::{LeafNode, Node, PartialPath}; +use crate::merkle::{LeafNode, Node, Path}; pub mod cached; pub mod compact; @@ -144,7 +144,7 @@ impl Obj { impl Obj { pub fn into_inner(mut self) -> Node { let empty_node = LeafNode { - partial_path: PartialPath(Vec::new()), + partial_path: Path(Vec::new()), data: Vec::new().into(), }; From 796c80c96fbefd8042745c0c139a9e3e58d39f38 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 14 Mar 2024 13:16:50 -0400 Subject: [PATCH 0509/1053] remove `EncodedNodeType` (#589) --- firewood/src/merkle.rs | 84 +++++++------ firewood/src/merkle/node.rs | 245 ++++++++++++++---------------------- 2 files changed, 140 insertions(+), 189 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 23f76c6cbceb..812a24b3370b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -19,8 +19,7 @@ mod stream; mod trie_hash; pub use node::{ - BinarySerde, Bincode, BranchNode, Data, EncodedNode, EncodedNodeType, LeafNode, Node, NodeType, - Path, + BinarySerde, Bincode, BranchNode, Data, EncodedNode, LeafNode, Node, NodeType, Path, }; pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; @@ -115,11 +114,17 @@ where #[allow(dead_code)] fn encode(&self, node: &NodeType) -> Result, MerkleError> { let encoded = match node { - NodeType::Leaf(n) => EncodedNode::new(EncodedNodeType::Leaf(n.clone())), + NodeType::Leaf(n) => { + let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + EncodedNode { + partial_path: n.partial_path.clone(), + children: Box::new(children), + value: n.data.clone().into(), + phantom: PhantomData, + } + } NodeType::Branch(n) => { - let path = n.partial_path.clone(); - // pair up DiskAddresses with encoded children and pick the right one let encoded_children = n.chd().iter().zip(n.children_encoded.iter()); let children = encoded_children @@ -138,11 +143,13 @@ where .try_into() .expect("MAX_CHILDREN will always be yielded"); - EncodedNode::new(EncodedNodeType::Branch { - path, + let value = n.value.as_ref().map(|v| v.0.clone()); + EncodedNode { + partial_path: n.partial_path.clone(), children, - value: n.value.clone(), - }) + value, + phantom: PhantomData, + } } }; @@ -154,23 +161,23 @@ where let encoded: EncodedNode = T::deserialize(buf).map_err(|e| MerkleError::BinarySerdeError(e.to_string()))?; - match encoded.node { - EncodedNodeType::Leaf(leaf) => Ok(NodeType::Leaf(leaf)), - EncodedNodeType::Branch { - path, - children, - value, - } => { - let path = Path::decode(&path); - let value = value.map(|v| v.0); - let branch = NodeType::Branch( - BranchNode::new(path, [None; BranchNode::MAX_CHILDREN], value, *children) - .into(), - ); - - Ok(branch) - } + if encoded.children.iter().all(|b| b.is_none()) { + // This is a leaf node + return Ok(NodeType::Leaf(LeafNode::new( + encoded.partial_path, + Data(encoded.value.expect("leaf nodes must always have a value")), + ))); } + + Ok(NodeType::Branch( + BranchNode::new( + encoded.partial_path, + [None; BranchNode::MAX_CHILDREN], + encoded.value, + *encoded.children, + ) + .into(), + )) } } @@ -1451,8 +1458,11 @@ mod tests { let path = Path(path.into_iter().collect()); let children = Default::default(); - // TODO: Properly test empty data as a value - let value = Some(Data(value)); + let value = if value.is_empty() { + None + } else { + Some(Data(value)) + }; let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { @@ -1513,16 +1523,16 @@ mod tests { assert_eq!(encoded, new_node_encoded); } - #[test_case(Bincode::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with Bincode")] - #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with Bincode")] - #[test_case(Bincode::new(), branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd with Bincode")] - #[test_case(Bincode::new(), branch(b"", b"value", None); "branch without chd with Bincode")] - #[test_case(Bincode::new(), branch_without_data(b"", None); "branch without value and chd with Bincode")] - #[test_case(PlainCodec::new(), leaf(Vec::new(), Vec::new()) ; "empty leaf encoding with PlainCodec")] - #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"", b"value", Some(Vec::new())); "branch with empty chd with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"", b"", vec![1, 2, 3].into()); "branch with empty value with PlainCodec")] + #[test_case(Bincode::new(), leaf(vec![], vec![4, 5]) ; "leaf without partial path encoding with Bincode")] + #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with Bincode")] + #[test_case(Bincode::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with Bincode")] + #[test_case(Bincode::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with Bincode")] + #[test_case(Bincode::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with Bincode")] + #[test_case(PlainCodec::new(), leaf(Vec::new(), vec![4, 5]) ; "leaf without partial path encoding with PlainCodec")] + #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with PlainCodec")] + #[test_case(PlainCodec::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with PlainCodec")] fn node_encode_decode(_codec: T, node: Node) where T: BinarySerde, diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 03531b13b4b1..78152f395467 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -56,6 +56,12 @@ impl std::ops::Deref for Data { } } +impl From for Option> { + fn from(val: Data) -> Self { + Some(val.0) + } +} + impl From> for Data { fn from(v: Vec) -> Self { Self(v) @@ -482,82 +488,55 @@ impl Storable for Node { } } +/// Contains the fields that we include in a node's hash. +/// If this is a leaf node, `children` is empty and `value` is Some. +/// If this is a branch node, `children` is non-empty. +#[derive(Debug)] pub struct EncodedNode { - pub(crate) node: EncodedNodeType, + pub(crate) partial_path: Path, + /// If a child is None, it doesn't exist. + /// If it's Some, it's the value or value hash of the child. + pub(crate) children: Box<[Option>; BranchNode::MAX_CHILDREN]>, + pub(crate) value: Option>, pub(crate) phantom: PhantomData, } -impl EncodedNode { - pub const fn new(node: EncodedNodeType) -> Self { - Self { - node, - phantom: PhantomData, - } +impl PartialEq for EncodedNode { + fn eq(&self, other: &Self) -> bool { + self.partial_path == other.partial_path + && self.children == other.children + && self.value == other.value } } -#[derive(Debug, PartialEq)] -pub enum EncodedNodeType { - Leaf(LeafNode), - Branch { - path: Path, - children: Box<[Option>; BranchNode::MAX_CHILDREN]>, - value: Option, - }, -} - -// TODO: probably can merge with `EncodedNodeType`. -#[derive(Debug, Deserialize)] -struct EncodedBranchNode { - chd: Vec<(u64, Vec)>, - data: Option>, - path: Vec, -} - // Note that the serializer passed in should always be the same type as T in EncodedNode. impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let (chd, data, path) = match &self.node { - EncodedNodeType::Leaf(n) => { - let data = Some(&*n.data); - let chd: Vec<(u64, Vec)> = Default::default(); - let path: Vec<_> = nibbles_to_bytes_iter(&n.partial_path.encode()).collect(); - (chd, data, path) - } + let chd: Vec<(u64, Vec)> = self + .children + .iter() + .enumerate() + .filter_map(|(i, c)| c.as_ref().map(|c| (i as u64, c))) + .map(|(i, c)| { + if c.len() >= TRIE_HASH_LEN { + (i, Keccak256::digest(c).to_vec()) + } else { + (i, c.to_vec()) + } + }) + .collect(); - EncodedNodeType::Branch { - path, - children, - value, - } => { - let chd: Vec<(u64, Vec)> = children - .iter() - .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|c| (i as u64, c))) - .map(|(i, c)| { - if c.len() >= TRIE_HASH_LEN { - (i, Keccak256::digest(c).to_vec()) - } else { - (i, c.to_vec()) - } - }) - .collect(); - - let data = value.as_deref(); - - let path = nibbles_to_bytes_iter(&path.encode()).collect(); - - (chd, data, path) - } - }; + let value = self.value.as_deref(); + + let path: Vec = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); let mut s = serializer.serialize_tuple(3)?; s.serialize_element(&chd)?; - s.serialize_element(&data)?; + s.serialize_element(&value)?; s.serialize_element(&path)?; s.end() @@ -569,96 +548,63 @@ impl<'de> Deserialize<'de> for EncodedNode { where D: serde::Deserializer<'de>, { - let EncodedBranchNode { chd, data, path } = Deserialize::deserialize(deserializer)?; - - let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - - if chd.is_empty() { - let data = if let Some(d) = data { - Data(d) - } else { - Data(Vec::new()) - }; - - let node = EncodedNodeType::Leaf(LeafNode { - partial_path: path, - data, - }); - - Ok(Self::new(node)) - } else { - let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - let value = data.map(Data); + let chd: Vec<(u64, Vec)>; + let value: Option>; + let path: Vec; - #[allow(clippy::indexing_slicing)] - for (i, chd) in chd { - children[i as usize] = Some(chd); - } + (chd, value, path) = Deserialize::deserialize(deserializer)?; - let node = EncodedNodeType::Branch { - path, - children: children.into(), - value, - }; + let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - Ok(Self::new(node)) + let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + #[allow(clippy::indexing_slicing)] + for (i, chd) in chd { + children[i as usize] = Some(chd); } + + Ok(Self { + partial_path: path, + children: children.into(), + value, + phantom: PhantomData, + }) } } // Note that the serializer passed in should always be the same type as T in EncodedNode. impl Serialize for EncodedNode { fn serialize(&self, serializer: S) -> Result { - match &self.node { - EncodedNodeType::Leaf(n) => { - let list = [ - nibbles_to_bytes_iter(&n.partial_path.encode()).collect(), - n.data.to_vec(), - ]; - let mut seq = serializer.serialize_seq(Some(list.len()))?; - for e in list { - seq.serialize_element(&e)?; - } - seq.end() + let mut list = <[Vec; BranchNode::MAX_CHILDREN + 2]>::default(); + let children = self + .children + .iter() + .enumerate() + .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))); + + #[allow(clippy::indexing_slicing)] + for (i, child) in children { + if child.len() >= TRIE_HASH_LEN { + let serialized_hash = Keccak256::digest(child).to_vec(); + list[i] = serialized_hash; + } else { + list[i] = child.to_vec(); } + } - EncodedNodeType::Branch { - path, - children, - value, - } => { - let mut list = <[Vec; BranchNode::MAX_CHILDREN + 2]>::default(); - let children = children - .iter() - .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))); - - #[allow(clippy::indexing_slicing)] - for (i, child) in children { - if child.len() >= TRIE_HASH_LEN { - let serialized_hash = Keccak256::digest(child).to_vec(); - list[i] = serialized_hash; - } else { - list[i] = child.to_vec(); - } - } - - if let Some(Data(val)) = &value { - list[BranchNode::MAX_CHILDREN] = val.clone(); - } - - let serialized_path = nibbles_to_bytes_iter(&path.encode()).collect(); - list[BranchNode::MAX_CHILDREN + 1] = serialized_path; + if let Some(val) = &self.value { + list[BranchNode::MAX_CHILDREN] = val.clone(); + } - let mut seq = serializer.serialize_seq(Some(list.len()))?; + let serialized_path = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); + list[BranchNode::MAX_CHILDREN + 1] = serialized_path; - for e in list { - seq.serialize_element(&e)?; - } + let mut seq = serializer.serialize_seq(Some(list.len()))?; - seq.end() - } + for e in list { + seq.serialize_element(&e)?; } + + seq.end() } } @@ -686,11 +632,13 @@ impl<'de> Deserialize<'de> for EncodedNode { )); }; let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - let node = EncodedNodeType::Leaf(LeafNode { + let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); + Ok(Self { partial_path: path, - data: Data(data), - }); - Ok(Self::new(node)) + children: children.into(), + value: Some(data), + phantom: PhantomData, + }) } BranchNode::MSIZE => { @@ -698,11 +646,7 @@ impl<'de> Deserialize<'de> for EncodedNode { let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); let value = items.pop().expect("length was checked above"); - let value = if value.is_empty() { - None - } else { - Some(Data(value)) - }; + let value = if value.is_empty() { None } else { Some(value) }; let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); @@ -711,14 +655,10 @@ impl<'de> Deserialize<'de> for EncodedNode { (children[i] = Some(chd).filter(|chd| !chd.is_empty())); } - let node = EncodedNodeType::Branch { - path, + Ok(Self { + partial_path: path, children: children.into(), value, - }; - - Ok(Self { - node, phantom: PhantomData, }) } @@ -885,17 +825,18 @@ mod tests { #[test_case(&[0x0F,0x0F])] #[test_case(&[0x0F,0x01,0x0F])] fn encoded_branch_node_bincode_serialize(path_nibbles: &[u8]) -> Result<(), Error> { - let node = EncodedNode::::new(EncodedNodeType::Branch { - path: Path(path_nibbles.to_vec()), + let node = EncodedNode:: { + partial_path: Path(path_nibbles.to_vec()), children: Default::default(), - value: Some(Data(vec![1, 2, 3, 4])), - }); + value: Some(vec![1, 2, 3, 4]), + phantom: PhantomData, + }; let node_bytes = Bincode::serialize(&node)?; let deserialized_node: EncodedNode = Bincode::deserialize(&node_bytes)?; - assert_eq!(&node.node, &deserialized_node.node); + assert_eq!(&node, &deserialized_node); Ok(()) } From 0edc6e34bcd343513c8e445ba6cc1215ba0acc3d Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 15 Mar 2024 09:24:54 -0400 Subject: [PATCH 0510/1053] remove `Data` type (#590) --- firewood/src/merkle.rs | 76 +++++++++++++----------------- firewood/src/merkle/node.rs | 40 +++------------- firewood/src/merkle/node/branch.rs | 34 ++++--------- firewood/src/merkle/node/leaf.rs | 9 ++-- firewood/src/merkle/proof.rs | 2 +- firewood/src/merkle/stream.rs | 8 ++-- firewood/src/shale/mod.rs | 2 +- 7 files changed, 59 insertions(+), 112 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 812a24b3370b..216969776e42 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -18,9 +18,7 @@ pub mod proof; mod stream; mod trie_hash; -pub use node::{ - BinarySerde, Bincode, BranchNode, Data, EncodedNode, LeafNode, Node, NodeType, Path, -}; +pub use node::{BinarySerde, Bincode, BranchNode, EncodedNode, LeafNode, Node, NodeType, Path}; pub use proof::{Proof, ProofError}; pub use stream::MerkleKeyValueStream; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; @@ -143,11 +141,10 @@ where .try_into() .expect("MAX_CHILDREN will always be yielded"); - let value = n.value.as_ref().map(|v| v.0.clone()); EncodedNode { partial_path: n.partial_path.clone(), children, - value, + value: n.value.clone(), phantom: PhantomData, } } @@ -165,17 +162,17 @@ where // This is a leaf node return Ok(NodeType::Leaf(LeafNode::new( encoded.partial_path, - Data(encoded.value.expect("leaf nodes must always have a value")), + encoded.value.expect("leaf nodes must always have a value"), ))); } Ok(NodeType::Branch( - BranchNode::new( - encoded.partial_path, - [None; BranchNode::MAX_CHILDREN], - encoded.value, - *encoded.children, - ) + BranchNode { + partial_path: encoded.partial_path, + children: [None; BranchNode::MAX_CHILDREN], + value: encoded.value, + children_encoded: *encoded.children, + } .into(), )) } @@ -326,7 +323,7 @@ impl Merkle { self.update_data_and_move_node_if_larger( (&mut parents, &mut deleted), node, - Data(val), + val, )?; } @@ -337,8 +334,7 @@ impl Merkle { (index[0], path.to_vec()) }; - let new_leaf = - Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); + let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); let new_leaf = self.put_node(new_leaf)?.as_ptr(); @@ -381,7 +377,7 @@ impl Merkle { let mut new_branch = BranchNode { partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], - value: Some(val.into()), + value: Some(val), children_encoded: Default::default(), }; @@ -415,8 +411,7 @@ impl Merkle { )? .as_ptr(); - let new_leaf = - Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); + let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); let new_leaf = self.put_node(new_leaf)?.as_ptr(); @@ -450,7 +445,7 @@ impl Merkle { let leaf_ptr = self .put_node(Node::from_leaf(LeafNode::new( Path(key_nibbles.collect()), - Data(val), + val, )))? .as_ptr(); @@ -482,7 +477,7 @@ impl Merkle { self.update_data_and_move_node_if_larger( (&mut parents, &mut deleted), node, - Data(val), + val, )?; break None; } @@ -505,7 +500,7 @@ impl Merkle { None => { let new_leaf = Node::from_leaf(LeafNode::new( Path(new_leaf_path.to_vec()), - Data(val), + val, )); let new_leaf = self.put_node(new_leaf)?.as_ptr(); @@ -542,7 +537,7 @@ impl Merkle { let mut new_branch = BranchNode { partial_path: Path(new_branch_path), children: [None; BranchNode::MAX_CHILDREN], - value: Some(val.into()), + value: Some(val), children_encoded: Default::default(), }; @@ -578,8 +573,7 @@ impl Merkle { )? .as_ptr(); - let new_leaf = - Node::from_leaf(LeafNode::new(Path(new_leaf_path), Data(val))); + let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); let new_leaf = self.put_node(new_leaf)?.as_ptr(); @@ -620,12 +614,12 @@ impl Merkle { |u| { info = match &mut u.inner { NodeType::Branch(n) => { - n.value = Some(Data(val)); + n.value = Some(val); None } NodeType::Leaf(n) => { if n.partial_path.len() == 0 { - n.data = Data(val); + n.data = val; None } else { @@ -667,7 +661,7 @@ impl Merkle { .put_node(Node::from_branch(BranchNode { partial_path: vec![].into(), children: chd, - value: Some(Data(val)), + value: Some(val), children_encoded: Default::default(), }))? .as_ptr(); @@ -850,7 +844,7 @@ impl Merkle { self.free_node(ptr)?; } - Ok(data.map(|data| data.0)) + Ok(data) } fn remove_tree_( @@ -1232,7 +1226,7 @@ impl Merkle { &'a self, (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), mut node: NodeObjRef<'a>, - data: Data, + data: Vec, ) -> Result { let write_result = node.write(|node| { node.inner_mut().set_data(data); @@ -1329,8 +1323,8 @@ impl<'a, S: CachedStore, T> RefMut<'a, S, T> { |u| { #[allow(clippy::unwrap_used)] modify(match &mut u.inner { - NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, - NodeType::Leaf(n) => &mut n.data.0, + NodeType::Branch(n) => n.value.as_mut().unwrap(), + NodeType::Leaf(n) => &mut n.data, }); u.rehash() }, @@ -1403,7 +1397,7 @@ mod tests { use test_case::test_case; fn leaf(path: Vec, data: Vec) -> Node { - Node::from_leaf(LeafNode::new(Path(path), Data(data))) + Node::from_leaf(LeafNode::new(Path(path), data)) } #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] @@ -1458,11 +1452,7 @@ mod tests { let path = Path(path.into_iter().collect()); let children = Default::default(); - let value = if value.is_empty() { - None - } else { - Some(Data(value)) - }; + let value = if value.is_empty() { None } else { Some(value) }; let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { @@ -2094,7 +2084,7 @@ mod tests { let node = Node::from_leaf(LeafNode { partial_path: Path::from(path), - data: Data(data.clone()), + data: data.clone(), }); check_node_update(node, double_path, data) @@ -2113,7 +2103,7 @@ mod tests { let node = Node::from_leaf(LeafNode { partial_path: Path::from(path.clone()), - data: Data(data), + data, }); check_node_update(node, path, double_data) @@ -2133,7 +2123,7 @@ mod tests { let node = Node::from_branch(BranchNode { partial_path: Path::from(path.clone()), children: Default::default(), - value: Some(Data(data.clone())), + value: Some(data.clone()), children_encoded: Default::default(), }); @@ -2154,7 +2144,7 @@ mod tests { let node = Node::from_branch(BranchNode { partial_path: Path::from(path.clone()), children: Default::default(), - value: Some(Data(data)), + value: Some(data), children_encoded: Default::default(), }); @@ -2176,7 +2166,7 @@ mod tests { // make sure that doubling the path length will fail on a normal write let write_result = node_ref.write(|node| { node.inner_mut().set_path(Path(new_path.clone())); - node.inner_mut().set_data(Data(new_data.clone())); + node.inner_mut().set_data(new_data.clone()); node.rehash(); }); @@ -2201,7 +2191,7 @@ mod tests { }; assert_eq!(path, &Path(new_path)); - assert_eq!(data, Some(&Data(new_data))); + assert_eq!(data, Some(&new_data)); Ok(()) } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 78152f395467..a32d9483013d 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -46,34 +46,6 @@ bitflags! { } } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Data(pub(super) Vec); - -impl std::ops::Deref for Data { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl From for Option> { - fn from(val: Data) -> Self { - Some(val.0) - } -} - -impl From> for Data { - fn from(v: Vec) -> Self { - Self(v) - } -} - -impl Data { - pub fn into_inner(self) -> Vec { - self.0 - } -} - #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] pub enum NodeType { Branch(Box), @@ -130,7 +102,7 @@ impl NodeType { } } - pub fn set_data(&mut self, data: Data) { + pub fn set_data(&mut self, data: Vec) { match self { NodeType::Branch(u) => u.value = Some(data), NodeType::Leaf(node) => node.data = data, @@ -218,7 +190,7 @@ impl Node { BranchNode { partial_path: vec![].into(), children: [Some(DiskAddress::null()); BranchNode::MAX_CHILDREN], - value: Some(Data(Vec::new())), + value: Some(Vec::new()), children_encoded: Default::default(), } .into(), @@ -772,11 +744,11 @@ mod tests { encoded: impl Into>>, is_encoded_longer_than_hash_len: impl Into>, ) { - let leaf = NodeType::Leaf(LeafNode::new(Path(vec![1, 2, 3]), Data(vec![4, 5]))); + let leaf = NodeType::Leaf(LeafNode::new(Path(vec![1, 2, 3]), vec![4, 5])); let branch = NodeType::Branch(Box::new(BranchNode { partial_path: vec![].into(), children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], - value: Some(Data(vec![1, 2, 3])), + value: Some(vec![1, 2, 3]), children_encoded: std::array::from_fn(|_| Some(vec![1])), })); @@ -810,7 +782,7 @@ mod tests { fn leaf_node>(path: Iter, data: Iter) { let node = Node::from_leaf(LeafNode::new( Path(path.map(|x| x & 0xf).collect()), - Data(data.collect()), + data.collect::>(), )); check_node_encoding(node); @@ -873,7 +845,7 @@ mod tests { let value = value .into() - .map(|x| Data(std::iter::repeat(x).take(x as usize).collect())); + .map(|x| std::iter::repeat(x).take(x as usize).collect()); let node = Node::from_branch(BranchNode { partial_path, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 060bd665e194..0159f0ea876e 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Data, Node}; +use super::Node; use crate::{ merkle::{nibbles_to_bytes_iter, to_nibble_array, Path}, nibbles::Nibbles, @@ -25,7 +25,7 @@ const MAX_CHILDREN: usize = 16; pub struct BranchNode { pub(crate) partial_path: Path, pub(crate) children: [Option; MAX_CHILDREN], - pub(crate) value: Option, + pub(crate) value: Option>, pub(crate) children_encoded: [Option>; MAX_CHILDREN], } @@ -61,21 +61,7 @@ impl BranchNode { pub const MAX_CHILDREN: usize = MAX_CHILDREN; pub const MSIZE: usize = Self::MAX_CHILDREN + 2; - pub fn new( - partial_path: Path, - chd: [Option; Self::MAX_CHILDREN], - value: Option>, - chd_encoded: [Option>; Self::MAX_CHILDREN], - ) -> Self { - BranchNode { - partial_path, - children: chd, - value: value.map(Data), - children_encoded: chd_encoded, - } - } - - pub const fn value(&self) -> &Option { + pub const fn value(&self) -> &Option> { &self.value } @@ -117,12 +103,12 @@ impl BranchNode { (chd_encoded[i] = Some(chd).filter(|data| !data.is_empty())); } - Ok(BranchNode::new( - path, - [None; Self::MAX_CHILDREN], + Ok(BranchNode { + partial_path: path, + children: [None; Self::MAX_CHILDREN], value, - chd_encoded, - )) + children_encoded: chd_encoded, + }) } pub(super) fn encode(&self, store: &CompactSpace) -> Vec { @@ -171,7 +157,7 @@ impl BranchNode { } #[allow(clippy::unwrap_used)] - if let Some(Data(val)) = &self.value { + if let Some(val) = &self.value { list[Self::MAX_CHILDREN] = val.clone(); } @@ -312,7 +298,7 @@ impl Storable for BranchNode { addr += len as usize; - Some(Data(data.as_deref())) + Some(data.as_deref()) } None => None, }; diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 1dcc5f1ac270..a22fa850bc4f 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -1,7 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::Data; use crate::{ merkle::{nibbles_to_bytes_iter, Path}, nibbles::Nibbles, @@ -23,7 +22,7 @@ type DataLen = u32; #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { pub(crate) partial_path: Path, - pub(crate) data: Data, + pub(crate) data: Vec, } impl Debug for LeafNode { @@ -38,7 +37,7 @@ impl Debug for LeafNode { } impl LeafNode { - pub fn new, D: Into>(partial_path: P, data: D) -> Self { + pub fn new, D: Into>>(partial_path: P, data: D) -> Self { Self { partial_path: partial_path.into(), data: data.into(), @@ -49,7 +48,7 @@ impl LeafNode { &self.partial_path } - pub const fn data(&self) -> &Data { + pub const fn data(&self) -> &Vec { &self.data } @@ -141,7 +140,7 @@ impl Storable for LeafNode { Path::from_nibbles(nibbles).0 }; - let data = Data(data.to_vec()); + let data = data.to_vec(); Ok(Self::new(path, data)) } diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 5f9f5fe46ff3..fc49f7c230ff 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -434,7 +434,7 @@ fn locate_subproof( let Some(index) = key_nibbles.next().map(|nib| nib as usize) else { let encoded = n.value; - let sub_proof = encoded.map(|encoded| SubProof::Data(encoded.into_inner())); + let sub_proof = encoded.map(SubProof::Data); return Ok((sub_proof, key_nibbles)); }; diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 18ad4fc62d55..d0752c46ebcd 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -635,7 +635,7 @@ mod tests { }; assert_eq!(key, vec![0x01, 0x03, 0x03, 0x07].into_boxed_slice()); - assert_eq!(node.inner().as_leaf().unwrap().data, vec![0x42].into()); + assert_eq!(node.inner().as_leaf().unwrap().data, vec![0x42]); assert!(stream.next().is_none()); } @@ -670,7 +670,7 @@ mod tests { ); assert_eq!( node.inner().as_branch().unwrap().value, - Some(vec![0x00, 0x00, 0x00].into()), + Some(vec![0x00, 0x00, 0x00]), ); let (key, node) = match stream.next() { @@ -684,7 +684,7 @@ mod tests { ); assert_eq!( node.inner().as_leaf().unwrap().data, - vec![0x00, 0x00, 0x00, 0x0FF].into(), + vec![0x00, 0x00, 0x00, 0x0FF], ); assert!(stream.next().is_none()); @@ -718,7 +718,7 @@ mod tests { ); assert_eq!( node.inner().as_branch().unwrap().value, - Some(vec![0x00, 0x00, 0x00].into()), + Some(vec![0x00, 0x00, 0x00]), ); assert!(stream.next().is_none()); diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index da506eaa7442..1b0548fc6e64 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -145,7 +145,7 @@ impl Obj { pub fn into_inner(mut self) -> Node { let empty_node = LeafNode { partial_path: Path(Vec::new()), - data: Vec::new().into(), + data: Vec::new(), }; std::mem::replace(&mut self.value.item, Node::from_leaf(empty_node)) From 45e80141aaf40d89e401d3f30b5c6b413cabab0c Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 15 Mar 2024 09:35:17 -0400 Subject: [PATCH 0511/1053] always use 'value' instead of 'data' to describe the thing inside nodes (#591) --- firewood/examples/insert.rs | 6 +- firewood/src/merkle.rs | 117 ++++++++++++++--------------- firewood/src/merkle/node.rs | 20 ++--- firewood/src/merkle/node/branch.rs | 39 +++++----- firewood/src/merkle/node/leaf.rs | 54 +++++++------ firewood/src/merkle/proof.rs | 30 ++++---- firewood/src/merkle/stream.rs | 26 +++---- firewood/src/shale/mod.rs | 2 +- firewood/tests/merkle.rs | 22 +++--- 9 files changed, 162 insertions(+), 154 deletions(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 18d987fa48ef..471360e79e91 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -21,7 +21,7 @@ struct Args { #[arg(short, long, default_value = "1-64", value_parser = string_to_range)] keylen: RangeInclusive, #[arg(short, long, default_value = "32", value_parser = string_to_range)] - datalen: RangeInclusive, + valuelen: RangeInclusive, #[arg(short, long, default_value_t = 1)] batch_size: usize, #[arg(short, long, default_value_t = 100)] @@ -65,7 +65,7 @@ async fn main() -> Result<(), Box> { for _ in 0..args.number_of_batches { let keylen = rng.gen_range(args.keylen.clone()); - let datalen = rng.gen_range(args.datalen.clone()); + let valuelen = rng.gen_range(args.valuelen.clone()); let batch: Batch, Vec> = (0..keys) .map(|_| { ( @@ -75,7 +75,7 @@ async fn main() -> Result<(), Box> { .collect::>(), rng.borrow_mut() .sample_iter(&Alphanumeric) - .take(datalen) + .take(valuelen) .collect::>(), ) }) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 216969776e42..d5a15ee312dc 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -117,7 +117,7 @@ where EncodedNode { partial_path: n.partial_path.clone(), children: Box::new(children), - value: n.data.clone().into(), + value: n.value.clone().into(), phantom: PhantomData, } } @@ -318,9 +318,9 @@ impl Merkle { #[allow(clippy::indexing_slicing)] match (overlap.unique_a.len(), overlap.unique_b.len()) { - // same node, overwrite the data + // same node, overwrite the value (0, 0) => { - self.update_data_and_move_node_if_larger( + self.update_value_and_move_node_if_larger( (&mut parents, &mut deleted), node, val, @@ -344,7 +344,7 @@ impl Merkle { let new_branch = BranchNode { partial_path: Path(overlap.shared.to_vec()), children, - value: n.data.clone().into(), + value: n.value.clone().into(), children_encoded: Default::default(), }; @@ -472,9 +472,9 @@ impl Merkle { #[allow(clippy::indexing_slicing)] match (overlap.unique_a.len(), overlap.unique_b.len()) { - // same node, overwrite the data + // same node, overwrite the value (0, 0) => { - self.update_data_and_move_node_if_larger( + self.update_value_and_move_node_if_larger( (&mut parents, &mut deleted), node, val, @@ -619,7 +619,7 @@ impl Merkle { } NodeType::Leaf(n) => { if n.partial_path.len() == 0 { - n.data = val; + n.value = val; None } else { @@ -684,7 +684,7 @@ impl Merkle { let mut deleted = Vec::new(); - let data = { + let value = { let (node, mut parents) = self.get_node_and_parents_by_key(self.get_node(root)?, key)?; @@ -692,22 +692,21 @@ impl Merkle { return Ok(None); }; - let data = match &node.inner { + let value = match &node.inner { NodeType::Branch(branch) => { - let data = branch.value.clone(); - let children = branch.children; - - if data.is_none() { + let value = branch.value.clone(); + if value.is_none() { return Ok(None); } - let children: Vec<_> = children + let children: Vec<_> = branch + .children .iter() .enumerate() .filter_map(|(i, child)| child.map(|child| (i, child))) .collect(); - // don't change the sentinal node + // don't change the sentinel node if children.len() == 1 && !parents.is_empty() { let branch_path = &branch.partial_path.0; @@ -738,11 +737,11 @@ impl Merkle { })? } - data + value } NodeType::Leaf(n) => { - let data = Some(n.data.clone()); + let value = Some(n.value.clone()); // TODO: handle unwrap better deleted.push(node.as_ptr()); @@ -767,7 +766,7 @@ impl Merkle { .collect(); match (children.len(), &branch.value, !parents.is_empty()) { - // node is invalid, all single-child nodes should have data + // node is invalid, all single-child nodes should have a value (1, None, true) => { let parent_path = &branch.partial_path.0; @@ -814,10 +813,10 @@ impl Merkle { } // branch nodes shouldn't have no children - (0, Some(data), true) => { + (0, Some(value), true) => { let leaf = Node::from_leaf(LeafNode::new( Path(branch.partial_path.0.clone()), - data.clone(), + value.clone(), )); let leaf = self.put_node(leaf)?.as_ptr(); @@ -829,7 +828,7 @@ impl Merkle { _ => parent.write(|parent| parent.rehash())?, } - data + value } }; @@ -837,14 +836,14 @@ impl Merkle { parent.write(|u| u.rehash())?; } - data + value }; for ptr in deleted.into_iter() { self.free_node(ptr)?; } - Ok(data) + Ok(value) } fn remove_tree_( @@ -1146,7 +1145,7 @@ impl Merkle { // transpose the Option> to Result, E> // If this is an error, the ? operator will return it - let Some((first_key, first_data)) = first_result.transpose()? else { + let Some((first_key, first_value)) = first_result.transpose()? else { // nothing returned, either the trie is empty or the key wasn't found return Ok(None); }; @@ -1156,7 +1155,7 @@ impl Merkle { .map_err(|e| api::Error::InternalError(Box::new(e)))?; let limit = limit.map(|old_limit| old_limit - 1); - let mut middle = vec![(first_key.into_vec(), first_data)]; + let mut middle = vec![(first_key.into_vec(), first_value)]; // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested @@ -1220,16 +1219,16 @@ impl Merkle { self.move_node_if_write_failed((parents, to_delete), node, write_result) } - /// Try to update the [NodeObjRef]'s data/value in-place. If the update fails because the node can no longer fit at its old address, + /// Try to update the [NodeObjRef]'s value in-place. If the update fails because the node can no longer fit at its old address, /// then the old address is marked for deletion and the [Node] (with its update) is inserted at a new address. - fn update_data_and_move_node_if_larger<'a>( + fn update_value_and_move_node_if_larger<'a>( &'a self, (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), mut node: NodeObjRef<'a>, - data: Vec, + value: Vec, ) -> Result { let write_result = node.write(|node| { - node.inner_mut().set_data(data); + node.inner_mut().set_value(value); node.rehash(); }); @@ -1285,7 +1284,7 @@ impl<'a> std::ops::Deref for Ref<'a> { fn deref(&self) -> &[u8] { match &self.0.inner { NodeType::Branch(n) => n.value.as_ref().unwrap(), - NodeType::Leaf(n) => &n.data, + NodeType::Leaf(n) => &n.value, } } } @@ -1324,7 +1323,7 @@ impl<'a, S: CachedStore, T> RefMut<'a, S, T> { #[allow(clippy::unwrap_used)] modify(match &mut u.inner { NodeType::Branch(n) => n.value.as_mut().unwrap(), - NodeType::Leaf(n) => &mut n.data, + NodeType::Leaf(n) => &mut n.value, }); u.rehash() }, @@ -1396,8 +1395,8 @@ mod tests { use shale::{cached::InMemLinearStore, CachedStore}; use test_case::test_case; - fn leaf(path: Vec, data: Vec) -> Node { - Node::from_leaf(LeafNode::new(Path(path), data)) + fn leaf(path: Vec, value: Vec) -> Node { + Node::from_leaf(LeafNode::new(Path(path), value)) } #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] @@ -1467,13 +1466,13 @@ mod tests { }) } - fn branch_without_data(path: &[u8], encoded_child: Option>) -> Node { + fn branch_without_value(path: &[u8], encoded_child: Option>) -> Node { let path = path.to_vec(); let path = Nibbles::<0>::new(&path); let path = Path(path.into_iter().collect()); let children = Default::default(); - // TODO: Properly test empty data as a value + // TODO: Properly test empty value let value = None; let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); @@ -1493,7 +1492,7 @@ mod tests { #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] #[test_case(branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd")] #[test_case(branch(b"", b"value", None); "branch without chd")] - #[test_case(branch_without_data(b"", None); "branch without value and chd")] + #[test_case(branch_without_value(b"", None); "branch without value and chd")] #[test_case(branch(b"", b"", None); "branch without path value or children")] #[test_case(branch(b"", b"value", None) ; "branch with value")] #[test_case(branch(&[2], b"", None); "branch with path")] @@ -2074,7 +2073,7 @@ mod tests { #[test] fn update_leaf_with_larger_path() -> Result<(), MerkleError> { let path = vec![0x00]; - let data = vec![0x00]; + let value = vec![0x00]; let double_path = path .clone() @@ -2084,35 +2083,35 @@ mod tests { let node = Node::from_leaf(LeafNode { partial_path: Path::from(path), - data: data.clone(), + value: value.clone(), }); - check_node_update(node, double_path, data) + check_node_update(node, double_path, value) } #[test] - fn update_leaf_with_larger_data() -> Result<(), MerkleError> { + fn update_leaf_with_larger_value() -> Result<(), MerkleError> { let path = vec![0x00]; - let data = vec![0x00]; + let value = vec![0x00]; - let double_data = data + let double_value = value .clone() .into_iter() - .chain(data.clone()) + .chain(value.clone()) .collect::>(); let node = Node::from_leaf(LeafNode { partial_path: Path::from(path.clone()), - data, + value, }); - check_node_update(node, path, double_data) + check_node_update(node, path, double_value) } #[test] fn update_branch_with_larger_path() -> Result<(), MerkleError> { let path = vec![0x00]; - let data = vec![0x00]; + let value = vec![0x00]; let double_path = path .clone() @@ -2123,38 +2122,38 @@ mod tests { let node = Node::from_branch(BranchNode { partial_path: Path::from(path.clone()), children: Default::default(), - value: Some(data.clone()), + value: Some(value.clone()), children_encoded: Default::default(), }); - check_node_update(node, double_path, data) + check_node_update(node, double_path, value) } #[test] - fn update_branch_with_larger_data() -> Result<(), MerkleError> { + fn update_branch_with_larger_value() -> Result<(), MerkleError> { let path = vec![0x00]; - let data = vec![0x00]; + let value = vec![0x00]; - let double_data = data + let double_value = value .clone() .into_iter() - .chain(data.clone()) + .chain(value.clone()) .collect::>(); let node = Node::from_branch(BranchNode { partial_path: Path::from(path.clone()), children: Default::default(), - value: Some(data), + value: Some(value), children_encoded: Default::default(), }); - check_node_update(node, path, double_data) + check_node_update(node, path, double_value) } fn check_node_update( node: Node, new_path: Vec, - new_data: Vec, + new_value: Vec, ) -> Result<(), MerkleError> { let merkle = create_test_merkle(); let root = merkle.init_root()?; @@ -2166,7 +2165,7 @@ mod tests { // make sure that doubling the path length will fail on a normal write let write_result = node_ref.write(|node| { node.inner_mut().set_path(Path(new_path.clone())); - node.inner_mut().set_data(new_data.clone()); + node.inner_mut().set_value(new_value.clone()); node.rehash(); }); @@ -2185,13 +2184,13 @@ mod tests { assert_ne!(node.as_ptr(), addr); assert_eq!(&to_delete[0], &addr); - let (path, data) = match node.inner() { - NodeType::Leaf(leaf) => (&leaf.partial_path, Some(&leaf.data)), + let (path, value) = match node.inner() { + NodeType::Leaf(leaf) => (&leaf.partial_path, Some(&leaf.value)), NodeType::Branch(branch) => (&branch.partial_path, branch.value.as_ref()), }; assert_eq!(path, &Path(new_path)); - assert_eq!(data, Some(&new_data)); + assert_eq!(value, Some(&new_value)); Ok(()) } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index a32d9483013d..df58710057fc 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -69,9 +69,9 @@ impl NodeType { let cur_key = cur_key_path.into_inner(); #[allow(clippy::unwrap_used)] - let data: Vec = items.next().unwrap(); + let value: Vec = items.next().unwrap(); - Ok(NodeType::Leaf(LeafNode::new(cur_key, data))) + Ok(NodeType::Leaf(LeafNode::new(cur_key, value))) } // TODO: add path BranchNode::MSIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), @@ -102,10 +102,10 @@ impl NodeType { } } - pub fn set_data(&mut self, data: Vec) { + pub fn set_value(&mut self, value: Vec) { match self { - NodeType::Branch(u) => u.value = Some(data), - NodeType::Leaf(node) => node.data = data, + NodeType::Branch(u) => u.value = Some(value), + NodeType::Leaf(node) => node.value = value, } } } @@ -598,9 +598,9 @@ impl<'de> Deserialize<'de> for EncodedNode { "incorrect encoded type for leaf node path", )); }; - let Some(data) = items.next() else { + let Some(value) = items.next() else { return Err(D::Error::custom( - "incorrect encoded type for leaf node data", + "incorrect encoded type for leaf node value", )); }; let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); @@ -608,7 +608,7 @@ impl<'de> Deserialize<'de> for EncodedNode { Ok(Self { partial_path: path, children: children.into(), - value: Some(data), + value: Some(value), phantom: PhantomData, }) } @@ -779,10 +779,10 @@ mod tests { (0..0, 0..15, 0..16, 0..31, 0..32), [0..0, 0..16, 0..32] )] - fn leaf_node>(path: Iter, data: Iter) { + fn leaf_node>(path: Iter, value: Iter) { let node = Node::from_leaf(LeafNode::new( Path(path.map(|x| x & 0xf).collect()), - data.collect::>(), + value.collect::>(), )); check_node_encoding(node); diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 0159f0ea876e..f511e87e80c7 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -16,7 +16,7 @@ use std::{ }; type PathLen = u8; -pub type DataLen = u32; +pub type ValueLen = u32; pub type EncodedChildLen = u8; const MAX_CHILDREN: usize = 16; @@ -90,9 +90,9 @@ impl BranchNode { // we've already validated the size, that's why we can safely unwrap #[allow(clippy::unwrap_used)] - let data = items.pop().unwrap(); + let value = items.pop().unwrap(); // Extract the value of the branch node and set to None if it's an empty Vec - let value = Some(data).filter(|data| !data.is_empty()); + let value = Some(value).filter(|value| !value.is_empty()); // encode all children. let mut chd_encoded: [Option>; Self::MAX_CHILDREN] = Default::default(); @@ -100,7 +100,7 @@ impl BranchNode { // we popped the last element, so their should only be NBRANCH items left for (i, chd) in items.into_iter().enumerate() { #[allow(clippy::indexing_slicing)] - (chd_encoded[i] = Some(chd).filter(|data| !data.is_empty())); + (chd_encoded[i] = Some(chd).filter(|value| !value.is_empty())); } Ok(BranchNode { @@ -175,14 +175,14 @@ impl BranchNode { impl Storable for BranchNode { fn serialized_len(&self) -> u64 { let children_len = Self::MAX_CHILDREN as u64 * DiskAddress::MSIZE; - let data_len = optional_data_len::(self.value.as_deref()); + let value_len = optional_value_len::(self.value.as_deref()); let children_encoded_len = self.children_encoded.iter().fold(0, |len, child| { - len + optional_data_len::(child.as_ref()) + len + optional_value_len::(child.as_ref()) }); let path_len_size = size_of::() as u64; let path_len = self.partial_path.serialized_len(); - children_len + data_len + children_encoded_len + path_len_size + path_len + children_len + value_len + children_encoded_len + path_len_size + path_len } fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { @@ -200,8 +200,8 @@ impl Storable for BranchNode { let (value_len, value) = self .value .as_ref() - .map(|val| (val.len() as DataLen, &**val)) - .unwrap_or((DataLen::MAX, &[])); + .map(|val| (val.len() as ValueLen, &**val)) + .unwrap_or((ValueLen::MAX, &[])); cursor.write_all(&value_len.to_le_bytes())?; cursor.write_all(value)?; @@ -224,9 +224,9 @@ impl Storable for BranchNode { mem: &T, ) -> Result { const PATH_LEN_SIZE: u64 = size_of::() as u64; - const DATA_LEN_SIZE: usize = size_of::(); + const VALUE_LEN_SIZE: usize = size_of::(); const BRANCH_HEADER_SIZE: u64 = - BranchNode::MAX_CHILDREN as u64 * DiskAddress::MSIZE + DATA_LEN_SIZE as u64; + BranchNode::MAX_CHILDREN as u64 * DiskAddress::MSIZE + VALUE_LEN_SIZE as u64; let path_len = mem .get_view(addr, PATH_LEN_SIZE) @@ -280,16 +280,16 @@ impl Storable for BranchNode { } let raw_len = { - let mut buf = [0; DATA_LEN_SIZE]; + let mut buf = [0; VALUE_LEN_SIZE]; cursor.read_exact(&mut buf)?; - Some(DataLen::from_le_bytes(buf)) - .filter(|len| *len != DataLen::MAX) + Some(ValueLen::from_le_bytes(buf)) + .filter(|len| *len != ValueLen::MAX) .map(|len| len as u64) }; let value = match raw_len { Some(len) => { - let data = mem + let value = mem .get_view(addr, len) .ok_or(ShaleError::InvalidCacheView { offset: addr, @@ -298,7 +298,7 @@ impl Storable for BranchNode { addr += len as usize; - Some(data.as_deref()) + Some(value.as_deref()) } None => None, }; @@ -354,6 +354,9 @@ impl Storable for BranchNode { } } -fn optional_data_len>(data: Option) -> u64 { - size_of::() as u64 + data.as_ref().map_or(0, |data| data.as_ref().len() as u64) +fn optional_value_len>(value: Option) -> u64 { + size_of::() as u64 + + value + .as_ref() + .map_or(0, |value| value.as_ref().len() as u64) } diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index a22fa850bc4f..3cc438940e43 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -17,12 +17,12 @@ use std::{ pub const SIZE: usize = 2; type PathLen = u8; -type DataLen = u32; +type ValueLen = u32; #[derive(PartialEq, Eq, Clone)] pub struct LeafNode { pub(crate) partial_path: Path, - pub(crate) data: Vec, + pub(crate) value: Vec, } impl Debug for LeafNode { @@ -31,16 +31,16 @@ impl Debug for LeafNode { f, "[Leaf {:?} {}]", self.partial_path, - hex::encode(&*self.data) + hex::encode(&*self.value) ) } } impl LeafNode { - pub fn new, D: Into>>(partial_path: P, data: D) -> Self { + pub fn new, V: Into>>(partial_path: P, value: V) -> Self { Self { partial_path: partial_path.into(), - data: data.into(), + value: value.into(), } } @@ -48,8 +48,8 @@ impl LeafNode { &self.partial_path } - pub const fn data(&self) -> &Vec { - &self.data + pub const fn value(&self) -> &Vec { + &self.value } pub(super) fn encode(&self) -> Vec { @@ -58,7 +58,7 @@ impl LeafNode { .serialize( [ nibbles_to_bytes_iter(&self.partial_path.encode()).collect(), - self.data.to_vec(), + self.value.to_vec(), ] .as_slice(), ) @@ -70,7 +70,7 @@ impl LeafNode { #[repr(C, packed)] struct Meta { path_len: PathLen, - data_len: DataLen, + value_len: ValueLen, } impl Meta { @@ -81,9 +81,9 @@ impl Storable for LeafNode { fn serialized_len(&self) -> u64 { let meta_len = size_of::() as u64; let path_len = self.partial_path.serialized_len(); - let data_len = self.data.len() as u64; + let value_len = self.value.len() as u64; - meta_len + path_len + data_len + meta_len + path_len + value_len } fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { @@ -91,12 +91,15 @@ impl Storable for LeafNode { let path = &self.partial_path.encode(); let path = nibbles_to_bytes_iter(path); - let data = &self.data; + let value = &self.value; let path_len = self.partial_path.serialized_len() as PathLen; - let data_len = data.len() as DataLen; + let value_len = value.len() as ValueLen; - let meta = Meta { path_len, data_len }; + let meta = Meta { + path_len, + value_len, + }; cursor.write_all(bytemuck::bytes_of(&meta))?; @@ -104,7 +107,7 @@ impl Storable for LeafNode { cursor.write_all(&[nibble])?; } - cursor.write_all(data)?; + cursor.write_all(value)?; Ok(()) } @@ -125,24 +128,27 @@ impl Storable for LeafNode { .as_deref(); let offset = offset + Meta::SIZE; - let Meta { path_len, data_len } = *bytemuck::from_bytes(&node_header_raw); - let size = path_len as u64 + data_len as u64; + let Meta { + path_len, + value_len, + } = *bytemuck::from_bytes(&node_header_raw); + let size = path_len as u64 + value_len as u64; let remainder = mem .get_view(offset, size) .ok_or(InvalidCacheView { offset, size })? .as_deref(); - let (path, data) = remainder.split_at(path_len as usize); + let (path, value) = remainder.split_at(path_len as usize); let path = { let nibbles = Nibbles::<0>::new(path).into_iter(); Path::from_nibbles(nibbles).0 }; - let data = data.to_vec(); + let value = value.to_vec(); - Ok(Self::new(path, data)) + Ok(Self::new(path, value)) } } @@ -160,15 +166,15 @@ mod tests { // This is combined with the first nibble of the path (0b0000_0010) to become 0b0001_0010 #[test_case(0b0001_0010, vec![0x34], vec![2, 3, 4]; "odd length")] fn encode_regression_test(prefix: u8, path: Vec, nibbles: Vec) { - let data = vec![5, 6, 7, 8]; + let value = vec![5, 6, 7, 8]; let serialized_path = [vec![prefix], path.clone()].concat(); let serialized_path = [vec![serialized_path.len() as u8], serialized_path].concat(); - let serialized_data = [vec![data.len() as u8], data.clone()].concat(); + let serialized_value = [vec![value.len() as u8], value.clone()].concat(); - let serialized = [vec![2], serialized_path, serialized_data].concat(); + let serialized = [vec![2], serialized_path, serialized_value].concat(); - let node = LeafNode::new(nibbles, data.clone()); + let node = LeafNode::new(nibbles, value.clone()); assert_eq!(node.encode(), serialized); } diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index fc49f7c230ff..e07ae6a0690d 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -96,7 +96,7 @@ impl From for ProofError { /// A proof that a single key is present /// -/// The generic N represents the storage for the node data +/// The generic N represents the storage for the node #[derive(Clone, Debug)] pub struct Proof(pub HashMap); @@ -106,7 +106,7 @@ pub struct Proof(pub HashMap); #[derive(Debug)] enum SubProof { - Data(Vec), + Value(Vec), Hash(HashKey), } @@ -115,7 +115,7 @@ impl + Send> Proof { /// key in a trie with the given root hash. VerifyProof returns an error if the /// proof contains invalid trie nodes or the wrong value. /// - /// The generic N represents the storage for the node data + /// The generic N represents the storage for the node pub fn verify>( &self, key: K, @@ -138,7 +138,7 @@ impl + Send> Proof { cur_hash = match sub_proof { // Return when reaching the end of the key. - Some(SubProof::Data(value)) if key_nibbles.is_empty() => return Ok(Some(value)), + Some(SubProof::Value(value)) if key_nibbles.is_empty() => return Ok(Some(value)), // The trie doesn't contain the key. Some(SubProof::Hash(hash)) => hash, _ => return Ok(None), @@ -208,7 +208,7 @@ impl + Send> Proof { // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if keys.len() == 1 && first_key.as_ref() == last_key.as_ref() { - let data = + let value = self.proof_to_path(first_key.as_ref(), root_hash, &mut in_mem_merkle, false)?; #[allow(clippy::indexing_slicing)] @@ -216,7 +216,7 @@ impl + Send> Proof { // correct proof but invalid key Err(ProofError::InvalidEdgeKeys) } else { - match data { + match value { #[allow(clippy::indexing_slicing)] Some(val) if val == vals[0].as_ref() => Ok(true), None => Ok(false), @@ -337,7 +337,7 @@ impl + Send> Proof { .iter() .copied() .eq(key_nibbles) // all nibbles have to match - .then(|| n.data().to_vec()); + .then(|| n.value().to_vec()); } NodeType::Branch(n) => { @@ -356,11 +356,11 @@ impl + Send> Proof { .chd_encode() .get(*index as usize) .and_then(|inner| inner.as_ref()) - .map(|data| &**data); + .map(|value| &**value); subproof } else { - break n.value.as_ref().map(|data| data.to_vec()); + break n.value.as_ref().map(|value| value.to_vec()); } } }; @@ -377,7 +377,7 @@ impl + Send> Proof { }; match sub_proof { - Some(data) => Ok(Some(data)), + Some(value) => Ok(Some(value)), None if allow_non_existent_node => Ok(None), None => Err(ProofError::NodeNotInTrie), } @@ -413,9 +413,9 @@ fn locate_subproof( return Ok((None, Nibbles::<0>::new(&[]).into_iter())); } - let encoded: Vec = n.data().to_vec(); + let encoded: Vec = n.value().to_vec(); - let sub_proof = SubProof::Data(encoded); + let sub_proof = SubProof::Value(encoded); Ok((sub_proof.into(), key_nibbles)) } @@ -434,17 +434,17 @@ fn locate_subproof( let Some(index) = key_nibbles.next().map(|nib| nib as usize) else { let encoded = n.value; - let sub_proof = encoded.map(SubProof::Data); + let sub_proof = encoded.map(SubProof::Value); return Ok((sub_proof, key_nibbles)); }; // consume items returning the item at index #[allow(clippy::indexing_slicing)] - let data = n.chd_encode()[index] + let value = n.chd_encode()[index] .as_ref() .ok_or(ProofError::InvalidData)?; - generate_subproof(data).map(|subproof| (Some(subproof), key_nibbles)) + generate_subproof(value).map(|subproof| (Some(subproof), key_nibbles)) } } } diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index d0752c46ebcd..e8e3e16be068 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -381,7 +381,7 @@ impl<'a, S: CachedStore, T> Stream for MerkleKeyValueStream<'a, S, T> { Poll::Ready(Some(Ok((key, value)))) } NodeType::Leaf(leaf) => { - let value = leaf.data.to_vec(); + let value = leaf.value.to_vec(); Poll::Ready(Some(Ok((key, value)))) } }, @@ -635,7 +635,7 @@ mod tests { }; assert_eq!(key, vec![0x01, 0x03, 0x03, 0x07].into_boxed_slice()); - assert_eq!(node.inner().as_leaf().unwrap().data, vec![0x42]); + assert_eq!(node.inner().as_leaf().unwrap().value, vec![0x42]); assert!(stream.next().is_none()); } @@ -683,7 +683,7 @@ mod tests { vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice() ); assert_eq!( - node.inner().as_leaf().unwrap().data, + node.inner().as_leaf().unwrap().value, vec![0x00, 0x00, 0x00, 0x0FF], ); @@ -753,7 +753,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); - assert_eq!(node.inner().as_leaf().unwrap().data.to_vec(), vec![0x00]); + assert_eq!(node.inner().as_leaf().unwrap().value.to_vec(), vec![0x00]); check_stream_is_done(stream).await; } @@ -823,26 +823,26 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); - assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); + assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); assert_eq!(node.partial_path.to_vec(), vec![0x01]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); - assert_eq!(node.clone().data.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); + assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); assert_eq!(node.partial_path.to_vec(), vec![0x0F]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); - assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xD0, 0xD0]); + assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]); assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO // Covers case of leaf with no partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); let node = node.inner().as_leaf().unwrap(); - assert_eq!(node.clone().data.to_vec(), vec![0x00, 0xFF]); + assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]); assert_eq!(node.partial_path.to_vec(), vec![0x0F]); check_stream_is_done(stream).await; @@ -857,7 +857,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().data.to_vec(), + node.inner().as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xD0, 0xD0] ); @@ -865,7 +865,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().data.to_vec(), + node.inner().as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); @@ -881,7 +881,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().data.to_vec(), + node.inner().as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xD0, 0xD0] ); @@ -889,7 +889,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().data.to_vec(), + node.inner().as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); @@ -1039,7 +1039,7 @@ mod tests { } #[tokio::test] - async fn key_value_root_with_empty_data() { + async fn key_value_root_with_empty_value() { let mut merkle = create_test_merkle(); let root = merkle.init_root().unwrap(); diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 1b0548fc6e64..77b06f4db084 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -145,7 +145,7 @@ impl Obj { pub fn into_inner(mut self) -> Node { let empty_node = LeafNode { partial_path: Path(Vec::new()), - data: Vec::new(), + value: Vec::new(), }; std::mem::replace(&mut self.value.item, Node::from_leaf(empty_node)) diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs index 14843e03b34c..078879744430 100644 --- a/firewood/tests/merkle.rs +++ b/firewood/tests/merkle.rs @@ -946,8 +946,8 @@ fn test_empty_value_range_proof() -> Result<(), ProofError> { // Create a new entry with a slightly modified key let mid_index = items.len() / 2; let key = increase_key(items[mid_index - 1].0); - let empty_data: [u8; 20] = [0; 20]; - items.splice(mid_index..mid_index, [(&key, &empty_data)].iter().cloned()); + let empty_value: [u8; 20] = [0; 20]; + items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); let start = 1; let end = items.len() - 1; @@ -982,8 +982,8 @@ fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { // Create a new entry with a slightly modified key let mid_index = items.len() / 2; let key = increase_key(items[mid_index - 1].0); - let empty_data: [u8; 20] = [0; 20]; - items.splice(mid_index..mid_index, [(&key, &empty_data)].iter().cloned()); + let empty_value: [u8; 20] = [0; 20]; + items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); let start = 0; let end = items.len() - 1; @@ -1049,12 +1049,12 @@ fn test_bloadted_range_proof() -> Result<(), ProofError> { let mut items = Vec::new(); for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; - let mut data: [u8; 20] = [0; 20]; + let mut value: [u8; 20] = [0; 20]; for (index, d) in i.to_be_bytes().iter().enumerate() { key[index] = *d; - data[index] = *d; + value[index] = *d; } - items.push((key, data)); + items.push((key, value)); } let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; @@ -1085,18 +1085,18 @@ fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; - let mut data: [u8; 20] = [0; 20]; + let mut value: [u8; 20] = [0; 20]; for (index, d) in i.to_be_bytes().iter().enumerate() { key[index] = *d; - data[index] = *d; + value[index] = *d; } - items.insert(key, data); + items.insert(key, value); let mut more_key: [u8; 32] = [0; 32]; for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { more_key[index] = *d; } - items.insert(more_key, data); + items.insert(more_key, value); } // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. From 513f097ebc6f1eecc3411b8e983b8e5cd23f1b52 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 15 Mar 2024 11:25:35 -0400 Subject: [PATCH 0512/1053] proof nits (#592) --- firewood/src/merkle/proof.rs | 38 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index e07ae6a0690d..ca4c4b5bc62f 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -20,7 +20,7 @@ use crate::{ merkle_util::{DataStoreError, InMemoryMerkle}, }; -use super::{BinarySerde, EncodedNode, NodeObjRef}; +use super::{BinarySerde, EncodedNode}; #[derive(Debug, Error)] pub enum ProofError { @@ -285,19 +285,19 @@ impl + Send> Proof { // Start with the sentinel root let sentinel = in_mem_merkle.get_sentinel_address(); let merkle = in_mem_merkle.get_merkle_mut(); - let mut parent_node_ref = merkle + let mut parent_node = merkle .get_node(sentinel) .map_err(|_| ProofError::NoSuchNode)?; let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter().peekable(); let mut child_hash = root_hash; - let proofs_map = &self.0; + let proof_nodes_map = &self.0; let sub_proof = loop { // Link the child to the parent based on the node type. // if a child is already linked, use it instead - let child_node = match &parent_node_ref.inner() { + let child_node = match &parent_node.inner() { #[allow(clippy::indexing_slicing)] NodeType::Branch(n) => { let Some(child_index) = key_nibbles.next().map(usize::from) else { @@ -308,10 +308,17 @@ impl + Send> Proof { // If the child already resolved, then use the existing node. Some(node) => merkle.get_node(node)?, None => { - let child_node = decode_subproof(merkle, proofs_map, &child_hash)?; + // Look up the child's encoded bytes and decode to get the child. + let child_node_bytes = proof_nodes_map + .get(&child_hash) + .ok_or(ProofError::ProofNodeMissing)?; + + let child_node = NodeType::decode(child_node_bytes.as_ref())?; + + let child_node = merkle.put_node(Node::from(child_node))?; - // insert the leaf to the empty slot - parent_node_ref.write(|node| { + // insert `child_node` to the appropriate index in the `parent_node` + parent_node.write(|node| { #[allow(clippy::indexing_slicing)] let node = node .inner_mut() @@ -329,7 +336,7 @@ impl + Send> Proof { _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), }; - // find the encoded subproof of the child if the partial-path and nibbles match + // find the encoded subproof of the child if the partial path and nibbles match let encoded_sub_proof = match child_node.inner() { NodeType::Leaf(n) => { break n @@ -373,7 +380,7 @@ impl + Send> Proof { } } - parent_node_ref = child_node; + parent_node = child_node; }; match sub_proof { @@ -384,19 +391,6 @@ impl + Send> Proof { } } -fn decode_subproof<'a, S: CachedStore, T, N: AsRef<[u8]>>( - merkle: &'a Merkle, - proofs_map: &HashMap, - child_hash: &HashKey, -) -> Result, ProofError> { - let child_proof = proofs_map - .get(child_hash) - .ok_or(ProofError::ProofNodeMissing)?; - let child_node = NodeType::decode(child_proof.as_ref())?; - let node = merkle.put_node(Node::from(child_node))?; - Ok(node) -} - fn locate_subproof( mut key_nibbles: NibblesIterator<'_, 0>, node: NodeType, From 0d37ee3e670d460aa9e1f0cec650c415693b3582 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Fri, 15 Mar 2024 18:06:58 +0100 Subject: [PATCH 0513/1053] Don't `Box` the children! (#594) --- firewood/src/merkle.rs | 4 ++-- firewood/src/merkle/node.rs | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d5a15ee312dc..b02b51b0aa95 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -116,7 +116,7 @@ where let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); EncodedNode { partial_path: n.partial_path.clone(), - children: Box::new(children), + children, value: n.value.clone().into(), phantom: PhantomData, } @@ -171,7 +171,7 @@ where partial_path: encoded.partial_path, children: [None; BranchNode::MAX_CHILDREN], value: encoded.value, - children_encoded: *encoded.children, + children_encoded: encoded.children, } .into(), )) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index df58710057fc..2bf5636fbda4 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -463,21 +463,27 @@ impl Storable for Node { /// Contains the fields that we include in a node's hash. /// If this is a leaf node, `children` is empty and `value` is Some. /// If this is a branch node, `children` is non-empty. -#[derive(Debug)] +#[derive(Debug, Eq)] pub struct EncodedNode { pub(crate) partial_path: Path, /// If a child is None, it doesn't exist. /// If it's Some, it's the value or value hash of the child. - pub(crate) children: Box<[Option>; BranchNode::MAX_CHILDREN]>, + pub(crate) children: [Option>; BranchNode::MAX_CHILDREN], pub(crate) value: Option>, pub(crate) phantom: PhantomData, } +// driving this adds an unnecessary bound, T: PartialEq +// PhantomData is PartialEq for all T impl PartialEq for EncodedNode { fn eq(&self, other: &Self) -> bool { - self.partial_path == other.partial_path - && self.children == other.children - && self.value == other.value + let Self { + partial_path, + children, + value, + phantom: _, + } = self; + partial_path == &other.partial_path && children == &other.children && value == &other.value } } @@ -536,7 +542,7 @@ impl<'de> Deserialize<'de> for EncodedNode { Ok(Self { partial_path: path, - children: children.into(), + children, value, phantom: PhantomData, }) @@ -607,7 +613,7 @@ impl<'de> Deserialize<'de> for EncodedNode { let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); Ok(Self { partial_path: path, - children: children.into(), + children, value: Some(value), phantom: PhantomData, }) @@ -629,7 +635,7 @@ impl<'de> Deserialize<'de> for EncodedNode { Ok(Self { partial_path: path, - children: children.into(), + children, value, phantom: PhantomData, }) From 16410d34cfc8ffa2d689b86260742cdc406eae57 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 20 Mar 2024 09:58:18 -0400 Subject: [PATCH 0514/1053] don't assume DiskAddress is 8 bytes in CompactSpaceHeader (#596) --- firewood/src/shale/compact.rs | 43 +++++++++++++++++++++++------- firewood/src/shale/disk_address.rs | 6 ++--- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 6a97a55b89f1..3d81a9a2d6d8 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -171,7 +171,11 @@ impl CompactSpaceHeaderSliced { } impl CompactSpaceHeader { - pub const MSIZE: u64 = 32; + pub const MSIZE: u64 = 4 * DiskAddress::MSIZE; + const META_SPACE_TAIL_OFFSET: usize = 0; + const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::MSIZE as usize; + const BASE_ADDR_OFFSET: usize = 2 * DiskAddress::MSIZE as usize; + const ALLOC_ADDR_OFFSET: usize = 3 * DiskAddress::MSIZE as usize; pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { @@ -184,10 +188,30 @@ impl CompactSpaceHeader { fn into_fields(r: Obj) -> Result { Ok(CompactSpaceHeaderSliced { - meta_space_tail: StoredView::slice(&r, 0, 8, r.meta_space_tail)?, - data_space_tail: StoredView::slice(&r, 8, 8, r.data_space_tail)?, - base_addr: StoredView::slice(&r, 16, 8, r.base_addr)?, - alloc_addr: StoredView::slice(&r, 24, 8, r.alloc_addr)?, + meta_space_tail: StoredView::slice( + &r, + Self::META_SPACE_TAIL_OFFSET, + DiskAddress::MSIZE, + r.meta_space_tail, + )?, + data_space_tail: StoredView::slice( + &r, + Self::DATA_SPACE_TAIL_OFFSET, + DiskAddress::MSIZE, + r.data_space_tail, + )?, + base_addr: StoredView::slice( + &r, + Self::BASE_ADDR_OFFSET, + DiskAddress::MSIZE, + r.base_addr, + )?, + alloc_addr: StoredView::slice( + &r, + Self::ALLOC_ADDR_OFFSET, + DiskAddress::MSIZE, + r.alloc_addr, + )?, }) } } @@ -201,13 +225,14 @@ impl Storable for CompactSpaceHeader { size: Self::MSIZE, })?; #[allow(clippy::indexing_slicing)] - let meta_space_tail = raw.as_deref()[..8].into(); + let meta_space_tail = raw.as_deref()[..Self::DATA_SPACE_TAIL_OFFSET].into(); #[allow(clippy::indexing_slicing)] - let data_space_tail = raw.as_deref()[8..16].into(); + let data_space_tail = + raw.as_deref()[Self::DATA_SPACE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET].into(); #[allow(clippy::indexing_slicing)] - let base_addr = raw.as_deref()[16..24].into(); + let base_addr = raw.as_deref()[Self::BASE_ADDR_OFFSET..Self::ALLOC_ADDR_OFFSET].into(); #[allow(clippy::indexing_slicing)] - let alloc_addr = raw.as_deref()[24..].into(); + let alloc_addr = raw.as_deref()[Self::ALLOC_ADDR_OFFSET..].into(); Ok(Self { meta_space_tail, data_space_tail, diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 679bde29ae27..f841a312a523 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -48,7 +48,7 @@ impl DiskAddress { } /// Get the little endian bytes for a DiskAddress for storage - pub fn to_le_bytes(&self) -> [u8; 8] { + pub fn to_le_bytes(&self) -> [u8; Self::MSIZE as usize] { self.0.map(|v| v.get()).unwrap_or_default().to_le_bytes() } @@ -67,7 +67,7 @@ impl From for DiskAddress { /// Convert from a serialized le_bytes to a DiskAddress impl From<[u8; 8]> for DiskAddress { - fn from(value: [u8; 8]) -> Self { + fn from(value: [u8; Self::MSIZE as usize]) -> Self { Self::from(usize::from_le_bytes(value)) } } @@ -78,7 +78,7 @@ impl From<[u8; 8]> for DiskAddress { impl From<&[u8]> for DiskAddress { fn from(value: &[u8]) -> Self { #[allow(clippy::unwrap_used)] - let bytes: [u8; 8] = value.try_into().unwrap(); + let bytes: [u8; Self::MSIZE as usize] = value.try_into().unwrap(); bytes.into() } } From a6f817be847eab6d48b9d12c99270fa01653e72e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 20 Mar 2024 14:47:11 -0400 Subject: [PATCH 0515/1053] dont panic on invalid `DiskAddress` length (#595) --- firewood/src/db.rs | 9 +++++++-- firewood/src/shale/compact.rs | 19 +++++++++++++------ firewood/src/shale/disk_address.rs | 11 ++++++----- firewood/src/shale/mod.rs | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b8363d1d4174..b6e9d5a3dc8b 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -206,14 +206,19 @@ impl DbHeader { impl Storable for DbHeader { fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem + let root_bytes = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: Self::MSIZE, })?; + let root_bytes = root_bytes.as_deref(); + let root_bytes = root_bytes.as_slice(); + Ok(Self { - kv_root: raw.as_deref().as_slice().into(), + kv_root: root_bytes + .try_into() + .expect("Self::MSIZE == DiskAddress:MSIZE"), }) } diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 3d81a9a2d6d8..ad3eceb40171 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -225,14 +225,21 @@ impl Storable for CompactSpaceHeader { size: Self::MSIZE, })?; #[allow(clippy::indexing_slicing)] - let meta_space_tail = raw.as_deref()[..Self::DATA_SPACE_TAIL_OFFSET].into(); + let meta_space_tail = raw.as_deref()[..Self::DATA_SPACE_TAIL_OFFSET] + .try_into() + .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); #[allow(clippy::indexing_slicing)] - let data_space_tail = - raw.as_deref()[Self::DATA_SPACE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET].into(); + let data_space_tail = raw.as_deref()[Self::DATA_SPACE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET] + .try_into() + .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); #[allow(clippy::indexing_slicing)] - let base_addr = raw.as_deref()[Self::BASE_ADDR_OFFSET..Self::ALLOC_ADDR_OFFSET].into(); + let base_addr = raw.as_deref()[Self::BASE_ADDR_OFFSET..Self::ALLOC_ADDR_OFFSET] + .try_into() + .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); #[allow(clippy::indexing_slicing)] - let alloc_addr = raw.as_deref()[Self::ALLOC_ADDR_OFFSET..].into(); + let alloc_addr = raw.as_deref()[Self::ALLOC_ADDR_OFFSET..] + .try_into() + .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); Ok(Self { meta_space_tail, data_space_tail, @@ -656,7 +663,7 @@ impl CompactSpace { #[allow(clippy::unwrap_used)] if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { return Err(ShaleError::InvalidAddressLength { - expected: DiskAddress::from(CompactSpaceHeader::MSIZE as usize), + expected: CompactSpaceHeader::MSIZE, found: ptr.0.map(|inner| inner.get()).unwrap_or_default() as u64, }); } diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index f841a312a523..6b8d561e23f6 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -75,11 +75,12 @@ impl From<[u8; 8]> for DiskAddress { /// Convert from a slice of bytes to a DiskAddress /// panics if the slice isn't 8 bytes; used for /// serialization from disk -impl From<&[u8]> for DiskAddress { - fn from(value: &[u8]) -> Self { - #[allow(clippy::unwrap_used)] - let bytes: [u8; Self::MSIZE as usize] = value.try_into().unwrap(); - bytes.into() +impl TryFrom<&[u8]> for DiskAddress { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + let bytes: [u8; Self::MSIZE as usize] = value.try_into()?; + Ok(bytes.into()) } } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 77b06f4db084..af16ebb2fdf7 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -28,7 +28,7 @@ pub enum ShaleError { error: &'static str, }, #[error("invalid address length expected: {expected:?} found: {found:?})")] - InvalidAddressLength { expected: DiskAddress, found: u64 }, + InvalidAddressLength { expected: u64, found: u64 }, #[error("invalid node type")] InvalidNodeType, #[error("invalid node metadata")] From e72ab0e0493495cd39c861650b7e4d33b3c3da98 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 20 Mar 2024 14:59:12 -0400 Subject: [PATCH 0516/1053] delete unused sender.rs (#600) --- firewood/src/sender.rs | 47 ------------------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 firewood/src/sender.rs diff --git a/firewood/src/sender.rs b/firewood/src/sender.rs deleted file mode 100644 index 6dc2ea025ac6..000000000000 --- a/firewood/src/sender.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::api::DB; - -pub struct Sender {} - -impl, V: AsRef<[u8]>> DB for Sender { - fn kv_root_hash(&self) -> Result { - todo!() - } - - fn kv_get(&self, key: K) -> Option> { - todo!() - } - - fn kv_dump(&self, writer: W) -> Result<(), crate::db::DbError> { - todo!() - } - - fn root_hash(&self) -> Result { - todo!() - } - - fn dump(&self, writer: W) -> Result<(), crate::db::DbError> { - todo!() - } - - fn prove(&self, key: K) -> Result { - todo!() - } - - fn verify_range_proof( - &self, - proof: crate::proof::Proof, - first_key: K, - last_key: K, - keys: Vec, - values: Vec, - ) { - todo!() - } - - fn exist(&self, key: K) -> Result { - todo!() - } -} From 6d7e02ec963d7b4e8532ffa3ecb1b13b216dfe58 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 20 Mar 2024 12:06:54 -0700 Subject: [PATCH 0517/1053] Warning cleanups for next version of rust (#601) Co-authored-by: Dan Laine --- firewood/src/file.rs | 1 + firewood/src/merkle.rs | 2 +- firewood/src/merkle/node.rs | 2 +- firewood/src/merkle/node/branch.rs | 4 ++-- firewood/src/merkle/stream.rs | 3 +-- firewood/src/shale/compact.rs | 8 +++----- firewood/src/storage/buffer.rs | 4 +--- fwdctl/src/create.rs | 1 - fwdctl/src/delete.rs | 1 - fwdctl/src/dump.rs | 1 - fwdctl/src/get.rs | 1 - fwdctl/src/insert.rs | 1 - fwdctl/src/root.rs | 1 - growth-ring/tests/common/mod.rs | 1 - libaio/src/abi.rs | 1 - 15 files changed, 10 insertions(+), 22 deletions(-) diff --git a/firewood/src/file.rs b/firewood/src/file.rs index 0f9a7705b752..e1d5e45e1eec 100644 --- a/firewood/src/file.rs +++ b/firewood/src/file.rs @@ -54,6 +54,7 @@ impl File { filepath.push(fname); Ok(std::fs::File::options() .create(true) + .truncate(true) .read(true) .write(true) .mode(0o600) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b02b51b0aa95..4ffdae5e8148 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1392,7 +1392,7 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { mod tests { use super::*; use crate::merkle::node::PlainCodec; - use shale::{cached::InMemLinearStore, CachedStore}; + use shale::cached::InMemLinearStore; use test_case::test_case; fn leaf(path: Vec, value: Vec) -> Node { diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 2bf5636fbda4..b3ff322a7264 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -570,7 +570,7 @@ impl Serialize for EncodedNode { } if let Some(val) = &self.value { - list[BranchNode::MAX_CHILDREN] = val.clone(); + list[BranchNode::MAX_CHILDREN].clone_from(val); } let serialized_path = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index f511e87e80c7..5eeb71613a90 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -150,7 +150,7 @@ impl BranchNode { #[allow(clippy::indexing_slicing)] if let Some(v) = &self.children_encoded[i] { #[allow(clippy::indexing_slicing)] - (list[i] = v.clone()); + list[i].clone_from(v); } } }; @@ -158,7 +158,7 @@ impl BranchNode { #[allow(clippy::unwrap_used)] if let Some(val) = &self.value { - list[Self::MAX_CHILDREN] = val.clone(); + list[Self::MAX_CHILDREN].clone_from(val); } #[allow(clippy::unwrap_used)] diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index e8e3e16be068..56939d0ecb9e 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -585,7 +585,6 @@ mod tests { use crate::{merkle::Bincode, shale::cached::InMemLinearStore}; use super::*; - use futures::StreamExt; use test_case::test_case; impl Merkle { @@ -1102,7 +1101,7 @@ mod tests { assert!(first_key < intermediate); - let key_values = vec![ + let key_values = [ vec![first_key], vec![intermediate, intermediate], vec![intermediate, intermediate, intermediate], diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index ad3eceb40171..463209ad5ec4 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -691,7 +691,7 @@ impl CompactSpace { mod tests { use sha3::Digest; - use crate::shale::{self, cached::InMemLinearStore, ObjCache}; + use crate::shale::{self, cached::InMemLinearStore}; use super::*; @@ -787,15 +787,13 @@ mod tests { .cache .lock() .pinned - .get(&DiskAddress::from(4113)) - .is_some()); + .contains_key(&DiskAddress::from(4113))); // dirty assert!(obj_ref .cache .lock() .dirty - .get(&DiskAddress::from(4113)) - .is_some()); + .contains(&DiskAddress::from(4113))); drop(obj_ref); // write is visible assert_eq!( diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 75111f3babf2..92c4bbb41b10 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -638,15 +638,13 @@ impl DiskBufferRequester { #[allow(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { use sha3::Digest; - use std::path::{Path, PathBuf}; - use tokio::task::block_in_place; use super::*; use crate::shale::CachedStore; use crate::{ file, storage::{ - Ash, CachedSpace, DeltaPage, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, + Ash, CachedSpace, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, StoreRevShared, ZeroStore, }, }; diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 51d3ed25186b..a1984f7af851 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -6,7 +6,6 @@ use firewood::{ db::{Db, DbConfig, DbRevConfig, DiskBufferConfig, WalConfig}, v2::api, }; -use log; #[derive(Args)] pub struct Options { diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index c47f5a9b4b20..d16caa33771f 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -8,7 +8,6 @@ use firewood::{ db::{BatchOp, Db, DbConfig, WalConfig}, v2::api::{self, Db as _, Proposal}, }; -use log; #[derive(Debug, Args)] pub struct Options { diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 928574b4bf03..be601e14670b 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -8,7 +8,6 @@ use firewood::{ v2::api::{self, Db as _}, }; use futures_util::StreamExt; -use log; use std::borrow::Cow; #[derive(Debug, Args)] diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index a94c437d1c7d..c43fca80c732 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -6,7 +6,6 @@ use firewood::{ db::{Db, DbConfig, WalConfig}, v2::api::{self, Db as _, DbView}, }; -use log; use std::str; #[derive(Debug, Args)] diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 8cb56b8f7a93..09959a939a16 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -8,7 +8,6 @@ use firewood::{ db::{BatchOp, Db, DbConfig, WalConfig}, v2::api::{self, Db as _, Proposal}, }; -use log; #[derive(Debug, Args)] pub struct Options { diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 973d446165e9..ec4da10a17cd 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -7,7 +7,6 @@ use firewood::{ db::{Db, DbConfig, WalConfig}, v2::api, }; -use log; use std::str; #[derive(Debug, Args)] diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs index e601d5b8565c..152351b7dabe 100644 --- a/growth-ring/tests/common/mod.rs +++ b/growth-ring/tests/common/mod.rs @@ -11,7 +11,6 @@ use rand::Rng; use std::cell::RefCell; use std::collections::VecDeque; use std::collections::{hash_map, HashMap}; -use std::convert::TryInto; use std::path::PathBuf; use std::rc::Rc; diff --git a/libaio/src/abi.rs b/libaio/src/abi.rs index b4784bd5cbac..9d6fe58cb23f 100644 --- a/libaio/src/abi.rs +++ b/libaio/src/abi.rs @@ -7,7 +7,6 @@ pub use libc::timespec; use libc::{c_int, c_long, size_t}; -use std::default::Default; use std::mem::zeroed; #[repr(C)] From 7b2acf66374d8bcbba7b996762ddc2ada2f2f8f9 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 20 Mar 2024 16:52:21 -0400 Subject: [PATCH 0518/1053] rename MSIZE to SERIALIZED_LEN (#598) --- firewood/benches/hashops.rs | 4 +- firewood/benches/shale-bench.rs | 2 +- firewood/src/db.rs | 6 +- firewood/src/merkle.rs | 2 +- firewood/src/merkle/node/branch.rs | 6 +- firewood/src/merkle_util.rs | 9 ++- firewood/src/shale/compact.rs | 102 +++++++++++++++-------------- firewood/src/shale/disk_address.rs | 14 ++-- 8 files changed, 76 insertions(+), 69 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index cdebb7c9a1c6..bc171c66bbab 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -96,9 +96,9 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = StoredView::ptr_to_obj( - &InMemLinearStore::new(2 * CompactHeader::MSIZE, 9), + &InMemLinearStore::new(2 * CompactHeader::SERIALIZED_LEN, 9), merkle_payload_header, - CompactHeader::MSIZE, + CompactHeader::SERIALIZED_LEN, ) .unwrap(); diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 186136070b01..5677b37af6fa 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -82,7 +82,7 @@ fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); #[allow(clippy::unwrap_used)] let _: Obj = - StoredView::ptr_to_obj(m, compact_header_obj, CompactHeader::MSIZE).unwrap(); + StoredView::ptr_to_obj(m, compact_header_obj, CompactHeader::SERIALIZED_LEN).unwrap(); } fn bench_cursors(c: &mut Criterion) { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b6e9d5a3dc8b..9879403f8faf 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -700,7 +700,7 @@ impl Db { let db_header: DiskAddress = DiskAddress::from(offset); offset += DbHeader::MSIZE as usize; let merkle_payload_header: DiskAddress = DiskAddress::from(offset); - offset += CompactSpaceHeader::MSIZE as usize; + offset += CompactSpaceHeader::SERIALIZED_LEN as usize; assert!(offset <= SPACE_RESERVED as usize); let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); @@ -757,7 +757,7 @@ impl Db { StoredView::ptr_to_obj( meta_ref, payload_header, - shale::compact::CompactHeader::MSIZE, + shale::compact::CompactHeader::SERIALIZED_LEN, ) .map_err(Into::into) } @@ -777,7 +777,7 @@ impl Db { // TODO: This should be a compile time check const DB_OFFSET: u64 = Db::PARAM_SIZE; let merkle_offset = DB_OFFSET + DbHeader::MSIZE; - assert!(merkle_offset + CompactSpaceHeader::MSIZE <= SPACE_RESERVED); + assert!(merkle_offset + CompactSpaceHeader::SERIALIZED_LEN <= SPACE_RESERVED); let mut db_header_ref = header_refs.0; let merkle_payload_header_ref = header_refs.1; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4ffdae5e8148..4e2b39fabca2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1427,7 +1427,7 @@ mod tests { let compact_header = shale::StoredView::ptr_to_obj( &dm, compact_header, - shale::compact::CompactHeader::MSIZE, + shale::compact::CompactHeader::SERIALIZED_LEN, ) .unwrap(); let mem_meta = dm; diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index 5eeb71613a90..c4ef6466168f 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -174,7 +174,7 @@ impl BranchNode { impl Storable for BranchNode { fn serialized_len(&self) -> u64 { - let children_len = Self::MAX_CHILDREN as u64 * DiskAddress::MSIZE; + let children_len = Self::MAX_CHILDREN as u64 * DiskAddress::SERIALIZED_LEN; let value_len = optional_value_len::(self.value.as_deref()); let children_encoded_len = self.children_encoded.iter().fold(0, |len, child| { len + optional_value_len::(child.as_ref()) @@ -226,7 +226,7 @@ impl Storable for BranchNode { const PATH_LEN_SIZE: u64 = size_of::() as u64; const VALUE_LEN_SIZE: usize = size_of::(); const BRANCH_HEADER_SIZE: u64 = - BranchNode::MAX_CHILDREN as u64 * DiskAddress::MSIZE + VALUE_LEN_SIZE as u64; + BranchNode::MAX_CHILDREN as u64 * DiskAddress::SERIALIZED_LEN + VALUE_LEN_SIZE as u64; let path_len = mem .get_view(addr, PATH_LEN_SIZE) @@ -270,7 +270,7 @@ impl Storable for BranchNode { let mut cursor = Cursor::new(node_raw.as_deref()); let mut children = [None; BranchNode::MAX_CHILDREN]; - let mut buf = [0u8; DiskAddress::MSIZE as usize]; + let mut buf = [0u8; DiskAddress::SERIALIZED_LEN as usize]; for child in &mut children { cursor.read_exact(&mut buf)?; diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 897958d6bae6..632ea00f5c11 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -61,9 +61,12 @@ where ) .expect("write should succeed"); #[allow(clippy::unwrap_used)] - let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, shale::compact::CompactHeader::MSIZE) - .unwrap(); + let compact_header = StoredView::ptr_to_obj( + &dm, + compact_header, + shale::compact::CompactHeader::SERIALIZED_LEN, + ) + .unwrap(); let mem_meta = dm; let mem_payload = InMemLinearStore::new(compact_size, 0x1); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 463209ad5ec4..83ba143a1f86 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -24,7 +24,7 @@ pub struct CompactHeader { } impl CompactHeader { - pub const MSIZE: u64 = 17; + pub const SERIALIZED_LEN: u64 = 17; pub const fn is_freed(&self) -> bool { self.is_freed @@ -38,10 +38,10 @@ impl CompactHeader { impl Storable for CompactHeader { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] let payload_size = @@ -59,7 +59,7 @@ impl Storable for CompactHeader { } fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -77,16 +77,16 @@ struct CompactFooter { } impl CompactFooter { - const MSIZE: u64 = std::mem::size_of::() as u64; + const SERIALIZED_LEN: u64 = std::mem::size_of::() as u64; } impl Storable for CompactFooter { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: Self::SERIALIZED_LEN, })?; #[allow(clippy::unwrap_used)] let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); @@ -94,7 +94,7 @@ impl Storable for CompactFooter { } fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -110,16 +110,16 @@ struct CompactDescriptor { } impl CompactDescriptor { - const MSIZE: u64 = 16; + const SERIALIZED_LEN: u64 = 16; } impl Storable for CompactDescriptor { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] let payload_size = @@ -133,7 +133,7 @@ impl Storable for CompactDescriptor { } fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -171,11 +171,11 @@ impl CompactSpaceHeaderSliced { } impl CompactSpaceHeader { - pub const MSIZE: u64 = 4 * DiskAddress::MSIZE; + pub const SERIALIZED_LEN: u64 = 4 * DiskAddress::SERIALIZED_LEN; const META_SPACE_TAIL_OFFSET: usize = 0; - const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::MSIZE as usize; - const BASE_ADDR_OFFSET: usize = 2 * DiskAddress::MSIZE as usize; - const ALLOC_ADDR_OFFSET: usize = 3 * DiskAddress::MSIZE as usize; + const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; + const BASE_ADDR_OFFSET: usize = 2 * DiskAddress::SERIALIZED_LEN as usize; + const ALLOC_ADDR_OFFSET: usize = 3 * DiskAddress::SERIALIZED_LEN as usize; pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { @@ -191,25 +191,25 @@ impl CompactSpaceHeader { meta_space_tail: StoredView::slice( &r, Self::META_SPACE_TAIL_OFFSET, - DiskAddress::MSIZE, + DiskAddress::SERIALIZED_LEN, r.meta_space_tail, )?, data_space_tail: StoredView::slice( &r, Self::DATA_SPACE_TAIL_OFFSET, - DiskAddress::MSIZE, + DiskAddress::SERIALIZED_LEN, r.data_space_tail, )?, base_addr: StoredView::slice( &r, Self::BASE_ADDR_OFFSET, - DiskAddress::MSIZE, + DiskAddress::SERIALIZED_LEN, r.base_addr, )?, alloc_addr: StoredView::slice( &r, Self::ALLOC_ADDR_OFFSET, - DiskAddress::MSIZE, + DiskAddress::SERIALIZED_LEN, r.alloc_addr, )?, }) @@ -219,10 +219,10 @@ impl CompactSpaceHeader { impl Storable for CompactSpaceHeader { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] let meta_space_tail = raw.as_deref()[..Self::DATA_SPACE_TAIL_OFFSET] @@ -249,7 +249,7 @@ impl Storable for CompactSpaceHeader { } fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -285,7 +285,7 @@ impl From> for CompactSpaceInner impl CompactSpaceInner { fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.meta_space, ptr, CompactDescriptor::MSIZE) + StoredView::ptr_to_obj(&self.meta_space, ptr, CompactDescriptor::SERIALIZED_LEN) } fn get_data_ref( @@ -297,15 +297,15 @@ impl CompactSpaceInner { } fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactHeader::MSIZE) + self.get_data_ref::(ptr, CompactHeader::SERIALIZED_LEN) } fn get_footer(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactFooter::MSIZE) + self.get_data_ref::(ptr, CompactFooter::SERIALIZED_LEN) } fn del_desc(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { - let desc_size = CompactDescriptor::MSIZE; + let desc_size = CompactDescriptor::SERIALIZED_LEN; // TODO: subtracting two disk addresses is only used here, probably can rewrite this // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); #[allow(clippy::unwrap_used)] @@ -332,15 +332,15 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] self.header .meta_space_tail - .modify(|r| *r += CompactDescriptor::MSIZE as usize) + .modify(|r| *r += CompactDescriptor::SERIALIZED_LEN as usize) .unwrap(); Ok(DiskAddress(addr)) } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { - let hsize = CompactHeader::MSIZE; - let fsize = CompactFooter::MSIZE; + let hsize = CompactHeader::SERIALIZED_LEN; + let fsize = CompactFooter::SERIALIZED_LEN; let regn_size = 1 << self.regn_nbit; let mut offset = addr - hsize; @@ -424,9 +424,9 @@ impl CompactSpaceInner { return Ok(None); } - let hsize = CompactHeader::MSIZE as usize; - let fsize = CompactFooter::MSIZE as usize; - let dsize = CompactDescriptor::MSIZE as usize; + let hsize = CompactHeader::SERIALIZED_LEN as usize; + let fsize = CompactFooter::SERIALIZED_LEN as usize; + let dsize = CompactDescriptor::SERIALIZED_LEN as usize; let mut old_alloc_addr = *self.header.alloc_addr; @@ -531,7 +531,7 @@ impl CompactSpaceInner { fn alloc_new(&mut self, length: u64) -> Result { let regn_size = 1 << self.regn_nbit; - let total_length = CompactHeader::MSIZE + length + CompactFooter::MSIZE; + let total_length = CompactHeader::SERIALIZED_LEN + length + CompactFooter::SERIALIZED_LEN; let mut offset = *self.header.data_space_tail; #[allow(clippy::unwrap_used)] self.header @@ -547,7 +547,8 @@ impl CompactSpaceInner { }) .unwrap(); let mut h = self.get_header(offset)?; - let mut f = self.get_footer(offset + CompactHeader::MSIZE as usize + length as usize)?; + let mut f = + self.get_footer(offset + CompactHeader::SERIALIZED_LEN as usize + length as usize)?; #[allow(clippy::unwrap_used)] h.modify(|h| { h.payload_size = length; @@ -558,7 +559,10 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] f.modify(|f| f.payload_size = length).unwrap(); #[allow(clippy::unwrap_used)] - Ok((offset + CompactHeader::MSIZE as usize).0.unwrap().get() as u64) + Ok((offset + CompactHeader::SERIALIZED_LEN as usize) + .0 + .unwrap() + .get() as u64) } fn alloc(&mut self, length: u64) -> Result { @@ -661,15 +665,15 @@ impl CompactSpace { } #[allow(clippy::unwrap_used)] - if ptr < DiskAddress::from(CompactSpaceHeader::MSIZE as usize) { + if ptr < DiskAddress::from(CompactSpaceHeader::SERIALIZED_LEN as usize) { return Err(ShaleError::InvalidAddressLength { - expected: CompactSpaceHeader::MSIZE, + expected: CompactSpaceHeader::SERIALIZED_LEN, found: ptr.0.map(|inner| inner.get()).unwrap_or_default() as u64, }); } let payload_size = inner - .get_header(ptr - CompactHeader::MSIZE as usize)? + .get_header(ptr - CompactHeader::SERIALIZED_LEN as usize)? .payload_size; let obj = self.obj_cache.put(inner.get_data_ref(ptr, payload_size)?); let cache = &self.obj_cache; @@ -702,7 +706,7 @@ mod tests { pub struct Hash(pub [u8; HASH_SIZE]); impl Hash { - const MSIZE: u64 = 32; + const SERIALIZED_LEN: u64 = 32; } impl std::ops::Deref for Hash { @@ -714,21 +718,21 @@ mod tests { impl Storable for Hash { fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; + let raw = + mem.get_view(addr, Self::SERIALIZED_LEN) + .ok_or(ShaleError::InvalidCacheView { + offset: addr, + size: Self::SERIALIZED_LEN, + })?; Ok(Self( - raw.as_deref()[..Self::MSIZE as usize] + raw.as_deref()[..Self::SERIALIZED_LEN as usize] .try_into() .expect("invalid slice"), )) } fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -758,7 +762,7 @@ mod tests { ) .unwrap(); let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::MSIZE).unwrap(); + StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::SERIALIZED_LEN).unwrap(); let mem_meta = dm; let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 6b8d561e23f6..5b2491f49571 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -30,7 +30,7 @@ impl DerefMut for DiskAddress { } impl DiskAddress { - pub(crate) const MSIZE: u64 = size_of::() as u64; + pub(crate) const SERIALIZED_LEN: u64 = size_of::() as u64; /// Return a None DiskAddress pub const fn null() -> Self { @@ -48,7 +48,7 @@ impl DiskAddress { } /// Get the little endian bytes for a DiskAddress for storage - pub fn to_le_bytes(&self) -> [u8; Self::MSIZE as usize] { + pub fn to_le_bytes(&self) -> [u8; Self::SERIALIZED_LEN as usize] { self.0.map(|v| v.get()).unwrap_or_default().to_le_bytes() } @@ -67,7 +67,7 @@ impl From for DiskAddress { /// Convert from a serialized le_bytes to a DiskAddress impl From<[u8; 8]> for DiskAddress { - fn from(value: [u8; Self::MSIZE as usize]) -> Self { + fn from(value: [u8; Self::SERIALIZED_LEN as usize]) -> Self { Self::from(usize::from_le_bytes(value)) } } @@ -79,7 +79,7 @@ impl TryFrom<&[u8]> for DiskAddress { type Error = std::array::TryFromSliceError; fn try_from(value: &[u8]) -> Result { - let bytes: [u8; Self::MSIZE as usize] = value.try_into()?; + let bytes: [u8; Self::SERIALIZED_LEN as usize] = value.try_into()?; Ok(bytes.into()) } } @@ -166,7 +166,7 @@ impl std::ops::BitAnd for DiskAddress { impl Storable for DiskAddress { fn serialized_len(&self) -> u64 { - Self::MSIZE + Self::SERIALIZED_LEN } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { @@ -178,10 +178,10 @@ impl Storable for DiskAddress { fn deserialize(addr: usize, mem: &U) -> Result { let raw = mem - .get_view(addr, Self::MSIZE) + .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { offset: addr, - size: Self::MSIZE, + size: Self::SERIALIZED_LEN, })?; let addrdyn = &*raw; let addrvec = addrdyn.as_deref(); From 230a87c2d92c305fee0ac7a795bae255c08f15dc Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 20 Mar 2024 17:12:22 -0400 Subject: [PATCH 0519/1053] fix/clarify MSIZE declarations (#599) --- firewood/src/shale/compact.rs | 43 +++++++++++++++++++++--------- firewood/src/shale/disk_address.rs | 2 +- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 83ba143a1f86..cca03a4d0be7 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -24,7 +24,9 @@ pub struct CompactHeader { } impl CompactHeader { - pub const SERIALIZED_LEN: u64 = 17; + const IS_FREED_OFFSET: usize = std::mem::size_of::(); + const DESC_ADDR_OFFSET: usize = Self::IS_FREED_OFFSET + 1; + pub const SERIALIZED_LEN: u64 = (Self::DESC_ADDR_OFFSET + std::mem::size_of::()) as u64; pub const fn is_freed(&self) -> bool { self.is_freed @@ -44,13 +46,19 @@ impl Storable for CompactHeader { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] - let payload_size = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let payload_size = u64::from_le_bytes( + raw.as_deref()[..Self::IS_FREED_OFFSET] + .try_into() + .expect("invalid slice"), + ); #[allow(clippy::indexing_slicing)] - let is_freed = raw.as_deref()[8] != 0; + let is_freed = raw.as_deref()[Self::IS_FREED_OFFSET] != 0; #[allow(clippy::indexing_slicing)] - let desc_addr = - usize::from_le_bytes(raw.as_deref()[9..17].try_into().expect("invalid slice")); + let desc_addr = usize::from_le_bytes( + raw.as_deref()[Self::DESC_ADDR_OFFSET..Self::SERIALIZED_LEN as usize] + .try_into() + .expect("invalid slice"), + ); Ok(Self { payload_size, is_freed, @@ -110,7 +118,8 @@ struct CompactDescriptor { } impl CompactDescriptor { - const SERIALIZED_LEN: u64 = 16; + const HADDR_OFFSET: usize = 8; + const SERIALIZED_LEN: u64 = (Self::HADDR_OFFSET + std::mem::size_of::()) as u64; } impl Storable for CompactDescriptor { @@ -122,10 +131,17 @@ impl Storable for CompactDescriptor { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] - let payload_size = - u64::from_le_bytes(raw.as_deref()[..8].try_into().expect("invalid slice")); + let payload_size = u64::from_le_bytes( + raw.as_deref()[..Self::HADDR_OFFSET] + .try_into() + .expect("invalid slice"), + ); #[allow(clippy::indexing_slicing)] - let haddr = usize::from_le_bytes(raw.as_deref()[8..].try_into().expect("invalid slice")); + let haddr = usize::from_le_bytes( + raw.as_deref()[Self::HADDR_OFFSET..] + .try_into() + .expect("invalid slice"), + ); Ok(Self { payload_size, haddr, @@ -171,11 +187,12 @@ impl CompactSpaceHeaderSliced { } impl CompactSpaceHeader { - pub const SERIALIZED_LEN: u64 = 4 * DiskAddress::SERIALIZED_LEN; const META_SPACE_TAIL_OFFSET: usize = 0; const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; - const BASE_ADDR_OFFSET: usize = 2 * DiskAddress::SERIALIZED_LEN as usize; - const ALLOC_ADDR_OFFSET: usize = 3 * DiskAddress::SERIALIZED_LEN as usize; + const BASE_ADDR_OFFSET: usize = + Self::DATA_SPACE_TAIL_OFFSET + DiskAddress::SERIALIZED_LEN as usize; + const ALLOC_ADDR_OFFSET: usize = Self::BASE_ADDR_OFFSET + DiskAddress::SERIALIZED_LEN as usize; + pub const SERIALIZED_LEN: u64 = Self::ALLOC_ADDR_OFFSET as u64 + DiskAddress::SERIALIZED_LEN; pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index 5b2491f49571..f17ff850b225 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -30,7 +30,7 @@ impl DerefMut for DiskAddress { } impl DiskAddress { - pub(crate) const SERIALIZED_LEN: u64 = size_of::() as u64; + pub(crate) const SERIALIZED_LEN: u64 = size_of::() as u64; /// Return a None DiskAddress pub const fn null() -> Self { From c3861027e78ca918939724fab35ecdc60be1aaed Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Thu, 21 Mar 2024 09:53:40 -0400 Subject: [PATCH 0520/1053] remove PayloadSize type (#597) --- firewood/src/shale/compact.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index cca03a4d0be7..21fa0f4bca87 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -14,11 +14,9 @@ use std::io::{Cursor, Write}; use std::num::NonZeroUsize; use std::sync::RwLock; -type PayLoadSize = u64; - #[derive(Debug)] pub struct CompactHeader { - payload_size: PayLoadSize, + payload_size: u64, is_freed: bool, desc_addr: DiskAddress, } @@ -81,11 +79,11 @@ impl Storable for CompactHeader { #[derive(Debug)] struct CompactFooter { - payload_size: PayLoadSize, + payload_size: u64, } impl CompactFooter { - const SERIALIZED_LEN: u64 = std::mem::size_of::() as u64; + const SERIALIZED_LEN: u64 = std::mem::size_of::() as u64; } impl Storable for CompactFooter { @@ -113,7 +111,7 @@ impl Storable for CompactFooter { #[derive(Clone, Copy, Debug)] struct CompactDescriptor { - payload_size: PayLoadSize, + payload_size: u64, haddr: usize, // disk address of the free space } From 14126629017744f673dbacd7ae6a120ad52ed005 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 25 Mar 2024 12:24:17 -0400 Subject: [PATCH 0521/1053] Shale cleanup (#607) --- firewood/benches/hashops.rs | 2 +- firewood/benches/shale-bench.rs | 6 ++-- firewood/src/db.rs | 14 ++++----- firewood/src/db/proposal.rs | 2 +- firewood/src/merkle.rs | 10 +++---- firewood/src/merkle/node.rs | 12 ++++---- firewood/src/merkle/node/branch.rs | 6 ++-- firewood/src/merkle/node/leaf.rs | 2 +- firewood/src/merkle/proof.rs | 4 +-- firewood/src/merkle/stream.rs | 18 ++++++------ firewood/src/merkle/trie_hash.rs | 4 +-- firewood/src/merkle_util.rs | 2 +- firewood/src/shale/cached.rs | 15 +++++----- firewood/src/shale/compact.rs | 18 ++++++------ firewood/src/shale/disk_address.rs | 4 +-- firewood/src/shale/mod.rs | 47 ++++++++++++++++-------------- firewood/src/storage/buffer.rs | 2 +- firewood/src/storage/mod.rs | 26 ++++++++--------- 18 files changed, 99 insertions(+), 95 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bc171c66bbab..e325d7eea9ec 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -11,7 +11,7 @@ use firewood::{ cached::InMemLinearStore, compact::{CompactHeader, CompactSpace}, disk_address::DiskAddress, - CachedStore, ObjCache, Storable, StoredView, + LinearStore, ObjCache, Storable, StoredView, }, storage::WalConfig, v2::api::{Db, Proposal}, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 5677b37af6fa..7a82612ffc6b 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -8,7 +8,7 @@ use firewood::shale::{ cached::InMemLinearStore, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, - CachedStore, Obj, StoredView, + LinearStore, Obj, StoredView, }; use pprof::ProfilerGuard; use rand::Rng; @@ -57,7 +57,7 @@ impl Profiler for FlamegraphProfiler { } } -fn get_view(b: &mut Bencher, mut cached: C) { +fn get_view(b: &mut Bencher, mut cached: C) { let mut rng = rand::thread_rng(); b.iter(|| { @@ -78,7 +78,7 @@ fn get_view(b: &mut Bencher, mut cached: C) { }); } -fn serialize(m: &T) { +fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); #[allow(clippy::unwrap_used)] let _: Obj = diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 9879403f8faf..55147fd726a8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -22,7 +22,7 @@ use crate::{ use crate::{ merkle, shale::{ - self, compact::CompactSpaceHeader, disk_address::DiskAddress, CachedStore, Obj, ShaleError, + self, compact::CompactSpaceHeader, disk_address::DiskAddress, LinearStore, Obj, ShaleError, SpaceId, Storable, StoredView, }, }; @@ -205,7 +205,7 @@ impl DbHeader { } impl Storable for DbHeader { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let root_bytes = mem .get_view(addr, Self::MSIZE) .ok_or(ShaleError::InvalidCacheView { @@ -285,7 +285,7 @@ pub struct DbRev { } #[async_trait] -impl api::DbView for DbRev { +impl api::DbView for DbRev { type Stream<'a> = MerkleKeyValueStream<'a, T, Bincode> where Self: 'a; async fn root_hash(&self) -> Result { @@ -338,7 +338,7 @@ impl api::DbView for DbRev { } } -impl DbRev { +impl DbRev { pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { self.merkle.key_value_iter(self.header.kv_root) } @@ -749,7 +749,7 @@ impl Db { Ok((store, rev)) } - fn get_payload_header_ref( + fn get_payload_header_ref( meta_ref: &K, header_offset: u64, ) -> Result, DbError> { @@ -762,12 +762,12 @@ impl Db { .map_err(Into::into) } - fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { + fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { let db_header = DiskAddress::from(Db::PARAM_SIZE as usize); StoredView::ptr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) } - fn new_revision>( + fn new_revision>( header_refs: (Obj, Obj), merkle: (T, T), payload_regn_nbit: u64, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index cad50e44bcd7..e6c96ff2f046 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -6,7 +6,7 @@ use super::{ Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, }; use crate::merkle::{Bincode, MerkleKeyValueStream, Proof}; -use crate::shale::CachedStore; +use crate::shale::LinearStore; use crate::storage::StoreRevShared; use crate::{ merkle::{TrieHash, TRIE_HASH_LEN}, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4e2b39fabca2..e9ed3bc6dd5f 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; use crate::shale::compact::CompactSpace; -use crate::shale::CachedStore; +use crate::shale::LinearStore; use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError}; use crate::storage::{StoreRevMut, StoreRevShared}; use crate::v2::api; @@ -81,7 +81,7 @@ impl From> for Merkle { } } -impl Merkle { +impl Merkle { pub fn get_node(&self, ptr: DiskAddress) -> Result { self.store.get_item(ptr).map_err(Into::into) } @@ -97,7 +97,7 @@ impl Merkle { impl<'de, S, T> Merkle where - S: CachedStore, + S: LinearStore, T: BinarySerde, EncodedNode: serde::Serialize + serde::Deserialize<'de>, { @@ -178,7 +178,7 @@ where } } -impl Merkle { +impl Merkle { pub fn init_root(&self) -> Result { self.store .put_item( @@ -1299,7 +1299,7 @@ impl<'a, S, T> RefMut<'a, S, T> { } } -impl<'a, S: CachedStore, T> RefMut<'a, S, T> { +impl<'a, S: LinearStore, T> RefMut<'a, S, T> { #[allow(clippy::unwrap_used)] pub fn get(&self) -> Ref { Ref(self.merkle.get_node(self.ptr).unwrap()) diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index b3ff322a7264..9e90f68c9b0a 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -4,7 +4,7 @@ use crate::{ logger::trace, merkle::nibbles_to_bytes_iter, - shale::{compact::CompactSpace, disk_address::DiskAddress, CachedStore, ShaleError, Storable}, + shale::{compact::CompactSpace, disk_address::DiskAddress, LinearStore, ShaleError, Storable}, }; use bincode::{Error, Options}; use bitflags::bitflags; @@ -81,7 +81,7 @@ impl NodeType { } } - pub fn encode(&self, store: &CompactSpace) -> Vec { + pub fn encode(&self, store: &CompactSpace) -> Vec { match &self { NodeType::Leaf(n) => n.encode(), NodeType::Branch(n) => n.encode(store), @@ -201,18 +201,18 @@ impl Node { }) } - pub(super) fn get_encoded(&self, store: &CompactSpace) -> &[u8] { + pub(super) fn get_encoded(&self, store: &CompactSpace) -> &[u8] { self.encoded.get_or_init(|| self.inner.encode(store)) } - pub(super) fn get_root_hash(&self, store: &CompactSpace) -> &TrieHash { + pub(super) fn get_root_hash(&self, store: &CompactSpace) -> &TrieHash { self.root_hash.get_or_init(|| { self.set_dirty(true); TrieHash(Keccak256::digest(self.get_encoded(store)).into()) }) } - fn is_encoded_longer_than_hash_len( + fn is_encoded_longer_than_hash_len( &self, store: &CompactSpace, ) -> bool { @@ -328,7 +328,7 @@ mod type_id { use type_id::NodeTypeId; impl Storable for Node { - fn deserialize(offset: usize, mem: &T) -> Result { + fn deserialize(offset: usize, mem: &T) -> Result { let meta_raw = mem.get_view(offset, Meta::SIZE as u64) .ok_or(ShaleError::InvalidCacheView { diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index c4ef6466168f..f55d62e60458 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -5,7 +5,7 @@ use super::Node; use crate::{ merkle::{nibbles_to_bytes_iter, to_nibble_array, Path}, nibbles::Nibbles, - shale::{compact::CompactSpace, CachedStore, DiskAddress, ShaleError, Storable}, + shale::{compact::CompactSpace, DiskAddress, LinearStore, ShaleError, Storable}, }; use bincode::{Error, Options}; use serde::de::Error as DeError; @@ -111,7 +111,7 @@ impl BranchNode { }) } - pub(super) fn encode(&self, store: &CompactSpace) -> Vec { + pub(super) fn encode(&self, store: &CompactSpace) -> Vec { // path + children + value let mut list = <[Vec; Self::MSIZE]>::default(); @@ -219,7 +219,7 @@ impl Storable for BranchNode { Ok(()) } - fn deserialize( + fn deserialize( mut addr: usize, mem: &T, ) -> Result { diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs index 3cc438940e43..c582edece994 100644 --- a/firewood/src/merkle/node/leaf.rs +++ b/firewood/src/merkle/node/leaf.rs @@ -112,7 +112,7 @@ impl Storable for LeafNode { Ok(()) } - fn deserialize( + fn deserialize( offset: usize, mem: &T, ) -> Result diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index ca4c4b5bc62f..03335f7af946 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -5,7 +5,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use crate::shale::{disk_address::DiskAddress, ShaleError}; -use crate::shale::{CachedStore, ObjWriteSizeError}; +use crate::shale::{LinearStore, ObjWriteSizeError}; use crate::v2::api::HashKey; use aiofut::AioError; use nix::errno::Errno; @@ -708,7 +708,7 @@ where // keep the entire branch and return. // - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -fn unset_node_ref, S: CachedStore, T: BinarySerde>( +fn unset_node_ref, S: LinearStore, T: BinarySerde>( merkle: &Merkle, parent: DiskAddress, node: Option, diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 56939d0ecb9e..54b2df1e180c 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -4,7 +4,7 @@ use super::{BranchNode, Key, Merkle, MerkleError, NodeObjRef, NodeType, Value}; use crate::{ nibbles::{Nibbles, NibblesIterator}, - shale::{CachedStore, DiskAddress}, + shale::{DiskAddress, LinearStore}, v2::api, }; use futures::{stream::FusedStream, Stream, StreamExt}; @@ -73,7 +73,7 @@ pub struct MerkleNodeStream<'a, S, T> { merkle: &'a Merkle, } -impl<'a, S: CachedStore, T> FusedStream for MerkleNodeStream<'a, S, T> { +impl<'a, S: LinearStore, T> FusedStream for MerkleNodeStream<'a, S, T> { fn is_terminated(&self) -> bool { // The top of `iter_stack` is the next node to return. // If `iter_stack` is empty, there are no more nodes to visit. @@ -93,7 +93,7 @@ impl<'a, S, T> MerkleNodeStream<'a, S, T> { } } -impl<'a, S: CachedStore, T> Stream for MerkleNodeStream<'a, S, T> { +impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { type Item = Result<(Key, NodeObjRef<'a>), api::Error>; fn poll_next( @@ -178,7 +178,7 @@ impl<'a, S: CachedStore, T> Stream for MerkleNodeStream<'a, S, T> { /// Returns the initial state for an iterator over the given `merkle` with root `root_node` /// which starts at `key`. -fn get_iterator_intial_state<'a, S: CachedStore, T>( +fn get_iterator_intial_state<'a, S: LinearStore, T>( merkle: &'a Merkle, root_node: DiskAddress, key: &[u8], @@ -321,7 +321,7 @@ pub struct MerkleKeyValueStream<'a, S, T> { merkle: &'a Merkle, } -impl<'a, S: CachedStore, T> FusedStream for MerkleKeyValueStream<'a, S, T> { +impl<'a, S: LinearStore, T> FusedStream for MerkleKeyValueStream<'a, S, T> { fn is_terminated(&self) -> bool { matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) } @@ -345,7 +345,7 @@ impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { } } -impl<'a, S: CachedStore, T> Stream for MerkleKeyValueStream<'a, S, T> { +impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { type Item = Result<(Key, Value), api::Error>; fn poll_next( @@ -426,7 +426,7 @@ pub struct PathIterator<'a, 'b, S, T> { merkle: &'a Merkle, } -impl<'a, 'b, S: CachedStore, T> PathIterator<'a, 'b, S, T> { +impl<'a, 'b, S: LinearStore, T> PathIterator<'a, 'b, S, T> { pub(super) fn new( merkle: &'a Merkle, sentinel_node: NodeObjRef<'a>, @@ -456,7 +456,7 @@ impl<'a, 'b, S: CachedStore, T> PathIterator<'a, 'b, S, T> { } } -impl<'a, 'b, S: CachedStore, T> Iterator for PathIterator<'a, 'b, S, T> { +impl<'a, 'b, S: LinearStore, T> Iterator for PathIterator<'a, 'b, S, T> { type Item = Result<(Key, NodeObjRef<'a>), MerkleError>; fn next(&mut self) -> Option { @@ -587,7 +587,7 @@ mod tests { use super::*; use test_case::test_case; - impl Merkle { + impl Merkle { pub(crate) fn node_iter(&self, root: DiskAddress) -> MerkleNodeStream<'_, S, T> { MerkleNodeStream::new(self, root, Box::new([])) } diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs index 08195e249b0e..7cfc099092da 100644 --- a/firewood/src/merkle/trie_hash.rs +++ b/firewood/src/merkle/trie_hash.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::shale::{CachedStore, ShaleError, Storable}; +use crate::shale::{LinearStore, ShaleError, Storable}; use std::{ fmt::{self, Debug}, io::Write, @@ -21,7 +21,7 @@ impl std::ops::Deref for TrieHash { } impl Storable for TrieHash { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, U64_TRIE_HASH_LEN) .ok_or(ShaleError::InvalidCacheView { diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 632ea00f5c11..1257558373de 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -6,7 +6,7 @@ use crate::{ proof::{Proof, ProofError}, BinarySerde, EncodedNode, Merkle, Ref, RefMut, TrieHash, }, - shale::{self, cached::InMemLinearStore, disk_address::DiskAddress, CachedStore, StoredView}, + shale::{self, cached::InMemLinearStore, disk_address::DiskAddress, LinearStore, StoredView}, }; use std::num::NonZeroUsize; use thiserror::Error; diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 65064104b5eb..4e02220cd17e 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::shale::{CachedStore, CachedView, SendSyncDerefMut, SpaceId}; +use crate::shale::{LinearStore, LinearStoreView, SendSyncDerefMut, SpaceId}; use std::{ fmt::Debug, ops::{Deref, DerefMut}, @@ -25,12 +25,12 @@ impl InMemLinearStore { } } -impl CachedStore for InMemLinearStore { +impl LinearStore for InMemLinearStore { fn get_view( &self, offset: usize, length: u64, - ) -> Option>>> { + ) -> Option>>> { let length = length as usize; let size = offset + length; #[allow(clippy::unwrap_used)] @@ -51,7 +51,7 @@ impl CachedStore for InMemLinearStore { })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(InMemLinearStoreShared(Self { space: self.space.clone(), id: self.id, @@ -98,8 +98,9 @@ struct InMemLinearStoreView { struct InMemLinearStoreShared(InMemLinearStore); impl Deref for InMemLinearStoreShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { + type Target = dyn LinearStore; + + fn deref(&self) -> &(dyn LinearStore + 'static) { &self.0 } } @@ -110,7 +111,7 @@ impl DerefMut for InMemLinearStoreShared { } } -impl CachedView for InMemLinearStoreView { +impl LinearStoreView for InMemLinearStoreView { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 21fa0f4bca87..cecbbb92db48 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -7,7 +7,7 @@ use crate::shale::ObjCache; use crate::storage::{StoreRevMut, StoreRevShared}; use super::disk_address::DiskAddress; -use super::{CachedStore, Obj, ObjRef, ShaleError, Storable, StoredView}; +use super::{LinearStore, Obj, ObjRef, ShaleError, Storable, StoredView}; use bytemuck::{Pod, Zeroable}; use std::fmt::Debug; use std::io::{Cursor, Write}; @@ -36,7 +36,7 @@ impl CompactHeader { } impl Storable for CompactHeader { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { @@ -87,7 +87,7 @@ impl CompactFooter { } impl Storable for CompactFooter { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { @@ -121,7 +121,7 @@ impl CompactDescriptor { } impl Storable for CompactDescriptor { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { @@ -232,7 +232,7 @@ impl CompactSpaceHeader { } impl Storable for CompactSpaceHeader { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { @@ -298,7 +298,7 @@ impl From> for CompactSpaceInner } } -impl CompactSpaceInner { +impl CompactSpaceInner { fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { StoredView::ptr_to_obj(&self.meta_space, ptr, CompactDescriptor::SERIALIZED_LEN) } @@ -597,7 +597,7 @@ pub struct CompactSpace { obj_cache: ObjCache, } -impl CompactSpace { +impl CompactSpace { pub fn new( meta_space: M, data_space: M, @@ -631,7 +631,7 @@ impl From> for CompactSpace CompactSpace { +impl CompactSpace { pub(crate) fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; #[allow(clippy::unwrap_used)] @@ -732,7 +732,7 @@ mod tests { } impl Storable for Hash { - fn deserialize(addr: usize, mem: &T) -> Result { + fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem.get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs index f17ff850b225..e96b1eb701c9 100644 --- a/firewood/src/shale/disk_address.rs +++ b/firewood/src/shale/disk_address.rs @@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut}; use bytemuck::{Pod, Zeroable}; -use crate::shale::{CachedStore, ShaleError, Storable}; +use crate::shale::{LinearStore, ShaleError, Storable}; /// The virtual disk address of an object #[repr(transparent)] @@ -176,7 +176,7 @@ impl Storable for DiskAddress { Ok(()) } - fn deserialize(addr: usize, mem: &U) -> Result { + fn deserialize(addr: usize, mem: &U) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) .ok_or(ShaleError::InvalidCacheView { diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index af16ebb2fdf7..cfd182c54033 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -51,8 +51,8 @@ pub struct ObjWriteSizeError; pub type SpaceId = u8; pub const INVALID_SPACE_ID: SpaceId = 0xff; -/// A handle that pins and provides a readable access to a portion of the linear memory image. -pub trait CachedView { +/// A handle that pins and provides a readable access to a portion of a [LinearStore]. +pub trait LinearStoreView { type DerefReturn: Deref; fn as_deref(&self) -> Self::DerefReturn; } @@ -63,21 +63,24 @@ impl SendSyncDerefMut for T {} /// In-memory store that offers access to intervals from a linear byte space, which is usually /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear -/// persistent store. Reads could trigger disk reads to bring data into memory, but writes will -/// *only* be visible in memory (it does not write back to the disk). -pub trait CachedStore: Debug + Send + Sync { - /// Returns a handle that pins the `length` of bytes starting from `offset` and makes them - /// directly accessible. +/// persistent store. Reads may trigger disk reads to bring data into memory, but writes will +/// *only* be visible in memory -- they do not write to disk. +pub trait LinearStore: Debug + Send + Sync { + /// Returns a view containing `length` bytes starting from `offset` from this + /// store. The returned view is pinned. fn get_view( &self, offset: usize, length: u64, - ) -> Option>>>; - /// Returns a handle that allows shared access to the store. - fn get_shared(&self) -> Box>; - /// Write the `change` to the portion of the linear space starting at `offset`. The change - /// should be immediately visible to all `CachedView` associated to this linear space. + ) -> Option>>>; + + /// Returns a handle that allows shared access to this store. + fn get_shared(&self) -> Box>; + + /// Write the `change` to the linear space starting at `offset`. The change should + /// be immediately visible to all `LinearStoreView` associated with this linear space. fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError>; + /// Returns the identifier of this storage space. fn id(&self) -> SpaceId; @@ -135,7 +138,7 @@ impl Obj { #[allow(clippy::unwrap_used)] self.value.serialize(&mut new_value).unwrap(); let offset = self.value.get_offset(); - let bx: &mut dyn CachedStore = self.value.get_mut_mem_store(); + let bx: &mut dyn LinearStore = self.value.get_mut_mem_store(); bx.write(offset, &new_value).expect("write should succeed"); } } @@ -248,7 +251,7 @@ impl<'a, T: Storable> Drop for ObjRef<'a, T> { pub trait Storable { fn serialized_len(&self) -> u64; fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError>; - fn deserialize(addr: usize, mem: &T) -> Result + fn deserialize(addr: usize, mem: &T) -> Result where Self: Sized; } @@ -263,7 +266,7 @@ pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { pub struct StoredView { /// The item this stores. item: T, - mem: Box>, + mem: Box>, offset: usize, /// If the serialized length of `item` is greater than this, /// `serialized_len` will return `None`. @@ -298,11 +301,11 @@ impl StoredView { self.offset } - fn get_mem_store(&self) -> &dyn CachedStore { + fn get_mem_store(&self) -> &dyn LinearStore { &**self.mem } - fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore { + fn get_mut_mem_store(&mut self) -> &mut dyn LinearStore { &mut **self.mem } @@ -327,7 +330,7 @@ impl StoredView { impl StoredView { #[inline(always)] - fn new(offset: usize, len_limit: u64, space: &U) -> Result { + fn new(offset: usize, len_limit: u64, space: &U) -> Result { let item = T::deserialize(offset, space)?; Ok(Self { @@ -343,7 +346,7 @@ impl StoredView { offset: usize, len_limit: u64, item: T, - space: &dyn CachedStore, + space: &dyn LinearStore, ) -> Result { Ok(Self { offset, @@ -354,7 +357,7 @@ impl StoredView { } #[inline(always)] - pub fn ptr_to_obj( + pub fn ptr_to_obj( store: &U, ptr: DiskAddress, len_limit: u64, @@ -368,7 +371,7 @@ impl StoredView { #[inline(always)] pub fn item_to_obj( - store: &dyn CachedStore, + store: &dyn LinearStore, addr: usize, len_limit: u64, item: T, @@ -384,7 +387,7 @@ impl StoredView { offset: usize, len_limit: u64, item: T, - space: &dyn CachedStore, + space: &dyn LinearStore, ) -> Result { Ok(Self { offset, diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 92c4bbb41b10..75295d29604c 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -640,7 +640,7 @@ mod tests { use sha3::Digest; use super::*; - use crate::shale::CachedStore; + use crate::shale::LinearStore; use crate::{ file, storage::{ diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index ba316d9adc35..a073dde85c42 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,7 +4,7 @@ // TODO: try to get rid of the use `RefCell` in this file use self::buffer::DiskBufferRequester; use crate::file::File; -use crate::shale::{self, CachedStore, CachedView, SendSyncDerefMut, ShaleError, SpaceId}; +use crate::shale::{self, LinearStore, LinearStoreView, SendSyncDerefMut, ShaleError, SpaceId}; use nix::fcntl::{Flock, FlockArg}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -335,17 +335,17 @@ impl StoreRevShared { } } -impl CachedStore for StoreRevShared { +impl LinearStore for StoreRevShared { fn get_view( &self, offset: usize, length: u64, - ) -> Option>>> { + ) -> Option>>> { let data = self.0.get_slice(offset as u64, length)?; Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(StoreShared(self.clone())) } @@ -394,7 +394,7 @@ impl Deref for StoreRef { } } -impl CachedView for StoreRef { +impl LinearStoreView for StoreRef { type DerefReturn = Vec; fn as_deref(&self) -> Self::DerefReturn { @@ -402,16 +402,16 @@ impl CachedView for StoreRef { } } -struct StoreShared(S); +struct StoreShared(S); -impl Deref for StoreShared { - type Target = dyn CachedStore; - fn deref(&self) -> &(dyn CachedStore + 'static) { +impl Deref for StoreShared { + type Target = dyn LinearStore; + fn deref(&self) -> &(dyn LinearStore + 'static) { &self.0 } } -impl DerefMut for StoreShared { +impl DerefMut for StoreShared { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -505,12 +505,12 @@ impl StoreRevMut { } } -impl CachedStore for StoreRevMut { +impl LinearStore for StoreRevMut { fn get_view( &self, offset: usize, length: u64, - ) -> Option>>> { + ) -> Option>>> { let data = if length == 0 { Vec::new() } else { @@ -572,7 +572,7 @@ impl CachedStore for StoreRevMut { Some(Box::new(StoreRef { data })) } - fn get_shared(&self) -> Box> { + fn get_shared(&self) -> Box> { Box::new(StoreShared(self.clone())) } From 061d55c9794988638e16641a42caf723ffab60ae Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 25 Mar 2024 12:32:59 -0400 Subject: [PATCH 0522/1053] nit reorder impl (#608) --- firewood/src/shale/cached.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/cached.rs index 4e02220cd17e..78e0c4cb94d3 100644 --- a/firewood/src/shale/cached.rs +++ b/firewood/src/shale/cached.rs @@ -95,6 +95,15 @@ struct InMemLinearStoreView { mem: InMemLinearStore, } +impl LinearStoreView for InMemLinearStoreView { + type DerefReturn = Vec; + + fn as_deref(&self) -> Self::DerefReturn { + #[allow(clippy::indexing_slicing, clippy::unwrap_used)] + self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() + } +} + struct InMemLinearStoreShared(InMemLinearStore); impl Deref for InMemLinearStoreShared { @@ -111,15 +120,6 @@ impl DerefMut for InMemLinearStoreShared { } } -impl LinearStoreView for InMemLinearStoreView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() - } -} - #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { From edad8d568c17ecefc6748ed6ff873f73eb2638eb Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 25 Mar 2024 13:14:40 -0400 Subject: [PATCH 0523/1053] rename cached.rs to in_mem.rs (#609) --- firewood/benches/hashops.rs | 2 +- firewood/benches/shale-bench.rs | 2 +- firewood/src/merkle.rs | 4 ++-- firewood/src/merkle/node.rs | 2 +- firewood/src/merkle/stream.rs | 2 +- firewood/src/merkle_util.rs | 2 +- firewood/src/shale/compact.rs | 2 +- firewood/src/shale/{cached.rs => in_mem.rs} | 0 firewood/src/shale/mod.rs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename firewood/src/shale/{cached.rs => in_mem.rs} (100%) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index e325d7eea9ec..d3a8d138badb 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -8,9 +8,9 @@ use firewood::{ db::{BatchOp, DbConfig}, merkle::{Bincode, Merkle, TrieHash, TRIE_HASH_LEN}, shale::{ - cached::InMemLinearStore, compact::{CompactHeader, CompactSpace}, disk_address::DiskAddress, + in_mem::InMemLinearStore, LinearStore, ObjCache, Storable, StoredView, }, storage::WalConfig, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 7a82612ffc6b..3b27c7d21258 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -5,9 +5,9 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; use firewood::shale::{ - cached::InMemLinearStore, compact::{CompactHeader, CompactSpaceHeader}, disk_address::DiskAddress, + in_mem::InMemLinearStore, LinearStore, Obj, StoredView, }; use pprof::ProfilerGuard; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e9ed3bc6dd5f..e510d31165ad 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1392,7 +1392,7 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { mod tests { use super::*; use crate::merkle::node::PlainCodec; - use shale::cached::InMemLinearStore; + use shale::in_mem::InMemLinearStore; use test_case::test_case; fn leaf(path: Vec, value: Vec) -> Node { @@ -1413,7 +1413,7 @@ mod tests { { const RESERVED: usize = 0x1000; - let mut dm = shale::cached::InMemLinearStore::new(0x10000, 0); + let mut dm = shale::in_mem::InMemLinearStore::new(0x10000, 0); let compact_header = DiskAddress::null(); dm.write( compact_header.into(), diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 9e90f68c9b0a..3cc39debb4a9 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -736,7 +736,7 @@ impl BinarySerde for PlainCodec { #[cfg(test)] mod tests { use super::*; - use crate::shale::cached::InMemLinearStore; + use crate::shale::in_mem::InMemLinearStore; use std::iter::repeat; use test_case::{test_case, test_matrix}; diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 54b2df1e180c..7e3fefb2e075 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -582,7 +582,7 @@ use super::tests::create_test_merkle; #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use crate::{merkle::Bincode, shale::cached::InMemLinearStore}; + use crate::{merkle::Bincode, shale::in_mem::InMemLinearStore}; use super::*; use test_case::test_case; diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 1257558373de..e0fe2baba6ce 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -6,7 +6,7 @@ use crate::{ proof::{Proof, ProofError}, BinarySerde, EncodedNode, Merkle, Ref, RefMut, TrieHash, }, - shale::{self, cached::InMemLinearStore, disk_address::DiskAddress, LinearStore, StoredView}, + shale::{self, disk_address::DiskAddress, in_mem::InMemLinearStore, LinearStore, StoredView}, }; use std::num::NonZeroUsize; use thiserror::Error; diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index cecbbb92db48..26f5ae962642 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -710,7 +710,7 @@ impl CompactSpace { mod tests { use sha3::Digest; - use crate::shale::{self, cached::InMemLinearStore}; + use crate::shale::{self, in_mem::InMemLinearStore}; use super::*; diff --git a/firewood/src/shale/cached.rs b/firewood/src/shale/in_mem.rs similarity index 100% rename from firewood/src/shale/cached.rs rename to firewood/src/shale/in_mem.rs diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index cfd182c54033..a7bcf2bb31dd 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -14,9 +14,9 @@ use thiserror::Error; use crate::merkle::{LeafNode, Node, Path}; -pub mod cached; pub mod compact; pub mod disk_address; +pub mod in_mem; #[derive(Debug, Error)] #[non_exhaustive] From ea68a292e4d1cc523bc14dc285d0166fcfcc04ea Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 25 Mar 2024 21:54:41 -0400 Subject: [PATCH 0524/1053] Rename `Compact` types (#610) --- firewood/benches/hashops.rs | 8 +- firewood/benches/shale-bench.rs | 6 +- firewood/src/db.rs | 26 ++-- firewood/src/merkle.rs | 14 +- firewood/src/merkle/node.rs | 13 +- firewood/src/merkle/node/branch.rs | 4 +- firewood/src/merkle_util.rs | 8 +- firewood/src/shale/compact.rs | 221 ++++++++++++++--------------- firewood/src/shale/mod.rs | 2 +- 9 files changed, 148 insertions(+), 154 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index d3a8d138badb..bc37258470de 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -8,7 +8,7 @@ use firewood::{ db::{BatchOp, DbConfig}, merkle::{Bincode, Merkle, TrieHash, TRIE_HASH_LEN}, shale::{ - compact::{CompactHeader, CompactSpace}, + compact::{ChunkHeader, Store}, disk_address::DiskAddress, in_mem::InMemLinearStore, LinearStore, ObjCache, Storable, StoredView, @@ -96,14 +96,14 @@ fn bench_merkle(criterion: &mut Criterion) { #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = StoredView::ptr_to_obj( - &InMemLinearStore::new(2 * CompactHeader::SERIALIZED_LEN, 9), + &InMemLinearStore::new(2 * ChunkHeader::SERIALIZED_LEN, 9), merkle_payload_header, - CompactHeader::SERIALIZED_LEN, + ChunkHeader::SERIALIZED_LEN, ) .unwrap(); #[allow(clippy::unwrap_used)] - let store = CompactSpace::new( + let store = Store::new( InMemLinearStore::new(TEST_MEM_SIZE, 0), InMemLinearStore::new(TEST_MEM_SIZE, 1), merkle_payload_header_ref, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index 3b27c7d21258..fd4674c46829 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -5,7 +5,7 @@ use criterion::{ black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, }; use firewood::shale::{ - compact::{CompactHeader, CompactSpaceHeader}, + compact::{ChunkHeader, StoreHeader}, disk_address::DiskAddress, in_mem::InMemLinearStore, LinearStore, Obj, StoredView, @@ -81,8 +81,8 @@ fn get_view(b: &mut Bencher, mut cached: C) { fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); #[allow(clippy::unwrap_used)] - let _: Obj = - StoredView::ptr_to_obj(m, compact_header_obj, CompactHeader::SERIALIZED_LEN).unwrap(); + let _: Obj = + StoredView::ptr_to_obj(m, compact_header_obj, ChunkHeader::SERIALIZED_LEN).unwrap(); } fn bench_cursors(c: &mut Criterion) { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 55147fd726a8..21678e1e05cd 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -22,7 +22,7 @@ use crate::{ use crate::{ merkle, shale::{ - self, compact::CompactSpaceHeader, disk_address::DiskAddress, LinearStore, Obj, ShaleError, + self, compact::StoreHeader, disk_address::DiskAddress, LinearStore, Obj, ShaleError, SpaceId, Storable, StoredView, }, }; @@ -118,7 +118,7 @@ struct DbParams { } #[derive(Clone, Debug)] -/// Necessary linear space instances bundled for a `CompactSpace`. +/// Necessary linear space instances bundled for a `Store`. struct SubUniverse { meta: T, payload: T, @@ -616,7 +616,7 @@ impl Db { let meta: StoreRevMut = base.merkle.meta.clone().into(); let payload: StoreRevMut = base.merkle.payload.clone().into(); - // get references to the DbHeader and the CompactSpaceHeader + // get references to the DbHeader and the StoreHeader // for free space management let db_header_ref = Db::get_db_header_ref(&meta)?; let merkle_payload_header_ref = @@ -656,7 +656,7 @@ impl Db { // The header consists of three parts: // DbParams // DbHeader (just a pointer to the sentinel) - // CompactSpaceHeader for future allocations + // StoreHeader for future allocations let (params, hdr, csh); let header_bytes: Vec = { params = DbParams { @@ -677,10 +677,10 @@ impl Db { bytemuck::bytes_of(&hdr) }) .chain({ - // write out the CompactSpaceHeader + // write out the StoreHeader let space_reserved = NonZeroUsize::new(SPACE_RESERVED as usize).expect("SPACE_RESERVED is non-zero"); - csh = CompactSpaceHeader::new(space_reserved, space_reserved); + csh = StoreHeader::new(space_reserved, space_reserved); bytemuck::bytes_of(&csh) }) .copied() @@ -700,7 +700,7 @@ impl Db { let db_header: DiskAddress = DiskAddress::from(offset); offset += DbHeader::MSIZE as usize; let merkle_payload_header: DiskAddress = DiskAddress::from(offset); - offset += CompactSpaceHeader::SERIALIZED_LEN as usize; + offset += StoreHeader::SERIALIZED_LEN as usize; assert!(offset <= SPACE_RESERVED as usize); let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); @@ -710,7 +710,7 @@ impl Db { #[allow(clippy::unwrap_used)] merkle_meta_store.write( merkle_payload_header.into(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + &shale::to_dehydrated(&shale::compact::StoreHeader::new( NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), #[allow(clippy::unwrap_used)] NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), @@ -752,12 +752,12 @@ impl Db { fn get_payload_header_ref( meta_ref: &K, header_offset: u64, - ) -> Result, DbError> { + ) -> Result, DbError> { let payload_header = DiskAddress::from(header_offset as usize); StoredView::ptr_to_obj( meta_ref, payload_header, - shale::compact::CompactHeader::SERIALIZED_LEN, + shale::compact::ChunkHeader::SERIALIZED_LEN, ) .map_err(Into::into) } @@ -768,7 +768,7 @@ impl Db { } fn new_revision>( - header_refs: (Obj, Obj), + header_refs: (Obj, Obj), merkle: (T, T), payload_regn_nbit: u64, payload_max_walk: u64, @@ -777,7 +777,7 @@ impl Db { // TODO: This should be a compile time check const DB_OFFSET: u64 = Db::PARAM_SIZE; let merkle_offset = DB_OFFSET + DbHeader::MSIZE; - assert!(merkle_offset + CompactSpaceHeader::SERIALIZED_LEN <= SPACE_RESERVED); + assert!(merkle_offset + StoreHeader::SERIALIZED_LEN <= SPACE_RESERVED); let mut db_header_ref = header_refs.0; let merkle_payload_header_ref = header_refs.1; @@ -786,7 +786,7 @@ impl Db { let merkle_payload = merkle.1.into(); #[allow(clippy::unwrap_used)] - let merkle_space = shale::compact::CompactSpace::new( + let merkle_space = shale::compact::Store::new( merkle_meta, merkle_payload, merkle_payload_header_ref, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e510d31165ad..588cc4c24e51 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. use crate::nibbles::Nibbles; -use crate::shale::compact::CompactSpace; +use crate::shale::compact::Store; use crate::shale::LinearStore; use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError}; use crate::storage::{StoreRevMut, StoreRevShared}; @@ -67,7 +67,7 @@ macro_rules! write_node { #[derive(Debug)] pub struct Merkle { - store: CompactSpace, + store: Store, phantom: PhantomData, } @@ -101,7 +101,7 @@ where T: BinarySerde, EncodedNode: serde::Serialize + serde::Deserialize<'de>, { - pub const fn new(store: CompactSpace) -> Self { + pub const fn new(store: Store) -> Self { Self { store, phantom: PhantomData, @@ -1417,7 +1417,7 @@ mod tests { let compact_header = DiskAddress::null(); dm.write( compact_header.into(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + &shale::to_dehydrated(&shale::compact::StoreHeader::new( std::num::NonZeroUsize::new(RESERVED).unwrap(), std::num::NonZeroUsize::new(RESERVED).unwrap(), )) @@ -1427,7 +1427,7 @@ mod tests { let compact_header = shale::StoredView::ptr_to_obj( &dm, compact_header, - shale::compact::CompactHeader::SERIALIZED_LEN, + shale::compact::ChunkHeader::SERIALIZED_LEN, ) .unwrap(); let mem_meta = dm; @@ -1435,8 +1435,8 @@ mod tests { let cache = shale::ObjCache::new(1); let space = - shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("CompactSpace init fail"); + shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("Store init fail"); Merkle::new(space) } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 3cc39debb4a9..2636711987de 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -4,7 +4,7 @@ use crate::{ logger::trace, merkle::nibbles_to_bytes_iter, - shale::{compact::CompactSpace, disk_address::DiskAddress, LinearStore, ShaleError, Storable}, + shale::{compact::Store, disk_address::DiskAddress, LinearStore, ShaleError, Storable}, }; use bincode::{Error, Options}; use bitflags::bitflags; @@ -81,7 +81,7 @@ impl NodeType { } } - pub fn encode(&self, store: &CompactSpace) -> Vec { + pub fn encode(&self, store: &Store) -> Vec { match &self { NodeType::Leaf(n) => n.encode(), NodeType::Branch(n) => n.encode(store), @@ -201,21 +201,18 @@ impl Node { }) } - pub(super) fn get_encoded(&self, store: &CompactSpace) -> &[u8] { + pub(super) fn get_encoded(&self, store: &Store) -> &[u8] { self.encoded.get_or_init(|| self.inner.encode(store)) } - pub(super) fn get_root_hash(&self, store: &CompactSpace) -> &TrieHash { + pub(super) fn get_root_hash(&self, store: &Store) -> &TrieHash { self.root_hash.get_or_init(|| { self.set_dirty(true); TrieHash(Keccak256::digest(self.get_encoded(store)).into()) }) } - fn is_encoded_longer_than_hash_len( - &self, - store: &CompactSpace, - ) -> bool { + fn is_encoded_longer_than_hash_len(&self, store: &Store) -> bool { *self .is_encoded_longer_than_hash_len .get_or_init(|| self.get_encoded(store).len() >= TRIE_HASH_LEN) diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs index f55d62e60458..1e46ed31d49d 100644 --- a/firewood/src/merkle/node/branch.rs +++ b/firewood/src/merkle/node/branch.rs @@ -5,7 +5,7 @@ use super::Node; use crate::{ merkle::{nibbles_to_bytes_iter, to_nibble_array, Path}, nibbles::Nibbles, - shale::{compact::CompactSpace, DiskAddress, LinearStore, ShaleError, Storable}, + shale::{compact::Store, DiskAddress, LinearStore, ShaleError, Storable}, }; use bincode::{Error, Options}; use serde::de::Error as DeError; @@ -111,7 +111,7 @@ impl BranchNode { }) } - pub(super) fn encode(&self, store: &CompactSpace) -> Vec { + pub(super) fn encode(&self, store: &Store) -> Vec { // path + children + value let mut list = <[Vec; Self::MSIZE]>::default(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index e0fe2baba6ce..e62b7845926e 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -52,7 +52,7 @@ where #[allow(clippy::unwrap_used)] dm.write( compact_header.into(), - &shale::to_dehydrated(&shale::compact::CompactSpaceHeader::new( + &shale::to_dehydrated(&shale::compact::StoreHeader::new( NonZeroUsize::new(RESERVED).unwrap(), #[allow(clippy::unwrap_used)] NonZeroUsize::new(RESERVED).unwrap(), @@ -64,7 +64,7 @@ where let compact_header = StoredView::ptr_to_obj( &dm, compact_header, - shale::compact::CompactHeader::SERIALIZED_LEN, + shale::compact::ChunkHeader::SERIALIZED_LEN, ) .unwrap(); let mem_meta = dm; @@ -72,8 +72,8 @@ where let cache = shale::ObjCache::new(1); let space = - shale::compact::CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("CompactSpace init fail"); + shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) + .expect("Store init fail"); let merkle = Merkle::new(space); #[allow(clippy::unwrap_used)] diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 26f5ae962642..07c2e79c76be 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -14,14 +14,16 @@ use std::io::{Cursor, Write}; use std::num::NonZeroUsize; use std::sync::RwLock; +/// Marks the start of a linear chunk of the store. +/// The chunk may be freed or in use. #[derive(Debug)] -pub struct CompactHeader { - payload_size: u64, +pub struct ChunkHeader { + chunk_size: u64, is_freed: bool, desc_addr: DiskAddress, } -impl CompactHeader { +impl ChunkHeader { const IS_FREED_OFFSET: usize = std::mem::size_of::(); const DESC_ADDR_OFFSET: usize = Self::IS_FREED_OFFSET + 1; pub const SERIALIZED_LEN: u64 = (Self::DESC_ADDR_OFFSET + std::mem::size_of::()) as u64; @@ -30,12 +32,12 @@ impl CompactHeader { self.is_freed } - pub const fn payload_size(&self) -> u64 { - self.payload_size + pub const fn chunk_size(&self) -> u64 { + self.chunk_size } } -impl Storable for CompactHeader { +impl Storable for ChunkHeader { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) @@ -44,7 +46,7 @@ impl Storable for CompactHeader { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] - let payload_size = u64::from_le_bytes( + let chunk_size = u64::from_le_bytes( raw.as_deref()[..Self::IS_FREED_OFFSET] .try_into() .expect("invalid slice"), @@ -58,7 +60,7 @@ impl Storable for CompactHeader { .expect("invalid slice"), ); Ok(Self { - payload_size, + chunk_size, is_freed, desc_addr: DiskAddress(NonZeroUsize::new(desc_addr)), }) @@ -70,23 +72,24 @@ impl Storable for CompactHeader { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&self.chunk_size.to_le_bytes())?; cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; cur.write_all(&self.desc_addr.to_le_bytes())?; Ok(()) } } +/// Marks the end of a linear chunk of the store. #[derive(Debug)] -struct CompactFooter { - payload_size: u64, +struct ChunkFooter { + chunk_size: u64, } -impl CompactFooter { +impl ChunkFooter { const SERIALIZED_LEN: u64 = std::mem::size_of::() as u64; } -impl Storable for CompactFooter { +impl Storable for ChunkFooter { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) @@ -95,8 +98,8 @@ impl Storable for CompactFooter { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::unwrap_used)] - let payload_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); - Ok(Self { payload_size }) + let chunk_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); + Ok(Self { chunk_size }) } fn serialized_len(&self) -> u64 { @@ -104,23 +107,23 @@ impl Storable for CompactFooter { } fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.payload_size.to_le_bytes())?; + Cursor::new(to).write_all(&self.chunk_size.to_le_bytes())?; Ok(()) } } #[derive(Clone, Copy, Debug)] -struct CompactDescriptor { - payload_size: u64, +struct ChunkDescriptor { + chunk_size: u64, haddr: usize, // disk address of the free space } -impl CompactDescriptor { +impl ChunkDescriptor { const HADDR_OFFSET: usize = 8; const SERIALIZED_LEN: u64 = (Self::HADDR_OFFSET + std::mem::size_of::()) as u64; } -impl Storable for CompactDescriptor { +impl Storable for ChunkDescriptor { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) @@ -129,7 +132,7 @@ impl Storable for CompactDescriptor { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] - let payload_size = u64::from_le_bytes( + let chunk_size = u64::from_le_bytes( raw.as_deref()[..Self::HADDR_OFFSET] .try_into() .expect("invalid slice"), @@ -140,10 +143,7 @@ impl Storable for CompactDescriptor { .try_into() .expect("invalid slice"), ); - Ok(Self { - payload_size, - haddr, - }) + Ok(Self { chunk_size, haddr }) } fn serialized_len(&self) -> u64 { @@ -152,15 +152,16 @@ impl Storable for CompactDescriptor { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.payload_size.to_le_bytes())?; + cur.write_all(&self.chunk_size.to_le_bytes())?; cur.write_all(&self.haddr.to_le_bytes())?; Ok(()) } } +/// A header for a [Store]. #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] -pub struct CompactSpaceHeader { +pub struct StoreHeader { meta_space_tail: DiskAddress, data_space_tail: DiskAddress, base_addr: DiskAddress, @@ -168,14 +169,14 @@ pub struct CompactSpaceHeader { } #[derive(Debug)] -struct CompactSpaceHeaderSliced { +struct StoreHeaderSliced { meta_space_tail: Obj, data_space_tail: Obj, base_addr: Obj, alloc_addr: Obj, } -impl CompactSpaceHeaderSliced { +impl StoreHeaderSliced { fn flush_dirty(&mut self) { self.meta_space_tail.flush_dirty(); self.data_space_tail.flush_dirty(); @@ -184,7 +185,7 @@ impl CompactSpaceHeaderSliced { } } -impl CompactSpaceHeader { +impl StoreHeader { const META_SPACE_TAIL_OFFSET: usize = 0; const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; const BASE_ADDR_OFFSET: usize = @@ -201,8 +202,8 @@ impl CompactSpaceHeader { } } - fn into_fields(r: Obj) -> Result { - Ok(CompactSpaceHeaderSliced { + fn into_fields(r: Obj) -> Result { + Ok(StoreHeaderSliced { meta_space_tail: StoredView::slice( &r, Self::META_SPACE_TAIL_OFFSET, @@ -231,7 +232,7 @@ impl CompactSpaceHeader { } } -impl Storable for CompactSpaceHeader { +impl Storable for StoreHeader { fn deserialize(addr: usize, mem: &T) -> Result { let raw = mem .get_view(addr, Self::SERIALIZED_LEN) @@ -278,17 +279,17 @@ impl Storable for CompactSpaceHeader { } #[derive(Debug)] -struct CompactSpaceInner { +struct StoreInner { meta_space: M, data_space: M, - header: CompactSpaceHeaderSliced, + header: StoreHeaderSliced, alloc_max_walk: u64, regn_nbit: u64, } -impl From> for CompactSpaceInner { - fn from(value: CompactSpaceInner) -> CompactSpaceInner { - CompactSpaceInner { +impl From> for StoreInner { + fn from(value: StoreInner) -> StoreInner { + StoreInner { meta_space: value.meta_space.into(), data_space: value.data_space.into(), header: value.header, @@ -298,9 +299,9 @@ impl From> for CompactSpaceInner } } -impl CompactSpaceInner { - fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.meta_space, ptr, CompactDescriptor::SERIALIZED_LEN) +impl StoreInner { + fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { + StoredView::ptr_to_obj(&self.meta_space, ptr, ChunkDescriptor::SERIALIZED_LEN) } fn get_data_ref( @@ -311,16 +312,16 @@ impl CompactSpaceInner { StoredView::ptr_to_obj(&self.data_space, ptr, len_limit) } - fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactHeader::SERIALIZED_LEN) + fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { + self.get_data_ref::(ptr, ChunkHeader::SERIALIZED_LEN) } - fn get_footer(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, CompactFooter::SERIALIZED_LEN) + fn get_footer(&self, ptr: DiskAddress) -> Result, ShaleError> { + self.get_data_ref::(ptr, ChunkFooter::SERIALIZED_LEN) } fn del_desc(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { - let desc_size = CompactDescriptor::SERIALIZED_LEN; + let desc_size = ChunkDescriptor::SERIALIZED_LEN; // TODO: subtracting two disk addresses is only used here, probably can rewrite this // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); #[allow(clippy::unwrap_used)] @@ -347,43 +348,43 @@ impl CompactSpaceInner { #[allow(clippy::unwrap_used)] self.header .meta_space_tail - .modify(|r| *r += CompactDescriptor::SERIALIZED_LEN as usize) + .modify(|r| *r += ChunkDescriptor::SERIALIZED_LEN as usize) .unwrap(); Ok(DiskAddress(addr)) } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { - let hsize = CompactHeader::SERIALIZED_LEN; - let fsize = CompactFooter::SERIALIZED_LEN; + let hsize = ChunkHeader::SERIALIZED_LEN; + let fsize = ChunkFooter::SERIALIZED_LEN; let regn_size = 1 << self.regn_nbit; let mut offset = addr - hsize; - let header_payload_size = { + let header_chunk_size = { let header = self.get_header(DiskAddress::from(offset as usize))?; assert!(!header.is_freed); - header.payload_size + header.chunk_size }; let mut h = offset; - let mut payload_size = header_payload_size; + let mut chunk_size = header_chunk_size; if offset & (regn_size - 1) > 0 { // merge with lower data segment offset -= fsize; - let (pheader_is_freed, pheader_payload_size, pheader_desc_addr) = { + let (pheader_is_freed, pheader_chunk_size, pheader_desc_addr) = { let pfooter = self.get_footer(DiskAddress::from(offset as usize))?; - offset -= pfooter.payload_size + hsize; + offset -= pfooter.chunk_size + hsize; let pheader = self.get_header(DiskAddress::from(offset as usize))?; - (pheader.is_freed, pheader.payload_size, pheader.desc_addr) + (pheader.is_freed, pheader.chunk_size, pheader.desc_addr) }; if pheader_is_freed { h = offset; - payload_size += hsize + fsize + pheader_payload_size; + chunk_size += hsize + fsize + pheader_chunk_size; self.del_desc(pheader_desc_addr)?; } } - offset = addr + header_payload_size; + offset = addr + header_chunk_size; let mut f = offset; #[allow(clippy::unwrap_used)] @@ -392,18 +393,18 @@ impl CompactSpaceInner { { // merge with higher data segment offset += fsize; - let (nheader_is_freed, nheader_payload_size, nheader_desc_addr) = { + let (nheader_is_freed, nheader_chunk_size, nheader_desc_addr) = { let nheader = self.get_header(DiskAddress::from(offset as usize))?; - (nheader.is_freed, nheader.payload_size, nheader.desc_addr) + (nheader.is_freed, nheader.chunk_size, nheader.desc_addr) }; if nheader_is_freed { - offset += hsize + nheader_payload_size; + offset += hsize + nheader_chunk_size; f = offset; { let nfooter = self.get_footer(DiskAddress::from(offset as usize))?; - assert!(nheader_payload_size == nfooter.payload_size); + assert!(nheader_chunk_size == nfooter.chunk_size); } - payload_size += hsize + fsize + nheader_payload_size; + chunk_size += hsize + fsize + nheader_chunk_size; self.del_desc(nheader_desc_addr)?; } } @@ -413,7 +414,7 @@ impl CompactSpaceInner { let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] desc.modify(|d| { - d.payload_size = payload_size; + d.chunk_size = chunk_size; d.haddr = h as usize; }) .unwrap(); @@ -422,13 +423,13 @@ impl CompactSpaceInner { let mut f = self.get_footer(DiskAddress::from(f as usize))?; #[allow(clippy::unwrap_used)] h.modify(|h| { - h.payload_size = payload_size; + h.chunk_size = chunk_size; h.is_freed = true; h.desc_addr = desc_addr; }) .unwrap(); #[allow(clippy::unwrap_used)] - f.modify(|f| f.payload_size = payload_size).unwrap(); + f.modify(|f| f.chunk_size = chunk_size).unwrap(); Ok(()) } @@ -439,9 +440,9 @@ impl CompactSpaceInner { return Ok(None); } - let hsize = CompactHeader::SERIALIZED_LEN as usize; - let fsize = CompactFooter::SERIALIZED_LEN as usize; - let dsize = CompactDescriptor::SERIALIZED_LEN as usize; + let hsize = ChunkHeader::SERIALIZED_LEN as usize; + let fsize = ChunkFooter::SERIALIZED_LEN as usize; + let dsize = ChunkDescriptor::SERIALIZED_LEN as usize; let mut old_alloc_addr = *self.header.alloc_addr; @@ -453,52 +454,52 @@ impl CompactSpaceInner { let mut res: Option = None; for _ in 0..self.alloc_max_walk { assert!(ptr < tail); - let (desc_payload_size, desc_haddr) = { + let (chunk_size, desc_haddr) = { let desc = self.get_descriptor(ptr)?; - (desc.payload_size as usize, desc.haddr) + (desc.chunk_size as usize, desc.haddr) }; - let exit = if desc_payload_size == length as usize { + let exit = if chunk_size == length as usize { // perfect match { let mut header = self.get_header(DiskAddress::from(desc_haddr))?; - assert_eq!(header.payload_size as usize, desc_payload_size); + assert_eq!(header.chunk_size as usize, chunk_size); assert!(header.is_freed); #[allow(clippy::unwrap_used)] header.modify(|h| h.is_freed = false).unwrap(); } self.del_desc(ptr)?; true - } else if desc_payload_size > length as usize + hsize + fsize { + } else if chunk_size > length as usize + hsize + fsize { // able to split { let mut lheader = self.get_header(DiskAddress::from(desc_haddr))?; - assert_eq!(lheader.payload_size as usize, desc_payload_size); + assert_eq!(lheader.chunk_size as usize, chunk_size); assert!(lheader.is_freed); #[allow(clippy::unwrap_used)] lheader .modify(|h| { h.is_freed = false; - h.payload_size = length; + h.chunk_size = length; }) .unwrap(); } { let mut lfooter = self.get_footer(DiskAddress::from(desc_haddr + hsize + length as usize))?; - //assert!(lfooter.payload_size == desc_payload_size); + //assert!(lfooter.chunk_size == chunk_size); #[allow(clippy::unwrap_used)] - lfooter.modify(|f| f.payload_size = length).unwrap(); + lfooter.modify(|f| f.chunk_size = length).unwrap(); } let offset = desc_haddr + hsize + length as usize + fsize; - let rpayload_size = desc_payload_size - length as usize - fsize - hsize; + let rchunk_size = chunk_size - length as usize - fsize - hsize; let rdesc_addr = self.new_desc()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; #[allow(clippy::unwrap_used)] rdesc .modify(|rd| { - rd.payload_size = rpayload_size as u64; + rd.chunk_size = rchunk_size as u64; rd.haddr = offset; }) .unwrap(); @@ -509,17 +510,17 @@ impl CompactSpaceInner { rheader .modify(|rh| { rh.is_freed = true; - rh.payload_size = rpayload_size as u64; + rh.chunk_size = rchunk_size as u64; rh.desc_addr = rdesc_addr; }) .unwrap(); } { let mut rfooter = - self.get_footer(DiskAddress::from(offset + hsize + rpayload_size))?; + self.get_footer(DiskAddress::from(offset + hsize + rchunk_size))?; #[allow(clippy::unwrap_used)] rfooter - .modify(|f| f.payload_size = rpayload_size as u64) + .modify(|f| f.chunk_size = rchunk_size as u64) .unwrap(); } self.del_desc(ptr)?; @@ -546,7 +547,7 @@ impl CompactSpaceInner { fn alloc_new(&mut self, length: u64) -> Result { let regn_size = 1 << self.regn_nbit; - let total_length = CompactHeader::SERIALIZED_LEN + length + CompactFooter::SERIALIZED_LEN; + let total_length = ChunkHeader::SERIALIZED_LEN + length + ChunkFooter::SERIALIZED_LEN; let mut offset = *self.header.data_space_tail; #[allow(clippy::unwrap_used)] self.header @@ -563,18 +564,18 @@ impl CompactSpaceInner { .unwrap(); let mut h = self.get_header(offset)?; let mut f = - self.get_footer(offset + CompactHeader::SERIALIZED_LEN as usize + length as usize)?; + self.get_footer(offset + ChunkHeader::SERIALIZED_LEN as usize + length as usize)?; #[allow(clippy::unwrap_used)] h.modify(|h| { - h.payload_size = length; + h.chunk_size = length; h.is_freed = false; h.desc_addr = DiskAddress::null(); }) .unwrap(); #[allow(clippy::unwrap_used)] - f.modify(|f| f.payload_size = length).unwrap(); + f.modify(|f| f.chunk_size = length).unwrap(); #[allow(clippy::unwrap_used)] - Ok((offset + CompactHeader::SERIALIZED_LEN as usize) + Ok((offset + ChunkHeader::SERIALIZED_LEN as usize) .0 .unwrap() .get() as u64) @@ -592,25 +593,25 @@ impl CompactSpaceInner { } #[derive(Debug)] -pub struct CompactSpace { - inner: RwLock>, +pub struct Store { + inner: RwLock>, obj_cache: ObjCache, } -impl CompactSpace { +impl Store { pub fn new( meta_space: M, data_space: M, - header: Obj, + header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, regn_nbit: u64, ) -> Result { - let cs = CompactSpace { - inner: RwLock::new(CompactSpaceInner { + let cs = Store { + inner: RwLock::new(StoreInner { meta_space, data_space, - header: CompactSpaceHeader::into_fields(header)?, + header: StoreHeader::into_fields(header)?, alloc_max_walk, regn_nbit, }), @@ -620,18 +621,18 @@ impl CompactSpace { } } -impl From> for CompactSpace { +impl From> for Store { #[allow(clippy::unwrap_used)] - fn from(value: CompactSpace) -> Self { + fn from(value: Store) -> Self { let inner = value.inner.into_inner().unwrap(); - CompactSpace { + Store { inner: RwLock::new(inner.into()), obj_cache: value.obj_cache, } } } -impl CompactSpace { +impl Store { pub(crate) fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { let size = item.serialized_len() + extra; #[allow(clippy::unwrap_used)] @@ -680,17 +681,17 @@ impl CompactSpace { } #[allow(clippy::unwrap_used)] - if ptr < DiskAddress::from(CompactSpaceHeader::SERIALIZED_LEN as usize) { + if ptr < DiskAddress::from(StoreHeader::SERIALIZED_LEN as usize) { return Err(ShaleError::InvalidAddressLength { - expected: CompactSpaceHeader::SERIALIZED_LEN, + expected: StoreHeader::SERIALIZED_LEN, found: ptr.0.map(|inner| inner.get()).unwrap_or_default() as u64, }); } - let payload_size = inner - .get_header(ptr - CompactHeader::SERIALIZED_LEN as usize)? - .payload_size; - let obj = self.obj_cache.put(inner.get_data_ref(ptr, payload_size)?); + let chunk_size = inner + .get_header(ptr - ChunkHeader::SERIALIZED_LEN as usize)? + .chunk_size; + let obj = self.obj_cache.put(inner.get_data_ref(ptr, chunk_size)?); let cache = &self.obj_cache; Ok(ObjRef::new(obj, cache)) @@ -769,21 +770,17 @@ mod tests { let compact_header = DiskAddress::from(0x1); dm.write( compact_header.unwrap().get(), - &shale::to_dehydrated(&CompactSpaceHeader::new( - reserved.0.unwrap(), - reserved.0.unwrap(), - )) - .unwrap(), + &shale::to_dehydrated(&StoreHeader::new(reserved.0.unwrap(), reserved.0.unwrap())) + .unwrap(), ) .unwrap(); let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, CompactHeader::SERIALIZED_LEN).unwrap(); + StoredView::ptr_to_obj(&dm, compact_header, ChunkHeader::SERIALIZED_LEN).unwrap(); let mem_meta = dm; let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); let cache: ObjCache = ObjCache::new(1); - let space = - CompactSpace::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + let space = Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); // initial write let data = b"hello world"; diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index a7bcf2bb31dd..de7b756b5765 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -426,7 +426,7 @@ pub struct ObjCacheInner { dirty: HashSet, } -/// [ObjRef] pool that is used by [compact::CompactSpace] to construct [ObjRef]s. +/// [ObjRef] pool that is used by [compact::Store] to construct [ObjRef]s. #[derive(Debug)] pub struct ObjCache(Arc>>); From 658a8ff8dd9d928351d7b2f080aa97f835893431 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 25 Mar 2024 22:12:07 -0400 Subject: [PATCH 0525/1053] Clean up header and footer usage (#611) --- firewood/src/shale/compact.rs | 48 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 07c2e79c76be..c5da7f65485f 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -355,11 +355,9 @@ impl StoreInner { } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { - let hsize = ChunkHeader::SERIALIZED_LEN; - let fsize = ChunkFooter::SERIALIZED_LEN; let regn_size = 1 << self.regn_nbit; - let mut offset = addr - hsize; + let mut offset = addr - ChunkHeader::SERIALIZED_LEN; let header_chunk_size = { let header = self.get_header(DiskAddress::from(offset as usize))?; assert!(!header.is_freed); @@ -370,16 +368,17 @@ impl StoreInner { if offset & (regn_size - 1) > 0 { // merge with lower data segment - offset -= fsize; + offset -= ChunkFooter::SERIALIZED_LEN; let (pheader_is_freed, pheader_chunk_size, pheader_desc_addr) = { let pfooter = self.get_footer(DiskAddress::from(offset as usize))?; - offset -= pfooter.chunk_size + hsize; + offset -= pfooter.chunk_size + ChunkHeader::SERIALIZED_LEN; let pheader = self.get_header(DiskAddress::from(offset as usize))?; (pheader.is_freed, pheader.chunk_size, pheader.desc_addr) }; if pheader_is_freed { h = offset; - chunk_size += hsize + fsize + pheader_chunk_size; + chunk_size += + ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + pheader_chunk_size; self.del_desc(pheader_desc_addr)?; } } @@ -388,23 +387,25 @@ impl StoreInner { let mut f = offset; #[allow(clippy::unwrap_used)] - if offset + fsize < self.header.data_space_tail.unwrap().get() as u64 - && (regn_size - (offset & (regn_size - 1))) >= fsize + hsize + if offset + ChunkFooter::SERIALIZED_LEN < self.header.data_space_tail.unwrap().get() as u64 + && (regn_size - (offset & (regn_size - 1))) + >= ChunkFooter::SERIALIZED_LEN + ChunkHeader::SERIALIZED_LEN { // merge with higher data segment - offset += fsize; + offset += ChunkFooter::SERIALIZED_LEN; let (nheader_is_freed, nheader_chunk_size, nheader_desc_addr) = { let nheader = self.get_header(DiskAddress::from(offset as usize))?; (nheader.is_freed, nheader.chunk_size, nheader.desc_addr) }; if nheader_is_freed { - offset += hsize + nheader_chunk_size; + offset += ChunkHeader::SERIALIZED_LEN + nheader_chunk_size; f = offset; { let nfooter = self.get_footer(DiskAddress::from(offset as usize))?; assert!(nheader_chunk_size == nfooter.chunk_size); } - chunk_size += hsize + fsize + nheader_chunk_size; + chunk_size += + ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + nheader_chunk_size; self.del_desc(nheader_desc_addr)?; } } @@ -435,15 +436,15 @@ impl StoreInner { } fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { + const HEADER_SIZE: usize = ChunkHeader::SERIALIZED_LEN as usize; + const FOOTER_SIZE: usize = ChunkFooter::SERIALIZED_LEN as usize; + const DESCRIPTOR_SIZE: usize = ChunkDescriptor::SERIALIZED_LEN as usize; + let tail = *self.header.meta_space_tail; if tail == *self.header.base_addr { return Ok(None); } - let hsize = ChunkHeader::SERIALIZED_LEN as usize; - let fsize = ChunkFooter::SERIALIZED_LEN as usize; - let dsize = ChunkDescriptor::SERIALIZED_LEN as usize; - let mut old_alloc_addr = *self.header.alloc_addr; if old_alloc_addr >= tail { @@ -469,7 +470,7 @@ impl StoreInner { } self.del_desc(ptr)?; true - } else if chunk_size > length as usize + hsize + fsize { + } else if chunk_size > length as usize + HEADER_SIZE + FOOTER_SIZE { // able to split { let mut lheader = self.get_header(DiskAddress::from(desc_haddr))?; @@ -484,15 +485,16 @@ impl StoreInner { .unwrap(); } { - let mut lfooter = - self.get_footer(DiskAddress::from(desc_haddr + hsize + length as usize))?; + let mut lfooter = self.get_footer(DiskAddress::from( + desc_haddr + HEADER_SIZE + length as usize, + ))?; //assert!(lfooter.chunk_size == chunk_size); #[allow(clippy::unwrap_used)] lfooter.modify(|f| f.chunk_size = length).unwrap(); } - let offset = desc_haddr + hsize + length as usize + fsize; - let rchunk_size = chunk_size - length as usize - fsize - hsize; + let offset = desc_haddr + HEADER_SIZE + length as usize + FOOTER_SIZE; + let rchunk_size = chunk_size - length as usize - FOOTER_SIZE - HEADER_SIZE; let rdesc_addr = self.new_desc()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; @@ -517,7 +519,7 @@ impl StoreInner { } { let mut rfooter = - self.get_footer(DiskAddress::from(offset + hsize + rchunk_size))?; + self.get_footer(DiskAddress::from(offset + HEADER_SIZE + rchunk_size))?; #[allow(clippy::unwrap_used)] rfooter .modify(|f| f.chunk_size = rchunk_size as u64) @@ -531,10 +533,10 @@ impl StoreInner { #[allow(clippy::unwrap_used)] if exit { self.header.alloc_addr.modify(|r| *r = ptr).unwrap(); - res = Some((desc_haddr + hsize) as u64); + res = Some((desc_haddr + HEADER_SIZE) as u64); break; } - ptr += dsize; + ptr += DESCRIPTOR_SIZE; if ptr >= tail { ptr = *self.header.base_addr; } From 80805dcf1d355312e559cfa5d8ebed8bc36e3fb6 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 26 Mar 2024 10:02:44 -0400 Subject: [PATCH 0526/1053] rename space to store (#612) --- firewood/src/config.rs | 2 +- firewood/src/db.rs | 104 ++++++++++++------------- firewood/src/db/proposal.rs | 26 +++---- firewood/src/lib.rs | 58 +++++++------- firewood/src/merkle.rs | 4 +- firewood/src/merkle_util.rs | 4 +- firewood/src/shale/compact.rs | 100 ++++++++++++------------ firewood/src/shale/in_mem.rs | 34 ++++----- firewood/src/shale/mod.rs | 30 ++++---- firewood/src/storage/buffer.rs | 100 ++++++++++++------------ firewood/src/storage/mod.rs | 134 ++++++++++++++++----------------- fwdctl/src/create.rs | 2 +- growth-ring/src/wal.rs | 4 +- 13 files changed, 301 insertions(+), 301 deletions(-) diff --git a/firewood/src/config.rs b/firewood/src/config.rs index 6a13e4ffd613..9cd35a477c62 100644 --- a/firewood/src/config.rs +++ b/firewood/src/config.rs @@ -18,7 +18,7 @@ pub struct DbConfig { #[builder(default = 22)] // 4MB file by default pub meta_file_nbit: u64, /// Maximum cached pages for the item stash. This is the low-level cache used by the linear - /// space that holds Trie nodes and account objects. + /// store that holds Trie nodes and account objects. #[builder(default = 262144)] // 1G total size by default pub payload_ncached_pages: usize, /// Maximum cached file descriptors for the item stash. diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 21678e1e05cd..6bbdc7273804 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,7 +14,7 @@ use crate::{ }, storage::{ buffer::{DiskBuffer, DiskBufferRequester}, - CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, + CachedStore, MemStoreR, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, StoreWrite, ZeroStore, PAGE_SIZE_NBIT, }, v2::api::{self, HashKey, KeyType, ValueType}, @@ -23,7 +23,7 @@ use crate::{ merkle, shale::{ self, compact::StoreHeader, disk_address::DiskAddress, LinearStore, Obj, ShaleError, - SpaceId, Storable, StoredView, + Storable, StoreId, StoredView, }, }; use aiofut::AioError; @@ -51,10 +51,10 @@ mod proposal; use self::proposal::ProposalBase; -const MERKLE_META_SPACE: SpaceId = 0x0; -const MERKLE_PAYLOAD_SPACE: SpaceId = 0x1; -const ROOT_HASH_SPACE: SpaceId = 0x2; -const SPACE_RESERVED: u64 = 0x1000; +const MERKLE_META_STORE_ID: StoreId = 0x0; +const MERKLE_PAYLOAD_STORE_ID: StoreId = 0x1; +const ROOT_HASH_STORE_ID: StoreId = 0x2; +const RESERVED_STORE_ID: u64 = 0x1000; const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; @@ -118,7 +118,7 @@ struct DbParams { } #[derive(Clone, Debug)] -/// Necessary linear space instances bundled for a `Store`. +/// Necessary linear store instances bundled for a `Store`. struct SubUniverse { meta: T, payload: T, @@ -151,8 +151,8 @@ impl SubUniverse { impl SubUniverse> { fn rewind( &self, - meta_writes: &[SpaceWrite], - payload_writes: &[SpaceWrite], + meta_writes: &[StoreWrite], + payload_writes: &[StoreWrite], ) -> SubUniverse { SubUniverse::new( StoreRevShared::from_ash(self.meta.clone(), meta_writes), @@ -161,7 +161,7 @@ impl SubUniverse> { } } -impl SubUniverse> { +impl SubUniverse> { fn to_mem_store_r(&self) -> SubUniverse> { SubUniverse { meta: self.meta.clone(), @@ -171,7 +171,7 @@ impl SubUniverse> { } fn get_sub_universe_from_deltas( - sub_universe: &SubUniverse>, + sub_universe: &SubUniverse>, meta_delta: StoreDelta, payload_delta: StoreDelta, ) -> SubUniverse { @@ -182,7 +182,7 @@ fn get_sub_universe_from_deltas( } fn get_sub_universe_from_empty_delta( - sub_universe: &SubUniverse>, + sub_universe: &SubUniverse>, ) -> SubUniverse { get_sub_universe_from_deltas(sub_universe, StoreDelta::default(), StoreDelta::default()) } @@ -234,7 +234,7 @@ impl Storable for DbHeader { } #[derive(Clone, Debug)] -/// Necessary linear space instances bundled for the state of the entire DB. +/// Necessary linear store instances bundled for the state of the entire DB. struct Universe { merkle: SubUniverse, } @@ -255,7 +255,7 @@ impl Universe { } } -impl Universe> { +impl Universe> { fn to_mem_store_r(&self) -> Universe> { Universe { merkle: self.merkle.to_mem_store_r(), @@ -266,8 +266,8 @@ impl Universe> { impl Universe> { fn rewind( &self, - merkle_meta_writes: &[SpaceWrite], - merkle_payload_writes: &[SpaceWrite], + merkle_meta_writes: &[StoreWrite], + merkle_payload_writes: &[StoreWrite], ) -> Universe { Universe { merkle: self @@ -417,8 +417,8 @@ impl From> for DbRev { struct DbInner { disk_requester: DiskBufferRequester, disk_thread: Option>, - cached_space: Universe>, - // Whether to reset the store headers when creating a new store on top of the cached space. + cached_store: Universe>, + // Whether to reset the store headers when creating a new store on top of the cached store. reset_store_headers: bool, root_hash_staging: StoreRevMut, } @@ -511,7 +511,7 @@ impl Db { let merkle_payload_path = file::touch_dir("compact", &merkle_path)?; let root_hash_path = file::touch_dir("root_hash", &db_path)?; - let meta_file = crate::file::File::new(0, SPACE_RESERVED, &merkle_meta_path)?; + let meta_file = crate::file::File::new(0, RESERVED_STORE_ID, &merkle_meta_path)?; let meta_fd = meta_file.as_fd(); if reset_store_headers { @@ -552,11 +552,11 @@ impl Db { // set up caches #[allow(clippy::unwrap_used)] - let root_hash_cache: Arc = CachedSpace::new( + let root_hash_cache: Arc = CachedStore::new( &StoreConfig::builder() .ncached_pages(cfg.root_hash_ncached_pages) .ncached_files(cfg.root_hash_ncached_files) - .space_id(ROOT_HASH_SPACE) + .store_id(ROOT_HASH_STORE_ID) .file_nbit(params.root_hash_file_nbit) .rootdir(root_hash_path) .build(), @@ -567,12 +567,12 @@ impl Db { #[allow(clippy::unwrap_used)] let data_cache = Universe { - merkle: SubUniverse::>::new( - CachedSpace::new( + merkle: SubUniverse::>::new( + CachedStore::new( &StoreConfig::builder() .ncached_pages(cfg.meta_ncached_pages) .ncached_files(cfg.meta_ncached_files) - .space_id(MERKLE_META_SPACE) + .store_id(MERKLE_META_STORE_ID) .file_nbit(params.meta_file_nbit) .rootdir(merkle_meta_path) .build(), @@ -580,11 +580,11 @@ impl Db { ) .unwrap() .into(), - CachedSpace::new( + CachedStore::new( &StoreConfig::builder() .ncached_pages(cfg.payload_ncached_pages) .ncached_files(cfg.payload_ncached_files) - .space_id(MERKLE_PAYLOAD_SPACE) + .store_id(MERKLE_PAYLOAD_STORE_ID) .file_nbit(params.payload_file_nbit) .rootdir(merkle_payload_path) .build(), @@ -601,8 +601,8 @@ impl Db { root_hash_cache.as_ref(), ] .into_iter() - .for_each(|cached_space| { - disk_requester.reg_cached_space(cached_space.id(), cached_space.clone_files()); + .for_each(|cached_store| { + disk_requester.reg_cached_store(cached_store.id(), cached_store.clone_files()); }); // recover from Wal @@ -635,7 +635,7 @@ impl Db { inner: Arc::new(RwLock::new(DbInner { disk_thread, disk_requester, - cached_space: data_cache, + cached_store: data_cache, reset_store_headers, root_hash_staging: StoreRevMut::new(root_hash_cache), })), @@ -678,9 +678,9 @@ impl Db { }) .chain({ // write out the StoreHeader - let space_reserved = - NonZeroUsize::new(SPACE_RESERVED as usize).expect("SPACE_RESERVED is non-zero"); - csh = StoreHeader::new(space_reserved, space_reserved); + let store_reserved = NonZeroUsize::new(RESERVED_STORE_ID as usize) + .expect("RESERVED_STORE_ID is non-zero"); + csh = StoreHeader::new(store_reserved, store_reserved); bytemuck::bytes_of(&csh) }) .copied() @@ -693,7 +693,7 @@ impl Db { /// Create a new mutable store and an alterable revision of the DB on top. fn new_store( &self, - cached_space: &Universe>, + cached_store: &Universe>, reset_store_headers: bool, ) -> Result<(Universe, DbRev), DbError> { let mut offset = Db::PARAM_SIZE as usize; @@ -701,9 +701,9 @@ impl Db { offset += DbHeader::MSIZE as usize; let merkle_payload_header: DiskAddress = DiskAddress::from(offset); offset += StoreHeader::SERIALIZED_LEN as usize; - assert!(offset <= SPACE_RESERVED as usize); + assert!(offset <= RESERVED_STORE_ID as usize); - let mut merkle_meta_store = StoreRevMut::new(cached_space.merkle.meta.clone()); + let mut merkle_meta_store = StoreRevMut::new(cached_store.merkle.meta.clone()); if reset_store_headers { // initialize store headers @@ -711,9 +711,9 @@ impl Db { merkle_meta_store.write( merkle_payload_header.into(), &shale::to_dehydrated(&shale::compact::StoreHeader::new( - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(RESERVED_STORE_ID as usize).unwrap(), #[allow(clippy::unwrap_used)] - NonZeroUsize::new(SPACE_RESERVED as usize).unwrap(), + NonZeroUsize::new(RESERVED_STORE_ID as usize).unwrap(), ))?, )?; merkle_meta_store.write( @@ -725,7 +725,7 @@ impl Db { let store = Universe { merkle: SubUniverse::new( merkle_meta_store, - StoreRevMut::new(cached_space.merkle.payload.clone()), + StoreRevMut::new(cached_store.merkle.payload.clone()), ), }; @@ -777,7 +777,7 @@ impl Db { // TODO: This should be a compile time check const DB_OFFSET: u64 = Db::PARAM_SIZE; let merkle_offset = DB_OFFSET + DbHeader::MSIZE; - assert!(merkle_offset + StoreHeader::SERIALIZED_LEN <= SPACE_RESERVED); + assert!(merkle_offset + StoreHeader::SERIALIZED_LEN <= RESERVED_STORE_ID); let mut db_header_ref = header_refs.0; let merkle_payload_header_ref = header_refs.1; @@ -786,7 +786,7 @@ impl Db { let merkle_payload = merkle.1.into(); #[allow(clippy::unwrap_used)] - let merkle_space = shale::compact::Store::new( + let merkle_store = shale::compact::Store::new( merkle_meta, merkle_payload, merkle_payload_header_ref, @@ -796,7 +796,7 @@ impl Db { ) .unwrap(); - let merkle = Merkle::new(merkle_space); + let merkle = Merkle::new(merkle_store); if db_header_ref.kv_root.is_null() { let mut err = Ok(()); @@ -826,7 +826,7 @@ impl Db { ) -> Result { let mut inner = self.inner.write(); let reset_store_headers = inner.reset_store_headers; - let (store, mut rev) = self.new_store(&inner.cached_space, reset_store_headers)?; + let (store, mut rev) = self.new_store(&inner.cached_store, reset_store_headers)?; // Flip the reset flag after resetting the store headers. if reset_store_headers { @@ -900,7 +900,7 @@ impl Db { StoreRevShared::from_ash( Arc::new(ZeroStore::default()), #[allow(clippy::indexing_slicing)] - &ash.0[&ROOT_HASH_SPACE].redo, + &ash.0[&ROOT_HASH_STORE_ID].redo, ) }) .map(|root_hash_store| { @@ -928,22 +928,22 @@ impl Db { let u = match revisions.inner.back() { Some(u) => u.to_mem_store_r().rewind( #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_META_SPACE].undo, + &ash.0[&MERKLE_META_STORE_ID].undo, #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_PAYLOAD_SPACE].undo, + &ash.0[&MERKLE_PAYLOAD_STORE_ID].undo, ), - None => inner_lock.cached_space.to_mem_store_r().rewind( + None => inner_lock.cached_store.to_mem_store_r().rewind( #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_META_SPACE].undo, + &ash.0[&MERKLE_META_STORE_ID].undo, #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_PAYLOAD_SPACE].undo, + &ash.0[&MERKLE_PAYLOAD_STORE_ID].undo, ), }; revisions.inner.push_back(u); } } - let space = if nback == 0 { + let store = if nback == 0 { &revisions.base } else { #[allow(clippy::indexing_slicing)] @@ -953,11 +953,11 @@ impl Db { drop(inner_lock); #[allow(clippy::unwrap_used)] - let db_header_ref = Db::get_db_header_ref(&space.merkle.meta).unwrap(); + let db_header_ref = Db::get_db_header_ref(&store.merkle.meta).unwrap(); #[allow(clippy::unwrap_used)] let merkle_payload_header_ref = - Db::get_payload_header_ref(&space.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) + Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) .unwrap(); let header_refs = (db_header_ref, merkle_payload_header_ref); @@ -965,7 +965,7 @@ impl Db { #[allow(clippy::unwrap_used)] Db::new_revision( header_refs, - (space.merkle.meta.clone(), space.merkle.payload.clone()), + (store.merkle.meta.clone(), store.merkle.payload.clone()), self.payload_regn_nbit, 0, &self.cfg.rev, diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index e6c96ff2f046..5eba71df71c6 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -3,7 +3,7 @@ use super::{ get_sub_universe_from_deltas, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, - Universe, MERKLE_META_SPACE, MERKLE_PAYLOAD_SPACE, ROOT_HASH_SPACE, + Universe, MERKLE_META_STORE_ID, MERKLE_PAYLOAD_STORE_ID, ROOT_HASH_STORE_ID, }; use crate::merkle::{Bincode, MerkleKeyValueStream, Proof}; use crate::shale::LinearStore; @@ -171,21 +171,21 @@ impl Proposal { } }; - // clear the staging layer and apply changes to the CachedSpace + // clear the staging layer and apply changes to the CachedStore let (merkle_payload_redo, merkle_payload_wal) = store.merkle.payload.delta(); let (merkle_meta_redo, merkle_meta_wal) = store.merkle.meta.delta(); let mut rev_inner = m.write(); #[allow(clippy::unwrap_used)] let merkle_meta_undo = rev_inner - .cached_space + .cached_store .merkle .meta .update(&merkle_meta_redo) .unwrap(); #[allow(clippy::unwrap_used)] let merkle_payload_undo = rev_inner - .cached_space + .cached_store .merkle .payload .update(&merkle_payload_redo) @@ -194,7 +194,7 @@ impl Proposal { // update the rolling window of past revisions let latest_past = Universe { merkle: get_sub_universe_from_deltas( - &rev_inner.cached_space.merkle, + &rev_inner.cached_store.merkle, merkle_meta_undo, merkle_payload_undo, ), @@ -204,10 +204,10 @@ impl Proposal { if let Some(rev) = revisions.inner.front_mut() { rev.merkle .meta - .set_base_space(latest_past.merkle.meta.inner().clone()); + .set_base_store(latest_past.merkle.meta.inner().clone()); rev.merkle .payload - .set_base_space(latest_past.merkle.payload.inner().clone()); + .set_base_store(latest_past.merkle.payload.inner().clone()); } revisions.inner.push_front(latest_past); while revisions.inner.len() > max_revisions { @@ -231,23 +231,23 @@ impl Proposal { rev_inner.disk_requester.write( Box::new([ BufferWrite { - space_id: store.merkle.payload.id(), + store_id: store.merkle.payload.id(), delta: merkle_payload_redo, }, BufferWrite { - space_id: store.merkle.meta.id(), + store_id: store.merkle.meta.id(), delta: merkle_meta_redo, }, BufferWrite { - space_id: rev_inner.root_hash_staging.id(), + store_id: rev_inner.root_hash_staging.id(), delta: root_hash_redo, }, ]), AshRecord( [ - (MERKLE_META_SPACE, merkle_meta_wal), - (MERKLE_PAYLOAD_SPACE, merkle_payload_wal), - (ROOT_HASH_SPACE, root_hash_wal), + (MERKLE_META_STORE_ID, merkle_meta_wal), + (MERKLE_PAYLOAD_STORE_ID, merkle_payload_wal), + (ROOT_HASH_STORE_ID, root_hash_wal), ] .into(), ), diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index d72b76721ca9..c8f57a904da8 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -69,12 +69,12 @@ //! Firewood is built by three layers of abstractions that totally decouple the //! layout/representation of the data on disk from the actual logical data structure it retains: //! -//! - Linear, memory-like space: the `shale` crate offers a `CachedStore` abstraction for a -//! (64-bit) byte-addressable space that abstracts away the intricate method that actually persists +//! - Linear, memory-like store: the `shale` crate offers a `CachedStore` abstraction for a +//! (64-bit) byte-addressable store that abstracts away the intricate method that actually persists //! the in-memory data on the secondary storage medium (e.g., hard drive). The implementor of `CachedStore` //! provides the functions to give the user of `CachedStore` an illusion that the user is operating upon a -//! byte-addressable memory space. It is just a "magical" array of bytes one can view and change -//! that is mirrored to the disk. In reality, the linear space will be chunked into files under a +//! byte-addressable memory store. It is just a "magical" array of bytes one can view and change +//! that is mirrored to the disk. In reality, the linear store will be chunked into files under a //! directory, but the user does not have to even know about this. //! //! - Persistent item storage stash: `CompactStore` in `shale` defines a pool of typed objects that are @@ -87,9 +87,9 @@ //! and maintain the code. //! //! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (trie) is what the linear space (`CachedStore`) keeps track of. That is, +//! state of the data structure (trie) is what the linear store (`CachedStore`) keeps track of. That is, //! a flat but conceptually large byte vector. In other words, given a valid byte vector as the -//! content of the linear space, the higher level data structure can be *uniquely* determined, there +//! content of the linear store, the higher level data structure can be *uniquely* determined, there //! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) //! or less than that, like a way to interpret the bytes. This nice property allows us to completely //! separate the logical data from its physical representation, greatly simplifies the storage @@ -99,18 +99,18 @@ //! //! ## Page-based Shadowing and Revisions //! -//! Following the idea that the tries are just a view of a linear byte space, all writes made to the +//! Following the idea that the tries are just a view of a linear byte store, all writes made to the //! tries inside Firewood will eventually be consolidated into some interval writes to the linear -//! space. The writes may overlap and some frequent writes are even done to the same spot in the -//! space. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit -//! virtual space into pages (yeah it appears to be more and more like an OS) and keep track of the +//! store. The writes may overlap and some frequent writes are even done to the same spot in the +//! store. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit +//! virtual store into pages (yeah it appears to be more and more like an OS) and keep track of the //! dirty pages in some `CachedStore` instantiation (see `storage::StoreRevMut`). When a //! [`db::Proposal`] commits, both the recorded interval writes and the aggregated in-memory -//! dirty pages induced by this write batch are taken out from the linear space. Although they are +//! dirty pages induced by this write batch are taken out from the linear store. Although they are //! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, //! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL //! subsystem (supported by growthring). After the WAL record is written (one record per write batch), -//! the dirty pages are then pushed to the on-disk linear space to mirror the change by some +//! the dirty pages are then pushed to the on-disk linear store to mirror the change by some //! asynchronous, out-of-order file writes. See the `BufferCmd::WriteBatch` part of `DiskBuffer::process` //! for the detailed logic. //! @@ -120,15 +120,15 @@ //! memory, then: //! //! - Bring the necessary pages that contain the accessed nodes into the memory and cache them -//! (`storage::CachedSpace`). +//! (`storage::CachedStore`). //! //! - Make changes to the trie, and that induces the writes to some nodes. The nodes are either //! already cached in memory (its pages are cached, or its handle `ObjRef` is still in //! `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the //! second step for it). //! -//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` space that -//! overlays atop `CachedSpace`, so all dirty pages during the current write batch will be +//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` store that +//! overlays atop `CachedStore`, so all dirty pages during the current write batch will be //! exactly captured in `StoreRevMut` (see `StoreRevMut::delta`). //! //! - Finally: @@ -139,18 +139,18 @@ //! //! - Commit: otherwise, the write batch is committed, the interval writes (`storage::Ash`) will be bundled //! into a single WAL record (`storage::AshRecord`) and sent to WAL subsystem, before dirty pages -//! are scheduled to be written to the space files. Also the dirty pages are applied to the -//! underlying `CachedSpace`. `StoreRevMut` becomes empty again for further write batches. +//! are scheduled to be written to the store files. Also the dirty pages are applied to the +//! underlying `CachedStore`. `StoreRevMut` becomes empty again for further write batches. //! -//! Parts of the following diagram show this normal flow, the "staging" space (implemented by +//! Parts of the following diagram show this normal flow, the "staging" store (implemented by //! `StoreRevMut`) concept is a bit similar to the staging area in Git, which enables the handling //! of (resuming from) write errors, clean abortion of an on-going write batch so the entire store //! state remains intact, and also reduces unnecessary premature disk writes. Essentially, we -//! copy-on-write pages in the space that are touched upon, without directly mutating the -//! underlying "master" space. The staging space is just a collection of these "shadowing" pages +//! copy-on-write pages in the store that are touched upon, without directly mutating the +//! underlying "master" store. The staging store is just a collection of these "shadowing" pages //! and a reference to the its base (master) so any reads could partially hit those dirty pages //! and/or fall through to the base, whereas all writes are captured. Finally, when things go well, -//! we "push down" these changes to the base and clear up the staging space. +//! we "push down" these changes to the base and clear up the staging store. //! //!

    //! @@ -161,25 +161,25 @@ //! shows previously logged write batch records could be kept even though they are no longer needed //! for the purpose of crash recovery. The interval writes from a record can be aggregated into //! pages (see `storage::StoreDelta::new`) and used to reconstruct a "ghost" image of past -//! revision of the linear space (just like how staging space works, except that the ghost space is +//! revision of the linear store (just like how staging store works, except that the ghost store is //! essentially read-only once constructed). The shadow pages there will function as some -//! "rewinding" changes to patch the necessary locations in the linear space, while the rest of the -//! linear space is very likely untouched by that historical write batch. +//! "rewinding" changes to patch the necessary locations in the linear store, while the rest of the +//! linear store is very likely untouched by that historical write batch. //! //! Then, with the three-layer abstraction we previously talked about, a historical trie could be //! derived. In fact, because there is no mandatory traversal or scanning in the process, the //! only cost to revive a historical state from the log is to just playback the records and create -//! those shadow pages. There is very little additional cost because the ghost space is summoned on an +//! those shadow pages. There is very little additional cost because the ghost store is summoned on an //! on-demand manner while one accesses the historical trie. //! //! In the other direction, when new write batches are committed, the system moves forward, we can //! therefore maintain a rolling window of past revisions in memory with *zero* cost. The -//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) space goes one -//! step forward, the staging space is cleared, and an extra ghost space (colored in purple) can be +//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) store goes one +//! step forward, the staging store is cleared, and an extra ghost store (colored in purple) can be //! created to hold the version of the store before the commit. The backward delta is applied to //! counteract the change that has been made to the persisted store, which is also a set of shadow pages. -//! No change is required for other historical ghost space instances. Finally, we can phase out -//! some very old ghost space to keep the size of the rolling window invariant. +//! No change is required for other historical ghost store instances. Finally, we can phase out +//! some very old ghost store to keep the size of the rolling window invariant. //! pub mod db; pub(crate) mod file; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 588cc4c24e51..2c8be8225e85 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1434,11 +1434,11 @@ mod tests { let mem_payload = InMemLinearStore::new(0x10000, 0x1); let cache = shale::ObjCache::new(1); - let space = + let store = shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("Store init fail"); - Merkle::new(space) + Merkle::new(store) } pub(super) fn create_test_merkle() -> Merkle { diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index e62b7845926e..36492e291d43 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -71,11 +71,11 @@ where let mem_payload = InMemLinearStore::new(compact_size, 0x1); let cache = shale::ObjCache::new(1); - let space = + let store = shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) .expect("Store init fail"); - let merkle = Merkle::new(space); + let merkle = Merkle::new(store); #[allow(clippy::unwrap_used)] let root = merkle.init_root().unwrap(); diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index c5da7f65485f..d661333ef52f 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -162,41 +162,41 @@ impl Storable for ChunkDescriptor { #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct StoreHeader { - meta_space_tail: DiskAddress, - data_space_tail: DiskAddress, + meta_store_tail: DiskAddress, + data_store_tail: DiskAddress, base_addr: DiskAddress, alloc_addr: DiskAddress, } #[derive(Debug)] struct StoreHeaderSliced { - meta_space_tail: Obj, - data_space_tail: Obj, + meta_store_tail: Obj, + data_store_tail: Obj, base_addr: Obj, alloc_addr: Obj, } impl StoreHeaderSliced { fn flush_dirty(&mut self) { - self.meta_space_tail.flush_dirty(); - self.data_space_tail.flush_dirty(); + self.meta_store_tail.flush_dirty(); + self.data_store_tail.flush_dirty(); self.base_addr.flush_dirty(); self.alloc_addr.flush_dirty(); } } impl StoreHeader { - const META_SPACE_TAIL_OFFSET: usize = 0; - const DATA_SPACE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; + const META_STORE_TAIL_OFFSET: usize = 0; + const DATA_STORE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; const BASE_ADDR_OFFSET: usize = - Self::DATA_SPACE_TAIL_OFFSET + DiskAddress::SERIALIZED_LEN as usize; + Self::DATA_STORE_TAIL_OFFSET + DiskAddress::SERIALIZED_LEN as usize; const ALLOC_ADDR_OFFSET: usize = Self::BASE_ADDR_OFFSET + DiskAddress::SERIALIZED_LEN as usize; pub const SERIALIZED_LEN: u64 = Self::ALLOC_ADDR_OFFSET as u64 + DiskAddress::SERIALIZED_LEN; pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { Self { - meta_space_tail: DiskAddress::new(meta_base), - data_space_tail: DiskAddress::new(compact_base), + meta_store_tail: DiskAddress::new(meta_base), + data_store_tail: DiskAddress::new(compact_base), base_addr: DiskAddress::new(meta_base), alloc_addr: DiskAddress::new(meta_base), } @@ -204,17 +204,17 @@ impl StoreHeader { fn into_fields(r: Obj) -> Result { Ok(StoreHeaderSliced { - meta_space_tail: StoredView::slice( + meta_store_tail: StoredView::slice( &r, - Self::META_SPACE_TAIL_OFFSET, + Self::META_STORE_TAIL_OFFSET, DiskAddress::SERIALIZED_LEN, - r.meta_space_tail, + r.meta_store_tail, )?, - data_space_tail: StoredView::slice( + data_store_tail: StoredView::slice( &r, - Self::DATA_SPACE_TAIL_OFFSET, + Self::DATA_STORE_TAIL_OFFSET, DiskAddress::SERIALIZED_LEN, - r.data_space_tail, + r.data_store_tail, )?, base_addr: StoredView::slice( &r, @@ -241,11 +241,11 @@ impl Storable for StoreHeader { size: Self::SERIALIZED_LEN, })?; #[allow(clippy::indexing_slicing)] - let meta_space_tail = raw.as_deref()[..Self::DATA_SPACE_TAIL_OFFSET] + let meta_store_tail = raw.as_deref()[..Self::DATA_STORE_TAIL_OFFSET] .try_into() .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); #[allow(clippy::indexing_slicing)] - let data_space_tail = raw.as_deref()[Self::DATA_SPACE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET] + let data_store_tail = raw.as_deref()[Self::DATA_STORE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET] .try_into() .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); #[allow(clippy::indexing_slicing)] @@ -257,8 +257,8 @@ impl Storable for StoreHeader { .try_into() .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); Ok(Self { - meta_space_tail, - data_space_tail, + meta_store_tail, + data_store_tail, base_addr, alloc_addr, }) @@ -270,8 +270,8 @@ impl Storable for StoreHeader { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.meta_space_tail.to_le_bytes())?; - cur.write_all(&self.data_space_tail.to_le_bytes())?; + cur.write_all(&self.meta_store_tail.to_le_bytes())?; + cur.write_all(&self.data_store_tail.to_le_bytes())?; cur.write_all(&self.base_addr.to_le_bytes())?; cur.write_all(&self.alloc_addr.to_le_bytes())?; Ok(()) @@ -280,8 +280,8 @@ impl Storable for StoreHeader { #[derive(Debug)] struct StoreInner { - meta_space: M, - data_space: M, + meta_store: M, + data_store: M, header: StoreHeaderSliced, alloc_max_walk: u64, regn_nbit: u64, @@ -290,8 +290,8 @@ struct StoreInner { impl From> for StoreInner { fn from(value: StoreInner) -> StoreInner { StoreInner { - meta_space: value.meta_space.into(), - data_space: value.data_space.into(), + meta_store: value.meta_store.into(), + data_store: value.data_store.into(), header: value.header, alloc_max_walk: value.alloc_max_walk, regn_nbit: value.regn_nbit, @@ -301,7 +301,7 @@ impl From> for StoreInner { impl StoreInner { fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.meta_space, ptr, ChunkDescriptor::SERIALIZED_LEN) + StoredView::ptr_to_obj(&self.meta_store, ptr, ChunkDescriptor::SERIALIZED_LEN) } fn get_data_ref( @@ -309,7 +309,7 @@ impl StoreInner { ptr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.data_space, ptr, len_limit) + StoredView::ptr_to_obj(&self.data_store, ptr, len_limit) } fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { @@ -326,12 +326,12 @@ impl StoreInner { // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); #[allow(clippy::unwrap_used)] self.header - .meta_space_tail + .meta_store_tail .modify(|r| *r -= desc_size as usize) .unwrap(); - if desc_addr != DiskAddress(**self.header.meta_space_tail) { - let desc_last = self.get_descriptor(*self.header.meta_space_tail.value)?; + if desc_addr != DiskAddress(**self.header.meta_store_tail) { + let desc_last = self.get_descriptor(*self.header.meta_store_tail.value)?; let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] desc.modify(|r| *r = *desc_last).unwrap(); @@ -344,10 +344,10 @@ impl StoreInner { } fn new_desc(&mut self) -> Result { - let addr = **self.header.meta_space_tail; + let addr = **self.header.meta_store_tail; #[allow(clippy::unwrap_used)] self.header - .meta_space_tail + .meta_store_tail .modify(|r| *r += ChunkDescriptor::SERIALIZED_LEN as usize) .unwrap(); @@ -387,7 +387,7 @@ impl StoreInner { let mut f = offset; #[allow(clippy::unwrap_used)] - if offset + ChunkFooter::SERIALIZED_LEN < self.header.data_space_tail.unwrap().get() as u64 + if offset + ChunkFooter::SERIALIZED_LEN < self.header.data_store_tail.unwrap().get() as u64 && (regn_size - (offset & (regn_size - 1))) >= ChunkFooter::SERIALIZED_LEN + ChunkHeader::SERIALIZED_LEN { @@ -440,7 +440,7 @@ impl StoreInner { const FOOTER_SIZE: usize = ChunkFooter::SERIALIZED_LEN as usize; const DESCRIPTOR_SIZE: usize = ChunkDescriptor::SERIALIZED_LEN as usize; - let tail = *self.header.meta_space_tail; + let tail = *self.header.meta_store_tail; if tail == *self.header.base_addr { return Ok(None); } @@ -550,10 +550,10 @@ impl StoreInner { fn alloc_new(&mut self, length: u64) -> Result { let regn_size = 1 << self.regn_nbit; let total_length = ChunkHeader::SERIALIZED_LEN + length + ChunkFooter::SERIALIZED_LEN; - let mut offset = *self.header.data_space_tail; + let mut offset = *self.header.data_store_tail; #[allow(clippy::unwrap_used)] self.header - .data_space_tail + .data_store_tail .modify(|r| { // an item is always fully in one region let rem = regn_size - (offset & (regn_size - 1)).get(); @@ -602,8 +602,8 @@ pub struct Store { impl Store { pub fn new( - meta_space: M, - data_space: M, + meta_store: M, + data_store: M, header: Obj, obj_cache: super::ObjCache, alloc_max_walk: u64, @@ -611,8 +611,8 @@ impl Store { ) -> Result { let cs = Store { inner: RwLock::new(StoreInner { - meta_space, - data_space, + meta_store, + data_store, header: StoreHeader::into_fields(header)?, alloc_max_walk, regn_nbit, @@ -645,9 +645,9 @@ impl Store { #[allow(clippy::unwrap_used)] let obj = { let inner = self.inner.read().unwrap(); - let data_space = &inner.data_space; + let data_store = &inner.data_store; #[allow(clippy::unwrap_used)] - let view = StoredView::item_to_obj(data_space, addr.try_into().unwrap(), size, item)?; + let view = StoredView::item_to_obj(data_store, addr.try_into().unwrap(), size, item)?; self.obj_cache.put(view) }; @@ -761,14 +761,14 @@ mod tests { } #[test] - fn test_space_item() { + fn test_store_item() { let meta_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); let compact_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); let reserved: DiskAddress = 0x1000.into(); let mut dm = InMemLinearStore::new(meta_size.get() as u64, 0x0); - // initialize compact space + // initialize compact store let compact_header = DiskAddress::from(0x1); dm.write( compact_header.unwrap().get(), @@ -782,15 +782,15 @@ mod tests { let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); let cache: ObjCache = ObjCache::new(1); - let space = Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); + let store = Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); // initial write let data = b"hello world"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - let obj_ref = space.put_item(Hash(hash), 0).unwrap(); + let obj_ref = store.put_item(Hash(hash), 0).unwrap(); assert_eq!(obj_ref.as_ptr(), DiskAddress::from(4113)); // create hash ptr from address and attempt to read dirty write. - let hash_ref = space.get_item(DiskAddress::from(4113)).unwrap(); + let hash_ref = store.get_item(DiskAddress::from(4113)).unwrap(); // read before flush results in zeroed hash assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); // not cached @@ -815,7 +815,7 @@ mod tests { drop(obj_ref); // write is visible assert_eq!( - space.get_item(DiskAddress::from(4113)).unwrap().as_ref(), + store.get_item(DiskAddress::from(4113)).unwrap().as_ref(), hash ); } diff --git a/firewood/src/shale/in_mem.rs b/firewood/src/shale/in_mem.rs index 78e0c4cb94d3..8fea3849724b 100644 --- a/firewood/src/shale/in_mem.rs +++ b/firewood/src/shale/in_mem.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::shale::{LinearStore, LinearStoreView, SendSyncDerefMut, SpaceId}; +use crate::shale::{LinearStore, LinearStoreView, SendSyncDerefMut, StoreId}; use std::{ fmt::Debug, ops::{Deref, DerefMut}, @@ -14,14 +14,14 @@ use super::ShaleError; // [CachedStore]. Allocates more space on `write` if original size isn't enough. #[derive(Debug)] pub struct InMemLinearStore { - space: Arc>>, - id: SpaceId, + store: Arc>>, + id: StoreId, } impl InMemLinearStore { - pub fn new(size: u64, id: SpaceId) -> Self { - let space = Arc::new(RwLock::new(vec![0; size as usize])); - Self { space, id } + pub fn new(size: u64, id: StoreId) -> Self { + let store = Arc::new(RwLock::new(vec![0; size as usize])); + Self { store, id } } } @@ -34,18 +34,18 @@ impl LinearStore for InMemLinearStore { let length = length as usize; let size = offset + length; #[allow(clippy::unwrap_used)] - let mut space = self.space.write().unwrap(); + let mut store = self.store.write().unwrap(); // Increase the size if the request range exceeds the current limit. - if size > space.len() { - space.resize(size, 0); + if size > store.len() { + store.resize(size, 0); } Some(Box::new(InMemLinearStoreView { offset, length, mem: Self { - space: self.space.clone(), + store: self.store.clone(), id: self.id, }, })) @@ -53,7 +53,7 @@ impl LinearStore for InMemLinearStore { fn get_shared(&self) -> Box> { Box::new(InMemLinearStoreShared(Self { - space: self.space.clone(), + store: self.store.clone(), id: self.id, })) } @@ -63,19 +63,19 @@ impl LinearStore for InMemLinearStore { let size = offset + length; #[allow(clippy::unwrap_used)] - let mut space = self.space.write().unwrap(); + let mut store = self.store.write().unwrap(); // Increase the size if the request range exceeds the current limit. - if size > space.len() { - space.resize(size, 0); + if size > store.len() { + store.resize(size, 0); } #[allow(clippy::indexing_slicing)] - space[offset..offset + length].copy_from_slice(change); + store[offset..offset + length].copy_from_slice(change); Ok(()) } - fn id(&self) -> SpaceId { + fn id(&self) -> StoreId { self.id } @@ -100,7 +100,7 @@ impl LinearStoreView for InMemLinearStoreView { fn as_deref(&self) -> Self::DerefReturn { #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - self.mem.space.read().unwrap()[self.offset..self.offset + self.length].to_vec() + self.mem.store.read().unwrap()[self.offset..self.offset + self.length].to_vec() } } diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index de7b756b5765..a290fe415ac2 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -45,11 +45,11 @@ pub enum ShaleError { // this could probably included with ShaleError, // but keeping it separate for now as Obj/ObjRef might change in the near future #[derive(Debug, Error)] -#[error("object cannot be written in the space provided")] +#[error("object cannot be written in the store provided")] pub struct ObjWriteSizeError; -pub type SpaceId = u8; -pub const INVALID_SPACE_ID: SpaceId = 0xff; +pub type StoreId = u8; +pub const INVALID_STORE_ID: StoreId = 0xff; /// A handle that pins and provides a readable access to a portion of a [LinearStore]. pub trait LinearStoreView { @@ -61,7 +61,7 @@ pub trait SendSyncDerefMut: DerefMut + Send + Sync {} impl SendSyncDerefMut for T {} -/// In-memory store that offers access to intervals from a linear byte space, which is usually +/// In-memory store that offers access to intervals from a linear byte store, which is usually /// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear /// persistent store. Reads may trigger disk reads to bring data into memory, but writes will /// *only* be visible in memory -- they do not write to disk. @@ -77,12 +77,12 @@ pub trait LinearStore: Debug + Send + Sync { /// Returns a handle that allows shared access to this store. fn get_shared(&self) -> Box>; - /// Write the `change` to the linear space starting at `offset`. The change should - /// be immediately visible to all `LinearStoreView` associated with this linear space. + /// Write the `change` to the linear store starting at `offset`. The change should + /// be immediately visible to all `LinearStoreView` associated with this linear store. fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError>; - /// Returns the identifier of this storage space. - fn id(&self) -> SpaceId; + /// Returns the identifier of this store. + fn id(&self) -> StoreId; /// Returns whether or not this store is writable fn is_writeable(&self) -> bool; @@ -330,13 +330,13 @@ impl StoredView { impl StoredView { #[inline(always)] - fn new(offset: usize, len_limit: u64, space: &U) -> Result { - let item = T::deserialize(offset, space)?; + fn new(offset: usize, len_limit: u64, store: &U) -> Result { + let item = T::deserialize(offset, store)?; Ok(Self { offset, item, - mem: space.get_shared(), + mem: store.get_shared(), len_limit, }) } @@ -346,12 +346,12 @@ impl StoredView { offset: usize, len_limit: u64, item: T, - space: &dyn LinearStore, + store: &dyn LinearStore, ) -> Result { Ok(Self { offset, item, - mem: space.get_shared(), + mem: store.get_shared(), len_limit, }) } @@ -387,12 +387,12 @@ impl StoredView { offset: usize, len_limit: u64, item: T, - space: &dyn LinearStore, + store: &dyn LinearStore, ) -> Result { Ok(Self { offset, item, - mem: space.get_shared(), + mem: store.get_shared(), len_limit, }) } diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs index 75295d29604c..23fd753bea35 100644 --- a/firewood/src/storage/buffer.rs +++ b/firewood/src/storage/buffer.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; -use crate::shale::SpaceId; +use crate::shale::StoreId; use crate::storage::DeltaPage; use aiofut::{AioBuilder, AioError, AioManager}; use futures::future::join_all; @@ -40,10 +40,10 @@ pub enum BufferCmd { /// Process a write batch against the underlying store. WriteBatch(BufferWrites, AshRecord), /// Get a page from the disk buffer. - GetPage((SpaceId, u64), oneshot::Sender>), + GetPage((StoreId, u64), oneshot::Sender>), CollectAsh(usize, oneshot::Sender>), - /// Register a new space and add the files to a memory mapped pool. - RegCachedSpace(SpaceId, Arc), + /// Register a new store and add the files to a memory mapped pool. + RegCachedStore(StoreId, Arc), /// Returns false if the Shutdown, } @@ -77,7 +77,7 @@ pub struct DiskBufferConfig { /// List of pages to write to disk. #[derive(Debug)] pub struct BufferWrite { - pub space_id: SpaceId, + pub store_id: StoreId, pub delta: StoreDelta, } @@ -212,12 +212,12 @@ struct WalQueueMax { /// Add an pending pages to aio manager for processing by the local pool. fn schedule_write( - pending: Rc>>, + pending: Rc>>, fc_notifier: Rc, file_pools: Rc>; 255]>>, aiomgr: Rc, max: WalQueueMax, - page_key: (SpaceId, u64), + page_key: (StoreId, u64), ) { use std::collections::hash_map::Entry::*; @@ -299,12 +299,12 @@ async fn init_wal( |raw, _| { let batch = AshRecord::deserialize(raw); - for (space_id, ash) in batch.0 { + for (store_id, ash) in batch.0 { for (undo, redo) in ash.iter() { let offset = undo.offset; let file_pools = file_pools.borrow(); #[allow(clippy::unwrap_used, clippy::indexing_slicing)] - let file_pool = file_pools[space_id as usize].as_ref().unwrap(); + let file_pool = file_pools[store_id as usize].as_ref().unwrap(); let file_nbit = file_pool.get_file_nbit(); let file_mask = (1 << file_nbit) - 1; let fid = offset >> file_nbit; @@ -343,7 +343,7 @@ async fn init_wal( async fn run_wal_queue( max: WalQueueMax, wal: Rc>>, - pending: Rc>>, + pending: Rc>>, file_pools: Rc>; 255]>>, mut writes: mpsc::Receiver<(BufferWrites, AshRecord)>, fc_notifier: Rc, @@ -382,9 +382,9 @@ async fn run_wal_queue( let sem = Rc::new(tokio::sync::Semaphore::new(0)); let mut npermit = 0; - for BufferWrite { space_id, delta } in bwrites { + for BufferWrite { store_id, delta } in bwrites { for DeltaPage(page_id, page) in delta.0 { - let page_key = (space_id, page_id); + let page_key = (store_id, page_id); let should_write = match pending.borrow_mut().entry(page_key) { Occupied(mut e) => { @@ -461,7 +461,7 @@ fn panic_on_intialization_failure_with<'a, T>( #[allow(clippy::too_many_arguments)] async fn process( - pending: Rc>>, + pending: Rc>>, fc_notifier: Rc, file_pools: Rc>; 255]>>, aiomgr: Rc, @@ -544,11 +544,11 @@ async fn process( #[allow(clippy::unwrap_used)] tx.send(ash).unwrap(); } - BufferCmd::RegCachedSpace(space_id, files) => { + BufferCmd::RegCachedStore(store_id, files) => { file_pools .borrow_mut() .as_mut_slice() - .index_mut(space_id as usize) + .index_mut(store_id as usize) .replace(files); } } @@ -581,10 +581,10 @@ impl DiskBufferRequester { } /// Get a page from the buffer. - pub fn get_page(&self, space_id: SpaceId, page_id: u64) -> Option { + pub fn get_page(&self, store_id: StoreId, page_id: u64) -> Option { let (resp_tx, resp_rx) = oneshot::channel(); self.sender - .send(BufferCmd::GetPage((space_id, page_id), resp_tx)) + .send(BufferCmd::GetPage((store_id, page_id), resp_tx)) .map_err(StoreError::Send) .ok(); #[allow(clippy::unwrap_used)] @@ -625,10 +625,10 @@ impl DiskBufferRequester { block_in_place(|| resp_rx.blocking_recv().map_err(StoreError::Receive)) } - /// Register a cached space to the buffer. - pub fn reg_cached_space(&self, space_id: SpaceId, files: Arc) { + /// Register a cached store to the buffer. + pub fn reg_cached_store(&self, store_id: StoreId, files: Arc) { self.sender - .send(BufferCmd::RegCachedSpace(space_id, files)) + .send(BufferCmd::RegCachedStore(store_id, files)) .map_err(StoreError::Send) .ok(); } @@ -644,12 +644,12 @@ mod tests { use crate::{ file, storage::{ - Ash, CachedSpace, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, + Ash, CachedStore, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, StoreRevShared, ZeroStore, }, }; - const STATE_SPACE: SpaceId = 0x0; + const STATE_STORE_ID: StoreId = 0x0; const HASH_SIZE: usize = 32; fn get_tmp_dir() -> PathBuf { @@ -659,15 +659,15 @@ mod tests { .join("firewood") } - fn new_cached_space_for_test( + fn new_cached_store_for_test( state_path: PathBuf, disk_requester: DiskBufferRequester, - ) -> Arc { - CachedSpace::new( + ) -> Arc { + CachedStore::new( &StoreConfig::builder() .ncached_pages(1) .ncached_files(1) - .space_id(STATE_SPACE) + .store_id(STATE_STORE_ID) .file_nbit(1) .rootdir(state_path) .build(), @@ -697,11 +697,11 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache = new_cached_space_for_test(state_path, disk_requester.clone()); + let state_cache = new_cached_store_for_test(state_path, disk_requester.clone()); - // add an in memory cached space. this will allow us to write to the + // add an in memory cached store. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.id(), state_cache.inner.read().files.clone()); + disk_requester.reg_cached_store(state_cache.id(), state_cache.inner.read().files.clone()); // memory mapped store let mut mut_store = StoreRevMut::new(state_cache); @@ -710,14 +710,14 @@ mod tests { // write to the in memory buffer not to disk mut_store.write(0, change).unwrap(); - assert_eq!(mut_store.id(), STATE_SPACE); + assert_eq!(mut_store.id(), STATE_STORE_ID); // mutate the in memory buffer. let change = b"this is another test"; // write to the in memory buffer (ash) not yet to disk mut_store.write(0, change).unwrap(); - assert_eq!(mut_store.id(), STATE_SPACE); + assert_eq!(mut_store.id(), STATE_STORE_ID); // wal should have no records. assert!(disk_requester.collect_ash(1).unwrap().is_empty()); @@ -734,7 +734,7 @@ mod tests { // wal is empty assert!(d1.collect_ash(1).unwrap().is_empty()); // page is not yet persisted to disk. - assert!(d1.get_page(STATE_SPACE, 0).is_none()); + assert!(d1.get_page(STATE_STORE_ID, 0).is_none()); d1.write(page_batch, write_batch); }); // wait for the write to complete. @@ -773,11 +773,11 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache = new_cached_space_for_test(state_path, disk_requester.clone()); + let state_cache = new_cached_store_for_test(state_path, disk_requester.clone()); - // add an in memory cached space. this will allow us to write to the + // add an in memory cached store. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.id(), state_cache.clone_files()); + disk_requester.reg_cached_store(state_cache.id(), state_cache.clone_files()); // memory mapped store let mut mut_store = StoreRevMut::new(state_cache.clone()); @@ -788,7 +788,7 @@ mod tests { // write to the in memory buffer (ash) not yet to disk mut_store.write(0, &hash).unwrap(); - assert_eq!(mut_store.id(), STATE_SPACE); + assert_eq!(mut_store.id(), STATE_STORE_ID); // wal should have no records. assert!(disk_requester.collect_ash(1).unwrap().is_empty()); @@ -798,7 +798,7 @@ mod tests { assert_eq!(view.as_deref(), hash); // Commit the change. Take the delta from cached store, - // then apply changes to the CachedSpace. + // then apply changes to the CachedStore. let (redo_delta, wal) = mut_store.delta(); state_cache.update(&redo_delta).unwrap(); @@ -806,13 +806,13 @@ mod tests { // wal is empty assert!(disk_requester.collect_ash(1).unwrap().is_empty()); // page is not yet persisted to disk. - assert!(disk_requester.get_page(STATE_SPACE, 0).is_none()); + assert!(disk_requester.get_page(STATE_STORE_ID, 0).is_none()); disk_requester.write( Box::new([BufferWrite { - space_id: STATE_SPACE, + store_id: STATE_STORE_ID, delta: redo_delta, }]), - AshRecord([(STATE_SPACE, wal)].into()), + AshRecord([(STATE_STORE_ID, wal)].into()), ); // verify @@ -822,7 +822,7 @@ mod tests { // replay the redo from the wal let shared_store = StoreRevShared::from_ash( Arc::new(ZeroStore::default()), - &ashes[0].0[&STATE_SPACE].redo, + &ashes[0].0[&STATE_STORE_ID].redo, ); let view = shared_store.get_view(0, hash.len() as u64).unwrap(); assert_eq!(view.as_deref(), hash); @@ -846,11 +846,11 @@ mod tests { disk_requester.init_wal("wal", &root_db_path); // create a new state cache which tracks on disk state. - let state_cache: Arc = CachedSpace::new( + let state_cache: Arc = CachedStore::new( &StoreConfig::builder() .ncached_pages(1) .ncached_files(1) - .space_id(STATE_SPACE) + .store_id(STATE_STORE_ID) .file_nbit(1) .rootdir(state_path) .build(), @@ -859,9 +859,9 @@ mod tests { .unwrap() .into(); - // add an in memory cached space. this will allow us to write to the + // add an in memory cached store. this will allow us to write to the // disk buffer then later persist the change to disk. - disk_requester.reg_cached_space(state_cache.id(), state_cache.clone_files()); + disk_requester.reg_cached_store(state_cache.id(), state_cache.clone_files()); // memory mapped store let mut store = StoreRevMut::new(state_cache.clone()); @@ -870,7 +870,7 @@ mod tests { let data = b"this is a test"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); block_in_place(|| store.write(0, &hash)).unwrap(); - assert_eq!(store.id(), STATE_SPACE); + assert_eq!(store.id(), STATE_STORE_ID); let another_data = b"this is another test"; let another_hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(another_data).into(); @@ -878,7 +878,7 @@ mod tests { // mutate the in memory buffer in another StoreRev new from the above. let mut another_store = StoreRevMut::new_from_other(&store); block_in_place(|| another_store.write(32, &another_hash)).unwrap(); - assert_eq!(another_store.id(), STATE_SPACE); + assert_eq!(another_store.id(), STATE_STORE_ID); // wal should have no records. assert!(block_in_place(|| disk_requester.collect_ash(1)) @@ -915,7 +915,7 @@ mod tests { assert_eq!(1, another_redo_delta.0.len()); assert_eq!(2, another_wal.undo.len()); - // Verify after the changes been applied to underlying CachedSpace, + // Verify after the changes been applied to underlying CachedStore, // the newly created stores should see the previous changes. state_cache.update(&redo_delta).unwrap(); let store = StoreRevMut::new(state_cache.clone()); @@ -960,11 +960,11 @@ mod tests { pages.sort_by_key(|p| p.0); let page_batch = Box::new([BufferWrite { - space_id: STATE_SPACE, + store_id: STATE_STORE_ID, delta: StoreDelta(pages), }]); - let write_batch = AshRecord([(STATE_SPACE, deltas.plain)].into()); + let write_batch = AshRecord([(STATE_STORE_ID, deltas.plain)].into()); (page_batch, write_batch) } } diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs index a073dde85c42..a9eb6e3a951b 100644 --- a/firewood/src/storage/mod.rs +++ b/firewood/src/storage/mod.rs @@ -4,7 +4,7 @@ // TODO: try to get rid of the use `RefCell` in this file use self::buffer::DiskBufferRequester; use crate::file::File; -use crate::shale::{self, LinearStore, LinearStoreView, SendSyncDerefMut, ShaleError, SpaceId}; +use crate::shale::{self, LinearStore, LinearStoreView, SendSyncDerefMut, ShaleError, StoreId}; use nix::fcntl::{Flock, FlockArg}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -52,14 +52,14 @@ impl From for StoreError { pub trait MemStoreR: Debug + Send + Sync { /// Returns a slice of bytes from memory. fn get_slice(&self, offset: u64, length: u64) -> Option>; - fn id(&self) -> SpaceId; + fn id(&self) -> StoreId; } // Page should be boxed as to not take up so much stack-space type Page = Box<[u8; PAGE_SIZE as usize]>; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct SpaceWrite { +pub struct StoreWrite { offset: u64, data: Box<[u8]>, } @@ -68,19 +68,19 @@ pub struct SpaceWrite { /// In memory representation of Write-ahead log with `undo` and `redo`. pub struct Ash { /// Deltas to undo the changes. - pub undo: Vec, + pub undo: Vec, /// Deltas to replay the changes. - pub redo: Vec, + pub redo: Vec, } impl Ash { - fn iter(&self) -> impl Iterator { + fn iter(&self) -> impl Iterator { self.undo.iter().zip(self.redo.iter()) } } #[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct AshRecord(pub HashMap); +pub struct AshRecord(pub HashMap); impl growthring::wal::Record for AshRecord { fn serialize(&self) -> growthring::wal::WalBytes { @@ -97,7 +97,7 @@ impl AshRecord { } } -/// Basic copy-on-write item in the linear storage space for multi-versioning. +/// Basic copy-on-write item in the linear storage store for multi-versioning. pub struct DeltaPage(u64, Page); impl DeltaPage { @@ -134,7 +134,7 @@ impl Deref for StoreDelta { } impl StoreDelta { - pub fn new(src: &dyn MemStoreR, writes: &[SpaceWrite]) -> Self { + pub fn new(src: &dyn MemStoreR, writes: &[StoreWrite]) -> Self { let mut deltas = Vec::new(); #[allow(clippy::indexing_slicing)] let mut widx: Vec<_> = (0..writes.len()) @@ -220,7 +220,7 @@ impl StoreDelta { } pub struct StoreRev { - base_space: RwLock>, + base_store: RwLock>, delta: StoreDelta, } @@ -236,7 +236,7 @@ impl fmt::Debug for StoreRev { impl MemStoreR for StoreRev { fn get_slice(&self, offset: u64, length: u64) -> Option> { - let base_space = self.base_space.read(); + let base_store = self.base_store.read(); let mut start = offset; let end = start + length; let delta = &self.delta; @@ -244,7 +244,7 @@ impl MemStoreR for StoreRev { let mut r = delta.len(); // no dirty page, before or after all dirty pages if r == 0 { - return base_space.get_slice(start, end - start); + return base_store.get_slice(start, end - start); } // otherwise, some dirty pages are covered by the range while r - l > 1 { @@ -262,7 +262,7 @@ impl MemStoreR for StoreRev { } #[allow(clippy::indexing_slicing)] if l >= delta.len() || end < delta[l].offset() { - return base_space.get_slice(start, end - start); + return base_store.get_slice(start, end - start); } let mut data = Vec::new(); #[allow(clippy::indexing_slicing)] @@ -270,7 +270,7 @@ impl MemStoreR for StoreRev { #[allow(clippy::indexing_slicing)] if start < delta[l].offset() { #[allow(clippy::indexing_slicing)] - data.extend(base_space.get_slice(start, delta[l].offset() - start)?); + data.extend(base_store.get_slice(start, delta[l].offset() - start)?); #[allow(clippy::indexing_slicing)] data.extend(&delta[l].data()[..p_off as usize]); } else { @@ -283,13 +283,13 @@ impl MemStoreR for StoreRev { l += 1; #[allow(clippy::indexing_slicing)] if l >= delta.len() || end < delta[l].offset() { - data.extend(base_space.get_slice(start, end - start)?); + data.extend(base_store.get_slice(start, end - start)?); break; } #[allow(clippy::indexing_slicing)] if delta[l].offset() > start { #[allow(clippy::indexing_slicing)] - data.extend(base_space.get_slice(start, delta[l].offset() - start)?); + data.extend(base_store.get_slice(start, delta[l].offset() - start)?); } #[allow(clippy::indexing_slicing)] if end < delta[l].offset() + PAGE_SIZE { @@ -306,8 +306,8 @@ impl MemStoreR for StoreRev { Some(data) } - fn id(&self) -> SpaceId { - self.base_space.read().id() + fn id(&self) -> StoreId { + self.base_store.read().id() } } @@ -315,19 +315,19 @@ impl MemStoreR for StoreRev { pub struct StoreRevShared(Arc); impl StoreRevShared { - pub fn from_ash(base_space: Arc, writes: &[SpaceWrite]) -> Self { - let delta = StoreDelta::new(base_space.as_ref(), writes); - let base_space = RwLock::new(base_space); - Self(Arc::new(StoreRev { base_space, delta })) + pub fn from_ash(base_store: Arc, writes: &[StoreWrite]) -> Self { + let delta = StoreDelta::new(base_store.as_ref(), writes); + let base_store = RwLock::new(base_store); + Self(Arc::new(StoreRev { base_store, delta })) } - pub fn from_delta(base_space: Arc, delta: StoreDelta) -> Self { - let base_space = RwLock::new(base_space); - Self(Arc::new(StoreRev { base_space, delta })) + pub fn from_delta(base_store: Arc, delta: StoreDelta) -> Self { + let base_store = RwLock::new(base_store); + Self(Arc::new(StoreRev { base_store, delta })) } - pub fn set_base_space(&mut self, base_space: Arc) { - *self.0.base_space.write() = base_space + pub fn set_base_store(&mut self, base_store: Arc) { + *self.0.base_store.write() = base_store } pub const fn inner(&self) -> &Arc { @@ -355,7 +355,7 @@ impl LinearStore for StoreRevShared { Err(ShaleError::ImmutableWrite) } - fn id(&self) -> SpaceId { + fn id(&self) -> StoreId { ::id(&self.0) } @@ -375,7 +375,7 @@ impl From for StoreRevShared { let delta = StoreDelta(pages); let rev = Arc::new(StoreRev { - base_space: RwLock::new(value.base_space), + base_store: RwLock::new(value.base_store), delta, }); StoreRevShared(rev) @@ -425,13 +425,13 @@ struct StoreRevMutDelta { #[derive(Clone, Debug)] /// A mutable revision of the store. The view is constructed by applying the `deltas` to the -/// `base space`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply +/// `base_store`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply /// the changes. `StoreRevMut` supports basing on top of another `StoreRevMut`, by chaining /// `prev_deltas` (from based `StoreRevMut`) with current `deltas` from itself . In this way, /// callers can create a new `StoreRevMut` from an existing one without actually committing -/// the mutations to the base space. +/// the mutations to the base store. pub struct StoreRevMut { - base_space: Arc, + base_store: Arc, deltas: Arc>, prev_deltas: Arc>, } @@ -439,7 +439,7 @@ pub struct StoreRevMut { impl From for StoreRevMut { fn from(value: StoreRevShared) -> Self { StoreRevMut { - base_space: value.0.base_space.read().clone(), + base_store: value.0.base_store.read().clone(), deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), prev_deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), } @@ -447,9 +447,9 @@ impl From for StoreRevMut { } impl StoreRevMut { - pub fn new(base_space: Arc) -> Self { + pub fn new(base_store: Arc) -> Self { Self { - base_space, + base_store, deltas: Default::default(), prev_deltas: Default::default(), } @@ -457,7 +457,7 @@ impl StoreRevMut { pub fn new_from_other(other: &StoreRevMut) -> Self { Self { - base_space: other.base_space.clone(), + base_store: other.base_store.clone(), deltas: Default::default(), prev_deltas: other.deltas.clone(), } @@ -476,7 +476,7 @@ impl StoreRevMut { .or_insert_with(|| match prev_deltas.pages.get(&pid) { Some(p) => Box::new(*p.as_ref()), None => Box::new( - self.base_space + self.base_store .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) .unwrap() .try_into() @@ -528,7 +528,7 @@ impl LinearStore for StoreRevMut { None => match prev_deltas.get(&s_pid) { #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..e_off + 1].to_vec(), - None => self.base_space.get_slice(offset as u64, length)?, + None => self.base_store.get_slice(offset as u64, length)?, }, } } else { @@ -539,7 +539,7 @@ impl LinearStore for StoreRevMut { #[allow(clippy::indexing_slicing)] Some(p) => p[s_off..].to_vec(), None => self - .base_space + .base_store .get_slice(offset as u64, PAGE_SIZE - s_off as u64)?, }, }; @@ -549,7 +549,7 @@ impl LinearStore for StoreRevMut { None => match prev_deltas.get(&p) { Some(p) => data.extend(**p), None => data.extend( - &self.base_space.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?, + &self.base_store.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?, ), }, }; @@ -561,7 +561,7 @@ impl LinearStore for StoreRevMut { #[allow(clippy::indexing_slicing)] Some(p) => data.extend(&p[..e_off + 1]), None => data.extend( - self.base_space + self.base_store .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, ), }, @@ -629,11 +629,11 @@ impl LinearStore for StoreRevMut { let plain = &mut self.deltas.write().plain; assert!(undo.len() == redo.len()); - plain.undo.push(SpaceWrite { + plain.undo.push(StoreWrite { offset: offset as u64, data: undo.into(), }); - plain.redo.push(SpaceWrite { + plain.redo.push(StoreWrite { offset: offset as u64, data: redo, }); @@ -641,8 +641,8 @@ impl LinearStore for StoreRevMut { Ok(()) } - fn id(&self) -> SpaceId { - self.base_space.id() + fn id(&self) -> StoreId { + self.base_store.id() } fn is_writeable(&self) -> bool { @@ -659,8 +659,8 @@ impl MemStoreR for ZeroStore { Some(vec![0; length as usize]) } - fn id(&self) -> SpaceId { - shale::INVALID_SPACE_ID + fn id(&self) -> StoreId { + shale::INVALID_STORE_ID } } @@ -686,7 +686,7 @@ mod test { canvas[(idx - min) as usize] = *byte; } println!("[0x{l:x}, 0x{r:x})"); - writes.push(SpaceWrite { offset: l, data }); + writes.push(StoreWrite { offset: l, data }); } let z = Arc::new(ZeroStore::default()); let rev = StoreRevShared::from_ash(z, &writes); @@ -719,12 +719,12 @@ pub struct StoreConfig { ncached_files: usize, #[builder(default = 22)] // 4MB file by default file_nbit: u64, - space_id: SpaceId, + store_id: StoreId, rootdir: PathBuf, } #[derive(Debug)] -struct CachedSpaceInner { +struct CachedStoreInner { cached_pages: lru::LruCache, pinned_pages: HashMap, files: Arc, @@ -732,20 +732,20 @@ struct CachedSpaceInner { } #[derive(Clone, Debug)] -pub struct CachedSpace { - inner: Arc>, - space_id: SpaceId, +pub struct CachedStore { + inner: Arc>, + store_id: StoreId, } -impl CachedSpace { +impl CachedStore { pub fn new( cfg: &StoreConfig, disk_requester: DiskBufferRequester, ) -> Result> { - let space_id = cfg.space_id; + let store_id = cfg.store_id; let files = Arc::new(FilePool::new(cfg)?); Ok(Self { - inner: Arc::new(RwLock::new(CachedSpaceInner { + inner: Arc::new(RwLock::new(CachedStoreInner { cached_pages: lru::LruCache::new( NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size"), ), @@ -753,7 +753,7 @@ impl CachedSpace { files, disk_requester, })), - space_id, + store_id, }) } @@ -765,7 +765,7 @@ impl CachedSpace { pub fn update(&self, delta: &StoreDelta) -> Option { let mut pages = Vec::new(); for DeltaPage(pid, page) in &delta.0 { - let data = self.inner.write().pin_page(self.space_id, *pid).ok()?; + let data = self.inner.write().pin_page(self.store_id, *pid).ok()?; // save the original data #[allow(clippy::unwrap_used)] pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap()))); @@ -776,10 +776,10 @@ impl CachedSpace { } } -impl CachedSpaceInner { +impl CachedStoreInner { fn pin_page( &mut self, - space_id: SpaceId, + store_id: StoreId, pid: u64, ) -> Result<&'static mut [u8], StoreError> { let base = match self.pinned_pages.get_mut(&pid) { @@ -791,7 +791,7 @@ impl CachedSpaceInner { let page = self .cached_pages .pop(&pid) - .or_else(|| self.disk_requester.get_page(space_id, pid)); + .or_else(|| self.disk_requester.get_page(store_id, pid)); let mut page = match page { Some(page) => page, None => { @@ -844,7 +844,7 @@ impl CachedSpaceInner { struct PageRef { pid: u64, data: &'static mut [u8], - store: CachedSpace, + store: CachedStore, } impl std::ops::Deref for PageRef { @@ -861,10 +861,10 @@ impl std::ops::DerefMut for PageRef { } impl PageRef { - fn new(pid: u64, store: &CachedSpace) -> Option { + fn new(pid: u64, store: &CachedStore) -> Option { Some(Self { pid, - data: store.inner.write().pin_page(store.space_id, pid).ok()?, + data: store.inner.write().pin_page(store.store_id, pid).ok()?, store: store.clone(), }) } @@ -876,7 +876,7 @@ impl Drop for PageRef { } } -impl MemStoreR for CachedSpace { +impl MemStoreR for CachedStore { fn get_slice(&self, offset: u64, length: u64) -> Option> { if length == 0 { return Some(Default::default()); @@ -903,8 +903,8 @@ impl MemStoreR for CachedSpace { Some(data) } - fn id(&self) -> SpaceId { - self.space_id + fn id(&self) -> StoreId { + self.store_id } } diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index a1984f7af851..fff7afe68533 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -51,7 +51,7 @@ pub struct Options { default_value_t = 262144, value_name = "PAYLOAD_NCACHED_PAGES", help = "Maximum cached pages for the item stash. This is the low-level cache used by the linear - space that holds trie nodes and account objects." + store that holds trie nodes and account objects." )] pub payload_ncached_pages: usize, diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs index 0a128f78c7e6..cc984b8b4d65 100644 --- a/growth-ring/src/wal.rs +++ b/growth-ring/src/wal.rs @@ -190,7 +190,7 @@ struct WalState { #[async_trait(?Send)] pub trait WalFile { - /// Initialize the file space in [offset, offset + length) to zero. + /// Initialize the file store in [offset, offset + length) to zero. async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError>; /// Write data with offset. We assume all previous `allocate`/`truncate` invocations are visible /// if ordered earlier (should be guaranteed by most OS). Additionally, the write caused @@ -380,7 +380,7 @@ impl> WalFilePool { std::mem::replace(&mut *self.last_write.get(), std::mem::MaybeUninit::uninit()) .assume_init() }; - // pre-allocate the file space + // pre-allocate the file store let alloc = async move { last_write.await?; let mut last_h: Option< From 7f481f8f18802831c7495e349663ccd07a851d3b Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 26 Mar 2024 10:36:11 -0400 Subject: [PATCH 0527/1053] tweak descriptor method names (#614) --- firewood/src/shale/compact.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index d661333ef52f..31e89b589e87 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -300,10 +300,6 @@ impl From> for StoreInner { } impl StoreInner { - fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.meta_store, ptr, ChunkDescriptor::SERIALIZED_LEN) - } - fn get_data_ref( &self, ptr: DiskAddress, @@ -320,7 +316,11 @@ impl StoreInner { self.get_data_ref::(ptr, ChunkFooter::SERIALIZED_LEN) } - fn del_desc(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { + fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { + StoredView::ptr_to_obj(&self.meta_store, ptr, ChunkDescriptor::SERIALIZED_LEN) + } + + fn delete_descriptor(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { let desc_size = ChunkDescriptor::SERIALIZED_LEN; // TODO: subtracting two disk addresses is only used here, probably can rewrite this // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); @@ -343,7 +343,7 @@ impl StoreInner { Ok(()) } - fn new_desc(&mut self) -> Result { + fn new_descriptor_address(&mut self) -> Result { let addr = **self.header.meta_store_tail; #[allow(clippy::unwrap_used)] self.header @@ -379,7 +379,7 @@ impl StoreInner { h = offset; chunk_size += ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + pheader_chunk_size; - self.del_desc(pheader_desc_addr)?; + self.delete_descriptor(pheader_desc_addr)?; } } @@ -406,11 +406,11 @@ impl StoreInner { } chunk_size += ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + nheader_chunk_size; - self.del_desc(nheader_desc_addr)?; + self.delete_descriptor(nheader_desc_addr)?; } } - let desc_addr = self.new_desc()?; + let desc_addr = self.new_descriptor_address()?; { let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] @@ -468,7 +468,7 @@ impl StoreInner { #[allow(clippy::unwrap_used)] header.modify(|h| h.is_freed = false).unwrap(); } - self.del_desc(ptr)?; + self.delete_descriptor(ptr)?; true } else if chunk_size > length as usize + HEADER_SIZE + FOOTER_SIZE { // able to split @@ -495,7 +495,7 @@ impl StoreInner { let offset = desc_haddr + HEADER_SIZE + length as usize + FOOTER_SIZE; let rchunk_size = chunk_size - length as usize - FOOTER_SIZE - HEADER_SIZE; - let rdesc_addr = self.new_desc()?; + let rdesc_addr = self.new_descriptor_address()?; { let mut rdesc = self.get_descriptor(rdesc_addr)?; #[allow(clippy::unwrap_used)] @@ -525,7 +525,7 @@ impl StoreInner { .modify(|f| f.chunk_size = rchunk_size as u64) .unwrap(); } - self.del_desc(ptr)?; + self.delete_descriptor(ptr)?; true } else { false From e4ee2ccb31bcc41e45000a57193241b486d50613 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 27 Mar 2024 09:14:20 -0400 Subject: [PATCH 0528/1053] Improve `free` readability (#613) --- firewood/src/shale/compact.rs | 123 +++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 31e89b589e87..888117429d12 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -324,6 +324,8 @@ impl StoreInner { let desc_size = ChunkDescriptor::SERIALIZED_LEN; // TODO: subtracting two disk addresses is only used here, probably can rewrite this // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); + + // Move the last descriptor to the position of the deleted descriptor #[allow(clippy::unwrap_used)] self.header .meta_store_tail @@ -331,13 +333,16 @@ impl StoreInner { .unwrap(); if desc_addr != DiskAddress(**self.header.meta_store_tail) { - let desc_last = self.get_descriptor(*self.header.meta_store_tail.value)?; + let last_desc = self.get_descriptor(*self.header.meta_store_tail.value)?; + let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] - desc.modify(|r| *r = *desc_last).unwrap(); - let mut header = self.get_header(desc.haddr.into())?; + desc.modify(|r| *r = *last_desc).unwrap(); + + // `chunk_header` is associated with the deleted descriptor + let mut chunk_header = self.get_header(desc.haddr.into())?; #[allow(clippy::unwrap_used)] - header.modify(|h| h.desc_addr = desc_addr).unwrap(); + chunk_header.modify(|h| h.desc_addr = desc_addr).unwrap(); } Ok(()) @@ -355,58 +360,62 @@ impl StoreInner { } fn free(&mut self, addr: u64) -> Result<(), ShaleError> { - let regn_size = 1 << self.regn_nbit; + let region_size = 1 << self.regn_nbit; - let mut offset = addr - ChunkHeader::SERIALIZED_LEN; + let header_offset = addr - ChunkHeader::SERIALIZED_LEN; let header_chunk_size = { - let header = self.get_header(DiskAddress::from(offset as usize))?; + let header = self.get_header(DiskAddress::from(header_offset as usize))?; assert!(!header.is_freed); header.chunk_size }; - let mut h = offset; - let mut chunk_size = header_chunk_size; - - if offset & (regn_size - 1) > 0 { - // merge with lower data segment - offset -= ChunkFooter::SERIALIZED_LEN; - let (pheader_is_freed, pheader_chunk_size, pheader_desc_addr) = { - let pfooter = self.get_footer(DiskAddress::from(offset as usize))?; - offset -= pfooter.chunk_size + ChunkHeader::SERIALIZED_LEN; - let pheader = self.get_header(DiskAddress::from(offset as usize))?; - (pheader.is_freed, pheader.chunk_size, pheader.desc_addr) - }; - if pheader_is_freed { - h = offset; - chunk_size += - ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + pheader_chunk_size; - self.delete_descriptor(pheader_desc_addr)?; + let mut free_header_offset = header_offset; + let mut free_chunk_size = header_chunk_size; + + if header_offset & (region_size - 1) > 0 { + // TODO danlaine: document what this condition means. + // merge with previous chunk if it's freed. + let prev_footer_offset = header_offset - ChunkFooter::SERIALIZED_LEN; + let prev_footer = self.get_footer(DiskAddress::from(prev_footer_offset as usize))?; + + let prev_header_offset = + prev_footer_offset - prev_footer.chunk_size - ChunkHeader::SERIALIZED_LEN; + let prev_header = self.get_header(DiskAddress::from(prev_header_offset as usize))?; + + if prev_header.is_freed { + free_header_offset = prev_header_offset; + free_chunk_size += ChunkHeader::SERIALIZED_LEN + + ChunkFooter::SERIALIZED_LEN + + prev_header.chunk_size; + self.delete_descriptor(prev_header.desc_addr)?; } } - offset = addr + header_chunk_size; - let mut f = offset; + let footer_offset = addr + header_chunk_size; + let mut free_footer_offset = footer_offset; #[allow(clippy::unwrap_used)] - if offset + ChunkFooter::SERIALIZED_LEN < self.header.data_store_tail.unwrap().get() as u64 - && (regn_size - (offset & (regn_size - 1))) + if footer_offset + ChunkFooter::SERIALIZED_LEN + < self.header.data_store_tail.unwrap().get() as u64 + && (region_size - (footer_offset & (region_size - 1))) >= ChunkFooter::SERIALIZED_LEN + ChunkHeader::SERIALIZED_LEN { - // merge with higher data segment - offset += ChunkFooter::SERIALIZED_LEN; - let (nheader_is_freed, nheader_chunk_size, nheader_desc_addr) = { - let nheader = self.get_header(DiskAddress::from(offset as usize))?; - (nheader.is_freed, nheader.chunk_size, nheader.desc_addr) - }; - if nheader_is_freed { - offset += ChunkHeader::SERIALIZED_LEN + nheader_chunk_size; - f = offset; + // TODO danlaine: document what this condition means. + // merge with next chunk if it's freed. + let next_header_offset = footer_offset + ChunkFooter::SERIALIZED_LEN; + let next_header = self.get_header(DiskAddress::from(next_header_offset as usize))?; + if next_header.is_freed { + let next_footer_offset = + next_header_offset + ChunkHeader::SERIALIZED_LEN + next_header.chunk_size; + free_footer_offset = next_footer_offset; { - let nfooter = self.get_footer(DiskAddress::from(offset as usize))?; - assert!(nheader_chunk_size == nfooter.chunk_size); + let next_footer = + self.get_footer(DiskAddress::from(next_footer_offset as usize))?; + assert!(next_header.chunk_size == next_footer.chunk_size); } - chunk_size += - ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + nheader_chunk_size; - self.delete_descriptor(nheader_desc_addr)?; + free_chunk_size += ChunkHeader::SERIALIZED_LEN + + ChunkFooter::SERIALIZED_LEN + + next_header.chunk_size; + self.delete_descriptor(next_header.desc_addr)?; } } @@ -415,22 +424,26 @@ impl StoreInner { let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] desc.modify(|d| { - d.chunk_size = chunk_size; - d.haddr = h as usize; + d.chunk_size = free_chunk_size; + d.haddr = free_header_offset as usize; }) .unwrap(); } - let mut h = self.get_header(DiskAddress::from(h as usize))?; - let mut f = self.get_footer(DiskAddress::from(f as usize))?; + let mut free_header = self.get_header(DiskAddress::from(free_header_offset as usize))?; #[allow(clippy::unwrap_used)] - h.modify(|h| { - h.chunk_size = chunk_size; - h.is_freed = true; - h.desc_addr = desc_addr; - }) - .unwrap(); + free_header + .modify(|h| { + h.chunk_size = free_chunk_size; + h.is_freed = true; + h.desc_addr = desc_addr; + }) + .unwrap(); + + let mut free_footer = self.get_footer(DiskAddress::from(free_footer_offset as usize))?; #[allow(clippy::unwrap_used)] - f.modify(|f| f.chunk_size = chunk_size).unwrap(); + free_footer + .modify(|f| f.chunk_size = free_chunk_size) + .unwrap(); Ok(()) } @@ -548,7 +561,7 @@ impl StoreInner { } fn alloc_new(&mut self, length: u64) -> Result { - let regn_size = 1 << self.regn_nbit; + let region_size = 1 << self.regn_nbit; let total_length = ChunkHeader::SERIALIZED_LEN + length + ChunkFooter::SERIALIZED_LEN; let mut offset = *self.header.data_store_tail; #[allow(clippy::unwrap_used)] @@ -556,7 +569,7 @@ impl StoreInner { .data_store_tail .modify(|r| { // an item is always fully in one region - let rem = regn_size - (offset & (regn_size - 1)).get(); + let rem = region_size - (offset & (region_size - 1)).get(); if rem < total_length as usize { offset += rem; *r += rem; From 7704d878e4bd9ad5303da87aa381862a8dca2538 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 29 Mar 2024 14:59:21 -0400 Subject: [PATCH 0529/1053] rename ptr to addr (#619) --- firewood/benches/hashops.rs | 2 +- firewood/benches/shale-bench.rs | 2 +- firewood/src/db.rs | 4 +- firewood/src/merkle.rs | 66 ++++++++++++++++----------------- firewood/src/merkle/proof.rs | 8 ++-- firewood/src/merkle_util.rs | 2 +- firewood/src/shale/compact.rs | 60 +++++++++++++++--------------- firewood/src/shale/mod.rs | 14 +++---- 8 files changed, 79 insertions(+), 79 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bc37258470de..bf51543e4b45 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -95,7 +95,7 @@ fn bench_merkle(criterion: &mut Criterion) { let merkle_payload_header = DiskAddress::from(0); #[allow(clippy::unwrap_used)] - let merkle_payload_header_ref = StoredView::ptr_to_obj( + let merkle_payload_header_ref = StoredView::addr_to_obj( &InMemLinearStore::new(2 * ChunkHeader::SERIALIZED_LEN, 9), merkle_payload_header, ChunkHeader::SERIALIZED_LEN, diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs index fd4674c46829..93b2000240e3 100644 --- a/firewood/benches/shale-bench.rs +++ b/firewood/benches/shale-bench.rs @@ -82,7 +82,7 @@ fn serialize(m: &T) { let compact_header_obj: DiskAddress = DiskAddress::from(0x0); #[allow(clippy::unwrap_used)] let _: Obj = - StoredView::ptr_to_obj(m, compact_header_obj, ChunkHeader::SERIALIZED_LEN).unwrap(); + StoredView::addr_to_obj(m, compact_header_obj, ChunkHeader::SERIALIZED_LEN).unwrap(); } fn bench_cursors(c: &mut Criterion) { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6bbdc7273804..37bcc10de0b4 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -754,7 +754,7 @@ impl Db { header_offset: u64, ) -> Result, DbError> { let payload_header = DiskAddress::from(header_offset as usize); - StoredView::ptr_to_obj( + StoredView::addr_to_obj( meta_ref, payload_header, shale::compact::ChunkHeader::SERIALIZED_LEN, @@ -764,7 +764,7 @@ impl Db { fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { let db_header = DiskAddress::from(Db::PARAM_SIZE as usize); - StoredView::ptr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) + StoredView::addr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) } fn new_revision>( diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 2c8be8225e85..d4e406c9f143 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -55,9 +55,9 @@ pub enum MerkleError { macro_rules! write_node { ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { if let Err(_) = $r.write($modify) { - let ptr = $self.put_node($r.clone())?.as_ptr(); + let ptr = $self.put_node($r.clone())?.as_addr(); set_parent(ptr, $parents); - $deleted.push($r.as_ptr()); + $deleted.push($r.as_addr()); true } else { false @@ -191,7 +191,7 @@ impl Merkle { Node::max_branch_node_size(), ) .map_err(MerkleError::Shale) - .map(|node| node.as_ptr()) + .map(|node| node.as_addr()) } pub fn empty_root() -> &'static TrieHash { @@ -336,7 +336,7 @@ impl Merkle { let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); + let new_leaf = self.put_node(new_leaf)?.as_addr(); let mut children = [None; BranchNode::MAX_CHILDREN]; children[new_leaf_index as usize] = Some(new_leaf); @@ -350,11 +350,11 @@ impl Merkle { let new_branch = Node::from_branch(new_branch); - let new_branch = self.put_node(new_branch)?.as_ptr(); + let new_branch = self.put_node(new_branch)?.as_addr(); set_parent(new_branch, &mut parents); - deleted.push(node.as_ptr()); + deleted.push(node.as_addr()); } // old node is a child of the new node @@ -372,7 +372,7 @@ impl Merkle { node, Path(old_leaf_path.to_vec()), )? - .as_ptr(); + .as_addr(); let mut new_branch = BranchNode { partial_path: Path(new_branch_path), @@ -384,7 +384,7 @@ impl Merkle { new_branch.children[old_leaf_index as usize] = Some(old_leaf); let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_ptr(); + let node = self.put_node(node)?.as_addr(); set_parent(node, &mut parents); } @@ -409,11 +409,11 @@ impl Merkle { node, Path(old_leaf_path.to_vec()), )? - .as_ptr(); + .as_addr(); let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); + let new_leaf = self.put_node(new_leaf)?.as_addr(); let mut new_branch = BranchNode { partial_path: Path(new_branch_path), @@ -426,7 +426,7 @@ impl Merkle { new_branch.children[new_leaf_index as usize] = Some(new_leaf); let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_ptr(); + let node = self.put_node(node)?.as_addr(); set_parent(node, &mut parents); } @@ -447,7 +447,7 @@ impl Merkle { Path(key_nibbles.collect()), val, )))? - .as_ptr(); + .as_addr(); // set the current child to point to this leaf #[allow(clippy::indexing_slicing)] @@ -503,7 +503,7 @@ impl Merkle { val, )); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); + let new_leaf = self.put_node(new_leaf)?.as_addr(); #[allow(clippy::indexing_slicing)] node.write(|node| { @@ -532,7 +532,7 @@ impl Merkle { node, Path(old_branch_path.to_vec()), )? - .as_ptr(); + .as_addr(); let mut new_branch = BranchNode { partial_path: Path(new_branch_path), @@ -544,7 +544,7 @@ impl Merkle { new_branch.children[old_branch_index as usize] = Some(old_branch); let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_ptr(); + let node = self.put_node(node)?.as_addr(); set_parent(node, &mut parents); @@ -571,11 +571,11 @@ impl Merkle { node, Path(old_branch_path.to_vec()), )? - .as_ptr(); + .as_addr(); let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); - let new_leaf = self.put_node(new_leaf)?.as_ptr(); + let new_leaf = self.put_node(new_leaf)?.as_addr(); let mut new_branch = BranchNode { partial_path: Path(new_branch_path), @@ -588,7 +588,7 @@ impl Merkle { new_branch.children[new_leaf_index as usize] = Some(new_leaf); let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_ptr(); + let node = self.put_node(node)?.as_addr(); set_parent(node, &mut parents); @@ -640,7 +640,7 @@ impl Merkle { &mut deleted ); - node.as_ptr() + node.as_addr() }; if let Some((idx, more, ext, val)) = info { @@ -664,7 +664,7 @@ impl Merkle { value: Some(val), children_encoded: Default::default(), }))? - .as_ptr(); + .as_addr(); set_parent(branch, &mut parents); } @@ -727,9 +727,9 @@ impl Merkle { child.rehash(); })?; - set_parent(child.as_ptr(), &mut parents); + set_parent(child.as_addr(), &mut parents); - deleted.push(node.as_ptr()); + deleted.push(node.as_addr()); } else { node.write(|node| { node.as_branch_mut().value = None; @@ -744,7 +744,7 @@ impl Merkle { let value = Some(n.value.clone()); // TODO: handle unwrap better - deleted.push(node.as_ptr()); + deleted.push(node.as_addr()); let (mut parent, child_index) = parents.pop().expect("parents is never empty"); @@ -805,11 +805,11 @@ impl Merkle { } }; - let child = self.put_node(new_child)?.as_ptr(); + let child = self.put_node(new_child)?.as_addr(); set_parent(child, &mut parents); - deleted.push(parent.as_ptr()); + deleted.push(parent.as_addr()); } // branch nodes shouldn't have no children @@ -819,10 +819,10 @@ impl Merkle { value.clone(), )); - let leaf = self.put_node(leaf)?.as_ptr(); + let leaf = self.put_node(leaf)?.as_addr(); set_parent(leaf, &mut parents); - deleted.push(parent.as_ptr()); + deleted.push(parent.as_addr()); } _ => parent.write(|parent| parent.rehash())?, @@ -948,7 +948,7 @@ impl Merkle { break; }; - start_loop_callback(node_ref.as_ptr(), nib); + start_loop_callback(node_ref.as_addr(), nib); let next_ptr = match &node_ref.inner { #[allow(clippy::indexing_slicing)] @@ -1243,11 +1243,11 @@ impl Merkle { write_result: Result<(), ObjWriteSizeError>, ) -> Result, MerkleError> { if let Err(ObjWriteSizeError) = write_result { - let old_node_address = node.as_ptr(); + let old_node_address = node.as_addr(); node = self.put_node(node.into_inner())?; deleted.push(old_node_address); - set_parent(node.as_ptr(), parents); + set_parent(node.as_addr(), parents); } Ok(node) @@ -1424,7 +1424,7 @@ mod tests { .unwrap(), ) .unwrap(); - let compact_header = shale::StoredView::ptr_to_obj( + let compact_header = shale::StoredView::addr_to_obj( &dm, compact_header, shale::compact::ChunkHeader::SERIALIZED_LEN, @@ -2160,7 +2160,7 @@ mod tests { let root = merkle.get_node(root)?; let mut node_ref = merkle.put_node(node)?; - let addr = node_ref.as_ptr(); + let addr = node_ref.as_addr(); // make sure that doubling the path length will fail on a normal write let write_result = node_ref.write(|node| { @@ -2181,7 +2181,7 @@ mod tests { Path(new_path.clone()), )?; - assert_ne!(node.as_ptr(), addr); + assert_ne!(node.as_addr(), addr); assert_eq!(&to_delete[0], &addr); let (path, value) = match node.inner() { diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 03335f7af946..5b75c0d6fd3a 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -324,7 +324,7 @@ impl + Send> Proof { .inner_mut() .as_branch_mut() .expect("parent_node_ref must be a branch"); - node.chd_mut()[child_index] = Some(child_node.as_ptr()); + node.chd_mut()[child_index] = Some(child_node.as_addr()); })?; child_node @@ -539,7 +539,7 @@ where _ => (), }; - parent = u_ref.as_ptr(); + parent = u_ref.as_addr(); u_ref = merkle.get_node(left_node.expect("left_node none"))?; index += 1; } @@ -590,7 +590,7 @@ where return Ok(false); } - let p = u_ref.as_ptr(); + let p = u_ref.as_addr(); index += n.partial_path.len(); // Only one proof points to non-existent key. @@ -727,7 +727,7 @@ fn unset_node_ref, S: LinearStore, T: BinarySerde>( #[allow(clippy::unwrap_used)] let mut u_ref = merkle.get_node(node).map_err(|_| ProofError::NoSuchNode)?; - let p = u_ref.as_ptr(); + let p = u_ref.as_addr(); if index >= chunks.len() { return Err(ProofError::InvalidProof); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 36492e291d43..9473b1e39b1a 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -61,7 +61,7 @@ where ) .expect("write should succeed"); #[allow(clippy::unwrap_used)] - let compact_header = StoredView::ptr_to_obj( + let compact_header = StoredView::addr_to_obj( &dm, compact_header, shale::compact::ChunkHeader::SERIALIZED_LEN, diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 888117429d12..cc8094579c17 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -302,22 +302,22 @@ impl From> for StoreInner { impl StoreInner { fn get_data_ref( &self, - ptr: DiskAddress, + addr: DiskAddress, len_limit: u64, ) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.data_store, ptr, len_limit) + StoredView::addr_to_obj(&self.data_store, addr, len_limit) } - fn get_header(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, ChunkHeader::SERIALIZED_LEN) + fn get_header(&self, addr: DiskAddress) -> Result, ShaleError> { + self.get_data_ref::(addr, ChunkHeader::SERIALIZED_LEN) } - fn get_footer(&self, ptr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(ptr, ChunkFooter::SERIALIZED_LEN) + fn get_footer(&self, addr: DiskAddress) -> Result, ShaleError> { + self.get_data_ref::(addr, ChunkFooter::SERIALIZED_LEN) } - fn get_descriptor(&self, ptr: DiskAddress) -> Result, ShaleError> { - StoredView::ptr_to_obj(&self.meta_store, ptr, ChunkDescriptor::SERIALIZED_LEN) + fn get_descriptor(&self, addr: DiskAddress) -> Result, ShaleError> { + StoredView::addr_to_obj(&self.meta_store, addr, ChunkDescriptor::SERIALIZED_LEN) } fn delete_descriptor(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { @@ -464,12 +464,12 @@ impl StoreInner { old_alloc_addr = *self.header.base_addr; } - let mut ptr = old_alloc_addr; + let mut addr = old_alloc_addr; let mut res: Option = None; for _ in 0..self.alloc_max_walk { - assert!(ptr < tail); + assert!(addr < tail); let (chunk_size, desc_haddr) = { - let desc = self.get_descriptor(ptr)?; + let desc = self.get_descriptor(addr)?; (desc.chunk_size as usize, desc.haddr) }; let exit = if chunk_size == length as usize { @@ -481,7 +481,7 @@ impl StoreInner { #[allow(clippy::unwrap_used)] header.modify(|h| h.is_freed = false).unwrap(); } - self.delete_descriptor(ptr)?; + self.delete_descriptor(addr)?; true } else if chunk_size > length as usize + HEADER_SIZE + FOOTER_SIZE { // able to split @@ -538,22 +538,22 @@ impl StoreInner { .modify(|f| f.chunk_size = rchunk_size as u64) .unwrap(); } - self.delete_descriptor(ptr)?; + self.delete_descriptor(addr)?; true } else { false }; #[allow(clippy::unwrap_used)] if exit { - self.header.alloc_addr.modify(|r| *r = ptr).unwrap(); + self.header.alloc_addr.modify(|r| *r = addr).unwrap(); res = Some((desc_haddr + HEADER_SIZE) as u64); break; } - ptr += DESCRIPTOR_SIZE; - if ptr >= tail { - ptr = *self.header.base_addr; + addr += DESCRIPTOR_SIZE; + if addr >= tail { + addr = *self.header.base_addr; } - if ptr == old_alloc_addr { + if addr == old_alloc_addr { break; } } @@ -677,15 +677,15 @@ impl Store { } #[allow(clippy::unwrap_used)] - pub(crate) fn free_item(&mut self, ptr: DiskAddress) -> Result<(), ShaleError> { + pub(crate) fn free_item(&mut self, addr: DiskAddress) -> Result<(), ShaleError> { let mut inner = self.inner.write().unwrap(); - self.obj_cache.pop(ptr); + self.obj_cache.pop(addr); #[allow(clippy::unwrap_used)] - inner.free(ptr.unwrap().get() as u64) + inner.free(addr.unwrap().get() as u64) } - pub(crate) fn get_item(&self, ptr: DiskAddress) -> Result, ShaleError> { - let obj = self.obj_cache.get(ptr)?; + pub(crate) fn get_item(&self, addr: DiskAddress) -> Result, ShaleError> { + let obj = self.obj_cache.get(addr)?; #[allow(clippy::unwrap_used)] let inner = self.inner.read().unwrap(); @@ -696,17 +696,17 @@ impl Store { } #[allow(clippy::unwrap_used)] - if ptr < DiskAddress::from(StoreHeader::SERIALIZED_LEN as usize) { + if addr < DiskAddress::from(StoreHeader::SERIALIZED_LEN as usize) { return Err(ShaleError::InvalidAddressLength { expected: StoreHeader::SERIALIZED_LEN, - found: ptr.0.map(|inner| inner.get()).unwrap_or_default() as u64, + found: addr.0.map(|inner| inner.get()).unwrap_or_default() as u64, }); } let chunk_size = inner - .get_header(ptr - ChunkHeader::SERIALIZED_LEN as usize)? + .get_header(addr - ChunkHeader::SERIALIZED_LEN as usize)? .chunk_size; - let obj = self.obj_cache.put(inner.get_data_ref(ptr, chunk_size)?); + let obj = self.obj_cache.put(inner.get_data_ref(addr, chunk_size)?); let cache = &self.obj_cache; Ok(ObjRef::new(obj, cache)) @@ -790,7 +790,7 @@ mod tests { ) .unwrap(); let compact_header = - StoredView::ptr_to_obj(&dm, compact_header, ChunkHeader::SERIALIZED_LEN).unwrap(); + StoredView::addr_to_obj(&dm, compact_header, ChunkHeader::SERIALIZED_LEN).unwrap(); let mem_meta = dm; let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); @@ -801,8 +801,8 @@ mod tests { let data = b"hello world"; let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); let obj_ref = store.put_item(Hash(hash), 0).unwrap(); - assert_eq!(obj_ref.as_ptr(), DiskAddress::from(4113)); - // create hash ptr from address and attempt to read dirty write. + assert_eq!(obj_ref.as_addr(), DiskAddress::from(4113)); + // create hash addr from address and attempt to read dirty write. let hash_ref = store.get_item(DiskAddress::from(4113)).unwrap(); // read before flush results in zeroed hash assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index a290fe415ac2..7db6b4230ce1 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -89,7 +89,7 @@ pub trait LinearStore: Debug + Send + Sync { } /// A wrapper of `StoredView` to enable writes. The direct construction (by [Obj::from_stored_view] -/// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. +/// or [StoredView::addr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. /// headers/metadata at bootstrap) stored at a given [DiskAddress]. #[derive(Debug)] pub struct Obj { @@ -100,7 +100,7 @@ pub struct Obj { impl Obj { #[inline(always)] - pub const fn as_ptr(&self) -> DiskAddress { + pub const fn as_addr(&self) -> DiskAddress { DiskAddress(NonZeroUsize::new(self.value.get_offset())) } @@ -187,13 +187,13 @@ impl<'a, T: Storable + Debug> ObjRef<'a, T> { pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { self.inner.modify(modify)?; - self.cache.lock().dirty.insert(self.inner.as_ptr()); + self.cache.lock().dirty.insert(self.inner.as_addr()); Ok(()) } pub fn into_ptr(self) -> DiskAddress { - self.deref().as_ptr() + self.deref().as_addr() } } @@ -227,7 +227,7 @@ impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { impl<'a, T: Storable> Drop for ObjRef<'a, T> { fn drop(&mut self) { - let ptr = self.inner.as_ptr(); + let ptr = self.inner.as_addr(); let mut cache = self.cache.lock(); match cache.pinned.remove(&ptr) { Some(true) => { @@ -357,7 +357,7 @@ impl StoredView { } #[inline(always)] - pub fn ptr_to_obj( + pub fn addr_to_obj( store: &U, ptr: DiskAddress, len_limit: u64, @@ -483,7 +483,7 @@ impl ObjCache { #[inline(always)] fn put(&self, inner: Obj) -> Obj { - let ptr = inner.as_ptr(); + let ptr = inner.as_addr(); self.lock().pinned.insert(ptr, false); inner } From e1579674a603b6b803b1caa66e34e0f27f36ae22 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 29 Mar 2024 15:05:28 -0400 Subject: [PATCH 0530/1053] Improve `alloc_from_freed` readability (#615) --- firewood/src/shale/compact.rs | 64 ++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index cc8094579c17..6f63d79f51cb 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -560,40 +560,58 @@ impl StoreInner { Ok(res) } - fn alloc_new(&mut self, length: u64) -> Result { + fn alloc_new(&mut self, alloc_size: u64) -> Result { let region_size = 1 << self.regn_nbit; - let total_length = ChunkHeader::SERIALIZED_LEN + length + ChunkFooter::SERIALIZED_LEN; - let mut offset = *self.header.data_store_tail; + let new_chunk_size = ChunkHeader::SERIALIZED_LEN + alloc_size + ChunkFooter::SERIALIZED_LEN; + let mut free_chunk_header_offset = *self.header.data_store_tail; + #[allow(clippy::unwrap_used)] self.header .data_store_tail - .modify(|r| { + .modify(|data_store_tail| { // an item is always fully in one region - let rem = region_size - (offset & (region_size - 1)).get(); - if rem < total_length as usize { - offset += rem; - *r += rem; + // TODO danlaine: we should document the above better. Where is this guaranteed? + let remaining_region_size = + region_size - (free_chunk_header_offset & (region_size - 1)).get(); + + if remaining_region_size < new_chunk_size as usize { + // There is not enough space in the current region for this alloc. + // Move to the next region. + free_chunk_header_offset += remaining_region_size; + *data_store_tail += remaining_region_size; } - *r += total_length as usize + + *data_store_tail += new_chunk_size as usize }) .unwrap(); - let mut h = self.get_header(offset)?; - let mut f = - self.get_footer(offset + ChunkHeader::SERIALIZED_LEN as usize + length as usize)?; + + let mut free_chunk_header = self.get_header(free_chunk_header_offset)?; + #[allow(clippy::unwrap_used)] - h.modify(|h| { - h.chunk_size = length; - h.is_freed = false; - h.desc_addr = DiskAddress::null(); - }) - .unwrap(); + free_chunk_header + .modify(|h| { + h.chunk_size = alloc_size; + h.is_freed = false; + h.desc_addr = DiskAddress::null(); + }) + .unwrap(); + + let mut free_chunk_footer = self.get_footer( + free_chunk_header_offset + ChunkHeader::SERIALIZED_LEN as usize + alloc_size as usize, + )?; + #[allow(clippy::unwrap_used)] - f.modify(|f| f.chunk_size = length).unwrap(); + free_chunk_footer + .modify(|f| f.chunk_size = alloc_size) + .unwrap(); + #[allow(clippy::unwrap_used)] - Ok((offset + ChunkHeader::SERIALIZED_LEN as usize) - .0 - .unwrap() - .get() as u64) + Ok( + (free_chunk_header_offset + ChunkHeader::SERIALIZED_LEN as usize) + .0 + .unwrap() + .get() as u64, + ) } fn alloc(&mut self, length: u64) -> Result { From fa6155ef95dc02bd34d4546797ab112cfe90c46a Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 29 Mar 2024 15:23:20 -0400 Subject: [PATCH 0531/1053] clean up `delete_descriptor` (#616) --- firewood/src/shale/compact.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 6f63d79f51cb..458089cb8ee3 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -321,15 +321,13 @@ impl StoreInner { } fn delete_descriptor(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { - let desc_size = ChunkDescriptor::SERIALIZED_LEN; // TODO: subtracting two disk addresses is only used here, probably can rewrite this - // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % desc_size == 0); - + // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % ChunkDescriptor::SERIALIZED_LEN == 0); // Move the last descriptor to the position of the deleted descriptor #[allow(clippy::unwrap_used)] self.header .meta_store_tail - .modify(|r| *r -= desc_size as usize) + .modify(|r| *r -= ChunkDescriptor::SERIALIZED_LEN as usize) .unwrap(); if desc_addr != DiskAddress(**self.header.meta_store_tail) { From 493361381785b24b0d16c73b6666c61ca751f940 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 2 Apr 2024 19:10:57 -0400 Subject: [PATCH 0532/1053] rename root to sentinel where needed (#622) --- firewood/benches/hashops.rs | 8 +- firewood/src/db.rs | 40 ++--- firewood/src/db/proposal.rs | 4 +- firewood/src/merkle.rs | 271 ++++++++++++++++++---------------- firewood/src/merkle/proof.rs | 6 +- firewood/src/merkle/stream.rs | 236 ++++++++++++++++------------- firewood/src/merkle_util.rs | 25 ++-- 7 files changed, 316 insertions(+), 274 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index bf51543e4b45..4acccb885edf 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -115,7 +115,7 @@ fn bench_merkle(criterion: &mut Criterion) { let merkle = MerkleWithEncoder::new(store); #[allow(clippy::unwrap_used)] - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec> = repeat_with(|| { (&mut rng) @@ -126,12 +126,12 @@ fn bench_merkle(criterion: &mut Criterion) { .take(N) .collect(); - (merkle, root, keys) + (merkle, sentinel_addr, keys) }, #[allow(clippy::unwrap_used)] - |(mut merkle, root, keys)| { + |(mut merkle, sentinel_addr, keys)| { keys.into_iter() - .for_each(|key| merkle.insert(key, vec![b'v'], root).unwrap()) + .for_each(|key| merkle.insert(key, vec![b'v'], sentinel_addr).unwrap()) }, BatchSize::SmallInput, ); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 37bcc10de0b4..04d58d4f32a3 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -191,7 +191,7 @@ fn get_sub_universe_from_empty_delta( #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] struct DbHeader { - kv_root: DiskAddress, + sentinel_addr: DiskAddress, } impl DbHeader { @@ -199,7 +199,7 @@ impl DbHeader { pub const fn new_empty() -> Self { Self { - kv_root: DiskAddress::null(), + sentinel_addr: DiskAddress::null(), } } } @@ -216,7 +216,7 @@ impl Storable for DbHeader { let root_bytes = root_bytes.as_slice(); Ok(Self { - kv_root: root_bytes + sentinel_addr: root_bytes .try_into() .expect("Self::MSIZE == DiskAddress:MSIZE"), }) @@ -228,7 +228,7 @@ impl Storable for DbHeader { fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { let mut cur = Cursor::new(to); - cur.write_all(&self.kv_root.to_le_bytes())?; + cur.write_all(&self.sentinel_addr.to_le_bytes())?; Ok(()) } } @@ -290,13 +290,13 @@ impl api::DbView for DbRev { async fn root_hash(&self) -> Result { self.merkle - .root_hash(self.header.kv_root) + .root_hash(self.header.sentinel_addr) .map(|h| *h) .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } async fn val(&self, key: K) -> Result>, api::Error> { - let obj_ref = self.merkle.get(key, self.header.kv_root); + let obj_ref = self.merkle.get(key, self.header.sentinel_addr); match obj_ref { Err(e) => Err(api::Error::IO(std::io::Error::new(ErrorKind::Other, e))), Ok(obj) => Ok(obj.map(|inner| inner.deref().to_owned())), @@ -308,7 +308,7 @@ impl api::DbView for DbRev { key: K, ) -> Result>>, api::Error> { self.merkle - .prove(key, self.header.kv_root) + .prove(key, self.header.sentinel_addr) .map(Some) .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) } @@ -320,7 +320,7 @@ impl api::DbView for DbRev { limit: Option, ) -> Result, Vec>>, api::Error> { self.merkle - .range_proof(self.header.kv_root, first_key, last_key, limit) + .range_proof(self.header.sentinel_addr, first_key, last_key, limit) .await .map_err(|e| api::Error::InternalError(Box::new(e))) } @@ -330,34 +330,34 @@ impl api::DbView for DbRev { first_key: Option, ) -> Result, api::Error> { Ok(match first_key { - None => self.merkle.key_value_iter(self.header.kv_root), + None => self.merkle.key_value_iter(self.header.sentinel_addr), Some(key) => self .merkle - .key_value_iter_from_key(self.header.kv_root, key.as_ref().into()), + .key_value_iter_from_key(self.header.sentinel_addr, key.as_ref().into()), }) } } impl DbRev { pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { - self.merkle.key_value_iter(self.header.kv_root) + self.merkle.key_value_iter(self.header.sentinel_addr) } pub fn stream_from(&self, start_key: Key) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { self.merkle - .key_value_iter_from_key(self.header.kv_root, start_key) + .key_value_iter_from_key(self.header.sentinel_addr, start_key) } /// Get root hash of the generic key-value storage. pub fn kv_root_hash(&self) -> Result { self.merkle - .root_hash(self.header.kv_root) + .root_hash(self.header.sentinel_addr) .map_err(DbError::Merkle) } /// Get a value associated with a key. pub fn kv_get>(&self, key: K) -> Option> { - let obj_ref = self.merkle.get(key, self.header.kv_root); + let obj_ref = self.merkle.get(key, self.header.sentinel_addr); match obj_ref { Err(_) => None, Ok(obj) => obj.map(|o| o.to_vec()), @@ -367,12 +367,12 @@ impl DbRev { /// Dump the Trie of the generic key-value storage. pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { self.merkle - .dump(self.header.kv_root, w) + .dump(self.header.sentinel_addr, w) .map_err(DbError::Merkle) } pub fn prove>(&self, key: K) -> Result>, MerkleError> { - self.merkle.prove::(key, self.header.kv_root) + self.merkle.prove::(key, self.header.sentinel_addr) } /// Verifies a range proof is valid for a set of keys. @@ -798,14 +798,14 @@ impl Db { let merkle = Merkle::new(merkle_store); - if db_header_ref.kv_root.is_null() { + if db_header_ref.sentinel_addr.is_null() { let mut err = Ok(()); // create the sentinel node #[allow(clippy::unwrap_used)] db_header_ref .modify(|r| { err = (|| { - r.kv_root = merkle.init_root()?; + r.sentinel_addr = merkle.init_sentinel()?; Ok(()) })(); }) @@ -838,14 +838,14 @@ impl Db { BatchOp::Put { key, value } => { let (header, merkle) = rev.borrow_split(); merkle - .insert(key, value.as_ref().to_vec(), header.kv_root) + .insert(key, value.as_ref().to_vec(), header.sentinel_addr) .map_err(DbError::Merkle)?; Ok(()) } BatchOp::Delete { key } => { let (header, merkle) = rev.borrow_split(); merkle - .remove(key, header.kv_root) + .remove(key, header.sentinel_addr) .map_err(DbError::Merkle)?; Ok(()) } diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs index 5eba71df71c6..91703ad21ba7 100644 --- a/firewood/src/db/proposal.rs +++ b/firewood/src/db/proposal.rs @@ -93,14 +93,14 @@ impl Proposal { BatchOp::Put { key, value } => { let (header, merkle) = rev.borrow_split(); merkle - .insert(key, value.as_ref().to_vec(), header.kv_root) + .insert(key, value.as_ref().to_vec(), header.sentinel_addr) .map_err(DbError::Merkle)?; Ok(()) } BatchOp::Delete { key } => { let (header, merkle) = rev.borrow_split(); merkle - .remove(key, header.kv_root) + .remove(key, header.sentinel_addr) .map_err(DbError::Merkle)?; Ok(()) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d4e406c9f143..00bec2d6a2e4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -179,7 +179,8 @@ where } impl Merkle { - pub fn init_root(&self) -> Result { + /// Creates the sentinel node, puts it into the store, and returns its address. + pub fn init_sentinel(&self) -> Result { self.store .put_item( Node::from_branch(BranchNode { @@ -207,9 +208,9 @@ impl Merkle { }) } - pub fn root_hash(&self, sentinel: DiskAddress) -> Result { + pub fn root_hash(&self, sentinel_addr: DiskAddress) -> Result { let root = self - .get_node(sentinel)? + .get_node(sentinel_addr)? .inner .as_branch() .ok_or(MerkleError::NotBranchNode)? @@ -252,11 +253,11 @@ impl Merkle { Ok(()) } - pub fn dump(&self, root: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { - if root.is_null() { + pub fn dump(&self, sentinel_addr: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { + if sentinel_addr.is_null() { write!(w, "")?; } else { - self.dump_(root, w)?; + self.dump_(sentinel_addr, w)?; }; Ok(()) } @@ -265,9 +266,9 @@ impl Merkle { &mut self, key: K, val: Vec, - root: DiskAddress, + sentinel_addr: DiskAddress, ) -> Result<(), MerkleError> { - let (parents, deleted) = self.insert_and_return_updates(key, val, root)?; + let (parents, deleted) = self.insert_and_return_updates(key, val, sentinel_addr)?; for mut r in parents { r.write(|u| u.rehash())?; @@ -284,7 +285,7 @@ impl Merkle { &self, key: K, val: Vec, - root: DiskAddress, + sentinel_addr: DiskAddress, ) -> Result<(impl Iterator, Vec), MerkleError> { // as we split a node, we need to track deleted nodes and parents let mut deleted = Vec::new(); @@ -295,7 +296,7 @@ impl Merkle { // and always only has one child let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); - let mut node = self.get_node(root)?; + let mut node = self.get_node(sentinel_addr)?; // walk down the merkle tree starting from next_node, currently the root // return None if the value is inserted @@ -676,9 +677,9 @@ impl Merkle { pub fn remove>( &mut self, key: K, - root: DiskAddress, + sentinel_addr: DiskAddress, ) -> Result>, MerkleError> { - if root.is_null() { + if sentinel_addr.is_null() { return Ok(None); } @@ -686,7 +687,7 @@ impl Merkle { let value = { let (node, mut parents) = - self.get_node_and_parents_by_key(self.get_node(root)?, key)?; + self.get_node_and_parents_by_key(self.get_node(sentinel_addr)?, key)?; let Some(mut node) = node else { return Ok(None); @@ -864,12 +865,12 @@ impl Merkle { Ok(()) } - pub fn remove_tree(&mut self, root: DiskAddress) -> Result<(), MerkleError> { + pub fn remove_tree(&mut self, sentinel_addr: DiskAddress) -> Result<(), MerkleError> { let mut deleted = Vec::new(); - if root.is_null() { + if sentinel_addr.is_null() { return Ok(()); } - self.remove_tree_(root, &mut deleted)?; + self.remove_tree_(sentinel_addr, &mut deleted)?; for ptr in deleted.into_iter() { self.free_node(ptr)?; } @@ -1023,14 +1024,14 @@ impl Merkle { pub fn get_mut>( &mut self, key: K, - root: DiskAddress, + sentinel_addr: DiskAddress, ) -> Result>, MerkleError> { - if root.is_null() { + if sentinel_addr.is_null() { return Ok(None); } let (ptr, parents) = { - let root_node = self.get_node(root)?; + let root_node = self.get_node(sentinel_addr)?; let (node_ref, parents) = self.get_node_and_parent_addresses_by_key(root_node, key)?; (node_ref.map(|n| n.into_ptr()), parents) @@ -1046,16 +1047,20 @@ impl Merkle { /// If the trie does not contain a value for key, the returned proof contains /// all nodes of the longest existing prefix of the key, ending with the node /// that proves the absence of the key (at least the root node). - pub fn prove(&self, key: K, root: DiskAddress) -> Result>, MerkleError> + pub fn prove( + &self, + key: K, + sentinel_addr: DiskAddress, + ) -> Result>, MerkleError> where K: AsRef<[u8]>, { let mut proofs = HashMap::new(); - if root.is_null() { + if sentinel_addr.is_null() { return Ok(Proof(proofs)); } - let sentinel_node = self.get_node(root)?; + let sentinel_node = self.get_node(sentinel_addr)?; let path_iter = self.path_iter(sentinel_node, key.as_ref()); @@ -1075,13 +1080,13 @@ impl Merkle { pub fn get>( &self, key: K, - root: DiskAddress, + sentinel_addr: DiskAddress, ) -> Result, MerkleError> { - if root.is_null() { + if sentinel_addr.is_null() { return Ok(None); } - let root_node = self.get_node(root)?; + let root_node = self.get_node(sentinel_addr)?; let node_ref = self.get_node_by_key(root_node, key)?; Ok(node_ref.map(Ref)) @@ -1099,21 +1104,24 @@ impl Merkle { PathIterator::new(self, sentinel_node, key) } - pub(crate) fn key_value_iter(&self, root: DiskAddress) -> MerkleKeyValueStream<'_, S, T> { - MerkleKeyValueStream::new(self, root) + pub(crate) fn key_value_iter( + &self, + sentinel_addr: DiskAddress, + ) -> MerkleKeyValueStream<'_, S, T> { + MerkleKeyValueStream::new(self, sentinel_addr) } pub(crate) fn key_value_iter_from_key( &self, - root: DiskAddress, + sentinel_addr: DiskAddress, key: Key, ) -> MerkleKeyValueStream<'_, S, T> { - MerkleKeyValueStream::from_key(self, root, key) + MerkleKeyValueStream::from_key(self, sentinel_addr, key) } pub(super) async fn range_proof( &self, - root: DiskAddress, + sentinel_addr: DiskAddress, first_key: Option, last_key: Option, limit: Option, @@ -1134,10 +1142,9 @@ impl Merkle { let mut stream = match first_key { // TODO: fix the call-site to force the caller to do the allocation - Some(key) => { - self.key_value_iter_from_key(root, key.as_ref().to_vec().into_boxed_slice()) - } - None => self.key_value_iter(root), + Some(key) => self + .key_value_iter_from_key(sentinel_addr, key.as_ref().to_vec().into_boxed_slice()), + None => self.key_value_iter(sentinel_addr), }; // fetch the first key from the stream @@ -1151,7 +1158,7 @@ impl Merkle { }; let first_key_proof = self - .prove(&first_key, root) + .prove(&first_key, sentinel_addr) .map_err(|e| api::Error::InternalError(Box::new(e)))?; let limit = limit.map(|old_limit| old_limit - 1); @@ -1192,7 +1199,7 @@ impl Merkle { })) } Some((last_key, _)) => self - .prove(last_key, root) + .prove(last_key, sentinel_addr) .map_err(|e| api::Error::InternalError(Box::new(e)))?, }; @@ -1542,11 +1549,11 @@ mod tests { let val = b"world"; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } @@ -1554,16 +1561,16 @@ mod tests { #[test] fn insert_and_retrieve_multiple() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { let key = vec![key_val]; let val = vec![key_val]; - merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - let fetched_val = merkle.get(&key, root).unwrap(); + let fetched_val = merkle.get(&key, sentinel_addr).unwrap(); // make sure the value was inserted assert_eq!(fetched_val.as_deref(), val.as_slice().into()); @@ -1574,7 +1581,7 @@ mod tests { let key = vec![key_val]; let val = vec![key_val]; - let fetched_val = merkle.get(&key, root).unwrap(); + let fetched_val = merkle.get(&key, sentinel_addr).unwrap(); assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } @@ -1617,18 +1624,18 @@ mod tests { ]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); for (key, val) in &key_val { - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } for (key, val) in key_val { - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } @@ -1640,35 +1647,35 @@ mod tests { let val = b"world"; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(key, root).unwrap().as_deref(), + merkle.get(key, sentinel_addr).unwrap().as_deref(), val.as_slice().into() ); - let removed_val = merkle.remove(key, root).unwrap(); + let removed_val = merkle.remove(key, sentinel_addr).unwrap(); assert_eq!(removed_val.as_deref(), val.as_slice().into()); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); assert!(fetched_val.is_none()); } #[test] fn remove_many() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { let key = &[key_val]; let val = &[key_val]; - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); // make sure the value was inserted assert_eq!(fetched_val.as_deref(), val.as_slice().into()); @@ -1679,13 +1686,13 @@ mod tests { let key = &[key_val]; let val = &[key_val]; - let Ok(removed_val) = merkle.remove(key, root) else { + let Ok(removed_val) = merkle.remove(key, sentinel_addr) else { panic!("({key_val}, {key_val}) missing"); }; assert_eq!(removed_val.as_deref(), val.as_slice().into()); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); assert!(fetched_val.is_none()); } } @@ -1693,9 +1700,9 @@ mod tests { #[test] fn get_empty_proof() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - let proof = merkle.prove(b"any-key", root).unwrap(); + let proof = merkle.prove(b"any-key", sentinel_addr).unwrap(); assert!(proof.0.is_empty()); } @@ -1703,10 +1710,10 @@ mod tests { #[tokio::test] async fn empty_range_proof() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); assert!(merkle - .range_proof::<&[u8]>(root, None, None, None) + .range_proof::<&[u8]>(sentinel_addr, None, None, None) .await .unwrap() .is_none()); @@ -1715,12 +1722,12 @@ mod tests { #[tokio::test] async fn range_proof_invalid_bounds() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let start_key = &[0x01]; let end_key = &[0x00]; match merkle - .range_proof::<&[u8]>(root, Some(start_key), Some(end_key), Some(1)) + .range_proof::<&[u8]>(sentinel_addr, Some(start_key), Some(end_key), Some(1)) .await { Err(api::Error::InvalidRange { @@ -1735,25 +1742,25 @@ mod tests { #[tokio::test] async fn full_range_proof() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { let key = &[key_val]; let val = &[key_val]; - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); } merkle.flush_dirty(); let rangeproof = merkle - .range_proof::<&[u8]>(root, None, None, None) + .range_proof::<&[u8]>(sentinel_addr, None, None, None) .await .unwrap() .unwrap(); assert_eq!(rangeproof.middle.len(), u8::MAX as usize + 1); assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - let left_proof = merkle.prove([u8::MIN], root).unwrap(); - let right_proof = merkle.prove([u8::MAX], root).unwrap(); + let left_proof = merkle.prove([u8::MIN], sentinel_addr).unwrap(); + let right_proof = merkle.prove([u8::MAX], sentinel_addr).unwrap(); assert_eq!(rangeproof.first_key_proof.0, left_proof.0); assert_eq!(rangeproof.last_key_proof.0, right_proof.0); } @@ -1763,18 +1770,18 @@ mod tests { const RANDOM_KEY: u8 = 42; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // insert values for key_val in u8::MIN..=u8::MAX { let key = &[key_val]; let val = &[key_val]; - merkle.insert(key, val.to_vec(), root).unwrap(); + merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); } merkle.flush_dirty(); let rangeproof = merkle - .range_proof(root, Some([RANDOM_KEY]), None, Some(1)) + .range_proof(sentinel_addr, Some([RANDOM_KEY]), None, Some(1)) .await .unwrap() .unwrap(); @@ -1785,21 +1792,21 @@ mod tests { #[test] fn shared_path_proof() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let key1 = b"key1"; let value1 = b"1"; - merkle.insert(key1, value1.to_vec(), root).unwrap(); + merkle.insert(key1, value1.to_vec(), sentinel_addr).unwrap(); let key2 = b"key2"; let value2 = b"2"; - merkle.insert(key2, value2.to_vec(), root).unwrap(); + merkle.insert(key2, value2.to_vec(), sentinel_addr).unwrap(); - let root_hash = merkle.root_hash(root).unwrap(); + let root_hash = merkle.root_hash(sentinel_addr).unwrap(); let verified = { let key = key1; - let proof = merkle.prove(key, root).unwrap(); + let proof = merkle.prove(key, sentinel_addr).unwrap(); proof.verify(key, root_hash.0).unwrap() }; @@ -1807,7 +1814,7 @@ mod tests { let verified = { let key = key2; - let proof = merkle.prove(key, root).unwrap(); + let proof = merkle.prove(key, sentinel_addr).unwrap(); proof.verify(key, root_hash.0).unwrap() }; @@ -1838,20 +1845,20 @@ mod tests { ]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); for (key, val) in &pairs { let val = val.to_vec(); - merkle.insert(key, val.clone(), root).unwrap(); + merkle.insert(key, val.clone(), sentinel_addr).unwrap(); - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); // make sure the value was inserted assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } for (key, val) in pairs { - let fetched_val = merkle.get(key, root).unwrap(); + let fetched_val = merkle.get(key, sentinel_addr).unwrap(); // make sure the value was inserted assert_eq!(fetched_val.as_deref(), val.into()); @@ -1865,19 +1872,21 @@ mod tests { let overwrite = vec![2]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); - merkle.insert(&key, overwrite.clone(), root).unwrap(); + merkle + .insert(&key, overwrite.clone(), sentinel_addr) + .unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(overwrite.as_slice()) ); } @@ -1890,18 +1899,18 @@ mod tests { let val_2 = vec![2]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); - merkle.insert(&key_2, val_2.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); } @@ -1914,18 +1923,18 @@ mod tests { let val_2 = vec![2]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); - merkle.insert(&key_2, val_2.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); } @@ -1940,24 +1949,24 @@ mod tests { let val_3 = vec![3]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); - merkle.insert(&key_2, val_2.clone(), root).unwrap(); - merkle.insert(&key_3, val_3.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_3, val_3.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); assert_eq!( - merkle.get(&key_3, root).unwrap().as_deref(), + merkle.get(&key_3, sentinel_addr).unwrap().as_deref(), Some(val_3.as_slice()) ); } @@ -1972,24 +1981,24 @@ mod tests { let val_3 = vec![3]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); - merkle.insert(&key_2, val_2.clone(), root).unwrap(); - merkle.insert(&key_3, val_3.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_3, val_3.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); assert_eq!( - merkle.get(&key_3, root).unwrap().as_deref(), + merkle.get(&key_3, sentinel_addr).unwrap().as_deref(), Some(val_3.as_slice()) ); } @@ -2004,30 +2013,32 @@ mod tests { let overwrite = vec![3]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(&key, val.clone(), root).unwrap(); - merkle.insert(&key_2, val_2.clone(), root).unwrap(); + merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(val.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); - merkle.insert(&key, overwrite.clone(), root).unwrap(); + merkle + .insert(&key, overwrite.clone(), sentinel_addr) + .unwrap(); assert_eq!( - merkle.get(&key, root).unwrap().as_deref(), + merkle.get(&key, sentinel_addr).unwrap().as_deref(), Some(overwrite.as_slice()) ); assert_eq!( - merkle.get(&key_2, root).unwrap().as_deref(), + merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), Some(val_2.as_slice()) ); } @@ -2035,14 +2046,14 @@ mod tests { #[test] fn single_key_proof_with_one_node() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let key = b"key"; let value = b"value"; - merkle.insert(key, value.to_vec(), root).unwrap(); - let root_hash = merkle.root_hash(root).unwrap(); + merkle.insert(key, value.to_vec(), sentinel_addr).unwrap(); + let root_hash = merkle.root_hash(sentinel_addr).unwrap(); - let proof = merkle.prove(key, root).unwrap(); + let proof = merkle.prove(key, sentinel_addr).unwrap(); let verified = proof.verify(key, root_hash.0).unwrap(); @@ -2052,18 +2063,18 @@ mod tests { #[test] fn two_key_proof_without_shared_path() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let key1 = &[0x00]; let key2 = &[0xff]; - merkle.insert(key1, key1.to_vec(), root).unwrap(); - merkle.insert(key2, key2.to_vec(), root).unwrap(); + merkle.insert(key1, key1.to_vec(), sentinel_addr).unwrap(); + merkle.insert(key2, key2.to_vec(), sentinel_addr).unwrap(); - let root_hash = merkle.root_hash(root).unwrap(); + let root_hash = merkle.root_hash(sentinel_addr).unwrap(); let verified = { - let proof = merkle.prove(key1, root).unwrap(); + let proof = merkle.prove(key1, sentinel_addr).unwrap(); proof.verify(key1, root_hash.0).unwrap() }; @@ -2156,8 +2167,8 @@ mod tests { new_value: Vec, ) -> Result<(), MerkleError> { let merkle = create_test_merkle(); - let root = merkle.init_root()?; - let root = merkle.get_node(root)?; + let sentinel_addr = merkle.init_sentinel()?; + let sentinel = merkle.get_node(sentinel_addr)?; let mut node_ref = merkle.put_node(node)?; let addr = node_ref.as_addr(); @@ -2173,7 +2184,7 @@ mod tests { let mut to_delete = vec![]; // could be any branch node, convenient to use the root. - let mut parents = vec![(root, 0)]; + let mut parents = vec![(sentinel, 0)]; let node = merkle.update_path_and_move_node_if_larger( (&mut parents, &mut to_delete), diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs index 5b75c0d6fd3a..536d1815684f 100644 --- a/firewood/src/merkle/proof.rs +++ b/firewood/src/merkle/proof.rs @@ -497,9 +497,11 @@ where // Add the sentinel root let mut right_chunks = vec![0]; right_chunks.extend(right.as_ref().iter().copied().flat_map(to_nibble_array)); - let root = in_mem_merkle.get_sentinel_address(); + let sentinel_addr = in_mem_merkle.get_sentinel_address(); let merkle = in_mem_merkle.get_merkle_mut(); - let mut u_ref = merkle.get_node(root).map_err(|_| ProofError::NoSuchNode)?; + let mut u_ref = merkle + .get_node(sentinel_addr) + .map_err(|_| ProofError::NoSuchNode)?; let mut parent = DiskAddress::null(); let mut fork_left = Ordering::Equal; diff --git a/firewood/src/merkle/stream.rs b/firewood/src/merkle/stream.rs index 7e3fefb2e075..f1c7b3190176 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/merkle/stream.rs @@ -69,7 +69,7 @@ impl NodeStreamState<'_> { #[derive(Debug)] pub struct MerkleNodeStream<'a, S, T> { state: NodeStreamState<'a>, - merkle_root: DiskAddress, + sentinel_addr: DiskAddress, merkle: &'a Merkle, } @@ -84,10 +84,10 @@ impl<'a, S: LinearStore, T> FusedStream for MerkleNodeStream<'a, S, T> { impl<'a, S, T> MerkleNodeStream<'a, S, T> { /// Returns a new iterator that will iterate over all the nodes in `merkle` /// with keys greater than or equal to `key`. - pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { + pub(super) fn new(merkle: &'a Merkle, sentinel_addr: DiskAddress, key: Key) -> Self { Self { state: NodeStreamState::new(key), - merkle_root, + sentinel_addr, merkle, } } @@ -104,13 +104,13 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { // at the same time as immutable access to `merkle`. let Self { state, - merkle_root, + sentinel_addr, merkle, } = &mut *self; match state { NodeStreamState::StartFromKey(key) => { - self.state = get_iterator_intial_state(merkle, *merkle_root, key)?; + self.state = get_iterator_intial_state(merkle, *sentinel_addr, key)?; self.poll_next(_cx) } NodeStreamState::Iterating { iter_stack } => { @@ -176,15 +176,14 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { } } -/// Returns the initial state for an iterator over the given `merkle` with root `root_node` -/// which starts at `key`. +/// Returns the initial state for an iterator over the given `merkle` which starts at `key`. fn get_iterator_intial_state<'a, S: LinearStore, T>( merkle: &'a Merkle, - root_node: DiskAddress, + sentinel_addr: DiskAddress, key: &[u8], ) -> Result, api::Error> { // Invariant: `node`'s key is a prefix of `key`. - let mut node = merkle.get_node(root_node)?; + let mut node = merkle.get_node(sentinel_addr)?; // Invariant: `matched_key_nibbles` is the key of `node` at the start // of each loop iteration. @@ -317,7 +316,7 @@ impl<'a, S, T> MerkleKeyValueStreamState<'a, S, T> { #[derive(Debug)] pub struct MerkleKeyValueStream<'a, S, T> { state: MerkleKeyValueStreamState<'a, S, T>, - merkle_root: DiskAddress, + sentinel_addr: DiskAddress, merkle: &'a Merkle, } @@ -328,18 +327,18 @@ impl<'a, S: LinearStore, T> FusedStream for MerkleKeyValueStream<'a, S, T> { } impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { - pub(super) fn new(merkle: &'a Merkle, merkle_root: DiskAddress) -> Self { + pub(super) fn new(merkle: &'a Merkle, sentinel_addr: DiskAddress) -> Self { Self { state: MerkleKeyValueStreamState::new(), - merkle_root, + sentinel_addr, merkle, } } - pub(super) fn from_key(merkle: &'a Merkle, merkle_root: DiskAddress, key: Key) -> Self { + pub(super) fn from_key(merkle: &'a Merkle, sentinel_addr: DiskAddress, key: Key) -> Self { Self { state: MerkleKeyValueStreamState::with_key(key), - merkle_root, + sentinel_addr, merkle, } } @@ -356,13 +355,13 @@ impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { // at the same time as immutable access to `merkle` let Self { state, - merkle_root, + sentinel_addr, merkle, } = &mut *self; match state { MerkleKeyValueStreamState::Uninitialized(key) => { - let iter = MerkleNodeStream::new(merkle, *merkle_root, key.clone()); + let iter = MerkleNodeStream::new(merkle, *sentinel_addr, key.clone()); self.state = MerkleKeyValueStreamState::Initialized { node_iter: iter }; self.poll_next(_cx) } @@ -432,9 +431,9 @@ impl<'a, 'b, S: LinearStore, T> PathIterator<'a, 'b, S, T> { sentinel_node: NodeObjRef<'a>, key: &'b [u8], ) -> Self { - let root = match sentinel_node.inner() { + let sentinel_addr = match sentinel_node.inner() { NodeType::Branch(branch) => match branch.children[0] { - Some(root) => root, + Some(sentinel_addr) => sentinel_addr, None => { return Self { state: PathIteratorState::Exhausted, @@ -450,7 +449,7 @@ impl<'a, 'b, S: LinearStore, T> PathIterator<'a, 'b, S, T> { state: PathIteratorState::Iterating { matched_key: vec![], unmatched_key: Nibbles::new(key).into_iter(), - address: root, + address: sentinel_addr, }, } } @@ -588,16 +587,16 @@ mod tests { use test_case::test_case; impl Merkle { - pub(crate) fn node_iter(&self, root: DiskAddress) -> MerkleNodeStream<'_, S, T> { - MerkleNodeStream::new(self, root, Box::new([])) + pub(crate) fn node_iter(&self, sentinel_addr: DiskAddress) -> MerkleNodeStream<'_, S, T> { + MerkleNodeStream::new(self, sentinel_addr, Box::new([])) } pub(crate) fn node_iter_from( &self, - root: DiskAddress, + sentinel_addr: DiskAddress, key: Key, ) -> MerkleNodeStream<'_, S, T> { - MerkleNodeStream::new(self, root, key) + MerkleNodeStream::new(self, sentinel_addr, key) } } @@ -606,8 +605,8 @@ mod tests { #[tokio::test] async fn path_iterate_empty_merkle_empty_key(key: &[u8]) { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - let sentinel_node = merkle.get_node(root).unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); + let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); let mut stream = merkle.path_iter(sentinel_node, key); assert!(stream.next().is_none()); } @@ -620,11 +619,13 @@ mod tests { #[tokio::test] async fn path_iterate_singleton_merkle(key: &[u8]) { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(vec![0x13, 0x37], vec![0x42], root).unwrap(); + merkle + .insert(vec![0x13, 0x37], vec![0x42], sentinel_addr) + .unwrap(); - let sentinel_node = merkle.get_node(root).unwrap(); + let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); let mut stream = merkle.path_iter(sentinel_node, key); let (key, node) = match stream.next() { @@ -644,9 +645,9 @@ mod tests { #[test_case(&[0x00, 0x00, 0x00, 0xFF, 0x01]; "past leaf key")] #[tokio::test] async fn path_iterate_non_singleton_merkle_seek_leaf(key: &[u8]) { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); - let sentinel_node = merkle.get_node(root).unwrap(); + let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); let mut stream = merkle.path_iter(sentinel_node, key); @@ -691,11 +692,11 @@ mod tests { #[tokio::test] async fn path_iterate_non_singleton_merkle_seek_branch() { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); let key = &[0x00, 0x00, 0x00]; - let sentinel_node = merkle.get_node(root).unwrap(); + let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); let mut stream = merkle.path_iter(sentinel_node, key); let (key, node) = match stream.next() { @@ -726,16 +727,17 @@ mod tests { #[tokio::test] async fn key_value_iterate_empty() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - let stream = merkle.key_value_iter_from_key(root, b"x".to_vec().into_boxed_slice()); + let sentinel_addr = merkle.init_sentinel().unwrap(); + let stream = + merkle.key_value_iter_from_key(sentinel_addr, b"x".to_vec().into_boxed_slice()); check_stream_is_done(stream).await; } #[tokio::test] async fn node_iterate_empty() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - let stream = merkle.node_iter(root); + let sentinel_addr = merkle.init_sentinel().unwrap(); + let stream = merkle.node_iter(sentinel_addr); check_stream_is_done(stream).await; } @@ -743,11 +745,13 @@ mod tests { async fn node_iterate_root_only() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(vec![0x00], vec![0x00], root).unwrap(); + merkle + .insert(vec![0x00], vec![0x00], sentinel_addr) + .unwrap(); - let mut stream = merkle.node_iter(root); + let mut stream = merkle.node_iter(sentinel_addr); let (key, node) = stream.next().await.unwrap().unwrap(); @@ -770,39 +774,47 @@ mod tests { /// The number next to each branch is the position of the child in the branch's children array. fn created_populated_merkle() -> (Merkle, DiskAddress) { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); merkle - .insert(vec![0x00, 0x00, 0x00], vec![0x00, 0x00, 0x00], root) + .insert( + vec![0x00, 0x00, 0x00], + vec![0x00, 0x00, 0x00], + sentinel_addr, + ) .unwrap(); merkle .insert( vec![0x00, 0x00, 0x00, 0x01], vec![0x00, 0x00, 0x00, 0x01], - root, + sentinel_addr, ) .unwrap(); merkle .insert( vec![0x00, 0x00, 0x00, 0xFF], vec![0x00, 0x00, 0x00, 0xFF], - root, + sentinel_addr, ) .unwrap(); merkle - .insert(vec![0x00, 0xD0, 0xD0], vec![0x00, 0xD0, 0xD0], root) + .insert( + vec![0x00, 0xD0, 0xD0], + vec![0x00, 0xD0, 0xD0], + sentinel_addr, + ) .unwrap(); merkle - .insert(vec![0x00, 0xFF], vec![0x00, 0xFF], root) + .insert(vec![0x00, 0xFF], vec![0x00, 0xFF], sentinel_addr) .unwrap(); - (merkle, root) + (merkle, sentinel_addr) } #[tokio::test] async fn node_iterator_no_start_key() { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); - let mut stream = merkle.node_iter(root); + let mut stream = merkle.node_iter(sentinel_addr); // Covers case of branch with no value let (key, node) = stream.next().await.unwrap().unwrap(); @@ -849,9 +861,10 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_between_nodes() { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); - let mut stream = merkle.node_iter_from(root, vec![0x00, 0x00, 0x01].into_boxed_slice()); + let mut stream = + merkle.node_iter_from(sentinel_addr, vec![0x00, 0x00, 0x01].into_boxed_slice()); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); @@ -873,9 +886,10 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_on_node() { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); - let mut stream = merkle.node_iter_from(root, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + let mut stream = + merkle.node_iter_from(sentinel_addr, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); @@ -897,9 +911,9 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_after_last_key() { - let (merkle, root) = created_populated_merkle(); + let (merkle, sentinel_addr) = created_populated_merkle(); - let stream = merkle.node_iter_from(root, vec![0xFF].into_boxed_slice()); + let stream = merkle.node_iter_from(sentinel_addr, vec![0xFF].into_boxed_slice()); check_stream_is_done(stream).await; } @@ -911,16 +925,18 @@ mod tests { #[tokio::test] async fn key_value_iterate_many(start: Option<&[u8]>) { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // insert all values from u8::MIN to u8::MAX, with the key and value the same for k in u8::MIN..=u8::MAX { - merkle.insert([k], vec![k], root).unwrap(); + merkle.insert([k], vec![k], sentinel_addr).unwrap(); } let mut stream = match start { - Some(start) => merkle.key_value_iter_from_key(root, start.to_vec().into_boxed_slice()), - None => merkle.key_value_iter(root), + Some(start) => { + merkle.key_value_iter_from_key(sentinel_addr, start.to_vec().into_boxed_slice()) + } + None => merkle.key_value_iter(sentinel_addr), }; // we iterate twice because we should get a None then start over @@ -941,14 +957,14 @@ mod tests { #[tokio::test] async fn key_value_fused_empty() { let merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - check_stream_is_done(merkle.key_value_iter(root)).await; + let sentinel_addr = merkle.init_sentinel().unwrap(); + check_stream_is_done(merkle.key_value_iter(sentinel_addr)).await; } #[tokio::test] async fn key_value_table_test() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); // Insert key-values in reverse order to ensure iterator // doesn't just return the keys in insertion order. @@ -957,12 +973,12 @@ mod tests { let key = vec![i, j]; let value = vec![i, j]; - merkle.insert(key, value, root).unwrap(); + merkle.insert(key, value, sentinel_addr).unwrap(); } } // Test with no start key - let mut stream = merkle.key_value_iter(root); + let mut stream = merkle.key_value_iter(sentinel_addr); for i in 0..=u8::MAX { for j in 0..=u8::MAX { let expected_key = vec![i, j]; @@ -981,7 +997,8 @@ mod tests { // Test with start key for i in 0..=u8::MAX { - let mut stream = merkle.key_value_iter_from_key(root, vec![i].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![i].into_boxed_slice()); for j in 0..=u8::MAX { let expected_key = vec![i, j]; let expected_value = vec![i, j]; @@ -1009,7 +1026,7 @@ mod tests { #[tokio::test] async fn key_value_fused_full() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let last = vec![0x00, 0x00, 0x00]; @@ -1023,10 +1040,10 @@ mod tests { } for kv in key_values.iter() { - merkle.insert(kv, kv.clone(), root).unwrap(); + merkle.insert(kv, kv.clone(), sentinel_addr).unwrap(); } - let mut stream = merkle.key_value_iter(root); + let mut stream = merkle.key_value_iter(sentinel_addr); for kv in key_values.iter() { let next = stream.next().await.unwrap().unwrap(); @@ -1040,14 +1057,14 @@ mod tests { #[tokio::test] async fn key_value_root_with_empty_value() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let key = vec![].into_boxed_slice(); let value = vec![0x00]; - merkle.insert(&key, value.clone(), root).unwrap(); + merkle.insert(&key, value.clone(), sentinel_addr).unwrap(); - let mut stream = merkle.key_value_iter(root); + let mut stream = merkle.key_value_iter(sentinel_addr); assert_eq!(stream.next().await.unwrap().unwrap(), (key, value)); } @@ -1055,22 +1072,24 @@ mod tests { #[tokio::test] async fn key_value_get_branch_and_leaf() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let first_leaf = &[0x00, 0x00]; let second_leaf = &[0x00, 0x0f]; let branch = &[0x00]; merkle - .insert(first_leaf, first_leaf.to_vec(), root) + .insert(first_leaf, first_leaf.to_vec(), sentinel_addr) .unwrap(); merkle - .insert(second_leaf, second_leaf.to_vec(), root) + .insert(second_leaf, second_leaf.to_vec(), sentinel_addr) .unwrap(); - merkle.insert(branch, branch.to_vec(), root).unwrap(); + merkle + .insert(branch, branch.to_vec(), sentinel_addr) + .unwrap(); - let mut stream = merkle.key_value_iter(root); + let mut stream = merkle.key_value_iter(sentinel_addr); assert_eq!( stream.next().await.unwrap().unwrap(), @@ -1094,7 +1113,7 @@ mod tests { #[tokio::test] async fn key_value_start_at_key_not_in_trie() { let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let first_key = 0x00; let intermediate = 0x80; @@ -1110,11 +1129,11 @@ mod tests { assert!(key_values[1] < key_values[2]); for key in key_values.iter() { - merkle.insert(key, key.to_vec(), root).unwrap(); + merkle.insert(key, key.to_vec(), sentinel_addr).unwrap(); } let mut stream = - merkle.key_value_iter_from_key(root, vec![intermediate].into_boxed_slice()); + merkle.key_value_iter_from_key(sentinel_addr, vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); let first = stream.next().await.unwrap().unwrap(); @@ -1138,19 +1157,19 @@ mod tests { let children = 0..=0x0f; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); children.clone().for_each(|child_path| { let key = vec![sibling_path, child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); }); let mut keys: Vec<_> = children .map(|child_path| { let key = vec![branch_path, child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) @@ -1161,7 +1180,8 @@ mod tests { let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.key_value_iter_from_key(root, vec![branch_path].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![branch_path].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1182,23 +1202,23 @@ mod tests { let children = (0..=0xf).map(|val| (val << 4) + val); // 0x00, 0x11, ... 0xff let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); merkle - .insert(&branch_key, branch_key.clone(), root) + .insert(&branch_key, branch_key.clone(), sentinel_addr) .unwrap(); children.clone().for_each(|child_path| { let key = vec![sibling_path, child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); }); let mut keys: Vec<_> = children .map(|child_path| { let key = vec![branch_path, child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) @@ -1210,7 +1230,8 @@ mod tests { let start = keys.iter().position(|key| key == &branch_key).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.key_value_iter_from_key(root, branch_key.into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, branch_key.into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1227,13 +1248,13 @@ mod tests { let missing = 0x0a; let children = (0..=0x0f).filter(|x| *x != missing); let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) @@ -1241,7 +1262,8 @@ mod tests { let keys = &keys[(missing as usize)..]; - let mut stream = merkle.key_value_iter_from_key(root, vec![missing].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1262,13 +1284,14 @@ mod tests { let children = (0..=0x0f).map(|val| vec![shared_path, val]); let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); children.for_each(|key| { - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); }); - let stream = merkle.key_value_iter_from_key(root, vec![start_key].into_boxed_slice()); + let stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![start_key].into_boxed_slice()); check_stream_is_done(stream).await; } @@ -1282,16 +1305,17 @@ mod tests { let children = (0..=0x0f).map(|val| vec![shared_path, val]); let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|key| { - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) .collect(); - let mut stream = merkle.key_value_iter_from_key(root, vec![start_key].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![start_key].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1310,13 +1334,13 @@ mod tests { .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff .filter(|x| *x != missing); let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) @@ -1324,7 +1348,8 @@ mod tests { let keys = &keys[((missing >> 4) as usize)..]; - let mut stream = merkle.key_value_iter_from_key(root, vec![missing].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1341,9 +1366,9 @@ mod tests { let key = vec![0x00]; let greater_key = vec![0xff]; let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); - merkle.insert(key.clone(), key, root).unwrap(); - let stream = merkle.key_value_iter_from_key(root, greater_key.into_boxed_slice()); + let sentinel_addr = merkle.init_sentinel().unwrap(); + merkle.insert(key.clone(), key, sentinel_addr).unwrap(); + let stream = merkle.key_value_iter_from_key(sentinel_addr, greater_key.into_boxed_slice()); check_stream_is_done(stream).await; } @@ -1355,13 +1380,13 @@ mod tests { .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff .filter(|x| *x != greatest); let mut merkle = create_test_merkle(); - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), root).unwrap(); + merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); key }) @@ -1369,7 +1394,8 @@ mod tests { let keys = &keys[((greatest >> 4) as usize)..]; - let mut stream = merkle.key_value_iter_from_key(root, vec![greatest].into_boxed_slice()); + let mut stream = + merkle.key_value_iter_from_key(sentinel_addr, vec![greatest].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs index 9473b1e39b1a..12f30e61911d 100644 --- a/firewood/src/merkle_util.rs +++ b/firewood/src/merkle_util.rs @@ -34,7 +34,7 @@ pub enum DataStoreError { } pub struct InMemoryMerkle { - root: DiskAddress, + sentinel_addr: DiskAddress, merkle: Merkle, } @@ -77,26 +77,29 @@ where let merkle = Merkle::new(store); #[allow(clippy::unwrap_used)] - let root = merkle.init_root().unwrap(); + let sentinel_addr = merkle.init_sentinel().unwrap(); - InMemoryMerkle { root, merkle } + InMemoryMerkle { + sentinel_addr, + merkle, + } } pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { self.merkle - .insert(key, val, self.root) + .insert(key, val, self.sentinel_addr) .map_err(|_err| DataStoreError::InsertionError) } pub fn remove>(&mut self, key: K) -> Result>, DataStoreError> { self.merkle - .remove(key, self.root) + .remove(key, self.sentinel_addr) .map_err(|_err| DataStoreError::RemovalError) } pub fn get>(&self, key: K) -> Result, DataStoreError> { self.merkle - .get(key, self.root) + .get(key, self.sentinel_addr) .map_err(|_err| DataStoreError::GetError) } @@ -105,12 +108,12 @@ where key: K, ) -> Result>, DataStoreError> { self.merkle - .get_mut(key, self.root) + .get_mut(key, self.sentinel_addr) .map_err(|_err| DataStoreError::GetError) } pub const fn get_sentinel_address(&self) -> DiskAddress { - self.root + self.sentinel_addr } pub fn get_merkle_mut(&mut self) -> &mut Merkle { @@ -119,21 +122,21 @@ where pub fn root_hash(&self) -> Result { self.merkle - .root_hash(self.root) + .root_hash(self.sentinel_addr) .map_err(|_err| DataStoreError::RootHashError) } pub fn dump(&self) -> Result { let mut s = Vec::new(); self.merkle - .dump(self.root, &mut s) + .dump(self.sentinel_addr, &mut s) .map_err(|_err| DataStoreError::DumpError)?; String::from_utf8(s).map_err(|_err| DataStoreError::UTF8Error) } pub fn prove>(&self, key: K) -> Result>, DataStoreError> { self.merkle - .prove(key, self.root) + .prove(key, self.sentinel_addr) .map_err(|_err| DataStoreError::ProofError) } From 7cbfae85afaa44de793db0b6bf80a4d3777319a2 Mon Sep 17 00:00:00 2001 From: "forestkeeperio.eth" <87507039+ForestKeeperIO@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:18:27 -0600 Subject: [PATCH 0533/1053] Update README.md (#623) Signed-off-by: forestkeeperio.eth <87507039+ForestKeeperIO@users.noreply.github.com> Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 408cc564fa9d..e09044102c01 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Firewood is an embedded key-value store, optimized to store recent Merkleized blockchain state with minimal overhead. Firewood is implemented from the ground up to directly -store trie nodes on-disk. Unlike most of state management approaches in the field, +store trie nodes on-disk. Unlike most state management approaches in the field, it is not built on top of a generic KV store such as LevelDB/RocksDB. Firewood, like a B+-tree based database, directly uses the trie structure as the index on-disk. Thus, there is no additional “emulation” of the logical trie to flatten out the data structure @@ -17,12 +17,12 @@ to feed into the underlying database that is unaware of the data being stored. T byproduct of this approach is that iteration is still fast (for serving state sync queries) but compaction is not required to maintain the index. Firewood was first conceived to provide a very fast storage layer for the EVM but could be used on any blockchain that -requires authenticated state. +requires an authenticated state. Firewood only attempts to store the latest state on-disk and will actively clean up -unused state when state diffs are committed. To avoid reference counting trie nodes, +unused data when state diffs are committed. To avoid reference counting trie nodes, Firewood does not copy-on-write (COW) the state trie and instead keeps -one latest version of the trie index on disk and applies in-place updates to it. +the latest version of the trie index on disk and applies in-place updates to it. Firewood keeps some configurable number of previous states in memory to power state sync (which may occur at a few roots behind the current state). @@ -63,7 +63,7 @@ versions with no additional cost at all. - `Put` - An operation for a `Key`/`Value` pair. A put means "create if it doesn't exist, or update it if it does. A put operation is how you add a `Value` for a specific `Key`. -- `Delete` - An operation indicating that a `Key` that should be removed from the trie. +- `Delete` - An operation indicating that a `Key` should be removed from the trie. - `Batch Operation` - An operation of either `Put` or `Delete`. - `Batch` - An ordered set of `Batch Operation`s. - `Proposal` - A proposal consists of a base `Root Hash` and a `Batch`, but is not @@ -100,13 +100,13 @@ reader and writer interfaces to have consistent read/write semantics. ### Seasoned milestone This milestone will add support for proposals, including proposed future -branches, with a cache to make committing these branches efficiently. +branches, with a cache to make committing these branches efficient. -- [x] Be able to support multiple proposed revisions against latest committed +- [x] Be able to support multiple proposed revisions against the latest committed version. - [x] Be able to propose a batch against the existing committed revision, or propose a batch against any existing proposed revision. -- [x] Commit a batch that has been proposed will invalidate all other proposals +- [x] Committing a batch that has been proposed will invalidate all other proposals that are not children of the committed proposed batch. - [x] Be able to quickly commit a batch that has been proposed. - [x] Remove RLP encoding @@ -119,7 +119,7 @@ be developed for this milestone. - [x] Migrate to a fully async interface - [x] Pluggable encoding for nodes, for optional compatibility with MerkleDB -- [ ] :runner: MerkleDB root hash in parity for seamless transition between MerkleDB +- [ ] :runner: MerkleDB root hash in parity for a seamless transition between MerkleDB and Firewood. - [ ] :runner: Support replicating the full state with corresponding range proofs that verify the correctness of the data. From 8eef1a5272a06bb3a50c248d6bbf134e60f805bf Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 2 Apr 2024 19:33:23 -0400 Subject: [PATCH 0534/1053] more `compact` nits/cleanup (#618) Co-authored-by: xinifinity <113067541+xinifinity@users.noreply.github.com> --- firewood/src/shale/compact.rs | 57 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs index 458089cb8ee3..e23f6de6a037 100644 --- a/firewood/src/shale/compact.rs +++ b/firewood/src/shale/compact.rs @@ -169,14 +169,14 @@ pub struct StoreHeader { } #[derive(Debug)] -struct StoreHeaderSliced { +struct StoreHeaderObjs { meta_store_tail: Obj, data_store_tail: Obj, base_addr: Obj, alloc_addr: Obj, } -impl StoreHeaderSliced { +impl StoreHeaderObjs { fn flush_dirty(&mut self) { self.meta_store_tail.flush_dirty(); self.data_store_tail.flush_dirty(); @@ -202,8 +202,8 @@ impl StoreHeader { } } - fn into_fields(r: Obj) -> Result { - Ok(StoreHeaderSliced { + fn into_fields(r: Obj) -> Result { + Ok(StoreHeaderObjs { meta_store_tail: StoredView::slice( &r, Self::META_STORE_TAIL_OFFSET, @@ -282,7 +282,7 @@ impl Storable for StoreHeader { struct StoreInner { meta_store: M, data_store: M, - header: StoreHeaderSliced, + header: StoreHeaderObjs, alloc_max_walk: u64, regn_nbit: u64, } @@ -357,22 +357,21 @@ impl StoreInner { Ok(DiskAddress(addr)) } - fn free(&mut self, addr: u64) -> Result<(), ShaleError> { + fn free(&mut self, freed_addr: u64) -> Result<(), ShaleError> { let region_size = 1 << self.regn_nbit; - let header_offset = addr - ChunkHeader::SERIALIZED_LEN; - let header_chunk_size = { - let header = self.get_header(DiskAddress::from(header_offset as usize))?; + let mut freed_header_offset = freed_addr - ChunkHeader::SERIALIZED_LEN; + let mut freed_chunk_size = { + let header = self.get_header(DiskAddress::from(freed_header_offset as usize))?; assert!(!header.is_freed); header.chunk_size }; - let mut free_header_offset = header_offset; - let mut free_chunk_size = header_chunk_size; + let mut freed_footer_offset = freed_addr + freed_chunk_size; - if header_offset & (region_size - 1) > 0 { + if freed_header_offset & (region_size - 1) > 0 { // TODO danlaine: document what this condition means. // merge with previous chunk if it's freed. - let prev_footer_offset = header_offset - ChunkFooter::SERIALIZED_LEN; + let prev_footer_offset = freed_header_offset - ChunkFooter::SERIALIZED_LEN; let prev_footer = self.get_footer(DiskAddress::from(prev_footer_offset as usize))?; let prev_header_offset = @@ -380,37 +379,35 @@ impl StoreInner { let prev_header = self.get_header(DiskAddress::from(prev_header_offset as usize))?; if prev_header.is_freed { - free_header_offset = prev_header_offset; - free_chunk_size += ChunkHeader::SERIALIZED_LEN + freed_header_offset = prev_header_offset; + freed_chunk_size += ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + prev_header.chunk_size; self.delete_descriptor(prev_header.desc_addr)?; } } - let footer_offset = addr + header_chunk_size; - let mut free_footer_offset = footer_offset; - #[allow(clippy::unwrap_used)] - if footer_offset + ChunkFooter::SERIALIZED_LEN + if freed_footer_offset + ChunkFooter::SERIALIZED_LEN < self.header.data_store_tail.unwrap().get() as u64 - && (region_size - (footer_offset & (region_size - 1))) + && (region_size - (freed_footer_offset & (region_size - 1))) >= ChunkFooter::SERIALIZED_LEN + ChunkHeader::SERIALIZED_LEN { // TODO danlaine: document what this condition means. // merge with next chunk if it's freed. - let next_header_offset = footer_offset + ChunkFooter::SERIALIZED_LEN; + let next_header_offset = freed_footer_offset + ChunkFooter::SERIALIZED_LEN; let next_header = self.get_header(DiskAddress::from(next_header_offset as usize))?; + if next_header.is_freed { let next_footer_offset = next_header_offset + ChunkHeader::SERIALIZED_LEN + next_header.chunk_size; - free_footer_offset = next_footer_offset; + freed_footer_offset = next_footer_offset; { let next_footer = self.get_footer(DiskAddress::from(next_footer_offset as usize))?; assert!(next_header.chunk_size == next_footer.chunk_size); } - free_chunk_size += ChunkHeader::SERIALIZED_LEN + freed_chunk_size += ChunkHeader::SERIALIZED_LEN + ChunkFooter::SERIALIZED_LEN + next_header.chunk_size; self.delete_descriptor(next_header.desc_addr)?; @@ -422,25 +419,25 @@ impl StoreInner { let mut desc = self.get_descriptor(desc_addr)?; #[allow(clippy::unwrap_used)] desc.modify(|d| { - d.chunk_size = free_chunk_size; - d.haddr = free_header_offset as usize; + d.chunk_size = freed_chunk_size; + d.haddr = freed_header_offset as usize; }) .unwrap(); } - let mut free_header = self.get_header(DiskAddress::from(free_header_offset as usize))?; + let mut freed_header = self.get_header(DiskAddress::from(freed_header_offset as usize))?; #[allow(clippy::unwrap_used)] - free_header + freed_header .modify(|h| { - h.chunk_size = free_chunk_size; + h.chunk_size = freed_chunk_size; h.is_freed = true; h.desc_addr = desc_addr; }) .unwrap(); - let mut free_footer = self.get_footer(DiskAddress::from(free_footer_offset as usize))?; + let mut free_footer = self.get_footer(DiskAddress::from(freed_footer_offset as usize))?; #[allow(clippy::unwrap_used)] free_footer - .modify(|f| f.chunk_size = free_chunk_size) + .modify(|f| f.chunk_size = freed_chunk_size) .unwrap(); Ok(()) From 2f9d07b50f2c06b5e9603f2a7a5e480722a1324d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 13 Aug 2024 10:04:25 -1000 Subject: [PATCH 0535/1053] New node store interface (#625) Co-authored-by: Dan Laine Co-authored-by: hhao --- .github/workflows/ci.yaml | 18 +- Cargo.toml | 3 +- README.md | 86 +- docs/assets/architecture.svg | 2 +- firewood/Cargo.toml | 23 +- firewood/benches/hashops.rs | 195 +- firewood/benches/shale-bench.rs | 102 - firewood/examples/insert.rs | 120 +- firewood/src/config.rs | 69 - firewood/src/db.rs | 1114 ++----- firewood/src/db/proposal.rs | 317 -- firewood/src/file.rs | 118 - firewood/src/lib.rs | 14 +- firewood/src/manager.rs | 191 ++ firewood/src/merkle.rs | 4217 ++++++++++++++----------- firewood/src/merkle/node.rs | 906 ------ firewood/src/merkle/node/branch.rs | 362 --- firewood/src/merkle/node/leaf.rs | 181 -- firewood/src/merkle/node/path.rs | 122 - firewood/src/merkle/proof.rs | 853 ----- firewood/src/merkle/trie_hash.rs | 65 - firewood/src/merkle_util.rs | 165 - firewood/src/nibbles.rs | 259 -- firewood/src/proof.rs | 232 ++ firewood/src/range_proof.rs | 18 + firewood/src/shale/README.md | 3 - firewood/src/shale/compact.rs | 848 ----- firewood/src/shale/disk_address.rs | 193 -- firewood/src/shale/in_mem.rs | 143 - firewood/src/shale/mod.rs | 515 --- firewood/src/storage/buffer.rs | 970 ------ firewood/src/storage/mod.rs | 967 ------ firewood/src/{merkle => }/stream.rs | 871 +++-- firewood/src/v2/api.rs | 44 +- firewood/src/v2/db.rs | 6 +- firewood/src/v2/emptydb.rs | 64 +- firewood/src/v2/propose.rs | 17 +- firewood/tests/common/mod.rs | 20 +- firewood/tests/db.rs | 603 ++-- firewood/tests/merkle.rs | 1146 ------- firewood/tests/v2api.rs | 89 +- fwdctl/src/create.rs | 234 +- fwdctl/src/delete.rs | 32 +- fwdctl/src/dump.rs | 49 +- fwdctl/src/get.rs | 42 +- fwdctl/src/insert.rs | 34 +- fwdctl/src/main.rs | 11 +- fwdctl/src/root.rs | 23 +- fwdctl/tests/cli.rs | 6 + growth-ring/Cargo.toml | 44 - growth-ring/README.md | 3 - growth-ring/examples/.gitignore | 2 - growth-ring/examples/demo1.rs | 110 - growth-ring/src/lib.rs | 370 --- growth-ring/src/wal.rs | 1363 -------- growth-ring/src/walerror.rs | 33 - growth-ring/tests/common/mod.rs | 683 ---- growth-ring/tests/rand_fail.rs | 126 - grpc-testtool/Cargo.toml | 6 +- grpc-testtool/src/service.rs | 28 +- grpc-testtool/src/service/database.rs | 111 +- grpc-testtool/src/service/db.rs | 66 +- libaio/Cargo.toml | 24 - libaio/README.md | 3 - libaio/build.rs | 18 - libaio/libaio/.gitignore | 10 - libaio/libaio/COPYING | 515 --- libaio/libaio/Makefile | 73 - libaio/libaio/aio_ring.h | 49 - libaio/libaio/compat-0_1.c | 62 - libaio/libaio/io_cancel.c | 23 - libaio/libaio/io_destroy.c | 23 - libaio/libaio/io_getevents.c | 35 - libaio/libaio/io_pgetevents.c | 56 - libaio/libaio/io_queue_init.c | 33 - libaio/libaio/io_queue_release.c | 27 - libaio/libaio/io_queue_run.c | 39 - libaio/libaio/io_queue_wait.c | 31 - libaio/libaio/io_setup.c | 23 - libaio/libaio/io_submit.c | 23 - libaio/libaio/libaio.h | 300 -- libaio/libaio/libaio.map | 27 - libaio/libaio/raw_syscall.c | 19 - libaio/libaio/syscall-alpha.h | 5 - libaio/libaio/syscall-arm.h | 26 - libaio/libaio/syscall-generic.h | 11 - libaio/libaio/syscall-i386.h | 6 - libaio/libaio/syscall-ia64.h | 5 - libaio/libaio/syscall-ppc.h | 5 - libaio/libaio/syscall-s390.h | 5 - libaio/libaio/syscall-sparc.h | 5 - libaio/libaio/syscall-x86_64.h | 6 - libaio/libaio/syscall.h | 73 - libaio/libaio/vsys_def.h | 24 - libaio/src/abi.rs | 113 - libaio/src/lib.rs | 600 ---- libaio/tests/simple_test.rs | 69 - storage/Cargo.toml | 20 + storage/src/hashednode.rs | 253 ++ storage/src/lib.rs | 33 + storage/src/linear/filebacked.rs | 64 + storage/src/linear/memory.rs | 85 + storage/src/linear/mod.rs | 56 + storage/src/node/branch.rs | 173 + storage/src/node/leaf.rs | 29 + storage/src/node/mod.rs | 102 + storage/src/node/path.rs | 304 ++ storage/src/nodestore.rs | 903 ++++++ storage/src/trie_hash.rs | 102 + 109 files changed, 6266 insertions(+), 17851 deletions(-) delete mode 100644 firewood/benches/shale-bench.rs delete mode 100644 firewood/src/config.rs delete mode 100644 firewood/src/db/proposal.rs delete mode 100644 firewood/src/file.rs create mode 100644 firewood/src/manager.rs delete mode 100644 firewood/src/merkle/node.rs delete mode 100644 firewood/src/merkle/node/branch.rs delete mode 100644 firewood/src/merkle/node/leaf.rs delete mode 100644 firewood/src/merkle/node/path.rs delete mode 100644 firewood/src/merkle/proof.rs delete mode 100644 firewood/src/merkle/trie_hash.rs delete mode 100644 firewood/src/merkle_util.rs delete mode 100644 firewood/src/nibbles.rs create mode 100644 firewood/src/proof.rs create mode 100644 firewood/src/range_proof.rs delete mode 100644 firewood/src/shale/README.md delete mode 100644 firewood/src/shale/compact.rs delete mode 100644 firewood/src/shale/disk_address.rs delete mode 100644 firewood/src/shale/in_mem.rs delete mode 100644 firewood/src/shale/mod.rs delete mode 100644 firewood/src/storage/buffer.rs delete mode 100644 firewood/src/storage/mod.rs rename firewood/src/{merkle => }/stream.rs (56%) delete mode 100644 firewood/tests/merkle.rs delete mode 100644 growth-ring/Cargo.toml delete mode 100644 growth-ring/README.md delete mode 100644 growth-ring/examples/.gitignore delete mode 100644 growth-ring/examples/demo1.rs delete mode 100644 growth-ring/src/lib.rs delete mode 100644 growth-ring/src/wal.rs delete mode 100644 growth-ring/src/walerror.rs delete mode 100644 growth-ring/tests/common/mod.rs delete mode 100644 growth-ring/tests/rand_fail.rs delete mode 100644 libaio/Cargo.toml delete mode 100644 libaio/README.md delete mode 100644 libaio/build.rs delete mode 100644 libaio/libaio/.gitignore delete mode 100644 libaio/libaio/COPYING delete mode 100644 libaio/libaio/Makefile delete mode 100644 libaio/libaio/aio_ring.h delete mode 100644 libaio/libaio/compat-0_1.c delete mode 100644 libaio/libaio/io_cancel.c delete mode 100644 libaio/libaio/io_destroy.c delete mode 100644 libaio/libaio/io_getevents.c delete mode 100644 libaio/libaio/io_pgetevents.c delete mode 100644 libaio/libaio/io_queue_init.c delete mode 100644 libaio/libaio/io_queue_release.c delete mode 100644 libaio/libaio/io_queue_run.c delete mode 100644 libaio/libaio/io_queue_wait.c delete mode 100644 libaio/libaio/io_setup.c delete mode 100644 libaio/libaio/io_submit.c delete mode 100644 libaio/libaio/libaio.h delete mode 100644 libaio/libaio/libaio.map delete mode 100644 libaio/libaio/raw_syscall.c delete mode 100644 libaio/libaio/syscall-alpha.h delete mode 100644 libaio/libaio/syscall-arm.h delete mode 100644 libaio/libaio/syscall-generic.h delete mode 100644 libaio/libaio/syscall-i386.h delete mode 100644 libaio/libaio/syscall-ia64.h delete mode 100644 libaio/libaio/syscall-ppc.h delete mode 100644 libaio/libaio/syscall-s390.h delete mode 100644 libaio/libaio/syscall-sparc.h delete mode 100644 libaio/libaio/syscall-x86_64.h delete mode 100644 libaio/libaio/syscall.h delete mode 100644 libaio/libaio/vsys_def.h delete mode 100644 libaio/src/abi.rs delete mode 100644 libaio/src/lib.rs delete mode 100644 libaio/tests/simple_test.rs create mode 100644 storage/Cargo.toml create mode 100644 storage/src/hashednode.rs create mode 100644 storage/src/lib.rs create mode 100644 storage/src/linear/filebacked.rs create mode 100644 storage/src/linear/memory.rs create mode 100644 storage/src/linear/mod.rs create mode 100644 storage/src/node/branch.rs create mode 100644 storage/src/node/leaf.rs create mode 100644 storage/src/node/mod.rs create mode 100644 storage/src/node/path.rs create mode 100644 storage/src/nodestore.rs create mode 100644 storage/src/trie_hash.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 04b2a67b4090..9af16b42a5db 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,9 +37,9 @@ jobs: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- ${{ runner.os }}-deps- - name: Check - run: cargo check --workspace --tests --examples --benches --all-features + run: cargo check --workspace --tests --examples --benches - name: Build - run: cargo build --workspace --tests --examples --benches --all-features + run: cargo build --workspace --tests --examples --benches # Always update the cache - name: Cleanup run: | @@ -100,7 +100,7 @@ jobs: - name: Format run: cargo fmt -- --check - name: Clippy - run: cargo clippy --tests --examples --benches --all-features -- -D warnings + run: cargo clippy --tests --examples --benches -- -D warnings test: needs: build @@ -122,7 +122,7 @@ jobs: target/ key: ${{ needs.build.outputs.cache-key }} - name: Run tests - run: cargo test --all-features --verbose + run: cargo test --verbose examples: needs: build @@ -142,11 +142,11 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ needs.build.outputs.cache-key }} - # benchmarks were not being done in --release mode, we can enable this again later - # - name: Run benchmark example - # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - - name: Run insert example - run: RUST_BACKTRACE=1 cargo run --example insert + # benchmarks were not being done in --release mode, we can enable this again later + # - name: Run benchmark example + # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 + # - name: Run insert example + # run: RUST_BACKTRACE=1 cargo run --example insert docs: needs: build diff --git a/Cargo.toml b/Cargo.toml index 018f2a39f377..7118fbd72da1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,7 @@ members = [ "firewood", "fwdctl", - "growth-ring", - "libaio", + "storage", "grpc-testtool", ] resolver = "2" diff --git a/README.md b/README.md index e09044102c01..34f6b8afdf58 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,10 @@ but compaction is not required to maintain the index. Firewood was first conceiv a very fast storage layer for the EVM but could be used on any blockchain that requires an authenticated state. -Firewood only attempts to store the latest state on-disk and will actively clean up -unused data when state diffs are committed. To avoid reference counting trie nodes, -Firewood does not copy-on-write (COW) the state trie and instead keeps -the latest version of the trie index on disk and applies in-place updates to it. -Firewood keeps some configurable number of previous states in memory to power -state sync (which may occur at a few roots behind the current state). - -Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL -guarantees atomicity and durability in the database, but also offers -“reversibility”: some portion of the old WAL can be optionally kept around to -allow a fast in-memory rollback to recover some past versions of the entire -store back in memory. While running the store, new changes will also contribute -to the configured window of changes (at batch granularity) to access any past -versions with no additional cost at all. +Firewood only attempts to store recent revisions on-disk and will actively clean up +unused data when revisions expire. Firewood keeps some configurable number of previous states in memory and on disk to power state sync (which may occur at a few roots behind the current state). To do this, a new root is always created for each revision that can reference either new nodes from this revision or nodes from a prior revision. When creating a revision, a list of nodes that are no longer needed are computed and saved to disk in a future-delete log (FDL) as well as kept in memory. When a revision expires, the nodes that were deleted when it was created are returned to the free space. + +Firewood guarantees recoverability by not referencing the new nodes in a new revision before they are flushed to disk, as well as carefully managing the free list during the creation and expiration of revisions. ## Architecture Diagram @@ -73,69 +63,11 @@ versions with no additional cost at all. `Revision`. ## Roadmap - -**LEGEND** - -- [ ] Not started -- [ ] :runner: In progress -- [x] Complete - -### Green Milestone - -This milestone will focus on additional code cleanup, including supporting -concurrent access to a specific revision, as well as cleaning up the basic -reader and writer interfaces to have consistent read/write semantics. - -- [x] Concurrent readers of pinned revisions while allowing additional batches - to commit, to support parallel reads for the past consistent states. The revisions - are uniquely identified by root hashes. -- [x] Pin a reader to a specific revision, so that future commits or other - operations do not see any changes. -- [x] Be able to read-your-write in a batch that is not committed. Uncommitted - changes will not be shown to any other concurrent readers. -- [x] Add some metrics framework to support timings and volume for future milestones - To support this, a new method Db::metrics() returns an object that can be serialized - into prometheus metrics or json (it implements [serde::Serialize]) - -### Seasoned milestone - -This milestone will add support for proposals, including proposed future -branches, with a cache to make committing these branches efficient. - -- [x] Be able to support multiple proposed revisions against the latest committed - version. -- [x] Be able to propose a batch against the existing committed revision, or - propose a batch against any existing proposed revision. -- [x] Committing a batch that has been proposed will invalidate all other proposals - that are not children of the committed proposed batch. -- [x] Be able to quickly commit a batch that has been proposed. -- [x] Remove RLP encoding - -### Dried milestone - -The focus of this milestone will be to support synchronization to other -instances to replicate the state. A synchronization library should also -be developed for this milestone. - -- [x] Migrate to a fully async interface -- [x] Pluggable encoding for nodes, for optional compatibility with MerkleDB -- [ ] :runner: MerkleDB root hash in parity for a seamless transition between MerkleDB - and Firewood. -- [ ] :runner: Support replicating the full state with corresponding range proofs that - verify the correctness of the data. -- [ ] Pluggable IO subsystem (tokio\_uring, monoio, etc) -- [ ] Add metric reporting -- [ ] Enforce limits on the size of the range proof as well as keys to make - synchronization easier for clients. -- [ ] Add support for Ava Labs generic test tool via grpc client -- [ ] Support replicating the delta state from the last sync point with - corresponding change proofs that verify the correctness of the data. -- [ ] Refactor `Shale` to be more idiomatic, consider rearchitecting it - -## Build - -Firewood currently is Linux-only, as it has a dependency on the asynchronous -I/O provided by the Linux kernel (see `libaio`). + - [ ] Complete the proof code + - [ ] Complete the revision manager + - [ ] Complete the API implementation + - [ ] Implement a node cache + - [ ] Hook up the RPC ## Run diff --git a/docs/assets/architecture.svg b/docs/assets/architecture.svg index e57a3025d58a..a88885f0d78e 100644 --- a/docs/assets/architecture.svg +++ b/docs/assets/architecture.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 60cafb5bad7a..af6e55f5d573 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -18,38 +18,31 @@ readme = "../README.md" [dependencies] aquamarine = "0.5.0" async-trait = "0.1.77" -bytemuck = { version = "1.14.3", features = ["derive"] } -enum-as-inner = "0.6.0" -growth-ring = { version = "0.0.4", path = "../growth-ring" } -libaio = {version = "0.0.4", path = "../libaio" } +storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" -lru = "0.12.2" metered = "0.9.0" nix = {version = "0.28.0", features = ["fs", "uio"]} -parking_lot = "0.12.1" -serde = { version = "1.0", features = ["derive"] } -sha3 = "0.10.8" +serde = { version = "1.0" } +sha2 = "0.10.8" thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.18.1" bincode = "1.3.3" -bitflags = { version = "2.4.2", features = ["bytemuck"] } -env_logger = { version = "0.11.2", optional = true } log = { version = "0.4.20", optional = true } +test-case = "3.3.1" +integer-encoding = "4.0.0" [features] -logger = ["dep:env_logger", "log"] +default = [] +logger = ["log"] +nightly = [] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} -keccak-hasher = "0.15.3" rand = "0.8.5" triehash = "0.8.4" -assert_cmd = "2.0.13" -predicates = "3.1.0" clap = { version = "4.5.0", features = ['derive'] } -test-case = "3.3.1" pprof = { version = "0.13.0", features = ["flamegraph"] } [[bench]] diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 4acccb885edf..a6ee313cb4cb 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -4,25 +4,12 @@ // hash benchmarks; run with 'cargo bench' use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; -use firewood::{ - db::{BatchOp, DbConfig}, - merkle::{Bincode, Merkle, TrieHash, TRIE_HASH_LEN}, - shale::{ - compact::{ChunkHeader, Store}, - disk_address::DiskAddress, - in_mem::InMemLinearStore, - LinearStore, ObjCache, Storable, StoredView, - }, - storage::WalConfig, - v2::api::{Db, Proposal}, -}; +use firewood::merkle::Merkle; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; -use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path, sync::Arc}; - -pub type MerkleWithEncoder = Merkle; - -const ZERO_HASH: TrieHash = TrieHash([0u8; TRIE_HASH_LEN]); +use std::sync::Arc; +use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path}; +use storage::{MemStore, NodeStore}; // To enable flamegraph output // cargo bench --bench shale-bench -- --profile-time=N @@ -65,25 +52,27 @@ impl Profiler for FlamegraphProfiler { } } -fn bench_trie_hash(criterion: &mut Criterion) { - let mut to = [1u8; TRIE_HASH_LEN]; - let mut store = InMemLinearStore::new(TRIE_HASH_LEN as u64, 0u8); - store.write(0, &*ZERO_HASH).expect("write should succeed"); - - #[allow(clippy::unwrap_used)] - criterion - .benchmark_group("TrieHash") - .bench_function("dehydrate", |b| { - b.iter(|| ZERO_HASH.serialize(&mut to).unwrap()); - }) - .bench_function("hydrate", |b| { - b.iter(|| TrieHash::deserialize(0, &store).unwrap()); - }); -} - -fn bench_merkle(criterion: &mut Criterion) { - const TEST_MEM_SIZE: u64 = 20_000_000; - const KEY_LEN: usize = 4; +// TODO danlaine use or remove +// fn bench_trie_hash(criterion: &mut Criterion) { +// let mut to = [1u8; TRIE_HASH_LEN]; +// let mut store = InMemLinearStore::new(TRIE_HASH_LEN as u64, 0u8); +// store.write(0, &*ZERO_HASH).expect("write should succeed"); + +// #[allow(clippy::unwrap_used)] +// criterion +// .benchmark_group("TrieHash") +// .bench_function("dehydrate", |b| { +// b.iter(|| ZERO_HASH.serialize(&mut to).unwrap()); +// }) +// .bench_function("hydrate", |b| { +// b.iter(|| TrieHash::deserialize(0, &store).unwrap()); +// }); +// } + +// This benchmark peeks into the merkle layer and times how long it takes +// to insert NKEYS with a key length of KEYSIZE +#[allow(clippy::unwrap_used)] +fn bench_merkle(criterion: &mut Criterion) { let mut rng = StdRng::seed_from_u64(1234); criterion @@ -92,105 +81,83 @@ fn bench_merkle(criterion: &mut Criterion) { .bench_function("insert", |b| { b.iter_batched( || { - let merkle_payload_header = DiskAddress::from(0); - - #[allow(clippy::unwrap_used)] - let merkle_payload_header_ref = StoredView::addr_to_obj( - &InMemLinearStore::new(2 * ChunkHeader::SERIALIZED_LEN, 9), - merkle_payload_header, - ChunkHeader::SERIALIZED_LEN, - ) - .unwrap(); - - #[allow(clippy::unwrap_used)] - let store = Store::new( - InMemLinearStore::new(TEST_MEM_SIZE, 0), - InMemLinearStore::new(TEST_MEM_SIZE, 1), - merkle_payload_header_ref, - ObjCache::new(1 << 20), - 4096, - 4096, - ) - .unwrap(); - - let merkle = MerkleWithEncoder::new(store); - #[allow(clippy::unwrap_used)] - let sentinel_addr = merkle.init_sentinel().unwrap(); + let store = Arc::new(MemStore::new(vec![])); + let nodestore = NodeStore::new_empty_proposal(store); + let merkle = Merkle::from(nodestore); let keys: Vec> = repeat_with(|| { (&mut rng) .sample_iter(&Alphanumeric) - .take(KEY_LEN) + .take(KEYSIZE) .collect() }) - .take(N) + .take(NKEYS) .collect(); - (merkle, sentinel_addr, keys) + (merkle, keys) }, #[allow(clippy::unwrap_used)] - |(mut merkle, sentinel_addr, keys)| { + |(mut merkle, keys)| { keys.into_iter() - .for_each(|key| merkle.insert(key, vec![b'v'], sentinel_addr).unwrap()) + .for_each(|key| merkle.insert(&key, Box::new(*b"v")).unwrap()); + let _frozen = merkle.hash(); }, BatchSize::SmallInput, ); }); } -fn bench_db(criterion: &mut Criterion) { - const KEY_LEN: usize = 4; - let mut rng = StdRng::seed_from_u64(1234); - - #[allow(clippy::unwrap_used)] - criterion - .benchmark_group("Db") - .sample_size(30) - .bench_function("commit", |b| { - b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter_batched( - || { - let batch_ops: Vec<_> = repeat_with(|| { - (&mut rng) - .sample_iter(&Alphanumeric) - .take(KEY_LEN) - .collect() - }) - .map(|key: Vec<_>| BatchOp::Put { - key, - value: vec![b'v'], - }) - .take(N) - .collect(); - batch_ops - }, - |batch_ops| async { - let db_path = std::env::temp_dir(); - let db_path = db_path.join("benchmark_db"); - let cfg = - DbConfig::builder().wal(WalConfig::builder().max_revisions(10).build()); - - #[allow(clippy::unwrap_used)] - let db = - firewood::db::Db::new(db_path, &cfg.clone().truncate(true).build()) - .await - .unwrap(); - - #[allow(clippy::unwrap_used)] - Arc::new(db.propose(batch_ops).await.unwrap()) - .commit() - .await - .unwrap() - }, - BatchSize::SmallInput, - ); - }); -} +// This bechmark does the same thing as bench_merkle except it uses the revision manager +// TODO: Enable again once the revision manager is stable +// fn _bench_db(criterion: &mut Criterion) { +// const KEY_LEN: usize = 4; +// let mut rng = StdRng::seed_from_u64(1234); + +// #[allow(clippy::unwrap_used)] +// criterion +// .benchmark_group("Db") +// .sample_size(30) +// .bench_function("commit", |b| { +// b.to_async(tokio::runtime::Runtime::new().unwrap()) +// .iter_batched( +// || { +// let batch_ops: Vec<_> = repeat_with(|| { +// (&mut rng) +// .sample_iter(&Alphanumeric) +// .take(KEY_LEN) +// .collect() +// }) +// .map(|key: Vec<_>| BatchOp::Put { +// key, +// value: vec![b'v'], +// }) +// .take(N) +// .collect(); +// batch_ops +// }, +// |batch_ops| async { +// let db_path = std::env::temp_dir(); +// let db_path = db_path.join("benchmark_db"); +// let cfg = DbConfig::builder(); + +// #[allow(clippy::unwrap_used)] +// let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) +// .await +// .unwrap(); + +// #[allow(clippy::unwrap_used)] +// db.propose(batch_ops).await.unwrap().commit().await.unwrap() +// }, +// BatchSize::SmallInput, +// ); +// }); +// } criterion_group! { name = benches; config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); - targets = bench_trie_hash, bench_merkle::<3>, bench_db::<100> + // targets = bench_trie_hash, bench_merkle::<3, 32>, bench_db::<100> + targets = bench_merkle::<3, 4>, bench_merkle<3, 32> } criterion_main!(benches); diff --git a/firewood/benches/shale-bench.rs b/firewood/benches/shale-bench.rs deleted file mode 100644 index 93b2000240e3..000000000000 --- a/firewood/benches/shale-bench.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use criterion::{ - black_box, criterion_group, criterion_main, profiler::Profiler, Bencher, Criterion, -}; -use firewood::shale::{ - compact::{ChunkHeader, StoreHeader}, - disk_address::DiskAddress, - in_mem::InMemLinearStore, - LinearStore, Obj, StoredView, -}; -use pprof::ProfilerGuard; -use rand::Rng; -use std::{fs::File, os::raw::c_int, path::Path}; - -const BENCH_MEM_SIZE: usize = 2_000_000; - -// To enable flamegraph output -// cargo bench --bench shale-bench -- --profile-time=N -enum FlamegraphProfiler { - Init(c_int), - Active(ProfilerGuard<'static>), -} - -fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { - |_| panic!("Error on file `{}`", path.display()) -} - -impl Profiler for FlamegraphProfiler { - #[allow(clippy::unwrap_used)] - fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { - if let Self::Init(frequency) = self { - let guard = ProfilerGuard::new(*frequency).unwrap(); - *self = Self::Active(guard); - } - } - - #[allow(clippy::unwrap_used)] - fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { - std::fs::create_dir_all(benchmark_dir).unwrap(); - let filename = "shale-flamegraph.svg"; - let flamegraph_path = benchmark_dir.join(filename); - #[allow(clippy::unwrap_used)] - let flamegraph_file = - File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); - - #[allow(clippy::unwrap_used)] - if let Self::Active(profiler) = self { - profiler - .report() - .build() - .unwrap() - .flamegraph(flamegraph_file) - .unwrap_or_else(file_error_panic(&flamegraph_path)); - } - } -} - -fn get_view(b: &mut Bencher, mut cached: C) { - let mut rng = rand::thread_rng(); - - b.iter(|| { - let len = rng.gen_range(0..26); - #[allow(clippy::indexing_slicing)] - let rdata = black_box(&"abcdefghijklmnopqrstuvwxyz".as_bytes()[..len]); - - let offset = rng.gen_range(0..BENCH_MEM_SIZE - len); - - cached.write(offset, rdata).expect("write should succeed"); - #[allow(clippy::unwrap_used)] - let view = cached - .get_view(offset, rdata.len().try_into().unwrap()) - .unwrap(); - - serialize(&cached); - assert_eq!(view.as_deref(), rdata); - }); -} - -fn serialize(m: &T) { - let compact_header_obj: DiskAddress = DiskAddress::from(0x0); - #[allow(clippy::unwrap_used)] - let _: Obj = - StoredView::addr_to_obj(m, compact_header_obj, ChunkHeader::SERIALIZED_LEN).unwrap(); -} - -fn bench_cursors(c: &mut Criterion) { - let mut group = c.benchmark_group("shale-bench"); - group.bench_function("InMemLinearStore", |b| { - let mem = InMemLinearStore::new(BENCH_MEM_SIZE as u64, 0); - get_view(b, mem) - }); -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); - targets = bench_cursors -} - -criterion_main!(benches); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 471360e79e91..5514cb013357 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,16 +5,13 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::{ - borrow::BorrowMut as _, collections::HashMap, error::Error, ops::RangeInclusive, sync::Arc, - time::Instant, -}; +use std::{collections::HashMap, error::Error, ops::RangeInclusive}; use firewood::{ - db::{Batch, BatchOp, Db, DbConfig}, - v2::api::{Db as _, DbView, Proposal}, + db::{Batch, BatchOp}, + v2::api::DbView, }; -use rand::{distributions::Alphanumeric, Rng, SeedableRng}; +use rand::Rng; #[derive(Parser, Debug)] struct Args { @@ -46,60 +43,63 @@ fn string_to_range(input: &str) -> Result, Box Result<(), Box> { - let cfg = DbConfig::builder().truncate(true).build(); - - let args = Args::parse(); - - let db = Db::new("rev_db", &cfg) - .await - .expect("db initiation should succeed"); - - let keys = args.batch_size; - let start = Instant::now(); - - let mut rng = if let Some(seed) = args.seed { - rand::rngs::StdRng::seed_from_u64(seed) - } else { - rand::rngs::StdRng::from_entropy() - }; - - for _ in 0..args.number_of_batches { - let keylen = rng.gen_range(args.keylen.clone()); - let valuelen = rng.gen_range(args.valuelen.clone()); - let batch: Batch, Vec> = (0..keys) - .map(|_| { - ( - rng.borrow_mut() - .sample_iter(&Alphanumeric) - .take(keylen) - .collect::>(), - rng.borrow_mut() - .sample_iter(&Alphanumeric) - .take(valuelen) - .collect::>(), - ) - }) - .map(|(key, value)| BatchOp::Put { key, value }) - .collect(); - - let verify = get_keys_to_verify(&batch, args.read_verify_percent); - - #[allow(clippy::unwrap_used)] - let proposal = Arc::new(db.propose(batch).await.unwrap()); - proposal.commit().await?; - verify_keys(&db, verify).await?; - } - - let duration = start.elapsed(); - println!( - "Generated and inserted {} batches of size {keys} in {duration:?}", - args.number_of_batches - ); - Ok(()) + + // TODO replace + // let cfg = DbConfig::builder().truncate(true).build(); + + // let args = Args::parse(); + + // let db = Db::new("rev_db", cfg) + // .await + // .expect("db initiation should succeed"); + + // let keys = args.batch_size; + // let start = Instant::now(); + + // let mut rng = if let Some(seed) = args.seed { + // rand::rngs::StdRng::seed_from_u64(seed) + // } else { + // rand::rngs::StdRng::from_entropy() + // }; + + // for _ in 0..args.number_of_batches { + // let keylen = rng.gen_range(args.keylen.clone()); + // let valuelen = rng.gen_range(args.valuelen.clone()); + // let batch: Batch, Vec> = (0..keys) + // .map(|_| { + // ( + // rng.borrow_mut() + // .sample_iter(&Alphanumeric) + // .take(keylen) + // .collect::>(), + // rng.borrow_mut() + // .sample_iter(&Alphanumeric) + // .take(valuelen) + // .collect::>(), + // ) + // }) + // .map(|(key, value)| BatchOp::Put { key, value }) + // .collect(); + + // let verify = get_keys_to_verify(&batch, args.read_verify_percent); + + // #[allow(clippy::unwrap_used)] + // let proposal = db.propose(batch).await.unwrap(); + // proposal.commit().await?; + // verify_keys(&db, verify).await?; + // } + + // let duration = start.elapsed(); + // println!( + // "Generated and inserted {} batches of size {keys} in {duration:?}", + // args.number_of_batches + // ); + + // Ok(()) } -fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec> { +fn _get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec> { if pct == 0 { HashMap::new() } else { @@ -117,12 +117,12 @@ fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec>, ) -> Result<(), firewood::v2::api::Error> { if !verify.is_empty() { - let hash = db.root_hash().await?; + let hash = db.root_hash().await?.expect("root hash should exist"); let revision = db.revision(hash).await?; for (key, value) in verify { assert_eq!(Some(value), revision.val(key).await?); diff --git a/firewood/src/config.rs b/firewood/src/config.rs deleted file mode 100644 index 9cd35a477c62..000000000000 --- a/firewood/src/config.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -pub use crate::storage::{buffer::DiskBufferConfig, WalConfig}; -use typed_builder::TypedBuilder; - -/// Database configuration. -#[derive(Clone, TypedBuilder, Debug)] -pub struct DbConfig { - /// Maximum cached pages for the free list of the item stash. - #[builder(default = 16384)] // 64M total size by default - pub meta_ncached_pages: usize, - /// Maximum cached file descriptors for the free list of the item stash. - #[builder(default = 1024)] // 1K fds by default - pub meta_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub meta_file_nbit: u64, - /// Maximum cached pages for the item stash. This is the low-level cache used by the linear - /// store that holds Trie nodes and account objects. - #[builder(default = 262144)] // 1G total size by default - pub payload_ncached_pages: usize, - /// Maximum cached file descriptors for the item stash. - #[builder(default = 1024)] // 1K fds by default - pub payload_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub payload_file_nbit: u64, - /// Maximum steps of walk to recycle a freed item. - #[builder(default = 10)] - pub payload_max_walk: u64, - /// Region size in bits (should be not greater than `payload_file_nbit`). One file is - /// partitioned into multiple regions. Just use the default value. - #[builder(default = 22)] - pub payload_regn_nbit: u64, - /// Maximum cached pages for the free list of the item stash. - #[builder(default = 16384)] // 64M total size by default - pub root_hash_ncached_pages: usize, - /// Maximum cached file descriptors for the free list of the item stash. - #[builder(default = 1024)] // 1K fds by default - pub root_hash_ncached_files: usize, - /// Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - /// the power of 2 for the file size. - #[builder(default = 22)] // 4MB file by default - pub root_hash_file_nbit: u64, - /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its - /// existing contents will be lost. - #[builder(default = false)] - pub truncate: bool, - /// Config for accessing a version of the DB. - #[builder(default = DbRevConfig::builder().build())] - pub rev: DbRevConfig, - /// Config for the disk buffer. - #[builder(default = DiskBufferConfig::builder().build())] - pub buffer: DiskBufferConfig, - /// Config for Wal. - #[builder(default = WalConfig::builder().build())] - pub wal: WalConfig, -} - -/// Config for accessing a version of the DB. -#[derive(TypedBuilder, Clone, Debug)] -pub struct DbRevConfig { - /// Maximum cached Trie objects. - #[builder(default = 1 << 20)] - pub merkle_ncached_objs: usize, -} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 04d58d4f32a3..abd8809213b1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,73 +1,34 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -pub use crate::{ - config::{DbConfig, DbRevConfig}, - storage::{buffer::DiskBufferConfig, WalConfig}, - v2::api::{Batch, BatchOp, Proposal}, -}; -use crate::{ - file, - merkle::{ - Bincode, Key, Merkle, MerkleError, MerkleKeyValueStream, Proof, ProofError, TrieHash, - TRIE_HASH_LEN, - }, - storage::{ - buffer::{DiskBuffer, DiskBufferRequester}, - CachedStore, MemStoreR, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared, StoreWrite, - ZeroStore, PAGE_SIZE_NBIT, - }, - v2::api::{self, HashKey, KeyType, ValueType}, -}; -use crate::{ - merkle, - shale::{ - self, compact::StoreHeader, disk_address::DiskAddress, LinearStore, Obj, ShaleError, - Storable, StoreId, StoredView, - }, -}; -use aiofut::AioError; +use crate::merkle::MerkleError; +use crate::proof::{Proof, ProofNode}; +use crate::range_proof::RangeProof; +use crate::stream::MerkleKeyValueStream; +use crate::v2::api::{self, KeyType}; +pub use crate::v2::api::{Batch, BatchOp}; + +use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; -use bytemuck::{cast_slice, Pod, Zeroable}; - use metered::metered; -use parking_lot::{Mutex, RwLock}; -use std::{ - collections::VecDeque, - error::Error, - fmt, - io::{Cursor, ErrorKind, Write}, - mem::size_of, - num::NonZeroUsize, - ops::Deref, - os::fd::{AsFd, BorrowedFd}, - path::Path, - sync::Arc, - thread::JoinHandle, -}; -use tokio::task::block_in_place; - -mod proposal; - -use self::proposal::ProposalBase; - -const MERKLE_META_STORE_ID: StoreId = 0x0; -const MERKLE_PAYLOAD_STORE_ID: StoreId = 0x1; -const ROOT_HASH_STORE_ID: StoreId = 0x2; -const RESERVED_STORE_ID: u64 = 0x1000; - -const MAGIC_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; +use std::error::Error; +use std::fmt; +use std::io::Write; +use std::path::Path; +use std::sync::Arc; +use storage::{Committed, FileBacked, HashedNodeReader, NodeStore}; +use typed_builder::TypedBuilder; + +// TODO use or remove +const _VERSION_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; #[derive(Debug)] #[non_exhaustive] pub enum DbError { - Aio(AioError), InvalidParams, Merkle(MerkleError), System(nix::Error), - KeyNotFound, CreateError, - Shale(ShaleError), IO(std::io::Error), InvalidProposal, } @@ -75,14 +36,11 @@ pub enum DbError { impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - DbError::Aio(e) => write!(f, "aio error: {e:?}"), DbError::InvalidParams => write!(f, "invalid parameters provided"), DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), DbError::System(e) => write!(f, "system error: {e:?}"), - DbError::KeyNotFound => write!(f, "not found"), DbError::CreateError => write!(f, "database create error"), DbError::IO(e) => write!(f, "I/O error: {e:?}"), - DbError::Shale(e) => write!(f, "shale error: {e:?}"), DbError::InvalidProposal => write!(f, "invalid proposal"), } } @@ -94,893 +52,223 @@ impl From for DbError { } } -impl From for DbError { - fn from(e: ShaleError) -> Self { - DbError::Shale(e) - } -} - impl Error for DbError {} -/// DbParams contains the constants that are fixed upon the creation of the DB, this ensures the -/// correct parameters are used when the DB is opened later (the parameters here will override the -/// parameters in [DbConfig] if the DB already exists). -#[repr(C)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -struct DbParams { - magic: [u8; 16], - meta_file_nbit: u64, - payload_file_nbit: u64, - payload_regn_nbit: u64, - wal_file_nbit: u64, - wal_block_nbit: u64, - root_hash_file_nbit: u64, -} - -#[derive(Clone, Debug)] -/// Necessary linear store instances bundled for a `Store`. -struct SubUniverse { - meta: T, - payload: T, -} - -impl SubUniverse { - const fn new(meta: T, payload: T) -> Self { - Self { meta, payload } - } -} - -impl SubUniverse { - fn to_mem_store_r(&self) -> SubUniverse> { - SubUniverse { - meta: self.meta.inner().clone(), - payload: self.payload.inner().clone(), - } - } -} - -impl SubUniverse { - fn new_from_other(&self) -> SubUniverse { - SubUniverse { - meta: StoreRevMut::new_from_other(&self.meta), - payload: StoreRevMut::new_from_other(&self.payload), - } - } -} - -impl SubUniverse> { - fn rewind( - &self, - meta_writes: &[StoreWrite], - payload_writes: &[StoreWrite], - ) -> SubUniverse { - SubUniverse::new( - StoreRevShared::from_ash(self.meta.clone(), meta_writes), - StoreRevShared::from_ash(self.payload.clone(), payload_writes), - ) - } -} - -impl SubUniverse> { - fn to_mem_store_r(&self) -> SubUniverse> { - SubUniverse { - meta: self.meta.clone(), - payload: self.payload.clone(), - } - } -} - -fn get_sub_universe_from_deltas( - sub_universe: &SubUniverse>, - meta_delta: StoreDelta, - payload_delta: StoreDelta, -) -> SubUniverse { - SubUniverse::new( - StoreRevShared::from_delta(sub_universe.meta.clone(), meta_delta), - StoreRevShared::from_delta(sub_universe.payload.clone(), payload_delta), - ) -} - -fn get_sub_universe_from_empty_delta( - sub_universe: &SubUniverse>, -) -> SubUniverse { - get_sub_universe_from_deltas(sub_universe, StoreDelta::default(), StoreDelta::default()) -} - -/// mutable DB-wide metadata, it keeps track of the root of the top-level trie. -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct DbHeader { - sentinel_addr: DiskAddress, -} - -impl DbHeader { - pub const MSIZE: u64 = std::mem::size_of::() as u64; - - pub const fn new_empty() -> Self { - Self { - sentinel_addr: DiskAddress::null(), - } - } -} - -impl Storable for DbHeader { - fn deserialize(addr: usize, mem: &T) -> Result { - let root_bytes = mem - .get_view(addr, Self::MSIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::MSIZE, - })?; - let root_bytes = root_bytes.as_deref(); - let root_bytes = root_bytes.as_slice(); - - Ok(Self { - sentinel_addr: root_bytes - .try_into() - .expect("Self::MSIZE == DiskAddress:MSIZE"), - }) - } - - fn serialized_len(&self) -> u64 { - Self::MSIZE - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.sentinel_addr.to_le_bytes())?; - Ok(()) - } -} - -#[derive(Clone, Debug)] -/// Necessary linear store instances bundled for the state of the entire DB. -struct Universe { - merkle: SubUniverse, -} - -impl Universe { - fn to_mem_store_r(&self) -> Universe> { - Universe { - merkle: self.merkle.to_mem_store_r(), - } - } -} - -impl Universe { - fn new_from_other(&self) -> Universe { - Universe { - merkle: self.merkle.new_from_other(), - } - } -} - -impl Universe> { - fn to_mem_store_r(&self) -> Universe> { - Universe { - merkle: self.merkle.to_mem_store_r(), - } - } -} - -impl Universe> { - fn rewind( - &self, - merkle_meta_writes: &[StoreWrite], - merkle_payload_writes: &[StoreWrite], - ) -> Universe { - Universe { - merkle: self - .merkle - .rewind(merkle_meta_writes, merkle_payload_writes), - } - } -} - -/// Some readable version of the DB. -#[derive(Debug)] -pub struct DbRev { - header: shale::Obj, - merkle: Merkle, -} +type HistoricalRev = NodeStore; #[async_trait] -impl api::DbView for DbRev { - type Stream<'a> = MerkleKeyValueStream<'a, T, Bincode> where Self: 'a; - - async fn root_hash(&self) -> Result { - self.merkle - .root_hash(self.header.sentinel_addr) - .map(|h| *h) - .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) +impl api::DbView for HistoricalRev { + type Stream<'a> = MerkleKeyValueStream<'a, Self> where Self: 'a; + + async fn root_hash(&self) -> Result, api::Error> { + HashedNodeReader::root_hash(self).map_err(api::Error::IO) } - async fn val(&self, key: K) -> Result>, api::Error> { - let obj_ref = self.merkle.get(key, self.header.sentinel_addr); - match obj_ref { - Err(e) => Err(api::Error::IO(std::io::Error::new(ErrorKind::Other, e))), - Ok(obj) => Ok(obj.map(|inner| inner.deref().to_owned())), - } + async fn val(&self, _key: K) -> Result>, api::Error> { + todo!() } async fn single_key_proof( &self, - key: K, - ) -> Result>>, api::Error> { - self.merkle - .prove(key, self.header.sentinel_addr) - .map(Some) - .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) + _key: K, + ) -> Result>, api::Error> { + todo!() } async fn range_proof( &self, - first_key: Option, - last_key: Option, - limit: Option, - ) -> Result, Vec>>, api::Error> { - self.merkle - .range_proof(self.header.sentinel_addr, first_key, last_key, limit) - .await - .map_err(|e| api::Error::InternalError(Box::new(e))) + _first_key: Option, + _last_key: Option, + _limit: Option, + ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { + todo!() } fn iter_option( &self, - first_key: Option, + _first_key: Option, ) -> Result, api::Error> { - Ok(match first_key { - None => self.merkle.key_value_iter(self.header.sentinel_addr), - Some(key) => self - .merkle - .key_value_iter_from_key(self.header.sentinel_addr, key.as_ref().into()), - }) - } -} - -impl DbRev { - pub fn stream(&self) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { - self.merkle.key_value_iter(self.header.sentinel_addr) - } - - pub fn stream_from(&self, start_key: Key) -> merkle::MerkleKeyValueStream<'_, T, Bincode> { - self.merkle - .key_value_iter_from_key(self.header.sentinel_addr, start_key) - } - - /// Get root hash of the generic key-value storage. - pub fn kv_root_hash(&self) -> Result { - self.merkle - .root_hash(self.header.sentinel_addr) - .map_err(DbError::Merkle) - } - - /// Get a value associated with a key. - pub fn kv_get>(&self, key: K) -> Option> { - let obj_ref = self.merkle.get(key, self.header.sentinel_addr); - match obj_ref { - Err(_) => None, - Ok(obj) => obj.map(|o| o.to_vec()), - } - } - - /// Dump the Trie of the generic key-value storage. - pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.merkle - .dump(self.header.sentinel_addr, w) - .map_err(DbError::Merkle) - } - - pub fn prove>(&self, key: K) -> Result>, MerkleError> { - self.merkle.prove::(key, self.header.sentinel_addr) - } - - /// Verifies a range proof is valid for a set of keys. - pub fn verify_range_proof + Send, K: AsRef<[u8]>, V: AsRef<[u8]>>( - &self, - proof: Proof, - first_key: K, - last_key: K, - keys: Vec, - values: Vec, - ) -> Result { - let hash: [u8; 32] = *self.kv_root_hash()?; - let valid = - proof.verify_range_proof::(hash, first_key, last_key, keys, values)?; - Ok(valid) - } -} - -impl DbRev { - fn borrow_split(&mut self) -> (&mut shale::Obj, &mut Merkle) { - (&mut self.header, &mut self.merkle) - } - - fn flush_dirty(&mut self) -> Option<()> { - self.header.flush_dirty(); - self.merkle.flush_dirty()?; - Some(()) - } -} - -impl From> for DbRev { - fn from(mut value: DbRev) -> Self { - value.flush_dirty(); - DbRev { - header: value.header, - merkle: value.merkle.into(), - } - } -} - -#[derive(Debug)] -struct DbInner { - disk_requester: DiskBufferRequester, - disk_thread: Option>, - cached_store: Universe>, - // Whether to reset the store headers when creating a new store on top of the cached store. - reset_store_headers: bool, - root_hash_staging: StoreRevMut, -} - -impl Drop for DbInner { - fn drop(&mut self) { - self.disk_requester.shutdown(); - self.disk_thread.take().map(JoinHandle::join); - } -} - -#[async_trait] -impl api::Db for Db { - type Historical = DbRev; - - type Proposal = proposal::Proposal; - - async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { - let rev = self.get_revision(&TrieHash(root_hash)); - if let Some(rev) = rev { - Ok(Arc::new(rev)) - } else { - Err(api::Error::HashNotFound { - provided: root_hash, - }) - } - } - - async fn root_hash(&self) -> Result { - self.kv_root_hash().map(|hash| hash.0).map_err(Into::into) - } - - async fn propose( - &self, - batch: api::Batch, - ) -> Result { - self.new_proposal(batch).map_err(Into::into) - } -} - -#[derive(Debug)] -pub struct DbRevInner { - inner: VecDeque>, - root_hashes: VecDeque, - max_revisions: usize, - base: Universe, - base_revision: Arc>, -} - -/// Firewood database handle. + todo!() + } +} + +// impl HistoricalRev { +// pub fn stream(&self) -> MerkleKeyValueStream<'_, T> { +// todo!() +// } + +// pub fn stream_from(&self, _start_key: &[u8]) -> MerkleKeyValueStream<'_, T> { +// todo!() +// } + +// /// Get root hash of the generic key-value storage. +// pub fn kv_root_hash(&self) -> Result { +// todo!() +// } + +// /// Get a value associated with a key. +// pub fn get(&self, _key: &[u8]) -> Option> { +// todo!() +// } + +// /// Dump the Trie of the generic key-value storage. +// pub fn dump(&self, _w: &mut dyn Write) -> Result<(), DbError> { +// todo!() +// } + +// pub fn prove(&self, _key: &[u8]) -> Result, MerkleError> { +// todo!() +// } + +// /// Verifies a range proof is valid for a set of keys. +// pub fn verify_range_proof>( +// &self, +// _proof: &Proof, +// _first_key: &[u8], +// _last_key: &[u8], +// _keys: Vec<&[u8]>, +// _values: Vec, +// ) -> Result { +// todo!() +// } +// } + +/// TODO danlaine: implement +// pub struct Proposal { +// _proposal: T, +// } + +// #[async_trait] +// impl api::Proposal for Proposal { +// type Proposal = Proposal; + +// async fn commit(self: Arc) -> Result<(), api::Error> { +// todo!() +// } + +// async fn propose( +// self: Arc, +// _data: api::Batch, +// ) -> Result, api::Error> { +// todo!() +// } +// } + +// #[async_trait] +// impl api::DbView for Proposal { +// type Stream<'a> = MerkleKeyValueStream<'a, T> where T: 'a; + +// async fn root_hash(&self) -> Result { +// todo!() +// } + +// async fn val(&self, _key: K) -> Result>, api::Error> +// where +// K: api::KeyType, +// { +// todo!() +// } + +// async fn single_key_proof(&self, _key: K) -> Result>, api::Error> +// where +// K: api::KeyType, +// { +// todo!() +// } + +// async fn range_proof( +// &self, +// _first_key: Option, +// _last_key: Option, +// _limit: Option, +// ) -> Result, Vec, ProofNode>>, api::Error> +// where +// K: api::KeyType, +// { +// todo!(); +// } + +// fn iter_option( +// &self, +// _first_key: Option, +// ) -> Result, api::Error> { +// todo!() +// } +// } + +/// Database configuration. +#[derive(Clone, TypedBuilder, Debug)] +pub struct DbConfig { + /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its + /// existing contents will be lost. + #[builder(default = false)] + pub truncate: bool, + #[builder(default = RevisionManagerConfig::builder().build())] + pub manager: RevisionManagerConfig, +} + +/// TODO danlaine: implement #[derive(Debug)] pub struct Db { - inner: Arc>, - revisions: Arc>>, - payload_regn_nbit: u64, metrics: Arc, - cfg: DbConfig, + _manager: RevisionManager, } -#[metered(registry = DbMetrics, visibility = pub)] -impl Db { - const PARAM_SIZE: u64 = size_of::() as u64; - - pub async fn new>(db_path: P, cfg: &DbConfig) -> Result { - if cfg.truncate { - let _ = tokio::fs::remove_dir_all(db_path.as_ref()).await; - } - - #[cfg(feature = "logger")] - // initialize the logger, but ignore if this fails. This could fail because the calling - // library already initialized the logger or if you're opening a second database - let _ = env_logger::try_init(); - - block_in_place(|| Db::new_internal(db_path, cfg.clone())) - .map_err(|e| api::Error::InternalError(Box::new(e))) - } - - /// Open a database. - fn new_internal>(db_path: P, cfg: DbConfig) -> Result { - let open_options = if cfg.truncate { - file::Options::Truncate - } else { - file::Options::NoTruncate - }; - - let (db_path, reset_store_headers) = file::open_dir(db_path, open_options)?; +// #[async_trait] +// impl api::Db for Db { +// type Historical = HistoricalRev; - let merkle_path = file::touch_dir("merkle", &db_path)?; - let merkle_meta_path = file::touch_dir("meta", &merkle_path)?; - let merkle_payload_path = file::touch_dir("compact", &merkle_path)?; - let root_hash_path = file::touch_dir("root_hash", &db_path)?; +// type Proposal = Proposal; - let meta_file = crate::file::File::new(0, RESERVED_STORE_ID, &merkle_meta_path)?; - let meta_fd = meta_file.as_fd(); +// async fn revision( +// &self, +// _root_hash: HashKey, +// ) -> Result>, api::Error> { +// let store = self.manager.revision(_root_hash)?; +// Ok(Arc::new(HistoricalRev:: { +// _historical: store, +// })) +// } - if reset_store_headers { - // initialize DbParams - if cfg.payload_file_nbit < cfg.payload_regn_nbit - || cfg.payload_regn_nbit < PAGE_SIZE_NBIT - { - return Err(DbError::InvalidParams); - } - Self::initialize_header_on_disk(&cfg, meta_fd)?; - } - - // read DbParams - let mut header_bytes = [0; size_of::()]; - nix::sys::uio::pread(meta_fd, &mut header_bytes, 0).map_err(DbError::System)?; - drop(meta_file); - #[allow(clippy::indexing_slicing)] - let params: DbParams = cast_slice(&header_bytes)[0]; - - let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); - let disk_requester = DiskBufferRequester::new(sender); - - let wal_config = WalConfig::builder() - .file_nbit(params.wal_file_nbit) - .block_nbit(params.wal_block_nbit) - .max_revisions(cfg.wal.max_revisions) - .build(); - - let disk_buffer = - DiskBuffer::new(inbound, &cfg.buffer, &wal_config).map_err(DbError::Aio)?; - - let disk_thread = Some( - std::thread::Builder::new() - .name("DiskBuffer".to_string()) - .spawn(move || disk_buffer.run()) - .expect("thread spawn should succeed"), - ); - - // set up caches - #[allow(clippy::unwrap_used)] - let root_hash_cache: Arc = CachedStore::new( - &StoreConfig::builder() - .ncached_pages(cfg.root_hash_ncached_pages) - .ncached_files(cfg.root_hash_ncached_files) - .store_id(ROOT_HASH_STORE_ID) - .file_nbit(params.root_hash_file_nbit) - .rootdir(root_hash_path) - .build(), - disk_requester.clone(), - ) - .unwrap() - .into(); - - #[allow(clippy::unwrap_used)] - let data_cache = Universe { - merkle: SubUniverse::>::new( - CachedStore::new( - &StoreConfig::builder() - .ncached_pages(cfg.meta_ncached_pages) - .ncached_files(cfg.meta_ncached_files) - .store_id(MERKLE_META_STORE_ID) - .file_nbit(params.meta_file_nbit) - .rootdir(merkle_meta_path) - .build(), - disk_requester.clone(), - ) - .unwrap() - .into(), - CachedStore::new( - &StoreConfig::builder() - .ncached_pages(cfg.payload_ncached_pages) - .ncached_files(cfg.payload_ncached_files) - .store_id(MERKLE_PAYLOAD_STORE_ID) - .file_nbit(params.payload_file_nbit) - .rootdir(merkle_payload_path) - .build(), - disk_requester.clone(), - ) - .unwrap() - .into(), - ), - }; +// async fn root_hash(&self) -> Result { +// Ok(self.manager.root_hash()?) +// } - [ - data_cache.merkle.meta.as_ref(), - data_cache.merkle.payload.as_ref(), - root_hash_cache.as_ref(), - ] - .into_iter() - .for_each(|cached_store| { - disk_requester.reg_cached_store(cached_store.id(), cached_store.clone_files()); - }); - - // recover from Wal - disk_requester.init_wal("wal", &db_path); - - let base = Universe { - merkle: get_sub_universe_from_empty_delta(&data_cache.merkle), - }; +// async fn propose( +// &self, +// _batch: api::Batch, +// ) -> Result, api::Error> { +// todo!() +// } +// } - // convert the base merkle objects into writable ones - let meta: StoreRevMut = base.merkle.meta.clone().into(); - let payload: StoreRevMut = base.merkle.payload.clone().into(); - - // get references to the DbHeader and the StoreHeader - // for free space management - let db_header_ref = Db::get_db_header_ref(&meta)?; - let merkle_payload_header_ref = - Db::get_payload_header_ref(&meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - let header_refs = (db_header_ref, merkle_payload_header_ref); - - let base_revision = Db::new_revision::( - header_refs, - (meta, payload), - params.payload_regn_nbit, - cfg.payload_max_walk, - &cfg.rev, +#[metered(registry = DbMetrics, visibility = pub)] +impl Db { + pub async fn new>(db_path: P, cfg: DbConfig) -> Result { + let metrics = DbMetrics::default().into(); + let manager = RevisionManager::new( + db_path.as_ref().to_path_buf(), + cfg.truncate, + cfg.manager.clone(), )?; - - Ok(Self { - inner: Arc::new(RwLock::new(DbInner { - disk_thread, - disk_requester, - cached_store: data_cache, - reset_store_headers, - root_hash_staging: StoreRevMut::new(root_hash_cache), - })), - revisions: Arc::new(Mutex::new(DbRevInner { - inner: VecDeque::new(), - root_hashes: VecDeque::new(), - max_revisions: cfg.wal.max_revisions as usize, - base, - base_revision: Arc::new(base_revision.into()), - })), - payload_regn_nbit: params.payload_regn_nbit, - metrics: Arc::new(DbMetrics::default()), - cfg: cfg.clone(), - }) - } - - fn initialize_header_on_disk(cfg: &DbConfig, fd0: BorrowedFd) -> Result<(), DbError> { - // The header consists of three parts: - // DbParams - // DbHeader (just a pointer to the sentinel) - // StoreHeader for future allocations - let (params, hdr, csh); - let header_bytes: Vec = { - params = DbParams { - magic: *MAGIC_STR, - meta_file_nbit: cfg.meta_file_nbit, - payload_file_nbit: cfg.payload_file_nbit, - payload_regn_nbit: cfg.payload_regn_nbit, - wal_file_nbit: cfg.wal.file_nbit, - wal_block_nbit: cfg.wal.block_nbit, - root_hash_file_nbit: cfg.root_hash_file_nbit, - }; - let bytes = bytemuck::bytes_of(¶ms); - bytes.iter() - } - .chain({ - // compute the DbHeader as bytes - hdr = DbHeader::new_empty(); - bytemuck::bytes_of(&hdr) - }) - .chain({ - // write out the StoreHeader - let store_reserved = NonZeroUsize::new(RESERVED_STORE_ID as usize) - .expect("RESERVED_STORE_ID is non-zero"); - csh = StoreHeader::new(store_reserved, store_reserved); - bytemuck::bytes_of(&csh) - }) - .copied() - .collect(); - - nix::sys::uio::pwrite(fd0, &header_bytes, 0).map_err(DbError::System)?; - Ok(()) - } - - /// Create a new mutable store and an alterable revision of the DB on top. - fn new_store( - &self, - cached_store: &Universe>, - reset_store_headers: bool, - ) -> Result<(Universe, DbRev), DbError> { - let mut offset = Db::PARAM_SIZE as usize; - let db_header: DiskAddress = DiskAddress::from(offset); - offset += DbHeader::MSIZE as usize; - let merkle_payload_header: DiskAddress = DiskAddress::from(offset); - offset += StoreHeader::SERIALIZED_LEN as usize; - assert!(offset <= RESERVED_STORE_ID as usize); - - let mut merkle_meta_store = StoreRevMut::new(cached_store.merkle.meta.clone()); - - if reset_store_headers { - // initialize store headers - #[allow(clippy::unwrap_used)] - merkle_meta_store.write( - merkle_payload_header.into(), - &shale::to_dehydrated(&shale::compact::StoreHeader::new( - NonZeroUsize::new(RESERVED_STORE_ID as usize).unwrap(), - #[allow(clippy::unwrap_used)] - NonZeroUsize::new(RESERVED_STORE_ID as usize).unwrap(), - ))?, - )?; - merkle_meta_store.write( - db_header.into(), - &shale::to_dehydrated(&DbHeader::new_empty())?, - )?; - } - - let store = Universe { - merkle: SubUniverse::new( - merkle_meta_store, - StoreRevMut::new(cached_store.merkle.payload.clone()), - ), + let db = Self { + metrics, + _manager: manager, }; - - let db_header_ref = Db::get_db_header_ref(&store.merkle.meta)?; - - let merkle_payload_header_ref = - Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - - let header_refs = (db_header_ref, merkle_payload_header_ref); - - let mut rev: DbRev = Db::new_revision( - header_refs, - (store.merkle.meta.clone(), store.merkle.payload.clone()), - self.payload_regn_nbit, - self.cfg.payload_max_walk, - &self.cfg.rev, - )?; - #[allow(clippy::unwrap_used)] - rev.flush_dirty().unwrap(); - - Ok((store, rev)) - } - - fn get_payload_header_ref( - meta_ref: &K, - header_offset: u64, - ) -> Result, DbError> { - let payload_header = DiskAddress::from(header_offset as usize); - StoredView::addr_to_obj( - meta_ref, - payload_header, - shale::compact::ChunkHeader::SERIALIZED_LEN, - ) - .map_err(Into::into) - } - - fn get_db_header_ref(meta_ref: &K) -> Result, DbError> { - let db_header = DiskAddress::from(Db::PARAM_SIZE as usize); - StoredView::addr_to_obj(meta_ref, db_header, DbHeader::MSIZE).map_err(Into::into) - } - - fn new_revision>( - header_refs: (Obj, Obj), - merkle: (T, T), - payload_regn_nbit: u64, - payload_max_walk: u64, - cfg: &DbRevConfig, - ) -> Result, DbError> { - // TODO: This should be a compile time check - const DB_OFFSET: u64 = Db::PARAM_SIZE; - let merkle_offset = DB_OFFSET + DbHeader::MSIZE; - assert!(merkle_offset + StoreHeader::SERIALIZED_LEN <= RESERVED_STORE_ID); - - let mut db_header_ref = header_refs.0; - let merkle_payload_header_ref = header_refs.1; - - let merkle_meta = merkle.0.into(); - let merkle_payload = merkle.1.into(); - - #[allow(clippy::unwrap_used)] - let merkle_store = shale::compact::Store::new( - merkle_meta, - merkle_payload, - merkle_payload_header_ref, - shale::ObjCache::new(cfg.merkle_ncached_objs), - payload_max_walk, - payload_regn_nbit, - ) - .unwrap(); - - let merkle = Merkle::new(merkle_store); - - if db_header_ref.sentinel_addr.is_null() { - let mut err = Ok(()); - // create the sentinel node - #[allow(clippy::unwrap_used)] - db_header_ref - .modify(|r| { - err = (|| { - r.sentinel_addr = merkle.init_sentinel()?; - Ok(()) - })(); - }) - .unwrap(); - err.map_err(DbError::Merkle)? - } - - Ok(DbRev { - header: db_header_ref, - merkle, - }) + Ok(db) } /// Create a proposal. - pub(crate) fn new_proposal( - &self, - data: Batch, - ) -> Result { - let mut inner = self.inner.write(); - let reset_store_headers = inner.reset_store_headers; - let (store, mut rev) = self.new_store(&inner.cached_store, reset_store_headers)?; - - // Flip the reset flag after resetting the store headers. - if reset_store_headers { - inner.reset_store_headers = false; - } - - data.into_iter().try_for_each(|op| -> Result<(), DbError> { - match op { - BatchOp::Put { key, value } => { - let (header, merkle) = rev.borrow_split(); - merkle - .insert(key, value.as_ref().to_vec(), header.sentinel_addr) - .map_err(DbError::Merkle)?; - Ok(()) - } - BatchOp::Delete { key } => { - let (header, merkle) = rev.borrow_split(); - merkle - .remove(key, header.sentinel_addr) - .map_err(DbError::Merkle)?; - Ok(()) - } - } - })?; - - // Calculated the root hash before flushing so it can be persisted. - let root_hash = rev.kv_root_hash()?; - #[allow(clippy::unwrap_used)] - rev.flush_dirty().unwrap(); - - let parent = ProposalBase::View(Arc::clone(&self.revisions.lock().base_revision)); - Ok(proposal::Proposal { - m: Arc::clone(&self.inner), - r: Arc::clone(&self.revisions), - cfg: self.cfg.clone(), - rev, - store, - committed: Arc::new(Mutex::new(false)), - root_hash, - parent, - }) - } - - /// Get a handle that grants the access to any committed state of the entire DB, - /// with a given root hash. If the given root hash matches with more than one - /// revisions, we use the most recent one as the trie are the same. - /// - /// If no revision with matching root hash found, returns None. - // #[measure([HitCount])] - pub fn get_revision(&self, root_hash: &TrieHash) -> Option> { - let mut revisions = self.revisions.lock(); - let inner_lock = self.inner.read(); - - // Find the revision index with the given root hash. - let mut nback = revisions.root_hashes.iter().position(|r| r == root_hash); - let rlen = revisions.root_hashes.len(); - - #[allow(clippy::unwrap_used)] - if nback.is_none() && rlen < revisions.max_revisions { - let ashes = inner_lock - .disk_requester - .collect_ash(revisions.max_revisions) - .ok() - .unwrap(); - - #[allow(clippy::indexing_slicing)] - (nback = ashes - .iter() - .skip(rlen) - .map(|ash| { - StoreRevShared::from_ash( - Arc::new(ZeroStore::default()), - #[allow(clippy::indexing_slicing)] - &ash.0[&ROOT_HASH_STORE_ID].redo, - ) - }) - .map(|root_hash_store| { - root_hash_store - .get_view(0, TRIE_HASH_LEN as u64) - .expect("get view failed") - .as_deref() - }) - .map(|data| TrieHash(data[..TRIE_HASH_LEN].try_into().unwrap())) - .position(|trie_hash| &trie_hash == root_hash)); - } - - let nback = nback?; - - let rlen = revisions.inner.len(); - if rlen < nback { - // TODO: Remove unwrap - #[allow(clippy::unwrap_used)] - let ashes = inner_lock.disk_requester.collect_ash(nback).ok().unwrap(); - for mut ash in ashes.into_iter().skip(rlen) { - for (_, a) in ash.0.iter_mut() { - a.undo.reverse() - } - - let u = match revisions.inner.back() { - Some(u) => u.to_mem_store_r().rewind( - #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_META_STORE_ID].undo, - #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_PAYLOAD_STORE_ID].undo, - ), - None => inner_lock.cached_store.to_mem_store_r().rewind( - #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_META_STORE_ID].undo, - #[allow(clippy::indexing_slicing)] - &ash.0[&MERKLE_PAYLOAD_STORE_ID].undo, - ), - }; - revisions.inner.push_back(u); - } - } - - let store = if nback == 0 { - &revisions.base - } else { - #[allow(clippy::indexing_slicing)] - &revisions.inner[nback - 1] - }; - // Release the lock after we find the revision - drop(inner_lock); - - #[allow(clippy::unwrap_used)] - let db_header_ref = Db::get_db_header_ref(&store.merkle.meta).unwrap(); - - #[allow(clippy::unwrap_used)] - let merkle_payload_header_ref = - Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE) - .unwrap(); - - let header_refs = (db_header_ref, merkle_payload_header_ref); - - #[allow(clippy::unwrap_used)] - Db::new_revision( - header_refs, - (store.merkle.meta.clone(), store.merkle.payload.clone()), - self.payload_regn_nbit, - 0, - &self.cfg.rev, - ) - .unwrap() - .into() - } - - /// Dump the Trie of the latest generic key-value storage. - pub fn kv_dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - self.revisions.lock().base_revision.kv_dump(w) - } - /// Get root hash of the latest generic key-value storage. - pub(crate) fn kv_root_hash(&self) -> Result { - self.revisions.lock().base_revision.kv_root_hash() + // pub fn new_proposal( + // &self, + // _data: Batch, + // ) -> Result, DbError> { + // todo!() + // } + + /// Dump the Trie of the latest revision. + pub fn dump(&self, _w: &mut dyn Write) -> Result<(), DbError> { + todo!() } pub fn metrics(&self) -> Arc { diff --git a/firewood/src/db/proposal.rs b/firewood/src/db/proposal.rs deleted file mode 100644 index 91703ad21ba7..000000000000 --- a/firewood/src/db/proposal.rs +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::{ - get_sub_universe_from_deltas, Db, DbConfig, DbError, DbHeader, DbInner, DbRev, DbRevInner, - Universe, MERKLE_META_STORE_ID, MERKLE_PAYLOAD_STORE_ID, ROOT_HASH_STORE_ID, -}; -use crate::merkle::{Bincode, MerkleKeyValueStream, Proof}; -use crate::shale::LinearStore; -use crate::storage::StoreRevShared; -use crate::{ - merkle::{TrieHash, TRIE_HASH_LEN}, - storage::{buffer::BufferWrite, AshRecord, StoreRevMut}, - v2::api::{self, Batch, BatchOp, KeyType, ValueType}, -}; -use async_trait::async_trait; -use parking_lot::{Mutex, RwLock}; -use std::{io::ErrorKind, sync::Arc}; -use tokio::task::block_in_place; - -/// An atomic batch of changes proposed against the latest committed revision, -/// or any existing [Proposal]. Multiple proposals can be created against the -/// latest committed revision at the same time. [Proposal] is immutable meaning -/// the internal batch cannot be altered after creation. Committing a proposal -/// invalidates all other proposals that are not children of the committed one. -pub struct Proposal { - // State of the Db - pub(super) m: Arc>, - pub(super) r: Arc>>, - pub(super) cfg: DbConfig, - - // State of the proposal - pub(super) rev: DbRev, - pub(super) store: Universe, - pub(super) committed: Arc>, - pub(super) root_hash: TrieHash, - - pub(super) parent: ProposalBase, -} - -pub enum ProposalBase { - Proposal(Arc), - View(Arc>), -} - -#[async_trait] -impl crate::v2::api::Proposal for Proposal { - type Proposal = Proposal; - - #[allow(clippy::unwrap_used)] - async fn commit(self: Arc) -> Result<(), api::Error> { - let proposal = Arc::::into_inner(self).unwrap(); - block_in_place(|| proposal.commit_sync().map_err(Into::into)) - } - - async fn propose( - self: Arc, - data: api::Batch, - ) -> Result { - self.propose_sync(data).map_err(Into::into) - } -} - -impl Proposal { - // Propose a new proposal from this proposal. The new proposal will be - // the child of it. - pub fn propose_sync( - self: Arc, - data: Batch, - ) -> Result { - let store = self.store.new_from_other(); - - let m = Arc::clone(&self.m); - let r = Arc::clone(&self.r); - let cfg = self.cfg.clone(); - - let db_header_ref = Db::get_db_header_ref(&store.merkle.meta)?; - - let merkle_payload_header_ref = - Db::get_payload_header_ref(&store.merkle.meta, Db::PARAM_SIZE + DbHeader::MSIZE)?; - - let header_refs = (db_header_ref, merkle_payload_header_ref); - - let mut rev = Db::new_revision( - header_refs, - (store.merkle.meta.clone(), store.merkle.payload.clone()), - cfg.payload_regn_nbit, - cfg.payload_max_walk, - &cfg.rev, - )?; - data.into_iter().try_for_each(|op| -> Result<(), DbError> { - match op { - BatchOp::Put { key, value } => { - let (header, merkle) = rev.borrow_split(); - merkle - .insert(key, value.as_ref().to_vec(), header.sentinel_addr) - .map_err(DbError::Merkle)?; - Ok(()) - } - BatchOp::Delete { key } => { - let (header, merkle) = rev.borrow_split(); - merkle - .remove(key, header.sentinel_addr) - .map_err(DbError::Merkle)?; - Ok(()) - } - } - })?; - - // Calculated the root hash before flushing so it can be persisted. - let hash = rev.kv_root_hash()?; - #[allow(clippy::unwrap_used)] - rev.flush_dirty().unwrap(); - - let parent = ProposalBase::Proposal(self); - - Ok(Proposal { - m, - r, - cfg, - rev, - store, - committed: Arc::new(Mutex::new(false)), - root_hash: hash, - parent, - }) - } - - /// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are - /// either retained on disk or lost together during a crash. - pub fn commit_sync(self) -> Result<(), DbError> { - let Self { - m, - r, - cfg: _, - rev, - store, - committed, - root_hash: hash, - parent, - } = self; - - let mut committed = committed.lock(); - if *committed { - return Ok(()); - } - - if let ProposalBase::Proposal(_p) = parent { - // p.commit_sync()?; - todo!(); - } - - // Check for if it can be committed - let mut revisions = r.lock(); - let committed_root_hash = revisions.base_revision.kv_root_hash().ok(); - let committed_root_hash = - committed_root_hash.expect("committed_root_hash should not be none"); - match &parent { - ProposalBase::Proposal(p) => { - if p.root_hash != committed_root_hash { - return Err(DbError::InvalidProposal); - } - } - ProposalBase::View(p) => { - let parent_root_hash = p.kv_root_hash().ok(); - let parent_root_hash = - parent_root_hash.expect("parent_root_hash should not be none"); - if parent_root_hash != committed_root_hash { - return Err(DbError::InvalidProposal); - } - } - }; - - // clear the staging layer and apply changes to the CachedStore - let (merkle_payload_redo, merkle_payload_wal) = store.merkle.payload.delta(); - let (merkle_meta_redo, merkle_meta_wal) = store.merkle.meta.delta(); - - let mut rev_inner = m.write(); - #[allow(clippy::unwrap_used)] - let merkle_meta_undo = rev_inner - .cached_store - .merkle - .meta - .update(&merkle_meta_redo) - .unwrap(); - #[allow(clippy::unwrap_used)] - let merkle_payload_undo = rev_inner - .cached_store - .merkle - .payload - .update(&merkle_payload_redo) - .unwrap(); - - // update the rolling window of past revisions - let latest_past = Universe { - merkle: get_sub_universe_from_deltas( - &rev_inner.cached_store.merkle, - merkle_meta_undo, - merkle_payload_undo, - ), - }; - - let max_revisions = revisions.max_revisions; - if let Some(rev) = revisions.inner.front_mut() { - rev.merkle - .meta - .set_base_store(latest_past.merkle.meta.inner().clone()); - rev.merkle - .payload - .set_base_store(latest_past.merkle.payload.inner().clone()); - } - revisions.inner.push_front(latest_past); - while revisions.inner.len() > max_revisions { - revisions.inner.pop_back(); - } - - revisions.base_revision = Arc::new(rev.into()); - - // update the rolling window of root hashes - revisions.root_hashes.push_front(hash); - if revisions.root_hashes.len() > max_revisions { - revisions - .root_hashes - .resize(max_revisions, TrieHash([0; TRIE_HASH_LEN])); - } - - rev_inner.root_hash_staging.write(0, &hash.0)?; - let (root_hash_redo, root_hash_wal) = rev_inner.root_hash_staging.delta(); - - // schedule writes to the disk - rev_inner.disk_requester.write( - Box::new([ - BufferWrite { - store_id: store.merkle.payload.id(), - delta: merkle_payload_redo, - }, - BufferWrite { - store_id: store.merkle.meta.id(), - delta: merkle_meta_redo, - }, - BufferWrite { - store_id: rev_inner.root_hash_staging.id(), - delta: root_hash_redo, - }, - ]), - AshRecord( - [ - (MERKLE_META_STORE_ID, merkle_meta_wal), - (MERKLE_PAYLOAD_STORE_ID, merkle_payload_wal), - (ROOT_HASH_STORE_ID, root_hash_wal), - ] - .into(), - ), - ); - *committed = true; - Ok(()) - } -} - -impl Proposal { - pub const fn get_revision(&self) -> &DbRev { - &self.rev - } -} - -#[async_trait] -impl api::DbView for Proposal { - type Stream<'a> = MerkleKeyValueStream<'a, StoreRevMut, Bincode>; - - async fn root_hash(&self) -> Result { - self.get_revision() - .kv_root_hash() - .map(|hash| hash.0) - .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) - } - async fn val(&self, key: K) -> Result>, api::Error> - where - K: api::KeyType, - { - // TODO: pass back errors from kv_get - Ok(self.get_revision().kv_get(key)) - } - - async fn single_key_proof(&self, key: K) -> Result>>, api::Error> - where - K: api::KeyType, - { - self.get_revision() - .prove(key) - .map(Some) - .map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e))) - } - - async fn range_proof( - &self, - _first_key: Option, - _last_key: Option, - _limit: Option, - ) -> Result, Vec>>, api::Error> - where - K: api::KeyType, - { - todo!(); - } - - fn iter_option( - &self, - first_key: Option, - ) -> Result, api::Error> { - let rev = self.get_revision(); - let iter = match first_key { - None => rev.stream(), - Some(first_key) => rev.stream_from(first_key.as_ref().into()), - }; - Ok(iter) - } -} diff --git a/firewood/src/file.rs b/firewood/src/file.rs deleted file mode 100644 index e1d5e45e1eec..000000000000 --- a/firewood/src/file.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -// Copied from CedrusDB - -use std::fs::{create_dir, remove_dir_all}; -use std::ops::Deref; -use std::os::fd::{AsRawFd, OwnedFd}; - -use std::path::{Path, PathBuf}; -use std::{io::ErrorKind, os::unix::prelude::OpenOptionsExt}; - -use nix::fcntl::Flockable; - -pub struct File { - fd: OwnedFd, -} - -impl AsRawFd for File { - fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd { - self.fd.as_raw_fd() - } -} - -// SAFETY: Docs for Flockable say it's safe if T is not Clone, -// and File is not clone -unsafe impl Flockable for File {} - -#[derive(PartialEq, Eq)] -pub enum Options { - Truncate, - NoTruncate, -} - -impl File { - pub fn open_file( - rootpath: PathBuf, - fname: &str, - options: Options, - ) -> Result { - let mut filepath = rootpath; - filepath.push(fname); - Ok(std::fs::File::options() - .truncate(options == Options::Truncate) - .read(true) - .write(true) - .mode(0o600) - .open(filepath)? - .into()) - } - - pub fn create_file(rootpath: PathBuf, fname: &str) -> Result { - let mut filepath = rootpath; - filepath.push(fname); - Ok(std::fs::File::options() - .create(true) - .truncate(true) - .read(true) - .write(true) - .mode(0o600) - .open(filepath)? - .into()) - } - - fn _get_fname(fid: u64) -> String { - format!("{fid:08x}.fw") - } - - pub fn new>(fid: u64, _flen: u64, rootdir: P) -> Result { - let fname = Self::_get_fname(fid); - let fd = match Self::open_file(rootdir.as_ref().to_path_buf(), &fname, Options::NoTruncate) - { - Ok(fd) => fd, - Err(e) => match e.kind() { - ErrorKind::NotFound => Self::create_file(rootdir.as_ref().to_path_buf(), &fname)?, - _ => return Err(e), - }, - }; - Ok(File { fd }) - } -} - -impl Deref for File { - type Target = OwnedFd; - - fn deref(&self) -> &Self::Target { - &self.fd - } -} - -pub(crate) fn touch_dir(dirname: &str, rootdir: &Path) -> Result { - let path = rootdir.join(dirname); - if let Err(e) = std::fs::create_dir(&path) { - // ignore already-exists error - if e.kind() != ErrorKind::AlreadyExists { - return Err(e); - } - } - Ok(path) -} - -pub(crate) fn open_dir>( - path: P, - options: Options, -) -> Result<(PathBuf, bool), std::io::Error> { - let truncate = options == Options::Truncate; - - if truncate { - let _ = remove_dir_all(path.as_ref()); - } - - match create_dir(path.as_ref()) { - Err(e) if truncate || e.kind() != ErrorKind::AlreadyExists => Err(e), - // the DB already exists - Err(_) => Ok((path.as_ref().to_path_buf(), false)), - Ok(_) => Ok((path.as_ref().to_path_buf(), true)), - } -} diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index c8f57a904da8..845ead1f80f5 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -105,7 +105,7 @@ //! store. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit //! virtual store into pages (yeah it appears to be more and more like an OS) and keep track of the //! dirty pages in some `CachedStore` instantiation (see `storage::StoreRevMut`). When a -//! [`db::Proposal`] commits, both the recorded interval writes and the aggregated in-memory +//! `db::Proposal` commits, both the recorded interval writes and the aggregated in-memory //! dirty pages induced by this write batch are taken out from the linear store. Although they are //! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, //! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL @@ -182,15 +182,11 @@ //! some very old ghost store to keep the size of the rolling window invariant. //! pub mod db; -pub(crate) mod file; +pub mod manager; pub mod merkle; -pub mod merkle_util; -pub mod storage; - -pub mod config; -pub mod nibbles; -// TODO: shale should not be pub, but there are integration test dependencies :( -pub mod shale; +pub mod proof; +pub mod range_proof; +pub mod stream; pub mod logger; pub mod v2; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs new file mode 100644 index 000000000000..336cf7399d23 --- /dev/null +++ b/firewood/src/manager.rs @@ -0,0 +1,191 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![allow(dead_code)] + +use std::io::Error; +use std::path::PathBuf; + +use typed_builder::TypedBuilder; + +use crate::v2::api::HashKey; + +use storage::FileBacked; + +#[derive(Clone, Debug, TypedBuilder)] +pub struct RevisionManagerConfig { + /// The number of historical revisions to keep in memory. + #[builder(default = 64)] + max_revisions: usize, +} + +#[derive(Debug)] +pub(crate) struct RevisionManager { + max_revisions: usize, + filebacked: FileBacked, + // historical: VecDeque>, + // proposals: Vec>, // TODO: Should be Vec> + // committing_proposals: VecDeque>, + // TODO: by_hash: HashMap + // TODO: maintain root hash of the most recent commit +} + +impl RevisionManager { + pub fn new( + filename: PathBuf, + truncate: bool, + config: RevisionManagerConfig, + ) -> Result { + Ok(Self { + max_revisions: config.max_revisions, + filebacked: FileBacked::new(filename, truncate)?, + // historical: Default::default(), + // proposals: Default::default(), + // committing_proposals: Default::default(), + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum RevisionManagerError { + #[error("The proposal cannot be committed since a sibling was committed")] + SiblingCommitted, + #[error( + "The proposal cannot be committed since it is not a direct child of the most recent commit" + )] + NotLatest, + #[error("An IO error occurred during the commit")] + IO(#[from] std::io::Error), +} + +impl RevisionManager { + // TODO fix this or remove it. It should take in a proposal. + fn commit(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { + todo!() + // // detach FileBacked from all revisions to make writes safe + // let new_historical = self.prepare_for_writes(&proposal)?; + + // // append this historical to the list of known historicals + // self.historical.push_back(new_historical); + + // // forget about older revisions + // while self.historical.len() > self.max_revisions { + // self.historical.pop_front(); + // } + + // // If we do copy on writes for underneath files, since we keep all changes + // // after bootstrapping, we should be able to read from the changes and the + // // read only file map to the state at bootstrapping. + // // We actually doesn't care whether the writes are successful or not + // // (crash recovery may need to be handled above) + // for write in proposal.new.iter() { + // self.filebacked.write(*write.0, write.1)?; + // } + + // self.writes_completed(proposal) + } + + // TODO fix or remove this. It should take in a proposal. + fn prepare_for_writes(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { + todo!() + // // check to see if we can commit this proposal + // let parent = proposal.parent(); + // match parent { + // LinearStoreParent::FileBacked(_) => { + // if !self.committing_proposals.is_empty() { + // return Err(RevisionManagerError::NotLatest); + // } + // } + // LinearStoreParent::Proposed(ref parent_proposal) => { + // let Some(last_commiting_proposal) = self.committing_proposals.back() else { + // return Err(RevisionManagerError::NotLatest); + // }; + // if !Arc::ptr_eq(parent_proposal, last_commiting_proposal) { + // return Err(RevisionManagerError::NotLatest); + // } + // } + // _ => return Err(RevisionManagerError::SiblingCommitted), + // } + // // checks complete: safe to commit + + // let new_historical = Arc::new(Historical::new( + // std::mem::take(&mut proposal.old.clone()), // TODO: remove clone + // parent.clone(), + // proposal.size()?, + // )); + + // // reparent the oldest historical to point to the new proposal + // if let Some(historical) = self.historical.back() { + // historical.reparent(new_historical.clone().into()); + // } + + // // for each outstanding proposal, see if their parent is the last committed linear store + // for candidate in self + // .proposals + // .iter() + // .filter(|&candidate| candidate.has_parent(&parent) && !Arc::ptr_eq(candidate, proposal)) + // { + // candidate.reparent(LinearStoreParent::Historical(new_historical.clone())); + // } + + // // mark this proposal as committing + // self.committing_proposals.push_back(proposal.clone()); + + // Ok(new_historical) + } + + // TODO fix or remove this. It should take in a proposal. + fn writes_completed(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { + todo!() + // // now that the committed proposal is on disk, reparent anything that pointed to our proposal, + // // which is now fully flushed to our parent, as our parent + // // TODO: This needs work when we support multiple simultaneous commit writes; we should + // // only do this work when the entire stack below us has been flushed + // let parent = proposal.parent(); + // let proposal = LinearStoreParent::Proposed(proposal); + // for candidate in self + // .proposals + // .iter() + // .filter(|&candidate| candidate.has_parent(&proposal)) + // { + // candidate.reparent(parent.clone()); + // } + + // // TODO: As of now, this is always what we just pushed, no support for multiple simultaneous + // // commits yet; the assert verifies this and should be removed when we add support for this + // let should_be_us = self + // .committing_proposals + // .pop_front() + // .expect("can't be empty"); + // assert!( + // matches!(proposal, LinearStoreParent::Proposed(us) if Arc::ptr_eq(&us, &should_be_us)) + // ); + + // // TODO: we should reparent fileback as the parent of this committed proposal?? + + // Ok(()) + } +} + +pub type NewProposalError = (); // TODO implement + +impl RevisionManager { + // TODO fix this or remove it. It should take in a proposal. + pub fn add_proposal(&mut self, _proposal: ()) { + todo!() + // self.proposals.push(proposal); + } + + pub fn revision(&self, _root_hash: HashKey) -> Result<(), RevisionManagerError> { + todo!() + } + + pub fn root_hash(&self) -> Result { + todo!() + } +} + +#[cfg(test)] +mod tests { + // TODO +} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 00bec2d6a2e4..b0b7071b59b5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,1355 +1,777 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::nibbles::Nibbles; -use crate::shale::compact::Store; -use crate::shale::LinearStore; -use crate::shale::{self, disk_address::DiskAddress, ObjWriteSizeError, ShaleError}; -use crate::storage::{StoreRevMut, StoreRevShared}; + +use crate::proof::{Proof, ProofError, ProofNode}; +use crate::range_proof::RangeProof; +use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; -use sha3::Digest; -use std::{ - collections::HashMap, future::ready, io::Write, iter::once, marker::PhantomData, sync::OnceLock, +use std::collections::HashSet; +use std::fmt::Debug; +use std::future::ready; +use std::io::Write; +use std::iter::once; +use std::num::NonZeroUsize; +use std::sync::Arc; +use storage::{ + BranchNode, Child, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, LinearAddress, + MutableProposal, NibblesIterator, Node, NodeReader, NodeStore, Path, ReadableStorage, TrieHash, + TrieReader, ValueDigest, }; -use thiserror::Error; - -mod node; -pub mod proof; -mod stream; -mod trie_hash; - -pub use node::{BinarySerde, Bincode, BranchNode, EncodedNode, LeafNode, Node, NodeType, Path}; -pub use proof::{Proof, ProofError}; -pub use stream::MerkleKeyValueStream; -pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; - -use self::stream::PathIterator; -type NodeObjRef<'a> = shale::ObjRef<'a, Node>; -type ParentRefs<'a> = Vec<(NodeObjRef<'a>, u8)>; -type ParentAddresses = Vec<(DiskAddress, u8)>; +use thiserror::Error; pub type Key = Box<[u8]>; -type Value = Vec; +pub type Value = Vec; #[derive(Debug, Error)] pub enum MerkleError { - #[error("merkle datastore error: {0:?}")] - Shale(#[from] ShaleError), + #[error("can't generate proof for empty node")] + Empty, #[error("read only")] ReadOnly, #[error("node not a branch node")] NotBranchNode, - #[error("format error: {0:?}")] - Format(#[from] std::io::Error), + #[error("IO error: {0:?}")] + IO(#[from] std::io::Error), #[error("parent should not be a leaf branch")] ParentLeafBranch, #[error("removing internal node references failed")] UnsetInternal, - #[error("error updating nodes: {0}")] - WriteError(#[from] ObjWriteSizeError), #[error("merkle serde error: {0}")] BinarySerdeError(String), + #[error("invalid utf8")] + UTF8Error, + #[error("node not found")] + NodeNotFound, } -macro_rules! write_node { - ($self: expr, $r: expr, $modify: expr, $parents: expr, $deleted: expr) => { - if let Err(_) = $r.write($modify) { - let ptr = $self.put_node($r.clone())?.as_addr(); - set_parent(ptr, $parents); - $deleted.push($r.as_addr()); - true - } else { - false - } - }; -} - -#[derive(Debug)] -pub struct Merkle { - store: Store, - phantom: PhantomData, -} - -impl From> for Merkle { - fn from(value: Merkle) -> Self { - let store = value.store.into(); - Merkle { - store, - phantom: PhantomData, - } - } -} - -impl Merkle { - pub fn get_node(&self, ptr: DiskAddress) -> Result { - self.store.get_item(ptr).map_err(Into::into) - } - - pub fn put_node(&self, node: Node) -> Result { - self.store.put_item(node, 0).map_err(Into::into) - } - - fn free_node(&mut self, ptr: DiskAddress) -> Result<(), MerkleError> { - self.store.free_item(ptr).map_err(Into::into) - } +// convert a set of nibbles into a printable string +// panics if there is a non-nibble byte in the set +fn nibbles_formatter>(nib: X) -> String { + nib.into_iter() + .map(|c| { + *b"0123456789abcdef" + .get(c as usize) + .expect("requires nibbles") as char + }) + .collect::() } -impl<'de, S, T> Merkle -where - S: LinearStore, - T: BinarySerde, - EncodedNode: serde::Serialize + serde::Deserialize<'de>, -{ - pub const fn new(store: Store) -> Self { - Self { - store, - phantom: PhantomData, +macro_rules! write_attributes { + ($writer:ident, $node:expr, $value:expr) => { + if !$node.partial_path.0.is_empty() { + write!( + $writer, + " pp={}", + nibbles_formatter($node.partial_path.0.clone()) + )?; } - } - - // TODO: use `encode` / `decode` instead of `node.encode` / `node.decode` after extention node removal. - #[allow(dead_code)] - fn encode(&self, node: &NodeType) -> Result, MerkleError> { - let encoded = match node { - NodeType::Leaf(n) => { - let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - EncodedNode { - partial_path: n.partial_path.clone(), - children, - value: n.value.clone().into(), - phantom: PhantomData, + #[allow(clippy::unnecessary_to_owned)] + if !$value.is_empty() { + match std::str::from_utf8($value) { + Ok(string) if string.chars().all(char::is_alphanumeric) => { + write!($writer, " val={}", string)? } - } - - NodeType::Branch(n) => { - // pair up DiskAddresses with encoded children and pick the right one - let encoded_children = n.chd().iter().zip(n.children_encoded.iter()); - let children = encoded_children - .map(|(child_addr, encoded_child)| { - child_addr - // if there's a child disk address here, get the encoded bytes - .map(|addr| { - self.get_node(addr) - .and_then(|node| self.encode(node.inner())) - }) - // or look for the pre-fetched bytes - .or_else(|| encoded_child.as_ref().map(|child| Ok(child.to_vec()))) - .transpose() - }) - .collect::>>, MerkleError>>()? - .try_into() - .expect("MAX_CHILDREN will always be yielded"); - - EncodedNode { - partial_path: n.partial_path.clone(), - children, - value: n.value.clone(), - phantom: PhantomData, + _ => { + write!($writer, " val={}", hex::encode($value))?; } } - }; - - T::serialize(&encoded).map_err(|e| MerkleError::BinarySerdeError(e.to_string())) - } - - #[allow(dead_code)] - fn decode(&self, buf: &'de [u8]) -> Result { - let encoded: EncodedNode = - T::deserialize(buf).map_err(|e| MerkleError::BinarySerdeError(e.to_string()))?; - - if encoded.children.iter().all(|b| b.is_none()) { - // This is a leaf node - return Ok(NodeType::Leaf(LeafNode::new( - encoded.partial_path, - encoded.value.expect("leaf nodes must always have a value"), - ))); } + }; +} - Ok(NodeType::Branch( - BranchNode { - partial_path: encoded.partial_path, - children: [None; BranchNode::MAX_CHILDREN], - value: encoded.value, - children_encoded: encoded.children, +/// Returns the value mapped to by `key` in the subtrie rooted at `node`. +fn get_helper( + nodestore: &T, + node: &Node, + key: &[u8], +) -> Result>, MerkleError> { + // 4 possibilities for the position of the `key` relative to `node`: + // 1. The node is at `key` + // 2. The key is above the node (i.e. its ancestor) + // 3. The key is below the node (i.e. its descendant) + // 4. Neither is an ancestor of the other + let path_overlap = PrefixOverlap::from(key, node.partial_path()); + let unique_key = path_overlap.unique_a; + let unique_node = path_overlap.unique_b; + + match ( + unique_key.split_first().map(|(index, path)| (*index, path)), + unique_node.split_first(), + ) { + (_, Some(_)) => { + // Case (2) or (4) + Ok(None) + } + (None, None) => Ok(Some(Arc::new(node.clone()))), // 1. The node is at `key` + (Some((child_index, remaining_key)), None) => { + // 3. The key is below the node (i.e. its descendant) + match node { + Node::Leaf(_) => Ok(None), + Node::Branch(node) => match node + .children + .get(child_index as usize) + .expect("index is in bounds") + { + None => Ok(None), + Some(Child::Node(ref child)) => get_helper(nodestore, child, remaining_key), + Some(Child::AddressWithHash(addr, _)) => { + let child = nodestore.read_node(*addr)?; + get_helper(nodestore, &child, remaining_key) + } + }, } - .into(), - )) + } } } -impl Merkle { - /// Creates the sentinel node, puts it into the store, and returns its address. - pub fn init_sentinel(&self) -> Result { - self.store - .put_item( - Node::from_branch(BranchNode { - partial_path: vec![].into(), - children: [None; BranchNode::MAX_CHILDREN], - value: None, - children_encoded: Default::default(), - }), - Node::max_branch_node_size(), - ) - .map_err(MerkleError::Shale) - .map(|node| node.as_addr()) +#[derive(Debug)] +pub struct Merkle { + nodestore: T, +} + +impl From for Merkle { + fn from(nodestore: T) -> Self { + Merkle { nodestore } } +} - pub fn empty_root() -> &'static TrieHash { - static V: OnceLock = OnceLock::new(); - #[allow(clippy::unwrap_used)] - V.get_or_init(|| { - TrieHash( - hex::decode("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - .unwrap() - .try_into() - .unwrap(), - ) - }) +impl Merkle { + pub fn root(&self) -> Option> { + self.nodestore.root_node() } - pub fn root_hash(&self, sentinel_addr: DiskAddress) -> Result { - let root = self - .get_node(sentinel_addr)? - .inner - .as_branch() - .ok_or(MerkleError::NotBranchNode)? - .children[0]; - Ok(if let Some(root) = root { - let mut node = self.get_node(root)?; - let res = *node.get_root_hash(&self.store); - #[allow(clippy::unwrap_used)] - if node.is_dirty() { - node.write(|_| {}).unwrap(); - node.set_dirty(false); - } - res - } else { - *Self::empty_root() - }) + pub const fn nodestore(&self) -> &T { + &self.nodestore } - fn dump_(&self, u: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { - let u_ref = self.get_node(u)?; + pub(crate) fn read_node(&self, addr: LinearAddress) -> Result, MerkleError> { + self.nodestore.read_node(addr).map_err(Into::into) + } - let hash = match u_ref.root_hash.get() { - Some(h) => h, - None => u_ref.get_root_hash(&self.store), + /// Returns a proof that the given key has a certain value, + /// or that the key isn't in the trie. + pub fn prove(&self, key: &[u8]) -> Result, MerkleError> { + let Some(root) = self.root() else { + return Err(MerkleError::Empty); }; - write!(w, "{u:?} => {}: ", hex::encode(**hash))?; + // Get the path to the key + let path_iter = self.path_iter(key)?; + let mut proof = Vec::new(); + for node in path_iter { + let node = node?; + proof.push(ProofNode::from(node)); + } - match &u_ref.inner { - NodeType::Branch(n) => { - writeln!(w, "{n:?}")?; - for c in n.children.iter().flatten() { - self.dump_(*c, w)? + if proof.is_empty() { + // No nodes, even the root, are before `key`. + // The root alone proves the non-existence of `key`. + // TODO reduce duplicate code with ProofNode::from + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + if let Some(branch) = root.as_branch() { + // TODO danlaine: can we avoid indexing? + #[allow(clippy::indexing_slicing)] + for (i, hash) in branch.children_iter() { + child_hashes[i] = Some(hash.clone()); } } - #[allow(clippy::unwrap_used)] - NodeType::Leaf(n) => writeln!(w, "{n:?}").unwrap(), + + proof.push(ProofNode { + key: root.partial_path().bytes(), + value_digest: root + .value() + .map(|value| ValueDigest::Value(value.to_vec().into_boxed_slice())), + child_hashes, + }) } - Ok(()) + Ok(Proof(proof.into_boxed_slice())) } - pub fn dump(&self, sentinel_addr: DiskAddress, w: &mut dyn Write) -> Result<(), MerkleError> { - if sentinel_addr.is_null() { - write!(w, "")?; - } else { - self.dump_(sentinel_addr, w)?; - }; - Ok(()) + pub fn verify_range_proof>( + &self, + _proof: &Proof, + _first_key: &[u8], + _last_key: &[u8], + _keys: Vec<&[u8]>, + _vals: Vec, + ) -> Result { + todo!() } - pub fn insert>( - &mut self, - key: K, - val: Vec, - sentinel_addr: DiskAddress, - ) -> Result<(), MerkleError> { - let (parents, deleted) = self.insert_and_return_updates(key, val, sentinel_addr)?; - - for mut r in parents { - r.write(|u| u.rehash())?; - } + pub fn path_iter<'a>(&self, key: &'a [u8]) -> Result, MerkleError> { + PathIterator::new(&self.nodestore, key) + } - for ptr in deleted { - self.free_node(ptr)? - } + pub(crate) fn _key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { + MerkleKeyValueStream::from(&self.nodestore) + } - Ok(()) + pub(crate) fn _key_value_iter_from_key(&self, key: Key) -> MerkleKeyValueStream<'_, T> { + // TODO danlaine: change key to &[u8] + MerkleKeyValueStream::_from_key(&self.nodestore, key) } - fn insert_and_return_updates>( + pub(super) async fn _range_proof( &self, - key: K, - val: Vec, - sentinel_addr: DiskAddress, - ) -> Result<(impl Iterator, Vec), MerkleError> { - // as we split a node, we need to track deleted nodes and parents - let mut deleted = Vec::new(); - let mut parents = Vec::new(); - - // we use Nibbles::<1> so that 1 zero nibble is at the front - // this is for the sentinel node, which avoids moving the root - // and always only has one child - let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); - - let mut node = self.get_node(sentinel_addr)?; - - // walk down the merkle tree starting from next_node, currently the root - // return None if the value is inserted - let next_node_and_val = loop { - let Some(mut next_nibble) = key_nibbles.next() else { - break Some((node, val)); - }; - - let (node_ref, next_node_ptr) = match &node.inner { - // For a Branch node, we look at the child pointer. If it points - // to another node, we walk down that. Otherwise, we can store our - // value as a leaf and we're done - NodeType::Leaf(n) => { - // TODO: avoid extra allocation - let key_remainder = once(next_nibble) - .chain(key_nibbles.clone()) - .collect::>(); - - let overlap = PrefixOverlap::from(&n.partial_path, &key_remainder); - - #[allow(clippy::indexing_slicing)] - match (overlap.unique_a.len(), overlap.unique_b.len()) { - // same node, overwrite the value - (0, 0) => { - self.update_value_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - val, - )?; - } - - // new node is a child of the old node - (0, _) => { - let (new_leaf_index, new_leaf_path) = { - let (index, path) = overlap.unique_b.split_at(1); - (index[0], path.to_vec()) - }; - - let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); - - let new_leaf = self.put_node(new_leaf)?.as_addr(); - - let mut children = [None; BranchNode::MAX_CHILDREN]; - children[new_leaf_index as usize] = Some(new_leaf); - - let new_branch = BranchNode { - partial_path: Path(overlap.shared.to_vec()), - children, - value: n.value.clone().into(), - children_encoded: Default::default(), - }; - - let new_branch = Node::from_branch(new_branch); - - let new_branch = self.put_node(new_branch)?.as_addr(); - - set_parent(new_branch, &mut parents); - - deleted.push(node.as_addr()); - } - - // old node is a child of the new node - (_, 0) => { - let (old_leaf_index, old_leaf_path) = { - let (index, path) = overlap.unique_a.split_at(1); - (index[0], path.to_vec()) - }; - - let new_branch_path = overlap.shared.to_vec(); - - let old_leaf = self - .update_path_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - Path(old_leaf_path.to_vec()), - )? - .as_addr(); - - let mut new_branch = BranchNode { - partial_path: Path(new_branch_path), - children: [None; BranchNode::MAX_CHILDREN], - value: Some(val), - children_encoded: Default::default(), - }; - - new_branch.children[old_leaf_index as usize] = Some(old_leaf); - - let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_addr(); - - set_parent(node, &mut parents); - } - - // nodes are siblings - _ => { - let (old_leaf_index, old_leaf_path) = { - let (index, path) = overlap.unique_a.split_at(1); - (index[0], path.to_vec()) - }; - - let (new_leaf_index, new_leaf_path) = { - let (index, path) = overlap.unique_b.split_at(1); - (index[0], path.to_vec()) - }; - - let new_branch_path = overlap.shared.to_vec(); - - let old_leaf = self - .update_path_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - Path(old_leaf_path.to_vec()), - )? - .as_addr(); - - let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); - - let new_leaf = self.put_node(new_leaf)?.as_addr(); - - let mut new_branch = BranchNode { - partial_path: Path(new_branch_path), - children: [None; BranchNode::MAX_CHILDREN], - value: None, - children_encoded: Default::default(), - }; - - new_branch.children[old_leaf_index as usize] = Some(old_leaf); - new_branch.children[new_leaf_index as usize] = Some(new_leaf); - - let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_addr(); - - set_parent(node, &mut parents); - } - } - - break None; - } - - NodeType::Branch(n) if n.partial_path.len() == 0 => { - #[allow(clippy::indexing_slicing)] - match n.children[next_nibble as usize] { - Some(c) => (node, c), - None => { - // insert the leaf to the empty slot - // create a new leaf - let leaf_ptr = self - .put_node(Node::from_leaf(LeafNode::new( - Path(key_nibbles.collect()), - val, - )))? - .as_addr(); - - // set the current child to point to this leaf - #[allow(clippy::indexing_slicing)] - node.write(|node| { - node.as_branch_mut().children[next_nibble as usize] = - Some(leaf_ptr); - node.rehash(); - })?; - - break None; - } - } - } - - NodeType::Branch(n) => { - // TODO: avoid extra allocation - let key_remainder = once(next_nibble) - .chain(key_nibbles.clone()) - .collect::>(); - - let overlap = PrefixOverlap::from(&n.partial_path, &key_remainder); - - #[allow(clippy::indexing_slicing)] - match (overlap.unique_a.len(), overlap.unique_b.len()) { - // same node, overwrite the value - (0, 0) => { - self.update_value_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - val, - )?; - break None; - } - - // new node is a child of the old node - (0, _) => { - let (new_leaf_index, new_leaf_path) = { - let (index, path) = overlap.unique_b.split_at(1); - (index[0], path) - }; - - (0..overlap.shared.len()).for_each(|_| { - key_nibbles.next(); - }); - - next_nibble = new_leaf_index; - - match n.children[next_nibble as usize] { - Some(ptr) => (node, ptr), - None => { - let new_leaf = Node::from_leaf(LeafNode::new( - Path(new_leaf_path.to_vec()), - val, - )); - - let new_leaf = self.put_node(new_leaf)?.as_addr(); - - #[allow(clippy::indexing_slicing)] - node.write(|node| { - node.as_branch_mut().children[next_nibble as usize] = - Some(new_leaf); - node.rehash(); - })?; - - break None; - } - } - } - - // old node is a child of the new node - (_, 0) => { - let (old_branch_index, old_branch_path) = { - let (index, path) = overlap.unique_a.split_at(1); - (index[0], path.to_vec()) - }; - - let new_branch_path = overlap.shared.to_vec(); - - let old_branch = self - .update_path_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - Path(old_branch_path.to_vec()), - )? - .as_addr(); - - let mut new_branch = BranchNode { - partial_path: Path(new_branch_path), - children: [None; BranchNode::MAX_CHILDREN], - value: Some(val), - children_encoded: Default::default(), - }; - - new_branch.children[old_branch_index as usize] = Some(old_branch); + start_key: Option<&[u8]>, + end_key: Option<&[u8]>, + limit: Option, + ) -> Result, Box<[u8]>, ProofNode>, api::Error> { + if let (Some(k1), Some(k2)) = (&start_key, &end_key) { + if k1 > k2 { + return Err(api::Error::InvalidRange { + start_key: k1.to_vec().into(), + end_key: k2.to_vec().into(), + }); + } + } - let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_addr(); + let mut stream = match start_key { + // TODO: fix the call-site to force the caller to do the allocation + Some(key) => self._key_value_iter_from_key(key.to_vec().into_boxed_slice()), + None => self._key_value_iter(), + }; - set_parent(node, &mut parents); + // fetch the first key from the stream + let first_result = stream.next().await; - break None; - } + // transpose the Option> to Result, E> + // If this is an error, the ? operator will return it + let Some((first_key, first_value)) = first_result.transpose()? else { + // The trie is empty. + if start_key.is_none() && end_key.is_none() { + // The caller requested a range proof over an empty trie. + return Err(api::Error::RangeProofOnEmptyTrie); + } - // nodes are siblings - _ => { - let (old_branch_index, old_branch_path) = { - let (index, path) = overlap.unique_a.split_at(1); - (index[0], path.to_vec()) - }; + let start_proof = start_key + .map(|start_key| self.prove(start_key)) + .transpose()?; - let (new_leaf_index, new_leaf_path) = { - let (index, path) = overlap.unique_b.split_at(1); - (index[0], path.to_vec()) - }; + let end_proof = end_key.map(|end_key| self.prove(end_key)).transpose()?; - let new_branch_path = overlap.shared.to_vec(); + return Ok(RangeProof { + start_proof, + key_values: Box::new([]), + end_proof, + }); + }; - let old_branch = self - .update_path_and_move_node_if_larger( - (&mut parents, &mut deleted), - node, - Path(old_branch_path.to_vec()), - )? - .as_addr(); + let start_proof = self.prove(&first_key)?; + let limit = limit.map(|old_limit| old_limit.get() - 1); - let new_leaf = Node::from_leaf(LeafNode::new(Path(new_leaf_path), val)); + let mut key_values = vec![(first_key, first_value.into_boxed_slice())]; - let new_leaf = self.put_node(new_leaf)?.as_addr(); + // we stop streaming if either we hit the limit or the key returned was larger + // than the largest key requested + #[allow(clippy::unwrap_used)] + key_values.extend( + stream + .take(limit.unwrap_or(usize::MAX)) + .take_while(|kv| { + // no last key asked for, so keep going + let Some(last_key) = end_key else { + return ready(true); + }; - let mut new_branch = BranchNode { - partial_path: Path(new_branch_path), - children: [None; BranchNode::MAX_CHILDREN], - value: None, - children_encoded: Default::default(), - }; + // return the error if there was one + let Ok(kv) = kv else { + return ready(true); + }; - new_branch.children[old_branch_index as usize] = Some(old_branch); - new_branch.children[new_leaf_index as usize] = Some(new_leaf); + // keep going if the key returned is less than the last key requested + ready(&*kv.0 <= last_key) + }) + .map(|kv| kv.map(|(k, v)| (k, v.into()))) + .try_collect::, Box<[u8]>)>>() + .await?, + ); - let node = Node::from_branch(new_branch); - let node = self.put_node(node)?.as_addr(); + let end_proof = key_values + .last() + .map(|(largest_key, _)| self.prove(largest_key)) + .transpose()?; - set_parent(node, &mut parents); + debug_assert!(end_proof.is_some()); - break None; - } - } - } - }; + Ok(RangeProof { + start_proof: Some(start_proof), + key_values: key_values.into(), + end_proof, + }) + } - // push another parent, and follow the next pointer - parents.push((node_ref, next_nibble)); - node = self.get_node(next_node_ptr)?; + pub fn get_value(&self, key: &[u8]) -> Result>, MerkleError> { + let Some(node) = self.get_node(key)? else { + return Ok(None); }; - - if let Some((mut node, val)) = next_node_and_val { - // we walked down the tree and reached the end of the key, - // but haven't inserted the value yet - let mut info = None; - let u_ptr = { - write_node!( - self, - node, - |u| { - info = match &mut u.inner { - NodeType::Branch(n) => { - n.value = Some(val); - None - } - NodeType::Leaf(n) => { - if n.partial_path.len() == 0 { - n.value = val; - - None - } else { - #[allow(clippy::indexing_slicing)] - let idx = n.partial_path[0]; - #[allow(clippy::indexing_slicing)] - (n.partial_path = Path(n.partial_path[1..].to_vec())); - u.rehash(); - - Some((idx, true, None, val)) - } - } - }; - - u.rehash() - }, - &mut parents, - &mut deleted - ); - - node.as_addr() - }; - - if let Some((idx, more, ext, val)) = info { - let mut chd = [None; BranchNode::MAX_CHILDREN]; - - let c_ptr = if more { - u_ptr - } else { - deleted.push(u_ptr); - #[allow(clippy::unwrap_used)] - ext.unwrap() - }; - - #[allow(clippy::indexing_slicing)] - (chd[idx as usize] = Some(c_ptr)); - - let branch = self - .put_node(Node::from_branch(BranchNode { - partial_path: vec![].into(), - children: chd, - value: Some(val), - children_encoded: Default::default(), - }))? - .as_addr(); - - set_parent(branch, &mut parents); - } - } - - Ok((parents.into_iter().rev().map(|(node, _)| node), deleted)) + Ok(node.value().map(|v| v.to_vec().into_boxed_slice())) } - pub fn remove>( - &mut self, - key: K, - sentinel_addr: DiskAddress, - ) -> Result>, MerkleError> { - if sentinel_addr.is_null() { + pub fn get_node(&self, key: &[u8]) -> Result>, MerkleError> { + let Some(root) = self.root() else { return Ok(None); - } - - let mut deleted = Vec::new(); - - let value = { - let (node, mut parents) = - self.get_node_and_parents_by_key(self.get_node(sentinel_addr)?, key)?; - - let Some(mut node) = node else { - return Ok(None); - }; - - let value = match &node.inner { - NodeType::Branch(branch) => { - let value = branch.value.clone(); - if value.is_none() { - return Ok(None); - } - - let children: Vec<_> = branch - .children - .iter() - .enumerate() - .filter_map(|(i, child)| child.map(|child| (i, child))) - .collect(); - - // don't change the sentinel node - if children.len() == 1 && !parents.is_empty() { - let branch_path = &branch.partial_path.0; - - #[allow(clippy::indexing_slicing)] - let (child_index, child) = children[0]; - let mut child = self.get_node(child)?; - - child.write(|child| { - let child_path = child.inner.path_mut(); - let path = branch_path - .iter() - .copied() - .chain(once(child_index as u8)) - .chain(child_path.0.iter().copied()) - .collect(); - *child_path = Path(path); - - child.rehash(); - })?; - - set_parent(child.as_addr(), &mut parents); - - deleted.push(node.as_addr()); - } else { - node.write(|node| { - node.as_branch_mut().value = None; - node.rehash(); - })? - } - - value - } - - NodeType::Leaf(n) => { - let value = Some(n.value.clone()); - - // TODO: handle unwrap better - deleted.push(node.as_addr()); - - let (mut parent, child_index) = parents.pop().expect("parents is never empty"); - - #[allow(clippy::indexing_slicing)] - parent.write(|parent| { - parent.as_branch_mut().children[child_index as usize] = None; - })?; - - let branch = parent - .inner - .as_branch() - .expect("parents are always branch nodes"); - - let children: Vec<_> = branch - .children - .iter() - .enumerate() - .filter_map(|(i, child)| child.map(|child| (i, child))) - .collect(); - - match (children.len(), &branch.value, !parents.is_empty()) { - // node is invalid, all single-child nodes should have a value - (1, None, true) => { - let parent_path = &branch.partial_path.0; - - #[allow(clippy::indexing_slicing)] - let (child_index, child) = children[0]; - let child = self.get_node(child)?; - - // TODO: - // there's an optimization here for when the paths are the same length - // and that clone isn't great but ObjRef causes problems - // we can't write directly to the child because we could be changing its size - let new_child = match child.inner.clone() { - NodeType::Branch(mut child) => { - let path = parent_path - .iter() - .copied() - .chain(once(child_index as u8)) - .chain(child.partial_path.0.iter().copied()) - .collect(); - - child.partial_path = Path(path); - - Node::from_branch(child) - } - NodeType::Leaf(mut child) => { - let path = parent_path - .iter() - .copied() - .chain(once(child_index as u8)) - .chain(child.partial_path.0.iter().copied()) - .collect(); - - child.partial_path = Path(path); - - Node::from_leaf(child) - } - }; - - let child = self.put_node(new_child)?.as_addr(); - - set_parent(child, &mut parents); - - deleted.push(parent.as_addr()); - } - - // branch nodes shouldn't have no children - (0, Some(value), true) => { - let leaf = Node::from_leaf(LeafNode::new( - Path(branch.partial_path.0.clone()), - value.clone(), - )); - - let leaf = self.put_node(leaf)?.as_addr(); - set_parent(leaf, &mut parents); - - deleted.push(parent.as_addr()); - } - - _ => parent.write(|parent| parent.rehash())?, - } - - value - } - }; - - for (mut parent, _) in parents { - parent.write(|u| u.rehash())?; - } - - value }; - for ptr in deleted.into_iter() { - self.free_node(ptr)?; - } - - Ok(value) + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); + get_helper(&self.nodestore, &root, &key) } +} - fn remove_tree_( +impl Merkle { + pub fn dump_node( &self, - u: DiskAddress, - deleted: &mut Vec, + addr: LinearAddress, + hash: Option<&TrieHash>, + seen: &mut HashSet, + writer: &mut dyn Write, ) -> Result<(), MerkleError> { - let u_ref = self.get_node(u)?; - match &u_ref.inner { - NodeType::Branch(n) => { - for c in n.children.iter().flatten() { - self.remove_tree_(*c, deleted)? + write!(writer, " {addr}[label=\"addr:{addr:?} hash:{hash:?}")?; + + match &*self.read_node(addr)? { + Node::Branch(b) => { + write_attributes!(writer, b, &b.value.clone().unwrap_or(Box::from([]))); + writeln!(writer, "\"]")?; + for (childidx, child) in b.children.iter().enumerate() { + let (child_addr, child_hash) = match child { + None => continue, + Some(Child::Node(_)) => continue, // TODO + Some(Child::AddressWithHash(addr, hash)) => (*addr, Some(hash)), + }; + + let inserted = seen.insert(child_addr); + if !inserted { + // We have already seen this child, which shouldn't happen. + // Indicate this with a red edge. + writeln!( + writer, + " {addr} -> {child_addr}[label=\"{childidx} (dup)\" color=red]" + )?; + } else { + writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]")?; + self.dump_node(child_addr, child_hash, seen, writer)?; + } } } - NodeType::Leaf(_) => (), - } - deleted.push(u); + Node::Leaf(l) => { + write_attributes!(writer, l, &l.value); + writeln!(writer, "\" shape=rect]")?; + } + }; Ok(()) } - pub fn remove_tree(&mut self, sentinel_addr: DiskAddress) -> Result<(), MerkleError> { - let mut deleted = Vec::new(); - if sentinel_addr.is_null() { - return Ok(()); + pub fn dump(&self) -> Result { + let mut result = vec![]; + writeln!(result, "digraph Merkle {{")?; + if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { + writeln!(result, " root -> {root_addr}")?; + let mut seen = HashSet::new(); + self.dump_node(root_addr, Some(&root_hash), &mut seen, &mut result)?; } - self.remove_tree_(sentinel_addr, &mut deleted)?; - for ptr in deleted.into_iter() { - self.free_node(ptr)?; - } - Ok(()) - } + write!(result, "}}")?; - fn get_node_by_key<'a, K: AsRef<[u8]>>( - &'a self, - node_ref: NodeObjRef<'a>, - key: K, - ) -> Result>, MerkleError> { - let key = key.as_ref(); - let path_iter = self.path_iter(node_ref, key); - - match path_iter.last() { - None => Ok(None), - Some(Err(e)) => Err(e), - Some(Ok((node_key, node))) => { - let key_nibbles = Nibbles::<0>::new(key).into_iter(); - if key_nibbles.eq(node_key.iter().copied()) { - Ok(Some(node)) - } else { - Ok(None) - } - } - } + Ok(String::from_utf8_lossy(&result).to_string()) } +} - fn get_node_and_parents_by_key<'a, K: AsRef<[u8]>>( - &'a self, - node_ref: NodeObjRef<'a>, - key: K, - ) -> Result<(Option>, ParentRefs<'a>), MerkleError> { - let mut parents = Vec::new(); - let node_ref = self.get_node_by_key_with_callbacks( - node_ref, - key, - |_, _| {}, - |node_ref, nib| { - parents.push((node_ref, nib)); - }, - )?; - - Ok((node_ref, parents)) +impl From>> + for Merkle> +{ + fn from(m: Merkle>) -> Self { + Merkle { + nodestore: m.nodestore.into(), + } } +} - fn get_node_and_parent_addresses_by_key<'a, K: AsRef<[u8]>>( - &'a self, - node_ref: NodeObjRef<'a>, - key: K, - ) -> Result<(Option>, ParentAddresses), MerkleError> { - let mut parents = Vec::new(); - let node_ref = self.get_node_by_key_with_callbacks( - node_ref, - key, - |_, _| {}, - |node_ref, nib| { - parents.push((node_ref.into_ptr(), nib)); - }, - )?; - - Ok((node_ref, parents)) +impl Merkle> { + pub fn hash(self) -> Merkle> { + self.into() } - fn get_node_by_key_with_callbacks<'a, K: AsRef<[u8]>>( - &'a self, - mut node_ref: NodeObjRef<'a>, - key: K, - mut start_loop_callback: impl FnMut(DiskAddress, u8), - mut end_loop_callback: impl FnMut(NodeObjRef<'a>, u8), - ) -> Result>, MerkleError> { - let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter(); - - loop { - let Some(mut nib) = key_nibbles.next() else { - break; - }; - - start_loop_callback(node_ref.as_addr(), nib); + /// Map `key` to `value` in the trie. + pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - let next_ptr = match &node_ref.inner { - #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) if n.partial_path.len() == 0 => { - match n.children[nib as usize] { - Some(c) => c, - None => return Ok(None), - } - } - NodeType::Branch(n) => { - let mut n_path_iter = n.partial_path.iter().copied(); + let root = self.nodestore.mut_root(); - if n_path_iter.next() != Some(nib) { - return Ok(None); - } - - let path_matches = n_path_iter - .map(Some) - .all(|n_path_nibble| key_nibbles.next() == n_path_nibble); - - if !path_matches { - return Ok(None); - } - - nib = if let Some(nib) = key_nibbles.next() { - nib - } else { - return Ok(if n.value.is_some() { - Some(node_ref) - } else { - None - }); - }; - - #[allow(clippy::indexing_slicing)] - match n.children[nib as usize] { - Some(c) => c, - None => return Ok(None), - } - } - NodeType::Leaf(n) => { - let node_ref = if once(nib) - .chain(key_nibbles) - .eq(n.partial_path.iter().copied()) - { - Some(node_ref) - } else { - None - }; - - return Ok(node_ref); - } - }; - - end_loop_callback(node_ref, nib); - - node_ref = self.get_node(next_ptr)?; - } - - // when we're done iterating over nibbles, check if the node we're at has a value - let node_ref = match &node_ref.inner { - NodeType::Branch(n) if n.value.as_ref().is_some() && n.partial_path.is_empty() => { - Some(node_ref) - } - NodeType::Leaf(n) if n.partial_path.len() == 0 => Some(node_ref), - _ => None, + let Some(root_node) = std::mem::take(root) else { + // The trie is empty. Create a new leaf node with `value` and set + // it as the root. + let root_node = Node::Leaf(LeafNode { + partial_path: key, + value, + }); + *root = root_node.into(); + return Ok(()); }; - Ok(node_ref) + let root_node = self.insert_helper(root_node, key.as_ref(), value)?; + *self.nodestore.mut_root() = root_node.into(); + Ok(()) } - pub fn get_mut>( + /// Map `key` to `value` into the subtrie rooted at `node`. + /// Returns the new root of the subtrie. + pub fn insert_helper( &mut self, - key: K, - sentinel_addr: DiskAddress, - ) -> Result>, MerkleError> { - if sentinel_addr.is_null() { - return Ok(None); - } - - let (ptr, parents) = { - let root_node = self.get_node(sentinel_addr)?; - let (node_ref, parents) = self.get_node_and_parent_addresses_by_key(root_node, key)?; - - (node_ref.map(|n| n.into_ptr()), parents) - }; - - Ok(ptr.map(|ptr| RefMut::new(ptr, parents, self))) - } - - /// Constructs a merkle proof for key. The result contains all encoded nodes - /// on the path to the value at key. The value itself is also included in the - /// last node and can be retrieved by verifying the proof. - /// - /// If the trie does not contain a value for key, the returned proof contains - /// all nodes of the longest existing prefix of the key, ending with the node - /// that proves the absence of the key (at least the root node). - pub fn prove( - &self, - key: K, - sentinel_addr: DiskAddress, - ) -> Result>, MerkleError> - where - K: AsRef<[u8]>, - { - let mut proofs = HashMap::new(); - if sentinel_addr.is_null() { - return Ok(Proof(proofs)); - } - - let sentinel_node = self.get_node(sentinel_addr)?; - - let path_iter = self.path_iter(sentinel_node, key.as_ref()); - - let nodes = path_iter - .map(|result| result.map(|(_, node)| node)) - .collect::, MerkleError>>()?; + mut node: Node, + key: &[u8], + value: Box<[u8]>, + ) -> Result { + // 4 possibilities for the position of the `key` relative to `node`: + // 1. The node is at `key` + // 2. The key is above the node (i.e. its ancestor) + // 3. The key is below the node (i.e. its descendant) + // 4. Neither is an ancestor of the other + let path_overlap = PrefixOverlap::from(key, node.partial_path().as_ref()); + + let unique_key = path_overlap.unique_a; + let unique_node = path_overlap.unique_b; + + match ( + unique_key + .split_first() + .map(|(index, path)| (*index, path.into())), + unique_node + .split_first() + .map(|(index, path)| (*index, path.into())), + ) { + (None, None) => { + // 1. The node is at `key` + node.update_value(value); + Ok(node) + } + (None, Some((child_index, partial_path))) => { + // 2. The key is above the node (i.e. its ancestor) + // Make a new branch node and insert the current node as a child. + // ... ... + // | --> | + // node key + // | + // node + let mut branch = BranchNode { + partial_path: path_overlap.shared.into(), + value: Some(value), + children: Default::default(), + }; - // Get the hashes of the nodes. - for node in nodes.into_iter() { - let encoded = node.get_encoded(&self.store); - let hash: [u8; TRIE_HASH_LEN] = sha3::Keccak256::digest(encoded).into(); - proofs.insert(hash, encoded.to_vec()); - } - Ok(Proof(proofs)) - } + // Shorten the node's partial path since it has a new parent. + node.update_partial_path(partial_path); + branch.update_child(child_index, Some(Child::Node(node))); - pub fn get>( - &self, - key: K, - sentinel_addr: DiskAddress, - ) -> Result, MerkleError> { - if sentinel_addr.is_null() { - return Ok(None); - } - - let root_node = self.get_node(sentinel_addr)?; - let node_ref = self.get_node_by_key(root_node, key)?; + Ok(Node::Branch(Box::new(branch))) + } + (Some((child_index, partial_path)), None) => { + // 3. The key is below the node (i.e. its descendant) + // ... ... + // | | + // node --> node + // | | + // ... (key may be below) ... (key is below) + match node { + Node::Branch(ref mut branch) => { + #[allow(clippy::indexing_slicing)] + let child = match std::mem::take(&mut branch.children[child_index as usize]) + { + None => { + // There is no child at this index. + // Create a new leaf and put it here. + let new_leaf = Node::Leaf(LeafNode { + value, + partial_path, + }); + branch.update_child(child_index, Some(Child::Node(new_leaf))); + return Ok(node); + } + Some(Child::Node(child)) => child, + Some(Child::AddressWithHash(addr, _)) => { + let node = self.nodestore.read_node(addr)?; + self.nodestore.delete_node(addr); + (*node).clone() + } + }; - Ok(node_ref.map(Ref)) - } + let child = self.insert_helper(child, partial_path.as_ref(), value)?; + branch.update_child(child_index, Some(Child::Node(child))); + Ok(node) + } + Node::Leaf(ref mut leaf) => { + // Turn this node into a branch node and put a new leaf as a child. + let mut branch = BranchNode { + partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), + value: Some(std::mem::take(&mut leaf.value)), + children: Default::default(), + }; - pub fn flush_dirty(&self) -> Option<()> { - self.store.flush_dirty() - } + let new_leaf = Node::Leaf(LeafNode { + value, + partial_path, + }); - pub fn path_iter<'a, 'b>( - &'a self, - sentinel_node: NodeObjRef<'a>, - key: &'b [u8], - ) -> PathIterator<'_, 'b, S, T> { - PathIterator::new(self, sentinel_node, key) - } + branch.update_child(child_index, Some(Child::Node(new_leaf))); - pub(crate) fn key_value_iter( - &self, - sentinel_addr: DiskAddress, - ) -> MerkleKeyValueStream<'_, S, T> { - MerkleKeyValueStream::new(self, sentinel_addr) - } + Ok(Node::Branch(Box::new(branch))) + } + } + } + (Some((key_index, key_partial_path)), Some((node_index, node_partial_path))) => { + // 4. Neither is an ancestor of the other + // ... ... + // | | + // node --> branch + // | | \ + // node key + // Make a branch node that has both the current node and a new leaf node as children. + let mut branch = BranchNode { + partial_path: path_overlap.shared.into(), + value: None, + children: Default::default(), + }; - pub(crate) fn key_value_iter_from_key( - &self, - sentinel_addr: DiskAddress, - key: Key, - ) -> MerkleKeyValueStream<'_, S, T> { - MerkleKeyValueStream::from_key(self, sentinel_addr, key) - } + node.update_partial_path(node_partial_path); + branch.update_child(node_index, Some(Child::Node(node))); - pub(super) async fn range_proof( - &self, - sentinel_addr: DiskAddress, - first_key: Option, - last_key: Option, - limit: Option, - ) -> Result, Vec>>, api::Error> { - if let (Some(k1), Some(k2)) = (&first_key, &last_key) { - if k1.as_ref() > k2.as_ref() { - return Err(api::Error::InvalidRange { - first_key: k1.as_ref().to_vec(), - last_key: k2.as_ref().to_vec(), + let new_leaf = Node::Leaf(LeafNode { + value, + partial_path: key_partial_path, }); - } - } + branch.update_child(key_index, Some(Child::Node(new_leaf))); - // limit of 0 is always an empty RangeProof - if limit == Some(0) { - return Ok(None); + Ok(Node::Branch(Box::new(branch))) + } } + } - let mut stream = match first_key { - // TODO: fix the call-site to force the caller to do the allocation - Some(key) => self - .key_value_iter_from_key(sentinel_addr, key.as_ref().to_vec().into_boxed_slice()), - None => self.key_value_iter(sentinel_addr), - }; - - // fetch the first key from the stream - let first_result = stream.next().await; + /// Removes the value associated with the given `key`. + /// Returns the value that was removed, if any. + /// Otherwise returns `None`. + pub fn remove(&mut self, key: &[u8]) -> Result>, MerkleError> { + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - // transpose the Option> to Result, E> - // If this is an error, the ? operator will return it - let Some((first_key, first_value)) = first_result.transpose()? else { - // nothing returned, either the trie is empty or the key wasn't found + let root = self.nodestore.mut_root(); + let Some(root_node) = std::mem::take(root) else { + // The trie is empty. There is nothing to remove. return Ok(None); }; - let first_key_proof = self - .prove(&first_key, sentinel_addr) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; - let limit = limit.map(|old_limit| old_limit - 1); - - let mut middle = vec![(first_key.into_vec(), first_value)]; - - // we stop streaming if either we hit the limit or the key returned was larger - // than the largest key requested - #[allow(clippy::unwrap_used)] - middle.extend( - stream - .take(limit.unwrap_or(usize::MAX)) - .take_while(|kv_result| { - // no last key asked for, so keep going - let Some(last_key) = last_key.as_ref() else { - return ready(true); - }; - - // return the error if there was one - let Ok(kv) = kv_result else { - return ready(true); - }; - - // keep going if the key returned is less than the last key requested - ready(&*kv.0 <= last_key.as_ref()) - }) - .map(|kv_result| kv_result.map(|(k, v)| (k.into_vec(), v))) - .try_collect::, Vec)>>() - .await?, - ); + let (root_node, removed_value) = self.remove_helper(root_node, &key)?; + *self.nodestore.mut_root() = root_node; + Ok(removed_value) + } - // remove the last key from middle and do a proof on it - let last_key_proof = match middle.last() { - None => { - return Ok(Some(api::RangeProof { - first_key_proof: first_key_proof.clone(), - middle: vec![], - last_key_proof: first_key_proof, - })) + /// Removes the value associated with the given `key` from the subtrie rooted at `node`. + /// Returns the new root of the subtrie and the value that was removed, if any. + #[allow(clippy::type_complexity)] + fn remove_helper( + &mut self, + mut node: Node, + key: &[u8], + ) -> Result<(Option, Option>), MerkleError> { + // 4 possibilities for the position of the `key` relative to `node`: + // 1. The node is at `key` + // 2. The key is above the node (i.e. its ancestor) + // 3. The key is below the node (i.e. its descendant) + // 4. Neither is an ancestor of the other + let path_overlap = PrefixOverlap::from(key, node.partial_path().as_ref()); + + let unique_key = path_overlap.unique_a; + let unique_node = path_overlap.unique_b; + + match ( + unique_key + .split_first() + .map(|(index, path)| (*index, Path::from(path))), + unique_node.split_first(), + ) { + (_, Some(_)) => { + // Case (2) or (4) + Ok((Some(node), None)) } - Some((last_key, _)) => self - .prove(last_key, sentinel_addr) - .map_err(|e| api::Error::InternalError(Box::new(e)))?, - }; + (None, None) => { + // 1. The node is at `key` + match &mut node { + Node::Branch(ref mut branch) => { + let Some(removed_value) = branch.value.take() else { + // The branch has no value. Return the node as is. + return Ok((Some(node), None)); + }; - Ok(Some(api::RangeProof { - first_key_proof, - middle, - last_key_proof, - })) - } + // This branch node has a value. + // If it has multiple children, return the node as is. + // Otherwise, its only child becomes the root of this subtrie. + let mut children_iter = + branch + .children + .iter_mut() + .enumerate() + .filter_map(|(index, child)| { + child.as_mut().map(|child| (index, child)) + }); + + let (child_index, child) = children_iter + .next() + .expect("branch node must have children"); + + if children_iter.next().is_some() { + // The branch has more than 1 child so it can't be removed. + Ok((Some(node), Some(removed_value))) + } else { + // The branch's only child becomes the root of this subtrie. + let mut child = match child { + Child::Node(ref mut child_node) => std::mem::take(child_node), + Child::AddressWithHash(addr, _) => { + let node = self.nodestore.read_node(*addr)?; + self.nodestore.delete_node(*addr); + (*node).clone() + } + }; - /// Try to update the [NodeObjRef]'s path in-place. If the update fails because the node can no longer fit at its old address, - /// then the old address is marked for deletion and the [Node] (with its update) is inserted at a new address. - fn update_path_and_move_node_if_larger<'a>( - &'a self, - (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), - mut node: NodeObjRef<'a>, - path: Path, - ) -> Result, MerkleError> { - let write_result = node.write(|node| { - node.inner_mut().set_path(path); - node.rehash(); - }); - - self.move_node_if_write_failed((parents, to_delete), node, write_result) - } + // The child's partial path is the concatenation of its (now removed) parent, + // its (former) child index, and its partial path. + match child { + Node::Branch(ref mut child_branch) => { + let partial_path = Path::from_nibbles_iterator( + branch + .partial_path + .iter() + .copied() + .chain(once(child_index as u8)) + .chain(child_branch.partial_path.iter().copied()), + ); + child_branch.partial_path = partial_path; + } + Node::Leaf(ref mut leaf) => { + let partial_path = Path::from_nibbles_iterator( + branch + .partial_path + .iter() + .copied() + .chain(once(child_index as u8)) + .chain(leaf.partial_path.iter().copied()), + ); + leaf.partial_path = partial_path; + } + } - /// Try to update the [NodeObjRef]'s value in-place. If the update fails because the node can no longer fit at its old address, - /// then the old address is marked for deletion and the [Node] (with its update) is inserted at a new address. - fn update_value_and_move_node_if_larger<'a>( - &'a self, - (parents, to_delete): (&mut [(NodeObjRef, u8)], &mut Vec), - mut node: NodeObjRef<'a>, - value: Vec, - ) -> Result { - let write_result = node.write(|node| { - node.inner_mut().set_value(value); - node.rehash(); - }); - - self.move_node_if_write_failed((parents, to_delete), node, write_result) - } + let node_partial_path = + std::mem::replace(&mut branch.partial_path, Path::new()); - /// Checks if the `write_result` is an [ObjWriteSizeError]. If it is, then the `node` is moved to a new address and the old address is marked for deletion. - fn move_node_if_write_failed<'a>( - &'a self, - (parents, deleted): (&mut [(NodeObjRef, u8)], &mut Vec), - mut node: NodeObjRef<'a>, - write_result: Result<(), ObjWriteSizeError>, - ) -> Result, MerkleError> { - if let Err(ObjWriteSizeError) = write_result { - let old_node_address = node.as_addr(); - node = self.put_node(node.into_inner())?; - deleted.push(old_node_address); - - set_parent(node.as_addr(), parents); - } + let partial_path = Path::from_nibbles_iterator( + branch + .partial_path + .iter() + .chain(once(&(child_index as u8))) + .chain(node_partial_path.iter()) + .copied(), + ); - Ok(node) - } -} + node.update_partial_path(partial_path); -fn set_parent(new_chd: DiskAddress, parents: &mut [(NodeObjRef, u8)]) { - #[allow(clippy::unwrap_used)] - let (p_ref, idx) = parents.last_mut().unwrap(); - #[allow(clippy::unwrap_used)] - p_ref - .write(|p| { - match &mut p.inner { - #[allow(clippy::indexing_slicing)] - NodeType::Branch(pp) => pp.children[*idx as usize] = Some(new_chd), - _ => unreachable!(), + Ok((Some(child), Some(removed_value))) + } + } + Node::Leaf(leaf) => { + let removed_value = std::mem::take(&mut leaf.value); + Ok((None, Some(removed_value))) + } + } } - p.rehash(); - }) - .unwrap(); -} + (Some((child_index, child_partial_path)), None) => { + // 3. The key is below the node (i.e. its descendant) + match node { + Node::Leaf(ref mut leaf) => Ok((None, Some(std::mem::take(&mut leaf.value)))), + Node::Branch(ref mut branch) => { + #[allow(clippy::indexing_slicing)] + let child = match std::mem::take(&mut branch.children[child_index as usize]) + { + None => { + return Ok((Some(node), None)); + } + Some(Child::Node(node)) => node, + Some(Child::AddressWithHash(addr, _)) => { + let node = self.nodestore.read_node(addr)?; + self.nodestore.delete_node(addr); + (*node).clone() + } + }; -pub struct Ref<'a>(NodeObjRef<'a>); + let (child, removed_value) = + self.remove_helper(child, child_partial_path.as_ref())?; -pub struct RefMut<'a, S, T> { - ptr: DiskAddress, - parents: ParentAddresses, - merkle: &'a mut Merkle, -} + if let Some(child) = child { + branch.update_child(child_index, Some(Child::Node(child))); + } else { + branch.update_child(child_index, None); + } -impl<'a> std::ops::Deref for Ref<'a> { - type Target = [u8]; - #[allow(clippy::unwrap_used)] - fn deref(&self) -> &[u8] { - match &self.0.inner { - NodeType::Branch(n) => n.value.as_ref().unwrap(), - NodeType::Leaf(n) => &n.value, - } - } -} + let mut children_iter = + branch + .children + .iter_mut() + .enumerate() + .filter_map(|(index, child)| { + child.as_mut().map(|child| (index, child)) + }); + + let Some((child_index, child)) = children_iter.next() else { + // The branch has no children. Turn it into a leaf. + let leaf = Node::Leaf(LeafNode { + value: branch.value.take().expect( + "branch node must have a value if it previously had only 1 child", + ), + partial_path: branch.partial_path.clone(), // TODO remove clone + }); + return Ok((Some(leaf), removed_value)); + }; -impl<'a, S, T> RefMut<'a, S, T> { - fn new(ptr: DiskAddress, parents: ParentAddresses, merkle: &'a mut Merkle) -> Self { - Self { - ptr, - parents, - merkle, - } - } -} + if children_iter.next().is_some() { + // The branch has more than 1 child. Return the branch. + return Ok((Some(node), removed_value)); + } -impl<'a, S: LinearStore, T> RefMut<'a, S, T> { - #[allow(clippy::unwrap_used)] - pub fn get(&self) -> Ref { - Ref(self.merkle.get_node(self.ptr).unwrap()) - } + // The branch has only 1 child. Remove the branch and return the child. + let mut child = match child { + Child::Node(child_node) => std::mem::replace( + child_node, + Node::Leaf(LeafNode { + value: Box::from([]), + partial_path: Path::new(), + }), + ), + Child::AddressWithHash(addr, _) => { + let node = self.nodestore.read_node(*addr)?; + self.nodestore.delete_node(*addr); + (*node).clone() + } + }; - pub fn write(&mut self, modify: impl FnOnce(&mut Vec)) -> Result<(), MerkleError> { - let mut deleted = Vec::new(); - #[allow(clippy::unwrap_used)] - { - let mut u_ref = self.merkle.get_node(self.ptr).unwrap(); - #[allow(clippy::unwrap_used)] - let mut parents: Vec<_> = self - .parents - .iter() - .map(|(ptr, nib)| (self.merkle.get_node(*ptr).unwrap(), *nib)) - .collect(); - write_node!( - self.merkle, - u_ref, - |u| { - #[allow(clippy::unwrap_used)] - modify(match &mut u.inner { - NodeType::Branch(n) => n.value.as_mut().unwrap(), - NodeType::Leaf(n) => &mut n.value, - }); - u.rehash() - }, - &mut parents, - &mut deleted - ); - } - for ptr in deleted.into_iter() { - self.merkle.free_node(ptr)?; + // The child's partial path is the concatenation of its (now removed) parent, + // its (former) child index, and its partial path. + let branch_partial_path = + std::mem::replace(&mut branch.partial_path, Path::new()); + + let child_partial_path = Path::from_nibbles_iterator( + branch_partial_path + .iter() + .chain(once(&(child_index as u8))) + .chain(child.partial_path().iter()) + .copied(), + ); + child.update_partial_path(child_partial_path); + + Ok((Some(child), removed_value)) + } + } + } } - Ok(()) } } -// nibbles, high bits first, then low bits -pub const fn to_nibble_array(x: u8) -> [u8; 2] { - [x >> 4, x & 0b_0000_1111] -} - /// Returns an iterator where each element is the result of combining /// 2 nibbles of `nibbles`. If `nibbles` is odd length, panics in /// debug mode and drops the final nibble in release mode. @@ -1372,19 +794,14 @@ struct PrefixOverlap<'a, T> { impl<'a, T: PartialEq> PrefixOverlap<'a, T> { fn from(a: &'a [T], b: &'a [T]) -> Self { - let mut split_index = 0; - - #[allow(clippy::indexing_slicing)] - for i in 0..std::cmp::min(a.len(), b.len()) { - if a[i] != b[i] { - break; - } - - split_index += 1; - } + let split_index = a + .iter() + .zip(b) + .position(|(a, b)| *a != *b) + .unwrap_or_else(|| std::cmp::min(a.len(), b.len())); let (shared, unique_a) = a.split_at(split_index); - let (_, unique_b) = b.split_at(split_index); + let unique_b = b.get(split_index..).expect(""); Self { shared, @@ -1398,179 +815,174 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; - use crate::merkle::node::PlainCodec; - use shale::in_mem::InMemLinearStore; + use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; + use storage::{MemStore, MutableProposal, NodeStore, RootReader}; use test_case::test_case; - fn leaf(path: Vec, value: Vec) -> Node { - Node::from_leaf(LeafNode::new(Path(path), value)) - } - - #[test_case(vec![0x12, 0x34, 0x56], &[0x1, 0x2, 0x3, 0x4, 0x5, 0x6])] - #[test_case(vec![0xc0, 0xff], &[0xc, 0x0, 0xf, 0xf])] - fn to_nibbles(bytes: Vec, nibbles: &[u8]) { - let n: Vec<_> = bytes.into_iter().flat_map(to_nibble_array).collect(); - assert_eq!(n, nibbles); - } - - fn create_generic_test_merkle<'de, T>() -> Merkle - where - T: BinarySerde, - EncodedNode: serde::Serialize + serde::Deserialize<'de>, - { - const RESERVED: usize = 0x1000; - - let mut dm = shale::in_mem::InMemLinearStore::new(0x10000, 0); - let compact_header = DiskAddress::null(); - dm.write( - compact_header.into(), - &shale::to_dehydrated(&shale::compact::StoreHeader::new( - std::num::NonZeroUsize::new(RESERVED).unwrap(), - std::num::NonZeroUsize::new(RESERVED).unwrap(), - )) - .unwrap(), - ) - .unwrap(); - let compact_header = shale::StoredView::addr_to_obj( - &dm, - compact_header, - shale::compact::ChunkHeader::SERIALIZED_LEN, - ) - .unwrap(); - let mem_meta = dm; - let mem_payload = InMemLinearStore::new(0x10000, 0x1); - - let cache = shale::ObjCache::new(1); - let store = - shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("Store init fail"); - - Merkle::new(store) - } + // Returns n random key-value pairs. + fn generate_random_kvs(seed: u64, n: usize) -> Vec<(Vec, Vec)> { + eprintln!("Used seed: {}", seed); - pub(super) fn create_test_merkle() -> Merkle { - create_generic_test_merkle::() - } + let mut rng = StdRng::seed_from_u64(seed); - fn branch(path: &[u8], value: &[u8], encoded_child: Option>) -> Node { - let (path, value) = (path.to_vec(), value.to_vec()); - let path = Nibbles::<0>::new(&path); - let path = Path(path.into_iter().collect()); + let mut kvs: Vec<(Vec, Vec)> = Vec::new(); + for _ in 0..n { + let key_len = rng.gen_range(1..=4096); + let key: Vec = (0..key_len).map(|_| rng.gen()).collect(); - let children = Default::default(); - let value = if value.is_empty() { None } else { Some(value) }; - let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); + let val_len = rng.gen_range(1..=4096); + let val: Vec = (0..val_len).map(|_| rng.gen()).collect(); - if let Some(child) = encoded_child { - children_encoded[0] = Some(child); + kvs.push((key, val)); } - Node::from_branch(BranchNode { - partial_path: path, - children, - value, - children_encoded, - }) + kvs } - fn branch_without_value(path: &[u8], encoded_child: Option>) -> Node { - let path = path.to_vec(); - let path = Nibbles::<0>::new(&path); - let path = Path(path.into_iter().collect()); + #[test] + fn test_get_regression() { + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&[0], Box::new([0])).unwrap(); + assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); - let children = Default::default(); - // TODO: Properly test empty value - let value = None; - let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); + merkle.insert(&[1], Box::new([1])).unwrap(); + assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); - if let Some(child) = encoded_child { - children_encoded[0] = Some(child); - } + merkle.insert(&[2], Box::new([2])).unwrap(); + assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); - Node::from_branch(BranchNode { - partial_path: path, - children, - value, - children_encoded, - }) - } + let merkle = merkle.hash(); - #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] - #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - #[test_case(branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd")] - #[test_case(branch(b"", b"value", None); "branch without chd")] - #[test_case(branch_without_value(b"", None); "branch without value and chd")] - #[test_case(branch(b"", b"", None); "branch without path value or children")] - #[test_case(branch(b"", b"value", None) ; "branch with value")] - #[test_case(branch(&[2], b"", None); "branch with path")] - #[test_case(branch(b"", b"", vec![1, 2, 3].into()); "branch with children")] - #[test_case(branch(&[2], b"value", None); "branch with path and value")] - #[test_case(branch(b"", b"value", vec![1, 2, 3].into()); "branch with value and children")] - #[test_case(branch(&[2], b"", vec![1, 2, 3].into()); "branch with path and children")] - #[test_case(branch(&[2], b"value", vec![1, 2, 3].into()); "branch with path value and children")] - fn encode(node: Node) { - let merkle = create_test_merkle(); - - let node_ref = merkle.put_node(node).unwrap(); - let encoded = node_ref.get_encoded(&merkle.store); - let new_node = Node::from(NodeType::decode(encoded).unwrap()); - let new_node_encoded = new_node.get_encoded(&merkle.store); - - assert_eq!(encoded, new_node_encoded); - } + assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); + assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); + assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); - #[test_case(Bincode::new(), leaf(vec![], vec![4, 5]) ; "leaf without partial path encoding with Bincode")] - #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with Bincode")] - #[test_case(Bincode::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with Bincode")] - #[test_case(Bincode::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with Bincode")] - #[test_case(Bincode::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with Bincode")] - #[test_case(PlainCodec::new(), leaf(Vec::new(), vec![4, 5]) ; "leaf without partial path encoding with PlainCodec")] - #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with PlainCodec")] - #[test_case(PlainCodec::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with PlainCodec")] - fn node_encode_decode(_codec: T, node: Node) - where - T: BinarySerde, - for<'de> EncodedNode: serde::Serialize + serde::Deserialize<'de>, - { - let merkle = create_generic_test_merkle::(); - let node_ref = merkle.put_node(node.clone()).unwrap(); - - let encoded = merkle.encode(node_ref.inner()).unwrap(); - let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); - - assert_eq!(node, new_node); + for result in merkle.path_iter(&[2]).unwrap() { + result.unwrap(); + } } #[test] - fn insert_and_retrieve_one() { - let key = b"hello"; - let val = b"world"; - - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); - - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } + fn insert_one() { + let mut merkle = create_in_memory_merkle(); + merkle.insert(b"abc", Box::new([])).unwrap() + } + + fn create_in_memory_merkle() -> Merkle> { + let memstore = MemStore::new(vec![]); + + let nodestore = NodeStore::new_empty_proposal(memstore.into()); + + Merkle { nodestore } + } + + // use super::*; + // use test_case::test_case; + + // fn branch(path: &[u8], value: &[u8], encoded_child: Option>) -> Node { + // let (path, value) = (path.to_vec(), value.to_vec()); + // let path = Nibbles::<0>::new(&path); + // let path = Path(path.into_iter().collect()); + + // let children = Default::default(); + // let value = if value.is_empty() { None } else { Some(value) }; + // let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); + + // if let Some(child) = encoded_child { + // children_encoded[0] = Some(child); + // } + + // Node::from_branch(BranchNode { + // partial_path: path, + // children, + // value, + // children_encoded, + // }) + // } + + // fn branch_without_value(path: &[u8], encoded_child: Option>) -> Node { + // let path = path.to_vec(); + // let path = Nibbles::<0>::new(&path); + // let path = Path(path.into_iter().collect()); + + // let children = Default::default(); + // // TODO: Properly test empty value + // let value = None; + // let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); + + // if let Some(child) = encoded_child { + // children_encoded[0] = Some(child); + // } + + // Node::from_branch(BranchNode { + // partial_path: path, + // children, + // value, + // children_encoded, + // }) + // } + + // #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] + // #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] + // #[test_case(branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd")] + // #[test_case(branch(b"", b"value", None); "branch without chd")] + // #[test_case(branch_without_value(b"", None); "branch without value and chd")] + // #[test_case(branch(b"", b"", None); "branch without path value or children")] + // #[test_case(branch(b"", b"value", None) ; "branch with value")] + // #[test_case(branch(&[2], b"", None); "branch with path")] + // #[test_case(branch(b"", b"", vec![1, 2, 3].into()); "branch with children")] + // #[test_case(branch(&[2], b"value", None); "branch with path and value")] + // #[test_case(branch(b"", b"value", vec![1, 2, 3].into()); "branch with value and children")] + // #[test_case(branch(&[2], b"", vec![1, 2, 3].into()); "branch with path and children")] + // #[test_case(branch(&[2], b"value", vec![1, 2, 3].into()); "branch with path value and children")] + // fn encode(node: Node) { + // let merkle = create_in_memory_merkle(); + + // let node_ref = merkle.put_node(node).unwrap(); + // let encoded = node_ref.get_encoded(&merkle.store); + // let new_node = Node::from(NodeType::decode(encoded).unwrap()); + // let new_node_encoded = new_node.get_encoded(&merkle.store); + + // assert_eq!(encoded, new_node_encoded); + // } + + // #[test_case(Bincode::new(), leaf(vec![], vec![4, 5]) ; "leaf without partial path encoding with Bincode")] + // #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with Bincode")] + // #[test_case(Bincode::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with Bincode")] + // #[test_case(Bincode::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with Bincode")] + // #[test_case(Bincode::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with Bincode")] + // #[test_case(PlainCodec::new(), leaf(Vec::new(), vec![4, 5]) ; "leaf without partial path encoding with PlainCodec")] + // #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with PlainCodec")] + // #[test_case(PlainCodec::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with PlainCodec")] + // #[test_case(PlainCodec::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with PlainCodec")] + // #[test_case(PlainCodec::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with PlainCodec")] + // fn node_encode_decode(_codec: T, node: Node) + // where + // T: BinarySerde, + // for<'de> EncodedNode: serde::Serialize + serde::Deserialize<'de>, + // { + // let merkle = create_generic_test_merkle::(); + // let node_ref = merkle.put_node(node.clone()).unwrap(); + + // let encoded = merkle.encode(node_ref.inner()).unwrap(); + // let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); + + // assert_eq!(node, new_node); + // } #[test] - fn insert_and_retrieve_multiple() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + fn test_insert_and_get() { + let mut merkle = create_in_memory_merkle(); // insert values for key_val in u8::MIN..=u8::MAX { let key = vec![key_val]; - let val = vec![key_val]; + let val = Box::new([key_val]); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, val.clone()).unwrap(); - let fetched_val = merkle.get(&key, sentinel_addr).unwrap(); + let fetched_val = merkle.get_value(&key).unwrap(); // make sure the value was inserted assert_eq!(fetched_val.as_deref(), val.as_slice().into()); @@ -1581,628 +993,1673 @@ mod tests { let key = vec![key_val]; let val = vec![key_val]; - let fetched_val = merkle.get(&key, sentinel_addr).unwrap(); - - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } - } - - #[test] - fn long_insert_and_retrieve_multiple() { - let key_val: Vec<(&'static [u8], _)> = vec![ - ( - &[0, 0, 0, 1, 0, 101, 151, 236], - [16, 15, 159, 195, 34, 101, 227, 73], - ), - ( - &[0, 0, 1, 107, 198, 92, 205], - [26, 147, 21, 200, 138, 106, 137, 218], - ), - (&[0, 1, 0, 1, 0, 56], [194, 147, 168, 193, 19, 226, 51, 204]), - (&[1, 90], [101, 38, 25, 65, 181, 79, 88, 223]), - ( - &[1, 1, 1, 0, 0, 0, 1, 59], - [105, 173, 182, 126, 67, 166, 166, 196], - ), - ( - &[0, 1, 0, 0, 1, 1, 55, 33, 38, 194], - [90, 140, 160, 53, 230, 100, 237, 236], - ), - ( - &[1, 1, 0, 1, 249, 46, 69], - [16, 104, 134, 6, 57, 46, 200, 35], - ), - ( - &[1, 1, 0, 1, 0, 0, 1, 33, 163], - [95, 97, 187, 124, 198, 28, 75, 226], - ), - ( - &[1, 1, 0, 1, 0, 57, 156], - [184, 18, 69, 29, 96, 252, 188, 58], - ), - (&[1, 0, 1, 1, 0, 218], [155, 38, 43, 54, 93, 134, 73, 209]), - ]; - - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - for (key, val) in &key_val { - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); - - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } - - for (key, val) in key_val { - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); + let fetched_val = merkle.get_value(&key).unwrap(); assert_eq!(fetched_val.as_deref(), val.as_slice().into()); } } #[test] - fn remove_one() { - let key = b"hello"; - let val = b"world"; - - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - - assert_eq!( - merkle.get(key, sentinel_addr).unwrap().as_deref(), - val.as_slice().into() - ); - - let removed_val = merkle.remove(key, sentinel_addr).unwrap(); - assert_eq!(removed_val.as_deref(), val.as_slice().into()); - - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); - assert!(fetched_val.is_none()); + fn remove_root() { + let key0 = vec![0]; + let val0 = [0]; + let key1 = vec![0, 1]; + let val1 = [0, 1]; + let key2 = vec![0, 1, 2]; + let val2 = [0, 1, 2]; + let key3 = vec![0, 1, 15]; + let val3 = [0, 1, 15]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key0, Box::from(val0)).unwrap(); + merkle.insert(&key1, Box::from(val1)).unwrap(); + merkle.insert(&key2, Box::from(val2)).unwrap(); + merkle.insert(&key3, Box::from(val3)).unwrap(); + // Trie is: + // key0 + // | + // key1 + // / \ + // key2 key3 + + // Test removal of root when it's a branch with 1 branch child + let removed_val = merkle.remove(&key0).unwrap(); + assert_eq!(removed_val, Some(Box::from(val0))); + assert!(merkle.get_value(&key0).unwrap().is_none()); + // Removing an already removed key is a no-op + assert!(merkle.remove(&key0).unwrap().is_none()); + + // Trie is: + // key1 + // / \ + // key2 key3 + // Test removal of root when it's a branch with multiple children + assert_eq!(merkle.remove(&key1).unwrap(), Some(Box::from(val1))); + assert!(merkle.get_value(&key1).unwrap().is_none()); + assert!(merkle.remove(&key1).unwrap().is_none()); + + // Trie is: + // key1 (now has no value) + // / \ + // key2 key3 + let removed_val = merkle.remove(&key2).unwrap(); + assert_eq!(removed_val, Some(Box::from(val2))); + assert!(merkle.get_value(&key2).unwrap().is_none()); + assert!(merkle.remove(&key2).unwrap().is_none()); + + // Trie is: + // key3 + let removed_val = merkle.remove(&key3).unwrap(); + assert_eq!(removed_val, Some(Box::from(val3))); + assert!(merkle.get_value(&key3).unwrap().is_none()); + assert!(merkle.remove(&key3).unwrap().is_none()); + + assert!(merkle.nodestore.root_node().is_none()); } #[test] fn remove_many() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - // insert values + // insert key-value pairs for key_val in u8::MIN..=u8::MAX { - let key = &[key_val]; - let val = &[key_val]; - - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); + let key = [key_val]; + let val = [key_val]; - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); - - // make sure the value was inserted - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); } - // remove values + // remove key-value pairs for key_val in u8::MIN..=u8::MAX { - let key = &[key_val]; - let val = &[key_val]; + let key = [key_val]; + let val = [key_val]; - let Ok(removed_val) = merkle.remove(key, sentinel_addr) else { - panic!("({key_val}, {key_val}) missing"); - }; + let got = merkle.remove(&key).unwrap().unwrap(); + assert_eq!(&*got, val); - assert_eq!(removed_val.as_deref(), val.as_slice().into()); + // Removing an already removed key is a no-op + assert!(merkle.remove(&key).unwrap().is_none()); - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); - assert!(fetched_val.is_none()); + let got = merkle.get_value(&key).unwrap(); + assert!(got.is_none()); } + assert!(merkle.nodestore.root_node().is_none()); } #[test] fn get_empty_proof() { - let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - let proof = merkle.prove(b"any-key", sentinel_addr).unwrap(); - - assert!(proof.0.is_empty()); - } - - #[tokio::test] - async fn empty_range_proof() { - let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - assert!(merkle - .range_proof::<&[u8]>(sentinel_addr, None, None, None) - .await - .unwrap() - .is_none()); - } - - #[tokio::test] - async fn range_proof_invalid_bounds() { - let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let start_key = &[0x01]; - let end_key = &[0x00]; - - match merkle - .range_proof::<&[u8]>(sentinel_addr, Some(start_key), Some(end_key), Some(1)) - .await - { - Err(api::Error::InvalidRange { - first_key, - last_key, - }) if first_key == start_key && last_key == end_key => (), - Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), - _ => panic!("expected InvalidRange error"), - } - } - - #[tokio::test] - async fn full_range_proof() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - // insert values - for key_val in u8::MIN..=u8::MAX { - let key = &[key_val]; - let val = &[key_val]; - - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - } - merkle.flush_dirty(); - - let rangeproof = merkle - .range_proof::<&[u8]>(sentinel_addr, None, None, None) - .await - .unwrap() - .unwrap(); - assert_eq!(rangeproof.middle.len(), u8::MAX as usize + 1); - assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - let left_proof = merkle.prove([u8::MIN], sentinel_addr).unwrap(); - let right_proof = merkle.prove([u8::MAX], sentinel_addr).unwrap(); - assert_eq!(rangeproof.first_key_proof.0, left_proof.0); - assert_eq!(rangeproof.last_key_proof.0, right_proof.0); - } - - #[tokio::test] - async fn single_value_range_proof() { - const RANDOM_KEY: u8 = 42; - - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - // insert values - for key_val in u8::MIN..=u8::MAX { - let key = &[key_val]; - let val = &[key_val]; - - merkle.insert(key, val.to_vec(), sentinel_addr).unwrap(); - } - merkle.flush_dirty(); - - let rangeproof = merkle - .range_proof(sentinel_addr, Some([RANDOM_KEY]), None, Some(1)) - .await - .unwrap() - .unwrap(); - assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - assert_eq!(rangeproof.middle.len(), 1); + let merkle = create_in_memory_merkle().hash(); + let proof = merkle.prove(b"any-key"); + assert!(matches!(proof.unwrap_err(), MerkleError::Empty)); } #[test] - fn shared_path_proof() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - let key1 = b"key1"; - let value1 = b"1"; - merkle.insert(key1, value1.to_vec(), sentinel_addr).unwrap(); - - let key2 = b"key2"; - let value2 = b"2"; - merkle.insert(key2, value2.to_vec(), sentinel_addr).unwrap(); - - let root_hash = merkle.root_hash(sentinel_addr).unwrap(); - - let verified = { - let key = key1; - let proof = merkle.prove(key, sentinel_addr).unwrap(); - proof.verify(key, root_hash.0).unwrap() - }; - - assert_eq!(verified, Some(value1.to_vec())); + fn single_key_proof() { + let mut merkle = create_in_memory_merkle(); + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| thread_rng().gen()); - let verified = { - let key = key2; - let proof = merkle.prove(key, sentinel_addr).unwrap(); - proof.verify(key, root_hash.0).unwrap() - }; + const TEST_SIZE: usize = 1; - assert_eq!(verified, Some(value2.to_vec())); - } + let kvs = generate_random_kvs(seed, TEST_SIZE); - // this was a specific failing case - #[test] - fn shared_path_on_insert() { - type Bytes = &'static [u8]; - let pairs: Vec<(Bytes, Bytes)> = vec![ - ( - &[1, 1, 46, 82, 67, 218], - &[23, 252, 128, 144, 235, 202, 124, 243], - ), - ( - &[1, 0, 0, 1, 1, 0, 63, 80], - &[99, 82, 31, 213, 180, 196, 49, 242], - ), - ( - &[0, 0, 0, 169, 176, 15], - &[105, 211, 176, 51, 231, 182, 74, 207], - ), - ( - &[1, 0, 0, 0, 53, 57, 93], - &[234, 139, 214, 220, 172, 38, 168, 164], - ), - ]; + for (key, val) in &kvs { + merkle.insert(key, val.clone().into_boxed_slice()).unwrap(); + } - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let merkle = merkle.hash(); - for (key, val) in &pairs { - let val = val.to_vec(); - merkle.insert(key, val.clone(), sentinel_addr).unwrap(); + let root_hash = merkle.nodestore.root_hash().unwrap().unwrap(); - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); + for (key, value) in kvs { + let proof = merkle.prove(&key).unwrap(); - // make sure the value was inserted - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } + proof + .verify(key.clone(), Some(value.clone()), &root_hash) + .unwrap(); - for (key, val) in pairs { - let fetched_val = merkle.get(key, sentinel_addr).unwrap(); + { + // Test that the proof is invalid when the value is different + let mut value = value.clone(); + value[0] = value[0].wrapping_add(1); + assert!(proof.verify(key.clone(), Some(value), &root_hash).is_err()); + } - // make sure the value was inserted - assert_eq!(fetched_val.as_deref(), val.into()); + { + // Test that the proof is invalid when the hash is different + assert!(proof + .verify(key, Some(value), &TrieHash::default()) + .is_err()); + } } } - #[test] - fn overwrite_leaf() { - let key = vec![0x00]; - let val = vec![1]; - let overwrite = vec![2]; - - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); - - merkle - .insert(&key, overwrite.clone(), sentinel_addr) - .unwrap(); - - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(overwrite.as_slice()) - ); - } + #[tokio::test] + async fn empty_range_proof() { + let merkle = create_in_memory_merkle(); + + assert!(matches!( + merkle._range_proof(None, None, None).await.unwrap_err(), + api::Error::RangeProofOnEmptyTrie + )); + } + + // #[tokio::test] + // async fn range_proof_invalid_bounds() { + // let merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + // let start_key = &[0x01]; + // let end_key = &[0x00]; + + // match merkle + // .range_proof::<&[u8]>(root_addr, Some(start_key), Some(end_key), Some(1)) + // .await + // { + // Err(api::Error::InvalidRange { + // first_key, + // last_key, + // }) if first_key == start_key && last_key == end_key => (), + // Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), + // _ => panic!("expected InvalidRange error"), + // } + // } + + // #[tokio::test] + // async fn full_range_proof() { + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + // // insert values + // for key_val in u8::MIN..=u8::MAX { + // let key = &[key_val]; + // let val = &[key_val]; + + // merkle.insert(key, val.to_vec(), root_addr).unwrap(); + // } + // merkle.flush_dirty(); + + // let rangeproof = merkle + // .range_proof::<&[u8]>(root_addr, None, None, None) + // .await + // .unwrap() + // .unwrap(); + // assert_eq!(rangeproof.middle.len(), u8::MAX as usize + 1); + // assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); + // let left_proof = merkle.prove([u8::MIN], root_addr).unwrap(); + // let right_proof = merkle.prove([u8::MAX], root_addr).unwrap(); + // assert_eq!(rangeproof.first_key_proof.0, left_proof.0); + // assert_eq!(rangeproof.last_key_proof.0, right_proof.0); + // } + + // #[tokio::test] + // async fn single_value_range_proof() { + // const RANDOM_KEY: u8 = 42; + + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + // // insert values + // for key_val in u8::MIN..=u8::MAX { + // let key = &[key_val]; + // let val = &[key_val]; + + // merkle.insert(key, val.to_vec(), root_addr).unwrap(); + // } + // merkle.flush_dirty(); + + // let rangeproof = merkle + // .range_proof(root_addr, Some([RANDOM_KEY]), None, Some(1)) + // .await + // .unwrap() + // .unwrap(); + // assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); + // assert_eq!(rangeproof.middle.len(), 1); + // } + + // #[test] + // fn shared_path_proof() { + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + + // let key1 = b"key1"; + // let value1 = b"1"; + // merkle.insert(key1, value1.to_vec(), root_addr).unwrap(); + + // let key2 = b"key2"; + // let value2 = b"2"; + // merkle.insert(key2, value2.to_vec(), root_addr).unwrap(); + + // let root_hash = merkle.root_hash(root_addr).unwrap(); + + // let verified = { + // let key = key1; + // let proof = merkle.prove(key, root_addr).unwrap(); + // proof.verify(key, root_hash.0).unwrap() + // }; + + // assert_eq!(verified, Some(value1.to_vec())); + + // let verified = { + // let key = key2; + // let proof = merkle.prove(key, root_addr).unwrap(); + // proof.verify(key, root_hash.0).unwrap() + // }; + + // assert_eq!(verified, Some(value2.to_vec())); + // } + + // // this was a specific failing case + // #[test] + // fn shared_path_on_insert() { + // type Bytes = &'static [u8]; + // let pairs: Vec<(Bytes, Bytes)> = vec![ + // ( + // &[1, 1, 46, 82, 67, 218], + // &[23, 252, 128, 144, 235, 202, 124, 243], + // ), + // ( + // &[1, 0, 0, 1, 1, 0, 63, 80], + // &[99, 82, 31, 213, 180, 196, 49, 242], + // ), + // ( + // &[0, 0, 0, 169, 176, 15], + // &[105, 211, 176, 51, 231, 182, 74, 207], + // ), + // ( + // &[1, 0, 0, 0, 53, 57, 93], + // &[234, 139, 214, 220, 172, 38, 168, 164], + // ), + // ]; + + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + + // for (key, val) in &pairs { + // let val = val.to_vec(); + // merkle.insert(key, val.clone(), root_addr).unwrap(); + + // let fetched_val = merkle.get(key, root_addr).unwrap(); + + // // make sure the value was inserted + // assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + // } + + // for (key, val) in pairs { + // let fetched_val = merkle.get(key, root_addr).unwrap(); + + // // make sure the value was inserted + // assert_eq!(fetched_val.as_deref(), val.into()); + // } + // } + + // #[test] + // fn overwrite_leaf() { + // let key = vec![0x00]; + // let val = vec![1]; + // let overwrite = vec![2]; + + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + + // merkle.insert(&key, val.clone(), root_addr).unwrap(); + + // assert_eq!( + // merkle.get(&key, root_addr).unwrap().as_deref(), + // Some(val.as_slice()) + // ); + + // merkle + // .insert(&key, overwrite.clone(), root_addr) + // .unwrap(); + + // assert_eq!( + // merkle.get(&key, root_addr).unwrap().as_deref(), + // Some(overwrite.as_slice()) + // ); + // } #[test] - fn new_leaf_is_a_child_of_the_old_leaf() { + fn test_insert_leaf_suffix() { + // key_2 is a suffix of key, which is a leaf let key = vec![0xff]; - let val = vec![1]; + let val = [1]; let key_2 = vec![0xff, 0x00]; - let val_2 = vec![2]; + let val_2 = [2]; - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + assert_eq!(*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); } #[test] - fn old_leaf_is_a_child_of_the_new_leaf() { + fn test_insert_leaf_prefix() { + // key_2 is a prefix of key, which is a leaf let key = vec![0xff, 0x00]; - let val = vec![1]; + let val = [1]; let key_2 = vec![0xff]; - let val_2 = vec![2]; + let val_2 = [2]; - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); } #[test] - fn new_leaf_is_sibling_of_old_leaf() { + fn test_insert_sibling_leaf() { + // The node at key is a branch node with children key_2 and key_3. + // TODO assert in this test that key is the parent of key_2 and key_3. + // i.e. the node types are branch, leaf, leaf respectively. let key = vec![0xff]; - let val = vec![1]; + let val = [1]; let key_2 = vec![0xff, 0x00]; - let val_2 = vec![2]; + let val_2 = [2]; let key_3 = vec![0xff, 0x0f]; - let val_3 = vec![3]; + let val_3 = [3]; - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_3, val_3.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + merkle.insert(&key_3, Box::new(val_3)).unwrap(); - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); - assert_eq!( - merkle.get(&key_3, sentinel_addr).unwrap().as_deref(), - Some(val_3.as_slice()) - ); + let got = merkle.get_value(&key_3).unwrap().unwrap(); + assert_eq!(*got, val_3); } #[test] - fn old_branch_is_a_child_of_new_branch() { + fn test_insert_branch_as_branch_parent() { let key = vec![0xff, 0xf0]; - let val = vec![1]; + let val = [1]; let key_2 = vec![0xff, 0xf0, 0x00]; - let val_2 = vec![2]; + let val_2 = [2]; let key_3 = vec![0xff]; - let val_3 = vec![3]; + let val_3 = [3]; - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_3, val_3.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, Box::new(val)).unwrap(); + // key is a leaf - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + // key is branch with child key_2 - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + merkle.insert(&key_3, Box::new(val_3)).unwrap(); + // key_3 is a branch with child key + // key is a branch with child key_3 - assert_eq!( - merkle.get(&key_3, sentinel_addr).unwrap().as_deref(), - Some(val_3.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(&*got, val_2); + + let got = merkle.get_value(&key_3).unwrap().unwrap(); + assert_eq!(&*got, val_3); } #[test] - fn overlapping_branch_insert() { + fn test_insert_overwrite_branch_value() { let key = vec![0xff]; - let val = vec![1]; + let val = [1]; let key_2 = vec![0xff, 0x00]; - let val_2 = vec![2]; - - let overwrite = vec![3]; + let val_2 = [2]; + let overwrite = [3]; - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let mut merkle = create_in_memory_merkle(); - merkle.insert(&key, val.clone(), sentinel_addr).unwrap(); - merkle.insert(&key_2, val_2.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(val.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); - merkle - .insert(&key, overwrite.clone(), sentinel_addr) - .unwrap(); + merkle.insert(&key, Box::new(overwrite)).unwrap(); - assert_eq!( - merkle.get(&key, sentinel_addr).unwrap().as_deref(), - Some(overwrite.as_slice()) - ); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, overwrite); - assert_eq!( - merkle.get(&key_2, sentinel_addr).unwrap().as_deref(), - Some(val_2.as_slice()) - ); + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); } - #[test] - fn single_key_proof_with_one_node() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let key = b"key"; - let value = b"value"; + // #[test] + // fn single_key_proof_with_one_node() { + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); + // let key = b"key"; + // let value = b"value"; - merkle.insert(key, value.to_vec(), sentinel_addr).unwrap(); - let root_hash = merkle.root_hash(sentinel_addr).unwrap(); + // merkle.insert(key, value.to_vec(), root_addr).unwrap(); + // let root_hash = merkle.root_hash(root_addr).unwrap(); - let proof = merkle.prove(key, sentinel_addr).unwrap(); + // let proof = merkle.prove(key, root_addr).unwrap(); - let verified = proof.verify(key, root_hash.0).unwrap(); + // let verified = proof.verify(key, root_hash.0).unwrap(); - assert_eq!(verified, Some(value.to_vec())); - } + // assert_eq!(verified, Some(value.to_vec())); + // } - #[test] - fn two_key_proof_without_shared_path() { - let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + // #[test] + // fn two_key_proof_without_shared_path() { + // let mut merkle = create_in_memory_merkle(); + // let root_addr = merkle.init_sentinel().unwrap(); - let key1 = &[0x00]; - let key2 = &[0xff]; + // let key1 = &[0x00]; + // let key2 = &[0xff]; - merkle.insert(key1, key1.to_vec(), sentinel_addr).unwrap(); - merkle.insert(key2, key2.to_vec(), sentinel_addr).unwrap(); + // merkle.insert(key1, key1.to_vec(), root_addr).unwrap(); + // merkle.insert(key2, key2.to_vec(), root_addr).unwrap(); - let root_hash = merkle.root_hash(sentinel_addr).unwrap(); + // let root_hash = merkle.root_hash(root_addr).unwrap(); - let verified = { - let proof = merkle.prove(key1, sentinel_addr).unwrap(); - proof.verify(key1, root_hash.0).unwrap() - }; + // let verified = { + // let proof = merkle.prove(key1, root_addr).unwrap(); + // proof.verify(key1, root_hash.0).unwrap() + // }; - assert_eq!(verified.as_deref(), Some(key1.as_slice())); - } + // assert_eq!(verified.as_deref(), Some(key1.as_slice())); + // } - #[test] - fn update_leaf_with_larger_path() -> Result<(), MerkleError> { - let path = vec![0x00]; - let value = vec![0x00]; - - let double_path = path - .clone() - .into_iter() - .chain(path.clone()) - .collect::>(); - - let node = Node::from_leaf(LeafNode { - partial_path: Path::from(path), - value: value.clone(), - }); - - check_node_update(node, double_path, value) - } + fn merkle_build_test, V: AsRef<[u8]>>( + items: Vec<(K, V)>, + ) -> Result>, MerkleError> { + let nodestore = NodeStore::new_empty_proposal(MemStore::new(vec![]).into()); + let mut merkle = Merkle::from(nodestore); + for (k, v) in items.iter() { + merkle.insert(k.as_ref(), Box::from(v.as_ref()))?; + } - #[test] - fn update_leaf_with_larger_value() -> Result<(), MerkleError> { - let path = vec![0x00]; - let value = vec![0x00]; - - let double_value = value - .clone() - .into_iter() - .chain(value.clone()) - .collect::>(); - - let node = Node::from_leaf(LeafNode { - partial_path: Path::from(path.clone()), - value, - }); - - check_node_update(node, path, double_value) + Ok(merkle) } #[test] - fn update_branch_with_larger_path() -> Result<(), MerkleError> { - let path = vec![0x00]; - let value = vec![0x00]; - - let double_path = path - .clone() - .into_iter() - .chain(path.clone()) - .collect::>(); - - let node = Node::from_branch(BranchNode { - partial_path: Path::from(path.clone()), - children: Default::default(), - value: Some(value.clone()), - children_encoded: Default::default(), - }); - - check_node_update(node, double_path, value) + fn test_root_hash_simple_insertions() -> Result<(), MerkleError> { + let items = vec![ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]; + let merkle = merkle_build_test(items).unwrap().hash(); + + merkle.dump().unwrap(); + Ok(()) } - #[test] - fn update_branch_with_larger_value() -> Result<(), MerkleError> { - let path = vec![0x00]; - let value = vec![0x00]; - - let double_value = value - .clone() - .into_iter() - .chain(value.clone()) - .collect::>(); - - let node = Node::from_branch(BranchNode { - partial_path: Path::from(path.clone()), - children: Default::default(), - value: Some(value), - children_encoded: Default::default(), - }); - - check_node_update(node, path, double_value) + #[test_case(vec![], None; "empty trie")] + #[test_case(vec![(&[0],&[0])], Some("073615413d814b23383fc2c8d8af13abfffcb371b654b98dbf47dd74b1e4d1b9"); "root")] + #[test_case(vec![(&[0,1],&[0,1])], Some("28e67ae4054c8cdf3506567aa43f122224fe65ef1ab3e7b7899f75448a69a6fd"); "root with partial path")] + #[test_case(vec![(&[0],&[1;32])], Some("ba0283637f46fa807280b7d08013710af08dfdc236b9b22f9d66e60592d6c8a3"); "leaf value >= 32 bytes")] + #[test_case(vec![(&[0],&[0]),(&[0,1],&[1;32])], Some("3edbf1fdd345db01e47655bcd0a9a456857c4093188cf35c5c89b8b0fb3de17e"); "branch value >= 32 bytes")] + #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")] + #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")] + #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")] + fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) { + let merkle = merkle_build_test(kvs).unwrap().hash(); + let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else { + assert!(expected_hash.is_none()); + return; + }; + + let expected_hash = expected_hash.unwrap(); + + // This hash is from merkledb + let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap(); + + assert_eq!(got_hash, TrieHash::from(expected_hash)); } - fn check_node_update( - node: Node, - new_path: Vec, - new_value: Vec, - ) -> Result<(), MerkleError> { - let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel()?; - let sentinel = merkle.get_node(sentinel_addr)?; - - let mut node_ref = merkle.put_node(node)?; - let addr = node_ref.as_addr(); - - // make sure that doubling the path length will fail on a normal write - let write_result = node_ref.write(|node| { - node.inner_mut().set_path(Path(new_path.clone())); - node.inner_mut().set_value(new_value.clone()); - node.rehash(); - }); - - assert!(matches!(write_result, Err(ObjWriteSizeError))); - - let mut to_delete = vec![]; - // could be any branch node, convenient to use the root. - let mut parents = vec![(sentinel, 0)]; - - let node = merkle.update_path_and_move_node_if_larger( - (&mut parents, &mut to_delete), - node_ref, - Path(new_path.clone()), - )?; - - assert_ne!(node.as_addr(), addr); - assert_eq!(&to_delete[0], &addr); - - let (path, value) = match node.inner() { - NodeType::Leaf(leaf) => (&leaf.partial_path, Some(&leaf.value)), - NodeType::Branch(branch) => (&branch.partial_path, branch.value.as_ref()), + #[test] + fn test_root_hash_fuzz_insertions() -> Result<(), MerkleError> { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + ( + rng.gen_range(1..max_len0 + 1), + rng.gen_range(1..max_len1 + 1), + ) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().gen_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .collect(); + key }; - assert_eq!(path, &Path(new_path)); - assert_eq!(value, Some(&new_value)); + for _ in 0..10 { + let mut items = Vec::new(); + + for _ in 0..10 { + let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + items.push((keygen(), val)); + } + + merkle_build_test(items)?; + } Ok(()) } + + // #[test] + // #[allow(clippy::unwrap_used)] + // fn test_root_hash_reversed_deletions() -> Result<(), MerkleError> { + // use rand::rngs::StdRng; + // use rand::{Rng, SeedableRng}; + // let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + // let max_len0 = 8; + // let max_len1 = 4; + // let keygen = || { + // let (len0, len1): (usize, usize) = { + // let mut rng = rng.borrow_mut(); + // ( + // rng.gen_range(1..max_len0 + 1), + // rng.gen_range(1..max_len1 + 1), + // ) + // }; + // let key: Vec = (0..len0) + // .map(|_| rng.borrow_mut().gen_range(0..2)) + // .chain((0..len1).map(|_| rng.borrow_mut().gen())) + // .collect(); + // key + // }; + + // for _ in 0..10 { + // let mut items: Vec<_> = (0..10) + // .map(|_| keygen()) + // .map(|key| { + // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + // (key, val) + // }) + // .collect(); + + // items.sort(); + + // let mut merkle = + // Merkle::new(HashedNodeStore::initialize(MemStore::new(vec![])).unwrap()); + + // let mut hashes = Vec::new(); + + // for (k, v) in items.iter() { + // hashes.push((merkle.root_hash()?, merkle.dump()?)); + // merkle.insert(k, v.clone())?; + // } + + // let mut new_hashes = Vec::new(); + + // for (k, _) in items.iter().rev() { + // let before = merkle.dump()?; + // merkle.remove(k)?; + // new_hashes.push((merkle.root_hash()?, k, before, merkle.dump()?)); + // } + + // hashes.reverse(); + + // for i in 0..hashes.len() { + // #[allow(clippy::indexing_slicing)] + // let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; + // #[allow(clippy::indexing_slicing)] + // let expected_hash = &hashes[i]; + // let key = key.iter().fold(String::new(), |mut s, b| { + // let _ = write!(s, "{:02x}", b); + // s + // }); + // // assert_eq!(new_hash, expected_hash, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_dump}\n"); + // } + // } + + // Ok(()) + // } + + // #[test] + // #[allow(clippy::unwrap_used)] + // fn test_root_hash_random_deletions() -> Result<(), MerkleError> { + // use rand::rngs::StdRng; + // use rand::seq::SliceRandom; + // use rand::{Rng, SeedableRng}; + // let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + // let max_len0 = 8; + // let max_len1 = 4; + // let keygen = || { + // let (len0, len1): (usize, usize) = { + // let mut rng = rng.borrow_mut(); + // ( + // rng.gen_range(1..max_len0 + 1), + // rng.gen_range(1..max_len1 + 1), + // ) + // }; + // let key: Vec = (0..len0) + // .map(|_| rng.borrow_mut().gen_range(0..2)) + // .chain((0..len1).map(|_| rng.borrow_mut().gen())) + // .collect(); + // key + // }; + + // for i in 0..10 { + // let mut items = std::collections::HashMap::new(); + + // for _ in 0..10 { + // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + // items.insert(keygen(), val); + // } + + // let mut items_ordered: Vec<_> = + // items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + // items_ordered.sort(); + // items_ordered.shuffle(&mut *rng.borrow_mut()); + // let mut merkle = + // Merkle::new(HashedNodeStore::initialize(MemStore::new(vec![])).unwrap()); + + // for (k, v) in items.iter() { + // merkle.insert(k, v.clone())?; + // } + + // for (k, v) in items.iter() { + // assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); + // } + + // for (k, _) in items_ordered.into_iter() { + // assert!(merkle.get(&k)?.is_some()); + + // merkle.remove(&k)?; + + // assert!(merkle.get(&k)?.is_none()); + + // items.remove(&k); + + // for (k, v) in items.iter() { + // assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); + // } + + // let h = triehash::trie_root::, _, _>( + // items.iter().collect(), + // ); + + // let h0 = merkle.root_hash()?; + + // if TrieHash::from(h) != h0 { + // println!("{} != {}", hex::encode(h), hex::encode(*h0)); + // } + // } + + // println!("i = {i}"); + // } + // Ok(()) + // } + + // #[test] + // #[allow(clippy::unwrap_used, clippy::indexing_slicing)] + // fn test_proof() -> Result<(), MerkleError> { + // let set = fixed_and_pseudorandom_data(500); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + // let (keys, vals): (Vec<[u8; 32]>, Vec<[u8; 20]>) = items.into_iter().unzip(); + + // for (i, key) in keys.iter().enumerate() { + // let proof = merkle.prove(key)?; + // assert!(!proof.0.is_empty()); + // let val = merkle.verify_proof(key, &proof)?; + // assert!(val.is_some()); + // assert_eq!(val.unwrap(), vals[i]); + // } + + // Ok(()) + // } + + // #[test] + // /// Verify the proofs that end with leaf node with the given key. + // fn test_proof_end_with_leaf() -> Result<(), MerkleError> { + // let items = vec![ + // ("do", "verb"), + // ("doe", "reindeer"), + // ("dog", "puppy"), + // ("doge", "coin"), + // ("horse", "stallion"), + // ("ddd", "ok"), + // ]; + // let merkle = merkle_build_test(items)?; + // let key = "doe".as_ref(); + + // let proof = merkle.prove(key)?; + // assert!(!proof.0.is_empty()); + + // let verify_proof = merkle.verify_proof(key, &proof)?; + // assert!(verify_proof.is_some()); + + // Ok(()) + // } + + // #[test] + // /// Verify the proofs that end with branch node with the given key. + // fn test_proof_end_with_branch() -> Result<(), MerkleError> { + // let items = vec![ + // ("d", "verb"), + // ("do", "verb"), + // ("doe", "reindeer"), + // ("e", "coin"), + // ]; + // let merkle = merkle_build_test(items)?; + // let key = "d".as_ref(); + + // let proof = merkle.prove(key)?; + // assert!(!proof.0.is_empty()); + + // let verify_proof = merkle.verify_proof(key, &proof)?; + // assert!(verify_proof.is_some()); + + // Ok(()) + // } + + // #[test] + // fn test_bad_proof() -> Result<(), MerkleError> { + // let set = fixed_and_pseudorandom_data(800); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + // let (keys, _): (Vec<[u8; 32]>, Vec<[u8; 20]>) = items.into_iter().unzip(); + + // for key in keys.iter() { + // let mut proof = merkle.prove(key)?; + // assert!(!proof.0.is_empty()); + + // // Delete an entry from the generated proofs. + // let len = proof.0.len(); + // let new_proof = Proof(proof.0.drain().take(len - 1).collect()); + // assert!(merkle.verify_proof(key, &new_proof).is_err()); + // } + + // Ok(()) + // } + + // #[test] + // // Tests that missing keys can also be proven. The test explicitly uses a single + // // entry trie and checks for missing keys both before and after the single entry. + // fn test_missing_key_proof() -> Result<(), MerkleError> { + // let items = vec![("k", "v")]; + // let merkle = merkle_build_test(items)?; + // for key in ["a", "j", "l", "z"] { + // let proof = merkle.prove(key.as_ref())?; + // assert!(!proof.0.is_empty()); + // assert!(proof.0.len() == 1); + + // let val = merkle.verify_proof(key.as_ref(), &proof)?; + // assert!(val.is_none()); + // } + + // Ok(()) + // } + + // #[test] + // fn test_empty_tree_proof() -> Result<(), MerkleError> { + // let items: Vec<(&str, &str)> = Vec::new(); + // let merkle = merkle_build_test(items)?; + // let key = "x".as_ref(); + + // let proof = merkle.prove(key)?; + // assert!(proof.0.is_empty()); + + // Ok(()) + // } + + // #[test] + // // Tests normal range proof with both edge proofs as the existent proof. + // // The test cases are generated randomly. + // #[allow(clippy::indexing_slicing)] + // fn test_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // for _ in 0..10 { + // let start = rand::thread_rng().gen_range(0..items.len()); + // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + // if end <= start { + // continue; + // } + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end - 1].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let mut keys = Vec::new(); + // let mut vals = Vec::new(); + // for item in items[start..end].iter() { + // keys.push(item.0.as_ref()); + // vals.push(&item.1); + // } + + // merkle.verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals)?; + // } + // Ok(()) + // } + + // #[test] + // // Tests a few cases which the proof is wrong. + // // The prover is expected to detect the error. + // #[allow(clippy::indexing_slicing)] + // fn test_bad_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // for _ in 0..10 { + // let start = rand::thread_rng().gen_range(0..items.len()); + // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + // if end <= start { + // continue; + // } + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end - 1].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let mut keys: Vec<[u8; 32]> = Vec::new(); + // let mut vals: Vec<[u8; 20]> = Vec::new(); + // for item in items[start..end].iter() { + // keys.push(*item.0); + // vals.push(*item.1); + // } + + // let test_case: u32 = rand::thread_rng().gen_range(0..6); + // let index = rand::thread_rng().gen_range(0..end - start); + // match test_case { + // 0 => { + // // Modified key + // keys[index] = rand::thread_rng().gen::<[u8; 32]>(); // In theory it can't be same + // } + // 1 => { + // // Modified val + // vals[index] = rand::thread_rng().gen::<[u8; 20]>(); // In theory it can't be same + // } + // 2 => { + // // Gapped entry slice + // if index == 0 || index == end - start - 1 { + // continue; + // } + // keys.remove(index); + // vals.remove(index); + // } + // 3 => { + // // Out of order + // let index_1 = rand::thread_rng().gen_range(0..end - start); + // let index_2 = rand::thread_rng().gen_range(0..end - start); + // if index_1 == index_2 { + // continue; + // } + // keys.swap(index_1, index_2); + // vals.swap(index_1, index_2); + // } + // 4 => { + // // Set random key to empty, do nothing + // keys[index] = [0; 32]; + // } + // 5 => { + // // Set random value to nil + // vals[index] = [0; 20]; + // } + // _ => unreachable!(), + // } + + // let keys_slice = keys.iter().map(|k| k.as_ref()).collect::>(); + // assert!(merkle + // .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys_slice, vals) + // .is_err()); + // } + + // Ok(()) + // } + + // #[test] + // // Tests normal range proof with two non-existent proofs. + // // The test cases are generated randomly. + // #[allow(clippy::indexing_slicing)] + // fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // for _ in 0..10 { + // let start = rand::thread_rng().gen_range(0..items.len()); + // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + + // if end <= start { + // continue; + // } + + // // Short circuit if the decreased key is same with the previous key + // let first = decrease_key(items[start].0); + // if start != 0 && first.as_ref() == items[start - 1].0.as_ref() { + // continue; + // } + // // Short circuit if the decreased key is underflow + // if &first > items[start].0 { + // continue; + // } + // // Short circuit if the increased key is same with the next key + // let last = increase_key(items[end - 1].0); + // if end != items.len() && last.as_ref() == items[end].0.as_ref() { + // continue; + // } + // // Short circuit if the increased key is overflow + // if &last < items[end - 1].0 { + // continue; + // } + + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let mut keys: Vec<&[u8]> = Vec::new(); + // let mut vals: Vec<[u8; 20]> = Vec::new(); + // for item in items[start..end].iter() { + // keys.push(item.0); + // vals.push(*item.1); + // } + + // merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; + // } + + // // Special case, two edge proofs for two edge key. + // let first = &[0; 32]; + // let last = &[255; 32]; + // let mut proof = merkle.prove(first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); + // let keys = keys.iter().map(|k| k.as_ref()).collect::>(); + // merkle.verify_range_proof(&proof, first, last, keys, vals)?; + + // Ok(()) + // } + + // #[test] + // // Tests such scenarios: + // // - There exists a gap between the first element and the left edge proof + // // - There exists a gap between the last element and the right edge proof + // #[allow(clippy::indexing_slicing)] + // fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // // Case 1 + // let mut start = 100; + // let mut end = 200; + // let first = decrease_key(items[start].0); + + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end - 1].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // start = 105; // Gap created + // let mut keys: Vec<&[u8]> = Vec::new(); + // let mut vals: Vec<[u8; 20]> = Vec::new(); + // // Create gap + // for item in items[start..end].iter() { + // keys.push(item.0); + // vals.push(*item.1); + // } + // assert!(merkle + // .verify_range_proof(&proof, &first, items[end - 1].0, keys, vals) + // .is_err()); + + // // Case 2 + // start = 100; + // end = 200; + // let last = increase_key(items[end - 1].0); + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // end = 195; // Capped slice + // let mut keys: Vec<&[u8]> = Vec::new(); + // let mut vals: Vec<[u8; 20]> = Vec::new(); + // // Create gap + // for item in items[start..end].iter() { + // keys.push(item.0); + // vals.push(*item.1); + // } + // assert!(merkle + // .verify_range_proof(&proof, items[start].0, &last, keys, vals) + // .is_err()); + + // Ok(()) + // } + + // #[test] + // // Tests the proof with only one element. The first edge proof can be existent one or + // // non-existent one. + // #[allow(clippy::indexing_slicing)] + // fn test_one_element_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // // One element with existent edge proof, both edge proofs + // // point to the SAME key. + // let start = 1000; + // let start_proof = merkle.prove(items[start].0)?; + // assert!(!start_proof.0.is_empty()); + + // merkle.verify_range_proof( + // &start_proof, + // items[start].0, + // items[start].0, + // vec![items[start].0], + // vec![&items[start].1], + // )?; + + // // One element with left non-existent edge proof + // let first = decrease_key(items[start].0); + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[start].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof( + // &proof, + // &first, + // items[start].0, + // vec![items[start].0], + // vec![*items[start].1], + // )?; + + // // One element with right non-existent edge proof + // let last = increase_key(items[start].0); + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof( + // &proof, + // items[start].0, + // &last, + // vec![items[start].0], + // vec![*items[start].1], + // )?; + + // // One element with two non-existent edge proofs + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof( + // &proof, + // &first, + // &last, + // vec![items[start].0], + // vec![*items[start].1], + // )?; + + // // Test the mini trie with only a single element. + // let key = rand::thread_rng().gen::<[u8; 32]>(); + // let val = rand::thread_rng().gen::<[u8; 20]>(); + // let merkle = merkle_build_test(vec![(key, val)])?; + + // let first = &[0; 32]; + // let mut proof = merkle.prove(first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&key)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof(&proof, first, &key, vec![&key], vec![val])?; + + // Ok(()) + // } + + // #[test] + // // Tests the range proof with all elements. + // // The edge proofs can be nil. + // #[allow(clippy::indexing_slicing)] + // fn test_all_elements_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let item_iter = items.clone().into_iter(); + // let keys: Vec<&[u8]> = item_iter.clone().map(|item| item.0.as_ref()).collect(); + // let vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); + + // let empty_proof = Proof(HashMap::<[u8; 32], Vec>::new()); + // let empty_key: [u8; 32] = [0; 32]; + // merkle.verify_range_proof( + // &empty_proof, + // &empty_key, + // &empty_key, + // keys.clone(), + // vals.clone(), + // )?; + + // // With edge proofs, it should still work. + // let start = 0; + // let end = &items.len() - 1; + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof( + // &proof, + // items[start].0, + // items[end].0, + // keys.clone(), + // vals.clone(), + // )?; + + // // Even with non-existent edge proofs, it should still work. + // let first = &[0; 32]; + // let last = &[255; 32]; + // let mut proof = merkle.prove(first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // merkle.verify_range_proof(&proof, first, last, keys, vals)?; + + // Ok(()) + // } + + // #[test] + // // Tests the range proof with "no" element. The first edge proof must + // // be a non-existent proof. + // #[allow(clippy::indexing_slicing)] + // fn test_empty_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let cases = [(items.len() - 1, false)]; + // for c in cases.iter() { + // let first = increase_key(items[c.0].0); + // let proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + + // // key and value vectors are intentionally empty. + // let keys: Vec<&[u8]> = Vec::new(); + // let vals: Vec<[u8; 20]> = Vec::new(); + + // if c.1 { + // assert!(merkle + // .verify_range_proof(&proof, &first, &first, keys, vals) + // .is_err()); + // } else { + // merkle.verify_range_proof(&proof, &first, &first, keys, vals)?; + // } + // } + + // Ok(()) + // } + + // #[test] + // // Focuses on the small trie with embedded nodes. If the gapped + // // node is embedded in the trie, it should be detected too. + // #[allow(clippy::indexing_slicing)] + // fn test_gapped_range_proof() -> Result<(), ProofError> { + // let mut items = Vec::new(); + // // Sorted entries + // for i in 0..10_u32 { + // let mut key = [0; 32]; + // for (index, d) in i.to_be_bytes().iter().enumerate() { + // key[index] = *d; + // } + // items.push((key, i.to_be_bytes())); + // } + // let merkle = merkle_build_test(items.clone())?; + + // let first = 2; + // let last = 8; + + // let mut proof = merkle.prove(&items[first].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&items[last - 1].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let middle = (first + last) / 2 - first; + // let (keys, vals): (Vec<&[u8]>, Vec<&[u8; 4]>) = items[first..last] + // .iter() + // .enumerate() + // .filter(|(pos, _)| *pos != middle) + // .map(|(_, item)| (item.0.as_ref(), &item.1)) + // .unzip(); + + // assert!(merkle + // .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) + // .is_err()); + + // Ok(()) + // } + + // #[test] + // // Tests the element is not in the range covered by proofs. + // #[allow(clippy::indexing_slicing)] + // fn test_same_side_proof() -> Result<(), MerkleError> { + // let set = fixed_and_pseudorandom_data(4096); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let pos = 1000; + // let mut last = decrease_key(items[pos].0); + // let mut first = last; + // first = decrease_key(&first); + + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // assert!(merkle + // .verify_range_proof( + // &proof, + // &first, + // &last, + // vec![items[pos].0], + // vec![items[pos].1] + // ) + // .is_err()); + + // first = increase_key(items[pos].0); + // last = first; + // last = increase_key(&last); + + // let mut proof = merkle.prove(&first)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&last)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // assert!(merkle + // .verify_range_proof( + // &proof, + // &first, + // &last, + // vec![items[pos].0], + // vec![items[pos].1] + // ) + // .is_err()); + + // Ok(()) + // } + + // #[test] + // #[allow(clippy::indexing_slicing)] + // // Tests the range starts from zero. + // fn test_single_side_range_proof() -> Result<(), ProofError> { + // for _ in 0..10 { + // let mut set = HashMap::new(); + // for _ in 0..4096_u32 { + // let key = rand::thread_rng().gen::<[u8; 32]>(); + // let val = rand::thread_rng().gen::<[u8; 20]>(); + // set.insert(key, val); + // } + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let cases = vec![0, 1, 100, 1000, items.len() - 1]; + // for case in cases { + // let start = &[0; 32]; + // let mut proof = merkle.prove(start)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[case].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let item_iter = items.clone().into_iter().take(case + 1); + // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); + // let vals = item_iter.map(|item| item.1).collect(); + + // merkle.verify_range_proof(&proof, start, items[case].0, keys, vals)?; + // } + // } + // Ok(()) + // } + + // #[test] + // #[allow(clippy::indexing_slicing)] + // // Tests the range ends with 0xffff...fff. + // fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { + // for _ in 0..10 { + // let mut set = HashMap::new(); + // for _ in 0..1024_u32 { + // let key = rand::thread_rng().gen::<[u8; 32]>(); + // let val = rand::thread_rng().gen::<[u8; 20]>(); + // set.insert(key, val); + // } + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let cases = vec![0, 1, 100, 1000, items.len() - 1]; + // for case in cases { + // let end = &[255; 32]; + // let mut proof = merkle.prove(items[case].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(end)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let item_iter = items.clone().into_iter().skip(case); + // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); + // let vals = item_iter.map(|item| item.1).collect(); + + // merkle.verify_range_proof(&proof, items[case].0, end, keys, vals)?; + // } + // } + // Ok(()) + // } + + // #[test] + // // Tests the range starts with zero and ends with 0xffff...fff. + // fn test_both_sides_range_proof() -> Result<(), ProofError> { + // for _ in 0..10 { + // let mut set = HashMap::new(); + // for _ in 0..4096_u32 { + // let key = rand::thread_rng().gen::<[u8; 32]>(); + // let val = rand::thread_rng().gen::<[u8; 20]>(); + // set.insert(key, val); + // } + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // let start = &[0; 32]; + // let end = &[255; 32]; + + // let mut proof = merkle.prove(start)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(end)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); + // let keys = keys.iter().map(|k| k.as_ref()).collect::>(); + // merkle.verify_range_proof(&proof, start, end, keys, vals)?; + // } + // Ok(()) + // } + + // #[test] + // #[allow(clippy::indexing_slicing)] + // // Tests normal range proof with both edge proofs + // // as the existent proof, but with an extra empty value included, which is a + // // noop technically, but practically should be rejected. + // fn test_empty_value_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(512); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // // Create a new entry with a slightly modified key + // let mid_index = items.len() / 2; + // let key = increase_key(items[mid_index - 1].0); + // let empty_value: [u8; 20] = [0; 20]; + // items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); + + // let start = 1; + // let end = items.len() - 1; + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end - 1].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let item_iter = items.clone().into_iter().skip(start).take(end - start); + // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); + // let vals = item_iter.map(|item| item.1).collect(); + // assert!(merkle + // .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals) + // .is_err()); + + // Ok(()) + // } + + // #[test] + // #[allow(clippy::indexing_slicing)] + // // Tests the range proof with all elements, + // // but with an extra empty value included, which is a noop technically, but + // // practically should be rejected. + // fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { + // let set = fixed_and_pseudorandom_data(512); + // let mut items = Vec::from_iter(set.iter()); + // items.sort(); + // let merkle = merkle_build_test(items.clone())?; + + // // Create a new entry with a slightly modified key + // let mid_index = items.len() / 2; + // let key = increase_key(items[mid_index - 1].0); + // let empty_value: [u8; 20] = [0; 20]; + // items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); + + // let start = 0; + // let end = items.len() - 1; + + // let mut proof = merkle.prove(items[start].0)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(items[end].0)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let item_iter = items.clone().into_iter(); + // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); + // let vals = item_iter.map(|item| item.1).collect(); + // assert!(merkle + // .verify_range_proof(&proof, items[start].0, items[end].0, keys, vals) + // .is_err()); + + // Ok(()) + // } + + // #[test] + // fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { + // let items = vec![ + // ( + // hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") + // .expect("Decoding failed"), + // hex::decode("02").expect("Decoding failed"), + // ), + // ( + // hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") + // .expect("Decoding failed"), + // hex::decode("03").expect("Decoding failed"), + // ), + // ]; + // let merkle = merkle_build_test(items.clone())?; + + // let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + // .expect("Decoding failed"); + // let end = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + // .expect("Decoding failed"); + + // let mut proof = merkle.prove(&start)?; + // assert!(!proof.0.is_empty()); + // let end_proof = merkle.prove(&end)?; + // assert!(!end_proof.0.is_empty()); + // proof.extend(end_proof); + + // let keys = items.iter().map(|item| item.0.as_ref()).collect(); + // let vals = items.iter().map(|item| item.1.clone()).collect(); + + // merkle.verify_range_proof(&proof, &start, &end, keys, vals)?; + + // Ok(()) + // } + + // #[test] + // #[allow(clippy::indexing_slicing)] + // // Tests a malicious proof, where the proof is more or less the + // // whole trie. This is to match corresponding test in geth. + // fn test_bloadted_range_proof() -> Result<(), ProofError> { + // // Use a small trie + // let mut items = Vec::new(); + // for i in 0..100_u32 { + // let mut key: [u8; 32] = [0; 32]; + // let mut value: [u8; 20] = [0; 20]; + // for (index, d) in i.to_be_bytes().iter().enumerate() { + // key[index] = *d; + // value[index] = *d; + // } + // items.push((key, value)); + // } + // let merkle = merkle_build_test(items.clone())?; + + // // In the 'malicious' case, we add proofs for every single item + // // (but only one key/value pair used as leaf) + // let mut proof = Proof(HashMap::new()); + // let mut keys = Vec::new(); + // let mut vals = Vec::new(); + // for (i, item) in items.iter().enumerate() { + // let cur_proof = merkle.prove(&item.0)?; + // assert!(!cur_proof.0.is_empty()); + // proof.extend(cur_proof); + // if i == 50 { + // keys.push(item.0.as_ref()); + // vals.push(item.1); + // } + // } + + // merkle.verify_range_proof(&proof, keys[0], keys[keys.len() - 1], keys.clone(), vals)?; + + // Ok(()) + // } + + // generate pseudorandom data, but prefix it with some known data + // The number of fixed data points is 100; you specify how much random data you want + // #[allow(clippy::indexing_slicing)] + // fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { + // let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); + // for i in 0..100_u32 { + // let mut key: [u8; 32] = [0; 32]; + // let mut value: [u8; 20] = [0; 20]; + // for (index, d) in i.to_be_bytes().iter().enumerate() { + // key[index] = *d; + // value[index] = *d; + // } + // items.insert(key, value); + + // let mut more_key: [u8; 32] = [0; 32]; + // for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { + // more_key[index] = *d; + // } + // items.insert(more_key, value); + // } + + // // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. + // let seed = std::env::var("FIREWOOD_TEST_SEED") + // .ok() + // .map_or_else( + // || None, + // |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + // ) + // .unwrap_or_else(|| thread_rng().gen()); + + // // the test framework will only render this in verbose mode or if the test fails + // // to re-run the test when it fails, just specify the seed instead of randomly + // // selecting one + // eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + // let mut r = StdRng::seed_from_u64(seed); + // for _ in 0..random_count { + // let key = r.gen::<[u8; 32]>(); + // let val = r.gen::<[u8; 20]>(); + // items.insert(key, val); + // } + // items + // } + + // fn increase_key(key: &[u8; 32]) -> [u8; 32] { + // let mut new_key = *key; + // for ch in new_key.iter_mut().rev() { + // let overflow; + // (*ch, overflow) = ch.overflowing_add(1); + // if !overflow { + // break; + // } + // } + // new_key + // } + + // fn decrease_key(key: &[u8; 32]) -> [u8; 32] { + // let mut new_key = *key; + // for ch in new_key.iter_mut().rev() { + // let overflow; + // (*ch, overflow) = ch.overflowing_sub(1); + // if !overflow { + // break; + // } + // } + // new_key + // } } diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs deleted file mode 100644 index 2636711987de..000000000000 --- a/firewood/src/merkle/node.rs +++ /dev/null @@ -1,906 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::{ - logger::trace, - merkle::nibbles_to_bytes_iter, - shale::{compact::Store, disk_address::DiskAddress, LinearStore, ShaleError, Storable}, -}; -use bincode::{Error, Options}; -use bitflags::bitflags; -use bytemuck::{CheckedBitPattern, NoUninit, Pod, Zeroable}; -use enum_as_inner::EnumAsInner; -use serde::{ - ser::{SerializeSeq, SerializeTuple}, - Deserialize, Serialize, -}; -use sha3::{Digest, Keccak256}; -use std::{ - fmt::Debug, - io::{Cursor, Write}, - marker::PhantomData, - mem::size_of, - sync::{ - atomic::{AtomicBool, Ordering}, - OnceLock, - }, - vec, -}; - -mod branch; -mod leaf; -mod path; - -pub use branch::BranchNode; -pub use leaf::{LeafNode, SIZE as LEAF_NODE_SIZE}; -pub use path::Path; - -use crate::nibbles::Nibbles; - -use super::{TrieHash, TRIE_HASH_LEN}; - -bitflags! { - // should only ever be the size of a nibble - struct Flags: u8 { - const ODD_LEN = 0b0001; - } -} - -#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] -pub enum NodeType { - Branch(Box), - Leaf(LeafNode), -} - -impl NodeType { - pub fn decode(buf: &[u8]) -> Result { - let items: Vec> = bincode::DefaultOptions::new().deserialize(buf)?; - - match items.len() { - LEAF_NODE_SIZE => { - let mut items = items.into_iter(); - - #[allow(clippy::unwrap_used)] - let decoded_key: Vec = items.next().unwrap(); - - let decoded_key_nibbles = Nibbles::<0>::new(&decoded_key); - - let cur_key_path = Path::from_nibbles(decoded_key_nibbles.into_iter()); - - let cur_key = cur_key_path.into_inner(); - #[allow(clippy::unwrap_used)] - let value: Vec = items.next().unwrap(); - - Ok(NodeType::Leaf(LeafNode::new(cur_key, value))) - } - // TODO: add path - BranchNode::MSIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?.into())), - size => Err(Box::new(bincode::ErrorKind::Custom(format!( - "invalid size: {size}" - )))), - } - } - - pub fn encode(&self, store: &Store) -> Vec { - match &self { - NodeType::Leaf(n) => n.encode(), - NodeType::Branch(n) => n.encode(store), - } - } - - pub fn path_mut(&mut self) -> &mut Path { - match self { - NodeType::Branch(u) => &mut u.partial_path, - NodeType::Leaf(node) => &mut node.partial_path, - } - } - - pub fn set_path(&mut self, path: Path) { - match self { - NodeType::Branch(u) => u.partial_path = path, - NodeType::Leaf(node) => node.partial_path = path, - } - } - - pub fn set_value(&mut self, value: Vec) { - match self { - NodeType::Branch(u) => u.value = Some(value), - NodeType::Leaf(node) => node.value = value, - } - } -} - -#[derive(Debug)] -pub struct Node { - pub(super) root_hash: OnceLock, - encoded: OnceLock>, - is_encoded_longer_than_hash_len: OnceLock, - // lazy_dirty is an atomicbool, but only writers ever set it - // Therefore, we can always use Relaxed ordering. It's atomic - // just to ensure Sync + Send. - lazy_dirty: AtomicBool, - pub(super) inner: NodeType, -} - -impl Eq for Node {} - -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - let is_dirty = self.is_dirty(); - - let Node { - root_hash, - encoded, - is_encoded_longer_than_hash_len: _, - lazy_dirty: _, - inner, - } = self; - *root_hash == other.root_hash - && *encoded == other.encoded - && is_dirty == other.is_dirty() - && *inner == other.inner - } -} - -impl Clone for Node { - fn clone(&self) -> Self { - Self { - root_hash: self.root_hash.clone(), - is_encoded_longer_than_hash_len: self.is_encoded_longer_than_hash_len.clone(), - encoded: self.encoded.clone(), - lazy_dirty: AtomicBool::new(self.is_dirty()), - inner: self.inner.clone(), - } - } -} - -impl From for Node { - fn from(inner: NodeType) -> Self { - let mut s = Self { - root_hash: OnceLock::new(), - encoded: OnceLock::new(), - is_encoded_longer_than_hash_len: OnceLock::new(), - inner, - lazy_dirty: AtomicBool::new(false), - }; - s.rehash(); - s - } -} - -bitflags! { - #[derive(Debug, Default, Clone, Copy, Pod, Zeroable)] - #[repr(transparent)] - struct NodeAttributes: u8 { - const HAS_ROOT_HASH = 0b001; - const ENCODED_LENGTH_IS_KNOWN = 0b010; - const ENCODED_IS_LONG = 0b110; - } -} - -impl Node { - pub(super) fn max_branch_node_size() -> u64 { - let max_size: OnceLock = OnceLock::new(); - *max_size.get_or_init(|| { - Self { - root_hash: OnceLock::new(), - encoded: OnceLock::new(), - is_encoded_longer_than_hash_len: OnceLock::new(), - inner: NodeType::Branch( - BranchNode { - partial_path: vec![].into(), - children: [Some(DiskAddress::null()); BranchNode::MAX_CHILDREN], - value: Some(Vec::new()), - children_encoded: Default::default(), - } - .into(), - ), - lazy_dirty: AtomicBool::new(false), - } - .serialized_len() - }) - } - - pub(super) fn get_encoded(&self, store: &Store) -> &[u8] { - self.encoded.get_or_init(|| self.inner.encode(store)) - } - - pub(super) fn get_root_hash(&self, store: &Store) -> &TrieHash { - self.root_hash.get_or_init(|| { - self.set_dirty(true); - TrieHash(Keccak256::digest(self.get_encoded(store)).into()) - }) - } - - fn is_encoded_longer_than_hash_len(&self, store: &Store) -> bool { - *self - .is_encoded_longer_than_hash_len - .get_or_init(|| self.get_encoded(store).len() >= TRIE_HASH_LEN) - } - - pub(super) fn rehash(&mut self) { - self.encoded = OnceLock::new(); - self.is_encoded_longer_than_hash_len = OnceLock::new(); - self.root_hash = OnceLock::new(); - } - - pub fn from_branch>>(node: B) -> Self { - Self::from(NodeType::Branch(node.into())) - } - - pub fn from_leaf(leaf: LeafNode) -> Self { - Self::from(NodeType::Leaf(leaf)) - } - - pub const fn inner(&self) -> &NodeType { - &self.inner - } - - pub fn inner_mut(&mut self) -> &mut NodeType { - &mut self.inner - } - - pub(super) fn new_from_hash( - root_hash: Option, - encoded: Option>, - is_encoded_longer_than_hash_len: Option, - inner: NodeType, - ) -> Self { - Self { - root_hash: match root_hash { - Some(h) => OnceLock::from(h), - None => OnceLock::new(), - }, - encoded: match encoded.filter(|encoded| !encoded.is_empty()) { - Some(e) => OnceLock::from(e), - None => OnceLock::new(), - }, - is_encoded_longer_than_hash_len: match is_encoded_longer_than_hash_len { - Some(v) => OnceLock::from(v), - None => OnceLock::new(), - }, - inner, - lazy_dirty: AtomicBool::new(false), - } - } - - pub(super) fn is_dirty(&self) -> bool { - self.lazy_dirty.load(Ordering::Relaxed) - } - - pub(super) fn set_dirty(&self, is_dirty: bool) { - self.lazy_dirty.store(is_dirty, Ordering::Relaxed) - } - - pub(crate) fn as_branch_mut(&mut self) -> &mut Box { - self.inner_mut() - .as_branch_mut() - .expect("must be a branch node") - } -} - -#[derive(Clone, Copy, CheckedBitPattern, NoUninit)] -#[repr(C, packed)] -struct Meta { - root_hash: [u8; TRIE_HASH_LEN], - attrs: NodeAttributes, - encoded_len: u64, - encoded: [u8; TRIE_HASH_LEN], - type_id: NodeTypeId, -} - -impl Meta { - const SIZE: usize = size_of::(); -} - -mod type_id { - use super::{CheckedBitPattern, NoUninit, NodeType}; - use crate::shale::ShaleError; - - #[derive(Clone, Copy, CheckedBitPattern, NoUninit)] - #[repr(u8)] - pub enum NodeTypeId { - Branch = 0, - Leaf = 1, - } - - impl TryFrom for NodeTypeId { - type Error = ShaleError; - - fn try_from(value: u8) -> Result { - bytemuck::checked::try_cast::<_, Self>(value).map_err(|_| ShaleError::InvalidNodeType) - } - } - - impl From<&NodeType> for NodeTypeId { - fn from(node_type: &NodeType) -> Self { - match node_type { - NodeType::Branch(_) => NodeTypeId::Branch, - NodeType::Leaf(_) => NodeTypeId::Leaf, - } - } - } -} - -use type_id::NodeTypeId; - -impl Storable for Node { - fn deserialize(offset: usize, mem: &T) -> Result { - let meta_raw = - mem.get_view(offset, Meta::SIZE as u64) - .ok_or(ShaleError::InvalidCacheView { - offset, - size: Meta::SIZE as u64, - })?; - let meta_raw = meta_raw.as_deref(); - - let meta = bytemuck::checked::try_from_bytes::(&meta_raw) - .map_err(|_| ShaleError::InvalidNodeMeta)?; - - let Meta { - root_hash, - attrs, - encoded_len, - encoded, - type_id, - } = *meta; - - trace!("[{mem:p}] Deserializing node at {offset}"); - - let offset = offset + Meta::SIZE; - - let root_hash = if attrs.contains(NodeAttributes::HAS_ROOT_HASH) { - Some(TrieHash(root_hash)) - } else { - None - }; - - let encoded = if encoded_len > 0 { - Some(encoded.iter().take(encoded_len as usize).copied().collect()) - } else { - None - }; - - let is_encoded_longer_than_hash_len = - if attrs.contains(NodeAttributes::ENCODED_LENGTH_IS_KNOWN) { - attrs.contains(NodeAttributes::ENCODED_IS_LONG).into() - } else { - None - }; - - match type_id { - NodeTypeId::Branch => { - let inner = NodeType::Branch(Box::new(BranchNode::deserialize(offset, mem)?)); - - Ok(Self::new_from_hash( - root_hash, - encoded, - is_encoded_longer_than_hash_len, - inner, - )) - } - - NodeTypeId::Leaf => { - let inner = NodeType::Leaf(LeafNode::deserialize(offset, mem)?); - let node = - Self::new_from_hash(root_hash, encoded, is_encoded_longer_than_hash_len, inner); - - Ok(node) - } - } - } - - fn serialized_len(&self) -> u64 { - Meta::SIZE as u64 - + match &self.inner { - NodeType::Branch(n) => n.serialized_len(), - NodeType::Leaf(n) => n.serialized_len(), - } - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - trace!("[{self:p}] Serializing node"); - let mut cursor = Cursor::new(to); - - let (mut attrs, root_hash) = match self.root_hash.get() { - Some(hash) => (NodeAttributes::HAS_ROOT_HASH, hash.0), - None => Default::default(), - }; - - let encoded = self - .encoded - .get() - .filter(|encoded| encoded.len() < TRIE_HASH_LEN); - - let encoded_len = encoded.map(Vec::len).unwrap_or(0) as u64; - - if let Some(&is_encoded_longer_than_hash_len) = self.is_encoded_longer_than_hash_len.get() { - attrs.insert(if is_encoded_longer_than_hash_len { - NodeAttributes::ENCODED_IS_LONG - } else { - NodeAttributes::ENCODED_LENGTH_IS_KNOWN - }); - } - - let encoded = std::array::from_fn({ - let mut encoded = encoded.into_iter().flatten().copied(); - move |_| encoded.next().unwrap_or(0) - }); - - let type_id = NodeTypeId::from(&self.inner); - - let meta = Meta { - root_hash, - attrs, - encoded_len, - encoded, - type_id, - }; - - cursor.write_all(bytemuck::bytes_of(&meta))?; - - match &self.inner { - NodeType::Branch(n) => { - let pos = cursor.position() as usize; - - #[allow(clippy::indexing_slicing)] - n.serialize(&mut cursor.get_mut()[pos..]) - } - - NodeType::Leaf(n) => { - let pos = cursor.position() as usize; - - #[allow(clippy::indexing_slicing)] - n.serialize(&mut cursor.get_mut()[pos..]) - } - } - } -} - -/// Contains the fields that we include in a node's hash. -/// If this is a leaf node, `children` is empty and `value` is Some. -/// If this is a branch node, `children` is non-empty. -#[derive(Debug, Eq)] -pub struct EncodedNode { - pub(crate) partial_path: Path, - /// If a child is None, it doesn't exist. - /// If it's Some, it's the value or value hash of the child. - pub(crate) children: [Option>; BranchNode::MAX_CHILDREN], - pub(crate) value: Option>, - pub(crate) phantom: PhantomData, -} - -// driving this adds an unnecessary bound, T: PartialEq -// PhantomData is PartialEq for all T -impl PartialEq for EncodedNode { - fn eq(&self, other: &Self) -> bool { - let Self { - partial_path, - children, - value, - phantom: _, - } = self; - partial_path == &other.partial_path && children == &other.children && value == &other.value - } -} - -// Note that the serializer passed in should always be the same type as T in EncodedNode. -impl Serialize for EncodedNode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let chd: Vec<(u64, Vec)> = self - .children - .iter() - .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|c| (i as u64, c))) - .map(|(i, c)| { - if c.len() >= TRIE_HASH_LEN { - (i, Keccak256::digest(c).to_vec()) - } else { - (i, c.to_vec()) - } - }) - .collect(); - - let value = self.value.as_deref(); - - let path: Vec = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); - - let mut s = serializer.serialize_tuple(3)?; - - s.serialize_element(&chd)?; - s.serialize_element(&value)?; - s.serialize_element(&path)?; - - s.end() - } -} - -impl<'de> Deserialize<'de> for EncodedNode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let chd: Vec<(u64, Vec)>; - let value: Option>; - let path: Vec; - - (chd, value, path) = Deserialize::deserialize(deserializer)?; - - let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - - let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - #[allow(clippy::indexing_slicing)] - for (i, chd) in chd { - children[i as usize] = Some(chd); - } - - Ok(Self { - partial_path: path, - children, - value, - phantom: PhantomData, - }) - } -} - -// Note that the serializer passed in should always be the same type as T in EncodedNode. -impl Serialize for EncodedNode { - fn serialize(&self, serializer: S) -> Result { - let mut list = <[Vec; BranchNode::MAX_CHILDREN + 2]>::default(); - let children = self - .children - .iter() - .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|c| (i, c))); - - #[allow(clippy::indexing_slicing)] - for (i, child) in children { - if child.len() >= TRIE_HASH_LEN { - let serialized_hash = Keccak256::digest(child).to_vec(); - list[i] = serialized_hash; - } else { - list[i] = child.to_vec(); - } - } - - if let Some(val) = &self.value { - list[BranchNode::MAX_CHILDREN].clone_from(val); - } - - let serialized_path = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); - list[BranchNode::MAX_CHILDREN + 1] = serialized_path; - - let mut seq = serializer.serialize_seq(Some(list.len()))?; - - for e in list { - seq.serialize_element(&e)?; - } - - seq.end() - } -} - -impl<'de> Deserialize<'de> for EncodedNode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let mut items: Vec> = Deserialize::deserialize(deserializer)?; - let len = items.len(); - - match len { - LEAF_NODE_SIZE => { - let mut items = items.into_iter(); - let Some(path) = items.next() else { - return Err(D::Error::custom( - "incorrect encoded type for leaf node path", - )); - }; - let Some(value) = items.next() else { - return Err(D::Error::custom( - "incorrect encoded type for leaf node value", - )); - }; - let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - let children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - Ok(Self { - partial_path: path, - children, - value: Some(value), - phantom: PhantomData, - }) - } - - BranchNode::MSIZE => { - let path = items.pop().expect("length was checked above"); - let path = Path::from_nibbles(Nibbles::<0>::new(&path).into_iter()); - - let value = items.pop().expect("length was checked above"); - let value = if value.is_empty() { None } else { Some(value) }; - - let mut children: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - - for (i, chd) in items.into_iter().enumerate() { - #[allow(clippy::indexing_slicing)] - (children[i] = Some(chd).filter(|chd| !chd.is_empty())); - } - - Ok(Self { - partial_path: path, - children, - value, - phantom: PhantomData, - }) - } - size => Err(D::Error::custom(format!("invalid size: {size}"))), - } - } -} - -pub trait BinarySerde { - type SerializeError: serde::ser::Error; - type DeserializeError: serde::de::Error; - - fn new() -> Self; - - fn serialize(t: &T) -> Result, Self::SerializeError> - where - Self: Sized, - { - Self::new().serialize_impl(t) - } - - fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result - where - Self: Sized, - { - Self::new().deserialize_impl(bytes) - } - - fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError>; - fn deserialize_impl<'de, T: Deserialize<'de>>( - &self, - bytes: &'de [u8], - ) -> Result; -} - -#[derive(Default)] -pub struct Bincode(pub bincode::DefaultOptions); - -impl Debug for Bincode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "[bincode::DefaultOptions]") - } -} - -impl BinarySerde for Bincode { - type SerializeError = bincode::Error; - type DeserializeError = Self::SerializeError; - - fn new() -> Self { - Self(bincode::DefaultOptions::new()) - } - - fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError> { - self.0.serialize(t) - } - - fn deserialize_impl<'de, T: Deserialize<'de>>( - &self, - bytes: &'de [u8], - ) -> Result { - self.0.deserialize(bytes) - } -} - -#[derive(Default)] -pub struct PlainCodec(pub bincode::DefaultOptions); - -impl Debug for PlainCodec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "PlainCodec") - } -} - -impl BinarySerde for PlainCodec { - type SerializeError = bincode::Error; - type DeserializeError = Self::SerializeError; - - fn new() -> Self { - Self(bincode::DefaultOptions::new()) - } - - fn serialize_impl(&self, t: &T) -> Result, Self::SerializeError> { - // Serializes the object directly into a Writer without include the length. - let mut writer = Vec::new(); - self.0.serialize_into(&mut writer, t)?; - Ok(writer) - } - - fn deserialize_impl<'de, T: Deserialize<'de>>( - &self, - bytes: &'de [u8], - ) -> Result { - self.0.deserialize(bytes) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::shale::in_mem::InMemLinearStore; - use std::iter::repeat; - use test_case::{test_case, test_matrix}; - - #[test_matrix( - [Nil, [0x00; TRIE_HASH_LEN]], - [Nil, vec![], vec![0x01], (0..TRIE_HASH_LEN as u8).collect::>(), (0..33).collect::>()], - [Nil, false, true] - )] - fn cached_node_data( - root_hash: impl Into>, - encoded: impl Into>>, - is_encoded_longer_than_hash_len: impl Into>, - ) { - let leaf = NodeType::Leaf(LeafNode::new(Path(vec![1, 2, 3]), vec![4, 5])); - let branch = NodeType::Branch(Box::new(BranchNode { - partial_path: vec![].into(), - children: [Some(DiskAddress::from(1)); BranchNode::MAX_CHILDREN], - value: Some(vec![1, 2, 3]), - children_encoded: std::array::from_fn(|_| Some(vec![1])), - })); - - let root_hash = root_hash.into().map(TrieHash); - let encoded = encoded.into(); - let is_encoded_longer_than_hash_len = is_encoded_longer_than_hash_len.into(); - - let node = Node::new_from_hash( - root_hash, - encoded.clone(), - is_encoded_longer_than_hash_len, - leaf, - ); - - check_node_encoding(node); - - let node = Node::new_from_hash( - root_hash, - encoded.clone(), - is_encoded_longer_than_hash_len, - branch, - ); - - check_node_encoding(node); - } - - #[test_matrix( - (0..0, 0..15, 0..16, 0..31, 0..32), - [0..0, 0..16, 0..32] - )] - fn leaf_node>(path: Iter, value: Iter) { - let node = Node::from_leaf(LeafNode::new( - Path(path.map(|x| x & 0xf).collect()), - value.collect::>(), - )); - - check_node_encoding(node); - } - - #[test_case(&[])] - #[test_case(&[0x00])] - #[test_case(&[0x0F])] - #[test_case(&[0x00, 0x00])] - #[test_case(&[0x01, 0x02])] - #[test_case(&[0x00,0x0F])] - #[test_case(&[0x0F,0x0F])] - #[test_case(&[0x0F,0x01,0x0F])] - fn encoded_branch_node_bincode_serialize(path_nibbles: &[u8]) -> Result<(), Error> { - let node = EncodedNode:: { - partial_path: Path(path_nibbles.to_vec()), - children: Default::default(), - value: Some(vec![1, 2, 3, 4]), - phantom: PhantomData, - }; - - let node_bytes = Bincode::serialize(&node)?; - - let deserialized_node: EncodedNode = Bincode::deserialize(&node_bytes)?; - - assert_eq!(&node, &deserialized_node); - - Ok(()) - } - - #[test_matrix( - [&[], &[0xf], &[0xf, 0xf]], - [vec![], vec![1,0,0,0,0,0,0,1], vec![1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], repeat(1).take(16).collect()], - [Nil, 0, 15], - [ - std::array::from_fn(|_| None), - std::array::from_fn(|_| Some(vec![1])), - [Some(vec![1]), None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(vec![1])], - std::array::from_fn(|_| Some(vec![1; 32])), - std::array::from_fn(|_| Some(vec![1; 33])) - ] - )] - fn branch_encoding( - path: &[u8], - children: Vec, - value: impl Into>, - children_encoded: [Option>; BranchNode::MAX_CHILDREN], - ) { - let partial_path = Path(path.iter().copied().map(|x| x & 0xf).collect()); - - let mut children = children.into_iter().map(|x| { - if x == 0 { - None - } else { - Some(DiskAddress::from(x)) - } - }); - - let children = std::array::from_fn(|_| children.next().flatten()); - - let value = value - .into() - .map(|x| std::iter::repeat(x).take(x as usize).collect()); - - let node = Node::from_branch(BranchNode { - partial_path, - children, - value, - children_encoded, - }); - - check_node_encoding(node); - } - - fn check_node_encoding(node: Node) { - let serialized_len = node.serialized_len(); - - let mut bytes = vec![0; serialized_len as usize]; - node.serialize(&mut bytes).expect("node should serialize"); - - let mut mem = InMemLinearStore::new(serialized_len, 0); - mem.write(0, &bytes).expect("write should succed"); - - let mut hydrated_node = Node::deserialize(0, &mem).expect("node should deserialize"); - - let encoded = node - .encoded - .get() - .filter(|encoded| encoded.len() >= TRIE_HASH_LEN); - - match encoded { - // long-encoded won't be serialized - Some(encoded) if hydrated_node.encoded.get().is_none() => { - hydrated_node.encoded = OnceLock::from(encoded.clone()); - } - _ => (), - } - - assert_eq!(node, hydrated_node); - } - - struct Nil; - - macro_rules! impl_nil_for { - // match a comma separated list of types - ($($t:ty),* $(,)?) => { - $( - impl From for Option<$t> { - fn from(_val: Nil) -> Self { - None - } - } - )* - }; - } - - impl_nil_for!([u8; 32], Vec, usize, u8, bool); -} diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs deleted file mode 100644 index 1e46ed31d49d..000000000000 --- a/firewood/src/merkle/node/branch.rs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::Node; -use crate::{ - merkle::{nibbles_to_bytes_iter, to_nibble_array, Path}, - nibbles::Nibbles, - shale::{compact::Store, DiskAddress, LinearStore, ShaleError, Storable}, -}; -use bincode::{Error, Options}; -use serde::de::Error as DeError; -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Read, Write}, - mem::size_of, -}; - -type PathLen = u8; -pub type ValueLen = u32; -pub type EncodedChildLen = u8; - -const MAX_CHILDREN: usize = 16; - -#[derive(PartialEq, Eq, Clone)] -pub struct BranchNode { - pub(crate) partial_path: Path, - pub(crate) children: [Option; MAX_CHILDREN], - pub(crate) value: Option>, - pub(crate) children_encoded: [Option>; MAX_CHILDREN], -} - -impl Debug for BranchNode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - write!(f, "[Branch")?; - write!(f, r#" path="{:?}""#, self.partial_path)?; - - for (i, c) in self.children.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {c:?})")?; - } - } - - for (i, c) in self.children_encoded.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {:?})", c)?; - } - } - - write!( - f, - " v={}]", - match &self.value { - Some(v) => hex::encode(&**v), - None => "nil".to_string(), - } - ) - } -} - -impl BranchNode { - pub const MAX_CHILDREN: usize = MAX_CHILDREN; - pub const MSIZE: usize = Self::MAX_CHILDREN + 2; - - pub const fn value(&self) -> &Option> { - &self.value - } - - pub const fn chd(&self) -> &[Option; Self::MAX_CHILDREN] { - &self.children - } - - pub fn chd_mut(&mut self) -> &mut [Option; Self::MAX_CHILDREN] { - &mut self.children - } - - pub const fn chd_encode(&self) -> &[Option>; Self::MAX_CHILDREN] { - &self.children_encoded - } - - pub fn chd_encoded_mut(&mut self) -> &mut [Option>; Self::MAX_CHILDREN] { - &mut self.children_encoded - } - - pub(super) fn decode(buf: &[u8]) -> Result { - let mut items: Vec> = bincode::DefaultOptions::new().deserialize(buf)?; - - let path = items.pop().ok_or(Error::custom("Invalid Branch Node"))?; - let path = Nibbles::<0>::new(&path); - let path = Path::from_nibbles(path.into_iter()); - - // we've already validated the size, that's why we can safely unwrap - #[allow(clippy::unwrap_used)] - let value = items.pop().unwrap(); - // Extract the value of the branch node and set to None if it's an empty Vec - let value = Some(value).filter(|value| !value.is_empty()); - - // encode all children. - let mut chd_encoded: [Option>; Self::MAX_CHILDREN] = Default::default(); - - // we popped the last element, so their should only be NBRANCH items left - for (i, chd) in items.into_iter().enumerate() { - #[allow(clippy::indexing_slicing)] - (chd_encoded[i] = Some(chd).filter(|value| !value.is_empty())); - } - - Ok(BranchNode { - partial_path: path, - children: [None; Self::MAX_CHILDREN], - value, - children_encoded: chd_encoded, - }) - } - - pub(super) fn encode(&self, store: &Store) -> Vec { - // path + children + value - let mut list = <[Vec; Self::MSIZE]>::default(); - - for (i, c) in self.children.iter().enumerate() { - match c { - Some(c) => { - #[allow(clippy::unwrap_used)] - let mut c_ref = store.get_item(*c).unwrap(); - - #[allow(clippy::unwrap_used)] - if c_ref.is_encoded_longer_than_hash_len(store) { - #[allow(clippy::indexing_slicing)] - (list[i] = c_ref.get_root_hash(store).to_vec()); - - // See struct docs for ordering requirements - if c_ref.is_dirty() { - c_ref.write(|_| {}).unwrap(); - c_ref.set_dirty(false); - } - } else { - let child_encoded = c_ref.get_encoded(store); - #[allow(clippy::indexing_slicing)] - (list[i] = child_encoded.to_vec()); - } - } - - // TODO: - // we need a better solution for this. This is only used for reconstructing a - // merkle-tree in memory. The proper way to do it is to abstract a trait for nodes - // but that's a heavy lift. - // TODO: - // change the data-structure children: [(Option, Option>); Self::MAX_CHILDREN] - None => { - // Check if there is already a calculated encoded value for the child, which - // can happen when manually constructing a trie from proof. - #[allow(clippy::indexing_slicing)] - if let Some(v) = &self.children_encoded[i] { - #[allow(clippy::indexing_slicing)] - list[i].clone_from(v); - } - } - }; - } - - #[allow(clippy::unwrap_used)] - if let Some(val) = &self.value { - list[Self::MAX_CHILDREN].clone_from(val); - } - - #[allow(clippy::unwrap_used)] - let path = nibbles_to_bytes_iter(&self.partial_path.encode()).collect::>(); - - list[Self::MAX_CHILDREN + 1] = path; - - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .expect("serializing `Encoded` to always succeed") - } -} - -impl Storable for BranchNode { - fn serialized_len(&self) -> u64 { - let children_len = Self::MAX_CHILDREN as u64 * DiskAddress::SERIALIZED_LEN; - let value_len = optional_value_len::(self.value.as_deref()); - let children_encoded_len = self.children_encoded.iter().fold(0, |len, child| { - len + optional_value_len::(child.as_ref()) - }); - let path_len_size = size_of::() as u64; - let path_len = self.partial_path.serialized_len(); - - children_len + value_len + children_encoded_len + path_len_size + path_len - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { - let mut cursor = Cursor::new(to); - - let path: Vec = nibbles_to_bytes_iter(&self.partial_path.encode()).collect(); - cursor.write_all(&[path.len() as PathLen])?; - cursor.write_all(&path)?; - - for child in &self.children { - let bytes = child.map(|addr| addr.to_le_bytes()).unwrap_or_default(); - cursor.write_all(&bytes)?; - } - - let (value_len, value) = self - .value - .as_ref() - .map(|val| (val.len() as ValueLen, &**val)) - .unwrap_or((ValueLen::MAX, &[])); - - cursor.write_all(&value_len.to_le_bytes())?; - cursor.write_all(value)?; - - for child_encoded in &self.children_encoded { - let (child_len, child) = child_encoded - .as_ref() - .map(|child| (child.len() as EncodedChildLen, child.as_slice())) - .unwrap_or((EncodedChildLen::MIN, &[])); - - cursor.write_all(&child_len.to_le_bytes())?; - cursor.write_all(child)?; - } - - Ok(()) - } - - fn deserialize( - mut addr: usize, - mem: &T, - ) -> Result { - const PATH_LEN_SIZE: u64 = size_of::() as u64; - const VALUE_LEN_SIZE: usize = size_of::(); - const BRANCH_HEADER_SIZE: u64 = - BranchNode::MAX_CHILDREN as u64 * DiskAddress::SERIALIZED_LEN + VALUE_LEN_SIZE as u64; - - let path_len = mem - .get_view(addr, PATH_LEN_SIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: PATH_LEN_SIZE, - })? - .as_deref(); - - addr += PATH_LEN_SIZE as usize; - - let path_len = { - let mut buf = [0u8; PATH_LEN_SIZE as usize]; - let mut cursor = Cursor::new(path_len); - cursor.read_exact(buf.as_mut())?; - - PathLen::from_le_bytes(buf) as u64 - }; - - let path = mem - .get_view(addr, path_len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: path_len, - })? - .as_deref(); - - addr += path_len as usize; - - let path: Vec = path.into_iter().flat_map(to_nibble_array).collect(); - let path = Path::decode(&path); - - let node_raw = - mem.get_view(addr, BRANCH_HEADER_SIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: BRANCH_HEADER_SIZE, - })?; - - addr += BRANCH_HEADER_SIZE as usize; - - let mut cursor = Cursor::new(node_raw.as_deref()); - let mut children = [None; BranchNode::MAX_CHILDREN]; - let mut buf = [0u8; DiskAddress::SERIALIZED_LEN as usize]; - - for child in &mut children { - cursor.read_exact(&mut buf)?; - *child = Some(usize::from_le_bytes(buf)) - .filter(|addr| *addr != 0) - .map(DiskAddress::from); - } - - let raw_len = { - let mut buf = [0; VALUE_LEN_SIZE]; - cursor.read_exact(&mut buf)?; - Some(ValueLen::from_le_bytes(buf)) - .filter(|len| *len != ValueLen::MAX) - .map(|len| len as u64) - }; - - let value = match raw_len { - Some(len) => { - let value = mem - .get_view(addr, len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: len, - })?; - - addr += len as usize; - - Some(value.as_deref()) - } - None => None, - }; - - let mut children_encoded: [Option>; BranchNode::MAX_CHILDREN] = Default::default(); - - for child in &mut children_encoded { - const ENCODED_CHILD_LEN_SIZE: u64 = size_of::() as u64; - - let len_raw = mem - .get_view(addr, ENCODED_CHILD_LEN_SIZE) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: ENCODED_CHILD_LEN_SIZE, - })? - .as_deref(); - - let mut cursor = Cursor::new(len_raw); - - let len = { - let mut buf = [0; ENCODED_CHILD_LEN_SIZE as usize]; - cursor.read_exact(buf.as_mut())?; - EncodedChildLen::from_le_bytes(buf) as u64 - }; - - addr += ENCODED_CHILD_LEN_SIZE as usize; - - if len == 0 { - continue; - } - - let encoded = mem - .get_view(addr, len) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: len, - })? - .as_deref(); - - addr += len as usize; - - *child = Some(encoded); - } - - let node = BranchNode { - partial_path: path, - children, - value, - children_encoded, - }; - - Ok(node) - } -} - -fn optional_value_len>(value: Option) -> u64 { - size_of::() as u64 - + value - .as_ref() - .map_or(0, |value| value.as_ref().len() as u64) -} diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs deleted file mode 100644 index c582edece994..000000000000 --- a/firewood/src/merkle/node/leaf.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::{ - merkle::{nibbles_to_bytes_iter, Path}, - nibbles::Nibbles, - shale::{ShaleError::InvalidCacheView, Storable}, -}; -use bincode::Options; -use bytemuck::{Pod, Zeroable}; -use std::{ - fmt::{Debug, Error as FmtError, Formatter}, - io::{Cursor, Write}, - mem::size_of, -}; - -pub const SIZE: usize = 2; - -type PathLen = u8; -type ValueLen = u32; - -#[derive(PartialEq, Eq, Clone)] -pub struct LeafNode { - pub(crate) partial_path: Path, - pub(crate) value: Vec, -} - -impl Debug for LeafNode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - write!( - f, - "[Leaf {:?} {}]", - self.partial_path, - hex::encode(&*self.value) - ) - } -} - -impl LeafNode { - pub fn new, V: Into>>(partial_path: P, value: V) -> Self { - Self { - partial_path: partial_path.into(), - value: value.into(), - } - } - - pub const fn path(&self) -> &Path { - &self.partial_path - } - - pub const fn value(&self) -> &Vec { - &self.value - } - - pub(super) fn encode(&self) -> Vec { - #[allow(clippy::unwrap_used)] - bincode::DefaultOptions::new() - .serialize( - [ - nibbles_to_bytes_iter(&self.partial_path.encode()).collect(), - self.value.to_vec(), - ] - .as_slice(), - ) - .unwrap() - } -} - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C, packed)] -struct Meta { - path_len: PathLen, - value_len: ValueLen, -} - -impl Meta { - const SIZE: usize = size_of::(); -} - -impl Storable for LeafNode { - fn serialized_len(&self) -> u64 { - let meta_len = size_of::() as u64; - let path_len = self.partial_path.serialized_len(); - let value_len = self.value.len() as u64; - - meta_len + path_len + value_len - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), crate::shale::ShaleError> { - let mut cursor = Cursor::new(to); - - let path = &self.partial_path.encode(); - let path = nibbles_to_bytes_iter(path); - let value = &self.value; - - let path_len = self.partial_path.serialized_len() as PathLen; - let value_len = value.len() as ValueLen; - - let meta = Meta { - path_len, - value_len, - }; - - cursor.write_all(bytemuck::bytes_of(&meta))?; - - for nibble in path { - cursor.write_all(&[nibble])?; - } - - cursor.write_all(value)?; - - Ok(()) - } - - fn deserialize( - offset: usize, - mem: &T, - ) -> Result - where - Self: Sized, - { - let node_header_raw = mem - .get_view(offset, Meta::SIZE as u64) - .ok_or(InvalidCacheView { - offset, - size: Meta::SIZE as u64, - })? - .as_deref(); - - let offset = offset + Meta::SIZE; - let Meta { - path_len, - value_len, - } = *bytemuck::from_bytes(&node_header_raw); - let size = path_len as u64 + value_len as u64; - - let remainder = mem - .get_view(offset, size) - .ok_or(InvalidCacheView { offset, size })? - .as_deref(); - - let (path, value) = remainder.split_at(path_len as usize); - - let path = { - let nibbles = Nibbles::<0>::new(path).into_iter(); - Path::from_nibbles(nibbles).0 - }; - - let value = value.to_vec(); - - Ok(Self::new(path, value)) - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - use test_case::test_case; - - // these tests will fail if the encoding mechanism changes and should be updated accordingly - // - // Even length so ODD_LEN flag is not set so flag byte is 0b0000_0000 - #[test_case(0x00, vec![0x12, 0x34], vec![1, 2, 3, 4]; "even length")] - // Odd length so ODD_LEN flag is set so flag byte is 0b0000_0001 - // This is combined with the first nibble of the path (0b0000_0010) to become 0b0001_0010 - #[test_case(0b0001_0010, vec![0x34], vec![2, 3, 4]; "odd length")] - fn encode_regression_test(prefix: u8, path: Vec, nibbles: Vec) { - let value = vec![5, 6, 7, 8]; - - let serialized_path = [vec![prefix], path.clone()].concat(); - let serialized_path = [vec![serialized_path.len() as u8], serialized_path].concat(); - let serialized_value = [vec![value.len() as u8], value.clone()].concat(); - - let serialized = [vec![2], serialized_path, serialized_value].concat(); - - let node = LeafNode::new(nibbles, value.clone()); - - assert_eq!(node.encode(), serialized); - } -} diff --git a/firewood/src/merkle/node/path.rs b/firewood/src/merkle/node/path.rs deleted file mode 100644 index f83888a18bce..000000000000 --- a/firewood/src/merkle/node/path.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::Flags; -use crate::nibbles::NibblesIterator; -use std::{ - fmt::{self, Debug}, - iter::once, -}; - -// TODO: use smallvec -/// Path is part or all of a node's path in the trie. -/// Each element is a nibble. -#[derive(PartialEq, Eq, Clone)] -pub struct Path(pub Vec); - -impl Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - for nib in self.0.iter() { - write!(f, "{:x}", *nib & 0xf)?; - } - Ok(()) - } -} - -impl std::ops::Deref for Path { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl From> for Path { - fn from(value: Vec) -> Self { - Self(value) - } -} - -impl Path { - pub fn into_inner(self) -> Vec { - self.0 - } - - pub(crate) fn encode(&self) -> Vec { - let mut flags = Flags::empty(); - - let has_odd_len = self.0.len() & 1 == 1; - - let extra_byte = if has_odd_len { - flags.insert(Flags::ODD_LEN); - - None - } else { - Some(0) - }; - - once(flags.bits()) - .chain(extra_byte) - .chain(self.0.iter().copied()) - .collect() - } - - // TODO: remove all non `Nibbles` usages and delete this function. - // I also think `Path` could probably borrow instead of own data. - // - /// Returns the decoded path. - pub fn decode(raw: &[u8]) -> Self { - Self::from_iter(raw.iter().copied()) - } - - /// Returns the decoded path. - pub fn from_nibbles(nibbles: NibblesIterator<'_, N>) -> Self { - Self::from_iter(nibbles) - } - - /// Assumes all bytes are nibbles, prefer to use `from_nibbles` instead. - fn from_iter>(mut iter: Iter) -> Self { - let flags = Flags::from_bits_retain(iter.next().unwrap_or_default()); - - if !flags.contains(Flags::ODD_LEN) { - let _ = iter.next(); - } - - Self(iter.collect()) - } - - pub(super) fn serialized_len(&self) -> u64 { - let len = self.0.len(); - - // if len is even the prefix takes an extra byte - // otherwise is combined with the first nibble - let len = if len & 1 == 1 { - (len + 1) / 2 - } else { - len / 2 + 1 - }; - - len as u64 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - #[test_case(&[1, 2, 3, 4])] - #[test_case(&[1, 2, 3])] - #[test_case(&[0, 1, 2])] - #[test_case(&[1, 2])] - #[test_case(&[1])] - fn test_encoding(steps: &[u8]) { - let path = Path(steps.to_vec()); - let encoded = path.encode(); - - assert_eq!(encoded.len(), path.serialized_len() as usize * 2); - - let decoded = Path::decode(&encoded); - - assert_eq!(&&*decoded, &steps); - } -} diff --git a/firewood/src/merkle/proof.rs b/firewood/src/merkle/proof.rs deleted file mode 100644 index 536d1815684f..000000000000 --- a/firewood/src/merkle/proof.rs +++ /dev/null @@ -1,853 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::cmp::Ordering; -use std::collections::HashMap; - -use crate::shale::{disk_address::DiskAddress, ShaleError}; -use crate::shale::{LinearStore, ObjWriteSizeError}; -use crate::v2::api::HashKey; -use aiofut::AioError; -use nix::errno::Errno; -use sha3::Digest; -use thiserror::Error; - -use crate::nibbles::Nibbles; -use crate::nibbles::NibblesIterator; -use crate::{ - db::DbError, - merkle::{to_nibble_array, Merkle, MerkleError, Node, NodeType}, - merkle_util::{DataStoreError, InMemoryMerkle}, -}; - -use super::{BinarySerde, EncodedNode}; - -#[derive(Debug, Error)] -pub enum ProofError { - #[error("aio error: {0:?}")] - AioError(AioError), - #[error("decoding error")] - DecodeError(#[from] bincode::Error), - #[error("no such node")] - NoSuchNode, - #[error("proof node missing")] - ProofNodeMissing, - #[error("inconsistent proof data")] - InconsistentProofData, - #[error("non-monotonic range increase")] - NonMonotonicIncreaseRange, - #[error("invalid data")] - InvalidData, - #[error("invalid proof")] - InvalidProof, - #[error("invalid edge keys")] - InvalidEdgeKeys, - #[error("node insertion error")] - NodesInsertionError, - #[error("node not in trie")] - NodeNotInTrie, - #[error("invalid node {0:?}")] - InvalidNode(#[from] MerkleError), - #[error("empty range")] - EmptyRange, - #[error("fork left")] - ForkLeft, - #[error("fork right")] - ForkRight, - #[error("system error: {0:?}")] - SystemError(Errno), - #[error("shale error: {0:?}")] - Shale(ShaleError), - #[error("invalid root hash")] - InvalidRootHash, - #[error("{0}")] - WriteError(#[from] ObjWriteSizeError), -} - -impl From for ProofError { - fn from(d: DataStoreError) -> ProofError { - match d { - DataStoreError::InsertionError => ProofError::NodesInsertionError, - DataStoreError::RootHashError => ProofError::InvalidRootHash, - _ => ProofError::InvalidProof, - } - } -} - -impl From for ProofError { - fn from(d: DbError) -> ProofError { - match d { - DbError::Aio(e) => ProofError::AioError(e), - DbError::InvalidParams => ProofError::InvalidProof, - DbError::Merkle(e) => ProofError::InvalidNode(e), - DbError::System(e) => ProofError::SystemError(e), - DbError::KeyNotFound => ProofError::InvalidEdgeKeys, - DbError::CreateError => ProofError::NoSuchNode, - // TODO: fix better by adding a new error to ProofError - #[allow(clippy::unwrap_used)] - DbError::IO(e) => { - ProofError::SystemError(nix::errno::Errno::from_raw(e.raw_os_error().unwrap())) - } - DbError::Shale(e) => ProofError::Shale(e), - DbError::InvalidProposal => ProofError::InvalidProof, - } - } -} - -/// A proof that a single key is present -/// -/// The generic N represents the storage for the node -#[derive(Clone, Debug)] -pub struct Proof(pub HashMap); - -/// `SubProof` contains the value or the hash of a node that maps -/// to a single proof step. If reaches an end step during proof verification, -/// the `SubProof` should be the `Value` variant. - -#[derive(Debug)] -enum SubProof { - Value(Vec), - Hash(HashKey), -} - -impl + Send> Proof { - /// verify_proof checks merkle proofs. The given proof must contain the value for - /// key in a trie with the given root hash. VerifyProof returns an error if the - /// proof contains invalid trie nodes or the wrong value. - /// - /// The generic N represents the storage for the node - pub fn verify>( - &self, - key: K, - root_hash: HashKey, - ) -> Result>, ProofError> { - let mut key_nibbles = Nibbles::<0>::new(key.as_ref()).into_iter(); - - let mut cur_hash = root_hash; - let proofs_map = &self.0; - - loop { - let cur_proof = proofs_map - .get(&cur_hash) - .ok_or(ProofError::ProofNodeMissing)?; - - let node = NodeType::decode(cur_proof.as_ref())?; - // TODO: I think this will currently fail if the key is &[]; - let (sub_proof, traversed_nibbles) = locate_subproof(key_nibbles, node)?; - key_nibbles = traversed_nibbles; - - cur_hash = match sub_proof { - // Return when reaching the end of the key. - Some(SubProof::Value(value)) if key_nibbles.is_empty() => return Ok(Some(value)), - // The trie doesn't contain the key. - Some(SubProof::Hash(hash)) => hash, - _ => return Ok(None), - }; - } - } - - pub fn extend(&mut self, other: Proof) { - self.0.extend(other.0) - } - - pub fn verify_range_proof( - &self, - root_hash: HashKey, - first_key: K, - last_key: K, - keys: Vec, - vals: Vec, - ) -> Result - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - T: BinarySerde, - EncodedNode: serde::Serialize + serde::de::DeserializeOwned, - { - if keys.len() != vals.len() { - return Err(ProofError::InconsistentProofData); - } - - // Ensure the received batch is monotonic increasing and contains no deletions - #[allow(clippy::indexing_slicing)] - if !keys.windows(2).all(|w| w[0].as_ref() < w[1].as_ref()) { - return Err(ProofError::NonMonotonicIncreaseRange); - } - - // Use in-memory merkle - let mut in_mem_merkle = InMemoryMerkle::new(0x10000, 0x10000); - - // Special case, there is no edge proof at all. The given range is expected - // to be the whole leaf-set in the trie. - if self.0.is_empty() { - for (index, k) in keys.iter().enumerate() { - #[allow(clippy::indexing_slicing)] - in_mem_merkle.insert(k, vals[index].as_ref().to_vec())?; - } - - let merkle_root = &*in_mem_merkle.root_hash()?; - - return if merkle_root == &root_hash { - Ok(false) - } else { - Err(ProofError::InvalidProof) - }; - } - - // Special case when there is a provided edge proof but zero key/value pairs, - // ensure there are no more accounts / slots in the trie. - if keys.is_empty() { - let proof_to_path = - self.proof_to_path(first_key, root_hash, &mut in_mem_merkle, true)?; - return match proof_to_path { - Some(_) => Err(ProofError::InvalidData), - None => Ok(false), - }; - } - - // Special case, there is only one element and two edge keys are same. - // In this case, we can't construct two edge paths. So handle it here. - if keys.len() == 1 && first_key.as_ref() == last_key.as_ref() { - let value = - self.proof_to_path(first_key.as_ref(), root_hash, &mut in_mem_merkle, false)?; - - #[allow(clippy::indexing_slicing)] - return if first_key.as_ref() != keys[0].as_ref() { - // correct proof but invalid key - Err(ProofError::InvalidEdgeKeys) - } else { - match value { - #[allow(clippy::indexing_slicing)] - Some(val) if val == vals[0].as_ref() => Ok(true), - None => Ok(false), - _ => Err(ProofError::InvalidData), - } - }; - } - - // Ok, in all other cases, we require two edge paths available. - // First check the validity of edge keys. - if first_key.as_ref() >= last_key.as_ref() { - return Err(ProofError::InvalidEdgeKeys); - } - - // Convert the edge proofs to edge trie paths. Then we can - // have the same tree architecture with the original one. - // For the first edge proof, non-existent proof is allowed. - self.proof_to_path(first_key.as_ref(), root_hash, &mut in_mem_merkle, true)?; - - // Pass the root node here, the second path will be merged - // with the first one. For the last edge proof, non-existent - // proof is also allowed. - self.proof_to_path(last_key.as_ref(), root_hash, &mut in_mem_merkle, true)?; - - // Remove all internal caculated values. All the removed parts should - // be re-filled(or re-constructed) by the given leaves range. - let fork_at_root = - unset_internal(&mut in_mem_merkle, first_key.as_ref(), last_key.as_ref())?; - - // If the fork point is the root, the trie should be empty, start with a new one. - if fork_at_root { - in_mem_merkle = InMemoryMerkle::new(0x100000, 0x100000); - } - - for (key, val) in keys.iter().zip(vals.iter()) { - in_mem_merkle.insert(key.as_ref(), val.as_ref().to_vec())?; - } - - // Calculate the hash - let merkle_root = &*in_mem_merkle.root_hash()?; - - if merkle_root == &root_hash { - Ok(true) - } else { - Err(ProofError::InvalidProof) - } - } - - /// proofToPath converts a merkle proof to trie node path. The main purpose of - /// this function is recovering a node path from the merkle proof stream. All - /// necessary nodes will be resolved and leave the remaining as hashnode. - /// - /// The given edge proof is allowed to be an existent or non-existent proof. - fn proof_to_path( - &self, - key: K, - root_hash: HashKey, - in_mem_merkle: &mut InMemoryMerkle, - allow_non_existent_node: bool, - ) -> Result>, ProofError> - where - K: AsRef<[u8]>, - T: BinarySerde, - EncodedNode: serde::Serialize + serde::de::DeserializeOwned, - { - // Start with the sentinel root - let sentinel = in_mem_merkle.get_sentinel_address(); - let merkle = in_mem_merkle.get_merkle_mut(); - let mut parent_node = merkle - .get_node(sentinel) - .map_err(|_| ProofError::NoSuchNode)?; - - let mut key_nibbles = Nibbles::<1>::new(key.as_ref()).into_iter().peekable(); - - let mut child_hash = root_hash; - let proof_nodes_map = &self.0; - - let sub_proof = loop { - // Link the child to the parent based on the node type. - // if a child is already linked, use it instead - let child_node = match &parent_node.inner() { - #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) => { - let Some(child_index) = key_nibbles.next().map(usize::from) else { - break None; - }; - - match n.chd()[child_index] { - // If the child already resolved, then use the existing node. - Some(node) => merkle.get_node(node)?, - None => { - // Look up the child's encoded bytes and decode to get the child. - let child_node_bytes = proof_nodes_map - .get(&child_hash) - .ok_or(ProofError::ProofNodeMissing)?; - - let child_node = NodeType::decode(child_node_bytes.as_ref())?; - - let child_node = merkle.put_node(Node::from(child_node))?; - - // insert `child_node` to the appropriate index in the `parent_node` - parent_node.write(|node| { - #[allow(clippy::indexing_slicing)] - let node = node - .inner_mut() - .as_branch_mut() - .expect("parent_node_ref must be a branch"); - node.chd_mut()[child_index] = Some(child_node.as_addr()); - })?; - - child_node - } - } - } - - // We should not hit a leaf node as a parent. - _ => return Err(ProofError::InvalidNode(MerkleError::ParentLeafBranch)), - }; - - // find the encoded subproof of the child if the partial path and nibbles match - let encoded_sub_proof = match child_node.inner() { - NodeType::Leaf(n) => { - break n - .partial_path - .iter() - .copied() - .eq(key_nibbles) // all nibbles have to match - .then(|| n.value().to_vec()); - } - - NodeType::Branch(n) => { - let paths_match = n - .partial_path - .iter() - .copied() - .all(|nibble| Some(nibble) == key_nibbles.next()); - - if !paths_match { - break None; - } - - if let Some(index) = key_nibbles.peek() { - let subproof = n - .chd_encode() - .get(*index as usize) - .and_then(|inner| inner.as_ref()) - .map(|value| &**value); - - subproof - } else { - break n.value.as_ref().map(|value| value.to_vec()); - } - } - }; - - match encoded_sub_proof { - None => break None, - Some(encoded) => { - let hash = generate_subproof_hash(encoded)?; - child_hash = hash; - } - } - - parent_node = child_node; - }; - - match sub_proof { - Some(value) => Ok(Some(value)), - None if allow_non_existent_node => Ok(None), - None => Err(ProofError::NodeNotInTrie), - } - } -} - -fn locate_subproof( - mut key_nibbles: NibblesIterator<'_, 0>, - node: NodeType, -) -> Result<(Option, NibblesIterator<'_, 0>), ProofError> { - match node { - NodeType::Leaf(n) => { - let cur_key = &n.path().0; - // Check if the key of current node match with the given key - // and consume the current-key portion of the nibbles-iterator - let does_not_match = key_nibbles.size_hint().0 < cur_key.len() - || !cur_key.iter().all(|val| key_nibbles.next() == Some(*val)); - - if does_not_match { - return Ok((None, Nibbles::<0>::new(&[]).into_iter())); - } - - let encoded: Vec = n.value().to_vec(); - - let sub_proof = SubProof::Value(encoded); - - Ok((sub_proof.into(), key_nibbles)) - } - NodeType::Branch(n) => { - let partial_path = &n.partial_path.0; - - let does_not_match = key_nibbles.size_hint().0 < partial_path.len() - || !partial_path - .iter() - .all(|val| key_nibbles.next() == Some(*val)); - - if does_not_match { - return Ok((None, Nibbles::<0>::new(&[]).into_iter())); - } - - let Some(index) = key_nibbles.next().map(|nib| nib as usize) else { - let encoded = n.value; - - let sub_proof = encoded.map(SubProof::Value); - - return Ok((sub_proof, key_nibbles)); - }; - - // consume items returning the item at index - #[allow(clippy::indexing_slicing)] - let value = n.chd_encode()[index] - .as_ref() - .ok_or(ProofError::InvalidData)?; - generate_subproof(value).map(|subproof| (Some(subproof), key_nibbles)) - } - } -} - -fn generate_subproof_hash(encoded: &[u8]) -> Result { - match encoded.len() { - 0..=31 => { - let sub_hash = sha3::Keccak256::digest(encoded).into(); - Ok(sub_hash) - } - - 32 => { - let sub_hash = encoded - .try_into() - .expect("slice length checked in match arm"); - - Ok(sub_hash) - } - - len => Err(ProofError::DecodeError(Box::new( - bincode::ErrorKind::Custom(format!("invalid proof length: {len}")), - ))), - } -} - -fn generate_subproof(encoded: &[u8]) -> Result { - Ok(SubProof::Hash(generate_subproof_hash(encoded)?)) -} - -// unset_internal removes all internal node references. -// It should be called after a trie is constructed with two edge paths. Also -// the given boundary keys must be the one used to construct the edge paths. -// -// It's the key step for range proof. The precalculated encoded value of all internal -// nodes should be removed. But if the proof is valid, -// the missing children will be filled, otherwise it will be thrown anyway. -// -// Note we have the assumption here the given boundary keys are different -// and right is larger than left. -// -// The return value indicates if the fork point is root node. If so, unset the -// entire trie. -fn unset_internal( - in_mem_merkle: &mut InMemoryMerkle, - left: K, - right: K, -) -> Result -where - K: AsRef<[u8]>, - T: BinarySerde, - EncodedNode: serde::Serialize + serde::de::DeserializeOwned, -{ - // Add the sentinel root - let mut left_chunks = vec![0]; - left_chunks.extend(left.as_ref().iter().copied().flat_map(to_nibble_array)); - // Add the sentinel root - let mut right_chunks = vec![0]; - right_chunks.extend(right.as_ref().iter().copied().flat_map(to_nibble_array)); - let sentinel_addr = in_mem_merkle.get_sentinel_address(); - let merkle = in_mem_merkle.get_merkle_mut(); - let mut u_ref = merkle - .get_node(sentinel_addr) - .map_err(|_| ProofError::NoSuchNode)?; - let mut parent = DiskAddress::null(); - - let mut fork_left = Ordering::Equal; - let mut fork_right = Ordering::Equal; - - let mut index = 0; - loop { - match &u_ref.inner() { - #[allow(clippy::indexing_slicing)] - NodeType::Branch(n) => { - // If either the key of left proof or right proof doesn't match with - // stop here, this is the forkpoint. - let path = &*n.partial_path; - - if !path.is_empty() { - [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] - .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) - .map(|key| key.cmp(path)); - - if !fork_left.is_eq() || !fork_right.is_eq() { - break; - } - - index += path.len(); - } - - // If either the node pointed by left proof or right proof is nil, - // stop here and the forkpoint is the fullnode. - #[allow(clippy::indexing_slicing)] - let left_node = n.chd()[left_chunks[index] as usize]; - #[allow(clippy::indexing_slicing)] - let right_node = n.chd()[right_chunks[index] as usize]; - - match (left_node.as_ref(), right_node.as_ref()) { - (None, _) | (_, None) => break, - (left, right) if left != right => break, - _ => (), - }; - - parent = u_ref.as_addr(); - u_ref = merkle.get_node(left_node.expect("left_node none"))?; - index += 1; - } - - #[allow(clippy::indexing_slicing)] - NodeType::Leaf(n) => { - let path = &*n.partial_path; - - [fork_left, fork_right] = [&left_chunks[index..], &right_chunks[index..]] - .map(|chunks| chunks.chunks(path.len()).next().unwrap_or_default()) - .map(|key| key.cmp(path)); - - break; - } - } - } - - match &u_ref.inner() { - NodeType::Branch(n) => { - if fork_left.is_lt() && fork_right.is_lt() { - return Err(ProofError::EmptyRange); - } - - if fork_left.is_gt() && fork_right.is_gt() { - return Err(ProofError::EmptyRange); - } - - if fork_left.is_ne() && fork_right.is_ne() { - // The fork point is root node, unset the entire trie - if parent.is_null() { - return Ok(true); - } - - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - #[allow(clippy::unwrap_used)] - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - #[allow(clippy::indexing_slicing)] - (pp.chd_mut()[left_chunks[index - 1] as usize] = None); - #[allow(clippy::indexing_slicing)] - (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); - }) - .unwrap(); - - return Ok(false); - } - - let p = u_ref.as_addr(); - index += n.partial_path.len(); - - // Only one proof points to non-existent key. - if fork_right.is_ne() { - #[allow(clippy::indexing_slicing)] - let left_node = n.chd()[left_chunks[index] as usize]; - - drop(u_ref); - #[allow(clippy::indexing_slicing)] - unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; - return Ok(false); - } - - if fork_left.is_ne() { - #[allow(clippy::indexing_slicing)] - let right_node = n.chd()[right_chunks[index] as usize]; - - drop(u_ref); - #[allow(clippy::indexing_slicing)] - unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; - return Ok(false); - }; - - #[allow(clippy::indexing_slicing)] - let left_node = n.chd()[left_chunks[index] as usize]; - #[allow(clippy::indexing_slicing)] - let right_node = n.chd()[right_chunks[index] as usize]; - - // unset all internal nodes calculated encoded value in the forkpoint - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - for i in left_chunks[index] + 1..right_chunks[index] { - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - #[allow(clippy::indexing_slicing)] - (uu.chd_mut()[i as usize] = None); - #[allow(clippy::indexing_slicing)] - (uu.chd_encoded_mut()[i as usize] = None); - }) - .unwrap(); - } - - drop(u_ref); - - #[allow(clippy::indexing_slicing)] - unset_node_ref(merkle, p, left_node, &left_chunks[index..], 1, false)?; - - #[allow(clippy::indexing_slicing)] - unset_node_ref(merkle, p, right_node, &right_chunks[index..], 1, true)?; - - Ok(false) - } - - NodeType::Leaf(_) => { - if fork_left.is_lt() && fork_right.is_lt() { - return Err(ProofError::EmptyRange); - } - - if fork_left.is_gt() && fork_right.is_gt() { - return Err(ProofError::EmptyRange); - } - - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - - #[allow(clippy::unwrap_used)] - if fork_left.is_ne() && fork_right.is_ne() { - p_ref - .write(|p| { - if let NodeType::Branch(n) = p.inner_mut() { - #[allow(clippy::indexing_slicing)] - (n.chd_mut()[left_chunks[index - 1] as usize] = None); - #[allow(clippy::indexing_slicing)] - (n.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); - } - }) - .unwrap(); - } else if fork_right.is_ne() { - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - #[allow(clippy::indexing_slicing)] - (pp.chd_mut()[left_chunks[index - 1] as usize] = None); - #[allow(clippy::indexing_slicing)] - (pp.chd_encoded_mut()[left_chunks[index - 1] as usize] = None); - }) - .unwrap(); - } else if fork_left.is_ne() { - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - #[allow(clippy::indexing_slicing)] - (pp.chd_mut()[right_chunks[index - 1] as usize] = None); - #[allow(clippy::indexing_slicing)] - (pp.chd_encoded_mut()[right_chunks[index - 1] as usize] = None); - }) - .unwrap(); - } - - Ok(false) - } - } -} - -// unset removes all internal node references either the left most or right most. -// It can meet these scenarios: -// -// - The given path is existent in the trie, unset the associated nodes with the -// specific direction -// - The given path is non-existent in the trie -// - the fork point is a fullnode, the corresponding child pointed by path -// is nil, return -// - the fork point is a shortnode, the shortnode is included in the range, -// keep the entire branch and return. -// - the fork point is a shortnode, the shortnode is excluded in the range, -// unset the entire branch. -fn unset_node_ref, S: LinearStore, T: BinarySerde>( - merkle: &Merkle, - parent: DiskAddress, - node: Option, - key: K, - index: usize, - remove_left: bool, -) -> Result<(), ProofError> { - let Some(node) = node else { - // If the node is nil, then it's a child of the fork point - // fullnode(it's a non-existent branch). - return Ok(()); - }; - - let mut chunks = Vec::new(); - chunks.extend(key.as_ref()); - - #[allow(clippy::unwrap_used)] - let mut u_ref = merkle.get_node(node).map_err(|_| ProofError::NoSuchNode)?; - let p = u_ref.as_addr(); - - if index >= chunks.len() { - return Err(ProofError::InvalidProof); - } - - #[allow(clippy::indexing_slicing)] - match &u_ref.inner() { - NodeType::Branch(n) if chunks[index..].starts_with(&n.partial_path) => { - let index = index + n.partial_path.len(); - let child_index = chunks[index] as usize; - - let node = n.chd()[child_index]; - - let iter = if remove_left { - 0..child_index - } else { - child_index + 1..16 - }; - - #[allow(clippy::unwrap_used)] - for i in iter { - u_ref - .write(|u| { - let uu = u.inner_mut().as_branch_mut().unwrap(); - #[allow(clippy::indexing_slicing)] - (uu.chd_mut()[i] = None); - #[allow(clippy::indexing_slicing)] - (uu.chd_encoded_mut()[i] = None); - }) - .unwrap(); - } - - drop(u_ref); - - unset_node_ref(merkle, p, node, key, index + 1, remove_left) - } - - NodeType::Branch(n) => { - let cur_key = &n.partial_path; - - // Find the fork point, it's a non-existent branch. - // - // for (true, Ordering::Less) - // The key of fork shortnode is less than the path - // (it belongs to the range), unset the entire - // branch. The parent must be a fullnode. - // - // for (false, Ordering::Greater) - // The key of fork shortnode is greater than the - // path(it belongs to the range), unset the entrie - // branch. The parent must be a fullnode. Otherwise the - // key is not part of the range and should remain in the - // cached hash. - #[allow(clippy::indexing_slicing)] - let should_unset_entire_branch = matches!( - (remove_left, cur_key.cmp(&chunks[index..])), - (true, Ordering::Less) | (false, Ordering::Greater) - ); - - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - if should_unset_entire_branch { - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - - p_ref - .write(|p| match p.inner_mut() { - NodeType::Branch(pp) => { - pp.chd_mut()[chunks[index - 1] as usize] = None; - pp.chd_encoded_mut()[chunks[index - 1] as usize] = None; - } - NodeType::Leaf(_) => (), - }) - .unwrap(); - } - - Ok(()) - } - - NodeType::Leaf(n) => { - let mut p_ref = merkle - .get_node(parent) - .map_err(|_| ProofError::NoSuchNode)?; - let cur_key = n.path(); - - // Similar to branch node, we need to compare the path to see if the node - // needs to be unset. - #[allow(clippy::indexing_slicing)] - if !(chunks[index..]).starts_with(cur_key) { - #[allow(clippy::indexing_slicing)] - match (cur_key.cmp(&chunks[index..]), remove_left) { - (Ordering::Greater, false) | (Ordering::Less, true) => { - p_ref - .write(|p| { - let pp = p.inner_mut().as_branch_mut().expect("not a branch node"); - let index = chunks[index - 1] as usize; - pp.chd_mut()[index] = None; - pp.chd_encoded_mut()[index] = None; - }) - .expect("node write failure"); - } - _ => (), - } - } else { - p_ref - .write(|p| { - if let NodeType::Branch(n) = p.inner_mut() { - #[allow(clippy::indexing_slicing)] - let index = chunks[index - 1] as usize; - - n.chd_mut()[index] = None; - n.chd_encoded_mut()[index] = None; - } - }) - .expect("node write failure"); - } - - Ok(()) - } - } -} diff --git a/firewood/src/merkle/trie_hash.rs b/firewood/src/merkle/trie_hash.rs deleted file mode 100644 index 7cfc099092da..000000000000 --- a/firewood/src/merkle/trie_hash.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::shale::{LinearStore, ShaleError, Storable}; -use std::{ - fmt::{self, Debug}, - io::Write, -}; - -pub const TRIE_HASH_LEN: usize = 32; -const U64_TRIE_HASH_LEN: u64 = TRIE_HASH_LEN as u64; - -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct TrieHash(pub [u8; TRIE_HASH_LEN]); - -impl std::ops::Deref for TrieHash { - type Target = [u8; TRIE_HASH_LEN]; - fn deref(&self) -> &[u8; TRIE_HASH_LEN] { - &self.0 - } -} - -impl Storable for TrieHash { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, U64_TRIE_HASH_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: U64_TRIE_HASH_LEN, - })?; - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - Ok(Self(raw.as_deref()[..TRIE_HASH_LEN].try_into().unwrap())) - } - - fn serialized_len(&self) -> u64 { - U64_TRIE_HASH_LEN - } - - fn serialize(&self, mut to: &mut [u8]) -> Result<(), ShaleError> { - to.write_all(&self.0).map_err(ShaleError::Io) - } -} - -impl Debug for TrieHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", hex::encode(self.0)) - } -} - -#[cfg(test)] -#[allow(clippy::indexing_slicing)] -mod tests { - use super::*; - - #[test] - fn test_dehydrate() { - let zero_hash = TrieHash([0u8; TRIE_HASH_LEN]); - - let mut to = [1u8; TRIE_HASH_LEN]; - #[allow(clippy::unwrap_used)] - zero_hash.serialize(&mut to).unwrap(); - - assert_eq!(&to, &zero_hash.0); - } -} diff --git a/firewood/src/merkle_util.rs b/firewood/src/merkle_util.rs deleted file mode 100644 index 12f30e61911d..000000000000 --- a/firewood/src/merkle_util.rs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::{ - merkle::{ - proof::{Proof, ProofError}, - BinarySerde, EncodedNode, Merkle, Ref, RefMut, TrieHash, - }, - shale::{self, disk_address::DiskAddress, in_mem::InMemLinearStore, LinearStore, StoredView}, -}; -use std::num::NonZeroUsize; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum DataStoreError { - #[error("failed to insert data")] - InsertionError, - #[error("failed to remove data")] - RemovalError, - #[error("failed to get data")] - GetError, - #[error("failed to generate root hash")] - RootHashError, - #[error("failed to dump data")] - DumpError, - #[error("invalid utf8")] - UTF8Error, - #[error("bad proof")] - ProofError, - #[error("failed to verify proof")] - ProofVerificationError, - #[error("no keys or values found in proof")] - ProofEmptyKeyValuesError, -} - -pub struct InMemoryMerkle { - sentinel_addr: DiskAddress, - merkle: Merkle, -} - -impl InMemoryMerkle -where - T: BinarySerde, - EncodedNode: serde::Serialize + serde::de::DeserializeOwned, -{ - pub fn new(meta_size: u64, compact_size: u64) -> Self { - const RESERVED: usize = 0x1000; - assert!(meta_size as usize > RESERVED); - assert!(compact_size as usize > RESERVED); - let mut dm = InMemLinearStore::new(meta_size, 0); - let compact_header = DiskAddress::null(); - #[allow(clippy::unwrap_used)] - dm.write( - compact_header.into(), - &shale::to_dehydrated(&shale::compact::StoreHeader::new( - NonZeroUsize::new(RESERVED).unwrap(), - #[allow(clippy::unwrap_used)] - NonZeroUsize::new(RESERVED).unwrap(), - )) - .unwrap(), - ) - .expect("write should succeed"); - #[allow(clippy::unwrap_used)] - let compact_header = StoredView::addr_to_obj( - &dm, - compact_header, - shale::compact::ChunkHeader::SERIALIZED_LEN, - ) - .unwrap(); - let mem_meta = dm; - let mem_payload = InMemLinearStore::new(compact_size, 0x1); - - let cache = shale::ObjCache::new(1); - let store = - shale::compact::Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16) - .expect("Store init fail"); - - let merkle = Merkle::new(store); - #[allow(clippy::unwrap_used)] - let sentinel_addr = merkle.init_sentinel().unwrap(); - - InMemoryMerkle { - sentinel_addr, - merkle, - } - } - - pub fn insert>(&mut self, key: K, val: Vec) -> Result<(), DataStoreError> { - self.merkle - .insert(key, val, self.sentinel_addr) - .map_err(|_err| DataStoreError::InsertionError) - } - - pub fn remove>(&mut self, key: K) -> Result>, DataStoreError> { - self.merkle - .remove(key, self.sentinel_addr) - .map_err(|_err| DataStoreError::RemovalError) - } - - pub fn get>(&self, key: K) -> Result, DataStoreError> { - self.merkle - .get(key, self.sentinel_addr) - .map_err(|_err| DataStoreError::GetError) - } - - pub fn get_mut>( - &mut self, - key: K, - ) -> Result>, DataStoreError> { - self.merkle - .get_mut(key, self.sentinel_addr) - .map_err(|_err| DataStoreError::GetError) - } - - pub const fn get_sentinel_address(&self) -> DiskAddress { - self.sentinel_addr - } - - pub fn get_merkle_mut(&mut self) -> &mut Merkle { - &mut self.merkle - } - - pub fn root_hash(&self) -> Result { - self.merkle - .root_hash(self.sentinel_addr) - .map_err(|_err| DataStoreError::RootHashError) - } - - pub fn dump(&self) -> Result { - let mut s = Vec::new(); - self.merkle - .dump(self.sentinel_addr, &mut s) - .map_err(|_err| DataStoreError::DumpError)?; - String::from_utf8(s).map_err(|_err| DataStoreError::UTF8Error) - } - - pub fn prove>(&self, key: K) -> Result>, DataStoreError> { - self.merkle - .prove(key, self.sentinel_addr) - .map_err(|_err| DataStoreError::ProofError) - } - - pub fn verify_proof + Send, K: AsRef<[u8]>>( - &self, - key: K, - proof: &Proof, - ) -> Result>, DataStoreError> { - let hash: [u8; 32] = *self.root_hash()?; - proof - .verify(key, hash) - .map_err(|_err| DataStoreError::ProofVerificationError) - } - - pub fn verify_range_proof + Send, K: AsRef<[u8]>, V: AsRef<[u8]>>( - &self, - proof: &Proof, - first_key: K, - last_key: K, - keys: Vec, - vals: Vec, - ) -> Result { - let hash: [u8; 32] = *self.root_hash()?; - proof.verify_range_proof(hash, first_key, last_key, keys, vals) - } -} diff --git a/firewood/src/nibbles.rs b/firewood/src/nibbles.rs deleted file mode 100644 index 0f2138c39faf..000000000000 --- a/firewood/src/nibbles.rs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::{iter::FusedIterator, ops::Index}; - -static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - -/// Nibbles is a newtype that contains only a reference to a [u8], and produces -/// nibbles. Nibbles can be indexed using nib\[x\] or you can get an iterator -/// with `into_iter()` -/// -/// Nibbles can be constructed with a number of leading zeroes. This is used -/// in firewood because there is a sentinel node, so we always want the first -/// byte to be 0 -/// -/// When creating a Nibbles object, use the syntax `Nibbles::(r)` where -/// `N` is the number of leading zero bytes you need and `r` is a reference to -/// a [u8] -/// -/// # Examples -/// -/// ``` -/// # use firewood::nibbles; -/// # fn main() { -/// let nib = nibbles::Nibbles::<0>::new(&[0x56, 0x78]); -/// assert_eq!(nib.into_iter().collect::>(), [0x5, 0x6, 0x7, 0x8]); -/// -/// // nibbles can be efficiently advanced without rendering the -/// // intermediate values -/// assert_eq!(nib.into_iter().skip(3).collect::>(), [0x8]); -/// -/// // nibbles can also be indexed -/// -/// assert_eq!(nib[1], 0x6); -/// -/// // or reversed -/// assert_eq!(nib.into_iter().rev().next(), Some(0x8)); -/// # } -/// ``` -#[derive(Debug, Copy, Clone)] -pub struct Nibbles<'a, const LEADING_ZEROES: usize>(&'a [u8]); - -impl<'a, const LEADING_ZEROES: usize> Index for Nibbles<'a, LEADING_ZEROES> { - type Output = u8; - - fn index(&self, index: usize) -> &Self::Output { - match index { - _ if index < LEADING_ZEROES => &NIBBLES[0], - _ if (index - LEADING_ZEROES) % 2 == 0 => - { - #[allow(clippy::indexing_slicing)] - &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] >> 4) as usize] - } - #[allow(clippy::indexing_slicing)] - _ => &NIBBLES[(self.0[(index - LEADING_ZEROES) / 2] & 0xf) as usize], - } - } -} - -impl<'a, const LEADING_ZEROES: usize> IntoIterator for Nibbles<'a, LEADING_ZEROES> { - type Item = u8; - type IntoIter = NibblesIterator<'a, LEADING_ZEROES>; - - #[must_use] - fn into_iter(self) -> Self::IntoIter { - NibblesIterator { - data: self, - head: 0, - tail: self.len(), - } - } -} - -impl<'a, const LEADING_ZEROES: usize> Nibbles<'a, LEADING_ZEROES> { - #[must_use] - pub const fn len(&self) -> usize { - LEADING_ZEROES + 2 * self.0.len() - } - - #[must_use] - pub const fn is_empty(&self) -> bool { - LEADING_ZEROES == 0 && self.0.is_empty() - } - - pub const fn new(inner: &'a [u8]) -> Self { - Nibbles(inner) - } -} - -/// An iterator returned by [Nibbles::into_iter] -/// See their documentation for details. -#[derive(Clone, Debug)] -pub struct NibblesIterator<'a, const LEADING_ZEROES: usize> { - data: Nibbles<'a, LEADING_ZEROES>, - head: usize, - tail: usize, -} - -impl<'a, const LEADING_ZEROES: usize> FusedIterator for NibblesIterator<'a, LEADING_ZEROES> {} - -impl<'a, const LEADING_ZEROES: usize> Iterator for NibblesIterator<'a, LEADING_ZEROES> { - type Item = u8; - - fn next(&mut self) -> Option { - if self.is_empty() { - return None; - } - #[allow(clippy::indexing_slicing)] - let result = Some(self.data[self.head]); - self.head += 1; - result - } - - fn size_hint(&self) -> (usize, Option) { - let remaining = self.tail - self.head; - (remaining, Some(remaining)) - } - - fn nth(&mut self, n: usize) -> Option { - self.head += std::cmp::min(n, self.tail - self.head); - self.next() - } -} - -impl<'a, const LEADING_ZEROES: usize> NibblesIterator<'a, LEADING_ZEROES> { - #[inline(always)] - pub const fn is_empty(&self) -> bool { - self.head == self.tail - } -} - -impl<'a, const LEADING_ZEROES: usize> DoubleEndedIterator for NibblesIterator<'a, LEADING_ZEROES> { - fn next_back(&mut self) -> Option { - if self.is_empty() { - return None; - } - self.tail -= 1; - #[allow(clippy::indexing_slicing)] - Some(self.data[self.tail]) - } - - fn nth_back(&mut self, n: usize) -> Option { - self.tail -= std::cmp::min(n, self.tail - self.head); - self.next_back() - } -} - -#[cfg(test)] -#[allow(clippy::indexing_slicing)] -mod test { - use super::Nibbles; - static TEST_BYTES: [u8; 4] = [0xdeu8, 0xad, 0xbe, 0xef]; - - #[test] - fn happy_regular_nibbles() { - let nib = Nibbles::<0>(&TEST_BYTES); - let expected = [0xdu8, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; - for v in expected.into_iter().enumerate() { - assert_eq!(nib[v.0], v.1, "{v:?}"); - } - } - - #[test] - fn leading_zero_nibbles_index() { - let nib = Nibbles::<1>(&TEST_BYTES); - let expected = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; - for v in expected.into_iter().enumerate() { - assert_eq!(nib[v.0], v.1, "{v:?}"); - } - } - #[test] - fn leading_zero_nibbles_iter() { - let nib = Nibbles::<1>(&TEST_BYTES); - let expected: [u8; 9] = [0u8, 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; - expected.into_iter().eq(nib); - } - - #[test] - fn skip_skips_zeroes() { - let nib1 = Nibbles::<1>(&TEST_BYTES); - let nib0 = Nibbles::<0>(&TEST_BYTES); - assert!(nib1.into_iter().skip(1).eq(nib0.into_iter())); - } - - #[test] - #[should_panic] - fn out_of_bounds_panics() { - let nib = Nibbles::<0>(&TEST_BYTES); - let _ = nib[8]; - } - - #[test] - fn last_nibble() { - let nib = Nibbles::<0>(&TEST_BYTES); - assert_eq!(nib[7], 0xf); - } - - #[test] - fn size_hint_0() { - let nib = Nibbles::<0>(&TEST_BYTES); - let mut nib_iter = nib.into_iter(); - assert_eq!((8, Some(8)), nib_iter.size_hint()); - let _ = nib_iter.next(); - assert_eq!((7, Some(7)), nib_iter.size_hint()); - } - - #[test] - fn size_hint_1() { - let nib = Nibbles::<1>(&TEST_BYTES); - let mut nib_iter = nib.into_iter(); - assert_eq!((9, Some(9)), nib_iter.size_hint()); - let _ = nib_iter.next(); - assert_eq!((8, Some(8)), nib_iter.size_hint()); - } - - #[test] - fn backwards() { - let nib = Nibbles::<1>(&TEST_BYTES); - let nib_iter = nib.into_iter().rev(); - let expected = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd, 0x0]; - - assert!(nib_iter.eq(expected)); - } - - #[test] - fn empty() { - let nib = Nibbles::<0>(&[]); - assert!(nib.is_empty()); - let it = nib.into_iter(); - assert!(it.is_empty()); - assert_eq!(it.size_hint().0, 0); - } - - #[test] - fn not_empty_because_of_leading_nibble() { - let nib = Nibbles::<1>(&[]); - assert!(!nib.is_empty()); - let mut it = nib.into_iter(); - assert!(!it.is_empty()); - assert_eq!(it.size_hint(), (1, Some(1))); - assert_eq!(it.next(), Some(0)); - assert!(it.is_empty()); - assert_eq!(it.size_hint(), (0, Some(0))); - } - #[test] - fn not_empty_because_of_data() { - let nib = Nibbles::<0>(&[1]); - assert!(!nib.is_empty()); - let mut it = nib.into_iter(); - assert!(!it.is_empty()); - assert_eq!(it.size_hint(), (2, Some(2))); - assert_eq!(it.next(), Some(0)); - assert!(!it.is_empty()); - assert_eq!(it.size_hint(), (1, Some(1))); - assert_eq!(it.next(), Some(1)); - assert!(it.is_empty()); - assert_eq!(it.size_hint(), (0, Some(0))); - } -} diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs new file mode 100644 index 000000000000..615933e1ca2e --- /dev/null +++ b/firewood/src/proof.rs @@ -0,0 +1,232 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::merkle::MerkleError; +use sha2::{Digest, Sha256}; +use storage::{ + BranchNode, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ProofError { + #[error("non-monotonic range increase")] + NonMonotonicIncreaseRange, + #[error("unexpected hash")] + UnexpectedHash, + #[error("unexpected value")] + UnexpectedValue, + #[error("value mismatch")] + ValueMismatch, + #[error("expected value but got None")] + ExpectedValue, + #[error("proof can't be empty")] + Empty, + #[error("each proof node key should be a prefix of the proven key")] + ShouldBePrefixOfProvenKey, + #[error("each proof node key should be a prefix of the next key")] + ShouldBePrefixOfNextKey, + #[error("child index is out of bounds")] + ChildIndexOutOfBounds, + #[error("only nodes with even length key can have values")] + ValueAtOddNibbleLength, + #[error("node not in trie")] + NodeNotInTrie, + #[error("{0:?}")] + Merkle(#[from] MerkleError), + #[error("empty range")] + EmptyRange, +} + +#[derive(Clone, Debug)] +pub struct ProofNode { + /// The key this node is at. Each byte is a nibble. + pub key: Box<[u8]>, + /// None if the node does not have a value. + /// Otherwise, the node's value or the hash of its value. + pub value_digest: Option>>, + /// The hash of each child, or None if the child does not exist. + pub child_hashes: [Option; BranchNode::MAX_CHILDREN], +} + +impl Hashable for ProofNode { + fn key(&self) -> impl Iterator + Clone { + self.key.as_ref().iter().copied() + } + + fn value_digest(&self) -> Option> { + self.value_digest.as_ref().map(|vd| match vd { + ValueDigest::Value(v) => ValueDigest::Value(v.as_ref()), + ValueDigest::_Hash(h) => ValueDigest::_Hash(h.as_ref()), + }) + } + + fn children(&self) -> impl Iterator + Clone { + self.child_hashes + .iter() + .enumerate() + .filter_map(|(i, hash)| hash.as_ref().map(|h| (i, h))) + } +} + +impl From for ProofNode { + fn from(item: PathIterItem) -> Self { + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + + if let Some(branch) = item.node.as_branch() { + // TODO danlaine: can we avoid indexing? + #[allow(clippy::indexing_slicing)] + for (i, hash) in branch.children_iter() { + child_hashes[i] = Some(hash.clone()); + } + } + + Self { + key: item.key_nibbles, + value_digest: item + .node + .value() + .map(|value| ValueDigest::Value(value.to_vec().into_boxed_slice())), + child_hashes, + } + } +} + +impl From<&ProofNode> for TrieHash { + fn from(node: &ProofNode) -> Self { + node.to_hash() + } +} + +/// A proof that a given key-value pair either exists or does not exist in a trie. +#[derive(Clone, Debug)] +pub struct Proof(pub Box<[T]>); + +impl Proof { + pub fn verify, V: AsRef<[u8]>>( + &self, + key: K, + expected_value: Option, + root_hash: &TrieHash, + ) -> Result<(), ProofError> { + let value_digest = self.value_digest(key, root_hash)?; + + let Some(value_digest) = value_digest else { + // This proof proves that `key` maps to None. + if expected_value.is_some() { + return Err(ProofError::ExpectedValue); + } + return Ok(()); + }; + + let Some(expected_value) = expected_value else { + // We were expecting `key` to map to None. + return Err(ProofError::UnexpectedValue); + }; + + match value_digest { + ValueDigest::Value(got_value) => { + // This proof proves that `key` maps to `got_value`. + if got_value != expected_value.as_ref() { + // `key` maps to an unexpected value. + return Err(ProofError::ValueMismatch); + } + } + ValueDigest::_Hash(got_hash) => { + // This proof proves that `key` maps to a value + // whose hash is `got_hash`. + let value_hash = Sha256::digest(expected_value.as_ref()); + if got_hash != value_hash.as_slice() { + // `key` maps to an unexpected value. + return Err(ProofError::ValueMismatch); + } + } + } + Ok(()) + } + + /// Returns the value digest associated with the given `key` in the trie revision + /// with the given `root_hash`. If the key does not exist in the trie, returns `None`. + /// Returns an error if the proof is invalid or doesn't prove the key for the + /// given revision. + fn value_digest>( + &self, + key: K, + root_hash: &TrieHash, + ) -> Result>, ProofError> { + let key: Box<[u8]> = NibblesIterator::new(key.as_ref()).collect(); + + let Some(last_node) = self.0.last() else { + return Err(ProofError::Empty); + }; + + let mut expected_hash = root_hash; + + let mut iter = self.0.iter().peekable(); + while let Some(node) = iter.next() { + if node.to_hash() != *expected_hash { + return Err(ProofError::UnexpectedHash); + } + + // Assert that only nodes whose keys are an even number of nibbles + // have a `value_digest`. + if node.key().count() % 2 != 0 && node.value_digest().is_some() { + return Err(ProofError::ValueAtOddNibbleLength); + } + + if let Some(next_node) = iter.peek() { + // Assert that every node's key is a prefix of `key`, except for the last node, + // whose key can be equal to or a suffix of `key` in an exclusion proof. + if next_nibble(node.key(), key.iter().copied()).is_none() { + return Err(ProofError::ShouldBePrefixOfProvenKey); + } + + // Assert that every node's key is a prefix of the next node's key. + let next_node_index = next_nibble(node.key(), next_node.key()); + + let Some(next_nibble) = next_node_index else { + return Err(ProofError::ShouldBePrefixOfNextKey); + }; + + expected_hash = node + .children() + .find_map(|(i, hash)| { + if i == next_nibble as usize { + Some(hash) + } else { + None + } + }) + .ok_or(ProofError::NodeNotInTrie)?; + } + } + + if last_node.key().count() == key.len() { + return Ok(last_node.value_digest()); + } + + // This is an exclusion proof. + Ok(None) + } +} + +/// Returns the next nibble in `c` after `b`. +/// Returns None if `b` is not a strict prefix of `c`. +fn next_nibble(b: B, c: C) -> Option +where + B: IntoIterator, + C: IntoIterator, +{ + let b = b.into_iter(); + let mut c = c.into_iter(); + + // Check if b is a prefix of c + for b_item in b { + match c.next() { + Some(c_item) if b_item == c_item => continue, + _ => return None, + } + } + + c.next() +} diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs new file mode 100644 index 000000000000..5705ddb3bca8 --- /dev/null +++ b/firewood/src/range_proof.rs @@ -0,0 +1,18 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use storage::Hashable; + +use crate::proof::Proof; + +/// A range proof proves that a given set of key-value pairs +/// are in the trie with a given root hash. +#[derive(Debug)] +pub struct RangeProof, V: AsRef<[u8]>, H: Hashable> { + #[allow(dead_code)] + pub(crate) start_proof: Option>, + #[allow(dead_code)] + pub(crate) end_proof: Option>, + #[allow(dead_code)] + pub(crate) key_values: Box<[(K, V)]>, +} diff --git a/firewood/src/shale/README.md b/firewood/src/shale/README.md deleted file mode 100644 index 361804102e36..000000000000 --- a/firewood/src/shale/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# shale - -This directory was forked from [`shale`](https://github.com/Determinant/shale) at commit [`caa6d75`](https://github.com/Determinant/shale/commit/caa6d7543d253e2172c51a65d226d65d232a9b9a), under MIT license. diff --git a/firewood/src/shale/compact.rs b/firewood/src/shale/compact.rs deleted file mode 100644 index e23f6de6a037..000000000000 --- a/firewood/src/shale/compact.rs +++ /dev/null @@ -1,848 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::logger::trace; -use crate::merkle::Node; -use crate::shale::ObjCache; -use crate::storage::{StoreRevMut, StoreRevShared}; - -use super::disk_address::DiskAddress; -use super::{LinearStore, Obj, ObjRef, ShaleError, Storable, StoredView}; -use bytemuck::{Pod, Zeroable}; -use std::fmt::Debug; -use std::io::{Cursor, Write}; -use std::num::NonZeroUsize; -use std::sync::RwLock; - -/// Marks the start of a linear chunk of the store. -/// The chunk may be freed or in use. -#[derive(Debug)] -pub struct ChunkHeader { - chunk_size: u64, - is_freed: bool, - desc_addr: DiskAddress, -} - -impl ChunkHeader { - const IS_FREED_OFFSET: usize = std::mem::size_of::(); - const DESC_ADDR_OFFSET: usize = Self::IS_FREED_OFFSET + 1; - pub const SERIALIZED_LEN: u64 = (Self::DESC_ADDR_OFFSET + std::mem::size_of::()) as u64; - - pub const fn is_freed(&self) -> bool { - self.is_freed - } - - pub const fn chunk_size(&self) -> u64 { - self.chunk_size - } -} - -impl Storable for ChunkHeader { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - #[allow(clippy::indexing_slicing)] - let chunk_size = u64::from_le_bytes( - raw.as_deref()[..Self::IS_FREED_OFFSET] - .try_into() - .expect("invalid slice"), - ); - #[allow(clippy::indexing_slicing)] - let is_freed = raw.as_deref()[Self::IS_FREED_OFFSET] != 0; - #[allow(clippy::indexing_slicing)] - let desc_addr = usize::from_le_bytes( - raw.as_deref()[Self::DESC_ADDR_OFFSET..Self::SERIALIZED_LEN as usize] - .try_into() - .expect("invalid slice"), - ); - Ok(Self { - chunk_size, - is_freed, - desc_addr: DiskAddress(NonZeroUsize::new(desc_addr)), - }) - } - - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.chunk_size.to_le_bytes())?; - cur.write_all(&[if self.is_freed { 1 } else { 0 }])?; - cur.write_all(&self.desc_addr.to_le_bytes())?; - Ok(()) - } -} - -/// Marks the end of a linear chunk of the store. -#[derive(Debug)] -struct ChunkFooter { - chunk_size: u64, -} - -impl ChunkFooter { - const SERIALIZED_LEN: u64 = std::mem::size_of::() as u64; -} - -impl Storable for ChunkFooter { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - #[allow(clippy::unwrap_used)] - let chunk_size = u64::from_le_bytes(raw.as_deref().try_into().unwrap()); - Ok(Self { chunk_size }) - } - - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - Cursor::new(to).write_all(&self.chunk_size.to_le_bytes())?; - Ok(()) - } -} - -#[derive(Clone, Copy, Debug)] -struct ChunkDescriptor { - chunk_size: u64, - haddr: usize, // disk address of the free space -} - -impl ChunkDescriptor { - const HADDR_OFFSET: usize = 8; - const SERIALIZED_LEN: u64 = (Self::HADDR_OFFSET + std::mem::size_of::()) as u64; -} - -impl Storable for ChunkDescriptor { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - #[allow(clippy::indexing_slicing)] - let chunk_size = u64::from_le_bytes( - raw.as_deref()[..Self::HADDR_OFFSET] - .try_into() - .expect("invalid slice"), - ); - #[allow(clippy::indexing_slicing)] - let haddr = usize::from_le_bytes( - raw.as_deref()[Self::HADDR_OFFSET..] - .try_into() - .expect("invalid slice"), - ); - Ok(Self { chunk_size, haddr }) - } - - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.chunk_size.to_le_bytes())?; - cur.write_all(&self.haddr.to_le_bytes())?; - Ok(()) - } -} - -/// A header for a [Store]. -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -pub struct StoreHeader { - meta_store_tail: DiskAddress, - data_store_tail: DiskAddress, - base_addr: DiskAddress, - alloc_addr: DiskAddress, -} - -#[derive(Debug)] -struct StoreHeaderObjs { - meta_store_tail: Obj, - data_store_tail: Obj, - base_addr: Obj, - alloc_addr: Obj, -} - -impl StoreHeaderObjs { - fn flush_dirty(&mut self) { - self.meta_store_tail.flush_dirty(); - self.data_store_tail.flush_dirty(); - self.base_addr.flush_dirty(); - self.alloc_addr.flush_dirty(); - } -} - -impl StoreHeader { - const META_STORE_TAIL_OFFSET: usize = 0; - const DATA_STORE_TAIL_OFFSET: usize = DiskAddress::SERIALIZED_LEN as usize; - const BASE_ADDR_OFFSET: usize = - Self::DATA_STORE_TAIL_OFFSET + DiskAddress::SERIALIZED_LEN as usize; - const ALLOC_ADDR_OFFSET: usize = Self::BASE_ADDR_OFFSET + DiskAddress::SERIALIZED_LEN as usize; - pub const SERIALIZED_LEN: u64 = Self::ALLOC_ADDR_OFFSET as u64 + DiskAddress::SERIALIZED_LEN; - - pub const fn new(meta_base: NonZeroUsize, compact_base: NonZeroUsize) -> Self { - Self { - meta_store_tail: DiskAddress::new(meta_base), - data_store_tail: DiskAddress::new(compact_base), - base_addr: DiskAddress::new(meta_base), - alloc_addr: DiskAddress::new(meta_base), - } - } - - fn into_fields(r: Obj) -> Result { - Ok(StoreHeaderObjs { - meta_store_tail: StoredView::slice( - &r, - Self::META_STORE_TAIL_OFFSET, - DiskAddress::SERIALIZED_LEN, - r.meta_store_tail, - )?, - data_store_tail: StoredView::slice( - &r, - Self::DATA_STORE_TAIL_OFFSET, - DiskAddress::SERIALIZED_LEN, - r.data_store_tail, - )?, - base_addr: StoredView::slice( - &r, - Self::BASE_ADDR_OFFSET, - DiskAddress::SERIALIZED_LEN, - r.base_addr, - )?, - alloc_addr: StoredView::slice( - &r, - Self::ALLOC_ADDR_OFFSET, - DiskAddress::SERIALIZED_LEN, - r.alloc_addr, - )?, - }) - } -} - -impl Storable for StoreHeader { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = mem - .get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - #[allow(clippy::indexing_slicing)] - let meta_store_tail = raw.as_deref()[..Self::DATA_STORE_TAIL_OFFSET] - .try_into() - .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); - #[allow(clippy::indexing_slicing)] - let data_store_tail = raw.as_deref()[Self::DATA_STORE_TAIL_OFFSET..Self::BASE_ADDR_OFFSET] - .try_into() - .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); - #[allow(clippy::indexing_slicing)] - let base_addr = raw.as_deref()[Self::BASE_ADDR_OFFSET..Self::ALLOC_ADDR_OFFSET] - .try_into() - .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); - #[allow(clippy::indexing_slicing)] - let alloc_addr = raw.as_deref()[Self::ALLOC_ADDR_OFFSET..] - .try_into() - .expect("Self::MSIZE = 4 * DiskAddress::MSIZE"); - Ok(Self { - meta_store_tail, - data_store_tail, - base_addr, - alloc_addr, - }) - } - - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = Cursor::new(to); - cur.write_all(&self.meta_store_tail.to_le_bytes())?; - cur.write_all(&self.data_store_tail.to_le_bytes())?; - cur.write_all(&self.base_addr.to_le_bytes())?; - cur.write_all(&self.alloc_addr.to_le_bytes())?; - Ok(()) - } -} - -#[derive(Debug)] -struct StoreInner { - meta_store: M, - data_store: M, - header: StoreHeaderObjs, - alloc_max_walk: u64, - regn_nbit: u64, -} - -impl From> for StoreInner { - fn from(value: StoreInner) -> StoreInner { - StoreInner { - meta_store: value.meta_store.into(), - data_store: value.data_store.into(), - header: value.header, - alloc_max_walk: value.alloc_max_walk, - regn_nbit: value.regn_nbit, - } - } -} - -impl StoreInner { - fn get_data_ref( - &self, - addr: DiskAddress, - len_limit: u64, - ) -> Result, ShaleError> { - StoredView::addr_to_obj(&self.data_store, addr, len_limit) - } - - fn get_header(&self, addr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(addr, ChunkHeader::SERIALIZED_LEN) - } - - fn get_footer(&self, addr: DiskAddress) -> Result, ShaleError> { - self.get_data_ref::(addr, ChunkFooter::SERIALIZED_LEN) - } - - fn get_descriptor(&self, addr: DiskAddress) -> Result, ShaleError> { - StoredView::addr_to_obj(&self.meta_store, addr, ChunkDescriptor::SERIALIZED_LEN) - } - - fn delete_descriptor(&mut self, desc_addr: DiskAddress) -> Result<(), ShaleError> { - // TODO: subtracting two disk addresses is only used here, probably can rewrite this - // debug_assert!((desc_addr.0 - self.header.base_addr.value.into()) % ChunkDescriptor::SERIALIZED_LEN == 0); - // Move the last descriptor to the position of the deleted descriptor - #[allow(clippy::unwrap_used)] - self.header - .meta_store_tail - .modify(|r| *r -= ChunkDescriptor::SERIALIZED_LEN as usize) - .unwrap(); - - if desc_addr != DiskAddress(**self.header.meta_store_tail) { - let last_desc = self.get_descriptor(*self.header.meta_store_tail.value)?; - - let mut desc = self.get_descriptor(desc_addr)?; - #[allow(clippy::unwrap_used)] - desc.modify(|r| *r = *last_desc).unwrap(); - - // `chunk_header` is associated with the deleted descriptor - let mut chunk_header = self.get_header(desc.haddr.into())?; - #[allow(clippy::unwrap_used)] - chunk_header.modify(|h| h.desc_addr = desc_addr).unwrap(); - } - - Ok(()) - } - - fn new_descriptor_address(&mut self) -> Result { - let addr = **self.header.meta_store_tail; - #[allow(clippy::unwrap_used)] - self.header - .meta_store_tail - .modify(|r| *r += ChunkDescriptor::SERIALIZED_LEN as usize) - .unwrap(); - - Ok(DiskAddress(addr)) - } - - fn free(&mut self, freed_addr: u64) -> Result<(), ShaleError> { - let region_size = 1 << self.regn_nbit; - - let mut freed_header_offset = freed_addr - ChunkHeader::SERIALIZED_LEN; - let mut freed_chunk_size = { - let header = self.get_header(DiskAddress::from(freed_header_offset as usize))?; - assert!(!header.is_freed); - header.chunk_size - }; - let mut freed_footer_offset = freed_addr + freed_chunk_size; - - if freed_header_offset & (region_size - 1) > 0 { - // TODO danlaine: document what this condition means. - // merge with previous chunk if it's freed. - let prev_footer_offset = freed_header_offset - ChunkFooter::SERIALIZED_LEN; - let prev_footer = self.get_footer(DiskAddress::from(prev_footer_offset as usize))?; - - let prev_header_offset = - prev_footer_offset - prev_footer.chunk_size - ChunkHeader::SERIALIZED_LEN; - let prev_header = self.get_header(DiskAddress::from(prev_header_offset as usize))?; - - if prev_header.is_freed { - freed_header_offset = prev_header_offset; - freed_chunk_size += ChunkHeader::SERIALIZED_LEN - + ChunkFooter::SERIALIZED_LEN - + prev_header.chunk_size; - self.delete_descriptor(prev_header.desc_addr)?; - } - } - - #[allow(clippy::unwrap_used)] - if freed_footer_offset + ChunkFooter::SERIALIZED_LEN - < self.header.data_store_tail.unwrap().get() as u64 - && (region_size - (freed_footer_offset & (region_size - 1))) - >= ChunkFooter::SERIALIZED_LEN + ChunkHeader::SERIALIZED_LEN - { - // TODO danlaine: document what this condition means. - // merge with next chunk if it's freed. - let next_header_offset = freed_footer_offset + ChunkFooter::SERIALIZED_LEN; - let next_header = self.get_header(DiskAddress::from(next_header_offset as usize))?; - - if next_header.is_freed { - let next_footer_offset = - next_header_offset + ChunkHeader::SERIALIZED_LEN + next_header.chunk_size; - freed_footer_offset = next_footer_offset; - { - let next_footer = - self.get_footer(DiskAddress::from(next_footer_offset as usize))?; - assert!(next_header.chunk_size == next_footer.chunk_size); - } - freed_chunk_size += ChunkHeader::SERIALIZED_LEN - + ChunkFooter::SERIALIZED_LEN - + next_header.chunk_size; - self.delete_descriptor(next_header.desc_addr)?; - } - } - - let desc_addr = self.new_descriptor_address()?; - { - let mut desc = self.get_descriptor(desc_addr)?; - #[allow(clippy::unwrap_used)] - desc.modify(|d| { - d.chunk_size = freed_chunk_size; - d.haddr = freed_header_offset as usize; - }) - .unwrap(); - } - let mut freed_header = self.get_header(DiskAddress::from(freed_header_offset as usize))?; - #[allow(clippy::unwrap_used)] - freed_header - .modify(|h| { - h.chunk_size = freed_chunk_size; - h.is_freed = true; - h.desc_addr = desc_addr; - }) - .unwrap(); - - let mut free_footer = self.get_footer(DiskAddress::from(freed_footer_offset as usize))?; - #[allow(clippy::unwrap_used)] - free_footer - .modify(|f| f.chunk_size = freed_chunk_size) - .unwrap(); - - Ok(()) - } - - fn alloc_from_freed(&mut self, length: u64) -> Result, ShaleError> { - const HEADER_SIZE: usize = ChunkHeader::SERIALIZED_LEN as usize; - const FOOTER_SIZE: usize = ChunkFooter::SERIALIZED_LEN as usize; - const DESCRIPTOR_SIZE: usize = ChunkDescriptor::SERIALIZED_LEN as usize; - - let tail = *self.header.meta_store_tail; - if tail == *self.header.base_addr { - return Ok(None); - } - - let mut old_alloc_addr = *self.header.alloc_addr; - - if old_alloc_addr >= tail { - old_alloc_addr = *self.header.base_addr; - } - - let mut addr = old_alloc_addr; - let mut res: Option = None; - for _ in 0..self.alloc_max_walk { - assert!(addr < tail); - let (chunk_size, desc_haddr) = { - let desc = self.get_descriptor(addr)?; - (desc.chunk_size as usize, desc.haddr) - }; - let exit = if chunk_size == length as usize { - // perfect match - { - let mut header = self.get_header(DiskAddress::from(desc_haddr))?; - assert_eq!(header.chunk_size as usize, chunk_size); - assert!(header.is_freed); - #[allow(clippy::unwrap_used)] - header.modify(|h| h.is_freed = false).unwrap(); - } - self.delete_descriptor(addr)?; - true - } else if chunk_size > length as usize + HEADER_SIZE + FOOTER_SIZE { - // able to split - { - let mut lheader = self.get_header(DiskAddress::from(desc_haddr))?; - assert_eq!(lheader.chunk_size as usize, chunk_size); - assert!(lheader.is_freed); - #[allow(clippy::unwrap_used)] - lheader - .modify(|h| { - h.is_freed = false; - h.chunk_size = length; - }) - .unwrap(); - } - { - let mut lfooter = self.get_footer(DiskAddress::from( - desc_haddr + HEADER_SIZE + length as usize, - ))?; - //assert!(lfooter.chunk_size == chunk_size); - #[allow(clippy::unwrap_used)] - lfooter.modify(|f| f.chunk_size = length).unwrap(); - } - - let offset = desc_haddr + HEADER_SIZE + length as usize + FOOTER_SIZE; - let rchunk_size = chunk_size - length as usize - FOOTER_SIZE - HEADER_SIZE; - let rdesc_addr = self.new_descriptor_address()?; - { - let mut rdesc = self.get_descriptor(rdesc_addr)?; - #[allow(clippy::unwrap_used)] - rdesc - .modify(|rd| { - rd.chunk_size = rchunk_size as u64; - rd.haddr = offset; - }) - .unwrap(); - } - { - let mut rheader = self.get_header(DiskAddress::from(offset))?; - #[allow(clippy::unwrap_used)] - rheader - .modify(|rh| { - rh.is_freed = true; - rh.chunk_size = rchunk_size as u64; - rh.desc_addr = rdesc_addr; - }) - .unwrap(); - } - { - let mut rfooter = - self.get_footer(DiskAddress::from(offset + HEADER_SIZE + rchunk_size))?; - #[allow(clippy::unwrap_used)] - rfooter - .modify(|f| f.chunk_size = rchunk_size as u64) - .unwrap(); - } - self.delete_descriptor(addr)?; - true - } else { - false - }; - #[allow(clippy::unwrap_used)] - if exit { - self.header.alloc_addr.modify(|r| *r = addr).unwrap(); - res = Some((desc_haddr + HEADER_SIZE) as u64); - break; - } - addr += DESCRIPTOR_SIZE; - if addr >= tail { - addr = *self.header.base_addr; - } - if addr == old_alloc_addr { - break; - } - } - Ok(res) - } - - fn alloc_new(&mut self, alloc_size: u64) -> Result { - let region_size = 1 << self.regn_nbit; - let new_chunk_size = ChunkHeader::SERIALIZED_LEN + alloc_size + ChunkFooter::SERIALIZED_LEN; - let mut free_chunk_header_offset = *self.header.data_store_tail; - - #[allow(clippy::unwrap_used)] - self.header - .data_store_tail - .modify(|data_store_tail| { - // an item is always fully in one region - // TODO danlaine: we should document the above better. Where is this guaranteed? - let remaining_region_size = - region_size - (free_chunk_header_offset & (region_size - 1)).get(); - - if remaining_region_size < new_chunk_size as usize { - // There is not enough space in the current region for this alloc. - // Move to the next region. - free_chunk_header_offset += remaining_region_size; - *data_store_tail += remaining_region_size; - } - - *data_store_tail += new_chunk_size as usize - }) - .unwrap(); - - let mut free_chunk_header = self.get_header(free_chunk_header_offset)?; - - #[allow(clippy::unwrap_used)] - free_chunk_header - .modify(|h| { - h.chunk_size = alloc_size; - h.is_freed = false; - h.desc_addr = DiskAddress::null(); - }) - .unwrap(); - - let mut free_chunk_footer = self.get_footer( - free_chunk_header_offset + ChunkHeader::SERIALIZED_LEN as usize + alloc_size as usize, - )?; - - #[allow(clippy::unwrap_used)] - free_chunk_footer - .modify(|f| f.chunk_size = alloc_size) - .unwrap(); - - #[allow(clippy::unwrap_used)] - Ok( - (free_chunk_header_offset + ChunkHeader::SERIALIZED_LEN as usize) - .0 - .unwrap() - .get() as u64, - ) - } - - fn alloc(&mut self, length: u64) -> Result { - self.alloc_from_freed(length).and_then(|addr| { - if let Some(addr) = addr { - Ok(addr) - } else { - self.alloc_new(length) - } - }) - } -} - -#[derive(Debug)] -pub struct Store { - inner: RwLock>, - obj_cache: ObjCache, -} - -impl Store { - pub fn new( - meta_store: M, - data_store: M, - header: Obj, - obj_cache: super::ObjCache, - alloc_max_walk: u64, - regn_nbit: u64, - ) -> Result { - let cs = Store { - inner: RwLock::new(StoreInner { - meta_store, - data_store, - header: StoreHeader::into_fields(header)?, - alloc_max_walk, - regn_nbit, - }), - obj_cache, - }; - Ok(cs) - } -} - -impl From> for Store { - #[allow(clippy::unwrap_used)] - fn from(value: Store) -> Self { - let inner = value.inner.into_inner().unwrap(); - Store { - inner: RwLock::new(inner.into()), - obj_cache: value.obj_cache, - } - } -} - -impl Store { - pub(crate) fn put_item(&self, item: T, extra: u64) -> Result, ShaleError> { - let size = item.serialized_len() + extra; - #[allow(clippy::unwrap_used)] - let addr = self.inner.write().unwrap().alloc(size)?; - - trace!("{self:p} put_item at {addr} size {size}"); - - #[allow(clippy::unwrap_used)] - let obj = { - let inner = self.inner.read().unwrap(); - let data_store = &inner.data_store; - #[allow(clippy::unwrap_used)] - let view = StoredView::item_to_obj(data_store, addr.try_into().unwrap(), size, item)?; - - self.obj_cache.put(view) - }; - - let cache = &self.obj_cache; - - let mut obj_ref = ObjRef::new(obj, cache); - - // should this use a `?` instead of `unwrap`? - #[allow(clippy::unwrap_used)] - obj_ref.write(|_| {}).unwrap(); - - Ok(obj_ref) - } - - #[allow(clippy::unwrap_used)] - pub(crate) fn free_item(&mut self, addr: DiskAddress) -> Result<(), ShaleError> { - let mut inner = self.inner.write().unwrap(); - self.obj_cache.pop(addr); - #[allow(clippy::unwrap_used)] - inner.free(addr.unwrap().get() as u64) - } - - pub(crate) fn get_item(&self, addr: DiskAddress) -> Result, ShaleError> { - let obj = self.obj_cache.get(addr)?; - - #[allow(clippy::unwrap_used)] - let inner = self.inner.read().unwrap(); - let cache = &self.obj_cache; - - if let Some(obj) = obj { - return Ok(ObjRef::new(obj, cache)); - } - - #[allow(clippy::unwrap_used)] - if addr < DiskAddress::from(StoreHeader::SERIALIZED_LEN as usize) { - return Err(ShaleError::InvalidAddressLength { - expected: StoreHeader::SERIALIZED_LEN, - found: addr.0.map(|inner| inner.get()).unwrap_or_default() as u64, - }); - } - - let chunk_size = inner - .get_header(addr - ChunkHeader::SERIALIZED_LEN as usize)? - .chunk_size; - let obj = self.obj_cache.put(inner.get_data_ref(addr, chunk_size)?); - let cache = &self.obj_cache; - - Ok(ObjRef::new(obj, cache)) - } - - #[allow(clippy::unwrap_used)] - pub(crate) fn flush_dirty(&self) -> Option<()> { - let mut inner = self.inner.write().unwrap(); - inner.header.flush_dirty(); - // hold the write lock to ensure that both cache and header are flushed in-sync - self.obj_cache.flush_dirty() - } -} - -#[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] -mod tests { - use sha3::Digest; - - use crate::shale::{self, in_mem::InMemLinearStore}; - - use super::*; - - const HASH_SIZE: usize = 32; - const ZERO_HASH: Hash = Hash([0u8; HASH_SIZE]); - - #[derive(PartialEq, Eq, Debug, Clone)] - pub struct Hash(pub [u8; HASH_SIZE]); - - impl Hash { - const SERIALIZED_LEN: u64 = 32; - } - - impl std::ops::Deref for Hash { - type Target = [u8; HASH_SIZE]; - fn deref(&self) -> &[u8; HASH_SIZE] { - &self.0 - } - } - - impl Storable for Hash { - fn deserialize(addr: usize, mem: &T) -> Result { - let raw = - mem.get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - Ok(Self( - raw.as_deref()[..Self::SERIALIZED_LEN as usize] - .try_into() - .expect("invalid slice"), - )) - } - - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - let mut cur = to; - cur.write_all(&self.0)?; - Ok(()) - } - } - - #[test] - fn test_store_item() { - let meta_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); - let compact_size: NonZeroUsize = NonZeroUsize::new(0x10000).unwrap(); - let reserved: DiskAddress = 0x1000.into(); - - let mut dm = InMemLinearStore::new(meta_size.get() as u64, 0x0); - - // initialize compact store - let compact_header = DiskAddress::from(0x1); - dm.write( - compact_header.unwrap().get(), - &shale::to_dehydrated(&StoreHeader::new(reserved.0.unwrap(), reserved.0.unwrap())) - .unwrap(), - ) - .unwrap(); - let compact_header = - StoredView::addr_to_obj(&dm, compact_header, ChunkHeader::SERIALIZED_LEN).unwrap(); - let mem_meta = dm; - let mem_payload = InMemLinearStore::new(compact_size.get() as u64, 0x1); - - let cache: ObjCache = ObjCache::new(1); - let store = Store::new(mem_meta, mem_payload, compact_header, cache, 10, 16).unwrap(); - - // initial write - let data = b"hello world"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - let obj_ref = store.put_item(Hash(hash), 0).unwrap(); - assert_eq!(obj_ref.as_addr(), DiskAddress::from(4113)); - // create hash addr from address and attempt to read dirty write. - let hash_ref = store.get_item(DiskAddress::from(4113)).unwrap(); - // read before flush results in zeroed hash - assert_eq!(hash_ref.as_ref(), ZERO_HASH.as_ref()); - // not cached - assert!(obj_ref - .cache - .lock() - .cached - .get(&DiskAddress::from(4113)) - .is_none()); - // pinned - assert!(obj_ref - .cache - .lock() - .pinned - .contains_key(&DiskAddress::from(4113))); - // dirty - assert!(obj_ref - .cache - .lock() - .dirty - .contains(&DiskAddress::from(4113))); - drop(obj_ref); - // write is visible - assert_eq!( - store.get_item(DiskAddress::from(4113)).unwrap().as_ref(), - hash - ); - } -} diff --git a/firewood/src/shale/disk_address.rs b/firewood/src/shale/disk_address.rs deleted file mode 100644 index e96b1eb701c9..000000000000 --- a/firewood/src/shale/disk_address.rs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::hash::Hash; -use std::mem::size_of; -use std::num::NonZeroUsize; -use std::ops::{Deref, DerefMut}; - -use bytemuck::{Pod, Zeroable}; - -use crate::shale::{LinearStore, ShaleError, Storable}; - -/// The virtual disk address of an object -#[repr(transparent)] -#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialOrd, PartialEq, Pod, Zeroable)] -pub struct DiskAddress(pub Option); - -impl Deref for DiskAddress { - type Target = Option; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for DiskAddress { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl DiskAddress { - pub(crate) const SERIALIZED_LEN: u64 = size_of::() as u64; - - /// Return a None DiskAddress - pub const fn null() -> Self { - DiskAddress(None) - } - - /// Indicate whether the DiskAddress is null - pub fn is_null(&self) -> bool { - self.is_none() - } - - /// Convert a NonZeroUsize to a DiskAddress - pub const fn new(addr: NonZeroUsize) -> Self { - DiskAddress(Some(addr)) - } - - /// Get the little endian bytes for a DiskAddress for storage - pub fn to_le_bytes(&self) -> [u8; Self::SERIALIZED_LEN as usize] { - self.0.map(|v| v.get()).unwrap_or_default().to_le_bytes() - } - - /// Get the inner usize, using 0 if None - pub fn get(&self) -> usize { - self.0.map(|v| v.get()).unwrap_or_default() - } -} - -/// Convert from a usize to a DiskAddress -impl From for DiskAddress { - fn from(value: usize) -> Self { - DiskAddress(NonZeroUsize::new(value)) - } -} - -/// Convert from a serialized le_bytes to a DiskAddress -impl From<[u8; 8]> for DiskAddress { - fn from(value: [u8; Self::SERIALIZED_LEN as usize]) -> Self { - Self::from(usize::from_le_bytes(value)) - } -} - -/// Convert from a slice of bytes to a DiskAddress -/// panics if the slice isn't 8 bytes; used for -/// serialization from disk -impl TryFrom<&[u8]> for DiskAddress { - type Error = std::array::TryFromSliceError; - - fn try_from(value: &[u8]) -> Result { - let bytes: [u8; Self::SERIALIZED_LEN as usize] = value.try_into()?; - Ok(bytes.into()) - } -} - -/// Convert a DiskAddress into a usize -/// TODO: panic if the DiskAddress is None -impl From for usize { - fn from(value: DiskAddress) -> usize { - value.get() - } -} - -/// Add two disk addresses; -/// TODO: panic if either are null -impl std::ops::Add for DiskAddress { - type Output = DiskAddress; - - fn add(self, rhs: DiskAddress) -> Self::Output { - self + rhs.get() - } -} - -/// Add a usize to a DiskAddress -/// TODO: panic if the DiskAddress is null -impl std::ops::Add for DiskAddress { - type Output = DiskAddress; - - fn add(self, rhs: usize) -> Self::Output { - (self.get() + rhs).into() - } -} - -/// subtract one disk address from another -/// TODO: panic if either are null -impl std::ops::Sub for DiskAddress { - type Output = DiskAddress; - - fn sub(self, rhs: DiskAddress) -> Self::Output { - self - rhs.get() - } -} - -/// subtract a usize from a diskaddress -/// panic if the DiskAddress is null -impl std::ops::Sub for DiskAddress { - type Output = DiskAddress; - - fn sub(self, rhs: usize) -> Self::Output { - (self.get() - rhs).into() - } -} - -impl std::ops::AddAssign for DiskAddress { - fn add_assign(&mut self, rhs: DiskAddress) { - *self = *self + rhs; - } -} - -impl std::ops::AddAssign for DiskAddress { - fn add_assign(&mut self, rhs: usize) { - *self = *self + rhs; - } -} - -impl std::ops::SubAssign for DiskAddress { - fn sub_assign(&mut self, rhs: DiskAddress) { - *self = *self - rhs; - } -} - -impl std::ops::SubAssign for DiskAddress { - fn sub_assign(&mut self, rhs: usize) { - *self = *self - rhs; - } -} - -impl std::ops::BitAnd for DiskAddress { - type Output = DiskAddress; - - fn bitand(self, rhs: usize) -> Self::Output { - (self.get() & rhs).into() - } -} - -impl Storable for DiskAddress { - fn serialized_len(&self) -> u64 { - Self::SERIALIZED_LEN - } - - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError> { - use std::io::{Cursor, Write}; - #[allow(clippy::unwrap_used)] - Cursor::new(to).write_all(&self.0.unwrap().get().to_le_bytes())?; - Ok(()) - } - - fn deserialize(addr: usize, mem: &U) -> Result { - let raw = mem - .get_view(addr, Self::SERIALIZED_LEN) - .ok_or(ShaleError::InvalidCacheView { - offset: addr, - size: Self::SERIALIZED_LEN, - })?; - let addrdyn = &*raw; - let addrvec = addrdyn.as_deref(); - #[allow(clippy::unwrap_used)] - Ok(Self(NonZeroUsize::new(usize::from_le_bytes( - addrvec.try_into().unwrap(), - )))) - } -} diff --git a/firewood/src/shale/in_mem.rs b/firewood/src/shale/in_mem.rs deleted file mode 100644 index 8fea3849724b..000000000000 --- a/firewood/src/shale/in_mem.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::shale::{LinearStore, LinearStoreView, SendSyncDerefMut, StoreId}; -use std::{ - fmt::Debug, - ops::{Deref, DerefMut}, - sync::{Arc, RwLock}, -}; - -use super::ShaleError; - -// Purely volatile, dynamically allocated vector-based implementation for -// [CachedStore]. Allocates more space on `write` if original size isn't enough. -#[derive(Debug)] -pub struct InMemLinearStore { - store: Arc>>, - id: StoreId, -} - -impl InMemLinearStore { - pub fn new(size: u64, id: StoreId) -> Self { - let store = Arc::new(RwLock::new(vec![0; size as usize])); - Self { store, id } - } -} - -impl LinearStore for InMemLinearStore { - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>> { - let length = length as usize; - let size = offset + length; - #[allow(clippy::unwrap_used)] - let mut store = self.store.write().unwrap(); - - // Increase the size if the request range exceeds the current limit. - if size > store.len() { - store.resize(size, 0); - } - - Some(Box::new(InMemLinearStoreView { - offset, - length, - mem: Self { - store: self.store.clone(), - id: self.id, - }, - })) - } - - fn get_shared(&self) -> Box> { - Box::new(InMemLinearStoreShared(Self { - store: self.store.clone(), - id: self.id, - })) - } - - fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError> { - let length = change.len(); - let size = offset + length; - - #[allow(clippy::unwrap_used)] - let mut store = self.store.write().unwrap(); - - // Increase the size if the request range exceeds the current limit. - if size > store.len() { - store.resize(size, 0); - } - #[allow(clippy::indexing_slicing)] - store[offset..offset + length].copy_from_slice(change); - - Ok(()) - } - - fn id(&self) -> StoreId { - self.id - } - - fn is_writeable(&self) -> bool { - true - } -} - -/// A range within an in-memory linear byte store. -#[derive(Debug)] -struct InMemLinearStoreView { - /// The start of the range. - offset: usize, - /// The length of the range. - length: usize, - /// The underlying store. - mem: InMemLinearStore, -} - -impl LinearStoreView for InMemLinearStoreView { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - self.mem.store.read().unwrap()[self.offset..self.offset + self.length].to_vec() - } -} - -struct InMemLinearStoreShared(InMemLinearStore); - -impl Deref for InMemLinearStoreShared { - type Target = dyn LinearStore; - - fn deref(&self) -> &(dyn LinearStore + 'static) { - &self.0 - } -} - -impl DerefMut for InMemLinearStoreShared { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] -mod tests { - use super::*; - - #[test] - fn test_dynamic_mem() { - let mut view = InMemLinearStoreShared(InMemLinearStore::new(2, 0)); - let mem = &mut *view; - mem.write(0, &[1, 2]).unwrap(); - mem.write(0, &[3, 4]).unwrap(); - assert_eq!(mem.get_view(0, 2).unwrap().as_deref(), [3, 4]); - mem.get_shared().write(0, &[5, 6]).unwrap(); - - // capacity is increased - mem.write(5, &[0; 10]).unwrap(); - - // get a view larger than recent growth - assert_eq!(mem.get_view(3, 20).unwrap().as_deref(), [0; 20]); - } -} diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs deleted file mode 100644 index 7db6b4230ce1..000000000000 --- a/firewood/src/shale/mod.rs +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -pub(crate) use disk_address::DiskAddress; -use std::any::type_name; -use std::collections::{HashMap, HashSet}; -use std::fmt::{self, Debug, Formatter}; -use std::mem::ManuallyDrop; -use std::num::NonZeroUsize; -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, RwLock, RwLockWriteGuard}; - -use thiserror::Error; - -use crate::merkle::{LeafNode, Node, Path}; - -pub mod compact; -pub mod disk_address; -pub mod in_mem; - -#[derive(Debug, Error)] -#[non_exhaustive] -pub enum ShaleError { - #[error("obj invalid: {addr:?} obj: {obj_type:?} error: {error:?}")] - InvalidObj { - addr: usize, - obj_type: &'static str, - error: &'static str, - }, - #[error("invalid address length expected: {expected:?} found: {found:?})")] - InvalidAddressLength { expected: u64, found: u64 }, - #[error("invalid node type")] - InvalidNodeType, - #[error("invalid node metadata")] - InvalidNodeMeta, - #[error("failed to create view: offset: {offset:?} size: {size:?}")] - InvalidCacheView { offset: usize, size: u64 }, - #[error("io error: {0}")] - Io(#[from] std::io::Error), - #[error("Write on immutable cache")] - ImmutableWrite, -} - -// TODO: -// this could probably included with ShaleError, -// but keeping it separate for now as Obj/ObjRef might change in the near future -#[derive(Debug, Error)] -#[error("object cannot be written in the store provided")] -pub struct ObjWriteSizeError; - -pub type StoreId = u8; -pub const INVALID_STORE_ID: StoreId = 0xff; - -/// A handle that pins and provides a readable access to a portion of a [LinearStore]. -pub trait LinearStoreView { - type DerefReturn: Deref; - fn as_deref(&self) -> Self::DerefReturn; -} - -pub trait SendSyncDerefMut: DerefMut + Send + Sync {} - -impl SendSyncDerefMut for T {} - -/// In-memory store that offers access to intervals from a linear byte store, which is usually -/// backed by a cached/memory-mapped pool of the accessed intervals from the underlying linear -/// persistent store. Reads may trigger disk reads to bring data into memory, but writes will -/// *only* be visible in memory -- they do not write to disk. -pub trait LinearStore: Debug + Send + Sync { - /// Returns a view containing `length` bytes starting from `offset` from this - /// store. The returned view is pinned. - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>>; - - /// Returns a handle that allows shared access to this store. - fn get_shared(&self) -> Box>; - - /// Write the `change` to the linear store starting at `offset`. The change should - /// be immediately visible to all `LinearStoreView` associated with this linear store. - fn write(&mut self, offset: usize, change: &[u8]) -> Result<(), ShaleError>; - - /// Returns the identifier of this store. - fn id(&self) -> StoreId; - - /// Returns whether or not this store is writable - fn is_writeable(&self) -> bool; -} - -/// A wrapper of `StoredView` to enable writes. The direct construction (by [Obj::from_stored_view] -/// or [StoredView::addr_to_obj]) could be useful for some unsafe access to a low-level item (e.g. -/// headers/metadata at bootstrap) stored at a given [DiskAddress]. -#[derive(Debug)] -pub struct Obj { - value: StoredView, - /// None if the object isn't dirty, otherwise the length of the serialized object. - dirty: Option, -} - -impl Obj { - #[inline(always)] - pub const fn as_addr(&self) -> DiskAddress { - DiskAddress(NonZeroUsize::new(self.value.get_offset())) - } - - /// Modifies the value of this object and marks it as dirty. - #[inline] - pub fn modify(&mut self, modify_func: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { - modify_func(self.value.mut_item_ref()); - - // if `serialized_len` gives overflow, the object will not be written - self.dirty = match self.value.serialized_len() { - Some(len) => Some(len), - None => return Err(ObjWriteSizeError), - }; - - // catch writes that cannot be flushed early during debugging - debug_assert!(self.value.get_mem_store().is_writeable()); - - Ok(()) - } - - #[inline(always)] - pub const fn from_stored_view(value: StoredView) -> Self { - Obj { value, dirty: None } - } - - pub fn flush_dirty(&mut self) { - // faster than calling `self.dirty.take()` on a `None` - if self.dirty.is_none() { - return; - } - - if let Some(new_value_len) = self.dirty.take() { - let mut new_value = vec![0; new_value_len as usize]; - // TODO: log error - #[allow(clippy::unwrap_used)] - self.value.serialize(&mut new_value).unwrap(); - let offset = self.value.get_offset(); - let bx: &mut dyn LinearStore = self.value.get_mut_mem_store(); - bx.write(offset, &new_value).expect("write should succeed"); - } - } -} - -impl Obj { - pub fn into_inner(mut self) -> Node { - let empty_node = LeafNode { - partial_path: Path(Vec::new()), - value: Vec::new(), - }; - - std::mem::replace(&mut self.value.item, Node::from_leaf(empty_node)) - } -} - -impl Drop for Obj { - fn drop(&mut self) { - self.flush_dirty() - } -} - -impl Deref for Obj { - type Target = T; - fn deref(&self) -> &T { - &self.value - } -} - -/// User handle that offers read & write access to the stored items. -#[derive(Debug)] -pub struct ObjRef<'a, T: Storable> { - inner: ManuallyDrop>, - cache: &'a ObjCache, -} - -impl<'a, T: Storable + Debug> ObjRef<'a, T> { - const fn new(inner: Obj, cache: &'a ObjCache) -> Self { - Self { - inner: ManuallyDrop::new(inner), - cache, - } - } - - #[inline] - pub fn write(&mut self, modify: impl FnOnce(&mut T)) -> Result<(), ObjWriteSizeError> { - self.inner.modify(modify)?; - - self.cache.lock().dirty.insert(self.inner.as_addr()); - - Ok(()) - } - - pub fn into_ptr(self) -> DiskAddress { - self.deref().as_addr() - } -} - -impl<'a> ObjRef<'a, Node> { - pub fn into_inner(mut self) -> Node { - // Safety: okay because we'll never be touching "self.inner" again - let b = unsafe { ManuallyDrop::take(&mut self.inner) }; - - // Safety: safe because self.cache: - // - is valid for both reads and writes. - // - is properly aligned - // - is nonnull - // - upholds invariant T - // - does not have a manual drop() implementation - // - is not accessed after drop_in_place and is not Copy - unsafe { std::ptr::drop_in_place(&mut self.cache) }; - - // we have dropped or moved everything out of self, so we can forget it - std::mem::forget(self); - - b.into_inner() - } -} - -impl<'a, T: Storable + Debug> Deref for ObjRef<'a, T> { - type Target = Obj; - fn deref(&self) -> &Obj { - &self.inner - } -} - -impl<'a, T: Storable> Drop for ObjRef<'a, T> { - fn drop(&mut self) { - let ptr = self.inner.as_addr(); - let mut cache = self.cache.lock(); - match cache.pinned.remove(&ptr) { - Some(true) => { - self.inner.dirty = None; - // SAFETY: self.inner will have completed it's destructor - // so it must not be referenced after this line, and it isn't - unsafe { ManuallyDrop::drop(&mut self.inner) }; - } - _ => { - // SAFETY: safe because self.inner is not referenced after this line - let b = unsafe { ManuallyDrop::take(&mut self.inner) }; - cache.cached.put(ptr, b); - } - } - } -} - -/// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient -/// implementation could be directly transmuting to/from a POD struct. But sometimes necessary -/// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access. -pub trait Storable { - fn serialized_len(&self) -> u64; - fn serialize(&self, to: &mut [u8]) -> Result<(), ShaleError>; - fn deserialize(addr: usize, mem: &T) -> Result - where - Self: Sized; -} - -pub fn to_dehydrated(item: &dyn Storable) -> Result, ShaleError> { - let mut buf = vec![0; item.serialized_len() as usize]; - item.serialize(&mut buf)?; - Ok(buf) -} - -/// A stored view of any [Storable] -pub struct StoredView { - /// The item this stores. - item: T, - mem: Box>, - offset: usize, - /// If the serialized length of `item` is greater than this, - /// `serialized_len` will return `None`. - len_limit: u64, -} - -impl Debug for StoredView { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let StoredView { - item, - offset, - len_limit, - mem: _, - } = self; - f.debug_struct("StoredView") - .field("item", item) - .field("offset", offset) - .field("len_limit", len_limit) - .finish() - } -} - -impl Deref for StoredView { - type Target = T; - fn deref(&self) -> &T { - &self.item - } -} - -impl StoredView { - const fn get_offset(&self) -> usize { - self.offset - } - - fn get_mem_store(&self) -> &dyn LinearStore { - &**self.mem - } - - fn get_mut_mem_store(&mut self) -> &mut dyn LinearStore { - &mut **self.mem - } - - /// Returns the serialized length of the item if it's less than the limit, otherwise `None`. - fn serialized_len(&self) -> Option { - let len = self.item.serialized_len(); - if len > self.len_limit { - None - } else { - Some(len) - } - } - - fn serialize(&self, mem_image: &mut [u8]) -> Result<(), ShaleError> { - self.item.serialize(mem_image) - } - - fn mut_item_ref(&mut self) -> &mut T { - &mut self.item - } -} - -impl StoredView { - #[inline(always)] - fn new(offset: usize, len_limit: u64, store: &U) -> Result { - let item = T::deserialize(offset, store)?; - - Ok(Self { - offset, - item, - mem: store.get_shared(), - len_limit, - }) - } - - #[inline(always)] - fn from_hydrated( - offset: usize, - len_limit: u64, - item: T, - store: &dyn LinearStore, - ) -> Result { - Ok(Self { - offset, - item, - mem: store.get_shared(), - len_limit, - }) - } - - #[inline(always)] - pub fn addr_to_obj( - store: &U, - ptr: DiskAddress, - len_limit: u64, - ) -> Result, ShaleError> { - Ok(Obj::from_stored_view(Self::new( - ptr.get(), - len_limit, - store, - )?)) - } - - #[inline(always)] - pub fn item_to_obj( - store: &dyn LinearStore, - addr: usize, - len_limit: u64, - item: T, - ) -> Result, ShaleError> { - Ok(Obj::from_stored_view(Self::from_hydrated( - addr, len_limit, item, store, - )?)) - } -} - -impl StoredView { - fn new_from_slice( - offset: usize, - len_limit: u64, - item: T, - store: &dyn LinearStore, - ) -> Result { - Ok(Self { - offset, - item, - mem: store.get_shared(), - len_limit, - }) - } - - pub fn slice( - s: &Obj, - offset: usize, - length: u64, - item: U, - ) -> Result, ShaleError> { - let addr_ = s.value.get_offset() + offset; - if s.dirty.is_some() { - return Err(ShaleError::InvalidObj { - addr: offset, - obj_type: type_name::(), - error: "dirty write", - }); - } - let r = StoredView::new_from_slice(addr_, length, item, s.value.get_mem_store())?; - Ok(Obj { - value: r, - dirty: None, - }) - } -} - -#[derive(Debug)] -pub struct ObjCacheInner { - cached: lru::LruCache>, - pinned: HashMap, - dirty: HashSet, -} - -/// [ObjRef] pool that is used by [compact::Store] to construct [ObjRef]s. -#[derive(Debug)] -pub struct ObjCache(Arc>>); - -impl ObjCache { - pub fn new(capacity: usize) -> Self { - Self(Arc::new(RwLock::new(ObjCacheInner { - cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")), - pinned: HashMap::new(), - dirty: HashSet::new(), - }))) - } - - fn lock(&self) -> RwLockWriteGuard> { - #[allow(clippy::unwrap_used)] - self.0.write().unwrap() - } - - #[inline(always)] - fn get(&self, ptr: DiskAddress) -> Result>, ShaleError> { - #[allow(clippy::unwrap_used)] - let mut inner = self.0.write().unwrap(); - - let obj_ref = inner.cached.pop(&ptr).map(|r| { - // insert and set to `false` if you can - // When using `get` in parallel, one should not `write` to the same address - inner - .pinned - .entry(ptr) - .and_modify(|is_pinned| *is_pinned = false) - .or_insert(false); - - // if we need to re-enable this code, it has to return from the outer function - // - // return if inner.pinned.insert(ptr, false).is_some() { - // Err(ShaleError::InvalidObj { - // addr: ptr.addr(), - // obj_type: type_name::(), - // error: "address already in use", - // }) - // } else { - // Ok(Some(ObjRef { - // inner: Some(r), - // cache: Self(self.0.clone()), - // _life: PhantomData, - // })) - // }; - - // always return instead of the code above - r - }); - - Ok(obj_ref) - } - - #[inline(always)] - fn put(&self, inner: Obj) -> Obj { - let ptr = inner.as_addr(); - self.lock().pinned.insert(ptr, false); - inner - } - - #[inline(always)] - pub fn pop(&self, ptr: DiskAddress) { - let mut inner = self.lock(); - if let Some(f) = inner.pinned.get_mut(&ptr) { - *f = true - } - if let Some(mut r) = inner.cached.pop(&ptr) { - r.dirty = None - } - inner.dirty.remove(&ptr); - } - - pub fn flush_dirty(&self) -> Option<()> { - let mut inner = self.lock(); - if !inner.pinned.is_empty() { - return None; - } - for ptr in std::mem::take(&mut inner.dirty) { - if let Some(r) = inner.cached.peek_mut(&ptr) { - r.flush_dirty() - } - } - Some(()) - } -} diff --git a/firewood/src/storage/buffer.rs b/firewood/src/storage/buffer.rs deleted file mode 100644 index 23fd753bea35..000000000000 --- a/firewood/src/storage/buffer.rs +++ /dev/null @@ -1,970 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -//! Disk buffer for staging in memory pages and flushing them to disk. -use std::fmt::Debug; -use std::ops::IndexMut; -use std::os::fd::{AsFd, AsRawFd}; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::sync::Arc; -use std::{cell::RefCell, collections::HashMap}; - -use super::{AshRecord, FilePool, Page, StoreDelta, StoreError, WalConfig, PAGE_SIZE_NBIT}; -use crate::shale::StoreId; -use crate::storage::DeltaPage; -use aiofut::{AioBuilder, AioError, AioManager}; -use futures::future::join_all; -use growthring::{ - wal::{RecoverPolicy, WalLoader, WalWriter}, - walerror::WalError, - WalFileImpl, WalStoreImpl, -}; -use tokio::task::block_in_place; -use tokio::{ - sync::{ - mpsc, - oneshot::{self, error::RecvError}, - Mutex, Notify, Semaphore, - }, - task, -}; -use typed_builder::TypedBuilder; - -type BufferWrites = Box<[BufferWrite]>; - -#[derive(Debug)] -pub enum BufferCmd { - /// Initialize the Wal. - InitWal(PathBuf, String), - /// Process a write batch against the underlying store. - WriteBatch(BufferWrites, AshRecord), - /// Get a page from the disk buffer. - GetPage((StoreId, u64), oneshot::Sender>), - CollectAsh(usize, oneshot::Sender>), - /// Register a new store and add the files to a memory mapped pool. - RegCachedStore(StoreId, Arc), - /// Returns false if the - Shutdown, -} - -/// Config for the disk buffer. -#[derive(TypedBuilder, Clone, Debug)] -pub struct DiskBufferConfig { - /// Maximum number of pending pages. - #[builder(default = 65536)] // 256MB total size by default - pub max_pending: usize, - /// Maximum number of concurrent async I/O requests. - #[builder(default = 1024)] - pub max_aio_requests: u32, - /// Maximum number of async I/O responses that it polls for at a time. - #[builder(default = 128)] - pub max_aio_response: u16, - /// Maximum number of async I/O requests per submission. - #[builder(default = 128)] - pub max_aio_submit: usize, - /// Maximum number of concurrent async I/O requests in Wal. - #[builder(default = 256)] - pub wal_max_aio_requests: usize, - /// Maximum buffered Wal records. - #[builder(default = 1024)] - pub wal_max_buffered: usize, - /// Maximum batched Wal records per write. - #[builder(default = 4096)] - pub wal_max_batch: usize, -} - -/// List of pages to write to disk. -#[derive(Debug)] -pub struct BufferWrite { - pub store_id: StoreId, - pub delta: StoreDelta, -} - -#[derive(Debug)] -struct PendingPage { - staging_data: Page, - file_nbit: u64, - notifiers: Notifiers, -} - -#[derive(Debug)] -struct Notifiers { - staging: Vec>, - writing: Vec>, -} - -impl Notifiers { - /// adds one permit to earch semaphore clone and leaves an empty `Vec` for `writing` - fn drain_writing(&mut self) { - std::mem::take(&mut self.writing) - .into_iter() - .for_each(|notifier| notifier.add_permits(1)) - } - - /// takes all staging semaphores and moves them to writing, leaving an `Vec` for `staging` - fn staging_to_writing(&mut self) { - self.writing = std::mem::take(&mut self.staging); - } -} - -/// Responsible for processing [`BufferCmd`]s from the [`DiskBufferRequester`] -/// and managing the persistence of pages. -pub struct DiskBuffer { - inbound: mpsc::UnboundedReceiver, - aiomgr: AioManager, - cfg: DiskBufferConfig, - wal_cfg: WalConfig, -} - -impl DiskBuffer { - /// Create a new aio managed disk buffer. - pub fn new( - inbound: mpsc::UnboundedReceiver, - cfg: &DiskBufferConfig, - wal: &WalConfig, - ) -> Result { - let aiomgr = AioBuilder::default() - .max_events(cfg.max_aio_requests) - .max_nwait(cfg.max_aio_response) - .max_nbatched(cfg.max_aio_submit) - .build() - .map_err(|_| AioError::OtherError)?; - - Ok(Self { - cfg: cfg.clone(), - inbound, - aiomgr, - wal_cfg: wal.clone(), - }) - } - - #[tokio::main(flavor = "current_thread")] - pub async fn run(self) { - let mut inbound = self.inbound; - let aiomgr = Rc::new(self.aiomgr); - let cfg = self.cfg; - let wal_cfg = self.wal_cfg; - - let pending_writes = Rc::new(RefCell::new(HashMap::new())); - let file_pools = Rc::new(RefCell::new(std::array::from_fn(|_| None))); - let local_pool = tokio::task::LocalSet::new(); - - let max = WalQueueMax { - batch: cfg.wal_max_batch, - revisions: wal_cfg.max_revisions, - pending: cfg.max_pending, - }; - - let (wal_in, writes) = mpsc::channel(cfg.wal_max_buffered); - - let mut writes = Some(writes); - let mut wal = None; - - let notifier = Rc::new(Notify::new()); - - local_pool - // everything needs to be moved into this future in order to be properly dropped - .run_until(async move { - loop { - // can't hold the borrowed `pending_writes` across the .await point inside the if-statement - let pending_len = pending_writes.borrow().len(); - - if pending_len >= cfg.max_pending { - notifier.notified().await; - } - - // process the the request - #[allow(clippy::unwrap_used)] - let process_result = process( - pending_writes.clone(), - notifier.clone(), - file_pools.clone(), - aiomgr.clone(), - &mut wal, - &wal_cfg, - inbound.recv().await.unwrap(), - max, - wal_in.clone(), - &mut writes, - ) - .await; - - // stop handling new requests and exit the loop - if !process_result { - break; - } - } - }) - .await; - - // when finished process all requests, wait for any pending-futures to complete - local_pool.await; - } -} - -#[derive(Clone, Copy)] -struct WalQueueMax { - batch: usize, - revisions: u32, - pending: usize, -} - -/// Add an pending pages to aio manager for processing by the local pool. -fn schedule_write( - pending: Rc>>, - fc_notifier: Rc, - file_pools: Rc>; 255]>>, - aiomgr: Rc, - max: WalQueueMax, - page_key: (StoreId, u64), -) { - use std::collections::hash_map::Entry::*; - - let fut = { - let pending = pending.borrow(); - #[allow(clippy::unwrap_used)] - let p = pending.get(&page_key).unwrap(); - let offset = page_key.1 << PAGE_SIZE_NBIT; - let fid = offset >> p.file_nbit; - let fmask = (1 << p.file_nbit) - 1; - #[allow(clippy::unwrap_used, clippy::indexing_slicing)] - let file = file_pools.borrow()[page_key.0 as usize] - .as_ref() - .unwrap() - .get_file(fid) - .unwrap(); - aiomgr.write( - file.as_raw_fd(), - offset & fmask, - p.staging_data.clone(), - None, - ) - }; - - let task = { - async move { - let (res, _) = fut.await; - #[allow(clippy::unwrap_used)] - res.unwrap(); - - let pending_len = pending.borrow().len(); - - let write_again = match pending.borrow_mut().entry(page_key) { - Occupied(mut e) => { - let slot = e.get_mut(); - - slot.notifiers.drain_writing(); - - // if staging is empty, all we need to do is notify any potential waiters - if slot.notifiers.staging.is_empty() { - e.remove(); - - if pending_len < max.pending { - fc_notifier.notify_one(); - } - - false - } else { - // if `staging` is not empty, move all semaphores to `writing` and recurse - // to schedule the new writes. - slot.notifiers.staging_to_writing(); - - true - } - } - _ => unreachable!(), - }; - - if write_again { - schedule_write(pending, fc_notifier, file_pools, aiomgr, max, page_key); - } - } - }; - - task::spawn_local(task); -} - -/// Initialize the Wal subsystem if it does not exists and attempts to replay the Wal if exists. -async fn init_wal( - file_pools: &Rc>; 255]>>, - store: WalStoreImpl, - loader: WalLoader, - max_revisions: u32, - final_path: &Path, -) -> Result>>, WalError> { - let wal = loader - .load( - store, - |raw, _| { - let batch = AshRecord::deserialize(raw); - - for (store_id, ash) in batch.0 { - for (undo, redo) in ash.iter() { - let offset = undo.offset; - let file_pools = file_pools.borrow(); - #[allow(clippy::unwrap_used, clippy::indexing_slicing)] - let file_pool = file_pools[store_id as usize].as_ref().unwrap(); - let file_nbit = file_pool.get_file_nbit(); - let file_mask = (1 << file_nbit) - 1; - let fid = offset >> file_nbit; - - nix::sys::uio::pwrite( - file_pool - .get_file(fid) - .map_err(|e| { - WalError::Other(format!( - "file pool error: {:?} - final path {:?}", - e, final_path - )) - })? - .as_fd(), - &redo.data, - (offset & file_mask) as nix::libc::off_t, - ) - .map_err(|e| { - WalError::Other(format!( - "wal loader error: {:?} - final path {:?}", - e, final_path - )) - })?; - } - } - - Ok(()) - }, - max_revisions, - ) - .await?; - - Ok(Rc::new(Mutex::new(wal))) -} - -async fn run_wal_queue( - max: WalQueueMax, - wal: Rc>>, - pending: Rc>>, - file_pools: Rc>; 255]>>, - mut writes: mpsc::Receiver<(BufferWrites, AshRecord)>, - fc_notifier: Rc, - aiomgr: Rc, -) { - use std::collections::hash_map::Entry::*; - - loop { - let mut bwrites = Vec::new(); - let mut records = Vec::new(); - let wal = wal.clone(); - - if let Some((bw, ac)) = writes.recv().await { - records.push(ac); - bwrites.extend(bw.into_vec()); - } else { - break; - } - - while let Ok((bw, ac)) = writes.try_recv() { - records.push(ac); - bwrites.extend(bw.into_vec()); - - if records.len() >= max.batch { - break; - } - } - - // first write to Wal - #[allow(clippy::unwrap_used)] - let ring_ids = join_all(wal.clone().lock().await.grow(records)) - .await - .into_iter() - .map(|ring| ring.map_err(|_| "Wal Error while writing").unwrap().1) - .collect::>(); - let sem = Rc::new(tokio::sync::Semaphore::new(0)); - let mut npermit = 0; - - for BufferWrite { store_id, delta } in bwrites { - for DeltaPage(page_id, page) in delta.0 { - let page_key = (store_id, page_id); - - let should_write = match pending.borrow_mut().entry(page_key) { - Occupied(mut e) => { - let e = e.get_mut(); - e.staging_data = page; - e.notifiers.staging.push(sem.clone()); - - false - } - Vacant(e) => { - #[allow(clippy::unwrap_used, clippy::indexing_slicing)] - let file_nbit = file_pools.borrow()[page_key.0 as usize] - .as_ref() - .unwrap() - .file_nbit; - - e.insert(PendingPage { - staging_data: page, - file_nbit, - notifiers: { - let semaphore = sem.clone(); - Notifiers { - staging: Vec::new(), - writing: vec![semaphore], - } - }, - }); - - true - } - }; - - if should_write { - schedule_write( - pending.clone(), - fc_notifier.clone(), - file_pools.clone(), - aiomgr.clone(), - max, - page_key, - ); - } - - npermit += 1; - } - } - - let task = async move { - #[allow(clippy::unwrap_used)] - let _ = sem.acquire_many(npermit).await.unwrap(); - - #[allow(clippy::unwrap_used)] - wal.lock() - .await - .peel(ring_ids, max.revisions) - .await - .map_err(|_| "Wal errored while pruning") - .unwrap() - }; - - task::spawn_local(task); - } - - // if this function breaks for any reason, make sure there is no one waiting for staged writes. - fc_notifier.notify_one(); -} - -fn panic_on_intialization_failure_with<'a, T>( - rootpath: &'a Path, - waldir: &'a str, -) -> impl Fn(WalError) -> T + 'a { - move |e| panic!("Initialize Wal in dir {rootpath:?} failed creating {waldir:?}: {e:?}") -} - -#[allow(clippy::too_many_arguments)] -async fn process( - pending: Rc>>, - fc_notifier: Rc, - file_pools: Rc>; 255]>>, - aiomgr: Rc, - wal: &mut Option>>>, - wal_cfg: &WalConfig, - req: BufferCmd, - max: WalQueueMax, - wal_in: mpsc::Sender<(BufferWrites, AshRecord)>, - writes: &mut Option>, -) -> bool { - match req { - BufferCmd::Shutdown => return false, - BufferCmd::InitWal(rootpath, waldir) => { - let final_path = rootpath.join(&waldir); - - let store = WalStoreImpl::new(final_path.clone(), false) - .unwrap_or_else(panic_on_intialization_failure_with(&rootpath, &waldir)); - - let mut loader = WalLoader::new(); - loader - .file_nbit(wal_cfg.file_nbit) - .block_nbit(wal_cfg.block_nbit) - .recover_policy(RecoverPolicy::Strict); - - let initialized_wal = init_wal( - &file_pools, - store, - loader, - wal_cfg.max_revisions, - final_path.as_path(), - ) - .await - .unwrap_or_else(panic_on_intialization_failure_with(&rootpath, &waldir)); - - wal.replace(initialized_wal.clone()); - - #[allow(clippy::unwrap_used)] - let writes = writes.take().unwrap(); - - let task = run_wal_queue( - max, - initialized_wal, - pending, - file_pools.clone(), - writes, - fc_notifier, - aiomgr, - ); - - task::spawn_local(task); - } - - #[allow(clippy::unwrap_used)] - BufferCmd::GetPage(page_key, tx) => tx - .send( - pending - .borrow() - .get(&page_key) - .map(|e| e.staging_data.clone()), - ) - .unwrap(), - BufferCmd::WriteBatch(writes, wal_writes) => { - #[allow(clippy::unwrap_used)] - wal_in.send((writes, wal_writes)).await.unwrap(); - } - BufferCmd::CollectAsh(nrecords, tx) => { - // wait to ensure writes are paused for Wal - #[allow(clippy::unwrap_used)] - let ash = wal - .as_ref() - .unwrap() - .lock() - .await - .read_recent_records(nrecords, &RecoverPolicy::Strict) - .await - .unwrap() - .into_iter() - .map(AshRecord::deserialize) - .collect(); - #[allow(clippy::unwrap_used)] - tx.send(ash).unwrap(); - } - BufferCmd::RegCachedStore(store_id, files) => { - file_pools - .borrow_mut() - .as_mut_slice() - .index_mut(store_id as usize) - .replace(files); - } - } - - true -} - -/// Communicates with the [`DiskBuffer`] over channels. -#[cfg_attr(doc, aquamarine::aquamarine)] -/// ```mermaid -/// graph LR -/// s([Caller]) --> a[[DiskBufferRequester]] -/// r[[DiskBuffer]] --> f([Disk]) -/// subgraph rustc[Thread] -/// r -/// end -/// subgraph rustc[Thread] -/// a -. BufferCmd .-> r -/// end -/// ``` -#[derive(Clone, Debug)] -pub struct DiskBufferRequester { - sender: mpsc::UnboundedSender, -} - -impl DiskBufferRequester { - /// Create a new requester. - pub const fn new(sender: mpsc::UnboundedSender) -> Self { - Self { sender } - } - - /// Get a page from the buffer. - pub fn get_page(&self, store_id: StoreId, page_id: u64) -> Option { - let (resp_tx, resp_rx) = oneshot::channel(); - self.sender - .send(BufferCmd::GetPage((store_id, page_id), resp_tx)) - .map_err(StoreError::Send) - .ok(); - #[allow(clippy::unwrap_used)] - block_in_place(move || resp_rx.blocking_recv().unwrap()) - } - - /// Sends a batch of writes to the buffer. - pub fn write(&self, page_batch: BufferWrites, write_batch: AshRecord) { - self.sender - .send(BufferCmd::WriteBatch(page_batch, write_batch)) - .map_err(StoreError::Send) - .ok(); - } - - pub fn shutdown(&self) { - #[allow(clippy::unwrap_used)] - self.sender.send(BufferCmd::Shutdown).ok().unwrap() - } - - /// Initialize the Wal. - pub fn init_wal(&self, waldir: &str, rootpath: &Path) { - self.sender - .send(BufferCmd::InitWal( - rootpath.to_path_buf(), - waldir.to_string(), - )) - .map_err(StoreError::Send) - .ok(); - } - - /// Collect the last N records from the Wal. - pub fn collect_ash(&self, nrecords: usize) -> Result, StoreError> { - let (resp_tx, resp_rx) = oneshot::channel(); - self.sender - .send(BufferCmd::CollectAsh(nrecords, resp_tx)) - .map_err(StoreError::Send) - .ok(); - block_in_place(|| resp_rx.blocking_recv().map_err(StoreError::Receive)) - } - - /// Register a cached store to the buffer. - pub fn reg_cached_store(&self, store_id: StoreId, files: Arc) { - self.sender - .send(BufferCmd::RegCachedStore(store_id, files)) - .map_err(StoreError::Send) - .ok(); - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used, clippy::indexing_slicing)] -mod tests { - use sha3::Digest; - - use super::*; - use crate::shale::LinearStore; - use crate::{ - file, - storage::{ - Ash, CachedStore, MemStoreR, StoreConfig, StoreRevMut, StoreRevMutDelta, - StoreRevShared, ZeroStore, - }, - }; - - const STATE_STORE_ID: StoreId = 0x0; - const HASH_SIZE: usize = 32; - - fn get_tmp_dir() -> PathBuf { - option_env!("CARGO_TARGET_TMPDIR") - .map(Into::into) - .unwrap_or(std::env::temp_dir()) - .join("firewood") - } - - fn new_cached_store_for_test( - state_path: PathBuf, - disk_requester: DiskBufferRequester, - ) -> Arc { - CachedStore::new( - &StoreConfig::builder() - .ncached_pages(1) - .ncached_files(1) - .store_id(STATE_STORE_ID) - .file_nbit(1) - .rootdir(state_path) - .build(), - disk_requester, - ) - .unwrap() - .into() - } - - #[tokio::test] - #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] - async fn test_buffer_with_undo() { - let temp_dir = get_tmp_dir(); - - let buf_cfg = DiskBufferConfig::builder().build(); - let wal_cfg = WalConfig::builder().build(); - let disk_requester = init_buffer(buf_cfg, wal_cfg); - - // TODO: Run the test in a separate standalone directory for concurrency reasons - let (root_db_path, reset) = - file::open_dir(temp_dir.as_path(), file::Options::Truncate).unwrap(); - - // file descriptor of the state directory - let state_path = file::touch_dir("state", &root_db_path).unwrap(); - assert!(reset); - // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", &root_db_path); - - // create a new state cache which tracks on disk state. - let state_cache = new_cached_store_for_test(state_path, disk_requester.clone()); - - // add an in memory cached store. this will allow us to write to the - // disk buffer then later persist the change to disk. - disk_requester.reg_cached_store(state_cache.id(), state_cache.inner.read().files.clone()); - - // memory mapped store - let mut mut_store = StoreRevMut::new(state_cache); - - let change = b"this is a test"; - - // write to the in memory buffer not to disk - mut_store.write(0, change).unwrap(); - assert_eq!(mut_store.id(), STATE_STORE_ID); - - // mutate the in memory buffer. - let change = b"this is another test"; - - // write to the in memory buffer (ash) not yet to disk - mut_store.write(0, change).unwrap(); - assert_eq!(mut_store.id(), STATE_STORE_ID); - - // wal should have no records. - assert!(disk_requester.collect_ash(1).unwrap().is_empty()); - - // get RO view of the buffer from the beginning. - let view = mut_store.get_view(0, change.len() as u64).unwrap(); - assert_eq!(view.as_deref(), change); - - let (page_batch, write_batch) = create_batches(&mut_store); - - // create a mutation request to the disk buffer by passing the page and write batch. - let d1 = disk_requester.clone(); - let write_thread_handle = std::thread::spawn(move || { - // wal is empty - assert!(d1.collect_ash(1).unwrap().is_empty()); - // page is not yet persisted to disk. - assert!(d1.get_page(STATE_STORE_ID, 0).is_none()); - d1.write(page_batch, write_batch); - }); - // wait for the write to complete. - write_thread_handle.join().unwrap(); - // This is not ACID compliant, write should not return before Wal log - // is written to disk. - - let log_file = temp_dir - .parent() - .unwrap() - .join("sender_api_test_db") - .join("wal") - .join("00000000.log"); - assert!(log_file.exists()); - - // verify - assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); - } - - #[tokio::test] - #[ignore = "ref: https://github.com/ava-labs/firewood/issues/45"] - async fn test_buffer_with_redo() { - let buf_cfg = DiskBufferConfig::builder().build(); - let wal_cfg = WalConfig::builder().build(); - let disk_requester = init_buffer(buf_cfg, wal_cfg); - - // TODO: Run the test in a separate standalone directory for concurrency reasons - let tmp_dir = get_tmp_dir(); - let path = get_file_path(tmp_dir.as_path(), file!(), line!()); - let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); - - // file descriptor of the state directory - let state_path = file::touch_dir("state", &root_db_path).unwrap(); - assert!(reset); - // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", &root_db_path); - - // create a new state cache which tracks on disk state. - let state_cache = new_cached_store_for_test(state_path, disk_requester.clone()); - - // add an in memory cached store. this will allow us to write to the - // disk buffer then later persist the change to disk. - disk_requester.reg_cached_store(state_cache.id(), state_cache.clone_files()); - - // memory mapped store - let mut mut_store = StoreRevMut::new(state_cache.clone()); - - // mutate the in memory buffer. - let data = b"this is another test"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - - // write to the in memory buffer (ash) not yet to disk - mut_store.write(0, &hash).unwrap(); - assert_eq!(mut_store.id(), STATE_STORE_ID); - - // wal should have no records. - assert!(disk_requester.collect_ash(1).unwrap().is_empty()); - - // get RO view of the buffer from the beginning. - let view = mut_store.get_view(0, hash.len() as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - - // Commit the change. Take the delta from cached store, - // then apply changes to the CachedStore. - let (redo_delta, wal) = mut_store.delta(); - state_cache.update(&redo_delta).unwrap(); - - // create a mutation request to the disk buffer by passing the page and write batch. - // wal is empty - assert!(disk_requester.collect_ash(1).unwrap().is_empty()); - // page is not yet persisted to disk. - assert!(disk_requester.get_page(STATE_STORE_ID, 0).is_none()); - disk_requester.write( - Box::new([BufferWrite { - store_id: STATE_STORE_ID, - delta: redo_delta, - }]), - AshRecord([(STATE_STORE_ID, wal)].into()), - ); - - // verify - assert_eq!(disk_requester.collect_ash(1).unwrap().len(), 1); - let ashes = disk_requester.collect_ash(1).unwrap(); - - // replay the redo from the wal - let shared_store = StoreRevShared::from_ash( - Arc::new(ZeroStore::default()), - &ashes[0].0[&STATE_STORE_ID].redo, - ); - let view = shared_store.get_view(0, hash.len() as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_multi_stores() { - let buf_cfg = DiskBufferConfig::builder().build(); - let wal_cfg = WalConfig::builder().build(); - let disk_requester = init_buffer(buf_cfg, wal_cfg); - - let tmp_dir = get_tmp_dir(); - let path = get_file_path(tmp_dir.as_path(), file!(), line!()); - std::fs::create_dir_all(&path).unwrap(); - let (root_db_path, reset) = file::open_dir(path, file::Options::Truncate).unwrap(); - - // file descriptor of the state directory - let state_path = file::touch_dir("state", &root_db_path).unwrap(); - assert!(reset); - // create a new wal directory on top of root_db_fd - disk_requester.init_wal("wal", &root_db_path); - - // create a new state cache which tracks on disk state. - let state_cache: Arc = CachedStore::new( - &StoreConfig::builder() - .ncached_pages(1) - .ncached_files(1) - .store_id(STATE_STORE_ID) - .file_nbit(1) - .rootdir(state_path) - .build(), - disk_requester.clone(), - ) - .unwrap() - .into(); - - // add an in memory cached store. this will allow us to write to the - // disk buffer then later persist the change to disk. - disk_requester.reg_cached_store(state_cache.id(), state_cache.clone_files()); - - // memory mapped store - let mut store = StoreRevMut::new(state_cache.clone()); - - // mutate the in memory buffer. - let data = b"this is a test"; - let hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(data).into(); - block_in_place(|| store.write(0, &hash)).unwrap(); - assert_eq!(store.id(), STATE_STORE_ID); - - let another_data = b"this is another test"; - let another_hash: [u8; HASH_SIZE] = sha3::Keccak256::digest(another_data).into(); - - // mutate the in memory buffer in another StoreRev new from the above. - let mut another_store = StoreRevMut::new_from_other(&store); - block_in_place(|| another_store.write(32, &another_hash)).unwrap(); - assert_eq!(another_store.id(), STATE_STORE_ID); - - // wal should have no records. - assert!(block_in_place(|| disk_requester.collect_ash(1)) - .unwrap() - .is_empty()); - - // get RO view of the buffer from the beginning. Both stores should have the same view. - let view = store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - - let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - - // get RO view of the buffer from the second hash. Only the new store should see the value. - let view = another_store.get_view(32, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), another_hash); - let empty: [u8; HASH_SIZE] = [0; HASH_SIZE]; - let view = store.get_view(32, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), empty); - - // Overwrite the value from the beginning in the new store. Only the new store should see the change. - another_store.write(0, &another_hash).unwrap(); - let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), another_hash); - let view = store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - - // Commit the change. Take the delta from both stores. - let (redo_delta, wal) = store.delta(); - assert_eq!(1, redo_delta.0.len()); - assert_eq!(1, wal.undo.len()); - - let (another_redo_delta, another_wal) = another_store.delta(); - assert_eq!(1, another_redo_delta.0.len()); - assert_eq!(2, another_wal.undo.len()); - - // Verify after the changes been applied to underlying CachedStore, - // the newly created stores should see the previous changes. - state_cache.update(&redo_delta).unwrap(); - let store = StoreRevMut::new(state_cache.clone()); - let view = store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), hash); - - state_cache.update(&another_redo_delta).unwrap(); - let another_store = StoreRevMut::new(state_cache); - let view = another_store.get_view(0, HASH_SIZE as u64).unwrap(); - assert_eq!(view.as_deref(), another_hash); - block_in_place(|| disk_requester.shutdown()); - } - - fn get_file_path(path: &Path, file: &str, line: u32) -> PathBuf { - path.join(format!("{}_{}", file.replace('/', "-"), line)) - } - - fn init_buffer(buf_cfg: DiskBufferConfig, wal_cfg: WalConfig) -> DiskBufferRequester { - let (sender, inbound) = tokio::sync::mpsc::unbounded_channel(); - let disk_requester = DiskBufferRequester::new(sender); - std::thread::spawn(move || { - let disk_buffer = DiskBuffer::new(inbound, &buf_cfg, &wal_cfg).unwrap(); - disk_buffer.run() - }); - disk_requester - } - - fn create_batches(rev_mut: &StoreRevMut) -> (BufferWrites, AshRecord) { - let deltas = std::mem::replace( - &mut *rev_mut.deltas.write(), - StoreRevMutDelta { - pages: HashMap::new(), - plain: Ash::default(), - }, - ); - - // create a list of delta pages from existing in memory data. - let mut pages = Vec::new(); - for (pid, page) in deltas.pages.into_iter() { - pages.push(DeltaPage(pid, page)); - } - pages.sort_by_key(|p| p.0); - - let page_batch = Box::new([BufferWrite { - store_id: STATE_STORE_ID, - delta: StoreDelta(pages), - }]); - - let write_batch = AshRecord([(STATE_STORE_ID, deltas.plain)].into()); - (page_batch, write_batch) - } -} diff --git a/firewood/src/storage/mod.rs b/firewood/src/storage/mod.rs deleted file mode 100644 index a9eb6e3a951b..000000000000 --- a/firewood/src/storage/mod.rs +++ /dev/null @@ -1,967 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -// TODO: try to get rid of the use `RefCell` in this file -use self::buffer::DiskBufferRequester; -use crate::file::File; -use crate::shale::{self, LinearStore, LinearStoreView, SendSyncDerefMut, ShaleError, StoreId}; -use nix::fcntl::{Flock, FlockArg}; -use parking_lot::RwLock; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fmt::{self, Debug}, - num::NonZeroUsize, - ops::{Deref, DerefMut}, - os::fd::AsFd, - path::PathBuf, - sync::Arc, -}; -use thiserror::Error; -use tokio::sync::{mpsc::error::SendError, oneshot::error::RecvError}; -use typed_builder::TypedBuilder; - -pub mod buffer; - -pub(crate) const PAGE_SIZE_NBIT: u64 = 12; -pub(crate) const PAGE_SIZE: u64 = 1 << PAGE_SIZE_NBIT; -pub(crate) const PAGE_MASK: u64 = PAGE_SIZE - 1; - -#[derive(Debug, Error)] -pub enum StoreError { - #[error("system error: `{0}`")] - System(#[from] nix::Error), - #[error("io error: `{0}`")] - Io(Box), - #[error("init error: `{0}`")] - Init(String), - // TODO: more error report from the DiskBuffer - //WriterError, - #[error("error sending data: `{0}`")] - Send(#[from] SendError), - #[error("error receiving data: `{0}")] - Receive(#[from] RecvError), -} - -impl From for StoreError { - fn from(e: std::io::Error) -> Self { - StoreError::Io(Box::new(e)) - } -} - -pub trait MemStoreR: Debug + Send + Sync { - /// Returns a slice of bytes from memory. - fn get_slice(&self, offset: u64, length: u64) -> Option>; - fn id(&self) -> StoreId; -} - -// Page should be boxed as to not take up so much stack-space -type Page = Box<[u8; PAGE_SIZE as usize]>; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct StoreWrite { - offset: u64, - data: Box<[u8]>, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -/// In memory representation of Write-ahead log with `undo` and `redo`. -pub struct Ash { - /// Deltas to undo the changes. - pub undo: Vec, - /// Deltas to replay the changes. - pub redo: Vec, -} - -impl Ash { - fn iter(&self) -> impl Iterator { - self.undo.iter().zip(self.redo.iter()) - } -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct AshRecord(pub HashMap); - -impl growthring::wal::Record for AshRecord { - fn serialize(&self) -> growthring::wal::WalBytes { - #[allow(clippy::unwrap_used)] - bincode::serialize(self).unwrap().into() - } -} - -impl AshRecord { - #[allow(clippy::boxed_local)] - fn deserialize(raw: growthring::wal::WalBytes) -> Self { - #[allow(clippy::unwrap_used)] - bincode::deserialize(raw.as_ref()).unwrap() - } -} - -/// Basic copy-on-write item in the linear storage store for multi-versioning. -pub struct DeltaPage(u64, Page); - -impl DeltaPage { - #[inline(always)] - const fn offset(&self) -> u64 { - self.0 << PAGE_SIZE_NBIT - } - - #[inline(always)] - fn data(&self) -> &[u8] { - self.1.as_ref() - } - - #[inline(always)] - fn data_mut(&mut self) -> &mut [u8] { - self.1.as_mut() - } -} - -#[derive(Default)] -pub struct StoreDelta(Vec); - -impl fmt::Debug for StoreDelta { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "") - } -} - -impl Deref for StoreDelta { - type Target = [DeltaPage]; - fn deref(&self) -> &[DeltaPage] { - &self.0 - } -} - -impl StoreDelta { - pub fn new(src: &dyn MemStoreR, writes: &[StoreWrite]) -> Self { - let mut deltas = Vec::new(); - #[allow(clippy::indexing_slicing)] - let mut widx: Vec<_> = (0..writes.len()) - .filter(|i| writes[*i].data.len() > 0) - .collect(); - if widx.is_empty() { - // the writes are all empty - return Self(deltas); - } - - // sort by the starting point - #[allow(clippy::indexing_slicing)] - widx.sort_by_key(|i| writes[*i].offset); - - let mut witer = widx.into_iter(); - #[allow(clippy::indexing_slicing, clippy::unwrap_used)] - let w0 = &writes[witer.next().unwrap()]; - let mut head = w0.offset >> PAGE_SIZE_NBIT; - let mut tail = (w0.offset + w0.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; - - macro_rules! create_dirty_pages { - ($l: expr, $r: expr) => { - for p in $l..=$r { - let off = p << PAGE_SIZE_NBIT; - deltas.push(DeltaPage( - p, - Box::new(src.get_slice(off, PAGE_SIZE).unwrap().try_into().unwrap()), - )); - } - }; - } - - for i in witer { - #[allow(clippy::indexing_slicing)] - let w = &writes[i]; - let ep = (w.offset + w.data.len() as u64 - 1) >> PAGE_SIZE_NBIT; - let wp = w.offset >> PAGE_SIZE_NBIT; - if wp > tail { - // all following writes won't go back past w.offset, so the previous continuous - // write area is determined - create_dirty_pages!(head, tail); - head = wp; - } - tail = std::cmp::max(tail, ep) - } - create_dirty_pages!(head, tail); - - let psize = PAGE_SIZE as usize; - for w in writes.iter() { - let mut l = 0; - let mut r = deltas.len(); - while r - l > 1 { - let mid = (l + r) >> 1; - #[allow(clippy::indexing_slicing)] - ((*if w.offset < deltas[mid].offset() { - &mut r - } else { - &mut l - }) = mid); - } - #[allow(clippy::indexing_slicing)] - let off = (w.offset - deltas[l].offset()) as usize; - let len = std::cmp::min(psize - off, w.data.len()); - #[allow(clippy::indexing_slicing)] - deltas[l].data_mut()[off..off + len].copy_from_slice(&w.data[..len]); - #[allow(clippy::indexing_slicing)] - let mut data = &w.data[len..]; - while data.len() >= psize { - l += 1; - #[allow(clippy::indexing_slicing)] - deltas[l].data_mut().copy_from_slice(&data[..psize]); - #[allow(clippy::indexing_slicing)] - (data = &data[psize..]); - } - if !data.is_empty() { - l += 1; - #[allow(clippy::indexing_slicing)] - deltas[l].data_mut()[..data.len()].copy_from_slice(data); - } - } - Self(deltas) - } -} - -pub struct StoreRev { - base_store: RwLock>, - delta: StoreDelta, -} - -impl fmt::Debug for StoreRev { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "") - } -} - -impl MemStoreR for StoreRev { - fn get_slice(&self, offset: u64, length: u64) -> Option> { - let base_store = self.base_store.read(); - let mut start = offset; - let end = start + length; - let delta = &self.delta; - let mut l = 0; - let mut r = delta.len(); - // no dirty page, before or after all dirty pages - if r == 0 { - return base_store.get_slice(start, end - start); - } - // otherwise, some dirty pages are covered by the range - while r - l > 1 { - let mid = (l + r) >> 1; - #[allow(clippy::indexing_slicing)] - ((*if start < delta[mid].offset() { - &mut r - } else { - &mut l - }) = mid); - } - #[allow(clippy::indexing_slicing)] - if start >= delta[l].offset() + PAGE_SIZE { - l += 1 - } - #[allow(clippy::indexing_slicing)] - if l >= delta.len() || end < delta[l].offset() { - return base_store.get_slice(start, end - start); - } - let mut data = Vec::new(); - #[allow(clippy::indexing_slicing)] - let p_off = std::cmp::min(end - delta[l].offset(), PAGE_SIZE); - #[allow(clippy::indexing_slicing)] - if start < delta[l].offset() { - #[allow(clippy::indexing_slicing)] - data.extend(base_store.get_slice(start, delta[l].offset() - start)?); - #[allow(clippy::indexing_slicing)] - data.extend(&delta[l].data()[..p_off as usize]); - } else { - #[allow(clippy::indexing_slicing)] - data.extend(&delta[l].data()[(start - delta[l].offset()) as usize..p_off as usize]); - }; - #[allow(clippy::indexing_slicing)] - (start = delta[l].offset() + p_off); - while start < end { - l += 1; - #[allow(clippy::indexing_slicing)] - if l >= delta.len() || end < delta[l].offset() { - data.extend(base_store.get_slice(start, end - start)?); - break; - } - #[allow(clippy::indexing_slicing)] - if delta[l].offset() > start { - #[allow(clippy::indexing_slicing)] - data.extend(base_store.get_slice(start, delta[l].offset() - start)?); - } - #[allow(clippy::indexing_slicing)] - if end < delta[l].offset() + PAGE_SIZE { - #[allow(clippy::indexing_slicing)] - data.extend(&delta[l].data()[..(end - delta[l].offset()) as usize]); - break; - } - #[allow(clippy::indexing_slicing)] - data.extend(delta[l].data()); - #[allow(clippy::indexing_slicing)] - (start = delta[l].offset() + PAGE_SIZE); - } - assert!(data.len() == length as usize); - Some(data) - } - - fn id(&self) -> StoreId { - self.base_store.read().id() - } -} - -#[derive(Clone, Debug)] -pub struct StoreRevShared(Arc); - -impl StoreRevShared { - pub fn from_ash(base_store: Arc, writes: &[StoreWrite]) -> Self { - let delta = StoreDelta::new(base_store.as_ref(), writes); - let base_store = RwLock::new(base_store); - Self(Arc::new(StoreRev { base_store, delta })) - } - - pub fn from_delta(base_store: Arc, delta: StoreDelta) -> Self { - let base_store = RwLock::new(base_store); - Self(Arc::new(StoreRev { base_store, delta })) - } - - pub fn set_base_store(&mut self, base_store: Arc) { - *self.0.base_store.write() = base_store - } - - pub const fn inner(&self) -> &Arc { - &self.0 - } -} - -impl LinearStore for StoreRevShared { - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>> { - let data = self.0.get_slice(offset as u64, length)?; - Some(Box::new(StoreRef { data })) - } - - fn get_shared(&self) -> Box> { - Box::new(StoreShared(self.clone())) - } - - fn write(&mut self, _offset: usize, _change: &[u8]) -> Result<(), ShaleError> { - // StoreRevShared is a read-only view version of CachedStore - // Writes could be induced by lazy hashing and we can just ignore those - Err(ShaleError::ImmutableWrite) - } - - fn id(&self) -> StoreId { - ::id(&self.0) - } - - fn is_writeable(&self) -> bool { - false - } -} - -impl From for StoreRevShared { - fn from(value: StoreRevMut) -> Self { - let mut pages = Vec::new(); - let deltas = std::mem::take(&mut *value.deltas.write()); - for (pid, page) in deltas.pages.into_iter() { - pages.push(DeltaPage(pid, page)); - } - pages.sort_by_key(|p| p.0); - let delta = StoreDelta(pages); - - let rev = Arc::new(StoreRev { - base_store: RwLock::new(value.base_store), - delta, - }); - StoreRevShared(rev) - } -} - -#[derive(Debug)] -struct StoreRef { - data: Vec, -} - -impl Deref for StoreRef { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.data - } -} - -impl LinearStoreView for StoreRef { - type DerefReturn = Vec; - - fn as_deref(&self) -> Self::DerefReturn { - self.deref().to_vec() - } -} - -struct StoreShared(S); - -impl Deref for StoreShared { - type Target = dyn LinearStore; - fn deref(&self) -> &(dyn LinearStore + 'static) { - &self.0 - } -} - -impl DerefMut for StoreShared { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[derive(Debug, Default)] -struct StoreRevMutDelta { - pages: HashMap, - plain: Ash, -} - -#[derive(Clone, Debug)] -/// A mutable revision of the store. The view is constructed by applying the `deltas` to the -/// `base_store`. The `deltas` tracks both `undo` and `redo` to be able to rewind or reapply -/// the changes. `StoreRevMut` supports basing on top of another `StoreRevMut`, by chaining -/// `prev_deltas` (from based `StoreRevMut`) with current `deltas` from itself . In this way, -/// callers can create a new `StoreRevMut` from an existing one without actually committing -/// the mutations to the base store. -pub struct StoreRevMut { - base_store: Arc, - deltas: Arc>, - prev_deltas: Arc>, -} - -impl From for StoreRevMut { - fn from(value: StoreRevShared) -> Self { - StoreRevMut { - base_store: value.0.base_store.read().clone(), - deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), - prev_deltas: Arc::new(RwLock::new(StoreRevMutDelta::default())), - } - } -} - -impl StoreRevMut { - pub fn new(base_store: Arc) -> Self { - Self { - base_store, - deltas: Default::default(), - prev_deltas: Default::default(), - } - } - - pub fn new_from_other(other: &StoreRevMut) -> Self { - Self { - base_store: other.base_store.clone(), - deltas: Default::default(), - prev_deltas: other.deltas.clone(), - } - } - - fn get_page_mut<'a>( - &self, - deltas: &'a mut StoreRevMutDelta, - prev_deltas: &StoreRevMutDelta, - pid: u64, - ) -> &'a mut [u8] { - #[allow(clippy::unwrap_used)] - let page = deltas - .pages - .entry(pid) - .or_insert_with(|| match prev_deltas.pages.get(&pid) { - Some(p) => Box::new(*p.as_ref()), - None => Box::new( - self.base_store - .get_slice(pid << PAGE_SIZE_NBIT, PAGE_SIZE) - .unwrap() - .try_into() - .unwrap(), - ), - }); - - page.as_mut() - } - - #[must_use] - pub fn delta(&self) -> (StoreDelta, Ash) { - let guard = self.deltas.read(); - let mut pages: Vec = guard - .pages - .iter() - .map(|page| DeltaPage(*page.0, page.1.clone())) - .collect(); - pages.sort_by_key(|p| p.0); - (StoreDelta(pages), guard.plain.clone()) - } - pub fn reset_deltas(&self) { - let mut guard = self.deltas.write(); - guard.plain = Ash::default(); - guard.pages = HashMap::new(); - } -} - -impl LinearStore for StoreRevMut { - fn get_view( - &self, - offset: usize, - length: u64, - ) -> Option>>> { - let data = if length == 0 { - Vec::new() - } else { - let end = offset + length as usize - 1; - let s_pid = (offset >> PAGE_SIZE_NBIT) as u64; - let s_off = offset & PAGE_MASK as usize; - let e_pid = (end >> PAGE_SIZE_NBIT) as u64; - let e_off = end & PAGE_MASK as usize; - let deltas = &self.deltas.read().pages; - let prev_deltas = &self.prev_deltas.read().pages; - if s_pid == e_pid { - match deltas.get(&s_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => p[s_off..e_off + 1].to_vec(), - None => match prev_deltas.get(&s_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => p[s_off..e_off + 1].to_vec(), - None => self.base_store.get_slice(offset as u64, length)?, - }, - } - } else { - let mut data = match deltas.get(&s_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => p[s_off..].to_vec(), - None => match prev_deltas.get(&s_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => p[s_off..].to_vec(), - None => self - .base_store - .get_slice(offset as u64, PAGE_SIZE - s_off as u64)?, - }, - }; - for p in s_pid + 1..e_pid { - match deltas.get(&p) { - Some(p) => data.extend(**p), - None => match prev_deltas.get(&p) { - Some(p) => data.extend(**p), - None => data.extend( - &self.base_store.get_slice(p << PAGE_SIZE_NBIT, PAGE_SIZE)?, - ), - }, - }; - } - match deltas.get(&e_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => data.extend(&p[..e_off + 1]), - None => match prev_deltas.get(&e_pid) { - #[allow(clippy::indexing_slicing)] - Some(p) => data.extend(&p[..e_off + 1]), - None => data.extend( - self.base_store - .get_slice(e_pid << PAGE_SIZE_NBIT, e_off as u64 + 1)?, - ), - }, - } - data - } - }; - Some(Box::new(StoreRef { data })) - } - - fn get_shared(&self) -> Box> { - Box::new(StoreShared(self.clone())) - } - - fn write(&mut self, offset: usize, mut change: &[u8]) -> Result<(), ShaleError> { - let length = change.len() as u64; - let end = offset + length as usize - 1; - let s_pid = offset >> PAGE_SIZE_NBIT; - let s_off = offset & PAGE_MASK as usize; - let e_pid = end >> PAGE_SIZE_NBIT; - let e_off = end & PAGE_MASK as usize; - let mut undo: Vec = Vec::new(); - let redo: Box<[u8]> = change.into(); - - if s_pid == e_pid { - let mut deltas = self.deltas.write(); - #[allow(clippy::indexing_slicing)] - let slice = &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), s_pid as u64) - [s_off..e_off + 1]; - undo.extend(&*slice); - slice.copy_from_slice(change) - } else { - let len = PAGE_SIZE as usize - s_off; - - { - let mut deltas = self.deltas.write(); - #[allow(clippy::indexing_slicing)] - let slice = - &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), s_pid as u64) - [s_off..]; - undo.extend(&*slice); - #[allow(clippy::indexing_slicing)] - slice.copy_from_slice(&change[..len]); - } - - #[allow(clippy::indexing_slicing)] - (change = &change[len..]); - - let mut deltas = self.deltas.write(); - for p in s_pid + 1..e_pid { - let slice = self.get_page_mut(&mut deltas, &self.prev_deltas.read(), p as u64); - undo.extend(&*slice); - #[allow(clippy::indexing_slicing)] - slice.copy_from_slice(&change[..PAGE_SIZE as usize]); - #[allow(clippy::indexing_slicing)] - (change = &change[PAGE_SIZE as usize..]); - } - - #[allow(clippy::indexing_slicing)] - let slice = &mut self.get_page_mut(&mut deltas, &self.prev_deltas.read(), e_pid as u64) - [..e_off + 1]; - undo.extend(&*slice); - slice.copy_from_slice(change); - } - - let plain = &mut self.deltas.write().plain; - assert!(undo.len() == redo.len()); - plain.undo.push(StoreWrite { - offset: offset as u64, - data: undo.into(), - }); - plain.redo.push(StoreWrite { - offset: offset as u64, - data: redo, - }); - - Ok(()) - } - - fn id(&self) -> StoreId { - self.base_store.id() - } - - fn is_writeable(&self) -> bool { - true - } -} - -#[derive(Clone, Debug, Default)] -/// A zero-filled in memory store which can serve as a plain base to overlay deltas on top. -pub struct ZeroStore(()); - -impl MemStoreR for ZeroStore { - fn get_slice(&self, _: u64, length: u64) -> Option> { - Some(vec![0; length as usize]) - } - - fn id(&self) -> StoreId { - shale::INVALID_STORE_ID - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used, clippy::indexing_slicing)] -mod test { - use super::*; - #[test] - fn test_from_ash() { - use rand::{rngs::StdRng, Rng, SeedableRng}; - let mut rng = StdRng::seed_from_u64(42); - let min = rng.gen_range(0..2 * PAGE_SIZE); - let max = rng.gen_range(min + PAGE_SIZE..min + 100 * PAGE_SIZE); - for _ in 0..20 { - let n = 20; - let mut canvas = vec![0; (max - min) as usize]; - let mut writes: Vec<_> = Vec::new(); - for _ in 0..n { - let l = rng.gen_range(min..max); - let r = rng.gen_range(l + 1..std::cmp::min(l + 3 * PAGE_SIZE, max)); - let data: Box<[u8]> = (l..r).map(|_| rng.gen()).collect(); - for (idx, byte) in (l..r).zip(data.iter()) { - canvas[(idx - min) as usize] = *byte; - } - println!("[0x{l:x}, 0x{r:x})"); - writes.push(StoreWrite { offset: l, data }); - } - let z = Arc::new(ZeroStore::default()); - let rev = StoreRevShared::from_ash(z, &writes); - println!("{rev:?}"); - assert_eq!( - rev.get_view(min as usize, max - min) - .as_deref() - .unwrap() - .as_deref(), - canvas - ); - for _ in 0..2 * n { - let l = rng.gen_range(min..max); - let r = rng.gen_range(l + 1..max); - assert_eq!( - rev.get_view(l as usize, r - l) - .as_deref() - .unwrap() - .as_deref(), - canvas[(l - min) as usize..(r - min) as usize] - ); - } - } - } -} - -#[derive(TypedBuilder)] -pub struct StoreConfig { - ncached_pages: usize, - ncached_files: usize, - #[builder(default = 22)] // 4MB file by default - file_nbit: u64, - store_id: StoreId, - rootdir: PathBuf, -} - -#[derive(Debug)] -struct CachedStoreInner { - cached_pages: lru::LruCache, - pinned_pages: HashMap, - files: Arc, - disk_requester: DiskBufferRequester, -} - -#[derive(Clone, Debug)] -pub struct CachedStore { - inner: Arc>, - store_id: StoreId, -} - -impl CachedStore { - pub fn new( - cfg: &StoreConfig, - disk_requester: DiskBufferRequester, - ) -> Result> { - let store_id = cfg.store_id; - let files = Arc::new(FilePool::new(cfg)?); - Ok(Self { - inner: Arc::new(RwLock::new(CachedStoreInner { - cached_pages: lru::LruCache::new( - NonZeroUsize::new(cfg.ncached_pages).expect("non-zero cache size"), - ), - pinned_pages: HashMap::new(), - files, - disk_requester, - })), - store_id, - }) - } - - pub fn clone_files(&self) -> Arc { - self.inner.read().files.clone() - } - - /// Apply `delta` to the store and return the StoreDelta that can undo this change. - pub fn update(&self, delta: &StoreDelta) -> Option { - let mut pages = Vec::new(); - for DeltaPage(pid, page) in &delta.0 { - let data = self.inner.write().pin_page(self.store_id, *pid).ok()?; - // save the original data - #[allow(clippy::unwrap_used)] - pages.push(DeltaPage(*pid, Box::new(data.try_into().unwrap()))); - // apply the change - data.copy_from_slice(page.as_ref()); - } - Some(StoreDelta(pages)) - } -} - -impl CachedStoreInner { - fn pin_page( - &mut self, - store_id: StoreId, - pid: u64, - ) -> Result<&'static mut [u8], StoreError> { - let base = match self.pinned_pages.get_mut(&pid) { - Some(e) => { - e.0 += 1; - e.1.as_mut_ptr() - } - None => { - let page = self - .cached_pages - .pop(&pid) - .or_else(|| self.disk_requester.get_page(store_id, pid)); - let mut page = match page { - Some(page) => page, - None => { - let file_nbit = self.files.get_file_nbit(); - let file_size = 1 << file_nbit; - let poff = pid << PAGE_SIZE_NBIT; - let file = self.files.get_file(poff >> file_nbit)?; - let mut page: Page = Page::new([0; PAGE_SIZE as usize]); - - nix::sys::uio::pread( - file.as_fd(), - &mut *page, - (poff & (file_size - 1)) as nix::libc::off_t, - ) - .map_err(StoreError::System)?; - - page - } - }; - - let ptr = page.as_mut_ptr(); - self.pinned_pages.insert(pid, (1, page)); - - ptr - } - }; - - Ok(unsafe { std::slice::from_raw_parts_mut(base, PAGE_SIZE as usize) }) - } - - fn unpin_page(&mut self, pid: u64) { - use std::collections::hash_map::Entry::*; - let page = match self.pinned_pages.entry(pid) { - Occupied(mut e) => { - let cnt = &mut e.get_mut().0; - assert!(*cnt > 0); - *cnt -= 1; - if *cnt == 0 { - e.remove().1 - } else { - return; - } - } - _ => unreachable!(), - }; - self.cached_pages.put(pid, page); - } -} - -struct PageRef { - pid: u64, - data: &'static mut [u8], - store: CachedStore, -} - -impl std::ops::Deref for PageRef { - type Target = [u8]; - fn deref(&self) -> &[u8] { - self.data - } -} - -impl std::ops::DerefMut for PageRef { - fn deref_mut(&mut self) -> &mut [u8] { - self.data - } -} - -impl PageRef { - fn new(pid: u64, store: &CachedStore) -> Option { - Some(Self { - pid, - data: store.inner.write().pin_page(store.store_id, pid).ok()?, - store: store.clone(), - }) - } -} - -impl Drop for PageRef { - fn drop(&mut self) { - self.store.inner.write().unpin_page(self.pid); - } -} - -impl MemStoreR for CachedStore { - fn get_slice(&self, offset: u64, length: u64) -> Option> { - if length == 0 { - return Some(Default::default()); - } - let end = offset + length - 1; - let s_pid = offset >> PAGE_SIZE_NBIT; - let s_off = (offset & PAGE_MASK) as usize; - let e_pid = end >> PAGE_SIZE_NBIT; - let e_off = (end & PAGE_MASK) as usize; - if s_pid == e_pid { - #[allow(clippy::indexing_slicing)] - return PageRef::new(s_pid, self).map(|e| e[s_off..e_off + 1].to_vec()); - } - let mut data: Vec = Vec::new(); - { - #[allow(clippy::indexing_slicing)] - data.extend(&PageRef::new(s_pid, self)?[s_off..]); - for p in s_pid + 1..e_pid { - data.extend(&PageRef::new(p, self)?[..]); - } - #[allow(clippy::indexing_slicing)] - data.extend(&PageRef::new(e_pid, self)?[..e_off + 1]); - } - Some(data) - } - - fn id(&self) -> StoreId { - self.store_id - } -} - -#[derive(Debug)] -pub struct FilePool { - files: parking_lot::Mutex>>, - file_nbit: u64, - rootdir: PathBuf, -} - -impl FilePool { - fn new(cfg: &StoreConfig) -> Result> { - let rootdir = &cfg.rootdir; - let file_nbit = cfg.file_nbit; - let s = Self { - files: parking_lot::Mutex::new(lru::LruCache::new( - NonZeroUsize::new(cfg.ncached_files).expect("non-zero file num"), - )), - file_nbit, - rootdir: rootdir.to_path_buf(), - }; - let f0 = s.get_file(0)?; - if let Some(inner) = Arc::::into_inner(f0) { - // first open of this file, acquire the lock - Flock::lock(inner, FlockArg::LockExclusiveNonblock) - .map_err(|_| StoreError::Init("the store is busy".into()))?; - } - Ok(s) - } - - fn get_file(&self, fid: u64) -> Result, StoreError> { - let mut files = self.files.lock(); - - let file = match files.get(&fid) { - Some(f) => f.clone(), - None => { - let file_size = 1 << self.file_nbit; - let file = Arc::new(File::new(fid, file_size, &self.rootdir)?); - files.put(fid, file.clone()); - file - } - }; - - Ok(file) - } - - const fn get_file_nbit(&self) -> u64 { - self.file_nbit - } -} - -#[derive(TypedBuilder, Clone, Debug)] -pub struct WalConfig { - #[builder(default = 22)] // 4MB Wal logs - pub file_nbit: u64, - #[builder(default = 15)] // 32KB - pub block_nbit: u64, - #[builder(default = 100)] // preserve a rolling window of 100 past commits - pub max_revisions: u32, -} diff --git a/firewood/src/merkle/stream.rs b/firewood/src/stream.rs similarity index 56% rename from firewood/src/merkle/stream.rs rename to firewood/src/stream.rs index f1c7b3190176..40e27bd91e4d 100644 --- a/firewood/src/merkle/stream.rs +++ b/firewood/src/stream.rs @@ -1,23 +1,23 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{BranchNode, Key, Merkle, MerkleError, NodeObjRef, NodeType, Value}; use crate::{ - nibbles::{Nibbles, NibblesIterator}, - shale::{DiskAddress, LinearStore}, + merkle::{Key, MerkleError, Value}, v2::api, }; + use futures::{stream::FusedStream, Stream, StreamExt}; -use std::task::Poll; use std::{cmp::Ordering, iter::once}; +use std::{sync::Arc, task::Poll}; +use storage::{BranchNode, Child, NibblesIterator, Node, PathIterItem, TrieReader}; /// Represents an ongoing iteration over a node and its children. -enum IterationNode<'a> { +enum IterationNode { /// This node has not been returned yet. Unvisited { /// The key (as nibbles) of this node. key: Key, - node: NodeObjRef<'a>, + node: Arc, }, /// This node has been returned. Track which child to visit next. Visited { @@ -25,11 +25,11 @@ enum IterationNode<'a> { key: Key, /// Returns the non-empty children of this node and their positions /// in the node's children array. - children_iter: Box + Send>, + children_iter: Box + Send>, }, } -impl<'a> std::fmt::Debug for IterationNode<'a> { +impl std::fmt::Debug for IterationNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Unvisited { key, node } => f @@ -46,7 +46,7 @@ impl<'a> std::fmt::Debug for IterationNode<'a> { } #[derive(Debug)] -enum NodeStreamState<'a> { +enum NodeStreamState { /// The iterator state is lazily initialized when poll_next is called /// for the first time. The iteration start key is stored here. StartFromKey(Key), @@ -56,24 +56,23 @@ enum NodeStreamState<'a> { /// On each call to poll_next we pop the next element. /// If it's unvisited, we visit it. /// If it's visited, we push its next child onto this stack. - iter_stack: Vec>, + iter_stack: Vec, }, } -impl NodeStreamState<'_> { - fn new(key: Key) -> Self { - Self::StartFromKey(key) - } +#[derive(Debug)] +pub struct MerkleNodeStream<'a, T> { + state: NodeStreamState, + merkle: &'a T, } -#[derive(Debug)] -pub struct MerkleNodeStream<'a, S, T> { - state: NodeStreamState<'a>, - sentinel_addr: DiskAddress, - merkle: &'a Merkle, +impl From for NodeStreamState { + fn from(key: Key) -> Self { + Self::StartFromKey(key) + } } -impl<'a, S: LinearStore, T> FusedStream for MerkleNodeStream<'a, S, T> { +impl<'a, T: TrieReader> FusedStream for MerkleNodeStream<'a, T> { fn is_terminated(&self) -> bool { // The top of `iter_stack` is the next node to return. // If `iter_stack` is empty, there are no more nodes to visit. @@ -81,20 +80,19 @@ impl<'a, S: LinearStore, T> FusedStream for MerkleNodeStream<'a, S, T> { } } -impl<'a, S, T> MerkleNodeStream<'a, S, T> { +impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { /// Returns a new iterator that will iterate over all the nodes in `merkle` /// with keys greater than or equal to `key`. - pub(super) fn new(merkle: &'a Merkle, sentinel_addr: DiskAddress, key: Key) -> Self { + pub(super) fn new(merkle: &'a T, key: Key) -> Self { Self { - state: NodeStreamState::new(key), - sentinel_addr, + state: NodeStreamState::from(key), merkle, } } } -impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { - type Item = Result<(Key, NodeObjRef<'a>), api::Error>; +impl<'a, T: TrieReader> Stream for MerkleNodeStream<'a, T> { + type Item = Result<(Key, Arc), api::Error>; fn poll_next( mut self: std::pin::Pin<&mut Self>, @@ -102,23 +100,20 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { ) -> Poll> { // destructuring is necessary here because we need mutable access to `state` // at the same time as immutable access to `merkle`. - let Self { - state, - sentinel_addr, - merkle, - } = &mut *self; + let Self { state, merkle } = &mut *self; match state { NodeStreamState::StartFromKey(key) => { - self.state = get_iterator_intial_state(merkle, *sentinel_addr, key)?; + self.state = get_iterator_intial_state(*merkle, key)?; self.poll_next(_cx) } NodeStreamState::Iterating { iter_stack } => { while let Some(mut iter_node) = iter_stack.pop() { match iter_node { IterationNode::Unvisited { key, node } => { - match node.inner() { - NodeType::Branch(branch) => { + match &*node { + Node::Leaf(_) => {} + Node::Branch(branch) => { // `node` is a branch node. Visit its children next. iter_stack.push(IterationNode::Visited { key: key.clone(), @@ -127,10 +122,9 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { )), }); } - NodeType::Leaf(_) => {} } - let key = key_from_nibble_iter(key.iter().copied().skip(1)); + let key = key_from_nibble_iter(key.iter().copied()); return Poll::Ready(Some(Ok((key, node)))); } IterationNode::Visited { @@ -138,28 +132,29 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { ref mut children_iter, } => { // We returned `node` already. Visit its next child. - let Some((pos, child_addr)) = children_iter.next() else { + let Some((pos, child)) = children_iter.next() else { // We visited all this node's descendants. Go back to its parent. continue; }; - let child = merkle.get_node(child_addr)?; - - let partial_path = match child.inner() { - NodeType::Branch(branch) => branch.partial_path.iter().copied(), - NodeType::Leaf(leaf) => leaf.partial_path.iter().copied(), + let child = match child { + Child::AddressWithHash(addr, _) => merkle.read_node(addr)?, + Child::Node(node) => Arc::new(node.clone()), }; + let child_partial_path = child.partial_path().iter().copied(); + // The child's key is its parent's key, followed by the child's index, // followed by the child's partial path (if any). let child_key: Key = key .iter() .copied() .chain(once(pos)) - .chain(partial_path) + .chain(child_partial_path) .collect(); // There may be more children of this node to visit. + // Visit it again after visiting its `child`. iter_stack.push(iter_node); iter_stack.push(IterationNode::Unvisited { @@ -177,174 +172,147 @@ impl<'a, S: LinearStore, T> Stream for MerkleNodeStream<'a, S, T> { } /// Returns the initial state for an iterator over the given `merkle` which starts at `key`. -fn get_iterator_intial_state<'a, S: LinearStore, T>( - merkle: &'a Merkle, - sentinel_addr: DiskAddress, +fn get_iterator_intial_state( + merkle: &T, key: &[u8], -) -> Result, api::Error> { - // Invariant: `node`'s key is a prefix of `key`. - let mut node = merkle.get_node(sentinel_addr)?; - - // Invariant: `matched_key_nibbles` is the key of `node` at the start - // of each loop iteration. +) -> Result { + let Some(root) = merkle.root_node() else { + // This merkle is empty. + return Ok(NodeStreamState::Iterating { iter_stack: vec![] }); + }; + let mut node = root; + + // Invariant: `matched_key_nibbles` is the path before `node`'s + // partial path at the start of each loop iteration. let mut matched_key_nibbles = vec![]; - let mut unmatched_key_nibbles = Nibbles::<1>::new(key).into_iter(); + let mut unmatched_key_nibbles = NibblesIterator::new(key); let mut iter_stack: Vec = vec![]; loop { - // `next_unmatched_key_nibble` is the first nibble after `matched_key_nibbles`. - let Some(next_unmatched_key_nibble) = unmatched_key_nibbles.next() else { - // The invariant tells us `node` is a prefix of `key`. - // There is no more `key` left so `node` must be at `key`. - // Visit and return `node` first. - match &node.inner { - NodeType::Branch(_) | NodeType::Leaf(_) => { + // See if `node`'s key is a prefix of `key`. + let partial_path = node.partial_path(); + + let (comparison, new_unmatched_key_nibbles) = + compare_partial_path(partial_path.iter(), unmatched_key_nibbles); + unmatched_key_nibbles = new_unmatched_key_nibbles; + + matched_key_nibbles.extend(partial_path.iter()); + + match comparison { + Ordering::Less => { + // `node` is before `key`. It shouldn't be visited + // and neither should its descendants. + return Ok(NodeStreamState::Iterating { iter_stack }); + } + Ordering::Greater => { + // `node` is after `key`. Visit it first. + iter_stack.push(IterationNode::Unvisited { + key: Box::from(matched_key_nibbles), + node, + }); + return Ok(NodeStreamState::Iterating { iter_stack }); + } + Ordering::Equal => match &*node { + Node::Leaf(_) => { iter_stack.push(IterationNode::Unvisited { - key: Box::from(matched_key_nibbles), + key: matched_key_nibbles.clone().into_boxed_slice(), node, }); + return Ok(NodeStreamState::Iterating { iter_stack }); } - } - - return Ok(NodeStreamState::Iterating { iter_stack }); - }; + Node::Branch(branch) => { + let Some(next_unmatched_key_nibble) = unmatched_key_nibbles.next() else { + // There is no more key to traverse. + iter_stack.push(IterationNode::Unvisited { + key: matched_key_nibbles.clone().into_boxed_slice(), + node, + }); - match &node.inner { - NodeType::Branch(branch) => { - // The next nibble in `key` is `next_unmatched_key_nibble`, - // so all children of `node` with a position > `next_unmatched_key_nibble` - // should be visited since they are after `key`. - iter_stack.push(IterationNode::Visited { - key: matched_key_nibbles.iter().copied().collect(), - children_iter: Box::new( - as_enumerated_children_iter(branch) - .filter(move |(pos, _)| *pos > next_unmatched_key_nibble), - ), - }); + return Ok(NodeStreamState::Iterating { iter_stack }); + }; - // Figure out if the child at `next_unmatched_key_nibble` is a prefix of `key`. - // (i.e. if we should run this loop body again) - #[allow(clippy::indexing_slicing)] - let Some(child_addr) = branch.children[next_unmatched_key_nibble as usize] else { // There is no child at `next_unmatched_key_nibble`. // We'll visit `node`'s first child at index > `next_unmatched_key_nibble` // first (if it exists). - return Ok(NodeStreamState::Iterating { iter_stack }); - }; - - matched_key_nibbles.push(next_unmatched_key_nibble); - - let child = merkle.get_node(child_addr)?; - - let partial_key = match child.inner() { - NodeType::Branch(branch) => &branch.partial_path, - NodeType::Leaf(leaf) => &leaf.partial_path, - }; - - let (comparison, new_unmatched_key_nibbles) = - compare_partial_path(partial_key.iter(), unmatched_key_nibbles); - unmatched_key_nibbles = new_unmatched_key_nibbles; + iter_stack.push(IterationNode::Visited { + key: matched_key_nibbles.clone().into_boxed_slice(), + children_iter: Box::new( + as_enumerated_children_iter(branch) + .filter(move |(pos, _)| *pos > next_unmatched_key_nibble), + ), + }); - match comparison { - Ordering::Less => { - // `child` is before `key`. - return Ok(NodeStreamState::Iterating { iter_stack }); - } - Ordering::Equal => { - // `child` is a prefix of `key`. - matched_key_nibbles.extend(partial_key.iter().copied()); - node = child; - } - Ordering::Greater => { - // `child` is after `key`. - let key = matched_key_nibbles - .iter() - .chain(partial_key.iter()) - .copied() - .collect(); - iter_stack.push(IterationNode::Unvisited { key, node: child }); + #[allow(clippy::indexing_slicing)] + let child = &branch.children[next_unmatched_key_nibble as usize]; + node = match child { + None => return Ok(NodeStreamState::Iterating { iter_stack }), + Some(Child::AddressWithHash(addr, _)) => merkle.read_node(*addr)?, + Some(Child::Node(node)) => Arc::new((*node).clone()), // TODO can we avoid cloning this? + }; - return Ok(NodeStreamState::Iterating { iter_stack }); - } - } - } - NodeType::Leaf(leaf) => { - if compare_partial_path(leaf.partial_path.iter(), unmatched_key_nibbles).0 - == Ordering::Greater - { - // `child` is after `key`. - let key = matched_key_nibbles - .iter() - .chain(leaf.partial_path.iter()) - .copied() - .collect(); - iter_stack.push(IterationNode::Unvisited { key, node }); + matched_key_nibbles.push(next_unmatched_key_nibble); } - return Ok(NodeStreamState::Iterating { iter_stack }); - } - }; + }, + } } } #[derive(Debug)] -enum MerkleKeyValueStreamState<'a, S, T> { +enum MerkleKeyValueStreamState<'a, T> { /// The iterator state is lazily initialized when poll_next is called /// for the first time. The iteration start key is stored here. - Uninitialized(Key), + _Uninitialized(Key), /// The iterator works by iterating over the nodes in the merkle trie /// and returning the key-value pairs for nodes that have values. - Initialized { - node_iter: MerkleNodeStream<'a, S, T>, - }, + Initialized { node_iter: MerkleNodeStream<'a, T> }, } -impl<'a, S, T> MerkleKeyValueStreamState<'a, S, T> { - /// Returns a new iterator that will iterate over all the key-value pairs in `merkle`. - fn new() -> Self { - Self::Uninitialized(Box::new([])) +impl<'a, T> From for MerkleKeyValueStreamState<'a, T> { + fn from(key: Key) -> Self { + Self::_Uninitialized(key) } +} - /// Returns a new iterator that will iterate over all the key-value pairs in `merkle` - /// with keys greater than or equal to `key`. - fn with_key(key: Key) -> Self { - Self::Uninitialized(key) +impl<'a, T: TrieReader> MerkleKeyValueStreamState<'a, T> { + /// Returns a new iterator that will iterate over all the key-value pairs in `merkle`. + fn _new() -> Self { + Self::_Uninitialized(Box::new([])) } } #[derive(Debug)] -pub struct MerkleKeyValueStream<'a, S, T> { - state: MerkleKeyValueStreamState<'a, S, T>, - sentinel_addr: DiskAddress, - merkle: &'a Merkle, +pub struct MerkleKeyValueStream<'a, T> { + state: MerkleKeyValueStreamState<'a, T>, + merkle: &'a T, } -impl<'a, S: LinearStore, T> FusedStream for MerkleKeyValueStream<'a, S, T> { - fn is_terminated(&self) -> bool { - matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) - } -} - -impl<'a, S, T> MerkleKeyValueStream<'a, S, T> { - pub(super) fn new(merkle: &'a Merkle, sentinel_addr: DiskAddress) -> Self { +impl<'a, T: TrieReader> From<&'a T> for MerkleKeyValueStream<'a, T> { + fn from(merkle: &'a T) -> Self { Self { - state: MerkleKeyValueStreamState::new(), - sentinel_addr, + state: MerkleKeyValueStreamState::_new(), merkle, } } +} + +impl<'a, T: TrieReader> FusedStream for MerkleKeyValueStream<'a, T> { + fn is_terminated(&self) -> bool { + matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) + } +} - pub(super) fn from_key(merkle: &'a Merkle, sentinel_addr: DiskAddress, key: Key) -> Self { +impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { + pub(super) fn _from_key(merkle: &'a T, key: Key) -> Self { Self { - state: MerkleKeyValueStreamState::with_key(key), - sentinel_addr, + state: MerkleKeyValueStreamState::from(key), merkle, } } } -impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { +impl<'a, T: TrieReader> Stream for MerkleKeyValueStream<'a, T> { type Item = Result<(Key, Value), api::Error>; fn poll_next( @@ -353,23 +321,19 @@ impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { ) -> Poll> { // destructuring is necessary here because we need mutable access to `key_state` // at the same time as immutable access to `merkle` - let Self { - state, - sentinel_addr, - merkle, - } = &mut *self; + let Self { state, merkle } = &mut *self; match state { - MerkleKeyValueStreamState::Uninitialized(key) => { - let iter = MerkleNodeStream::new(merkle, *sentinel_addr, key.clone()); + MerkleKeyValueStreamState::_Uninitialized(key) => { + let iter = MerkleNodeStream::new(*merkle, key.clone()); self.state = MerkleKeyValueStreamState::Initialized { node_iter: iter }; self.poll_next(_cx) } MerkleKeyValueStreamState::Initialized { node_iter: iter } => { match iter.poll_next_unpin(_cx) { Poll::Ready(node) => match node { - Some(Ok((key, node))) => match node.inner() { - NodeType::Branch(branch) => { + Some(Ok((key, node))) => match &*node { + Node::Branch(branch) => { let Some(value) = branch.value.as_ref() else { // This node doesn't have a value to return. // Continue to the next node. @@ -379,7 +343,7 @@ impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { let value = value.to_vec(); Poll::Ready(Some(Ok((key, value)))) } - NodeType::Leaf(leaf) => { + Node::Leaf(leaf) => { let value = leaf.value.to_vec(); Poll::Ready(Some(Ok((key, value)))) } @@ -394,6 +358,7 @@ impl<'a, S: LinearStore, T> Stream for MerkleKeyValueStream<'a, S, T> { } } +#[derive(Debug)] enum PathIteratorState<'a> { Iterating { /// The key, as nibbles, of the node at `address`, without the @@ -403,60 +368,44 @@ enum PathIteratorState<'a> { /// Note the node at `address` may not have a key which is a /// prefix of the key we're traversing to. matched_key: Vec, - unmatched_key: NibblesIterator<'a, 0>, - address: DiskAddress, + unmatched_key: NibblesIterator<'a>, + node: Arc, }, Exhausted, } /// Iterates over all nodes on the path to a given key starting from the root. /// All nodes are branch nodes except possibly the last, which may be a leaf. -/// If the key is in the trie, the last node is the one at the given key. -/// Otherwise, the last node proves the non-existence of the key. -/// Specifically, if during the traversal, we encounter: -/// * A branch node with no child at the index of the next nibble in the key, -/// then the branch node proves the non-existence of the key. -/// * A node (either branch or leaf) whose partial path doesn't match the -/// remaining unmatched key, the node proves the non-existence of the key. -/// Note that thi means that the last node's key isn't necessarily a prefix of -/// the key we're traversing to. -pub struct PathIterator<'a, 'b, S, T> { +/// All returned nodes have keys which are a prefix of the given key. +/// If the given key is in the trie, the last node is at that key. +#[derive(Debug)] +pub struct PathIterator<'a, 'b, T> { state: PathIteratorState<'b>, - merkle: &'a Merkle, + merkle: &'a T, } -impl<'a, 'b, S: LinearStore, T> PathIterator<'a, 'b, S, T> { - pub(super) fn new( - merkle: &'a Merkle, - sentinel_node: NodeObjRef<'a>, - key: &'b [u8], - ) -> Self { - let sentinel_addr = match sentinel_node.inner() { - NodeType::Branch(branch) => match branch.children[0] { - Some(sentinel_addr) => sentinel_addr, - None => { - return Self { - state: PathIteratorState::Exhausted, - merkle, - } - } - }, - _ => unreachable!("sentinel node is not a branch"), +impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { + pub(super) fn new(merkle: &'a T, key: &'b [u8]) -> Result { + let Some(root) = merkle.root_node() else { + return Ok(Self { + state: PathIteratorState::Exhausted, + merkle, + }); }; - Self { + Ok(Self { merkle, state: PathIteratorState::Iterating { matched_key: vec![], - unmatched_key: Nibbles::new(key).into_iter(), - address: sentinel_addr, + unmatched_key: NibblesIterator::new(key), + node: root, }, - } + }) } } -impl<'a, 'b, S: LinearStore, T> Iterator for PathIterator<'a, 'b, S, T> { - type Item = Result<(Key, NodeObjRef<'a>), MerkleError>; +impl<'a, 'b, T: TrieReader> Iterator for PathIterator<'a, 'b, T> { + type Item = Result; fn next(&mut self) -> Option { // destructuring is necessary here because we need mutable access to `state` @@ -468,56 +417,99 @@ impl<'a, 'b, S: LinearStore, T> Iterator for PathIterator<'a, 'b, S, T> { PathIteratorState::Iterating { matched_key, unmatched_key, - address, + node, } => { - let node = match merkle.get_node(*address) { - Ok(node) => node, - Err(e) => return Some(Err(e)), - }; - - let partial_path = match node.inner() { - NodeType::Branch(branch) => &branch.partial_path, - NodeType::Leaf(leaf) => &leaf.partial_path, + let partial_path = match &**node { + Node::Branch(branch) => &branch.partial_path, + Node::Leaf(leaf) => &leaf.partial_path, }; let (comparison, unmatched_key) = compare_partial_path(partial_path.iter(), unmatched_key); - matched_key.extend(partial_path.iter()); - let node_key = matched_key.clone().into_boxed_slice(); - match comparison { Ordering::Less | Ordering::Greater => { self.state = PathIteratorState::Exhausted; - Some(Ok((node_key, node))) + None } - Ordering::Equal => match node.inner() { - NodeType::Leaf(_) => { - self.state = PathIteratorState::Exhausted; - Some(Ok((node_key, node))) - } - NodeType::Branch(branch) => { - let Some(next_unmatched_key_nibble) = unmatched_key.next() else { - // There's no more key to match. We're done. - self.state = PathIteratorState::Exhausted; - return Some(Ok((node_key, node))); - }; + Ordering::Equal => { + matched_key.extend(partial_path.iter()); + let node_key = matched_key.clone().into_boxed_slice(); - #[allow(clippy::indexing_slicing)] - let Some(child) = branch.children[next_unmatched_key_nibble as usize] else { - // There's no child at the index of the next nibble in the key. - // The node we're traversing to isn't in the trie. + match &**node { + Node::Leaf(_) => { + // We're at a leaf so we're done. + let node = node.clone(); self.state = PathIteratorState::Exhausted; - return Some(Ok((node_key, node))); - }; - - matched_key.push(next_unmatched_key_nibble); - - *address = child; + Some(Ok(PathIterItem { + key_nibbles: node_key.clone(), + node, + next_nibble: None, + })) + } + Node::Branch(branch) => { + // We're at a branch whose key is a prefix of `key`. + // Find its child (if any) that matches the next nibble in the key. + let Some(next_unmatched_key_nibble) = unmatched_key.next() else { + // We're at the node at `key` so we're done. + let node = node.clone(); + self.state = PathIteratorState::Exhausted; + return Some(Ok(PathIterItem { + key_nibbles: node_key.clone(), + node, + next_nibble: None, + })); + }; - Some(Ok((node_key, node))) + #[allow(clippy::indexing_slicing)] + let child = &branch.children[next_unmatched_key_nibble as usize]; + match child { + None => { + // There's no child at the index of the next nibble in the key. + // There's no node at `key` in this trie so we're done. + let node = node.clone(); + self.state = PathIteratorState::Exhausted; + Some(Ok(PathIterItem { + key_nibbles: node_key.clone(), + node, + next_nibble: None, + })) + } + Some(Child::AddressWithHash(child_addr, _)) => { + let child = match merkle.read_node(*child_addr) { + Ok(child) => child, + Err(e) => return Some(Err(e.into())), + }; + + let node_key = matched_key.clone().into_boxed_slice(); + matched_key.push(next_unmatched_key_nibble); + + let ret = node.clone(); + *node = child; + + Some(Ok(PathIterItem { + key_nibbles: node_key, + node: ret, + next_nibble: Some(next_unmatched_key_nibble), + })) + } + Some(Child::Node(child)) => { + let node_key = matched_key.clone().into_boxed_slice(); + matched_key.push(next_unmatched_key_nibble); + + let ret = node.clone(); + *node = Arc::new(child.clone()); + + Some(Ok(PathIterItem { + key_nibbles: node_key, + node: ret, + next_nibble: Some(next_unmatched_key_nibble), + })) + } + } + } } - }, + } } } } @@ -530,6 +522,7 @@ impl<'a, 'b, S: LinearStore, T> Iterator for PathIterator<'a, 'b, S, T> { /// * [Ordering::Less] if the node is before the key. /// * [Ordering::Equal] if the node is a prefix of the key. /// * [Ordering::Greater] if the node is after the key. +/// /// The second returned element is the unmatched portion of the key after the /// partial path has been matched. fn compare_partial_path<'a, I1, I2>( @@ -555,14 +548,15 @@ where (Ordering::Equal, unmatched_key_nibbles_iter) } -/// Returns an iterator that returns (`pos`,`child_addr`) for each non-empty child of `branch`, +/// Returns an iterator that returns (`pos`,`child`) for each non-empty child of `branch`, /// where `pos` is the position of the child in `branch`'s children array. -fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator { +fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator { branch .children + .clone() .into_iter() .enumerate() - .filter_map(|(pos, child_addr)| child_addr.map(|child_addr| (pos as u8, child_addr))) + .filter_map(|(pos, child)| child.map(|child| (pos as u8, child))) } fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { @@ -575,29 +569,21 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { data.into_boxed_slice() } -#[cfg(test)] -use super::tests::create_test_merkle; - #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use crate::{merkle::Bincode, shale::in_mem::InMemLinearStore}; + use storage::{MemStore, MutableProposal, NodeStore}; + + use crate::merkle::Merkle; use super::*; use test_case::test_case; - impl Merkle { - pub(crate) fn node_iter(&self, sentinel_addr: DiskAddress) -> MerkleNodeStream<'_, S, T> { - MerkleNodeStream::new(self, sentinel_addr, Box::new([])) - } - - pub(crate) fn node_iter_from( - &self, - sentinel_addr: DiskAddress, - key: Key, - ) -> MerkleNodeStream<'_, S, T> { - MerkleNodeStream::new(self, sentinel_addr, key) - } + pub(super) fn create_test_merkle() -> Merkle> { + let memstore = MemStore::new(vec![]); + let memstore = Arc::new(memstore); + let nodestore = NodeStore::new_empty_proposal(memstore); + Merkle::from(nodestore) } #[test_case(&[]; "empty key")] @@ -605,121 +591,123 @@ mod tests { #[tokio::test] async fn path_iterate_empty_merkle_empty_key(key: &[u8]) { let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); - let mut stream = merkle.path_iter(sentinel_node, key); + let mut stream = merkle.path_iter(key).unwrap(); assert!(stream.next().is_none()); } - #[test_case(&[]; "empty key")] - #[test_case(&[13]; "prefix of singleton key")] - #[test_case(&[13, 37]; "match singleton key")] - #[test_case(&[13, 37,1]; "suffix of singleton key")] - #[test_case(&[255]; "no key nibbles match singleton key")] + #[test_case(&[],false; "empty key")] + #[test_case(&[0xBE,0xE0],false; "prefix of singleton key")] + #[test_case(&[0xBE, 0xEF],true; "match singleton key")] + #[test_case(&[0xBE, 0xEF,0x10],true; "suffix of singleton key")] + #[test_case(&[0xF0],false; "no key nibbles match singleton key")] #[tokio::test] - async fn path_iterate_singleton_merkle(key: &[u8]) { + async fn path_iterate_singleton_merkle(key: &[u8], should_yield_elt: bool) { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle - .insert(vec![0x13, 0x37], vec![0x42], sentinel_addr) - .unwrap(); + merkle.insert(&[0xBE, 0xEF], Box::new([0x42])).unwrap(); - let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); - - let mut stream = merkle.path_iter(sentinel_node, key); - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let mut stream = merkle.path_iter(key).unwrap(); + let node = match stream.next() { + Some(Ok(item)) => item, Some(Err(e)) => panic!("{:?}", e), - None => panic!("unexpected end of iterator"), + None => { + assert!(!should_yield_elt); + return; + } }; - assert_eq!(key, vec![0x01, 0x03, 0x03, 0x07].into_boxed_slice()); - assert_eq!(node.inner().as_leaf().unwrap().value, vec![0x42]); + assert!(should_yield_elt); + assert_eq!( + node.key_nibbles, + vec![0x0B, 0x0E, 0x0E, 0x0F].into_boxed_slice() + ); + assert_eq!(node.node.as_leaf().unwrap().value, Box::from([0x42])); + assert_eq!(node.next_nibble, None); assert!(stream.next().is_none()); } #[test_case(&[0x00, 0x00, 0x00, 0xFF]; "leaf key")] - #[test_case(&[0x00, 0x00, 0x00, 0xF3]; "leaf sibling key")] - #[test_case(&[0x00, 0x00, 0x00, 0xFF, 0x01]; "past leaf key")] + #[test_case(&[0x00, 0x00, 0x00, 0xFF, 0x01]; "leaf key suffix")] #[tokio::test] async fn path_iterate_non_singleton_merkle_seek_leaf(key: &[u8]) { - let (merkle, sentinel_addr) = created_populated_merkle(); - - let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); + let merkle = created_populated_merkle(); - let mut stream = merkle.path_iter(sentinel_node, key); + let mut stream = merkle.path_iter(key).unwrap(); - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let node = match stream.next() { + Some(Ok(node)) => node, Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; - assert_eq!(key, vec![0x00, 0x00].into_boxed_slice()); - assert!(node.inner().as_branch().unwrap().value.is_none()); + assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + assert_eq!(node.next_nibble, Some(0)); + assert!(node.node.as_branch().unwrap().value.is_none()); - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let node = match stream.next() { + Some(Ok(node)) => node, Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; assert_eq!( - key, + node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() ); + assert_eq!(node.next_nibble, Some(0x0F)); assert_eq!( - node.inner().as_branch().unwrap().value, - Some(vec![0x00, 0x00, 0x00]), + node.node.as_branch().unwrap().value, + Some(vec![0x00, 0x00, 0x00].into_boxed_slice()), ); - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let node = match stream.next() { + Some(Ok(node)) => node, Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; assert_eq!( - key, + node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice() ); + assert_eq!(node.next_nibble, None); assert_eq!( - node.inner().as_leaf().unwrap().value, - vec![0x00, 0x00, 0x00, 0x0FF], + node.node.as_leaf().unwrap().value, + Box::from([0x00, 0x00, 0x00, 0x0FF]) ); assert!(stream.next().is_none()); } + #[test_case(&[0x00, 0x00, 0x00]; "branch key")] + #[test_case(&[0x00, 0x00, 0x00, 0x10]; "branch key suffix (but not a leaf key)")] #[tokio::test] - async fn path_iterate_non_singleton_merkle_seek_branch() { - let (merkle, sentinel_addr) = created_populated_merkle(); + async fn path_iterate_non_singleton_merkle_seek_branch(key: &[u8]) { + let merkle = created_populated_merkle(); - let key = &[0x00, 0x00, 0x00]; + let mut stream = merkle.path_iter(key).unwrap(); - let sentinel_node = merkle.get_node(sentinel_addr).unwrap(); - let mut stream = merkle.path_iter(sentinel_node, key); - - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let node = match stream.next() { + Some(Ok(node)) => node, Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; - assert_eq!(key, vec![0x00, 0x00].into_boxed_slice()); - assert!(node.inner().as_branch().unwrap().value.is_none()); + assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + assert!(node.node.as_branch().unwrap().value.is_none()); + assert_eq!(node.next_nibble, Some(0)); - let (key, node) = match stream.next() { - Some(Ok((key, node))) => (key, node), + let node = match stream.next() { + Some(Ok(node)) => node, Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; assert_eq!( - key, + node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() ); assert_eq!( - node.inner().as_branch().unwrap().value, - Some(vec![0x00, 0x00, 0x00]), + node.node.as_branch().unwrap().value, + Some(vec![0x00, 0x00, 0x00].into_boxed_slice()), ); + assert_eq!(node.next_nibble, None); assert!(stream.next().is_none()); } @@ -727,17 +715,14 @@ mod tests { #[tokio::test] async fn key_value_iterate_empty() { let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let stream = - merkle.key_value_iter_from_key(sentinel_addr, b"x".to_vec().into_boxed_slice()); + let stream = merkle._key_value_iter_from_key(b"x".to_vec().into_boxed_slice()); check_stream_is_done(stream).await; } #[tokio::test] async fn node_iterate_empty() { let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let stream = merkle.node_iter(sentinel_addr); + let stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); check_stream_is_done(stream).await; } @@ -745,114 +730,104 @@ mod tests { async fn node_iterate_root_only() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - - merkle - .insert(vec![0x00], vec![0x00], sentinel_addr) - .unwrap(); + merkle.insert(&[0x00], Box::new([0x00])).unwrap(); - let mut stream = merkle.node_iter(sentinel_addr); + let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); - assert_eq!(node.inner().as_leaf().unwrap().value.to_vec(), vec![0x00]); + assert_eq!(node.as_leaf().unwrap().value.to_vec(), vec![0x00]); check_stream_is_done(stream).await; } - /// Returns a new [Merkle] with the following structure: - /// sentinel - /// | 0 + /// Returns a new [Merkle] with the following key-value pairs: + /// Note each hex symbol in the keys below is a nibble (not two nibbles). + /// Each hex symbol in the values below is a byte. + /// 000000 --> 000000 + /// 00000001 -->00000001 + /// 000000FF --> 000000FF + /// 00D0D0 --> 00D0D0 + /// 00FF --> 00FF + /// structure: /// 00 <-- branch with no value /// 0/ D| \F - /// 00 0D0 F <-- leaf with no partial path + /// 000 0D0 F <-- leaf with no partial path /// 0/ \F /// 1 F /// - /// Note the 0000 branch has no value and the F0F0 /// The number next to each branch is the position of the child in the branch's children array. - fn created_populated_merkle() -> (Merkle, DiskAddress) { + fn created_populated_merkle() -> Merkle> { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); merkle - .insert( - vec![0x00, 0x00, 0x00], - vec![0x00, 0x00, 0x00], - sentinel_addr, - ) + .insert(&[0x00, 0x00, 0x00], Box::new([0x00, 0x00, 0x00])) .unwrap(); merkle .insert( - vec![0x00, 0x00, 0x00, 0x01], - vec![0x00, 0x00, 0x00, 0x01], - sentinel_addr, + &[0x00, 0x00, 0x00, 0x01], + Box::new([0x00, 0x00, 0x00, 0x01]), ) .unwrap(); merkle .insert( - vec![0x00, 0x00, 0x00, 0xFF], - vec![0x00, 0x00, 0x00, 0xFF], - sentinel_addr, + &[0x00, 0x00, 0x00, 0xFF], + Box::new([0x00, 0x00, 0x00, 0xFF]), ) .unwrap(); merkle - .insert( - vec![0x00, 0xD0, 0xD0], - vec![0x00, 0xD0, 0xD0], - sentinel_addr, - ) + .insert(&[0x00, 0xD0, 0xD0], Box::new([0x00, 0xD0, 0xD0])) .unwrap(); merkle - .insert(vec![0x00, 0xFF], vec![0x00, 0xFF], sentinel_addr) + .insert(&[0x00, 0xFF], Box::new([0x00, 0xFF])) .unwrap(); - (merkle, sentinel_addr) + merkle } #[tokio::test] async fn node_iterator_no_start_key() { - let (merkle, sentinel_addr) = created_populated_merkle(); + let merkle = created_populated_merkle(); - let mut stream = merkle.node_iter(sentinel_addr); + let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); // Covers case of branch with no value let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); - let node = node.inner().as_branch().unwrap(); + let node = node.as_branch().unwrap(); assert!(node.value.is_none()); assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00]); // Covers case of branch with value let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); - let node = node.inner().as_branch().unwrap(); + let node = node.as_branch().unwrap(); assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00, 0x00]); // Covers case of leaf with partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); - let node = node.inner().as_leaf().unwrap(); + let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); assert_eq!(node.partial_path.to_vec(), vec![0x01]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); - let node = node.inner().as_leaf().unwrap(); + let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); assert_eq!(node.partial_path.to_vec(), vec![0x0F]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); - let node = node.inner().as_leaf().unwrap(); + let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]); assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO // Covers case of leaf with no partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); - let node = node.inner().as_leaf().unwrap(); + let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]); assert_eq!(node.partial_path.to_vec(), vec![0x0F]); @@ -861,15 +836,17 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_between_nodes() { - let (merkle, sentinel_addr) = created_populated_merkle(); + let merkle = created_populated_merkle(); - let mut stream = - merkle.node_iter_from(sentinel_addr, vec![0x00, 0x00, 0x01].into_boxed_slice()); + let mut stream = MerkleNodeStream::new( + merkle.nodestore(), + vec![0x00, 0x00, 0x01].into_boxed_slice(), + ); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().value.to_vec(), + node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xD0, 0xD0] ); @@ -877,7 +854,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().value.to_vec(), + node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); @@ -886,15 +863,17 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_on_node() { - let (merkle, sentinel_addr) = created_populated_merkle(); + let merkle = created_populated_merkle(); - let mut stream = - merkle.node_iter_from(sentinel_addr, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); + let mut stream = MerkleNodeStream::new( + merkle.nodestore(), + vec![0x00, 0xD0, 0xD0].into_boxed_slice(), + ); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().value.to_vec(), + node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xD0, 0xD0] ); @@ -902,7 +881,7 @@ mod tests { let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( - node.inner().as_leaf().unwrap().clone().value.to_vec(), + node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); @@ -911,9 +890,9 @@ mod tests { #[tokio::test] async fn node_iterator_start_key_after_last_key() { - let (merkle, sentinel_addr) = created_populated_merkle(); + let merkle = created_populated_merkle(); - let stream = merkle.node_iter_from(sentinel_addr, vec![0xFF].into_boxed_slice()); + let stream = MerkleNodeStream::new(merkle.nodestore(), vec![0xFF].into_boxed_slice()); check_stream_is_done(stream).await; } @@ -925,18 +904,15 @@ mod tests { #[tokio::test] async fn key_value_iterate_many(start: Option<&[u8]>) { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); // insert all values from u8::MIN to u8::MAX, with the key and value the same for k in u8::MIN..=u8::MAX { - merkle.insert([k], vec![k], sentinel_addr).unwrap(); + merkle.insert(&[k], Box::new([k])).unwrap(); } let mut stream = match start { - Some(start) => { - merkle.key_value_iter_from_key(sentinel_addr, start.to_vec().into_boxed_slice()) - } - None => merkle.key_value_iter(sentinel_addr), + Some(start) => merkle._key_value_iter_from_key(start.to_vec().into_boxed_slice()), + None => merkle._key_value_iter(), }; // we iterate twice because we should get a None then start over @@ -957,30 +933,29 @@ mod tests { #[tokio::test] async fn key_value_fused_empty() { let merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - check_stream_is_done(merkle.key_value_iter(sentinel_addr)).await; + check_stream_is_done(merkle._key_value_iter()).await; } #[tokio::test] async fn key_value_table_test() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); + let max: u8 = 100; // Insert key-values in reverse order to ensure iterator // doesn't just return the keys in insertion order. - for i in (0..=u8::MAX).rev() { - for j in (0..=u8::MAX).rev() { - let key = vec![i, j]; - let value = vec![i, j]; + for i in (0..=max).rev() { + for j in (0..=max).rev() { + let key = &[i, j]; + let value = Box::new([i, j]); - merkle.insert(key, value, sentinel_addr).unwrap(); + merkle.insert(key, value).unwrap(); } } // Test with no start key - let mut stream = merkle.key_value_iter(sentinel_addr); - for i in 0..=u8::MAX { - for j in 0..=u8::MAX { + let mut stream = merkle._key_value_iter(); + for i in 0..=max { + for j in 0..=max { let expected_key = vec![i, j]; let expected_value = vec![i, j]; @@ -996,10 +971,9 @@ mod tests { check_stream_is_done(stream).await; // Test with start key - for i in 0..=u8::MAX { - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![i].into_boxed_slice()); - for j in 0..=u8::MAX { + for i in 0..=max { + let mut stream = merkle._key_value_iter_from_key(vec![i].into_boxed_slice()); + for j in 0..=max { let expected_key = vec![i, j]; let expected_value = vec![i, j]; assert_eq!( @@ -1010,7 +984,7 @@ mod tests { j, ); } - if i == u8::MAX { + if i == max { check_stream_is_done(stream).await; } else { assert_eq!( @@ -1026,7 +1000,6 @@ mod tests { #[tokio::test] async fn key_value_fused_full() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let last = vec![0x00, 0x00, 0x00]; @@ -1040,10 +1013,10 @@ mod tests { } for kv in key_values.iter() { - merkle.insert(kv, kv.clone(), sentinel_addr).unwrap(); + merkle.insert(kv, kv.clone().into_boxed_slice()).unwrap(); } - let mut stream = merkle.key_value_iter(sentinel_addr); + let mut stream = merkle._key_value_iter(); for kv in key_values.iter() { let next = stream.next().await.unwrap().unwrap(); @@ -1057,39 +1030,31 @@ mod tests { #[tokio::test] async fn key_value_root_with_empty_value() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let key = vec![].into_boxed_slice(); - let value = vec![0x00]; + let value = [0x00]; - merkle.insert(&key, value.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, value.into()).unwrap(); - let mut stream = merkle.key_value_iter(sentinel_addr); + let mut stream = merkle._key_value_iter(); - assert_eq!(stream.next().await.unwrap().unwrap(), (key, value)); + assert_eq!(stream.next().await.unwrap().unwrap(), (key, value.into())); } #[tokio::test] async fn key_value_get_branch_and_leaf() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - let first_leaf = &[0x00, 0x00]; - let second_leaf = &[0x00, 0x0f]; - let branch = &[0x00]; + let first_leaf = [0x00, 0x00]; + let second_leaf = [0x00, 0x0f]; + let branch = [0x00]; - merkle - .insert(first_leaf, first_leaf.to_vec(), sentinel_addr) - .unwrap(); - merkle - .insert(second_leaf, second_leaf.to_vec(), sentinel_addr) - .unwrap(); + merkle.insert(&first_leaf, first_leaf.into()).unwrap(); + merkle.insert(&second_leaf, second_leaf.into()).unwrap(); - merkle - .insert(branch, branch.to_vec(), sentinel_addr) - .unwrap(); + merkle.insert(&branch, branch.into()).unwrap(); - let mut stream = merkle.key_value_iter(sentinel_addr); + let mut stream = merkle._key_value_iter(); assert_eq!( stream.next().await.unwrap().unwrap(), @@ -1113,7 +1078,6 @@ mod tests { #[tokio::test] async fn key_value_start_at_key_not_in_trie() { let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let first_key = 0x00; let intermediate = 0x80; @@ -1129,11 +1093,10 @@ mod tests { assert!(key_values[1] < key_values[2]); for key in key_values.iter() { - merkle.insert(key, key.to_vec(), sentinel_addr).unwrap(); + merkle.insert(key, key.clone().into_boxed_slice()).unwrap(); } - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![intermediate].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); let first = stream.next().await.unwrap().unwrap(); @@ -1157,19 +1120,18 @@ mod tests { let children = 0..=0x0f; let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); children.clone().for_each(|child_path| { let key = vec![sibling_path, child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); }); let mut keys: Vec<_> = children .map(|child_path| { let key = vec![branch_path, child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) @@ -1180,8 +1142,7 @@ mod tests { let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); let keys = &keys[start..]; - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![branch_path].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![branch_path].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1202,23 +1163,22 @@ mod tests { let children = (0..=0xf).map(|val| (val << 4) + val); // 0x00, 0x11, ... 0xff let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); merkle - .insert(&branch_key, branch_key.clone(), sentinel_addr) + .insert(&branch_key, branch_key.clone().into()) .unwrap(); children.clone().for_each(|child_path| { let key = vec![sibling_path, child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); }); let mut keys: Vec<_> = children .map(|child_path| { let key = vec![branch_path, child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) @@ -1230,8 +1190,7 @@ mod tests { let start = keys.iter().position(|key| key == &branch_key).unwrap(); let keys = &keys[start..]; - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, branch_key.into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(branch_key.into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1248,13 +1207,12 @@ mod tests { let missing = 0x0a; let children = (0..=0x0f).filter(|x| *x != missing); let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) @@ -1262,8 +1220,7 @@ mod tests { let keys = &keys[(missing as usize)..]; - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![missing].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1284,14 +1241,12 @@ mod tests { let children = (0..=0x0f).map(|val| vec![shared_path, val]); let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); children.for_each(|key| { - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); }); - let stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![start_key].into_boxed_slice()); + let stream = merkle._key_value_iter_from_key(vec![start_key].into_boxed_slice()); check_stream_is_done(stream).await; } @@ -1305,17 +1260,15 @@ mod tests { let children = (0..=0x0f).map(|val| vec![shared_path, val]); let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|key| { - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) .collect(); - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![start_key].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![start_key].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1334,13 +1287,12 @@ mod tests { .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff .filter(|x| *x != missing); let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) @@ -1348,8 +1300,7 @@ mod tests { let keys = &keys[((missing >> 4) as usize)..]; - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![missing].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1363,12 +1314,12 @@ mod tests { #[tokio::test] async fn key_value_start_at_key_greater_than_all_others_leaf() { - let key = vec![0x00]; - let greater_key = vec![0xff]; + let key = [0x00]; + let greater_key = [0xff]; let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); - merkle.insert(key.clone(), key, sentinel_addr).unwrap(); - let stream = merkle.key_value_iter_from_key(sentinel_addr, greater_key.into_boxed_slice()); + merkle.insert(&key, key.into()).unwrap(); + + let stream = merkle._key_value_iter_from_key(greater_key.into()); check_stream_is_done(stream).await; } @@ -1380,13 +1331,12 @@ mod tests { .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff .filter(|x| *x != greatest); let mut merkle = create_test_merkle(); - let sentinel_addr = merkle.init_sentinel().unwrap(); let keys: Vec<_> = children .map(|child_path| { let key = vec![child_path]; - merkle.insert(&key, key.clone(), sentinel_addr).unwrap(); + merkle.insert(&key, key.clone().into()).unwrap(); key }) @@ -1394,8 +1344,7 @@ mod tests { let keys = &keys[((greatest >> 4) as usize)..]; - let mut stream = - merkle.key_value_iter_from_key(sentinel_addr, vec![greatest].into_boxed_slice()); + let mut stream = merkle._key_value_iter_from_key(vec![greatest].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 165fe9113f34..ebfab2b7deea 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,8 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::MerkleError; -pub use crate::merkle::Proof; +use crate::manager::RevisionManagerError; +use crate::proof::ProofNode; +use crate::range_proof::RangeProof; +use crate::{merkle::MerkleError, proof::Proof}; use async_trait::async_trait; use futures::Stream; use std::{fmt::Debug, sync::Arc}; @@ -30,7 +32,7 @@ impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} /// in time /// - They are used to provide integrity at different points in a /// proof -pub type HashKey = [u8; 32]; +pub type HashKey = storage::TrieHash; /// A key/value pair operation. Only put (upsert) and delete are /// supported @@ -67,14 +69,14 @@ pub enum Error { IncorrectRootHash { provided: HashKey, current: HashKey }, /// Invalid range - #[error("Invalid range: {first_key:?} > {last_key:?}")] + #[error("Invalid range: {start_key:?} > {end_key:?}")] InvalidRange { - first_key: Vec, - last_key: Vec, + start_key: Box<[u8]>, + end_key: Box<[u8]>, }, #[error("IO error: {0}")] - IO(std::io::Error), + IO(#[from] std::io::Error), #[error("Invalid proposal")] InvalidProposal, @@ -84,6 +86,9 @@ pub enum Error { #[error("Range too small")] RangeTooSmall, + + #[error("request RangeProof for empty trie")] + RangeProofOnEmptyTrie, } impl From for Error { @@ -93,13 +98,11 @@ impl From for Error { } } -/// A range proof, consisting of a proof of the first key and the last key, -/// and a vector of all key/value pairs -#[derive(Debug)] -pub struct RangeProof { - pub first_key_proof: Proof>, - pub last_key_proof: Proof>, - pub middle: Vec<(K, V)>, +impl From for Error { + fn from(err: RevisionManagerError) -> Self { + // TODO: do a better job + Error::InternalError(Box::new(err)) + } } /// The database interface, which includes a type for a static view of @@ -119,7 +122,7 @@ pub trait Db { async fn revision(&self, hash: HashKey) -> Result, Error>; /// Get the hash of the most recently committed version - async fn root_hash(&self) -> Result; + async fn root_hash(&self) -> Result, Error>; /// Propose a change to the database via a batch /// @@ -134,7 +137,7 @@ pub trait Db { async fn propose( &self, data: Batch, - ) -> Result; + ) -> Result, Error>; } /// A view of the database at a specific time. These are wrapped with @@ -153,13 +156,14 @@ pub trait DbView { Self: 'a; /// Get the root hash for the current DbView - async fn root_hash(&self) -> Result; + async fn root_hash(&self) -> Result, Error>; /// Get the value of a specific key async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key - async fn single_key_proof(&self, key: K) -> Result>>, Error>; + async fn single_key_proof(&self, key: K) + -> Result>, Error>; /// Obtain a range proof over a set of keys /// @@ -174,7 +178,7 @@ pub trait DbView { first_key: Option, last_key: Option, limit: Option, - ) -> Result, Vec>>, Error>; + ) -> Result, Box<[u8]>, ProofNode>>, Error>; /// Obtain a stream over the keys/values of this view, using an optional starting point /// @@ -231,5 +235,5 @@ pub trait Proposal: DbView + Send + Sync { async fn propose( self: Arc, data: Batch, - ) -> Result; + ) -> Result, Error>; } diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 252ff157f122..cff28aa85ae1 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -17,14 +17,10 @@ use crate::{db::DbError, v2::api}; impl From for api::Error { fn from(value: DbError) -> Self { match value { - DbError::Aio(e) => api::Error::InternalError(Box::new(e)), DbError::InvalidParams => api::Error::InternalError(Box::new(value)), DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), DbError::System(e) => api::Error::IO(e.into()), - DbError::KeyNotFound | DbError::CreateError => { - api::Error::InternalError(Box::new(value)) - } - DbError::Shale(e) => api::Error::InternalError(Box::new(e)), + DbError::CreateError => api::Error::InternalError(Box::new(value)), DbError::IO(e) => api::Error::IO(e), DbError::InvalidProposal => api::Error::InvalidProposal, } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 4fb65dc7d8b0..92b83f9b3070 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,11 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::{ + proof::{Proof, ProofNode}, + range_proof::RangeProof, +}; + use super::{ - api::{Batch, Db, DbView, Error, HashKey, KeyType, RangeProof, ValueType}, + api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}, propose::{Proposal, ProposalBase}, }; -use crate::merkle::Proof; use async_trait::async_trait; use futures::Stream; use std::sync::Arc; @@ -21,9 +25,6 @@ pub struct EmptyDb; #[derive(Debug)] pub struct HistoricalImpl; -/// This is the hash of the [EmptyDb] root -const ROOT_HASH: [u8; 32] = [0; 32]; - #[async_trait] impl Db for EmptyDb { type Historical = HistoricalImpl; @@ -31,18 +32,14 @@ impl Db for EmptyDb { type Proposal = Proposal; async fn revision(&self, hash_key: HashKey) -> Result, Error> { - if hash_key == ROOT_HASH { - Ok(HistoricalImpl.into()) - } else { - Err(Error::HashNotFound { provided: hash_key }) - } + Err(Error::HashNotFound { provided: hash_key }) } - async fn root_hash(&self) -> Result { - Ok(ROOT_HASH) + async fn root_hash(&self) -> Result, Error> { + Ok(None) } - async fn propose(&self, data: Batch) -> Result + async fn propose(&self, data: Batch) -> Result, Error> where K: KeyType, V: ValueType, @@ -58,15 +55,18 @@ impl Db for EmptyDb { impl DbView for HistoricalImpl { type Stream<'a> = EmptyStreamer; - async fn root_hash(&self) -> Result { - Ok(ROOT_HASH) + async fn root_hash(&self) -> Result, Error> { + Ok(None) } async fn val(&self, _key: K) -> Result>, Error> { Ok(None) } - async fn single_key_proof(&self, _key: K) -> Result>>, Error> { + async fn single_key_proof( + &self, + _key: K, + ) -> Result>, Error> { Ok(None) } @@ -75,7 +75,7 @@ impl DbView for HistoricalImpl { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Vec>>, Error> { + ) -> Result, Box<[u8]>, ProofNode>>, Error> { Ok(None) } @@ -100,8 +100,6 @@ impl Stream for EmptyStreamer { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use futures::StreamExt; - use super::*; use crate::v2::api::{BatchOp, Proposal}; @@ -139,7 +137,7 @@ mod tests { BatchOp::Delete { key: b"z" }, ]; - let proposal1 = Arc::new(db.propose(batch).await?); + let proposal1 = db.propose(batch).await?; // create proposal2 which adds key "z" with value "undo" let proposal2 = proposal1 @@ -149,7 +147,6 @@ mod tests { value: "undo", }]) .await?; - let proposal2 = Arc::new(proposal2); // both proposals still have (k,v) assert_eq!(proposal1.val(b"k").await.unwrap().unwrap(), b"v"); assert_eq!(proposal2.val(b"k").await.unwrap().unwrap(), b"v"); @@ -159,31 +156,14 @@ mod tests { assert_eq!(proposal2.val(b"z").await.unwrap().unwrap(), b"undo"); // create a proposal3 by adding the two proposals together, keeping the originals - let proposal3 = proposal1.as_ref() + proposal2.as_ref(); - assert_eq!(proposal3.val(b"k").await.unwrap().unwrap(), b"v"); - assert_eq!(proposal3.val(b"z").await.unwrap().unwrap(), b"undo"); + // TODO: consider making this possible again + // let proposal3 = proposal1.as_ref() + proposal2.as_ref(); + // assert_eq!(proposal3.val(b"k").await.unwrap().unwrap(), b"v"); + // assert_eq!(proposal3.val(b"z").await.unwrap().unwrap(), b"undo"); // now consume proposal1 and proposal2 proposal2.commit().await?; Ok(()) } - - #[tokio::test] - async fn empty_streamer() -> Result<(), Error> { - let emptydb = EmptyDb {}; - let rev = emptydb.revision(ROOT_HASH).await?; - let mut iter = rev.iter()?; - assert!(iter.next().await.is_none()); - Ok(()) - } - - #[tokio::test] - async fn empty_streamer_start_at() -> Result<(), Error> { - let emptydb = EmptyDb {}; - let rev = emptydb.revision(ROOT_HASH).await?; - let mut iter = rev.iter_from(b"ignored")?; - assert!(iter.next().await.is_none()); - Ok(()) - } } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 95b015f99b80..95e442d58111 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -6,9 +6,10 @@ use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use async_trait::async_trait; use futures::stream::Empty; -use crate::{merkle::Proof, v2::api}; - use super::api::{KeyType, ValueType}; +use crate::proof::{Proof, ProofNode}; +use crate::range_proof::RangeProof; +use crate::v2::api; #[derive(Clone, Debug)] pub(crate) enum KeyOp { @@ -85,7 +86,7 @@ impl Proposal { pub(crate) fn new( base: ProposalBase, batch: api::Batch, - ) -> Self { + ) -> Arc { let delta = batch .into_iter() .map(|op| match op { @@ -96,7 +97,7 @@ impl Proposal { }) .collect(); - Self { base, delta } + Arc::new(Self { base, delta }) } } @@ -105,7 +106,7 @@ impl api::DbView for Proposal { // TODO: Replace with the correct stream type for an in-memory proposal implementation type Stream<'a> = Empty, Vec), api::Error>> where T: 'a; - async fn root_hash(&self) -> Result { + async fn root_hash(&self) -> Result, api::Error> { todo!(); } @@ -128,7 +129,7 @@ impl api::DbView for Proposal { async fn single_key_proof( &self, _key: K, - ) -> Result>>, api::Error> { + ) -> Result>, api::Error> { todo!(); } @@ -137,7 +138,7 @@ impl api::DbView for Proposal { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Vec>>, api::Error> { + ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { todo!(); } @@ -156,7 +157,7 @@ impl api::Proposal for Proposal { async fn propose( self: Arc, data: api::Batch, - ) -> Result { + ) -> Result, api::Error> { // find the Arc for this base proposal from the parent Ok(Proposal::new(ProposalBase::Proposal(self), data)) } diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs index 36c1c5d8d8c4..db8315f60ad6 100644 --- a/firewood/tests/common/mod.rs +++ b/firewood/tests/common/mod.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{env::temp_dir, fs::remove_dir_all, ops::Deref, path::PathBuf}; +use std::{env::temp_dir, fs::remove_file, ops::Deref, path::PathBuf}; use firewood::db::{Db, DbConfig}; use typed_builder::TypedBuilder; @@ -9,11 +9,11 @@ use typed_builder::TypedBuilder; #[derive(Clone, Debug, TypedBuilder)] pub struct TestDbCreator { #[builder(setter(into))] - test_name: String, + _test_name: String, #[builder(default, setter(into))] path: Option, #[builder(default = DbConfig::builder().truncate(true).build())] - cfg: DbConfig, + _cfg: DbConfig, } pub struct TestDb { @@ -24,7 +24,7 @@ pub struct TestDb { impl TestDbCreator { #[allow(clippy::unwrap_used)] - pub async fn create(self) -> TestDb { + pub async fn _create(self) -> TestDb { let path = self.path.clone().unwrap_or_else(|| { let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") .unwrap_or(temp_dir().into()) @@ -32,11 +32,11 @@ impl TestDbCreator { if path.join("tmp").is_dir() { path.push("tmp"); } - path.join(&self.test_name) + path.join(&self._test_name) }); let mut creator = self.clone(); creator.path = path.clone().into(); - let db = Db::new(&path, &self.cfg).await.unwrap(); + let db = Db::new(&path, self._cfg).await.unwrap(); TestDb { creator, db, @@ -55,12 +55,12 @@ impl Deref for TestDb { impl TestDb { /// reopen the database, consuming the old TestDb and giving you a new one - pub async fn reopen(mut self) -> Self { + pub async fn _reopen(mut self) -> Self { let mut creator = self.creator.clone(); self.preserve_on_drop = true; drop(self); - creator.cfg.truncate = false; - creator.create().await + creator._cfg.truncate = false; + creator._create().await } } @@ -68,7 +68,7 @@ impl Drop for TestDb { fn drop(&mut self) { if !self.preserve_on_drop { #[allow(clippy::unwrap_used)] - remove_dir_all(self.creator.path.as_ref().unwrap()).unwrap(); + remove_file(self.creator.path.as_ref().unwrap()).unwrap(); } } } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index dc15398f409f..c6216462740d 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -1,343 +1,296 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::{ - db::{DbConfig, WalConfig}, - v2::api::{self, BatchOp, Db as _, DbView, Proposal}, -}; -use tokio::task::block_in_place; - -use std::{collections::VecDeque, env::temp_dir, path::PathBuf, sync::Arc}; - mod common; -use common::TestDbCreator; // TODO: use a trait -macro_rules! kv_dump { +macro_rules! _kv_dump { ($e: ident) => {{ let mut s = Vec::new(); - $e.kv_dump(&mut s).unwrap(); + $e.dump(&mut s).unwrap(); String::from_utf8(s).unwrap() }}; } -#[tokio::test(flavor = "multi_thread")] -#[allow(clippy::unwrap_used)] -async fn test_basic_metrics() { - let cfg = DbConfig::builder() - .meta_ncached_pages(1024) - .meta_ncached_files(128) - .payload_ncached_pages(1024) - .payload_ncached_files(128) - .payload_file_nbit(16) - .payload_regn_nbit(16) - .wal( - WalConfig::builder() - .file_nbit(15) - .block_nbit(8) - .max_revisions(10) - .build(), - ); - - let db = TestDbCreator::builder() - .cfg(cfg.build()) - .test_name("test_basic_metrics") - .build() - .create() - .await; - - // let metrics = db.metrics(); - // TODO: kv_get is no longer a valid metric, and DbRev has no access to Db.metrics (yet) - //assert_eq!(metrics.kv_get.hit_count.get(), 0); - - // TODO: we can't fetch the revision for the empty tree, so insert a single value - Arc::new( - db.propose(vec![BatchOp::Put { - key: b"a", - value: b"b", - }]) - .await - .unwrap(), - ) - .commit() - .await - .unwrap(); - - let root = db.root_hash().await.unwrap(); - let rev = db.revision(root).await.unwrap(); - rev.val("a").await.ok().unwrap().unwrap(); - //assert_eq!(metrics.val.hit_count.get(), 1); -} - -#[tokio::test(flavor = "multi_thread")] -#[allow(clippy::unwrap_used)] -async fn test_revisions() { - use rand::{rngs::StdRng, Rng, SeedableRng}; - let cfg = DbConfig::builder() - .meta_ncached_pages(1024) - .meta_ncached_files(128) - .payload_ncached_pages(1024) - .payload_ncached_files(128) - .payload_file_nbit(16) - .payload_regn_nbit(16) - .wal( - WalConfig::builder() - .file_nbit(15) - .block_nbit(8) - .max_revisions(10) - .build(), - ) - .build(); - - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.gen_range(1..max_len0 + 1), - rng.gen_range(1..max_len1 + 1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().gen_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().gen())) - .collect(); - key - }; - - for i in 0..10 { - let db = TestDbCreator::builder() - .cfg(cfg.clone()) - .test_name("test_revisions") - .build() - .create() - .await; - let mut dumped = VecDeque::new(); - let mut hashes: VecDeque = VecDeque::new(); - for _ in 0..10 { - { - let mut batch = Vec::new(); - let m = rng.borrow_mut().gen_range(1..20); - for _ in 0..m { - let key = keygen(); - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - let write = BatchOp::Put { - key, - value: val.to_vec(), - }; - batch.push(write); - } - let proposal = Arc::new(db.propose(batch).await.unwrap()); - proposal.commit().await.unwrap(); - } - while dumped.len() > 10 { - dumped.pop_back(); - hashes.pop_back(); - } - let root_hash = db.root_hash().await.unwrap(); - hashes.push_front(root_hash); - dumped.push_front(kv_dump!(db)); - for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { - let rev = db.revision(hash).await.unwrap(); - assert_eq!(rev.root_hash().await.unwrap(), hash); - assert_eq!(kv_dump!(rev), *dump, "not the same: Pass {i}"); - } - } - - let db = db.reopen().await; - for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { - let rev = db.revision(hash).await.unwrap(); - rev.root_hash().await.unwrap(); - assert_eq!( - *dump, - block_in_place(|| kv_dump!(rev)), - "not the same: pass {i}" - ); - } - } -} - -#[tokio::test(flavor = "multi_thread")] -#[allow(clippy::unwrap_used)] -async fn create_db_issue_proof() { - let cfg = DbConfig::builder() - .meta_ncached_pages(1024) - .meta_ncached_files(128) - .payload_ncached_pages(1024) - .payload_ncached_files(128) - .payload_file_nbit(16) - .payload_regn_nbit(16) - .wal( - WalConfig::builder() - .file_nbit(15) - .block_nbit(8) - .max_revisions(10) - .build(), - ); - - let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(temp_dir().into()) - .into(); - tmpdir.push("/tmp/test_db_proof"); - - let db = firewood::db::Db::new(tmpdir, &cfg.truncate(true).build()) - .await - .unwrap(); - - let items = vec![ - ("d", "verb"), - ("do", "verb"), - ("doe", "reindeer"), - ("e", "coin"), - ]; - - let mut batch = Vec::new(); - for (k, v) in items { - let write = BatchOp::Put { - key: k.as_bytes(), - value: v.as_bytes().to_vec(), - }; - batch.push(write); - } - let proposal = Arc::new(db.propose(batch).await.unwrap()); - proposal.commit().await.unwrap(); - - let root_hash = db.root_hash().await.unwrap(); - - // Add second commit - let mut batch = Vec::new(); - for (k, v) in Vec::from([("x", "two")]).iter() { - let write = BatchOp::Put { - key: k.to_string().as_bytes().to_vec(), - value: v.as_bytes().to_vec(), - }; - batch.push(write); - } - let proposal = Arc::new(db.propose(batch).await.unwrap()); - proposal.commit().await.unwrap(); - - let rev = db.revision(root_hash).await.unwrap(); - let key = "doe".as_bytes(); - let root_hash = rev.root_hash().await.unwrap(); - - match rev.single_key_proof(key).await { - Ok(proof) => { - let verification = proof.unwrap().verify(key, root_hash).unwrap(); - assert!(verification.is_some()); - } - Err(e) => { - panic!("Error: {}", e); - } - } - - let missing_key = "dog".as_bytes(); - // The proof for the missing key will return the path to the missing key - if let Err(e) = rev.single_key_proof(missing_key).await { - println!("Error: {}", e); - // TODO do type assertion on error - } -} - -macro_rules! assert_val { +// #[ignore = "unimplemented"] +// #[tokio::test(flavor = "multi_thread")] +// #[allow(clippy::unwrap_used)] +// async fn test_basic_metrics() { +// let cfg = DbConfig::builder(); + +// let db = TestDbCreator::builder() +// .cfg(cfg.build()) +// .test_name("test_basic_metrics") +// .build() +// .create() +// .await; + +// // let metrics = db.metrics(); +// // TODO: kv_get is no longer a valid metric, and DbRev has no access to Db.metrics (yet) +// //assert_eq!(metrics.kv_get.hit_count.get(), 0); + +// // TODO: we can't fetch the revision for the empty tree, so insert a single value + +// db.propose(vec![BatchOp::Put { +// key: b"a", +// value: b"b", +// }]) +// .await +// .unwrap() +// .commit() +// .await +// .unwrap(); + +// let root = db.root_hash().await.unwrap(); +// let rev = db.revision(root).await.unwrap(); +// rev.val("a").await.ok().unwrap().unwrap(); +// //assert_eq!(metrics.val.hit_count.get(), 1); +// } + +// #[ignore = "unimplemented"] +// #[tokio::test(flavor = "multi_thread")] +// #[allow(clippy::unwrap_used)] +// async fn test_revisions() { +// use rand::{rngs::StdRng, Rng, SeedableRng}; +// let cfg = DbConfig::builder().build(); + +// let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); +// let max_len0 = 8; +// let max_len1 = 4; +// let keygen = || { +// let (len0, len1): (usize, usize) = { +// let mut rng = rng.borrow_mut(); +// ( +// rng.gen_range(1..max_len0 + 1), +// rng.gen_range(1..max_len1 + 1), +// ) +// }; +// let key: Vec = (0..len0) +// .map(|_| rng.borrow_mut().gen_range(0..2)) +// .chain((0..len1).map(|_| rng.borrow_mut().gen())) +// .collect(); +// key +// }; + +// for i in 0..10 { +// let db = TestDbCreator::builder() +// .cfg(cfg.clone()) +// .test_name("test_revisions") +// .build() +// .create() +// .await; +// let mut dumped = VecDeque::new(); +// let mut hashes: VecDeque = VecDeque::new(); +// for _ in 0..10 { +// { +// let mut batch = Vec::new(); +// let m = rng.borrow_mut().gen_range(1..20); +// for _ in 0..m { +// let key = keygen(); +// let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); +// let write = BatchOp::Put { +// key, +// value: val.to_vec(), +// }; +// batch.push(write); +// } +// let proposal = db.propose(batch).await.unwrap(); +// proposal.commit().await.unwrap(); +// } +// while dumped.len() > 10 { +// dumped.pop_back(); +// hashes.pop_back(); +// } +// let root_hash = db.root_hash().await.unwrap(); +// hashes.push_front(root_hash); +// dumped.push_front(kv_dump!(db)); +// for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { +// let rev = db.revision(hash).await.unwrap(); +// assert_eq!(rev.root_hash().await.unwrap(), hash); +// assert_eq!(kv_dump!(rev), *dump, "not the same: Pass {i}"); +// } +// } + +// let db = db.reopen().await; +// for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { +// let rev = db.revision(hash).await.unwrap(); +// rev.root_hash().await.unwrap(); +// assert_eq!( +// *dump, +// block_in_place(|| kv_dump!(rev)), +// "not the same: pass {i}" +// ); +// } +// } +// } + +// #[ignore = "unimplemented"] +// #[tokio::test(flavor = "multi_thread")] +// #[allow(clippy::unwrap_used)] +// async fn create_db_issue_proof() { +// let cfg = DbConfig::builder(); + +// let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") +// .unwrap_or(temp_dir().into()) +// .into(); +// tmpdir.push("/tmp/test_db_proof"); + +// let db = firewood::db::Db::new(tmpdir, cfg.truncate(true).build()) +// .await +// .unwrap(); + +// let items = vec![ +// ("d", "verb"), +// ("do", "verb"), +// ("doe", "reindeer"), +// ("e", "coin"), +// ]; + +// let mut batch = Vec::new(); +// for (k, v) in items { +// let write = BatchOp::Put { +// key: k.as_bytes(), +// value: v.as_bytes().to_vec(), +// }; +// batch.push(write); +// } +// let proposal = db.propose(batch).await.unwrap(); +// proposal.commit().await.unwrap(); + +// let root_hash = db.root_hash().await.unwrap(); + +// // Add second commit +// let mut batch = Vec::new(); +// for (k, v) in Vec::from([("x", "two")]).iter() { +// let write = BatchOp::Put { +// key: k.to_string().as_bytes().to_vec(), +// value: v.as_bytes().to_vec(), +// }; +// batch.push(write); +// } +// let proposal = db.propose(batch).await.unwrap(); +// proposal.commit().await.unwrap(); + +// let rev = db.revision(root_hash).await.unwrap(); +// let key = "doe".as_bytes(); +// let root_hash = rev.root_hash().await.unwrap(); + +// match rev.single_key_proof(key).await { +// Ok(proof) => { +// let verification = proof.unwrap().verify(key, root_hash).unwrap(); +// assert!(verification.is_some()); +// } +// Err(e) => { +// panic!("Error: {}", e); +// } +// } + +// let missing_key = "dog".as_bytes(); +// // The proof for the missing key will return the path to the missing key +// if let Err(e) = rev.single_key_proof(missing_key).await { +// println!("Error: {}", e); +// // TODO do type assertion on error +// } +// } + +macro_rules! _assert_val { ($rev: ident, $key:literal, $expected_val:literal) => { let actual = $rev.val($key.as_bytes()).await.unwrap().unwrap(); assert_eq!(actual, $expected_val.as_bytes().to_vec()); }; } -#[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] -#[tokio::test(flavor = "multi_thread")] -#[allow(clippy::unwrap_used)] -async fn db_proposal() -> Result<(), api::Error> { - let cfg = DbConfig::builder() - .wal(WalConfig::builder().max_revisions(10).build()) - .build(); - - let db = TestDbCreator::builder() - .cfg(cfg) - .test_name("db_proposal") - .build() - .create() - .await; - - let batch = vec![ - BatchOp::Put { - key: b"k", - value: "v".as_bytes().to_vec(), - }, - BatchOp::Delete { key: b"z" }, - ]; - - let proposal = Arc::new(db.propose(batch).await?); - assert_val!(proposal, "k", "v"); - - let batch_2 = vec![BatchOp::Put { - key: b"k2", - value: "v2".as_bytes().to_vec(), - }]; - let proposal_2 = Arc::new(proposal.clone().propose(batch_2).await?); - assert_val!(proposal_2, "k", "v"); - assert_val!(proposal_2, "k2", "v2"); - - proposal.clone().commit().await?; - proposal_2.commit().await?; - - let t1 = tokio::spawn({ - let proposal = proposal.clone(); - async move { - let another_batch = vec![BatchOp::Put { - key: b"another_k_t1", - value: "another_v_t1".as_bytes().to_vec(), - }]; - let another_proposal = proposal.clone().propose(another_batch).await.unwrap(); - let rev = another_proposal.get_revision(); - assert_val!(rev, "k", "v"); - assert_val!(rev, "another_k_t1", "another_v_t1"); - // The proposal is invalid and cannot be committed - assert!(Arc::new(another_proposal).commit().await.is_err()); - } - }); - let t2 = tokio::spawn({ - let proposal = proposal.clone(); - async move { - let another_batch = vec![BatchOp::Put { - key: b"another_k_t2", - value: "another_v_t2".as_bytes().to_vec(), - }]; - let another_proposal = proposal.clone().propose(another_batch).await.unwrap(); - let rev = another_proposal.get_revision(); - assert_val!(rev, "k", "v"); - assert_val!(rev, "another_k_t2", "another_v_t2"); - assert!(Arc::new(another_proposal).commit().await.is_err()); - } - }); - let (first, second) = tokio::join!(t1, t2); - first.unwrap(); - second.unwrap(); - - // Recursive commit - - let batch = vec![BatchOp::Put { - key: b"k3", - value: "v3".as_bytes().to_vec(), - }]; - let proposal = Arc::new(db.propose(batch).await?); - assert_val!(proposal, "k", "v"); - assert_val!(proposal, "k2", "v2"); - assert_val!(proposal, "k3", "v3"); - - let batch_2 = vec![BatchOp::Put { - key: b"k4", - value: "v4".as_bytes().to_vec(), - }]; - let proposal_2 = Arc::new(proposal.clone().propose(batch_2).await?); - assert_val!(proposal_2, "k", "v"); - assert_val!(proposal_2, "k2", "v2"); - assert_val!(proposal_2, "k3", "v3"); - assert_val!(proposal_2, "k4", "v4"); - - proposal_2.commit().await?; - Ok(()) -} +// #[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] +// #[tokio::test(flavor = "multi_thread")] +// #[allow(clippy::unwrap_used)] +// async fn db_proposal() -> Result<(), api::Error> { +// let cfg = DbConfig::builder().build(); + +// let db = TestDbCreator::builder() +// .cfg(cfg) +// .test_name("db_proposal") +// .build() +// .create() +// .await; + +// let batch = vec![ +// BatchOp::Put { +// key: b"k", +// value: "v".as_bytes().to_vec(), +// }, +// BatchOp::Delete { key: b"z" }, +// ]; + +// let proposal = db.propose(batch).await?; +// assert_val!(proposal, "k", "v"); + +// let batch_2 = vec![BatchOp::Put { +// key: b"k2", +// value: "v2".as_bytes().to_vec(), +// }]; +// let proposal_2 = proposal.clone().propose(batch_2).await?; +// assert_val!(proposal_2, "k", "v"); +// assert_val!(proposal_2, "k2", "v2"); + +// proposal.clone().commit().await?; +// proposal_2.commit().await?; + +// let t1 = tokio::spawn({ +// let proposal = proposal.clone(); +// async move { +// let another_batch = vec![BatchOp::Put { +// key: b"another_k_t1", +// value: "another_v_t1".as_bytes().to_vec(), +// }]; +// let _another_proposal = proposal.clone().propose(another_batch).await.unwrap(); +// // TODO: re-enable this once proposals can fetch their view +// // let rev = another_proposal.get_revision(); +// // assert_val!(rev, "k", "v"); +// // assert_val!(rev, "another_k_t1", "another_v_t1"); +// // // The proposal is invalid and cannot be committed +// // assert!(Arc::new(another_proposal).commit().await.is_err()); +// } +// }); +// let t2 = tokio::spawn({ +// let proposal = proposal.clone(); +// async move { +// let another_batch = vec![BatchOp::Put { +// key: b"another_k_t2", +// value: "another_v_t2".as_bytes().to_vec(), +// }]; +// let _another_proposal = proposal.clone().propose(another_batch).await.unwrap(); +// // TODO: re-enable this once proposals can fetch their view +// // let rev = another_proposal.get_revision(); +// // assert_val!(rev, "k", "v"); +// // assert_val!(rev, "another_k_t2", "another_v_t2"); +// // assert!(Arc::new(another_proposal).commit().await.is_err()); +// } +// }); +// let (first, second) = tokio::join!(t1, t2); +// first.unwrap(); +// second.unwrap(); + +// // Recursive commit + +// let batch = vec![BatchOp::Put { +// key: b"k3", +// value: "v3".as_bytes().to_vec(), +// }]; +// let proposal = db.propose(batch).await?; +// assert_val!(proposal, "k", "v"); +// assert_val!(proposal, "k2", "v2"); +// assert_val!(proposal, "k3", "v3"); + +// let batch_2 = vec![BatchOp::Put { +// key: b"k4", +// value: "v4".as_bytes().to_vec(), +// }]; +// let proposal_2 = proposal.clone().propose(batch_2).await?; +// assert_val!(proposal_2, "k", "v"); +// assert_val!(proposal_2, "k2", "v2"); +// assert_val!(proposal_2, "k3", "v3"); +// assert_val!(proposal_2, "k4", "v4"); + +// proposal_2.commit().await?; +// Ok(()) +// } diff --git a/firewood/tests/merkle.rs b/firewood/tests/merkle.rs deleted file mode 100644 index 078879744430..000000000000 --- a/firewood/tests/merkle.rs +++ /dev/null @@ -1,1146 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use firewood::{ - merkle::{Bincode, Proof, ProofError}, - merkle_util::{DataStoreError, InMemoryMerkle}, -}; -use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng as _}; -use std::{collections::HashMap, fmt::Write}; - -fn merkle_build_test< - K: AsRef<[u8]> + std::cmp::Ord + Clone + std::fmt::Debug, - V: AsRef<[u8]> + Clone, ->( - items: Vec<(K, V)>, - meta_size: u64, - compact_size: u64, -) -> Result, DataStoreError> { - let mut merkle = InMemoryMerkle::new(meta_size, compact_size); - for (k, v) in items.iter() { - merkle.insert(k, v.as_ref().to_vec())?; - } - - Ok(merkle) -} - -#[test] -fn test_root_hash_simple_insertions() -> Result<(), DataStoreError> { - let items = vec![ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - - merkle.dump()?; - - Ok(()) -} - -#[test] -fn test_root_hash_fuzz_insertions() -> Result<(), DataStoreError> { - use rand::{rngs::StdRng, Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.gen_range(1..max_len0 + 1), - rng.gen_range(1..max_len1 + 1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().gen_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().gen())) - .collect(); - key - }; - - for _ in 0..10 { - let mut items = Vec::new(); - - for _ in 0..10 { - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - items.push((keygen(), val)); - } - - merkle_build_test(items, 0x1000000, 0x1000000)?; - } - - Ok(()) -} - -#[test] -#[allow(clippy::unwrap_used)] -fn test_root_hash_reversed_deletions() -> Result<(), DataStoreError> { - use rand::{rngs::StdRng, Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.gen_range(1..max_len0 + 1), - rng.gen_range(1..max_len1 + 1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().gen_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().gen())) - .collect(); - key - }; - - for _ in 0..10 { - let mut items: Vec<_> = (0..10) - .map(|_| keygen()) - .map(|key| { - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - (key, val) - }) - .collect(); - - items.sort(); - - let mut merkle: InMemoryMerkle = InMemoryMerkle::new(0x100000, 0x100000); - - let mut hashes = Vec::new(); - - for (k, v) in items.iter() { - hashes.push((merkle.root_hash()?, merkle.dump()?)); - merkle.insert(k, v.to_vec())?; - } - - let mut new_hashes = Vec::new(); - - for (k, _) in items.iter().rev() { - let before = merkle.dump()?; - merkle.remove(k)?; - new_hashes.push((merkle.root_hash()?, k, before, merkle.dump()?)); - } - - hashes.reverse(); - - for i in 0..hashes.len() { - #[allow(clippy::indexing_slicing)] - let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; - #[allow(clippy::indexing_slicing)] - let (expected_hash, expected_dump) = &hashes[i]; - let key = key.iter().fold(String::new(), |mut s, b| { - let _ = write!(s, "{:02x}", b); - s - }); - - assert_eq!(new_hash, expected_hash, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_dump}\n"); - } - } - - Ok(()) -} - -#[test] -#[allow(clippy::unwrap_used)] -fn test_root_hash_random_deletions() -> Result<(), DataStoreError> { - use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.gen_range(1..max_len0 + 1), - rng.gen_range(1..max_len1 + 1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().gen_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().gen())) - .collect(); - key - }; - - for i in 0..10 { - let mut items = std::collections::HashMap::new(); - - for _ in 0..10 { - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); - items.insert(keygen(), val); - } - - let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); - items_ordered.sort(); - items_ordered.shuffle(&mut *rng.borrow_mut()); - let mut merkle: InMemoryMerkle = InMemoryMerkle::new(0x100000, 0x100000); - - for (k, v) in items.iter() { - merkle.insert(k, v.to_vec())?; - } - - for (k, v) in items.iter() { - assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); - assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); - } - - for (k, _) in items_ordered.into_iter() { - assert!(merkle.get(&k)?.is_some()); - assert!(merkle.get_mut(&k)?.is_some()); - - merkle.remove(&k)?; - - assert!(merkle.get(&k)?.is_none()); - assert!(merkle.get_mut(&k)?.is_none()); - - items.remove(&k); - - for (k, v) in items.iter() { - assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); - assert_eq!(&*merkle.get_mut(k)?.unwrap().get(), &v[..]); - } - - let h = triehash::trie_root::, _, _>( - items.iter().collect(), - ); - - let h0 = merkle.root_hash()?; - - if h[..] != *h0 { - println!("{} != {}", hex::encode(h), hex::encode(*h0)); - } - } - - println!("i = {i}"); - } - Ok(()) -} - -#[test] -#[allow(clippy::unwrap_used, clippy::indexing_slicing)] -fn test_proof() -> Result<(), DataStoreError> { - let set = fixed_and_pseudorandom_data(500); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - - for (i, key) in keys.iter().enumerate() { - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - let val = merkle.verify_proof(key, &proof)?; - assert!(val.is_some()); - assert_eq!(val.unwrap(), vals[i]); - } - - Ok(()) -} - -#[test] -/// Verify the proofs that end with leaf node with the given key. -fn test_proof_end_with_leaf() -> Result<(), DataStoreError> { - let items = vec![ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - let key = "doe"; - - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - - let verify_proof = merkle.verify_proof(key, &proof)?; - assert!(verify_proof.is_some()); - - Ok(()) -} - -#[test] -/// Verify the proofs that end with branch node with the given key. -fn test_proof_end_with_branch() -> Result<(), DataStoreError> { - let items = vec![ - ("d", "verb"), - ("do", "verb"), - ("doe", "reindeer"), - ("e", "coin"), - ]; - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - let key = "d"; - - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - - let verify_proof = merkle.verify_proof(key, &proof)?; - assert!(verify_proof.is_some()); - - Ok(()) -} - -#[test] -fn test_bad_proof() -> Result<(), DataStoreError> { - let set = fixed_and_pseudorandom_data(800); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - let (keys, _): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - - for key in keys.iter() { - let mut proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - - // Delete an entry from the generated proofs. - let len = proof.0.len(); - let new_proof = Proof(proof.0.drain().take(len - 1).collect()); - assert!(merkle.verify_proof(key, &new_proof).is_err()); - } - - Ok(()) -} - -#[test] -// Tests that missing keys can also be proven. The test explicitly uses a single -// entry trie and checks for missing keys both before and after the single entry. -fn test_missing_key_proof() -> Result<(), DataStoreError> { - let items = vec![("k", "v")]; - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - for key in &["a", "j", "l", "z"] { - let proof = merkle.prove(key)?; - assert!(!proof.0.is_empty()); - assert!(proof.0.len() == 1); - - let val = merkle.verify_proof(key, &proof)?; - assert!(val.is_none()); - } - - Ok(()) -} - -#[test] -fn test_empty_tree_proof() -> Result<(), DataStoreError> { - let items: Vec<(&str, &str)> = Vec::new(); - let merkle = merkle_build_test(items, 0x10000, 0x10000)?; - let key = "x"; - - let proof = merkle.prove(key)?; - assert!(proof.0.is_empty()); - - Ok(()) -} - -#[test] -// Tests normal range proof with both edge proofs as the existent proof. -// The test cases are generated randomly. -#[allow(clippy::indexing_slicing)] -fn test_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - for _ in 0..10 { - let start = rand::thread_rng().gen_range(0..items.len()); - let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; - - if end <= start { - continue; - } - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end - 1].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let mut keys = Vec::new(); - let mut vals = Vec::new(); - for item in items[start..end].iter() { - keys.push(&item.0); - vals.push(&item.1); - } - - merkle.verify_range_proof(&proof, &items[start].0, &items[end - 1].0, keys, vals)?; - } - Ok(()) -} - -#[test] -// Tests a few cases which the proof is wrong. -// The prover is expected to detect the error. -#[allow(clippy::indexing_slicing)] -fn test_bad_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - for _ in 0..10 { - let start = rand::thread_rng().gen_range(0..items.len()); - let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; - - if end <= start { - continue; - } - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end - 1].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let mut keys: Vec<[u8; 32]> = Vec::new(); - let mut vals: Vec<[u8; 20]> = Vec::new(); - for item in items[start..end].iter() { - keys.push(*item.0); - vals.push(*item.1); - } - - let test_case: u32 = rand::thread_rng().gen_range(0..6); - let index = rand::thread_rng().gen_range(0..end - start); - match test_case { - 0 => { - // Modified key - keys[index] = rand::thread_rng().gen::<[u8; 32]>(); // In theory it can't be same - } - 1 => { - // Modified val - vals[index] = rand::thread_rng().gen::<[u8; 20]>(); // In theory it can't be same - } - 2 => { - // Gapped entry slice - if index == 0 || index == end - start - 1 { - continue; - } - keys.remove(index); - vals.remove(index); - } - 3 => { - // Out of order - let index_1 = rand::thread_rng().gen_range(0..end - start); - let index_2 = rand::thread_rng().gen_range(0..end - start); - if index_1 == index_2 { - continue; - } - keys.swap(index_1, index_2); - vals.swap(index_1, index_2); - } - 4 => { - // Set random key to empty, do nothing - keys[index] = [0; 32]; - } - 5 => { - // Set random value to nil - vals[index] = [0; 20]; - } - _ => unreachable!(), - } - assert!(merkle - .verify_range_proof(&proof, *items[start].0, *items[end - 1].0, keys, vals) - .is_err()); - } - - Ok(()) -} - -#[test] -// Tests normal range proof with two non-existent proofs. -// The test cases are generated randomly. -#[allow(clippy::indexing_slicing)] -fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - for _ in 0..10 { - let start = rand::thread_rng().gen_range(0..items.len()); - let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; - - if end <= start { - continue; - } - - // Short circuit if the decreased key is same with the previous key - let first = decrease_key(items[start].0); - if start != 0 && first.as_ref() == items[start - 1].0.as_ref() { - continue; - } - // Short circuit if the decreased key is underflow - if first.as_ref() > items[start].0.as_ref() { - continue; - } - // Short circuit if the increased key is same with the next key - let last = increase_key(items[end - 1].0); - if end != items.len() && last.as_ref() == items[end].0.as_ref() { - continue; - } - // Short circuit if the increased key is overflow - if last.as_ref() < items[end - 1].0.as_ref() { - continue; - } - - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let mut keys: Vec<[u8; 32]> = Vec::new(); - let mut vals: Vec<[u8; 20]> = Vec::new(); - for item in items[start..end].iter() { - keys.push(*item.0); - vals.push(*item.1); - } - - merkle.verify_range_proof(&proof, first, last, keys, vals)?; - } - - // Special case, two edge proofs for two edge key. - let first: [u8; 32] = [0; 32]; - let last: [u8; 32] = [255; 32]; - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; - - Ok(()) -} - -#[test] -// Tests such scenarios: -// - There exists a gap between the first element and the left edge proof -// - There exists a gap between the last element and the right edge proof -#[allow(clippy::indexing_slicing)] -fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - // Case 1 - let mut start = 100; - let mut end = 200; - let first = decrease_key(items[start].0); - - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end - 1].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - start = 105; // Gap created - let mut keys: Vec<[u8; 32]> = Vec::new(); - let mut vals: Vec<[u8; 20]> = Vec::new(); - // Create gap - for item in items[start..end].iter() { - keys.push(*item.0); - vals.push(*item.1); - } - assert!(merkle - .verify_range_proof(&proof, first, *items[end - 1].0, keys, vals) - .is_err()); - - // Case 2 - start = 100; - end = 200; - let last = increase_key(items[end - 1].0); - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - end = 195; // Capped slice - let mut keys: Vec<[u8; 32]> = Vec::new(); - let mut vals: Vec<[u8; 20]> = Vec::new(); - // Create gap - for item in items[start..end].iter() { - keys.push(*item.0); - vals.push(*item.1); - } - assert!(merkle - .verify_range_proof(&proof, *items[start].0, last, keys, vals) - .is_err()); - - Ok(()) -} - -#[test] -// Tests the proof with only one element. The first edge proof can be existent one or -// non-existent one. -#[allow(clippy::indexing_slicing)] -fn test_one_element_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - // One element with existent edge proof, both edge proofs - // point to the SAME key. - let start = 1000; - let start_proof = merkle.prove(items[start].0)?; - assert!(!start_proof.0.is_empty()); - - merkle.verify_range_proof( - &start_proof, - &items[start].0, - &items[start].0, - vec![&items[start].0], - vec![&items[start].1], - )?; - - // One element with left non-existent edge proof - let first = decrease_key(items[start].0); - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[start].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof( - &proof, - first, - *items[start].0, - vec![*items[start].0], - vec![*items[start].1], - )?; - - // One element with right non-existent edge proof - let last = increase_key(items[start].0); - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof( - &proof, - *items[start].0, - last, - vec![*items[start].0], - vec![*items[start].1], - )?; - - // One element with two non-existent edge proofs - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof( - &proof, - first, - last, - vec![*items[start].0], - vec![*items[start].1], - )?; - - // Test the mini trie with only a single element. - let key = rand::thread_rng().gen::<[u8; 32]>(); - let val = rand::thread_rng().gen::<[u8; 20]>(); - let merkle = merkle_build_test(vec![(key, val)], 0x10000, 0x10000)?; - - let first: [u8; 32] = [0; 32]; - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(key)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof(&proof, first, key, vec![key], vec![val])?; - - Ok(()) -} - -#[test] -// Tests the range proof with all elements. -// The edge proofs can be nil. -#[allow(clippy::indexing_slicing)] -fn test_all_elements_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let item_iter = items.clone().into_iter(); - let keys: Vec<&[u8; 32]> = item_iter.clone().map(|item| item.0).collect(); - let vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); - - let empty_proof = Proof(HashMap::<[u8; 32], Vec>::new()); - let empty_key: [u8; 32] = [0; 32]; - merkle.verify_range_proof( - &empty_proof, - &empty_key, - &empty_key, - keys.clone(), - vals.clone(), - )?; - - // With edge proofs, it should still work. - let start = 0; - let end = &items.len() - 1; - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof( - &proof, - items[start].0, - items[end].0, - keys.clone(), - vals.clone(), - )?; - - // Even with non-existent edge proofs, it should still work. - let first: [u8; 32] = [0; 32]; - let last: [u8; 32] = [255; 32]; - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; - - Ok(()) -} - -#[test] -// Tests the range proof with "no" element. The first edge proof must -// be a non-existent proof. -#[allow(clippy::indexing_slicing)] -fn test_empty_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let cases = [(items.len() - 1, false)]; - for c in cases.iter() { - let first = increase_key(items[c.0].0); - let proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - - // key and value vectors are intentionally empty. - let keys: Vec<[u8; 32]> = Vec::new(); - let vals: Vec<[u8; 20]> = Vec::new(); - - if c.1 { - assert!(merkle - .verify_range_proof(&proof, first, first, keys, vals) - .is_err()); - } else { - merkle.verify_range_proof(&proof, first, first, keys, vals)?; - } - } - - Ok(()) -} - -#[test] -// Focuses on the small trie with embedded nodes. If the gapped -// node is embedded in the trie, it should be detected too. -#[allow(clippy::indexing_slicing)] -fn test_gapped_range_proof() -> Result<(), ProofError> { - let mut items = Vec::new(); - // Sorted entries - for i in 0..10_u32 { - let mut key: [u8; 32] = [0; 32]; - for (index, d) in i.to_be_bytes().iter().enumerate() { - key[index] = *d; - } - items.push((key, i.to_be_bytes())); - } - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let first = 2; - let last = 8; - - let mut proof = merkle.prove(items[first].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[last - 1].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let middle = (first + last) / 2 - first; - let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 4]>) = items[first..last] - .iter() - .enumerate() - .filter(|(pos, _)| *pos != middle) - .map(|(_, item)| (&item.0, &item.1)) - .unzip(); - - assert!(merkle - .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) - .is_err()); - - Ok(()) -} - -#[test] -// Tests the element is not in the range covered by proofs. -#[allow(clippy::indexing_slicing)] -fn test_same_side_proof() -> Result<(), DataStoreError> { - let set = fixed_and_pseudorandom_data(4096); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let pos = 1000; - let mut last = decrease_key(items[pos].0); - let mut first = last; - first = decrease_key(&first); - - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - assert!(merkle - .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) - .is_err()); - - first = increase_key(items[pos].0); - last = first; - last = increase_key(&last); - - let mut proof = merkle.prove(first)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(last)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - assert!(merkle - .verify_range_proof(&proof, first, last, vec![*items[pos].0], vec![items[pos].1]) - .is_err()); - - Ok(()) -} - -#[test] -#[allow(clippy::indexing_slicing)] -// Tests the range starts from zero. -fn test_single_side_range_proof() -> Result<(), ProofError> { - for _ in 0..10 { - let mut set = HashMap::new(); - for _ in 0..4096_u32 { - let key = rand::thread_rng().gen::<[u8; 32]>(); - let val = rand::thread_rng().gen::<[u8; 20]>(); - set.insert(key, val); - } - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let cases = vec![0, 1, 100, 1000, items.len() - 1]; - for case in cases { - let start: [u8; 32] = [0; 32]; - let mut proof = merkle.prove(start)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[case].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let item_iter = items.clone().into_iter().take(case + 1); - let keys = item_iter.clone().map(|item| *item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - - merkle.verify_range_proof(&proof, start, *items[case].0, keys, vals)?; - } - } - Ok(()) -} - -#[test] -#[allow(clippy::indexing_slicing)] -// Tests the range ends with 0xffff...fff. -fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { - for _ in 0..10 { - let mut set = HashMap::new(); - for _ in 0..1024_u32 { - let key = rand::thread_rng().gen::<[u8; 32]>(); - let val = rand::thread_rng().gen::<[u8; 20]>(); - set.insert(key, val); - } - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let cases = vec![0, 1, 100, 1000, items.len() - 1]; - for case in cases { - let end: [u8; 32] = [255; 32]; - let mut proof = merkle.prove(items[case].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(end)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let item_iter = items.clone().into_iter().skip(case); - let keys = item_iter.clone().map(|item| item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - - merkle.verify_range_proof(&proof, items[case].0, &end, keys, vals)?; - } - } - Ok(()) -} - -#[test] -// Tests the range starts with zero and ends with 0xffff...fff. -fn test_both_sides_range_proof() -> Result<(), ProofError> { - for _ in 0..10 { - let mut set = HashMap::new(); - for _ in 0..4096_u32 { - let key = rand::thread_rng().gen::<[u8; 32]>(); - let val = rand::thread_rng().gen::<[u8; 20]>(); - set.insert(key, val); - } - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let start: [u8; 32] = [0; 32]; - let end: [u8; 32] = [255; 32]; - - let mut proof = merkle.prove(start)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(end)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - merkle.verify_range_proof(&proof, &start, &end, keys, vals)?; - } - Ok(()) -} - -#[test] -#[allow(clippy::indexing_slicing)] -// Tests normal range proof with both edge proofs -// as the existent proof, but with an extra empty value included, which is a -// noop technically, but practically should be rejected. -fn test_empty_value_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(512); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - // Create a new entry with a slightly modified key - let mid_index = items.len() / 2; - let key = increase_key(items[mid_index - 1].0); - let empty_value: [u8; 20] = [0; 20]; - items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); - - let start = 1; - let end = items.len() - 1; - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end - 1].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let item_iter = items.clone().into_iter().skip(start).take(end - start); - let keys = item_iter.clone().map(|item| item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - assert!(merkle - .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals) - .is_err()); - - Ok(()) -} - -#[test] -#[allow(clippy::indexing_slicing)] -// Tests the range proof with all elements, -// but with an extra empty value included, which is a noop technically, but -// practically should be rejected. -fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { - let set = fixed_and_pseudorandom_data(512); - let mut items = Vec::from_iter(set.iter()); - items.sort(); - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - // Create a new entry with a slightly modified key - let mid_index = items.len() / 2; - let key = increase_key(items[mid_index - 1].0); - let empty_value: [u8; 20] = [0; 20]; - items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); - - let start = 0; - let end = items.len() - 1; - - let mut proof = merkle.prove(items[start].0)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(items[end].0)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let item_iter = items.clone().into_iter(); - let keys = item_iter.clone().map(|item| item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - assert!(merkle - .verify_range_proof(&proof, items[start].0, items[end].0, keys, vals) - .is_err()); - - Ok(()) -} - -#[test] -fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { - let items = vec![ - ( - hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") - .expect("Decoding failed"), - hex::decode("02").expect("Decoding failed"), - ), - ( - hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") - .expect("Decoding failed"), - hex::decode("03").expect("Decoding failed"), - ), - ]; - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") - .expect("Decoding failed"); - let end = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - .expect("Decoding failed"); - - let mut proof = merkle.prove(&start)?; - assert!(!proof.0.is_empty()); - let end_proof = merkle.prove(&end)?; - assert!(!end_proof.0.is_empty()); - proof.extend(end_proof); - - let item_iter = items.into_iter(); - let keys = item_iter.clone().map(|item| item.0).collect(); - let vals = item_iter.map(|item| item.1).collect(); - - merkle.verify_range_proof(&proof, start, end, keys, vals)?; - - Ok(()) -} - -#[test] -#[allow(clippy::indexing_slicing)] -// Tests a malicious proof, where the proof is more or less the -// whole trie. This is to match corresponding test in geth. -fn test_bloadted_range_proof() -> Result<(), ProofError> { - // Use a small trie - let mut items = Vec::new(); - for i in 0..100_u32 { - let mut key: [u8; 32] = [0; 32]; - let mut value: [u8; 20] = [0; 20]; - for (index, d) in i.to_be_bytes().iter().enumerate() { - key[index] = *d; - value[index] = *d; - } - items.push((key, value)); - } - let merkle = merkle_build_test(items.clone(), 0x10000, 0x10000)?; - - // In the 'malicious' case, we add proofs for every single item - // (but only one key/value pair used as leaf) - let mut proof = Proof(HashMap::new()); - let mut keys = Vec::new(); - let mut vals = Vec::new(); - for (i, item) in items.iter().enumerate() { - let cur_proof = merkle.prove(item.0)?; - assert!(!cur_proof.0.is_empty()); - proof.extend(cur_proof); - if i == 50 { - keys.push(item.0); - vals.push(item.1); - } - } - - merkle.verify_range_proof(&proof, keys[0], keys[keys.len() - 1], keys, vals)?; - - Ok(()) -} - -// generate pseudorandom data, but prefix it with some known data -// The number of fixed data points is 100; you specify how much random data you want -#[allow(clippy::indexing_slicing)] -fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { - let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); - for i in 0..100_u32 { - let mut key: [u8; 32] = [0; 32]; - let mut value: [u8; 20] = [0; 20]; - for (index, d) in i.to_be_bytes().iter().enumerate() { - key[index] = *d; - value[index] = *d; - } - items.insert(key, value); - - let mut more_key: [u8; 32] = [0; 32]; - for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { - more_key[index] = *d; - } - items.insert(more_key, value); - } - - // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| thread_rng().gen()); - - // the test framework will only render this in verbose mode or if the test fails - // to re-run the test when it fails, just specify the seed instead of randomly - // selecting one - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let mut r = StdRng::seed_from_u64(seed); - for _ in 0..random_count { - let key = r.gen::<[u8; 32]>(); - let val = r.gen::<[u8; 20]>(); - items.insert(key, val); - } - items -} - -fn increase_key(key: &[u8; 32]) -> [u8; 32] { - let mut new_key = *key; - for ch in new_key.iter_mut().rev() { - let overflow; - (*ch, overflow) = ch.overflowing_add(1); - if !overflow { - break; - } - } - new_key -} - -fn decrease_key(key: &[u8; 32]) -> [u8; 32] { - let mut new_key = *key; - for ch in new_key.iter_mut().rev() { - let overflow; - (*ch, overflow) = ch.overflowing_sub(1); - if !overflow { - break; - } - } - new_key -} diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index 99f4b3e624ba..ab69c5bf7796 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -1,52 +1,45 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::Arc; - -use firewood::{ - db::{BatchOp, DbConfig}, - v2::api::{Db, DbView, Proposal}, -}; - pub mod common; -use common::TestDbCreator; - -#[tokio::test(flavor = "multi_thread")] -#[allow(clippy::unwrap_used)] -async fn smoke() -> Result<(), Box> { - let cfg = DbConfig::builder().truncate(true).build(); - let db = TestDbCreator::builder() - .cfg(cfg) - .test_name("smoke") - .build() - .create() - .await; - - let empty_hash = db.root_hash().await?; - assert_ne!(empty_hash, [0; 32]); - - // insert a single key/value - let (key, value) = (b"smoke", b"test"); - let batch_put = BatchOp::Put { key, value }; - let proposal = Arc::new(db.propose(vec![batch_put]).await?); - proposal.commit().await?; - - // ensure the latest hash is different - let latest = db.root_hash().await?; - assert_ne!(empty_hash, latest); - - // fetch the view of the latest - let view = db.revision(latest).await.unwrap(); - - // check that the key/value is there - let got_value = view.val(key).await.unwrap().unwrap(); - assert_eq!(got_value, value); - - // TODO: also fetch view of empty; this currently does not work, as you can't reference - // the empty hash - // let empty_view = db.revision(empty_hash).await.unwrap(); - // let value = empty_view.val(b"smoke").await.unwrap(); - // assert_eq!(value, None); - - Ok(()) -} + +// #[ignore = "unimplemented"] +// #[tokio::test(flavor = "multi_thread")] +// #[allow(clippy::unwrap_used)] +// async fn smoke() -> Result<(), Box> { +// let cfg = DbConfig::builder().truncate(true).build(); +// let db = TestDbCreator::builder() +// .cfg(cfg) +// .test_name("smoke") +// .build() +// .create() +// .await; + +// let empty_hash = db.root_hash().await?; +// assert_ne!(empty_hash, [0; 32]); + +// // insert a single key/value +// let (key, value) = (b"smoke", b"test"); +// let batch_put = BatchOp::Put { key, value }; +// let proposal = db.propose(vec![batch_put]).await?; +// proposal.commit().await?; + +// // ensure the latest hash is different +// let latest = db.root_hash().await?; +// assert_ne!(empty_hash, latest); + +// // fetch the view of the latest +// let view = db.revision(latest).await.unwrap(); + +// // check that the key/value is there +// let got_value = view.val(key).await.unwrap().unwrap(); +// assert_eq!(got_value, value); + +// // TODO: also fetch view of empty; this currently does not work, as you can't reference +// // the empty hash +// // let empty_view = db.revision(empty_hash).await.unwrap(); +// // let value = empty_view.val(b"smoke").await.unwrap(); +// // assert_eq!(value, None); + +// Ok(()) +// } diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index fff7afe68533..366c7da36622 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -3,7 +3,7 @@ use clap::{value_parser, Args}; use firewood::{ - db::{Db, DbConfig, DbRevConfig, DiskBufferConfig, WalConfig}, + db::{Db, DbConfig}, v2::api, }; @@ -17,110 +17,6 @@ pub struct Options { )] pub name: String, - #[arg( - long, - required = false, - default_value_t = 16384, - value_name = "META_NCACHED_PAGES", - help = "Maximum cached pages for the free list of the item stash." - )] - pub meta_ncached_pages: usize, - - #[arg( - long, - required = false, - default_value_t = 1024, - value_name = "META_NCACHED_FILES", - help = "Maximum cached file descriptors for the free list of the item stash." - )] - pub meta_ncached_files: usize, - - #[arg( - long, - required = false, - default_value_t = 22, - value_name = "META_FILE_NBIT", - help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - the power of 2 for the file size." - )] - pub meta_file_nbit: u64, - - #[arg( - long, - required = false, - default_value_t = 262144, - value_name = "PAYLOAD_NCACHED_PAGES", - help = "Maximum cached pages for the item stash. This is the low-level cache used by the linear - store that holds trie nodes and account objects." - )] - pub payload_ncached_pages: usize, - - #[arg( - long, - required = false, - default_value_t = 1024, - value_name = "PAYLOAD_NCACHED_FILES", - help = "Maximum cached file descriptors for the item stash." - )] - pub payload_ncached_files: usize, - - #[arg( - long, - required = false, - default_value_t = 22, - value_name = "PAYLOAD_FILE_NBIT", - help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - the power of 2 for the file size." - )] - pub payload_file_nbit: u64, - - #[arg( - long, - required = false, - default_value_t = 10, - value_name = "PAYLOAD_MAX_WALK", - help = "Maximum steps of walk to recycle a freed item." - )] - pub payload_max_walk: u64, - - #[arg( - long, - required = false, - default_value_t = 22, - value_name = "PAYLOAD_REGN_NBIT", - help = "Region size in bits (should be not greater than PAYLOAD_FILE_NBIT). One file is - partitioned into multiple regions. Suggested to use the default value." - )] - pub payload_regn_nbit: u64, - - #[arg( - long, - required = false, - default_value_t = 16384, - value_name = "ROOT_HASH_NCACHED_PAGES", - help = "Maximum cached pages for the free list of the item stash." - )] - pub root_hash_ncached_pages: usize, - - #[arg( - long, - required = false, - default_value_t = 1024, - value_name = "ROOT_HASH_NCACHED_FILES", - help = "Maximum cached file descriptors for the free list of the item stash." - )] - pub root_hash_ncached_files: usize, - - #[arg( - long, - required = false, - default_value_t = 22, - value_name = "ROOT_HASH_FILE_NBIT", - help = "Number of low-bits in the 64-bit address to determine the file ID. It is the exponent to - the power of 2 for the file size." - )] - pub root_hash_file_nbit: u64, - #[arg( long, required = false, @@ -133,87 +29,6 @@ pub struct Options { )] pub truncate: bool, - /// Revision options - #[arg( - long, - required = false, - default_value_t = 1 << 20, - value_name = "REV_MERKLE_NCACHED", - help = "Config for accessing a version of the DB. Maximum cached trie objects.")] - merkle_ncached_objs: usize, - - #[arg( - long, - required = false, - default_value_t = 4096, - value_name = "REV_BLOB_NCACHED", - help = "Maximum cached Blob objects." - )] - blob_ncached_objs: usize, - - #[arg( - long, - required = false, - default_value_t = 65536, - value_name = "DISK_BUFFER_MAX_PENDING", - help = "Maximum number of pending pages." - )] - max_pending: usize, - - #[arg( - long, - required = false, - default_value_t = 1024, - value_name = "DISK_BUFFER_MAX_AIO_REQUESTS", - help = "Maximum number of concurrent async I/O requests." - )] - max_aio_requests: u32, - - #[arg( - long, - required = false, - default_value_t = 128, - value_name = "DISK_BUFFER_MAX_AIO_RESPONSE", - help = "Maximum number of async I/O responses that firewood polls for at a time." - )] - max_aio_response: u16, - - #[arg( - long, - required = false, - default_value_t = 128, - value_name = "DISK_BUFFER_MAX_AIO_SUBMIT", - help = "Maximum number of async I/O requests per submission." - )] - max_aio_submit: usize, - - #[arg( - long, - required = false, - default_value_t = 256, - value_name = "DISK_BUFFER_WAL_MAX_AIO_REQUESTS", - help = "Maximum number of concurrent async I/O requests in WAL." - )] - wal_max_aio_requests: usize, - - #[arg( - long, - required = false, - default_value_t = 1024, - value_name = "DISK_BUFFER_WAL_MAX_BUFFERED", - help = "Maximum buffered WAL records." - )] - wal_max_buffered: usize, - - #[arg( - long, - required = false, - default_value_t = 4096, - value_name = "DISK_BUFFER_WAL_MAX_BATCH", - help = "Maximum batched WAL records per write." - )] - wal_max_batch: usize, - /// WAL Config #[arg( long, @@ -224,15 +39,6 @@ pub struct Options { )] file_nbit: u64, - #[arg( - long, - required = false, - default_value_t = 15, - value_name = "WAL_BLOCK_NBIT", - help = "Size of WAL blocks." - )] - block_nbit: u64, - #[arg( long, required = false, @@ -244,45 +50,15 @@ pub struct Options { max_revisions: u32, } -pub(super) const fn initialize_db_config(opts: &Options) -> DbConfig { - DbConfig { - meta_ncached_pages: opts.meta_ncached_pages, - meta_ncached_files: opts.meta_ncached_files, - meta_file_nbit: opts.meta_file_nbit, - payload_ncached_pages: opts.payload_ncached_pages, - payload_ncached_files: opts.payload_ncached_files, - payload_file_nbit: opts.payload_file_nbit, - payload_max_walk: opts.payload_max_walk, - payload_regn_nbit: opts.payload_regn_nbit, - root_hash_ncached_pages: opts.payload_ncached_pages, - root_hash_ncached_files: opts.root_hash_ncached_files, - root_hash_file_nbit: opts.root_hash_file_nbit, - truncate: opts.truncate, - rev: DbRevConfig { - merkle_ncached_objs: opts.merkle_ncached_objs, - }, - buffer: DiskBufferConfig { - max_pending: opts.max_pending, - max_aio_requests: opts.max_aio_requests, - max_aio_response: opts.max_aio_response, - max_aio_submit: opts.max_aio_submit, - wal_max_aio_requests: opts.wal_max_aio_requests, - wal_max_buffered: opts.wal_max_buffered, - wal_max_batch: opts.wal_max_batch, - }, - wal: WalConfig { - file_nbit: opts.file_nbit, - block_nbit: opts.block_nbit, - max_revisions: opts.max_revisions, - }, - } +pub(super) fn new(opts: &Options) -> DbConfig { + DbConfig::builder().truncate(opts.truncate).build() } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - let db_config = initialize_db_config(opts); + let db_config = new(opts); log::debug!("database configuration parameters: \n{:?}\n", db_config); - Db::new(opts.name.clone(), &db_config).await?; + Db::new(opts.name.clone(), db_config).await?; println!("created firewood database in {:?}", opts.name); Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index d16caa33771f..8e8d95ff7360 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -1,13 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::Arc; - use clap::Args; -use firewood::{ - db::{BatchOp, Db, DbConfig, WalConfig}, - v2::api::{self, Db as _, Proposal}, -}; #[derive(Debug, Args)] pub struct Options { @@ -26,20 +20,18 @@ pub struct Options { pub db: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("deleting key {:?}", opts); - let cfg = DbConfig::builder() - .truncate(false) - .wal(WalConfig::builder().max_revisions(10).build()); +// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +// log::debug!("deleting key {:?}", opts); +// let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), &cfg.build()).await?; +// let db = Db::new(opts.db.clone(), cfg.build()).await?; - let batch: Vec> = vec![BatchOp::Delete { - key: opts.key.clone(), - }]; - let proposal = Arc::new(db.propose(batch).await?); - proposal.commit().await?; +// let batch: Vec> = vec![BatchOp::Delete { +// key: opts.key.clone(), +// }]; +// let proposal = db.propose(batch).await?; +// proposal.commit().await?; - println!("key {} deleted successfully", opts.key); - Ok(()) -} +// println!("key {} deleted successfully", opts.key); +// Ok(()) +// } diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index be601e14670b..2d4ce162387a 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -2,12 +2,7 @@ // See the file LICENSE.md for licensing terms. use clap::Args; -use firewood::{ - db::{Db, DbConfig, WalConfig}, - merkle::Key, - v2::api::{self, Db as _}, -}; -use futures_util::StreamExt; +use firewood::merkle::Key; use std::borrow::Cow; #[derive(Debug, Args)] @@ -32,30 +27,28 @@ pub struct Options { pub start_key: Option, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("dump database {:?}", opts); - let cfg = DbConfig::builder() - .truncate(false) - .wal(WalConfig::builder().max_revisions(10).build()); +// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +// log::debug!("dump database {:?}", opts); +// let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), &cfg.build()).await?; - let latest_hash = db.root_hash().await?; - let latest_rev = db.revision(latest_hash).await?; - let start_key = opts.start_key.clone().unwrap_or(Box::new([])); - let mut stream = latest_rev.stream_from(start_key); - loop { - match stream.next().await { - None => break, - Some(Ok((key, value))) => { - println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); - } - Some(Err(e)) => return Err(e), - } - } - Ok(()) -} +// let db = Db::new(opts.db.clone(), cfg.build()).await?; +// let latest_hash = db.root_hash().await?; +// let latest_rev = db.revision(latest_hash).await?; +// let start_key = opts.start_key.clone().unwrap_or(Box::new([])); +// let mut stream = latest_rev.stream_from(&start_key); +// loop { +// match stream.next().await { +// None => break, +// Some(Ok((key, value))) => { +// println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); +// } +// Some(Err(e)) => return Err(e), +// } +// } +// Ok(()) +// } -fn u8_to_string(data: &[u8]) -> Cow<'_, str> { +fn _u8_to_string(data: &[u8]) -> Cow<'_, str> { String::from_utf8_lossy(data) } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index c43fca80c732..4f0bf57ede8c 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -2,10 +2,6 @@ // See the file LICENSE.md for licensing terms. use clap::Args; -use firewood::{ - db::{Db, DbConfig, WalConfig}, - v2::api::{self, Db as _, DbView}, -}; use std::str; #[derive(Debug, Args)] @@ -25,26 +21,24 @@ pub struct Options { pub db: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("get key value pair {:?}", opts); - let cfg = DbConfig::builder() - .truncate(false) - .wal(WalConfig::builder().max_revisions(10).build()); +// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +// log::debug!("get key value pair {:?}", opts); +// let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), &cfg.build()).await?; +// let db = Db::new(opts.db.clone(), cfg.build()).await?; - let rev = db.revision(db.root_hash().await?).await?; +// let rev = db.revision(db.root_hash().await?).await?; - match rev.val(opts.key.as_bytes()).await { - Ok(Some(val)) => { - let s = String::from_utf8_lossy(val.as_ref()); - println!("{s:?}"); - Ok(()) - } - Ok(None) => { - eprintln!("Key '{}' not found", opts.key); - Ok(()) - } - Err(e) => Err(e), - } -} +// match rev.val(opts.key.as_bytes()).await { +// Ok(Some(val)) => { +// let s = String::from_utf8_lossy(val.as_ref()); +// println!("{s:?}"); +// Ok(()) +// } +// Ok(None) => { +// eprintln!("Key '{}' not found", opts.key); +// Ok(()) +// } +// Err(e) => Err(e), +// } +// } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 09959a939a16..77d3bc96072c 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -1,13 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::Arc; - use clap::Args; -use firewood::{ - db::{BatchOp, Db, DbConfig, WalConfig}, - v2::api::{self, Db as _, Proposal}, -}; #[derive(Debug, Args)] pub struct Options { @@ -30,21 +24,19 @@ pub struct Options { pub db: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("inserting key value pair {:?}", opts); - let cfg = DbConfig::builder() - .truncate(false) - .wal(WalConfig::builder().max_revisions(10).build()); +// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +// log::debug!("inserting key value pair {:?}", opts); +// let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), &cfg.build()).await?; +// let db = Db::new(opts.db.clone(), cfg.build()).await?; - let batch: Vec, Vec>> = vec![BatchOp::Put { - key: opts.key.clone().into(), - value: opts.value.bytes().collect(), - }]; - let proposal = Arc::new(db.propose(batch).await?); - proposal.commit().await?; +// let batch: Vec, Vec>> = vec![BatchOp::Put { +// key: opts.key.clone().into(), +// value: opts.value.bytes().collect(), +// }]; +// let proposal = db.propose(batch).await?; +// proposal.commit().await?; - println!("{}", opts.key); - Ok(()) -} +// println!("{}", opts.key); +// Ok(()) +// } diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index fca9d6d873b3..b360d05fd13a 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -57,10 +57,11 @@ async fn main() -> Result<(), api::Error> { match &cli.command { Commands::Create(opts) => create::run(opts).await, - Commands::Insert(opts) => insert::run(opts).await, - Commands::Get(opts) => get::run(opts).await, - Commands::Delete(opts) => delete::run(opts).await, - Commands::Root(opts) => root::run(opts).await, - Commands::Dump(opts) => dump::run(opts).await, + _ => todo!(), + //Commands::Insert(opts) => insert::run(opts).await, + // Commands::Get(opts) => get::run(opts).await, + //Commands::Delete(opts) => delete::run(opts).await, + //Commands::Root(opts) => root::run(opts).await, + //Commands::Dump(opts) => dump::run(opts).await, } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index ec4da10a17cd..58af03e90f26 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -2,11 +2,6 @@ // See the file LICENSE.md for licensing terms. use clap::Args; -use firewood::v2::api::Db as _; -use firewood::{ - db::{Db, DbConfig, WalConfig}, - v2::api, -}; use std::str; #[derive(Debug, Args)] @@ -22,15 +17,13 @@ pub struct Options { pub db: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("root hash {:?}", opts); - let cfg = DbConfig::builder() - .truncate(false) - .wal(WalConfig::builder().max_revisions(10).build()); +// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +// log::debug!("root hash {:?}", opts); +// let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), &cfg.build()).await?; +// let db = Db::new(opts.db.clone(), cfg.build()).await?; - let root = db.root_hash().await?; - println!("{root:X?}"); - Ok(()) -} +// let root = db.root_hash().await?; +// println!("{root:X?}"); +// Ok(()) +// } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 89c4d4f51991..7223213ad95e 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -35,6 +35,7 @@ fn fwdctl_prints_version() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_creates_database() -> Result<()> { @@ -49,6 +50,7 @@ fn fwdctl_creates_database() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_insert_successful() -> Result<()> { @@ -75,6 +77,7 @@ fn fwdctl_insert_successful() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_get_successful() -> Result<()> { @@ -110,6 +113,7 @@ fn fwdctl_get_successful() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_delete_successful() -> Result<()> { @@ -144,6 +148,7 @@ fn fwdctl_delete_successful() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_root_hash() -> Result<()> { @@ -177,6 +182,7 @@ fn fwdctl_root_hash() -> Result<()> { Ok(()) } +#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_dump() -> Result<()> { diff --git a/growth-ring/Cargo.toml b/growth-ring/Cargo.toml deleted file mode 100644 index ab38d269227a..000000000000 --- a/growth-ring/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "growth-ring" -version = "0.0.4" -edition = "2021" -keywords = ["wal", "db", "futures"] -license = "../LICENSE.md" -description = "Simple and modular write-ahead-logging implementation." - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -lru = "0.12.2" -scan_fmt = "0.2.6" -regex = "1.10.3" -async-trait = "0.1.77" -futures = "0.3.30" -nix = {version = "0.28.0", features = ["fs", "uio"]} -libc = "0.2.153" -bytemuck = {version = "1.14.3", features = ["derive"]} -thiserror = "1.0.57" -tokio = { version = "1.36.0", features = ["fs", "io-util", "sync"] } -crc32fast = "1.4.0" -strum_macros = "0.26.1" - -[dev-dependencies] -hex = "0.4.3" -rand = "0.8.5" -indexmap = "2.2.3" -tokio = { version = "1.36.0", features = ["tokio-macros", "rt", "macros"] } -test-case = "3.3.1" - -[lib] -name = "growthring" -path = "src/lib.rs" -crate-type = ["dylib", "rlib", "staticlib"] - -[lints.rust] -unsafe_code = "deny" - -[lints.clippy] -unwrap_used = "warn" -indexing_slicing = "warn" -explicit_deref_methods = "warn" -missing_const_for_fn = "warn" diff --git a/growth-ring/README.md b/growth-ring/README.md deleted file mode 100644 index a6f1035573b8..000000000000 --- a/growth-ring/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# growth-ring - -This crate was forked from [`growth-ring`](https://github.com/Determinant/growth-ring) at commit [`8a1d4b9`](https://github.com/Determinant/growth-ring/commit/8a1d4b9d3a06449b02127645c2110accc9d5390d), under MIT license. \ No newline at end of file diff --git a/growth-ring/examples/.gitignore b/growth-ring/examples/.gitignore deleted file mode 100644 index 7d977f4aac48..000000000000 --- a/growth-ring/examples/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -demo -testdb diff --git a/growth-ring/examples/demo1.rs b/growth-ring/examples/demo1.rs deleted file mode 100644 index 0c4616f9c7f4..000000000000 --- a/growth-ring/examples/demo1.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use futures::executor::block_on; -use growthring::{ - wal::{WalBytes, WalLoader, WalRingId, WalWriter}, - walerror::WalError, - WalFileImpl, WalStoreImpl, -}; -use rand::{seq::SliceRandom, Rng, SeedableRng}; - -fn test( - records: Vec, - wal: &mut WalWriter, -) -> Result, ()> { - let mut res = Vec::new(); - for r in wal.grow(records).into_iter() { - let ring_id = futures::executor::block_on(r)?.1; - println!("got ring id: {:?}", ring_id); - res.push(ring_id); - } - Ok(res) -} - -fn recover(payload: WalBytes, ringid: WalRingId) -> Result<(), WalError> { - println!( - "recover(payload={}, ringid={:?}", - std::str::from_utf8(&payload).map_err(|e| WalError::Other(e.to_string()))?, - ringid - ); - Ok(()) -} - -fn main() -> Result<(), WalError> { - let wal_dir = "./wal_demo1"; - let mut rng = rand::rngs::StdRng::seed_from_u64(0); - let mut loader = WalLoader::new(); - loader.file_nbit(9).block_nbit(8); - - let store = WalStoreImpl::new(wal_dir, true)?; - let mut wal = block_on(loader.load(store, recover, 0))?; - for _ in 0..3 { - let _ = test( - ["hi", "hello", "lol"] - .iter() - .map(|s| s.to_string()) - .collect::>(), - &mut wal, - ); - } - for _ in 0..3 { - let _ = test( - vec!["a".repeat(10), "b".repeat(100), "c".repeat(1000)], - &mut wal, - ); - } - - let store = WalStoreImpl::new(wal_dir, false)?; - let mut wal = block_on(loader.load(store, recover, 0))?; - for _ in 0..3 { - let _ = test( - vec![ - "a".repeat(10), - "b".repeat(100), - "c".repeat(300), - "d".repeat(400), - ], - &mut wal, - ); - } - - let store = WalStoreImpl::new(wal_dir, false)?; - let mut wal = block_on(loader.load(store, recover, 100))?; - let mut history = std::collections::VecDeque::new(); - for _ in 0..3 { - let mut ids = Vec::new(); - for _ in 0..3 { - let mut records = Vec::new(); - for _ in 0..100 { - let rec = "a".repeat(rng.gen_range(1..1000)); - history.push_back(rec.clone()); - if history.len() > 100 { - history.pop_front(); - } - records.push(rec) - } - for id in test(records, &mut wal) - .map_err(|_| WalError::Other("test failed".to_string()))? - .iter() - { - ids.push(*id) - } - } - ids.shuffle(&mut rng); - for e in ids.chunks(20) { - println!("peel(20)"); - futures::executor::block_on(wal.peel(e, 100))?; - } - } - for (rec, ans) in - block_on(wal.read_recent_records(100, &growthring::wal::RecoverPolicy::Strict))? - .into_iter() - .zip(history.into_iter().rev()) - { - assert_eq!(&String::from_utf8_lossy(&rec), &ans); - println!("{}", String::from_utf8_lossy(&rec)); - } - - Ok(()) -} diff --git a/growth-ring/src/lib.rs b/growth-ring/src/lib.rs deleted file mode 100644 index b27844f69399..000000000000 --- a/growth-ring/src/lib.rs +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. -// -//! Simple and modular write-ahead-logging implementation. -//! -//! # Examples -//! -//! ```no_run -//! use growthring::{WalStoreImpl, wal::WalLoader}; -//! use futures::executor::block_on; -//! let mut loader = WalLoader::new(); -//! loader.file_nbit(9).block_nbit(8); -//! -//! -//! // Start with empty WAL (truncate = true). -//! let store = WalStoreImpl::new("/tmp/walfiles", true).unwrap(); -//! let mut wal = block_on(loader.load(store, |_, _| {Ok(())}, 0)).unwrap(); -//! // Write a vector of records to WAL. -//! for f in wal.grow(vec!["record1(foo)", "record2(bar)", "record3(foobar)"]).into_iter() { -//! let ring_id = block_on(f).unwrap().1; -//! println!("WAL recorded record to {:?}", ring_id); -//! } -//! -//! -//! // Load from WAL (truncate = false). -//! let store = WalStoreImpl::new("/tmp/walfiles", false).unwrap(); -//! let mut wal = block_on(loader.load(store, |payload, ringid| { -//! // redo the operations in your application -//! println!("recover(payload={}, ringid={:?})", -//! std::str::from_utf8(&payload).unwrap(), -//! ringid); -//! Ok(()) -//! }, 0)).unwrap(); -//! // We saw some log playback, even there is no failure. -//! // Let's try to grow the WAL to create many files. -//! let ring_ids = wal.grow((1..100).into_iter().map(|i| "a".repeat(i)).collect::>()) -//! .into_iter().map(|f| block_on(f).unwrap().1).collect::>(); -//! // Then assume all these records are not longer needed. We can tell WalWriter by the `peel` -//! // method. -//! block_on(wal.peel(ring_ids, 0)).unwrap(); -//! // There will only be one remaining file in /tmp/walfiles. -//! -//! let store = WalStoreImpl::new("/tmp/walfiles", false).unwrap(); -//! let wal = block_on(loader.load(store, |payload, _| { -//! println!("payload.len() = {}", payload.len()); -//! Ok(()) -//! }, 0)).unwrap(); -//! // After each recovery, the /tmp/walfiles is empty. -//! ``` - -pub mod wal; -pub mod walerror; - -use async_trait::async_trait; -use std::fs; -use std::io::SeekFrom; -use std::path::{Path, PathBuf}; -use tokio::{ - fs::{File, OpenOptions}, - io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, - sync::Mutex, -}; -use wal::{WalBytes, WalFile, WalPos, WalStore}; -use walerror::WalError; - -struct RawWalFile(File); - -impl RawWalFile { - pub async fn open>(path: P) -> Result { - OpenOptions::new() - .read(true) - .write(true) - .truncate(false) - .create(true) - .mode(0o600) - .open(path) - .await - .map(Self) - } -} - -pub struct WalFileImpl { - file_mutex: Mutex, -} - -impl From for WalFileImpl { - fn from(file: RawWalFile) -> Self { - let file = Mutex::new(file); - Self { file_mutex: file } - } -} - -#[async_trait(?Send)] -impl WalFile for WalFileImpl { - async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { - self.file_mutex - .lock() - .await - .0 - .set_len(offset + length as u64) - .await - .map_err(Into::into) - } - - async fn truncate(&self, len: usize) -> Result<(), WalError> { - self.file_mutex - .lock() - .await - .0 - .set_len(len as u64) - .await - .map_err(Into::into) - } - - async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { - let file = &mut self.file_mutex.lock().await.0; - file.seek(SeekFrom::Start(offset)).await?; - - Ok(file.write_all(&data).await?) - } - - async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { - let (result, bytes_read) = { - let mut result = Vec::with_capacity(length); - let file = &mut self.file_mutex.lock().await.0; - file.seek(SeekFrom::Start(offset)).await?; - let bytes_read = file.read_buf(&mut result).await?; - (result, bytes_read) - }; - - let result = Some(result) - .filter(|_| bytes_read == length) - .map(Vec::into_boxed_slice); - - Ok(result) - } -} - -pub struct WalStoreImpl { - root_dir: PathBuf, -} - -impl WalStoreImpl { - pub fn new>(wal_dir: P, truncate: bool) -> Result { - if truncate { - if let Err(e) = fs::remove_dir_all(&wal_dir) { - if e.kind() != std::io::ErrorKind::NotFound { - return Err(From::from(e)); - } - } - fs::create_dir(&wal_dir)?; - } else if !wal_dir.as_ref().exists() { - // create Wal dir - fs::create_dir(&wal_dir)?; - } - - Ok(WalStoreImpl { - root_dir: wal_dir.as_ref().to_path_buf(), - }) - } -} - -#[async_trait(?Send)] -impl WalStore for WalStoreImpl { - type FileNameIter = std::vec::IntoIter; - - async fn open_file(&self, filename: &str, _touch: bool) -> Result { - let path = self.root_dir.join(filename); - - let file = RawWalFile::open(path).await?; - - Ok(file.into()) - } - - async fn remove_file(&self, filename: String) -> Result<(), WalError> { - let file_to_remove = self.root_dir.join(filename); - fs::remove_file(file_to_remove).map_err(From::from) - } - - fn enumerate_files(&self) -> Result { - let mut filenames = Vec::new(); - #[allow(clippy::unwrap_used)] - for path in fs::read_dir(&self.root_dir)?.filter_map(|entry| entry.ok()) { - filenames.push(path.path()); - } - Ok(filenames.into_iter()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn truncation_makes_a_file_smaller() { - const HALF_LENGTH: usize = 512; - - let walfile_path = get_temp_walfile_path(file!(), line!()); - - tokio::fs::remove_file(&walfile_path).await.ok(); - - #[allow(clippy::unwrap_used)] - let walfile = RawWalFile::open(walfile_path).await.unwrap(); - - let walfile_impl = WalFileImpl::from(walfile); - - let first_half = vec![1u8; HALF_LENGTH]; - let second_half = vec![2u8; HALF_LENGTH]; - - let data = first_half - .iter() - .copied() - .chain(second_half.iter().copied()) - .collect(); - - #[allow(clippy::unwrap_used)] - walfile_impl.write(0, data).await.unwrap(); - #[allow(clippy::unwrap_used)] - walfile_impl.truncate(HALF_LENGTH).await.unwrap(); - - #[allow(clippy::unwrap_used)] - let result = walfile_impl.read(0, HALF_LENGTH).await.unwrap(); - - assert_eq!(result, Some(first_half.into())) - } - - #[tokio::test] - async fn truncation_extends_a_file_with_zeros() { - const LENGTH: usize = 512; - - let walfile_path = get_temp_walfile_path(file!(), line!()); - - tokio::fs::remove_file(&walfile_path).await.ok(); - - #[allow(clippy::unwrap_used)] - let walfile = RawWalFile::open(walfile_path).await.unwrap(); - - let walfile_impl = WalFileImpl::from(walfile); - - #[allow(clippy::unwrap_used)] - walfile_impl - .write(0, vec![1u8; LENGTH].into()) - .await - .unwrap(); - - #[allow(clippy::unwrap_used)] - walfile_impl.truncate(2 * LENGTH).await.unwrap(); - - #[allow(clippy::unwrap_used)] - let result = walfile_impl.read(LENGTH as u64, LENGTH).await.unwrap(); - - assert_eq!(result, Some(vec![0u8; LENGTH].into())) - } - - #[tokio::test] - async fn write_and_read_full() { - let walfile = { - let walfile_path = get_temp_walfile_path(file!(), line!()); - tokio::fs::remove_file(&walfile_path).await.ok(); - #[allow(clippy::unwrap_used)] - RawWalFile::open(walfile_path).await.unwrap() - }; - - let walfile_impl = WalFileImpl::from(walfile); - - let data: Vec = (0..=u8::MAX).collect(); - - #[allow(clippy::unwrap_used)] - walfile_impl.write(0, data.clone().into()).await.unwrap(); - - #[allow(clippy::unwrap_used)] - let result = walfile_impl.read(0, data.len()).await.unwrap(); - - assert_eq!(result, Some(data.into())); - } - - #[tokio::test] - async fn write_and_read_subset() { - let walfile = { - let walfile_path = get_temp_walfile_path(file!(), line!()); - tokio::fs::remove_file(&walfile_path).await.ok(); - #[allow(clippy::unwrap_used)] - RawWalFile::open(walfile_path).await.unwrap() - }; - - let walfile_impl = WalFileImpl::from(walfile); - - let data: Vec = (0..=u8::MAX).collect(); - #[allow(clippy::unwrap_used)] - walfile_impl.write(0, data.clone().into()).await.unwrap(); - - let mid = data.len() / 2; - let (start, end) = data.split_at(mid); - #[allow(clippy::unwrap_used)] - let read_start_result = walfile_impl.read(0, mid).await.unwrap(); - #[allow(clippy::unwrap_used)] - let read_end_result = walfile_impl.read(mid as u64, mid).await.unwrap(); - - assert_eq!(read_start_result, Some(start.into())); - assert_eq!(read_end_result, Some(end.into())); - } - - #[tokio::test] - async fn write_and_read_beyond_len() { - let walfile = { - let walfile_path = get_temp_walfile_path(file!(), line!()); - tokio::fs::remove_file(&walfile_path).await.ok(); - #[allow(clippy::unwrap_used)] - RawWalFile::open(walfile_path).await.unwrap() - }; - - let walfile_impl = WalFileImpl::from(walfile); - - let data: Vec = (0..=u8::MAX).collect(); - - #[allow(clippy::unwrap_used)] - walfile_impl.write(0, data.clone().into()).await.unwrap(); - - #[allow(clippy::unwrap_used)] - let result = walfile_impl - .read((data.len() / 2) as u64, data.len()) - .await - .unwrap(); - - assert_eq!(result, None); - } - - #[tokio::test] - async fn write_at_offset() { - const OFFSET: u64 = 2; - - let walfile = { - let walfile_path = get_temp_walfile_path(file!(), line!()); - tokio::fs::remove_file(&walfile_path).await.ok(); - #[allow(clippy::unwrap_used)] - RawWalFile::open(walfile_path).await.unwrap() - }; - - let walfile_impl = WalFileImpl::from(walfile); - - let data: Vec = (0..=u8::MAX).collect(); - - #[allow(clippy::unwrap_used)] - walfile_impl - .write(OFFSET, data.clone().into()) - .await - .unwrap(); - - #[allow(clippy::unwrap_used)] - let result = walfile_impl - .read(0, data.len() + OFFSET as usize) - .await - .unwrap(); - - let data: Vec<_> = std::iter::repeat(0) - .take(OFFSET as usize) - .chain(data) - .collect(); - - assert_eq!(result, Some(data.into())); - } - - #[allow(clippy::unwrap_used)] - fn get_temp_walfile_path(file: &str, line: u32) -> PathBuf { - let path = option_env!("CARGO_TARGET_TMPDIR") - .map(PathBuf::from) - .unwrap_or(std::env::temp_dir()); - path.join(format!("{}_{}", file.replace('/', "-"), line)) - } -} diff --git a/growth-ring/src/wal.rs b/growth-ring/src/wal.rs deleted file mode 100644 index cc984b8b4d65..000000000000 --- a/growth-ring/src/wal.rs +++ /dev/null @@ -1,1363 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use async_trait::async_trait; -use bytemuck::{cast_slice, Pod, Zeroable}; -use crc32fast::Hasher; -use futures::{ - future::{self, FutureExt, TryFutureExt}, - stream::StreamExt, - Future, -}; - -use std::mem::MaybeUninit; -use std::num::NonZeroUsize; -use std::pin::Pin; -use std::{ - cell::{RefCell, UnsafeCell}, - ffi::OsStr, - path::{Path, PathBuf}, -}; -use std::{ - collections::{hash_map, BinaryHeap, HashMap, VecDeque}, - marker::PhantomData, -}; - -use strum_macros::FromRepr; - -pub use crate::walerror::WalError; - -#[derive(Debug, FromRepr)] -enum WalRingType { - Null = 0x0, - Full, - First, - Middle, - Last, -} - -#[repr(C, packed)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -struct WalRingBlob { - counter: u32, - crc32: u32, - rsize: u32, - rtype: u8, - // payload follows -} - -type WalFileId = u64; -pub type WalBytes = Box<[u8]>; -pub type WalPos = u64; - -// convert XXXXXX.log into number from the XXXXXX (in hex) -fn get_fid(fname: &Path) -> Result { - let wal_err: WalError = WalError::Other("not a log file".to_string()); - - if fname.extension() != Some(OsStr::new("log")) { - return Err(wal_err); - } - - u64::from_str_radix( - fname - .file_stem() - .unwrap_or(OsStr::new("")) - .to_str() - .unwrap_or(""), - 16, - ) - .map_err(|_| wal_err) -} - -fn get_fname(fid: WalFileId) -> String { - format!("{:08x}.log", fid) -} - -fn sort_fids(file_nbit: u64, mut fids: Vec) -> Vec<(u8, u64)> { - let (min, max) = fids.iter().fold((u64::MAX, u64::MIN), |acc, fid| { - ((*fid).min(acc.0), (*fid).max(acc.1)) - }); - let fid_half = u64::MAX >> (file_nbit + 1); - if max > min && max - min > fid_half { - // we treat this as u64 overflow has happened, take proper care here - let mut aux: Vec<_> = fids - .into_iter() - .map(|fid| (if fid < fid_half { 1 } else { 0 }, fid)) - .collect(); - aux.sort(); - aux - } else { - fids.sort(); - fids.into_iter().map(|fid| (0, fid)).collect() - } -} - -const fn counter_lt(a: u32, b: u32) -> bool { - if u32::abs_diff(a, b) > u32::MAX / 2 { - b < a - } else { - a < b - } -} - -#[repr(transparent)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -struct Header { - /// all preceding files ((); - -#[repr(C)] -#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] -pub struct WalRingId { - start: WalPos, - end: WalPos, - counter: u32, -} - -impl Default for WalRingId { - fn default() -> Self { - Self::empty_id() - } -} - -impl WalRingId { - pub const fn empty_id() -> Self { - WalRingId { - start: 0, - end: 0, - counter: 0, - } - } - pub const fn get_start(&self) -> WalPos { - self.start - } - pub const fn get_end(&self) -> WalPos { - self.end - } -} - -impl Ord for WalRingId { - fn cmp(&self, other: &WalRingId) -> std::cmp::Ordering { - other - .start - .cmp(&self.start) - .then_with(|| other.end.cmp(&self.end)) - } -} - -impl PartialOrd for WalRingId { - fn partial_cmp(&self, other: &WalRingId) -> Option { - Some(self.cmp(other)) - } -} - -pub trait Record { - fn serialize(&self) -> WalBytes; -} - -impl Record for WalBytes { - fn serialize(&self) -> WalBytes { - self[..].into() - } -} - -impl Record for String { - fn serialize(&self) -> WalBytes { - self.as_bytes().into() - } -} - -impl Record for &str { - fn serialize(&self) -> WalBytes { - self.as_bytes().into() - } -} - -/// the state for a WAL writer -struct WalState { - /// the next position for a record, addressed in the entire Wal space - next: WalPos, - /// number of bits for a file - file_nbit: u64, - next_complete: WalRingId, - counter: u32, - io_complete: BinaryHeap, - pending_removal: VecDeque<(WalFileId, u32)>, -} - -#[async_trait(?Send)] -pub trait WalFile { - /// Initialize the file store in [offset, offset + length) to zero. - async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError>; - /// Write data with offset. We assume all previous `allocate`/`truncate` invocations are visible - /// if ordered earlier (should be guaranteed by most OS). Additionally, the write caused - /// by each invocation of this function should be _atomic_ (the entire single write should be - /// all or nothing). - async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError>; - /// Read data with offset. Return `Ok(None)` when it reaches EOF. - async fn read(&self, offset: WalPos, length: usize) -> Result, WalError>; - /// Truncate a file to a specified length. - async fn truncate(&self, length: usize) -> Result<(), WalError>; -} - -#[async_trait(?Send)] -pub trait WalStore { - type FileNameIter: Iterator; - - /// Open a file given the filename, create the file if not exists when `touch` is `true`. - async fn open_file(&self, filename: &str, touch: bool) -> Result; - /// Unlink a file given the filename. - async fn remove_file(&self, filename: String) -> Result<(), WalError>; - /// Enumerate all Wal filenames. It should include all Wal files that are previously opened - /// (created) but not removed. The list could be unordered. - #[allow(clippy::result_unit_err)] - fn enumerate_files(&self) -> Result; -} - -struct WalFileHandle<'a, F: WalFile + 'static, S: WalStore> { - fid: WalFileId, - handle: &'a dyn WalFile, - pool: &'a WalFilePool, - wal_file: PhantomData, -} - -impl<'a, F: WalFile, S: WalStore> std::ops::Deref for WalFileHandle<'a, F, S> { - type Target = dyn WalFile + 'a; - fn deref(&self) -> &Self::Target { - self.handle - } -} - -impl<'a, F: WalFile + 'static, S: WalStore> Drop for WalFileHandle<'a, F, S> { - fn drop(&mut self) { - (self.pool).release_file(self.fid); - } -} - -/// The middle layer that manages WAL file handles and invokes public trait functions to actually -/// manipulate files and their contents. -struct WalFilePool> { - store: S, - header_file: F, - handle_cache: RefCell>>, - #[allow(clippy::type_complexity)] - handle_used: RefCell, usize)>>>, - #[allow(clippy::type_complexity)] - last_write: UnsafeCell>>>>>, - #[allow(clippy::type_complexity)] - last_peel: UnsafeCell>>>>>, - file_nbit: u64, - file_size: u64, - block_nbit: u64, -} - -impl> WalFilePool { - async fn new( - store: S, - file_nbit: u64, - block_nbit: u64, - cache_size: NonZeroUsize, - ) -> Result { - let header_file = store.open_file("HEAD", true).await?; - header_file.truncate(HEADER_SIZE).await?; - Ok(WalFilePool { - store, - header_file, - handle_cache: RefCell::new(lru::LruCache::new(cache_size)), - handle_used: RefCell::new(HashMap::new()), - last_write: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), - last_peel: UnsafeCell::new(MaybeUninit::new(Box::pin(future::ready(Ok(()))))), - file_nbit, - file_size: 1 << file_nbit, - block_nbit, - }) - } - - async fn read_header(&self) -> Result { - let bytes = self - .header_file - .read(0, HEADER_SIZE) - .await? - .ok_or(WalError::Other("EOF".to_string()))?; - let slice = cast_slice::<_, Header>(&bytes); - slice - .first() - .copied() - .ok_or(WalError::Other("short read".to_string())) - } - - async fn write_header(&self, header: Header) -> Result<(), WalError> { - self.header_file - .write(0, cast_slice(&[header]).into()) - .await?; - Ok(()) - } - - #[allow(clippy::await_holding_refcell_ref)] - // TODO: Refactor to remove mutable reference from being awaited. - async fn get_file(&self, fid: u64, touch: bool) -> Result, WalError> { - if let Some(h) = self.handle_cache.borrow_mut().pop(&fid) { - let handle = match self.handle_used.borrow_mut().entry(fid) { - #[allow(unsafe_code)] - hash_map::Entry::Vacant(e) => unsafe { - &*(*e.insert(UnsafeCell::new((h, 1))).get()).0 - }, - _ => unreachable!(), - }; - Ok(WalFileHandle { - fid, - handle, - pool: self, - wal_file: PhantomData, - }) - } else { - #[allow(unsafe_code)] - let v = unsafe { - &mut *match self.handle_used.borrow_mut().entry(fid) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => { - let file = self.store.open_file(&get_fname(fid), touch).await?; - e.insert(UnsafeCell::new((Box::new(file), 0))) - } - } - .get() - }; - v.1 += 1; - Ok(WalFileHandle { - fid, - handle: &*v.0, - pool: self, - wal_file: PhantomData, - }) - } - } - - fn release_file(&self, fid: WalFileId) { - match self.handle_used.borrow_mut().entry(fid) { - hash_map::Entry::Occupied(e) => { - #[allow(unsafe_code)] - let v = unsafe { &mut *e.get().get() }; - v.1 -= 1; - if v.1 == 0 { - self.handle_cache - .borrow_mut() - .put(fid, e.remove().into_inner().0); - } - } - _ => unreachable!(), - } - } - - #[allow(clippy::type_complexity)] - fn write<'a>( - &'a mut self, - writes: Vec<(WalPos, WalBytes)>, - ) -> Vec> + 'a>>> { - if writes.is_empty() { - return Vec::new(); - } - let file_size = self.file_size; - let file_nbit = self.file_nbit; - let meta: Vec<(u64, u64)> = writes - .iter() - .map(|(off, w)| ((*off) >> file_nbit, w.len() as u64)) - .collect(); - let mut files: Vec + 'a>>> = Vec::new(); - for &(fid, _) in meta.iter() { - files.push(Box::pin(self.get_file(fid, true)) as Pin + 'a>>) - } - #[allow(clippy::indexing_slicing)] - let mut fid = writes[0].0 >> file_nbit; - #[allow(clippy::indexing_slicing)] - let mut alloc_start = writes[0].0 & (self.file_size - 1); - #[allow(clippy::indexing_slicing)] - let mut alloc_end = alloc_start + writes[0].1.len() as u64; - #[allow(unsafe_code)] - let last_write = unsafe { - std::mem::replace(&mut *self.last_write.get(), std::mem::MaybeUninit::uninit()) - .assume_init() - }; - // pre-allocate the file store - let alloc = async move { - last_write.await?; - let mut last_h: Option< - Pin, WalError>> + 'a>>, - > = None; - for ((next_fid, wl), h) in meta.into_iter().zip(files) { - if let Some(lh) = last_h.take() { - if next_fid != fid { - lh.await? - .allocate(alloc_start, (alloc_end - alloc_start) as usize) - .await?; - last_h = Some(h); - alloc_start = 0; - alloc_end = alloc_start + wl; - fid = next_fid; - } else { - last_h = Some(lh); - alloc_end += wl; - } - } else { - last_h = Some(h); - } - } - if let Some(lh) = last_h { - lh.await? - .allocate(alloc_start, (alloc_end - alloc_start) as usize) - .await? - } - Ok(()) - }; - - let mut res = Vec::new(); - let mut prev = Box::pin(alloc) as Pin + 'a>>; - - for (off, w) in writes.into_iter() { - let f = self.get_file(off >> file_nbit, true); - let w = (async move { - prev.await?; - let f = f.await?; - f.write(off & (file_size - 1), w).await - }) - .shared(); - prev = Box::pin(w.clone()); - res.push(Box::pin(w) as Pin + 'a>>) - } - - #[allow(unsafe_code)] - unsafe { - (*self.last_write.get()) = MaybeUninit::new(std::mem::transmute::< - Pin + 'a>>, - Pin + 'static>>, - >(prev)) - } - res - } - - #[allow(clippy::type_complexity)] - fn remove_files<'a>( - &'a mut self, - state: &mut WalState, - keep_nrecords: u32, - ) -> impl Future> + 'a { - #[allow(unsafe_code)] - let last_peel = unsafe { - std::mem::replace(&mut *self.last_peel.get(), std::mem::MaybeUninit::uninit()) - .assume_init() - }; - - let mut removes: Vec>>>> = Vec::new(); - - #[allow(clippy::unwrap_used)] - while state.pending_removal.len() > 1 { - let (fid, counter) = state.pending_removal.front().unwrap(); - - if counter_lt(counter + keep_nrecords, state.counter) { - removes.push(self.store.remove_file(get_fname(*fid)) - as Pin + 'a>>); - state.pending_removal.pop_front(); - } else { - break; - } - } - - let p = async move { - last_peel.await.ok(); - - for r in removes.into_iter() { - r.await.ok(); - } - - Ok(()) - } - .shared(); - - #[allow(unsafe_code)] - unsafe { - (*self.last_peel.get()) = MaybeUninit::new(std::mem::transmute( - Box::pin(p.clone()) as Pin + 'a>> - )) - } - - p - } - - fn in_use_len(&self) -> usize { - self.handle_used.borrow().len() - } - - fn reset(&mut self) { - self.handle_cache.borrow_mut().clear(); - self.handle_used.borrow_mut().clear() - } -} - -pub struct WalWriter> { - state: WalState, - file_pool: WalFilePool, - block_buffer: WalBytes, - block_size: u32, - msize: usize, -} - -impl> WalWriter { - fn new(state: WalState, file_pool: WalFilePool) -> Self { - let mut b = Vec::new(); - let block_size = 1 << file_pool.block_nbit as u32; - let msize = std::mem::size_of::(); - b.resize(block_size as usize, 0); - WalWriter { - state, - file_pool, - block_buffer: b.into_boxed_slice(), - block_size, - msize, - } - } - - /// Submit a sequence of records to Wal. It returns a vector of futures, each of which - /// corresponds to one record. When a future resolves to `WalRingId`, it is guaranteed the - /// record is already logged. Then, after finalizing the changes encoded by that record to - /// the persistent storage, the caller can recycle the Wal files by invoking the given - /// `peel` with the given `WalRingId`s. Note: each serialized record should contain at least 1 - /// byte (empty record payload will result in assertion failure). - pub fn grow<'a, R: Record + 'a>( - &'a mut self, - records: Vec, - ) -> Vec> + 'a> { - let mut res = Vec::new(); - let mut writes = Vec::new(); - let msize = self.msize as u32; - // the global offest of the begining of the block - // the start of the unwritten data - let mut bbuff_start = self.state.next as u32 & (self.block_size - 1); - // the end of the unwritten data - let mut bbuff_cur = bbuff_start; - - for rec in records.iter() { - let bytes = rec.serialize(); - let mut rec = &bytes[..]; - let mut rsize = rec.len() as u32; - let mut ring_start = None; - assert!(rsize > 0); - - while rsize > 0 { - let remain = self.block_size - bbuff_cur; - - #[allow(clippy::indexing_slicing)] // TODO: remove this to reduce scope - if remain > msize { - let d = remain - msize; - let rs0 = self.state.next + (bbuff_cur - bbuff_start) as u64; - - #[allow(unsafe_code)] - let blob = unsafe { - #[allow(clippy::indexing_slicing)] - &mut *self.block_buffer[bbuff_cur as usize..] - .as_mut_ptr() - .cast::() - }; - - bbuff_cur += msize; - - if d >= rsize { - // the remaining rec fits in the block - let payload = rec; - blob.counter = self.state.counter; - - let mut hasher = Hasher::new(); - hasher.update(payload); - blob.crc32 = hasher.finalize(); - - blob.rsize = rsize; - - let (rs, rt) = if let Some(rs) = ring_start.take() { - self.state.counter += 1; - (rs, WalRingType::Last) - } else { - self.state.counter += 1; - (rs0, WalRingType::Full) - }; - - blob.rtype = rt as u8; - #[allow(clippy::indexing_slicing)] - self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] - .copy_from_slice(payload); - bbuff_cur += rsize; - rsize = 0; - let end = self.state.next + (bbuff_cur - bbuff_start) as u64; - - res.push(( - WalRingId { - start: rs, - end, - counter: blob.counter, - }, - Vec::new(), - )); - } else { - // the remaining block can only accommodate partial rec - #[allow(clippy::indexing_slicing)] - let payload = &rec[..d as usize]; - blob.counter = self.state.counter; - - let mut hasher = Hasher::new(); - hasher.update(payload); - blob.crc32 = hasher.finalize(); - - blob.rsize = d; - - blob.rtype = if ring_start.is_some() { - WalRingType::Middle - } else { - ring_start = Some(rs0); - WalRingType::First - } as u8; - - #[allow(clippy::indexing_slicing)] - self.block_buffer[bbuff_cur as usize..bbuff_cur as usize + payload.len()] - .copy_from_slice(payload); - bbuff_cur += d; - rsize -= d; - // TODO: not allowed: #[allow(clippy::indexing_slicing)] - rec = &rec[d as usize..]; - } - } else { - // add padding space by moving the point to the end of the block - bbuff_cur = self.block_size; - } - - if bbuff_cur == self.block_size { - #[allow(clippy::indexing_slicing)] - writes.push(( - self.state.next, - self.block_buffer[bbuff_start as usize..] - .to_vec() - .into_boxed_slice(), - )); - self.state.next += (self.block_size - bbuff_start) as u64; - bbuff_start = 0; - bbuff_cur = 0; - } - } - } - - if bbuff_cur > bbuff_start { - #[allow(clippy::indexing_slicing)] - writes.push(( - self.state.next, - self.block_buffer[bbuff_start as usize..bbuff_cur as usize] - .to_vec() - .into_boxed_slice(), - )); - - self.state.next += (bbuff_cur - bbuff_start) as u64; - } - - // mark the block info for each record - let mut i = 0; - - 'outer: for (j, (off, w)) in writes.iter().enumerate() { - let blk_s = *off; - let blk_e = blk_s + w.len() as u64; - - #[allow(clippy::indexing_slicing)] - while res[i].0.end <= blk_s { - i += 1; - - if i >= res.len() { - break 'outer; - } - } - - #[allow(clippy::indexing_slicing)] - while res[i].0.start < blk_e { - res[i].1.push(j); - - if res[i].0.end >= blk_e { - break; - } - - i += 1; - - if i >= res.len() { - break 'outer; - } - } - } - - let writes: Vec> = self - .file_pool - .write(writes) - .into_iter() - .map(move |f| f.shared()) - .collect(); - - res.into_iter() - .zip(records) - .map(|((ringid, blks), rec)| { - #[allow(clippy::indexing_slicing)] - future::try_join_all(blks.into_iter().map(|idx| writes[idx].clone())) - .or_else(|_| future::ready(Err(()))) - .and_then(move |_| future::ready(Ok((rec, ringid)))) - }) - .collect() - } - - /// Inform the `WalWriter` that some data writes are complete so that it could automatically - /// remove obsolete Wal files. The given list of `WalRingId` does not need to be ordered and - /// could be of arbitrary length. Use `0` for `keep_nrecords` if all obsolete Wal files - /// need to removed (the obsolete files do not affect the speed of recovery or correctness). - pub async fn peel>( - &mut self, - records: T, - keep_nrecords: u32, - ) -> Result<(), WalError> { - let msize = self.msize as u64; - let block_size = self.block_size as u64; - let state = &mut self.state; - - for rec in records.as_ref() { - state.io_complete.push(*rec); - } - - while let Some(s) = state.io_complete.peek().map(|&e| e.start) { - if s != state.next_complete.end { - break; - } - - #[allow(clippy::unwrap_used)] - let mut m = state.io_complete.pop().unwrap(); - let block_remain = block_size - (m.end & (block_size - 1)); - - if block_remain <= msize { - m.end += block_remain - } - - let fid = m.start >> state.file_nbit; - - match state.pending_removal.back_mut() { - Some(l) => { - if l.0 == fid { - l.1 = m.counter - } else { - for i in l.0 + 1..fid + 1 { - state.pending_removal.push_back((i, m.counter)) - } - } - } - None => state.pending_removal.push_back((fid, m.counter)), - } - - state.next_complete = m; - } - - self.file_pool.remove_files(state, keep_nrecords).await.ok(); - - Ok(()) - } - - pub fn file_pool_in_use(&self) -> usize { - self.file_pool.in_use_len() - } - - #[allow(clippy::unwrap_used)] - pub async fn read_recent_records<'a>( - &'a self, - nrecords: usize, - recover_policy: &RecoverPolicy, - ) -> Result, WalError> { - let file_pool = &self.file_pool; - let file_nbit = file_pool.file_nbit; - let block_size = 1 << file_pool.block_nbit; - let msize = std::mem::size_of::(); - - let logfiles = sort_fids( - file_nbit, - file_pool - .store - .enumerate_files()? - .flat_map(|s| get_fid(&s)) - .collect(), - ); - - let mut chunks: Option> = None; - let mut records = Vec::new(); - 'outer: for (_, fid) in logfiles.into_iter().rev() { - let f = file_pool.get_file(fid, false).await?; - let ring_stream = WalLoader::read_rings(&f, true, file_pool.block_nbit, recover_policy); - let mut off = fid << file_nbit; - let mut rings = Vec::new(); - futures::pin_mut!(ring_stream); - while let Some(ring) = ring_stream.next().await { - rings.push(ring); - } - for ring in rings.into_iter().rev() { - let ring = ring.map_err(|_| WalError::Other("error mapping ring".to_string()))?; - let (header, payload) = ring; - #[allow(clippy::unwrap_used)] - let payload = payload.unwrap(); - match WalRingType::from_repr(header.rtype as usize) { - Some(WalRingType::Full) => { - assert!(chunks.is_none()); - if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(WalError::InvalidChecksum); - } - off += header.rsize as u64; - records.push(payload); - } - Some(WalRingType::First) => { - if !WalLoader::verify_checksum_(&payload, header.crc32, recover_policy)? { - return Err(WalError::InvalidChecksum); - } - if let Some(mut chunks) = chunks.take() { - chunks.push(payload); - let mut acc = Vec::new(); - chunks.into_iter().rev().fold(&mut acc, |acc, v| { - acc.extend(v.iter()); - acc - }); - records.push(acc.into()); - } else { - unreachable!() - } - off += header.rsize as u64; - } - Some(WalRingType::Middle) => { - if let Some(chunks) = &mut chunks { - chunks.push(payload); - } else { - unreachable!() - } - off += header.rsize as u64; - } - Some(WalRingType::Last) => { - assert!(chunks.is_none()); - chunks = Some(vec![payload]); - off += header.rsize as u64; - } - Some(WalRingType::Null) => break, - None => match recover_policy { - RecoverPolicy::Strict => { - return Err(WalError::Other( - "invalid ring type - strict recovery requested".to_string(), - )) - } - RecoverPolicy::BestEffort => break 'outer, - }, - } - let block_remain = block_size - (off & (block_size - 1)); - if block_remain <= msize as u64 { - off += block_remain; - } - if records.len() >= nrecords { - break 'outer; - } - } - } - Ok(records) - } -} - -#[derive(Copy, Clone)] -pub enum RecoverPolicy { - /// all checksums must be correct, otherwise recovery fails - Strict, - /// stop recovering when hitting the first corrupted record - BestEffort, -} - -pub struct WalLoader { - file_nbit: u64, - block_nbit: u64, - cache_size: NonZeroUsize, - recover_policy: RecoverPolicy, -} - -impl Default for WalLoader { - #[allow(clippy::unwrap_used)] - fn default() -> Self { - WalLoader { - file_nbit: 22, // 4MB - block_nbit: 15, // 32KB, - cache_size: NonZeroUsize::new(16).unwrap(), - recover_policy: RecoverPolicy::Strict, - } - } -} - -impl WalLoader { - pub fn new() -> Self { - Default::default() - } - - pub fn file_nbit(&mut self, v: u64) -> &mut Self { - self.file_nbit = v; - self - } - - pub fn block_nbit(&mut self, v: u64) -> &mut Self { - self.block_nbit = v; - self - } - - pub fn cache_size(&mut self, v: NonZeroUsize) -> &mut Self { - self.cache_size = v; - self - } - - pub fn recover_policy(&mut self, p: RecoverPolicy) -> &mut Self { - self.recover_policy = p; - self - } - - fn verify_checksum_(data: &[u8], checksum: u32, p: &RecoverPolicy) -> Result { - let mut hasher = Hasher::new(); - hasher.update(data); - let expected_checksum = hasher.finalize(); - - if checksum == expected_checksum { - Ok(true) - } else { - match p { - RecoverPolicy::Strict => Err(WalError::Other("invalid checksum".to_string())), - RecoverPolicy::BestEffort => Ok(false), - } - } - } - - fn verify_checksum(&self, data: &[u8], checksum: u32) -> Result { - Self::verify_checksum_(data, checksum, &self.recover_policy) - } - - #[allow(clippy::await_holding_refcell_ref)] - // TODO: Refactor to a more safe solution. - fn read_rings<'a, F: WalFile + 'static, S: WalStore + 'a>( - file: &'a WalFileHandle<'a, F, S>, - read_payload: bool, - block_nbit: u64, - recover_policy: &'a RecoverPolicy, - ) -> impl futures::Stream), bool>> + 'a { - let block_size = 1 << block_nbit; - let msize = std::mem::size_of::(); - - struct Vars<'a, F: WalFile + 'static, S: WalStore> { - done: bool, - off: u64, - file: &'a WalFileHandle<'a, F, S>, - } - - let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { - done: false, - off: 0, - file, - })); - - futures::stream::unfold((), move |_| { - let v = vars.clone(); - async move { - let mut v = v.borrow_mut(); - - macro_rules! check { - ($res: expr) => { - match $res { - Ok(t) => t, - Err(_) => die!(), - } - }; - } - - macro_rules! die { - () => {{ - v.done = true; - return Some((Err(true), ())); - }}; - } - - macro_rules! _yield { - () => {{ - v.done = true; - return None; - }}; - ($v: expr) => {{ - let v = $v; - catch_up!(); - return Some((Ok(v), ())); - }}; - } - - macro_rules! catch_up { - () => {{ - let block_remain = block_size - (v.off & (block_size - 1)); - if block_remain <= msize as u64 { - v.off += block_remain; - } - }}; - } - - if v.done { - return None; - } - let header_raw = match check!(v.file.read(v.off, msize).await) { - Some(h) => h, - None => _yield!(), - }; - v.off += msize as u64; - let header: &[WalRingBlob] = cast_slice(&header_raw); - let header = *header.first()?; - - let payload; - match WalRingType::from_repr(header.rtype as usize) { - Some(WalRingType::Full) - | Some(WalRingType::First) - | Some(WalRingType::Middle) - | Some(WalRingType::Last) => { - payload = if read_payload { - Some(check!(check!( - v.file.read(v.off, header.rsize as usize).await - ) - .ok_or(WalError::Other))) - } else { - None - }; - v.off += header.rsize as u64; - } - Some(WalRingType::Null) => _yield!(), - None => match recover_policy { - RecoverPolicy::Strict => die!(), - RecoverPolicy::BestEffort => { - v.done = true; - return Some((Err(false), ())); - } - }, - } - _yield!((header, payload)) - } - }) - } - - #[allow(clippy::await_holding_refcell_ref)] - fn read_records<'a, F: WalFile + 'static, S: WalStore + 'a>( - &'a self, - file: &'a WalFileHandle<'a, F, S>, - chunks: &'a mut Option<(Vec, WalPos)>, - ) -> impl futures::Stream> + 'a { - let fid = file.fid; - let file_nbit = self.file_nbit; - let block_size = 1 << self.block_nbit; - let msize = std::mem::size_of::(); - - struct Vars<'a, F: WalFile + 'static, S: WalStore> { - done: bool, - chunks: &'a mut Option<(Vec, WalPos)>, - off: u64, - file: &'a WalFileHandle<'a, F, S>, - } - - let vars = std::rc::Rc::new(std::cell::RefCell::new(Vars { - done: false, - off: 0, - chunks, - file, - })); - - futures::stream::unfold((), move |_| { - let v = vars.clone(); - async move { - let mut v = v.borrow_mut(); - - macro_rules! check { - ($res: expr) => { - match $res { - Ok(t) => t, - Err(_) => die!(), - } - }; - } - - macro_rules! die { - () => {{ - v.done = true; - return Some((Err(true), ())); - }}; - } - - macro_rules! _yield { - () => {{ - v.done = true; - return None; - }}; - ($v: expr) => {{ - let v = $v; - catch_up!(); - return Some((Ok(v), ())); - }}; - } - - macro_rules! catch_up { - () => {{ - let block_remain = block_size - (v.off & (block_size - 1)); - if block_remain <= msize as u64 { - v.off += block_remain; - } - }}; - } - - if v.done { - return None; - } - loop { - let header_raw = match check!(v.file.read(v.off, msize).await) { - Some(h) => h, - None => _yield!(), - }; - let ringid_start = (fid << file_nbit) + v.off; - v.off += msize as u64; - let header: WalRingBlob = *cast_slice(&header_raw).first()?; - let rsize = header.rsize; - match WalRingType::from_repr(header.rtype as usize) { - Some(WalRingType::Full) => { - assert!(v.chunks.is_none()); - let payload = check!(check!(v.file.read(v.off, rsize as usize).await) - .ok_or(WalError::Other)); - // TODO: improve the behavior when CRC32 fails - if !check!(self.verify_checksum(&payload, header.crc32)) { - die!() - } - v.off += rsize as u64; - _yield!(( - payload, - WalRingId { - start: ringid_start, - end: (fid << file_nbit) + v.off, - counter: header.counter - }, - header.counter - )) - } - Some(WalRingType::First) => { - assert!(v.chunks.is_none()); - let chunk = check!(check!(v.file.read(v.off, rsize as usize).await) - .ok_or(WalError::Other)); - if !check!(self.verify_checksum(&chunk, header.crc32)) { - die!() - } - *v.chunks = Some((vec![chunk], ringid_start)); - v.off += rsize as u64; - } - Some(WalRingType::Middle) => { - let Vars { - chunks, off, file, .. - } = &mut *v; - if let Some((chunks, _)) = chunks { - let chunk = check!(check!(file.read(*off, rsize as usize).await) - .ok_or(WalError::Other)); - if !check!(self.verify_checksum(&chunk, header.crc32)) { - die!() - } - chunks.push(chunk); - } // otherwise ignore the leftover - *off += rsize as u64; - } - Some(WalRingType::Last) => { - let v_off = v.off; - v.off += rsize as u64; - if let Some((mut chunks, ringid_start)) = v.chunks.take() { - let chunk = - check!(check!(v.file.read(v_off, rsize as usize).await) - .ok_or(WalError::Other)); - if !check!(self.verify_checksum(&chunk, header.crc32)) { - die!() - } - chunks.push(chunk); - let mut payload = - vec![0; chunks.iter().fold(0, |acc, v| acc + v.len())]; - let mut ps = &mut payload[..]; - #[allow(clippy::indexing_slicing)] - for c in chunks { - ps[..c.len()].copy_from_slice(&c); - ps = &mut ps[c.len()..]; - } - _yield!(( - payload.into_boxed_slice(), - WalRingId { - start: ringid_start, - end: (fid << file_nbit) + v.off, - counter: header.counter, - }, - header.counter - )) - } - } - Some(WalRingType::Null) => _yield!(), - None => match self.recover_policy { - RecoverPolicy::Strict => die!(), - RecoverPolicy::BestEffort => { - v.done = true; - return Some((Err(false), ())); - } - }, - } - catch_up!() - } - } - }) - } - - /// Recover by reading the Wal files. - pub async fn load< - F: WalFile + 'static, - S: WalStore, - Func: FnMut(WalBytes, WalRingId) -> Result<(), WalError>, - >( - &self, - store: S, - mut recover_func: Func, - keep_nrecords: u32, - ) -> Result, WalError> { - let msize = std::mem::size_of::(); - assert!(self.file_nbit > self.block_nbit); - assert!(msize < 1 << self.block_nbit); - let mut file_pool = - WalFilePool::new(store, self.file_nbit, self.block_nbit, self.cache_size).await?; - let logfiles = sort_fids( - self.file_nbit, - file_pool - .store - .enumerate_files()? - .flat_map(|s| get_fid(&s)) - .collect(), - ); - - let header = file_pool.read_header().await?; - - let mut chunks = None; - let mut pre_skip = true; - let mut scanned: Vec<(String, WalFileHandle)> = Vec::new(); - let mut counter = 0; - - // TODO: check for missing logfiles - 'outer: for (_, fid) in logfiles.into_iter() { - let fname = get_fname(fid); - let f = file_pool.get_file(fid, false).await?; - if header.recover_fid == fid { - pre_skip = false; - } - if pre_skip { - scanned.push((fname, f)); - continue; - } - { - let stream = self.read_records(&f, &mut chunks); - futures::pin_mut!(stream); - while let Some(res) = stream.next().await { - let (bytes, ring_id, _) = match res { - Err(e) => { - if e { - return Err(WalError::Other( - "error loading from storage".to_string(), - )); - } else { - break 'outer; - } - } - Ok(t) => t, - }; - recover_func(bytes, ring_id)?; - } - } - scanned.push((fname, f)); - } - - 'outer: for (_, f) in scanned.iter().rev() { - let records: Vec<_> = Self::read_rings(f, false, self.block_nbit, &self.recover_policy) - .collect() - .await; - for e in records.into_iter().rev() { - let (rec, _) = - e.map_err(|_| WalError::Other("error decoding WalRingBlob".to_string()))?; - if rec.rtype == WalRingType::Full as u8 || rec.rtype == WalRingType::Last as u8 { - counter = rec.counter + 1; - break 'outer; - } - } - } - - let fid_mask = (!0) >> self.file_nbit; - let mut pending_removal = VecDeque::new(); - let recover_fid = match scanned.last() { - Some((_, f)) => (f.fid + 1) & fid_mask, - None => 0, - }; - - file_pool.write_header(Header { recover_fid }).await?; - - let mut skip_remove = false; - for (fname, f) in scanned.into_iter() { - let mut last = None; - let stream = Self::read_rings(&f, false, self.block_nbit, &self.recover_policy); - futures::pin_mut!(stream); - while let Some(r) = stream.next().await { - last = - Some(r.map_err(|_| WalError::Other("error decoding WalRingBlob".to_string()))?); - } - if let Some((last_rec, _)) = last { - if !counter_lt(last_rec.counter + keep_nrecords, counter) { - skip_remove = true; - } - if skip_remove { - pending_removal.push_back((f.fid, last_rec.counter)); - } - } - if !skip_remove { - f.truncate(0).await?; - file_pool.store.remove_file(fname).await?; - } - } - - file_pool.reset(); - - let next = recover_fid << self.file_nbit; - let next_complete = WalRingId { - start: 0, - end: next, - counter, - }; - Ok(WalWriter::new( - WalState { - counter, - next_complete, - next, - file_nbit: self.file_nbit, - io_complete: BinaryHeap::new(), - pending_removal, - }, - file_pool, - )) - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod test { - use super::*; - use test_case::test_case; - - #[test_case("foo", Err("not a log file"); "no log extension")] - #[test_case("foo.log", Err("not a log file"); "invalid digit found in string")] - #[test_case("0000001.log", Ok(1); "happy path")] - #[test_case("1.log", Ok(1); "no leading zeroes")] - - fn test_get_fid(input: &str, expected: Result) { - let got = get_fid(Path::new(input)); - match expected { - Err(has) => { - let err = got.err().unwrap().to_string(); - assert!(err.contains(has), "{:?}", err) - } - Ok(val) => assert_eq!(got.unwrap(), val), - } - } -} diff --git a/growth-ring/src/walerror.rs b/growth-ring/src/walerror.rs deleted file mode 100644 index 6900f0ba2d8c..000000000000 --- a/growth-ring/src/walerror.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::sync::Arc; - -use nix::errno::Errno; -use thiserror::Error; - -#[derive(Clone, Debug, Error)] -pub enum WalError { - #[error("an unclassified error has occurred: {0}")] - Other(String), - #[error("an OS error {0} has occurred")] - UnixError(#[from] Errno), - #[error("a checksum check has failed")] - InvalidChecksum, - #[error("an I/O error has occurred")] - IOError(Arc), - #[error("Wal directory already exists")] - WalDirExists, -} - -impl From for WalError { - fn from(value: i32) -> Self { - Self::UnixError(Errno::from_raw(value)) - } -} - -impl From for WalError { - fn from(err: std::io::Error) -> Self { - Self::IOError(Arc::new(err)) - } -} diff --git a/growth-ring/tests/common/mod.rs b/growth-ring/tests/common/mod.rs deleted file mode 100644 index 152351b7dabe..000000000000 --- a/growth-ring/tests/common/mod.rs +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#![allow(clippy::indexing_slicing)] -#[cfg(test)] -use async_trait::async_trait; -use futures::executor::block_on; -use growthring::wal::{WalBytes, WalError, WalFile, WalLoader, WalPos, WalRingId, WalStore}; -use indexmap::{map::Entry, IndexMap}; -use rand::Rng; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::collections::{hash_map, HashMap}; -use std::path::PathBuf; -use std::rc::Rc; - -pub trait FailGen { - fn next_fail(&self) -> bool; -} - -struct FileContentEmul(RefCell>); - -impl FileContentEmul { - pub const fn new() -> Self { - FileContentEmul(RefCell::new(Vec::new())) - } -} - -impl std::ops::Deref for FileContentEmul { - type Target = RefCell>; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Emulate the a virtual file handle. -pub struct WalFileEmul { - file: Rc, - fgen: Rc, -} - -#[async_trait(?Send)] -impl WalFile for WalFileEmul { - async fn allocate(&self, offset: WalPos, length: usize) -> Result<(), WalError> { - if self.fgen.next_fail() { - return Err(WalError::Other("allocate fgen next fail".to_string())); - } - let offset = offset as usize; - if offset + length > self.file.borrow().len() { - self.file.borrow_mut().resize(offset + length, 0) - } - for v in &mut self.file.borrow_mut()[offset..offset + length] { - *v = 0 - } - Ok(()) - } - - async fn truncate(&self, length: usize) -> Result<(), WalError> { - if self.fgen.next_fail() { - return Err(WalError::Other("truncate fgen next fail".to_string())); - } - self.file.borrow_mut().resize(length, 0); - Ok(()) - } - - async fn write(&self, offset: WalPos, data: WalBytes) -> Result<(), WalError> { - if self.fgen.next_fail() { - return Err(WalError::Other("write fgen next fail".to_string())); - } - let offset = offset as usize; - self.file.borrow_mut()[offset..offset + data.len()].copy_from_slice(&data); - Ok(()) - } - - async fn read(&self, offset: WalPos, length: usize) -> Result, WalError> { - if self.fgen.next_fail() { - return Err(WalError::Other("read fgen next fail".to_string())); - } - - let offset = offset as usize; - let file = self.file.borrow(); - if offset + length > file.len() { - Ok(None) - } else { - Ok(Some( - file[offset..offset + length].to_vec().into_boxed_slice(), - )) - } - } -} - -pub struct WalStoreEmulState { - files: HashMap>, -} - -impl WalStoreEmulState { - pub fn new() -> Self { - WalStoreEmulState { - files: HashMap::new(), - } - } - pub fn clone(&self) -> Self { - WalStoreEmulState { - files: self.files.clone(), - } - } -} - -/// Emulate the persistent storage state. -pub struct WalStoreEmul<'a, G> -where - G: FailGen, -{ - state: RefCell<&'a mut WalStoreEmulState>, - fgen: Rc, -} - -impl<'a, G: FailGen> WalStoreEmul<'a, G> { - pub fn new(state: &'a mut WalStoreEmulState, fgen: Rc) -> Self { - let state = RefCell::new(state); - WalStoreEmul { state, fgen } - } -} - -#[async_trait(?Send)] -impl<'a, G> WalStore> for WalStoreEmul<'a, G> -where - G: 'static + FailGen, -{ - type FileNameIter = std::vec::IntoIter; - - async fn open_file(&self, filename: &str, touch: bool) -> Result, WalError> { - if self.fgen.next_fail() { - return Err(WalError::Other("open_file fgen next fail".to_string())); - } - match self.state.borrow_mut().files.entry(filename.to_string()) { - hash_map::Entry::Occupied(e) => { - let file = WalFileEmul { - file: e.get().clone(), - fgen: self.fgen.clone(), - }; - Ok(file) - } - hash_map::Entry::Vacant(e) => { - if touch { - let file = WalFileEmul { - file: e.insert(Rc::new(FileContentEmul::new())).clone(), - fgen: self.fgen.clone(), - }; - Ok(file) - } else { - Err(WalError::Other("open_file not found".to_string())) - } - } - } - } - - async fn remove_file(&self, filename: String) -> Result<(), WalError> { - //println!("remove_file(filename={})", filename); - if self.fgen.next_fail() { - return Err(WalError::Other("remove_file fgen next fail".to_string())); - } - self.state - .borrow_mut() - .files - .remove(&filename) - .ok_or(WalError::Other("remove_file not found".to_string())) - .map(|_| ()) - } - - fn enumerate_files(&self) -> Result { - if self.fgen.next_fail() { - return Err(WalError::Other( - "enumerate_files fgen next fail".to_string(), - )); - } - let mut logfiles = Vec::new(); - for (fname, _) in self.state.borrow().files.iter() { - logfiles.push(fname.into()) - } - Ok(logfiles.into_iter()) - } -} - -pub struct SingleFailGen { - cnt: std::cell::Cell, - fail_point: usize, -} - -impl SingleFailGen { - pub const fn new(fail_point: usize) -> Self { - SingleFailGen { - cnt: std::cell::Cell::new(0), - fail_point, - } - } -} - -impl FailGen for SingleFailGen { - fn next_fail(&self) -> bool { - let c = self.cnt.get(); - self.cnt.set(c + 1); - c == self.fail_point - } -} - -pub struct ZeroFailGen; - -impl FailGen for ZeroFailGen { - fn next_fail(&self) -> bool { - false - } -} - -pub struct CountFailGen(std::cell::Cell); - -impl CountFailGen { - pub const fn new() -> Self { - CountFailGen(std::cell::Cell::new(0)) - } - pub fn get_count(&self) -> usize { - self.0.get() - } -} - -impl FailGen for CountFailGen { - fn next_fail(&self) -> bool { - self.0.set(self.0.get() + 1); - false - } -} - -/// An ordered list of intervals: `(begin, end, color)*`. -#[derive(Clone)] -pub struct PaintStrokes(Vec<(u32, u32, u32)>); - -impl PaintStrokes { - pub const fn new() -> Self { - PaintStrokes(Vec::new()) - } - - pub fn to_bytes(&self) -> WalBytes { - let mut res: Vec = Vec::new(); - let is = std::mem::size_of::(); - let len = self.0.len() as u32; - res.resize(is * (1 + 3 * self.0.len()), 0); - let mut rs = &mut res[..]; - rs[..is].copy_from_slice(&len.to_le_bytes()); - rs = &mut rs[is..]; - for (s, e, c) in self.0.iter() { - rs[..is].copy_from_slice(&s.to_le_bytes()); - rs[is..is * 2].copy_from_slice(&e.to_le_bytes()); - rs[is * 2..is * 3].copy_from_slice(&c.to_le_bytes()); - rs = &mut rs[is * 3..]; - } - res.into_boxed_slice() - } - - pub fn from_bytes(raw: &[u8]) -> Self { - assert!(raw.len() > 4); - assert!(raw.len() & 3 == 0); - let is = std::mem::size_of::(); - let (len_raw, mut rest) = raw.split_at(is); - #[allow(clippy::unwrap_used)] - let len = u32::from_le_bytes(len_raw.try_into().unwrap()); - let mut res = Vec::new(); - for _ in 0..len { - let (s_raw, rest1) = rest.split_at(is); - let (e_raw, rest2) = rest1.split_at(is); - let (c_raw, rest3) = rest2.split_at(is); - #[allow(clippy::unwrap_used)] - res.push(( - u32::from_le_bytes(s_raw.try_into().unwrap()), - #[allow(clippy::unwrap_used)] - u32::from_le_bytes(e_raw.try_into().unwrap()), - #[allow(clippy::unwrap_used)] - u32::from_le_bytes(c_raw.try_into().unwrap()), - )); - rest = rest3 - } - PaintStrokes(res) - } - - pub fn gen_rand( - max_pos: u32, - max_len: u32, - max_col: u32, - n: usize, - rng: &mut R, - ) -> PaintStrokes { - assert!(max_pos > 0); - let mut strokes = Self::new(); - for _ in 0..n { - let pos = rng.gen_range(0..max_pos); - let len = rng.gen_range(1..std::cmp::min(max_len, max_pos - pos + 1)); - strokes.stroke(pos, pos + len, rng.gen_range(0..max_col)) - } - strokes - } - - pub fn stroke(&mut self, start: u32, end: u32, color: u32) { - self.0.push((start, end, color)) - } - - pub fn into_vec(self) -> Vec<(u32, u32, u32)> { - self.0 - } -} - -impl growthring::wal::Record for PaintStrokes { - fn serialize(&self) -> WalBytes { - self.to_bytes() - } -} - -#[test] -fn test_paint_strokes() { - let mut p = PaintStrokes::new(); - for i in 0..3 { - p.stroke(i, i + 3, i + 10) - } - let pr = p.to_bytes(); - for ((s, e, c), i) in PaintStrokes::from_bytes(&pr) - .into_vec() - .into_iter() - .zip(0..) - { - assert_eq!(s, i); - assert_eq!(e, i + 3); - assert_eq!(c, i + 10); - } -} - -pub struct Canvas { - waiting: HashMap, - queue: IndexMap>, - canvas: Box<[u32]>, -} - -impl Canvas { - pub fn new(size: usize) -> Self { - let canvas = vec![0; size].into_boxed_slice(); - // fill the background color 0 - Canvas { - waiting: HashMap::new(), - queue: IndexMap::new(), - canvas, - } - } - - pub fn new_reference(&self, ops: &[PaintStrokes]) -> Self { - let mut res = Self::new(self.canvas.len()); - for op in ops { - for (s, e, c) in op.0.iter() { - for i in *s..*e { - res.canvas[i as usize] = *c - } - } - } - res - } - - fn get_waiting(&mut self, rid: WalRingId) -> &mut usize { - match self.waiting.entry(rid) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => e.insert(0), - } - } - - fn get_queued(&mut self, pos: u32) -> &mut VecDeque<(u32, WalRingId)> { - match self.queue.entry(pos) { - Entry::Occupied(e) => e.into_mut(), - Entry::Vacant(e) => e.insert(VecDeque::new()), - } - } - - pub fn prepaint(&mut self, strokes: &PaintStrokes, rid: &WalRingId) { - let rid = *rid; - let mut nwait = 0; - for (s, e, c) in strokes.0.iter() { - for i in *s..*e { - nwait += 1; - self.get_queued(i).push_back((*c, rid)) - } - } - *self.get_waiting(rid) = nwait - } - - // TODO: allow customized scheduler - /// Schedule to paint one position, randomly. It optionally returns a finished batch write - /// identified by its start position of WalRingId. - pub fn rand_paint(&mut self, rng: &mut R) -> Option<(Option, u32)> { - if self.is_empty() { - return None; - } - let idx = rng.gen_range(0..self.queue.len()); - #[allow(clippy::unwrap_used)] - let (pos, _) = self.queue.get_index(idx).unwrap(); - let pos = *pos; - Some((self.paint(pos), pos)) - } - - pub fn clear_queued(&mut self) { - self.queue.clear(); - self.waiting.clear(); - } - - #[allow(clippy::unwrap_used)] - pub fn paint_all(&mut self) { - for (pos, q) in self.queue.iter() { - self.canvas[*pos as usize] = q.back().unwrap().0; - } - self.clear_queued() - } - - pub fn is_empty(&self) -> bool { - self.queue.is_empty() - } - - #[allow(clippy::unwrap_used)] - pub fn paint(&mut self, pos: u32) -> Option { - let q = self.queue.get_mut(&pos).unwrap(); - #[allow(clippy::unwrap_used)] - let (c, rid) = q.pop_front().unwrap(); - if q.is_empty() { - self.queue.swap_remove(&pos); - } - self.canvas[pos as usize] = c; - if let Some(cnt) = self.waiting.get_mut(&rid) { - *cnt -= 1; - if *cnt == 0 { - self.waiting.remove(&rid); - Some(rid) - } else { - None - } - } else { - None - } - } - - pub fn is_same(&self, other: &Canvas) -> bool { - self.canvas.cmp(&other.canvas) == std::cmp::Ordering::Equal - } - - pub fn print(&self, max_col: usize) { - println!("# begin canvas"); - for r in self.canvas.chunks(max_col) { - for c in r.iter() { - print!("{:02x} ", c & 0xff); - } - println!(); - } - println!("# end canvas"); - } -} - -#[test] -fn test_canvas() { - let mut rng = ::seed_from_u64(42); - let mut canvas1 = Canvas::new(100); - let mut canvas2 = Canvas::new(100); - let canvas3 = Canvas::new(101); - let dummy = WalRingId::empty_id(); - let s1 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); - let s2 = PaintStrokes::gen_rand(100, 10, 256, 2, &mut rng); - assert!(canvas1.is_same(&canvas2)); - assert!(!canvas2.is_same(&canvas3)); - canvas1.prepaint(&s1, &dummy); - canvas1.prepaint(&s2, &dummy); - canvas2.prepaint(&s1, &dummy); - canvas2.prepaint(&s2, &dummy); - assert!(canvas1.is_same(&canvas2)); - canvas1.rand_paint(&mut rng); - assert!(!canvas1.is_same(&canvas2)); - while canvas1.rand_paint(&mut rng).is_some() {} - while canvas2.rand_paint(&mut rng).is_some() {} - assert!(canvas1.is_same(&canvas2)); - canvas1.print(10); -} - -pub struct PaintingSim { - pub block_nbit: u64, - pub file_nbit: u64, - pub file_cache: usize, - /// number of PaintStrokes (WriteBatch) - pub n: usize, - /// number of strokes per PaintStrokes - pub m: usize, - /// number of scheduled ticks per PaintStroke submission - pub k: usize, - /// the size of canvas - pub csize: usize, - /// max length of a single stroke - pub stroke_max_len: u32, - /// max color value - pub stroke_max_col: u32, - /// max number of strokes per PaintStroke - pub stroke_max_n: usize, - /// random seed - pub seed: u64, -} - -impl PaintingSim { - pub fn run( - &self, - state: &mut WalStoreEmulState, - canvas: &mut Canvas, - loader: WalLoader, - ops: &mut Vec, - ringid_map: &mut HashMap, - fgen: Rc, - ) -> Result<(), WalError> { - let mut rng = ::seed_from_u64(self.seed); - let mut wal = block_on(loader.load( - WalStoreEmul::new(state, fgen.clone()), - |_, _| { - if fgen.next_fail() { - Err(WalError::Other("run fgen fail".to_string())) - } else { - Ok(()) - } - }, - 0, - ))?; - for _ in 0..self.n { - let pss = (0..self.m) - .map(|_| { - PaintStrokes::gen_rand( - self.csize as u32, - self.stroke_max_len, - self.stroke_max_col, - rng.gen_range(1..self.stroke_max_n + 1), - &mut rng, - ) - }) - .collect::>(); - let pss_ = pss.clone(); - // write ahead - let rids = wal.grow(pss); - assert_eq!(rids.len(), self.m); - let recs = rids - .into_iter() - .zip(pss_.into_iter()) - .map(|(r, ps)| -> Result<_, _> { - ops.push(ps); - let (rec, rid) = futures::executor::block_on(r) - .map_err(|_| WalError::Other("paintstrokes executor error".to_string()))?; - ringid_map.insert(rid, ops.len() - 1); - Ok((rec, rid)) - }) - .collect::, WalError>>()?; - // finish appending to Wal - /* - for rid in rids.iter() { - println!("got ringid: {:?}", rid); - } - */ - // prepare data writes - for (ps, rid) in recs.into_iter() { - canvas.prepaint(&ps, &rid); - } - // run k ticks of the fine-grained scheduler - for _ in 0..rng.gen_range(1..self.k) { - // storage I/O could fail - if fgen.next_fail() { - return Err(WalError::Other("run fgen fail: storage i/o".to_string())); - } - if let Some((fin_rid, _)) = canvas.rand_paint(&mut rng) { - if let Some(rid) = fin_rid { - futures::executor::block_on(wal.peel(&[rid], 0))? - } - } else { - break; - } - } - } - //canvas.print(40); - assert_eq!(wal.file_pool_in_use(), 0); - Ok(()) - } - - pub fn get_walloader(&self) -> WalLoader { - let mut loader = WalLoader::new(); - #[allow(clippy::unwrap_used)] - loader - .file_nbit(self.file_nbit) - .block_nbit(self.block_nbit) - .cache_size(std::num::NonZeroUsize::new(self.file_cache).unwrap()); - loader - } - - pub fn get_nticks(&self, state: &mut WalStoreEmulState) -> usize { - let mut canvas = Canvas::new(self.csize); - let mut ops: Vec = Vec::new(); - let mut ringid_map = HashMap::new(); - let fgen = Rc::new(CountFailGen::new()); - #[allow(clippy::unwrap_used)] - self.run( - state, - &mut canvas, - self.get_walloader(), - &mut ops, - &mut ringid_map, - fgen.clone(), - ) - .unwrap(); - fgen.get_count() - } - - pub fn check( - &self, - state: &mut WalStoreEmulState, - canvas: &mut Canvas, - wal: WalLoader, - ops: &[PaintStrokes], - ringid_map: &HashMap, - ) -> bool { - if ops.is_empty() { - return true; - } - let mut last_idx = 0; - let mut napplied = 0; - canvas.clear_queued(); - #[allow(clippy::unwrap_used)] - block_on(wal.load( - WalStoreEmul::new(state, Rc::new(ZeroFailGen)), - |payload, ringid| { - let s = PaintStrokes::from_bytes(&payload); - canvas.prepaint(&s, &ringid); - last_idx = *ringid_map.get(&ringid).unwrap() + 1; - napplied += 1; - Ok(()) - }, - 0, - )) - .unwrap(); - println!("last = {}/{}, applied = {}", last_idx, ops.len(), napplied); - canvas.paint_all(); - - // recover complete - let start_ops = if last_idx > 0 { &ops[..last_idx] } else { &[] }; - let canvas0 = canvas.new_reference(start_ops); - - if canvas.is_same(&canvas0) { - return true; - } - - if last_idx > 0 { - canvas.print(40); - canvas0.print(40); - - return false; - } - - let (start_ops, end_ops) = ops.split_at(self.m); - let mut canvas0 = canvas0.new_reference(start_ops); - - if canvas.is_same(&canvas0) { - return true; - } - - for op in end_ops { - const EMPTY_ID: WalRingId = WalRingId::empty_id(); - - canvas0.prepaint(op, &EMPTY_ID); - canvas0.paint_all(); - - if canvas.is_same(&canvas0) { - return true; - } - } - - canvas.print(40); - canvas0.print(40); - - false - } - - pub fn new_canvas(&self) -> Canvas { - Canvas::new(self.csize) - } -} diff --git a/growth-ring/tests/rand_fail.rs b/growth-ring/tests/rand_fail.rs deleted file mode 100644 index dcfecaec06c1..000000000000 --- a/growth-ring/tests/rand_fail.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#![allow(clippy::indexing_slicing)] -#[cfg(test)] -mod common; - -use std::collections::HashMap; -use std::rc::Rc; - -fn multi_point_failure(sims: &[common::PaintingSim]) { - fn track_recursion(sims: &[common::PaintingSim], state: &common::WalStoreEmulState, f: usize) { - let sim = &sims[0]; - // save the current state and start from there - let mut state = state.clone(); - let mut state0 = state.clone(); - let nticks = sim.get_nticks(&mut state0); - println!("fail = {f}, nticks = {nticks}"); - - for pos in 0..nticks { - println!("fail = {f}, pos = {pos}"); - let mut canvas = sim.new_canvas(); - let mut ops: Vec = Vec::new(); - let mut ringid_map = HashMap::new(); - let fgen = common::SingleFailGen::new(pos); - - let sim_result = sim.run( - &mut state, - &mut canvas, - sim.get_walloader(), - &mut ops, - &mut ringid_map, - Rc::new(fgen), - ); - - if sim_result.is_ok() { - return; - } - - if sims.len() > 1 { - track_recursion(&sims[1..], &state, f + 1) - } else { - assert!(sim.check( - &mut state, - &mut canvas, - sim.get_walloader(), - &ops, - &ringid_map, - )) - } - } - } - - track_recursion(sims, &common::WalStoreEmulState::new(), 1); -} - -#[test] -fn short_single_point_failure() { - let sim = common::PaintingSim { - block_nbit: 5, - file_nbit: 6, - file_cache: 1000, - n: 10, - m: 10, - k: 10, - csize: 100, - stroke_max_len: 10, - stroke_max_col: 256, - stroke_max_n: 2, - seed: 0, - }; - multi_point_failure(&[sim]); -} - -#[test] -#[ignore] -fn single_point_failure1() { - let sim = common::PaintingSim { - block_nbit: 5, - file_nbit: 6, - file_cache: 1000, - n: 100, - m: 10, - k: 1000, - csize: 1000, - stroke_max_len: 10, - stroke_max_col: 256, - stroke_max_n: 5, - seed: 0, - }; - multi_point_failure(&[sim]); -} - -#[test] -#[ignore] -fn two_failures() { - let sims = [ - common::PaintingSim { - block_nbit: 5, - file_nbit: 6, - file_cache: 1000, - n: 10, - m: 5, - k: 100, - csize: 1000, - stroke_max_len: 10, - stroke_max_col: 256, - stroke_max_n: 3, - seed: 0, - }, - common::PaintingSim { - block_nbit: 5, - file_nbit: 6, - file_cache: 1000, - n: 10, - m: 5, - k: 100, - csize: 1000, - stroke_max_len: 10, - stroke_max_col: 256, - stroke_max_n: 3, - seed: 0, - }, - ]; - multi_point_failure(&sims); -} diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index f7a22fe29820..c07bd889e028 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -18,12 +18,10 @@ bench = false [dependencies] firewood = { version = "0.0.4", path = "../firewood" } prost = "0.12.3" -thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.11.0", features = ["tls"] } tracing = { version = "0.1.40" } clap = { version = "4.5.0", features = ["derive"] } -tempdir = "0.3.7" log = "0.4.20" env_logger = "0.11.2" chrono = "0.4.34" @@ -49,3 +47,7 @@ unwrap_used = "warn" indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" + +[package.metadata.cargo-machete] +ignored = ["prost", "tonic_build"] + diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index b82ba676aba7..06809f9ad2d6 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -2,8 +2,7 @@ // See the file LICENSE.md for licensing terms. use firewood::db::{Db, DbConfig}; -use firewood::storage::WalConfig; -use firewood::v2::{api::Db as _, api::Error}; +use firewood::v2::api::Error; use std::path::Path; use std::{ @@ -21,11 +20,11 @@ pub mod database; pub mod db; pub mod process; -trait IntoStatusResultExt { +trait _IntoStatusResultExt { fn into_status_result(self) -> Result; } -impl IntoStatusResultExt for Result { +impl _IntoStatusResultExt for Result { // We map errors from bad arguments into Status::invalid_argument; all other errors are Status::internal errors fn into_status_result(self) -> Result { self.map_err(|err| match err { @@ -47,17 +46,14 @@ pub struct Database { } impl Database { - pub async fn new>(path: P, history_length: u32) -> Result { + pub async fn new>(path: P, _history_length: u32) -> Result { // try to create the parents for this directory, but it's okay if it fails; it will get caught in Db::new std::fs::create_dir_all(&path).ok(); // TODO: truncate should be false // see https://github.com/ava-labs/firewood/issues/418 - let cfg = DbConfig::builder() - .wal(WalConfig::builder().max_revisions(history_length).build()) - .truncate(true) - .build(); + let cfg = DbConfig::builder().truncate(true).build(); - let db = Db::new(path, &cfg).await?; + let db = Db::new(path, cfg).await?; Ok(Self { db, @@ -74,12 +70,12 @@ impl Deref for Database { } } -impl Database { - async fn latest(&self) -> Result::Historical>, Error> { - let root_hash = self.root_hash().await?; - self.revision(root_hash).await - } -} +// impl Database { +// async fn latest(&self) -> Result::Historical>, Error> { +// let root_hash = self.root_hash().await?; +// self.revision(root_hash).await +// } +// } // TODO: implement Iterator #[derive(Debug)] diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index 7fc08ba05362..e3857c5a8c9b 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Database as DatabaseService, IntoStatusResultExt, Iter}; +use super::{Database as DatabaseService, Iter}; use crate::rpcdb::{ database_server::Database, CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, GetRequest, GetResponse, HasRequest, HasResponse, @@ -10,67 +10,71 @@ use crate::rpcdb::{ NewIteratorWithStartAndPrefixRequest, NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, WriteBatchResponse, }; -use firewood::v2::api::{BatchOp, Db, DbView, Proposal}; -use std::sync::Arc; +use firewood::v2::api::BatchOp; + use tonic::{async_trait, Request, Response, Status}; #[async_trait] impl Database for DatabaseService { - async fn has(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.latest().await.into_status_result()?; + async fn has(&self, _request: Request) -> Result, Status> { + todo!() + // let key = request.into_inner().key; + // let revision = self.latest().await.into_status_result()?; - let val = revision.val(key).await.into_status_result()?; + // let val = revision.val(key).await.into_status_result()?; - let response = HasResponse { - has: val.is_some(), - ..Default::default() - }; + // let response = HasResponse { + // has: val.is_some(), + // ..Default::default() + // }; - Ok(Response::new(response)) + // Ok(Response::new(response)) } - async fn get(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.latest().await.into_status_result()?; + async fn get(&self, _request: Request) -> Result, Status> { + todo!() + // let key = request.into_inner().key; + // let revision = self.latest().await.into_status_result()?; - let value = revision - .val(key) - .await - .into_status_result()? - .map(|v| v.to_vec()); + // let value = revision + // .val(key) + // .await + // .into_status_result()? + // .map(|v| v.to_vec()); - let Some(value) = value else { - return Err(Status::not_found("key not found")); - }; + // let Some(value) = value else { + // return Err(Status::not_found("key not found")); + // }; - let response = GetResponse { - value, - ..Default::default() - }; + // let response = GetResponse { + // value, + // ..Default::default() + // }; - Ok(Response::new(response)) + // Ok(Response::new(response)) } - async fn put(&self, request: Request) -> Result, Status> { - let PutRequest { key, value } = request.into_inner(); - let batch = BatchOp::Put { key, value }; - let proposal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); - let _ = proposal.commit().await.into_status_result()?; + async fn put(&self, _request: Request) -> Result, Status> { + todo!() + // let PutRequest { key, value } = request.into_inner(); + // let batch = BatchOp::Put { key, value }; + // let proposal = self.db.propose(vec![batch]).await.into_status_result()?; + // let _ = proposal.commit().await.into_status_result()?; - Ok(Response::new(PutResponse::default())) + // Ok(Response::new(PutResponse::default())) } async fn delete( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let DeleteRequest { key } = request.into_inner(); - let batch = BatchOp::<_, Vec>::Delete { key }; - let propoal = Arc::new(self.db.propose(vec![batch]).await.into_status_result()?); - let _ = propoal.commit().await.into_status_result()?; + todo!() + // let DeleteRequest { key } = request.into_inner(); + // let batch = BatchOp::<_, Vec>::Delete { key }; + // let proposal = self.db.propose(vec![batch]).await.into_status_result()?; + // let _ = proposal.commit().await.into_status_result()?; - Ok(Response::new(DeleteResponse::default())) + // Ok(Response::new(DeleteResponse::default())) } async fn compact( @@ -97,18 +101,19 @@ impl Database for DatabaseService { async fn write_batch( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let WriteBatchRequest { puts, deletes } = request.into_inner(); - let batch = puts - .into_iter() - .map(from_put_request) - .chain(deletes.into_iter().map(from_delete_request)) - .collect(); - let proposal = Arc::new(self.db.propose(batch).await.into_status_result()?); - let _ = proposal.commit().await.into_status_result()?; - - Ok(Response::new(WriteBatchResponse::default())) + todo!() + // let WriteBatchRequest { puts, deletes } = request.into_inner(); + // let batch = puts + // .into_iter() + // .map(from_put_request) + // .chain(deletes.into_iter().map(from_delete_request)) + // .collect(); + // let proposal = self.db.propose(batch).await.into_status_result()?; + // let _ = proposal.commit().await.into_status_result()?; + + // Ok(Response::new(WriteBatchResponse::default())) } async fn new_iterator_with_start_and_prefix( @@ -158,13 +163,13 @@ impl Database for DatabaseService { } } -fn from_put_request(request: PutRequest) -> BatchOp, Vec> { +fn _from_put_request(request: PutRequest) -> BatchOp, Vec> { BatchOp::Put { key: request.key, value: request.value, } } -fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { +fn _from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { BatchOp::Delete { key: request.key } } diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index 9f49a02fd12b..a1cd2841931e 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -1,14 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Database, IntoStatusResultExt}; +use super::Database; use crate::sync::{ db_server::Db as DbServerTrait, CommitChangeProofRequest, CommitRangeProofRequest, GetChangeProofRequest, GetChangeProofResponse, GetMerkleRootResponse, GetProofRequest, GetProofResponse, GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, VerifyChangeProofResponse, }; -use firewood::v2::api::Db; use tonic::{async_trait, Request, Response, Status}; #[async_trait] @@ -18,67 +17,64 @@ impl DbServerTrait for Database { &self, _request: Request<()>, ) -> Result, Status> { - let root_hash = self.db.root_hash().await.into_status_result()?.to_vec(); - - let response = GetMerkleRootResponse { root_hash }; - - Ok(Response::new(response)) + todo!() + // let root_hash = self.db.root_hash().await.into_status_result()?.to_vec(); + // + // let response = GetMerkleRootResponse { root_hash }; + // + // Ok(Response::new(response)) } #[tracing::instrument(level = "trace")] async fn get_proof( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let GetProofRequest { key: _ } = request.into_inner(); - let _revision = self.latest().await.into_status_result()?; - todo!() + // let GetProofRequest { key: _ } = request.into_inner(); + // let _revision = self.latest().await.into_status_result()?; } #[tracing::instrument(level = "trace")] async fn get_change_proof( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let GetChangeProofRequest { - start_root_hash: _, - end_root_hash: _, - start_key: _, - end_key: _, - key_limit: _, - } = request.into_inner(); - - let _revision = self.latest().await.into_status_result()?; - todo!() + // let GetChangeProofRequest { + // start_root_hash: _, + // end_root_hash: _, + // start_key: _, + // end_key: _, + // key_limit: _, + // } = request.into_inner(); + + // let _revision = self.latest().await.into_status_result()?; } #[tracing::instrument(level = "trace")] async fn verify_change_proof( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let VerifyChangeProofRequest { - proof: _, - start_key: _, - end_key: _, - expected_root_hash: _, - } = request.into_inner(); - - let _revision = self.latest().await.into_status_result()?; - todo!() + // let VerifyChangeProofRequest { + // proof: _, + // start_key: _, + // end_key: _, + // expected_root_hash: _, + // } = request.into_inner(); + + // let _revision = self.latest().await.into_status_result()?; } #[tracing::instrument(level = "trace")] async fn commit_change_proof( &self, - request: Request, + _request: Request, ) -> Result, Status> { - let CommitChangeProofRequest { proof: _ } = request.into_inner(); - todo!() + // let CommitChangeProofRequest { proof: _ } = request.into_inner(); } #[tracing::instrument(level = "trace")] diff --git a/libaio/Cargo.toml b/libaio/Cargo.toml deleted file mode 100644 index bde24dcdd0a8..000000000000 --- a/libaio/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "libaio" -version = "0.0.4" -edition = "2021" -keywords = ["libaio", "aio", "async", "futures"] -license = "../LICENSE.md" -description = "Straightforward Linux AIO using Futures/async/await." - -[features] -emulated-failure = [] - -[dependencies] -libc = "0.2.153" -parking_lot = "0.12.1" -crossbeam-channel = "0.5.11" -thiserror = "1.0.57" - -[dev-dependencies] -futures = "0.3.30" - -[lib] -name = "aiofut" -path = "src/lib.rs" -crate-type = ["dylib", "rlib", "staticlib"] diff --git a/libaio/README.md b/libaio/README.md deleted file mode 100644 index e245c1808d48..000000000000 --- a/libaio/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# libaio - -This crate was forked from [`libaio-futures`](https://github.com/Determinant/libaio-futures) at commit [`61861e2`](https://github.com/Determinant/libaio-futures/commit/61861e20cee14211d03a1b895721b27fce148aef), under MIT license. \ No newline at end of file diff --git a/libaio/build.rs b/libaio/build.rs deleted file mode 100644 index ec369eef8626..000000000000 --- a/libaio/build.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::env; - -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - std::process::Command::new("make") - .args(&[format!("{}/libaio.a", out_dir)]) - .env("OUT_DIR", &out_dir) - .current_dir("./libaio") - .status() - .expect("failed to make libaio"); - eprintln!("{}", out_dir); - // the current source version of libaio is 0.3.112 - println!("cargo:rerun-if-changed={}/libaio.a", out_dir); - println!("cargo:rustc-link-search=native={}", out_dir); -} diff --git a/libaio/libaio/.gitignore b/libaio/libaio/.gitignore deleted file mode 100644 index d4a430941e88..000000000000 --- a/libaio/libaio/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -*.rej -*.orig -*~ -/*.patch - -*.o -*.o[ls] - -/src/libaio.a -/src/libaio.so* diff --git a/libaio/libaio/COPYING b/libaio/libaio/COPYING deleted file mode 100644 index c4792dd27a32..000000000000 --- a/libaio/libaio/COPYING +++ /dev/null @@ -1,515 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations -below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. -^L - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it -becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. -^L - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control -compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. -^L - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. -^L - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. -^L - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. -^L - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply, and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License -may add an explicit geographical distribution limitation excluding those -countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. -^L - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS -^L - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms -of the ordinary General Public License). - - To apply these terms, attach the following notices to the library. -It is safest to attach them to the start of each source file to most -effectively convey the exclusion of warranty; and each file should -have at least the "copyright" line and a pointer to where the full -notice is found. - - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Also add information on how to contact you by electronic and paper -mail. - -You should also get your employer (if you work as a programmer) or -your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James -Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/libaio/libaio/Makefile b/libaio/libaio/Makefile deleted file mode 100644 index 1865bbe6622b..000000000000 --- a/libaio/libaio/Makefile +++ /dev/null @@ -1,73 +0,0 @@ -prefix=/usr -includedir=$(prefix)/include -libdir=$(prefix)/lib - -CFLAGS ?= -g -fomit-frame-pointer -O2 -CFLAGS += -Wall -I. -fPIC -SO_CFLAGS=-shared $(CFLAGS) -L_CFLAGS=$(CFLAGS) -LINK_FLAGS= -LINK_FLAGS+=$(LDFLAGS) -ENABLE_SHARED ?= 1 - -soname=libaio.so.1 -minor=0 -micro=1 -libname=$(soname).$(minor).$(micro) -all_targets += $(OUT_DIR)/libaio.a - -ifeq ($(ENABLE_SHARED),1) -all_targets += $(libname) -endif - -all: $(all_targets) - -# libaio provided functions -libaio_srcs := io_queue_init.c io_queue_release.c -libaio_srcs += io_queue_wait.c io_queue_run.c - -# real syscalls -libaio_srcs += io_getevents.c io_submit.c io_cancel.c -libaio_srcs += io_setup.c io_destroy.c io_pgetevents.c - -# internal functions -libaio_srcs += raw_syscall.c - -# old symbols -libaio_srcs += compat-0_1.c - -libaio_objs := $(patsubst %.c,$(OUT_DIR)/%.ol,$(libaio_srcs)) -libaio_sobjs := $(patsubst %.c,$(OUT_DIR)/%.os,$(libaio_srcs)) - -$(libaio_objs) $(libaio_sobjs): libaio.h vsys_def.h - -$(OUT_DIR)/%.os: %.c - $(CC) $(SO_CFLAGS) -c -o $@ $< - -$(OUT_DIR)/%.ol: %.c - $(CC) $(L_CFLAGS) -c -o $@ $< - -AR ?= ar -RANLIB ?= ranlib -$(OUT_DIR)/libaio.a: $(libaio_objs) - rm -f $(OUT_DIR)/libaio.a - $(AR) r $(OUT_DIR)/libaio.a $^ - $(RANLIB) $(OUT_DIR)/libaio.a - -$(libname): $(libaio_sobjs) libaio.map - $(CC) $(SO_CFLAGS) -Wl,--version-script=libaio.map -Wl,-soname=$(soname) -o $@ $(libaio_sobjs) $(LINK_FLAGS) - -install: $(all_targets) - install -D -m 644 libaio.h $(includedir)/libaio.h - install -D -m 644 libaio.a $(libdir)/libaio.a -ifeq ($(ENABLE_SHARED),1) - install -D -m 755 $(libname) $(libdir)/$(libname) - ln -sf $(libname) $(libdir)/$(soname) - ln -sf $(libname) $(libdir)/libaio.so -endif - -$(libaio_objs): libaio.h - -clean: - rm -f $(all_targets) $(libaio_objs) $(libaio_sobjs) $(soname).new - rm -f *.so* *.a *.o diff --git a/libaio/libaio/aio_ring.h b/libaio/libaio/aio_ring.h deleted file mode 100644 index 3842c4b3c127..000000000000 --- a/libaio/libaio/aio_ring.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef _AIO_RING_H -#define _AIO_RING_H - -#define AIO_RING_MAGIC 0xa10a10a1 - -struct aio_ring { - unsigned id; /* kernel internal index number */ - unsigned nr; /* number of io_events */ - unsigned head; - unsigned tail; - - unsigned magic; - unsigned compat_features; - unsigned incompat_features; - unsigned header_length; /* size of aio_ring */ -}; - -static inline int aio_ring_is_empty(io_context_t ctx, struct timespec *timeout) -{ - struct aio_ring *ring = (struct aio_ring *)ctx; - - if (!ring || ring->magic != AIO_RING_MAGIC) - return 0; - if (!timeout || timeout->tv_sec || timeout->tv_nsec) - return 0; - if (ring->head != ring->tail) - return 0; - return 1; -} - -#endif /* _AIO_RING_H */ diff --git a/libaio/libaio/compat-0_1.c b/libaio/libaio/compat-0_1.c deleted file mode 100644 index 966020ae8c8a..000000000000 --- a/libaio/libaio/compat-0_1.c +++ /dev/null @@ -1,62 +0,0 @@ -/* libaio Linux async I/O interface - - compat-0_1.c : compatibility symbols for libaio 0.1.x-0.3.x - - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include - -#include "libaio.h" -#include "vsys_def.h" - -#include "syscall.h" - - -/* ABI change. Provide partial compatibility on this one for now. */ -SYMVER(compat0_1_io_cancel, io_cancel, 0.1); -int compat0_1_io_cancel(io_context_t ctx, struct iocb *iocb) -{ - struct io_event event; - - /* FIXME: the old ABI would return the event on the completion queue */ - return io_cancel(ctx, iocb, &event); -} - -SYMVER(compat0_1_io_queue_wait, io_queue_wait, 0.1); -int compat0_1_io_queue_wait(io_context_t ctx, struct timespec *when) -{ - struct timespec timeout; - if (when) - timeout = *when; - return io_getevents(ctx, 0, 0, NULL, when ? &timeout : NULL); -} - - -/* ABI change. Provide backwards compatibility for this one. */ -SYMVER(compat0_1_io_getevents, io_getevents, 0.1); -int compat0_1_io_getevents(io_context_t ctx_id, long nr, - struct io_event *events, - const struct timespec *const_timeout) -{ - struct timespec timeout; - if (const_timeout) - timeout = *const_timeout; - return io_getevents(ctx_id, 1, nr, events, - const_timeout ? &timeout : NULL); -} - diff --git a/libaio/libaio/io_cancel.c b/libaio/libaio/io_cancel.c deleted file mode 100644 index 2f0f5f4aa0d4..000000000000 --- a/libaio/libaio/io_cancel.c +++ /dev/null @@ -1,23 +0,0 @@ -/* io_cancel.c - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include "syscall.h" - -io_syscall3(int, io_cancel_0_4, io_cancel, io_context_t, ctx, struct iocb *, iocb, struct io_event *, event) -DEFSYMVER(io_cancel_0_4, io_cancel, 0.4) diff --git a/libaio/libaio/io_destroy.c b/libaio/libaio/io_destroy.c deleted file mode 100644 index 0ab6bd17433d..000000000000 --- a/libaio/libaio/io_destroy.c +++ /dev/null @@ -1,23 +0,0 @@ -/* io_destroy - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include "syscall.h" - -io_syscall1(int, io_destroy, io_destroy, io_context_t, ctx) diff --git a/libaio/libaio/io_getevents.c b/libaio/libaio/io_getevents.c deleted file mode 100644 index c471fbcd70bd..000000000000 --- a/libaio/libaio/io_getevents.c +++ /dev/null @@ -1,35 +0,0 @@ -/* io_getevents.c - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include -#include "syscall.h" -#include "aio_ring.h" - -io_syscall5(int, __io_getevents_0_4, io_getevents, io_context_t, ctx, long, min_nr, long, nr, struct io_event *, events, struct timespec *, timeout) - -int io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event * events, struct timespec * timeout) -{ - if (aio_ring_is_empty(ctx, timeout)) - return 0; - return __io_getevents_0_4(ctx, min_nr, nr, events, timeout); -} - -//DEFSYMVER(io_getevents_0_4, io_getevents, 0.4) diff --git a/libaio/libaio/io_pgetevents.c b/libaio/libaio/io_pgetevents.c deleted file mode 100644 index e6b061468897..000000000000 --- a/libaio/libaio/io_pgetevents.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - libaio Linux async I/O interface - Copyright 2018 Christoph Hellwig. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include -#include -#include "syscall.h" -#include "aio_ring.h" - -#ifdef __NR_io_pgetevents -io_syscall6(int, __io_pgetevents, io_pgetevents, io_context_t, ctx, long, - min_nr, long, nr, struct io_event *, events, - struct timespec *, timeout, void *, sigmask); - -int io_pgetevents(io_context_t ctx, long min_nr, long nr, - struct io_event *events, struct timespec *timeout, - sigset_t *sigmask) -{ - struct { - unsigned long ss; - unsigned long ss_len; - } data; - - if (aio_ring_is_empty(ctx, timeout)) - return 0; - - data.ss = (unsigned long)sigmask; - data.ss_len = _NSIG / 8; - return __io_pgetevents(ctx, min_nr, nr, events, timeout, &data); -} -#else -int io_pgetevents(io_context_t ctx, long min_nr, long nr, - struct io_event *events, struct timespec *timeout, - sigset_t *sigmask) - -{ - return -ENOSYS; -} -#endif /* __NR_io_pgetevents */ diff --git a/libaio/libaio/io_queue_init.c b/libaio/libaio/io_queue_init.c deleted file mode 100644 index 563d1375a425..000000000000 --- a/libaio/libaio/io_queue_init.c +++ /dev/null @@ -1,33 +0,0 @@ -/* io_queue_init.c - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include - -#include "syscall.h" - -int io_queue_init(int maxevents, io_context_t *ctxp) -{ - if (maxevents > 0) { - *ctxp = NULL; - return io_setup(maxevents, ctxp); - } - return -EINVAL; -} diff --git a/libaio/libaio/io_queue_release.c b/libaio/libaio/io_queue_release.c deleted file mode 100644 index 94bbb867a085..000000000000 --- a/libaio/libaio/io_queue_release.c +++ /dev/null @@ -1,27 +0,0 @@ -/* io_queue_release.c - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include - -int io_queue_release(io_context_t ctx) -{ - return io_destroy(ctx); -} diff --git a/libaio/libaio/io_queue_run.c b/libaio/libaio/io_queue_run.c deleted file mode 100644 index e0132f4009e8..000000000000 --- a/libaio/libaio/io_queue_run.c +++ /dev/null @@ -1,39 +0,0 @@ -/* io_submit - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include - -int io_queue_run(io_context_t ctx) -{ - static struct timespec timeout = { 0, 0 }; - struct io_event event; - int ret; - - /* FIXME: batch requests? */ - while (1 == (ret = io_getevents(ctx, 0, 1, &event, &timeout))) { - io_callback_t cb = (io_callback_t)event.data; - struct iocb *iocb = event.obj; - - cb(ctx, iocb, event.res, event.res2); - } - - return ret; -} diff --git a/libaio/libaio/io_queue_wait.c b/libaio/libaio/io_queue_wait.c deleted file mode 100644 index 538d2f3b7bfd..000000000000 --- a/libaio/libaio/io_queue_wait.c +++ /dev/null @@ -1,31 +0,0 @@ -/* io_submit - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#define NO_SYSCALL_ERRNO -#include -#include -#include -#include "syscall.h" - -struct timespec; - -int io_queue_wait_0_4(io_context_t ctx, struct timespec *timeout) -{ - return io_getevents(ctx, 0, 0, NULL, timeout); -} -DEFSYMVER(io_queue_wait_0_4, io_queue_wait, 0.4) diff --git a/libaio/libaio/io_setup.c b/libaio/libaio/io_setup.c deleted file mode 100644 index 4ba1afc993a5..000000000000 --- a/libaio/libaio/io_setup.c +++ /dev/null @@ -1,23 +0,0 @@ -/* io_setup - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include "syscall.h" - -io_syscall2(int, io_setup, io_setup, int, maxevents, io_context_t *, ctxp) diff --git a/libaio/libaio/io_submit.c b/libaio/libaio/io_submit.c deleted file mode 100644 index e22ba549601c..000000000000 --- a/libaio/libaio/io_submit.c +++ /dev/null @@ -1,23 +0,0 @@ -/* io_submit - libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include "syscall.h" - -io_syscall3(int, io_submit, io_submit, io_context_t, ctx, long, nr, struct iocb **, iocbs) diff --git a/libaio/libaio/libaio.h b/libaio/libaio/libaio.h deleted file mode 100644 index 2bc24e089ecf..000000000000 --- a/libaio/libaio/libaio.h +++ /dev/null @@ -1,300 +0,0 @@ -/* /usr/include/libaio.h - * - * Copyright 2000,2001,2002 Red Hat, Inc. - * - * Written by Benjamin LaHaise - * - * libaio Linux async I/O interface - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __LIBAIO_H -#define __LIBAIO_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -struct timespec; -struct sockaddr; -struct iovec; - -typedef struct io_context *io_context_t; - -typedef enum io_iocb_cmd { - IO_CMD_PREAD = 0, - IO_CMD_PWRITE = 1, - - IO_CMD_FSYNC = 2, - IO_CMD_FDSYNC = 3, - - IO_CMD_POLL = 5, - IO_CMD_NOOP = 6, - IO_CMD_PREADV = 7, - IO_CMD_PWRITEV = 8, -} io_iocb_cmd_t; - -/* little endian, 32 bits */ -#if defined(__i386__) || (defined(__arm__) && !defined(__ARMEB__)) || \ - defined(__sh__) || defined(__bfin__) || defined(__MIPSEL__) || \ - defined(__cris__) || (defined(__riscv) && __riscv_xlen == 32) || \ - (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 4) -#define PADDED(x, y) x; unsigned y -#define PADDEDptr(x, y) x; unsigned y -#define PADDEDul(x, y) unsigned long x; unsigned y - -/* little endian, 64 bits */ -#elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__) || \ - (defined(__aarch64__) && defined(__AARCH64EL__)) || \ - (defined(__riscv) && __riscv_xlen == 64) || \ - (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 8) -#define PADDED(x, y) x, y -#define PADDEDptr(x, y) x -#define PADDEDul(x, y) unsigned long x - -/* big endian, 64 bits */ -#elif defined(__powerpc64__) || defined(__s390x__) || \ - (defined(__sparc__) && defined(__arch64__)) || \ - (defined(__aarch64__) && defined(__AARCH64EB__)) || \ - (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ && __SIZEOF_LONG__ == 8) -#define PADDED(x, y) unsigned y; x -#define PADDEDptr(x,y) x -#define PADDEDul(x, y) unsigned long x - -/* big endian, 32 bits */ -#elif defined(__PPC__) || defined(__s390__) || \ - (defined(__arm__) && defined(__ARMEB__)) || \ - defined(__sparc__) || defined(__MIPSEB__) || defined(__m68k__) || \ - defined(__hppa__) || defined(__frv__) || defined(__avr32__) || \ - (defined(__GNUC__) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ && __SIZEOF_LONG__ == 4) -#define PADDED(x, y) unsigned y; x -#define PADDEDptr(x, y) unsigned y; x -#define PADDEDul(x, y) unsigned y; unsigned long x - -#else -#error endian? -#endif - -struct io_iocb_poll { - PADDED(int events, __pad1); -}; /* result code is the set of result flags or -'ve errno */ - -struct io_iocb_sockaddr { - struct sockaddr *addr; - int len; -}; /* result code is the length of the sockaddr, or -'ve errno */ - -struct io_iocb_common { - PADDEDptr(void *buf, __pad1); - PADDEDul(nbytes, __pad2); - long long offset; - long long __pad3; - unsigned flags; - unsigned resfd; -}; /* result code is the amount read or -'ve errno */ - -struct io_iocb_vector { - const struct iovec *vec; - int nr; - long long offset; -}; /* result code is the amount read or -'ve errno */ - -struct iocb { - PADDEDptr(void *data, __pad1); /* Return in the io completion event */ - /* key: For use in identifying io requests */ - /* aio_rw_flags: RWF_* flags (such as RWF_NOWAIT) */ - PADDED(unsigned key, aio_rw_flags); - - short aio_lio_opcode; - short aio_reqprio; - int aio_fildes; - - union { - struct io_iocb_common c; - struct io_iocb_vector v; - struct io_iocb_poll poll; - struct io_iocb_sockaddr saddr; - } u; -}; - -struct io_event { - PADDEDptr(void *data, __pad1); - PADDEDptr(struct iocb *obj, __pad2); - PADDEDul(res, __pad3); - PADDEDul(res2, __pad4); -}; - -#undef PADDED -#undef PADDEDptr -#undef PADDEDul - -typedef void (*io_callback_t)(io_context_t ctx, struct iocb *iocb, long res, long res2); - -/* library wrappers */ -extern int io_queue_init(int maxevents, io_context_t *ctxp); -/*extern int io_queue_grow(io_context_t ctx, int new_maxevents);*/ -extern int io_queue_release(io_context_t ctx); -/*extern int io_queue_wait(io_context_t ctx, struct timespec *timeout);*/ -extern int io_queue_run(io_context_t ctx); - -/* Actual syscalls */ -extern int io_setup(int maxevents, io_context_t *ctxp); -extern int io_destroy(io_context_t ctx); -extern int io_submit(io_context_t ctx, long nr, struct iocb *ios[]); -extern int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); -extern int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout); -extern int io_pgetevents(io_context_t ctx_id, long min_nr, long nr, - struct io_event *events, struct timespec *timeout, - sigset_t *sigmask); - - -static inline void io_set_callback(struct iocb *iocb, io_callback_t cb) -{ - iocb->data = (void *)cb; -} - -static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PREAD; - iocb->aio_reqprio = 0; - iocb->u.c.buf = buf; - iocb->u.c.nbytes = count; - iocb->u.c.offset = offset; -} - -static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PWRITE; - iocb->aio_reqprio = 0; - iocb->u.c.buf = buf; - iocb->u.c.nbytes = count; - iocb->u.c.offset = offset; -} - -static inline void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PREADV; - iocb->aio_reqprio = 0; - iocb->u.c.buf = (void *)iov; - iocb->u.c.nbytes = iovcnt; - iocb->u.c.offset = offset; -} - -static inline void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PWRITEV; - iocb->aio_reqprio = 0; - iocb->u.c.buf = (void *)iov; - iocb->u.c.nbytes = iovcnt; - iocb->u.c.offset = offset; -} - -static inline void io_prep_preadv2(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset, int flags) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PREADV; - iocb->aio_reqprio = 0; - iocb->aio_rw_flags = flags; - iocb->u.c.buf = (void *)iov; - iocb->u.c.nbytes = iovcnt; - iocb->u.c.offset = offset; -} - -static inline void io_prep_pwritev2(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset, int flags) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_PWRITEV; - iocb->aio_reqprio = 0; - iocb->aio_rw_flags = flags; - iocb->u.c.buf = (void *)iov; - iocb->u.c.nbytes = iovcnt; - iocb->u.c.offset = offset; -} - -static inline void io_prep_poll(struct iocb *iocb, int fd, int events) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_POLL; - iocb->aio_reqprio = 0; - iocb->u.poll.events = events; -} - -static inline int io_poll(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd, int events) -{ - io_prep_poll(iocb, fd, events); - io_set_callback(iocb, cb); - return io_submit(ctx, 1, &iocb); -} - -static inline void io_prep_fsync(struct iocb *iocb, int fd) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_FSYNC; - iocb->aio_reqprio = 0; -} - -static inline int io_fsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) -{ - io_prep_fsync(iocb, fd); - io_set_callback(iocb, cb); - return io_submit(ctx, 1, &iocb); -} - -static inline void io_prep_fdsync(struct iocb *iocb, int fd) -{ - memset(iocb, 0, sizeof(*iocb)); - iocb->aio_fildes = fd; - iocb->aio_lio_opcode = IO_CMD_FDSYNC; - iocb->aio_reqprio = 0; -} - -static inline int io_fdsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) -{ - io_prep_fdsync(iocb, fd); - io_set_callback(iocb, cb); - return io_submit(ctx, 1, &iocb); -} - -static inline void io_set_eventfd(struct iocb *iocb, int eventfd) -{ - iocb->u.c.flags |= (1 << 0) /* IOCB_FLAG_RESFD */; - iocb->u.c.resfd = eventfd; -} - -#ifdef __cplusplus -} -#endif - -#endif /* __LIBAIO_H */ diff --git a/libaio/libaio/libaio.map b/libaio/libaio/libaio.map deleted file mode 100644 index ec9d13b5f2c8..000000000000 --- a/libaio/libaio/libaio.map +++ /dev/null @@ -1,27 +0,0 @@ -LIBAIO_0.1 { - global: - io_queue_init; - io_queue_run; - io_queue_wait; - io_queue_release; - io_cancel; - io_submit; - io_getevents; - local: - *; - -}; - -LIBAIO_0.4 { - global: - io_setup; - io_destroy; - io_cancel; - io_getevents; - io_queue_wait; -} LIBAIO_0.1; - -LIBAIO_0.5 { - global: - io_pgetevents; -} LIBAIO_0.4; diff --git a/libaio/libaio/raw_syscall.c b/libaio/libaio/raw_syscall.c deleted file mode 100644 index c3fe4b8deb93..000000000000 --- a/libaio/libaio/raw_syscall.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "syscall.h" - -#if defined(__ia64__) -/* based on code from glibc by Jes Sorensen */ -__asm__(".text\n" - ".globl __ia64_aio_raw_syscall\n" - ".proc __ia64_aio_raw_syscall\n" - "__ia64_aio_raw_syscall:\n" - "alloc r2=ar.pfs,1,0,8,0\n" - "mov r15=r32\n" - "break 0x100000\n" - ";;" - "br.ret.sptk.few b0\n" - ".size __ia64_aio_raw_syscall, . - __ia64_aio_raw_syscall\n" - ".endp __ia64_aio_raw_syscall" -); -#endif - -; diff --git a/libaio/libaio/syscall-alpha.h b/libaio/libaio/syscall-alpha.h deleted file mode 100644 index 0aa4d3d51b67..000000000000 --- a/libaio/libaio/syscall-alpha.h +++ /dev/null @@ -1,5 +0,0 @@ -#define __NR_io_setup 398 -#define __NR_io_destroy 399 -#define __NR_io_getevents 400 -#define __NR_io_submit 401 -#define __NR_io_cancel 402 diff --git a/libaio/libaio/syscall-arm.h b/libaio/libaio/syscall-arm.h deleted file mode 100644 index 556852bbbf36..000000000000 --- a/libaio/libaio/syscall-arm.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * linux/include/asm-arm/unistd.h - * - * Copyright (C) 2001-2005 Russell King - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Please forward _all_ changes to this file to rmk@arm.linux.org.uk, - * no matter what the change is. Thanks! - */ - -#define __NR_OABI_SYSCALL_BASE 0x900000 - -#if defined(__thumb__) || defined(__ARM_EABI__) -#define __NR_SYSCALL_BASE 0 -#else -#define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE -#endif - -#define __NR_io_setup (__NR_SYSCALL_BASE+243) -#define __NR_io_destroy (__NR_SYSCALL_BASE+244) -#define __NR_io_getevents (__NR_SYSCALL_BASE+245) -#define __NR_io_submit (__NR_SYSCALL_BASE+246) -#define __NR_io_cancel (__NR_SYSCALL_BASE+247) diff --git a/libaio/libaio/syscall-generic.h b/libaio/libaio/syscall-generic.h deleted file mode 100644 index b217b531be16..000000000000 --- a/libaio/libaio/syscall-generic.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * This is based on the include/uapi/asm-generic/unistd.h header file - * in the kernel, which is a generic syscall schema for new architectures. - */ - -#define __NR_io_setup 0 -#define __NR_io_destroy 1 -#define __NR_io_submit 2 -#define __NR_io_cancel 3 -#define __NR_io_getevents 4 diff --git a/libaio/libaio/syscall-i386.h b/libaio/libaio/syscall-i386.h deleted file mode 100644 index bc66bb176f88..000000000000 --- a/libaio/libaio/syscall-i386.h +++ /dev/null @@ -1,6 +0,0 @@ -#define __NR_io_setup 245 -#define __NR_io_destroy 246 -#define __NR_io_getevents 247 -#define __NR_io_submit 248 -#define __NR_io_cancel 249 -#define __NR_io_pgetevents 385 diff --git a/libaio/libaio/syscall-ia64.h b/libaio/libaio/syscall-ia64.h deleted file mode 100644 index a21e93b9883f..000000000000 --- a/libaio/libaio/syscall-ia64.h +++ /dev/null @@ -1,5 +0,0 @@ -#define __NR_io_setup 1238 -#define __NR_io_destroy 1239 -#define __NR_io_getevents 1240 -#define __NR_io_submit 1241 -#define __NR_io_cancel 1242 diff --git a/libaio/libaio/syscall-ppc.h b/libaio/libaio/syscall-ppc.h deleted file mode 100644 index dcfb11866392..000000000000 --- a/libaio/libaio/syscall-ppc.h +++ /dev/null @@ -1,5 +0,0 @@ -#define __NR_io_setup 227 -#define __NR_io_destroy 228 -#define __NR_io_getevents 229 -#define __NR_io_submit 230 -#define __NR_io_cancel 231 diff --git a/libaio/libaio/syscall-s390.h b/libaio/libaio/syscall-s390.h deleted file mode 100644 index f0805f5fb03b..000000000000 --- a/libaio/libaio/syscall-s390.h +++ /dev/null @@ -1,5 +0,0 @@ -#define __NR_io_setup 243 -#define __NR_io_destroy 244 -#define __NR_io_getevents 245 -#define __NR_io_submit 246 -#define __NR_io_cancel 247 diff --git a/libaio/libaio/syscall-sparc.h b/libaio/libaio/syscall-sparc.h deleted file mode 100644 index 3e63e92577f4..000000000000 --- a/libaio/libaio/syscall-sparc.h +++ /dev/null @@ -1,5 +0,0 @@ -#define __NR_io_setup 268 -#define __NR_io_destroy 269 -#define __NR_io_submit 270 -#define __NR_io_cancel 271 -#define __NR_io_getevents 272 diff --git a/libaio/libaio/syscall-x86_64.h b/libaio/libaio/syscall-x86_64.h deleted file mode 100644 index 0eccef342e2a..000000000000 --- a/libaio/libaio/syscall-x86_64.h +++ /dev/null @@ -1,6 +0,0 @@ -#define __NR_io_setup 206 -#define __NR_io_destroy 207 -#define __NR_io_getevents 208 -#define __NR_io_submit 209 -#define __NR_io_cancel 210 -#define __NR_io_pgetevents 333 diff --git a/libaio/libaio/syscall.h b/libaio/libaio/syscall.h deleted file mode 100644 index 74bd6fb8f16b..000000000000 --- a/libaio/libaio/syscall.h +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#define _SYMSTR(str) #str -#define SYMSTR(str) _SYMSTR(str) - -#define SYMVER(compat_sym, orig_sym, ver_sym) \ - //__asm__(".symver " SYMSTR(compat_sym) "," SYMSTR(orig_sym) "@LIBAIO_" SYMSTR(ver_sym)); - -#define DEFSYMVER(compat_sym, orig_sym, ver_sym) \ - //__asm__(".symver " SYMSTR(compat_sym) "," SYMSTR(orig_sym) "@@LIBAIO_" SYMSTR(ver_sym)); - -#if defined(__i386__) -#include "syscall-i386.h" -#elif defined(__x86_64__) -#include "syscall-x86_64.h" -#elif defined(__ia64__) -#include "syscall-ia64.h" -#elif defined(__PPC__) -#include "syscall-ppc.h" -#elif defined(__s390__) -#include "syscall-s390.h" -#elif defined(__alpha__) -#include "syscall-alpha.h" -#elif defined(__arm__) -#include "syscall-arm.h" -#elif defined(__sparc__) -#include "syscall-sparc.h" -#elif defined(__aarch64__) || defined(__riscv) -#include "syscall-generic.h" -#else -#warning "using system call numbers from sys/syscall.h" -#endif - -#define _body_io_syscall(sname, args...) \ -{ \ - int ret, saved_errno; \ - saved_errno = errno; \ - ret= syscall(__NR_##sname, ## args); \ - if (ret < 0) { \ - ret = -errno; \ - errno = saved_errno; \ - } \ - return ret; \ -} - -#define io_syscall1(type,fname,sname,type1,arg1) \ -type fname(type1 arg1) \ -_body_io_syscall(sname, (long)arg1) - -#define io_syscall2(type,fname,sname,type1,arg1,type2,arg2) \ -type fname(type1 arg1,type2 arg2) \ -_body_io_syscall(sname, (long)arg1, (long)arg2) - -#define io_syscall3(type,fname,sname,type1,arg1,type2,arg2,type3,arg3) \ -type fname(type1 arg1,type2 arg2,type3 arg3) \ -_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3) - -#define io_syscall4(type,fname,sname,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \ -type fname (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \ -_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4) - -#define io_syscall5(type,fname,sname,type1,arg1,type2,arg2,type3,arg3,type4,arg4, type5,arg5) \ -type fname (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \ -_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4, (long)arg5) - -#define io_syscall6(type,fname,sname,type1,arg1,type2,arg2,type3,arg3, \ - type4,arg4,type5,arg5,type6,arg6) \ -type fname (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5, \ - type6 arg6) \ -_body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3, (long)arg4, \ - (long)arg5, (long)arg6) diff --git a/libaio/libaio/vsys_def.h b/libaio/libaio/vsys_def.h deleted file mode 100644 index 13d032e330b2..000000000000 --- a/libaio/libaio/vsys_def.h +++ /dev/null @@ -1,24 +0,0 @@ -/* libaio Linux async I/O interface - Copyright 2002 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -extern int vsys_io_setup(unsigned nr_reqs, io_context_t *ctxp); -extern int vsys_io_destroy(io_context_t ctx); -extern int vsys_io_submit(io_context_t ctx, long nr, struct iocb *iocbs[]); -extern int vsys_io_cancel(io_context_t ctx, struct iocb *iocb); -extern int vsys_io_wait(io_context_t ctx, struct iocb *iocb, const struct timespec *when); -extern int vsys_io_getevents(io_context_t ctx_id, long nr, struct io_event *events, const struct timespec *timeout); - diff --git a/libaio/src/abi.rs b/libaio/src/abi.rs deleted file mode 100644 index 9d6fe58cb23f..000000000000 --- a/libaio/src/abi.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -// libaio ABI adapted from: -// https://raw.githubusercontent.com/jsgf/libaio-rust/master/src/aioabi.rs -#![allow(dead_code)] - -pub use libc::timespec; -use libc::{c_int, c_long, size_t}; -use std::mem::zeroed; - -#[repr(C)] -pub enum IoCmd { - PRead = 0, - PWrite = 1, - FSync = 2, - FdSync = 3, - // 4 was the experimental IOCB_CMD_PREADX, - Poll = 5, - Noop = 6, - PReadV = 7, - PWriteV = 8, -} - -pub const IOCB_FLAG_RESFD: u32 = 1 << 0; -pub const IOCB_FLAG_IOPRIO: u32 = 1 << 1; - -// Taken from linux/include/linux/aio_abi.h -// This is a kernel ABI, so there should be no need to worry about it changing. -#[repr(C)] -pub struct IoCb { - pub aio_data: u64, // ends up in io_event.data - // NOTE: the order of aio_key and aio_rw_flags could be byte-order depedent - pub aio_key: u32, - pub aio_rw_flags: u32, - - pub aio_lio_opcode: u16, - pub aio_reqprio: u16, - pub aio_fildes: u32, - - pub aio_buf: u64, - pub aio_nbytes: u64, - pub aio_offset: u64, - - pub aio_reserved2: u64, - pub aio_flags: u32, - pub aio_resfd: u32, -} - -impl Default for IoCb { - fn default() -> IoCb { - IoCb { - aio_lio_opcode: IoCmd::Noop as u16, - aio_fildes: (-1_i32) as u32, - ..unsafe { zeroed() } - } - } -} - -#[derive(Clone)] -#[repr(C)] -pub struct IoEvent { - pub data: u64, - pub obj: u64, - pub res: i64, - pub res2: i64, -} - -impl Default for IoEvent { - fn default() -> IoEvent { - unsafe { zeroed() } - } -} - -pub enum IoContext {} -pub type IoContextPtr = *mut IoContext; - -#[repr(C)] -pub struct IoVector { - pub iov_base: *mut u8, - pub iov_len: size_t, -} - -#[link(name = "aio", kind = "static")] -extern "C" { - pub fn io_queue_init(maxevents: c_int, ctxp: *mut IoContextPtr) -> c_int; - pub fn io_queue_release(ctx: IoContextPtr) -> c_int; - pub fn io_queue_run(ctx: IoContextPtr) -> c_int; - pub fn io_setup(maxevents: c_int, ctxp: *mut IoContextPtr) -> c_int; - pub fn io_destroy(ctx: IoContextPtr) -> c_int; - pub fn io_submit(ctx: IoContextPtr, nr: c_long, ios: *mut *mut IoCb) -> c_int; - pub fn io_cancel(ctx: IoContextPtr, iocb: *mut IoCb, evt: *mut IoEvent) -> c_int; - pub fn io_getevents( - ctx_id: IoContextPtr, - min_nr: c_long, - nr: c_long, - events: *mut IoEvent, - timeout: *mut timespec, - ) -> c_int; - pub fn io_set_eventfd(iocb: *mut IoCb, eventfd: c_int); -} - -#[cfg(test)] -mod test { - use std::mem::size_of; - - #[test] - fn test_sizes() { - // Check against kernel ABI - assert!(size_of::() == 32); - assert!(size_of::() == 64); - } -} diff --git a/libaio/src/lib.rs b/libaio/src/lib.rs deleted file mode 100644 index 59bf35e89808..000000000000 --- a/libaio/src/lib.rs +++ /dev/null @@ -1,600 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. -//! Straightforward Linux AIO using Futures/async/await. -//! -//! # Example -//! -//! Use aiofut to schedule writes to a file: -//! -//! ```rust -//! use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; -//! use aiofut::AioBuilder; -//! use std::os::unix::io::AsRawFd; -//! let mut aiomgr = AioBuilder::default().build().unwrap(); -//! let file = std::fs::OpenOptions::new() -//! .read(true) -//! .write(true) -//! .create(true) -//! .truncate(true) -//! .open("/tmp/test") -//! .unwrap(); -//! let fd = file.as_raw_fd(); -//! // keep all returned futures in a vector -//! let ws = vec![(0, "hello"), (5, "world"), (2, "xxxx")] -//! .into_iter() -//! .map(|(off, s)| aiomgr.write(fd, off, s.as_bytes().into(), None)) -//! .collect::>(); -//! // here we use futures::executor::LocalPool to poll all futures -//! let mut pool = LocalPool::new(); -//! let spawner = pool.spawner(); -//! for w in ws.into_iter() { -//! let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { -//! println!("wrote {} bytes", r.0.unwrap()); -//! }); -//! spawner.spawn_local(h).unwrap(); -//! } -//! pool.run(); -//! # std::fs::remove_file("/tmp/test").ok(); -//! ``` - -mod abi; -use abi::IoCb; -use libc::time_t; -use parking_lot::Mutex; -use std::collections::{hash_map, HashMap}; -use std::os::raw::c_long; -use std::os::unix::io::RawFd; -use std::pin::Pin; -use std::sync::{ - atomic::{AtomicPtr, AtomicUsize, Ordering}, - Arc, -}; -use thiserror::Error; - -const LIBAIO_EAGAIN: libc::c_int = -libc::EAGAIN; -const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM; -const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS; - -#[derive(Clone, Debug, Error)] -pub enum AioError { - #[error("maxevents is too large")] - MaxEventsTooLarge, - #[error("low kernel resources")] - LowKernelRes, - #[error("not supported")] - NotSupported, - #[error("other aio error")] - OtherError, -} - -// NOTE: I assume it io_context_t is thread-safe, no? -struct AioContext(abi::IoContextPtr); -unsafe impl Sync for AioContext {} -unsafe impl Send for AioContext {} - -impl std::ops::Deref for AioContext { - type Target = abi::IoContextPtr; - fn deref(&self) -> &abi::IoContextPtr { - &self.0 - } -} - -impl AioContext { - fn new(maxevents: u32) -> Result { - let mut ctx = std::ptr::null_mut(); - unsafe { - match abi::io_setup(maxevents as libc::c_int, &mut ctx) { - 0 => Ok(()), - LIBAIO_EAGAIN => Err(AioError::MaxEventsTooLarge), - LIBAIO_ENOMEM => Err(AioError::LowKernelRes), - LIBAIO_ENOSYS => Err(AioError::NotSupported), - _ => Err(AioError::OtherError), - } - .map(|_| AioContext(ctx)) - } - } -} - -impl Drop for AioContext { - fn drop(&mut self) { - unsafe { - assert_eq!(abi::io_destroy(self.0), 0); - } - } -} - -/// Represent the necessary data for an AIO operation. Memory-safe when moved. -pub struct Aio { - // hold the buffer used by iocb - data: Option>, - iocb: AtomicPtr, - id: u64, -} - -impl Aio { - fn new( - id: u64, - fd: RawFd, - off: u64, - data: Box<[u8]>, - priority: u16, - flags: u32, - opcode: abi::IoCmd, - ) -> Self { - let mut iocb = Box::::default(); - iocb.aio_fildes = fd as u32; - iocb.aio_lio_opcode = opcode as u16; - iocb.aio_reqprio = priority; - iocb.aio_buf = data.as_ptr() as u64; - iocb.aio_nbytes = data.len() as u64; - iocb.aio_offset = off; - iocb.aio_flags = flags; - iocb.aio_data = id; - let iocb = AtomicPtr::new(Box::into_raw(iocb)); - let data = Some(data); - Aio { iocb, id, data } - } -} - -impl Drop for Aio { - fn drop(&mut self) { - unsafe { - drop(Box::from_raw(self.iocb.load(Ordering::Acquire))); - } - } -} - -/// The result of an AIO operation: the number of bytes written on success, -/// or the errno on failure. -pub type AioResult = (Result, Box<[u8]>); - -/// Represents a scheduled (future) asynchronous I/O operation, which gets executed (resolved) -/// automatically. -pub struct AioFuture { - notifier: Arc, - aio_id: u64, -} - -impl AioFuture { - pub fn get_id(&self) -> u64 { - self.aio_id - } -} - -impl std::future::Future for AioFuture { - type Output = AioResult; - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> std::task::Poll { - if let Some(ret) = self.notifier.poll(self.aio_id, cx.waker()) { - std::task::Poll::Ready(ret) - } else { - std::task::Poll::Pending - } - } -} - -impl Drop for AioFuture { - fn drop(&mut self) { - self.notifier.dropped(self.aio_id) - } -} - -#[allow(clippy::enum_variant_names)] -enum AioState { - FutureInit(Aio, bool), - FuturePending(Aio, std::task::Waker, bool), - FutureDone(AioResult), -} - -/// The state machine for finished AIO operations and wakes up the futures. -pub struct AioNotifier { - waiting: Mutex>, - npending: AtomicUsize, - io_ctx: AioContext, - #[cfg(feature = "emulated-failure")] - emul_fail: Option, -} - -impl AioNotifier { - fn register_notify(&self, id: u64, state: AioState) { - let mut waiting = self.waiting.lock(); - assert!(waiting.insert(id, state).is_none()); - } - - fn dropped(&self, id: u64) { - let mut waiting = self.waiting.lock(); - if let hash_map::Entry::Occupied(mut e) = waiting.entry(id) { - match e.get_mut() { - AioState::FutureInit(_, dropped) => *dropped = true, - AioState::FuturePending(_, _, dropped) => *dropped = true, - AioState::FutureDone(_) => { - e.remove(); - } - } - } - } - - fn poll(&self, id: u64, waker: &std::task::Waker) -> Option { - let mut waiting = self.waiting.lock(); - match waiting.entry(id) { - hash_map::Entry::Occupied(e) => { - let v = e.remove(); - match v { - AioState::FutureInit(aio, _) => { - waiting.insert(id, AioState::FuturePending(aio, waker.clone(), false)); - None - } - AioState::FuturePending(aio, waker, dropped) => { - waiting.insert(id, AioState::FuturePending(aio, waker, dropped)); - None - } - AioState::FutureDone(res) => Some(res), - } - } - _ => unreachable!(), - } - } - - fn finish(&self, id: u64, res: i64) { - let mut w = self.waiting.lock(); - self.npending.fetch_sub(1, Ordering::Relaxed); - match w.entry(id) { - hash_map::Entry::Occupied(e) => match e.remove() { - AioState::FutureInit(mut aio, dropped) => { - if !dropped { - let data = aio.data.take().unwrap(); - w.insert( - id, - AioState::FutureDone(if res >= 0 { - (Ok(res as usize), data) - } else { - (Err(-res as i32), data) - }), - ); - } - } - AioState::FuturePending(mut aio, waker, dropped) => { - if !dropped { - let data = aio.data.take().unwrap(); - w.insert( - id, - AioState::FutureDone(if res >= 0 { - (Ok(res as usize), data) - } else { - (Err(-res as i32), data) - }), - ); - waker.wake(); - } - } - AioState::FutureDone(ret) => { - w.insert(id, AioState::FutureDone(ret)); - } - }, - _ => unreachable!(), - } - } -} - -pub struct AioBuilder { - max_events: u32, - max_nwait: u16, - max_nbatched: usize, - timeout: Option, - #[cfg(feature = "emulated-failure")] - emul_fail: Option, -} - -impl Default for AioBuilder { - fn default() -> Self { - AioBuilder { - max_events: 128, - max_nwait: 128, - max_nbatched: 128, - timeout: None, - #[cfg(feature = "emulated-failure")] - emul_fail: None, - } - } -} - -impl AioBuilder { - /// Maximum concurrent async IO operations. - pub fn max_events(&mut self, v: u32) -> &mut Self { - self.max_events = v; - self - } - - /// Maximum complete IOs per poll. - pub fn max_nwait(&mut self, v: u16) -> &mut Self { - self.max_nwait = v; - self - } - - /// Maximum number of IOs per submission. - pub fn max_nbatched(&mut self, v: usize) -> &mut Self { - self.max_nbatched = v; - self - } - - /// Timeout for a polling iteration (default is None). - pub fn timeout(&mut self, sec: u32) -> &mut Self { - self.timeout = Some(sec); - self - } - - #[cfg(feature = "emulated-failure")] - pub fn emulated_failure(&mut self, ef: EmulatedFailureShared) -> &mut Self { - self.emul_fail = Some(ef); - self - } - - /// Build an AIOManager object based on the configuration (and auto-start the background IO - /// scheduling thread). - pub fn build(&mut self) -> Result { - let (scheduler_in, scheduler_out) = new_batch_scheduler(self.max_nbatched); - let (exit_s, exit_r) = crossbeam_channel::bounded(0); - - let notifier = Arc::new(AioNotifier { - io_ctx: AioContext::new(self.max_events)?, - waiting: Mutex::new(HashMap::new()), - npending: AtomicUsize::new(0), - #[cfg(feature = "emulated-failure")] - emul_fail: self.emul_fail.as_ref().cloned(), - }); - let mut aiomgr = AioManager { - notifier, - listener: None, - scheduler_in, - exit_s, - }; - aiomgr.start(scheduler_out, exit_r, self.max_nwait, self.timeout)?; - Ok(aiomgr) - } -} - -pub trait EmulatedFailure: Send { - fn tick(&mut self) -> Option; -} - -pub type EmulatedFailureShared = Arc>; - -/// Manager all AIOs. -pub struct AioManager { - notifier: Arc, - scheduler_in: AioBatchSchedulerIn, - listener: Option>, - exit_s: crossbeam_channel::Sender<()>, -} - -impl AioManager { - fn start( - &mut self, - mut scheduler_out: AioBatchSchedulerOut, - exit_r: crossbeam_channel::Receiver<()>, - max_nwait: u16, - timeout: Option, - ) -> Result<(), AioError> { - let n = self.notifier.clone(); - self.listener = Some(std::thread::spawn(move || { - let mut timespec = timeout.map(|sec: u32| libc::timespec { - tv_sec: sec as time_t, - tv_nsec: 0, - }); - - let mut ongoing = 0; - loop { - // try to quiesce - if ongoing == 0 && scheduler_out.is_empty() { - let mut sel = crossbeam_channel::Select::new(); - sel.recv(&exit_r); - sel.recv(scheduler_out.get_receiver()); - if sel.ready() == 0 { - exit_r.recv().unwrap(); - break; - } - } - // submit as many aios as possible - loop { - let nacc = scheduler_out.submit(&n); - ongoing += nacc; - if nacc == 0 { - break; - } - } - // no need to wait if there is no progress - if ongoing == 0 { - continue; - } - // then block on any finishing aios - let mut events = vec![abi::IoEvent::default(); max_nwait as usize]; - let ret = unsafe { - abi::io_getevents( - *n.io_ctx, - 1, - max_nwait as c_long, - events.as_mut_ptr(), - timespec - .as_mut() - .map(|t| t as *mut libc::timespec) - .unwrap_or(std::ptr::null_mut()), - ) - }; - // TODO: AIO fatal error handling - // avoid empty slice - if ret == 0 { - continue; - } - assert!(ret > 0); - ongoing -= ret as usize; - for ev in events[..ret as usize].iter() { - #[cfg(not(feature = "emulated-failure"))] - n.finish(ev.data, ev.res); - #[cfg(feature = "emulated-failure")] - { - let mut res = ev.res; - if let Some(emul_fail) = n.emul_fail.as_ref() { - let mut ef = emul_fail.lock(); - if let Some(e) = ef.tick() { - res = e - } - } - n.finish(ev.data, res); - } - } - } - })); - Ok(()) - } - - pub fn read(&self, fd: RawFd, offset: u64, length: usize, priority: Option) -> AioFuture { - let priority = priority.unwrap_or(0); - let data = vec![0; length].into_boxed_slice(); - let aio = Aio::new( - self.scheduler_in.next_id(), - fd, - offset, - data, - priority, - 0, - abi::IoCmd::PRead, - ); - self.scheduler_in.schedule(aio, &self.notifier) - } - - pub fn write( - &self, - fd: RawFd, - offset: u64, - data: Box<[u8]>, - priority: Option, - ) -> AioFuture { - let priority = priority.unwrap_or(0); - let aio = Aio::new( - self.scheduler_in.next_id(), - fd, - offset, - data, - priority, - 0, - abi::IoCmd::PWrite, - ); - self.scheduler_in.schedule(aio, &self.notifier) - } - - /// Get a copy of the current data in the buffer. - pub fn copy_data(&self, aio_id: u64) -> Option> { - let w = self.notifier.waiting.lock(); - w.get(&aio_id).map(|state| { - match state { - AioState::FutureInit(aio, _) => aio.data.as_ref().unwrap(), - AioState::FuturePending(aio, _, _) => aio.data.as_ref().unwrap(), - AioState::FutureDone(res) => &res.1, - } - .to_vec() - }) - } - - /// Get the number of pending AIOs (approximation). - pub fn get_npending(&self) -> usize { - self.notifier.npending.load(Ordering::Relaxed) - } -} - -impl Drop for AioManager { - fn drop(&mut self) { - self.exit_s.send(()).unwrap(); - self.listener.take().unwrap().join().unwrap(); - } -} - -pub struct AioBatchSchedulerIn { - queue_in: crossbeam_channel::Sender>, - last_id: std::cell::Cell, -} - -pub struct AioBatchSchedulerOut { - queue_out: crossbeam_channel::Receiver>, - max_nbatched: usize, - leftover: Vec>, -} - -impl AioBatchSchedulerIn { - fn schedule(&self, aio: Aio, notifier: &Arc) -> AioFuture { - let fut = AioFuture { - notifier: notifier.clone(), - aio_id: aio.id, - }; - let iocb = aio.iocb.load(Ordering::Acquire); - notifier.register_notify(aio.id, AioState::FutureInit(aio, false)); - self.queue_in.send(AtomicPtr::new(iocb)).unwrap(); - notifier.npending.fetch_add(1, Ordering::Relaxed); - fut - } - - fn next_id(&self) -> u64 { - let id = self.last_id.get(); - self.last_id.set(id.wrapping_add(1)); - id - } -} - -impl AioBatchSchedulerOut { - fn get_receiver(&self) -> &crossbeam_channel::Receiver> { - &self.queue_out - } - fn is_empty(&self) -> bool { - self.leftover.len() == 0 - } - fn submit(&mut self, notifier: &AioNotifier) -> usize { - let mut quota = self.max_nbatched; - let mut pending = self - .leftover - .iter() - .map(|p| p.load(Ordering::Acquire)) - .collect::>(); - if pending.len() < quota { - quota -= pending.len(); - while let Ok(iocb) = self.queue_out.try_recv() { - pending.push(iocb.load(Ordering::Acquire)); - quota -= 1; - if quota == 0 { - break; - } - } - } - if pending.is_empty() { - return 0; - } - let mut ret = unsafe { - abi::io_submit( - *notifier.io_ctx, - pending.len() as c_long, - pending.as_mut_ptr(), - ) - }; - if ret < 0 && ret == LIBAIO_EAGAIN { - ret = 0 - } - let nacc = ret as usize; - self.leftover = pending[nacc..] - .iter() - .map(|p| AtomicPtr::new(*p)) - .collect::>(); - nacc - } -} - -/// Create the scheduler that submits AIOs in batches. -fn new_batch_scheduler(max_nbatched: usize) -> (AioBatchSchedulerIn, AioBatchSchedulerOut) { - let (queue_in, queue_out) = crossbeam_channel::unbounded(); - let bin = AioBatchSchedulerIn { - queue_in, - last_id: std::cell::Cell::new(0), - }; - let bout = AioBatchSchedulerOut { - queue_out, - max_nbatched, - leftover: Vec::new(), - }; - (bin, bout) -} diff --git a/libaio/tests/simple_test.rs b/libaio/tests/simple_test.rs deleted file mode 100644 index e01b24254d22..000000000000 --- a/libaio/tests/simple_test.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use aiofut::AioBuilder; -use futures::{executor::LocalPool, future::FutureExt, task::LocalSpawnExt}; -use std::{os::unix::io::AsRawFd, path::PathBuf}; - -fn tmp_dir() -> PathBuf { - option_env!("CARGO_TARGET_TMPDIR") - .map(PathBuf::from) - .unwrap_or(std::env::temp_dir()) -} - -#[test] -fn simple1() { - let aiomgr = AioBuilder::default().max_events(100).build().unwrap(); - let file = std::fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(tmp_dir().join("test")) - .unwrap(); - let fd = file.as_raw_fd(); - let ws = vec![(0, "hello"), (5, "world"), (2, "xxxx")] - .into_iter() - .map(|(off, s)| aiomgr.write(fd, off, s.as_bytes().into(), None)) - .collect::>(); - let mut pool = LocalPool::new(); - let spawner = pool.spawner(); - for w in ws.into_iter() { - let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { - println!("wrote {} bytes", r.0.unwrap()); - }); - spawner.spawn_local(h).unwrap(); - } - pool.run(); -} - -#[test] -fn simple2() { - let aiomgr = AioBuilder::default().build().unwrap(); - let file = std::fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(tmp_dir().join("test2")) - .unwrap(); - let fd = file.as_raw_fd(); - let ws = (0..4000) - .map(|i| { - let off = i * 128; - let s = char::from((97 + i % 26) as u8) - .to_string() - .repeat((i + 1) as usize); - aiomgr.write(fd, off as u64, s.as_bytes().into(), None) - }) - .collect::>(); - let mut pool = LocalPool::new(); - let spawner = pool.spawner(); - for w in ws.into_iter() { - let h = spawner.spawn_local_with_handle(w).unwrap().map(|r| { - println!("wrote {} bytes", r.0.unwrap()); - }); - spawner.spawn_local(h).unwrap(); - } - pool.run(); -} diff --git a/storage/Cargo.toml b/storage/Cargo.toml new file mode 100644 index 000000000000..ad9a6860fe9e --- /dev/null +++ b/storage/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "storage" +version = "0.0.4" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1.3.3" +bitflags = "2.5.0" +enum-as-inner = "0.6.0" +hex = "0.4.3" +serde = { version = "1.0.199", features = ["derive"] } +smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } +sha2 = "0.10.8" +integer-encoding = "4.0.0" + +[dev-dependencies] +rand = "0.8.5" +test-case = "3.3.1" diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs new file mode 100644 index 000000000000..e4b40eef6f19 --- /dev/null +++ b/storage/src/hashednode.rs @@ -0,0 +1,253 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use sha2::{Digest, Sha256}; +use std::iter::{self}; + +use crate::{BranchNode, Child, LeafNode, TrieHash}; +use crate::{Node, Path}; + +use integer_encoding::VarInt; + +const MAX_VARINT_SIZE: usize = 10; +const BITS_PER_NIBBLE: u64 = 4; + +/// Returns the hash of `node`, which is at the given `path_prefix`. +pub fn hash_node(node: &Node, path_prefix: &Path) -> TrieHash { + match node { + Node::Branch(node) => { + // All child hashes should be filled in. + // TODO danlaine: Enforce this with the type system. + debug_assert!(node + .children + .iter() + .all(|c| !matches!(c, Some(Child::Node(_))))); + NodeAndPrefix { + node: node.as_ref(), + prefix: path_prefix, + } + .into() + } + Node::Leaf(node) => NodeAndPrefix { + node, + prefix: path_prefix, + } + .into(), + } +} + +/// Returns the serialized representation of `node` used as the pre-image +/// when hashing the node. The node is at the given `path_prefix`. +pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { + // Key, 3 options, value digest + let est_len = node.partial_path().len() + path_prefix.len() + 3 + TrieHash::default().len(); + let mut buf = Vec::with_capacity(est_len); + match node { + Node::Branch(node) => { + NodeAndPrefix { + node: node.as_ref(), + prefix: path_prefix, + } + .write(&mut buf); + } + Node::Leaf(node) => NodeAndPrefix { + node, + prefix: path_prefix, + } + .write(&mut buf), + } + buf.into_boxed_slice() +} + +pub trait HasUpdate { + fn update>(&mut self, data: T); +} + +impl HasUpdate for Sha256 { + fn update>(&mut self, data: T) { + sha2::Digest::update(self, data) + } +} + +impl HasUpdate for Vec { + fn update>(&mut self, data: T) { + self.extend(data.as_ref()); + } +} + +#[derive(Clone, Debug)] +/// A ValueDigest is either a node's value or the hash of its value. +pub enum ValueDigest { + /// The node's value. + Value(T), + /// TODO this variant will be used when we deserialize a proof node + /// from a remote Firewood instance. The serialized proof node they + /// send us may the hash of the value, not the value itself. + _Hash(T), +} + +/// A node in the trie that can be hashed. +pub trait Hashable { + /// The key of the node where each byte is a nibble. + fn key(&self) -> impl Iterator + Clone; + /// The node's value or hash. + fn value_digest(&self) -> Option>; + /// Each element is a child's index and hash. + /// Yields 0 elements if the node is a leaf. + fn children(&self) -> impl Iterator + Clone; +} + +/// A preimage of a hash. +pub trait Preimage { + /// Returns the hash of this preimage. + fn to_hash(&self) -> TrieHash; + /// Write this hash preimage to `buf`. + fn write(&self, buf: &mut impl HasUpdate); +} + +// Implement Preimage for all types that implement Hashable +impl Preimage for T { + fn to_hash(&self) -> TrieHash { + let mut hasher = Sha256::new(); + self.write(&mut hasher); + hasher.finalize().into() + } + + fn write(&self, buf: &mut impl HasUpdate) { + let children = self.children(); + + let num_children = children.clone().count() as u64; + add_varint_to_buf(buf, num_children); + + for (index, hash) in children { + add_varint_to_buf(buf, index as u64); + buf.update(hash); + } + + // Add value digest (if any) to hash pre-image + add_value_digest_to_buf(buf, self.value_digest()); + + // Add key length (in bits) to hash pre-image + let mut key = self.key(); + // let mut key = key.as_ref().iter(); + let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; + add_varint_to_buf(buf, key_bit_len); + + // Add key to hash pre-image + while let Some(high_nibble) = key.next() { + let low_nibble = key.next().unwrap_or(0); + let byte = (high_nibble << 4) | low_nibble; + buf.update([byte]); + } + } +} + +trait HashableNode { + fn partial_path(&self) -> impl Iterator + Clone; + fn value(&self) -> Option<&[u8]>; + fn children_iter(&self) -> impl Iterator + Clone; +} + +impl HashableNode for BranchNode { + fn partial_path(&self) -> impl Iterator + Clone { + self.partial_path.0.iter().copied() + } + + fn value(&self) -> Option<&[u8]> { + self.value.as_deref() + } + + fn children_iter(&self) -> impl Iterator + Clone { + self.children_iter() + } +} + +impl HashableNode for LeafNode { + fn partial_path(&self) -> impl Iterator + Clone { + self.partial_path.0.iter().copied() + } + + fn value(&self) -> Option<&[u8]> { + Some(&self.value) + } + + fn children_iter(&self) -> impl Iterator + Clone { + iter::empty() + } +} + +struct NodeAndPrefix<'a, N: HashableNode> { + node: &'a N, + prefix: &'a Path, +} + +impl<'a, N: HashableNode> From> for TrieHash { + fn from(node: NodeAndPrefix<'a, N>) -> Self { + node.to_hash() + } +} + +impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { + fn key(&self) -> impl Iterator + Clone { + self.prefix + .0 + .iter() + .copied() + .chain(self.node.partial_path()) + } + + fn value_digest(&self) -> Option> { + self.node.value().map(ValueDigest::Value) + } + + fn children(&self) -> impl Iterator + Clone { + self.node.children_iter() + } +} + +fn add_value_digest_to_buf>( + buf: &mut H, + value_digest: Option>, +) { + let Some(value_digest) = value_digest else { + let value_exists: u8 = 0; + buf.update([value_exists]); + return; + }; + + let value_exists: u8 = 1; + buf.update([value_exists]); + + match value_digest { + ValueDigest::Value(value) if value.as_ref().len() >= 32 => { + let hash = Sha256::digest(value); + add_len_and_value_to_buf(buf, hash); + } + ValueDigest::Value(value) => { + add_len_and_value_to_buf(buf, value); + } + ValueDigest::_Hash(hash) => { + add_len_and_value_to_buf(buf, hash); + } + } +} + +#[inline] +/// Writes the length of `value` and `value` to `buf`. +fn add_len_and_value_to_buf>(buf: &mut H, value: V) { + let value_len = value.as_ref().len(); + buf.update([value_len as u8]); + buf.update(value); +} + +#[inline] +/// Encodes `value` as a varint and writes it to `buf`. +fn add_varint_to_buf(buf: &mut H, value: u64) { + let mut buf_arr = [0u8; MAX_VARINT_SIZE]; + let len = value.encode_var(&mut buf_arr); + buf.update( + buf_arr + .get(..len) + .expect("length is always less than MAX_VARINT_SIZE"), + ); +} diff --git a/storage/src/lib.rs b/storage/src/lib.rs new file mode 100644 index 000000000000..3821240bf0f4 --- /dev/null +++ b/storage/src/lib.rs @@ -0,0 +1,33 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. +#![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] +#![forbid(unsafe_code)] + +//! # storage implements the storage of a [Node] on top of a LinearStore +//! +//! Nodes are stored at a [LinearAddress] within a [ReadableStorage]. +//! +//! The [NodeStore] maintains a free list and the [LinearAddress] of a root node. +//! +//! A [NodeStore] is backed by a [ReadableStorage] which is persisted storage. + +mod hashednode; +mod linear; +mod node; +mod nodestore; +mod trie_hash; + +// re-export these so callers don't need to know where they are +pub use hashednode::{hash_node, hash_preimage, Hashable, Preimage, ValueDigest}; +pub use linear::{ReadableStorage, WritableStorage}; +pub use node::{ + path::NibblesIterator, path::Path, BranchNode, Child, LeafNode, Node, PathIterItem, +}; +pub use nodestore::{ + Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, + NodeStore, ReadInMemoryNode, RootReader, TrieReader, UpdateError, +}; + +pub use linear::{filebacked::FileBacked, memory::MemStore}; + +pub use trie_hash::TrieHash; diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs new file mode 100644 index 000000000000..ecbe601d069b --- /dev/null +++ b/storage/src/linear/filebacked.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// during development only +#![allow(dead_code)] + +// This synchronous file layer is a simple implementation of what we +// want to do for I/O. This uses a [Mutex] lock around a simple `File` +// object. Instead, we probably should use an IO system that can perform multiple +// read/write operations at once + +use std::fs::{File, OpenOptions}; +use std::io::{Error, Read, Seek}; +use std::os::unix::fs::FileExt; +use std::path::PathBuf; +use std::sync::Mutex; + +use super::ReadableStorage; + +#[derive(Debug)] +/// A [ReadableStorage] backed by a file +pub struct FileBacked { + fd: Mutex, +} + +impl FileBacked { + /// Create or open a file at a given path + pub fn new(path: PathBuf, truncate: bool) -> Result { + let fd = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(truncate) + .open(path)?; + + Ok(Self { fd: Mutex::new(fd) }) + } +} + +impl ReadableStorage for FileBacked { + fn stream_from(&self, addr: u64) -> Result, Error> { + let mut fd = self.fd.lock().expect("p"); + fd.seek(std::io::SeekFrom::Start(addr))?; + Ok(Box::new(fd.try_clone().expect("poisoned lock"))) + } + + fn size(&self) -> Result { + self.fd + .lock() + .expect("poisoned lock") + .seek(std::io::SeekFrom::End(0)) + } +} + +impl FileBacked { + /// Write to the backend filestore. This does not implement [crate::WritableStorage] + /// because we don't want someone accidentally writing nodes directly to disk + pub fn write(&mut self, offset: u64, object: &[u8]) -> Result { + self.fd + .lock() + .expect("poisoned lock") + .write_at(object, offset) + } +} diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs new file mode 100644 index 000000000000..a77ab83d4001 --- /dev/null +++ b/storage/src/linear/memory.rs @@ -0,0 +1,85 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::{ReadableStorage, WritableStorage}; +use std::{ + io::{Cursor, Read}, + sync::Mutex, +}; + +#[derive(Debug, Default)] +/// An in-memory impelementation of [WritableStorage] and [ReadableStorage] +pub struct MemStore { + bytes: Mutex>, +} + +impl MemStore { + /// Create a new, empty [MemStore] + pub const fn new(bytes: Vec) -> Self { + Self { + bytes: Mutex::new(bytes), + } + } +} + +impl WritableStorage for MemStore { + fn write(&self, offset: u64, object: &[u8]) -> Result { + let offset = offset as usize; + let mut guard = self.bytes.lock().expect("poisoned lock"); + if offset + object.len() > guard.len() { + guard.resize(offset + object.len(), 0); + } + guard[offset..offset + object.len()].copy_from_slice(object); + Ok(object.len()) + } +} + +impl ReadableStorage for MemStore { + fn stream_from(&self, addr: u64) -> Result, std::io::Error> { + let bytes = self + .bytes + .lock() + .expect("poisoned lock") + .get(addr as usize..) + .unwrap_or_default() + .to_owned(); + + Ok(Box::new(Cursor::new(bytes))) + } + + fn size(&self) -> Result { + Ok(self.bytes.lock().expect("poisoned lock").len() as u64) + } +} + +#[allow(clippy::unwrap_used)] +#[cfg(test)] +mod test { + use super::*; + use test_case::test_case; + + #[test_case(&[(0,&[1, 2, 3])],(0,&[1, 2, 3]); "write to empty store")] + #[test_case(&[(0,&[1, 2, 3])],(1,&[2, 3]); "read from middle of store")] + #[test_case(&[(0,&[1, 2, 3])],(2,&[3]); "read from end of store")] + #[test_case(&[(0,&[1, 2, 3])],(3,&[]); "read past end of store")] + #[test_case(&[(0,&[1, 2, 3]),(3,&[4,5,6])],(0,&[1, 2, 3,4,5,6]); "write to end of store")] + #[test_case(&[(0,&[1, 2, 3]),(0,&[4])],(0,&[4,2,3]); "overwrite start of store")] + #[test_case(&[(0,&[1, 2, 3]),(1,&[4])],(0,&[1,4,3]); "overwrite middle of store")] + #[test_case(&[(0,&[1, 2, 3]),(2,&[4])],(0,&[1,2,4]); "overwrite end of store")] + #[test_case(&[(0,&[1, 2, 3]),(2,&[4,5])],(0,&[1,2,4,5]); "overwrite/extend end of store")] + fn test_in_mem_write_linear_store(writes: &[(u64, &[u8])], expected: (u64, &[u8])) { + let store = MemStore { + bytes: Mutex::new(vec![]), + }; + assert_eq!(store.size().unwrap(), 0); + + for write in writes { + store.write(write.0, write.1).unwrap(); + } + + let mut reader = store.stream_from(expected.0).unwrap(); + let mut read_bytes = vec![]; + reader.read_to_end(&mut read_bytes).unwrap(); + assert_eq!(read_bytes, expected.1); + } +} diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs new file mode 100644 index 000000000000..942ca9c11220 --- /dev/null +++ b/storage/src/linear/mod.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// TODO: remove this once we have code that uses it +#![allow(dead_code)] + +//! A LinearStore provides a view of a set of bytes at +//! a given time. A LinearStore has three different types, +//! which refer to another base type, as follows: +//! ```mermaid +//! stateDiagram-v2 +//! R1(Committed) --> R2(Committed) +//! R2(Committed) --> R3(FileBacked) +//! P1(Proposed) --> R3(FileBacked) +//! P2(Proposed) --> P1(Proposed) +//! ``` +//! +//! Each type is described in more detail below. + +use std::fmt::Debug; +use std::io::{Error, Read}; +pub(super) mod filebacked; +pub mod memory; + +/// Trait for readable storage. +pub trait ReadableStorage: Debug + Sync + Send { + /// Stream data from the specified address. + /// + /// # Arguments + /// + /// * `addr` - The address from which to stream the data. + /// + /// # Returns + /// + /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. + + fn stream_from(&self, addr: u64) -> Result, Error>; + + /// Return the size of the underlying storage, in bytes + fn size(&self) -> Result; +} + +/// Trait for writable storage. +pub trait WritableStorage: ReadableStorage { + /// Writes the given object at the specified offset. + /// + /// # Arguments + /// + /// * `offset` - The offset at which to write the object. + /// * `object` - The object to write. + /// + /// # Returns + /// + /// The number of bytes written, or an error if the write operation fails. + fn write(&self, offset: u64, object: &[u8]) -> Result; +} diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs new file mode 100644 index 000000000000..dd1abe7f2e50 --- /dev/null +++ b/storage/src/node/branch.rs @@ -0,0 +1,173 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use serde::{ser::SerializeStruct as _, Deserialize, Serialize}; + +use crate::{LeafNode, LinearAddress, Node, Path, TrieHash}; +use std::fmt::{Debug, Error as FmtError, Formatter}; + +#[derive(PartialEq, Eq, Clone, Debug)] +/// A child of a branch node. +pub enum Child { + /// There is a child at this index, but we haven't hashed it + /// or written it to storage yet. + Node(Node), + /// We know the child's address and hash. + AddressWithHash(LinearAddress, TrieHash), +} + +#[derive(PartialEq, Eq, Clone)] +/// A branch node +pub struct BranchNode { + /// The partial path for this branch + pub partial_path: Path, + + /// The value of the data for this branch, if any + pub value: Option>, + + /// The children of this branch. + /// Element i is the child at index i, or None if there is no child at that index. + /// Each element is (child_hash, child_address). + /// child_address is None if we don't know the child's hash. + pub children: [Option; Self::MAX_CHILDREN], +} + +impl Serialize for BranchNode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("BranchNode", 3)?; + state.serialize_field("partial_path", &self.partial_path)?; + state.serialize_field("value", &self.value)?; + + let mut children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN] = + Default::default(); + + for (i, c) in self.children.iter().enumerate() { + match c { + None => {} + Some(Child::Node(_)) => { + return Err(serde::ser::Error::custom( + "node has children in memory. TODO make this impossible.", + )) + } + Some(Child::AddressWithHash(addr, hash)) => { + children[i] = Some((*addr, (*hash).clone())) + } + } + } + + state.serialize_field("children", &children)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for BranchNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct SerializedBranchNode { + partial_path: Path, + value: Option>, + children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN], + } + + let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?; + + let mut children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + for (i, c) in s.children.iter().enumerate() { + if let Some((addr, hash)) = c { + children[i] = Some(Child::AddressWithHash(*addr, hash.clone())); + } + } + + Ok(BranchNode { + partial_path: s.partial_path, + value: s.value, + children, + }) + } +} + +// struct SerializedBranchNode<'a> { +// partial_path: &'a Path, +// value: Option<&'a [u8]>, +// children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN], +// } + +impl Debug for BranchNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "[Branch")?; + write!(f, r#" path="{:?}""#, self.partial_path)?; + + for (i, c) in self.children.iter().enumerate() { + match c { + None => {} + Some(Child::Node(_)) => {} //TODO + Some(Child::AddressWithHash(addr, hash)) => write!( + f, + "(index: {i:?}), address={addr:?}, hash={:?})", + hex::encode(hash), + )?, + } + } + + write!( + f, + " v={}]", + match &self.value { + Some(v) => hex::encode(&**v), + None => "nil".to_string(), + } + ) + } +} + +impl BranchNode { + /// The maximum number of children in a [BranchNode] + pub const MAX_CHILDREN: usize = 16; + + /// Returns the address of the child at the given index. + /// Panics if `child_index` >= [BranchNode::MAX_CHILDREN]. + pub fn child(&self, child_index: u8) -> &Option { + self.children + .get(child_index as usize) + .expect("child_index is in bounds") + } + + /// Update the child at `child_index` to be `new_child_addr`. + /// If `new_child_addr` is None, the child is removed. + pub fn update_child(&mut self, child_index: u8, new_child: Option) { + let child = self + .children + .get_mut(child_index as usize) + .expect("child_index is in bounds"); + + *child = new_child; + } + + /// Returns (index, hash) for each child that has a hash set. + pub fn children_iter(&self) -> impl Iterator + Clone { + self.children.iter().enumerate().filter_map( + #[allow(clippy::indexing_slicing)] + |(i, child)| match child { + None => None, + Some(Child::Node(_)) => unreachable!("TODO make unreachable"), + Some(Child::AddressWithHash(_, hash)) => Some((i, hash)), + }, + ) + } +} + +impl From<&LeafNode> for BranchNode { + fn from(leaf: &LeafNode) -> Self { + BranchNode { + partial_path: leaf.partial_path.clone(), + value: Some(leaf.value.clone()), + children: Default::default(), + } + } +} diff --git a/storage/src/node/leaf.rs b/storage/src/node/leaf.rs new file mode 100644 index 000000000000..0e554f27d95c --- /dev/null +++ b/storage/src/node/leaf.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use serde::{Deserialize, Serialize}; + +use std::fmt::{Debug, Error as FmtError, Formatter}; + +use crate::Path; + +/// A leaf node +#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct LeafNode { + /// The path of this leaf, but only the remaining nibbles + pub partial_path: Path, + + /// The value associated with this leaf + pub value: Box<[u8]>, +} + +impl Debug for LeafNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!( + f, + "[Leaf {:?} {}]", + self.partial_path, + hex::encode(&*self.value) + ) + } +} diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs new file mode 100644 index 000000000000..b27bace62d12 --- /dev/null +++ b/storage/src/node/mod.rs @@ -0,0 +1,102 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use enum_as_inner::EnumAsInner; +use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, sync::Arc}; + +mod branch; +mod leaf; +pub mod path; + +pub use branch::BranchNode; +pub use branch::Child; +pub use leaf::LeafNode; + +use crate::Path; + +/// A node, either a Branch or Leaf + +// TODO: explain why Branch is boxed but Leaf is not +#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Serialize, Deserialize)] +pub enum Node { + /// This node is a [BranchNode] + Branch(Box), + /// This node is a [LeafNode] + Leaf(LeafNode), +} + +impl Default for Node { + fn default() -> Self { + Node::Leaf(LeafNode { + partial_path: Path::new(), + value: Box::default(), + }) + } +} + +impl Node { + /// Returns the partial path of the node. + pub fn partial_path(&self) -> &Path { + match self { + Node::Branch(b) => &b.partial_path, + Node::Leaf(l) => &l.partial_path, + } + } + + /// Updates the partial path of the node to `partial_path`. + pub fn update_partial_path(&mut self, partial_path: Path) { + match self { + Node::Branch(b) => b.partial_path = partial_path, + Node::Leaf(l) => l.partial_path = partial_path, + } + } + + /// Updates the value of the node to `value`. + pub fn update_value(&mut self, value: Box<[u8]>) { + match self { + Node::Branch(b) => b.value = Some(value), + Node::Leaf(l) => l.value = value, + } + } + + /// Returns a new `Arc` which is the same as `self` but with the given `partial_path`. + pub fn new_with_partial_path(self: &Node, partial_path: Path) -> Node { + match self { + Node::Branch(b) => Node::Branch(Box::new(BranchNode { + partial_path, + value: b.value.clone(), + children: b.children.clone(), + })), + Node::Leaf(l) => Node::Leaf(LeafNode { + partial_path, + value: l.value.clone(), + }), + } + } + + /// Returns Some(value) inside the node, or None if the node is a branch + /// with no value. + pub fn value(&self) -> Option<&[u8]> { + match self { + Node::Branch(b) => b.value.as_deref(), + Node::Leaf(l) => Some(&l.value), + } + } +} + +/// A path iterator item, which has the key nibbles up to this point, +/// a node, the address of the node, and the nibble that points to the +/// next child down the list +#[derive(Debug)] +pub struct PathIterItem { + /// The key of the node at `address` as nibbles. + pub key_nibbles: Box<[u8]>, + /// A reference to the node + pub node: Arc, + /// The next item returned by the iterator is a child of `node`. + /// Specifically, it's the child at index `next_nibble` in `node`'s + /// children array. + /// None if `node` is the last node in the path. + pub next_nibble: Option, +} diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs new file mode 100644 index 000000000000..f24ea01b903e --- /dev/null +++ b/storage/src/node/path.rs @@ -0,0 +1,304 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// TODO: remove bitflags, we only use one bit +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; +use std::iter::FusedIterator; +use std::{ + fmt::{self, Debug}, + iter::once, +}; + +static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + +/// Path is part or all of a node's path in the trie. +/// Each element is a nibble. +#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Path(pub SmallVec<[u8; 64]>); + +impl Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for nib in self.0.iter() { + if *nib > 0xf { + write!(f, "[invalid {:02x}] ", *nib)?; + } else { + write!(f, "{:x} ", *nib)?; + } + } + Ok(()) + } +} + +impl std::ops::Deref for Path { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl> From for Path { + fn from(value: T) -> Self { + Self(SmallVec::from_slice(value.as_ref())) + } +} + +bitflags! { + // should only ever be the size of a nibble + struct Flags: u8 { + const ODD_LEN = 0b0001; + } +} + +impl Path { + /// Return an iterator over the encoded bytes + pub fn iter_encoded(&self) -> impl Iterator + '_ { + let mut flags = Flags::empty(); + + let has_odd_len = self.0.len() & 1 == 1; + + let extra_byte = if has_odd_len { + flags.insert(Flags::ODD_LEN); + + None + } else { + Some(0) + }; + + once(flags.bits()) + .chain(extra_byte) + .chain(self.0.iter().copied()) + } + + /// Creates a Path from a [Iterator] or other iterator that returns + /// nibbles + pub fn from_nibbles_iterator>(nibbles_iter: T) -> Self { + Path(SmallVec::from_iter(nibbles_iter)) + } + + /// Creates an empty Path + pub fn new() -> Self { + Path(SmallVec::new()) + } + + /// Read from an iterator that returns nibbles with a prefix + /// The prefix is one optional byte -- if not present, the Path is empty + /// If there is one byte, and the byte contains a [Flags::ODD_LEN] (0x1) + /// then there is another discarded byte after that. + #[cfg(test)] + pub fn from_encoded_iter>(mut iter: Iter) -> Self { + let flags = Flags::from_bits_retain(iter.next().unwrap_or_default()); + + if !flags.contains(Flags::ODD_LEN) { + let _ = iter.next(); + } + + Self(iter.collect()) + } + + /// Add nibbles to the end of a path + pub fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } + + /// Create an iterator that returns the bytes from the underlying nibbles + /// If there is an odd nibble at the end, it is dropped + pub fn bytes_iter(&self) -> BytesIterator<'_> { + BytesIterator { + nibbles_iter: self.iter(), + } + } + + /// Create a boxed set of bytes from the Path + pub fn bytes(&self) -> Box<[u8]> { + self.bytes_iter().collect() + } +} + +/// Returns the nibbles in `nibbles_iter` as compressed bytes. +/// That is, each two nibbles are combined into a single byte. +#[derive(Debug)] +pub struct BytesIterator<'a> { + nibbles_iter: std::slice::Iter<'a, u8>, +} + +impl Iterator for BytesIterator<'_> { + type Item = u8; + fn next(&mut self) -> Option { + if let Some(&hi) = self.nibbles_iter.next() { + if let Some(&lo) = self.nibbles_iter.next() { + return Some(hi * 16 + lo); + } + } + None + } + + // this helps make the collection into a box faster + fn size_hint(&self) -> (usize, Option) { + ( + self.nibbles_iter.size_hint().0 / 2, + self.nibbles_iter.size_hint().1.map(|max| max / 2), + ) + } +} + +/// Iterates over the nibbles in `data`. +/// That is, each byte in `data` is converted to two nibbles. +#[derive(Clone, Debug)] +pub struct NibblesIterator<'a> { + data: &'a [u8], + head: usize, + tail: usize, +} + +impl<'a> FusedIterator for NibblesIterator<'a> {} + +impl<'a> Iterator for NibblesIterator<'a> { + type Item = u8; + + fn next(&mut self) -> Option { + if self.is_empty() { + return None; + } + let result = if self.head % 2 == 0 { + #[allow(clippy::indexing_slicing)] + NIBBLES[(self.data[self.head / 2] >> 4) as usize] + } else { + #[allow(clippy::indexing_slicing)] + NIBBLES[(self.data[self.head / 2] & 0xf) as usize] + }; + self.head += 1; + Some(result) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.tail - self.head; + (remaining, Some(remaining)) + } + + fn nth(&mut self, n: usize) -> Option { + self.head += std::cmp::min(n, self.tail - self.head); + self.next() + } +} + +impl<'a> NibblesIterator<'a> { + #[inline(always)] + const fn is_empty(&self) -> bool { + self.head == self.tail + } + + /// Returns a new `NibblesIterator` over the given `data`. + /// Each byte in `data` is converted to two nibbles. + pub const fn new(data: &'a [u8]) -> Self { + NibblesIterator { + data, + head: 0, + tail: 2 * data.len(), + } + } +} + +impl<'a> DoubleEndedIterator for NibblesIterator<'a> { + fn next_back(&mut self) -> Option { + if self.is_empty() { + return None; + } + + let result = if self.tail % 2 == 0 { + #[allow(clippy::indexing_slicing)] + NIBBLES[(self.data[self.tail / 2 - 1] & 0xf) as usize] + } else { + #[allow(clippy::indexing_slicing)] + NIBBLES[(self.data[self.tail / 2] >> 4) as usize] + }; + self.tail -= 1; + + Some(result) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.tail -= std::cmp::min(n, self.tail - self.head); + self.next_back() + } +} + +#[cfg(test)] +#[allow(clippy::indexing_slicing)] +mod test { + use super::*; + use std::fmt::Debug; + use test_case::test_case; + + static TEST_BYTES: [u8; 4] = [0xde, 0xad, 0xbe, 0xef]; + + #[test] + fn happy_regular_nibbles() { + let iter = NibblesIterator::new(&TEST_BYTES); + let expected = [0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; + + assert!(iter.eq(expected)); + } + + #[test] + fn size_hint() { + let mut iter = NibblesIterator::new(&TEST_BYTES); + assert_eq!((8, Some(8)), iter.size_hint()); + let _ = iter.next(); + assert_eq!((7, Some(7)), iter.size_hint()); + } + + #[test] + fn backwards() { + let iter = NibblesIterator::new(&TEST_BYTES).rev(); + let expected = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd]; + assert!(iter.eq(expected)); + } + + #[test] + fn nth_back() { + let mut iter = NibblesIterator::new(&TEST_BYTES); + assert_eq!(iter.nth_back(0), Some(0xf)); + assert_eq!(iter.nth_back(0), Some(0xe)); + assert_eq!(iter.nth_back(1), Some(0xb)); + assert_eq!(iter.nth_back(2), Some(0xe)); + assert_eq!(iter.nth_back(0), Some(0xd)); + assert_eq!(iter.nth_back(0), None); + } + + #[test] + fn empty() { + let nib = NibblesIterator::new(&[]); + assert!(nib.is_empty()); + let it = nib.into_iter(); + assert!(it.is_empty()); + assert_eq!(it.size_hint().0, 0); + } + + #[test] + fn not_empty_because_of_data() { + let mut iter = NibblesIterator::new(&[1]); + assert!(!iter.is_empty()); + assert!(!iter.is_empty()); + assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.next(), Some(0)); + assert!(!iter.is_empty()); + assert_eq!(iter.size_hint(), (1, Some(1))); + assert_eq!(iter.next(), Some(1)); + assert!(iter.is_empty()); + assert_eq!(iter.size_hint(), (0, Some(0))); + } + + #[test_case([0, 0, 2, 3], [2, 3])] + #[test_case([1, 2, 3, 4], [2, 3, 4])] + fn encode_decode + PartialEq + Debug, U: AsRef<[u8]>>(encode: T, expected: U) { + let from_encoded = Path::from_encoded_iter(encode.as_ref().iter().copied()); + assert_eq!( + from_encoded.0, + SmallVec::<[u8; 32]>::from_slice(expected.as_ref()) + ); + let to_encoded = from_encoded.iter_encoded().collect::>(); + assert_eq!(encode.as_ref(), to_encoded.as_ref()); + } +} diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs new file mode 100644 index 000000000000..45a6739f5893 --- /dev/null +++ b/storage/src/nodestore.rs @@ -0,0 +1,903 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![allow(dead_code)] + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::Debug; +/// The [NodeStore] handles the serialization of nodes and +/// free space management of nodes in the page store. It lays out the format +/// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] +/// and a [FreeSpaceHeader] at the beginning +use std::io::{Error, ErrorKind, Write}; +use std::iter::once; +use std::num::NonZeroU64; +use std::sync::Arc; + +use crate::hashednode::hash_node; +use crate::node::Node; +use crate::{Child, Path, ReadableStorage, TrieHash}; + +use super::linear::WritableStorage; + +/// [NodeStore] divides the linear store into blocks of different sizes. +/// [AREA_SIZES] is every valid block size. +const AREA_SIZES: [u64; 21] = [ + 1 << MIN_AREA_SIZE_LOG, // Min block size is 8 + 1 << 4, + 1 << 5, + 1 << 6, + 1 << 7, + 1 << 8, + 1 << 9, + 1 << 10, + 1 << 11, + 1 << 12, + 1 << 13, + 1 << 14, + 1 << 15, + 1 << 16, + 1 << 17, + 1 << 18, + 1 << 19, + 1 << 20, + 1 << 21, + 1 << 22, + 1 << 23, // 16 MiB +]; + +/// The type of an index into the [AREA_SIZES] array +/// This is not usize because we can store this as a single byte +pub type AreaIndex = u8; + +// TODO danlaine: have type for index in AREA_SIZES +// Implement try_into() for it. +const MIN_AREA_SIZE_LOG: AreaIndex = 3; +const NUM_AREA_SIZES: usize = AREA_SIZES.len(); +const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; +const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; + +const SOME_FREE_LIST_ELT_SIZE: u64 = 1 + std::mem::size_of::() as u64; +const FREE_LIST_MAX_SIZE: u64 = NUM_AREA_SIZES as u64 * SOME_FREE_LIST_ELT_SIZE; + +/// Number of children in a branch +const BRANCH_CHILDREN: usize = 16; + +/// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. +fn area_size_to_index(n: u64) -> Result { + if n > MAX_AREA_SIZE { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Node size {} is too large", n), + )); + } + + if n <= MIN_AREA_SIZE { + return Ok(0); + } + + let mut log = n.ilog2(); + // If n is not a power of 2, we need to round up to the next power of 2. + if n != 1 << log { + log += 1; + } + + Ok(log as AreaIndex - MIN_AREA_SIZE_LOG) +} + +/// Objects cannot be stored at the zero address, so a [LinearAddress] is guaranteed not +/// to be zero. This reserved zero can be used as a [None] value for some use cases. In particular, +/// branches can use `Option` which is the same size as a [LinearAddress] +pub type LinearAddress = NonZeroU64; + +/// Each [StoredArea] contains an [Area] which is either a [Node] or a [FreeArea]. +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +enum Area { + Node(T), + Free(U), +} + +/// Every item stored in the [NodeStore]'s ReadableStorage after the +/// [NodeStoreHeader] is a [StoredArea]. +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +struct StoredArea { + /// Index in [AREA_SIZES] of this area's size + area_size_index: AreaIndex, + area: T, +} + +impl NodeStore { + /// Returns (index, area_size) for the [StoredArea] at `addr`. + /// `index` is the index of `area_size` in [AREA_SIZES]. + fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { + let mut area_stream = self.storage.stream_from(addr.get())?; + + let index: AreaIndex = bincode::deserialize_from(&mut area_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + let size = *AREA_SIZES.get(index as usize).ok_or(Error::new( + ErrorKind::InvalidData, + format!("Invalid area size index {}", index), + ))?; + + Ok((index, size)) + } + + /// Read a [Node] from the provided [LinearAddress]. + /// `addr` is the address of a StoredArea in the ReadableStorage. + pub fn read_node_from_disk(&self, addr: LinearAddress) -> Result, Error> { + debug_assert!(addr.get() % 8 == 0); + + let addr = addr.get() + 1; // Skip the index byte + + let area_stream = self.storage.stream_from(addr)?; + let area: Area = bincode::deserialize_from(area_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + match area { + Area::Node(node) => Ok(node.into()), + Area::Free(_) => Err(Error::new( + ErrorKind::InvalidData, + "Attempted to read a freed area", + )), + } + } +} + +impl NodeStore { + /// Open an existing [NodeStore] + /// Assumes the header is written in the [ReadableStorage]. + pub fn open(storage: Arc) -> Result { + let mut stream = storage.stream_from(0)?; + + let header: NodeStoreHeader = bincode::deserialize_from(&mut stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + drop(stream); + + Ok(Self { + header, + kind: Committed { + deleted: Default::default(), + }, + storage, + }) + } + + /// Create a new, empty, Committed [NodeStore] and clobber + /// the underlying store with an empty freelist and no root node + pub fn new_empty_committed(storage: Arc) -> Result { + let header = NodeStoreHeader::new(); + + // let header_bytes = bincode::serialize(&header).map_err(|e| { + // Error::new( + // ErrorKind::InvalidData, + // format!("Failed to serialize header: {}", e), + // ) + // })?; + + // storage.write(0, header_bytes.as_slice())?; + + Ok(Self { + header, + storage, + kind: Committed { + deleted: Default::default(), + }, + }) + } +} + +impl NodeStore { + /// Create a new MutableProposal [NodeStore] from a parent [NodeStore] + pub fn new + ReadInMemoryNode>( + parent: NodeStore, + storage: Arc, + ) -> Result { + let mut deleted: Vec<_> = Default::default(); + let root = if let Some(root_addr) = parent.header.root_address { + deleted.push(root_addr); + let root = parent.read_node(root_addr)?; + Some((*root).clone()) + } else { + None + }; + let kind = MutableProposal { + root, + deleted, + parent: parent.kind.into(), + }; + Ok(NodeStore { + header: parent.header.clone(), + kind, + storage, + }) + } + + /// Marks the node at `addr` as deleted in this proposal. + pub fn delete_node(&mut self, addr: LinearAddress) { + self.kind.deleted.push(addr); + } + + /// Returns the root of this proposal. + pub fn mut_root(&mut self) -> &mut Option { + &mut self.kind.root + } +} + +impl NodeStore { + // TODO danlaine: Use this code in the revision management code. + // TODO danlaine: Write only the parts of the header that have changed instead of the whole thing + // fn write_header(&mut self) -> Result<(), Error> { + // let header_bytes = bincode::serialize(&self.header).map_err(|e| { + // Error::new( + // ErrorKind::InvalidData, + // format!("Failed to serialize free lists: {}", e), + // ) + // })?; + + // self.storage.write(0, header_bytes.as_slice())?; + + // Ok(()) + // } +} + +impl NodeStore { + /// Creates a new, empty, [NodeStore] and clobbers the underlying `storage` with an empty header. + pub fn new_empty_proposal(storage: Arc) -> Self { + let header = NodeStoreHeader::new(); + let header_bytes = bincode::serialize(&header).expect("failed to serialize header"); + storage + .write(0, header_bytes.as_slice()) + .expect("failed to write header"); + NodeStore { + header, + kind: MutableProposal { + root: None, + deleted: Default::default(), + parent: NodeStoreParent::Committed, + }, + storage, + } + } +} + +impl NodeStore { + /// Attempts to allocate `n` bytes from the free lists. + /// If successful returns the address of the newly allocated area + /// and the index of the free list that was used. + /// If there are no free areas big enough for `n` bytes, returns None. + /// TODO danlaine: If we return a larger area than requested, we should split it. + fn allocate_from_freed(&mut self, n: u64) -> Result, Error> { + // Find the smallest free list that can fit this size. + let index = area_size_to_index(n)?; + + // rustify: rewrite using self.header.free_lists.iter_mut().find(...) + for index in index as usize..NUM_AREA_SIZES { + // Get the first free block of sufficient size. + if let Some(free_stored_area_addr) = self.header.free_lists[index] { + // Update the free list head. + // Skip the index byte and Area discriminant byte + let free_area_addr = free_stored_area_addr.get() + 2; + let free_head_stream = self.storage.stream_from(free_area_addr)?; + let free_head: FreeArea = bincode::deserialize_from(free_head_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + // Update the free list to point to the next free block. + self.header.free_lists[index] = free_head.next_free_block; + + // Return the address of the newly allocated block. + return Ok(Some((free_stored_area_addr, index as AreaIndex))); + } + // No free blocks in this list, try the next size up. + } + + Ok(None) + } + + fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), Error> { + let index = area_size_to_index(n)?; + let area_size = AREA_SIZES[index as usize]; + let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); + self.header.size += area_size; + debug_assert!(addr.get() % 8 == 0); + Ok((addr, index)) + } + + /// Returns the length of the serialized area for a node. + fn stored_len(node: &Node) -> u64 { + // TODO: calculate length without serializing! + let area: Area<&Node, FreeArea> = Area::Node(node); + let area_bytes = bincode::serialize(&area) + .map_err(|e| Error::new(ErrorKind::InvalidData, e)) + .expect("fixme"); + + // +1 for the size index byte + // TODO: do a better job packing the boolean (freed) with the possible node sizes + // A reasonable option is use a i8 and negative values indicate it's freed whereas positive values are non-free + // This would still allow for 127 different sizes + area_bytes.len() as u64 + 1 + } + + /// Returns an address that can be used to store the given `node` and updates + /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. + /// Also returns the index of the free list the node was allocated from. + pub fn allocate_node(&mut self, node: &Node) -> Result<(LinearAddress, AreaIndex), Error> { + let stored_area_size = Self::stored_len(node); + + // Attempt to allocate from a free list. + // If we can't allocate from a free list, allocate past the existing + // of the ReadableStorage. + let (addr, index) = match self.allocate_from_freed(stored_area_size)? { + Some((addr, index)) => (addr, index), + None => self.allocate_from_end(stored_area_size)?, + }; + + Ok((addr, index)) + } + + // TODO danlaine: Use this code inside the revision management code or delete it. + // The inner implementation of `create_node` that doesn't update the free lists. + // fn create_node_inner(&mut self, node: Node) -> Result { + // let (addr, index) = self.allocate_node(&node)?; + + // let stored_area = StoredArea { + // area_size_index: index, + // area: Area::<_, FreeArea>::Node(node), + // }; + + // let stored_area_bytes = + // bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + // self.storage + // .write(addr.get(), stored_area_bytes.as_slice())?; + + // Ok(addr) + // } + + // TODO danlaine: use this code in the revision management code or delete it. + // Update a node in-place. This should only be used when the node was allocated using + // allocate_node. + // TODO: We should enforce this by having a new type for allocated nodes, which could + // carry the size information too + // pub fn update_in_place(&mut self, addr: LinearAddress, node: &Node) -> Result<(), Error> { + // let new_area: Area<&Node, FreeArea> = Area::Node(node); + // let new_area_bytes = + // bincode::serialize(&new_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + // let addr = addr.get() + 1; // Skip the index byte + // self.storage.write(addr, new_area_bytes.as_slice())?; + // Ok(()) + // } + + // TODO danlaine: use this code in the revision management code. + // Deletes the [Node] at the given address. + // pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), Error> { + // debug_assert!(addr.get() % 8 == 0); + + // let (area_size_index, _) = self.area_index_and_size(addr)?; + + // // The area that contained the node is now free. + // let area: Area = Area::Free(FreeArea { + // next_free_block: self.header.free_lists[area_size_index as usize], + // }); + + // let stored_area = StoredArea { + // area_size_index, + // area, + // }; + + // let stored_area_bytes = + // bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + // self.storage.write(addr.into(), &stored_area_bytes)?; + + // // The newly freed block is now the head of the free list. + // self.header.free_lists[area_size_index as usize] = Some(addr); + + // Ok(()) + // } +} + +/// An error from doing an update +#[derive(Debug)] +pub enum UpdateError { + /// An IO error occurred during the update + Io(Error), +} + +impl From for UpdateError { + fn from(value: Error) -> Self { + UpdateError::Io(value) + } +} + +/// Can be used by filesystem tooling such as "file" to identify +/// the version of firewood used to create this [NodeStore] file. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +struct Version { + bytes: [u8; 16], +} + +impl Version { + const SIZE: u64 = std::mem::size_of::() as u64; + + /// construct a [Version] header from the firewood version + fn new() -> Self { + let mut version_bytes: [u8; Self::SIZE as usize] = Default::default(); + let version = env!("CARGO_PKG_VERSION"); + let _ = version_bytes + .as_mut_slice() + .write_all(format!("firewood {}", version).as_bytes()); + Self { + bytes: version_bytes, + } + } +} + +pub type FreeLists = [Option; NUM_AREA_SIZES]; + +/// Persisted metadata for a [NodeStore]. +/// The [NodeStoreHeader] is at the start of the ReadableStorage. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +struct NodeStoreHeader { + /// Identifies the version of firewood used to create this [NodeStore]. + version: Version, + size: u64, + /// Element i is the pointer to the first free block of size `BLOCK_SIZES[i]`. + free_lists: FreeLists, + root_address: Option, +} + +impl NodeStoreHeader { + /// The first SIZE bytes of the ReadableStorage are the [NodeStoreHeader]. + /// The serialized NodeStoreHeader may be less than SIZE bytes but we + /// reserve this much space for it since it can grow and it must always be + /// at the start of the ReadableStorage so it can't be moved in a resize. + const SIZE: u64 = { + // 8 and 9 for `size` and `root_address` respectively + let max_size = Version::SIZE + 8 + 9 + FREE_LIST_MAX_SIZE; + // Round up to the nearest multiple of MIN_AREA_SIZE + let remainder = max_size % MIN_AREA_SIZE; + if remainder == 0 { + max_size + } else { + max_size + MIN_AREA_SIZE - remainder + } + }; + + fn new() -> Self { + Self { + // The store just contains the header at this point + size: Self::SIZE, + root_address: None, + version: Version::new(), + free_lists: Default::default(), + } + } +} + +/// A [FreeArea] is stored at the start of the area that contained a node that +/// has been freed. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +struct FreeArea { + next_free_block: Option, +} + +/// Reads from an immutable (i.e. already hashed) merkle trie. +pub trait HashedNodeReader: TrieReader { + /// Gets the address and hash of the root node of an immutable merkle trie. + fn root_address_and_hash(&self) -> Result, Error>; + + /// Gets the hash of the root node of an immutable merkle trie. + fn root_hash(&self) -> Result, Error> { + Ok(self.root_address_and_hash()?.map(|(_, hash)| hash)) + } +} + +/// Reads nodes and the root address from a merkle trie. +pub trait TrieReader: NodeReader + RootReader {} + +/// Reads nodes from a merkle trie. +pub trait NodeReader { + /// Returns the node at `addr`. + fn read_node(&self, addr: LinearAddress) -> Result, Error>; +} + +/// Reads the root of a merkle trie. +pub trait RootReader { + /// Returns the root of the trie. + fn root_node(&self) -> Option>; +} + +/// A committed revision of a merkle trie. +#[derive(Debug)] +pub struct Committed { + deleted: Box<[LinearAddress]>, +} + +impl ReadInMemoryNode for Committed { + // A committed revision has no in-memory changes. All its nodes are in storage. + fn read_in_memory_node(&self, _addr: LinearAddress) -> Option> { + None + } +} + +#[derive(Debug)] +pub enum NodeStoreParent { + Proposed(Arc), + Committed, +} + +impl From> for NodeStoreParent { + fn from(_: NodeStore) -> Self { + NodeStoreParent::Committed + } +} + +impl From> for NodeStoreParent { + fn from(val: NodeStore) -> Self { + NodeStoreParent::Proposed(Arc::new(val.kind)) + } +} + +#[derive(Debug)] +/// Contains state for a proposed revision of the trie. +pub struct ImmutableProposal { + /// Address --> Node for nodes created in this proposal. + new: HashMap>, + /// Nodes that have been deleted in this proposal. + deleted: Box<[LinearAddress]>, + /// The parent of this proposal. + parent: NodeStoreParent, +} + +impl ReadInMemoryNode for ImmutableProposal { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + // Check if the node being requested was created in this proposal. + if let Some(node) = self.new.get(&addr) { + return Some(node.clone()); + } + + // It wasn't. Try our parent, and its parent, and so on until we find it or find + // a committed revision. + match self.parent { + NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), + NodeStoreParent::Committed => None, + } + } +} + +/// Proposed [NodeStore] types keep some nodes in memory. These nodes are new nodes that were allocated from +/// the free list, but are not yet on disk. This trait checks to see if a node is in memory and returns it if +/// it's there. If it's not there, it will be read from disk. +/// +/// This trait does not know anything about the underlying storage, so it just returns None if the node isn't in memory. +pub trait ReadInMemoryNode { + /// Returns the node at `addr` if it is in memory. + /// Returns None if it isn't. + fn read_in_memory_node(&self, addr: LinearAddress) -> Option>; +} + +/// Contains the state of a revision of a merkle trie. +/// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. +/// The second generic parameter is the type of the storage used, either +/// in-memory or on-disk. +/// +/// The lifecycle of a [NodeStore] is as follows: +/// 1. Create a new, empty, [Committed] [NodeStore] using [NodeStore::new_empty_committed]. +/// 2. Create a [NodeStore] from disk using [NodeStore::open]. +/// 3. Create a new mutable proposal from either a [Committed] or [ImmutableProposal] [NodeStore] using [NodeStore::new]. +/// 4. Convert a mutable proposal to an immutable proposal using [std::convert::TryInto], which hashes the nodes and assigns addresses +/// 5. Convert an immutable proposal to a committed revision using [std::convert::TryInto], which writes the nodes to disk. + +#[derive(Debug)] +pub struct NodeStore { + // Metadata for this revision. + header: NodeStoreHeader, + /// This is one of [Committed], [ImmutableProposal], or [MutableProposal]. + kind: T, + // Persisted storage to read nodes from. + storage: Arc, +} + +/// Contains the state of a proposal that is still being modified. +#[derive(Debug)] +pub struct MutableProposal { + /// The root of the trie in this proposal. + root: Option, + /// Nodes that have been deleted in this proposal. + deleted: Vec, + parent: NodeStoreParent, +} + +impl ReadInMemoryNode for NodeStoreParent { + /// Returns the node at `addr` if it is in memory from a parent proposal. + /// If the base revision is committed, there are no in-memory nodes, so we return None + fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + match self { + NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), + NodeStoreParent::Committed => None, + } + } +} + +impl ReadInMemoryNode for MutableProposal { + /// [MutableProposal] types do not have any nodes in memory, but their parent proposal might, so we check there. + /// This might be recursive: a grandparent might also have that node in memory. + fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + self.parent.read_in_memory_node(addr) + } + + // fn read_in_memory_root(&self) -> Option>> { + // let Some(root) = &self.root else { + // return Some(None); + // }; + + // let root = Arc::new(root.clone()); + // Some(Some(root)) + // } +} + +impl, S: ReadableStorage> From> + for NodeStore +{ + fn from(val: NodeStore) -> Self { + NodeStore { + header: val.header, + kind: MutableProposal { + root: None, + deleted: Default::default(), + parent: val.kind.into(), + }, + storage: val.storage, + } + } +} + +impl NodeStore { + /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. + /// Returns the hashed node and its hash. + fn hash_helper(&mut self, mut node: Node, path_prefix: &mut Path) -> (LinearAddress, TrieHash) { + // Allocate addresses and calculate hashes for all new nodes + match node { + Node::Branch(ref mut b) => { + for (nibble, child) in b.children.iter_mut().enumerate() { + // Take child from b.children + let Some(Child::Node(child_node)) = std::mem::take(child) else { + // There is no child or we already know its hash. + continue; + }; + + // Hash this child and update + // we extend and truncate path_prefix to reduce memory allocations + let original_length = path_prefix.len(); + path_prefix + .0 + .extend(b.partial_path.0.iter().copied().chain(once(nibble as u8))); + + let (child_addr, child_hash) = self.hash_helper(child_node, path_prefix); + *child = Some(Child::AddressWithHash(child_addr, child_hash)); + path_prefix.0.truncate(original_length); + } + } + Node::Leaf(_) => {} + } + + let hash = hash_node(&node, path_prefix); + let (addr, _) = self.allocate_node(&node).expect("TODO handle error"); + + self.kind.new.insert(addr, Arc::new(node)); + + (addr, hash) + } +} + +impl From> for NodeStore { + fn from(val: NodeStore) -> Self { + let NodeStore { + header, + kind, + storage, + } = val; + + let mut nodestore = NodeStore { + header, + kind: ImmutableProposal { + new: HashMap::new(), + deleted: kind.deleted.into(), + parent: kind.parent, + }, + storage, + }; + + let Some(root) = kind.root else { + // This trie is now empty. + nodestore.header.root_address = None; + return nodestore; + }; + + // Hashes the trie and returns the address of the new root. + let (root_addr, _) = nodestore.hash_helper(root, &mut Path::new()); + + nodestore.header.root_address = Some(root_addr); + + nodestore + } +} + +impl NodeReader for NodeStore { + fn read_node(&self, addr: LinearAddress) -> Result, Error> { + if let Some(node) = self.kind.read_in_memory_node(addr) { + return Ok(node); + } + + self.read_node_from_disk(addr) + } +} + +impl RootReader for NodeStore { + fn root_node(&self) -> Option> { + self.kind.root.as_ref().map(|node| Arc::new(node.clone())) + } +} + +trait Hashed {} +impl Hashed for Committed {} +impl Hashed for ImmutableProposal {} + +impl RootReader for NodeStore { + fn root_node(&self) -> Option> { + self.header + .root_address + .and_then(|addr| self.kind.read_in_memory_node(addr)) + } +} + +impl TrieReader for NodeStore where + NodeStore: RootReader +{ +} + +impl HashedNodeReader for NodeStore +where + NodeStore: TrieReader, +{ + fn root_address_and_hash(&self) -> Result, Error> { + if let Some(root_addr) = self.header.root_address { + let root_node = self.read_node(root_addr)?; + let root_hash = hash_node(&root_node, &Path::new()); + Ok(Some((root_addr, root_hash))) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use crate::linear::memory::MemStore; + + use super::*; + + #[test] + fn test_area_size_to_index() { + // TODO: rustify using: for size in AREA_SIZES + for (i, &area_size) in AREA_SIZES.iter().enumerate() { + // area size is at top of range + assert_eq!(area_size_to_index(area_size).unwrap(), i as AreaIndex); + + if i > 0 { + // 1 less than top of range stays in range + assert_eq!(area_size_to_index(area_size - 1).unwrap(), i as AreaIndex); + } + + if i < NUM_AREA_SIZES - 1 { + // 1 more than top of range goes to next range + assert_eq!( + area_size_to_index(area_size + 1).unwrap(), + (i + 1) as AreaIndex + ); + } + } + + for i in 0..=MIN_AREA_SIZE { + assert_eq!(area_size_to_index(i).unwrap(), 0); + } + + assert!(area_size_to_index(MAX_AREA_SIZE + 1).is_err()); + } + + // TODO add new tests + // #[test] + // fn test_create() { + // let memstore = Arc::new(MemStore::new(vec![])); + // let mut node_store = NodeStore::new_empty_proposal(memstore); + + // let leaf = Node::Leaf(LeafNode { + // partial_path: Path::from([0, 1, 2]), + // value: Box::new([3, 4, 5]), + // }); + + // let leaf_addr = node_store.create_node(leaf.clone()).unwrap(); + // let got_leaf = node_store.kind.new.get(&leaf_addr).unwrap(); + // assert_eq!(**got_leaf, leaf); + // let got_leaf = node_store.read_node(leaf_addr).unwrap(); + // assert_eq!(*got_leaf, leaf); + + // // The header should be unchanged in storage + // { + // let mut header_bytes = node_store.storage.stream_from(0).unwrap(); + // let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); + // assert_eq!(header.version, Version::new()); + // let empty_free_lists: FreeLists = Default::default(); + // assert_eq!(header.free_lists, empty_free_lists); + // assert_eq!(header.root_address, None); + // } + + // // Leaf should go right after the header + // assert_eq!(leaf_addr.get(), NodeStoreHeader::SIZE); + + // // Create another node + // let branch = Node::Branch(Box::new(BranchNode { + // partial_path: Path::from([6, 7, 8]), + // value: Some(vec![9, 10, 11].into_boxed_slice()), + // children: Default::default(), + // })); + + // let old_size = node_store.header.size; + // let branch_addr = node_store.create_node(branch.clone()).unwrap(); + // assert!(node_store.header.size > old_size); + + // // branch should go after leaf + // assert!(branch_addr.get() > leaf_addr.get()); + + // // The header should be unchanged in storage + // { + // let mut header_bytes = node_store.storage.stream_from(0).unwrap(); + // let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); + // assert_eq!(header.version, Version::new()); + // let empty_free_lists: FreeLists = Default::default(); + // assert_eq!(header.free_lists, empty_free_lists); + // assert_eq!(header.root_address, None); + // } + // } + + // #[test] + // fn test_delete() { + // let memstore = Arc::new(MemStore::new(vec![])); + // let mut node_store = NodeStore::new_empty_proposal(memstore); + + // // Create a leaf + // let leaf = Node::Leaf(LeafNode { + // partial_path: Path::new(), + // value: Box::new([1]), + // }); + // let leaf_addr = node_store.create_node(leaf.clone()).unwrap(); + + // // Delete the node + // node_store.delete_node(leaf_addr).unwrap(); + // assert!(node_store.kind.deleted.contains(&leaf_addr)); + + // // Create a new node with the same size + // let new_leaf_addr = node_store.create_node(leaf).unwrap(); + + // // The new node shouldn't be at the same address + // assert_ne!(new_leaf_addr, leaf_addr); + // } + + #[test] + fn test_node_store_new() { + let memstore = MemStore::new(vec![]); + let node_store = NodeStore::new_empty_proposal(memstore.into()); + + // Check the empty header is written at the start of the ReadableStorage. + let mut header_bytes = node_store.storage.stream_from(0).unwrap(); + let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); + assert_eq!(header.version, Version::new()); + let empty_free_list: FreeLists = Default::default(); + assert_eq!(header.free_lists, empty_free_list); + } +} diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs new file mode 100644 index 000000000000..4d64e367de03 --- /dev/null +++ b/storage/src/trie_hash.rs @@ -0,0 +1,102 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt::{self, Debug}; + +use serde::{de::Visitor, Deserialize, Serialize}; +use sha2::digest::{generic_array::GenericArray, typenum}; + +/// A hash value inside a merkle trie +/// We use the same type as returned by sha2 here to avoid copies +#[derive(PartialEq, Eq, Clone, Default)] +pub struct TrieHash(GenericArray); + +impl std::ops::Deref for TrieHash { + type Target = GenericArray; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for TrieHash { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl AsRef<[u8]> for TrieHash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Debug for TrieHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", hex::encode(self.0)) + } +} + +impl From<[u8; 32]> for TrieHash { + fn from(value: [u8; Self::len()]) -> Self { + TrieHash(value.into()) + } +} + +impl From> for TrieHash { + fn from(value: GenericArray) -> Self { + TrieHash(value) + } +} + +impl TrieHash { + /// Return the length of a TrieHash + const fn len() -> usize { + std::mem::size_of::() + } + + /// Returns true iff each element in this hash is 0. + pub fn is_empty(&self) -> bool { + *self == TrieHash::default() + } +} + +impl Serialize for TrieHash { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +impl<'de> Deserialize<'de> for TrieHash { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_bytes(TrieVisitor) + } +} + +struct TrieVisitor; + +impl<'de> Visitor<'de> for TrieVisitor { + type Value = TrieHash; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an array of u8 hash bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + let mut hash = TrieHash::default(); + if v.len() == hash.0.len() { + hash.0.copy_from_slice(v); + Ok(hash) + } else { + Err(E::invalid_length(v.len(), &self)) + } + } +} From 8d46663e292ff451a2986e2c40e1edb80d14541a Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 19 Aug 2024 14:56:24 -0400 Subject: [PATCH 0536/1053] add comments on where nibbles vs bytes are expected (#702) --- firewood/src/merkle.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b0b7071b59b5..746b0c1a32d3 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -394,6 +394,7 @@ impl Merkle> { } /// Map `key` to `value` in the trie. + /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); @@ -416,6 +417,7 @@ impl Merkle> { } /// Map `key` to `value` into the subtrie rooted at `node`. + /// Each element of `key` is 1 nibble. /// Returns the new root of the subtrie. pub fn insert_helper( &mut self, @@ -550,6 +552,7 @@ impl Merkle> { /// Removes the value associated with the given `key`. /// Returns the value that was removed, if any. /// Otherwise returns `None`. + /// Each element of `key` is 2 nibbles. pub fn remove(&mut self, key: &[u8]) -> Result>, MerkleError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); @@ -566,6 +569,7 @@ impl Merkle> { /// Removes the value associated with the given `key` from the subtrie rooted at `node`. /// Returns the new root of the subtrie and the value that was removed, if any. + /// Each element of `key` is 1 nibble. #[allow(clippy::type_complexity)] fn remove_helper( &mut self, From 41c66c9b3f43208013df30290ffb3a426e714335 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 19 Aug 2024 09:08:42 -1000 Subject: [PATCH 0537/1053] Remove nix dependency (#703) --- firewood/Cargo.toml | 1 - firewood/src/db.rs | 2 -- firewood/src/v2/db.rs | 1 - 3 files changed, 4 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index af6e55f5d573..d098bf2c3fd1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,7 +22,6 @@ storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metered = "0.9.0" -nix = {version = "0.28.0", features = ["fs", "uio"]} serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index abd8809213b1..6d7918b25fdf 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -27,7 +27,6 @@ const _VERSION_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; pub enum DbError { InvalidParams, Merkle(MerkleError), - System(nix::Error), CreateError, IO(std::io::Error), InvalidProposal, @@ -38,7 +37,6 @@ impl fmt::Display for DbError { match self { DbError::InvalidParams => write!(f, "invalid parameters provided"), DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), - DbError::System(e) => write!(f, "system error: {e:?}"), DbError::CreateError => write!(f, "database create error"), DbError::IO(e) => write!(f, "I/O error: {e:?}"), DbError::InvalidProposal => write!(f, "invalid proposal"), diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index cff28aa85ae1..6caca6509c97 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -19,7 +19,6 @@ impl From for api::Error { match value { DbError::InvalidParams => api::Error::InternalError(Box::new(value)), DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), - DbError::System(e) => api::Error::IO(e.into()), DbError::CreateError => api::Error::InternalError(Box::new(value)), DbError::IO(e) => api::Error::IO(e), DbError::InvalidProposal => api::Error::InvalidProposal, From b5b7ecedb70a08570b01815ceb36069f12df6748 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 19 Aug 2024 09:21:29 -1000 Subject: [PATCH 0538/1053] Rkuris/fix codeowners (#704) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index b34e810c2fcf..5a8f3a6bc94d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @xinifinity @rkuris @richardpringle @danlaine +* @rkuris @richardpringle From df42dac1c1f0a0950602e9cb2bbf2aea7148e180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:41:28 -1000 Subject: [PATCH 0539/1053] build(deps): update typed-builder requirement from 0.18.1 to 0.19.1 (#684) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d098bf2c3fd1..9073681606bc 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -26,7 +26,7 @@ serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } -typed-builder = "0.18.1" +typed-builder = "0.19.1" bincode = "1.3.3" log = { version = "0.4.20", optional = true } test-case = "3.3.1" From f33ac3c4a80dd19322d6da8c0a5719c4852a9465 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 19 Aug 2024 10:19:20 -1000 Subject: [PATCH 0540/1053] Update dependencies from dependabot (#705) --- grpc-testtool/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index c07bd889e028..db238dc8c4e5 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,9 +17,9 @@ bench = false [dependencies] firewood = { version = "0.0.4", path = "../firewood" } -prost = "0.12.3" +prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.11.0", features = ["tls"] } +tonic = { version = "0.12.1", features = ["tls"] } tracing = { version = "0.1.40" } clap = { version = "4.5.0", features = ["derive"] } log = "0.4.20" @@ -29,7 +29,7 @@ serde_json = "1.0.113" serde = { version = "1.0.196", features = ["derive"] } [build-dependencies] -tonic-build = "0.11.0" +tonic-build = "0.12.1" [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} From c06ab387456fe03fd036eef5ecea16e19516f0b3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 21 Aug 2024 09:21:38 -1000 Subject: [PATCH 0541/1053] Implement commit (#701) --- firewood/examples/insert.rs | 121 ++++++++------- firewood/src/db.rs | 168 ++++++++++++++++---- firewood/src/manager.rs | 256 ++++++++++++++++--------------- firewood/src/merkle.rs | 10 +- firewood/src/v2/api.rs | 27 +++- firewood/src/v2/emptydb.rs | 28 ++-- firewood/src/v2/propose.rs | 4 +- storage/Cargo.toml | 1 + storage/src/lib.rs | 2 +- storage/src/linear/filebacked.rs | 41 ++++- storage/src/linear/mod.rs | 17 ++ storage/src/nodestore.rs | 223 ++++++++++++++++++++++----- storage/src/trie_hash.rs | 2 +- 13 files changed, 617 insertions(+), 283 deletions(-) diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 5514cb013357..dfd86e79bed9 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,13 +5,15 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::{collections::HashMap, error::Error, ops::RangeInclusive}; +use std::{ + borrow::BorrowMut as _, collections::HashMap, error::Error, ops::RangeInclusive, time::Instant, +}; use firewood::{ - db::{Batch, BatchOp}, - v2::api::DbView, + db::{Batch, BatchOp, Db, DbConfig}, + v2::api::{Db as _, DbView, Proposal as _}, }; -use rand::Rng; +use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; #[derive(Parser, Debug)] struct Args { @@ -43,63 +45,60 @@ fn string_to_range(input: &str) -> Result, Box Result<(), Box> { - Ok(()) + let cfg = DbConfig::builder().truncate(true).build(); + + let args = Args::parse(); + + let mut db = Db::new("rev_db", cfg) + .await + .expect("db initiation should succeed"); - // TODO replace - // let cfg = DbConfig::builder().truncate(true).build(); - - // let args = Args::parse(); - - // let db = Db::new("rev_db", cfg) - // .await - // .expect("db initiation should succeed"); - - // let keys = args.batch_size; - // let start = Instant::now(); - - // let mut rng = if let Some(seed) = args.seed { - // rand::rngs::StdRng::seed_from_u64(seed) - // } else { - // rand::rngs::StdRng::from_entropy() - // }; - - // for _ in 0..args.number_of_batches { - // let keylen = rng.gen_range(args.keylen.clone()); - // let valuelen = rng.gen_range(args.valuelen.clone()); - // let batch: Batch, Vec> = (0..keys) - // .map(|_| { - // ( - // rng.borrow_mut() - // .sample_iter(&Alphanumeric) - // .take(keylen) - // .collect::>(), - // rng.borrow_mut() - // .sample_iter(&Alphanumeric) - // .take(valuelen) - // .collect::>(), - // ) - // }) - // .map(|(key, value)| BatchOp::Put { key, value }) - // .collect(); - - // let verify = get_keys_to_verify(&batch, args.read_verify_percent); - - // #[allow(clippy::unwrap_used)] - // let proposal = db.propose(batch).await.unwrap(); - // proposal.commit().await?; - // verify_keys(&db, verify).await?; - // } - - // let duration = start.elapsed(); - // println!( - // "Generated and inserted {} batches of size {keys} in {duration:?}", - // args.number_of_batches - // ); - - // Ok(()) + let keys = args.batch_size; + let start = Instant::now(); + + let mut rng = if let Some(seed) = args.seed { + rand::rngs::StdRng::seed_from_u64(seed) + } else { + rand::rngs::StdRng::from_entropy() + }; + + for _ in 0..args.number_of_batches { + let keylen = rng.gen_range(args.keylen.clone()); + let valuelen = rng.gen_range(args.valuelen.clone()); + let batch: Batch, Vec> = (0..keys) + .map(|_| { + ( + rng.borrow_mut() + .sample_iter(&Alphanumeric) + .take(keylen) + .collect::>(), + rng.borrow_mut() + .sample_iter(&Alphanumeric) + .take(valuelen) + .collect::>(), + ) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect(); + + let verify = get_keys_to_verify(&batch, args.read_verify_percent); + + #[allow(clippy::unwrap_used)] + let proposal = db.propose(batch).await.unwrap(); + proposal.commit().await?; + verify_keys(&db, verify).await?; + } + + let duration = start.elapsed(); + println!( + "Generated and inserted {} batches of size {keys} in {duration:?}", + args.number_of_batches + ); + + Ok(()) } -fn _get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec> { +fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>> { if pct == 0 { HashMap::new() } else { @@ -108,7 +107,7 @@ fn _get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Vec>, pct: u16) -> HashMap, Vec>, + verify: HashMap, Box<[u8]>>, ) -> Result<(), firewood::v2::api::Error> { if !verify.is_empty() { let hash = db.root_hash().await?.expect("root hash should exist"); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6d7918b25fdf..2652bdcfedef 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,11 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::MerkleError; +use crate::merkle::{Merkle, MerkleError}; use crate::proof::{Proof, ProofNode}; use crate::range_proof::RangeProof; use crate::stream::MerkleKeyValueStream; -use crate::v2::api::{self, KeyType}; +use crate::v2::api::{self, KeyType, ValueType}; pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; @@ -15,8 +15,8 @@ use std::error::Error; use std::fmt; use std::io::Write; use std::path::Path; -use std::sync::Arc; -use storage::{Committed, FileBacked, HashedNodeReader, NodeStore}; +use std::sync::{Arc, RwLock}; +use storage::{Committed, FileBacked, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash}; use typed_builder::TypedBuilder; // TODO use or remove @@ -62,8 +62,9 @@ impl api::DbView for HistoricalRev { HashedNodeReader::root_hash(self).map_err(api::Error::IO) } - async fn val(&self, _key: K) -> Result>, api::Error> { - todo!() + async fn val(&self, key: K) -> Result>, api::Error> { + let merkle = Merkle::from(self); + Ok(merkle.get_value(key.as_ref())?) } async fn single_key_proof( @@ -209,36 +210,72 @@ pub struct DbConfig { #[derive(Debug)] pub struct Db { metrics: Arc, - _manager: RevisionManager, + // TODO: consider using https://docs.rs/lock_api/latest/lock_api/struct.RwLock.html#method.upgradable_read + // TODO: This should probably use an async RwLock + manager: RwLock, } -// #[async_trait] -// impl api::Db for Db { -// type Historical = HistoricalRev; - -// type Proposal = Proposal; - -// async fn revision( -// &self, -// _root_hash: HashKey, -// ) -> Result>, api::Error> { -// let store = self.manager.revision(_root_hash)?; -// Ok(Arc::new(HistoricalRev:: { -// _historical: store, -// })) -// } +#[async_trait] +impl api::Db for Db +where + for<'p> Proposal<'p>: api::Proposal, +{ + type Historical = NodeStore; + + type Proposal<'p> = Proposal<'p> where Self: 'p; + + async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { + let nodestore = self + .manager + .read() + .expect("poisoned lock") + .revision(root_hash)?; + Ok(nodestore) + } -// async fn root_hash(&self) -> Result { -// Ok(self.manager.root_hash()?) -// } + async fn root_hash(&self) -> Result, api::Error> { + Ok(self.manager.read().expect("poisoned lock").root_hash()?) + } -// async fn propose( -// &self, -// _batch: api::Batch, -// ) -> Result, api::Error> { -// todo!() -// } -// } + async fn propose<'p, K: KeyType, V: ValueType>( + &'p mut self, + batch: api::Batch, + ) -> Result>, api::Error> + where + Self: 'p, + { + let parent = self + .manager + .read() + .expect("poisoned lock") + .latest_revision() + .expect("no latest revision"); + let proposal = NodeStore::new(parent)?; + let mut merkle = Merkle::from(proposal); + for op in batch { + match op { + BatchOp::Put { key, value } => { + merkle.insert(key.as_ref(), value.as_ref().into())?; + } + BatchOp::Delete { key } => { + merkle.remove(key.as_ref())?; + } + } + } + let nodestore = merkle.into_inner(); + let immutable: Arc> = Arc::new(nodestore.into()); + self.manager + .write() + .expect("poisoned lock") + .add_proposal(immutable.clone()); + + Ok(Self::Proposal { + nodestore: immutable, + db: self, + } + .into()) + } +} #[metered(registry = DbMetrics, visibility = pub)] impl Db { @@ -251,7 +288,7 @@ impl Db { )?; let db = Self { metrics, - _manager: manager, + manager: manager.into(), }; Ok(db) } @@ -273,3 +310,68 @@ impl Db { self.metrics.clone() } } + +#[derive(Debug)] +pub struct Proposal<'p> { + nodestore: Arc>, + db: &'p Db, +} + +#[async_trait] +impl<'a> api::DbView for Proposal<'a> { + type Stream<'b> = MerkleKeyValueStream<'b, NodeStore> where Self: 'b; + + async fn root_hash(&self) -> Result, api::Error> { + todo!() + } + + async fn val(&self, _key: K) -> Result>, api::Error> { + todo!() + } + + async fn single_key_proof( + &self, + _key: K, + ) -> Result>, api::Error> { + todo!() + } + + async fn range_proof( + &self, + _first_key: Option, + _last_key: Option, + _limit: Option, + ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { + todo!() + } + + fn iter_option( + &self, + _first_key: Option, + ) -> Result, api::Error> { + todo!() + } +} + +#[async_trait] +impl<'a> api::Proposal for Proposal<'a> { + type Proposal = Proposal<'a>; + + async fn propose( + self: Arc, + _data: api::Batch, + ) -> Result, api::Error> { + todo!() + } + + // When committing a proposal, refuse to commit if there are any cloned proposals. + async fn commit(self: Arc) -> Result<(), api::Error> { + match Arc::into_inner(self) { + Some(proposal) => { + let mut manager = proposal.db.manager.write().expect("poisoned lock"); + Ok(manager.commit(proposal.nodestore.clone())?) + } + None => Err(api::Error::CannotCommitClonedProposal), + } + } +} diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 336cf7399d23..b00bcd73f035 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -3,30 +3,42 @@ #![allow(dead_code)] -use std::io::Error; +use std::collections::HashMap; +use std::num::NonZero; use std::path::PathBuf; +use std::sync::Arc; +use std::{collections::VecDeque, io::Error}; use typed_builder::TypedBuilder; use crate::v2::api::HashKey; -use storage::FileBacked; +use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; #[derive(Clone, Debug, TypedBuilder)] pub struct RevisionManagerConfig { /// The number of historical revisions to keep in memory. #[builder(default = 64)] max_revisions: usize, + + #[builder(default_code = "NonZero::new(1024).expect(\"non-zero\")")] + node_cache_size: NonZero, } #[derive(Debug)] pub(crate) struct RevisionManager { + /// Maximum number of revisions to keep on disk max_revisions: usize, - filebacked: FileBacked, - // historical: VecDeque>, - // proposals: Vec>, // TODO: Should be Vec> + + /// The underlying file storage + filebacked: Arc, + + /// The list of revisions that are on disk; these point to the different roots + /// stored in the filebacked storage. + historical: VecDeque>>, + proposals: Vec>>, // committing_proposals: VecDeque>, - // TODO: by_hash: HashMap + by_hash: HashMap>>, // TODO: maintain root hash of the most recent commit } @@ -36,13 +48,21 @@ impl RevisionManager { truncate: bool, config: RevisionManagerConfig, ) -> Result { - Ok(Self { + let storage = Arc::new(FileBacked::new(filename, config.node_cache_size, truncate)?); + let nodestore = Arc::new(NodeStore::new_empty_committed(storage.clone())?); + let manager = Self { max_revisions: config.max_revisions, - filebacked: FileBacked::new(filename, truncate)?, - // historical: Default::default(), - // proposals: Default::default(), + filebacked: storage, + historical: VecDeque::from([nodestore]), + by_hash: Default::default(), + proposals: Default::default(), // committing_proposals: Default::default(), - }) + }; + Ok(manager) + } + + pub fn latest_revision(&self) -> Option>> { + self.historical.back().cloned() } } @@ -59,129 +79,117 @@ pub(crate) enum RevisionManagerError { } impl RevisionManager { - // TODO fix this or remove it. It should take in a proposal. - fn commit(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { - todo!() - // // detach FileBacked from all revisions to make writes safe - // let new_historical = self.prepare_for_writes(&proposal)?; - - // // append this historical to the list of known historicals - // self.historical.push_back(new_historical); - - // // forget about older revisions - // while self.historical.len() > self.max_revisions { - // self.historical.pop_front(); - // } - - // // If we do copy on writes for underneath files, since we keep all changes - // // after bootstrapping, we should be able to read from the changes and the - // // read only file map to the state at bootstrapping. - // // We actually doesn't care whether the writes are successful or not - // // (crash recovery may need to be handled above) - // for write in proposal.new.iter() { - // self.filebacked.write(*write.0, write.1)?; - // } - - // self.writes_completed(proposal) - } - - // TODO fix or remove this. It should take in a proposal. - fn prepare_for_writes(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { - todo!() - // // check to see if we can commit this proposal - // let parent = proposal.parent(); - // match parent { - // LinearStoreParent::FileBacked(_) => { - // if !self.committing_proposals.is_empty() { - // return Err(RevisionManagerError::NotLatest); - // } - // } - // LinearStoreParent::Proposed(ref parent_proposal) => { - // let Some(last_commiting_proposal) = self.committing_proposals.back() else { - // return Err(RevisionManagerError::NotLatest); - // }; - // if !Arc::ptr_eq(parent_proposal, last_commiting_proposal) { - // return Err(RevisionManagerError::NotLatest); - // } - // } - // _ => return Err(RevisionManagerError::SiblingCommitted), - // } - // // checks complete: safe to commit - - // let new_historical = Arc::new(Historical::new( - // std::mem::take(&mut proposal.old.clone()), // TODO: remove clone - // parent.clone(), - // proposal.size()?, - // )); - - // // reparent the oldest historical to point to the new proposal - // if let Some(historical) = self.historical.back() { - // historical.reparent(new_historical.clone().into()); - // } - - // // for each outstanding proposal, see if their parent is the last committed linear store - // for candidate in self - // .proposals - // .iter() - // .filter(|&candidate| candidate.has_parent(&parent) && !Arc::ptr_eq(candidate, proposal)) - // { - // candidate.reparent(LinearStoreParent::Historical(new_historical.clone())); - // } - - // // mark this proposal as committing - // self.committing_proposals.push_back(proposal.clone()); - - // Ok(new_historical) - } - - // TODO fix or remove this. It should take in a proposal. - fn writes_completed(&mut self, _proposal: ()) -> Result<(), RevisionManagerError> { - todo!() - // // now that the committed proposal is on disk, reparent anything that pointed to our proposal, - // // which is now fully flushed to our parent, as our parent - // // TODO: This needs work when we support multiple simultaneous commit writes; we should - // // only do this work when the entire stack below us has been flushed - // let parent = proposal.parent(); - // let proposal = LinearStoreParent::Proposed(proposal); - // for candidate in self - // .proposals - // .iter() - // .filter(|&candidate| candidate.has_parent(&proposal)) - // { - // candidate.reparent(parent.clone()); - // } - - // // TODO: As of now, this is always what we just pushed, no support for multiple simultaneous - // // commits yet; the assert verifies this and should be removed when we add support for this - // let should_be_us = self - // .committing_proposals - // .pop_front() - // .expect("can't be empty"); - // assert!( - // matches!(proposal, LinearStoreParent::Proposed(us) if Arc::ptr_eq(&us, &should_be_us)) - // ); - - // // TODO: we should reparent fileback as the parent of this committed proposal?? - - // Ok(()) + /// Commit a proposal + /// To commit a proposal involves a few steps: + /// 1. Commit check. + /// The proposal’s parent must be the last committed revision, otherwise the commit fails. + /// 2. Persist delete list. + /// The list of all nodes that were to be deleted for this proposal must be fully flushed to disk. + /// The address of the root node and the root hash is also persisted. + /// Note that this is *not* a write ahead log. + /// It only contains the address of the nodes that are deleted, which should be very small. + /// 3. Set last committed revision. + /// Set last committed revision in memory. + /// Another commit can start after this but before the node flush is completed. + /// 4. Free list flush. + /// Persist/write the free list header. + /// The free list is flushed first to prevent future allocations from using the space allocated to this proposal. + /// This should be done in a single write since the free list headers are small, and must be persisted to disk before starting the next step. + /// 5. Node flush. + /// Persist/write all the nodes to disk. + /// Note that since these are all freshly allocated nodes, they will never be referred to by any prior commit. + /// After flushing all nodes, the file should be flushed to disk (fsync) before performing the next step. + /// 6. Root move. + /// The root address on disk must be updated. + /// This write can be delayed, but would mean that recovery will not roll forward to this revision. + /// 7. Proposal Cleanup. + /// Any other proposals that have this proposal as a parent should be reparented to the committed version. + /// 8. Revision reaping. + /// If more than the configurable number of revisions is available, the oldest revision can be forgotten. + pub fn commit( + &mut self, + proposal: Arc>, + ) -> Result<(), RevisionManagerError> { + // 1. Commit check + let current_revision = self.current_revision(); + if !proposal + .kind + .parent_is(¤t_revision.kind.as_nodestore_parent()) + { + return Err(RevisionManagerError::NotLatest); + } + // 2. Persist delete list + // TODO + + // 3. Set last committed revision + let committed: Arc> = proposal.as_committed().into(); + self.historical.push_back(committed.clone()); + if let Some(hash) = committed.kind.root_hash() { + self.by_hash.insert(hash, committed.clone()); + } + // TODO: We could allow other commits to start here using the pending list + + // 4. Free list flush + proposal.flush_freelist()?; + + // 5. Node flush + proposal.flush_nodes()?; + + // 6. Root move + proposal.flush_header()?; + + // 7. Proposal Cleanup + self.proposals.retain(|p| { + // TODO: reparent proposals; this needs a lock on the parent element of immutable proposals + // if p + // .kind + // .parent_is(&proposal.kind.as_nodestore_parent()) + // { + // p.kind.reparent_to(&committed.kind.as_nodestore_parent()); + // } + !Arc::ptr_eq(&proposal, p) + }); + + Ok(()) } } pub type NewProposalError = (); // TODO implement impl RevisionManager { - // TODO fix this or remove it. It should take in a proposal. - pub fn add_proposal(&mut self, _proposal: ()) { - todo!() - // self.proposals.push(proposal); + pub fn add_proposal(&mut self, proposal: Arc>) { + self.proposals.push(proposal); + } + + pub fn revision( + &self, + root_hash: HashKey, + ) -> Result>, RevisionManagerError> { + self.by_hash + .get(&root_hash) + .cloned() + .ok_or(RevisionManagerError::IO(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Revision not found", + ))) } - pub fn revision(&self, _root_hash: HashKey) -> Result<(), RevisionManagerError> { - todo!() + pub fn root_hash(&self) -> Result, RevisionManagerError> { + self.current_revision() + .kind + .root_hash() + .map(Option::Some) + .ok_or(RevisionManagerError::IO(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Root hash not found", + ))) } - pub fn root_hash(&self) -> Result { - todo!() + fn current_revision(&self) -> Arc> { + self.historical + .back() + .expect("there is always one revision") + .clone() } } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 746b0c1a32d3..b24f6fd514d6 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -127,11 +127,17 @@ fn get_helper( } #[derive(Debug)] -pub struct Merkle { +pub struct Merkle { nodestore: T, } -impl From for Merkle { +impl Merkle { + pub fn into_inner(self) -> T { + self.nodestore + } +} + +impl From for Merkle { fn from(nodestore: T) -> Self { Merkle { nodestore } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index ebfab2b7deea..95f7c2f88e4e 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -3,11 +3,12 @@ use crate::manager::RevisionManagerError; use crate::proof::ProofNode; -use crate::range_proof::RangeProof; +pub use crate::range_proof::RangeProof; use crate::{merkle::MerkleError, proof::Proof}; use async_trait::async_trait; use futures::Stream; use std::{fmt::Debug, sync::Arc}; +use storage::TrieHash; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with @@ -81,6 +82,12 @@ pub enum Error { #[error("Invalid proposal")] InvalidProposal, + // Cloned proposals are problematic because if they are committed, then you could + // create another proposal from this committed proposal, so we error at commit time + // if there are outstanding clones + #[error("Cannot commit a cloned proposal")] + CannotCommitClonedProposal, + #[error("Internal error")] InternalError(Box), @@ -112,17 +119,19 @@ impl From for Error { pub trait Db { type Historical: DbView; - type Proposal: DbView + Proposal; + type Proposal<'p>: DbView + Proposal + where + Self: 'p; /// Get a reference to a specific view based on a hash /// /// # Arguments /// /// - `hash` - Identifies the revision for the view - async fn revision(&self, hash: HashKey) -> Result, Error>; + async fn revision(&self, hash: TrieHash) -> Result, Error>; /// Get the hash of the most recently committed version - async fn root_hash(&self) -> Result, Error>; + async fn root_hash(&self) -> Result, Error>; /// Propose a change to the database via a batch /// @@ -134,10 +143,12 @@ pub trait Db { /// * `data` - A batch consisting of [BatchOp::Put] and /// [BatchOp::Delete] operations to apply /// - async fn propose( - &self, + async fn propose<'p, K: KeyType, V: ValueType>( + &'p mut self, data: Batch, - ) -> Result, Error>; + ) -> Result>, Error> + where + Self: 'p; } /// A view of the database at a specific time. These are wrapped with @@ -159,7 +170,7 @@ pub trait DbView { async fn root_hash(&self) -> Result, Error>; /// Get the value of a specific key - async fn val(&self, key: K) -> Result>, Error>; + async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key async fn single_key_proof(&self, key: K) diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 92b83f9b3070..2dc95203b28f 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -29,7 +29,7 @@ pub struct HistoricalImpl; impl Db for EmptyDb { type Historical = HistoricalImpl; - type Proposal = Proposal; + type Proposal<'p> = Proposal; async fn revision(&self, hash_key: HashKey) -> Result, Error> { Err(Error::HashNotFound { provided: hash_key }) @@ -39,7 +39,10 @@ impl Db for EmptyDb { Ok(None) } - async fn propose(&self, data: Batch) -> Result, Error> + async fn propose<'p, K, V>( + &'p mut self, + data: Batch, + ) -> Result>, Error> where K: KeyType, V: ValueType, @@ -59,7 +62,7 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn val(&self, _key: K) -> Result>, Error> { + async fn val(&self, _key: K) -> Result>, Error> { Ok(None) } @@ -105,7 +108,7 @@ mod tests { #[tokio::test] async fn basic_proposal() -> Result<(), Error> { - let db = Arc::new(EmptyDb); + let mut db = EmptyDb; let batch = vec![ BatchOp::Put { @@ -117,7 +120,10 @@ mod tests { let proposal = db.propose(batch).await?; - assert_eq!(proposal.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!( + proposal.val(b"k").await.unwrap().unwrap(), + Box::from(b"v".as_slice()) + ); assert!(proposal.val(b"z").await.unwrap().is_none()); @@ -126,8 +132,7 @@ mod tests { #[tokio::test] async fn nested_proposal() -> Result<(), Error> { - let db = Arc::new(EmptyDb); - + let mut db = EmptyDb; // create proposal1 which adds key "k" with value "v" and deletes "z" let batch = vec![ BatchOp::Put { @@ -148,12 +153,15 @@ mod tests { }]) .await?; // both proposals still have (k,v) - assert_eq!(proposal1.val(b"k").await.unwrap().unwrap(), b"v"); - assert_eq!(proposal2.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(proposal1.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); + assert_eq!(proposal2.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); // only proposal1 doesn't have z assert!(proposal1.val(b"z").await.unwrap().is_none()); // proposal2 has z with value "undo" - assert_eq!(proposal2.val(b"z").await.unwrap().unwrap(), b"undo"); + assert_eq!( + proposal2.val(b"z").await.unwrap().unwrap().to_vec(), + b"undo" + ); // create a proposal3 by adding the two proposals together, keeping the originals // TODO: consider making this possible again diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 95e442d58111..e86bfdaf67ee 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -110,12 +110,12 @@ impl api::DbView for Proposal { todo!(); } - async fn val(&self, key: K) -> Result>, api::Error> { + async fn val(&self, key: K) -> Result>, api::Error> { // see if this key is in this proposal match self.delta.get(key.as_ref()) { Some(change) => match change { // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(Some(val.to_owned())), + KeyOp::Put(val) => Ok(Some(val.clone().into_boxed_slice())), KeyOp::Delete => Ok(None), // key was deleted in this proposal }, None => match &self.base { diff --git a/storage/Cargo.toml b/storage/Cargo.toml index ad9a6860fe9e..52a568d87304 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1.0.199", features = ["derive"] } smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" +lru = "0.8.0" [dev-dependencies] rand = "0.8.5" diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 3821240bf0f4..0250cb7bfa37 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -25,7 +25,7 @@ pub use node::{ }; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, - NodeStore, ReadInMemoryNode, RootReader, TrieReader, UpdateError, + NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, }; pub use linear::{filebacked::FileBacked, memory::MemStore}; diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index ecbe601d069b..93815e367f4f 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -11,21 +11,31 @@ use std::fs::{File, OpenOptions}; use std::io::{Error, Read, Seek}; +use std::num::NonZero; use std::os::unix::fs::FileExt; use std::path::PathBuf; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; -use super::ReadableStorage; +use lru::LruCache; + +use crate::{LinearAddress, Node}; + +use super::{ReadableStorage, WritableStorage}; #[derive(Debug)] /// A [ReadableStorage] backed by a file pub struct FileBacked { fd: Mutex, + cache: Mutex>>, } impl FileBacked { /// Create or open a file at a given path - pub fn new(path: PathBuf, truncate: bool) -> Result { + pub fn new( + path: PathBuf, + node_cache_size: NonZero, + truncate: bool, + ) -> Result { let fd = OpenOptions::new() .read(true) .write(true) @@ -33,7 +43,10 @@ impl FileBacked { .truncate(truncate) .open(path)?; - Ok(Self { fd: Mutex::new(fd) }) + Ok(Self { + fd: Mutex::new(fd), + cache: Mutex::new(LruCache::new(node_cache_size)), + }) } } @@ -50,15 +63,31 @@ impl ReadableStorage for FileBacked { .expect("poisoned lock") .seek(std::io::SeekFrom::End(0)) } + + fn read_cached_node(&self, addr: LinearAddress) -> Option> { + let mut guard = self.cache.lock().expect("poisoned lock"); + guard.get(&addr).cloned() + } } -impl FileBacked { +impl WritableStorage for FileBacked { /// Write to the backend filestore. This does not implement [crate::WritableStorage] /// because we don't want someone accidentally writing nodes directly to disk - pub fn write(&mut self, offset: u64, object: &[u8]) -> Result { + fn write(&self, offset: u64, object: &[u8]) -> Result { self.fd .lock() .expect("poisoned lock") .write_at(object, offset) } + + fn write_cached_nodes<'a>( + &self, + nodes: impl Iterator, &'a std::sync::Arc)>, + ) -> Result<(), Error> { + let mut guard = self.cache.lock().expect("poisoned lock"); + for (addr, node) in nodes { + guard.put(*addr, node.clone()); + } + Ok(()) + } } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 942ca9c11220..5799c633e839 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -19,6 +19,10 @@ use std::fmt::Debug; use std::io::{Error, Read}; +use std::num::NonZero; +use std::sync::Arc; + +use crate::{LinearAddress, Node}; pub(super) mod filebacked; pub mod memory; @@ -38,6 +42,11 @@ pub trait ReadableStorage: Debug + Sync + Send { /// Return the size of the underlying storage, in bytes fn size(&self) -> Result; + + /// Read a node from the cache (if any) + fn read_cached_node(&self, _addr: LinearAddress) -> Option> { + None + } } /// Trait for writable storage. @@ -53,4 +62,12 @@ pub trait WritableStorage: ReadableStorage { /// /// The number of bytes written, or an error if the write operation fails. fn write(&self, offset: u64, object: &[u8]) -> Result; + + /// Write all nodes to the cache (if any) + fn write_cached_nodes<'a>( + &self, + _nodes: impl Iterator, &'a Arc)>, + ) -> Result<(), Error> { + Ok(()) + } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 45a6739f5893..6e4c59ba2d5e 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,8 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![allow(dead_code)] - use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -10,14 +8,33 @@ use std::fmt::Debug; /// free space management of nodes in the page store. It lays out the format /// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] /// and a [FreeSpaceHeader] at the beginning +/// +/// Nodestores represent a revision of the trie. There are three types of nodestores: +/// - Committed: A committed revision of the trie. It has no in-memory changes. +/// - MutableProposal: A proposal that is still being modified. It has some nodes in memory. +/// - ImmutableProposal: A proposal that has been hashed and assigned addresses. It has no in-memory changes. +/// +/// The general lifecycle of nodestores is as follows: +/// ```mermaid +/// flowchart TD +/// subgraph subgraph["Committed Revisions"] +/// L("Latest Nodestore<Committed, S>") --- |...|O("Oldest NodeStore<Committed, S>") +/// end +/// O --> E("Expire") +/// L --> |start propose|M("NodeStore<ProposedMutable, S>") +/// M --> |finish propose + hash|I("NodeStore<ProposedImmutable, S>") +/// I --> |commit|N("New commit NodeStore<Committed, S>") +/// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF +/// ``` use std::io::{Error, ErrorKind, Write}; use std::iter::once; +use std::mem::offset_of; use std::num::NonZeroU64; use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::Node; -use crate::{Child, Path, ReadableStorage, TrieHash}; +use crate::{Child, FileBacked, Path, ReadableStorage, TrieHash}; use super::linear::WritableStorage; @@ -61,9 +78,6 @@ const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; const SOME_FREE_LIST_ELT_SIZE: u64 = 1 + std::mem::size_of::() as u64; const FREE_LIST_MAX_SIZE: u64 = NUM_AREA_SIZES as u64 * SOME_FREE_LIST_ELT_SIZE; -/// Number of children in a branch -const BRANCH_CHILDREN: usize = 16; - /// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. fn area_size_to_index(n: u64) -> Result { if n > MAX_AREA_SIZE { @@ -110,6 +124,7 @@ struct StoredArea { impl NodeStore { /// Returns (index, area_size) for the [StoredArea] at `addr`. /// `index` is the index of `area_size` in [AREA_SIZES]. + #[allow(dead_code)] fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { let mut area_stream = self.storage.stream_from(addr.get())?; @@ -127,6 +142,10 @@ impl NodeStore { /// Read a [Node] from the provided [LinearAddress]. /// `addr` is the address of a StoredArea in the ReadableStorage. pub fn read_node_from_disk(&self, addr: LinearAddress) -> Result, Error> { + if let Some(node) = self.storage.read_cached_node(addr) { + return Ok(node); + } + debug_assert!(addr.get() % 8 == 0); let addr = addr.get() + 1; // Skip the index byte @@ -160,6 +179,7 @@ impl NodeStore { header, kind: Committed { deleted: Default::default(), + root_hash: None, // TODO: get the root hash }, storage, }) @@ -184,16 +204,46 @@ impl NodeStore { storage, kind: Committed { deleted: Default::default(), + root_hash: None, }, }) } } +/// Some nodestore kinds implement Parentable. +/// This means that the nodestore can have children. +/// Only [ImmutableProposal] and [Committed] implement this trait. +/// [MutableProposal] does not implement this trait because it is not a valid parent. +/// TODO: Maybe this can be renamed to ImmutableNodestore +pub trait Parentable { + /// Returns the parent of this nodestore. + fn as_nodestore_parent(&self) -> NodeStoreParent; + /// Returns the root hash of this nodestore. This works because all parentable nodestores have a hash + fn root_hash(&self) -> Option; +} + +impl Parentable for ImmutableProposal { + fn as_nodestore_parent(&self) -> NodeStoreParent { + NodeStoreParent::Proposed(Arc::new(self.clone())) + } + fn root_hash(&self) -> Option { + self.root_hash.clone() + } +} + +impl Parentable for Committed { + fn as_nodestore_parent(&self) -> NodeStoreParent { + NodeStoreParent::Committed(self.root_hash.clone()) + } + fn root_hash(&self) -> Option { + self.root_hash.clone() + } +} + impl NodeStore { /// Create a new MutableProposal [NodeStore] from a parent [NodeStore] - pub fn new + ReadInMemoryNode>( - parent: NodeStore, - storage: Arc, + pub fn new( + parent: Arc>, ) -> Result { let mut deleted: Vec<_> = Default::default(); let root = if let Some(root_addr) = parent.header.root_address { @@ -206,12 +256,12 @@ impl NodeStore { let kind = MutableProposal { root, deleted, - parent: parent.kind.into(), + parent: parent.kind.as_nodestore_parent(), }; Ok(NodeStore { header: parent.header.clone(), kind, - storage, + storage: parent.storage.clone(), }) } @@ -256,7 +306,7 @@ impl NodeStore { kind: MutableProposal { root: None, deleted: Default::default(), - parent: NodeStoreParent::Committed, + parent: NodeStoreParent::Committed(None), }, storage, } @@ -499,6 +549,20 @@ pub trait HashedNodeReader: TrieReader { /// Reads nodes and the root address from a merkle trie. pub trait TrieReader: NodeReader + RootReader {} +impl TrieReader for &NodeStore {} +impl NodeReader for &NodeStore { + fn read_node(&self, addr: LinearAddress) -> Result, Error> { + self.read_node_from_disk(addr) + } +} +impl RootReader for &NodeStore { + fn root_node(&self) -> Option> { + self.header + .root_address + .map(|addr| self.read_node_from_disk(addr).unwrap()) + } +} + /// Reads nodes from a merkle trie. pub trait NodeReader { /// Returns the node at `addr`. @@ -512,9 +576,11 @@ pub trait RootReader { } /// A committed revision of a merkle trie. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Committed { + #[allow(dead_code)] deleted: Box<[LinearAddress]>, + root_hash: Option, } impl ReadInMemoryNode for Committed { @@ -524,39 +590,48 @@ impl ReadInMemoryNode for Committed { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum NodeStoreParent { Proposed(Arc), - Committed, + Committed(Option), } -impl From> for NodeStoreParent { - fn from(_: NodeStore) -> Self { - NodeStoreParent::Committed +impl PartialEq for NodeStoreParent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (NodeStoreParent::Proposed(a), NodeStoreParent::Proposed(b)) => Arc::ptr_eq(a, b), + (NodeStoreParent::Committed(a), NodeStoreParent::Committed(b)) => a == b, + _ => false, + } } } -impl From> for NodeStoreParent { - fn from(val: NodeStore) -> Self { - NodeStoreParent::Proposed(Arc::new(val.kind)) - } -} +impl Eq for NodeStoreParent {} -#[derive(Debug)] +#[derive(Clone, Debug)] /// Contains state for a proposed revision of the trie. pub struct ImmutableProposal { /// Address --> Node for nodes created in this proposal. - new: HashMap>, + new: HashMap)>, /// Nodes that have been deleted in this proposal. deleted: Box<[LinearAddress]>, /// The parent of this proposal. parent: NodeStoreParent, + /// The hash of the root node for this proposal + root_hash: Option, +} + +impl ImmutableProposal { + /// Returns true if the parent of this proposal is `parent`. + pub fn parent_is(&self, parent: &NodeStoreParent) -> bool { + &self.parent == parent + } } impl ReadInMemoryNode for ImmutableProposal { fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { // Check if the node being requested was created in this proposal. - if let Some(node) = self.new.get(&addr) { + if let Some((_, node)) = self.new.get(&addr) { return Some(node.clone()); } @@ -564,7 +639,7 @@ impl ReadInMemoryNode for ImmutableProposal { // a committed revision. match self.parent { NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), - NodeStoreParent::Committed => None, + NodeStoreParent::Committed(_) => None, } } } @@ -593,13 +668,13 @@ pub trait ReadInMemoryNode { /// 5. Convert an immutable proposal to a committed revision using [std::convert::TryInto], which writes the nodes to disk. #[derive(Debug)] -pub struct NodeStore { +pub struct NodeStore { // Metadata for this revision. header: NodeStoreHeader, /// This is one of [Committed], [ImmutableProposal], or [MutableProposal]. - kind: T, - // Persisted storage to read nodes from. - storage: Arc, + pub kind: T, + /// Persisted storage to read nodes from. + pub storage: Arc, } /// Contains the state of a proposal that is still being modified. @@ -618,7 +693,7 @@ impl ReadInMemoryNode for NodeStoreParent { fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { match self { NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), - NodeStoreParent::Committed => None, + NodeStoreParent::Committed(_) => None, } } } @@ -656,6 +731,20 @@ impl, S: ReadableStorage> From From> for NodeStore { + fn from(val: NodeStore) -> Self { + NodeStore { + header: val.header, + kind: Committed { + deleted: val.kind.deleted, + root_hash: val.kind.root_hash, + }, + storage: val.storage, + } + } +} + impl NodeStore { /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. /// Returns the hashed node and its hash. @@ -686,14 +775,76 @@ impl NodeStore { } let hash = hash_node(&node, path_prefix); - let (addr, _) = self.allocate_node(&node).expect("TODO handle error"); + let (addr, size) = self.allocate_node(&node).expect("TODO handle error"); - self.kind.new.insert(addr, Arc::new(node)); + self.kind.new.insert(addr, (size, Arc::new(node))); (addr, hash) } } +impl NodeStore { + /// Persist the freelist from this proposal to storage. + pub fn flush_freelist(&self) -> Result<(), Error> { + // Write the free lists to storage + let free_list_bytes = bincode::serialize(&self.header.free_lists) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; + self.storage + .write(free_list_offset, free_list_bytes.as_slice())?; + Ok(()) + } + + /// Persist the header from this proposal to storage. + pub fn flush_header(&self) -> Result<(), Error> { + let header_bytes = bincode::serialize(&self.header).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to serialize header: {}", e), + ) + })?; + + self.storage.write(0, header_bytes.as_slice())?; + + Ok(()) + } + + /// Persist all the nodes of a proposal to storage. + pub fn flush_nodes(&self) -> Result<(), Error> { + for (addr, (area_size_index, node)) in self.kind.new.iter() { + let stored_area = StoredArea { + area_size_index: *area_size_index, + area: Area::<_, FreeArea>::Node(node.as_ref()), + }; + + let stored_area_bytes = bincode::serialize(&stored_area) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + self.storage + .write(addr.get(), stored_area_bytes.as_slice())?; + } + self.storage + .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + + Ok(()) + } +} + +impl NodeStore { + /// Return a Committed version of this proposal, which doesn't have any modified nodes. + /// This function is used during commit. + pub fn as_committed(&self) -> NodeStore { + NodeStore { + header: self.header.clone(), + kind: Committed { + deleted: self.kind.deleted.clone(), + root_hash: self.kind.root_hash.clone(), + }, + storage: self.storage.clone(), + } + } +} + impl From> for NodeStore { fn from(val: NodeStore) -> Self { let NodeStore { @@ -708,6 +859,7 @@ impl From> for NodeStore From> for NodeStore); impl std::ops::Deref for TrieHash { From 1e9b7b0a9bace96efa491087023181410bddf21a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 07:03:06 -1000 Subject: [PATCH 0542/1053] build(deps): update lru requirement from 0.8.0 to 0.12.4 (#708) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 52a568d87304..70a458e87116 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1.0.199", features = ["derive"] } smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" -lru = "0.8.0" +lru = "0.12.4" [dev-dependencies] rand = "0.8.5" From f4e341c0e97973fd564032545d5c38a519261bc4 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 22 Aug 2024 10:54:17 -1000 Subject: [PATCH 0543/1053] Code cleanups (#709) --- firewood/Cargo.toml | 1 + firewood/src/db.rs | 70 +++++++++++++++++++++++++++++++++++++++-- firewood/src/manager.rs | 55 ++++++++++++++------------------ 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 9073681606bc..cf87264ed9e7 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -43,6 +43,7 @@ rand = "0.8.5" triehash = "0.8.4" clap = { version = "4.5.0", features = ['derive'] } pprof = { version = "0.13.0", features = ["flamegraph"] } +tempfile = "3.12.0" [[bench]] name = "hashops" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 2652bdcfedef..449770b228db 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -248,8 +248,7 @@ where .manager .read() .expect("poisoned lock") - .latest_revision() - .expect("no latest revision"); + .current_revision(); let proposal = NodeStore::new(parent)?; let mut merkle = Merkle::from(proposal); for op in batch { @@ -375,3 +374,70 @@ impl<'a> api::Proposal for Proposal<'a> { } } } +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod test { + use std::{ + ops::{Deref, DerefMut}, + path::PathBuf, + }; + + use crate::{ + db::Db, + v2::api::{Db as _, Error, Proposal}, + }; + + use super::DbConfig; + + #[tokio::test] + async fn test_cloned_proposal_error() { + let mut db = testdb().await; + let proposal = db + .propose::, Vec>(Default::default()) + .await + .unwrap(); + let cloned = proposal.clone(); + + // attempt to commit the clone; this should fail + let result = cloned.commit().await; + assert!( + matches!(result, Err(Error::CannotCommitClonedProposal)), + "{result:?}" + ); + + // the prior attempt consumed the Arc though, so cloned is no longer valid + // that means the actual proposal can be committed + let result = proposal.commit().await; + assert!(matches!(result, Ok(())), "{result:?}"); + } + + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear + struct TestDb { + db: Db, + _tmpdir: tempfile::TempDir, + } + impl Deref for TestDb { + type Target = Db; + fn deref(&self) -> &Self::Target { + &self.db + } + } + impl DerefMut for TestDb { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.db + } + } + + async fn testdb() -> TestDb { + let tmpdir = tempfile::tempdir().unwrap(); + let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect(); + let dbconfig = DbConfig::builder().truncate(true).build(); + let db = Db::new(dbpath, dbconfig).await.unwrap(); + TestDb { + db, + _tmpdir: tmpdir, + } + } +} diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index b00bcd73f035..b221969670d3 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -25,6 +25,9 @@ pub struct RevisionManagerConfig { node_cache_size: NonZero, } +type CommittedRevision = Arc>; +type ProposedRevision = Arc>; + #[derive(Debug)] pub(crate) struct RevisionManager { /// Maximum number of revisions to keep on disk @@ -35,13 +38,25 @@ pub(crate) struct RevisionManager { /// The list of revisions that are on disk; these point to the different roots /// stored in the filebacked storage. - historical: VecDeque>>, - proposals: Vec>>, + historical: VecDeque, + proposals: Vec, // committing_proposals: VecDeque>, - by_hash: HashMap>>, + by_hash: HashMap, // TODO: maintain root hash of the most recent commit } +#[derive(Debug, thiserror::Error)] +pub(crate) enum RevisionManagerError { + #[error("The proposal cannot be committed since a sibling was committed")] + SiblingCommitted, + #[error( + "The proposal cannot be committed since it is not a direct child of the most recent commit" + )] + NotLatest, + #[error("An IO error occurred during the commit")] + IO(#[from] std::io::Error), +} + impl RevisionManager { pub fn new( filename: PathBuf, @@ -61,24 +76,6 @@ impl RevisionManager { Ok(manager) } - pub fn latest_revision(&self) -> Option>> { - self.historical.back().cloned() - } -} - -#[derive(Debug, thiserror::Error)] -pub(crate) enum RevisionManagerError { - #[error("The proposal cannot be committed since a sibling was committed")] - SiblingCommitted, - #[error( - "The proposal cannot be committed since it is not a direct child of the most recent commit" - )] - NotLatest, - #[error("An IO error occurred during the commit")] - IO(#[from] std::io::Error), -} - -impl RevisionManager { /// Commit a proposal /// To commit a proposal involves a few steps: /// 1. Commit check. @@ -106,10 +103,7 @@ impl RevisionManager { /// Any other proposals that have this proposal as a parent should be reparented to the committed version. /// 8. Revision reaping. /// If more than the configurable number of revisions is available, the oldest revision can be forgotten. - pub fn commit( - &mut self, - proposal: Arc>, - ) -> Result<(), RevisionManagerError> { + pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); if !proposal @@ -122,7 +116,7 @@ impl RevisionManager { // TODO // 3. Set last committed revision - let committed: Arc> = proposal.as_committed().into(); + let committed: CommittedRevision = proposal.as_committed().into(); self.historical.push_back(committed.clone()); if let Some(hash) = committed.kind.root_hash() { self.by_hash.insert(hash, committed.clone()); @@ -157,14 +151,11 @@ impl RevisionManager { pub type NewProposalError = (); // TODO implement impl RevisionManager { - pub fn add_proposal(&mut self, proposal: Arc>) { + pub fn add_proposal(&mut self, proposal: ProposedRevision) { self.proposals.push(proposal); } - pub fn revision( - &self, - root_hash: HashKey, - ) -> Result>, RevisionManagerError> { + pub fn revision(&self, root_hash: HashKey) -> Result { self.by_hash .get(&root_hash) .cloned() @@ -185,7 +176,7 @@ impl RevisionManager { ))) } - fn current_revision(&self) -> Arc> { + pub fn current_revision(&self) -> CommittedRevision { self.historical .back() .expect("there is always one revision") From f139784a9dc5f948098db5cd9cdc7d4cf6022d02 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 22 Aug 2024 12:46:36 -1000 Subject: [PATCH 0544/1053] Implement reparenting (depends on 701) (#706) Signed-off-by: Ron Kuris --- firewood/src/db.rs | 8 +- firewood/src/manager.rs | 24 ++--- firewood/src/merkle.rs | 4 +- storage/Cargo.toml | 1 + storage/src/nodestore.rs | 209 +++++++++++++++++++++++++++++++++------ 5 files changed, 199 insertions(+), 47 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 449770b228db..b9a422619311 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -262,7 +262,8 @@ where } } let nodestore = merkle.into_inner(); - let immutable: Arc> = Arc::new(nodestore.into()); + let immutable: Arc, FileBacked>> = + Arc::new(nodestore.into()); self.manager .write() .expect("poisoned lock") @@ -312,13 +313,13 @@ impl Db { #[derive(Debug)] pub struct Proposal<'p> { - nodestore: Arc>, + nodestore: Arc, FileBacked>>, db: &'p Db, } #[async_trait] impl<'a> api::DbView for Proposal<'a> { - type Stream<'b> = MerkleKeyValueStream<'b, NodeStore> where Self: 'b; + type Stream<'b> = MerkleKeyValueStream<'b, NodeStore, FileBacked>> where Self: 'b; async fn root_hash(&self) -> Result, api::Error> { todo!() @@ -363,7 +364,6 @@ impl<'a> api::Proposal for Proposal<'a> { todo!() } - // When committing a proposal, refuse to commit if there are any cloned proposals. async fn commit(self: Arc) -> Result<(), api::Error> { match Arc::into_inner(self) { Some(proposal) => { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index b221969670d3..9daadede63a6 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -26,7 +26,7 @@ pub struct RevisionManagerConfig { } type CommittedRevision = Arc>; -type ProposedRevision = Arc>; +type ProposedRevision = Arc, FileBacked>>; #[derive(Debug)] pub(crate) struct RevisionManager { @@ -108,7 +108,7 @@ impl RevisionManager { let current_revision = self.current_revision(); if !proposal .kind - .parent_is(¤t_revision.kind.as_nodestore_parent()) + .parent_hash_is(current_revision.kind.root_hash()) { return Err(RevisionManagerError::NotLatest); } @@ -133,16 +133,16 @@ impl RevisionManager { proposal.flush_header()?; // 7. Proposal Cleanup - self.proposals.retain(|p| { - // TODO: reparent proposals; this needs a lock on the parent element of immutable proposals - // if p - // .kind - // .parent_is(&proposal.kind.as_nodestore_parent()) - // { - // p.kind.reparent_to(&committed.kind.as_nodestore_parent()); - // } - !Arc::ptr_eq(&proposal, p) - }); + // first remove the committing proposal from the list of outstanding proposals + self.proposals.retain(|p| !Arc::ptr_eq(&proposal, p)); + + // then reparent any proposals that have this proposal as a parent + for p in self.proposals.iter() { + proposal.commit_reparent(p); + } + + // 8. Revision reaping + // TODO Ok(()) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b24f6fd514d6..5c9ffbc9d226 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -385,7 +385,7 @@ impl Merkle { } impl From>> - for Merkle> + for Merkle, S>> { fn from(m: Merkle>) -> Self { Merkle { @@ -395,7 +395,7 @@ impl From>> } impl Merkle> { - pub fn hash(self) -> Merkle> { + pub fn hash(self) -> Merkle, S>> { self.into() } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 70a458e87116..7bf019dbef6a 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1.0.199", features = ["derive"] } smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" +arc-swap = "1.7.1" lru = "0.12.4" [dev-dependencies] diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 6e4c59ba2d5e..6eee06c20e84 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,6 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use arc_swap::access::DynAccess; +use arc_swap::ArcSwap; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -26,6 +28,24 @@ use std::fmt::Debug; /// I --> |commit|N("New commit NodeStore<Committed, S>") /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` +/// +/// Nodestores represent a revision of the trie. There are three types of nodestores: +/// - Committed: A committed revision of the trie. It has no in-memory changes. +/// - MutableProposal: A proposal that is still being modified. It has some nodes in memory. +/// - ImmutableProposal: A proposal that has been hashed and assigned addresses. It has no in-memory changes. +/// +/// The general lifecycle of nodestores is as follows: +/// ```mermaid +/// flowchart TD +/// subgraph subgraph["Committed Revisions"] +/// L("Latest Nodestore<Committed, S>") --- |...|O("Oldest NodeStore<Committed, S>") +/// end +/// O --> E("Expire") +/// L --> |start propose|M("NodeStore<ProposedMutable, S>") +/// M --> |finish propose + hash|I("NodeStore<ProposedImmutable, S>") +/// I --> |commit|N("New commit NodeStore<Committed, S>") +/// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF +/// ``` use std::io::{Error, ErrorKind, Write}; use std::iter::once; use std::mem::offset_of; @@ -222,15 +242,36 @@ pub trait Parentable { fn root_hash(&self) -> Option; } -impl Parentable for ImmutableProposal { +impl Parentable for Arc { fn as_nodestore_parent(&self) -> NodeStoreParent { - NodeStoreParent::Proposed(Arc::new(self.clone())) + NodeStoreParent::Proposed(self.clone()) } fn root_hash(&self) -> Option { self.root_hash.clone() } } +impl NodeStore, S> { + /// When an immutable proposal commits, we need to reparent any proposal that + /// has the committed proposal as it's parent + pub fn commit_reparent(&self, other: &Arc, S>>) -> bool { + match *other.kind.parent.load() { + NodeStoreParent::Proposed(ref parent) => { + if Arc::ptr_eq(&self.kind, parent) { + other + .kind + .parent + .store(NodeStoreParent::Committed(self.kind.root_hash()).into()); + true + } else { + false + } + } + NodeStoreParent::Committed(_) => false, + } + } +} + impl Parentable for Committed { fn as_nodestore_parent(&self) -> NodeStoreParent { NodeStoreParent::Committed(self.root_hash.clone()) @@ -313,7 +354,7 @@ impl NodeStore { } } -impl NodeStore { +impl NodeStore, S> { /// Attempts to allocate `n` bytes from the free lists. /// If successful returns the address of the newly allocated area /// and the index of the free list that was used. @@ -616,19 +657,26 @@ pub struct ImmutableProposal { /// Nodes that have been deleted in this proposal. deleted: Box<[LinearAddress]>, /// The parent of this proposal. - parent: NodeStoreParent, + parent: Arc>, /// The hash of the root node for this proposal root_hash: Option, } impl ImmutableProposal { - /// Returns true if the parent of this proposal is `parent`. - pub fn parent_is(&self, parent: &NodeStoreParent) -> bool { - &self.parent == parent + /// Returns true if the parent of this proposal is committed and has the given hash. + pub fn parent_hash_is(&self, hash: Option) -> bool { + match > as arc_swap::access::DynAccess>>::load( + &self.parent, + ) + .as_ref() + { + NodeStoreParent::Committed(root_hash) => *root_hash == hash, + _ => false, + } } } -impl ReadInMemoryNode for ImmutableProposal { +impl ReadInMemoryNode for Arc { fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { // Check if the node being requested was created in this proposal. if let Some((_, node)) = self.new.get(&addr) { @@ -637,7 +685,7 @@ impl ReadInMemoryNode for ImmutableProposal { // It wasn't. Try our parent, and its parent, and so on until we find it or find // a committed revision. - match self.parent { + match *self.parent.load() { NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), NodeStoreParent::Committed(_) => None, } @@ -704,15 +752,6 @@ impl ReadInMemoryNode for MutableProposal { fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { self.parent.read_in_memory_node(addr) } - - // fn read_in_memory_root(&self) -> Option>> { - // let Some(root) = &self.root else { - // return Some(None); - // }; - - // let root = Arc::new(root.clone()); - // Some(Some(root)) - // } } impl, S: ReadableStorage> From> @@ -745,10 +784,15 @@ impl From> for NodeStore NodeStore { +impl NodeStore, S> { /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. /// Returns the hashed node and its hash. - fn hash_helper(&mut self, mut node: Node, path_prefix: &mut Path) -> (LinearAddress, TrieHash) { + fn hash_helper( + &mut self, + mut node: Node, + path_prefix: &mut Path, + new_nodes: &mut HashMap)>, + ) -> (LinearAddress, TrieHash) { // Allocate addresses and calculate hashes for all new nodes match node { Node::Branch(ref mut b) => { @@ -766,7 +810,8 @@ impl NodeStore { .0 .extend(b.partial_path.0.iter().copied().chain(once(nibble as u8))); - let (child_addr, child_hash) = self.hash_helper(child_node, path_prefix); + let (child_addr, child_hash) = + self.hash_helper(child_node, path_prefix, new_nodes); *child = Some(Child::AddressWithHash(child_addr, child_hash)); path_prefix.0.truncate(original_length); } @@ -777,7 +822,7 @@ impl NodeStore { let hash = hash_node(&node, path_prefix); let (addr, size) = self.allocate_node(&node).expect("TODO handle error"); - self.kind.new.insert(addr, (size, Arc::new(node))); + new_nodes.insert(addr, (size, Arc::new(node))); (addr, hash) } @@ -845,7 +890,71 @@ impl NodeStore { } } -impl From> for NodeStore { +impl NodeStore, S> { + /// Persist the freelist from this proposal to storage. + pub fn flush_freelist(&self) -> Result<(), Error> { + // Write the free lists to storage + let free_list_bytes = bincode::serialize(&self.header.free_lists) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; + self.storage + .write(free_list_offset, free_list_bytes.as_slice())?; + Ok(()) + } + + /// Persist the header from this proposal to storage. + pub fn flush_header(&self) -> Result<(), Error> { + let header_bytes = bincode::serialize(&self.header).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to serialize header: {}", e), + ) + })?; + + self.storage.write(0, header_bytes.as_slice())?; + + Ok(()) + } + + /// Persist all the nodes of a proposal to storage. + pub fn flush_nodes(&self) -> Result<(), Error> { + for (addr, (area_size_index, node)) in self.kind.new.iter() { + let stored_area = StoredArea { + area_size_index: *area_size_index, + area: Area::<_, FreeArea>::Node(node.as_ref()), + }; + + let stored_area_bytes = bincode::serialize(&stored_area) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + self.storage + .write(addr.get(), stored_area_bytes.as_slice())?; + } + self.storage + .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + + Ok(()) + } +} + +impl NodeStore, FileBacked> { + /// Return a Committed version of this proposal, which doesn't have any modified nodes. + /// This function is used during commit. + pub fn as_committed(&self) -> NodeStore { + NodeStore { + header: self.header.clone(), + kind: Committed { + deleted: self.kind.deleted.clone(), + root_hash: self.kind.root_hash.clone(), + }, + storage: self.storage.clone(), + } + } +} + +impl From> + for NodeStore, S> +{ fn from(val: NodeStore) -> Self { let NodeStore { header, @@ -855,12 +964,12 @@ impl From> for NodeStore From> for NodeStore RootReader for NodeStore { trait Hashed {} impl Hashed for Committed {} -impl Hashed for ImmutableProposal {} +impl Hashed for Arc {} impl RootReader for NodeStore { fn root_node(&self) -> Option> { @@ -932,6 +1049,7 @@ where #[allow(clippy::unwrap_used)] mod tests { use crate::linear::memory::MemStore; + use arc_swap::access::DynGuard; use super::*; @@ -963,6 +1081,39 @@ mod tests { assert!(area_size_to_index(MAX_AREA_SIZE + 1).is_err()); } + #[test] + fn test_reparent() { + // create an empty base revision + let memstore = MemStore::new(vec![]); + let base = NodeStore::new_empty_committed(memstore.into()) + .unwrap() + .into(); + + // create an empty r1, check that it's parent is the empty committed version + let r1 = NodeStore::new(base).unwrap(); + let r1: Arc, _>> = Arc::new(r1.into()); + let parent: DynGuard> = r1.kind.parent.load(); + assert!(matches!(**parent, NodeStoreParent::Committed(None))); + + // create an empty r2, check that it's parent is the proposed version r1 + let r2: NodeStore = NodeStore::new(r1.clone()).unwrap(); + let r2: Arc, _>> = Arc::new(r2.into()); + let parent: DynGuard> = r2.kind.parent.load(); + assert!(matches!(**parent, NodeStoreParent::Proposed(_))); + + // reparent r2 + r1.commit_reparent(&r2); + + // now check r2's parent, should match the hash of r1 (which is still None) + let parent: DynGuard> = r2.kind.parent.load(); + if let NodeStoreParent::Committed(hash) = &**parent { + assert_eq!(*hash, r1.root_hash().unwrap()); + assert_eq!(*hash, None); + } else { + panic!("expected committed parent"); + } + } + // TODO add new tests // #[test] // fn test_create() { From e6ab2f6f3af52c6aa88293430116cadc4506f904 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 06:17:33 -1000 Subject: [PATCH 0545/1053] build(deps): update typed-builder requirement from 0.19.1 to 0.20.0 (#711) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index cf87264ed9e7..86c16738ec13 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -26,7 +26,7 @@ serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } -typed-builder = "0.19.1" +typed-builder = "0.20.0" bincode = "1.3.3" log = { version = "0.4.20", optional = true } test-case = "3.3.1" From c7c87ac2f6a1ad6441a8694e20ab3def97a13864 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 27 Aug 2024 10:10:09 -1000 Subject: [PATCH 0546/1053] Implement more of the API (#710) Co-authored-by: Richard Pringle --- firewood/benches/hashops.rs | 107 +++++++-------- firewood/examples/insert.rs | 2 +- firewood/src/db.rs | 183 +++++++------------------- firewood/src/manager.rs | 3 - firewood/src/v2/api.rs | 29 ++-- firewood/src/v2/emptydb.rs | 23 ++-- firewood/src/v2/propose.rs | 5 +- grpc-testtool/benches/insert.rs | 2 +- grpc-testtool/src/service.rs | 17 +-- grpc-testtool/src/service/database.rs | 109 ++++++++------- storage/src/nodestore.rs | 63 ++++++--- 11 files changed, 231 insertions(+), 312 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index a6ee313cb4cb..d875db63262a 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -4,7 +4,9 @@ // hash benchmarks; run with 'cargo bench' use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; +use firewood::db::{BatchOp, DbConfig}; use firewood::merkle::Merkle; +use firewood::v2::api::{Db as _, Proposal as _}; use pprof::ProfilerGuard; use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; use std::sync::Arc; @@ -52,23 +54,6 @@ impl Profiler for FlamegraphProfiler { } } -// TODO danlaine use or remove -// fn bench_trie_hash(criterion: &mut Criterion) { -// let mut to = [1u8; TRIE_HASH_LEN]; -// let mut store = InMemLinearStore::new(TRIE_HASH_LEN as u64, 0u8); -// store.write(0, &*ZERO_HASH).expect("write should succeed"); - -// #[allow(clippy::unwrap_used)] -// criterion -// .benchmark_group("TrieHash") -// .bench_function("dehydrate", |b| { -// b.iter(|| ZERO_HASH.serialize(&mut to).unwrap()); -// }) -// .bench_function("hydrate", |b| { -// b.iter(|| TrieHash::deserialize(0, &store).unwrap()); -// }); -// } - // This benchmark peeks into the merkle layer and times how long it takes // to insert NKEYS with a key length of KEYSIZE #[allow(clippy::unwrap_used)] @@ -107,57 +92,53 @@ fn bench_merkle(criterion: &mut Criter }); } -// This bechmark does the same thing as bench_merkle except it uses the revision manager -// TODO: Enable again once the revision manager is stable -// fn _bench_db(criterion: &mut Criterion) { -// const KEY_LEN: usize = 4; -// let mut rng = StdRng::seed_from_u64(1234); - -// #[allow(clippy::unwrap_used)] -// criterion -// .benchmark_group("Db") -// .sample_size(30) -// .bench_function("commit", |b| { -// b.to_async(tokio::runtime::Runtime::new().unwrap()) -// .iter_batched( -// || { -// let batch_ops: Vec<_> = repeat_with(|| { -// (&mut rng) -// .sample_iter(&Alphanumeric) -// .take(KEY_LEN) -// .collect() -// }) -// .map(|key: Vec<_>| BatchOp::Put { -// key, -// value: vec![b'v'], -// }) -// .take(N) -// .collect(); -// batch_ops -// }, -// |batch_ops| async { -// let db_path = std::env::temp_dir(); -// let db_path = db_path.join("benchmark_db"); -// let cfg = DbConfig::builder(); - -// #[allow(clippy::unwrap_used)] -// let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) -// .await -// .unwrap(); - -// #[allow(clippy::unwrap_used)] -// db.propose(batch_ops).await.unwrap().commit().await.unwrap() -// }, -// BatchSize::SmallInput, -// ); -// }); -// } +#[allow(clippy::unwrap_used)] +fn bench_db(criterion: &mut Criterion) { + const KEY_LEN: usize = 4; + let mut rng = StdRng::seed_from_u64(1234); + + criterion + .benchmark_group("Db") + .sample_size(30) + .bench_function("commit", |b| { + b.to_async(tokio::runtime::Runtime::new().unwrap()) + .iter_batched( + || { + let batch_ops: Vec<_> = repeat_with(|| { + (&mut rng) + .sample_iter(&Alphanumeric) + .take(KEY_LEN) + .collect() + }) + .map(|key: Vec<_>| BatchOp::Put { + key, + value: vec![b'v'], + }) + .take(N) + .collect(); + batch_ops + }, + |batch_ops| async { + let db_path = std::env::temp_dir(); + let db_path = db_path.join("benchmark_db"); + let cfg = DbConfig::builder(); + + let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) + .await + .unwrap(); + + db.propose(batch_ops).await.unwrap().commit().await.unwrap() + }, + BatchSize::SmallInput, + ); + }); +} criterion_group! { name = benches; config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); // targets = bench_trie_hash, bench_merkle::<3, 32>, bench_db::<100> - targets = bench_merkle::<3, 4>, bench_merkle<3, 32> + targets = bench_merkle::<3, 4>, bench_merkle<3, 32>, bench_db<100> } criterion_main!(benches); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index dfd86e79bed9..f9f32f2f9618 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -49,7 +49,7 @@ async fn main() -> Result<(), Box> { let args = Args::parse(); - let mut db = Db::new("rev_db", cfg) + let db = Db::new("rev_db", cfg) .await .expect("db initiation should succeed"); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b9a422619311..23903b96f52b 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -19,9 +19,6 @@ use std::sync::{Arc, RwLock}; use storage::{Committed, FileBacked, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash}; use typed_builder::TypedBuilder; -// TODO use or remove -const _VERSION_STR: &[u8; 16] = b"firewood v0.1\0\0\0"; - #[derive(Debug)] #[non_exhaustive] pub enum DbError { @@ -69,9 +66,10 @@ impl api::DbView for HistoricalRev { async fn single_key_proof( &self, - _key: K, - ) -> Result>, api::Error> { - todo!() + key: K, + ) -> Result, api::Error> { + let merkle = Merkle::from(self); + merkle.prove(key.as_ref()).map_err(api::Error::from) } async fn range_proof( @@ -91,110 +89,6 @@ impl api::DbView for HistoricalRev { } } -// impl HistoricalRev { -// pub fn stream(&self) -> MerkleKeyValueStream<'_, T> { -// todo!() -// } - -// pub fn stream_from(&self, _start_key: &[u8]) -> MerkleKeyValueStream<'_, T> { -// todo!() -// } - -// /// Get root hash of the generic key-value storage. -// pub fn kv_root_hash(&self) -> Result { -// todo!() -// } - -// /// Get a value associated with a key. -// pub fn get(&self, _key: &[u8]) -> Option> { -// todo!() -// } - -// /// Dump the Trie of the generic key-value storage. -// pub fn dump(&self, _w: &mut dyn Write) -> Result<(), DbError> { -// todo!() -// } - -// pub fn prove(&self, _key: &[u8]) -> Result, MerkleError> { -// todo!() -// } - -// /// Verifies a range proof is valid for a set of keys. -// pub fn verify_range_proof>( -// &self, -// _proof: &Proof, -// _first_key: &[u8], -// _last_key: &[u8], -// _keys: Vec<&[u8]>, -// _values: Vec, -// ) -> Result { -// todo!() -// } -// } - -/// TODO danlaine: implement -// pub struct Proposal { -// _proposal: T, -// } - -// #[async_trait] -// impl api::Proposal for Proposal { -// type Proposal = Proposal; - -// async fn commit(self: Arc) -> Result<(), api::Error> { -// todo!() -// } - -// async fn propose( -// self: Arc, -// _data: api::Batch, -// ) -> Result, api::Error> { -// todo!() -// } -// } - -// #[async_trait] -// impl api::DbView for Proposal { -// type Stream<'a> = MerkleKeyValueStream<'a, T> where T: 'a; - -// async fn root_hash(&self) -> Result { -// todo!() -// } - -// async fn val(&self, _key: K) -> Result>, api::Error> -// where -// K: api::KeyType, -// { -// todo!() -// } - -// async fn single_key_proof(&self, _key: K) -> Result>, api::Error> -// where -// K: api::KeyType, -// { -// todo!() -// } - -// async fn range_proof( -// &self, -// _first_key: Option, -// _last_key: Option, -// _limit: Option, -// ) -> Result, Vec, ProofNode>>, api::Error> -// where -// K: api::KeyType, -// { -// todo!(); -// } - -// fn iter_option( -// &self, -// _first_key: Option, -// ) -> Result, api::Error> { -// todo!() -// } -// } - /// Database configuration. #[derive(Clone, TypedBuilder, Debug)] pub struct DbConfig { @@ -238,7 +132,7 @@ where } async fn propose<'p, K: KeyType, V: ValueType>( - &'p mut self, + &'p self, batch: api::Batch, ) -> Result>, api::Error> where @@ -293,17 +187,17 @@ impl Db { Ok(db) } - /// Create a proposal. - // pub fn new_proposal( - // &self, - // _data: Batch, - // ) -> Result, DbError> { - // todo!() - // } - /// Dump the Trie of the latest revision. - pub fn dump(&self, _w: &mut dyn Write) -> Result<(), DbError> { - todo!() + pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { + let latest_rev_nodestore = self + .manager + .read() + .expect("poisoned lock") + .current_revision(); + let merkle = Merkle::from(latest_rev_nodestore); + // TODO: This should be a stream + let output = merkle.dump().map_err(DbError::Merkle)?; + write!(w, "{}", output).map_err(DbError::IO) } pub fn metrics(&self) -> Arc { @@ -322,18 +216,17 @@ impl<'a> api::DbView for Proposal<'a> { type Stream<'b> = MerkleKeyValueStream<'b, NodeStore, FileBacked>> where Self: 'b; async fn root_hash(&self) -> Result, api::Error> { - todo!() + self.nodestore.root_hash().map_err(api::Error::from) } - async fn val(&self, _key: K) -> Result>, api::Error> { - todo!() + async fn val(&self, key: K) -> Result>, api::Error> { + let merkle = Merkle::from(self.nodestore.clone()); + merkle.get_value(key.as_ref()).map_err(api::Error::from) } - async fn single_key_proof( - &self, - _key: K, - ) -> Result>, api::Error> { - todo!() + async fn single_key_proof(&self, key: K) -> Result, api::Error> { + let merkle = Merkle::from(self.nodestore.clone()); + merkle.prove(key.as_ref()).map_err(api::Error::from) } async fn range_proof( @@ -359,9 +252,35 @@ impl<'a> api::Proposal for Proposal<'a> { async fn propose( self: Arc, - _data: api::Batch, + batch: api::Batch, ) -> Result, api::Error> { - todo!() + let parent = self.nodestore.clone(); + let proposal = NodeStore::new(parent)?; + let mut merkle = Merkle::from(proposal); + for op in batch { + match op { + BatchOp::Put { key, value } => { + merkle.insert(key.as_ref(), value.as_ref().into())?; + } + BatchOp::Delete { key } => { + merkle.remove(key.as_ref())?; + } + } + } + let nodestore = merkle.into_inner(); + let immutable: Arc, FileBacked>> = + Arc::new(nodestore.into()); + self.db + .manager + .write() + .expect("poisoned lock") + .add_proposal(immutable.clone()); + + Ok(Self::Proposal { + nodestore: immutable, + db: self.db, + } + .into()) } async fn commit(self: Arc) -> Result<(), api::Error> { @@ -391,7 +310,7 @@ mod test { #[tokio::test] async fn test_cloned_proposal_error() { - let mut db = testdb().await; + let db = testdb().await; let proposal = db .propose::, Vec>(Default::default()) .await diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 9daadede63a6..958b3a6fa254 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -42,7 +42,6 @@ pub(crate) struct RevisionManager { proposals: Vec, // committing_proposals: VecDeque>, by_hash: HashMap, - // TODO: maintain root hash of the most recent commit } #[derive(Debug, thiserror::Error)] @@ -148,8 +147,6 @@ impl RevisionManager { } } -pub type NewProposalError = (); // TODO implement - impl RevisionManager { pub fn add_proposal(&mut self, proposal: ProposedRevision) { self.proposals.push(proposal); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 95f7c2f88e4e..7f3980d6df11 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -96,19 +96,27 @@ pub enum Error { #[error("request RangeProof for empty trie")] RangeProofOnEmptyTrie, -} -impl From for Error { - fn from(err: MerkleError) -> Self { - // TODO: do a better job - Error::InternalError(Box::new(err)) - } + #[error("the latest revision is empty and has no root hash")] + LatestIsEmpty, + + #[error("commit the parents of this proposal first")] + NotLatest, + + #[error("sibling already committed")] + SiblingCommitted, + + #[error("merkle error: {0}")] + Merkle(#[from] MerkleError), } impl From for Error { fn from(err: RevisionManagerError) -> Self { - // TODO: do a better job - Error::InternalError(Box::new(err)) + match err { + RevisionManagerError::IO(io_err) => Error::IO(io_err), + RevisionManagerError::NotLatest => Error::NotLatest, + RevisionManagerError::SiblingCommitted => Error::SiblingCommitted, + } } } @@ -144,7 +152,7 @@ pub trait Db { /// [BatchOp::Delete] operations to apply /// async fn propose<'p, K: KeyType, V: ValueType>( - &'p mut self, + &'p self, data: Batch, ) -> Result>, Error> where @@ -173,8 +181,7 @@ pub trait DbView { async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key - async fn single_key_proof(&self, key: K) - -> Result>, Error>; + async fn single_key_proof(&self, key: K) -> Result, Error>; /// Obtain a range proof over a set of keys /// diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 2dc95203b28f..f3ff0c77e08a 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -40,7 +40,7 @@ impl Db for EmptyDb { } async fn propose<'p, K, V>( - &'p mut self, + &'p self, data: Batch, ) -> Result>, Error> where @@ -66,11 +66,8 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn single_key_proof( - &self, - _key: K, - ) -> Result>, Error> { - Ok(None) + async fn single_key_proof(&self, _key: K) -> Result, Error> { + Err(Error::RangeProofOnEmptyTrie) } async fn range_proof( @@ -108,7 +105,7 @@ mod tests { #[tokio::test] async fn basic_proposal() -> Result<(), Error> { - let mut db = EmptyDb; + let db = EmptyDb; let batch = vec![ BatchOp::Put { @@ -132,7 +129,7 @@ mod tests { #[tokio::test] async fn nested_proposal() -> Result<(), Error> { - let mut db = EmptyDb; + let db = EmptyDb; // create proposal1 which adds key "k" with value "v" and deletes "z" let batch = vec![ BatchOp::Put { @@ -164,10 +161,12 @@ mod tests { ); // create a proposal3 by adding the two proposals together, keeping the originals - // TODO: consider making this possible again - // let proposal3 = proposal1.as_ref() + proposal2.as_ref(); - // assert_eq!(proposal3.val(b"k").await.unwrap().unwrap(), b"v"); - // assert_eq!(proposal3.val(b"z").await.unwrap().unwrap(), b"undo"); + let proposal3 = proposal1.as_ref() + proposal2.as_ref(); + assert_eq!(proposal3.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); + assert_eq!( + proposal3.val(b"z").await.unwrap().unwrap().to_vec(), + b"undo" + ); // now consume proposal1 and proposal2 proposal2.commit().await?; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index e86bfdaf67ee..4fa6ee490029 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -126,10 +126,7 @@ impl api::DbView for Proposal { } } - async fn single_key_proof( - &self, - _key: K, - ) -> Result>, api::Error> { + async fn single_key_proof(&self, _key: K) -> Result, api::Error> { todo!(); } diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs index 7168c6feedf4..46ff809e8a16 100644 --- a/grpc-testtool/benches/insert.rs +++ b/grpc-testtool/benches/insert.rs @@ -20,7 +20,7 @@ const TESTDIR: &str = "/tmp/benchdb"; /// The port to use for testing const TESTPORT: u16 = 5000; /// The URI to connect to; this better match the TESTPORT -const TESTURI: &str = "http://localhost:5000"; +const TESTURI: &str = "http://127.0.0.1:5000"; /// Retry timeouts (in seconds); we want this long for processes /// to start and exit const RETRY_TIMEOUT_SEC: u32 = 5; diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index 06809f9ad2d6..163cb006c4ac 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use firewood::db::{Db, DbConfig}; +use firewood::v2::api::Db as _; use firewood::v2::api::Error; use std::path::Path; @@ -20,11 +21,11 @@ pub mod database; pub mod db; pub mod process; -trait _IntoStatusResultExt { +trait IntoStatusResultExt { fn into_status_result(self) -> Result; } -impl _IntoStatusResultExt for Result { +impl IntoStatusResultExt for Result { // We map errors from bad arguments into Status::invalid_argument; all other errors are Status::internal errors fn into_status_result(self) -> Result { self.map_err(|err| match err { @@ -70,12 +71,12 @@ impl Deref for Database { } } -// impl Database { -// async fn latest(&self) -> Result::Historical>, Error> { -// let root_hash = self.root_hash().await?; -// self.revision(root_hash).await -// } -// } +impl Database { + async fn latest(&self) -> Result::Historical>, Error> { + let root_hash = self.root_hash().await?.ok_or(Error::LatestIsEmpty)?; + self.revision(root_hash).await + } +} // TODO: implement Iterator #[derive(Debug)] diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index e3857c5a8c9b..06e9e782721c 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{Database as DatabaseService, Iter}; +use super::{Database as DatabaseService, IntoStatusResultExt as _, Iter}; use crate::rpcdb::{ database_server::Database, CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, GetRequest, GetResponse, HasRequest, HasResponse, @@ -10,71 +10,67 @@ use crate::rpcdb::{ NewIteratorWithStartAndPrefixRequest, NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, WriteBatchResponse, }; -use firewood::v2::api::BatchOp; +use firewood::v2::api::{BatchOp, Db as _, DbView as _, Proposal as _}; use tonic::{async_trait, Request, Response, Status}; #[async_trait] impl Database for DatabaseService { - async fn has(&self, _request: Request) -> Result, Status> { - todo!() - // let key = request.into_inner().key; - // let revision = self.latest().await.into_status_result()?; + async fn has(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.latest().await.into_status_result()?; - // let val = revision.val(key).await.into_status_result()?; + let val = revision.val(key).await.into_status_result()?; - // let response = HasResponse { - // has: val.is_some(), - // ..Default::default() - // }; + let response = HasResponse { + has: val.is_some(), + ..Default::default() + }; - // Ok(Response::new(response)) + Ok(Response::new(response)) } - async fn get(&self, _request: Request) -> Result, Status> { - todo!() - // let key = request.into_inner().key; - // let revision = self.latest().await.into_status_result()?; + async fn get(&self, request: Request) -> Result, Status> { + let key = request.into_inner().key; + let revision = self.latest().await.into_status_result()?; - // let value = revision - // .val(key) - // .await - // .into_status_result()? - // .map(|v| v.to_vec()); + let value = revision + .val(key) + .await + .into_status_result()? + .map(|v| v.to_vec()); - // let Some(value) = value else { - // return Err(Status::not_found("key not found")); - // }; + let Some(value) = value else { + return Err(Status::not_found("key not found")); + }; - // let response = GetResponse { - // value, - // ..Default::default() - // }; + let response = GetResponse { + value, + ..Default::default() + }; - // Ok(Response::new(response)) + Ok(Response::new(response)) } - async fn put(&self, _request: Request) -> Result, Status> { - todo!() - // let PutRequest { key, value } = request.into_inner(); - // let batch = BatchOp::Put { key, value }; - // let proposal = self.db.propose(vec![batch]).await.into_status_result()?; - // let _ = proposal.commit().await.into_status_result()?; + async fn put(&self, request: Request) -> Result, Status> { + let PutRequest { key, value } = request.into_inner(); + let batch = BatchOp::Put { key, value }; + let proposal = self.db.propose(vec![batch]).await.into_status_result()?; + let _ = proposal.commit().await.into_status_result()?; - // Ok(Response::new(PutResponse::default())) + Ok(Response::new(PutResponse::default())) } async fn delete( &self, - _request: Request, + request: Request, ) -> Result, Status> { - todo!() - // let DeleteRequest { key } = request.into_inner(); - // let batch = BatchOp::<_, Vec>::Delete { key }; - // let proposal = self.db.propose(vec![batch]).await.into_status_result()?; - // let _ = proposal.commit().await.into_status_result()?; + let DeleteRequest { key } = request.into_inner(); + let batch = BatchOp::<_, Vec>::Delete { key }; + let proposal = self.db.propose(vec![batch]).await.into_status_result()?; + let _ = proposal.commit().await.into_status_result()?; - // Ok(Response::new(DeleteResponse::default())) + Ok(Response::new(DeleteResponse::default())) } async fn compact( @@ -101,19 +97,18 @@ impl Database for DatabaseService { async fn write_batch( &self, - _request: Request, + request: Request, ) -> Result, Status> { - todo!() - // let WriteBatchRequest { puts, deletes } = request.into_inner(); - // let batch = puts - // .into_iter() - // .map(from_put_request) - // .chain(deletes.into_iter().map(from_delete_request)) - // .collect(); - // let proposal = self.db.propose(batch).await.into_status_result()?; - // let _ = proposal.commit().await.into_status_result()?; - - // Ok(Response::new(WriteBatchResponse::default())) + let WriteBatchRequest { puts, deletes } = request.into_inner(); + let batch = puts + .into_iter() + .map(from_put_request) + .chain(deletes.into_iter().map(from_delete_request)) + .collect(); + let proposal = self.db.propose(batch).await.into_status_result()?; + let _ = proposal.commit().await.into_status_result()?; + + Ok(Response::new(WriteBatchResponse::default())) } async fn new_iterator_with_start_and_prefix( @@ -163,13 +158,13 @@ impl Database for DatabaseService { } } -fn _from_put_request(request: PutRequest) -> BatchOp, Vec> { +fn from_put_request(request: PutRequest) -> BatchOp, Vec> { BatchOp::Put { key: request.key, value: request.value, } } -fn _from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { +fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { BatchOp::Delete { key: request.key } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 6eee06c20e84..9915430087c5 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -50,6 +50,7 @@ use std::io::{Error, ErrorKind, Write}; use std::iter::once; use std::mem::offset_of; use std::num::NonZeroU64; +use std::ops::Deref; use std::sync::Arc; use crate::hashednode::hash_node; @@ -244,7 +245,7 @@ pub trait Parentable { impl Parentable for Arc { fn as_nodestore_parent(&self) -> NodeStoreParent { - NodeStoreParent::Proposed(self.clone()) + NodeStoreParent::Proposed(Arc::clone(self)) } fn root_hash(&self) -> Option { self.root_hash.clone() @@ -587,29 +588,46 @@ pub trait HashedNodeReader: TrieReader { } } +impl HashedNodeReader for T +where + T: Deref, + T::Target: HashedNodeReader, +{ + fn root_address_and_hash(&self) -> Result, Error> { + self.deref().root_address_and_hash() + } +} + /// Reads nodes and the root address from a merkle trie. pub trait TrieReader: NodeReader + RootReader {} +impl TrieReader for T where T: NodeReader + RootReader {} + +/// Reads nodes from a merkle trie. +pub trait NodeReader { + /// Returns the node at `addr`. + fn read_node(&self, addr: LinearAddress) -> Result, Error>; +} -impl TrieReader for &NodeStore {} -impl NodeReader for &NodeStore { +impl NodeReader for T +where + T: Deref, + T::Target: NodeReader, +{ fn read_node(&self, addr: LinearAddress) -> Result, Error> { - self.read_node_from_disk(addr) + self.deref().read_node(addr) } } -impl RootReader for &NodeStore { + +impl RootReader for T +where + T: Deref, + T::Target: RootReader, +{ fn root_node(&self) -> Option> { - self.header - .root_address - .map(|addr| self.read_node_from_disk(addr).unwrap()) + self.deref().root_node() } } -/// Reads nodes from a merkle trie. -pub trait NodeReader { - /// Returns the node at `addr`. - fn read_node(&self, addr: LinearAddress) -> Result, Error>; -} - /// Reads the root of a merkle trie. pub trait RootReader { /// Returns the root of the trie. @@ -676,7 +694,7 @@ impl ImmutableProposal { } } -impl ReadInMemoryNode for Arc { +impl ReadInMemoryNode for ImmutableProposal { fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { // Check if the node being requested was created in this proposal. if let Some((_, node)) = self.new.get(&addr) { @@ -703,6 +721,16 @@ pub trait ReadInMemoryNode { fn read_in_memory_node(&self, addr: LinearAddress) -> Option>; } +impl ReadInMemoryNode for T +where + T: Deref, + T::Target: ReadInMemoryNode, +{ + fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + self.deref().read_in_memory_node(addr) + } +} + /// Contains the state of a revision of a merkle trie. /// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. /// The second generic parameter is the type of the storage used, either @@ -1025,11 +1053,6 @@ impl RootReader for NodeStore< } } -impl TrieReader for NodeStore where - NodeStore: RootReader -{ -} - impl HashedNodeReader for NodeStore where NodeStore: TrieReader, From 34b28e61d2edd3a3fe69169f2f5cc1a2f33d4339 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 27 Aug 2024 10:30:09 -1000 Subject: [PATCH 0547/1053] Implement reopen (#713) --- firewood/src/db.rs | 71 ++++++++++++++++++++++++++++++++++++---- firewood/src/manager.rs | 15 +++++++-- storage/src/nodestore.rs | 17 +++++++--- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 23903b96f52b..ac6626f54ec2 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -303,10 +303,10 @@ mod test { use crate::{ db::Db, - v2::api::{Db as _, Error, Proposal}, + v2::api::{Db as _, DbView as _, Error, Proposal as _}, }; - use super::DbConfig; + use super::{BatchOp, DbConfig}; #[tokio::test] async fn test_cloned_proposal_error() { @@ -330,10 +330,53 @@ mod test { assert!(matches!(result, Ok(())), "{result:?}"); } + #[tokio::test] + async fn test_proposal_reads() { + let db = testdb().await; + let batch = vec![BatchOp::Put { + key: b"k", + value: b"v", + }]; + let proposal = db.propose(batch).await.unwrap(); + assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v"); + + assert_eq!(proposal.val(b"notfound").await.unwrap(), None); + proposal.commit().await.unwrap(); + + let batch = vec![BatchOp::Put { + key: b"k", + value: b"v2", + }]; + let proposal = db.propose(batch).await.unwrap(); + assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v2"); + + let committed = db.root_hash().await.unwrap().unwrap(); + let historical = db.revision(committed).await.unwrap(); + assert_eq!(&*historical.val(b"k").await.unwrap().unwrap(), b"v"); + } + + #[tokio::test] + async fn reopen_test() { + let db = testdb().await; + let batch = vec![BatchOp::Put { + key: b"k", + value: b"v", + }]; + let proposal = db.propose(batch).await.unwrap(); + proposal.commit().await.unwrap(); + println!("{:?}", db.root_hash().await.unwrap().unwrap()); + + let db = db.reopen().await; + println!("{:?}", db.root_hash().await.unwrap().unwrap()); + let committed = db.root_hash().await.unwrap().unwrap(); + let historical = db.revision(committed).await.unwrap(); + assert_eq!(&*historical.val(b"k").await.unwrap().unwrap(), b"v"); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, - _tmpdir: tempfile::TempDir, + tmpdir: tempfile::TempDir, } impl Deref for TestDb { type Target = Db; @@ -354,9 +397,25 @@ mod test { .collect(); let dbconfig = DbConfig::builder().truncate(true).build(); let db = Db::new(dbpath, dbconfig).await.unwrap(); - TestDb { - db, - _tmpdir: tmpdir, + TestDb { db, tmpdir } + } + + impl TestDb { + fn path(&self) -> PathBuf { + [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect() + } + async fn reopen(self) -> Self { + let path = self.path(); + drop(self.db); + let dbconfig = DbConfig::builder().truncate(false).build(); + + let db = Db::new(path, dbconfig).await.unwrap(); + TestDb { + db, + tmpdir: self.tmpdir, + } } } } diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 958b3a6fa254..31708b881f4a 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -63,15 +63,24 @@ impl RevisionManager { config: RevisionManagerConfig, ) -> Result { let storage = Arc::new(FileBacked::new(filename, config.node_cache_size, truncate)?); - let nodestore = Arc::new(NodeStore::new_empty_committed(storage.clone())?); - let manager = Self { + let nodestore = match truncate { + true => Arc::new(NodeStore::new_empty_committed(storage.clone())?), + false => Arc::new(NodeStore::open(storage.clone())?), + }; + let mut manager = Self { max_revisions: config.max_revisions, filebacked: storage, - historical: VecDeque::from([nodestore]), + historical: VecDeque::from([nodestore.clone()]), by_hash: Default::default(), proposals: Default::default(), // committing_proposals: Default::default(), }; + if nodestore.kind.root_hash().is_some() { + manager.by_hash.insert( + nodestore.kind.root_hash().expect("root hash is present"), + nodestore.clone(), + ); + } Ok(manager) } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 9915430087c5..0300048cbf63 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -196,14 +196,22 @@ impl NodeStore { drop(stream); - Ok(Self { + let mut nodestore = Self { header, kind: Committed { deleted: Default::default(), - root_hash: None, // TODO: get the root hash + root_hash: None, }, storage, - }) + }; + + if let Some(root_address) = nodestore.header.root_address { + let node = nodestore.read_node_from_disk(root_address); + let root_hash = node.map(|n| hash_node(&n, &Path(Default::default())))?; + nodestore.kind.root_hash = Some(root_hash); + } + + Ok(nodestore) } /// Create a new, empty, Committed [NodeStore] and clobber @@ -1047,9 +1055,10 @@ impl Hashed for Arc {} impl RootReader for NodeStore { fn root_node(&self) -> Option> { + // TODO: If the read_node fails, we just say there is no root; this is incorrect self.header .root_address - .and_then(|addr| self.kind.read_in_memory_node(addr)) + .and_then(|addr| self.read_node(addr).ok()) } } From 02ac49b45b03e6ee8a0f1f80a009121a9648c233 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 12:19:17 -1000 Subject: [PATCH 0548/1053] Reap deleted nodes (#714) --- firewood/Cargo.toml | 2 + firewood/examples/insert.rs | 21 ++++++-- firewood/src/manager.rs | 31 +++++++---- storage/src/linear/filebacked.rs | 9 +++- storage/src/linear/mod.rs | 3 ++ storage/src/nodestore.rs | 89 +++++++++++++------------------- 6 files changed, 87 insertions(+), 68 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 86c16738ec13..884d61e4d55a 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -31,11 +31,13 @@ bincode = "1.3.3" log = { version = "0.4.20", optional = true } test-case = "3.3.1" integer-encoding = "4.0.0" +io-uring = {version = "0.6", optional = true } [features] default = [] logger = ["log"] nightly = [] +iouring = ["io-uring"] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index f9f32f2f9618..5f1b00f32469 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -6,11 +6,13 @@ use clap::Parser; use std::{ - borrow::BorrowMut as _, collections::HashMap, error::Error, ops::RangeInclusive, time::Instant, + borrow::BorrowMut as _, collections::HashMap, error::Error, num::NonZeroUsize, + ops::RangeInclusive, time::Instant, }; use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, + manager::RevisionManagerConfig, v2::api::{Db as _, DbView, Proposal as _}, }; use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; @@ -29,6 +31,12 @@ struct Args { read_verify_percent: u16, #[arg(short, long)] seed: Option, + #[arg(short, long, default_value_t = NonZeroUsize::new(20480).expect("is non-zero"))] + cache_size: NonZeroUsize, + #[arg(short, long, default_value_t = true)] + truncate: bool, + #[arg(short, long, default_value_t = 128)] + revisions: usize, } fn string_to_range(input: &str) -> Result, Box> { @@ -45,10 +53,17 @@ fn string_to_range(input: &str) -> Result, Box Result<(), Box> { - let cfg = DbConfig::builder().truncate(true).build(); - let args = Args::parse(); + let mgrcfg = RevisionManagerConfig::builder() + .node_cache_size(args.cache_size) + .max_revisions(args.revisions) + .build(); + let cfg = DbConfig::builder() + .truncate(args.truncate) + .manager(mgrcfg) + .build(); + let db = Db::new("rev_db", cfg) .await .expect("db initiation should succeed"); diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 31708b881f4a..5c96d0471f39 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -18,10 +18,10 @@ use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, T #[derive(Clone, Debug, TypedBuilder)] pub struct RevisionManagerConfig { /// The number of historical revisions to keep in memory. - #[builder(default = 64)] + #[builder(default = 128)] max_revisions: usize, - #[builder(default_code = "NonZero::new(1024).expect(\"non-zero\")")] + #[builder(default_code = "NonZero::new(20480).expect(\"non-zero\")")] node_cache_size: NonZero, } @@ -120,18 +120,34 @@ impl RevisionManager { { return Err(RevisionManagerError::NotLatest); } - // 2. Persist delete list - // TODO + + let mut committed = proposal.as_committed(); + + // 2. Persist delete list for this committed revision to disk for recovery + + // 2.5 Take the deleted entries from the oldest revision and mark them as free for this revision + // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. + // TODO: Handle the case where we get something off the free list that is not free + if self.historical.len() > self.max_revisions { + let oldest = self.historical.pop_front().expect("must be present"); + if let Some(oldest_hash) = oldest.kind.root_hash() { + self.by_hash.remove(&oldest_hash); + } + + if let Some(oldest) = Arc::into_inner(oldest) { + committed.reap_deleted(&oldest)?; + } + } // 3. Set last committed revision - let committed: CommittedRevision = proposal.as_committed().into(); + let committed: CommittedRevision = committed.into(); self.historical.push_back(committed.clone()); if let Some(hash) = committed.kind.root_hash() { self.by_hash.insert(hash, committed.clone()); } // TODO: We could allow other commits to start here using the pending list - // 4. Free list flush + // 4. Free list flush, which will prevent allocating on top of the nodes we are about to write proposal.flush_freelist()?; // 5. Node flush @@ -149,9 +165,6 @@ impl RevisionManager { proposal.commit_reparent(p); } - // 8. Revision reaping - // TODO - Ok(()) } } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 93815e367f4f..75804b126b88 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -71,8 +71,6 @@ impl ReadableStorage for FileBacked { } impl WritableStorage for FileBacked { - /// Write to the backend filestore. This does not implement [crate::WritableStorage] - /// because we don't want someone accidentally writing nodes directly to disk fn write(&self, offset: u64, object: &[u8]) -> Result { self.fd .lock() @@ -90,4 +88,11 @@ impl WritableStorage for FileBacked { } Ok(()) } + + fn invalidate_cached_nodes<'a>(&self, addresses: impl Iterator) { + let mut guard = self.cache.lock().expect("poisoned lock"); + for addr in addresses { + guard.pop(addr); + } + } } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 5799c633e839..866cecd3f4fb 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -70,4 +70,7 @@ pub trait WritableStorage: ReadableStorage { ) -> Result<(), Error> { Ok(()) } + + /// Invalidate all nodes that are part of a specific revision, as these will never be referenced again + fn invalidate_cached_nodes<'a>(&self, _addresses: impl Iterator) {} } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 0300048cbf63..27413cde01a3 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -436,68 +436,37 @@ impl NodeStore, S> { Ok((addr, index)) } +} - // TODO danlaine: Use this code inside the revision management code or delete it. - // The inner implementation of `create_node` that doesn't update the free lists. - // fn create_node_inner(&mut self, node: Node) -> Result { - // let (addr, index) = self.allocate_node(&node)?; - - // let stored_area = StoredArea { - // area_size_index: index, - // area: Area::<_, FreeArea>::Node(node), - // }; - - // let stored_area_bytes = - // bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - // self.storage - // .write(addr.get(), stored_area_bytes.as_slice())?; - - // Ok(addr) - // } - - // TODO danlaine: use this code in the revision management code or delete it. - // Update a node in-place. This should only be used when the node was allocated using - // allocate_node. - // TODO: We should enforce this by having a new type for allocated nodes, which could - // carry the size information too - // pub fn update_in_place(&mut self, addr: LinearAddress, node: &Node) -> Result<(), Error> { - // let new_area: Area<&Node, FreeArea> = Area::Node(node); - // let new_area_bytes = - // bincode::serialize(&new_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - // let addr = addr.get() + 1; // Skip the index byte - // self.storage.write(addr, new_area_bytes.as_slice())?; - // Ok(()) - // } - - // TODO danlaine: use this code in the revision management code. - // Deletes the [Node] at the given address. - // pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), Error> { - // debug_assert!(addr.get() % 8 == 0); +impl NodeStore { + /// Deletes the [Node] at the given address, updating the next pointer at + /// the given addr, and changing the header of this committed nodestore to + /// have the address on the freelist + pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), Error> { + debug_assert!(addr.get() % 8 == 0); - // let (area_size_index, _) = self.area_index_and_size(addr)?; + let (area_size_index, _) = self.area_index_and_size(addr)?; - // // The area that contained the node is now free. - // let area: Area = Area::Free(FreeArea { - // next_free_block: self.header.free_lists[area_size_index as usize], - // }); + // The area that contained the node is now free. + let area: Area = Area::Free(FreeArea { + next_free_block: self.header.free_lists[area_size_index as usize], + }); - // let stored_area = StoredArea { - // area_size_index, - // area, - // }; + let stored_area = StoredArea { + area_size_index, + area, + }; - // let stored_area_bytes = - // bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let stored_area_bytes = + bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - // self.storage.write(addr.into(), &stored_area_bytes)?; + self.storage.write(addr.into(), &stored_area_bytes)?; - // // The newly freed block is now the head of the free list. - // self.header.free_lists[area_size_index as usize] = Some(addr); + // The newly freed block is now the head of the free list. + self.header.free_lists[area_size_index as usize] = Some(addr); - // Ok(()) - // } + Ok(()) + } } /// An error from doing an update @@ -1077,6 +1046,18 @@ where } } +impl NodeStore { + /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal + pub fn reap_deleted(&mut self, oldest: &NodeStore) -> Result<(), Error> { + self.storage + .invalidate_cached_nodes(oldest.kind.deleted.iter()); + for addr in oldest.kind.deleted.iter() { + self.delete_node(*addr)?; + } + Ok(()) + } +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { From 65de7d3a5946e0ba6410a0eed4608b706fee2c6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 18:53:46 -1000 Subject: [PATCH 0549/1053] build(deps): bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#715) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gh-pages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index 416db804d596..2c00c1f3810e 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download pages artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: name: pages path: . From 4bbb4da4d0c5680b02e2b90b2da83854478aedd8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 5 Sep 2024 07:29:51 -1000 Subject: [PATCH 0550/1053] Re-enable fwdctl (#716) --- fwdctl/src/create.rs | 6 ++--- fwdctl/src/delete.rs | 26 ++++++++++++---------- fwdctl/src/dump.rs | 45 ++++++++++++++++++++++---------------- fwdctl/src/get.rs | 52 ++++++++++++++++++++++++++------------------ fwdctl/src/insert.rs | 28 +++++++++++++----------- fwdctl/src/main.rs | 11 +++++----- fwdctl/src/root.rs | 19 +++++++++------- fwdctl/tests/cli.rs | 3 ++- 8 files changed, 106 insertions(+), 84 deletions(-) diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 366c7da36622..d901c8bef90c 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -2,10 +2,8 @@ // See the file LICENSE.md for licensing terms. use clap::{value_parser, Args}; -use firewood::{ - db::{Db, DbConfig}, - v2::api, -}; +use firewood::db::{Db, DbConfig}; +use firewood::v2::api; #[derive(Args)] pub struct Options { diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 8e8d95ff7360..190ce4cb5572 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -2,6 +2,8 @@ // See the file LICENSE.md for licensing terms. use clap::Args; +use firewood::db::{BatchOp, Db, DbConfig}; +use firewood::v2::api::{self, Db as _, Proposal as _}; #[derive(Debug, Args)] pub struct Options { @@ -20,18 +22,18 @@ pub struct Options { pub db: String, } -// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { -// log::debug!("deleting key {:?}", opts); -// let cfg = DbConfig::builder().truncate(false); +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + log::debug!("deleting key {:?}", opts); + let cfg = DbConfig::builder().truncate(false); -// let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.db.clone(), cfg.build()).await?; -// let batch: Vec> = vec![BatchOp::Delete { -// key: opts.key.clone(), -// }]; -// let proposal = db.propose(batch).await?; -// proposal.commit().await?; + let batch: Vec> = vec![BatchOp::Delete { + key: opts.key.clone(), + }]; + let proposal = db.propose(batch).await?; + proposal.commit().await?; -// println!("key {} deleted successfully", opts.key); -// Ok(()) -// } + println!("key {} deleted successfully", opts.key); + Ok(()) +} diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 2d4ce162387a..0c7d5738761b 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -2,7 +2,9 @@ // See the file LICENSE.md for licensing terms. use clap::Args; +use firewood::db::{Db, DbConfig}; use firewood::merkle::Key; +use firewood::v2::api::{self, Db as _}; use std::borrow::Cow; #[derive(Debug, Args)] @@ -27,26 +29,31 @@ pub struct Options { pub start_key: Option, } -// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { -// log::debug!("dump database {:?}", opts); -// let cfg = DbConfig::builder().truncate(false); +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + log::debug!("dump database {:?}", opts); + let cfg = DbConfig::builder().truncate(false); -// let db = Db::new(opts.db.clone(), cfg.build()).await?; -// let latest_hash = db.root_hash().await?; -// let latest_rev = db.revision(latest_hash).await?; -// let start_key = opts.start_key.clone().unwrap_or(Box::new([])); -// let mut stream = latest_rev.stream_from(&start_key); -// loop { -// match stream.next().await { -// None => break, -// Some(Ok((key, value))) => { -// println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); -// } -// Some(Err(e)) => return Err(e), -// } -// } -// Ok(()) -// } + let db = Db::new(opts.db.clone(), cfg.build()).await?; + let latest_hash = db.root_hash().await?; + let Some(_latest_hash) = latest_hash else { + println!("Database is empty"); + return Ok(()); + }; + todo!() + // let latest_rev = db.revision(latest_hash).await?; + // let start_key = opts.start_key.clone().unwrap_or(Box::new([])); + // let mut stream = latest_rev.stream_from(&start_key)?; + // loop { + // match stream.next().await { + // None => break, + // Some(Ok((key, value))) => { + // println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); + // } + // Some(Err(e)) => return Err(e), + // } + // } + // Ok(()) +} fn _u8_to_string(data: &[u8]) -> Cow<'_, str> { String::from_utf8_lossy(data) diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 4f0bf57ede8c..6c2d6afb19de 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -4,6 +4,9 @@ use clap::Args; use std::str; +use firewood::db::{Db, DbConfig}; +use firewood::v2::api::{self, Db as _, DbView as _}; + #[derive(Debug, Args)] pub struct Options { /// The key to get the value for @@ -21,24 +24,31 @@ pub struct Options { pub db: String, } -// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { -// log::debug!("get key value pair {:?}", opts); -// let cfg = DbConfig::builder().truncate(false); - -// let db = Db::new(opts.db.clone(), cfg.build()).await?; - -// let rev = db.revision(db.root_hash().await?).await?; - -// match rev.val(opts.key.as_bytes()).await { -// Ok(Some(val)) => { -// let s = String::from_utf8_lossy(val.as_ref()); -// println!("{s:?}"); -// Ok(()) -// } -// Ok(None) => { -// eprintln!("Key '{}' not found", opts.key); -// Ok(()) -// } -// Err(e) => Err(e), -// } -// } +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + log::debug!("get key value pair {:?}", opts); + let cfg = DbConfig::builder().truncate(false); + + let db = Db::new(opts.db.clone(), cfg.build()).await?; + + let hash = db.root_hash().await?; + + let Some(hash) = hash else { + println!("Database is empty"); + return Ok(()); + }; + + let rev = db.revision(hash).await?; + + match rev.val(opts.key.as_bytes()).await { + Ok(Some(val)) => { + let s = String::from_utf8_lossy(val.as_ref()); + println!("{s:?}"); + Ok(()) + } + Ok(None) => { + eprintln!("Key '{}' not found", opts.key); + Ok(()) + } + Err(e) => Err(e), + } +} diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 77d3bc96072c..969266d39eca 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -2,6 +2,8 @@ // See the file LICENSE.md for licensing terms. use clap::Args; +use firewood::db::{BatchOp, Db, DbConfig}; +use firewood::v2::api::{self, Db as _, Proposal as _}; #[derive(Debug, Args)] pub struct Options { @@ -24,19 +26,19 @@ pub struct Options { pub db: String, } -// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { -// log::debug!("inserting key value pair {:?}", opts); -// let cfg = DbConfig::builder().truncate(false); +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + log::debug!("inserting key value pair {:?}", opts); + let cfg = DbConfig::builder().truncate(false); -// let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.db.clone(), cfg.build()).await?; -// let batch: Vec, Vec>> = vec![BatchOp::Put { -// key: opts.key.clone().into(), -// value: opts.value.bytes().collect(), -// }]; -// let proposal = db.propose(batch).await?; -// proposal.commit().await?; + let batch: Vec, Vec>> = vec![BatchOp::Put { + key: opts.key.clone().into(), + value: opts.value.bytes().collect(), + }]; + let proposal = db.propose(batch).await?; + proposal.commit().await?; -// println!("{}", opts.key); -// Ok(()) -// } + println!("{}", opts.key); + Ok(()) +} diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index b360d05fd13a..fca9d6d873b3 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -57,11 +57,10 @@ async fn main() -> Result<(), api::Error> { match &cli.command { Commands::Create(opts) => create::run(opts).await, - _ => todo!(), - //Commands::Insert(opts) => insert::run(opts).await, - // Commands::Get(opts) => get::run(opts).await, - //Commands::Delete(opts) => delete::run(opts).await, - //Commands::Root(opts) => root::run(opts).await, - //Commands::Dump(opts) => dump::run(opts).await, + Commands::Insert(opts) => insert::run(opts).await, + Commands::Get(opts) => get::run(opts).await, + Commands::Delete(opts) => delete::run(opts).await, + Commands::Root(opts) => root::run(opts).await, + Commands::Dump(opts) => dump::run(opts).await, } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 58af03e90f26..afa6306c5235 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -4,6 +4,9 @@ use clap::Args; use std::str; +use firewood::db::{Db, DbConfig}; +use firewood::v2::api::{self, Db as _}; + #[derive(Debug, Args)] pub struct Options { /// The database path (if no path is provided, return an error). Defaults to firewood. @@ -17,13 +20,13 @@ pub struct Options { pub db: String, } -// pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { -// log::debug!("root hash {:?}", opts); -// let cfg = DbConfig::builder().truncate(false); +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + let cfg = DbConfig::builder().truncate(false); + + let db = Db::new(opts.db.clone(), cfg.build()).await?; -// let db = Db::new(opts.db.clone(), cfg.build()).await?; + let hash = db.root_hash().await?; -// let root = db.root_hash().await?; -// println!("{root:X?}"); -// Ok(()) -// } + println!("{hash:?}"); + Ok(()) +} diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 7223213ad95e..053c7de32b2c 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -5,7 +5,8 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; -use std::{fs::remove_dir_all, path::PathBuf}; +use std::fs::remove_dir_all; +use std::path::PathBuf; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); From 56decb86f19cbd15df8972a5cfa92fd2a76c5499 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 08:56:34 -1000 Subject: [PATCH 0551/1053] Use a boxed slice instead of a vec (#718) --- firewood/src/stream.rs | 6 +++--- firewood/src/v2/propose.rs | 15 +++++++++------ storage/src/nodestore.rs | 2 ++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 40e27bd91e4d..15038b2cd4b0 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -375,6 +375,7 @@ enum PathIteratorState<'a> { } /// Iterates over all nodes on the path to a given key starting from the root. +/// /// All nodes are branch nodes except possibly the last, which may be a leaf. /// All returned nodes have keys which are a prefix of the given key. /// If the given key is in the trie, the last node is at that key. @@ -1262,9 +1263,8 @@ mod tests { let mut merkle = create_test_merkle(); let keys: Vec<_> = children - .map(|key| { - merkle.insert(&key, key.clone().into()).unwrap(); - key + .inspect(|key| { + merkle.insert(key, key.clone().into()).unwrap(); }) .collect(); diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 4fa6ee490029..a03ab5abca52 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -68,7 +68,7 @@ impl Clone for ProposalBase { #[derive(Debug)] pub struct Proposal { pub(crate) base: ProposalBase, - pub(crate) delta: BTreeMap, KeyOp>>, + pub(crate) delta: BTreeMap, KeyOp>>, } // Implement Clone because T doesn't need to be Clone @@ -90,12 +90,15 @@ impl Proposal { let delta = batch .into_iter() .map(|op| match op { - api::BatchOp::Put { key, value } => { - (key.as_ref().to_vec(), KeyOp::Put(value.as_ref().to_vec())) + api::BatchOp::Put { key, value } => ( + key.as_ref().to_vec().into_boxed_slice(), + KeyOp::Put(value.as_ref().to_vec().into_boxed_slice()), + ), + api::BatchOp::Delete { key } => { + (key.as_ref().to_vec().into_boxed_slice(), KeyOp::Delete) } - api::BatchOp::Delete { key } => (key.as_ref().to_vec(), KeyOp::Delete), }) - .collect(); + .collect::>(); Arc::new(Self { base, delta }) } @@ -115,7 +118,7 @@ impl api::DbView for Proposal { match self.delta.get(key.as_ref()) { Some(change) => match change { // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(Some(val.clone().into_boxed_slice())), + KeyOp::Put(val) => Ok(Some(val.clone())), KeyOp::Delete => Ok(None), // key was deleted in this proposal }, None => match &self.base { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 27413cde01a3..327a4f6c6e27 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -240,6 +240,7 @@ impl NodeStore { } /// Some nodestore kinds implement Parentable. +/// /// This means that the nodestore can have children. /// Only [ImmutableProposal] and [Committed] implement this trait. /// [MutableProposal] does not implement this trait because it is not a valid parent. @@ -709,6 +710,7 @@ where } /// Contains the state of a revision of a merkle trie. +/// /// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. /// The second generic parameter is the type of the storage used, either /// in-memory or on-disk. From 734cfe2d57505d934d7aab290c6bfaf809334e88 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 10:06:49 -1000 Subject: [PATCH 0552/1053] Doc update (#717) --- firewood/src/lib.rs | 176 +++++++++++++------------------------------- 1 file changed, 50 insertions(+), 126 deletions(-) diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 845ead1f80f5..a452d13798e1 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -15,20 +15,13 @@ //! a very fast storage layer for the EVM but could be used on any blockchain that //! requires authenticated state. //! -//! Firewood only attempts to store the latest state on-disk and will actively clean up -//! unused state when state diffs are committed. To avoid reference counting trie nodes, -//! Firewood does not copy-on-write (COW) the state trie and instead keeps -//! one latest version of the trie index on disk and applies in-place updates to it. -//! Firewood keeps some configurable number of previous states in memory to power -//! state sync (which may occur at a few roots behind the current state). -//! -//! Firewood provides OS-level crash recovery via a write-ahead log (WAL). The WAL -//! guarantees atomicity and durability in the database, but also offers -//! “reversibility”: some portion of the old WAL can be optionally kept around to -//! allow a fast in-memory rollback to recover some past versions of the entire -//! store back in memory. While running the store, new changes will also contribute -//! to the configured window of changes (at batch granularity) to access any past -//! versions with no additional cost at all. +//! Firewood only attempts to store recent revisions on-disk and will actively clean up +//! unused older revisions when state diffs are committed. The number of revisions is +//! configured when the database is opened. +//! +//! Firewood provides OS-level crash recovery, but not machine-level crash recovery. That is, +//! if the firewood process crashes, the OS will flush the cache leave the system in a valid state. +//! No protection is (currently) offered to handle machine failures. //! //! # Design Philosophy & Overview //! @@ -62,124 +55,55 @@ //! benefit from such a design. //! //! In Firewood, we take a closer look at the second regime and have come up with a simple but -//! robust architecture that fulfills the need for such blockchain storage. +//! robust architecture that fulfills the need for such blockchain storage. However, firewood +//! can also efficiently handle the first regime. //! //! ## Storage Model //! -//! Firewood is built by three layers of abstractions that totally decouple the -//! layout/representation of the data on disk from the actual logical data structure it retains: -//! -//! - Linear, memory-like store: the `shale` crate offers a `CachedStore` abstraction for a -//! (64-bit) byte-addressable store that abstracts away the intricate method that actually persists -//! the in-memory data on the secondary storage medium (e.g., hard drive). The implementor of `CachedStore` -//! provides the functions to give the user of `CachedStore` an illusion that the user is operating upon a -//! byte-addressable memory store. It is just a "magical" array of bytes one can view and change -//! that is mirrored to the disk. In reality, the linear store will be chunked into files under a -//! directory, but the user does not have to even know about this. -//! -//! - Persistent item storage stash: `CompactStore` in `shale` defines a pool of typed objects that are -//! persisted on disk but also made accessible in memory transparently. It is built on top of `CachedStore` -//! and defines how "items" of a given type are laid out, allocated and recycled throughout their lifecycles. -//! -//! - Data structure: in Firewood, one trie is maintained by invoking `CompactStore` (see `src/merkle.rs`). -//! The data structure code is totally unaware of how its objects (i.e., nodes) are organized or -//! persisted on disk. It is as if they're just in memory, which makes it much easier to write -//! and maintain the code. -//! -//! Given the abstraction, one can easily realize the fact that the actual data that affect the -//! state of the data structure (trie) is what the linear store (`CachedStore`) keeps track of. That is, -//! a flat but conceptually large byte vector. In other words, given a valid byte vector as the -//! content of the linear store, the higher level data structure can be *uniquely* determined, there -//! is nothing more (except for some auxiliary data that are kept for performance reasons, such as caching) -//! or less than that, like a way to interpret the bytes. This nice property allows us to completely -//! separate the logical data from its physical representation, greatly simplifies the storage -//! management, and allows reusing the code. It is still a very versatile abstraction, as in theory -//! any persistent data could be stored this way -- sometimes you need to swap in a different -//! `CachedStore` implementation, but without having to touch the code for the persisted data structure. -//! -//! ## Page-based Shadowing and Revisions -//! -//! Following the idea that the tries are just a view of a linear byte store, all writes made to the -//! tries inside Firewood will eventually be consolidated into some interval writes to the linear -//! store. The writes may overlap and some frequent writes are even done to the same spot in the -//! store. To reduce the overhead and be friendly to the disk, we partition the entire 64-bit -//! virtual store into pages (yeah it appears to be more and more like an OS) and keep track of the -//! dirty pages in some `CachedStore` instantiation (see `storage::StoreRevMut`). When a -//! `db::Proposal` commits, both the recorded interval writes and the aggregated in-memory -//! dirty pages induced by this write batch are taken out from the linear store. Although they are -//! mathematically equivalent, interval writes are more compact than pages (which are 4K in size, -//! become dirty even if a single byte is touched upon) . So interval writes are fed into the WAL -//! subsystem (supported by growthring). After the WAL record is written (one record per write batch), -//! the dirty pages are then pushed to the on-disk linear store to mirror the change by some -//! asynchronous, out-of-order file writes. See the `BufferCmd::WriteBatch` part of `DiskBuffer::process` -//! for the detailed logic. +//! Firewood is built by layers of abstractions that totally decouple the layout/representation +//! of the data on disk from the actual logical data structure it retains: +//! +//! - The storage module has a [storage::NodeStore] which has a generic parameter identifying +//! the state of the nodestore, and a storage type. +//! +//! There are three states for a nodestore: +//! - [storage::Committed] for revisions that are on disk +//! - [storage::ImmutableProposal] for revisions that are proposals against committed versions +//! - [storage::MutableProposal] for revisions where nodes are still being added. +//! +//! For more information on these node states, see their associated documentation. +//! +//! The storage type is either a file or memory. Memory storage is used for creating temporary +//! merkle tries for proofs as well as testing. Nodes are identified by their offset within the +//! storage medium (a memory array or a disk file). +//! +//! ## Node caching +//! +//! Once committed, nodes never change until they expire for re-use. This means that a node cache +//! can reduce the amount of serialization and deserialization of nodes. The size of the cache, in +//! nodes, is specified when the database is opened. //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! -//! - Traverse the trie, and that induces the access to some nodes. Suppose the nodes are not already in -//! memory, then: -//! -//! - Bring the necessary pages that contain the accessed nodes into the memory and cache them -//! (`storage::CachedStore`). -//! -//! - Make changes to the trie, and that induces the writes to some nodes. The nodes are either -//! already cached in memory (its pages are cached, or its handle `ObjRef` is still in -//! `shale::ObjCache`) or need to be brought into the memory (if that's the case, go back to the -//! second step for it). -//! -//! - Writes to nodes are converted into interval writes to the stagging `StoreRevMut` store that -//! overlays atop `CachedStore`, so all dirty pages during the current write batch will be -//! exactly captured in `StoreRevMut` (see `StoreRevMut::delta`). -//! -//! - Finally: -//! -//! - Abort: when the write batch is dropped without invoking `db::Proposal::commit`, all in-memory -//! changes will be discarded, the dirty pages from `StoreRevMut` will be dropped and the merkle -//! will "revert" back to its original state without actually having to rollback anything. -//! -//! - Commit: otherwise, the write batch is committed, the interval writes (`storage::Ash`) will be bundled -//! into a single WAL record (`storage::AshRecord`) and sent to WAL subsystem, before dirty pages -//! are scheduled to be written to the store files. Also the dirty pages are applied to the -//! underlying `CachedStore`. `StoreRevMut` becomes empty again for further write batches. -//! -//! Parts of the following diagram show this normal flow, the "staging" store (implemented by -//! `StoreRevMut`) concept is a bit similar to the staging area in Git, which enables the handling -//! of (resuming from) write errors, clean abortion of an on-going write batch so the entire store -//! state remains intact, and also reduces unnecessary premature disk writes. Essentially, we -//! copy-on-write pages in the store that are touched upon, without directly mutating the -//! underlying "master" store. The staging store is just a collection of these "shadowing" pages -//! and a reference to the its base (master) so any reads could partially hit those dirty pages -//! and/or fall through to the base, whereas all writes are captured. Finally, when things go well, -//! we "push down" these changes to the base and clear up the staging store. -//! -//!

    -//! -//!

    -//! -//! Thanks to the shadow pages, we can both revive some historical versions of the store and -//! maintain a rolling window of past revisions on-the-fly. The right hand side of the diagram -//! shows previously logged write batch records could be kept even though they are no longer needed -//! for the purpose of crash recovery. The interval writes from a record can be aggregated into -//! pages (see `storage::StoreDelta::new`) and used to reconstruct a "ghost" image of past -//! revision of the linear store (just like how staging store works, except that the ghost store is -//! essentially read-only once constructed). The shadow pages there will function as some -//! "rewinding" changes to patch the necessary locations in the linear store, while the rest of the -//! linear store is very likely untouched by that historical write batch. -//! -//! Then, with the three-layer abstraction we previously talked about, a historical trie could be -//! derived. In fact, because there is no mandatory traversal or scanning in the process, the -//! only cost to revive a historical state from the log is to just playback the records and create -//! those shadow pages. There is very little additional cost because the ghost store is summoned on an -//! on-demand manner while one accesses the historical trie. -//! -//! In the other direction, when new write batches are committed, the system moves forward, we can -//! therefore maintain a rolling window of past revisions in memory with *zero* cost. The -//! mid-bottom of the diagram shows when a write batch is committed, the persisted (master) store goes one -//! step forward, the staging store is cleared, and an extra ghost store (colored in purple) can be -//! created to hold the version of the store before the commit. The backward delta is applied to -//! counteract the change that has been made to the persisted store, which is also a set of shadow pages. -//! No change is required for other historical ghost store instances. Finally, we can phase out -//! some very old ghost store to keep the size of the rolling window invariant. +//! - Create a [storage::MutableProposal] [storage::NodeStore] from the most recent [storage::Committed] one. +//! - Traverse the trie, starting at the root. Make a new root node by duplicating the existing +//! root from the committed one and save that in memory. As you continue traversing, make copies +//! of each node accessed if they are not already in memory. +//! +//! - Make changes to the trie, in memory. Each node you've accessed is currently in memory and is +//! owned by the [storage::MutableProposal]. Adding a node simply means adding a reference to it. +//! +//! - If you delete a node, mark it as deleted in the proposal and remove the child reference to it. +//! +//! - After making all mutations, convert the [storage::MutableProposal] to an [storage::ImmutableProposal]. This +//! involves walking the in-memory trie and looking for nodes without disk addresses, then assigning +//! them from the freelist of the parent. This gives the node an address, but it is stil in +//! memory. +//! +//! - Since the root is guaranteed to be new, the new root will reference all of the new revision. +//! +//! A commit involves simply writing the nodes and the freelist to disk. If the proposal is +//! abandoned, nothing has actually been written to disk. //! pub mod db; pub mod manager; From 78407976d05d01ded571970415165e022c993d9b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 16:57:51 -1000 Subject: [PATCH 0553/1053] Prometheus spike --- firewood/Cargo.toml | 1 + firewood/examples/benchmark.rs | 155 +++++++++++++++++++++++++++++++++ firewood/src/db.rs | 5 +- 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 firewood/examples/benchmark.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 884d61e4d55a..8f6d6ef7ee87 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,6 +22,7 @@ storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metered = "0.9.0" +metrics-exporter-prometheus = "0.9.0" serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs new file mode 100644 index 000000000000..455427c421aa --- /dev/null +++ b/firewood/examples/benchmark.rs @@ -0,0 +1,155 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// The idea behind this benchmark: +// Generate known keys from the SHA256 of the row number, starting at 0 + +use clap::Parser; +use metrics_exporter_prometheus::PrometheusBuilder; +use sha2::{Digest, Sha256}; +use std::{ + borrow::BorrowMut as _, + collections::HashMap, + error::Error, + num::NonZeroUsize, + ops::RangeInclusive, + time::{Duration, Instant}, +}; + +use firewood::{ + db::{Batch, BatchOp, Db, DbConfig}, + manager::RevisionManagerConfig, + v2::api::{Db as _, DbView, Proposal as _}, +}; +use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; + +#[derive(Parser, Debug)] +struct Args { + #[arg(short, long, default_value = "1-64", value_parser = string_to_range)] + keylen: RangeInclusive, + #[arg(short, long, default_value = "32", value_parser = string_to_range)] + valuelen: RangeInclusive, + #[arg(short, long, default_value_t = 1)] + batch_size: usize, + #[arg(short, long, default_value_t = 100)] + number_of_batches: usize, + #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] + read_verify_percent: u16, + #[arg(short, long)] + seed: Option, + #[arg(short, long, default_value_t = NonZeroUsize::new(20480).expect("is non-zero"))] + cache_size: NonZeroUsize, + #[arg(short, long, default_value_t = true)] + truncate: bool, + #[arg(short, long, default_value_t = 128)] + revisions: usize, +} + +fn string_to_range(input: &str) -> Result, Box> { + //::Err> { + let parts: Vec<&str> = input.split('-').collect(); + #[allow(clippy::indexing_slicing)] + match parts.len() { + 1 => Ok(input.parse()?..=input.parse()?), + 2 => Ok(parts[0].parse()?..=parts[1].parse()?), + _ => Err("Too many dashes in input string".into()), + } +} + +/// cargo run --release --example insert +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + + let builder = PrometheusBuilder::new(); + builder + .with_push_gateway("http://localhost:9090", Duration::from_secs(10)) + .expect("cannot setup push gateway") + .install() + .expect("unable in run prometheusbuilder"); + + let mgrcfg = RevisionManagerConfig::builder() + .node_cache_size(args.cache_size) + .max_revisions(args.revisions) + .build(); + let cfg = DbConfig::builder() + .truncate(args.truncate) + .manager(mgrcfg) + .build(); + + let db = Db::new("rev_db", cfg) + .await + .expect("db initiation should succeed"); + + let keys = args.batch_size; + let start = Instant::now(); + + let mut rng = if let Some(seed) = args.seed { + rand::rngs::StdRng::seed_from_u64(seed) + } else { + rand::rngs::StdRng::from_entropy() + }; + + for key in 0..args.number_of_batches { + let valuelen = rng.gen_range(args.valuelen.clone()); + let batch: Batch, Vec> = (0..keys) + .map(|_| { + ( + Sha256::digest(key.to_ne_bytes()).to_vec(), + rng.borrow_mut() + .sample_iter(&Alphanumeric) + .take(valuelen) + .collect::>(), + ) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect(); + + let verify = get_keys_to_verify(&batch, args.read_verify_percent); + + #[allow(clippy::unwrap_used)] + let proposal = db.propose(batch).await.unwrap(); + proposal.commit().await?; + verify_keys(&db, verify).await?; + } + + let duration = start.elapsed(); + println!( + "Generated and inserted {} batches of size {keys} in {duration:?}", + args.number_of_batches + ); + + Ok(()) +} + +fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>> { + if pct == 0 { + HashMap::new() + } else { + batch + .iter() + .filter(|_last_key| rand::thread_rng().gen_range(0..=(100 - pct)) == 0) + .map(|op| { + if let BatchOp::Put { key, value } = op { + (key.clone(), value.clone().into_boxed_slice()) + } else { + unreachable!() + } + }) + .collect() + } +} + +async fn verify_keys( + db: &impl firewood::v2::api::Db, + verify: HashMap, Box<[u8]>>, +) -> Result<(), firewood::v2::api::Error> { + if !verify.is_empty() { + let hash = db.root_hash().await?.expect("root hash should exist"); + let revision = db.revision(hash).await?; + for (key, value) in verify { + assert_eq!(Some(value), revision.val(key).await?); + } + } + Ok(()) +} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ac6626f54ec2..d1102ce5136d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -10,7 +10,7 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; -use metered::metered; +use metered::{metered, HitCount, Throughput}; use std::error::Error; use std::fmt; use std::io::Write; @@ -109,6 +109,7 @@ pub struct Db { manager: RwLock, } +#[metered(registry = DbMetrics, visibility = pub)] #[async_trait] impl api::Db for Db where @@ -131,6 +132,7 @@ where Ok(self.manager.read().expect("poisoned lock").root_hash()?) } + #[measure([HitCount, Throughput])] async fn propose<'p, K: KeyType, V: ValueType>( &'p self, batch: api::Batch, @@ -171,7 +173,6 @@ where } } -#[metered(registry = DbMetrics, visibility = pub)] impl Db { pub async fn new>(db_path: P, cfg: DbConfig) -> Result { let metrics = DbMetrics::default().into(); From 258ffc6a233577e5e168f89fd191acf14b257d9d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 17:24:15 -1000 Subject: [PATCH 0554/1053] Fix key --- firewood/examples/benchmark.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 455427c421aa..27ca925d6eff 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -25,8 +25,6 @@ use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; #[derive(Parser, Debug)] struct Args { - #[arg(short, long, default_value = "1-64", value_parser = string_to_range)] - keylen: RangeInclusive, #[arg(short, long, default_value = "32", value_parser = string_to_range)] valuelen: RangeInclusive, #[arg(short, long, default_value_t = 1)] @@ -93,9 +91,9 @@ async fn main() -> Result<(), Box> { for key in 0..args.number_of_batches { let valuelen = rng.gen_range(args.valuelen.clone()); let batch: Batch, Vec> = (0..keys) - .map(|_| { + .map(|inner_key| { ( - Sha256::digest(key.to_ne_bytes()).to_vec(), + Sha256::digest((key * keys + inner_key).to_ne_bytes()).to_vec(), rng.borrow_mut() .sample_iter(&Alphanumeric) .take(valuelen) From 2554cbca0e7a142a1e13a561300f840aa47fce23 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 17:57:10 -1000 Subject: [PATCH 0555/1053] WIP --- firewood/Cargo.toml | 2 +- firewood/src/db.rs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8f6d6ef7ee87..6d3831f77707 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.77" storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" -metered = "0.9.0" +metrics = "0.23.0" metrics-exporter-prometheus = "0.9.0" serde = { version = "1.0" } sha2 = "0.10.8" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d1102ce5136d..daaa3bd9e902 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -10,7 +10,7 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; -use metered::{metered, HitCount, Throughput}; +use metrics::counter; use std::error::Error; use std::fmt; use std::io::Write; @@ -51,6 +51,9 @@ impl Error for DbError {} type HistoricalRev = NodeStore; +#[derive(Debug, Default)] +pub struct DbMetrics; + #[async_trait] impl api::DbView for HistoricalRev { type Stream<'a> = MerkleKeyValueStream<'a, Self> where Self: 'a; @@ -109,7 +112,6 @@ pub struct Db { manager: RwLock, } -#[metered(registry = DbMetrics, visibility = pub)] #[async_trait] impl api::Db for Db where @@ -132,7 +134,6 @@ where Ok(self.manager.read().expect("poisoned lock").root_hash()?) } - #[measure([HitCount, Throughput])] async fn propose<'p, K: KeyType, V: ValueType>( &'p self, batch: api::Batch, @@ -175,7 +176,7 @@ where impl Db { pub async fn new>(db_path: P, cfg: DbConfig) -> Result { - let metrics = DbMetrics::default().into(); + let metrics = Arc::new(DbMetrics); let manager = RevisionManager::new( db_path.as_ref().to_path_buf(), cfg.truncate, @@ -277,6 +278,8 @@ impl<'a> api::Proposal for Proposal<'a> { .expect("poisoned lock") .add_proposal(immutable.clone()); + counter!("firewood.proposals").increment(1); + Ok(Self::Proposal { nodestore: immutable, db: self.db, From 057dec6ac3104e3ac3a226b48f4d1f3962188956 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 18:34:09 -1000 Subject: [PATCH 0556/1053] Add listener port 3000 --- firewood/Cargo.toml | 3 ++- firewood/examples/benchmark.rs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 6d3831f77707..7ceeb811cd88 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,7 +22,7 @@ storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metrics = "0.23.0" -metrics-exporter-prometheus = "0.9.0" +metrics-exporter-prometheus = "0.1.3" serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" @@ -33,6 +33,7 @@ log = { version = "0.4.20", optional = true } test-case = "3.3.1" integer-encoding = "4.0.0" io-uring = {version = "0.6", optional = true } +metrics-util = "0.17.0" [features] default = [] diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 27ca925d6eff..45d020b8b34c 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -6,14 +6,10 @@ use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; +use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; use std::{ - borrow::BorrowMut as _, - collections::HashMap, - error::Error, - num::NonZeroUsize, - ops::RangeInclusive, - time::{Duration, Instant}, + borrow::BorrowMut as _, collections::HashMap, error::Error, net::{Ipv4Addr, SocketAddr}, num::NonZeroUsize, ops::RangeInclusive, time::{Duration, Instant} }; use firewood::{ @@ -63,6 +59,14 @@ async fn main() -> Result<(), Box> { builder .with_push_gateway("http://localhost:9090", Duration::from_secs(10)) .expect("cannot setup push gateway") + .with_http_listener(SocketAddr::new( + Ipv4Addr::UNSPECIFIED.into(), + 3000, + )) + .idle_timeout( + MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, + Some(Duration::from_secs(10)), + ) .install() .expect("unable in run prometheusbuilder"); From 4b0abd60416940477717e8118f7a1598201b4a28 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 18:48:18 -1000 Subject: [PATCH 0557/1053] WIP --- firewood/Cargo.toml | 2 +- firewood/examples/benchmark.rs | 2 +- firewood/src/db.rs | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 7ceeb811cd88..a7b35e002f76 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,7 +22,7 @@ storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metrics = "0.23.0" -metrics-exporter-prometheus = "0.1.3" +metrics-exporter-prometheus = "0.15.3" serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 45d020b8b34c..a2789696f80a 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -57,7 +57,7 @@ async fn main() -> Result<(), Box> { let builder = PrometheusBuilder::new(); builder - .with_push_gateway("http://localhost:9090", Duration::from_secs(10)) + .with_push_gateway("http://localhost:9090", Duration::from_secs(10), None, None) .expect("cannot setup push gateway") .with_http_listener(SocketAddr::new( Ipv4Addr::UNSPECIFIED.into(), diff --git a/firewood/src/db.rs b/firewood/src/db.rs index daaa3bd9e902..c4f450022a4d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -10,7 +10,7 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; -use metrics::counter; +use metrics::{counter, describe_counter}; use std::error::Error; use std::fmt; use std::io::Write; @@ -51,8 +51,16 @@ impl Error for DbError {} type HistoricalRev = NodeStore; -#[derive(Debug, Default)] -pub struct DbMetrics; + +pub struct DbMetrics { + _proposals: metrics::Counter, +} + +impl std::fmt::Debug for DbMetrics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DbMetrics").finish() + } +} #[async_trait] impl api::DbView for HistoricalRev { @@ -166,6 +174,8 @@ where .expect("poisoned lock") .add_proposal(immutable.clone()); + counter!("firewood.proposals").increment(1); + Ok(Self::Proposal { nodestore: immutable, db: self, @@ -176,7 +186,8 @@ where impl Db { pub async fn new>(db_path: P, cfg: DbConfig) -> Result { - let metrics = Arc::new(DbMetrics); + let metrics = Arc::new(DbMetrics { _proposals: counter!("firewood.proposals") }); + describe_counter!("firewood.proposals", "Number of proposals created"); let manager = RevisionManager::new( db_path.as_ref().to_path_buf(), cfg.truncate, @@ -278,7 +289,6 @@ impl<'a> api::Proposal for Proposal<'a> { .expect("poisoned lock") .add_proposal(immutable.clone()); - counter!("firewood.proposals").increment(1); Ok(Self::Proposal { nodestore: immutable, From 8c9257bb234b309fdce5b1cfd0afc797d80ec933 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Sep 2024 18:52:09 -1000 Subject: [PATCH 0558/1053] WIP --- firewood/src/db.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c4f450022a4d..8bb58413e76c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -53,7 +53,7 @@ type HistoricalRev = NodeStore; pub struct DbMetrics { - _proposals: metrics::Counter, + proposals: metrics::Counter, } impl std::fmt::Debug for DbMetrics { @@ -174,7 +174,7 @@ where .expect("poisoned lock") .add_proposal(immutable.clone()); - counter!("firewood.proposals").increment(1); + self.metrics.proposals.increment(1); Ok(Self::Proposal { nodestore: immutable, @@ -186,7 +186,7 @@ where impl Db { pub async fn new>(db_path: P, cfg: DbConfig) -> Result { - let metrics = Arc::new(DbMetrics { _proposals: counter!("firewood.proposals") }); + let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals") }); describe_counter!("firewood.proposals", "Number of proposals created"); let manager = RevisionManager::new( db_path.as_ref().to_path_buf(), From e87ea737af58b72736e009b1004a377ba384e9f0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Sep 2024 08:31:56 -1000 Subject: [PATCH 0559/1053] Benchmark WIP --- firewood/examples/benchmark.rs | 10 ++++++---- firewood/src/db.rs | 1 - firewood/src/merkle.rs | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index a2789696f80a..0cc3977b350a 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -2,7 +2,8 @@ // See the file LICENSE.md for licensing terms. // The idea behind this benchmark: -// Generate known keys from the SHA256 of the row number, starting at 0 +// Phase 1: Generate known keys from the SHA256 of the row number, starting at 0, for 1B keys +// use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; @@ -11,6 +12,7 @@ use sha2::{Digest, Sha256}; use std::{ borrow::BorrowMut as _, collections::HashMap, error::Error, net::{Ipv4Addr, SocketAddr}, num::NonZeroUsize, ops::RangeInclusive, time::{Duration, Instant} }; +use Url::Url; use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, @@ -37,6 +39,8 @@ struct Args { truncate: bool, #[arg(short, long, default_value_t = 128)] revisions: usize, + #[arg(short = 'p', long, default_value_t = 3000)] + prometheus_port: u16, } fn string_to_range(input: &str) -> Result, Box> { @@ -57,11 +61,9 @@ async fn main() -> Result<(), Box> { let builder = PrometheusBuilder::new(); builder - .with_push_gateway("http://localhost:9090", Duration::from_secs(10), None, None) - .expect("cannot setup push gateway") .with_http_listener(SocketAddr::new( Ipv4Addr::UNSPECIFIED.into(), - 3000, + args.prometheus_port, )) .idle_timeout( MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8bb58413e76c..2e4499931ac9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -111,7 +111,6 @@ pub struct DbConfig { pub manager: RevisionManagerConfig, } -/// TODO danlaine: implement #[derive(Debug)] pub struct Db { metrics: Arc, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5c9ffbc9d226..21b15dc3c889 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,6 +6,7 @@ use crate::range_proof::RangeProof; use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; +use metrics::{counter, histogram}; use std::collections::HashSet; use std::fmt::Debug; use std::future::ready; @@ -402,6 +403,8 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { + histogram!("firewood.insert.key.length").record(key.len() as f64); + histogram!("firewood.insert.data.length").record(value.len() as f64); let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -414,6 +417,7 @@ impl Merkle> { value, }); *root = root_node.into(); + counter!("firewood.merkle.insert.root").increment(1); return Ok(()); }; @@ -452,6 +456,7 @@ impl Merkle> { (None, None) => { // 1. The node is at `key` node.update_value(value); + counter!("firewood.merkle.update").increment(1); Ok(node) } (None, Some((child_index, partial_path))) => { @@ -471,6 +476,7 @@ impl Merkle> { // Shorten the node's partial path since it has a new parent. node.update_partial_path(partial_path); branch.update_child(child_index, Some(Child::Node(node))); + counter!("firewood.merkle.insert.above").increment(1); Ok(Node::Branch(Box::new(branch))) } @@ -483,6 +489,7 @@ impl Merkle> { // ... (key may be below) ... (key is below) match node { Node::Branch(ref mut branch) => { + counter!("firewood.merkle.insert.below").increment(1); #[allow(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) { @@ -509,6 +516,7 @@ impl Merkle> { Ok(node) } Node::Leaf(ref mut leaf) => { + counter!("firewood.merkle.inserted.split").increment(1); // Turn this node into a branch node and put a new leaf as a child. let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), @@ -535,6 +543,7 @@ impl Merkle> { // | | \ // node key // Make a branch node that has both the current node and a new leaf node as children. + counter!("firewood.merkle.inserted.split").increment(1); let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: None, @@ -565,11 +574,17 @@ impl Merkle> { let root = self.nodestore.mut_root(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. + counter!("firewood.merkle.remove.nonexistent").increment(1); return Ok(None); }; let (root_node, removed_value) = self.remove_helper(root_node, &key)?; *self.nodestore.mut_root() = root_node; + if removed_value.is_some() { + counter!("firewood.merkle.remove.success").increment(1); + } else { + counter!("firewood.merkle.remove.nonexistent").increment(1); + } Ok(removed_value) } From d865f60dc99b41569da15395d5436623b1c6cc7a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Sep 2024 10:07:39 -1000 Subject: [PATCH 0560/1053] WIP --- firewood/examples/benchmark.rs | 1 - firewood/src/merkle.rs | 9 +++++---- storage/Cargo.toml | 1 + storage/src/linear/filebacked.rs | 9 ++++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 0cc3977b350a..5c21a00ac1e9 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -12,7 +12,6 @@ use sha2::{Digest, Sha256}; use std::{ borrow::BorrowMut as _, collections::HashMap, error::Error, net::{Ipv4Addr, SocketAddr}, num::NonZeroUsize, ops::RangeInclusive, time::{Duration, Instant} }; -use Url::Url; use firewood::{ db::{Batch, BatchOp, Db, DbConfig}, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 21b15dc3c889..a3c03ff0afcb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -405,6 +405,8 @@ impl Merkle> { pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { histogram!("firewood.insert.key.length").record(key.len() as f64); histogram!("firewood.insert.data.length").record(value.len() as f64); + counter!("firewood.merkle.insert").increment(1); + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -417,7 +419,6 @@ impl Merkle> { value, }); *root = root_node.into(); - counter!("firewood.merkle.insert.root").increment(1); return Ok(()); }; @@ -489,7 +490,6 @@ impl Merkle> { // ... (key may be below) ... (key is below) match node { Node::Branch(ref mut branch) => { - counter!("firewood.merkle.insert.below").increment(1); #[allow(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) { @@ -501,6 +501,7 @@ impl Merkle> { partial_path, }); branch.update_child(child_index, Some(Child::Node(new_leaf))); + counter!("firewood.merkle.insert.below").increment(1); return Ok(node); } Some(Child::Node(child)) => child, @@ -516,7 +517,6 @@ impl Merkle> { Ok(node) } Node::Leaf(ref mut leaf) => { - counter!("firewood.merkle.inserted.split").increment(1); // Turn this node into a branch node and put a new leaf as a child. let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), @@ -531,6 +531,7 @@ impl Merkle> { branch.update_child(child_index, Some(Child::Node(new_leaf))); + counter!("firewood.merkle.insert.split").increment(1); Ok(Node::Branch(Box::new(branch))) } } @@ -543,7 +544,6 @@ impl Merkle> { // | | \ // node key // Make a branch node that has both the current node and a new leaf node as children. - counter!("firewood.merkle.inserted.split").increment(1); let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: None, @@ -559,6 +559,7 @@ impl Merkle> { }); branch.update_child(key_index, Some(Child::Node(new_leaf))); + counter!("firewood.merkle.insert.split").increment(1); Ok(Node::Branch(Box::new(branch))) } } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 7bf019dbef6a..825609d85f8e 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -16,6 +16,7 @@ sha2 = "0.10.8" integer-encoding = "4.0.0" arc-swap = "1.7.1" lru = "0.12.4" +metrics = "0.23.0" [dev-dependencies] rand = "0.8.5" diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 75804b126b88..701a79b6fb24 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -17,6 +17,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use lru::LruCache; +use metrics::counter; use crate::{LinearAddress, Node}; @@ -66,7 +67,13 @@ impl ReadableStorage for FileBacked { fn read_cached_node(&self, addr: LinearAddress) -> Option> { let mut guard = self.cache.lock().expect("poisoned lock"); - guard.get(&addr).cloned() + let cached = guard.get(&addr).cloned(); + if cached.is_some() { + counter!("firewood.node.cache.hit").increment(1); + } else { + counter!("firewood.node.cache.miss").increment(1); + } + cached } } From 8d93f48d824ad73763be80f746e2a41b0425f769 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Sep 2024 13:08:01 -1000 Subject: [PATCH 0561/1053] Change to firewood.insert{merkle=\"above\"} --- firewood/src/merkle.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index a3c03ff0afcb..d842735f32b7 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -477,7 +477,7 @@ impl Merkle> { // Shorten the node's partial path since it has a new parent. node.update_partial_path(partial_path); branch.update_child(child_index, Some(Child::Node(node))); - counter!("firewood.merkle.insert.above").increment(1); + counter!("firewood.insert{merkle=\"above\"}").increment(1); Ok(Node::Branch(Box::new(branch))) } @@ -501,7 +501,7 @@ impl Merkle> { partial_path, }); branch.update_child(child_index, Some(Child::Node(new_leaf))); - counter!("firewood.merkle.insert.below").increment(1); + counter!("firewood.insert{merkle=\"below\"}").increment(1); return Ok(node); } Some(Child::Node(child)) => child, @@ -531,7 +531,7 @@ impl Merkle> { branch.update_child(child_index, Some(Child::Node(new_leaf))); - counter!("firewood.merkle.insert.split").increment(1); + counter!("firewood.insert{merkle=\"split\"}").increment(1); Ok(Node::Branch(Box::new(branch))) } } @@ -559,7 +559,7 @@ impl Merkle> { }); branch.update_child(key_index, Some(Child::Node(new_leaf))); - counter!("firewood.merkle.insert.split").increment(1); + counter!("firewood.insert{merkle=\"split\"}").increment(1); Ok(Node::Branch(Box::new(branch))) } } @@ -575,16 +575,16 @@ impl Merkle> { let root = self.nodestore.mut_root(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. - counter!("firewood.merkle.remove.nonexistent").increment(1); + counter!("firewood.remove{result = \"nonexistent\"}").increment(1); return Ok(None); }; let (root_node, removed_value) = self.remove_helper(root_node, &key)?; *self.nodestore.mut_root() = root_node; if removed_value.is_some() { - counter!("firewood.merkle.remove.success").increment(1); + counter!("firewood.remove{result = \"success\"}").increment(1); } else { - counter!("firewood.merkle.remove.nonexistent").increment(1); + counter!("firewood.remove{result = \"nonexistent\"}").increment(1); } Ok(removed_value) } From a36ee18cc4f9f574ff9d3db921bb3bee7290fc2a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 06:39:48 -1000 Subject: [PATCH 0562/1053] Implement steady state for benchmark --- firewood/examples/benchmark.rs | 160 +++++++++++++++++++++++---------- firewood/src/db.rs | 6 +- 2 files changed, 114 insertions(+), 52 deletions(-) diff --git a/firewood/examples/benchmark.rs b/firewood/examples/benchmark.rs index 5c21a00ac1e9..cb99db1fd02c 100644 --- a/firewood/examples/benchmark.rs +++ b/firewood/examples/benchmark.rs @@ -2,43 +2,54 @@ // See the file LICENSE.md for licensing terms. // The idea behind this benchmark: -// Phase 1: Generate known keys from the SHA256 of the row number, starting at 0, for 1B keys -// +// Phase 1: (setup) Generate known keys from the SHA256 of the row number, starting at 0, for 1B keys +// Phase 2: (steady-state) Continuously insert, delete, and update keys in the database + +// Phase 2 consists of: +// 1. 25% of batch size is inserting more rows like phase 1 +// 2. 25% of batch size is deleting rows from the beginning +// 3. 50% of batch size is updating rows in the middle, but setting the value to the hash of the first row inserted +// use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; -use std::{ - borrow::BorrowMut as _, collections::HashMap, error::Error, net::{Ipv4Addr, SocketAddr}, num::NonZeroUsize, ops::RangeInclusive, time::{Duration, Instant} -}; - -use firewood::{ - db::{Batch, BatchOp, Db, DbConfig}, - manager::RevisionManagerConfig, - v2::api::{Db as _, DbView, Proposal as _}, -}; -use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; +use std::collections::HashMap; +use std::error::Error; +use std::net::{Ipv4Addr, SocketAddr}; +use std::num::NonZeroUsize; +use std::ops::RangeInclusive; +use std::time::{Duration, Instant}; + +use firewood::db::{Batch, BatchOp, Db, DbConfig}; +use firewood::manager::RevisionManagerConfig; +use firewood::v2::api::{Db as _, DbView, Proposal as _}; +use rand::Rng; #[derive(Parser, Debug)] struct Args { #[arg(short, long, default_value = "32", value_parser = string_to_range)] valuelen: RangeInclusive, #[arg(short, long, default_value_t = 1)] - batch_size: usize, + batch_size: u64, #[arg(short, long, default_value_t = 100)] - number_of_batches: usize, + number_of_batches: u64, #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, - #[arg(short, long)] - seed: Option, + #[arg( + short, + long, + help = "Only initialize the database, do not do the insert/delete/update loop" + )] + initialize_only: bool, #[arg(short, long, default_value_t = NonZeroUsize::new(20480).expect("is non-zero"))] cache_size: NonZeroUsize, - #[arg(short, long, default_value_t = true)] - truncate: bool, + #[arg(short, long)] + assume_preloaded_rows: Option, #[arg(short, long, default_value_t = 128)] revisions: usize, - #[arg(short = 'p', long, default_value_t = 3000)] + #[arg(short = 'l', long, default_value_t = 3000)] prometheus_port: u16, } @@ -76,7 +87,7 @@ async fn main() -> Result<(), Box> { .max_revisions(args.revisions) .build(); let cfg = DbConfig::builder() - .truncate(args.truncate) + .truncate(args.assume_preloaded_rows.is_none()) .manager(mgrcfg) .build(); @@ -87,42 +98,93 @@ async fn main() -> Result<(), Box> { let keys = args.batch_size; let start = Instant::now(); - let mut rng = if let Some(seed) = args.seed { - rand::rngs::StdRng::seed_from_u64(seed) - } else { - rand::rngs::StdRng::from_entropy() - }; - - for key in 0..args.number_of_batches { - let valuelen = rng.gen_range(args.valuelen.clone()); - let batch: Batch, Vec> = (0..keys) - .map(|inner_key| { - ( - Sha256::digest((key * keys + inner_key).to_ne_bytes()).to_vec(), - rng.borrow_mut() - .sample_iter(&Alphanumeric) - .take(valuelen) - .collect::>(), - ) - }) - .map(|(key, value)| BatchOp::Put { key, value }) - .collect(); + if args.assume_preloaded_rows.is_none() { + for key in 0..args.number_of_batches { + let batch = generate_inserts(key * keys, args.batch_size).collect(); - let verify = get_keys_to_verify(&batch, args.read_verify_percent); + let verify = get_keys_to_verify(&batch, args.read_verify_percent); - #[allow(clippy::unwrap_used)] - let proposal = db.propose(batch).await.unwrap(); - proposal.commit().await?; - verify_keys(&db, verify).await?; + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + verify_keys(&db, verify).await?; + } + + let duration = start.elapsed(); + println!( + "Generated and inserted {} batches of size {keys} in {duration:?}", + args.number_of_batches + ); + } + + let current_hash = db.root_hash().await?.expect("root hash should exist"); + + if args.initialize_only { + println!("Completed initialization with hash of {:?}", current_hash); + + return Ok(()); } - let duration = start.elapsed(); + // batches consist of + // 1. 25% deletes from low + // 2. 25% new insertions from high + // 3. 50% updates from the middle + println!( - "Generated and inserted {} batches of size {keys} in {duration:?}", - args.number_of_batches + "Starting inner loop with database hash of {:?}", + current_hash ); - Ok(()) + let mut low = 0; + let mut high = args + .assume_preloaded_rows + .unwrap_or(args.number_of_batches * args.batch_size); + let twenty_five_pct = args.batch_size / 4; + + loop { + let batch: Vec> = generate_inserts(high, twenty_five_pct) + .chain(generate_deletes(low, twenty_five_pct)) + .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) + .collect(); + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + low += twenty_five_pct; + high += twenty_five_pct; + } +} + +fn generate_inserts(start: u64, count: u64) -> impl Iterator, Vec>> { + (start..start + count) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + (digest.clone(), digest) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() +} + +fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { + (start..start + count) + .map(|key| Sha256::digest(key.to_ne_bytes()).to_vec()) + .map(|key| BatchOp::Delete { key }) + .collect::>() + .into_iter() +} + +fn generate_updates( + start: u64, + count: u64, + low: u64, +) -> impl Iterator, Vec>> { + let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); + (start..start + count) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + (digest, hash_of_low.clone()) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() } fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>> { diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 2e4499931ac9..d4bedda68b42 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -51,7 +51,6 @@ impl Error for DbError {} type HistoricalRev = NodeStore; - pub struct DbMetrics { proposals: metrics::Counter, } @@ -185,7 +184,9 @@ where impl Db { pub async fn new>(db_path: P, cfg: DbConfig) -> Result { - let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals") }); + let metrics = Arc::new(DbMetrics { + proposals: counter!("firewood.proposals"), + }); describe_counter!("firewood.proposals", "Number of proposals created"); let manager = RevisionManager::new( db_path.as_ref().to_path_buf(), @@ -288,7 +289,6 @@ impl<'a> api::Proposal for Proposal<'a> { .expect("poisoned lock") .add_proposal(immutable.clone()); - Ok(Self::Proposal { nodestore: immutable, db: self.db, From dccfa60ba3ed252f4e6da89fffd74ae1bd8af055 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 10:05:46 -1000 Subject: [PATCH 0563/1053] Move benchmark to separate project This avoids cluttering the firewood library dependencies with prometheus --- Cargo.toml | 2 +- benchmark/Cargo.toml | 13 +++++++++++++ .../examples/benchmark.rs => benchmark/src/main.rs | 2 +- firewood/Cargo.toml | 2 -- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 benchmark/Cargo.toml rename firewood/examples/benchmark.rs => benchmark/src/main.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 7118fbd72da1..b6590957f6ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "firewood", "fwdctl", "storage", - "grpc-testtool", + "grpc-testtool", "benchmark", ] resolver = "2" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml new file mode 100644 index 000000000000..bad1e80e8848 --- /dev/null +++ b/benchmark/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "benchmark" +version = "0.1.0" +edition = "2021" + +[dependencies] +firewood = { path = "../firewood" } +clap = { version = "4.5.0", features = ['derive'] } +sha2 = "0.10.8" +metrics-util = "0.17.0" +metrics-exporter-prometheus = "0.15.3" +tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } +rand = "0.8.5" diff --git a/firewood/examples/benchmark.rs b/benchmark/src/main.rs similarity index 99% rename from firewood/examples/benchmark.rs rename to benchmark/src/main.rs index cb99db1fd02c..3e789b97c840 100644 --- a/firewood/examples/benchmark.rs +++ b/benchmark/src/main.rs @@ -14,6 +14,7 @@ use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; +use rand::Rng as _; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::error::Error; @@ -25,7 +26,6 @@ use std::time::{Duration, Instant}; use firewood::db::{Batch, BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; use firewood::v2::api::{Db as _, DbView, Proposal as _}; -use rand::Rng; #[derive(Parser, Debug)] struct Args { diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index a7b35e002f76..ff2e3889d476 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -22,7 +22,6 @@ storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metrics = "0.23.0" -metrics-exporter-prometheus = "0.15.3" serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" @@ -33,7 +32,6 @@ log = { version = "0.4.20", optional = true } test-case = "3.3.1" integer-encoding = "4.0.0" io-uring = {version = "0.6", optional = true } -metrics-util = "0.17.0" [features] default = [] From f53cbf1d1d2f6ad4407b9c5435531aeba8ac77b7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 10:34:18 -1000 Subject: [PATCH 0564/1053] Improve defaults --- benchmark/src/main.rs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 3e789b97c840..9d121813bf38 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -29,11 +29,9 @@ use firewood::v2::api::{Db as _, DbView, Proposal as _}; #[derive(Parser, Debug)] struct Args { - #[arg(short, long, default_value = "32", value_parser = string_to_range)] - valuelen: RangeInclusive, - #[arg(short, long, default_value_t = 1)] + #[arg(short, long, default_value_t = 10000)] batch_size: u64, - #[arg(short, long, default_value_t = 100)] + #[arg(short, long, default_value_t = 100000)] number_of_batches: u64, #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, @@ -43,7 +41,7 @@ struct Args { help = "Only initialize the database, do not do the insert/delete/update loop" )] initialize_only: bool, - #[arg(short, long, default_value_t = NonZeroUsize::new(20480).expect("is non-zero"))] + #[arg(short, long, default_value_t = NonZeroUsize::new(1500000).expect("is non-zero"))] cache_size: NonZeroUsize, #[arg(short, long)] assume_preloaded_rows: Option, @@ -53,18 +51,6 @@ struct Args { prometheus_port: u16, } -fn string_to_range(input: &str) -> Result, Box> { - //::Err> { - let parts: Vec<&str> = input.split('-').collect(); - #[allow(clippy::indexing_slicing)] - match parts.len() { - 1 => Ok(input.parse()?..=input.parse()?), - 2 => Ok(parts[0].parse()?..=parts[1].parse()?), - _ => Err("Too many dashes in input string".into()), - } -} - -/// cargo run --release --example insert #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { let args = Args::parse(); From 19ba5c233a767a46e28954413b7e5e0c0cbba065 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Sep 2024 10:50:30 -1000 Subject: [PATCH 0565/1053] Warning fix --- benchmark/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 9d121813bf38..860a69949c1e 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -20,7 +20,6 @@ use std::collections::HashMap; use std::error::Error; use std::net::{Ipv4Addr, SocketAddr}; use std::num::NonZeroUsize; -use std::ops::RangeInclusive; use std::time::{Duration, Instant}; use firewood::db::{Batch, BatchOp, Db, DbConfig}; From ba02724525ef12244cead50194624dfe6e156f00 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 9 Sep 2024 10:26:23 -1000 Subject: [PATCH 0566/1053] BUGFIX: Insert truncated trie We were deleting some children if they were already on disk --- storage/src/nodestore.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 327a4f6c6e27..98dffc5a959b 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -804,9 +804,14 @@ impl NodeStore, S> { match node { Node::Branch(ref mut b) => { for (nibble, child) in b.children.iter_mut().enumerate() { - // Take child from b.children + // if this is already hashed, we're done + if matches!(child, Some(Child::AddressWithHash(_, _))) { + // We already know the hash of this child. + continue; + } + + // If this child is a node, hash it and update the child. let Some(Child::Node(child_node)) = std::mem::take(child) else { - // There is no child or we already know its hash. continue; }; From c6592db760dd5836ecb4c4082860b4b02c61c051 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 9 Sep 2024 10:27:43 -1000 Subject: [PATCH 0567/1053] Re-enable dump option of fwdctl Added hex output option since the benchmark uses raw hex values --- benchmark/Cargo.toml | 1 + fwdctl/Cargo.toml | 1 + fwdctl/src/dump.rs | 42 +++++++++++++++++++++++++----------------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index bad1e80e8848..dca75f34e814 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] firewood = { path = "../firewood" } +hex = "0.4.3" clap = { version = "4.5.0", features = ['derive'] } sha2 = "0.10.8" metrics-util = "0.17.0" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 3c05ebfcc203..00511ab3aa17 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -11,6 +11,7 @@ env_logger = "0.11.2" log = "0.4.20" tokio = { version = "1.36.0", features = ["full"] } futures-util = "0.3.30" +hex = "0.4.3" [dev-dependencies] assert_cmd = "2.0.13" diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 0c7d5738761b..9072d423d31f 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -4,7 +4,9 @@ use clap::Args; use firewood::db::{Db, DbConfig}; use firewood::merkle::Key; -use firewood::v2::api::{self, Db as _}; +use firewood::stream::MerkleKeyValueStream; +use firewood::v2::api::{self, Db as _, DbView}; +use futures_util::StreamExt; use std::borrow::Cow; #[derive(Debug, Args)] @@ -27,6 +29,8 @@ pub struct Options { help = "Start dumping from this key (inclusive)." )] pub start_key: Option, + #[arg(short, long, help = "Print the keys and values in hex format.")] + pub hex: bool, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { @@ -35,27 +39,31 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db = Db::new(opts.db.clone(), cfg.build()).await?; let latest_hash = db.root_hash().await?; - let Some(_latest_hash) = latest_hash else { + let Some(latest_hash) = latest_hash else { println!("Database is empty"); return Ok(()); }; - todo!() - // let latest_rev = db.revision(latest_hash).await?; - // let start_key = opts.start_key.clone().unwrap_or(Box::new([])); - // let mut stream = latest_rev.stream_from(&start_key)?; - // loop { - // match stream.next().await { - // None => break, - // Some(Ok((key, value))) => { - // println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); - // } - // Some(Err(e)) => return Err(e), - // } - // } - // Ok(()) + let latest_rev = db.revision(latest_hash).await?; + latest_rev.val("xxxx".as_bytes()).await?; + let _start_key = opts.start_key.clone().unwrap_or(Box::new([])); + let mut stream = MerkleKeyValueStream::from(&latest_rev); + loop { + match stream.next().await { + None => break, + Some(Ok((key, value))) => { + if opts.hex { + println!("'{}': '{}'", hex::encode(&key), hex::encode(&value)); + } else { + println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); + } + } + Some(Err(e)) => return Err(e), + } + } + Ok(()) } -fn _u8_to_string(data: &[u8]) -> Cow<'_, str> { +fn u8_to_string(data: &[u8]) -> Cow<'_, str> { String::from_utf8_lossy(data) } From f405bf554d45e3f3752fefb0877ded68ff9e850e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 9 Sep 2024 14:06:25 -1000 Subject: [PATCH 0568/1053] More metrics improvements Attempt to use labels to describe the insert or delete --- firewood/src/merkle.rs | 17 ++++++++--------- storage/src/nodestore.rs | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d842735f32b7..c4028b7e47ed 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -405,7 +405,6 @@ impl Merkle> { pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { histogram!("firewood.insert.key.length").record(key.len() as f64); histogram!("firewood.insert.data.length").record(value.len() as f64); - counter!("firewood.merkle.insert").increment(1); let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); @@ -457,7 +456,7 @@ impl Merkle> { (None, None) => { // 1. The node is at `key` node.update_value(value); - counter!("firewood.merkle.update").increment(1); + counter!("firewood.insert", "merkle" => "update").increment(1); Ok(node) } (None, Some((child_index, partial_path))) => { @@ -477,7 +476,7 @@ impl Merkle> { // Shorten the node's partial path since it has a new parent. node.update_partial_path(partial_path); branch.update_child(child_index, Some(Child::Node(node))); - counter!("firewood.insert{merkle=\"above\"}").increment(1); + counter!("firewood.insert", "merkle"=>"above").increment(1); Ok(Node::Branch(Box::new(branch))) } @@ -501,7 +500,7 @@ impl Merkle> { partial_path, }); branch.update_child(child_index, Some(Child::Node(new_leaf))); - counter!("firewood.insert{merkle=\"below\"}").increment(1); + counter!("firewood.insert", "merkle"=>"below").increment(1); return Ok(node); } Some(Child::Node(child)) => child, @@ -531,7 +530,7 @@ impl Merkle> { branch.update_child(child_index, Some(Child::Node(new_leaf))); - counter!("firewood.insert{merkle=\"split\"}").increment(1); + counter!("firewood.insert", "merkle"=>"split").increment(1); Ok(Node::Branch(Box::new(branch))) } } @@ -559,7 +558,7 @@ impl Merkle> { }); branch.update_child(key_index, Some(Child::Node(new_leaf))); - counter!("firewood.insert{merkle=\"split\"}").increment(1); + counter!("firewood.insert", "merkle" => "split").increment(1); Ok(Node::Branch(Box::new(branch))) } } @@ -575,16 +574,16 @@ impl Merkle> { let root = self.nodestore.mut_root(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. - counter!("firewood.remove{result = \"nonexistent\"}").increment(1); + counter!("firewood.remove", "result" => "nonexistent").increment(1); return Ok(None); }; let (root_node, removed_value) = self.remove_helper(root_node, &key)?; *self.nodestore.mut_root() = root_node; if removed_value.is_some() { - counter!("firewood.remove{result = \"success\"}").increment(1); + counter!("firewood.remove", "result" => "success").increment(1); } else { - counter!("firewood.remove{result = \"nonexistent\"}").increment(1); + counter!("firewood.remove", "result" => "nonexistent").increment(1); } Ok(removed_value) } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 98dffc5a959b..69cdf6dceda3 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -566,16 +566,6 @@ pub trait HashedNodeReader: TrieReader { } } -impl HashedNodeReader for T -where - T: Deref, - T::Target: HashedNodeReader, -{ - fn root_address_and_hash(&self) -> Result, Error> { - self.deref().root_address_and_hash() - } -} - /// Reads nodes and the root address from a merkle trie. pub trait TrieReader: NodeReader + RootReader {} impl TrieReader for T where T: NodeReader + RootReader {} @@ -1038,9 +1028,28 @@ impl RootReader for NodeStore< } } -impl HashedNodeReader for NodeStore +impl HashedNodeReader for NodeStore +where + NodeStore: TrieReader, + T: ReadInMemoryNode, + S: ReadableStorage, +{ + fn root_address_and_hash(&self) -> Result, Error> { + if let Some(root_addr) = self.header.root_address { + let root_node = self.read_node(root_addr)?; + let root_hash = hash_node(&root_node, &Path::new()); + Ok(Some((root_addr, root_hash))) + } else { + Ok(None) + } + } +} + +impl HashedNodeReader for Arc> where NodeStore: TrieReader, + T: ReadInMemoryNode, + S: ReadableStorage, { fn root_address_and_hash(&self) -> Result, Error> { if let Some(root_addr) = self.header.root_address { From 72aa71e803277b77ec505ac432a49718abe491f4 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 9 Sep 2024 14:38:58 -1000 Subject: [PATCH 0569/1053] Code cleanup Check in some debugging statements, possibly useful down the road --- benchmark/Cargo.toml | 4 +++ benchmark/README.md | 55 ++++++++++++++++++++++++++++++++++++++++++ benchmark/src/main.rs | 25 ++++++++++++++++--- firewood/src/logger.rs | 2 +- 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 benchmark/README.md diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index dca75f34e814..536d8ad26ceb 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -12,3 +12,7 @@ metrics-util = "0.17.0" metrics-exporter-prometheus = "0.15.3" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" +log = { version = "0.4.20", optional = true } + +[features] +logger = ["log"] diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000000..430e99bf0849 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,55 @@ +# Firewood Benchmark + +Welcome to the Firewood Benchmark repository! This repository contains the benchmarking code and resources for the Firewood project. + +## Table of Contents +- [Introduction](#introduction) +- [Installation](#installation) +- [Usage](#usage) +- [Contributing](#contributing) +- [License](#license) + +## Introduction +The Firewood Benchmark is a performance testing suite designed to evaluate the performance of the Firewood project. It includes a set of benchmarks that measure various aspects of the project's performance, such as execution time, memory usage, and scalability. + +## Installation +To install the Firewood Benchmark, follow these steps: + +1. Clone the repository: `git clone https://github.com/ava-labs/firewood.git` +2. Navigate to the firewood directory: `cd firewood` +3. Build the executable: `cargo build --release` +4. Execute the benchmark: `nohup time cargo run --release bin benchmark` + +As the benchmark is running, statistics for prometheus are availble on port 3000 (by default). + +If you want to install grafana and prometheus on an AWS host (using Ubuntu as a base), do the following: + +``` + +``` + + +## Usage +Since the benchmark is in two phases, you may want to create the database first and then +examine the steady-state performance second. This can easily be accomplished with a few +command line options. + +To pre-create the database, use the following command: + +``` +nohup time cargo run --release --bin benchmark -- --initialize-only +``` + +Then, you can look at nohup.out and see how long the database took to initialize. Then, to run +the second phase, use: + +``` +nohup time cargo run --release --bin benchmark -- --assume-preloaded-rows=1000000000 +``` + + +## Contributing +We welcome contributions to the Firewood Benchmark repository! If you would like to contribute, please follow our [contribution guidelines](CONTRIBUTING.md). + +## License +The Firewood Benchmark is open source software licensed under the [MIT License](LICENSE). diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 860a69949c1e..b99eaf9d24b4 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -12,6 +12,7 @@ // use clap::Parser; +use firewood::logger::debug; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use rand::Rng as _; @@ -28,9 +29,9 @@ use firewood::v2::api::{Db as _, DbView, Proposal as _}; #[derive(Parser, Debug)] struct Args { - #[arg(short, long, default_value_t = 10000)] + #[arg(short, long, default_value_t = 10)] batch_size: u64, - #[arg(short, long, default_value_t = 100000)] + #[arg(short, long, default_value_t = 2)] number_of_batches: u64, #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, @@ -109,7 +110,7 @@ async fn main() -> Result<(), Box> { return Ok(()); } - // batches consist of + // batches consist of: // 1. 25% deletes from low // 2. 25% new insertions from high // 3. 50% updates from the middle @@ -141,6 +142,11 @@ fn generate_inserts(start: u64, count: u64) -> impl Iterator impl Iterator impl Iterator, Vec>> { (start..start + count) - .map(|key| Sha256::digest(key.to_ne_bytes()).to_vec()) + .map(|key| { + let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); + debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); + #[allow(clippy::let_and_return)] + digest + }) .map(|key| BatchOp::Delete { key }) .collect::>() .into_iter() @@ -165,6 +176,12 @@ fn generate_updates( (start..start + count) .map(|inner_key| { let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + debug!( + "updating {:?} with digest {} to {}", + inner_key, + hex::encode(&digest), + hex::encode(&hash_of_low) + ); (digest, hash_of_low.clone()) }) .map(|(key, value)| BatchOp::Put { key, value }) diff --git a/firewood/src/logger.rs b/firewood/src/logger.rs index 2f36a73699d8..75eeec375279 100644 --- a/firewood/src/logger.rs +++ b/firewood/src/logger.rs @@ -15,7 +15,7 @@ pub use noop_logger::{debug, error, info, trace, warn}; mod noop_logger { #[macro_export] macro_rules! noop { - ($(target: $a:expr,)? $b:tt) => {}; + ($($arg:tt)+) => {}; } pub use noop as debug; From 1833a2a02419d73a3bdccfec10ef2e86bea0e11e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Sep 2024 07:28:53 -1000 Subject: [PATCH 0570/1053] Use proper defaults for sizes Also pretty-print load times --- benchmark/Cargo.toml | 1 + benchmark/src/main.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 536d8ad26ceb..95ef845d3d97 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -13,6 +13,7 @@ metrics-exporter-prometheus = "0.15.3" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" log = { version = "0.4.20", optional = true } +pretty-duration = "0.1.1" [features] logger = ["log"] diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index b99eaf9d24b4..ef11ce25c909 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -15,6 +15,7 @@ use clap::Parser; use firewood::logger::debug; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; +use pretty_duration::pretty_duration; use rand::Rng as _; use sha2::{Digest, Sha256}; use std::collections::HashMap; @@ -29,9 +30,9 @@ use firewood::v2::api::{Db as _, DbView, Proposal as _}; #[derive(Parser, Debug)] struct Args { - #[arg(short, long, default_value_t = 10)] + #[arg(short, long, default_value_t = 10000)] batch_size: u64, - #[arg(short, long, default_value_t = 2)] + #[arg(short, long, default_value_t = 100000)] number_of_batches: u64, #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, @@ -97,8 +98,9 @@ async fn main() -> Result<(), Box> { let duration = start.elapsed(); println!( - "Generated and inserted {} batches of size {keys} in {duration:?}", - args.number_of_batches + "Generated and inserted {} batches of size {keys} in {}", + args.number_of_batches, + pretty_duration(&duration, None) ); } From 4dbb88c4b6efcc0b2ec4765d183dfa63e69f68de Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Sep 2024 08:38:13 -1000 Subject: [PATCH 0571/1053] Tune release builds We get another 10-20 percent improvement by turning on more optimizations. See https://nnethercote.github.io/perf-book/build-configuration.html --- benchmark/Cargo.toml | 6 ++++++ benchmark/src/main.rs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 95ef845d3d97..21a12e706bf7 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -14,6 +14,12 @@ tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thre rand = "0.8.5" log = { version = "0.4.20", optional = true } pretty-duration = "0.1.1" +tikv-jemallocator = "0.6.0" [features] logger = ["log"] + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = "fat" diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index ef11ce25c909..d24ec7d38bac 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -52,6 +52,9 @@ struct Args { prometheus_port: u16, } +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { let args = Args::parse(); From 7c81cda3e03ac6ff17152d24864466252f450568 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Sep 2024 08:54:29 -1000 Subject: [PATCH 0572/1053] Add a freelist cache This should reduce read requests for the next freelist item in the benchmark. --- benchmark/src/main.rs | 3 +++ firewood/src/manager.rs | 10 +++++++++- storage/src/linear/filebacked.rs | 22 +++++++++++++++++----- storage/src/linear/mod.rs | 8 ++++++++ storage/src/nodestore.rs | 19 +++++++++++++------ 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index d24ec7d38bac..6e5c93f32ecb 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -74,6 +74,9 @@ async fn main() -> Result<(), Box> { let mgrcfg = RevisionManagerConfig::builder() .node_cache_size(args.cache_size) + .free_list_cache_size( + NonZeroUsize::new(2 * args.batch_size as usize).expect("batch size > 0"), + ) .max_revisions(args.revisions) .build(); let cfg = DbConfig::builder() diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 5c96d0471f39..ca5c59ef9dd2 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -23,6 +23,9 @@ pub struct RevisionManagerConfig { #[builder(default_code = "NonZero::new(20480).expect(\"non-zero\")")] node_cache_size: NonZero, + + #[builder(default_code = "NonZero::new(10000).expect(\"non-zero\")")] + free_list_cache_size: NonZero, } type CommittedRevision = Arc>; @@ -62,7 +65,12 @@ impl RevisionManager { truncate: bool, config: RevisionManagerConfig, ) -> Result { - let storage = Arc::new(FileBacked::new(filename, config.node_cache_size, truncate)?); + let storage = Arc::new(FileBacked::new( + filename, + config.node_cache_size, + config.free_list_cache_size, + truncate, + )?); let nodestore = match truncate { true => Arc::new(NodeStore::new_empty_committed(storage.clone())?), false => Arc::new(NodeStore::open(storage.clone())?), diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 701a79b6fb24..94573cae62e6 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -28,6 +28,7 @@ use super::{ReadableStorage, WritableStorage}; pub struct FileBacked { fd: Mutex, cache: Mutex>>, + free_list_cache: Mutex>>, } impl FileBacked { @@ -35,6 +36,7 @@ impl FileBacked { pub fn new( path: PathBuf, node_cache_size: NonZero, + free_list_cache_size: NonZero, truncate: bool, ) -> Result { let fd = OpenOptions::new() @@ -47,6 +49,7 @@ impl FileBacked { Ok(Self { fd: Mutex::new(fd), cache: Mutex::new(LruCache::new(node_cache_size)), + free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)), }) } } @@ -68,11 +71,15 @@ impl ReadableStorage for FileBacked { fn read_cached_node(&self, addr: LinearAddress) -> Option> { let mut guard = self.cache.lock().expect("poisoned lock"); let cached = guard.get(&addr).cloned(); - if cached.is_some() { - counter!("firewood.node.cache.hit").increment(1); - } else { - counter!("firewood.node.cache.miss").increment(1); - } + counter!("firewood.cache.node", "type" => if cached.is_some() { "hit" } else { "miss" }) + .increment(1); + cached + } + + fn free_list_cache(&self, addr: LinearAddress) -> Option> { + let mut guard = self.free_list_cache.lock().expect("poisoned lock"); + let cached = guard.pop(&addr); + counter!("firewood.cache.freelist", "type" => if cached.is_some() { "hit" } else { "miss" }).increment(1); cached } } @@ -102,4 +109,9 @@ impl WritableStorage for FileBacked { guard.pop(addr); } } + + fn add_to_free_list_cache(&self, addr: LinearAddress, next: Option) { + let mut guard = self.free_list_cache.lock().expect("poisoned lock"); + guard.put(addr, next); + } } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 866cecd3f4fb..e60755b2a8ab 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -47,6 +47,11 @@ pub trait ReadableStorage: Debug + Sync + Send { fn read_cached_node(&self, _addr: LinearAddress) -> Option> { None } + + /// Fetch the next pointer from the freelist cache + fn free_list_cache(&self, _addr: LinearAddress) -> Option> { + None + } } /// Trait for writable storage. @@ -73,4 +78,7 @@ pub trait WritableStorage: ReadableStorage { /// Invalidate all nodes that are part of a specific revision, as these will never be referenced again fn invalidate_cached_nodes<'a>(&self, _addresses: impl Iterator) {} + + /// Add a new entry to the freelist cache + fn add_to_free_list_cache(&self, _addr: LinearAddress, _next: Option) {} } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 69cdf6dceda3..28c2b154101d 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -380,13 +380,17 @@ impl NodeStore, S> { if let Some(free_stored_area_addr) = self.header.free_lists[index] { // Update the free list head. // Skip the index byte and Area discriminant byte - let free_area_addr = free_stored_area_addr.get() + 2; - let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: FreeArea = bincode::deserialize_from(free_head_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + if let Some(free_head) = self.storage.free_list_cache(free_stored_area_addr) { + self.header.free_lists[index] = free_head; + } else { + let free_area_addr = free_stored_area_addr.get() + 2; + let free_head_stream = self.storage.stream_from(free_area_addr)?; + let free_head: FreeArea = bincode::deserialize_from(free_head_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - // Update the free list to point to the next free block. - self.header.free_lists[index] = free_head.next_free_block; + // Update the free list to point to the next free block. + self.header.free_lists[index] = free_head.next_free_block; + } // Return the address of the newly allocated block. return Ok(Some((free_stored_area_addr, index as AreaIndex))); @@ -463,6 +467,9 @@ impl NodeStore { self.storage.write(addr.into(), &stored_area_bytes)?; + self.storage + .add_to_free_list_cache(addr, self.header.free_lists[area_size_index as usize]); + // The newly freed block is now the head of the free list. self.header.free_lists[area_size_index as usize] = Some(addr); From 001150a7ea2f68146abe1a004008419d548ac838 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Sep 2024 09:31:21 -1000 Subject: [PATCH 0573/1053] Move benchmark instructions here Should be public so others can reproduce it --- benchmark/Grafana-dashboard.json | 604 +++++++++++++++++++++++++++++++ benchmark/README.md | 37 +- benchmark/setup.sh | 41 +++ 3 files changed, 670 insertions(+), 12 deletions(-) create mode 100644 benchmark/Grafana-dashboard.json create mode 100644 benchmark/setup.sh diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json new file mode 100644 index 000000000000..ca9a65a2084c --- /dev/null +++ b/benchmark/Grafana-dashboard.json @@ -0,0 +1,604 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "11.2.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 14, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean", + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "irate(firewood_insert[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Insertion rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "neutral": 50 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": false, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(firewood_insert)/10000000", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Percent complete", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 8 + }, + "id": 3, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": false, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "firewood_proposals", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Proposals", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "neutral": 0 + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 8 + }, + "id": 4, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": false, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "100 * rate(firewood_node_cache_hit[$__rate_interval])/(rate(firewood_node_cache_miss[$__rate_interval]) + rate(firewood_node_cache_hit[$__rate_interval]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Cache hit rate", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 14, + "x": 0, + "y": 12 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(firewood_node_cache_miss[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "misses per second", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Node cache misses (read+deserialize)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 14, + "x": 0, + "y": 20 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "(firewood_node_cache_hit + firewood_node_cache_miss) / scalar(sum(firewood_insert))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "rpi", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Reads per insert", + "type": "timeseries" + } + ], + "refresh": "10s", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Firewood Dashboard", + "uid": "adxfhfmwx5ypsc", + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/benchmark/README.md b/benchmark/README.md index 430e99bf0849..ea321e66bb9b 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -1,6 +1,6 @@ # Firewood Benchmark -Welcome to the Firewood Benchmark repository! This repository contains the benchmarking code and resources for the Firewood project. +Welcome to the Firewood Benchmark! This repository contains the benchmarking code and resources for the Firewood project. ## Table of Contents - [Introduction](#introduction) @@ -24,10 +24,30 @@ As the benchmark is running, statistics for prometheus are availble on port 3000 If you want to install grafana and prometheus on an AWS host (using Ubuntu as a base), do the following: -``` - -``` - +1. Log in to the AWS EC2 console +2. Launch a new instance: + a. Name: Firewood Benchmark + b. Click on 'Ubuntu' in the quick start section + c. Set the instance type to m5d.2xlarge + d. Set your key pair + e. Check 'Allow HTTP traffic from the internet' for grafana + f. Configure storage to 400GiB disk + g. [optional] Save money by selecting 'spot instance' in advanced + h. Launch the instance +3. ssh ubuntu@AWS-IP +4. Run the script in setup.sh on the instance as root +5. Log in to grafana on http://AWS-IP + a. username: admin, password: admin +6. When prompted, change the password (firewood_is_fast) +7. On the left panel, click "Data Sources" + a. Select "Prometheus" + b. For the URL, use http://localhost:9090 + c. click "Save and test" +8. On the left panel, click Dashboards + a. On the right top pulldown, click New->Import + b. Import the dashboard from the Grafana-dashboard.json file + c. Set the data source to Prometheus +9. You may also want to install a stock dashboard from [here](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) ## Usage Since the benchmark is in two phases, you may want to create the database first and then @@ -46,10 +66,3 @@ the second phase, use: ``` nohup time cargo run --release --bin benchmark -- --assume-preloaded-rows=1000000000 ``` - - -## Contributing -We welcome contributions to the Firewood Benchmark repository! If you would like to contribute, please follow our [contribution guidelines](CONTRIBUTING.md). - -## License -The Firewood Benchmark is open source software licensed under the [MIT License](LICENSE). diff --git a/benchmark/setup.sh b/benchmark/setup.sh new file mode 100644 index 000000000000..dd2fc047833a --- /dev/null +++ b/benchmark/setup.sh @@ -0,0 +1,41 @@ +mkdir -p /etc/apt/keyrings/ +wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null +echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list +apt-get update +apt-get upgrade + +mkdir -p /etc/systemd/system/grafana-server.service.d +cat > /etc/systemd/system/grafana-server.service.d/override.conf <> /etc/prometheus/prometheus.yml < Date: Tue, 10 Sep 2024 12:53:33 -1000 Subject: [PATCH 0574/1053] Dashboard updates Also additional script fixes --- benchmark/Grafana-dashboard.json | 240 ++++++++++++++++--------------- benchmark/setup.sh | 4 +- 2 files changed, 126 insertions(+), 118 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index ca9a65a2084c..5f6c0ced30cc 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -1,41 +1,4 @@ { - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "gauge", - "name": "Gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.2.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { @@ -55,13 +18,13 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, + "id": 2, "links": [], "panels": [ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { @@ -146,7 +109,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "disableTextWrap": false, "editorMode": "builder", @@ -165,18 +128,18 @@ }, { "datasource": { + "default": true, "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "custom": { - "neutral": 50 - }, "mappings": [], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -195,7 +158,7 @@ }, "gridPos": { "h": 4, - "w": 3, + "w": 4, "x": 0, "y": 8 }, @@ -220,11 +183,11 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "disableTextWrap": false, "editorMode": "code", - "expr": "sum(firewood_insert)/10000000", + "expr": "firewood_proposals/1000", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -239,21 +202,31 @@ }, { "datasource": { + "default": true, "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, + "custom": { + "neutral": 0 + }, "mappings": [], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null + }, + { + "color": "red", + "value": 80 } ] } @@ -262,11 +235,11 @@ }, "gridPos": { "h": 4, - "w": 3, - "x": 3, + "w": 10, + "x": 4, "y": 8 }, - "id": 3, + "id": 4, "options": { "minVizHeight": 75, "minVizWidth": 75, @@ -280,46 +253,89 @@ }, "showThresholdLabels": false, "showThresholdMarkers": false, - "sizing": "auto" + "sizing": "auto", + "text": {} }, "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "firewood_proposals", + "editorMode": "code", + "expr": "100 * sum(increase(firewood_cache_node{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_node[$__rate_interval])) by (name)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "node", "range": true, "refId": "A", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddxieanioluyoa" + }, + "editorMode": "code", + "expr": "100 * sum(increase(firewood_cache_freelist{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_freelist[$__rate_interval])) by (name)", + "hide": false, + "instant": false, + "legendFormat": "freelist", + "range": true, + "refId": "B" } ], - "title": "Proposals", + "title": "Cache hit rate", "type": "gauge" }, { "datasource": { + "default": true, "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, "custom": { - "neutral": 0 + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], - "max": 100, - "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -337,54 +353,54 @@ "overrides": [] }, "gridPos": { - "h": 4, - "w": 3, - "x": 6, - "y": 8 + "h": 8, + "w": 14, + "x": 0, + "y": 12 }, - "id": 4, + "id": 5, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "min", + "max", + "mean" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sizing": "auto", - "text": {} + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "disableTextWrap": false, - "editorMode": "code", - "expr": "100 * rate(firewood_node_cache_hit[$__rate_interval])/(rate(firewood_node_cache_miss[$__rate_interval]) + rate(firewood_node_cache_hit[$__rate_interval]))", + "editorMode": "builder", + "expr": "rate(firewood_cache_node{type=\"miss\"}[$__rate_interval])", "fullMetaSearch": false, - "includeNullMetadata": true, + "includeNullMetadata": false, "instant": false, - "legendFormat": "__auto", + "legendFormat": "misses per second", "range": true, "refId": "A", "useBackend": false } ], - "title": "Cache hit rate", - "type": "gauge" + "title": "Node cache misses (read+deserialize)", + "type": "timeseries" }, { "datasource": { + "default": true, "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { @@ -429,8 +445,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -445,17 +460,13 @@ "h": 8, "w": 14, "x": 0, - "y": 12 + "y": 20 }, - "id": 5, + "id": 6, "options": { "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", + "calcs": [], + "displayMode": "list", "placement": "bottom", "showLegend": true }, @@ -468,27 +479,28 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(firewood_node_cache_miss[$__rate_interval])", + "editorMode": "code", + "expr": "sum(firewood_cache_node) / sum(firewood_insert)", "fullMetaSearch": false, - "includeNullMetadata": false, + "includeNullMetadata": true, "instant": false, - "legendFormat": "misses per second", + "legendFormat": "__auto", "range": true, "refId": "A", "useBackend": false } ], - "title": "Node cache misses (read+deserialize)", + "title": "Reads per insert", "type": "timeseries" }, { "datasource": { + "default": true, "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, "fieldConfig": { "defaults": { @@ -548,9 +560,9 @@ "h": 8, "w": 14, "x": 0, - "y": 20 + "y": 28 }, - "id": 6, + "id": 7, "options": { "legend": { "calcs": [], @@ -567,21 +579,17 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "ddxieanioluyoa" }, - "disableTextWrap": false, "editorMode": "code", - "expr": "(firewood_node_cache_hit + firewood_node_cache_miss) / scalar(sum(firewood_insert))", - "fullMetaSearch": false, - "includeNullMetadata": true, + "expr": "node_memory_Dirty_bytes", "instant": false, - "legendFormat": "rpi", + "legendFormat": "__auto", "range": true, - "refId": "A", - "useBackend": false + "refId": "A" } ], - "title": "Reads per insert", + "title": "Dirty bytes", "type": "timeseries" } ], @@ -599,6 +607,6 @@ "timezone": "browser", "title": "Firewood Dashboard", "uid": "adxfhfmwx5ypsc", - "version": 8, + "version": 6, "weekStart": "" } \ No newline at end of file diff --git a/benchmark/setup.sh b/benchmark/setup.sh index dd2fc047833a..714d4d6ca3f1 100644 --- a/benchmark/setup.sh +++ b/benchmark/setup.sh @@ -2,7 +2,7 @@ mkdir -p /etc/apt/keyrings/ wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list apt-get update -apt-get upgrade +apt-get upgrade -y mkdir -p /etc/systemd/system/grafana-server.service.d cat > /etc/systemd/system/grafana-server.service.d/override.conf < Date: Tue, 10 Sep 2024 13:31:13 -1000 Subject: [PATCH 0575/1053] Moar dashboard updates --- benchmark/Grafana-dashboard.json | 70 +++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index 5f6c0ced30cc..0770c0025807 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -1,4 +1,41 @@ { + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "11.2.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], "annotations": { "list": [ { @@ -18,13 +55,13 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2, + "id": null, "links": [], "panels": [ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -109,7 +146,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "disableTextWrap": false, "editorMode": "builder", @@ -128,9 +165,8 @@ }, { "datasource": { - "default": true, "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -183,7 +219,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "disableTextWrap": false, "editorMode": "code", @@ -202,9 +238,8 @@ }, { "datasource": { - "default": true, "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -261,7 +296,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "disableTextWrap": false, "editorMode": "code", @@ -277,7 +312,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "100 * sum(increase(firewood_cache_freelist{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_freelist[$__rate_interval])) by (name)", @@ -293,9 +328,8 @@ }, { "datasource": { - "default": true, "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -379,7 +413,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "disableTextWrap": false, "editorMode": "builder", @@ -398,9 +432,8 @@ }, { "datasource": { - "default": true, "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -479,7 +512,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "disableTextWrap": false, "editorMode": "code", @@ -498,9 +531,8 @@ }, { "datasource": { - "default": true, "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { @@ -579,7 +611,7 @@ { "datasource": { "type": "prometheus", - "uid": "ddxieanioluyoa" + "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "node_memory_Dirty_bytes", From e7d21c80673f53aa964c1f35ddc481312fea4c5b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 13 Sep 2024 10:19:44 -1000 Subject: [PATCH 0576/1053] Use NVME if available --- benchmark/README.md | 2 +- benchmark/setup.sh | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index ea321e66bb9b..1ea95b40d416 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -31,7 +31,7 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a c. Set the instance type to m5d.2xlarge d. Set your key pair e. Check 'Allow HTTP traffic from the internet' for grafana - f. Configure storage to 400GiB disk + f. Configure storage to 20GiB disk g. [optional] Save money by selecting 'spot instance' in advanced h. Launch the instance 3. ssh ubuntu@AWS-IP diff --git a/benchmark/setup.sh b/benchmark/setup.sh index 714d4d6ca3f1..94c77dc37fbc 100644 --- a/benchmark/setup.sh +++ b/benchmark/setup.sh @@ -30,6 +30,17 @@ systemctl start grafana-server systemctl enable grafana-server.service systemctl restart prometheus +NVME_DEV="$(realpath /dev/disk/by-id/nvme-Amazon_EC2_NVMe_Instance_Storage_* | uniq)" +if [ -n "$NVME_DEV" ]; then + mkfs.ext4 -E nodiscard -i 6291456 "$NVME_DEV" + NVME_MOUNT=/mnt/nvme + mkdir -p "$NVME_MOUNT" + mount -o noatime "$NVME_DEV" "$NVME_MOUNT" + echo "$NVME_DEV $NVME_MOUNT ext4 noatime 0 0" >> /etc/fstab + mkdir "$NVME_MOUNT/ubuntu" + chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" + ln -s /home/ubuntu/firewood "$NVME_MOUNT/ubuntu/firewood" +fi curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y . "$HOME/.cargo/env" @@ -38,4 +49,5 @@ cd firewood git checkout rkuris/prometheus cargo build --release -nohup time cargo run --release --bin benchmark -- -b 10000 -c 1500000 -n 100000 & +# nohup time cargo run --release --bin benchmark -- -b 10000 -c 1500000 -n 100000 & +nohup time cargo run --release --bin benchmark -- -b 100000 -c 1500000 -n 1000 -i & From 6d7b94abfb9fd06669c1f7a88482698c0656e478 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 20 Sep 2024 09:25:51 -1000 Subject: [PATCH 0577/1053] Benchmark cleanups Updated dashboard Removed read verification code --- benchmark/Grafana-dashboard.json | 33 ++++++++++++++++++------- benchmark/src/main.rs | 41 ++------------------------------ 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index 0770c0025807..6dd6197c8c7f 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -119,7 +119,7 @@ "overrides": [] }, "gridPos": { - "h": 8, + "h": 12, "w": 14, "x": 0, "y": 0 @@ -158,6 +158,23 @@ "range": true, "refId": "A", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(firewood_remove[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false } ], "title": "Insertion rate", @@ -196,7 +213,7 @@ "h": 4, "w": 4, "x": 0, - "y": 8 + "y": 12 }, "id": 2, "options": { @@ -223,7 +240,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "firewood_proposals/1000", + "expr": "sum(firewood_insert)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -272,7 +289,7 @@ "h": 4, "w": 10, "x": 4, - "y": 8 + "y": 12 }, "id": 4, "options": { @@ -390,7 +407,7 @@ "h": 8, "w": 14, "x": 0, - "y": 12 + "y": 16 }, "id": 5, "options": { @@ -493,7 +510,7 @@ "h": 8, "w": 14, "x": 0, - "y": 20 + "y": 24 }, "id": 6, "options": { @@ -592,7 +609,7 @@ "h": 8, "w": 14, "x": 0, - "y": 28 + "y": 32 }, "id": 7, "options": { @@ -639,6 +656,6 @@ "timezone": "browser", "title": "Firewood Dashboard", "uid": "adxfhfmwx5ypsc", - "version": 6, + "version": 3, "weekStart": "" } \ No newline at end of file diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 6e5c93f32ecb..e04902b468bb 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -16,17 +16,15 @@ use firewood::logger::debug; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use pretty_duration::pretty_duration; -use rand::Rng as _; use sha2::{Digest, Sha256}; -use std::collections::HashMap; use std::error::Error; use std::net::{Ipv4Addr, SocketAddr}; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; -use firewood::db::{Batch, BatchOp, Db, DbConfig}; +use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; -use firewood::v2::api::{Db as _, DbView, Proposal as _}; +use firewood::v2::api::{Db as _, Proposal as _}; #[derive(Parser, Debug)] struct Args { @@ -95,11 +93,8 @@ async fn main() -> Result<(), Box> { for key in 0..args.number_of_batches { let batch = generate_inserts(key * keys, args.batch_size).collect(); - let verify = get_keys_to_verify(&batch, args.read_verify_percent); - let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; - verify_keys(&db, verify).await?; } let duration = start.elapsed(); @@ -196,35 +191,3 @@ fn generate_updates( .collect::>() .into_iter() } - -fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>> { - if pct == 0 { - HashMap::new() - } else { - batch - .iter() - .filter(|_last_key| rand::thread_rng().gen_range(0..=(100 - pct)) == 0) - .map(|op| { - if let BatchOp::Put { key, value } = op { - (key.clone(), value.clone().into_boxed_slice()) - } else { - unreachable!() - } - }) - .collect() - } -} - -async fn verify_keys( - db: &impl firewood::v2::api::Db, - verify: HashMap, Box<[u8]>>, -) -> Result<(), firewood::v2::api::Error> { - if !verify.is_empty() { - let hash = db.root_hash().await?.expect("root hash should exist"); - let revision = db.revision(hash).await?; - for (key, value) in verify { - assert_eq!(Some(value), revision.val(key).await?); - } - } - Ok(()) -} From 50aa2b15d657f1d44836a7a1dccd3d11bb1dcd29 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 20 Sep 2024 11:33:37 -1000 Subject: [PATCH 0578/1053] Add all_hashes to API In the benchmark, we render the oldest hash and show the length of the hash array returned by all_hashes --- benchmark/src/main.rs | 7 +++++++ firewood/src/db.rs | 4 ++++ firewood/src/manager.rs | 8 ++++++++ firewood/src/v2/api.rs | 3 +++ firewood/src/v2/emptydb.rs | 4 ++++ 5 files changed, 26 insertions(+) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index e04902b468bb..4593b2c5adec 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -113,6 +113,13 @@ async fn main() -> Result<(), Box> { return Ok(()); } + let all_hashes = db.all_hashes().await?; + println!( + "Database has {} hashes (oldest {:?})", + all_hashes.len(), + all_hashes.first().expect("one hash must exist") + ); + // batches consist of: // 1. 25% deletes from low // 2. 25% new insertions from high diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d4bedda68b42..c60049de5722 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -140,6 +140,10 @@ where Ok(self.manager.read().expect("poisoned lock").root_hash()?) } + async fn all_hashes(&self) -> Result, api::Error> { + Ok(self.manager.read().expect("poisoned lock").all_hashes()) + } + async fn propose<'p, K: KeyType, V: ValueType>( &'p self, batch: api::Batch, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index ca5c59ef9dd2..1e404ded668d 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -92,6 +92,14 @@ impl RevisionManager { Ok(manager) } + pub fn all_hashes(&self) -> Vec { + self.historical + .iter() + .filter_map(|r| r.kind.root_hash()) + .chain(self.proposals.iter().filter_map(|p| p.kind.root_hash())) + .collect() + } + /// Commit a proposal /// To commit a proposal involves a few steps: /// 1. Commit check. diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 7f3980d6df11..a1570adde1e1 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -141,6 +141,9 @@ pub trait Db { /// Get the hash of the most recently committed version async fn root_hash(&self) -> Result, Error>; + /// Get all the hashes available + async fn all_hashes(&self) -> Result, Error>; + /// Propose a change to the database via a batch /// /// This proposal assumes it is based off the most recently diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index f3ff0c77e08a..cd171cb65351 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -52,6 +52,10 @@ impl Db for EmptyDb { data, )) } + + async fn all_hashes(&self) -> Result, Error> { + Ok(vec![]) + } } #[async_trait] From 47ba00578ff8cd13d77eaf9a03e4b055fe7d03e1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 24 Sep 2024 09:55:02 -1000 Subject: [PATCH 0579/1053] Add license header check info --- .github/check-license-headers.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 90c67096bc93..58dd0588aa64 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -16,7 +16,8 @@ "libaio/**", "docs/**", "CODEOWNERS", - "CONTRIBUTING.md" + "CONTRIBUTING.md", + "benchmark/**", ], "license": "./.github/license-header.txt" }, From ec7946cc8a834d348e0851f8167472b7564122db Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 24 Sep 2024 09:55:28 -1000 Subject: [PATCH 0580/1053] Fix comment It was true but misleading --- storage/src/node/branch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index dd1abe7f2e50..4b8c9790fb8b 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -10,7 +10,7 @@ use std::fmt::{Debug, Error as FmtError, Formatter}; /// A child of a branch node. pub enum Child { /// There is a child at this index, but we haven't hashed it - /// or written it to storage yet. + /// or allocated space in storage for it yet. Node(Node), /// We know the child's address and hash. AddressWithHash(LinearAddress, TrieHash), From b4ab1d985dce6029853ca85876ace78b29a5d7a7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 09:06:03 -1000 Subject: [PATCH 0581/1053] Refactor logging Move logging into storage, add some trace messages for debugging --- benchmark/Cargo.toml | 5 +---- benchmark/src/main.rs | 8 ++++++-- firewood/Cargo.toml | 3 +-- firewood/src/lib.rs | 3 ++- firewood/src/manager.rs | 10 ++++++++-- storage/Cargo.toml | 4 ++++ storage/src/lib.rs | 3 +++ {firewood => storage}/src/logger.rs | 1 + storage/src/nodestore.rs | 8 ++++++++ 9 files changed, 34 insertions(+), 11 deletions(-) rename {firewood => storage}/src/logger.rs (92%) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 21a12e706bf7..2119ca50e7ae 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -12,12 +12,9 @@ metrics-util = "0.17.0" metrics-exporter-prometheus = "0.15.3" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" -log = { version = "0.4.20", optional = true } pretty-duration = "0.1.1" tikv-jemallocator = "0.6.0" - -[features] -logger = ["log"] +env_logger = "0.11.5" [profile.release] panic = "abort" diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 4593b2c5adec..d878fd5d89c1 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -12,7 +12,7 @@ // use clap::Parser; -use firewood::logger::debug; +use firewood::logger::{debug, trace}; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use pretty_duration::pretty_duration; @@ -48,6 +48,8 @@ struct Args { revisions: usize, #[arg(short = 'l', long, default_value_t = 3000)] prometheus_port: u16, + #[arg(short, long)] + test_name: Option, } #[global_allocator] @@ -57,6 +59,8 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; async fn main() -> Result<(), Box> { let args = Args::parse(); + env_logger::init(); + let builder = PrometheusBuilder::new(); builder .with_http_listener(SocketAddr::new( @@ -152,7 +156,7 @@ fn generate_inserts(start: u64, count: u64) -> impl Iterator committed.reap_deleted(&oldest)?, + Err(original) => { + // TODO: try reaping the next revision + warn!("Oldest revision could not be reaped; still referenced"); + self.historical.push_front(original); + } } } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 825609d85f8e..b004e5ee37f9 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -17,7 +17,11 @@ integer-encoding = "4.0.0" arc-swap = "1.7.1" lru = "0.12.4" metrics = "0.23.0" +log = { version = "0.4.20", optional = true } [dev-dependencies] rand = "0.8.5" test-case = "3.3.1" + +[features] +logger = ["log"] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0250cb7bfa37..dfc1c28bce35 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -17,6 +17,9 @@ mod node; mod nodestore; mod trie_hash; +/// Logger module for handling logging functionality +pub mod logger; + // re-export these so callers don't need to know where they are pub use hashednode::{hash_node, hash_preimage, Hashable, Preimage, ValueDigest}; pub use linear::{ReadableStorage, WritableStorage}; diff --git a/firewood/src/logger.rs b/storage/src/logger.rs similarity index 92% rename from firewood/src/logger.rs rename to storage/src/logger.rs index 75eeec375279..21779044190e 100644 --- a/firewood/src/logger.rs +++ b/storage/src/logger.rs @@ -14,6 +14,7 @@ pub use noop_logger::{debug, error, info, trace, warn}; #[cfg(not(feature = "logger"))] mod noop_logger { #[macro_export] + /// A noop logger, when the logger feature is disabled macro_rules! noop { ($($arg:tt)+) => {}; } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 28c2b154101d..93dc48bbe55b 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -3,6 +3,7 @@ use arc_swap::access::DynAccess; use arc_swap::ArcSwap; +use crate::logger::trace; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -318,6 +319,7 @@ impl NodeStore { /// Marks the node at `addr` as deleted in this proposal. pub fn delete_node(&mut self, addr: LinearAddress) { + trace!("Pending delete at {addr:?}"); self.kind.deleted.push(addr); } @@ -393,11 +395,13 @@ impl NodeStore, S> { } // Return the address of the newly allocated block. + trace!("Allocating from free list: addr: {free_stored_area_addr:?}, size: {}", AREA_SIZES[index]); return Ok(Some((free_stored_area_addr, index as AreaIndex))); } // No free blocks in this list, try the next size up. } + trace!("No free blocks of sufficient size {index} found"); Ok(None) } @@ -407,6 +411,7 @@ impl NodeStore, S> { let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); self.header.size += area_size; debug_assert!(addr.get() % 8 == 0); + trace!("Allocating from end: addr: {:?}, size: {}", addr, area_size); Ok((addr, index)) } @@ -451,6 +456,7 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; + trace!("Deleting node at {addr:?} of size {}", AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. let area: Area = Area::Free(FreeArea { @@ -1074,7 +1080,9 @@ impl NodeStore { pub fn reap_deleted(&mut self, oldest: &NodeStore) -> Result<(), Error> { self.storage .invalidate_cached_nodes(oldest.kind.deleted.iter()); + trace!("There are {} nodes to reap", oldest.kind.deleted.len()); for addr in oldest.kind.deleted.iter() { + trace!("reap {addr}"); self.delete_node(*addr)?; } Ok(()) From 10d503d0958e800d1d2ed1f703dcfa3599caebaa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 09:48:22 -1000 Subject: [PATCH 0582/1053] Refactor benchmarks to add more tests Arguments to run the benchmark have changed and now require a test name Running `target/debug/benchmark --help` Usage: benchmark [OPTIONS] --test-name Options: -b, --batch-size [default: 10000] -n, --number-of-batches [default: 100000] -p, --read-verify-percent [default: 0] -i, --initialize-only Only initialize the database, do not do the insert/delete/update loop -c, --cache-size [default: 1500000] -r, --revisions [default: 128] -l, --prometheus-port [default: 3000] -t, --test-name [possible values: create, ten-k-random] -h, --help Print help --- benchmark/src/create.rs | 32 ++++++ benchmark/src/main.rs | 188 ++++++++++++++---------------------- benchmark/src/tenkrandom.rs | 34 +++++++ 3 files changed, 139 insertions(+), 115 deletions(-) create mode 100644 benchmark/src/create.rs create mode 100644 benchmark/src/tenkrandom.rs diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs new file mode 100644 index 000000000000..1cca5adeb54d --- /dev/null +++ b/benchmark/src/create.rs @@ -0,0 +1,32 @@ +use std::{error::Error, time::Instant}; + +use firewood::{db::Db, v2::api::{Db as _, Proposal as _}}; + +use pretty_duration::pretty_duration; + +use crate::{Args, TestRunner}; + +#[derive(Clone)] +pub struct Create; + +impl TestRunner for Create { + async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + let keys = args.batch_size; + let start = Instant::now(); + + for key in 0..args.number_of_batches { + let batch = Self::generate_inserts(key * keys, args.batch_size).collect(); + + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + } + let duration = start.elapsed(); + println!( + "Generated and inserted {} batches of size {keys} in {}", + args.number_of_batches, + pretty_duration(&duration, None) + ); + + Ok(()) + } +} diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index d878fd5d89c1..26f71b64a3c2 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -15,16 +15,14 @@ use clap::Parser; use firewood::logger::{debug, trace}; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; -use pretty_duration::pretty_duration; use sha2::{Digest, Sha256}; use std::error::Error; use std::net::{Ipv4Addr, SocketAddr}; use std::num::NonZeroUsize; -use std::time::{Duration, Instant}; +use std::time::Duration; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; -use firewood::v2::api::{Db as _, Proposal as _}; #[derive(Parser, Debug)] struct Args { @@ -42,16 +40,75 @@ struct Args { initialize_only: bool, #[arg(short, long, default_value_t = NonZeroUsize::new(1500000).expect("is non-zero"))] cache_size: NonZeroUsize, - #[arg(short, long)] - assume_preloaded_rows: Option, #[arg(short, long, default_value_t = 128)] revisions: usize, #[arg(short = 'l', long, default_value_t = 3000)] prometheus_port: u16, #[arg(short, long)] - test_name: Option, + test_name: TestName, +} + +#[derive(clap::ValueEnum, Clone, Debug)] +enum TestName { + Create, + TenKRandom, +} + +trait TestRunner { + async fn run(&self, db: &Db, args: &Args) -> Result<(), Box>; + fn generate_updates( + start: u64, + count: u64, + low: u64, + ) -> impl Iterator, Vec>> { + let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); + (start..start + count) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + debug!( + "updating {:?} with digest {} to {}", + inner_key, + hex::encode(&digest), + hex::encode(&hash_of_low) + ); + (digest, hash_of_low.clone()) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() + } + fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { + (start..start + count) + .map(|key| { + let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); + debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); + #[allow(clippy::let_and_return)] + digest + }) + .map(|key| BatchOp::Delete { key }) + .collect::>() + .into_iter() + } + fn generate_inserts(start: u64, count: u64) -> impl Iterator, Vec>> { + (start..start + count) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + trace!( + "inserting {:?} with digest {}", + inner_key, + hex::encode(&digest), + ); + (digest.clone(), digest) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() + } } +mod create; +mod tenkrandom; + #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -82,7 +139,7 @@ async fn main() -> Result<(), Box> { .max_revisions(args.revisions) .build(); let cfg = DbConfig::builder() - .truncate(args.assume_preloaded_rows.is_none()) + .truncate(matches!(args.test_name, TestName::Create)) .manager(mgrcfg) .build(); @@ -90,115 +147,16 @@ async fn main() -> Result<(), Box> { .await .expect("db initiation should succeed"); - let keys = args.batch_size; - let start = Instant::now(); - - if args.assume_preloaded_rows.is_none() { - for key in 0..args.number_of_batches { - let batch = generate_inserts(key * keys, args.batch_size).collect(); - - let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; + match args.test_name { + TestName::Create => { + let runner = create::Create; + runner.run(&db, &args).await?; + } + TestName::TenKRandom => { + let runner = tenkrandom::TenKRandom; + runner.run(&db, &args).await?; } - - let duration = start.elapsed(); - println!( - "Generated and inserted {} batches of size {keys} in {}", - args.number_of_batches, - pretty_duration(&duration, None) - ); - } - - let current_hash = db.root_hash().await?.expect("root hash should exist"); - - if args.initialize_only { - println!("Completed initialization with hash of {:?}", current_hash); - - return Ok(()); - } - - let all_hashes = db.all_hashes().await?; - println!( - "Database has {} hashes (oldest {:?})", - all_hashes.len(), - all_hashes.first().expect("one hash must exist") - ); - - // batches consist of: - // 1. 25% deletes from low - // 2. 25% new insertions from high - // 3. 50% updates from the middle - - println!( - "Starting inner loop with database hash of {:?}", - current_hash - ); - - let mut low = 0; - let mut high = args - .assume_preloaded_rows - .unwrap_or(args.number_of_batches * args.batch_size); - let twenty_five_pct = args.batch_size / 4; - - loop { - let batch: Vec> = generate_inserts(high, twenty_five_pct) - .chain(generate_deletes(low, twenty_five_pct)) - .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) - .collect(); - let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; - low += twenty_five_pct; - high += twenty_five_pct; } + Ok(()) } -fn generate_inserts(start: u64, count: u64) -> impl Iterator, Vec>> { - (start..start + count) - .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); - trace!( - "inserting {:?} with digest {}", - inner_key, - hex::encode(&digest), - ); - (digest.clone(), digest) - }) - .map(|(key, value)| BatchOp::Put { key, value }) - .collect::>() - .into_iter() -} - -fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { - (start..start + count) - .map(|key| { - let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); - debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); - #[allow(clippy::let_and_return)] - digest - }) - .map(|key| BatchOp::Delete { key }) - .collect::>() - .into_iter() -} - -fn generate_updates( - start: u64, - count: u64, - low: u64, -) -> impl Iterator, Vec>> { - let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); - (start..start + count) - .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); - debug!( - "updating {:?} with digest {} to {}", - inner_key, - hex::encode(&digest), - hex::encode(&hash_of_low) - ); - (digest, hash_of_low.clone()) - }) - .map(|(key, value)| BatchOp::Put { key, value }) - .collect::>() - .into_iter() -} diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs new file mode 100644 index 000000000000..6a3679d9ee00 --- /dev/null +++ b/benchmark/src/tenkrandom.rs @@ -0,0 +1,34 @@ +use std::error::Error; + +use firewood::{ + db::{BatchOp, Db}, + v2::api::{Db as _, Proposal as _}, +}; + +use crate::{Args, TestRunner}; + +#[derive(Clone)] +pub struct TenKRandom; + +impl TestRunner for TenKRandom { + async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + let mut low = 0; + let mut high = args.number_of_batches * args.batch_size; + let twenty_five_pct = args.batch_size / 4; + + loop { + let batch: Vec> = Self::generate_inserts(high, twenty_five_pct) + .chain(Self::generate_deletes(low, twenty_five_pct)) + .chain(Self::generate_updates( + low + high / 2, + twenty_five_pct * 2, + low, + )) + .collect(); + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + low += twenty_five_pct; + high += twenty_five_pct; + } + } +} From 9c17f9d3a49ea1d311f93610b1519c986fd574d8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 10:37:09 -1000 Subject: [PATCH 0583/1053] Implement zipf benchmark --- benchmark/Cargo.toml | 4 +++ benchmark/README.md | 4 +-- benchmark/src/create.rs | 6 +++-- benchmark/src/main.rs | 43 ++++++------------------------- benchmark/src/tenkrandom.rs | 51 +++++++++++++++++++++++++++++-------- benchmark/src/zipf.rs | 49 +++++++++++++++++++++++++++++++++++ firewood/Cargo.toml | 3 ++- 7 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 benchmark/src/zipf.rs diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 2119ca50e7ae..2e9104168b8f 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -15,6 +15,10 @@ rand = "0.8.5" pretty-duration = "0.1.1" tikv-jemallocator = "0.6.0" env_logger = "0.11.5" +zipf = "7.0.1" + +[features] +logger = ["firewood/logger"] [profile.release] panic = "abort" diff --git a/benchmark/README.md b/benchmark/README.md index 1ea95b40d416..fa23cc4e0f1a 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -18,7 +18,7 @@ To install the Firewood Benchmark, follow these steps: 1. Clone the repository: `git clone https://github.com/ava-labs/firewood.git` 2. Navigate to the firewood directory: `cd firewood` 3. Build the executable: `cargo build --release` -4. Execute the benchmark: `nohup time cargo run --release bin benchmark` +4. Execute the benchmark: `nohup time cargo run --release bin benchmark -- --test-name create` As the benchmark is running, statistics for prometheus are availble on port 3000 (by default). @@ -57,7 +57,7 @@ command line options. To pre-create the database, use the following command: ``` -nohup time cargo run --release --bin benchmark -- --initialize-only +nohup time cargo run --release --bin benchmark -- --test-name create ``` Then, you can look at nohup.out and see how long the database took to initialize. Then, to run diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index 1cca5adeb54d..f0bc3c4147dd 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -1,6 +1,8 @@ -use std::{error::Error, time::Instant}; +use std::error::Error; +use std::time::Instant; -use firewood::{db::Db, v2::api::{Db as _, Proposal as _}}; +use firewood::db::Db; +use firewood::v2::api::{Db as _, Proposal as _}; use pretty_duration::pretty_duration; diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 26f71b64a3c2..75cfae884a50 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -12,7 +12,7 @@ // use clap::Parser; -use firewood::logger::{debug, trace}; +use firewood::logger::trace; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; @@ -52,43 +52,12 @@ struct Args { enum TestName { Create, TenKRandom, + Zipf, } trait TestRunner { async fn run(&self, db: &Db, args: &Args) -> Result<(), Box>; - fn generate_updates( - start: u64, - count: u64, - low: u64, - ) -> impl Iterator, Vec>> { - let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); - (start..start + count) - .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); - debug!( - "updating {:?} with digest {} to {}", - inner_key, - hex::encode(&digest), - hex::encode(&hash_of_low) - ); - (digest, hash_of_low.clone()) - }) - .map(|(key, value)| BatchOp::Put { key, value }) - .collect::>() - .into_iter() - } - fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { - (start..start + count) - .map(|key| { - let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); - debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); - #[allow(clippy::let_and_return)] - digest - }) - .map(|key| BatchOp::Delete { key }) - .collect::>() - .into_iter() - } + fn generate_inserts(start: u64, count: u64) -> impl Iterator, Vec>> { (start..start + count) .map(|inner_key| { @@ -108,6 +77,7 @@ trait TestRunner { mod create; mod tenkrandom; +mod zipf; #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -156,7 +126,10 @@ async fn main() -> Result<(), Box> { let runner = tenkrandom::TenKRandom; runner.run(&db, &args).await?; } + TestName::Zipf => { + let runner = zipf::Zipf; + runner.run(&db, &args).await?; + } } Ok(()) } - diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 6a3679d9ee00..9cc41223c5b9 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -1,13 +1,13 @@ use std::error::Error; -use firewood::{ - db::{BatchOp, Db}, - v2::api::{Db as _, Proposal as _}, -}; +use firewood::db::{BatchOp, Db}; +use firewood::logger::debug; +use firewood::v2::api::{Db as _, Proposal as _}; use crate::{Args, TestRunner}; +use sha2::{Digest, Sha256}; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct TenKRandom; impl TestRunner for TenKRandom { @@ -18,12 +18,8 @@ impl TestRunner for TenKRandom { loop { let batch: Vec> = Self::generate_inserts(high, twenty_five_pct) - .chain(Self::generate_deletes(low, twenty_five_pct)) - .chain(Self::generate_updates( - low + high / 2, - twenty_five_pct * 2, - low, - )) + .chain(generate_deletes(low, twenty_five_pct)) + .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) .collect(); let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; @@ -32,3 +28,36 @@ impl TestRunner for TenKRandom { } } } +fn generate_updates( + start: u64, + count: u64, + low: u64, +) -> impl Iterator, Vec>> { + let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); + (start..start + count) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + debug!( + "updating {:?} with digest {} to {}", + inner_key, + hex::encode(&digest), + hex::encode(&hash_of_low) + ); + (digest, hash_of_low.clone()) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() +} +fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { + (start..start + count) + .map(|key| { + let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); + debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); + #[allow(clippy::let_and_return)] + digest + }) + .map(|key| BatchOp::Delete { key }) + .collect::>() + .into_iter() +} diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs new file mode 100644 index 000000000000..54fc50bba503 --- /dev/null +++ b/benchmark/src/zipf.rs @@ -0,0 +1,49 @@ +use crate::{Args, TestRunner}; +use firewood::db::{BatchOp, Db}; +use firewood::logger::debug; +use firewood::v2::api::{Db as _, Proposal as _}; +use rand::prelude::Distribution as _; +use rand::thread_rng; +use sha2::{Digest, Sha256}; +use std::error::Error; + +#[derive(Clone)] +pub struct Zipf; + +impl TestRunner for Zipf { + async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + let rows = (args.number_of_batches * args.batch_size) as usize; + let zipf = zipf::ZipfDistribution::new(rows, 1.0).unwrap(); + + for batch_id in 0.. { + let batch: Vec> = + generate_updates(batch_id, args.batch_size as usize, &zipf).collect(); + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + } + unreachable!() + } +} +fn generate_updates( + batch_id: u32, + batch_size: usize, + zipf: &zipf::ZipfDistribution, +) -> impl Iterator, Vec>> { + let hash_of_batch_id = Sha256::digest(batch_id.to_ne_bytes()).to_vec(); + let rng = thread_rng(); + zipf.sample_iter(rng) + .take(batch_size) + .map(|inner_key| { + let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + debug!( + "updating {:?} with digest {} to {}", + inner_key, + hex::encode(&digest), + hex::encode(&hash_of_batch_id) + ); + (digest, hash_of_batch_id.clone()) + }) + .map(|(key, value)| BatchOp::Put { key, value }) + .collect::>() + .into_iter() +} diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index c0802bbd87ba..aa0d522a8497 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -31,12 +31,13 @@ bincode = "1.3.3" log = "0.4.20" test-case = "3.3.1" integer-encoding = "4.0.0" -io-uring = {version = "0.6", optional = true } +io-uring = {version = "0.7", optional = true } [features] default = [] nightly = [] iouring = ["io-uring"] +logger = ["storage/logger"] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} From a4ef7b538412f12d7270400ab6e08fa510b5232f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 11:02:47 -1000 Subject: [PATCH 0584/1053] Fix lint warnings --- .github/check-license-headers.yaml | 1 + benchmark/src/create.rs | 3 +++ benchmark/src/tenkrandom.rs | 3 +++ benchmark/src/zipf.rs | 3 +++ storage/src/nodestore.rs | 12 +++++++++--- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 58dd0588aa64..534995df7826 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -34,6 +34,7 @@ "*/Cargo.toml", "libaio/**", "docs/**", + "benchmark/**", "CODEOWNERS", "CONTRIBUTING.md" ], diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index f0bc3c4147dd..f8181248b898 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::error::Error; use std::time::Instant; diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 9cc41223c5b9..18657b6d7ecf 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::error::Error; use firewood::db::{BatchOp, Db}; diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 54fc50bba503..473d9bf650ae 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use crate::{Args, TestRunner}; use firewood::db::{BatchOp, Db}; use firewood::logger::debug; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 93dc48bbe55b..efc348b71d21 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,9 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::logger::trace; use arc_swap::access::DynAccess; use arc_swap::ArcSwap; -use crate::logger::trace; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -395,7 +395,10 @@ impl NodeStore, S> { } // Return the address of the newly allocated block. - trace!("Allocating from free list: addr: {free_stored_area_addr:?}, size: {}", AREA_SIZES[index]); + trace!( + "Allocating from free list: addr: {free_stored_area_addr:?}, size: {}", + AREA_SIZES[index] + ); return Ok(Some((free_stored_area_addr, index as AreaIndex))); } // No free blocks in this list, try the next size up. @@ -456,7 +459,10 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; - trace!("Deleting node at {addr:?} of size {}", AREA_SIZES[area_size_index as usize]); + trace!( + "Deleting node at {addr:?} of size {}", + AREA_SIZES[area_size_index as usize] + ); // The area that contained the node is now free. let area: Area = Area::Free(FreeArea { From ab1e0864614e57a14e10d9be3292317c3add10f3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 14:00:53 -1000 Subject: [PATCH 0585/1053] Implement single row benchmark Made some other changes as follows: - Added a 'maxperf' profile to add maximum link time optimizations - Changed the benchmark to use subcommands - Updated instructions to open port 3000 for remote grafana --- Cargo.toml | 7 +++++ README.md | 3 ++ benchmark/Cargo.toml | 6 +--- benchmark/README.md | 44 +++++++++++++++++++++------- benchmark/src/create.rs | 3 +- benchmark/src/main.rs | 64 ++++++++++++++++++++++++++++------------- benchmark/src/single.rs | 39 +++++++++++++++++++++++++ benchmark/src/zipf.rs | 49 +++++++++++++++++++++++++++---- 8 files changed, 173 insertions(+), 42 deletions(-) create mode 100644 benchmark/src/single.rs diff --git a/Cargo.toml b/Cargo.toml index b6590957f6ac..e6de218a2992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,10 @@ resolver = "2" [profile.release] debug = true + +[profile.maxperf] +panic = "abort" +codegen-units = 1 +lto = "fat" +debug = false +inherits = "release" diff --git a/README.md b/README.md index 34f6b8afdf58..6e0e20ae3881 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release --example simple`. +For maximum performance, use `cargo run --maxperf` instead, which enables maximum +link time compiler optimizations, but takes a lot longer to compile. + ## Logging If you want logging, enable the `logging` feature flag, and then set RUST\_LOG accordingly. diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 2e9104168b8f..9580a7353dcb 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -16,11 +16,7 @@ pretty-duration = "0.1.1" tikv-jemallocator = "0.6.0" env_logger = "0.11.5" zipf = "7.0.1" +log = "0.4.20" [features] logger = ["firewood/logger"] - -[profile.release] -panic = "abort" -codegen-units = 1 -lto = "fat" diff --git a/benchmark/README.md b/benchmark/README.md index fa23cc4e0f1a..400909045a60 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -3,22 +3,37 @@ Welcome to the Firewood Benchmark! This repository contains the benchmarking code and resources for the Firewood project. ## Table of Contents + - [Introduction](#introduction) +- [Benchmark Description](#benchmark-description) - [Installation](#installation) - [Usage](#usage) -- [Contributing](#contributing) -- [License](#license) ## Introduction + The Firewood Benchmark is a performance testing suite designed to evaluate the performance of the Firewood project. It includes a set of benchmarks that measure various aspects of the project's performance, such as execution time, memory usage, and scalability. +## Benchmark Description + +There are currently three different benchmarks, as follows: + +1. `tenkrandom` which does transactions of size 10k, 5k updates, 2.5k inserts, and 2.5k deletes +2. `zipf` which uses a zipf distribution across the database to perform updates +3. `single` which only updates a single row (row 1) repeatedly in a tiny transactoin + +There is also a `create` benchmark which creates the database to begin with. The defaults will create +a 10M row database. If you want a larger one, increase the number of batches. + ## Installation + To install the Firewood Benchmark, follow these steps: 1. Clone the repository: `git clone https://github.com/ava-labs/firewood.git` 2. Navigate to the firewood directory: `cd firewood` 3. Build the executable: `cargo build --release` -4. Execute the benchmark: `nohup time cargo run --release bin benchmark -- --test-name create` +4. Create the benchmark database: `nohup time cargo run --profile maxperf --bin benchmark -- create`. For a larger database, add `--number-of-batches=10000` for a 100M row database (each batch by default is 10K rows) before the subcommand 'create'. +5. \[Optional] Save the benchmark database in rev_db +6. Run the benchmark you want: `nohup time cargo run --profile maxperf --bin benchmark -- NAME` (selecting NAME from the list above). If you're not using the default database size, make sure you specify the number of batches here as well. As the benchmark is running, statistics for prometheus are availble on port 3000 (by default). @@ -30,39 +45,46 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a b. Click on 'Ubuntu' in the quick start section c. Set the instance type to m5d.2xlarge d. Set your key pair - e. Check 'Allow HTTP traffic from the internet' for grafana + e. Open SSH, HTTP, and port 3000 so prometheus can be probed remotely (we use a 'firewood-bench' security group) f. Configure storage to 20GiB disk - g. [optional] Save money by selecting 'spot instance' in advanced + g. \[optional] Save money by selecting 'spot instance' in advanced h. Launch the instance 3. ssh ubuntu@AWS-IP 4. Run the script in setup.sh on the instance as root -5. Log in to grafana on http://AWS-IP +5. Log in to grafana on a. username: admin, password: admin 6. When prompted, change the password (firewood_is_fast) 7. On the left panel, click "Data Sources" a. Select "Prometheus" - b. For the URL, use http://localhost:9090 + b. For the URL, use c. click "Save and test" 8. On the left panel, click Dashboards a. On the right top pulldown, click New->Import b. Import the dashboard from the Grafana-dashboard.json file c. Set the data source to Prometheus -9. You may also want to install a stock dashboard from [here](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) +9. \[optional] Install a stock dashboard from [here](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) ## Usage + Since the benchmark is in two phases, you may want to create the database first and then examine the steady-state performance second. This can easily be accomplished with a few command line options. To pre-create the database, use the following command: -``` -nohup time cargo run --release --bin benchmark -- --test-name create +```sh +nohup time cargo run --profile maxperf --bin benchmark -- create ``` Then, you can look at nohup.out and see how long the database took to initialize. Then, to run the second phase, use: +```sh +nohup time cargo run --profile maxperf --bin benchmark -- -zipf ``` -nohup time cargo run --release --bin benchmark -- --assume-preloaded-rows=1000000000 + +If you're looking for detailed logging, there are some command line options to enable it. For example, to enable debug logging for the single benchmark, you can use the following: + +```sh +cargo run --profile release --bin benchmark -- -l debug single ``` diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index f8181248b898..f7f32e289ce4 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -6,6 +6,7 @@ use std::time::Instant; use firewood::db::Db; use firewood::v2::api::{Db as _, Proposal as _}; +use log::info; use pretty_duration::pretty_duration; @@ -26,7 +27,7 @@ impl TestRunner for Create { proposal.commit().await?; } let duration = start.elapsed(); - println!( + info!( "Generated and inserted {} batches of size {keys} in {}", args.number_of_batches, pretty_duration(&duration, None) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 75cfae884a50..28467a069c55 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -11,8 +11,9 @@ // 3. 50% of batch size is updating rows in the middle, but setting the value to the hash of the first row inserted // -use clap::Parser; +use clap::{Parser, Subcommand}; use firewood::logger::trace; +use log::LevelFilter; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; @@ -28,31 +29,48 @@ use firewood::manager::RevisionManagerConfig; struct Args { #[arg(short, long, default_value_t = 10000)] batch_size: u64, - #[arg(short, long, default_value_t = 100000)] + #[arg(short, long, default_value_t = 1000)] number_of_batches: u64, - #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] - read_verify_percent: u16, - #[arg( - short, - long, - help = "Only initialize the database, do not do the insert/delete/update loop" - )] - initialize_only: bool, #[arg(short, long, default_value_t = NonZeroUsize::new(1500000).expect("is non-zero"))] cache_size: NonZeroUsize, #[arg(short, long, default_value_t = 128)] revisions: usize, - #[arg(short = 'l', long, default_value_t = 3000)] + #[arg(short = 'p', long, default_value_t = 3000)] prometheus_port: u16, - #[arg(short, long)] + + #[clap(flatten)] + global_opts: GlobalOpts, + + #[clap(subcommand)] test_name: TestName, } -#[derive(clap::ValueEnum, Clone, Debug)] +#[derive(clap::Args, Debug)] +struct GlobalOpts { + #[arg( + long, + short = 'l', + required = false, + help = "Log level. Respects RUST_LOG.", + value_name = "LOG_LEVEL", + num_args = 1, + value_parser = ["debug", "info"], + default_value_t = String::from("info"), + )] + log_level: String, +} + +mod create; +mod single; +mod tenkrandom; +mod zipf; + +#[derive(Debug, Subcommand)] enum TestName { Create, TenKRandom, - Zipf, + Zipf(zipf::Args), + Single, } trait TestRunner { @@ -75,10 +93,6 @@ trait TestRunner { } } -mod create; -mod tenkrandom; -mod zipf; - #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -86,7 +100,13 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; async fn main() -> Result<(), Box> { let args = Args::parse(); - env_logger::init(); + env_logger::Builder::new() + .filter_level(match args.global_opts.log_level.as_str() { + "debug" => LevelFilter::Debug, + "info" => LevelFilter::Info, + _ => LevelFilter::Info, + }) + .init(); let builder = PrometheusBuilder::new(); builder @@ -126,10 +146,14 @@ async fn main() -> Result<(), Box> { let runner = tenkrandom::TenKRandom; runner.run(&db, &args).await?; } - TestName::Zipf => { + TestName::Zipf(_) => { let runner = zipf::Zipf; runner.run(&db, &args).await?; } + TestName::Single => { + let runner = single::Single; + runner.run(&db, &args).await?; + } } Ok(()) } diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs new file mode 100644 index 000000000000..1c83edd55c51 --- /dev/null +++ b/benchmark/src/single.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::TestRunner; +use firewood::db::{BatchOp, Db}; +use firewood::v2::api::{Db as _, Proposal as _}; +use log::debug; +use pretty_duration::pretty_duration; +use sha2::{Digest, Sha256}; +use std::error::Error; +use std::time::Instant; + +#[derive(Clone)] +pub struct Single; + +impl TestRunner for Single { + async fn run(&self, db: &Db, _args: &crate::Args) -> Result<(), Box> { + let start = Instant::now(); + let inner_key = Sha256::digest(0u64.to_ne_bytes()).to_vec(); + + for batch_id in 0.. { + let batch = vec![BatchOp::Put { + key: inner_key.clone(), + value: vec![batch_id as u8], + }]; + let proposal = db.propose(batch).await.expect("proposal should succeed"); + proposal.commit().await?; + + if log::log_enabled!(log::Level::Debug) && batch_id % 1000 == 999 { + debug!( + "completed {} batches in {}", + 1 + batch_id, + pretty_duration(&start.elapsed(), None) + ); + } + } + unreachable!() + } +} diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 473d9bf650ae..e0d090795a39 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -1,28 +1,67 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{Args, TestRunner}; +use crate::TestRunner; use firewood::db::{BatchOp, Db}; -use firewood::logger::debug; use firewood::v2::api::{Db as _, Proposal as _}; +use log::{debug, trace}; +use pretty_duration::pretty_duration; use rand::prelude::Distribution as _; use rand::thread_rng; use sha2::{Digest, Sha256}; +use std::collections::HashSet; use std::error::Error; +use std::time::Instant; + +#[derive(clap::Args, Debug)] +pub struct Args { + #[arg(short, long, help = "zipf exponent", default_value_t = 1.2)] + exponent: f64, +} #[derive(Clone)] pub struct Zipf; impl TestRunner for Zipf { - async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { + let exponent = if let crate::TestName::Zipf(args) = &args.test_name { + args.exponent + } else { + unreachable!() + }; let rows = (args.number_of_batches * args.batch_size) as usize; - let zipf = zipf::ZipfDistribution::new(rows, 1.0).unwrap(); + let zipf = zipf::ZipfDistribution::new(rows, exponent).unwrap(); + let start = Instant::now(); for batch_id in 0.. { let batch: Vec> = generate_updates(batch_id, args.batch_size as usize, &zipf).collect(); + if log::log_enabled!(log::Level::Debug) { + let mut distinct = HashSet::new(); + for op in &batch { + match op { + BatchOp::Put { key, value: _ } => { + distinct.insert(key); + } + _ => unreachable!(), + } + } + debug!( + "inserting batch {} with {} distinct data values", + batch_id, + distinct.len() + ); + } let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; + + if log::log_enabled!(log::Level::Debug) { + debug!( + "completed batch {} in {}", + batch_id, + pretty_duration(&start.elapsed(), None) + ); + } } unreachable!() } @@ -38,7 +77,7 @@ fn generate_updates( .take(batch_size) .map(|inner_key| { let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); - debug!( + trace!( "updating {:?} with digest {} to {}", inner_key, hex::encode(&digest), From b21f7af5c1beb165c86f55a16ac12600fc35a507 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 14:10:09 -1000 Subject: [PATCH 0586/1053] Additional README tweaks --- benchmark/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/README.md b/benchmark/README.md index 400909045a60..c79404aa05ae 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -31,7 +31,7 @@ To install the Firewood Benchmark, follow these steps: 1. Clone the repository: `git clone https://github.com/ava-labs/firewood.git` 2. Navigate to the firewood directory: `cd firewood` 3. Build the executable: `cargo build --release` -4. Create the benchmark database: `nohup time cargo run --profile maxperf --bin benchmark -- create`. For a larger database, add `--number-of-batches=10000` for a 100M row database (each batch by default is 10K rows) before the subcommand 'create'. +4. Create the benchmark database: `nohup time cargo run --profile maxperf --bin benchmark -- create`. For a larger database, add `--number-of-batches=10000` before the subcommand 'create' for a 100M row database (each batch by default is 10K rows). 5. \[Optional] Save the benchmark database in rev_db 6. Run the benchmark you want: `nohup time cargo run --profile maxperf --bin benchmark -- NAME` (selecting NAME from the list above). If you're not using the default database size, make sure you specify the number of batches here as well. From 4f97e40895dfe04b622f31a411fb6f3340101dc3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 14:26:21 -1000 Subject: [PATCH 0587/1053] Use boxes instead of Vec This saves a tiny bit of memory --- benchmark/src/main.rs | 7 +++++-- benchmark/src/tenkrandom.rs | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 28467a069c55..ee31ac801311 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -76,10 +76,13 @@ enum TestName { trait TestRunner { async fn run(&self, db: &Db, args: &Args) -> Result<(), Box>; - fn generate_inserts(start: u64, count: u64) -> impl Iterator, Vec>> { + fn generate_inserts( + start: u64, + count: u64, + ) -> impl Iterator, Box<[u8]>>> { (start..start + count) .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + let digest: Box<[u8]> = Sha256::digest(inner_key.to_ne_bytes())[..].into(); trace!( "inserting {:?} with digest {}", inner_key, diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 18657b6d7ecf..327c191baa4c 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -35,11 +35,11 @@ fn generate_updates( start: u64, count: u64, low: u64, -) -> impl Iterator, Vec>> { - let hash_of_low = Sha256::digest(low.to_ne_bytes()).to_vec(); +) -> impl Iterator, Box<[u8]>>> { + let hash_of_low: Box<[u8]> = Sha256::digest(low.to_ne_bytes())[..].into(); (start..start + count) .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + let digest = Sha256::digest(inner_key.to_ne_bytes())[..].into(); debug!( "updating {:?} with digest {} to {}", inner_key, @@ -52,10 +52,10 @@ fn generate_updates( .collect::>() .into_iter() } -fn generate_deletes(start: u64, count: u64) -> impl Iterator, Vec>> { +fn generate_deletes(start: u64, count: u64) -> impl Iterator, Box<[u8]>>> { (start..start + count) .map(|key| { - let digest = Sha256::digest(key.to_ne_bytes()).to_vec(); + let digest = Sha256::digest(key.to_ne_bytes())[..].into(); debug!("deleting {:?} with digest {}", key, hex::encode(&digest)); #[allow(clippy::let_and_return)] digest From da32cf70cab6de6f3cec9db0b19b8ef198e74f86 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 15:15:02 -1000 Subject: [PATCH 0588/1053] Instruction updates --- benchmark/cloud-config.txt | 89 ++++++++++++++++++++++++++++++++++++++ benchmark/setup.sh | 25 +++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 benchmark/cloud-config.txt diff --git a/benchmark/cloud-config.txt b/benchmark/cloud-config.txt new file mode 100644 index 000000000000..508e97c5fa59 --- /dev/null +++ b/benchmark/cloud-config.txt @@ -0,0 +1,89 @@ +#cloud-config +write_files: +- path: /etc/systemd/system/grafana-server.service.d/override.conf + owner: root:root + permissions: '0644' + content: | + [Service] + CapabilityBoundingSet=CAP_NET_BIND_SERVICE + AmbientCapabilities=CAP_NET_BIND_SERVICE + PrivateUsers=false +- path: /run/firewood/build-firewood.sh + permissions: '0755' + content: | + #!/bin/bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + . "$HOME/.cargo/env" + git clone https://github.com/ava-labs/firewood.git + cd firewood + git checkout rkuris/prometheus + cargo build --release +- path: /etc/prometheus/prometheus.yml.addon + content: |2 + - job_name: firewood + static_configs: + - targets: ['localhost:3000'] +apt: + sources: + grafana: + source: deb https://apt.grafana.com stable main + key: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + =J9CI + -----END PGP PUBLIC KEY BLOCK----- + +package_update: true +package_upgrade: true +packages: +- git +- protobuf-compiler +- build-essential +- apt-transport-https +- grafana +- prometheus +- net-tools +runcmd: + - [ perl, -pi, -e, "s/^;?http_port = .*/http_port = 80/", /etc/grafana/grafana.ini ] + - [ dd, if=/etc/prometheus/prometheus.yml.addon, of=/etc/prometheus/prometheus.yml, conv=notrunc, oflag=append ] + - [ systemctl, daemon-reload ] + - [ systemctl, enable, grafana-server ] + - [ systemctl, start, grafana-server ] + - [ sudo, -l, -u, ubuntu, /run/firewood/build-firewood.sh ] diff --git a/benchmark/setup.sh b/benchmark/setup.sh index 94c77dc37fbc..77fafafef487 100644 --- a/benchmark/setup.sh +++ b/benchmark/setup.sh @@ -1,3 +1,5 @@ +#### run these commands from the root user #### + mkdir -p /etc/apt/keyrings/ wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list @@ -39,15 +41,30 @@ if [ -n "$NVME_DEV" ]; then echo "$NVME_DEV $NVME_MOUNT ext4 noatime 0 0" >> /etc/fstab mkdir "$NVME_MOUNT/ubuntu" chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" - ln -s /home/ubuntu/firewood "$NVME_MOUNT/ubuntu/firewood" + ln -s "$NVME_MOUNT/ubuntu/firewood" /home/ubuntu/firewood fi + + +#### you can switch to the ubuntu user here #### curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y . "$HOME/.cargo/env" git clone https://github.com/ava-labs/firewood.git cd firewood git checkout rkuris/prometheus -cargo build --release +cargo build --profile maxperf + +# 10M rows: +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 create +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 zipf +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 single + +# 50M rows: +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 create +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 zipf +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 single -# nohup time cargo run --release --bin benchmark -- -b 10000 -c 1500000 -n 100000 & -nohup time cargo run --release --bin benchmark -- -b 100000 -c 1500000 -n 1000 -i & +# 100M rows: +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 create +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 zipf +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 single From 8016d0aecff0c42231dc542d90b579f0a909b9a8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 27 Sep 2024 16:49:58 -1000 Subject: [PATCH 0589/1053] More fixes to startup script We have to mark /mnt as valid in prometheus-node-exporter, otherwise the debian installation just excludes it. Also cleaned up checkout process, all of firewood directory is now on the SSD. --- benchmark/setup.sh | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/benchmark/setup.sh b/benchmark/setup.sh index 77fafafef487..d53f1da13675 100644 --- a/benchmark/setup.sh +++ b/benchmark/setup.sh @@ -26,8 +26,12 @@ cat >> /etc/prometheus/prometheus.yml <> /etc/default/prometheus-node-exporter <> /etc/fstab - mkdir "$NVME_MOUNT/ubuntu" - chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" + mkdir -p "$NVME_MOUNT/ubuntu/firewood" + chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" "$NVME_MOUNT/ubuntu/firewood" ln -s "$NVME_MOUNT/ubuntu/firewood" /home/ubuntu/firewood fi @@ -49,22 +53,24 @@ fi curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y . "$HOME/.cargo/env" -git clone https://github.com/ava-labs/firewood.git cd firewood +git clone https://github.com/ava-labs/firewood.git . git checkout rkuris/prometheus cargo build --profile maxperf +#### stop here, these commands are run by hand #### + # 10M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 create -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 zipf -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 single +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 create & +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 zipf & +nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 single & # 50M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 create -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 zipf -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 single +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 create & +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 zipf & +nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 single & # 100M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 create -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 zipf -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 single +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 create & +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 zipf & +nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 single & From 04940608d152dc5c48824deb39d29956f7e6608d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 29 Sep 2024 08:53:01 -1000 Subject: [PATCH 0590/1053] Fix fwdctl create and friends Database create and doing nothing afterwards left you with an empty file. This diff forces the header to disk during database creation. Also, the default for `create` should be truncate. Maybe the option should go away, as I can't see why you'd want to do 'create' but no truncate. Also, fwdctl assumed firewood creates directories, which caused the test framework to fail. --- firewood/src/manager.rs | 5 +++++ fwdctl/src/create.rs | 4 ++-- fwdctl/src/dump.rs | 2 +- fwdctl/tests/cli.rs | 10 ++-------- storage/src/nodestore.rs | 40 ++++++++++++++-------------------------- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index ae023a2ba91c..0adde072c01a 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -90,6 +90,11 @@ impl RevisionManager { nodestore.clone(), ); } + + if truncate { + nodestore.flush_header()?; + } + Ok(manager) } diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index d901c8bef90c..2d55d103a25f 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -20,10 +20,10 @@ pub struct Options { required = false, value_parser = value_parser!(bool), default_missing_value = "false", - default_value_t = false, + default_value_t = true, value_name = "TRUNCATE", help = "Whether to truncate the DB when opening it. If set, the DB will be reset and all its - existing contents will be lost. [default: false]" + existing contents will be lost" )] pub truncate: bool, diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 9072d423d31f..2da57540721f 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -29,7 +29,7 @@ pub struct Options { help = "Start dumping from this key (inclusive)." )] pub start_key: Option, - #[arg(short, long, help = "Print the keys and values in hex format.")] + #[arg(short = 'x', long, help = "Print the keys and values in hex format.")] pub hex: bool, } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 053c7de32b2c..6ce8628edfed 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; -use std::fs::remove_dir_all; +use std::fs::remove_file; use std::path::PathBuf; const PRG: &str = "fwdctl"; @@ -13,7 +13,7 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { - if let Err(e) = remove_dir_all(tmpdb::path()) { + if let Err(e) = remove_file(tmpdb::path()) { eprintln!("failed to delete testing dir: {e}"); return Err(anyhow!(e)); } @@ -36,7 +36,6 @@ fn fwdctl_prints_version() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_creates_database() -> Result<()> { @@ -51,7 +50,6 @@ fn fwdctl_creates_database() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_insert_successful() -> Result<()> { @@ -78,7 +76,6 @@ fn fwdctl_insert_successful() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_get_successful() -> Result<()> { @@ -114,7 +111,6 @@ fn fwdctl_get_successful() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_delete_successful() -> Result<()> { @@ -149,7 +145,6 @@ fn fwdctl_delete_successful() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_root_hash() -> Result<()> { @@ -183,7 +178,6 @@ fn fwdctl_root_hash() -> Result<()> { Ok(()) } -#[ignore = "unimplemented"] #[test] #[serial] fn fwdctl_dump() -> Result<()> { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index efc348b71d21..76a542504a28 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -849,18 +849,7 @@ impl NodeStore, S> { } } -impl NodeStore { - /// Persist the freelist from this proposal to storage. - pub fn flush_freelist(&self) -> Result<(), Error> { - // Write the free lists to storage - let free_list_bytes = bincode::serialize(&self.header.free_lists) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; - self.storage - .write(free_list_offset, free_list_bytes.as_slice())?; - Ok(()) - } - +impl NodeStore { /// Persist the header from this proposal to storage. pub fn flush_header(&self) -> Result<(), Error> { let header_bytes = bincode::serialize(&self.header).map_err(|e| { @@ -874,6 +863,19 @@ impl NodeStore { Ok(()) } +} + +impl NodeStore { + /// Persist the freelist from this proposal to storage. + pub fn flush_freelist(&self) -> Result<(), Error> { + // Write the free lists to storage + let free_list_bytes = bincode::serialize(&self.header.free_lists) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; + self.storage + .write(free_list_offset, free_list_bytes.as_slice())?; + Ok(()) + } /// Persist all the nodes of a proposal to storage. pub fn flush_nodes(&self) -> Result<(), Error> { @@ -923,20 +925,6 @@ impl NodeStore, S> { Ok(()) } - /// Persist the header from this proposal to storage. - pub fn flush_header(&self) -> Result<(), Error> { - let header_bytes = bincode::serialize(&self.header).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to serialize header: {}", e), - ) - })?; - - self.storage.write(0, header_bytes.as_slice())?; - - Ok(()) - } - /// Persist all the nodes of a proposal to storage. pub fn flush_nodes(&self) -> Result<(), Error> { for (addr, (area_size_index, node)) in self.kind.new.iter() { From 59fb5ece52efda12ab1d41acba48603e309e3a5b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 29 Sep 2024 09:16:00 -1000 Subject: [PATCH 0591/1053] Fix fwdctl 'dump' start-key The start key was an unnamed option, now is '--start-key'. Also fixed up temporary underscores in the key/value streaming code. --- firewood/src/merkle.rs | 13 ++++++------ firewood/src/stream.rs | 46 +++++++++++++++++++++--------------------- fwdctl/src/dump.rs | 9 +++++---- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index c4028b7e47ed..26c26d26fbcf 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -212,16 +212,17 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } - pub(crate) fn _key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { + fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { MerkleKeyValueStream::from(&self.nodestore) } - pub(crate) fn _key_value_iter_from_key(&self, key: Key) -> MerkleKeyValueStream<'_, T> { + fn key_value_iter_from_key>(&self, key: K) -> MerkleKeyValueStream<'_, T> { // TODO danlaine: change key to &[u8] - MerkleKeyValueStream::_from_key(&self.nodestore, key) + MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) } - pub(super) async fn _range_proof( + #[allow(dead_code)] + pub(super) async fn range_proof( &self, start_key: Option<&[u8]>, end_key: Option<&[u8]>, @@ -238,8 +239,8 @@ impl Merkle { let mut stream = match start_key { // TODO: fix the call-site to force the caller to do the allocation - Some(key) => self._key_value_iter_from_key(key.to_vec().into_boxed_slice()), - None => self._key_value_iter(), + Some(key) => self.key_value_iter_from_key(key.to_vec().into_boxed_slice()), + None => self.key_value_iter(), }; // fetch the first key from the stream diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 15038b2cd4b0..7c17e1507ba4 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -269,9 +269,9 @@ enum MerkleKeyValueStreamState<'a, T> { Initialized { node_iter: MerkleNodeStream<'a, T> }, } -impl<'a, T> From for MerkleKeyValueStreamState<'a, T> { - fn from(key: Key) -> Self { - Self::_Uninitialized(key) +impl<'a, T, K: AsRef<[u8]>> From for MerkleKeyValueStreamState<'a, T> { + fn from(key: K) -> Self { + Self::_Uninitialized(key.as_ref().into()) } } @@ -304,9 +304,9 @@ impl<'a, T: TrieReader> FusedStream for MerkleKeyValueStream<'a, T> { } impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { - pub(super) fn _from_key(merkle: &'a T, key: Key) -> Self { + pub fn from_key>(merkle: &'a T, key: K) -> Self { Self { - state: MerkleKeyValueStreamState::from(key), + state: MerkleKeyValueStreamState::from(key.as_ref()), merkle, } } @@ -716,7 +716,7 @@ mod tests { #[tokio::test] async fn key_value_iterate_empty() { let merkle = create_test_merkle(); - let stream = merkle._key_value_iter_from_key(b"x".to_vec().into_boxed_slice()); + let stream = merkle.key_value_iter_from_key(b"x".to_vec().into_boxed_slice()); check_stream_is_done(stream).await; } @@ -912,8 +912,8 @@ mod tests { } let mut stream = match start { - Some(start) => merkle._key_value_iter_from_key(start.to_vec().into_boxed_slice()), - None => merkle._key_value_iter(), + Some(start) => merkle.key_value_iter_from_key(start.to_vec().into_boxed_slice()), + None => merkle.key_value_iter(), }; // we iterate twice because we should get a None then start over @@ -934,7 +934,7 @@ mod tests { #[tokio::test] async fn key_value_fused_empty() { let merkle = create_test_merkle(); - check_stream_is_done(merkle._key_value_iter()).await; + check_stream_is_done(merkle.key_value_iter()).await; } #[tokio::test] @@ -954,7 +954,7 @@ mod tests { } // Test with no start key - let mut stream = merkle._key_value_iter(); + let mut stream = merkle.key_value_iter(); for i in 0..=max { for j in 0..=max { let expected_key = vec![i, j]; @@ -973,7 +973,7 @@ mod tests { // Test with start key for i in 0..=max { - let mut stream = merkle._key_value_iter_from_key(vec![i].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![i].into_boxed_slice()); for j in 0..=max { let expected_key = vec![i, j]; let expected_value = vec![i, j]; @@ -1017,7 +1017,7 @@ mod tests { merkle.insert(kv, kv.clone().into_boxed_slice()).unwrap(); } - let mut stream = merkle._key_value_iter(); + let mut stream = merkle.key_value_iter(); for kv in key_values.iter() { let next = stream.next().await.unwrap().unwrap(); @@ -1037,7 +1037,7 @@ mod tests { merkle.insert(&key, value.into()).unwrap(); - let mut stream = merkle._key_value_iter(); + let mut stream = merkle.key_value_iter(); assert_eq!(stream.next().await.unwrap().unwrap(), (key, value.into())); } @@ -1055,7 +1055,7 @@ mod tests { merkle.insert(&branch, branch.into()).unwrap(); - let mut stream = merkle._key_value_iter(); + let mut stream = merkle.key_value_iter(); assert_eq!( stream.next().await.unwrap().unwrap(), @@ -1097,7 +1097,7 @@ mod tests { merkle.insert(key, key.clone().into_boxed_slice()).unwrap(); } - let mut stream = merkle._key_value_iter_from_key(vec![intermediate].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); let first = stream.next().await.unwrap().unwrap(); @@ -1143,7 +1143,7 @@ mod tests { let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); let keys = &keys[start..]; - let mut stream = merkle._key_value_iter_from_key(vec![branch_path].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![branch_path].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1191,7 +1191,7 @@ mod tests { let start = keys.iter().position(|key| key == &branch_key).unwrap(); let keys = &keys[start..]; - let mut stream = merkle._key_value_iter_from_key(branch_key.into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(branch_key.into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1221,7 +1221,7 @@ mod tests { let keys = &keys[(missing as usize)..]; - let mut stream = merkle._key_value_iter_from_key(vec![missing].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1247,7 +1247,7 @@ mod tests { merkle.insert(&key, key.clone().into()).unwrap(); }); - let stream = merkle._key_value_iter_from_key(vec![start_key].into_boxed_slice()); + let stream = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); check_stream_is_done(stream).await; } @@ -1268,7 +1268,7 @@ mod tests { }) .collect(); - let mut stream = merkle._key_value_iter_from_key(vec![start_key].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1300,7 +1300,7 @@ mod tests { let keys = &keys[((missing >> 4) as usize)..]; - let mut stream = merkle._key_value_iter_from_key(vec![missing].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); @@ -1319,7 +1319,7 @@ mod tests { let mut merkle = create_test_merkle(); merkle.insert(&key, key.into()).unwrap(); - let stream = merkle._key_value_iter_from_key(greater_key.into()); + let stream = merkle.key_value_iter_from_key(greater_key); check_stream_is_done(stream).await; } @@ -1344,7 +1344,7 @@ mod tests { let keys = &keys[((greatest >> 4) as usize)..]; - let mut stream = merkle._key_value_iter_from_key(vec![greatest].into_boxed_slice()); + let mut stream = merkle.key_value_iter_from_key(vec![greatest].into_boxed_slice()); for key in keys { let next = stream.next().await.unwrap().unwrap(); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 2da57540721f..4cfb286b7e26 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -5,7 +5,7 @@ use clap::Args; use firewood::db::{Db, DbConfig}; use firewood::merkle::Key; use firewood::stream::MerkleKeyValueStream; -use firewood::v2::api::{self, Db as _, DbView}; +use firewood::v2::api::{self, Db as _}; use futures_util::StreamExt; use std::borrow::Cow; @@ -23,6 +23,8 @@ pub struct Options { /// The key to start dumping from (if no key is provided, start from the beginning). /// Defaults to None. #[arg( + short = 's', + long = "start-key", required = false, value_name = "START_KEY", value_parser = key_parser, @@ -44,9 +46,8 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { return Ok(()); }; let latest_rev = db.revision(latest_hash).await?; - latest_rev.val("xxxx".as_bytes()).await?; - let _start_key = opts.start_key.clone().unwrap_or(Box::new([])); - let mut stream = MerkleKeyValueStream::from(&latest_rev); + let start_key = opts.start_key.clone().unwrap_or(Box::new([])); + let mut stream = MerkleKeyValueStream::from_key(&latest_rev, start_key); loop { match stream.next().await { None => break, From a51349b83fc71e9c4e2e6bc73baa2beef642d8f1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 06:00:10 -1000 Subject: [PATCH 0592/1053] Fix visibility errors --- firewood/src/merkle.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 26c26d26fbcf..25b33fbbecbd 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -212,11 +212,11 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } - fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { + pub(super) fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { MerkleKeyValueStream::from(&self.nodestore) } - fn key_value_iter_from_key>(&self, key: K) -> MerkleKeyValueStream<'_, T> { + pub(super) fn key_value_iter_from_key>(&self, key: K) -> MerkleKeyValueStream<'_, T> { // TODO danlaine: change key to &[u8] MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) } @@ -1174,7 +1174,7 @@ mod tests { let merkle = create_in_memory_merkle(); assert!(matches!( - merkle._range_proof(None, None, None).await.unwrap_err(), + merkle.range_proof(None, None, None).await.unwrap_err(), api::Error::RangeProofOnEmptyTrie )); } From 979cf06f27348b29b28e5978ab0a85a139a06c4c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 08:39:04 -1000 Subject: [PATCH 0593/1053] Benchmark: allow for trace and no logging --- benchmark/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index ee31ac801311..58566371e4b1 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -54,7 +54,7 @@ struct GlobalOpts { help = "Log level. Respects RUST_LOG.", value_name = "LOG_LEVEL", num_args = 1, - value_parser = ["debug", "info"], + value_parser = ["trace", "debug", "info", "warn", "none"], default_value_t = String::from("info"), )] log_level: String, @@ -107,6 +107,8 @@ async fn main() -> Result<(), Box> { .filter_level(match args.global_opts.log_level.as_str() { "debug" => LevelFilter::Debug, "info" => LevelFilter::Info, + "trace" => LevelFilter::Trace, + "none" => LevelFilter::Off, _ => LevelFilter::Info, }) .init(); From 0624d252f6460444fb78bfc9fb297e750b92e7d6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 08:40:35 -1000 Subject: [PATCH 0594/1053] Refactor: add read_for_update Whenever we read a node and know it's going to change, we always call read, delete, then clone to create this mutable object. DRY - created a function to do this. --- firewood/src/merkle.rs | 20 ++++++-------------- storage/src/nodestore.rs | 9 +++++++++ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 25b33fbbecbd..09c7b2d8addb 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,7 +16,7 @@ use std::num::NonZeroUsize; use std::sync::Arc; use storage::{ BranchNode, Child, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, LinearAddress, - MutableProposal, NibblesIterator, Node, NodeReader, NodeStore, Path, ReadableStorage, TrieHash, + MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, TrieHash, TrieReader, ValueDigest, }; @@ -153,7 +153,7 @@ impl Merkle { &self.nodestore } - pub(crate) fn read_node(&self, addr: LinearAddress) -> Result, MerkleError> { + fn read_node(&self, addr: LinearAddress) -> Result, MerkleError> { self.nodestore.read_node(addr).map_err(Into::into) } @@ -506,9 +506,7 @@ impl Merkle> { } Some(Child::Node(child)) => child, Some(Child::AddressWithHash(addr, _)) => { - let node = self.nodestore.read_node(addr)?; - self.nodestore.delete_node(addr); - (*node).clone() + self.nodestore.read_for_update(addr)? } }; @@ -651,9 +649,7 @@ impl Merkle> { let mut child = match child { Child::Node(ref mut child_node) => std::mem::take(child_node), Child::AddressWithHash(addr, _) => { - let node = self.nodestore.read_node(*addr)?; - self.nodestore.delete_node(*addr); - (*node).clone() + self.nodestore.read_for_update(*addr)? } }; @@ -720,9 +716,7 @@ impl Merkle> { } Some(Child::Node(node)) => node, Some(Child::AddressWithHash(addr, _)) => { - let node = self.nodestore.read_node(addr)?; - self.nodestore.delete_node(addr); - (*node).clone() + self.nodestore.read_for_update(addr)? } }; @@ -770,9 +764,7 @@ impl Merkle> { }), ), Child::AddressWithHash(addr, _) => { - let node = self.nodestore.read_node(*addr)?; - self.nodestore.delete_node(*addr); - (*node).clone() + self.nodestore.read_for_update(*addr)? } }; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 76a542504a28..be998ed1f3d6 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -323,6 +323,15 @@ impl NodeStore { self.kind.deleted.push(addr); } + /// Reads a node for update, marking it as deleted in this proposal. + /// We get an arc from cache (reading it from disk if necessary) then + /// copy/clone the node and return it. + pub fn read_for_update(&mut self, addr: LinearAddress) -> Result { + self.delete_node(addr); + let arc_wrapped_node = self.read_node(addr)?; + Ok((*arc_wrapped_node).clone()) + } + /// Returns the root of this proposal. pub fn mut_root(&mut self) -> &mut Option { &mut self.kind.root From a0689299272771d612d396cd27cdfeeebabb11e4 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 08:42:34 -1000 Subject: [PATCH 0595/1053] Code cleanup Removed code we will never use. Lint fixes too. --- storage/src/linear/filebacked.rs | 3 --- storage/src/nodestore.rs | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 94573cae62e6..529a56f5d6b3 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -1,9 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -// during development only -#![allow(dead_code)] - // This synchronous file layer is a simple implementation of what we // want to do for I/O. This uses a [Mutex] lock around a simple `File` // object. Instead, we probably should use an IO system that can perform multiple diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index be998ed1f3d6..be322dfc0597 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -220,15 +220,6 @@ impl NodeStore { pub fn new_empty_committed(storage: Arc) -> Result { let header = NodeStoreHeader::new(); - // let header_bytes = bincode::serialize(&header).map_err(|e| { - // Error::new( - // ErrorKind::InvalidData, - // format!("Failed to serialize header: {}", e), - // ) - // })?; - - // storage.write(0, header_bytes.as_slice())?; - Ok(Self { header, storage, @@ -1085,7 +1076,6 @@ impl NodeStore { .invalidate_cached_nodes(oldest.kind.deleted.iter()); trace!("There are {} nodes to reap", oldest.kind.deleted.len()); for addr in oldest.kind.deleted.iter() { - trace!("reap {addr}"); self.delete_node(*addr)?; } Ok(()) From 93bb3880b440858e62f555cebe484cfe2c1f5947 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 08:45:22 -1000 Subject: [PATCH 0596/1053] trace cleanups We should always print the area index, not the size Could print both of them I guess, but should be the same thing printed each time. --- storage/src/nodestore.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index be322dfc0597..708d0e6f079f 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -397,7 +397,7 @@ impl NodeStore, S> { // Return the address of the newly allocated block. trace!( "Allocating from free list: addr: {free_stored_area_addr:?}, size: {}", - AREA_SIZES[index] + index ); return Ok(Some((free_stored_area_addr, index as AreaIndex))); } @@ -414,7 +414,7 @@ impl NodeStore, S> { let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); self.header.size += area_size; debug_assert!(addr.get() % 8 == 0); - trace!("Allocating from end: addr: {:?}, size: {}", addr, area_size); + trace!("Allocating from end: addr: {:?}, size: {}", addr, index); Ok((addr, index)) } @@ -422,9 +422,7 @@ impl NodeStore, S> { fn stored_len(node: &Node) -> u64 { // TODO: calculate length without serializing! let area: Area<&Node, FreeArea> = Area::Node(node); - let area_bytes = bincode::serialize(&area) - .map_err(|e| Error::new(ErrorKind::InvalidData, e)) - .expect("fixme"); + let area_bytes = bincode::serialize(&area).expect("fixme"); // +1 for the size index byte // TODO: do a better job packing the boolean (freed) with the possible node sizes @@ -459,10 +457,7 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; - trace!( - "Deleting node at {addr:?} of size {}", - AREA_SIZES[area_size_index as usize] - ); + trace!("Deleting node at {addr:?} of size {}", area_size_index); // The area that contained the node is now free. let area: Area = Area::Free(FreeArea { From 5fceeec20e357f269c3d9523094be30ceda2e5f2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 08:47:56 -1000 Subject: [PATCH 0597/1053] Don't make byte ordering assumptions This fix avoids the magic of adding 2 bytes and assuming the layout of the structure in serde will "do the right thing". It was not the right thing when compiling with maximum performance optimizations. --- firewood/src/merkle.rs | 9 ++++++--- storage/src/nodestore.rs | 29 +++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 09c7b2d8addb..1ad76ea2c6c7 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,8 +16,8 @@ use std::num::NonZeroUsize; use std::sync::Arc; use storage::{ BranchNode, Child, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, LinearAddress, - MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, TrieHash, - TrieReader, ValueDigest, + MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, TrieHash, TrieReader, + ValueDigest, }; use thiserror::Error; @@ -216,7 +216,10 @@ impl Merkle { MerkleKeyValueStream::from(&self.nodestore) } - pub(super) fn key_value_iter_from_key>(&self, key: K) -> MerkleKeyValueStream<'_, T> { + pub(super) fn key_value_iter_from_key>( + &self, + key: K, + ) -> MerkleKeyValueStream<'_, T> { // TODO danlaine: change key to &[u8] MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 708d0e6f079f..0b9e7bbfcb79 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -376,19 +376,36 @@ impl NodeStore, S> { // Find the smallest free list that can fit this size. let index = area_size_to_index(n)?; - // rustify: rewrite using self.header.free_lists.iter_mut().find(...) + // TODO: rustify: rewrite using self.header.free_lists.iter_mut().find(...) for index in index as usize..NUM_AREA_SIZES { // Get the first free block of sufficient size. if let Some(free_stored_area_addr) = self.header.free_lists[index] { - // Update the free list head. - // Skip the index byte and Area discriminant byte if let Some(free_head) = self.storage.free_list_cache(free_stored_area_addr) { + trace!("free_head@{free_stored_area_addr}(cached): {free_head:?} size:{index}"); self.header.free_lists[index] = free_head; } else { - let free_area_addr = free_stored_area_addr.get() + 2; + let free_area_addr = free_stored_area_addr.get(); let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: FreeArea = bincode::deserialize_from(free_head_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_head: StoredArea> = + bincode::deserialize_from(free_head_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let StoredArea { + area: Area::Free(free_head), + area_size_index: read_index, + } = free_head + else { + return Err(Error::new( + ErrorKind::InvalidData, + "Attempted to read a non-free area", + )); + }; + debug_assert_eq!(read_index as usize, index); + + trace!( + "free_head@{}( read ): {:?} size:{index}", + free_area_addr, + free_head + ); // Update the free list to point to the next free block. self.header.free_lists[index] = free_head.next_free_block; From fd11103b36d2549b8598123bd72c11cd1c639d67 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 14:27:06 -1000 Subject: [PATCH 0598/1053] Don't save the serialized bytes This results in a 7% increase in performance for loading smaller databases. --- storage/src/node/mod.rs | 12 ++++++++ storage/src/nodestore.rs | 63 +++++++++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index b27bace62d12..6a19af8ce3bf 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -35,6 +35,18 @@ impl Default for Node { } } +impl From for Node { + fn from(branch: BranchNode) -> Self { + Node::Branch(Box::new(branch)) + } +} + +impl From for Node { + fn from(leaf: LeafNode) -> Self { + Node::Leaf(leaf) + } +} + impl Node { /// Returns the partial path of the node. pub fn partial_path(&self) -> &Path { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 0b9e7bbfcb79..3362980e492e 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -4,6 +4,7 @@ use crate::logger::trace; use arc_swap::access::DynAccess; use arc_swap::ArcSwap; +use bincode::Options as _; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -437,15 +438,28 @@ impl NodeStore, S> { /// Returns the length of the serialized area for a node. fn stored_len(node: &Node) -> u64 { + struct ByteCount(u64); + + impl Write for ByteCount { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 += buf.len() as u64; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + // TODO: calculate length without serializing! let area: Area<&Node, FreeArea> = Area::Node(node); - let area_bytes = bincode::serialize(&area).expect("fixme"); - - // +1 for the size index byte - // TODO: do a better job packing the boolean (freed) with the possible node sizes - // A reasonable option is use a i8 and negative values indicate it's freed whereas positive values are non-free - // This would still allow for 127 different sizes - area_bytes.len() as u64 + 1 + let mut bytecounter = ByteCount(0); + let mut serializer = bincode::Serializer::new( + &mut bytecounter, + bincode::DefaultOptions::new().with_fixint_encoding(), + ); + area.serialize(&mut serializer).expect("fixme"); + bytecounter.0 + 1 // Add 1 for the index byte } /// Returns an address that can be used to store the given `node` and updates @@ -1097,8 +1111,9 @@ impl NodeStore { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::linear::memory::MemStore; + use crate::{linear::memory::MemStore, BranchNode, LeafNode}; use arc_swap::access::DynGuard; + use test_case::test_case; use super::*; @@ -1253,4 +1268,36 @@ mod tests { let empty_free_list: FreeLists = Default::default(); assert_eq!(header.free_lists, empty_free_list); } + + #[test_case(BranchNode { + partial_path: Path::from([6, 7, 8]), + value: Some(vec![9, 10, 11].into_boxed_slice()), + children: [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), [0; 32].into()))], + }; "branch node with 1 child")] + #[test_case( + Node::Leaf(LeafNode { + partial_path: Path::from([0, 1, 2]), + value: Box::new([3, 4, 5]), + }); "leaf node")] + + fn test_serialized_len>(node: N) { + let node = node.into(); + + let area_size = NodeStore::, MemStore>::stored_len(&node); + + let area: Area<&Node, FreeArea> = Area::Node(&node); + let actually_serialized = bincode::serialize(&area).unwrap().len() as u64; + assert_eq!(area_size, actually_serialized + 1); + + let leaf: Node = Node::Leaf(LeafNode { + partial_path: Path::from([0, 1, 2]), + value: Box::new([3, 4, 5]), + }); + + let area_size = NodeStore::, MemStore>::stored_len(&leaf); + + let area: Area<&Node, FreeArea> = Area::Node(&leaf); + let actually_serialized = bincode::serialize(&area).unwrap().len() as u64; + assert_eq!(area_size, actually_serialized + 1); + } } From 84691537760e3a72fb7f1345707f5392e43bd841 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 1 Oct 2024 14:38:51 -1000 Subject: [PATCH 0599/1053] BUGFIX: Revision history should never grow Since it's possible that if the `try_unwrap` can't get an exclusive access to the oldest revision, the historical array might grow by 1 or more. When this happens, it will stay that long since it doesn't remove more than one old revision at a time. Also cleaned up the comments, and added some notes re: `try_unwrap` --- firewood/src/manager.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 0adde072c01a..8d2a4674d487 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -115,24 +115,24 @@ impl RevisionManager { /// The address of the root node and the root hash is also persisted. /// Note that this is *not* a write ahead log. /// It only contains the address of the nodes that are deleted, which should be very small. - /// 3. Set last committed revision. + /// 3. Revision reaping. If more than the maximum number of revisions are kept in memory, the + /// oldest revision is reaped. + /// 4. Set last committed revision. /// Set last committed revision in memory. /// Another commit can start after this but before the node flush is completed. - /// 4. Free list flush. + /// 5. Free list flush. /// Persist/write the free list header. /// The free list is flushed first to prevent future allocations from using the space allocated to this proposal. /// This should be done in a single write since the free list headers are small, and must be persisted to disk before starting the next step. - /// 5. Node flush. + /// 6. Node flush. /// Persist/write all the nodes to disk. /// Note that since these are all freshly allocated nodes, they will never be referred to by any prior commit. /// After flushing all nodes, the file should be flushed to disk (fsync) before performing the next step. - /// 6. Root move. + /// 7. Root move. /// The root address on disk must be updated. /// This write can be delayed, but would mean that recovery will not roll forward to this revision. - /// 7. Proposal Cleanup. + /// 8. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. - /// 8. Revision reaping. - /// If more than the configurable number of revisions is available, the oldest revision can be forgotten. pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); @@ -147,15 +147,20 @@ impl RevisionManager { // 2. Persist delete list for this committed revision to disk for recovery - // 2.5 Take the deleted entries from the oldest revision and mark them as free for this revision + // 3 Take the deleted entries from the oldest revision and mark them as free for this revision // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free - if self.historical.len() > self.max_revisions { + while self.historical.len() > self.max_revisions { let oldest = self.historical.pop_front().expect("must be present"); if let Some(oldest_hash) = oldest.kind.root_hash() { self.by_hash.remove(&oldest_hash); } + // This `try_unwrap` is safe because nobody else will call `try_unwrap` on this Arc + // in a different thread, so we don't have to worry about the race condition where + // the Arc we get back is not usable as indicated in the docs for `try_unwrap`. + // This guarantee is there because we have a `&mut self` reference to the manager, so + // the compiler guarantees we are the only one using this manager. match Arc::try_unwrap(oldest) { Ok(oldest) => committed.reap_deleted(&oldest)?, Err(original) => { @@ -166,7 +171,7 @@ impl RevisionManager { } } - // 3. Set last committed revision + // 4. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical.push_back(committed.clone()); if let Some(hash) = committed.kind.root_hash() { @@ -174,16 +179,16 @@ impl RevisionManager { } // TODO: We could allow other commits to start here using the pending list - // 4. Free list flush, which will prevent allocating on top of the nodes we are about to write + // 5. Free list flush, which will prevent allocating on top of the nodes we are about to write proposal.flush_freelist()?; - // 5. Node flush + // 6. Node flush proposal.flush_nodes()?; - // 6. Root move + // 7. Root move proposal.flush_header()?; - // 7. Proposal Cleanup + // 8. Proposal Cleanup // first remove the committing proposal from the list of outstanding proposals self.proposals.retain(|p| !Arc::ptr_eq(&proposal, p)); From ad972fe199a3db5939a8caf576cfa77c7a8aeb96 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Sep 2024 16:05:36 -1000 Subject: [PATCH 0600/1053] Further serialization/deserialization improvements There is a `serialized_size` function in bincode, so let's use it. Change to varint encoding to reduce image sizes. --- storage/src/nodestore.rs | 162 ++++++++------------------------------- 1 file changed, 30 insertions(+), 132 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 3362980e492e..fd619d5ee6bc 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -4,7 +4,7 @@ use crate::logger::trace; use arc_swap::access::DynAccess; use arc_swap::ArcSwap; -use bincode::Options as _; +use bincode::{DefaultOptions, Options as _}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -151,7 +151,8 @@ impl NodeStore { fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { let mut area_stream = self.storage.stream_from(addr.get())?; - let index: AreaIndex = bincode::deserialize_from(&mut area_stream) + let index: AreaIndex = DefaultOptions::new() + .deserialize_from(&mut area_stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let size = *AREA_SIZES.get(index as usize).ok_or(Error::new( @@ -174,7 +175,8 @@ impl NodeStore { let addr = addr.get() + 1; // Skip the index byte let area_stream = self.storage.stream_from(addr)?; - let area: Area = bincode::deserialize_from(area_stream) + let area: Area = DefaultOptions::new() + .deserialize_from(area_stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; match area { @@ -193,7 +195,8 @@ impl NodeStore { pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; - let header: NodeStoreHeader = bincode::deserialize_from(&mut stream) + let header: NodeStoreHeader = DefaultOptions::new() + .deserialize_from(&mut stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; drop(stream); @@ -387,9 +390,9 @@ impl NodeStore, S> { } else { let free_area_addr = free_stored_area_addr.get(); let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: StoredArea> = - bincode::deserialize_from(free_head_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_head: StoredArea> = DefaultOptions::new() + .deserialize_from(free_head_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let StoredArea { area: Area::Free(free_head), area_size_index: read_index, @@ -438,28 +441,9 @@ impl NodeStore, S> { /// Returns the length of the serialized area for a node. fn stored_len(node: &Node) -> u64 { - struct ByteCount(u64); - - impl Write for ByteCount { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0 += buf.len() as u64; - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } - } - - // TODO: calculate length without serializing! let area: Area<&Node, FreeArea> = Area::Node(node); - let mut bytecounter = ByteCount(0); - let mut serializer = bincode::Serializer::new( - &mut bytecounter, - bincode::DefaultOptions::new().with_fixint_encoding(), - ); - area.serialize(&mut serializer).expect("fixme"); - bytecounter.0 + 1 // Add 1 for the index byte + + DefaultOptions::new().serialized_size(&area).expect("fixme") + 1 } /// Returns an address that can be used to store the given `node` and updates @@ -500,8 +484,9 @@ impl NodeStore { area, }; - let stored_area_bytes = - bincode::serialize(&stored_area).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let stored_area_bytes = DefaultOptions::new() + .serialize(&stored_area) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage.write(addr.into(), &stored_area_bytes)?; @@ -878,13 +863,9 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. pub fn flush_header(&self) -> Result<(), Error> { - let header_bytes = bincode::serialize(&self.header).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to serialize header: {}", e), - ) - })?; - + let header_bytes = DefaultOptions::new() + .serialize(&self.header) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage.write(0, header_bytes.as_slice())?; Ok(()) @@ -895,7 +876,8 @@ impl NodeStore { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = bincode::serialize(&self.header.free_lists) + let free_list_bytes = DefaultOptions::new() + .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; self.storage @@ -911,7 +893,8 @@ impl NodeStore { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = bincode::serialize(&stored_area) + let stored_area_bytes = DefaultOptions::new() + .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage @@ -943,7 +926,8 @@ impl NodeStore, S> { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = bincode::serialize(&self.header.free_lists) + let free_list_bytes = DefaultOptions::new() + .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; self.storage @@ -959,7 +943,8 @@ impl NodeStore, S> { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = bincode::serialize(&stored_area) + let stored_area_bytes = DefaultOptions::new() + .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage @@ -1178,84 +1163,6 @@ mod tests { } } - // TODO add new tests - // #[test] - // fn test_create() { - // let memstore = Arc::new(MemStore::new(vec![])); - // let mut node_store = NodeStore::new_empty_proposal(memstore); - - // let leaf = Node::Leaf(LeafNode { - // partial_path: Path::from([0, 1, 2]), - // value: Box::new([3, 4, 5]), - // }); - - // let leaf_addr = node_store.create_node(leaf.clone()).unwrap(); - // let got_leaf = node_store.kind.new.get(&leaf_addr).unwrap(); - // assert_eq!(**got_leaf, leaf); - // let got_leaf = node_store.read_node(leaf_addr).unwrap(); - // assert_eq!(*got_leaf, leaf); - - // // The header should be unchanged in storage - // { - // let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - // let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); - // assert_eq!(header.version, Version::new()); - // let empty_free_lists: FreeLists = Default::default(); - // assert_eq!(header.free_lists, empty_free_lists); - // assert_eq!(header.root_address, None); - // } - - // // Leaf should go right after the header - // assert_eq!(leaf_addr.get(), NodeStoreHeader::SIZE); - - // // Create another node - // let branch = Node::Branch(Box::new(BranchNode { - // partial_path: Path::from([6, 7, 8]), - // value: Some(vec![9, 10, 11].into_boxed_slice()), - // children: Default::default(), - // })); - - // let old_size = node_store.header.size; - // let branch_addr = node_store.create_node(branch.clone()).unwrap(); - // assert!(node_store.header.size > old_size); - - // // branch should go after leaf - // assert!(branch_addr.get() > leaf_addr.get()); - - // // The header should be unchanged in storage - // { - // let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - // let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); - // assert_eq!(header.version, Version::new()); - // let empty_free_lists: FreeLists = Default::default(); - // assert_eq!(header.free_lists, empty_free_lists); - // assert_eq!(header.root_address, None); - // } - // } - - // #[test] - // fn test_delete() { - // let memstore = Arc::new(MemStore::new(vec![])); - // let mut node_store = NodeStore::new_empty_proposal(memstore); - - // // Create a leaf - // let leaf = Node::Leaf(LeafNode { - // partial_path: Path::new(), - // value: Box::new([1]), - // }); - // let leaf_addr = node_store.create_node(leaf.clone()).unwrap(); - - // // Delete the node - // node_store.delete_node(leaf_addr).unwrap(); - // assert!(node_store.kind.deleted.contains(&leaf_addr)); - - // // Create a new node with the same size - // let new_leaf_addr = node_store.create_node(leaf).unwrap(); - - // // The new node shouldn't be at the same address - // assert_ne!(new_leaf_addr, leaf_addr); - // } - #[test] fn test_node_store_new() { let memstore = MemStore::new(vec![]); @@ -1263,7 +1170,9 @@ mod tests { // Check the empty header is written at the start of the ReadableStorage. let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - let header: NodeStoreHeader = bincode::deserialize_from(&mut header_bytes).unwrap(); + let header: NodeStoreHeader = DefaultOptions::new() + .deserialize_from(&mut header_bytes) + .unwrap(); assert_eq!(header.version, Version::new()); let empty_free_list: FreeLists = Default::default(); assert_eq!(header.free_lists, empty_free_list); @@ -1286,18 +1195,7 @@ mod tests { let area_size = NodeStore::, MemStore>::stored_len(&node); let area: Area<&Node, FreeArea> = Area::Node(&node); - let actually_serialized = bincode::serialize(&area).unwrap().len() as u64; - assert_eq!(area_size, actually_serialized + 1); - - let leaf: Node = Node::Leaf(LeafNode { - partial_path: Path::from([0, 1, 2]), - value: Box::new([3, 4, 5]), - }); - - let area_size = NodeStore::, MemStore>::stored_len(&leaf); - - let area: Area<&Node, FreeArea> = Area::Node(&leaf); - let actually_serialized = bincode::serialize(&area).unwrap().len() as u64; + let actually_serialized = DefaultOptions::new().serialize(&area).unwrap().len() as u64; assert_eq!(area_size, actually_serialized + 1); } } From 79bd93ad37b0d18b6a29b3e2eacb94c62dc22c90 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 2 Oct 2024 12:05:04 -1000 Subject: [PATCH 0601/1053] Rustify loop and add space metrics Free space is an issue so lets track the space used and wasted based on power of two algorthm. --- storage/src/nodestore.rs | 93 ++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index fd619d5ee6bc..3d48a16631b2 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -5,9 +5,11 @@ use crate::logger::trace; use arc_swap::access::DynAccess; use arc_swap::ArcSwap; use bincode::{DefaultOptions, Options as _}; +use metrics::{counter, histogram}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; + /// The [NodeStore] handles the serialization of nodes and /// free space management of nodes in the page store. It lays out the format /// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] @@ -378,54 +380,58 @@ impl NodeStore, S> { /// TODO danlaine: If we return a larger area than requested, we should split it. fn allocate_from_freed(&mut self, n: u64) -> Result, Error> { // Find the smallest free list that can fit this size. - let index = area_size_to_index(n)?; - - // TODO: rustify: rewrite using self.header.free_lists.iter_mut().find(...) - for index in index as usize..NUM_AREA_SIZES { + let index_wanted = area_size_to_index(n)?; + + if let Some((index, free_stored_area_addr)) = self + .header + .free_lists + .iter_mut() + .enumerate() + .skip(index_wanted as usize) + .find(|item| item.1.is_some()) + { + let address = free_stored_area_addr + .take() + .expect("impossible due to find earlier"); // Get the first free block of sufficient size. - if let Some(free_stored_area_addr) = self.header.free_lists[index] { - if let Some(free_head) = self.storage.free_list_cache(free_stored_area_addr) { - trace!("free_head@{free_stored_area_addr}(cached): {free_head:?} size:{index}"); - self.header.free_lists[index] = free_head; - } else { - let free_area_addr = free_stored_area_addr.get(); - let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: StoredArea> = DefaultOptions::new() - .deserialize_from(free_head_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - let StoredArea { - area: Area::Free(free_head), - area_size_index: read_index, - } = free_head - else { - return Err(Error::new( - ErrorKind::InvalidData, - "Attempted to read a non-free area", - )); - }; - debug_assert_eq!(read_index as usize, index); - - trace!( - "free_head@{}( read ): {:?} size:{index}", - free_area_addr, - free_head - ); + if let Some(free_head) = self.storage.free_list_cache(address) { + trace!("free_head@{address}(cached): {free_head:?} size:{index}"); + *free_stored_area_addr = free_head; + } else { + let free_area_addr = address.get(); + let free_head_stream = self.storage.stream_from(free_area_addr)?; + let free_head: StoredArea> = DefaultOptions::new() + .deserialize_from(free_head_stream) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let StoredArea { + area: Area::Free(free_head), + area_size_index: read_index, + } = free_head + else { + return Err(Error::new( + ErrorKind::InvalidData, + "Attempted to read a non-free area", + )); + }; + debug_assert_eq!(read_index as usize, index); + + // Update the free list to point to the next free block. + *free_stored_area_addr = free_head.next_free_block; + } - // Update the free list to point to the next free block. - self.header.free_lists[index] = free_head.next_free_block; - } + counter!("firewood.space.reused").increment(AREA_SIZES[index]); + counter!("firewood.space.wasted").increment(AREA_SIZES[index] - n); - // Return the address of the newly allocated block. - trace!( - "Allocating from free list: addr: {free_stored_area_addr:?}, size: {}", - index - ); - return Ok(Some((free_stored_area_addr, index as AreaIndex))); - } - // No free blocks in this list, try the next size up. + // Return the address of the newly allocated block. + trace!( + "Allocating from free list: addr: {address:?}, size: {}", + index + ); + return Ok(Some((address, index as AreaIndex))); } trace!("No free blocks of sufficient size {index} found"); + counter!("firewood.space.notfree").increment(AREA_SIZES[index_wanted as usize]); Ok(None) } @@ -451,6 +457,7 @@ impl NodeStore, S> { /// Also returns the index of the free list the node was allocated from. pub fn allocate_node(&mut self, node: &Node) -> Result<(LinearAddress, AreaIndex), Error> { let stored_area_size = Self::stored_len(node); + histogram!("firewood.node_size").record(stored_area_size as f64); // Attempt to allocate from a free list. // If we can't allocate from a free list, allocate past the existing @@ -473,6 +480,8 @@ impl NodeStore { let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {}", area_size_index); + counter!("firewood.delete_node").increment(1); + counter!("firewood.space.freed").increment(AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. let area: Area = Area::Free(FreeArea { From e1fe9b625d56f7901dbd2b54a1c100c166cdd217 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 4 Oct 2024 11:12:31 -1000 Subject: [PATCH 0602/1053] Switch to varint encoding --- storage/src/nodestore.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 3d48a16631b2..179837eb936c 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -872,7 +872,7 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. pub fn flush_header(&self) -> Result<(), Error> { - let header_bytes = DefaultOptions::new() + let header_bytes = DefaultOptions::new().with_varint_encoding() .serialize(&self.header) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage.write(0, header_bytes.as_slice())?; @@ -885,7 +885,7 @@ impl NodeStore { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new() + let free_list_bytes = DefaultOptions::new().with_varint_encoding() .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; @@ -902,7 +902,7 @@ impl NodeStore { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new() + let stored_area_bytes = DefaultOptions::new().with_varint_encoding() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -935,7 +935,7 @@ impl NodeStore, S> { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new() + let free_list_bytes = DefaultOptions::new().with_varint_encoding() .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; @@ -952,7 +952,7 @@ impl NodeStore, S> { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new() + let stored_area_bytes = DefaultOptions::new().with_varint_encoding() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -1179,7 +1179,7 @@ mod tests { // Check the empty header is written at the start of the ReadableStorage. let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - let header: NodeStoreHeader = DefaultOptions::new() + let header: NodeStoreHeader = DefaultOptions::new().with_varint_encoding() .deserialize_from(&mut header_bytes) .unwrap(); assert_eq!(header.version, Version::new()); From 0c6369bb7cc8b75259a2b6e8678dd64c32b07172 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 6 Oct 2024 07:03:35 -1000 Subject: [PATCH 0603/1053] Lint fixes --- storage/src/nodestore.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 179837eb936c..16366bd48888 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -872,7 +872,8 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. pub fn flush_header(&self) -> Result<(), Error> { - let header_bytes = DefaultOptions::new().with_varint_encoding() + let header_bytes = DefaultOptions::new() + .with_varint_encoding() .serialize(&self.header) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.storage.write(0, header_bytes.as_slice())?; @@ -885,7 +886,8 @@ impl NodeStore { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new().with_varint_encoding() + let free_list_bytes = DefaultOptions::new() + .with_varint_encoding() .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; @@ -902,7 +904,8 @@ impl NodeStore { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new().with_varint_encoding() + let stored_area_bytes = DefaultOptions::new() + .with_varint_encoding() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -935,7 +938,8 @@ impl NodeStore, S> { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new().with_varint_encoding() + let free_list_bytes = DefaultOptions::new() + .with_varint_encoding() .serialize(&self.header.free_lists) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; @@ -952,7 +956,8 @@ impl NodeStore, S> { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new().with_varint_encoding() + let stored_area_bytes = DefaultOptions::new() + .with_varint_encoding() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -1179,7 +1184,8 @@ mod tests { // Check the empty header is written at the start of the ReadableStorage. let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - let header: NodeStoreHeader = DefaultOptions::new().with_varint_encoding() + let header: NodeStoreHeader = DefaultOptions::new() + .with_varint_encoding() .deserialize_from(&mut header_bytes) .unwrap(); assert_eq!(header.version, Version::new()); From d1c94c9db76f34057e72884a1ddc6cd2a71ab130 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 6 Oct 2024 07:04:13 -1000 Subject: [PATCH 0604/1053] Add serialization benchmarks --- storage/Cargo.toml | 5 +++ storage/benches/serializer.rs | 73 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 storage/benches/serializer.rs diff --git a/storage/Cargo.toml b/storage/Cargo.toml index b004e5ee37f9..87b49a0c25fa 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -22,6 +22,11 @@ log = { version = "0.4.20", optional = true } [dev-dependencies] rand = "0.8.5" test-case = "3.3.1" +criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } [features] logger = ["log"] + +[[bench]] +name = "serializer" +harness = false diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs new file mode 100644 index 000000000000..08da2f31f387 --- /dev/null +++ b/storage/benches/serializer.rs @@ -0,0 +1,73 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::num::NonZeroU64; + +use bincode::Options; +use criterion::{criterion_group, criterion_main, Criterion}; +use smallvec::SmallVec; +use storage::{LeafNode, Node, Path}; + +fn leaf(c: &mut Criterion) { + let mut group = c.benchmark_group("leaf"); + let input = Node::Leaf(LeafNode { + partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + value: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice(), + }); + let serializer = bincode::DefaultOptions::new().with_varint_encoding(); + group.bench_with_input("leaf", &input, |b, input| { + b.iter(|| { + serializer.serialize(input).unwrap(); + }) + }); +} + +fn branch(c: &mut Criterion) { + let mut group = c.benchmark_group("branch"); + let mut input = Node::Branch(Box::new(storage::BranchNode { + partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()), + children: [ + Some(storage::Child::AddressWithHash( + NonZeroU64::new(1).unwrap(), + storage::TrieHash::from([0; 32]), + )), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ], + })); + let serializer = bincode::DefaultOptions::new().with_varint_encoding(); + let benchfn = |b: &mut criterion::Bencher, input: &storage::Node| { + b.iter(|| { + serializer.serialize(input).unwrap(); + }) + }; + + group.bench_with_input("1_child+has_value", &input, benchfn); + + input.as_branch_mut().unwrap().value = None; + group.bench_with_input("1_child", &input, benchfn); + let child = input.as_branch().unwrap().children[0].clone(); + + input.as_branch_mut().unwrap().children[1] = child.clone(); + group.bench_with_input("2_child", &input, benchfn); + + input.as_branch_mut().unwrap().children = std::array::from_fn(|_| child.clone()); + group.bench_with_input("16_child", &input, benchfn); +} + +criterion_group!(serializers, leaf, branch); +criterion_main!(serializers); From b49e177a0e3e8800667a0eb6fde530554b1c7f8c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 4 Oct 2024 11:13:36 -1000 Subject: [PATCH 0605/1053] Branch encoding changes Encode the children as a vec of (offset, childaddr, childhash) instead of an array of [childaddr, childhash] --- storage/src/node/branch.rs | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 4b8c9790fb8b..180e150516a0 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use serde::{ser::SerializeStruct as _, Deserialize, Serialize}; +use smallvec::SmallVec; use crate::{LeafNode, LinearAddress, Node, Path, TrieHash}; use std::fmt::{Debug, Error as FmtError, Formatter}; @@ -41,22 +42,20 @@ impl Serialize for BranchNode { state.serialize_field("partial_path", &self.partial_path)?; state.serialize_field("value", &self.value)?; - let mut children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN] = - Default::default(); - - for (i, c) in self.children.iter().enumerate() { - match c { - None => {} + let children: SmallVec<[(u8, LinearAddress, TrieHash); 16]> = self + .children + .iter() + .enumerate() + .filter_map(|(offset, child)| match child { + None => None, Some(Child::Node(_)) => { - return Err(serde::ser::Error::custom( - "node has children in memory. TODO make this impossible.", - )) + panic!("serializing in-memory node for disk storage") } Some(Child::AddressWithHash(addr, hash)) => { - children[i] = Some((*addr, (*hash).clone())) + Some((offset as u8, *addr, (*hash).clone())) } - } - } + }) + .collect(); state.serialize_field("children", &children)?; state.end() @@ -72,16 +71,14 @@ impl<'de> Deserialize<'de> for BranchNode { struct SerializedBranchNode { partial_path: Path, value: Option>, - children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN], + children: SmallVec<[(u8, LinearAddress, TrieHash); 16]>, } let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?; let mut children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); - for (i, c) in s.children.iter().enumerate() { - if let Some((addr, hash)) = c { - children[i] = Some(Child::AddressWithHash(*addr, hash.clone())); - } + for (offset, addr, hash) in s.children.iter() { + children[*offset as usize] = Some(Child::AddressWithHash(*addr, hash.clone())); } Ok(BranchNode { @@ -92,12 +89,6 @@ impl<'de> Deserialize<'de> for BranchNode { } } -// struct SerializedBranchNode<'a> { -// partial_path: &'a Path, -// value: Option<&'a [u8]>, -// children: [Option<(LinearAddress, TrieHash)>; BranchNode::MAX_CHILDREN], -// } - impl Debug for BranchNode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "[Branch")?; From 8d5c50e9fe801bf8e6548e59846d34a444ebf261 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 4 Oct 2024 11:25:12 -1000 Subject: [PATCH 0606/1053] Tests: Use a more random hash The test hash was zero for serialization; now is 0 1 2 3 ... --- storage/src/nodestore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 16366bd48888..c681e16d2c52 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1196,7 +1196,7 @@ mod tests { #[test_case(BranchNode { partial_path: Path::from([6, 7, 8]), value: Some(vec![9, 10, 11].into_boxed_slice()), - children: [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), [0; 32].into()))], + children: [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into()))], }; "branch node with 1 child")] #[test_case( Node::Leaf(LeafNode { From 58178a813004319beed68401134f828a16c8c497 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 6 Oct 2024 08:45:58 -1000 Subject: [PATCH 0607/1053] Perf: Use smallvec to optimize for 16 byte values This changes the value in a LeafNode to a smallvec, size 16. This puts the value on the stack unless it exceeds 16 bytes, in which case it is back on the heap. Roughly 8% performance improvement for serialization. ``` Running benches/serializer.rs (/Users/rkuris/open-source/firewood/target/release/deps/serializer-af8faa5284629abf) leaf/leaf time: [35.693 ns 35.824 ns 35.987 ns] change: [-11.729% -7.8407% -3.6818%] (p = 0.00 < 0.05) Performance has improved. Found 13 outliers among 100 measurements (13.00%) 3 (3.00%) high mild 10 (10.00%) high severe ``` --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 11 ++++++----- storage/benches/serializer.rs | 2 +- storage/src/node/branch.rs | 2 +- storage/src/node/leaf.rs | 3 ++- storage/src/node/mod.rs | 5 +++-- storage/src/nodestore.rs | 3 ++- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index aa0d522a8497..e0908f19117c 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -32,6 +32,7 @@ log = "0.4.20" test-case = "3.3.1" integer-encoding = "4.0.0" io-uring = {version = "0.7", optional = true } +smallvec = "1.6.1" [features] default = [] diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1ad76ea2c6c7..263b93afb34c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -7,6 +7,7 @@ use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use metrics::{counter, histogram}; +use smallvec::SmallVec; use std::collections::HashSet; use std::fmt::Debug; use std::future::ready; @@ -419,7 +420,7 @@ impl Merkle> { // it as the root. let root_node = Node::Leaf(LeafNode { partial_path: key, - value, + value: SmallVec::from(&value[..]), }); *root = root_node.into(); return Ok(()); @@ -500,7 +501,7 @@ impl Merkle> { // There is no child at this index. // Create a new leaf and put it here. let new_leaf = Node::Leaf(LeafNode { - value, + value: SmallVec::from(&value[..]), partial_path, }); branch.update_child(child_index, Some(Child::Node(new_leaf))); @@ -526,7 +527,7 @@ impl Merkle> { }; let new_leaf = Node::Leaf(LeafNode { - value, + value: SmallVec::from(&value[..]), partial_path, }); @@ -555,7 +556,7 @@ impl Merkle> { branch.update_child(node_index, Some(Child::Node(node))); let new_leaf = Node::Leaf(LeafNode { - value, + value: SmallVec::from(&value[..]), partial_path: key_partial_path, }); branch.update_child(key_index, Some(Child::Node(new_leaf))); @@ -702,7 +703,7 @@ impl Merkle> { } Node::Leaf(leaf) => { let removed_value = std::mem::take(&mut leaf.value); - Ok((None, Some(removed_value))) + Ok((None, Some(removed_value.into_boxed_slice()))) } } } diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 08da2f31f387..d96bd5cd18b6 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -12,7 +12,7 @@ fn leaf(c: &mut Criterion) { let mut group = c.benchmark_group("leaf"); let input = Node::Leaf(LeafNode { partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), - value: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice(), + value: SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), }); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); group.bench_with_input("leaf", &input, |b, input| { diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 180e150516a0..d0a6514a37b3 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -157,7 +157,7 @@ impl From<&LeafNode> for BranchNode { fn from(leaf: &LeafNode) -> Self { BranchNode { partial_path: leaf.partial_path.clone(), - value: Some(leaf.value.clone()), + value: Some(Box::from(&leaf.value[..])), children: Default::default(), } } diff --git a/storage/src/node/leaf.rs b/storage/src/node/leaf.rs index 0e554f27d95c..1ba13e6a4f29 100644 --- a/storage/src/node/leaf.rs +++ b/storage/src/node/leaf.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; use std::fmt::{Debug, Error as FmtError, Formatter}; @@ -14,7 +15,7 @@ pub struct LeafNode { pub partial_path: Path, /// The value associated with this leaf - pub value: Box<[u8]>, + pub value: SmallVec<[u8; 16]>, } impl Debug for LeafNode { diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 6a19af8ce3bf..b6d068176089 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -3,6 +3,7 @@ use enum_as_inner::EnumAsInner; use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; use std::{fmt::Debug, sync::Arc}; mod branch; @@ -30,7 +31,7 @@ impl Default for Node { fn default() -> Self { Node::Leaf(LeafNode { partial_path: Path::new(), - value: Box::default(), + value: SmallVec::default(), }) } } @@ -68,7 +69,7 @@ impl Node { pub fn update_value(&mut self, value: Box<[u8]>) { match self { Node::Branch(b) => b.value = Some(value), - Node::Leaf(l) => l.value = value, + Node::Leaf(l) => l.value = SmallVec::from(&value[..]), } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index c681e16d2c52..e932d1a096b9 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1112,6 +1112,7 @@ impl NodeStore { mod tests { use crate::{linear::memory::MemStore, BranchNode, LeafNode}; use arc_swap::access::DynGuard; + use smallvec::SmallVec; use test_case::test_case; use super::*; @@ -1201,7 +1202,7 @@ mod tests { #[test_case( Node::Leaf(LeafNode { partial_path: Path::from([0, 1, 2]), - value: Box::new([3, 4, 5]), + value: SmallVec::from_slice(&[3, 4, 5]), }); "leaf node")] fn test_serialized_len>(node: N) { From e12aa1c42cef0b1ba0fc82a662b4dcf1a4b9a42d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 7 Oct 2024 08:27:25 -1000 Subject: [PATCH 0608/1053] Fixup compiler errors A few more places needed smallvec --- firewood/src/merkle.rs | 16 ++++++++++------ firewood/src/stream.rs | 22 ++++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 263b93afb34c..01abf60b1343 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -522,7 +522,7 @@ impl Merkle> { // Turn this node into a branch node and put a new leaf as a child. let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), - value: Some(std::mem::take(&mut leaf.value)), + value: Some(std::mem::take(&mut leaf.value).into_boxed_slice()), children: Default::default(), }; @@ -710,7 +710,10 @@ impl Merkle> { (Some((child_index, child_partial_path)), None) => { // 3. The key is below the node (i.e. its descendant) match node { - Node::Leaf(ref mut leaf) => Ok((None, Some(std::mem::take(&mut leaf.value)))), + Node::Leaf(ref mut leaf) => Ok(( + None, + Some(std::mem::take(&mut leaf.value).into_boxed_slice()), + )), Node::Branch(ref mut branch) => { #[allow(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) @@ -745,9 +748,9 @@ impl Merkle> { let Some((child_index, child)) = children_iter.next() else { // The branch has no children. Turn it into a leaf. let leaf = Node::Leaf(LeafNode { - value: branch.value.take().expect( + value: SmallVec::from(&(*branch.value.take().expect( "branch node must have a value if it previously had only 1 child", - ), + ))[..]), partial_path: branch.partial_path.clone(), // TODO remove clone }); return Ok((Some(leaf), removed_value)); @@ -763,7 +766,7 @@ impl Merkle> { Child::Node(child_node) => std::mem::replace( child_node, Node::Leaf(LeafNode { - value: Box::from([]), + value: SmallVec::default(), partial_path: Path::new(), }), ), @@ -837,7 +840,8 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; - use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; + use rand::rngs::StdRng; + use rand::{thread_rng, Rng, SeedableRng}; use storage::{MemStore, MutableProposal, NodeStore, RootReader}; use test_case::test_case; diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 7c17e1507ba4..21a5154fa1ff 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1,14 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{ - merkle::{Key, MerkleError, Value}, - v2::api, -}; - -use futures::{stream::FusedStream, Stream, StreamExt}; -use std::{cmp::Ordering, iter::once}; -use std::{sync::Arc, task::Poll}; +use crate::merkle::{Key, MerkleError, Value}; +use crate::v2::api; + +use futures::stream::FusedStream; +use futures::{Stream, StreamExt}; +use std::cmp::Ordering; +use std::iter::once; +use std::sync::Arc; +use std::task::Poll; use storage::{BranchNode, Child, NibblesIterator, Node, PathIterItem, TrieReader}; /// Represents an ongoing iteration over a node and its children. @@ -573,6 +574,7 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { + use smallvec::SmallVec; use storage::{MemStore, MutableProposal, NodeStore}; use crate::merkle::Merkle; @@ -622,7 +624,7 @@ mod tests { node.key_nibbles, vec![0x0B, 0x0E, 0x0E, 0x0F].into_boxed_slice() ); - assert_eq!(node.node.as_leaf().unwrap().value, Box::from([0x42])); + assert_eq!(node.node.as_leaf().unwrap().value, SmallVec::from([0x42])); assert_eq!(node.next_nibble, None); assert!(stream.next().is_none()); @@ -672,7 +674,7 @@ mod tests { assert_eq!(node.next_nibble, None); assert_eq!( node.node.as_leaf().unwrap().value, - Box::from([0x00, 0x00, 0x00, 0x0FF]) + SmallVec::from([0x00, 0x00, 0x00, 0x0FF]) ); assert!(stream.next().is_none()); From 7287c06ee242bb963593dd6c718e6f8202d7ba84 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 7 Oct 2024 08:57:41 -1000 Subject: [PATCH 0609/1053] Remove dead tests These old merkle tests tested serialization. We do it completely differently now so these tests aren't needed. --- firewood/src/merkle.rs | 94 ------------------------------------------ 1 file changed, 94 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 01abf60b1343..3f91653daace 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -903,100 +903,6 @@ mod tests { Merkle { nodestore } } - // use super::*; - // use test_case::test_case; - - // fn branch(path: &[u8], value: &[u8], encoded_child: Option>) -> Node { - // let (path, value) = (path.to_vec(), value.to_vec()); - // let path = Nibbles::<0>::new(&path); - // let path = Path(path.into_iter().collect()); - - // let children = Default::default(); - // let value = if value.is_empty() { None } else { Some(value) }; - // let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); - - // if let Some(child) = encoded_child { - // children_encoded[0] = Some(child); - // } - - // Node::from_branch(BranchNode { - // partial_path: path, - // children, - // value, - // children_encoded, - // }) - // } - - // fn branch_without_value(path: &[u8], encoded_child: Option>) -> Node { - // let path = path.to_vec(); - // let path = Nibbles::<0>::new(&path); - // let path = Path(path.into_iter().collect()); - - // let children = Default::default(); - // // TODO: Properly test empty value - // let value = None; - // let mut children_encoded = <[Option>; BranchNode::MAX_CHILDREN]>::default(); - - // if let Some(child) = encoded_child { - // children_encoded[0] = Some(child); - // } - - // Node::from_branch(BranchNode { - // partial_path: path, - // children, - // value, - // children_encoded, - // }) - // } - - // #[test_case(leaf(Vec::new(), Vec::new()) ; "empty leaf encoding")] - // #[test_case(leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf encoding")] - // #[test_case(branch(b"", b"value", vec![1, 2, 3].into()) ; "branch with chd")] - // #[test_case(branch(b"", b"value", None); "branch without chd")] - // #[test_case(branch_without_value(b"", None); "branch without value and chd")] - // #[test_case(branch(b"", b"", None); "branch without path value or children")] - // #[test_case(branch(b"", b"value", None) ; "branch with value")] - // #[test_case(branch(&[2], b"", None); "branch with path")] - // #[test_case(branch(b"", b"", vec![1, 2, 3].into()); "branch with children")] - // #[test_case(branch(&[2], b"value", None); "branch with path and value")] - // #[test_case(branch(b"", b"value", vec![1, 2, 3].into()); "branch with value and children")] - // #[test_case(branch(&[2], b"", vec![1, 2, 3].into()); "branch with path and children")] - // #[test_case(branch(&[2], b"value", vec![1, 2, 3].into()); "branch with path value and children")] - // fn encode(node: Node) { - // let merkle = create_in_memory_merkle(); - - // let node_ref = merkle.put_node(node).unwrap(); - // let encoded = node_ref.get_encoded(&merkle.store); - // let new_node = Node::from(NodeType::decode(encoded).unwrap()); - // let new_node_encoded = new_node.get_encoded(&merkle.store); - - // assert_eq!(encoded, new_node_encoded); - // } - - // #[test_case(Bincode::new(), leaf(vec![], vec![4, 5]) ; "leaf without partial path encoding with Bincode")] - // #[test_case(Bincode::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with Bincode")] - // #[test_case(Bincode::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with Bincode")] - // #[test_case(Bincode::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with Bincode")] - // #[test_case(Bincode::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with Bincode")] - // #[test_case(PlainCodec::new(), leaf(Vec::new(), vec![4, 5]) ; "leaf without partial path encoding with PlainCodec")] - // #[test_case(PlainCodec::new(), leaf(vec![1, 2, 3], vec![4, 5]) ; "leaf with partial path encoding with PlainCodec")] - // #[test_case(PlainCodec::new(), branch(b"abcd", b"value", vec![1, 2, 3].into()) ; "branch with partial path and value with PlainCodec")] - // #[test_case(PlainCodec::new(), branch(b"abcd", &[], vec![1, 2, 3].into()) ; "branch with partial path and no value with PlainCodec")] - // #[test_case(PlainCodec::new(), branch(b"", &[1,3,3,7], vec![1, 2, 3].into()) ; "branch with no partial path and value with PlainCodec")] - // fn node_encode_decode(_codec: T, node: Node) - // where - // T: BinarySerde, - // for<'de> EncodedNode: serde::Serialize + serde::Deserialize<'de>, - // { - // let merkle = create_generic_test_merkle::(); - // let node_ref = merkle.put_node(node.clone()).unwrap(); - - // let encoded = merkle.encode(node_ref.inner()).unwrap(); - // let new_node = Node::from(merkle.decode(encoded.as_ref()).unwrap()); - - // assert_eq!(node, new_node); - // } - #[test] fn test_insert_and_get() { let mut merkle = create_in_memory_merkle(); From 46f35a56dec611d06e14b28a52bf45c3bf74f9a1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Oct 2024 08:34:33 -1000 Subject: [PATCH 0610/1053] For NodeStoreHeader, use bytemuck not bincode (#723) --- firewood/src/db.rs | 29 +++++---- firewood/src/manager.rs | 6 +- storage/Cargo.toml | 2 + storage/src/nodestore.rs | 134 +++++++++++++++++++-------------------- 4 files changed, 87 insertions(+), 84 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c60049de5722..df5489c1f905 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -63,7 +63,10 @@ impl std::fmt::Debug for DbMetrics { #[async_trait] impl api::DbView for HistoricalRev { - type Stream<'a> = MerkleKeyValueStream<'a, Self> where Self: 'a; + type Stream<'a> + = MerkleKeyValueStream<'a, Self> + where + Self: 'a; async fn root_hash(&self) -> Result, api::Error> { HashedNodeReader::root_hash(self).map_err(api::Error::IO) @@ -125,7 +128,10 @@ where { type Historical = NodeStore; - type Proposal<'p> = Proposal<'p> where Self: 'p; + type Proposal<'p> + = Proposal<'p> + where + Self: 'p; async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { let nodestore = self @@ -230,7 +236,10 @@ pub struct Proposal<'p> { #[async_trait] impl<'a> api::DbView for Proposal<'a> { - type Stream<'b> = MerkleKeyValueStream<'b, NodeStore, FileBacked>> where Self: 'b; + type Stream<'b> + = MerkleKeyValueStream<'b, NodeStore, FileBacked>> + where + Self: 'b; async fn root_hash(&self) -> Result, api::Error> { self.nodestore.root_hash().map_err(api::Error::from) @@ -313,15 +322,11 @@ impl<'a> api::Proposal for Proposal<'a> { #[cfg(test)] #[allow(clippy::unwrap_used)] mod test { - use std::{ - ops::{Deref, DerefMut}, - path::PathBuf, - }; - - use crate::{ - db::Db, - v2::api::{Db as _, DbView as _, Error, Proposal as _}, - }; + use std::ops::{Deref, DerefMut}; + use std::path::PathBuf; + + use crate::db::Db; + use crate::v2::api::{Db as _, DbView as _, Error, Proposal as _}; use super::{BatchOp, DbConfig}; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 8d2a4674d487..4753c8dcaa02 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -3,11 +3,11 @@ #![allow(dead_code)] -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; +use std::io::Error; use std::num::NonZero; use std::path::PathBuf; use std::sync::Arc; -use std::{collections::VecDeque, io::Error}; use storage::logger::warn; use typed_builder::TypedBuilder; @@ -92,7 +92,7 @@ impl RevisionManager { } if truncate { - nodestore.flush_header()?; + nodestore.flush_header_with_padding()?; } Ok(manager) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 87b49a0c25fa..d5a8a28435cc 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -18,6 +18,8 @@ arc-swap = "1.7.1" lru = "0.12.4" metrics = "0.23.0" log = { version = "0.4.20", optional = true } +bytemuck = "1.7.0" +bytemuck_derive = "1.7.0" [dev-dependencies] rand = "0.8.5" diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index e932d1a096b9..6bf1b9955174 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -5,6 +5,7 @@ use crate::logger::trace; use arc_swap::access::DynAccess; use arc_swap::ArcSwap; use bincode::{DefaultOptions, Options as _}; +use bytemuck_derive::{AnyBitPattern, NoUninit}; use metrics::{counter, histogram}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -100,9 +101,6 @@ const NUM_AREA_SIZES: usize = AREA_SIZES.len(); const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; -const SOME_FREE_LIST_ELT_SIZE: u64 = 1 + std::mem::size_of::() as u64; -const FREE_LIST_MAX_SIZE: u64 = NUM_AREA_SIZES as u64 * SOME_FREE_LIST_ELT_SIZE; - /// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. fn area_size_to_index(n: u64) -> Result { if n > MAX_AREA_SIZE { @@ -196,13 +194,25 @@ impl NodeStore { /// Assumes the header is written in the [ReadableStorage]. pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; - - let header: NodeStoreHeader = DefaultOptions::new() - .deserialize_from(&mut stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let mut header = NodeStoreHeader::new(); + let header_bytes = bytemuck::bytes_of_mut(&mut header); + stream.read_exact(header_bytes)?; drop(stream); + if header.version != Version::new() { + return Err(Error::new( + ErrorKind::InvalidData, + "Incompatible firewood version", + )); + } + if header.endian_test != 1 { + return Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in endianness", + )); + } + let mut nodestore = Self { header, kind: Committed { @@ -308,7 +318,7 @@ impl NodeStore { parent: parent.kind.as_nodestore_parent(), }; Ok(NodeStore { - header: parent.header.clone(), + header: parent.header, kind, storage: parent.storage.clone(), }) @@ -335,30 +345,14 @@ impl NodeStore { } } -impl NodeStore { - // TODO danlaine: Use this code in the revision management code. - // TODO danlaine: Write only the parts of the header that have changed instead of the whole thing - // fn write_header(&mut self) -> Result<(), Error> { - // let header_bytes = bincode::serialize(&self.header).map_err(|e| { - // Error::new( - // ErrorKind::InvalidData, - // format!("Failed to serialize free lists: {}", e), - // ) - // })?; - - // self.storage.write(0, header_bytes.as_slice())?; - - // Ok(()) - // } -} - impl NodeStore { /// Creates a new, empty, [NodeStore] and clobbers the underlying `storage` with an empty header. + /// This is used during testing and during the creation of an in-memory merkle for proofs pub fn new_empty_proposal(storage: Arc) -> Self { let header = NodeStoreHeader::new(); - let header_bytes = bincode::serialize(&header).expect("failed to serialize header"); + let header_bytes = bytemuck::bytes_of(&header); storage - .write(0, header_bytes.as_slice()) + .write(0, header_bytes) .expect("failed to write header"); NodeStore { header, @@ -524,7 +518,8 @@ impl From for UpdateError { /// Can be used by filesystem tooling such as "file" to identify /// the version of firewood used to create this [NodeStore] file. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, NoUninit, AnyBitPattern)] +#[repr(transparent)] struct Version { bytes: [u8; 16], } @@ -549,10 +544,13 @@ pub type FreeLists = [Option; NUM_AREA_SIZES]; /// Persisted metadata for a [NodeStore]. /// The [NodeStoreHeader] is at the start of the ReadableStorage. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] +#[repr(C)] struct NodeStoreHeader { /// Identifies the version of firewood used to create this [NodeStore]. version: Version, + /// always "1"; verifies endianness + endian_test: u64, size: u64, /// Element i is the pointer to the first free block of size `BLOCK_SIZES[i]`. free_lists: FreeLists, @@ -560,26 +558,22 @@ struct NodeStoreHeader { } impl NodeStoreHeader { - /// The first SIZE bytes of the ReadableStorage are the [NodeStoreHeader]. - /// The serialized NodeStoreHeader may be less than SIZE bytes but we - /// reserve this much space for it since it can grow and it must always be - /// at the start of the ReadableStorage so it can't be moved in a resize. - const SIZE: u64 = { - // 8 and 9 for `size` and `root_address` respectively - let max_size = Version::SIZE + 8 + 9 + FREE_LIST_MAX_SIZE; - // Round up to the nearest multiple of MIN_AREA_SIZE - let remainder = max_size % MIN_AREA_SIZE; - if remainder == 0 { - max_size - } else { - max_size + MIN_AREA_SIZE - remainder - } - }; + /// The first SIZE bytes of the ReadableStorage are reserved for the + /// [NodeStoreHeader]. + /// We also want it aligned to a disk block + + const SIZE: u64 = 2048; + + /// Number of extra bytes to write on the first creation of the NodeStoreHeader + /// (zero-padded) + /// also a compile time check to prevent setting SIZE too small + const EXTRA_BYTES: usize = Self::SIZE as usize - std::mem::size_of::(); fn new() -> Self { Self { // The store just contains the header at this point size: Self::SIZE, + endian_test: 1, root_address: None, version: Version::new(), free_lists: Default::default(), @@ -872,12 +866,22 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. pub fn flush_header(&self) -> Result<(), Error> { - let header_bytes = DefaultOptions::new() - .with_varint_encoding() - .serialize(&self.header) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - self.storage.write(0, header_bytes.as_slice())?; + let header_bytes = bytemuck::bytes_of(&self.header); + self.storage.write(0, header_bytes)?; + Ok(()) + } + /// Persist the header, including all the padding + /// This is only done the first time we write the header + pub fn flush_header_with_padding(&self) -> Result<(), Error> { + let header_bytes = bytemuck::bytes_of(&self.header) + .iter() + .copied() + .chain(std::iter::repeat(0u8).take(NodeStoreHeader::EXTRA_BYTES)) + .collect::>(); + debug_assert_eq!(header_bytes.len(), NodeStoreHeader::SIZE as usize); + + self.storage.write(0, &header_bytes)?; Ok(()) } } @@ -886,13 +890,9 @@ impl NodeStore { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new() - .with_varint_encoding() - .serialize(&self.header.free_lists) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; - self.storage - .write(free_list_offset, free_list_bytes.as_slice())?; + self.storage.write(free_list_offset, free_list_bytes)?; Ok(()) } @@ -924,7 +924,7 @@ impl NodeStore { /// This function is used during commit. pub fn as_committed(&self) -> NodeStore { NodeStore { - header: self.header.clone(), + header: self.header, kind: Committed { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), @@ -938,13 +938,9 @@ impl NodeStore, S> { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage - let free_list_bytes = DefaultOptions::new() - .with_varint_encoding() - .serialize(&self.header.free_lists) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; - self.storage - .write(free_list_offset, free_list_bytes.as_slice())?; + self.storage.write(free_list_offset, free_list_bytes)?; Ok(()) } @@ -976,7 +972,7 @@ impl NodeStore, FileBacked> { /// This function is used during commit. pub fn as_committed(&self) -> NodeStore { NodeStore { - header: self.header.clone(), + header: self.header, kind: Committed { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), @@ -1110,7 +1106,8 @@ impl NodeStore { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::{linear::memory::MemStore, BranchNode, LeafNode}; + use crate::linear::memory::MemStore; + use crate::{BranchNode, LeafNode}; use arc_swap::access::DynGuard; use smallvec::SmallVec; use test_case::test_case; @@ -1184,11 +1181,10 @@ mod tests { let node_store = NodeStore::new_empty_proposal(memstore.into()); // Check the empty header is written at the start of the ReadableStorage. - let mut header_bytes = node_store.storage.stream_from(0).unwrap(); - let header: NodeStoreHeader = DefaultOptions::new() - .with_varint_encoding() - .deserialize_from(&mut header_bytes) - .unwrap(); + let mut header = NodeStoreHeader::new(); + let mut header_stream = node_store.storage.stream_from(0).unwrap(); + let header_bytes = bytemuck::bytes_of_mut(&mut header); + header_stream.read_exact(header_bytes).unwrap(); assert_eq!(header.version, Version::new()); let empty_free_list: FreeLists = Default::default(); assert_eq!(header.free_lists, empty_free_list); From 34c02d259b64fc72a84ae7d8632d1ecb90046ba6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Oct 2024 11:35:51 -1000 Subject: [PATCH 0611/1053] Improve revision reaping (#725) --- firewood/src/manager.rs | 7 +++---- storage/src/nodestore.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 4753c8dcaa02..41875644fd5c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -143,14 +143,14 @@ impl RevisionManager { return Err(RevisionManagerError::NotLatest); } - let mut committed = proposal.as_committed(); + let committed = proposal.as_committed(); // 2. Persist delete list for this committed revision to disk for recovery // 3 Take the deleted entries from the oldest revision and mark them as free for this revision // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free - while self.historical.len() > self.max_revisions { + while self.historical.len() >= self.max_revisions { let oldest = self.historical.pop_front().expect("must be present"); if let Some(oldest_hash) = oldest.kind.root_hash() { self.by_hash.remove(&oldest_hash); @@ -162,9 +162,8 @@ impl RevisionManager { // This guarantee is there because we have a `&mut self` reference to the manager, so // the compiler guarantees we are the only one using this manager. match Arc::try_unwrap(oldest) { - Ok(oldest) => committed.reap_deleted(&oldest)?, + Ok(oldest) => oldest.reap_deleted()?, Err(original) => { - // TODO: try reaping the next revision warn!("Oldest revision could not be reaped; still referenced"); self.historical.push_front(original); } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 6bf1b9955174..3d8040e03ec8 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -53,7 +53,7 @@ use std::fmt::Debug; /// ``` use std::io::{Error, ErrorKind, Write}; use std::iter::once; -use std::mem::offset_of; +use std::mem::{offset_of, take}; use std::num::NonZeroU64; use std::ops::Deref; use std::sync::Arc; @@ -1092,12 +1092,12 @@ where impl NodeStore { /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal - pub fn reap_deleted(&mut self, oldest: &NodeStore) -> Result<(), Error> { + pub fn reap_deleted(mut self) -> Result<(), Error> { self.storage - .invalidate_cached_nodes(oldest.kind.deleted.iter()); - trace!("There are {} nodes to reap", oldest.kind.deleted.len()); - for addr in oldest.kind.deleted.iter() { - self.delete_node(*addr)?; + .invalidate_cached_nodes(self.kind.deleted.iter()); + trace!("There are {} nodes to reap", self.kind.deleted.len()); + for addr in take(&mut self.kind.deleted) { + self.delete_node(addr)?; } Ok(()) } From 242abf37c9066c33eda91b783dc96d92465159cf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Oct 2024 18:09:31 -1000 Subject: [PATCH 0612/1053] Expose graph option for fwdctl (#724) --- firewood/src/merkle.rs | 14 +++++++++++--- fwdctl/src/graph.rs | 27 +++++++++++++++++++++++++++ fwdctl/src/main.rs | 4 ++++ storage/src/nodestore.rs | 2 +- storage/src/trie_hash.rs | 3 ++- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 fwdctl/src/graph.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 3f91653daace..e42f2594f835 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -76,7 +76,12 @@ macro_rules! write_attributes { write!($writer, " val={}", string)? } _ => { - write!($writer, " val={}", hex::encode($value))?; + let hex = hex::encode($value); + if hex.len() > 6 { + write!($writer, " val={:.6}...", hex)?; + } else { + write!($writer, " val={}", hex)?; + } } } } @@ -341,7 +346,10 @@ impl Merkle { seen: &mut HashSet, writer: &mut dyn Write, ) -> Result<(), MerkleError> { - write!(writer, " {addr}[label=\"addr:{addr:?} hash:{hash:?}")?; + write!(writer, " {addr}[label=\"addr:{addr:?}")?; + if let Some(hash) = hash { + write!(writer, " hash:{hash:.6?}...")?; + } match &*self.read_node(addr)? { Node::Branch(b) => { @@ -378,7 +386,7 @@ impl Merkle { pub fn dump(&self) -> Result { let mut result = vec![]; - writeln!(result, "digraph Merkle {{")?; + writeln!(result, "digraph Merkle {{\n rankdir=LR;")?; if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { writeln!(result, " root -> {root_addr}")?; let mut seen = HashSet::new(); diff --git a/fwdctl/src/graph.rs b/fwdctl/src/graph.rs new file mode 100644 index 000000000000..833c1e140fe9 --- /dev/null +++ b/fwdctl/src/graph.rs @@ -0,0 +1,27 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use clap::Args; +use firewood::db::{Db, DbConfig}; +use firewood::v2::api; +use std::io::stdout; + +#[derive(Debug, Args)] +pub struct Options { + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db: String, +} + +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + log::debug!("dump database {:?}", opts); + let cfg = DbConfig::builder().truncate(false); + + let db = Db::new(opts.db.clone(), cfg.build()).await?; + db.dump(&mut stdout())?; + Ok(()) +} diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index fca9d6d873b3..1c80537e753b 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -8,6 +8,7 @@ pub mod create; pub mod delete; pub mod dump; pub mod get; +pub mod graph; pub mod insert; pub mod root; @@ -44,6 +45,8 @@ enum Commands { Root(root::Options), /// Dump contents of key/value store Dump(dump::Options), + /// Produce a dot file of the database + Graph(graph::Options), } #[tokio::main] @@ -62,5 +65,6 @@ async fn main() -> Result<(), api::Error> { Commands::Delete(opts) => delete::run(opts).await, Commands::Root(opts) => root::run(opts).await, Commands::Dump(opts) => dump::run(opts).await, + Commands::Graph(opts) => graph::run(opts).await, } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 3d8040e03ec8..44d51db81a57 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -424,7 +424,7 @@ impl NodeStore, S> { return Ok(Some((address, index as AreaIndex))); } - trace!("No free blocks of sufficient size {index} found"); + trace!("No free blocks of sufficient size {index_wanted} found"); counter!("firewood.space.notfree").increment(AREA_SIZES[index_wanted as usize]); Ok(None) } diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 35f403ccdee1..89df4b061641 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -32,7 +32,8 @@ impl AsRef<[u8]> for TrieHash { impl Debug for TrieHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", hex::encode(self.0)) + let width = f.precision().unwrap_or_default(); + write!(f, "{:.*}", width, hex::encode(self.0)) } } From 4c24348900fa161fd7c1f84db4dee35abe4caefa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 09:07:04 -1000 Subject: [PATCH 0613/1053] Benchmark stats (#726) --- benchmark/Cargo.toml | 1 + benchmark/src/main.rs | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 9580a7353dcb..5f890b70f594 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -8,6 +8,7 @@ firewood = { path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive'] } sha2 = "0.10.8" +metrics = "0.23.0" metrics-util = "0.17.0" metrics-exporter-prometheus = "0.15.3" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 58566371e4b1..91328f6f401b 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -18,7 +18,7 @@ use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; use std::error::Error; -use std::net::{Ipv4Addr, SocketAddr}; +use std::net::{Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; use std::time::Duration; @@ -37,6 +37,8 @@ struct Args { revisions: usize, #[arg(short = 'p', long, default_value_t = 3000)] prometheus_port: u16, + #[arg(short = 's', long, default_value_t = false)] + stats_dump: bool, #[clap(flatten)] global_opts: GlobalOpts, @@ -113,19 +115,25 @@ async fn main() -> Result<(), Box> { }) .init(); + // Manually set up prometheus let builder = PrometheusBuilder::new(); - builder + let (prometheus_recorder, listener_future) = builder .with_http_listener(SocketAddr::new( - Ipv4Addr::UNSPECIFIED.into(), + Ipv6Addr::UNSPECIFIED.into(), args.prometheus_port, )) .idle_timeout( MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, Some(Duration::from_secs(10)), ) - .install() + .build() .expect("unable in run prometheusbuilder"); + // Clone the handle so we can dump the stats at the end + let prometheus_handle = prometheus_recorder.handle(); + metrics::set_global_recorder(prometheus_recorder)?; + tokio::spawn(listener_future); + let mgrcfg = RevisionManagerConfig::builder() .node_cache_size(args.cache_size) .free_list_cache_size( @@ -160,5 +168,10 @@ async fn main() -> Result<(), Box> { runner.run(&db, &args).await?; } } + + if args.stats_dump { + println!("{}", prometheus_handle.render()); + } + Ok(()) } From 42fea7391e1a93a3e7d17930fda5d6516cd0fd8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:15:23 -1000 Subject: [PATCH 0614/1053] build(deps): update aquamarine requirement from 0.5.0 to 0.6.0 (#727) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index e0908f19117c..9d60069746b1 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -16,7 +16,7 @@ homepage = "https://avalabs.org" readme = "../README.md" [dependencies] -aquamarine = "0.5.0" +aquamarine = "0.6.0" async-trait = "0.1.77" storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" From 8a9d32a31351522013d68d5bd3151c66f0f8f0fd Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 09:15:50 -1000 Subject: [PATCH 0615/1053] Allow configurable dbname in benchmark (#728) --- benchmark/Cargo.toml | 2 +- benchmark/src/main.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 5f890b70f594..467cdd11860c 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] firewood = { path = "../firewood" } hex = "0.4.3" -clap = { version = "4.5.0", features = ['derive'] } +clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" metrics = "0.23.0" metrics-util = "0.17.0" diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 91328f6f401b..eb1bc90ef933 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -20,6 +20,7 @@ use sha2::{Digest, Sha256}; use std::error::Error; use std::net::{Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; +use std::path::PathBuf; use std::time::Duration; use firewood::db::{BatchOp, Db, DbConfig}; @@ -60,6 +61,15 @@ struct GlobalOpts { default_value_t = String::from("info"), )] log_level: String, + #[arg( + long, + short = 'd', + required = false, + help = "Use this database name instead of the default", + value_name = "TRUNCATE", + default_value = PathBuf::from("benchmark_db").into_os_string(), + )] + dbname: PathBuf, } mod create; @@ -146,7 +156,7 @@ async fn main() -> Result<(), Box> { .manager(mgrcfg) .build(); - let db = Db::new("rev_db", cfg) + let db = Db::new(args.global_opts.dbname.clone(), cfg) .await .expect("db initiation should succeed"); From a308069e32e226b5d212d41cf563a10208573169 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 09:44:05 -1000 Subject: [PATCH 0616/1053] Break after determining we can't reap the latest (#729) --- firewood/src/manager.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 41875644fd5c..d2b8bfcfd2ce 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -166,6 +166,7 @@ impl RevisionManager { Err(original) => { warn!("Oldest revision could not be reaped; still referenced"); self.historical.push_front(original); + break; } } } From bd243b5e54b148c79c96078513ba4be03cd982e6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 09:44:35 -1000 Subject: [PATCH 0617/1053] Allow for arbitrary sized allocations (#730) --- storage/src/nodestore.rs | 108 ++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 44d51db81a57..4c011d70bd50 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -66,37 +66,68 @@ use super::linear::WritableStorage; /// [NodeStore] divides the linear store into blocks of different sizes. /// [AREA_SIZES] is every valid block size. -const AREA_SIZES: [u64; 21] = [ - 1 << MIN_AREA_SIZE_LOG, // Min block size is 8 - 1 << 4, - 1 << 5, - 1 << 6, - 1 << 7, - 1 << 8, - 1 << 9, - 1 << 10, - 1 << 11, - 1 << 12, - 1 << 13, - 1 << 14, - 1 << 15, - 1 << 16, - 1 << 17, - 1 << 18, - 1 << 19, - 1 << 20, - 1 << 21, - 1 << 22, - 1 << 23, // 16 MiB +const AREA_SIZES: [u64; 23] = [ + 16, // Min block size + 32, + 64, + 96, + 128, + 256, + 512, + 768, + 1024, + 1024 << 1, + 1024 << 2, + 1024 << 3, + 1024 << 4, + 1024 << 5, + 1024 << 6, + 1024 << 7, + 1024 << 8, + 1024 << 9, + 1024 << 10, + 1024 << 11, + 1024 << 12, + 1024 << 13, + 1024 << 14, ]; +// TODO: automate this, must stay in sync with above +fn index_name(index: AreaIndex) -> &'static str { + match index { + 0 => "16", + 1 => "32", + 2 => "64", + 3 => "96", + 4 => "128", + 5 => "256", + 6 => "512", + 7 => "768", + 8 => "1024", + 9 => "2048", + 10 => "4096", + 11 => "8192", + 12 => "16384", + 13 => "32768", + 14 => "65536", + 15 => "131072", + 16 => "262144", + 17 => "524288", + 18 => "1048576", + 19 => "2097152", + 20 => "4194304", + 21 => "8388608", + 22 => "16777216", + _ => "unknown", + } +} + /// The type of an index into the [AREA_SIZES] array /// This is not usize because we can store this as a single byte pub type AreaIndex = u8; // TODO danlaine: have type for index in AREA_SIZES // Implement try_into() for it. -const MIN_AREA_SIZE_LOG: AreaIndex = 3; const NUM_AREA_SIZES: usize = AREA_SIZES.len(); const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; @@ -114,13 +145,16 @@ fn area_size_to_index(n: u64) -> Result { return Ok(0); } - let mut log = n.ilog2(); - // If n is not a power of 2, we need to round up to the next power of 2. - if n != 1 << log { - log += 1; - } - - Ok(log as AreaIndex - MIN_AREA_SIZE_LOG) + AREA_SIZES + .iter() + .position(|&size| size >= n) + .map(|index| index as AreaIndex) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!("Node size {} is too large", n), + ) + }) } /// Objects cannot be stored at the zero address, so a [LinearAddress] is guaranteed not @@ -413,8 +447,10 @@ impl NodeStore, S> { *free_stored_area_addr = free_head.next_free_block; } - counter!("firewood.space.reused").increment(AREA_SIZES[index]); - counter!("firewood.space.wasted").increment(AREA_SIZES[index] - n); + counter!("firewood.space.reused", "index" => index_name(index as u8)) + .increment(AREA_SIZES[index]); + counter!("firewood.space.wasted", "index" => index_name(index as u8)) + .increment(AREA_SIZES[index] - n); // Return the address of the newly allocated block. trace!( @@ -425,7 +461,8 @@ impl NodeStore, S> { } trace!("No free blocks of sufficient size {index_wanted} found"); - counter!("firewood.space.notfree").increment(AREA_SIZES[index_wanted as usize]); + counter!("firewood.space.from_end", "index" => index_name(index_wanted as u8)) + .increment(AREA_SIZES[index_wanted as usize]); Ok(None) } @@ -474,8 +511,9 @@ impl NodeStore { let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {}", area_size_index); - counter!("firewood.delete_node").increment(1); - counter!("firewood.space.freed").increment(AREA_SIZES[area_size_index as usize]); + counter!("firewood.delete_node", "index" => index_name(area_size_index)).increment(1); + counter!("firewood.space.freed", "index" => index_name(area_size_index)) + .increment(AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. let area: Area = Area::Free(FreeArea { From 6da66b9b87acfddc994a32df7b740470c40b3750 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 10:54:41 -1000 Subject: [PATCH 0618/1053] Rkuris/varint everywhere (#731) --- storage/src/nodestore.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 4c011d70bd50..b7156aa396bb 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -92,6 +92,10 @@ const AREA_SIZES: [u64; 23] = [ 1024 << 14, ]; +fn serializer() -> impl bincode::Options { + DefaultOptions::new().with_varint_encoding() +} + // TODO: automate this, must stay in sync with above fn index_name(index: AreaIndex) -> &'static str { match index { @@ -185,7 +189,7 @@ impl NodeStore { fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { let mut area_stream = self.storage.stream_from(addr.get())?; - let index: AreaIndex = DefaultOptions::new() + let index: AreaIndex = serializer() .deserialize_from(&mut area_stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -209,7 +213,7 @@ impl NodeStore { let addr = addr.get() + 1; // Skip the index byte let area_stream = self.storage.stream_from(addr)?; - let area: Area = DefaultOptions::new() + let area: Area = serializer() .deserialize_from(area_stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -428,7 +432,7 @@ impl NodeStore, S> { } else { let free_area_addr = address.get(); let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: StoredArea> = DefaultOptions::new() + let free_head: StoredArea> = serializer() .deserialize_from(free_head_stream) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let StoredArea { @@ -480,7 +484,7 @@ impl NodeStore, S> { fn stored_len(node: &Node) -> u64 { let area: Area<&Node, FreeArea> = Area::Node(node); - DefaultOptions::new().serialized_size(&area).expect("fixme") + 1 + serializer().serialized_size(&area).expect("fixme") + 1 } /// Returns an address that can be used to store the given `node` and updates @@ -525,7 +529,7 @@ impl NodeStore { area, }; - let stored_area_bytes = DefaultOptions::new() + let stored_area_bytes = serializer() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -942,8 +946,7 @@ impl NodeStore { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new() - .with_varint_encoding() + let stored_area_bytes = serializer() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -990,8 +993,7 @@ impl NodeStore, S> { area: Area::<_, FreeArea>::Node(node.as_ref()), }; - let stored_area_bytes = DefaultOptions::new() - .with_varint_encoding() + let stored_area_bytes = serializer() .serialize(&stored_area) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; @@ -1245,7 +1247,7 @@ mod tests { let area_size = NodeStore::, MemStore>::stored_len(&node); let area: Area<&Node, FreeArea> = Area::Node(&node); - let actually_serialized = DefaultOptions::new().serialize(&area).unwrap().len() as u64; + let actually_serialized = serializer().serialize(&area).unwrap().len() as u64; assert_eq!(area_size, actually_serialized + 1); } } From 48395d7fe105a6cf75d720fade6d4d2e20ff3531 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Oct 2024 12:19:31 -1000 Subject: [PATCH 0619/1053] Must delete from context of latest revision (#732) --- firewood/src/manager.rs | 4 ++-- storage/src/nodestore.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index d2b8bfcfd2ce..962191ee993d 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -143,7 +143,7 @@ impl RevisionManager { return Err(RevisionManagerError::NotLatest); } - let committed = proposal.as_committed(); + let mut committed = proposal.as_committed(); // 2. Persist delete list for this committed revision to disk for recovery @@ -162,7 +162,7 @@ impl RevisionManager { // This guarantee is there because we have a `&mut self` reference to the manager, so // the compiler guarantees we are the only one using this manager. match Arc::try_unwrap(oldest) { - Ok(oldest) => oldest.reap_deleted()?, + Ok(oldest) => oldest.reap_deleted(&mut committed)?, Err(original) => { warn!("Oldest revision could not be reaped; still referenced"); self.historical.push_front(original); diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index b7156aa396bb..32cff965cdfe 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1132,12 +1132,12 @@ where impl NodeStore { /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal - pub fn reap_deleted(mut self) -> Result<(), Error> { + pub fn reap_deleted(mut self, proposal: &mut NodeStore) -> Result<(), Error> { self.storage .invalidate_cached_nodes(self.kind.deleted.iter()); trace!("There are {} nodes to reap", self.kind.deleted.len()); for addr in take(&mut self.kind.deleted) { - self.delete_node(addr)?; + proposal.delete_node(addr)?; } Ok(()) } From 4ae87d1aee3fdc6ce980e4bee55598e4d2daf9ce Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 10 Oct 2024 07:41:28 -1000 Subject: [PATCH 0620/1053] Cache size adjustments (#733) --- firewood/src/manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 962191ee993d..0c49047f7cea 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -22,10 +22,10 @@ pub struct RevisionManagerConfig { #[builder(default = 128)] max_revisions: usize, - #[builder(default_code = "NonZero::new(20480).expect(\"non-zero\")")] + #[builder(default_code = "NonZero::new(1500000).expect(\"non-zero\")")] node_cache_size: NonZero, - #[builder(default_code = "NonZero::new(10000).expect(\"non-zero\")")] + #[builder(default_code = "NonZero::new(20000).expect(\"non-zero\")")] free_list_cache_size: NonZero, } From 14d029c75f995e906ea7ea2c9d855d5640f698ea Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 10 Oct 2024 07:41:58 -1000 Subject: [PATCH 0621/1053] Terminate the benchmark after 65 mintues (#734) --- benchmark/src/main.rs | 9 +++++++++ benchmark/src/single.rs | 8 +++++--- benchmark/src/tenkrandom.rs | 6 +++++- benchmark/src/zipf.rs | 6 ++++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index eb1bc90ef933..a5d7b794b131 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -70,6 +70,15 @@ struct GlobalOpts { default_value = PathBuf::from("benchmark_db").into_os_string(), )] dbname: PathBuf, + #[arg( + long, + short = 't', + required = false, + help = "Terminate the test after this many minutes", + value_name = "TRUNCATE", + default_value_t = 65 + )] + duration_minutes: u64, } mod create; diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index 1c83edd55c51..b43684b98f69 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -14,11 +14,12 @@ use std::time::Instant; pub struct Single; impl TestRunner for Single { - async fn run(&self, db: &Db, _args: &crate::Args) -> Result<(), Box> { + async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { let start = Instant::now(); let inner_key = Sha256::digest(0u64.to_ne_bytes()).to_vec(); + let mut batch_id = 0; - for batch_id in 0.. { + while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch = vec![BatchOp::Put { key: inner_key.clone(), value: vec![batch_id as u8], @@ -33,7 +34,8 @@ impl TestRunner for Single { pretty_duration(&start.elapsed(), None) ); } + batch_id += 1; } - unreachable!() + Ok(()) } } diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 327c191baa4c..1fa98847af10 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use std::error::Error; +use std::time::Instant; use firewood::db::{BatchOp, Db}; use firewood::logger::debug; @@ -19,7 +20,9 @@ impl TestRunner for TenKRandom { let mut high = args.number_of_batches * args.batch_size; let twenty_five_pct = args.batch_size / 4; - loop { + let start = Instant::now(); + + while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch: Vec> = Self::generate_inserts(high, twenty_five_pct) .chain(generate_deletes(low, twenty_five_pct)) .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) @@ -29,6 +32,7 @@ impl TestRunner for TenKRandom { low += twenty_five_pct; high += twenty_five_pct; } + Ok(()) } } fn generate_updates( diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index e0d090795a39..4e6dfba620ba 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -32,8 +32,9 @@ impl TestRunner for Zipf { let rows = (args.number_of_batches * args.batch_size) as usize; let zipf = zipf::ZipfDistribution::new(rows, exponent).unwrap(); let start = Instant::now(); + let mut batch_id = 0; - for batch_id in 0.. { + while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch: Vec> = generate_updates(batch_id, args.batch_size as usize, &zipf).collect(); if log::log_enabled!(log::Level::Debug) { @@ -62,8 +63,9 @@ impl TestRunner for Zipf { pretty_duration(&start.elapsed(), None) ); } + batch_id += 1; } - unreachable!() + Ok(()) } } fn generate_updates( From b1aad780152b9642510314c1307ed6e0ca7e1415 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 10 Oct 2024 10:23:54 -1000 Subject: [PATCH 0622/1053] Increase free list cache sizes (#735) --- benchmark/src/main.rs | 4 +--- firewood/src/manager.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index a5d7b794b131..9c8b2e49d9cb 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -66,7 +66,6 @@ struct GlobalOpts { short = 'd', required = false, help = "Use this database name instead of the default", - value_name = "TRUNCATE", default_value = PathBuf::from("benchmark_db").into_os_string(), )] dbname: PathBuf, @@ -75,7 +74,6 @@ struct GlobalOpts { short = 't', required = false, help = "Terminate the test after this many minutes", - value_name = "TRUNCATE", default_value_t = 65 )] duration_minutes: u64, @@ -156,7 +154,7 @@ async fn main() -> Result<(), Box> { let mgrcfg = RevisionManagerConfig::builder() .node_cache_size(args.cache_size) .free_list_cache_size( - NonZeroUsize::new(2 * args.batch_size as usize).expect("batch size > 0"), + NonZeroUsize::new(4 * args.batch_size as usize).expect("batch size > 0"), ) .max_revisions(args.revisions) .build(); diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 0c49047f7cea..289ad5702d82 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -25,7 +25,7 @@ pub struct RevisionManagerConfig { #[builder(default_code = "NonZero::new(1500000).expect(\"non-zero\")")] node_cache_size: NonZero, - #[builder(default_code = "NonZero::new(20000).expect(\"non-zero\")")] + #[builder(default_code = "NonZero::new(40000).expect(\"non-zero\")")] free_list_cache_size: NonZero, } From ff24a436b3840edde847fb5d8d06b92d4093ed71 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 11 Oct 2024 09:06:29 -1000 Subject: [PATCH 0623/1053] Remove histograms (#736) --- firewood/src/merkle.rs | 5 +---- storage/src/nodestore.rs | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e42f2594f835..32e6011614f2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,7 +6,7 @@ use crate::range_proof::RangeProof; use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; -use metrics::{counter, histogram}; +use metrics::counter; use smallvec::SmallVec; use std::collections::HashSet; use std::fmt::Debug; @@ -416,9 +416,6 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { - histogram!("firewood.insert.key.length").record(key.len() as f64); - histogram!("firewood.insert.data.length").record(value.len() as f64); - let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 32cff965cdfe..7af9ed2ecd00 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -6,7 +6,7 @@ use arc_swap::access::DynAccess; use arc_swap::ArcSwap; use bincode::{DefaultOptions, Options as _}; use bytemuck_derive::{AnyBitPattern, NoUninit}; -use metrics::{counter, histogram}; +use metrics::counter; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -492,7 +492,6 @@ impl NodeStore, S> { /// Also returns the index of the free list the node was allocated from. pub fn allocate_node(&mut self, node: &Node) -> Result<(LinearAddress, AreaIndex), Error> { let stored_area_size = Self::stored_len(node); - histogram!("firewood.node_size").record(stored_area_size as f64); // Attempt to allocate from a free list. // If we can't allocate from a free list, allocate past the existing From 5e9db421cd195740d2269aea49548e97c7104f3b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 11 Oct 2024 09:22:03 -1000 Subject: [PATCH 0624/1053] Rkuris/giant node test (#737) --- storage/src/nodestore.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 7af9ed2ecd00..a16895657b20 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1249,4 +1249,22 @@ mod tests { let actually_serialized = serializer().serialize(&area).unwrap().len() as u64; assert_eq!(area_size, actually_serialized + 1); } + #[test] + #[should_panic(expected = "Node size 16777228 is too large")] + fn giant_node() { + let memstore = MemStore::new(vec![]); + let mut node_store = NodeStore::new_empty_proposal(memstore.into()); + + let huge_value = vec![0u8; *AREA_SIZES.last().unwrap() as usize]; + + let giant_leaf = Node::Leaf(LeafNode { + partial_path: Path::from([0, 1, 2]), + value: SmallVec::from_vec(huge_value), + }); + + node_store.mut_root().replace(giant_leaf); + + let immutable = NodeStore::, _>::from(node_store); + println!("{:?}", immutable); // should not be reached, but need to consume immutable to avoid optimization removal + } } From e6dbc924fb6857db1ca290a7a51fd671bc54d6cd Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 1 Nov 2024 13:15:20 -0700 Subject: [PATCH 0625/1053] Metrics updates (#745) --- benchmark/Cargo.toml | 6 +++--- firewood/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 467cdd11860c..29a129561e7f 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -8,9 +8,9 @@ firewood = { path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" -metrics = "0.23.0" -metrics-util = "0.17.0" -metrics-exporter-prometheus = "0.15.3" +metrics = "0.24.0" +metrics-util = "0.18.0" +metrics-exporter-prometheus = "0.16.0" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" pretty-duration = "0.1.1" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 9d60069746b1..d5f4a253e041 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.77" storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" -metrics = "0.23.0" +metrics = "0.24.0" serde = { version = "1.0" } sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index d5a8a28435cc..ad9bf3013be3 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -16,7 +16,7 @@ sha2 = "0.10.8" integer-encoding = "4.0.0" arc-swap = "1.7.1" lru = "0.12.4" -metrics = "0.23.0" +metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" From 279e7e2437fe0f6ab2fb8af3ef6845bfcaa34e2c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 3 Nov 2024 12:34:51 -0800 Subject: [PATCH 0626/1053] Add feature branch_factor_256 (#746) --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 39 +++++++++++++++++++++++------------ firewood/src/proof.rs | 4 +++- firewood/src/stream.rs | 33 +++++++++++++++++++++++------ storage/Cargo.toml | 1 + storage/benches/serializer.rs | 33 ++++++++++------------------- storage/src/node/branch.rs | 14 +++++++++---- storage/src/node/path.rs | 24 ++++++++++++++++++++- storage/src/nodestore.rs | 10 ++++++++- 9 files changed, 111 insertions(+), 48 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d5f4a253e041..dac5243c81f2 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -39,6 +39,7 @@ default = [] nightly = [] iouring = ["io-uring"] logger = ["storage/logger"] +branch_factor_256 = [ "storage/branch_factor_256" ] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 32e6011614f2..66812c31d5da 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -50,6 +50,7 @@ pub enum MerkleError { // convert a set of nibbles into a printable string // panics if there is a non-nibble byte in the set +#[cfg(not(feature = "branch_factor_256"))] fn nibbles_formatter>(nib: X) -> String { nib.into_iter() .map(|c| { @@ -60,6 +61,12 @@ fn nibbles_formatter>(nib: X) -> String { .collect::() } +#[cfg(feature = "branch_factor_256")] +fn nibbles_formatter>(nib: X) -> String { + let collected: Box<[u8]> = nib.into_iter().collect(); + hex::encode(&collected) +} + macro_rules! write_attributes { ($writer:ident, $node:expr, $value:expr) => { if !$node.partial_path.0.is_empty() { @@ -182,7 +189,8 @@ impl Merkle { // No nodes, even the root, are before `key`. // The root alone proves the non-existence of `key`. // TODO reduce duplicate code with ProofNode::from - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = + [const { None }; BranchNode::MAX_CHILDREN]; if let Some(branch) = root.as_branch() { // TODO danlaine: can we avoid indexing? #[allow(clippy::indexing_slicing)] @@ -480,7 +488,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: Some(value), - children: Default::default(), + children: [const { None }; BranchNode::MAX_CHILDREN], }; // Shorten the node's partial path since it has a new parent. @@ -528,7 +536,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), value: Some(std::mem::take(&mut leaf.value).into_boxed_slice()), - children: Default::default(), + children: [const { None }; BranchNode::MAX_CHILDREN], }; let new_leaf = Node::Leaf(LeafNode { @@ -554,7 +562,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: None, - children: Default::default(), + children: [const { None }; BranchNode::MAX_CHILDREN], }; node.update_partial_path(node_partial_path); @@ -1464,19 +1472,24 @@ mod tests { #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")] #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")] #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")] + #[allow(unused_variables)] fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) { - let merkle = merkle_build_test(kvs).unwrap().hash(); - let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else { - assert!(expected_hash.is_none()); - return; - }; + // TODO: get the hashes from merkledb and verify compatibility with branch factor 256 + #[cfg(not(feature = "branch_factor_256"))] + { + let merkle = merkle_build_test(kvs).unwrap().hash(); + let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else { + assert!(expected_hash.is_none()); + return; + }; - let expected_hash = expected_hash.unwrap(); + let expected_hash = expected_hash.unwrap(); - // This hash is from merkledb - let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap(); + // This hash is from merkledb + let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap(); - assert_eq!(got_hash, TrieHash::from(expected_hash)); + assert_eq!(got_hash, TrieHash::from(expected_hash)); + } } #[test] diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 615933e1ca2e..8e82ade7908f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -71,7 +71,8 @@ impl Hashable for ProofNode { impl From for ProofNode { fn from(item: PathIterItem) -> Self { - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = + [const { None }; BranchNode::MAX_CHILDREN]; if let Some(branch) = item.node.as_branch() { // TODO danlaine: can we avoid indexing? @@ -170,6 +171,7 @@ impl Proof { // Assert that only nodes whose keys are an even number of nibbles // have a `value_digest`. + #[cfg(not(feature = "branch_factor_256"))] if node.key().count() % 2 != 0 && node.value_digest().is_some() { return Err(ProofError::ValueAtOddNibbleLength); } diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 21a5154fa1ff..6f305c4b0eb1 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -561,6 +561,12 @@ fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator>(nibbles: Iter) -> Key { + nibbles.collect() +} + +#[cfg(not(feature = "branch_factor_256"))] fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { let mut data = Vec::with_capacity(nibbles.size_hint().0 / 2); @@ -620,10 +626,13 @@ mod tests { }; assert!(should_yield_elt); + #[cfg(not(feature = "branch_factor_256"))] assert_eq!( node.key_nibbles, vec![0x0B, 0x0E, 0x0E, 0x0F].into_boxed_slice() ); + #[cfg(feature = "branch_factor_256")] + assert_eq!(node.key_nibbles, vec![0xBE, 0xEF].into_boxed_slice()); assert_eq!(node.node.as_leaf().unwrap().value, SmallVec::from([0x42])); assert_eq!(node.next_nibble, None); @@ -643,7 +652,10 @@ mod tests { Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; + #[cfg(not(feature = "branch_factor_256"))] assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + #[cfg(feature = "branch_factor_256")] + assert_eq!(node.key_nibbles, vec![0].into_boxed_slice()); assert_eq!(node.next_nibble, Some(0)); assert!(node.node.as_branch().unwrap().value.is_none()); @@ -652,11 +664,19 @@ mod tests { Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; + #[cfg(not(feature = "branch_factor_256"))] assert_eq!( node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() ); + #[cfg(feature = "branch_factor_256")] + assert_eq!(node.key_nibbles, vec![0, 0, 0].into_boxed_slice()); + + #[cfg(not(feature = "branch_factor_256"))] assert_eq!(node.next_nibble, Some(0x0F)); + #[cfg(feature = "branch_factor_256")] + assert_eq!(node.next_nibble, Some(0xFF)); + assert_eq!( node.node.as_branch().unwrap().value, Some(vec![0x00, 0x00, 0x00].into_boxed_slice()), @@ -667,6 +687,7 @@ mod tests { Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; + #[cfg(not(feature = "branch_factor_256"))] assert_eq!( node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice() @@ -693,7 +714,10 @@ mod tests { Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; + // TODO: make this branch factor 16 compatible + #[cfg(not(feature = "branch_factor_256"))] assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + assert!(node.node.as_branch().unwrap().value.is_none()); assert_eq!(node.next_nibble, Some(0)); @@ -702,6 +726,7 @@ mod tests { Some(Err(e)) => panic!("{:?}", e), None => panic!("unexpected end of iterator"), }; + #[cfg(not(feature = "branch_factor_256"))] assert_eq!( node.key_nibbles, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() @@ -799,40 +824,34 @@ mod tests { assert_eq!(key, vec![0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert!(node.value.is_none()); - assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00]); // Covers case of branch with value let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); - assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00, 0x00]); // Covers case of leaf with partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); - assert_eq!(node.partial_path.to_vec(), vec![0x01]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); - assert_eq!(node.partial_path.to_vec(), vec![0x0F]); let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]); - assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO // Covers case of leaf with no partial path let (key, node) = stream.next().await.unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]); - assert_eq!(node.partial_path.to_vec(), vec![0x0F]); check_stream_is_done(stream).await; } @@ -1059,6 +1078,8 @@ mod tests { let mut stream = merkle.key_value_iter(); + println!("{}", merkle.dump().unwrap()); + assert_eq!( stream.next().await.unwrap().unwrap(), (branch.to_vec().into_boxed_slice(), branch.to_vec()) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index ad9bf3013be3..95c16808b4f0 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -28,6 +28,7 @@ criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } [features] logger = ["log"] +branch_factor_256 = [] [[bench]] name = "serializer" diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index d96bd5cd18b6..75b0030e0351 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::num::NonZeroU64; +use std::{array::from_fn, num::NonZeroU64}; use bincode::Options; use criterion::{criterion_group, criterion_main, Criterion}; @@ -27,27 +27,16 @@ fn branch(c: &mut Criterion) { let mut input = Node::Branch(Box::new(storage::BranchNode { partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()), - children: [ - Some(storage::Child::AddressWithHash( - NonZeroU64::new(1).unwrap(), - storage::TrieHash::from([0; 32]), - )), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ], + children: from_fn(|i| { + if i == 0 { + Some(storage::Child::AddressWithHash( + NonZeroU64::new(1).unwrap(), + storage::TrieHash::from([0; 32]), + )) + } else { + None + } + }), })); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); let benchfn = |b: &mut criterion::Bencher, input: &storage::Node| { diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index d0a6514a37b3..2248178bc8e5 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -42,7 +42,7 @@ impl Serialize for BranchNode { state.serialize_field("partial_path", &self.partial_path)?; state.serialize_field("value", &self.value)?; - let children: SmallVec<[(u8, LinearAddress, TrieHash); 16]> = self + let children: SmallVec<[(u8, LinearAddress, TrieHash); Self::MAX_CHILDREN]> = self .children .iter() .enumerate() @@ -71,12 +71,13 @@ impl<'de> Deserialize<'de> for BranchNode { struct SerializedBranchNode { partial_path: Path, value: Option>, - children: SmallVec<[(u8, LinearAddress, TrieHash); 16]>, + children: SmallVec<[(u8, LinearAddress, TrieHash); BranchNode::MAX_CHILDREN]>, } let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?; - let mut children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + let mut children: [Option; BranchNode::MAX_CHILDREN] = + [const { None }; BranchNode::MAX_CHILDREN]; for (offset, addr, hash) in s.children.iter() { children[*offset as usize] = Some(Child::AddressWithHash(*addr, hash.clone())); } @@ -119,6 +120,11 @@ impl Debug for BranchNode { impl BranchNode { /// The maximum number of children in a [BranchNode] + #[cfg(feature = "branch_factor_256")] + pub const MAX_CHILDREN: usize = 256; + + /// The maximum number of children in a [BranchNode] + #[cfg(not(feature = "branch_factor_256"))] pub const MAX_CHILDREN: usize = 16; /// Returns the address of the child at the given index. @@ -158,7 +164,7 @@ impl From<&LeafNode> for BranchNode { BranchNode { partial_path: leaf.partial_path.clone(), value: Some(Box::from(&leaf.value[..])), - children: Default::default(), + children: [const { None }; BranchNode::MAX_CHILDREN], } } } diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index f24ea01b903e..777d1a395f59 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -157,6 +157,17 @@ impl<'a> FusedIterator for NibblesIterator<'a> {} impl<'a> Iterator for NibblesIterator<'a> { type Item = u8; + #[cfg(feature = "branch_factor_256")] + fn next(&mut self) -> Option { + if self.is_empty() { + return None; + } + let result = self.data[self.head]; + self.head += 1; + Some(result) + } + + #[cfg(not(feature = "branch_factor_256"))] fn next(&mut self) -> Option { if self.is_empty() { return None; @@ -184,6 +195,11 @@ impl<'a> Iterator for NibblesIterator<'a> { } impl<'a> NibblesIterator<'a> { + #[cfg(not(feature = "branch_factor_256"))] + const BYTES_PER_NIBBLE: usize = 2; + #[cfg(feature = "branch_factor_256")] + const BYTES_PER_NIBBLE: usize = 1; + #[inline(always)] const fn is_empty(&self) -> bool { self.head == self.tail @@ -195,7 +211,7 @@ impl<'a> NibblesIterator<'a> { NibblesIterator { data, head: 0, - tail: 2 * data.len(), + tail: Self::BYTES_PER_NIBBLE * data.len(), } } } @@ -231,9 +247,11 @@ mod test { use std::fmt::Debug; use test_case::test_case; + #[cfg(not(feature = "branch_factor_256"))] static TEST_BYTES: [u8; 4] = [0xde, 0xad, 0xbe, 0xef]; #[test] + #[cfg(not(feature = "branch_factor_256"))] fn happy_regular_nibbles() { let iter = NibblesIterator::new(&TEST_BYTES); let expected = [0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; @@ -242,6 +260,7 @@ mod test { } #[test] + #[cfg(not(feature = "branch_factor_256"))] fn size_hint() { let mut iter = NibblesIterator::new(&TEST_BYTES); assert_eq!((8, Some(8)), iter.size_hint()); @@ -250,6 +269,7 @@ mod test { } #[test] + #[cfg(not(feature = "branch_factor_256"))] fn backwards() { let iter = NibblesIterator::new(&TEST_BYTES).rev(); let expected = [0xf, 0xe, 0xe, 0xb, 0xd, 0xa, 0xe, 0xd]; @@ -257,6 +277,7 @@ mod test { } #[test] + #[cfg(not(feature = "branch_factor_256"))] fn nth_back() { let mut iter = NibblesIterator::new(&TEST_BYTES); assert_eq!(iter.nth_back(0), Some(0xf)); @@ -277,6 +298,7 @@ mod test { } #[test] + #[cfg(not(feature = "branch_factor_256"))] fn not_empty_because_of_data() { let mut iter = NibblesIterator::new(&[1]); assert!(!iter.is_empty()); diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index a16895657b20..20232b9e22f9 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1145,6 +1145,8 @@ impl NodeStore { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { + use std::array::from_fn; + use crate::linear::memory::MemStore; use crate::{BranchNode, LeafNode}; use arc_swap::access::DynGuard; @@ -1232,7 +1234,13 @@ mod tests { #[test_case(BranchNode { partial_path: Path::from([6, 7, 8]), value: Some(vec![9, 10, 11].into_boxed_slice()), - children: [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into()))], + children: from_fn(|i| { + if i == 15 { + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + } else { + None + } + }), }; "branch node with 1 child")] #[test_case( Node::Leaf(LeafNode { From a4488aceb941409614c8f13d9966c26ba179ea58 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 4 Nov 2024 10:03:13 -0800 Subject: [PATCH 0627/1053] Lint cleanups (#748) --- firewood/src/db.rs | 54 +++++++++++++++--------------------- firewood/src/lib.rs | 14 ++++++++++ firewood/src/manager.rs | 2 ++ firewood/src/merkle.rs | 46 +++++++++++++++--------------- firewood/src/proof.rs | 29 +++++++++++++++++++ firewood/src/stream.rs | 4 +++ firewood/src/v2/api.rs | 52 +++++++++++++++++++++++++++------- firewood/src/v2/db.rs | 6 ++-- firewood/src/v2/emptydb.rs | 2 ++ firewood/src/v2/mod.rs | 7 ++++- fwdctl/src/graph.rs | 2 +- grpc-testtool/src/service.rs | 4 +-- storage/src/nodestore.rs | 2 +- 13 files changed, 150 insertions(+), 74 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index df5489c1f905..4564d925e348 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -15,28 +15,26 @@ use std::error::Error; use std::fmt; use std::io::Write; use std::path::Path; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use storage::{Committed, FileBacked, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash}; +use tokio::sync::RwLock; use typed_builder::TypedBuilder; #[derive(Debug)] #[non_exhaustive] +/// Represents the different types of errors that can occur in the database. pub enum DbError { - InvalidParams, + /// Merkle error occurred. Merkle(MerkleError), - CreateError, + /// I/O error occurred. IO(std::io::Error), - InvalidProposal, } impl fmt::Display for DbError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DbError::InvalidParams => write!(f, "invalid parameters provided"), DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), - DbError::CreateError => write!(f, "database create error"), DbError::IO(e) => write!(f, "I/O error: {e:?}"), - DbError::InvalidProposal => write!(f, "invalid proposal"), } } } @@ -51,6 +49,8 @@ impl Error for DbError {} type HistoricalRev = NodeStore; +/// Metrics for the database. +/// TODO: Add more metrics pub struct DbMetrics { proposals: metrics::Counter, } @@ -109,11 +109,13 @@ pub struct DbConfig { /// existing contents will be lost. #[builder(default = false)] pub truncate: bool, + /// Revision manager configuration. #[builder(default = RevisionManagerConfig::builder().build())] pub manager: RevisionManagerConfig, } #[derive(Debug)] +/// A database instance. pub struct Db { metrics: Arc, // TODO: consider using https://docs.rs/lock_api/latest/lock_api/struct.RwLock.html#method.upgradable_read @@ -134,20 +136,16 @@ where Self: 'p; async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { - let nodestore = self - .manager - .read() - .expect("poisoned lock") - .revision(root_hash)?; + let nodestore = self.manager.read().await.revision(root_hash)?; Ok(nodestore) } async fn root_hash(&self) -> Result, api::Error> { - Ok(self.manager.read().expect("poisoned lock").root_hash()?) + Ok(self.manager.read().await.root_hash()?) } async fn all_hashes(&self) -> Result, api::Error> { - Ok(self.manager.read().expect("poisoned lock").all_hashes()) + Ok(self.manager.read().await.all_hashes()) } async fn propose<'p, K: KeyType, V: ValueType>( @@ -157,11 +155,7 @@ where where Self: 'p, { - let parent = self - .manager - .read() - .expect("poisoned lock") - .current_revision(); + let parent = self.manager.read().await.current_revision(); let proposal = NodeStore::new(parent)?; let mut merkle = Merkle::from(proposal); for op in batch { @@ -177,10 +171,7 @@ where let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = Arc::new(nodestore.into()); - self.manager - .write() - .expect("poisoned lock") - .add_proposal(immutable.clone()); + self.manager.write().await.add_proposal(immutable.clone()); self.metrics.proposals.increment(1); @@ -193,6 +184,7 @@ where } impl Db { + /// Create a new database instance. pub async fn new>(db_path: P, cfg: DbConfig) -> Result { let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals"), @@ -211,24 +203,22 @@ impl Db { } /// Dump the Trie of the latest revision. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - let latest_rev_nodestore = self - .manager - .read() - .expect("poisoned lock") - .current_revision(); + pub async fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { + let latest_rev_nodestore = self.manager.read().await.current_revision(); let merkle = Merkle::from(latest_rev_nodestore); // TODO: This should be a stream let output = merkle.dump().map_err(DbError::Merkle)?; write!(w, "{}", output).map_err(DbError::IO) } + /// Get a copy of the database metrics pub fn metrics(&self) -> Arc { self.metrics.clone() } } #[derive(Debug)] +/// A user-visible database proposal pub struct Proposal<'p> { nodestore: Arc, FileBacked>>, db: &'p Db, @@ -299,7 +289,7 @@ impl<'a> api::Proposal for Proposal<'a> { self.db .manager .write() - .expect("poisoned lock") + .await .add_proposal(immutable.clone()); Ok(Self::Proposal { @@ -312,7 +302,7 @@ impl<'a> api::Proposal for Proposal<'a> { async fn commit(self: Arc) -> Result<(), api::Error> { match Arc::into_inner(self) { Some(proposal) => { - let mut manager = proposal.db.manager.write().expect("poisoned lock"); + let mut manager = proposal.db.manager.write().await; Ok(manager.commit(proposal.nodestore.clone())?) } None => Err(api::Error::CannotCommitClonedProposal), diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 26037445aea2..2d0d15f45224 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -105,13 +105,27 @@ //! A commit involves simply writing the nodes and the freelist to disk. If the proposal is //! abandoned, nothing has actually been written to disk. //! +#![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] +/// Database module for Firewood. pub mod db; + +/// Database manager module pub mod manager; + +/// Merkle module, containing merkle operations pub mod merkle; + +/// Proof module pub mod proof; + +/// Range proof module pub mod range_proof; + +/// Stream module, for both node and key-value streams pub mod stream; +/// Version 2 API pub mod v2; +/// Expose the storage logger pub use storage::logger; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 289ad5702d82..d5ba09ab768f 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -17,11 +17,13 @@ use crate::v2::api::HashKey; use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; #[derive(Clone, Debug, TypedBuilder)] +/// Revision manager configuratoin pub struct RevisionManagerConfig { /// The number of historical revisions to keep in memory. #[builder(default = 128)] max_revisions: usize, + /// The size of the node cache #[builder(default_code = "NonZero::new(1500000).expect(\"non-zero\")")] node_cache_size: NonZero, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 66812c31d5da..4382c750a023 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -23,29 +23,23 @@ use storage::{ use thiserror::Error; +/// Keys are boxed u8 slices pub type Key = Box<[u8]>; + +/// Values are vectors +/// TODO: change to Box<[u8]> pub type Value = Vec; #[derive(Debug, Error)] +/// Errors that can occur when interacting with the Merkle trie pub enum MerkleError { + /// Can't generate proof for empty node #[error("can't generate proof for empty node")] Empty, - #[error("read only")] - ReadOnly, - #[error("node not a branch node")] - NotBranchNode, + + /// IO error #[error("IO error: {0:?}")] IO(#[from] std::io::Error), - #[error("parent should not be a leaf branch")] - ParentLeafBranch, - #[error("removing internal node references failed")] - UnsetInternal, - #[error("merkle serde error: {0}")] - BinarySerdeError(String), - #[error("invalid utf8")] - UTF8Error, - #[error("node not found")] - NodeNotFound, } // convert a set of nibbles into a printable string @@ -141,12 +135,13 @@ fn get_helper( } #[derive(Debug)] +/// Merkle operations against a nodestore pub struct Merkle { nodestore: T, } impl Merkle { - pub fn into_inner(self) -> T { + pub(crate) fn into_inner(self) -> T { self.nodestore } } @@ -158,11 +153,12 @@ impl From for Merkle { } impl Merkle { - pub fn root(&self) -> Option> { + pub(crate) fn root(&self) -> Option> { self.nodestore.root_node() } - pub const fn nodestore(&self) -> &T { + #[cfg(test)] + pub(crate) const fn nodestore(&self) -> &T { &self.nodestore } @@ -211,6 +207,7 @@ impl Merkle { Ok(Proof(proof.into_boxed_slice())) } + /// Verify a proof that a key has a certain value, or that the key isn't in the trie. pub fn verify_range_proof>( &self, _proof: &Proof, @@ -222,7 +219,10 @@ impl Merkle { todo!() } - pub fn path_iter<'a>(&self, key: &'a [u8]) -> Result, MerkleError> { + pub(crate) fn path_iter<'a>( + &self, + key: &'a [u8], + ) -> Result, MerkleError> { PathIterator::new(&self.nodestore, key) } @@ -329,14 +329,14 @@ impl Merkle { }) } - pub fn get_value(&self, key: &[u8]) -> Result>, MerkleError> { + pub(crate) fn get_value(&self, key: &[u8]) -> Result>, MerkleError> { let Some(node) = self.get_node(key)? else { return Ok(None); }; Ok(node.value().map(|v| v.to_vec().into_boxed_slice())) } - pub fn get_node(&self, key: &[u8]) -> Result>, MerkleError> { + pub(crate) fn get_node(&self, key: &[u8]) -> Result>, MerkleError> { let Some(root) = self.root() else { return Ok(None); }; @@ -347,7 +347,7 @@ impl Merkle { } impl Merkle { - pub fn dump_node( + pub(crate) fn dump_node( &self, addr: LinearAddress, hash: Option<&TrieHash>, @@ -392,7 +392,7 @@ impl Merkle { Ok(()) } - pub fn dump(&self) -> Result { + pub(crate) fn dump(&self) -> Result { let mut result = vec![]; writeln!(result, "digraph Merkle {{\n rankdir=LR;")?; if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { @@ -417,6 +417,8 @@ impl From>> } impl Merkle> { + /// Convert a merkle backed by an MutableProposal into an ImmutableProposal + /// TODO: We probably don't need this function pub fn hash(self) -> Merkle, S>> { self.into() } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8e82ade7908f..6168d05553bf 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -9,36 +9,64 @@ use storage::{ use thiserror::Error; #[derive(Debug, Error)] +/// Reasons why a proof is invalid pub enum ProofError { + /// Non-monotonic range decrease #[error("non-monotonic range increase")] NonMonotonicIncreaseRange, + + /// Unexpected hash #[error("unexpected hash")] UnexpectedHash, + + /// Unexpected value #[error("unexpected value")] UnexpectedValue, + + /// Value mismatch #[error("value mismatch")] ValueMismatch, + + /// Expected value but got None #[error("expected value but got None")] ExpectedValue, + + /// Proof is empty #[error("proof can't be empty")] Empty, + + /// Each proof node key should be a prefix of the proven key #[error("each proof node key should be a prefix of the proven key")] ShouldBePrefixOfProvenKey, + + /// Each proof node key should be a prefix of the next key #[error("each proof node key should be a prefix of the next key")] ShouldBePrefixOfNextKey, + + /// Child index is out of bounds #[error("child index is out of bounds")] ChildIndexOutOfBounds, + + /// Only nodes with even length key can have values #[error("only nodes with even length key can have values")] ValueAtOddNibbleLength, + + /// Node not in trie #[error("node not in trie")] NodeNotInTrie, + + /// Error from the merkle package #[error("{0:?}")] Merkle(#[from] MerkleError), + + /// Empty range #[error("empty range")] EmptyRange, } #[derive(Clone, Debug)] + +/// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. pub key: Box<[u8]>, @@ -104,6 +132,7 @@ impl From<&ProofNode> for TrieHash { pub struct Proof(pub Box<[T]>); impl Proof { + /// Verify a proof pub fn verify, V: AsRef<[u8]>>( &self, key: K, diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 6f305c4b0eb1..097f02a2a214 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -62,6 +62,7 @@ enum NodeStreamState { } #[derive(Debug)] +/// A stream of nodes in order starting from a specific point in the trie. pub struct MerkleNodeStream<'a, T> { state: NodeStreamState, merkle: &'a T, @@ -284,6 +285,7 @@ impl<'a, T: TrieReader> MerkleKeyValueStreamState<'a, T> { } #[derive(Debug)] +/// A stream of key-value pairs in order starting from a specific point in the trie. pub struct MerkleKeyValueStream<'a, T> { state: MerkleKeyValueStreamState<'a, T>, merkle: &'a T, @@ -305,6 +307,8 @@ impl<'a, T: TrieReader> FusedStream for MerkleKeyValueStream<'a, T> { } impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { + /// Construct a [MerkleKeyValueStream] that will iterate over all the key-value pairs in `merkle` + /// starting from a particular key pub fn from_key>(merkle: &'a T, key: K) -> Self { Self { state: MerkleKeyValueStreamState::from(key.as_ref()), diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index a1570adde1e1..b441fc3e01a9 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -39,8 +39,19 @@ pub type HashKey = storage::TrieHash; /// supported #[derive(Debug)] pub enum BatchOp { - Put { key: K, value: V }, - Delete { key: K }, + /// Upsert a key/value pair + Put { + /// the key + key: K, + /// the value + value: V, + }, + + /// Delete a key + Delete { + /// The key + key: K, + }, } /// A list of operations to consist of a batch that @@ -63,49 +74,66 @@ pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch {end_key:?}")] InvalidRange { + /// The provided starting key start_key: Box<[u8]>, + /// The provided ending key end_key: Box<[u8]>, }, #[error("IO error: {0}")] + /// An IO error occurred IO(#[from] std::io::Error), - #[error("Invalid proposal")] - InvalidProposal, - - // Cloned proposals are problematic because if they are committed, then you could - // create another proposal from this committed proposal, so we error at commit time - // if there are outstanding clones + /// Cannot commit a cloned proposal + /// + /// Cloned proposals are problematic because if they are committed, then you could + /// create another proposal from this committed proposal, so we error at commit time + /// if there are outstanding clones #[error("Cannot commit a cloned proposal")] CannotCommitClonedProposal, + /// Internal error #[error("Internal error")] InternalError(Box), + /// Range too small #[error("Range too small")] RangeTooSmall, + /// Request RangeProof for empty trie #[error("request RangeProof for empty trie")] RangeProofOnEmptyTrie, + /// Request RangeProof for empty range #[error("the latest revision is empty and has no root hash")] LatestIsEmpty, + /// This is not the latest proposal #[error("commit the parents of this proposal first")] NotLatest, + /// Sibling already committed #[error("sibling already committed")] SiblingCommitted, + /// Generic merkle error #[error("merkle error: {0}")] Merkle(#[from] MerkleError), } @@ -125,8 +153,10 @@ impl From for Error { /// is the api::DbView trait defined next. #[async_trait] pub trait Db { + /// The type of a historical revision type Historical: DbView; + /// The type of a proposal type Proposal<'p>: DbView + Proposal where Self: 'p; @@ -173,6 +203,7 @@ pub trait Db { /// A [Proposal] requires implementing DbView #[async_trait] pub trait DbView { + /// The type of a stream of key/value pairs type Stream<'a>: Stream, Vec), Error>> where Self: 'a; @@ -238,6 +269,7 @@ pub trait DbView { /// obtain proofs. #[async_trait] pub trait Proposal: DbView + Send + Sync { + /// The type of a proposal type Proposal: DbView + Proposal; /// Commit this revision diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index 6caca6509c97..c544faca1948 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -1,7 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{db::DbError, v2::api}; +use crate::db::DbError; +use crate::v2::api; #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid @@ -17,11 +18,8 @@ use crate::{db::DbError, v2::api}; impl From for api::Error { fn from(value: DbError) -> Self { match value { - DbError::InvalidParams => api::Error::InternalError(Box::new(value)), DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), - DbError::CreateError => api::Error::InternalError(Box::new(value)), DbError::IO(e) => api::Error::IO(e), - DbError::InvalidProposal => api::Error::InvalidProposal, } } } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index cd171cb65351..77cb9cf1a7c3 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -88,6 +88,8 @@ impl DbView for HistoricalImpl { } } +#[derive(Debug)] +/// An empty streamer that doesn't stream any data pub struct EmptyStreamer; impl Stream for EmptyStreamer { diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 454b1d3cfeca..b1a3a938ba07 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -1,9 +1,14 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +/// The public API pub mod api; + +/// The database pub mod db; + +/// The proposal pub mod propose; -// #[cfg(test)] +/// An empty database implementation for testing pub mod emptydb; diff --git a/fwdctl/src/graph.rs b/fwdctl/src/graph.rs index 833c1e140fe9..e070169e4b24 100644 --- a/fwdctl/src/graph.rs +++ b/fwdctl/src/graph.rs @@ -22,6 +22,6 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; - db.dump(&mut stdout())?; + db.dump(&mut stdout()).await?; Ok(()) } diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index 163cb006c4ac..9d15705447ec 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -32,9 +32,7 @@ impl IntoStatusResultExt for Result { Error::IncorrectRootHash { .. } | Error::HashNotFound { .. } | Error::RangeTooSmall => { Status::invalid_argument(err.to_string()) } - Error::IO { .. } | Error::InternalError { .. } | Error::InvalidProposal => { - Status::internal(err.to_string()) - } + Error::IO { .. } | Error::InternalError { .. } => Status::internal(err.to_string()), _ => Status::internal(err.to_string()), }) } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 20232b9e22f9..e30e4d82f2df 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -709,7 +709,7 @@ impl PartialEq for NodeStoreParent { impl Eq for NodeStoreParent {} -#[derive(Clone, Debug)] +#[derive(Debug)] /// Contains state for a proposed revision of the trie. pub struct ImmutableProposal { /// Address --> Node for nodes created in this proposal. From a6599acf814f1b524aa170050c2030f3370a06f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:30:43 +0000 Subject: [PATCH 0628/1053] build(deps): update thiserror requirement from 1.0.57 to 2.0.3 (#751) --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index dac5243c81f2..010491b76c2a 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -24,7 +24,7 @@ hex = "0.4.3" metrics = "0.24.0" serde = { version = "1.0" } sha2 = "0.10.8" -thiserror = "1.0.57" +thiserror = "2.0.3" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.20.0" bincode = "1.3.3" From db086dac39473a4b8ec70cd3ee89c31d030586df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:40:14 +0000 Subject: [PATCH 0629/1053] build(deps): update pprof requirement from 0.13.0 to 0.14.0 (#750) --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 010491b76c2a..2392d519b40a 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -46,7 +46,7 @@ criterion = {version = "0.5.1", features = ["async_tokio"]} rand = "0.8.5" triehash = "0.8.4" clap = { version = "4.5.0", features = ['derive'] } -pprof = { version = "0.13.0", features = ["flamegraph"] } +pprof = { version = "0.14.0", features = ["flamegraph"] } tempfile = "3.12.0" [[bench]] From 581de903c45000a07bc4e8e3169cb540e282730f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 15 Nov 2024 12:25:58 -0800 Subject: [PATCH 0630/1053] Benchmark README updates (#752) --- benchmark/README.md | 104 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index c79404aa05ae..6de3cdf5d2e5 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -24,6 +24,86 @@ There are currently three different benchmarks, as follows: There is also a `create` benchmark which creates the database to begin with. The defaults will create a 10M row database. If you want a larger one, increase the number of batches. +## Benchmark Specification + +This describes each benchmark in detail, and forms a standard for how to test merkle databases. The pseudo-code for each +benchmark is provided. + +Note that the `commit` operation is expected to persist the database and compute the root hash for each merkle. + +### create + +Inserts are done starting from an empty database, 10,000 rows at a time, starting with row id 0 and increasing by 1 for each row. +The key and data consist of the SHA-256 of the row id, in native endian format. + +```rust + for key in (0..N) { + let key_and_data = sha256(key.to_ne_bytes()); + testdb.insert(key_and_data, key_and_data); + if key % 10000 == 9999 { + testdb.commit(); + } + } +``` + +Exception: when creating the 1B row database, 100,000 rows at a time are added. + +### tenkrandom + +This test uses two variables, low and high, which initially start off at 0 and N, where N is the number of rows in the database. +A batch consists of deleting 2,500 rows from low to low+2499, 2,500 new insertions from high to high+2499, and 5,000 updates from (low+high)/2 to (low+high)/2+4999. The key is computed based on the sha256 as when the database is created, but the data is set to the sha256 of the value of 'low', to ensure that the data is updated. Once a batch is completed, low and high are increased by 2,500. + +```rust + let low = 0; + let high = N; + loop { + let hashed_low = sha256(low.to_ne_bytes()); + for key in (low..low+2499) { + testdb.delete(hashed_key); + let hashed_key = sha256(key.to_ne_bytes()); + } + for key in (high..high+2499) { + let hashed_key = sha256(key.to_ne_bytes()); + testdb.insert(hashed_key, hashed_low); + } + for key in ((low+high)/2, (low+high)/2+4999) { + let hashed_key = sha256(key.to_ne_bytes()); + testdb.upsert(hashed_key, hashed_low); + } + testdb.commit(); + } +``` + +### zipf + +A zipf distribution with an exponent of 1.2 on the total number of inserted rows is used to compute which rows to update with a batch of 10,000 rows. Note that this results in duplicates -- the duplicates are passed to the database for resolution. + +```rust + for id in (0..N) { + let key = zipf(1.2, N); + let hashed_key = sha256(key.to_ne_bytes()); + let hashed_data = sha256(id.to_ne_bytes()); + testdb.upsert(hashed_key, hashed_data); + if id % 10000 == 9999 { + testdb.commit(); + } + } + +``` + +### single + +This test repeatedly updates the first row. + +```rust + let hashed_key = sha256(0.to_ne_bytes()); + for id in (0..) { + let hashed_data = sha256(id.to_ne_bytes()); + testdb.upsert(hashed_key, hashed_data); + testdb.commit(); + } +``` + ## Installation To install the Firewood Benchmark, follow these steps: @@ -41,14 +121,14 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a 1. Log in to the AWS EC2 console 2. Launch a new instance: - a. Name: Firewood Benchmark - b. Click on 'Ubuntu' in the quick start section - c. Set the instance type to m5d.2xlarge - d. Set your key pair - e. Open SSH, HTTP, and port 3000 so prometheus can be probed remotely (we use a 'firewood-bench' security group) - f. Configure storage to 20GiB disk - g. \[optional] Save money by selecting 'spot instance' in advanced - h. Launch the instance + 1. Name: Firewood Benchmark + 2. Click on 'Ubuntu' in the quick start section + 3. Set the instance type to m5d.2xlarge + 4. Set your key pair + 5. Open SSH, HTTP, and port 3000 so prometheus can be probed remotely (we use a 'firewood-bench' security group) + 6. Configure storage to 20GiB disk + 7. \[optional] Save money by selecting 'spot instance' in advanced + 8. Launch the instance 3. ssh ubuntu@AWS-IP 4. Run the script in setup.sh on the instance as root 5. Log in to grafana on @@ -70,21 +150,21 @@ Since the benchmark is in two phases, you may want to create the database first examine the steady-state performance second. This can easily be accomplished with a few command line options. -To pre-create the database, use the following command: +To create test databases, use the following command: ```sh -nohup time cargo run --profile maxperf --bin benchmark -- create + nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 create ``` Then, you can look at nohup.out and see how long the database took to initialize. Then, to run the second phase, use: ```sh -nohup time cargo run --profile maxperf --bin benchmark -- -zipf + nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 zipf ``` If you're looking for detailed logging, there are some command line options to enable it. For example, to enable debug logging for the single benchmark, you can use the following: ```sh -cargo run --profile release --bin benchmark -- -l debug single + cargo run --profile release --bin benchmark -- -l debug -n 10000 single ``` From e57a0ad932987491e58b2ba176a7c1b307657533 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 18 Nov 2024 09:18:54 -0800 Subject: [PATCH 0631/1053] Single now takes batch size argument (#753) --- benchmark/src/main.rs | 6 +++++- benchmark/src/single.rs | 15 ++++++++++----- benchmark/src/zipf.rs | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 9c8b2e49d9cb..30d4aff6a434 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -84,7 +84,7 @@ mod single; mod tenkrandom; mod zipf; -#[derive(Debug, Subcommand)] +#[derive(Debug, Subcommand, PartialEq)] enum TestName { Create, TenKRandom, @@ -122,6 +122,10 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; async fn main() -> Result<(), Box> { let args = Args::parse(); + if args.test_name == TestName::Single && args.batch_size > 1000 { + panic!("Single test is not designed to handle batch sizes > 1000"); + } + env_logger::Builder::new() .filter_level(match args.global_opts.log_level.as_str() { "debug" => LevelFilter::Debug, diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index b43684b98f69..e90ae354416f 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -16,14 +16,19 @@ pub struct Single; impl TestRunner for Single { async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { let start = Instant::now(); - let inner_key = Sha256::digest(0u64.to_ne_bytes()).to_vec(); + let inner_keys: Vec<_> = (0..args.batch_size) + .map(|i| Sha256::digest(i.to_ne_bytes())) + .collect(); let mut batch_id = 0; while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { - let batch = vec![BatchOp::Put { - key: inner_key.clone(), - value: vec![batch_id as u8], - }]; + let batch = inner_keys + .iter() + .map(|key| BatchOp::Put { + key, + value: vec![batch_id as u8], + }) + .collect(); let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 4e6dfba620ba..a054abdfbc78 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -13,7 +13,7 @@ use std::collections::HashSet; use std::error::Error; use std::time::Instant; -#[derive(clap::Args, Debug)] +#[derive(clap::Args, Debug, PartialEq)] pub struct Args { #[arg(short, long, help = "zipf exponent", default_value_t = 1.2)] exponent: f64, From 02aba8efc59e3b5a63d81ae1987283cc6b95605b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Nov 2024 10:55:11 -0800 Subject: [PATCH 0632/1053] Fix deprecated gh-pages action (#754) --- .github/workflows/gh-pages.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index 2c00c1f3810e..baca4f46067f 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -44,11 +44,13 @@ jobs: run: | cp -rv target/doc/* ./_site cp -rv docs/assets ./_site - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: pages path: _site if-no-files-found: error + overwrite: true + include-hidden-files: true deploy: needs: build permissions: From ded2f28cbe69c9809d6608393624eca998060644 Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:21:41 +1300 Subject: [PATCH 0633/1053] Add build prerequisites to readme (#756) Co-authored-by: Ron Kuris --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e0e20ae3881..a2e9bd23d56a 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,20 @@ Firewood guarantees recoverability by not referencing the new nodes in a new rev - [ ] Implement a node cache - [ ] Hook up the RPC +## Build + +In order to build firewood, `protoc` must be installed. See instructions for installation [here](https://grpc.io/docs/protoc-installation/). + +On Mac, you can install via brew: +``` +brew install protobuf +``` + ## Run There are several examples, in the examples directory, that simulate real world use-cases. Try running them via the command-line, via `cargo run --release ---example simple`. +--example insert`. For maximum performance, use `cargo run --maxperf` instead, which enables maximum link time compiler optimizations, but takes a lot longer to compile. From 08d4833ca91c25b4d777b64d7b314ad145bee17d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Nov 2024 11:51:19 -0800 Subject: [PATCH 0634/1053] Rkuris/manual serialization (#757) --- RELEASE.md | 10 +- benchmark/src/main.rs | 14 +- firewood/benches/hashops.rs | 5 +- storage/Cargo.toml | 3 + storage/benches/serializer.rs | 99 +++++++- storage/src/linear/filebacked.rs | 120 +++++++++- storage/src/node/branch.rs | 17 +- storage/src/node/mod.rs | 398 +++++++++++++++++++++++++++++++ storage/src/nodestore.rs | 109 +++------ 9 files changed, 660 insertions(+), 115 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 2c41c04507f0..6525f1599ca7 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,20 +1,16 @@ # Releasing firewood -Releasing firewood is straightforward and can be done entirely in CI. +Releasing firewood is straightforward and can be done entirely in CI. Firewood is made up of several sub-projects in a workspace. Each project is in -its own crate and has an independent version. -* firewood -* growth-ring -* libaio -* shale +its own crate and has an independent version. The first step in drafting a release is ensuring all crates within the firewood project are using the version of the new release. There is a utility to ensure all versions are updated simultaneously in `cargo-workspace-version`. To use it to update to 0.0.4, for example: - $ cargo install cargo-workspace-version $ cargo workspace-version update + \$ cargo install cargo-workspace-version $ cargo workspace-version update v0.0.5 See the [source code](https://github.com/ava-labs/cargo-workspace-version) for diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 30d4aff6a434..a4f2a9c453e4 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -36,9 +36,19 @@ struct Args { cache_size: NonZeroUsize, #[arg(short, long, default_value_t = 128)] revisions: usize, - #[arg(short = 'p', long, default_value_t = 3000)] + #[arg( + short = 'p', + long, + default_value_t = 3000, + help = "Port to listen for prometheus" + )] prometheus_port: u16, - #[arg(short = 's', long, default_value_t = false)] + #[arg( + short = 's', + long, + default_value_t = false, + help = "Dump prometheus stats on exit" + )] stats_dump: bool, #[clap(flatten)] diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index d875db63262a..b6992b563814 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -14,7 +14,7 @@ use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path}; use storage::{MemStore, NodeStore}; // To enable flamegraph output -// cargo bench --bench shale-bench -- --profile-time=N +// cargo bench --bench hashops -- --profile-time=N enum FlamegraphProfiler { Init(c_int), Active(ProfilerGuard<'static>), @@ -137,8 +137,7 @@ fn bench_db(criterion: &mut Criterion) { criterion_group! { name = benches; config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); - // targets = bench_trie_hash, bench_merkle::<3, 32>, bench_db::<100> - targets = bench_merkle::<3, 4>, bench_merkle<3, 32>, bench_db<100> + targets = bench_merkle::<3, 4>, bench_merkle<3, 32>, bench_db::<100> } criterion_main!(benches); diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 95c16808b4f0..adb8e161d1c7 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -20,11 +20,14 @@ metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" +bitfield = "0.17.0" [dev-dependencies] rand = "0.8.5" test-case = "3.3.1" criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } +pprof = { version = "0.14.0", features = ["flamegraph"] } +tempfile = "3.12.0" [features] logger = ["log"] diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 75b0030e0351..99f8e143c240 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -1,31 +1,84 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{array::from_fn, num::NonZeroU64}; +use std::{array::from_fn, fs::File, num::NonZeroU64, os::raw::c_int}; use bincode::Options; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, profiler::Profiler, Criterion}; +use pprof::ProfilerGuard; use smallvec::SmallVec; use storage::{LeafNode, Node, Path}; +use std::path::Path as FsPath; + +// For flamegraphs: +// cargo bench --bench serializer -- --profile-time=5 + +enum FlamegraphProfiler { + Init(c_int), + Active(ProfilerGuard<'static>), +} + +fn file_error_panic(path: &FsPath) -> impl FnOnce(T) -> U + '_ { + |_| panic!("Error on file `{}`", path.display()) +} + +impl Profiler for FlamegraphProfiler { + #[allow(clippy::unwrap_used)] + fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &FsPath) { + if let Self::Init(frequency) = self { + let guard = ProfilerGuard::new(*frequency).unwrap(); + *self = Self::Active(guard); + } + } + + #[allow(clippy::unwrap_used)] + fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &FsPath) { + std::fs::create_dir_all(benchmark_dir).unwrap(); + let filename = "firewood-flamegraph.svg"; + let flamegraph_path = benchmark_dir.join(filename); + #[allow(clippy::unwrap_used)] + let flamegraph_file = + File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); + + #[allow(clippy::unwrap_used)] + if let Self::Active(profiler) = self { + profiler + .report() + .build() + .unwrap() + .flamegraph(flamegraph_file) + .unwrap_or_else(file_error_panic(&flamegraph_path)); + } + } +} + fn leaf(c: &mut Criterion) { let mut group = c.benchmark_group("leaf"); let input = Node::Leaf(LeafNode { - partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + partial_path: Path(SmallVec::from_slice(&[0, 1])), value: SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), }); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); - group.bench_with_input("leaf", &input, |b, input| { + group.bench_with_input("serde", &input, |b, input| { b.iter(|| { serializer.serialize(input).unwrap(); }) }); + + group.bench_with_input("manual", &input, |b, input| { + b.iter(|| { + let mut bytes = Vec::::new(); + input.as_bytes(0, &mut bytes); + }) + }); + group.finish(); } fn branch(c: &mut Criterion) { - let mut group = c.benchmark_group("branch"); + let mut group = c.benchmark_group("has_value"); let mut input = Node::Branch(Box::new(storage::BranchNode { - partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + partial_path: Path(SmallVec::from_slice(&[0, 1])), value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()), children: from_fn(|i| { if i == 0 { @@ -39,24 +92,46 @@ fn branch(c: &mut Criterion) { }), })); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); - let benchfn = |b: &mut criterion::Bencher, input: &storage::Node| { + let serde_serializer = |b: &mut criterion::Bencher, input: &storage::Node| { b.iter(|| { serializer.serialize(input).unwrap(); }) }; - group.bench_with_input("1_child+has_value", &input, benchfn); + let manual_serializer = |b: &mut criterion::Bencher, input: &storage::Node| { + b.iter(|| { + let mut bytes = Vec::new(); + input.as_bytes(0, &mut bytes); + }) + }; + + group.bench_with_input("serde", &input, serde_serializer); + group.bench_with_input("manual", &input, manual_serializer); + group.finish(); + let mut group = c.benchmark_group("1_child"); input.as_branch_mut().unwrap().value = None; - group.bench_with_input("1_child", &input, benchfn); + group.bench_with_input("serde", &input, serde_serializer); + group.bench_with_input("manual", &input, manual_serializer); let child = input.as_branch().unwrap().children[0].clone(); + group.finish(); + let mut group = c.benchmark_group("2_child"); input.as_branch_mut().unwrap().children[1] = child.clone(); - group.bench_with_input("2_child", &input, benchfn); + group.bench_with_input("serde", &input, serde_serializer); + group.bench_with_input("manual", &input, manual_serializer); + group.finish(); + let mut group = c.benchmark_group("16_child"); input.as_branch_mut().unwrap().children = std::array::from_fn(|_| child.clone()); - group.bench_with_input("16_child", &input, benchfn); + group.bench_with_input("serde", &input, serde_serializer); + group.bench_with_input("manual", &input, manual_serializer); + group.finish(); } -criterion_group!(serializers, leaf, branch); +criterion_group!( + name = serializers; + config = Criterion::default().with_profiler(FlamegraphProfiler::Init(100)); + targets = leaf, branch +); criterion_main!(serializers); diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 529a56f5d6b3..4e98919fbcb6 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -53,9 +53,7 @@ impl FileBacked { impl ReadableStorage for FileBacked { fn stream_from(&self, addr: u64) -> Result, Error> { - let mut fd = self.fd.lock().expect("p"); - fd.seek(std::io::SeekFrom::Start(addr))?; - Ok(Box::new(fd.try_clone().expect("poisoned lock"))) + Ok(Box::new(PredictiveReader::new(self, addr))) } fn size(&self) -> Result { @@ -112,3 +110,119 @@ impl WritableStorage for FileBacked { guard.put(addr, next); } } + +/// A reader that can predictively read from a file, avoiding reading past boundaries, but reading in 1k chunks +struct PredictiveReader { + fd: File, + buffer: [u8; Self::PREDICTIVE_READ_BUFFER_SIZE], + offset: u64, + len: usize, + pos: usize, +} + +impl PredictiveReader { + const PREDICTIVE_READ_BUFFER_SIZE: usize = 1024; + + fn new(fb: &FileBacked, start: u64) -> Self { + let fd = fb + .fd + .lock() + .expect("poisoned lock") + .try_clone() + .expect("resource exhaustion"); + + Self { + fd, + buffer: [0u8; Self::PREDICTIVE_READ_BUFFER_SIZE], + offset: start, + len: 0, + pos: 0, + } + } +} + +impl Read for PredictiveReader { + fn read(&mut self, buf: &mut [u8]) -> Result { + if self.len == self.pos { + let bytes_left_in_page = Self::PREDICTIVE_READ_BUFFER_SIZE + - (self.offset % Self::PREDICTIVE_READ_BUFFER_SIZE as u64) as usize; + self.fd.seek(std::io::SeekFrom::Start(self.offset))?; + let read = self.fd.read(&mut self.buffer[..bytes_left_in_page])?; + self.offset += read as u64; + self.len = read; + self.pos = 0; + } + let max_to_return = std::cmp::min(buf.len(), self.len - self.pos); + buf[..max_to_return].copy_from_slice(&self.buffer[self.pos..self.pos + max_to_return]); + self.pos += max_to_return; + Ok(max_to_return) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn basic_reader_test() { + let mut tf = NamedTempFile::new().unwrap(); + let path = tf.path().to_path_buf(); + let output = tf.as_file_mut(); + write!(output, "hello world").unwrap(); + + // whole thing at once, this is always less than 1K so it should + // read the whole thing in + let fb = FileBacked::new( + path, + NonZero::new(10).unwrap(), + NonZero::new(10).unwrap(), + false, + ) + .unwrap(); + let mut reader = fb.stream_from(0).unwrap(); + let mut buf: String = String::new(); + assert_eq!(reader.read_to_string(&mut buf).unwrap(), 11); + assert_eq!(buf, "hello world".to_string()); + assert_eq!(0, reader.read(&mut [0u8; 1]).unwrap()); + + // byte at a time + let mut reader = fb.stream_from(0).unwrap(); + for ch in b"hello world" { + let mut buf = [0u8; 1]; + let read = reader.read(&mut buf).unwrap(); + assert_eq!(read, 1); + assert_eq!(buf[0], *ch); + } + assert_eq!(0, reader.read(&mut [0u8; 1]).unwrap()); + + // with offset + let mut reader = fb.stream_from(6).unwrap(); + buf = String::new(); + assert_eq!(reader.read_to_string(&mut buf).unwrap(), 5); + assert_eq!(buf, "world".to_string()); + } + + #[test] + fn big_file() { + let mut tf = NamedTempFile::new().unwrap(); + let path = tf.path().to_path_buf(); + let output = tf.as_file_mut(); + for _ in 0..1000 { + write!(output, "hello world").unwrap(); + } + + let fb = FileBacked::new( + path, + NonZero::new(10).unwrap(), + NonZero::new(10).unwrap(), + false, + ) + .unwrap(); + let mut reader = fb.stream_from(0).unwrap(); + let mut buf: String = String::new(); + assert_eq!(reader.read_to_string(&mut buf).unwrap(), 11000); + assert_eq!(buf.len(), 11000); + } +} diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 2248178bc8e5..6aaf8bb8291e 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -8,6 +8,7 @@ use crate::{LeafNode, LinearAddress, Node, Path, TrieHash}; use std::fmt::{Debug, Error as FmtError, Formatter}; #[derive(PartialEq, Eq, Clone, Debug)] +#[repr(C)] /// A child of a branch node. pub enum Child { /// There is a child at this index, but we haven't hashed it @@ -42,7 +43,7 @@ impl Serialize for BranchNode { state.serialize_field("partial_path", &self.partial_path)?; state.serialize_field("value", &self.value)?; - let children: SmallVec<[(u8, LinearAddress, TrieHash); Self::MAX_CHILDREN]> = self + let children: SmallVec<[(u8, LinearAddress, &TrieHash); Self::MAX_CHILDREN]> = self .children .iter() .enumerate() @@ -51,9 +52,7 @@ impl Serialize for BranchNode { Some(Child::Node(_)) => { panic!("serializing in-memory node for disk storage") } - Some(Child::AddressWithHash(addr, hash)) => { - Some((offset as u8, *addr, (*hash).clone())) - } + Some(Child::AddressWithHash(addr, hash)) => Some((offset as u8, *addr, hash)), }) .collect(); @@ -92,18 +91,16 @@ impl<'de> Deserialize<'de> for BranchNode { impl Debug for BranchNode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - write!(f, "[Branch")?; + write!(f, "[BranchNode")?; write!(f, r#" path="{:?}""#, self.partial_path)?; for (i, c) in self.children.iter().enumerate() { match c { None => {} Some(Child::Node(_)) => {} //TODO - Some(Child::AddressWithHash(addr, hash)) => write!( - f, - "(index: {i:?}), address={addr:?}, hash={:?})", - hex::encode(hash), - )?, + Some(Child::AddressWithHash(addr, hash)) => { + write!(f, "({i:?}: address={addr:?} hash={})", hex::encode(hash),)? + } } } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index b6d068176089..554b3f352e3a 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -1,9 +1,14 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use bitfield::bitfield; use enum_as_inner::EnumAsInner; +use integer_encoding::{VarIntReader as _, VarIntWriter as _}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; +use std::io::{Error, ErrorKind, Read, Write}; +use std::num::NonZero; +use std::vec; use std::{fmt::Debug, sync::Arc}; mod branch; @@ -20,6 +25,7 @@ use crate::Path; // TODO: explain why Branch is boxed but Leaf is not #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Serialize, Deserialize)] +#[repr(C)] pub enum Node { /// This node is a [BranchNode] Branch(Box), @@ -48,6 +54,105 @@ impl From for Node { } } +#[cfg(not(feature = "branch_factor_256"))] +bitfield! { + struct BranchFirstByte(u8); + impl Debug; + impl new; + u8; + has_value, set_has_value: 1, 1; + number_children, set_number_children: 5, 2; + partial_path_length, set_partial_path_length: 7, 6; +} +#[cfg(not(feature = "branch_factor_256"))] +const MAX_ENCODED_PARTIAL_PATH_LEN: usize = 2; + +#[cfg(feature = "branch_factor_256")] +bitfield! { + struct BranchFirstByte(u8); + impl Debug; + impl new; + u8; + has_value, set_has_value: 1, 1; + partial_path_length, set_partial_path_length: 7, 2; +} +#[cfg(feature = "branch_factor_256")] +const MAX_ENCODED_PARTIAL_PATH_LEN: usize = 63; + +bitfield! { + struct LeafFirstByte(u8); + impl Debug; + impl new; + u8; + is_leaf, set_is_leaf: 0, 0; + partial_path_length, set_partial_path_length: 7, 1; +} + +impl Default for LeafFirstByte { + fn default() -> Self { + LeafFirstByte(1) + } +} + +// TODO: Unstable extend_reserve re-implemented here +// Extend::extend_reserve is unstable so we implement it here +// see https://github.com/rust-lang/rust/issues/72631 +pub trait ExtendableBytes: Write { + fn extend>(&mut self, other: T); + fn reserve(&mut self, reserve: usize) { + let _ = reserve; + } + fn push(&mut self, value: u8); + + fn extend_from_slice(&mut self, other: &[u8]) { + self.extend(other.iter().copied()); + } +} + +impl ExtendableBytes for Vec { + fn extend>(&mut self, other: T) { + std::iter::Extend::extend(self, other); + } + fn reserve(&mut self, reserve: usize) { + self.reserve(reserve); + } + fn push(&mut self, value: u8) { + Vec::push(self, value); + } +} + +pub struct ByteCounter(u64); + +impl ByteCounter { + pub fn new() -> Self { + ByteCounter(0) + } + + pub fn count(&self) -> u64 { + self.0 + } +} + +impl Write for ByteCounter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 += buf.len() as u64; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl ExtendableBytes for ByteCounter { + fn extend>(&mut self, other: T) { + self.0 += other.into_iter().count() as u64; + } + fn push(&mut self, _value: u8) { + self.0 += 1; + } +} + impl Node { /// Returns the partial path of the node. pub fn partial_path(&self) -> &Path { @@ -96,6 +201,250 @@ impl Node { Node::Leaf(l) => Some(&l.value), } } + + /// Given a [Node], returns a set of bytes to write to storage + /// The format is as follows: + /// + /// For a branch: + /// - Byte 0: + /// - Bit 0: always 0 + /// - Bit 1: indicates if the branch has a value + /// - Bits 2-5: the number of children (unless branch_factor_256, which stores it in the next byte) + /// - Bits 6-7: 0: empty partial_path, 1: 1 nibble, 2: 2 nibbles, 3: length is encoded in the next byte + /// (for branch_factor_256, bits 2-7 are used for partial_path length, up to 63 nibbles) + /// + /// The remaining bytes are in the following order: + /// - The partial path, possibly preceeded by the length if it is longer than 3 nibbles (varint encoded) + /// - The number of children, if the branch factor is 256 + /// - The children. If the number of children == [BranchNode::MAX_CHILDREN], then the children are just + /// addresses with hashes. Otherwise, they are offset, address, hash tuples. + /// + /// For a leaf: + /// - Byte 0: + /// - Bit 0: always 1 + /// - Bits 1-7: the length of the partial path. If the partial path is longer than 126 nibbles, this is set to + /// 126 and the length is encoded in the next byte. + /// + /// The remaining bytes are in the following order: + /// - The partial path, possibly preceeded by the length if it is longer than 126 nibbles (varint encoded) + /// - The value, always preceeded by the length, varint encoded + /// + /// Note that this means the first byte cannot be 255, which would be a leaf with 127 nibbles. We save this extra + /// value to mark this as a freed area. + /// + /// Note that there is a "prefix" byte which is the size of the area when serializing this object. Since + /// we always have one of those, we include it as a parameter for serialization. + /// + /// TODO: We could pack two bytes of the partial path into one and handle the odd byte length + pub fn as_bytes(&self, prefix: u8, encoded: &mut T) { + match self { + Node::Branch(b) => { + let child_iter = b + .children + .iter() + .enumerate() + .filter_map(|(offset, child)| child.as_ref().map(|c| (offset, c))); + let childcount = child_iter.clone().count(); + + // encode the first byte + let pp_len = if b.partial_path.0.len() <= MAX_ENCODED_PARTIAL_PATH_LEN { + b.partial_path.0.len() as u8 + } else { + MAX_ENCODED_PARTIAL_PATH_LEN as u8 + 1 + }; + #[cfg(not(feature = "branch_factor_256"))] + let first_byte: BranchFirstByte = BranchFirstByte::new( + b.value.is_some() as u8, + (childcount % BranchNode::MAX_CHILDREN) as u8, + pp_len, + ); + #[cfg(feature = "branch_factor_256")] + let first_byte: BranchFirstByte = + BranchFirstByte::new(b.value.is_some() as u8, pp_len); + + // create an output stack item, which can overflow to memory for very large branch nodes + const OPTIMIZE_BRANCHES_FOR_SIZE: usize = 1024; + encoded.reserve(OPTIMIZE_BRANCHES_FOR_SIZE); + encoded.push(prefix); + encoded.push(first_byte.0); + #[cfg(feature = "branch_factor_256")] + encoded.extend_one((childcount % BranchNode::MAX_CHILDREN) as u8); + + // encode the partial path, including the length if it didn't fit above + if b.partial_path.0.len() > MAX_ENCODED_PARTIAL_PATH_LEN { + encoded + .write_varint(b.partial_path.len()) + .expect("writing to vec should succeed"); + } + encoded.extend_from_slice(&b.partial_path); + + // encode the value. For tries that have the same length keys, this is always empty + if let Some(v) = &b.value { + encoded + .write_varint(v.len()) + .expect("writing to vec should succeed"); + encoded.extend_from_slice(v); + } + + // encode the children + if childcount == BranchNode::MAX_CHILDREN { + for (_, child) in child_iter { + if let Child::AddressWithHash(address, hash) = child { + encoded.extend_from_slice(&address.get().to_ne_bytes()); + encoded.extend_from_slice(hash); + } else { + panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); + } + } + } else { + for (position, child) in child_iter { + encoded + .write_varint(position) + .expect("writing to vec should succeed"); + if let Child::AddressWithHash(address, hash) = child { + encoded.extend_from_slice(&address.get().to_ne_bytes()); + encoded.extend_from_slice(hash); + } else { + panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); + } + } + } + } + Node::Leaf(l) => { + let first_byte: LeafFirstByte = LeafFirstByte::new(1, l.partial_path.0.len() as u8); + + const OPTIMIZE_LEAVES_FOR_SIZE: usize = 128; + encoded.reserve(OPTIMIZE_LEAVES_FOR_SIZE); + encoded.push(prefix); + encoded.push(first_byte.0); + + // encode the partial path, including the length if it didn't fit above + if l.partial_path.0.len() >= 127 { + encoded + .write_varint(l.partial_path.len()) + .expect("write to array should succeed"); + } + encoded.extend_from_slice(&l.partial_path); + + // encode the value + encoded + .write_varint(l.value.len()) + .expect("write to array should succeed"); + encoded.extend_from_slice(&l.value); + } + } + } + + /// Given a reader, return a [Node] from those bytes + pub fn from_reader(mut serialized: impl Read) -> Result { + let mut first_byte: [u8; 1] = [0]; + serialized.read_exact(&mut first_byte)?; + match first_byte[0] { + 255 => { + // this is a freed area + Err(Error::new(ErrorKind::Other, "attempt to read freed area")) + } + leaf_first_byte if leaf_first_byte & 1 == 1 => { + let partial_path_len = if leaf_first_byte < 255 { + // less than 126 nibbles + LeafFirstByte(leaf_first_byte).partial_path_length() as usize + } else { + serialized.read_varint()? + }; + + let mut partial_path = vec![0u8; partial_path_len]; + serialized.read_exact(&mut partial_path)?; + + let mut value_len_buf = [0u8; 1]; + serialized.read_exact(&mut value_len_buf)?; + let value_len = value_len_buf[0] as usize; + + let mut value = vec![0u8; value_len]; + serialized.read_exact(&mut value)?; + + Ok(Node::Leaf(LeafNode { + partial_path: Path::from(partial_path), + value: value.into(), + })) + } + branch_first_byte => { + let branch_first_byte = BranchFirstByte(branch_first_byte); + + let has_value = branch_first_byte.has_value() == 1; + #[cfg(not(feature = "branch_factor_256"))] + let childcount = branch_first_byte.number_children() as usize; + #[cfg(feature = "branch_factor_256")] + let childcount = { + let mut childcount_buf = [0u8; 1]; + serialized.read_exact(&mut childcount_buf)?; + childcount_buf[0] as usize + }; + + let mut partial_path_len = branch_first_byte.partial_path_length() as usize; + if partial_path_len > MAX_ENCODED_PARTIAL_PATH_LEN { + partial_path_len = serialized.read_varint()?; + } + + let mut partial_path = vec![0u8; partial_path_len]; + serialized.read_exact(&mut partial_path)?; + + let value = if has_value { + let mut value_len_buf = [0u8; 1]; + serialized.read_exact(&mut value_len_buf)?; + let value_len = value_len_buf[0] as usize; + + let mut value = vec![0u8; value_len]; + serialized.read_exact(&mut value)?; + Some(value.into()) + } else { + None + }; + + let mut children = [const { None }; BranchNode::MAX_CHILDREN]; + if childcount == 0 { + // branch is full of all children + for child in children.iter_mut() { + // TODO: we can read them all at once + let mut address_buf = [0u8; 8]; + serialized.read_exact(&mut address_buf)?; + let address = u64::from_ne_bytes(address_buf); + + let mut hash = [0u8; 32]; + serialized.read_exact(&mut hash)?; + + *child = Some(Child::AddressWithHash( + NonZero::new(address).ok_or(Error::other("zero address in child"))?, + hash.into(), + )); + } + } else { + for _ in 0..childcount { + let mut position_buf = [0u8; 1]; + serialized.read_exact(&mut position_buf)?; + let position = position_buf[0] as usize; + + let mut address_buf = [0u8; 8]; + serialized.read_exact(&mut address_buf)?; + let address = u64::from_ne_bytes(address_buf); + + let mut hash = [0u8; 32]; + serialized.read_exact(&mut hash)?; + + children[position] = Some(Child::AddressWithHash( + NonZero::new(address).ok_or(Error::other("zero address in child"))?, + hash.into(), + )); + } + } + + Ok(Node::Branch(Box::new(BranchNode { + partial_path: partial_path.into(), + value, + children, + }))) + } + } + } } /// A path iterator item, which has the key nibbles up to this point, @@ -113,3 +462,52 @@ pub struct PathIterItem { /// None if `node` is the last node in the path. pub next_nibble: Option, } + +#[cfg(test)] + +mod test { + use crate::{ + node::{BranchNode, LeafNode, Node}, + Child, LinearAddress, Path, + }; + use test_case::test_case; + + #[test_case( + Node::Leaf(LeafNode { + partial_path: Path::from(vec![0, 1, 2, 3]), + value: vec![4, 5, 6, 7].into() + }), 11; "leaf node with value")] + #[test_case(Node::Branch(Box::new(BranchNode { + partial_path: Path::from(vec![0, 1]), + value: None, + children: std::array::from_fn(|i| { + if i == 15 { + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + } else { + None + } + })})), 45; "one child branch node with short partial path and no value" + )] + #[test_case(Node::Branch(Box::new(BranchNode { + partial_path: Path::from(vec![0, 1, 2, 3]), + value: Some(vec![4, 5, 6, 7].into()), + children: std::array::from_fn(|_| + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + )})), 652; "full branch node with long partial path and value" + )] + #[allow(unused_variables)] + fn test_serialize_deserialize(node: Node, expected_length: usize) { + use crate::node::Node; + use std::io::Cursor; + + let mut serialized = Vec::new(); + node.as_bytes(0, &mut serialized); + #[cfg(not(feature = "branch_factor_256"))] // TODO: enable this test for branch_factor_256 + assert_eq!(serialized.len(), expected_length); + let mut cursor = Cursor::new(&serialized); + cursor.set_position(1); + let deserialized = Node::from_reader(cursor).unwrap(); + + assert_eq!(node, deserialized); + } +} diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index e30e4d82f2df..ebe3641e624d 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -59,7 +59,7 @@ use std::ops::Deref; use std::sync::Arc; use crate::hashednode::hash_node; -use crate::node::Node; +use crate::node::{ByteCounter, Node}; use crate::{Child, FileBacked, Path, ReadableStorage, TrieHash}; use super::linear::WritableStorage; @@ -167,14 +167,21 @@ fn area_size_to_index(n: u64) -> Result { pub type LinearAddress = NonZeroU64; /// Each [StoredArea] contains an [Area] which is either a [Node] or a [FreeArea]. + +#[repr(u8)] #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] enum Area { Node(T), - Free(U), + Free(U) = 255, // this is magic: no node starts with a byte of 255 } /// Every item stored in the [NodeStore]'s ReadableStorage after the /// [NodeStoreHeader] is a [StoredArea]. +/// +/// As an overview of what this looks like stored, we get something like this: +/// - Byte 0: The index of the area size +/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf +/// - Bytes 2..n: The actual data #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] struct StoredArea { /// Index in [AREA_SIZES] of this area's size @@ -210,20 +217,11 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); - let addr = addr.get() + 1; // Skip the index byte + let addr = addr.get() + 1; // skip the length byte let area_stream = self.storage.stream_from(addr)?; - let area: Area = serializer() - .deserialize_from(area_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - match area { - Area::Node(node) => Ok(node.into()), - Area::Free(_) => Err(Error::new( - ErrorKind::InvalidData, - "Attempted to read a freed area", - )), - } + let node = Node::from_reader(area_stream)?; + Ok(node.into()) } } @@ -482,9 +480,9 @@ impl NodeStore, S> { /// Returns the length of the serialized area for a node. fn stored_len(node: &Node) -> u64 { - let area: Area<&Node, FreeArea> = Area::Node(node); - - serializer().serialized_size(&area).expect("fixme") + 1 + let mut bytecounter = ByteCounter::new(); + node.as_bytes(0, &mut bytecounter); + bytecounter.count() } /// Returns an address that can be used to store the given `node` and updates @@ -927,53 +925,6 @@ impl NodeStore { } } -impl NodeStore { - /// Persist the freelist from this proposal to storage. - pub fn flush_freelist(&self) -> Result<(), Error> { - // Write the free lists to storage - let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); - let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; - self.storage.write(free_list_offset, free_list_bytes)?; - Ok(()) - } - - /// Persist all the nodes of a proposal to storage. - pub fn flush_nodes(&self) -> Result<(), Error> { - for (addr, (area_size_index, node)) in self.kind.new.iter() { - let stored_area = StoredArea { - area_size_index: *area_size_index, - area: Area::<_, FreeArea>::Node(node.as_ref()), - }; - - let stored_area_bytes = serializer() - .serialize(&stored_area) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - self.storage - .write(addr.get(), stored_area_bytes.as_slice())?; - } - self.storage - .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; - - Ok(()) - } -} - -impl NodeStore { - /// Return a Committed version of this proposal, which doesn't have any modified nodes. - /// This function is used during commit. - pub fn as_committed(&self) -> NodeStore { - NodeStore { - header: self.header, - kind: Committed { - deleted: self.kind.deleted.clone(), - root_hash: self.kind.root_hash.clone(), - }, - storage: self.storage.clone(), - } - } -} - impl NodeStore, S> { /// Persist the freelist from this proposal to storage. pub fn flush_freelist(&self) -> Result<(), Error> { @@ -987,18 +938,12 @@ impl NodeStore, S> { /// Persist all the nodes of a proposal to storage. pub fn flush_nodes(&self) -> Result<(), Error> { for (addr, (area_size_index, node)) in self.kind.new.iter() { - let stored_area = StoredArea { - area_size_index: *area_size_index, - area: Area::<_, FreeArea>::Node(node.as_ref()), - }; - - let stored_area_bytes = serializer() - .serialize(&stored_area) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - + let mut stored_area_bytes = Vec::new(); + node.as_bytes(*area_size_index, &mut stored_area_bytes); self.storage .write(addr.get(), stored_area_bytes.as_slice())?; } + self.storage .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; @@ -1242,6 +1187,13 @@ mod tests { } }), }; "branch node with 1 child")] + #[test_case(BranchNode { + partial_path: Path::from([6, 7, 8]), + value: Some(vec![9, 10, 11].into_boxed_slice()), + children: from_fn(|_| + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + ), + }; "branch node with all child")] #[test_case( Node::Leaf(LeafNode { partial_path: Path::from([0, 1, 2]), @@ -1251,14 +1203,15 @@ mod tests { fn test_serialized_len>(node: N) { let node = node.into(); - let area_size = NodeStore::, MemStore>::stored_len(&node); + let computed_length = + NodeStore::, MemStore>::stored_len(&node); - let area: Area<&Node, FreeArea> = Area::Node(&node); - let actually_serialized = serializer().serialize(&area).unwrap().len() as u64; - assert_eq!(area_size, actually_serialized + 1); + let mut serialized = Vec::new(); + node.as_bytes(0, &mut serialized); + assert_eq!(serialized.len() as u64, computed_length); } #[test] - #[should_panic(expected = "Node size 16777228 is too large")] + #[should_panic(expected = "Node size 16777225 is too large")] fn giant_node() { let memstore = MemStore::new(vec![]); let mut node_store = NodeStore::new_empty_proposal(memstore.into()); From 60291dbd423645f83b845bc194bfddcb9708ee5b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Dec 2024 12:18:17 -0800 Subject: [PATCH 0635/1053] README updates (#758) --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a2e9bd23d56a..6d34bb466e34 100644 --- a/README.md +++ b/README.md @@ -63,18 +63,20 @@ Firewood guarantees recoverability by not referencing the new nodes in a new rev `Revision`. ## Roadmap - - [ ] Complete the proof code - - [ ] Complete the revision manager - - [ ] Complete the API implementation - - [ ] Implement a node cache - - [ ] Hook up the RPC + +- [X] Complete the revision manager +- [X] Complete the API implementation +- [X] Implement a node cache +- [ ] Complete the proof code +- [ ] Hook up the RPC ## Build In order to build firewood, `protoc` must be installed. See instructions for installation [here](https://grpc.io/docs/protoc-installation/). On Mac, you can install via brew: -``` + +```sh brew install protobuf ``` @@ -103,7 +105,7 @@ Firewood comes with a CLI tool called `fwdctl` that enables one to create and in ## Test -``` +```sh cargo test --release ``` From a9b1546537f4b8ef5b2a96484e1fdfab41c2a963 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Dec 2024 10:46:19 -0800 Subject: [PATCH 0636/1053] Rkuris/readme (#759) --- RELEASE.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 6525f1599ca7..cedeaa9c3af4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,10 +8,12 @@ its own crate and has an independent version. The first step in drafting a release is ensuring all crates within the firewood project are using the version of the new release. There is a utility to ensure all versions are updated simultaneously in `cargo-workspace-version`. To use it -to update to 0.0.4, for example: +to update to 0.0.5, for example: - \$ cargo install cargo-workspace-version $ cargo workspace-version update -v0.0.5 +```sh + cargo install cargo-workspace-version + cargo workspace-version update v0.0.5 +``` See the [source code](https://github.com/ava-labs/cargo-workspace-version) for more information on the tool. From 29d0d3d65c0b2f42dab68b4f2d8a22d48354e480 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 11 Dec 2024 13:25:00 -0500 Subject: [PATCH 0637/1053] Elide lifetimes (#760) --- firewood/src/db.rs | 2 +- firewood/src/stream.rs | 14 +++++++------- firewood/src/v2/db.rs | 11 ----------- firewood/src/v2/propose.rs | 5 ++++- fwdctl/src/dump.rs | 2 +- storage/src/linear/mod.rs | 1 - storage/src/node/mod.rs | 1 - storage/src/node/path.rs | 6 +++--- storage/src/nodestore.rs | 2 -- storage/src/trie_hash.rs | 2 +- 10 files changed, 17 insertions(+), 29 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4564d925e348..d862ecb0106e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -225,7 +225,7 @@ pub struct Proposal<'p> { } #[async_trait] -impl<'a> api::DbView for Proposal<'a> { +impl api::DbView for Proposal<'_> { type Stream<'b> = MerkleKeyValueStream<'b, NodeStore, FileBacked>> where diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 097f02a2a214..ac10234207fa 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -74,7 +74,7 @@ impl From for NodeStreamState { } } -impl<'a, T: TrieReader> FusedStream for MerkleNodeStream<'a, T> { +impl FusedStream for MerkleNodeStream<'_, T> { fn is_terminated(&self) -> bool { // The top of `iter_stack` is the next node to return. // If `iter_stack` is empty, there are no more nodes to visit. @@ -93,7 +93,7 @@ impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { } } -impl<'a, T: TrieReader> Stream for MerkleNodeStream<'a, T> { +impl Stream for MerkleNodeStream<'_, T> { type Item = Result<(Key, Arc), api::Error>; fn poll_next( @@ -271,13 +271,13 @@ enum MerkleKeyValueStreamState<'a, T> { Initialized { node_iter: MerkleNodeStream<'a, T> }, } -impl<'a, T, K: AsRef<[u8]>> From for MerkleKeyValueStreamState<'a, T> { +impl> From for MerkleKeyValueStreamState<'_, T> { fn from(key: K) -> Self { Self::_Uninitialized(key.as_ref().into()) } } -impl<'a, T: TrieReader> MerkleKeyValueStreamState<'a, T> { +impl MerkleKeyValueStreamState<'_, T> { /// Returns a new iterator that will iterate over all the key-value pairs in `merkle`. fn _new() -> Self { Self::_Uninitialized(Box::new([])) @@ -300,7 +300,7 @@ impl<'a, T: TrieReader> From<&'a T> for MerkleKeyValueStream<'a, T> { } } -impl<'a, T: TrieReader> FusedStream for MerkleKeyValueStream<'a, T> { +impl FusedStream for MerkleKeyValueStream<'_, T> { fn is_terminated(&self) -> bool { matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) } @@ -317,7 +317,7 @@ impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { } } -impl<'a, T: TrieReader> Stream for MerkleKeyValueStream<'a, T> { +impl Stream for MerkleKeyValueStream<'_, T> { type Item = Result<(Key, Value), api::Error>; fn poll_next( @@ -410,7 +410,7 @@ impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { } } -impl<'a, 'b, T: TrieReader> Iterator for PathIterator<'a, 'b, T> { +impl Iterator for PathIterator<'_, '_, T> { type Item = Result; fn next(&mut self) -> Option { diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs index c544faca1948..ba6d1255ef67 100644 --- a/firewood/src/v2/db.rs +++ b/firewood/src/v2/db.rs @@ -4,17 +4,6 @@ use crate::db::DbError; use crate::v2::api; -#[cfg_attr(doc, aquamarine::aquamarine)] -/// ```mermaid -/// graph LR -/// RevRootHash --> DBRevID -/// RevHeight --> DBRevID -/// DBRevID -- Identify --> DbRev -/// Db/Proposal -- propose with batch --> Proposal -/// Proposal -- translate --> DbRev -/// DB -- commit proposal --> DB -/// ``` - impl From for api::Error { fn from(value: DbError) -> Self { match value { diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index a03ab5abca52..835ca61d0da0 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -107,7 +107,10 @@ impl Proposal { #[async_trait] impl api::DbView for Proposal { // TODO: Replace with the correct stream type for an in-memory proposal implementation - type Stream<'a> = Empty, Vec), api::Error>> where T: 'a; + type Stream<'a> + = Empty, Vec), api::Error>> + where + T: 'a; async fn root_hash(&self) -> Result, api::Error> { todo!(); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 4cfb286b7e26..1b33342c9d9e 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -69,5 +69,5 @@ fn u8_to_string(data: &[u8]) -> Cow<'_, str> { } fn key_parser(s: &str) -> Result, std::io::Error> { - return Ok(Box::from(s.as_bytes())); + Ok(Box::from(s.as_bytes())) } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index e60755b2a8ab..016776666f53 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -37,7 +37,6 @@ pub trait ReadableStorage: Debug + Sync + Send { /// # Returns /// /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. - fn stream_from(&self, addr: u64) -> Result, Error>; /// Return the size of the underlying storage, in bytes diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 554b3f352e3a..7c67e49fbc9b 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -464,7 +464,6 @@ pub struct PathIterItem { } #[cfg(test)] - mod test { use crate::{ node::{BranchNode, LeafNode, Node}, diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 777d1a395f59..4b34e7f8ffa9 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -152,9 +152,9 @@ pub struct NibblesIterator<'a> { tail: usize, } -impl<'a> FusedIterator for NibblesIterator<'a> {} +impl FusedIterator for NibblesIterator<'_> {} -impl<'a> Iterator for NibblesIterator<'a> { +impl Iterator for NibblesIterator<'_> { type Item = u8; #[cfg(feature = "branch_factor_256")] @@ -216,7 +216,7 @@ impl<'a> NibblesIterator<'a> { } } -impl<'a> DoubleEndedIterator for NibblesIterator<'a> { +impl DoubleEndedIterator for NibblesIterator<'_> { fn next_back(&mut self) -> Option { if self.is_empty() { return None; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index ebe3641e624d..34e3ba6d4c0b 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -167,7 +167,6 @@ fn area_size_to_index(n: u64) -> Result { pub type LinearAddress = NonZeroU64; /// Each [StoredArea] contains an [Area] which is either a [Node] or a [FreeArea]. - #[repr(u8)] #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] enum Area { @@ -600,7 +599,6 @@ impl NodeStoreHeader { /// The first SIZE bytes of the ReadableStorage are reserved for the /// [NodeStoreHeader]. /// We also want it aligned to a disk block - const SIZE: u64 = 2048; /// Number of extra bytes to write on the first creation of the NodeStoreHeader diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 89df4b061641..0f48000a687e 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -81,7 +81,7 @@ impl<'de> Deserialize<'de> for TrieHash { struct TrieVisitor; -impl<'de> Visitor<'de> for TrieVisitor { +impl Visitor<'_> for TrieVisitor { type Value = TrieHash; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { From d99bc32f2183ae5a841aa79bcf84568f18c65a31 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 11 Dec 2024 13:55:55 -0500 Subject: [PATCH 0638/1053] Opentelemetry implementation (#761) --- benchmark/Cargo.toml | 5 +++++ benchmark/src/create.rs | 5 +++++ benchmark/src/main.rs | 34 ++++++++++++++++++++++++++++++++++ firewood/Cargo.toml | 1 + firewood/src/db.rs | 9 +++++++++ firewood/src/manager.rs | 1 + firewood/src/v2/propose.rs | 2 -- storage/Cargo.toml | 1 + storage/src/nodestore.rs | 5 +++++ storage/src/trie_hash.rs | 7 +------ 10 files changed, 62 insertions(+), 8 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 29a129561e7f..d6253b76bf9d 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -18,6 +18,11 @@ tikv-jemallocator = "0.6.0" env_logger = "0.11.5" zipf = "7.0.1" log = "0.4.20" +fastrace = { version = "0.7.4", features = ["enable"] } +fastrace-opentelemetry = { version = "0.8.0" } +opentelemetry-otlp = "0.27.0" +opentelemetry = "0.27.0" +opentelemetry_sdk = "0.27.1" [features] logger = ["firewood/logger"] diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index f7f32e289ce4..bcc13546b296 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -4,6 +4,8 @@ use std::error::Error; use std::time::Instant; +use fastrace::prelude::SpanContext; +use fastrace::{func_path, Span}; use firewood::db::Db; use firewood::v2::api::{Db as _, Proposal as _}; use log::info; @@ -21,6 +23,9 @@ impl TestRunner for Create { let start = Instant::now(); for key in 0..args.number_of_batches { + let root = Span::root(func_path!(), SpanContext::random()); + let _guard = root.set_local_parent(); + let batch = Self::generate_inserts(key * keys, args.batch_size).collect(); let proposal = db.propose(batch).await.expect("proposal should succeed"); diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index a4f2a9c453e4..39c712cb962d 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -12,11 +12,13 @@ // use clap::{Parser, Subcommand}; +use fastrace_opentelemetry::OpenTelemetryReporter; use firewood::logger::trace; use log::LevelFilter; use metrics_exporter_prometheus::PrometheusBuilder; use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; +use std::borrow::Cow; use std::error::Error; use std::net::{Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; @@ -26,6 +28,15 @@ use std::time::Duration; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; +use fastrace::collector::Config; + +use opentelemetry::trace::SpanKind; +use opentelemetry::InstrumentationScope; +use opentelemetry::KeyValue; +use opentelemetry_otlp::SpanExporter; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::Resource; + #[derive(Parser, Debug)] struct Args { #[arg(short, long, default_value_t = 10000)] @@ -130,6 +141,27 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { + let reporter = OpenTelemetryReporter::new( + SpanExporter::builder() + .with_tonic() + .with_endpoint("http://127.0.0.1:4317".to_string()) + .with_protocol(opentelemetry_otlp::Protocol::Grpc) + .with_timeout(Duration::from_secs( + opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, + )) + .build() + .expect("initialize oltp exporter"), + SpanKind::Server, + Cow::Owned(Resource::new([KeyValue::new( + "service.name", + "avalabs.firewood.benchmark", + )])), + InstrumentationScope::builder("firewood") + .with_version(env!("CARGO_PKG_VERSION")) + .build(), + ); + fastrace::set_reporter(reporter, Config::default()); + let args = Args::parse(); if args.test_name == TestName::Single && args.batch_size > 1000 { @@ -204,5 +236,7 @@ async fn main() -> Result<(), Box> { println!("{}", prometheus_handle.render()); } + fastrace::flush(); + Ok(()) } diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 2392d519b40a..58f71551662f 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -33,6 +33,7 @@ test-case = "3.3.1" integer-encoding = "4.0.0" io-uring = {version = "0.7", optional = true } smallvec = "1.6.1" +fastrace = { version = "0.7.4" } [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d862ecb0106e..ad435fca1068 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -148,6 +148,7 @@ where Ok(self.manager.read().await.all_hashes()) } + #[fastrace::trace(short_name = true)] async fn propose<'p, K: KeyType, V: ValueType>( &'p self, batch: api::Batch, @@ -158,6 +159,7 @@ where let parent = self.manager.read().await.current_revision(); let proposal = NodeStore::new(parent)?; let mut merkle = Merkle::from(proposal); + let span = fastrace::Span::enter_with_local_parent("merkleops"); for op in batch { match op { BatchOp::Put { key, value } => { @@ -168,9 +170,15 @@ where } } } + + drop(span); + let span = fastrace::Span::enter_with_local_parent("freeze"); + let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = Arc::new(nodestore.into()); + + drop(span); self.manager.write().await.add_proposal(immutable.clone()); self.metrics.proposals.increment(1); @@ -266,6 +274,7 @@ impl api::DbView for Proposal<'_> { impl<'a> api::Proposal for Proposal<'a> { type Proposal = Proposal<'a>; + #[fastrace::trace(short_name = true)] async fn propose( self: Arc, batch: api::Batch, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index d5ba09ab768f..e85123af4920 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -135,6 +135,7 @@ impl RevisionManager { /// This write can be delayed, but would mean that recovery will not roll forward to this revision. /// 8. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. + #[fastrace::trace(short_name = true)] pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 835ca61d0da0..a8dc0f216bdb 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -166,8 +166,6 @@ impl api::Proposal for Proposal { } async fn commit(self: Arc) -> Result<(), api::Error> { - // TODO: commit should modify the db; this will only work for - // emptydb at the moment match &self.base { ProposalBase::Proposal(base) => base.clone().commit().await, ProposalBase::View(_) => Ok(()), diff --git a/storage/Cargo.toml b/storage/Cargo.toml index adb8e161d1c7..e390de49d49a 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -21,6 +21,7 @@ log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" bitfield = "0.17.0" +fastrace = { version = "0.7.4" } [dev-dependencies] rand = "0.8.5" diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 34e3ba6d4c0b..ef56b7893715 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -6,6 +6,7 @@ use arc_swap::access::DynAccess; use arc_swap::ArcSwap; use bincode::{DefaultOptions, Options as _}; use bytemuck_derive::{AnyBitPattern, NoUninit}; +use fastrace::local::LocalSpan; use metrics::counter; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -218,6 +219,8 @@ impl NodeStore { let addr = addr.get() + 1; // skip the length byte + let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); + let area_stream = self.storage.stream_from(addr)?; let node = Node::from_reader(area_stream)?; Ok(node.into()) @@ -925,6 +928,7 @@ impl NodeStore { impl NodeStore, S> { /// Persist the freelist from this proposal to storage. + #[fastrace::trace(short_name = true)] pub fn flush_freelist(&self) -> Result<(), Error> { // Write the free lists to storage let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); @@ -934,6 +938,7 @@ impl NodeStore, S> { } /// Persist all the nodes of a proposal to storage. + #[fastrace::trace(short_name = true)] pub fn flush_nodes(&self) -> Result<(), Error> { for (addr, (area_size_index, node)) in self.kind.new.iter() { let mut stored_area_bytes = Vec::new(); diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 0f48000a687e..569812dc6137 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -51,14 +51,9 @@ impl From> for TrieHash { impl TrieHash { /// Return the length of a TrieHash - const fn len() -> usize { + pub(crate) const fn len() -> usize { std::mem::size_of::() } - - /// Returns true iff each element in this hash is 0. - pub fn is_empty(&self) -> bool { - *self == TrieHash::default() - } } impl Serialize for TrieHash { From 27a9e2907868604e8fbb6fae7ad2f4e48e4614a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:19:46 -0800 Subject: [PATCH 0639/1053] build(deps): update metrics-util requirement from 0.18.0 to 0.19.0 (#765) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- benchmark/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index d6253b76bf9d..36d4cd53b81e 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -9,7 +9,7 @@ hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" metrics = "0.24.0" -metrics-util = "0.18.0" +metrics-util = "0.19.0" metrics-exporter-prometheus = "0.16.0" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" From dd508de1b878d765c5564aa0f2e76163c53ce0ab Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 15 Jan 2025 12:58:07 -0800 Subject: [PATCH 0640/1053] Fix delete wrong key (#766) --- firewood/src/merkle.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4382c750a023..41b872e491f6 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -725,10 +725,8 @@ impl Merkle> { (Some((child_index, child_partial_path)), None) => { // 3. The key is below the node (i.e. its descendant) match node { - Node::Leaf(ref mut leaf) => Ok(( - None, - Some(std::mem::take(&mut leaf.value).into_boxed_slice()), - )), + // we found a non-matching leaf node, so the value does not exist + Node::Leaf(_) => Ok((Some(node), None)), Node::Branch(ref mut branch) => { #[allow(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) @@ -1530,6 +1528,15 @@ mod tests { Ok(()) } + #[test] + fn test_delete_child() { + let items = vec![("do", "verb")]; + let mut merkle = merkle_build_test(items).unwrap(); + + assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); + assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); + } + // #[test] // #[allow(clippy::unwrap_used)] // fn test_root_hash_reversed_deletions() -> Result<(), MerkleError> { From 3e1e4efbb991792a37c70620fed815a8df374178 Mon Sep 17 00:00:00 2001 From: zdf Date: Fri, 17 Jan 2025 09:54:07 -0800 Subject: [PATCH 0641/1053] Improve fwdctl dump (#764) Adds new options and output formats --- Cargo.toml | 3 +- fwdctl/Cargo.toml | 1 + fwdctl/src/dump.rs | 257 ++++++++++++++++++++++++++++++++-- fwdctl/tests/cli.rs | 331 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 578 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6de218a2992..c1cb91b3e3e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "firewood", "fwdctl", "storage", - "grpc-testtool", "benchmark", + "grpc-testtool", + "benchmark", ] resolver = "2" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 00511ab3aa17..74b039b37aaf 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -12,6 +12,7 @@ log = "0.4.20" tokio = { version = "1.36.0", features = ["full"] } futures-util = "0.3.30" hex = "0.4.3" +csv = "1.3.1" [dev-dependencies] assert_cmd = "2.0.13" diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 1b33342c9d9e..a52994d39b54 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -8,6 +8,12 @@ use firewood::stream::MerkleKeyValueStream; use firewood::v2::api::{self, Db as _}; use futures_util::StreamExt; use std::borrow::Cow; +use std::error::Error; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; + +type KeyFromStream = Option), api::Error>>; #[derive(Debug, Args)] pub struct Options { @@ -24,21 +30,95 @@ pub struct Options { /// Defaults to None. #[arg( short = 's', - long = "start-key", + long, required = false, value_name = "START_KEY", value_parser = key_parser, help = "Start dumping from this key (inclusive)." )] pub start_key: Option, + + /// The key to stop dumping to (if no key is provided, stop to the end). + /// Defaults to None. + #[arg( + short = 'S', + long, + required = false, + value_name = "STOP_KEY", + value_parser = key_parser, + help = "Stop dumping to this key (inclusive)." + )] + pub stop_key: Option, + + /// The key to start dumping from (if no key is provided, start from the beginning) in hex format. + /// Defaults to None. + #[arg( + long, + required = false, + conflicts_with = "start_key", + value_name = "START_KEY_HEX", + value_parser = key_parser_hex, + help = "Start dumping from this key (inclusive) in hex format. Conflicts with start_key" + )] + pub start_key_hex: Option, + + /// The key to stop dumping to (if no key is provided, stop to the end) in hex format. + /// Defaults to None. + #[arg( + long, + required = false, + conflicts_with = "stop_key", + value_name = "STOP_KEY_HEX", + value_parser = key_parser_hex, + help = "Stop dumping to this key (inclusive) in hex format. Conflicts with stop_key" + )] + pub stop_key_hex: Option, + + /// The max number of the keys going to be dumped. + /// Defaults to None. + #[arg( + short = 'm', + long, + required = false, + value_name = "MAX_KEY_COUNT", + help = "Maximum number of keys going to be dumped." + )] + pub max_key_count: Option, + + /// The output format of database dump. + /// Possible Values: ["csv", "json", "stdout"]. + /// Defaults to "stdout" + #[arg( + short = 'o', + long, + required = false, + value_name = "OUTPUT_FORMAT", + value_parser = ["csv", "json", "stdout"], + default_value = "stdout", + help = "Output format of database dump, default to stdout. CSV and JSON formats are available." + )] + pub output_format: String, + + /// The output file name of database dump. + /// Output format must be set when the file name is set. + #[arg( + short = 'f', + long, + requires = "output_format", + value_name = "OUTPUT_FILE_NAME", + default_value = "dump", + help = "Output file name of database dump, default to dump. Output format must be set when the file name is set." + )] + pub output_file_name: PathBuf, + #[arg(short = 'x', long, help = "Print the keys and values in hex format.")] pub hex: bool, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {:?}", opts); - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; let latest_hash = db.root_hash().await?; let Some(latest_hash) = latest_hash else { @@ -46,24 +126,44 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { return Ok(()); }; let latest_rev = db.revision(latest_hash).await?; - let start_key = opts.start_key.clone().unwrap_or(Box::new([])); + + let start_key = opts + .start_key + .clone() + .or(opts.start_key_hex.clone()) + .unwrap_or_default(); + let stop_key = opts.stop_key.clone().or(opts.stop_key_hex.clone()); + let mut key_count = 0; + let mut stream = MerkleKeyValueStream::from_key(&latest_rev, start_key); - loop { - match stream.next().await { - None => break, - Some(Ok((key, value))) => { - if opts.hex { - println!("'{}': '{}'", hex::encode(&key), hex::encode(&value)); - } else { - println!("'{}': '{}'", u8_to_string(&key), u8_to_string(&value)); + let mut output_handler = create_output_handler(opts).expect("Error creating output handler"); + + while let Some(item) = stream.next().await { + match item { + Ok((key, value)) => { + output_handler.handle_record(&key, &value)?; + + key_count += 1; + + if (stop_key.as_ref().is_some_and(|stop_key| key >= *stop_key)) + || key_count_exceeded(opts.max_key_count, key_count) + { + handle_next_key(stream.next().await).await; + break; } } - Some(Err(e)) => return Err(e), + Err(e) => return Err(e), } } + output_handler.flush()?; + Ok(()) } +fn key_count_exceeded(max: Option, key_count: u32) -> bool { + max.map(|max| key_count >= max).unwrap_or(false) +} + fn u8_to_string(data: &[u8]) -> Cow<'_, str> { String::from_utf8_lossy(data) } @@ -71,3 +171,136 @@ fn u8_to_string(data: &[u8]) -> Cow<'_, str> { fn key_parser(s: &str) -> Result, std::io::Error> { Ok(Box::from(s.as_bytes())) } + +fn key_parser_hex(s: &str) -> Result, std::io::Error> { + hex::decode(s) + .map(Vec::into_boxed_slice) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) +} + +// Helper function to convert key and value to a string +fn key_value_to_string(key: &[u8], value: &[u8], hex: bool) -> (String, String) { + let key_str = if hex { + hex::encode(key) + } else { + u8_to_string(key).to_string() + }; + let value_str = if hex { + hex::encode(value) + } else { + u8_to_string(value).to_string() + }; + (key_str, value_str) +} + +async fn handle_next_key(next_key: KeyFromStream) { + match next_key { + Some(Ok((key, _))) => { + println!( + "Next key is {0}, resume with \"--start-key={0}\" or \"--start-key-hex={1}\".", + u8_to_string(&key), + hex::encode(&key) + ); + } + Some(Err(e)) => { + eprintln!("Error occurred while fetching the next key: {}.", e); + } + None => { + println!("There is no next key. Data dump completed."); + } + } +} + +trait OutputHandler { + fn handle_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), std::io::Error>; + fn flush(&mut self) -> Result<(), std::io::Error>; +} + +struct CsvOutputHandler { + writer: csv::Writer, + hex: bool, +} + +impl OutputHandler for CsvOutputHandler { + fn handle_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), std::io::Error> { + let (key_str, value_str) = key_value_to_string(key, value, self.hex); + self.writer.write_record(&[key_str, value_str])?; + Ok(()) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + self.writer.flush()?; + Ok(()) + } +} + +struct JsonOutputHandler { + writer: BufWriter, + hex: bool, + is_first: bool, +} + +impl OutputHandler for JsonOutputHandler { + fn handle_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), std::io::Error> { + let (key_str, value_str) = key_value_to_string(key, value, self.hex); + if self.is_first { + self.writer.write_all(b"{\n")?; + self.is_first = false; + } else { + self.writer.write_all(b",\n")?; + } + + write!(self.writer, r#" "{key_str}": "{value_str}""#)?; + Ok(()) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + let _ = self.writer.write(b"\n}\n"); + self.writer.flush()?; + Ok(()) + } +} + +struct StdoutOutputHandler { + hex: bool, +} + +impl OutputHandler for StdoutOutputHandler { + fn handle_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), std::io::Error> { + let (key_str, value_str) = key_value_to_string(key, value, self.hex); + println!("'{}': '{}'", key_str, value_str); + Ok(()) + } + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } +} + +fn create_output_handler( + opts: &Options, +) -> Result, Box> { + let hex = opts.hex; + let mut file_name = opts.output_file_name.clone(); + file_name.set_extension(opts.output_format.as_str()); + match opts.output_format.as_str() { + "csv" => { + println!("Dumping to {}", file_name.display()); + let file = File::create(file_name)?; + Ok(Box::new(CsvOutputHandler { + writer: csv::Writer::from_writer(file), + hex, + })) + } + "json" => { + println!("Dumping to {}", file_name.display()); + let file = File::create(file_name)?; + Ok(Box::new(JsonOutputHandler { + writer: BufWriter::new(file), + hex, + is_first: true, + })) + } + "stdout" => Ok(Box::new(StdoutOutputHandler { hex })), + _ => unreachable!(), + } +} diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 6ce8628edfed..13b902658419 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; -use std::fs::remove_file; +use std::fs::{self, remove_file}; use std::path::PathBuf; const PRG: &str = "fwdctl"; @@ -210,6 +210,335 @@ fn fwdctl_dump() -> Result<()> { Ok(()) } +#[test] +#[serial] +fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["a"]) + .args(["1"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("a")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["b"]) + .args(["2"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("b")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["c"]) + .args(["3"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("c")); + + // Test stop in the middle + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--stop-key"]) + .arg("b") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains( + "Next key is c, resume with \"--start-key=c\"", + )); + + // Test stop in the end + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--stop-key"]) + .arg("c") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains( + "There is no next key. Data dump completed.", + )); + + // Test start in the middle + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--start-key"]) + .arg("b") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::starts_with("\'b")); + + // Test start and stop + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--start-key"]) + .arg("b") + .args(["--stop-key"]) + .arg("b") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::starts_with("\'b")) + .stdout(predicate::str::contains( + "Next key is c, resume with \"--start-key=c\"", + )); + + // Test start and stop + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--start-key"]) + .arg("b") + .args(["--max-key-count"]) + .arg("1") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::starts_with("\'b")) + .stdout(predicate::str::contains( + "Next key is c, resume with \"--start-key=c\"", + )); + + fwdctl_delete_db().map_err(|e| anyhow!(e))?; + + Ok(()) +} + +#[test] +#[serial] +fn fwdctl_dump_with_csv_and_json() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["a"]) + .args(["1"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("a")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["b"]) + .args(["2"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("b")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["c"]) + .args(["3"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("c")); + + // Test output csv + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--output-format"]) + .arg("csv") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("Dumping to dump.csv")); + + let contents = fs::read_to_string("dump.csv").expect("Should read dump.csv file"); + assert_eq!(contents, "a,1\nb,2\nc,3\n"); + fs::remove_file("dump.csv").expect("Should remove dump.csv file"); + + // Test output json + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--output-format"]) + .arg("json") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("Dumping to dump.json")); + + let contents = fs::read_to_string("dump.json").expect("Should read dump.json file"); + assert_eq!( + contents, + "{\n \"a\": \"1\",\n \"b\": \"2\",\n \"c\": \"3\"\n}\n" + ); + fs::remove_file("dump.json").expect("Should remove dump.json file"); + + fwdctl_delete_db().map_err(|e| anyhow!(e))?; + + Ok(()) +} + +#[test] +#[serial] +fn fwdctl_dump_with_file_name() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["a"]) + .args(["1"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("a")); + + // Test without output format + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--output-file-name"]) + .arg("test") + .args([tmpdb::path()]) + .assert() + .failure() + .stderr(predicate::str::contains("--output-format")); + + // Test output csv + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--output-format"]) + .arg("csv") + .args(["--output-file-name"]) + .arg("test") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("Dumping to test.csv")); + + let contents = fs::read_to_string("test.csv").expect("Should read test.csv file"); + assert_eq!(contents, "a,1\n"); + fs::remove_file("test.csv").expect("Should remove test.csv file"); + + // Test output json + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--output-format"]) + .arg("json") + .args(["--output-file-name"]) + .arg("test") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("Dumping to test.json")); + + let contents = fs::read_to_string("test.json").expect("Should read test.json file"); + assert_eq!(contents, "{\n \"a\": \"1\"\n}\n"); + fs::remove_file("test.json").expect("Should remove test.json file"); + + fwdctl_delete_db().map_err(|e| anyhow!(e))?; + + Ok(()) +} + +#[test] +#[serial] +fn fwdctl_dump_with_hex() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["a"]) + .args(["1"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("a")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["b"]) + .args(["2"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("b")); + + Command::cargo_bin(PRG)? + .arg("insert") + .args(["c"]) + .args(["3"]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::contains("c")); + + // Test without output format + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--start-key"]) + .arg("a") + .args(["--start-key-hex"]) + .arg("61") + .args([tmpdb::path()]) + .assert() + .failure() + .stderr(predicate::str::contains("--start-key")) + .stderr(predicate::str::contains("--start-key-hex")); + + // Test start with hex value + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--start-key-hex"]) + .arg("62") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::starts_with("\'b")); + + // Test stop with hex value + Command::cargo_bin(PRG)? + .arg("dump") + .args(["--stop-key-hex"]) + .arg("62") + .args([tmpdb::path()]) + .assert() + .success() + .stdout(predicate::str::starts_with("\'a")) + .stdout(predicate::str::contains("Next key is c")) + .stdout(predicate::str::contains("--start-key=c")) + .stdout(predicate::str::contains("--start-key-hex=63")); + + fwdctl_delete_db().map_err(|e| anyhow!(e))?; + + Ok(()) +} + // A module to create a temporary database name for use in // tests. The directory will be one of: // - cargo's compile-time CARGO_TARGET_TMPDIR, if that exists From 414df47300db812c7bb29230e39345205c4ccd93 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 17 Jan 2025 10:10:37 -0800 Subject: [PATCH 0642/1053] Rkuris/golang ffi (#763) --- .github/check-license-headers.yaml | 2 + Cargo.toml | 1 + benchmark/Cargo.toml | 4 +- ffi/.gitignore | 2 + ffi/Cargo.toml | 14 ++ ffi/README.md | 7 + ffi/build.rs | 24 +++ ffi/cbindgen.toml | 140 ++++++++++++++ ffi/firewood.go | 96 ++++++++++ ffi/firewood.h | 141 +++++++++++++++ ffi/firewood_test.go | 105 +++++++++++ ffi/go.mod | 5 + ffi/go.sum | 0 ffi/kvbackend.go | 62 +++++++ ffi/src/lib.rs | 260 ++++++++++++++++++++++++++ firewood/src/db.rs | 146 ++++++++++++++- firewood/src/merkle.rs | 282 +++++++++++++++++++++++++++++ firewood/src/v2/api.rs | 6 + firewood/src/v2/propose.rs | 3 + 19 files changed, 1288 insertions(+), 12 deletions(-) create mode 100644 ffi/.gitignore create mode 100644 ffi/Cargo.toml create mode 100644 ffi/README.md create mode 100644 ffi/build.rs create mode 100644 ffi/cbindgen.toml create mode 100644 ffi/firewood.go create mode 100644 ffi/firewood.h create mode 100644 ffi/firewood_test.go create mode 100644 ffi/go.mod create mode 100644 ffi/go.sum create mode 100644 ffi/kvbackend.go create mode 100644 ffi/src/lib.rs diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 534995df7826..997c78ed6bf6 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -18,6 +18,7 @@ "CODEOWNERS", "CONTRIBUTING.md", "benchmark/**", + "ffi/**", ], "license": "./.github/license-header.txt" }, @@ -35,6 +36,7 @@ "libaio/**", "docs/**", "benchmark/**", + "ffi/**", "CODEOWNERS", "CONTRIBUTING.md" ], diff --git a/Cargo.toml b/Cargo.toml index c1cb91b3e3e8..1d98590caa53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "storage", "grpc-testtool", "benchmark", + "ffi", ] resolver = "2" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 36d4cd53b81e..591d37677166 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -8,9 +8,9 @@ firewood = { path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" -metrics = "0.24.0" +metrics = "0.24.1" metrics-util = "0.19.0" -metrics-exporter-prometheus = "0.16.0" +metrics-exporter-prometheus = "0.16.1" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.8.5" pretty-duration = "0.1.1" diff --git a/ffi/.gitignore b/ffi/.gitignore new file mode 100644 index 000000000000..4e24ab4c7504 --- /dev/null +++ b/ffi/.gitignore @@ -0,0 +1,2 @@ +dbtest +_obj diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml new file mode 100644 index 000000000000..97db3b2a6e77 --- /dev/null +++ b/ffi/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "firewood-ffi" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +libc = "0.2.2" +firewood = { path = "../firewood" } + +[build-dependencies] +cbindgen = "0.27.0" diff --git a/ffi/README.md b/ffi/README.md new file mode 100644 index 000000000000..3e782aa993d2 --- /dev/null +++ b/ffi/README.md @@ -0,0 +1,7 @@ +A firewood golang interface + +This allows calling into firewood from golang + +# Building + +just run make. This builds the dbtest.go executable diff --git a/ffi/build.rs b/ffi/build.rs new file mode 100644 index 000000000000..630c98b4b3ef --- /dev/null +++ b/ffi/build.rs @@ -0,0 +1,24 @@ +use std::env; + +extern crate cbindgen; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + let config = cbindgen::Config::from_file("cbindgen.toml").expect("cbindgen.toml is present"); + + cbindgen::Builder::new() + .with_crate(crate_dir) + // Add any additional configuration options here + .with_config(config) + .generate() + .map_or_else( + |error| match error { + cbindgen::Error::ParseSyntaxError { .. } => {} + e => panic!("{:?}", e), + }, + |bindings| { + bindings.write_to_file("firewood.h"); + }, + ); +} diff --git a/ffi/cbindgen.toml b/ffi/cbindgen.toml new file mode 100644 index 000000000000..a07b7f83a94d --- /dev/null +++ b/ffi/cbindgen.toml @@ -0,0 +1,140 @@ +# This is a template cbindgen.toml file with all of the default values. +# Some values are commented out because their absence is the real default. +# +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + + +language = "C" + + + +############## Options for Wrapping the Contents of the Header ################# + +# header = "/* Text to put at the beginning of the generated file. Probably a license. */" +# trailer = "/* Text to put at the end of the generated file */" +# include_guard = "my_bindings_h" +# pragma_once = true +# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = false +# namespace = "my_namespace" +namespaces = [] +using_namespaces = [] +sys_includes = [] +includes = [] +no_includes = false +# cpp_compat = true +after_includes = "" + + +############################ Code Style Options ################################ + +braces = "SameLine" +line_length = 100 +tab_width = 2 +documentation = true +documentation_style = "auto" +documentation_length = "full" +line_endings = "LF" # also "CR", "CRLF", "Native" + + +############################# Codegen Options ################################## + +style = "both" +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +usize_is_size_t = true + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + +[export] +include = [] +exclude = [] +# prefix = "CAPI_" +item_types = [] +renaming_overrides_prefixing = false + +[export.rename] +"Db" = "void" + +[export.body] + +[export.mangle] + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# deprecated = "DEPRECATED_FUNC" +# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE" +# no_return = "NO_RETURN" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" +sort_by = "Name" + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +# deprecated = "DEPRECATED_STRUCT" +# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE" +derive_constructor = false +derive_eq = false +derive_neq = false +derive_lt = false +derive_lte = false +derive_gt = false +derive_gte = false + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +# deprecated = "DEPRECATED_ENUM" +# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE" +add_sentinel = false +prefix_with_name = false +derive_helper_methods = false +derive_const_casts = false +derive_mut_casts = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_destructor = false +derive_tagged_enum_copy_constructor = false +enum_class = true +private_default_tagged_enum_constructor = false + + + + +[const] +allow_static_const = true +allow_constexpr = false +sort_by = "Name" + + + + +[macro_expansion] +bitflags = false + + + + + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +exclude = [] +clean = false +extra_bindings = [] + + + +[parse.expand] +crates = [] +all_features = false +default_features = true +features = [] diff --git a/ffi/firewood.go b/ffi/firewood.go new file mode 100644 index 000000000000..c1d2fe0f6088 --- /dev/null +++ b/ffi/firewood.go @@ -0,0 +1,96 @@ +package firewood + +// #cgo LDFLAGS: -L../target/release -L/usr/local/lib -lfirewood_ffi +// #include "firewood.h" +// #include +import "C" +import ( + "runtime" + "unsafe" +) + +const ( + // The size of the node cache for firewood + NodeCache = 1000000 + // The number of revisions to keep (must be >=2) + Revisions = 100 +) + +type Firewood struct { + Db *C.void +} + +// CreateDatabase creates a new Firewood database at the given path. +// Returns a handle that can be used for subsequent database operations. +func CreateDatabase(path string) Firewood { + db := C.fwd_create_db(C.CString(path), C.size_t(NodeCache), C.size_t(Revisions)) + ptr := (*C.void)(db) + return Firewood{Db: ptr} +} + +func OpenDatabase(path string) Firewood { + db := C.fwd_open_db(C.CString(path), C.size_t(NodeCache), C.size_t(Revisions)) + ptr := (*C.void)(db) + return Firewood{Db: ptr} +} + +const ( + OP_PUT = iota + OP_DELETE +) + +type KeyValue struct { + Key []byte + Value []byte +} + +func (f *Firewood) Batch(ops []KeyValue) []byte { + var pin runtime.Pinner + defer pin.Unpin() + + ffi_ops := make([]C.struct_KeyValue, len(ops)) + for i, op := range ops { + ffi_ops[i] = C.struct_KeyValue{ + key: make_value(&pin, op.Key), + value: make_value(&pin, op.Value), + } + } + ptr := (*C.struct_KeyValue)(unsafe.Pointer(&ffi_ops[0])) + hash := C.fwd_batch(unsafe.Pointer(f.Db), C.size_t(len(ops)), ptr) + hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) + C.fwd_free_value(&hash) + return hash_bytes +} + +func (f *Firewood) Get(input_key []byte) ([]byte, error) { + var pin runtime.Pinner + defer pin.Unpin() + ffi_key := make_value(&pin, input_key) + + value := C.fwd_get(unsafe.Pointer(f.Db), ffi_key) + ffi_bytes := C.GoBytes(unsafe.Pointer(value.data), C.int(value.len)) + C.fwd_free_value(&value) + if len(ffi_bytes) == 0 { + return nil, nil + } + return ffi_bytes, nil +} +func make_value(pin *runtime.Pinner, data []byte) C.struct_Value { + if len(data) == 0 { + return C.struct_Value{0, nil} + } + ptr := (*C.uchar)(unsafe.Pointer(&data[0])) + pin.Pin(ptr) + return C.struct_Value{C.size_t(len(data)), ptr} +} + +func (f *Firewood) Root() []byte { + hash := C.fwd_root_hash(unsafe.Pointer(f.Db)) + hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) + return hash_bytes +} + +func (f *Firewood) Close() error { + C.fwd_close_db(unsafe.Pointer(f.Db)) + return nil +} diff --git a/ffi/firewood.h b/ffi/firewood.h new file mode 100644 index 000000000000..78624114567c --- /dev/null +++ b/ffi/firewood.h @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include + + +typedef struct Value { + size_t len; + const uint8_t *data; +} Value; + +/** + * A `KeyValue` struct that represents a key-value pair in the database. + */ +typedef struct KeyValue { + struct Value key; + struct Value value; +} KeyValue; + +/** + * Puts the given key-value pairs into the database. + * + * # Returns + * + * The current root hash of the database, in Value form. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. + * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. + * + */ +struct Value fwd_batch(void *db, + size_t nkeys, + const struct KeyValue *values); + +/** + * Close iand free the memory for a database handle + * + * # Safety + * + * This function uses raw pointers so it is unsafe. + * It is the caller's responsibility to ensure that the database handle is valid. + * Using the db after calling this function is undefined behavior + * + * # Arguments + * + * * `db` - The database handle to close, previously returned from a call to open_db() + */ +void fwd_close_db(void *db); + +/** + * Create a database with the given cache size and maximum number of revisions + * + * # Arguments + * + * * `path` - The path to the database file, which will be overwritten + * * `cache_size` - The size of the node cache, panics if <= 0 + * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + * + * # Returns + * + * A database handle, or panics if it cannot be created + * + * # Safety + * + * This function uses raw pointers so it is unsafe. + * It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. + * The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. + * The caller must call `close` to free the memory associated with the returned database handle. + * + */ +void *fwd_create_db(const char *path, + size_t cache_size, + size_t revisions); + +/** + * Frees the memory associated with a `Value`. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must ensure that `value` is a valid pointer. + */ +void fwd_free_value(const struct Value *value); + +/** + * Gets the value associated with the given key from the database. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * + * # Safety + * + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `key` is a valid pointer to a `Value` struct + * * call `free_value` to free the memory associated with the returned `Value` + */ +struct Value fwd_get(void *db, struct Value key); + +/** + * Open a database with the given cache size and maximum number of revisions + * + * # Arguments + * + * * `path` - The path to the database file, which should exist + * * `cache_size` - The size of the node cache, panics if <= 0 + * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + * + * # Returns + * + * A database handle, or panics if it cannot be created + * + * # Safety + * + * This function uses raw pointers so it is unsafe. + * It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. + * The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. + * The caller must call `close` to free the memory associated with the returned database handle. + * + */ +void *fwd_open_db(const char *path, + size_t cache_size, + size_t revisions); + +/** + * Get the root hash of the latest version of the database + * Don't forget to call `free_value` to free the memory associated with the returned `Value`. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must ensure that `db` is a valid pointer returned by `open_db` + */ +struct Value fwd_root_hash(void *db); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go new file mode 100644 index 000000000000..cc870e76c8ce --- /dev/null +++ b/ffi/firewood_test.go @@ -0,0 +1,105 @@ +package firewood + +import ( + "os" + "strconv" + "testing" +) + +func TestInsert(t *testing.T) { + var f Firewood = CreateDatabase("test.db") + defer f.Close() + f.Batch([]KeyValue{ + {[]byte("abc"), []byte("def")}, + }) + + value, _ := f.Get([]byte("abc")) + if string(value) != "def" { + t.Errorf("expected def, got %s", value) + } +} + +func TestInsert100(t *testing.T) { + var f Firewood = CreateDatabase("test.db") + defer f.Close() + defer os.Remove("test.db") + ops := make([]KeyValue, 100) + for i := 0; i < 100; i++ { + ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} + } + f.Batch(ops) + + for i := 0; i < 100; i++ { + value, err := f.Get([]byte("key" + strconv.Itoa(i))) + if err != nil { + t.FailNow() + } + if string(value) != "value"+strconv.Itoa(i) { + t.Errorf("expected value%d, got %s", i, value) + } + } + + hash := f.Root() + if len(hash) != 32 { + t.Errorf("expected 32 bytes, got %d", len(hash)) + } + + // we know the hash starts with 0xf8 + if hash[0] != 0xf8 { + t.Errorf("expected 0xf8, got %x", hash[0]) + } + + delete_ops := make([]KeyValue, 1) + ops[0] = KeyValue{[]byte(""), []byte("")} + f.Batch(delete_ops) +} + +func TestRangeDelete(t *testing.T) { + const N = 100 + var f Firewood = CreateDatabase("test.db") + defer f.Close() + defer os.Remove("test.db") + ops := make([]KeyValue, N) + for i := 0; i < N; i++ { + ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} + } + f.Batch(ops) + + // delete all keys that start with "key" + delete_ops := make([]KeyValue, 1) + delete_ops[0] = KeyValue{[]byte("key1"), []byte("")} + f.Batch(delete_ops) + + for i := 0; i < N; i++ { + keystring := "key" + strconv.Itoa(i) + value, err := f.Get([]byte(keystring)) + if err != nil { + t.FailNow() + } + if (value != nil) == (keystring[3] == '1') { + t.Errorf("incorrect response for %s %s %x", keystring, value, keystring[3]) + } + } +} + +func TestInvariants(t *testing.T) { + var f Firewood = CreateDatabase("test.db") + defer f.Close() + defer os.Remove("test.db") + + // validate that the root of an empty trie is all zeroes + empty_root := f.Root() + if len(empty_root) != 32 { + t.Errorf("expected 32 bytes, got %d", len(empty_root)) + } + empty_array := [32]byte(empty_root) + if empty_array != [32]byte{} { + t.Errorf("expected empty root, got %x", empty_root) + } + + // validate that get returns nil, nil for non-existent key + val, err := f.Get([]byte("non-existent")) + if val != nil || err != nil { + t.Errorf("expected nil, nil, got %v, %v", val, err) + } +} diff --git a/ffi/go.mod b/ffi/go.mod new file mode 100644 index 000000000000..f75f434a5595 --- /dev/null +++ b/ffi/go.mod @@ -0,0 +1,5 @@ +module github.com/ava-labs/firewood/ffi/v2 + +go 1.22.8 + +toolchain go1.23.4 diff --git a/ffi/go.sum b/ffi/go.sum new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go new file mode 100644 index 000000000000..0aa987641352 --- /dev/null +++ b/ffi/kvbackend.go @@ -0,0 +1,62 @@ +package firewood + +// implement a specific interface for firewood +// this is used for some of the firewood performance tests + +// Validate that Firewood implements the KVBackend interface +var _ KVBackend = (*Firewood)(nil) + +// Copy of KVBackend from ava-labs/avalanchego +type KVBackend interface { + // Returns the current root hash of the trie. + // Empty trie must return common.Hash{}. + // Length of the returned slice must be common.HashLength. + Root() []byte + + // Get retrieves the value for the given key. + // If the key does not exist, it must return (nil, nil). + Get(key []byte) ([]byte, error) + + // Prefetch loads the intermediary nodes of the given key into memory. + // The first return value is ignored. + Prefetch(key []byte) ([]byte, error) + + // After this call, Root() should return the same hash as returned by this call. + // Note when length of a particular value is zero, it means the corresponding + // key should be deleted. + // There may be duplicate keys in the batch provided, and the last one should + // take effect. + // Note after this call, the next call to Update must build on the returned root, + // regardless of whether Commit is called. + // Length of the returned root must be common.HashLength. + Update(keys, vals [][]byte) ([]byte, error) + + // After this call, changes related to [root] should be persisted to disk. + // This may be implemented as no-op if Update already persists changes, or + // commits happen on a rolling basis. + // Length of the root slice is guaranteed to be common.HashLength. + Commit(root []byte) error + + // Close closes the backend and releases all held resources. + Close() error +} + +// Prefetch does nothing since we don't need to prefetch for firewood +func (f *Firewood) Prefetch(key []byte) ([]byte, error) { + return nil, nil +} + +// / Commit does nothing, since update already persists changes +func (f *Firewood) Commit(root []byte) error { + return nil +} + +// Update could use some more work, but for now we just batch the keys and values +func (f *Firewood) Update(keys, vals [][]byte) ([]byte, error) { + // batch the keys and values + ops := make([]KeyValue, len(keys)) + for i := range keys { + ops[i] = KeyValue{keys[i], vals[i]} + } + return f.Batch(ops), nil +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs new file mode 100644 index 000000000000..722ff96da0f6 --- /dev/null +++ b/ffi/src/lib.rs @@ -0,0 +1,260 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::ffi::{CStr, OsStr}; +use std::fmt::{self, Display, Formatter}; +use std::os::unix::ffi::OsStrExt as _; +use std::path::Path; + +use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _}; +use firewood::manager::RevisionManagerConfig; + +#[derive(Debug)] +#[repr(C)] +pub struct Value { + pub len: usize, + pub data: *const u8, +} + +impl Display for Value { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}", self.as_slice()) + } +} + +/// Gets the value associated with the given key from the database. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `db` is a valid pointer returned by `open_db` +/// * ensure that `key` is a valid pointer to a `Value` struct +/// * call `free_value` to free the memory associated with the returned `Value` +#[no_mangle] +pub unsafe extern "C" fn fwd_get(db: *mut Db, key: Value) -> Value { + let db = unsafe { db.as_ref() }.expect("db should be non-null"); + let root = db.root_hash_sync(); + let Ok(Some(root)) = root else { + return Value { + len: 0, + data: std::ptr::null(), + }; + }; + let rev = db.revision_sync(root).expect("revision should exist"); + let value = rev + .val_sync(key.as_slice()) + .expect("get should succeed") + .unwrap_or_default(); + value.into() +} + +/// A `KeyValue` struct that represents a key-value pair in the database. +#[repr(C)] +#[allow(unused)] +#[no_mangle] +pub struct KeyValue { + key: Value, + value: Value, +} + +/// Puts the given key-value pairs into the database. +/// +/// # Returns +/// +/// The current root hash of the database, in Value form. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must: +/// * ensure that `db` is a valid pointer returned by `open_db` +/// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. +/// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. +/// +#[no_mangle] +pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Value { + let db = unsafe { db.as_ref() }.expect("db should be non-null"); + let mut batch = Vec::with_capacity(nkeys); + for i in 0..nkeys { + let kv = unsafe { values.add(i).as_ref() }.expect("values should be non-null"); + if kv.value.len == 0 { + batch.push(DbBatchOp::DeleteRange { + prefix: kv.key.as_slice(), + }); + continue; + } + batch.push(DbBatchOp::Put { + key: kv.key.as_slice(), + value: kv.value.as_slice(), + }); + } + let proposal = db.propose_sync(batch).expect("proposal should succeed"); + proposal.commit_sync().expect("commit should succeed"); + hash(db) +} + +/// Get the root hash of the latest version of the database +/// Don't forget to call `free_value` to free the memory associated with the returned `Value`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that `db` is a valid pointer returned by `open_db` +#[no_mangle] +pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { + let db = unsafe { db.as_ref() }.expect("db should be non-null"); + hash(db) +} + +/// cbindgen::ignore +/// +/// This function is not exposed to the C API. +/// It returns the current hash of an already-fetched database handle +fn hash(db: &Db) -> Value { + let root = db.root_hash_sync().unwrap_or_default().unwrap_or_default(); + Value::from(root.as_slice()) +} + +impl Value { + pub fn as_slice(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.data, self.len) } + } +} + +impl From<&[u8]> for Value { + fn from(data: &[u8]) -> Self { + let boxed: Box<[u8]> = data.into(); + boxed.into() + } +} + +impl From> for Value { + fn from(data: Box<[u8]>) -> Self { + let len = data.len(); + let data = Box::leak(data).as_ptr(); + Value { len, data } + } +} + +/// Frees the memory associated with a `Value`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that `value` is a valid pointer. +#[no_mangle] +pub unsafe extern "C" fn fwd_free_value(value: *const Value) { + if (*value).len == 0 { + return; + } + let recreated_box = unsafe { + Box::from_raw(std::slice::from_raw_parts_mut( + (*value).data as *mut u8, + (*value).len, + )) + }; + drop(recreated_box); +} + +/// Create a database with the given cache size and maximum number of revisions +/// +/// # Arguments +/// +/// * `path` - The path to the database file, which will be overwritten +/// * `cache_size` - The size of the node cache, panics if <= 0 +/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +/// +/// # Returns +/// +/// A database handle, or panics if it cannot be created +/// +/// # Safety +/// +/// This function uses raw pointers so it is unsafe. +/// It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. +/// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. +/// The caller must call `close` to free the memory associated with the returned database handle. +/// +#[no_mangle] +pub unsafe extern "C" fn fwd_create_db( + path: *const std::ffi::c_char, + cache_size: usize, + revisions: usize, +) -> *mut Db { + let cfg = DbConfig::builder() + .truncate(true) + .manager(manager_config(cache_size, revisions)) + .build(); + common_create(path, cfg) +} + +/// Open a database with the given cache size and maximum number of revisions +/// +/// # Arguments +/// +/// * `path` - The path to the database file, which should exist +/// * `cache_size` - The size of the node cache, panics if <= 0 +/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +/// +/// # Returns +/// +/// A database handle, or panics if it cannot be created +/// +/// # Safety +/// +/// This function uses raw pointers so it is unsafe. +/// It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. +/// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. +/// The caller must call `close` to free the memory associated with the returned database handle. +/// +#[no_mangle] +pub unsafe extern "C" fn fwd_open_db( + path: *const std::ffi::c_char, + cache_size: usize, + revisions: usize, +) -> *mut Db { + let cfg = DbConfig::builder() + .truncate(false) + .manager(manager_config(cache_size, revisions)) + .build(); + common_create(path, cfg) +} + +unsafe fn common_create(path: *const std::ffi::c_char, cfg: DbConfig) -> *mut Db { + let path = unsafe { CStr::from_ptr(path) }; + let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); + Box::into_raw(Box::new( + Db::new_sync(path, cfg).expect("db initialization should succeed"), + )) +} + +fn manager_config(cache_size: usize, revisions: usize) -> RevisionManagerConfig { + RevisionManagerConfig::builder() + .node_cache_size( + cache_size + .try_into() + .expect("cache size should always be non-zero"), + ) + .max_revisions(revisions) + .build() +} + +/// Close iand free the memory for a database handle +/// +/// # Safety +/// +/// This function uses raw pointers so it is unsafe. +/// It is the caller's responsibility to ensure that the database handle is valid. +/// Using the db after calling this function is undefined behavior +/// +/// # Arguments +/// +/// * `db` - The database handle to close, previously returned from a call to open_db() +#[no_mangle] +pub unsafe extern "C" fn fwd_close_db(db: *mut Db) { + let _ = Box::from_raw(db); +} diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ad435fca1068..7ff930285fa9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -15,9 +15,8 @@ use std::error::Error; use std::fmt; use std::io::Write; use std::path::Path; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use storage::{Committed, FileBacked, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash}; -use tokio::sync::RwLock; use typed_builder::TypedBuilder; #[derive(Debug)] @@ -61,6 +60,20 @@ impl std::fmt::Debug for DbMetrics { } } +/// A synchronous view of the database. +pub trait DbViewSync { + /// find a value synchronously + fn val_sync(&self, key: K) -> Result>, DbError>; +} + +impl DbViewSync for HistoricalRev { + fn val_sync(&self, key: K) -> Result>, DbError> { + let merkle = Merkle::from(self); + let value = merkle.get_value(key.as_ref()).map_err(DbError::Merkle)?; + Ok(value) + } +} + #[async_trait] impl api::DbView for HistoricalRev { type Stream<'a> @@ -136,16 +149,20 @@ where Self: 'p; async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { - let nodestore = self.manager.read().await.revision(root_hash)?; + let nodestore = self + .manager + .read() + .expect("poisoned lock") + .revision(root_hash)?; Ok(nodestore) } async fn root_hash(&self) -> Result, api::Error> { - Ok(self.manager.read().await.root_hash()?) + Ok(self.manager.read().expect("poisoned lock").root_hash()?) } async fn all_hashes(&self) -> Result, api::Error> { - Ok(self.manager.read().await.all_hashes()) + Ok(self.manager.read().expect("poisoned lock").all_hashes()) } #[fastrace::trace(short_name = true)] @@ -156,7 +173,11 @@ where where Self: 'p, { - let parent = self.manager.read().await.current_revision(); + let parent = self + .manager + .read() + .expect("poisoned lock") + .current_revision(); let proposal = NodeStore::new(parent)?; let mut merkle = Merkle::from(proposal); let span = fastrace::Span::enter_with_local_parent("merkleops"); @@ -168,6 +189,9 @@ where BatchOp::Delete { key } => { merkle.remove(key.as_ref())?; } + BatchOp::DeleteRange { prefix } => { + merkle.remove_prefix(prefix.as_ref())?; + } } } @@ -179,7 +203,10 @@ where Arc::new(nodestore.into()); drop(span); - self.manager.write().await.add_proposal(immutable.clone()); + self.manager + .write() + .expect("poisoned lock") + .add_proposal(immutable.clone()); self.metrics.proposals.increment(1); @@ -210,9 +237,92 @@ impl Db { Ok(db) } + /// Create a new database instance with synchronous I/O. + pub fn new_sync>(db_path: P, cfg: DbConfig) -> Result { + let metrics = Arc::new(DbMetrics { + proposals: counter!("firewood.proposals"), + }); + describe_counter!("firewood.proposals", "Number of proposals created"); + let manager = RevisionManager::new( + db_path.as_ref().to_path_buf(), + cfg.truncate, + cfg.manager.clone(), + )?; + let db = Self { + metrics, + manager: manager.into(), + }; + Ok(db) + } + + /// Synchronously get the root hash of the latest revision. + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(self.manager.read().expect("poisoned lock").root_hash()?) + } + + /// Synchronously get a revision from a root hash + pub fn revision_sync(&self, root_hash: TrieHash) -> Result, api::Error> { + let nodestore = self + .manager + .read() + .expect("poisoned lock") + .revision(root_hash)?; + Ok(nodestore) + } + + /// propose a new batch synchronously + pub fn propose_sync( + &'_ self, + batch: Batch, + ) -> Result>, api::Error> { + let parent = self + .manager + .read() + .expect("poisoned lock") + .current_revision(); + let proposal = NodeStore::new(parent)?; + let mut merkle = Merkle::from(proposal); + for op in batch { + match op { + BatchOp::Put { key, value } => { + merkle.insert(key.as_ref(), value.as_ref().into())?; + } + BatchOp::Delete { key } => { + merkle.remove(key.as_ref())?; + } + BatchOp::DeleteRange { prefix } => { + merkle.remove_prefix(prefix.as_ref())?; + } + } + } + let nodestore = merkle.into_inner(); + let immutable: Arc, FileBacked>> = + Arc::new(nodestore.into()); + self.manager + .write() + .expect("poisoned lock") + .add_proposal(immutable.clone()); + + self.metrics.proposals.increment(1); + + Ok(Arc::new(Proposal { + nodestore: immutable, + db: self, + })) + } + /// Dump the Trie of the latest revision. pub async fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { - let latest_rev_nodestore = self.manager.read().await.current_revision(); + self.dump_sync(w) + } + + /// Dump the Trie of the latest revision, synchronously. + pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), DbError> { + let latest_rev_nodestore = self + .manager + .read() + .expect("poisoned lock") + .current_revision(); let merkle = Merkle::from(latest_rev_nodestore); // TODO: This should be a stream let output = merkle.dump().map_err(DbError::Merkle)?; @@ -290,6 +400,9 @@ impl<'a> api::Proposal for Proposal<'a> { BatchOp::Delete { key } => { merkle.remove(key.as_ref())?; } + BatchOp::DeleteRange { prefix } => { + merkle.remove_prefix(prefix.as_ref())?; + } } } let nodestore = merkle.into_inner(); @@ -298,7 +411,7 @@ impl<'a> api::Proposal for Proposal<'a> { self.db .manager .write() - .await + .expect("poisoned lock") .add_proposal(immutable.clone()); Ok(Self::Proposal { @@ -311,7 +424,20 @@ impl<'a> api::Proposal for Proposal<'a> { async fn commit(self: Arc) -> Result<(), api::Error> { match Arc::into_inner(self) { Some(proposal) => { - let mut manager = proposal.db.manager.write().await; + let mut manager = proposal.db.manager.write().expect("poisoned lock"); + Ok(manager.commit(proposal.nodestore.clone())?) + } + None => Err(api::Error::CannotCommitClonedProposal), + } + } +} + +impl Proposal<'_> { + /// Commit a proposal synchronously + pub fn commit_sync(self: Arc) -> Result<(), api::Error> { + match Arc::into_inner(self) { + Some(proposal) => { + let mut manager = proposal.db.manager.write().expect("poisoned lock"); Ok(manager.commit(proposal.nodestore.clone())?) } None => Err(api::Error::CannotCommitClonedProposal), diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 41b872e491f6..ef794808f8d5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -808,6 +808,184 @@ impl Merkle> { } } } + + /// Removes any key-value pairs with keys that have the given `prefix`. + /// Returns the number of key-value pairs removed. + pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { + let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); + + let root = self.nodestore.mut_root(); + let Some(root_node) = std::mem::take(root) else { + // The trie is empty. There is nothing to remove. + return Ok(0); + }; + + let mut deleted = 0; + let root_node = self.remove_prefix_helper(root_node, &prefix, &mut deleted)?; + *self.nodestore.mut_root() = root_node; + Ok(deleted) + } + + fn remove_prefix_helper( + &mut self, + mut node: Node, + key: &[u8], + deleted: &mut usize, + ) -> Result, MerkleError> { + // 4 possibilities for the position of the `key` relative to `node`: + // 1. The node is at `key`, in which case we need to delete this node and all its children. + // 2. The key is above the node (i.e. its ancestor), so the parent needs to be restructured (TODO). + // 3. The key is below the node (i.e. its descendant), so continue traversing the trie. + // 4. Neither is an ancestor of the other, in which case there's no work to do. + let path_overlap = PrefixOverlap::from(key, node.partial_path().as_ref()); + + let unique_key = path_overlap.unique_a; + let unique_node = path_overlap.unique_b; + + match ( + unique_key + .split_first() + .map(|(index, path)| (*index, Path::from(path))), + unique_node.split_first(), + ) { + (None, _) => { + // 1. The node is at `key`, or we're just above it + // so we can start deleting below here + match &mut node { + Node::Branch(ref mut branch) => { + if branch.value.is_some() { + // a KV pair was in the branch itself + *deleted += 1; + } + self.delete_children(branch, deleted)?; + } + Node::Leaf(_) => { + // the prefix matched only a leaf, so we remove it and indicate only one item was removed + *deleted += 1; + } + }; + Ok(None) + } + (_, Some(_)) => { + // Case (2) or (4) + Ok(Some(node)) + } + (Some((child_index, child_partial_path)), None) => { + // 3. The key is below the node (i.e. its descendant) + match node { + Node::Leaf(_) => Ok(Some(node)), + Node::Branch(ref mut branch) => { + #[allow(clippy::indexing_slicing)] + let child = match std::mem::take(&mut branch.children[child_index as usize]) + { + None => { + return Ok(Some(node)); + } + Some(Child::Node(node)) => node, + Some(Child::AddressWithHash(addr, _)) => { + self.nodestore.read_for_update(addr)? + } + }; + + let child = + self.remove_prefix_helper(child, child_partial_path.as_ref(), deleted)?; + + if let Some(child) = child { + branch.update_child(child_index, Some(Child::Node(child))); + } else { + branch.update_child(child_index, None); + } + + let mut children_iter = + branch + .children + .iter_mut() + .enumerate() + .filter_map(|(index, child)| { + child.as_mut().map(|child| (index, child)) + }); + + let Some((child_index, child)) = children_iter.next() else { + // The branch has no children. Turn it into a leaf. + let leaf = Node::Leaf(LeafNode { + value: SmallVec::from(&(*branch.value.take().expect( + "branch node must have a value if it previously had only 1 child", + ))[..]), + partial_path: branch.partial_path.clone(), // TODO remove clone + }); + return Ok(Some(leaf)); + }; + + if children_iter.next().is_some() { + // The branch has more than 1 child. Return the branch. + return Ok(Some(node)); + } + + // The branch has only 1 child. Remove the branch and return the child. + let mut child = match child { + Child::Node(child_node) => std::mem::replace( + child_node, + Node::Leaf(LeafNode { + value: SmallVec::default(), + partial_path: Path::new(), + }), + ), + Child::AddressWithHash(addr, _) => { + self.nodestore.read_for_update(*addr)? + } + }; + + // The child's partial path is the concatenation of its (now removed) parent, + // its (former) child index, and its partial path. + let branch_partial_path = + std::mem::replace(&mut branch.partial_path, Path::new()); + + let child_partial_path = Path::from_nibbles_iterator( + branch_partial_path + .iter() + .chain(once(&(child_index as u8))) + .chain(child.partial_path().iter()) + .copied(), + ); + child.update_partial_path(child_partial_path); + + Ok(Some(child)) + } + } + } + } + } + + /// Recursively deletes all children of a branch node. + fn delete_children( + &mut self, + branch: &mut BranchNode, + deleted: &mut usize, + ) -> Result<(), std::io::Error> { + if branch.value.is_some() { + // a KV pair was in the branch itself + *deleted += 1; + } + for children in branch.children.iter_mut() { + // read the child node + let child = match children { + Some(Child::Node(node)) => node, + Some(Child::AddressWithHash(addr, _)) => { + &mut self.nodestore.read_for_update(*addr)? + } + None => continue, + }; + match child { + Node::Branch(ref mut child_branch) => { + self.delete_children(child_branch, deleted)?; + } + Node::Leaf(_) => { + *deleted += 1; + } + } + } + Ok(()) + } } /// Returns an iterator where each element is the result of combining @@ -1003,6 +1181,51 @@ mod tests { assert!(merkle.nodestore.root_node().is_none()); } + #[test] + fn remove_prefix_exact() { + let mut merkle = two_byte_all_keys(); + for key_val in u8::MIN..=u8::MAX { + let key = [key_val]; + let got = merkle.remove_prefix(&key).unwrap(); + assert_eq!(got, 1); + let got = merkle.get_value(&key).unwrap(); + assert!(got.is_none()); + } + } + + fn two_byte_all_keys() -> Merkle> { + let mut merkle = create_in_memory_merkle(); + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let val = [key_val]; + + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + } + merkle + } + + #[test] + fn remove_prefix_all() { + let mut merkle = two_byte_all_keys(); + let got = merkle.remove_prefix(&[]).unwrap(); + assert_eq!(got, 256); + } + + #[test] + fn remove_prefix_partial() { + let mut merkle = create_in_memory_merkle(); + merkle + .insert(b"abc", Box::from(b"value".as_slice())) + .unwrap(); + merkle + .insert(b"abd", Box::from(b"value".as_slice())) + .unwrap(); + let got = merkle.remove_prefix(b"ab").unwrap(); + assert_eq!(got, 2); + } + #[test] fn remove_many() { let mut merkle = create_in_memory_merkle(); @@ -1034,6 +1257,36 @@ mod tests { assert!(merkle.nodestore.root_node().is_none()); } + #[test] + fn remove_prefix() { + let mut merkle = create_in_memory_merkle(); + + // insert key-value pairs + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let val = [key_val]; + + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + } + + // remove key-value pairs with prefix [0] + let prefix = [0]; + assert_eq!(merkle.remove_prefix(&[0]).unwrap(), 1); + + // make sure all keys with prefix [0] were removed + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let got = merkle.get_value(&key).unwrap(); + if key[0] == prefix[0] { + assert!(got.is_none()); + } else { + assert!(got.is_some()); + } + } + } + #[test] fn get_empty_proof() { let merkle = create_in_memory_merkle().hash(); @@ -1537,6 +1790,27 @@ mod tests { assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); } + #[test] + fn test_delete_some() { + let items = (0..100) + .map(|n| { + let key = format!("key{}", n); + let val = format!("value{}", n); + (key.as_bytes().to_vec(), val.as_bytes().to_vec()) + }) + .collect::, Vec)>>(); + let mut merkle = merkle_build_test(items.clone()).unwrap(); + merkle.remove_prefix(b"key1").unwrap(); + for item in items { + let (key, val) = item; + if key.starts_with(b"key1") { + assert!(merkle.get_value(&key).unwrap().is_none()); + } else { + assert_eq!(&*merkle.get_value(&key).unwrap().unwrap(), val.as_slice()); + } + } + } + // #[test] // #[allow(clippy::unwrap_used)] // fn test_root_hash_reversed_deletions() -> Result<(), MerkleError> { @@ -2621,4 +2895,12 @@ mod tests { // } // new_key // } + #[test] + fn remove_nonexistent_with_one() { + let items = vec![("do", "verb")]; + let mut merkle = merkle_build_test(items).unwrap(); + + assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); + assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); + } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index b441fc3e01a9..4bf6ba274ff5 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -52,6 +52,12 @@ pub enum BatchOp { /// The key key: K, }, + + /// Delete a range of keys by prefix + DeleteRange { + /// The prefix of the keys to delete + prefix: K, + }, } /// A list of operations to consist of a batch that diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index a8dc0f216bdb..85521d3445f6 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -97,6 +97,9 @@ impl Proposal { api::BatchOp::Delete { key } => { (key.as_ref().to_vec().into_boxed_slice(), KeyOp::Delete) } + api::BatchOp::DeleteRange { prefix } => { + (prefix.as_ref().to_vec().into_boxed_slice(), KeyOp::Delete) + } }) .collect::>(); From ffe57218e91b69633de2291ad8c724dacbfb3d19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:36:05 -0800 Subject: [PATCH 0643/1053] build(deps): update cbindgen requirement from 0.27.0 to 0.28.0 (#767) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 97db3b2a6e77..9c117d309990 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -11,4 +11,4 @@ libc = "0.2.2" firewood = { path = "../firewood" } [build-dependencies] -cbindgen = "0.27.0" +cbindgen = "0.28.0" From 747da525612b27ef7059cbd3d4e169f17db75e49 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 21 Jan 2025 11:21:17 -0800 Subject: [PATCH 0644/1053] Fix leak for db.Root() method (#768) --- ffi/firewood.go | 1 + ffi/firewood_test.go | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index c1d2fe0f6088..0193bd95f889 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -87,6 +87,7 @@ func make_value(pin *runtime.Pinner, data []byte) C.struct_Value { func (f *Firewood) Root() []byte { hash := C.fwd_root_hash(unsafe.Pointer(f.Db)) hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) + C.fwd_free_value(&hash) return hash_bytes } diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index cc870e76c8ce..42ab7f6d9a5a 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -8,6 +8,7 @@ import ( func TestInsert(t *testing.T) { var f Firewood = CreateDatabase("test.db") + defer os.Remove("test.db") defer f.Close() f.Batch([]KeyValue{ {[]byte("abc"), []byte("def")}, @@ -21,8 +22,8 @@ func TestInsert(t *testing.T) { func TestInsert100(t *testing.T) { var f Firewood = CreateDatabase("test.db") - defer f.Close() defer os.Remove("test.db") + defer f.Close() ops := make([]KeyValue, 100) for i := 0; i < 100; i++ { ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} @@ -57,8 +58,8 @@ func TestInsert100(t *testing.T) { func TestRangeDelete(t *testing.T) { const N = 100 var f Firewood = CreateDatabase("test.db") - defer f.Close() defer os.Remove("test.db") + defer f.Close() ops := make([]KeyValue, N) for i := 0; i < N; i++ { ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} @@ -84,8 +85,8 @@ func TestRangeDelete(t *testing.T) { func TestInvariants(t *testing.T) { var f Firewood = CreateDatabase("test.db") - defer f.Close() defer os.Remove("test.db") + defer f.Close() // validate that the root of an empty trie is all zeroes empty_root := f.Root() From d13b2eb3877d83c9e53635608b060386d1705fce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 09:45:59 -0800 Subject: [PATCH 0645/1053] build(deps): update bitfield requirement from 0.17.0 to 0.18.1 (#772) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index e390de49d49a..2c8c0993adf7 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -20,7 +20,7 @@ metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" -bitfield = "0.17.0" +bitfield = "0.18.1" fastrace = { version = "0.7.4" } [dev-dependencies] From 575ca6cc0b7a7809527b07159e402ddd02a58050 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 31 Jan 2025 09:28:31 -0800 Subject: [PATCH 0646/1053] Don't use smallvec for the value (#774) --- ffi/firewood.go | 5 ----- firewood/src/merkle.rs | 25 ++++++++++++------------- firewood/src/stream.rs | 5 ++--- storage/benches/serializer.rs | 2 +- storage/src/node/leaf.rs | 3 +-- storage/src/node/mod.rs | 5 ++--- storage/src/nodestore.rs | 5 ++--- 7 files changed, 20 insertions(+), 30 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 0193bd95f889..340b5a3cdd25 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -34,11 +34,6 @@ func OpenDatabase(path string) Firewood { return Firewood{Db: ptr} } -const ( - OP_PUT = iota - OP_DELETE -) - type KeyValue struct { Key []byte Value []byte diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index ef794808f8d5..b9e866c76579 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -7,7 +7,6 @@ use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use metrics::counter; -use smallvec::SmallVec; use std::collections::HashSet; use std::fmt::Debug; use std::future::ready; @@ -435,7 +434,7 @@ impl Merkle> { // it as the root. let root_node = Node::Leaf(LeafNode { partial_path: key, - value: SmallVec::from(&value[..]), + value, }); *root = root_node.into(); return Ok(()); @@ -516,7 +515,7 @@ impl Merkle> { // There is no child at this index. // Create a new leaf and put it here. let new_leaf = Node::Leaf(LeafNode { - value: SmallVec::from(&value[..]), + value, partial_path, }); branch.update_child(child_index, Some(Child::Node(new_leaf))); @@ -537,12 +536,12 @@ impl Merkle> { // Turn this node into a branch node and put a new leaf as a child. let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), - value: Some(std::mem::take(&mut leaf.value).into_boxed_slice()), + value: Some(std::mem::take(&mut leaf.value)), children: [const { None }; BranchNode::MAX_CHILDREN], }; let new_leaf = Node::Leaf(LeafNode { - value: SmallVec::from(&value[..]), + value, partial_path, }); @@ -571,7 +570,7 @@ impl Merkle> { branch.update_child(node_index, Some(Child::Node(node))); let new_leaf = Node::Leaf(LeafNode { - value: SmallVec::from(&value[..]), + value, partial_path: key_partial_path, }); branch.update_child(key_index, Some(Child::Node(new_leaf))); @@ -718,7 +717,7 @@ impl Merkle> { } Node::Leaf(leaf) => { let removed_value = std::mem::take(&mut leaf.value); - Ok((None, Some(removed_value.into_boxed_slice()))) + Ok((None, Some(removed_value))) } } } @@ -761,9 +760,9 @@ impl Merkle> { let Some((child_index, child)) = children_iter.next() else { // The branch has no children. Turn it into a leaf. let leaf = Node::Leaf(LeafNode { - value: SmallVec::from(&(*branch.value.take().expect( + value: branch.value.take().expect( "branch node must have a value if it previously had only 1 child", - ))[..]), + ), partial_path: branch.partial_path.clone(), // TODO remove clone }); return Ok((Some(leaf), removed_value)); @@ -779,7 +778,7 @@ impl Merkle> { Child::Node(child_node) => std::mem::replace( child_node, Node::Leaf(LeafNode { - value: SmallVec::default(), + value: Box::default(), partial_path: Path::new(), }), ), @@ -908,9 +907,9 @@ impl Merkle> { let Some((child_index, child)) = children_iter.next() else { // The branch has no children. Turn it into a leaf. let leaf = Node::Leaf(LeafNode { - value: SmallVec::from(&(*branch.value.take().expect( + value: branch.value.take().expect( "branch node must have a value if it previously had only 1 child", - ))[..]), + ), partial_path: branch.partial_path.clone(), // TODO remove clone }); return Ok(Some(leaf)); @@ -926,7 +925,7 @@ impl Merkle> { Child::Node(child_node) => std::mem::replace( child_node, Node::Leaf(LeafNode { - value: SmallVec::default(), + value: Box::default(), partial_path: Path::new(), }), ), diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index ac10234207fa..be811c84408a 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -584,7 +584,6 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use smallvec::SmallVec; use storage::{MemStore, MutableProposal, NodeStore}; use crate::merkle::Merkle; @@ -637,7 +636,7 @@ mod tests { ); #[cfg(feature = "branch_factor_256")] assert_eq!(node.key_nibbles, vec![0xBE, 0xEF].into_boxed_slice()); - assert_eq!(node.node.as_leaf().unwrap().value, SmallVec::from([0x42])); + assert_eq!(node.node.as_leaf().unwrap().value, Box::from([0x42])); assert_eq!(node.next_nibble, None); assert!(stream.next().is_none()); @@ -699,7 +698,7 @@ mod tests { assert_eq!(node.next_nibble, None); assert_eq!( node.node.as_leaf().unwrap().value, - SmallVec::from([0x00, 0x00, 0x00, 0x0FF]) + Box::from([0x00, 0x00, 0x00, 0x0FF]) ); assert!(stream.next().is_none()); diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 99f8e143c240..6aa7046065ea 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -57,7 +57,7 @@ fn leaf(c: &mut Criterion) { let mut group = c.benchmark_group("leaf"); let input = Node::Leaf(LeafNode { partial_path: Path(SmallVec::from_slice(&[0, 1])), - value: SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + value: Box::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), }); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); group.bench_with_input("serde", &input, |b, input| { diff --git a/storage/src/node/leaf.rs b/storage/src/node/leaf.rs index 1ba13e6a4f29..0e554f27d95c 100644 --- a/storage/src/node/leaf.rs +++ b/storage/src/node/leaf.rs @@ -2,7 +2,6 @@ // See the file LICENSE.md for licensing terms. use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; use std::fmt::{Debug, Error as FmtError, Formatter}; @@ -15,7 +14,7 @@ pub struct LeafNode { pub partial_path: Path, /// The value associated with this leaf - pub value: SmallVec<[u8; 16]>, + pub value: Box<[u8]>, } impl Debug for LeafNode { diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 7c67e49fbc9b..2f68a298a21d 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -5,7 +5,6 @@ use bitfield::bitfield; use enum_as_inner::EnumAsInner; use integer_encoding::{VarIntReader as _, VarIntWriter as _}; use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; use std::io::{Error, ErrorKind, Read, Write}; use std::num::NonZero; use std::vec; @@ -37,7 +36,7 @@ impl Default for Node { fn default() -> Self { Node::Leaf(LeafNode { partial_path: Path::new(), - value: SmallVec::default(), + value: Box::default(), }) } } @@ -174,7 +173,7 @@ impl Node { pub fn update_value(&mut self, value: Box<[u8]>) { match self { Node::Branch(b) => b.value = Some(value), - Node::Leaf(l) => l.value = SmallVec::from(&value[..]), + Node::Leaf(l) => l.value = value, } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index ef56b7893715..a8329f500592 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1098,7 +1098,6 @@ mod tests { use crate::linear::memory::MemStore; use crate::{BranchNode, LeafNode}; use arc_swap::access::DynGuard; - use smallvec::SmallVec; use test_case::test_case; use super::*; @@ -1200,7 +1199,7 @@ mod tests { #[test_case( Node::Leaf(LeafNode { partial_path: Path::from([0, 1, 2]), - value: SmallVec::from_slice(&[3, 4, 5]), + value: Box::new([3, 4, 5]), }); "leaf node")] fn test_serialized_len>(node: N) { @@ -1223,7 +1222,7 @@ mod tests { let giant_leaf = Node::Leaf(LeafNode { partial_path: Path::from([0, 1, 2]), - value: SmallVec::from_vec(huge_value), + value: huge_value.into_boxed_slice(), }); node_store.mut_root().replace(giant_leaf); From 5e4dbab1878cae77b3e48686a1b333025b909801 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 31 Jan 2025 10:04:20 -0800 Subject: [PATCH 0647/1053] Properly document recursive hash committed (#773) --- storage/src/nodestore.rs | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index a8329f500592..8f067ea585a3 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -863,36 +863,36 @@ impl NodeStore, S> { path_prefix: &mut Path, new_nodes: &mut HashMap)>, ) -> (LinearAddress, TrieHash) { - // Allocate addresses and calculate hashes for all new nodes - match node { - Node::Branch(ref mut b) => { - for (nibble, child) in b.children.iter_mut().enumerate() { - // if this is already hashed, we're done - if matches!(child, Some(Child::AddressWithHash(_, _))) { - // We already know the hash of this child. - continue; - } - - // If this child is a node, hash it and update the child. - let Some(Child::Node(child_node)) = std::mem::take(child) else { - continue; - }; - - // Hash this child and update - // we extend and truncate path_prefix to reduce memory allocations - let original_length = path_prefix.len(); - path_prefix - .0 - .extend(b.partial_path.0.iter().copied().chain(once(nibble as u8))); - - let (child_addr, child_hash) = - self.hash_helper(child_node, path_prefix, new_nodes); - *child = Some(Child::AddressWithHash(child_addr, child_hash)); - path_prefix.0.truncate(original_length); + // If this is a branch, find all unhashed children and recursively call hash_helper on them. + if let Node::Branch(ref mut b) = node { + for (nibble, child) in b.children.iter_mut().enumerate() { + // if this is already hashed, we're done + if matches!(child, Some(Child::AddressWithHash(_, _))) { + // We already know the hash of this child. + continue; } + + // If there was no child, we're done. Otherwise, remove the child from + // the branch and hash it. This has the side effect of dropping the [Child::Node] + // that was allocated. This is fine because we're about to replace it with a + // [Child::AddressWithHash]. + let Some(Child::Node(child_node)) = std::mem::take(child) else { + continue; + }; + + // Hash this child and update + // we extend and truncate path_prefix to reduce memory allocations + let original_length = path_prefix.len(); + path_prefix + .0 + .extend(b.partial_path.0.iter().copied().chain(once(nibble as u8))); + + let (child_addr, child_hash) = self.hash_helper(child_node, path_prefix, new_nodes); + *child = Some(Child::AddressWithHash(child_addr, child_hash)); + path_prefix.0.truncate(original_length); } - Node::Leaf(_) => {} } + // At this point, we either have a leaf or a branch with all children hashed. let hash = hash_node(&node, path_prefix); let (addr, size) = self.allocate_node(&node).expect("TODO handle error"); From 1a8a320ab2623beeb1cff11a1fbb57cd0e789642 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:42:46 -0800 Subject: [PATCH 0648/1053] build(deps): update lru requirement from 0.12.4 to 0.13.0 (#771) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2c8c0993adf7..f2a2cf34e988 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -15,7 +15,7 @@ smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" arc-swap = "1.7.1" -lru = "0.12.4" +lru = "0.13.0" metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" From d9eb6d13a7879dedadee8942650cd810616c8b99 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 4 Feb 2025 15:02:22 -0800 Subject: [PATCH 0649/1053] Migrate to rand_distr for zipf (#775) --- benchmark/Cargo.toml | 4 ++-- benchmark/src/zipf.rs | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 591d37677166..96655192313a 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -12,11 +12,11 @@ metrics = "0.24.1" metrics-util = "0.19.0" metrics-exporter-prometheus = "0.16.1" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } -rand = "0.8.5" +rand = "0.9.0" +rand_distr = "0.5.0" pretty-duration = "0.1.1" tikv-jemallocator = "0.6.0" env_logger = "0.11.5" -zipf = "7.0.1" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } fastrace-opentelemetry = { version = "0.8.0" } diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index a054abdfbc78..e2cede73bd56 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -6,8 +6,7 @@ use firewood::db::{BatchOp, Db}; use firewood::v2::api::{Db as _, Proposal as _}; use log::{debug, trace}; use pretty_duration::pretty_duration; -use rand::prelude::Distribution as _; -use rand::thread_rng; +use rand::prelude::*; use sha2::{Digest, Sha256}; use std::collections::HashSet; use std::error::Error; @@ -29,14 +28,14 @@ impl TestRunner for Zipf { } else { unreachable!() }; - let rows = (args.number_of_batches * args.batch_size) as usize; - let zipf = zipf::ZipfDistribution::new(rows, exponent).unwrap(); + let rows = (args.number_of_batches * args.batch_size) as f64; + let zipf = rand_distr::Zipf::new(rows, exponent).unwrap(); let start = Instant::now(); let mut batch_id = 0; while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch: Vec> = - generate_updates(batch_id, args.batch_size as usize, &zipf).collect(); + generate_updates(batch_id, args.batch_size as usize, zipf).collect(); if log::log_enabled!(log::Level::Debug) { let mut distinct = HashSet::new(); for op in &batch { @@ -71,14 +70,14 @@ impl TestRunner for Zipf { fn generate_updates( batch_id: u32, batch_size: usize, - zipf: &zipf::ZipfDistribution, + zipf: rand_distr::Zipf, ) -> impl Iterator, Vec>> { let hash_of_batch_id = Sha256::digest(batch_id.to_ne_bytes()).to_vec(); - let rng = thread_rng(); + let rng = rand::rng(); zipf.sample_iter(rng) .take(batch_size) .map(|inner_key| { - let digest = Sha256::digest(inner_key.to_ne_bytes()).to_vec(); + let digest = Sha256::digest((inner_key as u64).to_ne_bytes()).to_vec(); trace!( "updating {:?} with digest {} to {}", inner_key, From 5df4e17d8eb5785d237b4f21d047bb6c74c9dee1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 7 Feb 2025 13:21:49 -0800 Subject: [PATCH 0650/1053] Use read_at and write_at to avoid mutating File (#776) --- storage/src/linear/filebacked.rs | 45 ++++++++++++-------------------- storage/src/linear/mod.rs | 2 +- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 4e98919fbcb6..1fdabba0efa6 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -23,7 +23,7 @@ use super::{ReadableStorage, WritableStorage}; #[derive(Debug)] /// A [ReadableStorage] backed by a file pub struct FileBacked { - fd: Mutex, + fd: File, cache: Mutex>>, free_list_cache: Mutex>>, } @@ -44,7 +44,7 @@ impl FileBacked { .open(path)?; Ok(Self { - fd: Mutex::new(fd), + fd, cache: Mutex::new(LruCache::new(node_cache_size)), free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)), }) @@ -52,15 +52,12 @@ impl FileBacked { } impl ReadableStorage for FileBacked { - fn stream_from(&self, addr: u64) -> Result, Error> { + fn stream_from(&self, addr: u64) -> Result, Error> { Ok(Box::new(PredictiveReader::new(self, addr))) } fn size(&self) -> Result { - self.fd - .lock() - .expect("poisoned lock") - .seek(std::io::SeekFrom::End(0)) + Ok(self.fd.metadata()?.len()) } fn read_cached_node(&self, addr: LinearAddress) -> Option> { @@ -81,10 +78,7 @@ impl ReadableStorage for FileBacked { impl WritableStorage for FileBacked { fn write(&self, offset: u64, object: &[u8]) -> Result { - self.fd - .lock() - .expect("poisoned lock") - .write_at(object, offset) + self.fd.write_at(object, offset) } fn write_cached_nodes<'a>( @@ -111,29 +105,24 @@ impl WritableStorage for FileBacked { } } +const PREDICTIVE_READ_BUFFER_SIZE: usize = 1024; + /// A reader that can predictively read from a file, avoiding reading past boundaries, but reading in 1k chunks -struct PredictiveReader { - fd: File, - buffer: [u8; Self::PREDICTIVE_READ_BUFFER_SIZE], +struct PredictiveReader<'a> { + fd: &'a File, + buffer: [u8; PREDICTIVE_READ_BUFFER_SIZE], offset: u64, len: usize, pos: usize, } -impl PredictiveReader { - const PREDICTIVE_READ_BUFFER_SIZE: usize = 1024; - - fn new(fb: &FileBacked, start: u64) -> Self { - let fd = fb - .fd - .lock() - .expect("poisoned lock") - .try_clone() - .expect("resource exhaustion"); +impl<'a> PredictiveReader<'a> { + fn new(fb: &'a FileBacked, start: u64) -> Self { + let fd = &fb.fd; Self { fd, - buffer: [0u8; Self::PREDICTIVE_READ_BUFFER_SIZE], + buffer: [0u8; PREDICTIVE_READ_BUFFER_SIZE], offset: start, len: 0, pos: 0, @@ -141,11 +130,11 @@ impl PredictiveReader { } } -impl Read for PredictiveReader { +impl Read for PredictiveReader<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { if self.len == self.pos { - let bytes_left_in_page = Self::PREDICTIVE_READ_BUFFER_SIZE - - (self.offset % Self::PREDICTIVE_READ_BUFFER_SIZE as u64) as usize; + let bytes_left_in_page = PREDICTIVE_READ_BUFFER_SIZE + - (self.offset % PREDICTIVE_READ_BUFFER_SIZE as u64) as usize; self.fd.seek(std::io::SeekFrom::Start(self.offset))?; let read = self.fd.read(&mut self.buffer[..bytes_left_in_page])?; self.offset += read as u64; diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 016776666f53..824b0e0cbc43 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -37,7 +37,7 @@ pub trait ReadableStorage: Debug + Sync + Send { /// # Returns /// /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. - fn stream_from(&self, addr: u64) -> Result, Error>; + fn stream_from(&self, addr: u64) -> Result, Error>; /// Return the size of the underlying storage, in bytes fn size(&self) -> Result; From a81954a654a28cbd88c8f354a41c2368ccbc3a8b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 7 Feb 2025 18:53:44 -0800 Subject: [PATCH 0651/1053] Remove one more seek (#777) --- storage/src/linear/filebacked.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 1fdabba0efa6..9b93a907dd46 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -7,7 +7,7 @@ // read/write operations at once use std::fs::{File, OpenOptions}; -use std::io::{Error, Read, Seek}; +use std::io::{Error, Read}; use std::num::NonZero; use std::os::unix::fs::FileExt; use std::path::PathBuf; @@ -135,8 +135,9 @@ impl Read for PredictiveReader<'_> { if self.len == self.pos { let bytes_left_in_page = PREDICTIVE_READ_BUFFER_SIZE - (self.offset % PREDICTIVE_READ_BUFFER_SIZE as u64) as usize; - self.fd.seek(std::io::SeekFrom::Start(self.offset))?; - let read = self.fd.read(&mut self.buffer[..bytes_left_in_page])?; + let read = self + .fd + .read_at(&mut self.buffer[..bytes_left_in_page], self.offset)?; self.offset += read as u64; self.len = read; self.pos = 0; From b379b90b4b04d5e8a0730978dc7af4633c75a93f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 8 Feb 2025 08:19:12 -0800 Subject: [PATCH 0652/1053] Add option to disable the telemetry reporting (#779) --- benchmark/src/main.rs | 46 +++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 39c712cb962d..a8fbdd38f0f2 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -39,6 +39,8 @@ use opentelemetry_sdk::Resource; #[derive(Parser, Debug)] struct Args { + #[arg(short = 't', long, default_value_t = false)] + no_telemetry_server: bool, #[arg(short, long, default_value_t = 10000)] batch_size: u64, #[arg(short, long, default_value_t = 1000)] @@ -141,29 +143,31 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { - let reporter = OpenTelemetryReporter::new( - SpanExporter::builder() - .with_tonic() - .with_endpoint("http://127.0.0.1:4317".to_string()) - .with_protocol(opentelemetry_otlp::Protocol::Grpc) - .with_timeout(Duration::from_secs( - opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, - )) - .build() - .expect("initialize oltp exporter"), - SpanKind::Server, - Cow::Owned(Resource::new([KeyValue::new( - "service.name", - "avalabs.firewood.benchmark", - )])), - InstrumentationScope::builder("firewood") - .with_version(env!("CARGO_PKG_VERSION")) - .build(), - ); - fastrace::set_reporter(reporter, Config::default()); - let args = Args::parse(); + if !args.no_telemetry_server { + let reporter = OpenTelemetryReporter::new( + SpanExporter::builder() + .with_tonic() + .with_endpoint("http://127.0.0.1:4317".to_string()) + .with_protocol(opentelemetry_otlp::Protocol::Grpc) + .with_timeout(Duration::from_secs( + opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, + )) + .build() + .expect("initialize oltp exporter"), + SpanKind::Server, + Cow::Owned(Resource::new([KeyValue::new( + "service.name", + "avalabs.firewood.benchmark", + )])), + InstrumentationScope::builder("firewood") + .with_version(env!("CARGO_PKG_VERSION")) + .build(), + ); + fastrace::set_reporter(reporter, Config::default()); + } + if args.test_name == TestName::Single && args.batch_size > 1000 { panic!("Single test is not designed to handle batch sizes > 1000"); } From f9d95944f5c71e09473dd18e174efdfef2369276 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sat, 8 Feb 2025 09:00:55 -0800 Subject: [PATCH 0653/1053] Upgrade rand to 0.9.0 (#780) --- firewood/Cargo.toml | 3 +- firewood/benches/hashops.rs | 3 +- firewood/examples/insert.rs | 11 ++--- firewood/src/merkle.rs | 88 ++++++++++++++++++------------------- storage/Cargo.toml | 2 +- 5 files changed, 55 insertions(+), 52 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 58f71551662f..5907dbc4b758 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -44,7 +44,8 @@ branch_factor_256 = [ "storage/branch_factor_256" ] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} -rand = "0.8.5" +rand = "0.9.0" +rand_distr = "0.5.0" triehash = "0.8.4" clap = { version = "4.5.0", features = ['derive'] } pprof = { version = "0.14.0", features = ["flamegraph"] } diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index b6992b563814..249dbdf331e5 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -8,7 +8,8 @@ use firewood::db::{BatchOp, DbConfig}; use firewood::merkle::Merkle; use firewood::v2::api::{Db as _, Proposal as _}; use pprof::ProfilerGuard; -use rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand_distr::Alphanumeric; use std::sync::Arc; use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path}; use storage::{MemStore, NodeStore}; diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 5f1b00f32469..04f0447635b7 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -15,7 +15,8 @@ use firewood::{ manager::RevisionManagerConfig, v2::api::{Db as _, DbView, Proposal as _}, }; -use rand::{distributions::Alphanumeric, Rng, SeedableRng as _}; +use rand::{Rng, SeedableRng as _}; +use rand_distr::Alphanumeric; #[derive(Parser, Debug)] struct Args { @@ -74,12 +75,12 @@ async fn main() -> Result<(), Box> { let mut rng = if let Some(seed) = args.seed { rand::rngs::StdRng::seed_from_u64(seed) } else { - rand::rngs::StdRng::from_entropy() + rand::rngs::StdRng::from_os_rng() }; for _ in 0..args.number_of_batches { - let keylen = rng.gen_range(args.keylen.clone()); - let valuelen = rng.gen_range(args.valuelen.clone()); + let keylen = rng.random_range(args.keylen.clone()); + let valuelen = rng.random_range(args.valuelen.clone()); let batch: Batch, Vec> = (0..keys) .map(|_| { ( @@ -119,7 +120,7 @@ fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap PrefixOverlap<'a, T> { mod tests { use super::*; use rand::rngs::StdRng; - use rand::{thread_rng, Rng, SeedableRng}; + use rand::{rng, Rng, SeedableRng}; use storage::{MemStore, MutableProposal, NodeStore, RootReader}; use test_case::test_case; @@ -1043,11 +1043,11 @@ mod tests { let mut kvs: Vec<(Vec, Vec)> = Vec::new(); for _ in 0..n { - let key_len = rng.gen_range(1..=4096); - let key: Vec = (0..key_len).map(|_| rng.gen()).collect(); + let key_len = rng.random_range(1..=4096); + let key: Vec = (0..key_len).map(|_| rng.random()).collect(); - let val_len = rng.gen_range(1..=4096); - let val: Vec = (0..val_len).map(|_| rng.gen()).collect(); + let val_len = rng.random_range(1..=4096); + let val: Vec = (0..val_len).map(|_| rng.random()).collect(); kvs.push((key, val)); } @@ -1303,7 +1303,7 @@ mod tests { || None, |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), ) - .unwrap_or_else(|| thread_rng().gen()); + .unwrap_or_else(|| rng().random()); const TEST_SIZE: usize = 1; @@ -1755,13 +1755,13 @@ mod tests { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); ( - rng.gen_range(1..max_len0 + 1), - rng.gen_range(1..max_len1 + 1), + rng.random_range(1..max_len0 + 1), + rng.random_range(1..max_len1 + 1), ) }; let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().gen_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().gen())) + .map(|_| rng.borrow_mut().random_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().random())) .collect(); key }; @@ -1770,7 +1770,7 @@ mod tests { let mut items = Vec::new(); for _ in 0..10 { - let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + let val: Vec = (0..8).map(|_| rng.borrow_mut().random()).collect(); items.push((keygen(), val)); } @@ -1822,13 +1822,13 @@ mod tests { // let (len0, len1): (usize, usize) = { // let mut rng = rng.borrow_mut(); // ( - // rng.gen_range(1..max_len0 + 1), - // rng.gen_range(1..max_len1 + 1), + // rng.random_range(1..max_len0 + 1), + // rng.random_range(1..max_len1 + 1), // ) // }; // let key: Vec = (0..len0) - // .map(|_| rng.borrow_mut().gen_range(0..2)) - // .chain((0..len1).map(|_| rng.borrow_mut().gen())) + // .map(|_| rng.borrow_mut().random_range(0..2)) + // .chain((0..len1).map(|_| rng.borrow_mut().random())) // .collect(); // key // }; @@ -1837,7 +1837,7 @@ mod tests { // let mut items: Vec<_> = (0..10) // .map(|_| keygen()) // .map(|key| { - // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); // (key, val) // }) // .collect(); @@ -1893,13 +1893,13 @@ mod tests { // let (len0, len1): (usize, usize) = { // let mut rng = rng.borrow_mut(); // ( - // rng.gen_range(1..max_len0 + 1), - // rng.gen_range(1..max_len1 + 1), + // rng.random_range(1..max_len0 + 1), + // rng.random_range(1..max_len1 + 1), // ) // }; // let key: Vec = (0..len0) - // .map(|_| rng.borrow_mut().gen_range(0..2)) - // .chain((0..len1).map(|_| rng.borrow_mut().gen())) + // .map(|_| rng.borrow_mut().random_range(0..2)) + // .chain((0..len1).map(|_| rng.borrow_mut().random())) // .collect(); // key // }; @@ -1908,7 +1908,7 @@ mod tests { // let mut items = std::collections::HashMap::new(); // for _ in 0..10 { - // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().gen()).collect(); + // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); // items.insert(keygen(), val); // } @@ -2082,8 +2082,8 @@ mod tests { // let merkle = merkle_build_test(items.clone())?; // for _ in 0..10 { - // let start = rand::thread_rng().gen_range(0..items.len()); - // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + // let start = rand::rng().random_range(0..items.len()); + // let end = rand::rng().random_range(0..items.len() - start) + start - 1; // if end <= start { // continue; @@ -2118,8 +2118,8 @@ mod tests { // let merkle = merkle_build_test(items.clone())?; // for _ in 0..10 { - // let start = rand::thread_rng().gen_range(0..items.len()); - // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + // let start = rand::rng().random_range(0..items.len()); + // let end = rand::rng().random_range(0..items.len() - start) + start - 1; // if end <= start { // continue; @@ -2138,16 +2138,16 @@ mod tests { // vals.push(*item.1); // } - // let test_case: u32 = rand::thread_rng().gen_range(0..6); - // let index = rand::thread_rng().gen_range(0..end - start); + // let test_case: u32 = rand::rng().random_range(0..6); + // let index = rand::rng().random_range(0..end - start); // match test_case { // 0 => { // // Modified key - // keys[index] = rand::thread_rng().gen::<[u8; 32]>(); // In theory it can't be same + // keys[index] = rand::rng().random::<[u8; 32]>(); // In theory it can't be same // } // 1 => { // // Modified val - // vals[index] = rand::thread_rng().gen::<[u8; 20]>(); // In theory it can't be same + // vals[index] = rand::rng().random::<[u8; 20]>(); // In theory it can't be same // } // 2 => { // // Gapped entry slice @@ -2159,8 +2159,8 @@ mod tests { // } // 3 => { // // Out of order - // let index_1 = rand::thread_rng().gen_range(0..end - start); - // let index_2 = rand::thread_rng().gen_range(0..end - start); + // let index_1 = rand::rng().random_range(0..end - start); + // let index_2 = rand::rng().random_range(0..end - start); // if index_1 == index_2 { // continue; // } @@ -2198,8 +2198,8 @@ mod tests { // let merkle = merkle_build_test(items.clone())?; // for _ in 0..10 { - // let start = rand::thread_rng().gen_range(0..items.len()); - // let end = rand::thread_rng().gen_range(0..items.len() - start) + start - 1; + // let start = rand::rng().random_range(0..items.len()); + // let end = rand::rng().random_range(0..items.len() - start) + start - 1; // if end <= start { // continue; @@ -2388,8 +2388,8 @@ mod tests { // )?; // // Test the mini trie with only a single element. - // let key = rand::thread_rng().gen::<[u8; 32]>(); - // let val = rand::thread_rng().gen::<[u8; 20]>(); + // let key = rand::rng().random::<[u8; 32]>(); + // let val = rand::rng().random::<[u8; 20]>(); // let merkle = merkle_build_test(vec![(key, val)])?; // let first = &[0; 32]; @@ -2592,8 +2592,8 @@ mod tests { // for _ in 0..10 { // let mut set = HashMap::new(); // for _ in 0..4096_u32 { - // let key = rand::thread_rng().gen::<[u8; 32]>(); - // let val = rand::thread_rng().gen::<[u8; 20]>(); + // let key = rand::rng().random::<[u8; 32]>(); + // let val = rand::rng().random::<[u8; 20]>(); // set.insert(key, val); // } // let mut items = Vec::from_iter(set.iter()); @@ -2626,8 +2626,8 @@ mod tests { // for _ in 0..10 { // let mut set = HashMap::new(); // for _ in 0..1024_u32 { - // let key = rand::thread_rng().gen::<[u8; 32]>(); - // let val = rand::thread_rng().gen::<[u8; 20]>(); + // let key = rand::rng().random::<[u8; 32]>(); + // let val = rand::rng().random::<[u8; 20]>(); // set.insert(key, val); // } // let mut items = Vec::from_iter(set.iter()); @@ -2659,8 +2659,8 @@ mod tests { // for _ in 0..10 { // let mut set = HashMap::new(); // for _ in 0..4096_u32 { - // let key = rand::thread_rng().gen::<[u8; 32]>(); - // let val = rand::thread_rng().gen::<[u8; 20]>(); + // let key = rand::rng().random::<[u8; 32]>(); + // let val = rand::rng().random::<[u8; 20]>(); // set.insert(key, val); // } // let mut items = Vec::from_iter(set.iter()); @@ -2856,7 +2856,7 @@ mod tests { // || None, // |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), // ) - // .unwrap_or_else(|| thread_rng().gen()); + // .unwrap_or_else(|| rng().random()); // // the test framework will only render this in verbose mode or if the test fails // // to re-run the test when it fails, just specify the seed instead of randomly @@ -2864,8 +2864,8 @@ mod tests { // eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); // let mut r = StdRng::seed_from_u64(seed); // for _ in 0..random_count { - // let key = r.gen::<[u8; 32]>(); - // let val = r.gen::<[u8; 20]>(); + // let key = r.random::<[u8; 32]>(); + // let val = r.random::<[u8; 20]>(); // items.insert(key, val); // } // items diff --git a/storage/Cargo.toml b/storage/Cargo.toml index f2a2cf34e988..9a9710b6aa61 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -24,7 +24,7 @@ bitfield = "0.18.1" fastrace = { version = "0.7.4" } [dev-dependencies] -rand = "0.8.5" +rand = "0.9.0" test-case = "3.3.1" criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } pprof = { version = "0.14.0", features = ["flamegraph"] } From 03e48fed923aed27202af81537a80de4747c55db Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 9 Feb 2025 19:14:42 -0800 Subject: [PATCH 0654/1053] Remove or downgrade dependencies (#782) --- firewood/Cargo.toml | 6 +++--- fwdctl/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 5907dbc4b758..7cc070dbad2b 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -24,12 +24,10 @@ hex = "0.4.3" metrics = "0.24.0" serde = { version = "1.0" } sha2 = "0.10.8" +test-case = "3.3.1" thiserror = "2.0.3" -tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.20.0" bincode = "1.3.3" -log = "0.4.20" -test-case = "3.3.1" integer-encoding = "4.0.0" io-uring = {version = "0.7", optional = true } smallvec = "1.6.1" @@ -50,6 +48,8 @@ triehash = "0.8.4" clap = { version = "4.5.0", features = ['derive'] } pprof = { version = "0.14.0", features = ["flamegraph"] } tempfile = "3.12.0" +tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } + [[bench]] name = "hashops" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 74b039b37aaf..b65f646d58c4 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] firewood = { version = "0.0.4", path = "../firewood" } clap = { version = "4.5.0", features = ["cargo", "derive"] } -anyhow = "1.0.79" env_logger = "0.11.2" log = "0.4.20" tokio = { version = "1.36.0", features = ["full"] } @@ -15,6 +14,7 @@ hex = "0.4.3" csv = "1.3.1" [dev-dependencies] +anyhow = "1.0.79" assert_cmd = "2.0.13" predicates = "3.1.0" serial_test = "3.0.0" From 5b9a256156541c6eeb07685c1961a67b4b6e1d9a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 11 Feb 2025 08:52:01 -0800 Subject: [PATCH 0655/1053] Invert and rename telemetry argument (#784) --- benchmark/Cargo.toml | 8 ++++---- benchmark/README.md | 13 +++++++++++++ benchmark/src/main.rs | 24 ++++++++++++++---------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 96655192313a..d6436b2b2a06 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -19,10 +19,10 @@ tikv-jemallocator = "0.6.0" env_logger = "0.11.5" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } -fastrace-opentelemetry = { version = "0.8.0" } -opentelemetry-otlp = "0.27.0" -opentelemetry = "0.27.0" -opentelemetry_sdk = "0.27.1" +fastrace-opentelemetry = { version = "0.9.0" } +opentelemetry-otlp = { version = "0.28.0", features = ["grpc-tonic"] } +opentelemetry = "0.28.0" +opentelemetry_sdk = "0.28.0" [features] logger = ["firewood/logger"] diff --git a/benchmark/README.md b/benchmark/README.md index 6de3cdf5d2e5..27e1ba94074a 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -168,3 +168,16 @@ If you're looking for detailed logging, there are some command line options to e ```sh cargo run --profile release --bin benchmark -- -l debug -n 10000 single ``` + +# Using opentelemetry + +To use the opentelemetry server and record timings, just run a docker image that collects the data using: + +```sh +docker run -p 127.0.0.1:4318:4318 -p 127.0.0.1:55679:55679 otel/opentelemetry-collector-contrib:0.97.0 2>&1 +``` + +Then, pass the `-e` option to the benchmark. +``` + +Then, pass the `-e` option to the benchmark. diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index a8fbdd38f0f2..c489efb600bd 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -32,15 +32,18 @@ use fastrace::collector::Config; use opentelemetry::trace::SpanKind; use opentelemetry::InstrumentationScope; -use opentelemetry::KeyValue; -use opentelemetry_otlp::SpanExporter; -use opentelemetry_otlp::WithExportConfig; +use opentelemetry_otlp::{SpanExporter, WithExportConfig}; use opentelemetry_sdk::Resource; #[derive(Parser, Debug)] struct Args { - #[arg(short = 't', long, default_value_t = false)] - no_telemetry_server: bool, + #[arg( + short = 'e', + long, + default_value_t = false, + help = "Enable telemetry server reporting" + )] + telemetry_server: bool, #[arg(short, long, default_value_t = 10000)] batch_size: u64, #[arg(short, long, default_value_t = 1000)] @@ -145,7 +148,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; async fn main() -> Result<(), Box> { let args = Args::parse(); - if !args.no_telemetry_server { + if args.telemetry_server { let reporter = OpenTelemetryReporter::new( SpanExporter::builder() .with_tonic() @@ -157,10 +160,11 @@ async fn main() -> Result<(), Box> { .build() .expect("initialize oltp exporter"), SpanKind::Server, - Cow::Owned(Resource::new([KeyValue::new( - "service.name", - "avalabs.firewood.benchmark", - )])), + Cow::Owned( + Resource::builder() + .with_service_name("avalabs.firewood.benchmark") + .build(), + ), InstrumentationScope::builder("firewood") .with_version(env!("CARGO_PKG_VERSION")) .build(), From 88754d3a4e02708c2ee5cd7a1b597f8d34127dbd Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 11 Feb 2025 15:12:23 -0800 Subject: [PATCH 0656/1053] Reformat all imports (#788) --- firewood/benches/hashops.rs | 11 ++++++++--- firewood/examples/insert.rs | 20 ++++++++++---------- firewood/src/v2/api.rs | 7 ++++--- firewood/src/v2/emptydb.rs | 14 +++++--------- firewood/src/v2/propose.rs | 4 +++- firewood/tests/common/mod.rs | 5 ++++- grpc-testtool/benches/insert.rs | 16 +++++++++++----- grpc-testtool/src/bin/process-server.rs | 24 +++++++++++------------- grpc-testtool/src/service.rs | 15 +++++---------- grpc-testtool/src/service/database.rs | 13 +++++++------ grpc-testtool/src/service/db.rs | 7 ++++--- storage/benches/serializer.rs | 8 ++++++-- storage/src/hashednode.rs | 3 +-- storage/src/lib.rs | 8 ++++---- storage/src/linear/memory.rs | 6 ++---- storage/src/node/branch.rs | 3 ++- storage/src/node/mod.rs | 12 +++++------- storage/src/node/path.rs | 7 ++----- storage/src/trie_hash.rs | 6 ++++-- 19 files changed, 98 insertions(+), 91 deletions(-) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 249dbdf331e5..8e9e3aa2f146 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -3,15 +3,20 @@ // hash benchmarks; run with 'cargo bench' -use criterion::{criterion_group, criterion_main, profiler::Profiler, BatchSize, Criterion}; +use criterion::profiler::Profiler; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use firewood::db::{BatchOp, DbConfig}; use firewood::merkle::Merkle; use firewood::v2::api::{Db as _, Proposal as _}; use pprof::ProfilerGuard; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; use rand_distr::Alphanumeric; +use std::fs::File; +use std::iter::repeat_with; +use std::os::raw::c_int; +use std::path::Path; use std::sync::Arc; -use std::{fs::File, iter::repeat_with, os::raw::c_int, path::Path}; use storage::{MemStore, NodeStore}; // To enable flamegraph output diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 04f0447635b7..763e2e7911fd 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,16 +5,16 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::{ - borrow::BorrowMut as _, collections::HashMap, error::Error, num::NonZeroUsize, - ops::RangeInclusive, time::Instant, -}; - -use firewood::{ - db::{Batch, BatchOp, Db, DbConfig}, - manager::RevisionManagerConfig, - v2::api::{Db as _, DbView, Proposal as _}, -}; +use std::borrow::BorrowMut as _; +use std::collections::HashMap; +use std::error::Error; +use std::num::NonZeroUsize; +use std::ops::RangeInclusive; +use std::time::Instant; + +use firewood::db::{Batch, BatchOp, Db, DbConfig}; +use firewood::manager::RevisionManagerConfig; +use firewood::v2::api::{Db as _, DbView, Proposal as _}; use rand::{Rng, SeedableRng as _}; use rand_distr::Alphanumeric; diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 4bf6ba274ff5..d82b1ee36047 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,12 +2,13 @@ // See the file LICENSE.md for licensing terms. use crate::manager::RevisionManagerError; -use crate::proof::ProofNode; +use crate::merkle::MerkleError; +use crate::proof::{Proof, ProofNode}; pub use crate::range_proof::RangeProof; -use crate::{merkle::MerkleError, proof::Proof}; use async_trait::async_trait; use futures::Stream; -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; +use std::sync::Arc; use storage::TrieHash; /// A `KeyType` is something that can be xcast to a u8 reference, diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 77cb9cf1a7c3..12fe17a0de29 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,15 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{ - proof::{Proof, ProofNode}, - range_proof::RangeProof, -}; - -use super::{ - api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}, - propose::{Proposal, ProposalBase}, -}; +use crate::proof::{Proof, ProofNode}; +use crate::range_proof::RangeProof; + +use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}; +use super::propose::{Proposal, ProposalBase}; use async_trait::async_trait; use futures::Stream; use std::sync::Arc; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 85521d3445f6..d785b6f7c007 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -1,7 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::sync::Arc; use async_trait::async_trait; use futures::stream::Empty; diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs index db8315f60ad6..39f04f9c78cf 100644 --- a/firewood/tests/common/mod.rs +++ b/firewood/tests/common/mod.rs @@ -1,7 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{env::temp_dir, fs::remove_file, ops::Deref, path::PathBuf}; +use std::env::temp_dir; +use std::fs::remove_file; +use std::ops::Deref; +use std::path::PathBuf; use firewood::db::{Db, DbConfig}; use typed_builder::TypedBuilder; diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs index 46ff809e8a16..1cb0e11e3635 100644 --- a/grpc-testtool/benches/insert.rs +++ b/grpc-testtool/benches/insert.rs @@ -2,11 +2,17 @@ // See the file LICENSE.md for licensing terms. use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use rand::{distributions::Alphanumeric, Rng, SeedableRng}; -use std::{ - borrow::BorrowMut as _, cell::RefCell, env, fs::remove_dir_all, net::TcpStream, - os::unix::process::CommandExt, path::PathBuf, thread::sleep, time::Duration, -}; +use rand::distributions::Alphanumeric; +use rand::{Rng, SeedableRng}; +use std::borrow::BorrowMut as _; +use std::cell::RefCell; +use std::env; +use std::fs::remove_dir_all; +use std::net::TcpStream; +use std::os::unix::process::CommandExt; +use std::path::PathBuf; +use std::thread::sleep; +use std::time::Duration; use rpc::rpcdb::{self, PutRequest, WriteBatchRequest}; pub use rpc::service::Database as DatabaseService; diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index 9f12043eec27..c323a16c6dc7 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -5,20 +5,18 @@ use chrono::Local; use clap::Parser; use env_logger::Builder; use log::{info, LevelFilter}; -use rpc::{ - process_server::process_server_service_server::ProcessServerServiceServer, - rpcdb::database_server::DatabaseServer as RpcServer, sync::db_server::DbServer as SyncServer, - DatabaseService, -}; +use rpc::process_server::process_server_service_server::ProcessServerServiceServer; +use rpc::rpcdb::database_server::DatabaseServer as RpcServer; +use rpc::sync::db_server::DbServer as SyncServer; +use rpc::DatabaseService; use serde::Deserialize; -use std::{ - error::Error, - io::Write, - net::{IpAddr::V4, Ipv4Addr}, - path::PathBuf, - str::FromStr, - sync::Arc, -}; +use std::error::Error; +use std::io::Write; +use std::net::IpAddr::V4; +use std::net::Ipv4Addr; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; use tonic::transport::Server; #[derive(Clone, Debug, Deserialize)] diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index 9d15705447ec..d317e031b1de 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -2,18 +2,13 @@ // See the file LICENSE.md for licensing terms. use firewood::db::{Db, DbConfig}; -use firewood::v2::api::Db as _; -use firewood::v2::api::Error; +use firewood::v2::api::{Db as _, Error}; +use std::collections::HashMap; +use std::ops::Deref; use std::path::Path; -use std::{ - collections::HashMap, - ops::Deref, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, -}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; use tokio::sync::Mutex; use tonic::Status; diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index 06e9e782721c..ce7563a409cd 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -2,13 +2,14 @@ // See the file LICENSE.md for licensing terms. use super::{Database as DatabaseService, IntoStatusResultExt as _, Iter}; +use crate::rpcdb::database_server::Database; use crate::rpcdb::{ - database_server::Database, CloseRequest, CloseResponse, CompactRequest, CompactResponse, - DeleteRequest, DeleteResponse, GetRequest, GetResponse, HasRequest, HasResponse, - HealthCheckResponse, IteratorErrorRequest, IteratorErrorResponse, IteratorNextRequest, - IteratorNextResponse, IteratorReleaseRequest, IteratorReleaseResponse, - NewIteratorWithStartAndPrefixRequest, NewIteratorWithStartAndPrefixResponse, PutRequest, - PutResponse, WriteBatchRequest, WriteBatchResponse, + CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, + GetRequest, GetResponse, HasRequest, HasResponse, HealthCheckResponse, IteratorErrorRequest, + IteratorErrorResponse, IteratorNextRequest, IteratorNextResponse, IteratorReleaseRequest, + IteratorReleaseResponse, NewIteratorWithStartAndPrefixRequest, + NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, + WriteBatchResponse, }; use firewood::v2::api::{BatchOp, Db as _, DbView as _, Proposal as _}; diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index a1cd2841931e..976f395473bb 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -2,10 +2,11 @@ // See the file LICENSE.md for licensing terms. use super::Database; +use crate::sync::db_server::Db as DbServerTrait; use crate::sync::{ - db_server::Db as DbServerTrait, CommitChangeProofRequest, CommitRangeProofRequest, - GetChangeProofRequest, GetChangeProofResponse, GetMerkleRootResponse, GetProofRequest, - GetProofResponse, GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, + CommitChangeProofRequest, CommitRangeProofRequest, GetChangeProofRequest, + GetChangeProofResponse, GetMerkleRootResponse, GetProofRequest, GetProofResponse, + GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, VerifyChangeProofResponse, }; use tonic::{async_trait, Request, Response, Status}; diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 6aa7046065ea..e679a8197d16 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -1,10 +1,14 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::{array::from_fn, fs::File, num::NonZeroU64, os::raw::c_int}; +use std::array::from_fn; +use std::fs::File; +use std::num::NonZeroU64; +use std::os::raw::c_int; use bincode::Options; -use criterion::{criterion_group, criterion_main, profiler::Profiler, Criterion}; +use criterion::profiler::Profiler; +use criterion::{criterion_group, criterion_main, Criterion}; use pprof::ProfilerGuard; use smallvec::SmallVec; use storage::{LeafNode, Node, Path}; diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index e4b40eef6f19..b3d2a72166a5 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -4,8 +4,7 @@ use sha2::{Digest, Sha256}; use std::iter::{self}; -use crate::{BranchNode, Child, LeafNode, TrieHash}; -use crate::{Node, Path}; +use crate::{BranchNode, Child, LeafNode, Node, Path, TrieHash}; use integer_encoding::VarInt; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index dfc1c28bce35..e8ed908733df 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -23,14 +23,14 @@ pub mod logger; // re-export these so callers don't need to know where they are pub use hashednode::{hash_node, hash_preimage, Hashable, Preimage, ValueDigest}; pub use linear::{ReadableStorage, WritableStorage}; -pub use node::{ - path::NibblesIterator, path::Path, BranchNode, Child, LeafNode, Node, PathIterItem, -}; +pub use node::path::{NibblesIterator, Path}; +pub use node::{BranchNode, Child, LeafNode, Node, PathIterItem}; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, }; -pub use linear::{filebacked::FileBacked, memory::MemStore}; +pub use linear::filebacked::FileBacked; +pub use linear::memory::MemStore; pub use trie_hash::TrieHash; diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index a77ab83d4001..d1c07ff58364 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -2,10 +2,8 @@ // See the file LICENSE.md for licensing terms. use super::{ReadableStorage, WritableStorage}; -use std::{ - io::{Cursor, Read}, - sync::Mutex, -}; +use std::io::{Cursor, Read}; +use std::sync::Mutex; #[derive(Debug, Default)] /// An in-memory impelementation of [WritableStorage] and [ReadableStorage] diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 6aaf8bb8291e..1572c1b3e425 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -1,7 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use serde::{ser::SerializeStruct as _, Deserialize, Serialize}; +use serde::ser::SerializeStruct as _; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use crate::{LeafNode, LinearAddress, Node, Path, TrieHash}; diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 2f68a298a21d..48d0dd05fb2a 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -5,17 +5,17 @@ use bitfield::bitfield; use enum_as_inner::EnumAsInner; use integer_encoding::{VarIntReader as _, VarIntWriter as _}; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use std::io::{Error, ErrorKind, Read, Write}; use std::num::NonZero; +use std::sync::Arc; use std::vec; -use std::{fmt::Debug, sync::Arc}; mod branch; mod leaf; pub mod path; -pub use branch::BranchNode; -pub use branch::Child; +pub use branch::{BranchNode, Child}; pub use leaf::LeafNode; use crate::Path; @@ -464,10 +464,8 @@ pub struct PathIterItem { #[cfg(test)] mod test { - use crate::{ - node::{BranchNode, LeafNode, Node}, - Child, LinearAddress, Path, - }; + use crate::node::{BranchNode, LeafNode, Node}; + use crate::{Child, LinearAddress, Path}; use test_case::test_case; #[test_case( diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 4b34e7f8ffa9..7581a1209cd0 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -5,11 +5,8 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use std::iter::FusedIterator; -use std::{ - fmt::{self, Debug}, - iter::once, -}; +use std::fmt::{self, Debug}; +use std::iter::{once, FusedIterator}; static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 569812dc6137..7aae43e87b8a 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -3,8 +3,10 @@ use std::fmt::{self, Debug}; -use serde::{de::Visitor, Deserialize, Serialize}; -use sha2::digest::{generic_array::GenericArray, typenum}; +use serde::de::Visitor; +use serde::{Deserialize, Serialize}; +use sha2::digest::generic_array::GenericArray; +use sha2::digest::typenum; /// A hash value inside a merkle trie /// We use the same type as returned by sha2 here to avoid copies From 0f9d959df48a117b7ba7f8ec2a74c9a0377c24a1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 12 Feb 2025 09:10:06 -0800 Subject: [PATCH 0657/1053] Allow for caching of reads (#789) --- firewood/src/manager.rs | 8 +++++++- storage/src/lib.rs | 16 ++++++++++++++++ storage/src/linear/filebacked.rs | 29 ++++++++++++++++++++++++++++- storage/src/linear/mod.rs | 10 +++++++++- storage/src/nodestore.rs | 21 ++++++++++++++++----- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index e85123af4920..51684e70936c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -14,7 +14,9 @@ use typed_builder::TypedBuilder; use crate::v2::api::HashKey; -use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; +use storage::{ + CacheReadStrategy, Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash, +}; #[derive(Clone, Debug, TypedBuilder)] /// Revision manager configuratoin @@ -29,6 +31,9 @@ pub struct RevisionManagerConfig { #[builder(default_code = "NonZero::new(40000).expect(\"non-zero\")")] free_list_cache_size: NonZero, + + #[builder(default = CacheReadStrategy::WritesOnly)] + cache_read_strategy: CacheReadStrategy, } type CommittedRevision = Arc>; @@ -73,6 +78,7 @@ impl RevisionManager { config.node_cache_size, config.free_list_cache_size, truncate, + config.cache_read_strategy, )?); let nodestore = match truncate { true => Arc::new(NodeStore::new_empty_committed(storage.clone())?), diff --git a/storage/src/lib.rs b/storage/src/lib.rs index e8ed908733df..8a413b8581d4 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -34,3 +34,19 @@ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; pub use trie_hash::TrieHash; + +/// The strategy for caching nodes that are read +/// from the storage layer. Generally, we only want to +/// cache write operations, but for some read-heavy workloads +/// you can enable caching of branch reads or all reads. +#[derive(Clone, Debug)] +pub enum CacheReadStrategy { + /// Only cache writes (no reads will be cached) + WritesOnly, + + /// Cache branch reads (reads that are not leaf nodes) + BranchReads, + + /// Cache all reads (leaves and branches) + All, +} diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 9b93a907dd46..47eae344d1db 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -16,7 +16,7 @@ use std::sync::{Arc, Mutex}; use lru::LruCache; use metrics::counter; -use crate::{LinearAddress, Node}; +use crate::{CacheReadStrategy, LinearAddress, Node}; use super::{ReadableStorage, WritableStorage}; @@ -26,6 +26,7 @@ pub struct FileBacked { fd: File, cache: Mutex>>, free_list_cache: Mutex>>, + cache_read_strategy: CacheReadStrategy, } impl FileBacked { @@ -35,6 +36,7 @@ impl FileBacked { node_cache_size: NonZero, free_list_cache_size: NonZero, truncate: bool, + cache_read_strategy: CacheReadStrategy, ) -> Result { let fd = OpenOptions::new() .read(true) @@ -47,6 +49,7 @@ impl FileBacked { fd, cache: Mutex::new(LruCache::new(node_cache_size)), free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)), + cache_read_strategy, }) } } @@ -74,6 +77,28 @@ impl ReadableStorage for FileBacked { counter!("firewood.cache.freelist", "type" => if cached.is_some() { "hit" } else { "miss" }).increment(1); cached } + + fn cache_read_strategy(&self) -> &CacheReadStrategy { + &self.cache_read_strategy + } + + fn cache_node(&self, addr: LinearAddress, node: Arc) { + match self.cache_read_strategy { + CacheReadStrategy::WritesOnly => { + // we don't cache reads + } + CacheReadStrategy::All => { + let mut guard = self.cache.lock().expect("poisoned lock"); + guard.put(addr, node); + } + CacheReadStrategy::BranchReads => { + if !node.is_leaf() { + let mut guard = self.cache.lock().expect("poisoned lock"); + guard.put(addr, node); + } + } + } + } } impl WritableStorage for FileBacked { @@ -169,6 +194,7 @@ mod test { NonZero::new(10).unwrap(), NonZero::new(10).unwrap(), false, + CacheReadStrategy::WritesOnly, ) .unwrap(); let mut reader = fb.stream_from(0).unwrap(); @@ -208,6 +234,7 @@ mod test { NonZero::new(10).unwrap(), NonZero::new(10).unwrap(), false, + CacheReadStrategy::WritesOnly, ) .unwrap(); let mut reader = fb.stream_from(0).unwrap(); diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 824b0e0cbc43..87340f5e8f55 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -22,7 +22,7 @@ use std::io::{Error, Read}; use std::num::NonZero; use std::sync::Arc; -use crate::{LinearAddress, Node}; +use crate::{CacheReadStrategy, LinearAddress, Node}; pub(super) mod filebacked; pub mod memory; @@ -51,6 +51,14 @@ pub trait ReadableStorage: Debug + Sync + Send { fn free_list_cache(&self, _addr: LinearAddress) -> Option> { None } + + /// Return the cache read strategy for this readable storage + fn cache_read_strategy(&self) -> &CacheReadStrategy { + &CacheReadStrategy::WritesOnly + } + + /// Cache a node for future reads + fn cache_node(&self, _addr: LinearAddress, _node: Arc) {} } /// Trait for writable storage. diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 8f067ea585a3..f63614fffb70 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -61,7 +61,7 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::{ByteCounter, Node}; -use crate::{Child, FileBacked, Path, ReadableStorage, TrieHash}; +use crate::{CacheReadStrategy, Child, FileBacked, Path, ReadableStorage, TrieHash}; use super::linear::WritableStorage; @@ -217,13 +217,24 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); - let addr = addr.get() + 1; // skip the length byte + let actual_addr = addr.get() + 1; // skip the length byte let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); - let area_stream = self.storage.stream_from(addr)?; - let node = Node::from_reader(area_stream)?; - Ok(node.into()) + let area_stream = self.storage.stream_from(actual_addr)?; + let node = Arc::new(Node::from_reader(area_stream)?); + match self.storage.cache_read_strategy() { + CacheReadStrategy::All => { + self.storage.cache_node(addr, node.clone()); + } + CacheReadStrategy::BranchReads => { + if !node.is_leaf() { + self.storage.cache_node(addr, node.clone()); + } + } + CacheReadStrategy::WritesOnly => {} + } + Ok(node) } } From 2c8c7728db6972930ae7533eb7c90b43a3bf1adf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 12 Feb 2025 13:00:25 -0800 Subject: [PATCH 0658/1053] Rkuris/cache read strategy in benchmark cmd (#790) --- benchmark/Cargo.toml | 1 + benchmark/src/create.rs | 8 ++-- benchmark/src/main.rs | 77 ++++++++++++++++++++++++++++--------- benchmark/src/single.rs | 2 +- benchmark/src/tenkrandom.rs | 4 +- benchmark/src/zipf.rs | 4 +- firewood/src/manager.rs | 5 +-- storage/Cargo.toml | 2 + storage/src/lib.rs | 9 ++++- 9 files changed, 81 insertions(+), 31 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index d6436b2b2a06..3caba5a09d04 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -23,6 +23,7 @@ fastrace-opentelemetry = { version = "0.9.0" } opentelemetry-otlp = { version = "0.28.0", features = ["grpc-tonic"] } opentelemetry = "0.28.0" opentelemetry_sdk = "0.28.0" +strum = "0.27.0" [features] logger = ["firewood/logger"] diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index bcc13546b296..060f5e0d3b80 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -19,14 +19,14 @@ pub struct Create; impl TestRunner for Create { async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { - let keys = args.batch_size; + let keys = args.global_opts.batch_size; let start = Instant::now(); - for key in 0..args.number_of_batches { + for key in 0..args.global_opts.number_of_batches { let root = Span::root(func_path!(), SpanContext::random()); let _guard = root.set_local_parent(); - let batch = Self::generate_inserts(key * keys, args.batch_size).collect(); + let batch = Self::generate_inserts(key * keys, args.global_opts.batch_size).collect(); let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; @@ -34,7 +34,7 @@ impl TestRunner for Create { let duration = start.elapsed(); info!( "Generated and inserted {} batches of size {keys} in {}", - args.number_of_batches, + args.global_opts.number_of_batches, pretty_duration(&duration, None) ); diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index c489efb600bd..f50111004dfb 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -11,7 +11,7 @@ // 3. 50% of batch size is updating rows in the middle, but setting the value to the hash of the first row inserted // -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; use fastrace_opentelemetry::OpenTelemetryReporter; use firewood::logger::trace; use log::LevelFilter; @@ -20,13 +20,14 @@ use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; use std::borrow::Cow; use std::error::Error; +use std::fmt::Display; use std::net::{Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; use std::path::PathBuf; use std::time::Duration; use firewood::db::{BatchOp, Db, DbConfig}; -use firewood::manager::RevisionManagerConfig; +use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; use fastrace::collector::Config; @@ -37,6 +38,15 @@ use opentelemetry_sdk::Resource; #[derive(Parser, Debug)] struct Args { + #[clap(flatten)] + global_opts: GlobalOpts, + + #[clap(subcommand)] + test_name: TestName, +} + +#[derive(clap::Args, Debug)] +struct GlobalOpts { #[arg( short = 'e', long, @@ -67,15 +77,6 @@ struct Args { )] stats_dump: bool, - #[clap(flatten)] - global_opts: GlobalOpts, - - #[clap(subcommand)] - test_name: TestName, -} - -#[derive(clap::Args, Debug)] -struct GlobalOpts { #[arg( long, short = 'l', @@ -103,6 +104,38 @@ struct GlobalOpts { default_value_t = 65 )] duration_minutes: u64, + #[arg( + long, + short = 'C', + required = false, + help = "Read cache strategy", + default_value_t = ArgCacheReadStrategy::WritesOnly + )] + cache_read_strategy: ArgCacheReadStrategy, +} +#[derive(Debug, PartialEq, ValueEnum, Clone)] +pub enum ArgCacheReadStrategy { + WritesOnly, + BranchReads, + All, +} +impl Display for ArgCacheReadStrategy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArgCacheReadStrategy::WritesOnly => write!(f, "writes-only"), + ArgCacheReadStrategy::BranchReads => write!(f, "branch-reads"), + ArgCacheReadStrategy::All => write!(f, "all"), + } + } +} +impl From for CacheReadStrategy { + fn from(arg: ArgCacheReadStrategy) -> Self { + match arg { + ArgCacheReadStrategy::WritesOnly => CacheReadStrategy::WritesOnly, + ArgCacheReadStrategy::BranchReads => CacheReadStrategy::BranchReads, + ArgCacheReadStrategy::All => CacheReadStrategy::All, + } + } } mod create; @@ -112,9 +145,16 @@ mod zipf; #[derive(Debug, Subcommand, PartialEq)] enum TestName { + /// Create a database Create, + + /// Insert batches of random keys TenKRandom, + + /// Insert batches of keys following a Zipf distribution Zipf(zipf::Args), + + /// Repeatedly update a single row Single, } @@ -148,7 +188,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; async fn main() -> Result<(), Box> { let args = Args::parse(); - if args.telemetry_server { + if args.global_opts.telemetry_server { let reporter = OpenTelemetryReporter::new( SpanExporter::builder() .with_tonic() @@ -172,7 +212,7 @@ async fn main() -> Result<(), Box> { fastrace::set_reporter(reporter, Config::default()); } - if args.test_name == TestName::Single && args.batch_size > 1000 { + if args.test_name == TestName::Single && args.global_opts.batch_size > 1000 { panic!("Single test is not designed to handle batch sizes > 1000"); } @@ -191,7 +231,7 @@ async fn main() -> Result<(), Box> { let (prometheus_recorder, listener_future) = builder .with_http_listener(SocketAddr::new( Ipv6Addr::UNSPECIFIED.into(), - args.prometheus_port, + args.global_opts.prometheus_port, )) .idle_timeout( MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, @@ -206,11 +246,12 @@ async fn main() -> Result<(), Box> { tokio::spawn(listener_future); let mgrcfg = RevisionManagerConfig::builder() - .node_cache_size(args.cache_size) + .node_cache_size(args.global_opts.cache_size) .free_list_cache_size( - NonZeroUsize::new(4 * args.batch_size as usize).expect("batch size > 0"), + NonZeroUsize::new(4 * args.global_opts.batch_size as usize).expect("batch size > 0"), ) - .max_revisions(args.revisions) + .cache_read_strategy(args.global_opts.cache_read_strategy.clone().into()) + .max_revisions(args.global_opts.revisions) .build(); let cfg = DbConfig::builder() .truncate(matches!(args.test_name, TestName::Create)) @@ -240,7 +281,7 @@ async fn main() -> Result<(), Box> { } } - if args.stats_dump { + if args.global_opts.stats_dump { println!("{}", prometheus_handle.render()); } diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index e90ae354416f..6cda4bdc9ce5 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -16,7 +16,7 @@ pub struct Single; impl TestRunner for Single { async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { let start = Instant::now(); - let inner_keys: Vec<_> = (0..args.batch_size) + let inner_keys: Vec<_> = (0..args.global_opts.batch_size) .map(|i| Sha256::digest(i.to_ne_bytes())) .collect(); let mut batch_id = 0; diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 1fa98847af10..78c9ab50e60c 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -17,8 +17,8 @@ pub struct TenKRandom; impl TestRunner for TenKRandom { async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { let mut low = 0; - let mut high = args.number_of_batches * args.batch_size; - let twenty_five_pct = args.batch_size / 4; + let mut high = args.global_opts.number_of_batches * args.global_opts.batch_size; + let twenty_five_pct = args.global_opts.batch_size / 4; let start = Instant::now(); diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index e2cede73bd56..30111c0a5a8b 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -28,14 +28,14 @@ impl TestRunner for Zipf { } else { unreachable!() }; - let rows = (args.number_of_batches * args.batch_size) as f64; + let rows = (args.global_opts.number_of_batches * args.global_opts.batch_size) as f64; let zipf = rand_distr::Zipf::new(rows, exponent).unwrap(); let start = Instant::now(); let mut batch_id = 0; while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch: Vec> = - generate_updates(batch_id, args.batch_size as usize, zipf).collect(); + generate_updates(batch_id, args.global_opts.batch_size as usize, zipf).collect(); if log::log_enabled!(log::Level::Debug) { let mut distinct = HashSet::new(); for op in &batch { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 51684e70936c..97bfaea72e2b 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -14,9 +14,8 @@ use typed_builder::TypedBuilder; use crate::v2::api::HashKey; -use storage::{ - CacheReadStrategy, Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash, -}; +pub use storage::CacheReadStrategy; +use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; #[derive(Clone, Debug, TypedBuilder)] /// Revision manager configuratoin diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 9a9710b6aa61..a8148eae4c6c 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -22,6 +22,8 @@ bytemuck = "1.7.0" bytemuck_derive = "1.7.0" bitfield = "0.18.1" fastrace = { version = "0.7.4" } +strum = "0.27.0" +strum_macros = "0.27.0" [dev-dependencies] rand = "0.9.0" diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 8a413b8581d4..fb221223851e 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -33,13 +33,14 @@ pub use nodestore::{ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; +use strum_macros::VariantArray; pub use trie_hash::TrieHash; /// The strategy for caching nodes that are read /// from the storage layer. Generally, we only want to /// cache write operations, but for some read-heavy workloads /// you can enable caching of branch reads or all reads. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, VariantArray)] pub enum CacheReadStrategy { /// Only cache writes (no reads will be cached) WritesOnly, @@ -50,3 +51,9 @@ pub enum CacheReadStrategy { /// Cache all reads (leaves and branches) All, } + +impl std::fmt::Display for CacheReadStrategy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} From a18b8ae8e2c67c50e299e0b05bb96822c12b071d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 12 Feb 2025 15:56:16 -0800 Subject: [PATCH 0659/1053] Rkuris/cache read strategy expose to ffi (#791) --- ffi/README.md | 9 ++-- ffi/firewood.go | 119 +++++++++++++++++++++++++++++++++++-------- ffi/firewood.h | 9 ++-- ffi/firewood_test.go | 8 +-- ffi/kvbackend.go | 10 ++-- ffi/src/lib.rs | 20 ++++++-- 6 files changed, 134 insertions(+), 41 deletions(-) diff --git a/ffi/README.md b/ffi/README.md index 3e782aa993d2..65feb27f2e6b 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -1,7 +1,10 @@ -A firewood golang interface +# A firewood golang interface This allows calling into firewood from golang -# Building +## Building -just run make. This builds the dbtest.go executable +First, build the release version (`cargo build --release`). This creates the ffi +interface file "firewood.h" as a side effect. + +Then, you can run the tests in go, using `go test .` diff --git a/ffi/firewood.go b/ffi/firewood.go index 340b5a3cdd25..553c475ea4c2 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -6,39 +6,112 @@ package firewood import "C" import ( "runtime" + "strconv" "unsafe" ) -const ( - // The size of the node cache for firewood - NodeCache = 1000000 - // The number of revisions to keep (must be >=2) - Revisions = 100 -) - +// Firewood is a handle to a Firewood database type Firewood struct { - Db *C.void + db *C.void } -// CreateDatabase creates a new Firewood database at the given path. -// Returns a handle that can be used for subsequent database operations. -func CreateDatabase(path string) Firewood { - db := C.fwd_create_db(C.CString(path), C.size_t(NodeCache), C.size_t(Revisions)) - ptr := (*C.void)(db) - return Firewood{Db: ptr} +// openConfig is used to configure the database at open time +type openConfig struct { + path string + nodeCacheEntries uintptr + revisions uintptr + readCacheStrategy int8 + create bool +} + +// OpenOption is a function that configures the database at open time +type OpenOption func(*openConfig) + +// WithPath sets the path for the database +func WithPath(path string) OpenOption { + return func(o *openConfig) { + o.path = path + } +} + +// WithNodeCacheEntries sets the number of node cache entries +func WithNodeCacheEntries(entries uintptr) OpenOption { + if entries < 1 { + panic("Node cache entries must be >= 1") + } + return func(o *openConfig) { + o.nodeCacheEntries = entries + } +} + +// WithRevisions sets the number of revisions to keep +func WithRevisions(revisions uintptr) OpenOption { + if revisions < 2 { + panic("Revisions must be >= 2") + } + return func(o *openConfig) { + o.revisions = revisions + } } -func OpenDatabase(path string) Firewood { - db := C.fwd_open_db(C.CString(path), C.size_t(NodeCache), C.size_t(Revisions)) +// WithReadCacheStrategy sets the read cache strategy +// 0: Only writes are cached +// 1: Branch reads are cached +// 2: All reads are cached +func WithReadCacheStrategy(strategy int8) OpenOption { + if (strategy < 0) || (strategy > 2) { + panic("Invalid read cache strategy " + strconv.Itoa(int(strategy))) + } + return func(o *openConfig) { + o.readCacheStrategy = strategy + } +} + +// WithCreate sets whether to create a new database +// If false, the database will be opened +// If true, the database will be created +func WithCreate(create bool) OpenOption { + return func(o *openConfig) { + o.create = create + } +} + +// NewDatabase opens or creates a new Firewood database with the given options. +// Returns a handle that can be used for subsequent database operations. +func NewDatabase(options ...OpenOption) Firewood { + opts := &openConfig{ + nodeCacheEntries: 1_000_000, + revisions: 100, + readCacheStrategy: 0, + path: "firewood.db", + } + + for _, opt := range options { + opt(opts) + } + var db unsafe.Pointer + if opts.create { + db = C.fwd_create_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy)) + } else { + db = C.fwd_open_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy)) + } + ptr := (*C.void)(db) - return Firewood{Db: ptr} + return Firewood{db: ptr} } +// KeyValue is a key-value pair type KeyValue struct { Key []byte Value []byte } +// Apply a batch of updates to the database +// Returns the hash of the root node after the batch is applied +// Note that if the Value is empty, the key will be deleted as a prefix +// delete (that is, all children will be deleted) +// WARNING: Calling it with an empty key and value will delete the entire database + func (f *Firewood) Batch(ops []KeyValue) []byte { var pin runtime.Pinner defer pin.Unpin() @@ -51,18 +124,19 @@ func (f *Firewood) Batch(ops []KeyValue) []byte { } } ptr := (*C.struct_KeyValue)(unsafe.Pointer(&ffi_ops[0])) - hash := C.fwd_batch(unsafe.Pointer(f.Db), C.size_t(len(ops)), ptr) + hash := C.fwd_batch(unsafe.Pointer(f.db), C.size_t(len(ops)), ptr) hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) C.fwd_free_value(&hash) return hash_bytes } +// Get retrieves the value for the given key. func (f *Firewood) Get(input_key []byte) ([]byte, error) { var pin runtime.Pinner defer pin.Unpin() ffi_key := make_value(&pin, input_key) - value := C.fwd_get(unsafe.Pointer(f.Db), ffi_key) + value := C.fwd_get(unsafe.Pointer(f.db), ffi_key) ffi_bytes := C.GoBytes(unsafe.Pointer(value.data), C.int(value.len)) C.fwd_free_value(&value) if len(ffi_bytes) == 0 { @@ -70,6 +144,7 @@ func (f *Firewood) Get(input_key []byte) ([]byte, error) { } return ffi_bytes, nil } + func make_value(pin *runtime.Pinner, data []byte) C.struct_Value { if len(data) == 0 { return C.struct_Value{0, nil} @@ -79,14 +154,16 @@ func make_value(pin *runtime.Pinner, data []byte) C.struct_Value { return C.struct_Value{C.size_t(len(data)), ptr} } +// Root returns the current root hash of the trie. func (f *Firewood) Root() []byte { - hash := C.fwd_root_hash(unsafe.Pointer(f.Db)) + hash := C.fwd_root_hash(unsafe.Pointer(f.db)) hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) C.fwd_free_value(&hash) return hash_bytes } +// Close closes the database and releases all held resources. func (f *Firewood) Close() error { - C.fwd_close_db(unsafe.Pointer(f.Db)) + C.fwd_close_db(unsafe.Pointer(f.db)) return nil } diff --git a/ffi/firewood.h b/ffi/firewood.h index 78624114567c..e287d8788500 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -54,7 +54,8 @@ struct Value fwd_batch(void *db, void fwd_close_db(void *db); /** - * Create a database with the given cache size and maximum number of revisions + * Create a database with the given cache size and maximum number of revisions, as well + * as a specific cache strategy * * # Arguments * @@ -76,7 +77,8 @@ void fwd_close_db(void *db); */ void *fwd_create_db(const char *path, size_t cache_size, - size_t revisions); + size_t revisions, + uint8_t strategy); /** * Frees the memory associated with a `Value`. @@ -127,7 +129,8 @@ struct Value fwd_get(void *db, struct Value key); */ void *fwd_open_db(const char *path, size_t cache_size, - size_t revisions); + size_t revisions, + uint8_t strategy); /** * Get the root hash of the latest version of the database diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 42ab7f6d9a5a..0e9ece98d141 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -7,7 +7,7 @@ import ( ) func TestInsert(t *testing.T) { - var f Firewood = CreateDatabase("test.db") + var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) defer os.Remove("test.db") defer f.Close() f.Batch([]KeyValue{ @@ -21,7 +21,7 @@ func TestInsert(t *testing.T) { } func TestInsert100(t *testing.T) { - var f Firewood = CreateDatabase("test.db") + var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) defer os.Remove("test.db") defer f.Close() ops := make([]KeyValue, 100) @@ -57,7 +57,7 @@ func TestInsert100(t *testing.T) { func TestRangeDelete(t *testing.T) { const N = 100 - var f Firewood = CreateDatabase("test.db") + var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) defer os.Remove("test.db") defer f.Close() ops := make([]KeyValue, N) @@ -84,7 +84,7 @@ func TestRangeDelete(t *testing.T) { } func TestInvariants(t *testing.T) { - var f Firewood = CreateDatabase("test.db") + var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) defer os.Remove("test.db") defer f.Close() diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index 0aa987641352..e42dfb57ab6b 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -4,10 +4,9 @@ package firewood // this is used for some of the firewood performance tests // Validate that Firewood implements the KVBackend interface -var _ KVBackend = (*Firewood)(nil) +var _ kVBackend = (*Firewood)(nil) -// Copy of KVBackend from ava-labs/avalanchego -type KVBackend interface { +type kVBackend interface { // Returns the current root hash of the trie. // Empty trie must return common.Hash{}. // Length of the returned slice must be common.HashLength. @@ -46,12 +45,13 @@ func (f *Firewood) Prefetch(key []byte) ([]byte, error) { return nil, nil } -// / Commit does nothing, since update already persists changes +// Commit does nothing, since update already persists changes func (f *Firewood) Commit(root []byte) error { return nil } -// Update could use some more work, but for now we just batch the keys and values +// Update batches all the keys and values and applies them to the +// database func (f *Firewood) Update(keys, vals [][]byte) ([]byte, error) { // batch the keys and values ops := make([]KeyValue, len(keys)) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 722ff96da0f6..e05e67717397 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -7,7 +7,7 @@ use std::os::unix::ffi::OsStrExt as _; use std::path::Path; use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _}; -use firewood::manager::RevisionManagerConfig; +use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; #[derive(Debug)] #[repr(C)] @@ -160,7 +160,8 @@ pub unsafe extern "C" fn fwd_free_value(value: *const Value) { drop(recreated_box); } -/// Create a database with the given cache size and maximum number of revisions +/// Create a database with the given cache size and maximum number of revisions, as well +/// as a specific cache strategy /// /// # Arguments /// @@ -184,10 +185,11 @@ pub unsafe extern "C" fn fwd_create_db( path: *const std::ffi::c_char, cache_size: usize, revisions: usize, + strategy: u8, ) -> *mut Db { let cfg = DbConfig::builder() .truncate(true) - .manager(manager_config(cache_size, revisions)) + .manager(manager_config(cache_size, revisions, strategy)) .build(); common_create(path, cfg) } @@ -216,10 +218,11 @@ pub unsafe extern "C" fn fwd_open_db( path: *const std::ffi::c_char, cache_size: usize, revisions: usize, + strategy: u8, ) -> *mut Db { let cfg = DbConfig::builder() .truncate(false) - .manager(manager_config(cache_size, revisions)) + .manager(manager_config(cache_size, revisions, strategy)) .build(); common_create(path, cfg) } @@ -232,7 +235,13 @@ unsafe fn common_create(path: *const std::ffi::c_char, cfg: DbConfig) -> *mut Db )) } -fn manager_config(cache_size: usize, revisions: usize) -> RevisionManagerConfig { +fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> RevisionManagerConfig { + let cache_read_strategy = match strategy { + 0 => CacheReadStrategy::WritesOnly, + 1 => CacheReadStrategy::BranchReads, + 2 => CacheReadStrategy::All, + _ => panic!("invalid cache strategy"), + }; RevisionManagerConfig::builder() .node_cache_size( cache_size @@ -240,6 +249,7 @@ fn manager_config(cache_size: usize, revisions: usize) -> RevisionManagerConfig .expect("cache size should always be non-zero"), ) .max_revisions(revisions) + .cache_read_strategy(cache_read_strategy) .build() } From 36fc157b64366df96ef98c6b2a7e2013f5fb01ee Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 13 Feb 2025 19:26:38 -0800 Subject: [PATCH 0660/1053] Add periodic metric reporting to the ffi (#792) --- ffi/Cargo.toml | 4 ++ ffi/firewood.go | 22 ++++-- ffi/firewood.h | 6 +- ffi/src/lib.rs | 17 ++++- ffi/src/metrics.rs | 175 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 ffi/src/metrics.rs diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 9c117d309990..b9348cbc6398 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -9,6 +9,10 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.2" firewood = { path = "../firewood" } +metrics = "0.24.1" +metrics-util = "0.19.0" +chrono = "0.4.39" +oxhttp = "0.3.0" [build-dependencies] cbindgen = "0.28.0" diff --git a/ffi/firewood.go b/ffi/firewood.go index 553c475ea4c2..e686d8b33e75 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -1,6 +1,6 @@ package firewood -// #cgo LDFLAGS: -L../target/release -L/usr/local/lib -lfirewood_ffi +// #cgo LDFLAGS: -L../target/release -L/usr/local/lib -lfirewood_ffi -lm // #include "firewood.h" // #include import "C" @@ -20,8 +20,9 @@ type openConfig struct { path string nodeCacheEntries uintptr revisions uintptr - readCacheStrategy int8 + readCacheStrategy uint8 create bool + metricsPort uint16 } // OpenOption is a function that configures the database at open time @@ -54,12 +55,20 @@ func WithRevisions(revisions uintptr) OpenOption { } } +// WithMetricIntervalSeconds sets the interval in seconds for metrics +// set to 0 for no metrics +func WithMetricsPort(metrics_port uint16) OpenOption { + return func(o *openConfig) { + o.metricsPort = metrics_port + } +} + // WithReadCacheStrategy sets the read cache strategy // 0: Only writes are cached // 1: Branch reads are cached // 2: All reads are cached -func WithReadCacheStrategy(strategy int8) OpenOption { - if (strategy < 0) || (strategy > 2) { +func WithReadCacheStrategy(strategy uint8) OpenOption { + if strategy > 2 { panic("Invalid read cache strategy " + strconv.Itoa(int(strategy))) } return func(o *openConfig) { @@ -84,6 +93,7 @@ func NewDatabase(options ...OpenOption) Firewood { revisions: 100, readCacheStrategy: 0, path: "firewood.db", + metricsPort: 3000, } for _, opt := range options { @@ -91,9 +101,9 @@ func NewDatabase(options ...OpenOption) Firewood { } var db unsafe.Pointer if opts.create { - db = C.fwd_create_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy)) + db = C.fwd_create_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy), C.uint16_t(opts.metricsPort)) } else { - db = C.fwd_open_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy)) + db = C.fwd_open_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy), C.uint16_t(opts.metricsPort)) } ptr := (*C.void)(db) diff --git a/ffi/firewood.h b/ffi/firewood.h index e287d8788500..2389a08a119f 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -78,7 +78,8 @@ void fwd_close_db(void *db); void *fwd_create_db(const char *path, size_t cache_size, size_t revisions, - uint8_t strategy); + uint8_t strategy, + uint16_t metrics_port); /** * Frees the memory associated with a `Value`. @@ -130,7 +131,8 @@ struct Value fwd_get(void *db, struct Value key); void *fwd_open_db(const char *path, size_t cache_size, size_t revisions, - uint8_t strategy); + uint8_t strategy, + uint16_t metrics_port); /** * Get the root hash of the latest version of the database diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e05e67717397..c3b332dbcc99 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -9,6 +9,8 @@ use std::path::Path; use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; +mod metrics; + #[derive(Debug)] #[repr(C)] pub struct Value { @@ -186,12 +188,13 @@ pub unsafe extern "C" fn fwd_create_db( cache_size: usize, revisions: usize, strategy: u8, + metrics_port: u16, ) -> *mut Db { let cfg = DbConfig::builder() .truncate(true) .manager(manager_config(cache_size, revisions, strategy)) .build(); - common_create(path, cfg) + common_create(path, metrics_port, cfg) } /// Open a database with the given cache size and maximum number of revisions @@ -219,17 +222,25 @@ pub unsafe extern "C" fn fwd_open_db( cache_size: usize, revisions: usize, strategy: u8, + metrics_port: u16, ) -> *mut Db { let cfg = DbConfig::builder() .truncate(false) .manager(manager_config(cache_size, revisions, strategy)) .build(); - common_create(path, cfg) + common_create(path, metrics_port, cfg) } -unsafe fn common_create(path: *const std::ffi::c_char, cfg: DbConfig) -> *mut Db { +unsafe fn common_create( + path: *const std::ffi::c_char, + metrics_port: u16, + cfg: DbConfig, +) -> *mut Db { let path = unsafe { CStr::from_ptr(path) }; let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); + if metrics_port > 0 { + metrics::setup_metrics(metrics_port); + } Box::into_raw(Box::new( Db::new_sync(path, cfg).expect("db initialization should succeed"), )) diff --git a/ffi/src/metrics.rs b/ffi/src/metrics.rs new file mode 100644 index 000000000000..edacb8d48628 --- /dev/null +++ b/ffi/src/metrics.rs @@ -0,0 +1,175 @@ +use std::{ + collections::HashSet, + io::Write, + net::Ipv6Addr, + ops::Deref, + sync::{atomic::Ordering, Arc, Once}, + time::SystemTime, +}; + +use oxhttp::model::{Body, Response, StatusCode}; +use oxhttp::Server; +use std::net::Ipv4Addr; +use std::time::Duration; + +use chrono::{DateTime, Utc}; + +use metrics::Key; +use metrics_util::registry::{AtomicStorage, Registry}; + +static INIT: Once = Once::new(); + +pub(crate) fn setup_metrics(metrics_port: u16) { + INIT.call_once(|| { + let inner: TextRecorderInner = TextRecorderInner { + registry: Registry::atomic(), + }; + let recorder = TextRecorder { + inner: Arc::new(inner), + }; + metrics::set_global_recorder(recorder.clone()).expect("failed to set recorder"); + + Server::new(move |request| { + if request.method() != "GET" { + Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::from("Method not allowed")) + .expect("failed to build response") + } else { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/plain") + .body(Body::from(recorder.stats())) + .expect("failed to build response") + } + }) + .bind((Ipv4Addr::LOCALHOST, metrics_port)) + .bind((Ipv6Addr::LOCALHOST, metrics_port)) + .with_global_timeout(Duration::from_secs(60 * 60)) + .with_max_concurrent_connections(2) + .spawn() + .expect("failed to spawn server"); + }); +} + +#[derive(Debug)] +struct TextRecorderInner { + registry: Registry, +} + +#[derive(Debug, Clone)] +struct TextRecorder { + inner: Arc, +} + +impl TextRecorder { + fn stats(&self) -> String { + let mut output = Vec::new(); + let systemtime_now = SystemTime::now(); + let utc_now: DateTime = systemtime_now.into(); + let epoch_duration = systemtime_now + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + let epoch_ms = epoch_duration.as_secs() * 1000 + epoch_duration.subsec_millis() as u64; + writeln!(output, "# {}", utc_now).unwrap(); + + let counters = self.registry.get_counter_handles(); + let mut seen = HashSet::new(); + for (key, counter) in counters { + let sanitized_key_name = key.name().to_string().replace('.', "_"); + if !seen.contains(&sanitized_key_name) { + writeln!( + output, + "# TYPE {} counter", + key.name().to_string().replace('.', "_") + ) + .expect("write error"); + seen.insert(sanitized_key_name.clone()); + } + write!(output, "{sanitized_key_name}").expect("write error"); + if key.labels().len() > 0 { + write!( + output, + "{{{}}}", + key.labels() + .map(|label| format!("{}=\"{}\"", label.key(), label.value())) + .collect::>() + .join(",") + ) + .expect("write error"); + } + writeln!(output, " {} {}", counter.load(Ordering::Relaxed), epoch_ms) + .expect("write error"); + } + writeln!(output).expect("write error"); + output.flush().expect("flush error"); + + std::str::from_utf8(output.as_slice()) + .expect("failed to convert to string") + .into() + } +} + +impl Deref for TextRecorder { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl metrics::Recorder for TextRecorder { + fn describe_counter( + &self, + _key: metrics::KeyName, + _unit: Option, + _description: metrics::SharedString, + ) { + } + + fn describe_gauge( + &self, + _key: metrics::KeyName, + _unit: Option, + _description: metrics::SharedString, + ) { + } + + fn describe_histogram( + &self, + _key: metrics::KeyName, + _unit: Option, + _description: metrics::SharedString, + ) { + } + + fn register_counter( + &self, + key: &metrics::Key, + _metadata: &metrics::Metadata<'_>, + ) -> metrics::Counter { + self.inner + .registry + .get_or_create_counter(key, |c| c.clone().into()) + } + + fn register_gauge( + &self, + key: &metrics::Key, + _metadata: &metrics::Metadata<'_>, + ) -> metrics::Gauge { + self.inner + .registry + .get_or_create_gauge(key, |c| c.clone().into()) + } + + fn register_histogram( + &self, + key: &metrics::Key, + _metadata: &metrics::Metadata<'_>, + ) -> metrics::Histogram { + self.inner + .registry + .get_or_create_histogram(key, |c| c.clone().into()) + } +} From 97e4e82e0fdc29c44c7a981b71957466c4191f44 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 14 Feb 2025 17:11:01 -0800 Subject: [PATCH 0661/1053] Switch Arc for triomphe::Arc (#793) --- benchmark/README.md | 5 +--- firewood/src/merkle.rs | 14 +++++------ firewood/src/stream.rs | 17 +++++++------- storage/Cargo.toml | 3 +-- storage/src/lib.rs | 6 +++-- storage/src/linear/filebacked.rs | 12 +++++----- storage/src/linear/mod.rs | 9 ++++--- storage/src/node/mod.rs | 20 ++-------------- storage/src/nodestore.rs | 40 ++++++++++++++++---------------- 9 files changed, 54 insertions(+), 72 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 27e1ba94074a..18a35bcbc921 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -169,7 +169,7 @@ If you're looking for detailed logging, there are some command line options to e cargo run --profile release --bin benchmark -- -l debug -n 10000 single ``` -# Using opentelemetry +## Using opentelemetry To use the opentelemetry server and record timings, just run a docker image that collects the data using: @@ -178,6 +178,3 @@ docker run -p 127.0.0.1:4318:4318 -p 127.0.0.1:55679:55679 otel/openteleme ``` Then, pass the `-e` option to the benchmark. -``` - -Then, pass the `-e` option to the benchmark. diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1f6e2f31c1db..bc431c3394d6 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,8 +16,8 @@ use std::num::NonZeroUsize; use std::sync::Arc; use storage::{ BranchNode, Child, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, LinearAddress, - MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, TrieHash, TrieReader, - ValueDigest, + MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, SharedNode, TrieHash, + TrieReader, ValueDigest, }; use thiserror::Error; @@ -93,7 +93,7 @@ fn get_helper( nodestore: &T, node: &Node, key: &[u8], -) -> Result>, MerkleError> { +) -> Result, MerkleError> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -111,7 +111,7 @@ fn get_helper( // Case (2) or (4) Ok(None) } - (None, None) => Ok(Some(Arc::new(node.clone()))), // 1. The node is at `key` + (None, None) => Ok(Some(node.clone().into())), // 1. The node is at `key` (Some((child_index, remaining_key)), None) => { // 3. The key is below the node (i.e. its descendant) match node { @@ -152,7 +152,7 @@ impl From for Merkle { } impl Merkle { - pub(crate) fn root(&self) -> Option> { + pub(crate) fn root(&self) -> Option { self.nodestore.root_node() } @@ -161,7 +161,7 @@ impl Merkle { &self.nodestore } - fn read_node(&self, addr: LinearAddress) -> Result, MerkleError> { + fn read_node(&self, addr: LinearAddress) -> Result { self.nodestore.read_node(addr).map_err(Into::into) } @@ -335,7 +335,7 @@ impl Merkle { Ok(node.value().map(|v| v.to_vec().into_boxed_slice())) } - pub(crate) fn get_node(&self, key: &[u8]) -> Result>, MerkleError> { + pub(crate) fn get_node(&self, key: &[u8]) -> Result, MerkleError> { let Some(root) = self.root() else { return Ok(None); }; diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index be811c84408a..0df5ed62f2ae 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -8,9 +8,8 @@ use futures::stream::FusedStream; use futures::{Stream, StreamExt}; use std::cmp::Ordering; use std::iter::once; -use std::sync::Arc; use std::task::Poll; -use storage::{BranchNode, Child, NibblesIterator, Node, PathIterItem, TrieReader}; +use storage::{BranchNode, Child, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader}; /// Represents an ongoing iteration over a node and its children. enum IterationNode { @@ -18,7 +17,7 @@ enum IterationNode { Unvisited { /// The key (as nibbles) of this node. key: Key, - node: Arc, + node: SharedNode, }, /// This node has been returned. Track which child to visit next. Visited { @@ -94,7 +93,7 @@ impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { } impl Stream for MerkleNodeStream<'_, T> { - type Item = Result<(Key, Arc), api::Error>; + type Item = Result<(Key, SharedNode), api::Error>; fn poll_next( mut self: std::pin::Pin<&mut Self>, @@ -141,7 +140,7 @@ impl Stream for MerkleNodeStream<'_, T> { let child = match child { Child::AddressWithHash(addr, _) => merkle.read_node(addr)?, - Child::Node(node) => Arc::new(node.clone()), + Child::Node(node) => node.clone().into(), }; let child_partial_path = child.partial_path().iter().copied(); @@ -251,7 +250,7 @@ fn get_iterator_intial_state( node = match child { None => return Ok(NodeStreamState::Iterating { iter_stack }), Some(Child::AddressWithHash(addr, _)) => merkle.read_node(*addr)?, - Some(Child::Node(node)) => Arc::new((*node).clone()), // TODO can we avoid cloning this? + Some(Child::Node(node)) => (*node).clone().into(), // TODO can we avoid cloning this? }; matched_key_nibbles.push(next_unmatched_key_nibble); @@ -374,7 +373,7 @@ enum PathIteratorState<'a> { /// prefix of the key we're traversing to. matched_key: Vec, unmatched_key: NibblesIterator<'a>, - node: Arc, + node: SharedNode, }, Exhausted, } @@ -504,7 +503,7 @@ impl Iterator for PathIterator<'_, '_, T> { matched_key.push(next_unmatched_key_nibble); let ret = node.clone(); - *node = Arc::new(child.clone()); + *node = child.clone().into(); Some(Ok(PathIterItem { key_nibbles: node_key, @@ -584,6 +583,8 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { #[cfg(test)] #[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { + use std::sync::Arc; + use storage::{MemStore, MutableProposal, NodeStore}; use crate::merkle::Merkle; diff --git a/storage/Cargo.toml b/storage/Cargo.toml index a8148eae4c6c..2a64241d3cba 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -22,8 +22,7 @@ bytemuck = "1.7.0" bytemuck_derive = "1.7.0" bitfield = "0.18.1" fastrace = { version = "0.7.4" } -strum = "0.27.0" -strum_macros = "0.27.0" +triomphe = "0.1.14" [dev-dependencies] rand = "0.9.0" diff --git a/storage/src/lib.rs b/storage/src/lib.rs index fb221223851e..69fcfc932cdc 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -33,14 +33,16 @@ pub use nodestore::{ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; -use strum_macros::VariantArray; pub use trie_hash::TrieHash; +/// A shared node, which is just a triophe Arc of a node +pub type SharedNode = triomphe::Arc; + /// The strategy for caching nodes that are read /// from the storage layer. Generally, we only want to /// cache write operations, but for some read-heavy workloads /// you can enable caching of branch reads or all reads. -#[derive(Clone, Debug, VariantArray)] +#[derive(Clone, Debug)] pub enum CacheReadStrategy { /// Only cache writes (no reads will be cached) WritesOnly, diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 47eae344d1db..d53e272d9075 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -11,12 +11,12 @@ use std::io::{Error, Read}; use std::num::NonZero; use std::os::unix::fs::FileExt; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; use lru::LruCache; use metrics::counter; -use crate::{CacheReadStrategy, LinearAddress, Node}; +use crate::{CacheReadStrategy, LinearAddress, SharedNode}; use super::{ReadableStorage, WritableStorage}; @@ -24,7 +24,7 @@ use super::{ReadableStorage, WritableStorage}; /// A [ReadableStorage] backed by a file pub struct FileBacked { fd: File, - cache: Mutex>>, + cache: Mutex>, free_list_cache: Mutex>>, cache_read_strategy: CacheReadStrategy, } @@ -63,7 +63,7 @@ impl ReadableStorage for FileBacked { Ok(self.fd.metadata()?.len()) } - fn read_cached_node(&self, addr: LinearAddress) -> Option> { + fn read_cached_node(&self, addr: LinearAddress) -> Option { let mut guard = self.cache.lock().expect("poisoned lock"); let cached = guard.get(&addr).cloned(); counter!("firewood.cache.node", "type" => if cached.is_some() { "hit" } else { "miss" }) @@ -82,7 +82,7 @@ impl ReadableStorage for FileBacked { &self.cache_read_strategy } - fn cache_node(&self, addr: LinearAddress, node: Arc) { + fn cache_node(&self, addr: LinearAddress, node: SharedNode) { match self.cache_read_strategy { CacheReadStrategy::WritesOnly => { // we don't cache reads @@ -108,7 +108,7 @@ impl WritableStorage for FileBacked { fn write_cached_nodes<'a>( &self, - nodes: impl Iterator, &'a std::sync::Arc)>, + nodes: impl Iterator, &'a SharedNode)>, ) -> Result<(), Error> { let mut guard = self.cache.lock().expect("poisoned lock"); for (addr, node) in nodes { diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 87340f5e8f55..a61f4a61e7e2 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -20,9 +20,8 @@ use std::fmt::Debug; use std::io::{Error, Read}; use std::num::NonZero; -use std::sync::Arc; -use crate::{CacheReadStrategy, LinearAddress, Node}; +use crate::{CacheReadStrategy, LinearAddress, SharedNode}; pub(super) mod filebacked; pub mod memory; @@ -43,7 +42,7 @@ pub trait ReadableStorage: Debug + Sync + Send { fn size(&self) -> Result; /// Read a node from the cache (if any) - fn read_cached_node(&self, _addr: LinearAddress) -> Option> { + fn read_cached_node(&self, _addr: LinearAddress) -> Option { None } @@ -58,7 +57,7 @@ pub trait ReadableStorage: Debug + Sync + Send { } /// Cache a node for future reads - fn cache_node(&self, _addr: LinearAddress, _node: Arc) {} + fn cache_node(&self, _addr: LinearAddress, _node: SharedNode) {} } /// Trait for writable storage. @@ -78,7 +77,7 @@ pub trait WritableStorage: ReadableStorage { /// Write all nodes to the cache (if any) fn write_cached_nodes<'a>( &self, - _nodes: impl Iterator, &'a Arc)>, + _nodes: impl Iterator, &'a SharedNode)>, ) -> Result<(), Error> { Ok(()) } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 48d0dd05fb2a..94723ff20e1b 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::io::{Error, ErrorKind, Read, Write}; use std::num::NonZero; -use std::sync::Arc; use std::vec; mod branch; @@ -18,7 +17,7 @@ pub mod path; pub use branch::{BranchNode, Child}; pub use leaf::LeafNode; -use crate::Path; +use crate::{Path, SharedNode}; /// A node, either a Branch or Leaf @@ -177,21 +176,6 @@ impl Node { } } - /// Returns a new `Arc` which is the same as `self` but with the given `partial_path`. - pub fn new_with_partial_path(self: &Node, partial_path: Path) -> Node { - match self { - Node::Branch(b) => Node::Branch(Box::new(BranchNode { - partial_path, - value: b.value.clone(), - children: b.children.clone(), - })), - Node::Leaf(l) => Node::Leaf(LeafNode { - partial_path, - value: l.value.clone(), - }), - } - } - /// Returns Some(value) inside the node, or None if the node is a branch /// with no value. pub fn value(&self) -> Option<&[u8]> { @@ -454,7 +438,7 @@ pub struct PathIterItem { /// The key of the node at `address` as nibbles. pub key_nibbles: Box<[u8]>, /// A reference to the node - pub node: Arc, + pub node: SharedNode, /// The next item returned by the iterator is a child of `node`. /// Specifically, it's the child at index `next_nibble` in `node`'s /// children array. diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index f63614fffb70..4f9bf9715ab8 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -61,7 +61,7 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::{ByteCounter, Node}; -use crate::{CacheReadStrategy, Child, FileBacked, Path, ReadableStorage, TrieHash}; +use crate::{CacheReadStrategy, Child, FileBacked, Path, ReadableStorage, SharedNode, TrieHash}; use super::linear::WritableStorage; @@ -210,7 +210,7 @@ impl NodeStore { /// Read a [Node] from the provided [LinearAddress]. /// `addr` is the address of a StoredArea in the ReadableStorage. - pub fn read_node_from_disk(&self, addr: LinearAddress) -> Result, Error> { + pub fn read_node_from_disk(&self, addr: LinearAddress) -> Result { if let Some(node) = self.storage.read_cached_node(addr) { return Ok(node); } @@ -222,7 +222,7 @@ impl NodeStore { let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); let area_stream = self.storage.stream_from(actual_addr)?; - let node = Arc::new(Node::from_reader(area_stream)?); + let node: SharedNode = Node::from_reader(area_stream)?.into(); match self.storage.cache_read_strategy() { CacheReadStrategy::All => { self.storage.cache_node(addr, node.clone()); @@ -657,7 +657,7 @@ impl TrieReader for T where T: NodeReader + RootReader {} /// Reads nodes from a merkle trie. pub trait NodeReader { /// Returns the node at `addr`. - fn read_node(&self, addr: LinearAddress) -> Result, Error>; + fn read_node(&self, addr: LinearAddress) -> Result; } impl NodeReader for T @@ -665,7 +665,7 @@ where T: Deref, T::Target: NodeReader, { - fn read_node(&self, addr: LinearAddress) -> Result, Error> { + fn read_node(&self, addr: LinearAddress) -> Result { self.deref().read_node(addr) } } @@ -675,7 +675,7 @@ where T: Deref, T::Target: RootReader, { - fn root_node(&self) -> Option> { + fn root_node(&self) -> Option { self.deref().root_node() } } @@ -683,7 +683,7 @@ where /// Reads the root of a merkle trie. pub trait RootReader { /// Returns the root of the trie. - fn root_node(&self) -> Option>; + fn root_node(&self) -> Option; } /// A committed revision of a merkle trie. @@ -696,7 +696,7 @@ pub struct Committed { impl ReadInMemoryNode for Committed { // A committed revision has no in-memory changes. All its nodes are in storage. - fn read_in_memory_node(&self, _addr: LinearAddress) -> Option> { + fn read_in_memory_node(&self, _addr: LinearAddress) -> Option { None } } @@ -723,7 +723,7 @@ impl Eq for NodeStoreParent {} /// Contains state for a proposed revision of the trie. pub struct ImmutableProposal { /// Address --> Node for nodes created in this proposal. - new: HashMap)>, + new: HashMap, /// Nodes that have been deleted in this proposal. deleted: Box<[LinearAddress]>, /// The parent of this proposal. @@ -747,7 +747,7 @@ impl ImmutableProposal { } impl ReadInMemoryNode for ImmutableProposal { - fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { // Check if the node being requested was created in this proposal. if let Some((_, node)) = self.new.get(&addr) { return Some(node.clone()); @@ -770,7 +770,7 @@ impl ReadInMemoryNode for ImmutableProposal { pub trait ReadInMemoryNode { /// Returns the node at `addr` if it is in memory. /// Returns None if it isn't. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option>; + fn read_in_memory_node(&self, addr: LinearAddress) -> Option; } impl ReadInMemoryNode for T @@ -778,7 +778,7 @@ where T: Deref, T::Target: ReadInMemoryNode, { - fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { self.deref().read_in_memory_node(addr) } } @@ -819,7 +819,7 @@ pub struct MutableProposal { impl ReadInMemoryNode for NodeStoreParent { /// Returns the node at `addr` if it is in memory from a parent proposal. /// If the base revision is committed, there are no in-memory nodes, so we return None - fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { match self { NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), NodeStoreParent::Committed(_) => None, @@ -830,7 +830,7 @@ impl ReadInMemoryNode for NodeStoreParent { impl ReadInMemoryNode for MutableProposal { /// [MutableProposal] types do not have any nodes in memory, but their parent proposal might, so we check there. /// This might be recursive: a grandparent might also have that node in memory. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option> { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { self.parent.read_in_memory_node(addr) } } @@ -872,7 +872,7 @@ impl NodeStore, S> { &mut self, mut node: Node, path_prefix: &mut Path, - new_nodes: &mut HashMap)>, + new_nodes: &mut HashMap, ) -> (LinearAddress, TrieHash) { // If this is a branch, find all unhashed children and recursively call hash_helper on them. if let Node::Branch(ref mut b) = node { @@ -908,7 +908,7 @@ impl NodeStore, S> { let hash = hash_node(&node, path_prefix); let (addr, size) = self.allocate_node(&node).expect("TODO handle error"); - new_nodes.insert(addr, (size, Arc::new(node))); + new_nodes.insert(addr, (size, node.into())); (addr, hash) } @@ -1026,7 +1026,7 @@ impl From> } impl NodeReader for NodeStore { - fn read_node(&self, addr: LinearAddress) -> Result, Error> { + fn read_node(&self, addr: LinearAddress) -> Result { if let Some(node) = self.kind.read_in_memory_node(addr) { return Ok(node); } @@ -1036,8 +1036,8 @@ impl NodeReader for NodeStore { } impl RootReader for NodeStore { - fn root_node(&self) -> Option> { - self.kind.root.as_ref().map(|node| Arc::new(node.clone())) + fn root_node(&self) -> Option { + self.kind.root.as_ref().map(|node| node.clone().into()) } } @@ -1046,7 +1046,7 @@ impl Hashed for Committed {} impl Hashed for Arc {} impl RootReader for NodeStore { - fn root_node(&self) -> Option> { + fn root_node(&self) -> Option { // TODO: If the read_node fails, we just say there is no root; this is incorrect self.header .root_address From 565a80ff0fbb7ad0a9da349a0ec0181a0a9e2873 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 14 Feb 2025 17:38:39 -0800 Subject: [PATCH 0662/1053] io_uring implementation (#778) Signed-off-by: Ron Kuris --- firewood/Cargo.toml | 10 ++-- firewood/src/db.rs | 4 +- storage/Cargo.toml | 2 + storage/src/lib.rs | 2 +- storage/src/linear/filebacked.rs | 52 ++++++++++++++++++++- storage/src/nodestore.rs | 80 +++++++++++++++++++++++++++++++- 6 files changed, 142 insertions(+), 8 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 7cc070dbad2b..5b5c79a79867 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -18,7 +18,6 @@ readme = "../README.md" [dependencies] aquamarine = "0.6.0" async-trait = "0.1.77" -storage = { version = "0.0.4", path = "../storage" } futures = "0.3.30" hex = "0.4.3" metrics = "0.24.0" @@ -29,14 +28,13 @@ thiserror = "2.0.3" typed-builder = "0.20.0" bincode = "1.3.3" integer-encoding = "4.0.0" -io-uring = {version = "0.7", optional = true } smallvec = "1.6.1" fastrace = { version = "0.7.4" } [features] default = [] nightly = [] -iouring = ["io-uring"] +io-uring = ["storage/io-uring"] logger = ["storage/logger"] branch_factor_256 = [ "storage/branch_factor_256" ] @@ -60,3 +58,9 @@ unwrap_used = "warn" indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" + +[target.'cfg(target_os = "linux")'.dependencies] +storage = { version = "0.0.4", path = "../storage", features = ["io-uring"] } + +[target.'cfg(not(target_os = "linux"))'.dependencies] +storage = { version = "0.0.4", path = "../storage" } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7ff930285fa9..e5855a5d8ca8 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -425,7 +425,7 @@ impl<'a> api::Proposal for Proposal<'a> { match Arc::into_inner(self) { Some(proposal) => { let mut manager = proposal.db.manager.write().expect("poisoned lock"); - Ok(manager.commit(proposal.nodestore.clone())?) + Ok(manager.commit(proposal.nodestore)?) } None => Err(api::Error::CannotCommitClonedProposal), } @@ -438,7 +438,7 @@ impl Proposal<'_> { match Arc::into_inner(self) { Some(proposal) => { let mut manager = proposal.db.manager.write().expect("poisoned lock"); - Ok(manager.commit(proposal.nodestore.clone())?) + Ok(manager.commit(proposal.nodestore)?) } None => Err(api::Error::CannotCommitClonedProposal), } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2a64241d3cba..9555e7fa5592 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -22,6 +22,7 @@ bytemuck = "1.7.0" bytemuck_derive = "1.7.0" bitfield = "0.18.1" fastrace = { version = "0.7.4" } +io-uring = { version = "0.7.4", optional = true } triomphe = "0.1.14" [dev-dependencies] @@ -34,6 +35,7 @@ tempfile = "3.12.0" [features] logger = ["log"] branch_factor_256 = [] +io-uring = ["dep:io-uring"] [[bench]] name = "serializer" diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 69fcfc932cdc..9577aec0fbf7 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] -#![forbid(unsafe_code)] +#![deny(unsafe_code)] //! # storage implements the storage of a [Node] on top of a LinearStore //! diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index d53e272d9075..c3bc2ec39907 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -20,16 +20,48 @@ use crate::{CacheReadStrategy, LinearAddress, SharedNode}; use super::{ReadableStorage, WritableStorage}; -#[derive(Debug)] /// A [ReadableStorage] backed by a file pub struct FileBacked { fd: File, cache: Mutex>, free_list_cache: Mutex>>, cache_read_strategy: CacheReadStrategy, + #[cfg(feature = "io-uring")] + pub(crate) ring: Mutex, +} + +// Manual implementation since ring doesn't implement Debug :( +impl std::fmt::Debug for FileBacked { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FileBacked") + .field("fd", &self.fd) + .field("cache", &self.cache) + .field("free_list_cache", &self.free_list_cache) + .finish() + } } impl FileBacked { + /// Make a write operation from a raw data buffer for this file + #[cfg(feature = "io-uring")] + pub(crate) fn make_op(&self, data: &[u8]) -> io_uring::opcode::Write { + use std::os::fd::AsRawFd as _; + + use io_uring::{opcode::Write, types}; + + Write::new( + types::Fd(self.fd.as_raw_fd()), + data.as_ptr(), + data.len() as _, + ) + } + + #[cfg(feature = "io-uring")] + // The size of the kernel ring buffer. This buffer will control how many writes we do with + // a single system call. + // TODO: make this configurable + pub(crate) const RINGSIZE: u32 = 32; + /// Create or open a file at a given path pub fn new( path: PathBuf, @@ -45,11 +77,29 @@ impl FileBacked { .truncate(truncate) .open(path)?; + #[cfg(feature = "io-uring")] + let ring = { + // The kernel will stop the worker thread in this many ms if there is no work to do + const IDLETIME_MS: u32 = 1000; + + io_uring::IoUring::builder() + // we promise not to fork and we are the only issuer of writes to this ring + .dontfork() + .setup_single_issuer() + // completion queue should be larger than the request queue, we allocate double + .setup_cqsize(FileBacked::RINGSIZE * 2) + // start a kernel thread to do the IO + .setup_sqpoll(IDLETIME_MS) + .build(FileBacked::RINGSIZE)? + }; + Ok(Self { fd, cache: Mutex::new(LruCache::new(node_cache_size)), free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)), cache_read_strategy, + #[cfg(feature = "io-uring")] + ring: ring.into(), }) } } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 4f9bf9715ab8..839af1bdd7ff 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -937,7 +937,7 @@ impl NodeStore { } } -impl NodeStore, S> { +impl NodeStore, FileBacked> { /// Persist the freelist from this proposal to storage. #[fastrace::trace(short_name = true)] pub fn flush_freelist(&self) -> Result<(), Error> { @@ -950,6 +950,7 @@ impl NodeStore, S> { /// Persist all the nodes of a proposal to storage. #[fastrace::trace(short_name = true)] + #[cfg(not(feature = "io-uring"))] pub fn flush_nodes(&self) -> Result<(), Error> { for (addr, (area_size_index, node)) in self.kind.new.iter() { let mut stored_area_bytes = Vec::new(); @@ -963,6 +964,83 @@ impl NodeStore, S> { Ok(()) } + + /// Persist all the nodes of a proposal to storage. + #[fastrace::trace(short_name = true)] + #[cfg(feature = "io-uring")] + pub fn flush_nodes(&self) -> Result<(), Error> { + const RINGSIZE: usize = FileBacked::RINGSIZE as usize; + + let mut ring = self.storage.ring.lock().expect("poisoned lock"); + let mut saved_pinned_buffers = vec![(false, std::pin::Pin::new(Box::default())); RINGSIZE]; + for (&addr, &(area_size_index, ref node)) in self.kind.new.iter() { + let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger + node.as_bytes(area_size_index, &mut serialized); + let mut serialized = serialized.into_boxed_slice(); + loop { + // Find the first available write buffer, enumerate to get the position for marking it completed + if let Some((pos, (busy, found))) = saved_pinned_buffers + .iter_mut() + .enumerate() + .find(|(_, (busy, _))| !*busy) + { + *found = std::pin::Pin::new(std::mem::take(&mut serialized)); + let submission_queue_entry = self + .storage + .make_op(found) + .offset(addr.get()) + .build() + .user_data(pos as u64); + + *busy = true; + // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope + // until the operation has been completed. This is ensured by marking the slot busy, + // and not marking it !busy until the kernel has said it's done below. + #[allow(unsafe_code)] + while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { + ring.submitter().squeue_wait()?; + trace!("submission queue is full"); + counter!("ring.full").increment(1); + } + break; + } + // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation + // to complete, then handle the completion queue + counter!("ring.full").increment(1); + ring.submit_and_wait(1)?; + let completion_queue = ring.completion(); + trace!("competion queue length: {}", completion_queue.len()); + for entry in completion_queue { + let item = entry.user_data() as usize; + saved_pinned_buffers + .get_mut(item) + .expect("should be an index into the array") + .0 = false; + } + } + } + let pending = saved_pinned_buffers + .iter() + .filter(|(busy, _)| *busy) + .count(); + ring.submit_and_wait(pending)?; + + for entry in ring.completion() { + let item = entry.user_data() as usize; + saved_pinned_buffers + .get_mut(item) + .expect("should be an index into the array") + .0 = false; + } + + debug_assert_eq!(saved_pinned_buffers.iter().find(|(busy, _)| *busy), None); + + self.storage + .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + debug_assert!(ring.completion().is_empty()); + + Ok(()) + } } impl NodeStore, FileBacked> { From 13602da8f0706503e79de3e7674c9ad4d9d9ae4d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Feb 2025 10:38:29 -0800 Subject: [PATCH 0663/1053] Include a mode for cache metrics. (#796) --- storage/src/linear/filebacked.rs | 4 ++-- storage/src/linear/mod.rs | 2 +- storage/src/nodestore.rs | 29 +++++++++++++++++++---------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index c3bc2ec39907..09224a9c81fc 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -113,10 +113,10 @@ impl ReadableStorage for FileBacked { Ok(self.fd.metadata()?.len()) } - fn read_cached_node(&self, addr: LinearAddress) -> Option { + fn read_cached_node(&self, addr: LinearAddress, mode: &'static str) -> Option { let mut guard = self.cache.lock().expect("poisoned lock"); let cached = guard.get(&addr).cloned(); - counter!("firewood.cache.node", "type" => if cached.is_some() { "hit" } else { "miss" }) + counter!("firewood.cache.node", "mode" => mode, "type" => if cached.is_some() { "hit" } else { "miss" }) .increment(1); cached } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index a61f4a61e7e2..fb3ac623c511 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -42,7 +42,7 @@ pub trait ReadableStorage: Debug + Sync + Send { fn size(&self) -> Result; /// Read a node from the cache (if any) - fn read_cached_node(&self, _addr: LinearAddress) -> Option { + fn read_cached_node(&self, _addr: LinearAddress, _mode: &'static str) -> Option { None } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 839af1bdd7ff..56e0f65ffcc8 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -210,8 +210,12 @@ impl NodeStore { /// Read a [Node] from the provided [LinearAddress]. /// `addr` is the address of a StoredArea in the ReadableStorage. - pub fn read_node_from_disk(&self, addr: LinearAddress) -> Result { - if let Some(node) = self.storage.read_cached_node(addr) { + pub fn read_node_from_disk( + &self, + addr: LinearAddress, + mode: &'static str, + ) -> Result { + if let Some(node) = self.storage.read_cached_node(addr, mode) { return Ok(node); } @@ -272,7 +276,7 @@ impl NodeStore { }; if let Some(root_address) = nodestore.header.root_address { - let node = nodestore.read_node_from_disk(root_address); + let node = nodestore.read_node_from_disk(root_address, "open"); let root_hash = node.map(|n| hash_node(&n, &Path(Default::default())))?; nodestore.kind.root_hash = Some(root_hash); } @@ -1103,13 +1107,22 @@ impl From> } } -impl NodeReader for NodeStore { +impl NodeReader for NodeStore { fn read_node(&self, addr: LinearAddress) -> Result { if let Some(node) = self.kind.read_in_memory_node(addr) { return Ok(node); } - self.read_node_from_disk(addr) + self.read_node_from_disk(addr, "write") + } +} + +impl NodeReader for NodeStore { + fn read_node(&self, addr: LinearAddress) -> Result { + if let Some(node) = self.kind.read_in_memory_node(addr) { + return Ok(node); + } + self.read_node_from_disk(addr, "read") } } @@ -1119,11 +1132,7 @@ impl RootReader for NodeStore { } } -trait Hashed {} -impl Hashed for Committed {} -impl Hashed for Arc {} - -impl RootReader for NodeStore { +impl RootReader for NodeStore { fn root_node(&self) -> Option { // TODO: If the read_node fails, we just say there is no root; this is incorrect self.header From ebdea71a5fc86506128818f4aa68a503cb98e42d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Feb 2025 11:25:33 -0800 Subject: [PATCH 0664/1053] Add metrics for prefix deletes (#794) --- firewood/src/merkle.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index bc431c3394d6..33616ddb3391 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -591,16 +591,18 @@ impl Merkle> { let root = self.nodestore.mut_root(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. - counter!("firewood.remove", "result" => "nonexistent").increment(1); + counter!("firewood.remove", "prefix" => "false", "result" => "nonexistent") + .increment(1); return Ok(None); }; let (root_node, removed_value) = self.remove_helper(root_node, &key)?; *self.nodestore.mut_root() = root_node; if removed_value.is_some() { - counter!("firewood.remove", "result" => "success").increment(1); + counter!("firewood.remove", "prefix" => "false", "result" => "success").increment(1); } else { - counter!("firewood.remove", "result" => "nonexistent").increment(1); + counter!("firewood.remove", "prefix" => "false", "result" => "nonexistent") + .increment(1); } Ok(removed_value) } @@ -816,11 +818,14 @@ impl Merkle> { let root = self.nodestore.mut_root(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. + counter!("firewood.remove", "prefix" => "true", "result" => "nonexistent").increment(1); return Ok(0); }; let mut deleted = 0; let root_node = self.remove_prefix_helper(root_node, &prefix, &mut deleted)?; + counter!("firewood.remove", "prefix" => "true", "result" => "success") + .increment(deleted as u64); *self.nodestore.mut_root() = root_node; Ok(deleted) } From ad24ac0dbc525dabe465f8e38c52665fcc49b64c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Feb 2025 14:49:20 -0800 Subject: [PATCH 0665/1053] Dashboard updates (#797) --- benchmark/Grafana-dashboard.json | 471 ++++++++++++++++++++++--------- 1 file changed, 338 insertions(+), 133 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index 6dd6197c8c7f..b9d579af5e44 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -11,17 +11,11 @@ ], "__elements": {}, "__requires": [ - { - "type": "panel", - "id": "gauge", - "name": "Gauge", - "version": "" - }, { "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "11.2.0" + "version": "11.5.1" }, { "type": "datasource", @@ -58,6 +52,19 @@ "id": null, "links": [], "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 10, + "panels": [], + "title": "Cache", + "type": "row" + }, { "datasource": { "type": "prometheus", @@ -77,7 +84,7 @@ "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 25, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -95,7 +102,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" @@ -119,29 +126,30 @@ "overrides": [] }, "gridPos": { - "h": 12, - "w": 14, + "h": 8, + "w": 12, "x": 0, - "y": 0 + "y": 1 }, - "id": 1, + "id": 5, "options": { "legend": { "calcs": [ "min", "max", - "mean", - "last" + "mean" ], "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.5.1", "targets": [ { "datasource": { @@ -150,34 +158,17 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "irate(firewood_insert[$__rate_interval])", + "expr": "rate(firewood_cache_node{type=\"miss\", mode!=\"open\"}[$__rate_interval])", "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(firewood_remove[$__rate_interval])", - "fullMetaSearch": false, - "hide": false, "includeNullMetadata": false, "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{mode}}", "range": true, - "refId": "B", + "refId": "A", "useBackend": false } ], - "title": "Insertion rate", + "title": "Node cache misses (read+deserialize)", "type": "timeseries" }, { @@ -188,11 +179,43 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "max": 100, - "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -210,28 +233,30 @@ "overrides": [] }, "gridPos": { - "h": 4, - "w": 4, - "x": 0, - "y": 12 + "h": 8, + "w": 12, + "x": 12, + "y": 1 }, - "id": 2, + "id": 4, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "min", + "mean", + "max" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sizing": "auto" + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.0", + "pluginVersion": "11.5.1", "targets": [ { "datasource": { @@ -240,18 +265,44 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "sum(firewood_insert)", + "expr": "100 * sum(increase(firewood_cache_node{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_node[$__rate_interval])) by (name)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "node", "range": true, "refId": "A", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "100 * sum(increase(firewood_cache_freelist{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_freelist[$__rate_interval])) by (name)", + "hide": false, + "instant": false, + "legendFormat": "freelist", + "range": true, + "refId": "B" } ], - "title": "Percent complete", - "type": "gauge" + "title": "Cache hit rate", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 9, + "panels": [], + "title": "Throughput", + "type": "row" }, { "datasource": { @@ -261,14 +312,42 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, "custom": { - "neutral": 0 + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], - "max": 100, - "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -286,62 +365,41 @@ "overrides": [] }, "gridPos": { - "h": 4, - "w": 10, - "x": 4, - "y": 12 + "h": 12, + "w": 11, + "x": 0, + "y": 10 }, - "id": 4, + "id": 8, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sizing": "auto", - "text": {} + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "11.2.0", + "pluginVersion": "11.5.1", "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, "editorMode": "code", - "expr": "100 * sum(increase(firewood_cache_node{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_node[$__rate_interval])) by (name)", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "node", + "expr": "firewood_proposals", + "legendFormat": "__auto", "range": true, "refId": "A", - "useBackend": false - }, - { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "100 * sum(increase(firewood_cache_freelist{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_freelist[$__rate_interval])) by (name)", - "hide": false, - "instant": false, - "legendFormat": "freelist", - "range": true, - "refId": "B" + } } ], - "title": "Cache hit rate", - "type": "gauge" + "title": "Proposals Submitted (Blocks Processed)", + "type": "timeseries" }, { "datasource": { @@ -362,7 +420,7 @@ "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 25, "gradientMode": "none", "hideFrom": { "legend": false, @@ -380,7 +438,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "none" + "mode": "normal" }, "thresholdsStyle": { "mode": "off" @@ -404,49 +462,88 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 14, - "x": 0, - "y": 16 + "h": 12, + "w": 13, + "x": 11, + "y": 10 }, - "id": 5, + "id": 1, "options": { "legend": { "calcs": [ "min", "max", - "mean" + "mean", + "last" ], "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.5.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(firewood_cache_node{type=\"miss\"}[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, + "editorMode": "code", + "expr": "irate(firewood_remove[$__rate_interval])", + "hide": false, "instant": false, - "legendFormat": "misses per second", + "legendFormat": "Remove {prefix=\"{{prefix}}\"}", "range": true, - "refId": "A", - "useBackend": false + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(irate(firewood_insert{merkle!=\"update\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "Insert", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "irate(firewood_insert{merkle=\"update\"}[$__rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "Update", + "range": true, + "refId": "A" } ], - "title": "Node cache misses (read+deserialize)", + "title": "Operation Rate", "type": "timeseries" }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 11, + "panels": [], + "title": "Internals", + "type": "row" + }, { "datasource": { "type": "prometheus", @@ -495,7 +592,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -508,9 +606,9 @@ }, "gridPos": { "h": 8, - "w": 14, + "w": 12, "x": 0, - "y": 24 + "y": 23 }, "id": 6, "options": { @@ -521,10 +619,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.5.1", "targets": [ { "datasource": { @@ -594,22 +694,24 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "bytes" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 14, - "x": 0, - "y": 32 + "w": 11, + "x": 12, + "y": 23 }, "id": 7, "options": { @@ -620,10 +722,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "11.5.1", "targets": [ { "datasource": { @@ -638,24 +742,125 @@ "refId": "A" } ], - "title": "Dirty bytes", + "title": "Dirty bytes (OS pending write to disk)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "min", + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "editorMode": "code", + "expr": "irate(firewood_insert[$__rate_interval])", + "legendFormat": "merkle=\"{{merkle}}\"", + "range": true, + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + } + } + ], + "title": "Insert Merkle Ops by Type", "type": "timeseries" } ], "refresh": "10s", - "schemaVersion": 39, + "schemaVersion": 40, "tags": [], "templating": { "list": [] }, "time": { - "from": "now-15m", + "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "browser", "title": "Firewood Dashboard", "uid": "adxfhfmwx5ypsc", - "version": 3, + "version": 12, "weekStart": "" } \ No newline at end of file From 2b50c16ce62b0b54e498fc1c4fbe2b7a240729f1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Sun, 23 Feb 2025 11:07:21 -0800 Subject: [PATCH 0666/1053] Adds metrics each ffi batch reports "firewood.ffi.batch_ms" for elapsed millis each ffi batch reports "firewood.ffi.batch" for each run (allows the mean time per call to be batch_ms/batch) each predictive_reader reports "firewood.io.read_ms" for elapsed millis each predictive_reader reports "firewood.io.read" for each run These should complete the following requested statistics: What percentage of time is spent performing blocking DB reads during EVM execution (now in firewood.io.read*) What percentage of time is spent during merkalization after EVM execution -> (now in firewood.ffi.batch*) These other statistics were already available: Adds the following metrics: (#800) What percentage of time is spent during EVM execution (the EVM records these) What is our CPU utilization during the benchmark (Are we CPU bound?) (the system level node recorder records these) What is our RAM utilization during the benchmark (Are we using memory more efficiently?) (the system level node recorder records these) What is our disk utilization during the benchmark (Are we fully utilizing our available IOPS) (the node recorder records these too) --- ffi/Cargo.toml | 1 + ffi/src/lib.rs | 12 +++++++++--- ffi/src/{metrics.rs => metrics_setup.rs} | 0 storage/Cargo.toml | 1 + storage/src/linear/filebacked.rs | 10 ++++++++++ 5 files changed, 21 insertions(+), 3 deletions(-) rename ffi/src/{metrics.rs => metrics_setup.rs} (100%) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index b9348cbc6398..d2154f7e0858 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -13,6 +13,7 @@ metrics = "0.24.1" metrics-util = "0.19.0" chrono = "0.4.39" oxhttp = "0.3.0" +coarsetime = "0.1.35" [build-dependencies] cbindgen = "0.28.0" diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index c3b332dbcc99..ff1d732e824b 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -9,7 +9,9 @@ use std::path::Path; use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -mod metrics; +mod metrics_setup; + +use metrics::counter; #[derive(Debug)] #[repr(C)] @@ -79,6 +81,7 @@ pub struct KeyValue { /// #[no_mangle] pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Value { + let start = coarsetime::Instant::now(); let db = unsafe { db.as_ref() }.expect("db should be non-null"); let mut batch = Vec::with_capacity(nkeys); for i in 0..nkeys { @@ -96,7 +99,10 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key } let proposal = db.propose_sync(batch).expect("proposal should succeed"); proposal.commit_sync().expect("commit should succeed"); - hash(db) + let hash = hash(db); + counter!("firewood.ffi.batch_ms").increment(start.elapsed().as_millis()); + counter!("firewood.ffi.batch").increment(1); + hash } /// Get the root hash of the latest version of the database @@ -239,7 +245,7 @@ unsafe fn common_create( let path = unsafe { CStr::from_ptr(path) }; let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); if metrics_port > 0 { - metrics::setup_metrics(metrics_port); + metrics_setup::setup_metrics(metrics_port); } Box::into_raw(Box::new( Db::new_sync(path, cfg).expect("db initialization should succeed"), diff --git a/ffi/src/metrics.rs b/ffi/src/metrics_setup.rs similarity index 100% rename from ffi/src/metrics.rs rename to ffi/src/metrics_setup.rs diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 9555e7fa5592..bd46304cdd84 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -24,6 +24,7 @@ bitfield = "0.18.1" fastrace = { version = "0.7.4" } io-uring = { version = "0.7.4", optional = true } triomphe = "0.1.14" +coarsetime = "0.1.35" [dev-dependencies] rand = "0.9.0" diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 09224a9c81fc..be09b34403a1 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -189,6 +189,7 @@ struct PredictiveReader<'a> { offset: u64, len: usize, pos: usize, + started: coarsetime::Instant, } impl<'a> PredictiveReader<'a> { @@ -201,10 +202,19 @@ impl<'a> PredictiveReader<'a> { offset: start, len: 0, pos: 0, + started: coarsetime::Instant::now(), } } } +impl Drop for PredictiveReader<'_> { + fn drop(&mut self) { + let elapsed = self.started.elapsed(); + counter!("firewood.io.read_ms").increment(elapsed.as_millis()); + counter!("firewood.io.read").increment(1); + } +} + impl Read for PredictiveReader<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { if self.len == self.pos { From 77f3b50ed5f53d775633e0075337bbcf4edc2531 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 24 Feb 2025 09:26:01 -0800 Subject: [PATCH 0667/1053] Rkuris/write stats (#802) --- ffi/src/lib.rs | 6 +++++- storage/src/nodestore.rs | 32 +++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ff1d732e824b..cd52ed9ecc32 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -98,9 +98,13 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key }); } let proposal = db.propose_sync(batch).expect("proposal should succeed"); + let propose_time = start.elapsed().as_millis(); + counter!("firewood.ffi.propose_ms").increment(propose_time); proposal.commit_sync().expect("commit should succeed"); let hash = hash(db); - counter!("firewood.ffi.batch_ms").increment(start.elapsed().as_millis()); + let propose_plus_commit_time = start.elapsed().as_millis(); + counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); + counter!("firewood.ffi.commit_ms").increment(propose_plus_commit_time - propose_time); counter!("firewood.ffi.batch").increment(1); hash } diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 56e0f65ffcc8..e0809057fd5a 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,17 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::logger::trace; -use arc_swap::access::DynAccess; -use arc_swap::ArcSwap; -use bincode::{DefaultOptions, Options as _}; -use bytemuck_derive::{AnyBitPattern, NoUninit}; -use fastrace::local::LocalSpan; -use metrics::counter; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt::Debug; - /// The [NodeStore] handles the serialization of nodes and /// free space management of nodes in the page store. It lays out the format /// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] @@ -52,6 +41,17 @@ use std::fmt::Debug; /// I --> |commit|N("New commit NodeStore<Committed, S>") /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` +use crate::logger::trace; +use arc_swap::access::DynAccess; +use arc_swap::ArcSwap; +use bincode::{DefaultOptions, Options as _}; +use bytemuck_derive::{AnyBitPattern, NoUninit}; +use coarsetime::Instant; +use fastrace::local::LocalSpan; +use metrics::counter; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::Debug; use std::io::{Error, ErrorKind, Write}; use std::iter::once; use std::mem::{offset_of, take}; @@ -956,6 +956,8 @@ impl NodeStore, FileBacked> { #[fastrace::trace(short_name = true)] #[cfg(not(feature = "io-uring"))] pub fn flush_nodes(&self) -> Result<(), Error> { + let flush_start = Instant::now(); + for (addr, (area_size_index, node)) in self.kind.new.iter() { let mut stored_area_bytes = Vec::new(); node.as_bytes(*area_size_index, &mut stored_area_bytes); @@ -966,6 +968,9 @@ impl NodeStore, FileBacked> { self.storage .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + let flush_time = flush_start.elapsed().as_millis(); + counter!("firewood.flush_nodes").increment(flush_time); + Ok(()) } @@ -975,6 +980,8 @@ impl NodeStore, FileBacked> { pub fn flush_nodes(&self) -> Result<(), Error> { const RINGSIZE: usize = FileBacked::RINGSIZE as usize; + let flush_start = Instant::now(); + let mut ring = self.storage.ring.lock().expect("poisoned lock"); let mut saved_pinned_buffers = vec![(false, std::pin::Pin::new(Box::default())); RINGSIZE]; for (&addr, &(area_size_index, ref node)) in self.kind.new.iter() { @@ -1043,6 +1050,9 @@ impl NodeStore, FileBacked> { .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; debug_assert!(ring.completion().is_empty()); + let flush_time = flush_start.elapsed().as_millis(); + counter!("firewood.flush_nodes").increment(flush_time); + Ok(()) } } From ecbb627af607303ff84f03e5e717cedc0635d643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:15:56 +0000 Subject: [PATCH 0668/1053] build(deps): update bitfield requirement from 0.18.1 to 0.19.0 (#801) --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index bd46304cdd84..0c568c1c477c 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -20,7 +20,7 @@ metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" -bitfield = "0.18.1" +bitfield = "0.19.0" fastrace = { version = "0.7.4" } io-uring = { version = "0.7.4", optional = true } triomphe = "0.1.14" From 59e22db4280e8cbb34c0cc275c86cc080124f494 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 6 Mar 2025 15:52:06 -0600 Subject: [PATCH 0669/1053] Remove MerkleError and DbError (#811) --- firewood/src/db.rs | 15 ++- firewood/src/merkle.rs | 239 ++++++++++++++++++++--------------------- firewood/src/proof.rs | 3 +- firewood/src/stream.rs | 8 +- firewood/src/v2/api.rs | 9 +- firewood/src/v2/db.rs | 14 --- firewood/src/v2/mod.rs | 3 - 7 files changed, 131 insertions(+), 160 deletions(-) delete mode 100644 firewood/src/v2/db.rs diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e5855a5d8ca8..d89be0d375f3 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::{Merkle, MerkleError}; +use crate::merkle::Merkle; use crate::proof::{Proof, ProofNode}; use crate::range_proof::RangeProof; use crate::stream::MerkleKeyValueStream; @@ -23,8 +23,6 @@ use typed_builder::TypedBuilder; #[non_exhaustive] /// Represents the different types of errors that can occur in the database. pub enum DbError { - /// Merkle error occurred. - Merkle(MerkleError), /// I/O error occurred. IO(std::io::Error), } @@ -32,7 +30,6 @@ pub enum DbError { impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DbError::Merkle(e) => write!(f, "merkle error: {e:?}"), DbError::IO(e) => write!(f, "I/O error: {e:?}"), } } @@ -69,7 +66,7 @@ pub trait DbViewSync { impl DbViewSync for HistoricalRev { fn val_sync(&self, key: K) -> Result>, DbError> { let merkle = Merkle::from(self); - let value = merkle.get_value(key.as_ref()).map_err(DbError::Merkle)?; + let value = merkle.get_value(key.as_ref())?; Ok(value) } } @@ -312,12 +309,12 @@ impl Db { } /// Dump the Trie of the latest revision. - pub async fn dump(&self, w: &mut dyn Write) -> Result<(), DbError> { + pub async fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { self.dump_sync(w) } /// Dump the Trie of the latest revision, synchronously. - pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), DbError> { + pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self .manager .read() @@ -325,8 +322,8 @@ impl Db { .current_revision(); let merkle = Merkle::from(latest_rev_nodestore); // TODO: This should be a stream - let output = merkle.dump().map_err(DbError::Merkle)?; - write!(w, "{}", output).map_err(DbError::IO) + let output = merkle.dump()?; + write!(w, "{}", output) } /// Get a copy of the database metrics diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 33616ddb3391..d6d41533d4d1 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -8,9 +8,9 @@ use crate::v2::api; use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; -use std::fmt::Debug; +use std::fmt::{Debug, Write}; use std::future::ready; -use std::io::Write; +use std::io::Error; use std::iter::once; use std::num::NonZeroUsize; use std::sync::Arc; @@ -20,8 +20,6 @@ use storage::{ TrieReader, ValueDigest, }; -use thiserror::Error; - /// Keys are boxed u8 slices pub type Key = Box<[u8]>; @@ -29,18 +27,6 @@ pub type Key = Box<[u8]>; /// TODO: change to Box<[u8]> pub type Value = Vec; -#[derive(Debug, Error)] -/// Errors that can occur when interacting with the Merkle trie -pub enum MerkleError { - /// Can't generate proof for empty node - #[error("can't generate proof for empty node")] - Empty, - - /// IO error - #[error("IO error: {0:?}")] - IO(#[from] std::io::Error), -} - // convert a set of nibbles into a printable string // panics if there is a non-nibble byte in the set #[cfg(not(feature = "branch_factor_256"))] @@ -67,20 +53,21 @@ macro_rules! write_attributes { $writer, " pp={}", nibbles_formatter($node.partial_path.0.clone()) - )?; + ) + .map_err(|e| Error::other(e))?; } #[allow(clippy::unnecessary_to_owned)] if !$value.is_empty() { match std::str::from_utf8($value) { Ok(string) if string.chars().all(char::is_alphanumeric) => { - write!($writer, " val={}", string)? + write!($writer, " val={}", string).map_err(|e| Error::other(e))? } _ => { let hex = hex::encode($value); if hex.len() > 6 { - write!($writer, " val={:.6}...", hex)?; + write!($writer, " val={:.6}...", hex).map_err(|e| Error::other(e))?; } else { - write!($writer, " val={}", hex)?; + write!($writer, " val={}", hex).map_err(|e| Error::other(e))?; } } } @@ -93,7 +80,7 @@ fn get_helper( nodestore: &T, node: &Node, key: &[u8], -) -> Result, MerkleError> { +) -> Result, Error> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -161,15 +148,15 @@ impl Merkle { &self.nodestore } - fn read_node(&self, addr: LinearAddress) -> Result { - self.nodestore.read_node(addr).map_err(Into::into) + fn read_node(&self, addr: LinearAddress) -> Result { + self.nodestore.read_node(addr) } /// Returns a proof that the given key has a certain value, /// or that the key isn't in the trie. - pub fn prove(&self, key: &[u8]) -> Result, MerkleError> { + pub fn prove(&self, key: &[u8]) -> Result, ProofError> { let Some(root) = self.root() else { - return Err(MerkleError::Empty); + return Err(ProofError::Empty); }; // Get the path to the key @@ -218,10 +205,7 @@ impl Merkle { todo!() } - pub(crate) fn path_iter<'a>( - &self, - key: &'a [u8], - ) -> Result, MerkleError> { + pub(crate) fn path_iter<'a>(&self, key: &'a [u8]) -> Result, Error> { PathIterator::new(&self.nodestore, key) } @@ -328,14 +312,14 @@ impl Merkle { }) } - pub(crate) fn get_value(&self, key: &[u8]) -> Result>, MerkleError> { + pub(crate) fn get_value(&self, key: &[u8]) -> Result>, Error> { let Some(node) = self.get_node(key)? else { return Ok(None); }; Ok(node.value().map(|v| v.to_vec().into_boxed_slice())) } - pub(crate) fn get_node(&self, key: &[u8]) -> Result, MerkleError> { + pub(crate) fn get_node(&self, key: &[u8]) -> Result, Error> { let Some(root) = self.root() else { return Ok(None); }; @@ -352,16 +336,16 @@ impl Merkle { hash: Option<&TrieHash>, seen: &mut HashSet, writer: &mut dyn Write, - ) -> Result<(), MerkleError> { - write!(writer, " {addr}[label=\"addr:{addr:?}")?; + ) -> Result<(), Error> { + write!(writer, " {addr}[label=\"addr:{addr:?}").map_err(Error::other)?; if let Some(hash) = hash { - write!(writer, " hash:{hash:.6?}...")?; + write!(writer, " hash:{hash:.6?}...").map_err(Error::other)?; } match &*self.read_node(addr)? { Node::Branch(b) => { write_attributes!(writer, b, &b.value.clone().unwrap_or(Box::from([]))); - writeln!(writer, "\"]")?; + writeln!(writer, "\"]").map_err(Error::other)?; for (childidx, child) in b.children.iter().enumerate() { let (child_addr, child_hash) = match child { None => continue, @@ -376,32 +360,34 @@ impl Merkle { writeln!( writer, " {addr} -> {child_addr}[label=\"{childidx} (dup)\" color=red]" - )?; + ) + .map_err(Error::other)?; } else { - writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]")?; + writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]") + .map_err(Error::other)?; self.dump_node(child_addr, child_hash, seen, writer)?; } } } Node::Leaf(l) => { write_attributes!(writer, l, &l.value); - writeln!(writer, "\" shape=rect]")?; + writeln!(writer, "\" shape=rect]").map_err(Error::other)?; } }; Ok(()) } - pub(crate) fn dump(&self) -> Result { - let mut result = vec![]; - writeln!(result, "digraph Merkle {{\n rankdir=LR;")?; + pub(crate) fn dump(&self) -> Result { + let mut result = String::new(); + writeln!(result, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { - writeln!(result, " root -> {root_addr}")?; + writeln!(result, " root -> {root_addr}").map_err(Error::other)?; let mut seen = HashSet::new(); self.dump_node(root_addr, Some(&root_hash), &mut seen, &mut result)?; } - write!(result, "}}")?; + write!(result, "}}").map_err(Error::other)?; - Ok(String::from_utf8_lossy(&result).to_string()) + Ok(result) } } @@ -424,7 +410,7 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. - pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), MerkleError> { + pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), Error> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -453,7 +439,7 @@ impl Merkle> { mut node: Node, key: &[u8], value: Box<[u8]>, - ) -> Result { + ) -> Result { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -585,7 +571,7 @@ impl Merkle> { /// Returns the value that was removed, if any. /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. - pub fn remove(&mut self, key: &[u8]) -> Result>, MerkleError> { + pub fn remove(&mut self, key: &[u8]) -> Result>, Error> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -615,7 +601,7 @@ impl Merkle> { &mut self, mut node: Node, key: &[u8], - ) -> Result<(Option, Option>), MerkleError> { + ) -> Result<(Option, Option>), Error> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -812,7 +798,7 @@ impl Merkle> { /// Removes any key-value pairs with keys that have the given `prefix`. /// Returns the number of key-value pairs removed. - pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { + pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); let root = self.nodestore.mut_root(); @@ -835,7 +821,7 @@ impl Merkle> { mut node: Node, key: &[u8], deleted: &mut usize, - ) -> Result, MerkleError> { + ) -> Result, Error> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key`, in which case we need to delete this node and all its children. // 2. The key is above the node (i.e. its ancestor), so the parent needs to be restructured (TODO). @@ -965,7 +951,7 @@ impl Merkle> { &mut self, branch: &mut BranchNode, deleted: &mut usize, - ) -> Result<(), std::io::Error> { + ) -> Result<(), Error> { if branch.value.is_some() { // a KV pair was in the branch itself *deleted += 1; @@ -1295,7 +1281,7 @@ mod tests { fn get_empty_proof() { let merkle = create_in_memory_merkle().hash(); let proof = merkle.prove(b"any-key"); - assert!(matches!(proof.unwrap_err(), MerkleError::Empty)); + assert!(matches!(proof.unwrap_err(), ProofError::Empty)); } #[test] @@ -1695,7 +1681,7 @@ mod tests { fn merkle_build_test, V: AsRef<[u8]>>( items: Vec<(K, V)>, - ) -> Result>, MerkleError> { + ) -> Result>, Error> { let nodestore = NodeStore::new_empty_proposal(MemStore::new(vec![]).into()); let mut merkle = Merkle::from(nodestore); for (k, v) in items.iter() { @@ -1706,7 +1692,7 @@ mod tests { } #[test] - fn test_root_hash_simple_insertions() -> Result<(), MerkleError> { + fn test_root_hash_simple_insertions() -> Result<(), Error> { let items = vec![ ("do", "verb"), ("doe", "reindeer"), @@ -1750,7 +1736,7 @@ mod tests { } #[test] - fn test_root_hash_fuzz_insertions() -> Result<(), MerkleError> { + fn test_root_hash_fuzz_insertions() -> Result<(), Error> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -1815,79 +1801,86 @@ mod tests { } } - // #[test] - // #[allow(clippy::unwrap_used)] - // fn test_root_hash_reversed_deletions() -> Result<(), MerkleError> { - // use rand::rngs::StdRng; - // use rand::{Rng, SeedableRng}; - // let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - // let max_len0 = 8; - // let max_len1 = 4; - // let keygen = || { - // let (len0, len1): (usize, usize) = { - // let mut rng = rng.borrow_mut(); - // ( - // rng.random_range(1..max_len0 + 1), - // rng.random_range(1..max_len1 + 1), - // ) - // }; - // let key: Vec = (0..len0) - // .map(|_| rng.borrow_mut().random_range(0..2)) - // .chain((0..len1).map(|_| rng.borrow_mut().random())) - // .collect(); - // key - // }; + #[test] + #[allow(clippy::unwrap_used)] + fn test_root_hash_reversed_deletions() -> Result<(), Error> { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + ( + rng.random_range(1..max_len0 + 1), + rng.random_range(1..max_len1 + 1), + ) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().random_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().random())) + .collect(); + key + }; - // for _ in 0..10 { - // let mut items: Vec<_> = (0..10) - // .map(|_| keygen()) - // .map(|key| { - // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); - // (key, val) - // }) - // .collect(); + for _ in 0..10 { + let mut items: Vec<_> = (0..10) + .map(|_| keygen()) + .map(|key| { + let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); + (key, val) + }) + .collect(); - // items.sort(); + items.sort(); - // let mut merkle = - // Merkle::new(HashedNodeStore::initialize(MemStore::new(vec![])).unwrap()); + let mut merkle = merkle_build_test(items.clone())?; - // let mut hashes = Vec::new(); + let mut hashes = Vec::new(); - // for (k, v) in items.iter() { - // hashes.push((merkle.root_hash()?, merkle.dump()?)); - // merkle.insert(k, v.clone())?; - // } + for (k, v) in items.iter() { + let root_hash = merkle + .nodestore + .root_address_and_hash()? + .map(|(_, hash)| hash); + hashes.push((root_hash, merkle.dump()?)); + merkle.insert(k, v.clone())?; + } - // let mut new_hashes = Vec::new(); + let mut new_hashes = Vec::new(); - // for (k, _) in items.iter().rev() { - // let before = merkle.dump()?; - // merkle.remove(k)?; - // new_hashes.push((merkle.root_hash()?, k, before, merkle.dump()?)); - // } + for (k, _) in items.iter().rev() { + let before = merkle.dump()?; + merkle.remove(k)?; + let root_hash = merkle + .nodestore + .root_address_and_hash()? + .map(|(_, hash)| hash); + new_hashes.push((root_hash, k, before, merkle.dump()?)); + } - // hashes.reverse(); - - // for i in 0..hashes.len() { - // #[allow(clippy::indexing_slicing)] - // let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; - // #[allow(clippy::indexing_slicing)] - // let expected_hash = &hashes[i]; - // let key = key.iter().fold(String::new(), |mut s, b| { - // let _ = write!(s, "{:02x}", b); - // s - // }); - // // assert_eq!(new_hash, expected_hash, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_dump}\n"); - // } - // } + hashes.reverse(); - // Ok(()) - // } + for i in 0..hashes.len() { + #[allow(clippy::indexing_slicing)] + let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; + #[allow(clippy::indexing_slicing)] + let expected_hash = &hashes[i]; + let key = key.iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "{:02x}", b); + s + }); + assert_eq!(new_hash.clone(), expected_hash.0, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{:?}\n", expected_hash.0); + } + } + + Ok(()) + } // #[test] // #[allow(clippy::unwrap_used)] - // fn test_root_hash_random_deletions() -> Result<(), MerkleError> { + // fn test_root_hash_random_deletions() -> Result<(), Error> { // use rand::rngs::StdRng; // use rand::seq::SliceRandom; // use rand::{Rng, SeedableRng}; @@ -1963,7 +1956,7 @@ mod tests { // #[test] // #[allow(clippy::unwrap_used, clippy::indexing_slicing)] - // fn test_proof() -> Result<(), MerkleError> { + // fn test_proof() -> Result<(), Error> { // let set = fixed_and_pseudorandom_data(500); // let mut items = Vec::from_iter(set.iter()); // items.sort(); @@ -1983,7 +1976,7 @@ mod tests { // #[test] // /// Verify the proofs that end with leaf node with the given key. - // fn test_proof_end_with_leaf() -> Result<(), MerkleError> { + // fn test_proof_end_with_leaf() -> Result<(), Error> { // let items = vec![ // ("do", "verb"), // ("doe", "reindeer"), @@ -2006,7 +1999,7 @@ mod tests { // #[test] // /// Verify the proofs that end with branch node with the given key. - // fn test_proof_end_with_branch() -> Result<(), MerkleError> { + // fn test_proof_end_with_branch() -> Result<(), Error> { // let items = vec![ // ("d", "verb"), // ("do", "verb"), @@ -2026,7 +2019,7 @@ mod tests { // } // #[test] - // fn test_bad_proof() -> Result<(), MerkleError> { + // fn test_bad_proof() -> Result<(), Error> { // let set = fixed_and_pseudorandom_data(800); // let mut items = Vec::from_iter(set.iter()); // items.sort(); @@ -2049,7 +2042,7 @@ mod tests { // #[test] // // Tests that missing keys can also be proven. The test explicitly uses a single // // entry trie and checks for missing keys both before and after the single entry. - // fn test_missing_key_proof() -> Result<(), MerkleError> { + // fn test_missing_key_proof() -> Result<(), Error> { // let items = vec![("k", "v")]; // let merkle = merkle_build_test(items)?; // for key in ["a", "j", "l", "z"] { @@ -2065,7 +2058,7 @@ mod tests { // } // #[test] - // fn test_empty_tree_proof() -> Result<(), MerkleError> { + // fn test_empty_tree_proof() -> Result<(), Error> { // let items: Vec<(&str, &str)> = Vec::new(); // let merkle = merkle_build_test(items)?; // let key = "x".as_ref(); @@ -2540,7 +2533,7 @@ mod tests { // #[test] // // Tests the element is not in the range covered by proofs. // #[allow(clippy::indexing_slicing)] - // fn test_same_side_proof() -> Result<(), MerkleError> { + // fn test_same_side_proof() -> Result<(), Error> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); // items.sort(); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 6168d05553bf..224057ba08bd 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,7 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::MerkleError; use sha2::{Digest, Sha256}; use storage::{ BranchNode, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, @@ -57,7 +56,7 @@ pub enum ProofError { /// Error from the merkle package #[error("{0:?}")] - Merkle(#[from] MerkleError), + IO(#[from] std::io::Error), /// Empty range #[error("empty range")] diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 0df5ed62f2ae..1377f2449b6d 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::merkle::{Key, MerkleError, Value}; +use crate::merkle::{Key, Value}; use crate::v2::api; use futures::stream::FusedStream; @@ -390,7 +390,7 @@ pub struct PathIterator<'a, 'b, T> { } impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { - pub(super) fn new(merkle: &'a T, key: &'b [u8]) -> Result { + pub(super) fn new(merkle: &'a T, key: &'b [u8]) -> Result { let Some(root) = merkle.root_node() else { return Ok(Self { state: PathIteratorState::Exhausted, @@ -410,7 +410,7 @@ impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { } impl Iterator for PathIterator<'_, '_, T> { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { // destructuring is necessary here because we need mutable access to `state` @@ -483,7 +483,7 @@ impl Iterator for PathIterator<'_, '_, T> { Some(Child::AddressWithHash(child_addr, _)) => { let child = match merkle.read_node(*child_addr) { Ok(child) => child, - Err(e) => return Some(Err(e.into())), + Err(e) => return Some(Err(e)), }; let node_key = matched_key.clone().into_boxed_slice(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index d82b1ee36047..f440f7242472 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,8 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::manager::RevisionManagerError; -use crate::merkle::MerkleError; -use crate::proof::{Proof, ProofNode}; +use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; use async_trait::async_trait; use futures::Stream; @@ -140,9 +139,9 @@ pub enum Error { #[error("sibling already committed")] SiblingCommitted, - /// Generic merkle error - #[error("merkle error: {0}")] - Merkle(#[from] MerkleError), + /// Proof error + #[error("proof error")] + ProofError(#[from] ProofError), } impl From for Error { diff --git a/firewood/src/v2/db.rs b/firewood/src/v2/db.rs deleted file mode 100644 index ba6d1255ef67..000000000000 --- a/firewood/src/v2/db.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::db::DbError; -use crate::v2::api; - -impl From for api::Error { - fn from(value: DbError) -> Self { - match value { - DbError::Merkle(e) => api::Error::InternalError(Box::new(e)), - DbError::IO(e) => api::Error::IO(e), - } - } -} diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index b1a3a938ba07..39cf64e4217f 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -4,9 +4,6 @@ /// The public API pub mod api; -/// The database -pub mod db; - /// The proposal pub mod propose; From 884e05f75b88c36c2e1007052ef2e0eed15a41fd Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:18:44 +0000 Subject: [PATCH 0670/1053] refactor!(ffi): overhaul Go bindings (#810) --- .github/workflows/ci.yaml | 31 +++++ ffi/firewood.go | 258 +++++++++++++++++++------------------- ffi/firewood.h | 36 +++--- ffi/firewood_test.go | 221 +++++++++++++++++++++----------- ffi/go.mod | 14 ++- ffi/go.sum | 10 ++ ffi/kvbackend.go | 17 ++- ffi/src/lib.rs | 55 ++++---- 8 files changed, 385 insertions(+), 257 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9af16b42a5db..0a345b522f8d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -167,3 +167,34 @@ jobs: target/ key: ${{ needs.build.outputs.cache-key }} - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps + + ffi: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v2 + - name: Restore Cargo Cache + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ needs.build.outputs.cache-key }} + - name: Build Firewood FFI + working-directory: ffi + run: cargo build --release + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "ffi/go.mod" + cache-dependency-path: "ffi/go.sum" + - name: Test Go FFI bindings + working-directory: ffi + # cgocheck2 is expensive but provides complete pointer checks + run: GOEXPERIMENT=cgocheck2 go test ./... diff --git a/ffi/firewood.go b/ffi/firewood.go index e686d8b33e75..92295e89b53b 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -1,179 +1,177 @@ +// Package firewood provides a Go wrapper around the [Firewood] database. +// +// [Firewood]: https://github.com/ava-labs/firewood package firewood -// #cgo LDFLAGS: -L../target/release -L/usr/local/lib -lfirewood_ffi -lm +// // Note that -lm is required on Linux but not on Mac. +// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm // #include "firewood.h" -// #include import "C" + import ( + "fmt" "runtime" - "strconv" "unsafe" ) -// Firewood is a handle to a Firewood database -type Firewood struct { - db *C.void +// A Database is a handle to a Firewood database. +type Database struct { + // handle is returned and accepted by cgo functions. It MUST be treated as + // an opaque value without special meaning. + // https://en.wikipedia.org/wiki/Blinkenlights + handle unsafe.Pointer } -// openConfig is used to configure the database at open time -type openConfig struct { - path string - nodeCacheEntries uintptr - revisions uintptr - readCacheStrategy uint8 - create bool - metricsPort uint16 +// Config configures the opening of a [Database]. +type Config struct { + Create bool + NodeCacheEntries uint + Revisions uint + ReadCacheStrategy CacheStrategy + MetricsPort uint16 } -// OpenOption is a function that configures the database at open time -type OpenOption func(*openConfig) - -// WithPath sets the path for the database -func WithPath(path string) OpenOption { - return func(o *openConfig) { - o.path = path +// DefaultConfig returns a sensible default Config. +func DefaultConfig() *Config { + return &Config{ + NodeCacheEntries: 1_000_000, + Revisions: 100, + ReadCacheStrategy: OnlyCacheWrites, + MetricsPort: 3000, } } -// WithNodeCacheEntries sets the number of node cache entries -func WithNodeCacheEntries(entries uintptr) OpenOption { - if entries < 1 { - panic("Node cache entries must be >= 1") - } - return func(o *openConfig) { - o.nodeCacheEntries = entries - } -} +// A CacheStrategy represents the caching strategy used by a [Database]. +type CacheStrategy uint8 -// WithRevisions sets the number of revisions to keep -func WithRevisions(revisions uintptr) OpenOption { - if revisions < 2 { - panic("Revisions must be >= 2") - } - return func(o *openConfig) { - o.revisions = revisions - } -} +const ( + OnlyCacheWrites CacheStrategy = iota + CacheBranchReads + CacheAllReads -// WithMetricIntervalSeconds sets the interval in seconds for metrics -// set to 0 for no metrics -func WithMetricsPort(metrics_port uint16) OpenOption { - return func(o *openConfig) { - o.metricsPort = metrics_port - } -} + // invalidCacheStrategy MUST be the final value in the iota block to make it + // the smallest value greater than all valid values. + invalidCacheStrategy +) -// WithReadCacheStrategy sets the read cache strategy -// 0: Only writes are cached -// 1: Branch reads are cached -// 2: All reads are cached -func WithReadCacheStrategy(strategy uint8) OpenOption { - if strategy > 2 { - panic("Invalid read cache strategy " + strconv.Itoa(int(strategy))) +// New opens or creates a new Firewood database with the given configuration. If +// a nil `Config` is provided [DefaultConfig] will be used instead. +func New(filePath string, conf *Config) (*Database, error) { + if conf == nil { + conf = DefaultConfig() } - return func(o *openConfig) { - o.readCacheStrategy = strategy + if conf.ReadCacheStrategy >= invalidCacheStrategy { + return nil, fmt.Errorf("invalid %T (%[1]d)", conf.ReadCacheStrategy) } -} - -// WithCreate sets whether to create a new database -// If false, the database will be opened -// If true, the database will be created -func WithCreate(create bool) OpenOption { - return func(o *openConfig) { - o.create = create + if conf.Revisions < 2 { + return nil, fmt.Errorf("%T.Revisions must be >= 2", conf) } -} - -// NewDatabase opens or creates a new Firewood database with the given options. -// Returns a handle that can be used for subsequent database operations. -func NewDatabase(options ...OpenOption) Firewood { - opts := &openConfig{ - nodeCacheEntries: 1_000_000, - revisions: 100, - readCacheStrategy: 0, - path: "firewood.db", - metricsPort: 3000, + if conf.NodeCacheEntries < 1 { + return nil, fmt.Errorf("%T.NodeCacheEntries must be >= 1", conf) } - for _, opt := range options { - opt(opts) + args := C.struct_CreateOrOpenArgs{ + path: C.CString(filePath), + cache_size: C.size_t(conf.NodeCacheEntries), + revisions: C.size_t(conf.Revisions), + strategy: C.uint8_t(conf.ReadCacheStrategy), + metrics_port: C.uint16_t(conf.MetricsPort), } + var db unsafe.Pointer - if opts.create { - db = C.fwd_create_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy), C.uint16_t(opts.metricsPort)) + if conf.Create { + db = C.fwd_create_db(args) } else { - db = C.fwd_open_db(C.CString(opts.path), C.size_t(opts.nodeCacheEntries), C.size_t(opts.revisions), C.uint8_t(opts.readCacheStrategy), C.uint16_t(opts.metricsPort)) + db = C.fwd_open_db(args) } - - ptr := (*C.void)(db) - return Firewood{db: ptr} + return &Database{handle: db}, nil } -// KeyValue is a key-value pair +// KeyValue is a key-value pair. type KeyValue struct { Key []byte Value []byte } -// Apply a batch of updates to the database -// Returns the hash of the root node after the batch is applied -// Note that if the Value is empty, the key will be deleted as a prefix -// delete (that is, all children will be deleted) -// WARNING: Calling it with an empty key and value will delete the entire database - -func (f *Firewood) Batch(ops []KeyValue) []byte { - var pin runtime.Pinner - defer pin.Unpin() - - ffi_ops := make([]C.struct_KeyValue, len(ops)) +// Batch applies a batch of updates to the database, returning the hash of the +// root node after the batch is applied. +// +// NOTE that if the `Value` is empty, the respective `Key` will be deleted as a +// prefix deletion; i.e. all children will be deleted. +// +// WARNING: a consequence of prefix deletion is that calling Batch with an empty +// key and value will delete the entire database. +func (db *Database) Batch(ops []KeyValue) []byte { + // TODO(arr4n) refactor this to require explicit signalling from the caller + // that they want prefix deletion, similar to `rm --no-preserve-root`. + + values, cleanup := newValueFactory() + defer cleanup() + + ffiOps := make([]C.struct_KeyValue, len(ops)) for i, op := range ops { - ffi_ops[i] = C.struct_KeyValue{ - key: make_value(&pin, op.Key), - value: make_value(&pin, op.Value), + ffiOps[i] = C.struct_KeyValue{ + key: values.from(op.Key), + value: values.from(op.Value), } } - ptr := (*C.struct_KeyValue)(unsafe.Pointer(&ffi_ops[0])) - hash := C.fwd_batch(unsafe.Pointer(f.db), C.size_t(len(ops)), ptr) - hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) - C.fwd_free_value(&hash) - return hash_bytes + + hash := C.fwd_batch( + db.handle, + C.size_t(len(ffiOps)), + (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), // implicitly pinned + ) + return extractBytesThenFree(&hash) } -// Get retrieves the value for the given key. -func (f *Firewood) Get(input_key []byte) ([]byte, error) { - var pin runtime.Pinner - defer pin.Unpin() - ffi_key := make_value(&pin, input_key) - - value := C.fwd_get(unsafe.Pointer(f.db), ffi_key) - ffi_bytes := C.GoBytes(unsafe.Pointer(value.data), C.int(value.len)) - C.fwd_free_value(&value) - if len(ffi_bytes) == 0 { - return nil, nil - } - return ffi_bytes, nil +// extractBytesThenFree converts the cgo `Value` payload to a byte slice, frees +// the `Value`, and returns the extracted slice. +func extractBytesThenFree(v *C.struct_Value) []byte { + buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) + C.fwd_free_value(v) + return buf } -func make_value(pin *runtime.Pinner, data []byte) C.struct_Value { - if len(data) == 0 { - return C.struct_Value{0, nil} - } - ptr := (*C.uchar)(unsafe.Pointer(&data[0])) - pin.Pin(ptr) - return C.struct_Value{C.size_t(len(data)), ptr} +// Get retrieves the value for the given key. It always returns a nil error. +func (db *Database) Get(key []byte) ([]byte, error) { + values, cleanup := newValueFactory() + defer cleanup() + val := C.fwd_get(db.handle, values.from(key)) + return extractBytesThenFree(&val), nil } // Root returns the current root hash of the trie. -func (f *Firewood) Root() []byte { - hash := C.fwd_root_hash(unsafe.Pointer(f.db)) - hash_bytes := C.GoBytes(unsafe.Pointer(hash.data), C.int(hash.len)) - C.fwd_free_value(&hash) - return hash_bytes +func (db *Database) Root() []byte { + hash := C.fwd_root_hash(db.handle) + return extractBytesThenFree(&hash) } -// Close closes the database and releases all held resources. -func (f *Firewood) Close() error { - C.fwd_close_db(unsafe.Pointer(f.db)) +// Close closes the database and releases all held resources. It always returns +// nil. +func (db *Database) Close() error { + C.fwd_close_db(db.handle) + db.handle = nil return nil } + +// newValueFactory returns a factory for converting byte slices into cgo `Value` +// structs that can be passed as arguments to cgo functions. The returned +// cleanup function MUST be called when the constructed values are no longer +// required, after which they can no longer be used as cgo arguments. +func newValueFactory() (*valueFactory, func()) { + f := new(valueFactory) + return f, func() { f.pin.Unpin() } +} + +type valueFactory struct { + pin runtime.Pinner +} + +func (f *valueFactory) from(data []byte) C.struct_Value { + if len(data) == 0 { + return C.struct_Value{0, nil} + } + ptr := (*C.uchar)(unsafe.SliceData(data)) + f.pin.Pin(ptr) + return C.struct_Value{C.size_t(len(data)), ptr} +} diff --git a/ffi/firewood.h b/ffi/firewood.h index 2389a08a119f..deb59b19bc66 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -18,6 +18,22 @@ typedef struct KeyValue { struct Value value; } KeyValue; +/** + * Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. + * + * * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` + * otherwise should exist if passed to `fwd_open_db()`. + * * `cache_size` - The size of the node cache, panics if <= 0 + * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + */ +typedef struct CreateOrOpenArgs { + const char *path; + size_t cache_size; + size_t revisions; + uint8_t strategy; + uint16_t metrics_port; +} CreateOrOpenArgs; + /** * Puts the given key-value pairs into the database. * @@ -59,9 +75,7 @@ void fwd_close_db(void *db); * * # Arguments * - * * `path` - The path to the database file, which will be overwritten - * * `cache_size` - The size of the node cache, panics if <= 0 - * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + * See `CreateOrOpenArgs`. * * # Returns * @@ -75,11 +89,7 @@ void fwd_close_db(void *db); * The caller must call `close` to free the memory associated with the returned database handle. * */ -void *fwd_create_db(const char *path, - size_t cache_size, - size_t revisions, - uint8_t strategy, - uint16_t metrics_port); +void *fwd_create_db(struct CreateOrOpenArgs args); /** * Frees the memory associated with a `Value`. @@ -112,9 +122,7 @@ struct Value fwd_get(void *db, struct Value key); * * # Arguments * - * * `path` - The path to the database file, which should exist - * * `cache_size` - The size of the node cache, panics if <= 0 - * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + * See `CreateOrOpenArgs`. * * # Returns * @@ -128,11 +136,7 @@ struct Value fwd_get(void *db, struct Value key); * The caller must call `close` to free the memory associated with the returned database handle. * */ -void *fwd_open_db(const char *path, - size_t cache_size, - size_t revisions, - uint8_t strategy, - uint16_t metrics_port); +void *fwd_open_db(struct CreateOrOpenArgs args); /** * Get the root hash of the latest version of the database diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 0e9ece98d141..af5d0f94b6e6 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1,106 +1,177 @@ package firewood import ( + "bytes" + "fmt" "os" + "path/filepath" "strconv" + "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestMain(m *testing.M) { + // The cgocheck debugging flag checks that all pointers are pinned. + // TODO(arr4n) why doesn't `//go:debug cgocheck=1` work? https://go.dev/doc/godebug + debug := strings.Split(os.Getenv("GODEBUG"), ",") + var hasCgoCheck bool + for _, kv := range debug { + switch strings.TrimSpace(kv) { + case "cgocheck=1": + hasCgoCheck = true + break + case "cgocheck=0": + fmt.Fprint(os.Stderr, "GODEBUG=cgocheck=0; MUST be 1 for Firewood cgo tests") + os.Exit(1) + } + } + + if !hasCgoCheck { + debug = append(debug, "cgocheck=1") + if err := os.Setenv("GODEBUG", strings.Join(debug, ",")); err != nil { + fmt.Fprintf(os.Stderr, `os.Setenv("GODEBUG", ...) error %v`, err) + os.Exit(1) + } + } + + os.Exit(m.Run()) +} + +func newTestDatabase(t *testing.T) *Database { + t.Helper() + + conf := DefaultConfig() + conf.Create = true + // The TempDir directory is automatically cleaned up so there's no need to + // remove test.db. + dbFile := filepath.Join(t.TempDir(), "test.db") + + f, err := New(dbFile, conf) + require.NoErrorf(t, err, "NewDatabase(%+v)", conf) + // Close() always returns nil, its signature returning an error only to + // conform with an externally required interface. + t.Cleanup(func() { f.Close() }) + return f +} + func TestInsert(t *testing.T) { - var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) - defer os.Remove("test.db") - defer f.Close() - f.Batch([]KeyValue{ - {[]byte("abc"), []byte("def")}, + db := newTestDatabase(t) + const ( + key = "abc" + val = "def" + ) + db.Batch([]KeyValue{ + {[]byte(key), []byte(val)}, }) - value, _ := f.Get([]byte("abc")) - if string(value) != "def" { - t.Errorf("expected def, got %s", value) + got, err := db.Get([]byte(key)) + require.NoErrorf(t, err, "%T.Get(%q)", db, key) + assert.Equal(t, val, string(got), "Recover lone batch-inserted value") +} + +func keyForTest(i int) []byte { + return []byte("key" + strconv.Itoa(i)) +} + +func valForTest(i int) []byte { + return []byte("value" + strconv.Itoa(i)) +} + +func kvForTest(i int) KeyValue { + return KeyValue{ + Key: keyForTest(i), + Value: valForTest(i), } } func TestInsert100(t *testing.T) { - var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) - defer os.Remove("test.db") - defer f.Close() - ops := make([]KeyValue, 100) - for i := 0; i < 100; i++ { - ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} + tests := []struct { + name string + insert func(*Database, []KeyValue) (root []byte, _ error) + }{ + { + name: "Batch", + insert: func(db *Database, kvs []KeyValue) ([]byte, error) { + return db.Batch(kvs), nil + }, + }, + { + name: "Update", + insert: func(db *Database, kvs []KeyValue) ([]byte, error) { + var keys, vals [][]byte + for _, kv := range kvs { + keys = append(keys, kv.Key) + vals = append(vals, kv.Value) + } + return db.Update(keys, vals) + }, + }, } - f.Batch(ops) - for i := 0; i < 100; i++ { - value, err := f.Get([]byte("key" + strconv.Itoa(i))) - if err != nil { - t.FailNow() - } - if string(value) != "value"+strconv.Itoa(i) { - t.Errorf("expected value%d, got %s", i, value) - } - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := newTestDatabase(t) - hash := f.Root() - if len(hash) != 32 { - t.Errorf("expected 32 bytes, got %d", len(hash)) - } + ops := make([]KeyValue, 100) + for i := range ops { + ops[i] = kvForTest(i) + } + rootFromInsert, err := tt.insert(db, ops) + require.NoError(t, err, "inserting") - // we know the hash starts with 0xf8 - if hash[0] != 0xf8 { - t.Errorf("expected 0xf8, got %x", hash[0]) - } + for _, op := range ops { + got, err := db.Get(op.Key) + require.NoErrorf(t, err, "%T.Get(%q)", db, op.Key) + // Cast as strings to improve debug messages. + want := string(op.Value) + assert.Equal(t, want, string(got), "Recover nth batch-inserted value") + } - delete_ops := make([]KeyValue, 1) - ops[0] = KeyValue{[]byte(""), []byte("")} - f.Batch(delete_ops) + hash := db.Root() + assert.Lenf(t, hash, 32, "%T.Root()", db) + // we know the hash starts with 0xf8 + assert.Equalf(t, byte(0xf8), hash[0], "First byte of %T.Root()", db) + assert.Equalf(t, rootFromInsert, hash, "%T.Root() matches value returned by insertion", db) + }) + } } func TestRangeDelete(t *testing.T) { - const N = 100 - var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) - defer os.Remove("test.db") - defer f.Close() - ops := make([]KeyValue, N) - for i := 0; i < N; i++ { - ops[i] = KeyValue{[]byte("key" + strconv.Itoa(i)), []byte("value" + strconv.Itoa(i))} + db := newTestDatabase(t) + ops := make([]KeyValue, 100) + for i := range ops { + ops[i] = kvForTest(i) } - f.Batch(ops) - - // delete all keys that start with "key" - delete_ops := make([]KeyValue, 1) - delete_ops[0] = KeyValue{[]byte("key1"), []byte("")} - f.Batch(delete_ops) - - for i := 0; i < N; i++ { - keystring := "key" + strconv.Itoa(i) - value, err := f.Get([]byte(keystring)) - if err != nil { - t.FailNow() - } - if (value != nil) == (keystring[3] == '1') { - t.Errorf("incorrect response for %s %s %x", keystring, value, keystring[3]) + db.Batch(ops) + + const deletePrefix = 1 + db.Batch([]KeyValue{{ + Key: keyForTest(deletePrefix), + // delete all keys that start with "key1" + Value: nil, + }}) + + for _, op := range ops { + got, err := db.Get(op.Key) + require.NoError(t, err) + + if deleted := bytes.HasPrefix(op.Key, keyForTest(deletePrefix)); deleted { + assert.Empty(t, err, got) + } else { + assert.Equal(t, op.Value, got) } } } func TestInvariants(t *testing.T) { - var f Firewood = NewDatabase(WithCreate(true), WithPath("test.db")) - defer os.Remove("test.db") - defer f.Close() - - // validate that the root of an empty trie is all zeroes - empty_root := f.Root() - if len(empty_root) != 32 { - t.Errorf("expected 32 bytes, got %d", len(empty_root)) - } - empty_array := [32]byte(empty_root) - if empty_array != [32]byte{} { - t.Errorf("expected empty root, got %x", empty_root) - } + db := newTestDatabase(t) - // validate that get returns nil, nil for non-existent key - val, err := f.Get([]byte("non-existent")) - if val != nil || err != nil { - t.Errorf("expected nil, nil, got %v, %v", val, err) - } + assert.Equalf(t, make([]byte, 32), db.Root(), "%T.Root() of empty trie") + + got, err := db.Get([]byte("non-existent")) + require.NoError(t, err) + assert.Emptyf(t, got, "%T.Get([non-existent key])", db) } diff --git a/ffi/go.mod b/ffi/go.mod index f75f434a5595..508578f0be71 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -1,5 +1,13 @@ -module github.com/ava-labs/firewood/ffi/v2 +module github.com/ava-labs/firewood/ffi -go 1.22.8 +go 1.23 -toolchain go1.23.4 +toolchain go1.23.6 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/ffi/go.sum b/ffi/go.sum index e69de29bb2d1..713a0b4f0a3a 100644 --- a/ffi/go.sum +++ b/ffi/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index e42dfb57ab6b..bc52eb7b4f37 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -4,7 +4,7 @@ package firewood // this is used for some of the firewood performance tests // Validate that Firewood implements the KVBackend interface -var _ kVBackend = (*Firewood)(nil) +var _ kVBackend = (*Database)(nil) type kVBackend interface { // Returns the current root hash of the trie. @@ -40,23 +40,22 @@ type kVBackend interface { Close() error } -// Prefetch does nothing since we don't need to prefetch for firewood -func (f *Firewood) Prefetch(key []byte) ([]byte, error) { +// Prefetch is a no-op since we don't need to prefetch for Firewood. +func (*Database) Prefetch(key []byte) ([]byte, error) { return nil, nil } -// Commit does nothing, since update already persists changes -func (f *Firewood) Commit(root []byte) error { +// Commit is a no-op, since [Database.Update] already persists changes. +func (*Database) Commit(root []byte) error { return nil } // Update batches all the keys and values and applies them to the -// database -func (f *Firewood) Update(keys, vals [][]byte) ([]byte, error) { - // batch the keys and values +// database. +func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { ops := make([]KeyValue, len(keys)) for i := range keys { ops[i] = KeyValue{keys[i], vals[i]} } - return f.Batch(ops), nil + return db.Batch(ops), nil } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index cd52ed9ecc32..72a44b37da10 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -172,14 +172,27 @@ pub unsafe extern "C" fn fwd_free_value(value: *const Value) { drop(recreated_box); } +/// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. +/// +/// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` +/// otherwise should exist if passed to `fwd_open_db()`. +/// * `cache_size` - The size of the node cache, panics if <= 0 +/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +#[repr(C)] +pub struct CreateOrOpenArgs { + path: *const std::ffi::c_char, + cache_size: usize, + revisions: usize, + strategy: u8, + metrics_port: u16, +} + /// Create a database with the given cache size and maximum number of revisions, as well /// as a specific cache strategy /// /// # Arguments /// -/// * `path` - The path to the database file, which will be overwritten -/// * `cache_size` - The size of the node cache, panics if <= 0 -/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +/// See `CreateOrOpenArgs`. /// /// # Returns /// @@ -193,27 +206,23 @@ pub unsafe extern "C" fn fwd_free_value(value: *const Value) { /// The caller must call `close` to free the memory associated with the returned database handle. /// #[no_mangle] -pub unsafe extern "C" fn fwd_create_db( - path: *const std::ffi::c_char, - cache_size: usize, - revisions: usize, - strategy: u8, - metrics_port: u16, -) -> *mut Db { +pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { let cfg = DbConfig::builder() .truncate(true) - .manager(manager_config(cache_size, revisions, strategy)) + .manager(manager_config( + args.cache_size, + args.revisions, + args.strategy, + )) .build(); - common_create(path, metrics_port, cfg) + common_create(args.path, args.metrics_port, cfg) } /// Open a database with the given cache size and maximum number of revisions /// /// # Arguments /// -/// * `path` - The path to the database file, which should exist -/// * `cache_size` - The size of the node cache, panics if <= 0 -/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +/// See `CreateOrOpenArgs`. /// /// # Returns /// @@ -227,18 +236,16 @@ pub unsafe extern "C" fn fwd_create_db( /// The caller must call `close` to free the memory associated with the returned database handle. /// #[no_mangle] -pub unsafe extern "C" fn fwd_open_db( - path: *const std::ffi::c_char, - cache_size: usize, - revisions: usize, - strategy: u8, - metrics_port: u16, -) -> *mut Db { +pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *mut Db { let cfg = DbConfig::builder() .truncate(false) - .manager(manager_config(cache_size, revisions, strategy)) + .manager(manager_config( + args.cache_size, + args.revisions, + args.strategy, + )) .build(); - common_create(path, metrics_port, cfg) + common_create(args.path, args.metrics_port, cfg) } unsafe fn common_create( From 535bd11fe7d38fd2116a1b9d18e883b04910ea0e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 18 Mar 2025 12:39:17 -0700 Subject: [PATCH 0671/1053] ethhash implementation This adds full ethereum compatibility including hashing of account subtrees. The toughest areas are around accounts, since we need a node that is a fake root node in some cases, and that can change -- an account branch that previously had one child could now have more than one which means it has to rehash something that didn't change. This means that hash_helper and friends can now fail, as they do I/O. The propogation of the error back to the callers now passes all the way back through the API. CI now tests with logger and ethhash features enabled, and again with them disabled. --- .github/workflows/ci.yaml | 7 +- firewood/Cargo.toml | 8 +- firewood/src/db.rs | 22 ++- firewood/src/lib.rs | 6 + firewood/src/merkle.rs | 161 +++++++++++++++-- firewood/src/proof.rs | 38 ++-- storage/Cargo.toml | 4 + storage/benches/serializer.rs | 2 +- storage/src/hashednode.rs | 163 ++++++----------- storage/src/hashers/ethhash.rs | 310 ++++++++++++++++++++++++++++++++ storage/src/hashers/merkledb.rs | 101 +++++++++++ storage/src/hashers/mod.rs | 7 + storage/src/lib.rs | 3 +- storage/src/logger.rs | 14 +- storage/src/node/branch.rs | 165 ++++++++++++++++- storage/src/node/mod.rs | 23 ++- storage/src/nodestore.rs | 134 +++++++++++--- storage/src/trie_hash.rs | 47 ++++- 18 files changed, 1017 insertions(+), 198 deletions(-) create mode 100644 storage/src/hashers/ethhash.rs create mode 100644 storage/src/hashers/merkledb.rs create mode 100644 storage/src/hashers/mod.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0a345b522f8d..2ab4805cb446 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -121,8 +121,13 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ needs.build.outputs.cache-key }} - - name: Run tests + - name: Run tests with features enabled + run: cargo test --verbose --features logger,ethhash + - name: Run tests with ethhash disabled run: cargo test --verbose + # TODO: Enable testing with branch_factor_256 + # - name: Run tests with branch_factor_256 + # run: cargo test --verbose --features branch_factor_256 examples: needs: build diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 5b5c79a79867..d4d1b79cd731 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -37,6 +37,7 @@ nightly = [] io-uring = ["storage/io-uring"] logger = ["storage/logger"] branch_factor_256 = [ "storage/branch_factor_256" ] +ethhash = [ "storage/ethhash" ] [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} @@ -47,7 +48,12 @@ clap = { version = "4.5.0", features = ['derive'] } pprof = { version = "0.14.0", features = ["flamegraph"] } tempfile = "3.12.0" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } - +ethereum-types = "0.15.1" +sha3 = "0.10.8" +plain_hasher = "0.2.3" +hex-literal = "1.0.0" +env_logger = "0.11.7" +hash-db = "0.15.2" [[bench]] name = "hashops" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d89be0d375f3..7c3a8a450a8a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -197,7 +197,7 @@ where let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = - Arc::new(nodestore.into()); + Arc::new(nodestore.try_into()?); drop(span); self.manager @@ -294,7 +294,7 @@ impl Db { } let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = - Arc::new(nodestore.into()); + Arc::new(nodestore.try_into()?); self.manager .write() .expect("poisoned lock") @@ -404,7 +404,7 @@ impl<'a> api::Proposal for Proposal<'a> { } let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = - Arc::new(nodestore.into()); + Arc::new(nodestore.try_into()?); self.db .manager .write() @@ -502,10 +502,16 @@ mod test { #[tokio::test] async fn reopen_test() { let db = testdb().await; - let batch = vec![BatchOp::Put { - key: b"k", - value: b"v", - }]; + let batch = vec![ + BatchOp::Put { + key: b"a", + value: b"1", + }, + BatchOp::Put { + key: b"b", + value: b"2", + }, + ]; let proposal = db.propose(batch).await.unwrap(); proposal.commit().await.unwrap(); println!("{:?}", db.root_hash().await.unwrap().unwrap()); @@ -514,7 +520,7 @@ mod test { println!("{:?}", db.root_hash().await.unwrap().unwrap()); let committed = db.root_hash().await.unwrap().unwrap(); let historical = db.revision(committed).await.unwrap(); - assert_eq!(&*historical.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); } // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 2d0d15f45224..1b77fc151647 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -106,6 +106,12 @@ //! abandoned, nothing has actually been written to disk. //! #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] + +#[cfg(all(feature = "ethhash", feature = "branch_factor_256"))] +compile_error!( + "feature \"ethhash\" and feature \"branch_factor_256\" cannot be enabled at the same time" +); + /// Database module for Firewood. pub mod db; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index d6d41533d4d1..db9cbdcfcf74 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -15,9 +15,9 @@ use std::iter::once; use std::num::NonZeroUsize; use std::sync::Arc; use storage::{ - BranchNode, Child, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, LinearAddress, - MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, SharedNode, TrieHash, - TrieReader, ValueDigest, + BranchNode, Child, HashType, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, + LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, + SharedNode, TrieReader, ValueDigest, }; /// Keys are boxed u8 slices @@ -171,7 +171,7 @@ impl Merkle { // No nodes, even the root, are before `key`. // The root alone proves the non-existence of `key`. // TODO reduce duplicate code with ProofNode::from - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = [const { None }; BranchNode::MAX_CHILDREN]; if let Some(branch) = root.as_branch() { // TODO danlaine: can we avoid indexing? @@ -183,6 +183,8 @@ impl Merkle { proof.push(ProofNode { key: root.partial_path().bytes(), + #[cfg(feature = "ethhash")] + partial_len: root.partial_path().0.len(), value_digest: root .value() .map(|value| ValueDigest::Value(value.to_vec().into_boxed_slice())), @@ -333,7 +335,7 @@ impl Merkle { pub(crate) fn dump_node( &self, addr: LinearAddress, - hash: Option<&TrieHash>, + hash: Option<&HashType>, seen: &mut HashSet, writer: &mut dyn Write, ) -> Result<(), Error> { @@ -383,7 +385,10 @@ impl Merkle { if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { writeln!(result, " root -> {root_addr}").map_err(Error::other)?; let mut seen = HashSet::new(); - self.dump_node(root_addr, Some(&root_hash), &mut seen, &mut result)?; + // If ethhash is off, root_hash.into() is already the correct type + // so we disable the warning here + #[allow(clippy::useless_conversion)] + self.dump_node(root_addr, Some(&root_hash.into()), &mut seen, &mut result)?; } write!(result, "}}").map_err(Error::other)?; @@ -391,13 +396,14 @@ impl Merkle { } } -impl From>> +impl TryFrom>> for Merkle, S>> { - fn from(m: Merkle>) -> Self { - Merkle { - nodestore: m.nodestore.into(), - } + type Error = std::io::Error; + fn try_from(m: Merkle>) -> Result { + Ok(Merkle { + nodestore: m.nodestore.try_into()?, + }) } } @@ -405,7 +411,8 @@ impl Merkle> { /// Convert a merkle backed by an MutableProposal into an ImmutableProposal /// TODO: We probably don't need this function pub fn hash(self) -> Merkle, S>> { - self.into() + // I think this is only used in testing + self.try_into().expect("failed to convert") } /// Map `key` to `value` in the trie. @@ -1023,12 +1030,12 @@ mod tests { use super::*; use rand::rngs::StdRng; use rand::{rng, Rng, SeedableRng}; - use storage::{MemStore, MutableProposal, NodeStore, RootReader}; + use storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; use test_case::test_case; // Returns n random key-value pairs. fn generate_random_kvs(seed: u64, n: usize) -> Vec<(Vec, Vec)> { - eprintln!("Used seed: {}", seed); + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); let mut rng = StdRng::seed_from_u64(seed); @@ -1684,7 +1691,7 @@ mod tests { ) -> Result>, Error> { let nodestore = NodeStore::new_empty_proposal(MemStore::new(vec![]).into()); let mut merkle = Merkle::from(nodestore); - for (k, v) in items.iter() { + for (k, v) in items { merkle.insert(k.as_ref(), Box::from(v.as_ref()))?; } @@ -1707,6 +1714,7 @@ mod tests { Ok(()) } + #[cfg(not(feature = "ethhash"))] #[test_case(vec![], None; "empty trie")] #[test_case(vec![(&[0],&[0])], Some("073615413d814b23383fc2c8d8af13abfffcb371b654b98dbf47dd74b1e4d1b9"); "root")] #[test_case(vec![(&[0,1],&[0,1])], Some("28e67ae4054c8cdf3506567aa43f122224fe65ef1ab3e7b7899f75448a69a6fd"); "root with partial path")] @@ -1735,6 +1743,120 @@ mod tests { } } + #[cfg(feature = "ethhash")] + mod ethhasher { + use ethereum_types::H256; + use hash_db::Hasher; + use plain_hasher::PlainHasher; + use sha3::{Digest, Keccak256}; + + #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] + pub struct KeccakHasher; + + impl Hasher for KeccakHasher { + type Out = H256; + type StdHasher = PlainHasher; + const LENGTH: usize = 32; + + #[inline] + fn hash(x: &[u8]) -> Self::Out { + let mut hasher = Keccak256::new(); + hasher.update(x); + let result = hasher.finalize(); + H256::from_slice(result.as_slice()) + } + } + } + + #[cfg(feature = "ethhash")] + #[test_case(&[("doe", "reindeer")])] + #[test_case(&[("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cat")])] + #[test_case(&[("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] + #[test_case(&[("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] + fn test_root_hash_eth_compatible + Clone + Ord>(kvs: &[(T, T)]) { + use ethereum_types::H256; + use ethhasher::KeccakHasher; + use triehash::trie_root; + + let merkle = merkle_build_test(kvs.to_vec()).unwrap().hash(); + let firewood_hash = merkle.nodestore.root_hash().unwrap().unwrap_or_default(); + let eth_hash = trie_root::(kvs.to_vec()); + let firewood_hash = H256::from_slice(firewood_hash.as_ref()); + + assert_eq!(firewood_hash, eth_hash); + } + + #[cfg(feature = "ethhash")] + #[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[], + "c00ca9b8e6a74b03f6b1ae2db4a65ead348e61b74b339fe4b117e860d79c7821" + )] + #[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[ + ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000") + ], + "91336bf4e6756f68e1af0ad092f4a551c52b4a66860dc31adbd736f0acbadaf6" + )] + #[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[ + ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000"), + ("0e81f83a84964b811dd1b8328262a9f57e6bc3e5e7eb53627d10437c73c4b8da", "a02800000000000000000000000000000000000000000000000000000000000000"), + ], + "c267104830880c966c2cc8c669659e4bfaf3126558dbbd6216123b457944001b" + )] + fn test_eth_compatible_accounts( + account: &str, + account_value: &str, + key_suffixes_and_values: &[(&str, &str)], + expected_root: &str, + ) { + use sha2::Digest as _; + use sha3::Keccak256; + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")) + .is_test(true) + .try_init() + .ok(); + + let account = make_box(account); + let expected_key_hash = Keccak256::digest(&account); + + let items = once(( + Box::from(expected_key_hash.as_slice()), + make_box(account_value), + )) + .chain(key_suffixes_and_values.iter().map(|(key_suffix, value)| { + let key = expected_key_hash + .iter() + .copied() + .chain(make_box(key_suffix).iter().copied()) + .collect(); + let value = make_box(value); + (key, value) + })) + .collect::, Box<_>)>>(); + + let merkle = merkle_build_test(items).unwrap().hash(); + let firewood_hash = merkle.nodestore.root_hash().unwrap(); + + assert_eq!( + firewood_hash, + TrieHash::try_from(&*make_box(expected_root)).ok() + ); + } + + // helper method to convert a string into a boxed slice + #[cfg(feature = "ethhash")] + fn make_box(hex_str: &str) -> Box<[u8]> { + hex::decode(hex_str).unwrap().into_boxed_slice() + } + #[test] fn test_root_hash_fuzz_insertions() -> Result<(), Error> { use rand::rngs::StdRng; @@ -1757,14 +1879,17 @@ mod tests { key }; - for _ in 0..10 { + // TODO: figure out why this fails if we use more than 27 iterations with branch_factor_256 + for _ in 0..27 { let mut items = Vec::new(); - for _ in 0..10 { - let val: Vec = (0..8).map(|_| rng.borrow_mut().random()).collect(); + for _ in 0..100 { + let val: Vec = (0..256).map(|_| rng.borrow_mut().random()).collect(); items.push((keygen(), val)); } + // #[cfg(feature = "ethhash")] + // test_root_hash_eth_compatible(&items); merkle_build_test(items)?; } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 224057ba08bd..2d017f275b4b 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -3,7 +3,7 @@ use sha2::{Digest, Sha256}; use storage::{ - BranchNode, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, + BranchNode, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, }; use thiserror::Error; @@ -69,11 +69,14 @@ pub enum ProofError { pub struct ProofNode { /// The key this node is at. Each byte is a nibble. pub key: Box<[u8]>, + /// The length of the key prefix that is shared with the previous node. + #[cfg(feature = "ethhash")] + pub partial_len: usize, /// None if the node does not have a value. /// Otherwise, the node's value or the hash of its value. pub value_digest: Option>>, /// The hash of each child, or None if the child does not exist. - pub child_hashes: [Option; BranchNode::MAX_CHILDREN], + pub child_hashes: [Option; BranchNode::MAX_CHILDREN], } impl Hashable for ProofNode { @@ -81,14 +84,19 @@ impl Hashable for ProofNode { self.key.as_ref().iter().copied() } + #[cfg(feature = "ethhash")] + fn partial_path(&self) -> impl Iterator + Clone { + self.key.as_ref().iter().skip(self.partial_len).copied() + } + fn value_digest(&self) -> Option> { self.value_digest.as_ref().map(|vd| match vd { ValueDigest::Value(v) => ValueDigest::Value(v.as_ref()), - ValueDigest::_Hash(h) => ValueDigest::_Hash(h.as_ref()), + ValueDigest::Hash(h) => ValueDigest::Hash(h.as_ref()), }) } - fn children(&self) -> impl Iterator + Clone { + fn children(&self) -> impl Iterator + Clone { self.child_hashes .iter() .enumerate() @@ -98,7 +106,7 @@ impl Hashable for ProofNode { impl From for ProofNode { fn from(item: PathIterItem) -> Self { - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = + let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = [const { None }; BranchNode::MAX_CHILDREN]; if let Some(branch) = item.node.as_branch() { @@ -109,8 +117,13 @@ impl From for ProofNode { } } + #[cfg(feature = "ethhash")] + let partial_len = item.key_nibbles.len() - item.node.partial_path().0.len(); + Self { key: item.key_nibbles, + #[cfg(feature = "ethhash")] + partial_len, value_digest: item .node .value() @@ -120,12 +133,6 @@ impl From for ProofNode { } } -impl From<&ProofNode> for TrieHash { - fn from(node: &ProofNode) -> Self { - node.to_hash() - } -} - /// A proof that a given key-value pair either exists or does not exist in a trie. #[derive(Clone, Debug)] pub struct Proof(pub Box<[T]>); @@ -161,7 +168,7 @@ impl Proof { return Err(ProofError::ValueMismatch); } } - ValueDigest::_Hash(got_hash) => { + ValueDigest::Hash(got_hash) => { // This proof proves that `key` maps to a value // whose hash is `got_hash`. let value_hash = Sha256::digest(expected_value.as_ref()); @@ -189,11 +196,12 @@ impl Proof { return Err(ProofError::Empty); }; - let mut expected_hash = root_hash; + #[allow(clippy::useless_conversion)] + let mut expected_hash: HashType = root_hash.clone().into(); let mut iter = self.0.iter().peekable(); while let Some(node) = iter.next() { - if node.to_hash() != *expected_hash { + if node.to_hash() != expected_hash { return Err(ProofError::UnexpectedHash); } @@ -222,7 +230,7 @@ impl Proof { .children() .find_map(|(i, hash)| { if i == next_nibble as usize { - Some(hash) + Some(hash.clone()) } else { None } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 0c568c1c477c..51ed7b2e370a 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -25,6 +25,9 @@ fastrace = { version = "0.7.4" } io-uring = { version = "0.7.4", optional = true } triomphe = "0.1.14" coarsetime = "0.1.35" +rlp = { version = "0.6.1", optional = true } +sha3 = { version = "0.10.8", optional = true } +bytes = { version = "1.10.1", optional = true } [dev-dependencies] rand = "0.9.0" @@ -37,6 +40,7 @@ tempfile = "3.12.0" logger = ["log"] branch_factor_256 = [] io-uring = ["dep:io-uring"] +ethhash = [ "dep:rlp", "dep:sha3", "dep:bytes" ] [[bench]] name = "serializer" diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index e679a8197d16..9cb76d1b348e 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -88,7 +88,7 @@ fn branch(c: &mut Criterion) { if i == 0 { Some(storage::Child::AddressWithHash( NonZeroU64::new(1).unwrap(), - storage::TrieHash::from([0; 32]), + storage::HashType::from([0; 32]), )) } else { None diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index b3d2a72166a5..5359b2f6c044 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -1,26 +1,29 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use sha2::{Digest, Sha256}; -use std::iter::{self}; +use std::{ + iter::{self}, + ops::Deref, +}; -use crate::{BranchNode, Child, LeafNode, Node, Path, TrieHash}; +use smallvec::SmallVec; -use integer_encoding::VarInt; - -const MAX_VARINT_SIZE: usize = 10; -const BITS_PER_NIBBLE: u64 = 4; +use crate::{BranchNode, Child, HashType, LeafNode, Node, Path}; /// Returns the hash of `node`, which is at the given `path_prefix`. -pub fn hash_node(node: &Node, path_prefix: &Path) -> TrieHash { +pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { match node { Node::Branch(node) => { // All child hashes should be filled in. // TODO danlaine: Enforce this with the type system. - debug_assert!(node - .children - .iter() - .all(|c| !matches!(c, Some(Child::Node(_))))); + #[cfg(debug_assertions)] + debug_assert!( + node.children + .iter() + .all(|c| !matches!(c, Some(Child::Node(_)))), + "branch children: {:?}", + node.children + ); NodeAndPrefix { node: node.as_ref(), prefix: path_prefix, @@ -39,7 +42,7 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> TrieHash { /// when hashing the node. The node is at the given `path_prefix`. pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { // Key, 3 options, value digest - let est_len = node.partial_path().len() + path_prefix.len() + 3 + TrieHash::default().len(); + let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::default().len(); let mut buf = Vec::with_capacity(est_len); match node { Node::Branch(node) => { @@ -62,15 +65,22 @@ pub trait HasUpdate { fn update>(&mut self, data: T); } -impl HasUpdate for Sha256 { +impl HasUpdate for Vec { fn update>(&mut self, data: T) { - sha2::Digest::update(self, data) + self.extend(data.as_ref().iter().copied()); } } -impl HasUpdate for Vec { +// TODO: make it work with any size SmallVec +// impl + smallvec::Array> HasUpdate for SmallVec { +// fn update>(&mut self, data: U) { +// self.extend(data.as_ref()); +// } +// } + +impl HasUpdate for SmallVec<[u8; 32]> { fn update>(&mut self, data: T) { - self.extend(data.as_ref()); + self.extend(data.as_ref().iter().copied()); } } @@ -82,69 +92,46 @@ pub enum ValueDigest { /// TODO this variant will be used when we deserialize a proof node /// from a remote Firewood instance. The serialized proof node they /// send us may the hash of the value, not the value itself. - _Hash(T), + Hash(T), +} + +impl Deref for ValueDigest { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + ValueDigest::Value(value) => value, + ValueDigest::Hash(hash) => hash, + } + } } /// A node in the trie that can be hashed. pub trait Hashable { /// The key of the node where each byte is a nibble. fn key(&self) -> impl Iterator + Clone; + /// The partial path of this node + #[cfg(feature = "ethhash")] + fn partial_path(&self) -> impl Iterator + Clone; /// The node's value or hash. fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. - fn children(&self) -> impl Iterator + Clone; + fn children(&self) -> impl Iterator + Clone; } /// A preimage of a hash. pub trait Preimage { /// Returns the hash of this preimage. - fn to_hash(&self) -> TrieHash; + fn to_hash(&self) -> HashType; /// Write this hash preimage to `buf`. fn write(&self, buf: &mut impl HasUpdate); } -// Implement Preimage for all types that implement Hashable -impl Preimage for T { - fn to_hash(&self) -> TrieHash { - let mut hasher = Sha256::new(); - self.write(&mut hasher); - hasher.finalize().into() - } - - fn write(&self, buf: &mut impl HasUpdate) { - let children = self.children(); - - let num_children = children.clone().count() as u64; - add_varint_to_buf(buf, num_children); - - for (index, hash) in children { - add_varint_to_buf(buf, index as u64); - buf.update(hash); - } - - // Add value digest (if any) to hash pre-image - add_value_digest_to_buf(buf, self.value_digest()); - - // Add key length (in bits) to hash pre-image - let mut key = self.key(); - // let mut key = key.as_ref().iter(); - let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; - add_varint_to_buf(buf, key_bit_len); - - // Add key to hash pre-image - while let Some(high_nibble) = key.next() { - let low_nibble = key.next().unwrap_or(0); - let byte = (high_nibble << 4) | low_nibble; - buf.update([byte]); - } - } -} - trait HashableNode { fn partial_path(&self) -> impl Iterator + Clone; fn value(&self) -> Option<&[u8]>; - fn children_iter(&self) -> impl Iterator + Clone; + fn children_iter(&self) -> impl Iterator + Clone; } impl HashableNode for BranchNode { @@ -156,7 +143,7 @@ impl HashableNode for BranchNode { self.value.as_deref() } - fn children_iter(&self) -> impl Iterator + Clone { + fn children_iter(&self) -> impl Iterator + Clone { self.children_iter() } } @@ -170,7 +157,7 @@ impl HashableNode for LeafNode { Some(&self.value) } - fn children_iter(&self) -> impl Iterator + Clone { + fn children_iter(&self) -> impl Iterator + Clone { iter::empty() } } @@ -180,7 +167,7 @@ struct NodeAndPrefix<'a, N: HashableNode> { prefix: &'a Path, } -impl<'a, N: HashableNode> From> for TrieHash { +impl<'a, N: HashableNode> From> for HashType { fn from(node: NodeAndPrefix<'a, N>) -> Self { node.to_hash() } @@ -195,58 +182,16 @@ impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { .chain(self.node.partial_path()) } + #[cfg(feature = "ethhash")] + fn partial_path(&self) -> impl Iterator + Clone { + self.node.partial_path() + } + fn value_digest(&self) -> Option> { self.node.value().map(ValueDigest::Value) } - fn children(&self) -> impl Iterator + Clone { + fn children(&self) -> impl Iterator + Clone { self.node.children_iter() } } - -fn add_value_digest_to_buf>( - buf: &mut H, - value_digest: Option>, -) { - let Some(value_digest) = value_digest else { - let value_exists: u8 = 0; - buf.update([value_exists]); - return; - }; - - let value_exists: u8 = 1; - buf.update([value_exists]); - - match value_digest { - ValueDigest::Value(value) if value.as_ref().len() >= 32 => { - let hash = Sha256::digest(value); - add_len_and_value_to_buf(buf, hash); - } - ValueDigest::Value(value) => { - add_len_and_value_to_buf(buf, value); - } - ValueDigest::_Hash(hash) => { - add_len_and_value_to_buf(buf, hash); - } - } -} - -#[inline] -/// Writes the length of `value` and `value` to `buf`. -fn add_len_and_value_to_buf>(buf: &mut H, value: V) { - let value_len = value.as_ref().len(); - buf.update([value_len as u8]); - buf.update(value); -} - -#[inline] -/// Encodes `value` as a varint and writes it to `buf`. -fn add_varint_to_buf(buf: &mut H, value: u64) { - let mut buf_arr = [0u8; MAX_VARINT_SIZE]; - let len = value.encode_var(&mut buf_arr); - buf.update( - buf_arr - .get(..len) - .expect("length is always less than MAX_VARINT_SIZE"), - ); -} diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs new file mode 100644 index 000000000000..469a4317a4c0 --- /dev/null +++ b/storage/src/hashers/ethhash.rs @@ -0,0 +1,310 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// Ethereum compatible hashing algorithm. + +use std::iter::once; + +use crate::logger::warn; +use crate::{ + hashednode::HasUpdate, + logger::{trace, trace_enabled}, + HashType, Hashable, Preimage, TrieHash, ValueDigest, +}; +use bitfield::bitfield; +use bytes::BytesMut; +use sha3::{Digest, Keccak256}; +use smallvec::SmallVec; + +use rlp::{Rlp, RlpStream}; + +impl HasUpdate for Keccak256 { + fn update>(&mut self, data: T) { + sha3::Digest::update(self, data) + } +} + +// Takes a set of nibbles and converts them to a set of bytes that we can hash +// The input consists of nibbles, but there may be an invalid nibble at the end of 0x10 +// which indicates that we need to set bit 5 of the first output byte +// The input may also have an odd number of nibbles, in which case the first output byte +// will have bit 4 set and the low nibble will be the low nibble of the first byte +// Restated: 00ABCCCC +// 0 is always 0 +// A is 1 if this is a leaf +// B is 1 if the input had an odd number of nibbles +// CCCC is the first nibble if B is 1, otherwise it is all 0s + +fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec<[u8; 32]> { + // This is a bitfield that represents the first byte of the output, documented above + bitfield! { + struct CompactFirstByte(u8); + impl Debug; + impl new; + u8; + is_leaf, set_is_leaf: 5; + odd_nibbles, set_odd_nibbles: 4; + low_nibble, set_low_nibble: 3, 0; + } + + let nibbles = nibbles.as_ref(); + + // nibble_pairs points to the first nibble that will be combined with the next nibble + // so we skip the first byte if there's an odd length and set the odd_nibbles bit to true + let (nibble_pairs, first_byte) = if nibbles.len() & 1 == 1 { + let low_nibble = nibbles[0]; + debug_assert!(low_nibble < 16); + ( + &nibbles[1..], + CompactFirstByte::new(is_leaf, true, low_nibble), + ) + } else { + (nibbles, CompactFirstByte::new(is_leaf, false, 0)) + }; + + // at this point, we can be sure that nibble_pairs has an even length + debug_assert!(nibble_pairs.len() % 2 == 0); + + // now assemble everything: the first byte, and the nibble pairs compacted back together + once(first_byte.0) + .chain( + nibble_pairs + .chunks(2) + .map(|chunk| (chunk[0] << 4) | chunk[1]), + ) + .collect() +} + +impl Preimage for T { + fn to_hash(&self) -> HashType { + // first collect the thing that would be hashed, and if it's smaller than a hash, + // just use it directly + let mut collector = SmallVec::with_capacity(32); + self.write(&mut collector); + + if trace_enabled() { + if self.key().size_hint().0 == 64 { + trace!("SIZE WAS 64 {}", hex::encode(&collector)); + } else { + trace!( + "SIZE WAS {1} {0}", + hex::encode(&collector), + self.key().size_hint().0 + ); + } + } + + if collector.len() >= 32 { + HashType::Hash(Keccak256::digest(collector).into()) + } else { + HashType::Rlp(collector) + } + } + + fn write(&self, buf: &mut impl HasUpdate) { + let is_account = self.key().size_hint().0 == 64; + trace!("is_account: {is_account}"); + + let children = self.children().count(); + + if children == 0 { + // since there are no children, this must be a leaf + // we append two items, the partial_path, encoded, and the value + // note that leaves must always have a value, so we know there + // will be 2 items + + let mut rlp = RlpStream::new_list(2); + + rlp.append(&&*nibbles_to_eth_compact( + self.partial_path().collect::>(), + true, + )); + + if is_account { + // we are a leaf that is at depth 32 + match self.value_digest() { + Some(ValueDigest::Value(bytes)) => { + let new_hash = Keccak256::digest(rlp::NULL_RLP).as_slice().to_vec(); + let bytes_mut = BytesMut::from(bytes); + if let Some(result) = replace_hash(bytes_mut, new_hash) { + rlp.append(&&*result); + } else { + rlp.append(&bytes); + } + } + Some(ValueDigest::Hash(hash)) => { + rlp.append(&hash); + } + None => { + rlp.append_empty_data(); + } + }; + } else { + match self.value_digest() { + Some(ValueDigest::Value(bytes)) => rlp.append(&bytes), + Some(ValueDigest::Hash(hash)) => rlp.append(&hash), + None => rlp.append_empty_data(), + }; + } + + let bytes = rlp.out(); + if crate::logger::trace_enabled() { + trace!( + "partial path {:?}", + hex::encode(self.partial_path().collect::>()) + ); + trace!("serialized leaf-rlp: {:?}", hex::encode(&bytes)); + } + buf.update(&bytes); + } else { + // for a branch, there are always 16 children and a value + // Child::None we encode as RLP empty_data (0x80) + let mut rlp = RlpStream::new_list(17); + let mut child_iter = self.children().peekable(); + for index in 0..=15 { + if let Some(&(child_index, digest)) = child_iter.peek() { + if child_index == index { + match digest { + HashType::Hash(hash) => rlp.append(&hash.as_slice()), + HashType::Rlp(rlp_bytes) => rlp.append_raw(rlp_bytes, 1), + }; + child_iter.next(); + } else { + rlp.append_empty_data(); + } + } else { + // exhausted all indexes + rlp.append_empty_data(); + } + } + + if let Some(digest) = self.value_digest() { + if is_account { + rlp.append_empty_data(); + } else { + rlp.append(&*digest); + } + } else { + rlp.append_empty_data(); + } + let bytes = rlp.out(); + if crate::logger::trace_enabled() { + trace!("pass 1 bytes {:02X?}", hex::encode(&bytes)); + } + + // we've collected all the children in bytes + + let updated_bytes = if is_account { + // need to get the value again + if let Some(ValueDigest::Value(rlp_encoded_bytes)) = self.value_digest() { + // rlp_encoded__bytes needs to be decoded + // TODO: Handle corruption + // needs to be the hash of the RLP encoding of the root node that + // would have existed here (instead of this account node) + // the "root node" is actually this branch node iff there is + // more than one child. If there is only one child, then the + // child is actually the root node, so we need the hash of that + // child here. + let replacement_hash = if children == 1 { + // we need to treat this child like it's a root node, so the partial path is + // actually one longer than it is reported + match self.children().next().expect("we know there is one").1 { + HashType::Hash(hash) => hash.clone(), + HashType::Rlp(rlp_bytes) => { + let mut rlp = RlpStream::new_list(2); + rlp.append(&&*nibbles_to_eth_compact( + self.partial_path().collect::>(), + true, + )); + rlp.append_raw(rlp_bytes, 1); + let bytes = rlp.out(); + TrieHash::from(Keccak256::digest(bytes)) + } + } + } else { + TrieHash::from(Keccak256::digest(bytes)) + }; + trace!("replacement hash {:?}", hex::encode(&replacement_hash)); + + let bytes = replace_hash(rlp_encoded_bytes, replacement_hash) + .unwrap_or_else(|| BytesMut::from(rlp_encoded_bytes)); + trace!("updated encoded value {:02X?}", hex::encode(&bytes)); + bytes + } else { + // treat like non-account since it didn't have a value + warn!("Account node without value"); + bytes.as_ref().into() + } + } else { + bytes.as_ref().into() + }; + + let partial_path = self.partial_path().collect::>(); + if partial_path.is_empty() { + if crate::logger::trace_enabled() { + trace!("pass 2=bytes {:02X?}", hex::encode(&updated_bytes)); + } + buf.update(updated_bytes); + } else { + let mut final_bytes = RlpStream::new_list(2); + final_bytes.append(&&*nibbles_to_eth_compact(partial_path, is_account)); + // if the RLP is short enough, we can use it as-is, otherwise we hash it + // to make the maximum length 32 bytes + if updated_bytes.len() > 31 && !is_account { + let hashed_bytes = Keccak256::digest(updated_bytes); + final_bytes.append(&hashed_bytes.as_slice()); + } else { + final_bytes.append(&updated_bytes); + } + let final_bytes = final_bytes.out(); + if crate::logger::trace_enabled() { + trace!("pass 2 bytes {:02X?}", hex::encode(&final_bytes)); + } + buf.update(final_bytes); + } + } + } +} + +// TODO: we could be super fancy and just plunk the correct bytes into the existing BytesMut +fn replace_hash, U: AsRef<[u8]>>(bytes: T, new_hash: U) -> Option { + // rlp_encoded_bytes needs to be decoded + let rlp = Rlp::new(bytes.as_ref()); + let mut list = rlp.as_list().ok()?; + let replace = list.get_mut(2)?; + *replace = Vec::from(new_hash.as_ref()); + + if trace_enabled() { + trace!("inbound bytes: {}", hex::encode(bytes.as_ref())); + trace!("list length was {}", list.len()); + trace!("replacement hash {:?}", hex::encode(&new_hash)); + } + + let mut rlp = RlpStream::new_list(list.len()); + for item in list { + rlp.append(&item); + } + let bytes = rlp.out(); + if trace_enabled() { + trace!("updated encoded value {:02X?}", hex::encode(&bytes)); + } + Some(bytes) +} + +#[cfg(test)] +mod test { + use test_case::test_case; + + #[test_case(&[], false, &[0x00])] + #[test_case(&[], true, &[0x20])] + #[test_case(&[1, 2, 3, 4, 5], false, &[0x11, 0x23, 0x45])] + #[test_case(&[0, 1, 2, 3, 4, 5], false, &[0x00, 0x01, 0x23, 0x45])] + #[test_case(&[15, 1, 12, 11, 8], true, &[0x3f, 0x1c, 0xb8])] + #[test_case(&[0, 15, 1, 12, 11, 8], true, &[0x20, 0x0f, 0x1c, 0xb8])] + fn test_hex_to_compact(hex: &[u8], has_value: bool, expected_compact: &[u8]) { + assert_eq!( + &*super::nibbles_to_eth_compact(hex, has_value), + expected_compact + ); + } +} diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs new file mode 100644 index 000000000000..4123783c5c89 --- /dev/null +++ b/storage/src/hashers/merkledb.rs @@ -0,0 +1,101 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::hashednode::{HasUpdate, Hashable, Preimage}; +use crate::{TrieHash, ValueDigest}; +/// Merkledb compatible hashing algorithm. +use integer_encoding::VarInt; +use sha2::{Digest, Sha256}; + +const MAX_VARINT_SIZE: usize = 10; +const BITS_PER_NIBBLE: u64 = 4; + +impl HasUpdate for Sha256 { + fn update>(&mut self, data: T) { + sha2::Digest::update(self, data) + } +} + +impl Preimage for T { + fn to_hash(&self) -> TrieHash { + let mut hasher = Sha256::new(); + + self.write(&mut hasher); + hasher.finalize().into() + } + + fn write(&self, buf: &mut impl HasUpdate) { + let children = self.children(); + + let num_children = children.clone().count() as u64; + add_varint_to_buf(buf, num_children); + + for (index, hash) in children { + add_varint_to_buf(buf, index as u64); + buf.update(hash); + } + + // Add value digest (if any) to hash pre-image + add_value_digest_to_buf(buf, self.value_digest()); + + // Add key length (in bits) to hash pre-image + let mut key = self.key(); + // let mut key = key.as_ref().iter(); + let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; + add_varint_to_buf(buf, key_bit_len); + + // Add key to hash pre-image + while let Some(high_nibble) = key.next() { + let low_nibble = key.next().unwrap_or(0); + let byte = (high_nibble << 4) | low_nibble; + buf.update([byte]); + } + } +} + +fn add_value_digest_to_buf>( + buf: &mut H, + value_digest: Option>, +) { + let Some(value_digest) = value_digest else { + let value_exists: u8 = 0; + buf.update([value_exists]); + return; + }; + + let value_exists: u8 = 1; + buf.update([value_exists]); + + match value_digest { + ValueDigest::Value(value) if value.as_ref().len() >= 32 => { + let hash = Sha256::digest(value); + add_len_and_value_to_buf(buf, hash); + } + ValueDigest::Value(value) => { + add_len_and_value_to_buf(buf, value); + } + ValueDigest::Hash(hash) => { + add_len_and_value_to_buf(buf, hash); + } + } +} + +#[inline] +/// Writes the length of `value` and `value` to `buf`. +fn add_len_and_value_to_buf>(buf: &mut H, value: V) { + let value_len = value.as_ref().len(); + buf.update([value_len as u8]); + buf.update(value); +} + +#[inline] +/// Encodes `value` as a varint and writes it to `buf`. +fn add_varint_to_buf(buf: &mut H, value: u64) { + let mut buf_arr = [0u8; MAX_VARINT_SIZE]; + let len = value.encode_var(&mut buf_arr); + buf.update( + buf_arr + .get(..len) + .expect("length is always less than MAX_VARINT_SIZE"), + ); +} diff --git a/storage/src/hashers/mod.rs b/storage/src/hashers/mod.rs new file mode 100644 index 000000000000..786b0a90333d --- /dev/null +++ b/storage/src/hashers/mod.rs @@ -0,0 +1,7 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#[cfg(feature = "ethhash")] +mod ethhash; +#[cfg(not(feature = "ethhash"))] +mod merkledb; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 9577aec0fbf7..515c31cb5122 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -12,6 +12,7 @@ //! A [NodeStore] is backed by a [ReadableStorage] which is persisted storage. mod hashednode; +mod hashers; mod linear; mod node; mod nodestore; @@ -24,7 +25,7 @@ pub mod logger; pub use hashednode::{hash_node, hash_preimage, Hashable, Preimage, ValueDigest}; pub use linear::{ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; -pub use node::{BranchNode, Child, LeafNode, Node, PathIterItem}; +pub use node::{branch::HashType, BranchNode, Child, LeafNode, Node, PathIterItem}; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, diff --git a/storage/src/logger.rs b/storage/src/logger.rs index 21779044190e..410ed4b8b811 100644 --- a/storage/src/logger.rs +++ b/storage/src/logger.rs @@ -8,8 +8,14 @@ #[cfg(feature = "logger")] pub use log::{debug, error, info, trace, warn}; +/// Returns true if the trace log level is enabled +#[cfg(feature = "logger")] +pub fn trace_enabled() -> bool { + log::log_enabled!(log::Level::Trace) +} + #[cfg(not(feature = "logger"))] -pub use noop_logger::{debug, error, info, trace, warn}; +pub use noop_logger::{debug, error, info, trace, trace_enabled, warn}; #[cfg(not(feature = "logger"))] mod noop_logger { @@ -24,4 +30,10 @@ mod noop_logger { pub use noop as info; pub use noop as trace; pub use noop as warn; + + /// trace_enabled for a noop logger is always false + #[inline] + pub fn trace_enabled() -> bool { + false + } } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 1572c1b3e425..66569578da9d 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -5,8 +5,25 @@ use serde::ser::SerializeStruct as _; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use crate::{LeafNode, LinearAddress, Node, Path, TrieHash}; -use std::fmt::{Debug, Error as FmtError, Formatter}; +use crate::{LeafNode, LinearAddress, Node, Path}; +use std::fmt::{Debug, Formatter}; + +/// The type of a hash. For ethereum compatible hashes, this might be a RLP encoded +/// value if it's small enough to fit in less than 32 bytes. For merkledb compatible +/// hashes, it's always a TrieHash. +#[cfg(feature = "ethhash")] +pub type HashType = ethhash::HashOrRlp; + +#[cfg(not(feature = "ethhash"))] +/// The type of a hash. For non-ethereum compatible hashes, this is always a TrieHash. +pub type HashType = crate::TrieHash; + +pub(crate) trait Serializable { + fn serialized_bytes(&self) -> Vec; + fn from_reader(reader: R) -> Result + where + Self: Sized; +} #[derive(PartialEq, Eq, Clone, Debug)] #[repr(C)] @@ -16,7 +33,139 @@ pub enum Child { /// or allocated space in storage for it yet. Node(Node), /// We know the child's address and hash. - AddressWithHash(LinearAddress, TrieHash), + AddressWithHash(LinearAddress, HashType), +} + +#[cfg(feature = "ethhash")] +mod ethhash { + use serde::{Deserialize, Serialize}; + use sha2::Digest as _; + use sha3::Keccak256; + use smallvec::SmallVec; + use std::{ + fmt::{Display, Formatter}, + io::Read, + ops::Deref as _, + }; + + use crate::TrieHash; + + use super::Serializable; + + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] + pub enum HashOrRlp { + Hash(TrieHash), + // TODO: this slice is never larger than 32 bytes so smallvec is probably not our best container + // the length is stored in a `usize` but it could be in a `u8` and it will never overflow + Rlp(SmallVec<[u8; 32]>), + } + + impl HashOrRlp { + pub fn as_slice(&self) -> &[u8] { + self.deref() + } + + pub(crate) fn into_triehash(self) -> TrieHash { + self.into() + } + } + + impl Serializable for HashOrRlp { + fn serialized_bytes(&self) -> Vec { + match self { + HashOrRlp::Hash(h) => std::iter::once(0) + .chain(h.as_ref().iter().copied()) + .collect(), + HashOrRlp::Rlp(r) => { + debug_assert!(!r.is_empty()); + debug_assert!(r.len() < 32); + std::iter::once(r.len() as u8) + .chain(r.iter().copied()) + .collect() + } + } + } + + fn from_reader(mut reader: R) -> Result { + let mut bytes = [0; 32]; + reader.read_exact(&mut bytes[0..1])?; + match bytes[0] { + 0 => { + reader.read_exact(&mut bytes)?; + Ok(HashOrRlp::Hash(TrieHash::from(bytes))) + } + len if len < 32 => { + reader.read_exact(&mut bytes[0..len as usize])?; + Ok(HashOrRlp::Rlp(SmallVec::from_buf_and_len( + bytes, + len as usize, + ))) + } + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid RLP length", + )), + } + } + } + + impl From for TrieHash { + fn from(val: HashOrRlp) -> Self { + match val { + HashOrRlp::Hash(h) => h, + HashOrRlp::Rlp(r) => Keccak256::digest(&r).into(), + } + } + } + + impl From for HashOrRlp { + fn from(val: TrieHash) -> Self { + HashOrRlp::Hash(val) + } + } + + impl From<[u8; 32]> for HashOrRlp { + fn from(value: [u8; 32]) -> Self { + HashOrRlp::Hash(TrieHash::into(value.into())) + } + } + + impl AsRef<[u8]> for HashOrRlp { + fn as_ref(&self) -> &[u8] { + match self { + HashOrRlp::Hash(h) => h.as_ref(), + HashOrRlp::Rlp(r) => r.as_ref(), + } + } + } + + impl std::ops::Deref for HashOrRlp { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + match self { + HashOrRlp::Hash(h) => h, + HashOrRlp::Rlp(r) => r.deref(), + } + } + } + + impl Display for HashOrRlp { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + HashOrRlp::Hash(h) => write!(f, "{}", h), + HashOrRlp::Rlp(r) => { + let width = f.precision().unwrap_or(32); + write!(f, "{:.*}", width, hex::encode(r)) + } + } + } + } + + impl Default for HashOrRlp { + fn default() -> Self { + HashOrRlp::Hash(TrieHash::default()) + } + } } #[derive(PartialEq, Eq, Clone)] @@ -44,7 +193,7 @@ impl Serialize for BranchNode { state.serialize_field("partial_path", &self.partial_path)?; state.serialize_field("value", &self.value)?; - let children: SmallVec<[(u8, LinearAddress, &TrieHash); Self::MAX_CHILDREN]> = self + let children: SmallVec<[(_, _, _); Self::MAX_CHILDREN]> = self .children .iter() .enumerate() @@ -71,7 +220,7 @@ impl<'de> Deserialize<'de> for BranchNode { struct SerializedBranchNode { partial_path: Path, value: Option>, - children: SmallVec<[(u8, LinearAddress, TrieHash); BranchNode::MAX_CHILDREN]>, + children: SmallVec<[(u8, LinearAddress, HashType); BranchNode::MAX_CHILDREN]>, } let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?; @@ -91,7 +240,7 @@ impl<'de> Deserialize<'de> for BranchNode { } impl Debug for BranchNode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "[BranchNode")?; write!(f, r#" path="{:?}""#, self.partial_path)?; @@ -100,7 +249,7 @@ impl Debug for BranchNode { None => {} Some(Child::Node(_)) => {} //TODO Some(Child::AddressWithHash(addr, hash)) => { - write!(f, "({i:?}: address={addr:?} hash={})", hex::encode(hash),)? + write!(f, "({i:?}: address={addr:?} hash={})", hash)? } } } @@ -145,7 +294,7 @@ impl BranchNode { } /// Returns (index, hash) for each child that has a hash set. - pub fn children_iter(&self) -> impl Iterator + Clone { + pub fn children_iter(&self) -> impl Iterator + Clone { self.children.iter().enumerate().filter_map( #[allow(clippy::indexing_slicing)] |(i, child)| match child { diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 94723ff20e1b..f6b229ed2f50 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use bitfield::bitfield; +use branch::Serializable as _; use enum_as_inner::EnumAsInner; use integer_encoding::{VarIntReader as _, VarIntWriter as _}; use serde::{Deserialize, Serialize}; @@ -10,14 +11,14 @@ use std::io::{Error, ErrorKind, Read, Write}; use std::num::NonZero; use std::vec; -mod branch; +pub mod branch; mod leaf; pub mod path; pub use branch::{BranchNode, Child}; pub use leaf::LeafNode; -use crate::{Path, SharedNode}; +use crate::{HashType, Path, SharedNode}; /// A node, either a Branch or Leaf @@ -251,7 +252,7 @@ impl Node { encoded.push(prefix); encoded.push(first_byte.0); #[cfg(feature = "branch_factor_256")] - encoded.extend_one((childcount % BranchNode::MAX_CHILDREN) as u8); + encoded.push((childcount % BranchNode::MAX_CHILDREN) as u8); // encode the partial path, including the length if it didn't fit above if b.partial_path.0.len() > MAX_ENCODED_PARTIAL_PATH_LEN { @@ -274,7 +275,7 @@ impl Node { for (_, child) in child_iter { if let Child::AddressWithHash(address, hash) = child { encoded.extend_from_slice(&address.get().to_ne_bytes()); - encoded.extend_from_slice(hash); + encoded.extend_from_slice(&hash.serialized_bytes()); } else { panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); } @@ -286,7 +287,7 @@ impl Node { .expect("writing to vec should succeed"); if let Child::AddressWithHash(address, hash) = child { encoded.extend_from_slice(&address.get().to_ne_bytes()); - encoded.extend_from_slice(hash); + encoded.extend_from_slice(&hash.serialized_bytes()); } else { panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); } @@ -392,12 +393,11 @@ impl Node { serialized.read_exact(&mut address_buf)?; let address = u64::from_ne_bytes(address_buf); - let mut hash = [0u8; 32]; - serialized.read_exact(&mut hash)?; + let hash = HashType::from_reader(&mut serialized)?; *child = Some(Child::AddressWithHash( NonZero::new(address).ok_or(Error::other("zero address in child"))?, - hash.into(), + hash, )); } } else { @@ -410,12 +410,11 @@ impl Node { serialized.read_exact(&mut address_buf)?; let address = u64::from_ne_bytes(address_buf); - let mut hash = [0u8; 32]; - serialized.read_exact(&mut hash)?; + let hash = HashType::from_reader(&mut serialized)?; children[position] = Some(Child::AddressWithHash( NonZero::new(address).ok_or(Error::other("zero address in child"))?, - hash.into(), + hash, )); } } @@ -482,7 +481,7 @@ mod test { let mut serialized = Vec::new(); node.as_bytes(0, &mut serialized); - #[cfg(not(feature = "branch_factor_256"))] // TODO: enable this test for branch_factor_256 + #[cfg(not(any(feature = "branch_factor_256", feature = "ethhash")))] assert_eq!(serialized.len(), expected_length); let mut cursor = Cursor::new(&serialized); cursor.set_position(1); diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index e0809057fd5a..4718a674a020 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -53,7 +53,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; use std::io::{Error, ErrorKind, Write}; -use std::iter::once; use std::mem::{offset_of, take}; use std::num::NonZeroU64; use std::ops::Deref; @@ -61,7 +60,9 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::{ByteCounter, Node}; -use crate::{CacheReadStrategy, Child, FileBacked, Path, ReadableStorage, SharedNode, TrieHash}; +use crate::{ + CacheReadStrategy, Child, FileBacked, HashType, Path, ReadableStorage, SharedNode, TrieHash, +}; use super::linear::WritableStorage; @@ -278,7 +279,7 @@ impl NodeStore { if let Some(root_address) = nodestore.header.root_address { let node = nodestore.read_node_from_disk(root_address, "open"); let root_hash = node.map(|n| hash_node(&n, &Path(Default::default())))?; - nodestore.kind.root_hash = Some(root_hash); + nodestore.kind.root_hash = Some(root_hash.into_triehash()); } Ok(nodestore) @@ -877,9 +878,63 @@ impl NodeStore, S> { mut node: Node, path_prefix: &mut Path, new_nodes: &mut HashMap, - ) -> (LinearAddress, TrieHash) { + #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, + ) -> Result<(LinearAddress, HashType), Error> { // If this is a branch, find all unhashed children and recursively call hash_helper on them. + trace!("hashing {node:?} at {path_prefix:?}"); if let Node::Branch(ref mut b) = node { + // special case code for ethereum hashes at the account level + #[cfg(feature = "ethhash")] + let make_fake_root = if path_prefix.0.len() + b.partial_path.0.len() == 64 { + // looks like we're at an account branch + // tally up how many hashes we need to deal with + let (unhashed, mut hashed) = b.children.iter_mut().enumerate().fold( + (Vec::new(), Vec::new()), + |(mut unhashed, mut hashed), (idx, child)| { + match child { + None => {} + Some(Child::AddressWithHash(a, h)) => hashed.push((idx, (a, h))), + Some(Child::Node(node)) => unhashed.push((idx, node)), + } + (unhashed, hashed) + }, + ); + if hashed.len() == 1 && !unhashed.is_empty() { + // Previously, only one child was hashed, but now there are more, so + // we must recompute the hash of that child without the magic prefix + trace!("Rehashing account branch: hashed {hashed:?} unhashed {unhashed:?}"); + let invalidated_node = hashed.first_mut().expect("hashed is not empty"); + let hashable_node = self.read_node(*invalidated_node.1 .0)?.deref().clone(); + let original_length = path_prefix.len(); + path_prefix.0.extend( + b.partial_path + .0 + .iter() + .copied() + .chain(std::iter::once(invalidated_node.0 as u8)), + ); + + let hash = hash_node(&hashable_node, path_prefix); + path_prefix.0.truncate(original_length); + *invalidated_node.1 .1 = hash; + } + // handle the single-child case for an account special below + if hashed.is_empty() && unhashed.len() == 1 { + Some(unhashed.last().expect("only one").0 as u8) + } else { + None + } + } else { + // not a single child + None + }; + + // branch children -- 1. 1 child, already hashed, 2. >1 child, already hashed, + // 3. 1 hashed child, 1 unhashed child + // 4. 0 hashed, 1 unhashed <-- handle child special + // 5. 1 hashed, >0 unhashed <-- rehash case + // everything already hashed + for (nibble, child) in b.children.iter_mut().enumerate() { // if this is already hashed, we're done if matches!(child, Some(Child::AddressWithHash(_, _))) { @@ -898,23 +953,53 @@ impl NodeStore, S> { // Hash this child and update // we extend and truncate path_prefix to reduce memory allocations let original_length = path_prefix.len(); - path_prefix - .0 - .extend(b.partial_path.0.iter().copied().chain(once(nibble as u8))); + path_prefix.0.extend(b.partial_path.0.iter().copied()); + #[cfg(feature = "ethhash")] + if make_fake_root.is_none() { + // we don't push the nibble there is only one unhashed child and + // we're on an account + path_prefix.0.push(nibble as u8); + } + #[cfg(not(feature = "ethhash"))] + path_prefix.0.push(nibble as u8); + + #[cfg(feature = "ethhash")] + let (child_addr, child_hash) = + self.hash_helper(child_node, path_prefix, new_nodes, make_fake_root)?; + #[cfg(not(feature = "ethhash"))] + let (child_addr, child_hash) = + self.hash_helper(child_node, path_prefix, new_nodes)?; - let (child_addr, child_hash) = self.hash_helper(child_node, path_prefix, new_nodes); *child = Some(Child::AddressWithHash(child_addr, child_hash)); path_prefix.0.truncate(original_length); } } // At this point, we either have a leaf or a branch with all children hashed. + // if the encoded child hash <32 bytes then we use that RLP + + #[cfg(feature = "ethhash")] + // if we have a child that is the only child of an account branch, we will hash this child as if it + // is a root node. This means we have to take the nibble from the parent and prefix it to the partial path + let hash = if let Some(nibble) = fake_root_extra_nibble { + let mut fake_root = node.clone(); + trace!("old node: {:?}", fake_root); + fake_root.update_partial_path(Path::from_nibbles_iterator( + std::iter::once(nibble).chain(fake_root.partial_path().0.iter().copied()), + )); + trace!("new node: {:?}", fake_root); + hash_node(&fake_root, path_prefix) + } else { + hash_node(&node, path_prefix) + }; + #[cfg(not(feature = "ethhash"))] let hash = hash_node(&node, path_prefix); - let (addr, size) = self.allocate_node(&node).expect("TODO handle error"); + + let (addr, size) = self.allocate_node(&node)?; new_nodes.insert(addr, (size, node.into())); - (addr, hash) + Ok((addr, hash)) } } @@ -1072,10 +1157,12 @@ impl NodeStore, FileBacked> { } } -impl From> +impl TryFrom> for NodeStore, S> { - fn from(val: NodeStore) -> Self { + type Error = std::io::Error; + + fn try_from(val: NodeStore) -> Result { let NodeStore { header, kind, @@ -1096,12 +1183,17 @@ impl From> let Some(root) = kind.root else { // This trie is now empty. nodestore.header.root_address = None; - return nodestore; + return Ok(nodestore); }; // Hashes the trie and returns the address of the new root. let mut new_nodes = HashMap::new(); - let (root_addr, root_hash) = nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes); + #[cfg(feature = "ethhash")] + let (root_addr, root_hash) = + nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes, None)?; + #[cfg(not(feature = "ethhash"))] + let (root_addr, root_hash) = + nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes)?; nodestore.header.root_address = Some(root_addr); let immutable_proposal = @@ -1110,10 +1202,10 @@ impl From> new: new_nodes, deleted: immutable_proposal.deleted, parent: immutable_proposal.parent, - root_hash: Some(root_hash), + root_hash: Some(root_hash.into_triehash()), }); - nodestore + Ok(nodestore) } } @@ -1161,7 +1253,7 @@ where if let Some(root_addr) = self.header.root_address { let root_node = self.read_node(root_addr)?; let root_hash = hash_node(&root_node, &Path::new()); - Ok(Some((root_addr, root_hash))) + Ok(Some((root_addr, root_hash.into_triehash()))) } else { Ok(None) } @@ -1178,7 +1270,7 @@ where if let Some(root_addr) = self.header.root_address { let root_node = self.read_node(root_addr)?; let root_hash = hash_node(&root_node, &Path::new()); - Ok(Some((root_addr, root_hash))) + Ok(Some((root_addr, root_hash.into_triehash()))) } else { Ok(None) } @@ -1248,13 +1340,13 @@ mod tests { // create an empty r1, check that it's parent is the empty committed version let r1 = NodeStore::new(base).unwrap(); - let r1: Arc, _>> = Arc::new(r1.into()); + let r1: Arc, _>> = Arc::new(r1.try_into().unwrap()); let parent: DynGuard> = r1.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Committed(None))); // create an empty r2, check that it's parent is the proposed version r1 let r2: NodeStore = NodeStore::new(r1.clone()).unwrap(); - let r2: Arc, _>> = Arc::new(r2.into()); + let r2: Arc, _>> = Arc::new(r2.try_into().unwrap()); let parent: DynGuard> = r2.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Proposed(_))); @@ -1335,7 +1427,7 @@ mod tests { node_store.mut_root().replace(giant_leaf); - let immutable = NodeStore::, _>::from(node_store); + let immutable = NodeStore::, _>::try_from(node_store).unwrap(); println!("{:?}", immutable); // should not be reached, but need to consume immutable to avoid optimization removal } } diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 7aae43e87b8a..393632ec62f0 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -1,13 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt::{self, Debug}; +use std::fmt::{self, Debug, Display, Formatter}; use serde::de::Visitor; use serde::{Deserialize, Serialize}; use sha2::digest::generic_array::GenericArray; use sha2::digest::typenum; +use crate::node::branch::Serializable; + /// A hash value inside a merkle trie /// We use the same type as returned by sha2 here to avoid copies #[derive(PartialEq, Eq, Clone, Default, Hash)] @@ -34,7 +36,13 @@ impl AsRef<[u8]> for TrieHash { impl Debug for TrieHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let width = f.precision().unwrap_or_default(); + let width = f.precision().unwrap_or(64); + write!(f, "{:.*}", width, hex::encode(self.0)) + } +} +impl Display for TrieHash { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + let width = f.precision().unwrap_or(64); write!(f, "{:.*}", width, hex::encode(self.0)) } } @@ -45,6 +53,20 @@ impl From<[u8; 32]> for TrieHash { } } +impl TryFrom<&[u8]> for TrieHash { + type Error = &'static str; + + fn try_from(value: &[u8]) -> Result { + if value.len() == Self::len() { + let mut hash = TrieHash::default(); + hash.0.copy_from_slice(value); + Ok(hash) + } else { + Err("Invalid length") + } + } +} + impl From> for TrieHash { fn from(value: GenericArray) -> Self { TrieHash(value) @@ -56,6 +78,27 @@ impl TrieHash { pub(crate) const fn len() -> usize { std::mem::size_of::() } + + /// Some code needs a TrieHash even though it only has a HashType. + /// This function is a no-op, as HashType is a TrieHash in this context. + pub const fn into_triehash(self) -> Self { + self + } +} + +impl Serializable for TrieHash { + fn serialized_bytes(&self) -> Vec { + self.0.to_vec() + } + + fn from_reader(mut reader: R) -> Result + where + Self: Sized, + { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + Ok(TrieHash::from(buf)) + } } impl Serialize for TrieHash { From dd3769ed8174fb8aceb5d6fbe5a1d32169ab3a4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 09:28:36 -0700 Subject: [PATCH 0672/1053] build(deps): update typed-builder requirement from 0.20.0 to 0.21.0 (#815) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d4d1b79cd731..0c77ba7af267 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -25,7 +25,7 @@ serde = { version = "1.0" } sha2 = "0.10.8" test-case = "3.3.1" thiserror = "2.0.3" -typed-builder = "0.20.0" +typed-builder = "0.21.0" bincode = "1.3.3" integer-encoding = "4.0.0" smallvec = "1.6.1" From d905cb164e889738191d80272994bd46cdd212a2 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 24 Mar 2025 11:02:56 -0700 Subject: [PATCH 0673/1053] Add logging to ffi (#820) --- ffi/Cargo.toml | 4 ++++ ffi/src/lib.rs | 4 ++++ firewood/src/manager.rs | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index d2154f7e0858..f70d47be172a 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -14,6 +14,10 @@ metrics-util = "0.19.0" chrono = "0.4.39" oxhttp = "0.3.0" coarsetime = "0.1.35" +env_logger = {version = "0.11.7", optional = true} + +[features] +logger = ["dep:env_logger", "firewood/logger"] [build-dependencies] cbindgen = "0.28.0" diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 72a44b37da10..3f91ad0f3e63 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -253,6 +253,10 @@ unsafe fn common_create( metrics_port: u16, cfg: DbConfig, ) -> *mut Db { + #[cfg(feature = "logger")] + let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .try_init(); + let path = unsafe { CStr::from_ptr(path) }; let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); if metrics_port > 0 { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 97bfaea72e2b..2a2ae3ec1ec9 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -9,9 +9,10 @@ use std::num::NonZero; use std::path::PathBuf; use std::sync::Arc; -use storage::logger::warn; +use storage::logger::{trace, trace_enabled, warn}; use typed_builder::TypedBuilder; +use crate::merkle::Merkle; use crate::v2::api::HashKey; pub use storage::CacheReadStrategy; @@ -205,6 +206,11 @@ impl RevisionManager { proposal.commit_reparent(p); } + if trace_enabled() { + let _merkle = Merkle::from(committed); + trace!("{}", _merkle.dump()?); + } + Ok(()) } } From 4855af8db29077fb4566f65a86cc8313f2f0ed74 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 24 Mar 2025 13:41:13 -0700 Subject: [PATCH 0674/1053] Fix branch delete with value (#821) --- firewood/src/merkle.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index db9cbdcfcf74..e87c640ff352 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -763,8 +763,8 @@ impl Merkle> { return Ok((Some(leaf), removed_value)); }; - if children_iter.next().is_some() { - // The branch has more than 1 child. Return the branch. + // if there is more than one child or the branch has a value, return it + if branch.value.is_some() || children_iter.next().is_some() { return Ok((Some(node), removed_value)); } @@ -913,8 +913,8 @@ impl Merkle> { return Ok(Some(leaf)); }; - if children_iter.next().is_some() { - // The branch has more than 1 child. Return the branch. + // if there is more than one child or the branch has a value, return it + if branch.value.is_some() || children_iter.next().is_some() { return Ok(Some(node)); } @@ -1648,6 +1648,29 @@ mod tests { assert_eq!(*got, val_2); } + #[test] + fn test_delete_one_child_with_branch_value() { + let mut merkle = create_in_memory_merkle(); + // insert a parent with a value + merkle.insert(&[0], Box::new([42u8])).unwrap(); + // insert child1 with a value + merkle.insert(&[0, 1], Box::new([43u8])).unwrap(); + // insert child2 with a value + merkle.insert(&[0, 2], Box::new([44u8])).unwrap(); + + // now delete one of the children + let deleted = merkle.remove(&[0, 1]).unwrap(); + assert_eq!(deleted, Some([43u8].to_vec().into_boxed_slice())); + + // make sure the parent still has the correct value + let got = merkle.get_value(&[0]).unwrap().unwrap(); + assert_eq!(*got, [42u8]); + + // and check the remaining child + let other_child = merkle.get_value(&[0, 2]).unwrap().unwrap(); + assert_eq!(*other_child, [44u8]); + } + // #[test] // fn single_key_proof_with_one_node() { // let mut merkle = create_in_memory_merkle(); From fda91dc295f04a1f83b7dda04a8acbd097d0ba9f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 25 Mar 2025 12:23:46 -0700 Subject: [PATCH 0675/1053] Fix ethhash rehash logic (#823) --- storage/src/nodestore.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 4718a674a020..bd969f225fb8 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -899,21 +899,21 @@ impl NodeStore, S> { (unhashed, hashed) }, ); - if hashed.len() == 1 && !unhashed.is_empty() { - // Previously, only one child was hashed, but now there are more, so - // we must recompute the hash of that child without the magic prefix - trace!("Rehashing account branch: hashed {hashed:?} unhashed {unhashed:?}"); + trace!("hashed {hashed:?} unhashed {unhashed:?}"); + if hashed.len() == 1 { + // we were left with one hashed node that must be rehashed let invalidated_node = hashed.first_mut().expect("hashed is not empty"); - let hashable_node = self.read_node(*invalidated_node.1 .0)?.deref().clone(); + let mut hashable_node = self.read_node(*invalidated_node.1 .0)?.deref().clone(); let original_length = path_prefix.len(); - path_prefix.0.extend( - b.partial_path - .0 - .iter() - .copied() - .chain(std::iter::once(invalidated_node.0 as u8)), - ); - + path_prefix.0.extend(b.partial_path.0.iter().copied()); + if !unhashed.is_empty() { + path_prefix.0.push(invalidated_node.0 as u8); + } else { + hashable_node.update_partial_path(Path::from_nibbles_iterator( + std::iter::once(invalidated_node.0 as u8) + .chain(hashable_node.partial_path().0.iter().copied()), + )); + } let hash = hash_node(&hashable_node, path_prefix); path_prefix.0.truncate(original_length); *invalidated_node.1 .1 = hash; From c78c2bcbf6eb037f5cc72357800b32c911aab559 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 25 Mar 2025 12:30:11 -0700 Subject: [PATCH 0676/1053] Remove an allocation (#822) --- firewood/src/merkle.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e87c640ff352..4de55e1ba60d 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -784,11 +784,9 @@ impl Merkle> { // The child's partial path is the concatenation of its (now removed) parent, // its (former) child index, and its partial path. - let branch_partial_path = - std::mem::replace(&mut branch.partial_path, Path::new()); - let child_partial_path = Path::from_nibbles_iterator( - branch_partial_path + branch + .partial_path .iter() .chain(once(&(child_index as u8))) .chain(child.partial_path().iter()) @@ -934,11 +932,9 @@ impl Merkle> { // The child's partial path is the concatenation of its (now removed) parent, // its (former) child index, and its partial path. - let branch_partial_path = - std::mem::replace(&mut branch.partial_path, Path::new()); - let child_partial_path = Path::from_nibbles_iterator( - branch_partial_path + branch + .partial_path .iter() .chain(once(&(child_index as u8))) .chain(child.partial_path().iter()) From 1ff4a784e2b436c36543664439ff53bcd412955b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 25 Mar 2025 12:51:52 -0700 Subject: [PATCH 0677/1053] Use jemalloc in ffi (#824) --- ffi/Cargo.toml | 1 + ffi/src/lib.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index f70d47be172a..0b5735e3416d 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -15,6 +15,7 @@ chrono = "0.4.39" oxhttp = "0.3.0" coarsetime = "0.1.35" env_logger = {version = "0.11.7", optional = true} +tikv-jemallocator = "0.6.0" [features] logger = ["dep:env_logger", "firewood/logger"] diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 3f91ad0f3e63..f5ad2e09bb9e 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -13,6 +13,9 @@ mod metrics_setup; use metrics::counter; +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + #[derive(Debug)] #[repr(C)] pub struct Value { From 972a50d451aa4ff17796f2c04694a0eb7e14f16e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 26 Mar 2025 08:50:59 -0700 Subject: [PATCH 0678/1053] Fixes build --release warning (#828) --- storage/src/hashednode.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 5359b2f6c044..8e400d6018e3 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -8,7 +8,7 @@ use std::{ use smallvec::SmallVec; -use crate::{BranchNode, Child, HashType, LeafNode, Node, Path}; +use crate::{BranchNode, HashType, LeafNode, Node, Path}; /// Returns the hash of `node`, which is at the given `path_prefix`. pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { @@ -20,7 +20,7 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { debug_assert!( node.children .iter() - .all(|c| !matches!(c, Some(Child::Node(_)))), + .all(|c| !matches!(c, Some(crate::Child::Node(_)))), "branch children: {:?}", node.children ); From 1c26efad39b67b25ce12c5fa65449bcdd3370902 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:00:05 +0000 Subject: [PATCH 0679/1053] build(deps): update tonic requirement from 0.12.1 to 0.13.0 (#826) --- grpc-testtool/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index db238dc8c4e5..3071936aeba8 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -19,7 +19,7 @@ bench = false firewood = { version = "0.0.4", path = "../firewood" } prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.12.1", features = ["tls"] } +tonic = { version = "0.13.0", features = ["tls-ring"] } tracing = { version = "0.1.40" } clap = { version = "4.5.0", features = ["derive"] } log = "0.4.20" @@ -29,7 +29,7 @@ serde_json = "1.0.113" serde = { version = "1.0.196", features = ["derive"] } [build-dependencies] -tonic-build = "0.12.1" +tonic-build = "0.13.0" [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} From 90f393199d6a48b603a8855b10d6c794fe882d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:10:46 +0000 Subject: [PATCH 0680/1053] build(deps): update opentelemetry requirement from 0.28.0 to 0.29.0 (#816) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- benchmark/Cargo.toml | 8 ++++---- benchmark/src/main.rs | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 3caba5a09d04..e50f4f0de7d3 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -19,10 +19,10 @@ tikv-jemallocator = "0.6.0" env_logger = "0.11.5" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } -fastrace-opentelemetry = { version = "0.9.0" } -opentelemetry-otlp = { version = "0.28.0", features = ["grpc-tonic"] } -opentelemetry = "0.28.0" -opentelemetry_sdk = "0.28.0" +fastrace-opentelemetry = { version = "0.10.0" } +opentelemetry-otlp = { version = "0.29.0", features = ["grpc-tonic"] } +opentelemetry = "0.29.0" +opentelemetry_sdk = "0.29.0" strum = "0.27.0" [features] diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index f50111004dfb..e9651dc5a918 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -194,9 +194,7 @@ async fn main() -> Result<(), Box> { .with_tonic() .with_endpoint("http://127.0.0.1:4317".to_string()) .with_protocol(opentelemetry_otlp::Protocol::Grpc) - .with_timeout(Duration::from_secs( - opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, - )) + .with_timeout(opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT) .build() .expect("initialize oltp exporter"), SpanKind::Server, From a2344470ab585009153b1651753a49f79bd6e67a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 26 Mar 2025 11:33:20 -0700 Subject: [PATCH 0681/1053] return nil, nil on not found (#829) --- ffi/firewood.go | 7 ++++++- ffi/firewood_test.go | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 92295e89b53b..ebe7688af99c 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -133,11 +133,16 @@ func extractBytesThenFree(v *C.struct_Value) []byte { } // Get retrieves the value for the given key. It always returns a nil error. +// If the key is not found, the return value will be (nil, nil). func (db *Database) Get(key []byte) ([]byte, error) { values, cleanup := newValueFactory() defer cleanup() val := C.fwd_get(db.handle, values.from(key)) - return extractBytesThenFree(&val), nil + bytes := extractBytesThenFree(&val) + if len(bytes) == 0 { + return nil, nil + } + return bytes, nil } // Root returns the current root hash of the trie. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index af5d0f94b6e6..a15ad2a34475 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -72,6 +72,13 @@ func TestInsert(t *testing.T) { assert.Equal(t, val, string(got), "Recover lone batch-inserted value") } +func TestGetNonExistent(t *testing.T) { + db := newTestDatabase(t) + got, err := db.Get([]byte("non-existent")) + require.NoError(t, err) + assert.Nil(t, got) +} + func keyForTest(i int) []byte { return []byte("key" + strconv.Itoa(i)) } From 1934a40fde9d18b2ae8e3beaf90316076ae1f94d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 26 Mar 2025 15:45:51 -0700 Subject: [PATCH 0682/1053] Rust 2024 (#799) --- benchmark/Cargo.toml | 3 +- benchmark/src/create.rs | 2 +- benchmark/src/main.rs | 2 +- benchmark/src/tenkrandom.rs | 2 +- ffi/Cargo.toml | 3 +- ffi/src/lib.rs | 29 +++++++-------- ffi/src/metrics_setup.rs | 17 +++++---- firewood/Cargo.toml | 2 +- firewood/benches/hashops.rs | 16 ++++----- firewood/examples/insert.rs | 4 +-- firewood/src/db.rs | 2 +- firewood/src/manager.rs | 8 ----- firewood/src/merkle.rs | 48 ++++++++++++++----------- firewood/src/proof.rs | 2 +- firewood/src/range_proof.rs | 6 ++-- firewood/src/stream.rs | 10 +++--- firewood/src/v2/api.rs | 1 - firewood/src/v2/emptydb.rs | 2 +- firewood/tests/common/mod.rs | 4 +-- firewood/tests/db.rs | 8 ++--- firewood/tests/v2api.rs | 2 +- fwdctl/Cargo.toml | 3 +- fwdctl/src/create.rs | 2 +- fwdctl/tests/cli.rs | 2 +- grpc-testtool/Cargo.toml | 3 +- grpc-testtool/benches/insert.rs | 2 +- grpc-testtool/src/bin/process-server.rs | 4 +-- grpc-testtool/src/lib.rs | 6 ++-- grpc-testtool/src/service.rs | 2 +- grpc-testtool/src/service/database.rs | 2 +- grpc-testtool/src/service/db.rs | 2 +- grpc-testtool/src/service/process.rs | 2 +- storage/Cargo.toml | 3 +- storage/benches/serializer.rs | 10 +++--- storage/src/hashers/ethhash.rs | 2 +- storage/src/lib.rs | 4 +-- storage/src/linear/filebacked.rs | 3 +- storage/src/linear/memory.rs | 2 +- storage/src/linear/mod.rs | 3 -- storage/src/node/branch.rs | 10 +++--- storage/src/node/mod.rs | 9 +++-- storage/src/node/path.rs | 13 ++++--- storage/src/nodestore.rs | 33 +++++++++-------- storage/src/trie_hash.rs | 10 ++---- 44 files changed, 149 insertions(+), 156 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index e50f4f0de7d3..198f23aa73dd 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "benchmark" version = "0.1.0" -edition = "2021" +edition = "2024" +rust-version = "1.85.0" [dependencies] firewood = { path = "../firewood" } diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index 060f5e0d3b80..1c2490996509 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -5,7 +5,7 @@ use std::error::Error; use std::time::Instant; use fastrace::prelude::SpanContext; -use fastrace::{func_path, Span}; +use fastrace::{Span, func_path}; use firewood::db::Db; use firewood::v2::api::{Db as _, Proposal as _}; use log::info; diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index e9651dc5a918..e5fe98500d97 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -31,8 +31,8 @@ use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; use fastrace::collector::Config; -use opentelemetry::trace::SpanKind; use opentelemetry::InstrumentationScope; +use opentelemetry::trace::SpanKind; use opentelemetry_otlp::{SpanExporter, WithExportConfig}; use opentelemetry_sdk::Resource; diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 78c9ab50e60c..35b62f4243e0 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -61,7 +61,7 @@ fn generate_deletes(start: u64, count: u64) -> impl Iterator Value { let db = unsafe { db.as_ref() }.expect("db should be non-null"); let root = db.root_hash_sync(); @@ -62,7 +62,7 @@ pub unsafe extern "C" fn fwd_get(db: *mut Db, key: Value) -> Value { /// A `KeyValue` struct that represents a key-value pair in the database. #[repr(C)] #[allow(unused)] -#[no_mangle] +#[unsafe(no_mangle)] pub struct KeyValue { key: Value, value: Value, @@ -82,7 +82,7 @@ pub struct KeyValue { /// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. /// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. /// -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Value { let start = coarsetime::Instant::now(); let db = unsafe { db.as_ref() }.expect("db should be non-null"); @@ -119,7 +119,7 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key /// /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `db` is a valid pointer returned by `open_db` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { let db = unsafe { db.as_ref() }.expect("db should be non-null"); hash(db) @@ -161,15 +161,16 @@ impl From> for Value { /// /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `value` is a valid pointer. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_free_value(value: *const Value) { - if (*value).len == 0 { + let value = unsafe { &*value as &Value }; + if value.len == 0 { return; } let recreated_box = unsafe { Box::from_raw(std::slice::from_raw_parts_mut( - (*value).data as *mut u8, - (*value).len, + value.data as *mut u8, + value.len, )) }; drop(recreated_box); @@ -208,7 +209,7 @@ pub struct CreateOrOpenArgs { /// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. /// The caller must call `close` to free the memory associated with the returned database handle. /// -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { let cfg = DbConfig::builder() .truncate(true) @@ -218,7 +219,7 @@ pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { args.strategy, )) .build(); - common_create(args.path, args.metrics_port, cfg) + unsafe { common_create(args.path, args.metrics_port, cfg) } } /// Open a database with the given cache size and maximum number of revisions @@ -238,7 +239,7 @@ pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { /// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. /// The caller must call `close` to free the memory associated with the returned database handle. /// -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *mut Db { let cfg = DbConfig::builder() .truncate(false) @@ -248,7 +249,7 @@ pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *mut Db { args.strategy, )) .build(); - common_create(args.path, args.metrics_port, cfg) + unsafe { common_create(args.path, args.metrics_port, cfg) } } unsafe fn common_create( @@ -299,7 +300,7 @@ fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> Revision /// # Arguments /// /// * `db` - The database handle to close, previously returned from a call to open_db() -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_close_db(db: *mut Db) { - let _ = Box::from_raw(db); + let _ = unsafe { Box::from_raw(db) }; } diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index edacb8d48628..d833a9f18a5b 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -1,14 +1,13 @@ -use std::{ - collections::HashSet, - io::Write, - net::Ipv6Addr, - ops::Deref, - sync::{atomic::Ordering, Arc, Once}, - time::SystemTime, -}; +use std::collections::HashSet; +use std::io::Write; +use std::net::Ipv6Addr; +use std::ops::Deref; +use std::sync::atomic::Ordering; +use std::sync::{Arc, Once}; +use std::time::SystemTime; -use oxhttp::model::{Body, Response, StatusCode}; use oxhttp::Server; +use oxhttp::model::{Body, Response, StatusCode}; use std::net::Ipv4Addr; use std::time::Duration; diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 0c77ba7af267..340cc06bd793 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "firewood" version = "0.0.4" -edition = "2021" +edition = "2024" authors = [ "Ted Yin (@Determinant) ", "Dan Sover (@exdx) ", diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 8e9e3aa2f146..23904cdf88c0 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -4,7 +4,7 @@ // hash benchmarks; run with 'cargo bench' use criterion::profiler::Profiler; -use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use firewood::db::{BatchOp, DbConfig}; use firewood::merkle::Merkle; use firewood::v2::api::{Db as _, Proposal as _}; @@ -26,12 +26,12 @@ enum FlamegraphProfiler { Active(ProfilerGuard<'static>), } -fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U + '_ { +fn file_error_panic(path: &Path) -> impl FnOnce(T) -> U { |_| panic!("Error on file `{}`", path.display()) } impl Profiler for FlamegraphProfiler { - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &Path) { if let Self::Init(frequency) = self { let guard = ProfilerGuard::new(*frequency).unwrap(); @@ -39,16 +39,15 @@ impl Profiler for FlamegraphProfiler { } } - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) { std::fs::create_dir_all(benchmark_dir).unwrap(); let filename = "firewood-flamegraph.svg"; let flamegraph_path = benchmark_dir.join(filename); - #[allow(clippy::unwrap_used)] let flamegraph_file = File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] if let Self::Active(profiler) = self { profiler .report() @@ -62,7 +61,6 @@ impl Profiler for FlamegraphProfiler { // This benchmark peeks into the merkle layer and times how long it takes // to insert NKEYS with a key length of KEYSIZE -#[allow(clippy::unwrap_used)] fn bench_merkle(criterion: &mut Criterion) { let mut rng = StdRng::seed_from_u64(1234); @@ -87,7 +85,7 @@ fn bench_merkle(criterion: &mut Criter (merkle, keys) }, - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] |(mut merkle, keys)| { keys.into_iter() .for_each(|key| merkle.insert(&key, Box::new(*b"v")).unwrap()); @@ -98,7 +96,7 @@ fn bench_merkle(criterion: &mut Criter }); } -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] fn bench_db(criterion: &mut Criterion) { const KEY_LEN: usize = 4; let mut rng = StdRng::seed_from_u64(1234); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 763e2e7911fd..16db1aaefd7f 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -43,7 +43,7 @@ struct Args { fn string_to_range(input: &str) -> Result, Box> { //::Err> { let parts: Vec<&str> = input.split('-').collect(); - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] match parts.len() { 1 => Ok(input.parse()?..=input.parse()?), 2 => Ok(parts[0].parse()?..=parts[1].parse()?), @@ -99,7 +99,7 @@ async fn main() -> Result<(), Box> { let verify = get_keys_to_verify(&batch, args.read_verify_percent); - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] let proposal = db.propose(batch).await.unwrap(); proposal.commit().await?; verify_keys(&db, verify).await?; diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7c3a8a450a8a..841df7e7e4bf 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -442,7 +442,7 @@ impl Proposal<'_> { } } #[cfg(test)] -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] mod test { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 2a2ae3ec1ec9..fddc10f659da 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -1,8 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![allow(dead_code)] - use std::collections::{HashMap, VecDeque}; use std::io::Error; use std::num::NonZero; @@ -44,9 +42,6 @@ pub(crate) struct RevisionManager { /// Maximum number of revisions to keep on disk max_revisions: usize, - /// The underlying file storage - filebacked: Arc, - /// The list of revisions that are on disk; these point to the different roots /// stored in the filebacked storage. historical: VecDeque, @@ -57,8 +52,6 @@ pub(crate) struct RevisionManager { #[derive(Debug, thiserror::Error)] pub(crate) enum RevisionManagerError { - #[error("The proposal cannot be committed since a sibling was committed")] - SiblingCommitted, #[error( "The proposal cannot be committed since it is not a direct child of the most recent commit" )] @@ -86,7 +79,6 @@ impl RevisionManager { }; let mut manager = Self { max_revisions: config.max_revisions, - filebacked: storage, historical: VecDeque::from([nodestore.clone()]), by_hash: Default::default(), proposals: Default::default(), diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4de55e1ba60d..e978e6d6cb91 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -56,7 +56,6 @@ macro_rules! write_attributes { ) .map_err(|e| Error::other(e))?; } - #[allow(clippy::unnecessary_to_owned)] if !$value.is_empty() { match std::str::from_utf8($value) { Ok(string) if string.chars().all(char::is_alphanumeric) => { @@ -109,7 +108,7 @@ fn get_helper( .expect("index is in bounds") { None => Ok(None), - Some(Child::Node(ref child)) => get_helper(nodestore, child, remaining_key), + Some(Child::Node(child)) => get_helper(nodestore, child, remaining_key), Some(Child::AddressWithHash(addr, _)) => { let child = nodestore.read_node(*addr)?; get_helper(nodestore, &child, remaining_key) @@ -175,7 +174,7 @@ impl Merkle { [const { None }; BranchNode::MAX_CHILDREN]; if let Some(branch) = root.as_branch() { // TODO danlaine: can we avoid indexing? - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] for (i, hash) in branch.children_iter() { child_hashes[i] = Some(hash.clone()); } @@ -211,10 +210,12 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } + #[allow(dead_code)] pub(super) fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { MerkleKeyValueStream::from(&self.nodestore) } + #[allow(dead_code)] pub(super) fn key_value_iter_from_key>( &self, key: K, @@ -277,7 +278,6 @@ impl Merkle { // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested - #[allow(clippy::unwrap_used)] key_values.extend( stream .take(limit.unwrap_or(usize::MAX)) @@ -501,7 +501,7 @@ impl Merkle> { // ... (key may be below) ... (key is below) match node { Node::Branch(ref mut branch) => { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) { None => { @@ -603,7 +603,7 @@ impl Merkle> { /// Removes the value associated with the given `key` from the subtrie rooted at `node`. /// Returns the new root of the subtrie and the value that was removed, if any. /// Each element of `key` is 1 nibble. - #[allow(clippy::type_complexity)] + #[expect(clippy::type_complexity)] fn remove_helper( &mut self, mut node: Node, @@ -632,7 +632,7 @@ impl Merkle> { (None, None) => { // 1. The node is at `key` match &mut node { - Node::Branch(ref mut branch) => { + Node::Branch(branch) => { let Some(removed_value) = branch.value.take() else { // The branch has no value. Return the node as is. return Ok((Some(node), None)); @@ -660,7 +660,7 @@ impl Merkle> { } else { // The branch's only child becomes the root of this subtrie. let mut child = match child { - Child::Node(ref mut child_node) => std::mem::take(child_node), + Child::Node(child_node) => std::mem::take(child_node), Child::AddressWithHash(addr, _) => { self.nodestore.read_for_update(*addr)? } @@ -722,7 +722,7 @@ impl Merkle> { // we found a non-matching leaf node, so the value does not exist Node::Leaf(_) => Ok((Some(node), None)), Node::Branch(ref mut branch) => { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) { None => { @@ -847,7 +847,7 @@ impl Merkle> { // 1. The node is at `key`, or we're just above it // so we can start deleting below here match &mut node { - Node::Branch(ref mut branch) => { + Node::Branch(branch) => { if branch.value.is_some() { // a KV pair was in the branch itself *deleted += 1; @@ -870,7 +870,7 @@ impl Merkle> { match node { Node::Leaf(_) => Ok(Some(node)), Node::Branch(ref mut branch) => { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] let child = match std::mem::take(&mut branch.children[child_index as usize]) { None => { @@ -969,7 +969,7 @@ impl Merkle> { None => continue, }; match child { - Node::Branch(ref mut child_branch) => { + Node::Branch(child_branch) => { self.delete_children(child_branch, deleted)?; } Node::Leaf(_) => { @@ -984,9 +984,9 @@ impl Merkle> { /// Returns an iterator where each element is the result of combining /// 2 nibbles of `nibbles`. If `nibbles` is odd length, panics in /// debug mode and drops the final nibble in release mode. -pub fn nibbles_to_bytes_iter(nibbles: &[u8]) -> impl Iterator + '_ { +pub fn nibbles_to_bytes_iter(nibbles: &[u8]) -> impl Iterator { debug_assert_eq!(nibbles.len() & 1, 0); - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] nibbles.chunks_exact(2).map(|p| (p[0] << 4) | p[1]) } @@ -1021,11 +1021,11 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { } #[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] +#[expect(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; use rand::rngs::StdRng; - use rand::{rng, Rng, SeedableRng}; + use rand::{Rng, SeedableRng, rng}; use storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; use test_case::test_case; @@ -1327,9 +1327,11 @@ mod tests { { // Test that the proof is invalid when the hash is different - assert!(proof - .verify(key, Some(value), &TrieHash::default()) - .is_err()); + assert!( + proof + .verify(key, Some(value), &TrieHash::default()) + .is_err() + ); } } } @@ -1742,7 +1744,6 @@ mod tests { #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")] #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")] #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")] - #[allow(unused_variables)] fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) { // TODO: get the hashes from merkledb and verify compatibility with branch factor 256 #[cfg(not(feature = "branch_factor_256"))] @@ -2015,7 +2016,12 @@ mod tests { let _ = write!(s, "{:02x}", b); s }); - assert_eq!(new_hash.clone(), expected_hash.0, "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{:?}\n", expected_hash.0); + assert_eq!( + new_hash.clone(), + expected_hash.0, + "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{:?}\n", + expected_hash.0 + ); } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 2d017f275b4b..8f627c8bd00e 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -111,7 +111,7 @@ impl From for ProofNode { if let Some(branch) = item.node.as_branch() { // TODO danlaine: can we avoid indexing? - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] for (i, hash) in branch.children_iter() { child_hashes[i] = Some(hash.clone()); } diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index 5705ddb3bca8..7070ba427947 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -9,10 +9,10 @@ use crate::proof::Proof; /// are in the trie with a given root hash. #[derive(Debug)] pub struct RangeProof, V: AsRef<[u8]>, H: Hashable> { - #[allow(dead_code)] + #[expect(dead_code)] pub(crate) start_proof: Option>, - #[allow(dead_code)] + #[expect(dead_code)] pub(crate) end_proof: Option>, - #[allow(dead_code)] + #[expect(dead_code)] pub(crate) key_values: Box<[(K, V)]>, } diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 1377f2449b6d..134f9745bc64 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -245,7 +245,7 @@ fn get_iterator_intial_state( ), }); - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] let child = &branch.children[next_unmatched_key_nibble as usize]; node = match child { None => return Ok(NodeStreamState::Iterating { iter_stack }), @@ -466,7 +466,7 @@ impl Iterator for PathIterator<'_, '_, T> { })); }; - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] let child = &branch.children[next_unmatched_key_nibble as usize]; match child { None => { @@ -555,7 +555,7 @@ where /// Returns an iterator that returns (`pos`,`child`) for each non-empty child of `branch`, /// where `pos` is the position of the child in `branch`'s children array. -fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator { +fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator + use<> { branch .children .clone() @@ -581,7 +581,7 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { } #[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] +#[expect(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use std::sync::Arc; @@ -942,7 +942,7 @@ mod tests { }; // we iterate twice because we should get a None then start over - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { let next = stream.next().await.map(|kv| { let (k, v) = kv.unwrap(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index f440f7242472..5bca2a3117e9 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -149,7 +149,6 @@ impl From for Error { match err { RevisionManagerError::IO(io_err) => Error::IO(io_err), RevisionManagerError::NotLatest => Error::NotLatest, - RevisionManagerError::SiblingCommitted => Error::SiblingCommitted, } } } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 12fe17a0de29..0aa27d063f6c 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -100,7 +100,7 @@ impl Stream for EmptyStreamer { } #[cfg(test)] -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] mod tests { use super::*; use crate::v2::api::{BatchOp, Proposal}; diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs index 39f04f9c78cf..deee2e775927 100644 --- a/firewood/tests/common/mod.rs +++ b/firewood/tests/common/mod.rs @@ -26,7 +26,7 @@ pub struct TestDb { } impl TestDbCreator { - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] pub async fn _create(self) -> TestDb { let path = self.path.clone().unwrap_or_else(|| { let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") @@ -70,7 +70,7 @@ impl TestDb { impl Drop for TestDb { fn drop(&mut self) { if !self.preserve_on_drop { - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] remove_file(self.creator.path.as_ref().unwrap()).unwrap(); } } diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs index c6216462740d..bf1cb80c3c8c 100644 --- a/firewood/tests/db.rs +++ b/firewood/tests/db.rs @@ -14,7 +14,7 @@ macro_rules! _kv_dump { // #[ignore = "unimplemented"] // #[tokio::test(flavor = "multi_thread")] -// #[allow(clippy::unwrap_used)] +// #[expect(clippy::unwrap_used)] // async fn test_basic_metrics() { // let cfg = DbConfig::builder(); @@ -49,7 +49,7 @@ macro_rules! _kv_dump { // #[ignore = "unimplemented"] // #[tokio::test(flavor = "multi_thread")] -// #[allow(clippy::unwrap_used)] +// #[expect(clippy::unwrap_used)] // async fn test_revisions() { // use rand::{rngs::StdRng, Rng, SeedableRng}; // let cfg = DbConfig::builder().build(); @@ -126,7 +126,7 @@ macro_rules! _kv_dump { // #[ignore = "unimplemented"] // #[tokio::test(flavor = "multi_thread")] -// #[allow(clippy::unwrap_used)] +// #[expect(clippy::unwrap_used)] // async fn create_db_issue_proof() { // let cfg = DbConfig::builder(); @@ -202,7 +202,7 @@ macro_rules! _assert_val { // #[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] // #[tokio::test(flavor = "multi_thread")] -// #[allow(clippy::unwrap_used)] +// #[expect(clippy::unwrap_used)] // async fn db_proposal() -> Result<(), api::Error> { // let cfg = DbConfig::builder().build(); diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs index ab69c5bf7796..a26ccd97b064 100644 --- a/firewood/tests/v2api.rs +++ b/firewood/tests/v2api.rs @@ -5,7 +5,7 @@ pub mod common; // #[ignore = "unimplemented"] // #[tokio::test(flavor = "multi_thread")] -// #[allow(clippy::unwrap_used)] +// #[expect(clippy::unwrap_used)] // async fn smoke() -> Result<(), Box> { // let cfg = DbConfig::builder().truncate(true).build(); // let db = TestDbCreator::builder() diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index b65f646d58c4..f3317bb6de15 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "fwdctl" version = "0.0.4" -edition = "2021" +edition = "2024" +rust-version = "1.85.0" [dependencies] firewood = { version = "0.0.4", path = "../firewood" } diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 2d55d103a25f..5a685d7c9519 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use clap::{value_parser, Args}; +use clap::{Args, value_parser}; use firewood::db::{Db, DbConfig}; use firewood::v2::api; diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 13b902658419..657b7452b7d0 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 3071936aeba8..9c71c9da375b 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "rpc" version = "0.0.4" -edition = "2021" +edition = "2024" +rust-version = "1.85.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs index 1cb0e11e3635..017a6ed4d901 100644 --- a/grpc-testtool/benches/insert.rs +++ b/grpc-testtool/benches/insert.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; use rand::distributions::Alphanumeric; use rand::{Rng, SeedableRng}; use std::borrow::BorrowMut as _; diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs index c323a16c6dc7..d5fe5cd7207d 100644 --- a/grpc-testtool/src/bin/process-server.rs +++ b/grpc-testtool/src/bin/process-server.rs @@ -4,11 +4,11 @@ use chrono::Local; use clap::Parser; use env_logger::Builder; -use log::{info, LevelFilter}; +use log::{LevelFilter, info}; +use rpc::DatabaseService; use rpc::process_server::process_server_service_server::ProcessServerServiceServer; use rpc::rpcdb::database_server::DatabaseServer as RpcServer; use rpc::sync::db_server::DbServer as SyncServer; -use rpc::DatabaseService; use serde::Deserialize; use std::error::Error; use std::io::Write; diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 3819ac80a61f..1d29ed4c30b3 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -2,17 +2,17 @@ // See the file LICENSE.md for licensing terms. pub mod sync { - #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] + #![expect(clippy::missing_const_for_fn)] tonic::include_proto!("sync"); } pub mod rpcdb { - #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] + #![expect(clippy::missing_const_for_fn)] tonic::include_proto!("rpcdb"); } pub mod process_server { - #![allow(clippy::unwrap_used, clippy::missing_const_for_fn)] + #![expect(clippy::missing_const_for_fn)] tonic::include_proto!("process"); } diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index d317e031b1de..4d2679596b9b 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -7,8 +7,8 @@ use firewood::v2::api::{Db as _, Error}; use std::collections::HashMap; use std::ops::Deref; use std::path::Path; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; use tokio::sync::Mutex; use tonic::Status; diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index ce7563a409cd..1d8f12a58f11 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -13,7 +13,7 @@ use crate::rpcdb::{ }; use firewood::v2::api::{BatchOp, Db as _, DbView as _, Proposal as _}; -use tonic::{async_trait, Request, Response, Status}; +use tonic::{Request, Response, Status, async_trait}; #[async_trait] impl Database for DatabaseService { diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index 976f395473bb..7e038b82a04a 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -9,7 +9,7 @@ use crate::sync::{ GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, VerifyChangeProofResponse, }; -use tonic::{async_trait, Request, Response, Status}; +use tonic::{Request, Response, Status, async_trait}; #[async_trait] impl DbServerTrait for Database { diff --git a/grpc-testtool/src/service/process.rs b/grpc-testtool/src/service/process.rs index b98e7f7589de..226cc9afbc35 100644 --- a/grpc-testtool/src/service/process.rs +++ b/grpc-testtool/src/service/process.rs @@ -1,4 +1,4 @@ -use tonic::{async_trait, Request, Response, Status}; +use tonic::{Request, Response, Status, async_trait}; use crate::process_server::process_server_service_server::ProcessServerService as ProcessTrait; diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 51ed7b2e370a..8bace88b666a 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "storage" version = "0.0.4" -edition = "2021" +edition = "2024" +rust-version = "1.85.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 9cb76d1b348e..2e873dc394ce 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -8,7 +8,7 @@ use std::os::raw::c_int; use bincode::Options; use criterion::profiler::Profiler; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use pprof::ProfilerGuard; use smallvec::SmallVec; use storage::{LeafNode, Node, Path}; @@ -23,12 +23,12 @@ enum FlamegraphProfiler { Active(ProfilerGuard<'static>), } -fn file_error_panic(path: &FsPath) -> impl FnOnce(T) -> U + '_ { +fn file_error_panic(path: &FsPath) -> impl FnOnce(T) -> U { |_| panic!("Error on file `{}`", path.display()) } impl Profiler for FlamegraphProfiler { - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] fn start_profiling(&mut self, _benchmark_id: &str, _benchmark_dir: &FsPath) { if let Self::Init(frequency) = self { let guard = ProfilerGuard::new(*frequency).unwrap(); @@ -36,16 +36,14 @@ impl Profiler for FlamegraphProfiler { } } - #[allow(clippy::unwrap_used)] + #[expect(clippy::unwrap_used)] fn stop_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &FsPath) { std::fs::create_dir_all(benchmark_dir).unwrap(); let filename = "firewood-flamegraph.svg"; let flamegraph_path = benchmark_dir.join(filename); - #[allow(clippy::unwrap_used)] let flamegraph_file = File::create(&flamegraph_path).unwrap_or_else(file_error_panic(&flamegraph_path)); - #[allow(clippy::unwrap_used)] if let Self::Active(profiler) = self { profiler .report() diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 469a4317a4c0..cb3afe8c9c4e 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -7,9 +7,9 @@ use std::iter::once; use crate::logger::warn; use crate::{ + HashType, Hashable, Preimage, TrieHash, ValueDigest, hashednode::HasUpdate, logger::{trace, trace_enabled}, - HashType, Hashable, Preimage, TrieHash, ValueDigest, }; use bitfield::bitfield; use bytes::BytesMut; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 515c31cb5122..dc744abc98bb 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -22,10 +22,10 @@ mod trie_hash; pub mod logger; // re-export these so callers don't need to know where they are -pub use hashednode::{hash_node, hash_preimage, Hashable, Preimage, ValueDigest}; +pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; -pub use node::{branch::HashType, BranchNode, Child, LeafNode, Node, PathIterItem}; +pub use node::{BranchNode, Child, LeafNode, Node, PathIterItem, branch::HashType}; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index be09b34403a1..10696706ae49 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -47,7 +47,8 @@ impl FileBacked { pub(crate) fn make_op(&self, data: &[u8]) -> io_uring::opcode::Write { use std::os::fd::AsRawFd as _; - use io_uring::{opcode::Write, types}; + use io_uring::opcode::Write; + use io_uring::types; Write::new( types::Fd(self.fd.as_raw_fd()), diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index d1c07ff58364..5bce44dcb731 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -50,7 +50,7 @@ impl ReadableStorage for MemStore { } } -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] #[cfg(test)] mod test { use super::*; diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index fb3ac623c511..309bf636bedb 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -1,9 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -// TODO: remove this once we have code that uses it -#![allow(dead_code)] - //! A LinearStore provides a view of a set of bytes at //! a given time. A LinearStore has three different types, //! which refer to another base type, as follows: diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 66569578da9d..c5a4ebf596e6 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -295,14 +295,14 @@ impl BranchNode { /// Returns (index, hash) for each child that has a hash set. pub fn children_iter(&self) -> impl Iterator + Clone { - self.children.iter().enumerate().filter_map( - #[allow(clippy::indexing_slicing)] - |(i, child)| match child { + self.children + .iter() + .enumerate() + .filter_map(|(i, child)| match child { None => None, Some(Child::Node(_)) => unreachable!("TODO make unreachable"), Some(Child::AddressWithHash(_, hash)) => Some((i, hash)), - }, - ) + }) } } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index f6b229ed2f50..6d2ad07e3909 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -277,7 +277,9 @@ impl Node { encoded.extend_from_slice(&address.get().to_ne_bytes()); encoded.extend_from_slice(&hash.serialized_bytes()); } else { - panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); + panic!( + "attempt to serialize to persist a branch with a child that is not an AddressWithHash" + ); } } } else { @@ -289,7 +291,9 @@ impl Node { encoded.extend_from_slice(&address.get().to_ne_bytes()); encoded.extend_from_slice(&hash.serialized_bytes()); } else { - panic!("attempt to serialize to persist a branch with a child that is not an AddressWithHash"); + panic!( + "attempt to serialize to persist a branch with a child that is not an AddressWithHash" + ); } } } @@ -474,7 +478,6 @@ mod test { Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 652; "full branch node with long partial path and value" )] - #[allow(unused_variables)] fn test_serialize_deserialize(node: Node, expected_length: usize) { use crate::node::Node; use std::io::Cursor; diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 7581a1209cd0..f5cc37d95ab5 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -6,7 +6,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::fmt::{self, Debug}; -use std::iter::{once, FusedIterator}; +use std::iter::{FusedIterator, once}; static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; @@ -50,7 +50,7 @@ bitflags! { impl Path { /// Return an iterator over the encoded bytes - pub fn iter_encoded(&self) -> impl Iterator + '_ { + pub fn iter_encoded(&self) -> impl Iterator { let mut flags = Flags::empty(); let has_odd_len = self.0.len() & 1 == 1; @@ -170,10 +170,10 @@ impl Iterator for NibblesIterator<'_> { return None; } let result = if self.head % 2 == 0 { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.head / 2] >> 4) as usize] } else { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.head / 2] & 0xf) as usize] }; self.head += 1; @@ -220,10 +220,10 @@ impl DoubleEndedIterator for NibblesIterator<'_> { } let result = if self.tail % 2 == 0 { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.tail / 2 - 1] & 0xf) as usize] } else { - #[allow(clippy::indexing_slicing)] + #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.tail / 2] >> 4) as usize] }; self.tail -= 1; @@ -238,7 +238,6 @@ impl DoubleEndedIterator for NibblesIterator<'_> { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod test { use super::*; use std::fmt::Debug; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index bd969f225fb8..511e70618088 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,6 +1,18 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::logger::trace; +use arc_swap::ArcSwap; +use arc_swap::access::DynAccess; +use bincode::{DefaultOptions, Options as _}; +use bytemuck_derive::{AnyBitPattern, NoUninit}; +use coarsetime::Instant; +use fastrace::local::LocalSpan; +use metrics::counter; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::Debug; + /// The [NodeStore] handles the serialization of nodes and /// free space management of nodes in the page store. It lays out the format /// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] @@ -41,17 +53,6 @@ /// I --> |commit|N("New commit NodeStore<Committed, S>") /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` -use crate::logger::trace; -use arc_swap::access::DynAccess; -use arc_swap::ArcSwap; -use bincode::{DefaultOptions, Options as _}; -use bytemuck_derive::{AnyBitPattern, NoUninit}; -use coarsetime::Instant; -use fastrace::local::LocalSpan; -use metrics::counter; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt::Debug; use std::io::{Error, ErrorKind, Write}; use std::mem::{offset_of, take}; use std::num::NonZeroU64; @@ -193,7 +194,6 @@ struct StoredArea { impl NodeStore { /// Returns (index, area_size) for the [StoredArea] at `addr`. /// `index` is the index of `area_size` in [AREA_SIZES]. - #[allow(dead_code)] fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { let mut area_stream = self.storage.stream_from(addr.get())?; @@ -694,7 +694,6 @@ pub trait RootReader { /// A committed revision of a merkle trie. #[derive(Clone, Debug)] pub struct Committed { - #[allow(dead_code)] deleted: Box<[LinearAddress]>, root_hash: Option, } @@ -903,7 +902,7 @@ impl NodeStore, S> { if hashed.len() == 1 { // we were left with one hashed node that must be rehashed let invalidated_node = hashed.first_mut().expect("hashed is not empty"); - let mut hashable_node = self.read_node(*invalidated_node.1 .0)?.deref().clone(); + let mut hashable_node = self.read_node(*invalidated_node.1.0)?.deref().clone(); let original_length = path_prefix.len(); path_prefix.0.extend(b.partial_path.0.iter().copied()); if !unhashed.is_empty() { @@ -916,7 +915,7 @@ impl NodeStore, S> { } let hash = hash_node(&hashable_node, path_prefix); path_prefix.0.truncate(original_length); - *invalidated_node.1 .1 = hash; + *invalidated_node.1.1 = hash; } // handle the single-child case for an account special below if hashed.is_empty() && unhashed.len() == 1 { @@ -1092,7 +1091,7 @@ impl NodeStore, FileBacked> { // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope // until the operation has been completed. This is ensured by marking the slot busy, // and not marking it !busy until the kernel has said it's done below. - #[allow(unsafe_code)] + #[expect(unsafe_code)] while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { ring.submitter().squeue_wait()?; trace!("submission queue is full"); @@ -1291,7 +1290,7 @@ impl NodeStore { } #[cfg(test)] -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] mod tests { use std::array::from_fn; diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 393632ec62f0..0964406fbfa1 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -22,12 +22,6 @@ impl std::ops::Deref for TrieHash { } } -impl std::ops::DerefMut for TrieHash { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl AsRef<[u8]> for TrieHash { fn as_ref(&self) -> &[u8] { &self.0 @@ -47,8 +41,10 @@ impl Display for TrieHash { } } +const TRIE_HASH_LEN: usize = std::mem::size_of::(); + impl From<[u8; 32]> for TrieHash { - fn from(value: [u8; Self::len()]) -> Self { + fn from(value: [u8; TRIE_HASH_LEN]) -> Self { TrieHash(value.into()) } } From e69bb509ec6dc17028320e8ae64726d38ff44378 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 26 Mar 2025 16:06:59 -0700 Subject: [PATCH 0683/1053] Move triehash to here (#830) --- .github/check-license-headers.yaml | 4 +- Cargo.toml | 1 + firewood/Cargo.toml | 6 +- triehash/CHANGELOG.md | 26 ++ triehash/Cargo.toml | 32 +++ triehash/README.md | 2 + triehash/benches/triehash.rs | 124 ++++++++++ triehash/src/lib.rs | 374 +++++++++++++++++++++++++++++ 8 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 triehash/CHANGELOG.md create mode 100644 triehash/Cargo.toml create mode 100644 triehash/README.md create mode 100644 triehash/benches/triehash.rs create mode 100644 triehash/src/lib.rs diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 997c78ed6bf6..6ef82b5db0fb 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -19,6 +19,7 @@ "CONTRIBUTING.md", "benchmark/**", "ffi/**", + "triehash/**", ], "license": "./.github/license-header.txt" }, @@ -38,7 +39,8 @@ "benchmark/**", "ffi/**", "CODEOWNERS", - "CONTRIBUTING.md" + "CONTRIBUTING.md", + "triehash/**", ], } ] diff --git a/Cargo.toml b/Cargo.toml index 1d98590caa53..e4861f19e0ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "grpc-testtool", "benchmark", "ffi", + "triehash", ] resolver = "2" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 340cc06bd793..65a20ea60010 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -40,10 +40,10 @@ branch_factor_256 = [ "storage/branch_factor_256" ] ethhash = [ "storage/ethhash" ] [dev-dependencies] -criterion = {version = "0.5.1", features = ["async_tokio"]} +triehash = { version = "0.8.5", path = "../triehash" } +criterion = { version = "0.5.1", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" -triehash = "0.8.4" clap = { version = "4.5.0", features = ['derive'] } pprof = { version = "0.14.0", features = ["flamegraph"] } tempfile = "3.12.0" @@ -53,7 +53,7 @@ sha3 = "0.10.8" plain_hasher = "0.2.3" hex-literal = "1.0.0" env_logger = "0.11.7" -hash-db = "0.15.2" +hash-db = "0.16.0" [[bench]] name = "hashops" diff --git a/triehash/CHANGELOG.md b/triehash/CHANGELOG.md new file mode 100644 index 000000000000..0676a558db28 --- /dev/null +++ b/triehash/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +The format is based on [Keep a Changelog]. + +[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ + +## [Unreleased] + +## [0.8.5] - 2025-03-26 +- Updated `hash-db` to 0.16.0 +- Updated `rlp` to 0.6 +- Updated `criterion` to 0.5.1 +- Updated `keccak-hasher` to 0.16.0 +- Updated `ethereum-types` to 0.15.1 +- Updated `trie-standardmap` to 0.16.0 +- Updated `hex-literal` to 1.0.0 +## [0.8.4] - 2020-01-08 +- Updated `rlp` to 0.5. [#463](https://github.com/paritytech/parity-common/pull/463) +## [0.8.3] - 2020-03-16 +- License changed from GPL3 to dual MIT/Apache2. [#342](https://github.com/paritytech/parity-common/pull/342) +## [0.8.2] - 2019-12-15 +- Added no-std support. [#280](https://github.com/paritytech/parity-common/pull/280) +## [0.8.1] - 2019-10-24 +- Migrated to 2018 edition. [#214](https://github.com/paritytech/parity-common/pull/214) +### Dependencies +- Updated dependencies. [#239](https://github.com/paritytech/parity-common/pull/239) diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml new file mode 100644 index 000000000000..379eefefe37c --- /dev/null +++ b/triehash/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "triehash" +version = "0.8.5" +authors = ["Parity Technologies ", "Ron Kuris "] +description = "In-memory patricia trie operations" +repository = "https://github.com/paritytech/parity-common" +license = "MIT OR Apache-2.0" +edition = "2024" + +[dependencies] +hash-db = { version = "0.16.0", default-features = false } +rlp = { version = "0.6", default-features = false } + +[dev-dependencies] +criterion = "0.5.1" +keccak-hasher = "0.16.0" +ethereum-types = { version = "0.15.1" } +tiny-keccak = { version = "2.0", features = ["keccak"] } +trie-standardmap = "0.16.0" +hex-literal = "1.0.0" + +[features] +default = ["std"] +std = [ + "hash-db/std", + "rlp/std", +] + +[[bench]] +name = "triehash" +path = "benches/triehash.rs" +harness = false diff --git a/triehash/README.md b/triehash/README.md new file mode 100644 index 000000000000..99c5ce459989 --- /dev/null +++ b/triehash/README.md @@ -0,0 +1,2 @@ +This crate provides utility functions to validate and initialize tries using flexible input. +It is used extensively in `parity-ethereum` to validate blocks (mostly transactions and receipt roots). \ No newline at end of file diff --git a/triehash/benches/triehash.rs b/triehash/benches/triehash.rs new file mode 100644 index 000000000000..11a24ee908a0 --- /dev/null +++ b/triehash/benches/triehash.rs @@ -0,0 +1,124 @@ +// Copyright 2020 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use criterion::{Criterion, criterion_group, criterion_main}; +use ethereum_types::H256; +use keccak_hasher::KeccakHasher; +use tiny_keccak::{Hasher, Keccak}; +use trie_standardmap::{Alphabet, StandardMap, ValueMode}; +use triehash::trie_root; + +fn keccak256(input: &[u8]) -> [u8; 32] { + let mut keccak256 = Keccak::v256(); + let mut out = [0u8; 32]; + keccak256.update(input); + keccak256.finalize(&mut out); + out +} + +fn random_word(alphabet: &[u8], min_count: usize, diff_count: usize, seed: &mut H256) -> Vec { + assert!(min_count + diff_count <= 32); + *seed = H256(keccak256(seed.as_bytes())); + let r = min_count + (seed[31] as usize % (diff_count + 1)); + let mut ret: Vec = Vec::with_capacity(r); + for i in 0..r { + ret.push(alphabet[seed[i] as usize % alphabet.len()]); + } + ret +} + +fn random_bytes(min_count: usize, diff_count: usize, seed: &mut H256) -> Vec { + assert!(min_count + diff_count <= 32); + *seed = H256(keccak256(seed.as_bytes())); + let r = min_count + (seed[31] as usize % (diff_count + 1)); + seed[0..r].to_vec() +} + +fn random_value(seed: &mut H256) -> Vec { + *seed = H256(keccak256(seed.as_bytes())); + match seed[0] % 2 { + 1 => vec![seed[31]; 1], + _ => seed.as_bytes().to_vec(), + } +} + +fn bench_insertions(c: &mut Criterion) { + c.bench_function("32_mir_1k", |b| { + let st = StandardMap { + alphabet: Alphabet::All, + min_key: 32, + journal_key: 0, + value_mode: ValueMode::Mirror, + count: 1000, + }; + let d = st.make(); + b.iter(|| trie_root::(d.clone())); + }); + + c.bench_function("32_ran_1k", |b| { + let st = StandardMap { + alphabet: Alphabet::All, + min_key: 32, + journal_key: 0, + value_mode: ValueMode::Random, + count: 1000, + }; + let d = st.make(); + b.iter(|| trie_root::(d.clone())); + }); + + c.bench_function("six_high", |b| { + let mut d: Vec<(Vec, Vec)> = Vec::new(); + let mut seed = H256::default(); + for _ in 0..1000 { + let k = random_bytes(6, 0, &mut seed); + let v = random_value(&mut seed); + d.push((k, v)) + } + b.iter(|| trie_root::(d.clone())); + }); + + c.bench_function("six_mid", |b| { + let alphabet = b"@QWERTYUIOPASDFGHJKLZXCVBNM[/]^_"; + let mut d: Vec<(Vec, Vec)> = Vec::new(); + let mut seed = H256::default(); + for _ in 0..1000 { + let k = random_word(alphabet, 6, 0, &mut seed); + let v = random_value(&mut seed); + d.push((k, v)) + } + b.iter(|| trie_root::(d.clone())); + }); + + c.bench_function("random_mid", |b| { + let alphabet = b"@QWERTYUIOPASDFGHJKLZXCVBNM[/]^_"; + let mut d: Vec<(Vec, Vec)> = Vec::new(); + let mut seed = H256::default(); + for _ in 0..1000 { + let k = random_word(alphabet, 1, 5, &mut seed); + let v = random_value(&mut seed); + d.push((k, v)) + } + b.iter(|| trie_root::(d.clone())); + }); + + c.bench_function("six_low", |b| { + let alphabet = b"abcdef"; + let mut d: Vec<(Vec, Vec)> = Vec::new(); + let mut seed = H256::default(); + for _ in 0..1000 { + let k = random_word(alphabet, 6, 0, &mut seed); + let v = random_value(&mut seed); + d.push((k, v)) + } + b.iter(|| trie_root::(d.clone())); + }); +} + +criterion_group!(benches, bench_insertions); +criterion_main!(benches); diff --git a/triehash/src/lib.rs b/triehash/src/lib.rs new file mode 100644 index 000000000000..ba27c0a5aa21 --- /dev/null +++ b/triehash/src/lib.rs @@ -0,0 +1,374 @@ +// Copyright 2020 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Generetes trie root. +//! +//! This module should be used to generate trie root hash. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(feature = "std")] +mod rstd { + pub use std::collections::BTreeMap; +} + +#[cfg(not(feature = "std"))] +mod rstd { + pub use alloc::collections::BTreeMap; + pub use alloc::vec::Vec; +} + +use core::cmp; +use core::iter::once; +use rstd::*; + +use hash_db::Hasher; +use rlp::RlpStream; + +fn shared_prefix_len(first: &[T], second: &[T]) -> usize { + first + .iter() + .zip(second.iter()) + .position(|(f, s)| f != s) + .unwrap_or_else(|| cmp::min(first.len(), second.len())) +} + +/// Generates a trie root hash for a vector of values +/// +/// ``` +/// use hex_literal::hex; +/// use ethereum_types::H256; +/// use triehash::ordered_trie_root; +/// use keccak_hasher::KeccakHasher; +/// +/// let v = &["doe", "reindeer"]; +/// let root = H256::from(hex!("e766d5d51b89dc39d981b41bda63248d7abce4f0225eefd023792a540bcffee3")); +/// assert_eq!(ordered_trie_root::(v), root.as_ref()); +/// ``` +pub fn ordered_trie_root(input: I) -> H::Out +where + I: IntoIterator, + I::Item: AsRef<[u8]>, + H: Hasher, + ::Out: cmp::Ord, +{ + trie_root::( + input + .into_iter() + .enumerate() + .map(|(i, v)| (rlp::encode(&i), v)), + ) +} + +/// Generates a trie root hash for a vector of key-value tuples +/// +/// ``` +/// use hex_literal::hex; +/// use triehash::trie_root; +/// use ethereum_types::H256; +/// use keccak_hasher::KeccakHasher; +/// +/// let v = vec![ +/// ("doe", "reindeer"), +/// ("dog", "puppy"), +/// ("dogglesworth", "cat"), +/// ]; +/// +/// let root = H256::from(hex!("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3")); +/// assert_eq!(trie_root::(v), root.as_ref()); +/// ``` +pub fn trie_root(input: I) -> H::Out +where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + H: Hasher, + ::Out: cmp::Ord, +{ + // first put elements into btree to sort them and to remove duplicates + let input = input.into_iter().collect::>(); + + let mut nibbles = Vec::with_capacity(input.keys().map(|k| k.as_ref().len()).sum::() * 2); + let mut lens = Vec::with_capacity(input.len() + 1); + lens.push(0); + for k in input.keys() { + for &b in k.as_ref() { + nibbles.push(b >> 4); + nibbles.push(b & 0x0F); + } + lens.push(nibbles.len()); + } + + // then move them to a vector + let input = input + .into_iter() + .zip(lens.windows(2)) + .map(|((_, v), w)| (&nibbles[w[0]..w[1]], v)) + .collect::>(); + + let mut stream = RlpStream::new(); + hash256rlp::(&input, 0, &mut stream); + H::hash(&stream.out()) +} + +/// Generates a key-hashed (secure) trie root hash for a vector of key-value tuples. +/// +/// ``` +/// use hex_literal::hex; +/// use ethereum_types::H256; +/// use triehash::sec_trie_root; +/// use keccak_hasher::KeccakHasher; +/// +/// let v = vec![ +/// ("doe", "reindeer"), +/// ("dog", "puppy"), +/// ("dogglesworth", "cat"), +/// ]; +/// +/// let root = H256::from(hex!("d4cd937e4a4368d7931a9cf51686b7e10abb3dce38a39000fd7902a092b64585")); +/// assert_eq!(sec_trie_root::(v), root.as_ref()); +/// ``` +pub fn sec_trie_root(input: I) -> H::Out +where + I: IntoIterator, + A: AsRef<[u8]>, + B: AsRef<[u8]>, + H: Hasher, + ::Out: cmp::Ord, +{ + trie_root::(input.into_iter().map(|(k, v)| (H::hash(k.as_ref()), v))) +} + +/// Hex-prefix Notation. First nibble has flags: oddness = 2^0 & termination = 2^1. +/// +/// The "termination marker" and "leaf-node" specifier are completely equivalent. +/// +/// Input values are in range `[0, 0xf]`. +/// +/// ```markdown +/// [0,0,1,2,3,4,5] 0x10012345 // 7 > 4 +/// [0,1,2,3,4,5] 0x00012345 // 6 > 4 +/// [1,2,3,4,5] 0x112345 // 5 > 3 +/// [0,0,1,2,3,4] 0x00001234 // 6 > 3 +/// [0,1,2,3,4] 0x101234 // 5 > 3 +/// [1,2,3,4] 0x001234 // 4 > 3 +/// [0,0,1,2,3,4,5,T] 0x30012345 // 7 > 4 +/// [0,0,1,2,3,4,T] 0x20001234 // 6 > 4 +/// [0,1,2,3,4,5,T] 0x20012345 // 6 > 4 +/// [1,2,3,4,5,T] 0x312345 // 5 > 3 +/// [1,2,3,4,T] 0x201234 // 4 > 3 +/// ``` +fn hex_prefix_encode(nibbles: &[u8], leaf: bool) -> impl Iterator + '_ { + let inlen = nibbles.len(); + let oddness_factor = inlen % 2; + + let first_byte = { + let mut bits = ((inlen as u8 & 1) + (2 * leaf as u8)) << 4; + if oddness_factor == 1 { + bits += nibbles[0]; + } + bits + }; + once(first_byte).chain( + nibbles[oddness_factor..] + .chunks(2) + .map(|ch| (ch[0] << 4) | ch[1]), + ) +} + +fn hash256rlp(input: &[(A, B)], pre_len: usize, stream: &mut RlpStream) +where + A: AsRef<[u8]>, + B: AsRef<[u8]>, + H: Hasher, +{ + let inlen = input.len(); + + // in case of empty slice, just append empty data + if inlen == 0 { + stream.append_empty_data(); + return; + } + + // take slices + let key: &[u8] = input[0].0.as_ref(); + let value: &[u8] = input[0].1.as_ref(); + + // if the slice contains just one item, append the suffix of the key + // and then append value + if inlen == 1 { + stream.begin_list(2); + stream.append_iter(hex_prefix_encode(&key[pre_len..], true)); + stream.append(&value); + return; + } + + // get length of the longest shared prefix in slice keys + let shared_prefix = input + .iter() + // skip first tuple + .skip(1) + // get minimum number of shared nibbles between first and each successive + .fold(key.len(), |acc, (k, _)| { + cmp::min(shared_prefix_len(key, k.as_ref()), acc) + }); + + // if shared prefix is higher than current prefix append its + // new part of the key to the stream + // then recursively append suffixes of all items who had this key + if shared_prefix > pre_len { + stream.begin_list(2); + stream.append_iter(hex_prefix_encode(&key[pre_len..shared_prefix], false)); + hash256aux::(input, shared_prefix, stream); + return; + } + + // an item for every possible nibble/suffix + // + 1 for data + stream.begin_list(17); + + // if first key len is equal to prefix_len, move to next element + let mut begin = if pre_len == key.len() { 1 } else { 0 }; + + // iterate over all possible nibbles + for i in 0..16 { + // count how many successive elements have same next nibble + let len = input + .iter() + .skip(begin) + .take_while(|pair| pair.0.as_ref()[pre_len] == i) + .count(); + + // if at least 1 successive element has the same nibble + // append their suffixes + match len { + 0 => { + stream.append_empty_data(); + } + _ => hash256aux::(&input[begin..(begin + len)], pre_len + 1, stream), + } + begin += len; + } + + // if fist key len is equal prefix, append its value + if pre_len == key.len() { + stream.append(&value); + } else { + stream.append_empty_data(); + } +} + +fn hash256aux(input: &[(A, B)], pre_len: usize, stream: &mut RlpStream) +where + A: AsRef<[u8]>, + B: AsRef<[u8]>, + H: Hasher, +{ + let mut s = RlpStream::new(); + hash256rlp::(input, pre_len, &mut s); + let out = s.out(); + match out.len() { + 0..=31 => stream.append_raw(&out, 1), + _ => stream.append(&H::hash(&out).as_ref()), + }; +} + +#[cfg(test)] +mod tests { + use super::{hex_prefix_encode, shared_prefix_len, trie_root}; + use ethereum_types::H256; + use hex_literal::hex; + use keccak_hasher::KeccakHasher; + + #[test] + fn test_hex_prefix_encode() { + let v = vec![0, 0, 1, 2, 3, 4, 5]; + let e = vec![0x10, 0x01, 0x23, 0x45]; + let h = hex_prefix_encode(&v, false).collect::>(); + assert_eq!(h, e); + + let v = vec![0, 1, 2, 3, 4, 5]; + let e = vec![0x00, 0x01, 0x23, 0x45]; + let h = hex_prefix_encode(&v, false).collect::>(); + assert_eq!(h, e); + + let v = vec![0, 1, 2, 3, 4, 5]; + let e = vec![0x20, 0x01, 0x23, 0x45]; + let h = hex_prefix_encode(&v, true).collect::>(); + assert_eq!(h, e); + + let v = vec![1, 2, 3, 4, 5]; + let e = vec![0x31, 0x23, 0x45]; + let h = hex_prefix_encode(&v, true).collect::>(); + assert_eq!(h, e); + + let v = vec![1, 2, 3, 4]; + let e = vec![0x00, 0x12, 0x34]; + let h = hex_prefix_encode(&v, false).collect::>(); + assert_eq!(h, e); + + let v = vec![4, 1]; + let e = vec![0x20, 0x41]; + let h = hex_prefix_encode(&v, true).collect::>(); + assert_eq!(h, e); + } + + #[test] + fn simple_test() { + assert_eq!( + trie_root::(vec![( + b"A", + b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8] + )]), + H256::from(hex!( + "d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab" + )) + .as_ref(), + ); + } + + #[test] + fn test_triehash_out_of_order() { + assert_eq!( + trie_root::(vec![ + (vec![0x01u8, 0x23], vec![0x01u8, 0x23]), + (vec![0x81u8, 0x23], vec![0x81u8, 0x23]), + (vec![0xf1u8, 0x23], vec![0xf1u8, 0x23]), + ]), + trie_root::(vec![ + (vec![0x01u8, 0x23], vec![0x01u8, 0x23]), + (vec![0xf1u8, 0x23], vec![0xf1u8, 0x23]), // last two tuples are swapped + (vec![0x81u8, 0x23], vec![0x81u8, 0x23]), + ]), + ); + } + + #[test] + fn test_shared_prefix() { + let a = vec![1, 2, 3, 4, 5, 6]; + let b = vec![4, 2, 3, 4, 5, 6]; + assert_eq!(shared_prefix_len(&a, &b), 0); + } + + #[test] + fn test_shared_prefix2() { + let a = vec![1, 2, 3, 3, 5]; + let b = vec![1, 2, 3]; + assert_eq!(shared_prefix_len(&a, &b), 3); + } + + #[test] + fn test_shared_prefix3() { + let a = vec![1, 2, 3, 4, 5, 6]; + let b = vec![1, 2, 3, 4, 5, 6]; + assert_eq!(shared_prefix_len(&a, &b), 6); + } +} From 8f0aa972a528644c4c678f8302c370e0bf298623 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Apr 2025 14:37:43 -0700 Subject: [PATCH 0684/1053] Rust 1.86 lint cleanups (#833) --- benchmark/src/tenkrandom.rs | 2 +- ffi/firewood.h | 2 +- ffi/src/lib.rs | 2 +- storage/src/node/mod.rs | 4 ++-- storage/src/nodestore.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 35b62f4243e0..78c9ab50e60c 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -61,7 +61,7 @@ fn generate_deletes(start: u64, count: u64) -> impl Iterator { // this is a freed area - Err(Error::new(ErrorKind::Other, "attempt to read freed area")) + Err(Error::other("attempt to read freed area")) } leaf_first_byte if leaf_first_byte & 1 == 1 => { let partial_path_len = if leaf_first_byte < 255 { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 511e70618088..ab1ca50ebb2a 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1016,7 +1016,7 @@ impl NodeStore { let header_bytes = bytemuck::bytes_of(&self.header) .iter() .copied() - .chain(std::iter::repeat(0u8).take(NodeStoreHeader::EXTRA_BYTES)) + .chain(std::iter::repeat_n(0u8, NodeStoreHeader::EXTRA_BYTES)) .collect::>(); debug_assert_eq!(header_bytes.len(), NodeStoreHeader::SIZE as usize); From 3c0f6d65be3d4c9e8774e81c5fddfb35087daf82 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 8 Apr 2025 16:58:58 -0700 Subject: [PATCH 0685/1053] test(ffi/tests): basic eth compatibility (#825) Co-authored-by: Ron Kuris --- .github/workflows/ci.yaml | 29 ++ ffi/tests/eth_compatibility_test.go | 219 ++++++++++++++ ffi/tests/go.mod | 60 ++++ ffi/tests/go.sum | 449 ++++++++++++++++++++++++++++ 4 files changed, 757 insertions(+) create mode 100644 ffi/tests/eth_compatibility_test.go create mode 100644 ffi/tests/go.mod create mode 100644 ffi/tests/go.sum diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ab4805cb446..deafd0ed3df4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -203,3 +203,32 @@ jobs: working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 go test ./... + + ethhash: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v2 + - name: Restore Cargo Cache + id: cache-build-deps-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ needs.build.outputs.cache-key }} + - name: Build Firewood FFI (with ethhash) + run: cargo build --release --features ethhash + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "ffi/tests/go.mod" + cache-dependency-path: "ffi/tests/go.sum" + - name: Test Ethereum hash compatability + working-directory: ffi/tests + run: go test ./... diff --git a/ffi/tests/eth_compatibility_test.go b/ffi/tests/eth_compatibility_test.go new file mode 100644 index 000000000000..1802b549a73e --- /dev/null +++ b/ffi/tests/eth_compatibility_test.go @@ -0,0 +1,219 @@ +package tests + +import ( + "encoding/binary" + "math/big" + "math/rand" + "path" + "slices" + "testing" + + firewood "github.com/ava-labs/firewood/ffi/v2" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" +) + +func hashData(input []byte) common.Hash { + var hasher = sha3.NewLegacyKeccak256() + var hash common.Hash + hasher.Reset() + hasher.Write(input) + hasher.Sum(hash[:0]) + return hash +} + +func TestInsert(t *testing.T) { + file := path.Join(t.TempDir(), "test.db") + cfg := firewood.DefaultConfig() + cfg.Create = true + db, err := firewood.New(file, cfg) + require.NoError(t, err) + defer db.Close() + + type storageKey struct { + addr common.Address + key common.Hash + } + + rand.Seed(0) + + addrs := make([]common.Address, 0) + storages := make([]storageKey, 0) + + chooseAddr := func() common.Address { + return addrs[rand.Intn(len(addrs))] + } + + chooseStorage := func() storageKey { + return storages[rand.Intn(len(storages))] + } + + deleteStorage := func(k storageKey) { + storages = slices.DeleteFunc(storages, func(s storageKey) bool { + return s == k + }) + } + + deleteAccount := func(addr common.Address) { + addrs = slices.DeleteFunc(addrs, func(a common.Address) bool { + return a == addr + }) + storages = slices.DeleteFunc(storages, func(s storageKey) bool { + return s.addr == addr + }) + } + + memdb := rawdb.NewMemoryDatabase() + tdb := state.NewDatabase(memdb) + ethRoot := types.EmptyRootHash + + for i := 0; i < 10_000; i++ { + tr, err := tdb.OpenTrie(ethRoot) + require.NoError(t, err) + mergeSet := trie.NewMergedNodeSet() + + var fwKeys, fwVals [][]byte + + switch { + case i%100 == 99: // delete acc + addr := chooseAddr() + accHash := hashData(addr[:]) + + err = tr.TryDeleteAccount(addr) + require.NoError(t, err) + deleteAccount(addr) + + fwKeys = append(fwKeys, accHash[:]) + fwVals = append(fwVals, []byte{}) + case i%10 == 9: // delete storage + storageKey := chooseStorage() + accHash := hashData(storageKey.addr[:]) + keyHash := hashData(storageKey.key[:]) + + acc, err := tr.TryGetAccount(storageKey.addr) + require.NoError(t, err) + + str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + require.NoError(t, err) + + err = str.TryDelete(storageKey.key[:]) + require.NoError(t, err) + deleteStorage(storageKey) + + strRoot, set := str.Commit(false) + err = mergeSet.Merge(set) + require.NoError(t, err) + acc.Root = strRoot + err = tr.TryUpdateAccount(storageKey.addr, acc) + require.NoError(t, err) + + fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) + fwVals = append(fwVals, []byte{}) + + case i%4 == 0: // add acc + addr := common.BytesToAddress(hashData(binary.BigEndian.AppendUint64(nil, uint64(i))).Bytes()) + accHash := hashData(addr[:]) + acc := &types.StateAccount{ + Nonce: 1, + Balance: new(big.Int).SetUint64(100), + Root: types.EmptyRootHash, + CodeHash: types.EmptyCodeHash[:], + } + enc, err := rlp.EncodeToBytes(acc) + require.NoError(t, err) + + err = tr.TryUpdateAccount(addr, acc) + require.NoError(t, err) + addrs = append(addrs, addr) + + fwKeys = append(fwKeys, accHash[:]) + fwVals = append(fwVals, enc) + case i%4 == 1: // update acc + addr := chooseAddr() + accHash := hashData(addr[:]) + acc, err := tr.TryGetAccount(addr) + require.NoError(t, err) + acc.Nonce++ + enc, err := rlp.EncodeToBytes(acc) + require.NoError(t, err) + + err = tr.TryUpdateAccount(addr, acc) + require.NoError(t, err) + + fwKeys = append(fwKeys, accHash[:]) + fwVals = append(fwVals, enc) + case i%4 == 2: // add storage + addr := chooseAddr() + accHash := hashData(addr[:]) + key := hashData(binary.BigEndian.AppendUint64(nil, uint64(i))) + keyHash := hashData(key[:]) + + val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) + storageKey := storageKey{addr: addr, key: key} + + acc, err := tr.TryGetAccount(addr) + require.NoError(t, err) + + str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + require.NoError(t, err) + + err = str.TryUpdate(key[:], val[:]) + require.NoError(t, err) + storages = append(storages, storageKey) + + strRoot, set := str.Commit(false) + err = mergeSet.Merge(set) + require.NoError(t, err) + acc.Root = strRoot + err = tr.TryUpdateAccount(addr, acc) + require.NoError(t, err) + + fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) + fwVals = append(fwVals, val[:]) + case i%4 == 3: // update storage + storageKey := chooseStorage() + accHash := hashData(storageKey.addr[:]) + keyHash := hashData(storageKey.key[:]) + + val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) + + acc, err := tr.TryGetAccount(storageKey.addr) + require.NoError(t, err) + + str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + require.NoError(t, err) + + err = str.TryUpdate(storageKey.key[:], val[:]) + require.NoError(t, err) + + strRoot, set := str.Commit(false) + err = mergeSet.Merge(set) + require.NoError(t, err) + acc.Root = strRoot + err = tr.TryUpdateAccount(storageKey.addr, acc) + require.NoError(t, err) + + fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) + fwVals = append(fwVals, val[:]) + } + next, set := tr.Commit(true) + err = mergeSet.Merge(set) + require.NoError(t, err) + + err = tdb.TrieDB().Update(mergeSet) + require.NoError(t, err) + + // update firewood db + got, err := db.Update(fwKeys, fwVals) + require.NoError(t, err) + require.Equal(t, next[:], got) + + ethRoot = next + } +} diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod new file mode 100644 index 000000000000..9807b06fc1ef --- /dev/null +++ b/ffi/tests/go.mod @@ -0,0 +1,60 @@ +module github.com/ava-labs/firewood/ffi/tests + +go 1.23 + +toolchain go1.23.6 + +require ( + github.com/ava-labs/firewood/ffi/v2 v2.0.0 // this is replaced to use the parent folder + github.com/ethereum/go-ethereum v1.11.5 + github.com/stretchr/testify v1.10.0 + golang.org/x/crypto v0.1.0 +) + +require ( + github.com/DataDog/zstd v1.5.2 // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/VictoriaMetrics/fastcache v1.6.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect + github.com/cockroachdb/redact v1.1.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.2.0 // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/tklauser/numcpus v0.2.2 // indirect + golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/ava-labs/firewood/ffi/v2 => ../ diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum new file mode 100644 index 000000000000..8571ce7adf87 --- /dev/null +++ b/ffi/tests/go.sum @@ -0,0 +1,449 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ= +github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 88c47ddb0faf48906881b593899b7e50a424e30c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Apr 2025 11:59:54 -0700 Subject: [PATCH 0686/1053] Speed up CI (#834) Changes: Upgraded actions/checkout v3->v4 Upgraded arduino/setup-proto v2->v3 Migrated from actions/cache/* to Swatinem/rust-cache v2 Examples and benchmarks re-enabled, and happen after ethhash builds. They run with ethhash feature enabled because this is a lot faster from a CI perspective. There are two keys now, one for release builds with ethhash enabled (ethhash) and one for debug builds (build). --- .github/workflows/ci.yaml | 175 +++++--------------- .github/workflows/default-branch-cache.yaml | 50 +----- .github/workflows/gh-pages.yaml | 26 +-- firewood/examples/insert.rs | 2 +- 4 files changed, 49 insertions(+), 204 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index deafd0ed3df4..b6ac66e81e8c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,91 +12,34 @@ env: jobs: build: runs-on: ubuntu-latest - outputs: - cache-key: ${{ steps.cargo-cache.outputs.cache-primary-key }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - # caution: this is the same restore as in gh-pages.yaml - - name: Restore Cargo Cache - id: cargo-cache - uses: actions/cache/restore@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - # We can do this now because we use specific version and update with Dependabot - # but if we make the deps any less specifc, we'll have to fix - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - # start from the previous set of cached dependencies - restore-keys: | - ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-deps- + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 - name: Check run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches - # Always update the cache - - name: Cleanup - run: | - gh extension install actions/gh-actions-cache - - REPO=${{ github.repository }} - BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" - - echo "Fetching list of cache key" - cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) - - ## Setting this to not fail the workflow while deleting cache keys. - set +e - echo "Deleting caches..." - for cacheKey in $cacheKeysForPR - do - gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm - done - echo "Done" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Save Cargo Cache - uses: actions/cache/save@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ steps.cargo-cache.outputs.cache-primary-key }} lint: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: "build" - name: Check license headers uses: viperproject/check-license-header@v2 with: path: . config: .github/check-license-headers.yaml strict: true - - name: Restore Check Deps - id: cache-build-deps-restore - uses: actions/cache/restore@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} - name: Format run: cargo fmt -- --check - name: Clippy @@ -107,90 +50,61 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - - name: Restore Check Deps - id: cache-build-deps-restore - uses: actions/cache/restore@v3 + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} - - name: Run tests with features enabled - run: cargo test --verbose --features logger,ethhash + save-if: "false" + shared-key: "build" - name: Run tests with ethhash disabled run: cargo test --verbose + - name: Run tests with features enabled + run: cargo test --verbose --features logger,ethhash # TODO: Enable testing with branch_factor_256 # - name: Run tests with branch_factor_256 # run: cargo test --verbose --features branch_factor_256 examples: - needs: build + needs: ethhash runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - - name: Restore Check Deps - id: cache-build-deps-restore - uses: actions/cache/restore@v3 + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} - # benchmarks were not being done in --release mode, we can enable this again later - # - name: Run benchmark example - # run: RUST_BACKTRACE=1 cargo run --example benchmark -- --nbatch 100 --batch-size 1000 - # - name: Run insert example - # run: RUST_BACKTRACE=1 cargo run --example insert + save-if: "false" + shared-key: "ethhash" + - name: Run benchmark example + run: RUST_BACKTRACE=1 cargo run --features ethhash --release --bin benchmark -- --number-of-batches 100 --batch-size 1000 create + - name: Run insert example + run: RUST_BACKTRACE=1 cargo run --features ethhash --release --example insert docs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - - name: Restore Check Deps - id: cache-build-deps-restore - uses: actions/cache/restore@v3 + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} + save-if: "false" + shared-key: "build" - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps ffi: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - - name: Restore Cargo Cache - id: cache-build-deps-restore - uses: actions/cache/restore@v3 + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} + save-if: "false" + shared-key: "build" - name: Build Firewood FFI working-directory: ffi run: cargo build --release @@ -205,23 +119,12 @@ jobs: run: GOEXPERIMENT=cgocheck2 go test ./... ethhash: - needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 - - name: Restore Cargo Cache - id: cache-build-deps-restore - uses: actions/cache/restore@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ needs.build.outputs.cache-key }} + - uses: arduino/setup-protoc@v3 + - uses: Swatinem/rust-cache@v2 - name: Build Firewood FFI (with ethhash) run: cargo build --release --features ethhash - name: Set up Go diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 80f08891e7df..409689ee949d 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -18,55 +18,11 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v2 - - name: Restore Cargo Cache - id: cargo-cache - uses: actions/cache/restore@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - # We can do this now because we use specific verison and update with Dependabot - # but if we make the deps any less specifc, we'll have to fix - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - # start from the previous set of cached dependencies - restore-keys: | - ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-deps- - # TODO: do a `cargo fetch` here first + save-if: "false" + shared-key: "build" - name: Check run: cargo check --workspace --tests --examples --benches - name: Build run: cargo build --workspace --tests --examples --benches - - name: Delete old cache - run: | - gh extension install actions/gh-actions-cache - - REPO=${{ github.repository }} - BRANCH=${{ github.ref }} - - echo "Fetching list of cache key" - cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) - - ## Setting this to not fail the workflow while deleting cache keys. - set +e - echo "Deleting caches..." - for cacheKey in $cacheKeysForPR - do - gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm - done - echo "Done" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Save Cargo Cache - uses: actions/cache/save@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ steps.cargo-cache.outputs.cache-primary-key }} diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index baca4f46067f..ae644e8d0b43 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -12,27 +12,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 + - uses: arduino/setup-protoc@v3 # caution: this is the same restore as in ci.yaml - - name: Restore Cargo Cache - id: cargo-cache - uses: actions/cache/restore@v3 + - uses: Swatinem/rust-cache@v2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - # We can do this now because we use specific verison and update with Dependabot - # but if we make the deps any less specifc, we'll have to fix - key: ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('**/*.rs') }} - # start from the previous set of cached dependencies - restore-keys: | - ${{ runner.os }}-deps-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-deps- + save-if: "false" - name: Build run: cargo doc --document-private-items --no-deps - name: Set up _site redirect to firewood @@ -62,14 +48,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Download pages artifact - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v4 with: name: pages path: . - name: Setup Pages uses: actions/configure-pages@v3 - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: . - name: Deploy to GitHub pages diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 16db1aaefd7f..13d22dd49873 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -28,7 +28,7 @@ struct Args { batch_size: usize, #[arg(short, long, default_value_t = 100)] number_of_batches: usize, - #[arg(short, long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] + #[arg(short = 'p', long, default_value_t = 0, value_parser = clap::value_parser!(u16).range(0..=100))] read_verify_percent: u16, #[arg(short, long)] seed: Option, From bb372e8d89fae45f219cc8dfc01f831d979ca781 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 9 Apr 2025 14:10:34 -0700 Subject: [PATCH 0687/1053] chore(ffi/tests): update go-ethereum v1.15.7 (#838) --- ffi/tests/eth_compatibility_test.go | 68 ++-- ffi/tests/go.mod | 63 ++-- ffi/tests/go.sum | 470 ++++------------------------ 3 files changed, 121 insertions(+), 480 deletions(-) diff --git a/ffi/tests/eth_compatibility_test.go b/ffi/tests/eth_compatibility_test.go index 1802b549a73e..fdabcee5485c 100644 --- a/ffi/tests/eth_compatibility_test.go +++ b/ffi/tests/eth_compatibility_test.go @@ -2,7 +2,6 @@ package tests import ( "encoding/binary" - "math/big" "math/rand" "path" "slices" @@ -13,19 +12,16 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" - "golang.org/x/crypto/sha3" ) func hashData(input []byte) common.Hash { - var hasher = sha3.NewLegacyKeccak256() - var hash common.Hash - hasher.Reset() - hasher.Write(input) - hasher.Sum(hash[:0]) - return hash + return crypto.Keccak256Hash(input) } func TestInsert(t *testing.T) { @@ -70,13 +66,13 @@ func TestInsert(t *testing.T) { } memdb := rawdb.NewMemoryDatabase() - tdb := state.NewDatabase(memdb) + tdb := state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil) ethRoot := types.EmptyRootHash - for i := 0; i < 10_000; i++ { + for i := range 10_000 { tr, err := tdb.OpenTrie(ethRoot) require.NoError(t, err) - mergeSet := trie.NewMergedNodeSet() + mergeSet := trienode.NewMergedNodeSet() var fwKeys, fwVals [][]byte @@ -85,7 +81,7 @@ func TestInsert(t *testing.T) { addr := chooseAddr() accHash := hashData(addr[:]) - err = tr.TryDeleteAccount(addr) + err = tr.DeleteAccount(addr) require.NoError(t, err) deleteAccount(addr) @@ -96,13 +92,13 @@ func TestInsert(t *testing.T) { accHash := hashData(storageKey.addr[:]) keyHash := hashData(storageKey.key[:]) - acc, err := tr.TryGetAccount(storageKey.addr) + acc, err := tr.GetAccount(storageKey.addr) require.NoError(t, err) - str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + str, err := tdb.OpenStorageTrie(ethRoot, storageKey.addr, acc.Root, tr) require.NoError(t, err) - err = str.TryDelete(storageKey.key[:]) + err = str.DeleteStorage(storageKey.addr, storageKey.key[:]) require.NoError(t, err) deleteStorage(storageKey) @@ -110,7 +106,7 @@ func TestInsert(t *testing.T) { err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.TryUpdateAccount(storageKey.addr, acc) + err = tr.UpdateAccount(storageKey.addr, acc, 0) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) @@ -121,14 +117,14 @@ func TestInsert(t *testing.T) { accHash := hashData(addr[:]) acc := &types.StateAccount{ Nonce: 1, - Balance: new(big.Int).SetUint64(100), + Balance: uint256.NewInt(100), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash[:], } enc, err := rlp.EncodeToBytes(acc) require.NoError(t, err) - err = tr.TryUpdateAccount(addr, acc) + err = tr.UpdateAccount(addr, acc, 0) require.NoError(t, err) addrs = append(addrs, addr) @@ -137,13 +133,13 @@ func TestInsert(t *testing.T) { case i%4 == 1: // update acc addr := chooseAddr() accHash := hashData(addr[:]) - acc, err := tr.TryGetAccount(addr) + acc, err := tr.GetAccount(addr) require.NoError(t, err) acc.Nonce++ enc, err := rlp.EncodeToBytes(acc) require.NoError(t, err) - err = tr.TryUpdateAccount(addr, acc) + err = tr.UpdateAccount(addr, acc, 0) require.NoError(t, err) fwKeys = append(fwKeys, accHash[:]) @@ -157,13 +153,13 @@ func TestInsert(t *testing.T) { val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) storageKey := storageKey{addr: addr, key: key} - acc, err := tr.TryGetAccount(addr) + acc, err := tr.GetAccount(addr) require.NoError(t, err) - str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + str, err := tdb.OpenStorageTrie(ethRoot, addr, acc.Root, tr) require.NoError(t, err) - err = str.TryUpdate(key[:], val[:]) + err = str.UpdateStorage(addr, key[:], val[:]) require.NoError(t, err) storages = append(storages, storageKey) @@ -171,11 +167,15 @@ func TestInsert(t *testing.T) { err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.TryUpdateAccount(addr, acc) + err = tr.UpdateAccount(addr, acc, 0) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) - fwVals = append(fwVals, val[:]) + // UpdateStorage automatically encodes the value to rlp, + // so we need to encode prior to sending to firewood + encodedVal, err := rlp.EncodeToBytes(val[:]) + require.NoError(t, err) + fwVals = append(fwVals, encodedVal) case i%4 == 3: // update storage storageKey := chooseStorage() accHash := hashData(storageKey.addr[:]) @@ -183,30 +183,34 @@ func TestInsert(t *testing.T) { val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) - acc, err := tr.TryGetAccount(storageKey.addr) + acc, err := tr.GetAccount(storageKey.addr) require.NoError(t, err) - str, err := tdb.OpenStorageTrie(ethRoot, accHash, acc.Root) + str, err := tdb.OpenStorageTrie(ethRoot, storageKey.addr, acc.Root, tr) require.NoError(t, err) - err = str.TryUpdate(storageKey.key[:], val[:]) + err = str.UpdateStorage(storageKey.addr, storageKey.key[:], val[:]) require.NoError(t, err) strRoot, set := str.Commit(false) err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.TryUpdateAccount(storageKey.addr, acc) + err = tr.UpdateAccount(storageKey.addr, acc, 0) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) - fwVals = append(fwVals, val[:]) + // UpdateStorage automatically encodes the value to rlp, + // so we need to encode prior to sending to firewood + encodedVal, err := rlp.EncodeToBytes(val[:]) + require.NoError(t, err) + fwVals = append(fwVals, encodedVal) } next, set := tr.Commit(true) err = mergeSet.Merge(set) require.NoError(t, err) - err = tdb.TrieDB().Update(mergeSet) + err = tdb.TrieDB().Update(next, ethRoot, uint64(i), mergeSet, nil) require.NoError(t, err) // update firewood db diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod index 9807b06fc1ef..074292829348 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/go.mod @@ -1,60 +1,47 @@ module github.com/ava-labs/firewood/ffi/tests -go 1.23 +go 1.23.0 toolchain go1.23.6 require ( github.com/ava-labs/firewood/ffi/v2 v2.0.0 // this is replaced to use the parent folder - github.com/ethereum/go-ethereum v1.11.5 + github.com/ethereum/go-ethereum v1.15.7 + github.com/holiman/uint256 v1.3.2 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.1.0 ) require ( - github.com/DataDog/zstd v1.5.2 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.9.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect - github.com/cockroachdb/redact v1.1.3 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/bits-and-blooms/bitset v1.17.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/consensys/bavard v0.1.22 // indirect + github.com/consensys/gnark-crypto v0.14.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-ole/go-ole v1.2.1 // indirect - github.com/go-stack/stack v1.8.1 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/tklauser/go-sysconf v0.3.5 // indirect - github.com/tklauser/numcpus v0.2.2 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/ava-labs/firewood/ffi/v2 => ../ diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum index 8571ce7adf87..5c74782e9b64 100644 --- a/ffi/tests/go.sum +++ b/ffi/tests/go.sum @@ -1,50 +1,22 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= +github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= +github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,398 +24,76 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ= -github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= +github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From f235f3d7de65e62642862cd69ffe8f6c934c0f44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:32:01 +0000 Subject: [PATCH 0688/1053] build(deps): update lru requirement from 0.13.0 to 0.14.0 (#840) --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 8bace88b666a..a8b6f64adfba 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -16,7 +16,7 @@ smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" arc-swap = "1.7.1" -lru = "0.13.0" +lru = "0.14.0" metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" From cc1dfc810c5478ff302b5950b96d425f90bdd291 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 15 Apr 2025 19:17:52 -0400 Subject: [PATCH 0689/1053] chore(ffi): fix typo fwd_close_db comment (#843) Co-authored-by: Ron Kuris --- ffi/firewood.h | 2 +- ffi/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 965fa49d062f..465d3fe9956f 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -55,7 +55,7 @@ struct Value fwd_batch(void *db, const struct KeyValue *values); /** - * Close iand free the memory for a database handle + * Close and free the memory for a database handle * * # Safety * diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index d1b69ac6e1f4..c2a76aa73201 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -289,7 +289,7 @@ fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> Revision .build() } -/// Close iand free the memory for a database handle +/// Close and free the memory for a database handle /// /// # Safety /// From 25fc993f5f62d77214464455d226216e3bdcf542 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Apr 2025 10:36:42 -0700 Subject: [PATCH 0690/1053] Clippy was complaining that `Status` was too big (#844) --- grpc-testtool/src/service.rs | 12 ++++--- grpc-testtool/src/service/database.rs | 52 +++++++++++++++++++++------ grpc-testtool/src/service/db.rs | 8 ++--- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs index 4d2679596b9b..86b392492b68 100644 --- a/grpc-testtool/src/service.rs +++ b/grpc-testtool/src/service.rs @@ -17,18 +17,20 @@ pub mod db; pub mod process; trait IntoStatusResultExt { - fn into_status_result(self) -> Result; + fn into_status_result(self) -> Result>; } impl IntoStatusResultExt for Result { // We map errors from bad arguments into Status::invalid_argument; all other errors are Status::internal errors - fn into_status_result(self) -> Result { + fn into_status_result(self) -> Result> { self.map_err(|err| match err { Error::IncorrectRootHash { .. } | Error::HashNotFound { .. } | Error::RangeTooSmall => { - Status::invalid_argument(err.to_string()) + Box::new(Status::invalid_argument(err.to_string())) } - Error::IO { .. } | Error::InternalError { .. } => Status::internal(err.to_string()), - _ => Status::internal(err.to_string()), + Error::IO { .. } | Error::InternalError { .. } => { + Box::new(Status::internal(err.to_string())) + } + _ => Box::new(Status::internal(err.to_string())), }) } } diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs index 1d8f12a58f11..0218cd07a398 100644 --- a/grpc-testtool/src/service/database.rs +++ b/grpc-testtool/src/service/database.rs @@ -19,9 +19,13 @@ use tonic::{Request, Response, Status, async_trait}; impl Database for DatabaseService { async fn has(&self, request: Request) -> Result, Status> { let key = request.into_inner().key; - let revision = self.latest().await.into_status_result()?; + let revision = self.latest().await.into_status_result().map_err(|e| *e)?; - let val = revision.val(key).await.into_status_result()?; + let val = revision + .val(key) + .await + .into_status_result() + .map_err(|e| *e)?; let response = HasResponse { has: val.is_some(), @@ -33,12 +37,13 @@ impl Database for DatabaseService { async fn get(&self, request: Request) -> Result, Status> { let key = request.into_inner().key; - let revision = self.latest().await.into_status_result()?; + let revision = self.latest().await.into_status_result().map_err(|e| *e)?; let value = revision .val(key) .await - .into_status_result()? + .into_status_result() + .map_err(|e| *e)? .map(|v| v.to_vec()); let Some(value) = value else { @@ -56,8 +61,17 @@ impl Database for DatabaseService { async fn put(&self, request: Request) -> Result, Status> { let PutRequest { key, value } = request.into_inner(); let batch = BatchOp::Put { key, value }; - let proposal = self.db.propose(vec![batch]).await.into_status_result()?; - let _ = proposal.commit().await.into_status_result()?; + let proposal = self + .db + .propose(vec![batch]) + .await + .into_status_result() + .map_err(|e| *e)?; + let _ = proposal + .commit() + .await + .into_status_result() + .map_err(|e| *e)?; Ok(Response::new(PutResponse::default())) } @@ -68,8 +82,17 @@ impl Database for DatabaseService { ) -> Result, Status> { let DeleteRequest { key } = request.into_inner(); let batch = BatchOp::<_, Vec>::Delete { key }; - let proposal = self.db.propose(vec![batch]).await.into_status_result()?; - let _ = proposal.commit().await.into_status_result()?; + let proposal = self + .db + .propose(vec![batch]) + .await + .into_status_result() + .map_err(|e| *e)?; + let _ = proposal + .commit() + .await + .into_status_result() + .map_err(|e| *e)?; Ok(Response::new(DeleteResponse::default())) } @@ -106,8 +129,17 @@ impl Database for DatabaseService { .map(from_put_request) .chain(deletes.into_iter().map(from_delete_request)) .collect(); - let proposal = self.db.propose(batch).await.into_status_result()?; - let _ = proposal.commit().await.into_status_result()?; + let proposal = self + .db + .propose(batch) + .await + .into_status_result() + .map_err(|e| *e)?; + let _ = proposal + .commit() + .await + .into_status_result() + .map_err(|e| *e)?; Ok(Response::new(WriteBatchResponse::default())) } diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs index 7e038b82a04a..2f0e28c88de5 100644 --- a/grpc-testtool/src/service/db.rs +++ b/grpc-testtool/src/service/db.rs @@ -19,7 +19,7 @@ impl DbServerTrait for Database { _request: Request<()>, ) -> Result, Status> { todo!() - // let root_hash = self.db.root_hash().await.into_status_result()?.to_vec(); + // let root_hash = self.db.root_hash().await.into_status_result().map_err(|e| *e)?.to_vec(); // // let response = GetMerkleRootResponse { root_hash }; // @@ -33,7 +33,7 @@ impl DbServerTrait for Database { ) -> Result, Status> { todo!() // let GetProofRequest { key: _ } = request.into_inner(); - // let _revision = self.latest().await.into_status_result()?; + // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; } #[tracing::instrument(level = "trace")] @@ -50,7 +50,7 @@ impl DbServerTrait for Database { // key_limit: _, // } = request.into_inner(); - // let _revision = self.latest().await.into_status_result()?; + // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; } #[tracing::instrument(level = "trace")] @@ -66,7 +66,7 @@ impl DbServerTrait for Database { // expected_root_hash: _, // } = request.into_inner(); - // let _revision = self.latest().await.into_status_result()?; + // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; } #[tracing::instrument(level = "trace")] From 00d358539f8b2ea42f56b83e094af5dd427272a5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Apr 2025 13:07:00 -0700 Subject: [PATCH 0691/1053] Update CODEOWNERS (#846) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5a8f3a6bc94d..77cb728c8696 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @rkuris @richardpringle +* @rkuris @aaronbuchwald From f79fed1d665d98c3b49449d64c3bd0e1db0d373a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Apr 2025 13:55:50 -0700 Subject: [PATCH 0692/1053] Add READMEs to generated documentation (#847) --- benchmark/README.md | 14 +++++++------- benchmark/src/main.rs | 11 ++--------- fwdctl/README.md | 12 ++++++------ fwdctl/src/main.rs | 2 ++ grpc-testtool/src/lib.rs | 2 ++ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 18a35bcbc921..d95dcbc6e98d 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -132,16 +132,16 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a 3. ssh ubuntu@AWS-IP 4. Run the script in setup.sh on the instance as root 5. Log in to grafana on - a. username: admin, password: admin + a. username: admin, password: admin 6. When prompted, change the password (firewood_is_fast) 7. On the left panel, click "Data Sources" - a. Select "Prometheus" - b. For the URL, use - c. click "Save and test" + a. Select "Prometheus" + b. For the URL, use + c. click "Save and test" 8. On the left panel, click Dashboards - a. On the right top pulldown, click New->Import - b. Import the dashboard from the Grafana-dashboard.json file - c. Set the data source to Prometheus + a. On the right top pulldown, click New->Import + b. Import the dashboard from the Grafana-dashboard.json file + c. Set the data source to Prometheus 9. \[optional] Install a stock dashboard from [here](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) ## Usage diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index e5fe98500d97..4b5225242395 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -1,16 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. - -// The idea behind this benchmark: -// Phase 1: (setup) Generate known keys from the SHA256 of the row number, starting at 0, for 1B keys -// Phase 2: (steady-state) Continuously insert, delete, and update keys in the database - -// Phase 2 consists of: -// 1. 25% of batch size is inserting more rows like phase 1 -// 2. 25% of batch size is deleting rows from the beginning -// 3. 50% of batch size is updating rows in the middle, but setting the value to the hash of the first row inserted // +#![doc = include_str!("../README.md")] + use clap::{Parser, Subcommand, ValueEnum}; use fastrace_opentelemetry::OpenTelemetryReporter; use firewood::logger::trace; diff --git a/fwdctl/README.md b/fwdctl/README.md index 3696747e424a..c558a59c8ce9 100644 --- a/fwdctl/README.md +++ b/fwdctl/README.md @@ -30,19 +30,19 @@ $ fwdctl create firewood # Look inside, there are several folders representing different components of firewood, including the WAL. $ ls firewood ``` -* fwdctl get +* fwdctl get KEY ``` Get the value associated with a key in the database, if it exists. -fwdctl get +fwdctl get KEY ``` -* fwdctl insert +* fwdctl insert KEY VALUE ``` Insert a key/value pair into the database. -fwdctl insert +fwdctl insert KEY VALUE ``` -* fwdctl delete +* fwdctl delete KEY ``` Delete a key from the database, along with the associated value. -fwdctl delete +fwdctl delete KEY ``` diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 1c80537e753b..890e36b589fa 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -1,6 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![doc = include_str!("../README.md")] + use clap::{Parser, Subcommand}; use firewood::v2::api; diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs index 1d29ed4c30b3..4d331f2b921b 100644 --- a/grpc-testtool/src/lib.rs +++ b/grpc-testtool/src/lib.rs @@ -1,6 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![doc = include_str!("../README.md")] + pub mod sync { #![expect(clippy::missing_const_for_fn)] tonic::include_proto!("sync"); From 40fe0608ccb5ae3426b366bb0582d6ea8077871f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Apr 2025 14:43:10 -0700 Subject: [PATCH 0693/1053] Add some additional open checks (#845) Add some additional open checks If the database was created with a specific ethhash value, this prevents someone from opening it with a different value. Also, changing the sizes of the freelists is a database incompatible change, so we get the hash of that and store it in the header. This again prevents someone from opening an incompatible database. --- storage/src/nodestore.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index ab1ca50ebb2a..0d3febc77a1a 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -10,6 +10,7 @@ use coarsetime::Instant; use fastrace::local::LocalSpan; use metrics::counter; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::fmt::Debug; @@ -99,6 +100,14 @@ fn serializer() -> impl bincode::Options { DefaultOptions::new().with_varint_encoding() } +fn area_size_hash() -> TrieHash { + let mut hasher = Sha256::new(); + for size in AREA_SIZES { + hasher.update(size.to_ne_bytes()); + } + hasher.finalize().into() +} + // TODO: automate this, must stay in sync with above fn index_name(index: AreaIndex) -> &'static str { match index { @@ -267,6 +276,29 @@ impl NodeStore { )); } + if header.area_size_hash != area_size_hash().as_slice() { + return Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in area size hash", + )); + } + + #[cfg(not(feature = "ethhash"))] + if header.ethhash != 0 { + return Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created with ethhash enabled", + )); + } + + #[cfg(feature = "ethhash")] + if header.ethhash != 1 { + return Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created without ethhash enabled", + )); + } + let mut nodestore = Self { header, kind: Committed { @@ -612,6 +644,11 @@ struct NodeStoreHeader { /// Element i is the pointer to the first free block of size `BLOCK_SIZES[i]`. free_lists: FreeLists, root_address: Option, + /// The hash of the area sizes used in this database to prevent someone from changing the + /// area sizes and trying to read old databases with the wrong area sizes. + area_size_hash: [u8; 32], + /// Whether ethhash was enabled when this database was created. + ethhash: u64, } impl NodeStoreHeader { @@ -633,6 +670,11 @@ impl NodeStoreHeader { root_address: None, version: Version::new(), free_lists: Default::default(), + area_size_hash: area_size_hash().as_slice().try_into().unwrap(), + #[cfg(feature = "ethhash")] + ethhash: 1, + #[cfg(not(feature = "ethhash"))] + ethhash: 0, } } } From cc62c0d0127a6db1ecdc5bf5f21022fafaf1cec7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 18 Apr 2025 09:09:04 -0700 Subject: [PATCH 0694/1053] Downgrade upload-artifact (#849) upload-artifact@v4+ is not currently supported on GHES yet. More info: https://github.com/actions/upload-artifact?tab=readme-ov-file No easy way to test this without pushing to main I think. --- .github/workflows/gh-pages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index ae644e8d0b43..f3bf17d77bc1 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -30,7 +30,7 @@ jobs: run: | cp -rv target/doc/* ./_site cp -rv docs/assets ./_site - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 with: name: pages path: _site From 45d00d3ad08c03105b023465e55ec22234a5f7aa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 18 Apr 2025 12:57:52 -0700 Subject: [PATCH 0695/1053] Clean up installation scripts (#852) --- benchmark/README.md | 14 ++-- benchmark/setup-scripts/README.md | 25 +++++++ benchmark/setup-scripts/build-environment.sh | 36 ++++++++++ benchmark/setup-scripts/build-firewood.sh | 24 +++++++ benchmark/setup-scripts/install-grafana.sh | 75 +++++++++++++++++++ benchmark/setup-scripts/run-benchmarks.sh | 40 +++++++++++ benchmark/setup.sh | 76 -------------------- 7 files changed, 205 insertions(+), 85 deletions(-) create mode 100644 benchmark/setup-scripts/README.md create mode 100644 benchmark/setup-scripts/build-environment.sh create mode 100644 benchmark/setup-scripts/build-firewood.sh create mode 100644 benchmark/setup-scripts/install-grafana.sh create mode 100644 benchmark/setup-scripts/run-benchmarks.sh delete mode 100644 benchmark/setup.sh diff --git a/benchmark/README.md b/benchmark/README.md index d95dcbc6e98d..21ba986e9882 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -108,14 +108,10 @@ This test repeatedly updates the first row. To install the Firewood Benchmark, follow these steps: -1. Clone the repository: `git clone https://github.com/ava-labs/firewood.git` -2. Navigate to the firewood directory: `cd firewood` -3. Build the executable: `cargo build --release` -4. Create the benchmark database: `nohup time cargo run --profile maxperf --bin benchmark -- create`. For a larger database, add `--number-of-batches=10000` before the subcommand 'create' for a 100M row database (each batch by default is 10K rows). -5. \[Optional] Save the benchmark database in rev_db -6. Run the benchmark you want: `nohup time cargo run --profile maxperf --bin benchmark -- NAME` (selecting NAME from the list above). If you're not using the default database size, make sure you specify the number of batches here as well. - -As the benchmark is running, statistics for prometheus are availble on port 3000 (by default). +1. Install the build prerequisites: `sudo bash setup-scripts/build-environment.sh` +2. \[Optional\] Install grafana: `sudo bash setup-scripts/install-grafana.sh` +3. Build firewood: `bash setup-scripts/build-firewood.sh` +4. Create the benchmark database: `nohup time cargo run --profile maxperf --bin benchmark -- create`. For a larger database, add `--number-of-batches=10000` before the subcommand 'create' for a 100M row database (each batch by default is 10K rows). Additional options are documented in setup-scripts/run-benchmarks.sh If you want to install grafana and prometheus on an AWS host (using Ubuntu as a base), do the following: @@ -130,7 +126,7 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a 7. \[optional] Save money by selecting 'spot instance' in advanced 8. Launch the instance 3. ssh ubuntu@AWS-IP -4. Run the script in setup.sh on the instance as root +4. Run the scripts as described above, including the grafana installation. 5. Log in to grafana on a. username: admin, password: admin 6. When prompted, change the password (firewood_is_fast) diff --git a/benchmark/setup-scripts/README.md b/benchmark/setup-scripts/README.md new file mode 100644 index 000000000000..b182c9b7d5a0 --- /dev/null +++ b/benchmark/setup-scripts/README.md @@ -0,0 +1,25 @@ +# Setup Scripts + +This directory contains the scripts needed to set up the firewood benchmarks, as follows: + +```bash +sudo bash build-environment.sh +``` + +This script sets up the build environment, including installing the firewood build dependencies. + +```bash +sudo bash install-grafana.sh +``` + +This script sets up grafana to listen on port 3000 for firewood. It also sets up listening +for coreth as well, on port 6060, with the special metrics path coreth expects. + +```bash +bash build-firewood.sh +``` + +This script checks out and builds firewood. It assumes you have already set up the build environment earlier. + +The final script, `run-benchmarks.sh`, is a set of commands that can be copied/pasted to run individual +benchmarks of different sizes. diff --git a/benchmark/setup-scripts/build-environment.sh b/benchmark/setup-scripts/build-environment.sh new file mode 100644 index 000000000000..06a5442d141d --- /dev/null +++ b/benchmark/setup-scripts/build-environment.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# This script sets up the build environment, including installing the firewood build dependencies. +set -o errexit + +if [ "$EUID" -ne 0 ]; then + echo "This script must be run as root" >&2 + exit 1 +fi + +apt upgrade -y + +# install the build dependency packages +pkgs=(git protobuf-compiler build-essential apt-transport-https net-tools zfsutils-linux) +install_pkgs=() +for pkg in "${pkgs[@]}"; do + if ! dpkg -s "$pkg" > /dev/null 2>&1; then + install_pkgs+=("$pkg") + fi +done +if [ "${#install_pkgs[@]}" -gt 0 ]; then + apt-get install -y "${install_pkgs[@]}" +fi + +# If there is an NVMe device, format it and mount it to /mnt/nvme/ubuntu/firewood +# this happens on amazon ec2 instances +NVME_DEV="$(realpath /dev/disk/by-id/nvme-Amazon_EC2_NVMe_Instance_Storage_* | uniq)" +if [ -n "$NVME_DEV" ]; then + mkfs.ext4 -E nodiscard -i 6291456 "$NVME_DEV" + NVME_MOUNT=/mnt/nvme + mkdir -p "$NVME_MOUNT" + mount -o noatime "$NVME_DEV" "$NVME_MOUNT" + echo "$NVME_DEV $NVME_MOUNT ext4 noatime 0 0" >> /etc/fstab + mkdir -p "$NVME_MOUNT/ubuntu/firewood" + chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" "$NVME_MOUNT/ubuntu/firewood" + ln -s "$NVME_MOUNT/ubuntu/firewood" /home/ubuntu/firewood +fi diff --git a/benchmark/setup-scripts/build-firewood.sh b/benchmark/setup-scripts/build-firewood.sh new file mode 100644 index 000000000000..06ae19c202a1 --- /dev/null +++ b/benchmark/setup-scripts/build-firewood.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -o errexit + +if [ "$EUID" -eq 0 ]; then + echo "This script should be run as a non-root user" + exit 1 +fi + +# install rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +. "$HOME/.cargo/env" + +# clone the firewood repository +if [ ! -d "$HOME/firewood" ]; then + mkdir -p "$HOME/firewood" +fi +pushd "$HOME/firewood" + +git clone https://github.com/ava-labs/firewood.git . + +# build the firewood binary +cargo build --profile maxperf +popd + diff --git a/benchmark/setup-scripts/install-grafana.sh b/benchmark/setup-scripts/install-grafana.sh new file mode 100644 index 000000000000..36e05c81e067 --- /dev/null +++ b/benchmark/setup-scripts/install-grafana.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -o errexit + +# install the keyrings needed to validate the grafana apt repository +if ! [ -d /etc/apt/keyrings ]; then + mkdir -p /etc/apt/keyrings/ +fi +if ! [ -f /etc/apt/keyrings/grafana.gpg ]; then + wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null + echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list +fi +apt-get update + +# set up the systemd configuration to allow grafana to bind to port 80 +if ! [ -d /etc/systemd/system/grafana-server.service.d ]; then + mkdir -p /etc/systemd/system/grafana-server.service.d +fi + +if ! [ -f /etc/systemd/system/grafana-server.service.d/override.conf ]; then + cat > /etc/systemd/system/grafana-server.service.d/override.conf < /dev/null 2>&1; then + install_pkgs+=("$pkg") + fi +done +if [ "${#install_pkgs[@]}" -gt 0 ]; then + apt-get install -y "${install_pkgs[@]}" +fi + +# configure grafana to listen on port 80 +if ! grep -q '^http_port = 80$' /etc/grafana/grafana.ini; then + perl -pi -e 's/^;?http_port = .*/http_port = 80/' /etc/grafana/grafana.ini +fi + +# configure prometheus to scrape firewood +if ! grep -q '^ - job_name: firewood$' /etc/prometheus/prometheus.yml; then + cat >> /etc/prometheus/prometheus.yml <> /etc/default/prometheus-node-exporter < /dev/null -echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list -apt-get update -apt-get upgrade -y - -mkdir -p /etc/systemd/system/grafana-server.service.d -cat > /etc/systemd/system/grafana-server.service.d/override.conf <> /etc/prometheus/prometheus.yml <> /etc/default/prometheus-node-exporter <> /etc/fstab - mkdir -p "$NVME_MOUNT/ubuntu/firewood" - chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" "$NVME_MOUNT/ubuntu/firewood" - ln -s "$NVME_MOUNT/ubuntu/firewood" /home/ubuntu/firewood -fi - - -#### you can switch to the ubuntu user here #### - -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -. "$HOME/.cargo/env" -cd firewood -git clone https://github.com/ava-labs/firewood.git . -git checkout rkuris/prometheus -cargo build --profile maxperf - -#### stop here, these commands are run by hand #### - -# 10M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 create & -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 zipf & -nohup time cargo run --profile maxperf --bin benchmark -- -n 1000 single & - -# 50M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 create & -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 zipf & -nohup time cargo run --profile maxperf --bin benchmark -- -n 5000 single & - -# 100M rows: -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 create & -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 zipf & -nohup time cargo run --profile maxperf --bin benchmark -- -n 10000 single & From 0f3a1c9761ae25d1b4f59e390a9cbd823ee15148 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 18 Apr 2025 16:48:34 -0400 Subject: [PATCH 0696/1053] fix typo in benchmark readme (#851) --- benchmark/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/README.md b/benchmark/README.md index 21ba986e9882..3a02bc15b36d 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -19,7 +19,7 @@ There are currently three different benchmarks, as follows: 1. `tenkrandom` which does transactions of size 10k, 5k updates, 2.5k inserts, and 2.5k deletes 2. `zipf` which uses a zipf distribution across the database to perform updates -3. `single` which only updates a single row (row 1) repeatedly in a tiny transactoin +3. `single` which only updates a single row (row 1) repeatedly in a tiny transaction There is also a `create` benchmark which creates the database to begin with. The defaults will create a 10M row database. If you want a larger one, increase the number of batches. From 123aeb89bfe4f67bd615dc6917ed2f3b55642741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:17:21 +0000 Subject: [PATCH 0697/1053] build(deps): update metrics-exporter-prometheus requirement from 0.16.1 to 0.17.0 (#853) --- benchmark/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 198f23aa73dd..3189ebfebf9d 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" metrics = "0.24.1" metrics-util = "0.19.0" -metrics-exporter-prometheus = "0.16.1" +metrics-exporter-prometheus = "0.17.0" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.9.0" rand_distr = "0.5.0" From a3500cdcfd68678c17e5290478219d784d9f2c7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:15:17 +0000 Subject: [PATCH 0698/1053] build(deps): update rand requirement from 0.8.5 to 0.9.1 (#850) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- grpc-testtool/Cargo.toml | 3 ++- grpc-testtool/benches/insert.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 9c71c9da375b..aa1ddcf84e04 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -34,7 +34,8 @@ tonic-build = "0.13.0" [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} -rand = "0.8.5" +rand = "0.9.1" +rand_distr = "0.5.0" [lints.rust] unsafe_code = "deny" diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs index 017a6ed4d901..39fad985fab5 100644 --- a/grpc-testtool/benches/insert.rs +++ b/grpc-testtool/benches/insert.rs @@ -2,8 +2,8 @@ // See the file LICENSE.md for licensing terms. use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; -use rand::distributions::Alphanumeric; use rand::{Rng, SeedableRng}; +use rand_distr::Alphanumeric; use std::borrow::BorrowMut as _; use std::cell::RefCell; use std::env; From e0f6dcc4e928531b86f9e86ad3630f72c96facfd Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 22 Apr 2025 10:20:26 -0400 Subject: [PATCH 0699/1053] Update prereqs to include cargo and make (#848) Co-authored-by: Ron Kuris --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6d34bb466e34..fb5e51fca8ea 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,11 @@ Firewood guarantees recoverability by not referencing the new nodes in a new rev ## Build -In order to build firewood, `protoc` must be installed. See instructions for installation [here](https://grpc.io/docs/protoc-installation/). +In order to build firewood, the following dependencies must be installed: -On Mac, you can install via brew: - -```sh -brew install protobuf -``` +- `protoc` See instructions for installation [here](https://grpc.io/docs/protoc-installation/). +- `cargo` See instructions for installation [here](https://doc.rust-lang.org/cargo/getting-started/installation.html). +- `make` See download instructions [here](https://www.gnu.org/software/make/#download) or run `sudo apt install build-essential` on Linux. ## Run From 0cbb28b4654ca75edf306a751ec10ee6c2c355c1 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Apr 2025 07:58:36 -0700 Subject: [PATCH 0700/1053] GH Pages fixes (#858) --- .github/workflows/gh-pages.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index f3bf17d77bc1..aa28c22fa0fe 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -4,6 +4,7 @@ on: push: branches: - "main" + - "rkuris/gh-pages" env: CARGO_TERM_COLOR: always @@ -30,7 +31,7 @@ jobs: run: | cp -rv target/doc/* ./_site cp -rv docs/assets ./_site - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: pages path: _site @@ -60,4 +61,4 @@ jobs: path: . - name: Deploy to GitHub pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 From 9937d6775e0fddd17dfc5569da1b8c57bc46104d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 24 Apr 2025 15:28:22 -0700 Subject: [PATCH 0701/1053] Doc fixes (#859) --- firewood/src/v2/api.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 5bca2a3117e9..614b5d79970a 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -153,9 +153,10 @@ impl From for Error { } } -/// The database interface, which includes a type for a static view of -/// the database (the DbView). The most common implementation of the DbView -/// is the api::DbView trait defined next. +/// The database interface. The methods here operate on the most +/// recently committed revision, and allow the creation of a new +/// [Proposal] or a new [DbView] based on a specific historical +/// revision. #[async_trait] pub trait Db { /// The type of a historical revision @@ -197,15 +198,14 @@ pub trait Db { Self: 'p; } -/// A view of the database at a specific time. These are wrapped with -/// a Weak reference when fetching via a call to [Db::revision], as these -/// can disappear because they became too old. +/// A view of the database at a specific time. /// -/// You only need a DbView if you need to read from a snapshot at a given -/// root. Don't hold a strong reference to the DbView as it prevents older -/// views from being cleaned up. -/// -/// A [Proposal] requires implementing DbView +/// There are a few ways to create a [DbView]: +/// 1. From [Db::revision] which gives you a view for a specific +/// historical revision +/// 2. From [Db::propose] which is a view on top of the most recently +/// committed revision with changes applied; or +/// 3. From [Proposal::propose] which is a view on top of another proposal. #[async_trait] pub trait DbView { /// The type of a stream of key/value pairs From f192b76d06509b29ed782f2b99dfb4d89742a6fe Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 1 May 2025 09:45:34 -0400 Subject: [PATCH 0702/1053] fix(ffi): prevent memory leak and tips for finding leaks (#862) --- ffi/README.md | 20 ++++++++++++++++++++ ffi/firewood.go | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/ffi/README.md b/ffi/README.md index 65feb27f2e6b..58ded17729b0 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -8,3 +8,23 @@ First, build the release version (`cargo build --release`). This creates the ffi interface file "firewood.h" as a side effect. Then, you can run the tests in go, using `go test .` + +## Development +Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. + +### CGO Regeneration + +As you edit any Rust code and save the file in VS Code, the `firewood.h` file is automatically updated with edited function and struct definitions. However, the Go linter will not recognize these changes until you manually regenerate the cgo wrappers. To do this, you can run `go tool cgo firewood.go`. Alternatively, in VS Code, right above the `import "C"` definition, you can click on the small letters saying "regenerate CGO definitions". This will allow the linter to use the altered definitions. + +Because the C header file is autogenerated from the Rust code, the naming matches exactly (due to the `no_mangle` macro). However, the C definitions imported in Go do not match exactly, and are prefixed with `struct_`. Function naming is the same as the header file. These names are generated by the `go tool cgo` command above. + +### Testing +Although the VS Code testing feature does work, there are some quirks in ensuring proper building. The Rust code must be compiled separated, and sometimes the `go test` command continues to use a cached result. Whenever testing after making changes to the Rust/C builds, the cache should be cleared if results don't seem correct. Do not compile with `--features ethhash`, as some tests will fail. + +To ensure there are no memory leaks, the easiest way is to use your preferred CLI tool (e.g. `valgrind` for Linux, `leaks` for macOS) and compile the tests into a binary. You must not compile a release binary to ensure all memory can be managed. An example flow is given below. +``` +cd ffi +cargo build # use debug +go test -a -c -o binary_file # ignore cache +leaks --nostacks --atExit -- ./binary_file +``` \ No newline at end of file diff --git a/ffi/firewood.go b/ffi/firewood.go index ebe7688af99c..46a938d332ac 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -5,6 +5,7 @@ package firewood // // Note that -lm is required on Linux but not on Mac. // #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm +// #include // #include "firewood.h" import "C" @@ -84,6 +85,9 @@ func New(filePath string, conf *Config) (*Database, error) { } else { db = C.fwd_open_db(args) } + + // After creating the db, we can safely free the path string. + C.free(unsafe.Pointer(args.path)) return &Database{handle: db}, nil } From 5537e2098946f3af991d2e3419c1d2e039592d68 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 5 May 2025 17:33:18 -0400 Subject: [PATCH 0703/1053] feat(ffi): ffi error messages (#860) --- ffi/firewood.go | 62 +++++++++++++++++---- ffi/firewood_test.go | 34 ++++++++++-- ffi/kvbackend.go | 20 +++++-- ffi/src/lib.rs | 127 ++++++++++++++++++++++++++++++++----------- 4 files changed, 192 insertions(+), 51 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 46a938d332ac..3185fab24994 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -10,12 +10,24 @@ package firewood import "C" import ( + "errors" "fmt" "runtime" + "strings" "unsafe" ) +// These constants are used to identify errors returned by the Firewood Rust FFI. +// These must be changed if the Rust FFI changes - should be reported by tests. +const ( + rootHashNotFound = "IO error: Root hash not found" + keyNotFound = "key not found" +) + +var dbClosedErr = errors.New("firewood database already closed") + // A Database is a handle to a Firewood database. +// It is not safe to call these methods with a nil handle. type Database struct { // handle is returned and accepted by cgo functions. It MUST be treated as // an opaque value without special meaning. @@ -105,7 +117,7 @@ type KeyValue struct { // // WARNING: a consequence of prefix deletion is that calling Batch with an empty // key and value will delete the entire database. -func (db *Database) Batch(ops []KeyValue) []byte { +func (db *Database) Batch(ops []KeyValue) ([]byte, error) { // TODO(arr4n) refactor this to require explicit signalling from the caller // that they want prefix deletion, similar to `rm --no-preserve-root`. @@ -130,34 +142,62 @@ func (db *Database) Batch(ops []KeyValue) []byte { // extractBytesThenFree converts the cgo `Value` payload to a byte slice, frees // the `Value`, and returns the extracted slice. -func extractBytesThenFree(v *C.struct_Value) []byte { - buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) +// Generates error if the error term is nonnull. +func extractBytesThenFree(v *C.struct_Value) (buf []byte, err error) { + buf = C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) + if v.len == 0 { + errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) + err = fmt.Errorf("firewood error: %s", errStr) + } C.fwd_free_value(v) - return buf + + // Pin the returned value to prevent it from being garbage collected. + runtime.KeepAlive(v) + return } // Get retrieves the value for the given key. It always returns a nil error. // If the key is not found, the return value will be (nil, nil). func (db *Database) Get(key []byte) ([]byte, error) { + if db.handle == nil { + return nil, dbClosedErr + } + values, cleanup := newValueFactory() defer cleanup() val := C.fwd_get(db.handle, values.from(key)) - bytes := extractBytesThenFree(&val) - if len(bytes) == 0 { + bytes, err := extractBytesThenFree(&val) + + // If the root hash or key is not found, return nil. + if err != nil && (strings.Contains(err.Error(), rootHashNotFound) || strings.Contains(err.Error(), keyNotFound)) { return nil, nil } - return bytes, nil + return bytes, err } // Root returns the current root hash of the trie. -func (db *Database) Root() []byte { +// Empty trie must return common.Hash{}. +func (db *Database) Root() ([]byte, error) { + if db.handle == nil { + return nil, dbClosedErr + } hash := C.fwd_root_hash(db.handle) - return extractBytesThenFree(&hash) + bytes, err := extractBytesThenFree(&hash) + + // If the root hash is not found, return a zeroed slice. + if err != nil && strings.Contains(err.Error(), rootHashNotFound) { + bytes = make([]byte, 32) + err = nil + } + return bytes, err } -// Close closes the database and releases all held resources. It always returns -// nil. +// Close closes the database and releases all held resources. +// Returns an error if already closed. func (db *Database) Close() error { + if db.handle == nil { + return dbClosedErr + } C.fwd_close_db(db.handle) db.handle = nil return nil diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index a15ad2a34475..328267a21e69 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -79,6 +79,30 @@ func TestGetNonExistent(t *testing.T) { assert.Nil(t, got) } +// Attempt to make a call to a nil or invalid handle. +// Each function should return an error and not panic. +func TestGetBadHandle(t *testing.T) { + db := &Database{handle: nil} + + // This ignores error, but still shouldn't panic. + _, err := db.Get([]byte("non-existent")) + assert.ErrorIs(t, err, dbClosedErr) + + // We ignore the error, but it shouldn't panic. + _, err = db.Root() + assert.ErrorIs(t, err, dbClosedErr) + + root, err := db.Update( + [][]byte{[]byte("key")}, + [][]byte{[]byte("value")}, + ) + assert.Empty(t, root) + assert.ErrorIs(t, err, dbClosedErr) + + err = db.Close() + require.ErrorIs(t, err, dbClosedErr) +} + func keyForTest(i int) []byte { return []byte("key" + strconv.Itoa(i)) } @@ -102,7 +126,7 @@ func TestInsert100(t *testing.T) { { name: "Batch", insert: func(db *Database, kvs []KeyValue) ([]byte, error) { - return db.Batch(kvs), nil + return db.Batch(kvs) }, }, { @@ -137,7 +161,8 @@ func TestInsert100(t *testing.T) { assert.Equal(t, want, string(got), "Recover nth batch-inserted value") } - hash := db.Root() + hash, err := db.Root() + assert.NoError(t, err, "%T.Root()", db) assert.Lenf(t, hash, 32, "%T.Root()", db) // we know the hash starts with 0xf8 assert.Equalf(t, byte(0xf8), hash[0], "First byte of %T.Root()", db) @@ -175,8 +200,9 @@ func TestRangeDelete(t *testing.T) { func TestInvariants(t *testing.T) { db := newTestDatabase(t) - - assert.Equalf(t, make([]byte, 32), db.Root(), "%T.Root() of empty trie") + hash, err := db.Root() + require.NoError(t, err, "%T.Root()", db) + assert.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie") got, err := db.Get([]byte("non-existent")) require.NoError(t, err) diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index bc52eb7b4f37..d48c270e30ec 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -10,7 +10,7 @@ type kVBackend interface { // Returns the current root hash of the trie. // Empty trie must return common.Hash{}. // Length of the returned slice must be common.HashLength. - Root() []byte + Root() ([]byte, error) // Get retrieves the value for the given key. // If the key does not exist, it must return (nil, nil). @@ -41,21 +41,33 @@ type kVBackend interface { } // Prefetch is a no-op since we don't need to prefetch for Firewood. -func (*Database) Prefetch(key []byte) ([]byte, error) { +func (db *Database) Prefetch(key []byte) ([]byte, error) { + if db.handle == nil { + return nil, dbClosedErr + } + return nil, nil } // Commit is a no-op, since [Database.Update] already persists changes. -func (*Database) Commit(root []byte) error { +func (db *Database) Commit(root []byte) error { + if db.handle == nil { + return dbClosedErr + } + return nil } // Update batches all the keys and values and applies them to the // database. func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { + if db.handle == nil { + return nil, dbClosedErr + } + ops := make([]KeyValue, len(keys)) for i := range keys { ops[i] = KeyValue{keys[i], vals[i]} } - return db.Batch(ops), nil + return db.Batch(ops) } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index c2a76aa73201..4236fffef970 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::ffi::{CStr, OsStr}; +use std::ffi::{CStr, CString, OsStr, c_char}; use std::fmt::{self, Display, Formatter}; use std::os::unix::ffi::OsStrExt as _; use std::path::Path; @@ -43,20 +43,33 @@ impl Display for Value { /// * call `free_value` to free the memory associated with the returned `Value` #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get(db: *mut Db, key: Value) -> Value { - let db = unsafe { db.as_ref() }.expect("db should be non-null"); - let root = db.root_hash_sync(); - let Ok(Some(root)) = root else { - return Value { - len: 0, - data: std::ptr::null(), - }; - }; - let rev = db.revision_sync(root).expect("revision should exist"); + get(db, key).unwrap_or_else(|e| e.into()) +} + +/// cbindgen::ignore +/// +/// This function is not exposed to the C API. +/// Internal call for `fwd_get` to remove error handling from the C API +fn get(db: *mut Db, key: Value) -> Result { + // Check db is valid. + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Find root hash. + // Matches `hash` function but we use the TrieHash type here + let root = db + .root_hash_sync() + .map_err(|e| e.to_string())? + .ok_or_else(|| String::from("unexpected None from db.root_hash_sync"))?; + + // Find revision assoicated with root. + let rev = db.revision_sync(root).map_err(|e| e.to_string())?; + + // Get value associated with key. let value = rev .val_sync(key.as_slice()) - .expect("get should succeed") - .unwrap_or_default(); - value.into() + .map_err(|e| e.to_string())? + .ok_or_else(|| String::from("key not found"))?; + Ok(value.into()) } /// A `KeyValue` struct that represents a key-value pair in the database. @@ -84,11 +97,23 @@ pub struct KeyValue { /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Value { + batch(db, nkeys, values).unwrap_or_else(|e| e.into()) +} + +/// cbindgen::ignore +/// +/// This function is not exposed to the C API. +/// Internal call for `fwd_batch` to remove error handling from the C API +fn batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Result { let start = coarsetime::Instant::now(); - let db = unsafe { db.as_ref() }.expect("db should be non-null"); + // Check db is valid. + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Create a batch of operations to perform. let mut batch = Vec::with_capacity(nkeys); for i in 0..nkeys { - let kv = unsafe { values.add(i).as_ref() }.expect("values should be non-null"); + let kv = unsafe { values.add(i).as_ref() } + .ok_or_else(|| String::from("couldn't get key-value pair"))?; if kv.value.len == 0 { batch.push(DbBatchOp::DeleteRange { prefix: kv.key.as_slice(), @@ -100,16 +125,22 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key value: kv.value.as_slice(), }); } - let proposal = db.propose_sync(batch).expect("proposal should succeed"); + + // Propose the batch of operations. + let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; let propose_time = start.elapsed().as_millis(); counter!("firewood.ffi.propose_ms").increment(propose_time); - proposal.commit_sync().expect("commit should succeed"); - let hash = hash(db); + + // Commit the proposal. + proposal.commit_sync().map_err(|e| e.to_string())?; + + // Get the root hash of the database post-commit. + let hash_val = hash(db)?; let propose_plus_commit_time = start.elapsed().as_millis(); counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); counter!("firewood.ffi.commit_ms").increment(propose_plus_commit_time - propose_time); counter!("firewood.ffi.batch").increment(1); - hash + Ok(hash_val) } /// Get the root hash of the latest version of the database @@ -121,7 +152,19 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key /// The caller must ensure that `db` is a valid pointer returned by `open_db` #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { - let db = unsafe { db.as_ref() }.expect("db should be non-null"); + // Check db is valid. + root_hash(db).unwrap_or_else(|e| e.into()) +} + +/// cbindgen::ignore +/// +/// This function is not exposed to the C API. +/// Internal call for `fwd_root_hash` to remove error handling from the C API +fn root_hash(db: *mut Db) -> Result { + // Check db is valid. + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Get the root hash of the database. hash(db) } @@ -129,9 +172,11 @@ pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { /// /// This function is not exposed to the C API. /// It returns the current hash of an already-fetched database handle -fn hash(db: &Db) -> Value { - let root = db.root_hash_sync().unwrap_or_default().unwrap_or_default(); - Value::from(root.as_slice()) +fn hash(db: &Db) -> Result { + db.root_hash_sync() + .map_err(|e| e.to_string())? + .ok_or_else(|| String::from("unexpected None from db.root_hash_sync")) + .map(|root| Value::from(root.as_slice())) } impl Value { @@ -155,6 +200,17 @@ impl From> for Value { } } +impl From for Value { + fn from(s: String) -> Self { + // Create empty CString if s is null. + let cstr = CString::new(s).unwrap_or_default().into_raw(); + Value { + len: 0, + data: cstr.cast::(), + } + } +} + /// Frees the memory associated with a `Value`. /// /// # Safety @@ -163,17 +219,24 @@ impl From> for Value { /// The caller must ensure that `value` is a valid pointer. #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_free_value(value: *const Value) { + // Create a Rust reference to the pointer. + // Unsafe because we are dereferencing a raw pointer - possible another thread + // has a mutable reference to the same memory. let value = unsafe { &*value as &Value }; - if value.len == 0 { - return; + // We assume that if the length is 0, then the data is a null-terminated string. + if value.len > 0 { + let recreated_box = unsafe { + Box::from_raw(std::slice::from_raw_parts_mut( + value.data as *mut u8, + value.len, + )) + }; + drop(recreated_box); + } else { + let raw_str = value.data as *mut c_char; + let cstr = unsafe { CString::from_raw(raw_str) }; + drop(cstr); } - let recreated_box = unsafe { - Box::from_raw(std::slice::from_raw_parts_mut( - value.data as *mut u8, - value.len, - )) - }; - drop(recreated_box); } /// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. From 44c963a4517a6642deb1d7b416066028bffb4c4d Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 7 May 2025 12:11:24 -0400 Subject: [PATCH 0704/1053] fix(src): drop unused revisions (#866) --- firewood/src/db.rs | 107 ++++++++++++++++++++++++++++++++++++++++ firewood/src/manager.rs | 6 ++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 841df7e7e4bf..c42ec39ed80d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -523,6 +523,113 @@ mod test { assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); } + #[tokio::test] + // test that dropping a proposal removes it from the list of known proposals + // /-> P1 - will get committed + // R1 --> P2 - will get dropped + // \-> P3 - will get orphaned, but it's still known + async fn test_proposal_scope_historic() { + let db = testdb().await; + let batch1 = vec![BatchOp::Put { + key: b"k1", + value: b"v1", + }]; + let proposal1 = db.propose(batch1).await.unwrap(); + assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); + + let batch2 = vec![BatchOp::Put { + key: b"k2", + value: b"v2", + }]; + let proposal2 = db.propose(batch2).await.unwrap(); + assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); + + let batch3 = vec![BatchOp::Put { + key: b"k3", + value: b"v3", + }]; + let proposal3 = db.propose(batch3).await.unwrap(); + assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + + // the proposal is dropped here, but the underlying + // nodestore is still accessible because it's referenced by the revision manager + // The third proposal remains referenced + let p2hash = proposal2.root_hash().await.unwrap().unwrap(); + assert!(db.all_hashes().await.unwrap().contains(&p2hash)); + drop(proposal2); + + // commit the first proposal + proposal1.commit().await.unwrap(); + // Ensure we committed the first proposal's data + let committed = db.root_hash().await.unwrap().unwrap(); + let historical = db.revision(committed).await.unwrap(); + assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); + + // the second proposal shouldn't be available to commit anymore + assert!(!db.all_hashes().await.unwrap().contains(&p2hash)); + + // the third proposal should still be contained within the all_hashes list + // would be deleted if another proposal was committed and proposal3 was dropped here + let hash3 = proposal3.root_hash().await.unwrap().unwrap(); + assert!(db.manager.read().unwrap().all_hashes().contains(&hash3)); + } + + #[tokio::test] + // test that dropping a proposal removes it from the list of known proposals + // R1 - base revision + // \-> P1 - will get committed + // \-> P2 - will get dropped + // \-> P3 - will get orphaned, but it's still known + async fn test_proposal_scope_orphan() { + let db = testdb().await; + let batch1 = vec![BatchOp::Put { + key: b"k1", + value: b"v1", + }]; + let proposal1 = db.propose(batch1).await.unwrap(); + assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); + + let batch2 = vec![BatchOp::Put { + key: b"k2", + value: b"v2", + }]; + let proposal2 = proposal1.clone().propose(batch2).await.unwrap(); + assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); + + let batch3 = vec![BatchOp::Put { + key: b"k3", + value: b"v3", + }]; + let proposal3 = proposal2.clone().propose(batch3).await.unwrap(); + assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + + // the proposal is dropped here, but the underlying + // nodestore is still accessible because it's referenced by the revision manager + // The third proposal remains referenced + let p2hash = proposal2.root_hash().await.unwrap().unwrap(); + assert!(db.all_hashes().await.unwrap().contains(&p2hash)); + drop(proposal2); + + // commit the first proposal + proposal1.commit().await.unwrap(); + // Ensure we committed the first proposal's data + let committed = db.root_hash().await.unwrap().unwrap(); + let historical = db.revision(committed).await.unwrap(); + assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); + + // the second proposal shouldn't be available to commit anymore + assert!(!db.all_hashes().await.unwrap().contains(&p2hash)); + + // the third proposal should still be contained within the all_hashes list + let hash3 = proposal3.root_hash().await.unwrap().unwrap(); + assert!(db.manager.read().unwrap().all_hashes().contains(&hash3)); + + // moreover, the data from the second and third proposals should still be available + // through proposal3 + assert_eq!(&*proposal3.val(b"k2").await.unwrap().unwrap(), b"v2"); + assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index fddc10f659da..38458ac100ae 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -190,8 +190,10 @@ impl RevisionManager { proposal.flush_header()?; // 8. Proposal Cleanup - // first remove the committing proposal from the list of outstanding proposals - self.proposals.retain(|p| !Arc::ptr_eq(&proposal, p)); + // Free proposal that is being committed as well as any proposals no longer + // referenced by anyone else. + self.proposals + .retain(|p| !Arc::ptr_eq(&proposal, p) && Arc::strong_count(p) > 1); // then reparent any proposals that have this proposal as a parent for p in self.proposals.iter() { From 5b0292d7c961b63e4b3de4846f10b8617f12270f Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 8 May 2025 15:39:59 -0400 Subject: [PATCH 0705/1053] Add GH token to arduino/setup-protoc (#869) --- .github/workflows/ci.yaml | 14 ++++++++++++++ .github/workflows/default-branch-cache.yaml | 4 +++- .github/workflows/gh-pages.yaml | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6ac66e81e8c..882bb788cbcf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,6 +16,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 - name: Check run: cargo check --workspace --tests --examples --benches @@ -30,6 +32,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -53,6 +57,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -72,6 +78,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -88,6 +96,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -101,6 +111,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -124,6 +136,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 - name: Build Firewood FFI (with ethhash) run: cargo build --release --features ethhash diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 409689ee949d..97b1c761a1cc 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -17,7 +17,9 @@ jobs: steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v2 + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index aa28c22fa0fe..7343aab88c91 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -16,6 +16,8 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} # caution: this is the same restore as in ci.yaml - uses: Swatinem/rust-cache@v2 with: From a0a737a068201611ca12274f6e89ce83f1e6e199 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 9 May 2025 08:29:31 -0700 Subject: [PATCH 0706/1053] feat(ffi): proposal creation isolated from committing (#867) Co-authored-by: Austin Larson --- ffi/firewood.go | 82 +++++++-------- ffi/firewood.h | 100 ++++++++++++++++-- ffi/firewood_test.go | 129 +++++++++++++++++------ ffi/kvbackend.go | 6 +- ffi/memory.go | 124 ++++++++++++++++++++++ ffi/proposal.go | 46 ++++++++ ffi/src/lib.rs | 242 +++++++++++++++++++++++++++++++++++++++---- firewood/src/db.rs | 7 ++ 8 files changed, 630 insertions(+), 106 deletions(-) create mode 100644 ffi/memory.go create mode 100644 ffi/proposal.go diff --git a/ffi/firewood.go b/ffi/firewood.go index 3185fab24994..3baba32c63cb 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -12,7 +12,6 @@ import "C" import ( "errors" "fmt" - "runtime" "strings" "unsafe" ) @@ -24,7 +23,7 @@ const ( keyNotFound = "key not found" ) -var dbClosedErr = errors.New("firewood database already closed") +var errDbClosed = errors.New("firewood database already closed") // A Database is a handle to a Firewood database. // It is not safe to call these methods with a nil handle. @@ -32,7 +31,7 @@ type Database struct { // handle is returned and accepted by cgo functions. It MUST be treated as // an opaque value without special meaning. // https://en.wikipedia.org/wiki/Blinkenlights - handle unsafe.Pointer + handle *C.DatabaseHandle } // Config configures the opening of a [Database]. @@ -91,7 +90,7 @@ func New(filePath string, conf *Config) (*Database, error) { metrics_port: C.uint16_t(conf.MetricsPort), } - var db unsafe.Pointer + var db *C.DatabaseHandle if conf.Create { db = C.fwd_create_db(args) } else { @@ -103,12 +102,6 @@ func New(filePath string, conf *Config) (*Database, error) { return &Database{handle: db}, nil } -// KeyValue is a key-value pair. -type KeyValue struct { - Key []byte - Value []byte -} - // Batch applies a batch of updates to the database, returning the hash of the // root node after the batch is applied. // @@ -140,27 +133,44 @@ func (db *Database) Batch(ops []KeyValue) ([]byte, error) { return extractBytesThenFree(&hash) } -// extractBytesThenFree converts the cgo `Value` payload to a byte slice, frees -// the `Value`, and returns the extracted slice. -// Generates error if the error term is nonnull. -func extractBytesThenFree(v *C.struct_Value) (buf []byte, err error) { - buf = C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) - if v.len == 0 { - errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - err = fmt.Errorf("firewood error: %s", errStr) +func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { + if db.handle == nil { + return nil, errDbClosed } - C.fwd_free_value(v) - // Pin the returned value to prevent it from being garbage collected. - runtime.KeepAlive(v) - return + values, cleanup := newValueFactory() + defer cleanup() + + ffiOps := make([]C.struct_KeyValue, len(keys)) + for i := range keys { + ffiOps[i] = C.struct_KeyValue{ + key: values.from(keys[i]), + value: values.from(vals[i]), + } + } + id_or_err := C.fwd_propose_on_db( + db.handle, + C.size_t(len(ffiOps)), + (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), // implicitly pinned + ) + id, err := extractIdThenFree(&id_or_err) + + if err != nil { + return nil, err + } + + // The C function will never create an id of 0, unless it is an error. + return &Proposal{ + handle: db.handle, + id: id, + }, nil } // Get retrieves the value for the given key. It always returns a nil error. // If the key is not found, the return value will be (nil, nil). func (db *Database) Get(key []byte) ([]byte, error) { if db.handle == nil { - return nil, dbClosedErr + return nil, errDbClosed } values, cleanup := newValueFactory() @@ -179,7 +189,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { // Empty trie must return common.Hash{}. func (db *Database) Root() ([]byte, error) { if db.handle == nil { - return nil, dbClosedErr + return nil, errDbClosed } hash := C.fwd_root_hash(db.handle) bytes, err := extractBytesThenFree(&hash) @@ -196,31 +206,9 @@ func (db *Database) Root() ([]byte, error) { // Returns an error if already closed. func (db *Database) Close() error { if db.handle == nil { - return dbClosedErr + return errDbClosed } C.fwd_close_db(db.handle) db.handle = nil return nil } - -// newValueFactory returns a factory for converting byte slices into cgo `Value` -// structs that can be passed as arguments to cgo functions. The returned -// cleanup function MUST be called when the constructed values are no longer -// required, after which they can no longer be used as cgo arguments. -func newValueFactory() (*valueFactory, func()) { - f := new(valueFactory) - return f, func() { f.pin.Unpin() } -} - -type valueFactory struct { - pin runtime.Pinner -} - -func (f *valueFactory) from(data []byte) C.struct_Value { - if len(data) == 0 { - return C.struct_Value{0, nil} - } - ptr := (*C.uchar)(unsafe.SliceData(data)) - f.pin.Pin(ptr) - return C.struct_Value{C.size_t(len(data)), ptr} -} diff --git a/ffi/firewood.h b/ffi/firewood.h index 465d3fe9956f..0ab561df2372 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -5,6 +5,11 @@ #include +/** + * A handle to the database, which contains a reference to the database and a map of proposals. + */ +typedef struct DatabaseHandle DatabaseHandle; + typedef struct Value { size_t len; const uint8_t *data; @@ -37,9 +42,16 @@ typedef struct CreateOrOpenArgs { /** * Puts the given key-value pairs into the database. * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `nkeys` - The number of key-value pairs to put + * * `values` - A pointer to an array of `KeyValue` structs + * * # Returns * - * The current root hash of the database, in Value form. + * The new root hash of the database, in Value form. + * A `Value` containing {0, "error message"} if the commit failed. * * # Safety * @@ -50,7 +62,7 @@ typedef struct CreateOrOpenArgs { * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. * */ -struct Value fwd_batch(void *db, +struct Value fwd_batch(const struct DatabaseHandle *db, size_t nkeys, const struct KeyValue *values); @@ -67,7 +79,28 @@ struct Value fwd_batch(void *db, * * * `db` - The database handle to close, previously returned from a call to open_db() */ -void fwd_close_db(void *db); +void fwd_close_db(struct DatabaseHandle *db); + +/** + * Commits a proposal to the database. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `proposal_id` - The ID of the proposal to commit + * + * # Returns + * + * A `Value` containing {0, null} if the commit was successful. + * A `Value` containing {0, "error message"} if the commit failed. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must ensure that `db` is a valid pointer returned by `open_db` + * + */ +struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); /** * Create a database with the given cache size and maximum number of revisions, as well @@ -89,15 +122,20 @@ void fwd_close_db(void *db); * The caller must call `close` to free the memory associated with the returned database handle. * */ -void *fwd_create_db(struct CreateOrOpenArgs args); +const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); /** * Frees the memory associated with a `Value`. * + * # Arguments + * + * * `value` - The `Value` to free, previously returned from any Rust function. + * * # Safety * * This function is unsafe because it dereferences raw pointers. * The caller must ensure that `value` is a valid pointer. + * */ void fwd_free_value(const struct Value *value); @@ -107,6 +145,16 @@ void fwd_free_value(const struct Value *value); * # Arguments * * * `db` - The database handle returned by `open_db` + * * `key` - The key to look up, in `Value` form + * + * # Returns + * + * A `Value` containing the root hash of the database. + * A `Value` containing {0, "error message"} if the get failed. + * There are two error cases that may be expected to be nil by the caller, + * but should be handled externally: + * * The database has no entries - "IO error: Root hash not found" + * * The key is not found in the database - "key not found" * * # Safety * @@ -115,7 +163,7 @@ void fwd_free_value(const struct Value *value); * * ensure that `key` is a valid pointer to a `Value` struct * * call `free_value` to free the memory associated with the returned `Value` */ -struct Value fwd_get(void *db, struct Value key); +struct Value fwd_get(const struct DatabaseHandle *db, struct Value key); /** * Open a database with the given cache size and maximum number of revisions @@ -136,15 +184,53 @@ struct Value fwd_get(void *db, struct Value key); * The caller must call `close` to free the memory associated with the returned database handle. * */ -void *fwd_open_db(struct CreateOrOpenArgs args); +const struct DatabaseHandle *fwd_open_db(struct CreateOrOpenArgs args); + +/** + * Proposes a batch of operations to the database. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `nkeys` - The number of key-value pairs to put + * * `values` - A pointer to an array of `KeyValue` structs + * + * # Returns + * + * The new root hash of the database, in Value form. + * A `Value` containing {0, "error message"} if creating the proposal failed. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. + * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. + * + */ +struct Value fwd_propose_on_db(const struct DatabaseHandle *db, + size_t nkeys, + const struct KeyValue *values); /** * Get the root hash of the latest version of the database * Don't forget to call `free_value` to free the memory associated with the returned `Value`. * + * # Argument + * + * * `db` - The database handle returned by `open_db` + * + * # Returns + * + * A `Value` containing the root hash of the database. + * A `Value` containing {0, "error message"} if the root hash could not be retrieved. + * One expected error is "IO error: Root hash not found" if the database is empty. + * This should be handled by the caller. + * * # Safety * * This function is unsafe because it dereferences raw pointers. * The caller must ensure that `db` is a valid pointer returned by `open_db` */ -struct Value fwd_root_hash(void *db); +struct Value fwd_root_hash(const struct DatabaseHandle *db); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 328267a21e69..b800f02a9387 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -57,6 +57,8 @@ func newTestDatabase(t *testing.T) *Database { return f } +// Tests that a single key-value pair can be inserted and retrieved. +// This doesn't require storing a proposal across the FFI boundary. func TestInsert(t *testing.T) { db := newTestDatabase(t) const ( @@ -72,13 +74,6 @@ func TestInsert(t *testing.T) { assert.Equal(t, val, string(got), "Recover lone batch-inserted value") } -func TestGetNonExistent(t *testing.T) { - db := newTestDatabase(t) - got, err := db.Get([]byte("non-existent")) - require.NoError(t, err) - assert.Nil(t, got) -} - // Attempt to make a call to a nil or invalid handle. // Each function should return an error and not panic. func TestGetBadHandle(t *testing.T) { @@ -86,21 +81,21 @@ func TestGetBadHandle(t *testing.T) { // This ignores error, but still shouldn't panic. _, err := db.Get([]byte("non-existent")) - assert.ErrorIs(t, err, dbClosedErr) + assert.ErrorIs(t, err, errDbClosed) // We ignore the error, but it shouldn't panic. _, err = db.Root() - assert.ErrorIs(t, err, dbClosedErr) + assert.ErrorIs(t, err, errDbClosed) root, err := db.Update( [][]byte{[]byte("key")}, [][]byte{[]byte("value")}, ) assert.Empty(t, root) - assert.ErrorIs(t, err, dbClosedErr) + assert.ErrorIs(t, err, errDbClosed) err = db.Close() - require.ErrorIs(t, err, dbClosedErr) + require.ErrorIs(t, err, errDbClosed) } func keyForTest(i int) []byte { @@ -118,25 +113,32 @@ func kvForTest(i int) KeyValue { } } +// Tests that 100 key-value pairs can be inserted and retrieved. +// This happens in two ways: +// 1. By calling [Database.Propose] and then [Proposal.Commit]. +// 2. By calling [Database.Update] directly - no proposal storage is needed. func TestInsert100(t *testing.T) { tests := []struct { name string - insert func(*Database, []KeyValue) (root []byte, _ error) + insert func(*Database, [][]byte, [][]byte) (root []byte, _ error) }{ { - name: "Batch", - insert: func(db *Database, kvs []KeyValue) ([]byte, error) { - return db.Batch(kvs) + name: "Propose", + insert: func(db *Database, keys, vals [][]byte) ([]byte, error) { + proposal, err := db.Propose(keys, vals) + if err != nil { + return nil, err + } + err = proposal.Commit() + if err != nil { + return nil, err + } + return db.Root() }, }, { name: "Update", - insert: func(db *Database, kvs []KeyValue) ([]byte, error) { - var keys, vals [][]byte - for _, kv := range kvs { - keys = append(keys, kv.Key) - vals = append(vals, kv.Value) - } + insert: func(db *Database, keys, vals [][]byte) ([]byte, error) { return db.Update(keys, vals) }, }, @@ -146,18 +148,20 @@ func TestInsert100(t *testing.T) { t.Run(tt.name, func(t *testing.T) { db := newTestDatabase(t) - ops := make([]KeyValue, 100) - for i := range ops { - ops[i] = kvForTest(i) + keys := make([][]byte, 100) + vals := make([][]byte, 100) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) } - rootFromInsert, err := tt.insert(db, ops) + rootFromInsert, err := tt.insert(db, keys, vals) require.NoError(t, err, "inserting") - for _, op := range ops { - got, err := db.Get(op.Key) - require.NoErrorf(t, err, "%T.Get(%q)", db, op.Key) + for i := range keys { + got, err := db.Get(keys[i]) + require.NoErrorf(t, err, "%T.Get(%q)", db, keys[i]) // Cast as strings to improve debug messages. - want := string(op.Value) + want := string(vals[i]) assert.Equal(t, want, string(got), "Recover nth batch-inserted value") } @@ -171,6 +175,7 @@ func TestInsert100(t *testing.T) { } } +// Tests that a range of keys can be deleted. func TestRangeDelete(t *testing.T) { db := newTestDatabase(t) ops := make([]KeyValue, 100) @@ -198,6 +203,7 @@ func TestRangeDelete(t *testing.T) { } } +// Tests that the database is empty after creation and doesn't panic. func TestInvariants(t *testing.T) { db := newTestDatabase(t) hash, err := db.Root() @@ -208,3 +214,68 @@ func TestInvariants(t *testing.T) { require.NoError(t, err) assert.Emptyf(t, got, "%T.Get([non-existent key])", db) } + +func TestMultipleProposals(t *testing.T) { + db := newTestDatabase(t) + + // Create 10 proposals, each with 10 keys. + const numProposals = 10 + const numKeys = 10 + proposals := make([]*Proposal, numProposals) + for i := 0; i < numProposals; i++ { + keys := make([][]byte, numKeys) + vals := make([][]byte, numKeys) + for j := 0; j < numKeys; j++ { + keys[j] = keyForTest(i*numKeys + j) + vals[j] = valForTest(i*numKeys + j) + } + proposal, err := db.Propose(keys, vals) + require.NoError(t, err, "Propose(%d)", i) + proposals[i] = proposal + } + + // Commit only the first proposal. + err := proposals[0].Commit() + require.NoError(t, err, "Commit(%d)", 0) + // Check that the first proposal's keys are present. + for j := 0; j < numKeys; j++ { + got, err := db.Get(keyForTest(j)) + require.NoError(t, err, "Get(%d)", j) + assert.Equal(t, valForTest(j), got, "Get(%d)", j) + } + // Check that the other proposals' keys are not present. + for i := 1; i < numProposals; i++ { + for j := 0; j < numKeys; j++ { + got, err := db.Get(keyForTest(i*numKeys + j)) + require.NoError(t, err, "Get(%d)", i*numKeys+j) + assert.Empty(t, got, "Get(%d)", i*numKeys+j) + } + } + + // Now we ensure we cannot commit the other proposals. + for i := 1; i < numProposals; i++ { + err := proposals[i].Commit() + require.Contains(t, err.Error(), "commit the parents of this proposal first", "Commit(%d)", i) + } + + // After attempting to commit the other proposals, they should be completely invalid. + for i := 1; i < numProposals; i++ { + err := proposals[i].Commit() + require.ErrorIs(t, err, errDroppedProposal, "Commit(%d)", i) + } +} + +// Tests that a proposal with an invalid ID cannot be committed. +func TestFakeProposal(t *testing.T) { + db := newTestDatabase(t) + + // Create a fake proposal with an invalid ID. + proposal := &Proposal{ + handle: db.handle, + id: 1, // note that ID 0 is reserved for invalid proposals + } + + // Attempt to commit the fake proposal. + err := proposal.Commit() + require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") +} diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index d48c270e30ec..05fff47407d8 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -43,7 +43,7 @@ type kVBackend interface { // Prefetch is a no-op since we don't need to prefetch for Firewood. func (db *Database) Prefetch(key []byte) ([]byte, error) { if db.handle == nil { - return nil, dbClosedErr + return nil, errDbClosed } return nil, nil @@ -52,7 +52,7 @@ func (db *Database) Prefetch(key []byte) ([]byte, error) { // Commit is a no-op, since [Database.Update] already persists changes. func (db *Database) Commit(root []byte) error { if db.handle == nil { - return dbClosedErr + return errDbClosed } return nil @@ -62,7 +62,7 @@ func (db *Database) Commit(root []byte) error { // database. func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { if db.handle == nil { - return nil, dbClosedErr + return nil, errDbClosed } ops := make([]KeyValue, len(keys)) diff --git a/ffi/memory.go b/ffi/memory.go new file mode 100644 index 000000000000..bbd123dfc4af --- /dev/null +++ b/ffi/memory.go @@ -0,0 +1,124 @@ +// Package firewood provides a Go wrapper around the [Firewood] database. +// +// [Firewood]: https://github.com/ava-labs/firewood +package firewood + +// // Note that -lm is required on Linux but not on Mac. +// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm +// #include +// #include "firewood.h" +import "C" +import ( + "errors" + "fmt" + "runtime" + "unsafe" +) + +var ( + errNilBuffer = errors.New("firewood error: nil value returned from cgo") + errBadValue = errors.New("firewood error: value from cgo formatted incorrectly") +) + +// KeyValue is a key-value pair. +type KeyValue struct { + Key []byte + Value []byte +} + +// extractErrorThenFree converts the cgo `Value` payload to a string, frees the +// `Value` if an error is returned, and returns the extracted value. +// If the data is not formatted for a string, and non-null it returns an error. +func extractErrorThenFree(v *C.struct_Value) error { + if v == nil { + return errNilBuffer + } + + // The length isn't expected to be set in either case. + // May indicate a bug. + if v.len != 0 { + // We should still attempt to free the value. + C.fwd_free_value(v) + return errBadValue + } + + // Expected empty case for Rust's `()` + if v.data == nil { + return nil + } + + // If the value is an error string, it should be freed and an error + // returned. + go_str := C.GoString((*C.char)(unsafe.Pointer(v.data))) + C.fwd_free_value(v) + // Pin the returned value to prevent it from being garbage collected. + runtime.KeepAlive(v) + return fmt.Errorf("firewood error: %s", go_str) +} + +// extractIdThenFree converts the cgo `Value` payload to a uint32, frees the +// `Value` if an error is returned, and returns the extracted value. +func extractIdThenFree(v *C.struct_Value) (uint32, error) { + if v == nil { + return 0, errNilBuffer + } + if v.len == 0 && v.data == nil { + return 0, errBadValue + } + // If the value is an error string, it should be freed and an error + // returned. + // Any valid ID should be non-zero. + if v.len == 0 { + go_str := C.GoString((*C.char)(unsafe.Pointer(v.data))) + C.fwd_free_value(v) + // Pin the returned value to prevent it from being garbage collected. + runtime.KeepAlive(v) + return 0, fmt.Errorf("firewood error: %s", go_str) + } + return uint32(v.len), nil +} + +// extractBytesThenFree converts the cgo `Value` payload to a byte slice, frees +// the `Value`, and returns the extracted slice. +// Generates error if the error term is nonnull. +func extractBytesThenFree(v *C.struct_Value) (buf []byte, err error) { + if v == nil { + return nil, errNilBuffer + } + if v.data == nil { + return nil, errBadValue + } + + buf = C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) + if v.len == 0 { + errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) + err = fmt.Errorf("firewood error: %s", errStr) + } + C.fwd_free_value(v) + + // Pin the returned value to prevent it from being garbage collected. + runtime.KeepAlive(v) + return +} + +// newValueFactory returns a factory for converting byte slices into cgo `Value` +// structs that can be passed as arguments to cgo functions. The returned +// cleanup function MUST be called when the constructed values are no longer +// required, after which they can no longer be used as cgo arguments. +func newValueFactory() (*valueFactory, func()) { + f := new(valueFactory) + return f, func() { f.pin.Unpin() } +} + +type valueFactory struct { + pin runtime.Pinner +} + +func (f *valueFactory) from(data []byte) C.struct_Value { + if len(data) == 0 { + return C.struct_Value{0, nil} + } + ptr := (*C.uchar)(unsafe.SliceData(data)) + f.pin.Pin(ptr) + return C.struct_Value{C.size_t(len(data)), ptr} +} diff --git a/ffi/proposal.go b/ffi/proposal.go new file mode 100644 index 000000000000..06eb37943304 --- /dev/null +++ b/ffi/proposal.go @@ -0,0 +1,46 @@ +// Package firewood provides a Go wrapper around the [Firewood] database. +// +// [Firewood]: https://github.com/ava-labs/firewood +package firewood + +// // Note that -lm is required on Linux but not on Mac. +// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm +// #include +// #include "firewood.h" +import "C" +import ( + "errors" +) + +var errDroppedProposal = errors.New("proposal already dropped") + +type Proposal struct { + // handle is returned and accepted by cgo functions. It MUST be treated as + // an opaque value without special meaning. + // https://en.wikipedia.org/wiki/Blinkenlights + handle *C.DatabaseHandle + + // The proposal ID. + // id = 0 is reserved for a dropped proposal. + id uint32 +} + +func (p *Proposal) Commit() error { + if p.handle == nil { + return errDbClosed + } + + if p.id == 0 { + return errDroppedProposal + } + + // Commit the proposal and return the hash. + err_val := C.fwd_commit(p.handle, C.uint32_t(p.id)) + err := extractErrorThenFree(&err_val) + if err != nil { + // this is unrecoverable due to Rust's ownership model + // The underlying proposal is no longer valid. + p.id = 0 + } + return err +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 4236fffef970..6a28ae6385e7 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,12 +1,16 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr, c_char}; use std::fmt::{self, Display, Formatter}; +use std::ops::Deref; use std::os::unix::ffi::OsStrExt as _; use std::path::Path; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, RwLock}; -use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _}; +use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; mod metrics_setup; @@ -16,6 +20,37 @@ use metrics::counter; #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +type ProposalId = u32; +static ID_COUNTER: AtomicU32 = AtomicU32::new(1); +fn next_id() -> ProposalId { + ID_COUNTER.fetch_add(1, Ordering::Relaxed) +} + +/// A handle to the database, which contains a reference to the database and a map of proposals. +// All proposals must be dropped before the database handle is dropped due to lifetime +// guarantees defined during database creation. +pub struct DatabaseHandle<'p> { + proposals: RwLock>>>, + db: Db, +} + +impl From for DatabaseHandle<'_> { + fn from(db: Db) -> Self { + Self { + db, + proposals: RwLock::new(HashMap::new()), + } + } +} + +impl Deref for DatabaseHandle<'_> { + type Target = Db; + + fn deref(&self) -> &Self::Target { + &self.db + } +} + #[derive(Debug)] #[repr(C)] pub struct Value { @@ -34,6 +69,16 @@ impl Display for Value { /// # Arguments /// /// * `db` - The database handle returned by `open_db` +/// * `key` - The key to look up, in `Value` form +/// +/// # Returns +/// +/// A `Value` containing the root hash of the database. +/// A `Value` containing {0, "error message"} if the get failed. +/// There are two error cases that may be expected to be nil by the caller, +/// but should be handled externally: +/// * The database has no entries - "IO error: Root hash not found" +/// * The key is not found in the database - "key not found" /// /// # Safety /// @@ -42,7 +87,7 @@ impl Display for Value { /// * ensure that `key` is a valid pointer to a `Value` struct /// * call `free_value` to free the memory associated with the returned `Value` #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_get(db: *mut Db, key: Value) -> Value { +pub unsafe extern "C" fn fwd_get(db: *const DatabaseHandle, key: Value) -> Value { get(db, key).unwrap_or_else(|e| e.into()) } @@ -50,7 +95,7 @@ pub unsafe extern "C" fn fwd_get(db: *mut Db, key: Value) -> Value { /// /// This function is not exposed to the C API. /// Internal call for `fwd_get` to remove error handling from the C API -fn get(db: *mut Db, key: Value) -> Result { +fn get(db: *const DatabaseHandle, key: Value) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -83,9 +128,16 @@ pub struct KeyValue { /// Puts the given key-value pairs into the database. /// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `nkeys` - The number of key-value pairs to put +/// * `values` - A pointer to an array of `KeyValue` structs +/// /// # Returns /// -/// The current root hash of the database, in Value form. +/// The new root hash of the database, in Value form. +/// A `Value` containing {0, "error message"} if the commit failed. /// /// # Safety /// @@ -96,7 +148,11 @@ pub struct KeyValue { /// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Value { +pub unsafe extern "C" fn fwd_batch( + db: *const DatabaseHandle, + nkeys: usize, + values: *const KeyValue, +) -> Value { batch(db, nkeys, values).unwrap_or_else(|e| e.into()) } @@ -104,7 +160,11 @@ pub unsafe extern "C" fn fwd_batch(db: *mut Db, nkeys: usize, values: *const Key /// /// This function is not exposed to the C API. /// Internal call for `fwd_batch` to remove error handling from the C API -fn batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Result { +fn batch( + db: *const DatabaseHandle, + nkeys: usize, + values: *const KeyValue, +) -> Result { let start = coarsetime::Instant::now(); // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -113,7 +173,7 @@ fn batch(db: *mut Db, nkeys: usize, values: *const KeyValue) -> Result Result Value { + // Note: the id is guaranteed to be non-zero + // because we use an atomic counter that starts at 1. + propose_on_db(db, nkeys, values) + .map(|id| id.into()) + .unwrap_or_else(|e| e.into()) +} + +/// cbindgen::ignore +/// +/// This function is not exposed to the C API. +/// Internal call for `fwd_propose_on_db` to remove error handling from the C API +fn propose_on_db( + db: *const DatabaseHandle, + nkeys: usize, + values: *const KeyValue, +) -> Result { + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Create a batch of operations to perform. + let mut batch = Vec::with_capacity(nkeys); + for i in 0..nkeys { + let kv = unsafe { values.add(i).as_ref() } + .ok_or_else(|| String::from("couldn't get key-value pair"))?; + if kv.value.len == 0 { + batch.push(DbBatchOp::DeleteRange { + prefix: kv.key.as_slice(), + }); + continue; + } + batch.push(DbBatchOp::Put { + key: kv.key.as_slice(), + value: kv.value.as_slice(), + }); + } + let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; + let proposal_id = next_id(); // Guaranteed to be non-zero + db.proposals.write().unwrap().insert(proposal_id, proposal); + Ok(proposal_id) +} + +/// Commits a proposal to the database. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `proposal_id` - The ID of the proposal to commit +/// +/// # Returns +/// +/// A `Value` containing {0, null} if the commit was successful. +/// A `Value` containing {0, "error message"} if the commit failed. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that `db` is a valid pointer returned by `open_db` +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_commit(db: *const DatabaseHandle, proposal_id: u32) -> Value { + commit(db, proposal_id) + .map(|e| e.into()) + .unwrap_or_else(|e| e.into()) +} + +fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + let proposal = db + .proposals + .write() + .unwrap() + .remove(&proposal_id) + .ok_or_else(|| String::from("proposal not found"))?; + proposal.commit_sync().map_err(|e| e.to_string()) +} + /// Get the root hash of the latest version of the database /// Don't forget to call `free_value` to free the memory associated with the returned `Value`. /// +/// # Argument +/// +/// * `db` - The database handle returned by `open_db` +/// +/// # Returns +/// +/// A `Value` containing the root hash of the database. +/// A `Value` containing {0, "error message"} if the root hash could not be retrieved. +/// One expected error is "IO error: Root hash not found" if the database is empty. +/// This should be handled by the caller. +/// /// # Safety /// /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `db` is a valid pointer returned by `open_db` #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { +pub unsafe extern "C" fn fwd_root_hash(db: *const DatabaseHandle) -> Value { // Check db is valid. root_hash(db).unwrap_or_else(|e| e.into()) } @@ -160,7 +333,7 @@ pub unsafe extern "C" fn fwd_root_hash(db: *mut Db) -> Value { /// /// This function is not exposed to the C API. /// Internal call for `fwd_root_hash` to remove error handling from the C API -fn root_hash(db: *mut Db) -> Result { +fn root_hash(db: *const DatabaseHandle) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -211,18 +384,48 @@ impl From for Value { } } +impl From for Value { + fn from(v: u32) -> Self { + // WARNING: This should only be called with values >= 1. + // In much of the Go code, v.len == 0 is used to indicate a null-terminated string. + // This may cause a panic or memory corruption if used incorrectly. + assert_ne!(v, 0); + Self { + len: v as usize, + data: std::ptr::null(), + } + } +} + +impl From<()> for Value { + fn from(_: ()) -> Self { + Self { + len: 0, + data: std::ptr::null(), + } + } +} + /// Frees the memory associated with a `Value`. /// +/// # Arguments +/// +/// * `value` - The `Value` to free, previously returned from any Rust function. +/// /// # Safety /// /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `value` is a valid pointer. +/// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_free_value(value: *const Value) { - // Create a Rust reference to the pointer. - // Unsafe because we are dereferencing a raw pointer - possible another thread - // has a mutable reference to the same memory. - let value = unsafe { &*value as &Value }; + // Check value is valid. + let value = unsafe { value.as_ref() }.expect("value should be non-null"); + + if value.data.is_null() { + return; // nothing to free, but valid behavior. + } + // We assume that if the length is 0, then the data is a null-terminated string. if value.len > 0 { let recreated_box = unsafe { @@ -273,7 +476,7 @@ pub struct CreateOrOpenArgs { /// The caller must call `close` to free the memory associated with the returned database handle. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { +pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *const DatabaseHandle<'static> { let cfg = DbConfig::builder() .truncate(true) .manager(manager_config( @@ -303,7 +506,7 @@ pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *mut Db { /// The caller must call `close` to free the memory associated with the returned database handle. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *mut Db { +pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *const DatabaseHandle<'static> { let cfg = DbConfig::builder() .truncate(false) .manager(manager_config( @@ -319,7 +522,7 @@ unsafe fn common_create( path: *const std::ffi::c_char, metrics_port: u16, cfg: DbConfig, -) -> *mut Db { +) -> *const DatabaseHandle<'static> { #[cfg(feature = "logger")] let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .try_init(); @@ -329,9 +532,8 @@ unsafe fn common_create( if metrics_port > 0 { metrics_setup::setup_metrics(metrics_port); } - Box::into_raw(Box::new( - Db::new_sync(path, cfg).expect("db initialization should succeed"), - )) + let db = Db::new_sync(path, cfg).expect("db initialization should succeed"); + Box::into_raw(Box::new(db.into())) } fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> RevisionManagerConfig { @@ -364,6 +566,6 @@ fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> Revision /// /// * `db` - The database handle to close, previously returned from a call to open_db() #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_close_db(db: *mut Db) { +pub unsafe extern "C" fn fwd_close_db(db: *mut DatabaseHandle) { let _ = unsafe { Box::from_raw(db) }; } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c42ec39ed80d..4e762f71bf8a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -339,6 +339,13 @@ pub struct Proposal<'p> { db: &'p Db, } +impl Proposal<'_> { + /// Get the root hash of the proposal synchronously + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(self.nodestore.root_hash()?) + } +} + #[async_trait] impl api::DbView for Proposal<'_> { type Stream<'b> From a9269d6b6ed0dbaed3d74f380bc8669829ccce50 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 9 May 2025 12:12:32 -0700 Subject: [PATCH 0707/1053] Various cleanups (#870) --- ffi/firewood.go | 3 +- ffi/firewood.h | 27 ++++++++- ffi/memory.go | 7 +-- ffi/src/lib.rs | 143 +++++++++++++++++++++++++++++++++++++----------- 4 files changed, 138 insertions(+), 42 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 3baba32c63cb..aa9e8985271b 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -179,9 +179,10 @@ func (db *Database) Get(key []byte) ([]byte, error) { bytes, err := extractBytesThenFree(&val) // If the root hash or key is not found, return nil. - if err != nil && (strings.Contains(err.Error(), rootHashNotFound) || strings.Contains(err.Error(), keyNotFound)) { + if err != nil && strings.Contains(err.Error(), rootHashNotFound) { return nil, nil } + return bytes, err } diff --git a/ffi/firewood.h b/ffi/firewood.h index 0ab561df2372..3b8ec6ccc8b7 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -6,17 +6,31 @@ /** - * A handle to the database, which contains a reference to the database and a map of proposals. + * A handle to the database, returned by `fwd_create_db` and `fwd_open_db`. + * + * These handles are passed to the other FFI functions. + * */ typedef struct DatabaseHandle DatabaseHandle; +/** + * A value returned by the FFI. + * + * This is used in several different ways: + * + * - When returning data, the length is the length of the data and the data is a pointer to the data. + * - When returning an error, the length is 0 and the data is a null-terminated C-style string. + * - When returning an ID, the length is the ID and the data is null. + * + * A `Value` with length 0 and a null data pointer indicates that the data was not found. + */ typedef struct Value { size_t len; const uint8_t *data; } Value; /** - * A `KeyValue` struct that represents a key-value pair in the database. + * A `KeyValue` represents a key-value pair, passed to the FFI. */ typedef struct KeyValue { struct Value key; @@ -53,6 +67,13 @@ typedef struct CreateOrOpenArgs { * The new root hash of the database, in Value form. * A `Value` containing {0, "error message"} if the commit failed. * + * # Errors + * + * * `"key-value pair is null"` - A `KeyValue` struct is null + * * `"db should be non-null"` - The database handle is null + * * `"couldn't get key-value pair"` - A `KeyValue` struct is null + * * `"proposed revision is empty"` - The proposed revision is empty + * * # Safety * * This function is unsafe because it dereferences raw pointers. @@ -154,7 +175,7 @@ void fwd_free_value(const struct Value *value); * There are two error cases that may be expected to be nil by the caller, * but should be handled externally: * * The database has no entries - "IO error: Root hash not found" - * * The key is not found in the database - "key not found" + * * The key is not found in the database returns a `Value` with length 0 and a null data pointer. * * # Safety * diff --git a/ffi/memory.go b/ffi/memory.go index bbd123dfc4af..389cb20dfbcc 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -82,11 +82,8 @@ func extractIdThenFree(v *C.struct_Value) (uint32, error) { // the `Value`, and returns the extracted slice. // Generates error if the error term is nonnull. func extractBytesThenFree(v *C.struct_Value) (buf []byte, err error) { - if v == nil { - return nil, errNilBuffer - } - if v.data == nil { - return nil, errBadValue + if v == nil || v.data == nil { + return nil, nil } buf = C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 6a28ae6385e7..001eae271bb5 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -26,11 +26,16 @@ fn next_id() -> ProposalId { ID_COUNTER.fetch_add(1, Ordering::Relaxed) } -/// A handle to the database, which contains a reference to the database and a map of proposals. -// All proposals must be dropped before the database handle is dropped due to lifetime -// guarantees defined during database creation. +/// A handle to the database, returned by `fwd_create_db` and `fwd_open_db`. +/// +/// These handles are passed to the other FFI functions. +/// pub struct DatabaseHandle<'p> { + /// List of oustanding proposals, by ID + // Keep proposals first, as they must be dropped before the database handle is dropped due to lifetime + // issues. proposals: RwLock>>>, + /// The database db: Db, } @@ -51,19 +56,6 @@ impl Deref for DatabaseHandle<'_> { } } -#[derive(Debug)] -#[repr(C)] -pub struct Value { - pub len: usize, - pub data: *const u8, -} - -impl Display for Value { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}", self.as_slice()) - } -} - /// Gets the value associated with the given key from the database. /// /// # Arguments @@ -78,7 +70,7 @@ impl Display for Value { /// There are two error cases that may be expected to be nil by the caller, /// but should be handled externally: /// * The database has no entries - "IO error: Root hash not found" -/// * The key is not found in the database - "key not found" +/// * The key is not found in the database returns a `Value` with length 0 and a null data pointer. /// /// # Safety /// @@ -91,8 +83,6 @@ pub unsafe extern "C" fn fwd_get(db: *const DatabaseHandle, key: Value) -> Value get(db, key).unwrap_or_else(|e| e.into()) } -/// cbindgen::ignore -/// /// This function is not exposed to the C API. /// Internal call for `fwd_get` to remove error handling from the C API fn get(db: *const DatabaseHandle, key: Value) -> Result { @@ -113,11 +103,11 @@ fn get(db: *const DatabaseHandle, key: Value) -> Result { let value = rev .val_sync(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(|| String::from("key not found"))?; + .ok_or_else(|| String::from(""))?; Ok(value.into()) } -/// A `KeyValue` struct that represents a key-value pair in the database. +/// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] #[allow(unused)] #[unsafe(no_mangle)] @@ -139,6 +129,13 @@ pub struct KeyValue { /// The new root hash of the database, in Value form. /// A `Value` containing {0, "error message"} if the commit failed. /// +/// # Errors +/// +/// * `"key-value pair is null"` - A `KeyValue` struct is null +/// * `"db should be non-null"` - The database handle is null +/// * `"couldn't get key-value pair"` - A `KeyValue` struct is null +/// * `"proposed revision is empty"` - The proposed revision is empty +/// /// # Safety /// /// This function is unsafe because it dereferences raw pointers. @@ -156,10 +153,8 @@ pub unsafe extern "C" fn fwd_batch( batch(db, nkeys, values).unwrap_or_else(|e| e.into()) } -/// cbindgen::ignore -/// -/// This function is not exposed to the C API. /// Internal call for `fwd_batch` to remove error handling from the C API +#[doc(hidden)] fn batch( db: *const DatabaseHandle, nkeys: usize, @@ -191,11 +186,17 @@ fn batch( let propose_time = start.elapsed().as_millis(); counter!("firewood.ffi.propose_ms").increment(propose_time); + let hash_val = proposal + .root_hash_sync() + .map_err(|e| e.to_string())? + .ok_or(String::from("Proposed revision is empty"))? + .as_slice() + .into(); + // Commit the proposal. proposal.commit_sync().map_err(|e| e.to_string())?; // Get the root hash of the database post-commit. - let hash_val = hash(db)?; let propose_plus_commit_time = start.elapsed().as_millis(); counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); counter!("firewood.ffi.commit_ms").increment(propose_plus_commit_time - propose_time); @@ -237,10 +238,8 @@ pub unsafe extern "C" fn fwd_propose_on_db( .unwrap_or_else(|e| e.into()) } -/// cbindgen::ignore -/// -/// This function is not exposed to the C API. /// Internal call for `fwd_propose_on_db` to remove error handling from the C API +#[doc(hidden)] fn propose_on_db( db: *const DatabaseHandle, nkeys: usize, @@ -294,6 +293,8 @@ pub unsafe extern "C" fn fwd_commit(db: *const DatabaseHandle, proposal_id: u32) .unwrap_or_else(|e| e.into()) } +/// Internal call for `fwd_commit` to remove error handling from the C API +#[doc(hidden)] fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; let proposal = db @@ -329,10 +330,9 @@ pub unsafe extern "C" fn fwd_root_hash(db: *const DatabaseHandle) -> Value { root_hash(db).unwrap_or_else(|e| e.into()) } -/// cbindgen::ignore -/// /// This function is not exposed to the C API. /// Internal call for `fwd_root_hash` to remove error handling from the C API +#[doc(hidden)] fn root_hash(db: *const DatabaseHandle) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -341,10 +341,9 @@ fn root_hash(db: *const DatabaseHandle) -> Result { hash(db) } -/// cbindgen::ignore -/// /// This function is not exposed to the C API. /// It returns the current hash of an already-fetched database handle +#[doc(hidden)] fn hash(db: &Db) -> Result { db.root_hash_sync() .map_err(|e| e.to_string())? @@ -352,6 +351,35 @@ fn hash(db: &Db) -> Result { .map(|root| Value::from(root.as_slice())) } +/// A value returned by the FFI. +/// +/// This is used in several different ways: +/// +/// - When returning data, the length is the length of the data and the data is a pointer to the data. +/// - When returning an error, the length is 0 and the data is a null-terminated C-style string. +/// - When returning an ID, the length is the ID and the data is null. +/// +/// A `Value` with length 0 and a null data pointer indicates that the data was not found. +#[derive(Debug)] +#[repr(C)] +pub struct Value { + pub len: usize, + pub data: *const u8, +} + +impl Display for Value { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match (self.len, self.data.is_null()) { + (0, true) => write!(f, "[not found]"), + (0, false) => write!(f, "[error] {}", unsafe { + CStr::from_ptr(self.data as *const i8).to_string_lossy() + }), + (len, true) => write!(f, "[id] {}", len), + (_, false) => write!(f, "[data] {:?}", self.as_slice()), + } + } +} + impl Value { pub fn as_slice(&self) -> &[u8] { unsafe { std::slice::from_raw_parts(self.data, self.len) } @@ -375,7 +403,12 @@ impl From> for Value { impl From for Value { fn from(s: String) -> Self { - // Create empty CString if s is null. + if s.is_empty() { + return Value { + len: 0, + data: std::ptr::null(), + }; + } let cstr = CString::new(s).unwrap_or_default().into_raw(); Value { len: 0, @@ -518,6 +551,8 @@ pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *const DatabaseH unsafe { common_create(args.path, args.metrics_port, cfg) } } +/// Internal call for `fwd_create_db` and `fwd_open_db` to remove error handling from the C API +#[doc(hidden)] unsafe fn common_create( path: *const std::ffi::c_char, metrics_port: u16, @@ -569,3 +604,45 @@ fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> Revision pub unsafe extern "C" fn fwd_close_db(db: *mut DatabaseHandle) { let _ = unsafe { Box::from_raw(db) }; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_invalid_value_display() { + let value = Value { + len: 0, + data: std::ptr::null(), + }; + assert_eq!(format!("{}", value), "[not found]"); + } + + #[test] + fn test_value_display_with_error_string() { + let cstr = CString::new("test").unwrap(); + let value = Value { + len: 0, + data: cstr.as_ptr().cast::(), + }; + assert_eq!(format!("{}", value), "[error] test"); + } + + #[test] + fn test_value_display_with_data() { + let value = Value { + len: 4, + data: Box::leak(b"test".to_vec().into_boxed_slice()).as_ptr(), + }; + assert_eq!(format!("{}", value), "[data] [116, 101, 115, 116]"); + } + + #[test] + fn test_value_display_with_id() { + let value = Value { + len: 4, + data: std::ptr::null(), + }; + assert_eq!(format!("{}", value), "[id] 4"); + } +} From 2c006f6803a161e457f9e2567286b8560596bbc6 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Fri, 9 May 2025 17:09:34 -0400 Subject: [PATCH 0708/1053] doc(ffi): remove private declarations from public docs (#874) --- ffi/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 001eae271bb5..9e92463bc9b8 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -13,15 +13,24 @@ use std::sync::{Arc, RwLock}; use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -mod metrics_setup; - use metrics::counter; +#[doc(hidden)] +mod metrics_setup; + #[global_allocator] +#[doc(hidden)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +/// A proposal ID is a 32-bit unsigned integer. +/// It is used to identify proposals internally. type ProposalId = u32; + +#[doc(hidden)] static ID_COUNTER: AtomicU32 = AtomicU32::new(1); + +/// Atomically retrieves the next proposal ID. +#[doc(hidden)] fn next_id() -> ProposalId { ID_COUNTER.fetch_add(1, Ordering::Relaxed) } @@ -85,6 +94,7 @@ pub unsafe extern "C" fn fwd_get(db: *const DatabaseHandle, key: Value) -> Value /// This function is not exposed to the C API. /// Internal call for `fwd_get` to remove error handling from the C API +#[doc(hidden)] fn get(db: *const DatabaseHandle, key: Value) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -571,6 +581,7 @@ unsafe fn common_create( Box::into_raw(Box::new(db.into())) } +#[doc(hidden)] fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> RevisionManagerConfig { let cache_read_strategy = match strategy { 0 => CacheReadStrategy::WritesOnly, From b432ee9920c032fc909f4d2e1b8ac994ab11c861 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 12 May 2025 10:51:52 -0400 Subject: [PATCH 0709/1053] fix(ffi): Clarify roles of `Value` extractors (#875) --- ffi/memory.go | 106 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/ffi/memory.go b/ffi/memory.go index 389cb20dfbcc..5bb22c5f0e11 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -26,76 +26,110 @@ type KeyValue struct { Value []byte } -// extractErrorThenFree converts the cgo `Value` payload to a string, frees the -// `Value` if an error is returned, and returns the extracted value. -// If the data is not formatted for a string, and non-null it returns an error. +// extractErrorThenFree converts the cgo `Value` payload to either: +// 1. a nil value, indicating no error, or +// 2. a non-nil error, indicating an error occurred. +// This should only be called when the `Value` is expected to only contain an error. +// Otherwise, an error is returned. func extractErrorThenFree(v *C.struct_Value) error { + // Pin the returned value to prevent it from being garbage collected. + defer runtime.KeepAlive(v) + if v == nil { return errNilBuffer } - // The length isn't expected to be set in either case. - // May indicate a bug. - if v.len != 0 { - // We should still attempt to free the value. - C.fwd_free_value(v) - return errBadValue - } - // Expected empty case for Rust's `()` + // Ignores the length. if v.data == nil { return nil } // If the value is an error string, it should be freed and an error // returned. - go_str := C.GoString((*C.char)(unsafe.Pointer(v.data))) + if v.len == 0 { + errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) + C.fwd_free_value(v) + return fmt.Errorf("firewood error: %s", errStr) + } + + // The value is formatted incorrectly. + // We should still attempt to free the value. C.fwd_free_value(v) - // Pin the returned value to prevent it from being garbage collected. - runtime.KeepAlive(v) - return fmt.Errorf("firewood error: %s", go_str) + return errBadValue + } -// extractIdThenFree converts the cgo `Value` payload to a uint32, frees the -// `Value` if an error is returned, and returns the extracted value. +// extractIdThenFree converts the cgo `Value` payload to either: +// 1. a nonzero uint32 and nil error, indicating a valid int +// 2. a zero uint32 and a non-nil error, indicating an error occurred. +// This should only be called when the `Value` is expected to only contain an error or an ID. +// Otherwise, an error is returned. func extractIdThenFree(v *C.struct_Value) (uint32, error) { + // Pin the returned value to prevent it from being garbage collected. + defer runtime.KeepAlive(v) + if v == nil { return 0, errNilBuffer } - if v.len == 0 && v.data == nil { - return 0, errBadValue + + // Normal case, length is non-zero and data is nil. + if v.len != 0 && v.data == nil { + return uint32(v.len), nil } + // If the value is an error string, it should be freed and an error // returned. - // Any valid ID should be non-zero. - if v.len == 0 { - go_str := C.GoString((*C.char)(unsafe.Pointer(v.data))) + if v.len == 0 && v.data != nil { + errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - // Pin the returned value to prevent it from being garbage collected. - runtime.KeepAlive(v) - return 0, fmt.Errorf("firewood error: %s", go_str) + return 0, fmt.Errorf("firewood error: %s", errStr) } - return uint32(v.len), nil + + // The value is formatted incorrectly. + // We should still attempt to free the value. + C.fwd_free_value(v) + return 0, errBadValue } -// extractBytesThenFree converts the cgo `Value` payload to a byte slice, frees -// the `Value`, and returns the extracted slice. -// Generates error if the error term is nonnull. -func extractBytesThenFree(v *C.struct_Value) (buf []byte, err error) { - if v == nil || v.data == nil { +// extractBytesThenFree converts the cgo `Value` payload to either: +// 1. a non-nil byte slice and nil error, indicating a valid byte slice +// 2. a nil byte slice and nil error, indicating an empty byte slice +// 3. a nil byte slice and a non-nil error, indicating an error occurred. +// This should only be called when the `Value` is expected to only contain an error or a byte slice. +// Otherwise, an error is returned. +func extractBytesThenFree(v *C.struct_Value) ([]byte, error) { + // Pin the returned value to prevent it from being garbage collected. + defer runtime.KeepAlive(v) + + if v == nil { + return nil, errNilBuffer + } + + // Expected behavior - no data and length is zero. + if v.len == 0 && v.data == nil { return nil, nil } - buf = C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) + // Normal case, data is non-nil and length is non-zero. + if v.len != 0 && v.data != nil { + buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) + C.fwd_free_value(v) + return buf, nil + } + + // Data non-nil but length is zero indcates an error. if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - err = fmt.Errorf("firewood error: %s", errStr) + C.fwd_free_value(v) + return nil, fmt.Errorf("firewood error: %s", errStr) } + + // The value is formatted incorrectly. + // We should still attempt to free the value. C.fwd_free_value(v) + return nil, errBadValue - // Pin the returned value to prevent it from being garbage collected. - runtime.KeepAlive(v) - return } // newValueFactory returns a factory for converting byte slices into cgo `Value` From fe474288512353fd2ed27f02195afb0afb774f3f Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 12 May 2025 12:07:14 -0400 Subject: [PATCH 0710/1053] Disable metrics during ffi unit tests (#876) --- ffi/firewood_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index b800f02a9387..078bdb6b9fbf 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -44,6 +44,7 @@ func newTestDatabase(t *testing.T) *Database { t.Helper() conf := DefaultConfig() + conf.MetricsPort = 0 conf.Create = true // The TempDir directory is automatically cleaned up so there's no need to // remove test.db. From 7d0457d76753633fc6ba474766adec67445892be Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 12 May 2025 13:47:14 -0400 Subject: [PATCH 0711/1053] feat(ffi): Get values from proposals (#877) --- ffi/firewood.go | 4 +-- ffi/firewood.h | 36 +++++++++++++++++++---- ffi/firewood_test.go | 69 +++++++++++++++++++++++++++++++++++++++++-- ffi/proposal.go | 16 ++++++++++ ffi/src/lib.rs | 70 +++++++++++++++++++++++++++++++++++++------- firewood/src/db.rs | 6 ++++ 6 files changed, 182 insertions(+), 19 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index aa9e8985271b..1790aae01db8 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -175,10 +175,10 @@ func (db *Database) Get(key []byte) ([]byte, error) { values, cleanup := newValueFactory() defer cleanup() - val := C.fwd_get(db.handle, values.from(key)) + val := C.fwd_get_latest(db.handle, values.from(key)) bytes, err := extractBytesThenFree(&val) - // If the root hash or key is not found, return nil. + // If the root hash is not found, return nil. if err != nil && strings.Contains(err.Error(), rootHashNotFound) { return nil, nil } diff --git a/ffi/firewood.h b/ffi/firewood.h index 3b8ec6ccc8b7..2c90283b1977 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -53,6 +53,8 @@ typedef struct CreateOrOpenArgs { uint16_t metrics_port; } CreateOrOpenArgs; +typedef uint32_t ProposalId; + /** * Puts the given key-value pairs into the database. * @@ -160,6 +162,31 @@ const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); */ void fwd_free_value(const struct Value *value); +/** + * Gets the value associated with the given key from the proposal provided. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `id` - The ID of the proposal to get the value from + * * `key` - The key to look up, in `Value` form + * + * # Returns + * + * A `Value` containing the root hash of the database. + * A `Value` containing {0, "error message"} if the get failed. + * + * # Safety + * + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `key` is a valid pointer to a `Value` struct + * * call `free_value` to free the memory associated with the returned `Value` + */ +struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, + ProposalId id, + struct Value key); + /** * Gets the value associated with the given key from the database. * @@ -172,10 +199,9 @@ void fwd_free_value(const struct Value *value); * * A `Value` containing the root hash of the database. * A `Value` containing {0, "error message"} if the get failed. - * There are two error cases that may be expected to be nil by the caller, - * but should be handled externally: - * * The database has no entries - "IO error: Root hash not found" - * * The key is not found in the database returns a `Value` with length 0 and a null data pointer. + * There is one error case that may be expected to be nil by the caller, + * but should be handled externally: The database has no entries - "IO error: Root hash not found" + * This is expected behavior if the database is empty. * * # Safety * @@ -184,7 +210,7 @@ void fwd_free_value(const struct Value *value); * * ensure that `key` is a valid pointer to a `Value` struct * * call `free_value` to free the memory associated with the returned `Value` */ -struct Value fwd_get(const struct DatabaseHandle *db, struct Value key); +struct Value fwd_get_latest(const struct DatabaseHandle *db, struct Value key); /** * Open a database with the given cache size and maximum number of revisions diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 078bdb6b9fbf..83c28ec96306 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -223,7 +223,7 @@ func TestMultipleProposals(t *testing.T) { const numProposals = 10 const numKeys = 10 proposals := make([]*Proposal, numProposals) - for i := 0; i < numProposals; i++ { + for i := range proposals { keys := make([][]byte, numKeys) vals := make([][]byte, numKeys) for j := 0; j < numKeys; j++ { @@ -235,6 +235,15 @@ func TestMultipleProposals(t *testing.T) { proposals[i] = proposal } + // Check that each value is present in each proposal. + for i, p := range proposals { + for j := 0; j < numKeys; j++ { + got, err := p.Get(keyForTest(i*numKeys + j)) + require.NoError(t, err, "Get(%d)", i*numKeys+j) + assert.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + } + } + // Commit only the first proposal. err := proposals[0].Commit() require.NoError(t, err, "Commit(%d)", 0) @@ -253,6 +262,15 @@ func TestMultipleProposals(t *testing.T) { } } + // Ensure we can still get values from the other proposals. + for i := 1; i < numProposals; i++ { + for j := 0; j < numKeys; j++ { + got, err := proposals[i].Get(keyForTest(i*numKeys + j)) + require.NoError(t, err, "Get(%d)", i*numKeys+j) + assert.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + } + } + // Now we ensure we cannot commit the other proposals. for i := 1; i < numProposals; i++ { err := proposals[i].Commit() @@ -264,6 +282,49 @@ func TestMultipleProposals(t *testing.T) { err := proposals[i].Commit() require.ErrorIs(t, err, errDroppedProposal, "Commit(%d)", i) } + + // Because they're invalid, we should not be able to get values from them. + for i := 1; i < numProposals; i++ { + for j := 0; j < numKeys; j++ { + got, err := proposals[i].Get(keyForTest(i*numKeys + j)) + require.ErrorIs(t, err, errDroppedProposal, "Get(%d)", i*numKeys+j) + assert.Empty(t, got, "Get(%d)", i*numKeys+j) + } + } +} + +// Tests that a proposal that deletes all keys can be committed. +func TestDeleteAll(t *testing.T) { + db := newTestDatabase(t) + + keys := make([][]byte, 10) + vals := make([][]byte, 10) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + // Insert 10 key-value pairs. + db.Update(keys, vals) + + // Create a proposal that deletes all keys. + proposal, err := db.Propose([][]byte{[]byte("key")}, [][]byte{nil}) + require.NoError(t, err, "Propose") + + // Check that the proposal doesn't have the keys we just inserted. + for i := range keys { + got, err := proposal.Get(keys[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Empty(t, got, "Get(%d)", i) + } + + // Commit the proposal. + err = proposal.Commit() + require.NoError(t, err, "Commit") + + // Check that the database is empty. + hash, err := db.Root() + require.NoError(t, err, "%T.Root()", db) + assert.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie") } // Tests that a proposal with an invalid ID cannot be committed. @@ -276,7 +337,11 @@ func TestFakeProposal(t *testing.T) { id: 1, // note that ID 0 is reserved for invalid proposals } + // Attempt to get a value from the fake proposal. + _, err := proposal.Get([]byte("non-existent")) + require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") + // Attempt to commit the fake proposal. - err := proposal.Commit() + err = proposal.Commit() require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") } diff --git a/ffi/proposal.go b/ffi/proposal.go index 06eb37943304..b9c3f851538e 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -25,6 +25,22 @@ type Proposal struct { id uint32 } +func (p *Proposal) Get(key []byte) ([]byte, error) { + if p.handle == nil { + return nil, errDbClosed + } + + if p.id == 0 { + return nil, errDroppedProposal + } + values, cleanup := newValueFactory() + defer cleanup() + + // Get the value for the given key. + val := C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), values.from(key)) + return extractBytesThenFree(&val) +} + func (p *Proposal) Commit() error { if p.handle == nil { return errDbClosed diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 9e92463bc9b8..068c59413199 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -22,8 +22,6 @@ mod metrics_setup; #[doc(hidden)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -/// A proposal ID is a 32-bit unsigned integer. -/// It is used to identify proposals internally. type ProposalId = u32; #[doc(hidden)] @@ -76,10 +74,9 @@ impl Deref for DatabaseHandle<'_> { /// /// A `Value` containing the root hash of the database. /// A `Value` containing {0, "error message"} if the get failed. -/// There are two error cases that may be expected to be nil by the caller, -/// but should be handled externally: -/// * The database has no entries - "IO error: Root hash not found" -/// * The key is not found in the database returns a `Value` with length 0 and a null data pointer. +/// There is one error case that may be expected to be nil by the caller, +/// but should be handled externally: The database has no entries - "IO error: Root hash not found" +/// This is expected behavior if the database is empty. /// /// # Safety /// @@ -88,14 +85,14 @@ impl Deref for DatabaseHandle<'_> { /// * ensure that `key` is a valid pointer to a `Value` struct /// * call `free_value` to free the memory associated with the returned `Value` #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_get(db: *const DatabaseHandle, key: Value) -> Value { - get(db, key).unwrap_or_else(|e| e.into()) +pub unsafe extern "C" fn fwd_get_latest(db: *const DatabaseHandle, key: Value) -> Value { + get_latest(db, key).unwrap_or_else(|e| e.into()) } /// This function is not exposed to the C API. -/// Internal call for `fwd_get` to remove error handling from the C API +/// Internal call for `fwd_get_latest` to remove error handling from the C API #[doc(hidden)] -fn get(db: *const DatabaseHandle, key: Value) -> Result { +fn get_latest(db: *const DatabaseHandle, key: Value) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -117,6 +114,59 @@ fn get(db: *const DatabaseHandle, key: Value) -> Result { Ok(value.into()) } +/// Gets the value associated with the given key from the proposal provided. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `id` - The ID of the proposal to get the value from +/// * `key` - The key to look up, in `Value` form +/// +/// # Returns +/// +/// A `Value` containing the root hash of the database. +/// A `Value` containing {0, "error message"} if the get failed. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `db` is a valid pointer returned by `open_db` +/// * ensure that `key` is a valid pointer to a `Value` struct +/// * call `free_value` to free the memory associated with the returned `Value` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_from_proposal( + db: *const DatabaseHandle, + id: ProposalId, + key: Value, +) -> Value { + get_from_proposal(db, id, key).unwrap_or_else(|e| e.into()) +} + +/// This function is not exposed to the C API. +/// Internal call for `fwd_get_from_proposal` to remove error handling from the C API +#[doc(hidden)] +fn get_from_proposal( + db: *const DatabaseHandle, + id: ProposalId, + key: Value, +) -> Result { + // Check db is valid. + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Get proposal from ID. + let proposals = db.proposals.read().unwrap(); + let proposal = proposals + .get(&id) + .ok_or_else(|| String::from("proposal not found"))?; + + // Get value associated with key. + let value = proposal + .val_sync(key.as_slice()) + .map_err(|e| e.to_string())? + .ok_or_else(|| String::from(""))?; + Ok(value.into()) +} + /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] #[allow(unused)] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 4e762f71bf8a..e755ae99b4e4 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -447,6 +447,12 @@ impl Proposal<'_> { None => Err(api::Error::CannotCommitClonedProposal), } } + + /// Get a value from the proposal synchronously + pub fn val_sync(&self, key: K) -> Result>, api::Error> { + let merkle = Merkle::from(self.nodestore.clone()); + merkle.get_value(key.as_ref()).map_err(api::Error::from) + } } #[cfg(test)] #[expect(clippy::unwrap_used)] From 3925eaf3634416a4b8fae683f4cf8198cd354566 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 14 May 2025 10:36:51 -0400 Subject: [PATCH 0712/1053] feat(ffi): Full proposal support (#878) Co-authored-by: Ron Kuris --- ffi/firewood.h | 52 +++++++- ffi/firewood_test.go | 287 ++++++++++++++++++++++++++++++++++++++++- ffi/proposal.go | 66 ++++++++++ ffi/src/lib.rs | 182 ++++++++++++++++++++------ firewood/src/db.rs | 75 ++++++----- firewood/src/v2/api.rs | 4 +- 6 files changed, 592 insertions(+), 74 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 2c90283b1977..d6cd970ba9d5 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -147,6 +147,23 @@ struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); */ const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); +/** + * Drops a proposal from the database. + * The propopsal's data is now inaccessible, and can be freed by the RevisionManager. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `proposal_id` - The ID of the proposal to drop + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must ensure that `db` is a valid pointer returned by `open_db` + * + */ +struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposal_id); + /** * Frees the memory associated with a `Value`. * @@ -199,7 +216,7 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * * A `Value` containing the root hash of the database. * A `Value` containing {0, "error message"} if the get failed. - * There is one error case that may be expected to be nil by the caller, + * There is one error case that may be expected to be null by the caller, * but should be handled externally: The database has no entries - "IO error: Root hash not found" * This is expected behavior if the database is empty. * @@ -244,7 +261,7 @@ const struct DatabaseHandle *fwd_open_db(struct CreateOrOpenArgs args); * * # Returns * - * The new root hash of the database, in Value form. + * A `Value` containing {id, null} if creating the proposal succeeded. * A `Value` containing {0, "error message"} if creating the proposal failed. * * # Safety @@ -260,9 +277,37 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, size_t nkeys, const struct KeyValue *values); +/** + * Proposes a batch of operations to the database on top of an existing proposal. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `proposal_id` - The ID of the proposal to propose on + * * `nkeys` - The number of key-value pairs to put + * * `values` - A pointer to an array of `KeyValue` structs + * + * # Returns + * + * A `Value` containing {id, nil} if creating the proposal succeeded. + * A `Value` containing {0, "error message"} if creating the proposal failed. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. + * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. + * + */ +struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, + ProposalId proposal_id, + size_t nkeys, + const struct KeyValue *values); + /** * Get the root hash of the latest version of the database - * Don't forget to call `free_value` to free the memory associated with the returned `Value`. * * # Argument * @@ -279,5 +324,6 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, * * This function is unsafe because it dereferences raw pointers. * The caller must ensure that `db` is a valid pointer returned by `open_db` + * */ struct Value fwd_root_hash(const struct DatabaseHandle *db); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 83c28ec96306..2e0b1c513420 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -216,7 +216,7 @@ func TestInvariants(t *testing.T) { assert.Emptyf(t, got, "%T.Get([non-existent key])", db) } -func TestMultipleProposals(t *testing.T) { +func TestParallelProposals(t *testing.T) { db := newTestDatabase(t) // Create 10 proposals, each with 10 keys. @@ -328,7 +328,7 @@ func TestDeleteAll(t *testing.T) { } // Tests that a proposal with an invalid ID cannot be committed. -func TestFakeProposal(t *testing.T) { +func TestCommitFakeProposal(t *testing.T) { db := newTestDatabase(t) // Create a fake proposal with an invalid ID. @@ -340,8 +340,291 @@ func TestFakeProposal(t *testing.T) { // Attempt to get a value from the fake proposal. _, err := proposal.Get([]byte("non-existent")) require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") +} + +func TestDropProposal(t *testing.T) { + db := newTestDatabase(t) + + // Create a proposal with 10 keys. + keys := make([][]byte, 10) + vals := make([][]byte, 10) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + proposal, err := db.Propose(keys, vals) + require.NoError(t, err, "Propose") + + // Drop the proposal. + err = proposal.Drop() + require.NoError(t, err, "Drop") + + // Attempt to commit the dropped proposal. + err = proposal.Commit() + require.ErrorIs(t, err, errDroppedProposal, "Commit(dropped proposal)") + + // Attempt to get a value from the dropped proposal. + _, err = proposal.Get([]byte("non-existent")) + require.ErrorIs(t, err, errDroppedProposal, "Get(dropped proposal)") + + // Attempt to "emulate" the proposal to ensure it isn't internally available still. + proposal = &Proposal{ + handle: db.handle, + id: 1, + } + _, err = proposal.Get([]byte("non-existent")) + require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") + + // Attempt to create a new proposal from the fake proposal. + _, err = proposal.Propose([][]byte{[]byte("key")}, [][]byte{[]byte("value")}) + require.Contains(t, err.Error(), "proposal not found", "Propose(fake proposal)") // Attempt to commit the fake proposal. err = proposal.Commit() require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") } + +// Tests that a proposal can be created from another proposal, and both can be +// committed sequentially. +func TestProposeFromProposal(t *testing.T) { + db := newTestDatabase(t) + + // Create two sets of keys and values. + keys1 := make([][]byte, 10) + vals1 := make([][]byte, 10) + keys2 := make([][]byte, 10) + vals2 := make([][]byte, 10) + for i := range keys1 { + keys1[i] = keyForTest(i) + vals1[i] = valForTest(i) + } + for i := range keys2 { + keys2[i] = keyForTest(i + 10) + vals2[i] = valForTest(i + 10) + } + + // Create the first proposal. + proposal1, err := db.Propose(keys1, vals1) + require.NoError(t, err, "Propose") + // Create the second proposal from the first. + proposal2, err := proposal1.Propose(keys2, vals2) + require.NoError(t, err, "Propose") + + // Assert that the first proposal doesn't have keys from the second. + for i := range keys2 { + got, err := proposal1.Get(keys2[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Empty(t, got, "Get(%d)", i) + } + // Assert that the second proposal has keys from the first. + for i := range keys1 { + got, err := proposal2.Get(keys1[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals1[i], got, "Get(%d)", i) + } + + // Commit the first proposal. + err = proposal1.Commit() + require.NoError(t, err, "Commit") + + // Assert that the second proposal has keys from the first and second. + for i := range keys1 { + got, err := db.Get(keys1[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals1[i], got, "Get(%d)", i) + } + for i := range keys2 { + got, err := proposal2.Get(keys2[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals2[i], got, "Get(%d)", i) + } + + // Commit the second proposal. + err = proposal2.Commit() + require.NoError(t, err, "Commit") + + // Assert that the database has keys from both proposals. + for i := range keys1 { + got, err := db.Get(keys1[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals1[i], got, "Get(%d)", i) + } + for i := range keys2 { + got, err := db.Get(keys2[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals2[i], got, "Get(%d)", i) + } +} + +func TestDeepPropose(t *testing.T) { + db := newTestDatabase(t) + + // Create a chain of two proposals, each with 10 keys. + const numKeys = 10 + const numProposals = 10 + proposals := make([]*Proposal, numProposals) + keys := make([][]byte, numKeys*numProposals) + vals := make([][]byte, numKeys*numProposals) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + + for i := range proposals { + var ( + p *Proposal + err error + ) + if i == 0 { + p, err = db.Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) + require.NoError(t, err, "Propose(%d)", i) + } else { + p, err = proposals[i-1].Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) + require.NoError(t, err, "Propose(%d)", i) + } + proposals[i] = p + } + + // Check that each value is present in the final proposal. + for i := range keys { + got, err := proposals[numProposals-1].Get(keys[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals[i], got, "Get(%d)", i) + } + + // Commit each proposal sequentially, and ensure that the values are + // present in the database after each commit. + for i := range proposals { + err := proposals[i].Commit() + require.NoError(t, err, "Commit(%d)", i) + + for j := i * numKeys; j < (i+1)*numKeys; j++ { + got, err := db.Get(keys[j]) + require.NoError(t, err, "Get(%d)", j) + assert.Equal(t, vals[j], got, "Get(%d)", j) + } + } +} + +// Tests that dropping a proposal and committing another one still allows +// access to the data of children proposals +func TestDropProposalAndCommit(t *testing.T) { + db := newTestDatabase(t) + + // Create a chain of three proposals, each with 10 keys. + const numKeys = 10 + const numProposals = 3 + proposals := make([]*Proposal, numProposals) + keys := make([][]byte, numKeys*numProposals) + vals := make([][]byte, numKeys*numProposals) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + for i := range proposals { + var ( + p *Proposal + err error + ) + if i == 0 { + p, err = db.Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) + require.NoError(t, err, "Propose(%d)", i) + } else { + p, err = proposals[i-1].Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) + require.NoError(t, err, "Propose(%d)", i) + } + proposals[i] = p + } + + // drop the second proposal + err := proposals[1].Drop() + require.NoError(t, err, "Drop(%d)", 1) + // Commit the first proposal + err = proposals[0].Commit() + require.NoError(t, err, "Commit(%d)", 0) + + // Check that the second proposal is dropped + _, err = proposals[1].Get(keys[0]) + require.ErrorIs(t, err, errDroppedProposal, "Get(%d)", 0) + + // Check that all keys can be accessed from the final proposal + for i := range keys { + got, err := proposals[numProposals-1].Get(keys[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, vals[i], got, "Get(%d)", i) + } +} + +// Create two proposals with the same root, and ensure that these proposals +// are identified as unique in the backend. +/* + /- P1 -\ /- P4 +R1 P2 + \- P2 -/ \- P5 +*/ +func TestProposeSameRoot(t *testing.T) { + db := newTestDatabase(t) + + // Create two chains of proposals, resulting in the same root. + keys := make([][]byte, 10) + vals := make([][]byte, 10) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + + // Create the first proposal chain. + proposal1, err := db.Propose(keys[0:5], vals[0:5]) + require.NoError(t, err, "Propose") + proposal3_top, err := proposal1.Propose(keys[5:10], vals[5:10]) + require.NoError(t, err, "Propose") + // Create the second proposal chain. + proposal2, err := db.Propose(keys[5:10], vals[5:10]) + require.NoError(t, err, "Propose") + proposal3_bottom, err := proposal2.Propose(keys[0:5], vals[0:5]) + require.NoError(t, err, "Propose") + // Because the proposals are identical, they should have the same root. + + // Create a unique proposal from each of the two chains. + top_keys := make([][]byte, 5) + top_vals := make([][]byte, 5) + for i := range top_keys { + top_keys[i] = keyForTest(i + 10) + top_vals[i] = valForTest(i + 10) + } + bot_keys := make([][]byte, 5) + bot_vals := make([][]byte, 5) + for i := range bot_keys { + bot_keys[i] = keyForTest(i + 20) + bot_vals[i] = valForTest(i + 20) + } + proposal4, err := proposal3_top.Propose(top_keys, top_vals) + require.NoError(t, err, "Propose") + proposal5, err := proposal3_bottom.Propose(bot_keys, bot_vals) + require.NoError(t, err, "Propose") + + // Now we will commit the top chain, and check that the bottom chain is still valid. + err = proposal1.Commit() + require.NoError(t, err, "Commit") + err = proposal3_top.Commit() + require.NoError(t, err, "Commit") + + // Check that both final proposals are valid. + for i := range keys { + got, err := proposal4.Get(keys[i]) + require.NoError(t, err, "P4 Get(%d)", i) + assert.Equal(t, vals[i], got, "P4 Get(%d)", i) + got, err = proposal5.Get(keys[i]) + require.NoError(t, err, "P5 Get(%d)", i) + assert.Equal(t, vals[i], got, "P5 Get(%d)", i) + } + + // Attempt to commit P5. Since this isn't in the canonical chain, it should + // fail. + err = proposal5.Commit() + require.Error(t, err, "Commit P5") // this error is internal to firewood + + // We should be able to commit P4, since it is in the canonical chain. + err = proposal4.Commit() + require.NoError(t, err, "Commit P4") +} diff --git a/ffi/proposal.go b/ffi/proposal.go index b9c3f851538e..ad058c548f07 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -10,6 +10,7 @@ package firewood import "C" import ( "errors" + "unsafe" ) var errDroppedProposal = errors.New("proposal already dropped") @@ -25,6 +26,8 @@ type Proposal struct { id uint32 } +// Get retrieves the value for the given key. +// If the key does not exist, it returns (nil, nil). func (p *Proposal) Get(key []byte) ([]byte, error) { if p.handle == nil { return nil, errDbClosed @@ -41,6 +44,51 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { return extractBytesThenFree(&val) } +// Propose creates a new proposal with the given keys and values. +// The proposal is not committed until Commit is called. +func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { + if p.handle == nil { + return nil, errDbClosed + } + + if p.id == 0 { + return nil, errDroppedProposal + } + + ops := make([]KeyValue, len(keys)) + for i := range keys { + ops[i] = KeyValue{keys[i], vals[i]} + } + + values, cleanup := newValueFactory() + defer cleanup() + + ffiOps := make([]C.struct_KeyValue, len(ops)) + for i, op := range ops { + ffiOps[i] = C.struct_KeyValue{ + key: values.from(op.Key), + value: values.from(op.Value), + } + } + + // Propose the keys and values. + val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), + C.size_t(len(ffiOps)), + (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), + ) + id, err := extractIdThenFree(&val) + if err != nil { + return nil, err + } + + return &Proposal{ + handle: p.handle, + id: id, + }, nil +} + +// Commit commits the proposal and returns any errors. +// If an error occurs, the proposal is dropped and no longer valid. func (p *Proposal) Commit() error { if p.handle == nil { return errDbClosed @@ -60,3 +108,21 @@ func (p *Proposal) Commit() error { } return err } + +// Drop removes the proposal from memory in Firewood. +// In the case of an error, the proposal can assumed to be dropped. +// An error is returned if the proposal was already dropped. +func (p *Proposal) Drop() error { + if p.handle == nil { + return errDbClosed + } + + if p.id == 0 { + return errDroppedProposal + } + + // Drop the proposal. + val := C.fwd_drop_proposal(p.handle, C.uint32_t(p.id)) + p.id = 0 + return extractErrorThenFree(&val) +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 068c59413199..2e7da550acc8 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -74,7 +74,7 @@ impl Deref for DatabaseHandle<'_> { /// /// A `Value` containing the root hash of the database. /// A `Value` containing {0, "error message"} if the get failed. -/// There is one error case that may be expected to be nil by the caller, +/// There is one error case that may be expected to be null by the caller, /// but should be handled externally: The database has no entries - "IO error: Root hash not found" /// This is expected behavior if the database is empty. /// @@ -154,7 +154,10 @@ fn get_from_proposal( let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; // Get proposal from ID. - let proposals = db.proposals.read().unwrap(); + let proposals = db + .proposals + .read() + .map_err(|_| "proposal lock is poisoned")?; let proposal = proposals .get(&id) .ok_or_else(|| String::from("proposal not found"))?; @@ -213,6 +216,30 @@ pub unsafe extern "C" fn fwd_batch( batch(db, nkeys, values).unwrap_or_else(|e| e.into()) } +/// Converts a slice of `KeyValue` structs to a vector of `DbBatchOp` structs. +/// +/// # Arguments +/// +/// * `values` - A slice of `KeyValue` structs +/// +/// # Returns +fn convert_to_batch(values: &[KeyValue]) -> Vec> { + let mut batch = Vec::with_capacity(values.len()); + for kv in values { + if kv.value.len == 0 { + batch.push(DbBatchOp::DeleteRange { + prefix: kv.key.as_slice(), + }); + } else { + batch.push(DbBatchOp::Put { + key: kv.key.as_slice(), + value: kv.value.as_slice(), + }); + } + } + batch +} + /// Internal call for `fwd_batch` to remove error handling from the C API #[doc(hidden)] fn batch( @@ -225,21 +252,8 @@ fn batch( let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; // Create a batch of operations to perform. - let mut batch = Vec::with_capacity(nkeys); - for i in 0..nkeys { - let kv = unsafe { values.add(i).as_ref() } - .ok_or_else(|| String::from("key-value pair is null"))?; - if kv.value.len == 0 { - batch.push(DbBatchOp::DeleteRange { - prefix: kv.key.as_slice(), - }); - continue; - } - batch.push(DbBatchOp::Put { - key: kv.key.as_slice(), - value: kv.value.as_slice(), - }); - } + let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; + let batch = convert_to_batch(key_value_ref); // Propose the batch of operations. let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; @@ -249,7 +263,7 @@ fn batch( let hash_val = proposal .root_hash_sync() .map_err(|e| e.to_string())? - .ok_or(String::from("Proposed revision is empty"))? + .ok_or_else(|| String::from("Proposed revision is empty"))? .as_slice() .into(); @@ -274,7 +288,7 @@ fn batch( /// /// # Returns /// -/// The new root hash of the database, in Value form. +/// A `Value` containing {id, null} if creating the proposal succeeded. /// A `Value` containing {0, "error message"} if creating the proposal failed. /// /// # Safety @@ -308,27 +322,87 @@ fn propose_on_db( let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; // Create a batch of operations to perform. - let mut batch = Vec::with_capacity(nkeys); - for i in 0..nkeys { - let kv = unsafe { values.add(i).as_ref() } - .ok_or_else(|| String::from("couldn't get key-value pair"))?; - if kv.value.len == 0 { - batch.push(DbBatchOp::DeleteRange { - prefix: kv.key.as_slice(), - }); - continue; - } - batch.push(DbBatchOp::Put { - key: kv.key.as_slice(), - value: kv.value.as_slice(), - }); - } + let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; + let batch = convert_to_batch(key_value_ref); + + // Propose the batch of operations. let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; let proposal_id = next_id(); // Guaranteed to be non-zero - db.proposals.write().unwrap().insert(proposal_id, proposal); + db.proposals + .write() + .map_err(|_| "proposal lock is poisoned")? + .insert(proposal_id, proposal); Ok(proposal_id) } +/// Proposes a batch of operations to the database on top of an existing proposal. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `proposal_id` - The ID of the proposal to propose on +/// * `nkeys` - The number of key-value pairs to put +/// * `values` - A pointer to an array of `KeyValue` structs +/// +/// # Returns +/// +/// A `Value` containing {id, nil} if creating the proposal succeeded. +/// A `Value` containing {0, "error message"} if creating the proposal failed. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must: +/// * ensure that `db` is a valid pointer returned by `open_db` +/// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. +/// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_propose_on_proposal( + db: *const DatabaseHandle, + proposal_id: ProposalId, + nkeys: usize, + values: *const KeyValue, +) -> Value { + // Note: the id is guaranteed to be non-zero + // because we use an atomic counter that starts at 1. + propose_on_proposal(db, proposal_id, nkeys, values) + .map(|id| id.into()) + .unwrap_or_else(|e| e.into()) +} + +/// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API +#[doc(hidden)] +fn propose_on_proposal( + db: *const DatabaseHandle, + proposal_id: ProposalId, + nkeys: usize, + values: *const KeyValue, +) -> Result { + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Create a batch of operations to perform. + let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; + let batch = convert_to_batch(key_value_ref); + + // Get proposal from ID. + // We need write access to add the proposal after we create it. + let guard = db.proposals.write().unwrap(); + let proposal = guard + .get(&proposal_id) + .ok_or_else(|| String::from("proposal not found"))?; + let new_proposal = proposal.propose_sync(batch).map_err(|e| e.to_string())?; + drop(guard); // Drop the read lock before we get the write lock. + + // Store the proposal in the map. We need the write lock instead. + let new_id = next_id(); // Guaranteed to be non-zero + db.proposals + .write() + .map_err(|_| "proposal lock is poisoned")? + .insert(new_id, new_proposal); + Ok(new_id) +} + /// Commits a proposal to the database. /// /// # Arguments @@ -360,14 +434,47 @@ fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { let proposal = db .proposals .write() - .unwrap() + .map_err(|_| "proposal lock is poisoned")? .remove(&proposal_id) .ok_or_else(|| String::from("proposal not found"))?; proposal.commit_sync().map_err(|e| e.to_string()) } +/// Drops a proposal from the database. +/// The propopsal's data is now inaccessible, and can be freed by the RevisionManager. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `proposal_id` - The ID of the proposal to drop +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that `db` is a valid pointer returned by `open_db` +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Value { + drop_proposal(db, proposal_id) + .map(|e| e.into()) + .unwrap_or_else(|e| e.into()) +} + +/// Internal call for `fwd_drop_proposal` to remove error handling from the C API +#[doc(hidden)] +fn drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + let mut proposals = db + .proposals + .write() + .map_err(|_| "proposal lock is poisoned")?; + proposals + .remove(&proposal_id) + .ok_or_else(|| String::from("proposal not found"))?; + Ok(()) +} + /// Get the root hash of the latest version of the database -/// Don't forget to call `free_value` to free the memory associated with the returned `Value`. /// /// # Argument /// @@ -384,6 +491,7 @@ fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { /// /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `db` is a valid pointer returned by `open_db` +/// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_root_hash(db: *const DatabaseHandle) -> Value { // Check db is valid. diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e755ae99b4e4..f7820edb9105 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -393,36 +393,7 @@ impl<'a> api::Proposal for Proposal<'a> { self: Arc, batch: api::Batch, ) -> Result, api::Error> { - let parent = self.nodestore.clone(); - let proposal = NodeStore::new(parent)?; - let mut merkle = Merkle::from(proposal); - for op in batch { - match op { - BatchOp::Put { key, value } => { - merkle.insert(key.as_ref(), value.as_ref().into())?; - } - BatchOp::Delete { key } => { - merkle.remove(key.as_ref())?; - } - BatchOp::DeleteRange { prefix } => { - merkle.remove_prefix(prefix.as_ref())?; - } - } - } - let nodestore = merkle.into_inner(); - let immutable: Arc, FileBacked>> = - Arc::new(nodestore.try_into()?); - self.db - .manager - .write() - .expect("poisoned lock") - .add_proposal(immutable.clone()); - - Ok(Self::Proposal { - nodestore: immutable, - db: self.db, - } - .into()) + Ok(self.create_proposal(batch)?.into()) } async fn commit(self: Arc) -> Result<(), api::Error> { @@ -453,7 +424,51 @@ impl Proposal<'_> { let merkle = Merkle::from(self.nodestore.clone()); merkle.get_value(key.as_ref()).map_err(api::Error::from) } + + /// Create a new proposal from the current one synchronously + pub fn propose_sync( + &self, + batch: api::Batch, + ) -> Result, api::Error> { + Ok(self.create_proposal(batch)?.into()) + } + + fn create_proposal( + &self, + batch: api::Batch, + ) -> Result { + let parent = self.nodestore.clone(); + let proposal = NodeStore::new(parent)?; + let mut merkle = Merkle::from(proposal); + for op in batch { + match op { + BatchOp::Put { key, value } => { + merkle.insert(key.as_ref(), value.as_ref().into())?; + } + BatchOp::Delete { key } => { + merkle.remove(key.as_ref())?; + } + BatchOp::DeleteRange { prefix } => { + merkle.remove_prefix(prefix.as_ref())?; + } + } + } + let nodestore = merkle.into_inner(); + let immutable: Arc, FileBacked>> = + Arc::new(nodestore.try_into()?); + self.db + .manager + .write() + .expect("poisoned lock") + .add_proposal(immutable.clone()); + + Ok(Self { + nodestore: immutable, + db: self.db, + }) + } } + #[cfg(test)] #[expect(clippy::unwrap_used)] mod test { diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 614b5d79970a..d31acce740eb 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -23,9 +23,9 @@ impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// This also means that the type of all the keys for a single /// API call must be the same, as well as the type of all values /// must be the same. -pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug + 'static {} +pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug {} -impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug + 'static {} +impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// The type and size of a single hash key /// These are 256-bit hashes that are used for a variety of reasons: From 4a9fe59ab8d9c0cedb127af7b6818f7ee0823b6e Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 14 May 2025 10:59:03 -0400 Subject: [PATCH 0713/1053] feat(ffi): Support `Get` for historical revisions (#881) --- ffi/firewood.go | 8 +++- ffi/firewood.h | 30 ++++++++++++++- ffi/firewood_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++++ ffi/revision.go | 65 +++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 52 +++++++++++++++++++++++++- 5 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 ffi/revision.go diff --git a/ffi/firewood.go b/ffi/firewood.go index 1790aae01db8..884f8c3b202a 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -19,6 +19,7 @@ import ( // These constants are used to identify errors returned by the Firewood Rust FFI. // These must be changed if the Rust FFI changes - should be reported by tests. const ( + RootLength = 32 rootHashNotFound = "IO error: Root hash not found" keyNotFound = "key not found" ) @@ -197,12 +198,17 @@ func (db *Database) Root() ([]byte, error) { // If the root hash is not found, return a zeroed slice. if err != nil && strings.Contains(err.Error(), rootHashNotFound) { - bytes = make([]byte, 32) + bytes = make([]byte, RootLength) err = nil } return bytes, err } +// Revision returns a historical revision of the database. +func (db *Database) Revision(root []byte) (*Revision, error) { + return NewRevision(db.handle, root) +} + // Close closes the database and releases all held resources. // Returns an error if already closed. func (db *Database) Close() error { diff --git a/ffi/firewood.h b/ffi/firewood.h index d6cd970ba9d5..692ffd6f7343 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -190,7 +190,7 @@ void fwd_free_value(const struct Value *value); * * # Returns * - * A `Value` containing the root hash of the database. + * A `Value` containing the requested value. * A `Value` containing {0, "error message"} if the get failed. * * # Safety @@ -204,6 +204,32 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, ProposalId id, struct Value key); +/** + * Gets a value assoicated with the given historical root hash and key. + * + * # Arguments + * + * * `db` - The database handle returned by `open_db` + * * `root` - The root hash to look up, in `Value` form + * * `key` - The key to look up, in `Value` form + * + * # Returns + * + * A `Value` containing the requested value. + * A `Value` containing {0, "error message"} if the get failed. + * + * # Safety + * + * The caller must: + * * ensure that `db` is a valid pointer returned by `open_db` + * * ensure that `key` is a valid pointer to a `Value` struct + * * ensure that `root` is a valid pointer to a `Value` struct + * * call `free_value` to free the memory associated with the returned `Value` + */ +struct Value fwd_get_from_root(const struct DatabaseHandle *db, + struct Value root, + struct Value key); + /** * Gets the value associated with the given key from the database. * @@ -214,7 +240,7 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * * # Returns * - * A `Value` containing the root hash of the database. + * A `Value` containing the requested value. * A `Value` containing {0, "error message"} if the get failed. * There is one error case that may be expected to be null by the caller, * but should be handled externally: The database has no entries - "IO error: Root hash not found" diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 2e0b1c513420..610b038d6848 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -384,6 +384,7 @@ func TestDropProposal(t *testing.T) { require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") } +// Create a proposal with 10 key-value pairs. // Tests that a proposal can be created from another proposal, and both can be // committed sequentially. func TestProposeFromProposal(t *testing.T) { @@ -628,3 +629,89 @@ func TestProposeSameRoot(t *testing.T) { err = proposal4.Commit() require.NoError(t, err, "Commit P4") } + +// Tests that an empty revision can be retrieved. +func TestRevision(t *testing.T) { + db := newTestDatabase(t) + + keys := make([][]byte, 10) + vals := make([][]byte, 10) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + + // Create a proposal with 10 key-value pairs. + proposal, err := db.Propose(keys, vals) + require.NoError(t, err, "Propose") + + // Commit the proposal. + err = proposal.Commit() + require.NoError(t, err, "Commit") + + root, err := db.Root() + require.NoError(t, err, "%T.Root()", db) + + // Create a revision from this root. + revision, err := NewRevision(db.handle, root) + require.NoError(t, err, "NewRevision") + // Check that all keys can be retrieved from the revision. + for i := range keys { + got, err := revision.Get(keys[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, valForTest(i), got, "Get(%d)", i) + } + + // Create a second proposal with 10 key-value pairs. + keys2 := make([][]byte, 10) + vals2 := make([][]byte, 10) + for i := range keys2 { + keys2[i] = keyForTest(i + 10) + vals2[i] = valForTest(i + 10) + } + proposal2, err := db.Propose(keys2, vals2) + require.NoError(t, err, "Propose") + // Commit the proposal. + err = proposal2.Commit() + require.NoError(t, err, "Commit") + + // Create a "new" revision from the first old root. + revision, err = db.Revision(root) + require.NoError(t, err, "NewRevision") + // Check that all keys can be retrieved from the revision. + for i := range keys { + got, err := revision.Get(keys[i]) + require.NoError(t, err, "Get(%d)", i) + assert.Equal(t, valForTest(i), got, "Get(%d)", i) + } +} + +func TestFakeRevision(t *testing.T) { + db := newTestDatabase(t) + + // Create a nil revision. + revision, err := db.Revision(nil) + require.ErrorIs(t, err, errInvalidRoot, "NewRevision(nil)") + assert.Nil(t, revision, "NewRevision(nil)") + + // Create a fake revision with an invalid root. + invalidRoot := []byte("not a valid root") + revision, err = db.Revision(invalidRoot) + require.ErrorIs(t, err, errInvalidRoot, "NewRevision(invalid root)") + require.Nil(t, revision, "NewRevision(invalid root)") + + // Create a fake revision with an valid root. + validRoot := []byte("counting 32 bytes to make a hash") + assert.Len(t, validRoot, 32, "valid root") + revision, err = db.Revision(validRoot) + require.NoError(t, err, "NewRevision(valid root)") + require.NotNil(t, revision, "NewRevision(valid root)") + + // Attempt to get a value from the fake revision. + _, err = revision.Get([]byte("non-existent")) + require.Contains(t, err.Error(), "Revision not found", "Get(non-existent)") + + // Attempt to get from the now invalid revision. + _, err = revision.Get([]byte("non-existent")) + require.ErrorIs(t, err, errRevisionClosed, "Get(non-existent)") +} diff --git a/ffi/revision.go b/ffi/revision.go new file mode 100644 index 000000000000..fc1c8b7a2f87 --- /dev/null +++ b/ffi/revision.go @@ -0,0 +1,65 @@ +// Package firewood provides a Go wrapper around the [Firewood] database. +// +// [Firewood]: https://github.com/ava-labs/firewood +package firewood + +// // Note that -lm is required on Linux but not on Mac. +// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm +// #include +// #include "firewood.h" +import "C" +import ( + "errors" + "fmt" +) + +var ( + errRevisionClosed = errors.New("firewood revision already closed") + errInvalidRoot = fmt.Errorf("firewood error: root hash must be %d bytes", RootLength) +) + +type Revision struct { + // handle is returned and accepted by cgo functions. It MUST be treated as + // an opaque value without special meaning. + // https://en.wikipedia.org/wiki/Blinkenlights + handle *C.DatabaseHandle + // The revision root + root []byte +} + +func NewRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { + if handle == nil { + return nil, errors.New("firewood error: nil handle or root") + } + + // Check that the root is the correct length. + if root == nil || len(root) != RootLength { + return nil, errInvalidRoot + } + + // All other verification of the root is done during use. + return &Revision{ + handle: handle, + root: root, + }, nil +} + +func (r *Revision) Get(key []byte) ([]byte, error) { + if r.handle == nil { + return nil, errDbClosed + } + if r.root == nil { + return nil, errRevisionClosed + } + + values, cleanup := newValueFactory() + defer cleanup() + + val := C.fwd_get_from_root(r.handle, values.from(r.root), values.from(key)) + value, err := extractBytesThenFree(&val) + if err != nil { + // Any error from this function indicates that the revision is inaccessible. + r.root = nil + } + return value, err +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 2e7da550acc8..0086775536c6 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -72,7 +72,7 @@ impl Deref for DatabaseHandle<'_> { /// /// # Returns /// -/// A `Value` containing the root hash of the database. +/// A `Value` containing the requested value. /// A `Value` containing {0, "error message"} if the get failed. /// There is one error case that may be expected to be null by the caller, /// but should be handled externally: The database has no entries - "IO error: Root hash not found" @@ -124,7 +124,7 @@ fn get_latest(db: *const DatabaseHandle, key: Value) -> Result { /// /// # Returns /// -/// A `Value` containing the root hash of the database. +/// A `Value` containing the requested value. /// A `Value` containing {0, "error message"} if the get failed. /// /// # Safety @@ -170,6 +170,54 @@ fn get_from_proposal( Ok(value.into()) } +/// Gets a value assoicated with the given historical root hash and key. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by `open_db` +/// * `root` - The root hash to look up, in `Value` form +/// * `key` - The key to look up, in `Value` form +/// +/// # Returns +/// +/// A `Value` containing the requested value. +/// A `Value` containing {0, "error message"} if the get failed. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `db` is a valid pointer returned by `open_db` +/// * ensure that `key` is a valid pointer to a `Value` struct +/// * ensure that `root` is a valid pointer to a `Value` struct +/// * call `free_value` to free the memory associated with the returned `Value` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_from_root( + db: *const DatabaseHandle, + root: Value, + key: Value, +) -> Value { + get_from_root(db, root, key).unwrap_or_else(|e| e.into()) +} + +/// Internal call for `fwd_get_from_root` to remove error handling from the C API +#[doc(hidden)] +fn get_from_root(db: *const DatabaseHandle, root: Value, key: Value) -> Result { + // Check db is valid. + let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + + // Get the revision associated with the root hash. + let rev = db + .revision_sync(root.as_slice().try_into()?) + .map_err(|e| e.to_string())?; + + // Get value associated with key. + let value = rev + .val_sync(key.as_slice()) + .map_err(|e| e.to_string())? + .ok_or_else(|| String::from(""))?; + Ok(value.into()) +} + /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] #[allow(unused)] From fd6b7341898d0d77a2120e409a229f08e4d18e8d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 14 May 2025 11:12:01 -0700 Subject: [PATCH 0714/1053] Error improvements (#857) --- ffi/Cargo.toml | 7 ++++ ffi/build.rs | 4 +-- ffi/firewood.h | 7 ++-- ffi/src/lib.rs | 70 ++++++++++++++++++++-------------------- ffi/src/metrics_setup.rs | 18 +++++------ firewood/src/manager.rs | 1 - 6 files changed, 58 insertions(+), 49 deletions(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index be5ce6e90f5a..ca0d9b4ab1fe 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,3 +23,10 @@ logger = ["dep:env_logger", "firewood/logger"] [build-dependencies] cbindgen = "0.28.0" + +[lints.clippy] +unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" +missing_const_for_fn = "warn" +pedantic = "warn" diff --git a/ffi/build.rs b/ffi/build.rs index 630c98b4b3ef..f49a0f706f65 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -3,7 +3,7 @@ use std::env; extern crate cbindgen; fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set"); let config = cbindgen::Config::from_file("cbindgen.toml").expect("cbindgen.toml is present"); @@ -15,7 +15,7 @@ fn main() { .map_or_else( |error| match error { cbindgen::Error::ParseSyntaxError { .. } => {} - e => panic!("{:?}", e), + e => panic!("{e:?}"), }, |bindings| { bindings.write_to_file("firewood.h"); diff --git a/ffi/firewood.h b/ffi/firewood.h index 692ffd6f7343..2acccf8e1089 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -100,7 +100,7 @@ struct Value fwd_batch(const struct DatabaseHandle *db, * * # Arguments * - * * `db` - The database handle to close, previously returned from a call to open_db() + * * `db` - The database handle to close, previously returned from a call to `open_db()` */ void fwd_close_db(struct DatabaseHandle *db); @@ -149,7 +149,7 @@ const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); /** * Drops a proposal from the database. - * The propopsal's data is now inaccessible, and can be freed by the RevisionManager. + * The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. * * # Arguments * @@ -176,6 +176,9 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa * This function is unsafe because it dereferences raw pointers. * The caller must ensure that `value` is a valid pointer. * + * # Panics + * + * This function panics if `value` is `null`. */ void fwd_free_value(const struct Value *value); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 0086775536c6..67334fe77f20 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -86,13 +86,13 @@ impl Deref for DatabaseHandle<'_> { /// * call `free_value` to free the memory associated with the returned `Value` #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_latest(db: *const DatabaseHandle, key: Value) -> Value { - get_latest(db, key).unwrap_or_else(|e| e.into()) + get_latest(db, &key).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. /// Internal call for `fwd_get_latest` to remove error handling from the C API #[doc(hidden)] -fn get_latest(db: *const DatabaseHandle, key: Value) -> Result { +fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -110,7 +110,7 @@ fn get_latest(db: *const DatabaseHandle, key: Value) -> Result { let value = rev .val_sync(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(|| String::from(""))?; + .ok_or_else(String::new)?; Ok(value.into()) } @@ -139,7 +139,7 @@ pub unsafe extern "C" fn fwd_get_from_proposal( id: ProposalId, key: Value, ) -> Value { - get_from_proposal(db, id, key).unwrap_or_else(|e| e.into()) + get_from_proposal(db, id, &key).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. @@ -148,7 +148,7 @@ pub unsafe extern "C" fn fwd_get_from_proposal( fn get_from_proposal( db: *const DatabaseHandle, id: ProposalId, - key: Value, + key: &Value, ) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -166,7 +166,7 @@ fn get_from_proposal( let value = proposal .val_sync(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(|| String::from(""))?; + .ok_or_else(String::new)?; Ok(value.into()) } @@ -196,12 +196,12 @@ pub unsafe extern "C" fn fwd_get_from_root( root: Value, key: Value, ) -> Value { - get_from_root(db, root, key).unwrap_or_else(|e| e.into()) + get_from_root(db, &root, &key).unwrap_or_else(Into::into) } /// Internal call for `fwd_get_from_root` to remove error handling from the C API #[doc(hidden)] -fn get_from_root(db: *const DatabaseHandle, root: Value, key: Value) -> Result { +fn get_from_root(db: *const DatabaseHandle, root: &Value, key: &Value) -> Result { // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; @@ -214,7 +214,7 @@ fn get_from_root(db: *const DatabaseHandle, root: Value, key: Value) -> Result Value { - batch(db, nkeys, values).unwrap_or_else(|e| e.into()) + batch(db, nkeys, values).unwrap_or_else(Into::into) } /// Converts a slice of `KeyValue` structs to a vector of `DbBatchOp` structs. @@ -355,9 +355,7 @@ pub unsafe extern "C" fn fwd_propose_on_db( ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_db(db, nkeys, values) - .map(|id| id.into()) - .unwrap_or_else(|e| e.into()) + propose_on_db(db, nkeys, values).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_propose_on_db` to remove error handling from the C API @@ -414,9 +412,7 @@ pub unsafe extern "C" fn fwd_propose_on_proposal( ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_proposal(db, proposal_id, nkeys, values) - .map(|id| id.into()) - .unwrap_or_else(|e| e.into()) + propose_on_proposal(db, proposal_id, nkeys, values).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API @@ -435,7 +431,10 @@ fn propose_on_proposal( // Get proposal from ID. // We need write access to add the proposal after we create it. - let guard = db.proposals.write().unwrap(); + let guard = db + .proposals + .write() + .expect("failed to acquire write lock on proposals"); let proposal = guard .get(&proposal_id) .ok_or_else(|| String::from("proposal not found"))?; @@ -470,9 +469,7 @@ fn propose_on_proposal( /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_commit(db: *const DatabaseHandle, proposal_id: u32) -> Value { - commit(db, proposal_id) - .map(|e| e.into()) - .unwrap_or_else(|e| e.into()) + commit(db, proposal_id).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_commit` to remove error handling from the C API @@ -489,7 +486,7 @@ fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { } /// Drops a proposal from the database. -/// The propopsal's data is now inaccessible, and can be freed by the RevisionManager. +/// The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. /// /// # Arguments /// @@ -503,9 +500,7 @@ fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Value { - drop_proposal(db, proposal_id) - .map(|e| e.into()) - .unwrap_or_else(|e| e.into()) + drop_proposal(db, proposal_id).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_drop_proposal` to remove error handling from the C API @@ -543,7 +538,7 @@ fn drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), Stri #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_root_hash(db: *const DatabaseHandle) -> Value { // Check db is valid. - root_hash(db).unwrap_or_else(|e| e.into()) + root_hash(db).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. @@ -588,16 +583,17 @@ impl Display for Value { match (self.len, self.data.is_null()) { (0, true) => write!(f, "[not found]"), (0, false) => write!(f, "[error] {}", unsafe { - CStr::from_ptr(self.data as *const i8).to_string_lossy() + CStr::from_ptr(self.data.cast::()).to_string_lossy() }), - (len, true) => write!(f, "[id] {}", len), + (len, true) => write!(f, "[id] {len}"), (_, false) => write!(f, "[data] {:?}", self.as_slice()), } } } impl Value { - pub fn as_slice(&self) -> &[u8] { + #[must_use] + pub const fn as_slice(&self) -> &[u8] { unsafe { std::slice::from_raw_parts(self.data, self.len) } } } @@ -647,7 +643,7 @@ impl From for Value { } impl From<()> for Value { - fn from(_: ()) -> Self { + fn from((): ()) -> Self { Self { len: 0, data: std::ptr::null(), @@ -666,6 +662,9 @@ impl From<()> for Value { /// This function is unsafe because it dereferences raw pointers. /// The caller must ensure that `value` is a valid pointer. /// +/// # Panics +/// +/// This function panics if `value` is `null`. #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_free_value(value: *const Value) { // Check value is valid. @@ -679,7 +678,7 @@ pub unsafe extern "C" fn fwd_free_value(value: *const Value) { if value.len > 0 { let recreated_box = unsafe { Box::from_raw(std::slice::from_raw_parts_mut( - value.data as *mut u8, + value.data.cast_mut(), value.len, )) }; @@ -816,13 +815,14 @@ fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> Revision /// /// # Arguments /// -/// * `db` - The database handle to close, previously returned from a call to open_db() +/// * `db` - The database handle to close, previously returned from a call to `open_db()` #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_close_db(db: *mut DatabaseHandle) { let _ = unsafe { Box::from_raw(db) }; } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; @@ -832,7 +832,7 @@ mod tests { len: 0, data: std::ptr::null(), }; - assert_eq!(format!("{}", value), "[not found]"); + assert_eq!(format!("{value}"), "[not found]"); } #[test] @@ -842,7 +842,7 @@ mod tests { len: 0, data: cstr.as_ptr().cast::(), }; - assert_eq!(format!("{}", value), "[error] test"); + assert_eq!(format!("{value}"), "[error] test"); } #[test] @@ -851,7 +851,7 @@ mod tests { len: 4, data: Box::leak(b"test".to_vec().into_boxed_slice()).as_ptr(), }; - assert_eq!(format!("{}", value), "[data] [116, 101, 115, 116]"); + assert_eq!(format!("{value}"), "[data] [116, 101, 115, 116]"); } #[test] @@ -860,6 +860,6 @@ mod tests { len: 4, data: std::ptr::null(), }; - assert_eq!(format!("{}", value), "[id] 4"); + assert_eq!(format!("{value}"), "[id] 4"); } } diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index d833a9f18a5b..1ff39b3093f6 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -29,17 +29,17 @@ pub(crate) fn setup_metrics(metrics_port: u16) { metrics::set_global_recorder(recorder.clone()).expect("failed to set recorder"); Server::new(move |request| { - if request.method() != "GET" { - Response::builder() - .status(StatusCode::METHOD_NOT_ALLOWED) - .body(Body::from("Method not allowed")) - .expect("failed to build response") - } else { + if request.method() == "GET" { Response::builder() .status(StatusCode::OK) .header("Content-Type", "text/plain") .body(Body::from(recorder.stats())) .expect("failed to build response") + } else { + Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::from("Method not allowed")) + .expect("failed to build response") } }) .bind((Ipv4Addr::LOCALHOST, metrics_port)) @@ -68,9 +68,9 @@ impl TextRecorder { let utc_now: DateTime = systemtime_now.into(); let epoch_duration = systemtime_now .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); - let epoch_ms = epoch_duration.as_secs() * 1000 + epoch_duration.subsec_millis() as u64; - writeln!(output, "# {}", utc_now).unwrap(); + .expect("system time is before Unix epoch"); + let epoch_ms = epoch_duration.as_secs() * 1000 + u64::from(epoch_duration.subsec_millis()); + writeln!(output, "# {utc_now}").unwrap(); let counters = self.registry.get_counter_handles(); let mut seen = HashSet::new(); diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 38458ac100ae..a215708e0bd1 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -15,7 +15,6 @@ use crate::v2::api::HashKey; pub use storage::CacheReadStrategy; use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; - #[derive(Clone, Debug, TypedBuilder)] /// Revision manager configuratoin pub struct RevisionManagerConfig { From c89a42620a24bcc5942f796212ec66f312038e40 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 14 May 2025 12:33:05 -0700 Subject: [PATCH 0715/1053] Austin owns ffi (#889) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 77cb728c8696..082826bc3a59 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS * @rkuris @aaronbuchwald - +/ffi @alarso16 From 354e908c03120ad0d3ec50492068cd0ef1a4e4d7 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 14 May 2025 13:19:02 -0700 Subject: [PATCH 0716/1053] If ethhash is enabled, return the hash for nil RLP (#888) --- firewood/src/manager.rs | 56 ++++++++++++++++++++++++++++------ storage/src/hashers/ethhash.rs | 1 + storage/src/lib.rs | 13 ++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index a215708e0bd1..546fc1041cd6 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -6,6 +6,8 @@ use std::io::Error; use std::num::NonZero; use std::path::PathBuf; use std::sync::Arc; +#[cfg(feature = "ethhash")] +use std::sync::OnceLock; use storage::logger::{trace, trace_enabled, warn}; use typed_builder::TypedBuilder; @@ -47,6 +49,9 @@ pub(crate) struct RevisionManager { proposals: Vec, // committing_proposals: VecDeque>, by_hash: HashMap, + + #[cfg(feature = "ethhash")] + empty_hash: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -82,12 +87,16 @@ impl RevisionManager { by_hash: Default::default(), proposals: Default::default(), // committing_proposals: Default::default(), + #[cfg(feature = "ethhash")] + empty_hash: OnceLock::new(), }; - if nodestore.kind.root_hash().is_some() { - manager.by_hash.insert( - nodestore.kind.root_hash().expect("root hash is present"), - nodestore.clone(), - ); + + if let Some(hash) = nodestore + .kind + .root_hash() + .or_else(|| manager.empty_trie_hash()) + { + manager.by_hash.insert(hash, nodestore.clone()); } if truncate { @@ -100,15 +109,19 @@ impl RevisionManager { pub fn all_hashes(&self) -> Vec { self.historical .iter() - .filter_map(|r| r.kind.root_hash()) - .chain(self.proposals.iter().filter_map(|p| p.kind.root_hash())) + .filter_map(|r| r.kind.root_hash().or_else(|| self.empty_trie_hash())) + .chain( + self.proposals + .iter() + .filter_map(|p| p.kind.root_hash().or_else(|| self.empty_trie_hash())), + ) .collect() } /// Commit a proposal /// To commit a proposal involves a few steps: /// 1. Commit check. - /// The proposal’s parent must be the last committed revision, otherwise the commit fails. + /// The proposal's parent must be the last committed revision, otherwise the commit fails. /// 2. Persist delete list. /// The list of all nodes that were to be deleted for this proposal must be fully flushed to disk. /// The address of the root node and the root hash is also persisted. @@ -152,7 +165,7 @@ impl RevisionManager { // TODO: Handle the case where we get something off the free list that is not free while self.historical.len() >= self.max_revisions { let oldest = self.historical.pop_front().expect("must be present"); - if let Some(oldest_hash) = oldest.kind.root_hash() { + if let Some(oldest_hash) = oldest.kind.root_hash().or_else(|| self.empty_trie_hash()) { self.by_hash.remove(&oldest_hash); } @@ -174,7 +187,11 @@ impl RevisionManager { // 4. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical.push_back(committed.clone()); - if let Some(hash) = committed.kind.root_hash() { + if let Some(hash) = committed + .kind + .root_hash() + .or_else(|| self.empty_trie_hash()) + { self.by_hash.insert(hash, committed.clone()); } // TODO: We could allow other commits to start here using the pending list @@ -227,6 +244,7 @@ impl RevisionManager { self.current_revision() .kind .root_hash() + .or_else(|| self.empty_trie_hash()) .map(Option::Some) .ok_or(RevisionManagerError::IO(std::io::Error::new( std::io::ErrorKind::NotFound, @@ -240,6 +258,24 @@ impl RevisionManager { .expect("there is always one revision") .clone() } + #[cfg(not(feature = "ethhash"))] + #[inline] + pub const fn empty_trie_hash(&self) -> Option { + None + } + + #[cfg(feature = "ethhash")] + #[inline] + pub fn empty_trie_hash(&self) -> Option { + // clippy is wrong here. we need to keep the closure since empty_trie_hash + // is an instance method that needs self. + #[allow(clippy::redundant_closure)] + Some( + self.empty_hash + .get_or_init(|| storage::empty_trie_hash()) + .clone(), + ) + } } #[cfg(test)] diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index cb3afe8c9c4e..5d3e38ed41b1 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -194,6 +194,7 @@ impl Preimage for T { // we've collected all the children in bytes + #[allow(clippy::let_and_return)] let updated_bytes = if is_account { // need to get the value again if let Some(ValueDigest::Value(rlp_encoded_bytes)) = self.value_digest() { diff --git a/storage/src/lib.rs b/storage/src/lib.rs index dc744abc98bb..4f3148b451b0 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -60,3 +60,16 @@ impl std::fmt::Display for CacheReadStrategy { write!(f, "{self:?}") } } + +/// Returns the hash of an empty trie, which is the Keccak256 hash of the RLP encoding of an empty byte array. +/// +/// This function is slow, so callers should cache the result +#[cfg(feature = "ethhash")] +pub fn empty_trie_hash() -> TrieHash { + use sha3::Digest as _; + + sha3::Keccak256::digest(rlp::NULL_RLP) + .as_slice() + .try_into() + .expect("empty trie hash is 32 bytes") +} From 59a2299f302a9ad6d2843103eba660de61262b54 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 14 May 2025 16:51:20 -0400 Subject: [PATCH 0717/1053] fix(ffi): Check revision is available (#890) --- ffi/firewood.go | 2 +- ffi/firewood_test.go | 29 +++++++++-------------------- ffi/revision.go | 22 +++++++++++++++++----- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 884f8c3b202a..a2ead93689e5 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -206,7 +206,7 @@ func (db *Database) Root() ([]byte, error) { // Revision returns a historical revision of the database. func (db *Database) Revision(root []byte) (*Revision, error) { - return NewRevision(db.handle, root) + return newRevision(db.handle, root) } // Close closes the database and releases all held resources. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 610b038d6848..16ab4cc19c6d 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -653,8 +653,8 @@ func TestRevision(t *testing.T) { require.NoError(t, err, "%T.Root()", db) // Create a revision from this root. - revision, err := NewRevision(db.handle, root) - require.NoError(t, err, "NewRevision") + revision, err := db.Revision(root) + require.NoError(t, err, "Revision") // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) @@ -677,7 +677,7 @@ func TestRevision(t *testing.T) { // Create a "new" revision from the first old root. revision, err = db.Revision(root) - require.NoError(t, err, "NewRevision") + require.NoError(t, err, "Revision") // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) @@ -690,28 +690,17 @@ func TestFakeRevision(t *testing.T) { db := newTestDatabase(t) // Create a nil revision. - revision, err := db.Revision(nil) - require.ErrorIs(t, err, errInvalidRoot, "NewRevision(nil)") - assert.Nil(t, revision, "NewRevision(nil)") + _, err := db.Revision(nil) + require.ErrorIs(t, err, errInvalidRootLength, "Revision(nil)") // Create a fake revision with an invalid root. invalidRoot := []byte("not a valid root") - revision, err = db.Revision(invalidRoot) - require.ErrorIs(t, err, errInvalidRoot, "NewRevision(invalid root)") - require.Nil(t, revision, "NewRevision(invalid root)") + _, err = db.Revision(invalidRoot) + require.ErrorIs(t, err, errInvalidRootLength, "Revision(invalid root)") // Create a fake revision with an valid root. validRoot := []byte("counting 32 bytes to make a hash") assert.Len(t, validRoot, 32, "valid root") - revision, err = db.Revision(validRoot) - require.NoError(t, err, "NewRevision(valid root)") - require.NotNil(t, revision, "NewRevision(valid root)") - - // Attempt to get a value from the fake revision. - _, err = revision.Get([]byte("non-existent")) - require.Contains(t, err.Error(), "Revision not found", "Get(non-existent)") - - // Attempt to get from the now invalid revision. - _, err = revision.Get([]byte("non-existent")) - require.ErrorIs(t, err, errRevisionClosed, "Get(non-existent)") + _, err = db.Revision(validRoot) + require.ErrorIs(t, err, errRevisionNotFound, "Revision(valid root)") } diff --git a/ffi/revision.go b/ffi/revision.go index fc1c8b7a2f87..c1fdac4ae44a 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -14,8 +14,8 @@ import ( ) var ( - errRevisionClosed = errors.New("firewood revision already closed") - errInvalidRoot = fmt.Errorf("firewood error: root hash must be %d bytes", RootLength) + errRevisionNotFound = errors.New("firewood error: revision not found") + errInvalidRootLength = fmt.Errorf("firewood error: root hash must be %d bytes", RootLength) ) type Revision struct { @@ -27,14 +27,26 @@ type Revision struct { root []byte } -func NewRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { +func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { if handle == nil { return nil, errors.New("firewood error: nil handle or root") } // Check that the root is the correct length. if root == nil || len(root) != RootLength { - return nil, errInvalidRoot + return nil, errInvalidRootLength + } + + // Attempt to get any value from the root. + // This will verify that the root is valid and accessible. + // If the root is not valid, this will return an error. + values, cleanup := newValueFactory() + defer cleanup() + val := C.fwd_get_from_root(handle, values.from(root), values.from([]byte{})) + _, err := extractBytesThenFree(&val) + if err != nil { + // Any error from this function indicates that the root is inaccessible. + return nil, errRevisionNotFound } // All other verification of the root is done during use. @@ -49,7 +61,7 @@ func (r *Revision) Get(key []byte) ([]byte, error) { return nil, errDbClosed } if r.root == nil { - return nil, errRevisionClosed + return nil, errRevisionNotFound } values, cleanup := newValueFactory() From a5026e9a769c07e9b972f33f721ff1a15cd25369 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 15 May 2025 11:38:37 -0400 Subject: [PATCH 0718/1053] Add GitHub Action workflow to generate and attach static libraries to source code for golang FFI (#868) --- .github/workflows/attach-static-libs.yaml | 171 ++++++++++++++++++++++ .github/workflows/ci.yaml | 6 +- ffi/README.md | 51 ++++++- ffi/firewood.go | 11 +- ffi/src/lib.rs | 2 +- 5 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/attach-static-libs.yaml diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml new file mode 100644 index 000000000000..abf574162eed --- /dev/null +++ b/.github/workflows/attach-static-libs.yaml @@ -0,0 +1,171 @@ +name: attach-static-libs + +on: + workflow_dispatch: + inputs: + create_branch_name: + description: "Name of the new branch to create and attach static libs" + required: true + push: + tags: + - "*" + pull_request: + +env: + CARGO_TERM_COLOR: always + +# Build, upload, and collect static libraries for each target architecture, +# so that golang projects can import the FFI package without needing to +# recompile Firewood locally. +# Supported architectures are: +# - x86_64-unknown-linux-gnu +# - aarch64-unknown-linux-gnu +# - x86_64-apple-darwin +# - aarch64-apple-darwin +jobs: + # Build the static libraries for each target architecture and upload + # them as artifacts to collect and attach in the next job. + build-firewood-ffi-libs: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - os: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + - os: macos-latest + target: aarch64-apple-darwin + - os: macos-13 + target: x86_64-apple-darwin + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + + - name: Build for ${{ matrix.target }} + # TODO: add ethhash feature flag after updating FFI tests + run: cargo build --profile maxperf --features logger --target ${{ matrix.target }} -p firewood-ffi + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.target }} + path: target/${{ matrix.target }}/maxperf/libfirewood_ffi.a + if-no-files-found: error + + # Collect all the static libraries built on the previous matrix of jobs + # and add them into ffi/libs directory. + # We commit and push this as a new branch with "--force" to overwrite + # the previous static libs that will not be on our branch. + push-firewood-ffi-libs: + needs: build-firewood-ffi-libs + runs-on: ubuntu-latest + outputs: + target_branch: ${{ steps.determine_branch.outputs.target_branch }} + steps: + - name: Determine branch name + id: determine_branch + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ github.event.inputs.create_branch_name }}" ]]; then + export target_branch="${{ github.event.inputs.create_branch_name }}" + echo "Using workflow input as target branch: $target_branch" + echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" + elif [[ "${{ github.event_name }}" == "push" ]]; then + export target_branch="${GITHUB_REF#refs/heads/}" + echo "Using tag/branch name as target_branch: $target_branch" + echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + export target_branch="${{ github.event.pull_request.head.ref }}" + echo "Using PR head name as target branch: $target_branch" + echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" + else + echo "No valid input or tag found." + exit 1 + fi + + - uses: actions/checkout@v4 + with: + path: firewood + + - uses: actions/checkout@v4 + with: + repository: ava-labs/firewood-go + token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} + path: firewood-go + + - name: Copy FFI Source Code + run: cp -r firewood/ffi firewood-go + + - name: Download binaries into libs directory + uses: actions/download-artifact@v4 + with: + path: firewood-go/ffi/libs + + - name: List downloaded target directory + run: find firewood-go -type f | sort + + - name: Push static libs to branch + working-directory: firewood-go + # GITHUB_TOKEN is configured in the last actions/checkout step + # to have read/write permissions to the firewood-go repo. + run: | + git config --global user.name "FirewoodCI" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b ${{ steps.determine_branch.outputs.target_branch }} + git add . + git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" + git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force + if [[ "${{ github.ref_type }}" == "tag" ]]; then + git tag -a "${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" + git push origin "${GITHUB_REF#refs/tags/}" + fi + + # Check out the branch created in the previous job on a matrix of + # our target architectures and test the FFI package on a fresh + # machine without re-compiling Firewood locally. + # This tests that the Firewood FFI package passes tests on the target + # architecture when it is forced to depend on the attached static libs. + test-firewood-ffi-libs: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, ubuntu-22.04-arm, macos-latest, macos-13] + needs: push-firewood-ffi-libs + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + repository: ava-labs/firewood-go + token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} + ref: ${{ needs.push-firewood-ffi-libs.outputs.target_branch }} + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "ffi/go.mod" + cache-dependency-path: "ffi/go.sum" + - name: Test Go FFI bindings + working-directory: ffi + # cgocheck2 is expensive but provides complete pointer checks + run: GOEXPERIMENT=cgocheck2 go test ./... + + remove-if-pr-only: + runs-on: ubuntu-latest + needs: [push-firewood-ffi-libs, test-firewood-ffi-libs] + if: needs.push-firewood-ffi-libs.result == 'success' && github.event_name == 'pull_request' + permissions: + # Give the GITHUB_TOKEN write permission to delete the + # branch created by the previous job if it is a pull request. + contents: write + steps: + - uses: actions/checkout@v4 + with: + repository: ava-labs/firewood-go + token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} + ref: ${{ needs.push-firewood-ffi-libs.outputs.target_branch }} + - name: Delete branch + run: | + git push origin --delete ${{ needs.push-firewood-ffi-libs.outputs.target_branch }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 882bb788cbcf..461b4df1b5a8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,6 @@ name: ci on: pull_request: - branches: ['*'] push: branches: [main] @@ -106,7 +105,10 @@ jobs: ffi: needs: build - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/ffi/README.md b/ffi/README.md index 58ded17729b0..1f15e7c019d7 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -1,13 +1,52 @@ -# A firewood golang interface +# Firewood Golang FFI -This allows calling into firewood from golang +The FFI package provides a golang FFI layer for Firewood. -## Building +## Building Firewood Golang FFI -First, build the release version (`cargo build --release`). This creates the ffi -interface file "firewood.h" as a side effect. +The Golang FFI layer uses a CGO directive to locate a C-API compatible binary built from Firewood. Firewood supports both seamless local development and a single-step compilation process for Go projects that depend or transitively depend on Firewood. -Then, you can run the tests in go, using `go test .` +To do this, [firewood.go](./firewood.go) includes CGO directives to include multiple search paths for the Firewood binary in the local `target/` build directory and `ffi/libs`. For the latter, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) GitHub Action pushes an FFI package with static libraries attached for the following supported architectures: + +- x86_64-unknown-linux-gnu +- aarch64-unknown-linux-gnu +- aarch64-apple-darwin +- x86_64-apple-darwin + +to a separate repo [firewood-go](https://github.com/ava-labs/firewood-go) (to avoid including binaries in the Firewood repo). + +### Local Development + +[firewood.go](./firewood.go) includes CGO directives to include builds in the `target/` directory. + +Firewood prioritizes builds in the following order: + +1. maxperf +2. release +3. debug + +To use and test the Firewood FFI locally, you can run: + +```bash +cargo build --profile maxperf +cd ffi +go test +``` + +To use a local build of Firewood for a project that depends on Firewood, you must redirect the `go.mod` to use the local version of Firewood FFI, for example: + +```bash +go mod edit -replace github.com/ava-labs/firewood-go/ffi=/path/to/firewood/ffi +go mod tidy +``` + +### Production Development Flow + +Firewood pushes the FFI source code and attached static libraries to [firewood-go](https://github.com/ava-labs/firewood-go) via [attach-static-libs](../.github/workflows/attach-static-libs.yaml). + +This enables consumers to utilize it directly without forcing them to compile Firewood locally. Go programs running on supported architectures can utilize `firewood-go/ffi` just like any other dependency. + +To trigger this build, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) supports triggers for both manual GitHub Actions and tags, so you can create a mirror branch/tag on [firewood-go](https://github.com/ava-labs/firewood-go) by either trigger a manual GitHub Action and selecting your branch or pushing a tag to Firewood. ## Development Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. diff --git a/ffi/firewood.go b/ffi/firewood.go index a2ead93689e5..9f85e645492d 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -4,7 +4,16 @@ package firewood // // Note that -lm is required on Linux but not on Mac. -// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm +// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu -lm +// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-unknown-linux-gnu -lm +// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-apple-darwin +// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-apple-darwin +// // XXX: last search path takes precedence, which means we prioritize +// // local builds over pre-built and maxperf over release build +// #cgo LDFLAGS: -L${SRCDIR}/../target/debug +// #cgo LDFLAGS: -L${SRCDIR}/../target/release +// #cgo LDFLAGS: -L${SRCDIR}/../target/maxperf +// #cgo LDFLAGS: -L/usr/local/lib -lfirewood_ffi // #include // #include "firewood.h" import "C" diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 67334fe77f20..7c8513aa9f35 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -583,7 +583,7 @@ impl Display for Value { match (self.len, self.data.is_null()) { (0, true) => write!(f, "[not found]"), (0, false) => write!(f, "[error] {}", unsafe { - CStr::from_ptr(self.data.cast::()).to_string_lossy() + CStr::from_ptr(self.data.cast::()).to_string_lossy() }), (len, true) => write!(f, "[id] {len}"), (_, false) => write!(f, "[data] {:?}", self.as_slice()), From 1d75a7f8a40f0a28b6ac11d89f2711e8baf73a8e Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 16 May 2025 14:27:39 -0400 Subject: [PATCH 0719/1053] Fix tag handling for attach static libs (#895) --- .github/workflows/attach-static-libs.yaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index abf574162eed..e56f1209192e 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -74,9 +74,9 @@ jobs: export target_branch="${{ github.event.inputs.create_branch_name }}" echo "Using workflow input as target branch: $target_branch" echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" - elif [[ "${{ github.event_name }}" == "push" ]]; then - export target_branch="${GITHUB_REF#refs/heads/}" - echo "Using tag/branch name as target_branch: $target_branch" + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_type }}" == "tag" ]]; then + export target_branch="${GITHUB_REF#refs/tags/}" + echo "Using tag name as target_branch: $target_branch" echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == "pull_request" ]]; then export target_branch="${{ github.event.pull_request.head.ref }}" @@ -118,10 +118,12 @@ jobs: git checkout -b ${{ steps.determine_branch.outputs.target_branch }} git add . git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" - git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force + if [[ "${{ github.ref_type }}" == "tag" ]]; then git tag -a "${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" - git push origin "${GITHUB_REF#refs/tags/}" + git push origin "refs/tags/${GITHUB_REF#refs/tags/}" + else + git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force fi # Check out the branch created in the previous job on a matrix of From 30819e75b6db809abea23b84b09ebde2e44bd343 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Fri, 16 May 2025 14:36:19 -0400 Subject: [PATCH 0720/1053] fix(ffi): prevent undefined behavior on empty slices (#894) --- ffi/firewood_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 16 ++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 16ab4cc19c6d..150287ee5836 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -704,3 +704,43 @@ func TestFakeRevision(t *testing.T) { _, err = db.Revision(validRoot) require.ErrorIs(t, err, errRevisionNotFound, "Revision(valid root)") } + +// Tests that edge case `Get` calls are handled correctly. +func TestGetNilCases(t *testing.T) { + db := newTestDatabase(t) + + // Commit 10 key-value pairs. + keys := make([][]byte, 20) + vals := make([][]byte, 20) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) + } + root, err := db.Update(keys[:10], vals[:10]) + require.NoError(t, err, "Update") + + // Create the other views + proposal, err := db.Propose(keys[10:], vals[10:]) + require.NoError(t, err, "Propose") + revision, err := db.Revision(root) + require.NoError(t, err, "Revision") + + // Create edge case keys. + specialKeys := [][]byte{ + nil, + {}, // empty slice + } + for _, k := range specialKeys { + got, err := db.Get(k) + require.NoError(t, err, "db.Get(%q)", k) + assert.Empty(t, got, "db.Get(%q)", k) + + got, err = revision.Get(k) + require.NoError(t, err, "Revision.Get(%q)", k) + assert.Empty(t, got, "Revision.Get(%q)", k) + + got, err = proposal.Get(k) + require.NoError(t, err, "Proposal.Get(%q)", k) + assert.Empty(t, got, "Proposal.Get(%q)", k) + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 7c8513aa9f35..6d389cdd77a9 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -296,8 +296,10 @@ fn batch( values: *const KeyValue, ) -> Result { let start = coarsetime::Instant::now(); - // Check db is valid. let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + if values.is_null() { + return Err(String::from("key-value list is null")); + } // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; @@ -366,6 +368,9 @@ fn propose_on_db( values: *const KeyValue, ) -> Result { let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + if values.is_null() { + return Err(String::from("key-value list is null")); + } // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; @@ -424,6 +429,9 @@ fn propose_on_proposal( values: *const KeyValue, ) -> Result { let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; + if values.is_null() { + return Err(String::from("key-value list is null")); + } // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; @@ -594,7 +602,11 @@ impl Display for Value { impl Value { #[must_use] pub const fn as_slice(&self) -> &[u8] { - unsafe { std::slice::from_raw_parts(self.data, self.len) } + if self.data.is_null() { + &[] + } else { + unsafe { std::slice::from_raw_parts(self.data, self.len) } + } } } From abdc230f5af815c9a5dc2b5fa68d4e95fe55cc84 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Fri, 16 May 2025 14:58:30 -0400 Subject: [PATCH 0721/1053] ci(ffi): add linter (#893) --- .github/workflows/ci.yaml | 5 ++ ffi/.golangci.yaml | 170 ++++++++++++++++++++++++++++++++++++++ ffi/firewood.go | 19 ++--- ffi/firewood_test.go | 129 +++++++++++++++-------------- ffi/kvbackend.go | 10 +-- ffi/memory.go | 7 +- ffi/proposal.go | 17 ++-- ffi/revision.go | 3 +- 8 files changed, 269 insertions(+), 91 deletions(-) create mode 100644 ffi/.golangci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 461b4df1b5a8..602020447257 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -127,6 +127,11 @@ jobs: with: go-version-file: "ffi/go.mod" cache-dependency-path: "ffi/go.sum" + - name: Run golanci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: ffi - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml new file mode 100644 index 000000000000..34e7ff1e6eb5 --- /dev/null +++ b/ffi/.golangci.yaml @@ -0,0 +1,170 @@ +# https://golangci-lint.run/usage/configuration/ +run: + timeout: 10m + + # If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + # + # Allowed values: readonly|vendor|mod + # By default, it isn't set. + modules-download-mode: readonly + +issues: + # Make issues output unique by line. + # Default: true + uniq-by-line: false + + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 0 + + # Enables skipping of directories: + # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # Default: true + exclude-dirs-use-default: false + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - copyloopvar + - depguard + - dupword + - dupl + - errcheck + - errname + - errorlint + - forbidigo + - gci + - goconst + - gocritic + # - err113 - encourages wrapping static errors + - gofmt + - gofumpt + # - mnd - unnecessary magic numbers + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + # - lll line length linter + - misspell + - nakedret + - nilerr + - noctx + - nolintlint + - perfsprint + - prealloc + - predeclared + - revive + - spancheck + - staticcheck + - stylecheck + - tagalign + - testifylint + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace + +linters-settings: + depguard: + rules: + packages: + deny: + - pkg: "github.com/golang/mock/gomock" + desc: go.uber.org/mock/gomock should be used instead. + - pkg: "github.com/stretchr/testify/assert" + desc: github.com/stretchr/testify/require should be used instead. + - pkg: "io/ioutil" + desc: io/ioutil is deprecated. Use package io or os instead. + errorlint: + # Check for plain type assertions and type switches. + asserts: false + # Check for plain error comparisons. + comparison: false + forbidigo: + # Forbid the following identifiers (list of regexp). + forbid: + - 'require\.Error$(# ErrorIs should be used instead)?' + - 'require\.ErrorContains$(# ErrorIs should be used instead)?' + - 'require\.EqualValues$(# Equal should be used instead)?' + - 'require\.NotEqualValues$(# NotEqual should be used instead)?' + - '^(t|b|tb|f)\.(Fatal|Fatalf|Error|Errorf)$(# the require library should be used instead)?' + revive: + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr + - name: bool-literal-in-expr + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return + - name: early-return + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines + - name: empty-lines + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format + - name: string-format + disabled: false + arguments: + - ["b.Logf[0]", "/.*%.*/", "no format directive, use b.Log instead"] + - ["fmt.Errorf[0]", "/.*%.*/", "no format directive, use errors.New instead"] + - ["fmt.Fprintf[1]", "/.*%.*/", "no format directive, use fmt.Fprint instead"] + - ["fmt.Printf[0]", "/.*%.*/", "no format directive, use fmt.Print instead"] + - ["fmt.Sprintf[0]", "/.*%.*/", "no format directive, use fmt.Sprint instead"] + - ["log.Fatalf[0]", "/.*%.*/", "no format directive, use log.Fatal instead"] + - ["log.Printf[0]", "/.*%.*/", "no format directive, use log.Print instead"] + - ["t.Logf[0]", "/.*%.*/", "no format directive, use t.Log instead"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + - name: struct-tag + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming + - name: unexported-naming + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error + - name: unhandled-error + # prefer the errcheck linter since it can be disabled directly with nolint directive + # but revive's disable directive (e.g. //revive:disable:unhandled-error) is not + # supported when run under golangci_lint + disabled: true + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver + - name: unused-receiver + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + disabled: false + tagalign: + align: true + sort: true + strict: true + order: + - serialize + testifylint: + # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). + # Default: false + enable-all: true + # Disable checkers by name + # (in addition to default + # suite-thelper + # ). + disable: + - go-require + - float-compare diff --git a/ffi/firewood.go b/ffi/firewood.go index 9f85e645492d..6b58ce2b04b6 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -33,7 +33,7 @@ const ( keyNotFound = "key not found" ) -var errDbClosed = errors.New("firewood database already closed") +var errDBClosed = errors.New("firewood database already closed") // A Database is a handle to a Firewood database. // It is not safe to call these methods with a nil handle. @@ -138,14 +138,14 @@ func (db *Database) Batch(ops []KeyValue) ([]byte, error) { hash := C.fwd_batch( db.handle, C.size_t(len(ffiOps)), - (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), // implicitly pinned + unsafe.SliceData(ffiOps), // implicitly pinned ) return extractBytesThenFree(&hash) } func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if db.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } values, cleanup := newValueFactory() @@ -158,13 +158,12 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { value: values.from(vals[i]), } } - id_or_err := C.fwd_propose_on_db( + idOrErr := C.fwd_propose_on_db( db.handle, C.size_t(len(ffiOps)), - (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), // implicitly pinned + unsafe.SliceData(ffiOps), // implicitly pinned ) - id, err := extractIdThenFree(&id_or_err) - + id, err := extractUintThenFree(&idOrErr) if err != nil { return nil, err } @@ -180,7 +179,7 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { // If the key is not found, the return value will be (nil, nil). func (db *Database) Get(key []byte) ([]byte, error) { if db.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } values, cleanup := newValueFactory() @@ -200,7 +199,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { // Empty trie must return common.Hash{}. func (db *Database) Root() ([]byte, error) { if db.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } hash := C.fwd_root_hash(db.handle) bytes, err := extractBytesThenFree(&hash) @@ -222,7 +221,7 @@ func (db *Database) Revision(root []byte) (*Revision, error) { // Returns an error if already closed. func (db *Database) Close() error { if db.handle == nil { - return errDbClosed + return errDBClosed } C.fwd_close_db(db.handle) db.handle = nil diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 150287ee5836..8b783e4db3f8 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -9,7 +9,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,7 +21,6 @@ func TestMain(m *testing.M) { switch strings.TrimSpace(kv) { case "cgocheck=1": hasCgoCheck = true - break case "cgocheck=0": fmt.Fprint(os.Stderr, "GODEBUG=cgocheck=0; MUST be 1 for Firewood cgo tests") os.Exit(1) @@ -66,13 +64,15 @@ func TestInsert(t *testing.T) { key = "abc" val = "def" ) - db.Batch([]KeyValue{ + + _, err := db.Batch([]KeyValue{ {[]byte(key), []byte(val)}, }) + require.NoError(t, err, "Batch(%q)", key) got, err := db.Get([]byte(key)) require.NoErrorf(t, err, "%T.Get(%q)", db, key) - assert.Equal(t, val, string(got), "Recover lone batch-inserted value") + require.Equal(t, val, string(got), "Recover lone batch-inserted value") } // Attempt to make a call to a nil or invalid handle. @@ -82,21 +82,21 @@ func TestGetBadHandle(t *testing.T) { // This ignores error, but still shouldn't panic. _, err := db.Get([]byte("non-existent")) - assert.ErrorIs(t, err, errDbClosed) + require.ErrorIs(t, err, errDBClosed) // We ignore the error, but it shouldn't panic. _, err = db.Root() - assert.ErrorIs(t, err, errDbClosed) + require.ErrorIs(t, err, errDBClosed) root, err := db.Update( [][]byte{[]byte("key")}, [][]byte{[]byte("value")}, ) - assert.Empty(t, root) - assert.ErrorIs(t, err, errDbClosed) + require.Empty(t, root) + require.ErrorIs(t, err, errDBClosed) err = db.Close() - require.ErrorIs(t, err, errDbClosed) + require.ErrorIs(t, err, errDBClosed) } func keyForTest(i int) []byte { @@ -163,15 +163,15 @@ func TestInsert100(t *testing.T) { require.NoErrorf(t, err, "%T.Get(%q)", db, keys[i]) // Cast as strings to improve debug messages. want := string(vals[i]) - assert.Equal(t, want, string(got), "Recover nth batch-inserted value") + require.Equal(t, want, string(got), "Recover nth batch-inserted value") } hash, err := db.Root() - assert.NoError(t, err, "%T.Root()", db) - assert.Lenf(t, hash, 32, "%T.Root()", db) + require.NoError(t, err, "%T.Root()", db) + require.Lenf(t, hash, 32, "%T.Root()", db) // we know the hash starts with 0xf8 - assert.Equalf(t, byte(0xf8), hash[0], "First byte of %T.Root()", db) - assert.Equalf(t, rootFromInsert, hash, "%T.Root() matches value returned by insertion", db) + require.Equalf(t, byte(0xf8), hash[0], "First byte of %T.Root()", db) + require.Equalf(t, rootFromInsert, hash, "%T.Root() matches value returned by insertion", db) }) } } @@ -183,23 +183,25 @@ func TestRangeDelete(t *testing.T) { for i := range ops { ops[i] = kvForTest(i) } - db.Batch(ops) + _, err := db.Batch(ops) + require.NoError(t, err, "Batch") const deletePrefix = 1 - db.Batch([]KeyValue{{ + _, err = db.Batch([]KeyValue{{ Key: keyForTest(deletePrefix), // delete all keys that start with "key1" Value: nil, }}) + require.NoError(t, err, "Batch") for _, op := range ops { got, err := db.Get(op.Key) require.NoError(t, err) if deleted := bytes.HasPrefix(op.Key, keyForTest(deletePrefix)); deleted { - assert.Empty(t, err, got) + require.NoError(t, err, got) } else { - assert.Equal(t, op.Value, got) + require.Equal(t, op.Value, got) } } } @@ -209,11 +211,11 @@ func TestInvariants(t *testing.T) { db := newTestDatabase(t) hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - assert.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie") + require.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie", db) got, err := db.Get([]byte("non-existent")) require.NoError(t, err) - assert.Emptyf(t, got, "%T.Get([non-existent key])", db) + require.Emptyf(t, got, "%T.Get([non-existent key])", db) } func TestParallelProposals(t *testing.T) { @@ -240,7 +242,7 @@ func TestParallelProposals(t *testing.T) { for j := 0; j < numKeys; j++ { got, err := p.Get(keyForTest(i*numKeys + j)) require.NoError(t, err, "Get(%d)", i*numKeys+j) - assert.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + require.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) } } @@ -251,14 +253,14 @@ func TestParallelProposals(t *testing.T) { for j := 0; j < numKeys; j++ { got, err := db.Get(keyForTest(j)) require.NoError(t, err, "Get(%d)", j) - assert.Equal(t, valForTest(j), got, "Get(%d)", j) + require.Equal(t, valForTest(j), got, "Get(%d)", j) } // Check that the other proposals' keys are not present. for i := 1; i < numProposals; i++ { for j := 0; j < numKeys; j++ { got, err := db.Get(keyForTest(i*numKeys + j)) require.NoError(t, err, "Get(%d)", i*numKeys+j) - assert.Empty(t, got, "Get(%d)", i*numKeys+j) + require.Empty(t, got, "Get(%d)", i*numKeys+j) } } @@ -267,7 +269,7 @@ func TestParallelProposals(t *testing.T) { for j := 0; j < numKeys; j++ { got, err := proposals[i].Get(keyForTest(i*numKeys + j)) require.NoError(t, err, "Get(%d)", i*numKeys+j) - assert.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + require.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) } } @@ -288,7 +290,7 @@ func TestParallelProposals(t *testing.T) { for j := 0; j < numKeys; j++ { got, err := proposals[i].Get(keyForTest(i*numKeys + j)) require.ErrorIs(t, err, errDroppedProposal, "Get(%d)", i*numKeys+j) - assert.Empty(t, got, "Get(%d)", i*numKeys+j) + require.Empty(t, got, "Get(%d)", i*numKeys+j) } } } @@ -304,7 +306,8 @@ func TestDeleteAll(t *testing.T) { vals[i] = valForTest(i) } // Insert 10 key-value pairs. - db.Update(keys, vals) + _, err := db.Update(keys, vals) + require.NoError(t, err, "Update") // Create a proposal that deletes all keys. proposal, err := db.Propose([][]byte{[]byte("key")}, [][]byte{nil}) @@ -314,7 +317,7 @@ func TestDeleteAll(t *testing.T) { for i := range keys { got, err := proposal.Get(keys[i]) require.NoError(t, err, "Get(%d)", i) - assert.Empty(t, got, "Get(%d)", i) + require.Empty(t, got, "Get(%d)", i) } // Commit the proposal. @@ -324,7 +327,7 @@ func TestDeleteAll(t *testing.T) { // Check that the database is empty. hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - assert.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie") + require.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie", db) } // Tests that a proposal with an invalid ID cannot be committed. @@ -415,13 +418,13 @@ func TestProposeFromProposal(t *testing.T) { for i := range keys2 { got, err := proposal1.Get(keys2[i]) require.NoError(t, err, "Get(%d)", i) - assert.Empty(t, got, "Get(%d)", i) + require.Empty(t, got, "Get(%d)", i) } // Assert that the second proposal has keys from the first. for i := range keys1 { got, err := proposal2.Get(keys1[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals1[i], got, "Get(%d)", i) + require.Equal(t, vals1[i], got, "Get(%d)", i) } // Commit the first proposal. @@ -432,12 +435,12 @@ func TestProposeFromProposal(t *testing.T) { for i := range keys1 { got, err := db.Get(keys1[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals1[i], got, "Get(%d)", i) + require.Equal(t, vals1[i], got, "Get(%d)", i) } for i := range keys2 { got, err := proposal2.Get(keys2[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals2[i], got, "Get(%d)", i) + require.Equal(t, vals2[i], got, "Get(%d)", i) } // Commit the second proposal. @@ -448,12 +451,12 @@ func TestProposeFromProposal(t *testing.T) { for i := range keys1 { got, err := db.Get(keys1[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals1[i], got, "Get(%d)", i) + require.Equal(t, vals1[i], got, "Get(%d)", i) } for i := range keys2 { got, err := db.Get(keys2[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals2[i], got, "Get(%d)", i) + require.Equal(t, vals2[i], got, "Get(%d)", i) } } @@ -490,7 +493,7 @@ func TestDeepPropose(t *testing.T) { for i := range keys { got, err := proposals[numProposals-1].Get(keys[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals[i], got, "Get(%d)", i) + require.Equal(t, vals[i], got, "Get(%d)", i) } // Commit each proposal sequentially, and ensure that the values are @@ -502,7 +505,7 @@ func TestDeepPropose(t *testing.T) { for j := i * numKeys; j < (i+1)*numKeys; j++ { got, err := db.Get(keys[j]) require.NoError(t, err, "Get(%d)", j) - assert.Equal(t, vals[j], got, "Get(%d)", j) + require.Equal(t, vals[j], got, "Get(%d)", j) } } } @@ -552,7 +555,7 @@ func TestDropProposalAndCommit(t *testing.T) { for i := range keys { got, err := proposals[numProposals-1].Get(keys[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, vals[i], got, "Get(%d)", i) + require.Equal(t, vals[i], got, "Get(%d)", i) } } @@ -577,53 +580,53 @@ func TestProposeSameRoot(t *testing.T) { // Create the first proposal chain. proposal1, err := db.Propose(keys[0:5], vals[0:5]) require.NoError(t, err, "Propose") - proposal3_top, err := proposal1.Propose(keys[5:10], vals[5:10]) + proposal3Top, err := proposal1.Propose(keys[5:10], vals[5:10]) require.NoError(t, err, "Propose") // Create the second proposal chain. proposal2, err := db.Propose(keys[5:10], vals[5:10]) require.NoError(t, err, "Propose") - proposal3_bottom, err := proposal2.Propose(keys[0:5], vals[0:5]) + proposal3Bottom, err := proposal2.Propose(keys[0:5], vals[0:5]) require.NoError(t, err, "Propose") // Because the proposals are identical, they should have the same root. // Create a unique proposal from each of the two chains. - top_keys := make([][]byte, 5) - top_vals := make([][]byte, 5) - for i := range top_keys { - top_keys[i] = keyForTest(i + 10) - top_vals[i] = valForTest(i + 10) - } - bot_keys := make([][]byte, 5) - bot_vals := make([][]byte, 5) - for i := range bot_keys { - bot_keys[i] = keyForTest(i + 20) - bot_vals[i] = valForTest(i + 20) - } - proposal4, err := proposal3_top.Propose(top_keys, top_vals) + topKeys := make([][]byte, 5) + topVals := make([][]byte, 5) + for i := range topKeys { + topKeys[i] = keyForTest(i + 10) + topVals[i] = valForTest(i + 10) + } + bottomKeys := make([][]byte, 5) + bottomVals := make([][]byte, 5) + for i := range bottomKeys { + bottomKeys[i] = keyForTest(i + 20) + bottomVals[i] = valForTest(i + 20) + } + proposal4, err := proposal3Top.Propose(topKeys, topVals) require.NoError(t, err, "Propose") - proposal5, err := proposal3_bottom.Propose(bot_keys, bot_vals) + proposal5, err := proposal3Bottom.Propose(bottomKeys, bottomVals) require.NoError(t, err, "Propose") // Now we will commit the top chain, and check that the bottom chain is still valid. err = proposal1.Commit() require.NoError(t, err, "Commit") - err = proposal3_top.Commit() + err = proposal3Top.Commit() require.NoError(t, err, "Commit") // Check that both final proposals are valid. for i := range keys { got, err := proposal4.Get(keys[i]) require.NoError(t, err, "P4 Get(%d)", i) - assert.Equal(t, vals[i], got, "P4 Get(%d)", i) + require.Equal(t, vals[i], got, "P4 Get(%d)", i) got, err = proposal5.Get(keys[i]) require.NoError(t, err, "P5 Get(%d)", i) - assert.Equal(t, vals[i], got, "P5 Get(%d)", i) + require.Equal(t, vals[i], got, "P5 Get(%d)", i) } // Attempt to commit P5. Since this isn't in the canonical chain, it should // fail. err = proposal5.Commit() - require.Error(t, err, "Commit P5") // this error is internal to firewood + require.Contains(t, err.Error(), "commit the parents of this proposal first", "Commit P5") // this error is internal to firewood // We should be able to commit P4, since it is in the canonical chain. err = proposal4.Commit() @@ -659,7 +662,7 @@ func TestRevision(t *testing.T) { for i := range keys { got, err := revision.Get(keys[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, valForTest(i), got, "Get(%d)", i) + require.Equal(t, valForTest(i), got, "Get(%d)", i) } // Create a second proposal with 10 key-value pairs. @@ -682,7 +685,7 @@ func TestRevision(t *testing.T) { for i := range keys { got, err := revision.Get(keys[i]) require.NoError(t, err, "Get(%d)", i) - assert.Equal(t, valForTest(i), got, "Get(%d)", i) + require.Equal(t, valForTest(i), got, "Get(%d)", i) } } @@ -700,7 +703,7 @@ func TestFakeRevision(t *testing.T) { // Create a fake revision with an valid root. validRoot := []byte("counting 32 bytes to make a hash") - assert.Len(t, validRoot, 32, "valid root") + require.Len(t, validRoot, 32, "valid root") _, err = db.Revision(validRoot) require.ErrorIs(t, err, errRevisionNotFound, "Revision(valid root)") } @@ -733,14 +736,14 @@ func TestGetNilCases(t *testing.T) { for _, k := range specialKeys { got, err := db.Get(k) require.NoError(t, err, "db.Get(%q)", k) - assert.Empty(t, got, "db.Get(%q)", k) + require.Empty(t, got, "db.Get(%q)", k) got, err = revision.Get(k) require.NoError(t, err, "Revision.Get(%q)", k) - assert.Empty(t, got, "Revision.Get(%q)", k) + require.Empty(t, got, "Revision.Get(%q)", k) got, err = proposal.Get(k) require.NoError(t, err, "Proposal.Get(%q)", k) - assert.Empty(t, got, "Proposal.Get(%q)", k) + require.Empty(t, got, "Proposal.Get(%q)", k) } } diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index 05fff47407d8..6c49a81d4e43 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -41,18 +41,18 @@ type kVBackend interface { } // Prefetch is a no-op since we don't need to prefetch for Firewood. -func (db *Database) Prefetch(key []byte) ([]byte, error) { +func (db *Database) Prefetch(_ []byte) ([]byte, error) { if db.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } return nil, nil } // Commit is a no-op, since [Database.Update] already persists changes. -func (db *Database) Commit(root []byte) error { +func (db *Database) Commit(_ []byte) error { if db.handle == nil { - return errDbClosed + return errDBClosed } return nil @@ -62,7 +62,7 @@ func (db *Database) Commit(root []byte) error { // database. func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { if db.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } ops := make([]KeyValue, len(keys)) diff --git a/ffi/memory.go b/ffi/memory.go index 5bb22c5f0e11..a803a382308f 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -8,6 +8,7 @@ package firewood // #include // #include "firewood.h" import "C" + import ( "errors" "fmt" @@ -57,15 +58,14 @@ func extractErrorThenFree(v *C.struct_Value) error { // We should still attempt to free the value. C.fwd_free_value(v) return errBadValue - } -// extractIdThenFree converts the cgo `Value` payload to either: +// extractUintThenFree converts the cgo `Value` payload to either: // 1. a nonzero uint32 and nil error, indicating a valid int // 2. a zero uint32 and a non-nil error, indicating an error occurred. // This should only be called when the `Value` is expected to only contain an error or an ID. // Otherwise, an error is returned. -func extractIdThenFree(v *C.struct_Value) (uint32, error) { +func extractUintThenFree(v *C.struct_Value) (uint32, error) { // Pin the returned value to prevent it from being garbage collected. defer runtime.KeepAlive(v) @@ -129,7 +129,6 @@ func extractBytesThenFree(v *C.struct_Value) ([]byte, error) { // We should still attempt to free the value. C.fwd_free_value(v) return nil, errBadValue - } // newValueFactory returns a factory for converting byte slices into cgo `Value` diff --git a/ffi/proposal.go b/ffi/proposal.go index ad058c548f07..8b086b3cbb89 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -8,6 +8,7 @@ package firewood // #include // #include "firewood.h" import "C" + import ( "errors" "unsafe" @@ -30,7 +31,7 @@ type Proposal struct { // If the key does not exist, it returns (nil, nil). func (p *Proposal) Get(key []byte) ([]byte, error) { if p.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } if p.id == 0 { @@ -48,7 +49,7 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { // The proposal is not committed until Commit is called. func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if p.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } if p.id == 0 { @@ -74,9 +75,9 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { // Propose the keys and values. val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), C.size_t(len(ffiOps)), - (*C.struct_KeyValue)(unsafe.SliceData(ffiOps)), + unsafe.SliceData(ffiOps), ) - id, err := extractIdThenFree(&val) + id, err := extractUintThenFree(&val) if err != nil { return nil, err } @@ -91,7 +92,7 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { // If an error occurs, the proposal is dropped and no longer valid. func (p *Proposal) Commit() error { if p.handle == nil { - return errDbClosed + return errDBClosed } if p.id == 0 { @@ -99,8 +100,8 @@ func (p *Proposal) Commit() error { } // Commit the proposal and return the hash. - err_val := C.fwd_commit(p.handle, C.uint32_t(p.id)) - err := extractErrorThenFree(&err_val) + errVal := C.fwd_commit(p.handle, C.uint32_t(p.id)) + err := extractErrorThenFree(&errVal) if err != nil { // this is unrecoverable due to Rust's ownership model // The underlying proposal is no longer valid. @@ -114,7 +115,7 @@ func (p *Proposal) Commit() error { // An error is returned if the proposal was already dropped. func (p *Proposal) Drop() error { if p.handle == nil { - return errDbClosed + return errDBClosed } if p.id == 0 { diff --git a/ffi/revision.go b/ffi/revision.go index c1fdac4ae44a..20603b6a278b 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -8,6 +8,7 @@ package firewood // #include // #include "firewood.h" import "C" + import ( "errors" "fmt" @@ -58,7 +59,7 @@ func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { func (r *Revision) Get(key []byte) ([]byte, error) { if r.handle == nil { - return nil, errDbClosed + return nil, errDBClosed } if r.root == nil { return nil, errRevisionNotFound From 289d442b47b81dc28116b70a74a5ab792948e292 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 20 May 2025 06:40:27 -0400 Subject: [PATCH 0722/1053] Update FFI tests to test both default firewood + ethhash (#891) --- .github/workflows/attach-static-libs.yaml | 5 +- .github/workflows/ci.yaml | 6 +- ffi/Cargo.toml | 1 + ffi/README.md | 12 +++ ffi/firewood_test.go | 126 +++++++++++++++++++--- ffi/tests/eth_compatibility_test.go | 2 +- 6 files changed, 134 insertions(+), 18 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index e56f1209192e..209e845660ed 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -47,8 +47,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build for ${{ matrix.target }} - # TODO: add ethhash feature flag after updating FFI tests - run: cargo build --profile maxperf --features logger --target ${{ matrix.target }} -p firewood-ffi + run: cargo build --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi - name: Upload binary uses: actions/upload-artifact@v4 @@ -152,7 +151,7 @@ jobs: - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks - run: GOEXPERIMENT=cgocheck2 go test ./... + run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test ./... remove-if-pr-only: runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 602020447257..4f9c080942f5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -135,7 +135,7 @@ jobs: - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks - run: GOEXPERIMENT=cgocheck2 go test ./... + run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... ethhash: runs-on: ubuntu-latest @@ -153,6 +153,10 @@ jobs: with: go-version-file: "ffi/tests/go.mod" cache-dependency-path: "ffi/tests/go.sum" + - name: Test Go FFI bindings + working-directory: ffi + # cgocheck2 is expensive but provides complete pointer checks + run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test ./... - name: Test Ethereum hash compatability working-directory: ffi/tests run: go test ./... diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index ca0d9b4ab1fe..2b8da24e8eb9 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -20,6 +20,7 @@ tikv-jemallocator = "0.6.0" [features] logger = ["dep:env_logger", "firewood/logger"] +ethhash = ["firewood/ethhash"] [build-dependencies] cbindgen = "0.28.0" diff --git a/ffi/README.md b/ffi/README.md index 1f15e7c019d7..213b3460cfa3 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -48,6 +48,18 @@ This enables consumers to utilize it directly without forcing them to compile Fi To trigger this build, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) supports triggers for both manual GitHub Actions and tags, so you can create a mirror branch/tag on [firewood-go](https://github.com/ava-labs/firewood-go) by either trigger a manual GitHub Action and selecting your branch or pushing a tag to Firewood. +### Hash Mode + +Firewood implemented its own optimized merkle trie structure. To support Ethereum Merkle Trie hash compatibility, it also provides a feature flag `ethhash`. + +This is an optional feature (disabled by default). To enable it for a local build, compile with: + +``` +cargo build -p firewood-ffi --features ethhash +``` + +To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries to [firewood-go](https://github.com/ava-labs/firewood-go) with `ethhash` enabled by default. + ## Development Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 8b783e4db3f8..0bab1f9929fa 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -2,6 +2,7 @@ package firewood import ( "bytes" + "encoding/hex" "fmt" "os" "path/filepath" @@ -12,6 +13,68 @@ import ( "github.com/stretchr/testify/require" ) +const ( + ethhashKey = "ethhash" + firewoodKey = "firewood" + emptyKey = "empty" + insert100Key = "100" + emptyEthhashRoot = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + emptyFirewoodRoot = "0000000000000000000000000000000000000000000000000000000000000000" +) + +// expectedRoots contains the expected root hashes for different use cases across both default +// firewood hashing and ethhash. +// By default, TestMain infers which mode Firewood is operating in and selects the expected roots +// accordingly (this does turn test empty database into an effective no-op). +// +// To test a specific hashing mode explicitly, set the environment variable: +// TEST_FIREWOOD_HASH_MODE=ethhash or TEST_FIREWOOD_HASH_MODE=firewood +// This will skip the inference step and enforce we use the expected roots for the specified mode. +var ( + // expectedRoots contains a mapping of expected root hashes for different test + // vectors. + expectedRootModes = map[string]map[string]string{ + ethhashKey: { + emptyKey: emptyEthhashRoot, + insert100Key: "c25a0076e0337d7c982c3c9dfa445c8088242a0a607f9d9def3762765bcb0fde", + }, + firewoodKey: { + emptyKey: emptyFirewoodRoot, + insert100Key: "f858b51ada79c4abeb6566ef1204a453030dba1cca3526d174e2cb3ce2aadc57", + }, + } + expectedEmptyRootToMode = map[string]string{ + emptyEthhashRoot: ethhashKey, + emptyFirewoodRoot: firewoodKey, + } + expectedRoots map[string]string +) + +func inferHashingMode() (string, error) { + dbFile := filepath.Join(os.TempDir(), "test.db") + db, closeDB, err := newDatabase(dbFile) + if err != nil { + return "", err + } + defer func() { + _ = closeDB() + _ = os.Remove(dbFile) + }() + + actualEmptyRoot, err := db.Root() + if err != nil { + return "", fmt.Errorf("failed to get root of empty database: %w", err) + } + actualEmptyRootHex := hex.EncodeToString(actualEmptyRoot) + + actualFwMode, ok := expectedEmptyRootToMode[actualEmptyRootHex] + if !ok { + return "", fmt.Errorf("unknown empty root %q, cannot infer mode", actualEmptyRootHex) + } + + return actualFwMode, nil +} + func TestMain(m *testing.M) { // The cgocheck debugging flag checks that all pointers are pinned. // TODO(arr4n) why doesn't `//go:debug cgocheck=1` work? https://go.dev/doc/godebug @@ -35,25 +98,49 @@ func TestMain(m *testing.M) { } } + // If TEST_FIREWOOD_HASH_MODE is set, use it to select the expected roots. + // Otherwise, infer the hash mode from an empty database. + hashMode := os.Getenv("TEST_FIREWOOD_HASH_MODE") + if hashMode == "" { + inferredHashMode, err := inferHashingMode() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to infer hash mode %v\n", err) + os.Exit(1) + } + hashMode = inferredHashMode + } + selectedExpectedRoots, ok := expectedRootModes[hashMode] + if !ok { + fmt.Fprintf(os.Stderr, "unknown hash mode %q\n", hashMode) + os.Exit(1) + } + expectedRoots = selectedExpectedRoots + os.Exit(m.Run()) } func newTestDatabase(t *testing.T) *Database { t.Helper() + dbFile := filepath.Join(t.TempDir(), "test.db") + db, closeDB, err := newDatabase(dbFile) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, closeDB()) + }) + return db +} + +func newDatabase(dbFile string) (*Database, func() error, error) { conf := DefaultConfig() conf.MetricsPort = 0 conf.Create = true - // The TempDir directory is automatically cleaned up so there's no need to - // remove test.db. - dbFile := filepath.Join(t.TempDir(), "test.db") f, err := New(dbFile, conf) - require.NoErrorf(t, err, "NewDatabase(%+v)", conf) - // Close() always returns nil, its signature returning an error only to - // conform with an externally required interface. - t.Cleanup(func() { f.Close() }) - return f + if err != nil { + return nil, nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) + } + return f, f.Close, nil } // Tests that a single key-value pair can be inserted and retrieved. @@ -168,9 +255,15 @@ func TestInsert100(t *testing.T) { hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - require.Lenf(t, hash, 32, "%T.Root()", db) - // we know the hash starts with 0xf8 - require.Equalf(t, byte(0xf8), hash[0], "First byte of %T.Root()", db) + + // Assert the hash is exactly as expected. Test failure indicates a + // non-hash compatible change has been made since the string was set. + // If that's expected, update the string at the top of the file to + // fix this test. + expectedHashHex := expectedRoots[insert100Key] + expectedHash, err := hex.DecodeString(expectedHashHex) + require.NoError(t, err, "failed to decode expected hash") + require.Equal(t, expectedHash, hash, "Root hash mismatch.\nExpected (hex): %x\nActual (hex): %x", expectedHash, hash) require.Equalf(t, rootFromInsert, hash, "%T.Root() matches value returned by insertion", db) }) } @@ -211,7 +304,11 @@ func TestInvariants(t *testing.T) { db := newTestDatabase(t) hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - require.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie", db) + + emptyRootStr := expectedRoots[emptyKey] + expectedHash, err := hex.DecodeString(emptyRootStr) + require.NoError(t, err) + require.Equalf(t, expectedHash, hash, "expected %x, got %x", expectedHash, hash) got, err := db.Get([]byte("non-existent")) require.NoError(t, err) @@ -327,7 +424,10 @@ func TestDeleteAll(t *testing.T) { // Check that the database is empty. hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - require.Equalf(t, make([]byte, 32), hash, "%T.Root() of empty trie", db) + expectedHashHex := expectedRoots[emptyKey] + expectedHash, err := hex.DecodeString(expectedHashHex) + require.NoError(t, err) + require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) } // Tests that a proposal with an invalid ID cannot be committed. diff --git a/ffi/tests/eth_compatibility_test.go b/ffi/tests/eth_compatibility_test.go index fdabcee5485c..b111b2fc70b8 100644 --- a/ffi/tests/eth_compatibility_test.go +++ b/ffi/tests/eth_compatibility_test.go @@ -37,7 +37,7 @@ func TestInsert(t *testing.T) { key common.Hash } - rand.Seed(0) + rand := rand.New(rand.NewSource(0)) addrs := make([]common.Address, 0) storages := make([]storageKey, 0) From f5efecc24c1c4ea9218ab7508f005c42cda4f203 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Tue, 27 May 2025 09:37:16 -0400 Subject: [PATCH 0723/1053] test(ethhash): Use libevm (#900) --- ffi/tests/eth_compatibility_test.go | 75 ++- ffi/tests/go.mod | 56 ++- ffi/tests/go.sum | 734 ++++++++++++++++++++++++++-- 3 files changed, 789 insertions(+), 76 deletions(-) diff --git a/ffi/tests/eth_compatibility_test.go b/ffi/tests/eth_compatibility_test.go index b111b2fc70b8..83f4889321be 100644 --- a/ffi/tests/eth_compatibility_test.go +++ b/ffi/tests/eth_compatibility_test.go @@ -7,15 +7,15 @@ import ( "slices" "testing" - firewood "github.com/ava-labs/firewood/ffi/v2" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/triedb" + firewood "github.com/ava-labs/firewood-go/ffi" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/state" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/crypto" + "github.com/ava-labs/libevm/rlp" + "github.com/ava-labs/libevm/trie/trienode" + "github.com/ava-labs/libevm/triedb" "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) @@ -43,11 +43,11 @@ func TestInsert(t *testing.T) { storages := make([]storageKey, 0) chooseAddr := func() common.Address { - return addrs[rand.Intn(len(addrs))] + return addrs[rand.Intn(len(addrs))] //nolint:gosec } chooseStorage := func() storageKey { - return storages[rand.Intn(len(storages))] + return storages[rand.Intn(len(storages))] //nolint:gosec } deleteStorage := func(k storageKey) { @@ -66,10 +66,10 @@ func TestInsert(t *testing.T) { } memdb := rawdb.NewMemoryDatabase() - tdb := state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil) + tdb := state.NewDatabaseWithConfig(memdb, triedb.HashDefaults) ethRoot := types.EmptyRootHash - for i := range 10_000 { + for i := range uint64(10_000) { tr, err := tdb.OpenTrie(ethRoot) require.NoError(t, err) mergeSet := trienode.NewMergedNodeSet() @@ -102,18 +102,24 @@ func TestInsert(t *testing.T) { require.NoError(t, err) deleteStorage(storageKey) - strRoot, set := str.Commit(false) + strRoot, set, err := str.Commit(false) + require.NoError(t, err) err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.UpdateAccount(storageKey.addr, acc, 0) + err = tr.UpdateAccount(storageKey.addr, acc) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) fwVals = append(fwVals, []byte{}) + // We must also update the account (not for hash, but to be accurate) + fwKeys = append(fwKeys, accHash[:]) + encodedVal, err := rlp.EncodeToBytes(acc) + require.NoError(t, err) + fwVals = append(fwVals, encodedVal) case i%4 == 0: // add acc - addr := common.BytesToAddress(hashData(binary.BigEndian.AppendUint64(nil, uint64(i))).Bytes()) + addr := common.BytesToAddress(hashData(binary.BigEndian.AppendUint64(nil, i)).Bytes()) accHash := hashData(addr[:]) acc := &types.StateAccount{ Nonce: 1, @@ -124,7 +130,7 @@ func TestInsert(t *testing.T) { enc, err := rlp.EncodeToBytes(acc) require.NoError(t, err) - err = tr.UpdateAccount(addr, acc, 0) + err = tr.UpdateAccount(addr, acc) require.NoError(t, err) addrs = append(addrs, addr) @@ -139,7 +145,7 @@ func TestInsert(t *testing.T) { enc, err := rlp.EncodeToBytes(acc) require.NoError(t, err) - err = tr.UpdateAccount(addr, acc, 0) + err = tr.UpdateAccount(addr, acc) require.NoError(t, err) fwKeys = append(fwKeys, accHash[:]) @@ -147,10 +153,10 @@ func TestInsert(t *testing.T) { case i%4 == 2: // add storage addr := chooseAddr() accHash := hashData(addr[:]) - key := hashData(binary.BigEndian.AppendUint64(nil, uint64(i))) + key := hashData(binary.BigEndian.AppendUint64(nil, i)) keyHash := hashData(key[:]) - val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) + val := hashData(binary.BigEndian.AppendUint64(nil, i+1)) storageKey := storageKey{addr: addr, key: key} acc, err := tr.GetAccount(addr) @@ -163,11 +169,12 @@ func TestInsert(t *testing.T) { require.NoError(t, err) storages = append(storages, storageKey) - strRoot, set := str.Commit(false) + strRoot, set, err := str.Commit(false) + require.NoError(t, err) err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.UpdateAccount(addr, acc, 0) + err = tr.UpdateAccount(addr, acc) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) @@ -176,12 +183,18 @@ func TestInsert(t *testing.T) { encodedVal, err := rlp.EncodeToBytes(val[:]) require.NoError(t, err) fwVals = append(fwVals, encodedVal) + + // We must also update the account (not for hash, but to be accurate) + fwKeys = append(fwKeys, accHash[:]) + encodedVal, err = rlp.EncodeToBytes(acc) + require.NoError(t, err) + fwVals = append(fwVals, encodedVal) case i%4 == 3: // update storage storageKey := chooseStorage() accHash := hashData(storageKey.addr[:]) keyHash := hashData(storageKey.key[:]) - val := hashData(binary.BigEndian.AppendUint64(nil, uint64(i+1))) + val := hashData(binary.BigEndian.AppendUint64(nil, i+1)) acc, err := tr.GetAccount(storageKey.addr) require.NoError(t, err) @@ -192,11 +205,12 @@ func TestInsert(t *testing.T) { err = str.UpdateStorage(storageKey.addr, storageKey.key[:], val[:]) require.NoError(t, err) - strRoot, set := str.Commit(false) + strRoot, set, err := str.Commit(false) + require.NoError(t, err) err = mergeSet.Merge(set) require.NoError(t, err) acc.Root = strRoot - err = tr.UpdateAccount(storageKey.addr, acc, 0) + err = tr.UpdateAccount(storageKey.addr, acc) require.NoError(t, err) fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) @@ -205,12 +219,19 @@ func TestInsert(t *testing.T) { encodedVal, err := rlp.EncodeToBytes(val[:]) require.NoError(t, err) fwVals = append(fwVals, encodedVal) + + // We must also update the account (not for hash, but to be accurate) + fwKeys = append(fwKeys, accHash[:]) + encodedVal, err = rlp.EncodeToBytes(acc) + require.NoError(t, err) + fwVals = append(fwVals, encodedVal) } - next, set := tr.Commit(true) + next, set, err := tr.Commit(true) + require.NoError(t, err) err = mergeSet.Merge(set) require.NoError(t, err) - err = tdb.TrieDB().Update(next, ethRoot, uint64(i), mergeSet, nil) + err = tdb.TrieDB().Update(next, ethRoot, i, mergeSet, nil) require.NoError(t, err) // update firewood db diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod index 074292829348..dd8e647642c6 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/go.mod @@ -5,43 +5,67 @@ go 1.23.0 toolchain go1.23.6 require ( - github.com/ava-labs/firewood/ffi/v2 v2.0.0 // this is replaced to use the parent folder - github.com/ethereum/go-ethereum v1.15.7 + github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder + github.com/ava-labs/libevm v1.13.14-0.2.0.release github.com/holiman/uint256 v1.3.2 github.com/stretchr/testify v1.10.0 ) require ( + github.com/DataDog/zstd v1.4.5 // indirect github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect - github.com/bits-and-blooms/bitset v1.17.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/consensys/bavard v0.1.22 // indirect - github.com/consensys/gnark-crypto v0.14.0 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect - github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.8.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect + github.com/cockroachdb/redact v1.0.8 // indirect + github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/ethereum/c-kzg-4844 v1.0.0 // indirect - github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.0 // indirect + github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/supranational/blst v0.3.14 // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.16.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ava-labs/firewood/ffi/v2 => ../ +replace github.com/ava-labs/firewood-go/ffi => ../ diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum index 5c74782e9b64..b862df4f3978 100644 --- a/ffi/tests/go.sum +++ b/ffi/tests/go.sum @@ -1,99 +1,767 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= -github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/ava-labs/libevm v1.13.14-0.2.0.release h1:uKGCc5/ceeBbfAPRVtBUxbQt50WzB2pEDb8Uy93ePgQ= +github.com/ava-labs/libevm v1.13.14-0.2.0.release/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= -github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= -github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= -github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= -github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= -github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= -github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= -github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= -github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= -github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= -github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= -github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= -github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From c962db9147c2e8f3067396c99768e3694c5ee043 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 28 May 2025 11:26:05 -0400 Subject: [PATCH 0724/1053] Skip attach static libs if required secret is missing for external PRs (#908) --- .github/workflows/attach-static-libs.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 209e845660ed..0663b799a4e3 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -38,6 +38,8 @@ jobs: target: aarch64-apple-darwin - os: macos-13 target: x86_64-apple-darwin + outputs: + has_secrets: ${{ steps.check_secrets.outputs.has_secrets }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -56,12 +58,24 @@ jobs: path: target/${{ matrix.target }}/maxperf/libfirewood_ffi.a if-no-files-found: error + - name: Check if FIREWOOD_GO_GITHUB_TOKEN is set + id: check_secrets + run: | + if [ -z "${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }}" ]; then + echo "FIREWOOD_GO_GITHUB_TOKEN is not set" + echo "has_secrets=false" >> "$GITHUB_OUTPUT" + else + echo "FIREWOOD_GO_GITHUB_TOKEN is set" + echo "has_secrets=true" >> "$GITHUB_OUTPUT" + fi + # Collect all the static libraries built on the previous matrix of jobs # and add them into ffi/libs directory. # We commit and push this as a new branch with "--force" to overwrite # the previous static libs that will not be on our branch. push-firewood-ffi-libs: needs: build-firewood-ffi-libs + if: needs.build-firewood-ffi-libs.outputs.has_secrets == 'true' runs-on: ubuntu-latest outputs: target_branch: ${{ steps.determine_branch.outputs.target_branch }} From 10fbf616178564656816daf2bc1933bfa0331705 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 28 May 2025 13:25:32 -0400 Subject: [PATCH 0725/1053] Add matrix of build profiles and optional features to CI (#911) --- .github/workflows/ci.yaml | 107 ++++++++++++++------ .github/workflows/default-branch-cache.yaml | 2 +- storage/src/node/mod.rs | 2 + 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4f9c080942f5..8f2e3611a012 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,25 @@ env: jobs: build: + # To optimize cargo performance and caching throughout the CI pipeline, we use Swatinem/rust-cache + # and set up an initial build job that writes the cache for a matrix of joint build profiles and + # feature sets. + # Future jobs specify the job they "need" (block to ensure the cache has been populated), the shared-key + # to read the cache and can set save-if: "false" to skip an expensive and unnecessary cache write. + # + # GitHub Actions does not provide easy support to define and re-use a matrix across multiple jobs + # and later jobs may also want to execute with only a subset of this matrix. Therefore, we define + # the matrix here and later jobs must re-specify the full matrix, a subset, or declare a single + # shared-key to read the cache. + strategy: + matrix: + include: + - profile-key: debug-no-features + profile-args: "" + - profile-key: debug-ethhash-logger + profile-args: "--features ethhash,logger" + - profile-key: maxperf-ethhash-logger + profile-args: "--profile maxperf --features ethhash,logger" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -18,25 +37,19 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 + with: + shared-key: ${{ matrix.profile-key }} - name: Check - run: cargo check --workspace --tests --examples --benches + run: cargo check ${{ matrix.profile-args }} --workspace --tests --examples --benches - name: Build - run: cargo build --workspace --tests --examples --benches + run: cargo build ${{ matrix.profile-args }} --workspace --tests --examples --benches - lint: - needs: build - runs-on: ubuntu-latest + no-rust-cache-lint: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: Swatinem/rust-cache@v2 - with: - save-if: "false" - shared-key: "build" - name: Check license headers uses: viperproject/check-license-header@v2 with: @@ -45,13 +58,43 @@ jobs: strict: true - name: Format run: cargo fmt -- --check + + rust-lint: + needs: build + runs-on: ubuntu-latest + strategy: + matrix: + include: + - profile-key: debug-no-features + profile-args: "" + - profile-key: debug-ethhash-logger + profile-args: "--features ethhash,logger" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: ${{ matrix.profile-key }} - name: Clippy - run: cargo clippy --tests --examples --benches -- -D warnings + run: cargo clippy ${{ matrix.profile-args }} --tests --examples --benches -- -D warnings test: needs: build runs-on: ubuntu-latest - + strategy: + matrix: + include: + - profile-key: debug-no-features + profile-args: "" + - profile-key: debug-ethhash-logger + profile-args: "--features ethhash,logger" + - profile-key: maxperf-ethhash-logger + profile-args: "--profile maxperf --features ethhash,logger" + # TODO: enable testing with branch_factor_256 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -61,18 +104,21 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: "build" - - name: Run tests with ethhash disabled - run: cargo test --verbose + shared-key: ${{ matrix.profile-key }} - name: Run tests with features enabled - run: cargo test --verbose --features logger,ethhash - # TODO: Enable testing with branch_factor_256 - # - name: Run tests with branch_factor_256 - # run: cargo test --verbose --features branch_factor_256 + run: cargo test --verbose ${{ matrix.profile-args }} examples: - needs: ethhash runs-on: ubuntu-latest + strategy: + matrix: + include: + - profile-key: debug-no-features + profile-args: "" + - profile-key: debug-ethhash-logger + profile-args: "--features ethhash,logger" + - profile-key: maxperf-ethhash-logger + profile-args: "--profile maxperf --features ethhash,logger" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -82,11 +128,11 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: "ethhash" + shared-key: ${{ matrix.profile-key}} - name: Run benchmark example - run: RUST_BACKTRACE=1 cargo run --features ethhash --release --bin benchmark -- --number-of-batches 100 --batch-size 1000 create + run: RUST_BACKTRACE=1 cargo run ${{ matrix.profile-args }} --bin benchmark -- --number-of-batches 100 --batch-size 1000 create - name: Run insert example - run: RUST_BACKTRACE=1 cargo run --features ethhash --release --example insert + run: RUST_BACKTRACE=1 cargo run ${{ matrix.profile-args }} --example insert docs: needs: build @@ -100,7 +146,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: "build" + shared-key: "debug-no-features" - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps ffi: @@ -118,7 +164,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: "build" + shared-key: "debug-no-features" - name: Build Firewood FFI working-directory: ffi run: cargo build --release @@ -137,7 +183,7 @@ jobs: # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... - ethhash: + ethhash-compatibility-go: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -146,8 +192,11 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: "debug-ethhash-logger" - name: Build Firewood FFI (with ethhash) - run: cargo build --release --features ethhash + run: cargo build --features ethhash,logger - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 97b1c761a1cc..f39b461f47b0 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -23,7 +23,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: "build" + shared-key: "debug-no-features" - name: Check run: cargo check --workspace --tests --examples --benches - name: Build diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index c1c70400e7a4..49660a0798ab 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -478,6 +478,8 @@ mod test { Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 652; "full branch node with long partial path and value" )] + // When ethhash is enabled, we don't actually check the `expected_length` + #[allow(unused_variables)] fn test_serialize_deserialize(node: Node, expected_length: usize) { use crate::node::Node; use std::io::Cursor; From 68fdafd63d629e966c7efc01547c6152a26152da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 10:50:32 -0700 Subject: [PATCH 0726/1053] build(deps): update pprof requirement from 0.14.0 to 0.15.0 (#906) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 65a20ea60010..f503a1016f5f 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -45,7 +45,7 @@ criterion = { version = "0.5.1", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" clap = { version = "4.5.0", features = ['derive'] } -pprof = { version = "0.14.0", features = ["flamegraph"] } +pprof = { version = "0.15.0", features = ["flamegraph"] } tempfile = "3.12.0" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } ethereum-types = "0.15.1" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index a8b6f64adfba..06d1fb9d3977 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -34,7 +34,7 @@ bytes = { version = "1.10.1", optional = true } rand = "0.9.0" test-case = "3.3.1" criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } -pprof = { version = "0.14.0", features = ["flamegraph"] } +pprof = { version = "0.15.0", features = ["flamegraph"] } tempfile = "3.12.0" [features] From 34128079803aabcd920343c7101906f263d8c27b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 18:01:22 +0000 Subject: [PATCH 0727/1053] build(deps): update cbindgen requirement from 0.28.0 to 0.29.0 (#899) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 2b8da24e8eb9..33068b3db0a6 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,7 +23,7 @@ logger = ["dep:env_logger", "firewood/logger"] ethhash = ["firewood/ethhash"] [build-dependencies] -cbindgen = "0.28.0" +cbindgen = "0.29.0" [lints.clippy] unwrap_used = "warn" From 7d4d7d7d1ab0c9832471d1dcd0fc1ac403cb6c6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 18:12:03 +0000 Subject: [PATCH 0728/1053] build(deps): update criterion requirement from 0.5.1 to 0.6.0 (#898) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- firewood/Cargo.toml | 2 +- grpc-testtool/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- triehash/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index f503a1016f5f..18850ba745c4 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -41,7 +41,7 @@ ethhash = [ "storage/ethhash" ] [dev-dependencies] triehash = { version = "0.8.5", path = "../triehash" } -criterion = { version = "0.5.1", features = ["async_tokio"] } +criterion = { version = "0.6.0", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" clap = { version = "4.5.0", features = ['derive'] } diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index aa1ddcf84e04..6b6c34bdacde 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0.196", features = ["derive"] } tonic-build = "0.13.0" [dev-dependencies] -criterion = {version = "0.5.1", features = ["async_tokio"]} +criterion = {version = "0.6.0", features = ["async_tokio"]} rand = "0.9.1" rand_distr = "0.5.0" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 06d1fb9d3977..9c4be78f5af9 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -33,7 +33,7 @@ bytes = { version = "1.10.1", optional = true } [dev-dependencies] rand = "0.9.0" test-case = "3.3.1" -criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } +criterion = { version = "0.6.0", features = ["async_tokio", "html_reports"] } pprof = { version = "0.15.0", features = ["flamegraph"] } tempfile = "3.12.0" diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 379eefefe37c..3cde54bf8c01 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -12,7 +12,7 @@ hash-db = { version = "0.16.0", default-features = false } rlp = { version = "0.6", default-features = false } [dev-dependencies] -criterion = "0.5.1" +criterion = "0.6.0" keccak-hasher = "0.16.0" ethereum-types = { version = "0.15.1" } tiny-keccak = { version = "2.0", features = ["keccak"] } From b3a9f8523b764dc2376498f4bd7bc0fa37eed92e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 18:49:21 +0000 Subject: [PATCH 0729/1053] build(deps): bump golang.org/x/crypto from 0.17.0 to 0.35.0 in /ffi/tests (#907) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris Co-authored-by: aaronbuchwald --- ffi/tests/go.mod | 4 ++-- ffi/tests/go.sum | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod index dd8e647642c6..ae2b71a220d4 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/go.mod @@ -59,10 +59,10 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.30.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum index b862df4f3978..4a59a3bb9cc2 100644 --- a/ffi/tests/go.sum +++ b/ffi/tests/go.sum @@ -441,8 +441,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -513,8 +513,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -585,8 +585,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -594,8 +594,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 2690e7a6a6f7be92765d415744393ec3247e6573 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 28 May 2025 19:50:04 +0100 Subject: [PATCH 0730/1053] Rkuris/readme fixes (#902) --- .github/workflows/ci.yaml | 10 +++++++++- .markdownlint.json | 3 +++ .vscode/extensions.json | 7 +++++++ CONTRIBUTING.md | 36 ++++++++++++++++++------------------ LICENSE.md | 7 +++---- README.docker.md | 4 ++-- README.md | 6 +++--- benchmark/README.md | 5 +++-- ffi/README.md | 11 +++++++---- fwdctl/README.md | 38 ++++++++++++++++++++++++-------------- grpc-testtool/README.md | 2 +- triehash/CHANGELOG.md | 11 +++++++++++ triehash/README.md | 4 +++- 13 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 .markdownlint.json create mode 100644 .vscode/extensions.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f2e3611a012..926375b05c70 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,7 +147,15 @@ jobs: with: save-if: "false" shared-key: "debug-no-features" - - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps + - name: md check + uses: DavidAnson/markdownlint-cli2-action@v20 + with: + globs: | + *.md + **/*.md + !target/** + - name: doc generation + run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps ffi: needs: build diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000000..92a0928f72c2 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,3 @@ +{ + "line-length": false, +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000000..4f731bf664e5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "rust-lang.rust-analyzer", + "vadimcn.vscode-lldb" + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8fda78eb9371..4035357f6d5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,23 @@ -# Welcome contributors! +# Welcome contributors We are eager for contributions and happy you found yourself here. Please read through this document to familiarize yourself with our guidelines for contributing to firewood. -# Table of Contents +## Table of Contents - * [Quick Links](#Quick Links) - * [Testing](#Testing) - * [How to submit changes](#How to submit changes) - * [Where can I ask for help?](#Where can I ask for help) +* [Quick Links](#Quick Links) +* [Testing](#testing) +* [How to submit changes](#How to submit changes) +* [Where can I ask for help?](#Where can I ask for help) -# [Quick Links] +## [Quick Links] - * [Setting up docker](README.docker.md) - * [Auto-generated documentation](https://ava-labs.github.io/firewood/firewood/) - * [Issue tracker](https://github.com/ava-labs/firewood/issues) +* [Setting up docker](README.docker.md) +* [Auto-generated documentation](https://ava-labs.github.io/firewood/firewood/) +* [Issue tracker](https://github.com/ava-labs/firewood/issues) -# [Testing] +## [Testing] After submitting a PR, we'll run all the tests and verify your code meets our submission guidelines. To ensure it's more likely to pass these checks, you should run the following commands locally: @@ -28,34 +28,34 @@ After submitting a PR, we'll run all the tests and verify your code meets our su Resolve any warnings or errors before making your PR. -# [How to submit changes] +## [How to submit changes] To create a PR, fork firewood, and use github to create the PR. We typically prioritize reviews in the middle of our the next work day, so you should expect a response during the week within 24 hours. -# [How to report a bug] +## [How to report a bug] Please use the [issue tracker](https://github.com/ava-labs/firewood/issues) for reporting issues. -# [First time fixes for contributors] +## [First time fixes for contributors] The [issue tracker](https://github.com/ava-labs/firewood/issues) typically has some issues tagged for first-time contributors. If not, please reach out. We hope you work on an easy task before tackling a harder one. -# [How to request an enhancement] +## [How to request an enhancement] Just like bugs, please use the [issue tracker](https://github.com/ava-labs/firewood/issues) for requesting enhancements. Please tag the issue with the "enhancement" tag. -# [Style Guide / Coding Conventions] +## [Style Guide / Coding Conventions] We generally follow the same rules that `cargo fmt` and `cargo clippy` will report as warnings, with a few notable exceptions as documented in the associated Cargo.toml file. By default, we prohibit bare `unwrap` calls and index dereferencing, as there are usually better ways to write this code. In the case where you can't, please use `expect` with a message explaining why it would be a bug, which we currently allow. For more information on our motivation, please read this great article on unwrap: [Using unwrap() in Rust is Okay](https://blog.burntsushi.net/unwrap) by [Andrew Gallant](https://blog.burntsushi.net). -# [Where can I ask for help]? +## [Where can I ask for help]? Please reach out on X (formerly twitter) @rkuris for help or questions! -# Thank you +## Thank you We'd like to extend a pre-emptive "thank you" for reading through this and submitting your first contribution! diff --git a/LICENSE.md b/LICENSE.md index fbc13b0da963..e7fe69c61eb0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,5 @@ -Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# Ecosystem License -Ecosystem License Version: 1.1 Subject to the terms herein, Ava Labs, Inc. (**“Ava Labs”**) hereby grants you @@ -33,7 +32,7 @@ Authorized Platform will be deemed to be the then-current iteration of such platform. You hereby acknowledge and agree to the terms set forth at -www.avalabs.org/important-notice. +. If you use the Licensed Software in violation of this License, this License will automatically terminate and Ava Labs reserves all rights to seek any @@ -63,4 +62,4 @@ STATUTE OR OTHERWISE, AND INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTY, TERM, OR CONDITION OF NON-INFRINGEMENT, MERCHANTABILITY, TITLE, OR FITNESS FOR PARTICULAR PURPOSE. YOU USE THE LICENSED SOFTWARE AT YOUR OWN RISK. AVA LABS EXPRESSLY DISCLAIMS ALL LIABILITY (INCLUDING FOR ALL DIRECT, CONSEQUENTIAL OR -OTHER DAMAGES OR LOSSES) RELATED TO ANY USE OF THE LICENSED SOFTWARE.** \ No newline at end of file +OTHER DAMAGES OR LOSSES) RELATED TO ANY USE OF THE LICENSED SOFTWARE.** diff --git a/README.docker.md b/README.docker.md index 07628ee5bfb1..4097d9f7395f 100644 --- a/README.docker.md +++ b/README.docker.md @@ -37,7 +37,7 @@ Open a terminal in vscode OR exec into the container directly as follows docker exec -it --privileged -u root firewood-app-1 zsh ``` -Once you're in the terminal you'll want to install the Rust toolset. You can find instructions [here](https://rustup.rs/) +Once you're in the terminal you'll want to install the Rust toolset. You can [find instructions here](https://rustup.rs/) **!!! IMPORTANT !!!** @@ -65,7 +65,7 @@ After adding the line, don't forget to `source` the file to make sure your curre ### Step 6 -Navigate to `/com.docker.devenvironments.code ` and run `cargo test`. If it worked, you are most of the way there! If it did not work, there are a couple of common issues. If the code will not compile, it's possible that your target directory isn't set up properly. Check inside `/root/target` to see if there are any build artifacts. If not, you might need to call `source ~/.zshrc` again (sub in whatever your preferred shell is). +Navigate to `/com.docker.devenvironments.code` and run `cargo test`. If it worked, you are most of the way there! If it did not work, there are a couple of common issues. If the code will not compile, it's possible that your target directory isn't set up properly. Check inside `/root/target` to see if there are any build artifacts. If not, you might need to call `source ~/.zshrc` again (sub in whatever your preferred shell is). Now for vscode, you need to configure your `rust-analyzer` in the "remote-environment" (the Docker container). There are a couple of places to do this. First, you want to open `/root/.vscode-server/Machine/settings.json` and make sure that you have the following entry: diff --git a/README.md b/README.md index fb5e51fca8ea..798b75c0914b 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,9 @@ Firewood guarantees recoverability by not referencing the new nodes in a new rev In order to build firewood, the following dependencies must be installed: -- `protoc` See instructions for installation [here](https://grpc.io/docs/protoc-installation/). -- `cargo` See instructions for installation [here](https://doc.rust-lang.org/cargo/getting-started/installation.html). -- `make` See download instructions [here](https://www.gnu.org/software/make/#download) or run `sudo apt install build-essential` on Linux. +- `protoc` See [installation instructions](https://grpc.io/docs/protoc-installation/). +- `cargo` See [installation instructions](https://doc.rust-lang.org/cargo/getting-started/installation.html). +- `make` See [download instructions](https://www.gnu.org/software/make/#download) or run `sudo apt install build-essential` on Linux. ## Run diff --git a/benchmark/README.md b/benchmark/README.md index 3a02bc15b36d..30d78e4094dd 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -1,6 +1,7 @@ # Firewood Benchmark -Welcome to the Firewood Benchmark! This repository contains the benchmarking code and resources for the Firewood project. +Welcome to the Firewood Benchmark! This repository contains the benchmarking +code and resources for the Firewood project. ## Table of Contents @@ -138,7 +139,7 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a a. On the right top pulldown, click New->Import b. Import the dashboard from the Grafana-dashboard.json file c. Set the data source to Prometheus -9. \[optional] Install a stock dashboard from [here](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) +9. \[optional] Install a stock dashboard from [grafana](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) ## Usage diff --git a/ffi/README.md b/ffi/README.md index 213b3460cfa3..5a36efad888a 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -54,28 +54,31 @@ Firewood implemented its own optimized merkle trie structure. To support Ethereu This is an optional feature (disabled by default). To enable it for a local build, compile with: -``` +```sh cargo build -p firewood-ffi --features ethhash ``` To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries to [firewood-go](https://github.com/ava-labs/firewood-go) with `ethhash` enabled by default. ## Development + Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. ### CGO Regeneration -As you edit any Rust code and save the file in VS Code, the `firewood.h` file is automatically updated with edited function and struct definitions. However, the Go linter will not recognize these changes until you manually regenerate the cgo wrappers. To do this, you can run `go tool cgo firewood.go`. Alternatively, in VS Code, right above the `import "C"` definition, you can click on the small letters saying "regenerate CGO definitions". This will allow the linter to use the altered definitions. +As you edit any Rust code and save the file in VS Code, the `firewood.h` file is automatically updated with edited function and struct definitions. However, the Go linter will not recognize these changes until you manually regenerate the cgo wrappers. To do this, you can run `go tool cgo firewood.go`. Alternatively, in VS Code, right above the `import "C"` definition, you can click on the small letters saying "regenerate CGO definitions". This will allow the linter to use the altered definitions. Because the C header file is autogenerated from the Rust code, the naming matches exactly (due to the `no_mangle` macro). However, the C definitions imported in Go do not match exactly, and are prefixed with `struct_`. Function naming is the same as the header file. These names are generated by the `go tool cgo` command above. ### Testing + Although the VS Code testing feature does work, there are some quirks in ensuring proper building. The Rust code must be compiled separated, and sometimes the `go test` command continues to use a cached result. Whenever testing after making changes to the Rust/C builds, the cache should be cleared if results don't seem correct. Do not compile with `--features ethhash`, as some tests will fail. To ensure there are no memory leaks, the easiest way is to use your preferred CLI tool (e.g. `valgrind` for Linux, `leaks` for macOS) and compile the tests into a binary. You must not compile a release binary to ensure all memory can be managed. An example flow is given below. -``` + +```sh cd ffi cargo build # use debug go test -a -c -o binary_file # ignore cache leaks --nostacks --atExit -- ./binary_file -``` \ No newline at end of file +``` diff --git a/fwdctl/README.md b/fwdctl/README.md index c558a59c8ce9..174090bdeaa1 100644 --- a/fwdctl/README.md +++ b/fwdctl/README.md @@ -1,28 +1,33 @@ # fwdctl -`fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. +`fwdctl` is a small CLI designed to make it easy to experiment with firewood locally. ## Building locally -*Note: fwdctl is linux-only* -``` + +```sh cargo build --release --bin fwdctl ``` + To use -``` -$ ./target/release/fwdctl -h + +```sh +./target/release/fwdctl -h ``` ## Supported commands + * `fwdctl create`: Create a new firewood database. * `fwdctl get`: Get the code associated with a key in the database. * `fwdctl insert`: Insert a key/value pair into the generic key/value store. -* `fwdctl delete`: Delete a key/value pair from the database. +* `fwdctl delete`: Delete a key/value pair from the database. * `fwdctl root`: Get the root hash of the key/value trie. * `fwdctl dump`: Dump the contents of the key/value store. ## Examples + * fwdctl create -``` + +```sh # Check available options when creating a database, including the defaults. $ fwdctl create -h # Create a new, blank instance of firewood using the default name "firewood". @@ -30,19 +35,24 @@ $ fwdctl create firewood # Look inside, there are several folders representing different components of firewood, including the WAL. $ ls firewood ``` + * fwdctl get KEY -``` -Get the value associated with a key in the database, if it exists. + +```sh +# Get the value associated with a key in the database, if it exists. fwdctl get KEY ``` + * fwdctl insert KEY VALUE -``` -Insert a key/value pair into the database. + +```sh +# Insert a key/value pair into the database. fwdctl insert KEY VALUE ``` + * fwdctl delete KEY -``` -Delete a key from the database, along with the associated value. + +```sh +# Delete a key from the database, along with the associated value. fwdctl delete KEY ``` - diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md index 7ff503e0a7e7..1cec486db8eb 100644 --- a/grpc-testtool/README.md +++ b/grpc-testtool/README.md @@ -19,7 +19,7 @@ There are 3 RPC specs that must be implemented: 2. The sync proto, which supports retrieving range and change proofs 3. The process-server proto, which currently only retrieves metrics -# Running +## Running To test the release version of firewood, just run `RUST_MIN_STACK=7000000 cargo bench`. If you make some changes and then run it again, it will give you a report showing how much it sped up or slowed down. diff --git a/triehash/CHANGELOG.md b/triehash/CHANGELOG.md index 0676a558db28..d44027d20d27 100644 --- a/triehash/CHANGELOG.md +++ b/triehash/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ## [0.8.5] - 2025-03-26 + - Updated `hash-db` to 0.16.0 - Updated `rlp` to 0.6 - Updated `criterion` to 0.5.1 @@ -14,13 +15,23 @@ The format is based on [Keep a Changelog]. - Updated `ethereum-types` to 0.15.1 - Updated `trie-standardmap` to 0.16.0 - Updated `hex-literal` to 1.0.0 + ## [0.8.4] - 2020-01-08 + - Updated `rlp` to 0.5. [#463](https://github.com/paritytech/parity-common/pull/463) + ## [0.8.3] - 2020-03-16 + - License changed from GPL3 to dual MIT/Apache2. [#342](https://github.com/paritytech/parity-common/pull/342) + ## [0.8.2] - 2019-12-15 + - Added no-std support. [#280](https://github.com/paritytech/parity-common/pull/280) + ## [0.8.1] - 2019-10-24 + - Migrated to 2018 edition. [#214](https://github.com/paritytech/parity-common/pull/214) + ### Dependencies + - Updated dependencies. [#239](https://github.com/paritytech/parity-common/pull/239) diff --git a/triehash/README.md b/triehash/README.md index 99c5ce459989..06b705982da3 100644 --- a/triehash/README.md +++ b/triehash/README.md @@ -1,2 +1,4 @@ +# triehash + This crate provides utility functions to validate and initialize tries using flexible input. -It is used extensively in `parity-ethereum` to validate blocks (mostly transactions and receipt roots). \ No newline at end of file +It is used extensively in `parity-ethereum` to validate blocks (mostly transactions and receipt roots). From a382a6ef4988173f0b7105ff85a1951d987679da Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 29 May 2025 13:06:33 -0400 Subject: [PATCH 0731/1053] Free C string filepath via defer (#916) --- ffi/firewood.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 6b58ce2b04b6..ae7c2287bf9d 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -99,6 +99,9 @@ func New(filePath string, conf *Config) (*Database, error) { strategy: C.uint8_t(conf.ReadCacheStrategy), metrics_port: C.uint16_t(conf.MetricsPort), } + // Defer freeing the C string allocated to the heap on the other side + // of the FFI boundary. + defer C.free(unsafe.Pointer(args.path)) var db *C.DatabaseHandle if conf.Create { @@ -107,8 +110,6 @@ func New(filePath string, conf *Config) (*Database, error) { db = C.fwd_open_db(args) } - // After creating the db, we can safely free the path string. - C.free(unsafe.Pointer(args.path)) return &Database{handle: db}, nil } From ad13c75744a2df8b387bf4951124cfeb528f639b Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 29 May 2025 13:26:39 -0400 Subject: [PATCH 0732/1053] Remove unreachable test cases (#917) --- ffi/firewood_test.go | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 0bab1f9929fa..f6fe7fa8d420 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -162,28 +162,26 @@ func TestInsert(t *testing.T) { require.Equal(t, val, string(got), "Recover lone batch-inserted value") } -// Attempt to make a call to a nil or invalid handle. -// Each function should return an error and not panic. -func TestGetBadHandle(t *testing.T) { - db := &Database{handle: nil} +func TestClosedDatabase(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + db, _, err := newDatabase(dbFile) + r.NoError(err) - // This ignores error, but still shouldn't panic. - _, err := db.Get([]byte("non-existent")) - require.ErrorIs(t, err, errDBClosed) + r.NoError(db.Close()) - // We ignore the error, but it shouldn't panic. _, err = db.Root() - require.ErrorIs(t, err, errDBClosed) + r.ErrorIs(err, errDBClosed) root, err := db.Update( [][]byte{[]byte("key")}, [][]byte{[]byte("value")}, ) - require.Empty(t, root) - require.ErrorIs(t, err, errDBClosed) + r.Empty(root) + r.ErrorIs(err, errDBClosed) err = db.Close() - require.ErrorIs(t, err, errDBClosed) + r.ErrorIs(err, errDBClosed) } func keyForTest(i int) []byte { @@ -430,21 +428,6 @@ func TestDeleteAll(t *testing.T) { require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) } -// Tests that a proposal with an invalid ID cannot be committed. -func TestCommitFakeProposal(t *testing.T) { - db := newTestDatabase(t) - - // Create a fake proposal with an invalid ID. - proposal := &Proposal{ - handle: db.handle, - id: 1, // note that ID 0 is reserved for invalid proposals - } - - // Attempt to get a value from the fake proposal. - _, err := proposal.Get([]byte("non-existent")) - require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") -} - func TestDropProposal(t *testing.T) { db := newTestDatabase(t) From 0908305384b3e1ff6b95bd6c4a43d468a437be5f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 29 May 2025 12:16:36 -0700 Subject: [PATCH 0733/1053] Add revision counters (#919) --- firewood/src/manager.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 546fc1041cd6..eafa2efb2430 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -9,6 +9,7 @@ use std::sync::Arc; #[cfg(feature = "ethhash")] use std::sync::OnceLock; +use metrics::gauge; use storage::logger::{trace, trace_enabled, warn}; use typed_builder::TypedBuilder; @@ -182,6 +183,8 @@ impl RevisionManager { break; } } + gauge!("firewood.active_revisions").set(self.historical.len() as f64); + gauge!("firewood.max_revisions").set(self.max_revisions as f64); } // 4. Set last committed revision From 46623b91d07c9658ae41862d1590ea6e382e7fa0 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Fri, 30 May 2025 11:32:31 -0400 Subject: [PATCH 0734/1053] build(deps): bump google.golang.org/protobuf from 1.27.1 to 1.33.0 /ffi/tests (#923) --- ffi/tests/go.mod | 2 +- ffi/tests/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod index ae2b71a220d4..0de16a30d242 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/go.mod @@ -63,7 +63,7 @@ require ( golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.30.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum index 4a59a3bb9cc2..7197b6cf982a 100644 --- a/ffi/tests/go.sum +++ b/ffi/tests/go.sum @@ -729,8 +729,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 6768afc0d0bc3a68b7a4918ebdc656494b5c91e8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 30 May 2025 14:17:29 -0700 Subject: [PATCH 0735/1053] Windows OS support (#922) --- Cargo.toml | 1 - benchmark/Cargo.toml | 4 +++- benchmark/src/main.rs | 1 + ffi/Cargo.toml | 2 ++ ffi/src/lib.rs | 5 +++++ storage/src/linear/filebacked.rs | 17 ++++++++++++++++- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4861f19e0ab..a83196116338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "firewood", "fwdctl", "storage", - "grpc-testtool", "benchmark", "ffi", "triehash", diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 3189ebfebf9d..5061c780ed15 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -16,7 +16,6 @@ tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thre rand = "0.9.0" rand_distr = "0.5.0" pretty-duration = "0.1.1" -tikv-jemallocator = "0.6.0" env_logger = "0.11.5" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } @@ -26,5 +25,8 @@ opentelemetry = "0.29.0" opentelemetry_sdk = "0.29.0" strum = "0.27.0" +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = "0.6.0" + [features] logger = ["firewood/logger"] diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 4b5225242395..20cefc0db2f3 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -175,6 +175,7 @@ trait TestRunner { } #[global_allocator] +#[cfg(unix)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[tokio::main(flavor = "multi_thread")] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 33068b3db0a6..a9deb7a8b644 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -16,6 +16,8 @@ chrono = "0.4.39" oxhttp = "0.3.0" coarsetime = "0.1.35" env_logger = {version = "0.11.7", optional = true} + +[target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" [features] diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 6d389cdd77a9..a8452d2d2f9f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr, c_char}; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; +#[cfg(unix)] use std::os::unix::ffi::OsStrExt as _; use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; @@ -18,6 +19,7 @@ use metrics::counter; #[doc(hidden)] mod metrics_setup; +#[cfg(unix)] #[global_allocator] #[doc(hidden)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -790,7 +792,10 @@ unsafe fn common_create( .try_init(); let path = unsafe { CStr::from_ptr(path) }; + #[cfg(unix)] let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); + #[cfg(windows)] + let path: &Path = OsStr::new(path.to_str().expect("path should be valid UTF-8")).as_ref(); if metrics_port > 0 { metrics_setup::setup_metrics(metrics_port); } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 10696706ae49..80f8bd215d45 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -9,7 +9,10 @@ use std::fs::{File, OpenOptions}; use std::io::{Error, Read}; use std::num::NonZero; +#[cfg(unix)] use std::os::unix::fs::FileExt; +#[cfg(windows)] +use std::os::windows::fs::FileExt; use std::path::PathBuf; use std::sync::Mutex; @@ -154,7 +157,14 @@ impl ReadableStorage for FileBacked { impl WritableStorage for FileBacked { fn write(&self, offset: u64, object: &[u8]) -> Result { - self.fd.write_at(object, offset) + #[cfg(unix)] + { + self.fd.write_at(object, offset) + } + #[cfg(windows)] + { + self.fd.seek_write(object, offset) + } } fn write_cached_nodes<'a>( @@ -221,9 +231,14 @@ impl Read for PredictiveReader<'_> { if self.len == self.pos { let bytes_left_in_page = PREDICTIVE_READ_BUFFER_SIZE - (self.offset % PREDICTIVE_READ_BUFFER_SIZE as u64) as usize; + #[cfg(unix)] let read = self .fd .read_at(&mut self.buffer[..bytes_left_in_page], self.offset)?; + #[cfg(windows)] + let read = self + .fd + .seek_read(&mut self.buffer[..bytes_left_in_page], self.offset)?; self.offset += read as u64; self.len = read; self.pos = 0; From 5af8b75e434db754923f7a2b6014fcdd356acbda Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:35:49 -0400 Subject: [PATCH 0736/1053] feat(ffi): add proposal root retrieval (#910) --- ffi/firewood.go | 19 ++----- ffi/firewood.h | 24 +++++---- ffi/firewood_test.go | 67 ++++++++++++++++++------- ffi/memory.go | 115 ++++++++++++++++++++++++------------------- ffi/proposal.go | 60 +++++++++++++++++----- ffi/revision.go | 4 +- ffi/src/lib.rs | 56 ++++++++++++++------- 7 files changed, 223 insertions(+), 122 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index ae7c2287bf9d..24bf27ef3e9f 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -141,7 +141,7 @@ func (db *Database) Batch(ops []KeyValue) ([]byte, error) { C.size_t(len(ffiOps)), unsafe.SliceData(ffiOps), // implicitly pinned ) - return extractBytesThenFree(&hash) + return bytesFromValue(&hash) } func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { @@ -159,21 +159,12 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { value: values.from(vals[i]), } } - idOrErr := C.fwd_propose_on_db( + val := C.fwd_propose_on_db( db.handle, C.size_t(len(ffiOps)), unsafe.SliceData(ffiOps), // implicitly pinned ) - id, err := extractUintThenFree(&idOrErr) - if err != nil { - return nil, err - } - - // The C function will never create an id of 0, unless it is an error. - return &Proposal{ - handle: db.handle, - id: id, - }, nil + return newProposal(db.handle, &val) } // Get retrieves the value for the given key. It always returns a nil error. @@ -186,7 +177,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { values, cleanup := newValueFactory() defer cleanup() val := C.fwd_get_latest(db.handle, values.from(key)) - bytes, err := extractBytesThenFree(&val) + bytes, err := bytesFromValue(&val) // If the root hash is not found, return nil. if err != nil && strings.Contains(err.Error(), rootHashNotFound) { @@ -203,7 +194,7 @@ func (db *Database) Root() ([]byte, error) { return nil, errDBClosed } hash := C.fwd_root_hash(db.handle) - bytes, err := extractBytesThenFree(&hash) + bytes, err := bytesFromValue(&hash) // If the root hash is not found, return a zeroed slice. if err != nil && strings.Contains(err.Error(), rootHashNotFound) { diff --git a/ffi/firewood.h b/ffi/firewood.h index 2acccf8e1089..78d0129e5c1d 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -16,13 +16,17 @@ typedef struct DatabaseHandle DatabaseHandle; /** * A value returned by the FFI. * - * This is used in several different ways: + * This is used in several different ways, including: + * * An C-style string. + * * An ID for a proposal. + * * A byte slice containing data. * - * - When returning data, the length is the length of the data and the data is a pointer to the data. - * - When returning an error, the length is 0 and the data is a null-terminated C-style string. - * - When returning an ID, the length is the ID and the data is null. + * For more details on how the data may be stored, refer to the function signature + * that returned it or the `From` implementations. + * + * The data stored in this struct (if `data` is not null) must be manually freed + * by the caller using `fwd_free_value`. * - * A `Value` with length 0 and a null data pointer indicates that the data was not found. */ typedef struct Value { size_t len; @@ -290,8 +294,9 @@ const struct DatabaseHandle *fwd_open_db(struct CreateOrOpenArgs args); * * # Returns * - * A `Value` containing {id, null} if creating the proposal succeeded. - * A `Value` containing {0, "error message"} if creating the proposal failed. + * On success, a `Value` containing {len=id, data=hash}. In this case, the + * hash will always be 32 bytes, and the id will be non-zero. + * On failure, a `Value` containing {0, "error message"}. * * # Safety * @@ -318,8 +323,9 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, * * # Returns * - * A `Value` containing {id, nil} if creating the proposal succeeded. - * A `Value` containing {0, "error message"} if creating the proposal failed. + * On success, a `Value` containing {len=id, data=hash}. In this case, the + * hash will always be 32 bytes, and the id will be non-zero. + * On failure, a `Value` containing {0, "error message"}. * * # Safety * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index f6fe7fa8d420..786622407cae 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -204,13 +204,19 @@ func kvForTest(i int) KeyValue { // 1. By calling [Database.Propose] and then [Proposal.Commit]. // 2. By calling [Database.Update] directly - no proposal storage is needed. func TestInsert100(t *testing.T) { + type dbView interface { + Get(key []byte) ([]byte, error) + Propose(keys, vals [][]byte) (*Proposal, error) + Root() ([]byte, error) + } + tests := []struct { name string - insert func(*Database, [][]byte, [][]byte) (root []byte, _ error) + insert func(dbView, [][]byte, [][]byte) (dbView, error) }{ { - name: "Propose", - insert: func(db *Database, keys, vals [][]byte) ([]byte, error) { + name: "Propose and Commit", + insert: func(db dbView, keys, vals [][]byte) (dbView, error) { proposal, err := db.Propose(keys, vals) if err != nil { return nil, err @@ -219,13 +225,28 @@ func TestInsert100(t *testing.T) { if err != nil { return nil, err } - return db.Root() + return db, nil }, }, { name: "Update", - insert: func(db *Database, keys, vals [][]byte) ([]byte, error) { - return db.Update(keys, vals) + insert: func(db dbView, keys, vals [][]byte) (dbView, error) { + actualDB, ok := db.(*Database) + if !ok { + return nil, fmt.Errorf("expected *Database, got %T", db) + } + _, err := actualDB.Update(keys, vals) + return db, err + }, + }, + { + name: "Propose", + insert: func(db dbView, keys, vals [][]byte) (dbView, error) { + proposal, err := db.Propose(keys, vals) + if err != nil { + return nil, err + } + return proposal, nil }, }, } @@ -240,20 +261,23 @@ func TestInsert100(t *testing.T) { keys[i] = keyForTest(i) vals[i] = valForTest(i) } - rootFromInsert, err := tt.insert(db, keys, vals) + newDB, err := tt.insert(db, keys, vals) require.NoError(t, err, "inserting") for i := range keys { - got, err := db.Get(keys[i]) + got, err := newDB.Get(keys[i]) require.NoErrorf(t, err, "%T.Get(%q)", db, keys[i]) // Cast as strings to improve debug messages. want := string(vals[i]) require.Equal(t, want, string(got), "Recover nth batch-inserted value") } - hash, err := db.Root() + hash, err := newDB.Root() require.NoError(t, err, "%T.Root()", db) + rootFromInsert, err := newDB.Root() + require.NoError(t, err, "%T.Root() after insertion", db) + // Assert the hash is exactly as expected. Test failure indicates a // non-hash compatible change has been made since the string was set. // If that's expected, update the string at the top of the file to @@ -415,6 +439,16 @@ func TestDeleteAll(t *testing.T) { require.Empty(t, got, "Get(%d)", i) } + emptyRootStr := expectedRoots[emptyKey] + expectedHash, err := hex.DecodeString(emptyRootStr) + require.NoError(t, err, "Decode expected empty root hash") + + // TODO: Check proposal root + // Requires #918 + // hash, err := proposal.Root() + // require.NoError(t, err, "%T.Root() after commit", proposal) + // require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) + // Commit the proposal. err = proposal.Commit() require.NoError(t, err, "Commit") @@ -422,9 +456,6 @@ func TestDeleteAll(t *testing.T) { // Check that the database is empty. hash, err := db.Root() require.NoError(t, err, "%T.Root()", db) - expectedHashHex := expectedRoots[emptyKey] - expectedHash, err := hex.DecodeString(expectedHashHex) - require.NoError(t, err) require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) } @@ -445,27 +476,25 @@ func TestDropProposal(t *testing.T) { err = proposal.Drop() require.NoError(t, err, "Drop") - // Attempt to commit the dropped proposal. + // Check all operations on the dropped proposal. err = proposal.Commit() require.ErrorIs(t, err, errDroppedProposal, "Commit(dropped proposal)") - - // Attempt to get a value from the dropped proposal. _, err = proposal.Get([]byte("non-existent")) require.ErrorIs(t, err, errDroppedProposal, "Get(dropped proposal)") + _, err = proposal.Root() + require.ErrorIs(t, err, errDroppedProposal, "Root(dropped proposal)") // Attempt to "emulate" the proposal to ensure it isn't internally available still. proposal = &Proposal{ handle: db.handle, id: 1, } + + // Check all operations on the fake proposal. _, err = proposal.Get([]byte("non-existent")) require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") - - // Attempt to create a new proposal from the fake proposal. _, err = proposal.Propose([][]byte{[]byte("key")}, [][]byte{[]byte("value")}) require.Contains(t, err.Error(), "proposal not found", "Propose(fake proposal)") - - // Attempt to commit the fake proposal. err = proposal.Commit() require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") } diff --git a/ffi/memory.go b/ffi/memory.go index a803a382308f..b01eb81e323b 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -27,78 +27,95 @@ type KeyValue struct { Value []byte } -// extractErrorThenFree converts the cgo `Value` payload to either: -// 1. a nil value, indicating no error, or -// 2. a non-nil error, indicating an error occurred. -// This should only be called when the `Value` is expected to only contain an error. -// Otherwise, an error is returned. -func extractErrorThenFree(v *C.struct_Value) error { +// hashAndIDFromValue converts the cgo `Value` payload into: +// +// case | data | len | meaning +// +// 1. | nil | 0 | invalid +// 2. | nil | non-0 | proposal deleted everything +// 3. | non-nil | 0 | error string +// 4. | non-nil | non-0 | hash and id +// +// The value should never be nil. +func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { // Pin the returned value to prevent it from being garbage collected. defer runtime.KeepAlive(v) if v == nil { - return errNilBuffer + return nil, 0, errNilBuffer } - // Expected empty case for Rust's `()` - // Ignores the length. if v.data == nil { - return nil + // Case 2 + if v.len != 0 { + return nil, uint32(v.len), nil + } + + // Case 1 + return nil, 0, errBadValue } - // If the value is an error string, it should be freed and an error - // returned. + // Case 3 if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - return fmt.Errorf("firewood error: %s", errStr) + return nil, 0, fmt.Errorf("firewood error: %s", errStr) } - // The value is formatted incorrectly. - // We should still attempt to free the value. + // Case 4 + id := uint32(v.len) + buf := C.GoBytes(unsafe.Pointer(v.data), RootLength) + v.len = C.size_t(RootLength) // set the length to free C.fwd_free_value(v) - return errBadValue + return buf, id, nil } -// extractUintThenFree converts the cgo `Value` payload to either: -// 1. a nonzero uint32 and nil error, indicating a valid int -// 2. a zero uint32 and a non-nil error, indicating an error occurred. -// This should only be called when the `Value` is expected to only contain an error or an ID. -// Otherwise, an error is returned. -func extractUintThenFree(v *C.struct_Value) (uint32, error) { +// errorFromValue converts the cgo `Value` payload into: +// +// case | data | len | meaning +// +// 1. | nil | 0 | empty +// 2. | nil | non-0 | invalid +// 3. | non-nil | 0 | error string +// 4. | non-nil | non-0 | invalid +// +// The value should never be nil. +func errorFromValue(v *C.struct_Value) error { // Pin the returned value to prevent it from being garbage collected. defer runtime.KeepAlive(v) if v == nil { - return 0, errNilBuffer + return errNilBuffer } - // Normal case, length is non-zero and data is nil. - if v.len != 0 && v.data == nil { - return uint32(v.len), nil + // Case 1 + if v.data == nil && v.len == 0 { + return nil } - // If the value is an error string, it should be freed and an error - // returned. - if v.len == 0 && v.data != nil { + // Case 3 + if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - return 0, fmt.Errorf("firewood error: %s", errStr) + return fmt.Errorf("firewood error: %s", errStr) } - // The value is formatted incorrectly. - // We should still attempt to free the value. + // Case 2 and 4 C.fwd_free_value(v) - return 0, errBadValue + return errBadValue } -// extractBytesThenFree converts the cgo `Value` payload to either: -// 1. a non-nil byte slice and nil error, indicating a valid byte slice -// 2. a nil byte slice and nil error, indicating an empty byte slice -// 3. a nil byte slice and a non-nil error, indicating an error occurred. -// This should only be called when the `Value` is expected to only contain an error or a byte slice. -// Otherwise, an error is returned. -func extractBytesThenFree(v *C.struct_Value) ([]byte, error) { +// bytesFromValue converts the cgo `Value` payload to: +// +// case | data | len | meaning +// +// 1. | nil | 0 | empty +// 2. | nil | non-0 | invalid +// 3. | non-nil | 0 | error string +// 4. | non-nil | non-0 | bytes (most common) +// +// The value should never be nil. +func bytesFromValue(v *C.struct_Value) ([]byte, error) { // Pin the returned value to prevent it from being garbage collected. defer runtime.KeepAlive(v) @@ -106,28 +123,26 @@ func extractBytesThenFree(v *C.struct_Value) ([]byte, error) { return nil, errNilBuffer } - // Expected behavior - no data and length is zero. - if v.len == 0 && v.data == nil { - return nil, nil - } - - // Normal case, data is non-nil and length is non-zero. + // Case 4 if v.len != 0 && v.data != nil { buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) C.fwd_free_value(v) return buf, nil } - // Data non-nil but length is zero indcates an error. + // Case 1 + if v.len == 0 && v.data == nil { + return nil, nil + } + + // Case 3 if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) return nil, fmt.Errorf("firewood error: %s", errStr) } - // The value is formatted incorrectly. - // We should still attempt to free the value. - C.fwd_free_value(v) + // Case 2 return nil, errBadValue } diff --git a/ffi/proposal.go b/ffi/proposal.go index 8b086b3cbb89..bdb4096eb460 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -25,6 +25,51 @@ type Proposal struct { // The proposal ID. // id = 0 is reserved for a dropped proposal. id uint32 + + // The proposal root hash. + root []byte +} + +// newProposal creates a new Proposal from the given DatabaseHandle and Value. +// The Value must be returned from a Firewood FFI function. +// An error can only occur from parsing the Value. +func newProposal(handle *C.DatabaseHandle, val *C.struct_Value) (*Proposal, error) { + bytes, id, err := hashAndIDFromValue(val) + if err != nil { + return nil, err + } + + // If the proposal root is nil, it means the proposal is empty. + if bytes == nil { + bytes = make([]byte, RootLength) + } + + return &Proposal{ + handle: handle, + id: id, + root: bytes, + }, nil +} + +// Root retrieves the root hash of the proposal. +// If the proposal is empty (i.e. no keys in database), +// it returns nil, nil. +func (p *Proposal) Root() ([]byte, error) { + if p.handle == nil { + return nil, errDBClosed + } + + if p.id == 0 { + return nil, errDroppedProposal + } + + // If the hash is empty, return the empty root hash. + if p.root == nil { + return make([]byte, RootLength), nil + } + + // Get the root hash of the proposal. + return p.root, nil } // Get retrieves the value for the given key. @@ -42,7 +87,7 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { // Get the value for the given key. val := C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), values.from(key)) - return extractBytesThenFree(&val) + return bytesFromValue(&val) } // Propose creates a new proposal with the given keys and values. @@ -77,15 +122,8 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { C.size_t(len(ffiOps)), unsafe.SliceData(ffiOps), ) - id, err := extractUintThenFree(&val) - if err != nil { - return nil, err - } - return &Proposal{ - handle: p.handle, - id: id, - }, nil + return newProposal(p.handle, &val) } // Commit commits the proposal and returns any errors. @@ -101,7 +139,7 @@ func (p *Proposal) Commit() error { // Commit the proposal and return the hash. errVal := C.fwd_commit(p.handle, C.uint32_t(p.id)) - err := extractErrorThenFree(&errVal) + err := errorFromValue(&errVal) if err != nil { // this is unrecoverable due to Rust's ownership model // The underlying proposal is no longer valid. @@ -125,5 +163,5 @@ func (p *Proposal) Drop() error { // Drop the proposal. val := C.fwd_drop_proposal(p.handle, C.uint32_t(p.id)) p.id = 0 - return extractErrorThenFree(&val) + return errorFromValue(&val) } diff --git a/ffi/revision.go b/ffi/revision.go index 20603b6a278b..3d4b53cdc889 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -44,7 +44,7 @@ func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { values, cleanup := newValueFactory() defer cleanup() val := C.fwd_get_from_root(handle, values.from(root), values.from([]byte{})) - _, err := extractBytesThenFree(&val) + _, err := bytesFromValue(&val) if err != nil { // Any error from this function indicates that the root is inaccessible. return nil, errRevisionNotFound @@ -69,7 +69,7 @@ func (r *Revision) Get(key []byte) ([]byte, error) { defer cleanup() val := C.fwd_get_from_root(r.handle, values.from(r.root), values.from(key)) - value, err := extractBytesThenFree(&val) + value, err := bytesFromValue(&val) if err != nil { // Any error from this function indicates that the revision is inaccessible. r.root = nil diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index a8452d2d2f9f..bdcef7ded197 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -340,8 +340,9 @@ fn batch( /// /// # Returns /// -/// A `Value` containing {id, null} if creating the proposal succeeded. -/// A `Value` containing {0, "error message"} if creating the proposal failed. +/// On success, a `Value` containing {len=id, data=hash}. In this case, the +/// hash will always be 32 bytes, and the id will be non-zero. +/// On failure, a `Value` containing {0, "error message"}. /// /// # Safety /// @@ -359,7 +360,7 @@ pub unsafe extern "C" fn fwd_propose_on_db( ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_db(db, nkeys, values).map_or_else(Into::into, Into::into) + propose_on_db(db, nkeys, values).unwrap_or_else(Into::into) } /// Internal call for `fwd_propose_on_db` to remove error handling from the C API @@ -368,7 +369,7 @@ fn propose_on_db( db: *const DatabaseHandle, nkeys: usize, values: *const KeyValue, -) -> Result { +) -> Result { let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; if values.is_null() { return Err(String::from("key-value list is null")); @@ -380,12 +381,21 @@ fn propose_on_db( // Propose the batch of operations. let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; - let proposal_id = next_id(); // Guaranteed to be non-zero + + // Get the root hash of the new proposal. + let mut root_hash: Value = match proposal.root_hash_sync().map_err(|e| e.to_string())? { + Some(root) => Value::from(root.as_slice()), + None => String::new().into(), + }; + + // Store the proposal in the map. We need the write lock instead. + let new_id = next_id(); // Guaranteed to be non-zero db.proposals .write() .map_err(|_| "proposal lock is poisoned")? - .insert(proposal_id, proposal); - Ok(proposal_id) + .insert(new_id, proposal); + root_hash.len = new_id as usize; // Set the length to the proposal ID + Ok(root_hash) } /// Proposes a batch of operations to the database on top of an existing proposal. @@ -399,8 +409,9 @@ fn propose_on_db( /// /// # Returns /// -/// A `Value` containing {id, nil} if creating the proposal succeeded. -/// A `Value` containing {0, "error message"} if creating the proposal failed. +/// On success, a `Value` containing {len=id, data=hash}. In this case, the +/// hash will always be 32 bytes, and the id will be non-zero. +/// On failure, a `Value` containing {0, "error message"}. /// /// # Safety /// @@ -419,7 +430,7 @@ pub unsafe extern "C" fn fwd_propose_on_proposal( ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_proposal(db, proposal_id, nkeys, values).map_or_else(Into::into, Into::into) + propose_on_proposal(db, proposal_id, nkeys, values).unwrap_or_else(Into::into) } /// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API @@ -429,7 +440,7 @@ fn propose_on_proposal( proposal_id: ProposalId, nkeys: usize, values: *const KeyValue, -) -> Result { +) -> Result { let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; if values.is_null() { return Err(String::from("key-value list is null")); @@ -451,13 +462,20 @@ fn propose_on_proposal( let new_proposal = proposal.propose_sync(batch).map_err(|e| e.to_string())?; drop(guard); // Drop the read lock before we get the write lock. + // Get the root hash of the new proposal. + let mut root_hash: Value = match new_proposal.root_hash_sync().map_err(|e| e.to_string())? { + Some(root) => Value::from(root.as_slice()), + None => String::new().into(), + }; + // Store the proposal in the map. We need the write lock instead. let new_id = next_id(); // Guaranteed to be non-zero db.proposals .write() .map_err(|_| "proposal lock is poisoned")? .insert(new_id, new_proposal); - Ok(new_id) + root_hash.len = new_id as usize; // Set the length to the proposal ID + Ok(root_hash) } /// Commits a proposal to the database. @@ -574,13 +592,17 @@ fn hash(db: &Db) -> Result { /// A value returned by the FFI. /// -/// This is used in several different ways: +/// This is used in several different ways, including: +/// * An C-style string. +/// * An ID for a proposal. +/// * A byte slice containing data. +/// +/// For more details on how the data may be stored, refer to the function signature +/// that returned it or the `From` implementations. /// -/// - When returning data, the length is the length of the data and the data is a pointer to the data. -/// - When returning an error, the length is 0 and the data is a null-terminated C-style string. -/// - When returning an ID, the length is the ID and the data is null. +/// The data stored in this struct (if `data` is not null) must be manually freed +/// by the caller using `fwd_free_value`. /// -/// A `Value` with length 0 and a null data pointer indicates that the data was not found. #[derive(Debug)] #[repr(C)] pub struct Value { From bf82c3f73543a6fd265dceb4a75965af1df9d02d Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 2 Jun 2025 12:07:46 -0400 Subject: [PATCH 0737/1053] build(deps): bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#924) --- ffi/tests/go.mod | 2 +- ffi/tests/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ffi/tests/go.mod b/ffi/tests/go.mod index 0de16a30d242..58052fab6ab3 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/go.mod @@ -63,7 +63,7 @@ require ( golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.30.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum index 7197b6cf982a..63dcf5cf65e0 100644 --- a/ffi/tests/go.sum +++ b/ffi/tests/go.sum @@ -729,8 +729,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From efa4a90c19bb247aff80c63089e08cdbd24587d6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 3 Jun 2025 07:37:26 -0700 Subject: [PATCH 0738/1053] fix: Fix empty hash values (#925) --- ffi/firewood.go | 3 +-- ffi/firewood_test.go | 10 ++++------ ffi/src/lib.rs | 42 ++++++++++++++++++++--------------------- firewood/src/db.rs | 17 ++++++++++++++--- firewood/src/manager.rs | 10 +--------- firewood/src/v2/api.rs | 10 ++++++++++ 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 24bf27ef3e9f..67e141208d34 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -197,9 +197,8 @@ func (db *Database) Root() ([]byte, error) { bytes, err := bytesFromValue(&hash) // If the root hash is not found, return a zeroed slice. - if err != nil && strings.Contains(err.Error(), rootHashNotFound) { + if err == nil && bytes == nil { bytes = make([]byte, RootLength) - err = nil } return bytes, err } diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 786622407cae..dfcc069103e7 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -443,18 +443,16 @@ func TestDeleteAll(t *testing.T) { expectedHash, err := hex.DecodeString(emptyRootStr) require.NoError(t, err, "Decode expected empty root hash") - // TODO: Check proposal root - // Requires #918 - // hash, err := proposal.Root() - // require.NoError(t, err, "%T.Root() after commit", proposal) - // require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) + hash, err := proposal.Root() + require.NoError(t, err, "%T.Root() after commit", proposal) + require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) // Commit the proposal. err = proposal.Commit() require.NoError(t, err, "Commit") // Check that the database is empty. - hash, err := db.Root() + hash, err = db.Root() require.NoError(t, err, "%T.Root()", db) require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index bdcef7ded197..e48b44e38e16 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -100,10 +100,9 @@ fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { // Find root hash. // Matches `hash` function but we use the TrieHash type here - let root = db - .root_hash_sync() - .map_err(|e| e.to_string())? - .ok_or_else(|| String::from("unexpected None from db.root_hash_sync"))?; + let Some(root) = db.root_hash_sync().map_err(|e| e.to_string())? else { + return Ok(Value::default()); + }; // Find revision assoicated with root. let rev = db.revision_sync(root).map_err(|e| e.to_string())?; @@ -586,8 +585,8 @@ fn root_hash(db: *const DatabaseHandle) -> Result { fn hash(db: &Db) -> Result { db.root_hash_sync() .map_err(|e| e.to_string())? - .ok_or_else(|| String::from("unexpected None from db.root_hash_sync")) .map(|root| Value::from(root.as_slice())) + .map_or_else(|| Ok(Value::default()), Ok) } /// A value returned by the FFI. @@ -623,6 +622,15 @@ impl Display for Value { } } +impl Default for Value { + fn default() -> Self { + Self { + len: 0, + data: std::ptr::null(), + } + } +} + impl Value { #[must_use] pub const fn as_slice(&self) -> &[u8] { @@ -652,15 +660,13 @@ impl From> for Value { impl From for Value { fn from(s: String) -> Self { if s.is_empty() { - return Value { + Self::default() + } else { + let cstr = CString::new(s).unwrap_or_default().into_raw(); + Value { len: 0, - data: std::ptr::null(), - }; - } - let cstr = CString::new(s).unwrap_or_default().into_raw(); - Value { - len: 0, - data: cstr.cast::(), + data: cstr.cast::(), + } } } } @@ -680,10 +686,7 @@ impl From for Value { impl From<()> for Value { fn from((): ()) -> Self { - Self { - len: 0, - data: std::ptr::null(), - } + Self::default() } } @@ -867,10 +870,7 @@ mod tests { #[test] fn test_invalid_value_display() { - let value = Value { - len: 0, - data: std::ptr::null(), - }; + let value = Value::default(); assert_eq!(format!("{value}"), "[not found]"); } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f7820edb9105..b6aeb78b4dd9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -155,7 +155,7 @@ where } async fn root_hash(&self) -> Result, api::Error> { - Ok(self.manager.read().expect("poisoned lock").root_hash()?) + self.root_hash_sync() } async fn all_hashes(&self) -> Result, api::Error> { @@ -254,7 +254,11 @@ impl Db { /// Synchronously get the root hash of the latest revision. pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(self.manager.read().expect("poisoned lock").root_hash()?) + let hash = self.manager.read().expect("poisoned lock").root_hash()?; + #[cfg(not(feature = "ethhash"))] + return Ok(hash); + #[cfg(feature = "ethhash")] + return Ok(Some(hash.unwrap_or_else(storage::empty_trie_hash))); } /// Synchronously get a revision from a root hash @@ -342,7 +346,14 @@ pub struct Proposal<'p> { impl Proposal<'_> { /// Get the root hash of the proposal synchronously pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(self.nodestore.root_hash()?) + #[cfg(not(feature = "ethhash"))] + return Ok(self.nodestore.root_hash()?); + #[cfg(feature = "ethhash")] + return Ok(Some( + self.nodestore + .root_hash()? + .unwrap_or_else(storage::empty_trie_hash), + )); } } diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index eafa2efb2430..966fa29f739b 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -244,15 +244,7 @@ impl RevisionManager { } pub fn root_hash(&self) -> Result, RevisionManagerError> { - self.current_revision() - .kind - .root_hash() - .or_else(|| self.empty_trie_hash()) - .map(Option::Some) - .ok_or(RevisionManagerError::IO(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Root hash not found", - ))) + Ok(self.current_revision().kind.root_hash()) } pub fn current_revision(&self) -> CommittedRevision { diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index d31acce740eb..5478aaed6d1c 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -175,6 +175,11 @@ pub trait Db { async fn revision(&self, hash: TrieHash) -> Result, Error>; /// Get the hash of the most recently committed version + /// + /// # Note + /// + /// If the database is empty, this will return None, unless the ethhash feature is enabled. + /// In that case, we return the special ethhash compatible empty trie hash. async fn root_hash(&self) -> Result, Error>; /// Get all the hashes available @@ -214,6 +219,11 @@ pub trait DbView { Self: 'a; /// Get the root hash for the current DbView + /// + /// # Note + /// + /// If the database is empty, this will return None, unless the ethhash feature is enabled. + /// In that case, we return the special ethhash compatible empty trie hash. async fn root_hash(&self) -> Result, Error>; /// Get the value of a specific key From a975ace29dd5389bed679aab8878ade58b953f85 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 4 Jun 2025 10:34:47 -0400 Subject: [PATCH 0739/1053] Batch dependabot opentelemetry updates (#913) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- benchmark/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 5061c780ed15..dbe939557974 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -19,10 +19,10 @@ pretty-duration = "0.1.1" env_logger = "0.11.5" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } -fastrace-opentelemetry = { version = "0.10.0" } -opentelemetry-otlp = { version = "0.29.0", features = ["grpc-tonic"] } -opentelemetry = "0.29.0" -opentelemetry_sdk = "0.29.0" +fastrace-opentelemetry = { version = "0.11.0" } +opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } +opentelemetry = "0.30.0" +opentelemetry_sdk = "0.30.0" strum = "0.27.0" [target.'cfg(unix)'.dependencies] From fb1dfc81f53a58b02b7cffb2a56a32b7818c991d Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 4 Jun 2025 11:02:49 -0400 Subject: [PATCH 0740/1053] refactor(ffi): Cleanup unused and duplicate code (#926) --- ffi/firewood.go | 33 ++++---------- ffi/firewood_test.go | 106 ++++++++++++++----------------------------- ffi/kvbackend.go | 14 ------ ffi/memory.go | 19 ++++++++ ffi/proposal.go | 15 +----- 5 files changed, 61 insertions(+), 126 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 67e141208d34..b2c31f1ddbb6 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -113,29 +113,19 @@ func New(filePath string, conf *Config) (*Database, error) { return &Database{handle: db}, nil } -// Batch applies a batch of updates to the database, returning the hash of the +// Update applies a batch of updates to the database, returning the hash of the // root node after the batch is applied. // -// NOTE that if the `Value` is empty, the respective `Key` will be deleted as a -// prefix deletion; i.e. all children will be deleted. -// -// WARNING: a consequence of prefix deletion is that calling Batch with an empty +// WARNING: a consequence of prefix deletion is that calling Update with an empty // key and value will delete the entire database. -func (db *Database) Batch(ops []KeyValue) ([]byte, error) { - // TODO(arr4n) refactor this to require explicit signalling from the caller - // that they want prefix deletion, similar to `rm --no-preserve-root`. +func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { + if db.handle == nil { + return nil, errDBClosed + } - values, cleanup := newValueFactory() + ffiOps, cleanup := createOps(keys, vals) defer cleanup() - ffiOps := make([]C.struct_KeyValue, len(ops)) - for i, op := range ops { - ffiOps[i] = C.struct_KeyValue{ - key: values.from(op.Key), - value: values.from(op.Value), - } - } - hash := C.fwd_batch( db.handle, C.size_t(len(ffiOps)), @@ -149,16 +139,9 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, errDBClosed } - values, cleanup := newValueFactory() + ffiOps, cleanup := createOps(keys, vals) defer cleanup() - ffiOps := make([]C.struct_KeyValue, len(keys)) - for i := range keys { - ffiOps[i] = C.struct_KeyValue{ - key: values.from(keys[i]), - value: values.from(vals[i]), - } - } val := C.fwd_propose_on_db( db.handle, C.size_t(len(ffiOps)), diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index dfcc069103e7..aa1e08448a19 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -147,19 +147,17 @@ func newDatabase(dbFile string) (*Database, func() error, error) { // This doesn't require storing a proposal across the FFI boundary. func TestInsert(t *testing.T) { db := newTestDatabase(t) - const ( - key = "abc" - val = "def" + var ( + key = []byte("abc") + val = []byte("def") ) - _, err := db.Batch([]KeyValue{ - {[]byte(key), []byte(val)}, - }) - require.NoError(t, err, "Batch(%q)", key) + _, err := db.Update([][]byte{key}, [][]byte{val}) + require.NoError(t, err, "Update(%q)", key) - got, err := db.Get([]byte(key)) + got, err := db.Get(key) require.NoErrorf(t, err, "%T.Get(%q)", db, key) - require.Equal(t, val, string(got), "Recover lone batch-inserted value") + require.Equal(t, val, got, "Recover lone batch-inserted value") } func TestClosedDatabase(t *testing.T) { @@ -192,17 +190,21 @@ func valForTest(i int) []byte { return []byte("value" + strconv.Itoa(i)) } -func kvForTest(i int) KeyValue { - return KeyValue{ - Key: keyForTest(i), - Value: valForTest(i), +func kvForTest(num int) ([][]byte, [][]byte) { + keys := make([][]byte, num) + vals := make([][]byte, num) + for i := range keys { + keys[i] = keyForTest(i) + vals[i] = valForTest(i) } + return keys, vals } // Tests that 100 key-value pairs can be inserted and retrieved. -// This happens in two ways: +// This happens in three ways: // 1. By calling [Database.Propose] and then [Proposal.Commit]. // 2. By calling [Database.Update] directly - no proposal storage is needed. +// 3. By calling [Database.Propose] and not committing, which returns a proposal. func TestInsert100(t *testing.T) { type dbView interface { Get(key []byte) ([]byte, error) @@ -252,15 +254,10 @@ func TestInsert100(t *testing.T) { } for _, tt := range tests { + keys, vals := kvForTest(100) t.Run(tt.name, func(t *testing.T) { db := newTestDatabase(t) - keys := make([][]byte, 100) - vals := make([][]byte, 100) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } newDB, err := tt.insert(db, keys, vals) require.NoError(t, err, "inserting") @@ -294,29 +291,22 @@ func TestInsert100(t *testing.T) { // Tests that a range of keys can be deleted. func TestRangeDelete(t *testing.T) { db := newTestDatabase(t) - ops := make([]KeyValue, 100) - for i := range ops { - ops[i] = kvForTest(i) - } - _, err := db.Batch(ops) - require.NoError(t, err, "Batch") + keys, vals := kvForTest(100) + _, err := db.Update(keys, vals) + require.NoError(t, err, "Update") const deletePrefix = 1 - _, err = db.Batch([]KeyValue{{ - Key: keyForTest(deletePrefix), - // delete all keys that start with "key1" - Value: nil, - }}) - require.NoError(t, err, "Batch") - - for _, op := range ops { - got, err := db.Get(op.Key) + _, err = db.Update([][]byte{keyForTest(deletePrefix)}, [][]byte{{}}) + require.NoError(t, err, "Update") + + for i := range keys { + got, err := db.Get(keys[i]) require.NoError(t, err) - if deleted := bytes.HasPrefix(op.Key, keyForTest(deletePrefix)); deleted { + if deleted := bytes.HasPrefix(keys[i], keyForTest(deletePrefix)); deleted { require.NoError(t, err, got) } else { - require.Equal(t, op.Value, got) + require.Equal(t, vals[i], got) } } } @@ -418,12 +408,7 @@ func TestParallelProposals(t *testing.T) { func TestDeleteAll(t *testing.T) { db := newTestDatabase(t) - keys := make([][]byte, 10) - vals := make([][]byte, 10) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(10) // Insert 10 key-value pairs. _, err := db.Update(keys, vals) require.NoError(t, err, "Update") @@ -461,12 +446,7 @@ func TestDropProposal(t *testing.T) { db := newTestDatabase(t) // Create a proposal with 10 keys. - keys := make([][]byte, 10) - vals := make([][]byte, 10) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(10) proposal, err := db.Propose(keys, vals) require.NoError(t, err, "Propose") @@ -577,12 +557,7 @@ func TestDeepPropose(t *testing.T) { const numKeys = 10 const numProposals = 10 proposals := make([]*Proposal, numProposals) - keys := make([][]byte, numKeys*numProposals) - vals := make([][]byte, numKeys*numProposals) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(numKeys * numProposals) for i := range proposals { var ( @@ -680,12 +655,7 @@ func TestProposeSameRoot(t *testing.T) { db := newTestDatabase(t) // Create two chains of proposals, resulting in the same root. - keys := make([][]byte, 10) - vals := make([][]byte, 10) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(10) // Create the first proposal chain. proposal1, err := db.Propose(keys[0:5], vals[0:5]) @@ -747,12 +717,7 @@ func TestProposeSameRoot(t *testing.T) { func TestRevision(t *testing.T) { db := newTestDatabase(t) - keys := make([][]byte, 10) - vals := make([][]byte, 10) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(10) // Create a proposal with 10 key-value pairs. proposal, err := db.Propose(keys, vals) @@ -823,12 +788,7 @@ func TestGetNilCases(t *testing.T) { db := newTestDatabase(t) // Commit 10 key-value pairs. - keys := make([][]byte, 20) - vals := make([][]byte, 20) - for i := range keys { - keys[i] = keyForTest(i) - vals[i] = valForTest(i) - } + keys, vals := kvForTest(20) root, err := db.Update(keys[:10], vals[:10]) require.NoError(t, err, "Update") diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index 6c49a81d4e43..9571a5d7ef94 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -57,17 +57,3 @@ func (db *Database) Commit(_ []byte) error { return nil } - -// Update batches all the keys and values and applies them to the -// database. -func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { - if db.handle == nil { - return nil, errDBClosed - } - - ops := make([]KeyValue, len(keys)) - for i := range keys { - ops[i] = KeyValue{keys[i], vals[i]} - } - return db.Batch(ops) -} diff --git a/ffi/memory.go b/ffi/memory.go index b01eb81e323b..c8d72ceab2f6 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -167,3 +167,22 @@ func (f *valueFactory) from(data []byte) C.struct_Value { f.pin.Pin(ptr) return C.struct_Value{C.size_t(len(data)), ptr} } + +// createOps creates a slice of cgo `KeyValue` structs from the given keys and +// values and pins the memory of the underlying byte slices to prevent +// garbage collection while the cgo function is using them. The returned cleanup +// function MUST be called when the constructed values are no longer required, +// after which they can no longer be used as cgo arguments. +func createOps(keys, vals [][]byte) ([]C.struct_KeyValue, func()) { + values, cleanup := newValueFactory() + + ffiOps := make([]C.struct_KeyValue, len(keys)) + for i := range keys { + ffiOps[i] = C.struct_KeyValue{ + key: values.from(keys[i]), + value: values.from(vals[i]), + } + } + + return ffiOps, cleanup +} diff --git a/ffi/proposal.go b/ffi/proposal.go index bdb4096eb460..5f8c2de968f7 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -101,22 +101,9 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, errDroppedProposal } - ops := make([]KeyValue, len(keys)) - for i := range keys { - ops[i] = KeyValue{keys[i], vals[i]} - } - - values, cleanup := newValueFactory() + ffiOps, cleanup := createOps(keys, vals) defer cleanup() - ffiOps := make([]C.struct_KeyValue, len(ops)) - for i, op := range ops { - ffiOps[i] = C.struct_KeyValue{ - key: values.from(op.Key), - value: values.from(op.Value), - } - } - // Propose the keys and values. val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), C.size_t(len(ffiOps)), From 7d51037fdde48e671415a3f102a85d9198228e2b Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 4 Jun 2025 15:53:03 -0400 Subject: [PATCH 0741/1053] Cleanup ffi unit tests (#932) --- ffi/firewood_test.go | 293 +++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 135 deletions(-) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index aa1e08448a19..0d6a1aed6266 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -121,12 +121,13 @@ func TestMain(m *testing.M) { func newTestDatabase(t *testing.T) *Database { t.Helper() + r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") db, closeDB, err := newDatabase(dbFile) - require.NoError(t, err) + r.NoError(err) t.Cleanup(func() { - require.NoError(t, closeDB()) + r.NoError(closeDB()) }) return db } @@ -143,21 +144,30 @@ func newDatabase(dbFile string) (*Database, func() error, error) { return f, f.Close, nil } -// Tests that a single key-value pair can be inserted and retrieved. -// This doesn't require storing a proposal across the FFI boundary. -func TestInsert(t *testing.T) { +func TestUpdateSingleKV(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) - var ( - key = []byte("abc") - val = []byte("def") - ) + keys, vals := kvForTest(1) + _, err := db.Update(keys, vals) + r.NoError(err) + + got, err := db.Get(keys[0]) + r.NoError(err) + r.Equal(vals[0], got) +} - _, err := db.Update([][]byte{key}, [][]byte{val}) - require.NoError(t, err, "Update(%q)", key) +func TestUpdateMultiKV(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) - got, err := db.Get(key) - require.NoErrorf(t, err, "%T.Get(%q)", db, key) - require.Equal(t, val, got, "Recover lone batch-inserted value") + for i, key := range keys { + got, err := db.Get(key) + r.NoError(err) + r.Equal(vals[i], got) + } } func TestClosedDatabase(t *testing.T) { @@ -256,24 +266,25 @@ func TestInsert100(t *testing.T) { for _, tt := range tests { keys, vals := kvForTest(100) t.Run(tt.name, func(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) newDB, err := tt.insert(db, keys, vals) - require.NoError(t, err, "inserting") + r.NoError(err) for i := range keys { got, err := newDB.Get(keys[i]) - require.NoErrorf(t, err, "%T.Get(%q)", db, keys[i]) + r.NoError(err) // Cast as strings to improve debug messages. want := string(vals[i]) - require.Equal(t, want, string(got), "Recover nth batch-inserted value") + r.Equal(want, string(got)) } hash, err := newDB.Root() - require.NoError(t, err, "%T.Root()", db) + r.NoError(err) rootFromInsert, err := newDB.Root() - require.NoError(t, err, "%T.Root() after insertion", db) + r.NoError(err) // Assert the hash is exactly as expected. Test failure indicates a // non-hash compatible change has been made since the string was set. @@ -281,53 +292,56 @@ func TestInsert100(t *testing.T) { // fix this test. expectedHashHex := expectedRoots[insert100Key] expectedHash, err := hex.DecodeString(expectedHashHex) - require.NoError(t, err, "failed to decode expected hash") - require.Equal(t, expectedHash, hash, "Root hash mismatch.\nExpected (hex): %x\nActual (hex): %x", expectedHash, hash) - require.Equalf(t, rootFromInsert, hash, "%T.Root() matches value returned by insertion", db) + r.NoError(err) + r.Equal(expectedHash, hash, "Root hash mismatch.\nExpected (hex): %x\nActual (hex): %x", expectedHash, hash) + r.Equal(rootFromInsert, hash) }) } } // Tests that a range of keys can be deleted. func TestRangeDelete(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) keys, vals := kvForTest(100) _, err := db.Update(keys, vals) - require.NoError(t, err, "Update") + r.NoError(err) const deletePrefix = 1 _, err = db.Update([][]byte{keyForTest(deletePrefix)}, [][]byte{{}}) - require.NoError(t, err, "Update") + r.NoError(err) for i := range keys { got, err := db.Get(keys[i]) - require.NoError(t, err) + r.NoError(err) if deleted := bytes.HasPrefix(keys[i], keyForTest(deletePrefix)); deleted { - require.NoError(t, err, got) + r.NoError(err) } else { - require.Equal(t, vals[i], got) + r.Equal(vals[i], got) } } } // Tests that the database is empty after creation and doesn't panic. func TestInvariants(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) hash, err := db.Root() - require.NoError(t, err, "%T.Root()", db) + r.NoError(err) emptyRootStr := expectedRoots[emptyKey] expectedHash, err := hex.DecodeString(emptyRootStr) - require.NoError(t, err) - require.Equalf(t, expectedHash, hash, "expected %x, got %x", expectedHash, hash) + r.NoError(err) + r.Equalf(expectedHash, hash, "expected %x, got %x", expectedHash, hash) got, err := db.Get([]byte("non-existent")) - require.NoError(t, err) - require.Emptyf(t, got, "%T.Get([non-existent key])", db) + r.NoError(err) + r.Empty(got) } -func TestParallelProposals(t *testing.T) { +func TestConflictingProposals(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create 10 proposals, each with 10 keys. @@ -342,7 +356,7 @@ func TestParallelProposals(t *testing.T) { vals[j] = valForTest(i*numKeys + j) } proposal, err := db.Propose(keys, vals) - require.NoError(t, err, "Propose(%d)", i) + r.NoError(err) proposals[i] = proposal } @@ -350,26 +364,26 @@ func TestParallelProposals(t *testing.T) { for i, p := range proposals { for j := 0; j < numKeys; j++ { got, err := p.Get(keyForTest(i*numKeys + j)) - require.NoError(t, err, "Get(%d)", i*numKeys+j) - require.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + r.NoError(err) + r.Equal(valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) } } // Commit only the first proposal. err := proposals[0].Commit() - require.NoError(t, err, "Commit(%d)", 0) + r.NoError(err) // Check that the first proposal's keys are present. for j := 0; j < numKeys; j++ { got, err := db.Get(keyForTest(j)) - require.NoError(t, err, "Get(%d)", j) - require.Equal(t, valForTest(j), got, "Get(%d)", j) + r.NoError(err) + r.Equal(valForTest(j), got, "Get(%d)", j) } // Check that the other proposals' keys are not present. for i := 1; i < numProposals; i++ { for j := 0; j < numKeys; j++ { got, err := db.Get(keyForTest(i*numKeys + j)) - require.NoError(t, err, "Get(%d)", i*numKeys+j) - require.Empty(t, got, "Get(%d)", i*numKeys+j) + r.Empty(got, "Get(%d)", i*numKeys+j) + r.NoError(err, "Get(%d)", i*numKeys+j) } } @@ -377,90 +391,92 @@ func TestParallelProposals(t *testing.T) { for i := 1; i < numProposals; i++ { for j := 0; j < numKeys; j++ { got, err := proposals[i].Get(keyForTest(i*numKeys + j)) - require.NoError(t, err, "Get(%d)", i*numKeys+j) - require.Equal(t, valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) + r.NoError(err, "Get(%d)", i*numKeys+j) + r.Equal(valForTest(i*numKeys+j), got, "Get(%d)", i*numKeys+j) } } // Now we ensure we cannot commit the other proposals. for i := 1; i < numProposals; i++ { err := proposals[i].Commit() - require.Contains(t, err.Error(), "commit the parents of this proposal first", "Commit(%d)", i) + r.Contains(err.Error(), "commit the parents of this proposal first", "Commit(%d)", i) } // After attempting to commit the other proposals, they should be completely invalid. for i := 1; i < numProposals; i++ { err := proposals[i].Commit() - require.ErrorIs(t, err, errDroppedProposal, "Commit(%d)", i) + r.ErrorIs(err, errDroppedProposal, "Commit(%d)", i) } // Because they're invalid, we should not be able to get values from them. for i := 1; i < numProposals; i++ { for j := 0; j < numKeys; j++ { got, err := proposals[i].Get(keyForTest(i*numKeys + j)) - require.ErrorIs(t, err, errDroppedProposal, "Get(%d)", i*numKeys+j) - require.Empty(t, got, "Get(%d)", i*numKeys+j) + r.ErrorIs(err, errDroppedProposal, "Get(%d)", i*numKeys+j) + r.Empty(got, "Get(%d)", i*numKeys+j) } } } // Tests that a proposal that deletes all keys can be committed. func TestDeleteAll(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) keys, vals := kvForTest(10) // Insert 10 key-value pairs. _, err := db.Update(keys, vals) - require.NoError(t, err, "Update") + r.NoError(err) // Create a proposal that deletes all keys. proposal, err := db.Propose([][]byte{[]byte("key")}, [][]byte{nil}) - require.NoError(t, err, "Propose") + r.NoError(err) // Check that the proposal doesn't have the keys we just inserted. for i := range keys { got, err := proposal.Get(keys[i]) - require.NoError(t, err, "Get(%d)", i) - require.Empty(t, got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Empty(got, "Get(%d)", i) } emptyRootStr := expectedRoots[emptyKey] expectedHash, err := hex.DecodeString(emptyRootStr) - require.NoError(t, err, "Decode expected empty root hash") + r.NoError(err, "Decode expected empty root hash") hash, err := proposal.Root() - require.NoError(t, err, "%T.Root() after commit", proposal) - require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) + r.NoError(err, "%T.Root() after commit", proposal) + r.Equalf(expectedHash, hash, "%T.Root() of empty trie", db) // Commit the proposal. err = proposal.Commit() - require.NoError(t, err, "Commit") + r.NoError(err, "Commit") // Check that the database is empty. hash, err = db.Root() - require.NoError(t, err, "%T.Root()", db) - require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db) + r.NoError(err, "%T.Root()", db) + r.Equalf(expectedHash, hash, "%T.Root() of empty trie", db) } func TestDropProposal(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create a proposal with 10 keys. keys, vals := kvForTest(10) proposal, err := db.Propose(keys, vals) - require.NoError(t, err, "Propose") + r.NoError(err, "Propose") // Drop the proposal. err = proposal.Drop() - require.NoError(t, err, "Drop") + r.NoError(err) // Check all operations on the dropped proposal. err = proposal.Commit() - require.ErrorIs(t, err, errDroppedProposal, "Commit(dropped proposal)") + r.ErrorIs(err, errDroppedProposal) _, err = proposal.Get([]byte("non-existent")) - require.ErrorIs(t, err, errDroppedProposal, "Get(dropped proposal)") + r.ErrorIs(err, errDroppedProposal) _, err = proposal.Root() - require.ErrorIs(t, err, errDroppedProposal, "Root(dropped proposal)") + r.ErrorIs(err, errDroppedProposal) // Attempt to "emulate" the proposal to ensure it isn't internally available still. proposal = &Proposal{ @@ -470,17 +486,18 @@ func TestDropProposal(t *testing.T) { // Check all operations on the fake proposal. _, err = proposal.Get([]byte("non-existent")) - require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)") + r.Contains(err.Error(), "proposal not found", "Get(fake proposal)") _, err = proposal.Propose([][]byte{[]byte("key")}, [][]byte{[]byte("value")}) - require.Contains(t, err.Error(), "proposal not found", "Propose(fake proposal)") + r.Contains(err.Error(), "proposal not found", "Propose(fake proposal)") err = proposal.Commit() - require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)") + r.Contains(err.Error(), "proposal not found", "Commit(fake proposal)") } // Create a proposal with 10 key-value pairs. // Tests that a proposal can be created from another proposal, and both can be // committed sequentially. func TestProposeFromProposal(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create two sets of keys and values. @@ -499,58 +516,59 @@ func TestProposeFromProposal(t *testing.T) { // Create the first proposal. proposal1, err := db.Propose(keys1, vals1) - require.NoError(t, err, "Propose") + r.NoError(err) // Create the second proposal from the first. proposal2, err := proposal1.Propose(keys2, vals2) - require.NoError(t, err, "Propose") + r.NoError(err) // Assert that the first proposal doesn't have keys from the second. for i := range keys2 { got, err := proposal1.Get(keys2[i]) - require.NoError(t, err, "Get(%d)", i) - require.Empty(t, got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Empty(got, "Get(%d)", i) } // Assert that the second proposal has keys from the first. for i := range keys1 { got, err := proposal2.Get(keys1[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals1[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals1[i], got, "Get(%d)", i) } // Commit the first proposal. err = proposal1.Commit() - require.NoError(t, err, "Commit") + r.NoError(err, "Commit") // Assert that the second proposal has keys from the first and second. for i := range keys1 { got, err := db.Get(keys1[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals1[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals1[i], got, "Get(%d)", i) } for i := range keys2 { got, err := proposal2.Get(keys2[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals2[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals2[i], got, "Get(%d)", i) } // Commit the second proposal. err = proposal2.Commit() - require.NoError(t, err, "Commit") + r.NoError(err) // Assert that the database has keys from both proposals. for i := range keys1 { got, err := db.Get(keys1[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals1[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals1[i], got, "Get(%d)", i) } for i := range keys2 { got, err := db.Get(keys2[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals2[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals2[i], got, "Get(%d)", i) } } func TestDeepPropose(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create a chain of two proposals, each with 10 keys. @@ -566,10 +584,10 @@ func TestDeepPropose(t *testing.T) { ) if i == 0 { p, err = db.Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) - require.NoError(t, err, "Propose(%d)", i) + r.NoError(err, "Propose(%d)", i) } else { p, err = proposals[i-1].Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) - require.NoError(t, err, "Propose(%d)", i) + r.NoError(err, "Propose(%d)", i) } proposals[i] = p } @@ -577,20 +595,20 @@ func TestDeepPropose(t *testing.T) { // Check that each value is present in the final proposal. for i := range keys { got, err := proposals[numProposals-1].Get(keys[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals[i], got, "Get(%d)", i) } // Commit each proposal sequentially, and ensure that the values are // present in the database after each commit. for i := range proposals { err := proposals[i].Commit() - require.NoError(t, err, "Commit(%d)", i) + r.NoError(err, "Commit(%d)", i) for j := i * numKeys; j < (i+1)*numKeys; j++ { got, err := db.Get(keys[j]) - require.NoError(t, err, "Get(%d)", j) - require.Equal(t, vals[j], got, "Get(%d)", j) + r.NoError(err, "Get(%d)", j) + r.Equal(vals[j], got, "Get(%d)", j) } } } @@ -598,6 +616,7 @@ func TestDeepPropose(t *testing.T) { // Tests that dropping a proposal and committing another one still allows // access to the data of children proposals func TestDropProposalAndCommit(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create a chain of three proposals, each with 10 keys. @@ -617,30 +636,30 @@ func TestDropProposalAndCommit(t *testing.T) { ) if i == 0 { p, err = db.Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) - require.NoError(t, err, "Propose(%d)", i) + r.NoError(err, "Propose(%d)", i) } else { p, err = proposals[i-1].Propose(keys[i:(i+1)*numKeys], vals[i:(i+1)*numKeys]) - require.NoError(t, err, "Propose(%d)", i) + r.NoError(err, "Propose(%d)", i) } proposals[i] = p } // drop the second proposal err := proposals[1].Drop() - require.NoError(t, err, "Drop(%d)", 1) + r.NoError(err) // Commit the first proposal err = proposals[0].Commit() - require.NoError(t, err, "Commit(%d)", 0) + r.NoError(err) // Check that the second proposal is dropped _, err = proposals[1].Get(keys[0]) - require.ErrorIs(t, err, errDroppedProposal, "Get(%d)", 0) + r.ErrorIs(err, errDroppedProposal, "Get(%d)", 0) // Check that all keys can be accessed from the final proposal for i := range keys { got, err := proposals[numProposals-1].Get(keys[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, vals[i], got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(vals[i], got, "Get(%d)", i) } } @@ -652,6 +671,7 @@ R1 P2 \- P2 -/ \- P5 */ func TestProposeSameRoot(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create two chains of proposals, resulting in the same root. @@ -659,14 +679,14 @@ func TestProposeSameRoot(t *testing.T) { // Create the first proposal chain. proposal1, err := db.Propose(keys[0:5], vals[0:5]) - require.NoError(t, err, "Propose") + r.NoError(err) proposal3Top, err := proposal1.Propose(keys[5:10], vals[5:10]) - require.NoError(t, err, "Propose") + r.NoError(err) // Create the second proposal chain. proposal2, err := db.Propose(keys[5:10], vals[5:10]) - require.NoError(t, err, "Propose") + r.NoError(err) proposal3Bottom, err := proposal2.Propose(keys[0:5], vals[0:5]) - require.NoError(t, err, "Propose") + r.NoError(err) // Because the proposals are identical, they should have the same root. // Create a unique proposal from each of the two chains. @@ -683,61 +703,62 @@ func TestProposeSameRoot(t *testing.T) { bottomVals[i] = valForTest(i + 20) } proposal4, err := proposal3Top.Propose(topKeys, topVals) - require.NoError(t, err, "Propose") + r.NoError(err) proposal5, err := proposal3Bottom.Propose(bottomKeys, bottomVals) - require.NoError(t, err, "Propose") + r.NoError(err) // Now we will commit the top chain, and check that the bottom chain is still valid. err = proposal1.Commit() - require.NoError(t, err, "Commit") + r.NoError(err) err = proposal3Top.Commit() - require.NoError(t, err, "Commit") + r.NoError(err) // Check that both final proposals are valid. for i := range keys { got, err := proposal4.Get(keys[i]) - require.NoError(t, err, "P4 Get(%d)", i) - require.Equal(t, vals[i], got, "P4 Get(%d)", i) + r.NoError(err, "P4 Get(%d)", i) + r.Equal(vals[i], got, "P4 Get(%d)", i) got, err = proposal5.Get(keys[i]) - require.NoError(t, err, "P5 Get(%d)", i) - require.Equal(t, vals[i], got, "P5 Get(%d)", i) + r.NoError(err, "P5 Get(%d)", i) + r.Equal(vals[i], got, "P5 Get(%d)", i) } // Attempt to commit P5. Since this isn't in the canonical chain, it should // fail. err = proposal5.Commit() - require.Contains(t, err.Error(), "commit the parents of this proposal first", "Commit P5") // this error is internal to firewood + r.Contains(err.Error(), "commit the parents of this proposal first") // this error is internal to firewood // We should be able to commit P4, since it is in the canonical chain. err = proposal4.Commit() - require.NoError(t, err, "Commit P4") + r.NoError(err) } // Tests that an empty revision can be retrieved. func TestRevision(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) keys, vals := kvForTest(10) // Create a proposal with 10 key-value pairs. proposal, err := db.Propose(keys, vals) - require.NoError(t, err, "Propose") + r.NoError(err) // Commit the proposal. - err = proposal.Commit() - require.NoError(t, err, "Commit") + r.NoError(proposal.Commit()) root, err := db.Root() - require.NoError(t, err, "%T.Root()", db) + r.NoError(err) // Create a revision from this root. revision, err := db.Revision(root) - require.NoError(t, err, "Revision") + r.NoError(err) + // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, valForTest(i), got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(valForTest(i), got, "Get(%d)", i) } // Create a second proposal with 10 key-value pairs. @@ -748,55 +769,57 @@ func TestRevision(t *testing.T) { vals2[i] = valForTest(i + 10) } proposal2, err := db.Propose(keys2, vals2) - require.NoError(t, err, "Propose") + r.NoError(err) // Commit the proposal. err = proposal2.Commit() - require.NoError(t, err, "Commit") + r.NoError(err) // Create a "new" revision from the first old root. revision, err = db.Revision(root) - require.NoError(t, err, "Revision") + r.NoError(err) // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) - require.NoError(t, err, "Get(%d)", i) - require.Equal(t, valForTest(i), got, "Get(%d)", i) + r.NoError(err, "Get(%d)", i) + r.Equal(valForTest(i), got, "Get(%d)", i) } } -func TestFakeRevision(t *testing.T) { +func TestInvalidRevision(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Create a nil revision. _, err := db.Revision(nil) - require.ErrorIs(t, err, errInvalidRootLength, "Revision(nil)") + r.ErrorIs(err, errInvalidRootLength) // Create a fake revision with an invalid root. invalidRoot := []byte("not a valid root") _, err = db.Revision(invalidRoot) - require.ErrorIs(t, err, errInvalidRootLength, "Revision(invalid root)") + r.ErrorIs(err, errInvalidRootLength) // Create a fake revision with an valid root. validRoot := []byte("counting 32 bytes to make a hash") - require.Len(t, validRoot, 32, "valid root") + r.Len(validRoot, 32, "valid root") _, err = db.Revision(validRoot) - require.ErrorIs(t, err, errRevisionNotFound, "Revision(valid root)") + r.ErrorIs(err, errRevisionNotFound, "Revision(valid root)") } // Tests that edge case `Get` calls are handled correctly. func TestGetNilCases(t *testing.T) { + r := require.New(t) db := newTestDatabase(t) // Commit 10 key-value pairs. keys, vals := kvForTest(20) root, err := db.Update(keys[:10], vals[:10]) - require.NoError(t, err, "Update") + r.NoError(err) // Create the other views proposal, err := db.Propose(keys[10:], vals[10:]) - require.NoError(t, err, "Propose") + r.NoError(err) revision, err := db.Revision(root) - require.NoError(t, err, "Revision") + r.NoError(err) // Create edge case keys. specialKeys := [][]byte{ @@ -805,15 +828,15 @@ func TestGetNilCases(t *testing.T) { } for _, k := range specialKeys { got, err := db.Get(k) - require.NoError(t, err, "db.Get(%q)", k) - require.Empty(t, got, "db.Get(%q)", k) + r.NoError(err, "db.Get(%q)", k) + r.Empty(got, "db.Get(%q)", k) got, err = revision.Get(k) - require.NoError(t, err, "Revision.Get(%q)", k) - require.Empty(t, got, "Revision.Get(%q)", k) + r.NoError(err, "Revision.Get(%q)", k) + r.Empty(got, "Revision.Get(%q)", k) got, err = proposal.Get(k) - require.NoError(t, err, "Proposal.Get(%q)", k) - require.Empty(t, got, "Proposal.Get(%q)", k) + r.NoError(err, "Proposal.Get(%q)", k) + r.Empty(got, "Proposal.Get(%q)", k) } } From e97b272c044e444455188121bb73c837948aab0b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 4 Jun 2025 16:38:02 -0700 Subject: [PATCH 0742/1053] ci: Require conventional commit format (#933) --- .github/workflows/conventional-commits.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/conventional-commits.yaml diff --git a/.github/workflows/conventional-commits.yaml b/.github/workflows/conventional-commits.yaml new file mode 100644 index 000000000000..3dd61fdb655a --- /dev/null +++ b/.github/workflows/conventional-commits.yaml @@ -0,0 +1,16 @@ +name: Conventional Commits + +on: + pull_request: + branches: [ main ] + +jobs: + build: + name: Conventional Commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: webiny/action-conventional-commits@v1.3.0 + with: + allowed-commit-types: "build,chore,ci,docs,feat,fix,perf,refactor,style,test" From 5f99b2fed925ec0d8937498d35072b34624d253c Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:02:20 -0400 Subject: [PATCH 0743/1053] Remove duplicated comments in nodestore.rs (#931) Signed-off-by: Suyan Qu <36519575+qusuyan@users.noreply.github.com> --- storage/src/nodestore.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 0d3febc77a1a..5c72ad9afa26 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -36,24 +36,6 @@ use std::fmt::Debug; /// I --> |commit|N("New commit NodeStore<Committed, S>") /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` -/// -/// Nodestores represent a revision of the trie. There are three types of nodestores: -/// - Committed: A committed revision of the trie. It has no in-memory changes. -/// - MutableProposal: A proposal that is still being modified. It has some nodes in memory. -/// - ImmutableProposal: A proposal that has been hashed and assigned addresses. It has no in-memory changes. -/// -/// The general lifecycle of nodestores is as follows: -/// ```mermaid -/// flowchart TD -/// subgraph subgraph["Committed Revisions"] -/// L("Latest Nodestore<Committed, S>") --- |...|O("Oldest NodeStore<Committed, S>") -/// end -/// O --> E("Expire") -/// L --> |start propose|M("NodeStore<ProposedMutable, S>") -/// M --> |finish propose + hash|I("NodeStore<ProposedImmutable, S>") -/// I --> |commit|N("New commit NodeStore<Committed, S>") -/// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF -/// ``` use std::io::{Error, ErrorKind, Write}; use std::mem::{offset_of, take}; use std::num::NonZeroU64; From e88d73645ff9da59c2e9c02449992f344fabdcc0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 5 Jun 2025 08:29:36 -0700 Subject: [PATCH 0744/1053] chore: Bump to v0.5.0 (#934) --- .github/check-license-headers.yaml | 6 +- .github/workflows/ci.yaml | 1 + CHANGELOG.md | 262 +++++++++++++++++++++++++++++ RELEASE.md | 9 + benchmark/Cargo.toml | 2 +- cliff.toml | 106 ++++++++++++ ffi/Cargo.toml | 2 +- firewood/Cargo.toml | 6 +- fwdctl/Cargo.toml | 4 +- storage/Cargo.toml | 2 +- 10 files changed, 390 insertions(+), 10 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 cliff.toml diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 6ef82b5db0fb..9a605a5d627a 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -13,13 +13,14 @@ "**/README*", "Cargo.toml", "*/Cargo.toml", - "libaio/**", "docs/**", "CODEOWNERS", "CONTRIBUTING.md", "benchmark/**", "ffi/**", "triehash/**", + "CHANGELOG.md", + "cliff.toml", ], "license": "./.github/license-header.txt" }, @@ -34,13 +35,14 @@ "**/README*", "Cargo.toml", "*/Cargo.toml", - "libaio/**", "docs/**", "benchmark/**", "ffi/**", "CODEOWNERS", "CONTRIBUTING.md", "triehash/**", + "CHANGELOG.md", + "cliff.toml", ], } ] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 926375b05c70..65270d37d250 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -153,6 +153,7 @@ jobs: globs: | *.md **/*.md + !CHANGELOG.md !target/** - name: doc generation run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000000..549ca112addc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,262 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.0.5] - 2025-06-05 + +### 🚀 Features + +- *(ffi)* Ffi error messages (#860) +- *(ffi)* Proposal creation isolated from committing (#867) +- *(ffi)* Get values from proposals (#877) +- *(ffi)* Full proposal support (#878) +- *(ffi)* Support `Get` for historical revisions (#881) +- *(ffi)* Add proposal root retrieval (#910) + +### 🐛 Bug Fixes + +- *(ffi)* Prevent memory leak and tips for finding leaks (#862) +- *(src)* Drop unused revisions (#866) +- *(ffi)* Clarify roles of `Value` extractors (#875) +- *(ffi)* Check revision is available (#890) +- *(ffi)* Prevent undefined behavior on empty slices (#894) +- Fix empty hash values (#925) + +### 💼 Other + +- *(deps)* Update pprof requirement from 0.12.1 to 0.13.0 (#283) +- *(deps)* Update lru requirement from 0.11.0 to 0.12.0 (#306) +- *(deps)* Update typed-builder requirement from 0.16.0 to 0.17.0 (#320) +- *(deps)* Update typed-builder requirement from 0.17.0 to 0.18.0 (#324) +- Remove dead code (#333) +- Kv\_dump should be done with the iterator (#347) +- Add remaining lint checks (#397) +- Finish error handler mapper (#421) +- Switch from EmptyDB to Db (#422) +- *(deps)* Update aquamarine requirement from 0.3.1 to 0.4.0 (#434) +- *(deps)* Update serial\_test requirement from 2.0.0 to 3.0.0 (#477) +- *(deps)* Update aquamarine requirement from 0.4.0 to 0.5.0 (#496) +- *(deps)* Update env\_logger requirement from 0.10.1 to 0.11.0 (#502) +- *(deps)* Update tonic-build requirement from 0.10.2 to 0.11.0 (#522) +- *(deps)* Update tonic requirement from 0.10.2 to 0.11.0 (#523) +- *(deps)* Update nix requirement from 0.27.1 to 0.28.0 (#563) +- Move clippy pragma closer to usage (#578) +- *(deps)* Update typed-builder requirement from 0.18.1 to 0.19.1 (#684) +- *(deps)* Update lru requirement from 0.8.0 to 0.12.4 (#708) +- *(deps)* Update typed-builder requirement from 0.19.1 to 0.20.0 (#711) +- *(deps)* Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#715) +- Insert truncated trie +- Allow for trace and no logging +- Add read\_for\_update +- Revision history should never grow +- Use a more random hash +- Use smallvec to optimize for 16 byte values +- *(deps)* Update aquamarine requirement from 0.5.0 to 0.6.0 (#727) +- *(deps)* Update thiserror requirement from 1.0.57 to 2.0.3 (#751) +- *(deps)* Update pprof requirement from 0.13.0 to 0.14.0 (#750) +- *(deps)* Update metrics-util requirement from 0.18.0 to 0.19.0 (#765) +- *(deps)* Update cbindgen requirement from 0.27.0 to 0.28.0 (#767) +- *(deps)* Update bitfield requirement from 0.17.0 to 0.18.1 (#772) +- *(deps)* Update lru requirement from 0.12.4 to 0.13.0 (#771) +- *(deps)* Update bitfield requirement from 0.18.1 to 0.19.0 (#801) +- *(deps)* Update typed-builder requirement from 0.20.0 to 0.21.0 (#815) +- *(deps)* Update tonic requirement from 0.12.1 to 0.13.0 (#826) +- *(deps)* Update opentelemetry requirement from 0.28.0 to 0.29.0 (#816) +- *(deps)* Update lru requirement from 0.13.0 to 0.14.0 (#840) +- *(deps)* Update metrics-exporter-prometheus requirement from 0.16.1 to 0.17.0 (#853) +- *(deps)* Update rand requirement from 0.8.5 to 0.9.1 (#850) +- *(deps)* Update pprof requirement from 0.14.0 to 0.15.0 (#906) +- *(deps)* Update cbindgen requirement from 0.28.0 to 0.29.0 (#899) +- *(deps)* Update criterion requirement from 0.5.1 to 0.6.0 (#898) +- *(deps)* Bump golang.org/x/crypto from 0.17.0 to 0.35.0 in /ffi/tests (#907) +- *(deps)* Bump google.golang.org/protobuf from 1.27.1 to 1.33.0 /ffi/tests (#923) +- *(deps)* Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#924) + +### 🚜 Refactor + +- *(ffi)* Cleanup unused and duplicate code (#926) + +### 📚 Documentation + +- *(ffi)* Remove private declarations from public docs (#874) + +### 🧪 Testing + +- *(ffi/tests)* Basic eth compatibility (#825) +- *(ethhash)* Use libevm (#900) + +### ⚙️ Miscellaneous Tasks + +- Use `decode` in single key proof verification (#295) +- Use `decode` in range proof verification (#303) +- Naming the elements of `ExtNode` (#305) +- Remove the getter pattern over `ExtNode` (#310) +- Proof cleanup (#316) +- *(ffi/tests)* Update go-ethereum v1.15.7 (#838) +- *(ffi)* Fix typo fwd\_close\_db comment (#843) +- *(ffi)* Add linter (#893) + +## [0.0.4] - 2023-09-27 + +### 🚀 Features + +- Identify a revision with root hash (#126) +- Supports chains of `StoreRevMut` (#175) +- Add proposal (#181) + +### 🐛 Bug Fixes + +- Update release to cargo-workspace-version (#75) + +### 💼 Other + +- *(deps)* Update criterion requirement from 0.4.0 to 0.5.1 (#96) +- *(deps)* Update enum-as-inner requirement from 0.5.1 to 0.6.0 (#107) +- :position FTW? (#140) +- *(deps)* Update indexmap requirement from 1.9.1 to 2.0.0 (#147) +- *(deps)* Update pprof requirement from 0.11.1 to 0.12.0 (#152) +- *(deps)* Update typed-builder requirement from 0.14.0 to 0.15.0 (#153) +- *(deps)* Update lru requirement from 0.10.0 to 0.11.0 (#155) +- Update hash fn to root\_hash (#170) +- Remove generics on Db (#196) +- Remove generics for Proposal (#197) +- Use quotes around all (#200) +- :get: use Nibbles (#210) +- Variable renames (#211) +- Use thiserror (#221) +- *(deps)* Update typed-builder requirement from 0.15.0 to 0.16.0 (#222) +- *(deps)* Update tonic-build requirement from 0.9.2 to 0.10.0 (#247) +- *(deps)* Update prost requirement from 0.11.9 to 0.12.0 (#246) + +### ⚙️ Miscellaneous Tasks + +- Refactor `rev.rs` (#74) +- Disable `test\_buffer\_with\_redo` (#128) +- Verify concurrent committing write batches (#172) +- Remove redundant code (#174) +- Remove unused clone for `StoreRevMutDelta` (#178) +- Abstract out mutable store creation (#176) +- Proposal test cleanup (#184) +- Add comments for `Proposal` (#186) +- Deprecate `WriteBatch` and use `Proposal` instead (#188) +- Inline doc clean up (#240) +- Remove unused blob in db (#245) +- Add license header to firewood files (#262) +- Revert back `test\_proof` changes accidentally changed (#279) + +## [0.0.3] - 2023-04-28 + +### 💼 Other + +- Move benching to criterion (#61) +- Refactor file operations to use a Path (#26) +- Fix panic get\_item on a dirty write (#66) +- Improve error handling (#70) + +### 🧪 Testing + +- Speed up slow unit tests (#58) + +### ⚙️ Miscellaneous Tasks + +- Add backtrace to e2e tests (#59) + +## [0.0.2] - 2023-04-21 + +### 💼 Other + +- Fix test flake (#44) + +### 📚 Documentation + +- Add release notes (#27) +- Update CODEOWNERS (#28) +- Add badges to README (#33) + +## [0.0.1] - 2023-04-14 + +### 🐛 Bug Fixes + +- Clippy linting +- Specificy --lib in rustdoc linters +- Unset the pre calculated RLP values of interval nodes +- Run cargo clippy --fix +- Handle empty key value proof arguments as an error +- Tweak repo organization (#130) +- Run clippy --fix across all workspaces (#149) +- Update StoreError to use thiserror (#156) +- Update db::new() to accept a Path (#187) +- Use bytemuck instead of unsafe in growth-ring (#185) +- Update firewood sub-projects (#16) + +### 💼 Other + +- Fix additional clippy warnings +- Additional clippy fixes +- Fix additional clippy warnings +- Fix outstanding lint issues +- *(deps)* Update nix requirement from 0.25.0 to 0.26.1 +- Update version to 0.0.1 +- Add usage examples +- Add fwdctl create command +- Add fwdctl README and test +- Fix flag arguments; add fwdctl documentation +- Add logger +- Use log-level flag for setting logging level +- *(deps)* Update lru requirement from 0.8.0 to 0.9.0 +- Add generic key value insertion command +- Add get command +- Add delete command +- Move cli tests under tests/ +- Only use kv\_ functions in fwdctl +- Fix implementation and add tests +- Add exit codes and stderr error logging +- Add tests +- Add serial library for testing purposes +- Add root command +- Add dump command +- Fixup root tests to be serial +- *(deps)* Update typed-builder requirement from 0.11.0 to 0.12.0 +- Add VSCode +- Update merkle\_utils to return Results +- Fixup command UX to be positional +- Update firewood to match needed functionality +- Update DB and Merkle errors to implement the Error trait +- Update proof errors +- Add StdError trait to ProofError +- *(deps)* Update nix requirement from 0.25.0 to 0.26.2 +- *(deps)* Update lru requirement from 0.8.0 to 0.10.0 +- *(deps)* Update typed-builder requirement from 0.12.0 to 0.13.0 +- *(deps)* Update typed-builder requirement from 0.13.0 to 0.14.0 (#144) +- Update create\_file to return a Result (#150) +- *(deps)* Update predicates requirement from 2.1.1 to 3.0.1 (#154) +- Add new library crate (#158) +- *(deps)* Update serial\_test requirement from 1.0.0 to 2.0.0 (#173) +- Refactor kv\_remove to be more ergonomic (#168) +- Add e2e test (#167) +- Use eth and proof feature gates across all API surfaces. (#181) +- Add license header to firewood source code (#189) + +### 📚 Documentation + +- Add link to fwdctl README in main README +- Update fwdctl README with storage information +- Update fwdctl README with more examples +- Document get\_revisions function with additional information. (#177) +- Add alpha warning to firewood README (#191) + +### 🧪 Testing + +- Add more range proof tests +- Update tests to use Results +- Re-enable integration tests after introduce cargo workspaces + +### ⚙️ Miscellaneous Tasks + +- Add release and publish GH Actions +- Update batch sizes in ci e2e job +- Add docs linter to strengthen firewood documentation +- Clippy should fail in case of warnings (#151) +- Fail in case of error publishing firewood crate (#21) + + diff --git a/RELEASE.md b/RELEASE.md index cedeaa9c3af4..4f1e7ce4e627 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -25,3 +25,12 @@ more information on the tool. To trigger a release, simply push a semver-compatible tag to the main branch, for example `v0.0.5`. The CI will automatically publish a draft release which consists of release notes and changes. + +## Changelog + +To build the changelog, see git-cliff.org. Short version: + +```sh +cargo install git-cliff +git cliff --tag v0.0.5 | sed -e 's/_/\\_/g' > CHANGELOG.md +``` diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index dbe939557974..a343aaf46586 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "benchmark" -version = "0.1.0" +version = "0.0.5" edition = "2024" rust-version = "1.85.0" diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 000000000000..236198f1b8dc --- /dev/null +++ b/cliff.toml @@ -0,0 +1,106 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + + +[changelog] +# A Tera template to be rendered as the changelog's footer. +# See https://keats.github.io/tera/docs/#introduction +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# A Tera template to be rendered for each release in the changelog. +# See https://keats.github.io/tera/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# A Tera template to be rendered as the changelog's footer. +# See https://keats.github.io/tera/docs/#introduction +footer = """ + +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# Render body even when there are no releases to process. +render_always = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder with a URL. + #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] +# render body even when there are no releases to process +# render_always = true +# output file path +# output = "test.md" + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = true +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = true +# Require all commits to be conventional. +# Takes precedence over filter_unconventional. +require_conventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit message using https://github.com/crate-ci/typos. + # If the spelling is incorrect, it will be fixed automatically. + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^feat", group = "🚀 Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🚜 Refactor" }, + { message = "^style", group = "🎨 Styling" }, + { message = "^test", group = "🧪 Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, + { message = ".*", group = "💼 Other" }, +] +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# An array of link parsers for extracting external references, and turning them into URLs, using regex. +link_parsers = [] +# Include only the tags that belong to the current branch. +use_branch_tags = true +# Order releases topologically instead of chronologically. +topo_order = false +# Order releases topologically instead of chronologically. +topo_order_commits = true +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "oldest" +# Process submodules commits +recurse_submodules = false +# Only process tags in this pattern +tag_pattern = "v[0-9].*" + diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index a9deb7a8b644..d275483b9a94 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-ffi" -version = "0.1.0" +version = "0.0.5" edition = "2024" rust-version = "1.85.0" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 18850ba745c4..f5af9b5ffef4 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.4" +version = "0.0.5" edition = "2024" authors = [ "Ted Yin (@Determinant) ", @@ -66,7 +66,7 @@ explicit_deref_methods = "warn" missing_const_for_fn = "warn" [target.'cfg(target_os = "linux")'.dependencies] -storage = { version = "0.0.4", path = "../storage", features = ["io-uring"] } +storage = { path = "../storage", features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] -storage = { version = "0.0.4", path = "../storage" } +storage = { path = "../storage" } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index f3317bb6de15..59b45f7a316c 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "fwdctl" -version = "0.0.4" +version = "0.0.5" edition = "2024" rust-version = "1.85.0" [dependencies] -firewood = { version = "0.0.4", path = "../firewood" } +firewood = { version = "0.0.5", path = "../firewood" } clap = { version = "4.5.0", features = ["cargo", "derive"] } env_logger = "0.11.2" log = "0.4.20" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 9c4be78f5af9..53f81a3ea5ec 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storage" -version = "0.0.4" +version = "0.0.5" edition = "2024" rust-version = "1.85.0" From fbb0609c4418242775bdeb42e23f73ffdf463bb5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 5 Jun 2025 08:50:41 -0700 Subject: [PATCH 0745/1053] ci: Upgrade actions/checkout (#939) --- .github/workflows/cache-cleanup.yaml | 2 +- .github/workflows/conventional-commits.yaml | 2 +- .github/workflows/default-branch-cache.yaml | 2 +- .github/workflows/gh-pages.yaml | 2 +- .github/workflows/release.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cache-cleanup.yaml b/.github/workflows/cache-cleanup.yaml index 045241e13455..43461d14dfdc 100644 --- a/.github/workflows/cache-cleanup.yaml +++ b/.github/workflows/cache-cleanup.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cleanup run: | diff --git a/.github/workflows/conventional-commits.yaml b/.github/workflows/conventional-commits.yaml index 3dd61fdb655a..9b3c6d5fd017 100644 --- a/.github/workflows/conventional-commits.yaml +++ b/.github/workflows/conventional-commits.yaml @@ -9,7 +9,7 @@ jobs: name: Conventional Commits runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: webiny/action-conventional-commits@v1.3.0 with: diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index f39b461f47b0..f365104d32e7 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 with: diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index 7343aab88c91..ad5e073c67da 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -13,7 +13,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: arduino/setup-protoc@v3 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e5d9c8f3f7a3..547bd1a582c5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Release uses: softprops/action-gh-release@v1 with: From 90083ce017037a72f6f7b3bc286e7a549d077d71 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 5 Jun 2025 09:40:36 -0700 Subject: [PATCH 0746/1053] fix: Use saturating subtraction for metrics counter (#937) --- ffi/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e48b44e38e16..3342ae7d50f6 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -324,7 +324,8 @@ fn batch( // Get the root hash of the database post-commit. let propose_plus_commit_time = start.elapsed().as_millis(); counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); - counter!("firewood.ffi.commit_ms").increment(propose_plus_commit_time - propose_time); + counter!("firewood.ffi.commit_ms") + .increment(propose_plus_commit_time.saturating_sub(propose_time)); counter!("firewood.ffi.batch").increment(1); Ok(hash_val) } From 5125d5eac6094b8e0058cba2b3cb8c805dc25edc Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 6 Jun 2025 11:18:51 -0700 Subject: [PATCH 0747/1053] feat: Improve error handling and add sync iterator (#941) --- firewood/src/db.rs | 33 ++-- firewood/src/manager.rs | 19 +-- firewood/src/merkle.rs | 99 +++++++----- firewood/src/proof.rs | 5 +- firewood/src/stream.rs | 142 ++++++++++++----- firewood/src/v2/api.rs | 13 +- storage/src/lib.rs | 2 +- storage/src/linear/filebacked.rs | 53 +++++-- storage/src/linear/memory.rs | 8 +- storage/src/linear/mod.rs | 114 +++++++++++++- storage/src/node/mod.rs | 2 +- storage/src/nodestore.rs | 253 ++++++++++++++++++++++--------- 12 files changed, 539 insertions(+), 204 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index b6aeb78b4dd9..a63b05dfff19 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -11,38 +11,23 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; use metrics::{counter, describe_counter}; -use std::error::Error; -use std::fmt; use std::io::Write; use std::path::Path; use std::sync::{Arc, RwLock}; -use storage::{Committed, FileBacked, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash}; +use storage::{ + Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, +}; +use thiserror::Error; use typed_builder::TypedBuilder; -#[derive(Debug)] -#[non_exhaustive] +#[derive(Error, Debug)] /// Represents the different types of errors that can occur in the database. pub enum DbError { - /// I/O error occurred. - IO(std::io::Error), -} - -impl fmt::Display for DbError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DbError::IO(e) => write!(f, "I/O error: {e:?}"), - } - } + /// I/O error + #[error("I/O error: {0:?}")] + FileIo(#[from] FileIoError), } -impl From for DbError { - fn from(e: std::io::Error) -> Self { - DbError::IO(e) - } -} - -impl Error for DbError {} - type HistoricalRev = NodeStore; /// Metrics for the database. @@ -79,7 +64,7 @@ impl api::DbView for HistoricalRev { Self: 'a; async fn root_hash(&self) -> Result, api::Error> { - HashedNodeReader::root_hash(self).map_err(api::Error::IO) + HashedNodeReader::root_hash(self).map_err(api::Error::FileIO) } async fn val(&self, key: K) -> Result>, api::Error> { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 966fa29f739b..9badb5ac772e 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -2,7 +2,6 @@ // See the file LICENSE.md for licensing terms. use std::collections::{HashMap, VecDeque}; -use std::io::Error; use std::num::NonZero; use std::path::PathBuf; use std::sync::Arc; @@ -17,7 +16,10 @@ use crate::merkle::Merkle; use crate::v2::api::HashKey; pub use storage::CacheReadStrategy; -use storage::{Committed, FileBacked, ImmutableProposal, NodeStore, Parentable, TrieHash}; +use storage::{ + Committed, FileBacked, FileIoError, ImmutableProposal, NodeStore, Parentable, TrieHash, +}; + #[derive(Clone, Debug, TypedBuilder)] /// Revision manager configuratoin pub struct RevisionManagerConfig { @@ -61,8 +63,10 @@ pub(crate) enum RevisionManagerError { "The proposal cannot be committed since it is not a direct child of the most recent commit" )] NotLatest, + #[error("Revision not found")] + RevisionNotFound, #[error("An IO error occurred during the commit")] - IO(#[from] std::io::Error), + FileIoError(#[from] FileIoError), } impl RevisionManager { @@ -70,7 +74,7 @@ impl RevisionManager { filename: PathBuf, truncate: bool, config: RevisionManagerConfig, - ) -> Result { + ) -> Result { let storage = Arc::new(FileBacked::new( filename, config.node_cache_size, @@ -221,7 +225,7 @@ impl RevisionManager { if trace_enabled() { let _merkle = Merkle::from(committed); - trace!("{}", _merkle.dump()?); + trace!("{}", _merkle.dump().expect("failed to dump merkle")); } Ok(()) @@ -237,10 +241,7 @@ impl RevisionManager { self.by_hash .get(&root_hash) .cloned() - .ok_or(RevisionManagerError::IO(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Revision not found", - ))) + .ok_or(RevisionManagerError::RevisionNotFound) } pub fn root_hash(&self) -> Result, RevisionManagerError> { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e978e6d6cb91..0db92d1e1b39 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -15,9 +15,9 @@ use std::iter::once; use std::num::NonZeroUsize; use std::sync::Arc; use storage::{ - BranchNode, Child, HashType, Hashable, HashedNodeReader, ImmutableProposal, LeafNode, - LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, - SharedNode, TrieReader, ValueDigest, + BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, + LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, + ReadableStorage, SharedNode, TrieReader, ValueDigest, }; /// Keys are boxed u8 slices @@ -54,19 +54,24 @@ macro_rules! write_attributes { " pp={}", nibbles_formatter($node.partial_path.0.clone()) ) - .map_err(|e| Error::other(e))?; + .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; } if !$value.is_empty() { match std::str::from_utf8($value) { Ok(string) if string.chars().all(char::is_alphanumeric) => { - write!($writer, " val={}", string).map_err(|e| Error::other(e))? + write!($writer, " val={}", string) + .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; } _ => { let hex = hex::encode($value); if hex.len() > 6 { - write!($writer, " val={:.6}...", hex).map_err(|e| Error::other(e))?; + write!($writer, " val={:.6}...", hex).map_err(|e| { + FileIoError::from_generic_no_file(e, "write attributes") + })?; } else { - write!($writer, " val={}", hex).map_err(|e| Error::other(e))?; + write!($writer, " val={}", hex).map_err(|e| { + FileIoError::from_generic_no_file(e, "write attributes") + })?; } } } @@ -79,7 +84,7 @@ fn get_helper( nodestore: &T, node: &Node, key: &[u8], -) -> Result, Error> { +) -> Result, FileIoError> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -147,7 +152,7 @@ impl Merkle { &self.nodestore } - fn read_node(&self, addr: LinearAddress) -> Result { + fn read_node(&self, addr: LinearAddress) -> Result { self.nodestore.read_node(addr) } @@ -206,7 +211,10 @@ impl Merkle { todo!() } - pub(crate) fn path_iter<'a>(&self, key: &'a [u8]) -> Result, Error> { + pub(crate) fn path_iter<'a>( + &self, + key: &'a [u8], + ) -> Result, FileIoError> { PathIterator::new(&self.nodestore, key) } @@ -314,14 +322,14 @@ impl Merkle { }) } - pub(crate) fn get_value(&self, key: &[u8]) -> Result>, Error> { + pub(crate) fn get_value(&self, key: &[u8]) -> Result>, FileIoError> { let Some(node) = self.get_node(key)? else { return Ok(None); }; Ok(node.value().map(|v| v.to_vec().into_boxed_slice())) } - pub(crate) fn get_node(&self, key: &[u8]) -> Result, Error> { + pub(crate) fn get_node(&self, key: &[u8]) -> Result, FileIoError> { let Some(root) = self.root() else { return Ok(None); }; @@ -338,16 +346,21 @@ impl Merkle { hash: Option<&HashType>, seen: &mut HashSet, writer: &mut dyn Write, - ) -> Result<(), Error> { - write!(writer, " {addr}[label=\"addr:{addr:?}").map_err(Error::other)?; + ) -> Result<(), FileIoError> { + write!(writer, " {addr}[label=\"addr:{addr:?}") + .map_err(Error::other) + .map_err(|e| FileIoError::new(e, None, 0, None))?; if let Some(hash) = hash { - write!(writer, " hash:{hash:.6?}...").map_err(Error::other)?; + write!(writer, " hash:{hash:.6?}...") + .map_err(Error::other) + .map_err(|e| FileIoError::new(e, None, 0, None))?; } match &*self.read_node(addr)? { Node::Branch(b) => { write_attributes!(writer, b, &b.value.clone().unwrap_or(Box::from([]))); - writeln!(writer, "\"]").map_err(Error::other)?; + writeln!(writer, "\"]") + .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; for (childidx, child) in b.children.iter().enumerate() { let (child_addr, child_hash) = match child { None => continue, @@ -363,17 +376,18 @@ impl Merkle { writer, " {addr} -> {child_addr}[label=\"{childidx} (dup)\" color=red]" ) - .map_err(Error::other)?; + .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; } else { writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]") - .map_err(Error::other)?; + .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; self.dump_node(child_addr, child_hash, seen, writer)?; } } } Node::Leaf(l) => { write_attributes!(writer, l, &l.value); - writeln!(writer, "\" shape=rect]").map_err(Error::other)?; + writeln!(writer, "\" shape=rect]") + .map_err(|e| FileIoError::from_generic_no_file(e, "write leaf"))?; } }; Ok(()) @@ -382,15 +396,26 @@ impl Merkle { pub(crate) fn dump(&self) -> Result { let mut result = String::new(); writeln!(result, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; - if let Some((root_addr, root_hash)) = self.nodestore.root_address_and_hash()? { - writeln!(result, " root -> {root_addr}").map_err(Error::other)?; + if let Some((root_addr, root_hash)) = self + .nodestore + .root_address_and_hash() + .expect("failed to get root address and hash") + { + writeln!(result, " root -> {root_addr}") + .map_err(Error::other) + .map_err(|e| FileIoError::new(e, None, 0, None)) + .map_err(Error::other)?; let mut seen = HashSet::new(); // If ethhash is off, root_hash.into() is already the correct type // so we disable the warning here #[allow(clippy::useless_conversion)] - self.dump_node(root_addr, Some(&root_hash.into()), &mut seen, &mut result)?; + self.dump_node(root_addr, Some(&root_hash.into()), &mut seen, &mut result) + .map_err(Error::other)?; } - write!(result, "}}").map_err(Error::other)?; + write!(result, "}}") + .map_err(Error::other) + .map_err(|e| FileIoError::new(e, None, 0, None)) + .map_err(Error::other)?; Ok(result) } @@ -399,7 +424,7 @@ impl Merkle { impl TryFrom>> for Merkle, S>> { - type Error = std::io::Error; + type Error = FileIoError; fn try_from(m: Merkle>) -> Result { Ok(Merkle { nodestore: m.nodestore.try_into()?, @@ -417,7 +442,7 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. - pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), Error> { + pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -446,7 +471,7 @@ impl Merkle> { mut node: Node, key: &[u8], value: Box<[u8]>, - ) -> Result { + ) -> Result { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -578,7 +603,7 @@ impl Merkle> { /// Returns the value that was removed, if any. /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. - pub fn remove(&mut self, key: &[u8]) -> Result>, Error> { + pub fn remove(&mut self, key: &[u8]) -> Result>, FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -608,7 +633,7 @@ impl Merkle> { &mut self, mut node: Node, key: &[u8], - ) -> Result<(Option, Option>), Error> { + ) -> Result<(Option, Option>), FileIoError> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) @@ -803,7 +828,7 @@ impl Merkle> { /// Removes any key-value pairs with keys that have the given `prefix`. /// Returns the number of key-value pairs removed. - pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { + pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); let root = self.nodestore.mut_root(); @@ -826,7 +851,7 @@ impl Merkle> { mut node: Node, key: &[u8], deleted: &mut usize, - ) -> Result, Error> { + ) -> Result, FileIoError> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key`, in which case we need to delete this node and all its children. // 2. The key is above the node (i.e. its ancestor), so the parent needs to be restructured (TODO). @@ -954,7 +979,7 @@ impl Merkle> { &mut self, branch: &mut BranchNode, deleted: &mut usize, - ) -> Result<(), Error> { + ) -> Result<(), FileIoError> { if branch.value.is_some() { // a KV pair was in the branch itself *deleted += 1; @@ -1709,7 +1734,7 @@ mod tests { fn merkle_build_test, V: AsRef<[u8]>>( items: Vec<(K, V)>, - ) -> Result>, Error> { + ) -> Result>, FileIoError> { let nodestore = NodeStore::new_empty_proposal(MemStore::new(vec![]).into()); let mut merkle = Merkle::from(nodestore); for (k, v) in items { @@ -1878,7 +1903,7 @@ mod tests { } #[test] - fn test_root_hash_fuzz_insertions() -> Result<(), Error> { + fn test_root_hash_fuzz_insertions() -> Result<(), FileIoError> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -1948,7 +1973,7 @@ mod tests { #[test] #[allow(clippy::unwrap_used)] - fn test_root_hash_reversed_deletions() -> Result<(), Error> { + fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); @@ -1989,20 +2014,20 @@ mod tests { .nodestore .root_address_and_hash()? .map(|(_, hash)| hash); - hashes.push((root_hash, merkle.dump()?)); + hashes.push((root_hash, merkle.dump().unwrap())); merkle.insert(k, v.clone())?; } let mut new_hashes = Vec::new(); for (k, _) in items.iter().rev() { - let before = merkle.dump()?; + let before = merkle.dump().unwrap(); merkle.remove(k)?; let root_hash = merkle .nodestore .root_address_and_hash()? .map(|(_, hash)| hash); - new_hashes.push((root_hash, k, before, merkle.dump()?)); + new_hashes.push((root_hash, k, before, merkle.dump().unwrap())); } hashes.reverse(); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8f627c8bd00e..053c87e8bec5 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -3,7 +3,8 @@ use sha2::{Digest, Sha256}; use storage::{ - BranchNode, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, + BranchNode, FileIoError, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, + ValueDigest, }; use thiserror::Error; @@ -56,7 +57,7 @@ pub enum ProofError { /// Error from the merkle package #[error("{0:?}")] - IO(#[from] std::io::Error), + IO(#[from] FileIoError), /// Empty range #[error("empty range")] diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 134f9745bc64..2434c8e22f72 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -9,7 +9,9 @@ use futures::{Stream, StreamExt}; use std::cmp::Ordering; use std::iter::once; use std::task::Poll; -use storage::{BranchNode, Child, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader}; +use storage::{ + BranchNode, Child, FileIoError, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader, +}; /// Represents an ongoing iteration over a node and its children. enum IterationNode { @@ -90,23 +92,19 @@ impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { merkle, } } -} - -impl Stream for MerkleNodeStream<'_, T> { - type Item = Result<(Key, SharedNode), api::Error>; - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll> { - // destructuring is necessary here because we need mutable access to `state` - // at the same time as immutable access to `merkle`. + /// Internal function that handles the core iteration logic shared between Iterator and Stream implementations. + /// Returns None when iteration is complete, or Some(Result) with either a node or an error. + fn next_internal(&mut self) -> Option> { let Self { state, merkle } = &mut *self; match state { NodeStreamState::StartFromKey(key) => { - self.state = get_iterator_intial_state(*merkle, key)?; - self.poll_next(_cx) + match get_iterator_intial_state(*merkle, key) { + Ok(state) => self.state = state, + Err(e) => return Some(Err(e)), + } + self.next_internal() } NodeStreamState::Iterating { iter_stack } => { while let Some(mut iter_node) = iter_stack.pop() { @@ -126,7 +124,7 @@ impl Stream for MerkleNodeStream<'_, T> { } let key = key_from_nibble_iter(key.iter().copied()); - return Poll::Ready(Some(Ok((key, node)))); + return Some(Ok((key, node))); } IterationNode::Visited { ref key, @@ -139,7 +137,10 @@ impl Stream for MerkleNodeStream<'_, T> { }; let child = match child { - Child::AddressWithHash(addr, _) => merkle.read_node(addr)?, + Child::AddressWithHash(addr, _) => match merkle.read_node(addr) { + Ok(node) => node, + Err(e) => return Some(Err(e)), + }, Child::Node(node) => node.clone().into(), }; @@ -162,21 +163,43 @@ impl Stream for MerkleNodeStream<'_, T> { key: child_key, node: child, }); - return self.poll_next(_cx); + return self.next_internal(); } } } - Poll::Ready(None) + None } } } } +impl Stream for MerkleNodeStream<'_, T> { + type Item = Result<(Key, SharedNode), FileIoError>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll> { + match self.next_internal() { + Some(result) => Poll::Ready(Some(result)), + None => Poll::Ready(None), + } + } +} + +impl Iterator for MerkleNodeStream<'_, T> { + type Item = Result<(Key, SharedNode), FileIoError>; + + fn next(&mut self) -> Option { + self.next_internal() + } +} + /// Returns the initial state for an iterator over the given `merkle` which starts at `key`. fn get_iterator_intial_state( merkle: &T, key: &[u8], -) -> Result { +) -> Result { let Some(root) = merkle.root_node() else { // This merkle is empty. return Ok(NodeStreamState::Iterating { iter_stack: vec![] }); @@ -352,7 +375,7 @@ impl Stream for MerkleKeyValueStream<'_, T> { Poll::Ready(Some(Ok((key, value)))) } }, - Some(Err(e)) => Poll::Ready(Some(Err(e))), + Some(Err(e)) => Poll::Ready(Some(Err(e.into()))), None => Poll::Ready(None), }, Poll::Pending => Poll::Pending, @@ -390,7 +413,7 @@ pub struct PathIterator<'a, 'b, T> { } impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { - pub(super) fn new(merkle: &'a T, key: &'b [u8]) -> Result { + pub(super) fn new(merkle: &'a T, key: &'b [u8]) -> Result { let Some(root) = merkle.root_node() else { return Ok(Self { state: PathIteratorState::Exhausted, @@ -410,7 +433,7 @@ impl<'a, 'b, T: TrieReader> PathIterator<'a, 'b, T> { } impl Iterator for PathIterator<'_, '_, T> { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { // destructuring is necessary here because we need mutable access to `state` @@ -766,7 +789,10 @@ mod tests { let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); assert_eq!(node.as_leaf().unwrap().value.to_vec(), vec![0x00]); @@ -824,35 +850,53 @@ mod tests { let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); // Covers case of branch with no value - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert!(node.value.is_none()); // Covers case of branch with value - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); // Covers case of leaf with partial path - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]); // Covers case of leaf with no partial path - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]); @@ -869,7 +913,10 @@ mod tests { vec![0x00, 0x00, 0x01].into_boxed_slice(), ); - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -877,7 +924,10 @@ mod tests { ); // Covers case of leaf with no partial path - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -896,7 +946,10 @@ mod tests { vec![0x00, 0xD0, 0xD0].into_boxed_slice(), ); - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -904,7 +957,10 @@ mod tests { ); // Covers case of leaf with no partial path - let (key, node) = stream.next().await.unwrap().unwrap(); + let (key, node) = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -944,7 +1000,7 @@ mod tests { // we iterate twice because we should get a None then start over #[expect(clippy::indexing_slicing)] for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { - let next = stream.next().await.map(|kv| { + let next = futures::StreamExt::next(&mut stream).await.map(|kv| { let (k, v) = kv.unwrap(); assert_eq!(&*k, &*v); k @@ -986,7 +1042,10 @@ mod tests { let expected_value = vec![i, j]; assert_eq!( - stream.next().await.unwrap().unwrap(), + futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(), (expected_key.into_boxed_slice(), expected_value), "i: {}, j: {}", i, @@ -1003,7 +1062,10 @@ mod tests { let expected_key = vec![i, j]; let expected_value = vec![i, j]; assert_eq!( - stream.next().await.unwrap().unwrap(), + futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(), (expected_key.into_boxed_slice(), expected_value), "i: {}, j: {}", i, @@ -1014,7 +1076,10 @@ mod tests { check_stream_is_done(stream).await; } else { assert_eq!( - stream.next().await.unwrap().unwrap(), + futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(), (vec![i + 1, 0].into_boxed_slice(), vec![i + 1, 0]), "i: {}", i, @@ -1045,7 +1110,10 @@ mod tests { let mut stream = merkle.key_value_iter(); for kv in key_values.iter() { - let next = stream.next().await.unwrap().unwrap(); + let next = futures::StreamExt::next(&mut stream) + .await + .unwrap() + .unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&next.1, kv); } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 5478aaed6d1c..2aea7508128d 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use futures::Stream; use std::fmt::Debug; use std::sync::Arc; -use storage::TrieHash; +use storage::{FileIoError, TrieHash}; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with @@ -107,6 +107,10 @@ pub enum Error { /// An IO error occurred IO(#[from] std::io::Error), + #[error("File IO error: {0}")] + /// A file I/O error occurred + FileIO(#[from] FileIoError), + /// Cannot commit a cloned proposal /// /// Cloned proposals are problematic because if they are committed, then you could @@ -142,13 +146,18 @@ pub enum Error { /// Proof error #[error("proof error")] ProofError(#[from] ProofError), + + /// Revision not found + #[error("revision not found")] + RevisionNotFound, } impl From for Error { fn from(err: RevisionManagerError) -> Self { match err { - RevisionManagerError::IO(io_err) => Error::IO(io_err), + RevisionManagerError::FileIoError(io_err) => Error::FileIO(io_err), RevisionManagerError::NotLatest => Error::NotLatest, + RevisionManagerError::RevisionNotFound => Error::RevisionNotFound, } } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 4f3148b451b0..a3d7f1c1a197 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -23,7 +23,7 @@ pub mod logger; // re-export these so callers don't need to know where they are pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; -pub use linear::{ReadableStorage, WritableStorage}; +pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; pub use node::{BranchNode, Child, LeafNode, Node, PathIterItem, branch::HashType}; pub use nodestore::{ diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 80f8bd215d45..cb1cd4c6b2f8 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -7,7 +7,7 @@ // read/write operations at once use std::fs::{File, OpenOptions}; -use std::io::{Error, Read}; +use std::io::Read; use std::num::NonZero; #[cfg(unix)] use std::os::unix::fs::FileExt; @@ -21,11 +21,12 @@ use metrics::counter; use crate::{CacheReadStrategy, LinearAddress, SharedNode}; -use super::{ReadableStorage, WritableStorage}; +use super::{FileIoError, ReadableStorage, WritableStorage}; -/// A [ReadableStorage] backed by a file +/// A [ReadableStorage] and [WritableStorage] backed by a file pub struct FileBacked { fd: File, + filename: PathBuf, cache: Mutex>, free_list_cache: Mutex>>, cache_read_strategy: CacheReadStrategy, @@ -40,6 +41,7 @@ impl std::fmt::Debug for FileBacked { .field("fd", &self.fd) .field("cache", &self.cache) .field("free_list_cache", &self.free_list_cache) + .field("cache_read_strategy", &self.cache_read_strategy) .finish() } } @@ -73,13 +75,19 @@ impl FileBacked { free_list_cache_size: NonZero, truncate: bool, cache_read_strategy: CacheReadStrategy, - ) -> Result { + ) -> Result { let fd = OpenOptions::new() .read(true) .write(true) .create(true) .truncate(truncate) - .open(path)?; + .open(&path) + .map_err(|inner| FileIoError { + inner, + filename: Some(path.clone()), + offset: 0, + context: Some("file open".to_string()), + })?; #[cfg(feature = "io-uring")] let ring = { @@ -94,7 +102,13 @@ impl FileBacked { .setup_cqsize(FileBacked::RINGSIZE * 2) // start a kernel thread to do the IO .setup_sqpoll(IDLETIME_MS) - .build(FileBacked::RINGSIZE)? + .build(FileBacked::RINGSIZE) + .map_err(|e| FileIoError { + inner: e, + filename: Some(path.clone()), + offset: 0, + context: Some("IO-uring setup".to_string()), + })? }; Ok(Self { @@ -102,6 +116,7 @@ impl FileBacked { cache: Mutex::new(LruCache::new(node_cache_size)), free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)), cache_read_strategy, + filename: path, #[cfg(feature = "io-uring")] ring: ring.into(), }) @@ -109,12 +124,16 @@ impl FileBacked { } impl ReadableStorage for FileBacked { - fn stream_from(&self, addr: u64) -> Result, Error> { + fn stream_from(&self, addr: u64) -> Result, FileIoError> { Ok(Box::new(PredictiveReader::new(self, addr))) } - fn size(&self) -> Result { - Ok(self.fd.metadata()?.len()) + fn size(&self) -> Result { + Ok(self + .fd + .metadata() + .map_err(|e| self.file_io_error(e, 0, Some("size".to_string())))? + .len()) } fn read_cached_node(&self, addr: LinearAddress, mode: &'static str) -> Option { @@ -153,24 +172,32 @@ impl ReadableStorage for FileBacked { } } } + + fn filename(&self) -> Option { + Some(self.filename.clone()) + } } impl WritableStorage for FileBacked { - fn write(&self, offset: u64, object: &[u8]) -> Result { + fn write(&self, offset: u64, object: &[u8]) -> Result { #[cfg(unix)] { - self.fd.write_at(object, offset) + self.fd + .write_at(object, offset) + .map_err(|e| self.file_io_error(e, offset, Some("write".to_string()))) } #[cfg(windows)] { - self.fd.seek_write(object, offset) + self.fd + .seek_write(object, offset) + .map_err(|e| self.file_io_error(e, offset, Some("write".to_string()))) } } fn write_cached_nodes<'a>( &self, nodes: impl Iterator, &'a SharedNode)>, - ) -> Result<(), Error> { + ) -> Result<(), FileIoError> { let mut guard = self.cache.lock().expect("poisoned lock"); for (addr, node) in nodes { guard.put(*addr, node.clone()); diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index 5bce44dcb731..bb3931f7830a 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::{ReadableStorage, WritableStorage}; +use super::{FileIoError, ReadableStorage, WritableStorage}; use std::io::{Cursor, Read}; use std::sync::Mutex; @@ -21,7 +21,7 @@ impl MemStore { } impl WritableStorage for MemStore { - fn write(&self, offset: u64, object: &[u8]) -> Result { + fn write(&self, offset: u64, object: &[u8]) -> Result { let offset = offset as usize; let mut guard = self.bytes.lock().expect("poisoned lock"); if offset + object.len() > guard.len() { @@ -33,7 +33,7 @@ impl WritableStorage for MemStore { } impl ReadableStorage for MemStore { - fn stream_from(&self, addr: u64) -> Result, std::io::Error> { + fn stream_from(&self, addr: u64) -> Result, FileIoError> { let bytes = self .bytes .lock() @@ -45,7 +45,7 @@ impl ReadableStorage for MemStore { Ok(Box::new(Cursor::new(bytes))) } - fn size(&self) -> Result { + fn size(&self) -> Result { Ok(self.bytes.lock().expect("poisoned lock").len() as u64) } } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 309bf636bedb..f8a532b0d302 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -15,13 +15,97 @@ //! Each type is described in more detail below. use std::fmt::Debug; -use std::io::{Error, Read}; +use std::io::Read; use std::num::NonZero; +use std::ops::Deref; +use std::path::PathBuf; use crate::{CacheReadStrategy, LinearAddress, SharedNode}; pub(super) mod filebacked; pub mod memory; +/// An error that occurs when reading or writing to a [ReadableStorage] or [WritableStorage] +/// +/// This error is used to wrap errors that occur when reading or writing to a file. +/// It contains the filename, offset, and context of the error. +#[derive(Debug)] +pub struct FileIoError { + inner: std::io::Error, + filename: Option, + offset: u64, + context: Option, +} + +impl FileIoError { + /// Create a new [FileIoError] from a generic error + /// + /// Only use this constructor if you do not have any file or line information. + /// + /// # Arguments + /// + /// * `error` - The error to wrap + pub fn from_generic_no_file(error: T, context: &str) -> Self { + Self { + inner: std::io::Error::other(error.to_string()), + filename: None, + offset: 0, + context: Some(context.into()), + } + } + + /// Create a new [FileIoError] + /// + /// # Arguments + /// + /// * `inner` - The inner error + /// * `filename` - The filename of the file that caused the error + /// * `offset` - The offset of the file that caused the error + /// * `context` - The context of this error + pub fn new( + inner: std::io::Error, + filename: Option, + offset: u64, + context: Option, + ) -> Self { + Self { + inner, + filename, + offset, + context, + } + } +} + +impl std::error::Error for FileIoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.inner) + } +} + +impl std::fmt::Display for FileIoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{inner} at offset {offset} of file '{filename}' {context}", + inner = self.inner, + offset = self.offset, + filename = self + .filename + .as_ref() + .unwrap_or(&PathBuf::from("[unknown]")) + .display(), + context = self.context.as_ref().unwrap_or(&String::from("")) + ) + } +} + +impl Deref for FileIoError { + type Target = std::io::Error; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} /// Trait for readable storage. pub trait ReadableStorage: Debug + Sync + Send { /// Stream data from the specified address. @@ -33,10 +117,10 @@ pub trait ReadableStorage: Debug + Sync + Send { /// # Returns /// /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. - fn stream_from(&self, addr: u64) -> Result, Error>; + fn stream_from(&self, addr: u64) -> Result, FileIoError>; /// Return the size of the underlying storage, in bytes - fn size(&self) -> Result; + fn size(&self) -> Result; /// Read a node from the cache (if any) fn read_cached_node(&self, _addr: LinearAddress, _mode: &'static str) -> Option { @@ -55,6 +139,26 @@ pub trait ReadableStorage: Debug + Sync + Send { /// Cache a node for future reads fn cache_node(&self, _addr: LinearAddress, _node: SharedNode) {} + + /// Return the filename of the underlying storage + fn filename(&self) -> Option { + None + } + + /// Convert an io::Error into a FileIoError + fn file_io_error( + &self, + error: std::io::Error, + offset: u64, + context: Option, + ) -> FileIoError { + FileIoError { + inner: error, + filename: self.filename(), + offset, + context, + } + } } /// Trait for writable storage. @@ -69,13 +173,13 @@ pub trait WritableStorage: ReadableStorage { /// # Returns /// /// The number of bytes written, or an error if the write operation fails. - fn write(&self, offset: u64, object: &[u8]) -> Result; + fn write(&self, offset: u64, object: &[u8]) -> Result; /// Write all nodes to the cache (if any) fn write_cached_nodes<'a>( &self, _nodes: impl Iterator, &'a SharedNode)>, - ) -> Result<(), Error> { + ) -> Result<(), FileIoError> { Ok(()) } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 49660a0798ab..a92875cdd045 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -324,7 +324,7 @@ impl Node { } /// Given a reader, return a [Node] from those bytes - pub fn from_reader(mut serialized: impl Read) -> Result { + pub fn from_reader(mut serialized: impl Read) -> Result { let mut first_byte: [u8; 1] = [0]; serialized.read_exact(&mut first_byte)?; match first_byte[0] { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 5c72ad9afa26..45d6cbec2dfd 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::linear::FileIoError; use crate::logger::trace; use arc_swap::ArcSwap; use arc_swap::access::DynAccess; @@ -183,19 +184,31 @@ struct StoredArea { } impl NodeStore { - /// Returns (index, area_size) for the [StoredArea] at `addr`. - /// `index` is the index of `area_size` in [AREA_SIZES]. - fn area_index_and_size(&self, addr: LinearAddress) -> Result<(AreaIndex, u64), Error> { + /// Returns (index, area_size) for the stored area at `addr`. + /// `index` is the index of `area_size` in the array of valid block sizes. + pub fn area_index_and_size( + &self, + addr: LinearAddress, + ) -> Result<(AreaIndex, u64), FileIoError> { let mut area_stream = self.storage.stream_from(addr.get())?; let index: AreaIndex = serializer() .deserialize_from(&mut area_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - let size = *AREA_SIZES.get(index as usize).ok_or(Error::new( - ErrorKind::InvalidData, - format!("Invalid area size index {}", index), - ))?; + .map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("deserialize".to_string()), + ) + })?; + + let size = *AREA_SIZES + .get(index as usize) + .ok_or(self.storage.file_io_error( + Error::other(format!("Invalid area size index {index}")), + addr.get(), + None, + ))?; Ok((index, size)) } @@ -206,7 +219,7 @@ impl NodeStore { &self, addr: LinearAddress, mode: &'static str, - ) -> Result { + ) -> Result { if let Some(node) = self.storage.read_cached_node(addr, mode) { return Ok(node); } @@ -218,7 +231,12 @@ impl NodeStore { let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); let area_stream = self.storage.stream_from(actual_addr)?; - let node: SharedNode = Node::from_reader(area_stream)?.into(); + let node: SharedNode = Node::from_reader(area_stream) + .map_err(|e| { + self.storage + .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) + })? + .into(); match self.storage.cache_read_strategy() { CacheReadStrategy::All => { self.storage.cache_node(addr, node.clone()); @@ -232,52 +250,109 @@ impl NodeStore { } Ok(node) } + + /// Read a [Node] from the provided [LinearAddress] and size. + /// This is an uncached read, primarily used by check utilities + pub fn uncached_read_node_and_size( + &self, + addr: LinearAddress, + ) -> Result<(SharedNode, u8), FileIoError> { + let mut area_stream = self.storage.stream_from(addr.get())?; + let mut size = [0u8]; + area_stream.read_exact(&mut size).map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("uncached_read_node_and_size".to_string()), + ) + })?; + self.storage.stream_from(addr.get() + 1)?; + let node: SharedNode = Node::from_reader(area_stream) + .map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("uncached_read_node_and_size".to_string()), + ) + })? + .into(); + Ok((node, size[0])) + } + + /// Get a reference to the header of this nodestore + pub fn header(&self) -> &NodeStoreHeader { + &self.header + } + + /// Get the size of an area index (used by the checker) + pub fn size_from_area_index(&self, index: AreaIndex) -> u64 { + AREA_SIZES[index as usize] + } } impl NodeStore { /// Open an existing [NodeStore] /// Assumes the header is written in the [ReadableStorage]. - pub fn open(storage: Arc) -> Result { + pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; let mut header = NodeStoreHeader::new(); let header_bytes = bytemuck::bytes_of_mut(&mut header); - stream.read_exact(header_bytes)?; + stream + .read_exact(header_bytes) + .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; drop(stream); if header.version != Version::new() { - return Err(Error::new( - ErrorKind::InvalidData, - "Incompatible firewood version", + return Err(storage.file_io_error( + Error::new(ErrorKind::InvalidData, "Incompatible firewood version"), + 0, + Some("header read".to_string()), )); } if header.endian_test != 1 { - return Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in endianness", + return Err(storage.file_io_error( + Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in endianness", + ), + 0, + Some("header read".to_string()), )); } if header.area_size_hash != area_size_hash().as_slice() { - return Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in area size hash", + return Err(storage.file_io_error( + Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in area size hash", + ), + 0, + Some("header read".to_string()), )); } #[cfg(not(feature = "ethhash"))] if header.ethhash != 0 { - return Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created with ethhash enabled", + return Err(storage.file_io_error( + Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created with ethhash enabled", + ), + 0, + Some("header read".to_string()), )); } #[cfg(feature = "ethhash")] if header.ethhash != 1 { - return Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created without ethhash enabled", + return Err(storage.file_io_error( + Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created without ethhash enabled", + ), + 0, + Some("header read".to_string()), )); } @@ -301,7 +376,7 @@ impl NodeStore { /// Create a new, empty, Committed [NodeStore] and clobber /// the underlying store with an empty freelist and no root node - pub fn new_empty_committed(storage: Arc) -> Result { + pub fn new_empty_committed(storage: Arc) -> Result { let header = NodeStoreHeader::new(); Ok(Self { @@ -340,7 +415,7 @@ impl Parentable for Arc { impl NodeStore, S> { /// When an immutable proposal commits, we need to reparent any proposal that /// has the committed proposal as it's parent - pub fn commit_reparent(&self, other: &Arc, S>>) -> bool { + pub fn commit_reparent(&self, other: &Arc, S>>) { match *other.kind.parent.load() { NodeStoreParent::Proposed(ref parent) => { if Arc::ptr_eq(&self.kind, parent) { @@ -348,12 +423,9 @@ impl NodeStore, S> { .kind .parent .store(NodeStoreParent::Committed(self.kind.root_hash()).into()); - true - } else { - false } } - NodeStoreParent::Committed(_) => false, + NodeStoreParent::Committed(_) => {} } } } @@ -371,7 +443,7 @@ impl NodeStore { /// Create a new MutableProposal [NodeStore] from a parent [NodeStore] pub fn new( parent: Arc>, - ) -> Result { + ) -> Result { let mut deleted: Vec<_> = Default::default(); let root = if let Some(root_addr) = parent.header.root_address { deleted.push(root_addr); @@ -401,7 +473,7 @@ impl NodeStore { /// Reads a node for update, marking it as deleted in this proposal. /// We get an arc from cache (reading it from disk if necessary) then /// copy/clone the node and return it. - pub fn read_for_update(&mut self, addr: LinearAddress) -> Result { + pub fn read_for_update(&mut self, addr: LinearAddress) -> Result { self.delete_node(addr); let arc_wrapped_node = self.read_node(addr)?; Ok((*arc_wrapped_node).clone()) @@ -440,9 +512,15 @@ impl NodeStore, S> { /// and the index of the free list that was used. /// If there are no free areas big enough for `n` bytes, returns None. /// TODO danlaine: If we return a larger area than requested, we should split it. - fn allocate_from_freed(&mut self, n: u64) -> Result, Error> { + fn allocate_from_freed( + &mut self, + n: u64, + ) -> Result, FileIoError> { // Find the smallest free list that can fit this size. - let index_wanted = area_size_to_index(n)?; + let index_wanted = area_size_to_index(n).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("allocate_from_freed".to_string())) + })?; if let Some((index, free_stored_area_addr)) = self .header @@ -464,15 +542,22 @@ impl NodeStore, S> { let free_head_stream = self.storage.stream_from(free_area_addr)?; let free_head: StoredArea> = serializer() .deserialize_from(free_head_stream) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + .map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + free_area_addr, + Some("allocate_from_freed".to_string()), + ) + })?; let StoredArea { area: Area::Free(free_head), area_size_index: read_index, } = free_head else { - return Err(Error::new( - ErrorKind::InvalidData, - "Attempted to read a non-free area", + return Err(self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), + free_area_addr, + Some("allocate_from_freed".to_string()), )); }; debug_assert_eq!(read_index as usize, index); @@ -500,8 +585,11 @@ impl NodeStore, S> { Ok(None) } - fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), Error> { - let index = area_size_to_index(n)?; + fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { + let index = area_size_to_index(n).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("allocate_from_end".to_string())) + })?; let area_size = AREA_SIZES[index as usize]; let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); self.header.size += area_size; @@ -520,7 +608,10 @@ impl NodeStore, S> { /// Returns an address that can be used to store the given `node` and updates /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. /// Also returns the index of the free list the node was allocated from. - pub fn allocate_node(&mut self, node: &Node) -> Result<(LinearAddress, AreaIndex), Error> { + pub fn allocate_node( + &mut self, + node: &Node, + ) -> Result<(LinearAddress, AreaIndex), FileIoError> { let stored_area_size = Self::stored_len(node); // Attempt to allocate from a free list. @@ -539,7 +630,7 @@ impl NodeStore { /// Deletes the [Node] at the given address, updating the next pointer at /// the given addr, and changing the header of this committed nodestore to /// have the address on the freelist - pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), Error> { + pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), FileIoError> { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; @@ -558,9 +649,13 @@ impl NodeStore { area, }; - let stored_area_bytes = serializer() - .serialize(&stored_area) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let stored_area_bytes = serializer().serialize(&stored_area).map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("delete_node".to_string()), + ) + })?; self.storage.write(addr.into(), &stored_area_bytes)?; @@ -617,7 +712,7 @@ pub type FreeLists = [Option; NUM_AREA_SIZES]; /// The [NodeStoreHeader] is at the start of the ReadableStorage. #[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] #[repr(C)] -struct NodeStoreHeader { +pub struct NodeStoreHeader { /// Identifies the version of firewood used to create this [NodeStore]. version: Version, /// always "1"; verifies endianness @@ -659,6 +754,11 @@ impl NodeStoreHeader { ethhash: 0, } } + + // return the size of this nodestore + pub fn size(&self) -> u64 { + self.size + } } /// A [FreeArea] is stored at the start of the area that contained a node that @@ -671,10 +771,10 @@ struct FreeArea { /// Reads from an immutable (i.e. already hashed) merkle trie. pub trait HashedNodeReader: TrieReader { /// Gets the address and hash of the root node of an immutable merkle trie. - fn root_address_and_hash(&self) -> Result, Error>; + fn root_address_and_hash(&self) -> Result, FileIoError>; /// Gets the hash of the root node of an immutable merkle trie. - fn root_hash(&self) -> Result, Error> { + fn root_hash(&self) -> Result, FileIoError> { Ok(self.root_address_and_hash()?.map(|(_, hash)| hash)) } } @@ -686,7 +786,7 @@ impl TrieReader for T where T: NodeReader + RootReader {} /// Reads nodes from a merkle trie. pub trait NodeReader { /// Returns the node at `addr`. - fn read_node(&self, addr: LinearAddress) -> Result; + fn read_node(&self, addr: LinearAddress) -> Result; } impl NodeReader for T @@ -694,7 +794,7 @@ where T: Deref, T::Target: NodeReader, { - fn read_node(&self, addr: LinearAddress) -> Result { + fn read_node(&self, addr: LinearAddress) -> Result { self.deref().read_node(addr) } } @@ -902,7 +1002,7 @@ impl NodeStore, S> { path_prefix: &mut Path, new_nodes: &mut HashMap, #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, - ) -> Result<(LinearAddress, HashType), Error> { + ) -> Result<(LinearAddress, HashType), FileIoError> { // If this is a branch, find all unhashed children and recursively call hash_helper on them. trace!("hashing {node:?} at {path_prefix:?}"); if let Node::Branch(ref mut b) = node { @@ -1028,7 +1128,7 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. - pub fn flush_header(&self) -> Result<(), Error> { + pub fn flush_header(&self) -> Result<(), FileIoError> { let header_bytes = bytemuck::bytes_of(&self.header); self.storage.write(0, header_bytes)?; Ok(()) @@ -1036,7 +1136,7 @@ impl NodeStore { /// Persist the header, including all the padding /// This is only done the first time we write the header - pub fn flush_header_with_padding(&self) -> Result<(), Error> { + pub fn flush_header_with_padding(&self) -> Result<(), FileIoError> { let header_bytes = bytemuck::bytes_of(&self.header) .iter() .copied() @@ -1052,7 +1152,7 @@ impl NodeStore { impl NodeStore, FileBacked> { /// Persist the freelist from this proposal to storage. #[fastrace::trace(short_name = true)] - pub fn flush_freelist(&self) -> Result<(), Error> { + pub fn flush_freelist(&self) -> Result<(), FileIoError> { // Write the free lists to storage let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; @@ -1063,7 +1163,7 @@ impl NodeStore, FileBacked> { /// Persist all the nodes of a proposal to storage. #[fastrace::trace(short_name = true)] #[cfg(not(feature = "io-uring"))] - pub fn flush_nodes(&self) -> Result<(), Error> { + pub fn flush_nodes(&self) -> Result<(), FileIoError> { let flush_start = Instant::now(); for (addr, (area_size_index, node)) in self.kind.new.iter() { @@ -1085,7 +1185,7 @@ impl NodeStore, FileBacked> { /// Persist all the nodes of a proposal to storage. #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] - pub fn flush_nodes(&self) -> Result<(), Error> { + pub fn flush_nodes(&self) -> Result<(), FileIoError> { const RINGSIZE: usize = FileBacked::RINGSIZE as usize; let flush_start = Instant::now(); @@ -1117,7 +1217,13 @@ impl NodeStore, FileBacked> { // and not marking it !busy until the kernel has said it's done below. #[expect(unsafe_code)] while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { - ring.submitter().squeue_wait()?; + ring.submitter().squeue_wait().map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("io-uring squeue_wait".to_string()), + ) + })?; trace!("submission queue is full"); counter!("ring.full").increment(1); } @@ -1126,7 +1232,10 @@ impl NodeStore, FileBacked> { // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation // to complete, then handle the completion queue counter!("ring.full").increment(1); - ring.submit_and_wait(1)?; + ring.submit_and_wait(1).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("io-uring submit_and_wait".to_string())) + })?; let completion_queue = ring.completion(); trace!("competion queue length: {}", completion_queue.len()); for entry in completion_queue { @@ -1142,7 +1251,10 @@ impl NodeStore, FileBacked> { .iter() .filter(|(busy, _)| *busy) .count(); - ring.submit_and_wait(pending)?; + ring.submit_and_wait(pending).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) + })?; for entry in ring.completion() { let item = entry.user_data() as usize; @@ -1183,7 +1295,7 @@ impl NodeStore, FileBacked> { impl TryFrom> for NodeStore, S> { - type Error = std::io::Error; + type Error = FileIoError; fn try_from(val: NodeStore) -> Result { let NodeStore { @@ -1233,7 +1345,7 @@ impl TryFrom> } impl NodeReader for NodeStore { - fn read_node(&self, addr: LinearAddress) -> Result { + fn read_node(&self, addr: LinearAddress) -> Result { if let Some(node) = self.kind.read_in_memory_node(addr) { return Ok(node); } @@ -1243,7 +1355,7 @@ impl NodeReader for NodeStore { } impl NodeReader for NodeStore { - fn read_node(&self, addr: LinearAddress) -> Result { + fn read_node(&self, addr: LinearAddress) -> Result { if let Some(node) = self.kind.read_in_memory_node(addr) { return Ok(node); } @@ -1272,7 +1384,7 @@ where T: ReadInMemoryNode, S: ReadableStorage, { - fn root_address_and_hash(&self) -> Result, Error> { + fn root_address_and_hash(&self) -> Result, FileIoError> { if let Some(root_addr) = self.header.root_address { let root_node = self.read_node(root_addr)?; let root_hash = hash_node(&root_node, &Path::new()); @@ -1289,7 +1401,7 @@ where T: ReadInMemoryNode, S: ReadableStorage, { - fn root_address_and_hash(&self) -> Result, Error> { + fn root_address_and_hash(&self) -> Result, FileIoError> { if let Some(root_addr) = self.header.root_address { let root_node = self.read_node(root_addr)?; let root_hash = hash_node(&root_node, &Path::new()); @@ -1302,7 +1414,10 @@ where impl NodeStore { /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal - pub fn reap_deleted(mut self, proposal: &mut NodeStore) -> Result<(), Error> { + pub fn reap_deleted( + mut self, + proposal: &mut NodeStore, + ) -> Result<(), FileIoError> { self.storage .invalidate_cached_nodes(self.kind.deleted.iter()); trace!("There are {} nodes to reap", self.kind.deleted.len()); From 54d92e416e2a888a916cdf12982af6f453a0ed3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 15:20:48 +0000 Subject: [PATCH 0748/1053] build(deps): update fastrace-opentelemetry requirement from 0.11.0 to 0.12.0 (#943) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris --- benchmark/Cargo.toml | 2 +- benchmark/src/main.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index a343aaf46586..9fe3039d68f5 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -19,7 +19,7 @@ pretty-duration = "0.1.1" env_logger = "0.11.5" log = "0.4.20" fastrace = { version = "0.7.4", features = ["enable"] } -fastrace-opentelemetry = { version = "0.11.0" } +fastrace-opentelemetry = { version = "0.12.0" } opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry = "0.30.0" opentelemetry_sdk = "0.30.0" diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 20cefc0db2f3..c5af1c7608ff 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -25,7 +25,6 @@ use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; use fastrace::collector::Config; use opentelemetry::InstrumentationScope; -use opentelemetry::trace::SpanKind; use opentelemetry_otlp::{SpanExporter, WithExportConfig}; use opentelemetry_sdk::Resource; @@ -191,7 +190,6 @@ async fn main() -> Result<(), Box> { .with_timeout(opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT) .build() .expect("initialize oltp exporter"), - SpanKind::Server, Cow::Owned( Resource::builder() .with_service_name("avalabs.firewood.benchmark") From 5acb9a3cb78831e12232b804379513c7f13d8104 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 10 Jun 2025 09:55:17 -0400 Subject: [PATCH 0749/1053] fix(attach-static-libs): push commit/branch to remote on tag events (#944) --- .github/workflows/attach-static-libs.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 0663b799a4e3..046ecd6a712e 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -129,14 +129,16 @@ jobs: git config --global user.name "FirewoodCI" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b ${{ steps.determine_branch.outputs.target_branch }} + echo "Editing ffi/go.mod in place to use firewood-go/ffi module path" + # Note: requires sed -i '' ... on MacOS to edit in place without creating a backup file since BSD sed differs from GNU sed. + sed -i 's|module github.com/ava-labs/firewood/ffi|module github.com/ava-labs/firewood-go/ffi|' ffi/go.mod git add . git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" + git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force if [[ "${{ github.ref_type }}" == "tag" ]]; then git tag -a "${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" git push origin "refs/tags/${GITHUB_REF#refs/tags/}" - else - git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force fi # Check out the branch created in the previous job on a matrix of From e9a01b5fb52b5f13e038a9c9e155c3af2d48c8e2 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 10 Jun 2025 12:04:07 -0400 Subject: [PATCH 0750/1053] style(attach-static-libs): use go mod edit instead of sed to update mod path (#946) --- .github/workflows/attach-static-libs.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 046ecd6a712e..c61c3a40c8b6 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -129,9 +129,10 @@ jobs: git config --global user.name "FirewoodCI" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b ${{ steps.determine_branch.outputs.target_branch }} - echo "Editing ffi/go.mod in place to use firewood-go/ffi module path" - # Note: requires sed -i '' ... on MacOS to edit in place without creating a backup file since BSD sed differs from GNU sed. - sed -i 's|module github.com/ava-labs/firewood/ffi|module github.com/ava-labs/firewood-go/ffi|' ffi/go.mod + echo "Updating ffi module path before pushing to alternative remote" + pushd ffi + go mod edit -module github.com/ava-labs/firewood-go/ffi + popd git add . git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force From 7ce0b088c6f3d5d086c03b82a0470122a73715d5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 10 Jun 2025 14:14:14 -0700 Subject: [PATCH 0751/1053] feat(metrics): Add read_node counters (#947) --- storage/src/linear/filebacked.rs | 1 + storage/src/linear/memory.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index cb1cd4c6b2f8..3cc4454fdf1c 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -125,6 +125,7 @@ impl FileBacked { impl ReadableStorage for FileBacked { fn stream_from(&self, addr: u64) -> Result, FileIoError> { + counter!("firewood.read_node", "from" => "file").increment(1); Ok(Box::new(PredictiveReader::new(self, addr))) } diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index bb3931f7830a..cc6c07e78eeb 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use super::{FileIoError, ReadableStorage, WritableStorage}; +use metrics::counter; use std::io::{Cursor, Read}; use std::sync::Mutex; @@ -34,6 +35,7 @@ impl WritableStorage for MemStore { impl ReadableStorage for MemStore { fn stream_from(&self, addr: u64) -> Result, FileIoError> { + counter!("firewood.read_node", "from" => "memory").increment(1); let bytes = self .bytes .lock() From cf5eb775d02affbb835b0355e34117ce71da4df9 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 11 Jun 2025 09:48:13 -0400 Subject: [PATCH 0752/1053] Create two pkgs for firewood differential testing against eth and merkle hashing (#930) Co-authored-by: Ron Kuris --- .github/workflows/ci.yaml | 31 +- ffi/tests/{ => eth}/eth_compatibility_test.go | 2 +- ffi/tests/{ => eth}/go.mod | 48 +- ffi/tests/eth/go.sum | 512 ++++++++++++ ffi/tests/firewood/go.mod | 58 ++ ffi/tests/firewood/go.sum | 115 +++ .../firewood/merkle_compatibility_test.go | 436 ++++++++++ ffi/tests/go.sum | 767 ------------------ 8 files changed, 1175 insertions(+), 794 deletions(-) rename ffi/tests/{ => eth}/eth_compatibility_test.go (99%) rename ffi/tests/{ => eth}/go.mod (61%) create mode 100644 ffi/tests/eth/go.sum create mode 100644 ffi/tests/firewood/go.mod create mode 100644 ffi/tests/firewood/go.sum create mode 100644 ffi/tests/firewood/merkle_compatibility_test.go delete mode 100644 ffi/tests/go.sum diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 65270d37d250..1b1f110b8220 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -193,6 +193,7 @@ jobs: run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... ethhash-compatibility-go: + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -209,12 +210,36 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "ffi/tests/go.mod" - cache-dependency-path: "ffi/tests/go.sum" + go-version-file: "ffi/tests/eth/go.mod" + cache-dependency-path: "ffi/tests/eth/go.sum" - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test ./... - name: Test Ethereum hash compatability - working-directory: ffi/tests + working-directory: ffi/tests/eth + run: go test ./... + + firewood-merkle-differential-fuzz: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: "debug-no-features" + - name: Build Firewood FFI + run: cargo build -p firewood-ffi + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "ffi/tests/firewood/go.mod" + cache-dependency-path: "ffi/tests/firewood/go.sum" + - name: Test Firewood <> MerkleDB Differential fuzz + working-directory: ffi/tests/firewood run: go test ./... diff --git a/ffi/tests/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go similarity index 99% rename from ffi/tests/eth_compatibility_test.go rename to ffi/tests/eth/eth_compatibility_test.go index 83f4889321be..e60ec770a0cc 100644 --- a/ffi/tests/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -1,4 +1,4 @@ -package tests +package eth import ( "encoding/binary" diff --git a/ffi/tests/go.mod b/ffi/tests/eth/go.mod similarity index 61% rename from ffi/tests/go.mod rename to ffi/tests/eth/go.mod index 58052fab6ab3..1cbd7389a5c6 100644 --- a/ffi/tests/go.mod +++ b/ffi/tests/eth/go.mod @@ -1,8 +1,8 @@ module github.com/ava-labs/firewood/ffi/tests -go 1.23.0 +go 1.23.9 -toolchain go1.23.6 +toolchain go1.24.2 require ( github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder @@ -12,60 +12,62 @@ require ( ) require ( - github.com/DataDog/zstd v1.4.5 // indirect - github.com/StackExchange/wmi v1.2.1 // indirect + github.com/DataDog/zstd v1.5.2 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.8.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect - github.com/cockroachdb/redact v1.0.8 // indirect - github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.0 // indirect - github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/supranational/blst v0.3.11 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/sync v0.5.0 // indirect + golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ava-labs/firewood-go/ffi => ../ +replace github.com/ava-labs/firewood-go/ffi => ../../ diff --git a/ffi/tests/eth/go.sum b/ffi/tests/eth/go.sum new file mode 100644 index 000000000000..e52fe98013ab --- /dev/null +++ b/ffi/tests/eth/go.sum @@ -0,0 +1,512 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/ava-labs/libevm v1.13.14-0.2.0.release h1:uKGCc5/ceeBbfAPRVtBUxbQt50WzB2pEDb8Uy93ePgQ= +github.com/ava-labs/libevm v1.13.14-0.2.0.release/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod new file mode 100644 index 000000000000..02648984a889 --- /dev/null +++ b/ffi/tests/firewood/go.mod @@ -0,0 +1,58 @@ +module github.com/ava-labs/firewood/ffi/tests + +go 1.23.9 + +toolchain go1.24.2 + +require ( + github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder + github.com/stretchr/testify v1.10.0 +) + +require github.com/ava-labs/avalanchego v1.13.1 + +require ( + github.com/BurntSushi/toml v1.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect + github.com/gorilla/rpc v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + gonum.org/v1/gonum v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect + google.golang.org/grpc v1.66.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/ava-labs/firewood-go/ffi => ../../ diff --git a/ffi/tests/firewood/go.sum b/ffi/tests/firewood/go.sum new file mode 100644 index 000000000000..3a9479e8fef9 --- /dev/null +++ b/ffi/tests/firewood/go.sum @@ -0,0 +1,115 @@ +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/ava-labs/avalanchego v1.13.1 h1:sviA3MJbY00xYl77ru4vs9yiz7uTnAYfHpqYFucoXEE= +github.com/ava-labs/avalanchego v1.13.1/go.mod h1:h8VRoqOhLRzqSlBpgFSOtCIu2Hey2FxgjD3pBkOXOY8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= +github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8= +github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go new file mode 100644 index 000000000000..5e14e83632ba --- /dev/null +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -0,0 +1,436 @@ +package firewood + +import ( + "context" + "fmt" + "math/rand" + "path/filepath" + "slices" + "testing" + + firewood "github.com/ava-labs/firewood-go/ffi" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/x/merkledb" + "github.com/stretchr/testify/require" +) + +const ( + keySize = 4 + valSize = 4 + maxBatchSize = 5 + maxNumKeys = 10_000 +) + +const ( + dbGet byte = iota + dbUpdate + dbBatch + checkDBHash + createProposalOnProposal + createProposalOnDB + proposalGet + commitProposal + maxStep +) + +var stepMap = map[byte]string{ + dbGet: "dbGet", + dbUpdate: "dbUpdate", + dbBatch: "dbBatch", + checkDBHash: "checkDBHash", + createProposalOnProposal: "createProposalOnProposal", + createProposalOnDB: "createProposalOnDB", + proposalGet: "proposalGet", + commitProposal: "commitProposal", +} + +func newTestFirewoodDatabase(t *testing.T) *firewood.Database { + t.Helper() + + dbFile := filepath.Join(t.TempDir(), "test.db") + db, closeDB, err := newFirewoodDatabase(dbFile) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, closeDB()) + }) + return db +} + +func newFirewoodDatabase(dbFile string) (*firewood.Database, func() error, error) { + conf := firewood.DefaultConfig() + conf.MetricsPort = 0 + conf.Create = true + + f, err := firewood.New(dbFile, conf) + if err != nil { + return nil, nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) + } + return f, f.Close, nil +} + +type tree struct { + require *require.Assertions + rand *rand.Rand + + id int + nextID int + merkleDB merkledb.MerkleDB + fwdDB *firewood.Database + + children []*proposal + + proposals map[int]*proposal + keys [][]byte +} + +type proposal struct { + parentID int + id int + merkleView merkledb.View + fwdView *firewood.Proposal + + children []*proposal +} + +func newTestTree(t *testing.T, rand *rand.Rand) *tree { + r := require.New(t) + + memdb := memdb.New() + merkleDB, err := merkledb.New(context.Background(), memdb, merkledb.NewConfig()) + r.NoError(err) + + tr := &tree{ + merkleDB: merkleDB, + fwdDB: newTestFirewoodDatabase(t), + proposals: make(map[int]*proposal), + children: make([]*proposal, 0), + require: r, + rand: rand, + keys: createRandomByteSlices(min(1, rand.Intn(maxNumKeys)), keySize, rand), + } + return tr +} + +func (tr *tree) selectRandomProposal() *proposal { + if len(tr.proposals) == 0 { + return nil + } + + proposalIDs := make([]int, 0, len(tr.proposals)) + for proposalID := range tr.proposals { + proposalIDs = append(proposalIDs, proposalID) + } + slices.Sort(proposalIDs) + selectedProposalID := tr.rand.Intn(len(proposalIDs)) + return tr.proposals[selectedProposalID] +} + +func (tr *tree) dropAllProposals() { + // Drop all firewood proposals out of order (allowed by firewood). + for _, p := range tr.proposals { + tr.require.NoError(p.fwdView.Drop()) + } + // Free pointers at the root to allow garbage collection. + // MerkleDB does not require explicitly dropping merkle views, so clearing the + // pointers at the root is sufficient. + tr.children = nil + tr.proposals = make(map[int]*proposal) +} + +func (tr *tree) dbUpdate() { + key := tr.keys[tr.rand.Intn(len(tr.keys))] + val := createRandomSlice(valSize, tr.rand) + + // Insert the key-value pair into both databases. + tr.require.NoError(tr.merkleDB.Put(key, val)) + _, err := tr.fwdDB.Update([][]byte{key}, [][]byte{val}) + tr.require.NoError(err) + + tr.dropAllProposals() +} + +func (tr *tree) createRandomBatch(numKeys int) ([][]byte, [][]byte) { + keys := tr.selectRandomKeys(numKeys) + vals := createRandomByteSlices(numKeys, valSize, tr.rand) + return keys, vals +} + +func (tr *tree) selectRandomKeys(numKeys int) [][]byte { + numKeys = min(numKeys, len(tr.keys)) + selectedKeys := make([][]byte, numKeys) + for i := 0; i < numKeys; i++ { + selectedKeys[i] = tr.keys[tr.rand.Intn(len(tr.keys))] + } + return selectedKeys +} + +func (tr *tree) dbGet() { + keys := tr.selectRandomKeys(maxBatchSize) + for _, key := range keys { + fwdVal, fwdErr := tr.fwdDB.Get(key) + merkleVal, merkleErr := tr.merkleDB.GetValue(context.Background(), key) + tr.testGetResult(merkleVal, merkleErr, fwdVal, fwdErr) + } +} + +func (tr *tree) testGetResult(merkleVal []byte, merkleErr error, fwdVal []byte, fwdErr error) { + if merkleErr == database.ErrNotFound { + tr.require.Nil(merkleVal) + tr.require.NoError(fwdErr) + tr.require.Nil(fwdVal) + return + } + tr.require.NoError(merkleErr) + tr.require.NoError(fwdErr) + tr.require.Equal(fwdVal, merkleVal) +} + +func (tr *tree) dbBatch() { + batchSize := tr.rand.Intn(maxBatchSize) + 1 // XXX: ensure at least one KV pair to avoid firewood empty revision errors + keys, vals := tr.createRandomBatch(batchSize) + + batch := tr.merkleDB.NewBatch() + for i := range len(keys) { + tr.require.NoError(batch.Put(keys[i], vals[i])) + } + tr.require.NoError(batch.Write()) + + _, err := tr.fwdDB.Update(keys, vals) + tr.require.NoError(err) + + tr.dropAllProposals() +} + +func (tr *tree) commitRandomProposal() { + childIndex := tr.rand.Intn(len(tr.children)) // assumes non-zero number of children + + commitProposal := tr.children[childIndex] + tr.require.NoError(commitProposal.fwdView.Commit()) + tr.require.NoError(commitProposal.merkleView.CommitToDB(context.Background())) + delete(tr.proposals, commitProposal.id) + + remainingChildren := tr.children + for i := 0; i < len(remainingChildren); i++ { + dropChild := remainingChildren[i] + if dropChild.id == commitProposal.id { + continue + } + tr.require.NoError(dropChild.fwdView.Drop()) + delete(tr.proposals, dropChild.id) + + remainingChildren = append(remainingChildren, dropChild.children...) + } + + tr.children = commitProposal.children +} + +func (tr *tree) checkDBHash() { + // Get the root hash from both databases. + merkleRoot, err := tr.merkleDB.GetMerkleRoot(context.Background()) + tr.require.NoError(err) + + fwdRoot, err := tr.fwdDB.Root() + tr.require.NoError(err) + + // Compare the root hashes. + tr.require.Equal(merkleRoot[:], fwdRoot) +} + +func (tr *tree) createProposalOnProposal() { + pr := tr.selectRandomProposal() + + if pr == nil { + return + } + + batchSize := tr.rand.Intn(maxBatchSize) + 1 // ensure at least one key-value pair + keys, vals := tr.createRandomBatch(batchSize) + + fwdPr := pr.fwdView + merkleView := pr.merkleView + + fwdChildPr, err := fwdPr.Propose(keys, vals) + tr.require.NoError(err) + + merkleViewChange := merkledb.ViewChanges{} + for i := range keys { + merkleViewChange.BatchOps = append(merkleViewChange.BatchOps, database.BatchOp{ + Key: keys[i], + Value: vals[i], + }) + } + merkleChildView, err := merkleView.NewView(context.Background(), merkleViewChange) + tr.require.NoError(err) + + fwdRoot, err := fwdChildPr.Root() + tr.require.NoError(err) + merkleRoot, err := merkleChildView.GetMerkleRoot(context.Background()) + tr.require.NoError(err) + tr.require.Equal(fwdRoot, merkleRoot[:]) + + tr.nextID++ + newProposal := &proposal{ + parentID: pr.id, + id: tr.nextID, + merkleView: merkleChildView, + fwdView: fwdChildPr, + } + pr.children = append(pr.children, newProposal) + tr.proposals[newProposal.id] = newProposal +} + +func (tr *tree) createProposalOnDB() { + batchSize := tr.rand.Intn(maxBatchSize) + 1 // ensure at least one key-value pair + keys, vals := tr.createRandomBatch(batchSize) + fwdPr, err := tr.fwdDB.Propose(keys, vals) + tr.require.NoError(err) + + merkleViewChange := merkledb.ViewChanges{} + for i := range keys { + merkleViewChange.BatchOps = append(merkleViewChange.BatchOps, database.BatchOp{ + Key: keys[i], + Value: vals[i], + }) + } + merkleChildView, err := tr.merkleDB.NewView(context.Background(), merkleViewChange) + tr.require.NoError(err) + + fwdRoot, err := fwdPr.Root() + tr.require.NoError(err) + merkleRoot, err := merkleChildView.GetMerkleRoot(context.Background()) + tr.require.NoError(err) + tr.require.Equal(fwdRoot, merkleRoot[:]) + + tr.nextID++ + newProposal := &proposal{ + parentID: tr.id, + id: tr.nextID, + merkleView: merkleChildView, + fwdView: fwdPr, + } + tr.children = append(tr.children, newProposal) + tr.proposals[newProposal.id] = newProposal +} + +func (tr *tree) proposalGet() { + pr := tr.selectRandomProposal() + if pr == nil { + return + } + + keys := tr.selectRandomKeys(maxBatchSize) + for _, key := range keys { + fwdVal, fwdErr := pr.fwdView.Get(key) + merkleVal, merkleErr := pr.merkleView.GetValue(context.Background(), key) + + tr.testGetResult(merkleVal, merkleErr, fwdVal, fwdErr) + } +} + +func (tr *tree) commitProposal() { + if len(tr.children) == 0 { + return // no proposals to commit + } + tr.commitRandomProposal() +} + +func fuzzTree(t *testing.T, randSource int64, byteSteps []byte) { + rand := rand.New(rand.NewSource(randSource)) + + if len(byteSteps) > 100 { + byteSteps = byteSteps[:100] // limit the number of steps to 100 + } + + tr := newTestTree(t, rand) + + // TODO: replace randomly generated values with bytes from the fuzzer + for _, step := range byteSteps { + step = step % maxStep + // Make this two lines so debugger displays the stepStr value + stepStr := stepMap[step] + t.Log(stepStr) + switch step { + case dbGet: + tr.dbGet() + case dbUpdate: + tr.dbUpdate() + case dbBatch: + tr.dbBatch() + case checkDBHash: + tr.checkDBHash() + case createProposalOnProposal: + tr.createProposalOnProposal() + case createProposalOnDB: + tr.createProposalOnDB() + case proposalGet: + tr.proposalGet() + case commitProposal: + tr.commitProposal() + } + } +} + +func FuzzTree(f *testing.F) { + // Add interesting sequences to the fuzzer with a few different random seeds. + for i := range 5 { + f.Add(int64(i), []byte{ + createProposalOnDB, + createProposalOnDB, + createProposalOnProposal, + createProposalOnProposal, + createProposalOnDB, + dbGet, + proposalGet, + createProposalOnDB, + createProposalOnProposal, + commitProposal, + checkDBHash, + createProposalOnDB, + createProposalOnProposal, + commitProposal, + }) + f.Add(int64(i), []byte{ + dbUpdate, + dbGet, + dbBatch, + createProposalOnDB, + checkDBHash, + createProposalOnProposal, + commitProposal, + checkDBHash, + commitProposal, + }) + f.Add(int64(i), []byte{ + dbBatch, + dbBatch, + dbBatch, + dbGet, + createProposalOnDB, + createProposalOnProposal, + commitProposal, + commitProposal, + checkDBHash, + }) + } + f.Fuzz(fuzzTree) +} + +func createRandomSlice(maxSize int, rand *rand.Rand) []byte { + sliceSize := rand.Intn(maxSize) + 1 // always create non-empty slices + slice := make([]byte, sliceSize) + _, err := rand.Read(slice) + if err != nil { + panic(err) + } + return slice +} + +func createRandomByteSlices(numSlices int, maxSliceSize int, rand *rand.Rand) [][]byte { + slices := make([][]byte, numSlices) + for i := 0; i < numSlices; i++ { + slices[i] = createRandomSlice(maxSliceSize, rand) + } + return slices +} diff --git a/ffi/tests/go.sum b/ffi/tests/go.sum deleted file mode 100644 index 63dcf5cf65e0..000000000000 --- a/ffi/tests/go.sum +++ /dev/null @@ -1,767 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= -github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/libevm v1.13.14-0.2.0.release h1:uKGCc5/ceeBbfAPRVtBUxbQt50WzB2pEDb8Uy93ePgQ= -github.com/ava-labs/libevm v1.13.14-0.2.0.release/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= -github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= -github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= -github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= -github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= -github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= -github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= -github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= -github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= -github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= -github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= -github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= -github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= -github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From b3f8e83cc6732393e43fe57e56a7350274803dc3 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 11 Jun 2025 13:03:03 -0400 Subject: [PATCH 0753/1053] ci: add push to main to attach static libs triggers (#952) This PR adds a trigger for push to main and adds an elif condition to handle pushes to main. On a push to main, we generate a branch on firewood-go called `main-ci` as opposed to `main` to avoid overwriting the `main` branch. --- .github/workflows/attach-static-libs.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index c61c3a40c8b6..eff1a2a10dd0 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -9,6 +9,8 @@ on: push: tags: - "*" + branches: + - "main" pull_request: env: @@ -91,6 +93,10 @@ jobs: export target_branch="${GITHUB_REF#refs/tags/}" echo "Using tag name as target_branch: $target_branch" echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == "main" ]]; then + export target_branch="main-ci" # Avoid pushing to main branch of firewood-go repo + echo "Using main-ci as target branch for push to main: $target_branch" + echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == "pull_request" ]]; then export target_branch="${{ github.event.pull_request.head.ref }}" echo "Using PR head name as target branch: $target_branch" From b400f91178144c31cb66f26fa9d1c4e23cfed0a6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 11 Jun 2025 11:10:39 -0700 Subject: [PATCH 0754/1053] ci: Check the PR title for conventional commits (#953) --- .github/workflows/pr-title.yaml | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/pr-title.yaml diff --git a/.github/workflows/pr-title.yaml b/.github/workflows/pr-title.yaml new file mode 100644 index 000000000000..9191a61fce2b --- /dev/null +++ b/.github/workflows/pr-title.yaml @@ -0,0 +1,46 @@ +# Check that the PR title matches the conventional commit format +name: pr-title + +permissions: + pull-requests: write + +on: + pull_request: + types: + - edited + - opened + - reopened + +jobs: + check-pr-title: + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - name: Check PR title follows conventional commits + uses: amannn/action-semantic-pull-request@v5 + with: + types: | + build + chore + ci + docs + feat + fix + perf + refactor + style + test + # scope is not required ("feat: whatever" is okay) + requireScope: false + # if the PR only has one commit, we can validate the commit message + # instead of the PR title + validateSingleCommit: true + subjectPattern: ^.{1,}$ + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + matches the conventional commit format. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From 3560e220368ff079175c1037a5187dbafb568b35 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 11 Jun 2025 11:16:48 -0700 Subject: [PATCH 0755/1053] chore: add Brandon to CODEOWNERS (#954) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 082826bc3a59..a0cd42ca1aa3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ # CODEOWNERS -* @rkuris @aaronbuchwald +* @rkuris @aaronbuchwald @demosdemon /ffi @alarso16 From 2c0e4950f8a01e642eb704285ae9ac09049d6d9f Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 12 Jun 2025 15:11:19 -0400 Subject: [PATCH 0756/1053] test(ethhash): convert ethhash test to fuzz test for ethhash compatibility (#956) Update ethhash compatibility test to a fuzz test. Follow the style from https://github.com/ava-labs/firewood/pull/930 to define a state machine that randomly selects an operation and leaves to the fuzzer to determine the order of steps to run and the inputs as much as possible. Future improvements could be to leave as much to the fuzzer as possible (ie move from using a random seed to determine inputs to leaving all inputs to the fuzzer). --- .github/workflows/ci.yaml | 8 +- ffi/tests/eth/eth_compatibility_test.go | 504 +++++++++++++++--------- 2 files changed, 311 insertions(+), 201 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b1f110b8220..526c45305b5d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -192,7 +192,7 @@ jobs: # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... - ethhash-compatibility-go: + firewood-ethhash-differential-fuzz: needs: build runs-on: ubuntu-latest steps: @@ -216,9 +216,9 @@ jobs: working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test ./... - - name: Test Ethereum hash compatability + - name: Test Firewood <> Ethereum Differential Fuzz working-directory: ffi/tests/eth - run: go test ./... + run: go test -fuzz=. -fuzztime=1m firewood-merkle-differential-fuzz: needs: build @@ -242,4 +242,4 @@ jobs: cache-dependency-path: "ffi/tests/firewood/go.sum" - name: Test Firewood <> MerkleDB Differential fuzz working-directory: ffi/tests/firewood - run: go test ./... + run: go test -fuzz=. -fuzztime=1m diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index e60ec770a0cc..d35d8576bb16 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -20,225 +20,335 @@ import ( "github.com/stretchr/testify/require" ) -func hashData(input []byte) common.Hash { - return crypto.Keccak256Hash(input) +const ( + commit byte = iota + createAccount + updateAccount + deleteAccount + addStorage + updateStorage + deleteStorage + maxStep +) + +var ( + stepMap = map[byte]string{ + commit: "commit", + createAccount: "createAccount", + updateAccount: "updateAccount", + deleteAccount: "deleteAccount", + addStorage: "addStorage", + updateStorage: "updateStorage", + deleteStorage: "deleteStorage", + } +) + +type merkleTriePair struct { + fwdDB *firewood.Database + accountTrie state.Trie + ethDatabase state.Database + + lastRoot common.Hash + require *require.Assertions + + // current state + currentAddrs []common.Address + currentStorageInputIndices map[common.Address]uint64 + inputCounter uint64 + + // pending changes to both firewood and eth database + openStorageTries map[common.Address]state.Trie + pendingFwdKeys [][]byte + pendingFwdVals [][]byte } -func TestInsert(t *testing.T) { +func newMerkleTriePair(t *testing.T) *merkleTriePair { + r := require.New(t) + file := path.Join(t.TempDir(), "test.db") cfg := firewood.DefaultConfig() cfg.Create = true + cfg.MetricsPort = 0 db, err := firewood.New(file, cfg) - require.NoError(t, err) - defer db.Close() + r.NoError(err) - type storageKey struct { - addr common.Address - key common.Hash + tdb := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), triedb.HashDefaults) + ethRoot := types.EmptyRootHash + tr, err := tdb.OpenTrie(ethRoot) + r.NoError(err) + t.Cleanup(func() { + r.NoError(db.Close()) + }) + + return &merkleTriePair{ + fwdDB: db, + accountTrie: tr, + ethDatabase: tdb, + openStorageTries: make(map[common.Address]state.Trie), + currentStorageInputIndices: make(map[common.Address]uint64), + require: r, } +} + +// commit writes the pending changes to both tries and clears the pending changes +func (tr *merkleTriePair) commit() { + mergedNodeSet := trienode.NewMergedNodeSet() + for addr, str := range tr.openStorageTries { + accountStateRoot, set, err := str.Commit(false) + tr.require.NoError(err) + // A no-op change returns a nil set, which will cause merge to panic. + if set != nil { + tr.require.NoError(mergedNodeSet.Merge(set)) + } - rand := rand.New(rand.NewSource(0)) + acc, err := tr.accountTrie.GetAccount(addr) + tr.require.NoError(err) + // If the account was deleted, we can skip updating the account's + // state root. + if acc == nil { + continue + } - addrs := make([]common.Address, 0) - storages := make([]storageKey, 0) + acc.Root = accountStateRoot + tr.require.NoError(tr.accountTrie.UpdateAccount(addr, acc)) - chooseAddr := func() common.Address { - return addrs[rand.Intn(len(addrs))] //nolint:gosec + accHash := crypto.Keccak256(addr[:]) + tr.pendingFwdKeys = append(tr.pendingFwdKeys, accHash[:]) + updatedAccountRLP, err := rlp.EncodeToBytes(acc) + tr.require.NoError(err) + tr.pendingFwdVals = append(tr.pendingFwdVals, updatedAccountRLP) } - chooseStorage := func() storageKey { - return storages[rand.Intn(len(storages))] //nolint:gosec + updatedRoot, set, err := tr.accountTrie.Commit(true) + tr.require.NoError(err) + + // A no-op change returns a nil set, which will cause merge to panic. + if set != nil { + tr.require.NoError(mergedNodeSet.Merge(set)) } - deleteStorage := func(k storageKey) { - storages = slices.DeleteFunc(storages, func(s storageKey) bool { - return s == k - }) + tr.require.NoError(tr.ethDatabase.TrieDB().Update(updatedRoot, tr.lastRoot, 0, mergedNodeSet, nil)) + tr.lastRoot = updatedRoot + + fwdRoot, err := tr.fwdDB.Update(tr.pendingFwdKeys, tr.pendingFwdVals) + tr.require.NoError(err) + tr.require.Equal(fwdRoot, updatedRoot[:]) + + tr.pendingFwdKeys = nil + tr.pendingFwdVals = nil + tr.openStorageTries = make(map[common.Address]state.Trie) + + tr.accountTrie, err = tr.ethDatabase.OpenTrie(tr.lastRoot) + tr.require.NoError(err) +} + +// createAccount generates a new, unique account and adds it to both tries and the tracked +// current state. +func (tr *merkleTriePair) createAccount() { + tr.inputCounter++ + addr := common.BytesToAddress(crypto.Keccak256Hash(binary.BigEndian.AppendUint64(nil, tr.inputCounter)).Bytes()) + accHash := crypto.Keccak256Hash(addr[:]) + acc := &types.StateAccount{ + Nonce: 1, + Balance: uint256.NewInt(100), + Root: types.EmptyRootHash, + CodeHash: types.EmptyCodeHash[:], } + accountRLP, err := rlp.EncodeToBytes(acc) + tr.require.NoError(err) + + err = tr.accountTrie.UpdateAccount(addr, acc) + tr.require.NoError(err) + tr.currentAddrs = append(tr.currentAddrs, addr) + + tr.pendingFwdKeys = append(tr.pendingFwdKeys, accHash[:]) + tr.pendingFwdVals = append(tr.pendingFwdVals, accountRLP) +} + +// selectAccount returns a random account and account hash for the provided index +// assumes: addrIndex < len(tr.currentAddrs) +func (tr *merkleTriePair) selectAccount(addrIndex int) (common.Address, common.Hash) { + addr := tr.currentAddrs[addrIndex] + return addr, crypto.Keccak256Hash(addr[:]) +} + +// updateAccount selects a random account, increments its nonce, and adds the update +// to the pending changes for both tries. +func (tr *merkleTriePair) updateAccount(addrIndex int) { + addr, accHash := tr.selectAccount(addrIndex) + acc, err := tr.accountTrie.GetAccount(addr) + tr.require.NoError(err) + acc.Nonce++ + acc.CodeHash = crypto.Keccak256Hash(acc.CodeHash[:]).Bytes() + acc.Balance.Add(acc.Balance, uint256.NewInt(3)) + accountRLP, err := rlp.EncodeToBytes(acc) + tr.require.NoError(err) + + err = tr.accountTrie.UpdateAccount(addr, acc) + tr.require.NoError(err) + + tr.pendingFwdKeys = append(tr.pendingFwdKeys, accHash[:]) + tr.pendingFwdVals = append(tr.pendingFwdVals, accountRLP) +} + +// deleteAccount selects a random account and deletes it from both tries and the tracked +// current state. +func (tr *merkleTriePair) deleteAccount(accountIndex int) { + deleteAddr, accHash := tr.selectAccount(accountIndex) + + tr.require.NoError(tr.accountTrie.DeleteAccount(deleteAddr)) + tr.currentAddrs = slices.DeleteFunc(tr.currentAddrs, func(addr common.Address) bool { + return deleteAddr == addr + }) + + tr.pendingFwdKeys = append(tr.pendingFwdKeys, accHash[:]) + tr.pendingFwdVals = append(tr.pendingFwdVals, []byte{}) +} - deleteAccount := func(addr common.Address) { - addrs = slices.DeleteFunc(addrs, func(a common.Address) bool { - return a == addr - }) - storages = slices.DeleteFunc(storages, func(s storageKey) bool { - return s.addr == addr - }) +// openStorageTrie opens the storage trie for the provided account address. +// Uses an already opened trie, if there's a pending update to the ethereum nested +// storage trie. +// +// must maintain a map of currently open storage tries, so we can defer committing them +// until commit as opposed to after each storage update. +// This mimics the actual handling of state commitments in the EVM where storage tries are all committed immediately +// before updating the account trie along with the updated storage trie roots: +// https://github.com/ava-labs/libevm/blob/0bfe4a0380c86d7c9bf19fe84368b9695fcb96c7/core/state/statedb.go#L1155 +// +// If we attempt to commit the storage tries after each operation, then attempting to re-open the storage trie +// with an updated storage trie root from ethDatabase will fail since the storage trie root will not have been +// persisted yet - leading to a missing trie node error. +func (tr *merkleTriePair) openStorageTrie(addr common.Address) state.Trie { + storageTrie, ok := tr.openStorageTries[addr] + if ok { + return storageTrie } - memdb := rawdb.NewMemoryDatabase() - tdb := state.NewDatabaseWithConfig(memdb, triedb.HashDefaults) - ethRoot := types.EmptyRootHash + acc, err := tr.accountTrie.GetAccount(addr) + tr.require.NoError(err) + storageTrie, err = tr.ethDatabase.OpenStorageTrie(tr.lastRoot, addr, acc.Root, tr.accountTrie) + tr.require.NoError(err) + tr.openStorageTries[addr] = storageTrie + return storageTrie +} - for i := range uint64(10_000) { - tr, err := tdb.OpenTrie(ethRoot) - require.NoError(t, err) - mergeSet := trienode.NewMergedNodeSet() - - var fwKeys, fwVals [][]byte - - switch { - case i%100 == 99: // delete acc - addr := chooseAddr() - accHash := hashData(addr[:]) - - err = tr.DeleteAccount(addr) - require.NoError(t, err) - deleteAccount(addr) - - fwKeys = append(fwKeys, accHash[:]) - fwVals = append(fwVals, []byte{}) - case i%10 == 9: // delete storage - storageKey := chooseStorage() - accHash := hashData(storageKey.addr[:]) - keyHash := hashData(storageKey.key[:]) - - acc, err := tr.GetAccount(storageKey.addr) - require.NoError(t, err) - - str, err := tdb.OpenStorageTrie(ethRoot, storageKey.addr, acc.Root, tr) - require.NoError(t, err) - - err = str.DeleteStorage(storageKey.addr, storageKey.key[:]) - require.NoError(t, err) - deleteStorage(storageKey) - - strRoot, set, err := str.Commit(false) - require.NoError(t, err) - err = mergeSet.Merge(set) - require.NoError(t, err) - acc.Root = strRoot - err = tr.UpdateAccount(storageKey.addr, acc) - require.NoError(t, err) - - fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) - fwVals = append(fwVals, []byte{}) - - // We must also update the account (not for hash, but to be accurate) - fwKeys = append(fwKeys, accHash[:]) - encodedVal, err := rlp.EncodeToBytes(acc) - require.NoError(t, err) - fwVals = append(fwVals, encodedVal) - case i%4 == 0: // add acc - addr := common.BytesToAddress(hashData(binary.BigEndian.AppendUint64(nil, i)).Bytes()) - accHash := hashData(addr[:]) - acc := &types.StateAccount{ - Nonce: 1, - Balance: uint256.NewInt(100), - Root: types.EmptyRootHash, - CodeHash: types.EmptyCodeHash[:], - } - enc, err := rlp.EncodeToBytes(acc) - require.NoError(t, err) - - err = tr.UpdateAccount(addr, acc) - require.NoError(t, err) - addrs = append(addrs, addr) - - fwKeys = append(fwKeys, accHash[:]) - fwVals = append(fwVals, enc) - case i%4 == 1: // update acc - addr := chooseAddr() - accHash := hashData(addr[:]) - acc, err := tr.GetAccount(addr) - require.NoError(t, err) - acc.Nonce++ - enc, err := rlp.EncodeToBytes(acc) - require.NoError(t, err) - - err = tr.UpdateAccount(addr, acc) - require.NoError(t, err) - - fwKeys = append(fwKeys, accHash[:]) - fwVals = append(fwVals, enc) - case i%4 == 2: // add storage - addr := chooseAddr() - accHash := hashData(addr[:]) - key := hashData(binary.BigEndian.AppendUint64(nil, i)) - keyHash := hashData(key[:]) - - val := hashData(binary.BigEndian.AppendUint64(nil, i+1)) - storageKey := storageKey{addr: addr, key: key} - - acc, err := tr.GetAccount(addr) - require.NoError(t, err) - - str, err := tdb.OpenStorageTrie(ethRoot, addr, acc.Root, tr) - require.NoError(t, err) - - err = str.UpdateStorage(addr, key[:], val[:]) - require.NoError(t, err) - storages = append(storages, storageKey) - - strRoot, set, err := str.Commit(false) - require.NoError(t, err) - err = mergeSet.Merge(set) - require.NoError(t, err) - acc.Root = strRoot - err = tr.UpdateAccount(addr, acc) - require.NoError(t, err) - - fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) - // UpdateStorage automatically encodes the value to rlp, - // so we need to encode prior to sending to firewood - encodedVal, err := rlp.EncodeToBytes(val[:]) - require.NoError(t, err) - fwVals = append(fwVals, encodedVal) - - // We must also update the account (not for hash, but to be accurate) - fwKeys = append(fwKeys, accHash[:]) - encodedVal, err = rlp.EncodeToBytes(acc) - require.NoError(t, err) - fwVals = append(fwVals, encodedVal) - case i%4 == 3: // update storage - storageKey := chooseStorage() - accHash := hashData(storageKey.addr[:]) - keyHash := hashData(storageKey.key[:]) - - val := hashData(binary.BigEndian.AppendUint64(nil, i+1)) - - acc, err := tr.GetAccount(storageKey.addr) - require.NoError(t, err) - - str, err := tdb.OpenStorageTrie(ethRoot, storageKey.addr, acc.Root, tr) - require.NoError(t, err) - - err = str.UpdateStorage(storageKey.addr, storageKey.key[:], val[:]) - require.NoError(t, err) - - strRoot, set, err := str.Commit(false) - require.NoError(t, err) - err = mergeSet.Merge(set) - require.NoError(t, err) - acc.Root = strRoot - err = tr.UpdateAccount(storageKey.addr, acc) - require.NoError(t, err) - - fwKeys = append(fwKeys, append(accHash[:], keyHash[:]...)) - // UpdateStorage automatically encodes the value to rlp, - // so we need to encode prior to sending to firewood - encodedVal, err := rlp.EncodeToBytes(val[:]) - require.NoError(t, err) - fwVals = append(fwVals, encodedVal) - - // We must also update the account (not for hash, but to be accurate) - fwKeys = append(fwKeys, accHash[:]) - encodedVal, err = rlp.EncodeToBytes(acc) - require.NoError(t, err) - fwVals = append(fwVals, encodedVal) - } - next, set, err := tr.Commit(true) - require.NoError(t, err) - err = mergeSet.Merge(set) - require.NoError(t, err) +// addStorage selects an account and adds a new storage key-value pair to the account. +func (tr *merkleTriePair) addStorage(accountIndex int) { + addr, accHash := tr.selectAccount(accountIndex) + // Increment storageInputIndices for the account and take the next input to generate + // a new storage key-value pair for the account. + tr.currentStorageInputIndices[addr]++ + storageIndex := tr.currentStorageInputIndices[addr] + key := crypto.Keccak256Hash(binary.BigEndian.AppendUint64(nil, storageIndex)) + keyHash := crypto.Keccak256Hash(key[:]) + val := crypto.Keccak256Hash(keyHash[:]) + + str := tr.openStorageTrie(addr) + err := str.UpdateStorage(addr, key[:], val[:]) + tr.require.NoError(err) + + // Update storage key-value pair in firewood + tr.pendingFwdKeys = append(tr.pendingFwdKeys, append(accHash[:], keyHash[:]...)) + encodedVal, err := rlp.EncodeToBytes(val[:]) + tr.require.NoError(err) + tr.pendingFwdVals = append(tr.pendingFwdVals, encodedVal) + + tr.currentStorageInputIndices[addr]++ +} - err = tdb.TrieDB().Update(next, ethRoot, i, mergeSet, nil) - require.NoError(t, err) +// updateStorage selects an account and updates an existing storage key-value pair +// note: this may "update" a key-value pair that doesn't exist if it was previously deleted. +func (tr *merkleTriePair) updateStorage(accountIndex int, storageIndexInput uint64) { + addr, accHash := tr.selectAccount(accountIndex) + storageIndex := tr.currentStorageInputIndices[addr] + storageIndex %= storageIndexInput + + storageKey := crypto.Keccak256Hash(binary.BigEndian.AppendUint64(nil, storageIndex)) + storageKeyHash := crypto.Keccak256Hash(storageKey[:]) + tr.inputCounter++ + updatedValInput := binary.BigEndian.AppendUint64(storageKeyHash[:], tr.inputCounter) + updatedVal := crypto.Keccak256Hash(updatedValInput[:]) + + str := tr.openStorageTrie(addr) + tr.require.NoError(str.UpdateStorage(addr, storageKey[:], updatedVal[:])) + + tr.pendingFwdKeys = append(tr.pendingFwdKeys, append(accHash[:], storageKeyHash[:]...)) + updatedValRLP, err := rlp.EncodeToBytes(updatedVal[:]) + tr.require.NoError(err) + tr.pendingFwdVals = append(tr.pendingFwdVals, updatedValRLP[:]) +} + +// deleteStorage selects an account and deletes an existing storage key-value pair +// note: this may "delete" a key-value pair that doesn't exist if it was previously deleted. +func (tr *merkleTriePair) deleteStorage(accountIndex int, storageIndexInput uint64) { + addr, accHash := tr.selectAccount(accountIndex) + storageIndex := tr.currentStorageInputIndices[addr] + storageIndex %= storageIndexInput + storageKey := crypto.Keccak256Hash(binary.BigEndian.AppendUint64(nil, storageIndex)) + storageKeyHash := crypto.Keccak256Hash(storageKey[:]) + + str := tr.openStorageTrie(addr) + tr.require.NoError(str.DeleteStorage(addr, storageKey[:])) - // update firewood db - got, err := db.Update(fwKeys, fwVals) - require.NoError(t, err) - require.Equal(t, next[:], got) + tr.pendingFwdKeys = append(tr.pendingFwdKeys, append(accHash[:], storageKeyHash[:]...)) + tr.pendingFwdVals = append(tr.pendingFwdVals, []byte{}) +} - ethRoot = next +func FuzzTree(f *testing.F) { + for randSeed := range int64(5) { + rand := rand.New(rand.NewSource(randSeed)) + steps := make([]byte, 32) + _, err := rand.Read(steps) + if err != nil { + f.Fatal(err) + } + f.Add(randSeed, steps) } + f.Fuzz(func(t *testing.T, randSeed int64, byteSteps []byte) { + tr := newMerkleTriePair(t) + rand := rand.New(rand.NewSource(randSeed)) + + for range 10 { + tr.createAccount() + } + tr.commit() + + const maxSteps = 1000 + if len(byteSteps) > maxSteps { + byteSteps = byteSteps[:maxSteps] + } + + for _, step := range byteSteps { + step = step % maxStep + t.Log(stepMap[step]) + switch step { + case commit: + tr.commit() + case createAccount: + tr.createAccount() + case updateAccount: + if len(tr.currentAddrs) > 0 { + tr.updateAccount(rand.Intn(len(tr.currentAddrs))) + } + case deleteAccount: + if len(tr.currentAddrs) > 0 { + tr.deleteAccount(rand.Intn(len(tr.currentAddrs))) + } + case addStorage: + if len(tr.currentAddrs) > 0 { + tr.addStorage(rand.Intn(len(tr.currentAddrs))) + } + case updateStorage: + if len(tr.currentAddrs) > 0 { + tr.updateStorage(rand.Intn(len(tr.currentAddrs)), rand.Uint64()) + } + case deleteStorage: + if len(tr.currentAddrs) > 0 { + tr.deleteStorage(rand.Intn(len(tr.currentAddrs)), rand.Uint64()) + } + default: + t.Fatalf("unknown step: %d", step) + } + } + }) } From ae3548652ab4af5168140df434b3002e6f5240db Mon Sep 17 00:00:00 2001 From: Kushneryk Pavel Date: Fri, 13 Jun 2025 22:44:09 +0200 Subject: [PATCH 0757/1053] fix: add add_arithmetic_side_effects clippy (#949) Resolve https://github.com/ava-labs/firewood/issues/938 --------- Co-authored-by: Pavlo Kushneryk --- .gitignore | 1 + ffi/Cargo.toml | 1 + ffi/src/lib.rs | 1 - ffi/src/metrics_setup.rs | 5 ++++- firewood/Cargo.toml | 1 + firewood/examples/insert.rs | 2 +- firewood/src/merkle.rs | 10 +++++----- firewood/src/proof.rs | 5 ++++- firewood/src/stream.rs | 6 +++++- fwdctl/Cargo.toml | 1 + fwdctl/src/dump.rs | 4 ++-- grpc-testtool/Cargo.toml | 1 + 12 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 13fc140abf9f..161625f6ab46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Ignore VSCode directory .vscode +.idea compose-dev.yaml diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index d275483b9a94..41fcb3fe4937 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -33,3 +33,4 @@ indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" pedantic = "warn" +arithmetic_side_effects = "warn" diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 3342ae7d50f6..f61f521b066c 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,6 +1,5 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. - use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr, c_char}; use std::fmt::{self, Display, Formatter}; diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 1ff39b3093f6..f223d8df03d7 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -69,7 +69,10 @@ impl TextRecorder { let epoch_duration = systemtime_now .duration_since(SystemTime::UNIX_EPOCH) .expect("system time is before Unix epoch"); - let epoch_ms = epoch_duration.as_secs() * 1000 + u64::from(epoch_duration.subsec_millis()); + let epoch_ms = epoch_duration + .as_secs() + .saturating_mul(1000) + .saturating_add(u64::from(epoch_duration.subsec_millis())); writeln!(output, "# {utc_now}").unwrap(); let counters = self.registry.get_counter_handles(); diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index f5af9b5ffef4..404ff5064fa0 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -64,6 +64,7 @@ unwrap_used = "warn" indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" +arithmetic_side_effects = "warn" [target.'cfg(target_os = "linux")'.dependencies] storage = { path = "../storage", features = ["io-uring"] } diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 13d22dd49873..60eb6b61d83e 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -120,7 +120,7 @@ fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap Merkle { }; let start_proof = self.prove(&first_key)?; - let limit = limit.map(|old_limit| old_limit.get() - 1); + let limit = limit.map(|old_limit| old_limit.get().saturating_sub(1)); let mut key_values = vec![(first_key, first_value.into_boxed_slice())]; @@ -875,13 +875,13 @@ impl Merkle> { Node::Branch(branch) => { if branch.value.is_some() { // a KV pair was in the branch itself - *deleted += 1; + *deleted = deleted.saturating_add(1); } self.delete_children(branch, deleted)?; } Node::Leaf(_) => { // the prefix matched only a leaf, so we remove it and indicate only one item was removed - *deleted += 1; + *deleted = deleted.saturating_add(1); } }; Ok(None) @@ -982,7 +982,7 @@ impl Merkle> { ) -> Result<(), FileIoError> { if branch.value.is_some() { // a KV pair was in the branch itself - *deleted += 1; + *deleted = deleted.saturating_add(1); } for children in branch.children.iter_mut() { // read the child node @@ -998,7 +998,7 @@ impl Merkle> { self.delete_children(child_branch, deleted)?; } Node::Leaf(_) => { - *deleted += 1; + *deleted = deleted.saturating_add(1); } } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 053c87e8bec5..4d03e475a337 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -119,7 +119,10 @@ impl From for ProofNode { } #[cfg(feature = "ethhash")] - let partial_len = item.key_nibbles.len() - item.node.partial_path().0.len(); + let partial_len = item + .key_nibbles + .len() + .saturating_sub(item.node.partial_path().0.len()); Self { key: item.key_nibbles, diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 2434c8e22f72..1ce7013035b2 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -597,7 +597,11 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { let mut data = Vec::with_capacity(nibbles.size_hint().0 / 2); while let (Some(hi), Some(lo)) = (nibbles.next(), nibbles.next()) { - data.push((hi << 4) + lo); + let byte = hi + .checked_shl(4) + .and_then(|v| v.checked_add(lo)) + .expect("Nibble overflow while constructing byte"); + data.push(byte); } data.into_boxed_slice() diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 59b45f7a316c..4c2f19f9679e 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -28,3 +28,4 @@ unwrap_used = "warn" indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" +arithmetic_side_effects = "warn" diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index a52994d39b54..84f6e23eb483 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -133,7 +133,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { .or(opts.start_key_hex.clone()) .unwrap_or_default(); let stop_key = opts.stop_key.clone().or(opts.stop_key_hex.clone()); - let mut key_count = 0; + let mut key_count: u32 = 0; let mut stream = MerkleKeyValueStream::from_key(&latest_rev, start_key); let mut output_handler = create_output_handler(opts).expect("Error creating output handler"); @@ -143,7 +143,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { Ok((key, value)) => { output_handler.handle_record(&key, &value)?; - key_count += 1; + key_count = key_count.saturating_add(1); if (stop_key.as_ref().is_some_and(|stop_key| key >= *stop_key)) || key_count_exceeded(opts.max_key_count, key_count) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 6b6c34bdacde..513844cdf346 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -49,6 +49,7 @@ unwrap_used = "warn" indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" +arithmetic_side_effects = "warn" [package.metadata.cargo-machete] ignored = ["prost", "tonic_build"] From a39e5525d066c40c6e27b67848e5a7042c560136 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 16 Jun 2025 10:58:23 -0700 Subject: [PATCH 0758/1053] fix: improve ethhash warning message (#961) Previously, the message indicated you have some account but doesn't tell you what account it is. Since this is being hit in fuzz testing, might be good to know what account is having that problem. This code renders the nibbles, which someone can use to figure out the account. --- storage/src/hashers/ethhash.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 5d3e38ed41b1..0fcdae7440b3 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -233,7 +233,10 @@ impl Preimage for T { bytes } else { // treat like non-account since it didn't have a value - warn!("Account node without value"); + warn!( + "Account node {:x?} without value", + self.key().collect::>() + ); bytes.as_ref().into() } } else { From 4316802b7913e419055a297bef2e5d0b72d207b9 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:33:56 -0400 Subject: [PATCH 0759/1053] feat: Return database creation errors through FFI (#945) This returns errors from database creation across the FFI layer rather than panicking in `expect` clauses. I also wanted to test the case that prompted this (metric port errors), but I couldn't cause it to fail in CI by opening a port with `net.Listen`. If there's a way to consume the port, that would be really helpful --- ffi/firewood.go | 11 +++- ffi/firewood.h | 33 ++++++++++- ffi/firewood_test.go | 9 +++ ffi/memory.go | 22 ++++++-- ffi/src/lib.rs | 115 +++++++++++++++++++++++++++------------ ffi/src/metrics_setup.rs | 76 ++++++++++++++------------ 6 files changed, 188 insertions(+), 78 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index b2c31f1ddbb6..0c4e85071337 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -103,11 +103,16 @@ func New(filePath string, conf *Config) (*Database, error) { // of the FFI boundary. defer C.free(unsafe.Pointer(args.path)) - var db *C.DatabaseHandle + var dbResult C.struct_DatabaseCreationResult if conf.Create { - db = C.fwd_create_db(args) + dbResult = C.fwd_create_db(args) } else { - db = C.fwd_open_db(args) + dbResult = C.fwd_open_db(args) + } + + db, err := databaseFromResult(&dbResult) + if err != nil { + return nil, err } return &Database{handle: db}, nil diff --git a/ffi/firewood.h b/ffi/firewood.h index 78d0129e5c1d..71260f5cf558 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -41,6 +41,14 @@ typedef struct KeyValue { struct Value value; } KeyValue; +/** + * Struct returned by `fwd_create_db` and `fwd_open_db` + */ +typedef struct DatabaseCreationResult { + const struct DatabaseHandle *db; + uint8_t *error_str; +} DatabaseCreationResult; + /** * Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. * @@ -149,7 +157,7 @@ struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); * The caller must call `close` to free the memory associated with the returned database handle. * */ -const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); +struct DatabaseCreationResult fwd_create_db(struct CreateOrOpenArgs args); /** * Drops a proposal from the database. @@ -168,6 +176,25 @@ const struct DatabaseHandle *fwd_create_db(struct CreateOrOpenArgs args); */ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposal_id); +/** + * Frees the memory associated with a `DatabaseCreationResult`. + * This only needs to be called if the `error_str` field is non-null. + * + * # Arguments + * + * * `result` - The `DatabaseCreationResult` to free, previously returned from `fwd_create_db` or `fwd_open_db`. + * + * # Safety + * + * This function is unsafe because it dereferences raw pointers. + * The caller must ensure that `result` is a valid pointer. + * + * # Panics + * + * This function panics if `result` is `null`. + */ +void fwd_free_database_error_result(struct DatabaseCreationResult *result); + /** * Frees the memory associated with a `Value`. * @@ -184,7 +211,7 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa * * This function panics if `value` is `null`. */ -void fwd_free_value(const struct Value *value); +void fwd_free_value(struct Value *value); /** * Gets the value associated with the given key from the proposal provided. @@ -281,7 +308,7 @@ struct Value fwd_get_latest(const struct DatabaseHandle *db, struct Value key); * The caller must call `close` to free the memory associated with the returned database handle. * */ -const struct DatabaseHandle *fwd_open_db(struct CreateOrOpenArgs args); +struct DatabaseCreationResult fwd_open_db(struct CreateOrOpenArgs args); /** * Proposes a batch of operations to the database. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 0d6a1aed6266..6fef86e472fa 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -144,6 +144,15 @@ func newDatabase(dbFile string) (*Database, func() error, error) { return f, f.Close, nil } +func TestOpenNonexistentDatabase(t *testing.T) { + r := require.New(t) + cfg := DefaultConfig() + cfg.Create = false + db, err := New(filepath.Join(t.TempDir(), "test.db"), cfg) + r.ErrorContains(err, "File IO error") + r.Nil(db) +} + func TestUpdateSingleKV(t *testing.T) { r := require.New(t) db := newTestDatabase(t) diff --git a/ffi/memory.go b/ffi/memory.go index c8d72ceab2f6..374b64cb199a 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -17,7 +17,7 @@ import ( ) var ( - errNilBuffer = errors.New("firewood error: nil value returned from cgo") + errNilStruct = errors.New("firewood error: nil struct pointer cannot be freed") errBadValue = errors.New("firewood error: value from cgo formatted incorrectly") ) @@ -42,7 +42,7 @@ func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { defer runtime.KeepAlive(v) if v == nil { - return nil, 0, errNilBuffer + return nil, 0, errNilStruct } if v.data == nil { @@ -85,7 +85,7 @@ func errorFromValue(v *C.struct_Value) error { defer runtime.KeepAlive(v) if v == nil { - return errNilBuffer + return errNilStruct } // Case 1 @@ -120,7 +120,7 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { defer runtime.KeepAlive(v) if v == nil { - return nil, errNilBuffer + return nil, errNilStruct } // Case 4 @@ -146,6 +146,20 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { return nil, errBadValue } +func databaseFromResult(result *C.struct_DatabaseCreationResult) (*C.DatabaseHandle, error) { + if result == nil { + return nil, errNilStruct + } + + if result.error_str != nil { + errStr := C.GoString((*C.char)(unsafe.Pointer(result.error_str))) + C.fwd_free_database_error_result(result) + runtime.KeepAlive(result) + return nil, fmt.Errorf("firewood error: %s", errStr) + } + return result.db, nil +} + // newValueFactory returns a factory for converting byte slices into cgo `Value` // structs that can be passed as arguments to cgo functions. The returned // cleanup function MUST be called when the constructed values are no longer diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index f61f521b066c..ccb6ffbd2039 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -705,7 +705,7 @@ impl From<()> for Value { /// /// This function panics if `value` is `null`. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_value(value: *const Value) { +pub unsafe extern "C" fn fwd_free_value(value: *mut Value) { // Check value is valid. let value = unsafe { value.as_ref() }.expect("value should be non-null"); @@ -729,6 +729,61 @@ pub unsafe extern "C" fn fwd_free_value(value: *const Value) { } } +/// Struct returned by `fwd_create_db` and `fwd_open_db` +#[derive(Debug)] +#[repr(C)] +pub struct DatabaseCreationResult { + pub db: *const DatabaseHandle<'static>, + pub error_str: *mut u8, +} + +impl From> for DatabaseCreationResult { + fn from(result: Result) -> Self { + match result { + Ok(db) => DatabaseCreationResult { + db: Box::into_raw(Box::new(db.into())), + error_str: std::ptr::null_mut(), + }, + Err(error_msg) => { + let error_cstring = CString::new(error_msg).unwrap_or_default().into_raw(); + DatabaseCreationResult { + db: std::ptr::null(), + error_str: error_cstring.cast::(), + } + } + } + } +} + +/// Frees the memory associated with a `DatabaseCreationResult`. +/// This only needs to be called if the `error_str` field is non-null. +/// +/// # Arguments +/// +/// * `result` - The `DatabaseCreationResult` to free, previously returned from `fwd_create_db` or `fwd_open_db`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences raw pointers. +/// The caller must ensure that `result` is a valid pointer. +/// +/// # Panics +/// +/// This function panics if `result` is `null`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_database_error_result(result: *mut DatabaseCreationResult) { + // Check result is valid. + let result = unsafe { result.as_ref() }.expect("result should be non-null"); + + // Free the error string if it exists + if !result.error_str.is_null() { + let raw_str = result.error_str.cast::(); + let cstr = unsafe { CString::from_raw(raw_str) }; + drop(cstr); + } + // Note: we don't free the db pointer as it's managed by the caller +} + /// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. /// /// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` @@ -763,16 +818,8 @@ pub struct CreateOrOpenArgs { /// The caller must call `close` to free the memory associated with the returned database handle. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *const DatabaseHandle<'static> { - let cfg = DbConfig::builder() - .truncate(true) - .manager(manager_config( - args.cache_size, - args.revisions, - args.strategy, - )) - .build(); - unsafe { common_create(args.path, args.metrics_port, cfg) } +pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> DatabaseCreationResult { + unsafe { common_create(&args, true) }.into() } /// Open a database with the given cache size and maximum number of revisions @@ -793,58 +840,58 @@ pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> *const Databas /// The caller must call `close` to free the memory associated with the returned database handle. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> *const DatabaseHandle<'static> { +pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> DatabaseCreationResult { + unsafe { common_create(&args, false) }.into() +} + +/// Internal call for `fwd_create_db` and `fwd_open_db` to remove error handling from the C API +#[doc(hidden)] +unsafe fn common_create(args: &CreateOrOpenArgs, create_file: bool) -> Result { let cfg = DbConfig::builder() - .truncate(false) + .truncate(create_file) .manager(manager_config( args.cache_size, args.revisions, args.strategy, - )) + )?) .build(); - unsafe { common_create(args.path, args.metrics_port, cfg) } -} - -/// Internal call for `fwd_create_db` and `fwd_open_db` to remove error handling from the C API -#[doc(hidden)] -unsafe fn common_create( - path: *const std::ffi::c_char, - metrics_port: u16, - cfg: DbConfig, -) -> *const DatabaseHandle<'static> { #[cfg(feature = "logger")] let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .try_init(); - let path = unsafe { CStr::from_ptr(path) }; + let path = unsafe { CStr::from_ptr(args.path) }; #[cfg(unix)] let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); #[cfg(windows)] let path: &Path = OsStr::new(path.to_str().expect("path should be valid UTF-8")).as_ref(); - if metrics_port > 0 { - metrics_setup::setup_metrics(metrics_port); + if args.metrics_port > 0 { + metrics_setup::setup_metrics(args.metrics_port).map_err(|e| e.to_string())?; } - let db = Db::new_sync(path, cfg).expect("db initialization should succeed"); - Box::into_raw(Box::new(db.into())) + Db::new_sync(path, cfg).map_err(|e| e.to_string()) } #[doc(hidden)] -fn manager_config(cache_size: usize, revisions: usize, strategy: u8) -> RevisionManagerConfig { +fn manager_config( + cache_size: usize, + revisions: usize, + strategy: u8, +) -> Result { let cache_read_strategy = match strategy { 0 => CacheReadStrategy::WritesOnly, 1 => CacheReadStrategy::BranchReads, 2 => CacheReadStrategy::All, - _ => panic!("invalid cache strategy"), + _ => return Err("invalid cache strategy".to_string()), }; - RevisionManagerConfig::builder() + let config = RevisionManagerConfig::builder() .node_cache_size( cache_size .try_into() - .expect("cache size should always be non-zero"), + .map_err(|_| "cache size should be non-zero")?, ) .max_revisions(revisions) .cache_read_strategy(cache_read_strategy) - .build() + .build(); + Ok(config) } /// Close and free the memory for a database handle diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index f223d8df03d7..2aad80503835 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; +use std::error::Error; use std::io::Write; use std::net::Ipv6Addr; use std::ops::Deref; +use std::sync::Arc; use std::sync::atomic::Ordering; -use std::sync::{Arc, Once}; use std::time::SystemTime; use oxhttp::Server; @@ -16,39 +17,46 @@ use chrono::{DateTime, Utc}; use metrics::Key; use metrics_util::registry::{AtomicStorage, Registry}; -static INIT: Once = Once::new(); - -pub(crate) fn setup_metrics(metrics_port: u16) { - INIT.call_once(|| { - let inner: TextRecorderInner = TextRecorderInner { - registry: Registry::atomic(), - }; - let recorder = TextRecorder { - inner: Arc::new(inner), - }; - metrics::set_global_recorder(recorder.clone()).expect("failed to set recorder"); - - Server::new(move |request| { - if request.method() == "GET" { - Response::builder() - .status(StatusCode::OK) - .header("Content-Type", "text/plain") - .body(Body::from(recorder.stats())) - .expect("failed to build response") - } else { - Response::builder() - .status(StatusCode::METHOD_NOT_ALLOWED) - .body(Body::from("Method not allowed")) - .expect("failed to build response") - } - }) - .bind((Ipv4Addr::LOCALHOST, metrics_port)) - .bind((Ipv6Addr::LOCALHOST, metrics_port)) - .with_global_timeout(Duration::from_secs(60 * 60)) - .with_max_concurrent_connections(2) - .spawn() - .expect("failed to spawn server"); - }); +/// Sets up a metrics server over a specified port. +/// If `metrics_port` is set to 0, it will not initialize the metrics system. +/// This happens on a per-process basis, meaning that the metrics system +/// cannot be initialized if it has already been set up in the same process. +/// Any caller should ensure that the metrics system is not already initialized +/// by explicitly calling with `metrics_port` == 0.. +pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { + if metrics_port == 0 { + // If the port is 0, we do not initialize the metrics system. + return Ok(()); + } + + let inner: TextRecorderInner = TextRecorderInner { + registry: Registry::atomic(), + }; + let recorder = TextRecorder { + inner: Arc::new(inner), + }; + metrics::set_global_recorder(recorder.clone())?; + + Server::new(move |request| { + if request.method() == "GET" { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/plain") + .body(Body::from(recorder.stats())) + .expect("failed to build response") + } else { + Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::from("Method not allowed")) + .expect("failed to build response") + } + }) + .bind((Ipv4Addr::LOCALHOST, metrics_port)) + .bind((Ipv6Addr::LOCALHOST, metrics_port)) + .with_global_timeout(Duration::from_secs(60 * 60)) + .with_max_concurrent_connections(2) + .spawn()?; + Ok(()) } #[derive(Debug)] From 2dbcce5fdcbb50401cdd0e5a4a0ea32848278ad6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 16 Jun 2025 14:00:10 -0700 Subject: [PATCH 0760/1053] chore: Set up for publishing to crates.io (#962) It's been a while, but I'd like to get crates.io published. These are all the changes required to do that. Also, the authors have been updated to correctly reflect the authors for the latest release. We'll still need to publish a new revision to get this on crates.io. --- .github/workflows/publish.yaml | 45 ++++++++++++++++++++++++++++++++++ benchmark/Cargo.toml | 16 ++++++++++-- benchmark/cloud-config.txt | 1 - ffi/Cargo.toml | 13 +++++++++- firewood/Cargo.toml | 31 +++++++++++++---------- firewood/benches/hashops.rs | 2 +- firewood/src/db.rs | 10 ++++---- firewood/src/lib.rs | 16 ++++++------ firewood/src/manager.rs | 8 +++--- firewood/src/merkle.rs | 14 +++++------ firewood/src/proof.rs | 4 +-- firewood/src/range_proof.rs | 2 +- firewood/src/stream.rs | 8 +++--- firewood/src/v2/api.rs | 4 +-- fwdctl/Cargo.toml | 20 ++++++++++++++- grpc-testtool/Cargo.toml | 2 +- storage/Cargo.toml | 11 ++++++++- storage/benches/serializer.rs | 12 ++++----- triehash/Cargo.toml | 2 +- triehash/benches/triehash.rs | 2 +- triehash/src/lib.rs | 6 ++--- 21 files changed, 164 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000000..9825fd363afe --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,45 @@ +name: publish + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + publish-firewood-crate: + name: firewood-lib + runs-on: ubuntu-latest + if: "startsWith(github.event.release.tag_name, 'v')" + steps: + - uses: actions/checkout@v1 + - uses: dtolnay/rust-toolchain@stable + - name: publish firewood-storage crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-storage + - name: publish firewood-triehash crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-triehash + - name: publish firewood crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood + - name: publish firewood-ffi crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-ffi + - name: publish firewood-fwdctl crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-fwdctl + - name: publish firewood-benchmark crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-benchmark diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 9fe3039d68f5..64cccd848de1 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,11 +1,23 @@ [package] -name = "benchmark" +name = "firewood-benchmark" version = "0.0.5" edition = "2024" rust-version = "1.85.0" +authors = [ + "Aaron Buchwald ", + "Ron Kuris ", +] +description = "Benchmarking tool for Firewood, an embedded key-value store optimized for blockchain state." +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" + +[[bin]] +name = "benchmark" +path = "src/main.rs" [dependencies] -firewood = { path = "../firewood" } +firewood = { version = "0.0.5", path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" diff --git a/benchmark/cloud-config.txt b/benchmark/cloud-config.txt index 508e97c5fa59..658f8e3b5f72 100644 --- a/benchmark/cloud-config.txt +++ b/benchmark/cloud-config.txt @@ -16,7 +16,6 @@ write_files: . "$HOME/.cargo/env" git clone https://github.com/ava-labs/firewood.git cd firewood - git checkout rkuris/prometheus cargo build --release - path: /etc/prometheus/prometheus.yml.addon content: |2 diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 41fcb3fe4937..33f419231d75 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -3,13 +3,24 @@ name = "firewood-ffi" version = "0.0.5" edition = "2024" rust-version = "1.85.0" +authors = [ + "Aaron Buchwald ", + "Arran Schlosberg <519948+ARR4N@users.noreply.github.com>", + "Austin Larson <78000745+alarso16@users.noreply.github.com>", + "Darioush Jalali ", + "Ron Kuris ", +] +description = "C FFI bindings for Firewood, an embedded key-value store optimized for blockchain state." +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" [lib] crate-type = ["staticlib"] [dependencies] libc = "0.2.2" -firewood = { path = "../firewood" } +firewood = { version = "0.0.5", path = "../firewood" } metrics = "0.24.1" metrics-util = "0.19.0" chrono = "0.4.39" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 404ff5064fa0..a24d676c41a4 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -3,12 +3,17 @@ name = "firewood" version = "0.0.5" edition = "2024" authors = [ - "Ted Yin (@Determinant) ", - "Dan Sover (@exdx) ", - "Hao Hao (@haohao-os) ", - "Gyuho Lee (@gyuho) ", - "Sam Batschelet (@hexfusion) ", - "Ron Kuris (@rkuris) ", + "Angel Leon ", + "Austin Larson <78000745+alarso16@users.noreply.github.com>", + "Cesar <137245636+nytzuga@users.noreply.github.com>", + "Dan Laine ", + "Dan Sover ", + "Hao Hao ", + "Patrick O'Grady ", + "Richard Pringle ", + "Ron Kuris ", + "Sam Batschelet ", + "xinifinity <113067541+xinifinity@users.noreply.github.com>", ] description = "Firewood is an embedded key-value store, optimized to store blockchain state." license-file = "../LICENSE.md" @@ -34,13 +39,13 @@ fastrace = { version = "0.7.4" } [features] default = [] nightly = [] -io-uring = ["storage/io-uring"] -logger = ["storage/logger"] -branch_factor_256 = [ "storage/branch_factor_256" ] -ethhash = [ "storage/ethhash" ] +io-uring = ["firewood-storage/io-uring"] +logger = ["firewood-storage/logger"] +branch_factor_256 = [ "firewood-storage/branch_factor_256" ] +ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] -triehash = { version = "0.8.5", path = "../triehash" } +firewood-triehash = { version = "0.8.5", path = "../triehash" } criterion = { version = "0.6.0", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" @@ -67,7 +72,7 @@ missing_const_for_fn = "warn" arithmetic_side_effects = "warn" [target.'cfg(target_os = "linux")'.dependencies] -storage = { path = "../storage", features = ["io-uring"] } +firewood-storage = { version = "0.0.5", path = "../storage", features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] -storage = { path = "../storage" } +firewood-storage = { version = "0.0.5", path = "../storage" } diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 23904cdf88c0..e2451da401a8 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -8,6 +8,7 @@ use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use firewood::db::{BatchOp, DbConfig}; use firewood::merkle::Merkle; use firewood::v2::api::{Db as _, Proposal as _}; +use firewood_storage::{MemStore, NodeStore}; use pprof::ProfilerGuard; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -17,7 +18,6 @@ use std::iter::repeat_with; use std::os::raw::c_int; use std::path::Path; use std::sync::Arc; -use storage::{MemStore, NodeStore}; // To enable flamegraph output // cargo bench --bench hashops -- --profile-time=N diff --git a/firewood/src/db.rs b/firewood/src/db.rs index a63b05dfff19..099a37ca26a9 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -10,13 +10,13 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; +use firewood_storage::{ + Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, +}; use metrics::{counter, describe_counter}; use std::io::Write; use std::path::Path; use std::sync::{Arc, RwLock}; -use storage::{ - Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, -}; use thiserror::Error; use typed_builder::TypedBuilder; @@ -243,7 +243,7 @@ impl Db { #[cfg(not(feature = "ethhash"))] return Ok(hash); #[cfg(feature = "ethhash")] - return Ok(Some(hash.unwrap_or_else(storage::empty_trie_hash))); + return Ok(Some(hash.unwrap_or_else(firewood_storage::empty_trie_hash))); } /// Synchronously get a revision from a root hash @@ -337,7 +337,7 @@ impl Proposal<'_> { return Ok(Some( self.nodestore .root_hash()? - .unwrap_or_else(storage::empty_trie_hash), + .unwrap_or_else(firewood_storage::empty_trie_hash), )); } } diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 1b77fc151647..a9043e5c0bf2 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -63,13 +63,13 @@ //! Firewood is built by layers of abstractions that totally decouple the layout/representation //! of the data on disk from the actual logical data structure it retains: //! -//! - The storage module has a [storage::NodeStore] which has a generic parameter identifying +//! - The storage module has a [firewood_storage::NodeStore] which has a generic parameter identifying //! the state of the nodestore, and a storage type. //! //! There are three states for a nodestore: -//! - [storage::Committed] for revisions that are on disk -//! - [storage::ImmutableProposal] for revisions that are proposals against committed versions -//! - [storage::MutableProposal] for revisions where nodes are still being added. +//! - [firewood_storage::Committed] for revisions that are on disk +//! - [firewood_storage::ImmutableProposal] for revisions that are proposals against committed versions +//! - [firewood_storage::MutableProposal] for revisions where nodes are still being added. //! //! For more information on these node states, see their associated documentation. //! @@ -85,17 +85,17 @@ //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! -//! - Create a [storage::MutableProposal] [storage::NodeStore] from the most recent [storage::Committed] one. +//! - Create a [firewood_storage::MutableProposal] [firewood_storage::NodeStore] from the most recent [firewood_storage::Committed] one. //! - Traverse the trie, starting at the root. Make a new root node by duplicating the existing //! root from the committed one and save that in memory. As you continue traversing, make copies //! of each node accessed if they are not already in memory. //! //! - Make changes to the trie, in memory. Each node you've accessed is currently in memory and is -//! owned by the [storage::MutableProposal]. Adding a node simply means adding a reference to it. +//! owned by the [firewood_storage::MutableProposal]. Adding a node simply means adding a reference to it. //! //! - If you delete a node, mark it as deleted in the proposal and remove the child reference to it. //! -//! - After making all mutations, convert the [storage::MutableProposal] to an [storage::ImmutableProposal]. This +//! - After making all mutations, convert the [firewood_storage::MutableProposal] to an [firewood_storage::ImmutableProposal]. This //! involves walking the in-memory trie and looking for nodes without disk addresses, then assigning //! them from the freelist of the parent. This gives the node an address, but it is stil in //! memory. @@ -134,4 +134,4 @@ pub mod stream; pub mod v2; /// Expose the storage logger -pub use storage::logger; +pub use firewood_storage::logger; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 9badb5ac772e..136d090b3986 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -8,15 +8,15 @@ use std::sync::Arc; #[cfg(feature = "ethhash")] use std::sync::OnceLock; +use firewood_storage::logger::{trace, trace_enabled, warn}; use metrics::gauge; -use storage::logger::{trace, trace_enabled, warn}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; use crate::v2::api::HashKey; -pub use storage::CacheReadStrategy; -use storage::{ +pub use firewood_storage::CacheReadStrategy; +use firewood_storage::{ Committed, FileBacked, FileIoError, ImmutableProposal, NodeStore, Parentable, TrieHash, }; @@ -268,7 +268,7 @@ impl RevisionManager { #[allow(clippy::redundant_closure)] Some( self.empty_hash - .get_or_init(|| storage::empty_trie_hash()) + .get_or_init(|| firewood_storage::empty_trie_hash()) .clone(), ) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 98339c0e7543..9004bb15d861 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -5,6 +5,11 @@ use crate::proof::{Proof, ProofError, ProofNode}; use crate::range_proof::RangeProof; use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api; +use firewood_storage::{ + BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, + LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, + ReadableStorage, SharedNode, TrieReader, ValueDigest, +}; use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; @@ -14,11 +19,6 @@ use std::io::Error; use std::iter::once; use std::num::NonZeroUsize; use std::sync::Arc; -use storage::{ - BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, - LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, - ReadableStorage, SharedNode, TrieReader, ValueDigest, -}; /// Keys are boxed u8 slices pub type Key = Box<[u8]>; @@ -1049,9 +1049,9 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { #[expect(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; + use firewood_storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; use rand::rngs::StdRng; use rand::{Rng, SeedableRng, rng}; - use storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; use test_case::test_case; // Returns n random key-value pairs. @@ -1821,7 +1821,7 @@ mod tests { fn test_root_hash_eth_compatible + Clone + Ord>(kvs: &[(T, T)]) { use ethereum_types::H256; use ethhasher::KeccakHasher; - use triehash::trie_root; + use firewood_triehash::trie_root; let merkle = merkle_build_test(kvs.to_vec()).unwrap().hash(); let firewood_hash = merkle.nodestore.root_hash().unwrap().unwrap_or_default(); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 4d03e475a337..0f3fa5914b8e 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,11 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use sha2::{Digest, Sha256}; -use storage::{ +use firewood_storage::{ BranchNode, FileIoError, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, }; +use sha2::{Digest, Sha256}; use thiserror::Error; #[derive(Debug, Error)] diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index 7070ba427947..b6566fbf676e 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -1,7 +1,7 @@ // Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use storage::Hashable; +use firewood_storage::Hashable; use crate::proof::Proof; diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 1ce7013035b2..c606024987fc 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -4,14 +4,14 @@ use crate::merkle::{Key, Value}; use crate::v2::api; +use firewood_storage::{ + BranchNode, Child, FileIoError, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader, +}; use futures::stream::FusedStream; use futures::{Stream, StreamExt}; use std::cmp::Ordering; use std::iter::once; use std::task::Poll; -use storage::{ - BranchNode, Child, FileIoError, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader, -}; /// Represents an ongoing iteration over a node and its children. enum IterationNode { @@ -612,7 +612,7 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { mod tests { use std::sync::Arc; - use storage::{MemStore, MutableProposal, NodeStore}; + use firewood_storage::{MemStore, MutableProposal, NodeStore}; use crate::merkle::Merkle; diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 2aea7508128d..6be79736208b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -5,10 +5,10 @@ use crate::manager::RevisionManagerError; use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; use async_trait::async_trait; +use firewood_storage::{FileIoError, TrieHash}; use futures::Stream; use std::fmt::Debug; use std::sync::Arc; -use storage::{FileIoError, TrieHash}; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with @@ -33,7 +33,7 @@ impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// in time /// - They are used to provide integrity at different points in a /// proof -pub type HashKey = storage::TrieHash; +pub type HashKey = firewood_storage::TrieHash; /// A key/value pair operation. Only put (upsert) and delete are /// supported diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 4c2f19f9679e..caac973ec54c 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,8 +1,26 @@ [package] -name = "fwdctl" +name = "firewood-fwdctl" version = "0.0.5" edition = "2024" rust-version = "1.85.0" +authors = [ + "Dan Laine ", + "Dan Sover ", + "Hao Hao ", + "Richard Pringle ", + "Ron Kuris ", + "Sam Batschelet ", + "xinifinity <113067541+xinifinity@users.noreply.github.com>", + "zdf ", +] +description = "Command-line tool for Firewood, an embedded key-value store optimized for blockchain state." +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" + +[[bin]] +name = "fwdctl" +path = "src/main.rs" [dependencies] firewood = { version = "0.0.5", path = "../firewood" } diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 513844cdf346..3a4205595160 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,7 +17,7 @@ test = false bench = false [dependencies] -firewood = { version = "0.0.4", path = "../firewood" } +firewood = { version = "0.0.5", path = "../firewood" } prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.13.0", features = ["tls-ring"] } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 53f81a3ea5ec..a0071e099400 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,8 +1,17 @@ [package] -name = "storage" +name = "firewood-storage" version = "0.0.5" edition = "2024" rust-version = "1.85.0" +authors = [ + "Aaron Buchwald ", + "Ron Kuris ", + "Suyan Qu <36519575+qusuyan@users.noreply.github.com>", +] +description = "Storage layer for Firewood, an embedded key-value store optimized for blockchain state." +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 2e873dc394ce..240c5caf49ec 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -9,9 +9,9 @@ use std::os::raw::c_int; use bincode::Options; use criterion::profiler::Profiler; use criterion::{Criterion, criterion_group, criterion_main}; +use firewood_storage::{LeafNode, Node, Path}; use pprof::ProfilerGuard; use smallvec::SmallVec; -use storage::{LeafNode, Node, Path}; use std::path::Path as FsPath; @@ -79,14 +79,14 @@ fn leaf(c: &mut Criterion) { fn branch(c: &mut Criterion) { let mut group = c.benchmark_group("has_value"); - let mut input = Node::Branch(Box::new(storage::BranchNode { + let mut input = Node::Branch(Box::new(firewood_storage::BranchNode { partial_path: Path(SmallVec::from_slice(&[0, 1])), value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()), children: from_fn(|i| { if i == 0 { - Some(storage::Child::AddressWithHash( + Some(firewood_storage::Child::AddressWithHash( NonZeroU64::new(1).unwrap(), - storage::HashType::from([0; 32]), + firewood_storage::HashType::from([0; 32]), )) } else { None @@ -94,13 +94,13 @@ fn branch(c: &mut Criterion) { }), })); let serializer = bincode::DefaultOptions::new().with_varint_encoding(); - let serde_serializer = |b: &mut criterion::Bencher, input: &storage::Node| { + let serde_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { b.iter(|| { serializer.serialize(input).unwrap(); }) }; - let manual_serializer = |b: &mut criterion::Bencher, input: &storage::Node| { + let manual_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { b.iter(|| { let mut bytes = Vec::new(); input.as_bytes(0, &mut bytes); diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 3cde54bf8c01..e7f22e6eb788 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "triehash" +name = "firewood-triehash" version = "0.8.5" authors = ["Parity Technologies ", "Ron Kuris "] description = "In-memory patricia trie operations" diff --git a/triehash/benches/triehash.rs b/triehash/benches/triehash.rs index 11a24ee908a0..981b39251c80 100644 --- a/triehash/benches/triehash.rs +++ b/triehash/benches/triehash.rs @@ -8,10 +8,10 @@ use criterion::{Criterion, criterion_group, criterion_main}; use ethereum_types::H256; +use firewood_triehash::trie_root; use keccak_hasher::KeccakHasher; use tiny_keccak::{Hasher, Keccak}; use trie_standardmap::{Alphabet, StandardMap, ValueMode}; -use triehash::trie_root; fn keccak256(input: &[u8]) -> [u8; 32] { let mut keccak256 = Keccak::v256(); diff --git a/triehash/src/lib.rs b/triehash/src/lib.rs index ba27c0a5aa21..79efb331a6c2 100644 --- a/triehash/src/lib.rs +++ b/triehash/src/lib.rs @@ -46,7 +46,7 @@ fn shared_prefix_len(first: &[T], second: &[T]) -> usize { /// ``` /// use hex_literal::hex; /// use ethereum_types::H256; -/// use triehash::ordered_trie_root; +/// use firewood_triehash::ordered_trie_root; /// use keccak_hasher::KeccakHasher; /// /// let v = &["doe", "reindeer"]; @@ -72,7 +72,7 @@ where /// /// ``` /// use hex_literal::hex; -/// use triehash::trie_root; +/// use firewood_triehash::trie_root; /// use ethereum_types::H256; /// use keccak_hasher::KeccakHasher; /// @@ -124,7 +124,7 @@ where /// ``` /// use hex_literal::hex; /// use ethereum_types::H256; -/// use triehash::sec_trie_root; +/// use firewood_triehash::sec_trie_root; /// use keccak_hasher::KeccakHasher; /// /// let v = vec![ From 8cc3ee99f141d0b562e23a49e5900894473da60a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 17 Jun 2025 08:41:53 -0700 Subject: [PATCH 0761/1053] chore: remove remnants of no-std (#968) We don't use no-std and it no longer works anyway Fixes #959 --- triehash/Cargo.toml | 11 ++--------- triehash/src/lib.rs | 22 +++------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index e7f22e6eb788..6cfda0b5724a 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" edition = "2024" [dependencies] -hash-db = { version = "0.16.0", default-features = false } -rlp = { version = "0.6", default-features = false } +hash-db = "0.16.0" +rlp = "0.6" [dev-dependencies] criterion = "0.6.0" @@ -19,13 +19,6 @@ tiny-keccak = { version = "2.0", features = ["keccak"] } trie-standardmap = "0.16.0" hex-literal = "1.0.0" -[features] -default = ["std"] -std = [ - "hash-db/std", - "rlp/std", -] - [[bench]] name = "triehash" path = "benches/triehash.rs" diff --git a/triehash/src/lib.rs b/triehash/src/lib.rs index 79efb331a6c2..4abfc3cfc42d 100644 --- a/triehash/src/lib.rs +++ b/triehash/src/lib.rs @@ -10,25 +10,9 @@ //! //! This module should be used to generate trie root hash. -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(not(feature = "std"))] -extern crate alloc; - -#[cfg(feature = "std")] -mod rstd { - pub use std::collections::BTreeMap; -} - -#[cfg(not(feature = "std"))] -mod rstd { - pub use alloc::collections::BTreeMap; - pub use alloc::vec::Vec; -} - -use core::cmp; -use core::iter::once; -use rstd::*; +use std::cmp; +use std::collections::BTreeMap; +use std::iter::once; use hash_db::Hasher; use rlp::RlpStream; From f5cef841ac592bffcdc302f7e696589320755fec Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 17 Jun 2025 11:06:20 -0700 Subject: [PATCH 0762/1053] perf: remove some unecessary allocs during serialization (#965) In #831, it was mentioned that the benchmark for serialization showed we were slower than serde for a full node. I noticed when walking though the code that we were allocating a fixed sized array in a tight loop. This alloc was also unecessary as the `TrieHash` already implemented `AsRef<[u8]>`. Removing this alloc and copying directly from the existing hash significantly improved the benchmarks. There is a small and significant regression in the leaf benchmark; however, in aggregate the performance overall has improved. https://gist.github.com/demosdemon/fa1258e96c8b09851b18e84c45005a28 --- storage/src/node/branch.rs | 19 +++++++++++-------- storage/src/node/mod.rs | 4 ++-- storage/src/trie_hash.rs | 5 +++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index c5a4ebf596e6..be24d79d48eb 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -5,6 +5,7 @@ use serde::ser::SerializeStruct as _; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; +use crate::node::ExtendableBytes; use crate::{LeafNode, LinearAddress, Node, Path}; use std::fmt::{Debug, Formatter}; @@ -19,7 +20,8 @@ pub type HashType = ethhash::HashOrRlp; pub type HashType = crate::TrieHash; pub(crate) trait Serializable { - fn serialized_bytes(&self) -> Vec; + fn write_to(&self, vec: &mut W); + fn from_reader(reader: R) -> Result where Self: Sized; @@ -49,6 +51,7 @@ mod ethhash { }; use crate::TrieHash; + use crate::node::ExtendableBytes; use super::Serializable; @@ -71,17 +74,17 @@ mod ethhash { } impl Serializable for HashOrRlp { - fn serialized_bytes(&self) -> Vec { + fn write_to(&self, vec: &mut W) { match self { - HashOrRlp::Hash(h) => std::iter::once(0) - .chain(h.as_ref().iter().copied()) - .collect(), + HashOrRlp::Hash(h) => { + vec.push(0); + vec.extend_from_slice(h.as_ref()); + } HashOrRlp::Rlp(r) => { debug_assert!(!r.is_empty()); debug_assert!(r.len() < 32); - std::iter::once(r.len() as u8) - .chain(r.iter().copied()) - .collect() + vec.push(r.len() as u8); + vec.extend_from_slice(r.as_ref()); } } } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index a92875cdd045..5f472202bc90 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -275,7 +275,7 @@ impl Node { for (_, child) in child_iter { if let Child::AddressWithHash(address, hash) = child { encoded.extend_from_slice(&address.get().to_ne_bytes()); - encoded.extend_from_slice(&hash.serialized_bytes()); + hash.write_to(encoded); } else { panic!( "attempt to serialize to persist a branch with a child that is not an AddressWithHash" @@ -289,7 +289,7 @@ impl Node { .expect("writing to vec should succeed"); if let Child::AddressWithHash(address, hash) = child { encoded.extend_from_slice(&address.get().to_ne_bytes()); - encoded.extend_from_slice(&hash.serialized_bytes()); + hash.write_to(encoded); } else { panic!( "attempt to serialize to persist a branch with a child that is not an AddressWithHash" diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 0964406fbfa1..35a46bd725c8 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use sha2::digest::generic_array::GenericArray; use sha2::digest::typenum; +use crate::node::ExtendableBytes; use crate::node::branch::Serializable; /// A hash value inside a merkle trie @@ -83,8 +84,8 @@ impl TrieHash { } impl Serializable for TrieHash { - fn serialized_bytes(&self) -> Vec { - self.0.to_vec() + fn write_to(&self, vec: &mut W) { + vec.extend_from_slice(&self.0); } fn from_reader(mut reader: R) -> Result From 0cf014dbbff4613ffa8fe51ea2be8d6f9683bd88 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 17 Jun 2025 15:13:25 -0700 Subject: [PATCH 0763/1053] fix(storage): Parse and validate database versions (#964) Problem: When loading the database, we do a strict byte equality comparison on the version string. This is insufficient when we bump minor versions periodically. This meant each patch version bump would cause database opens to fail. Solution: Now, we parse the version identifier on load. We validate the version id is valid (to ensure we're opening a firewood database) and if it meets a minimum required version. Currently, the minimum version is set to `firewood 0.0.4` but can change. The choice was mostly arbitrary, but I did want a real version set in order to write a unit test. Resolves: #958 --- storage/Cargo.toml | 1 + storage/src/linear/mod.rs | 2 +- storage/src/nodestore.rs | 242 ++++++++++++++++++++++++++++---------- 3 files changed, 181 insertions(+), 64 deletions(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index a0071e099400..7bedd68b3846 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -38,6 +38,7 @@ coarsetime = "0.1.35" rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } bytes = { version = "1.10.1", optional = true } +semver = "1.0.26" [dev-dependencies] rand = "0.9.0" diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index f8a532b0d302..2e140de81e1b 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -24,7 +24,7 @@ use crate::{CacheReadStrategy, LinearAddress, SharedNode}; pub(super) mod filebacked; pub mod memory; -/// An error that occurs when reading or writing to a [ReadableStorage] or [WritableStorage] +/// An error that occurs when reading or writing to a [ReadableStorage] or [WritableStorage] /// /// This error is used to wrap errors that occur when reading or writing to a file. /// It contains the filename, offset, and context of the error. diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 45d6cbec2dfd..723425eff56b 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::linear::FileIoError; -use crate::logger::trace; +use crate::logger::{debug, trace}; use arc_swap::ArcSwap; use arc_swap::access::DynAccess; use bincode::{DefaultOptions, Options as _}; @@ -37,7 +37,7 @@ use std::fmt::Debug; /// I --> |commit|N("New commit NodeStore<Committed, S>") /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` -use std::io::{Error, ErrorKind, Write}; +use std::io::{Error, ErrorKind}; use std::mem::{offset_of, take}; use std::num::NonZeroU64; use std::ops::Deref; @@ -303,58 +303,9 @@ impl NodeStore { drop(stream); - if header.version != Version::new() { - return Err(storage.file_io_error( - Error::new(ErrorKind::InvalidData, "Incompatible firewood version"), - 0, - Some("header read".to_string()), - )); - } - if header.endian_test != 1 { - return Err(storage.file_io_error( - Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in endianness", - ), - 0, - Some("header read".to_string()), - )); - } - - if header.area_size_hash != area_size_hash().as_slice() { - return Err(storage.file_io_error( - Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in area size hash", - ), - 0, - Some("header read".to_string()), - )); - } - - #[cfg(not(feature = "ethhash"))] - if header.ethhash != 0 { - return Err(storage.file_io_error( - Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created with ethhash enabled", - ), - 0, - Some("header read".to_string()), - )); - } - - #[cfg(feature = "ethhash")] - if header.ethhash != 1 { - return Err(storage.file_io_error( - Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created without ethhash enabled", - ), - 0, - Some("header read".to_string()), - )); - } + header + .validate() + .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; let mut nodestore = Self { header, @@ -691,18 +642,107 @@ struct Version { } impl Version { - const SIZE: u64 = std::mem::size_of::() as u64; + const SIZE: usize = size_of::(); + + /// Version >= 0.0.4 + /// + /// Increase as needed to set the minimum required version of `firewood-storage` for + /// compatibility checks. + /// + /// We may want to add migrations if we need to add a breaking change. + const BASE_VERSION: semver::Comparator = semver::Comparator { + op: semver::Op::GreaterEq, + major: 0, + minor: Some(0), + patch: Some(4), + pre: semver::Prerelease::EMPTY, + }; + + /// Validates that the version identifier is valid and compatible with the current + /// build of firewood. + /// + /// # Errors + /// + /// - If the token contains invalid utf-8 bytes (nul is allowed). + /// - If the token does not start with "firewood ". + /// - If the version is not parsable by [`semver::Version`]. + /// - If the version is not compatible with the current build of firewood. + /// - Currently, the minimum required version is 0.0.4. + fn validate(&self) -> Result<(), Error> { + let version = std::str::from_utf8(&self.bytes).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid database version: invalid utf-8: {e} (original: [{:032x}])", + u128::from_be_bytes(self.bytes) + ), + ) + })?; + + // strip trailling nuls as they're only for padding + let version = version.trim_end_matches('\0'); + + // strip magic prefix or error + let version = version.strip_prefix("firewood ").ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid database version: does not start with magic 'firewood ': {version}", + ), + ) + })?; - /// construct a [Version] header from the firewood version + // Version strings from CARGO_PKG_VERSION are guaranteed to be parsable by + // semver (cargo uses the same library). + let version = semver::Version::parse(version).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid version string: unable to parse `{version}` as a semver string: {e}" + ), + ) + })?; + + // verify base compatibility version + if !Self::BASE_VERSION.matches(&version) { + return Err(Error::new( + ErrorKind::InvalidData, + format!( + "Database was created with firewood version {version}; however, this build of firewood requires version {}", + Self::BASE_VERSION, + ), + )); + } + + debug!( + "Database version is valid: {version} {}", + Self::BASE_VERSION + ); + Ok(()) + } + + /// Construct a [`Version`] instance for the current build of firewood. fn new() -> Self { - let mut version_bytes: [u8; Self::SIZE as usize] = Default::default(); - let version = env!("CARGO_PKG_VERSION"); - let _ = version_bytes - .as_mut_slice() - .write_all(format!("firewood {}", version).as_bytes()); - Self { - bytes: version_bytes, + // Note that with this magic token of 9 bytes, we can store a version string of + // up to 7 bytes. If we always include the major, minor, and patch versions, + // then no more than two of three can be 2 digits long. + const VERSION_STR: &str = concat!("firewood ", env!("CARGO_PKG_VERSION")); + const { + assert!( + VERSION_STR.len() <= Version::SIZE, + concat!( + "Database version string `firewood ", + env!("CARGO_PKG_VERSION"), + "` is too long for the Version struct! Update Cargo.toml or modify this code.", + ), + ); } + + // pad with nul bytes + let mut bytes = [0u8; Version::SIZE]; + bytes[..VERSION_STR.len()].copy_from_slice(VERSION_STR.as_bytes()); + + Self { bytes } } } @@ -755,6 +795,68 @@ impl NodeStoreHeader { } } + fn validate(&self) -> Result<(), Error> { + trace!("Checking version..."); + self.version.validate()?; + + trace!("Checking endianness..."); + self.validate_endian_test()?; + + trace!("Checking area size hash..."); + self.validate_area_size_hash()?; + + trace!("Checking if db ethhash flag matches build feature..."); + self.validate_ethhash()?; + + Ok(()) + } + + fn validate_endian_test(&self) -> Result<(), Error> { + if self.endian_test == 1 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in endianness", + )) + } + } + + fn validate_area_size_hash(&self) -> Result<(), Error> { + if self.area_size_hash == area_size_hash().as_slice() { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in area size hash", + )) + } + } + + #[cfg(not(feature = "ethhash"))] + fn validate_ethhash(&self) -> Result<(), Error> { + if self.ethhash == 0 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created with ethhash enabled", + )) + } + } + + #[cfg(feature = "ethhash")] + fn validate_ethhash(&self) -> Result<(), Error> { + if self.ethhash == 1 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created without ethhash enabled", + )) + } + } + // return the size of this nodestore pub fn size(&self) -> u64 { self.size @@ -1516,6 +1618,20 @@ mod tests { assert_eq!(header.free_lists, empty_free_list); } + #[test] + fn test_version_new_is_valid() { + Version::new() + .validate() + .expect("Version::new() should always be valid"); + } + + #[test_case(*b"invalid\0\0\0\0\0\0\0\0\0")] + #[test_case(*b"avalanche 0.1.0\0")] + #[test_case(*b"firewood 0.0.1\0\0")] + fn test_invalid_version_strings(bytes: [u8; 16]) { + assert!(Version { bytes }.validate().is_err()); + } + #[test_case(BranchNode { partial_path: Path::from([6, 7, 8]), value: Some(vec![9, 10, 11].into_boxed_slice()), From 32f021cd8660805d2f2cc1ee80cb8249875e13f5 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 17 Jun 2025 16:11:03 -0700 Subject: [PATCH 0764/1053] build: move lints to the workspace (#957) Previously, the lints were defined in each crate's `Cargo.toml`. This commit moves the lints to the workspace's `Cargo.toml` file. This allows for a more consistent linting configuration across all crates in the workspace and simplifies the management of lint rules. This change also ensures that all crates in the workspace adhere to the same linting standards, improving code quality and maintainability. New crates added to the workspace via `cargo new` will automatically inherit the linting rules defined in the workspace's `Cargo.toml`. However, manually crafted crates will need to ensure the stanza ```toml [lints] workspace = true ``` is included in `Cargo.toml`to benefit from the workspace-wide linting rules. Caveats: - This change adds an excessive amount of warnings because `clippy::pedantic` is very easy to violate and is now enabled workspace wide. This is also why I included pedantic lints with a lower priority, so that any individual lint within pedantic can be separately disabled with an `allow`. - Packages cannot override the workspace lints within their `Cargo.toml`. This can be worked around by adding the lint rule change via a crate attribute in the crate's `lib.rs` or `main.rs`. See the `ffi` crate for an example of this workaround. Resolves #951 --- .github/workflows/ci.yaml | 38 ++++- .github/workflows/default-branch-cache.yaml | 4 +- Cargo.toml | 12 ++ benchmark/Cargo.toml | 3 + benchmark/README.md | 2 +- benchmark/src/create.rs | 5 + benchmark/src/main.rs | 19 ++- benchmark/src/single.rs | 13 ++ benchmark/src/tenkrandom.rs | 5 + benchmark/src/zipf.rs | 21 +++ ffi/Cargo.toml | 9 +- ffi/src/lib.rs | 10 +- firewood/Cargo.toml | 10 +- firewood/benches/hashops.rs | 2 +- firewood/src/db.rs | 26 +++- firewood/src/lib.rs | 14 +- firewood/src/manager.rs | 40 ++++- firewood/src/merkle.rs | 71 ++++++--- firewood/src/proof.rs | 9 ++ firewood/src/stream.rs | 56 ++++--- firewood/src/v2/api.rs | 13 +- firewood/src/v2/emptydb.rs | 8 +- firewood/src/v2/propose.rs | 4 +- firewood/tests/common/mod.rs | 15 +- fwdctl/Cargo.toml | 11 +- fwdctl/src/create.rs | 2 +- fwdctl/src/delete.rs | 2 +- fwdctl/src/dump.rs | 21 ++- fwdctl/src/get.rs | 2 +- fwdctl/src/graph.rs | 2 +- fwdctl/src/insert.rs | 2 +- grpc-testtool/Cargo.toml | 12 +- storage/Cargo.toml | 3 + storage/benches/serializer.rs | 17 +- storage/src/hashednode.rs | 9 +- storage/src/hashers/ethhash.rs | 19 ++- storage/src/hashers/merkledb.rs | 17 +- storage/src/lib.rs | 14 +- storage/src/linear/filebacked.rs | 25 ++- storage/src/linear/memory.rs | 18 ++- storage/src/linear/mod.rs | 22 ++- storage/src/logger.rs | 6 +- storage/src/node/branch.rs | 43 +++-- storage/src/node/mod.rs | 51 ++++-- storage/src/node/path.rs | 25 ++- storage/src/nodestore.rs | 164 ++++++++++++-------- storage/src/trie_hash.rs | 7 +- triehash/Cargo.toml | 3 + triehash/benches/triehash.rs | 17 +- triehash/src/lib.rs | 19 ++- 50 files changed, 696 insertions(+), 246 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 526c45305b5d..71ff6d9c8cd2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,10 +40,9 @@ jobs: with: shared-key: ${{ matrix.profile-key }} - name: Check - run: cargo check ${{ matrix.profile-args }} --workspace --tests --examples --benches + run: cargo check ${{ matrix.profile-args }} --workspace --all-targets - name: Build - run: cargo build ${{ matrix.profile-args }} --workspace --tests --examples --benches - + run: cargo build ${{ matrix.profile-args }} --workspace --all-targets no-rust-cache-lint: runs-on: ubuntu-latest @@ -59,6 +58,35 @@ jobs: - name: Format run: cargo fmt -- --check + rust-lint-pr-comments: + # caveat emptor: actions-rs/clippy-check can only write comments on pull requests + # not originating from a fork. + if: github.event_name == 'pull_request' && github.repository == github.event.pull_request.head.repo.full_name + needs: build + runs-on: ubuntu-latest + strategy: + matrix: + include: + - profile-key: debug-no-features + profile-args: "" + - profile-key: debug-ethhash-logger + profile-args: "--features ethhash,logger" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: ${{ matrix.profile-key }} + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + name: ${{ matrix.profile-key }} + args: ${{ matrix.profile-args }} --workspace --all-targets + rust-lint: needs: build runs-on: ubuntu-latest @@ -80,7 +108,7 @@ jobs: save-if: "false" shared-key: ${{ matrix.profile-key }} - name: Clippy - run: cargo clippy ${{ matrix.profile-args }} --tests --examples --benches -- -D warnings + run: cargo clippy ${{ matrix.profile-args }} --workspace --all-targets -- -D warnings test: needs: build @@ -191,7 +219,7 @@ jobs: working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... - + firewood-ethhash-differential-fuzz: needs: build runs-on: ubuntu-latest diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index f365104d32e7..99de27d76e06 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -25,6 +25,6 @@ jobs: save-if: "false" shared-key: "debug-no-features" - name: Check - run: cargo check --workspace --tests --examples --benches + run: cargo check --workspace --all-targets - name: Build - run: cargo build --workspace --tests --examples --benches + run: cargo build --workspace --all-targets diff --git a/Cargo.toml b/Cargo.toml index a83196116338..22b37ba526f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,15 @@ codegen-units = 1 lto = "fat" debug = false inherits = "release" + +[workspace.lints.rust] +unsafe_code = "deny" + +[workspace.lints.clippy] +unwrap_used = "warn" +indexing_slicing = "warn" +explicit_deref_methods = "warn" +missing_const_for_fn = "warn" +arithmetic_side_effects = "warn" +# lower the priority of pedantic to allow overriding the lints it includes +pedantic = { level = "warn", priority = -1 } diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 64cccd848de1..f58c329ae100 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -42,3 +42,6 @@ tikv-jemallocator = "0.6.0" [features] logger = ["firewood/logger"] + +[lints] +workspace = true diff --git a/benchmark/README.md b/benchmark/README.md index 30d78e4094dd..75d7ec80022a 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -130,7 +130,7 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a 4. Run the scripts as described above, including the grafana installation. 5. Log in to grafana on a. username: admin, password: admin -6. When prompted, change the password (firewood_is_fast) +6. When prompted, change the password (`firewood_is_fast`) 7. On the left panel, click "Data Sources" a. Select "Prometheus" b. For the URL, use diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index 1c2490996509..cef1b4af8903 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -1,6 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 1 occurrences after enabling the lint." +)] + use std::error::Error; use std::time::Instant; diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index c5af1c7608ff..a76a34c8508b 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -2,6 +2,18 @@ // See the file LICENSE.md for licensing terms. // +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::match_same_arms, + reason = "Found 1 occurrences after enabling the lint." +)] #![doc = include_str!("../README.md")] use clap::{Parser, Subcommand, ValueEnum}; @@ -202,9 +214,10 @@ async fn main() -> Result<(), Box> { fastrace::set_reporter(reporter, Config::default()); } - if args.test_name == TestName::Single && args.global_opts.batch_size > 1000 { - panic!("Single test is not designed to handle batch sizes > 1000"); - } + assert!( + !(args.test_name == TestName::Single && args.global_opts.batch_size > 1000), + "Single test is not designed to handle batch sizes > 1000" + ); env_logger::Builder::new() .filter_level(match args.global_opts.log_level.as_str() { diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index 6cda4bdc9ce5..846b646b10cd 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -1,6 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_sign_loss, + reason = "Found 1 occurrences after enabling the lint." +)] + use crate::TestRunner; use firewood::db::{BatchOp, Db}; use firewood::v2::api::{Db as _, Proposal as _}; diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 78c9ab50e60c..926921b6db52 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -1,6 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 7 occurrences after enabling the lint." +)] + use std::error::Error; use std::time::Instant; diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 30111c0a5a8b..29b9e95327c3 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -1,6 +1,27 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_precision_loss, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_sign_loss, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unwrap_used, + reason = "Found 1 occurrences after enabling the lint." +)] + use crate::TestRunner; use firewood::db::{BatchOp, Db}; use firewood::v2::api::{Db as _, Proposal as _}; diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 33f419231d75..e0a4a36d578c 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -38,10 +38,5 @@ ethhash = ["firewood/ethhash"] [build-dependencies] cbindgen = "0.29.0" -[lints.clippy] -unwrap_used = "warn" -indexing_slicing = "warn" -explicit_deref_methods = "warn" -missing_const_for_fn = "warn" -pedantic = "warn" -arithmetic_side_effects = "warn" +[lints] +workspace = true diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ccb6ffbd2039..dbd7146ce702 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,5 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. + +#![allow( + unsafe_code, + reason = "This is an FFI library, so unsafe code is expected." +)] + use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr, c_char}; use std::fmt::{self, Display, Formatter}; @@ -240,7 +246,7 @@ pub struct KeyValue { /// The new root hash of the database, in Value form. /// A `Value` containing {0, "error message"} if the commit failed. /// -/// # Errors +/// # Errors /// /// * `"key-value pair is null"` - A `KeyValue` struct is null /// * `"db should be non-null"` - The database handle is null @@ -723,7 +729,7 @@ pub unsafe extern "C" fn fwd_free_value(value: *mut Value) { }; drop(recreated_box); } else { - let raw_str = value.data as *mut c_char; + let raw_str = value.data.cast_mut().cast::(); let cstr = unsafe { CString::from_raw(raw_str) }; drop(cstr); } diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index a24d676c41a4..2c4aaf2f44a7 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -64,15 +64,11 @@ hash-db = "0.16.0" name = "hashops" harness = false -[lints.clippy] -unwrap_used = "warn" -indexing_slicing = "warn" -explicit_deref_methods = "warn" -missing_const_for_fn = "warn" -arithmetic_side_effects = "warn" - [target.'cfg(target_os = "linux")'.dependencies] firewood-storage = { version = "0.0.5", path = "../storage", features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] firewood-storage = { version = "0.0.5", path = "../storage" } + +[lints] +workspace = true diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index e2451da401a8..b010a72803c6 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -131,7 +131,7 @@ fn bench_db(criterion: &mut Criterion) { .await .unwrap(); - db.propose(batch_ops).await.unwrap().commit().await.unwrap() + db.propose(batch_ops).await.unwrap().commit().await.unwrap(); }, BatchSize::SmallInput, ); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 099a37ca26a9..817aecd9a9c6 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1,6 +1,23 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::missing_errors_doc, + reason = "Found 12 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_panics_doc, + reason = "Found 5 occurrences after enabling the lint." +)] +#![expect( + clippy::needless_pass_by_value, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unused_async, + reason = "Found 2 occurrences after enabling the lint." +)] + use crate::merkle::Merkle; use crate::proof::{Proof, ProofNode}; use crate::range_proof::RangeProof; @@ -312,7 +329,7 @@ impl Db { let merkle = Merkle::from(latest_rev_nodestore); // TODO: This should be a stream let output = merkle.dump()?; - write!(w, "{}", output) + write!(w, "{output}") } /// Get a copy of the database metrics @@ -466,8 +483,13 @@ impl Proposal<'_> { } #[cfg(test)] -#[expect(clippy::unwrap_used)] mod test { + #![expect(clippy::unwrap_used)] + #![expect( + clippy::default_trait_access, + reason = "Found 1 occurrences after enabling the lint." + )] + use std::ops::{Deref, DerefMut}; use std::path::PathBuf; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index a9043e5c0bf2..6e0af50c7846 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -63,13 +63,13 @@ //! Firewood is built by layers of abstractions that totally decouple the layout/representation //! of the data on disk from the actual logical data structure it retains: //! -//! - The storage module has a [firewood_storage::NodeStore] which has a generic parameter identifying +//! - The storage module has a [`firewood_storage::NodeStore`] which has a generic parameter identifying //! the state of the nodestore, and a storage type. //! //! There are three states for a nodestore: -//! - [firewood_storage::Committed] for revisions that are on disk -//! - [firewood_storage::ImmutableProposal] for revisions that are proposals against committed versions -//! - [firewood_storage::MutableProposal] for revisions where nodes are still being added. +//! - [`firewood_storage::Committed`] for revisions that are on disk +//! - [`firewood_storage::ImmutableProposal`] for revisions that are proposals against committed versions +//! - [`firewood_storage::MutableProposal`] for revisions where nodes are still being added. //! //! For more information on these node states, see their associated documentation. //! @@ -85,17 +85,17 @@ //! //! In short, a Read-Modify-Write (RMW) style normal operation flow is as follows in Firewood: //! -//! - Create a [firewood_storage::MutableProposal] [firewood_storage::NodeStore] from the most recent [firewood_storage::Committed] one. +//! - Create a [`firewood_storage::MutableProposal`] [`firewood_storage::NodeStore`] from the most recent [`firewood_storage::Committed`] one. //! - Traverse the trie, starting at the root. Make a new root node by duplicating the existing //! root from the committed one and save that in memory. As you continue traversing, make copies //! of each node accessed if they are not already in memory. //! //! - Make changes to the trie, in memory. Each node you've accessed is currently in memory and is -//! owned by the [firewood_storage::MutableProposal]. Adding a node simply means adding a reference to it. +//! owned by the [`firewood_storage::MutableProposal`]. Adding a node simply means adding a reference to it. //! //! - If you delete a node, mark it as deleted in the proposal and remove the child reference to it. //! -//! - After making all mutations, convert the [firewood_storage::MutableProposal] to an [firewood_storage::ImmutableProposal]. This +//! - After making all mutations, convert the [`firewood_storage::MutableProposal`] to an [`firewood_storage::ImmutableProposal`]. This //! involves walking the in-memory trie and looking for nodes without disk addresses, then assigning //! them from the freelist of the parent. This gives the node an address, but it is stil in //! memory. diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 136d090b3986..c4d8b368d50c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -1,6 +1,37 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::unnecessary_wraps, + reason = "Found 2 occurrences after enabling the lint." +)] +#![cfg_attr( + feature = "ethhash", + expect( + clippy::used_underscore_binding, + reason = "Found 1 occurrences after enabling the lint." + ) +)] +#![cfg_attr( + not(feature = "ethhash"), + expect( + clippy::unused_self, + reason = "Found 1 occurrences after enabling the lint." + ) +)] +#![expect( + clippy::cast_precision_loss, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::default_trait_access, + reason = "Found 3 occurrences after enabling the lint." +)] +#![expect( + clippy::needless_pass_by_value, + reason = "Found 1 occurrences after enabling the lint." +)] + use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; @@ -82,9 +113,10 @@ impl RevisionManager { truncate, config.cache_read_strategy, )?); - let nodestore = match truncate { - true => Arc::new(NodeStore::new_empty_committed(storage.clone())?), - false => Arc::new(NodeStore::open(storage.clone())?), + let nodestore = if truncate { + Arc::new(NodeStore::new_empty_committed(storage.clone())?) + } else { + Arc::new(NodeStore::open(storage.clone())?) }; let mut manager = Self { max_revisions: config.max_revisions, @@ -219,7 +251,7 @@ impl RevisionManager { .retain(|p| !Arc::ptr_eq(&proposal, p) && Arc::strong_count(p) > 1); // then reparent any proposals that have this proposal as a parent - for p in self.proposals.iter() { + for p in &self.proposals { proposal.commit_reparent(p); } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 9004bb15d861..6fb71a6ba691 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,6 +1,27 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::cast_possible_truncation, + reason = "Found 5 occurrences after enabling the lint." +)] +#![expect( + clippy::match_same_arms, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_errors_doc, + reason = "Found 6 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_panics_doc, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::too_many_lines, + reason = "Found 1 occurrences after enabling the lint." +)] + use crate::proof::{Proof, ProofError, ProofNode}; use crate::range_proof::RangeProof; use crate::stream::{MerkleKeyValueStream, PathIterator}; @@ -193,7 +214,7 @@ impl Merkle { .value() .map(|value| ValueDigest::Value(value.to_vec().into_boxed_slice())), child_hashes, - }) + }); } Ok(Proof(proof.into_boxed_slice())) @@ -369,7 +390,11 @@ impl Merkle { }; let inserted = seen.insert(child_addr); - if !inserted { + if inserted { + writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]") + .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; + self.dump_node(child_addr, child_hash, seen, writer)?; + } else { // We have already seen this child, which shouldn't happen. // Indicate this with a red edge. writeln!( @@ -377,10 +402,6 @@ impl Merkle { " {addr} -> {child_addr}[label=\"{childidx} (dup)\" color=red]" ) .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; - } else { - writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]") - .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; - self.dump_node(child_addr, child_hash, seen, writer)?; } } } @@ -389,7 +410,7 @@ impl Merkle { writeln!(writer, "\" shape=rect]") .map_err(|e| FileIoError::from_generic_no_file(e, "write leaf"))?; } - }; + } Ok(()) } @@ -433,8 +454,9 @@ impl TryFrom>> } impl Merkle> { - /// Convert a merkle backed by an MutableProposal into an ImmutableProposal + /// Convert a merkle backed by an `MutableProposal` into an `ImmutableProposal` /// TODO: We probably don't need this function + #[must_use] pub fn hash(self) -> Merkle, S>> { // I think this is only used in testing self.try_into().expect("failed to convert") @@ -883,7 +905,7 @@ impl Merkle> { // the prefix matched only a leaf, so we remove it and indicate only one item was removed *deleted = deleted.saturating_add(1); } - }; + } Ok(None) } (_, Some(_)) => { @@ -984,7 +1006,7 @@ impl Merkle> { // a KV pair was in the branch itself *deleted = deleted.saturating_add(1); } - for children in branch.children.iter_mut() { + for children in &mut branch.children { // read the child node let child = match children { Some(Child::Node(node)) => node, @@ -1046,8 +1068,17 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { } #[cfg(test)] -#[expect(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { + #![expect(clippy::indexing_slicing, clippy::unwrap_used)] + #![expect( + clippy::items_after_statements, + reason = "Found 1 occurrences after enabling the lint." + )] + #![expect( + clippy::unnecessary_wraps, + reason = "Found 1 occurrences after enabling the lint." + )] + use super::*; use firewood_storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; use rand::rngs::StdRng; @@ -1101,7 +1132,7 @@ mod tests { #[test] fn insert_one() { let mut merkle = create_in_memory_merkle(); - merkle.insert(b"abc", Box::new([])).unwrap() + merkle.insert(b"abc", Box::new([])).unwrap(); } fn create_in_memory_merkle() -> Merkle> { @@ -1913,8 +1944,8 @@ mod tests { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); ( - rng.random_range(1..max_len0 + 1), - rng.random_range(1..max_len1 + 1), + rng.random_range(1..=max_len0), + rng.random_range(1..=max_len1), ) }; let key: Vec = (0..len0) @@ -1954,8 +1985,8 @@ mod tests { fn test_delete_some() { let items = (0..100) .map(|n| { - let key = format!("key{}", n); - let val = format!("value{}", n); + let key = format!("key{n}"); + let val = format!("value{n}"); (key.as_bytes().to_vec(), val.as_bytes().to_vec()) }) .collect::, Vec)>>(); @@ -1983,8 +2014,8 @@ mod tests { let (len0, len1): (usize, usize) = { let mut rng = rng.borrow_mut(); ( - rng.random_range(1..max_len0 + 1), - rng.random_range(1..max_len1 + 1), + rng.random_range(1..=max_len0), + rng.random_range(1..=max_len1), ) }; let key: Vec = (0..len0) @@ -2009,7 +2040,7 @@ mod tests { let mut hashes = Vec::new(); - for (k, v) in items.iter() { + for (k, v) in &items { let root_hash = merkle .nodestore .root_address_and_hash()? @@ -2038,7 +2069,7 @@ mod tests { #[allow(clippy::indexing_slicing)] let expected_hash = &hashes[i]; let key = key.iter().fold(String::new(), |mut s, b| { - let _ = write!(s, "{:02x}", b); + let _ = write!(s, "{b:02x}"); s }); assert_eq!( diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 0f3fa5914b8e..3bff4898ec98 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -1,6 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::missing_errors_doc, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::needless_continue, + reason = "Found 1 occurrences after enabling the lint." +)] + use firewood_storage::{ BranchNode, FileIoError, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index c606024987fc..d0d177885ac1 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1,6 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unnecessary_wraps, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::used_underscore_binding, + reason = "Found 3 occurrences after enabling the lint." +)] + use crate::merkle::{Key, Value}; use crate::v2::api; @@ -49,13 +62,13 @@ impl std::fmt::Debug for IterationNode { #[derive(Debug)] enum NodeStreamState { - /// The iterator state is lazily initialized when poll_next is called + /// The iterator state is lazily initialized when `poll_next` is called /// for the first time. The iteration start key is stored here. StartFromKey(Key), Iterating { /// Each element is a node that will be visited (i.e. returned) /// or has been visited but has unvisited children. - /// On each call to poll_next we pop the next element. + /// On each call to `poll_next` we pop the next element. /// If it's unvisited, we visit it. /// If it's visited, we push its next child onto this stack. iter_stack: Vec, @@ -285,7 +298,7 @@ fn get_iterator_intial_state( #[derive(Debug)] enum MerkleKeyValueStreamState<'a, T> { - /// The iterator state is lazily initialized when poll_next is called + /// The iterator state is lazily initialized when `poll_next` is called /// for the first time. The iteration start key is stored here. _Uninitialized(Key), /// The iterator works by iterating over the nodes in the merkle trie @@ -329,7 +342,7 @@ impl FusedStream for MerkleKeyValueStream<'_, T> { } impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { - /// Construct a [MerkleKeyValueStream] that will iterate over all the key-value pairs in `merkle` + /// Construct a [`MerkleKeyValueStream`] that will iterate over all the key-value pairs in `merkle` /// starting from a particular key pub fn from_key>(merkle: &'a T, key: K) -> Self { Self { @@ -547,9 +560,9 @@ impl Iterator for PathIterator<'_, '_, T> { /// Takes in an iterator over a node's partial path and an iterator over the /// unmatched portion of a key. /// The first returned element is: -/// * [Ordering::Less] if the node is before the key. -/// * [Ordering::Equal] if the node is a prefix of the key. -/// * [Ordering::Greater] if the node is after the key. +/// * [`Ordering::Less`] if the node is before the key. +/// * [`Ordering::Equal`] if the node is a prefix of the key. +/// * [`Ordering::Greater`] if the node is after the key. /// /// The second returned element is the unmatched portion of the key after the /// partial path has been matched. @@ -649,7 +662,7 @@ mod tests { let mut stream = merkle.path_iter(key).unwrap(); let node = match stream.next() { Some(Ok(item)) => item, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => { assert!(!should_yield_elt); return; @@ -680,7 +693,7 @@ mod tests { let node = match stream.next() { Some(Ok(node)) => node, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] @@ -692,7 +705,7 @@ mod tests { let node = match stream.next() { Some(Ok(node)) => node, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] @@ -715,7 +728,7 @@ mod tests { let node = match stream.next() { Some(Ok(node)) => node, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] @@ -742,7 +755,7 @@ mod tests { let node = match stream.next() { Some(Ok(node)) => node, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), }; // TODO: make this branch factor 16 compatible @@ -754,7 +767,7 @@ mod tests { let node = match stream.next() { Some(Ok(node)) => node, - Some(Err(e)) => panic!("{:?}", e), + Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] @@ -1051,9 +1064,7 @@ mod tests { .unwrap() .unwrap(), (expected_key.into_boxed_slice(), expected_value), - "i: {}, j: {}", - i, - j, + "i: {i}, j: {j}", ); } } @@ -1071,9 +1082,7 @@ mod tests { .unwrap() .unwrap(), (expected_key.into_boxed_slice(), expected_value), - "i: {}, j: {}", - i, - j, + "i: {i}, j: {j}", ); } if i == max { @@ -1085,8 +1094,7 @@ mod tests { .unwrap() .unwrap(), (vec![i + 1, 0].into_boxed_slice(), vec![i + 1, 0]), - "i: {}", - i, + "i: {i}", ); } } @@ -1107,13 +1115,13 @@ mod tests { key_values.push(last); } - for kv in key_values.iter() { + for kv in &key_values { merkle.insert(kv, kv.clone().into_boxed_slice()).unwrap(); } let mut stream = merkle.key_value_iter(); - for kv in key_values.iter() { + for kv in &key_values { let next = futures::StreamExt::next(&mut stream) .await .unwrap() @@ -1192,7 +1200,7 @@ mod tests { assert!(key_values[0] < key_values[1]); assert!(key_values[1] < key_values[2]); - for key in key_values.iter() { + for key in &key_values { merkle.insert(key, key.clone().into_boxed_slice()).unwrap(); } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 6be79736208b..3850c22cc927 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,6 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::iter_not_returning_iterator, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_errors_doc, + reason = "Found 3 occurrences after enabling the lint." +)] + use crate::manager::RevisionManagerError; use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; @@ -127,11 +136,11 @@ pub enum Error { #[error("Range too small")] RangeTooSmall, - /// Request RangeProof for empty trie + /// Request `RangeProof` for empty trie #[error("request RangeProof for empty trie")] RangeProofOnEmptyTrie, - /// Request RangeProof for empty range + /// Request `RangeProof` for empty range #[error("the latest revision is empty and has no root hash")] LatestIsEmpty, diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 0aa27d063f6c..6bec5499ff34 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -10,14 +10,14 @@ use async_trait::async_trait; use futures::Stream; use std::sync::Arc; -/// An EmptyDb is a simple implementation of api::Db +/// An `EmptyDb` is a simple implementation of `api::Db` /// that doesn't store any data. It contains a single -/// HistoricalImpl that has no keys or values +/// `HistoricalImpl` that has no keys or values #[derive(Debug)] pub struct EmptyDb; -/// HistoricalImpl is always empty, and there is only one, -/// since nothing can be committed to an EmptyDb. +/// `HistoricalImpl` is always empty, and there is only one, +/// since nothing can be committed to an `EmptyDb`. #[derive(Debug)] pub struct HistoricalImpl; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index d785b6f7c007..2ca7c269c621 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -36,10 +36,10 @@ impl Clone for ProposalBase { } } -/// A proposal is created either from the [[crate::v2::api::Db]] object +/// A proposal is created either from the [[`crate::v2::api::Db`]] object /// or from another proposal. Proposals are owned by the /// caller. A proposal can only be committed if it has a -/// base of the current revision of the [[crate::v2::api::Db]]. +/// base of the current revision of the [[`crate::v2::api::Db`]]. #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid /// graph LR diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs index deee2e775927..7b26006f5866 100644 --- a/firewood/tests/common/mod.rs +++ b/firewood/tests/common/mod.rs @@ -1,6 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::used_underscore_binding, + reason = "Found 3 occurrences after enabling the lint." +)] +#![expect( + clippy::used_underscore_items, + reason = "Found 1 occurrences after enabling the lint." +)] + use std::env::temp_dir; use std::fs::remove_file; use std::ops::Deref; @@ -27,6 +36,10 @@ pub struct TestDb { impl TestDbCreator { #[expect(clippy::unwrap_used)] + #[allow( + clippy::missing_panics_doc, + reason = "Found 1 occurrences after enabling the lint." + )] pub async fn _create(self) -> TestDb { let path = self.path.clone().unwrap_or_else(|| { let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") @@ -57,7 +70,7 @@ impl Deref for TestDb { } impl TestDb { - /// reopen the database, consuming the old TestDb and giving you a new one + /// reopen the database, consuming the old `TestDb` and giving you a new one pub async fn _reopen(mut self) -> Self { let mut creator = self.creator.clone(); self.preserve_on_drop = true; diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index caac973ec54c..1427b4564a67 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -38,12 +38,5 @@ assert_cmd = "2.0.13" predicates = "3.1.0" serial_test = "3.0.0" -[lints.rust] -unsafe_code = "deny" - -[lints.clippy] -unwrap_used = "warn" -indexing_slicing = "warn" -explicit_deref_methods = "warn" -missing_const_for_fn = "warn" -arithmetic_side_effects = "warn" +[lints] +workspace = true diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 5a685d7c9519..698116674060 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -54,7 +54,7 @@ pub(super) fn new(opts: &Options) -> DbConfig { pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db_config = new(opts); - log::debug!("database configuration parameters: \n{:?}\n", db_config); + log::debug!("database configuration parameters: \n{db_config:?}\n"); Db::new(opts.name.clone(), db_config).await?; println!("created firewood database in {:?}", opts.name); diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 190ce4cb5572..6c9319b768e7 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -23,7 +23,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("deleting key {:?}", opts); + log::debug!("deleting key {opts:?}"); let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 84f6e23eb483..f7cdf6771dd3 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,6 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::doc_link_with_quotes, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unnecessary_wraps, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unused_async, + reason = "Found 1 occurrences after enabling the lint." +)] + use clap::Args; use firewood::db::{Db, DbConfig}; use firewood::merkle::Key; @@ -116,7 +129,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("dump database {:?}", opts); + log::debug!("dump database {opts:?}"); let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; @@ -161,7 +174,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { } fn key_count_exceeded(max: Option, key_count: u32) -> bool { - max.map(|max| key_count >= max).unwrap_or(false) + max.is_some_and(|max| key_count >= max) } fn u8_to_string(data: &[u8]) -> Cow<'_, str> { @@ -203,7 +216,7 @@ async fn handle_next_key(next_key: KeyFromStream) { ); } Some(Err(e)) => { - eprintln!("Error occurred while fetching the next key: {}.", e); + eprintln!("Error occurred while fetching the next key: {e}."); } None => { println!("There is no next key. Data dump completed."); @@ -268,7 +281,7 @@ struct StdoutOutputHandler { impl OutputHandler for StdoutOutputHandler { fn handle_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), std::io::Error> { let (key_str, value_str) = key_value_to_string(key, value, self.hex); - println!("'{}': '{}'", key_str, value_str); + println!("'{key_str}': '{value_str}'"); Ok(()) } fn flush(&mut self) -> Result<(), std::io::Error> { diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 6c2d6afb19de..03a9ac564089 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -25,7 +25,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("get key value pair {:?}", opts); + log::debug!("get key value pair {opts:?}"); let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; diff --git a/fwdctl/src/graph.rs b/fwdctl/src/graph.rs index e070169e4b24..780fb31ba198 100644 --- a/fwdctl/src/graph.rs +++ b/fwdctl/src/graph.rs @@ -18,7 +18,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("dump database {:?}", opts); + log::debug!("dump database {opts:?}"); let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 969266d39eca..9d302ddcfae9 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -27,7 +27,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - log::debug!("inserting key value pair {:?}", opts); + log::debug!("inserting key value pair {opts:?}"); let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 3a4205595160..4269dd6e81a1 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -37,20 +37,12 @@ criterion = {version = "0.6.0", features = ["async_tokio"]} rand = "0.9.1" rand_distr = "0.5.0" -[lints.rust] -unsafe_code = "deny" - [[bench]] name = "insert" harness = false -[lints.clippy] -unwrap_used = "warn" -indexing_slicing = "warn" -explicit_deref_methods = "warn" -missing_const_for_fn = "warn" -arithmetic_side_effects = "warn" - [package.metadata.cargo-machete] ignored = ["prost", "tonic_build"] +[lints] +workspace = true diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 7bedd68b3846..9230cda5eae1 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -56,3 +56,6 @@ ethhash = [ "dep:rlp", "dep:sha3", "dep:bytes" ] [[bench]] name = "serializer" harness = false + +[lints] +workspace = true diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 240c5caf49ec..25c678915151 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -1,6 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::assigning_clones, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::unwrap_used, + reason = "Found 7 occurrences after enabling the lint." +)] + use std::array::from_fn; use std::fs::File; use std::num::NonZeroU64; @@ -65,14 +74,14 @@ fn leaf(c: &mut Criterion) { group.bench_with_input("serde", &input, |b, input| { b.iter(|| { serializer.serialize(input).unwrap(); - }) + }); }); group.bench_with_input("manual", &input, |b, input| { b.iter(|| { let mut bytes = Vec::::new(); input.as_bytes(0, &mut bytes); - }) + }); }); group.finish(); } @@ -97,14 +106,14 @@ fn branch(c: &mut Criterion) { let serde_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { b.iter(|| { serializer.serialize(input).unwrap(); - }) + }); }; let manual_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { b.iter(|| { let mut bytes = Vec::new(); input.as_bytes(0, &mut bytes); - }) + }); }; group.bench_with_input("serde", &input, serde_serializer); diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 8e400d6018e3..fa1cd36334e9 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -1,6 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 1 occurrences after enabling the lint." +)] + use std::{ iter::{self}, ops::Deref, @@ -11,6 +16,7 @@ use smallvec::SmallVec; use crate::{BranchNode, HashType, LeafNode, Node, Path}; /// Returns the hash of `node`, which is at the given `path_prefix`. +#[must_use] pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { match node { Node::Branch(node) => { @@ -40,6 +46,7 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { /// Returns the serialized representation of `node` used as the pre-image /// when hashing the node. The node is at the given `path_prefix`. +#[must_use] pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { // Key, 3 options, value digest let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::default().len(); @@ -85,7 +92,7 @@ impl HasUpdate for SmallVec<[u8; 32]> { } #[derive(Clone, Debug)] -/// A ValueDigest is either a node's value or the hash of its value. +/// A `ValueDigest` is either a node's value or the hash of its value. pub enum ValueDigest { /// The node's value. Value(T), diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 0fcdae7440b3..98956b8094e6 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -3,6 +3,21 @@ // Ethereum compatible hashing algorithm. +#![cfg_attr( + feature = "ethhash", + expect( + clippy::indexing_slicing, + reason = "Found 4 occurrences after enabling the lint." + ) +)] +#![cfg_attr( + feature = "ethhash", + expect( + clippy::too_many_lines, + reason = "Found 1 occurrences after enabling the lint." + ) +)] + use std::iter::once; use crate::logger::warn; @@ -20,7 +35,7 @@ use rlp::{Rlp, RlpStream}; impl HasUpdate for Keccak256 { fn update>(&mut self, data: T) { - sha3::Digest::update(self, data) + sha3::Digest::update(self, data); } } @@ -138,7 +153,7 @@ impl Preimage for T { None => { rlp.append_empty_data(); } - }; + } } else { match self.value_digest() { Some(ValueDigest::Value(bytes)) => rlp.append(&bytes), diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index 4123783c5c89..502582730240 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -1,6 +1,21 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![cfg_attr( + not(feature = "ethhash"), + expect( + clippy::arithmetic_side_effects, + reason = "Found 1 occurrences after enabling the lint." + ) +)] +#![cfg_attr( + not(feature = "ethhash"), + expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." + ) +)] + use crate::hashednode::{HasUpdate, Hashable, Preimage}; use crate::{TrieHash, ValueDigest}; /// Merkledb compatible hashing algorithm. @@ -12,7 +27,7 @@ const BITS_PER_NIBBLE: u64 = 4; impl HasUpdate for Sha256 { fn update>(&mut self, data: T) { - sha2::Digest::update(self, data) + sha2::Digest::update(self, data); } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index a3d7f1c1a197..6092d6536e60 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,15 +1,16 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. + #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] #![deny(unsafe_code)] -//! # storage implements the storage of a [Node] on top of a LinearStore +//! # storage implements the storage of a [Node] on top of a `LinearStore` //! -//! Nodes are stored at a [LinearAddress] within a [ReadableStorage]. +//! Nodes are stored at a [`LinearAddress`] within a [`ReadableStorage`]. //! -//! The [NodeStore] maintains a free list and the [LinearAddress] of a root node. +//! The [`NodeStore`] maintains a free list and the [`LinearAddress`] of a root node. //! -//! A [NodeStore] is backed by a [ReadableStorage] which is persisted storage. +//! A [`NodeStore`] is backed by a [`ReadableStorage`] which is persisted storage. mod hashednode; mod hashers; @@ -65,6 +66,11 @@ impl std::fmt::Display for CacheReadStrategy { /// /// This function is slow, so callers should cache the result #[cfg(feature = "ethhash")] +#[must_use] +#[expect( + clippy::missing_panics_doc, + reason = "Found 1 occurrences after enabling the lint." +)] pub fn empty_trie_hash() -> TrieHash { use sha3::Digest as _; diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 3cc4454fdf1c..9899b9bf7280 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -6,6 +6,27 @@ // object. Instead, we probably should use an IO system that can perform multiple // read/write operations at once +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 5 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 3 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_errors_doc, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_fields_in_debug, + reason = "Found 1 occurrences after enabling the lint." +)] + use std::fs::{File, OpenOptions}; use std::io::Read; use std::num::NonZero; @@ -23,7 +44,7 @@ use crate::{CacheReadStrategy, LinearAddress, SharedNode}; use super::{FileIoError, ReadableStorage, WritableStorage}; -/// A [ReadableStorage] and [WritableStorage] backed by a file +/// A [`ReadableStorage`] and [`WritableStorage`] backed by a file pub struct FileBacked { fd: File, filename: PathBuf, @@ -280,6 +301,8 @@ impl Read for PredictiveReader<'_> { #[cfg(test)] mod test { + #![expect(clippy::unwrap_used)] + use super::*; use std::io::Write; use tempfile::NamedTempFile; diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index cc6c07e78eeb..db9bb773d772 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -1,19 +1,33 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 3 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 1 occurrences after enabling the lint." +)] + use super::{FileIoError, ReadableStorage, WritableStorage}; use metrics::counter; use std::io::{Cursor, Read}; use std::sync::Mutex; #[derive(Debug, Default)] -/// An in-memory impelementation of [WritableStorage] and [ReadableStorage] +/// An in-memory impelementation of [`WritableStorage`] and [`ReadableStorage`] pub struct MemStore { bytes: Mutex>, } impl MemStore { - /// Create a new, empty [MemStore] + /// Create a new, empty [`MemStore`] + #[must_use] pub const fn new(bytes: Vec) -> Self { Self { bytes: Mutex::new(bytes), diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 2e140de81e1b..62370e7c2b3a 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -1,8 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -//! A LinearStore provides a view of a set of bytes at -//! a given time. A LinearStore has three different types, +//! A `LinearStore` provides a view of a set of bytes at +//! a given time. A `LinearStore` has three different types, //! which refer to another base type, as follows: //! ```mermaid //! stateDiagram-v2 @@ -14,6 +14,11 @@ //! //! Each type is described in more detail below. +#![expect( + clippy::missing_errors_doc, + reason = "Found 4 occurrences after enabling the lint." +)] + use std::fmt::Debug; use std::io::Read; use std::num::NonZero; @@ -24,7 +29,7 @@ use crate::{CacheReadStrategy, LinearAddress, SharedNode}; pub(super) mod filebacked; pub mod memory; -/// An error that occurs when reading or writing to a [ReadableStorage] or [WritableStorage] +/// An error that occurs when reading or writing to a [`ReadableStorage`] or [`WritableStorage`] /// /// This error is used to wrap errors that occur when reading or writing to a file. /// It contains the filename, offset, and context of the error. @@ -37,7 +42,7 @@ pub struct FileIoError { } impl FileIoError { - /// Create a new [FileIoError] from a generic error + /// Create a new [`FileIoError`] from a generic error /// /// Only use this constructor if you do not have any file or line information. /// @@ -53,7 +58,7 @@ impl FileIoError { } } - /// Create a new [FileIoError] + /// Create a new [`FileIoError`] /// /// # Arguments /// @@ -61,7 +66,8 @@ impl FileIoError { /// * `filename` - The filename of the file that caused the error /// * `offset` - The offset of the file that caused the error /// * `context` - The context of this error - pub fn new( + #[must_use] + pub const fn new( inner: std::io::Error, filename: Option, offset: u64, @@ -94,7 +100,7 @@ impl std::fmt::Display for FileIoError { .as_ref() .unwrap_or(&PathBuf::from("[unknown]")) .display(), - context = self.context.as_ref().unwrap_or(&String::from("")) + context = self.context.as_ref().unwrap_or(&String::new()) ) } } @@ -145,7 +151,7 @@ pub trait ReadableStorage: Debug + Sync + Send { None } - /// Convert an io::Error into a FileIoError + /// Convert an `io::Error` into a `FileIoError` fn file_io_error( &self, error: std::io::Error, diff --git a/storage/src/logger.rs b/storage/src/logger.rs index 410ed4b8b811..a6bd7615e60a 100644 --- a/storage/src/logger.rs +++ b/storage/src/logger.rs @@ -10,6 +10,7 @@ pub use log::{debug, error, info, trace, warn}; /// Returns true if the trace log level is enabled #[cfg(feature = "logger")] +#[must_use] pub fn trace_enabled() -> bool { log::log_enabled!(log::Level::Trace) } @@ -31,9 +32,10 @@ mod noop_logger { pub use noop as trace; pub use noop as warn; - /// trace_enabled for a noop logger is always false + /// `trace_enabled` for a noop logger is always false #[inline] - pub fn trace_enabled() -> bool { + #[must_use] + pub const fn trace_enabled() -> bool { false } } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index be24d79d48eb..94c7d9bd9e6a 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -1,6 +1,23 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::cast_possible_truncation, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::match_same_arms, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_panics_doc, + reason = "Found 2 occurrences after enabling the lint." +)] + use serde::ser::SerializeStruct as _; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -11,12 +28,12 @@ use std::fmt::{Debug, Formatter}; /// The type of a hash. For ethereum compatible hashes, this might be a RLP encoded /// value if it's small enough to fit in less than 32 bytes. For merkledb compatible -/// hashes, it's always a TrieHash. +/// hashes, it's always a `TrieHash`. #[cfg(feature = "ethhash")] pub type HashType = ethhash::HashOrRlp; #[cfg(not(feature = "ethhash"))] -/// The type of a hash. For non-ethereum compatible hashes, this is always a TrieHash. +/// The type of a hash. For non-ethereum compatible hashes, this is always a `TrieHash`. pub type HashType = crate::TrieHash; pub(crate) trait Serializable { @@ -47,7 +64,6 @@ mod ethhash { use std::{ fmt::{Display, Formatter}, io::Read, - ops::Deref as _, }; use crate::TrieHash; @@ -65,7 +81,7 @@ mod ethhash { impl HashOrRlp { pub fn as_slice(&self) -> &[u8] { - self.deref() + self } pub(crate) fn into_triehash(self) -> TrieHash { @@ -147,7 +163,7 @@ mod ethhash { fn deref(&self) -> &Self::Target { match self { HashOrRlp::Hash(h) => h, - HashOrRlp::Rlp(r) => r.deref(), + HashOrRlp::Rlp(r) => r, } } } @@ -155,7 +171,7 @@ mod ethhash { impl Display for HashOrRlp { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - HashOrRlp::Hash(h) => write!(f, "{}", h), + HashOrRlp::Hash(h) => write!(f, "{h}"), HashOrRlp::Rlp(r) => { let width = f.precision().unwrap_or(32); write!(f, "{:.*}", width, hex::encode(r)) @@ -182,8 +198,8 @@ pub struct BranchNode { /// The children of this branch. /// Element i is the child at index i, or None if there is no child at that index. - /// Each element is (child_hash, child_address). - /// child_address is None if we don't know the child's hash. + /// Each element is (`child_hash`, `child_address`). + /// `child_address` is None if we don't know the child's hash. pub children: [Option; Self::MAX_CHILDREN], } @@ -230,7 +246,7 @@ impl<'de> Deserialize<'de> for BranchNode { let mut children: [Option; BranchNode::MAX_CHILDREN] = [const { None }; BranchNode::MAX_CHILDREN]; - for (offset, addr, hash) in s.children.iter() { + for (offset, addr, hash) in &s.children { children[*offset as usize] = Some(Child::AddressWithHash(*addr, hash.clone())); } @@ -252,7 +268,7 @@ impl Debug for BranchNode { None => {} Some(Child::Node(_)) => {} //TODO Some(Child::AddressWithHash(addr, hash)) => { - write!(f, "({i:?}: address={addr:?} hash={})", hash)? + write!(f, "({i:?}: address={addr:?} hash={hash})")?; } } } @@ -269,16 +285,17 @@ impl Debug for BranchNode { } impl BranchNode { - /// The maximum number of children in a [BranchNode] + /// The maximum number of children in a [`BranchNode`] #[cfg(feature = "branch_factor_256")] pub const MAX_CHILDREN: usize = 256; - /// The maximum number of children in a [BranchNode] + /// The maximum number of children in a [`BranchNode`] #[cfg(not(feature = "branch_factor_256"))] pub const MAX_CHILDREN: usize = 16; /// Returns the address of the child at the given index. - /// Panics if `child_index` >= [BranchNode::MAX_CHILDREN]. + /// Panics if `child_index` >= [`BranchNode::MAX_CHILDREN`]. + #[must_use] pub fn child(&self, child_index: u8) -> &Option { self.children .get(child_index as usize) diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 5f472202bc90..67178f56258f 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -1,6 +1,31 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 4 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 6 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::items_after_statements, + reason = "Found 2 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_errors_doc, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_panics_doc, + reason = "Found 1 occurrences after enabling the lint." +)] + use bitfield::bitfield; use branch::Serializable as _; use enum_as_inner::EnumAsInner; @@ -26,9 +51,9 @@ use crate::{HashType, Path, SharedNode}; #[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Serialize, Deserialize)] #[repr(C)] pub enum Node { - /// This node is a [BranchNode] + /// This node is a [`BranchNode`] Branch(Box), - /// This node is a [LeafNode] + /// This node is a [`LeafNode`] Leaf(LeafNode), } @@ -123,11 +148,11 @@ impl ExtendableBytes for Vec { pub struct ByteCounter(u64); impl ByteCounter { - pub fn new() -> Self { + pub const fn new() -> Self { ByteCounter(0) } - pub fn count(&self) -> u64 { + pub const fn count(&self) -> u64 { self.0 } } @@ -154,6 +179,7 @@ impl ExtendableBytes for ByteCounter { impl Node { /// Returns the partial path of the node. + #[must_use] pub fn partial_path(&self) -> &Path { match self { Node::Branch(b) => &b.partial_path, @@ -179,6 +205,7 @@ impl Node { /// Returns Some(value) inside the node, or None if the node is a branch /// with no value. + #[must_use] pub fn value(&self) -> Option<&[u8]> { match self { Node::Branch(b) => b.value.as_deref(), @@ -193,14 +220,14 @@ impl Node { /// - Byte 0: /// - Bit 0: always 0 /// - Bit 1: indicates if the branch has a value - /// - Bits 2-5: the number of children (unless branch_factor_256, which stores it in the next byte) - /// - Bits 6-7: 0: empty partial_path, 1: 1 nibble, 2: 2 nibbles, 3: length is encoded in the next byte - /// (for branch_factor_256, bits 2-7 are used for partial_path length, up to 63 nibbles) + /// - Bits 2-5: the number of children (unless `branch_factor_256`, which stores it in the next byte) + /// - Bits 6-7: 0: empty `partial_path`, 1: 1 nibble, 2: 2 nibbles, 3: length is encoded in the next byte + /// (for `branch_factor_256`, bits 2-7 are used for `partial_path` length, up to 63 nibbles) /// /// The remaining bytes are in the following order: /// - The partial path, possibly preceeded by the length if it is longer than 3 nibbles (varint encoded) /// - The number of children, if the branch factor is 256 - /// - The children. If the number of children == [BranchNode::MAX_CHILDREN], then the children are just + /// - The children. If the number of children == [`BranchNode::MAX_CHILDREN`], then the children are just /// addresses with hashes. Otherwise, they are offset, address, hash tuples. /// /// For a leaf: @@ -238,13 +265,13 @@ impl Node { }; #[cfg(not(feature = "branch_factor_256"))] let first_byte: BranchFirstByte = BranchFirstByte::new( - b.value.is_some() as u8, + u8::from(b.value.is_some()), (childcount % BranchNode::MAX_CHILDREN) as u8, pp_len, ); #[cfg(feature = "branch_factor_256")] let first_byte: BranchFirstByte = - BranchFirstByte::new(b.value.is_some() as u8, pp_len); + BranchFirstByte::new(u8::from(b.value.is_some()), pp_len); // create an output stack item, which can overflow to memory for very large branch nodes const OPTIMIZE_BRANCHES_FOR_SIZE: usize = 1024; @@ -391,7 +418,7 @@ impl Node { let mut children = [const { None }; BranchNode::MAX_CHILDREN]; if childcount == 0 { // branch is full of all children - for child in children.iter_mut() { + for child in &mut children { // TODO: we can read them all at once let mut address_buf = [0u8; 8]; serialized.read_exact(&mut address_buf)?; @@ -451,6 +478,8 @@ pub struct PathIterItem { #[cfg(test)] mod test { + #![expect(clippy::needless_pass_by_value, clippy::unwrap_used)] + use crate::node::{BranchNode, LeafNode, Node}; use crate::{Child, LinearAddress, Path}; use test_case::test_case; diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index f5cc37d95ab5..1c5786254f07 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -1,6 +1,19 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 8 occurrences after enabling the lint." +)] +#![expect( + clippy::from_iter_instead_of_collect, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::inline_always, + reason = "Found 1 occurrences after enabling the lint." +)] + // TODO: remove bitflags, we only use one bit use bitflags::bitflags; use serde::{Deserialize, Serialize}; @@ -17,7 +30,7 @@ pub struct Path(pub SmallVec<[u8; 64]>); impl Debug for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - for nib in self.0.iter() { + for nib in &self.0 { if *nib > 0xf { write!(f, "[invalid {:02x}] ", *nib)?; } else { @@ -75,13 +88,14 @@ impl Path { } /// Creates an empty Path + #[must_use] pub fn new() -> Self { Path(SmallVec::new()) } /// Read from an iterator that returns nibbles with a prefix /// The prefix is one optional byte -- if not present, the Path is empty - /// If there is one byte, and the byte contains a [Flags::ODD_LEN] (0x1) + /// If there is one byte, and the byte contains a [`Flags::ODD_LEN`] (0x1) /// then there is another discarded byte after that. #[cfg(test)] pub fn from_encoded_iter>(mut iter: Iter) -> Self { @@ -96,11 +110,12 @@ impl Path { /// Add nibbles to the end of a path pub fn extend>(&mut self, iter: T) { - self.0.extend(iter) + self.0.extend(iter); } /// Create an iterator that returns the bytes from the underlying nibbles /// If there is an odd nibble at the end, it is dropped + #[must_use] pub fn bytes_iter(&self) -> BytesIterator<'_> { BytesIterator { nibbles_iter: self.iter(), @@ -108,6 +123,7 @@ impl Path { } /// Create a boxed set of bytes from the Path + #[must_use] pub fn bytes(&self) -> Box<[u8]> { self.bytes_iter().collect() } @@ -204,6 +220,7 @@ impl<'a> NibblesIterator<'a> { /// Returns a new `NibblesIterator` over the given `data`. /// Each byte in `data` is converted to two nibbles. + #[must_use] pub const fn new(data: &'a [u8]) -> Self { NibblesIterator { data, @@ -239,6 +256,8 @@ impl DoubleEndedIterator for NibblesIterator<'_> { #[cfg(test)] mod test { + #![expect(clippy::needless_pass_by_value)] + use super::*; use std::fmt::Debug; use test_case::test_case; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 723425eff56b..5237c40967f2 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,6 +1,43 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 5 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 16 occurrences after enabling the lint." +)] +#![expect( + clippy::default_trait_access, + reason = "Found 6 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 10 occurrences after enabling the lint." +)] +#![expect( + clippy::match_wildcard_for_single_variants, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_errors_doc, + reason = "Found 15 occurrences after enabling the lint." +)] +#![expect( + clippy::missing_panics_doc, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::needless_pass_by_value, + reason = "Found 3 occurrences after enabling the lint." +)] +#![expect( + clippy::unwrap_used, + reason = "Found 2 occurrences after enabling the lint." +)] + use crate::linear::FileIoError; use crate::logger::{debug, trace}; use arc_swap::ArcSwap; @@ -15,15 +52,15 @@ use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::fmt::Debug; -/// The [NodeStore] handles the serialization of nodes and +/// The [`NodeStore`] handles the serialization of nodes and /// free space management of nodes in the page store. It lays out the format -/// of the [PageStore]. More specifically, it places a [FileIdentifyingMagic] -/// and a [FreeSpaceHeader] at the beginning +/// of the [`PageStore`]. More specifically, it places a [`FileIdentifyingMagic`] +/// and a [`FreeSpaceHeader`] at the beginning /// /// Nodestores represent a revision of the trie. There are three types of nodestores: /// - Committed: A committed revision of the trie. It has no in-memory changes. -/// - MutableProposal: A proposal that is still being modified. It has some nodes in memory. -/// - ImmutableProposal: A proposal that has been hashed and assigned addresses. It has no in-memory changes. +/// - `MutableProposal`: A proposal that is still being modified. It has some nodes in memory. +/// - `ImmutableProposal`: A proposal that has been hashed and assigned addresses. It has no in-memory changes. /// /// The general lifecycle of nodestores is as follows: /// ```mermaid @@ -51,8 +88,8 @@ use crate::{ use super::linear::WritableStorage; -/// [NodeStore] divides the linear store into blocks of different sizes. -/// [AREA_SIZES] is every valid block size. +/// [`NodeStore`] divides the linear store into blocks of different sizes. +/// [`AREA_SIZES`] is every valid block size. const AREA_SIZES: [u64; 23] = [ 16, // Min block size 32, @@ -92,7 +129,7 @@ fn area_size_hash() -> TrieHash { } // TODO: automate this, must stay in sync with above -fn index_name(index: AreaIndex) -> &'static str { +const fn index_name(index: AreaIndex) -> &'static str { match index { 0 => "16", 1 => "32", @@ -121,7 +158,7 @@ fn index_name(index: AreaIndex) -> &'static str { } } -/// The type of an index into the [AREA_SIZES] array +/// The type of an index into the [`AREA_SIZES`] array /// This is not usize because we can store this as a single byte pub type AreaIndex = u8; @@ -136,7 +173,7 @@ fn area_size_to_index(n: u64) -> Result { if n > MAX_AREA_SIZE { return Err(Error::new( ErrorKind::InvalidData, - format!("Node size {} is too large", n), + format!("Node size {n} is too large"), )); } @@ -151,17 +188,17 @@ fn area_size_to_index(n: u64) -> Result { .ok_or_else(|| { Error::new( ErrorKind::InvalidData, - format!("Node size {} is too large", n), + format!("Node size {n} is too large"), ) }) } -/// Objects cannot be stored at the zero address, so a [LinearAddress] is guaranteed not +/// Objects cannot be stored at the zero address, so a [`LinearAddress`] is guaranteed not /// to be zero. This reserved zero can be used as a [None] value for some use cases. In particular, -/// branches can use `Option` which is the same size as a [LinearAddress] +/// branches can use `Option` which is the same size as a [`LinearAddress`] pub type LinearAddress = NonZeroU64; -/// Each [StoredArea] contains an [Area] which is either a [Node] or a [FreeArea]. +/// Each [`StoredArea`] contains an [Area] which is either a [Node] or a [`FreeArea`]. #[repr(u8)] #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] enum Area { @@ -169,8 +206,8 @@ enum Area { Free(U) = 255, // this is magic: no node starts with a byte of 255 } -/// Every item stored in the [NodeStore]'s ReadableStorage after the -/// [NodeStoreHeader] is a [StoredArea]. +/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the +/// [`NodeStoreHeader`] is a [`StoredArea`]. /// /// As an overview of what this looks like stored, we get something like this: /// - Byte 0: The index of the area size @@ -178,13 +215,13 @@ enum Area { /// - Bytes 2..n: The actual data #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] struct StoredArea { - /// Index in [AREA_SIZES] of this area's size + /// Index in [`AREA_SIZES`] of this area's size area_size_index: AreaIndex, area: T, } impl NodeStore { - /// Returns (index, area_size) for the stored area at `addr`. + /// Returns (index, `area_size`) for the stored area at `addr`. /// `index` is the index of `area_size` in the array of valid block sizes. pub fn area_index_and_size( &self, @@ -213,8 +250,8 @@ impl NodeStore { Ok((index, size)) } - /// Read a [Node] from the provided [LinearAddress]. - /// `addr` is the address of a StoredArea in the ReadableStorage. + /// Read a [Node] from the provided [`LinearAddress`]. + /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. pub fn read_node_from_disk( &self, addr: LinearAddress, @@ -251,7 +288,7 @@ impl NodeStore { Ok(node) } - /// Read a [Node] from the provided [LinearAddress] and size. + /// Read a [Node] from the provided [`LinearAddress`] and size. /// This is an uncached read, primarily used by check utilities pub fn uncached_read_node_and_size( &self, @@ -280,19 +317,19 @@ impl NodeStore { } /// Get a reference to the header of this nodestore - pub fn header(&self) -> &NodeStoreHeader { + pub const fn header(&self) -> &NodeStoreHeader { &self.header } /// Get the size of an area index (used by the checker) - pub fn size_from_area_index(&self, index: AreaIndex) -> u64 { + pub const fn size_from_area_index(&self, index: AreaIndex) -> u64 { AREA_SIZES[index as usize] } } impl NodeStore { - /// Open an existing [NodeStore] - /// Assumes the header is written in the [ReadableStorage]. + /// Open an existing [`NodeStore`] + /// Assumes the header is written in the [`ReadableStorage`]. pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; let mut header = NodeStoreHeader::new(); @@ -325,7 +362,7 @@ impl NodeStore { Ok(nodestore) } - /// Create a new, empty, Committed [NodeStore] and clobber + /// Create a new, empty, Committed [`NodeStore`] and clobber /// the underlying store with an empty freelist and no root node pub fn new_empty_committed(storage: Arc) -> Result { let header = NodeStoreHeader::new(); @@ -344,9 +381,9 @@ impl NodeStore { /// Some nodestore kinds implement Parentable. /// /// This means that the nodestore can have children. -/// Only [ImmutableProposal] and [Committed] implement this trait. -/// [MutableProposal] does not implement this trait because it is not a valid parent. -/// TODO: Maybe this can be renamed to ImmutableNodestore +/// Only [`ImmutableProposal`] and [Committed] implement this trait. +/// [`MutableProposal`] does not implement this trait because it is not a valid parent. +/// TODO: Maybe this can be renamed to `ImmutableNodestore` pub trait Parentable { /// Returns the parent of this nodestore. fn as_nodestore_parent(&self) -> NodeStoreParent; @@ -391,7 +428,7 @@ impl Parentable for Committed { } impl NodeStore { - /// Create a new MutableProposal [NodeStore] from a parent [NodeStore] + /// Create a new `MutableProposal` [`NodeStore`] from a parent [`NodeStore`] pub fn new( parent: Arc>, ) -> Result { @@ -431,13 +468,13 @@ impl NodeStore { } /// Returns the root of this proposal. - pub fn mut_root(&mut self) -> &mut Option { + pub const fn mut_root(&mut self) -> &mut Option { &mut self.kind.root } } impl NodeStore { - /// Creates a new, empty, [NodeStore] and clobbers the underlying `storage` with an empty header. + /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. /// This is used during testing and during the creation of an in-memory merkle for proofs pub fn new_empty_proposal(storage: Arc) -> Self { let header = NodeStoreHeader::new(); @@ -523,10 +560,7 @@ impl NodeStore, S> { .increment(AREA_SIZES[index] - n); // Return the address of the newly allocated block. - trace!( - "Allocating from free list: addr: {address:?}, size: {}", - index - ); + trace!("Allocating from free list: addr: {address:?}, size: {index}"); return Ok(Some((address, index as AreaIndex))); } @@ -545,7 +579,7 @@ impl NodeStore, S> { let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); self.header.size += area_size; debug_assert!(addr.get() % 8 == 0); - trace!("Allocating from end: addr: {:?}, size: {}", addr, index); + trace!("Allocating from end: addr: {addr:?}, size: {index}"); Ok((addr, index)) } @@ -585,7 +619,7 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; - trace!("Deleting node at {addr:?} of size {}", area_size_index); + trace!("Deleting node at {addr:?} of size {area_size_index}"); counter!("firewood.delete_node", "index" => index_name(area_size_index)).increment(1); counter!("firewood.space.freed", "index" => index_name(area_size_index)) .increment(AREA_SIZES[area_size_index as usize]); @@ -634,7 +668,7 @@ impl From for UpdateError { } /// Can be used by filesystem tooling such as "file" to identify -/// the version of firewood used to create this [NodeStore] file. +/// the version of firewood used to create this [`NodeStore`] file. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, NoUninit, AnyBitPattern)] #[repr(transparent)] struct Version { @@ -748,12 +782,12 @@ impl Version { pub type FreeLists = [Option; NUM_AREA_SIZES]; -/// Persisted metadata for a [NodeStore]. -/// The [NodeStoreHeader] is at the start of the ReadableStorage. +/// Persisted metadata for a [`NodeStore`]. +/// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. #[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] #[repr(C)] pub struct NodeStoreHeader { - /// Identifies the version of firewood used to create this [NodeStore]. + /// Identifies the version of firewood used to create this [`NodeStore`]. version: Version, /// always "1"; verifies endianness endian_test: u64, @@ -769,12 +803,12 @@ pub struct NodeStoreHeader { } impl NodeStoreHeader { - /// The first SIZE bytes of the ReadableStorage are reserved for the - /// [NodeStoreHeader]. + /// The first SIZE bytes of the `ReadableStorage` are reserved for the + /// [`NodeStoreHeader`]. /// We also want it aligned to a disk block const SIZE: u64 = 2048; - /// Number of extra bytes to write on the first creation of the NodeStoreHeader + /// Number of extra bytes to write on the first creation of the `NodeStoreHeader` /// (zero-padded) /// also a compile time check to prevent setting SIZE too small const EXTRA_BYTES: usize = Self::SIZE as usize - std::mem::size_of::(); @@ -858,12 +892,12 @@ impl NodeStoreHeader { } // return the size of this nodestore - pub fn size(&self) -> u64 { + pub const fn size(&self) -> u64 { self.size } } -/// A [FreeArea] is stored at the start of the area that contained a node that +/// A [`FreeArea`] is stored at the start of the area that contained a node that /// has been freed. #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] struct FreeArea { @@ -964,6 +998,7 @@ pub struct ImmutableProposal { impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. + #[must_use] pub fn parent_hash_is(&self, hash: Option) -> bool { match > as arc_swap::access::DynAccess>>::load( &self.parent, @@ -992,7 +1027,7 @@ impl ReadInMemoryNode for ImmutableProposal { } } -/// Proposed [NodeStore] types keep some nodes in memory. These nodes are new nodes that were allocated from +/// Proposed [`NodeStore`] types keep some nodes in memory. These nodes are new nodes that were allocated from /// the free list, but are not yet on disk. This trait checks to see if a node is in memory and returns it if /// it's there. If it's not there, it will be read from disk. /// @@ -1019,18 +1054,18 @@ where /// The second generic parameter is the type of the storage used, either /// in-memory or on-disk. /// -/// The lifecycle of a [NodeStore] is as follows: -/// 1. Create a new, empty, [Committed] [NodeStore] using [NodeStore::new_empty_committed]. -/// 2. Create a [NodeStore] from disk using [NodeStore::open]. -/// 3. Create a new mutable proposal from either a [Committed] or [ImmutableProposal] [NodeStore] using [NodeStore::new]. -/// 4. Convert a mutable proposal to an immutable proposal using [std::convert::TryInto], which hashes the nodes and assigns addresses -/// 5. Convert an immutable proposal to a committed revision using [std::convert::TryInto], which writes the nodes to disk. +/// The lifecycle of a [`NodeStore`] is as follows: +/// 1. Create a new, empty, [Committed] [`NodeStore`] using [`NodeStore::new_empty_committed`]. +/// 2. Create a [`NodeStore`] from disk using [`NodeStore::open`]. +/// 3. Create a new mutable proposal from either a [Committed] or [`ImmutableProposal`] [`NodeStore`] using [`NodeStore::new`]. +/// 4. Convert a mutable proposal to an immutable proposal using [`std::convert::TryInto`], which hashes the nodes and assigns addresses +/// 5. Convert an immutable proposal to a committed revision using [`std::convert::TryInto`], which writes the nodes to disk. #[derive(Debug)] pub struct NodeStore { // Metadata for this revision. header: NodeStoreHeader, - /// This is one of [Committed], [ImmutableProposal], or [MutableProposal]. + /// This is one of [Committed], [`ImmutableProposal`], or [`MutableProposal`]. pub kind: T, /// Persisted storage to read nodes from. pub storage: Arc, @@ -1058,7 +1093,7 @@ impl ReadInMemoryNode for NodeStoreParent { } impl ReadInMemoryNode for MutableProposal { - /// [MutableProposal] types do not have any nodes in memory, but their parent proposal might, so we check there. + /// [`MutableProposal`] types do not have any nodes in memory, but their parent proposal might, so we check there. /// This might be recursive: a grandparent might also have that node in memory. fn read_in_memory_node(&self, addr: LinearAddress) -> Option { self.parent.read_in_memory_node(addr) @@ -1131,13 +1166,13 @@ impl NodeStore, S> { let mut hashable_node = self.read_node(*invalidated_node.1.0)?.deref().clone(); let original_length = path_prefix.len(); path_prefix.0.extend(b.partial_path.0.iter().copied()); - if !unhashed.is_empty() { - path_prefix.0.push(invalidated_node.0 as u8); - } else { + if unhashed.is_empty() { hashable_node.update_partial_path(Path::from_nibbles_iterator( std::iter::once(invalidated_node.0 as u8) .chain(hashable_node.partial_path().0.iter().copied()), )); + } else { + path_prefix.0.push(invalidated_node.0 as u8); } let hash = hash_node(&hashable_node, path_prefix); path_prefix.0.truncate(original_length); @@ -1207,11 +1242,11 @@ impl NodeStore, S> { // is a root node. This means we have to take the nibble from the parent and prefix it to the partial path let hash = if let Some(nibble) = fake_root_extra_nibble { let mut fake_root = node.clone(); - trace!("old node: {:?}", fake_root); + trace!("old node: {fake_root:?}"); fake_root.update_partial_path(Path::from_nibbles_iterator( std::iter::once(nibble).chain(fake_root.partial_path().0.iter().copied()), )); - trace!("new node: {:?}", fake_root); + trace!("new node: {fake_root:?}"); hash_node(&fake_root, path_prefix) } else { hash_node(&node, path_prefix) @@ -1268,7 +1303,7 @@ impl NodeStore, FileBacked> { pub fn flush_nodes(&self) -> Result<(), FileIoError> { let flush_start = Instant::now(); - for (addr, (area_size_index, node)) in self.kind.new.iter() { + for (addr, (area_size_index, node)) in &self.kind.new { let mut stored_area_bytes = Vec::new(); node.as_bytes(*area_size_index, &mut stored_area_bytes); self.storage @@ -1294,7 +1329,7 @@ impl NodeStore, FileBacked> { let mut ring = self.storage.ring.lock().expect("poisoned lock"); let mut saved_pinned_buffers = vec![(false, std::pin::Pin::new(Box::default())); RINGSIZE]; - for (&addr, &(area_size_index, ref node)) in self.kind.new.iter() { + for (&addr, &(area_size_index, ref node)) in &self.kind.new { let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger node.as_bytes(area_size_index, &mut serialized); let mut serialized = serialized.into_boxed_slice(); @@ -1382,6 +1417,7 @@ impl NodeStore, FileBacked> { impl NodeStore, FileBacked> { /// Return a Committed version of this proposal, which doesn't have any modified nodes. /// This function is used during commit. + #[must_use] pub fn as_committed(&self) -> NodeStore { NodeStore { header: self.header, @@ -1682,6 +1718,6 @@ mod tests { node_store.mut_root().replace(giant_leaf); let immutable = NodeStore::, _>::try_from(node_store).unwrap(); - println!("{:?}", immutable); // should not be reached, but need to consume immutable to avoid optimization removal + println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal } } diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 35a46bd725c8..fb1d2c6679d5 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -71,13 +71,14 @@ impl From> for TrieHash { } impl TrieHash { - /// Return the length of a TrieHash + /// Return the length of a `TrieHash` pub(crate) const fn len() -> usize { std::mem::size_of::() } - /// Some code needs a TrieHash even though it only has a HashType. - /// This function is a no-op, as HashType is a TrieHash in this context. + /// Some code needs a `TrieHash` even though it only has a `HashType`. + /// This function is a no-op, as `HashType` is a `TrieHash` in this context. + #[must_use] pub const fn into_triehash(self) -> Self { self } diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 6cfda0b5724a..28267c4c571d 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -23,3 +23,6 @@ hex-literal = "1.0.0" name = "triehash" path = "benches/triehash.rs" harness = false + +[lints] +workspace = true diff --git a/triehash/benches/triehash.rs b/triehash/benches/triehash.rs index 981b39251c80..0ed8ef989588 100644 --- a/triehash/benches/triehash.rs +++ b/triehash/benches/triehash.rs @@ -6,6 +6,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 5 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 1 occurrences after enabling the lint." +)] + use criterion::{Criterion, criterion_group, criterion_main}; use ethereum_types::H256; use firewood_triehash::trie_root; @@ -78,7 +87,7 @@ fn bench_insertions(c: &mut Criterion) { for _ in 0..1000 { let k = random_bytes(6, 0, &mut seed); let v = random_value(&mut seed); - d.push((k, v)) + d.push((k, v)); } b.iter(|| trie_root::(d.clone())); }); @@ -90,7 +99,7 @@ fn bench_insertions(c: &mut Criterion) { for _ in 0..1000 { let k = random_word(alphabet, 6, 0, &mut seed); let v = random_value(&mut seed); - d.push((k, v)) + d.push((k, v)); } b.iter(|| trie_root::(d.clone())); }); @@ -102,7 +111,7 @@ fn bench_insertions(c: &mut Criterion) { for _ in 0..1000 { let k = random_word(alphabet, 1, 5, &mut seed); let v = random_value(&mut seed); - d.push((k, v)) + d.push((k, v)); } b.iter(|| trie_root::(d.clone())); }); @@ -114,7 +123,7 @@ fn bench_insertions(c: &mut Criterion) { for _ in 0..1000 { let k = random_word(alphabet, 6, 0, &mut seed); let v = random_value(&mut seed); - d.push((k, v)) + d.push((k, v)); } b.iter(|| trie_root::(d.clone())); }); diff --git a/triehash/src/lib.rs b/triehash/src/lib.rs index 4abfc3cfc42d..c05782d6f0e2 100644 --- a/triehash/src/lib.rs +++ b/triehash/src/lib.rs @@ -10,6 +10,23 @@ //! //! This module should be used to generate trie root hash. +#![expect( + clippy::arithmetic_side_effects, + reason = "Found 7 occurrences after enabling the lint." +)] +#![expect( + clippy::bool_to_int_with_if, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::cast_possible_truncation, + reason = "Found 1 occurrences after enabling the lint." +)] +#![expect( + clippy::indexing_slicing, + reason = "Found 13 occurrences after enabling the lint." +)] + use std::cmp; use std::collections::BTreeMap; use std::iter::once; @@ -155,7 +172,7 @@ fn hex_prefix_encode(nibbles: &[u8], leaf: bool) -> impl Iterator + ' let oddness_factor = inlen % 2; let first_byte = { - let mut bits = ((inlen as u8 & 1) + (2 * leaf as u8)) << 4; + let mut bits = ((inlen as u8 & 1) + (2 * u8::from(leaf))) << 4; if oddness_factor == 1 { bits += nibbles[0]; } From bb49db8584fd96c434bdc9825bf0d1193a70f4a9 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 18 Jun 2025 12:29:40 -0400 Subject: [PATCH 0765/1053] chore(ffi): rename ffi package to match dir (#971) This PR renames the FFI package to match the directory. Ref https://github.com/ava-labs/coreth/pull/959#discussion_r2143285308 --- ffi/firewood.go | 4 ++-- ffi/firewood_test.go | 2 +- ffi/kvbackend.go | 2 +- ffi/memory.go | 4 ++-- ffi/proposal.go | 4 ++-- ffi/revision.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 0c4e85071337..66034f3175aa 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -1,7 +1,7 @@ -// Package firewood provides a Go wrapper around the [Firewood] database. +// Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood -package firewood +package ffi // // Note that -lm is required on Linux but not on Mac. // #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu -lm diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 6fef86e472fa..c09e9374625c 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1,4 +1,4 @@ -package firewood +package ffi import ( "bytes" diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index 9571a5d7ef94..b17bd3f167d1 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -1,4 +1,4 @@ -package firewood +package ffi // implement a specific interface for firewood // this is used for some of the firewood performance tests diff --git a/ffi/memory.go b/ffi/memory.go index 374b64cb199a..a622d8ab85a3 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -1,7 +1,7 @@ -// Package firewood provides a Go wrapper around the [Firewood] database. +// Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood -package firewood +package ffi // // Note that -lm is required on Linux but not on Mac. // #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm diff --git a/ffi/proposal.go b/ffi/proposal.go index 5f8c2de968f7..83dbe89c7c29 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -1,7 +1,7 @@ -// Package firewood provides a Go wrapper around the [Firewood] database. +// Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood -package firewood +package ffi // // Note that -lm is required on Linux but not on Mac. // #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm diff --git a/ffi/revision.go b/ffi/revision.go index 3d4b53cdc889..ae3e4aff9109 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -1,7 +1,7 @@ -// Package firewood provides a Go wrapper around the [Firewood] database. +// Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood -package firewood +package ffi // // Note that -lm is required on Linux but not on Mac. // #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm From 1f87222c9e44ffb57801007c492ef2c6fe7fe231 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 18 Jun 2025 14:34:25 -0400 Subject: [PATCH 0766/1053] ci(attach-static-libs): add pre build command to set MACOSX_DEPLOYMENT_TARGET for static libs build (#973) Adds a pre-build-command to the matrix for the GH Action building and attaching static libraries. Use this to explicitly set the `MACOSX_DEPLOYMENT_TARGET` env variable to the lowest allowed patch version as described here: https://github.com/ava-labs/firewood/issues/972#issuecomment-2984988336 Fixes https://github.com/ava-labs/firewood/issues/972 (as much as we can for firewood-go) --------- Signed-off-by: aaronbuchwald Co-authored-by: Joachim Brandon LeBlanc --- .github/workflows/attach-static-libs.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index eff1a2a10dd0..b7f3583a52a0 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -38,8 +38,10 @@ jobs: target: aarch64-unknown-linux-gnu - os: macos-latest target: aarch64-apple-darwin + pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> "$GITHUB_ENV" - os: macos-13 target: x86_64-apple-darwin + pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> "$GITHUB_ENV" outputs: has_secrets: ${{ steps.check_secrets.outputs.has_secrets }} steps: @@ -50,6 +52,10 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 + - name: Run pre-build command + if: matrix.pre-build-cmd != '' + run: ${{ matrix.pre-build-cmd }} + - name: Build for ${{ matrix.target }} run: cargo build --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi From 3049499a3b3b277b94924468374f7e7f0c27716c Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 18 Jun 2025 17:57:48 -0400 Subject: [PATCH 0767/1053] feat(ffi): add go generate switch between enabled cgo blocks (#978) Adds a go generate script to parse the `firewood.go` file and use an environment variable to switch, which block of CGO directives is enabled. Adds that command into attach-static-libs GH Action job during push, so that firewood-go skips the local build directory search paths. --- .github/workflows/attach-static-libs.yaml | 4 + ffi/firewood.go | 16 +- ffi/generate_cgo.go | 200 ++++++++++++++++++++++ 3 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 ffi/generate_cgo.go diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index b7f3583a52a0..cae89ee7f2a4 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -125,6 +125,10 @@ jobs: - name: Copy FFI Source Code run: cp -r firewood/ffi firewood-go + - name: Switch to Static Libs CGO Mode + working-directory: firewood-go/ffi + run: FIREWOOD_LD_MODE=STATIC_LIBS go generate + - name: Download binaries into libs directory uses: actions/download-artifact@v4 with: diff --git a/ffi/firewood.go b/ffi/firewood.go index 66034f3175aa..bec60df00aa6 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -3,17 +3,21 @@ // [Firewood]: https://github.com/ava-labs/firewood package ffi +//go:generate go run generate_cgo.go + // // Note that -lm is required on Linux but not on Mac. -// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu -lm -// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-unknown-linux-gnu -lm -// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-apple-darwin -// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-apple-darwin -// // XXX: last search path takes precedence, which means we prioritize -// // local builds over pre-built and maxperf over release build +// // FIREWOOD_CGO_BEGIN_STATIC_LIBS +// // #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu -lm +// // #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-unknown-linux-gnu -lm +// // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-apple-darwin +// // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-apple-darwin +// // FIREWOOD_CGO_END_STATIC_LIBS +// // FIREWOOD_CGO_BEGIN_LOCAL_LIBS // #cgo LDFLAGS: -L${SRCDIR}/../target/debug // #cgo LDFLAGS: -L${SRCDIR}/../target/release // #cgo LDFLAGS: -L${SRCDIR}/../target/maxperf // #cgo LDFLAGS: -L/usr/local/lib -lfirewood_ffi +// // FIREWOOD_CGO_END_LOCAL_LIBS // #include // #include "firewood.h" import "C" diff --git a/ffi/generate_cgo.go b/ffi/generate_cgo.go new file mode 100644 index 000000000000..f04a8124a824 --- /dev/null +++ b/ffi/generate_cgo.go @@ -0,0 +1,200 @@ +//go:build ignore + +package main + +import ( + "fmt" + "log" + "os" + "regexp" + "strings" +) + +const ( + defaultMode = "LOCAL_LIBS" +) + +// run via go generate to parse $GOFILE and scan for blocks of CGO directives (including cgo comments, which require a "// //" prefix) +// Uses delimiters set by: +// FIREWOOD_CGO_BEGIN_ +// cgo line 1 +// . +// . +// . +// cgo line n +// FIREWOOD_CGO_END_ +// +// $GOFILE may contain multiple such blocks, where only one should be active at once. +// Discovers the set FIREWOOD_LD_MODE via env var or defaults to "LOCAL_LIBS", which is +// specific to the firewood.go file. +// Activates the block set by FIREWOOD_LD_MODE by uncommenting any commented out cgo directives +// and deactivates all other blocks' cgo directives. +func main() { + mode := os.Getenv("FIREWOOD_LD_MODE") + if mode == "" { + mode = defaultMode + } + + if err := switchCGOMode(mode); err != nil { + log.Fatalf("Error switching CGO mode to %s: %v", mode, err) + } + fmt.Printf("Successfully switched CGO directives to %s mode\n", mode) +} + +func getTargetFile() (string, error) { + targetFile, ok := os.LookupEnv("GOFILE") + if !ok { + return "", fmt.Errorf("GOFILE is not set") + } + return targetFile, nil +} + +type CGOBlock struct { + Name string // e.g., "STATIC_LIBS", "LOCAL_LIBS" + StartLine int // Line number where block starts + EndLine int // Line number where block ends + Lines []string // All lines in the block (including begin/end markers) +} + +func switchCGOMode(targetMode string) error { + targetFile, err := getTargetFile() + if err != nil { + return err + } + + content, err := os.ReadFile(targetFile) + if err != nil { + return fmt.Errorf("failed to read %s: %w", targetFile, err) + } + + lines := strings.Split(string(content), "\n") + + blocks, err := findCGOBlocks(lines) + if err != nil { + return fmt.Errorf("failed to find CGO blocks: %w", err) + } + + if err := validateCGOBlocks(blocks, lines); err != nil { + return fmt.Errorf("validation failed: %w", err) + } + + cgoBlockMap, err := createCGOBlockMap(blocks) + if err != nil { + return err + } + + targetCGOBlock, ok := cgoBlockMap[targetMode] + if !ok { + return fmt.Errorf("no CGO block found for FIREWOOD_LD_MODE=%s", targetMode) + } + + // Process lines: activate target block, deactivate others + result := make([]string, len(lines)) + copy(result, lines) + + for _, block := range blocks { + isTarget := block.Name == targetCGOBlock.Name + + for i := block.StartLine + 1; i < block.EndLine; i++ { + line := lines[i] + + if isTarget { + // Activate: "// // #cgo" -> "// #cgo" + result[i] = activateCGOLine(line) + } else { + // Deactivate: "// #cgo" -> "// // #cgo" + result[i] = deactivateCGOLine(line) + } + } + } + + newContent := strings.Join(result, "\n") + return os.WriteFile(targetFile, []byte(newContent), 0644) +} + +func findCGOBlocks(lines []string) ([]CGOBlock, error) { + var blocks []CGOBlock + beginRegex := regexp.MustCompile(`^// // FIREWOOD_CGO_BEGIN_(\w+)`) + endRegex := regexp.MustCompile(`^// // FIREWOOD_CGO_END_(\w+)`) + + for i, line := range lines { + matches := beginRegex.FindStringSubmatch(line) + if matches == nil { + continue + } + blockName := matches[1] + + // Find corresponding end marker + endLine := -1 + for j := i + 1; j < len(lines); j++ { + if endMatches := endRegex.FindStringSubmatch(lines[j]); endMatches != nil { + if endMatches[1] == blockName { + endLine = j + break + } + } + } + + if endLine == -1 { + return nil, fmt.Errorf("no matching end marker found for FIREWOOD_CGO_BEGIN_%s at line %d", blockName, i+1) + } + + blocks = append(blocks, CGOBlock{ + Name: blockName, + StartLine: i, + EndLine: endLine, + Lines: lines[i : endLine+1], + }) + } + + return blocks, nil +} + +func validateCGOBlocks(blocks []CGOBlock, lines []string) error { + // Check every CGO block contains ONLY valid CGO directives or valid comments within a CGO directive block + for _, block := range blocks { + for i := block.StartLine + 1; i < block.EndLine; i++ { + if !isCGODirective(lines[i]) { + return fmt.Errorf("invalid CGO directive at line %d in block %s: %s", i+1, block.Name, lines[i]) + } + } + } + + return nil +} + +func createCGOBlockMap(blocks []CGOBlock) (map[string]CGOBlock, error) { + cgoBlockMap := make(map[string]CGOBlock) + for _, block := range blocks { + if existingBlock, exists := cgoBlockMap[block.Name]; exists { + return nil, fmt.Errorf("duplicate CGO block name %q found at lines %d-%d, previously defined at lines %d-%d", + block.Name, block.StartLine+1, block.EndLine+1, + existingBlock.StartLine+1, existingBlock.EndLine+1) + } + cgoBlockMap[block.Name] = block + } + return cgoBlockMap, nil +} + +func isCGODirective(line string) bool { + trimmed := strings.TrimSpace(line) + return strings.HasPrefix(trimmed, "// #cgo") || strings.HasPrefix(trimmed, "// // ") +} + +func activateCGOLine(line string) string { + // Convert "// // #cgo" to "// #cgo" + if strings.Contains(line, "// // #cgo") { + return strings.Replace(line, "// // #cgo", "// #cgo", 1) + } + // Already active + return line +} + +func deactivateCGOLine(line string) string { + // Convert "// #cgo" to "// // #cgo" (but not "// // #cgo" to "// // // #cgo") + if strings.Contains(line, "// #cgo") && !strings.Contains(line, "// // #cgo") { + return strings.Replace(line, "// #cgo", "// // #cgo", 1) + } + // Already deactivated + return line +} From 19ec448f663665eb7cda0d6ac82a7e868b742a51 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 19 Jun 2025 09:49:51 -0400 Subject: [PATCH 0768/1053] ci: Use new firewood-go-* FFI repo naming (#975) Since changing the name of the repo for the Go FFI, we should update all instances throughout this repo to any tags are pushed to the correct location. This was tested via a manual GitHub workflow (branch `test-new-name-1`) --------- Co-authored-by: Ron Kuris --- .github/workflows/attach-static-libs.yaml | 24 +++++++++++------------ ffi/README.md | 14 ++++++------- ffi/tests/eth/eth_compatibility_test.go | 2 +- ffi/tests/eth/go.mod | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index cae89ee7f2a4..0174c926b412 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -100,7 +100,7 @@ jobs: echo "Using tag name as target_branch: $target_branch" echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == "main" ]]; then - export target_branch="main-ci" # Avoid pushing to main branch of firewood-go repo + export target_branch="main-ci" # Avoid pushing to main branch of firewood-go-ethhash repo echo "Using main-ci as target branch for push to main: $target_branch" echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" elif [[ "${{ github.event_name }}" == "pull_request" ]]; then @@ -118,36 +118,36 @@ jobs: - uses: actions/checkout@v4 with: - repository: ava-labs/firewood-go + repository: ava-labs/firewood-go-ethhash token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} - path: firewood-go + path: firewood-go-ethhash - name: Copy FFI Source Code - run: cp -r firewood/ffi firewood-go + run: cp -r firewood/ffi firewood-go-ethhash - name: Switch to Static Libs CGO Mode - working-directory: firewood-go/ffi + working-directory: firewood-go-ethhash/ffi run: FIREWOOD_LD_MODE=STATIC_LIBS go generate - name: Download binaries into libs directory uses: actions/download-artifact@v4 with: - path: firewood-go/ffi/libs + path: firewood-go-ethhash/ffi/libs - name: List downloaded target directory - run: find firewood-go -type f | sort + run: find firewood-go-ethhash -type f | sort - name: Push static libs to branch - working-directory: firewood-go + working-directory: firewood-go-ethhash # GITHUB_TOKEN is configured in the last actions/checkout step - # to have read/write permissions to the firewood-go repo. + # to have read/write permissions to the firewood-go-ethhash repo. run: | git config --global user.name "FirewoodCI" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b ${{ steps.determine_branch.outputs.target_branch }} echo "Updating ffi module path before pushing to alternative remote" pushd ffi - go mod edit -module github.com/ava-labs/firewood-go/ffi + go mod edit -module github.com/ava-labs/firewood-go-ethhash/ffi popd git add . git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" @@ -173,7 +173,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - repository: ava-labs/firewood-go + repository: ava-labs/firewood-go-ethhash token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} ref: ${{ needs.push-firewood-ffi-libs.outputs.target_branch }} - name: Set up Go @@ -197,7 +197,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - repository: ava-labs/firewood-go + repository: ava-labs/firewood-go-ethhash token: ${{ secrets.FIREWOOD_GO_GITHUB_TOKEN }} ref: ${{ needs.push-firewood-ffi-libs.outputs.target_branch }} - name: Delete branch diff --git a/ffi/README.md b/ffi/README.md index 5a36efad888a..7155ce5a5790 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -6,14 +6,14 @@ The FFI package provides a golang FFI layer for Firewood. The Golang FFI layer uses a CGO directive to locate a C-API compatible binary built from Firewood. Firewood supports both seamless local development and a single-step compilation process for Go projects that depend or transitively depend on Firewood. -To do this, [firewood.go](./firewood.go) includes CGO directives to include multiple search paths for the Firewood binary in the local `target/` build directory and `ffi/libs`. For the latter, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) GitHub Action pushes an FFI package with static libraries attached for the following supported architectures: +To do this, [firewood.go](./firewood.go) includes CGO directives to include multiple search paths for the Firewood binary in the local `target/` build directory and `ffi/libs`. For the latter, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) GitHub Action pushes an FFI package for `ethhash` with static libraries attached for the following supported architectures: - x86_64-unknown-linux-gnu - aarch64-unknown-linux-gnu - aarch64-apple-darwin - x86_64-apple-darwin -to a separate repo [firewood-go](https://github.com/ava-labs/firewood-go) (to avoid including binaries in the Firewood repo). +to a separate repo [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) (to avoid including binaries in the Firewood repo). ### Local Development @@ -36,17 +36,17 @@ go test To use a local build of Firewood for a project that depends on Firewood, you must redirect the `go.mod` to use the local version of Firewood FFI, for example: ```bash -go mod edit -replace github.com/ava-labs/firewood-go/ffi=/path/to/firewood/ffi +go mod edit -replace github.com/ava-labs/firewood-go-ethhash/ffi=/path/to/firewood/ffi go mod tidy ``` ### Production Development Flow -Firewood pushes the FFI source code and attached static libraries to [firewood-go](https://github.com/ava-labs/firewood-go) via [attach-static-libs](../.github/workflows/attach-static-libs.yaml). +Firewood pushes the FFI source code and attached static libraries to [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) via [attach-static-libs](../.github/workflows/attach-static-libs.yaml). -This enables consumers to utilize it directly without forcing them to compile Firewood locally. Go programs running on supported architectures can utilize `firewood-go/ffi` just like any other dependency. +This enables consumers to utilize it directly without forcing them to compile Firewood locally. Go programs running on supported architectures can utilize `firewood-go-ethhash/ffi` just like any other dependency. -To trigger this build, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) supports triggers for both manual GitHub Actions and tags, so you can create a mirror branch/tag on [firewood-go](https://github.com/ava-labs/firewood-go) by either trigger a manual GitHub Action and selecting your branch or pushing a tag to Firewood. +To trigger this build, [attach-static-libs](../.github/workflows/attach-static-libs.yaml) supports triggers for both manual GitHub Actions and tags, so you can create a mirror branch/tag on [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) by either trigger a manual GitHub Action and selecting your branch or pushing a tag to Firewood. ### Hash Mode @@ -58,7 +58,7 @@ This is an optional feature (disabled by default). To enable it for a local buil cargo build -p firewood-ffi --features ethhash ``` -To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries to [firewood-go](https://github.com/ava-labs/firewood-go) with `ethhash` enabled by default. +To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries for Ethereum-compatible hashing to [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) with `ethhash` enabled by default. To use Firewood's native hashing structure, you must still build the static library separately. ## Development diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index d35d8576bb16..90b7abbd1515 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -7,7 +7,7 @@ import ( "slices" "testing" - firewood "github.com/ava-labs/firewood-go/ffi" + firewood "github.com/ava-labs/firewood-go-ethhash/ffi" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/state" diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index 1cbd7389a5c6..4b2d5a045fae 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -5,7 +5,7 @@ go 1.23.9 toolchain go1.24.2 require ( - github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder + github.com/ava-labs/firewood-go-ethhash/ffi v0.0.0 // this is replaced to use the parent folder github.com/ava-labs/libevm v1.13.14-0.2.0.release github.com/holiman/uint256 v1.3.2 github.com/stretchr/testify v1.10.0 @@ -70,4 +70,4 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ava-labs/firewood-go/ffi => ../../ +replace github.com/ava-labs/firewood-go-ethhash/ffi => ../../ From 5cf7858b18cee4ae45e4e5d2f7cea9e84635d7f6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 20 Jun 2025 09:00:12 -0700 Subject: [PATCH 0769/1053] chore: Upgrade metrics packages (#982) While working on some unrelated code, things started to break, and this needs upgrading anyway, which may fix the problem. --- benchmark/Cargo.toml | 6 +++--- ffi/Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index f58c329ae100..fdaa3d96c954 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -21,9 +21,9 @@ firewood = { version = "0.0.5", path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" -metrics = "0.24.1" -metrics-util = "0.19.0" -metrics-exporter-prometheus = "0.17.0" +metrics = "0.24.2" +metrics-util = "0.20.0" +metrics-exporter-prometheus = "0.17.2" tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.9.0" rand_distr = "0.5.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index e0a4a36d578c..09c18db807a2 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -21,8 +21,8 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.2" firewood = { version = "0.0.5", path = "../firewood" } -metrics = "0.24.1" -metrics-util = "0.19.0" +metrics = "0.24.2" +metrics-util = "0.20.0" chrono = "0.4.39" oxhttp = "0.3.0" coarsetime = "0.1.35" From 37fa05fcb8ba3b6ae784fcbcd1a5e638f377a61d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 20 Jun 2025 18:09:47 -0700 Subject: [PATCH 0770/1053] chore: Release v0.0.6 (#985) --- CHANGELOG.md | 49 ++++++++++++++++++++++++++++++++++++++++ benchmark/Cargo.toml | 4 ++-- ffi/Cargo.toml | 4 ++-- firewood/Cargo.toml | 8 +++---- fwdctl/Cargo.toml | 4 ++-- grpc-testtool/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- triehash/CHANGELOG.md | 3 +++ triehash/Cargo.toml | 2 +- 9 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 549ca112addc..f8a3f1ead374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to this project will be documented in this file. +## [0.0.6] - 2025-06-21 + +### 🚀 Features + +- Improve error handling and add sync iterator (#941) +- *(metrics)* Add read\_node counters (#947) +- Return database creation errors through FFI (#945) +- *(ffi)* Add go generate switch between enabled cgo blocks (#978) + +### 🐛 Bug Fixes + +- Use saturating subtraction for metrics counter (#937) +- *(attach-static-libs)* Push commit/branch to remote on tag events (#944) +- Add add\_arithmetic\_side\_effects clippy (#949) +- Improve ethhash warning message (#961) +- *(storage)* Parse and validate database versions (#964) + +### 💼 Other + +- *(deps)* Update fastrace-opentelemetry requirement from 0.11.0 to 0.12.0 (#943) +- Move lints to the workspace (#957) + +### ⚡ Performance + +- Remove some unecessary allocs during serialization (#965) + +### 🎨 Styling + +- *(attach-static-libs)* Use go mod edit instead of sed to update mod path (#946) + +### 🧪 Testing + +- *(ethhash)* Convert ethhash test to fuzz test for ethhash compatibility (#956) + +### ⚙️ Miscellaneous Tasks + +- Upgrade actions/checkout (#939) +- Add push to main to attach static libs triggers (#952) +- Check the PR title for conventional commits (#953) +- Add Brandon to CODEOWNERS (#954) +- Set up for publishing to crates.io (#962) +- Remove remnants of no-std (#968) +- *(ffi)* Rename ffi package to match dir (#971) +- *(attach-static-libs)* Add pre build command to set MACOSX\_DEPLOYMENT\_TARGET for static libs build (#973) +- Use new firewood-go-* FFI repo naming (#975) +- Upgrade metrics packages (#982) + ## [0.0.5] - 2025-06-05 ### 🚀 Features @@ -95,6 +142,8 @@ All notable changes to this project will be documented in this file. - *(ffi/tests)* Update go-ethereum v1.15.7 (#838) - *(ffi)* Fix typo fwd\_close\_db comment (#843) - *(ffi)* Add linter (#893) +- Require conventional commit format (#933) +- Bump to v0.5.0 (#934) ## [0.0.4] - 2023-09-27 diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index fdaa3d96c954..8695196e1de0 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-benchmark" -version = "0.0.5" +version = "0.0.6" edition = "2024" rust-version = "1.85.0" authors = [ @@ -17,7 +17,7 @@ name = "benchmark" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.5", path = "../firewood" } +firewood = { version = "0.0.6", path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 09c18db807a2..3a07d56b8fab 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-ffi" -version = "0.0.5" +version = "0.0.6" edition = "2024" rust-version = "1.85.0" authors = [ @@ -20,7 +20,7 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.2" -firewood = { version = "0.0.5", path = "../firewood" } +firewood = { version = "0.0.6", path = "../firewood" } metrics = "0.24.2" metrics-util = "0.20.0" chrono = "0.4.39" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 2c4aaf2f44a7..d2f835689799 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.5" +version = "0.0.6" edition = "2024" authors = [ "Angel Leon ", @@ -45,7 +45,7 @@ branch_factor_256 = [ "firewood-storage/branch_factor_256" ] ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] -firewood-triehash = { version = "0.8.5", path = "../triehash" } +firewood-triehash = { version = "0.0.6", path = "../triehash" } criterion = { version = "0.6.0", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" @@ -65,10 +65,10 @@ name = "hashops" harness = false [target.'cfg(target_os = "linux")'.dependencies] -firewood-storage = { version = "0.0.5", path = "../storage", features = ["io-uring"] } +firewood-storage = { version = "0.0.6", path = "../storage", features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] -firewood-storage = { version = "0.0.5", path = "../storage" } +firewood-storage = { version = "0.0.6", path = "../storage" } [lints] workspace = true diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 1427b4564a67..b9bf853c036b 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-fwdctl" -version = "0.0.5" +version = "0.0.6" edition = "2024" rust-version = "1.85.0" authors = [ @@ -23,7 +23,7 @@ name = "fwdctl" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.5", path = "../firewood" } +firewood = { version = "0.0.6", path = "../firewood" } clap = { version = "4.5.0", features = ["cargo", "derive"] } env_logger = "0.11.2" log = "0.4.20" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 4269dd6e81a1..d9e0601a1899 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,7 +17,7 @@ test = false bench = false [dependencies] -firewood = { version = "0.0.5", path = "../firewood" } +firewood = { version = "0.0.6", path = "../firewood" } prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.13.0", features = ["tls-ring"] } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 9230cda5eae1..bae61e0defdc 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-storage" -version = "0.0.5" +version = "0.0.6" edition = "2024" rust-version = "1.85.0" authors = [ diff --git a/triehash/CHANGELOG.md b/triehash/CHANGELOG.md index d44027d20d27..22dac3ccae80 100644 --- a/triehash/CHANGELOG.md +++ b/triehash/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +This changelog is deprecatated. Please see the changelog at the top +level. + The format is based on [Keep a Changelog]. [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 28267c4c571d..be69f5671f71 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-triehash" -version = "0.8.5" +version = "0.0.6" authors = ["Parity Technologies ", "Ron Kuris "] description = "In-memory patricia trie operations" repository = "https://github.com/paritytech/parity-common" From c5e48cbc9d1c92f39c03b613285991742f9b2ff3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 23 Jun 2025 14:06:31 -0700 Subject: [PATCH 0771/1053] chore: Simplify + cleanup generate_cgo script (#979) - Simplify CGO block processing with state machine approach - Correct isCGODirective to ensure each line has an actual directive - Remove duplicate CGO LDFLAGS from memory.go, proposal.go, revision.go - Avoid writing the file if it is unchanged - Cleaner error reporting - Improved documentation The build probably still generated some warnings because the release directory didn't exist, and yet it was being added to LDFLAGS in a few other go files. This had the side effect of making the math library get included on linux. We should always just include the math library for dev builds. --------- Co-authored-by: Aaron Buchwald --- ffi/firewood.go | 6 +- ffi/generate_cgo.go | 224 +++++++++++++++++--------------------------- ffi/memory.go | 1 - ffi/proposal.go | 2 - ffi/revision.go | 2 - 5 files changed, 88 insertions(+), 147 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index bec60df00aa6..68f45af71fb2 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -7,8 +7,8 @@ package ffi // // Note that -lm is required on Linux but not on Mac. // // FIREWOOD_CGO_BEGIN_STATIC_LIBS -// // #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu -lm -// // #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-unknown-linux-gnu -lm +// // #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-unknown-linux-gnu +// // #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-unknown-linux-gnu // // #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/x86_64-apple-darwin // // #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/aarch64-apple-darwin // // FIREWOOD_CGO_END_STATIC_LIBS @@ -16,8 +16,8 @@ package ffi // #cgo LDFLAGS: -L${SRCDIR}/../target/debug // #cgo LDFLAGS: -L${SRCDIR}/../target/release // #cgo LDFLAGS: -L${SRCDIR}/../target/maxperf -// #cgo LDFLAGS: -L/usr/local/lib -lfirewood_ffi // // FIREWOOD_CGO_END_LOCAL_LIBS +// #cgo LDFLAGS: -lfirewood_ffi -lm // #include // #include "firewood.h" import "C" diff --git a/ffi/generate_cgo.go b/ffi/generate_cgo.go index f04a8124a824..20a2a0ae9a30 100644 --- a/ffi/generate_cgo.go +++ b/ffi/generate_cgo.go @@ -1,12 +1,40 @@ //go:build ignore +// go generate script +// +// This script fixes up a go file to enable/disable the correct cgo directives, +// tailored for use in firewood.go to eliminate linker warnings for production builds. +// +// It scans for blocks of cgo directives, using marker lines like this: +// FIREWOOD_CGO_BEGIN_ +// cgo line 1 +// ... +// cgo line n +// FIREWOOD_CGO_END_ +// +// FIREWOOD_LD_MODE is an environment variable that decides which blocks are activated. +// The default value for FIREWOOD_LD_MODE is "LOCAL_LIBS" for local development. +// When building production static libraries, FIREWOOD_LD_MODE is set to "STATIC_LIBS" +// in the github actions workflow. +// +// The script enables CGO directives for the target mode and comments out CGO directives +// that do not match. +// +// CGO directives are already comments and CGO does not allow interleaving regular +// comments with CGO directives. To disable, we must double escape the CGO directives +// with: +// +// // #cgo ... +// +// The go file may contain multiple such blocks, but nesting is not allowed. + package main import ( + "errors" "fmt" "log" "os" - "regexp" "strings" ) @@ -14,182 +42,100 @@ const ( defaultMode = "LOCAL_LIBS" ) -// run via go generate to parse $GOFILE and scan for blocks of CGO directives (including cgo comments, which require a "// //" prefix) -// Uses delimiters set by: -// FIREWOOD_CGO_BEGIN_ -// cgo line 1 -// . -// . -// . -// cgo line n -// FIREWOOD_CGO_END_ -// -// $GOFILE may contain multiple such blocks, where only one should be active at once. -// Discovers the set FIREWOOD_LD_MODE via env var or defaults to "LOCAL_LIBS", which is -// specific to the firewood.go file. -// Activates the block set by FIREWOOD_LD_MODE by uncommenting any commented out cgo directives -// and deactivates all other blocks' cgo directives. +var errGoFileNotSet = errors.New("GOFILE is not set") + func main() { - mode := os.Getenv("FIREWOOD_LD_MODE") - if mode == "" { - mode = defaultMode + mode := getFirewoodLdMode() + + targetFile, err := getTargetFile() + if err != nil { + log.Fatalf("Error switching CGO mode to %s:\n%v", mode, err) } - if err := switchCGOMode(mode); err != nil { - log.Fatalf("Error switching CGO mode to %s: %v", mode, err) + if err := changeCgoDirectivesForFile(mode, targetFile); err != nil { + log.Fatalf("Error switching CGO mode to %s:\n%v", mode, err) } + fmt.Printf("Successfully switched CGO directives to %s mode\n", mode) } +// getFirewoodLdMode returns the FIREWOOD_LD_MODE environment variable. +// Defaults to "LOCAL_LIBS". +func getFirewoodLdMode() string { + mode, ok := os.LookupEnv("FIREWOOD_LD_MODE") + if !ok { + mode = "LOCAL_LIBS" + } + return mode +} + func getTargetFile() (string, error) { targetFile, ok := os.LookupEnv("GOFILE") if !ok { - return "", fmt.Errorf("GOFILE is not set") + return "", errGoFileNotSet } return targetFile, nil } -type CGOBlock struct { - Name string // e.g., "STATIC_LIBS", "LOCAL_LIBS" - StartLine int // Line number where block starts - EndLine int // Line number where block ends - Lines []string // All lines in the block (including begin/end markers) -} - -func switchCGOMode(targetMode string) error { - targetFile, err := getTargetFile() - if err != nil { - return err - } - - content, err := os.ReadFile(targetFile) +func changeCgoDirectivesForFile(targetMode string, targetFile string) error { + originalFileContent, err := os.ReadFile(targetFile) if err != nil { return fmt.Errorf("failed to read %s: %w", targetFile, err) } - lines := strings.Split(string(content), "\n") - - blocks, err := findCGOBlocks(lines) - if err != nil { - return fmt.Errorf("failed to find CGO blocks: %w", err) - } - - if err := validateCGOBlocks(blocks, lines); err != nil { - return fmt.Errorf("validation failed: %w", err) - } - - cgoBlockMap, err := createCGOBlockMap(blocks) - if err != nil { - return err - } - - targetCGOBlock, ok := cgoBlockMap[targetMode] - if !ok { - return fmt.Errorf("no CGO block found for FIREWOOD_LD_MODE=%s", targetMode) - } - - // Process lines: activate target block, deactivate others - result := make([]string, len(lines)) - copy(result, lines) + fileLines := strings.Split(string(originalFileContent), "\n") - for _, block := range blocks { - isTarget := block.Name == targetCGOBlock.Name - - for i := block.StartLine + 1; i < block.EndLine; i++ { - line := lines[i] - - if isTarget { - // Activate: "// // #cgo" -> "// #cgo" - result[i] = activateCGOLine(line) - } else { - // Deactivate: "// #cgo" -> "// // #cgo" - result[i] = deactivateCGOLine(line) + // Initial state is "None" which does not process any lines + currentBlockName := "None" + for i, line := range fileLines { + // process state transitions + // if the line starts with "// FIREWOOD_CGO_BEGIN_", set the state to the text after the prefix + if newBlockName, ok := strings.CutPrefix(line, "// // FIREWOOD_CGO_BEGIN_"); ok { + if currentBlockName != "None" { + return fmt.Errorf("[ERROR] %s:%d: nested CGO blocks not allowed (found %s after %s)", targetFile, i+1, newBlockName, currentBlockName) } - } - } - - newContent := strings.Join(result, "\n") - return os.WriteFile(targetFile, []byte(newContent), 0644) -} - -func findCGOBlocks(lines []string) ([]CGOBlock, error) { - var blocks []CGOBlock - beginRegex := regexp.MustCompile(`^// // FIREWOOD_CGO_BEGIN_(\w+)`) - endRegex := regexp.MustCompile(`^// // FIREWOOD_CGO_END_(\w+)`) - - for i, line := range lines { - matches := beginRegex.FindStringSubmatch(line) - if matches == nil { + currentBlockName = newBlockName + continue + } else if line == fmt.Sprintf("// // FIREWOOD_CGO_END_%s", currentBlockName) { + currentBlockName = "None" continue } - blockName := matches[1] - - // Find corresponding end marker - endLine := -1 - for j := i + 1; j < len(lines); j++ { - if endMatches := endRegex.FindStringSubmatch(lines[j]); endMatches != nil { - if endMatches[1] == blockName { - endLine = j - break - } - } - } - - if endLine == -1 { - return nil, fmt.Errorf("no matching end marker found for FIREWOOD_CGO_BEGIN_%s at line %d", blockName, i+1) - } - - blocks = append(blocks, CGOBlock{ - Name: blockName, - StartLine: i, - EndLine: endLine, - Lines: lines[i : endLine+1], - }) - } - - return blocks, nil -} -func validateCGOBlocks(blocks []CGOBlock, lines []string) error { - // Check every CGO block contains ONLY valid CGO directives or valid comments within a CGO directive block - for _, block := range blocks { - for i := block.StartLine + 1; i < block.EndLine; i++ { - if !isCGODirective(lines[i]) { - return fmt.Errorf("invalid CGO directive at line %d in block %s: %s", i+1, block.Name, lines[i]) + // If we are in a block, process the line + if currentBlockName != "None" { + if !isCGODirective(line) { + return fmt.Errorf("[ERROR] %s:%d: invalid CGO directive in %s section:\n===\n%s\n===\n", targetFile, i+1, currentBlockName, line) + } + if currentBlockName == targetMode { + fileLines[i] = activateCGOLine(fileLines[i]) + } else { + fileLines[i] = deactivateCGOLine(fileLines[i]) } } } - return nil -} + if currentBlockName != "None" { + return fmt.Errorf("[ERROR] %s: unterminated CGO block ended in %s", targetFile, currentBlockName) + } -func createCGOBlockMap(blocks []CGOBlock) (map[string]CGOBlock, error) { - cgoBlockMap := make(map[string]CGOBlock) - for _, block := range blocks { - if existingBlock, exists := cgoBlockMap[block.Name]; exists { - return nil, fmt.Errorf("duplicate CGO block name %q found at lines %d-%d, previously defined at lines %d-%d", - block.Name, block.StartLine+1, block.EndLine+1, - existingBlock.StartLine+1, existingBlock.EndLine+1) - } - cgoBlockMap[block.Name] = block + // If the contents changed, write it back to the file + newContents := strings.Join(fileLines, "\n") + if newContents == string(originalFileContent) { + fmt.Printf("[INFO] No changes needed to %s\n", targetFile) + return nil } - return cgoBlockMap, nil + return os.WriteFile(targetFile, []byte(newContents), 0644) } func isCGODirective(line string) bool { trimmed := strings.TrimSpace(line) - return strings.HasPrefix(trimmed, "// #cgo") || strings.HasPrefix(trimmed, "// // ") + return strings.HasPrefix(trimmed, "// #cgo") || strings.HasPrefix(trimmed, "// // #cgo") } func activateCGOLine(line string) string { // Convert "// // #cgo" to "// #cgo" - if strings.Contains(line, "// // #cgo") { - return strings.Replace(line, "// // #cgo", "// #cgo", 1) - } - // Already active - return line + return strings.Replace(line, "// // #cgo", "// #cgo", 1) } - func deactivateCGOLine(line string) string { // Convert "// #cgo" to "// // #cgo" (but not "// // #cgo" to "// // // #cgo") if strings.Contains(line, "// #cgo") && !strings.Contains(line, "// // #cgo") { diff --git a/ffi/memory.go b/ffi/memory.go index a622d8ab85a3..9939ae5bfba4 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -4,7 +4,6 @@ package ffi // // Note that -lm is required on Linux but not on Mac. -// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm // #include // #include "firewood.h" import "C" diff --git a/ffi/proposal.go b/ffi/proposal.go index 83dbe89c7c29..f92f7337053f 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -3,8 +3,6 @@ // [Firewood]: https://github.com/ava-labs/firewood package ffi -// // Note that -lm is required on Linux but not on Mac. -// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm // #include // #include "firewood.h" import "C" diff --git a/ffi/revision.go b/ffi/revision.go index ae3e4aff9109..38afd29ff488 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -3,8 +3,6 @@ // [Firewood]: https://github.com/ava-labs/firewood package ffi -// // Note that -lm is required on Linux but not on Mac. -// #cgo LDFLAGS: -L${SRCDIR}/../target/release -L/usr/local/lib -lfirewood_ffi -lm // #include // #include "firewood.h" import "C" From 7d9b31f20dbbd5db0244e07c4b0edb7a20c27b62 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:33:49 -0400 Subject: [PATCH 0772/1053] test: Check support for empty proposals (#988) Although firewood internally supports empty proposals, this was not tested in the FFI layer. --- ffi/firewood_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index c09e9374625c..508a74caf639 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -849,3 +849,55 @@ func TestGetNilCases(t *testing.T) { r.Empty(got, "Proposal.Get(%q)", k) } } + +func TestEmptyProposals(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + numProposals := 10 + emptyProposals := make([]*Proposal, numProposals) + + // Create several empty proposals + for i := range numProposals { + // Create a proposal with no keys. + var err error + if i == 0 { + emptyProposals[i], err = db.Propose(nil, nil) + } else { + emptyProposals[i], err = emptyProposals[i-1].Propose(nil, nil) + } + r.NoError(err, "Propose(%d)", i) + + // Check that the proposal has no keys. + got, err := emptyProposals[i].Get([]byte("non-existent")) + r.NoError(err, "Get(%d)", i) + r.Empty(got, "Get(%d)", i) + } + + // Create one non-empty proposal. + keys, vals := kvForTest(10) + nonEmptyProposal, err := db.Propose(keys, vals) + r.NoError(err, "Propose non-empty proposal") + + // Check that the proposal has the keys we just inserted. + for i := range keys { + got, err := nonEmptyProposal.Get(keys[i]) + r.NoError(err, "Get(%d)", i) + r.Equal(vals[i], got, "Get(%d)", i) + } + + // Commit all empty proposals. + for _, p := range emptyProposals { + r.NoError(p.Commit(), "Commit empty proposal") + } + + // Commit the empty proposal. + err = nonEmptyProposal.Commit() + r.NoError(err) + + // Check that the database has the keys from the non-empty proposal. + for i := range keys { + got, err := db.Get(keys[i]) + r.NoError(err, "Get(%d)", i) + r.Equal(vals[i], got, "Get(%d)", i) + } +} From 8b01fe15886efd1e4c061e3605c60b9be325af3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Szab=C3=B3?= Date: Tue, 24 Jun 2025 19:54:49 +0300 Subject: [PATCH 0773/1053] chore: Update Cargo.toml add repository field (#987) To make it easier for potential contributors and automated tools to find the repository. More explanation: https://github.com/szabgab/rust-digger/issues/89 --- firewood/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d2f835689799..cd59376710a5 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -18,6 +18,7 @@ authors = [ description = "Firewood is an embedded key-value store, optimized to store blockchain state." license-file = "../LICENSE.md" homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" readme = "../README.md" [dependencies] From 9645d071ff1fd01d6e079f0ebc446ad5102146d8 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 24 Jun 2025 15:36:00 -0400 Subject: [PATCH 0774/1053] ci(fuzz): add step to upload fuzz testdata on failure (#990) This PR adds a step on failure to upload testdata from the fuzz tests, so we can easily re-run locally. --- .github/workflows/ci.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 71ff6d9c8cd2..e372b788c87a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -247,6 +247,13 @@ jobs: - name: Test Firewood <> Ethereum Differential Fuzz working-directory: ffi/tests/eth run: go test -fuzz=. -fuzztime=1m + - name: Upload Fuzz testdata + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ethhash-differential-fuzz-testdata + path: ffi/tests/eth/testdata + retention-days: 30 firewood-merkle-differential-fuzz: needs: build @@ -271,3 +278,10 @@ jobs: - name: Test Firewood <> MerkleDB Differential fuzz working-directory: ffi/tests/firewood run: go test -fuzz=. -fuzztime=1m + - name: Upload Fuzz testdata + if: failure() + uses: actions/upload-artifact@v4 + with: + name: firewood-merkle-differential-fuzz-testdata + path: ffi/tests/firewood/testdata + retention-days: 30 From 959ca472789b278a7ec6a21595b527e58ebd7c0d Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 24 Jun 2025 16:13:11 -0400 Subject: [PATCH 0775/1053] fix(ci): include submodule name in ffi tag (#991) This PR updates the tag pushed to https://github.com/ava-labs/firewood-go-ethhash to include the prefix the submodule name `ffi` as a prefix ie.`ffi/vX.X.X`. This ensures that `go get` successfully finds the submodule. `go get` exhibits some funny behavior. It supports fetching modules/submodules by commit, but treats semver tags as a special case. Specifically, https://github.com/ava-labs/firewood/pull/944 manually tested fetching the ffi submodule by both commit (manual GH action) and a tag, but this tag was not a semantic version it was a string to avoid creating a bogus release that may be permanently cached by the go module proxy server. However, after publishing v0.0.6, @alarso16 found that the ffi submodule could not be fetched via: ``` go get github.com/ava-labs/firewood-go-ethhash/ffi@v0.0.6 ``` Instead, this resulted in the error: ``` $ go get github.com/ava-labs/firewood-go-ethhash/ffi@v0.0.6 go: downloading github.com/ava-labs/firewood-go-ethhash v0.0.6 go: module github.com/ava-labs/firewood-go-ethhash@v0.0.6 found, but does not contain package github.com/ava-labs/firewood-go-ethhash/ffi ``` Providing the submodule name as a prefix to the tag ensures that `go get` correctly finds the submodule. The exact handling `go get` performs that causes this is a bit of a rabbit hole. --- .github/workflows/attach-static-libs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 0174c926b412..152d7df02fea 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -154,8 +154,8 @@ jobs: git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force if [[ "${{ github.ref_type }}" == "tag" ]]; then - git tag -a "${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" - git push origin "refs/tags/${GITHUB_REF#refs/tags/}" + git tag -a "ffi/${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" + git push origin "refs/tags/ffi/${GITHUB_REF#refs/tags/}" fi # Check out the branch created in the previous job on a matrix of From 93a62cbe0ebd8b747ae134f43a55ae92d833683c Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 24 Jun 2025 16:37:46 -0400 Subject: [PATCH 0776/1053] ci: add special case for non semver tags to attach static libs (#992) Add special case for non-semantic versions, so that we only add the `ffi/` prefix for valid semantic versions. Non-semantic versions including `v1.1.1-beta` are not treated specially by `go get`, so we do not include the `ffi/` prefix for them. --- .github/workflows/attach-static-libs.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 152d7df02fea..49d6881cc064 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -154,8 +154,18 @@ jobs: git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force if [[ "${{ github.ref_type }}" == "tag" ]]; then - git tag -a "ffi/${GITHUB_REF#refs/tags/}" -m "firewood ci ${{ github.sha }}: attach firewood static libs" - git push origin "refs/tags/ffi/${GITHUB_REF#refs/tags/}" + # If the tag is a semantic version, prefix it with "ffi/" to ensure go get correctly + # fetches the submodule. Otherwise, use the tag name as is. + # Note: we explicitly ignore semantic versions with suffixes ie. v1.1.1-beta because + # go get treats them as non-semantic version tags. + # Ref: https://github.com/ava-labs/firewood/pull/991 + if [[ "${{ github.ref_name }}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + tag_name="ffi/${GITHUB_REF#refs/tags/}" + else + tag_name="${GITHUB_REF#refs/tags/}" + fi + git tag -a "$tag_name" -m "firewood ci ${{ github.sha }}: attach firewood static libs" + git push origin "refs/tags/$tag_name" fi # Check out the branch created in the previous job on a matrix of From 4f1fdc88d11788cb8be23aa5f5bdc128a3399969 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:05:33 -0400 Subject: [PATCH 0777/1053] Cleanup Nodestore Public Interface (#986) The `kind` and `storage` fields of `Nodestore` are public so that `firewood` can compute the root hash - but it is already stored in memory when the `Nodestore` is created. This diff converts these two fields back to private. --- firewood/src/db.rs | 8 +-- firewood/src/manager.rs | 27 +++------- firewood/src/merkle.rs | 105 +++++++++++++++++++++++---------------- firewood/src/stream.rs | 9 ++-- storage/src/nodestore.rs | 68 +++++++++++-------------- 5 files changed, 107 insertions(+), 110 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 817aecd9a9c6..68814b596029 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -81,7 +81,7 @@ impl api::DbView for HistoricalRev { Self: 'a; async fn root_hash(&self) -> Result, api::Error> { - HashedNodeReader::root_hash(self).map_err(api::Error::FileIO) + Ok(HashedNodeReader::root_hash(self)) } async fn val(&self, key: K) -> Result>, api::Error> { @@ -349,11 +349,11 @@ impl Proposal<'_> { /// Get the root hash of the proposal synchronously pub fn root_hash_sync(&self) -> Result, api::Error> { #[cfg(not(feature = "ethhash"))] - return Ok(self.nodestore.root_hash()?); + return Ok(self.nodestore.root_hash()); #[cfg(feature = "ethhash")] return Ok(Some( self.nodestore - .root_hash()? + .root_hash() .unwrap_or_else(firewood_storage::empty_trie_hash), )); } @@ -367,7 +367,7 @@ impl api::DbView for Proposal<'_> { Self: 'b; async fn root_hash(&self) -> Result, api::Error> { - self.nodestore.root_hash().map_err(api::Error::from) + Ok(self.nodestore.root_hash()) } async fn val(&self, key: K) -> Result>, api::Error> { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index c4d8b368d50c..5e1e91304b0c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -48,7 +48,7 @@ use crate::v2::api::HashKey; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ - Committed, FileBacked, FileIoError, ImmutableProposal, NodeStore, Parentable, TrieHash, + Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, }; #[derive(Clone, Debug, TypedBuilder)] @@ -128,11 +128,7 @@ impl RevisionManager { empty_hash: OnceLock::new(), }; - if let Some(hash) = nodestore - .kind - .root_hash() - .or_else(|| manager.empty_trie_hash()) - { + if let Some(hash) = nodestore.root_hash().or_else(|| manager.empty_trie_hash()) { manager.by_hash.insert(hash, nodestore.clone()); } @@ -146,11 +142,11 @@ impl RevisionManager { pub fn all_hashes(&self) -> Vec { self.historical .iter() - .filter_map(|r| r.kind.root_hash().or_else(|| self.empty_trie_hash())) + .filter_map(|r| r.root_hash().or_else(|| self.empty_trie_hash())) .chain( self.proposals .iter() - .filter_map(|p| p.kind.root_hash().or_else(|| self.empty_trie_hash())), + .filter_map(|p| p.root_hash().or_else(|| self.empty_trie_hash())), ) .collect() } @@ -186,10 +182,7 @@ impl RevisionManager { pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); - if !proposal - .kind - .parent_hash_is(current_revision.kind.root_hash()) - { + if !proposal.parent_hash_is(current_revision.root_hash()) { return Err(RevisionManagerError::NotLatest); } @@ -202,7 +195,7 @@ impl RevisionManager { // TODO: Handle the case where we get something off the free list that is not free while self.historical.len() >= self.max_revisions { let oldest = self.historical.pop_front().expect("must be present"); - if let Some(oldest_hash) = oldest.kind.root_hash().or_else(|| self.empty_trie_hash()) { + if let Some(oldest_hash) = oldest.root_hash().or_else(|| self.empty_trie_hash()) { self.by_hash.remove(&oldest_hash); } @@ -226,11 +219,7 @@ impl RevisionManager { // 4. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical.push_back(committed.clone()); - if let Some(hash) = committed - .kind - .root_hash() - .or_else(|| self.empty_trie_hash()) - { + if let Some(hash) = committed.root_hash().or_else(|| self.empty_trie_hash()) { self.by_hash.insert(hash, committed.clone()); } // TODO: We could allow other commits to start here using the pending list @@ -277,7 +266,7 @@ impl RevisionManager { } pub fn root_hash(&self) -> Result, RevisionManagerError> { - Ok(self.current_revision().kind.root_hash()) + Ok(self.current_revision().root_hash()) } pub fn current_revision(&self) -> CommittedRevision { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 6fb71a6ba691..193e08becb3d 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -417,10 +417,8 @@ impl Merkle { pub(crate) fn dump(&self) -> Result { let mut result = String::new(); writeln!(result, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; - if let Some((root_addr, root_hash)) = self - .nodestore - .root_address_and_hash() - .expect("failed to get root address and hash") + if let (Some(root_addr), Some(root_hash)) = + (self.nodestore.root_address(), self.nodestore.root_hash()) { writeln!(result, " root -> {root_addr}") .map_err(Error::other) @@ -1365,7 +1363,7 @@ mod tests { let merkle = merkle.hash(); - let root_hash = merkle.nodestore.root_hash().unwrap().unwrap(); + let root_hash = merkle.nodestore.root_hash().unwrap(); for (key, value) in kvs { let proof = merkle.prove(&key).unwrap(); @@ -1805,7 +1803,7 @@ mod tests { #[cfg(not(feature = "branch_factor_256"))] { let merkle = merkle_build_test(kvs).unwrap().hash(); - let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else { + let Some(got_hash) = merkle.nodestore.root_hash() else { assert!(expected_hash.is_none()); return; }; @@ -1855,7 +1853,7 @@ mod tests { use firewood_triehash::trie_root; let merkle = merkle_build_test(kvs.to_vec()).unwrap().hash(); - let firewood_hash = merkle.nodestore.root_hash().unwrap().unwrap_or_default(); + let firewood_hash = merkle.nodestore.root_hash().unwrap_or_default(); let eth_hash = trie_root::(kvs.to_vec()); let firewood_hash = H256::from_slice(firewood_hash.as_ref()); @@ -1919,7 +1917,7 @@ mod tests { .collect::, Box<_>)>>(); let merkle = merkle_build_test(items).unwrap().hash(); - let firewood_hash = merkle.nodestore.root_hash().unwrap(); + let firewood_hash = merkle.nodestore.root_hash(); assert_eq!( firewood_hash, @@ -2007,7 +2005,18 @@ mod tests { fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); + let max_len0 = 8; let max_len1 = 4; let keygen = || { @@ -2036,47 +2045,55 @@ mod tests { items.sort(); - let mut merkle = merkle_build_test(items.clone())?; - - let mut hashes = Vec::new(); - - for (k, v) in &items { - let root_hash = merkle - .nodestore - .root_address_and_hash()? - .map(|(_, hash)| hash); - hashes.push((root_hash, merkle.dump().unwrap())); - merkle.insert(k, v.clone())?; - } - - let mut new_hashes = Vec::new(); - - for (k, _) in items.iter().rev() { - let before = merkle.dump().unwrap(); - merkle.remove(k)?; - let root_hash = merkle - .nodestore - .root_address_and_hash()? - .map(|(_, hash)| hash); - new_hashes.push((root_hash, k, before, merkle.dump().unwrap())); - } - - hashes.reverse(); + let init_merkle: Merkle> = + merkle_build_test::, Box<[u8]>>(vec![])?; + let init_immutable_merkle: Merkle, _>> = + init_merkle.try_into().unwrap(); + + let (hashes, complete_immutable_merkle) = items.iter().fold( + (vec![], init_immutable_merkle), + |(mut hashes, immutable_merkle), (k, v)| { + let root_hash = immutable_merkle.nodestore.root_hash(); + hashes.push(root_hash); + let mut merkle = + Merkle::from(NodeStore::new(Arc::new(immutable_merkle.nodestore)).unwrap()); + merkle.insert(k, v.clone()).unwrap(); + (hashes, merkle.try_into().unwrap()) + }, + ); + + let (new_hashes, _) = items.iter().rev().fold( + (vec![], complete_immutable_merkle), + |(mut new_hashes, immutable_merkle_before_removal), (k, _)| { + let before = immutable_merkle_before_removal.dump().unwrap(); + let mut merkle = Merkle::from( + NodeStore::new(Arc::new(immutable_merkle_before_removal.nodestore)) + .unwrap(), + ); + merkle.remove(k).unwrap(); + let immutable_merkle_after_removal: Merkle< + NodeStore, _>, + > = merkle.try_into().unwrap(); + new_hashes.push(( + immutable_merkle_after_removal.nodestore.root_hash(), + k, + before, + immutable_merkle_after_removal.dump().unwrap(), + )); + (new_hashes, immutable_merkle_after_removal) + }, + ); - for i in 0..hashes.len() { - #[allow(clippy::indexing_slicing)] - let (new_hash, key, before_removal, after_removal) = &new_hashes[i]; - #[allow(clippy::indexing_slicing)] - let expected_hash = &hashes[i]; + for (expected_hash, (actual_hash, key, before_removal, after_removal)) in + hashes.into_iter().rev().zip(new_hashes) + { let key = key.iter().fold(String::new(), |mut s, b| { let _ = write!(s, "{b:02x}"); s }); assert_eq!( - new_hash.clone(), - expected_hash.0, - "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{:?}\n", - expected_hash.0 + actual_hash, expected_hash, + "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_hash:?}\nactual:\n{actual_hash:?}\n", ); } } diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index d0d177885ac1..f35685998404 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -625,7 +625,7 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { mod tests { use std::sync::Arc; - use firewood_storage::{MemStore, MutableProposal, NodeStore}; + use firewood_storage::{ImmutableProposal, MemStore, MutableProposal, NodeStore}; use crate::merkle::Merkle; @@ -1160,9 +1160,12 @@ mod tests { merkle.insert(&branch, branch.into()).unwrap(); - let mut stream = merkle.key_value_iter(); + let immutable_merkle: Merkle, _>> = + merkle.try_into().unwrap(); + println!("{}", immutable_merkle.dump().unwrap()); + merkle = Merkle::from(NodeStore::new(Arc::new(immutable_merkle.into_inner())).unwrap()); - println!("{}", merkle.dump().unwrap()); + let mut stream = merkle.key_value_iter(); assert_eq!( stream.next().await.unwrap().unwrap(), diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 5237c40967f2..3a2200794623 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -316,11 +316,6 @@ impl NodeStore { Ok((node, size[0])) } - /// Get a reference to the header of this nodestore - pub const fn header(&self) -> &NodeStoreHeader { - &self.header - } - /// Get the size of an area index (used by the checker) pub const fn size_from_area_index(&self, index: AreaIndex) -> u64 { AREA_SIZES[index as usize] @@ -890,11 +885,6 @@ impl NodeStoreHeader { )) } } - - // return the size of this nodestore - pub const fn size(&self) -> u64 { - self.size - } } /// A [`FreeArea`] is stored at the start of the area that contained a node that @@ -906,13 +896,11 @@ struct FreeArea { /// Reads from an immutable (i.e. already hashed) merkle trie. pub trait HashedNodeReader: TrieReader { - /// Gets the address and hash of the root node of an immutable merkle trie. - fn root_address_and_hash(&self) -> Result, FileIoError>; + /// Gets the address of the root node of an immutable merkle trie. + fn root_address(&self) -> Option; /// Gets the hash of the root node of an immutable merkle trie. - fn root_hash(&self) -> Result, FileIoError> { - Ok(self.root_address_and_hash()?.map(|(_, hash)| hash)) - } + fn root_hash(&self) -> Option; } /// Reads nodes and the root address from a merkle trie. @@ -999,7 +987,7 @@ pub struct ImmutableProposal { impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. #[must_use] - pub fn parent_hash_is(&self, hash: Option) -> bool { + fn parent_hash_is(&self, hash: Option) -> bool { match > as arc_swap::access::DynAccess>>::load( &self.parent, ) @@ -1066,9 +1054,9 @@ pub struct NodeStore { // Metadata for this revision. header: NodeStoreHeader, /// This is one of [Committed], [`ImmutableProposal`], or [`MutableProposal`]. - pub kind: T, + kind: T, /// Persisted storage to read nodes from. - pub storage: Arc, + storage: Arc, } /// Contains the state of a proposal that is still being modified. @@ -1261,6 +1249,12 @@ impl NodeStore, S> { Ok((addr, hash)) } + + /// Re-export the `parent_hash_is` function of [`ImmutableProposal`]. + #[must_use] + pub fn parent_hash_is(&self, hash: Option) -> bool { + self.kind.parent_hash_is(hash) + } } impl NodeStore { @@ -1519,34 +1513,28 @@ impl RootReader for NodeSt impl HashedNodeReader for NodeStore where NodeStore: TrieReader, - T: ReadInMemoryNode, + T: Parentable, S: ReadableStorage, { - fn root_address_and_hash(&self) -> Result, FileIoError> { - if let Some(root_addr) = self.header.root_address { - let root_node = self.read_node(root_addr)?; - let root_hash = hash_node(&root_node, &Path::new()); - Ok(Some((root_addr, root_hash.into_triehash()))) - } else { - Ok(None) - } + fn root_address(&self) -> Option { + self.header.root_address + } + + fn root_hash(&self) -> Option { + self.kind.root_hash() } } -impl HashedNodeReader for Arc> +impl HashedNodeReader for Arc where - NodeStore: TrieReader, - T: ReadInMemoryNode, - S: ReadableStorage, + N: HashedNodeReader, { - fn root_address_and_hash(&self) -> Result, FileIoError> { - if let Some(root_addr) = self.header.root_address { - let root_node = self.read_node(root_addr)?; - let root_hash = hash_node(&root_node, &Path::new()); - Ok(Some((root_addr, root_hash.into_triehash()))) - } else { - Ok(None) - } + fn root_address(&self) -> Option { + self.as_ref().root_address() + } + + fn root_hash(&self) -> Option { + self.as_ref().root_hash() } } @@ -1632,7 +1620,7 @@ mod tests { // now check r2's parent, should match the hash of r1 (which is still None) let parent: DynGuard> = r2.kind.parent.load(); if let NodeStoreParent::Committed(hash) = &**parent { - assert_eq!(*hash, r1.root_hash().unwrap()); + assert_eq!(*hash, r1.root_hash()); assert_eq!(*hash, None); } else { panic!("expected committed parent"); From 969dabc7a25ec4e533c087317688cb780f295a7c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 25 Jun 2025 17:49:29 -0400 Subject: [PATCH 0778/1053] perf(metrics): Add some metrics around propose and commit times (#989) Adds: - firewood.proposal.create for number of created proposals - firewood.proposal.create_ms for number of ms spent creating proposals - firewood.proposal.commit for number of commits - firewood.proposal.commit_ms for number of ms spent commmiting proposals All metrics are tagged with "success=false" or "success=true" depending on whether the operation succeeded or failed. To do this easily and start the process of consolidating metrics, created a few metrics macros, start_metrics and finish_metrics, with appropriate documentation. --------- Signed-off-by: Ron Kuris Co-authored-by: Joachim Brandon LeBlanc --- .github/check-license-headers.yaml | 2 + firewood-macros/Cargo.toml | 27 ++ firewood-macros/README.md | 158 ++++++++++ firewood-macros/src/lib.rs | 292 ++++++++++++++++++ .../tests/compile_fail/invalid_args.rs | 9 + .../tests/compile_fail/invalid_args.stderr | 5 + firewood-macros/tests/compile_fail/no_args.rs | 9 + .../tests/compile_fail/no_args.stderr | 7 + .../tests/compile_fail/non_result_return.rs | 9 + .../compile_fail/non_result_return.stderr | 5 + .../tests/compile_pass/basic_usage.rs | 22 ++ .../tests/compile_pass/with_attributes.rs | 33 ++ firewood/Cargo.toml | 2 + firewood/src/db.rs | 1 + firewood/src/lib.rs | 5 +- firewood/src/manager.rs | 1 + 16 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 firewood-macros/Cargo.toml create mode 100644 firewood-macros/README.md create mode 100644 firewood-macros/src/lib.rs create mode 100644 firewood-macros/tests/compile_fail/invalid_args.rs create mode 100644 firewood-macros/tests/compile_fail/invalid_args.stderr create mode 100644 firewood-macros/tests/compile_fail/no_args.rs create mode 100644 firewood-macros/tests/compile_fail/no_args.stderr create mode 100644 firewood-macros/tests/compile_fail/non_result_return.rs create mode 100644 firewood-macros/tests/compile_fail/non_result_return.stderr create mode 100644 firewood-macros/tests/compile_pass/basic_usage.rs create mode 100644 firewood-macros/tests/compile_pass/with_attributes.rs diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 9a605a5d627a..ae9d210db3aa 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -21,6 +21,7 @@ "triehash/**", "CHANGELOG.md", "cliff.toml", + "**/tests/compile_*/**", ], "license": "./.github/license-header.txt" }, @@ -43,6 +44,7 @@ "triehash/**", "CHANGELOG.md", "cliff.toml", + "**/tests/compile_*/**", ], } ] diff --git a/firewood-macros/Cargo.toml b/firewood-macros/Cargo.toml new file mode 100644 index 000000000000..a694a7a36960 --- /dev/null +++ b/firewood-macros/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "firewood-macros" +version = "0.0.6" +edition = "2024" +rust-version = "1.85" +authors = [ + "Ron Kuris ", +] +description = "Proc macros for Firewood metrics" +license-file = "../LICENSE.md" +homepage = "https://avalabs.org" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } + +[dev-dependencies] +trybuild = "1.0" +metrics = "0.24" +coarsetime = "0.1" + +[lints] +workspace = true diff --git a/firewood-macros/README.md b/firewood-macros/README.md new file mode 100644 index 000000000000..eb0fda6a27ae --- /dev/null +++ b/firewood-macros/README.md @@ -0,0 +1,158 @@ +# Firewood Macros + +A Rust procedural macro crate providing zero-allocation metrics instrumentation for the Firewood database. + +## Overview + +This crate provides the `#[metrics]` attribute macro that automatically instruments functions with performance metrics collection. The macro is designed for high-performance applications where allocation overhead during metrics collection is unacceptable. + +## Features + +- **Zero Runtime Allocations**: Uses compile-time string concatenation and static label arrays +- **Automatic Timing**: Measures function execution time with microsecond precision +- **Success/Failure Tracking**: Automatically labels metrics based on `Result` return values +- **Metric Descriptions**: Optional human-readable descriptions for better observability +- **Compile-time Validation**: Ensures functions return `Result` types + +## Usage + +Add the dependency to your `Cargo.toml`: + +```toml +[dependencies] +firewood-macros = { path = "../firewood-macros" } +metrics = "0.24" +coarsetime = "0.1" +``` + +### Basic Usage + +```rust +use firewood_macros::metrics; + +#[metrics("firewood.example")] +fn example() -> Result, DatabaseError> { + // Your function implementation + Ok(vec![]) +} +``` + +### With Description + +```rust +#[metrics("firewood.example", "example operation")] +fn example(user: User) -> Result<(), DatabaseError> { + // Your function implementation + Ok(()) +} +``` + +## Generated Metrics + +For each instrumented function, the macro generates two metrics: + +1. **Count Metric** (base name): Tracks the number of function calls +2. **Timing Metric** (base name + "_ms"): Tracks execution time in milliseconds + +Both metrics include a `success` label: + +- `success="true"` for `Ok(_)` results +- `success="false"` for `Err(_)` results + +### Example Output + +For `#[metrics("firewood.query", "data retrieval")]`: + +- `firewood.example{success="true"}` - Count of successful queries +- `firewood.example{success="false"}` - Count of failed queries +- `firewood.example_ms{success="true"}` - Timing of successful queries +- `firewood.example_ms{success="false"}` - Timing of failed queries + +## Requirements + +- Functions must return a `Result` type +- The `metrics` and `coarsetime` crates must be available in scope +- Rust 1.70+ (for `is_some_and` method) + +## Performance Characteristics + +### Zero Allocations + +The macro generates code that avoids all runtime allocations: + +```rust +// Static label arrays (no allocation) +static __METRICS_LABELS_SUCCESS: &[(&str, &str)] = &[("success", "true")]; +static __METRICS_LABELS_ERROR: &[(&str, &str)] = &[("success", "false")]; + +// Compile-time string concatenation (no allocation) +metrics::counter!(concat!("my.metric", "_ms"), labels) +``` + +### Minimal Overhead + +- Single timestamp capture at function start, using the coarsetime crate, which is known to be extremely fast +- Branch-free label selection based on `Result::is_err()` +- Direct counter increments without intermediate allocations + +## Implementation Details + +### Code Generation + +The macro transforms this: + +```rust +#[metrics("my.operation")] +fn my_function() -> Result { + Ok("result".to_string()) +} +``` + +Into approximately this: + +```rust +fn my_function() -> Result { + // Register metrics (once per process) + static __METRICS_REGISTERED: std::sync::Once = std::sync::Once::new(); + __METRICS_REGISTERED.call_once(|| { + metrics::describe_counter!("my.operation", "Operation counter"); + metrics::describe_counter!(concat!("my.operation", "_ms"), "Operation timing"); + }); + + // Start timing + let __metrics_start = coarsetime::Instant::now(); + + // Execute original function + let __metrics_result = (|| { + Ok("result".to_string()) + })(); + + // Record metrics + static __METRICS_LABELS_SUCCESS: &[(&str, &str)] = &[("success", "true")]; + static __METRICS_LABELS_ERROR: &[(&str, &str)] = &[("success", "false")]; + let __metrics_labels = if __metrics_result.is_err() { + __METRICS_LABELS_ERROR + } else { + __METRICS_LABELS_SUCCESS + }; + + metrics::counter!("my.operation", __metrics_labels).increment(1); + metrics::counter!(concat!("my.operation", "_ms"), __metrics_labels) + .increment(__metrics_start.elapsed().as_millis()); + + __metrics_result +} +``` + +## Testing + +The crate includes comprehensive tests: + +```bash +cargo test -p firewood-macros +``` + +## License + +This crate is part of the Firewood project and follows the same licensing terms. +See LICENSE.md at the top level for details. diff --git a/firewood-macros/src/lib.rs b/firewood-macros/src/lib.rs new file mode 100644 index 000000000000..8b6293641bb1 --- /dev/null +++ b/firewood-macros/src/lib.rs @@ -0,0 +1,292 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! Proc macros for Firewood metrics + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{ItemFn, Lit, ReturnType, Token, parse_macro_input}; + +/// Arguments for the metrics macro +struct MetricsArgs { + name: String, + description: Option, +} + +impl Parse for MetricsArgs { + fn parse(input: ParseStream) -> syn::Result { + let name_lit: Lit = input.parse()?; + let name = match name_lit { + Lit::Str(s) => s.value(), + _ => { + return Err(syn::Error::new_spanned( + name_lit, + "Expected string literal for metric name", + )); + } + }; + + let description = if input.parse::().is_ok() { + let desc_lit: Lit = input.parse()?; + match desc_lit { + Lit::Str(s) => Some(s.value()), + _ => { + return Err(syn::Error::new_spanned( + desc_lit, + "Expected string literal for description", + )); + } + } + } else { + None + }; + + Ok(MetricsArgs { name, description }) + } +} + +/// A proc macro attribute that automatically adds metrics timing to functions. +/// +/// This macro adds timing instrumentation to functions that return `Result`. +/// It generates two counters: +/// 1. A count counter with the provided prefix that increments by 1 +/// 2. A timing counter with the prefix + "_ms" that records elapsed time in milliseconds +/// +/// Both counters include a "success" label that is "true" for Ok results and "false" for Err results. +/// The metrics are automatically registered with descriptions for better observability. +/// +/// # Usage +/// ```rust,ignore +/// use firewood_macros::metrics; +/// +/// // Basic usage with just a metric name +/// #[metrics("my.operation")] +/// fn my_function() -> Result { +/// // function body +/// Ok("success".to_string()) +/// } +/// +/// // With an optional description +/// #[metrics("my.operation", "Description of what this operation does")] +/// fn my_function_with_desc() -> Result { +/// // function body +/// Ok("success".to_string()) +/// } +/// ``` +/// +/// # Generated Code +/// The macro transforms the function to include: +/// - Metric registration with descriptions at the beginning +/// - A timer start at the beginning of the function +/// - Metrics collection before returning, with success/failure labels +/// - Preservation of the original return value +/// +/// # Requirements +/// - The function must return a `Result` type +/// - The `metrics` and `coarsetime` crates must be available +#[proc_macro_attribute] +pub fn metrics(args: TokenStream, input: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(input as ItemFn); + + // Parse the attribute arguments - expecting metric name and optional description + let (metric_prefix, description) = if args.is_empty() { + return syn::Error::new_spanned( + &input_fn, + "Expected string literal for metric prefix, e.g., #[metrics(\"my.operation\")] or #[metrics(\"my.operation\", \"description\")]", + ) + .to_compile_error() + .into(); + } else { + match syn::parse::(args) { + Ok(parsed_args) => (parsed_args.name, parsed_args.description), + Err(e) => { + return syn::Error::new( + e.span(), + "Expected string literal(s) for metric name and optional description", + ) + .to_compile_error() + .into(); + } + } + }; + + // Validate that the function returns a Result + let return_type = match &input_fn.sig.output { + ReturnType::Type(_, ty) => ty, + ReturnType::Default => { + return syn::Error::new_spanned( + &input_fn.sig, + "Function must return a Result to use #[metrics] attribute", + ) + .to_compile_error() + .into(); + } + }; + + // Check if it's a Result type (this is a simple check, could be more sophisticated) + let is_result = match return_type.as_ref() { + syn::Type::Path(type_path) => type_path + .path + .segments + .last() + .is_some_and(|seg| seg.ident == "Result"), + _ => false, + }; + + if !is_result { + return syn::Error::new_spanned( + return_type, + "Function must return a Result to use #[metrics] attribute", + ) + .to_compile_error() + .into(); + } + + let args = MetricsArgs { + name: metric_prefix, + description, + }; + + let expanded = generate_metrics_wrapper(&input_fn, &args); + TokenStream::from(expanded) +} + +fn generate_metrics_wrapper(input_fn: &ItemFn, args: &MetricsArgs) -> proc_macro2::TokenStream { + let fn_vis = &input_fn.vis; + let fn_sig = &input_fn.sig; + let fn_block = &input_fn.block; + let fn_attrs = &input_fn.attrs; + let metric_prefix = &args.name; + + // Generate description registration code if description is provided + let registration_code = if let Some(desc) = &args.description { + let count_desc = format!("Number of {desc} operations"); + let timing_desc = format!("Timing of {desc} operations in milliseconds"); + quote! { + // Register metrics with descriptions (only runs once due to static guard) + static __METRICS_REGISTERED: std::sync::Once = std::sync::Once::new(); + __METRICS_REGISTERED.call_once(|| { + metrics::describe_counter!(#metric_prefix, #count_desc); + metrics::describe_counter!(concat!(#metric_prefix, "_ms"), #timing_desc); + }); + } + } else { + quote! { + // Register metrics without descriptions (only runs once due to static guard) + static __METRICS_REGISTERED: std::sync::Once = std::sync::Once::new(); + __METRICS_REGISTERED.call_once(|| { + metrics::describe_counter!(#metric_prefix, "Operation counter"); + metrics::describe_counter!(concat!(#metric_prefix, "_ms"), "Operation timing in milliseconds"); + }); + } + }; + + quote! { + #(#fn_attrs)* + #fn_vis #fn_sig { + #registration_code + + let __metrics_start = coarsetime::Instant::now(); + + let __metrics_result = { #fn_block }; + + // Use static label arrays to avoid runtime allocation + static __METRICS_LABELS_SUCCESS: &[(&str, &str)] = &[("success", "true")]; + static __METRICS_LABELS_ERROR: &[(&str, &str)] = &[("success", "false")]; + let __metrics_labels = if __metrics_result.is_err() { + __METRICS_LABELS_ERROR + } else { + __METRICS_LABELS_SUCCESS + }; + + // Increment count counter (base name) + metrics::counter!(#metric_prefix, __metrics_labels).increment(1); + + // Increment timing counter (base name + "_ms") using compile-time concatenation + metrics::counter!(concat!(#metric_prefix, "_ms"), __metrics_labels) + .increment(__metrics_start.elapsed().as_millis()); + + __metrics_result + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_proc_macro_compilation() { + // Test that the proc macro generates compilable code + let t = trybuild::TestCases::new(); + t.pass("tests/compile_pass/*.rs"); + t.compile_fail("tests/compile_fail/*.rs"); + } + + #[test] + #[allow(clippy::unwrap_used)] + fn test_metrics_args_parsing() { + // Test single argument parsing + let input = quote::quote! { "test.metric" }; + let parsed: MetricsArgs = syn::parse2(input).unwrap(); + assert_eq!(parsed.name, "test.metric"); + assert_eq!(parsed.description, None); + + // Test two argument parsing + let input = quote::quote! { "test.metric", "test description" }; + let parsed: MetricsArgs = syn::parse2(input).unwrap(); + assert_eq!(parsed.name, "test.metric"); + assert_eq!(parsed.description, Some("test description".to_string())); + } + + #[test] + fn test_invalid_args_parsing() { + // Test that invalid arguments fail to parse + let input = quote::quote! { 123 }; + let result: syn::Result = syn::parse2(input); + assert!(result.is_err()); + + // Test that too many arguments fail + let input = quote::quote! { "test.metric", "description", "extra" }; + let result: syn::Result = syn::parse2(input); + assert!(result.is_err()); + } + + #[test] + fn test_generated_code_structure() { + // Test that the proc macro generates the expected code structure + use syn::{ItemFn, parse_quote}; + + let input: ItemFn = parse_quote! { + fn test_function() -> Result<(), &'static str> { + Ok(()) + } + }; + + let args = MetricsArgs { + name: "test.metric".to_string(), + description: Some("test description".to_string()), + }; + + let result = generate_metrics_wrapper(&input, &args); + let generated_code = result.to_string(); + + // Verify key components are present in the generated code + assert!(generated_code.contains("__METRICS_LABELS_SUCCESS")); + assert!(generated_code.contains("__METRICS_LABELS_ERROR")); + assert!(generated_code.contains("test.metric")); + assert!( + generated_code.contains("coarsetime") + && generated_code.contains("Instant") + && generated_code.contains("now") + ); + assert!(generated_code.contains("metrics") && generated_code.contains("counter")); + assert!(generated_code.contains("Number of test description operations")); + assert!(generated_code.contains("Timing of test description operations")); + + // Check for the _ms suffix - it should be generated by concat! + assert!(generated_code.contains("concat") && generated_code.contains('!')); + assert!(generated_code.contains("_ms")); + } +} diff --git a/firewood-macros/tests/compile_fail/invalid_args.rs b/firewood-macros/tests/compile_fail/invalid_args.rs new file mode 100644 index 000000000000..da5cc65af250 --- /dev/null +++ b/firewood-macros/tests/compile_fail/invalid_args.rs @@ -0,0 +1,9 @@ +// Test that metrics macro fails with invalid arguments +use firewood_macros::metrics; + +#[metrics(123)] +fn function_with_invalid_arg() -> Result<(), &'static str> { + Ok(()) +} + +fn main() {} diff --git a/firewood-macros/tests/compile_fail/invalid_args.stderr b/firewood-macros/tests/compile_fail/invalid_args.stderr new file mode 100644 index 000000000000..936dbbcb4483 --- /dev/null +++ b/firewood-macros/tests/compile_fail/invalid_args.stderr @@ -0,0 +1,5 @@ +error: Expected string literal(s) for metric name and optional description + --> tests/compile_fail/invalid_args.rs:4:11 + | +4 | #[metrics(123)] + | ^^^ diff --git a/firewood-macros/tests/compile_fail/no_args.rs b/firewood-macros/tests/compile_fail/no_args.rs new file mode 100644 index 000000000000..75a0da7d0486 --- /dev/null +++ b/firewood-macros/tests/compile_fail/no_args.rs @@ -0,0 +1,9 @@ +// Test that metrics macro fails when no arguments are provided +use firewood_macros::metrics; + +#[metrics()] +fn function_without_args() -> Result<(), &'static str> { + Ok(()) +} + +fn main() {} diff --git a/firewood-macros/tests/compile_fail/no_args.stderr b/firewood-macros/tests/compile_fail/no_args.stderr new file mode 100644 index 000000000000..b2035da74ab6 --- /dev/null +++ b/firewood-macros/tests/compile_fail/no_args.stderr @@ -0,0 +1,7 @@ +error: Expected string literal for metric prefix, e.g., #[metrics("my.operation")] or #[metrics("my.operation", "description")] + --> tests/compile_fail/no_args.rs:5:1 + | +5 | / fn function_without_args() -> Result<(), &'static str> { +6 | | Ok(()) +7 | | } + | |_^ diff --git a/firewood-macros/tests/compile_fail/non_result_return.rs b/firewood-macros/tests/compile_fail/non_result_return.rs new file mode 100644 index 000000000000..0dceede2de97 --- /dev/null +++ b/firewood-macros/tests/compile_fail/non_result_return.rs @@ -0,0 +1,9 @@ +// Test that metrics macro fails when function doesn't return Result +use firewood_macros::metrics; + +#[metrics("test.invalid")] +fn function_without_result() -> i32 { + 42 +} + +fn main() {} diff --git a/firewood-macros/tests/compile_fail/non_result_return.stderr b/firewood-macros/tests/compile_fail/non_result_return.stderr new file mode 100644 index 000000000000..5acb329a183a --- /dev/null +++ b/firewood-macros/tests/compile_fail/non_result_return.stderr @@ -0,0 +1,5 @@ +error: Function must return a Result to use #[metrics] attribute + --> tests/compile_fail/non_result_return.rs:5:33 + | +5 | fn function_without_result() -> i32 { + | ^^^ diff --git a/firewood-macros/tests/compile_pass/basic_usage.rs b/firewood-macros/tests/compile_pass/basic_usage.rs new file mode 100644 index 000000000000..1b9c6198cf6f --- /dev/null +++ b/firewood-macros/tests/compile_pass/basic_usage.rs @@ -0,0 +1,22 @@ +// Test that basic metrics macro usage compiles correctly +use firewood_macros::metrics; + +#[metrics("test.basic")] +fn test_basic_function() -> Result<(), &'static str> { + Ok(()) +} + +#[metrics("test.with_description", "test operation")] +fn test_function_with_description() -> Result> { + Ok("success".to_string()) +} + +#[metrics("test.complex")] +async fn test_async_function() -> Result, std::io::Error> { + Ok(vec![1, 2, 3]) +} + +fn main() { + // These functions should compile but we don't need to call them + // since this is just a compilation test +} diff --git a/firewood-macros/tests/compile_pass/with_attributes.rs b/firewood-macros/tests/compile_pass/with_attributes.rs new file mode 100644 index 000000000000..fceaf2b12f76 --- /dev/null +++ b/firewood-macros/tests/compile_pass/with_attributes.rs @@ -0,0 +1,33 @@ +// Test that metrics macro works with other function attributes +use firewood_macros::metrics; + +#[derive(Debug)] +struct TestError; + +impl std::fmt::Display for TestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TestError") + } +} + +impl std::error::Error for TestError {} + +#[metrics("test.with_doc", "documented function")] +/// This function has documentation +pub fn documented_function() -> Result { + Ok(42) +} + +#[inline] +#[metrics("test.inline")] +fn inline_function() -> Result<(), TestError> { + Ok(()) +} + +#[allow(dead_code)] +#[metrics("test.allowed", "function with allow attribute")] +fn function_with_allow() -> Result { + Ok(true) +} + +fn main() {} diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index cd59376710a5..3b83a4343452 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -36,6 +36,8 @@ bincode = "1.3.3" integer-encoding = "4.0.0" smallvec = "1.6.1" fastrace = { version = "0.7.4" } +coarsetime = "0.1.36" +firewood-macros = { version = "0.0.6", path = "../firewood-macros" } [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 68814b596029..55a51def7626 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -446,6 +446,7 @@ impl Proposal<'_> { Ok(self.create_proposal(batch)?.into()) } + #[crate::metrics("firewood.proposal.create", "database proposal creation")] fn create_proposal( &self, batch: api::Batch, diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 6e0af50c7846..ebf623aab21e 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -8,7 +8,7 @@ //! store trie nodes on-disk. Unlike most of state management approaches in the field, //! it is not built on top of a generic KV store such as LevelDB/RocksDB. Firewood, like a //! B+-tree based database, directly uses the trie structure as the index on-disk. Thus, -//! there is no additional “emulation” of the logical trie to flatten out the data structure +//! there is no additional "emulation" of the logical trie to flatten out the data structure //! to feed into the underlying database that is unaware of the data being stored. The convenient //! byproduct of this approach is that iteration is still fast (for serving state sync queries) //! but compaction is not required to maintain the index. Firewood was first conceived to provide @@ -124,6 +124,9 @@ pub mod merkle; /// Proof module pub mod proof; +// Re-export the proc macro from firewood-macros +pub use firewood_macros::metrics; + /// Range proof module pub mod range_proof; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 5e1e91304b0c..bfae32126b54 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -179,6 +179,7 @@ impl RevisionManager { /// 8. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. #[fastrace::trace(short_name = true)] + #[crate::metrics("firewood.proposal.commit", "proposal commit to storage")] pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); From a221306805c60b0ba38a4b369c5e4ec9d6feb9d3 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 25 Jun 2025 17:59:38 -0400 Subject: [PATCH 0779/1053] ci: Remove requirement for conventional commits (#994) We still require them for first commits and the PR title must have them, but we don't require them for subsequent commits when pushing additional commits on a branch. --- .github/workflows/conventional-commits.yaml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/workflows/conventional-commits.yaml diff --git a/.github/workflows/conventional-commits.yaml b/.github/workflows/conventional-commits.yaml deleted file mode 100644 index 9b3c6d5fd017..000000000000 --- a/.github/workflows/conventional-commits.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: Conventional Commits - -on: - pull_request: - branches: [ main ] - -jobs: - build: - name: Conventional Commits - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: webiny/action-conventional-commits@v1.3.0 - with: - allowed-commit-types: "build,chore,ci,docs,feat,fix,perf,refactor,style,test" From 83bf8621732ffb5ed60ff178fde6001086a37efa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 25 Jun 2025 18:14:18 -0400 Subject: [PATCH 0780/1053] feat: Add methods to fetch views from any hash (#993) There is a new method, view_sync, which gets a view from a specific hash. We need these in ffi to avoid locking issues in triedb due to asynchronous commit. --------- Co-authored-by: Austin Larson --- ffi/firewood.go | 28 ++++++++++++- ffi/firewood.h | 4 +- ffi/firewood_test.go | 58 +++++++++++++++++++++++++++ ffi/src/lib.rs | 14 ++++--- firewood/src/db.rs | 87 +++++++++++++++++++++++++++++++++++++---- firewood/src/manager.rs | 20 ++++++++++ 6 files changed, 195 insertions(+), 16 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 68f45af71fb2..00556e28edf6 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -23,6 +23,7 @@ package ffi import "C" import ( + "bytes" "errors" "fmt" "strings" @@ -37,7 +38,10 @@ const ( keyNotFound = "key not found" ) -var errDBClosed = errors.New("firewood database already closed") +var ( + errDBClosed = errors.New("firewood database already closed") + EmptyRoot = make([]byte, RootLength) +) // A Database is a handle to a Firewood database. // It is not safe to call these methods with a nil handle. @@ -179,6 +183,26 @@ func (db *Database) Get(key []byte) ([]byte, error) { return bytes, err } +// GetFromRoot retrieves the value for the given key from a specific root hash. +// If the root is not found, it returnas an error. +// If key is not found, it returns (nil, nil). +func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { + if db.handle == nil { + return nil, errDBClosed + } + + // If the root is empty, the database is empty. + if len(root) == 0 || bytes.Equal(root, EmptyRoot) { + return nil, nil + } + + values, cleanup := newValueFactory() + defer cleanup() + val := C.fwd_get_from_root(db.handle, values.from(root), values.from(key)) + + return bytesFromValue(&val) +} + // Root returns the current root hash of the trie. // Empty trie must return common.Hash{}. func (db *Database) Root() ([]byte, error) { @@ -190,7 +214,7 @@ func (db *Database) Root() ([]byte, error) { // If the root hash is not found, return a zeroed slice. if err == nil && bytes == nil { - bytes = make([]byte, RootLength) + bytes = EmptyRoot } return bytes, err } diff --git a/ffi/firewood.h b/ffi/firewood.h index 71260f5cf558..0d06fdfae0c1 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -239,7 +239,9 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, struct Value key); /** - * Gets a value assoicated with the given historical root hash and key. + * Gets a value assoicated with the given root hash and key. + * + * The hash may refer to a historical revision or an existing proposal. * * # Arguments * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 508a74caf639..55b1dfe277c1 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -901,3 +901,61 @@ func TestEmptyProposals(t *testing.T) { r.Equal(vals[i], got, "Get(%d)", i) } } + +// Tests the GetFromRoot function for retrieving values from specific root hashes. +func TestGetFromRoot(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Check empty database + emptyRoot, err := db.Root() + r.NoError(err, "Root of empty database") + got, err := db.GetFromRoot(emptyRoot, []byte("non-existent")) + r.NoError(err, "GetFromRoot empty root") + r.Empty(got, "GetFromRoot empty root should return empty value") + + // Insert some initial data + keys, vals := kvForTest(10) + committedRoot, err := db.Update(keys[:5], vals[:5]) + r.NoError(err) + + // Test getting values from first state + for i := range 5 { + got, err := db.GetFromRoot(committedRoot, keys[i]) + r.NoError(err, "GetFromRoot committed key %d", i) + r.Equal(vals[i], got, "GetFromRoot committed key %d", i) + } + + // Replace the first 5 keys with new values + p, err := db.Propose(keys[:5], vals[5:]) + r.NoError(err, "Propose to update first 5 keys") + r.NotNil(p) + + proposedRoot, err := p.Root() + t.Logf("%x", proposedRoot) + r.NoError(err, "Root of proposal") + + // Test that we can still get old values from the first root + for i := range 5 { + got, err := db.GetFromRoot(committedRoot, keys[i]) + r.NoError(err, "GetFromRoot root1 after update, key %d", i) + r.Equal(vals[i], got, "GetFromRoot root1 after update, key %d", i) + + got, err = db.GetFromRoot(proposedRoot, keys[i]) + r.NoError(err, "GetFromRoot root1 newer key %d", i) + r.Equal(vals[i+5], got, "GetFromRoot root1 newer key %d", i) + } + + // Test with invalid root hash + invalidRoot := []byte("this is not a valid 32-byte hash") + _, err = db.GetFromRoot(invalidRoot, []byte("key")) + r.Error(err, "GetFromRoot with invalid root should return error") + + // Test with valid-length but non-existent root + nonExistentRoot := make([]byte, RootLength) + for i := range nonExistentRoot { + nonExistentRoot[i] = 0xFF // All 1's, very unlikely to exist + } + _, err = db.GetFromRoot(nonExistentRoot, []byte("key")) + r.Error(err, "GetFromRoot with non-existent root should return error") +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index dbd7146ce702..65e436fb0eca 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -16,7 +16,9 @@ use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, RwLock}; -use firewood::db::{BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, Proposal}; +use firewood::db::{ + BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, DbViewSyncBytes as _, Proposal, +}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; use metrics::counter; @@ -114,7 +116,7 @@ fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { // Get value associated with key. let value = rev - .val_sync(key.as_slice()) + .val_sync_bytes(key.as_slice()) .map_err(|e| e.to_string())? .ok_or_else(String::new)?; Ok(value.into()) @@ -176,7 +178,9 @@ fn get_from_proposal( Ok(value.into()) } -/// Gets a value assoicated with the given historical root hash and key. +/// Gets a value assoicated with the given root hash and key. +/// +/// The hash may refer to a historical revision or an existing proposal. /// /// # Arguments /// @@ -213,12 +217,12 @@ fn get_from_root(db: *const DatabaseHandle, root: &Value, key: &Value) -> Result // Get the revision associated with the root hash. let rev = db - .revision_sync(root.as_slice().try_into()?) + .view_sync(root.as_slice().try_into()?) .map_err(|e| e.to_string())?; // Get value associated with key. let value = rev - .val_sync(key.as_slice()) + .val_sync_bytes(key.as_slice()) .map_err(|e| e.to_string())? .ok_or_else(String::new)?; Ok(value.into()) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 55a51def7626..250ed58997d1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -65,10 +65,39 @@ pub trait DbViewSync { fn val_sync(&self, key: K) -> Result>, DbError>; } -impl DbViewSync for HistoricalRev { +/// A synchronous view of the database with raw byte keys (object-safe version). +pub trait DbViewSyncBytes { + /// find a value synchronously using raw bytes + fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError>; +} + +// Provide blanket implementation for DbViewSync using DbViewSyncBytes +impl DbViewSync for T { fn val_sync(&self, key: K) -> Result>, DbError> { + self.val_sync_bytes(key.as_ref()) + } +} + +impl DbViewSyncBytes for Arc { + fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { let merkle = Merkle::from(self); - let value = merkle.get_value(key.as_ref())?; + let value = merkle.get_value(key)?; + Ok(value) + } +} + +impl DbViewSyncBytes for Proposal<'_> { + fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { + let merkle = Merkle::from(self.nodestore.clone()); + let value = merkle.get_value(key)?; + Ok(value) + } +} + +impl DbViewSyncBytes for Arc, FileBacked>> { + fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { + let merkle = Merkle::from(self.clone()); + let value = merkle.get_value(key)?; Ok(value) } } @@ -273,6 +302,16 @@ impl Db { Ok(nodestore) } + /// Synchronously get a view, either committed or proposed + pub fn view_sync(&self, root_hash: TrieHash) -> Result, api::Error> { + let nodestore = self + .manager + .read() + .expect("poisoned lock") + .view(root_hash)?; + Ok(nodestore) + } + /// propose a new batch synchronously pub fn propose_sync( &'_ self, @@ -432,12 +471,6 @@ impl Proposal<'_> { } } - /// Get a value from the proposal synchronously - pub fn val_sync(&self, key: K) -> Result>, api::Error> { - let merkle = Merkle::from(self.nodestore.clone()); - merkle.get_value(key.as_ref()).map_err(api::Error::from) - } - /// Create a new proposal from the current one synchronously pub fn propose_sync( &self, @@ -677,6 +710,44 @@ mod test { assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); } + #[tokio::test] + async fn test_view_sync() { + let db = testdb().await; + + // Create and commit some data to get a historical revision + let batch = vec![BatchOp::Put { + key: b"historical_key", + value: b"historical_value", + }]; + let proposal = db.propose(batch).await.unwrap(); + let historical_hash = proposal.root_hash().await.unwrap().unwrap(); + proposal.commit().await.unwrap(); + + // Create a new proposal (uncommitted) + let batch = vec![BatchOp::Put { + key: b"proposal_key", + value: b"proposal_value", + }]; + let proposal = db.propose(batch).await.unwrap(); + let proposal_hash = proposal.root_hash().await.unwrap().unwrap(); + + // Test that view_sync can find the historical revision + let historical_view = db.view_sync(historical_hash).unwrap(); + let value = historical_view + .val_sync_bytes(b"historical_key") + .unwrap() + .unwrap(); + assert_eq!(&*value, b"historical_value"); + + // Test that view_sync can find the proposal + let proposal_view = db.view_sync(proposal_hash).unwrap(); + let value = proposal_view + .val_sync_bytes(b"proposal_key") + .unwrap() + .unwrap(); + assert_eq!(&*value, b"proposal_value"); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index bfae32126b54..21a3305cc9f1 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -259,6 +259,26 @@ impl RevisionManager { self.proposals.push(proposal); } + pub fn view( + &self, + root_hash: HashKey, + ) -> Result, RevisionManagerError> { + // First try to find it in committed revisions + if let Ok(committed) = self.revision(root_hash.clone()) { + return Ok(Box::new(committed)); + } + + // If not found in committed revisions, try proposals + let proposal = self + .proposals + .iter() + .find(|p| p.root_hash().as_ref() == Some(&root_hash)) + .cloned() + .ok_or(RevisionManagerError::RevisionNotFound)?; + + Ok(Box::new(proposal)) + } + pub fn revision(&self, root_hash: HashKey) -> Result { self.by_hash .get(&root_hash) From 0a0e87285acc9a1b1f6279554983c1205289ba8f Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:59:38 -0400 Subject: [PATCH 0781/1053] style: use cbindgen to convert to pointers (#969) We use `cbindgen` to manage a lot of the FFI layer, but we are underutilizing it (#967), and can make our Rust-side FFI code much more idiomatic without any changes to the Go side. Every call that used to take `*const T` or something similar now takes `&T<'_>` or `Option<&T<'_>>`, except freeing the database in `fwd_close_db`, since we would immediately cast to a raw pointer anyway --- ffi/firewood.h | 9 +- ffi/src/lib.rs | 245 +++++++++++++++++++++---------------------------- 2 files changed, 113 insertions(+), 141 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 0d06fdfae0c1..16b6498fc445 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -30,7 +30,7 @@ typedef struct DatabaseHandle DatabaseHandle; */ typedef struct Value { size_t len; - const uint8_t *data; + uint8_t *data; } Value; /** @@ -45,7 +45,7 @@ typedef struct KeyValue { * Struct returned by `fwd_create_db` and `fwd_open_db` */ typedef struct DatabaseCreationResult { - const struct DatabaseHandle *db; + struct DatabaseHandle *db; uint8_t *error_str; } DatabaseCreationResult; @@ -192,6 +192,7 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa * # Panics * * This function panics if `result` is `null`. + * */ void fwd_free_database_error_result(struct DatabaseCreationResult *result); @@ -210,6 +211,7 @@ void fwd_free_database_error_result(struct DatabaseCreationResult *result); * # Panics * * This function panics if `value` is `null`. + * */ void fwd_free_value(struct Value *value); @@ -233,6 +235,7 @@ void fwd_free_value(struct Value *value); * * ensure that `db` is a valid pointer returned by `open_db` * * ensure that `key` is a valid pointer to a `Value` struct * * call `free_value` to free the memory associated with the returned `Value` + * */ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, ProposalId id, @@ -261,6 +264,7 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * * ensure that `key` is a valid pointer to a `Value` struct * * ensure that `root` is a valid pointer to a `Value` struct * * call `free_value` to free the memory associated with the returned `Value` + * */ struct Value fwd_get_from_root(const struct DatabaseHandle *db, struct Value root, @@ -288,6 +292,7 @@ struct Value fwd_get_from_root(const struct DatabaseHandle *db, * * ensure that `db` is a valid pointer returned by `open_db` * * ensure that `key` is a valid pointer to a `Value` struct * * call `free_value` to free the memory associated with the returned `Value` + * */ struct Value fwd_get_latest(const struct DatabaseHandle *db, struct Value key); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 65e436fb0eca..0d89edcfec37 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -46,6 +46,7 @@ fn next_id() -> ProposalId { /// /// These handles are passed to the other FFI functions. /// +#[derive(Debug)] pub struct DatabaseHandle<'p> { /// List of oustanding proposals, by ID // Keep proposals first, as they must be dropped before the database handle is dropped due to lifetime @@ -93,18 +94,17 @@ impl Deref for DatabaseHandle<'_> { /// * ensure that `db` is a valid pointer returned by `open_db` /// * ensure that `key` is a valid pointer to a `Value` struct /// * call `free_value` to free the memory associated with the returned `Value` +/// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_get_latest(db: *const DatabaseHandle, key: Value) -> Value { +pub unsafe extern "C" fn fwd_get_latest(db: Option<&DatabaseHandle<'_>>, key: Value) -> Value { get_latest(db, &key).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. /// Internal call for `fwd_get_latest` to remove error handling from the C API #[doc(hidden)] -fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { - // Check db is valid. - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - +fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &Value) -> Result { + let db = db.ok_or("db should be non-null")?; // Find root hash. // Matches `hash` function but we use the TrieHash type here let Some(root) = db.root_hash_sync().map_err(|e| e.to_string())? else { @@ -118,7 +118,7 @@ fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { let value = rev .val_sync_bytes(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(String::new)?; + .ok_or("")?; Ok(value.into()) } @@ -141,9 +141,10 @@ fn get_latest(db: *const DatabaseHandle, key: &Value) -> Result { /// * ensure that `db` is a valid pointer returned by `open_db` /// * ensure that `key` is a valid pointer to a `Value` struct /// * call `free_value` to free the memory associated with the returned `Value` +/// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_proposal( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, id: ProposalId, key: Value, ) -> Value { @@ -154,27 +155,23 @@ pub unsafe extern "C" fn fwd_get_from_proposal( /// Internal call for `fwd_get_from_proposal` to remove error handling from the C API #[doc(hidden)] fn get_from_proposal( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, id: ProposalId, key: &Value, ) -> Result { - // Check db is valid. - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - + let db = db.ok_or("db should be non-null")?; // Get proposal from ID. let proposals = db .proposals .read() .map_err(|_| "proposal lock is poisoned")?; - let proposal = proposals - .get(&id) - .ok_or_else(|| String::from("proposal not found"))?; + let proposal = proposals.get(&id).ok_or("proposal not found")?; // Get value associated with key. let value = proposal .val_sync(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(String::new)?; + .ok_or("")?; Ok(value.into()) } @@ -200,9 +197,10 @@ fn get_from_proposal( /// * ensure that `key` is a valid pointer to a `Value` struct /// * ensure that `root` is a valid pointer to a `Value` struct /// * call `free_value` to free the memory associated with the returned `Value` +/// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_root( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, root: Value, key: Value, ) -> Value { @@ -211,10 +209,12 @@ pub unsafe extern "C" fn fwd_get_from_root( /// Internal call for `fwd_get_from_root` to remove error handling from the C API #[doc(hidden)] -fn get_from_root(db: *const DatabaseHandle, root: &Value, key: &Value) -> Result { - // Check db is valid. - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - +fn get_from_root( + db: Option<&DatabaseHandle<'_>>, + root: &Value, + key: &Value, +) -> Result { + let db = db.ok_or("db should be non-null")?; // Get the revision associated with the root hash. let rev = db .view_sync(root.as_slice().try_into()?) @@ -224,7 +224,7 @@ fn get_from_root(db: *const DatabaseHandle, root: &Value, key: &Value) -> Result let value = rev .val_sync_bytes(key.as_slice()) .map_err(|e| e.to_string())? - .ok_or_else(String::new)?; + .ok_or("")?; Ok(value.into()) } @@ -267,9 +267,9 @@ pub struct KeyValue { /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_batch( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Value { batch(db, nkeys, values).unwrap_or_else(Into::into) } @@ -301,15 +301,13 @@ fn convert_to_batch(values: &[KeyValue]) -> Vec> { /// Internal call for `fwd_batch` to remove error handling from the C API #[doc(hidden)] fn batch( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Result { + let db = db.ok_or("db should be non-null")?; + let values = values.ok_or("key-value slice is null")?; let start = coarsetime::Instant::now(); - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - if values.is_null() { - return Err(String::from("key-value list is null")); - } // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; @@ -323,7 +321,7 @@ fn batch( let hash_val = proposal .root_hash_sync() .map_err(|e| e.to_string())? - .ok_or_else(|| String::from("Proposed revision is empty"))? + .ok_or("Proposed revision is empty")? .as_slice() .into(); @@ -362,10 +360,10 @@ fn batch( /// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_propose_on_db( - db: *const DatabaseHandle, +pub unsafe extern "C" fn fwd_propose_on_db<'p>( + db: Option<&'p DatabaseHandle<'p>>, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. @@ -374,16 +372,13 @@ pub unsafe extern "C" fn fwd_propose_on_db( /// Internal call for `fwd_propose_on_db` to remove error handling from the C API #[doc(hidden)] -fn propose_on_db( - db: *const DatabaseHandle, +fn propose_on_db<'p>( + db: Option<&'p DatabaseHandle<'p>>, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Result { - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - if values.is_null() { - return Err(String::from("key-value list is null")); - } - + let db = db.ok_or("db should be non-null")?; + let values = values.ok_or("key-value slice is null")?; // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; let batch = convert_to_batch(key_value_ref); @@ -432,10 +427,10 @@ fn propose_on_db( /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_propose_on_proposal( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, proposal_id: ProposalId, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. @@ -445,16 +440,13 @@ pub unsafe extern "C" fn fwd_propose_on_proposal( /// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API #[doc(hidden)] fn propose_on_proposal( - db: *const DatabaseHandle, + db: Option<&DatabaseHandle<'_>>, proposal_id: ProposalId, nkeys: usize, - values: *const KeyValue, + values: Option<&KeyValue>, ) -> Result { - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - if values.is_null() { - return Err(String::from("key-value list is null")); - } - + let db = db.ok_or("db should be non-null")?; + let values = values.ok_or("key-value slice is null")?; // Create a batch of operations to perform. let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; let batch = convert_to_batch(key_value_ref); @@ -465,9 +457,7 @@ fn propose_on_proposal( .proposals .write() .expect("failed to acquire write lock on proposals"); - let proposal = guard - .get(&proposal_id) - .ok_or_else(|| String::from("proposal not found"))?; + let proposal = guard.get(&proposal_id).ok_or("proposal not found")?; let new_proposal = proposal.propose_sync(batch).map_err(|e| e.to_string())?; drop(guard); // Drop the read lock before we get the write lock. @@ -505,20 +495,20 @@ fn propose_on_proposal( /// The caller must ensure that `db` is a valid pointer returned by `open_db` /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_commit(db: *const DatabaseHandle, proposal_id: u32) -> Value { +pub unsafe extern "C" fn fwd_commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Value { commit(db, proposal_id).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_commit` to remove error handling from the C API #[doc(hidden)] -fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; +fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), String> { + let db = db.ok_or("db should be non-null")?; let proposal = db .proposals .write() .map_err(|_| "proposal lock is poisoned")? .remove(&proposal_id) - .ok_or_else(|| String::from("proposal not found"))?; + .ok_or("proposal not found")?; proposal.commit_sync().map_err(|e| e.to_string()) } @@ -536,21 +526,22 @@ fn commit(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { /// The caller must ensure that `db` is a valid pointer returned by `open_db` /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Value { +pub unsafe extern "C" fn fwd_drop_proposal( + db: Option<&DatabaseHandle<'_>>, + proposal_id: u32, +) -> Value { drop_proposal(db, proposal_id).map_or_else(Into::into, Into::into) } /// Internal call for `fwd_drop_proposal` to remove error handling from the C API #[doc(hidden)] -fn drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), String> { - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; +fn drop_proposal(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), String> { + let db = db.ok_or("db should be non-null")?; let mut proposals = db .proposals .write() .map_err(|_| "proposal lock is poisoned")?; - proposals - .remove(&proposal_id) - .ok_or_else(|| String::from("proposal not found"))?; + proposals.remove(&proposal_id).ok_or("proposal not found")?; Ok(()) } @@ -573,26 +564,15 @@ fn drop_proposal(db: *const DatabaseHandle, proposal_id: u32) -> Result<(), Stri /// The caller must ensure that `db` is a valid pointer returned by `open_db` /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_root_hash(db: *const DatabaseHandle) -> Value { - // Check db is valid. +pub unsafe extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle<'_>>) -> Value { root_hash(db).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. /// Internal call for `fwd_root_hash` to remove error handling from the C API #[doc(hidden)] -fn root_hash(db: *const DatabaseHandle) -> Result { - // Check db is valid. - let db = unsafe { db.as_ref() }.ok_or_else(|| String::from("db should be non-null"))?; - - // Get the root hash of the database. - hash(db) -} - -/// This function is not exposed to the C API. -/// It returns the current hash of an already-fetched database handle -#[doc(hidden)] -fn hash(db: &Db) -> Result { +fn root_hash(db: Option<&DatabaseHandle<'_>>) -> Result { + let db = db.ok_or("db should be non-null")?; db.root_hash_sync() .map_err(|e| e.to_string())? .map(|root| Value::from(root.as_slice())) @@ -612,31 +592,22 @@ fn hash(db: &Db) -> Result { /// The data stored in this struct (if `data` is not null) must be manually freed /// by the caller using `fwd_free_value`. /// -#[derive(Debug)] +#[derive(Debug, Default)] #[repr(C)] pub struct Value { pub len: usize, - pub data: *const u8, + pub data: Option>, } impl Display for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.len, self.data.is_null()) { - (0, true) => write!(f, "[not found]"), - (0, false) => write!(f, "[error] {}", unsafe { - CStr::from_ptr(self.data.cast::()).to_string_lossy() + match (self.len, self.data) { + (0, None) => write!(f, "[not found]"), + (0, Some(data)) => write!(f, "[error] {}", unsafe { + CStr::from_ptr(data.as_ptr() as *const c_char).to_string_lossy() }), - (len, true) => write!(f, "[id] {len}"), - (_, false) => write!(f, "[data] {:?}", self.as_slice()), - } - } -} - -impl Default for Value { - fn default() -> Self { - Self { - len: 0, - data: std::ptr::null(), + (len, None) => write!(f, "[id] {len}"), + (_, Some(_)) => write!(f, "[data] {:?}", self.as_slice()), } } } @@ -644,10 +615,11 @@ impl Default for Value { impl Value { #[must_use] pub const fn as_slice(&self) -> &[u8] { - if self.data.is_null() { - &[] + if let Some(data) = self.data { + // SAFETY: We assume that the data is valid and the length is correct. + unsafe { std::slice::from_raw_parts(data.as_ptr(), self.len) } } else { - unsafe { std::slice::from_raw_parts(self.data, self.len) } + &[] } } } @@ -662,7 +634,8 @@ impl From<&[u8]> for Value { impl From> for Value { fn from(data: Box<[u8]>) -> Self { let len = data.len(); - let data = Box::leak(data).as_ptr(); + let leaked_ptr = Box::leak(data).as_mut_ptr(); + let data = std::ptr::NonNull::new(leaked_ptr); Value { len, data } } } @@ -675,7 +648,7 @@ impl From for Value { let cstr = CString::new(s).unwrap_or_default().into_raw(); Value { len: 0, - data: cstr.cast::(), + data: std::ptr::NonNull::new(cstr.cast::()), } } } @@ -689,7 +662,7 @@ impl From for Value { assert_ne!(v, 0); Self { len: v as usize, - data: std::ptr::null(), + data: None, } } } @@ -714,28 +687,22 @@ impl From<()> for Value { /// # Panics /// /// This function panics if `value` is `null`. +/// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_value(value: *mut Value) { - // Check value is valid. - let value = unsafe { value.as_ref() }.expect("value should be non-null"); - - if value.data.is_null() { - return; // nothing to free, but valid behavior. - } - - // We assume that if the length is 0, then the data is a null-terminated string. - if value.len > 0 { - let recreated_box = unsafe { - Box::from_raw(std::slice::from_raw_parts_mut( - value.data.cast_mut(), - value.len, - )) - }; - drop(recreated_box); - } else { - let raw_str = value.data.cast_mut().cast::(); - let cstr = unsafe { CString::from_raw(raw_str) }; - drop(cstr); +pub unsafe extern "C" fn fwd_free_value(value: Option<&mut Value>) { + let value = value.expect("value should be non-null"); + if let Some(data) = value.data { + let data_ptr = data.as_ptr(); + // We assume that if the length is 0, then the data is a null-terminated string. + if value.len > 0 { + let recreated_box = + unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data_ptr, value.len)) }; + drop(recreated_box); + } else { + let raw_str = data_ptr.cast::(); + let cstr = unsafe { CString::from_raw(raw_str) }; + drop(cstr); + } } } @@ -743,22 +710,22 @@ pub unsafe extern "C" fn fwd_free_value(value: *mut Value) { #[derive(Debug)] #[repr(C)] pub struct DatabaseCreationResult { - pub db: *const DatabaseHandle<'static>, - pub error_str: *mut u8, + pub db: Option>>, + pub error_str: Option>, } impl From> for DatabaseCreationResult { fn from(result: Result) -> Self { match result { Ok(db) => DatabaseCreationResult { - db: Box::into_raw(Box::new(db.into())), - error_str: std::ptr::null_mut(), + db: Some(Box::new(db.into())), + error_str: None, }, Err(error_msg) => { let error_cstring = CString::new(error_msg).unwrap_or_default().into_raw(); DatabaseCreationResult { - db: std::ptr::null(), - error_str: error_cstring.cast::(), + db: None, + error_str: std::ptr::NonNull::new(error_cstring.cast::()), } } } @@ -780,14 +747,15 @@ impl From> for DatabaseCreationResult { /// # Panics /// /// This function panics if `result` is `null`. +/// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_database_error_result(result: *mut DatabaseCreationResult) { - // Check result is valid. - let result = unsafe { result.as_ref() }.expect("result should be non-null"); - +pub unsafe extern "C" fn fwd_free_database_error_result( + result: Option<&mut DatabaseCreationResult>, +) { + let result = result.expect("result should be non-null"); // Free the error string if it exists - if !result.error_str.is_null() { - let raw_str = result.error_str.cast::(); + if let Some(nonnull) = result.error_str { + let raw_str = nonnull.cast::().as_ptr(); let cstr = unsafe { CString::from_raw(raw_str) }; drop(cstr); } @@ -936,7 +904,7 @@ mod tests { let cstr = CString::new("test").unwrap(); let value = Value { len: 0, - data: cstr.as_ptr().cast::(), + data: std::ptr::NonNull::new(cstr.as_ptr().cast::().cast_mut()), }; assert_eq!(format!("{value}"), "[error] test"); } @@ -945,17 +913,16 @@ mod tests { fn test_value_display_with_data() { let value = Value { len: 4, - data: Box::leak(b"test".to_vec().into_boxed_slice()).as_ptr(), + data: std::ptr::NonNull::new( + Box::leak(b"test".to_vec().into_boxed_slice()).as_mut_ptr(), + ), }; assert_eq!(format!("{value}"), "[data] [116, 101, 115, 116]"); } #[test] fn test_value_display_with_id() { - let value = Value { - len: 4, - data: std::ptr::null(), - }; + let value = Value { len: 4, data: None }; assert_eq!(format!("{value}"), "[id] 4"); } } From a7403b7dfd5ebc38a2bf3a17542aa0ef1634270e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 26 Jun 2025 10:27:35 -0400 Subject: [PATCH 0782/1053] chore: Release v0.0.7 (#997) --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ benchmark/Cargo.toml | 4 ++-- ffi/Cargo.toml | 4 ++-- firewood-macros/Cargo.toml | 2 +- firewood/Cargo.toml | 10 +++++----- fwdctl/Cargo.toml | 4 ++-- grpc-testtool/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- triehash/Cargo.toml | 2 +- 9 files changed, 46 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a3f1ead374..879bc88f06c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ All notable changes to this project will be documented in this file. +## [0.0.7] - 2025-06-26 + +### 🚀 Features + +- Add methods to fetch views from any hash (#993) + +### 🐛 Bug Fixes + +- *(ci)* Include submodule name in ffi tag (#991) + +### ⚡ Performance + +- *(metrics)* Add some metrics around propose and commit times (#989) + +### 🎨 Styling + +- Use cbindgen to convert to pointers (#969) + +### 🧪 Testing + +- Check support for empty proposals (#988) + +### ⚙️ Miscellaneous Tasks + +- Simplify + cleanup generate\_cgo script (#979) +- Update Cargo.toml add repository field (#987) +- *(fuzz)* Add step to upload fuzz testdata on failure (#990) +- Add special case for non semver tags to attach static libs (#992) +- Remove requirement for conventional commits (#994) + ## [0.0.6] - 2025-06-21 ### 🚀 Features @@ -48,6 +78,7 @@ All notable changes to this project will be documented in this file. - *(attach-static-libs)* Add pre build command to set MACOSX\_DEPLOYMENT\_TARGET for static libs build (#973) - Use new firewood-go-* FFI repo naming (#975) - Upgrade metrics packages (#982) +- Release v0.0.6 (#985) ## [0.0.5] - 2025-06-05 diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 8695196e1de0..76b9a090fd3f 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-benchmark" -version = "0.0.6" +version = "0.0.7" edition = "2024" rust-version = "1.85.0" authors = [ @@ -17,7 +17,7 @@ name = "benchmark" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.6", path = "../firewood" } +firewood = { version = "0.0.7", path = "../firewood" } hex = "0.4.3" clap = { version = "4.5.0", features = ['derive', 'string'] } sha2 = "0.10.8" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 3a07d56b8fab..37c5775d2615 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-ffi" -version = "0.0.6" +version = "0.0.7" edition = "2024" rust-version = "1.85.0" authors = [ @@ -20,7 +20,7 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.2" -firewood = { version = "0.0.6", path = "../firewood" } +firewood = { version = "0.0.7", path = "../firewood" } metrics = "0.24.2" metrics-util = "0.20.0" chrono = "0.4.39" diff --git a/firewood-macros/Cargo.toml b/firewood-macros/Cargo.toml index a694a7a36960..6beb32f4f48c 100644 --- a/firewood-macros/Cargo.toml +++ b/firewood-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-macros" -version = "0.0.6" +version = "0.0.7" edition = "2024" rust-version = "1.85" authors = [ diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 3b83a4343452..e2d01635ae65 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood" -version = "0.0.6" +version = "0.0.7" edition = "2024" authors = [ "Angel Leon ", @@ -37,7 +37,7 @@ integer-encoding = "4.0.0" smallvec = "1.6.1" fastrace = { version = "0.7.4" } coarsetime = "0.1.36" -firewood-macros = { version = "0.0.6", path = "../firewood-macros" } +firewood-macros = { version = "0.0.7", path = "../firewood-macros" } [features] default = [] @@ -48,7 +48,7 @@ branch_factor_256 = [ "firewood-storage/branch_factor_256" ] ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] -firewood-triehash = { version = "0.0.6", path = "../triehash" } +firewood-triehash = { version = "0.0.7", path = "../triehash" } criterion = { version = "0.6.0", features = ["async_tokio"] } rand = "0.9.0" rand_distr = "0.5.0" @@ -68,10 +68,10 @@ name = "hashops" harness = false [target.'cfg(target_os = "linux")'.dependencies] -firewood-storage = { version = "0.0.6", path = "../storage", features = ["io-uring"] } +firewood-storage = { version = "0.0.7", path = "../storage", features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] -firewood-storage = { version = "0.0.6", path = "../storage" } +firewood-storage = { version = "0.0.7", path = "../storage" } [lints] workspace = true diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index b9bf853c036b..e7588d2cd929 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-fwdctl" -version = "0.0.6" +version = "0.0.7" edition = "2024" rust-version = "1.85.0" authors = [ @@ -23,7 +23,7 @@ name = "fwdctl" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.6", path = "../firewood" } +firewood = { version = "0.0.7", path = "../firewood" } clap = { version = "4.5.0", features = ["cargo", "derive"] } env_logger = "0.11.2" log = "0.4.20" diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index d9e0601a1899..46df3ef0bd76 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,7 +17,7 @@ test = false bench = false [dependencies] -firewood = { version = "0.0.6", path = "../firewood" } +firewood = { version = "0.0.7", path = "../firewood" } prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.13.0", features = ["tls-ring"] } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index bae61e0defdc..43e8d45c30e3 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-storage" -version = "0.0.6" +version = "0.0.7" edition = "2024" rust-version = "1.85.0" authors = [ diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index be69f5671f71..42e3a7c733cd 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firewood-triehash" -version = "0.0.6" +version = "0.0.7" authors = ["Parity Technologies ", "Ron Kuris "] description = "In-memory patricia trie operations" repository = "https://github.com/paritytech/parity-common" From dbc2eac1e48faa89a2cfc0fe0ba26b6e2e3c61a0 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:24:17 -0400 Subject: [PATCH 0783/1053] fix: Unnecessary quotes in publish action (#996) This is a syntax error --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 9825fd363afe..bec3579a526a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -9,7 +9,7 @@ jobs: publish-firewood-crate: name: firewood-lib runs-on: ubuntu-latest - if: "startsWith(github.event.release.tag_name, 'v')" + if: startsWith(github.event.release.tag_name, 'v') steps: - uses: actions/checkout@v1 - uses: dtolnay/rust-toolchain@stable From 149e8ecce9ba304354c1fb62aae19411106fcf69 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:36:58 -0400 Subject: [PATCH 0784/1053] feat(checker): Firewood checker framework (#936) Creates a basic checker that traverses the trie and report the first error it finds. Signed-off-by: dependabot[bot] Co-authored-by: aaronbuchwald Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ron Kuris Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> --- firewood/src/merkle.rs | 2 +- firewood/src/proof.rs | 2 +- firewood/src/v2/api.rs | 6 +- fwdctl/Cargo.toml | 3 + fwdctl/src/check.rs | 41 ++ fwdctl/src/main.rs | 4 + fwdctl/tests/cli.rs | 104 +++-- storage/Cargo.toml | 2 + storage/src/checker.rs | 143 +++++++ storage/src/hashednode.rs | 2 +- storage/src/lib.rs | 47 +++ storage/src/linear/filebacked.rs | 10 +- storage/src/node/branch.rs | 17 +- storage/src/nodestore.rs | 56 ++- storage/src/range_set.rs | 659 +++++++++++++++++++++++++++++++ 15 files changed, 1054 insertions(+), 44 deletions(-) create mode 100644 fwdctl/src/check.rs create mode 100644 storage/src/checker.rs create mode 100644 storage/src/range_set.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 193e08becb3d..e8a675dbdca2 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -201,7 +201,7 @@ impl Merkle { if let Some(branch) = root.as_branch() { // TODO danlaine: can we avoid indexing? #[expect(clippy::indexing_slicing)] - for (i, hash) in branch.children_iter() { + for (i, hash) in branch.children_hashes() { child_hashes[i] = Some(hash.clone()); } } diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 3bff4898ec98..7bb0ddd2b2ef 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -122,7 +122,7 @@ impl From for ProofNode { if let Some(branch) = item.node.as_branch() { // TODO danlaine: can we avoid indexing? #[expect(clippy::indexing_slicing)] - for (i, hash) in branch.children_iter() { + for (i, hash) in branch.children_hashes() { child_hashes[i] = Some(hash.clone()); } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 3850c22cc927..f5f2ead9f1ae 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -14,7 +14,7 @@ use crate::manager::RevisionManagerError; use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; use async_trait::async_trait; -use firewood_storage::{FileIoError, TrieHash}; +use firewood_storage::{CheckerError, FileIoError, TrieHash}; use futures::Stream; use std::fmt::Debug; use std::sync::Arc; @@ -159,6 +159,10 @@ pub enum Error { /// Revision not found #[error("revision not found")] RevisionNotFound, + + /// Checker error + #[error("checker error")] + CheckerError(#[from] CheckerError), } impl From for Error { diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index e7588d2cd929..769f7aacc40d 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -24,6 +24,7 @@ path = "src/main.rs" [dependencies] firewood = { version = "0.0.7", path = "../firewood" } +firewood-storage = { version = "0.0.7", path = "../storage" } clap = { version = "4.5.0", features = ["cargo", "derive"] } env_logger = "0.11.2" log = "0.4.20" @@ -31,12 +32,14 @@ tokio = { version = "1.36.0", features = ["full"] } futures-util = "0.3.30" hex = "0.4.3" csv = "1.3.1" +nonzero_ext = "0.3.0" [dev-dependencies] anyhow = "1.0.79" assert_cmd = "2.0.13" predicates = "3.1.0" serial_test = "3.0.0" +rand = "0.9.1" [lints] workspace = true diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs new file mode 100644 index 000000000000..e3648d212eb7 --- /dev/null +++ b/fwdctl/src/check.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::path::PathBuf; +use std::sync::Arc; + +use clap::Args; +use firewood::v2::api; +use firewood_storage::{CacheReadStrategy, FileBacked, NodeStore}; +use nonzero_ext::nonzero; + +// TODO: (optionally) add a fix option +#[derive(Args)] +pub struct Options { + /// The database path (if no path is provided, return an error). Defaults to firewood. + #[arg( + long, + required = false, + value_name = "DB_NAME", + default_value_t = String::from("firewood"), + help = "Name of the database" + )] + pub db: String, +} + +#[allow(clippy::unused_async)] +pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { + let db_path = PathBuf::from(&opts.db); + let node_cache_size = nonzero!(1usize); + let free_list_cache_size = nonzero!(1usize); + + let storage = Arc::new(FileBacked::new( + db_path, + node_cache_size, + free_list_cache_size, + false, + CacheReadStrategy::WritesOnly, // we scan the database once - no need to cache anything + )?); + + NodeStore::open(storage)?.check().map_err(Into::into) +} diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 890e36b589fa..7ad10b336c00 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -6,6 +6,7 @@ use clap::{Parser, Subcommand}; use firewood::v2::api; +pub mod check; pub mod create; pub mod delete; pub mod dump; @@ -49,6 +50,8 @@ enum Commands { Dump(dump::Options), /// Produce a dot file of the database Graph(graph::Options), + /// Runs the checker on the database + Check(check::Options), } #[tokio::main] @@ -68,5 +71,6 @@ async fn main() -> Result<(), api::Error> { Commands::Root(opts) => root::run(opts).await, Commands::Dump(opts) => dump::run(opts).await, Commands::Graph(opts) => graph::run(opts).await, + Commands::Check(opts) => check::run(opts).await, } } diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 657b7452b7d0..595df47ce528 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -45,9 +45,7 @@ fn fwdctl_creates_database() -> Result<()> { .assert() .success(); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -71,9 +69,7 @@ fn fwdctl_insert_successful() -> Result<()> { .success() .stdout(predicate::str::contains("year")); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -106,9 +102,7 @@ fn fwdctl_get_successful() -> Result<()> { .success() .stdout(predicate::str::contains("2023")); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -140,9 +134,7 @@ fn fwdctl_delete_successful() -> Result<()> { .success() .stdout(predicate::str::contains("key year deleted successfully")); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -173,9 +165,7 @@ fn fwdctl_root_hash() -> Result<()> { .success() .stdout(predicate::str::is_empty().not()); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -205,9 +195,7 @@ fn fwdctl_dump() -> Result<()> { .success() .stdout(predicate::str::contains("2023")); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -313,9 +301,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { "Next key is c, resume with \"--start-key=c\"", )); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -388,9 +374,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { ); fs::remove_file("dump.json").expect("Should remove dump.json file"); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -454,9 +438,7 @@ fn fwdctl_dump_with_file_name() -> Result<()> { assert_eq!(contents, "{\n \"a\": \"1\"\n}\n"); fs::remove_file("test.json").expect("Should remove test.json file"); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; - - Ok(()) + fwdctl_delete_db() } #[test] @@ -534,9 +516,73 @@ fn fwdctl_dump_with_hex() -> Result<()> { .stdout(predicate::str::contains("--start-key=c")) .stdout(predicate::str::contains("--start-key-hex=63")); - fwdctl_delete_db().map_err(|e| anyhow!(e))?; + fwdctl_delete_db() +} - Ok(()) +#[test] +#[serial] +fn fwdctl_check_empty_db() -> Result<()> { + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + Command::cargo_bin(PRG)? + .arg("check") + .arg("--db") + .arg(tmpdb::path()) + .assert() + .success(); + + fwdctl_delete_db() +} + +#[test] +#[serial] +fn fwdctl_check_db_with_data() -> Result<()> { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng, rng}; + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let mut rng = StdRng::seed_from_u64(seed); + + Command::cargo_bin(PRG)? + .arg("create") + .arg(tmpdb::path()) + .assert() + .success(); + + // TODO: bulk loading data instead of inserting one by one + for _ in 0..4 { + let key = format!("key_{}", rng.random::()); + let value = format!("value_{}", rng.random::()); + Command::cargo_bin(PRG)? + .arg("insert") + .args([key]) + .args([value]) + .args(["--db"]) + .args([tmpdb::path()]) + .assert() + .success(); + } + + Command::cargo_bin(PRG)? + .arg("check") + .arg("--db") + .arg(tmpdb::path()) + .assert() + .success(); + + fwdctl_delete_db() } // A module to create a temporary database name for use in diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 43e8d45c30e3..015156f79701 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -38,6 +38,7 @@ coarsetime = "0.1.35" rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } bytes = { version = "1.10.1", optional = true } +thiserror = "2.0.3" semver = "1.0.26" [dev-dependencies] @@ -46,6 +47,7 @@ test-case = "3.3.1" criterion = { version = "0.6.0", features = ["async_tokio", "html_reports"] } pprof = { version = "0.15.0", features = ["flamegraph"] } tempfile = "3.12.0" +nonzero_ext = "0.3.0" [features] logger = ["log"] diff --git a/storage/src/checker.rs b/storage/src/checker.rs new file mode 100644 index 000000000000..3bda5849853f --- /dev/null +++ b/storage/src/checker.rs @@ -0,0 +1,143 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::range_set::LinearAddressRangeSet; +use crate::{ + CheckerError, Committed, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, + WritableStorage, +}; + +/// [`NodeStore`] checker +// TODO: S needs to be writeable if we ask checker to fix the issues +impl NodeStore { + /// Go through the filebacked storage and check for any inconsistencies. It proceeds in the following steps: + /// 1. Check the header + /// 2. traverse the trie and check the nodes + /// 3. check the free list + /// 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? + /// # Errors + /// Returns a [`CheckerError`] if the database is inconsistent. + // TODO: report all errors, not just the first one + // TODO: add merkle hash checks as well + pub fn check(&self) -> Result<(), CheckerError> { + // 1. Check the header + let db_size = self.size(); + let file_size = self.get_physical_size()?; + if db_size < file_size { + return Err(CheckerError::InvalidDBSize { + db_size, + description: format!( + "db size should not be smaller than the file size ({file_size})" + ), + }); + } + + let mut visited = LinearAddressRangeSet::new(db_size)?; + + // 2. traverse the trie and check the nodes + if let Some(root_address) = self.root_address() { + // the database is not empty, traverse the trie + self.traverse_trie(root_address, &mut visited)?; + } + + // 3. check the free list - this can happen in parallel with the trie traversal + + // 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? + let _ = visited.complement(); // TODO + + Ok(()) + } + + /// Recursively traverse the trie from the given root address. + fn traverse_trie( + &self, + subtree_root_address: LinearAddress, + visited: &mut LinearAddressRangeSet, + ) -> Result<(), CheckerError> { + let (_, area_size) = self.area_index_and_size(subtree_root_address)?; + visited.insert_area(subtree_root_address, area_size)?; + + if let Node::Branch(branch) = self.read_node(subtree_root_address)?.as_ref() { + // this is an internal node, traverse the children + for (_, address) in branch.children_addresses() { + self.traverse_trie(*address, visited)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + #![expect(clippy::unwrap_used)] + + use super::*; + use crate::linear::memory::MemStore; + use crate::nodestore::NodeStoreHeader; + use crate::nodestore::nodestore_test_utils::{write_header, write_new_node}; + use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; + + #[test] + // This test creates a simple trie and checks that the checker traverses it correctly. + // We use primitive calls here to do a low-level check. + // TODO: add a high-level test in the firewood crate + fn test_checker_traverse_correct_trie() { + let memstore = MemStore::new(vec![]); + let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + // set up a basic trie: + // ------------------------- + // | | X | X | ... | Root node + // ------------------------- + // | + // V + // ------------------------- + // | X | | X | ... | Branch node + // ------------------------- + // | + // V + // ------------------------- + // | [0,1] -> [3,4,5] | Leaf node + // ------------------------- + let mut high_watermark = NodeStoreHeader::SIZE; + let leaf = Node::Leaf(LeafNode { + partial_path: Path::from([0, 1]), + value: Box::new([3, 4, 5]), + }); + let leaf_addr = LinearAddress::new(high_watermark).unwrap(); + let leaf_area = write_new_node(&nodestore, &leaf, high_watermark); + high_watermark += leaf_area; + + let mut branch_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + branch_children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::default())); + let branch = Node::Branch(Box::new(BranchNode { + partial_path: Path::from([0]), + value: None, + children: branch_children, + })); + let branch_addr = LinearAddress::new(high_watermark).unwrap(); + let branch_area = write_new_node(&nodestore, &branch, high_watermark); + high_watermark += branch_area; + + let mut root_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + root_children[0] = Some(Child::AddressWithHash(branch_addr, HashType::default())); + let root = Node::Branch(Box::new(BranchNode { + partial_path: Path::from([]), + value: None, + children: root_children, + })); + let root_addr = LinearAddress::new(high_watermark).unwrap(); + let root_area = write_new_node(&nodestore, &root, high_watermark); + high_watermark += root_area; + + // write the header + write_header(&nodestore, root_addr, high_watermark); + + // verify that all of the space is accounted for - since there is no free area + let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + nodestore.traverse_trie(root_addr, &mut visited).unwrap(); + let complement = visited.complement(); + assert_eq!(complement.into_iter().collect::>(), vec![]); + } +} diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index fa1cd36334e9..779a9d652d63 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -151,7 +151,7 @@ impl HashableNode for BranchNode { } fn children_iter(&self) -> impl Iterator + Clone { - self.children_iter() + self.children_hashes() } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 6092d6536e60..d8f9d77b9be9 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -12,11 +12,16 @@ //! //! A [`NodeStore`] is backed by a [`ReadableStorage`] which is persisted storage. +use std::ops::Range; +use thiserror::Error; + +mod checker; mod hashednode; mod hashers; mod linear; mod node; mod nodestore; +mod range_set; mod trie_hash; /// Logger module for handling logging functionality @@ -79,3 +84,45 @@ pub fn empty_trie_hash() -> TrieHash { .try_into() .expect("empty trie hash is 32 bytes") } + +/// Errors returned by the checker +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum CheckerError { + /// The file size is not valid + #[error("Invalid DB size ({db_size}): {description}")] + InvalidDBSize { + /// The size of the db + db_size: u64, + /// The description of the error + description: String, + }, + + /// The address is out of bounds + #[error("stored area at {start} with size {size} is out of bounds ({bounds:?})")] + AreaOutOfBounds { + /// Start of the `StoredArea` + start: LinearAddress, + /// Size of the `StoredArea` + size: u64, + /// Valid range of addresses + bounds: Range, + }, + + /// Stored areas intersect + #[error( + "stored area at {start} with size {size} intersects with other stored areas: {intersection:?}" + )] + AreaIntersects { + /// Start of the `StoredArea` + start: LinearAddress, + /// Size of the `StoredArea` + size: u64, + /// The intersection + intersection: Vec>, + }, + + /// IO error + #[error("IO error")] + IO(#[from] FileIoError), +} diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 9899b9bf7280..402296d62118 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -304,6 +304,7 @@ mod test { #![expect(clippy::unwrap_used)] use super::*; + use nonzero_ext::nonzero; use std::io::Write; use tempfile::NamedTempFile; @@ -318,12 +319,13 @@ mod test { // read the whole thing in let fb = FileBacked::new( path, - NonZero::new(10).unwrap(), - NonZero::new(10).unwrap(), + nonzero!(10usize), + nonzero!(10usize), false, CacheReadStrategy::WritesOnly, ) .unwrap(); + let mut reader = fb.stream_from(0).unwrap(); let mut buf: String = String::new(); assert_eq!(reader.read_to_string(&mut buf).unwrap(), 11); @@ -358,8 +360,8 @@ mod test { let fb = FileBacked::new( path, - NonZero::new(10).unwrap(), - NonZero::new(10).unwrap(), + nonzero!(10usize), + nonzero!(10usize), false, CacheReadStrategy::WritesOnly, ) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 94c7d9bd9e6a..1a91052a26e3 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -313,17 +313,28 @@ impl BranchNode { *child = new_child; } - /// Returns (index, hash) for each child that has a hash set. - pub fn children_iter(&self) -> impl Iterator + Clone { + // Helper to iterate over only valid children + fn children_iter(&self) -> impl Iterator + Clone { self.children .iter() .enumerate() .filter_map(|(i, child)| match child { None => None, Some(Child::Node(_)) => unreachable!("TODO make unreachable"), - Some(Child::AddressWithHash(_, hash)) => Some((i, hash)), + Some(Child::AddressWithHash(address, hash)) => Some((i, (address, hash))), }) } + + /// Returns (index, hash) for each child that has a hash set. + pub fn children_hashes(&self) -> impl Iterator + Clone { + self.children_iter().map(|(idx, (_, hash))| (idx, hash)) + } + + /// Returns (index, address) for each child that has a hash set. + pub fn children_addresses(&self) -> impl Iterator + Clone { + self.children_iter() + .map(|(idx, (address, _))| (idx, address)) + } } impl From<&LeafNode> for BranchNode { diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 3a2200794623..2de4da525412 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -317,7 +317,8 @@ impl NodeStore { } /// Get the size of an area index (used by the checker) - pub const fn size_from_area_index(&self, index: AreaIndex) -> u64 { + #[must_use] + pub const fn size_from_area_index(index: AreaIndex) -> u64 { AREA_SIZES[index as usize] } } @@ -801,7 +802,7 @@ impl NodeStoreHeader { /// The first SIZE bytes of the `ReadableStorage` are reserved for the /// [`NodeStoreHeader`]. /// We also want it aligned to a disk block - const SIZE: u64 = 2048; + pub(crate) const SIZE: u64 = 2048; /// Number of extra bytes to write on the first creation of the `NodeStoreHeader` /// (zero-padded) @@ -1554,6 +1555,52 @@ impl NodeStore { } } +// Helper functions for the checker +impl NodeStore { + pub(crate) const fn size(&self) -> u64 { + self.header.size + } + + pub(crate) fn get_physical_size(&self) -> Result { + self.storage.size() + } +} + +#[cfg(test)] +pub(crate) mod nodestore_test_utils { + use super::*; + + // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. + #[allow(clippy::cast_possible_truncation)] + pub(crate) fn write_new_node( + nodestore: &NodeStore, + node: &Node, + offset: u64, + ) -> u64 { + let node_length = NodeStore::, FileBacked>::stored_len(node); + let area_size_index = area_size_to_index(node_length).unwrap(); + let mut stored_area_bytes = Vec::new(); + node.as_bytes(area_size_index, &mut stored_area_bytes); + nodestore + .storage + .write(offset, stored_area_bytes.as_slice()) + .unwrap(); + AREA_SIZES[area_size_index as usize] + } + + pub(crate) fn write_header( + nodestore: &NodeStore, + root_addr: LinearAddress, + size: u64, + ) { + let mut header = NodeStoreHeader::new(); + header.size = size; + header.root_address = Some(root_addr); + let header_bytes = bytemuck::bytes_of(&header); + nodestore.storage.write(0, header_bytes).unwrap(); + } +} + #[cfg(test)] #[expect(clippy::unwrap_used)] mod tests { @@ -1562,6 +1609,7 @@ mod tests { use crate::linear::memory::MemStore; use crate::{BranchNode, LeafNode}; use arc_swap::access::DynGuard; + use nonzero_ext::nonzero; use test_case::test_case; use super::*; @@ -1661,7 +1709,7 @@ mod tests { value: Some(vec![9, 10, 11].into_boxed_slice()), children: from_fn(|i| { if i == 15 { - Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) } else { None } @@ -1671,7 +1719,7 @@ mod tests { partial_path: Path::from([6, 7, 8]), value: Some(vec![9, 10, 11].into_boxed_slice()), children: from_fn(|_| - Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) ), }; "branch node with all child")] #[test_case( diff --git a/storage/src/range_set.rs b/storage/src/range_set.rs new file mode 100644 index 000000000000..b02116bb4a70 --- /dev/null +++ b/storage/src/range_set.rs @@ -0,0 +1,659 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![warn(clippy::pedantic)] + +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::ops::Range; + +use crate::nodestore::NodeStoreHeader; +use crate::{CheckerError, LinearAddress}; + +#[derive(Debug)] +// BTreeMap: range end --> range start +// To check if a value is in the range set, we will find the range with the smallest end that is greater than or equal to the given value +pub struct RangeSet(BTreeMap); + +struct CoalescingRanges<'a, T> { + prev_adjacent_range: Option>, + intersecting_ranges: Vec>, + next_adjacent_range: Option>, +} + +#[allow(dead_code)] +impl RangeSet { + pub const fn new() -> Self { + Self(BTreeMap::new()) + } + + // returns disjoint ranges that will coalesce with the given range in ascending order + // We look at all ranges whose end is greater than or equal to the given range's start + // For each range, there are 5 cases: + // Case 1: The range is before the given range + // Range x in RangeSet: |----| + // Given Range: |--------| + // Result: this will not happen since the end of x is less than the start of the given range + // Case 2: The end of the range is equal to the start of the given range + // Range x in RangeSet: |--------| + // Given Range: |--------| + // Result: prev_adjacent_range = Some(x) + // Case 3: The range intersects with the given range + // Range x in RangeSet: |----------| + // Given Range: |-----------| + // Result: intersecting_ranges = [x, ...] + // or: + // Range x in RangeSet: |----------------| + // Given Range: |--------| + // or: + // Range x in RangeSet: |--------| + // Given Range: |-----------------| + // or: + // Range x in RangeSet: |------------| + // Given Range: |--------| + // Result: intersecting_ranges = [.., x, ..] + // Case 4: The start of the range is equal to the end of the given range + // Range x in RangeSet: |--------| + // Given Range: |--------| + // Result: next_adjacent_range = Some(x), and we are done iterating through the ranges + // Case 5: The range is after the given range + // Range x in RangeSet: |--------| + // Given Range: |--------| + // Result: We are done iterating through the ranges + fn get_coalescing_ranges(&self, range: &Range) -> CoalescingRanges<'_, T> { + let mut prev_adjacent_range = None; + let mut intersecting_ranges = Vec::new(); + let mut next_adjacent_range = None; + + let next_items = self.0.range(range.start.clone()..); + // all ranges will have next_range_end >= range.start and next_range_end >= next_range_start + for (next_range_end, next_range_start) in next_items { + if next_range_end == &range.start { + // Case 2: The end of the range is equal to the start of the given range - this can only happen to the first item + prev_adjacent_range = Some(next_range_start..next_range_end); + } else if next_range_start < &range.end { + // Case 3: The range intersects with the given range + intersecting_ranges.push(next_range_start..next_range_end); + } else if next_range_start == &range.end { + // Case 4: The start of the range is equal to the end of the given range + next_adjacent_range = Some(next_range_start..next_range_end); + break; + } else { + // Case 5: the range is after the given range + break; + } + } + + CoalescingRanges { + prev_adjacent_range, + intersecting_ranges, + next_adjacent_range, + } + } + + // Try inserting the range + // if allow_intersecting is false and the range intersects with existing ranges, return error with the intersection + fn insert_range_helper( + &mut self, + range: Range, + allow_intersecting: bool, + ) -> Result<(), Vec>> { + // if the range is empty, do nothing + if range.is_empty() { + return Ok(()); + } + + let CoalescingRanges { + prev_adjacent_range: prev_consecutive_range, + intersecting_ranges, + next_adjacent_range: next_consecutive_range, + } = self.get_coalescing_ranges(&range); + + // if the insert needs to be disjoint but we found intersecting ranges, return error with the intersection + if !allow_intersecting && !intersecting_ranges.is_empty() { + let intersections = intersecting_ranges + .into_iter() + .map(|intersecting_range| { + let start = + std::cmp::max(range.start.clone(), intersecting_range.start.clone()); + let end = std::cmp::min(range.end.clone(), intersecting_range.end.clone()); + start..end + }) + .collect(); + return Err(intersections); + } + + // find the new start and end after the coalescing + let coalesced_start = match (&prev_consecutive_range, intersecting_ranges.first()) { + (Some(prev_range), _) => prev_range.start.clone(), + (None, Some(first_intersecting_range)) => { + std::cmp::min(range.start, first_intersecting_range.start.clone()) + } + (None, None) => range.start, + }; + let coalesced_end = match (&next_consecutive_range, intersecting_ranges.last()) { + (Some(next_range), _) => next_range.end.clone(), + (None, Some(last_intersecting_range)) => { + std::cmp::max(range.end, last_intersecting_range.end.clone()) + } + (None, None) => range.end, + }; + + // remove the coalescing ranges + let remove_ranges_iter = prev_consecutive_range + .into_iter() + .chain(intersecting_ranges) + .chain(next_consecutive_range); + let remove_keys = remove_ranges_iter + .map(|range| range.end.clone()) + .collect::>(); + for key in remove_keys { + self.0.remove(&key); + } + + // insert the new range after coalescing + self.0.insert(coalesced_end, coalesced_start); + Ok(()) + } + + /// Insert the range into the range set. + pub fn insert_range(&mut self, range: Range) { + self.insert_range_helper(range, true) + .expect("insert range should always success if we allow intersecting area insert"); + } + + /// Insert the given range into the range set if the range does not intersect with existing ranges, otherwise return the error with the intersection + pub fn insert_disjoint_range(&mut self, range: Range) -> Result<(), Vec>> { + self.insert_range_helper(range, false) + } + + /// Returns the complement of the range set in the given range. + pub fn complement(&self, min: &T, max: &T) -> Self { + let mut complement_tree = BTreeMap::new(); + // first range will start from min + let mut start = min; + for (next_range_end, next_range_start) in &self.0 { + // insert the range from the previous end to the current start + if next_range_start > start { + if next_range_start >= max { + // we have reached the max - we are done + break; + } + complement_tree.insert(next_range_start.clone(), start.clone()); + } + start = std::cmp::max(start, next_range_end); // in case the entire range is smaller than min + } + + // insert the last range before the max + if start < max { + complement_tree.insert(max.clone(), start.clone()); + } + + Self(complement_tree) + } +} + +impl IntoIterator for RangeSet { + type Item = Range; + type IntoIter = + std::iter::Map< as IntoIterator>::IntoIter, fn((T, T)) -> Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter().map(|(end, start)| Range { start, end }) + } +} + +pub(super) struct LinearAddressRangeSet { + range_set: RangeSet, + max_addr: LinearAddress, +} + +impl LinearAddressRangeSet { + const NODE_STORE_ADDR_START: LinearAddress = LinearAddress::new(NodeStoreHeader::SIZE).unwrap(); + + pub(super) fn new(db_size: u64) -> Result { + if db_size < NodeStoreHeader::SIZE { + return Err(CheckerError::InvalidDBSize { + db_size, + description: format!( + "db size should not be smaller than the header size ({})", + NodeStoreHeader::SIZE + ), + }); + } + + let max_addr = + LinearAddress::new(db_size).expect("db size will be valid due to previous check"); + + Ok(Self { + range_set: RangeSet::new(), + max_addr, // STORAGE_AREA_START..U64::MAX + }) + } + + pub(super) fn insert_area( + &mut self, + addr: LinearAddress, + size: u64, + ) -> Result<(), CheckerError> { + let start = addr; + let end = start + .checked_add(size) + .ok_or(CheckerError::AreaOutOfBounds { + start, + size, + bounds: Self::NODE_STORE_ADDR_START..self.max_addr, + })?; // This can only happen due to overflow + if addr < Self::NODE_STORE_ADDR_START || end > self.max_addr { + return Err(CheckerError::AreaOutOfBounds { + start: addr, + size, + bounds: Self::NODE_STORE_ADDR_START..self.max_addr, + }); + } + + if let Err(intersection) = self.range_set.insert_disjoint_range(start..end) { + return Err(CheckerError::AreaIntersects { + start: addr, + size, + intersection, + }); + } + Ok(()) + } + + pub(super) fn complement(&self) -> Self { + let complement_set = self + .range_set + .complement(&Self::NODE_STORE_ADDR_START, &self.max_addr); + + Self { + range_set: complement_set, + max_addr: self.max_addr, + } + } +} + +impl IntoIterator for LinearAddressRangeSet { + type Item = Range; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.range_set.into_iter() + } +} + +#[cfg(test)] +mod test_range_set { + use super::*; + + #[test] + fn test_create() { + let range_set: RangeSet = RangeSet::new(); + assert_eq!(range_set.into_iter().collect::>(), vec![]); + } + + #[test] + fn test_insert_range() { + let mut range_set = RangeSet::new(); + range_set.insert_range(0..10); + range_set.insert_range(20..30); + assert_eq!( + range_set.into_iter().collect::>(), + vec![0..10, 20..30] + ); + } + + #[test] + #[allow(clippy::reversed_empty_ranges)] + fn test_insert_empty_range() { + let mut range_set = RangeSet::new(); + range_set.insert_range(0..0); + range_set.insert_range(10..10); + range_set.insert_range(20..10); + range_set.insert_range(30..0); + assert_eq!(range_set.into_iter().collect::>(), vec![]); + } + + #[test] + fn test_insert_range_coalescing() { + // coalesce ranges that are disjoint + let mut range_set = RangeSet::new(); + range_set.insert_range(0..10); + range_set.insert_range(20..30); + range_set.insert_range(10..20); + assert_eq!(range_set.into_iter().collect::>(), vec![0..30]); + + // coalesce ranges that are partially overlapping + let mut range_set = RangeSet::new(); + range_set.insert_range(0..10); + range_set.insert_range(20..30); + range_set.insert_range(5..25); + assert_eq!(range_set.into_iter().collect::>(), vec![0..30]); + + // coalesce multiple ranges + let mut range_set = RangeSet::new(); + range_set.insert_range(5..10); + range_set.insert_range(15..20); + range_set.insert_range(25..30); + range_set.insert_range(0..25); + assert_eq!(range_set.into_iter().collect::>(), vec![0..30]); + } + + #[test] + fn test_insert_disjoint_range() { + // insert disjoint ranges + let mut range_set = RangeSet::new(); + assert!(range_set.insert_disjoint_range(0..10).is_ok()); + assert!(range_set.insert_disjoint_range(20..30).is_ok()); + assert!(range_set.insert_disjoint_range(12..18).is_ok()); + assert_eq!( + range_set.into_iter().collect::>(), + vec![0..10, 12..18, 20..30] + ); + + // insert consecutive ranges + let mut range_set = RangeSet::new(); + assert!(range_set.insert_disjoint_range(0..10).is_ok()); + assert!(range_set.insert_disjoint_range(20..30).is_ok()); + assert!(range_set.insert_disjoint_range(10..20).is_ok()); + assert_eq!(range_set.into_iter().collect::>(), vec![0..30]); + + // insert intersecting ranges + let mut range_set = RangeSet::new(); + assert!(range_set.insert_disjoint_range(0..10).is_ok()); + assert!(range_set.insert_disjoint_range(20..30).is_ok()); + assert!(matches!( + range_set.insert_disjoint_range(5..25), + Err(intersections) if intersections == vec![5..10, 20..25] + )); + assert_eq!( + range_set.into_iter().collect::>(), + vec![0..10, 20..30] + ); + + // insert with completely overlapping range + let mut range_set = RangeSet::new(); + assert!(range_set.insert_disjoint_range(0..10).is_ok()); + assert!(range_set.insert_disjoint_range(20..30).is_ok()); + assert!(range_set.insert_disjoint_range(40..50).is_ok()); + assert!(matches!( + range_set.insert_disjoint_range(5..45), + Err(intersections) if intersections == vec![5..10, 20..30, 40..45] + )); + assert_eq!( + range_set.into_iter().collect::>(), + vec![0..10, 20..30, 40..50] + ); + } + + #[test] + fn test_complement_with_empty() { + let range_set = RangeSet::new(); + assert_eq!( + range_set + .complement(&0, &40) + .into_iter() + .collect::>(), + vec![0..40] + ); + } + + #[test] + fn test_complement_with_different_bounds() { + let mut range_set = RangeSet::new(); + range_set.insert_range(0..10); + range_set.insert_range(20..30); + range_set.insert_range(40..50); + + // all ranges within bound + assert_eq!( + range_set + .complement(&0, &60) + .into_iter() + .collect::>(), + vec![10..20, 30..40, 50..60] + ); + + // some ranges entirely out of bound + assert_eq!( + range_set + .complement(&15, &35) + .into_iter() + .collect::>(), + vec![15..20, 30..35] + ); + + // some ranges are partially out of bound + assert_eq!( + range_set + .complement(&5, &45) + .into_iter() + .collect::>(), + vec![10..20, 30..40] + ); + + // test all ranges out of bound + assert_eq!( + range_set + .complement(&12, &18) + .into_iter() + .collect::>(), + vec![12..18] + ); + + // test complement empty + assert_eq!( + range_set + .complement(&20, &30) + .into_iter() + .collect::>(), + vec![] + ); + + // test boundaries at range start and end + assert_eq!( + range_set + .complement(&0, &30) + .into_iter() + .collect::>(), + vec![10..20] + ); + + // test with equal bounds + assert_eq!( + range_set + .complement(&35, &35) + .into_iter() + .collect::>(), + vec![] + ); + + // test with reversed bounds + assert_eq!( + range_set + .complement(&30, &0) + .into_iter() + .collect::>(), + vec![] + ); + } + + #[test] + fn test_many_small_ranges() { + let mut range_set = RangeSet::new(); + // Insert many small ranges that will coalesce + for i in 0..1000 { + range_set.insert_range(i * 10..(i * 10 + 5)); + } + // Should result in 1000 separate single-element ranges + assert_eq!(range_set.into_iter().count(), 1000); + } + + #[test] + fn test_large_range_coalescing() { + let mut range_set = RangeSet::new(); + // Insert ranges that will eventually all coalesce into one + for i in 0..1000 { + range_set.insert_range(i * 10..(i * 10 + 5)); + } + // Fill the gaps + range_set.insert_range(0..10000); + assert_eq!(range_set.into_iter().collect::>(), vec![0..10000]); + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used)] +mod test_linear_address_range_set { + + use super::*; + + #[test] + fn test_empty() { + let visited = LinearAddressRangeSet::new(0x1000).unwrap(); + let visited_ranges = visited + .into_iter() + .map(|range| (range.start, range.end)) + .collect::>(); + assert_eq!(visited_ranges, vec![]); + } + + #[test] + fn test_insert_area() { + let start = 2048; + let size = 1024; + + let start_addr = LinearAddress::new(start).unwrap(); + let end_addr = LinearAddress::new(start + size).unwrap(); + + let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); + visited + .insert_area(start_addr, size) + .expect("the given area should be within bounds"); + + let visited_ranges = visited + .into_iter() + .map(|range| (range.start, range.end)) + .collect::>(); + assert_eq!(visited_ranges, vec![(start_addr, end_addr)]); + } + + #[test] + fn test_consecutive_areas_merge() { + let start1 = 2048; + let size1 = 1024; + let start2 = start1 + size1; + let size2 = 1024; + + let start1_addr = LinearAddress::new(start1).unwrap(); + let start2_addr = LinearAddress::new(start2).unwrap(); + let end2_addr = LinearAddress::new(start2 + size2).unwrap(); + + let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); + visited + .insert_area(start1_addr, size1) + .expect("the given area should be within bounds"); + + visited + .insert_area(start2_addr, size2) + .expect("the given area should be within bounds"); + + let visited_ranges = visited + .into_iter() + .map(|range| (range.start, range.end)) + .collect::>(); + assert_eq!(visited_ranges, vec![(start1_addr, end2_addr),]); + } + + #[test] + fn test_intersecting_areas_will_fail() { + let start1 = 2048; + let size1 = 1024; + let start2 = start1 + size1 - 1; + let size2 = 1024; + + let start1_addr = LinearAddress::new(start1).unwrap(); + let end1_addr = LinearAddress::new(start1 + size1).unwrap(); + let start2_addr = LinearAddress::new(start2).unwrap(); + + let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); + visited + .insert_area(start1_addr, size1) + .expect("the given area should be within bounds"); + + let error = visited + .insert_area(start2_addr, size2) + .expect_err("the given area should intersect with the first area"); + + assert!( + matches!(error, CheckerError::AreaIntersects { start, size, intersection } if start == start2_addr && size == size2 && intersection == vec![start2_addr..end1_addr]) + ); + + // try inserting in opposite order + let mut visited2 = LinearAddressRangeSet::new(0x1000).unwrap(); + visited2 + .insert_area(start2_addr, size2) + .expect("the given area should be within bounds"); + + let error = visited2 + .insert_area(start1_addr, size1) + .expect_err("the given area should intersect with the first area"); + + assert!( + matches!(error, CheckerError::AreaIntersects { start, size, intersection } if start == start1_addr && size == size1 && intersection == vec![start2_addr..end1_addr]) + ); + } + + #[test] + fn test_complement() { + let start1 = 3000; + let size1 = 1024; + let start2 = 4096; + let size2 = 1024; + let db_size = 0x2000; + + let db_begin = LinearAddressRangeSet::NODE_STORE_ADDR_START; + let start1_addr = LinearAddress::new(start1).unwrap(); + let end1_addr = LinearAddress::new(start1 + size1).unwrap(); + let start2_addr = LinearAddress::new(start2).unwrap(); + let end2_addr = LinearAddress::new(start2 + size2).unwrap(); + let db_end = LinearAddress::new(db_size).unwrap(); + + let mut visited = LinearAddressRangeSet::new(db_size).unwrap(); + visited.insert_area(start1_addr, size1).unwrap(); + visited.insert_area(start2_addr, size2).unwrap(); + + let complement = visited.complement().into_iter().collect::>(); + assert_eq!( + complement, + vec![ + db_begin..start1_addr, + end1_addr..start2_addr, + end2_addr..db_end, + ] + ); + } + + #[test] + fn test_complement_with_full_range() { + let db_size = 0x1000; + let start = 2048; + let size = db_size - start; + + let mut visited = LinearAddressRangeSet::new(db_size).unwrap(); + visited + .insert_area(LinearAddress::new(start).unwrap(), size) + .unwrap(); + let complement = visited.complement().into_iter().collect::>(); + assert_eq!(complement, vec![]); + } + + #[test] + fn test_complement_with_empty() { + let db_size = LinearAddressRangeSet::NODE_STORE_ADDR_START; + let visited = LinearAddressRangeSet::new(db_size.get()).unwrap(); + let complement = visited.complement().into_iter().collect::>(); + assert_eq!(complement, vec![]); + } +} From 5a4b85acb2a4f6d25933d4c811f84c0c57a6485f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 07:17:23 -0700 Subject: [PATCH 0785/1053] build(deps): update lru requirement from 0.14.0 to 0.15.0 (#1001) Updates the requirements on [lru](https://github.com/jeromefroe/lru-rs) to permit the latest version.
    Commits
    • 514cc47 Merge pull request #213 from jeromefroe/jerome/prepare-0-15-0-release
    • c1e43af Prepare 0.15.0 release
    • 9903725 Merge pull request #212 from levkk/levkk-promote
    • 11d5b77 Return true/false for promote/demote
    • See full diff in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 015156f79701..95f7419d55a8 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -25,7 +25,7 @@ smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } sha2 = "0.10.8" integer-encoding = "4.0.0" arc-swap = "1.7.1" -lru = "0.14.0" +lru = "0.15.0" metrics = "0.24.0" log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" From e46b773a52cadb7c3c92a2c34c18f7f9fa4c1d20 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 30 Jun 2025 10:00:43 -0700 Subject: [PATCH 0786/1053] fix: Report IO errors (#1005) Previously, if an error occurred while flushing nodes, it was silently ignored during io-uring processing. To reproduce this, I created a tiny filesystem using an AWS instance running ubuntu: sudo mkdir /mnt/tmpfs sudo mount -t tmpfs -o size=5k,mode=1777,noatime tmpfs /mnt/tmpfs I then ran a few fwdctl commands, which passed on main: cargo run --bin fwdctl --features logger -- create /mnt/tmpfs/test.db target/debug/fwdctl insert --db /mnt/tmpfs/test.db a "$(tr -d '\n' < /etc/passwd)" target/debug/fwdctl insert --db /mnt/tmpfs/test.db b "$(tr -d '\n' < /etc/passwd)" target/debug/fwdctl insert --db /mnt/tmpfs/test.db c "$(tr -d '\n' < /etc/passwd)" When running this after these fixes, the following is produced from the final insert, which fills the disk: Error: FileIO(FileIoError { inner: Os { code: 28, kind: StorageFull, message: "No space left on device" }, filename: Some("/mnt/tmpfs/test.db"), offset: 8544, context: Some("write failure") }) This fix restructures the pinned buffer pool to add an offset, which is only used for error reporting. This offset is also used as a flag indicating the entry is in use rather than using a bool. --- storage/src/nodestore.rs | 94 ++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 2de4da525412..4fe4115c7d4c 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1318,35 +1318,85 @@ impl NodeStore, FileBacked> { #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] pub fn flush_nodes(&self) -> Result<(), FileIoError> { + use std::pin::Pin; + + #[derive(Clone, Debug)] + struct PinnedBufferEntry { + pinned_buffer: Pin>, + offset: Option, + } + + /// Helper function to handle completion queue entries and check for errors + fn handle_completion_queue( + storage: &FileBacked, + completion_queue: io_uring::cqueue::CompletionQueue<'_>, + saved_pinned_buffers: &mut [PinnedBufferEntry], + ) -> Result<(), FileIoError> { + for entry in completion_queue { + let item = entry.user_data() as usize; + let pbe = saved_pinned_buffers + .get_mut(item) + .expect("should be an index into the array"); + + if entry.result() + != pbe + .pinned_buffer + .len() + .try_into() + .expect("buffer should be small enough") + { + let error = if entry.result() >= 0 { + std::io::Error::other("Partial write") + } else { + std::io::Error::from_raw_os_error(0 - entry.result()) + }; + return Err(storage.file_io_error( + error, + pbe.offset.expect("offset should be Some"), + Some("write failure".to_string()), + )); + } + pbe.offset = None; + } + Ok(()) + } + const RINGSIZE: usize = FileBacked::RINGSIZE as usize; let flush_start = Instant::now(); let mut ring = self.storage.ring.lock().expect("poisoned lock"); - let mut saved_pinned_buffers = vec![(false, std::pin::Pin::new(Box::default())); RINGSIZE]; + let mut saved_pinned_buffers = vec![ + PinnedBufferEntry { + pinned_buffer: Pin::new(Box::new([0; 0])), + offset: None, + }; + RINGSIZE + ]; for (&addr, &(area_size_index, ref node)) in &self.kind.new { let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger node.as_bytes(area_size_index, &mut serialized); let mut serialized = serialized.into_boxed_slice(); loop { // Find the first available write buffer, enumerate to get the position for marking it completed - if let Some((pos, (busy, found))) = saved_pinned_buffers + if let Some((pos, pbe)) = saved_pinned_buffers .iter_mut() .enumerate() - .find(|(_, (busy, _))| !*busy) + .find(|(_, pbe)| pbe.offset.is_none()) { - *found = std::pin::Pin::new(std::mem::take(&mut serialized)); + pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); + pbe.offset = Some(addr.get()); + let submission_queue_entry = self .storage - .make_op(found) + .make_op(&pbe.pinned_buffer) .offset(addr.get()) .build() .user_data(pos as u64); - *busy = true; // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope - // until the operation has been completed. This is ensured by marking the slot busy, - // and not marking it !busy until the kernel has said it's done below. + // until the operation has been completed. This is ensured by having a Some(offset) + // and not marking it None until the kernel has said it's done below. #[expect(unsafe_code)] while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { ring.submitter().squeue_wait().map_err(|e| { @@ -1370,33 +1420,29 @@ impl NodeStore, FileBacked> { })?; let completion_queue = ring.completion(); trace!("competion queue length: {}", completion_queue.len()); - for entry in completion_queue { - let item = entry.user_data() as usize; - saved_pinned_buffers - .get_mut(item) - .expect("should be an index into the array") - .0 = false; - } + handle_completion_queue( + &self.storage, + completion_queue, + &mut saved_pinned_buffers, + )?; } } let pending = saved_pinned_buffers .iter() - .filter(|(busy, _)| *busy) + .filter(|pbe| pbe.offset.is_some()) .count(); ring.submit_and_wait(pending).map_err(|e| { self.storage .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) })?; - for entry in ring.completion() { - let item = entry.user_data() as usize; - saved_pinned_buffers - .get_mut(item) - .expect("should be an index into the array") - .0 = false; - } + handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; - debug_assert_eq!(saved_pinned_buffers.iter().find(|(busy, _)| *busy), None); + debug_assert!( + !saved_pinned_buffers.iter().any(|pbe| pbe.offset.is_some()), + "Found entry with offset still set: {:?}", + saved_pinned_buffers.iter().find(|pbe| pbe.offset.is_some()) + ); self.storage .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; From 82968d3c2d360249fc7841ff9902e03865d17c14 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:31:19 -0400 Subject: [PATCH 0787/1053] refactor(deps): move duplicates to workspace (#1002) This PR closes #929 by utilizing the `workspace.dependencies` table to remove duplicate dependencies across crates. ## How Across the member crates of the workspace, I identified dependencies which appeared more than once and specified it in the workspace dependencies table. In the case of two instances of a dependency with different minor versions, I default to using the greater minor version in the workspace. --- Cargo.toml | 23 +++++++++++++++++++++++ benchmark/Cargo.toml | 14 +++++++------- ffi/Cargo.toml | 8 ++++---- firewood/Cargo.toml | 30 +++++++++++++++--------------- fwdctl/Cargo.toml | 8 ++++---- storage/Cargo.toml | 26 +++++++++++++------------- triehash/Cargo.toml | 6 +++--- 7 files changed, 69 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22b37ba526f6..662ed9ac3344 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,26 @@ missing_const_for_fn = "warn" arithmetic_side_effects = "warn" # lower the priority of pedantic to allow overriding the lints it includes pedantic = { level = "warn", priority = -1 } + +[workspace.dependencies] +metrics = "0.24.2" +metrics-util = "0.20.0" +sha2 = "0.10.8" +tokio = "1.36.0" +clap = { version = "4.5.0", features = ["derive"] } +fastrace = "0.7.4" +bincode = "1.3.3" +serde = "1.0.199" +thiserror = "2.0.3" +coarsetime = "0.1.36" +integer-encoding = "4.0.0" +env_logger = "0.11.7" +smallvec = "1.6.1" + +# dev dependencies +rand = "0.9.1" +criterion = "0.6.0" +pprof = "0.15.0" +tempfile = "3.12.0" +ethereum-types = "0.15.1" +hex-literal = "1.0.0" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 76b9a090fd3f..4fc619eefac5 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -19,18 +19,18 @@ path = "src/main.rs" [dependencies] firewood = { version = "0.0.7", path = "../firewood" } hex = "0.4.3" -clap = { version = "4.5.0", features = ['derive', 'string'] } -sha2 = "0.10.8" -metrics = "0.24.2" -metrics-util = "0.20.0" +clap = { workspace = true, features = ['string'] } +sha2 = { workspace = true } +metrics = { workspace = true } +metrics-util = { workspace = true } metrics-exporter-prometheus = "0.17.2" -tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } +tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } rand = "0.9.0" rand_distr = "0.5.0" pretty-duration = "0.1.1" -env_logger = "0.11.5" +env_logger = { workspace = true } log = "0.4.20" -fastrace = { version = "0.7.4", features = ["enable"] } +fastrace = { workspace = true, features = ["enable"] } fastrace-opentelemetry = { version = "0.12.0" } opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry = "0.30.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 37c5775d2615..26027c8b4534 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -21,12 +21,12 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.2" firewood = { version = "0.0.7", path = "../firewood" } -metrics = "0.24.2" -metrics-util = "0.20.0" +metrics = { workspace = true } +metrics-util = { workspace = true } chrono = "0.4.39" oxhttp = "0.3.0" -coarsetime = "0.1.35" -env_logger = {version = "0.11.7", optional = true} +coarsetime = { workspace = true } +env_logger = { workspace = true, optional = true} [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index e2d01635ae65..d6fc6b29cd7f 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -26,17 +26,17 @@ aquamarine = "0.6.0" async-trait = "0.1.77" futures = "0.3.30" hex = "0.4.3" -metrics = "0.24.0" -serde = { version = "1.0" } -sha2 = "0.10.8" +metrics = { workspace = true } +serde = { workspace = true } +sha2 = { workspace = true } test-case = "3.3.1" -thiserror = "2.0.3" +thiserror = { workspace = true } typed-builder = "0.21.0" -bincode = "1.3.3" -integer-encoding = "4.0.0" -smallvec = "1.6.1" -fastrace = { version = "0.7.4" } -coarsetime = "0.1.36" +bincode = { workspace = true } +integer-encoding = { workspace = true } +smallvec = { workspace = true } +fastrace = { workspace = true } +coarsetime = { workspace = true } firewood-macros = { version = "0.0.7", path = "../firewood-macros" } [features] @@ -49,17 +49,17 @@ ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] firewood-triehash = { version = "0.0.7", path = "../triehash" } -criterion = { version = "0.6.0", features = ["async_tokio"] } -rand = "0.9.0" +criterion = { workspace = true, features = ["async_tokio"] } +rand = { workspace = true } rand_distr = "0.5.0" clap = { version = "4.5.0", features = ['derive'] } -pprof = { version = "0.15.0", features = ["flamegraph"] } -tempfile = "3.12.0" +pprof = { workspace = true, features = ["flamegraph"] } +tempfile = { workspace = true } tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } -ethereum-types = "0.15.1" +ethereum-types = { workspace = true } sha3 = "0.10.8" plain_hasher = "0.2.3" -hex-literal = "1.0.0" +hex-literal = { workspace = true } env_logger = "0.11.7" hash-db = "0.16.0" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 769f7aacc40d..e2df0d035e26 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -25,10 +25,10 @@ path = "src/main.rs" [dependencies] firewood = { version = "0.0.7", path = "../firewood" } firewood-storage = { version = "0.0.7", path = "../storage" } -clap = { version = "4.5.0", features = ["cargo", "derive"] } -env_logger = "0.11.2" +clap = { workspace = true, features = ["cargo"] } +env_logger = { workspace = true } log = "0.4.20" -tokio = { version = "1.36.0", features = ["full"] } +tokio = { workspace = true, features = ["full"] } futures-util = "0.3.30" hex = "0.4.3" csv = "1.3.1" @@ -39,7 +39,7 @@ anyhow = "1.0.79" assert_cmd = "2.0.13" predicates = "3.1.0" serial_test = "3.0.0" -rand = "0.9.1" +rand = { workspace = true } [lints] workspace = true diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 95f7419d55a8..2f69ca288e68 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -16,37 +16,37 @@ repository = "https://github.com/ava-labs/firewood" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bincode = "1.3.3" +bincode = { workspace = true } bitflags = "2.5.0" enum-as-inner = "0.6.0" hex = "0.4.3" -serde = { version = "1.0.199", features = ["derive"] } -smallvec = { version = "1.13.2", features = ["serde", "write", "union"] } -sha2 = "0.10.8" -integer-encoding = "4.0.0" +serde = { workspace = true, features = ["derive"] } +smallvec = { workspace = true, features = ["serde", "write", "union"] } +sha2 = { workspace = true } +integer-encoding = { workspace = true } arc-swap = "1.7.1" lru = "0.15.0" -metrics = "0.24.0" +metrics = { workspace = true } log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" bytemuck_derive = "1.7.0" bitfield = "0.19.0" -fastrace = { version = "0.7.4" } +fastrace = { workspace = true } io-uring = { version = "0.7.4", optional = true } triomphe = "0.1.14" -coarsetime = "0.1.35" +coarsetime = { workspace = true } rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } bytes = { version = "1.10.1", optional = true } -thiserror = "2.0.3" +thiserror = { workspace = true } semver = "1.0.26" [dev-dependencies] -rand = "0.9.0" +rand = { workspace = true } test-case = "3.3.1" -criterion = { version = "0.6.0", features = ["async_tokio", "html_reports"] } -pprof = { version = "0.15.0", features = ["flamegraph"] } -tempfile = "3.12.0" +criterion = { workspace = true, features = ["async_tokio", "html_reports"] } +pprof = { workspace = true, features = ["flamegraph"] } +tempfile = { workspace = true } nonzero_ext = "0.3.0" [features] diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 42e3a7c733cd..368a143bc3fb 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -12,12 +12,12 @@ hash-db = "0.16.0" rlp = "0.6" [dev-dependencies] -criterion = "0.6.0" +criterion = { workspace = true } keccak-hasher = "0.16.0" -ethereum-types = { version = "0.15.1" } +ethereum-types = { workspace = true } tiny-keccak = { version = "2.0", features = ["keccak"] } trie-standardmap = "0.16.0" -hex-literal = "1.0.0" +hex-literal = { workspace = true } [[bench]] name = "triehash" From fcbd85c83aa7e633ed133e7b6d2846a55d5f45a5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 1 Jul 2025 12:46:02 -0700 Subject: [PATCH 0788/1053] perf: Cache the latest view (#1004) Because of the usage pattern for coreth, there is contention between committing and getting values. Coreth attempts to commit the latest version asynchronously while fetching values from it. This is fixed by adding a single cached revision. In the normal case, there will be many reads on some revision, followed by creating a proposal, followed by an asynchronous commit. During this commit, there will be additional reads, so we save the view just before committing. Fixes #1003 --------- Co-authored-by: Brandon LeBlanc --- ffi/firewood.h | 6 +++ ffi/src/lib.rs | 97 +++++++++++++++++++++++++++++++++++++++------- firewood/src/db.rs | 2 +- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 16b6498fc445..17aae123d3e7 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -113,6 +113,12 @@ struct Value fwd_batch(const struct DatabaseHandle *db, * # Arguments * * * `db` - The database handle to close, previously returned from a call to `open_db()` + * + * # Panics + * + * This function panics if: + * * `db` is `None` (null pointer) + * * A lock is poisoned */ void fwd_close_db(struct DatabaseHandle *db); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 0d89edcfec37..8c374642ec30 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -14,13 +14,14 @@ use std::ops::Deref; use std::os::unix::ffi::OsStrExt as _; use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use firewood::db::{ - BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, DbViewSyncBytes as _, Proposal, + BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal, }; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; +use firewood::v2::api::HashKey; use metrics::counter; #[doc(hidden)] @@ -52,6 +53,10 @@ pub struct DatabaseHandle<'p> { // Keep proposals first, as they must be dropped before the database handle is dropped due to lifetime // issues. proposals: RwLock>>>, + + /// A single cached view to improve performance of reads while committing + cached_view: Mutex)>>, + /// The database db: Db, } @@ -61,10 +66,20 @@ impl From for DatabaseHandle<'_> { Self { db, proposals: RwLock::new(HashMap::new()), + cached_view: Mutex::new(None), } } } +impl DatabaseHandle<'_> { + fn clear_cached_view(&self) { + self.cached_view + .lock() + .expect("cached_view lock is poisoned") + .take(); + } +} + impl Deref for DatabaseHandle<'_> { type Target = Db; @@ -215,17 +230,37 @@ fn get_from_root( key: &Value, ) -> Result { let db = db.ok_or("db should be non-null")?; - // Get the revision associated with the root hash. + let requested_root = root.as_slice().try_into()?; + let mut cached_view = db.cached_view.lock().expect("cached_view lock is poisoned"); + let value = match cached_view.as_ref() { + // found the cached view, use it + Some((root_hash, view)) if root_hash == &requested_root => { + counter!("firewood.ffi.cached_view.hit").increment(1); + view.val_sync_bytes(key.as_slice()) + } + // If what was there didn't match the requested root, we need a new view, so we + // update the cache + _ => { + counter!("firewood.ffi.cached_view.miss").increment(1); + let rev = view_sync_from_root(db, root)?; + let result = rev.val_sync_bytes(key.as_slice()); + *cached_view = Some((requested_root.clone(), rev)); + result + } + } + .map_err(|e| e.to_string())? + .ok_or("")?; + + Ok(value.into()) +} +fn view_sync_from_root( + db: &DatabaseHandle<'_>, + root: &Value, +) -> Result, String> { let rev = db .view_sync(root.as_slice().try_into()?) .map_err(|e| e.to_string())?; - - // Get value associated with key. - let value = rev - .val_sync_bytes(key.as_slice()) - .map_err(|e| e.to_string())? - .ok_or("")?; - Ok(value.into()) + Ok(rev) } /// A `KeyValue` represents a key-value pair, passed to the FFI. @@ -509,7 +544,26 @@ fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), Strin .map_err(|_| "proposal lock is poisoned")? .remove(&proposal_id) .ok_or("proposal not found")?; - proposal.commit_sync().map_err(|e| e.to_string()) + + // Get the proposal hash and cache the view. We never cache an empty proposal. + let proposal_hash = proposal.root_hash_sync(); + + if let Ok(Some(proposal_hash)) = proposal_hash { + let mut guard = db.cached_view.lock().expect("cached_view lock is poisoned"); + match db.view_sync(proposal_hash.clone()) { + Ok(view) => *guard = Some((proposal_hash, view)), + Err(_) => *guard = None, // Clear cache on error + } + drop(guard); + } + + // Commit the proposal + let result = proposal.commit_sync().map_err(|e| e.to_string()); + + // Clear the cache, which will force readers after this point to find the committed root hash + db.clear_cached_view(); + + result } /// Drops a proposal from the database. @@ -883,9 +937,26 @@ fn manager_config( /// # Arguments /// /// * `db` - The database handle to close, previously returned from a call to `open_db()` +/// +/// # Panics +/// +/// This function panics if: +/// * `db` is `None` (null pointer) +/// * A lock is poisoned #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_close_db(db: *mut DatabaseHandle) { - let _ = unsafe { Box::from_raw(db) }; +pub unsafe extern "C" fn fwd_close_db(db: Option<&mut DatabaseHandle>) { + let db_handle = db.expect("db should be non-null"); + + // Explicitly clear the downstream items. Drop will do these in order, so this + // code is defensive in case someone reorders the struct memebers of DatabaseHandle. + db_handle + .proposals + .write() + .expect("proposals lock is poisoned") + .clear(); + db_handle.clear_cached_view(); + + // The database handle will be dropped automatically when db_handle goes out of scope } #[cfg(test)] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 250ed58997d1..adec719c82d1 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -66,7 +66,7 @@ pub trait DbViewSync { } /// A synchronous view of the database with raw byte keys (object-safe version). -pub trait DbViewSyncBytes { +pub trait DbViewSyncBytes: std::fmt::Debug { /// find a value synchronously using raw bytes fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError>; } From 73d14d27ceb6159f446dc8ecbfaeecf1bd706888 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 1 Jul 2025 13:12:31 -0700 Subject: [PATCH 0789/1053] perf: Allow cloned proposals (#1010) We were using the Arc as a way to know if a proposal could be committed. Instead, we now allow proposals to be cloned, and just check an AtomicBool to verify whether we can commit the proposal or not. This will allow for some additional optimizations in the ffi layer. --- firewood/src/db.rs | 53 +++++++++++++++++++++++++----------------- firewood/src/v2/api.rs | 10 +++----- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index adec719c82d1..e6d4b95bcc1a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -33,6 +33,7 @@ use firewood_storage::{ use metrics::{counter, describe_counter}; use std::io::Write; use std::path::Path; +use std::sync::atomic::AtomicBool; use std::sync::{Arc, RwLock}; use thiserror::Error; use typed_builder::TypedBuilder; @@ -241,6 +242,7 @@ where Ok(Self::Proposal { nodestore: immutable, db: self, + committed: AtomicBool::new(false), } .into()) } @@ -350,6 +352,7 @@ impl Db { Ok(Arc::new(Proposal { nodestore: immutable, db: self, + committed: AtomicBool::new(false), })) } @@ -382,9 +385,21 @@ impl Db { pub struct Proposal<'p> { nodestore: Arc, FileBacked>>, db: &'p Db, + committed: AtomicBool, } impl Proposal<'_> { + /// Get the root hash of the proposal synchronously + pub fn start_commit(&self) -> Result<(), api::Error> { + if self + .committed + .swap(true, std::sync::atomic::Ordering::Relaxed) + { + return Err(api::Error::AlreadyCommitted); + } + Ok(()) + } + /// Get the root hash of the proposal synchronously pub fn root_hash_sync(&self) -> Result, api::Error> { #[cfg(not(feature = "ethhash"))] @@ -449,26 +464,26 @@ impl<'a> api::Proposal for Proposal<'a> { } async fn commit(self: Arc) -> Result<(), api::Error> { - match Arc::into_inner(self) { - Some(proposal) => { - let mut manager = proposal.db.manager.write().expect("poisoned lock"); - Ok(manager.commit(proposal.nodestore)?) - } - None => Err(api::Error::CannotCommitClonedProposal), - } + self.start_commit()?; + Ok(self + .db + .manager + .write() + .expect("poisoned lock") + .commit(self.nodestore.clone())?) } } impl Proposal<'_> { /// Commit a proposal synchronously pub fn commit_sync(self: Arc) -> Result<(), api::Error> { - match Arc::into_inner(self) { - Some(proposal) => { - let mut manager = proposal.db.manager.write().expect("poisoned lock"); - Ok(manager.commit(proposal.nodestore)?) - } - None => Err(api::Error::CannotCommitClonedProposal), - } + self.start_commit()?; + Ok(self + .db + .manager + .write() + .expect("poisoned lock") + .commit(self.nodestore.clone())?) } /// Create a new proposal from the current one synchronously @@ -512,6 +527,7 @@ impl Proposal<'_> { Ok(Self { nodestore: immutable, db: self.db, + committed: AtomicBool::new(false), }) } } @@ -543,15 +559,10 @@ mod test { // attempt to commit the clone; this should fail let result = cloned.commit().await; - assert!( - matches!(result, Err(Error::CannotCommitClonedProposal)), - "{result:?}" - ); + assert!(result.is_ok()); - // the prior attempt consumed the Arc though, so cloned is no longer valid - // that means the actual proposal can be committed let result = proposal.commit().await; - assert!(matches!(result, Ok(())), "{result:?}"); + assert!(matches!(result, Err(Error::AlreadyCommitted)), "{result:?}"); } #[tokio::test] diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index f5f2ead9f1ae..5cd96de9ceaa 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -120,13 +120,9 @@ pub enum Error { /// A file I/O error occurred FileIO(#[from] FileIoError), - /// Cannot commit a cloned proposal - /// - /// Cloned proposals are problematic because if they are committed, then you could - /// create another proposal from this committed proposal, so we error at commit time - /// if there are outstanding clones - #[error("Cannot commit a cloned proposal")] - CannotCommitClonedProposal, + /// Cannot commit a committed proposal + #[error("Cannot commit a committed proposal")] + AlreadyCommitted, /// Internal error #[error("Internal error")] From 44d7e6b14f4c846e1f52da52d49cc552194c535a Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 1 Jul 2025 18:32:51 -0700 Subject: [PATCH 0790/1053] chore: suppress clippy::cast_possible_truncation across the workspace (#1012) As noted in the comment in `firewood/src/lib.rs`, this is to avoid the unecessary try_into calls for `u64 -> usize`. --- Cargo.toml | 4 ++++ benchmark/src/main.rs | 4 ---- benchmark/src/single.rs | 4 ---- benchmark/src/zipf.rs | 4 ---- ffi/src/lib.rs | 7 +++++++ firewood/src/lib.rs | 12 ++++++++++++ firewood/src/merkle.rs | 4 ---- firewood/src/stream.rs | 4 ---- storage/src/hashers/merkledb.rs | 7 ------- storage/src/lib.rs | 7 +++++++ storage/src/linear/filebacked.rs | 4 ---- storage/src/linear/memory.rs | 4 ---- storage/src/node/branch.rs | 4 ---- storage/src/node/mod.rs | 4 ---- storage/src/nodestore.rs | 5 ----- triehash/src/lib.rs | 4 ---- 16 files changed, 30 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 662ed9ac3344..de40473890f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,10 @@ missing_const_for_fn = "warn" arithmetic_side_effects = "warn" # lower the priority of pedantic to allow overriding the lints it includes pedantic = { level = "warn", priority = -1 } +# Ignore interger casts. This is to avoid unnecessary `try_into` calls for usize +# to u64 and vice versa and should be re-enabled if/when clippy has a separate +# lint for usize vs non-usize truncation. +cast_possible_truncation = { level = "allow" } [workspace.dependencies] metrics = "0.24.2" diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index a76a34c8508b..34894a68d21d 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -6,10 +6,6 @@ clippy::arithmetic_side_effects, reason = "Found 2 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::match_same_arms, reason = "Found 1 occurrences after enabling the lint." diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index 846b646b10cd..5f17017102e2 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -5,10 +5,6 @@ clippy::arithmetic_side_effects, reason = "Found 2 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::cast_sign_loss, reason = "Found 1 occurrences after enabling the lint." diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 29b9e95327c3..8dbe215ca4ec 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -5,10 +5,6 @@ clippy::arithmetic_side_effects, reason = "Found 2 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 2 occurrences after enabling the lint." -)] #![expect( clippy::cast_precision_loss, reason = "Found 1 occurrences after enabling the lint." diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8c374642ec30..04649c68a43f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -5,6 +5,13 @@ unsafe_code, reason = "This is an FFI library, so unsafe code is expected." )] +#![cfg_attr( + not(target_pointer_width = "64"), + forbid( + clippy::cast_possible_truncation, + reason = "non-64 bit target likely to cause issues during u64 to usize conversions" + ) +)] use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr, c_char}; diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index ebf623aab21e..ba359008d24d 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -106,6 +106,18 @@ //! abandoned, nothing has actually been written to disk. //! #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] +// Instead of using a `compile_error!`, cause clippy to hard fail if the target is not 64-bit. This +// is a workaround for the fact that the `clippy::cast_possible_truncation` lint does not delineate +// between 64-bit and non-64-bit targets with respect to `usize -> u64` casts and vice versa which +// leads to a lot of unecessary `TryInto` casts. This also allows 32-bit builds to compile as long +// as `clippy` is not part of the build process albeit with the risk of truncation errors. +#![cfg_attr( + not(target_pointer_width = "64"), + forbid( + clippy::cast_possible_truncation, + reason = "non-64 bit target likely to cause issues during u64 to usize conversions" + ) +)] #[cfg(all(feature = "ethhash", feature = "branch_factor_256"))] compile_error!( diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index e8a675dbdca2..4422649d8f3e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::cast_possible_truncation, - reason = "Found 5 occurrences after enabling the lint." -)] #![expect( clippy::match_same_arms, reason = "Found 1 occurrences after enabling the lint." diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index f35685998404..0fe2718c458a 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::unnecessary_wraps, reason = "Found 1 occurrences after enabling the lint." diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index 502582730240..af67882f1dad 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -8,13 +8,6 @@ reason = "Found 1 occurrences after enabling the lint." ) )] -#![cfg_attr( - not(feature = "ethhash"), - expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." - ) -)] use crate::hashednode::{HasUpdate, Hashable, Preimage}; use crate::{TrieHash, ValueDigest}; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index d8f9d77b9be9..033933b7b6e6 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -3,6 +3,13 @@ #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] #![deny(unsafe_code)] +#![cfg_attr( + not(target_pointer_width = "64"), + forbid( + clippy::cast_possible_truncation, + reason = "non-64 bit target likely to cause issues during u64 to usize conversions" + ) +)] //! # storage implements the storage of a [Node] on top of a `LinearStore` //! diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 402296d62118..79f264f6c96e 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -10,10 +10,6 @@ clippy::arithmetic_side_effects, reason = "Found 5 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 3 occurrences after enabling the lint." diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index db9bb773d772..ddbeee1d2253 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -5,10 +5,6 @@ clippy::arithmetic_side_effects, reason = "Found 3 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 2 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 1 occurrences after enabling the lint." diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 1a91052a26e3..27ad94455a39 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::cast_possible_truncation, - reason = "Found 2 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 2 occurrences after enabling the lint." diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 67178f56258f..f64b1f892ea4 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -5,10 +5,6 @@ clippy::arithmetic_side_effects, reason = "Found 4 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 6 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 1 occurrences after enabling the lint." diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 4fe4115c7d4c..b22e64a26ca9 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -5,10 +5,6 @@ clippy::arithmetic_side_effects, reason = "Found 5 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 16 occurrences after enabling the lint." -)] #![expect( clippy::default_trait_access, reason = "Found 6 occurrences after enabling the lint." @@ -1617,7 +1613,6 @@ pub(crate) mod nodestore_test_utils { use super::*; // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. - #[allow(clippy::cast_possible_truncation)] pub(crate) fn write_new_node( nodestore: &NodeStore, node: &Node, diff --git a/triehash/src/lib.rs b/triehash/src/lib.rs index c05782d6f0e2..9ac11c3bd4c5 100644 --- a/triehash/src/lib.rs +++ b/triehash/src/lib.rs @@ -18,10 +18,6 @@ clippy::bool_to_int_with_if, reason = "Found 1 occurrences after enabling the lint." )] -#![expect( - clippy::cast_possible_truncation, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 13 occurrences after enabling the lint." From 76b11d245689c5af93f23abdf327bc84385106f5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 1 Jul 2025 18:40:04 -0700 Subject: [PATCH 0791/1053] chore: Clippy pushdown (#1011) Lots of changes in nodestore.rs, and too many warnings are suppressed. I pushed these down to the individual functions, which should help with new development in this area. --- firewood/src/db.rs | 6 +- firewood/src/merkle.rs | 7 +- firewood/src/stream.rs | 2 +- storage/src/nodestore.rs | 164 +++++++++++++++++++++++++-------------- 4 files changed, 115 insertions(+), 64 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e6d4b95bcc1a..c3dcfeb10713 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -207,7 +207,7 @@ where .read() .expect("poisoned lock") .current_revision(); - let proposal = NodeStore::new(parent)?; + let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); let span = fastrace::Span::enter_with_local_parent("merkleops"); for op in batch { @@ -324,7 +324,7 @@ impl Db { .read() .expect("poisoned lock") .current_revision(); - let proposal = NodeStore::new(parent)?; + let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); for op in batch { match op { @@ -500,7 +500,7 @@ impl Proposal<'_> { batch: api::Batch, ) -> Result { let parent = self.nodestore.clone(); - let proposal = NodeStore::new(parent)?; + let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); for op in batch { match op { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4422649d8f3e..8a716e9aa507 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2051,8 +2051,9 @@ mod tests { |(mut hashes, immutable_merkle), (k, v)| { let root_hash = immutable_merkle.nodestore.root_hash(); hashes.push(root_hash); - let mut merkle = - Merkle::from(NodeStore::new(Arc::new(immutable_merkle.nodestore)).unwrap()); + let mut merkle = Merkle::from( + NodeStore::new(&Arc::new(immutable_merkle.nodestore)).unwrap(), + ); merkle.insert(k, v.clone()).unwrap(); (hashes, merkle.try_into().unwrap()) }, @@ -2063,7 +2064,7 @@ mod tests { |(mut new_hashes, immutable_merkle_before_removal), (k, _)| { let before = immutable_merkle_before_removal.dump().unwrap(); let mut merkle = Merkle::from( - NodeStore::new(Arc::new(immutable_merkle_before_removal.nodestore)) + NodeStore::new(&Arc::new(immutable_merkle_before_removal.nodestore)) .unwrap(), ); merkle.remove(k).unwrap(); diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 0fe2718c458a..308a4d2e2f32 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1159,7 +1159,7 @@ mod tests { let immutable_merkle: Merkle, _>> = merkle.try_into().unwrap(); println!("{}", immutable_merkle.dump().unwrap()); - merkle = Merkle::from(NodeStore::new(Arc::new(immutable_merkle.into_inner())).unwrap()); + merkle = Merkle::from(NodeStore::new(&Arc::new(immutable_merkle.into_inner())).unwrap()); let mut stream = merkle.key_value_iter(); diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index b22e64a26ca9..ac92db5ef7bf 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1,39 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::arithmetic_side_effects, - reason = "Found 5 occurrences after enabling the lint." -)] -#![expect( - clippy::default_trait_access, - reason = "Found 6 occurrences after enabling the lint." -)] -#![expect( - clippy::indexing_slicing, - reason = "Found 10 occurrences after enabling the lint." -)] -#![expect( - clippy::match_wildcard_for_single_variants, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::missing_errors_doc, - reason = "Found 15 occurrences after enabling the lint." -)] -#![expect( - clippy::missing_panics_doc, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::needless_pass_by_value, - reason = "Found 3 occurrences after enabling the lint." -)] -#![expect( - clippy::unwrap_used, - reason = "Found 2 occurrences after enabling the lint." -)] - use crate::linear::FileIoError; use crate::logger::{debug, trace}; use arc_swap::ArcSwap; @@ -45,6 +12,7 @@ use fastrace::local::LocalSpan; use metrics::counter; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use smallvec::SmallVec; use std::collections::HashMap; use std::fmt::Debug; @@ -125,7 +93,7 @@ fn area_size_hash() -> TrieHash { } // TODO: automate this, must stay in sync with above -const fn index_name(index: AreaIndex) -> &'static str { +const fn index_name(index: usize) -> &'static str { match index { 0 => "16", 1 => "32", @@ -164,6 +132,11 @@ const NUM_AREA_SIZES: usize = AREA_SIZES.len(); const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; +#[inline] +fn new_area_index(n: usize) -> AreaIndex { + n.try_into().expect("Area index out of bounds") +} + /// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. fn area_size_to_index(n: u64) -> Result { if n > MAX_AREA_SIZE { @@ -180,7 +153,7 @@ fn area_size_to_index(n: u64) -> Result { AREA_SIZES .iter() .position(|&size| size >= n) - .map(|index| index as AreaIndex) + .map(new_area_index) .ok_or_else(|| { Error::new( ErrorKind::InvalidData, @@ -219,6 +192,10 @@ struct StoredArea { impl NodeStore { /// Returns (index, `area_size`) for the stored area at `addr`. /// `index` is the index of `area_size` in the array of valid block sizes. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the area cannot be read. pub fn area_index_and_size( &self, addr: LinearAddress, @@ -248,6 +225,10 @@ impl NodeStore { /// Read a [Node] from the provided [`LinearAddress`]. /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. pub fn read_node_from_disk( &self, addr: LinearAddress, @@ -259,7 +240,9 @@ impl NodeStore { debug_assert!(addr.get() % 8 == 0); - let actual_addr = addr.get() + 1; // skip the length byte + // saturating because there is no way we can be reading at u64::MAX + // and this will fail very soon afterwards + let actual_addr = addr.get().saturating_add(1); // skip the length byte let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); @@ -286,6 +269,10 @@ impl NodeStore { /// Read a [Node] from the provided [`LinearAddress`] and size. /// This is an uncached read, primarily used by check utilities + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. pub fn uncached_read_node_and_size( &self, addr: LinearAddress, @@ -299,7 +286,7 @@ impl NodeStore { Some("uncached_read_node_and_size".to_string()), ) })?; - self.storage.stream_from(addr.get() + 1)?; + self.storage.stream_from(addr.get().saturating_add(1))?; let node: SharedNode = Node::from_reader(area_stream) .map_err(|e| { self.storage.file_io_error( @@ -313,8 +300,13 @@ impl NodeStore { } /// Get the size of an area index (used by the checker) + /// + /// # Panics + /// + /// Panics if `index` is out of bounds for the `AREA_SIZES` array. #[must_use] pub const fn size_from_area_index(index: AreaIndex) -> u64 { + #[expect(clippy::indexing_slicing)] AREA_SIZES[index as usize] } } @@ -322,6 +314,10 @@ impl NodeStore { impl NodeStore { /// Open an existing [`NodeStore`] /// Assumes the header is written in the [`ReadableStorage`]. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be read or validated. pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; let mut header = NodeStoreHeader::new(); @@ -339,7 +335,7 @@ impl NodeStore { let mut nodestore = Self { header, kind: Committed { - deleted: Default::default(), + deleted: Box::default(), root_hash: None, }, storage, @@ -347,7 +343,7 @@ impl NodeStore { if let Some(root_address) = nodestore.header.root_address { let node = nodestore.read_node_from_disk(root_address, "open"); - let root_hash = node.map(|n| hash_node(&n, &Path(Default::default())))?; + let root_hash = node.map(|n| hash_node(&n, &Path(SmallVec::default())))?; nodestore.kind.root_hash = Some(root_hash.into_triehash()); } @@ -356,6 +352,10 @@ impl NodeStore { /// Create a new, empty, Committed [`NodeStore`] and clobber /// the underlying store with an empty freelist and no root node + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the storage cannot be accessed. pub fn new_empty_committed(storage: Arc) -> Result { let header = NodeStoreHeader::new(); @@ -363,7 +363,7 @@ impl NodeStore { header, storage, kind: Committed { - deleted: Default::default(), + deleted: Box::default(), root_hash: None, }, }) @@ -421,10 +421,14 @@ impl Parentable for Committed { impl NodeStore { /// Create a new `MutableProposal` [`NodeStore`] from a parent [`NodeStore`] + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the parent root cannot be read. pub fn new( - parent: Arc>, + parent: &Arc>, ) -> Result { - let mut deleted: Vec<_> = Default::default(); + let mut deleted = Vec::default(); let root = if let Some(root_addr) = parent.header.root_address { deleted.push(root_addr); let root = parent.read_node(root_addr)?; @@ -453,6 +457,10 @@ impl NodeStore { /// Reads a node for update, marking it as deleted in this proposal. /// We get an arc from cache (reading it from disk if necessary) then /// copy/clone the node and return it. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. pub fn read_for_update(&mut self, addr: LinearAddress) -> Result { self.delete_node(addr); let arc_wrapped_node = self.read_node(addr)?; @@ -468,6 +476,10 @@ impl NodeStore { impl NodeStore { /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. /// This is used during testing and during the creation of an in-memory merkle for proofs + /// + /// # Panics + /// + /// Panics if the header cannot be written. pub fn new_empty_proposal(storage: Arc) -> Self { let header = NodeStoreHeader::new(); let header_bytes = bytemuck::bytes_of(&header); @@ -478,7 +490,7 @@ impl NodeStore { header, kind: MutableProposal { root: None, - deleted: Default::default(), + deleted: Vec::default(), parent: NodeStoreParent::Committed(None), }, storage, @@ -492,6 +504,7 @@ impl NodeStore, S> { /// and the index of the free list that was used. /// If there are no free areas big enough for `n` bytes, returns None. /// TODO danlaine: If we return a larger area than requested, we should split it. + #[expect(clippy::indexing_slicing)] fn allocate_from_freed( &mut self, n: u64, @@ -546,10 +559,10 @@ impl NodeStore, S> { *free_stored_area_addr = free_head.next_free_block; } - counter!("firewood.space.reused", "index" => index_name(index as u8)) + counter!("firewood.space.reused", "index" => index_name(index)) .increment(AREA_SIZES[index]); - counter!("firewood.space.wasted", "index" => index_name(index as u8)) - .increment(AREA_SIZES[index] - n); + counter!("firewood.space.wasted", "index" => index_name(index)) + .increment(AREA_SIZES[index].saturating_sub(n)); // Return the address of the newly allocated block. trace!("Allocating from free list: addr: {address:?}, size: {index}"); @@ -557,11 +570,12 @@ impl NodeStore, S> { } trace!("No free blocks of sufficient size {index_wanted} found"); - counter!("firewood.space.from_end", "index" => index_name(index_wanted as u8)) + counter!("firewood.space.from_end", "index" => index_name(index_wanted as usize)) .increment(AREA_SIZES[index_wanted as usize]); Ok(None) } + #[expect(clippy::indexing_slicing)] fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { let index = area_size_to_index(n).map_err(|e| { self.storage @@ -569,7 +583,7 @@ impl NodeStore, S> { })?; let area_size = AREA_SIZES[index as usize]; let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); - self.header.size += area_size; + self.header.size = self.header.size.saturating_add(area_size); debug_assert!(addr.get() % 8 == 0); trace!("Allocating from end: addr: {addr:?}, size: {index}"); Ok((addr, index)) @@ -585,6 +599,10 @@ impl NodeStore, S> { /// Returns an address that can be used to store the given `node` and updates /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. /// Also returns the index of the free list the node was allocated from. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be allocated. pub fn allocate_node( &mut self, node: &Node, @@ -607,13 +625,19 @@ impl NodeStore { /// Deletes the [Node] at the given address, updating the next pointer at /// the given addr, and changing the header of this committed nodestore to /// have the address on the freelist + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be deleted. + #[expect(clippy::indexing_slicing)] pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), FileIoError> { debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {area_size_index}"); - counter!("firewood.delete_node", "index" => index_name(area_size_index)).increment(1); - counter!("firewood.space.freed", "index" => index_name(area_size_index)) + counter!("firewood.delete_node", "index" => index_name(area_size_index as usize)) + .increment(1); + counter!("firewood.space.freed", "index" => index_name(area_size_index as usize)) .increment(AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. @@ -766,7 +790,10 @@ impl Version { // pad with nul bytes let mut bytes = [0u8; Version::SIZE]; - bytes[..VERSION_STR.len()].copy_from_slice(VERSION_STR.as_bytes()); + bytes + .get_mut(..VERSION_STR.len()) + .expect("must fit") + .copy_from_slice(VERSION_STR.as_bytes()); Self { bytes } } @@ -813,7 +840,10 @@ impl NodeStoreHeader { root_address: None, version: Version::new(), free_lists: Default::default(), - area_size_hash: area_size_hash().as_slice().try_into().unwrap(), + area_size_hash: area_size_hash() + .as_slice() + .try_into() + .expect("sizes should match"), #[cfg(feature = "ethhash")] ethhash: 1, #[cfg(not(feature = "ethhash"))] @@ -907,6 +937,10 @@ impl TrieReader for T where T: NodeReader + RootReader {} /// Reads nodes from a merkle trie. pub trait NodeReader { /// Returns the node at `addr`. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. fn read_node(&self, addr: LinearAddress) -> Result; } @@ -984,6 +1018,7 @@ pub struct ImmutableProposal { impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. #[must_use] + #[allow(clippy::needless_pass_by_value)] fn parent_hash_is(&self, hash: Option) -> bool { match > as arc_swap::access::DynAccess>>::load( &self.parent, @@ -991,7 +1026,7 @@ impl ImmutableProposal { .as_ref() { NodeStoreParent::Committed(root_hash) => *root_hash == hash, - _ => false, + NodeStoreParent::Proposed(_) => false, } } } @@ -1093,7 +1128,7 @@ impl, S: ReadableStorage> From NodeStore, S> { if let Node::Branch(ref mut b) = node { // special case code for ethereum hashes at the account level #[cfg(feature = "ethhash")] - let make_fake_root = if path_prefix.0.len() + b.partial_path.0.len() == 64 { + let make_fake_root = if path_prefix.0.len().saturating_add(b.partial_path.0.len()) == 64 + { // looks like we're at an account branch // tally up how many hashes we need to deal with let (unhashed, mut hashed) = b.children.iter_mut().enumerate().fold( @@ -1256,6 +1292,10 @@ impl NodeStore, S> { impl NodeStore { /// Persist the header from this proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be written. pub fn flush_header(&self) -> Result<(), FileIoError> { let header_bytes = bytemuck::bytes_of(&self.header); self.storage.write(0, header_bytes)?; @@ -1264,6 +1304,10 @@ impl NodeStore { /// Persist the header, including all the padding /// This is only done the first time we write the header + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be written. pub fn flush_header_with_padding(&self) -> Result<(), FileIoError> { let header_bytes = bytemuck::bytes_of(&self.header) .iter() @@ -1583,6 +1627,10 @@ where impl NodeStore { /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if a node cannot be deleted. pub fn reap_deleted( mut self, proposal: &mut NodeStore, @@ -1609,6 +1657,7 @@ impl NodeStore { } #[cfg(test)] +#[expect(clippy::unwrap_used, clippy::indexing_slicing)] pub(crate) mod nodestore_test_utils { use super::*; @@ -1644,6 +1693,7 @@ pub(crate) mod nodestore_test_utils { #[cfg(test)] #[expect(clippy::unwrap_used)] +#[expect(clippy::cast_possible_truncation)] mod tests { use std::array::from_fn; @@ -1692,13 +1742,13 @@ mod tests { .into(); // create an empty r1, check that it's parent is the empty committed version - let r1 = NodeStore::new(base).unwrap(); + let r1 = NodeStore::new(&base).unwrap(); let r1: Arc, _>> = Arc::new(r1.try_into().unwrap()); let parent: DynGuard> = r1.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Committed(None))); // create an empty r2, check that it's parent is the proposed version r1 - let r2: NodeStore = NodeStore::new(r1.clone()).unwrap(); + let r2: NodeStore = NodeStore::new(&r1.clone()).unwrap(); let r2: Arc, _>> = Arc::new(r2.try_into().unwrap()); let parent: DynGuard> = r2.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Proposed(_))); From 915c7d810ee7aa5354e5aa0074a3d91e207b6721 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:13:34 -0400 Subject: [PATCH 0792/1053] feat: enable a configurable free list cache in the FFI (#1017) This allows a configurable free list cache for all consumers of the FFI --- ffi/firewood.go | 34 ++++++++++++++++++++-------------- ffi/firewood.h | 10 ++++++++-- ffi/src/lib.rs | 17 +++++++++++++++-- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 00556e28edf6..929eb2006788 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -54,20 +54,22 @@ type Database struct { // Config configures the opening of a [Database]. type Config struct { - Create bool - NodeCacheEntries uint - Revisions uint - ReadCacheStrategy CacheStrategy - MetricsPort uint16 + Create bool + NodeCacheEntries uint + FreeListCacheEntries uint + Revisions uint + ReadCacheStrategy CacheStrategy + MetricsPort uint16 } // DefaultConfig returns a sensible default Config. func DefaultConfig() *Config { return &Config{ - NodeCacheEntries: 1_000_000, - Revisions: 100, - ReadCacheStrategy: OnlyCacheWrites, - MetricsPort: 3000, + NodeCacheEntries: 1_000_000, + FreeListCacheEntries: 40_000, + Revisions: 100, + ReadCacheStrategy: OnlyCacheWrites, + MetricsPort: 3000, } } @@ -99,13 +101,17 @@ func New(filePath string, conf *Config) (*Database, error) { if conf.NodeCacheEntries < 1 { return nil, fmt.Errorf("%T.NodeCacheEntries must be >= 1", conf) } + if conf.FreeListCacheEntries < 1 { + return nil, fmt.Errorf("%T.FreeListCacheEntries must be >= 1", conf) + } args := C.struct_CreateOrOpenArgs{ - path: C.CString(filePath), - cache_size: C.size_t(conf.NodeCacheEntries), - revisions: C.size_t(conf.Revisions), - strategy: C.uint8_t(conf.ReadCacheStrategy), - metrics_port: C.uint16_t(conf.MetricsPort), + path: C.CString(filePath), + cache_size: C.size_t(conf.NodeCacheEntries), + free_list_cache_size: C.size_t(conf.FreeListCacheEntries), + revisions: C.size_t(conf.Revisions), + strategy: C.uint8_t(conf.ReadCacheStrategy), + metrics_port: C.uint16_t(conf.MetricsPort), } // Defer freeing the C string allocated to the heap on the other side // of the FFI boundary. diff --git a/ffi/firewood.h b/ffi/firewood.h index 17aae123d3e7..b3c16d1d767f 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -54,12 +54,18 @@ typedef struct DatabaseCreationResult { * * * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` * otherwise should exist if passed to `fwd_open_db()`. - * * `cache_size` - The size of the node cache, panics if <= 0 - * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 + * * `cache_size` - The size of the node cache, returns an error if <= 0 + * * `free_list_cache_size` - The size of the free list cache, returns an error if <= 0 + * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. + * * `strategy` - The cache read strategy to use, 0 for writes only, + * 1 for branch reads, and 2 for all reads. + * Returns an error if the value is not 0, 1, or 2. + * * `metrics_port` - The port to use for metrics, 0 to disable metrics. */ typedef struct CreateOrOpenArgs { const char *path; size_t cache_size; + size_t free_list_cache_size; size_t revisions; uint8_t strategy; uint16_t metrics_port; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 04649c68a43f..74df22e51524 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -827,12 +827,18 @@ pub unsafe extern "C" fn fwd_free_database_error_result( /// /// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` /// otherwise should exist if passed to `fwd_open_db()`. -/// * `cache_size` - The size of the node cache, panics if <= 0 -/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2 +/// * `cache_size` - The size of the node cache, returns an error if <= 0 +/// * `free_list_cache_size` - The size of the free list cache, returns an error if <= 0 +/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. +/// * `strategy` - The cache read strategy to use, 0 for writes only, +/// 1 for branch reads, and 2 for all reads. +/// Returns an error if the value is not 0, 1, or 2. +/// * `metrics_port` - The port to use for metrics, 0 to disable metrics. #[repr(C)] pub struct CreateOrOpenArgs { path: *const std::ffi::c_char, cache_size: usize, + free_list_cache_size: usize, revisions: usize, strategy: u8, metrics_port: u16, @@ -890,6 +896,7 @@ unsafe fn common_create(args: &CreateOrOpenArgs, create_file: bool) -> Result Result Result { @@ -929,6 +937,11 @@ fn manager_config( ) .max_revisions(revisions) .cache_read_strategy(cache_read_strategy) + .free_list_cache_size( + free_list_cache_size + .try_into() + .map_err(|_| "free list cache size should be non-zero")?, + ) .build(); Ok(config) } From 4eb6cfbd84b691594fd118f638e6a81fdf11b266 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 2 Jul 2025 13:45:02 -0700 Subject: [PATCH 0793/1053] chore: allow some extra pedantic warnings (#1014) `needless_pass_by_value`, `unused_async`, `unnecessary_wraps`, and `unused_self` are all allowed across the workspace. They are a touch too pedantic and encourage some bad backwards incompatible changes. --- Cargo.toml | 8 +++++++- firewood/src/db.rs | 8 -------- firewood/src/manager.rs | 15 --------------- firewood/src/merkle.rs | 4 ---- firewood/src/stream.rs | 4 ---- fwdctl/src/check.rs | 1 - fwdctl/src/dump.rs | 8 -------- storage/src/node/mod.rs | 2 +- storage/src/node/path.rs | 2 -- storage/src/nodestore.rs | 1 - 10 files changed, 8 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de40473890f9..4af7df2dbbe0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,10 +30,16 @@ missing_const_for_fn = "warn" arithmetic_side_effects = "warn" # lower the priority of pedantic to allow overriding the lints it includes pedantic = { level = "warn", priority = -1 } +# These lints are from pedantic but allowed. They are a bit too pedantic and +# encourage making backwards incompatible changes. +needless_pass_by_value = "allow" +unused_async = "allow" +unnecessary_wraps = "allow" +unused_self = "allow" # Ignore interger casts. This is to avoid unnecessary `try_into` calls for usize # to u64 and vice versa and should be re-enabled if/when clippy has a separate # lint for usize vs non-usize truncation. -cast_possible_truncation = { level = "allow" } +cast_possible_truncation = "allow" [workspace.dependencies] metrics = "0.24.2" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c3dcfeb10713..82fda54bead6 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -9,14 +9,6 @@ clippy::missing_panics_doc, reason = "Found 5 occurrences after enabling the lint." )] -#![expect( - clippy::needless_pass_by_value, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::unused_async, - reason = "Found 2 occurrences after enabling the lint." -)] use crate::merkle::Merkle; use crate::proof::{Proof, ProofNode}; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 21a3305cc9f1..239f14132732 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::unnecessary_wraps, - reason = "Found 2 occurrences after enabling the lint." -)] #![cfg_attr( feature = "ethhash", expect( @@ -12,13 +8,6 @@ reason = "Found 1 occurrences after enabling the lint." ) )] -#![cfg_attr( - not(feature = "ethhash"), - expect( - clippy::unused_self, - reason = "Found 1 occurrences after enabling the lint." - ) -)] #![expect( clippy::cast_precision_loss, reason = "Found 2 occurrences after enabling the lint." @@ -27,10 +16,6 @@ clippy::default_trait_access, reason = "Found 3 occurrences after enabling the lint." )] -#![expect( - clippy::needless_pass_by_value, - reason = "Found 1 occurrences after enabling the lint." -)] use std::collections::{HashMap, VecDeque}; use std::num::NonZero; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 8a716e9aa507..78772d87cc39 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1068,10 +1068,6 @@ mod tests { clippy::items_after_statements, reason = "Found 1 occurrences after enabling the lint." )] - #![expect( - clippy::unnecessary_wraps, - reason = "Found 1 occurrences after enabling the lint." - )] use super::*; use firewood_storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 308a4d2e2f32..41dbce3da35c 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::unnecessary_wraps, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::used_underscore_binding, reason = "Found 3 occurrences after enabling the lint." diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index e3648d212eb7..e92601bc50c8 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -23,7 +23,6 @@ pub struct Options { pub db: String, } -#[allow(clippy::unused_async)] pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db_path = PathBuf::from(&opts.db); let node_cache_size = nonzero!(1usize); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index f7cdf6771dd3..baeb839ab302 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -5,14 +5,6 @@ clippy::doc_link_with_quotes, reason = "Found 1 occurrences after enabling the lint." )] -#![expect( - clippy::unnecessary_wraps, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::unused_async, - reason = "Found 1 occurrences after enabling the lint." -)] use clap::Args; use firewood::db::{Db, DbConfig}; diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index f64b1f892ea4..c4c9c361d1ad 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -474,7 +474,7 @@ pub struct PathIterItem { #[cfg(test)] mod test { - #![expect(clippy::needless_pass_by_value, clippy::unwrap_used)] + #![expect(clippy::unwrap_used)] use crate::node::{BranchNode, LeafNode, Node}; use crate::{Child, LinearAddress, Path}; diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 1c5786254f07..b2f778182433 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -256,8 +256,6 @@ impl DoubleEndedIterator for NibblesIterator<'_> { #[cfg(test)] mod test { - #![expect(clippy::needless_pass_by_value)] - use super::*; use std::fmt::Debug; use test_case::test_case; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index ac92db5ef7bf..ea35e6e589fe 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1018,7 +1018,6 @@ pub struct ImmutableProposal { impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. #[must_use] - #[allow(clippy::needless_pass_by_value)] fn parent_hash_is(&self, hash: Option) -> bool { match > as arc_swap::access::DynAccess>>::load( &self.parent, From fddaed63ea8d55f9db32315401d0fc0f01d97e6d Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 2 Jul 2025 14:25:55 -0700 Subject: [PATCH 0794/1053] fix: publish firewood-macros (#1019) The macros crate was not being published due to the missing stanza. Need to figure out a way to make this automatic in the future. I also added a note about how the crates in the publish workflow must be sorted in reverse topological order so that the dependencies are published before the packages that depend on them. --- .github/workflows/publish.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index bec3579a526a..08be902a3182 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -13,16 +13,23 @@ jobs: steps: - uses: actions/checkout@v1 - uses: dtolnay/rust-toolchain@stable - - name: publish firewood-storage crate + ## NOTE: keep these packages sorted in reverse topological order! + ## cargo tree --workspace -e all | grep firewood + - name: publish firewood-macros crate continue-on-error: false run: | cargo login ${{ secrets.CARGO_TOKEN }} - cargo publish -p firewood-storage + cargo publish -p firewood-macros - name: publish firewood-triehash crate continue-on-error: false run: | cargo login ${{ secrets.CARGO_TOKEN }} cargo publish -p firewood-triehash + - name: publish firewood-storage crate + continue-on-error: false + run: | + cargo login ${{ secrets.CARGO_TOKEN }} + cargo publish -p firewood-storage - name: publish firewood crate continue-on-error: false run: | From 4fa0d4acc76e6bfb781c227f66f45e9062c8b993 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 06:09:09 -0700 Subject: [PATCH 0795/1053] build(deps): update lru requirement from 0.15.0 to 0.16.0 (#1023) Updates the requirements on [lru](https://github.com/jeromefroe/lru-rs) to permit the latest version.
    Changelog

    Sourced from lru's changelog.

    v0.16.0 - 2025-07-02

    • Implement Clone for caches with custom hashers.

    v0.15.0 - 2025-06-26

    • Return bool from promote and demote to indicate whether key was found.

    v0.14.0 - 2025-04-12

    • Use NonZeroUsize::MAX instead of unwrap(), and update MSRV to 1.70.0.

    v0.13.0 - 2025-01-27

    • Add peek_mru and pop_mru methods, upgrade dependency on hashbrown to 0.15.2, and update MSRV to 1.65.0.

    v0.12.5 - 2024-10-30

    • Upgrade hashbrown dependency to 0.15.

    v0.12.4 - 2024-07-30

    • Add methods that take a reference to the key that should be inserted.

    v0.12.3 - 2024-02-24

    • Add get_key_value_mut method.

    v0.12.2 - 2024-01-28

    • Add clone method.

    v0.12.1 - 2023-11-21

    • Add get_key_value method.

    v0.12.0 - 2023-10-03

    • Add lifetime specifier to try_get_or_insert_mut.
    • Add BuildHasher trait bound to Debug for LruCache.

    v0.11.1 - 2023-09-05

    • Add try_get_or_insert_mut method.

    v0.11.0 - 2023-07-11

    • Update dependency on hashbrown to 0.14 and update MSRV to 1.64.0.

    v0.10.1 - 2023-06-29

    ... (truncated)

    Commits

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2f69ca288e68..cbf253e7452c 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -25,7 +25,7 @@ smallvec = { workspace = true, features = ["serde", "write", "union"] } sha2 = { workspace = true } integer-encoding = { workspace = true } arc-swap = "1.7.1" -lru = "0.15.0" +lru = "0.16.0" metrics = { workspace = true } log = { version = "0.4.20", optional = true } bytemuck = "1.7.0" From 02a913b0a9e6185fc078670a30c4aea4602161e1 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:36:29 -0400 Subject: [PATCH 0796/1053] feat(nodestore): add functionalities to iterate the free list (#1015) --- storage/src/checker.rs | 10 +- storage/src/lib.rs | 21 +++++ storage/src/nodestore.rs | 196 ++++++++++++++++++++++++++++++++++----- 3 files changed, 198 insertions(+), 29 deletions(-) diff --git a/storage/src/checker.rs b/storage/src/checker.rs index 3bda5849853f..3d46284686ba 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -75,7 +75,7 @@ mod test { use super::*; use crate::linear::memory::MemStore; use crate::nodestore::NodeStoreHeader; - use crate::nodestore::nodestore_test_utils::{write_header, write_new_node}; + use crate::nodestore::nodestore_test_utils::{test_write_header, test_write_new_node}; use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; #[test] @@ -106,7 +106,7 @@ mod test { value: Box::new([3, 4, 5]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); - let leaf_area = write_new_node(&nodestore, &leaf, high_watermark); + let leaf_area = test_write_new_node(&nodestore, &leaf, high_watermark); high_watermark += leaf_area; let mut branch_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); @@ -117,7 +117,7 @@ mod test { children: branch_children, })); let branch_addr = LinearAddress::new(high_watermark).unwrap(); - let branch_area = write_new_node(&nodestore, &branch, high_watermark); + let branch_area = test_write_new_node(&nodestore, &branch, high_watermark); high_watermark += branch_area; let mut root_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); @@ -128,11 +128,11 @@ mod test { children: root_children, })); let root_addr = LinearAddress::new(high_watermark).unwrap(); - let root_area = write_new_node(&nodestore, &root, high_watermark); + let root_area = test_write_new_node(&nodestore, &root, high_watermark); high_watermark += root_area; // write the header - write_header(&nodestore, root_addr, high_watermark); + test_write_header(&nodestore, root_addr, high_watermark); // verify that all of the space is accounted for - since there is no free area let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 033933b7b6e6..d208a81dd3b7 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -133,3 +133,24 @@ pub enum CheckerError { #[error("IO error")] IO(#[from] FileIoError), } + +#[cfg(test)] +mod test_utils { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng, rng}; + + pub fn seeded_rng() -> StdRng { + let seed = std::env::var("FIREWOOD_STORAGE_TEST_SEED") + .ok() + .map_or_else( + || rng().random(), + |s| { + str::parse(&s) + .expect("couldn't parse FIREWOOD_STORAGE_TEST_SEED; must be a u64") + }, + ); + + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_STORAGE_TEST_SEED={seed}"); + StdRng::seed_from_u64(seed) + } +} diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index ea35e6e589fe..b56c78529757 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -39,6 +39,7 @@ use std::fmt::Debug; /// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF /// ``` use std::io::{Error, ErrorKind}; +use std::iter::FusedIterator; use std::mem::{offset_of, take}; use std::num::NonZeroU64; use std::ops::Deref; @@ -531,28 +532,8 @@ impl NodeStore, S> { trace!("free_head@{address}(cached): {free_head:?} size:{index}"); *free_stored_area_addr = free_head; } else { - let free_area_addr = address.get(); - let free_head_stream = self.storage.stream_from(free_area_addr)?; - let free_head: StoredArea> = serializer() - .deserialize_from(free_head_stream) - .map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - free_area_addr, - Some("allocate_from_freed".to_string()), - ) - })?; - let StoredArea { - area: Area::Free(free_head), - area_size_index: read_index, - } = free_head - else { - return Err(self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), - free_area_addr, - Some("allocate_from_freed".to_string()), - )); - }; + let (free_head, read_index) = + FreeArea::from_storage(self.storage.as_ref(), address)?; debug_assert_eq!(read_index as usize, index); // Update the free list to point to the next free block. @@ -921,6 +902,38 @@ struct FreeArea { next_free_block: Option, } +impl FreeArea { + pub(crate) fn from_storage( + storage: &S, + address: LinearAddress, + ) -> Result<(Self, AreaIndex), FileIoError> { + let free_area_addr = address.get(); + let stored_area_stream = storage.stream_from(free_area_addr)?; + let stored_area: StoredArea> = serializer() + .deserialize_from(stored_area_stream) + .map_err(|e| { + storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + free_area_addr, + Some("FreeArea::from_storage".to_string()), + ) + })?; + let StoredArea { + area: Area::Free(free_area), + area_size_index: stored_area_index, + } = stored_area + else { + return Err(storage.file_io_error( + Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), + free_area_addr, + Some("FreeArea::from_storage".to_string()), + )); + }; + + Ok((free_area, stored_area_index as AreaIndex)) + } +} + /// Reads from an immutable (i.e. already hashed) merkle trie. pub trait HashedNodeReader: TrieReader { /// Gets the address of the root node of an immutable merkle trie. @@ -1655,13 +1668,79 @@ impl NodeStore { } } +pub(crate) struct FreeListIterator<'a, S: ReadableStorage> { + storage: &'a S, + next_addr: Option, +} + +impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { + pub(crate) const fn new(storage: &'a S, next_addr: Option) -> Self { + Self { storage, next_addr } + } +} + +impl Iterator for FreeListIterator<'_, S> { + type Item = Result<(LinearAddress, AreaIndex), FileIoError>; + + fn next(&mut self) -> Option { + let next_addr = self.next_addr?; + + // read the free area, propagate any IO error if it occurs + let (free_area, stored_area_index) = match FreeArea::from_storage(self.storage, next_addr) { + Ok(free_area) => free_area, + Err(e) => { + // if the read fails, we cannot proceed with the current freelist + self.next_addr = None; + return Some(Err(e)); + } + }; + + // update the next address to the next free block + self.next_addr = free_area.next_free_block; + Some(Ok((next_addr, stored_area_index))) + } +} + +impl FusedIterator for FreeListIterator<'_, S> {} + +#[allow(dead_code)] // TODO: free list iterators will be used in the checker +impl NodeStore { + // Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. + // The iterator returns a tuple of the address and the area index of the free area. + pub(crate) fn free_list_iter( + &self, + start_area_index: AreaIndex, + ) -> impl Iterator> { + self.free_list_iter_inner(start_area_index) + .map(|item| item.map(|(addr, area_index, _)| (addr, area_index))) + } + + // pub(crate) since checker will use this to verify that the free areas are in the correct free list + // Return free_list_id as usize instead of AreaIndex to avoid type conversion + pub(crate) fn free_list_iter_inner( + &self, + start_area_index: AreaIndex, + ) -> impl Iterator> { + self.header + .free_lists + .iter() + .enumerate() + .skip(start_area_index as usize) + .flat_map(move |(free_list_id, next_addr)| { + FreeListIterator::new(self.storage.as_ref(), *next_addr).map(move |item| { + item.map(|(addr, area_index)| (addr, area_index, free_list_id)) + }) + }) + } +} + #[cfg(test)] #[expect(clippy::unwrap_used, clippy::indexing_slicing)] pub(crate) mod nodestore_test_utils { use super::*; // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. - pub(crate) fn write_new_node( + pub(crate) fn test_write_new_node( nodestore: &NodeStore, node: &Node, offset: u64, @@ -1677,7 +1756,22 @@ pub(crate) mod nodestore_test_utils { AREA_SIZES[area_size_index as usize] } - pub(crate) fn write_header( + pub(crate) fn test_write_free_area( + nodestore: &NodeStore, + next_free_block: Option, + area_size_index: AreaIndex, + offset: u64, + ) { + let area: Area = Area::Free(FreeArea { next_free_block }); + let stored_area = StoredArea { + area_size_index, + area, + }; + let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); + nodestore.storage.write(offset, &stored_area_bytes).unwrap(); + } + + pub(crate) fn test_write_header( nodestore: &NodeStore, root_addr: LinearAddress, size: u64, @@ -1847,3 +1941,57 @@ mod tests { println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal } } + +#[cfg(test)] +#[expect(clippy::unwrap_used, clippy::indexing_slicing)] +mod test_free_list_iterator { + use super::nodestore_test_utils::*; + use super::*; + use crate::linear::memory::MemStore; + use crate::test_utils::seeded_rng; + + use rand::Rng; + use rand::seq::IteratorRandom; + + #[test] + fn free_list_iterator() { + let mut rng = seeded_rng(); + let memstore = MemStore::new(vec![]); + let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let area_index = rng.random_range(0..NUM_AREA_SIZES as u8); + let area_size = AREA_SIZES[area_index as usize]; + + // create a random free list scattered across the storage + let offsets = (1..100u64) + .map(|i| i * area_size) + .choose_multiple(&mut rng, 10); + for (cur, next) in offsets.iter().zip(offsets.iter().skip(1)) { + test_write_free_area( + &nodestore, + Some(LinearAddress::new(*next).unwrap()), + area_index, + *cur, + ); + } + test_write_free_area(&nodestore, None, area_index, *offsets.last().unwrap()); + + // test iterator from a random starting point + let skip = rng.random_range(0..offsets.len()); + let mut iterator = offsets.into_iter().skip(skip); + let start = iterator.next().unwrap(); + let mut free_list_iter = + FreeListIterator::new(nodestore.storage.as_ref(), LinearAddress::new(start)); + assert_eq!( + free_list_iter.next().unwrap().unwrap(), + (LinearAddress::new(start).unwrap(), area_index) + ); + + for offset in iterator { + let next_item = free_list_iter.next().unwrap().unwrap(); + assert_eq!(next_item, (LinearAddress::new(offset).unwrap(), area_index)); + } + + assert!(free_list_iter.next().is_none()); + } +} From 158a2d41497ea03945d8d10fd744684f897b6f9d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Jul 2025 09:13:46 -0700 Subject: [PATCH 0797/1053] build(deps): Upgrade sha2, tokio, clap, fastrace, serde... (#1025) ``` +---------------+-------------+-------------+ | Package | From | To | +---------------+-------------+-------------+ | sha2 | 0.10.8 | 0.10.9 | | tokio | 1.36.0 | 1.46.0 | | clap | 4.5.0 | 4.5.40 | | fastrace | 0.7.4 | 0.7.14 | | serde | 1.0.199 | 1.0.219 | | thiserror | 2.0.3 | 2.0.12 | | integer-enc. | 4.0.0 | 4.0.2 | | env_logger | 0.11.7 | 0.11.8 | | smallvec | 1.6.1 | 1.15.1 | | tempfile | 3.12.0 | 3.20.0 | +---------------+-------------+-------------+ ``` --- Cargo.toml | 20 ++++++++++---------- benchmark/Cargo.toml | 8 ++++---- ffi/Cargo.toml | 6 +++--- firewood/Cargo.toml | 12 ++++++------ fwdctl/Cargo.toml | 12 ++++++------ storage/Cargo.toml | 14 +++++++------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4af7df2dbbe0..d738583739fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,22 +44,22 @@ cast_possible_truncation = "allow" [workspace.dependencies] metrics = "0.24.2" metrics-util = "0.20.0" -sha2 = "0.10.8" -tokio = "1.36.0" -clap = { version = "4.5.0", features = ["derive"] } -fastrace = "0.7.4" +sha2 = "0.10.9" +tokio = "1.46.0" +clap = { version = "4.5.40", features = ["derive"] } +fastrace = "0.7.14" bincode = "1.3.3" -serde = "1.0.199" -thiserror = "2.0.3" +serde = "1.0.219" +thiserror = "2.0.12" coarsetime = "0.1.36" -integer-encoding = "4.0.0" -env_logger = "0.11.7" -smallvec = "1.6.1" +integer-encoding = "4.0.2" +env_logger = "0.11.8" +smallvec = "1.15.1" # dev dependencies rand = "0.9.1" criterion = "0.6.0" pprof = "0.15.0" -tempfile = "3.12.0" +tempfile = "3.20.0" ethereum-types = "0.15.1" hex-literal = "1.0.0" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 4fc619eefac5..9e53272edb7f 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -25,17 +25,17 @@ metrics = { workspace = true } metrics-util = { workspace = true } metrics-exporter-prometheus = "0.17.2" tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } -rand = "0.9.0" -rand_distr = "0.5.0" +rand = "0.9.1" +rand_distr = "0.5.1" pretty-duration = "0.1.1" env_logger = { workspace = true } -log = "0.4.20" +log = "0.4.27" fastrace = { workspace = true, features = ["enable"] } fastrace-opentelemetry = { version = "0.12.0" } opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry = "0.30.0" opentelemetry_sdk = "0.30.0" -strum = "0.27.0" +strum = "0.27.1" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 26027c8b4534..777c58873983 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -19,12 +19,12 @@ repository = "https://github.com/ava-labs/firewood" crate-type = ["staticlib"] [dependencies] -libc = "0.2.2" +libc = "0.2.174" firewood = { version = "0.0.7", path = "../firewood" } metrics = { workspace = true } metrics-util = { workspace = true } -chrono = "0.4.39" -oxhttp = "0.3.0" +chrono = "0.4.41" +oxhttp = "0.3.1" coarsetime = { workspace = true } env_logger = { workspace = true, optional = true} diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d6fc6b29cd7f..38cb364b50d6 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -23,8 +23,8 @@ readme = "../README.md" [dependencies] aquamarine = "0.6.0" -async-trait = "0.1.77" -futures = "0.3.30" +async-trait = "0.1.88" +futures = "0.3.31" hex = "0.4.3" metrics = { workspace = true } serde = { workspace = true } @@ -51,16 +51,16 @@ ethhash = [ "firewood-storage/ethhash" ] firewood-triehash = { version = "0.0.7", path = "../triehash" } criterion = { workspace = true, features = ["async_tokio"] } rand = { workspace = true } -rand_distr = "0.5.0" -clap = { version = "4.5.0", features = ['derive'] } +rand_distr = "0.5.1" +clap = { version = "4.5.40", features = ['derive'] } pprof = { workspace = true, features = ["flamegraph"] } tempfile = { workspace = true } -tokio = { version = "1.36.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } +tokio = { version = "1.46.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } ethereum-types = { workspace = true } sha3 = "0.10.8" plain_hasher = "0.2.3" hex-literal = { workspace = true } -env_logger = "0.11.7" +env_logger = "0.11.8" hash-db = "0.16.0" [[bench]] diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index e2df0d035e26..0386f0901115 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -27,18 +27,18 @@ firewood = { version = "0.0.7", path = "../firewood" } firewood-storage = { version = "0.0.7", path = "../storage" } clap = { workspace = true, features = ["cargo"] } env_logger = { workspace = true } -log = "0.4.20" +log = "0.4.27" tokio = { workspace = true, features = ["full"] } -futures-util = "0.3.30" +futures-util = "0.3.31" hex = "0.4.3" csv = "1.3.1" nonzero_ext = "0.3.0" [dev-dependencies] -anyhow = "1.0.79" -assert_cmd = "2.0.13" -predicates = "3.1.0" -serial_test = "3.0.0" +anyhow = "1.0.98" +assert_cmd = "2.0.17" +predicates = "3.1.3" +serial_test = "3.2.0" rand = { workspace = true } [lints] diff --git a/storage/Cargo.toml b/storage/Cargo.toml index cbf253e7452c..6b200d9c1411 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -17,8 +17,8 @@ repository = "https://github.com/ava-labs/firewood" [dependencies] bincode = { workspace = true } -bitflags = "2.5.0" -enum-as-inner = "0.6.0" +bitflags = "2.9.1" +enum-as-inner = "0.6.1" hex = "0.4.3" serde = { workspace = true, features = ["derive"] } smallvec = { workspace = true, features = ["serde", "write", "union"] } @@ -27,12 +27,12 @@ integer-encoding = { workspace = true } arc-swap = "1.7.1" lru = "0.16.0" metrics = { workspace = true } -log = { version = "0.4.20", optional = true } -bytemuck = "1.7.0" -bytemuck_derive = "1.7.0" -bitfield = "0.19.0" +log = { version = "0.4.27", optional = true } +bytemuck = "1.23.1" +bytemuck_derive = "1.9.3" +bitfield = "0.19.1" fastrace = { workspace = true } -io-uring = { version = "0.7.4", optional = true } +io-uring = { version = "0.7.8", optional = true } triomphe = "0.1.14" coarsetime = { workspace = true } rlp = { version = "0.6.1", optional = true } From c96fb182084481555298f586ce510cd983c58833 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Jul 2025 10:11:12 -0700 Subject: [PATCH 0798/1053] perf: Break up the RevisionManager lock (#1027) Historical and by_hash members might get some benefit from RwLock, but proposals are small enough and locked less frequently so a mutex is a better choice. Fearless concurrency. --- firewood/src/db.rs | 94 +++++++++-------------------------------- firewood/src/manager.rs | 74 +++++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 95 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 82fda54bead6..15cc47632f89 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -5,10 +5,6 @@ clippy::missing_errors_doc, reason = "Found 12 occurrences after enabling the lint." )] -#![expect( - clippy::missing_panics_doc, - reason = "Found 5 occurrences after enabling the lint." -)] use crate::merkle::Merkle; use crate::proof::{Proof, ProofNode}; @@ -25,8 +21,8 @@ use firewood_storage::{ use metrics::{counter, describe_counter}; use std::io::Write; use std::path::Path; +use std::sync::Arc; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, RwLock}; use thiserror::Error; use typed_builder::TypedBuilder; @@ -152,9 +148,7 @@ pub struct DbConfig { /// A database instance. pub struct Db { metrics: Arc, - // TODO: consider using https://docs.rs/lock_api/latest/lock_api/struct.RwLock.html#method.upgradable_read - // TODO: This should probably use an async RwLock - manager: RwLock, + manager: RevisionManager, } #[async_trait] @@ -170,11 +164,7 @@ where Self: 'p; async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { - let nodestore = self - .manager - .read() - .expect("poisoned lock") - .revision(root_hash)?; + let nodestore = self.manager.revision(root_hash)?; Ok(nodestore) } @@ -183,7 +173,7 @@ where } async fn all_hashes(&self) -> Result, api::Error> { - Ok(self.manager.read().expect("poisoned lock").all_hashes()) + Ok(self.manager.all_hashes()) } #[fastrace::trace(short_name = true)] @@ -194,11 +184,7 @@ where where Self: 'p, { - let parent = self - .manager - .read() - .expect("poisoned lock") - .current_revision(); + let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); let span = fastrace::Span::enter_with_local_parent("merkleops"); @@ -224,10 +210,7 @@ where Arc::new(nodestore.try_into()?); drop(span); - self.manager - .write() - .expect("poisoned lock") - .add_proposal(immutable.clone()); + self.manager.add_proposal(immutable.clone()); self.metrics.proposals.increment(1); @@ -252,10 +235,7 @@ impl Db { cfg.truncate, cfg.manager.clone(), )?; - let db = Self { - metrics, - manager: manager.into(), - }; + let db = Self { metrics, manager }; Ok(db) } @@ -270,16 +250,13 @@ impl Db { cfg.truncate, cfg.manager.clone(), )?; - let db = Self { - metrics, - manager: manager.into(), - }; + let db = Self { metrics, manager }; Ok(db) } /// Synchronously get the root hash of the latest revision. pub fn root_hash_sync(&self) -> Result, api::Error> { - let hash = self.manager.read().expect("poisoned lock").root_hash()?; + let hash = self.manager.root_hash()?; #[cfg(not(feature = "ethhash"))] return Ok(hash); #[cfg(feature = "ethhash")] @@ -288,21 +265,13 @@ impl Db { /// Synchronously get a revision from a root hash pub fn revision_sync(&self, root_hash: TrieHash) -> Result, api::Error> { - let nodestore = self - .manager - .read() - .expect("poisoned lock") - .revision(root_hash)?; + let nodestore = self.manager.revision(root_hash)?; Ok(nodestore) } /// Synchronously get a view, either committed or proposed pub fn view_sync(&self, root_hash: TrieHash) -> Result, api::Error> { - let nodestore = self - .manager - .read() - .expect("poisoned lock") - .view(root_hash)?; + let nodestore = self.manager.view(root_hash)?; Ok(nodestore) } @@ -311,11 +280,7 @@ impl Db { &'_ self, batch: Batch, ) -> Result>, api::Error> { - let parent = self - .manager - .read() - .expect("poisoned lock") - .current_revision(); + let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); for op in batch { @@ -334,10 +299,7 @@ impl Db { let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = Arc::new(nodestore.try_into()?); - self.manager - .write() - .expect("poisoned lock") - .add_proposal(immutable.clone()); + self.manager.add_proposal(immutable.clone()); self.metrics.proposals.increment(1); @@ -355,11 +317,7 @@ impl Db { /// Dump the Trie of the latest revision, synchronously. pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { - let latest_rev_nodestore = self - .manager - .read() - .expect("poisoned lock") - .current_revision(); + let latest_rev_nodestore = self.manager.current_revision(); let merkle = Merkle::from(latest_rev_nodestore); // TODO: This should be a stream let output = merkle.dump()?; @@ -457,12 +415,7 @@ impl<'a> api::Proposal for Proposal<'a> { async fn commit(self: Arc) -> Result<(), api::Error> { self.start_commit()?; - Ok(self - .db - .manager - .write() - .expect("poisoned lock") - .commit(self.nodestore.clone())?) + Ok(self.db.manager.commit(self.nodestore.clone())?) } } @@ -470,12 +423,7 @@ impl Proposal<'_> { /// Commit a proposal synchronously pub fn commit_sync(self: Arc) -> Result<(), api::Error> { self.start_commit()?; - Ok(self - .db - .manager - .write() - .expect("poisoned lock") - .commit(self.nodestore.clone())?) + Ok(self.db.manager.commit(self.nodestore.clone())?) } /// Create a new proposal from the current one synchronously @@ -510,11 +458,7 @@ impl Proposal<'_> { let nodestore = merkle.into_inner(); let immutable: Arc, FileBacked>> = Arc::new(nodestore.try_into()?); - self.db - .manager - .write() - .expect("poisoned lock") - .add_proposal(immutable.clone()); + self.db.manager.add_proposal(immutable.clone()); Ok(Self { nodestore: immutable, @@ -654,7 +598,7 @@ mod test { // the third proposal should still be contained within the all_hashes list // would be deleted if another proposal was committed and proposal3 was dropped here let hash3 = proposal3.root_hash().await.unwrap().unwrap(); - assert!(db.manager.read().unwrap().all_hashes().contains(&hash3)); + assert!(db.manager.all_hashes().contains(&hash3)); } #[tokio::test] @@ -705,7 +649,7 @@ mod test { // the third proposal should still be contained within the all_hashes list let hash3 = proposal3.root_hash().await.unwrap().unwrap(); - assert!(db.manager.read().unwrap().all_hashes().contains(&hash3)); + assert!(db.manager.all_hashes().contains(&hash3)); // moreover, the data from the second and third proposals should still be available // through proposal3 diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 239f14132732..e8e8bbd649e5 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -20,9 +20,9 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::Arc; #[cfg(feature = "ethhash")] use std::sync::OnceLock; +use std::sync::{Arc, Mutex, RwLock}; use firewood_storage::logger::{trace, trace_enabled, warn}; use metrics::gauge; @@ -64,10 +64,10 @@ pub(crate) struct RevisionManager { /// The list of revisions that are on disk; these point to the different roots /// stored in the filebacked storage. - historical: VecDeque, - proposals: Vec, + historical: RwLock>, + proposals: Mutex>, // committing_proposals: VecDeque>, - by_hash: HashMap, + by_hash: RwLock>, #[cfg(feature = "ethhash")] empty_hash: OnceLock, @@ -103,18 +103,22 @@ impl RevisionManager { } else { Arc::new(NodeStore::open(storage.clone())?) }; - let mut manager = Self { + let manager = Self { max_revisions: config.max_revisions, - historical: VecDeque::from([nodestore.clone()]), - by_hash: Default::default(), - proposals: Default::default(), + historical: RwLock::new(VecDeque::from([nodestore.clone()])), + by_hash: RwLock::new(Default::default()), + proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), #[cfg(feature = "ethhash")] empty_hash: OnceLock::new(), }; if let Some(hash) = nodestore.root_hash().or_else(|| manager.empty_trie_hash()) { - manager.by_hash.insert(hash, nodestore.clone()); + manager + .by_hash + .write() + .expect("poisoned lock") + .insert(hash, nodestore.clone()); } if truncate { @@ -126,10 +130,14 @@ impl RevisionManager { pub fn all_hashes(&self) -> Vec { self.historical + .read() + .expect("poisoned lock") .iter() .filter_map(|r| r.root_hash().or_else(|| self.empty_trie_hash())) .chain( self.proposals + .lock() + .expect("poisoned lock") .iter() .filter_map(|p| p.root_hash().or_else(|| self.empty_trie_hash())), ) @@ -165,7 +173,7 @@ impl RevisionManager { /// Any other proposals that have this proposal as a parent should be reparented to the committed version. #[fastrace::trace(short_name = true)] #[crate::metrics("firewood.proposal.commit", "proposal commit to storage")] - pub fn commit(&mut self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { + pub fn commit(&self, proposal: ProposedRevision) -> Result<(), RevisionManagerError> { // 1. Commit check let current_revision = self.current_revision(); if !proposal.parent_hash_is(current_revision.root_hash()) { @@ -179,10 +187,18 @@ impl RevisionManager { // 3 Take the deleted entries from the oldest revision and mark them as free for this revision // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free - while self.historical.len() >= self.max_revisions { - let oldest = self.historical.pop_front().expect("must be present"); + while self.historical.read().expect("poisoned lock").len() >= self.max_revisions { + let oldest = self + .historical + .write() + .expect("poisoned lock") + .pop_front() + .expect("must be present"); if let Some(oldest_hash) = oldest.root_hash().or_else(|| self.empty_trie_hash()) { - self.by_hash.remove(&oldest_hash); + self.by_hash + .write() + .expect("poisoned lock") + .remove(&oldest_hash); } // This `try_unwrap` is safe because nobody else will call `try_unwrap` on this Arc @@ -194,19 +210,29 @@ impl RevisionManager { Ok(oldest) => oldest.reap_deleted(&mut committed)?, Err(original) => { warn!("Oldest revision could not be reaped; still referenced"); - self.historical.push_front(original); + self.historical + .write() + .expect("poisoned lock") + .push_front(original); break; } } - gauge!("firewood.active_revisions").set(self.historical.len() as f64); + gauge!("firewood.active_revisions") + .set(self.historical.read().expect("poisoned lock").len() as f64); gauge!("firewood.max_revisions").set(self.max_revisions as f64); } // 4. Set last committed revision let committed: CommittedRevision = committed.into(); - self.historical.push_back(committed.clone()); + self.historical + .write() + .expect("poisoned lock") + .push_back(committed.clone()); if let Some(hash) = committed.root_hash().or_else(|| self.empty_trie_hash()) { - self.by_hash.insert(hash, committed.clone()); + self.by_hash + .write() + .expect("poisoned lock") + .insert(hash, committed.clone()); } // TODO: We could allow other commits to start here using the pending list @@ -223,10 +249,12 @@ impl RevisionManager { // Free proposal that is being committed as well as any proposals no longer // referenced by anyone else. self.proposals + .lock() + .expect("poisoned lock") .retain(|p| !Arc::ptr_eq(&proposal, p) && Arc::strong_count(p) > 1); // then reparent any proposals that have this proposal as a parent - for p in &self.proposals { + for p in &*self.proposals.lock().expect("poisoned lock") { proposal.commit_reparent(p); } @@ -240,8 +268,8 @@ impl RevisionManager { } impl RevisionManager { - pub fn add_proposal(&mut self, proposal: ProposedRevision) { - self.proposals.push(proposal); + pub fn add_proposal(&self, proposal: ProposedRevision) { + self.proposals.lock().expect("poisoned lock").push(proposal); } pub fn view( @@ -256,6 +284,8 @@ impl RevisionManager { // If not found in committed revisions, try proposals let proposal = self .proposals + .lock() + .expect("poisoned lock") .iter() .find(|p| p.root_hash().as_ref() == Some(&root_hash)) .cloned() @@ -266,6 +296,8 @@ impl RevisionManager { pub fn revision(&self, root_hash: HashKey) -> Result { self.by_hash + .read() + .expect("poisoned lock") .get(&root_hash) .cloned() .ok_or(RevisionManagerError::RevisionNotFound) @@ -277,6 +309,8 @@ impl RevisionManager { pub fn current_revision(&self) -> CommittedRevision { self.historical + .read() + .expect("poisoned lock") .back() .expect("there is always one revision") .clone() From acd0e19500f92915f9f1ae6de4a5c4513db3271f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Jul 2025 11:21:59 -0700 Subject: [PATCH 0799/1053] ci: Check for metrics changes (#1013) If someone changes metrics, and doesn't change the dashboard, let's emit a warning and add a comment to the PR to ensure we keep the dashboard updated. --- .github/workflows/metrics-check.yaml | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/metrics-check.yaml diff --git a/.github/workflows/metrics-check.yaml b/.github/workflows/metrics-check.yaml new file mode 100644 index 000000000000..20d3f279f899 --- /dev/null +++ b/.github/workflows/metrics-check.yaml @@ -0,0 +1,88 @@ +name: Metrics Change Check + +on: + pull_request: + branches: [ main, master ] + push: + branches: [ main, master ] + +jobs: + check-metrics-changes: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history to compare changes + + - name: Check for metric-related changes + id: check-metrics + run: | + # Get the base commit for comparison + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE_COMMIT="${{ github.event.pull_request.base.sha }}" + else + BASE_COMMIT="${{ github.event.before }}" + fi + + # Check if Grafana dashboard was modified - exit early if so + if git diff --name-only $BASE_COMMIT..HEAD | grep -q "benchmark/Grafana-dashboard.json"; then + echo "benchmark/Grafana-dashboard.json was modified - skipping metrics check" + exit 0 + fi + + # Regex pattern to match metric-related code changes + METRIC_REGEX_PATTERN="(counter!|gauge!|histogram!|#\\[metrics\\])" + + # Check for metric-related changes + METRIC_CHANGES=$(git diff $BASE_COMMIT..HEAD --unified=0 | grep -E "$METRIC_REGEX_PATTERN" || true) + + if [ -n "$METRIC_CHANGES" ]; then + echo "⚠️ WARNING: Found metric-related changes, but no dashboard modification:" + echo "$METRIC_CHANGES" + else + echo "✅ No metric-related changes found" + fi + + # Set output variables for the comment step + echo "metric_changes_found=$([ -n "$METRIC_CHANGES" ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT + echo "metric_pattern=$METRIC_REGEX_PATTERN" >> $GITHUB_OUTPUT + + - name: Comment on PR (if applicable) + if: github.event_name == 'pull_request' && steps.check-metrics.outputs.metric_changes_found == 'true' + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + + try { + // Get the base commit for comparison + const baseCommit = context.payload.pull_request.base.sha; + + // Get the metric pattern from the previous step + const metricPattern = "${{ steps.check-metrics.outputs.metric_pattern }}"; + + // Check for metric changes + const metricChanges = execSync(`git diff ${baseCommit}..HEAD --unified=0 | grep -E "${metricPattern}" || true`, { encoding: 'utf8' }); + + if (metricChanges.trim()) { + const comment = '## Metrics Change Detection ⚠️\n\n' + + 'This PR contains changes related to metrics:\n\n' + + '```\n' + + metricChanges + + '\n```\n\n' + + 'However, the dashboard was not modified.\n\n' + + 'You may need to update `benchmark/Grafana-dashboard.json` accordingly.\n\n' + + '---\n' + + '*This check is automated to help maintain the dashboard.*'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } + } catch (error) { + console.log('No metric changes found or error occurred:', error.message); + } From 9c01c61d56cd7c30e17fb8ce43e9037da66c540d Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:32:10 -0400 Subject: [PATCH 0800/1053] refactor(ffi)!: split starting metrics exporter from db startup (#1016) This PR closes #984 by splitting startup of the metrics exporter into its own ffi method (`fwd_start_metrics`). ## What I removed the metrics logic from `common_create` and put it into `fwd_start_metrics`. I also removed the metrics field from `CreateOrOpenArgs`. Given that starting the metrics exporter is now a separate function call, I also removed the special case of not starting the metrics exporter for when `metrics_port = 0`. ## Testing Added a unit test for starting up the metrics server and querying it. --------- Signed-off-by: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> --- ffi/firewood.go | 11 ++++-- ffi/firewood.h | 16 +++++++- ffi/firewood_test.go | 39 ++++++++++++++++++- ffi/src/lib.rs | 22 ++++++++--- ffi/src/metrics_setup.rs | 8 ---- ffi/tests/eth/eth_compatibility_test.go | 1 - .../firewood/merkle_compatibility_test.go | 1 - 7 files changed, 77 insertions(+), 21 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 929eb2006788..486f7f8d2b03 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -59,7 +59,6 @@ type Config struct { FreeListCacheEntries uint Revisions uint ReadCacheStrategy CacheStrategy - MetricsPort uint16 } // DefaultConfig returns a sensible default Config. @@ -69,7 +68,6 @@ func DefaultConfig() *Config { FreeListCacheEntries: 40_000, Revisions: 100, ReadCacheStrategy: OnlyCacheWrites, - MetricsPort: 3000, } } @@ -111,7 +109,6 @@ func New(filePath string, conf *Config) (*Database, error) { free_list_cache_size: C.size_t(conf.FreeListCacheEntries), revisions: C.size_t(conf.Revisions), strategy: C.uint8_t(conf.ReadCacheStrategy), - metrics_port: C.uint16_t(conf.MetricsPort), } // Defer freeing the C string allocated to the heap on the other side // of the FFI boundary. @@ -132,6 +129,14 @@ func New(filePath string, conf *Config) (*Database, error) { return &Database{handle: db}, nil } +// Starts metrics exporter for this process. +// Returns an error if the metrics exporter was unable to start or already started. +// This function should only be called once per process. +func StartMetrics(metricsPort uint16) error { + result := C.fwd_start_metrics(C.uint16_t(metricsPort)) + return errorFromValue(&result) +} + // Update applies a batch of updates to the database, returning the hash of the // root node after the batch is applied. // diff --git a/ffi/firewood.h b/ffi/firewood.h index b3c16d1d767f..61f68e225989 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -60,7 +60,6 @@ typedef struct DatabaseCreationResult { * * `strategy` - The cache read strategy to use, 0 for writes only, * 1 for branch reads, and 2 for all reads. * Returns an error if the value is not 0, 1, or 2. - * * `metrics_port` - The port to use for metrics, 0 to disable metrics. */ typedef struct CreateOrOpenArgs { const char *path; @@ -68,7 +67,6 @@ typedef struct CreateOrOpenArgs { size_t free_list_cache_size; size_t revisions; uint8_t strategy; - uint16_t metrics_port; } CreateOrOpenArgs; typedef uint32_t ProposalId; @@ -408,3 +406,17 @@ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, * */ struct Value fwd_root_hash(const struct DatabaseHandle *db); + +/** + * Start metrics exporter for this process + * + * # Arguments + * + * * `metrics_port` - the port where metrics will be exposed at + * + * # Returns + * + * A `Value` containing {0, null} if the metrics exporter successfully started. + * A `Value` containing {0, "error message"} if the metrics exporter failed to start. + */ +struct Value fwd_start_metrics(uint16_t metrics_port); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 55b1dfe277c1..6ef64416f895 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -2,13 +2,17 @@ package ffi import ( "bytes" + "context" "encoding/hex" "fmt" + "io" + "net/http" "os" "path/filepath" "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -134,7 +138,6 @@ func newTestDatabase(t *testing.T) *Database { func newDatabase(dbFile string) (*Database, func() error, error) { conf := DefaultConfig() - conf.MetricsPort = 0 conf.Create = true f, err := New(dbFile, conf) @@ -959,3 +962,37 @@ func TestGetFromRoot(t *testing.T) { _, err = db.GetFromRoot(nonExistentRoot, []byte("key")) r.Error(err, "GetFromRoot with non-existent root should return error") } + +func TestStartMetrics(t *testing.T) { + r := require.New(t) + ctx := context.Background() + + db := newTestDatabase(t) + + metricsPort := uint16(3000) + r.NoError(StartMetrics(metricsPort)) + + // Populate DB + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) + + req, err := http.NewRequestWithContext( + ctx, + http.MethodGet, + fmt.Sprintf("http://localhost:%d", metricsPort), + nil, + ) + r.NoError(err) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + r.NoError(err) + + body, err := io.ReadAll(resp.Body) + r.NoError(err) + r.NoError(resp.Body.Close()) + + // Check that batch op was recorded + r.Contains(string(body), "firewood_ffi_batch 1") +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 74df22e51524..1c0b7cffa1c8 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -823,6 +823,23 @@ pub unsafe extern "C" fn fwd_free_database_error_result( // Note: we don't free the db pointer as it's managed by the caller } +/// Start metrics exporter for this process +/// +/// # Arguments +/// +/// * `metrics_port` - the port where metrics will be exposed at +/// +/// # Returns +/// +/// A `Value` containing {0, null} if the metrics exporter successfully started. +/// A `Value` containing {0, "error message"} if the metrics exporter failed to start. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_start_metrics(metrics_port: u16) -> Value { + metrics_setup::setup_metrics(metrics_port) + .map_err(|e| e.to_string()) + .map_or_else(Into::into, Into::into) +} + /// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. /// /// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` @@ -833,7 +850,6 @@ pub unsafe extern "C" fn fwd_free_database_error_result( /// * `strategy` - The cache read strategy to use, 0 for writes only, /// 1 for branch reads, and 2 for all reads. /// Returns an error if the value is not 0, 1, or 2. -/// * `metrics_port` - The port to use for metrics, 0 to disable metrics. #[repr(C)] pub struct CreateOrOpenArgs { path: *const std::ffi::c_char, @@ -841,7 +857,6 @@ pub struct CreateOrOpenArgs { free_list_cache_size: usize, revisions: usize, strategy: u8, - metrics_port: u16, } /// Create a database with the given cache size and maximum number of revisions, as well @@ -910,9 +925,6 @@ unsafe fn common_create(args: &CreateOrOpenArgs, create_file: bool) -> Result 0 { - metrics_setup::setup_metrics(args.metrics_port).map_err(|e| e.to_string())?; - } Db::new_sync(path, cfg).map_err(|e| e.to_string()) } diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 2aad80503835..ff93470c150b 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -18,17 +18,9 @@ use metrics::Key; use metrics_util::registry::{AtomicStorage, Registry}; /// Sets up a metrics server over a specified port. -/// If `metrics_port` is set to 0, it will not initialize the metrics system. /// This happens on a per-process basis, meaning that the metrics system /// cannot be initialized if it has already been set up in the same process. -/// Any caller should ensure that the metrics system is not already initialized -/// by explicitly calling with `metrics_port` == 0.. pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { - if metrics_port == 0 { - // If the port is 0, we do not initialize the metrics system. - return Ok(()); - } - let inner: TextRecorderInner = TextRecorderInner { registry: Registry::atomic(), }; diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index 90b7abbd1515..fc21f90054cc 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -68,7 +68,6 @@ func newMerkleTriePair(t *testing.T) *merkleTriePair { file := path.Join(t.TempDir(), "test.db") cfg := firewood.DefaultConfig() cfg.Create = true - cfg.MetricsPort = 0 db, err := firewood.New(file, cfg) r.NoError(err) diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index 5e14e83632ba..0c7c68e92c43 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -60,7 +60,6 @@ func newTestFirewoodDatabase(t *testing.T) *firewood.Database { func newFirewoodDatabase(dbFile string) (*firewood.Database, func() error, error) { conf := firewood.DefaultConfig() - conf.MetricsPort = 0 conf.Create = true f, err := firewood.New(dbFile, conf) From 9356522fe503406d024806af797ccc80f219d4c6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 3 Jul 2025 11:37:40 -0700 Subject: [PATCH 0801/1053] docs: README cleanup (#1024) Signed-off-by: Ron Kuris Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> Co-authored-by: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> --- README.md | 93 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 798b75c0914b..011ceb397565 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,38 @@ ![Github Actions](https://github.com/ava-labs/firewood/actions/workflows/ci.yaml/badge.svg?branch=main) [![Ecosystem license](https://img.shields.io/badge/License-Ecosystem-blue.svg)](./LICENSE.md) -> :warning: Firewood is alpha-level software and is not ready for production -> use. The Firewood API and on-disk state representation may change with -> little to no warning. +> :warning: Firewood is beta-level software. +> The Firewood API may change with little to no warning. Firewood is an embedded key-value store, optimized to store recent Merkleized blockchain -state with minimal overhead. Firewood is implemented from the ground up to directly -store trie nodes on-disk. Unlike most state management approaches in the field, -it is not built on top of a generic KV store such as LevelDB/RocksDB. Firewood, like a -B+-tree based database, directly uses the trie structure as the index on-disk. Thus, -there is no additional “emulation” of the logical trie to flatten out the data structure -to feed into the underlying database that is unaware of the data being stored. The convenient -byproduct of this approach is that iteration is still fast (for serving state sync queries) -but compaction is not required to maintain the index. Firewood was first conceived to provide -a very fast storage layer for the EVM but could be used on any blockchain that -requires an authenticated state. - -Firewood only attempts to store recent revisions on-disk and will actively clean up -unused data when revisions expire. Firewood keeps some configurable number of previous states in memory and on disk to power state sync (which may occur at a few roots behind the current state). To do this, a new root is always created for each revision that can reference either new nodes from this revision or nodes from a prior revision. When creating a revision, a list of nodes that are no longer needed are computed and saved to disk in a future-delete log (FDL) as well as kept in memory. When a revision expires, the nodes that were deleted when it was created are returned to the free space. - -Firewood guarantees recoverability by not referencing the new nodes in a new revision before they are flushed to disk, as well as carefully managing the free list during the creation and expiration of revisions. +state with minimal overhead. Most blockchains, including Avalanche's C-Chain and Ethereum, store their state in Merkle tries to support efficient generation and verification of state proofs. +Firewood is implemented from the ground up to directly store trie nodes on-disk. +Unlike most state management approaches in the field, +it is not built on top of a generic KV store such as LevelDB/RocksDB. +Firewood, like a B+-tree based database, directly uses the trie structure as the index on-disk. +There is no additional “emulation” of the logical trie to flatten out the data structure +to feed into the underlying database that is unaware of the data being stored. +The convenient byproduct of this approach is that iteration is still fast (for serving state sync queries) +but compaction is not required to maintain the index. +Firewood was first conceived to provide a very fast storage layer for the EVM, +but could be used on any blockchain that requires an authenticated state. + +Firewood only attempts to store recent revisions on-disk and will actively clean up unused data when revisions expire. +Firewood keeps some configurable number of previous states in memory and on disk to power state sync and APIs +which may occur at a few roots behind the current state. +To do this, a new root is always created for each revision that can reference either new nodes from this revision or nodes from a prior revision. +When creating a revision, +a list of nodes that are no longer needed are computed and saved to disk in a future-delete log (FDL) as well as kept in memory. +When a revision expires, the nodes that were deleted when it was created are returned to the free space. + +Hashes are not used to determine where a node is stored on disk in the database file. +Instead space for nodes may be allocated from the end of the file, +or from space freed from expired revision. Free space management algorithmically resembles that of traditional heap memory management, with free lists used to track different-size spaces that can be reused. +The root address of a node is simply the disk offset within the database file, +and each branch node points to the disk offset of that other node. + +Firewood guarantees recoverability by not referencing the new nodes in a new revision before they are flushed to disk, +as well as carefully managing the free list during the creation and expiration of revisions. ## Architecture Diagram @@ -62,14 +74,6 @@ Firewood guarantees recoverability by not referencing the new nodes in a new rev - `Commit` - The operation of applying one or more `Proposal`s to the most recent `Revision`. -## Roadmap - -- [X] Complete the revision manager -- [X] Complete the API implementation -- [X] Implement a node cache -- [ ] Complete the proof code -- [ ] Hook up the RPC - ## Build In order to build firewood, the following dependencies must be installed: @@ -78,14 +82,43 @@ In order to build firewood, the following dependencies must be installed: - `cargo` See [installation instructions](https://doc.rust-lang.org/cargo/getting-started/installation.html). - `make` See [download instructions](https://www.gnu.org/software/make/#download) or run `sudo apt install build-essential` on Linux. +More detailed build instructions, including some scripts, +can be found in the [benchmark setup scripts](benchmark/setup-scripts). + +If you want to build and test the ffi layer for another platform, +you can find those instructions in the [ffi README](ffi/README.md). + +## Ethereum compatibility + +By default, Firewood builds with hashes compatible with [merkledb](https://github.com/ava-labs/avalanchego/tree/master/x/merkledb), +and does not support accounts. +To enable this feature (at the cost of some performance) enable the ethhash [feature flag](https://doc.rust-lang.org/cargo/reference/features.html#command-line-feature-options). + +Enabling this feature +changes the hashing algorithm from [sha256](https://docs.rs/sha2/latest/sha2/type.Sha256.html) +to [keccak256](https://docs.rs/sha3/latest/sha3/type.Keccak256.html), +understands that an "account" is actually just a node in the storage tree at a specific depth with a specific RLP-encoded value, +and computes the hash of the account trie as if it were an actual root. + +It is worth nothing that the hash stored as a value inside the account root RLP is not used. +During hash calculations, we know the hash of the children, +and use that directly to modify the value in-place +when hashing the node. +See [replace\_hash](firewood/storage/src/hashers/ethhash.rs) for more details. + ## Run -There are several examples, in the examples directory, that simulate real world -use-cases. Try running them via the command-line, via `cargo run --release +Example(s) are in the [examples](firewood/examples) directory, that simulate real world +use-cases. Try running the insert example via the command-line, via `cargo run --release --example insert`. -For maximum performance, use `cargo run --maxperf` instead, which enables maximum -link time compiler optimizations, but takes a lot longer to compile. +There is a [fwdctl cli](fwdctl) for command-line operations on a database. + +There is also a [benchmark](benchmark) that shows some other example uses. + +For maximum runtime performance at the cost of compile time, +use `cargo run --maxperf` instead, +which enables maximum link time compiler optimizations. ## Logging From 3fdb75f79e3f08cd72a950806a54569ebec32fac Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 3 Jul 2025 12:06:02 -0700 Subject: [PATCH 0802/1053] chore: share workspace metadata and packages (#1020) Many of the package fields can be declared at the workspace level and then reused by the individual packages. This reduces duplication and drift. Additionally, moving the workspace packages to be listed as workspace dependencies instead of strewn throughout the multiple Cargo.toml files makes it easier to bump version numbers across the workspace as they are all in one location. If we ever want to go back to individually set version, we can swap back the `version = "..."` line in each package as needed. ## Testing It was easy to check what cargo would complain about before publishing by using `cargo publish --dry-run -p `. This was helpful to verify that the changes to the `license-file` and `readme` fields would be correctly transformed. e.g., ```console $ cargo publish --dry-run -p firewood-storage warning: crate firewood-storage@0.0.7 already exists on crates.io index ... warning: aborting upload due to dry run ``` --- Cargo.toml | 24 +++++++-- RELEASE.md | 99 +++++++++++++++++++++++++++++++------- benchmark/Cargo.toml | 15 +++--- ffi/Cargo.toml | 15 +++--- firewood-macros/Cargo.toml | 13 ++--- firewood-macros/README.md | 4 +- firewood/Cargo.toml | 21 ++++---- fwdctl/Cargo.toml | 16 +++--- storage/Cargo.toml | 13 ++--- triehash/Cargo.toml | 9 ++-- 10 files changed, 159 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d738583739fc..ba5ac781658a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,24 @@ [workspace] members = [ + "benchmark", + "ffi", + "firewood-macros", "firewood", "fwdctl", "storage", - "benchmark", - "ffi", "triehash", ] resolver = "2" +[workspace.package] +version = "0.0.7" +edition = "2024" +license-file = "LICENSE.md" +homepage = "https://avalabs.org" +repository = "https://github.com/ava-labs/firewood" +readme = "README.md" +rust-version = "1.85.0" + [profile.release] debug = true @@ -42,6 +52,14 @@ unused_self = "allow" cast_possible_truncation = "allow" [workspace.dependencies] +# workspace local packages +firewood = { path = "firewood", version = "0.0.7" } +firewood-macros = { path = "firewood-macros", version = "0.0.7" } +firewood-storage = { path = "storage", version = "0.0.7" } +firewood-ffi = { path = "ffi", version = "0.0.7" } +firewood-triehash = { path = "triehash", version = "0.0.7" } + +# common dependencies metrics = "0.24.2" metrics-util = "0.20.0" sha2 = "0.10.9" @@ -56,7 +74,7 @@ integer-encoding = "4.0.2" env_logger = "0.11.8" smallvec = "1.15.1" -# dev dependencies +# common dev dependencies rand = "0.9.1" criterion = "0.6.0" pprof = "0.15.0" diff --git a/RELEASE.md b/RELEASE.md index 4f1e7ce4e627..f5c880a89853 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,30 +1,76 @@ # Releasing firewood -Releasing firewood is straightforward and can be done entirely in CI. +Releasing firewood is straightforward and can mostly be done in CI. Updating the +Cargo.toml file is currently manual. Firewood is made up of several sub-projects in a workspace. Each project is in its own crate and has an independent version. -The first step in drafting a release is ensuring all crates within the firewood -project are using the version of the new release. There is a utility to ensure -all versions are updated simultaneously in `cargo-workspace-version`. To use it -to update to 0.0.5, for example: +## Git Branch -```sh - cargo install cargo-workspace-version - cargo workspace-version update v0.0.5 +Start off by crating a new branch: + +```console +$ git fetch +$ git switch -c release/v0.0.8 origin/main +branch 'release/v0.0.8' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.8' ``` -See the [source code](https://github.com/ava-labs/cargo-workspace-version) for -more information on the tool. +## Package Version -> ❗ Be sure to update the versions of all sub-projects before creating a new -> release. Open a PR with the updated versions and merge it before continuing to -> the next step. +Next, update the workspace version and ensure all crates within the firewood +project are using the version of the new release. The root [Cargo.toml](Cargo.toml) +file uses the [`[workspace.package]`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-package-table) +table to define the version for all subpackages. + +```toml +[workspace.package] +version = "0.0.7" +``` + +Each package inherits this version by setting `package.version.workspace = true`. + +```toml +[package] +name = "firewood" +version.workspace = true +``` + +Therefore, updating only the version defined in the root config is needed. + +## Dependency Version + +The next step is to bump the dependency declarations to the new version. Packages +within the workspace that are used as libraries are also defined within the +[`[workspace.dependencies]`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table) +table. E.g.,: + +```toml +[workspace.dependencies] +# workspace local packages +firewood = { path = "firewood", version = "0.0.7" } +``` + +This allows packages within the workspace to inherit the dependency, +including path, version, and workspace-level features by adding `workspace = true` +to the dependency table (note: using `cargo add -p firewood-fwdctl firewood-metrics` +would automatically add the dependency with `workspace = true`). -To trigger a release, simply push a semver-compatible tag to the main branch, -for example `v0.0.5`. The CI will automatically publish a draft release which -consists of release notes and changes. +```toml +[dependencies] +firewood-macros.workspace = true + +# more complex example +[target.'cfg(target_os = "linux")'.dependencies] +firewood-storage = { workspace = true, features = ["io-uring"] } + +[target.'cfg(not(target_os = "linux"))'.dependencies] +firewood-storage.workspace = true +``` + +Thefefore, after updating the `workspace.package.version` value, we must update +the dependency versions to match. ## Changelog @@ -32,5 +78,24 @@ To build the changelog, see git-cliff.org. Short version: ```sh cargo install git-cliff -git cliff --tag v0.0.5 | sed -e 's/_/\\_/g' > CHANGELOG.md +git cliff --tag v0.0.8 | sed -e 's/_/\\_/g' > CHANGELOG.md ``` + +## Review + +> ❗ Be sure to update the versions of all sub-projects before creating a new +> release. Open a PR with the updated versions and merge it before continuing to +> the next step. + +## Publish + +To trigger a release, push a tag to the main branch matching the new version, + +```sh +git tag -S v0.0.8 +git push origin v0.0.8 +``` + +for `v0.0.8` for the merged version change. The CI will automatically publish a +draft release which consists of release notes and changes (see +[.github/workflows/release.yaml](.github/workflows/release.yaml)). diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 9e53272edb7f..30c622bf17e5 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,23 +1,24 @@ [package] name = "firewood-benchmark" -version = "0.0.7" -edition = "2024" -rust-version = "1.85.0" +version.workspace = true +edition.workspace = true authors = [ "Aaron Buchwald ", "Ron Kuris ", ] description = "Benchmarking tool for Firewood, an embedded key-value store optimized for blockchain state." -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -repository = "https://github.com/ava-labs/firewood" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true [[bin]] name = "benchmark" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.7", path = "../firewood" } +firewood.workspace = true hex = "0.4.3" clap = { workspace = true, features = ['string'] } sha2 = { workspace = true } diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 777c58873983..e7fd80e10e78 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "firewood-ffi" -version = "0.0.7" -edition = "2024" -rust-version = "1.85.0" +version.workspace = true +edition.workspace = true authors = [ "Aaron Buchwald ", "Arran Schlosberg <519948+ARR4N@users.noreply.github.com>", @@ -11,16 +10,18 @@ authors = [ "Ron Kuris ", ] description = "C FFI bindings for Firewood, an embedded key-value store optimized for blockchain state." -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -repository = "https://github.com/ava-labs/firewood" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true [lib] crate-type = ["staticlib"] [dependencies] libc = "0.2.174" -firewood = { version = "0.0.7", path = "../firewood" } +firewood.workspace = true metrics = { workspace = true } metrics-util = { workspace = true } chrono = "0.4.41" diff --git a/firewood-macros/Cargo.toml b/firewood-macros/Cargo.toml index 6beb32f4f48c..dea4abc5bd40 100644 --- a/firewood-macros/Cargo.toml +++ b/firewood-macros/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "firewood-macros" -version = "0.0.7" -edition = "2024" -rust-version = "1.85" +version.workspace = true +edition.workspace = true authors = [ "Ron Kuris ", ] description = "Proc macros for Firewood metrics" -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true [lib] proc-macro = true @@ -24,4 +25,4 @@ metrics = "0.24" coarsetime = "0.1" [lints] -workspace = true +workspace = true diff --git a/firewood-macros/README.md b/firewood-macros/README.md index eb0fda6a27ae..35bfeebd145f 100644 --- a/firewood-macros/README.md +++ b/firewood-macros/README.md @@ -20,7 +20,7 @@ Add the dependency to your `Cargo.toml`: ```toml [dependencies] -firewood-macros = { path = "../firewood-macros" } +firewood-macros.workspace = true metrics = "0.24" coarsetime = "0.1" ``` @@ -64,7 +64,7 @@ Both metrics include a `success` label: For `#[metrics("firewood.query", "data retrieval")]`: - `firewood.example{success="true"}` - Count of successful queries -- `firewood.example{success="false"}` - Count of failed queries +- `firewood.example{success="false"}` - Count of failed queries - `firewood.example_ms{success="true"}` - Timing of successful queries - `firewood.example_ms{success="false"}` - Timing of failed queries diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 38cb364b50d6..f1e606ee4027 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "firewood" -version = "0.0.7" -edition = "2024" +version.workspace = true +edition.workspace = true authors = [ "Angel Leon ", "Austin Larson <78000745+alarso16@users.noreply.github.com>", @@ -16,10 +16,11 @@ authors = [ "xinifinity <113067541+xinifinity@users.noreply.github.com>", ] description = "Firewood is an embedded key-value store, optimized to store blockchain state." -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -repository = "https://github.com/ava-labs/firewood" -readme = "../README.md" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true [dependencies] aquamarine = "0.6.0" @@ -37,7 +38,7 @@ integer-encoding = { workspace = true } smallvec = { workspace = true } fastrace = { workspace = true } coarsetime = { workspace = true } -firewood-macros = { version = "0.0.7", path = "../firewood-macros" } +firewood-macros.workspace = true [features] default = [] @@ -48,7 +49,7 @@ branch_factor_256 = [ "firewood-storage/branch_factor_256" ] ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] -firewood-triehash = { version = "0.0.7", path = "../triehash" } +firewood-triehash.workspace = true criterion = { workspace = true, features = ["async_tokio"] } rand = { workspace = true } rand_distr = "0.5.1" @@ -68,10 +69,10 @@ name = "hashops" harness = false [target.'cfg(target_os = "linux")'.dependencies] -firewood-storage = { version = "0.0.7", path = "../storage", features = ["io-uring"] } +firewood-storage = { workspace = true, features = ["io-uring"] } [target.'cfg(not(target_os = "linux"))'.dependencies] -firewood-storage = { version = "0.0.7", path = "../storage" } +firewood-storage.workspace = true [lints] workspace = true diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 0386f0901115..df6955bc748e 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "firewood-fwdctl" -version = "0.0.7" -edition = "2024" -rust-version = "1.85.0" +version.workspace = true +edition.workspace = true authors = [ "Dan Laine ", "Dan Sover ", @@ -14,17 +13,18 @@ authors = [ "zdf ", ] description = "Command-line tool for Firewood, an embedded key-value store optimized for blockchain state." -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -repository = "https://github.com/ava-labs/firewood" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true [[bin]] name = "fwdctl" path = "src/main.rs" [dependencies] -firewood = { version = "0.0.7", path = "../firewood" } -firewood-storage = { version = "0.0.7", path = "../storage" } +firewood.workspace = true +firewood-storage.workspace = true clap = { workspace = true, features = ["cargo"] } env_logger = { workspace = true } log = "0.4.27" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 6b200d9c1411..96e0a587a804 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "firewood-storage" -version = "0.0.7" -edition = "2024" -rust-version = "1.85.0" +version.workspace = true +edition.workspace = true authors = [ "Aaron Buchwald ", "Ron Kuris ", "Suyan Qu <36519575+qusuyan@users.noreply.github.com>", ] description = "Storage layer for Firewood, an embedded key-value store optimized for blockchain state." -license-file = "../LICENSE.md" -homepage = "https://avalabs.org" -repository = "https://github.com/ava-labs/firewood" +license-file.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index 368a143bc3fb..d165c341fb4b 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "firewood-triehash" -version = "0.0.7" +version.workspace = true authors = ["Parity Technologies ", "Ron Kuris "] description = "In-memory patricia trie operations" -repository = "https://github.com/paritytech/parity-common" +repository.workspace = true license = "MIT OR Apache-2.0" -edition = "2024" +edition.workspace = true +rust-version.workspace = true [dependencies] hash-db = "0.16.0" rlp = "0.6" [dev-dependencies] -criterion = { workspace = true } +criterion = { workspace = true } keccak-hasher = "0.16.0" ethereum-types = { workspace = true } tiny-keccak = { version = "2.0", features = ["keccak"] } From 84674e0feda0e7f81235dc584ec40f2daf78b2f3 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:14:37 -0400 Subject: [PATCH 0803/1053] feat(checker): traverse free lists (#1026) --- storage/src/checker.rs | 88 +++++++++++++++++++++++++++++++++++----- storage/src/lib.rs | 17 ++++++++ storage/src/nodestore.rs | 23 +++++++---- 3 files changed, 109 insertions(+), 19 deletions(-) diff --git a/storage/src/checker.rs b/storage/src/checker.rs index 3d46284686ba..3f4682ab72ae 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -17,12 +17,14 @@ impl NodeStore { /// 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. + /// # Panics + /// Panics if the header has too many free lists, which can never happen since freelists have a fixed size. // TODO: report all errors, not just the first one // TODO: add merkle hash checks as well pub fn check(&self) -> Result<(), CheckerError> { // 1. Check the header let db_size = self.size(); - let file_size = self.get_physical_size()?; + let file_size = self.physical_size()?; if db_size < file_size { return Err(CheckerError::InvalidDBSize { db_size, @@ -37,10 +39,11 @@ impl NodeStore { // 2. traverse the trie and check the nodes if let Some(root_address) = self.root_address() { // the database is not empty, traverse the trie - self.traverse_trie(root_address, &mut visited)?; + self.visit_trie(root_address, &mut visited)?; } // 3. check the free list - this can happen in parallel with the trie traversal + self.visit_freelist(&mut visited)?; // 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? let _ = visited.complement(); // TODO @@ -49,7 +52,7 @@ impl NodeStore { } /// Recursively traverse the trie from the given root address. - fn traverse_trie( + fn visit_trie( &self, subtree_root_address: LinearAddress, visited: &mut LinearAddressRangeSet, @@ -60,31 +63,52 @@ impl NodeStore { if let Node::Branch(branch) = self.read_node(subtree_root_address)?.as_ref() { // this is an internal node, traverse the children for (_, address) in branch.children_addresses() { - self.traverse_trie(*address, visited)?; + self.visit_trie(*address, visited)?; } } Ok(()) } + + /// Traverse all the free areas in the freelist + fn visit_freelist(&self, visited: &mut LinearAddressRangeSet) -> Result<(), CheckerError> { + for free_area in self.free_list_iter_inner(0) { + let (addr, stored_area_index, free_list_id) = free_area?; + let area_size = Self::size_from_area_index(stored_area_index); + if free_list_id != stored_area_index { + return Err(CheckerError::FreelistAreaSizeMismatch { + address: addr, + size: area_size, + actual_free_list: free_list_id, + expected_free_list: stored_area_index, + }); + } + visited.insert_area(addr, area_size)?; + } + Ok(()) + } } #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] + #![expect(clippy::indexing_slicing)] use super::*; use crate::linear::memory::MemStore; - use crate::nodestore::NodeStoreHeader; - use crate::nodestore::nodestore_test_utils::{test_write_header, test_write_new_node}; + use crate::nodestore::nodestore_test_utils::{ + test_write_free_area, test_write_header, test_write_new_node, + }; + use crate::nodestore::{AREA_SIZES, FreeLists, NodeStoreHeader}; use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; #[test] // This test creates a simple trie and checks that the checker traverses it correctly. // We use primitive calls here to do a low-level check. // TODO: add a high-level test in the firewood crate - fn test_checker_traverse_correct_trie() { + fn checker_traverse_correct_trie() { let memstore = MemStore::new(vec![]); - let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); // set up a basic trie: // ------------------------- @@ -132,11 +156,55 @@ mod test { high_watermark += root_area; // write the header - test_write_header(&nodestore, root_addr, high_watermark); + test_write_header( + &mut nodestore, + high_watermark, + Some(root_addr), + FreeLists::default(), + ); // verify that all of the space is accounted for - since there is no free area let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - nodestore.traverse_trie(root_addr, &mut visited).unwrap(); + nodestore.visit_trie(root_addr, &mut visited).unwrap(); + let complement = visited.complement(); + assert_eq!(complement.into_iter().collect::>(), vec![]); + } + + #[test] + fn traverse_correct_freelist() { + use rand::Rng; + + let mut rng = crate::test_utils::seeded_rng(); + + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + // write free areas + let mut high_watermark = NodeStoreHeader::SIZE; + let mut freelist = FreeLists::default(); + for (area_index, area_size) in AREA_SIZES.iter().enumerate() { + let mut next_free_block = None; + let num_free_areas = rng.random_range(0..4); + for _ in 0..num_free_areas { + test_write_free_area( + &nodestore, + next_free_block, + area_index as u8, + high_watermark, + ); + next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); + high_watermark += area_size; + } + + freelist[area_index] = next_free_block; + } + + // write header + test_write_header(&mut nodestore, high_watermark, None, freelist); + + // test that the we traversed all the free areas + let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + nodestore.visit_freelist(&mut visited).unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index d208a81dd3b7..d833f0ff5fa1 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -31,6 +31,8 @@ mod nodestore; mod range_set; mod trie_hash; +use crate::nodestore::AreaIndex; + /// Logger module for handling logging functionality pub mod logger; @@ -129,6 +131,21 @@ pub enum CheckerError { intersection: Vec>, }, + /// Freelist area size does not match + #[error( + "Free area {address} of size {size} is found in free list {actual_free_list} but it should be in freelist {expected_free_list}" + )] + FreelistAreaSizeMismatch { + /// Address of the free area + address: LinearAddress, + /// Actual size of the free area + size: u64, + /// Free list on which the area is stored + actual_free_list: AreaIndex, + /// Expected size of the area + expected_free_list: AreaIndex, + }, + /// IO error #[error("IO error")] IO(#[from] FileIoError), diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index b56c78529757..f8f5c7c0c46b 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -55,7 +55,7 @@ use super::linear::WritableStorage; /// [`NodeStore`] divides the linear store into blocks of different sizes. /// [`AREA_SIZES`] is every valid block size. -const AREA_SIZES: [u64; 23] = [ +pub(crate) const AREA_SIZES: [u64; 23] = [ 16, // Min block size 32, 64, @@ -903,7 +903,7 @@ struct FreeArea { } impl FreeArea { - pub(crate) fn from_storage( + fn from_storage( storage: &S, address: LinearAddress, ) -> Result<(Self, AreaIndex), FileIoError> { @@ -1663,7 +1663,7 @@ impl NodeStore { self.header.size } - pub(crate) fn get_physical_size(&self) -> Result { + pub(crate) fn physical_size(&self) -> Result { self.storage.size() } } @@ -1716,11 +1716,11 @@ impl NodeStore { } // pub(crate) since checker will use this to verify that the free areas are in the correct free list - // Return free_list_id as usize instead of AreaIndex to avoid type conversion + // Since this is a low-level iterator, we avoid safe conversion to AreaIndex for performance pub(crate) fn free_list_iter_inner( &self, start_area_index: AreaIndex, - ) -> impl Iterator> { + ) -> impl Iterator> { self.header .free_lists .iter() @@ -1728,7 +1728,7 @@ impl NodeStore { .skip(start_area_index as usize) .flat_map(move |(free_list_id, next_addr)| { FreeListIterator::new(self.storage.as_ref(), *next_addr).map(move |item| { - item.map(|(addr, area_index)| (addr, area_index, free_list_id)) + item.map(|(addr, area_index)| (addr, area_index, free_list_id as AreaIndex)) }) }) } @@ -1756,6 +1756,7 @@ pub(crate) mod nodestore_test_utils { AREA_SIZES[area_size_index as usize] } + // Helper function to write a free area to the given offset. pub(crate) fn test_write_free_area( nodestore: &NodeStore, next_free_block: Option, @@ -1771,15 +1772,19 @@ pub(crate) mod nodestore_test_utils { nodestore.storage.write(offset, &stored_area_bytes).unwrap(); } + // Helper function to write the NodeStoreHeader pub(crate) fn test_write_header( - nodestore: &NodeStore, - root_addr: LinearAddress, + nodestore: &mut NodeStore, size: u64, + root_addr: Option, + free_lists: FreeLists, ) { let mut header = NodeStoreHeader::new(); header.size = size; - header.root_address = Some(root_addr); + header.root_address = root_addr; + header.free_lists = free_lists; let header_bytes = bytemuck::bytes_of(&header); + nodestore.header = header; nodestore.storage.write(0, header_bytes).unwrap(); } } From 684acb58f3c3be7a85577583c79e3388f0f9e257 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 3 Jul 2025 14:40:59 -0700 Subject: [PATCH 0804/1053] fix: logger macros causing linting warnings (#1021) Previously, the logger macros were empty when logging was disabled. This caused no code to be generated which resulted in warnings from clippy around dead code. This change bypasses those warnings by generating code that lexically uses the parameters passed in however at runtime will never be executed and will be elided by the compiler when optimizations are enabled and logging is disabled. --- firewood/src/manager.rs | 15 ++++-------- storage/src/hashers/ethhash.rs | 42 ++++++++++++---------------------- storage/src/logger.rs | 15 +++++++++++- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index e8e8bbd649e5..10b2f12cbec3 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -1,13 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![cfg_attr( - feature = "ethhash", - expect( - clippy::used_underscore_binding, - reason = "Found 1 occurrences after enabling the lint." - ) -)] #![expect( clippy::cast_precision_loss, reason = "Found 2 occurrences after enabling the lint." @@ -24,7 +17,7 @@ use std::path::PathBuf; use std::sync::OnceLock; use std::sync::{Arc, Mutex, RwLock}; -use firewood_storage::logger::{trace, trace_enabled, warn}; +use firewood_storage::logger::{trace, warn}; use metrics::gauge; use typed_builder::TypedBuilder; @@ -258,9 +251,9 @@ impl RevisionManager { proposal.commit_reparent(p); } - if trace_enabled() { - let _merkle = Merkle::from(committed); - trace!("{}", _merkle.dump().expect("failed to dump merkle")); + if crate::logger::trace_enabled() { + let merkle = Merkle::from(committed); + trace!("{}", merkle.dump().expect("failed to dump merkle")); } Ok(()) diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 98956b8094e6..0612a16b95ff 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -22,9 +22,7 @@ use std::iter::once; use crate::logger::warn; use crate::{ - HashType, Hashable, Preimage, TrieHash, ValueDigest, - hashednode::HasUpdate, - logger::{trace, trace_enabled}, + HashType, Hashable, Preimage, TrieHash, ValueDigest, hashednode::HasUpdate, logger::trace, }; use bitfield::bitfield; use bytes::BytesMut; @@ -97,7 +95,7 @@ impl Preimage for T { let mut collector = SmallVec::with_capacity(32); self.write(&mut collector); - if trace_enabled() { + if crate::logger::trace_enabled() { if self.key().size_hint().0 == 64 { trace!("SIZE WAS 64 {}", hex::encode(&collector)); } else { @@ -163,13 +161,11 @@ impl Preimage for T { } let bytes = rlp.out(); - if crate::logger::trace_enabled() { - trace!( - "partial path {:?}", - hex::encode(self.partial_path().collect::>()) - ); - trace!("serialized leaf-rlp: {:?}", hex::encode(&bytes)); - } + trace!( + "partial path {:?}", + hex::encode(self.partial_path().collect::>()) + ); + trace!("serialized leaf-rlp: {:?}", hex::encode(&bytes)); buf.update(&bytes); } else { // for a branch, there are always 16 children and a value @@ -203,9 +199,7 @@ impl Preimage for T { rlp.append_empty_data(); } let bytes = rlp.out(); - if crate::logger::trace_enabled() { - trace!("pass 1 bytes {:02X?}", hex::encode(&bytes)); - } + trace!("pass 1 bytes {:02X?}", hex::encode(&bytes)); // we've collected all the children in bytes @@ -260,9 +254,7 @@ impl Preimage for T { let partial_path = self.partial_path().collect::>(); if partial_path.is_empty() { - if crate::logger::trace_enabled() { - trace!("pass 2=bytes {:02X?}", hex::encode(&updated_bytes)); - } + trace!("pass 2=bytes {:02X?}", hex::encode(&updated_bytes)); buf.update(updated_bytes); } else { let mut final_bytes = RlpStream::new_list(2); @@ -276,9 +268,7 @@ impl Preimage for T { final_bytes.append(&updated_bytes); } let final_bytes = final_bytes.out(); - if crate::logger::trace_enabled() { - trace!("pass 2 bytes {:02X?}", hex::encode(&final_bytes)); - } + trace!("pass 2 bytes {:02X?}", hex::encode(&final_bytes)); buf.update(final_bytes); } } @@ -293,20 +283,16 @@ fn replace_hash, U: AsRef<[u8]>>(bytes: T, new_hash: U) -> Option let replace = list.get_mut(2)?; *replace = Vec::from(new_hash.as_ref()); - if trace_enabled() { - trace!("inbound bytes: {}", hex::encode(bytes.as_ref())); - trace!("list length was {}", list.len()); - trace!("replacement hash {:?}", hex::encode(&new_hash)); - } + trace!("inbound bytes: {}", hex::encode(bytes.as_ref())); + trace!("list length was {}", list.len()); + trace!("replacement hash {:?}", hex::encode(&new_hash)); let mut rlp = RlpStream::new_list(list.len()); for item in list { rlp.append(&item); } let bytes = rlp.out(); - if trace_enabled() { - trace!("updated encoded value {:02X?}", hex::encode(&bytes)); - } + trace!("updated encoded value {:02X?}", hex::encode(&bytes)); Some(bytes) } diff --git a/storage/src/logger.rs b/storage/src/logger.rs index a6bd7615e60a..890b56bf35da 100644 --- a/storage/src/logger.rs +++ b/storage/src/logger.rs @@ -23,7 +23,20 @@ mod noop_logger { #[macro_export] /// A noop logger, when the logger feature is disabled macro_rules! noop { - ($($arg:tt)+) => {}; + ($($arg:tt)+) => { + if false { + // This is a no-op. If we had an empty macro, the compiler and + // clippy would generate warnings about variables in the + // expressions passed into the macro going unused. + // + // This is a workaround to avoid that. The `false` branch will + // never be execute, the expressions passed in will never be + // evaluated, this string will never be constructed, and the + // compiler will completely eliminate this branch when any + // level of optimization is enabled. + let _ = format!($($arg)+); + } + }; } pub use noop as debug; From ac6353066b38f22e2800fb7b1a7f5f5c5eacbd42 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 7 Jul 2025 11:41:59 -0400 Subject: [PATCH 0805/1053] ci: add concurrency group to attach static libs workflow (#1038) Adds a concurrency group to the attach static libs workflow https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflow-syntax-for-github-actions#example-using-concurrency-and-the-default-behavior . The attach static libs GitHub action pushes/pulls to a branch derived from the PR branch title to https://github.com/ava-labs/firewood-go-ethhash throughout the workflow. If multiple commits on the same branch concurrently attempt to push/pull the same branch they may interfere leading to unexpected results (pulled different code than it pushed) or git errors. This change ensures that the workflow started by commit 1 will be cancelled before starting the workflow for commit 2. This is intended to fix an issue observed in CI on https://github.com/ava-labs/firewood/pull/1024 CI running for commit 0 https://github.com/ava-labs/firewood/actions/runs/16055519899/job/45308974261#step:3:8 succeeded, but the timestamps show it ran concurrently with the next commit, which failed to push https://github.com/ava-labs/firewood/actions/runs/16055531848/job/45308958694#step:9:66. Screenshot 2025-07-07 at 11 34 22 Screenshot 2025-07-07 at 11 37 49 Subsequent workflow runs failed to check out the branch: https://github.com/ava-labs/firewood/actions/runs/16055543248/job/45309129819 Screenshot 2025-07-07 at 11 39 28 Conflicting attempts to push to the same branch seems to be the most likely cause given the conflicting timestamps and attempts to force push/pull to the same branch name when multiple commits can run the same workflow in parallel. --- .github/workflows/attach-static-libs.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 49d6881cc064..f0d1632a713c 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -16,6 +16,10 @@ on: env: CARGO_TERM_COLOR: always +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + # Build, upload, and collect static libraries for each target architecture, # so that golang projects can import the FFI package without needing to # recompile Firewood locally. From 6cbcd3d4ba67a88c9991bec7ca5d4973be340295 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 7 Jul 2025 13:22:01 -0700 Subject: [PATCH 0806/1053] chore: Bump version to v0.0.8 (#1018) --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++++++------ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879bc88f06c8..2bedcb57e1e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to this project will be documented in this file. +## [0.0.8] - 2025-07-07 + +### 🚀 Features + +- *(checker)* Firewood checker framework (#936) +- Enable a configurable free list cache in the FFI (#1017) +- *(nodestore)* Add functionalities to iterate the free list (#1015) +- *(checker)* Traverse free lists (#1026) + +### 🐛 Bug Fixes + +- Unnecessary quotes in publish action (#996) +- Report IO errors (#1005) +- Publish firewood-macros (#1019) +- Logger macros causing linting warnings (#1021) + +### 💼 Other + +- *(deps)* Update lru requirement from 0.14.0 to 0.15.0 (#1001) +- *(deps)* Update lru requirement from 0.15.0 to 0.16.0 (#1023) +- *(deps)* Upgrade sha2, tokio, clap, fastrace, serde... (#1025) + +### 🚜 Refactor + +- *(deps)* Move duplicates to workspace (#1002) +- *(ffi)* [**breaking**] Split starting metrics exporter from db startup (#1016) + +### 📚 Documentation + +- README cleanup (#1024) + +### ⚡ Performance + +- Cache the latest view (#1004) +- Allow cloned proposals (#1010) +- Break up the RevisionManager lock (#1027) + +### ⚙️ Miscellaneous Tasks + +- Suppress clippy::cast\_possible\_truncation across the workspace (#1012) +- Clippy pushdown (#1011) +- Allow some extra pedantic warnings (#1014) +- Check for metrics changes (#1013) +- Share workspace metadata and packages (#1020) +- Add concurrency group to attach static libs workflow (#1038) +- Bump version to v0.0.8 (#1018) + ## [0.0.7] - 2025-06-26 ### 🚀 Features @@ -31,6 +78,7 @@ All notable changes to this project will be documented in this file. - *(fuzz)* Add step to upload fuzz testdata on failure (#990) - Add special case for non semver tags to attach static libs (#992) - Remove requirement for conventional commits (#994) +- Release v0.0.7 (#997) ## [0.0.6] - 2025-06-21 diff --git a/Cargo.toml b/Cargo.toml index ba5ac781658a..c8369b68dcf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.0.7" +version = "0.0.8" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -53,11 +53,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.7" } -firewood-macros = { path = "firewood-macros", version = "0.0.7" } -firewood-storage = { path = "storage", version = "0.0.7" } -firewood-ffi = { path = "ffi", version = "0.0.7" } -firewood-triehash = { path = "triehash", version = "0.0.7" } +firewood = { path = "firewood", version = "0.0.8" } +firewood-macros = { path = "firewood-macros", version = "0.0.8" } +firewood-storage = { path = "storage", version = "0.0.8" } +firewood-ffi = { path = "ffi", version = "0.0.8" } +firewood-triehash = { path = "triehash", version = "0.0.8" } # common dependencies metrics = "0.24.2" From 6d9e25c4a4495e7d7fd91e7d6dab5e6e9a2532ee Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 7 Jul 2025 13:42:26 -0700 Subject: [PATCH 0807/1053] chore(build): Remove unused dependencies (#1037) Fixes #1032 --- .github/workflows/ci.yaml | 19 +++++++++++++++++++ Cargo.toml | 4 +++- benchmark/Cargo.toml | 1 - ffi/Cargo.toml | 4 +++- firewood/Cargo.toml | 8 ++++---- grpc-testtool/Cargo.toml | 5 +---- storage/Cargo.toml | 2 +- 7 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e372b788c87a..4173627fff78 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -285,3 +285,22 @@ jobs: name: firewood-merkle-differential-fuzz-testdata path: ffi/tests/firewood/testdata retention-days: 30 + + stale-deps: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + with: + save-if: "false" + shared-key: "debug-no-features" + - name: Install cargo-machete + run: cargo install cargo-machete + - name: Check for stale dependencies + run: cargo machete --with-metadata + diff --git a/Cargo.toml b/Cargo.toml index c8369b68dcf4..a1ba248c0c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ members = [ "storage", "triehash", ] +exclude = [ + "grpc-testtool", +] resolver = "2" [workspace.package] @@ -70,7 +73,6 @@ bincode = "1.3.3" serde = "1.0.219" thiserror = "2.0.12" coarsetime = "0.1.36" -integer-encoding = "4.0.2" env_logger = "0.11.8" smallvec = "1.15.1" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 30c622bf17e5..a9741a8ea6ed 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -36,7 +36,6 @@ fastrace-opentelemetry = { version = "0.12.0" } opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry = "0.30.0" opentelemetry_sdk = "0.30.0" -strum = "0.27.1" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index e7fd80e10e78..374c08ba6f57 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -20,7 +20,6 @@ rust-version.workspace = true crate-type = ["staticlib"] [dependencies] -libc = "0.2.174" firewood.workspace = true metrics = { workspace = true } metrics-util = { workspace = true } @@ -41,3 +40,6 @@ cbindgen = "0.29.0" [lints] workspace = true + +[package.metadata.cargo-machete] +ignored = ["cbindgen"] diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index f1e606ee4027..83723bb61e84 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -28,17 +28,14 @@ async-trait = "0.1.88" futures = "0.3.31" hex = "0.4.3" metrics = { workspace = true } -serde = { workspace = true } sha2 = { workspace = true } test-case = "3.3.1" thiserror = { workspace = true } typed-builder = "0.21.0" -bincode = { workspace = true } -integer-encoding = { workspace = true } -smallvec = { workspace = true } fastrace = { workspace = true } coarsetime = { workspace = true } firewood-macros.workspace = true +tokio = "1.0.0" [features] default = [] @@ -76,3 +73,6 @@ firewood-storage.workspace = true [lints] workspace = true + +[package.metadata.cargo-machete] +ignored = ["coarsetime", "hex-literal"] diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 46df3ef0bd76..985b7dad24db 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -42,7 +42,4 @@ name = "insert" harness = false [package.metadata.cargo-machete] -ignored = ["prost", "tonic_build"] - -[lints] -workspace = true +ignored = ["prost", "tonic-build"] diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 96e0a587a804..3d07f78399c2 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -24,7 +24,7 @@ hex = "0.4.3" serde = { workspace = true, features = ["derive"] } smallvec = { workspace = true, features = ["serde", "write", "union"] } sha2 = { workspace = true } -integer-encoding = { workspace = true } +integer-encoding = "4.0.2" arc-swap = "1.7.1" lru = "0.16.0" metrics = { workspace = true } From 3fb53e9064f6a3276b0715af3801f263d5c01d28 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 7 Jul 2025 14:25:07 -0700 Subject: [PATCH 0808/1053] feat(ffi): Add gauges to metrics reporter (#1035) Gauges weren't being reported via the homegrown HTTP endpoint we used for ffi, now they are. This is particularly important since we now have a few useful gauges in firewood. The surrounding HTTP structure might be going away, but the underlying `stats` call is likely to stay, so this enhancement is probably still useful. Also took the opportunity to reduce memory allocations here, and write directly to strings instead of converting back and forth between string/bytes/string with all the utf8 conversions. Histograms are still not supported, but we don't have any, and aren't likely to add any anytime soon. --- ffi/src/metrics_setup.rs | 128 +++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index ff93470c150b..941cfaba88cf 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -1,10 +1,11 @@ -use std::collections::HashSet; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::io::Write; +use std::fmt::Write; use std::net::Ipv6Addr; use std::ops::Deref; -use std::sync::Arc; use std::sync::atomic::Ordering; +use std::sync::{Arc, Mutex}; use std::time::SystemTime; use oxhttp::Server; @@ -23,6 +24,7 @@ use metrics_util::registry::{AtomicStorage, Registry}; pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { let inner: TextRecorderInner = TextRecorderInner { registry: Registry::atomic(), + help: Mutex::new(HashMap::new()), }; let recorder = TextRecorder { inner: Arc::new(inner), @@ -54,6 +56,7 @@ pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { #[derive(Debug)] struct TextRecorderInner { registry: Registry, + help: Mutex>, } #[derive(Debug, Clone)] @@ -63,7 +66,7 @@ struct TextRecorder { impl TextRecorder { fn stats(&self) -> String { - let mut output = Vec::new(); + let mut output = String::new(); let systemtime_now = SystemTime::now(); let utc_now: DateTime = systemtime_now.into(); let epoch_duration = systemtime_now @@ -75,40 +78,91 @@ impl TextRecorder { .saturating_add(u64::from(epoch_duration.subsec_millis())); writeln!(output, "# {utc_now}").unwrap(); + let help_guard = self.inner.help.lock().expect("poisoned lock"); + let counters = self.registry.get_counter_handles(); - let mut seen = HashSet::new(); + let mut seen_counters = HashSet::new(); for (key, counter) in counters { - let sanitized_key_name = key.name().to_string().replace('.', "_"); - if !seen.contains(&sanitized_key_name) { - writeln!( - output, - "# TYPE {} counter", - key.name().to_string().replace('.', "_") - ) - .expect("write error"); - seen.insert(sanitized_key_name.clone()); + let sanitized_key_name = self.sanitize_key_name(key.name()); + if !seen_counters.contains(sanitized_key_name.as_ref()) { + if let Some(help) = help_guard.get(sanitized_key_name.as_ref()) { + writeln!(output, "# HELP {sanitized_key_name} {help}").expect("write error"); + } + writeln!(output, "# TYPE {} counter", &sanitized_key_name).expect("write error"); + seen_counters.insert(sanitized_key_name.to_string()); } write!(output, "{sanitized_key_name}").expect("write error"); - if key.labels().len() > 0 { - write!( - output, - "{{{}}}", - key.labels() - .map(|label| format!("{}=\"{}\"", label.key(), label.value())) - .collect::>() - .join(",") - ) + self.write_labels(&mut output, key.labels()); + + writeln!(output, " {} {}", counter.load(Ordering::Relaxed), epoch_ms) .expect("write error"); + } + + // Get gauge handles: Uses self.registry.get_gauge_handles() to retrieve all registered gauges + let gauges = self.registry.get_gauge_handles(); + // Track seen gauges. We don't reuse the hashset above to allow us to check for duplicates. + let mut seen_gauges = HashSet::new(); + for (key, gauge) in gauges { + let sanitized_key_name = self.sanitize_key_name(key.name()); + if !seen_gauges.contains(sanitized_key_name.as_ref()) { + // Output type declaration: Writes # TYPE {name} gauge for each unique gauge name + if let Some(help) = help_guard.get(sanitized_key_name.as_ref()) { + writeln!(output, "# HELP {sanitized_key_name} {help}").expect("write error"); + } + writeln!(output, "# TYPE {sanitized_key_name} gauge").expect("write error"); + seen_gauges.insert(sanitized_key_name.to_string()); } - writeln!(output, " {} {}", counter.load(Ordering::Relaxed), epoch_ms) + // Format metric line: Outputs the gauge name, labels (if any), current value, and timestamp + write!(output, "{sanitized_key_name}").expect("write error"); + self.write_labels(&mut output, key.labels()); + // Load gauge value: Uses gauge.load(Ordering::Relaxed) to get the current gauge value + writeln!(output, " {} {}", gauge.load(Ordering::Relaxed), epoch_ms) .expect("write error"); } + + // Prometheus does not support multiple TYPE declarations for the same metric, + // but we will emit them anyway, and panic if we're in debug mode. + debug_assert_eq!( + seen_gauges.intersection(&seen_counters).count(), + 0, + "duplicate name(s) for gauge and counter: {:?}", + seen_gauges.intersection(&seen_counters).collect::>() + ); + drop(help_guard); writeln!(output).expect("write error"); - output.flush().expect("flush error"); - std::str::from_utf8(output.as_slice()) - .expect("failed to convert to string") - .into() + output + } + + // helper to write labels to the output, minimizing allocations + fn write_labels<'a, I: Iterator>( + &self, + output: &mut impl Write, + labels: I, + ) { + let mut label_iter = labels.into_iter(); + if let Some(first_label) = label_iter.next() { + write!( + output, + "{{{}=\"{}\"", + first_label.key(), + first_label.value() + ) + .expect("write error"); + label_iter.for_each(|label| { + write!(output, ",{}=\"{}\"", label.key(), label.value()).expect("write error"); + }); + write!(output, "}}").expect("write error"); + } + } + + // remove dots from key names, if they are present + fn sanitize_key_name<'a>(&self, key_name: &'a str) -> Cow<'a, str> { + if key_name.contains('.') { + Cow::Owned(key_name.replace('.', "_")) + } else { + Cow::Borrowed(key_name) + } } } @@ -123,18 +177,28 @@ impl Deref for TextRecorder { impl metrics::Recorder for TextRecorder { fn describe_counter( &self, - _key: metrics::KeyName, + key: metrics::KeyName, _unit: Option, - _description: metrics::SharedString, + description: metrics::SharedString, ) { + self.inner + .help + .lock() + .expect("poisoned lock") + .insert(key.as_str().to_string(), description.to_string()); } fn describe_gauge( &self, - _key: metrics::KeyName, + key: metrics::KeyName, _unit: Option, - _description: metrics::SharedString, + description: metrics::SharedString, ) { + self.inner + .help + .lock() + .expect("poisoned lock") + .insert(key.as_str().to_string(), description.to_string()); } fn describe_histogram( From 7c14009e2c0be21e745ea69a08adb0634f496e7c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 7 Jul 2025 14:30:48 -0700 Subject: [PATCH 0809/1053] chore: update firewood in grpc-testtool (#1040) I noticed in this action output an error from the incorrect firewood version: https://github.com/ava-labs/firewood/actions/runs/16127166800/job/45506937221#step:7:13 --- grpc-testtool/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml index 985b7dad24db..415d5954f6ab 100644 --- a/grpc-testtool/Cargo.toml +++ b/grpc-testtool/Cargo.toml @@ -17,7 +17,7 @@ test = false bench = false [dependencies] -firewood = { version = "0.0.7", path = "../firewood" } +firewood = { version = "*", path = "../firewood" } prost = "0.13.1" tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } tonic = { version = "0.13.0", features = ["tls-ring"] } @@ -33,7 +33,7 @@ serde = { version = "1.0.196", features = ["derive"] } tonic-build = "0.13.0" [dev-dependencies] -criterion = {version = "0.6.0", features = ["async_tokio"]} +criterion = { version = "0.6.0", features = ["async_tokio"] } rand = "0.9.1" rand_distr = "0.5.0" From e929fbe0f5143af7f40fb1dc692dc53661f3c410 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Jul 2025 08:53:46 -0700 Subject: [PATCH 0810/1053] fix: Avoid reference to LinearAddress (#1042) Upcoming changes make supporting this harder, and it's not necessary, as the size of a LinearAddress and the size of a pointer are the same, so it's more efficient to return the actual value rather than a pointer to the value. --- storage/src/checker.rs | 2 +- storage/src/node/branch.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/storage/src/checker.rs b/storage/src/checker.rs index 3f4682ab72ae..60bbc836d241 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -63,7 +63,7 @@ impl NodeStore { if let Node::Branch(branch) = self.read_node(subtree_root_address)?.as_ref() { // this is an internal node, traverse the children for (_, address) in branch.children_addresses() { - self.visit_trie(*address, visited)?; + self.visit_trie(address, visited)?; } } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 27ad94455a39..550c30ea00d1 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -310,14 +310,14 @@ impl BranchNode { } // Helper to iterate over only valid children - fn children_iter(&self) -> impl Iterator + Clone { + fn children_iter(&self) -> impl Iterator + Clone { self.children .iter() .enumerate() .filter_map(|(i, child)| match child { None => None, Some(Child::Node(_)) => unreachable!("TODO make unreachable"), - Some(Child::AddressWithHash(address, hash)) => Some((i, (address, hash))), + Some(Child::AddressWithHash(address, hash)) => Some((i, (*address, hash))), }) } @@ -327,7 +327,7 @@ impl BranchNode { } /// Returns (index, address) for each child that has a hash set. - pub fn children_addresses(&self) -> impl Iterator + Clone { + pub fn children_addresses(&self) -> impl Iterator + Clone { self.children_iter() .map(|(idx, (address, _))| (idx, address)) } From 3666daebfc4d2184ff5832c23e07861be7ecf4e5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Jul 2025 10:06:53 -0700 Subject: [PATCH 0811/1053] chore: Aaron is requested only for .github (#1043) Since github actions is his favorite programming language, let's keep him as a reviewer of those tickets. Everything else should be reviewed by the firewood team now. --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index a0cd42ca1aa3..1a2ad0f9bf5e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,4 @@ # CODEOWNERS -* @rkuris @aaronbuchwald @demosdemon +* @rkuris @demosdemon /ffi @alarso16 +/.github @aaronbuchwald From b9ff9daa4abe62595002d4aa30bc66758e1ae426 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 8 Jul 2025 10:28:30 -0700 Subject: [PATCH 0812/1053] chore: remove `#[allow]`s no longer needed (#1022) This cleans up a lot of the remaining `#[allow]` attributes in the code base. Only one remains, `firewood-macros/tests/compile_pass/with_attributes.rs` because I did not want to alter the tests of the macro. --- benchmark/src/tenkrandom.rs | 1 - ffi/src/lib.rs | 7 +- ffi/src/metrics_setup.rs | 2 +- firewood-macros/src/lib.rs | 3 +- firewood/benches/hashops.rs | 5 +- firewood/src/manager.rs | 5 +- firewood/src/merkle.rs | 47 +++--- firewood/src/proof.rs | 7 +- firewood/tests/common/mod.rs | 90 ---------- firewood/tests/db.rs | 296 --------------------------------- firewood/tests/v2api.rs | 45 ----- storage/src/hashers/ethhash.rs | 1 - storage/src/lib.rs | 5 +- storage/src/node/branch.rs | 27 +++ storage/src/node/mod.rs | 10 +- storage/src/nodestore.rs | 2 +- storage/src/range_set.rs | 5 +- 17 files changed, 75 insertions(+), 483 deletions(-) delete mode 100644 firewood/tests/common/mod.rs delete mode 100644 firewood/tests/db.rs delete mode 100644 firewood/tests/v2api.rs diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 926921b6db52..d58fd01fc104 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -66,7 +66,6 @@ fn generate_deletes(start: u64, count: u64) -> impl Iterator) { } #[cfg(test)] -#[allow(clippy::unwrap_used)] mod tests { + #![expect(clippy::unwrap_used)] + use super::*; #[test] diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 941cfaba88cf..1aafd2d6f0d4 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -76,7 +76,7 @@ impl TextRecorder { .as_secs() .saturating_mul(1000) .saturating_add(u64::from(epoch_duration.subsec_millis())); - writeln!(output, "# {utc_now}").unwrap(); + writeln!(output, "# {utc_now}").expect("write to string cannot fail"); let help_guard = self.inner.help.lock().expect("poisoned lock"); diff --git a/firewood-macros/src/lib.rs b/firewood-macros/src/lib.rs index 8b6293641bb1..b6d8a30af686 100644 --- a/firewood-macros/src/lib.rs +++ b/firewood-macros/src/lib.rs @@ -214,6 +214,8 @@ fn generate_metrics_wrapper(input_fn: &ItemFn, args: &MetricsArgs) -> proc_macro #[cfg(test)] mod tests { + #![expect(clippy::unwrap_used)] + use super::*; #[test] @@ -225,7 +227,6 @@ mod tests { } #[test] - #[allow(clippy::unwrap_used)] fn test_metrics_args_parsing() { // Test single argument parsing let input = quote::quote! { "test.metric" }; diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index b010a72803c6..e835615d5859 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -87,8 +87,9 @@ fn bench_merkle(criterion: &mut Criter }, #[expect(clippy::unwrap_used)] |(mut merkle, keys)| { - keys.into_iter() - .for_each(|key| merkle.insert(&key, Box::new(*b"v")).unwrap()); + for key in keys { + merkle.insert(&key, Box::new(*b"v")).unwrap(); + } let _frozen = merkle.hash(); }, BatchSize::SmallInput, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 10b2f12cbec3..41d452754d0c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -317,12 +317,9 @@ impl RevisionManager { #[cfg(feature = "ethhash")] #[inline] pub fn empty_trie_hash(&self) -> Option { - // clippy is wrong here. we need to keep the closure since empty_trie_hash - // is an instance method that needs self. - #[allow(clippy::redundant_closure)] Some( self.empty_hash - .get_or_init(|| firewood_storage::empty_trie_hash()) + .get_or_init(firewood_storage::empty_trie_hash) .clone(), ) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 78772d87cc39..34fb65b5525c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -19,21 +19,28 @@ )] use crate::proof::{Proof, ProofError, ProofNode}; +#[cfg(test)] use crate::range_proof::RangeProof; -use crate::stream::{MerkleKeyValueStream, PathIterator}; +#[cfg(test)] +use crate::stream::MerkleKeyValueStream; +use crate::stream::PathIterator; +#[cfg(test)] use crate::v2::api; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, - LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, + IntoHashType, LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, ReadableStorage, SharedNode, TrieReader, ValueDigest, }; +#[cfg(test)] use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; use std::fmt::{Debug, Write}; +#[cfg(test)] use std::future::ready; use std::io::Error; use std::iter::once; +#[cfg(test)] use std::num::NonZeroUsize; use std::sync::Arc; @@ -235,12 +242,12 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } - #[allow(dead_code)] + #[cfg(test)] pub(super) fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { MerkleKeyValueStream::from(&self.nodestore) } - #[allow(dead_code)] + #[cfg(test)] pub(super) fn key_value_iter_from_key>( &self, key: K, @@ -249,7 +256,7 @@ impl Merkle { MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) } - #[allow(dead_code)] + #[cfg(test)] pub(super) async fn range_proof( &self, start_key: Option<&[u8]>, @@ -421,11 +428,13 @@ impl Merkle { .map_err(|e| FileIoError::new(e, None, 0, None)) .map_err(Error::other)?; let mut seen = HashSet::new(); - // If ethhash is off, root_hash.into() is already the correct type - // so we disable the warning here - #[allow(clippy::useless_conversion)] - self.dump_node(root_addr, Some(&root_hash.into()), &mut seen, &mut result) - .map_err(Error::other)?; + self.dump_node( + root_addr, + Some(&root_hash.into_hash_type()), + &mut seen, + &mut result, + ) + .map_err(Error::other)?; } write!(result, "}}") .map_err(Error::other) @@ -1993,7 +2002,6 @@ mod tests { } #[test] - #[allow(clippy::unwrap_used)] fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -2095,7 +2103,6 @@ mod tests { } // #[test] - // #[allow(clippy::unwrap_used)] // fn test_root_hash_random_deletions() -> Result<(), Error> { // use rand::rngs::StdRng; // use rand::seq::SliceRandom; @@ -2171,7 +2178,6 @@ mod tests { // } // #[test] - // #[allow(clippy::unwrap_used, clippy::indexing_slicing)] // fn test_proof() -> Result<(), Error> { // let set = fixed_and_pseudorandom_data(500); // let mut items = Vec::from_iter(set.iter()); @@ -2288,7 +2294,6 @@ mod tests { // #[test] // // Tests normal range proof with both edge proofs as the existent proof. // // The test cases are generated randomly. - // #[allow(clippy::indexing_slicing)] // fn test_range_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2324,7 +2329,6 @@ mod tests { // #[test] // // Tests a few cases which the proof is wrong. // // The prover is expected to detect the error. - // #[allow(clippy::indexing_slicing)] // fn test_bad_range_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2404,7 +2408,6 @@ mod tests { // #[test] // // Tests normal range proof with two non-existent proofs. // // The test cases are generated randomly. - // #[allow(clippy::indexing_slicing)] // fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2474,7 +2477,6 @@ mod tests { // // Tests such scenarios: // // - There exists a gap between the first element and the left edge proof // // - There exists a gap between the last element and the right edge proof - // #[allow(clippy::indexing_slicing)] // fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2533,7 +2535,6 @@ mod tests { // #[test] // // Tests the proof with only one element. The first edge proof can be existent one or // // non-existent one. - // #[allow(clippy::indexing_slicing)] // fn test_one_element_range_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2621,7 +2622,6 @@ mod tests { // #[test] // // Tests the range proof with all elements. // // The edge proofs can be nil. - // #[allow(clippy::indexing_slicing)] // fn test_all_elements_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2677,7 +2677,6 @@ mod tests { // #[test] // // Tests the range proof with "no" element. The first edge proof must // // be a non-existent proof. - // #[allow(clippy::indexing_slicing)] // fn test_empty_range_proof() -> Result<(), ProofError> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2709,7 +2708,6 @@ mod tests { // #[test] // // Focuses on the small trie with embedded nodes. If the gapped // // node is embedded in the trie, it should be detected too. - // #[allow(clippy::indexing_slicing)] // fn test_gapped_range_proof() -> Result<(), ProofError> { // let mut items = Vec::new(); // // Sorted entries @@ -2748,7 +2746,6 @@ mod tests { // #[test] // // Tests the element is not in the range covered by proofs. - // #[allow(clippy::indexing_slicing)] // fn test_same_side_proof() -> Result<(), Error> { // let set = fixed_and_pseudorandom_data(4096); // let mut items = Vec::from_iter(set.iter()); @@ -2800,7 +2797,6 @@ mod tests { // } // #[test] - // #[allow(clippy::indexing_slicing)] // // Tests the range starts from zero. // fn test_single_side_range_proof() -> Result<(), ProofError> { // for _ in 0..10 { @@ -2834,7 +2830,6 @@ mod tests { // } // #[test] - // #[allow(clippy::indexing_slicing)] // // Tests the range ends with 0xffff...fff. // fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { // for _ in 0..10 { @@ -2898,7 +2893,6 @@ mod tests { // } // #[test] - // #[allow(clippy::indexing_slicing)] // // Tests normal range proof with both edge proofs // // as the existent proof, but with an extra empty value included, which is a // // noop technically, but practically should be rejected. @@ -2934,7 +2928,6 @@ mod tests { // } // #[test] - // #[allow(clippy::indexing_slicing)] // // Tests the range proof with all elements, // // but with an extra empty value included, which is a noop technically, but // // practically should be rejected. @@ -3005,7 +2998,6 @@ mod tests { // } // #[test] - // #[allow(clippy::indexing_slicing)] // // Tests a malicious proof, where the proof is more or less the // // whole trie. This is to match corresponding test in geth. // fn test_bloadted_range_proof() -> Result<(), ProofError> { @@ -3044,7 +3036,6 @@ mod tests { // generate pseudorandom data, but prefix it with some known data // The number of fixed data points is 100; you specify how much random data you want - // #[allow(clippy::indexing_slicing)] // fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { // let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); // for i in 0..100_u32 { diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 7bb0ddd2b2ef..afae23efa7ff 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -11,8 +11,8 @@ )] use firewood_storage::{ - BranchNode, FileIoError, HashType, Hashable, NibblesIterator, PathIterItem, Preimage, TrieHash, - ValueDigest, + BranchNode, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, PathIterItem, + Preimage, TrieHash, ValueDigest, }; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -209,8 +209,7 @@ impl Proof { return Err(ProofError::Empty); }; - #[allow(clippy::useless_conversion)] - let mut expected_hash: HashType = root_hash.clone().into(); + let mut expected_hash = root_hash.clone().into_hash_type(); let mut iter = self.0.iter().peekable(); while let Some(node) = iter.next() { diff --git a/firewood/tests/common/mod.rs b/firewood/tests/common/mod.rs deleted file mode 100644 index 7b26006f5866..000000000000 --- a/firewood/tests/common/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#![expect( - clippy::used_underscore_binding, - reason = "Found 3 occurrences after enabling the lint." -)] -#![expect( - clippy::used_underscore_items, - reason = "Found 1 occurrences after enabling the lint." -)] - -use std::env::temp_dir; -use std::fs::remove_file; -use std::ops::Deref; -use std::path::PathBuf; - -use firewood::db::{Db, DbConfig}; -use typed_builder::TypedBuilder; - -#[derive(Clone, Debug, TypedBuilder)] -pub struct TestDbCreator { - #[builder(setter(into))] - _test_name: String, - #[builder(default, setter(into))] - path: Option, - #[builder(default = DbConfig::builder().truncate(true).build())] - _cfg: DbConfig, -} - -pub struct TestDb { - creator: TestDbCreator, - preserve_on_drop: bool, - db: Db, -} - -impl TestDbCreator { - #[expect(clippy::unwrap_used)] - #[allow( - clippy::missing_panics_doc, - reason = "Found 1 occurrences after enabling the lint." - )] - pub async fn _create(self) -> TestDb { - let path = self.path.clone().unwrap_or_else(|| { - let mut path: PathBuf = std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(temp_dir().into()) - .into(); - if path.join("tmp").is_dir() { - path.push("tmp"); - } - path.join(&self._test_name) - }); - let mut creator = self.clone(); - creator.path = path.clone().into(); - let db = Db::new(&path, self._cfg).await.unwrap(); - TestDb { - creator, - db, - preserve_on_drop: false, - } - } -} - -impl Deref for TestDb { - type Target = Db; - - fn deref(&self) -> &Self::Target { - &self.db - } -} - -impl TestDb { - /// reopen the database, consuming the old `TestDb` and giving you a new one - pub async fn _reopen(mut self) -> Self { - let mut creator = self.creator.clone(); - self.preserve_on_drop = true; - drop(self); - creator._cfg.truncate = false; - creator._create().await - } -} - -impl Drop for TestDb { - fn drop(&mut self) { - if !self.preserve_on_drop { - #[expect(clippy::unwrap_used)] - remove_file(self.creator.path.as_ref().unwrap()).unwrap(); - } - } -} diff --git a/firewood/tests/db.rs b/firewood/tests/db.rs deleted file mode 100644 index bf1cb80c3c8c..000000000000 --- a/firewood/tests/db.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -mod common; - -// TODO: use a trait -macro_rules! _kv_dump { - ($e: ident) => {{ - let mut s = Vec::new(); - $e.dump(&mut s).unwrap(); - String::from_utf8(s).unwrap() - }}; -} - -// #[ignore = "unimplemented"] -// #[tokio::test(flavor = "multi_thread")] -// #[expect(clippy::unwrap_used)] -// async fn test_basic_metrics() { -// let cfg = DbConfig::builder(); - -// let db = TestDbCreator::builder() -// .cfg(cfg.build()) -// .test_name("test_basic_metrics") -// .build() -// .create() -// .await; - -// // let metrics = db.metrics(); -// // TODO: kv_get is no longer a valid metric, and DbRev has no access to Db.metrics (yet) -// //assert_eq!(metrics.kv_get.hit_count.get(), 0); - -// // TODO: we can't fetch the revision for the empty tree, so insert a single value - -// db.propose(vec![BatchOp::Put { -// key: b"a", -// value: b"b", -// }]) -// .await -// .unwrap() -// .commit() -// .await -// .unwrap(); - -// let root = db.root_hash().await.unwrap(); -// let rev = db.revision(root).await.unwrap(); -// rev.val("a").await.ok().unwrap().unwrap(); -// //assert_eq!(metrics.val.hit_count.get(), 1); -// } - -// #[ignore = "unimplemented"] -// #[tokio::test(flavor = "multi_thread")] -// #[expect(clippy::unwrap_used)] -// async fn test_revisions() { -// use rand::{rngs::StdRng, Rng, SeedableRng}; -// let cfg = DbConfig::builder().build(); - -// let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); -// let max_len0 = 8; -// let max_len1 = 4; -// let keygen = || { -// let (len0, len1): (usize, usize) = { -// let mut rng = rng.borrow_mut(); -// ( -// rng.gen_range(1..max_len0 + 1), -// rng.gen_range(1..max_len1 + 1), -// ) -// }; -// let key: Vec = (0..len0) -// .map(|_| rng.borrow_mut().gen_range(0..2)) -// .chain((0..len1).map(|_| rng.borrow_mut().gen())) -// .collect(); -// key -// }; - -// for i in 0..10 { -// let db = TestDbCreator::builder() -// .cfg(cfg.clone()) -// .test_name("test_revisions") -// .build() -// .create() -// .await; -// let mut dumped = VecDeque::new(); -// let mut hashes: VecDeque = VecDeque::new(); -// for _ in 0..10 { -// { -// let mut batch = Vec::new(); -// let m = rng.borrow_mut().gen_range(1..20); -// for _ in 0..m { -// let key = keygen(); -// let val: Vec = (0..8).map(|_| rng.borrow_mut().gen()).collect(); -// let write = BatchOp::Put { -// key, -// value: val.to_vec(), -// }; -// batch.push(write); -// } -// let proposal = db.propose(batch).await.unwrap(); -// proposal.commit().await.unwrap(); -// } -// while dumped.len() > 10 { -// dumped.pop_back(); -// hashes.pop_back(); -// } -// let root_hash = db.root_hash().await.unwrap(); -// hashes.push_front(root_hash); -// dumped.push_front(kv_dump!(db)); -// for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { -// let rev = db.revision(hash).await.unwrap(); -// assert_eq!(rev.root_hash().await.unwrap(), hash); -// assert_eq!(kv_dump!(rev), *dump, "not the same: Pass {i}"); -// } -// } - -// let db = db.reopen().await; -// for (dump, hash) in dumped.iter().zip(hashes.iter().cloned()) { -// let rev = db.revision(hash).await.unwrap(); -// rev.root_hash().await.unwrap(); -// assert_eq!( -// *dump, -// block_in_place(|| kv_dump!(rev)), -// "not the same: pass {i}" -// ); -// } -// } -// } - -// #[ignore = "unimplemented"] -// #[tokio::test(flavor = "multi_thread")] -// #[expect(clippy::unwrap_used)] -// async fn create_db_issue_proof() { -// let cfg = DbConfig::builder(); - -// let mut tmpdir: PathBuf = std::env::var_os("CARGO_TARGET_DIR") -// .unwrap_or(temp_dir().into()) -// .into(); -// tmpdir.push("/tmp/test_db_proof"); - -// let db = firewood::db::Db::new(tmpdir, cfg.truncate(true).build()) -// .await -// .unwrap(); - -// let items = vec![ -// ("d", "verb"), -// ("do", "verb"), -// ("doe", "reindeer"), -// ("e", "coin"), -// ]; - -// let mut batch = Vec::new(); -// for (k, v) in items { -// let write = BatchOp::Put { -// key: k.as_bytes(), -// value: v.as_bytes().to_vec(), -// }; -// batch.push(write); -// } -// let proposal = db.propose(batch).await.unwrap(); -// proposal.commit().await.unwrap(); - -// let root_hash = db.root_hash().await.unwrap(); - -// // Add second commit -// let mut batch = Vec::new(); -// for (k, v) in Vec::from([("x", "two")]).iter() { -// let write = BatchOp::Put { -// key: k.to_string().as_bytes().to_vec(), -// value: v.as_bytes().to_vec(), -// }; -// batch.push(write); -// } -// let proposal = db.propose(batch).await.unwrap(); -// proposal.commit().await.unwrap(); - -// let rev = db.revision(root_hash).await.unwrap(); -// let key = "doe".as_bytes(); -// let root_hash = rev.root_hash().await.unwrap(); - -// match rev.single_key_proof(key).await { -// Ok(proof) => { -// let verification = proof.unwrap().verify(key, root_hash).unwrap(); -// assert!(verification.is_some()); -// } -// Err(e) => { -// panic!("Error: {}", e); -// } -// } - -// let missing_key = "dog".as_bytes(); -// // The proof for the missing key will return the path to the missing key -// if let Err(e) = rev.single_key_proof(missing_key).await { -// println!("Error: {}", e); -// // TODO do type assertion on error -// } -// } - -macro_rules! _assert_val { - ($rev: ident, $key:literal, $expected_val:literal) => { - let actual = $rev.val($key.as_bytes()).await.unwrap().unwrap(); - assert_eq!(actual, $expected_val.as_bytes().to_vec()); - }; -} - -// #[ignore = "ref: https://github.com/ava-labs/firewood/issues/457"] -// #[tokio::test(flavor = "multi_thread")] -// #[expect(clippy::unwrap_used)] -// async fn db_proposal() -> Result<(), api::Error> { -// let cfg = DbConfig::builder().build(); - -// let db = TestDbCreator::builder() -// .cfg(cfg) -// .test_name("db_proposal") -// .build() -// .create() -// .await; - -// let batch = vec![ -// BatchOp::Put { -// key: b"k", -// value: "v".as_bytes().to_vec(), -// }, -// BatchOp::Delete { key: b"z" }, -// ]; - -// let proposal = db.propose(batch).await?; -// assert_val!(proposal, "k", "v"); - -// let batch_2 = vec![BatchOp::Put { -// key: b"k2", -// value: "v2".as_bytes().to_vec(), -// }]; -// let proposal_2 = proposal.clone().propose(batch_2).await?; -// assert_val!(proposal_2, "k", "v"); -// assert_val!(proposal_2, "k2", "v2"); - -// proposal.clone().commit().await?; -// proposal_2.commit().await?; - -// let t1 = tokio::spawn({ -// let proposal = proposal.clone(); -// async move { -// let another_batch = vec![BatchOp::Put { -// key: b"another_k_t1", -// value: "another_v_t1".as_bytes().to_vec(), -// }]; -// let _another_proposal = proposal.clone().propose(another_batch).await.unwrap(); -// // TODO: re-enable this once proposals can fetch their view -// // let rev = another_proposal.get_revision(); -// // assert_val!(rev, "k", "v"); -// // assert_val!(rev, "another_k_t1", "another_v_t1"); -// // // The proposal is invalid and cannot be committed -// // assert!(Arc::new(another_proposal).commit().await.is_err()); -// } -// }); -// let t2 = tokio::spawn({ -// let proposal = proposal.clone(); -// async move { -// let another_batch = vec![BatchOp::Put { -// key: b"another_k_t2", -// value: "another_v_t2".as_bytes().to_vec(), -// }]; -// let _another_proposal = proposal.clone().propose(another_batch).await.unwrap(); -// // TODO: re-enable this once proposals can fetch their view -// // let rev = another_proposal.get_revision(); -// // assert_val!(rev, "k", "v"); -// // assert_val!(rev, "another_k_t2", "another_v_t2"); -// // assert!(Arc::new(another_proposal).commit().await.is_err()); -// } -// }); -// let (first, second) = tokio::join!(t1, t2); -// first.unwrap(); -// second.unwrap(); - -// // Recursive commit - -// let batch = vec![BatchOp::Put { -// key: b"k3", -// value: "v3".as_bytes().to_vec(), -// }]; -// let proposal = db.propose(batch).await?; -// assert_val!(proposal, "k", "v"); -// assert_val!(proposal, "k2", "v2"); -// assert_val!(proposal, "k3", "v3"); - -// let batch_2 = vec![BatchOp::Put { -// key: b"k4", -// value: "v4".as_bytes().to_vec(), -// }]; -// let proposal_2 = proposal.clone().propose(batch_2).await?; -// assert_val!(proposal_2, "k", "v"); -// assert_val!(proposal_2, "k2", "v2"); -// assert_val!(proposal_2, "k3", "v3"); -// assert_val!(proposal_2, "k4", "v4"); - -// proposal_2.commit().await?; -// Ok(()) -// } diff --git a/firewood/tests/v2api.rs b/firewood/tests/v2api.rs deleted file mode 100644 index a26ccd97b064..000000000000 --- a/firewood/tests/v2api.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -pub mod common; - -// #[ignore = "unimplemented"] -// #[tokio::test(flavor = "multi_thread")] -// #[expect(clippy::unwrap_used)] -// async fn smoke() -> Result<(), Box> { -// let cfg = DbConfig::builder().truncate(true).build(); -// let db = TestDbCreator::builder() -// .cfg(cfg) -// .test_name("smoke") -// .build() -// .create() -// .await; - -// let empty_hash = db.root_hash().await?; -// assert_ne!(empty_hash, [0; 32]); - -// // insert a single key/value -// let (key, value) = (b"smoke", b"test"); -// let batch_put = BatchOp::Put { key, value }; -// let proposal = db.propose(vec![batch_put]).await?; -// proposal.commit().await?; - -// // ensure the latest hash is different -// let latest = db.root_hash().await?; -// assert_ne!(empty_hash, latest); - -// // fetch the view of the latest -// let view = db.revision(latest).await.unwrap(); - -// // check that the key/value is there -// let got_value = view.val(key).await.unwrap().unwrap(); -// assert_eq!(got_value, value); - -// // TODO: also fetch view of empty; this currently does not work, as you can't reference -// // the empty hash -// // let empty_view = db.revision(empty_hash).await.unwrap(); -// // let value = empty_view.val(b"smoke").await.unwrap(); -// // assert_eq!(value, None); - -// Ok(()) -// } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 0612a16b95ff..1b2fa3441c25 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -203,7 +203,6 @@ impl Preimage for T { // we've collected all the children in bytes - #[allow(clippy::let_and_return)] let updated_bytes = if is_account { // need to get the value again if let Some(ValueDigest::Value(rlp_encoded_bytes)) = self.value_digest() { diff --git a/storage/src/lib.rs b/storage/src/lib.rs index d833f0ff5fa1..9a79eb343ade 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -40,7 +40,10 @@ pub mod logger; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; -pub use node::{BranchNode, Child, LeafNode, Node, PathIterItem, branch::HashType}; +pub use node::{ + BranchNode, Child, LeafNode, Node, PathIterItem, + branch::{HashType, IntoHashType}, +}; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 550c30ea00d1..c2c9456a9102 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -32,6 +32,33 @@ pub type HashType = ethhash::HashOrRlp; /// The type of a hash. For non-ethereum compatible hashes, this is always a `TrieHash`. pub type HashType = crate::TrieHash; +/// A trait to convert a value into a [`HashType`]. +/// +/// This is used to allow different hash types to be conditionally used, e.g., when the +/// `ethhash` feature is enabled. When not enabled, this suppresses the clippy warnings +/// about useless `.into()` calls. +pub trait IntoHashType { + /// Converts the value into a `HashType`. + #[must_use] + fn into_hash_type(self) -> HashType; +} + +#[cfg(feature = "ethhash")] +impl IntoHashType for crate::TrieHash { + #[inline] + fn into_hash_type(self) -> HashType { + self.into() + } +} + +#[cfg(not(feature = "ethhash"))] +impl IntoHashType for crate::TrieHash { + #[inline] + fn into_hash_type(self) -> HashType { + self + } +} + pub(crate) trait Serializable { fn write_to(&self, vec: &mut W); diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index c4c9c361d1ad..6dec87581013 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -504,8 +504,14 @@ mod test { )})), 652; "full branch node with long partial path and value" )] // When ethhash is enabled, we don't actually check the `expected_length` - #[allow(unused_variables)] - fn test_serialize_deserialize(node: Node, expected_length: usize) { + fn test_serialize_deserialize( + node: Node, + #[cfg_attr( + any(feature = "branch_factor_256", feature = "ethhash"), + expect(unused_variables) + )] + expected_length: usize, + ) { use crate::node::Node; use std::io::Cursor; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index f8f5c7c0c46b..86a702901b19 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1703,10 +1703,10 @@ impl Iterator for FreeListIterator<'_, S> { impl FusedIterator for FreeListIterator<'_, S> {} -#[allow(dead_code)] // TODO: free list iterators will be used in the checker impl NodeStore { // Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. // The iterator returns a tuple of the address and the area index of the free area. + #[expect(dead_code)] // TODO: free list iterators will be used in the checker pub(crate) fn free_list_iter( &self, start_area_index: AreaIndex, diff --git a/storage/src/range_set.rs b/storage/src/range_set.rs index b02116bb4a70..2fbb1544400a 100644 --- a/storage/src/range_set.rs +++ b/storage/src/range_set.rs @@ -21,7 +21,6 @@ struct CoalescingRanges<'a, T> { next_adjacent_range: Option>, } -#[allow(dead_code)] impl RangeSet { pub const fn new() -> Self { Self(BTreeMap::new()) @@ -157,6 +156,7 @@ impl RangeSet { } /// Insert the range into the range set. + #[cfg(test)] pub fn insert_range(&mut self, range: Range) { self.insert_range_helper(range, true) .expect("insert range should always success if we allow intersecting area insert"); @@ -305,12 +305,13 @@ mod test_range_set { } #[test] - #[allow(clippy::reversed_empty_ranges)] fn test_insert_empty_range() { let mut range_set = RangeSet::new(); range_set.insert_range(0..0); range_set.insert_range(10..10); + #[expect(clippy::reversed_empty_ranges)] range_set.insert_range(20..10); + #[expect(clippy::reversed_empty_ranges)] range_set.insert_range(30..0); assert_eq!(range_set.into_iter().collect::>(), vec![]); } From d9e93c9f25814152c946159c2b82797c703bf20c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 8 Jul 2025 12:40:28 -0700 Subject: [PATCH 0813/1053] feat(delayed-persist): Part 1: Roots may be in mem (#1041) Introduces a new type, `MaybePersistedNode`, which is a node which is either a SharedNode or a LinearAddress. This new type will allow writes to occur while readers are still reading this revision using an `ArcSwap`. Note that this does not actually put any additional nodes in memory, it just sets things up so they could be in memory. Additional changes needed for this feature to be complete are: - [ ] BranchNode children can now be Node, LinearAddress or a MaybePersistedNode - [ ] When converting a `MutableProposal` to an `ImmutableProposal`, don't actually allocate space, just create `SharedNode`s from the `Node`s. - [ ] Remove `NodeStoreHeader` from `NodeStore`. The header should move into the `RevisionManager`. - [ ] Allocate new nodes from the recently-freed nodes from an expired revision, then from the freelist. This can be done by maintaining a local freelist from the nodes deleted in the expiring revision and falling back to the actual freelist. Deleted nodes that are not reused must still be pushed onto the freelist. --- storage/src/node/mod.rs | 1 + storage/src/node/persist.rs | 192 ++++++++++++++++++++++++++++++++++++ storage/src/nodestore.rs | 23 ++++- 3 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 storage/src/node/persist.rs diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 6dec87581013..7c8d10f7471e 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -35,6 +35,7 @@ use std::vec; pub mod branch; mod leaf; pub mod path; +pub mod persist; pub use branch::{BranchNode, Child}; pub use leaf::LeafNode; diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs new file mode 100644 index 000000000000..7f985523ebfd --- /dev/null +++ b/storage/src/node/persist.rs @@ -0,0 +1,192 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::sync::Arc; + +use arc_swap::ArcSwap; + +use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; + +/// A node that is either in memory or on disk. +/// +/// In-memory nodes that can be moved to disk. This structure allows that to happen +/// atomically. +/// +/// `MaybePersistedNode` owns a reference counted pointer to an atomically swapable +/// pointer. The atomically swapable pointer points to a reference counted pointer +/// to the enum of either an un-persisted but committed (or committing) node or the +/// linear address of a persisted node. +/// +/// This type is complicated, so here is a breakdown of the types involved: +/// +/// | Item | Description | +/// |------------------------|--------------------------------------------------------| +/// | [`MaybePersistedNode`] | Newtype wrapper around the remaining items. | +/// | [Arc] | reference counted pointer to an updatable pointer. | +/// | [`ArcSwap`] | Swappable [Arc]. Actually an `ArcSwapAny`<`Arc`<_>> | +/// | [Arc] | reference-counted pointer to the enum (required by `ArcSwap`) | +/// | `MaybePersisted` | Enum of either `Unpersisted` or `Persisted` | +/// | variant `Unpersisted` | The shared node, in memory, for unpersisted nodes | +/// | -> [`SharedNode`] | A `triomphe::Arc` of a [Node](`crate::Node`) | +/// | variant `Persisted` | The address of a persisted node. | +/// | -> [`LinearAddress`] | A 64-bit address for a persisted node on disk. | +/// +/// Traversing these pointers does incur a runtime penalty. +/// +/// When an `Unpersisted` node is `Persisted` using [`MaybePersistedNode::persist_at`], +/// a new `Arc` is created to the new `MaybePersisted::Persisted` variant and the `ArcSwap` +/// is updated atomically. Subsequent accesses to any instance of it, including any clones, +/// will see the `Persisted` node address. +#[derive(Debug, Clone)] +pub struct MaybePersistedNode(Arc>); + +impl From for MaybePersistedNode { + fn from(node: SharedNode) -> Self { + MaybePersistedNode(Arc::new(ArcSwap::new(Arc::new( + MaybePersisted::Unpersisted(node), + )))) + } +} + +impl From for MaybePersistedNode { + fn from(address: LinearAddress) -> Self { + MaybePersistedNode(Arc::new(ArcSwap::new(Arc::new(MaybePersisted::Persisted( + address, + ))))) + } +} + +impl From<&MaybePersistedNode> for Option { + fn from(node: &MaybePersistedNode) -> Option { + match node.0.load().as_ref() { + MaybePersisted::Unpersisted(_) => None, + MaybePersisted::Persisted(address) => Some(*address), + } + } +} + +impl MaybePersistedNode { + /// Converts this `MaybePersistedNode` to a `SharedNode` by reading from the appropriate source. + /// + /// If the node is in memory, it returns a clone of the in-memory node. + /// If the node is on disk, it reads the node from storage using the provided `NodeReader`. + /// + /// # Arguments + /// + /// * `storage` - A reference to a `NodeReader` implementation that can read nodes from storage + /// + /// # Returns + /// + /// Returns a `Result` where: + /// - `Ok(SharedNode)` contains the node if successfully retrieved + /// - `Err(FileIoError)` if there was an error reading from storage + pub fn as_shared_node(&self, storage: &S) -> Result { + match self.0.load().as_ref() { + MaybePersisted::Unpersisted(node) => Ok(node.clone()), + MaybePersisted::Persisted(address) => storage.read_node(*address), + } + } + + /// Updates the internal state to indicate this node is persisted at the specified disk address. + /// + /// This method changes the internal state of the `MaybePersistedNode` from `Mem` to `Disk`, + /// indicating that the node has been written to the specified disk location. + /// + /// This is done atomically using the `ArcSwap` mechanism. + /// + /// # Arguments + /// + /// * `addr` - The `LinearAddress` where the node has been persisted on disk + pub fn persist_at(&self, addr: LinearAddress) { + self.0.store(Arc::new(MaybePersisted::Persisted(addr))); + } +} + +/// The internal state of a `MaybePersistedNode`. +/// +/// This enum represents the two possible states of a `MaybePersisted`: +/// - `Unpersisted(SharedNode)`: The node is currently in memory +/// - `Persisted(LinearAddress)`: The node is currently on disk at the specified address +#[derive(Debug)] +enum MaybePersisted { + Unpersisted(SharedNode), + Persisted(LinearAddress), +} + +#[cfg(test)] +#[expect(clippy::unwrap_used)] +mod test { + use nonzero_ext::nonzero; + + use crate::{LeafNode, MemStore, Node, NodeStore, Path}; + + use super::*; + + #[test] + fn test_maybe_persisted_node() -> Result<(), FileIoError> { + let mem_store = MemStore::new(vec![]).into(); + let store = NodeStore::new_empty_committed(mem_store)?; + let node = SharedNode::new(Node::Leaf(LeafNode { + partial_path: Path::new(), + value: vec![0].into(), + })); + // create as unpersisted + let maybe_persisted_node = MaybePersistedNode::from(node.clone()); + let addr = nonzero!(2048u64); + assert_eq!(maybe_persisted_node.as_shared_node(&store).unwrap(), node); + assert_eq!( + Option::::None, + Option::from(&maybe_persisted_node) + ); + + maybe_persisted_node.persist_at(addr); + assert!(maybe_persisted_node.as_shared_node(&store).is_err()); + assert_eq!(Some(addr), Option::from(&maybe_persisted_node)); + Ok(()) + } + + #[test] + fn test_from_linear_address() { + let addr = nonzero!(1024u64); + let maybe_persisted_node = MaybePersistedNode::from(addr); + assert_eq!(Some(addr), Option::from(&maybe_persisted_node)); + } + + #[test] + fn test_clone_shares_underlying_shared_node() -> Result<(), FileIoError> { + let mem_store = MemStore::new(vec![]).into(); + let store = NodeStore::new_empty_committed(mem_store)?; + let node = SharedNode::new(Node::Leaf(LeafNode { + partial_path: Path::new(), + value: vec![42].into(), + })); + + let original = MaybePersistedNode::from(node.clone()); + let cloned = original.clone(); + + // Both should be unpersisted initially + assert_eq!(original.as_shared_node(&store).unwrap(), node); + assert_eq!(cloned.as_shared_node(&store).unwrap(), node); + assert_eq!(Option::::None, Option::from(&original)); + assert_eq!(Option::::None, Option::from(&cloned)); + + // First reference is 'node', second is shared by original and cloned + assert_eq!(triomphe::Arc::strong_count(&node), 2); + + // Persist the original + let addr = nonzero!(4096u64); + original.persist_at(addr); + + // Both original and clone should now be persisted since they share the same ArcSwap + assert!(original.as_shared_node(&store).is_err()); + assert!(cloned.as_shared_node(&store).is_err()); + assert_eq!(Some(addr), Option::from(&original)); + assert_eq!(Some(addr), Option::from(&cloned)); + + // 'node' is no longer referenced by anyone but our local copy, + // so the count should be 1 + assert_eq!(triomphe::Arc::strong_count(&node), 1); + + Ok(()) + } +} diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 86a702901b19..1461b83b2f8f 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -46,6 +46,7 @@ use std::ops::Deref; use std::sync::Arc; use crate::hashednode::hash_node; +use crate::node::persist::MaybePersistedNode; use crate::node::{ByteCounter, Node}; use crate::{ CacheReadStrategy, Child, FileBacked, HashType, Path, ReadableStorage, SharedNode, TrieHash, @@ -338,6 +339,7 @@ impl NodeStore { kind: Committed { deleted: Box::default(), root_hash: None, + root: header.root_address.map(Into::into), }, storage, }; @@ -366,6 +368,7 @@ impl NodeStore { kind: Committed { deleted: Box::default(), root_hash: None, + root: None, }, }) } @@ -988,6 +991,7 @@ pub trait RootReader { pub struct Committed { deleted: Box<[LinearAddress]>, root_hash: Option, + root: Option, } impl ReadInMemoryNode for Committed { @@ -1026,6 +1030,8 @@ pub struct ImmutableProposal { parent: Arc>, /// The hash of the root node for this proposal root_hash: Option, + /// The root node, either in memory or on disk + root: Option, } impl ImmutableProposal { @@ -1156,6 +1162,7 @@ impl From> for NodeStore, FileBacked> { kind: Committed { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), + root: self.kind.root.clone(), }, storage: self.storage.clone(), } @@ -1542,6 +1550,7 @@ impl TryFrom> deleted: kind.deleted.into(), parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), root_hash: None, + root: None, }), storage, }; @@ -1569,6 +1578,7 @@ impl TryFrom> deleted: immutable_proposal.deleted, parent: immutable_proposal.parent, root_hash: Some(root_hash.into_triehash()), + root: Some(root_addr.into()), }); Ok(nodestore) @@ -1600,12 +1610,17 @@ impl RootReader for NodeStore { } } -impl RootReader for NodeStore { +impl RootReader for NodeStore { fn root_node(&self) -> Option { // TODO: If the read_node fails, we just say there is no root; this is incorrect - self.header - .root_address - .and_then(|addr| self.read_node(addr).ok()) + self.kind.root.as_ref()?.as_shared_node(self).ok() + } +} + +impl RootReader for NodeStore, S> { + fn root_node(&self) -> Option { + // Use the MaybePersistedNode's as_shared_node method to get the root + self.kind.root.as_ref()?.as_shared_node(self).ok() } } From 1c485f5a22e9c2d68a2c7dd3c6cbbeef066bcba6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Jul 2025 12:38:30 -0700 Subject: [PATCH 0814/1053] feat(delayed-persist): 2.1: Unpersisted deletions (#1045) Nodes on the delete list might be unpersisted. This can't actually happen yet, but support for it is there. This is half of making branch children unpersisted. Reaping unpersisted nodes is easy, we just drop them. If they are persisted, they need to move to the freelist, as they did before. --- firewood/src/merkle.rs | 14 +++++------ storage/src/lib.rs | 1 + storage/src/linear/filebacked.rs | 8 +++--- storage/src/linear/mod.rs | 8 ++++-- storage/src/node/persist.rs | 13 ++++++++++ storage/src/nodestore.rs | 43 ++++++++++++++++++++------------ 6 files changed, 58 insertions(+), 29 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 34fb65b5525c..5f2a946ae573 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -567,7 +567,7 @@ impl Merkle> { } Some(Child::Node(child)) => child, Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr)? + self.nodestore.read_for_update(addr.into())? } }; @@ -712,7 +712,7 @@ impl Merkle> { let mut child = match child { Child::Node(child_node) => std::mem::take(child_node), Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update(*addr)? + self.nodestore.read_for_update((*addr).into())? } }; @@ -780,7 +780,7 @@ impl Merkle> { } Some(Child::Node(node)) => node, Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr)? + self.nodestore.read_for_update(addr.into())? } }; @@ -828,7 +828,7 @@ impl Merkle> { }), ), Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update(*addr)? + self.nodestore.read_for_update((*addr).into())? } }; @@ -928,7 +928,7 @@ impl Merkle> { } Some(Child::Node(node)) => node, Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr)? + self.nodestore.read_for_update(addr.into())? } }; @@ -976,7 +976,7 @@ impl Merkle> { }), ), Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update(*addr)? + self.nodestore.read_for_update((*addr).into())? } }; @@ -1014,7 +1014,7 @@ impl Merkle> { let child = match children { Some(Child::Node(node)) => node, Some(Child::AddressWithHash(addr, _)) => { - &mut self.nodestore.read_for_update(*addr)? + &mut self.nodestore.read_for_update((*addr).into())? } None => continue, }; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 9a79eb343ade..63425b78fa15 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -51,6 +51,7 @@ pub use nodestore::{ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; +pub use node::persist::MaybePersistedNode; pub use trie_hash::TrieHash; diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 79f264f6c96e..5d0979ccd1c7 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -36,7 +36,7 @@ use std::sync::Mutex; use lru::LruCache; use metrics::counter; -use crate::{CacheReadStrategy, LinearAddress, SharedNode}; +use crate::{CacheReadStrategy, LinearAddress, MaybePersistedNode, SharedNode}; use super::{FileIoError, ReadableStorage, WritableStorage}; @@ -223,10 +223,10 @@ impl WritableStorage for FileBacked { Ok(()) } - fn invalidate_cached_nodes<'a>(&self, addresses: impl Iterator) { + fn invalidate_cached_nodes<'a>(&self, nodes: impl Iterator) { let mut guard = self.cache.lock().expect("poisoned lock"); - for addr in addresses { - guard.pop(addr); + for addr in nodes.filter_map(MaybePersistedNode::as_linear_address) { + guard.pop(&addr); } } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 62370e7c2b3a..d1b4c7fc9c20 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -25,7 +25,7 @@ use std::num::NonZero; use std::ops::Deref; use std::path::PathBuf; -use crate::{CacheReadStrategy, LinearAddress, SharedNode}; +use crate::{CacheReadStrategy, LinearAddress, MaybePersistedNode, SharedNode}; pub(super) mod filebacked; pub mod memory; @@ -190,7 +190,11 @@ pub trait WritableStorage: ReadableStorage { } /// Invalidate all nodes that are part of a specific revision, as these will never be referenced again - fn invalidate_cached_nodes<'a>(&self, _addresses: impl Iterator) {} + fn invalidate_cached_nodes<'a>( + &self, + _addresses: impl Iterator, + ) { + } /// Add a new entry to the freelist cache fn add_to_free_list_cache(&self, _addr: LinearAddress, _next: Option) {} diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 7f985523ebfd..3deb1ea8356f 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -87,6 +87,19 @@ impl MaybePersistedNode { } } + /// Returns the linear address of the node if it is persisted on disk. + /// + /// # Returns + /// + /// Returns `Some(LinearAddress)` if the node is persisted on disk, otherwise `None`. + #[must_use] + pub fn as_linear_address(&self) -> Option { + match self.0.load().as_ref() { + MaybePersisted::Unpersisted(_) => None, + MaybePersisted::Persisted(address) => Some(*address), + } + } + /// Updates the internal state to indicate this node is persisted at the specified disk address. /// /// This method changes the internal state of the `MaybePersistedNode` from `Mem` to `Disk`, diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index 1461b83b2f8f..a5fc6a50e684 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -385,6 +385,8 @@ pub trait Parentable { fn as_nodestore_parent(&self) -> NodeStoreParent; /// Returns the root hash of this nodestore. This works because all parentable nodestores have a hash fn root_hash(&self) -> Option; + /// Returns the root node + fn root(&self) -> Option; } impl Parentable for Arc { @@ -394,6 +396,9 @@ impl Parentable for Arc { fn root_hash(&self) -> Option { self.root_hash.clone() } + fn root(&self) -> Option { + self.root.clone() + } } impl NodeStore, S> { @@ -421,6 +426,9 @@ impl Parentable for Committed { fn root_hash(&self) -> Option { self.root_hash.clone() } + fn root(&self) -> Option { + self.root.clone() + } } impl NodeStore { @@ -433,10 +441,10 @@ impl NodeStore { parent: &Arc>, ) -> Result { let mut deleted = Vec::default(); - let root = if let Some(root_addr) = parent.header.root_address { - deleted.push(root_addr); - let root = parent.read_node(root_addr)?; - Some((*root).clone()) + let root = if let Some(ref root) = parent.kind.root() { + deleted.push(root.clone()); + let root = root.as_shared_node(parent)?.deref().clone(); + Some(root) } else { None }; @@ -453,9 +461,9 @@ impl NodeStore { } /// Marks the node at `addr` as deleted in this proposal. - pub fn delete_node(&mut self, addr: LinearAddress) { - trace!("Pending delete at {addr:?}"); - self.kind.deleted.push(addr); + pub fn delete_node(&mut self, node: MaybePersistedNode) { + trace!("Pending delete at {node:?}"); + self.kind.deleted.push(node); } /// Reads a node for update, marking it as deleted in this proposal. @@ -465,9 +473,9 @@ impl NodeStore { /// # Errors /// /// Returns a [`FileIoError`] if the node cannot be read. - pub fn read_for_update(&mut self, addr: LinearAddress) -> Result { - self.delete_node(addr); - let arc_wrapped_node = self.read_node(addr)?; + pub fn read_for_update(&mut self, node: MaybePersistedNode) -> Result { + let arc_wrapped_node = node.as_shared_node(self)?; + self.delete_node(node); Ok((*arc_wrapped_node).clone()) } @@ -614,7 +622,10 @@ impl NodeStore { /// /// Returns a [`FileIoError`] if the node cannot be deleted. #[expect(clippy::indexing_slicing)] - pub fn delete_node(&mut self, addr: LinearAddress) -> Result<(), FileIoError> { + pub fn delete_node(&mut self, node: MaybePersistedNode) -> Result<(), FileIoError> { + let Some(addr) = node.as_linear_address() else { + return Ok(()); + }; debug_assert!(addr.get() % 8 == 0); let (area_size_index, _) = self.area_index_and_size(addr)?; @@ -989,7 +1000,7 @@ pub trait RootReader { /// A committed revision of a merkle trie. #[derive(Clone, Debug)] pub struct Committed { - deleted: Box<[LinearAddress]>, + deleted: Box<[MaybePersistedNode]>, root_hash: Option, root: Option, } @@ -1025,7 +1036,7 @@ pub struct ImmutableProposal { /// Address --> Node for nodes created in this proposal. new: HashMap, /// Nodes that have been deleted in this proposal. - deleted: Box<[LinearAddress]>, + deleted: Box<[MaybePersistedNode]>, /// The parent of this proposal. parent: Arc>, /// The hash of the root node for this proposal @@ -1115,7 +1126,7 @@ pub struct MutableProposal { /// The root of the trie in this proposal. root: Option, /// Nodes that have been deleted in this proposal. - deleted: Vec, + deleted: Vec, parent: NodeStoreParent, } @@ -1665,8 +1676,8 @@ impl NodeStore { self.storage .invalidate_cached_nodes(self.kind.deleted.iter()); trace!("There are {} nodes to reap", self.kind.deleted.len()); - for addr in take(&mut self.kind.deleted) { - proposal.delete_node(addr)?; + for node in take(&mut self.kind.deleted) { + proposal.delete_node(node)?; } Ok(()) } From 4b12575680cd43f7f2dc6340342dca7911af8ae5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 9 Jul 2025 14:03:39 -0700 Subject: [PATCH 0815/1053] feat(delayed-persist): Part 2.2: Branch Children (#1047) Branch children might also not be persisted. This also splits up hash_helper which got too long for clippy. - [x] Roots may not be persisted (Part 1: #1041) - [x] BranchNode children can now be Node, LinearAddress or a MaybePersistedNode (Part 2: #1045 and this PR #1047) - [ ] When converting a `MutableProposal` to an `ImmutableProposal`, don't actually allocate space, just create `SharedNode`s from the `Node`s. - [ ] Remove `NodeStoreHeader` from `NodeStore`. The header should move into the `RevisionManager`. - [ ] Allocate new nodes from the recently-freed nodes from an expired revision, then from the freelist. This can be done by maintaining a local freelist from the nodes deleted in the expiring revision and falling back to the actual freelist. Deleted nodes that are not reused must still be pushed onto the freelist. --- firewood/src/merkle.rs | 41 +++++++++++++--- firewood/src/stream.rs | 26 ++++++++++ storage/src/node/branch.rs | 59 ++++++++++++++++++++++- storage/src/node/persist.rs | 10 +++- storage/src/nodestore.rs | 94 +++++++++++++++++++++++++++---------- 5 files changed, 196 insertions(+), 34 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5f2a946ae573..1478d0cac6af 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::match_same_arms, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::missing_errors_doc, reason = "Found 6 occurrences after enabling the lint." @@ -142,6 +138,10 @@ fn get_helper( let child = nodestore.read_node(*addr)?; get_helper(nodestore, &child, remaining_key) } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + let child = maybe_persisted.as_shared_node(nodestore)?; + get_helper(nodestore, &child, remaining_key) + } }, } } @@ -388,8 +388,12 @@ impl Merkle { for (childidx, child) in b.children.iter().enumerate() { let (child_addr, child_hash) = match child { None => continue, - Some(Child::Node(_)) => continue, // TODO - Some(Child::AddressWithHash(addr, hash)) => (*addr, Some(hash)), + Some(node) => { + let (Some(addr), hash) = (node.persisted_address(), node.hash()) else { + continue; + }; + (addr, hash) + } }; let inserted = seen.insert(child_addr); @@ -569,6 +573,9 @@ impl Merkle> { Some(Child::AddressWithHash(addr, _)) => { self.nodestore.read_for_update(addr.into())? } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; let child = self.insert_helper(child, partial_path.as_ref(), value)?; @@ -714,6 +721,9 @@ impl Merkle> { Child::AddressWithHash(addr, _) => { self.nodestore.read_for_update((*addr).into())? } + Child::MaybePersisted(maybe_persisted, _) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; // The child's partial path is the concatenation of its (now removed) parent, @@ -782,6 +792,9 @@ impl Merkle> { Some(Child::AddressWithHash(addr, _)) => { self.nodestore.read_for_update(addr.into())? } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; let (child, removed_value) = @@ -830,6 +843,9 @@ impl Merkle> { Child::AddressWithHash(addr, _) => { self.nodestore.read_for_update((*addr).into())? } + Child::MaybePersisted(maybe_persisted, _) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; // The child's partial path is the concatenation of its (now removed) parent, @@ -930,6 +946,9 @@ impl Merkle> { Some(Child::AddressWithHash(addr, _)) => { self.nodestore.read_for_update(addr.into())? } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; let child = @@ -978,6 +997,9 @@ impl Merkle> { Child::AddressWithHash(addr, _) => { self.nodestore.read_for_update((*addr).into())? } + Child::MaybePersisted(maybe_persisted, _) => { + self.nodestore.read_for_update(maybe_persisted.clone())? + } }; // The child's partial path is the concatenation of its (now removed) parent, @@ -1016,6 +1038,13 @@ impl Merkle> { Some(Child::AddressWithHash(addr, _)) => { &mut self.nodestore.read_for_update((*addr).into())? } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + // For MaybePersisted, we need to get the node to update it + // We can't get a mutable reference from SharedNode, so we need to handle this differently + // For now, we'll skip this child since we can't modify it + let _shared_node = maybe_persisted.as_shared_node(&self.nodestore)?; + continue; + } None => continue, }; match child { diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 41dbce3da35c..78386debbd31 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -147,6 +147,13 @@ impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { Err(e) => return Some(Err(e)), }, Child::Node(node) => node.clone().into(), + Child::MaybePersisted(maybe_persisted, _) => { + // For MaybePersisted, we need to get the node + match maybe_persisted.as_shared_node(merkle) { + Ok(node) => node, + Err(e) => return Some(Err(e)), + } + } }; let child_partial_path = child.partial_path().iter().copied(); @@ -279,6 +286,10 @@ fn get_iterator_intial_state( None => return Ok(NodeStreamState::Iterating { iter_stack }), Some(Child::AddressWithHash(addr, _)) => merkle.read_node(*addr)?, Some(Child::Node(node)) => (*node).clone().into(), // TODO can we avoid cloning this? + Some(Child::MaybePersisted(maybe_persisted, _)) => { + // For MaybePersisted, we need to get the node + maybe_persisted.as_shared_node(merkle)? + } }; matched_key_nibbles.push(next_unmatched_key_nibble); @@ -539,6 +550,21 @@ impl Iterator for PathIterator<'_, '_, T> { next_nibble: Some(next_unmatched_key_nibble), })) } + Some(Child::MaybePersisted(maybe_persisted, _)) => { + let child = match maybe_persisted.as_shared_node(merkle) { + Ok(child) => child, + Err(e) => return Some(Err(e)), + }; + + let node_key = matched_key.clone().into_boxed_slice(); + matched_key.push(next_unmatched_key_nibble); + + Some(Ok(PathIterItem { + key_nibbles: node_key, + node: child, + next_nibble: Some(next_unmatched_key_nibble), + })) + } } } } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index c2c9456a9102..98841215d762 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use crate::node::ExtendableBytes; -use crate::{LeafNode, LinearAddress, Node, Path}; +use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path}; use std::fmt::{Debug, Formatter}; /// The type of a hash. For ethereum compatible hashes, this might be a RLP encoded @@ -74,8 +74,44 @@ pub enum Child { /// There is a child at this index, but we haven't hashed it /// or allocated space in storage for it yet. Node(Node), - /// We know the child's address and hash. + + /// We know the child's persisted address and hash. AddressWithHash(LinearAddress, HashType), + + /// A `MaybePersisted` child + MaybePersisted(MaybePersistedNode, HashType), +} + +impl Child { + /// Return a mutable reference to the underlying Node if the child + /// is a [`Child::Node`] variant, otherwise None. + #[must_use] + pub const fn as_mut_node(&mut self) -> Option<&mut Node> { + match self { + Child::Node(node) => Some(node), + _ => None, + } + } + + /// Return the persisted address of the child if it is a [`Child::AddressWithHash`] or [`Child::MaybePersisted`] variant, otherwise None. + #[must_use] + pub fn persisted_address(&self) -> Option { + match self { + Child::AddressWithHash(addr, _) => Some(*addr), + Child::MaybePersisted(maybe_persisted, _) => maybe_persisted.as_linear_address(), + Child::Node(_) => None, + } + } + + /// Return the hash of the child if it is a [`Child::AddressWithHash`] or [`Child::MaybePersisted`] variant, otherwise None. + #[must_use] + pub const fn hash(&self) -> Option<&HashType> { + match self { + Child::AddressWithHash(_, hash) => Some(hash), + Child::MaybePersisted(_, hash) => Some(hash), + Child::Node(_) => None, + } + } } #[cfg(feature = "ethhash")] @@ -245,6 +281,12 @@ impl Serialize for BranchNode { panic!("serializing in-memory node for disk storage") } Some(Child::AddressWithHash(addr, hash)) => Some((offset as u8, *addr, hash)), + Some(Child::MaybePersisted(maybe_persisted, hash)) => { + // For MaybePersisted, we need to get the address if it's persisted + maybe_persisted + .as_linear_address() + .map(|addr| (offset as u8, addr, hash)) + } }) .collect(); @@ -293,6 +335,13 @@ impl Debug for BranchNode { Some(Child::AddressWithHash(addr, hash)) => { write!(f, "({i:?}: address={addr:?} hash={hash})")?; } + Some(Child::MaybePersisted(maybe_persisted, hash)) => { + // For MaybePersisted, show the address if persisted, otherwise show as unpersisted + match maybe_persisted.as_linear_address() { + Some(addr) => write!(f, "({i:?}: address={addr:?} hash={hash})")?, + None => write!(f, "({i:?}: unpersisted hash={hash})")?, + } + } } } @@ -345,6 +394,12 @@ impl BranchNode { None => None, Some(Child::Node(_)) => unreachable!("TODO make unreachable"), Some(Child::AddressWithHash(address, hash)) => Some((i, (*address, hash))), + Some(Child::MaybePersisted(maybe_persisted, hash)) => { + // For MaybePersisted, we need the address if it's persisted + maybe_persisted + .as_linear_address() + .map(|addr| (i, (addr, hash))) + } }) } diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 3deb1ea8356f..8c1068d81c54 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -40,6 +40,14 @@ use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; #[derive(Debug, Clone)] pub struct MaybePersistedNode(Arc>); +impl PartialEq for MaybePersistedNode { + fn eq(&self, other: &MaybePersistedNode) -> bool { + self.0.load().as_ref() == other.0.load().as_ref() + } +} + +impl Eq for MaybePersistedNode {} + impl From for MaybePersistedNode { fn from(node: SharedNode) -> Self { MaybePersistedNode(Arc::new(ArcSwap::new(Arc::new( @@ -120,7 +128,7 @@ impl MaybePersistedNode { /// This enum represents the two possible states of a `MaybePersisted`: /// - `Unpersisted(SharedNode)`: The node is currently in memory /// - `Persisted(LinearAddress)`: The node is currently on disk at the specified address -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] enum MaybePersisted { Unpersisted(SharedNode), Persisted(LinearAddress), diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs index a5fc6a50e684..751623cdc82c 100644 --- a/storage/src/nodestore.rs +++ b/storage/src/nodestore.rs @@ -1180,7 +1180,54 @@ impl From> for NodeStore { + unhashed: Vec<(usize, Node)>, + hashed: Vec<(usize, (MaybePersistedNode, &'a mut HashType))>, +} + impl NodeStore, S> { + /// Helper function to classify children for ethereum hash processing + /// We have some special cases based on the number of children + /// and whether they are hashed or unhashed, so we need to classify them. + #[cfg(feature = "ethhash")] + fn ethhash_classify_children<'a>( + &self, + children: &'a mut [Option; crate::node::BranchNode::MAX_CHILDREN], + ) -> ClassifiedChildren<'a> { + children.iter_mut().enumerate().fold( + ClassifiedChildren { + unhashed: Vec::new(), + hashed: Vec::new(), + }, + |mut acc, (idx, child)| { + match child { + None => {} + Some(Child::AddressWithHash(a, h)) => { + // Convert address to MaybePersistedNode + let maybe_persisted_node = MaybePersistedNode::from(*a); + acc.hashed.push((idx, (maybe_persisted_node, h))); + } + Some(Child::Node(node)) => acc.unhashed.push((idx, node.clone())), + Some(Child::MaybePersisted(maybe_persisted, h)) => { + // For MaybePersisted, we need to get the address if it's persisted + if let Some(addr) = maybe_persisted.as_linear_address() { + let maybe_persisted_node = MaybePersistedNode::from(addr); + acc.hashed.push((idx, (maybe_persisted_node, h))); + } else { + // If not persisted, we need to get the node to hash it + if let Ok(node) = maybe_persisted.as_shared_node(self) { + acc.unhashed.push((idx, node.deref().clone())); + } + } + } + } + acc + }, + ) + } + /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. /// Returns the hashed node and its hash. fn hash_helper( @@ -1199,22 +1246,21 @@ impl NodeStore, S> { { // looks like we're at an account branch // tally up how many hashes we need to deal with - let (unhashed, mut hashed) = b.children.iter_mut().enumerate().fold( - (Vec::new(), Vec::new()), - |(mut unhashed, mut hashed), (idx, child)| { - match child { - None => {} - Some(Child::AddressWithHash(a, h)) => hashed.push((idx, (a, h))), - Some(Child::Node(node)) => unhashed.push((idx, node)), - } - (unhashed, hashed) - }, - ); + let ClassifiedChildren { + unhashed, + mut hashed, + } = self.ethhash_classify_children(&mut b.children); trace!("hashed {hashed:?} unhashed {unhashed:?}"); if hashed.len() == 1 { // we were left with one hashed node that must be rehashed let invalidated_node = hashed.first_mut().expect("hashed is not empty"); - let mut hashable_node = self.read_node(*invalidated_node.1.0)?.deref().clone(); + // Extract the address from the MaybePersistedNode + let addr = invalidated_node + .1 + .0 + .as_linear_address() + .expect("hashed node should be persisted"); + let mut hashable_node = self.read_node(addr)?.deref().clone(); let original_length = path_prefix.len(); path_prefix.0.extend(b.partial_path.0.iter().copied()); if unhashed.is_empty() { @@ -1240,27 +1286,25 @@ impl NodeStore, S> { None }; - // branch children -- 1. 1 child, already hashed, 2. >1 child, already hashed, + // branch children cases: + // 1. 1 child, already hashed + // 2. >1 child, already hashed, // 3. 1 hashed child, 1 unhashed child // 4. 0 hashed, 1 unhashed <-- handle child special // 5. 1 hashed, >0 unhashed <-- rehash case - // everything already hashed + // 6. everything already hashed for (nibble, child) in b.children.iter_mut().enumerate() { - // if this is already hashed, we're done - if matches!(child, Some(Child::AddressWithHash(_, _))) { - // We already know the hash of this child. - continue; - } - - // If there was no child, we're done. Otherwise, remove the child from - // the branch and hash it. This has the side effect of dropping the [Child::Node] - // that was allocated. This is fine because we're about to replace it with a - // [Child::AddressWithHash]. - let Some(Child::Node(child_node)) = std::mem::take(child) else { + // If this is empty or already hashed, we're done + // Empty matches None, and non-Node types match Some(None) here, so we want + // Some(Some(node)) + let Some(Some(child_node)) = child.as_mut().map(|child| child.as_mut_node()) else { continue; }; + // remove the child from the children array, we will replace it with a hashed variant + let child_node = std::mem::take(child_node); + // Hash this child and update // we extend and truncate path_prefix to reduce memory allocations let original_length = path_prefix.len(); From 91f2ab263ec1d75f2064d76c110dd5c80015c045 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:46:50 -0400 Subject: [PATCH 0816/1053] feat!: export firewood metrics (#1044) This PR enables Firewood to export metrics so that clients such as Coreth can aggregate those metrics into its own. ## How Splits the Firewood API into the following: ```go // Start recorder func StartMetrics() // Start recorder and exporter func StartMetricsWithExporter() // Gather latest metrics func GatherMetrics() (string, error) ``` `GatherMetrics()` is used by to create a custom Prometheus gatherer that can used by Coreth's Multigatherer. ## Testing Extended the existing metrics test to account for `Gatherer`. --- ffi/firewood.go | 8 ----- ffi/firewood.h | 29 ++++++++++++--- ffi/firewood_test.go | 38 -------------------- ffi/go.mod | 14 +++++++- ffi/go.sum | 30 +++++++++++++++- ffi/metrics.go | 71 ++++++++++++++++++++++++++++++++++++ ffi/metrics_test.go | 76 +++++++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 35 ++++++++++++++---- ffi/src/metrics_setup.rs | 35 +++++++++++++++--- ffi/tests/eth/go.mod | 15 ++++---- ffi/tests/eth/go.sum | 40 ++++++++++----------- ffi/tests/firewood/go.mod | 13 ++++--- ffi/tests/firewood/go.sum | 33 ++++++++--------- 13 files changed, 317 insertions(+), 120 deletions(-) create mode 100644 ffi/metrics.go create mode 100644 ffi/metrics_test.go diff --git a/ffi/firewood.go b/ffi/firewood.go index 486f7f8d2b03..ca1367334538 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -129,14 +129,6 @@ func New(filePath string, conf *Config) (*Database, error) { return &Database{handle: db}, nil } -// Starts metrics exporter for this process. -// Returns an error if the metrics exporter was unable to start or already started. -// This function should only be called once per process. -func StartMetrics(metricsPort uint16) error { - result := C.fwd_start_metrics(C.uint16_t(metricsPort)) - return errorFromValue(&result) -} - // Update applies a batch of updates to the database, returning the hash of the // root node after the batch is applied. // diff --git a/ffi/firewood.h b/ffi/firewood.h index 61f68e225989..fe2d4f336cf9 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -225,6 +225,16 @@ void fwd_free_database_error_result(struct DatabaseCreationResult *result); */ void fwd_free_value(struct Value *value); +/** + * Gather latest metrics for this process. + * + * # Returns + * + * A `Value` containing {len, bytes} representing the latest metrics for this process. + * A `Value` containing {0, "error message"} if unable to get the latest metrics. + */ +struct Value fwd_gather(void); + /** * Gets the value associated with the given key from the proposal provided. * @@ -408,15 +418,24 @@ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, struct Value fwd_root_hash(const struct DatabaseHandle *db); /** - * Start metrics exporter for this process + * Start metrics recorder for this process. * - * # Arguments + * # Returns + * + * A `Value` containing {0, null} if the metrics recorder was initialized. + * A `Value` containing {0, "error message"} if an error occurs. + */ +struct Value fwd_start_metrics(void); + +/** + * Start metrics recorder and exporter for this process. * * * `metrics_port` - the port where metrics will be exposed at * * # Returns * - * A `Value` containing {0, null} if the metrics exporter successfully started. - * A `Value` containing {0, "error message"} if the metrics exporter failed to start. + * A `Value` containing {0, null} if the metrics recorder was initialized and + * the exporter was started. + * A `Value` containing {0, "error message"} if an error occurs. */ -struct Value fwd_start_metrics(uint16_t metrics_port); +struct Value fwd_start_metrics_with_exporter(uint16_t metrics_port); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 6ef64416f895..c4bd54e68656 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -2,17 +2,13 @@ package ffi import ( "bytes" - "context" "encoding/hex" "fmt" - "io" - "net/http" "os" "path/filepath" "strconv" "strings" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -962,37 +958,3 @@ func TestGetFromRoot(t *testing.T) { _, err = db.GetFromRoot(nonExistentRoot, []byte("key")) r.Error(err, "GetFromRoot with non-existent root should return error") } - -func TestStartMetrics(t *testing.T) { - r := require.New(t) - ctx := context.Background() - - db := newTestDatabase(t) - - metricsPort := uint16(3000) - r.NoError(StartMetrics(metricsPort)) - - // Populate DB - keys, vals := kvForTest(10) - _, err := db.Update(keys, vals) - r.NoError(err) - - req, err := http.NewRequestWithContext( - ctx, - http.MethodGet, - fmt.Sprintf("http://localhost:%d", metricsPort), - nil, - ) - r.NoError(err) - - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - r.NoError(err) - - body, err := io.ReadAll(resp.Body) - r.NoError(err) - r.NoError(resp.Body.Close()) - - // Check that batch op was recorded - r.Contains(string(body), "firewood_ffi_batch 1") -} diff --git a/ffi/go.mod b/ffi/go.mod index 508578f0be71..7aebabae623b 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -4,10 +4,22 @@ go 1.23 toolchain go1.23.6 -require github.com/stretchr/testify v1.10.0 +require ( + github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_model v0.6.1 + github.com/prometheus/common v0.62.0 + github.com/stretchr/testify v1.10.0 +) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/sys v0.30.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/ffi/go.sum b/ffi/go.sum index 713a0b4f0a3a..546cb55f6fa1 100644 --- a/ffi/go.sum +++ b/ffi/go.sum @@ -1,10 +1,38 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ffi/metrics.go b/ffi/metrics.go new file mode 100644 index 000000000000..6d97f778e416 --- /dev/null +++ b/ffi/metrics.go @@ -0,0 +1,71 @@ +package ffi + +//go:generate go run generate_cgo.go + +// #include +// #include "firewood.h" +import "C" + +import ( + "strings" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" +) + +var _ prometheus.Gatherer = (*Gatherer)(nil) + +type Gatherer struct{} + +func (Gatherer) Gather() ([]*dto.MetricFamily, error) { + metrics, err := GatherMetrics() + if err != nil { + return nil, err + } + + reader := strings.NewReader(metrics) + + var parser expfmt.TextParser + parsedMetrics, err := parser.TextToMetricFamilies(reader) + if err != nil { + return nil, err + } + + lst := make([]*dto.MetricFamily, 0, len(parsedMetrics)) + for _, v := range parsedMetrics { + lst = append(lst, v) + } + + return lst, nil +} + +// Starts global recorder for metrics. +// This function only needs to be called once. +// An error is returned if this method is called a second time, or if it is +// called after StartMetricsWithExporter. +func StartMetrics() error { + result := C.fwd_start_metrics() + return errorFromValue(&result) +} + +// Start global recorder for metrics along with an HTTP exporter. +// This function only needs to be called once. +// An error is returned if this method is called a second time, if it is +// called after StartMetrics, or if the exporter failed to start. +func StartMetricsWithExporter(metricsPort uint16) error { + result := C.fwd_start_metrics_with_exporter(C.uint16_t(metricsPort)) + return errorFromValue(&result) +} + +// Collect metrics from global recorder +// Returns an error if the global recorder is not initialized. +// This method must be called after StartMetrics or StartMetricsWithExporter +func GatherMetrics() (string, error) { + result := C.fwd_gather() + b, err := bytesFromValue(&result) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go new file mode 100644 index 000000000000..8530153f5198 --- /dev/null +++ b/ffi/metrics_test.go @@ -0,0 +1,76 @@ +package ffi + +import ( + "context" + "fmt" + "io" + "net/http" + "testing" + "time" + + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +// Test calling metrics exporter along with gathering metrics +// This lives under one test as we can only instantiate the global recorder once +func TestMetrics(t *testing.T) { + r := require.New(t) + ctx := context.Background() + + db := newTestDatabase(t) + + metricsPort := uint16(3000) + r.NoError(StartMetricsWithExporter(metricsPort)) + + // Populate DB + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) + + req, err := http.NewRequestWithContext( + ctx, + http.MethodGet, + fmt.Sprintf("http://localhost:%d", metricsPort), + nil, + ) + r.NoError(err) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + r.NoError(err) + + body, err := io.ReadAll(resp.Body) + r.NoError(err) + r.NoError(resp.Body.Close()) + + // Check that batch op was recorded + r.Contains(string(body), "firewood_ffi_batch 1") + + g := Gatherer{} + metricsFamily, err := g.Gather() + r.NoError(err) + + expectedMetrics := map[string]dto.MetricType{ + "firewood_ffi_batch": dto.MetricType_COUNTER, + "firewood_proposal_commit": dto.MetricType_COUNTER, + "firewood_proposal_commit_ms": dto.MetricType_COUNTER, + "firewood_ffi_propose_ms": dto.MetricType_COUNTER, + "firewood_ffi_commit_ms": dto.MetricType_COUNTER, + "firewood_ffi_batch_ms": dto.MetricType_COUNTER, + "firewood_flush_nodes": dto.MetricType_COUNTER, + "firewood_insert": dto.MetricType_COUNTER, + "firewood_space_from_end": dto.MetricType_COUNTER, + } + + for k, v := range expectedMetrics { + var d *dto.MetricFamily + for _, m := range metricsFamily { + if *m.Name == k { + d = m + } + } + r.NotNil(d) + r.Equal(v, *d.Type) + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 9297180f0dcd..7bf8a85d992f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -821,23 +821,46 @@ pub unsafe extern "C" fn fwd_free_database_error_result( // Note: we don't free the db pointer as it's managed by the caller } -/// Start metrics exporter for this process +/// Start metrics recorder for this process. /// -/// # Arguments +/// # Returns +/// +/// A `Value` containing {0, null} if the metrics recorder was initialized. +/// A `Value` containing {0, "error message"} if an error occurs. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_start_metrics() -> Value { + metrics_setup::setup_metrics() + .map_err(|e| e.to_string()) + .map_or_else(Into::into, Into::into) +} + +/// Start metrics recorder and exporter for this process. /// /// * `metrics_port` - the port where metrics will be exposed at /// /// # Returns /// -/// A `Value` containing {0, null} if the metrics exporter successfully started. -/// A `Value` containing {0, "error message"} if the metrics exporter failed to start. +/// A `Value` containing {0, null} if the metrics recorder was initialized and +/// the exporter was started. +/// A `Value` containing {0, "error message"} if an error occurs. #[unsafe(no_mangle)] -pub extern "C" fn fwd_start_metrics(metrics_port: u16) -> Value { - metrics_setup::setup_metrics(metrics_port) +pub extern "C" fn fwd_start_metrics_with_exporter(metrics_port: u16) -> Value { + metrics_setup::setup_metrics_with_exporter(metrics_port) .map_err(|e| e.to_string()) .map_or_else(Into::into, Into::into) } +/// Gather latest metrics for this process. +/// +/// # Returns +/// +/// A `Value` containing {len, bytes} representing the latest metrics for this process. +/// A `Value` containing {0, "error message"} if unable to get the latest metrics. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_gather() -> Value { + metrics_setup::gather_metrics().map_or_else(Into::into, |s| s.as_bytes().into()) +} + /// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. /// /// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 1aafd2d6f0d4..80d87df9d9a8 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -4,6 +4,7 @@ use std::error::Error; use std::fmt::Write; use std::net::Ipv6Addr; use std::ops::Deref; +use std::sync::OnceLock; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use std::time::SystemTime; @@ -18,19 +19,35 @@ use chrono::{DateTime, Utc}; use metrics::Key; use metrics_util::registry::{AtomicStorage, Registry}; -/// Sets up a metrics server over a specified port. -/// This happens on a per-process basis, meaning that the metrics system -/// cannot be initialized if it has already been set up in the same process. -pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { - let inner: TextRecorderInner = TextRecorderInner { +static RECORDER: OnceLock = OnceLock::new(); + +/// Starts metrics recorder. +/// This happens on a per-process basis, meaning that the metrics system cannot +/// be initialized if it has already been set up in the same process. +pub fn setup_metrics() -> Result<(), Box> { + let inner = TextRecorderInner { registry: Registry::atomic(), help: Mutex::new(HashMap::new()), }; let recorder = TextRecorder { inner: Arc::new(inner), }; + metrics::set_global_recorder(recorder.clone())?; + RECORDER + .set(recorder) + .map_err(|_| "recorder already initialized")?; + + Ok(()) +} +/// Starts metrics recorder along with an exporter over a specified port. +/// This happens on a per-process basis, meaning that the metrics system +/// cannot be initialized if it has already been set up in the same process. +pub fn setup_metrics_with_exporter(metrics_port: u16) -> Result<(), Box> { + setup_metrics()?; + + let recorder = RECORDER.get().ok_or("recorder not initialized")?; Server::new(move |request| { if request.method() == "GET" { Response::builder() @@ -53,6 +70,14 @@ pub(crate) fn setup_metrics(metrics_port: u16) -> Result<(), Box> { Ok(()) } +/// Returns the latest metrics for this process. +pub fn gather_metrics() -> Result { + let Some(recorder) = RECORDER.get() else { + return Err(String::from("recorder not initialized")); + }; + Ok(recorder.stats()) +} + #[derive(Debug)] struct TextRecorderInner { registry: Registry, diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index 4b2d5a045fae..8b65d0d219a1 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -35,23 +35,22 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -65,7 +64,7 @@ require ( golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/ffi/tests/eth/go.sum b/ffi/tests/eth/go.sum index e52fe98013ab..37f9918afefb 100644 --- a/ffi/tests/eth/go.sum +++ b/ffi/tests/eth/go.sum @@ -124,7 +124,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -134,8 +133,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -146,8 +143,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -186,8 +183,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -216,8 +213,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -230,6 +225,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -260,15 +257,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -375,12 +372,11 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -481,8 +477,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index 02648984a889..9add5391b1ad 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -19,17 +19,16 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/rpc v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect @@ -50,7 +49,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect google.golang.org/grpc v1.66.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/ffi/tests/firewood/go.sum b/ffi/tests/firewood/go.sum index 3a9479e8fef9..cd8a9e419e92 100644 --- a/ffi/tests/firewood/go.sum +++ b/ffi/tests/firewood/go.sum @@ -17,12 +17,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= @@ -33,20 +29,20 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= @@ -85,7 +81,6 @@ golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDP golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= @@ -102,8 +97,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 53488a71e4624c0614743d1447b9412ee032e244 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 10 Jul 2025 12:20:02 -0400 Subject: [PATCH 0817/1053] style: Remove unnecessary error descriptor (#1049) Any error from Firewood is obviously a Firewood error, the user should be able to figure this out. --- ffi/memory.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ffi/memory.go b/ffi/memory.go index 9939ae5bfba4..8d28e3674986 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -10,7 +10,6 @@ import "C" import ( "errors" - "fmt" "runtime" "unsafe" ) @@ -58,7 +57,7 @@ func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - return nil, 0, fmt.Errorf("firewood error: %s", errStr) + return nil, 0, errors.New(errStr) } // Case 4 @@ -96,7 +95,7 @@ func errorFromValue(v *C.struct_Value) error { if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - return fmt.Errorf("firewood error: %s", errStr) + return errors.New(errStr) } // Case 2 and 4 @@ -138,7 +137,7 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) C.fwd_free_value(v) - return nil, fmt.Errorf("firewood error: %s", errStr) + return nil, errors.New(errStr) } // Case 2 @@ -154,7 +153,7 @@ func databaseFromResult(result *C.struct_DatabaseCreationResult) (*C.DatabaseHan errStr := C.GoString((*C.char)(unsafe.Pointer(result.error_str))) C.fwd_free_database_error_result(result) runtime.KeepAlive(result) - return nil, fmt.Errorf("firewood error: %s", errStr) + return nil, errors.New(errStr) } return result.db, nil } From ee5d1d8ee292ca346fe6ceb33af5f4149cb8a317 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 10 Jul 2025 18:14:52 -0700 Subject: [PATCH 0818/1053] chore: Split nodestore into functional areas (#1048) A nodestore and its related types evolved over time becoming more complex. This change splits up the functionality into different areas to make it easier to find specific parts of the nodestore logic. Doc comments about the layout are extensive and are not repeated here. Other than moving things around, some additional changes are: - `UpdateError` was dead code. It was used in early development of merkle's insert but is no longer used. - Accessors for various members of NodeStoreHeader instead of referencing them directly. This makes auditing their use a little easier, and improves scoping and readability. --- storage/src/checker.rs | 5 +- storage/src/lib.rs | 2 +- storage/src/nodestore.rs | 2072 ------------------------------ storage/src/nodestore/alloc.rs | 715 +++++++++++ storage/src/nodestore/hash.rs | 204 +++ storage/src/nodestore/header.rs | 336 +++++ storage/src/nodestore/mod.rs | 829 ++++++++++++ storage/src/nodestore/persist.rs | 249 ++++ 8 files changed, 2337 insertions(+), 2075 deletions(-) delete mode 100644 storage/src/nodestore.rs create mode 100644 storage/src/nodestore/alloc.rs create mode 100644 storage/src/nodestore/hash.rs create mode 100644 storage/src/nodestore/header.rs create mode 100644 storage/src/nodestore/mod.rs create mode 100644 storage/src/nodestore/persist.rs diff --git a/storage/src/checker.rs b/storage/src/checker.rs index 60bbc836d241..2f9ce8531397 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -96,10 +96,11 @@ mod test { use super::*; use crate::linear::memory::MemStore; - use crate::nodestore::nodestore_test_utils::{ + use crate::nodestore::NodeStoreHeader; + use crate::nodestore::alloc::test_utils::{ test_write_free_area, test_write_header, test_write_new_node, }; - use crate::nodestore::{AREA_SIZES, FreeLists, NodeStoreHeader}; + use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; #[test] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 63425b78fa15..77e6e8dfeb94 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -46,7 +46,7 @@ pub use node::{ }; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, - NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, UpdateError, + NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, }; pub use linear::filebacked::FileBacked; diff --git a/storage/src/nodestore.rs b/storage/src/nodestore.rs deleted file mode 100644 index 751623cdc82c..000000000000 --- a/storage/src/nodestore.rs +++ /dev/null @@ -1,2072 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::linear::FileIoError; -use crate::logger::{debug, trace}; -use arc_swap::ArcSwap; -use arc_swap::access::DynAccess; -use bincode::{DefaultOptions, Options as _}; -use bytemuck_derive::{AnyBitPattern, NoUninit}; -use coarsetime::Instant; -use fastrace::local::LocalSpan; -use metrics::counter; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use smallvec::SmallVec; -use std::collections::HashMap; -use std::fmt::Debug; - -/// The [`NodeStore`] handles the serialization of nodes and -/// free space management of nodes in the page store. It lays out the format -/// of the [`PageStore`]. More specifically, it places a [`FileIdentifyingMagic`] -/// and a [`FreeSpaceHeader`] at the beginning -/// -/// Nodestores represent a revision of the trie. There are three types of nodestores: -/// - Committed: A committed revision of the trie. It has no in-memory changes. -/// - `MutableProposal`: A proposal that is still being modified. It has some nodes in memory. -/// - `ImmutableProposal`: A proposal that has been hashed and assigned addresses. It has no in-memory changes. -/// -/// The general lifecycle of nodestores is as follows: -/// ```mermaid -/// flowchart TD -/// subgraph subgraph["Committed Revisions"] -/// L("Latest Nodestore<Committed, S>") --- |...|O("Oldest NodeStore<Committed, S>") -/// end -/// O --> E("Expire") -/// L --> |start propose|M("NodeStore<ProposedMutable, S>") -/// M --> |finish propose + hash|I("NodeStore<ProposedImmutable, S>") -/// I --> |commit|N("New commit NodeStore<Committed, S>") -/// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF -/// ``` -use std::io::{Error, ErrorKind}; -use std::iter::FusedIterator; -use std::mem::{offset_of, take}; -use std::num::NonZeroU64; -use std::ops::Deref; -use std::sync::Arc; - -use crate::hashednode::hash_node; -use crate::node::persist::MaybePersistedNode; -use crate::node::{ByteCounter, Node}; -use crate::{ - CacheReadStrategy, Child, FileBacked, HashType, Path, ReadableStorage, SharedNode, TrieHash, -}; - -use super::linear::WritableStorage; - -/// [`NodeStore`] divides the linear store into blocks of different sizes. -/// [`AREA_SIZES`] is every valid block size. -pub(crate) const AREA_SIZES: [u64; 23] = [ - 16, // Min block size - 32, - 64, - 96, - 128, - 256, - 512, - 768, - 1024, - 1024 << 1, - 1024 << 2, - 1024 << 3, - 1024 << 4, - 1024 << 5, - 1024 << 6, - 1024 << 7, - 1024 << 8, - 1024 << 9, - 1024 << 10, - 1024 << 11, - 1024 << 12, - 1024 << 13, - 1024 << 14, -]; - -fn serializer() -> impl bincode::Options { - DefaultOptions::new().with_varint_encoding() -} - -fn area_size_hash() -> TrieHash { - let mut hasher = Sha256::new(); - for size in AREA_SIZES { - hasher.update(size.to_ne_bytes()); - } - hasher.finalize().into() -} - -// TODO: automate this, must stay in sync with above -const fn index_name(index: usize) -> &'static str { - match index { - 0 => "16", - 1 => "32", - 2 => "64", - 3 => "96", - 4 => "128", - 5 => "256", - 6 => "512", - 7 => "768", - 8 => "1024", - 9 => "2048", - 10 => "4096", - 11 => "8192", - 12 => "16384", - 13 => "32768", - 14 => "65536", - 15 => "131072", - 16 => "262144", - 17 => "524288", - 18 => "1048576", - 19 => "2097152", - 20 => "4194304", - 21 => "8388608", - 22 => "16777216", - _ => "unknown", - } -} - -/// The type of an index into the [`AREA_SIZES`] array -/// This is not usize because we can store this as a single byte -pub type AreaIndex = u8; - -// TODO danlaine: have type for index in AREA_SIZES -// Implement try_into() for it. -const NUM_AREA_SIZES: usize = AREA_SIZES.len(); -const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; -const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; - -#[inline] -fn new_area_index(n: usize) -> AreaIndex { - n.try_into().expect("Area index out of bounds") -} - -/// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. -fn area_size_to_index(n: u64) -> Result { - if n > MAX_AREA_SIZE { - return Err(Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - )); - } - - if n <= MIN_AREA_SIZE { - return Ok(0); - } - - AREA_SIZES - .iter() - .position(|&size| size >= n) - .map(new_area_index) - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - ) - }) -} - -/// Objects cannot be stored at the zero address, so a [`LinearAddress`] is guaranteed not -/// to be zero. This reserved zero can be used as a [None] value for some use cases. In particular, -/// branches can use `Option` which is the same size as a [`LinearAddress`] -pub type LinearAddress = NonZeroU64; - -/// Each [`StoredArea`] contains an [Area] which is either a [Node] or a [`FreeArea`]. -#[repr(u8)] -#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] -enum Area { - Node(T), - Free(U) = 255, // this is magic: no node starts with a byte of 255 -} - -/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the -/// [`NodeStoreHeader`] is a [`StoredArea`]. -/// -/// As an overview of what this looks like stored, we get something like this: -/// - Byte 0: The index of the area size -/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf -/// - Bytes 2..n: The actual data -#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] -struct StoredArea { - /// Index in [`AREA_SIZES`] of this area's size - area_size_index: AreaIndex, - area: T, -} - -impl NodeStore { - /// Returns (index, `area_size`) for the stored area at `addr`. - /// `index` is the index of `area_size` in the array of valid block sizes. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the area cannot be read. - pub fn area_index_and_size( - &self, - addr: LinearAddress, - ) -> Result<(AreaIndex, u64), FileIoError> { - let mut area_stream = self.storage.stream_from(addr.get())?; - - let index: AreaIndex = serializer() - .deserialize_from(&mut area_stream) - .map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("deserialize".to_string()), - ) - })?; - - let size = *AREA_SIZES - .get(index as usize) - .ok_or(self.storage.file_io_error( - Error::other(format!("Invalid area size index {index}")), - addr.get(), - None, - ))?; - - Ok((index, size)) - } - - /// Read a [Node] from the provided [`LinearAddress`]. - /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - pub fn read_node_from_disk( - &self, - addr: LinearAddress, - mode: &'static str, - ) -> Result { - if let Some(node) = self.storage.read_cached_node(addr, mode) { - return Ok(node); - } - - debug_assert!(addr.get() % 8 == 0); - - // saturating because there is no way we can be reading at u64::MAX - // and this will fail very soon afterwards - let actual_addr = addr.get().saturating_add(1); // skip the length byte - - let _span = LocalSpan::enter_with_local_parent("read_and_deserialize"); - - let area_stream = self.storage.stream_from(actual_addr)?; - let node: SharedNode = Node::from_reader(area_stream) - .map_err(|e| { - self.storage - .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) - })? - .into(); - match self.storage.cache_read_strategy() { - CacheReadStrategy::All => { - self.storage.cache_node(addr, node.clone()); - } - CacheReadStrategy::BranchReads => { - if !node.is_leaf() { - self.storage.cache_node(addr, node.clone()); - } - } - CacheReadStrategy::WritesOnly => {} - } - Ok(node) - } - - /// Read a [Node] from the provided [`LinearAddress`] and size. - /// This is an uncached read, primarily used by check utilities - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - pub fn uncached_read_node_and_size( - &self, - addr: LinearAddress, - ) -> Result<(SharedNode, u8), FileIoError> { - let mut area_stream = self.storage.stream_from(addr.get())?; - let mut size = [0u8]; - area_stream.read_exact(&mut size).map_err(|e| { - self.storage.file_io_error( - e, - addr.get(), - Some("uncached_read_node_and_size".to_string()), - ) - })?; - self.storage.stream_from(addr.get().saturating_add(1))?; - let node: SharedNode = Node::from_reader(area_stream) - .map_err(|e| { - self.storage.file_io_error( - e, - addr.get(), - Some("uncached_read_node_and_size".to_string()), - ) - })? - .into(); - Ok((node, size[0])) - } - - /// Get the size of an area index (used by the checker) - /// - /// # Panics - /// - /// Panics if `index` is out of bounds for the `AREA_SIZES` array. - #[must_use] - pub const fn size_from_area_index(index: AreaIndex) -> u64 { - #[expect(clippy::indexing_slicing)] - AREA_SIZES[index as usize] - } -} - -impl NodeStore { - /// Open an existing [`NodeStore`] - /// Assumes the header is written in the [`ReadableStorage`]. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the header cannot be read or validated. - pub fn open(storage: Arc) -> Result { - let mut stream = storage.stream_from(0)?; - let mut header = NodeStoreHeader::new(); - let header_bytes = bytemuck::bytes_of_mut(&mut header); - stream - .read_exact(header_bytes) - .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; - - drop(stream); - - header - .validate() - .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; - - let mut nodestore = Self { - header, - kind: Committed { - deleted: Box::default(), - root_hash: None, - root: header.root_address.map(Into::into), - }, - storage, - }; - - if let Some(root_address) = nodestore.header.root_address { - let node = nodestore.read_node_from_disk(root_address, "open"); - let root_hash = node.map(|n| hash_node(&n, &Path(SmallVec::default())))?; - nodestore.kind.root_hash = Some(root_hash.into_triehash()); - } - - Ok(nodestore) - } - - /// Create a new, empty, Committed [`NodeStore`] and clobber - /// the underlying store with an empty freelist and no root node - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the storage cannot be accessed. - pub fn new_empty_committed(storage: Arc) -> Result { - let header = NodeStoreHeader::new(); - - Ok(Self { - header, - storage, - kind: Committed { - deleted: Box::default(), - root_hash: None, - root: None, - }, - }) - } -} - -/// Some nodestore kinds implement Parentable. -/// -/// This means that the nodestore can have children. -/// Only [`ImmutableProposal`] and [Committed] implement this trait. -/// [`MutableProposal`] does not implement this trait because it is not a valid parent. -/// TODO: Maybe this can be renamed to `ImmutableNodestore` -pub trait Parentable { - /// Returns the parent of this nodestore. - fn as_nodestore_parent(&self) -> NodeStoreParent; - /// Returns the root hash of this nodestore. This works because all parentable nodestores have a hash - fn root_hash(&self) -> Option; - /// Returns the root node - fn root(&self) -> Option; -} - -impl Parentable for Arc { - fn as_nodestore_parent(&self) -> NodeStoreParent { - NodeStoreParent::Proposed(Arc::clone(self)) - } - fn root_hash(&self) -> Option { - self.root_hash.clone() - } - fn root(&self) -> Option { - self.root.clone() - } -} - -impl NodeStore, S> { - /// When an immutable proposal commits, we need to reparent any proposal that - /// has the committed proposal as it's parent - pub fn commit_reparent(&self, other: &Arc, S>>) { - match *other.kind.parent.load() { - NodeStoreParent::Proposed(ref parent) => { - if Arc::ptr_eq(&self.kind, parent) { - other - .kind - .parent - .store(NodeStoreParent::Committed(self.kind.root_hash()).into()); - } - } - NodeStoreParent::Committed(_) => {} - } - } -} - -impl Parentable for Committed { - fn as_nodestore_parent(&self) -> NodeStoreParent { - NodeStoreParent::Committed(self.root_hash.clone()) - } - fn root_hash(&self) -> Option { - self.root_hash.clone() - } - fn root(&self) -> Option { - self.root.clone() - } -} - -impl NodeStore { - /// Create a new `MutableProposal` [`NodeStore`] from a parent [`NodeStore`] - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the parent root cannot be read. - pub fn new( - parent: &Arc>, - ) -> Result { - let mut deleted = Vec::default(); - let root = if let Some(ref root) = parent.kind.root() { - deleted.push(root.clone()); - let root = root.as_shared_node(parent)?.deref().clone(); - Some(root) - } else { - None - }; - let kind = MutableProposal { - root, - deleted, - parent: parent.kind.as_nodestore_parent(), - }; - Ok(NodeStore { - header: parent.header, - kind, - storage: parent.storage.clone(), - }) - } - - /// Marks the node at `addr` as deleted in this proposal. - pub fn delete_node(&mut self, node: MaybePersistedNode) { - trace!("Pending delete at {node:?}"); - self.kind.deleted.push(node); - } - - /// Reads a node for update, marking it as deleted in this proposal. - /// We get an arc from cache (reading it from disk if necessary) then - /// copy/clone the node and return it. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - pub fn read_for_update(&mut self, node: MaybePersistedNode) -> Result { - let arc_wrapped_node = node.as_shared_node(self)?; - self.delete_node(node); - Ok((*arc_wrapped_node).clone()) - } - - /// Returns the root of this proposal. - pub const fn mut_root(&mut self) -> &mut Option { - &mut self.kind.root - } -} - -impl NodeStore { - /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. - /// This is used during testing and during the creation of an in-memory merkle for proofs - /// - /// # Panics - /// - /// Panics if the header cannot be written. - pub fn new_empty_proposal(storage: Arc) -> Self { - let header = NodeStoreHeader::new(); - let header_bytes = bytemuck::bytes_of(&header); - storage - .write(0, header_bytes) - .expect("failed to write header"); - NodeStore { - header, - kind: MutableProposal { - root: None, - deleted: Vec::default(), - parent: NodeStoreParent::Committed(None), - }, - storage, - } - } -} - -impl NodeStore, S> { - /// Attempts to allocate `n` bytes from the free lists. - /// If successful returns the address of the newly allocated area - /// and the index of the free list that was used. - /// If there are no free areas big enough for `n` bytes, returns None. - /// TODO danlaine: If we return a larger area than requested, we should split it. - #[expect(clippy::indexing_slicing)] - fn allocate_from_freed( - &mut self, - n: u64, - ) -> Result, FileIoError> { - // Find the smallest free list that can fit this size. - let index_wanted = area_size_to_index(n).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("allocate_from_freed".to_string())) - })?; - - if let Some((index, free_stored_area_addr)) = self - .header - .free_lists - .iter_mut() - .enumerate() - .skip(index_wanted as usize) - .find(|item| item.1.is_some()) - { - let address = free_stored_area_addr - .take() - .expect("impossible due to find earlier"); - // Get the first free block of sufficient size. - if let Some(free_head) = self.storage.free_list_cache(address) { - trace!("free_head@{address}(cached): {free_head:?} size:{index}"); - *free_stored_area_addr = free_head; - } else { - let (free_head, read_index) = - FreeArea::from_storage(self.storage.as_ref(), address)?; - debug_assert_eq!(read_index as usize, index); - - // Update the free list to point to the next free block. - *free_stored_area_addr = free_head.next_free_block; - } - - counter!("firewood.space.reused", "index" => index_name(index)) - .increment(AREA_SIZES[index]); - counter!("firewood.space.wasted", "index" => index_name(index)) - .increment(AREA_SIZES[index].saturating_sub(n)); - - // Return the address of the newly allocated block. - trace!("Allocating from free list: addr: {address:?}, size: {index}"); - return Ok(Some((address, index as AreaIndex))); - } - - trace!("No free blocks of sufficient size {index_wanted} found"); - counter!("firewood.space.from_end", "index" => index_name(index_wanted as usize)) - .increment(AREA_SIZES[index_wanted as usize]); - Ok(None) - } - - #[expect(clippy::indexing_slicing)] - fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { - let index = area_size_to_index(n).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("allocate_from_end".to_string())) - })?; - let area_size = AREA_SIZES[index as usize]; - let addr = LinearAddress::new(self.header.size).expect("node store size can't be 0"); - self.header.size = self.header.size.saturating_add(area_size); - debug_assert!(addr.get() % 8 == 0); - trace!("Allocating from end: addr: {addr:?}, size: {index}"); - Ok((addr, index)) - } - - /// Returns the length of the serialized area for a node. - fn stored_len(node: &Node) -> u64 { - let mut bytecounter = ByteCounter::new(); - node.as_bytes(0, &mut bytecounter); - bytecounter.count() - } - - /// Returns an address that can be used to store the given `node` and updates - /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. - /// Also returns the index of the free list the node was allocated from. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be allocated. - pub fn allocate_node( - &mut self, - node: &Node, - ) -> Result<(LinearAddress, AreaIndex), FileIoError> { - let stored_area_size = Self::stored_len(node); - - // Attempt to allocate from a free list. - // If we can't allocate from a free list, allocate past the existing - // of the ReadableStorage. - let (addr, index) = match self.allocate_from_freed(stored_area_size)? { - Some((addr, index)) => (addr, index), - None => self.allocate_from_end(stored_area_size)?, - }; - - Ok((addr, index)) - } -} - -impl NodeStore { - /// Deletes the [Node] at the given address, updating the next pointer at - /// the given addr, and changing the header of this committed nodestore to - /// have the address on the freelist - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be deleted. - #[expect(clippy::indexing_slicing)] - pub fn delete_node(&mut self, node: MaybePersistedNode) -> Result<(), FileIoError> { - let Some(addr) = node.as_linear_address() else { - return Ok(()); - }; - debug_assert!(addr.get() % 8 == 0); - - let (area_size_index, _) = self.area_index_and_size(addr)?; - trace!("Deleting node at {addr:?} of size {area_size_index}"); - counter!("firewood.delete_node", "index" => index_name(area_size_index as usize)) - .increment(1); - counter!("firewood.space.freed", "index" => index_name(area_size_index as usize)) - .increment(AREA_SIZES[area_size_index as usize]); - - // The area that contained the node is now free. - let area: Area = Area::Free(FreeArea { - next_free_block: self.header.free_lists[area_size_index as usize], - }); - - let stored_area = StoredArea { - area_size_index, - area, - }; - - let stored_area_bytes = serializer().serialize(&stored_area).map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("delete_node".to_string()), - ) - })?; - - self.storage.write(addr.into(), &stored_area_bytes)?; - - self.storage - .add_to_free_list_cache(addr, self.header.free_lists[area_size_index as usize]); - - // The newly freed block is now the head of the free list. - self.header.free_lists[area_size_index as usize] = Some(addr); - - Ok(()) - } -} - -/// An error from doing an update -#[derive(Debug)] -pub enum UpdateError { - /// An IO error occurred during the update - Io(Error), -} - -impl From for UpdateError { - fn from(value: Error) -> Self { - UpdateError::Io(value) - } -} - -/// Can be used by filesystem tooling such as "file" to identify -/// the version of firewood used to create this [`NodeStore`] file. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, NoUninit, AnyBitPattern)] -#[repr(transparent)] -struct Version { - bytes: [u8; 16], -} - -impl Version { - const SIZE: usize = size_of::(); - - /// Version >= 0.0.4 - /// - /// Increase as needed to set the minimum required version of `firewood-storage` for - /// compatibility checks. - /// - /// We may want to add migrations if we need to add a breaking change. - const BASE_VERSION: semver::Comparator = semver::Comparator { - op: semver::Op::GreaterEq, - major: 0, - minor: Some(0), - patch: Some(4), - pre: semver::Prerelease::EMPTY, - }; - - /// Validates that the version identifier is valid and compatible with the current - /// build of firewood. - /// - /// # Errors - /// - /// - If the token contains invalid utf-8 bytes (nul is allowed). - /// - If the token does not start with "firewood ". - /// - If the version is not parsable by [`semver::Version`]. - /// - If the version is not compatible with the current build of firewood. - /// - Currently, the minimum required version is 0.0.4. - fn validate(&self) -> Result<(), Error> { - let version = std::str::from_utf8(&self.bytes).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!( - "Invalid database version: invalid utf-8: {e} (original: [{:032x}])", - u128::from_be_bytes(self.bytes) - ), - ) - })?; - - // strip trailling nuls as they're only for padding - let version = version.trim_end_matches('\0'); - - // strip magic prefix or error - let version = version.strip_prefix("firewood ").ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - format!( - "Invalid database version: does not start with magic 'firewood ': {version}", - ), - ) - })?; - - // Version strings from CARGO_PKG_VERSION are guaranteed to be parsable by - // semver (cargo uses the same library). - let version = semver::Version::parse(version).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!( - "Invalid version string: unable to parse `{version}` as a semver string: {e}" - ), - ) - })?; - - // verify base compatibility version - if !Self::BASE_VERSION.matches(&version) { - return Err(Error::new( - ErrorKind::InvalidData, - format!( - "Database was created with firewood version {version}; however, this build of firewood requires version {}", - Self::BASE_VERSION, - ), - )); - } - - debug!( - "Database version is valid: {version} {}", - Self::BASE_VERSION - ); - Ok(()) - } - - /// Construct a [`Version`] instance for the current build of firewood. - fn new() -> Self { - // Note that with this magic token of 9 bytes, we can store a version string of - // up to 7 bytes. If we always include the major, minor, and patch versions, - // then no more than two of three can be 2 digits long. - const VERSION_STR: &str = concat!("firewood ", env!("CARGO_PKG_VERSION")); - const { - assert!( - VERSION_STR.len() <= Version::SIZE, - concat!( - "Database version string `firewood ", - env!("CARGO_PKG_VERSION"), - "` is too long for the Version struct! Update Cargo.toml or modify this code.", - ), - ); - } - - // pad with nul bytes - let mut bytes = [0u8; Version::SIZE]; - bytes - .get_mut(..VERSION_STR.len()) - .expect("must fit") - .copy_from_slice(VERSION_STR.as_bytes()); - - Self { bytes } - } -} - -pub type FreeLists = [Option; NUM_AREA_SIZES]; - -/// Persisted metadata for a [`NodeStore`]. -/// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. -#[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] -#[repr(C)] -pub struct NodeStoreHeader { - /// Identifies the version of firewood used to create this [`NodeStore`]. - version: Version, - /// always "1"; verifies endianness - endian_test: u64, - size: u64, - /// Element i is the pointer to the first free block of size `BLOCK_SIZES[i]`. - free_lists: FreeLists, - root_address: Option, - /// The hash of the area sizes used in this database to prevent someone from changing the - /// area sizes and trying to read old databases with the wrong area sizes. - area_size_hash: [u8; 32], - /// Whether ethhash was enabled when this database was created. - ethhash: u64, -} - -impl NodeStoreHeader { - /// The first SIZE bytes of the `ReadableStorage` are reserved for the - /// [`NodeStoreHeader`]. - /// We also want it aligned to a disk block - pub(crate) const SIZE: u64 = 2048; - - /// Number of extra bytes to write on the first creation of the `NodeStoreHeader` - /// (zero-padded) - /// also a compile time check to prevent setting SIZE too small - const EXTRA_BYTES: usize = Self::SIZE as usize - std::mem::size_of::(); - - fn new() -> Self { - Self { - // The store just contains the header at this point - size: Self::SIZE, - endian_test: 1, - root_address: None, - version: Version::new(), - free_lists: Default::default(), - area_size_hash: area_size_hash() - .as_slice() - .try_into() - .expect("sizes should match"), - #[cfg(feature = "ethhash")] - ethhash: 1, - #[cfg(not(feature = "ethhash"))] - ethhash: 0, - } - } - - fn validate(&self) -> Result<(), Error> { - trace!("Checking version..."); - self.version.validate()?; - - trace!("Checking endianness..."); - self.validate_endian_test()?; - - trace!("Checking area size hash..."); - self.validate_area_size_hash()?; - - trace!("Checking if db ethhash flag matches build feature..."); - self.validate_ethhash()?; - - Ok(()) - } - - fn validate_endian_test(&self) -> Result<(), Error> { - if self.endian_test == 1 { - Ok(()) - } else { - Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in endianness", - )) - } - } - - fn validate_area_size_hash(&self) -> Result<(), Error> { - if self.area_size_hash == area_size_hash().as_slice() { - Ok(()) - } else { - Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened due to difference in area size hash", - )) - } - } - - #[cfg(not(feature = "ethhash"))] - fn validate_ethhash(&self) -> Result<(), Error> { - if self.ethhash == 0 { - Ok(()) - } else { - Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created with ethhash enabled", - )) - } - } - - #[cfg(feature = "ethhash")] - fn validate_ethhash(&self) -> Result<(), Error> { - if self.ethhash == 1 { - Ok(()) - } else { - Err(Error::new( - ErrorKind::InvalidData, - "Database cannot be opened as it was created without ethhash enabled", - )) - } - } -} - -/// A [`FreeArea`] is stored at the start of the area that contained a node that -/// has been freed. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -struct FreeArea { - next_free_block: Option, -} - -impl FreeArea { - fn from_storage( - storage: &S, - address: LinearAddress, - ) -> Result<(Self, AreaIndex), FileIoError> { - let free_area_addr = address.get(); - let stored_area_stream = storage.stream_from(free_area_addr)?; - let stored_area: StoredArea> = serializer() - .deserialize_from(stored_area_stream) - .map_err(|e| { - storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - free_area_addr, - Some("FreeArea::from_storage".to_string()), - ) - })?; - let StoredArea { - area: Area::Free(free_area), - area_size_index: stored_area_index, - } = stored_area - else { - return Err(storage.file_io_error( - Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), - free_area_addr, - Some("FreeArea::from_storage".to_string()), - )); - }; - - Ok((free_area, stored_area_index as AreaIndex)) - } -} - -/// Reads from an immutable (i.e. already hashed) merkle trie. -pub trait HashedNodeReader: TrieReader { - /// Gets the address of the root node of an immutable merkle trie. - fn root_address(&self) -> Option; - - /// Gets the hash of the root node of an immutable merkle trie. - fn root_hash(&self) -> Option; -} - -/// Reads nodes and the root address from a merkle trie. -pub trait TrieReader: NodeReader + RootReader {} -impl TrieReader for T where T: NodeReader + RootReader {} - -/// Reads nodes from a merkle trie. -pub trait NodeReader { - /// Returns the node at `addr`. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - fn read_node(&self, addr: LinearAddress) -> Result; -} - -impl NodeReader for T -where - T: Deref, - T::Target: NodeReader, -{ - fn read_node(&self, addr: LinearAddress) -> Result { - self.deref().read_node(addr) - } -} - -impl RootReader for T -where - T: Deref, - T::Target: RootReader, -{ - fn root_node(&self) -> Option { - self.deref().root_node() - } -} - -/// Reads the root of a merkle trie. -pub trait RootReader { - /// Returns the root of the trie. - fn root_node(&self) -> Option; -} - -/// A committed revision of a merkle trie. -#[derive(Clone, Debug)] -pub struct Committed { - deleted: Box<[MaybePersistedNode]>, - root_hash: Option, - root: Option, -} - -impl ReadInMemoryNode for Committed { - // A committed revision has no in-memory changes. All its nodes are in storage. - fn read_in_memory_node(&self, _addr: LinearAddress) -> Option { - None - } -} - -#[derive(Clone, Debug)] -pub enum NodeStoreParent { - Proposed(Arc), - Committed(Option), -} - -impl PartialEq for NodeStoreParent { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (NodeStoreParent::Proposed(a), NodeStoreParent::Proposed(b)) => Arc::ptr_eq(a, b), - (NodeStoreParent::Committed(a), NodeStoreParent::Committed(b)) => a == b, - _ => false, - } - } -} - -impl Eq for NodeStoreParent {} - -#[derive(Debug)] -/// Contains state for a proposed revision of the trie. -pub struct ImmutableProposal { - /// Address --> Node for nodes created in this proposal. - new: HashMap, - /// Nodes that have been deleted in this proposal. - deleted: Box<[MaybePersistedNode]>, - /// The parent of this proposal. - parent: Arc>, - /// The hash of the root node for this proposal - root_hash: Option, - /// The root node, either in memory or on disk - root: Option, -} - -impl ImmutableProposal { - /// Returns true if the parent of this proposal is committed and has the given hash. - #[must_use] - fn parent_hash_is(&self, hash: Option) -> bool { - match > as arc_swap::access::DynAccess>>::load( - &self.parent, - ) - .as_ref() - { - NodeStoreParent::Committed(root_hash) => *root_hash == hash, - NodeStoreParent::Proposed(_) => false, - } - } -} - -impl ReadInMemoryNode for ImmutableProposal { - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - // Check if the node being requested was created in this proposal. - if let Some((_, node)) = self.new.get(&addr) { - return Some(node.clone()); - } - - // It wasn't. Try our parent, and its parent, and so on until we find it or find - // a committed revision. - match *self.parent.load() { - NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), - NodeStoreParent::Committed(_) => None, - } - } -} - -/// Proposed [`NodeStore`] types keep some nodes in memory. These nodes are new nodes that were allocated from -/// the free list, but are not yet on disk. This trait checks to see if a node is in memory and returns it if -/// it's there. If it's not there, it will be read from disk. -/// -/// This trait does not know anything about the underlying storage, so it just returns None if the node isn't in memory. -pub trait ReadInMemoryNode { - /// Returns the node at `addr` if it is in memory. - /// Returns None if it isn't. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option; -} - -impl ReadInMemoryNode for T -where - T: Deref, - T::Target: ReadInMemoryNode, -{ - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - self.deref().read_in_memory_node(addr) - } -} - -/// Contains the state of a revision of a merkle trie. -/// -/// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. -/// The second generic parameter is the type of the storage used, either -/// in-memory or on-disk. -/// -/// The lifecycle of a [`NodeStore`] is as follows: -/// 1. Create a new, empty, [Committed] [`NodeStore`] using [`NodeStore::new_empty_committed`]. -/// 2. Create a [`NodeStore`] from disk using [`NodeStore::open`]. -/// 3. Create a new mutable proposal from either a [Committed] or [`ImmutableProposal`] [`NodeStore`] using [`NodeStore::new`]. -/// 4. Convert a mutable proposal to an immutable proposal using [`std::convert::TryInto`], which hashes the nodes and assigns addresses -/// 5. Convert an immutable proposal to a committed revision using [`std::convert::TryInto`], which writes the nodes to disk. - -#[derive(Debug)] -pub struct NodeStore { - // Metadata for this revision. - header: NodeStoreHeader, - /// This is one of [Committed], [`ImmutableProposal`], or [`MutableProposal`]. - kind: T, - /// Persisted storage to read nodes from. - storage: Arc, -} - -/// Contains the state of a proposal that is still being modified. -#[derive(Debug)] -pub struct MutableProposal { - /// The root of the trie in this proposal. - root: Option, - /// Nodes that have been deleted in this proposal. - deleted: Vec, - parent: NodeStoreParent, -} - -impl ReadInMemoryNode for NodeStoreParent { - /// Returns the node at `addr` if it is in memory from a parent proposal. - /// If the base revision is committed, there are no in-memory nodes, so we return None - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - match self { - NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), - NodeStoreParent::Committed(_) => None, - } - } -} - -impl ReadInMemoryNode for MutableProposal { - /// [`MutableProposal`] types do not have any nodes in memory, but their parent proposal might, so we check there. - /// This might be recursive: a grandparent might also have that node in memory. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - self.parent.read_in_memory_node(addr) - } -} - -impl, S: ReadableStorage> From> - for NodeStore -{ - fn from(val: NodeStore) -> Self { - NodeStore { - header: val.header, - kind: MutableProposal { - root: None, - deleted: Vec::default(), - parent: val.kind.into(), - }, - storage: val.storage, - } - } -} - -/// Commit a proposal to a new revision of the trie -impl From> for NodeStore { - fn from(val: NodeStore) -> Self { - NodeStore { - header: val.header, - kind: Committed { - deleted: val.kind.deleted, - root_hash: val.kind.root_hash, - root: val.kind.root, - }, - storage: val.storage, - } - } -} - -/// Classified children for ethereum hash processing -#[cfg(feature = "ethhash")] -struct ClassifiedChildren<'a> { - unhashed: Vec<(usize, Node)>, - hashed: Vec<(usize, (MaybePersistedNode, &'a mut HashType))>, -} - -impl NodeStore, S> { - /// Helper function to classify children for ethereum hash processing - /// We have some special cases based on the number of children - /// and whether they are hashed or unhashed, so we need to classify them. - #[cfg(feature = "ethhash")] - fn ethhash_classify_children<'a>( - &self, - children: &'a mut [Option; crate::node::BranchNode::MAX_CHILDREN], - ) -> ClassifiedChildren<'a> { - children.iter_mut().enumerate().fold( - ClassifiedChildren { - unhashed: Vec::new(), - hashed: Vec::new(), - }, - |mut acc, (idx, child)| { - match child { - None => {} - Some(Child::AddressWithHash(a, h)) => { - // Convert address to MaybePersistedNode - let maybe_persisted_node = MaybePersistedNode::from(*a); - acc.hashed.push((idx, (maybe_persisted_node, h))); - } - Some(Child::Node(node)) => acc.unhashed.push((idx, node.clone())), - Some(Child::MaybePersisted(maybe_persisted, h)) => { - // For MaybePersisted, we need to get the address if it's persisted - if let Some(addr) = maybe_persisted.as_linear_address() { - let maybe_persisted_node = MaybePersistedNode::from(addr); - acc.hashed.push((idx, (maybe_persisted_node, h))); - } else { - // If not persisted, we need to get the node to hash it - if let Ok(node) = maybe_persisted.as_shared_node(self) { - acc.unhashed.push((idx, node.deref().clone())); - } - } - } - } - acc - }, - ) - } - - /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. - /// Returns the hashed node and its hash. - fn hash_helper( - &mut self, - mut node: Node, - path_prefix: &mut Path, - new_nodes: &mut HashMap, - #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, - ) -> Result<(LinearAddress, HashType), FileIoError> { - // If this is a branch, find all unhashed children and recursively call hash_helper on them. - trace!("hashing {node:?} at {path_prefix:?}"); - if let Node::Branch(ref mut b) = node { - // special case code for ethereum hashes at the account level - #[cfg(feature = "ethhash")] - let make_fake_root = if path_prefix.0.len().saturating_add(b.partial_path.0.len()) == 64 - { - // looks like we're at an account branch - // tally up how many hashes we need to deal with - let ClassifiedChildren { - unhashed, - mut hashed, - } = self.ethhash_classify_children(&mut b.children); - trace!("hashed {hashed:?} unhashed {unhashed:?}"); - if hashed.len() == 1 { - // we were left with one hashed node that must be rehashed - let invalidated_node = hashed.first_mut().expect("hashed is not empty"); - // Extract the address from the MaybePersistedNode - let addr = invalidated_node - .1 - .0 - .as_linear_address() - .expect("hashed node should be persisted"); - let mut hashable_node = self.read_node(addr)?.deref().clone(); - let original_length = path_prefix.len(); - path_prefix.0.extend(b.partial_path.0.iter().copied()); - if unhashed.is_empty() { - hashable_node.update_partial_path(Path::from_nibbles_iterator( - std::iter::once(invalidated_node.0 as u8) - .chain(hashable_node.partial_path().0.iter().copied()), - )); - } else { - path_prefix.0.push(invalidated_node.0 as u8); - } - let hash = hash_node(&hashable_node, path_prefix); - path_prefix.0.truncate(original_length); - *invalidated_node.1.1 = hash; - } - // handle the single-child case for an account special below - if hashed.is_empty() && unhashed.len() == 1 { - Some(unhashed.last().expect("only one").0 as u8) - } else { - None - } - } else { - // not a single child - None - }; - - // branch children cases: - // 1. 1 child, already hashed - // 2. >1 child, already hashed, - // 3. 1 hashed child, 1 unhashed child - // 4. 0 hashed, 1 unhashed <-- handle child special - // 5. 1 hashed, >0 unhashed <-- rehash case - // 6. everything already hashed - - for (nibble, child) in b.children.iter_mut().enumerate() { - // If this is empty or already hashed, we're done - // Empty matches None, and non-Node types match Some(None) here, so we want - // Some(Some(node)) - let Some(Some(child_node)) = child.as_mut().map(|child| child.as_mut_node()) else { - continue; - }; - - // remove the child from the children array, we will replace it with a hashed variant - let child_node = std::mem::take(child_node); - - // Hash this child and update - // we extend and truncate path_prefix to reduce memory allocations - let original_length = path_prefix.len(); - path_prefix.0.extend(b.partial_path.0.iter().copied()); - #[cfg(feature = "ethhash")] - if make_fake_root.is_none() { - // we don't push the nibble there is only one unhashed child and - // we're on an account - path_prefix.0.push(nibble as u8); - } - #[cfg(not(feature = "ethhash"))] - path_prefix.0.push(nibble as u8); - - #[cfg(feature = "ethhash")] - let (child_addr, child_hash) = - self.hash_helper(child_node, path_prefix, new_nodes, make_fake_root)?; - #[cfg(not(feature = "ethhash"))] - let (child_addr, child_hash) = - self.hash_helper(child_node, path_prefix, new_nodes)?; - - *child = Some(Child::AddressWithHash(child_addr, child_hash)); - path_prefix.0.truncate(original_length); - } - } - // At this point, we either have a leaf or a branch with all children hashed. - // if the encoded child hash <32 bytes then we use that RLP - - #[cfg(feature = "ethhash")] - // if we have a child that is the only child of an account branch, we will hash this child as if it - // is a root node. This means we have to take the nibble from the parent and prefix it to the partial path - let hash = if let Some(nibble) = fake_root_extra_nibble { - let mut fake_root = node.clone(); - trace!("old node: {fake_root:?}"); - fake_root.update_partial_path(Path::from_nibbles_iterator( - std::iter::once(nibble).chain(fake_root.partial_path().0.iter().copied()), - )); - trace!("new node: {fake_root:?}"); - hash_node(&fake_root, path_prefix) - } else { - hash_node(&node, path_prefix) - }; - - #[cfg(not(feature = "ethhash"))] - let hash = hash_node(&node, path_prefix); - - let (addr, size) = self.allocate_node(&node)?; - - new_nodes.insert(addr, (size, node.into())); - - Ok((addr, hash)) - } - - /// Re-export the `parent_hash_is` function of [`ImmutableProposal`]. - #[must_use] - pub fn parent_hash_is(&self, hash: Option) -> bool { - self.kind.parent_hash_is(hash) - } -} - -impl NodeStore { - /// Persist the header from this proposal to storage. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the header cannot be written. - pub fn flush_header(&self) -> Result<(), FileIoError> { - let header_bytes = bytemuck::bytes_of(&self.header); - self.storage.write(0, header_bytes)?; - Ok(()) - } - - /// Persist the header, including all the padding - /// This is only done the first time we write the header - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the header cannot be written. - pub fn flush_header_with_padding(&self) -> Result<(), FileIoError> { - let header_bytes = bytemuck::bytes_of(&self.header) - .iter() - .copied() - .chain(std::iter::repeat_n(0u8, NodeStoreHeader::EXTRA_BYTES)) - .collect::>(); - debug_assert_eq!(header_bytes.len(), NodeStoreHeader::SIZE as usize); - - self.storage.write(0, &header_bytes)?; - Ok(()) - } -} - -impl NodeStore, FileBacked> { - /// Persist the freelist from this proposal to storage. - #[fastrace::trace(short_name = true)] - pub fn flush_freelist(&self) -> Result<(), FileIoError> { - // Write the free lists to storage - let free_list_bytes = bytemuck::bytes_of(&self.header.free_lists); - let free_list_offset = offset_of!(NodeStoreHeader, free_lists) as u64; - self.storage.write(free_list_offset, free_list_bytes)?; - Ok(()) - } - - /// Persist all the nodes of a proposal to storage. - #[fastrace::trace(short_name = true)] - #[cfg(not(feature = "io-uring"))] - pub fn flush_nodes(&self) -> Result<(), FileIoError> { - let flush_start = Instant::now(); - - for (addr, (area_size_index, node)) in &self.kind.new { - let mut stored_area_bytes = Vec::new(); - node.as_bytes(*area_size_index, &mut stored_area_bytes); - self.storage - .write(addr.get(), stored_area_bytes.as_slice())?; - } - - self.storage - .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; - - let flush_time = flush_start.elapsed().as_millis(); - counter!("firewood.flush_nodes").increment(flush_time); - - Ok(()) - } - - /// Persist all the nodes of a proposal to storage. - #[fastrace::trace(short_name = true)] - #[cfg(feature = "io-uring")] - pub fn flush_nodes(&self) -> Result<(), FileIoError> { - use std::pin::Pin; - - #[derive(Clone, Debug)] - struct PinnedBufferEntry { - pinned_buffer: Pin>, - offset: Option, - } - - /// Helper function to handle completion queue entries and check for errors - fn handle_completion_queue( - storage: &FileBacked, - completion_queue: io_uring::cqueue::CompletionQueue<'_>, - saved_pinned_buffers: &mut [PinnedBufferEntry], - ) -> Result<(), FileIoError> { - for entry in completion_queue { - let item = entry.user_data() as usize; - let pbe = saved_pinned_buffers - .get_mut(item) - .expect("should be an index into the array"); - - if entry.result() - != pbe - .pinned_buffer - .len() - .try_into() - .expect("buffer should be small enough") - { - let error = if entry.result() >= 0 { - std::io::Error::other("Partial write") - } else { - std::io::Error::from_raw_os_error(0 - entry.result()) - }; - return Err(storage.file_io_error( - error, - pbe.offset.expect("offset should be Some"), - Some("write failure".to_string()), - )); - } - pbe.offset = None; - } - Ok(()) - } - - const RINGSIZE: usize = FileBacked::RINGSIZE as usize; - - let flush_start = Instant::now(); - - let mut ring = self.storage.ring.lock().expect("poisoned lock"); - let mut saved_pinned_buffers = vec![ - PinnedBufferEntry { - pinned_buffer: Pin::new(Box::new([0; 0])), - offset: None, - }; - RINGSIZE - ]; - for (&addr, &(area_size_index, ref node)) in &self.kind.new { - let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger - node.as_bytes(area_size_index, &mut serialized); - let mut serialized = serialized.into_boxed_slice(); - loop { - // Find the first available write buffer, enumerate to get the position for marking it completed - if let Some((pos, pbe)) = saved_pinned_buffers - .iter_mut() - .enumerate() - .find(|(_, pbe)| pbe.offset.is_none()) - { - pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); - pbe.offset = Some(addr.get()); - - let submission_queue_entry = self - .storage - .make_op(&pbe.pinned_buffer) - .offset(addr.get()) - .build() - .user_data(pos as u64); - - // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope - // until the operation has been completed. This is ensured by having a Some(offset) - // and not marking it None until the kernel has said it's done below. - #[expect(unsafe_code)] - while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { - ring.submitter().squeue_wait().map_err(|e| { - self.storage.file_io_error( - e, - addr.get(), - Some("io-uring squeue_wait".to_string()), - ) - })?; - trace!("submission queue is full"); - counter!("ring.full").increment(1); - } - break; - } - // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation - // to complete, then handle the completion queue - counter!("ring.full").increment(1); - ring.submit_and_wait(1).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("io-uring submit_and_wait".to_string())) - })?; - let completion_queue = ring.completion(); - trace!("competion queue length: {}", completion_queue.len()); - handle_completion_queue( - &self.storage, - completion_queue, - &mut saved_pinned_buffers, - )?; - } - } - let pending = saved_pinned_buffers - .iter() - .filter(|pbe| pbe.offset.is_some()) - .count(); - ring.submit_and_wait(pending).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) - })?; - - handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; - - debug_assert!( - !saved_pinned_buffers.iter().any(|pbe| pbe.offset.is_some()), - "Found entry with offset still set: {:?}", - saved_pinned_buffers.iter().find(|pbe| pbe.offset.is_some()) - ); - - self.storage - .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; - debug_assert!(ring.completion().is_empty()); - - let flush_time = flush_start.elapsed().as_millis(); - counter!("firewood.flush_nodes").increment(flush_time); - - Ok(()) - } -} - -impl NodeStore, FileBacked> { - /// Return a Committed version of this proposal, which doesn't have any modified nodes. - /// This function is used during commit. - #[must_use] - pub fn as_committed(&self) -> NodeStore { - NodeStore { - header: self.header, - kind: Committed { - deleted: self.kind.deleted.clone(), - root_hash: self.kind.root_hash.clone(), - root: self.kind.root.clone(), - }, - storage: self.storage.clone(), - } - } -} - -impl TryFrom> - for NodeStore, S> -{ - type Error = FileIoError; - - fn try_from(val: NodeStore) -> Result { - let NodeStore { - header, - kind, - storage, - } = val; - - let mut nodestore = NodeStore { - header, - kind: Arc::new(ImmutableProposal { - new: HashMap::new(), - deleted: kind.deleted.into(), - parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), - root_hash: None, - root: None, - }), - storage, - }; - - let Some(root) = kind.root else { - // This trie is now empty. - nodestore.header.root_address = None; - return Ok(nodestore); - }; - - // Hashes the trie and returns the address of the new root. - let mut new_nodes = HashMap::new(); - #[cfg(feature = "ethhash")] - let (root_addr, root_hash) = - nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes, None)?; - #[cfg(not(feature = "ethhash"))] - let (root_addr, root_hash) = - nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes)?; - - nodestore.header.root_address = Some(root_addr); - let immutable_proposal = - Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); - nodestore.kind = Arc::new(ImmutableProposal { - new: new_nodes, - deleted: immutable_proposal.deleted, - parent: immutable_proposal.parent, - root_hash: Some(root_hash.into_triehash()), - root: Some(root_addr.into()), - }); - - Ok(nodestore) - } -} - -impl NodeReader for NodeStore { - fn read_node(&self, addr: LinearAddress) -> Result { - if let Some(node) = self.kind.read_in_memory_node(addr) { - return Ok(node); - } - - self.read_node_from_disk(addr, "write") - } -} - -impl NodeReader for NodeStore { - fn read_node(&self, addr: LinearAddress) -> Result { - if let Some(node) = self.kind.read_in_memory_node(addr) { - return Ok(node); - } - self.read_node_from_disk(addr, "read") - } -} - -impl RootReader for NodeStore { - fn root_node(&self) -> Option { - self.kind.root.as_ref().map(|node| node.clone().into()) - } -} - -impl RootReader for NodeStore { - fn root_node(&self) -> Option { - // TODO: If the read_node fails, we just say there is no root; this is incorrect - self.kind.root.as_ref()?.as_shared_node(self).ok() - } -} - -impl RootReader for NodeStore, S> { - fn root_node(&self) -> Option { - // Use the MaybePersistedNode's as_shared_node method to get the root - self.kind.root.as_ref()?.as_shared_node(self).ok() - } -} - -impl HashedNodeReader for NodeStore -where - NodeStore: TrieReader, - T: Parentable, - S: ReadableStorage, -{ - fn root_address(&self) -> Option { - self.header.root_address - } - - fn root_hash(&self) -> Option { - self.kind.root_hash() - } -} - -impl HashedNodeReader for Arc -where - N: HashedNodeReader, -{ - fn root_address(&self) -> Option { - self.as_ref().root_address() - } - - fn root_hash(&self) -> Option { - self.as_ref().root_hash() - } -} - -impl NodeStore { - /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if a node cannot be deleted. - pub fn reap_deleted( - mut self, - proposal: &mut NodeStore, - ) -> Result<(), FileIoError> { - self.storage - .invalidate_cached_nodes(self.kind.deleted.iter()); - trace!("There are {} nodes to reap", self.kind.deleted.len()); - for node in take(&mut self.kind.deleted) { - proposal.delete_node(node)?; - } - Ok(()) - } -} - -// Helper functions for the checker -impl NodeStore { - pub(crate) const fn size(&self) -> u64 { - self.header.size - } - - pub(crate) fn physical_size(&self) -> Result { - self.storage.size() - } -} - -pub(crate) struct FreeListIterator<'a, S: ReadableStorage> { - storage: &'a S, - next_addr: Option, -} - -impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { - pub(crate) const fn new(storage: &'a S, next_addr: Option) -> Self { - Self { storage, next_addr } - } -} - -impl Iterator for FreeListIterator<'_, S> { - type Item = Result<(LinearAddress, AreaIndex), FileIoError>; - - fn next(&mut self) -> Option { - let next_addr = self.next_addr?; - - // read the free area, propagate any IO error if it occurs - let (free_area, stored_area_index) = match FreeArea::from_storage(self.storage, next_addr) { - Ok(free_area) => free_area, - Err(e) => { - // if the read fails, we cannot proceed with the current freelist - self.next_addr = None; - return Some(Err(e)); - } - }; - - // update the next address to the next free block - self.next_addr = free_area.next_free_block; - Some(Ok((next_addr, stored_area_index))) - } -} - -impl FusedIterator for FreeListIterator<'_, S> {} - -impl NodeStore { - // Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. - // The iterator returns a tuple of the address and the area index of the free area. - #[expect(dead_code)] // TODO: free list iterators will be used in the checker - pub(crate) fn free_list_iter( - &self, - start_area_index: AreaIndex, - ) -> impl Iterator> { - self.free_list_iter_inner(start_area_index) - .map(|item| item.map(|(addr, area_index, _)| (addr, area_index))) - } - - // pub(crate) since checker will use this to verify that the free areas are in the correct free list - // Since this is a low-level iterator, we avoid safe conversion to AreaIndex for performance - pub(crate) fn free_list_iter_inner( - &self, - start_area_index: AreaIndex, - ) -> impl Iterator> { - self.header - .free_lists - .iter() - .enumerate() - .skip(start_area_index as usize) - .flat_map(move |(free_list_id, next_addr)| { - FreeListIterator::new(self.storage.as_ref(), *next_addr).map(move |item| { - item.map(|(addr, area_index)| (addr, area_index, free_list_id as AreaIndex)) - }) - }) - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used, clippy::indexing_slicing)] -pub(crate) mod nodestore_test_utils { - use super::*; - - // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. - pub(crate) fn test_write_new_node( - nodestore: &NodeStore, - node: &Node, - offset: u64, - ) -> u64 { - let node_length = NodeStore::, FileBacked>::stored_len(node); - let area_size_index = area_size_to_index(node_length).unwrap(); - let mut stored_area_bytes = Vec::new(); - node.as_bytes(area_size_index, &mut stored_area_bytes); - nodestore - .storage - .write(offset, stored_area_bytes.as_slice()) - .unwrap(); - AREA_SIZES[area_size_index as usize] - } - - // Helper function to write a free area to the given offset. - pub(crate) fn test_write_free_area( - nodestore: &NodeStore, - next_free_block: Option, - area_size_index: AreaIndex, - offset: u64, - ) { - let area: Area = Area::Free(FreeArea { next_free_block }); - let stored_area = StoredArea { - area_size_index, - area, - }; - let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); - nodestore.storage.write(offset, &stored_area_bytes).unwrap(); - } - - // Helper function to write the NodeStoreHeader - pub(crate) fn test_write_header( - nodestore: &mut NodeStore, - size: u64, - root_addr: Option, - free_lists: FreeLists, - ) { - let mut header = NodeStoreHeader::new(); - header.size = size; - header.root_address = root_addr; - header.free_lists = free_lists; - let header_bytes = bytemuck::bytes_of(&header); - nodestore.header = header; - nodestore.storage.write(0, header_bytes).unwrap(); - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -#[expect(clippy::cast_possible_truncation)] -mod tests { - use std::array::from_fn; - - use crate::linear::memory::MemStore; - use crate::{BranchNode, LeafNode}; - use arc_swap::access::DynGuard; - use nonzero_ext::nonzero; - use test_case::test_case; - - use super::*; - - #[test] - fn test_area_size_to_index() { - // TODO: rustify using: for size in AREA_SIZES - for (i, &area_size) in AREA_SIZES.iter().enumerate() { - // area size is at top of range - assert_eq!(area_size_to_index(area_size).unwrap(), i as AreaIndex); - - if i > 0 { - // 1 less than top of range stays in range - assert_eq!(area_size_to_index(area_size - 1).unwrap(), i as AreaIndex); - } - - if i < NUM_AREA_SIZES - 1 { - // 1 more than top of range goes to next range - assert_eq!( - area_size_to_index(area_size + 1).unwrap(), - (i + 1) as AreaIndex - ); - } - } - - for i in 0..=MIN_AREA_SIZE { - assert_eq!(area_size_to_index(i).unwrap(), 0); - } - - assert!(area_size_to_index(MAX_AREA_SIZE + 1).is_err()); - } - - #[test] - fn test_reparent() { - // create an empty base revision - let memstore = MemStore::new(vec![]); - let base = NodeStore::new_empty_committed(memstore.into()) - .unwrap() - .into(); - - // create an empty r1, check that it's parent is the empty committed version - let r1 = NodeStore::new(&base).unwrap(); - let r1: Arc, _>> = Arc::new(r1.try_into().unwrap()); - let parent: DynGuard> = r1.kind.parent.load(); - assert!(matches!(**parent, NodeStoreParent::Committed(None))); - - // create an empty r2, check that it's parent is the proposed version r1 - let r2: NodeStore = NodeStore::new(&r1.clone()).unwrap(); - let r2: Arc, _>> = Arc::new(r2.try_into().unwrap()); - let parent: DynGuard> = r2.kind.parent.load(); - assert!(matches!(**parent, NodeStoreParent::Proposed(_))); - - // reparent r2 - r1.commit_reparent(&r2); - - // now check r2's parent, should match the hash of r1 (which is still None) - let parent: DynGuard> = r2.kind.parent.load(); - if let NodeStoreParent::Committed(hash) = &**parent { - assert_eq!(*hash, r1.root_hash()); - assert_eq!(*hash, None); - } else { - panic!("expected committed parent"); - } - } - - #[test] - fn test_node_store_new() { - let memstore = MemStore::new(vec![]); - let node_store = NodeStore::new_empty_proposal(memstore.into()); - - // Check the empty header is written at the start of the ReadableStorage. - let mut header = NodeStoreHeader::new(); - let mut header_stream = node_store.storage.stream_from(0).unwrap(); - let header_bytes = bytemuck::bytes_of_mut(&mut header); - header_stream.read_exact(header_bytes).unwrap(); - assert_eq!(header.version, Version::new()); - let empty_free_list: FreeLists = Default::default(); - assert_eq!(header.free_lists, empty_free_list); - } - - #[test] - fn test_version_new_is_valid() { - Version::new() - .validate() - .expect("Version::new() should always be valid"); - } - - #[test_case(*b"invalid\0\0\0\0\0\0\0\0\0")] - #[test_case(*b"avalanche 0.1.0\0")] - #[test_case(*b"firewood 0.0.1\0\0")] - fn test_invalid_version_strings(bytes: [u8; 16]) { - assert!(Version { bytes }.validate().is_err()); - } - - #[test_case(BranchNode { - partial_path: Path::from([6, 7, 8]), - value: Some(vec![9, 10, 11].into_boxed_slice()), - children: from_fn(|i| { - if i == 15 { - Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) - } else { - None - } - }), - }; "branch node with 1 child")] - #[test_case(BranchNode { - partial_path: Path::from([6, 7, 8]), - value: Some(vec![9, 10, 11].into_boxed_slice()), - children: from_fn(|_| - Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) - ), - }; "branch node with all child")] - #[test_case( - Node::Leaf(LeafNode { - partial_path: Path::from([0, 1, 2]), - value: Box::new([3, 4, 5]), - }); "leaf node")] - - fn test_serialized_len>(node: N) { - let node = node.into(); - - let computed_length = - NodeStore::, MemStore>::stored_len(&node); - - let mut serialized = Vec::new(); - node.as_bytes(0, &mut serialized); - assert_eq!(serialized.len() as u64, computed_length); - } - #[test] - #[should_panic(expected = "Node size 16777225 is too large")] - fn giant_node() { - let memstore = MemStore::new(vec![]); - let mut node_store = NodeStore::new_empty_proposal(memstore.into()); - - let huge_value = vec![0u8; *AREA_SIZES.last().unwrap() as usize]; - - let giant_leaf = Node::Leaf(LeafNode { - partial_path: Path::from([0, 1, 2]), - value: huge_value.into_boxed_slice(), - }); - - node_store.mut_root().replace(giant_leaf); - - let immutable = NodeStore::, _>::try_from(node_store).unwrap(); - println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used, clippy::indexing_slicing)] -mod test_free_list_iterator { - use super::nodestore_test_utils::*; - use super::*; - use crate::linear::memory::MemStore; - use crate::test_utils::seeded_rng; - - use rand::Rng; - use rand::seq::IteratorRandom; - - #[test] - fn free_list_iterator() { - let mut rng = seeded_rng(); - let memstore = MemStore::new(vec![]); - let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - - let area_index = rng.random_range(0..NUM_AREA_SIZES as u8); - let area_size = AREA_SIZES[area_index as usize]; - - // create a random free list scattered across the storage - let offsets = (1..100u64) - .map(|i| i * area_size) - .choose_multiple(&mut rng, 10); - for (cur, next) in offsets.iter().zip(offsets.iter().skip(1)) { - test_write_free_area( - &nodestore, - Some(LinearAddress::new(*next).unwrap()), - area_index, - *cur, - ); - } - test_write_free_area(&nodestore, None, area_index, *offsets.last().unwrap()); - - // test iterator from a random starting point - let skip = rng.random_range(0..offsets.len()); - let mut iterator = offsets.into_iter().skip(skip); - let start = iterator.next().unwrap(); - let mut free_list_iter = - FreeListIterator::new(nodestore.storage.as_ref(), LinearAddress::new(start)); - assert_eq!( - free_list_iter.next().unwrap().unwrap(), - (LinearAddress::new(start).unwrap(), area_index) - ); - - for offset in iterator { - let next_item = free_list_iter.next().unwrap().unwrap(); - assert_eq!(next_item, (LinearAddress::new(offset).unwrap(), area_index)); - } - - assert!(free_list_iter.next().is_none()); - } -} diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs new file mode 100644 index 000000000000..8a73f1b5e405 --- /dev/null +++ b/storage/src/nodestore/alloc.rs @@ -0,0 +1,715 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # Allocation Module +//! +//! This module handles memory allocation and space management for nodes in the nodestore's +//! linear storage, implementing a malloc-like free space management system. +//! +//! ### Area Sizes +//! Storage is divided into 23 predefined area sizes from 16 bytes to 16MB: +//! - Small sizes (16, 32, 64, 96, 128, 256, 512, 768, 1024 bytes) for common nodes +//! - Power-of-two larger sizes (2KB, 4KB, 8KB, ..., 16MB) for larger data +//! +//! ### Storage Format +//! Each stored area follows this layout: +//! ```text +//! [AreaIndex:1][AreaType:1][NodeData:n] +//! ``` +//! - **`AreaIndex`** - Index into `AREA_SIZES` array (1 byte) +//! - **`AreaType`** - 0xFF for free areas, otherwise node type data (1 byte) +//! - **`NodeData`** - Serialized node content + +use crate::linear::FileIoError; +use crate::logger::trace; +use bincode::{DefaultOptions, Options as _}; +use metrics::counter; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::io::{Error, ErrorKind, Read}; +use std::iter::FusedIterator; +use std::num::NonZeroU64; +use std::sync::Arc; + +use crate::node::persist::MaybePersistedNode; +use crate::node::{ByteCounter, Node}; +use crate::{CacheReadStrategy, ReadableStorage, SharedNode, TrieHash}; + +use crate::linear::WritableStorage; + +/// [`NodeStore`] divides the linear store into blocks of different sizes. +/// [`AREA_SIZES`] is every valid block size. +pub const AREA_SIZES: [u64; 23] = [ + 16, // Min block size + 32, + 64, + 96, + 128, + 256, + 512, + 768, + 1024, + 1024 << 1, + 1024 << 2, + 1024 << 3, + 1024 << 4, + 1024 << 5, + 1024 << 6, + 1024 << 7, + 1024 << 8, + 1024 << 9, + 1024 << 10, + 1024 << 11, + 1024 << 12, + 1024 << 13, + 1024 << 14, +]; + +pub fn serializer() -> impl bincode::Options { + DefaultOptions::new().with_varint_encoding() +} + +pub fn area_size_hash() -> TrieHash { + let mut hasher = Sha256::new(); + for size in AREA_SIZES { + hasher.update(size.to_ne_bytes()); + } + hasher.finalize().into() +} + +// TODO: automate this, must stay in sync with above +pub const fn index_name(index: usize) -> &'static str { + match index { + 0 => "16", + 1 => "32", + 2 => "64", + 3 => "96", + 4 => "128", + 5 => "256", + 6 => "512", + 7 => "768", + 8 => "1024", + 9 => "2048", + 10 => "4096", + 11 => "8192", + 12 => "16384", + 13 => "32768", + 14 => "65536", + 15 => "131072", + 16 => "262144", + 17 => "524288", + 18 => "1048576", + 19 => "2097152", + 20 => "4194304", + 21 => "8388608", + 22 => "16777216", + _ => "unknown", + } +} + +/// The type of an index into the [`AREA_SIZES`] array +/// This is not usize because we can store this as a single byte +pub type AreaIndex = u8; + +pub const NUM_AREA_SIZES: usize = AREA_SIZES.len(); +pub const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; +pub const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; + +#[inline] +pub fn new_area_index(n: usize) -> AreaIndex { + n.try_into().expect("Area index out of bounds") +} + +/// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. +pub fn area_size_to_index(n: u64) -> Result { + if n > MAX_AREA_SIZE { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Node size {n} is too large"), + )); + } + + if n <= MIN_AREA_SIZE { + return Ok(0); + } + + AREA_SIZES + .iter() + .position(|&size| size >= n) + .map(new_area_index) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!("Node size {n} is too large"), + ) + }) +} + +/// Objects cannot be stored at the zero address, so a [`LinearAddress`] is guaranteed not +/// to be zero. This reserved zero can be used as a [None] value for some use cases. In particular, +/// branches can use `Option` which is the same size as a [`LinearAddress`] +pub type LinearAddress = NonZeroU64; + +/// Each [`StoredArea`] contains an [Area] which is either a [Node] or a [`FreeArea`]. +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +pub enum Area { + Node(T), + Free(U) = 255, // this is magic: no node starts with a byte of 255 +} + +/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the +/// `NodeStoreHeader` is a [`StoredArea`]. +/// +/// As an overview of what this looks like stored, we get something like this: +/// - Byte 0: The index of the area size +/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf +/// - Bytes 2..n: The actual data +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +pub struct StoredArea { + /// Index in [`AREA_SIZES`] of this area's size + area_size_index: AreaIndex, + area: T, +} + +impl StoredArea { + /// Create a new `StoredArea` + pub const fn new(area_size_index: AreaIndex, area: T) -> Self { + Self { + area_size_index, + area, + } + } + + /// Destructure the `StoredArea` into its components + pub fn into_parts(self) -> (AreaIndex, T) { + (self.area_size_index, self.area) + } +} + +pub type FreeLists = [Option; NUM_AREA_SIZES]; + +/// A [`FreeArea`] is stored at the start of the area that contained a node that +/// has been freed. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct FreeArea { + next_free_block: Option, +} + +impl FreeArea { + /// Create a new `FreeArea` + pub const fn new(next_free_block: Option) -> Self { + Self { next_free_block } + } + + /// Get the next free block address + pub const fn next_free_block(self) -> Option { + self.next_free_block + } +} + +impl FreeArea { + pub fn from_storage( + storage: &S, + address: LinearAddress, + ) -> Result<(Self, AreaIndex), FileIoError> { + let free_area_addr = address.get(); + let stored_area_stream = storage.stream_from(free_area_addr)?; + let stored_area: StoredArea> = serializer() + .deserialize_from(stored_area_stream) + .map_err(|e| { + storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + free_area_addr, + Some("FreeArea::from_storage".to_string()), + ) + })?; + let (stored_area_index, area) = stored_area.into_parts(); + let Area::Free(free_area) = area else { + return Err(storage.file_io_error( + Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), + free_area_addr, + Some("FreeArea::from_storage".to_string()), + )); + }; + + Ok((free_area, stored_area_index as AreaIndex)) + } +} + +// Re-export the NodeStore types we need +use super::{Committed, ImmutableProposal, NodeStore, ReadInMemoryNode}; + +impl NodeStore { + /// Returns (index, `area_size`) for the stored area at `addr`. + /// `index` is the index of `area_size` in the array of valid block sizes. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the area cannot be read. + pub fn area_index_and_size( + &self, + addr: LinearAddress, + ) -> Result<(AreaIndex, u64), FileIoError> { + let mut area_stream = self.storage.stream_from(addr.get())?; + + let index: AreaIndex = serializer() + .deserialize_from(&mut area_stream) + .map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("deserialize".to_string()), + ) + })?; + + let size = *AREA_SIZES + .get(index as usize) + .ok_or(self.storage.file_io_error( + Error::other(format!("Invalid area size index {index}")), + addr.get(), + None, + ))?; + + Ok((index, size)) + } + + /// Read a [Node] from the provided [`LinearAddress`]. + /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. + pub fn read_node_from_disk( + &self, + addr: LinearAddress, + mode: &'static str, + ) -> Result { + if let Some(node) = self.storage.read_cached_node(addr, mode) { + return Ok(node); + } + + debug_assert!(addr.get() % 8 == 0); + + // saturating because there is no way we can be reading at u64::MAX + // and this will fail very soon afterwards + let actual_addr = addr.get().saturating_add(1); // skip the length byte + + let _span = fastrace::local::LocalSpan::enter_with_local_parent("read_and_deserialize"); + + let area_stream = self.storage.stream_from(actual_addr)?; + let node: SharedNode = Node::from_reader(area_stream) + .map_err(|e| { + self.storage + .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) + })? + .into(); + match self.storage.cache_read_strategy() { + CacheReadStrategy::All => { + self.storage.cache_node(addr, node.clone()); + } + CacheReadStrategy::BranchReads => { + if !node.is_leaf() { + self.storage.cache_node(addr, node.clone()); + } + } + CacheReadStrategy::WritesOnly => {} + } + Ok(node) + } + + /// Read a [Node] from the provided [`LinearAddress`] and size. + /// This is an uncached read, primarily used by check utilities + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. + pub fn uncached_read_node_and_size( + &self, + addr: LinearAddress, + ) -> Result<(SharedNode, u8), FileIoError> { + let mut area_stream = self.storage.stream_from(addr.get())?; + let mut size = [0u8]; + area_stream.read_exact(&mut size).map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("uncached_read_node_and_size".to_string()), + ) + })?; + self.storage.stream_from(addr.get().saturating_add(1))?; + let node: SharedNode = Node::from_reader(area_stream) + .map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("uncached_read_node_and_size".to_string()), + ) + })? + .into(); + Ok((node, size[0])) + } + + /// Get the size of an area index (used by the checker) + /// + /// # Panics + /// + /// Panics if `index` is out of bounds for the `AREA_SIZES` array. + #[must_use] + pub const fn size_from_area_index(index: AreaIndex) -> u64 { + #[expect(clippy::indexing_slicing)] + AREA_SIZES[index as usize] + } +} + +impl NodeStore, S> { + /// Attempts to allocate `n` bytes from the free lists. + /// If successful returns the address of the newly allocated area + /// and the index of the free list that was used. + /// If there are no free areas big enough for `n` bytes, returns None. + /// TODO Consider splitting the area if we return a larger area than requested. + #[expect(clippy::indexing_slicing)] + fn allocate_from_freed( + &mut self, + n: u64, + ) -> Result, FileIoError> { + // Find the smallest free list that can fit this size. + let index_wanted = area_size_to_index(n).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("allocate_from_freed".to_string())) + })?; + + if let Some((index, free_stored_area_addr)) = self + .header + .free_lists_mut() + .iter_mut() + .enumerate() + .skip(index_wanted as usize) + .find(|item| item.1.is_some()) + { + let address = free_stored_area_addr + .take() + .expect("impossible due to find earlier"); + // Get the first free block of sufficient size. + if let Some(free_head) = self.storage.free_list_cache(address) { + trace!("free_head@{address}(cached): {free_head:?} size:{index}"); + *free_stored_area_addr = free_head; + } else { + let (free_head, read_index) = + FreeArea::from_storage(self.storage.as_ref(), address)?; + debug_assert_eq!(read_index as usize, index); + + // Update the free list to point to the next free block. + *free_stored_area_addr = free_head.next_free_block; + } + + counter!("firewood.space.reused", "index" => index_name(index)) + .increment(AREA_SIZES[index]); + counter!("firewood.space.wasted", "index" => index_name(index)) + .increment(AREA_SIZES[index].saturating_sub(n)); + + // Return the address of the newly allocated block. + trace!("Allocating from free list: addr: {address:?}, size: {index}"); + return Ok(Some((address, index as AreaIndex))); + } + + trace!("No free blocks of sufficient size {index_wanted} found"); + counter!("firewood.space.from_end", "index" => index_name(index_wanted as usize)) + .increment(AREA_SIZES[index_wanted as usize]); + Ok(None) + } + + #[expect(clippy::indexing_slicing)] + fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { + let index = area_size_to_index(n).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("allocate_from_end".to_string())) + })?; + let area_size = AREA_SIZES[index as usize]; + let addr = LinearAddress::new(self.header.size()).expect("node store size can't be 0"); + self.header + .set_size(self.header.size().saturating_add(area_size)); + debug_assert!(addr.get() % 8 == 0); + trace!("Allocating from end: addr: {addr:?}, size: {index}"); + Ok((addr, index)) + } + + /// Returns the length of the serialized area for a node. + #[must_use] + pub fn stored_len(node: &Node) -> u64 { + let mut bytecounter = ByteCounter::new(); + node.as_bytes(0, &mut bytecounter); + bytecounter.count() + } + + /// Returns an address that can be used to store the given `node` and updates + /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. + /// Also returns the index of the free list the node was allocated from. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be allocated. + pub fn allocate_node( + &mut self, + node: &Node, + ) -> Result<(LinearAddress, AreaIndex), FileIoError> { + let stored_area_size = Self::stored_len(node); + + // Attempt to allocate from a free list. + // If we can't allocate from a free list, allocate past the existing + // of the ReadableStorage. + let (addr, index) = match self.allocate_from_freed(stored_area_size)? { + Some((addr, index)) => (addr, index), + None => self.allocate_from_end(stored_area_size)?, + }; + + Ok((addr, index)) + } +} + +impl NodeStore { + /// Deletes the [Node] at the given address, updating the next pointer at + /// the given addr, and changing the header of this committed nodestore to + /// have the address on the freelist + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be deleted. + #[expect(clippy::indexing_slicing)] + pub fn delete_node(&mut self, node: MaybePersistedNode) -> Result<(), FileIoError> { + let Some(addr) = node.as_linear_address() else { + return Ok(()); + }; + debug_assert!(addr.get() % 8 == 0); + + let (area_size_index, _) = self.area_index_and_size(addr)?; + trace!("Deleting node at {addr:?} of size {area_size_index}"); + counter!("firewood.delete_node", "index" => index_name(area_size_index as usize)) + .increment(1); + counter!("firewood.space.freed", "index" => index_name(area_size_index as usize)) + .increment(AREA_SIZES[area_size_index as usize]); + + // The area that contained the node is now free. + let area: Area = Area::Free(FreeArea::new( + self.header.free_lists()[area_size_index as usize], + )); + + let stored_area = StoredArea::new(area_size_index, area); + + let stored_area_bytes = serializer().serialize(&stored_area).map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("delete_node".to_string()), + ) + })?; + + self.storage.write(addr.into(), &stored_area_bytes)?; + + self.storage + .add_to_free_list_cache(addr, self.header.free_lists()[area_size_index as usize]); + + // The newly freed block is now the head of the free list. + self.header.free_lists_mut()[area_size_index as usize] = Some(addr); + + Ok(()) + } +} + +/// Iterator over free lists in the nodestore +pub struct FreeListIterator<'a, S: ReadableStorage> { + storage: &'a S, + next_addr: Option, +} + +impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { + pub const fn new(storage: &'a S, next_addr: Option) -> Self { + Self { storage, next_addr } + } +} + +impl Iterator for FreeListIterator<'_, S> { + type Item = Result<(LinearAddress, AreaIndex), FileIoError>; + + fn next(&mut self) -> Option { + let next_addr = self.next_addr?; + + // read the free area, propagate any IO error if it occurs + let (free_area, stored_area_index) = match FreeArea::from_storage(self.storage, next_addr) { + Ok(free_area) => free_area, + Err(e) => { + // if the read fails, we cannot proceed with the current freelist + self.next_addr = None; + return Some(Err(e)); + } + }; + + // update the next address to the next free block + self.next_addr = free_area.next_free_block(); + Some(Ok((next_addr, stored_area_index))) + } +} + +impl FusedIterator for FreeListIterator<'_, S> {} + +/// Extension methods for `NodeStore` to provide free list iteration capabilities +impl NodeStore { + /// Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. + /// The iterator returns a tuple of the address and the area index of the free area. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if a free area cannot be read from storage. + pub fn free_list_iter( + &self, + start_area_index: AreaIndex, + ) -> impl Iterator> { + self.free_list_iter_inner(start_area_index) + .map(|item| item.map(|(addr, area_index, _)| (addr, area_index))) + } + + /// Returns an iterator over the free lists with detailed information for verification. + /// + /// This is a low-level iterator used by the checker to verify that free areas are in the correct free list. + /// Returns tuples of (address, `area_index`, `free_list_id`) for performance optimization. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if a free area cannot be read from storage. + pub fn free_list_iter_inner( + &self, + start_area_index: AreaIndex, + ) -> impl Iterator> { + self.header + .free_lists() + .iter() + .enumerate() + .skip(start_area_index as usize) + .flat_map(move |(free_list_id, next_addr)| { + FreeListIterator::new(self.storage.as_ref(), *next_addr).map(move |item| { + item.map(|(addr, area_index)| (addr, area_index, free_list_id as AreaIndex)) + }) + }) + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used, clippy::indexing_slicing)] +pub mod test_utils { + use super::super::{Committed, ImmutableProposal, NodeStore, NodeStoreHeader}; + use super::*; + use crate::FileBacked; + use crate::node::Node; + use bincode::Options; + + // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. + pub fn test_write_new_node( + nodestore: &NodeStore, + node: &Node, + offset: u64, + ) -> u64 { + let node_length = NodeStore::, FileBacked>::stored_len(node); + let area_size_index = area_size_to_index(node_length).unwrap(); + let mut stored_area_bytes = Vec::new(); + node.as_bytes(area_size_index, &mut stored_area_bytes); + nodestore + .storage + .write(offset, stored_area_bytes.as_slice()) + .unwrap(); + AREA_SIZES[area_size_index as usize] + } + + // Helper function to write a free area to the given offset. + pub fn test_write_free_area( + nodestore: &NodeStore, + next_free_block: Option, + area_size_index: AreaIndex, + offset: u64, + ) { + let area: Area = Area::Free(FreeArea::new(next_free_block)); + let stored_area = StoredArea::new(area_size_index, area); + let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); + nodestore.storage.write(offset, &stored_area_bytes).unwrap(); + } + + // Helper function to write the NodeStoreHeader + pub fn test_write_header( + nodestore: &mut NodeStore, + size: u64, + root_addr: Option, + free_lists: FreeLists, + ) { + let mut header = NodeStoreHeader::new(); + header.set_size(size); + header.set_root_address(root_addr); + *header.free_lists_mut() = free_lists; + let header_bytes = bytemuck::bytes_of(&header); + nodestore.header = header; + nodestore.storage.write(0, header_bytes).unwrap(); + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used, clippy::indexing_slicing)] +mod test_free_list_iterator { + use super::*; + use crate::linear::memory::MemStore; + use crate::test_utils::seeded_rng; + + use rand::Rng; + use rand::seq::IteratorRandom; + + // Simple helper function for this test - just writes to storage directly + fn write_free_area_to_storage( + storage: &S, + next_free_block: Option, + area_size_index: AreaIndex, + offset: u64, + ) { + let area: Area = Area::Free(FreeArea::new(next_free_block)); + let stored_area = StoredArea::new(area_size_index, area); + let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); + storage.write(offset, &stored_area_bytes).unwrap(); + } + + #[test] + fn free_list_iterator() { + let mut rng = seeded_rng(); + let memstore = MemStore::new(vec![]); + let storage = Arc::new(memstore); + + let area_index = rng.random_range(0..NUM_AREA_SIZES as u8); + let area_size = AREA_SIZES[area_index as usize]; + + // create a random free list scattered across the storage + let offsets = (1..100u64) + .map(|i| i * area_size) + .choose_multiple(&mut rng, 10); + for (cur, next) in offsets.iter().zip(offsets.iter().skip(1)) { + write_free_area_to_storage( + storage.as_ref(), + Some(LinearAddress::new(*next).unwrap()), + area_index, + *cur, + ); + } + write_free_area_to_storage(storage.as_ref(), None, area_index, *offsets.last().unwrap()); + + // test iterator from a random starting point + let skip = rng.random_range(0..offsets.len()); + let mut iterator = offsets.into_iter().skip(skip); + let start = iterator.next().unwrap(); + let mut free_list_iter = FreeListIterator::new(storage.as_ref(), LinearAddress::new(start)); + assert_eq!( + free_list_iter.next().unwrap().unwrap(), + (LinearAddress::new(start).unwrap(), area_index) + ); + + for offset in iterator { + let next_item = free_list_iter.next().unwrap().unwrap(); + assert_eq!(next_item, (LinearAddress::new(offset).unwrap(), area_index)); + } + + assert!(free_list_iter.next().is_none()); + } +} diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs new file mode 100644 index 000000000000..353d8832434c --- /dev/null +++ b/storage/src/nodestore/hash.rs @@ -0,0 +1,204 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # Hash Module +//! +//! This module contains all node hashing functionality for the nodestore, including +//! specialized support for Ethereum-compatible hash processing. + +use crate::hashednode::hash_node; +use crate::linear::FileIoError; +use crate::logger::trace; +use crate::node::Node; +use crate::{Child, HashType, Path, ReadableStorage, SharedNode}; + +#[cfg(feature = "ethhash")] +use super::NodeReader; +#[cfg(feature = "ethhash")] +use crate::node::persist::MaybePersistedNode; +#[cfg(feature = "ethhash")] +use std::ops::Deref; + +use std::collections::HashMap; +use std::sync::Arc; + +use super::alloc::LinearAddress; +use super::{ImmutableProposal, NodeStore}; + +/// Classified children for ethereum hash processing +#[cfg(feature = "ethhash")] +pub(super) struct ClassifiedChildren<'a> { + pub(super) unhashed: Vec<(usize, Node)>, + pub(super) hashed: Vec<(usize, (MaybePersistedNode, &'a mut HashType))>, +} + +impl NodeStore, S> { + /// Helper function to classify children for ethereum hash processing + /// We have some special cases based on the number of children + /// and whether they are hashed or unhashed, so we need to classify them. + #[cfg(feature = "ethhash")] + pub(super) fn ethhash_classify_children<'a>( + &self, + children: &'a mut [Option; crate::node::BranchNode::MAX_CHILDREN], + ) -> ClassifiedChildren<'a> { + children.iter_mut().enumerate().fold( + ClassifiedChildren { + unhashed: Vec::new(), + hashed: Vec::new(), + }, + |mut acc, (idx, child)| { + match child { + None => {} + Some(Child::AddressWithHash(a, h)) => { + // Convert address to MaybePersistedNode + let maybe_persisted_node = MaybePersistedNode::from(*a); + acc.hashed.push((idx, (maybe_persisted_node, h))); + } + Some(Child::Node(node)) => acc.unhashed.push((idx, node.clone())), + Some(Child::MaybePersisted(maybe_persisted, h)) => { + // For MaybePersisted, we need to get the address if it's persisted + if let Some(addr) = maybe_persisted.as_linear_address() { + let maybe_persisted_node = MaybePersistedNode::from(addr); + acc.hashed.push((idx, (maybe_persisted_node, h))); + } else { + // If not persisted, we need to get the node to hash it + if let Ok(node) = maybe_persisted.as_shared_node(self) { + acc.unhashed.push((idx, node.deref().clone())); + } + } + } + } + acc + }, + ) + } + + /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. + /// Returns the hashed node and its hash. + pub(super) fn hash_helper( + &mut self, + mut node: Node, + path_prefix: &mut Path, + new_nodes: &mut HashMap, + #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, + ) -> Result<(LinearAddress, HashType), FileIoError> { + // If this is a branch, find all unhashed children and recursively call hash_helper on them. + trace!("hashing {node:?} at {path_prefix:?}"); + if let Node::Branch(ref mut b) = node { + // special case code for ethereum hashes at the account level + #[cfg(feature = "ethhash")] + let make_fake_root = if path_prefix.0.len().saturating_add(b.partial_path.0.len()) == 64 + { + // looks like we're at an account branch + // tally up how many hashes we need to deal with + let ClassifiedChildren { + unhashed, + mut hashed, + } = self.ethhash_classify_children(&mut b.children); + trace!("hashed {hashed:?} unhashed {unhashed:?}"); + if hashed.len() == 1 { + // we were left with one hashed node that must be rehashed + let invalidated_node = hashed.first_mut().expect("hashed is not empty"); + // Extract the address from the MaybePersistedNode + let addr = invalidated_node + .1 + .0 + .as_linear_address() + .expect("hashed node should be persisted"); + let mut hashable_node = self.read_node(addr)?.deref().clone(); + let original_length = path_prefix.len(); + path_prefix.0.extend(b.partial_path.0.iter().copied()); + if unhashed.is_empty() { + hashable_node.update_partial_path(Path::from_nibbles_iterator( + std::iter::once(invalidated_node.0 as u8) + .chain(hashable_node.partial_path().0.iter().copied()), + )); + } else { + path_prefix.0.push(invalidated_node.0 as u8); + } + let hash = hash_node(&hashable_node, path_prefix); + path_prefix.0.truncate(original_length); + *invalidated_node.1.1 = hash; + } + // handle the single-child case for an account special below + if hashed.is_empty() && unhashed.len() == 1 { + Some(unhashed.last().expect("only one").0 as u8) + } else { + None + } + } else { + // not a single child + None + }; + + // branch children cases: + // 1. 1 child, already hashed + // 2. >1 child, already hashed, + // 3. 1 hashed child, 1 unhashed child + // 4. 0 hashed, 1 unhashed <-- handle child special + // 5. 1 hashed, >0 unhashed <-- rehash case + // 6. everything already hashed + + for (nibble, child) in b.children.iter_mut().enumerate() { + // If this is empty or already hashed, we're done + // Empty matches None, and non-Node types match Some(None) here, so we want + // Some(Some(node)) + let Some(Some(child_node)) = child.as_mut().map(|child| child.as_mut_node()) else { + continue; + }; + + // remove the child from the children array, we will replace it with a hashed variant + let child_node = std::mem::take(child_node); + + // Hash this child and update + // we extend and truncate path_prefix to reduce memory allocations + let original_length = path_prefix.len(); + path_prefix.0.extend(b.partial_path.0.iter().copied()); + #[cfg(feature = "ethhash")] + if make_fake_root.is_none() { + // we don't push the nibble there is only one unhashed child and + // we're on an account + path_prefix.0.push(nibble as u8); + } + #[cfg(not(feature = "ethhash"))] + path_prefix.0.push(nibble as u8); + + #[cfg(feature = "ethhash")] + let (child_addr, child_hash) = + self.hash_helper(child_node, path_prefix, new_nodes, make_fake_root)?; + #[cfg(not(feature = "ethhash"))] + let (child_addr, child_hash) = + self.hash_helper(child_node, path_prefix, new_nodes)?; + + *child = Some(Child::AddressWithHash(child_addr, child_hash)); + path_prefix.0.truncate(original_length); + } + } + // At this point, we either have a leaf or a branch with all children hashed. + // if the encoded child hash <32 bytes then we use that RLP + + #[cfg(feature = "ethhash")] + // if we have a child that is the only child of an account branch, we will hash this child as if it + // is a root node. This means we have to take the nibble from the parent and prefix it to the partial path + let hash = if let Some(nibble) = fake_root_extra_nibble { + let mut fake_root = node.clone(); + trace!("old node: {fake_root:?}"); + fake_root.update_partial_path(Path::from_nibbles_iterator( + std::iter::once(nibble).chain(fake_root.partial_path().0.iter().copied()), + )); + trace!("new node: {fake_root:?}"); + hash_node(&fake_root, path_prefix) + } else { + hash_node(&node, path_prefix) + }; + + #[cfg(not(feature = "ethhash"))] + let hash = hash_node(&node, path_prefix); + + let (addr, size) = self.allocate_node(&node)?; + + new_nodes.insert(addr, (size, node.into())); + + Ok((addr, hash)) + } +} diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs new file mode 100644 index 000000000000..0b07155405af --- /dev/null +++ b/storage/src/nodestore/header.rs @@ -0,0 +1,336 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # Header Module +//! +//! This module defines the nodestore header structure and validation logic for ensuring +//! database compatibility across different versions and configurations. +//! +//! ## Header Structure +//! +//! The `NodeStoreHeader` is stored at the beginning of every nodestore file and contains: +//! +//! - **Version String** - Human-readable firewood version (e.g., "firewood 0.1.0") +//! - **Endianness Test** - Detects byte order mismatches between platforms +//! - **Root Address** - Points to the merkle trie root node (if any) +//! - **Storage Size** - Total allocated storage space +//! - **Free Lists** - Array of free space linked list heads for each area size +//! +//! ## Storage Layout +//! +//! The header occupies the first 2048 bytes of storage: +//! - Fixed size for alignment with disk block boundaries +//! - Zero-padded to full size for consistent layout +//! - Uses C-compatible representation for cross-language access +//! + +use bytemuck_derive::{AnyBitPattern, NoUninit}; +use serde::{Deserialize, Serialize}; +use std::io::{Error, ErrorKind}; + +use super::alloc::{FreeLists, LinearAddress, area_size_hash}; +use crate::logger::{debug, trace}; + +/// Can be used by filesystem tooling such as "file" to identify +/// the version of firewood used to create this `NodeStore` file. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, NoUninit, AnyBitPattern)] +#[repr(transparent)] +pub struct Version { + bytes: [u8; 16], +} + +impl Version { + const SIZE: usize = size_of::(); + + /// Version >= 0.0.4 + /// + /// Increase as needed to set the minimum required version of `firewood-storage` for + /// compatibility checks. + /// + /// We may want to add migrations if we need to add a breaking change. + const BASE_VERSION: semver::Comparator = semver::Comparator { + op: semver::Op::GreaterEq, + major: 0, + minor: Some(0), + patch: Some(4), + pre: semver::Prerelease::EMPTY, + }; + + /// Validates that the version identifier is valid and compatible with the current + /// build of firewood. + /// + /// # Errors + /// + /// - If the token contains invalid utf-8 bytes (nul is allowed). + /// - If the token does not start with "firewood ". + /// - If the version is not parsable by [`semver::Version`]. + /// - If the version is not compatible with the current build of firewood. + /// - Currently, the minimum required version is 0.0.4. + pub fn validate(&self) -> Result<(), Error> { + let version = std::str::from_utf8(&self.bytes).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid database version: invalid utf-8: {e} (original: [{:032x}])", + u128::from_be_bytes(self.bytes) + ), + ) + })?; + + // strip trailling nuls as they're only for padding + let version = version.trim_end_matches('\0'); + + // strip magic prefix or error + let version = version.strip_prefix("firewood ").ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid database version: does not start with magic 'firewood ': {version}", + ), + ) + })?; + + // Version strings from CARGO_PKG_VERSION are guaranteed to be parsable by + // semver (cargo uses the same library). + let version = semver::Version::parse(version).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid version string: unable to parse `{version}` as a semver string: {e}" + ), + ) + })?; + + // verify base compatibility version + if !Self::BASE_VERSION.matches(&version) { + return Err(Error::new( + ErrorKind::InvalidData, + format!( + "Database was created with firewood version {version}; however, this build of firewood requires version {}", + Self::BASE_VERSION, + ), + )); + } + + debug!( + "Database version is valid: {version} {}", + Self::BASE_VERSION + ); + Ok(()) + } + + /// Construct a [`Version`] instance for the current build of firewood. + pub fn new() -> Self { + // Note that with this magic token of 9 bytes, we can store a version string of + // up to 7 bytes. If we always include the major, minor, and patch versions, + // then no more than two of three can be 2 digits long. + const VERSION_STR: &str = concat!("firewood ", env!("CARGO_PKG_VERSION")); + const { + assert!( + VERSION_STR.len() <= Version::SIZE, + concat!( + "Database version string `firewood ", + env!("CARGO_PKG_VERSION"), + "` is too long for the Version struct! Update Cargo.toml or modify this code.", + ), + ); + } + + // pad with nul bytes + let mut bytes = [0u8; Version::SIZE]; + bytes + .get_mut(..VERSION_STR.len()) + .expect("must fit") + .copy_from_slice(VERSION_STR.as_bytes()); + + Self { bytes } + } +} + +/// Persisted metadata for a `NodeStore`. +/// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. +#[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] +#[repr(C)] +pub struct NodeStoreHeader { + /// Identifies the version of firewood used to create this `NodeStore`. + version: Version, + /// always "1"; verifies endianness + endian_test: u64, + size: u64, + /// Element i is the pointer to the first free block of size `BLOCK_SIZES[i]`. + free_lists: FreeLists, + root_address: Option, + /// The hash of the area sizes used in this database to prevent someone from changing the + /// area sizes and trying to read old databases with the wrong area sizes. + area_size_hash: [u8; 32], + /// Whether ethhash was enabled when this database was created. + ethhash: u64, +} + +impl NodeStoreHeader { + /// The first SIZE bytes of the `ReadableStorage` are reserved for the + /// [`NodeStoreHeader`]. + /// We also want it aligned to a disk block + pub const SIZE: u64 = 2048; + + /// Number of extra bytes to write on the first creation of the `NodeStoreHeader` + /// (zero-padded) + /// also a compile time check to prevent setting SIZE too small + pub const EXTRA_BYTES: usize = Self::SIZE as usize - std::mem::size_of::(); + + pub fn new() -> Self { + Self { + // The store just contains the header at this point + size: Self::SIZE, + endian_test: 1, + root_address: None, + version: Version::new(), + free_lists: Default::default(), + area_size_hash: area_size_hash() + .as_slice() + .try_into() + .expect("sizes should match"), + #[cfg(feature = "ethhash")] + ethhash: 1, + #[cfg(not(feature = "ethhash"))] + ethhash: 0, + } + } + + pub fn validate(&self) -> Result<(), Error> { + trace!("Checking version..."); + self.version.validate()?; + + trace!("Checking endianness..."); + self.validate_endian_test()?; + + trace!("Checking area size hash..."); + self.validate_area_size_hash()?; + + trace!("Checking if db ethhash flag matches build feature..."); + self.validate_ethhash()?; + + Ok(()) + } + + /// Get the size of the nodestore + pub const fn size(&self) -> u64 { + self.size + } + + /// Set the size of the nodestore + pub const fn set_size(&mut self, size: u64) { + self.size = size; + } + + /// Get the free lists + pub const fn free_lists(&self) -> &FreeLists { + &self.free_lists + } + + /// Get mutable access to the free lists + pub const fn free_lists_mut(&mut self) -> &mut FreeLists { + &mut self.free_lists + } + + /// Get the root address + pub const fn root_address(&self) -> Option { + self.root_address + } + + /// Set the root address + pub const fn set_root_address(&mut self, root_address: Option) { + self.root_address = root_address; + } + + /// Get the offset of the `free_lists` field for use with `offset_of`! + pub const fn free_lists_offset() -> u64 { + std::mem::offset_of!(NodeStoreHeader, free_lists) as u64 + } + + fn validate_endian_test(&self) -> Result<(), Error> { + if self.endian_test == 1 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in endianness", + )) + } + } + + fn validate_area_size_hash(&self) -> Result<(), Error> { + if self.area_size_hash == area_size_hash().as_slice() { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened due to difference in area size hash", + )) + } + } + + #[cfg(not(feature = "ethhash"))] + fn validate_ethhash(&self) -> Result<(), Error> { + if self.ethhash == 0 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created with ethhash enabled", + )) + } + } + + #[cfg(feature = "ethhash")] + fn validate_ethhash(&self) -> Result<(), Error> { + if self.ethhash == 1 { + Ok(()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Database cannot be opened as it was created without ethhash enabled", + )) + } + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used)] +mod tests { + use super::*; + use crate::linear::ReadableStorage; + use crate::linear::memory::MemStore; + use crate::nodestore::NodeStore; + use std::io::Read; + use test_case::test_case; + + #[test] + fn test_version_new_is_valid() { + Version::new() + .validate() + .expect("Version::new() should always be valid"); + } + + #[test_case(*b"invalid\0\0\0\0\0\0\0\0\0")] + #[test_case(*b"avalanche 0.1.0\0")] + #[test_case(*b"firewood 0.0.1\0\0")] + fn test_invalid_version_strings(bytes: [u8; 16]) { + assert!(Version { bytes }.validate().is_err()); + } + + #[test] + fn test_node_store_new() { + let memstore = MemStore::new(vec![]); + let node_store = NodeStore::new_empty_proposal(memstore.into()); + + // Check the empty header is written at the start of the ReadableStorage. + let mut header = NodeStoreHeader::new(); + let mut header_stream = node_store.storage.stream_from(0).unwrap(); + let header_bytes = bytemuck::bytes_of_mut(&mut header); + header_stream.read_exact(header_bytes).unwrap(); + assert_eq!(header.version, Version::new()); + let empty_free_list: super::FreeLists = Default::default(); + assert_eq!(*header.free_lists(), empty_free_list); + } +} diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs new file mode 100644 index 000000000000..4e297400545d --- /dev/null +++ b/storage/src/nodestore/mod.rs @@ -0,0 +1,829 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # `NodeStore` Module +//! +//! The main module for nodestore functionality, containing core types, traits, and operations +//! for managing merkle trie data in Firewood. +//! +//! ## Module Structure +//! +//! The nodestore module is organized into several specialized submodules: +//! +//! - [`alloc`] - Memory allocation and area management for nodes in the linear store +//! - [`hash`] - Node hashing functionality, including specialized ethereum hash processing +//! - [`header`] - `NodeStore` header structure and validation logic +//! - [`persist`] - Persistence operations for writing nodes and metadata to storage +//! +//! ## Types +//! +//! This module defines the primary types for nodestore operations: +//! +//! - [`NodeStore`] - The main nodestore container parameterized by state type and storage +//! +//! `T` is one of the following state types: +//! - [`Committed`] - For a committed revision with no in-memory changes +//! - [`MutableProposal`] - For a proposal being actively modified with in-memory nodes +//! - [`ImmutableProposal`] - For a proposal that has been hashed and assigned addresses +//! +//! The nodestore follows a lifecycle pattern: +//! ```text +//! Committed -> MutableProposal -> ImmutableProposal -> Committed +//! ``` +//! +//! ## Traits +//! +//! - **`NodeReader`** - Interface for reading nodes by address +//! - **`RootReader`** - Interface for accessing the root node +//! - **`HashedNodeReader`** - Interface for immutable merkle trie access +//! - **`Parentable`** - Trait for nodestores that can have children +//! - **`ReadInMemoryNode`** - Interface for accessing in-memory nodes +//! + +pub(crate) mod alloc; +pub(crate) mod hash; +pub(crate) mod header; +pub(crate) mod persist; + +use crate::linear::FileIoError; +use crate::logger::trace; +use arc_swap::ArcSwap; +use arc_swap::access::DynAccess; +use smallvec::SmallVec; +use std::collections::HashMap; +use std::fmt::Debug; + +// Re-export types from alloc module +pub use alloc::{AreaIndex, LinearAddress}; + +// Re-export types from header module +pub use header::NodeStoreHeader; + +/// The [`NodeStore`] handles the serialization of nodes and +/// free space management of nodes in the page store. It lays out the format +/// of the [`PageStore`]. More specifically, it places a [`FileIdentifyingMagic`] +/// and a [`FreeSpaceHeader`] at the beginning +/// +/// Nodestores represent a revision of the trie. There are three types of nodestores: +/// - Committed: A committed revision of the trie. It has no in-memory changes. +/// - `MutableProposal`: A proposal that is still being modified. It has some nodes in memory. +/// - `ImmutableProposal`: A proposal that has been hashed and assigned addresses. It has no in-memory changes. +/// +/// The general lifecycle of nodestores is as follows: +/// ```mermaid +/// flowchart TD +/// subgraph subgraph["Committed Revisions"] +/// L("Latest Nodestore<Committed, S>") --- |...|O("Oldest NodeStore<Committed, S>") +/// end +/// O --> E("Expire") +/// L --> |start propose|M("NodeStore<ProposedMutable, S>") +/// M --> |finish propose + hash|I("NodeStore<ProposedImmutable, S>") +/// I --> |commit|N("New commit NodeStore<Committed, S>") +/// style E color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF +/// ``` +use std::mem::take; +use std::ops::Deref; +use std::sync::Arc; + +use crate::hashednode::hash_node; +use crate::node::Node; +use crate::node::persist::MaybePersistedNode; +use crate::{FileBacked, Path, ReadableStorage, SharedNode, TrieHash}; + +use super::linear::WritableStorage; + +impl NodeStore { + /// Open an existing [`NodeStore`] + /// Assumes the header is written in the [`ReadableStorage`]. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be read or validated. + pub fn open(storage: Arc) -> Result { + let mut stream = storage.stream_from(0)?; + let mut header = NodeStoreHeader::new(); + let header_bytes = bytemuck::bytes_of_mut(&mut header); + stream + .read_exact(header_bytes) + .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; + + drop(stream); + + header + .validate() + .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; + + let mut nodestore = Self { + header, + kind: Committed { + deleted: Box::default(), + root_hash: None, + root: header.root_address().map(Into::into), + }, + storage, + }; + + if let Some(root_address) = nodestore.header.root_address() { + let node = nodestore.read_node_from_disk(root_address, "open"); + let root_hash = node.map(|n| hash_node(&n, &Path(SmallVec::default())))?; + nodestore.kind.root_hash = Some(root_hash.into_triehash()); + } + + Ok(nodestore) + } + + /// Create a new, empty, Committed [`NodeStore`] and clobber + /// the underlying store with an empty freelist and no root node + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the storage cannot be accessed. + pub fn new_empty_committed(storage: Arc) -> Result { + let header = NodeStoreHeader::new(); + + Ok(Self { + header, + storage, + kind: Committed { + deleted: Box::default(), + root_hash: None, + root: None, + }, + }) + } +} + +/// Some nodestore kinds implement Parentable. +/// +/// This means that the nodestore can have children. +/// Only [`ImmutableProposal`] and [Committed] implement this trait. +/// [`MutableProposal`] does not implement this trait because it is not a valid parent. +/// TODO: Maybe this can be renamed to `ImmutableNodestore` +pub trait Parentable { + /// Returns the parent of this nodestore. + fn as_nodestore_parent(&self) -> NodeStoreParent; + /// Returns the root hash of this nodestore. This works because all parentable nodestores have a hash + fn root_hash(&self) -> Option; + /// Returns the root node + fn root(&self) -> Option; +} + +impl Parentable for Arc { + fn as_nodestore_parent(&self) -> NodeStoreParent { + NodeStoreParent::Proposed(Arc::clone(self)) + } + fn root_hash(&self) -> Option { + self.root_hash.clone() + } + fn root(&self) -> Option { + self.root.clone() + } +} + +impl NodeStore, S> { + /// When an immutable proposal commits, we need to reparent any proposal that + /// has the committed proposal as it's parent + pub fn commit_reparent(&self, other: &Arc, S>>) { + match *other.kind.parent.load() { + NodeStoreParent::Proposed(ref parent) => { + if Arc::ptr_eq(&self.kind, parent) { + other + .kind + .parent + .store(NodeStoreParent::Committed(self.kind.root_hash()).into()); + } + } + NodeStoreParent::Committed(_) => {} + } + } +} + +impl Parentable for Committed { + fn as_nodestore_parent(&self) -> NodeStoreParent { + NodeStoreParent::Committed(self.root_hash.clone()) + } + fn root_hash(&self) -> Option { + self.root_hash.clone() + } + fn root(&self) -> Option { + self.root.clone() + } +} + +impl NodeStore { + /// Create a new `MutableProposal` [`NodeStore`] from a parent [`NodeStore`] + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the parent root cannot be read. + pub fn new( + parent: &Arc>, + ) -> Result { + let mut deleted = Vec::default(); + let root = if let Some(ref root) = parent.kind.root() { + deleted.push(root.clone()); + let root = root.as_shared_node(parent)?.deref().clone(); + Some(root) + } else { + None + }; + let kind = MutableProposal { + root, + deleted, + parent: parent.kind.as_nodestore_parent(), + }; + Ok(NodeStore { + header: parent.header, + kind, + storage: parent.storage.clone(), + }) + } + + /// Marks the node at `addr` as deleted in this proposal. + pub fn delete_node(&mut self, node: MaybePersistedNode) { + trace!("Pending delete at {node:?}"); + self.kind.deleted.push(node); + } + + /// Reads a node for update, marking it as deleted in this proposal. + /// We get an arc from cache (reading it from disk if necessary) then + /// copy/clone the node and return it. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. + pub fn read_for_update(&mut self, node: MaybePersistedNode) -> Result { + let arc_wrapped_node = node.as_shared_node(self)?; + self.delete_node(node); + Ok((*arc_wrapped_node).clone()) + } + + /// Returns the root of this proposal. + pub const fn mut_root(&mut self) -> &mut Option { + &mut self.kind.root + } +} + +impl NodeStore { + /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. + /// This is used during testing and during the creation of an in-memory merkle for proofs + /// + /// # Panics + /// + /// Panics if the header cannot be written. + pub fn new_empty_proposal(storage: Arc) -> Self { + let header = NodeStoreHeader::new(); + let header_bytes = bytemuck::bytes_of(&header); + storage + .write(0, header_bytes) + .expect("failed to write header"); + NodeStore { + header, + kind: MutableProposal { + root: None, + deleted: Vec::default(), + parent: NodeStoreParent::Committed(None), + }, + storage, + } + } +} + +/// Reads from an immutable (i.e. already hashed) merkle trie. +pub trait HashedNodeReader: TrieReader { + /// Gets the address of the root node of an immutable merkle trie. + fn root_address(&self) -> Option; + + /// Gets the hash of the root node of an immutable merkle trie. + fn root_hash(&self) -> Option; +} + +/// Reads nodes and the root address from a merkle trie. +pub trait TrieReader: NodeReader + RootReader {} +impl TrieReader for T where T: NodeReader + RootReader {} + +/// Reads nodes from a merkle trie. +pub trait NodeReader { + /// Returns the node at `addr`. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. + fn read_node(&self, addr: LinearAddress) -> Result; +} + +impl NodeReader for T +where + T: Deref, + T::Target: NodeReader, +{ + fn read_node(&self, addr: LinearAddress) -> Result { + self.deref().read_node(addr) + } +} + +impl RootReader for T +where + T: Deref, + T::Target: RootReader, +{ + fn root_node(&self) -> Option { + self.deref().root_node() + } +} + +/// Reads the root of a merkle trie. +pub trait RootReader { + /// Returns the root of the trie. + fn root_node(&self) -> Option; +} + +/// A committed revision of a merkle trie. +#[derive(Clone, Debug)] +pub struct Committed { + deleted: Box<[MaybePersistedNode]>, + root_hash: Option, + root: Option, +} + +impl ReadInMemoryNode for Committed { + // A committed revision has no in-memory changes. All its nodes are in storage. + fn read_in_memory_node(&self, _addr: LinearAddress) -> Option { + None + } +} + +#[derive(Clone, Debug)] +pub enum NodeStoreParent { + Proposed(Arc), + Committed(Option), +} + +impl PartialEq for NodeStoreParent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (NodeStoreParent::Proposed(a), NodeStoreParent::Proposed(b)) => Arc::ptr_eq(a, b), + (NodeStoreParent::Committed(a), NodeStoreParent::Committed(b)) => a == b, + _ => false, + } + } +} + +impl Eq for NodeStoreParent {} + +#[derive(Debug)] +/// Contains state for a proposed revision of the trie. +pub struct ImmutableProposal { + /// Address --> Node for nodes created in this proposal. + new: HashMap, + /// Nodes that have been deleted in this proposal. + deleted: Box<[MaybePersistedNode]>, + /// The parent of this proposal. + parent: Arc>, + /// The hash of the root node for this proposal + root_hash: Option, + /// The root node, either in memory or on disk + root: Option, +} + +impl ImmutableProposal { + /// Returns true if the parent of this proposal is committed and has the given hash. + #[must_use] + fn parent_hash_is(&self, hash: Option) -> bool { + match > as arc_swap::access::DynAccess>>::load( + &self.parent, + ) + .as_ref() + { + NodeStoreParent::Committed(root_hash) => *root_hash == hash, + NodeStoreParent::Proposed(_) => false, + } + } +} + +impl ReadInMemoryNode for ImmutableProposal { + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { + // Check if the node being requested was created in this proposal. + if let Some((_, node)) = self.new.get(&addr) { + return Some(node.clone()); + } + + // It wasn't. Try our parent, and its parent, and so on until we find it or find + // a committed revision. + match *self.parent.load() { + NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), + NodeStoreParent::Committed(_) => None, + } + } +} + +/// Proposed [`NodeStore`] types keep some nodes in memory. These nodes are new nodes that were allocated from +/// the free list, but are not yet on disk. This trait checks to see if a node is in memory and returns it if +/// it's there. If it's not there, it will be read from disk. +/// +/// This trait does not know anything about the underlying storage, so it just returns None if the node isn't in memory. +pub trait ReadInMemoryNode { + /// Returns the node at `addr` if it is in memory. + /// Returns None if it isn't. + fn read_in_memory_node(&self, addr: LinearAddress) -> Option; +} + +impl ReadInMemoryNode for T +where + T: Deref, + T::Target: ReadInMemoryNode, +{ + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { + self.deref().read_in_memory_node(addr) + } +} + +/// Contains the state of a revision of a merkle trie. +/// +/// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. +/// The second generic parameter is the type of the storage used, either +/// in-memory or on-disk. +/// +/// The lifecycle of a [`NodeStore`] is as follows: +/// 1. Create a new, empty, [Committed] [`NodeStore`] using [`NodeStore::new_empty_committed`]. +/// 2. Create a [`NodeStore`] from disk using [`NodeStore::open`]. +/// 3. Create a new mutable proposal from either a [Committed] or [`ImmutableProposal`] [`NodeStore`] using [`NodeStore::new`]. +/// 4. Convert a mutable proposal to an immutable proposal using [`std::convert::TryInto`], which hashes the nodes and assigns addresses +/// 5. Convert an immutable proposal to a committed revision using [`std::convert::TryInto`], which writes the nodes to disk. + +#[derive(Debug)] +pub struct NodeStore { + // Metadata for this revision. + header: NodeStoreHeader, + /// This is one of [Committed], [`ImmutableProposal`], or [`MutableProposal`]. + kind: T, + /// Persisted storage to read nodes from. + storage: Arc, +} + +/// Contains the state of a proposal that is still being modified. +#[derive(Debug)] +pub struct MutableProposal { + /// The root of the trie in this proposal. + root: Option, + /// Nodes that have been deleted in this proposal. + deleted: Vec, + parent: NodeStoreParent, +} + +impl ReadInMemoryNode for NodeStoreParent { + /// Returns the node at `addr` if it is in memory from a parent proposal. + /// If the base revision is committed, there are no in-memory nodes, so we return None + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { + match self { + NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), + NodeStoreParent::Committed(_) => None, + } + } +} + +impl ReadInMemoryNode for MutableProposal { + /// [`MutableProposal`] types do not have any nodes in memory, but their parent proposal might, so we check there. + /// This might be recursive: a grandparent might also have that node in memory. + fn read_in_memory_node(&self, addr: LinearAddress) -> Option { + self.parent.read_in_memory_node(addr) + } +} + +impl, S: ReadableStorage> From> + for NodeStore +{ + fn from(val: NodeStore) -> Self { + NodeStore { + header: val.header, + kind: MutableProposal { + root: None, + deleted: Vec::default(), + parent: val.kind.into(), + }, + storage: val.storage, + } + } +} + +/// Commit a proposal to a new revision of the trie +impl From> for NodeStore { + fn from(val: NodeStore) -> Self { + NodeStore { + header: val.header, + kind: Committed { + deleted: val.kind.deleted, + root_hash: val.kind.root_hash, + root: val.kind.root, + }, + storage: val.storage, + } + } +} + +impl NodeStore, S> { + /// Re-export the `parent_hash_is` function of [`ImmutableProposal`]. + #[must_use] + pub fn parent_hash_is(&self, hash: Option) -> bool { + self.kind.parent_hash_is(hash) + } +} + +impl NodeStore, FileBacked> { + /// Return a Committed version of this proposal, which doesn't have any modified nodes. + /// This function is used during commit. + #[must_use] + pub fn as_committed(&self) -> NodeStore { + NodeStore { + header: self.header, + kind: Committed { + deleted: self.kind.deleted.clone(), + root_hash: self.kind.root_hash.clone(), + root: self.kind.root.clone(), + }, + storage: self.storage.clone(), + } + } +} + +impl TryFrom> + for NodeStore, S> +{ + type Error = FileIoError; + + fn try_from(val: NodeStore) -> Result { + let NodeStore { + header, + kind, + storage, + } = val; + + let mut nodestore = NodeStore { + header, + kind: Arc::new(ImmutableProposal { + new: HashMap::new(), + deleted: kind.deleted.into(), + parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), + root_hash: None, + root: None, + }), + storage, + }; + + let Some(root) = kind.root else { + // This trie is now empty. + nodestore.header.set_root_address(None); + return Ok(nodestore); + }; + + // Hashes the trie and returns the address of the new root. + let mut new_nodes = HashMap::new(); + #[cfg(feature = "ethhash")] + let (root_addr, root_hash) = + nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes, None)?; + #[cfg(not(feature = "ethhash"))] + let (root_addr, root_hash) = + nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes)?; + + nodestore.header.set_root_address(Some(root_addr)); + let immutable_proposal = + Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); + nodestore.kind = Arc::new(ImmutableProposal { + new: new_nodes, + deleted: immutable_proposal.deleted, + parent: immutable_proposal.parent, + root_hash: Some(root_hash.into_triehash()), + root: Some(root_addr.into()), + }); + + Ok(nodestore) + } +} + +impl NodeReader for NodeStore { + fn read_node(&self, addr: LinearAddress) -> Result { + if let Some(node) = self.kind.read_in_memory_node(addr) { + return Ok(node); + } + + self.read_node_from_disk(addr, "write") + } +} + +impl NodeReader for NodeStore { + fn read_node(&self, addr: LinearAddress) -> Result { + if let Some(node) = self.kind.read_in_memory_node(addr) { + return Ok(node); + } + self.read_node_from_disk(addr, "read") + } +} + +impl RootReader for NodeStore { + fn root_node(&self) -> Option { + self.kind.root.as_ref().map(|node| node.clone().into()) + } +} + +impl RootReader for NodeStore { + fn root_node(&self) -> Option { + // TODO: If the read_node fails, we just say there is no root; this is incorrect + self.kind.root.as_ref()?.as_shared_node(self).ok() + } +} + +impl RootReader for NodeStore, S> { + fn root_node(&self) -> Option { + // Use the MaybePersistedNode's as_shared_node method to get the root + self.kind.root.as_ref()?.as_shared_node(self).ok() + } +} + +impl HashedNodeReader for NodeStore +where + NodeStore: TrieReader, + T: Parentable, + S: ReadableStorage, +{ + fn root_address(&self) -> Option { + self.header.root_address() + } + + fn root_hash(&self) -> Option { + self.kind.root_hash() + } +} + +impl HashedNodeReader for Arc +where + N: HashedNodeReader, +{ + fn root_address(&self) -> Option { + self.as_ref().root_address() + } + + fn root_hash(&self) -> Option { + self.as_ref().root_hash() + } +} + +impl NodeStore { + /// adjust the freelist of this proposal to reflect the freed nodes in the oldest proposal + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if a node cannot be deleted. + pub fn reap_deleted( + mut self, + proposal: &mut NodeStore, + ) -> Result<(), FileIoError> { + self.storage + .invalidate_cached_nodes(self.kind.deleted.iter()); + trace!("There are {} nodes to reap", self.kind.deleted.len()); + for node in take(&mut self.kind.deleted) { + proposal.delete_node(node)?; + } + Ok(()) + } +} + +// Helper functions for the checker +impl NodeStore { + pub(crate) const fn size(&self) -> u64 { + self.header.size() + } + + pub(crate) fn physical_size(&self) -> Result { + self.storage.size() + } +} + +#[cfg(test)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::cast_possible_truncation)] +mod tests { + use std::array::from_fn; + + use crate::linear::memory::MemStore; + use crate::{BranchNode, Child, LeafNode}; + use arc_swap::access::DynGuard; + use nonzero_ext::nonzero; + use test_case::test_case; + + use super::*; + use alloc::{AREA_SIZES, MAX_AREA_SIZE, MIN_AREA_SIZE, NUM_AREA_SIZES, area_size_to_index}; + + #[test] + fn test_area_size_to_index() { + // TODO: rustify using: for size in AREA_SIZES + for (i, &area_size) in AREA_SIZES.iter().enumerate() { + // area size is at top of range + assert_eq!(area_size_to_index(area_size).unwrap(), i as AreaIndex); + + if i > 0 { + // 1 less than top of range stays in range + assert_eq!(area_size_to_index(area_size - 1).unwrap(), i as AreaIndex); + } + + if i < NUM_AREA_SIZES - 1 { + // 1 more than top of range goes to next range + assert_eq!( + area_size_to_index(area_size + 1).unwrap(), + (i + 1) as AreaIndex + ); + } + } + + for i in 0..=MIN_AREA_SIZE { + assert_eq!(area_size_to_index(i).unwrap(), 0); + } + + assert!(area_size_to_index(MAX_AREA_SIZE + 1).is_err()); + } + + #[test] + fn test_reparent() { + // create an empty base revision + let memstore = MemStore::new(vec![]); + let base = NodeStore::new_empty_committed(memstore.into()) + .unwrap() + .into(); + + // create an empty r1, check that it's parent is the empty committed version + let r1 = NodeStore::new(&base).unwrap(); + let r1: Arc, _>> = Arc::new(r1.try_into().unwrap()); + let parent: DynGuard> = r1.kind.parent.load(); + assert!(matches!(**parent, NodeStoreParent::Committed(None))); + + // create an empty r2, check that it's parent is the proposed version r1 + let r2: NodeStore = NodeStore::new(&r1.clone()).unwrap(); + let r2: Arc, _>> = Arc::new(r2.try_into().unwrap()); + let parent: DynGuard> = r2.kind.parent.load(); + assert!(matches!(**parent, NodeStoreParent::Proposed(_))); + + // reparent r2 + r1.commit_reparent(&r2); + + // now check r2's parent, should match the hash of r1 (which is still None) + let parent: DynGuard> = r2.kind.parent.load(); + if let NodeStoreParent::Committed(hash) = &**parent { + assert_eq!(*hash, r1.root_hash()); + assert_eq!(*hash, None); + } else { + panic!("expected committed parent"); + } + } + + #[test_case(BranchNode { + partial_path: Path::from([6, 7, 8]), + value: Some(vec![9, 10, 11].into_boxed_slice()), + children: from_fn(|i| { + if i == 15 { + Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) + } else { + None + } + }), + }; "branch node with 1 child")] + #[test_case(BranchNode { + partial_path: Path::from([6, 7, 8]), + value: Some(vec![9, 10, 11].into_boxed_slice()), + children: from_fn(|_| + Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) + ), + }; "branch node with all child")] + #[test_case( + Node::Leaf(LeafNode { + partial_path: Path::from([0, 1, 2]), + value: Box::new([3, 4, 5]), + }); "leaf node")] + + fn test_serialized_len>(node: N) { + let node = node.into(); + + let computed_length = + NodeStore::, MemStore>::stored_len(&node); + + let mut serialized = Vec::new(); + node.as_bytes(0, &mut serialized); + assert_eq!(serialized.len() as u64, computed_length); + } + #[test] + #[should_panic(expected = "Node size 16777225 is too large")] + fn giant_node() { + let memstore = MemStore::new(vec![]); + let mut node_store = NodeStore::new_empty_proposal(memstore.into()); + + let huge_value = vec![0u8; *AREA_SIZES.last().unwrap() as usize]; + + let giant_leaf = Node::Leaf(LeafNode { + partial_path: Path::from([0, 1, 2]), + value: huge_value.into_boxed_slice(), + }); + + node_store.mut_root().replace(giant_leaf); + + let immutable = NodeStore::, _>::try_from(node_store).unwrap(); + println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal + } +} diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs new file mode 100644 index 000000000000..b503071bf664 --- /dev/null +++ b/storage/src/nodestore/persist.rs @@ -0,0 +1,249 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # Persist Module +//! +//! This module handles all persistence operations for the nodestore, including writing +//! headers, nodes, and metadata to storage with support for different I/O backends. +//! +//! ## I/O Backend Support +//! +//! This module supports multiple I/O backends through conditional compilation: +//! +//! - **Standard I/O** - `#[cfg(not(feature = "io-uring"))]` - Uses standard file operations +//! - **io-uring** - `#[cfg(feature = "io-uring")]` - Uses Linux io-uring for async I/O +//! +//! This feature flag is automatically enabled when running on Linux, and disabled for all other platforms. +//! +//! The io-uring implementation provides: +//! - Asynchronous batch operations +//! - Reduced system call overhead +//! - Better performance for high-throughput workloads +//! +//! ## Performance Considerations +//! +//! - Nodes are written in batches to minimize I/O overhead +//! - Metrics are collected for flush operation timing +//! - Memory-efficient serialization with pre-allocated buffers +//! - Ring buffer management for io-uring operations + +use crate::linear::FileIoError; +use coarsetime::Instant; +use metrics::counter; +use std::sync::Arc; + +#[cfg(feature = "io-uring")] +use crate::logger::trace; + +use crate::{FileBacked, WritableStorage}; + +#[cfg(feature = "io-uring")] +use crate::ReadableStorage; + +use super::header::NodeStoreHeader; +use super::{ImmutableProposal, NodeStore}; + +impl NodeStore { + /// Persist the header from this proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be written. + pub fn flush_header(&self) -> Result<(), FileIoError> { + let header_bytes = bytemuck::bytes_of(&self.header); + self.storage.write(0, header_bytes)?; + Ok(()) + } + + /// Persist the header, including all the padding + /// This is only done the first time we write the header + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the header cannot be written. + pub fn flush_header_with_padding(&self) -> Result<(), FileIoError> { + let header_bytes = bytemuck::bytes_of(&self.header) + .iter() + .copied() + .chain(std::iter::repeat_n(0u8, NodeStoreHeader::EXTRA_BYTES)) + .collect::>(); + debug_assert_eq!(header_bytes.len(), NodeStoreHeader::SIZE as usize); + + self.storage.write(0, &header_bytes)?; + Ok(()) + } +} + +impl NodeStore, FileBacked> { + /// Persist the freelist from this proposal to storage. + #[fastrace::trace(short_name = true)] + pub fn flush_freelist(&self) -> Result<(), FileIoError> { + // Write the free lists to storage + let free_list_bytes = bytemuck::bytes_of(self.header.free_lists()); + let free_list_offset = NodeStoreHeader::free_lists_offset(); + self.storage.write(free_list_offset, free_list_bytes)?; + Ok(()) + } + + /// Persist all the nodes of a proposal to storage. + #[fastrace::trace(short_name = true)] + #[cfg(not(feature = "io-uring"))] + pub fn flush_nodes(&self) -> Result<(), FileIoError> { + let flush_start = Instant::now(); + + for (addr, (area_size_index, node)) in &self.kind.new { + let mut stored_area_bytes = Vec::new(); + node.as_bytes(*area_size_index, &mut stored_area_bytes); + self.storage + .write(addr.get(), stored_area_bytes.as_slice())?; + } + + self.storage + .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + + let flush_time = flush_start.elapsed().as_millis(); + counter!("firewood.flush_nodes").increment(flush_time); + + Ok(()) + } + + /// Persist all the nodes of a proposal to storage. + #[fastrace::trace(short_name = true)] + #[cfg(feature = "io-uring")] + pub fn flush_nodes(&self) -> Result<(), FileIoError> { + use std::pin::Pin; + + #[derive(Clone, Debug)] + struct PinnedBufferEntry { + pinned_buffer: Pin>, + offset: Option, + } + + /// Helper function to handle completion queue entries and check for errors + fn handle_completion_queue( + storage: &FileBacked, + completion_queue: io_uring::cqueue::CompletionQueue<'_>, + saved_pinned_buffers: &mut [PinnedBufferEntry], + ) -> Result<(), FileIoError> { + for entry in completion_queue { + let item = entry.user_data() as usize; + let pbe = saved_pinned_buffers + .get_mut(item) + .expect("should be an index into the array"); + + if entry.result() + != pbe + .pinned_buffer + .len() + .try_into() + .expect("buffer should be small enough") + { + let error = if entry.result() >= 0 { + std::io::Error::other("Partial write") + } else { + std::io::Error::from_raw_os_error(0 - entry.result()) + }; + return Err(storage.file_io_error( + error, + pbe.offset.expect("offset should be Some"), + Some("write failure".to_string()), + )); + } + pbe.offset = None; + } + Ok(()) + } + + const RINGSIZE: usize = FileBacked::RINGSIZE as usize; + + let flush_start = Instant::now(); + + let mut ring = self.storage.ring.lock().expect("poisoned lock"); + let mut saved_pinned_buffers = vec![ + PinnedBufferEntry { + pinned_buffer: Pin::new(Box::new([0; 0])), + offset: None, + }; + RINGSIZE + ]; + for (&addr, &(area_size_index, ref node)) in &self.kind.new { + let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger + node.as_bytes(area_size_index, &mut serialized); + let mut serialized = serialized.into_boxed_slice(); + loop { + // Find the first available write buffer, enumerate to get the position for marking it completed + if let Some((pos, pbe)) = saved_pinned_buffers + .iter_mut() + .enumerate() + .find(|(_, pbe)| pbe.offset.is_none()) + { + pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); + pbe.offset = Some(addr.get()); + + let submission_queue_entry = self + .storage + .make_op(&pbe.pinned_buffer) + .offset(addr.get()) + .build() + .user_data(pos as u64); + + // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope + // until the operation has been completed. This is ensured by having a Some(offset) + // and not marking it None until the kernel has said it's done below. + #[expect(unsafe_code)] + while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { + ring.submitter().squeue_wait().map_err(|e| { + self.storage.file_io_error( + e, + addr.get(), + Some("io-uring squeue_wait".to_string()), + ) + })?; + trace!("submission queue is full"); + counter!("ring.full").increment(1); + } + break; + } + // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation + // to complete, then handle the completion queue + counter!("ring.full").increment(1); + ring.submit_and_wait(1).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("io-uring submit_and_wait".to_string())) + })?; + let completion_queue = ring.completion(); + trace!("competion queue length: {}", completion_queue.len()); + handle_completion_queue( + &self.storage, + completion_queue, + &mut saved_pinned_buffers, + )?; + } + } + let pending = saved_pinned_buffers + .iter() + .filter(|pbe| pbe.offset.is_some()) + .count(); + ring.submit_and_wait(pending).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) + })?; + + handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; + + debug_assert!( + !saved_pinned_buffers.iter().any(|pbe| pbe.offset.is_some()), + "Found entry with offset still set: {:?}", + saved_pinned_buffers.iter().find(|pbe| pbe.offset.is_some()) + ); + + self.storage + .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + debug_assert!(ring.completion().is_empty()); + + let flush_time = flush_start.elapsed().as_millis(); + counter!("firewood.flush_nodes").increment(flush_time); + + Ok(()) + } +} From 3bb5e189f72c0fb2289efa77468433212342699a Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:06:07 -0500 Subject: [PATCH 0819/1053] feat(checker): add error to report finding leaked areas (#1052) This is the first part of this diff: #1033 --- storage/src/checker.rs | 6 ++- storage/src/iter.rs | 81 +++++++++++++++++++++++++++++++++ storage/src/lib.rs | 5 +++ storage/src/range_set.rs | 96 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 storage/src/iter.rs diff --git a/storage/src/checker.rs b/storage/src/checker.rs index 2f9ce8531397..f49cc270e24b 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::logger::warn; use crate::range_set::LinearAddressRangeSet; use crate::{ CheckerError, Committed, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, @@ -46,7 +47,10 @@ impl NodeStore { self.visit_freelist(&mut visited)?; // 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? - let _ = visited.complement(); // TODO + let leaked_ranges = visited.complement(); + if !leaked_ranges.is_empty() { + warn!("Found leaked ranges: {leaked_ranges}"); + } Ok(()) } diff --git a/storage/src/iter.rs b/storage/src/iter.rs new file mode 100644 index 000000000000..9dcbd82198bb --- /dev/null +++ b/storage/src/iter.rs @@ -0,0 +1,81 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! Internal utilities for working with iterators. + +/// Writes a limited number of items to a writer, separated by a specified separator. +/// +/// - If `limit` is `Some(0)`, it will only write the number of hidden items. +/// - If `limit` is `Some(n)`, it will write at most `n` items, followed by a message +/// indicating how many more items were not written. +/// - If `limit` is `None`, it will write all items without any limit. +/// Caution: if limit is None, this function will not work with iterators that do not terminate. +/// +/// # Arguments +/// - `writer`: The writer to which the items will be written. +/// - `iter`: An iterator of items that implement `std::fmt::Display`. +/// - `sep`: A separator that will be used between items. +/// - `limit`: An optional limit on the number of items to write. +/// +/// # Returns +/// A `std::fmt::Result` indicating success or failure of the write operation. +pub(crate) fn write_limited_with_sep( + writer: &mut (impl std::fmt::Write + ?Sized), + iter: impl IntoIterator, + sep: impl std::fmt::Display, + limit: Option, +) -> std::fmt::Result { + match limit { + Some(0) => { + let hidden_count = iter.into_iter().count(); + write!(writer, "({hidden_count} hidden)") + } + Some(limit) => { + let mut iter = iter.into_iter(); + let to_display_iter = iter.by_ref().take(limit); + write_all_with_sep(writer, to_display_iter, &sep)?; + + let hidden_count = iter.count(); + if hidden_count > 0 { + write!(writer, "{sep}... ({hidden_count} more hidden)")?; + } + Ok(()) + } + None => write_all_with_sep(writer, iter, &sep), + } +} + +// Helper function that writes all items in the iterator. +// Caution: this function will not work with iterators that do not terminate. +fn write_all_with_sep( + writer: &mut (impl std::fmt::Write + ?Sized), + iter: impl IntoIterator, + sep: &impl std::fmt::Display, +) -> std::fmt::Result { + let mut iter = iter.into_iter(); + if let Some(item) = iter.next() { + write!(writer, "{item}")?; + for item in iter { + write!(writer, "{sep}{item}")?; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + #![expect(clippy::unwrap_used)] + + use super::*; + use test_case::test_case; + + #[test_case(Some(0usize), "(4 hidden)"; "with limit 0")] + #[test_case(Some(2usize), "apple, banana, ... (2 more hidden)"; "with limit 2")] + #[test_case(None, "apple, banana, cherry, date"; "without limit")] + fn test_write_iter(limit: Option, expected: &str) { + let mut output = String::new(); + let items = ["apple", "banana", "cherry", "date"]; + write_limited_with_sep(&mut output, &items, ", ", limit).unwrap(); + assert_eq!(output, expected); + } +} diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 77e6e8dfeb94..dba0eebb5838 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -25,6 +25,7 @@ use thiserror::Error; mod checker; mod hashednode; mod hashers; +mod iter; mod linear; mod node; mod nodestore; @@ -150,6 +151,10 @@ pub enum CheckerError { expected_free_list: AreaIndex, }, + /// Found leaked areas + #[error("Found leaked areas: {0:?}")] + AreaLeaks(Vec>), + /// IO error #[error("IO error")] IO(#[from] FileIoError), diff --git a/storage/src/range_set.rs b/storage/src/range_set.rs index 2fbb1544400a..7997b11bc9eb 100644 --- a/storage/src/range_set.rs +++ b/storage/src/range_set.rs @@ -4,12 +4,15 @@ #![warn(clippy::pedantic)] use std::collections::BTreeMap; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::ops::Range; +use crate::iter::write_limited_with_sep; use crate::nodestore::NodeStoreHeader; use crate::{CheckerError, LinearAddress}; +const MAX_AREAS_TO_DISPLAY: usize = 10; + #[derive(Debug)] // BTreeMap: range end --> range start // To check if a value is in the range set, we will find the range with the smallest end that is greater than or equal to the given value @@ -191,6 +194,14 @@ impl RangeSet { Self(complement_tree) } + + pub fn iter(&self) -> impl Iterator> { + self.0.iter().map(|(end, start)| Range { start, end }) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl IntoIterator for RangeSet { @@ -272,6 +283,10 @@ impl LinearAddressRangeSet { max_addr: self.max_addr, } } + + pub(super) fn is_empty(&self) -> bool { + self.range_set.is_empty() + } } impl IntoIterator for LinearAddressRangeSet { @@ -283,6 +298,30 @@ impl IntoIterator for LinearAddressRangeSet { } } +impl Display for LinearAddressRangeSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct DisplayRange<'a>(Range<&'a LinearAddress>); + impl std::fmt::Display for DisplayRange<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (start, end) = (self.0.start, self.0.end); + write!(f, "Address Range: [{start:#x}, {end:#x})") + } + } + + if self.range_set.is_empty() { + write!(f, "Linear Address Range Set: ") + } else { + write!(f, "Linear Address Range Set:\n\t")?; + write_limited_with_sep( + f, + self.range_set.iter().map(DisplayRange), + "\n\t", + Some(MAX_AREAS_TO_DISPLAY), + ) + } + } +} + #[cfg(test)] mod test_range_set { use super::*; @@ -509,6 +548,7 @@ mod test_range_set { mod test_linear_address_range_set { use super::*; + use test_case::test_case; #[test] fn test_empty() { @@ -657,4 +697,58 @@ mod test_linear_address_range_set { let complement = visited.complement().into_iter().collect::>(); assert_eq!(complement, vec![]); } + + #[test_case(0, "Linear Address Range Set: "; "empty")] + #[test_case(1, "Linear Address Range Set:\n\ + \tAddress Range: [0x1000, 0x1010)"; "1 range")] + #[test_case(3, "Linear Address Range Set:\n\ + \tAddress Range: [0x1000, 0x1010)\n\ + \tAddress Range: [0x1020, 0x1030)\n\ + \tAddress Range: [0x1040, 0x1050)"; "3 ranges")] + #[test_case(10, "Linear Address Range Set:\n\ + \tAddress Range: [0x1000, 0x1010)\n\ + \tAddress Range: [0x1020, 0x1030)\n\ + \tAddress Range: [0x1040, 0x1050)\n\ + \tAddress Range: [0x1060, 0x1070)\n\ + \tAddress Range: [0x1080, 0x1090)\n\ + \tAddress Range: [0x10a0, 0x10b0)\n\ + \tAddress Range: [0x10c0, 0x10d0)\n\ + \tAddress Range: [0x10e0, 0x10f0)\n\ + \tAddress Range: [0x1100, 0x1110)\n\ + \tAddress Range: [0x1120, 0x1130)"; "10 ranges")] + #[test_case(11, "Linear Address Range Set:\n\ + \tAddress Range: [0x1000, 0x1010)\n\ + \tAddress Range: [0x1020, 0x1030)\n\ + \tAddress Range: [0x1040, 0x1050)\n\ + \tAddress Range: [0x1060, 0x1070)\n\ + \tAddress Range: [0x1080, 0x1090)\n\ + \tAddress Range: [0x10a0, 0x10b0)\n\ + \tAddress Range: [0x10c0, 0x10d0)\n\ + \tAddress Range: [0x10e0, 0x10f0)\n\ + \tAddress Range: [0x1100, 0x1110)\n\ + \tAddress Range: [0x1120, 0x1130)\n\ + \t... (1 more hidden)"; "11 ranges")] + #[test_case(20, "Linear Address Range Set:\n\ + \tAddress Range: [0x1000, 0x1010)\n\ + \tAddress Range: [0x1020, 0x1030)\n\ + \tAddress Range: [0x1040, 0x1050)\n\ + \tAddress Range: [0x1060, 0x1070)\n\ + \tAddress Range: [0x1080, 0x1090)\n\ + \tAddress Range: [0x10a0, 0x10b0)\n\ + \tAddress Range: [0x10c0, 0x10d0)\n\ + \tAddress Range: [0x10e0, 0x10f0)\n\ + \tAddress Range: [0x1100, 0x1110)\n\ + \tAddress Range: [0x1120, 0x1130)\n\ + \t... (10 more hidden)"; "20 ranges")] + fn test_display(items: usize, expected: &str) { + let mut range_set = LinearAddressRangeSet::new(0x2000).unwrap(); + for i in 0..items { + #[allow(clippy::arithmetic_side_effects)] + let offset = i as u64 * 0x20 + 0x1000; + range_set + .insert_area(LinearAddress::new(offset).unwrap(), 0x10) + .unwrap(); + } + assert_eq!(format!("{range_set}"), expected); + } } From af8d3dcf08dc171b5f4cab517ccb1d4f34e7c30a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 14 Jul 2025 17:06:28 -0700 Subject: [PATCH 0820/1053] feat(delayed-persist): dump unpersisted nodestore (#1055) dump has to work with unpersisted nodes, as there are a bunch of tests that assume an immutable proposal can be dumped. This was useful anyway while debugging some tests in the next commit, and small enough to commit separately. --- firewood/src/merkle.rs | 54 +++++++++++++++++------------------- storage/src/node/branch.rs | 15 +++++++++- storage/src/node/persist.rs | 20 ++++++++++++- storage/src/nodestore/mod.rs | 23 +++++++++++++++ 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1478d0cac6af..b0495c8809c0 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -24,8 +24,8 @@ use crate::stream::PathIterator; use crate::v2::api; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, - IntoHashType, LeafNode, LinearAddress, MutableProposal, NibblesIterator, Node, NodeStore, Path, - ReadableStorage, SharedNode, TrieReader, ValueDigest, + IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, + Path, ReadableStorage, SharedNode, TrieReader, ValueDigest, }; #[cfg(test)] use futures::{StreamExt, TryStreamExt}; @@ -176,10 +176,6 @@ impl Merkle { &self.nodestore } - fn read_node(&self, addr: LinearAddress) -> Result { - self.nodestore.read_node(addr) - } - /// Returns a proof that the given key has a certain value, /// or that the key isn't in the trie. pub fn prove(&self, key: &[u8]) -> Result, ProofError> { @@ -364,14 +360,15 @@ impl Merkle { } impl Merkle { + /// Dump a node, recursively, to a dot file pub(crate) fn dump_node( &self, - addr: LinearAddress, + node: &MaybePersistedNode, hash: Option<&HashType>, - seen: &mut HashSet, + seen: &mut HashSet, writer: &mut dyn Write, ) -> Result<(), FileIoError> { - write!(writer, " {addr}[label=\"addr:{addr:?}") + write!(writer, " {node}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None))?; if let Some(hash) = hash { @@ -380,33 +377,28 @@ impl Merkle { .map_err(|e| FileIoError::new(e, None, 0, None))?; } - match &*self.read_node(addr)? { + match &*node.as_shared_node(&self.nodestore)? { Node::Branch(b) => { write_attributes!(writer, b, &b.value.clone().unwrap_or(Box::from([]))); writeln!(writer, "\"]") .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; for (childidx, child) in b.children.iter().enumerate() { - let (child_addr, child_hash) = match child { + let (child, child_hash) = match child { None => continue, - Some(node) => { - let (Some(addr), hash) = (node.persisted_address(), node.hash()) else { - continue; - }; - (addr, hash) - } + Some(node) => (node.as_maybe_persisted_node(), node.hash()), }; - let inserted = seen.insert(child_addr); + let inserted = seen.insert(format!("{child}")); if inserted { - writeln!(writer, " {addr} -> {child_addr}[label=\"{childidx}\"]") + writeln!(writer, " {node} -> {child}[label=\"{childidx}\"]") .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; - self.dump_node(child_addr, child_hash, seen, writer)?; + self.dump_node(&child, child_hash, seen, writer)?; } else { // We have already seen this child, which shouldn't happen. // Indicate this with a red edge. writeln!( writer, - " {addr} -> {child_addr}[label=\"{childidx} (dup)\" color=red]" + " {node} -> {child}[label=\"{childidx} (dup)\" color=red]" ) .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; } @@ -421,19 +413,25 @@ impl Merkle { Ok(()) } + /// Dump the trie to a dot file. + /// + /// This function is primarily used in testing, but also has an API implementation + /// + /// Dot files can be rendered using `dot -Tpng -o output.png input.dot` + /// or online at pub(crate) fn dump(&self) -> Result { + let root = self.nodestore.root_as_maybe_persisted_node(); + let mut result = String::new(); writeln!(result, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; - if let (Some(root_addr), Some(root_hash)) = - (self.nodestore.root_address(), self.nodestore.root_hash()) - { - writeln!(result, " root -> {root_addr}") + if let (Some(root), Some(root_hash)) = (root, self.nodestore.root_hash()) { + writeln!(result, " root -> {root}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None)) .map_err(Error::other)?; let mut seen = HashSet::new(); self.dump_node( - root_addr, + &root, Some(&root_hash.into_hash_type()), &mut seen, &mut result, @@ -462,10 +460,10 @@ impl TryFrom>> impl Merkle> { /// Convert a merkle backed by an `MutableProposal` into an `ImmutableProposal` - /// TODO: We probably don't need this function + /// + /// This function is only used in benchmarks and tests #[must_use] pub fn hash(self) -> Merkle, S>> { - // I think this is only used in testing self.try_into().expect("failed to convert") } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 98841215d762..d8424aae6455 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use crate::node::ExtendableBytes; -use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path}; +use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path, SharedNode}; use std::fmt::{Debug, Formatter}; /// The type of a hash. For ethereum compatible hashes, this might be a RLP encoded @@ -112,6 +112,19 @@ impl Child { Child::Node(_) => None, } } + + /// Return a `MaybePersistedNode` from a child + /// + /// This is used in the dump utility, but otherwise should be avoided, + /// as it may create an unnecessary `MaybePersistedNode` + #[must_use] + pub fn as_maybe_persisted_node(&self) -> MaybePersistedNode { + match self { + Child::Node(node) => MaybePersistedNode::from(SharedNode::from(node.clone())), + Child::AddressWithHash(addr, _) => MaybePersistedNode::from(*addr), + Child::MaybePersisted(maybe_persisted, _) => maybe_persisted.clone(), + } + } } #[cfg(feature = "ethhash")] diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 8c1068d81c54..4cad3679073a 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; use arc_swap::ArcSwap; @@ -123,6 +123,24 @@ impl MaybePersistedNode { } } +/// Display the `MaybePersistedNode` as a string. +/// +/// This is used in the dump utility. +/// +/// We render these: +/// For disk addresses, just the address +/// For shared nodes, the address of the [`SharedNode`] object, prefixed with a 'M' +/// +/// If instead you want the node itself, use [`MaybePersistedNode::as_shared_node`] first. +impl Display for MaybePersistedNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0.load().as_ref() { + MaybePersisted::Unpersisted(node) => write!(f, "M{:p}", (*node).as_ptr()), + MaybePersisted::Persisted(addr) => write!(f, "{addr}"), + } + } +} + /// The internal state of a `MaybePersistedNode`. /// /// This enum represents the two possible states of a `MaybePersisted`: diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 4e297400545d..c23ea383e7bb 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -330,12 +330,23 @@ where fn root_node(&self) -> Option { self.deref().root_node() } + fn root_as_maybe_persisted_node(&self) -> Option { + self.deref().root_as_maybe_persisted_node() + } } /// Reads the root of a merkle trie. +/// +/// The root may be None if the trie is empty. pub trait RootReader { /// Returns the root of the trie. + /// Callers that just need the node at the root should use this function. fn root_node(&self) -> Option; + + /// Returns the root of the trie as a `MaybePersistedNode`. + /// Callers that might want to modify the root or know how it is stored + /// should use this function. + fn root_as_maybe_persisted_node(&self) -> Option; } /// A committed revision of a merkle trie. @@ -623,6 +634,12 @@ impl RootReader for NodeStore { fn root_node(&self) -> Option { self.kind.root.as_ref().map(|node| node.clone().into()) } + fn root_as_maybe_persisted_node(&self) -> Option { + self.kind + .root + .as_ref() + .map(|node| SharedNode::new(node.clone()).into()) + } } impl RootReader for NodeStore { @@ -630,6 +647,9 @@ impl RootReader for NodeStore { // TODO: If the read_node fails, we just say there is no root; this is incorrect self.kind.root.as_ref()?.as_shared_node(self).ok() } + fn root_as_maybe_persisted_node(&self) -> Option { + self.kind.root.clone() + } } impl RootReader for NodeStore, S> { @@ -637,6 +657,9 @@ impl RootReader for NodeStore, S> { // Use the MaybePersistedNode's as_shared_node method to get the root self.kind.root.as_ref()?.as_shared_node(self).ok() } + fn root_as_maybe_persisted_node(&self) -> Option { + self.kind.root.clone() + } } impl HashedNodeReader for NodeStore From da2c55dff172dc142c7d1d39c127f8310298fe5b Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Tue, 15 Jul 2025 17:21:18 -0400 Subject: [PATCH 0821/1053] chore: update `golangci-lint` (#1053) While looking into Taskfiles for `ffi`, I noticed that we're still using `golangci-lint v1` while AvalancheGo is already using `golangci-lint v2`. This PR upgrades the version of `golangci-lint` used in `ffi` to `v2`. To maintain synchrony between the Firewood and AvalancheGo linter rules, an additional CI job (`expected-golangci-yaml-diff`) ensures that the difference between the two `.golangci.yaml` files is equal to the contents of `golangci_yaml_expected_changes.txt`. ## What - Copied over current AvalancheGo `golangci.yml` file and removing AvalancheGo-specific rules. - Bumped version of `golangci-lint` action used in CI. - Added `expected-golangci-yaml-diff` CI job - Linted files ## How This Was Tested Ran `golangci-lint` locally + CI. --- .github/.golangci_yaml_expected_changes.txt | 62 +++++ .../scripts/verify_golangci_yaml_changes.sh | 19 ++ .github/workflows/ci.yaml | 5 +- .../expected-golangci-yaml-diff.yaml | 15 ++ ffi/.golangci.yaml | 251 ++++++++++-------- ffi/metrics.go | 3 +- ffi/metrics_test.go | 3 +- 7 files changed, 243 insertions(+), 115 deletions(-) create mode 100644 .github/.golangci_yaml_expected_changes.txt create mode 100755 .github/scripts/verify_golangci_yaml_changes.sh create mode 100644 .github/workflows/expected-golangci-yaml-diff.yaml diff --git a/.github/.golangci_yaml_expected_changes.txt b/.github/.golangci_yaml_expected_changes.txt new file mode 100644 index 000000000000..9c7042622939 --- /dev/null +++ b/.github/.golangci_yaml_expected_changes.txt @@ -0,0 +1,62 @@ +73,74d72 +< - pkg: container/list +< desc: github.com/ava-labs/avalanchego/utils/linked should be used instead. +89,92d86 +< - pattern: require\.Error$(# ErrorIs should be used instead)? +< - pattern: require\.ErrorContains$(# ErrorIs should be used instead)? +< - pattern: require\.EqualValues$(# Equal should be used instead)? +< - pattern: require\.NotEqualValues$(# NotEqual should be used instead)? +97,111d90 +< gosec: +< excludes: +< - G107 # Url provided to HTTP request as taint input https://securego.io/docs/rules/g107 +< - G115 # TODO(marun) Enable this ruleset in a follow-up PR +< importas: +< # Do not allow unaliased imports of aliased packages. +< no-unaliased: false +< # Do not allow non-required aliases. +< no-extra-aliases: false +< # List of aliases +< alias: +< - pkg: github.com/ava-labs/avalanchego/utils/math +< alias: safemath +< - pkg: github.com/ava-labs/avalanchego/utils/json +< alias: avajson +172,182d150 +< spancheck: +< # https://github.com/jjti/go-spancheck#checks +< checks: +< - end +< staticcheck: +< # https://staticcheck.io/docs/options#checks +< checks: +< - all +< - -SA6002A # Storing non-pointer values in sync.Pool allocates memory +< - -SA1019 # Using a deprecated function, variable, constant or field +< - -QF1008 # Unnecessary embedded expressions +190,199c158,167 +< # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). +< # Default: false +< enable-all: true +< # Disable checkers by name +< # (in addition to default +< # suite-thelper +< # ). +< disable: +< - go-require +< - float-compare +--- +> # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). +> # Default: false +> enable-all: true +> # Disable checkers by name +> # (in addition to default +> # suite-thelper +> # ). +> disable: +> - go-require +> - float-compare +228c196 +< - prefix(github.com/ava-labs/avalanchego) +--- +> - prefix(github.com/ava-labs/firewood/ffi) diff --git a/.github/scripts/verify_golangci_yaml_changes.sh b/.github/scripts/verify_golangci_yaml_changes.sh new file mode 100755 index 000000000000..3902babfc61c --- /dev/null +++ b/.github/scripts/verify_golangci_yaml_changes.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# ==== Generating Expected Diff ==== +# 1. Ensure working directory is .github +# 2. Store latest upstream version of .golangci.yaml to X +# 3. Run diff X ../ffi/.golangci.yaml > .golangci_yaml_expected_changes.txt + +cd "$(dirname "$(realpath "$0")")"/.. + +curl -o /tmp/upstream.yml https://raw.githubusercontent.com/ava-labs/avalanchego/refs/heads/master/.golangci.yml + +# Generate diff +diff /tmp/upstream.yml ../ffi/.golangci.yaml > /tmp/diff.txt || true + +# Compare with expected diff +if ! diff /tmp/diff.txt .golangci_yaml_expected_changes.txt; then + echo "ffi/.golangci.yaml has unexpected changes from AvalancheGo" + exit 1 +fi diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4173627fff78..169c3dfe71ff 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -210,8 +210,8 @@ jobs: with: go-version-file: "ffi/go.mod" cache-dependency-path: "ffi/go.sum" - - name: Run golanci-lint - uses: golangci/golangci-lint-action@v3 + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v7.0.1 with: version: latest working-directory: ffi @@ -303,4 +303,3 @@ jobs: run: cargo install cargo-machete - name: Check for stale dependencies run: cargo machete --with-metadata - diff --git a/.github/workflows/expected-golangci-yaml-diff.yaml b/.github/workflows/expected-golangci-yaml-diff.yaml new file mode 100644 index 000000000000..8daa5aae8bab --- /dev/null +++ b/.github/workflows/expected-golangci-yaml-diff.yaml @@ -0,0 +1,15 @@ +name: expected golangci.yaml diff + +on: + push: + branches: + - "main" + +jobs: + expected-golangci-yaml-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate expected golangci-lint changes + working-directory: .github + run: ./scripts/verify_golangci_yaml_changes.sh diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index 34e7ff1e6eb5..c4c4ffb5ae8c 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -1,7 +1,6 @@ # https://golangci-lint.run/usage/configuration/ +version: "2" run: - timeout: 10m - # If set we pass it to "go list -mod={option}". From "go help modules": # If invoked with -mod=readonly, the go command is disallowed from the implicit # automatic updating of go.mod described above. Instead, it fails when any changes @@ -15,53 +14,39 @@ run: # By default, it isn't set. modules-download-mode: readonly + # Include non-test files tagged as test-only. + # Context: https://github.com/ava-labs/avalanchego/pull/3173 + build-tags: + - test + issues: # Make issues output unique by line. - # Default: true uniq-by-line: false # Maximum issues count per one linter. - # Set to 0 to disable. - # Default: 50 max-issues-per-linter: 0 # Maximum count of issues with the same text. - # Set to 0 to disable. - # Default: 3 max-same-issues: 0 - # Enables skipping of directories: - # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - # Default: true - exclude-dirs-use-default: false - linters: - disable-all: true + default: none enable: - asciicheck - bodyclose - copyloopvar - depguard - dupword - - dupl - errcheck - - errname - errorlint - forbidigo - - gci - goconst - gocritic - # - err113 - encourages wrapping static errors - - gofmt - - gofumpt - # - mnd - unnecessary magic numbers - goprintffuncname - gosec - - gosimple - govet - importas - ineffassign - # - lll line length linter - misspell - nakedret - nilerr @@ -73,98 +58,144 @@ linters: - revive - spancheck - staticcheck - - stylecheck - tagalign - testifylint - - typecheck - unconvert - unparam - unused - usestdlibvars - whitespace - -linters-settings: - depguard: - rules: - packages: - deny: - - pkg: "github.com/golang/mock/gomock" - desc: go.uber.org/mock/gomock should be used instead. - - pkg: "github.com/stretchr/testify/assert" - desc: github.com/stretchr/testify/require should be used instead. - - pkg: "io/ioutil" - desc: io/ioutil is deprecated. Use package io or os instead. - errorlint: - # Check for plain type assertions and type switches. - asserts: false - # Check for plain error comparisons. - comparison: false - forbidigo: - # Forbid the following identifiers (list of regexp). - forbid: - - 'require\.Error$(# ErrorIs should be used instead)?' - - 'require\.ErrorContains$(# ErrorIs should be used instead)?' - - 'require\.EqualValues$(# Equal should be used instead)?' - - 'require\.NotEqualValues$(# NotEqual should be used instead)?' - - '^(t|b|tb|f)\.(Fatal|Fatalf|Error|Errorf)$(# the require library should be used instead)?' - revive: - rules: - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr - - name: bool-literal-in-expr - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return - - name: early-return - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines - - name: empty-lines - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format - - name: string-format - disabled: false - arguments: - - ["b.Logf[0]", "/.*%.*/", "no format directive, use b.Log instead"] - - ["fmt.Errorf[0]", "/.*%.*/", "no format directive, use errors.New instead"] - - ["fmt.Fprintf[1]", "/.*%.*/", "no format directive, use fmt.Fprint instead"] - - ["fmt.Printf[0]", "/.*%.*/", "no format directive, use fmt.Print instead"] - - ["fmt.Sprintf[0]", "/.*%.*/", "no format directive, use fmt.Sprint instead"] - - ["log.Fatalf[0]", "/.*%.*/", "no format directive, use log.Fatal instead"] - - ["log.Printf[0]", "/.*%.*/", "no format directive, use log.Print instead"] - - ["t.Logf[0]", "/.*%.*/", "no format directive, use t.Log instead"] - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag - - name: struct-tag - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming - - name: unexported-naming - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error - - name: unhandled-error - # prefer the errcheck linter since it can be disabled directly with nolint directive - # but revive's disable directive (e.g. //revive:disable:unhandled-error) is not - # supported when run under golangci_lint - disabled: true - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter - - name: unused-parameter - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver - - name: unused-receiver - disabled: false - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - - name: useless-break - disabled: false - tagalign: - align: true - sort: true - strict: true - order: - - serialize - testifylint: - # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). - # Default: false - enable-all: true - # Disable checkers by name - # (in addition to default - # suite-thelper - # ). - disable: - - go-require - - float-compare + settings: + depguard: + rules: + packages: + deny: + - pkg: github.com/golang/mock/gomock + desc: go.uber.org/mock/gomock should be used instead. + - pkg: github.com/stretchr/testify/assert + desc: github.com/stretchr/testify/require should be used instead. + - pkg: io/ioutil + desc: io/ioutil is deprecated. Use package io or os instead. + errorlint: + # Check for plain type assertions and type switches. + asserts: false + # Check for plain error comparisons. + comparison: false + forbidigo: + # Forbid the following identifiers (list of regexp). + forbid: + - pattern: ^(t|b|tb|f)\.(Fatal|Fatalf|Error|Errorf)$(# the require library should be used instead)? + - pattern: ^sort\.(Slice|Strings)$(# the slices package should be used instead)? + # Exclude godoc examples from forbidigo checks. + exclude-godoc-examples: false + revive: + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr + - name: bool-literal-in-expr + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return + - name: early-return + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines + - name: empty-lines + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format + - name: string-format + disabled: false + arguments: + - - b.Logf[0] + - /.*%.*/ + - no format directive, use b.Log instead + - - fmt.Errorf[0] + - /.*%.*/ + - no format directive, use errors.New instead + - - fmt.Fprintf[1] + - /.*%.*/ + - no format directive, use fmt.Fprint instead + - - fmt.Printf[0] + - /.*%.*/ + - no format directive, use fmt.Print instead + - - fmt.Sprintf[0] + - /.*%.*/ + - no format directive, use fmt.Sprint instead + - - log.Fatalf[0] + - /.*%.*/ + - no format directive, use log.Fatal instead + - - log.Printf[0] + - /.*%.*/ + - no format directive, use log.Print instead + - - t.Logf[0] + - /.*%.*/ + - no format directive, use t.Log instead + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + - name: struct-tag + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming + - name: unexported-naming + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error + - name: unhandled-error + # prefer the errcheck linter since it can be disabled directly with nolint directive + # but revive's disable directive (e.g. //revive:disable:unhandled-error) is not + # supported when run under golangci_lint + disabled: true + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver + - name: unused-receiver + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + disabled: false + tagalign: + align: true + sort: true + order: + - serialize + strict: true + testifylint: + # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). + # Default: false + enable-all: true + # Disable checkers by name + # (in addition to default + # suite-thelper + # ). + disable: + - go-require + - float-compare + unused: + # Mark all struct fields that have been written to as used. + # Default: true + field-writes-are-uses: false + # Treat IncDec statement (e.g. `i++` or `i--`) as both read and write operation instead of just write. + # Default: false + post-statements-are-reads: true + # Mark all local variables as used. + # default: true + local-variables-are-used: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling +formatters: + enable: + - gci + - gofmt + - gofumpt + settings: + gci: + sections: + - standard + - default + - blank + - prefix(github.com/ava-labs/firewood/ffi) + - alias + - dot + custom-order: true + exclusions: + generated: lax diff --git a/ffi/metrics.go b/ffi/metrics.go index 6d97f778e416..eb47dae657d8 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -10,8 +10,9 @@ import ( "strings" "github.com/prometheus/client_golang/prometheus" - dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" + + dto "github.com/prometheus/client_model/go" ) var _ prometheus.Gatherer = (*Gatherer)(nil) diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go index 8530153f5198..c3c3253aca46 100644 --- a/ffi/metrics_test.go +++ b/ffi/metrics_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" - dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + + dto "github.com/prometheus/client_model/go" ) // Test calling metrics exporter along with gathering metrics From 9b22467d8cfbc4214eecf2237083f01c667a4895 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 16 Jul 2025 08:57:14 -0700 Subject: [PATCH 0822/1053] fix: remove dependency on serde (#1066) The way we used serde when serializing free areas but not other nodes was inconsistent and caused an issue were we were expecting data to be encoded one way but in actuality it was encoded another way. This commit removes the dependency on serde for serialization in the storage crate, specifically in the `FreeArea` serialization. This ensures that all serialization is done manually, which is consistent and avoids the issues we were facing with serde's encoding. Fixes: #831 #1057 --- Cargo.toml | 2 - storage/Cargo.toml | 4 +- storage/benches/serializer.rs | 17 --- storage/src/node/branch.rs | 90 +++--------- storage/src/node/leaf.rs | 4 +- storage/src/node/mod.rs | 20 ++- storage/src/node/path.rs | 3 +- storage/src/nodestore/alloc.rs | 242 +++++++++++++++++++------------- storage/src/nodestore/header.rs | 5 +- storage/src/trie_hash.rs | 51 +------ 10 files changed, 192 insertions(+), 246 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1ba248c0c73..389b485ef5a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,8 +69,6 @@ sha2 = "0.10.9" tokio = "1.46.0" clap = { version = "4.5.40", features = ["derive"] } fastrace = "0.7.14" -bincode = "1.3.3" -serde = "1.0.219" thiserror = "2.0.12" coarsetime = "0.1.36" env_logger = "0.11.8" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 3d07f78399c2..af3b56a81781 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -17,12 +17,10 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bincode = { workspace = true } bitflags = "2.9.1" enum-as-inner = "0.6.1" hex = "0.4.3" -serde = { workspace = true, features = ["derive"] } -smallvec = { workspace = true, features = ["serde", "write", "union"] } +smallvec = { workspace = true, features = ["write", "union"] } sha2 = { workspace = true } integer-encoding = "4.0.2" arc-swap = "1.7.1" diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 25c678915151..4bafe0e6fee9 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -15,7 +15,6 @@ use std::fs::File; use std::num::NonZeroU64; use std::os::raw::c_int; -use bincode::Options; use criterion::profiler::Profiler; use criterion::{Criterion, criterion_group, criterion_main}; use firewood_storage::{LeafNode, Node, Path}; @@ -70,12 +69,6 @@ fn leaf(c: &mut Criterion) { partial_path: Path(SmallVec::from_slice(&[0, 1])), value: Box::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), }); - let serializer = bincode::DefaultOptions::new().with_varint_encoding(); - group.bench_with_input("serde", &input, |b, input| { - b.iter(|| { - serializer.serialize(input).unwrap(); - }); - }); group.bench_with_input("manual", &input, |b, input| { b.iter(|| { @@ -102,12 +95,6 @@ fn branch(c: &mut Criterion) { } }), })); - let serializer = bincode::DefaultOptions::new().with_varint_encoding(); - let serde_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { - b.iter(|| { - serializer.serialize(input).unwrap(); - }); - }; let manual_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { b.iter(|| { @@ -116,26 +103,22 @@ fn branch(c: &mut Criterion) { }); }; - group.bench_with_input("serde", &input, serde_serializer); group.bench_with_input("manual", &input, manual_serializer); group.finish(); let mut group = c.benchmark_group("1_child"); input.as_branch_mut().unwrap().value = None; - group.bench_with_input("serde", &input, serde_serializer); group.bench_with_input("manual", &input, manual_serializer); let child = input.as_branch().unwrap().children[0].clone(); group.finish(); let mut group = c.benchmark_group("2_child"); input.as_branch_mut().unwrap().children[1] = child.clone(); - group.bench_with_input("serde", &input, serde_serializer); group.bench_with_input("manual", &input, manual_serializer); group.finish(); let mut group = c.benchmark_group("16_child"); input.as_branch_mut().unwrap().children = std::array::from_fn(|_| child.clone()); - group.bench_with_input("serde", &input, serde_serializer); group.bench_with_input("manual", &input, manual_serializer); group.finish(); } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index d8424aae6455..6e31b696af2f 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::indexing_slicing, - reason = "Found 2 occurrences after enabling the lint." -)] #![expect( clippy::match_same_arms, reason = "Found 1 occurrences after enabling the lint." @@ -14,10 +10,6 @@ reason = "Found 2 occurrences after enabling the lint." )] -use serde::ser::SerializeStruct as _; -use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; - use crate::node::ExtendableBytes; use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path, SharedNode}; use std::fmt::{Debug, Formatter}; @@ -67,6 +59,24 @@ pub(crate) trait Serializable { Self: Sized; } +/// An extension trait for [`std::io::Read`] for convenience methods when +/// reading serialized data. +pub(crate) trait ReadSerializable: std::io::Read { + /// Read a single byte from the reader. + fn read_byte(&mut self) -> Result { + let mut this = 0; + self.read_exact(std::slice::from_mut(&mut this))?; + Ok(this) + } + + /// Read a value of type `T` from the reader. + fn next_value(&mut self) -> Result { + T::from_reader(self) + } +} + +impl ReadSerializable for T {} + #[derive(PartialEq, Eq, Clone, Debug)] #[repr(C)] /// A child of a branch node. @@ -129,7 +139,6 @@ impl Child { #[cfg(feature = "ethhash")] mod ethhash { - use serde::{Deserialize, Serialize}; use sha2::Digest as _; use sha3::Keccak256; use smallvec::SmallVec; @@ -143,7 +152,7 @@ mod ethhash { use super::Serializable; - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] + #[derive(Clone, Debug, PartialEq, Eq)] pub enum HashOrRlp { Hash(TrieHash), // TODO: this slice is never larger than 32 bytes so smallvec is probably not our best container @@ -275,67 +284,6 @@ pub struct BranchNode { pub children: [Option; Self::MAX_CHILDREN], } -impl Serialize for BranchNode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("BranchNode", 3)?; - state.serialize_field("partial_path", &self.partial_path)?; - state.serialize_field("value", &self.value)?; - - let children: SmallVec<[(_, _, _); Self::MAX_CHILDREN]> = self - .children - .iter() - .enumerate() - .filter_map(|(offset, child)| match child { - None => None, - Some(Child::Node(_)) => { - panic!("serializing in-memory node for disk storage") - } - Some(Child::AddressWithHash(addr, hash)) => Some((offset as u8, *addr, hash)), - Some(Child::MaybePersisted(maybe_persisted, hash)) => { - // For MaybePersisted, we need to get the address if it's persisted - maybe_persisted - .as_linear_address() - .map(|addr| (offset as u8, addr, hash)) - } - }) - .collect(); - - state.serialize_field("children", &children)?; - state.end() - } -} - -impl<'de> Deserialize<'de> for BranchNode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct SerializedBranchNode { - partial_path: Path, - value: Option>, - children: SmallVec<[(u8, LinearAddress, HashType); BranchNode::MAX_CHILDREN]>, - } - - let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?; - - let mut children: [Option; BranchNode::MAX_CHILDREN] = - [const { None }; BranchNode::MAX_CHILDREN]; - for (offset, addr, hash) in &s.children { - children[*offset as usize] = Some(Child::AddressWithHash(*addr, hash.clone())); - } - - Ok(BranchNode { - partial_path: s.partial_path, - value: s.value, - children, - }) - } -} - impl Debug for BranchNode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "[BranchNode")?; diff --git a/storage/src/node/leaf.rs b/storage/src/node/leaf.rs index 0e554f27d95c..4d6a022c1dff 100644 --- a/storage/src/node/leaf.rs +++ b/storage/src/node/leaf.rs @@ -1,14 +1,12 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use serde::{Deserialize, Serialize}; - use std::fmt::{Debug, Error as FmtError, Formatter}; use crate::Path; /// A leaf node -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone)] pub struct LeafNode { /// The path of this leaf, but only the remaining nibbles pub partial_path: Path, diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 7c8d10f7471e..cf302a4791be 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -25,8 +25,7 @@ use bitfield::bitfield; use branch::Serializable as _; use enum_as_inner::EnumAsInner; -use integer_encoding::{VarIntReader as _, VarIntWriter as _}; -use serde::{Deserialize, Serialize}; +use integer_encoding::{VarInt, VarIntReader as _, VarIntWriter as _}; use std::fmt::Debug; use std::io::{Error, Read, Write}; use std::num::NonZero; @@ -45,7 +44,7 @@ use crate::{HashType, Path, SharedNode}; /// A node, either a Branch or Leaf // TODO: explain why Branch is boxed but Leaf is not -#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner)] #[repr(C)] pub enum Node { /// This node is a [`BranchNode`] @@ -128,6 +127,18 @@ pub trait ExtendableBytes: Write { fn extend_from_slice(&mut self, other: &[u8]) { self.extend(other.iter().copied()); } + + /// Write a variable-length integer to the buffer without allocating an + /// intermediate buffer on the heap. + /// + /// This uses a stack buffer for holding the encoded integer and copies it + /// into the buffer. + #[expect(clippy::indexing_slicing)] + fn extend_var_int(&mut self, int: VI) { + let mut buf = [0u8; 10]; + let len = VarInt::encode_var(int, &mut buf); + self.extend_from_slice(&buf[..len]); + } } impl ExtendableBytes for Vec { @@ -140,6 +151,9 @@ impl ExtendableBytes for Vec { fn push(&mut self, value: u8) { Vec::push(self, value); } + fn extend_from_slice(&mut self, other: &[u8]) { + Vec::extend_from_slice(self, other); + } } pub struct ByteCounter(u64); diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index b2f778182433..4654a6ed1bba 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -16,7 +16,6 @@ // TODO: remove bitflags, we only use one bit use bitflags::bitflags; -use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::fmt::{self, Debug}; use std::iter::{FusedIterator, once}; @@ -25,7 +24,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// Path is part or all of a node's path in the trie. /// Each element is a nibble. -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone)] pub struct Path(pub SmallVec<[u8; 64]>); impl Debug for Path { diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 8a73f1b5e405..e37bd35e9b31 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -22,9 +22,9 @@ use crate::linear::FileIoError; use crate::logger::trace; -use bincode::{DefaultOptions, Options as _}; +use crate::node::branch::{ReadSerializable, Serializable}; +use integer_encoding::VarIntReader; use metrics::counter; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::io::{Error, ErrorKind, Read}; use std::iter::FusedIterator; @@ -32,11 +32,16 @@ use std::num::NonZeroU64; use std::sync::Arc; use crate::node::persist::MaybePersistedNode; -use crate::node::{ByteCounter, Node}; +use crate::node::{ByteCounter, ExtendableBytes, Node}; use crate::{CacheReadStrategy, ReadableStorage, SharedNode, TrieHash}; use crate::linear::WritableStorage; +/// Returns the maximum size needed to encode a `VarInt`. +const fn var_int_max_size() -> usize { + const { (size_of::() * 8 + 7) / 7 } +} + /// [`NodeStore`] divides the linear store into blocks of different sizes. /// [`AREA_SIZES`] is every valid block size. pub const AREA_SIZES: [u64; 23] = [ @@ -65,10 +70,6 @@ pub const AREA_SIZES: [u64; 23] = [ 1024 << 14, ]; -pub fn serializer() -> impl bincode::Options { - DefaultOptions::new().with_varint_encoding() -} - pub fn area_size_hash() -> TrieHash { let mut hasher = Sha256::new(); for size in AREA_SIZES { @@ -150,52 +151,107 @@ pub fn area_size_to_index(n: u64) -> Result { /// branches can use `Option` which is the same size as a [`LinearAddress`] pub type LinearAddress = NonZeroU64; -/// Each [`StoredArea`] contains an [Area] which is either a [Node] or a [`FreeArea`]. -#[repr(u8)] -#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] -pub enum Area { - Node(T), - Free(U) = 255, // this is magic: no node starts with a byte of 255 -} - -/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the -/// `NodeStoreHeader` is a [`StoredArea`]. -/// -/// As an overview of what this looks like stored, we get something like this: -/// - Byte 0: The index of the area size -/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf -/// - Bytes 2..n: The actual data -#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] -pub struct StoredArea { - /// Index in [`AREA_SIZES`] of this area's size - area_size_index: AreaIndex, - area: T, -} - -impl StoredArea { - /// Create a new `StoredArea` - pub const fn new(area_size_index: AreaIndex, area: T) -> Self { - Self { - area_size_index, - area, - } - } - - /// Destructure the `StoredArea` into its components - pub fn into_parts(self) -> (AreaIndex, T) { - (self.area_size_index, self.area) - } -} - pub type FreeLists = [Option; NUM_AREA_SIZES]; /// A [`FreeArea`] is stored at the start of the area that contained a node that /// has been freed. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct FreeArea { next_free_block: Option, } +impl Serializable for FreeArea { + fn write_to(&self, vec: &mut W) { + vec.push(0xff); // 0xff indicates a free area + vec.extend_var_int(self.next_free_block.map_or(0, LinearAddress::get)); + } + + /// Parse a [`FreeArea`]. + /// + /// The old serde generate code that unintentionally encoded [`FreeArea`]s + /// incorrectly. Integers were encoded as variable length integers, but + /// expanded to fixed-length below: + /// + /// ```text + /// [ + /// 0x01, // LE u32 begin -- field index of the old `StoredArea` struct (#1) + /// 0x00, + /// 0x00, + /// 0x00, // LE u32 end + /// 0x01, // `Option` discriminant, 1 Indicates `Some(_)` from `Option` + /// // because serde does not handle the niche optimization of + /// // `Option>` + /// 0x2a, // LinearAddress(LE u64) start + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, // LE u64 end + /// ] + /// ``` + /// + /// Our manual encoding format is (with variable int, but expanded below): + /// + /// ```text + /// [ + /// 0xff, // FreeArea marker + /// 0x2a, // LinearAddress(LE u64) start + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, + /// 0x00, // LE u64 end + /// ] + /// ``` + fn from_reader(mut reader: R) -> std::io::Result { + match reader.read_byte()? { + 0x01 => { + // might be old format, look for option discriminant + match reader.read_byte()? { + 0x00 => { + // serde encoded `Option::None` as 0 with no following data + Ok(Self { + next_free_block: None, + }) + } + 0x01 => { + // encoded `Some(_)` as 1 with the data following + let addr = LinearAddress::new(reader.read_varint()?).ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + "Option:: was Some(0) which is invalid", + ) + })?; + Ok(Self { + next_free_block: Some(addr), + }) + } + option_discriminant => Err(Error::new( + ErrorKind::InvalidData, + format!("Invalid Option discriminant: {option_discriminant}"), + )), + } + } + 0xFF => { + // new format: read the address directly (zero is allowed here to indicate None) + Ok(Self { + next_free_block: LinearAddress::new(reader.read_varint()?), + }) + } + first_byte => Err(Error::new( + ErrorKind::InvalidData, + format!( + "Invalid FreeArea marker, expected 0xFF (or 0x01 for old format), found {first_byte:#02x}" + ), + )), + } + } +} + impl FreeArea { /// Create a new `FreeArea` pub const fn new(next_free_block: Option) -> Self { @@ -206,34 +262,34 @@ impl FreeArea { pub const fn next_free_block(self) -> Option { self.next_free_block } -} -impl FreeArea { pub fn from_storage( storage: &S, address: LinearAddress, ) -> Result<(Self, AreaIndex), FileIoError> { let free_area_addr = address.get(); let stored_area_stream = storage.stream_from(free_area_addr)?; - let stored_area: StoredArea> = serializer() - .deserialize_from(stored_area_stream) - .map_err(|e| { - storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - free_area_addr, - Some("FreeArea::from_storage".to_string()), - ) - })?; - let (stored_area_index, area) = stored_area.into_parts(); - let Area::Free(free_area) = area else { - return Err(storage.file_io_error( - Error::new(ErrorKind::InvalidData, "Attempted to read a non-free area"), + Self::from_storage_reader(stored_area_stream).map_err(|e| { + storage.file_io_error( + e, free_area_addr, Some("FreeArea::from_storage".to_string()), - )); - }; + ) + }) + } + + pub fn as_bytes(self, area_index: AreaIndex, encoded: &mut T) { + const RESERVE_SIZE: usize = size_of::() + var_int_max_size::(); + + encoded.reserve(RESERVE_SIZE); + encoded.push(area_index); + self.write_to(encoded); + } - Ok((free_area, stored_area_index as AreaIndex)) + fn from_storage_reader(mut reader: impl Read) -> std::io::Result<(Self, AreaIndex)> { + let area_index = reader.read_byte()?; + let free_area = reader.next_value()?; + Ok((free_area, area_index)) } } @@ -253,15 +309,13 @@ impl NodeStore { ) -> Result<(AreaIndex, u64), FileIoError> { let mut area_stream = self.storage.stream_from(addr.get())?; - let index: AreaIndex = serializer() - .deserialize_from(&mut area_stream) - .map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("deserialize".to_string()), - ) - })?; + let index: AreaIndex = area_stream.read_byte().map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("deserialize".to_string()), + ) + })?; let size = *AREA_SIZES .get(index as usize) @@ -490,19 +544,9 @@ impl NodeStore { .increment(AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. - let area: Area = Area::Free(FreeArea::new( - self.header.free_lists()[area_size_index as usize], - )); - - let stored_area = StoredArea::new(area_size_index, area); - - let stored_area_bytes = serializer().serialize(&stored_area).map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("delete_node".to_string()), - ) - })?; + let mut stored_area_bytes = Vec::new(); + FreeArea::new(self.header.free_lists()[area_size_index as usize]) + .as_bytes(area_size_index, &mut stored_area_bytes); self.storage.write(addr.into(), &stored_area_bytes)?; @@ -600,7 +644,6 @@ pub mod test_utils { use super::*; use crate::FileBacked; use crate::node::Node; - use bincode::Options; // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. pub fn test_write_new_node( @@ -626,9 +669,8 @@ pub mod test_utils { area_size_index: AreaIndex, offset: u64, ) { - let area: Area = Area::Free(FreeArea::new(next_free_block)); - let stored_area = StoredArea::new(area_size_index, area); - let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); + let mut stored_area_bytes = Vec::new(); + FreeArea::new(next_free_block).as_bytes(area_size_index, &mut stored_area_bytes); nodestore.storage.write(offset, &stored_area_bytes).unwrap(); } @@ -651,13 +693,13 @@ pub mod test_utils { #[cfg(test)] #[expect(clippy::unwrap_used, clippy::indexing_slicing)] -mod test_free_list_iterator { +mod tests { use super::*; use crate::linear::memory::MemStore; use crate::test_utils::seeded_rng; - use rand::Rng; use rand::seq::IteratorRandom; + use test_case::test_case; // Simple helper function for this test - just writes to storage directly fn write_free_area_to_storage( @@ -666,12 +708,24 @@ mod test_free_list_iterator { area_size_index: AreaIndex, offset: u64, ) { - let area: Area = Area::Free(FreeArea::new(next_free_block)); - let stored_area = StoredArea::new(area_size_index, area); - let stored_area_bytes = serializer().serialize(&stored_area).unwrap(); + let mut stored_area_bytes = Vec::new(); + FreeArea::new(next_free_block).as_bytes(area_size_index, &mut stored_area_bytes); storage.write(offset, &stored_area_bytes).unwrap(); } + // StoredArea::new(12, Area::::Free(FreeArea::new(LinearAddress::new(42)))); + #[test_case(&[0x01, 0x01, 0x01, 0x2a], Some((1, 42)); "old format")] + // StoredArea::new(12, Area::::Free(FreeArea::new(None))); + #[test_case(&[0x02, 0x01, 0x00], Some((2, 0)); "none")] + #[test_case(&[0x03, 0xff, 0x2b], Some((3, 43)); "new format")] + #[test_case(&[0x03, 0x44, 0x55], None; "garbage")] + fn test_free_list_format(reader: &[u8], expected: Option<(AreaIndex, u64)>) { + let expected = + expected.map(|(index, addr)| (FreeArea::new(LinearAddress::new(addr)), index)); + let result = FreeArea::from_storage_reader(reader).ok(); + assert_eq!(result, expected, "Failed to parse FreeArea from {reader:?}"); + } + #[test] fn free_list_iterator() { let mut rng = seeded_rng(); diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index 0b07155405af..f4c644e62f2d 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -25,7 +25,6 @@ //! use bytemuck_derive::{AnyBitPattern, NoUninit}; -use serde::{Deserialize, Serialize}; use std::io::{Error, ErrorKind}; use super::alloc::{FreeLists, LinearAddress, area_size_hash}; @@ -33,7 +32,7 @@ use crate::logger::{debug, trace}; /// Can be used by filesystem tooling such as "file" to identify /// the version of firewood used to create this `NodeStore` file. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, NoUninit, AnyBitPattern)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit, AnyBitPattern)] #[repr(transparent)] pub struct Version { bytes: [u8; 16], @@ -149,7 +148,7 @@ impl Version { /// Persisted metadata for a `NodeStore`. /// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. -#[derive(Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, NoUninit, AnyBitPattern)] +#[derive(Copy, Debug, PartialEq, Eq, Clone, NoUninit, AnyBitPattern)] #[repr(C)] pub struct NodeStoreHeader { /// Identifies the version of firewood used to create this `NodeStore`. diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index fb1d2c6679d5..bd4ee988ce27 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -1,15 +1,11 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt::{self, Debug, Display, Formatter}; - -use serde::de::Visitor; -use serde::{Deserialize, Serialize}; -use sha2::digest::generic_array::GenericArray; -use sha2::digest::typenum; - use crate::node::ExtendableBytes; use crate::node::branch::Serializable; +use sha2::digest::generic_array::GenericArray; +use sha2::digest::typenum; +use std::fmt::{self, Debug, Display, Formatter}; /// A hash value inside a merkle trie /// We use the same type as returned by sha2 here to avoid copies @@ -98,44 +94,3 @@ impl Serializable for TrieHash { Ok(TrieHash::from(buf)) } } - -impl Serialize for TrieHash { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(&self.0) - } -} - -impl<'de> Deserialize<'de> for TrieHash { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_bytes(TrieVisitor) - } -} - -struct TrieVisitor; - -impl Visitor<'_> for TrieVisitor { - type Value = TrieHash; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an array of u8 hash bytes") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - let mut hash = TrieHash::default(); - if v.len() == hash.0.len() { - hash.0.copy_from_slice(v); - Ok(hash) - } else { - Err(E::invalid_length(v.len(), &self)) - } - } -} From 89bdaba1afbb073ba4ae07bf38a076ff46ad21f2 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:22:54 -0500 Subject: [PATCH 0823/1053] feat(checker): split leaked ranges into valid areas (#1059) Add functions to split leaked ranges into areas that can be added to free lists. This concludes this diff: #1033. --- storage/src/checker.rs | 239 ++++++++++++++++++++++++++++++++- storage/src/nodestore/alloc.rs | 10 ++ storage/src/nodestore/mod.rs | 14 ++ 3 files changed, 262 insertions(+), 1 deletion(-) diff --git a/storage/src/checker.rs b/storage/src/checker.rs index f49cc270e24b..cf8fd1633017 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker.rs @@ -2,12 +2,16 @@ // See the file LICENSE.md for licensing terms. use crate::logger::warn; +use crate::nodestore::alloc::{AREA_SIZES, AreaIndex}; use crate::range_set::LinearAddressRangeSet; use crate::{ CheckerError, Committed, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, WritableStorage, }; +use std::cmp::Ordering; +use std::ops::Range; + /// [`NodeStore`] checker // TODO: S needs to be writeable if we ask checker to fix the issues impl NodeStore { @@ -51,6 +55,8 @@ impl NodeStore { if !leaked_ranges.is_empty() { warn!("Found leaked ranges: {leaked_ranges}"); } + let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges); + // TODO: add leaked areas to the free list Ok(()) } @@ -91,6 +97,81 @@ impl NodeStore { } Ok(()) } + + /// Wrapper around `split_into_leaked_areas` that iterates over a collection of ranges. + fn split_all_leaked_ranges( + &self, + leaked_ranges: impl IntoIterator>, + ) -> impl Iterator { + leaked_ranges + .into_iter() + .flat_map(|range| self.split_range_into_leaked_areas(range)) + } + + /// Split a range of addresses into leaked areas that can be stored in the free list. + /// We assume that all space within `leaked_range` are leaked and the stored areas are contiguous. + /// Returns error if the last leaked area extends beyond the end of the range. + fn split_range_into_leaked_areas( + &self, + leaked_range: Range, + ) -> Vec<(LinearAddress, AreaIndex)> { + let mut leaked = Vec::new(); + let mut current_addr = leaked_range.start; + + // First attempt to read the valid stored areas from the leaked range + loop { + let (area_index, area_size) = match self.read_leaked_area(current_addr) { + Ok(area_index_and_size) => area_index_and_size, + Err(e) => { + warn!("Error reading stored area at {current_addr}: {e}"); + break; + } + }; + + let next_addr = current_addr + .checked_add(area_size) + .expect("address overflow is impossible"); + match next_addr.cmp(&leaked_range.end) { + Ordering::Equal => { + // we have reached the end of the leaked area, done + leaked.push((current_addr, area_index)); + return leaked; + } + Ordering::Greater => { + // the last area extends beyond the leaked area - this means the leaked area overlaps with an area we have visited + warn!( + "Leaked area extends beyond {leaked_range:?}: {current_addr} -> {next_addr}" + ); + break; + } + Ordering::Less => { + // continue to the next area + leaked.push((current_addr, area_index)); + current_addr = next_addr; + } + } + } + + // We encountered an error, split the rest of the leaked range into areas using heuristics + // The heuristic is to split the leaked range into areas of the largest size possible - we assume `AREA_SIZE` is in ascending order + for (area_index, area_size) in AREA_SIZES.iter().enumerate().rev() { + loop { + let next_addr = current_addr + .checked_add(*area_size) + .expect("address overflow is impossible"); + if next_addr <= leaked_range.end { + leaked.push((current_addr, area_index as AreaIndex)); + current_addr = next_addr; + } else { + break; + } + } + } + + // we assume that all areas are aligned to `MIN_AREA_SIZE`, in which case leaked ranges can always be split into free areas perfectly + debug_assert!(current_addr == leaked_range.end); + leaked + } } #[cfg(test)] @@ -102,11 +183,14 @@ mod test { use crate::linear::memory::MemStore; use crate::nodestore::NodeStoreHeader; use crate::nodestore::alloc::test_utils::{ - test_write_free_area, test_write_header, test_write_new_node, + test_write_free_area, test_write_header, test_write_new_node, test_write_zeroed_area, }; use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; + use nonzero_ext::nonzero; + use std::collections::HashMap; + #[test] // This test creates a simple trie and checks that the checker traverses it correctly. // We use primitive calls here to do a low-level check. @@ -213,4 +297,157 @@ mod test { let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); } + + #[test] + // This test creates a linear set of free areas and free them. + // When traversing it should break consecutive areas. + fn split_correct_range_into_leaked_areas() { + use rand::Rng; + use rand::seq::IteratorRandom; + + let mut rng = crate::test_utils::seeded_rng(); + + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let num_areas = 10; + + // randomly insert areas into the nodestore + let mut high_watermark = NodeStoreHeader::SIZE; + let mut stored_areas = Vec::new(); + for _ in 0..num_areas { + let (area_size_index, area_size) = + AREA_SIZES.iter().enumerate().choose(&mut rng).unwrap(); + let area_addr = LinearAddress::new(high_watermark).unwrap(); + test_write_free_area( + &nodestore, + None, + area_size_index as AreaIndex, + high_watermark, + ); + stored_areas.push((area_addr, area_size_index as AreaIndex, *area_size)); + high_watermark += *area_size; + } + test_write_header(&mut nodestore, high_watermark, None, FreeLists::default()); + + // randomly pick some areas as leaked + let mut leaked = Vec::new(); + let mut expected_free_areas = HashMap::new(); + let mut area_to_free = None; + for (area_start, area_size_index, area_size) in &stored_areas { + if rng.random::() < 0.5 { + // we free this area + expected_free_areas.insert(*area_start, *area_size_index); + let area_to_free_start = area_to_free.map_or(*area_start, |(start, _)| start); + let area_to_free_end = area_start.checked_add(*area_size).unwrap(); + area_to_free = Some((area_to_free_start, area_to_free_end)); + } else { + // we are not freeing this area, free the aggregated areas before this one + if let Some((start, end)) = area_to_free { + leaked.push(start..end); + area_to_free = None; + } + } + } + if let Some((start, end)) = area_to_free { + leaked.push(start..end); + } + + // check the leaked areas + let leaked_areas: HashMap<_, _> = nodestore.split_all_leaked_ranges(leaked).collect(); + + // assert that all leaked areas end up on the free list + assert_eq!(leaked_areas, expected_free_areas); + } + + #[test] + // This test creates a linear set of free areas and free them. + // When traversing it should break consecutive areas. + fn split_range_of_zeros_into_leaked_areas() { + let memstore = MemStore::new(vec![]); + let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let expected_leaked_area_indices = vec![8u8, 7, 4, 2, 0]; + let expected_leaked_area_sizes = expected_leaked_area_indices + .iter() + .map(|i| AREA_SIZES[*i as usize]) + .collect::>(); + assert_eq!(expected_leaked_area_sizes, vec![1024, 768, 128, 64, 16]); + let expected_offsets = expected_leaked_area_sizes + .iter() + .scan(0, |acc, i| { + let offset = *acc; + *acc += i; + LinearAddress::new(NodeStoreHeader::SIZE + offset) + }) + .collect::>(); + + // write an zeroed area + let leaked_range_size = expected_leaked_area_sizes.iter().sum(); + test_write_zeroed_area(&nodestore, leaked_range_size, NodeStoreHeader::SIZE); + + // check the leaked areas + let leaked_range = nonzero!(NodeStoreHeader::SIZE) + ..LinearAddress::new( + NodeStoreHeader::SIZE + .checked_add(leaked_range_size) + .unwrap(), + ) + .unwrap(); + let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = + nodestore + .split_range_into_leaked_areas(leaked_range) + .into_iter() + .unzip(); + + // assert that all leaked areas end up on the free list + assert_eq!(leaked_areas_offsets, expected_offsets); + assert_eq!(leaked_area_size_indices, expected_leaked_area_indices); + } + + #[test] + // With both valid and invalid areas in the range, return the valid areas until reaching one invalid area, then use heuristics to split the rest of the range. + fn split_range_into_leaked_areas_test() { + let memstore = MemStore::new(vec![]); + let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + // write two free areas + let mut high_watermark = NodeStoreHeader::SIZE; + test_write_free_area(&nodestore, None, 8, high_watermark); // 1024 + high_watermark += AREA_SIZES[8]; + test_write_free_area(&nodestore, None, 7, high_watermark); // 768 + high_watermark += AREA_SIZES[7]; + // write an zeroed area + test_write_zeroed_area(&nodestore, 768, high_watermark); + high_watermark += 768; + // write another free area + test_write_free_area(&nodestore, None, 8, high_watermark); // 1024 + high_watermark += AREA_SIZES[8]; + + let expected_indices = vec![8, 7, 8, 7]; + let expected_sizes = expected_indices + .iter() + .map(|i| AREA_SIZES[*i as usize]) + .collect::>(); + let expected_offsets = expected_sizes + .iter() + .scan(0, |acc, i| { + let offset = *acc; + *acc += i; + LinearAddress::new(NodeStoreHeader::SIZE + offset) + }) + .collect::>(); + + // check the leaked areas + let leaked_range = + nonzero!(NodeStoreHeader::SIZE)..LinearAddress::new(high_watermark).unwrap(); + let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = + nodestore + .split_range_into_leaked_areas(leaked_range) + .into_iter() + .unzip(); + + assert_eq!(leaked_areas_offsets, expected_offsets); + assert_eq!(leaked_area_size_indices, expected_indices); + } } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index e37bd35e9b31..fec9677e4d3b 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -689,6 +689,16 @@ pub mod test_utils { nodestore.header = header; nodestore.storage.write(0, header_bytes).unwrap(); } + + // Helper function to write a random stored area to the given offset. + pub(crate) fn test_write_zeroed_area( + nodestore: &NodeStore, + size: u64, + offset: u64, + ) { + let area_content = vec![0u8; size as usize]; + nodestore.storage.write(offset, &area_content).unwrap(); + } } #[cfg(test)] diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index c23ea383e7bb..eddadf42ee7d 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -719,6 +719,20 @@ impl NodeStore { pub(crate) fn physical_size(&self) -> Result { self.storage.size() } + + // Find the area index and size of the stored area at the given address if the area is valid. + // TODO: there should be a way to read stored area directly instead of try reading as a free area then as a node + pub(crate) fn read_leaked_area( + &self, + address: LinearAddress, + ) -> Result<(AreaIndex, u64), FileIoError> { + if alloc::FreeArea::from_storage(self.storage.as_ref(), address).is_err() { + self.read_node(address)?; + } + + let area_index_and_size = self.area_index_and_size(address)?; + Ok(area_index_and_size) + } } #[cfg(test)] From 639df29cfc3130d9e99e1a024a48ed3e31720882 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:45:03 -0500 Subject: [PATCH 0824/1053] feat(checker): check for misaligned stored areas (#1046) This PR adds checks for stored area misalignment. We only focus on areas that are not leaked (areas tracked in the trie or in the free list). As long as the areas not leaked are aligned, leaked areas will be aligned. When a misaligned area is identified, we return not only the address of this area but also who points to that area directly. If this area is allocated for a trie node, the root or the parent trie node will be returned. If this area is free, the free list head or the parent of this free area is returned. We return the parent because, if we were to fix the misalignment issue (by migrating to another area or remove it from the free list), the parent pointer needs to be updated accordingly. As a side effect, `FreeListIterator` will return the parent of the free list item. This design can help checker fix various issues it sees when traversing the free list but will add some overhead if it is used for other purposes. --- storage/Cargo.toml | 2 +- storage/src/{checker.rs => checker/mod.rs} | 53 +++- storage/src/{ => checker}/range_set.rs | 0 storage/src/lib.rs | 40 ++- storage/src/nodestore/alloc.rs | 303 +++++++++++++++++---- storage/src/nodestore/mod.rs | 18 ++ 6 files changed, 346 insertions(+), 70 deletions(-) rename storage/src/{checker.rs => checker/mod.rs} (91%) rename storage/src/{ => checker}/range_set.rs (100%) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index af3b56a81781..12710b271d9f 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -39,6 +39,7 @@ sha3 = { version = "0.10.8", optional = true } bytes = { version = "1.10.1", optional = true } thiserror = { workspace = true } semver = "1.0.26" +nonzero_ext = "0.3.0" [dev-dependencies] rand = { workspace = true } @@ -46,7 +47,6 @@ test-case = "3.3.1" criterion = { workspace = true, features = ["async_tokio", "html_reports"] } pprof = { workspace = true, features = ["flamegraph"] } tempfile = { workspace = true } -nonzero_ext = "0.3.0" [features] logger = ["log"] diff --git a/storage/src/checker.rs b/storage/src/checker/mod.rs similarity index 91% rename from storage/src/checker.rs rename to storage/src/checker/mod.rs index cf8fd1633017..0a3f488ead1b 100644 --- a/storage/src/checker.rs +++ b/storage/src/checker/mod.rs @@ -1,12 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +mod range_set; +use range_set::LinearAddressRangeSet; + use crate::logger::warn; -use crate::nodestore::alloc::{AREA_SIZES, AreaIndex}; -use crate::range_set::LinearAddressRangeSet; +use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata}; +use crate::nodestore::is_aligned; use crate::{ CheckerError, Committed, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, - WritableStorage, + StoredAreaParent, TrieNodeParent, WritableStorage, }; use std::cmp::Ordering; @@ -44,6 +47,10 @@ impl NodeStore { // 2. traverse the trie and check the nodes if let Some(root_address) = self.root_address() { // the database is not empty, traverse the trie + self.check_area_aligned( + root_address, + StoredAreaParent::TrieNode(TrieNodeParent::Root), + )?; self.visit_trie(root_address, &mut visited)?; } @@ -72,7 +79,14 @@ impl NodeStore { if let Node::Branch(branch) = self.read_node(subtree_root_address)?.as_ref() { // this is an internal node, traverse the children - for (_, address) in branch.children_addresses() { + for (child_idx, address) in branch.children_addresses() { + self.check_area_aligned( + address, + StoredAreaParent::TrieNode(TrieNodeParent::Parent( + subtree_root_address, + child_idx, + )), + )?; self.visit_trie(address, visited)?; } } @@ -82,15 +96,22 @@ impl NodeStore { /// Traverse all the free areas in the freelist fn visit_freelist(&self, visited: &mut LinearAddressRangeSet) -> Result<(), CheckerError> { - for free_area in self.free_list_iter_inner(0) { - let (addr, stored_area_index, free_list_id) = free_area?; - let area_size = Self::size_from_area_index(stored_area_index); - if free_list_id != stored_area_index { + let mut free_list_iter = self.free_list_iter(0); + while let Some(free_area) = free_list_iter.next_with_metadata() { + let FreeAreaWithMetadata { + addr, + area_index, + free_list_id, + parent, + } = free_area?; + self.check_area_aligned(addr, StoredAreaParent::FreeList(parent))?; + let area_size = Self::size_from_area_index(area_index); + if free_list_id != area_index { return Err(CheckerError::FreelistAreaSizeMismatch { address: addr, size: area_size, actual_free_list: free_list_id, - expected_free_list: stored_area_index, + expected_free_list: area_index, }); } visited.insert_area(addr, area_size)?; @@ -98,6 +119,20 @@ impl NodeStore { Ok(()) } + const fn check_area_aligned( + &self, + address: LinearAddress, + parent_ptr: StoredAreaParent, + ) -> Result<(), CheckerError> { + if !is_aligned(address) { + return Err(CheckerError::AreaMisaligned { + address, + parent_ptr, + }); + } + Ok(()) + } + /// Wrapper around `split_into_leaked_areas` that iterates over a collection of ranges. fn split_all_leaked_ranges( &self, diff --git a/storage/src/range_set.rs b/storage/src/checker/range_set.rs similarity index 100% rename from storage/src/range_set.rs rename to storage/src/checker/range_set.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index dba0eebb5838..9ea36cecb69e 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -29,7 +29,6 @@ mod iter; mod linear; mod node; mod nodestore; -mod range_set; mod trie_hash; use crate::nodestore::AreaIndex; @@ -99,6 +98,33 @@ pub fn empty_trie_hash() -> TrieHash { .expect("empty trie hash is 32 bytes") } +/// This enum encapsulates what points to the stored area. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StoredAreaParent { + /// The stored area is a trie node + TrieNode(TrieNodeParent), + /// The stored area is a free list + FreeList(FreeListParent), +} + +/// This enum encapsulates what points to the stored area allocated for a trie node. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TrieNodeParent { + /// The stored area is the root of the trie, so the header points to it + Root, + /// The stored area is not the root of the trie, so a parent trie node points to it + Parent(LinearAddress, usize), +} + +/// This enum encapsulates what points to the stored area allocated for a free list. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FreeListParent { + /// The stored area is the head of the free list, so the header points to it + FreeListHead(AreaIndex), + /// The stored area is not the head of the free list, so a previous free area points to it + PrevFreeArea(LinearAddress), +} + /// Errors returned by the checker #[derive(Error, Debug)] #[non_exhaustive] @@ -151,6 +177,18 @@ pub enum CheckerError { expected_free_list: AreaIndex, }, + /// The start address of a stored area is not a multiple of 16 + #[error( + "The start address of a stored area is not a multiple of {}: {address} (parent: {parent_ptr:?})", + nodestore::alloc::MIN_AREA_SIZE + )] + AreaMisaligned { + /// The start address of the stored area + address: LinearAddress, + /// The start address of the parent that points to the stored area + parent_ptr: StoredAreaParent, + }, + /// Found leaked areas #[error("Found leaked areas: {0:?}")] AreaLeaks(Vec>), diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index fec9677e4d3b..b2a9cbc00624 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -23,6 +23,7 @@ use crate::linear::FileIoError; use crate::logger::trace; use crate::node::branch::{ReadSerializable, Serializable}; +use crate::nodestore::is_aligned; use integer_encoding::VarIntReader; use metrics::counter; use sha2::{Digest, Sha256}; @@ -33,7 +34,7 @@ use std::sync::Arc; use crate::node::persist::MaybePersistedNode; use crate::node::{ByteCounter, ExtendableBytes, Node}; -use crate::{CacheReadStrategy, ReadableStorage, SharedNode, TrieHash}; +use crate::{CacheReadStrategy, FreeListParent, ReadableStorage, SharedNode, TrieHash}; use crate::linear::WritableStorage; @@ -343,7 +344,7 @@ impl NodeStore { return Ok(node); } - debug_assert!(addr.get() % 8 == 0); + debug_assert!(is_aligned(addr)); // saturating because there is no way we can be reading at u64::MAX // and this will fail very soon afterwards @@ -483,7 +484,7 @@ impl NodeStore, S> { let addr = LinearAddress::new(self.header.size()).expect("node store size can't be 0"); self.header .set_size(self.header.size().saturating_add(area_size)); - debug_assert!(addr.get() % 8 == 0); + debug_assert!(is_aligned(addr)); trace!("Allocating from end: addr: {addr:?}, size: {index}"); Ok((addr, index)) } @@ -534,7 +535,7 @@ impl NodeStore { let Some(addr) = node.as_linear_address() else { return Ok(()); }; - debug_assert!(addr.get() % 8 == 0); + debug_assert!(is_aligned(addr)); let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {area_size_index}"); @@ -561,14 +562,35 @@ impl NodeStore { } /// Iterator over free lists in the nodestore -pub struct FreeListIterator<'a, S: ReadableStorage> { +struct FreeListIterator<'a, S: ReadableStorage> { storage: &'a S, next_addr: Option, + parent: FreeListParent, } impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { - pub const fn new(storage: &'a S, next_addr: Option) -> Self { - Self { storage, next_addr } + const fn new( + storage: &'a S, + next_addr: Option, + src_ptr: FreeListParent, + ) -> Self { + Self { + storage, + next_addr, + parent: src_ptr, + } + } + + #[allow( + clippy::type_complexity, + reason = "this iterator is private and will not be exposed" + )] + fn next_with_parent( + &mut self, + ) -> Option> { + let parent = self.parent.clone(); + let next_addr = self.next()?; + Some(next_addr.map(|free_area| (free_area, parent))) } } @@ -589,6 +611,7 @@ impl Iterator for FreeListIterator<'_, S> { }; // update the next address to the next free block + self.parent = FreeListParent::PrevFreeArea(next_addr); self.next_addr = free_area.next_free_block(); Some(Ok((next_addr, stored_area_index))) } @@ -596,54 +619,117 @@ impl Iterator for FreeListIterator<'_, S> { impl FusedIterator for FreeListIterator<'_, S> {} -/// Extension methods for `NodeStore` to provide free list iteration capabilities -impl NodeStore { - /// Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. - /// The iterator returns a tuple of the address and the area index of the free area. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if a free area cannot be read from storage. - pub fn free_list_iter( - &self, - start_area_index: AreaIndex, - ) -> impl Iterator> { - self.free_list_iter_inner(start_area_index) - .map(|item| item.map(|(addr, area_index, _)| (addr, area_index))) - } +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct FreeAreaWithMetadata { + pub addr: LinearAddress, + pub area_index: AreaIndex, + pub free_list_id: AreaIndex, + pub parent: FreeListParent, +} - /// Returns an iterator over the free lists with detailed information for verification. - /// - /// This is a low-level iterator used by the checker to verify that free areas are in the correct free list. - /// Returns tuples of (address, `area_index`, `free_list_id`) for performance optimization. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if a free area cannot be read from storage. - pub fn free_list_iter_inner( - &self, +pub(crate) struct FreeListsIterator<'a, S: ReadableStorage> { + storage: &'a S, + free_lists_iter: std::iter::Skip< + std::iter::Enumerate>>>, + >, + current_free_list_id: AreaIndex, + free_list_iter: FreeListIterator<'a, S>, +} + +impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { + pub(crate) fn new( + storage: &'a S, + free_lists: &'a FreeLists, start_area_index: AreaIndex, - ) -> impl Iterator> { - self.header - .free_lists() + ) -> Self { + let mut free_lists_iter = free_lists .iter() .enumerate() - .skip(start_area_index as usize) - .flat_map(move |(free_list_id, next_addr)| { - FreeListIterator::new(self.storage.as_ref(), *next_addr).map(move |item| { - item.map(|(addr, area_index)| (addr, area_index, free_list_id as AreaIndex)) + .skip(start_area_index as usize); + let (current_free_list_id, free_list_head) = match free_lists_iter.next() { + Some((id, head)) => (id as AreaIndex, *head), + None => (NUM_AREA_SIZES as AreaIndex, None), + }; + let start_iterator = FreeListIterator::new( + storage, + free_list_head, + FreeListParent::FreeListHead(current_free_list_id), + ); + Self { + storage, + free_lists_iter, + current_free_list_id, + free_list_iter: start_iterator, + } + } + + pub(crate) fn next_with_metadata( + &mut self, + ) -> Option> { + self.next_inner(FreeListIterator::next_with_parent) + .map(|next_with_parent| { + next_with_parent.map(|((addr, area_index), parent)| FreeAreaWithMetadata { + addr, + area_index, + free_list_id: self.current_free_list_id, + parent, }) }) } + + fn next_inner) -> Option>( + &mut self, + mut next_fn: F, + ) -> Option { + loop { + if let Some(next) = next_fn(&mut self.free_list_iter) { + // the current free list is not exhausted, return the next free area + return Some(next); + } + + match self.free_lists_iter.next() { + Some((current_free_list_id, next_free_list_head)) => { + self.current_free_list_id = current_free_list_id as AreaIndex; + self.free_list_iter = FreeListIterator::new( + self.storage, + *next_free_list_head, + FreeListParent::FreeListHead(self.current_free_list_id), + ); + } + None => { + // no more free lists to iterate over + return None; + } + } + } + } +} + +impl Iterator for FreeListsIterator<'_, S> { + type Item = Result<(LinearAddress, AreaIndex), FileIoError>; + + fn next(&mut self) -> Option { + self.next_inner(FreeListIterator::next) + } +} + +/// Extension methods for `NodeStore` to provide free list iteration capabilities +impl NodeStore { + // Returns an iterator over the free lists of size no smaller than the size corresponding to `start_area_index`. + // The iterator returns a tuple of the address and the area index of the free area. + // Since this is a low-level iterator, we avoid safe conversion to AreaIndex for performance + pub(crate) fn free_list_iter(&self, start_area_index: AreaIndex) -> FreeListsIterator<'_, S> { + FreeListsIterator::new(self.storage.as_ref(), self.freelists(), start_area_index) + } } #[cfg(test)] #[expect(clippy::unwrap_used, clippy::indexing_slicing)] pub mod test_utils { - use super::super::{Committed, ImmutableProposal, NodeStore, NodeStoreHeader}; use super::*; use crate::FileBacked; use crate::node::Node; + use crate::nodestore::{Committed, ImmutableProposal, NodeStore, NodeStoreHeader}; // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. pub fn test_write_new_node( @@ -705,24 +791,14 @@ pub mod test_utils { #[expect(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { use super::*; + use crate::linear::memory::MemStore; + use crate::nodestore::header::NodeStoreHeader; use crate::test_utils::seeded_rng; use rand::Rng; use rand::seq::IteratorRandom; use test_case::test_case; - // Simple helper function for this test - just writes to storage directly - fn write_free_area_to_storage( - storage: &S, - next_free_block: Option, - area_size_index: AreaIndex, - offset: u64, - ) { - let mut stored_area_bytes = Vec::new(); - FreeArea::new(next_free_block).as_bytes(area_size_index, &mut stored_area_bytes); - storage.write(offset, &stored_area_bytes).unwrap(); - } - // StoredArea::new(12, Area::::Free(FreeArea::new(LinearAddress::new(42)))); #[test_case(&[0x01, 0x01, 0x01, 0x2a], Some((1, 42)); "old format")] // StoredArea::new(12, Area::::Free(FreeArea::new(None))); @@ -737,10 +813,11 @@ mod tests { } #[test] + // Create a random free list and test that `FreeListIterator` is able to traverse all the free areas fn free_list_iterator() { let mut rng = seeded_rng(); let memstore = MemStore::new(vec![]); - let storage = Arc::new(memstore); + let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); let area_index = rng.random_range(0..NUM_AREA_SIZES as u8); let area_size = AREA_SIZES[area_index as usize]; @@ -750,30 +827,138 @@ mod tests { .map(|i| i * area_size) .choose_multiple(&mut rng, 10); for (cur, next) in offsets.iter().zip(offsets.iter().skip(1)) { - write_free_area_to_storage( - storage.as_ref(), + test_utils::test_write_free_area( + &nodestore, Some(LinearAddress::new(*next).unwrap()), area_index, *cur, ); } - write_free_area_to_storage(storage.as_ref(), None, area_index, *offsets.last().unwrap()); + test_utils::test_write_free_area(&nodestore, None, area_index, *offsets.last().unwrap()); // test iterator from a random starting point let skip = rng.random_range(0..offsets.len()); let mut iterator = offsets.into_iter().skip(skip); let start = iterator.next().unwrap(); - let mut free_list_iter = FreeListIterator::new(storage.as_ref(), LinearAddress::new(start)); + let mut free_list_iter = FreeListIterator::new( + nodestore.storage.as_ref(), + LinearAddress::new(start), + FreeListParent::FreeListHead(area_index), + ); assert_eq!( free_list_iter.next().unwrap().unwrap(), (LinearAddress::new(start).unwrap(), area_index) ); for offset in iterator { - let next_item = free_list_iter.next().unwrap().unwrap(); - assert_eq!(next_item, (LinearAddress::new(offset).unwrap(), area_index)); + assert_eq!( + free_list_iter.next().unwrap().unwrap(), + (LinearAddress::new(offset).unwrap(), area_index) + ); } assert!(free_list_iter.next().is_none()); } + + // Create two free lists and check that `free_list_iter_with_metadata` correctly returns the free areas and their parents + #[test] + fn free_list_iter_with_metadata() { + let mut rng = seeded_rng(); + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let mut free_lists = FreeLists::default(); + let mut offset = NodeStoreHeader::SIZE; + + // first free list + let area_index1 = rng.random_range(0..NUM_AREA_SIZES as u8); + let area_size1 = AREA_SIZES[area_index1 as usize]; + let mut next_free_block1 = None; + + test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + let free_list1_area2 = LinearAddress::new(offset).unwrap(); + next_free_block1 = Some(free_list1_area2); + offset += area_size1; + + test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + let free_list1_area1 = LinearAddress::new(offset).unwrap(); + next_free_block1 = Some(free_list1_area1); + offset += area_size1; + + free_lists[area_index1 as usize] = next_free_block1; + + // second free list + let area_index2 = + (area_index1 + rng.random_range(1..NUM_AREA_SIZES as u8)) % NUM_AREA_SIZES as u8; // make sure the second free list is different from the first + assert_ne!(area_index1, area_index2); + let area_size2 = AREA_SIZES[area_index2 as usize]; + let mut next_free_block2 = None; + + test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + let free_list2_area2 = LinearAddress::new(offset).unwrap(); + next_free_block2 = Some(free_list2_area2); + offset += area_size2; + + test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + let free_list2_area1 = LinearAddress::new(offset).unwrap(); + next_free_block2 = Some(free_list2_area1); + offset += area_size2; + + free_lists[area_index2 as usize] = next_free_block2; + + // write header + test_utils::test_write_header(&mut nodestore, offset, None, free_lists); + + // test iterator + let mut free_list_iter = nodestore.free_list_iter(0); + + // expected + let expected_free_list1 = vec![ + FreeAreaWithMetadata { + addr: free_list1_area1, + area_index: area_index1, + free_list_id: area_index1, + parent: FreeListParent::FreeListHead(area_index1), + }, + FreeAreaWithMetadata { + addr: free_list1_area2, + area_index: area_index1, + free_list_id: area_index1, + parent: FreeListParent::PrevFreeArea(free_list1_area1), + }, + ]; + + let expected_free_list2 = vec![ + FreeAreaWithMetadata { + addr: free_list2_area1, + area_index: area_index2, + free_list_id: area_index2, + parent: FreeListParent::FreeListHead(area_index2), + }, + FreeAreaWithMetadata { + addr: free_list2_area2, + area_index: area_index2, + free_list_id: area_index2, + parent: FreeListParent::PrevFreeArea(free_list2_area1), + }, + ]; + + let mut expected_iterator = if area_index1 < area_index2 { + expected_free_list1.into_iter().chain(expected_free_list2) + } else { + expected_free_list2.into_iter().chain(expected_free_list1) + }; + + loop { + let next = free_list_iter.next_with_metadata(); + let expected = expected_iterator.next(); + + if expected.is_none() { + assert!(next.is_none()); + break; + } + + assert_eq!(next.unwrap().unwrap(), expected.unwrap()); + } + } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index eddadf42ee7d..46cf797433ab 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -92,6 +92,11 @@ use crate::{FileBacked, Path, ReadableStorage, SharedNode, TrieHash}; use super::linear::WritableStorage; +#[inline] +pub(crate) const fn is_aligned(addr: LinearAddress) -> bool { + addr.get() % alloc::MIN_AREA_SIZE == 0 +} + impl NodeStore { /// Open an existing [`NodeStore`] /// Assumes the header is written in the [`ReadableStorage`]. @@ -472,6 +477,12 @@ pub struct NodeStore { storage: Arc, } +impl NodeStore { + pub(crate) const fn freelists(&self) -> &alloc::FreeLists { + self.header.free_lists() + } +} + /// Contains the state of a proposal that is still being modified. #[derive(Debug)] pub struct MutableProposal { @@ -750,6 +761,13 @@ mod tests { use super::*; use alloc::{AREA_SIZES, MAX_AREA_SIZE, MIN_AREA_SIZE, NUM_AREA_SIZES, area_size_to_index}; + #[test] + fn area_sizes_aligned() { + for area_size in &AREA_SIZES { + assert_eq!(area_size % MIN_AREA_SIZE, 0); + } + } + #[test] fn test_area_size_to_index() { // TODO: rustify using: for size in AREA_SIZES From d72ec2f6d5b1f1615be7bbd96829e4f7dd7311e4 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:00:28 -0400 Subject: [PATCH 0825/1053] feat!: auto open or create with truncate (#1064) It's cumbersome (and more prone to TOCTOU issues) for users to validate whether a file exists, and if it does, set a flag. This should be handled internally. The `truncate` config now more closely resembles it's actual meaning. If the file already exists, it will be wiped and re-created. There are unit tests in Rust and in Go for this. Close #1039 --- ffi/firewood.go | 11 ++--- ffi/firewood.h | 28 ++---------- ffi/firewood_test.go | 43 ++++++++++++++----- ffi/src/lib.rs | 33 +++----------- ffi/tests/eth/eth_compatibility_test.go | 1 - .../firewood/merkle_compatibility_test.go | 2 - firewood/src/db.rs | 18 +++++++- firewood/src/manager.rs | 11 ++--- fwdctl/src/check.rs | 5 ++- storage/src/linear/filebacked.rs | 7 +-- storage/src/nodestore/mod.rs | 12 +++--- 11 files changed, 81 insertions(+), 90 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index ca1367334538..355081992dbd 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -54,7 +54,7 @@ type Database struct { // Config configures the opening of a [Database]. type Config struct { - Create bool + Truncate bool NodeCacheEntries uint FreeListCacheEntries uint Revisions uint @@ -109,18 +109,13 @@ func New(filePath string, conf *Config) (*Database, error) { free_list_cache_size: C.size_t(conf.FreeListCacheEntries), revisions: C.size_t(conf.Revisions), strategy: C.uint8_t(conf.ReadCacheStrategy), + truncate: C.bool(conf.Truncate), } // Defer freeing the C string allocated to the heap on the other side // of the FFI boundary. defer C.free(unsafe.Pointer(args.path)) - var dbResult C.struct_DatabaseCreationResult - if conf.Create { - dbResult = C.fwd_create_db(args) - } else { - dbResult = C.fwd_open_db(args) - } - + dbResult := C.fwd_open_db(args) db, err := databaseFromResult(&dbResult) if err != nil { return nil, err diff --git a/ffi/firewood.h b/ffi/firewood.h index fe2d4f336cf9..a40a333a8479 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -49,6 +49,8 @@ typedef struct DatabaseCreationResult { uint8_t *error_str; } DatabaseCreationResult; +typedef uint32_t ProposalId; + /** * Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. * @@ -59,6 +61,7 @@ typedef struct DatabaseCreationResult { * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. * * `strategy` - The cache read strategy to use, 0 for writes only, * 1 for branch reads, and 2 for all reads. + * * `truncate` - Whether to truncate the database file if it exists. * Returns an error if the value is not 0, 1, or 2. */ typedef struct CreateOrOpenArgs { @@ -67,10 +70,9 @@ typedef struct CreateOrOpenArgs { size_t free_list_cache_size; size_t revisions; uint8_t strategy; + bool truncate; } CreateOrOpenArgs; -typedef uint32_t ProposalId; - /** * Puts the given key-value pairs into the database. * @@ -147,28 +149,6 @@ void fwd_close_db(struct DatabaseHandle *db); */ struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); -/** - * Create a database with the given cache size and maximum number of revisions, as well - * as a specific cache strategy - * - * # Arguments - * - * See `CreateOrOpenArgs`. - * - * # Returns - * - * A database handle, or panics if it cannot be created - * - * # Safety - * - * This function uses raw pointers so it is unsafe. - * It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. - * The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. - * The caller must call `close` to free the memory associated with the returned database handle. - * - */ -struct DatabaseCreationResult fwd_create_db(struct CreateOrOpenArgs args); - /** * Drops a proposal from the database. * The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index c4bd54e68656..5da486d48739 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -134,7 +134,7 @@ func newTestDatabase(t *testing.T) *Database { func newDatabase(dbFile string) (*Database, func() error, error) { conf := DefaultConfig() - conf.Create = true + conf.Truncate = true // in tests, we use filepath.Join, which creates an empty file f, err := New(dbFile, conf) if err != nil { @@ -143,15 +143,6 @@ func newDatabase(dbFile string) (*Database, func() error, error) { return f, f.Close, nil } -func TestOpenNonexistentDatabase(t *testing.T) { - r := require.New(t) - cfg := DefaultConfig() - cfg.Create = false - db, err := New(filepath.Join(t.TempDir(), "test.db"), cfg) - r.ErrorContains(err, "File IO error") - r.Nil(db) -} - func TestUpdateSingleKV(t *testing.T) { r := require.New(t) db := newTestDatabase(t) @@ -178,6 +169,38 @@ func TestUpdateMultiKV(t *testing.T) { } } +func TestTruncateDatabase(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + // Create a new database with truncate enabled. + config := DefaultConfig() + config.Truncate = true + db, err := New(dbFile, config) + r.NoError(err) + + // Insert some data. + keys, vals := kvForTest(10) + _, err = db.Update(keys, vals) + r.NoError(err) + + // Close the database. + r.NoError(db.Close()) + + // Reopen the database with truncate enabled. + db, err = New(dbFile, config) + r.NoError(err) + + // Check that the database is empty after truncation. + hash, err := db.Root() + r.NoError(err) + emptyRootStr := expectedRoots[emptyKey] + expectedHash, err := hex.DecodeString(emptyRootStr) + r.NoError(err) + r.Equal(expectedHash, hash, "Root hash mismatch after truncation") + + r.NoError(db.Close()) +} + func TestClosedDatabase(t *testing.T) { r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 7bf8a85d992f..840f344eddb3 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -870,6 +870,7 @@ pub extern "C" fn fwd_gather() -> Value { /// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. /// * `strategy` - The cache read strategy to use, 0 for writes only, /// 1 for branch reads, and 2 for all reads. +/// * `truncate` - Whether to truncate the database file if it exists. /// Returns an error if the value is not 0, 1, or 2. #[repr(C)] pub struct CreateOrOpenArgs { @@ -878,29 +879,7 @@ pub struct CreateOrOpenArgs { free_list_cache_size: usize, revisions: usize, strategy: u8, -} - -/// Create a database with the given cache size and maximum number of revisions, as well -/// as a specific cache strategy -/// -/// # Arguments -/// -/// See `CreateOrOpenArgs`. -/// -/// # Returns -/// -/// A database handle, or panics if it cannot be created -/// -/// # Safety -/// -/// This function uses raw pointers so it is unsafe. -/// It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. -/// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. -/// The caller must call `close` to free the memory associated with the returned database handle. -/// -#[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> DatabaseCreationResult { - unsafe { common_create(&args, true) }.into() + truncate: bool, } /// Open a database with the given cache size and maximum number of revisions @@ -922,14 +901,14 @@ pub unsafe extern "C" fn fwd_create_db(args: CreateOrOpenArgs) -> DatabaseCreati /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> DatabaseCreationResult { - unsafe { common_create(&args, false) }.into() + unsafe { open_db(&args) }.into() } -/// Internal call for `fwd_create_db` and `fwd_open_db` to remove error handling from the C API +/// Internal call for `fwd_open_db` to remove error handling from the C API #[doc(hidden)] -unsafe fn common_create(args: &CreateOrOpenArgs, create_file: bool) -> Result { +unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { let cfg = DbConfig::builder() - .truncate(create_file) + .truncate(args.truncate) .manager(manager_config( args.cache_size, args.free_list_cache_size, diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index fc21f90054cc..cffa57595b2b 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -67,7 +67,6 @@ func newMerkleTriePair(t *testing.T) *merkleTriePair { file := path.Join(t.TempDir(), "test.db") cfg := firewood.DefaultConfig() - cfg.Create = true db, err := firewood.New(file, cfg) r.NoError(err) diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index 0c7c68e92c43..61f913f32c5c 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -60,8 +60,6 @@ func newTestFirewoodDatabase(t *testing.T) *firewood.Database { func newFirewoodDatabase(dbFile string) (*firewood.Database, func() error, error) { conf := firewood.DefaultConfig() - conf.Create = true - f, err := firewood.New(dbFile, conf) if err != nil { return nil, nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 15cc47632f89..7865e5d9434a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -529,6 +529,7 @@ mod test { #[tokio::test] async fn reopen_test() { let db = testdb().await; + let initial_root = db.root_hash().await.unwrap(); let batch = vec![ BatchOp::Put { key: b"a", @@ -548,6 +549,10 @@ mod test { let committed = db.root_hash().await.unwrap().unwrap(); let historical = db.revision(committed).await.unwrap(); assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); + + let db = db.replace().await; + println!("{:?}", db.root_hash().await.unwrap()); + assert!(db.root_hash().await.unwrap() == initial_root); } #[tokio::test] @@ -717,7 +722,7 @@ mod test { let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let dbconfig = DbConfig::builder().truncate(true).build(); + let dbconfig = DbConfig::builder().build(); let db = Db::new(dbpath, dbconfig).await.unwrap(); TestDb { db, tmpdir } } @@ -733,6 +738,17 @@ mod test { drop(self.db); let dbconfig = DbConfig::builder().truncate(false).build(); + let db = Db::new(path, dbconfig).await.unwrap(); + TestDb { + db, + tmpdir: self.tmpdir, + } + } + async fn replace(self) -> Self { + let path = self.path(); + drop(self.db); + let dbconfig = DbConfig::builder().truncate(true).build(); + let db = Db::new(path, dbconfig).await.unwrap(); TestDb { db, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 41d452754d0c..1b526ad3a14d 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -84,18 +84,15 @@ impl RevisionManager { truncate: bool, config: RevisionManagerConfig, ) -> Result { - let storage = Arc::new(FileBacked::new( + let fb = FileBacked::new( filename, config.node_cache_size, config.free_list_cache_size, truncate, config.cache_read_strategy, - )?); - let nodestore = if truncate { - Arc::new(NodeStore::new_empty_committed(storage.clone())?) - } else { - Arc::new(NodeStore::open(storage.clone())?) - }; + )?; + let storage = Arc::new(fb); + let nodestore = Arc::new(NodeStore::open(storage.clone())?); let manager = Self { max_revisions: config.max_revisions, historical: RwLock::new(VecDeque::from([nodestore.clone()])), diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index e92601bc50c8..b0a1892977cf 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -28,13 +28,14 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let node_cache_size = nonzero!(1usize); let free_list_cache_size = nonzero!(1usize); - let storage = Arc::new(FileBacked::new( + let fb = FileBacked::new( db_path, node_cache_size, free_list_cache_size, false, CacheReadStrategy::WritesOnly, // we scan the database once - no need to cache anything - )?); + )?; + let storage = Arc::new(fb); NodeStore::open(storage)?.check().map_err(Into::into) } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 5d0979ccd1c7..0a71c9f9cd5c 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -96,11 +96,11 @@ impl FileBacked { let fd = OpenOptions::new() .read(true) .write(true) - .create(true) .truncate(truncate) + .create(true) .open(&path) - .map_err(|inner| FileIoError { - inner, + .map_err(|e| FileIoError { + inner: e, filename: Some(path.clone()), offset: 0, context: Some("file open".to_string()), @@ -362,6 +362,7 @@ mod test { CacheReadStrategy::WritesOnly, ) .unwrap(); + let mut reader = fb.stream_from(0).unwrap(); let mut buf: String = String::new(); assert_eq!(reader.read_to_string(&mut buf).unwrap(), 11000); diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 46cf797433ab..2ac08698979b 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -45,7 +45,6 @@ pub(crate) mod hash; pub(crate) mod header; pub(crate) mod persist; -use crate::linear::FileIoError; use crate::logger::trace; use arc_swap::ArcSwap; use arc_swap::access::DynAccess; @@ -88,7 +87,7 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::Node; use crate::node::persist::MaybePersistedNode; -use crate::{FileBacked, Path, ReadableStorage, SharedNode, TrieHash}; +use crate::{FileBacked, FileIoError, Path, ReadableStorage, SharedNode, TrieHash}; use super::linear::WritableStorage; @@ -108,9 +107,12 @@ impl NodeStore { let mut stream = storage.stream_from(0)?; let mut header = NodeStoreHeader::new(); let header_bytes = bytemuck::bytes_of_mut(&mut header); - stream - .read_exact(header_bytes) - .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; + if let Err(e) = stream.read_exact(header_bytes) { + if e.kind() == std::io::ErrorKind::UnexpectedEof { + return Self::new_empty_committed(storage.clone()); + } + return Err(storage.file_io_error(e, 0, Some("header read".to_string()))); + } drop(stream); From 7d9be8a0a871b8c84022c2c92dc26bfba9087a06 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Jul 2025 11:19:43 -0700 Subject: [PATCH 0826/1053] feat(deferred-allocate): UnpersistedIterator (#1060) An UnpersistedIterator iterates through a NodeStore looking for unpersisted nodes. Unpersisted nodes are only of the type MaybeUnpersisted that are not yet persisted. Note that it's impossible to have a persisted parent for a MaybeUnpersistedNode, so it's relatively easy to find the unpersisted ones. There are tests that verify that the nodes are returned in the order that they will need persisting (that is, depth first). --------- Signed-off-by: Ron Kuris Co-authored-by: Suyan Qu <36519575+qusuyan@users.noreply.github.com> --- storage/src/node/branch.rs | 31 +++ storage/src/node/persist.rs | 13 ++ storage/src/nodestore/persist.rs | 323 ++++++++++++++++++++++++++++++- 3 files changed, 366 insertions(+), 1 deletion(-) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 6e31b696af2f..fe109dd65549 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -113,6 +113,17 @@ impl Child { } } + /// Return the unpersisted node if the child is an unpersisted [`Child::MaybePersisted`] + /// variant, otherwise None. + #[must_use] + pub fn unpersisted(&self) -> Option<&MaybePersistedNode> { + if let Child::MaybePersisted(maybe_persisted, _) = self { + maybe_persisted.unpersisted() + } else { + None + } + } + /// Return the hash of the child if it is a [`Child::AddressWithHash`] or [`Child::MaybePersisted`] variant, otherwise None. #[must_use] pub const fn hash(&self) -> Option<&HashType> { @@ -123,6 +134,26 @@ impl Child { } } + /// Return the persistence information (address and hash) of the child if it is persisted. + /// + /// This method returns `Some((address, hash))` for: + /// - [`Child::AddressWithHash`] variants (already persisted) + /// - [`Child::MaybePersisted`] variants that have been persisted + /// + /// Returns `None` for: + /// - [`Child::Node`] variants (unpersisted nodes) + /// - [`Child::MaybePersisted`] variants that are not yet persisted + #[must_use] + pub fn persist_info(&self) -> Option<(LinearAddress, &HashType)> { + match self { + Child::AddressWithHash(addr, hash) => Some((*addr, hash)), + Child::MaybePersisted(maybe_persisted, hash) => { + maybe_persisted.as_linear_address().map(|addr| (addr, hash)) + } + Child::Node(_) => None, + } + } + /// Return a `MaybePersistedNode` from a child /// /// This is used in the dump utility, but otherwise should be avoided, diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 4cad3679073a..ea265d82082d 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -108,6 +108,19 @@ impl MaybePersistedNode { } } + /// Returns a reference to the unpersisted node if it is unpersisted. + /// + /// # Returns + /// + /// Returns `Some(&Self)` if the node is unpersisted, otherwise `None`. + #[must_use] + pub fn unpersisted(&self) -> Option<&Self> { + match self.0.load().as_ref() { + MaybePersisted::Unpersisted(_) => Some(self), + MaybePersisted::Persisted(_) => None, + } + } + /// Updates the internal state to indicate this node is persisted at the specified disk address. /// /// This method changes the internal state of the `MaybePersistedNode` from `Mem` to `Disk`, diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index b503071bf664..835e51032ea3 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -27,16 +27,22 @@ //! - Memory-efficient serialization with pre-allocated buffers //! - Ring buffer management for io-uring operations +#[cfg(test)] +use std::iter::FusedIterator; +use std::sync::Arc; + use crate::linear::FileIoError; use coarsetime::Instant; use metrics::counter; -use std::sync::Arc; #[cfg(feature = "io-uring")] use crate::logger::trace; use crate::{FileBacked, WritableStorage}; +#[cfg(test)] +use crate::{MaybePersistedNode, NodeReader, RootReader}; + #[cfg(feature = "io-uring")] use crate::ReadableStorage; @@ -74,6 +80,115 @@ impl NodeStore { } } +/// Iterator that returns unpersisted nodes in depth first order. +/// +/// This iterator assumes the root node is unpersisted and will return it as the +/// last item. It looks at each node and traverses the children in depth first order. +/// A stack of child iterators is maintained to properly handle nested branches. +#[cfg(test)] +struct UnPersistedNodeIterator<'a, N> { + store: &'a N, + stack: Vec, + child_iter_stack: Vec + 'a>>, +} + +#[cfg(test)] +impl FusedIterator for UnPersistedNodeIterator<'_, N> {} + +#[cfg(test)] +impl<'a, N: NodeReader + RootReader> UnPersistedNodeIterator<'a, N> { + /// Creates a new iterator over unpersisted nodes in depth-first order. + fn new(store: &'a N) -> Self { + let root = store.root_as_maybe_persisted_node(); + + // we must have an unpersisted root node to use this iterator + // It's hard to tell at compile time if this is the case, so we assert it here + // TODO: can we use another trait or generic to enforce this? + debug_assert!(root.as_ref().is_none_or(|r| r.unpersisted().is_some())); + let (child_iter_stack, stack) = if let Some(root) = root { + if let Some(branch) = root + .as_shared_node(store) + .expect("in memory, so no io") + .as_branch() + { + // Create an iterator over unpersisted children + let unpersisted_children: Vec = branch + .children + .iter() + .filter_map(|child_opt| { + child_opt + .as_ref() + .and_then(|child| child.unpersisted().cloned()) + }) + .collect(); + + ( + vec![Box::new(unpersisted_children.into_iter()) + as Box + 'a>], + vec![root], + ) + } else { + // root is a leaf + (vec![], vec![root]) + } + } else { + (vec![], vec![]) + }; + + Self { + store, + stack, + child_iter_stack, + } + } +} + +#[cfg(test)] +impl Iterator for UnPersistedNodeIterator<'_, N> { + type Item = MaybePersistedNode; + + fn next(&mut self) -> Option { + // Try to get the next child from the current child iterator + while let Some(current_iter) = self.child_iter_stack.last_mut() { + if let Some(next_child) = current_iter.next() { + let shared_node = next_child + .as_shared_node(self.store) + .expect("in memory, so IO is impossible"); + + // It's a branch, so we need to get its children + if let Some(branch) = shared_node.as_branch() { + // Create an iterator over unpersisted children + let unpersisted_children: Vec = branch + .children + .iter() + .filter_map(|child_opt| { + child_opt + .as_ref() + .and_then(|child| child.unpersisted().cloned()) + }) + .collect(); + + // Push new child iterator to the stack + if !unpersisted_children.is_empty() { + self.child_iter_stack + .push(Box::new(unpersisted_children.into_iter())); + } + self.stack.push(next_child); // visit this node after the children + } else { + // leaf + return Some(next_child); + } + } else { + // Current iterator is exhausted, remove it + self.child_iter_stack.pop(); + } + } + + // No more children to process, pop the next node from the stack + self.stack.pop() + } +} + impl NodeStore, FileBacked> { /// Persist the freelist from this proposal to storage. #[fastrace::trace(short_name = true)] @@ -166,10 +281,12 @@ impl NodeStore, FileBacked> { }; RINGSIZE ]; + for (&addr, &(area_size_index, ref node)) in &self.kind.new { let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger node.as_bytes(area_size_index, &mut serialized); let mut serialized = serialized.into_boxed_slice(); + loop { // Find the first available write buffer, enumerate to get the position for marking it completed if let Some((pos, pbe)) = saved_pinned_buffers @@ -247,3 +364,207 @@ impl NodeStore, FileBacked> { Ok(()) } } + +#[cfg(test)] +#[expect(clippy::unwrap_used, clippy::indexing_slicing)] +mod tests { + use super::*; + use crate::{ + Child, HashType, LinearAddress, NodeStore, Path, SharedNode, + linear::memory::MemStore, + node::{BranchNode, LeafNode, Node}, + nodestore::MutableProposal, + }; + + /// Helper to create a test node store with a specific root + fn create_test_store_with_root(root: Node) -> NodeStore { + let mem_store = MemStore::new(vec![]).into(); + let mut store = NodeStore::new_empty_proposal(mem_store); + store.mut_root().replace(root); + store + } + + /// Helper to create a leaf node + fn create_leaf(path: &[u8], value: &[u8]) -> Node { + Node::Leaf(LeafNode { + partial_path: Path::from(path), + value: value.to_vec().into_boxed_slice(), + }) + } + + /// Helper to create a branch node with children + fn create_branch(path: &[u8], value: Option<&[u8]>, children: Vec<(u8, Node)>) -> Node { + let mut branch = BranchNode { + partial_path: Path::from(path), + value: value.map(|v| v.to_vec().into_boxed_slice()), + children: std::array::from_fn(|_| None), + }; + + for (index, child) in children { + let shared_child = SharedNode::new(child); + let maybe_persisted = MaybePersistedNode::from(shared_child); + let hash = HashType::default(); + branch.children[index as usize] = Some(Child::MaybePersisted(maybe_persisted, hash)); + } + + Node::Branch(Box::new(branch)) + } + + #[test] + fn test_empty_nodestore() { + let mem_store = MemStore::new(vec![]).into(); + let store = NodeStore::new_empty_proposal(mem_store); + let mut iter = UnPersistedNodeIterator::new(&store); + + assert!(iter.next().is_none()); + } + + #[test] + fn test_single_leaf_node() { + let leaf = create_leaf(&[1, 2, 3], &[4, 5, 6]); + let store = create_test_store_with_root(leaf.clone()); + let mut iter = + UnPersistedNodeIterator::new(&store).map(|node| node.as_shared_node(&store).unwrap()); + + // Should return the leaf node + let node = iter.next().unwrap(); + assert_eq!(*node, leaf); + + // Should be exhausted + assert!(iter.next().is_none()); + } + + #[test] + fn test_branch_with_single_child() { + let leaf = create_leaf(&[7, 8], &[9, 10]); + let branch = create_branch(&[1, 2], Some(&[3, 4]), vec![(5, leaf.clone())]); + let store = create_test_store_with_root(branch.clone()); + let mut iter = + UnPersistedNodeIterator::new(&store).map(|node| node.as_shared_node(&store).unwrap()); + + // Should return child first (depth-first) + let node = iter.next().unwrap(); + assert_eq!(*node, leaf); + + // Then the branch + let node = iter.next().unwrap(); + assert_eq!(&*node, &branch); + + assert!(iter.next().is_none()); + + // verify iterator is fused + assert!(iter.next().is_none()); + } + + #[test] + fn test_branch_with_multiple_children() { + let leaves = [ + create_leaf(&[1], &[10]), + create_leaf(&[2], &[20]), + create_leaf(&[3], &[30]), + ]; + let branch = create_branch( + &[0], + None, + vec![ + (1, leaves[0].clone()), + (5, leaves[1].clone()), + (10, leaves[2].clone()), + ], + ); + let store = create_test_store_with_root(branch.clone()); + + // Collect all nodes + let nodes: Vec<_> = UnPersistedNodeIterator::new(&store) + .map(|node| node.as_shared_node(&store).unwrap()) + .collect(); + + // Should have 4 nodes total (3 leaves + 1 branch) + assert_eq!(nodes.len(), 4); + + // The branch should be last (depth-first) + assert_eq!(&*nodes[3], &branch); + + // Children should come first - verify all expected leaf nodes are present + let children_nodes = &nodes[0..3]; + assert!(children_nodes.iter().any(|n| **n == leaves[0])); + assert!(children_nodes.iter().any(|n| **n == leaves[1])); + assert!(children_nodes.iter().any(|n| **n == leaves[2])); + } + + #[test] + fn test_nested_branches() { + let leaves = [ + create_leaf(&[1], &[100]), + create_leaf(&[2], &[200]), + create_leaf(&[3], &[255]), + ]; + + // Create a nested structure: root -> branch1 -> leaf[0] + // -> leaf[1] + // -> branch2 -> leaf[2] + let inner_branch = create_branch(&[10], Some(&[50]), vec![(0, leaves[2].clone())]); + + let root_branch: Node = BranchNode { + partial_path: Path::new(), + value: None, + children: [ + // unpersisted leaves + Some(Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(leaves[0].clone())), + HashType::default(), + )), + Some(Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(leaves[1].clone())), + HashType::default(), + )), + // unpersisted branch + Some(Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(inner_branch.clone())), + HashType::default(), + )), + // persisted branch + Some(Child::MaybePersisted( + MaybePersistedNode::from(LinearAddress::new(42).unwrap()), + HashType::default(), + )), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ], + } + .into(); + + let store = create_test_store_with_root(root_branch.clone()); + + // Collect all nodes + let nodes: Vec<_> = UnPersistedNodeIterator::new(&store) + .map(|node| node.as_shared_node(&store).unwrap()) + .collect(); + + // Should have 5 nodes total (3 leaves + 2 branches) + assert_eq!(nodes.len(), 5); + + // The root branch should be last (depth-first) + assert_eq!(**nodes.last().unwrap(), root_branch); + + // Find positions of some nodes + let root_pos = nodes.iter().position(|n| **n == root_branch).unwrap(); + let inner_branch_pos = nodes.iter().position(|n| **n == inner_branch).unwrap(); + let leaf3_pos = nodes.iter().position(|n| **n == leaves[2]).unwrap(); + + // Verify depth-first ordering: leaf3 should come before inner_branch, + // inner_branch should come before root_branch + assert!(leaf3_pos < inner_branch_pos); + assert!(inner_branch_pos < root_pos); + } +} From ec429925250893ed6691ae68f13a5e492727bbd6 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 16 Jul 2025 11:37:41 -0700 Subject: [PATCH 0827/1053] fix: encoding partial paths for leaf nodes (#1067) Branch nodes were already handling the overflow correctly, but leaf nodes were not. A failing test case was added to ensure this fix works. Previously: ```text ---- node::test::test_serialize_deserialize::leaf_node_obnoxiously_long_partial_path stdout ---- thread 'node::test::test_serialize_deserialize::leaf_node_obnoxiously_long_partial_path' panicked at storage/src/node/mod.rs:553:9: assertion `left == right` failed left: Leaf([Leaf 7 4 6 8 6 9 7 3 2 0 6 9 7 3 2 0 6 1 2 0 7 2 6 5 6 1 6 c 6 c 7 9 2 0 6 c 6 f 6 e 6 7 2 0 7 0 6 1 7 2 7 4 6 9 6 1 6 c 2 0 7 0 6 1 7 4 6 8 2 c 2 0 6 c 6 9 6 b 6 5 2 0 7 3 6 f 2 0 6 c 6 f 6 e 6 7 2 0 6 9 7 4 2 7 7 3 2 0 6 d 6 f 7 2 6 5 2 0 7 4 6 8 6 1 6 e 2 0 3 6 3 3 2 0 6 e 6 9 6 2 6 2 6 c 6 5 7 3 2 0 6 c 6 f 6 e 6 7 2 0 7 7 6 8 6 9 6 3 6 8 2 0 7 4 7 2 6 9 6 7 6 7 6 5 7 2 7 3 2 0 2 3 3 1 3 0 3 5 3 6 2 e 04050607]) right: Leaf([Leaf [invalid ca] 1 7 4 6 8 6 9 7 3 2 0 6 9 7 3 2 0 6 1 2 0 7 2 6 5 6 1 6 c 6 c 7 9 2 0 6 c 6 f 6 e 6 7 2 0 7 0 6 1 7 2 7 4 6 9 6 1 6 c 2 0 7 0 6 1 7 4 6 8 2 c 2 0 0c0609060b06]) note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` fixes #1056 --------- Signed-off-by: Joachim Brandon LeBlanc --- storage/benches/serializer.rs | 41 ++++++--- storage/src/node/branch.rs | 22 ++++- storage/src/node/mod.rs | 169 +++++++++++++++++++--------------- 3 files changed, 140 insertions(+), 92 deletions(-) diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 4bafe0e6fee9..a58ce601832e 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -16,7 +16,7 @@ use std::num::NonZeroU64; use std::os::raw::c_int; use criterion::profiler::Profiler; -use criterion::{Criterion, criterion_group, criterion_main}; +use criterion::{Bencher, Criterion, criterion_group, criterion_main}; use firewood_storage::{LeafNode, Node, Path}; use pprof::ProfilerGuard; use smallvec::SmallVec; @@ -63,6 +63,24 @@ impl Profiler for FlamegraphProfiler { } } +fn manual_serializer(b: &mut Bencher, input: &Node) { + b.iter(|| to_bytes(input)); +} + +fn manual_deserializer(b: &mut Bencher, input: &Vec) { + let (_area_index, input) = input + .as_slice() + .split_first() + .expect("always has at least one byte"); + b.iter(|| Node::from_reader(std::io::Cursor::new(input)).expect("to deserialize node")); +} + +fn to_bytes(input: &Node) -> Vec { + let mut bytes = Vec::new(); + input.as_bytes(0, &mut bytes); + bytes +} + fn leaf(c: &mut Criterion) { let mut group = c.benchmark_group("leaf"); let input = Node::Leaf(LeafNode { @@ -70,12 +88,8 @@ fn leaf(c: &mut Criterion) { value: Box::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), }); - group.bench_with_input("manual", &input, |b, input| { - b.iter(|| { - let mut bytes = Vec::::new(); - input.as_bytes(0, &mut bytes); - }); - }); + group.bench_with_input("manual", &input, manual_serializer); + group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); } @@ -96,30 +110,27 @@ fn branch(c: &mut Criterion) { }), })); - let manual_serializer = |b: &mut criterion::Bencher, input: &firewood_storage::Node| { - b.iter(|| { - let mut bytes = Vec::new(); - input.as_bytes(0, &mut bytes); - }); - }; - group.bench_with_input("manual", &input, manual_serializer); + group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); let mut group = c.benchmark_group("1_child"); input.as_branch_mut().unwrap().value = None; group.bench_with_input("manual", &input, manual_serializer); - let child = input.as_branch().unwrap().children[0].clone(); + group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); + let child = input.as_branch().unwrap().children[0].clone(); let mut group = c.benchmark_group("2_child"); input.as_branch_mut().unwrap().children[1] = child.clone(); group.bench_with_input("manual", &input, manual_serializer); + group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); let mut group = c.benchmark_group("16_child"); input.as_branch_mut().unwrap().children = std::array::from_fn(|_| child.clone()); group.bench_with_input("manual", &input, manual_serializer); + group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index fe109dd65549..6b045767a0e2 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -13,6 +13,7 @@ use crate::node::ExtendableBytes; use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path, SharedNode}; use std::fmt::{Debug, Formatter}; +use std::io::Read; /// The type of a hash. For ethereum compatible hashes, this might be a RLP encoded /// value if it's small enough to fit in less than 32 bytes. For merkledb compatible @@ -54,14 +55,14 @@ impl IntoHashType for crate::TrieHash { pub(crate) trait Serializable { fn write_to(&self, vec: &mut W); - fn from_reader(reader: R) -> Result + fn from_reader(reader: R) -> Result where Self: Sized; } -/// An extension trait for [`std::io::Read`] for convenience methods when +/// An extension trait for [`Read`] for convenience methods when /// reading serialized data. -pub(crate) trait ReadSerializable: std::io::Read { +pub(crate) trait ReadSerializable: Read { /// Read a single byte from the reader. fn read_byte(&mut self) -> Result { let mut this = 0; @@ -69,13 +70,26 @@ pub(crate) trait ReadSerializable: std::io::Read { Ok(this) } + /// Reads a fixed amount of bytes from the reader into a vector + fn read_fixed_len(&mut self, len: usize) -> Result, std::io::Error> { + let mut buf = Vec::with_capacity(len); + self.take(len as u64).read_to_end(&mut buf)?; + if buf.len() != len { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "not enough bytes read", + )); + } + Ok(buf) + } + /// Read a value of type `T` from the reader. fn next_value(&mut self) -> Result { T::from_reader(self) } } -impl ReadSerializable for T {} +impl ReadSerializable for T {} #[derive(PartialEq, Eq, Clone, Debug)] #[repr(C)] diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index cf302a4791be..14cad0ee076b 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -22,25 +22,23 @@ reason = "Found 1 occurrences after enabling the lint." )] +use crate::node::branch::ReadSerializable; +use crate::{HashType, Path, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; +pub use branch::{BranchNode, Child}; use enum_as_inner::EnumAsInner; -use integer_encoding::{VarInt, VarIntReader as _, VarIntWriter as _}; +use integer_encoding::{VarInt, VarIntReader as _}; +pub use leaf::LeafNode; use std::fmt::Debug; use std::io::{Error, Read, Write}; use std::num::NonZero; -use std::vec; pub mod branch; mod leaf; pub mod path; pub mod persist; -pub use branch::{BranchNode, Child}; -pub use leaf::LeafNode; - -use crate::{HashType, Path, SharedNode}; - /// A node, either a Branch or Leaf // TODO: explain why Branch is boxed but Leaf is not @@ -85,7 +83,7 @@ bitfield! { partial_path_length, set_partial_path_length: 7, 6; } #[cfg(not(feature = "branch_factor_256"))] -const MAX_ENCODED_PARTIAL_PATH_LEN: usize = 2; +const BRANCH_PARTIAL_PATH_LEN_OVERFLOW: u8 = (1 << 2) - 1; // 3 nibbles #[cfg(feature = "branch_factor_256")] bitfield! { @@ -97,7 +95,7 @@ bitfield! { partial_path_length, set_partial_path_length: 7, 2; } #[cfg(feature = "branch_factor_256")] -const MAX_ENCODED_PARTIAL_PATH_LEN: usize = 63; +const BRANCH_PARTIAL_PATH_LEN_OVERFLOW: u8 = (1 << 6) - 1; // 63 nibbles bitfield! { struct LeafFirstByte(u8); @@ -108,6 +106,8 @@ bitfield! { partial_path_length, set_partial_path_length: 7, 1; } +const LEAF_PARTIAL_PATH_LEN_OVERFLOW: u8 = (1 << 7) - 2; // 126 nibbles (-1 for indicating Free Area (0xff)) + impl Default for LeafFirstByte { fn default() -> Self { LeafFirstByte(1) @@ -269,11 +269,12 @@ impl Node { let childcount = child_iter.clone().count(); // encode the first byte - let pp_len = if b.partial_path.0.len() <= MAX_ENCODED_PARTIAL_PATH_LEN { - b.partial_path.0.len() as u8 - } else { - MAX_ENCODED_PARTIAL_PATH_LEN as u8 + 1 + let pp_len = match b.partial_path.len() { + // less than 3 or 62 nibbles + len if len < BRANCH_PARTIAL_PATH_LEN_OVERFLOW as usize => len as u8, + _ => BRANCH_PARTIAL_PATH_LEN_OVERFLOW, }; + #[cfg(not(feature = "branch_factor_256"))] let first_byte: BranchFirstByte = BranchFirstByte::new( u8::from(b.value.is_some()), @@ -293,18 +294,14 @@ impl Node { encoded.push((childcount % BranchNode::MAX_CHILDREN) as u8); // encode the partial path, including the length if it didn't fit above - if b.partial_path.0.len() > MAX_ENCODED_PARTIAL_PATH_LEN { - encoded - .write_varint(b.partial_path.len()) - .expect("writing to vec should succeed"); + if pp_len == BRANCH_PARTIAL_PATH_LEN_OVERFLOW { + encoded.extend_var_int(b.partial_path.len()); } encoded.extend_from_slice(&b.partial_path); // encode the value. For tries that have the same length keys, this is always empty if let Some(v) = &b.value { - encoded - .write_varint(v.len()) - .expect("writing to vec should succeed"); + encoded.extend_var_int(v.len()); encoded.extend_from_slice(v); } @@ -322,9 +319,7 @@ impl Node { } } else { for (position, child) in child_iter { - encoded - .write_varint(position) - .expect("writing to vec should succeed"); + encoded.extend_var_int(position); if let Child::AddressWithHash(address, hash) = child { encoded.extend_from_slice(&address.get().to_ne_bytes()); hash.write_to(encoded); @@ -337,7 +332,12 @@ impl Node { } } Node::Leaf(l) => { - let first_byte: LeafFirstByte = LeafFirstByte::new(1, l.partial_path.0.len() as u8); + let pp_len = match l.partial_path.len() { + // less than 126 nibbles + len if len < LEAF_PARTIAL_PATH_LEN_OVERFLOW as usize => len as u8, + _ => LEAF_PARTIAL_PATH_LEN_OVERFLOW, + }; + let first_byte: LeafFirstByte = LeafFirstByte::new(1, pp_len); const OPTIMIZE_LEAVES_FOR_SIZE: usize = 128; encoded.reserve(OPTIMIZE_LEAVES_FOR_SIZE); @@ -345,17 +345,13 @@ impl Node { encoded.push(first_byte.0); // encode the partial path, including the length if it didn't fit above - if l.partial_path.0.len() >= 127 { - encoded - .write_varint(l.partial_path.len()) - .expect("write to array should succeed"); + if pp_len == LEAF_PARTIAL_PATH_LEN_OVERFLOW { + encoded.extend_var_int(l.partial_path.len()); } encoded.extend_from_slice(&l.partial_path); // encode the value - encoded - .write_varint(l.value.len()) - .expect("write to array should succeed"); + encoded.extend_var_int(l.value.len()); encoded.extend_from_slice(&l.value); } } @@ -363,33 +359,21 @@ impl Node { /// Given a reader, return a [Node] from those bytes pub fn from_reader(mut serialized: impl Read) -> Result { - let mut first_byte: [u8; 1] = [0]; - serialized.read_exact(&mut first_byte)?; - match first_byte[0] { + match serialized.read_byte()? { 255 => { // this is a freed area Err(Error::other("attempt to read freed area")) } - leaf_first_byte if leaf_first_byte & 1 == 1 => { - let partial_path_len = if leaf_first_byte < 255 { - // less than 126 nibbles - LeafFirstByte(leaf_first_byte).partial_path_length() as usize - } else { - serialized.read_varint()? - }; - - let mut partial_path = vec![0u8; partial_path_len]; - serialized.read_exact(&mut partial_path)?; - - let mut value_len_buf = [0u8; 1]; - serialized.read_exact(&mut value_len_buf)?; - let value_len = value_len_buf[0] as usize; - - let mut value = vec![0u8; value_len]; - serialized.read_exact(&mut value)?; - + first_byte if first_byte & 1 == 1 => { + let partial_path = read_path_with_overflow_length( + &mut serialized, + first_byte >> 1, + LEAF_PARTIAL_PATH_LEN_OVERFLOW, + )?; + let value_len = serialized.read_varint()?; + let value = serialized.read_fixed_len(value_len)?; Ok(Node::Leaf(LeafNode { - partial_path: Path::from(partial_path), + partial_path, value: value.into(), })) } @@ -400,27 +384,17 @@ impl Node { #[cfg(not(feature = "branch_factor_256"))] let childcount = branch_first_byte.number_children() as usize; #[cfg(feature = "branch_factor_256")] - let childcount = { - let mut childcount_buf = [0u8; 1]; - serialized.read_exact(&mut childcount_buf)?; - childcount_buf[0] as usize - }; + let childcount = serialized.read_byte()? as usize; - let mut partial_path_len = branch_first_byte.partial_path_length() as usize; - if partial_path_len > MAX_ENCODED_PARTIAL_PATH_LEN { - partial_path_len = serialized.read_varint()?; - } - - let mut partial_path = vec![0u8; partial_path_len]; - serialized.read_exact(&mut partial_path)?; + let partial_path = read_path_with_overflow_length( + &mut serialized, + branch_first_byte.partial_path_length(), + BRANCH_PARTIAL_PATH_LEN_OVERFLOW, + )?; let value = if has_value { - let mut value_len_buf = [0u8; 1]; - serialized.read_exact(&mut value_len_buf)?; - let value_len = value_len_buf[0] as usize; - - let mut value = vec![0u8; value_len]; - serialized.read_exact(&mut value)?; + let value_len = serialized.read_varint()?; + let value = serialized.read_fixed_len(value_len)?; Some(value.into()) } else { None @@ -462,7 +436,7 @@ impl Node { } Ok(Node::Branch(Box::new(BranchNode { - partial_path: partial_path.into(), + partial_path, value, children, }))) @@ -487,12 +461,37 @@ pub struct PathIterItem { pub next_nibble: Option, } +fn read_path_with_overflow_length( + reader: &mut impl Read, + value: u8, + overflow: u8, +) -> std::io::Result { + if value < overflow { + // the value is less than the overflow, so we can read it directly + read_path_with_provided_length(reader, value as usize) + } else { + read_path_with_prefix_length(reader) + } +} + +#[cold] +#[inline(never)] +fn read_path_with_prefix_length(reader: &mut impl Read) -> std::io::Result { + let len = reader.read_varint()?; + read_path_with_provided_length(reader, len) +} + +#[inline] +fn read_path_with_provided_length(reader: &mut impl Read, len: usize) -> std::io::Result { + reader.read_fixed_len(len).map(Path::from) +} + #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] use crate::node::{BranchNode, LeafNode, Node}; - use crate::{Child, LinearAddress, Path}; + use crate::{Child, LinearAddress, NibblesIterator, Path}; use test_case::test_case; #[test_case( @@ -500,6 +499,11 @@ mod test { partial_path: Path::from(vec![0, 1, 2, 3]), value: vec![4, 5, 6, 7].into() }), 11; "leaf node with value")] + #[test_case( + Node::Leaf(LeafNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"this is a really long partial path, like so long it's more than 63 nibbles long which triggers #1056.")), + value: vec![4, 5, 6, 7].into() + }), 211; "leaf node obnoxiously long partial path")] #[test_case(Node::Branch(Box::new(BranchNode { partial_path: Path::from(vec![0, 1]), value: None, @@ -518,6 +522,25 @@ mod test { Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 652; "full branch node with long partial path and value" )] + #[test_case(Node::Branch(Box::new(BranchNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"this is a really long partial path, like so long it's more than 63 nibbles long which triggers #1056.")), + value: Some(vec![4, 5, 6, 7].into()), + children: std::array::from_fn(|_| + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + )})), 851; "full branch node with obnoxiously long partial path" + )] + #[test_case(Node::Branch(Box::new(BranchNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"this is a really long partial path, like so long it's more than 63 nibbles long which triggers #1056.")), + value: Some((*br" +We also need to test values that have a length longer than 255 bytes so that we +verify that we decode the entire value every time. previously, we would only read +the first byte for the value length, which is incorrect if the length is greater +than 126 bytes as the length would be encoded in multiple bytes. + ").into()), + children: std::array::from_fn(|_| + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) + )})), 1165; "full branch node with obnoxiously long partial path and long value" + )] // When ethhash is enabled, we don't actually check the `expected_length` fn test_serialize_deserialize( node: Node, From 237a7aba328956956aee57f647dcc67db1abad77 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 16 Jul 2025 16:43:26 -0700 Subject: [PATCH 0828/1053] fix: root_hash_reversed_deletions duplicate keys (#1076) This test inserts a bunch of keys and then verifies that the resulting hashes are the same if we delete them in the reverse order. However, the test is generating a duplicate key, which will not reproduce the original hash set. Changed the test to remove any duplicate keys found. It's okay if the data is the same. Now passes with the errant key from #1075 Also fixed the merkle dump generator, which I used to debug this. A bracket was missing so graphviz was failing to parse the file. Also shortened the hashes as they made the nodes look much bigger, and added a new line. See attached merkle dump for the new format. --- firewood/src/merkle.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b0495c8809c0..5c48467ef400 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -368,11 +368,11 @@ impl Merkle { seen: &mut HashSet, writer: &mut dyn Write, ) -> Result<(), FileIoError> { - write!(writer, " {node}") + writeln!(writer, " {node}[label=\"{node}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None))?; if let Some(hash) = hash { - write!(writer, " hash:{hash:.6?}...") + write!(writer, " H={hash:.6?}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None))?; } @@ -2033,6 +2033,8 @@ mod tests { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; + let _ = env_logger::Builder::new().is_test(true).try_init(); + let seed = std::env::var("FIREWOOD_TEST_SEED") .ok() .map_or_else( @@ -2071,6 +2073,7 @@ mod tests { .collect(); items.sort(); + items.dedup_by_key(|(k, _)| k.clone()); let init_merkle: Merkle> = merkle_build_test::, Box<[u8]>>(vec![])?; From 18ce0eb4ec33f3a8638c18f895e77890e8dff81d Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:08:53 -0500 Subject: [PATCH 0829/1053] fix(checker): avoid checking physical file size for compatibility (#1079) The size of the underlying file is not reliable on certain OS, and the high watermark can be either larger or smaller than the actual file size. Therefore, the checker should not compare the high watermark against the actual file size. --- storage/src/checker/mod.rs | 9 --------- storage/src/nodestore/mod.rs | 4 ---- 2 files changed, 13 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 0a3f488ead1b..2dbe98c2bcbd 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -32,15 +32,6 @@ impl NodeStore { pub fn check(&self) -> Result<(), CheckerError> { // 1. Check the header let db_size = self.size(); - let file_size = self.physical_size()?; - if db_size < file_size { - return Err(CheckerError::InvalidDBSize { - db_size, - description: format!( - "db size should not be smaller than the file size ({file_size})" - ), - }); - } let mut visited = LinearAddressRangeSet::new(db_size)?; diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 2ac08698979b..0e2fedc6a193 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -729,10 +729,6 @@ impl NodeStore { self.header.size() } - pub(crate) fn physical_size(&self) -> Result { - self.storage.size() - } - // Find the area index and size of the stored area at the given address if the area is valid. // TODO: there should be a way to read stored area directly instead of try reading as a free area then as a node pub(crate) fn read_leaked_area( From d8235ba35f5bc7043f8f7005ca2b56abc20a0198 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:47:16 -0500 Subject: [PATCH 0830/1053] feat(checker): add hash checks (#1063) While traversing the trie, we can optionally check if all hashes are valid. --- firewood/src/v2/api.rs | 6 +- fwdctl/src/check.rs | 17 ++- storage/src/checker/mod.rs | 188 +++++++++++++++++++++++-------- storage/src/checker/range_set.rs | 1 + storage/src/lib.rs | 16 +++ storage/src/node/branch.rs | 4 +- 6 files changed, 180 insertions(+), 52 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 5cd96de9ceaa..11e5691d88ba 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -14,7 +14,7 @@ use crate::manager::RevisionManagerError; use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; use async_trait::async_trait; -use firewood_storage::{CheckerError, FileIoError, TrieHash}; +use firewood_storage::{FileIoError, TrieHash}; use futures::Stream; use std::fmt::Debug; use std::sync::Arc; @@ -155,10 +155,6 @@ pub enum Error { /// Revision not found #[error("revision not found")] RevisionNotFound, - - /// Checker error - #[error("checker error")] - CheckerError(#[from] CheckerError), } impl From for Error { diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index b0a1892977cf..4981e878a059 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use clap::Args; use firewood::v2::api; -use firewood_storage::{CacheReadStrategy, FileBacked, NodeStore}; +use firewood_storage::{CacheReadStrategy, CheckOpt, FileBacked, NodeStore}; use nonzero_ext::nonzero; // TODO: (optionally) add a fix option @@ -21,6 +21,15 @@ pub struct Options { help = "Name of the database" )] pub db: String, + + /// Whether to perform hash check + #[arg( + long, + required = false, + default_value_t = false, + help = "Should perform hash check" + )] + pub hash_check: bool, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { @@ -37,5 +46,9 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { )?; let storage = Arc::new(fb); - NodeStore::open(storage)?.check().map_err(Into::into) + NodeStore::open(storage)? + .check(CheckOpt { + hash_check: opts.hash_check, + }) + .map_err(|e| api::Error::InternalError(Box::new(e))) } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 2dbe98c2bcbd..c2068ed0d5f5 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -8,15 +8,23 @@ use crate::logger::warn; use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata}; use crate::nodestore::is_aligned; use crate::{ - CheckerError, Committed, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, - StoredAreaParent, TrieNodeParent, WritableStorage, + CheckerError, Committed, HashType, HashedNodeReader, LinearAddress, Node, NodeReader, + NodeStore, Path, StoredAreaParent, TrieNodeParent, WritableStorage, hash_node, }; use std::cmp::Ordering; use std::ops::Range; +/// Options for the checker +#[derive(Debug)] +pub struct CheckOpt { + /// Whether to check the hash of the nodes + pub hash_check: bool, +} + /// [`NodeStore`] checker // TODO: S needs to be writeable if we ask checker to fix the issues +#[expect(clippy::result_large_err)] impl NodeStore { /// Go through the filebacked storage and check for any inconsistencies. It proceeds in the following steps: /// 1. Check the header @@ -28,21 +36,26 @@ impl NodeStore { /// # Panics /// Panics if the header has too many free lists, which can never happen since freelists have a fixed size. // TODO: report all errors, not just the first one - // TODO: add merkle hash checks as well - pub fn check(&self) -> Result<(), CheckerError> { + pub fn check(&self, opt: CheckOpt) -> Result<(), CheckerError> { // 1. Check the header let db_size = self.size(); let mut visited = LinearAddressRangeSet::new(db_size)?; // 2. traverse the trie and check the nodes - if let Some(root_address) = self.root_address() { + if let (Some(root_address), Some(root_hash)) = (self.root_address(), self.root_hash()) { // the database is not empty, traverse the trie self.check_area_aligned( root_address, StoredAreaParent::TrieNode(TrieNodeParent::Root), )?; - self.visit_trie(root_address, &mut visited)?; + self.visit_trie( + root_address, + HashType::from(root_hash), + Path::new(), + &mut visited, + opt.hash_check, + )?; } // 3. check the free list - this can happen in parallel with the trie traversal @@ -63,22 +76,48 @@ impl NodeStore { fn visit_trie( &self, subtree_root_address: LinearAddress, + subtree_root_hash: HashType, + path_prefix: Path, visited: &mut LinearAddressRangeSet, + hash_check: bool, ) -> Result<(), CheckerError> { let (_, area_size) = self.area_index_and_size(subtree_root_address)?; + let node = self.read_node(subtree_root_address)?; visited.insert_area(subtree_root_address, area_size)?; - if let Node::Branch(branch) = self.read_node(subtree_root_address)?.as_ref() { + // iterate over the children + if let Node::Branch(branch) = node.as_ref() { // this is an internal node, traverse the children - for (child_idx, address) in branch.children_addresses() { + for (nibble, (address, hash)) in branch.children_iter() { self.check_area_aligned( address, StoredAreaParent::TrieNode(TrieNodeParent::Parent( subtree_root_address, - child_idx, + nibble, )), )?; - self.visit_trie(address, visited)?; + let mut child_path_prefix = path_prefix.clone(); + child_path_prefix.0.push(nibble as u8); + self.visit_trie( + address, + hash.clone(), + child_path_prefix, + visited, + hash_check, + )?; + } + } + + // hash check - at this point all children hashes have been verified + if hash_check { + let hash = hash_node(&node, &path_prefix); + if hash != subtree_root_hash { + return Err(CheckerError::HashMismatch { + partial_path: path_prefix, + address: subtree_root_address, + parent_stored_hash: subtree_root_hash, + computed_hash: hash, + }); } } @@ -212,79 +251,140 @@ mod test { test_write_free_area, test_write_header, test_write_new_node, test_write_zeroed_area, }; use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; - use crate::{BranchNode, Child, HashType, LeafNode, NodeStore, Path}; - - use nonzero_ext::nonzero; - use std::collections::HashMap; - - #[test] - // This test creates a simple trie and checks that the checker traverses it correctly. - // We use primitive calls here to do a low-level check. - // TODO: add a high-level test in the firewood crate - fn checker_traverse_correct_trie() { - let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - - // set up a basic trie: - // ------------------------- - // | | X | X | ... | Root node - // ------------------------- - // | - // V - // ------------------------- - // | X | | X | ... | Branch node - // ------------------------- - // | - // V - // ------------------------- - // | [0,1] -> [3,4,5] | Leaf node - // ------------------------- + use crate::{BranchNode, Child, LeafNode, NodeStore, Path, hash_node}; + + // set up a basic trie: + // ------------------------- + // | | X | X | ... | Root node + // ------------------------- + // | + // V + // ------------------------- + // | X | | X | ... | Branch node + // ------------------------- + // | + // V + // ------------------------- + // | [0,1] -> [3,4,5] | Leaf node + // ------------------------- + #[expect(clippy::arithmetic_side_effects)] + fn gen_test_trie( + nodestore: &mut NodeStore, + ) -> (Vec<(Node, LinearAddress)>, u64, (LinearAddress, HashType)) { let mut high_watermark = NodeStoreHeader::SIZE; let leaf = Node::Leaf(LeafNode { partial_path: Path::from([0, 1]), value: Box::new([3, 4, 5]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); - let leaf_area = test_write_new_node(&nodestore, &leaf, high_watermark); + let leaf_hash = hash_node(&leaf, &Path::from_nibbles_iterator([0u8, 1].into_iter())); + let leaf_area = test_write_new_node(nodestore, &leaf, high_watermark); high_watermark += leaf_area; let mut branch_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); - branch_children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::default())); + branch_children[1] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); let branch = Node::Branch(Box::new(BranchNode { partial_path: Path::from([0]), value: None, children: branch_children, })); let branch_addr = LinearAddress::new(high_watermark).unwrap(); - let branch_area = test_write_new_node(&nodestore, &branch, high_watermark); + let branch_hash = hash_node(&branch, &Path::from_nibbles_iterator([0u8].into_iter())); + let branch_area = test_write_new_node(nodestore, &branch, high_watermark); high_watermark += branch_area; let mut root_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); - root_children[0] = Some(Child::AddressWithHash(branch_addr, HashType::default())); + root_children[0] = Some(Child::AddressWithHash(branch_addr, branch_hash)); let root = Node::Branch(Box::new(BranchNode { partial_path: Path::from([]), value: None, children: root_children, })); let root_addr = LinearAddress::new(high_watermark).unwrap(); - let root_area = test_write_new_node(&nodestore, &root, high_watermark); + let root_hash = hash_node(&root, &Path::new()); + let root_area = test_write_new_node(nodestore, &root, high_watermark); high_watermark += root_area; // write the header test_write_header( - &mut nodestore, + nodestore, high_watermark, Some(root_addr), FreeLists::default(), ); + ( + vec![(leaf, leaf_addr), (branch, branch_addr), (root, root_addr)], + high_watermark, + (root_addr, root_hash), + ) + } + + use nonzero_ext::nonzero; + use std::collections::HashMap; + + #[test] + // This test creates a simple trie and checks that the checker traverses it correctly. + // We use primitive calls here to do a low-level check. + // TODO: add a high-level test in the firewood crate + fn checker_traverse_correct_trie() { + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let (_, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); + // verify that all of the space is accounted for - since there is no free area let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - nodestore.visit_trie(root_addr, &mut visited).unwrap(); + nodestore + .visit_trie(root_addr, root_hash, Path::new(), &mut visited, true) + .unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); } + #[test] + // This test permutes the simple trie with a wrong hash and checks that the checker detects it. + fn checker_traverse_trie_with_wrong_hash() { + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let (mut nodes, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); + + // replace the branch hash in the root node with a wrong hash + let [_, (branch_node, branch_addr), (root_node, _)] = nodes.as_mut_slice() else { + panic!("test trie content changed, the test should be updated"); + }; + let wrong_hash = HashType::default(); + let branch_path = &branch_node.as_branch().unwrap().partial_path; + let Some(Child::AddressWithHash(_, hash)) = root_node.as_branch_mut().unwrap().children + [branch_path[0] as usize] + .replace(Child::AddressWithHash(*branch_addr, wrong_hash.clone())) + else { + panic!("test trie content changed, the test should be updated"); + }; + let branch_hash = hash; + test_write_new_node(&nodestore, root_node, root_addr.get()); + + // verify that all of the space is accounted for - since there is no free area + let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + let err = nodestore + .visit_trie(root_addr, root_hash, Path::new(), &mut visited, true) + .unwrap_err(); + assert!(matches!( + err, + CheckerError::HashMismatch { + address, + partial_path, + parent_stored_hash, + computed_hash + } + if address == *branch_addr + && partial_path == *branch_path + && parent_stored_hash == wrong_hash + && computed_hash == branch_hash + )); + } + #[test] fn traverse_correct_freelist() { use rand::Rng; diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index 7997b11bc9eb..42787756e276 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -219,6 +219,7 @@ pub(super) struct LinearAddressRangeSet { max_addr: LinearAddress, } +#[expect(clippy::result_large_err)] impl LinearAddressRangeSet { const NODE_STORE_ADDR_START: LinearAddress = LinearAddress::new(NodeStoreHeader::SIZE).unwrap(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 9ea36cecb69e..676ceba3d6ad 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -37,6 +37,7 @@ use crate::nodestore::AreaIndex; pub mod logger; // re-export these so callers don't need to know where they are +pub use checker::CheckOpt; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; @@ -138,6 +139,21 @@ pub enum CheckerError { description: String, }, + /// Hash mismatch for a node + #[error( + "Hash mismatch for node {partial_path:?} at address {address}: parent stored {parent_stored_hash}, computed {computed_hash}" + )] + HashMismatch { + /// The path of the node + partial_path: Path, + /// The address of the node + address: LinearAddress, + /// The hash value stored in the parent node + parent_stored_hash: HashType, + /// The hash value computed for the node + computed_hash: HashType, + }, + /// The address is out of bounds #[error("stored area at {start} with size {size} is out of bounds ({bounds:?})")] AreaOutOfBounds { diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 6b045767a0e2..0282e4a0635f 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -392,7 +392,9 @@ impl BranchNode { } // Helper to iterate over only valid children - fn children_iter(&self) -> impl Iterator + Clone { + pub(crate) fn children_iter( + &self, + ) -> impl Iterator + Clone { self.children .iter() .enumerate() From 368f98f8bed7dc8b173fc1342a473d1e1bd29cd3 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 17 Jul 2025 11:43:27 -0700 Subject: [PATCH 0831/1053] chore: Update CODEOWNERS (#1080) I noticed I was not auto-added to reviews for the ffi and .github directories, and I discovered that this is because the wildcard rule was being ignored if a more specific rule matched. I have updated the `CODEOWNERS` file to ensure that Ron and I are added to all reviews. --- CODEOWNERS | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1a2ad0f9bf5e..4aa1bc7b5e12 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,4 +1,5 @@ # CODEOWNERS -* @rkuris @demosdemon -/ffi @alarso16 -/.github @aaronbuchwald +# Note: more specific rules overrule wildcard +* @rkuris @demosdemon +/ffi @rkuris @demosdemon @alarso16 +/.github @rkuris @demosdemon @aaronbuchwald From 637413d0d177bef6e1a003fbf3fd7e369cded727 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 17 Jul 2025 11:49:29 -0700 Subject: [PATCH 0832/1053] ci: run CI with --no-default-features (#1081) Fixes: #960 --- .github/workflows/ci.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 169c3dfe71ff..92d9dc421bd9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,6 +23,8 @@ jobs: strategy: matrix: include: + - profile-key: debug-no-default-features + profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" - profile-key: debug-ethhash-logger @@ -67,6 +69,8 @@ jobs: strategy: matrix: include: + - profile-key: debug-no-default-features + profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" - profile-key: debug-ethhash-logger @@ -93,6 +97,8 @@ jobs: strategy: matrix: include: + - profile-key: debug-no-default-features + profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" - profile-key: debug-ethhash-logger @@ -116,13 +122,14 @@ jobs: strategy: matrix: include: + - profile-key: debug-no-default-features + profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" - profile-key: maxperf-ethhash-logger profile-args: "--profile maxperf --features ethhash,logger" - # TODO: enable testing with branch_factor_256 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -141,6 +148,8 @@ jobs: strategy: matrix: include: + - profile-key: debug-no-default-features + profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" - profile-key: debug-ethhash-logger @@ -156,7 +165,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: save-if: "false" - shared-key: ${{ matrix.profile-key}} + shared-key: ${{ matrix.profile-key }} - name: Run benchmark example run: RUST_BACKTRACE=1 cargo run ${{ matrix.profile-args }} --bin benchmark -- --number-of-batches 100 --batch-size 1000 create - name: Run insert example From 6d801a65402afc92f036b47fb640a8320b0c6adf Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 17 Jul 2025 12:00:41 -0700 Subject: [PATCH 0833/1053] chore: Release 0.0.9 (#1084) --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++++++------ RELEASE.md | 24 ++++++++++++++++-------- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bedcb57e1e0..576d5b157aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,47 @@ All notable changes to this project will be documented in this file. +## [0.0.9] - 2025-07-17 + +### 🚀 Features + +- *(ffi)* Add gauges to metrics reporter (#1035) +- *(delayed-persist)* Part 1: Roots may be in mem (#1041) +- *(delayed-persist)* 2.1: Unpersisted deletions (#1045) +- *(delayed-persist)* Part 2.2: Branch Children (#1047) +- [**breaking**] Export firewood metrics (#1044) +- *(checker)* Add error to report finding leaked areas (#1052) +- *(delayed-persist)* Dump unpersisted nodestore (#1055) +- *(checker)* Split leaked ranges into valid areas (#1059) +- *(checker)* Check for misaligned stored areas (#1046) +- [**breaking**] Auto open or create with truncate (#1064) +- *(deferred-allocate)* UnpersistedIterator (#1060) +- *(checker)* Add hash checks (#1063) + +### 🐛 Bug Fixes + +- Avoid reference to LinearAddress (#1042) +- Remove dependency on serde (#1066) +- Encoding partial paths for leaf nodes (#1067) +- Root\_hash\_reversed\_deletions duplicate keys (#1076) +- *(checker)* Avoid checking physical file size for compatibility (#1079) + +### 🎨 Styling + +- Remove unnecessary error descriptor (#1049) + +### ⚙️ Miscellaneous Tasks + +- *(build)* Remove unused dependencies (#1037) +- Update firewood in grpc-testtool (#1040) +- Aaron is requested only for .github (#1043) +- Remove `#[allow]`s no longer needed (#1022) +- Split nodestore into functional areas (#1048) +- Update `golangci-lint` (#1053) +- Update CODEOWNERS (#1080) +- Run CI with --no-default-features (#1081) +- Release 0.0.9 (#1084) + ## [0.0.8] - 2025-07-07 ### 🚀 Features diff --git a/Cargo.toml b/Cargo.toml index 389b485ef5a6..95a9badbeb9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ exclude = [ resolver = "2" [workspace.package] -version = "0.0.8" +version = "0.0.9" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -56,11 +56,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.8" } -firewood-macros = { path = "firewood-macros", version = "0.0.8" } -firewood-storage = { path = "storage", version = "0.0.8" } -firewood-ffi = { path = "ffi", version = "0.0.8" } -firewood-triehash = { path = "triehash", version = "0.0.8" } +firewood = { path = "firewood", version = "0.0.9" } +firewood-macros = { path = "firewood-macros", version = "0.0.9" } +firewood-storage = { path = "storage", version = "0.0.9" } +firewood-ffi = { path = "ffi", version = "0.0.9" } +firewood-triehash = { path = "triehash", version = "0.0.9" } # common dependencies metrics = "0.24.2" diff --git a/RELEASE.md b/RELEASE.md index f5c880a89853..d19ca7eefb14 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,9 +12,9 @@ Start off by crating a new branch: ```console $ git fetch -$ git switch -c release/v0.0.8 origin/main -branch 'release/v0.0.8' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.8' +$ git switch -c release/v0.0.10 origin/main +branch 'release/v0.0.10' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.10' ``` ## Package Version @@ -49,7 +49,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.7" } +firewood = { path = "firewood", version = "0.0.10" } ``` This allows packages within the workspace to inherit the dependency, @@ -78,7 +78,7 @@ To build the changelog, see git-cliff.org. Short version: ```sh cargo install git-cliff -git cliff --tag v0.0.8 | sed -e 's/_/\\_/g' > CHANGELOG.md +git cliff --tag v0.0.10 | sed -e 's/_/\\_/g' > CHANGELOG.md ``` ## Review @@ -92,10 +92,18 @@ git cliff --tag v0.0.8 | sed -e 's/_/\\_/g' > CHANGELOG.md To trigger a release, push a tag to the main branch matching the new version, ```sh -git tag -S v0.0.8 -git push origin v0.0.8 +git tag -S v0.0.10 +git push origin v0.0.10 ``` -for `v0.0.8` for the merged version change. The CI will automatically publish a +for `v0.0.10` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). + +## Milestone + +> NOTE: this should be automated as well. + +Close the GitHub milestone for the version that was released. Be sure to create +a new milestone for the next version if one does not already exist. Carry over +any incomplete tasks to the next milestone. From fe9986e31e711652002309182a08e16e4ca2dd37 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Fri, 18 Jul 2025 15:33:19 -0500 Subject: [PATCH 0834/1053] fix(fwdctl): fix fwdctl with ethhash (#1091) The checker decides whether to enable ethhash at build time through feature flags. Exposing this feature flag when building `fwdctl`. --- fwdctl/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index df6955bc748e..ce36248c451c 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -34,6 +34,9 @@ hex = "0.4.3" csv = "1.3.1" nonzero_ext = "0.3.0" +[features] +ethhash = ["firewood/ethhash"] + [dev-dependencies] anyhow = "1.0.98" assert_cmd = "2.0.17" From 5c7aa6ecb6200c396c8614d2e2e5e3e7e4c6b0f5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Jul 2025 09:01:58 -0700 Subject: [PATCH 0835/1053] build: Cargo.toml upgrades and fixes (#1099) For reviewers, there are two commits, one with just the edits and the second with the reordering only. tokio in firewood was set to version "1.0.0" which created some interesting debugging sessions. Other changes: - Added log, hex, rand_distr and test-case to workspace level - switched from `dependency = { workspace = true }` to `dependency.workspace = true` when possible `cargo upgrade --incmpatible` output: ```text Checking virtual workspace's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= tokio 1.46.0 1.46.1 1.46.1 1.46.1 clap 4.5.40 4.5.41 4.5.41 4.5.41 rand 0.9.1 0.9.2 0.9.2 0.9.2 Checking firewood's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= tokio 1.0.0 1.46.1 1.46.1 1.46.1 clap 4.5.40 4.5.41 4.5.41 4.5.41 tokio 1.46.0 1.46.1 1.46.1 1.46.1 Checking firewood-benchmark's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= fastrace-opentelemetry 0.12.0 0.12.0 0.13.0 0.13.0 rand 0.9.1 0.9.2 0.9.2 0.9.2 Checking firewood-ffi's dependencies Checking firewood-fwdctl's dependencies Checking firewood-macros's dependencies Checking firewood-storage's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= bytemuck_derive 1.9.3 1.10.0 1.10.0 1.10.0 Checking firewood-triehash's dependencies ``` --- Cargo.toml | 22 +++++++++++-------- benchmark/Cargo.toml | 30 ++++++++++++++------------ ffi/Cargo.toml | 11 ++++++---- firewood-macros/Cargo.toml | 7 ++++-- firewood/Cargo.toml | 44 +++++++++++++++++++++----------------- fwdctl/Cargo.toml | 16 ++++++++------ storage/Cargo.toml | 42 ++++++++++++++++++++---------------- triehash/Cargo.toml | 9 +++++--- 8 files changed, 104 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95a9badbeb9e..49edec0002ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,21 +63,25 @@ firewood-ffi = { path = "ffi", version = "0.0.9" } firewood-triehash = { path = "triehash", version = "0.0.9" } # common dependencies +coarsetime = "0.1.36" +env_logger = "0.11.8" +fastrace = "0.7.14" +hex = "0.4.3" +log = "0.4.27" metrics = "0.24.2" metrics-util = "0.20.0" +rand_distr = "0.5.1" sha2 = "0.10.9" -tokio = "1.46.0" -clap = { version = "4.5.40", features = ["derive"] } -fastrace = "0.7.14" -thiserror = "2.0.12" -coarsetime = "0.1.36" -env_logger = "0.11.8" smallvec = "1.15.1" +test-case = "3.3.1" +thiserror = "2.0.12" +tokio = "1.46.1" +clap = { version = "4.5.41", features = ["derive"] } # common dev dependencies -rand = "0.9.1" criterion = "0.6.0" -pprof = "0.15.0" -tempfile = "3.20.0" ethereum-types = "0.15.1" hex-literal = "1.0.0" +pprof = "0.15.0" +rand = "0.9.2" +tempfile = "3.20.0" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index a9741a8ea6ed..a7c10efd6e6f 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -18,24 +18,26 @@ name = "benchmark" path = "src/main.rs" [dependencies] -firewood.workspace = true -hex = "0.4.3" +# Workspace dependencies clap = { workspace = true, features = ['string'] } -sha2 = { workspace = true } -metrics = { workspace = true } -metrics-util = { workspace = true } -metrics-exporter-prometheus = "0.17.2" -tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } -rand = "0.9.1" -rand_distr = "0.5.1" -pretty-duration = "0.1.1" -env_logger = { workspace = true } -log = "0.4.27" +env_logger.workspace = true fastrace = { workspace = true, features = ["enable"] } -fastrace-opentelemetry = { version = "0.12.0" } -opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } +firewood.workspace = true +hex.workspace = true +log.workspace = true +metrics.workspace = true +metrics-util.workspace = true +rand.workspace = true +rand_distr.workspace = true +sha2.workspace = true +tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } +# Regular dependencies +fastrace-opentelemetry = { version = "0.13.0" } +metrics-exporter-prometheus = "0.17.2" opentelemetry = "0.30.0" +opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry_sdk = "0.30.0" +pretty-duration = "0.1.1" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 374c08ba6f57..26f44ea302d3 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -20,13 +20,16 @@ rust-version.workspace = true crate-type = ["staticlib"] [dependencies] +# Workspace dependencies +coarsetime.workspace = true firewood.workspace = true -metrics = { workspace = true } -metrics-util = { workspace = true } +metrics.workspace = true +metrics-util.workspace = true +# Regular dependencies chrono = "0.4.41" oxhttp = "0.3.1" -coarsetime = { workspace = true } -env_logger = { workspace = true, optional = true} +# Optional dependencies +env_logger = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/firewood-macros/Cargo.toml b/firewood-macros/Cargo.toml index dea4abc5bd40..f3e5fb905c6c 100644 --- a/firewood-macros/Cargo.toml +++ b/firewood-macros/Cargo.toml @@ -15,14 +15,17 @@ rust-version.workspace = true proc-macro = true [dependencies] +# Regular dependencies proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full", "extra-traits"] } [dev-dependencies] +# Workspace dependencies +coarsetime.workspace = true +metrics.workspace = true +# Regular dependencies trybuild = "1.0" -metrics = "0.24" -coarsetime = "0.1" [lints] workspace = true diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 83723bb61e84..67cc3138ae66 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -23,19 +23,21 @@ readme.workspace = true rust-version.workspace = true [dependencies] +# Workspace dependencies +coarsetime.workspace = true +fastrace.workspace = true +firewood-macros.workspace = true +hex.workspace = true +metrics.workspace = true +sha2.workspace = true +test-case.workspace = true +thiserror.workspace = true +tokio.workspace = true +# Regular dependencies aquamarine = "0.6.0" async-trait = "0.1.88" futures = "0.3.31" -hex = "0.4.3" -metrics = { workspace = true } -sha2 = { workspace = true } -test-case = "3.3.1" -thiserror = { workspace = true } typed-builder = "0.21.0" -fastrace = { workspace = true } -coarsetime = { workspace = true } -firewood-macros.workspace = true -tokio = "1.0.0" [features] default = [] @@ -46,20 +48,22 @@ branch_factor_256 = [ "firewood-storage/branch_factor_256" ] ethhash = [ "firewood-storage/ethhash" ] [dev-dependencies] -firewood-triehash.workspace = true +# Workspace dependencies +clap = { workspace = true, features = ['derive'] } criterion = { workspace = true, features = ["async_tokio"] } -rand = { workspace = true } -rand_distr = "0.5.1" -clap = { version = "4.5.40", features = ['derive'] } +env_logger.workspace = true +ethereum-types.workspace = true +firewood-triehash.workspace = true +hex-literal.workspace = true pprof = { workspace = true, features = ["flamegraph"] } -tempfile = { workspace = true } -tokio = { version = "1.46.0", features = ["rt", "sync", "macros", "rt-multi-thread"] } -ethereum-types = { workspace = true } -sha3 = "0.10.8" -plain_hasher = "0.2.3" -hex-literal = { workspace = true } -env_logger = "0.11.8" +rand.workspace = true +rand_distr.workspace = true +tempfile.workspace = true +tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } +# Regular dependencies hash-db = "0.16.0" +plain_hasher = "0.2.3" +sha3 = "0.10.8" [[bench]] name = "hashops" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index ce36248c451c..7b9daa93c8af 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -23,26 +23,30 @@ name = "fwdctl" path = "src/main.rs" [dependencies] +# Workspace dependencies +clap = { workspace = true, features = ["cargo"] } +env_logger.workspace = true firewood.workspace = true firewood-storage.workspace = true -clap = { workspace = true, features = ["cargo"] } -env_logger = { workspace = true } -log = "0.4.27" +hex.workspace = true +log.workspace = true tokio = { workspace = true, features = ["full"] } -futures-util = "0.3.31" -hex = "0.4.3" +# Regular dependencies csv = "1.3.1" +futures-util = "0.3.31" nonzero_ext = "0.3.0" [features] ethhash = ["firewood/ethhash"] [dev-dependencies] +# Workspace dependencies +rand.workspace = true +# Regular dependencies anyhow = "1.0.98" assert_cmd = "2.0.17" predicates = "3.1.3" serial_test = "3.2.0" -rand = { workspace = true } [lints] workspace = true diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 12710b271d9f..b7d0ed0925e2 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -17,36 +17,40 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +# Workspace dependencies +coarsetime.workspace = true +fastrace.workspace = true +hex.workspace = true +metrics.workspace = true +sha2.workspace = true +smallvec = { workspace = true, features = ["write", "union"] } +thiserror.workspace = true +# Regular dependencies +arc-swap = "1.7.1" +bitfield = "0.19.1" bitflags = "2.9.1" +bytemuck = "1.23.1" +bytemuck_derive = "1.10.0" enum-as-inner = "0.6.1" -hex = "0.4.3" -smallvec = { workspace = true, features = ["write", "union"] } -sha2 = { workspace = true } integer-encoding = "4.0.2" -arc-swap = "1.7.1" lru = "0.16.0" -metrics = { workspace = true } -log = { version = "0.4.27", optional = true } -bytemuck = "1.23.1" -bytemuck_derive = "1.9.3" -bitfield = "0.19.1" -fastrace = { workspace = true } -io-uring = { version = "0.7.8", optional = true } +nonzero_ext = "0.3.0" +semver = "1.0.26" triomphe = "0.1.14" -coarsetime = { workspace = true } +# Optional dependencies +bytes = { version = "1.10.1", optional = true } +io-uring = { version = "0.7.8", optional = true } +log = { version = "0.4.27", optional = true } rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } -bytes = { version = "1.10.1", optional = true } -thiserror = { workspace = true } -semver = "1.0.26" -nonzero_ext = "0.3.0" [dev-dependencies] -rand = { workspace = true } -test-case = "3.3.1" +# Workspace dependencies criterion = { workspace = true, features = ["async_tokio", "html_reports"] } pprof = { workspace = true, features = ["flamegraph"] } -tempfile = { workspace = true } +rand.workspace = true +tempfile.workspace = true +test-case.workspace = true [features] logger = ["log"] diff --git a/triehash/Cargo.toml b/triehash/Cargo.toml index d165c341fb4b..d39eac3f2194 100644 --- a/triehash/Cargo.toml +++ b/triehash/Cargo.toml @@ -9,16 +9,19 @@ edition.workspace = true rust-version.workspace = true [dependencies] +# Regular dependencies hash-db = "0.16.0" rlp = "0.6" [dev-dependencies] -criterion = { workspace = true } +# Workspace dependencies +criterion.workspace = true +ethereum-types.workspace = true +hex-literal.workspace = true +# Regular dependencies keccak-hasher = "0.16.0" -ethereum-types = { workspace = true } tiny-keccak = { version = "2.0", features = ["keccak"] } trie-standardmap = "0.16.0" -hex-literal = { workspace = true } [[bench]] name = "triehash" From b369e1dbdd06035eb8edd2545820870d2d2a0347 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Jul 2025 09:11:46 -0700 Subject: [PATCH 0836/1053] feat(async-iterator): Implement (#1096) I needed the iterator during some debugging but it panicked due to a todo(). The implementation was trivial so I added it. It could probably use some tests as a followup, especially if we need it for our current integration efforts. --- firewood/src/db.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7865e5d9434a..57ec9e009f67 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -126,9 +126,12 @@ impl api::DbView for HistoricalRev { fn iter_option( &self, - _first_key: Option, + first_key: Option, ) -> Result, api::Error> { - todo!() + match first_key { + Some(key) => Ok(MerkleKeyValueStream::from_key(self, key)), + None => Ok(MerkleKeyValueStream::from(self)), + } } } From 37a217639d005743eb2c0bf426d26ca39f46caaa Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Jul 2025 09:35:32 -0700 Subject: [PATCH 0837/1053] chore: Add propose-on-propose test (#1097) During debugging, the ffi test TestProposeFromProposal failed, even though all the firewood tests passed. We shouldn't be using ffi to test some functionality in firewood, so I ported the test into the firewood repo. --- firewood/src/db.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 57ec9e009f67..fcf4a4583840 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -703,6 +703,75 @@ mod test { assert_eq!(&*value, b"proposal_value"); } + /// Test that proposing on a proposal works as expected + /// + /// Test creates two batches and proposes them, and verifies that the values are in the correct proposal. + /// It then commits them one by one, and verifies the latest committed version is correct. + #[tokio::test] + async fn test_propose_on_proposal() { + // number of keys and values to create for this test + const N: usize = 20; + + let db = testdb().await; + + // create N keys and values like (key0, value0)..(keyN, valueN) + let (keys, vals): (Vec<_>, Vec<_>) = (0..N) + .map(|i| { + ( + format!("key{i}").into_bytes(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + // create two batches, one with the first half of keys and values, and one with the last half keys and values + let mut kviter = keys + .iter() + .zip(vals.iter()) + .map(|(k, v)| BatchOp::Put { key: k, value: v }); + let batch1 = kviter.by_ref().take(N / 2).collect(); + let batch2 = kviter.collect(); + + // create two proposals, second one has a base of the first one + let proposal1 = db.propose(batch1).await.unwrap(); + let proposal2 = proposal1.clone().propose(batch2).await.unwrap(); + + // iterate over the keys and values again, checking that the values are in the correct proposal + let mut kviter = keys.iter().zip(vals.iter()); + + // first half of the keys should be in both proposals + for (k, v) in kviter.by_ref().take(N / 2) { + assert_eq!(&proposal1.val(k).await.unwrap().unwrap(), v); + assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + } + + // remaining keys should only be in the second proposal + for (k, v) in kviter { + // second half of keys are in the second proposal + assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + // but not in the first + assert_eq!(proposal1.val(k).await.unwrap(), None); + } + + proposal1.commit().await.unwrap(); + + // all keys are still in the second proposal (first is no longer accessible) + for (k, v) in keys.iter().zip(vals.iter()) { + assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + } + + // commit the second proposal + proposal2.commit().await.unwrap(); + + // all keys are in the database + let committed = db.root_hash().await.unwrap().unwrap(); + let revision = db.revision(committed).await.unwrap(); + + for (k, v) in keys.into_iter().zip(vals.into_iter()) { + assert_eq!(revision.val(k).await.unwrap().unwrap(), v); + } + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, From f2876b291ed892b732bac270253ff1262b5b717e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 21 Jul 2025 11:27:32 -0700 Subject: [PATCH 0838/1053] chore: Implement newtype for LInearAddress (#1086) Took over the diff from @Salonii02 , see PR#1062 or the commit history for their original commit and my changes. Basically the same as PR#1062 with these changes: - Warning cleanups - serde->bytemuck - array dereference - more liberal use of into() - Implement LowerHex and delegate Display handling I'm not happy with where LinearAddress lives, it probably belongs one level up, but it can move later. This adds a lot of value. --------- Co-authored-by: Saloni Co-authored-by: Brandon LeBlanc --- storage/Cargo.toml | 1 + storage/benches/serializer.rs | 3 +- storage/src/checker/mod.rs | 18 ++-- storage/src/checker/range_set.rs | 26 +++-- storage/src/lib.rs | 2 +- storage/src/linear/filebacked.rs | 4 +- storage/src/linear/mod.rs | 3 +- storage/src/node/mod.rs | 10 +- storage/src/node/persist.rs | 5 +- storage/src/nodestore/alloc.rs | 170 ++++++++++++++++++++++++++++--- storage/src/nodestore/header.rs | 26 +++-- storage/src/nodestore/mod.rs | 27 ++--- storage/src/nodestore/persist.rs | 24 +++-- 13 files changed, 233 insertions(+), 86 deletions(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index b7d0ed0925e2..6cb9cfe68a7c 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -35,6 +35,7 @@ enum-as-inner = "0.6.1" integer-encoding = "4.0.2" lru = "0.16.0" nonzero_ext = "0.3.0" +num-traits = "0.2.19" semver = "1.0.26" triomphe = "0.1.14" # Optional dependencies diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index a58ce601832e..6c2e9cde31d0 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -12,7 +12,6 @@ use std::array::from_fn; use std::fs::File; -use std::num::NonZeroU64; use std::os::raw::c_int; use criterion::profiler::Profiler; @@ -101,7 +100,7 @@ fn branch(c: &mut Criterion) { children: from_fn(|i| { if i == 0 { Some(firewood_storage::Child::AddressWithHash( - NonZeroU64::new(1).unwrap(), + firewood_storage::LinearAddress::new(1).unwrap(), firewood_storage::HashType::from([0; 32]), )) } else { diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index c2068ed0d5f5..67a12b9f771b 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -6,7 +6,6 @@ use range_set::LinearAddressRangeSet; use crate::logger::warn; use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata}; -use crate::nodestore::is_aligned; use crate::{ CheckerError, Committed, HashType, HashedNodeReader, LinearAddress, Node, NodeReader, NodeStore, Path, StoredAreaParent, TrieNodeParent, WritableStorage, hash_node, @@ -154,7 +153,7 @@ impl NodeStore { address: LinearAddress, parent_ptr: StoredAreaParent, ) -> Result<(), CheckerError> { - if !is_aligned(address) { + if !address.is_aligned() { return Err(CheckerError::AreaMisaligned { address, parent_ptr, @@ -194,7 +193,7 @@ impl NodeStore { }; let next_addr = current_addr - .checked_add(area_size) + .advance(area_size) .expect("address overflow is impossible"); match next_addr.cmp(&leaked_range.end) { Ordering::Equal => { @@ -222,7 +221,7 @@ impl NodeStore { for (area_index, area_size) in AREA_SIZES.iter().enumerate().rev() { loop { let next_addr = current_addr - .checked_add(*area_size) + .advance(*area_size) .expect("address overflow is impossible"); if next_addr <= leaked_range.end { leaked.push((current_addr, area_index as AreaIndex)); @@ -244,6 +243,8 @@ mod test { #![expect(clippy::unwrap_used)] #![expect(clippy::indexing_slicing)] + use nonzero_ext::nonzero; + use super::*; use crate::linear::memory::MemStore; use crate::nodestore::NodeStoreHeader; @@ -320,7 +321,6 @@ mod test { ) } - use nonzero_ext::nonzero; use std::collections::HashMap; #[test] @@ -470,13 +470,13 @@ mod test { } else { // we are not freeing this area, free the aggregated areas before this one if let Some((start, end)) = area_to_free { - leaked.push(start..end); + leaked.push(start..end.into()); area_to_free = None; } } } if let Some((start, end)) = area_to_free { - leaked.push(start..end); + leaked.push(start..end.into()); } // check the leaked areas @@ -513,7 +513,7 @@ mod test { test_write_zeroed_area(&nodestore, leaked_range_size, NodeStoreHeader::SIZE); // check the leaked areas - let leaked_range = nonzero!(NodeStoreHeader::SIZE) + let leaked_range = nonzero!(NodeStoreHeader::SIZE).into() ..LinearAddress::new( NodeStoreHeader::SIZE .checked_add(leaked_range_size) @@ -566,7 +566,7 @@ mod test { // check the leaked areas let leaked_range = - nonzero!(NodeStoreHeader::SIZE)..LinearAddress::new(high_watermark).unwrap(); + nonzero!(NodeStoreHeader::SIZE).into()..LinearAddress::new(high_watermark).unwrap(); let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = nodestore .split_range_into_leaked_areas(leaked_range) diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index 42787756e276..b48869a93e72 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -221,7 +221,7 @@ pub(super) struct LinearAddressRangeSet { #[expect(clippy::result_large_err)] impl LinearAddressRangeSet { - const NODE_STORE_ADDR_START: LinearAddress = LinearAddress::new(NodeStoreHeader::SIZE).unwrap(); + const NODE_STORE_START_ADDR: LinearAddress = LinearAddress::new(NodeStoreHeader::SIZE).unwrap(); pub(super) fn new(db_size: u64) -> Result { if db_size < NodeStoreHeader::SIZE { @@ -239,7 +239,7 @@ impl LinearAddressRangeSet { Ok(Self { range_set: RangeSet::new(), - max_addr, // STORAGE_AREA_START..U64::MAX + max_addr, }) } @@ -249,18 +249,16 @@ impl LinearAddressRangeSet { size: u64, ) -> Result<(), CheckerError> { let start = addr; - let end = start - .checked_add(size) - .ok_or(CheckerError::AreaOutOfBounds { - start, - size, - bounds: Self::NODE_STORE_ADDR_START..self.max_addr, - })?; // This can only happen due to overflow - if addr < Self::NODE_STORE_ADDR_START || end > self.max_addr { + let end = start.advance(size).ok_or(CheckerError::AreaOutOfBounds { + start, + size, + bounds: Self::NODE_STORE_START_ADDR..self.max_addr, + })?; // This can only happen due to overflow + if addr < Self::NODE_STORE_START_ADDR || end > self.max_addr { return Err(CheckerError::AreaOutOfBounds { start: addr, size, - bounds: Self::NODE_STORE_ADDR_START..self.max_addr, + bounds: Self::NODE_STORE_START_ADDR..self.max_addr, }); } @@ -277,7 +275,7 @@ impl LinearAddressRangeSet { pub(super) fn complement(&self) -> Self { let complement_set = self .range_set - .complement(&Self::NODE_STORE_ADDR_START, &self.max_addr); + .complement(&Self::NODE_STORE_START_ADDR, &self.max_addr); Self { range_set: complement_set, @@ -655,7 +653,7 @@ mod test_linear_address_range_set { let size2 = 1024; let db_size = 0x2000; - let db_begin = LinearAddressRangeSet::NODE_STORE_ADDR_START; + let db_begin = LinearAddressRangeSet::NODE_STORE_START_ADDR; let start1_addr = LinearAddress::new(start1).unwrap(); let end1_addr = LinearAddress::new(start1 + size1).unwrap(); let start2_addr = LinearAddress::new(start2).unwrap(); @@ -693,7 +691,7 @@ mod test_linear_address_range_set { #[test] fn test_complement_with_empty() { - let db_size = LinearAddressRangeSet::NODE_STORE_ADDR_START; + let db_size = LinearAddressRangeSet::NODE_STORE_START_ADDR; let visited = LinearAddressRangeSet::new(db_size.get()).unwrap(); let complement = visited.complement().into_iter().collect::>(); assert_eq!(complement, vec![]); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 676ceba3d6ad..6611d08acebb 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -196,7 +196,7 @@ pub enum CheckerError { /// The start address of a stored area is not a multiple of 16 #[error( "The start address of a stored area is not a multiple of {}: {address} (parent: {parent_ptr:?})", - nodestore::alloc::MIN_AREA_SIZE + nodestore::alloc::LinearAddress::MIN_AREA_SIZE )] AreaMisaligned { /// The start address of the stored area diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 0a71c9f9cd5c..b15a37c05fdc 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -214,11 +214,11 @@ impl WritableStorage for FileBacked { fn write_cached_nodes<'a>( &self, - nodes: impl Iterator, &'a SharedNode)>, + nodes: impl Iterator, ) -> Result<(), FileIoError> { let mut guard = self.cache.lock().expect("poisoned lock"); for (addr, node) in nodes { - guard.put(*addr, node.clone()); + guard.put(addr, node.clone()); } Ok(()) } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index d1b4c7fc9c20..6a3c374847ca 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -21,7 +21,6 @@ use std::fmt::Debug; use std::io::Read; -use std::num::NonZero; use std::ops::Deref; use std::path::PathBuf; @@ -184,7 +183,7 @@ pub trait WritableStorage: ReadableStorage { /// Write all nodes to the cache (if any) fn write_cached_nodes<'a>( &self, - _nodes: impl Iterator, &'a SharedNode)>, + _nodes: impl Iterator, ) -> Result<(), FileIoError> { Ok(()) } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 14cad0ee076b..c39c085f1cde 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -23,7 +23,7 @@ )] use crate::node::branch::ReadSerializable; -use crate::{HashType, Path, SharedNode}; +use crate::{HashType, LinearAddress, Path, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; pub use branch::{BranchNode, Child}; @@ -32,13 +32,11 @@ use integer_encoding::{VarInt, VarIntReader as _}; pub use leaf::LeafNode; use std::fmt::Debug; use std::io::{Error, Read, Write}; -use std::num::NonZero; pub mod branch; mod leaf; pub mod path; pub mod persist; - /// A node, either a Branch or Leaf // TODO: explain why Branch is boxed but Leaf is not @@ -412,7 +410,8 @@ impl Node { let hash = HashType::from_reader(&mut serialized)?; *child = Some(Child::AddressWithHash( - NonZero::new(address).ok_or(Error::other("zero address in child"))?, + LinearAddress::new(address) + .ok_or(Error::other("zero address in child"))?, hash, )); } @@ -429,7 +428,8 @@ impl Node { let hash = HashType::from_reader(&mut serialized)?; children[position] = Some(Child::AddressWithHash( - NonZero::new(address).ok_or(Error::other("zero address in child"))?, + LinearAddress::new(address) + .ok_or(Error::other("zero address in child"))?, hash, )); } diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index ea265d82082d..21a7ca76bf81 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -191,6 +191,7 @@ mod test { Option::from(&maybe_persisted_node) ); + let addr = LinearAddress::new(addr.get()).unwrap(); maybe_persisted_node.persist_at(addr); assert!(maybe_persisted_node.as_shared_node(&store).is_err()); assert_eq!(Some(addr), Option::from(&maybe_persisted_node)); @@ -199,7 +200,7 @@ mod test { #[test] fn test_from_linear_address() { - let addr = nonzero!(1024u64); + let addr: LinearAddress = nonzero!(1024u64).into(); let maybe_persisted_node = MaybePersistedNode::from(addr); assert_eq!(Some(addr), Option::from(&maybe_persisted_node)); } @@ -226,7 +227,7 @@ mod test { assert_eq!(triomphe::Arc::strong_count(&node), 2); // Persist the original - let addr = nonzero!(4096u64); + let addr = nonzero!(1024u64).into(); original.persist_at(addr); // Both original and clone should now be persisted since they share the same ArcSwap diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index b2a9cbc00624..1f60c4d221f3 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -23,10 +23,11 @@ use crate::linear::FileIoError; use crate::logger::trace; use crate::node::branch::{ReadSerializable, Serializable}; -use crate::nodestore::is_aligned; use integer_encoding::VarIntReader; use metrics::counter; + use sha2::{Digest, Sha256}; +use std::fmt; use std::io::{Error, ErrorKind, Read}; use std::iter::FusedIterator; use std::num::NonZeroU64; @@ -113,9 +114,9 @@ pub const fn index_name(index: usize) -> &'static str { /// This is not usize because we can store this as a single byte pub type AreaIndex = u8; -pub const NUM_AREA_SIZES: usize = AREA_SIZES.len(); -pub const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; -pub const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; +const NUM_AREA_SIZES: usize = AREA_SIZES.len(); +const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; +const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; #[inline] pub fn new_area_index(n: usize) -> AreaIndex { @@ -147,12 +148,143 @@ pub fn area_size_to_index(n: u64) -> Result { }) } -/// Objects cannot be stored at the zero address, so a [`LinearAddress`] is guaranteed not -/// to be zero. This reserved zero can be used as a [None] value for some use cases. In particular, -/// branches can use `Option` which is the same size as a [`LinearAddress`] -pub type LinearAddress = NonZeroU64; +/// A linear address in the nodestore storage. +/// +/// This represents a non-zero address in the linear storage space. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct LinearAddress(NonZeroU64); + +#[expect(unsafe_code)] +unsafe impl bytemuck::ZeroableInOption for LinearAddress {} +#[expect(unsafe_code)] +unsafe impl bytemuck::PodInOption for LinearAddress {} + +impl LinearAddress { + /// Create a new `LinearAddress`, returns None if value is zero. + #[inline] + #[must_use] + pub const fn new(addr: u64) -> Option { + match NonZeroU64::new(addr) { + Some(addr) => Some(LinearAddress(addr)), + None => None, + } + } + + /// Get the underlying address as u64. + #[inline] + #[must_use] + pub const fn get(self) -> u64 { + self.0.get() + } + + /// Check if the address is 8-byte aligned. + #[inline] + #[must_use] + pub const fn is_aligned(self) -> bool { + self.0.get() % (Self::MIN_AREA_SIZE) == 0 + } + + /// The maximum area size available for allocation. + pub const MAX_AREA_SIZE: u64 = *AREA_SIZES.last().unwrap(); + + /// The minimum area size available for allocation. + pub const MIN_AREA_SIZE: u64 = *AREA_SIZES.first().unwrap(); + + /// Returns the number of different area sizes available. + #[inline] + #[must_use] + pub const fn num_area_sizes() -> usize { + const { AREA_SIZES.len() } + } + + /// Returns the inner `NonZeroU64` + #[inline] + #[must_use] + pub const fn into_nonzero(self) -> NonZeroU64 { + self.0 + } + + /// Advances a `LinearAddress` by `n` bytes. + /// + /// Returns `None` if the result overflows a u64 + /// Some(LinearAddress) otherwise + /// + #[inline] + #[must_use] + pub const fn advance(self, n: u64) -> Option { + match self.0.checked_add(n) { + // overflowed + None => None, + + // It is impossible to add a non-zero positive number to a u64 and get 0 without + // overflowing, so we don't check for that here, and panic instead. + Some(sum) => Some(LinearAddress(sum)), + } + } +} + +impl std::ops::Add for LinearAddress { + type Output = Self; + fn add(self, other: Self) -> Self { + self.checked_add(other.get()) + .map(LinearAddress) + .expect("non zero result") + } +} -pub type FreeLists = [Option; NUM_AREA_SIZES]; +impl num_traits::CheckedAdd for LinearAddress { + fn checked_add(&self, other: &Self) -> Option { + self.0.checked_add(other.get()).map(LinearAddress) + } +} + +impl std::ops::Deref for LinearAddress { + type Target = NonZeroU64; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for LinearAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(&self.get(), f) + } +} + +impl fmt::LowerHex for LinearAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::LowerHex::fmt(&self.get(), f) + } +} +impl From for u64 { + fn from(addr: LinearAddress) -> Self { + addr.get() + } +} + +impl From for LinearAddress { + fn from(addr: NonZeroU64) -> Self { + LinearAddress(addr) + } +} + +/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the +/// `NodeStoreHeader` is a [`StoredArea`]. +/// +/// As an overview of what this looks like stored, we get something like this: +/// - Byte 0: The index of the area size +/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf +/// - Bytes 2..n: The actual data +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct StoredArea { + /// Index in [`AREA_SIZES`] of this area's size + area_size_index: AreaIndex, + area: T, +} + +/// `FreeLists` is an array of `Option` for each area size. +pub type FreeLists = [Option; AREA_SIZES.len()]; /// A [`FreeArea`] is stored at the start of the area that contained a node that /// has been freed. @@ -344,7 +476,7 @@ impl NodeStore { return Ok(node); } - debug_assert!(is_aligned(addr)); + debug_assert!(addr.is_aligned()); // saturating because there is no way we can be reading at u64::MAX // and this will fail very soon afterwards @@ -484,7 +616,7 @@ impl NodeStore, S> { let addr = LinearAddress::new(self.header.size()).expect("node store size can't be 0"); self.header .set_size(self.header.size().saturating_add(area_size)); - debug_assert!(is_aligned(addr)); + debug_assert!(addr.is_aligned()); trace!("Allocating from end: addr: {addr:?}, size: {index}"); Ok((addr, index)) } @@ -535,7 +667,7 @@ impl NodeStore { let Some(addr) = node.as_linear_address() else { return Ok(()); }; - debug_assert!(is_aligned(addr)); + debug_assert!(addr.is_aligned()); let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {area_size_index}"); @@ -581,9 +713,9 @@ impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { } } - #[allow( + #[expect( clippy::type_complexity, - reason = "this iterator is private and will not be exposed" + reason = "TODO: we need some additional newtypes here" )] fn next_with_parent( &mut self, @@ -630,7 +762,7 @@ pub(crate) struct FreeAreaWithMetadata { pub(crate) struct FreeListsIterator<'a, S: ReadableStorage> { storage: &'a S, free_lists_iter: std::iter::Skip< - std::iter::Enumerate>>>, + std::iter::Enumerate>>, >, current_free_list_id: AreaIndex, free_list_iter: FreeListIterator<'a, S>, @@ -799,7 +931,6 @@ mod tests { use rand::seq::IteratorRandom; use test_case::test_case; - // StoredArea::new(12, Area::::Free(FreeArea::new(LinearAddress::new(42)))); #[test_case(&[0x01, 0x01, 0x01, 0x2a], Some((1, 42)); "old format")] // StoredArea::new(12, Area::::Free(FreeArea::new(None))); #[test_case(&[0x02, 0x01, 0x00], Some((2, 0)); "none")] @@ -961,4 +1092,11 @@ mod tests { assert_eq!(next.unwrap().unwrap(), expected.unwrap()); } } + + #[test] + fn const_expr_tests() { + // these are const expr + let _ = const { LinearAddress::new(0) }; + let _ = const { LinearAddress::new(1).unwrap().advance(1u64) }; + } } diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index f4c644e62f2d..937e640f3689 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -148,7 +148,7 @@ impl Version { /// Persisted metadata for a `NodeStore`. /// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. -#[derive(Copy, Debug, PartialEq, Eq, Clone, NoUninit, AnyBitPattern)] +#[derive(Copy, Debug, PartialEq, Eq, Clone)] #[repr(C)] pub struct NodeStoreHeader { /// Identifies the version of firewood used to create this `NodeStore`. @@ -172,10 +172,13 @@ impl NodeStoreHeader { /// We also want it aligned to a disk block pub const SIZE: u64 = 2048; - /// Number of extra bytes to write on the first creation of the `NodeStoreHeader` - /// (zero-padded) - /// also a compile time check to prevent setting SIZE too small - pub const EXTRA_BYTES: usize = Self::SIZE as usize - std::mem::size_of::(); + // Compile-time assertion that SIZE is large enough for the header + const _ASSERT_SIZE: () = assert!(Self::SIZE as usize >= std::mem::size_of::()); + + /// Deserialize a `NodeStoreHeader` from bytes using bytemuck + pub fn from_bytes(bytes: &[u8]) -> &Self { + bytemuck::from_bytes(bytes) + } pub fn new() -> Self { Self { @@ -294,6 +297,11 @@ impl NodeStoreHeader { } } +#[expect(unsafe_code)] +unsafe impl bytemuck::Zeroable for NodeStoreHeader {} +#[expect(unsafe_code)] +unsafe impl bytemuck::Pod for NodeStoreHeader {} + #[cfg(test)] #[expect(clippy::unwrap_used)] mod tests { @@ -324,12 +332,12 @@ mod tests { let node_store = NodeStore::new_empty_proposal(memstore.into()); // Check the empty header is written at the start of the ReadableStorage. - let mut header = NodeStoreHeader::new(); let mut header_stream = node_store.storage.stream_from(0).unwrap(); - let header_bytes = bytemuck::bytes_of_mut(&mut header); - header_stream.read_exact(header_bytes).unwrap(); + let mut header_bytes = vec![0u8; std::mem::size_of::()]; + header_stream.read_exact(&mut header_bytes).unwrap(); + let header = NodeStoreHeader::from_bytes(&header_bytes); assert_eq!(header.version, Version::new()); - let empty_free_list: super::FreeLists = Default::default(); + let empty_free_list: FreeLists = Default::default(); assert_eq!(*header.free_lists(), empty_free_list); } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 0e2fedc6a193..1e6db8f8d783 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -91,11 +91,6 @@ use crate::{FileBacked, FileIoError, Path, ReadableStorage, SharedNode, TrieHash use super::linear::WritableStorage; -#[inline] -pub(crate) const fn is_aligned(addr: LinearAddress) -> bool { - addr.get() % alloc::MIN_AREA_SIZE == 0 -} - impl NodeStore { /// Open an existing [`NodeStore`] /// Assumes the header is written in the [`ReadableStorage`]. @@ -105,9 +100,8 @@ impl NodeStore { /// Returns a [`FileIoError`] if the header cannot be read or validated. pub fn open(storage: Arc) -> Result { let mut stream = storage.stream_from(0)?; - let mut header = NodeStoreHeader::new(); - let header_bytes = bytemuck::bytes_of_mut(&mut header); - if let Err(e) = stream.read_exact(header_bytes) { + let mut header_bytes = vec![0u8; std::mem::size_of::()]; + if let Err(e) = stream.read_exact(&mut header_bytes) { if e.kind() == std::io::ErrorKind::UnexpectedEof { return Self::new_empty_committed(storage.clone()); } @@ -116,6 +110,7 @@ impl NodeStore { drop(stream); + let header = *NodeStoreHeader::from_bytes(&header_bytes); header .validate() .map_err(|e| storage.file_io_error(e, 0, Some("header read".to_string())))?; @@ -753,16 +748,16 @@ mod tests { use crate::linear::memory::MemStore; use crate::{BranchNode, Child, LeafNode}; use arc_swap::access::DynGuard; - use nonzero_ext::nonzero; + use test_case::test_case; use super::*; - use alloc::{AREA_SIZES, MAX_AREA_SIZE, MIN_AREA_SIZE, NUM_AREA_SIZES, area_size_to_index}; + use alloc::{AREA_SIZES, area_size_to_index}; #[test] fn area_sizes_aligned() { for area_size in &AREA_SIZES { - assert_eq!(area_size % MIN_AREA_SIZE, 0); + assert_eq!(area_size % LinearAddress::MIN_AREA_SIZE, 0); } } @@ -778,7 +773,7 @@ mod tests { assert_eq!(area_size_to_index(area_size - 1).unwrap(), i as AreaIndex); } - if i < NUM_AREA_SIZES - 1 { + if i < LinearAddress::num_area_sizes() - 1 { // 1 more than top of range goes to next range assert_eq!( area_size_to_index(area_size + 1).unwrap(), @@ -787,11 +782,11 @@ mod tests { } } - for i in 0..=MIN_AREA_SIZE { + for i in 0..=LinearAddress::MIN_AREA_SIZE { assert_eq!(area_size_to_index(i).unwrap(), 0); } - assert!(area_size_to_index(MAX_AREA_SIZE + 1).is_err()); + assert!(area_size_to_index(LinearAddress::MAX_AREA_SIZE + 1).is_err()); } #[test] @@ -832,7 +827,7 @@ mod tests { value: Some(vec![9, 10, 11].into_boxed_slice()), children: from_fn(|i| { if i == 15 { - Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) } else { None } @@ -842,7 +837,7 @@ mod tests { partial_path: Path::from([6, 7, 8]), value: Some(vec![9, 10, 11].into_boxed_slice()), children: from_fn(|_| - Some(Child::AddressWithHash(nonzero!(1u64), std::array::from_fn::(|i| i as u8).into())) + Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) ), }; "branch node with all child")] #[test_case( diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 835e51032ea3..e96d964c7ae7 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -68,11 +68,8 @@ impl NodeStore { /// /// Returns a [`FileIoError`] if the header cannot be written. pub fn flush_header_with_padding(&self) -> Result<(), FileIoError> { - let header_bytes = bytemuck::bytes_of(&self.header) - .iter() - .copied() - .chain(std::iter::repeat_n(0u8, NodeStoreHeader::EXTRA_BYTES)) - .collect::>(); + let mut header_bytes = bytemuck::bytes_of(&self.header).to_vec(); + header_bytes.resize(NodeStoreHeader::SIZE as usize, 0); debug_assert_eq!(header_bytes.len(), NodeStoreHeader::SIZE as usize); self.storage.write(0, &header_bytes)?; @@ -191,6 +188,10 @@ impl Iterator for UnPersistedNodeIterator<'_, N> { impl NodeStore, FileBacked> { /// Persist the freelist from this proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the free list cannot be written to storage. #[fastrace::trace(short_name = true)] pub fn flush_freelist(&self) -> Result<(), FileIoError> { // Write the free lists to storage @@ -201,6 +202,10 @@ impl NodeStore, FileBacked> { } /// Persist all the nodes of a proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] #[cfg(not(feature = "io-uring"))] pub fn flush_nodes(&self) -> Result<(), FileIoError> { @@ -214,8 +219,7 @@ impl NodeStore, FileBacked> { } self.storage - .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; - + .write_cached_nodes(self.kind.new.iter().map(|(&addr, (_, node))| (addr, node)))?; let flush_time = flush_start.elapsed().as_millis(); counter!("firewood.flush_nodes").increment(flush_time); @@ -223,6 +227,10 @@ impl NodeStore, FileBacked> { } /// Persist all the nodes of a proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] pub fn flush_nodes(&self) -> Result<(), FileIoError> { @@ -355,7 +363,7 @@ impl NodeStore, FileBacked> { ); self.storage - .write_cached_nodes(self.kind.new.iter().map(|(addr, (_, node))| (addr, node)))?; + .write_cached_nodes(self.kind.new.iter().map(|(&addr, (_, node))| (addr, node)))?; debug_assert!(ring.completion().is_empty()); let flush_time = flush_start.elapsed().as_millis(); From f1d43524523042f4d861c6472ca5a94a9a8595f2 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:08:46 -0400 Subject: [PATCH 0839/1053] style: remove unnecessary string in error (#1104) Obviously any error returned from Firewood is a "Firewood error"... --- ffi/memory.go | 4 ++-- ffi/revision.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ffi/memory.go b/ffi/memory.go index 8d28e3674986..85342df55a17 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -15,8 +15,8 @@ import ( ) var ( - errNilStruct = errors.New("firewood error: nil struct pointer cannot be freed") - errBadValue = errors.New("firewood error: value from cgo formatted incorrectly") + errNilStruct = errors.New("nil struct pointer cannot be freed") + errBadValue = errors.New("value from cgo formatted incorrectly") ) // KeyValue is a key-value pair. diff --git a/ffi/revision.go b/ffi/revision.go index 38afd29ff488..1f24a0c59598 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -13,8 +13,8 @@ import ( ) var ( - errRevisionNotFound = errors.New("firewood error: revision not found") - errInvalidRootLength = fmt.Errorf("firewood error: root hash must be %d bytes", RootLength) + errRevisionNotFound = errors.New("revision not found") + errInvalidRootLength = fmt.Errorf("root hash must be %d bytes", RootLength) ) type Revision struct { @@ -28,7 +28,7 @@ type Revision struct { func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { if handle == nil { - return nil, errors.New("firewood error: nil handle or root") + return nil, errDBClosed } // Check that the root is the correct length. From fb714dd9e99138897b850140a33abd27dbf2f8ca Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Tue, 22 Jul 2025 09:14:31 -0400 Subject: [PATCH 0840/1053] feat: export logs (#1070) ## What This PR extends logging by allowing logs to be written to a file, which can then be scraped by tools such as Promtail. ## How Logging customization is now done via the Firewood config, where clients can enabled logging and if so, specify where to store logs and the level of logging desired. ## Tested Extended `TestMetrics()` to verify that logs are being written when enabled. --------- Signed-off-by: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Co-authored-by: Ron Kuris Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ffi/Cargo.toml | 1 + ffi/firewood.h | 26 +++++++++++ ffi/metrics.go | 24 +++++++++++ ffi/metrics_test.go | 28 +++++++++++- ffi/src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 169 insertions(+), 12 deletions(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 26f44ea302d3..bf8431ef09f9 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,6 +23,7 @@ crate-type = ["staticlib"] # Workspace dependencies coarsetime.workspace = true firewood.workspace = true +log.workspace = true metrics.workspace = true metrics-util.workspace = true # Regular dependencies diff --git a/ffi/firewood.h b/ffi/firewood.h index a40a333a8479..79a4bb946f2e 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -73,6 +73,18 @@ typedef struct CreateOrOpenArgs { bool truncate; } CreateOrOpenArgs; +/** + * Arguments for logging + * + * * `path` - The file path where logs for this process are stored. By + * default, this is set to /tmp/firewood-log.txt + * * `filter_level` - The filter level for logs. By default, this is set to info. + */ +typedef struct LogArgs { + const char *path; + const char *filter_level; +} LogArgs; + /** * Puts the given key-value pairs into the database. * @@ -397,6 +409,20 @@ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, */ struct Value fwd_root_hash(const struct DatabaseHandle *db); +/** + * Start logs for this process. + * + * # Arguments + * + * See `LogArgs`. + * + * # Returns + * + * A `Value` containing {0, null} if the global logger was initialized. + * A `Value` containing {0, "error message"} if an error occurs. + */ +struct Value fwd_start_logs(const struct LogArgs *args); + /** * Start metrics recorder for this process. * diff --git a/ffi/metrics.go b/ffi/metrics.go index eb47dae657d8..f6b51d54f481 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -8,6 +8,7 @@ import "C" import ( "strings" + "unsafe" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" @@ -70,3 +71,26 @@ func GatherMetrics() (string, error) { } return string(b), nil } + +// LogConfig configures logs for this process. +type LogConfig struct { + Path string + FilterLevel string +} + +// Starts global logs. +// This function only needs to be called once. +// An error is returned if this method is called a second time. +func StartLogs(config *LogConfig) error { + args := &C.struct_LogArgs{} + if config.Path != "" { + args.path = C.CString(config.Path) + defer C.free(unsafe.Pointer(args.path)) + } + if config.FilterLevel != "" { + args.filter_level = C.CString(config.FilterLevel) + defer C.free(unsafe.Pointer(args.filter_level)) + } + result := C.fwd_start_logs(args) + return errorFromValue(&result) +} diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go index c3c3253aca46..cdac87a74596 100644 --- a/ffi/metrics_test.go +++ b/ffi/metrics_test.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "net/http" + "os" + "path/filepath" "testing" "time" @@ -19,11 +21,26 @@ func TestMetrics(t *testing.T) { r := require.New(t) ctx := context.Background() - db := newTestDatabase(t) + // test params + var ( + logPath = filepath.Join(t.TempDir(), "firewood.log") + metricsPort = uint16(3000) + ) - metricsPort := uint16(3000) + db := newTestDatabase(t) r.NoError(StartMetricsWithExporter(metricsPort)) + logConfig := &LogConfig{ + Path: logPath, + FilterLevel: "trace", + } + + var logsDisabled bool + if err := StartLogs(logConfig); err != nil { + r.Contains(err.Error(), "logger feature is disabled") + logsDisabled = true + } + // Populate DB keys, vals := kvForTest(10) _, err := db.Update(keys, vals) @@ -74,4 +91,11 @@ func TestMetrics(t *testing.T) { r.NotNil(d) r.Equal(v, *d.Type) } + + if !logsDisabled { + // logs should be non-empty if logging with trace filter level + f, err := os.ReadFile(logPath) + r.NoError(err) + r.NotEmpty(f) + } } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 840f344eddb3..2016b03733ba 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -19,7 +19,7 @@ use std::fmt::{self, Display, Formatter}; use std::ops::Deref; #[cfg(unix)] use std::os::unix::ffi::OsStrExt as _; -use std::path::Path; +use std::path::PathBuf; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex, RwLock}; @@ -916,18 +916,100 @@ unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { args.strategy, )?) .build(); - #[cfg(feature = "logger")] - let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) - .try_init(); - - let path = unsafe { CStr::from_ptr(args.path) }; - #[cfg(unix)] - let path: &Path = OsStr::from_bytes(path.to_bytes()).as_ref(); - #[cfg(windows)] - let path: &Path = OsStr::new(path.to_str().expect("path should be valid UTF-8")).as_ref(); + + let path = to_path(args.path).ok_or("database path is empty")?; Db::new_sync(path, cfg).map_err(|e| e.to_string()) } +/// Arguments for logging +/// +/// * `path` - The file path where logs for this process are stored. By +/// default, this is set to /tmp/firewood-log.txt +/// * `filter_level` - The filter level for logs. By default, this is set to info. +#[repr(C)] +pub struct LogArgs { + path: *const std::ffi::c_char, + filter_level: *const std::ffi::c_char, +} + +/// Start logs for this process. +/// +/// # Arguments +/// +/// See `LogArgs`. +/// +/// # Returns +/// +/// A `Value` containing {0, null} if the global logger was initialized. +/// A `Value` containing {0, "error message"} if an error occurs. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_start_logs(args: Option<&LogArgs>) -> Value { + match args { + Some(log_args) => start_logs(log_args).map_or_else(Into::into, Into::into), + None => String::from("failed to provide args").into(), + } +} + +#[cfg(feature = "logger")] +#[doc(hidden)] +fn start_logs(log_args: &LogArgs) -> Result<(), String> { + use env_logger::Target::Pipe; + use std::fs::OpenOptions; + use std::path::Path; + + let log_path = + to_path(log_args.path).unwrap_or(Path::join(&std::env::temp_dir(), "firewood-log.txt")); + + let log_dir = log_path.parent().unwrap_or_else(|| Path::new(".")); + std::fs::create_dir_all(log_dir).map_err(|e| e.to_string())?; + + let level_str = if log_args.filter_level.is_null() { + "info" + } else { + unsafe { CStr::from_ptr(log_args.filter_level) } + .to_str() + .map_err(|e| e.to_string())? + }; + let level = level_str + .parse::() + .map_err(|e| e.to_string())?; + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(false) + .open(log_path) + .map_err(|e| e.to_string())?; + + env_logger::Builder::new() + .filter_level(level) + .target(Pipe(Box::new(file))) + .try_init() + .map_err(|e| e.to_string())?; + + Ok(()) +} + +#[cfg(not(feature = "logger"))] +#[doc(hidden)] +fn start_logs(_log_args: &LogArgs) -> Result<(), String> { + Err(String::from("logger feature is disabled")) +} + +/// Helper function to convert C String pointers to a path +/// Returns None if the pointer is null or if the string is empty +#[doc(hidden)] +fn to_path(cstr: *const std::ffi::c_char) -> Option { + if cstr.is_null() { + return None; + } + + let cstr = unsafe { CStr::from_ptr(cstr) }; + let osstr = OsStr::from_bytes(cstr.to_bytes()); + + (!osstr.is_empty()).then(|| PathBuf::from(osstr)) +} + #[doc(hidden)] fn manager_config( cache_size: usize, From 65a9deda05b5f7fd1263dffe95314ca0e727204f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Jul 2025 12:10:09 -0700 Subject: [PATCH 0841/1053] feat: Render the commit sha in fwdctl (#1109) Sample output: ```text $ target/debug/fwdctl --version firewood-fwdctl 0.0.9 (234cf6a9a5d8956cef0d19b9a40e1f00026bc516, -ethhash) ``` Output, when git is not on the path: ```text $ target/debug/fwdctl --version firewood-fwdctl 0.0.9 (git not found: No such file or directory (os error 2), -ethhash) ``` Closes #1107 --- fwdctl/build.rs | 36 ++++++++++++++++++++++++++++++++++++ fwdctl/src/main.rs | 1 + fwdctl/tests/cli.rs | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 fwdctl/build.rs diff --git a/fwdctl/build.rs b/fwdctl/build.rs new file mode 100644 index 000000000000..a92302cacef6 --- /dev/null +++ b/fwdctl/build.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::process::Command; + +fn main() { + // Get the git commit SHA + let git_sha = match Command::new("git").args(["rev-parse", "HEAD"]).output() { + Ok(output) => { + if output.status.success() { + String::from_utf8_lossy(&output.stdout).trim().to_string() + } else { + let error_msg = String::from_utf8_lossy(&output.stderr); + format!("git error: {}", error_msg.trim()) + } + } + Err(e) => { + format!("git not found: {e}") + } + }; + + // Check if ethhash feature is enabled + let ethhash_feature = if cfg!(feature = "ethhash") { + "ethhash" + } else { + "-ethhash" + }; + + // Make the git SHA and ethhash status available to the main.rs file + println!("cargo:rustc-env=GIT_COMMIT_SHA={git_sha}"); + println!("cargo:rustc-env=ETHHASH_FEATURE={ethhash_feature}"); + + // Re-run this build script if the git HEAD changes + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/index"); +} diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 7ad10b336c00..ec52a3c0a801 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -18,6 +18,7 @@ pub mod root; #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] +#[command(version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_COMMIT_SHA"), ", ", env!("ETHHASH_FEATURE"), ")"))] struct Cli { #[command(subcommand)] command: Commands, diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 595df47ce528..e4b45b3629cd 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -24,7 +24,7 @@ fn fwdctl_delete_db() -> Result<()> { #[test] #[serial] fn fwdctl_prints_version() -> Result<()> { - let expected_version_output: String = format!("{PRG} {VERSION}\n"); + let expected_version_output: String = format!("{PRG} {VERSION}"); // version is defined and succeeds with the desired output Command::cargo_bin(PRG)? From 3750f3d95fda3fa06ee3e0b7c62024b5c2b2e1db Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 22 Jul 2025 22:30:08 -0700 Subject: [PATCH 0842/1053] chore: refactor verifying value digests (#1119) In #1117, I have a need to reuse this verification method. I have refactored it out into separate steps to make that easier. --- firewood/src/proof.rs | 49 +++++++++++---------------------------- storage/src/hashednode.rs | 24 +++++++++++++++---- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index afae23efa7ff..2414be299abf 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -14,7 +14,6 @@ use firewood_storage::{ BranchNode, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, PathIterItem, Preimage, TrieHash, ValueDigest, }; -use sha2::{Digest, Sha256}; use thiserror::Error; #[derive(Debug, Error)] @@ -158,40 +157,7 @@ impl Proof { expected_value: Option, root_hash: &TrieHash, ) -> Result<(), ProofError> { - let value_digest = self.value_digest(key, root_hash)?; - - let Some(value_digest) = value_digest else { - // This proof proves that `key` maps to None. - if expected_value.is_some() { - return Err(ProofError::ExpectedValue); - } - return Ok(()); - }; - - let Some(expected_value) = expected_value else { - // We were expecting `key` to map to None. - return Err(ProofError::UnexpectedValue); - }; - - match value_digest { - ValueDigest::Value(got_value) => { - // This proof proves that `key` maps to `got_value`. - if got_value != expected_value.as_ref() { - // `key` maps to an unexpected value. - return Err(ProofError::ValueMismatch); - } - } - ValueDigest::Hash(got_hash) => { - // This proof proves that `key` maps to a value - // whose hash is `got_hash`. - let value_hash = Sha256::digest(expected_value.as_ref()); - if got_hash != value_hash.as_slice() { - // `key` maps to an unexpected value. - return Err(ProofError::ValueMismatch); - } - } - } - Ok(()) + verify_opt_value_digest(expected_value, self.value_digest(key, root_hash)?) } /// Returns the value digest associated with the given `key` in the trie revision @@ -280,3 +246,16 @@ where c.next() } + +fn verify_opt_value_digest( + expected_value: Option>, + found_value: Option>>, +) -> Result<(), ProofError> { + match (expected_value, found_value) { + (None, None) => Ok(()), + (Some(_), None) => Err(ProofError::ExpectedValue), + (None, Some(_)) => Err(ProofError::UnexpectedValue), + (Some(ref expected), Some(found)) if found.verify(expected) => Ok(()), + (Some(_), Some(_)) => Err(ProofError::ValueMismatch), + } +} diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 779a9d652d63..de847eb22eff 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -6,15 +6,14 @@ reason = "Found 1 occurrences after enabling the lint." )] +use crate::{BranchNode, HashType, LeafNode, Node, Path}; +use sha2::{Digest, Sha256}; +use smallvec::SmallVec; use std::{ iter::{self}, ops::Deref, }; -use smallvec::SmallVec; - -use crate::{BranchNode, HashType, LeafNode, Node, Path}; - /// Returns the hash of `node`, which is at the given `path_prefix`. #[must_use] pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { @@ -113,6 +112,23 @@ impl Deref for ValueDigest { } } +impl> ValueDigest { + /// Verifies that the value or hash matches the expected value. + pub fn verify(&self, expected: impl AsRef<[u8]>) -> bool { + match self { + Self::Value(got_value) => { + // This proof proves that `key` maps to `got_value`. + got_value.as_ref() == expected.as_ref() + } + Self::Hash(got_hash) => { + // This proof proves that `key` maps to a value + // whose hash is `got_hash`. + got_hash.as_ref() == Sha256::digest(expected.as_ref()).as_slice() + } + } + } +} + /// A node in the trie that can be hashed. pub trait Hashable { /// The key of the node where each byte is a nibble. From 2b2c9a083554f07160d527bc57d513c744a64afc Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 22 Jul 2025 22:36:38 -0700 Subject: [PATCH 0843/1053] feat: update proof types to be generic over mutable or immutable collections (#1121) In a future PR, there will be several cleanups that take advantage of proof being able to switch back and forth from immutable and mutable containers. Particularly in testing, but a few other cases as well. See #1117 for a raw view. The `EmptyProofCollection` is primarily for tests. --- firewood/src/db.rs | 15 ++-- firewood/src/merkle.rs | 9 +- firewood/src/proof.rs | 159 ++++++++++++++++++++++++++++++++++-- firewood/src/range_proof.rs | 4 +- firewood/src/v2/api.rs | 10 ++- firewood/src/v2/emptydb.rs | 10 +-- firewood/src/v2/propose.rs | 8 +- 7 files changed, 179 insertions(+), 36 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index fcf4a4583840..66c587e3474a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -7,10 +7,8 @@ )] use crate::merkle::Merkle; -use crate::proof::{Proof, ProofNode}; -use crate::range_proof::RangeProof; use crate::stream::MerkleKeyValueStream; -use crate::v2::api::{self, KeyType, ValueType}; +use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; @@ -107,10 +105,7 @@ impl api::DbView for HistoricalRev { Ok(merkle.get_value(key.as_ref())?) } - async fn single_key_proof( - &self, - key: K, - ) -> Result, api::Error> { + async fn single_key_proof(&self, key: K) -> Result { let merkle = Merkle::from(self); merkle.prove(key.as_ref()).map_err(api::Error::from) } @@ -120,7 +115,7 @@ impl api::DbView for HistoricalRev { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { + ) -> Result { todo!() } @@ -382,7 +377,7 @@ impl api::DbView for Proposal<'_> { merkle.get_value(key.as_ref()).map_err(api::Error::from) } - async fn single_key_proof(&self, key: K) -> Result, api::Error> { + async fn single_key_proof(&self, key: K) -> Result { let merkle = Merkle::from(self.nodestore.clone()); merkle.prove(key.as_ref()).map_err(api::Error::from) } @@ -392,7 +387,7 @@ impl api::DbView for Proposal<'_> { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { + ) -> Result { todo!() } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5c48467ef400..4b391b4775ec 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -20,8 +20,9 @@ use crate::range_proof::RangeProof; #[cfg(test)] use crate::stream::MerkleKeyValueStream; use crate::stream::PathIterator; +use crate::v2::api::FrozenProof; #[cfg(test)] -use crate::v2::api; +use crate::v2::api::{self, FrozenRangeProof}; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, @@ -178,7 +179,7 @@ impl Merkle { /// Returns a proof that the given key has a certain value, /// or that the key isn't in the trie. - pub fn prove(&self, key: &[u8]) -> Result, ProofError> { + pub fn prove(&self, key: &[u8]) -> Result { let Some(root) = self.root() else { return Err(ProofError::Empty); }; @@ -216,7 +217,7 @@ impl Merkle { }); } - Ok(Proof(proof.into_boxed_slice())) + Ok(Proof::new(proof.into_boxed_slice())) } /// Verify a proof that a key has a certain value, or that the key isn't in the trie. @@ -258,7 +259,7 @@ impl Merkle { start_key: Option<&[u8]>, end_key: Option<&[u8]>, limit: Option, - ) -> Result, Box<[u8]>, ProofNode>, api::Error> { + ) -> Result { if let (Some(k1), Some(k2)) = (&start_key, &end_key) { if k1 > k2 { return Err(api::Error::InvalidRange { diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 2414be299abf..238fe783d815 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -146,10 +146,10 @@ impl From for ProofNode { } /// A proof that a given key-value pair either exists or does not exist in a trie. -#[derive(Clone, Debug)] -pub struct Proof(pub Box<[T]>); +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct Proof(T); -impl Proof { +impl Proof { /// Verify a proof pub fn verify, V: AsRef<[u8]>>( &self, @@ -164,20 +164,20 @@ impl Proof { /// with the given `root_hash`. If the key does not exist in the trie, returns `None`. /// Returns an error if the proof is invalid or doesn't prove the key for the /// given revision. - fn value_digest>( + pub fn value_digest>( &self, key: K, root_hash: &TrieHash, ) -> Result>, ProofError> { let key: Box<[u8]> = NibblesIterator::new(key.as_ref()).collect(); - let Some(last_node) = self.0.last() else { + let Some(last_node) = self.0.as_ref().last() else { return Err(ProofError::Empty); }; let mut expected_hash = root_hash.clone().into_hash_type(); - let mut iter = self.0.iter().peekable(); + let mut iter = self.0.as_ref().iter().peekable(); while let Some(node) = iter.next() { if node.to_hash() != expected_hash { return Err(ProofError::UnexpectedHash); @@ -224,6 +224,153 @@ impl Proof { // This is an exclusion proof. Ok(None) } + + /// Returns the length of the proof. + #[must_use] + pub fn len(&self) -> usize { + self.0.as_ref().len() + } + + /// Returns true if the proof is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.as_ref().is_empty() + } +} + +impl std::ops::Deref for Proof { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Proof { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Proof { + /// Constructs a new proof from a collection of proof nodes. + #[inline] + #[must_use] + pub const fn new(proof: T) -> Self { + Self(proof) + } +} + +impl Proof { + /// Constructs a new empty proof. + #[inline] + #[must_use] + pub const fn empty() -> Self { + Self::new(EmptyProofCollection) + } + + /// Converts an empty immutable proof into an empty mutable proof. + #[inline] + #[must_use] + pub const fn into_mutable(self) -> Proof> { + Proof::new(Vec::new()) + } +} + +impl Proof> { + /// Converts an immutable proof into a mutable proof. + #[inline] + #[must_use] + pub fn into_mutable(self) -> Proof> { + Proof::new(self.0.into_vec()) + } +} + +impl Proof> { + /// Converts a mutable proof into an immutable proof. + #[inline] + #[must_use] + pub fn into_immutable(self) -> Proof> { + Proof::new(self.0.into_boxed_slice()) + } +} + +impl Proof +where + T: Hashable, + V: ProofCollection + IntoIterator + FromIterator, +{ + /// Joins two proofs into one. + #[inline] + #[must_use] + pub fn join + IntoIterator>( + self, + other: Proof, + ) -> Proof { + self.into_iter().chain(other).collect() + } +} + +impl> FromIterator for Proof { + #[inline] + fn from_iter>(iter: I) -> Self { + Proof(iter.into_iter().collect()) + } +} + +impl> Extend for Proof { + #[inline] + fn extend>(&mut self, iter: I) { + self.0.extend(iter); + } +} + +impl> IntoIterator for Proof { + type Item = V::Node; + type IntoIter = V::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// A trait representing a collection of proof nodes. +/// +/// This allows [`Proof`] to be generic over different types of collections such +/// a `Box<[T]>` or `Vec`, where `T` implements the `Hashable` trait. +pub trait ProofCollection: AsRef<[Self::Node]> { + /// The type of nodes in the proof collection. + type Node: Hashable; +} + +impl ProofCollection for [T] { + type Node = T; +} + +impl ProofCollection for Box<[T]> { + type Node = T; +} + +impl ProofCollection for Vec { + type Node = T; +} + +/// A zero-sized type to represent an empty proof collection. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct EmptyProofCollection; + +impl AsRef<[ProofNode]> for EmptyProofCollection { + #[inline] + fn as_ref(&self) -> &[ProofNode] { + &[] + } +} + +impl ProofCollection for EmptyProofCollection { + type Node = ProofNode; } /// Returns the next nibble in `c` after `b`. diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index b6566fbf676e..34d920ae92f2 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -1,14 +1,12 @@ // Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood_storage::Hashable; - use crate::proof::Proof; /// A range proof proves that a given set of key-value pairs /// are in the trie with a given root hash. #[derive(Debug)] -pub struct RangeProof, V: AsRef<[u8]>, H: Hashable> { +pub struct RangeProof, V: AsRef<[u8]>, H> { #[expect(dead_code)] pub(crate) start_proof: Option>, #[expect(dead_code)] diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 11e5691d88ba..4490e350ee82 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -44,6 +44,12 @@ impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// proof pub type HashKey = firewood_storage::TrieHash; +/// A frozen proof is a proof that is stored in immutable memory. +pub type FrozenRangeProof = RangeProof, Box<[u8]>, Box<[ProofNode]>>; + +/// A frozen proof uses an immutable collection of proof nodes. +pub type FrozenProof = Proof>; + /// A key/value pair operation. Only put (upsert) and delete are /// supported #[derive(Debug)] @@ -244,7 +250,7 @@ pub trait DbView { async fn val(&self, key: K) -> Result>, Error>; /// Obtain a proof for a single key - async fn single_key_proof(&self, key: K) -> Result, Error>; + async fn single_key_proof(&self, key: K) -> Result; /// Obtain a range proof over a set of keys /// @@ -259,7 +265,7 @@ pub trait DbView { first_key: Option, last_key: Option, limit: Option, - ) -> Result, Box<[u8]>, ProofNode>>, Error>; + ) -> Result; /// Obtain a stream over the keys/values of this view, using an optional starting point /// diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 6bec5499ff34..ad5fa98ffbfd 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,11 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::proof::{Proof, ProofNode}; -use crate::range_proof::RangeProof; - use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}; use super::propose::{Proposal, ProposalBase}; +use crate::v2::api::{FrozenProof, FrozenRangeProof}; use async_trait::async_trait; use futures::Stream; use std::sync::Arc; @@ -66,7 +64,7 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn single_key_proof(&self, _key: K) -> Result, Error> { + async fn single_key_proof(&self, _key: K) -> Result { Err(Error::RangeProofOnEmptyTrie) } @@ -75,8 +73,8 @@ impl DbView for HistoricalImpl { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Box<[u8]>, ProofNode>>, Error> { - Ok(None) + ) -> Result { + Err(Error::RangeProofOnEmptyTrie) } fn iter_option(&self, _first_key: Option) -> Result { diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 2ca7c269c621..c647f5a0fab7 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -9,9 +9,7 @@ use async_trait::async_trait; use futures::stream::Empty; use super::api::{KeyType, ValueType}; -use crate::proof::{Proof, ProofNode}; -use crate::range_proof::RangeProof; -use crate::v2::api; +use crate::v2::api::{self, FrozenProof, FrozenRangeProof}; #[derive(Clone, Debug)] pub(crate) enum KeyOp { @@ -137,7 +135,7 @@ impl api::DbView for Proposal { } } - async fn single_key_proof(&self, _key: K) -> Result, api::Error> { + async fn single_key_proof(&self, _key: K) -> Result { todo!(); } @@ -146,7 +144,7 @@ impl api::DbView for Proposal { _first_key: Option, _last_key: Option, _limit: Option, - ) -> Result, Box<[u8]>, ProofNode>>, api::Error> { + ) -> Result { todo!(); } From 93230506ee5b6930a71d0650f17c2cfbf7904d0d Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 22 Jul 2025 22:58:51 -0700 Subject: [PATCH 0844/1053] feat: refactor value types to use the type alias (#1122) Also, switch to using boxed slices everywhere. I found a couple places where we do a lot of copies, more than just in serialization and more than I first though when I opened #1058. --- firewood/src/db.rs | 18 +++++++++--------- firewood/src/merkle.rs | 21 +++++++++------------ firewood/src/proof.rs | 10 ++++++---- firewood/src/stream.rs | 38 ++++++++++++++++++++------------------ firewood/src/v2/api.rs | 13 +++++++------ firewood/src/v2/emptydb.rs | 5 +++-- firewood/src/v2/propose.rs | 11 +++++++---- fwdctl/src/dump.rs | 4 ++-- 8 files changed, 63 insertions(+), 57 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 66c587e3474a..758f8e0b648f 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -6,7 +6,7 @@ reason = "Found 12 occurrences after enabling the lint." )] -use crate::merkle::Merkle; +use crate::merkle::{Merkle, Value}; use crate::stream::MerkleKeyValueStream; use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; pub use crate::v2::api::{Batch, BatchOp}; @@ -49,24 +49,24 @@ impl std::fmt::Debug for DbMetrics { /// A synchronous view of the database. pub trait DbViewSync { /// find a value synchronously - fn val_sync(&self, key: K) -> Result>, DbError>; + fn val_sync(&self, key: K) -> Result, DbError>; } /// A synchronous view of the database with raw byte keys (object-safe version). pub trait DbViewSyncBytes: std::fmt::Debug { /// find a value synchronously using raw bytes - fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError>; + fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError>; } // Provide blanket implementation for DbViewSync using DbViewSyncBytes impl DbViewSync for T { - fn val_sync(&self, key: K) -> Result>, DbError> { + fn val_sync(&self, key: K) -> Result, DbError> { self.val_sync_bytes(key.as_ref()) } } impl DbViewSyncBytes for Arc { - fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { + fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { let merkle = Merkle::from(self); let value = merkle.get_value(key)?; Ok(value) @@ -74,7 +74,7 @@ impl DbViewSyncBytes for Arc { } impl DbViewSyncBytes for Proposal<'_> { - fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { + fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { let merkle = Merkle::from(self.nodestore.clone()); let value = merkle.get_value(key)?; Ok(value) @@ -82,7 +82,7 @@ impl DbViewSyncBytes for Proposal<'_> { } impl DbViewSyncBytes for Arc, FileBacked>> { - fn val_sync_bytes(&self, key: &[u8]) -> Result>, DbError> { + fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { let merkle = Merkle::from(self.clone()); let value = merkle.get_value(key)?; Ok(value) @@ -100,7 +100,7 @@ impl api::DbView for HistoricalRev { Ok(HashedNodeReader::root_hash(self)) } - async fn val(&self, key: K) -> Result>, api::Error> { + async fn val(&self, key: K) -> Result, api::Error> { let merkle = Merkle::from(self); Ok(merkle.get_value(key.as_ref())?) } @@ -372,7 +372,7 @@ impl api::DbView for Proposal<'_> { Ok(self.nodestore.root_hash()) } - async fn val(&self, key: K) -> Result>, api::Error> { + async fn val(&self, key: K) -> Result, api::Error> { let merkle = Merkle::from(self.nodestore.clone()); merkle.get_value(key.as_ref()).map_err(api::Error::from) } diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 4b391b4775ec..0530345bfb69 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -44,9 +44,8 @@ use std::sync::Arc; /// Keys are boxed u8 slices pub type Key = Box<[u8]>; -/// Values are vectors -/// TODO: change to Box<[u8]> -pub type Value = Vec; +/// Values are boxed u8 slices +pub type Value = Box<[u8]>; // convert a set of nibbles into a printable string // panics if there is a non-nibble byte in the set @@ -303,7 +302,7 @@ impl Merkle { let start_proof = self.prove(&first_key)?; let limit = limit.map(|old_limit| old_limit.get().saturating_sub(1)); - let mut key_values = vec![(first_key, first_value.into_boxed_slice())]; + let mut key_values = vec![(first_key, first_value)]; // we stop streaming if either we hit the limit or the key returned was larger // than the largest key requested @@ -324,8 +323,7 @@ impl Merkle { // keep going if the key returned is less than the last key requested ready(&*kv.0 <= last_key) }) - .map(|kv| kv.map(|(k, v)| (k, v.into()))) - .try_collect::, Box<[u8]>)>>() + .try_collect::>() .await?, ); @@ -343,7 +341,7 @@ impl Merkle { }) } - pub(crate) fn get_value(&self, key: &[u8]) -> Result>, FileIoError> { + pub(crate) fn get_value(&self, key: &[u8]) -> Result, FileIoError> { let Some(node) = self.get_node(key)? else { return Ok(None); }; @@ -470,7 +468,7 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. - pub fn insert(&mut self, key: &[u8], value: Box<[u8]>) -> Result<(), FileIoError> { + pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -498,7 +496,7 @@ impl Merkle> { &mut self, mut node: Node, key: &[u8], - value: Box<[u8]>, + value: Value, ) -> Result { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` @@ -634,7 +632,7 @@ impl Merkle> { /// Returns the value that was removed, if any. /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. - pub fn remove(&mut self, key: &[u8]) -> Result>, FileIoError> { + pub fn remove(&mut self, key: &[u8]) -> Result, FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); let root = self.nodestore.mut_root(); @@ -659,12 +657,11 @@ impl Merkle> { /// Removes the value associated with the given `key` from the subtrie rooted at `node`. /// Returns the new root of the subtrie and the value that was removed, if any. /// Each element of `key` is 1 nibble. - #[expect(clippy::type_complexity)] fn remove_helper( &mut self, mut node: Node, key: &[u8], - ) -> Result<(Option, Option>), FileIoError> { + ) -> Result<(Option, Option), FileIoError> { // 4 possibilities for the position of the `key` relative to `node`: // 1. The node is at `key` // 2. The key is above the node (i.e. its ancestor) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 238fe783d815..517f779462bc 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -11,11 +11,13 @@ )] use firewood_storage::{ - BranchNode, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, PathIterItem, + BranchNode, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, PathIterItem, Preimage, TrieHash, ValueDigest, }; use thiserror::Error; +use crate::merkle::{Key, Value}; + #[derive(Debug, Error)] /// Reasons why a proof is invalid pub enum ProofError { @@ -77,13 +79,13 @@ pub enum ProofError { /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. - pub key: Box<[u8]>, + pub key: Key, /// The length of the key prefix that is shared with the previous node. #[cfg(feature = "ethhash")] pub partial_len: usize, /// None if the node does not have a value. /// Otherwise, the node's value or the hash of its value. - pub value_digest: Option>>, + pub value_digest: Option>, /// The hash of each child, or None if the child does not exist. pub child_hashes: [Option; BranchNode::MAX_CHILDREN], } @@ -169,7 +171,7 @@ impl Proof { key: K, root_hash: &TrieHash, ) -> Result>, ProofError> { - let key: Box<[u8]> = NibblesIterator::new(key.as_ref()).collect(); + let key = Path(NibblesIterator::new(key.as_ref()).collect()); let Some(last_node) = self.0.as_ref().last() else { return Err(ProofError::Empty); diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 78386debbd31..10ed67261f56 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -383,13 +383,9 @@ impl Stream for MerkleKeyValueStream<'_, T> { return self.poll_next(_cx); }; - let value = value.to_vec(); - Poll::Ready(Some(Ok((key, value)))) - } - Node::Leaf(leaf) => { - let value = leaf.value.to_vec(); - Poll::Ready(Some(Ok((key, value)))) + Poll::Ready(Some(Ok((key, value.clone())))) } + Node::Leaf(leaf) => Poll::Ready(Some(Ok((key, leaf.value.clone())))), }, Some(Err(e)) => Poll::Ready(Some(Err(e.into()))), None => Poll::Ready(None), @@ -1081,7 +1077,10 @@ mod tests { .await .unwrap() .unwrap(), - (expected_key.into_boxed_slice(), expected_value), + ( + expected_key.into_boxed_slice(), + expected_value.into_boxed_slice() + ), "i: {i}, j: {j}", ); } @@ -1099,7 +1098,10 @@ mod tests { .await .unwrap() .unwrap(), - (expected_key.into_boxed_slice(), expected_value), + ( + expected_key.into_boxed_slice(), + expected_value.into_boxed_slice() + ), "i: {i}, j: {j}", ); } @@ -1111,7 +1113,10 @@ mod tests { .await .unwrap() .unwrap(), - (vec![i + 1, 0].into_boxed_slice(), vec![i + 1, 0]), + ( + vec![i + 1, 0].into_boxed_slice(), + vec![i + 1, 0].into_boxed_slice() + ), "i: {i}", ); } @@ -1145,7 +1150,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(&*next.0, &*next.1); - assert_eq!(&next.1, kv); + assert_eq!(&*next.1, kv); } check_stream_is_done(stream).await; @@ -1187,20 +1192,17 @@ mod tests { assert_eq!( stream.next().await.unwrap().unwrap(), - (branch.to_vec().into_boxed_slice(), branch.to_vec()) + (branch.into(), branch.into()) ); assert_eq!( stream.next().await.unwrap().unwrap(), - (first_leaf.to_vec().into_boxed_slice(), first_leaf.to_vec()) + (first_leaf.into(), first_leaf.into()) ); assert_eq!( stream.next().await.unwrap().unwrap(), - ( - second_leaf.to_vec().into_boxed_slice(), - second_leaf.to_vec() - ) + (second_leaf.into(), second_leaf.into()) ); } @@ -1231,13 +1233,13 @@ mod tests { let first = stream.next().await.unwrap().unwrap(); assert_eq!(&*first.0, &*first.1); - assert_eq!(first.1, first_expected); + assert_eq!(&*first.1, first_expected); let second_expected = key_values[2].as_slice(); let second = stream.next().await.unwrap().unwrap(); assert_eq!(&*second.0, &*second.1); - assert_eq!(second.1, second_expected); + assert_eq!(&*second.1, second_expected); check_stream_is_done(stream).await; } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 4490e350ee82..7a00945a26f9 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -11,6 +11,7 @@ )] use crate::manager::RevisionManagerError; +use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; pub use crate::range_proof::RangeProof; use async_trait::async_trait; @@ -45,7 +46,7 @@ impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} pub type HashKey = firewood_storage::TrieHash; /// A frozen proof is a proof that is stored in immutable memory. -pub type FrozenRangeProof = RangeProof, Box<[u8]>, Box<[ProofNode]>>; +pub type FrozenRangeProof = RangeProof>; /// A frozen proof uses an immutable collection of proof nodes. pub type FrozenProof = Proof>; @@ -113,9 +114,9 @@ pub enum Error { #[error("Invalid range: {start_key:?} > {end_key:?}")] InvalidRange { /// The provided starting key - start_key: Box<[u8]>, + start_key: Key, /// The provided ending key - end_key: Box<[u8]>, + end_key: Key, }, #[error("IO error: {0}")] @@ -234,7 +235,7 @@ pub trait Db { #[async_trait] pub trait DbView { /// The type of a stream of key/value pairs - type Stream<'a>: Stream, Vec), Error>> + type Stream<'a>: Stream> where Self: 'a; @@ -247,7 +248,7 @@ pub trait DbView { async fn root_hash(&self) -> Result, Error>; /// Get the value of a specific key - async fn val(&self, key: K) -> Result>, Error>; + async fn val(&self, key: K) -> Result, Error>; /// Obtain a proof for a single key async fn single_key_proof(&self, key: K) -> Result; @@ -282,7 +283,7 @@ pub trait DbView { /// Obtain a stream over the keys/values of this view, starting from the beginning fn iter(&self) -> Result, Error> { - self.iter_option(Option::>::None) + self.iter_option(Option::::None) } /// Obtain a stream over the key/values, starting at a specific key diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index ad5fa98ffbfd..09a98d269bcb 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -3,6 +3,7 @@ use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}; use super::propose::{Proposal, ProposalBase}; +use crate::merkle::{Key, Value}; use crate::v2::api::{FrozenProof, FrozenRangeProof}; use async_trait::async_trait; use futures::Stream; @@ -60,7 +61,7 @@ impl DbView for HistoricalImpl { Ok(None) } - async fn val(&self, _key: K) -> Result>, Error> { + async fn val(&self, _key: K) -> Result, Error> { Ok(None) } @@ -87,7 +88,7 @@ impl DbView for HistoricalImpl { pub struct EmptyStreamer; impl Stream for EmptyStreamer { - type Item = Result<(Box<[u8]>, Vec), Error>; + type Item = Result<(Key, Value), Error>; fn poll_next( self: std::pin::Pin<&mut Self>, diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index c647f5a0fab7..2714910ce1e5 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -9,7 +9,10 @@ use async_trait::async_trait; use futures::stream::Empty; use super::api::{KeyType, ValueType}; -use crate::v2::api::{self, FrozenProof, FrozenRangeProof}; +use crate::{ + merkle::{Key, Value}, + v2::api::{self, FrozenProof, FrozenRangeProof}, +}; #[derive(Clone, Debug)] pub(crate) enum KeyOp { @@ -68,7 +71,7 @@ impl Clone for ProposalBase { #[derive(Debug)] pub struct Proposal { pub(crate) base: ProposalBase, - pub(crate) delta: BTreeMap, KeyOp>>, + pub(crate) delta: BTreeMap>, } // Implement Clone because T doesn't need to be Clone @@ -111,7 +114,7 @@ impl Proposal { impl api::DbView for Proposal { // TODO: Replace with the correct stream type for an in-memory proposal implementation type Stream<'a> - = Empty, Vec), api::Error>> + = Empty> where T: 'a; @@ -119,7 +122,7 @@ impl api::DbView for Proposal { todo!(); } - async fn val(&self, key: K) -> Result>, api::Error> { + async fn val(&self, key: K) -> Result, api::Error> { // see if this key is in this proposal match self.delta.get(key.as_ref()) { Some(change) => match change { diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index baeb839ab302..0116cdc23c23 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -8,7 +8,7 @@ use clap::Args; use firewood::db::{Db, DbConfig}; -use firewood::merkle::Key; +use firewood::merkle::{Key, Value}; use firewood::stream::MerkleKeyValueStream; use firewood::v2::api::{self, Db as _}; use futures_util::StreamExt; @@ -18,7 +18,7 @@ use std::fs::File; use std::io::{BufWriter, Write}; use std::path::PathBuf; -type KeyFromStream = Option), api::Error>>; +type KeyFromStream = Option>; #[derive(Debug, Args)] pub struct Options { From 2dd2d01a02b6cca6451dfba2ab95218e5ca9ec58 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 22 Jul 2025 23:05:48 -0700 Subject: [PATCH 0845/1053] feat(dumper): child links in hex (easy) (#1124) Found myself doing some decimal to hex conversions on the child links, so changed them to hex. --- firewood/src/merkle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 0530345bfb69..3147497080ec 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -389,7 +389,7 @@ impl Merkle { let inserted = seen.insert(format!("{child}")); if inserted { - writeln!(writer, " {node} -> {child}[label=\"{childidx}\"]") + writeln!(writer, " {node} -> {child}[label=\"{childidx:x}\"]") .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; self.dump_node(&child, child_hash, seen, writer)?; } else { @@ -397,7 +397,7 @@ impl Merkle { // Indicate this with a red edge. writeln!( writer, - " {node} -> {child}[label=\"{childidx} (dup)\" color=red]" + " {node} -> {child}[label=\"{childidx:x} (dup)\" color=red]" ) .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; } From f45ea8685608d67a3024c41c1fa1359f45155a10 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:57:02 -0400 Subject: [PATCH 0846/1053] docs: update ffi/README.md to include configs, metrics, and logs (#1111) After many changes to the FFI pattern, we should maintain an up-to-date guide on what the different configurations mean, and how to use them. Additionally, the docs are now available in the crate's documentation. To check this locally, run the following commands from the root of the repo: ```bash cd ffi cargo doc --open ``` Closes #1078 --- ffi/README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 1 + 2 files changed, 73 insertions(+) diff --git a/ffi/README.md b/ffi/README.md index 7155ce5a5790..da24e79c148c 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -60,6 +60,78 @@ cargo build -p firewood-ffi --features ethhash To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries for Ethereum-compatible hashing to [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) with `ethhash` enabled by default. To use Firewood's native hashing structure, you must still build the static library separately. +## Configuration + +### Database Config + +A `Config` should be provided when creating the database. A default config is provided at `ffi.DefaultConfig()`: + +```go +&Config{ + NodeCacheEntries: 1000000, + FreeListCacheEntries: 40000, + Revisions: 100, + ReadCacheStrategy: OnlyCacheWrites, +} +``` + +If no config is provided (`config == nil`), the default config is used. A description of all available values, and the default if not set, is available below. + +#### `Truncate` - `bool` + +If set to `true`, an empty database will be created, overriding any existing file. Otherwise, if a file exists, that file will be loaded to create the database. In either case, if the file doesn't exist, it will be created. + +*Default*: `false` + +#### `Revisions` - `uint` + +Indicates the number of committed roots accessible before the diff layer is compressed. Must be explicitly set if the config is specified to at least 2. + +#### `ReadCacheStrategy` - `uint` + +Should be one of `OnlyCacheWrites`, `CacheBranchReads`, or `CacheAllReads`. In the latter two cases, writes are still cached. + +*Default*: `OnlyCacheWrites` + +#### `NodeCacheEntries`- `uint` + +The number of nodes in the database that are stored in cache. Must be explicitly set if the config is supplied. + +#### `FreeListCacheEntries` - `uint` + +The number of entries in the free list (see [Firewood Overview](../README.md)). Must be explicitly set if the config is supplied. + +### Metrics + +By default, metrics are not enabled in Firewood's FFI. However, if compiled with this option, they can be recorded by a call to `StartMetrics()` or `StartMetricsWithExporter(port)`. One of these may be called exactly once, since it starts the metrics globally on the process. + +To use these metrics, you can: + +- Listen on the port specified, if you started the metrics with the exporter. +- Call `GatherMetrics()`, which returns an easily parsable string containing all metrics. +- Create the Prometheus gatherer, and call `Gather`. This can easily be integrated into other applications which already use prometehus. Example usage is below: + +```go +gatherer := ffi.Gatherer{} +metrics, err := gatherer.Gather() +``` + +### Logs + +Logs are configured globally on the process, and not enabled by default. They can be enabled using the `StartLogs(config)` function. Firewood must be built with the `logger` feature for this function to work. This should be called before opening the database. + +#### `Path` - `string` + +The path to the file where the logs will be written. + +*Default*: `{TEMP}/firewood-log.txt`, where `{TEMP}` is the platform's temporary directory. For Unix-based OSes, this is typically `/tmp`. + +#### `FilterLevel` - `string` + +One of `trace`, `debug`, `info`, `warn` or `error`. + +*Default*: `info` + ## Development Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 2016b03733ba..b4d66b6fc149 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,6 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#![doc = include_str!("../README.md")] #![expect( unsafe_code, reason = "This is an FFI library, so unsafe code is expected." From 404e6929e237ad06dbe405e0c3ee9ac72047d255 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 23 Jul 2025 07:19:52 -0700 Subject: [PATCH 0847/1053] feat(deferred-allocate): Part 3: Defer allocate (#1061) feat(deferred-allocate): Part 3: Defer allocate This implements deferred allocaion that delays allocating space for nodes until commit time. The nodestore refactor consisted of removing the 'new' hashmap, which mapped newly allocated nodes to their contents in a proposal. Instead, now we store those newly allocated nodes directly from the `Child` struct. The implementation constisted of: - A new type, NodeAllocator, to consolidate all the allocation code - Removing the 'new' hashmap, as freshly allocatred nodes are found by traversing the trie and inspecting their state - Use the most recently committed revision's freelist to allocate new nodes. This will change in a future revision when the freelist is tied to storage rather than the revision. - Rewrite flush_nodes to serialize then allocate. Previously, the node was serialized twice, once to determine the size during proposal creation (allocation time) and once to store it - Changes to the commit flow by separating the node persistence logic (see comments in commit.rs) Some miscellaneous changes included in this diff (could be split out): - Branch children iterators can no longer return reference to hashes, since they are sometimes behind an ArcSwap - Propogate logger feature in fwdctl (helped with debugging) - Remove use of stored_len in tests - size_from_area_index was a method on nodestore, but didn't use nodestore - uncached_node_and_size was dead code - New clippy lint for hash_helper not needing &self in non-ethhash fixed Task completion status: [x] Roots may not be persisted (Part 1: #1041) [x] BranchNode children can now be Node, LinearAddress or a MaybePersistedNode (Part 2: #1045 and #1047) [x] When converting a `MutableProposal` to an `ImmutableProposal`, don't actually allocate space, just create `SharedNode`s from the `Node`s. (Part 3: #1055 and this PR) [ ] Remove `NodeStoreHeader` from `NodeStore`. The header should move into the `RevisionManager`. [ ] Allocate new nodes from the recently-freed nodes from an expired revision, then from the freelist. This can be done by maintaining a local freelist from the nodes deleted in the expiring revision and falling back to the actual freelist. Deleted nodes that are not reused must still be pushed onto the freelist. --- firewood/src/manager.rs | 38 ++--- firewood/src/proof.rs | 4 +- fwdctl/Cargo.toml | 1 + storage/src/checker/mod.rs | 43 +++--- storage/src/hashednode.rs | 10 +- storage/src/hashers/ethhash.rs | 4 +- storage/src/lib.rs | 6 +- storage/src/linear/filebacked.rs | 6 +- storage/src/linear/mod.rs | 4 +- storage/src/node/branch.rs | 9 +- storage/src/node/mod.rs | 62 ++------- storage/src/nodestore/alloc.rs | 160 ++++++--------------- storage/src/nodestore/hash.rs | 43 +++--- storage/src/nodestore/header.rs | 6 + storage/src/nodestore/mod.rs | 230 ++++++++++++++----------------- storage/src/nodestore/persist.rs | 121 ++++++++++++---- 16 files changed, 330 insertions(+), 417 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 1b526ad3a14d..ecc7e22e8163 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -145,21 +145,10 @@ impl RevisionManager { /// It only contains the address of the nodes that are deleted, which should be very small. /// 3. Revision reaping. If more than the maximum number of revisions are kept in memory, the /// oldest revision is reaped. - /// 4. Set last committed revision. + /// 4. Persist to disk. This includes flushing everything to disk. + /// 5. Set last committed revision. /// Set last committed revision in memory. - /// Another commit can start after this but before the node flush is completed. - /// 5. Free list flush. - /// Persist/write the free list header. - /// The free list is flushed first to prevent future allocations from using the space allocated to this proposal. - /// This should be done in a single write since the free list headers are small, and must be persisted to disk before starting the next step. - /// 6. Node flush. - /// Persist/write all the nodes to disk. - /// Note that since these are all freshly allocated nodes, they will never be referred to by any prior commit. - /// After flushing all nodes, the file should be flushed to disk (fsync) before performing the next step. - /// 7. Root move. - /// The root address on disk must be updated. - /// This write can be delayed, but would mean that recovery will not roll forward to this revision. - /// 8. Proposal Cleanup. + /// 6. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. #[fastrace::trace(short_name = true)] #[crate::metrics("firewood.proposal.commit", "proposal commit to storage")] @@ -170,7 +159,7 @@ impl RevisionManager { return Err(RevisionManagerError::NotLatest); } - let mut committed = proposal.as_committed(); + let mut committed = proposal.as_committed(current_revision); // 2. Persist delete list for this committed revision to disk for recovery @@ -212,7 +201,12 @@ impl RevisionManager { gauge!("firewood.max_revisions").set(self.max_revisions as f64); } - // 4. Set last committed revision + // 4. Persist to disk. + // TODO: We can probably do this in another thread, but it requires that + // we move the header out of NodeStore, which is in a future PR. + committed.persist()?; + + // 5. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical .write() @@ -224,18 +218,8 @@ impl RevisionManager { .expect("poisoned lock") .insert(hash, committed.clone()); } - // TODO: We could allow other commits to start here using the pending list - - // 5. Free list flush, which will prevent allocating on top of the nodes we are about to write - proposal.flush_freelist()?; - - // 6. Node flush - proposal.flush_nodes()?; - - // 7. Root move - proposal.flush_header()?; - // 8. Proposal Cleanup + // 6. Proposal Cleanup // Free proposal that is being committed as well as any proposals no longer // referenced by anyone else. self.proposals diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 517f779462bc..c52d9683257f 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -107,11 +107,11 @@ impl Hashable for ProofNode { }) } - fn children(&self) -> impl Iterator + Clone { + fn children(&self) -> impl Iterator + Clone { self.child_hashes .iter() .enumerate() - .filter_map(|(i, hash)| hash.as_ref().map(|h| (i, h))) + .filter_map(|(i, hash)| hash.as_ref().map(|h| (i, h.clone()))) } } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 7b9daa93c8af..77fd93a29268 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -38,6 +38,7 @@ nonzero_ext = "0.3.0" [features] ethhash = ["firewood/ethhash"] +logger = ["firewood/logger"] [dev-dependencies] # Workspace dependencies diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 67a12b9f771b..746a165fe493 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -5,10 +5,11 @@ mod range_set; use range_set::LinearAddressRangeSet; use crate::logger::warn; -use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata}; +use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata, size_from_area_index}; use crate::{ - CheckerError, Committed, HashType, HashedNodeReader, LinearAddress, Node, NodeReader, - NodeStore, Path, StoredAreaParent, TrieNodeParent, WritableStorage, hash_node, + CheckerError, Committed, HashType, HashedNodeReader, IntoHashType, LinearAddress, Node, + NodeReader, NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, + hash_node, }; use std::cmp::Ordering; @@ -42,19 +43,25 @@ impl NodeStore { let mut visited = LinearAddressRangeSet::new(db_size)?; // 2. traverse the trie and check the nodes - if let (Some(root_address), Some(root_hash)) = (self.root_address(), self.root_hash()) { - // the database is not empty, traverse the trie - self.check_area_aligned( - root_address, - StoredAreaParent::TrieNode(TrieNodeParent::Root), - )?; - self.visit_trie( - root_address, - HashType::from(root_hash), - Path::new(), - &mut visited, - opt.hash_check, - )?; + if let (Some(root), Some(root_hash)) = + (self.root_as_maybe_persisted_node(), self.root_hash()) + { + // the database is not empty, and has a physical address, so traverse the trie + if let Some(root_address) = root.as_linear_address() { + self.check_area_aligned( + root_address, + StoredAreaParent::TrieNode(TrieNodeParent::Root), + )?; + self.visit_trie( + root_address, + root_hash.into_hash_type(), + Path::new(), + &mut visited, + opt.hash_check, + )?; + } else { + return Err(CheckerError::UnpersistedRoot); + } } // 3. check the free list - this can happen in parallel with the trie traversal @@ -71,7 +78,7 @@ impl NodeStore { Ok(()) } - /// Recursively traverse the trie from the given root address. + /// Recursively traverse the trie from the given root node. fn visit_trie( &self, subtree_root_address: LinearAddress, @@ -134,7 +141,7 @@ impl NodeStore { parent, } = free_area?; self.check_area_aligned(addr, StoredAreaParent::FreeList(parent))?; - let area_size = Self::size_from_area_index(area_index); + let area_size = size_from_area_index(area_index); if free_list_id != area_index { return Err(CheckerError::FreelistAreaSizeMismatch { address: addr, diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index de847eb22eff..618862c5ba2a 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -140,7 +140,7 @@ pub trait Hashable { fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. - fn children(&self) -> impl Iterator + Clone; + fn children(&self) -> impl Iterator + Clone; } /// A preimage of a hash. @@ -154,7 +154,7 @@ pub trait Preimage { trait HashableNode { fn partial_path(&self) -> impl Iterator + Clone; fn value(&self) -> Option<&[u8]>; - fn children_iter(&self) -> impl Iterator + Clone; + fn children_iter(&self) -> impl Iterator + Clone; } impl HashableNode for BranchNode { @@ -166,7 +166,7 @@ impl HashableNode for BranchNode { self.value.as_deref() } - fn children_iter(&self) -> impl Iterator + Clone { + fn children_iter(&self) -> impl Iterator + Clone { self.children_hashes() } } @@ -180,7 +180,7 @@ impl HashableNode for LeafNode { Some(&self.value) } - fn children_iter(&self) -> impl Iterator + Clone { + fn children_iter(&self) -> impl Iterator + Clone { iter::empty() } } @@ -214,7 +214,7 @@ impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { self.node.value().map(ValueDigest::Value) } - fn children(&self) -> impl Iterator + Clone { + fn children(&self) -> impl Iterator + Clone { self.node.children_iter() } } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 1b2fa3441c25..b949abddb9db 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -173,7 +173,7 @@ impl Preimage for T { let mut rlp = RlpStream::new_list(17); let mut child_iter = self.children().peekable(); for index in 0..=15 { - if let Some(&(child_index, digest)) = child_iter.peek() { + if let Some(&(child_index, ref digest)) = child_iter.peek() { if child_index == index { match digest { HashType::Hash(hash) => rlp.append(&hash.as_slice()), @@ -225,7 +225,7 @@ impl Preimage for T { self.partial_path().collect::>(), true, )); - rlp.append_raw(rlp_bytes, 1); + rlp.append_raw(&rlp_bytes, 1); let bytes = rlp.out(); TrieHash::from(Keccak256::digest(bytes)) } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 6611d08acebb..2e4bed65d553 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -47,7 +47,7 @@ pub use node::{ }; pub use nodestore::{ Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, - NodeStore, Parentable, ReadInMemoryNode, RootReader, TrieReader, + NodeStore, Parentable, RootReader, TrieReader, }; pub use linear::filebacked::FileBacked; @@ -209,6 +209,10 @@ pub enum CheckerError { #[error("Found leaked areas: {0:?}")] AreaLeaks(Vec>), + /// The root is not persisted + #[error("The checker can only check persisted nodestores")] + UnpersistedRoot, + /// IO error #[error("IO error")] IO(#[from] FileIoError), diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index b15a37c05fdc..c0f8d98575a0 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -212,13 +212,13 @@ impl WritableStorage for FileBacked { } } - fn write_cached_nodes<'a>( + fn write_cached_nodes( &self, - nodes: impl Iterator, + nodes: impl IntoIterator, ) -> Result<(), FileIoError> { let mut guard = self.cache.lock().expect("poisoned lock"); for (addr, node) in nodes { - guard.put(addr, node.clone()); + guard.put(addr, node); } Ok(()) } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 6a3c374847ca..a0bea787b00b 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -181,9 +181,9 @@ pub trait WritableStorage: ReadableStorage { fn write(&self, offset: u64, object: &[u8]) -> Result; /// Write all nodes to the cache (if any) - fn write_cached_nodes<'a>( + fn write_cached_nodes( &self, - _nodes: impl Iterator, + _nodes: impl IntoIterator, ) -> Result<(), FileIoError> { Ok(()) } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 0282e4a0635f..ddaf03efb0a1 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -412,8 +412,13 @@ impl BranchNode { } /// Returns (index, hash) for each child that has a hash set. - pub fn children_hashes(&self) -> impl Iterator + Clone { - self.children_iter().map(|(idx, (_, hash))| (idx, hash)) + pub fn children_hashes(&self) -> impl Iterator + Clone { + self.children.iter().enumerate().filter_map(|(i, child)| { + child + .as_ref() + .and_then(|child| child.hash().cloned()) + .map(|hash| (i, hash)) + }) } /// Returns (index, address) for each child that has a hash set. diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index c39c085f1cde..dd333aad36df 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::arithmetic_side_effects, - reason = "Found 4 occurrences after enabling the lint." -)] #![expect( clippy::indexing_slicing, reason = "Found 1 occurrences after enabling the lint." @@ -154,38 +150,6 @@ impl ExtendableBytes for Vec { } } -pub struct ByteCounter(u64); - -impl ByteCounter { - pub const fn new() -> Self { - ByteCounter(0) - } - - pub const fn count(&self) -> u64 { - self.0 - } -} - -impl Write for ByteCounter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0 += buf.len() as u64; - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl ExtendableBytes for ByteCounter { - fn extend>(&mut self, other: T) { - self.0 += other.into_iter().count() as u64; - } - fn push(&mut self, _value: u8) { - self.0 += 1; - } -} - impl Node { /// Returns the partial path of the node. #[must_use] @@ -306,26 +270,20 @@ impl Node { // encode the children if childcount == BranchNode::MAX_CHILDREN { for (_, child) in child_iter { - if let Child::AddressWithHash(address, hash) = child { - encoded.extend_from_slice(&address.get().to_ne_bytes()); - hash.write_to(encoded); - } else { - panic!( - "attempt to serialize to persist a branch with a child that is not an AddressWithHash" - ); - } + let (address, hash) = child + .persist_info() + .expect("child must be hashed when serializing"); + encoded.extend_from_slice(&address.get().to_ne_bytes()); + hash.write_to(encoded); } } else { for (position, child) in child_iter { encoded.extend_var_int(position); - if let Child::AddressWithHash(address, hash) = child { - encoded.extend_from_slice(&address.get().to_ne_bytes()); - hash.write_to(encoded); - } else { - panic!( - "attempt to serialize to persist a branch with a child that is not an AddressWithHash" - ); - } + let (address, hash) = child + .persist_info() + .expect("child must be hashed when serializing"); + encoded.extend_from_slice(&address.get().to_ne_bytes()); + hash.write_to(encoded); } } } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 1f60c4d221f3..0d26a4c99cee 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -23,6 +23,7 @@ use crate::linear::FileIoError; use crate::logger::trace; use crate::node::branch::{ReadSerializable, Serializable}; +use crate::nodestore::NodeStoreHeader; use integer_encoding::VarIntReader; use metrics::counter; @@ -31,13 +32,9 @@ use std::fmt; use std::io::{Error, ErrorKind, Read}; use std::iter::FusedIterator; use std::num::NonZeroU64; -use std::sync::Arc; -use crate::node::persist::MaybePersistedNode; -use crate::node::{ByteCounter, ExtendableBytes, Node}; -use crate::{CacheReadStrategy, FreeListParent, ReadableStorage, SharedNode, TrieHash}; - -use crate::linear::WritableStorage; +use crate::node::ExtendableBytes; +use crate::{FreeListParent, MaybePersistedNode, ReadableStorage, TrieHash, WritableStorage}; /// Returns the maximum size needed to encode a `VarInt`. const fn var_int_max_size() -> usize { @@ -80,6 +77,17 @@ pub fn area_size_hash() -> TrieHash { hasher.finalize().into() } +/// Get the size of an area index (used by the checker) +/// +/// # Panics +/// +/// Panics if `index` is out of bounds for the `AREA_SIZES` array. +#[must_use] +pub const fn size_from_area_index(index: AreaIndex) -> u64 { + #[expect(clippy::indexing_slicing)] + AREA_SIZES[index as usize] +} + // TODO: automate this, must stay in sync with above pub const fn index_name(index: usize) -> &'static str { match index { @@ -427,9 +435,20 @@ impl FreeArea { } // Re-export the NodeStore types we need -use super::{Committed, ImmutableProposal, NodeStore, ReadInMemoryNode}; +use super::NodeStore; + +/// Writable allocator for allocating and deleting nodes +#[derive(Debug)] +pub struct NodeAllocator<'a, S> { + storage: &'a S, + header: &'a mut NodeStoreHeader, +} + +impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { + pub const fn new(storage: &'a S, header: &'a mut NodeStoreHeader) -> Self { + Self { storage, header } + } -impl NodeStore { /// Returns (index, `area_size`) for the stored area at `addr`. /// `index` is the index of `area_size` in the array of valid block sizes. /// @@ -461,95 +480,6 @@ impl NodeStore { Ok((index, size)) } - /// Read a [Node] from the provided [`LinearAddress`]. - /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - pub fn read_node_from_disk( - &self, - addr: LinearAddress, - mode: &'static str, - ) -> Result { - if let Some(node) = self.storage.read_cached_node(addr, mode) { - return Ok(node); - } - - debug_assert!(addr.is_aligned()); - - // saturating because there is no way we can be reading at u64::MAX - // and this will fail very soon afterwards - let actual_addr = addr.get().saturating_add(1); // skip the length byte - - let _span = fastrace::local::LocalSpan::enter_with_local_parent("read_and_deserialize"); - - let area_stream = self.storage.stream_from(actual_addr)?; - let node: SharedNode = Node::from_reader(area_stream) - .map_err(|e| { - self.storage - .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) - })? - .into(); - match self.storage.cache_read_strategy() { - CacheReadStrategy::All => { - self.storage.cache_node(addr, node.clone()); - } - CacheReadStrategy::BranchReads => { - if !node.is_leaf() { - self.storage.cache_node(addr, node.clone()); - } - } - CacheReadStrategy::WritesOnly => {} - } - Ok(node) - } - - /// Read a [Node] from the provided [`LinearAddress`] and size. - /// This is an uncached read, primarily used by check utilities - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the node cannot be read. - pub fn uncached_read_node_and_size( - &self, - addr: LinearAddress, - ) -> Result<(SharedNode, u8), FileIoError> { - let mut area_stream = self.storage.stream_from(addr.get())?; - let mut size = [0u8]; - area_stream.read_exact(&mut size).map_err(|e| { - self.storage.file_io_error( - e, - addr.get(), - Some("uncached_read_node_and_size".to_string()), - ) - })?; - self.storage.stream_from(addr.get().saturating_add(1))?; - let node: SharedNode = Node::from_reader(area_stream) - .map_err(|e| { - self.storage.file_io_error( - e, - addr.get(), - Some("uncached_read_node_and_size".to_string()), - ) - })? - .into(); - Ok((node, size[0])) - } - - /// Get the size of an area index (used by the checker) - /// - /// # Panics - /// - /// Panics if `index` is out of bounds for the `AREA_SIZES` array. - #[must_use] - pub const fn size_from_area_index(index: AreaIndex) -> u64 { - #[expect(clippy::indexing_slicing)] - AREA_SIZES[index as usize] - } -} - -impl NodeStore, S> { /// Attempts to allocate `n` bytes from the free lists. /// If successful returns the address of the newly allocated area /// and the index of the free list that was used. @@ -582,8 +512,7 @@ impl NodeStore, S> { trace!("free_head@{address}(cached): {free_head:?} size:{index}"); *free_stored_area_addr = free_head; } else { - let (free_head, read_index) = - FreeArea::from_storage(self.storage.as_ref(), address)?; + let (free_head, read_index) = FreeArea::from_storage(self.storage, address)?; debug_assert_eq!(read_index as usize, index); // Update the free list to point to the next free block. @@ -621,14 +550,6 @@ impl NodeStore, S> { Ok((addr, index)) } - /// Returns the length of the serialized area for a node. - #[must_use] - pub fn stored_len(node: &Node) -> u64 { - let mut bytecounter = ByteCounter::new(); - node.as_bytes(0, &mut bytecounter); - bytecounter.count() - } - /// Returns an address that can be used to store the given `node` and updates /// `self.header` to reflect the allocation. Doesn't actually write the node to storage. /// Also returns the index of the free list the node was allocated from. @@ -638,9 +559,9 @@ impl NodeStore, S> { /// Returns a [`FileIoError`] if the node cannot be allocated. pub fn allocate_node( &mut self, - node: &Node, + node: &[u8], ) -> Result<(LinearAddress, AreaIndex), FileIoError> { - let stored_area_size = Self::stored_len(node); + let stored_area_size = node.len() as u64; // Attempt to allocate from a free list. // If we can't allocate from a free list, allocate past the existing @@ -654,14 +575,13 @@ impl NodeStore, S> { } } -impl NodeStore { - /// Deletes the [Node] at the given address, updating the next pointer at - /// the given addr, and changing the header of this committed nodestore to - /// have the address on the freelist +impl NodeAllocator<'_, S> { + /// Deletes the `Node` and updates the header of the allocator. + /// Nodes that are not persisted are just dropped. /// /// # Errors /// - /// Returns a [`FileIoError`] if the node cannot be deleted. + /// Returns a [`FileIoError`] if the area cannot be read or written. #[expect(clippy::indexing_slicing)] pub fn delete_node(&mut self, node: MaybePersistedNode) -> Result<(), FileIoError> { let Some(addr) = node.as_linear_address() else { @@ -859,9 +779,9 @@ impl NodeStore { #[expect(clippy::unwrap_used, clippy::indexing_slicing)] pub mod test_utils { use super::*; - use crate::FileBacked; + use crate::node::Node; - use crate::nodestore::{Committed, ImmutableProposal, NodeStore, NodeStoreHeader}; + use crate::nodestore::{Committed, NodeStore, NodeStoreHeader}; // Helper function to wrap the node in a StoredArea and write it to the given offset. Returns the size of the area on success. pub fn test_write_new_node( @@ -869,8 +789,10 @@ pub mod test_utils { node: &Node, offset: u64, ) -> u64 { - let node_length = NodeStore::, FileBacked>::stored_len(node); - let area_size_index = area_size_to_index(node_length).unwrap(); + let mut encoded_node = Vec::new(); + node.as_bytes(0, &mut encoded_node); + let encoded_node_len = encoded_node.len() as u64; + let area_size_index = area_size_to_index(encoded_node_len).unwrap(); let mut stored_area_bytes = Vec::new(); node.as_bytes(area_size_index, &mut stored_area_bytes); nodestore @@ -923,9 +845,7 @@ pub mod test_utils { #[expect(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { use super::*; - use crate::linear::memory::MemStore; - use crate::nodestore::header::NodeStoreHeader; use crate::test_utils::seeded_rng; use rand::Rng; use rand::seq::IteratorRandom; diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index 353d8832434c..2feb76b1002e 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -10,21 +10,12 @@ use crate::hashednode::hash_node; use crate::linear::FileIoError; use crate::logger::trace; use crate::node::Node; -use crate::{Child, HashType, Path, ReadableStorage, SharedNode}; +use crate::{Child, HashType, MaybePersistedNode, NodeStore, Path, ReadableStorage, SharedNode}; -#[cfg(feature = "ethhash")] use super::NodeReader; #[cfg(feature = "ethhash")] -use crate::node::persist::MaybePersistedNode; -#[cfg(feature = "ethhash")] use std::ops::Deref; -use std::collections::HashMap; -use std::sync::Arc; - -use super::alloc::LinearAddress; -use super::{ImmutableProposal, NodeStore}; - /// Classified children for ethereum hash processing #[cfg(feature = "ethhash")] pub(super) struct ClassifiedChildren<'a> { @@ -32,7 +23,10 @@ pub(super) struct ClassifiedChildren<'a> { pub(super) hashed: Vec<(usize, (MaybePersistedNode, &'a mut HashType))>, } -impl NodeStore, S> { +impl NodeStore +where + NodeStore: NodeReader, +{ /// Helper function to classify children for ethereum hash processing /// We have some special cases based on the number of children /// and whether they are hashed or unhashed, so we need to classify them. @@ -62,7 +56,7 @@ impl NodeStore, S> { acc.hashed.push((idx, (maybe_persisted_node, h))); } else { // If not persisted, we need to get the node to hash it - if let Ok(node) = maybe_persisted.as_shared_node(self) { + if let Ok(node) = maybe_persisted.as_shared_node(&self) { acc.unhashed.push((idx, node.deref().clone())); } } @@ -76,13 +70,12 @@ impl NodeStore, S> { /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. /// Returns the hashed node and its hash. pub(super) fn hash_helper( - &mut self, + #[cfg(feature = "ethhash")] &self, mut node: Node, path_prefix: &mut Path, - new_nodes: &mut HashMap, #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, - ) -> Result<(LinearAddress, HashType), FileIoError> { - // If this is a branch, find all unhashed children and recursively call hash_helper on them. + ) -> Result<(MaybePersistedNode, HashType), FileIoError> { + // If this is a branch, find all unhashed children and recursively hash them. trace!("hashing {node:?} at {path_prefix:?}"); if let Node::Branch(ref mut b) = node { // special case code for ethereum hashes at the account level @@ -143,7 +136,7 @@ impl NodeStore, S> { // If this is empty or already hashed, we're done // Empty matches None, and non-Node types match Some(None) here, so we want // Some(Some(node)) - let Some(Some(child_node)) = child.as_mut().map(|child| child.as_mut_node()) else { + let Some(child_node) = child.as_mut().and_then(|child| child.as_mut_node()) else { continue; }; @@ -164,13 +157,13 @@ impl NodeStore, S> { path_prefix.0.push(nibble as u8); #[cfg(feature = "ethhash")] - let (child_addr, child_hash) = - self.hash_helper(child_node, path_prefix, new_nodes, make_fake_root)?; + let (child_node, child_hash) = + self.hash_helper(child_node, path_prefix, make_fake_root)?; #[cfg(not(feature = "ethhash"))] - let (child_addr, child_hash) = - self.hash_helper(child_node, path_prefix, new_nodes)?; + let (child_node, child_hash) = Self::hash_helper(child_node, path_prefix)?; - *child = Some(Child::AddressWithHash(child_addr, child_hash)); + *child = Some(Child::MaybePersisted(child_node, child_hash)); + trace!("child now {child:?}"); path_prefix.0.truncate(original_length); } } @@ -195,10 +188,6 @@ impl NodeStore, S> { #[cfg(not(feature = "ethhash"))] let hash = hash_node(&node, path_prefix); - let (addr, size) = self.allocate_node(&node)?; - - new_nodes.insert(addr, (size, node.into())); - - Ok((addr, hash)) + Ok((SharedNode::new(node).into(), hash)) } } diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index 937e640f3689..0c04b92d8ef6 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -166,6 +166,12 @@ pub struct NodeStoreHeader { ethhash: u64, } +impl Default for NodeStoreHeader { + fn default() -> Self { + Self::new() + } +} + impl NodeStoreHeader { /// The first SIZE bytes of the `ReadableStorage` are reserved for the /// [`NodeStoreHeader`]. diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 1e6db8f8d783..5d92689ec2c8 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -46,14 +46,15 @@ pub(crate) mod header; pub(crate) mod persist; use crate::logger::trace; +use crate::node::branch::ReadSerializable as _; use arc_swap::ArcSwap; use arc_swap::access::DynAccess; use smallvec::SmallVec; -use std::collections::HashMap; use std::fmt::Debug; +use std::io::{Error, ErrorKind}; // Re-export types from alloc module -pub use alloc::{AreaIndex, LinearAddress}; +pub use alloc::{AreaIndex, LinearAddress, NodeAllocator}; // Re-export types from header module pub use header::NodeStoreHeader; @@ -87,7 +88,10 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::Node; use crate::node::persist::MaybePersistedNode; -use crate::{FileBacked, FileIoError, Path, ReadableStorage, SharedNode, TrieHash}; +use crate::nodestore::alloc::AREA_SIZES; +use crate::{ + CacheReadStrategy, FileBacked, FileIoError, Path, ReadableStorage, SharedNode, TrieHash, +}; use super::linear::WritableStorage; @@ -218,9 +222,7 @@ impl NodeStore { /// # Errors /// /// Returns a [`FileIoError`] if the parent root cannot be read. - pub fn new( - parent: &Arc>, - ) -> Result { + pub fn new(parent: &Arc>) -> Result { let mut deleted = Vec::default(); let root = if let Some(ref root) = parent.kind.root() { deleted.push(root.clone()); @@ -359,13 +361,6 @@ pub struct Committed { root: Option, } -impl ReadInMemoryNode for Committed { - // A committed revision has no in-memory changes. All its nodes are in storage. - fn read_in_memory_node(&self, _addr: LinearAddress) -> Option { - None - } -} - #[derive(Clone, Debug)] pub enum NodeStoreParent { Proposed(Arc), @@ -387,8 +382,6 @@ impl Eq for NodeStoreParent {} #[derive(Debug)] /// Contains state for a proposed revision of the trie. pub struct ImmutableProposal { - /// Address --> Node for nodes created in this proposal. - new: HashMap, /// Nodes that have been deleted in this proposal. deleted: Box<[MaybePersistedNode]>, /// The parent of this proposal. @@ -414,43 +407,6 @@ impl ImmutableProposal { } } -impl ReadInMemoryNode for ImmutableProposal { - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - // Check if the node being requested was created in this proposal. - if let Some((_, node)) = self.new.get(&addr) { - return Some(node.clone()); - } - - // It wasn't. Try our parent, and its parent, and so on until we find it or find - // a committed revision. - match *self.parent.load() { - NodeStoreParent::Proposed(ref parent) => parent.read_in_memory_node(addr), - NodeStoreParent::Committed(_) => None, - } - } -} - -/// Proposed [`NodeStore`] types keep some nodes in memory. These nodes are new nodes that were allocated from -/// the free list, but are not yet on disk. This trait checks to see if a node is in memory and returns it if -/// it's there. If it's not there, it will be read from disk. -/// -/// This trait does not know anything about the underlying storage, so it just returns None if the node isn't in memory. -pub trait ReadInMemoryNode { - /// Returns the node at `addr` if it is in memory. - /// Returns None if it isn't. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option; -} - -impl ReadInMemoryNode for T -where - T: Deref, - T::Target: ReadInMemoryNode, -{ - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - self.deref().read_in_memory_node(addr) - } -} - /// Contains the state of a revision of a merkle trie. /// /// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. @@ -490,26 +446,7 @@ pub struct MutableProposal { parent: NodeStoreParent, } -impl ReadInMemoryNode for NodeStoreParent { - /// Returns the node at `addr` if it is in memory from a parent proposal. - /// If the base revision is committed, there are no in-memory nodes, so we return None - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - match self { - NodeStoreParent::Proposed(proposed) => proposed.read_in_memory_node(addr), - NodeStoreParent::Committed(_) => None, - } - } -} - -impl ReadInMemoryNode for MutableProposal { - /// [`MutableProposal`] types do not have any nodes in memory, but their parent proposal might, so we check there. - /// This might be recursive: a grandparent might also have that node in memory. - fn read_in_memory_node(&self, addr: LinearAddress) -> Option { - self.parent.read_in_memory_node(addr) - } -} - -impl, S: ReadableStorage> From> +impl, S: ReadableStorage> From> for NodeStore { fn from(val: NodeStore) -> Self { @@ -552,9 +489,12 @@ impl NodeStore, FileBacked> { /// Return a Committed version of this proposal, which doesn't have any modified nodes. /// This function is used during commit. #[must_use] - pub fn as_committed(&self) -> NodeStore { + pub fn as_committed( + &self, + current_revision: Arc>, + ) -> NodeStore { NodeStore { - header: self.header, + header: current_revision.header, kind: Committed { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), @@ -580,7 +520,6 @@ impl TryFrom> let mut nodestore = NodeStore { header, kind: Arc::new(ImmutableProposal { - new: HashMap::new(), deleted: kind.deleted.into(), parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), root_hash: None, @@ -596,23 +535,19 @@ impl TryFrom> }; // Hashes the trie and returns the address of the new root. - let mut new_nodes = HashMap::new(); #[cfg(feature = "ethhash")] - let (root_addr, root_hash) = - nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes, None)?; + let (root, root_hash) = nodestore.hash_helper(root, &mut Path::new(), None)?; #[cfg(not(feature = "ethhash"))] - let (root_addr, root_hash) = - nodestore.hash_helper(root, &mut Path::new(), &mut new_nodes)?; + let (root, root_hash) = + NodeStore::::hash_helper(root, &mut Path::new())?; - nodestore.header.set_root_address(Some(root_addr)); let immutable_proposal = Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); nodestore.kind = Arc::new(ImmutableProposal { - new: new_nodes, deleted: immutable_proposal.deleted, parent: immutable_proposal.parent, root_hash: Some(root_hash.into_triehash()), - root: Some(root_addr.into()), + root: Some(root), }); Ok(nodestore) @@ -621,19 +556,12 @@ impl TryFrom> impl NodeReader for NodeStore { fn read_node(&self, addr: LinearAddress) -> Result { - if let Some(node) = self.kind.read_in_memory_node(addr) { - return Ok(node); - } - self.read_node_from_disk(addr, "write") } } -impl NodeReader for NodeStore { +impl NodeReader for NodeStore { fn read_node(&self, addr: LinearAddress) -> Result { - if let Some(node) = self.kind.read_in_memory_node(addr) { - return Ok(node); - } self.read_node_from_disk(addr, "read") } } @@ -685,6 +613,83 @@ where } } +impl NodeStore { + /// Read a [Node] from the provided [`LinearAddress`]. + /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the node cannot be read. + pub fn read_node_from_disk( + &self, + addr: LinearAddress, + mode: &'static str, + ) -> Result { + if let Some(node) = self.storage.read_cached_node(addr, mode) { + return Ok(node); + } + + debug_assert!(addr.is_aligned()); + + // saturating because there is no way we can be reading at u64::MAX + // and this will fail very soon afterwards + let actual_addr = addr.get().saturating_add(1); // skip the length byte + + let _span = fastrace::local::LocalSpan::enter_with_local_parent("read_and_deserialize"); + + let area_stream = self.storage.stream_from(actual_addr)?; + let node: SharedNode = Node::from_reader(area_stream) + .map_err(|e| { + self.storage + .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) + })? + .into(); + match self.storage.cache_read_strategy() { + CacheReadStrategy::All => { + self.storage.cache_node(addr, node.clone()); + } + CacheReadStrategy::BranchReads => { + if !node.is_leaf() { + self.storage.cache_node(addr, node.clone()); + } + } + CacheReadStrategy::WritesOnly => {} + } + Ok(node) + } + + /// Returns (index, `area_size`) for the stored area at `addr`. + /// `index` is the index of `area_size` in the array of valid block sizes. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the area cannot be read. + pub fn area_index_and_size( + &self, + addr: LinearAddress, + ) -> Result<(AreaIndex, u64), FileIoError> { + let mut area_stream = self.storage.stream_from(addr.get())?; + + let index: AreaIndex = area_stream.read_byte().map_err(|e| { + self.storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("area_index_and_size".to_string()), + ) + })?; + + let size = *AREA_SIZES + .get(index as usize) + .ok_or(self.storage.file_io_error( + Error::other(format!("Invalid area size index {index}")), + addr.get(), + Some("area_index_and_size".to_string()), + ))?; + + Ok((index, size)) + } +} + impl HashedNodeReader for Arc where N: HashedNodeReader, @@ -711,8 +716,9 @@ impl NodeStore { self.storage .invalidate_cached_nodes(self.kind.deleted.iter()); trace!("There are {} nodes to reap", self.kind.deleted.len()); + let mut allocator = NodeAllocator::new(self.storage.as_ref(), &mut proposal.header); for node in take(&mut self.kind.deleted) { - proposal.delete_node(node)?; + allocator.delete_node(node)?; } Ok(()) } @@ -743,14 +749,11 @@ impl NodeStore { #[expect(clippy::unwrap_used)] #[expect(clippy::cast_possible_truncation)] mod tests { - use std::array::from_fn; + use crate::LeafNode; use crate::linear::memory::MemStore; - use crate::{BranchNode, Child, LeafNode}; use arc_swap::access::DynGuard; - use test_case::test_case; - use super::*; use alloc::{AREA_SIZES, area_size_to_index}; @@ -822,41 +825,8 @@ mod tests { } } - #[test_case(BranchNode { - partial_path: Path::from([6, 7, 8]), - value: Some(vec![9, 10, 11].into_boxed_slice()), - children: from_fn(|i| { - if i == 15 { - Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) - } else { - None - } - }), - }; "branch node with 1 child")] - #[test_case(BranchNode { - partial_path: Path::from([6, 7, 8]), - value: Some(vec![9, 10, 11].into_boxed_slice()), - children: from_fn(|_| - Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) - ), - }; "branch node with all child")] - #[test_case( - Node::Leaf(LeafNode { - partial_path: Path::from([0, 1, 2]), - value: Box::new([3, 4, 5]), - }); "leaf node")] - - fn test_serialized_len>(node: N) { - let node = node.into(); - - let computed_length = - NodeStore::, MemStore>::stored_len(&node); - - let mut serialized = Vec::new(); - node.as_bytes(0, &mut serialized); - assert_eq!(serialized.len() as u64, computed_length); - } #[test] + #[ignore = "https://github.com/ava-labs/firewood/issues/1054"] #[should_panic(expected = "Node size 16777225 is too large")] fn giant_node() { let memstore = MemStore::new(vec![]); diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index e96d964c7ae7..00947771cec1 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -27,9 +27,7 @@ //! - Memory-efficient serialization with pre-allocated buffers //! - Ring buffer management for io-uring operations -#[cfg(test)] use std::iter::FusedIterator; -use std::sync::Arc; use crate::linear::FileIoError; use coarsetime::Instant; @@ -38,16 +36,20 @@ use metrics::counter; #[cfg(feature = "io-uring")] use crate::logger::trace; -use crate::{FileBacked, WritableStorage}; +use crate::{FileBacked, MaybePersistedNode, NodeReader, WritableStorage}; #[cfg(test)] -use crate::{MaybePersistedNode, NodeReader, RootReader}; +use crate::RootReader; #[cfg(feature = "io-uring")] use crate::ReadableStorage; +use super::alloc::NodeAllocator; use super::header::NodeStoreHeader; -use super::{ImmutableProposal, NodeStore}; +use super::{Committed, NodeStore}; + +#[cfg(not(test))] +use super::RootReader; impl NodeStore { /// Persist the header from this proposal to storage. @@ -82,17 +84,14 @@ impl NodeStore { /// This iterator assumes the root node is unpersisted and will return it as the /// last item. It looks at each node and traverses the children in depth first order. /// A stack of child iterators is maintained to properly handle nested branches. -#[cfg(test)] struct UnPersistedNodeIterator<'a, N> { store: &'a N, stack: Vec, child_iter_stack: Vec + 'a>>, } -#[cfg(test)] impl FusedIterator for UnPersistedNodeIterator<'_, N> {} -#[cfg(test)] impl<'a, N: NodeReader + RootReader> UnPersistedNodeIterator<'a, N> { /// Creates a new iterator over unpersisted nodes in depth-first order. fn new(store: &'a N) -> Self { @@ -140,7 +139,6 @@ impl<'a, N: NodeReader + RootReader> UnPersistedNodeIterator<'a, N> { } } -#[cfg(test)] impl Iterator for UnPersistedNodeIterator<'_, N> { type Item = MaybePersistedNode; @@ -186,7 +184,7 @@ impl Iterator for UnPersistedNodeIterator<'_, N> { } } -impl NodeStore, FileBacked> { +impl NodeStore { /// Persist the freelist from this proposal to storage. /// /// # Errors @@ -208,21 +206,72 @@ impl NodeStore, FileBacked> { /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] #[cfg(not(feature = "io-uring"))] - pub fn flush_nodes(&self) -> Result<(), FileIoError> { + pub fn flush_nodes(&mut self) -> Result { let flush_start = Instant::now(); - for (addr, (area_size_index, node)) in &self.kind.new { - let mut stored_area_bytes = Vec::new(); - node.as_bytes(*area_size_index, &mut stored_area_bytes); + // keep arcs to the allocated nodes to add them to cache + let mut cached_nodes = Vec::new(); + + // find all the unpersisted nodes + let unpersisted_iter = UnPersistedNodeIterator::new(self); + + let mut header = self.header; + let mut allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); + for node in unpersisted_iter { + let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); + let mut serialized = Vec::new(); + shared_node.as_bytes(0, &mut serialized); + + let (persisted_address, area_size_index) = + allocator.allocate_node(serialized.as_slice())?; + *serialized.get_mut(0).expect("byte was reserved") = area_size_index; self.storage - .write(addr.get(), stored_area_bytes.as_slice())?; + .write(persisted_address.get(), serialized.as_slice())?; + node.persist_at(persisted_address); + + // Move the arc to a vector of persisted nodes for caching + // we save them so we don't have to lock the cache while we write them + // If we ever persist out of band, we might have a race condition, so + // consider adding each node to the cache as we persist them + cached_nodes.push((persisted_address, shared_node)); } - self.storage - .write_cached_nodes(self.kind.new.iter().map(|(&addr, (_, node))| (addr, node)))?; + self.storage.write_cached_nodes(cached_nodes)?; + let flush_time = flush_start.elapsed().as_millis(); counter!("firewood.flush_nodes").increment(flush_time); + Ok(header) + } +} + +impl NodeStore { + /// Persist the entire nodestore to storage. + /// + /// This method performs a complete persistence operation by: + /// 1. Flushing all nodes to storage + /// 2. Setting the root address in the header + /// 3. Flushing the header to storage + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if any of the persistence operations fail. + #[fastrace::trace(short_name = true)] + pub fn persist(&mut self) -> Result<(), FileIoError> { + // First persist all the nodes + self.header = self.flush_nodes()?; + + // Set the root address in the header based on the persisted root + let root_address = self + .kind + .root + .as_ref() + .and_then(crate::MaybePersistedNode::as_linear_address); + self.header.set_root_address(root_address); + + // Finally persist the header + self.flush_header()?; + Ok(()) } @@ -233,7 +282,7 @@ impl NodeStore, FileBacked> { /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] - pub fn flush_nodes(&self) -> Result<(), FileIoError> { + pub fn flush_nodes(&mut self) -> Result { use std::pin::Pin; #[derive(Clone, Debug)] @@ -281,6 +330,18 @@ impl NodeStore, FileBacked> { let flush_start = Instant::now(); + let mut header = self.header; + let mut node_allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); + + // Collect all unpersisted nodes first to avoid mutating self while iterating + let unpersisted_nodes: Vec = { + let unpersisted_iter = UnPersistedNodeIterator::new(self); + unpersisted_iter.collect() + }; + + // Collect addresses and nodes for caching + let mut cached_nodes = Vec::new(); + let mut ring = self.storage.ring.lock().expect("poisoned lock"); let mut saved_pinned_buffers = vec![ PinnedBufferEntry { @@ -290,9 +351,14 @@ impl NodeStore, FileBacked> { RINGSIZE ]; - for (&addr, &(area_size_index, ref node)) in &self.kind.new { + // Process each unpersisted node + for node in unpersisted_nodes { + let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger - node.as_bytes(area_size_index, &mut serialized); + shared_node.as_bytes(0, &mut serialized); + let (persisted_address, area_size_index) = + node_allocator.allocate_node(serialized.as_slice())?; + *serialized.get_mut(0).expect("byte was reserved") = area_size_index; let mut serialized = serialized.into_boxed_slice(); loop { @@ -303,12 +369,12 @@ impl NodeStore, FileBacked> { .find(|(_, pbe)| pbe.offset.is_none()) { pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); - pbe.offset = Some(addr.get()); + pbe.offset = Some(persisted_address.get()); let submission_queue_entry = self .storage .make_op(&pbe.pinned_buffer) - .offset(addr.get()) + .offset(persisted_address.get()) .build() .user_data(pos as u64); @@ -320,7 +386,7 @@ impl NodeStore, FileBacked> { ring.submitter().squeue_wait().map_err(|e| { self.storage.file_io_error( e, - addr.get(), + persisted_address.get(), Some("io-uring squeue_wait".to_string()), ) })?; @@ -344,6 +410,10 @@ impl NodeStore, FileBacked> { &mut saved_pinned_buffers, )?; } + + // Mark node as persisted and collect for cache + node.persist_at(persisted_address); + cached_nodes.push((persisted_address, shared_node)); } let pending = saved_pinned_buffers .iter() @@ -362,14 +432,13 @@ impl NodeStore, FileBacked> { saved_pinned_buffers.iter().find(|pbe| pbe.offset.is_some()) ); - self.storage - .write_cached_nodes(self.kind.new.iter().map(|(&addr, (_, node))| (addr, node)))?; + self.storage.write_cached_nodes(cached_nodes)?; debug_assert!(ring.completion().is_empty()); let flush_time = flush_start.elapsed().as_millis(); counter!("firewood.flush_nodes").increment(flush_time); - Ok(()) + Ok(header) } } From 03e576419dd074b2671fa22feb362c2e9d3dbaa0 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 23 Jul 2025 08:16:27 -0700 Subject: [PATCH 0848/1053] test: add fuzz testing for checker, with fixes (#1118) Fixes #1110 Passes consistently with the following fixes: - The partial path for the branch was not added when computing the child hashes - Always render the path, not the partial path before this node --------- Co-authored-by: qusuyan --- firewood/src/db.rs | 55 +++++++++++++++++++++++++++++++++++++- storage/src/checker/mod.rs | 13 +++++---- storage/src/lib.rs | 4 +-- storage/src/node/path.rs | 10 +++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 758f8e0b648f..00465a9a045e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,7 +14,8 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; use firewood_storage::{ - Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, + CheckOpt, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, + TrieHash, }; use metrics::{counter, describe_counter}; use std::io::Write; @@ -326,6 +327,12 @@ impl Db { pub fn metrics(&self) -> Arc { self.metrics.clone() } + + /// Check the database for consistency + pub async fn check(&self, opt: CheckOpt) -> Result<(), firewood_storage::CheckerError> { + let latest_rev_nodestore = self.manager.current_revision(); + latest_rev_nodestore.check(opt) + } } #[derive(Debug)] @@ -477,6 +484,10 @@ mod test { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; + use firewood_storage::CheckOpt; + use firewood_storage::logger::trace; + use rand::rng; + use crate::db::Db; use crate::v2::api::{Db as _, DbView as _, Error, Proposal as _}; @@ -767,6 +778,48 @@ mod test { } } + #[tokio::test] + async fn fuzz_checker() { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + + let _ = env_logger::Builder::new().is_test(true).try_init(); + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); + + let db = testdb().await; + + // takes about 0.3s on a mac to run 50 times + for _ in 0..50 { + // create a batch of 10 random key-value pairs + let batch = (0..10).fold(vec![], |mut batch, _| { + let key: [u8; 32] = rng.borrow_mut().random(); + let value: [u8; 32] = rng.borrow_mut().random(); + batch.push(BatchOp::Put { key, value }); + trace!("batch: {batch:?}"); + batch + }); + let proposal = db.propose(batch).await.unwrap(); + proposal.commit().await.unwrap(); + + // check the database for consistency, sometimes checking the hashes + let hash_check = rng.borrow_mut().random(); + if let Err(e) = db.check(CheckOpt { hash_check }).await { + db.dump(&mut std::io::stdout()).await.unwrap(); + panic!("error: {e}"); + } + } + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 746a165fe493..045272bcf2e5 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -103,6 +103,7 @@ impl NodeStore { )), )?; let mut child_path_prefix = path_prefix.clone(); + child_path_prefix.0.extend_from_slice(node.partial_path()); child_path_prefix.0.push(nibble as u8); self.visit_trie( address, @@ -118,8 +119,10 @@ impl NodeStore { if hash_check { let hash = hash_node(&node, &path_prefix); if hash != subtree_root_hash { + let mut path = path_prefix.clone(); + path.0.extend_from_slice(node.partial_path()); return Err(CheckerError::HashMismatch { - partial_path: path_prefix, + path, address: subtree_root_address, parent_stored_hash: subtree_root_hash, computed_hash: hash, @@ -285,7 +288,7 @@ mod test { value: Box::new([3, 4, 5]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); - let leaf_hash = hash_node(&leaf, &Path::from_nibbles_iterator([0u8, 1].into_iter())); + let leaf_hash = hash_node(&leaf, &Path::from_nibbles_iterator([0u8, 0, 1].into_iter())); let leaf_area = test_write_new_node(nodestore, &leaf, high_watermark); high_watermark += leaf_area; @@ -362,7 +365,7 @@ mod test { panic!("test trie content changed, the test should be updated"); }; let wrong_hash = HashType::default(); - let branch_path = &branch_node.as_branch().unwrap().partial_path; + let branch_path = Path::from([0]) + branch_node.as_branch().unwrap().partial_path.clone(); let Some(Child::AddressWithHash(_, hash)) = root_node.as_branch_mut().unwrap().children [branch_path[0] as usize] .replace(Child::AddressWithHash(*branch_addr, wrong_hash.clone())) @@ -381,12 +384,12 @@ mod test { err, CheckerError::HashMismatch { address, - partial_path, + path, parent_stored_hash, computed_hash } if address == *branch_addr - && partial_path == *branch_path + && path == branch_path && parent_stored_hash == wrong_hash && computed_hash == branch_hash )); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 2e4bed65d553..c225e04e16e6 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -141,11 +141,11 @@ pub enum CheckerError { /// Hash mismatch for a node #[error( - "Hash mismatch for node {partial_path:?} at address {address}: parent stored {parent_stored_hash}, computed {computed_hash}" + "Hash mismatch for node {path:?} at address {address}: parent stored {parent_stored_hash}, computed {computed_hash}" )] HashMismatch { /// The path of the node - partial_path: Path, + path: Path, /// The address of the node address: LinearAddress, /// The hash value stored in the parent node diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 4654a6ed1bba..9d9f9a9a146f 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -19,6 +19,7 @@ use bitflags::bitflags; use smallvec::SmallVec; use std::fmt::{self, Debug}; use std::iter::{FusedIterator, once}; +use std::ops::Add; static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; @@ -135,6 +136,15 @@ pub struct BytesIterator<'a> { nibbles_iter: std::slice::Iter<'a, u8>, } +impl Add for Path { + type Output = Path; + fn add(self, other: Path) -> Self::Output { + let mut new = self.clone(); + new.extend(other.iter().copied()); + new + } +} + impl Iterator for BytesIterator<'_> { type Item = u8; fn next(&mut self) -> Option { From 72bedbf1050c908067dd093000517445844effb2 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:47:37 -0500 Subject: [PATCH 0849/1053] feat(checker): disable buggy ethhash checker (#1127) Current checker has bug with ethhash #1108. Disable for now. --- firewood/src/db.rs | 4 ++++ fwdctl/tests/cli.rs | 8 ++++++++ storage/src/checker/mod.rs | 12 ++++++++++++ storage/src/nodestore/alloc.rs | 3 ++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 00465a9a045e..2f6b9e2a891c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -779,6 +779,10 @@ mod test { } #[tokio::test] + #[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1108" + )] async fn fuzz_checker() { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index e4b45b3629cd..eb66b1e58135 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -521,6 +521,10 @@ fn fwdctl_dump_with_hex() -> Result<()> { #[test] #[serial] +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1108" +)] fn fwdctl_check_empty_db() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") @@ -540,6 +544,10 @@ fn fwdctl_check_empty_db() -> Result<()> { #[test] #[serial] +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1108" +)] fn fwdctl_check_db_with_data() -> Result<()> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng, rng}; diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 045272bcf2e5..711382faf7d3 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -37,6 +37,10 @@ impl NodeStore { /// Panics if the header has too many free lists, which can never happen since freelists have a fixed size. // TODO: report all errors, not just the first one pub fn check(&self, opt: CheckOpt) -> Result<(), CheckerError> { + if cfg!(feature = "ethhash") { + unimplemented!("ethhash is not supported yet"); + } + // 1. Check the header let db_size = self.size(); @@ -334,6 +338,10 @@ mod test { use std::collections::HashMap; #[test] + #[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1108" + )] // This test creates a simple trie and checks that the checker traverses it correctly. // We use primitive calls here to do a low-level check. // TODO: add a high-level test in the firewood crate @@ -353,6 +361,10 @@ mod test { } #[test] + #[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1108" + )] // This test permutes the simple trie with a wrong hash and checks that the checker detects it. fn checker_traverse_trie_with_wrong_hash() { let memstore = MemStore::new(vec![]); diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 0d26a4c99cee..1fd623354c17 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -776,7 +776,7 @@ impl NodeStore { } #[cfg(test)] -#[expect(clippy::unwrap_used, clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] pub mod test_utils { use super::*; @@ -789,6 +789,7 @@ pub mod test_utils { node: &Node, offset: u64, ) -> u64 { + #![expect(clippy::indexing_slicing)] let mut encoded_node = Vec::new(); node.as_bytes(0, &mut encoded_node); let encoded_node_len = encoded_node.len() as u64; From 984123d83b2fc675538e37551edac5a54b2f84f5 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 23 Jul 2025 15:27:55 -0700 Subject: [PATCH 0850/1053] feat: add `Children` type alias (#1123) This commit introduces a new type alias `Children` which is a fixed-size array of `Option`. This is convenient for all of the places where we represent the children of a branch node in the trie. Also included is a method to construct the array in const context, allowing for easy creation of `Children` instances. Perhaps most controversial in this change will be the changes to `children_addresses` and `children_hashes`. Instead of returning an iterator, they instead return the array of requested values. The implementation of this is also of particular note because I `zip` over an `iter_mut` preventing the need for any array indexing like was previously used to construct these arrays. This change was primarily motivated by my range proof verification work. I was touching code that indicated differences with branch_factor_256, so I expanded my clippy checks to ensure that the code compiles with the feature enabled and discovered that it didn't unrelated to my changes. The `Default::default()` calls work for the children array when `branch_factor_256` is disabled because rust stdlib has an [implementation for arrays of up to 32 elements](https://doc.rust-lang.org/std/primitive.array.html#:~:text=Arrays%20of%20sizes,to%20size%2032.). This is due to a limitation of the type system that prevents a generic implementation over the generic size. --- firewood/src/merkle.rs | 20 +++--- firewood/src/proof.rs | 41 +++++-------- storage/src/checker/mod.rs | 4 +- storage/src/hashednode.rs | 21 +++---- storage/src/hashers/ethhash.rs | 59 +++++++++--------- storage/src/hashers/merkledb.rs | 11 ++-- storage/src/lib.rs | 2 +- storage/src/node/branch.rs | 102 ++++++++++++++++++++++++++----- storage/src/node/mod.rs | 4 +- storage/src/nodestore/hash.rs | 4 +- storage/src/nodestore/persist.rs | 64 +++++++++---------- 11 files changed, 191 insertions(+), 141 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 3147497080ec..509f4d042698 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -195,15 +195,11 @@ impl Merkle { // No nodes, even the root, are before `key`. // The root alone proves the non-existence of `key`. // TODO reduce duplicate code with ProofNode::from - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = - [const { None }; BranchNode::MAX_CHILDREN]; - if let Some(branch) = root.as_branch() { - // TODO danlaine: can we avoid indexing? - #[expect(clippy::indexing_slicing)] - for (i, hash) in branch.children_hashes() { - child_hashes[i] = Some(hash.clone()); - } - } + let child_hashes = if let Some(branch) = root.as_branch() { + branch.children_hashes() + } else { + BranchNode::empty_children() + }; proof.push(ProofNode { key: root.partial_path().bytes(), @@ -533,7 +529,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: Some(value), - children: [const { None }; BranchNode::MAX_CHILDREN], + children: BranchNode::empty_children(), }; // Shorten the node's partial path since it has a new parent. @@ -584,7 +580,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), value: Some(std::mem::take(&mut leaf.value)), - children: [const { None }; BranchNode::MAX_CHILDREN], + children: BranchNode::empty_children(), }; let new_leaf = Node::Leaf(LeafNode { @@ -610,7 +606,7 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: None, - children: [const { None }; BranchNode::MAX_CHILDREN], + children: BranchNode::empty_children(), }; node.update_partial_path(node_partial_path); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index c52d9683257f..0402d71c8c8c 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -11,8 +11,8 @@ )] use firewood_storage::{ - BranchNode, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, PathIterItem, - Preimage, TrieHash, ValueDigest, + BranchNode, Children, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, + PathIterItem, Preimage, TrieHash, ValueDigest, }; use thiserror::Error; @@ -87,7 +87,7 @@ pub struct ProofNode { /// Otherwise, the node's value or the hash of its value. pub value_digest: Option>, /// The hash of each child, or None if the child does not exist. - pub child_hashes: [Option; BranchNode::MAX_CHILDREN], + pub child_hashes: Children, } impl Hashable for ProofNode { @@ -107,26 +107,18 @@ impl Hashable for ProofNode { }) } - fn children(&self) -> impl Iterator + Clone { - self.child_hashes - .iter() - .enumerate() - .filter_map(|(i, hash)| hash.as_ref().map(|h| (i, h.clone()))) + fn children(&self) -> Children { + self.child_hashes.clone() } } impl From for ProofNode { fn from(item: PathIterItem) -> Self { - let mut child_hashes: [Option; BranchNode::MAX_CHILDREN] = - [const { None }; BranchNode::MAX_CHILDREN]; - - if let Some(branch) = item.node.as_branch() { - // TODO danlaine: can we avoid indexing? - #[expect(clippy::indexing_slicing)] - for (i, hash) in branch.children_hashes() { - child_hashes[i] = Some(hash.clone()); - } - } + let child_hashes = if let Some(branch) = item.node.as_branch() { + branch.children_hashes() + } else { + BranchNode::empty_children() + }; #[cfg(feature = "ethhash")] let partial_len = item @@ -208,14 +200,11 @@ impl Proof { expected_hash = node .children() - .find_map(|(i, hash)| { - if i == next_nibble as usize { - Some(hash.clone()) - } else { - None - } - }) - .ok_or(ProofError::NodeNotInTrie)?; + .get(usize::from(next_nibble)) + .ok_or(ProofError::ChildIndexOutOfBounds)? + .as_ref() + .ok_or(ProofError::NodeNotInTrie)? + .clone(); } } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 711382faf7d3..d3759afd47d6 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -296,7 +296,7 @@ mod test { let leaf_area = test_write_new_node(nodestore, &leaf, high_watermark); high_watermark += leaf_area; - let mut branch_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + let mut branch_children = BranchNode::empty_children(); branch_children[1] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); let branch = Node::Branch(Box::new(BranchNode { partial_path: Path::from([0]), @@ -308,7 +308,7 @@ mod test { let branch_area = test_write_new_node(nodestore, &branch, high_watermark); high_watermark += branch_area; - let mut root_children: [Option; BranchNode::MAX_CHILDREN] = Default::default(); + let mut root_children = BranchNode::empty_children(); root_children[0] = Some(Child::AddressWithHash(branch_addr, branch_hash)); let root = Node::Branch(Box::new(BranchNode { partial_path: Path::from([]), diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 618862c5ba2a..5a6eaa57b7ed 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -6,13 +6,10 @@ reason = "Found 1 occurrences after enabling the lint." )] -use crate::{BranchNode, HashType, LeafNode, Node, Path}; +use crate::{BranchNode, Children, HashType, LeafNode, Node, Path}; use sha2::{Digest, Sha256}; use smallvec::SmallVec; -use std::{ - iter::{self}, - ops::Deref, -}; +use std::ops::Deref; /// Returns the hash of `node`, which is at the given `path_prefix`. #[must_use] @@ -140,7 +137,7 @@ pub trait Hashable { fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. - fn children(&self) -> impl Iterator + Clone; + fn children(&self) -> Children; } /// A preimage of a hash. @@ -154,7 +151,7 @@ pub trait Preimage { trait HashableNode { fn partial_path(&self) -> impl Iterator + Clone; fn value(&self) -> Option<&[u8]>; - fn children_iter(&self) -> impl Iterator + Clone; + fn child_hashes(&self) -> Children; } impl HashableNode for BranchNode { @@ -166,7 +163,7 @@ impl HashableNode for BranchNode { self.value.as_deref() } - fn children_iter(&self) -> impl Iterator + Clone { + fn child_hashes(&self) -> Children { self.children_hashes() } } @@ -180,8 +177,8 @@ impl HashableNode for LeafNode { Some(&self.value) } - fn children_iter(&self) -> impl Iterator + Clone { - iter::empty() + fn child_hashes(&self) -> Children { + BranchNode::empty_children() } } @@ -214,7 +211,7 @@ impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { self.node.value().map(ValueDigest::Value) } - fn children(&self) -> impl Iterator + Clone { - self.node.children_iter() + fn children(&self) -> Children { + self.node.child_hashes() } } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index b949abddb9db..399f98d466ac 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -18,18 +18,17 @@ ) )] -use std::iter::once; - use crate::logger::warn; use crate::{ - HashType, Hashable, Preimage, TrieHash, ValueDigest, hashednode::HasUpdate, logger::trace, + BranchNode, HashType, Hashable, Preimage, TrieHash, ValueDigest, hashednode::HasUpdate, + logger::trace, }; use bitfield::bitfield; use bytes::BytesMut; +use rlp::{Rlp, RlpStream}; use sha3::{Digest, Keccak256}; use smallvec::SmallVec; - -use rlp::{Rlp, RlpStream}; +use std::iter::once; impl HasUpdate for Keccak256 { fn update>(&mut self, data: T) { @@ -118,7 +117,9 @@ impl Preimage for T { let is_account = self.key().size_hint().0 == 64; trace!("is_account: {is_account}"); - let children = self.children().count(); + let child_hashes = self.children(); + + let children = child_hashes.iter().filter(|c| c.is_some()).count(); if children == 0 { // since there are no children, this must be a leaf @@ -170,31 +171,23 @@ impl Preimage for T { } else { // for a branch, there are always 16 children and a value // Child::None we encode as RLP empty_data (0x80) - let mut rlp = RlpStream::new_list(17); - let mut child_iter = self.children().peekable(); - for index in 0..=15 { - if let Some(&(child_index, ref digest)) = child_iter.peek() { - if child_index == index { - match digest { - HashType::Hash(hash) => rlp.append(&hash.as_slice()), - HashType::Rlp(rlp_bytes) => rlp.append_raw(rlp_bytes, 1), - }; - child_iter.next(); - } else { - rlp.append_empty_data(); - } - } else { - // exhausted all indexes - rlp.append_empty_data(); - } + let mut rlp = RlpStream::new_list(const { BranchNode::MAX_CHILDREN + 1 }); + for child in &child_hashes { + match child { + Some(HashType::Hash(hash)) => rlp.append(&hash.as_slice()), + Some(HashType::Rlp(rlp_bytes)) => rlp.append_raw(rlp_bytes, 1), + None => rlp.append_empty_data(), + }; } - if let Some(digest) = self.value_digest() { - if is_account { - rlp.append_empty_data(); - } else { - rlp.append(&*digest); - } + // For branch nodes, we need to append the value as the 17th element in the RLP list. + // However, account nodes (depth 32) handle values differently - they don't store + // the value directly in the branch node, but rather in the account structure itself. + // This is because account nodes have special handling where the storage root hash + // gets replaced in the account data structure during serialization. + let digest = (!is_account).then(|| self.value_digest()).flatten(); + if let Some(digest) = digest { + rlp.append(&*digest); } else { rlp.append_empty_data(); } @@ -217,7 +210,11 @@ impl Preimage for T { let replacement_hash = if children == 1 { // we need to treat this child like it's a root node, so the partial path is // actually one longer than it is reported - match self.children().next().expect("we know there is one").1 { + match child_hashes + .iter() + .find_map(|c| c.as_ref()) + .expect("we know there is exactly one child") + { HashType::Hash(hash) => hash.clone(), HashType::Rlp(rlp_bytes) => { let mut rlp = RlpStream::new_list(2); @@ -225,7 +222,7 @@ impl Preimage for T { self.partial_path().collect::>(), true, )); - rlp.append_raw(&rlp_bytes, 1); + rlp.append_raw(rlp_bytes, 1); let bytes = rlp.out(); TrieHash::from(Keccak256::digest(bytes)) } diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index af67882f1dad..98666ad23abc 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -35,12 +35,15 @@ impl Preimage for T { fn write(&self, buf: &mut impl HasUpdate) { let children = self.children(); - let num_children = children.clone().count() as u64; + let num_children = children.iter().filter(|c| c.is_some()).count() as u64; + add_varint_to_buf(buf, num_children); - for (index, hash) in children { - add_varint_to_buf(buf, index as u64); - buf.update(hash); + for (index, hash) in children.iter().enumerate() { + if let Some(hash) = hash { + add_varint_to_buf(buf, index as u64); + buf.update(hash); + } } // Add value digest (if any) to hash pre-image diff --git a/storage/src/lib.rs b/storage/src/lib.rs index c225e04e16e6..85b828fa5136 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -42,7 +42,7 @@ pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; pub use node::{ - BranchNode, Child, LeafNode, Node, PathIterItem, + BranchNode, Child, Children, LeafNode, Node, PathIterItem, branch::{HashType, IntoHashType}, }; pub use nodestore::{ diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index ddaf03efb0a1..c3286e51945b 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -313,6 +313,9 @@ mod ethhash { } } +/// Type alias for a collection of children in a branch node. +pub type Children = [Option; BranchNode::MAX_CHILDREN]; + #[derive(PartialEq, Eq, Clone)] /// A branch node pub struct BranchNode { @@ -326,7 +329,7 @@ pub struct BranchNode { /// Element i is the child at index i, or None if there is no child at that index. /// Each element is (`child_hash`, `child_address`). /// `child_address` is None if we don't know the child's hash. - pub children: [Option; Self::MAX_CHILDREN], + pub children: Children, } impl Debug for BranchNode { @@ -371,6 +374,12 @@ impl BranchNode { #[cfg(not(feature = "branch_factor_256"))] pub const MAX_CHILDREN: usize = 16; + /// Convenience function to create a new array of empty children. + #[must_use] + pub const fn empty_children() -> Children { + [const { None }; Self::MAX_CHILDREN] + } + /// Returns the address of the child at the given index. /// Panics if `child_index` >= [`BranchNode::MAX_CHILDREN`]. #[must_use] @@ -391,7 +400,15 @@ impl BranchNode { *child = new_child; } - // Helper to iterate over only valid children + /// Helper to iterate over only valid children + /// + /// ## Panics + /// + /// Note: This function will panic if any child is a [`Child::Node`] variant + /// as it is still mutable and has not been hashed yet. Unlike + /// [`BranchNode::children_addresses`], this will _not_ panic if the child + /// is an unpersisted [`Child::MaybePersisted`]. + #[track_caller] pub(crate) fn children_iter( &self, ) -> impl Iterator + Clone { @@ -400,7 +417,9 @@ impl BranchNode { .enumerate() .filter_map(|(i, child)| match child { None => None, - Some(Child::Node(_)) => unreachable!("TODO make unreachable"), + Some(Child::Node(_)) => { + panic!("attempted to iterate over an in-memory mutable node") + } Some(Child::AddressWithHash(address, hash)) => Some((i, (*address, hash))), Some(Child::MaybePersisted(maybe_persisted, hash)) => { // For MaybePersisted, we need the address if it's persisted @@ -411,20 +430,71 @@ impl BranchNode { }) } - /// Returns (index, hash) for each child that has a hash set. - pub fn children_hashes(&self) -> impl Iterator + Clone { - self.children.iter().enumerate().filter_map(|(i, child)| { - child - .as_ref() - .and_then(|child| child.hash().cloned()) - .map(|hash| (i, hash)) - }) + /// Returns a set of hashes for each child that has a hash set. + /// + /// The index of the hash in the returned array corresponds to the index of the child + /// in the branch node. + /// + /// ## Panics + /// + /// Note: This function will panic if any child is a [`Child::Node`] variant + /// as it is still mutable and has not been hashed yet. + /// + /// This is an unintentional side effect of the current implementation. Future + /// changes will have this check implemented structurally to prevent such panics. + #[must_use] + #[track_caller] + pub fn children_hashes(&self) -> Children { + let mut hashes = Self::empty_children(); + for (child, slot) in self.children.iter().zip(hashes.iter_mut()) { + match child { + None => {} + Some(Child::Node(_)) => { + panic!("attempted to get the hash of an in-memory mutable node") + } + Some(Child::AddressWithHash(_, hash)) => _ = slot.replace(hash.clone()), + Some(Child::MaybePersisted(_, hash)) => _ = slot.replace(hash.clone()), + } + } + hashes } - /// Returns (index, address) for each child that has a hash set. - pub fn children_addresses(&self) -> impl Iterator + Clone { - self.children_iter() - .map(|(idx, (address, _))| (idx, address)) + /// Returns a set of addresses for each child that has an address set. + /// + /// The index of the address in the returned array corresponds to the index of the child + /// in the branch node. + /// + /// ## Panics + /// + /// Note: This function will panic if: + /// - Any child is a [`Child::Node`] variant as it does not have an address. + /// - Any child is a [`Child::MaybePersisted`] variant that is not yet + /// persisted, as we do not yet know its address. + /// + /// This is an unintentional side effect of the current implementation. Future + /// changes will have this check implemented structurally to prevent such panics. + #[must_use] + #[track_caller] + pub fn children_addresses(&self) -> Children { + let mut addrs = Self::empty_children(); + for (child, slot) in self.children.iter().zip(addrs.iter_mut()) { + match child { + None => {} + Some(Child::Node(_)) => { + panic!("attempted to get the address of an in-memory mutable node") + } + Some(Child::AddressWithHash(address, _)) => _ = slot.replace(*address), + Some(Child::MaybePersisted(maybe_persisted, _)) => { + // For MaybePersisted, we need the address if it's persisted + if let Some(addr) = maybe_persisted.as_linear_address() { + slot.replace(addr); + } else { + panic!("attempted to get the address of an unpersisted MaybePersistedNode") + } + } + } + } + addrs } } @@ -433,7 +503,7 @@ impl From<&LeafNode> for BranchNode { BranchNode { partial_path: leaf.partial_path.clone(), value: Some(Box::from(&leaf.value[..])), - children: [const { None }; BranchNode::MAX_CHILDREN], + children: BranchNode::empty_children(), } } } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index dd333aad36df..5e7abbe3f7b0 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -22,7 +22,7 @@ use crate::node::branch::ReadSerializable; use crate::{HashType, LinearAddress, Path, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; -pub use branch::{BranchNode, Child}; +pub use branch::{BranchNode, Child, Children}; use enum_as_inner::EnumAsInner; use integer_encoding::{VarInt, VarIntReader as _}; pub use leaf::LeafNode; @@ -356,7 +356,7 @@ impl Node { None }; - let mut children = [const { None }; BranchNode::MAX_CHILDREN]; + let mut children = BranchNode::empty_children(); if childcount == 0 { // branch is full of all children for child in &mut children { diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index 2feb76b1002e..b321b7a9bbf2 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -6,6 +6,8 @@ //! This module contains all node hashing functionality for the nodestore, including //! specialized support for Ethereum-compatible hash processing. +#[cfg(feature = "ethhash")] +use crate::Children; use crate::hashednode::hash_node; use crate::linear::FileIoError; use crate::logger::trace; @@ -33,7 +35,7 @@ where #[cfg(feature = "ethhash")] pub(super) fn ethhash_classify_children<'a>( &self, - children: &'a mut [Option; crate::node::BranchNode::MAX_CHILDREN], + children: &'a mut Children, ) -> ClassifiedChildren<'a> { children.iter_mut().enumerate().fold( ClassifiedChildren { diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 00947771cec1..fe522c81cb49 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -250,7 +250,7 @@ impl NodeStore { /// /// This method performs a complete persistence operation by: /// 1. Flushing all nodes to storage - /// 2. Setting the root address in the header + /// 2. Setting the root address in the header /// 3. Flushing the header to storage /// /// # Errors @@ -582,42 +582,38 @@ mod tests { // -> branch2 -> leaf[2] let inner_branch = create_branch(&[10], Some(&[50]), vec![(0, leaves[2].clone())]); + let mut children = BranchNode::empty_children(); + for (value, slot) in [ + // unpersisted leaves + Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(leaves[0].clone())), + HashType::default(), + ), + Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(leaves[1].clone())), + HashType::default(), + ), + // unpersisted branch + Child::MaybePersisted( + MaybePersistedNode::from(SharedNode::new(inner_branch.clone())), + HashType::default(), + ), + // persisted branch + Child::MaybePersisted( + MaybePersistedNode::from(LinearAddress::new(42).unwrap()), + HashType::default(), + ), + ] + .into_iter() + .zip(children.iter_mut()) + { + slot.replace(value); + } + let root_branch: Node = BranchNode { partial_path: Path::new(), value: None, - children: [ - // unpersisted leaves - Some(Child::MaybePersisted( - MaybePersistedNode::from(SharedNode::new(leaves[0].clone())), - HashType::default(), - )), - Some(Child::MaybePersisted( - MaybePersistedNode::from(SharedNode::new(leaves[1].clone())), - HashType::default(), - )), - // unpersisted branch - Some(Child::MaybePersisted( - MaybePersistedNode::from(SharedNode::new(inner_branch.clone())), - HashType::default(), - )), - // persisted branch - Some(Child::MaybePersisted( - MaybePersistedNode::from(LinearAddress::new(42).unwrap()), - HashType::default(), - )), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ], + children, } .into(); From 697e85cc3a4b031869c38b16e761575e680e604c Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 23 Jul 2025 15:33:10 -0700 Subject: [PATCH 0851/1053] chore: Checker test cleanups (#1131) - Make each nibble and byte different for easier identification - Add a partial path to the root node - Return TestTrie structure for all the metadata for the test trie - Handle structural changes to test data more gracefully - Implement PartialEq on CheckerError - Replace ascii gen_test with mermaid diagram --- storage/Cargo.toml | 1 + storage/src/checker/mod.rs | 153 +++++++++++++++++++++---------------- storage/src/lib.rs | 7 +- 3 files changed, 92 insertions(+), 69 deletions(-) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 6cb9cfe68a7c..2635c58fec3d 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -38,6 +38,7 @@ nonzero_ext = "0.3.0" num-traits = "0.2.19" semver = "1.0.26" triomphe = "0.1.14" +derive-where = "1.5.0" # Optional dependencies bytes = { version = "1.10.1", optional = true } io-uring = { version = "0.7.8", optional = true } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index d3759afd47d6..e155a88f5d84 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -268,57 +268,58 @@ mod test { use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; use crate::{BranchNode, Child, LeafNode, NodeStore, Path, hash_node}; - // set up a basic trie: - // ------------------------- - // | | X | X | ... | Root node - // ------------------------- - // | - // V - // ------------------------- - // | X | | X | ... | Branch node - // ------------------------- - // | - // V - // ------------------------- - // | [0,1] -> [3,4,5] | Leaf node - // ------------------------- + #[derive(Debug)] + struct TestTrie { + nodes: Vec<(Node, LinearAddress)>, + high_watermark: u64, + root_address: LinearAddress, + root_hash: HashType, + } + + /// Generate a test trie with the following structure: + /// + #[cfg_attr(doc, aquamarine)] + /// ```mermaid + /// graph TD + /// Root["Root Node
    partial_path: [2]
    children: [0] -> Branch"] + /// Branch["Branch Node
    partial_path: [3]
    path: 0x203
    children: [1] -> Leaf"] + /// Leaf["Leaf Node
    partial_path: [4, 5]
    path: 0x203145
    value: [6, 7, 8]"] + /// + /// Root -->|"nibble 0"| Branch + /// Branch -->|"nibble 1"| Leaf + /// ``` #[expect(clippy::arithmetic_side_effects)] - fn gen_test_trie( - nodestore: &mut NodeStore, - ) -> (Vec<(Node, LinearAddress)>, u64, (LinearAddress, HashType)) { + fn gen_test_trie(nodestore: &mut NodeStore) -> TestTrie { let mut high_watermark = NodeStoreHeader::SIZE; let leaf = Node::Leaf(LeafNode { - partial_path: Path::from([0, 1]), - value: Box::new([3, 4, 5]), + partial_path: Path::from([4, 5]), + value: Box::new([6, 7, 8]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); - let leaf_hash = hash_node(&leaf, &Path::from_nibbles_iterator([0u8, 0, 1].into_iter())); - let leaf_area = test_write_new_node(nodestore, &leaf, high_watermark); - high_watermark += leaf_area; + let leaf_hash = hash_node(&leaf, &Path::from([2, 0, 3, 1])); + high_watermark += test_write_new_node(nodestore, &leaf, high_watermark); let mut branch_children = BranchNode::empty_children(); branch_children[1] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); let branch = Node::Branch(Box::new(BranchNode { - partial_path: Path::from([0]), + partial_path: Path::from([3]), value: None, children: branch_children, })); let branch_addr = LinearAddress::new(high_watermark).unwrap(); - let branch_hash = hash_node(&branch, &Path::from_nibbles_iterator([0u8].into_iter())); - let branch_area = test_write_new_node(nodestore, &branch, high_watermark); - high_watermark += branch_area; + let branch_hash = hash_node(&branch, &Path::from([2, 0])); + high_watermark += test_write_new_node(nodestore, &branch, high_watermark); let mut root_children = BranchNode::empty_children(); root_children[0] = Some(Child::AddressWithHash(branch_addr, branch_hash)); let root = Node::Branch(Box::new(BranchNode { - partial_path: Path::from([]), + partial_path: Path::from([2]), value: None, children: root_children, })); let root_addr = LinearAddress::new(high_watermark).unwrap(); let root_hash = hash_node(&root, &Path::new()); - let root_area = test_write_new_node(nodestore, &root, high_watermark); - high_watermark += root_area; + high_watermark += test_write_new_node(nodestore, &root, high_watermark); // write the header test_write_header( @@ -328,11 +329,12 @@ mod test { FreeLists::default(), ); - ( - vec![(leaf, leaf_addr), (branch, branch_addr), (root, root_addr)], + TestTrie { + nodes: vec![(leaf, leaf_addr), (branch, branch_addr), (root, root_addr)], high_watermark, - (root_addr, root_hash), - ) + root_address: root_addr, + root_hash, + } } use std::collections::HashMap; @@ -344,17 +346,23 @@ mod test { )] // This test creates a simple trie and checks that the checker traverses it correctly. // We use primitive calls here to do a low-level check. - // TODO: add a high-level test in the firewood crate fn checker_traverse_correct_trie() { let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - let (_, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); + let test_trie = gen_test_trie(&mut nodestore); + // let (_, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); // verify that all of the space is accounted for - since there is no free area - let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + let mut visited = LinearAddressRangeSet::new(test_trie.high_watermark).unwrap(); nodestore - .visit_trie(root_addr, root_hash, Path::new(), &mut visited, true) + .visit_trie( + test_trie.root_address, + test_trie.root_hash, + Path::new(), + &mut visited, + true, + ) .unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); @@ -370,41 +378,52 @@ mod test { let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - let (mut nodes, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); + let mut test_trie = gen_test_trie(&mut nodestore); - // replace the branch hash in the root node with a wrong hash - let [_, (branch_node, branch_addr), (root_node, _)] = nodes.as_mut_slice() else { - panic!("test trie content changed, the test should be updated"); - }; - let wrong_hash = HashType::default(); - let branch_path = Path::from([0]) + branch_node.as_branch().unwrap().partial_path.clone(); - let Some(Child::AddressWithHash(_, hash)) = root_node.as_branch_mut().unwrap().children - [branch_path[0] as usize] - .replace(Child::AddressWithHash(*branch_addr, wrong_hash.clone())) - else { - panic!("test trie content changed, the test should be updated"); - }; - let branch_hash = hash; + // find the root node and replace the branch hash with an incorrect (default) hash + let (root_node, root_addr) = test_trie + .nodes + .iter_mut() + .find(|(node, _)| matches!(node, Node::Branch(b) if *b.partial_path.0 == [2])) + .unwrap(); + + let root_branch = root_node.as_branch_mut().unwrap(); + + // Get the branch address and original hash from the root's first child + let (branch_addr, computed_hash) = root_branch.children[0] + .as_ref() + .unwrap() + .persist_info() + .unwrap(); + let computed_hash = computed_hash.clone(); + root_branch.children[0] = Some(Child::AddressWithHash(branch_addr, HashType::default())); + + // Replace the branch hash in the root node with a wrong hash + if let Node::Branch(root_branch) = root_node { + root_branch.children[0] = + Some(Child::AddressWithHash(branch_addr, HashType::default())); + } test_write_new_node(&nodestore, root_node, root_addr.get()); - // verify that all of the space is accounted for - since there is no free area - let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + // run the checker and verify that it returns the HashMismatch error + let mut visited = LinearAddressRangeSet::new(test_trie.high_watermark).unwrap(); let err = nodestore - .visit_trie(root_addr, root_hash, Path::new(), &mut visited, true) + .visit_trie( + test_trie.root_address, + test_trie.root_hash, + Path::new(), + &mut visited, + true, + ) .unwrap_err(); - assert!(matches!( - err, - CheckerError::HashMismatch { - address, - path, - parent_stored_hash, - computed_hash - } - if address == *branch_addr - && path == branch_path - && parent_stored_hash == wrong_hash - && computed_hash == branch_hash - )); + + let expected_error = CheckerError::HashMismatch { + address: branch_addr, + path: Path::from([2, 0, 3]), + parent_stored_hash: HashType::default(), + computed_hash, + }; + assert_eq!(err, expected_error); } #[test] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 85b828fa5136..372fc7911e81 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -20,7 +20,6 @@ //! A [`NodeStore`] is backed by a [`ReadableStorage`] which is persisted storage. use std::ops::Range; -use thiserror::Error; mod checker; mod hashednode; @@ -126,8 +125,11 @@ pub enum FreeListParent { PrevFreeArea(LinearAddress), } +use derive_where::derive_where; + /// Errors returned by the checker -#[derive(Error, Debug)] +#[derive(thiserror::Error, Debug)] +#[derive_where(PartialEq)] #[non_exhaustive] pub enum CheckerError { /// The file size is not valid @@ -215,6 +217,7 @@ pub enum CheckerError { /// IO error #[error("IO error")] + #[derive_where(skip_inner)] IO(#[from] FileIoError), } From c915413e66119f53c02f2050ef874a98c48a08d5 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 23 Jul 2025 17:03:21 -0700 Subject: [PATCH 0852/1053] chore: minor cleanups and nits (#1133) I pulled these out of another PR that was getting too large. These are minor cleanups and nits that don't change functionality, a few common auto-derived traits that are used in a later PR and a few style nits from an earlier change of mine. --- Cargo.toml | 3 ++- firewood/src/proof.rs | 3 +-- firewood/src/stream.rs | 8 ++++---- fwdctl/Cargo.toml | 2 +- storage/Cargo.toml | 2 +- storage/src/hashednode.rs | 2 +- storage/src/node/path.rs | 2 +- storage/src/nodestore/mod.rs | 2 -- 8 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49edec0002ca..7daf60f11c0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ firewood-ffi = { path = "ffi", version = "0.0.9" } firewood-triehash = { path = "triehash", version = "0.0.9" } # common dependencies +clap = { version = "4.5.41", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" @@ -70,13 +71,13 @@ hex = "0.4.3" log = "0.4.27" metrics = "0.24.2" metrics-util = "0.20.0" +nonzero_ext = "0.3.0" rand_distr = "0.5.1" sha2 = "0.10.9" smallvec = "1.15.1" test-case = "3.3.1" thiserror = "2.0.12" tokio = "1.46.1" -clap = { version = "4.5.41", features = ["derive"] } # common dev dependencies criterion = "0.6.0" diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 0402d71c8c8c..379c0e8361fb 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -74,8 +74,7 @@ pub enum ProofError { EmptyRange, } -#[derive(Clone, Debug)] - +#[derive(Clone, Debug, PartialEq, Eq, Default)] /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index 10ed67261f56..ac5c886baed7 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1079,7 +1079,7 @@ mod tests { .unwrap(), ( expected_key.into_boxed_slice(), - expected_value.into_boxed_slice() + expected_value.into_boxed_slice(), ), "i: {i}, j: {j}", ); @@ -1100,7 +1100,7 @@ mod tests { .unwrap(), ( expected_key.into_boxed_slice(), - expected_value.into_boxed_slice() + expected_value.into_boxed_slice(), ), "i: {i}, j: {j}", ); @@ -1115,7 +1115,7 @@ mod tests { .unwrap(), ( vec![i + 1, 0].into_boxed_slice(), - vec![i + 1, 0].into_boxed_slice() + vec![i + 1, 0].into_boxed_slice(), ), "i: {i}", ); @@ -1150,7 +1150,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(&*next.0, &*next.1); - assert_eq!(&*next.1, kv); + assert_eq!(&*next.1, &**kv); } check_stream_is_done(stream).await; diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 77fd93a29268..da8ec3ea3278 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -30,11 +30,11 @@ firewood.workspace = true firewood-storage.workspace = true hex.workspace = true log.workspace = true +nonzero_ext.workspace = true tokio = { workspace = true, features = ["full"] } # Regular dependencies csv = "1.3.1" futures-util = "0.3.31" -nonzero_ext = "0.3.0" [features] ethhash = ["firewood/ethhash"] diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2635c58fec3d..d79bed138807 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -22,6 +22,7 @@ coarsetime.workspace = true fastrace.workspace = true hex.workspace = true metrics.workspace = true +nonzero_ext.workspace = true sha2.workspace = true smallvec = { workspace = true, features = ["write", "union"] } thiserror.workspace = true @@ -34,7 +35,6 @@ bytemuck_derive = "1.10.0" enum-as-inner = "0.6.1" integer-encoding = "4.0.2" lru = "0.16.0" -nonzero_ext = "0.3.0" num-traits = "0.2.19" semver = "1.0.26" triomphe = "0.1.14" diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 5a6eaa57b7ed..9ce50a7975f8 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -87,7 +87,7 @@ impl HasUpdate for SmallVec<[u8; 32]> { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] /// A `ValueDigest` is either a node's value or the hash of its value. pub enum ValueDigest { /// The node's value. diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 9d9f9a9a146f..157ab6f51361 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -25,7 +25,7 @@ static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /// Path is part or all of a node's path in the trie. /// Each element is a nibble. -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Default)] pub struct Path(pub SmallVec<[u8; 64]>); impl Debug for Path { diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 5d92689ec2c8..3643a32f07a4 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -37,8 +37,6 @@ //! - **`RootReader`** - Interface for accessing the root node //! - **`HashedNodeReader`** - Interface for immutable merkle trie access //! - **`Parentable`** - Trait for nodestores that can have children -//! - **`ReadInMemoryNode`** - Interface for accessing in-memory nodes -//! pub(crate) mod alloc; pub(crate) mod hash; From 7a4a77deb683a30b4c1abc60f44dc821561eaecf Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 24 Jul 2025 08:01:25 -0700 Subject: [PATCH 0853/1053] feat: make NodeStore more generic (#1134) This commit enhances the `NodeStore` structure to be more generic, allowing it to work with different storage backends. In a future change, I have tests that execute a write-commit-read loop using `MemStore`. However, this does not work without this change as `NodeStore` currently has many methods that are specific to the `FileStore` implementation. Passing in an `Arc` is not required as we're not cloning the arc. This makes it easier to use the `NodeStore` without needing to wrap it in an `Arc` every time. A `fork` method is also added to `Merkle` for easier cloning of the trie into a new mutable instance. --- firewood/src/manager.rs | 2 +- firewood/src/merkle.rs | 37 ++++--- firewood/src/stream.rs | 2 +- storage/src/nodestore/mod.rs | 24 ++--- storage/src/nodestore/persist.rs | 165 ++++++++++++++++++++++++++++--- 5 files changed, 185 insertions(+), 45 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index ecc7e22e8163..485d9e7e8d66 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -159,7 +159,7 @@ impl RevisionManager { return Err(RevisionManagerError::NotLatest); } - let mut committed = proposal.as_committed(current_revision); + let mut committed = proposal.as_committed(¤t_revision); // 2. Persist delete list for this committed revision to disk for recovery diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 509f4d042698..062e74be6a1b 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,19 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::missing_errors_doc, - reason = "Found 6 occurrences after enabling the lint." -)] -#![expect( - clippy::missing_panics_doc, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::too_many_lines, - reason = "Found 1 occurrences after enabling the lint." -)] - use crate::proof::{Proof, ProofError, ProofNode}; #[cfg(test)] use crate::range_proof::RangeProof; @@ -26,7 +13,7 @@ use crate::v2::api::{self, FrozenRangeProof}; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, - Path, ReadableStorage, SharedNode, TrieReader, ValueDigest, + Parentable, Path, ReadableStorage, SharedNode, TrieReader, ValueDigest, }; #[cfg(test)] use futures::{StreamExt, TryStreamExt}; @@ -178,6 +165,10 @@ impl Merkle { /// Returns a proof that the given key has a certain value, /// or that the key isn't in the trie. + /// + /// ## Errors + /// + /// Returns an error if the trie is empty or an error occurs while reading from storage. pub fn prove(&self, key: &[u8]) -> Result { let Some(root) = self.root() else { return Err(ProofError::Empty); @@ -216,6 +207,7 @@ impl Merkle { } /// Verify a proof that a key has a certain value, or that the key isn't in the trie. + #[expect(clippy::missing_errors_doc)] pub fn verify_range_proof>( &self, _proof: &Proof, @@ -442,6 +434,17 @@ impl Merkle { } } +impl Merkle> { + /// Forks the current Merkle trie into a new mutable proposal. + /// + /// ## Errors + /// + /// Returns an error if the nodestore cannot be created. See [`NodeStore::new`]. + pub fn fork(&self) -> Result>, FileIoError> { + NodeStore::new(&self.nodestore).map(Into::into) + } +} + impl TryFrom>> for Merkle, S>> { @@ -453,10 +456,15 @@ impl TryFrom>> } } +#[expect(clippy::missing_errors_doc)] impl Merkle> { /// Convert a merkle backed by an `MutableProposal` into an `ImmutableProposal` /// /// This function is only used in benchmarks and tests + /// + /// ## Panics + /// + /// Panics if the conversion fails. This should only be used in tests or benchmarks. #[must_use] pub fn hash(self) -> Merkle, S>> { self.try_into().expect("failed to convert") @@ -653,6 +661,7 @@ impl Merkle> { /// Removes the value associated with the given `key` from the subtrie rooted at `node`. /// Returns the new root of the subtrie and the value that was removed, if any. /// Each element of `key` is 1 nibble. + #[expect(clippy::too_many_lines)] fn remove_helper( &mut self, mut node: Node, diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index ac5c886baed7..f67bc203584c 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1186,7 +1186,7 @@ mod tests { let immutable_merkle: Merkle, _>> = merkle.try_into().unwrap(); println!("{}", immutable_merkle.dump().unwrap()); - merkle = Merkle::from(NodeStore::new(&Arc::new(immutable_merkle.into_inner())).unwrap()); + merkle = immutable_merkle.fork().unwrap(); let mut stream = merkle.key_value_iter(); diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 3643a32f07a4..dda3652e03d1 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -87,9 +87,7 @@ use crate::hashednode::hash_node; use crate::node::Node; use crate::node::persist::MaybePersistedNode; use crate::nodestore::alloc::AREA_SIZES; -use crate::{ - CacheReadStrategy, FileBacked, FileIoError, Path, ReadableStorage, SharedNode, TrieHash, -}; +use crate::{CacheReadStrategy, FileIoError, Path, ReadableStorage, SharedNode, TrieHash}; use super::linear::WritableStorage; @@ -187,7 +185,7 @@ impl Parentable for Arc { impl NodeStore, S> { /// When an immutable proposal commits, we need to reparent any proposal that /// has the committed proposal as it's parent - pub fn commit_reparent(&self, other: &Arc, S>>) { + pub fn commit_reparent(&self, other: &NodeStore, S>) { match *other.kind.parent.load() { NodeStoreParent::Proposed(ref parent) => { if Arc::ptr_eq(&self.kind, parent) { @@ -220,7 +218,7 @@ impl NodeStore { /// # Errors /// /// Returns a [`FileIoError`] if the parent root cannot be read. - pub fn new(parent: &Arc>) -> Result { + pub fn new(parent: &NodeStore) -> Result { let mut deleted = Vec::default(); let root = if let Some(ref root) = parent.kind.root() { deleted.push(root.clone()); @@ -483,14 +481,14 @@ impl NodeStore, S> { } } -impl NodeStore, FileBacked> { +impl NodeStore, S> { /// Return a Committed version of this proposal, which doesn't have any modified nodes. /// This function is used during commit. #[must_use] pub fn as_committed( &self, - current_revision: Arc>, - ) -> NodeStore { + current_revision: &NodeStore, + ) -> NodeStore { NodeStore { header: current_revision.header, kind: Committed { @@ -794,19 +792,17 @@ mod tests { fn test_reparent() { // create an empty base revision let memstore = MemStore::new(vec![]); - let base = NodeStore::new_empty_committed(memstore.into()) - .unwrap() - .into(); + let base = NodeStore::new_empty_committed(memstore.into()).unwrap(); // create an empty r1, check that it's parent is the empty committed version let r1 = NodeStore::new(&base).unwrap(); - let r1: Arc, _>> = Arc::new(r1.try_into().unwrap()); + let r1: NodeStore, _> = r1.try_into().unwrap(); let parent: DynGuard> = r1.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Committed(None))); // create an empty r2, check that it's parent is the proposed version r1 - let r2: NodeStore = NodeStore::new(&r1.clone()).unwrap(); - let r2: Arc, _>> = Arc::new(r2.try_into().unwrap()); + let r2: NodeStore = NodeStore::new(&r1).unwrap(); + let r2: NodeStore, _> = r2.try_into().unwrap(); let parent: DynGuard> = r2.kind.parent.load(); assert!(matches!(**parent, NodeStoreParent::Proposed(_))); diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index fe522c81cb49..80c4b55929d9 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -77,6 +77,20 @@ impl NodeStore { self.storage.write(0, &header_bytes)?; Ok(()) } + + /// Persist the freelist from this proposal to storage. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the free list cannot be written to storage. + #[fastrace::trace(short_name = true)] + pub fn flush_freelist(&self) -> Result<(), FileIoError> { + // Write the free lists to storage + let free_list_bytes = bytemuck::bytes_of(self.header.free_lists()); + let free_list_offset = NodeStoreHeader::free_lists_offset(); + self.storage.write(free_list_offset, free_list_bytes)?; + Ok(()) + } } /// Iterator that returns unpersisted nodes in depth first order. @@ -184,19 +198,38 @@ impl Iterator for UnPersistedNodeIterator<'_, N> { } } -impl NodeStore { - /// Persist the freelist from this proposal to storage. +impl NodeStore { + /// Persist all the nodes of a proposal to storage. /// /// # Errors /// - /// Returns a [`FileIoError`] if the free list cannot be written to storage. + /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] - pub fn flush_freelist(&self) -> Result<(), FileIoError> { - // Write the free lists to storage - let free_list_bytes = bytemuck::bytes_of(self.header.free_lists()); - let free_list_offset = NodeStoreHeader::free_lists_offset(); - self.storage.write(free_list_offset, free_list_bytes)?; - Ok(()) + pub fn flush_nodes(&mut self) -> Result { + #[cfg(feature = "io-uring")] + if let Some(this) = self.downcast_to_file_backed() { + return this.flush_nodes_io_uring(); + } + self.flush_nodes_generic() + } + + #[cfg(feature = "io-uring")] + #[inline] + fn downcast_to_file_backed(&mut self) -> Option<&mut NodeStore> { + /* + * FIXME(rust-lang/rfcs#1210, rust-lang/rust#31844): + * + * This is a slight hack that exists because rust trait specialization + * is not yet stable. If the `io-uring` feature is enabled, we attempt to + * downcast `self` into a `NodeStore`, and if successful, + * we call the specialized `flush_nodes_io_uring` method. + * + * During monomorphization, this will be completely optimized out as the + * type id comparison is done with constants that the compiler can resolve + * and use to detect dead branches. + */ + let this = self as &mut dyn std::any::Any; + this.downcast_mut::>() } /// Persist all the nodes of a proposal to storage. @@ -204,9 +237,7 @@ impl NodeStore { /// # Errors /// /// Returns a [`FileIoError`] if any node cannot be written to storage. - #[fastrace::trace(short_name = true)] - #[cfg(not(feature = "io-uring"))] - pub fn flush_nodes(&mut self) -> Result { + fn flush_nodes_generic(&self) -> Result { let flush_start = Instant::now(); // keep arcs to the allocated nodes to add them to cache @@ -245,7 +276,7 @@ impl NodeStore { } } -impl NodeStore { +impl NodeStore { /// Persist the entire nodestore to storage. /// /// This method performs a complete persistence operation by: @@ -274,7 +305,9 @@ impl NodeStore { Ok(()) } +} +impl NodeStore { /// Persist all the nodes of a proposal to storage. /// /// # Errors @@ -282,7 +315,7 @@ impl NodeStore { /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] - pub fn flush_nodes(&mut self) -> Result { + fn flush_nodes_io_uring(&mut self) -> Result { use std::pin::Pin; #[derive(Clone, Debug)] @@ -447,11 +480,23 @@ impl NodeStore { mod tests { use super::*; use crate::{ - Child, HashType, LinearAddress, NodeStore, Path, SharedNode, + Child, HashType, ImmutableProposal, LinearAddress, NodeStore, Path, SharedNode, linear::memory::MemStore, node::{BranchNode, LeafNode, Node}, nodestore::MutableProposal, }; + use std::sync::Arc; + + fn into_committed( + ns: NodeStore, MemStore>, + parent: &NodeStore, + ) -> NodeStore { + ns.flush_freelist().unwrap(); + ns.flush_header().unwrap(); + let mut ns = ns.as_committed(parent); + ns.flush_nodes().unwrap(); + ns + } /// Helper to create a test node store with a specific root fn create_test_store_with_root(root: Node) -> NodeStore { @@ -640,4 +685,94 @@ mod tests { assert!(leaf3_pos < inner_branch_pos); assert!(inner_branch_pos < root_pos); } + + #[test] + fn test_into_committed_with_generic_storage() { + // Create a base committed store with MemStore + let mem_store = MemStore::new(vec![]); + let base_committed = NodeStore::new_empty_committed(mem_store.into()).unwrap(); + + // Create a mutable proposal from the base + let mut mutable_store = NodeStore::new(&base_committed).unwrap(); + + // Add some nodes to the mutable store + let leaf1 = create_leaf(&[1, 2, 3], b"value1"); + let leaf2 = create_leaf(&[4, 5, 6], b"value2"); + let branch = create_branch( + &[0], + Some(b"branch_value"), + vec![(1, leaf1.clone()), (2, leaf2.clone())], + ); + + mutable_store.mut_root().replace(branch.clone()); + + // Convert to immutable proposal + let immutable_store: NodeStore, _> = + mutable_store.try_into().unwrap(); + + // Commit the immutable store + let committed_store = into_committed(immutable_store, &base_committed); + + // Verify the committed store has the expected values + let root = committed_store.kind.root.as_ref().unwrap(); + let root_node = root.as_shared_node(&committed_store).unwrap(); + assert_eq!(*root_node.partial_path(), Path::from(&[0])); + assert_eq!(root_node.value(), Some(&b"branch_value"[..])); + assert!(root_node.is_branch()); + let root_branch = root_node.as_branch().unwrap(); + assert_eq!( + root_branch.children.iter().filter(|c| c.is_some()).count(), + 2 + ); + + let child1 = root_branch.children[1].as_ref().unwrap(); + let child1_node = child1 + .as_maybe_persisted_node() + .as_shared_node(&committed_store) + .unwrap(); + assert_eq!(*child1_node.partial_path(), Path::from(&[1, 2, 3])); + assert_eq!(child1_node.value(), Some(&b"value1"[..])); + + let child2 = root_branch.children[2].as_ref().unwrap(); + let child2_node = child2 + .as_maybe_persisted_node() + .as_shared_node(&committed_store) + .unwrap(); + assert_eq!(*child2_node.partial_path(), Path::from(&[4, 5, 6])); + assert_eq!(child2_node.value(), Some(&b"value2"[..])); + } + + #[cfg(feature = "io-uring")] + #[test] + fn test_downcast_to_file_backed() { + use nonzero_ext::nonzero; + + use crate::CacheReadStrategy; + + { + let tf = tempfile::NamedTempFile::new().unwrap(); + let path = tf.path().to_owned(); + + let fb = Arc::new( + FileBacked::new( + path, + nonzero!(10usize), + nonzero!(10usize), + false, + CacheReadStrategy::WritesOnly, + ) + .unwrap(), + ); + + let mut ns = NodeStore::new_empty_committed(fb.clone()).unwrap(); + + assert!(ns.downcast_to_file_backed().is_some()); + } + + { + let ms = Arc::new(MemStore::new(vec![])); + let mut ns = NodeStore::new_empty_committed(ms.clone()).unwrap(); + assert!(ns.downcast_to_file_backed().is_none()); + } + } } From 3f9ea532710aca1b915e2a39b953316bf0c02cf4 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:33:38 -0500 Subject: [PATCH 0854/1053] feat(checker): add progress bar (#1105) --- firewood/src/db.rs | 8 +++- fwdctl/Cargo.toml | 1 + fwdctl/src/check.rs | 10 +++++ storage/Cargo.toml | 2 +- storage/src/checker/mod.rs | 68 +++++++++++++++++++++++++++----- storage/src/checker/range_set.rs | 20 +++++++++- storage/src/nodestore/alloc.rs | 19 +++------ 7 files changed, 102 insertions(+), 26 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 2f6b9e2a891c..db9d5029fb0f 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -817,7 +817,13 @@ mod test { // check the database for consistency, sometimes checking the hashes let hash_check = rng.borrow_mut().random(); - if let Err(e) = db.check(CheckOpt { hash_check }).await { + if let Err(e) = db + .check(CheckOpt { + hash_check, + progress_bar: None, + }) + .await + { db.dump(&mut std::io::stdout()).await.unwrap(); panic!("error: {e}"); } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index da8ec3ea3278..620f98572c84 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -35,6 +35,7 @@ tokio = { workspace = true, features = ["full"] } # Regular dependencies csv = "1.3.1" futures-util = "0.3.31" +indicatif = "0.18.0" [features] ethhash = ["firewood/ethhash"] diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 4981e878a059..d8379ad817a5 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use clap::Args; use firewood::v2::api; use firewood_storage::{CacheReadStrategy, CheckOpt, FileBacked, NodeStore}; +use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use nonzero_ext::nonzero; // TODO: (optionally) add a fix option @@ -46,9 +47,18 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { )?; let storage = Arc::new(fb); + let progress_bar = ProgressBar::no_length() + .with_style( + ProgressStyle::with_template("{wide_bar} {bytes}/{total_bytes} [{msg}]") + .expect("valid template") + .progress_chars("#>-"), + ) + .with_finish(ProgressFinish::WithMessage("Check Completed!".into())); + NodeStore::open(storage)? .check(CheckOpt { hash_check: opts.hash_check, + progress_bar: Some(progress_bar), }) .map_err(|e| api::Error::InternalError(Box::new(e))) } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index d79bed138807..ebec4cd025cf 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -35,9 +35,9 @@ bytemuck_derive = "1.10.0" enum-as-inner = "0.6.1" integer-encoding = "4.0.2" lru = "0.16.0" -num-traits = "0.2.19" semver = "1.0.26" triomphe = "0.1.14" +indicatif = "0.18.0" derive-where = "1.5.0" # Optional dependencies bytes = { version = "1.10.1", optional = true } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index e155a88f5d84..565bdc2a019a 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -15,11 +15,15 @@ use crate::{ use std::cmp::Ordering; use std::ops::Range; +use indicatif::ProgressBar; + /// Options for the checker #[derive(Debug)] pub struct CheckOpt { /// Whether to check the hash of the nodes pub hash_check: bool, + /// Optional progress bar to show the checker progress + pub progress_bar: Option, } /// [`NodeStore`] checker @@ -30,7 +34,7 @@ impl NodeStore { /// 1. Check the header /// 2. traverse the trie and check the nodes /// 3. check the free list - /// 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? + /// 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. /// # Panics @@ -47,6 +51,10 @@ impl NodeStore { let mut visited = LinearAddressRangeSet::new(db_size)?; // 2. traverse the trie and check the nodes + if let Some(progress_bar) = &opt.progress_bar { + progress_bar.set_length(db_size); + progress_bar.set_message("Traversing the trie..."); + } if let (Some(root), Some(root_hash)) = (self.root_as_maybe_persisted_node(), self.root_hash()) { @@ -61,6 +69,7 @@ impl NodeStore { root_hash.into_hash_type(), Path::new(), &mut visited, + opt.progress_bar.as_ref(), opt.hash_check, )?; } else { @@ -69,14 +78,20 @@ impl NodeStore { } // 3. check the free list - this can happen in parallel with the trie traversal - self.visit_freelist(&mut visited)?; + if let Some(progress_bar) = &opt.progress_bar { + progress_bar.set_message("Traversing free lists..."); + } + self.visit_freelist(&mut visited, opt.progress_bar.as_ref())?; - // 4. check missed areas - what are the spaces between trie nodes and free lists we have traversed? + // 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? + if let Some(progress_bar) = &opt.progress_bar { + progress_bar.set_message("Checking leaked areas..."); + } let leaked_ranges = visited.complement(); if !leaked_ranges.is_empty() { warn!("Found leaked ranges: {leaked_ranges}"); } - let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges); + let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges, opt.progress_bar.as_ref()); // TODO: add leaked areas to the free list Ok(()) @@ -89,6 +104,7 @@ impl NodeStore { subtree_root_hash: HashType, path_prefix: Path, visited: &mut LinearAddressRangeSet, + progress_bar: Option<&ProgressBar>, hash_check: bool, ) -> Result<(), CheckerError> { let (_, area_size) = self.area_index_and_size(subtree_root_address)?; @@ -114,6 +130,7 @@ impl NodeStore { hash.clone(), child_path_prefix, visited, + progress_bar, hash_check, )?; } @@ -134,11 +151,17 @@ impl NodeStore { } } + update_progress_bar(progress_bar, visited); + Ok(()) } /// Traverse all the free areas in the freelist - fn visit_freelist(&self, visited: &mut LinearAddressRangeSet) -> Result<(), CheckerError> { + fn visit_freelist( + &self, + visited: &mut LinearAddressRangeSet, + progress_bar: Option<&ProgressBar>, + ) -> Result<(), CheckerError> { let mut free_list_iter = self.free_list_iter(0); while let Some(free_area) = free_list_iter.next_with_metadata() { let FreeAreaWithMetadata { @@ -158,6 +181,7 @@ impl NodeStore { }); } visited.insert_area(addr, area_size)?; + update_progress_bar(progress_bar, visited); } Ok(()) } @@ -180,10 +204,11 @@ impl NodeStore { fn split_all_leaked_ranges( &self, leaked_ranges: impl IntoIterator>, + progress_bar: Option<&ProgressBar>, ) -> impl Iterator { leaked_ranges .into_iter() - .flat_map(|range| self.split_range_into_leaked_areas(range)) + .flat_map(move |range| self.split_range_into_leaked_areas(range, progress_bar)) } /// Split a range of addresses into leaked areas that can be stored in the free list. @@ -192,6 +217,7 @@ impl NodeStore { fn split_range_into_leaked_areas( &self, leaked_range: Range, + progress_bar: Option<&ProgressBar>, ) -> Vec<(LinearAddress, AreaIndex)> { let mut leaked = Vec::new(); let mut current_addr = leaked_range.start; @@ -213,6 +239,9 @@ impl NodeStore { Ordering::Equal => { // we have reached the end of the leaked area, done leaked.push((current_addr, area_index)); + if let Some(progress_bar) = progress_bar { + progress_bar.inc(area_size); + } return leaked; } Ordering::Greater => { @@ -225,6 +254,9 @@ impl NodeStore { Ordering::Less => { // continue to the next area leaked.push((current_addr, area_index)); + if let Some(progress_bar) = progress_bar { + progress_bar.inc(area_size); + } current_addr = next_addr; } } @@ -239,6 +271,9 @@ impl NodeStore { .expect("address overflow is impossible"); if next_addr <= leaked_range.end { leaked.push((current_addr, area_index as AreaIndex)); + if let Some(progress_bar) = progress_bar { + progress_bar.inc(*area_size); + } current_addr = next_addr; } else { break; @@ -252,6 +287,17 @@ impl NodeStore { } } +fn update_progress_bar(progress_bar: Option<&ProgressBar>, range_set: &LinearAddressRangeSet) { + if let Some(progress_bar) = progress_bar { + progress_bar.set_position( + range_set + .bytes_in_set() + .checked_add(crate::nodestore::NodeStoreHeader::SIZE) + .expect("overflow can only happen if max_addr >= U64_MAX + NODE_STORE_START_ADDR"), + ); + } +} + #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] @@ -361,6 +407,7 @@ mod test { test_trie.root_hash, Path::new(), &mut visited, + None, true, ) .unwrap(); @@ -413,6 +460,7 @@ mod test { test_trie.root_hash, Path::new(), &mut visited, + None, true, ) .unwrap_err(); @@ -460,7 +508,7 @@ mod test { // test that the we traversed all the free areas let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - nodestore.visit_freelist(&mut visited).unwrap(); + nodestore.visit_freelist(&mut visited, None).unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); } @@ -521,7 +569,7 @@ mod test { } // check the leaked areas - let leaked_areas: HashMap<_, _> = nodestore.split_all_leaked_ranges(leaked).collect(); + let leaked_areas: HashMap<_, _> = nodestore.split_all_leaked_ranges(leaked, None).collect(); // assert that all leaked areas end up on the free list assert_eq!(leaked_areas, expected_free_areas); @@ -563,7 +611,7 @@ mod test { .unwrap(); let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = nodestore - .split_range_into_leaked_areas(leaked_range) + .split_range_into_leaked_areas(leaked_range, None) .into_iter() .unzip(); @@ -610,7 +658,7 @@ mod test { nonzero!(NodeStoreHeader::SIZE).into()..LinearAddress::new(high_watermark).unwrap(); let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = nodestore - .split_range_into_leaked_areas(leaked_range) + .split_range_into_leaked_areas(leaked_range, None) .into_iter() .unzip(); diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index b48869a93e72..955b646ed357 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -217,6 +217,7 @@ impl IntoIterator for RangeSet { pub(super) struct LinearAddressRangeSet { range_set: RangeSet, max_addr: LinearAddress, + bytes_in_set: u64, } #[expect(clippy::result_large_err)] @@ -240,6 +241,7 @@ impl LinearAddressRangeSet { Ok(Self { range_set: RangeSet::new(), max_addr, + bytes_in_set: 0, }) } @@ -269,6 +271,10 @@ impl LinearAddressRangeSet { intersection, }); } + self.bytes_in_set = self + .bytes_in_set + .checked_add(size) + .expect("overflow can only happen if max_addr >= U64_MAX + NODE_STORE_START_ADDR"); Ok(()) } @@ -276,16 +282,28 @@ impl LinearAddressRangeSet { let complement_set = self .range_set .complement(&Self::NODE_STORE_START_ADDR, &self.max_addr); - + let bytes_in_complement = self + .max_addr + .distance_from(Self::NODE_STORE_START_ADDR) + .expect("checked in new()") + .checked_sub(self.bytes_in_set) + .expect( + "bytes_in_set is always less than or equal to max_addr - NODE_STORE_START_ADDR", + ); Self { range_set: complement_set, max_addr: self.max_addr, + bytes_in_set: bytes_in_complement, } } pub(super) fn is_empty(&self) -> bool { self.range_set.is_empty() } + + pub(super) const fn bytes_in_set(&self) -> u64 { + self.bytes_in_set + } } impl IntoIterator for LinearAddressRangeSet { diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 1fd623354c17..cfdf15f2e8c9 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -230,20 +230,13 @@ impl LinearAddress { Some(sum) => Some(LinearAddress(sum)), } } -} - -impl std::ops::Add for LinearAddress { - type Output = Self; - fn add(self, other: Self) -> Self { - self.checked_add(other.get()) - .map(LinearAddress) - .expect("non zero result") - } -} -impl num_traits::CheckedAdd for LinearAddress { - fn checked_add(&self, other: &Self) -> Option { - self.0.checked_add(other.get()).map(LinearAddress) + /// Returns the number of bytes between `other` and `self` if `other` is less than or equal to `self`. + /// Otherwise, returns `None`. + #[inline] + #[must_use] + pub const fn distance_from(self, other: Self) -> Option { + self.0.get().checked_sub(other.0.get()) } } From 08383cf25325cdbe727213d2848629bdf0adcf2b Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 24 Jul 2025 13:26:56 -0500 Subject: [PATCH 0855/1053] feat(checker): checker errors include reference to parent (#1085) This is the first step towards fixing issues reported by the checker. We will include the parent reference in the error so that we can update the parent to remove the area with issue. --- storage/src/checker/mod.rs | 101 +++++++++++++++++++------------ storage/src/checker/range_set.rs | 49 ++++++++++----- storage/src/lib.rs | 74 +++++++++++++++++----- storage/src/node/path.rs | 26 +++++++- storage/src/nodestore/alloc.rs | 2 +- 5 files changed, 182 insertions(+), 70 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 565bdc2a019a..247ccd57be86 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. mod range_set; -use range_set::LinearAddressRangeSet; +pub(crate) use range_set::LinearAddressRangeSet; use crate::logger::warn; use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata, size_from_area_index}; @@ -26,6 +26,13 @@ pub struct CheckOpt { pub progress_bar: Option, } +struct SubTrieMetadata { + root_address: LinearAddress, + root_hash: HashType, + parent: TrieNodeParent, + path_prefix: Path, +} + /// [`NodeStore`] checker // TODO: S needs to be writeable if we ask checker to fix the issues #[expect(clippy::result_large_err)] @@ -37,8 +44,6 @@ impl NodeStore { /// 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. - /// # Panics - /// Panics if the header has too many free lists, which can never happen since freelists have a fixed size. // TODO: report all errors, not just the first one pub fn check(&self, opt: CheckOpt) -> Result<(), CheckerError> { if cfg!(feature = "ethhash") { @@ -67,7 +72,6 @@ impl NodeStore { self.visit_trie( root_address, root_hash.into_hash_type(), - Path::new(), &mut visited, opt.progress_bar.as_ref(), opt.hash_check, @@ -97,55 +101,79 @@ impl NodeStore { Ok(()) } - /// Recursively traverse the trie from the given root node. fn visit_trie( &self, - subtree_root_address: LinearAddress, - subtree_root_hash: HashType, - path_prefix: Path, + root_address: LinearAddress, + root_hash: HashType, visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, hash_check: bool, ) -> Result<(), CheckerError> { - let (_, area_size) = self.area_index_and_size(subtree_root_address)?; - let node = self.read_node(subtree_root_address)?; - visited.insert_area(subtree_root_address, area_size)?; + let subtrie = SubTrieMetadata { + root_address, + root_hash, + parent: TrieNodeParent::Root, + path_prefix: Path::new(), + }; + self.visit_trie_helper(subtrie, visited, progress_bar, hash_check) + } - // iterate over the children + /// Recursively traverse the trie from the given root node. + fn visit_trie_helper( + &self, + subtrie: SubTrieMetadata, + visited: &mut LinearAddressRangeSet, + progress_bar: Option<&ProgressBar>, + hash_check: bool, + ) -> Result<(), CheckerError> { + let SubTrieMetadata { + root_address: subtrie_root_address, + root_hash: subtrie_root_hash, + parent, + path_prefix, + } = subtrie; + + // check that address is aligned + self.check_area_aligned(subtrie_root_address, StoredAreaParent::TrieNode(parent))?; + + // check that the area is within bounds and does not intersect with other areas + let (_, area_size) = self.area_index_and_size(subtrie_root_address)?; + visited.insert_area( + subtrie_root_address, + area_size, + StoredAreaParent::TrieNode(parent), + )?; + + // read the node and iterate over the children if branch node + let node = self.read_node(subtrie_root_address)?; if let Node::Branch(branch) = node.as_ref() { // this is an internal node, traverse the children for (nibble, (address, hash)) in branch.children_iter() { - self.check_area_aligned( - address, - StoredAreaParent::TrieNode(TrieNodeParent::Parent( - subtree_root_address, - nibble, - )), - )?; + let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); let mut child_path_prefix = path_prefix.clone(); child_path_prefix.0.extend_from_slice(node.partial_path()); child_path_prefix.0.push(nibble as u8); - self.visit_trie( - address, - hash.clone(), - child_path_prefix, - visited, - progress_bar, - hash_check, - )?; + let child_subtrie = SubTrieMetadata { + root_address: address, + root_hash: hash.clone(), + parent, + path_prefix: child_path_prefix, + }; + self.visit_trie_helper(child_subtrie, visited, progress_bar, hash_check)?; } } // hash check - at this point all children hashes have been verified if hash_check { let hash = hash_node(&node, &path_prefix); - if hash != subtree_root_hash { + if hash != subtrie_root_hash { let mut path = path_prefix.clone(); path.0.extend_from_slice(node.partial_path()); return Err(CheckerError::HashMismatch { path, - address: subtree_root_address, - parent_stored_hash: subtree_root_hash, + address: subtrie_root_address, + parent, + parent_stored_hash: subtrie_root_hash, computed_hash: hash, }); } @@ -178,9 +206,10 @@ impl NodeStore { size: area_size, actual_free_list: free_list_id, expected_free_list: area_index, + parent, }); } - visited.insert_area(addr, area_size)?; + visited.insert_area(addr, area_size, StoredAreaParent::FreeList(parent))?; update_progress_bar(progress_bar, visited); } Ok(()) @@ -189,13 +218,10 @@ impl NodeStore { const fn check_area_aligned( &self, address: LinearAddress, - parent_ptr: StoredAreaParent, + parent: StoredAreaParent, ) -> Result<(), CheckerError> { if !address.is_aligned() { - return Err(CheckerError::AreaMisaligned { - address, - parent_ptr, - }); + return Err(CheckerError::AreaMisaligned { address, parent }); } Ok(()) } @@ -405,7 +431,6 @@ mod test { .visit_trie( test_trie.root_address, test_trie.root_hash, - Path::new(), &mut visited, None, true, @@ -458,7 +483,6 @@ mod test { .visit_trie( test_trie.root_address, test_trie.root_hash, - Path::new(), &mut visited, None, true, @@ -468,6 +492,7 @@ mod test { let expected_error = CheckerError::HashMismatch { address: branch_addr, path: Path::from([2, 0, 3]), + parent: TrieNodeParent::Parent(*root_addr, 0), parent_stored_hash: HashType::default(), computed_hash, }; diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index 955b646ed357..2278276fc216 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -9,11 +9,11 @@ use std::ops::Range; use crate::iter::write_limited_with_sep; use crate::nodestore::NodeStoreHeader; -use crate::{CheckerError, LinearAddress}; +use crate::{CheckerError, LinearAddress, StoredAreaParent}; const MAX_AREAS_TO_DISPLAY: usize = 10; -#[derive(Debug)] +#[derive(Debug, Default)] // BTreeMap: range end --> range start // To check if a value is in the range set, we will find the range with the smallest end that is greater than or equal to the given value pub struct RangeSet(BTreeMap); @@ -214,7 +214,9 @@ impl IntoIterator for RangeSet { } } -pub(super) struct LinearAddressRangeSet { +/// A set of disjoint ranges of linear addresses in ascending order. +#[derive(Debug)] +pub struct LinearAddressRangeSet { range_set: RangeSet, max_addr: LinearAddress, bytes_in_set: u64, @@ -249,18 +251,21 @@ impl LinearAddressRangeSet { &mut self, addr: LinearAddress, size: u64, + parent: StoredAreaParent, ) -> Result<(), CheckerError> { let start = addr; let end = start.advance(size).ok_or(CheckerError::AreaOutOfBounds { start, size, bounds: Self::NODE_STORE_START_ADDR..self.max_addr, + parent, })?; // This can only happen due to overflow if addr < Self::NODE_STORE_START_ADDR || end > self.max_addr { return Err(CheckerError::AreaOutOfBounds { start: addr, size, bounds: Self::NODE_STORE_START_ADDR..self.max_addr, + parent, }); } @@ -269,6 +274,7 @@ impl LinearAddressRangeSet { start: addr, size, intersection, + parent, }); } self.bytes_in_set = self @@ -564,9 +570,13 @@ mod test_range_set { #[expect(clippy::unwrap_used)] mod test_linear_address_range_set { + use crate::{FreeListParent, TrieNodeParent}; + use super::*; use test_case::test_case; + const TEST_PARENT: StoredAreaParent = StoredAreaParent::TrieNode(TrieNodeParent::Root); + #[test] fn test_empty() { let visited = LinearAddressRangeSet::new(0x1000).unwrap(); @@ -587,7 +597,7 @@ mod test_linear_address_range_set { let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); visited - .insert_area(start_addr, size) + .insert_area(start_addr, size, TEST_PARENT) .expect("the given area should be within bounds"); let visited_ranges = visited @@ -610,11 +620,11 @@ mod test_linear_address_range_set { let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); visited - .insert_area(start1_addr, size1) + .insert_area(start1_addr, size1, TEST_PARENT) .expect("the given area should be within bounds"); visited - .insert_area(start2_addr, size2) + .insert_area(start2_addr, size2, TEST_PARENT) .expect("the given area should be within bounds"); let visited_ranges = visited @@ -635,31 +645,34 @@ mod test_linear_address_range_set { let end1_addr = LinearAddress::new(start1 + size1).unwrap(); let start2_addr = LinearAddress::new(start2).unwrap(); + let parent1 = StoredAreaParent::TrieNode(TrieNodeParent::Parent(start1_addr, 5)); + let parent2 = StoredAreaParent::FreeList(FreeListParent::FreeListHead(3)); + let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); visited - .insert_area(start1_addr, size1) + .insert_area(start1_addr, size1, parent1) .expect("the given area should be within bounds"); let error = visited - .insert_area(start2_addr, size2) + .insert_area(start2_addr, size2, parent2) .expect_err("the given area should intersect with the first area"); assert!( - matches!(error, CheckerError::AreaIntersects { start, size, intersection } if start == start2_addr && size == size2 && intersection == vec![start2_addr..end1_addr]) + matches!(error, CheckerError::AreaIntersects { start, size, intersection, parent } if start == start2_addr && size == size2 && intersection == vec![start2_addr..end1_addr] && parent == parent2) ); // try inserting in opposite order let mut visited2 = LinearAddressRangeSet::new(0x1000).unwrap(); visited2 - .insert_area(start2_addr, size2) + .insert_area(start2_addr, size2, parent2) .expect("the given area should be within bounds"); let error = visited2 - .insert_area(start1_addr, size1) + .insert_area(start1_addr, size1, parent1) .expect_err("the given area should intersect with the first area"); assert!( - matches!(error, CheckerError::AreaIntersects { start, size, intersection } if start == start1_addr && size == size1 && intersection == vec![start2_addr..end1_addr]) + matches!(error, CheckerError::AreaIntersects { start, size, intersection, parent } if start == start1_addr && size == size1 && intersection == vec![start2_addr..end1_addr] && parent == parent1) ); } @@ -679,8 +692,12 @@ mod test_linear_address_range_set { let db_end = LinearAddress::new(db_size).unwrap(); let mut visited = LinearAddressRangeSet::new(db_size).unwrap(); - visited.insert_area(start1_addr, size1).unwrap(); - visited.insert_area(start2_addr, size2).unwrap(); + visited + .insert_area(start1_addr, size1, TEST_PARENT) + .unwrap(); + visited + .insert_area(start2_addr, size2, TEST_PARENT) + .unwrap(); let complement = visited.complement().into_iter().collect::>(); assert_eq!( @@ -701,7 +718,7 @@ mod test_linear_address_range_set { let mut visited = LinearAddressRangeSet::new(db_size).unwrap(); visited - .insert_area(LinearAddress::new(start).unwrap(), size) + .insert_area(LinearAddress::new(start).unwrap(), size, TEST_PARENT) .unwrap(); let complement = visited.complement().into_iter().collect::>(); assert_eq!(complement, vec![]); @@ -763,7 +780,7 @@ mod test_linear_address_range_set { #[allow(clippy::arithmetic_side_effects)] let offset = i as u64 * 0x20 + 0x1000; range_set - .insert_area(LinearAddress::new(offset).unwrap(), 0x10) + .insert_area(LinearAddress::new(offset).unwrap(), 0x10, TEST_PARENT) .unwrap(); } assert_eq!(format!("{range_set}"), expected); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 372fc7911e81..26b60b09fb86 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -19,6 +19,7 @@ //! //! A [`NodeStore`] is backed by a [`ReadableStorage`] which is persisted storage. +use std::fmt::{Display, Formatter, LowerHex, Result}; use std::ops::Range; mod checker; @@ -74,8 +75,8 @@ pub enum CacheReadStrategy { All, } -impl std::fmt::Display for CacheReadStrategy { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Display for CacheReadStrategy { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{self:?}") } } @@ -99,7 +100,7 @@ pub fn empty_trie_hash() -> TrieHash { } /// This enum encapsulates what points to the stored area. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum StoredAreaParent { /// The stored area is a trie node TrieNode(TrieNodeParent), @@ -108,7 +109,7 @@ pub enum StoredAreaParent { } /// This enum encapsulates what points to the stored area allocated for a trie node. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum TrieNodeParent { /// The stored area is the root of the trie, so the header points to it Root, @@ -117,7 +118,7 @@ pub enum TrieNodeParent { } /// This enum encapsulates what points to the stored area allocated for a free list. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum FreeListParent { /// The stored area is the head of the free list, so the header points to it FreeListHead(AreaIndex), @@ -125,11 +126,45 @@ pub enum FreeListParent { PrevFreeArea(LinearAddress), } +impl LowerHex for StoredAreaParent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + StoredAreaParent::TrieNode(trie_parent) => LowerHex::fmt(trie_parent, f), + StoredAreaParent::FreeList(free_list_parent) => LowerHex::fmt(free_list_parent, f), + } + } +} + +impl LowerHex for TrieNodeParent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + TrieNodeParent::Root => f.write_str("Root"), + TrieNodeParent::Parent(addr, index) => { + f.write_str("TrieNode@")?; + LowerHex::fmt(addr, f)?; + f.write_fmt(format_args!("[{index}]")) + } + } + } +} + +impl LowerHex for FreeListParent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + FreeListParent::FreeListHead(index) => f.write_fmt(format_args!("FreeLists[{index}]")), + FreeListParent::PrevFreeArea(addr) => { + f.write_str("FreeArea@")?; + LowerHex::fmt(addr, f) + } + } + } +} + use derive_where::derive_where; /// Errors returned by the checker #[derive(thiserror::Error, Debug)] -#[derive_where(PartialEq)] +#[derive_where(PartialEq, Eq)] #[non_exhaustive] pub enum CheckerError { /// The file size is not valid @@ -150,6 +185,8 @@ pub enum CheckerError { path: Path, /// The address of the node address: LinearAddress, + /// The parent of the node + parent: TrieNodeParent, /// The hash value stored in the parent node parent_stored_hash: HashType, /// The hash value computed for the node @@ -157,7 +194,9 @@ pub enum CheckerError { }, /// The address is out of bounds - #[error("stored area at {start} with size {size} is out of bounds ({bounds:?})")] + #[error( + "stored area at {start:#x} with size {size} (parent: {parent:#x}) is out of bounds ({bounds:#x?})" + )] AreaOutOfBounds { /// Start of the `StoredArea` start: LinearAddress, @@ -165,11 +204,13 @@ pub enum CheckerError { size: u64, /// Valid range of addresses bounds: Range, + /// The parent of the `StoredArea` + parent: StoredAreaParent, }, /// Stored areas intersect #[error( - "stored area at {start} with size {size} intersects with other stored areas: {intersection:?}" + "stored area at {start:#x} with size {size} (parent: {parent:#x}) intersects with other stored areas: {intersection:#x?}" )] AreaIntersects { /// Start of the `StoredArea` @@ -178,11 +219,13 @@ pub enum CheckerError { size: u64, /// The intersection intersection: Vec>, + /// The parent of the `StoredArea` + parent: StoredAreaParent, }, /// Freelist area size does not match #[error( - "Free area {address} of size {size} is found in free list {actual_free_list} but it should be in freelist {expected_free_list}" + "Free area {address:#x} of size {size} (parent: {parent:#x}) is found in free list {actual_free_list} but it should be in freelist {expected_free_list}" )] FreelistAreaSizeMismatch { /// Address of the free area @@ -193,23 +236,26 @@ pub enum CheckerError { actual_free_list: AreaIndex, /// Expected size of the area expected_free_list: AreaIndex, + /// The parent of the free area + parent: FreeListParent, }, /// The start address of a stored area is not a multiple of 16 #[error( - "The start address of a stored area is not a multiple of {}: {address} (parent: {parent_ptr:?})", + "The start address of a stored area (parent: {parent:#x}) is not a multiple of {}: {address:#x}", nodestore::alloc::LinearAddress::MIN_AREA_SIZE )] AreaMisaligned { /// The start address of the stored area address: LinearAddress, - /// The start address of the parent that points to the stored area - parent_ptr: StoredAreaParent, + /// The parent of the `StoredArea` + parent: StoredAreaParent, }, /// Found leaked areas - #[error("Found leaked areas: {0:?}")] - AreaLeaks(Vec>), + #[error("Found leaked areas: {0}")] + #[derive_where(skip_inner)] + AreaLeaks(checker::LinearAddressRangeSet), /// The root is not persisted #[error("The checker can only check persisted nodestores")] diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 157ab6f51361..9902bb4231a7 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -17,7 +17,7 @@ // TODO: remove bitflags, we only use one bit use bitflags::bitflags; use smallvec::SmallVec; -use std::fmt::{self, Debug}; +use std::fmt::{self, Debug, LowerHex}; use std::iter::{FusedIterator, once}; use std::ops::Add; @@ -41,6 +41,23 @@ impl Debug for Path { } } +impl LowerHex for Path { + // TODO: handle fill / alignment / etc + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + if self.0.is_empty() { + write!(f, "[]") + } else { + if f.alternate() { + write!(f, "0x")?; + } + for nib in &self.0 { + write!(f, "{:x}", *nib)?; + } + Ok(()) + } + } +} + impl std::ops::Deref for Path { type Target = [u8]; fn deref(&self) -> &[u8] { @@ -345,4 +362,11 @@ mod test { let to_encoded = from_encoded.iter_encoded().collect::>(); assert_eq!(encode.as_ref(), to_encoded.as_ref()); } + + #[test_case(Path::new(), "[]", "[]")] + #[test_case(Path::from([0x12, 0x34, 0x56, 0x78]), "12345678", "0x12345678")] + fn test_fmt_lower_hex(path: Path, expected: &str, expected_with_prefix: &str) { + assert_eq!(format!("{path:x}"), expected); + assert_eq!(format!("{path:#x}"), expected_with_prefix); + } } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index cfdf15f2e8c9..36ff1c3d8d20 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -633,7 +633,7 @@ impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { fn next_with_parent( &mut self, ) -> Option> { - let parent = self.parent.clone(); + let parent = self.parent; let next_addr = self.next()?; Some(next_addr.map(|free_area| (free_area, parent))) } From 0993e33604025386dc564e3f5d67f46d304ca446 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:03:15 -0500 Subject: [PATCH 0856/1053] fix(checker): fix checker with ethhash (#1130) With ethhash there is a special case checker did not handle: when an account has only one child. This diff aims to fix that bug and (hopefully) refactor `hash_helper` for ethhash for better understanding. --------- Co-authored-by: Ron Kuris --- firewood/src/db.rs | 19 ++++++++------- fwdctl/tests/cli.rs | 8 ------- storage/src/checker/mod.rs | 33 +++++++++++++------------ storage/src/nodestore/hash.rs | 45 +++++++++++++++++++++++++++-------- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index db9d5029fb0f..7fcb622d8998 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -485,7 +485,6 @@ mod test { use std::path::PathBuf; use firewood_storage::CheckOpt; - use firewood_storage::logger::trace; use rand::rng; use crate::db::Db; @@ -779,10 +778,6 @@ mod test { } #[tokio::test] - #[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1108" - )] async fn fuzz_checker() { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -807,9 +802,17 @@ mod test { // create a batch of 10 random key-value pairs let batch = (0..10).fold(vec![], |mut batch, _| { let key: [u8; 32] = rng.borrow_mut().random(); - let value: [u8; 32] = rng.borrow_mut().random(); - batch.push(BatchOp::Put { key, value }); - trace!("batch: {batch:?}"); + let value: [u8; 8] = rng.borrow_mut().random(); + batch.push(BatchOp::Put { + key: key.to_vec(), + value, + }); + if rng.borrow_mut().random_range(0..5) == 0 { + let addon: [u8; 32] = rng.borrow_mut().random(); + let key = [key, addon].concat(); + let value: [u8; 8] = rng.borrow_mut().random(); + batch.push(BatchOp::Put { key, value }); + } batch }); let proposal = db.propose(batch).await.unwrap(); diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index eb66b1e58135..e4b45b3629cd 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -521,10 +521,6 @@ fn fwdctl_dump_with_hex() -> Result<()> { #[test] #[serial] -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1108" -)] fn fwdctl_check_empty_db() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") @@ -544,10 +540,6 @@ fn fwdctl_check_empty_db() -> Result<()> { #[test] #[serial] -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1108" -)] fn fwdctl_check_db_with_data() -> Result<()> { use rand::rngs::StdRng; use rand::{Rng, SeedableRng, rng}; diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 247ccd57be86..7704928dbaab 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -9,9 +9,11 @@ use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata, size_ use crate::{ CheckerError, Committed, HashType, HashedNodeReader, IntoHashType, LinearAddress, Node, NodeReader, NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, - hash_node, }; +#[cfg(not(feature = "ethhash"))] +use crate::hashednode::hash_node; + use std::cmp::Ordering; use std::ops::Range; @@ -31,6 +33,8 @@ struct SubTrieMetadata { root_hash: HashType, parent: TrieNodeParent, path_prefix: Path, + #[cfg(feature = "ethhash")] + has_peers: bool, } /// [`NodeStore`] checker @@ -46,10 +50,6 @@ impl NodeStore { /// Returns a [`CheckerError`] if the database is inconsistent. // TODO: report all errors, not just the first one pub fn check(&self, opt: CheckOpt) -> Result<(), CheckerError> { - if cfg!(feature = "ethhash") { - unimplemented!("ethhash is not supported yet"); - } - // 1. Check the header let db_size = self.size(); @@ -109,13 +109,15 @@ impl NodeStore { progress_bar: Option<&ProgressBar>, hash_check: bool, ) -> Result<(), CheckerError> { - let subtrie = SubTrieMetadata { + let trie = SubTrieMetadata { root_address, root_hash, parent: TrieNodeParent::Root, path_prefix: Path::new(), + #[cfg(feature = "ethhash")] + has_peers: false, }; - self.visit_trie_helper(subtrie, visited, progress_bar, hash_check) + self.visit_trie_helper(trie, visited, progress_bar, hash_check) } /// Recursively traverse the trie from the given root node. @@ -131,6 +133,8 @@ impl NodeStore { root_hash: subtrie_root_hash, parent, path_prefix, + #[cfg(feature = "ethhash")] + has_peers, } = subtrie; // check that address is aligned @@ -148,6 +152,8 @@ impl NodeStore { let node = self.read_node(subtrie_root_address)?; if let Node::Branch(branch) = node.as_ref() { // this is an internal node, traverse the children + #[cfg(feature = "ethhash")] + let num_children = branch.children_iter().count(); for (nibble, (address, hash)) in branch.children_iter() { let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); let mut child_path_prefix = path_prefix.clone(); @@ -158,6 +164,8 @@ impl NodeStore { root_hash: hash.clone(), parent, path_prefix: child_path_prefix, + #[cfg(feature = "ethhash")] + has_peers: num_children != 1, }; self.visit_trie_helper(child_subtrie, visited, progress_bar, hash_check)?; } @@ -165,6 +173,9 @@ impl NodeStore { // hash check - at this point all children hashes have been verified if hash_check { + #[cfg(feature = "ethhash")] + let hash = Self::compute_node_ethhash(&node, &path_prefix, has_peers); + #[cfg(not(feature = "ethhash"))] let hash = hash_node(&node, &path_prefix); if hash != subtrie_root_hash { let mut path = path_prefix.clone(); @@ -412,10 +423,6 @@ mod test { use std::collections::HashMap; #[test] - #[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1108" - )] // This test creates a simple trie and checks that the checker traverses it correctly. // We use primitive calls here to do a low-level check. fn checker_traverse_correct_trie() { @@ -441,10 +448,6 @@ mod test { } #[test] - #[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1108" - )] // This test permutes the simple trie with a wrong hash and checks that the checker detects it. fn checker_traverse_trie_with_wrong_hash() { let memstore = MemStore::new(vec![]); diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index b321b7a9bbf2..21dd9fbbd055 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -58,9 +58,10 @@ where acc.hashed.push((idx, (maybe_persisted_node, h))); } else { // If not persisted, we need to get the node to hash it - if let Ok(node) = maybe_persisted.as_shared_node(&self) { - acc.unhashed.push((idx, node.deref().clone())); - } + let node = maybe_persisted + .as_shared_node(&self) + .expect("will never fail for unpersisted nodes"); + acc.unhashed.push((idx, node.deref().clone())); } } } @@ -70,6 +71,7 @@ where } /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. + /// The function appends to `path_prefix` and then truncate it back to the original length - we only reuse the memory space to avoid allocations /// Returns the hashed node and its hash. pub(super) fn hash_helper( #[cfg(feature = "ethhash")] &self, @@ -93,11 +95,10 @@ where trace!("hashed {hashed:?} unhashed {unhashed:?}"); if hashed.len() == 1 { // we were left with one hashed node that must be rehashed - let invalidated_node = hashed.first_mut().expect("hashed is not empty"); + let (invalidated_node_idx, (invalidated_node, invalidated_hash)) = + hashed.first_mut().expect("hashed is not empty"); // Extract the address from the MaybePersistedNode - let addr = invalidated_node - .1 - .0 + let addr: crate::LinearAddress = invalidated_node .as_linear_address() .expect("hashed node should be persisted"); let mut hashable_node = self.read_node(addr)?.deref().clone(); @@ -105,15 +106,15 @@ where path_prefix.0.extend(b.partial_path.0.iter().copied()); if unhashed.is_empty() { hashable_node.update_partial_path(Path::from_nibbles_iterator( - std::iter::once(invalidated_node.0 as u8) + std::iter::once(*invalidated_node_idx as u8) .chain(hashable_node.partial_path().0.iter().copied()), )); } else { - path_prefix.0.push(invalidated_node.0 as u8); + path_prefix.0.push(*invalidated_node_idx as u8); } let hash = hash_node(&hashable_node, path_prefix); path_prefix.0.truncate(original_length); - *invalidated_node.1.1 = hash; + **invalidated_hash = hash; } // handle the single-child case for an account special below if hashed.is_empty() && unhashed.len() == 1 { @@ -192,4 +193,28 @@ where Ok((SharedNode::new(node).into(), hash)) } + + #[cfg(feature = "ethhash")] + pub(crate) fn compute_node_ethhash( + node: &Node, + path_prefix: &Path, + have_peers: bool, + ) -> HashType { + if path_prefix.0.len() == 65 && !have_peers { + // This is the special case when this node is the only child of an account + // - 64 nibbles for account + 1 nibble for its position in account branch node + let mut fake_root = node.clone(); + fake_root.update_partial_path(Path::from_nibbles_iterator( + path_prefix + .0 + .last() + .into_iter() + .chain(fake_root.partial_path().0.iter()) + .copied(), + )); + hash_node(&fake_root, path_prefix) + } else { + hash_node(node, path_prefix) + } + } } From 9e7fd0a9821ed2c4fb3ae6fd60553f07c292216b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 13:42:22 +0000 Subject: [PATCH 0857/1053] build(deps): update criterion requirement from 0.6.0 to 0.7.0 (#1140) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7daf60f11c0f..3e2389b14d2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ thiserror = "2.0.12" tokio = "1.46.1" # common dev dependencies -criterion = "0.6.0" +criterion = "0.7.0" ethereum-types = "0.15.1" hex-literal = "1.0.0" pprof = "0.15.0" From 62175bfda2979d2604a0e12d1538eb846ed64b20 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 25 Jul 2025 08:45:44 -0700 Subject: [PATCH 0858/1053] chore: Add a golang install script (#1141) When I needed to test some ffi stuff, it was really hard to get set up with golang. This script works on all the AWS instances I cared about and also on my local machine to install and/or upgrade golang. Mostly AI generated with a few tweaks. --- benchmark/setup-scripts/install-golang.sh | 81 +++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 benchmark/setup-scripts/install-golang.sh diff --git a/benchmark/setup-scripts/install-golang.sh b/benchmark/setup-scripts/install-golang.sh new file mode 100644 index 000000000000..e00575d07546 --- /dev/null +++ b/benchmark/setup-scripts/install-golang.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +INSTALL_DIR="/usr/local/go" + +# Check write permission +if [ -d "$INSTALL_DIR" ]; then + if [ ! -w "$INSTALL_DIR" ]; then + echo "Error: $INSTALL_DIR exists but is not writable." >&2 + exit 1 + fi +else + if [ ! -w "$(dirname "$INSTALL_DIR")" ]; then + echo "Error: Cannot create $INSTALL_DIR. $(dirname "$INSTALL_DIR") is not writable." >&2 + exit 1 + fi +fi + +# Detect latest Go version +LATEST_VERSION=$(curl -s https://go.dev/dl/?mode=json | \ + grep -oE '"version": ?"go[0-9]+\.[0-9]+(\.[0-9]+)?"' | \ + head -n1 | cut -d\" -f4) + +if [ -z "$LATEST_VERSION" ]; then + echo "Error: Could not detect latest Go version." >&2 + exit 1 +fi + +# Detect platform +UNAME_OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +UNAME_ARCH="$(uname -m)" + +# Map to Go arch +case "$UNAME_ARCH" in + x86_64) ARCH="amd64" ;; + aarch64 | arm64) ARCH="arm64" ;; + armv6l | armv7l) ARCH="arm" ;; + *) echo "Unsupported architecture: $UNAME_ARCH" >&2; exit 1 ;; +esac + +# Map to Go OS +case "$UNAME_OS" in + linux | darwin) OS="$UNAME_OS" ;; + *) echo "Unsupported OS: $UNAME_OS" >&2; exit 1 ;; +esac + +# Build tarball name and URL +TARBALL="${LATEST_VERSION}.${OS}-${ARCH}.tar.gz" +URL="https://go.dev/dl/${TARBALL}" + +# Validate URL +echo "Checking URL: $URL" +if ! curl --head --fail --silent "$URL" >/dev/null; then + echo "Error: Go tarball not found at $URL" >&2 + exit 1 +fi + +# Download and install +TMP_DIR=$(mktemp -d) +cd "$TMP_DIR" +echo "Downloading $TARBALL..." +curl -fLO "$URL" + +# Validate archive format +if ! file "$TARBALL" | grep -q 'gzip compressed data'; then + echo "Error: Downloaded file is not a valid tar.gz archive." >&2 + exit 1 +fi + +echo "Removing any existing Go installation in $INSTALL_DIR..." +rm -rf "$INSTALL_DIR" + +echo "Extracting Go to $INSTALL_DIR..." +tar -C "$(dirname "$INSTALL_DIR")" -xzf "$TARBALL" + +rm -rf "$TMP_DIR" + +echo "✅ Go $LATEST_VERSION installed to $INSTALL_DIR" +echo "➕ Add to PATH if needed:" +echo " export PATH=\$PATH:/usr/local/go/bin" + From 906156823ead885fa0f385bc4054b828f43cf371 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 28 Jul 2025 09:43:57 -0700 Subject: [PATCH 0859/1053] fix: Fix broken deserialization of old FreeArea format (#1147) As noted in the issue, we were failing to deserialize FreeAreas when they were encoded in the old serde format. This was because I did not correctly implement deserializing the varints. Bincode's format is different from the format `integer-encoding` uses thus the problem when deserializing. This fixes the issue by implementing the correct deserialization logic for FreeArea. Now, it can correctly read the old format. Fixes: #1146 --- storage/src/nodestore/alloc.rs | 55 +++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 36ff1c3d8d20..8f109e5c6762 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -354,12 +354,13 @@ impl Serializable for FreeArea { } 0x01 => { // encoded `Some(_)` as 1 with the data following - let addr = LinearAddress::new(reader.read_varint()?).ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - "Option:: was Some(0) which is invalid", - ) - })?; + let addr = LinearAddress::new(read_bincode_varint_u64_le(&mut reader)?) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + "Option:: was Some(0) which is invalid", + ) + })?; Ok(Self { next_free_block: Some(addr), }) @@ -379,7 +380,7 @@ impl Serializable for FreeArea { first_byte => Err(Error::new( ErrorKind::InvalidData, format!( - "Invalid FreeArea marker, expected 0xFF (or 0x01 for old format), found {first_byte:#02x}" + "Invalid FreeArea marker, expected 0xFF (or 0x01 for old format), found {first_byte:#04x}" ), )), } @@ -768,6 +769,41 @@ impl NodeStore { } } +fn read_bincode_varint_u64_le(reader: &mut impl Read) -> std::io::Result { + // See https://github.com/ava-labs/firewood/issues/1146 for full details. + // emulate this behavior: https://github.com/bincode-org/bincode/blob/c44b5e364e7084cdbabf9f94b63a3c7f32b8fb68/src/config/int.rs#L241-L258 + + const SINGLE_BYTE_MAX: u8 = 250; + const U16_BYTE: u8 = 251; + const U32_BYTE: u8 = 252; + const U64_BYTE: u8 = 253; + + match reader.read_byte()? { + byte @ 0..=SINGLE_BYTE_MAX => Ok(u64::from(byte)), + U16_BYTE => { + let mut buf = [0u8; 2]; + reader.read_exact(&mut buf)?; + Ok(u64::from(u16::from_le_bytes(buf))) + } + U32_BYTE => { + let mut buf = [0u8; 4]; + reader.read_exact(&mut buf)?; + Ok(u64::from(u32::from_le_bytes(buf))) + } + U64_BYTE => { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(u64::from_le_bytes(buf)) + } + byte => Err(Error::new( + ErrorKind::InvalidData, + format!( + "Invalid bincode varint byte, expected 0-250, 251, 252, or 253, found {byte:#04x}" + ), + )), + } +} + #[cfg(test)] #[expect(clippy::unwrap_used)] pub mod test_utils { @@ -850,6 +886,11 @@ mod tests { #[test_case(&[0x02, 0x01, 0x00], Some((2, 0)); "none")] #[test_case(&[0x03, 0xff, 0x2b], Some((3, 43)); "new format")] #[test_case(&[0x03, 0x44, 0x55], None; "garbage")] + #[test_case( + &[0x03, 0x01, 0x01, 0xfd, 0xe0, 0xa2, 0x6d, 0x27, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x09, 0x03, 0x00], + Some((3, 0x6e_276d_a2e0)); + "old format with u64 address (issue #1146)" + )] fn test_free_list_format(reader: &[u8], expected: Option<(AreaIndex, u64)>) { let expected = expected.map(|(index, addr)| (FreeArea::new(LinearAddress::new(addr)), index)); From 92cc23cb79ac24b656036b00f33b8a15d7d1a7c0 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 28 Jul 2025 13:51:24 -0700 Subject: [PATCH 0860/1053] feat: Update RangeProof structure (#1136) While implementing the range proof verification, I realized that having an Option over a byte range is not ideal to work with. This meant that I needed to do `proof.is_none_or(|p| p.is_empty())` every time I wanted to use say a proof was empty. It was easier to use `proof.is_empty()` everywhere. The inverse was also true where when I wanted to use a proof, I first needed to destructure it and then check if it was empty. ```rust let proof = match proof { Some(proof) if !proof.is_empty() => Some(proof), None }; ``` Was in many places in my code. Also updated is the method signature and documentation for `verify_range_proof` to match how it will be used in the future. --- firewood/src/merkle.rs | 195 ++++++++++++++++++++++++++++++------ firewood/src/range_proof.rs | 100 ++++++++++++++++-- 2 files changed, 256 insertions(+), 39 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 062e74be6a1b..1732cc9eb4f5 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,19 +1,18 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::proof::{Proof, ProofError, ProofNode}; -#[cfg(test)] +use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; #[cfg(test)] use crate::stream::MerkleKeyValueStream; use crate::stream::PathIterator; -use crate::v2::api::FrozenProof; #[cfg(test)] -use crate::v2::api::{self, FrozenRangeProof}; +use crate::v2::api::FrozenRangeProof; +use crate::v2::api::{self, FrozenProof, KeyType, ValueType}; use firewood_storage::{ - BranchNode, Child, FileIoError, HashType, Hashable, HashedNodeReader, ImmutableProposal, - IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, - Parentable, Path, ReadableStorage, SharedNode, TrieReader, ValueDigest, + BranchNode, Child, FileIoError, HashType, HashedNodeReader, ImmutableProposal, IntoHashType, + LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, Parentable, + Path, ReadableStorage, SharedNode, TrieHash, TrieReader, ValueDigest, }; #[cfg(test)] use futures::{StreamExt, TryStreamExt}; @@ -206,16 +205,87 @@ impl Merkle { Ok(Proof::new(proof.into_boxed_slice())) } - /// Verify a proof that a key has a certain value, or that the key isn't in the trie. - #[expect(clippy::missing_errors_doc)] + /// Verify that a range proof is valid for the specified key range and root hash. + /// + /// This method validates a range proof by constructing a partial trie from the proof data + /// and verifying that it produces the expected root hash. The proof may contain fewer + /// key-value pairs than requested if the peer chose to limit the response size. + /// + /// # Parameters + /// + /// * `first_key` - The requested start of the range (inclusive). + /// - If `Some(key)`, verifies the proof covers keys >= this key + /// - If `None`, verifies the proof starts from the beginning of the trie + /// + /// * `last_key` - The requested end of the range (inclusive). + /// - If `Some(key)`, represents the upper bound that was requested + /// - If `None`, indicates no upper bound was specified + /// - Note: The proof may contain fewer keys than requested if the peer limited the response + /// + /// * `root_hash` - The expected root hash of the trie. The constructed partial trie + /// from the proof must produce this exact hash for the proof to be valid. + /// + /// * `proof` - The range proof to verify, containing: + /// - Start proof: Merkle proof for the lower boundary + /// - End proof: Merkle proof for the upper boundary + /// - Key-value pairs: The actual entries within the range + /// + /// # Returns + /// + /// Returns the constructed [`Merkle, _>`] that was built and + /// verified from the proof data, if the proof is valid. + /// + /// # Verification Process + /// + /// The verification follows these steps: + /// 1. **Structural validation**: Verify the proof structure is well-formed + /// - Check that start/end proofs are consistent with the key range + /// - Ensure key-value pairs are in the correct order + /// - Validate that boundary proofs correctly bound the key-value pairs + /// + /// 2. **Proposal construction**: Build a proposal trie containing the proof data + /// - Insert all key-value pairs from the proof + /// - Incorporate nodes from the start and end proofs + /// - Handle edge cases for empty ranges or partial proofs + /// + /// 3. **Hash verification**: Compute the root hash of the constructed proposal + /// - The computed hash must match the provided `root_hash` exactly + /// - Any mismatch indicates an invalid or tampered proof + /// + /// # Errors + /// + /// * [`api::Error::ProofError`] - The proof structure is malformed or inconsistent + /// * [`api::Error::InvalidRange`] - The proof boundaries don't match the requested range + /// * [`api::Error::IncorrectRootHash`] - The computed root hash doesn't match the expected hash + /// * [`api::Error`] - Other errors during proposal construction or verification + /// + /// # Examples + /// + /// ```ignore + /// // Verify a range proof received from a peer + /// let verified_proposal = merkle.verify_range_proof( + /// Some(b"alice"), + /// Some(b"charlie"), + /// &expected_root_hash, + /// &range_proof + /// )?; + /// ``` + /// + /// # Implementation Notes + /// + /// - Structural validation is performed first to avoid expensive proposal construction + /// for obviously invalid proofs + /// - The method is designed to handle partial proofs where the peer provides less + /// data than requested, which is common for large ranges + /// - Future optimization: Consider caching partial verification results for + /// incremental range proof verification pub fn verify_range_proof>( &self, - _proof: &Proof, - _first_key: &[u8], - _last_key: &[u8], - _keys: Vec<&[u8]>, - _vals: Vec, - ) -> Result { + _first_key: Option, + _last_key: Option, + _root_hash: &TrieHash, + _proof: &RangeProof, + ) -> Result<(), api::Error> { todo!() } @@ -240,6 +310,74 @@ impl Merkle { MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) } + /// Generate a cryptographic proof for a range of key-value pairs in the Merkle trie. + /// + /// This method creates a range proof that can be used to verify the existence (or absence) + /// of a contiguous set of keys within the trie. The proof includes boundary proofs and + /// the actual key-value pairs within the specified range. + /// + /// # Parameters + /// + /// * `start_key` - The optional lower bound of the range (inclusive). + /// - If `Some(key)`, the proof will include all keys >= this key + /// - If `None`, the proof starts from the beginning of the trie + /// + /// * `end_key` - The optional upper bound of the range (inclusive). + /// - If `Some(key)`, the proof will include all keys <= this key + /// - If `None`, the proof extends to the end of the trie + /// + /// * `limit` - Optional maximum number of key-value pairs to include in the proof. + /// - If `Some(n)`, at most n key-value pairs will be included + /// - If `None`, all key-value pairs in the range will be included + /// - Useful for paginating through large ranges + /// - **NOTE**: avalanchego's limit is based on the entire packet size and not the + /// number of key-value pairs. Currently, we only limit by the number of pairs. + /// + /// # Returns + /// + /// A `FrozenRangeProof` containing: + /// - Start proof: Merkle proof for the first key in the range + /// - End proof: Merkle proof for the last key in the range + /// - Key-value pairs: All entries within the specified bounds (up to the limit) + /// + /// # Errors + /// + /// * `api::Error::InvalidRange` - If `start_key` > `end_key` when both are provided. + /// This ensures the range bounds are logically consistent. + /// + /// * `api::Error::RangeProofOnEmptyTrie` - If the trie is empty and the caller + /// requests a proof for the entire trie (both `start_key` and `end_key` are `None`). + /// This prevents generating meaningless proofs for non-existent data. + /// + /// * `api::Error` - Various other errors can occur during proof generation, such as: + /// - I/O errors when reading nodes from storage + /// - Corrupted trie structure + /// - Invalid node references + /// + /// # Examples + /// + /// ```ignore + /// // Prove all keys between "alice" and "charlie" + /// let proof = merkle.range_proof( + /// Some(b"alice"), + /// Some(b"charlie"), + /// None + /// ).await?; + /// + /// // Prove the first 100 keys starting from "alice" + /// let proof = merkle.range_proof( + /// Some(b"alice"), + /// None, + /// Some(NonZeroUsize::new(100).unwrap()) + /// ).await?; + /// + /// // Prove that no keys exist in a range + /// let proof = merkle.range_proof( + /// Some(b"aardvark"), + /// Some(b"aaron"), + /// None + /// ).await?; + /// ``` #[cfg(test)] pub(super) async fn range_proof( &self, @@ -276,15 +414,15 @@ impl Merkle { let start_proof = start_key .map(|start_key| self.prove(start_key)) - .transpose()?; + .transpose()? + .unwrap_or_default(); - let end_proof = end_key.map(|end_key| self.prove(end_key)).transpose()?; + let end_proof = end_key + .map(|end_key| self.prove(end_key)) + .transpose()? + .unwrap_or_default(); - return Ok(RangeProof { - start_proof, - key_values: Box::new([]), - end_proof, - }); + return Ok(RangeProof::new(start_proof, end_proof, Box::new([]))); }; let start_proof = self.prove(&first_key)?; @@ -318,15 +456,14 @@ impl Merkle { let end_proof = key_values .last() .map(|(largest_key, _)| self.prove(largest_key)) - .transpose()?; - - debug_assert!(end_proof.is_some()); + .transpose()? + .unwrap_or_default(); - Ok(RangeProof { - start_proof: Some(start_proof), - key_values: key_values.into(), + Ok(RangeProof::new( + start_proof, end_proof, - }) + key_values.into_boxed_slice(), + )) } pub(crate) fn get_value(&self, key: &[u8]) -> Result, FileIoError> { diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index 34d920ae92f2..6c70ebafcf10 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -1,16 +1,96 @@ // Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::proof::Proof; +use crate::proof::{Proof, ProofCollection}; -/// A range proof proves that a given set of key-value pairs -/// are in the trie with a given root hash. +/// A range proof is a cryptographic proof that demonstrates a contiguous set of key-value pairs +/// exists within a Merkle trie with a given root hash. +/// +/// Range proofs are used to efficiently prove the presence (or absence) of multiple consecutive +/// keys in a trie without revealing the entire trie structure. They consist of: +/// - A start proof: proves the existence of the first key in the range (or the nearest key before it) +/// - An end proof: proves the existence of the last key in the range (or the nearest key after it) +/// - The actual key-value pairs within the range +/// +/// This allows verification that: +/// 1. The provided key-value pairs are indeed part of the trie +/// 2. There are no other keys between the start and end of the range +/// 3. The trie has the claimed root hash +/// +/// Range proofs are particularly useful in blockchain contexts for: +/// - State synchronization between nodes +/// - Light client verification +/// - Efficient auditing of specific key ranges #[derive(Debug)] -pub struct RangeProof, V: AsRef<[u8]>, H> { - #[expect(dead_code)] - pub(crate) start_proof: Option>, - #[expect(dead_code)] - pub(crate) end_proof: Option>, - #[expect(dead_code)] - pub(crate) key_values: Box<[(K, V)]>, +pub struct RangeProof { + start_proof: Proof, + end_proof: Proof, + key_values: Box<[(K, V)]>, +} + +impl RangeProof +where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + H: ProofCollection, +{ + /// Create a new range proof with the given start and end proofs + /// and the key-value pairs that are included in the proof. + /// + /// # Parameters + /// + /// * `start_proof` - A Merkle proof for the first key in the range, or if the range + /// starts before any existing key, a proof for the nearest key that comes after + /// the start of the range. This proof establishes the lower boundary of the range + /// and ensures no keys exist between the range start and the first included key. + /// May be empty if proving from the very beginning of the trie. + /// + /// * `end_proof` - A Merkle proof for the last key in the range, or if the range + /// extends beyond all existing keys, a proof for the nearest key that comes before + /// the end of the range. This proof establishes the upper boundary of the range + /// and ensures no keys exist between the last included key and the range end. + /// May be empty if proving to the very end of the trie. + /// + /// * `key_values` - The actual key-value pairs that exist within the proven range. + /// These are the consecutive entries from the trie that fall within the boundaries + /// established by the start and end proofs. The keys should be in lexicographic + /// order as they appear in the trie. May be empty if proving the absence of keys + /// in a range. + #[must_use] + pub const fn new( + start_proof: Proof, + end_proof: Proof, + key_values: Box<[(K, V)]>, + ) -> Self { + Self { + start_proof, + end_proof, + key_values, + } + } + + /// Returns a reference to the start proof, which may be empty. + #[must_use] + pub const fn start_proof(&self) -> &Proof { + &self.start_proof + } + + /// Returns a reference to the end proof, which may be empty. + #[must_use] + pub const fn end_proof(&self) -> &Proof { + &self.end_proof + } + + /// Returns the key-value pairs included in the range proof, which may be empty. + #[must_use] + pub const fn key_values(&self) -> &[(K, V)] { + &self.key_values + } + + /// Returns true if the range proof is empty, meaning it has no start or end proof + /// and no key-value pairs. + #[must_use] + pub fn is_empty(&self) -> bool { + self.start_proof.is_empty() && self.end_proof.is_empty() && self.key_values.is_empty() + } } From 45ed6ab281c930243af67517c6e91f8867de38f4 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 29 Jul 2025 16:25:06 -0700 Subject: [PATCH 0861/1053] test: Port TestDeepPropose from go->rust (#1115) Changed to test with 2 keys and values and 100 proposals, which seems like it exercises more of the code with less data. Fixes #1102 --- firewood/src/db.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7fcb622d8998..ede8d78be4a4 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -833,6 +833,86 @@ mod test { } } + #[tokio::test] + async fn test_deep_propose() { + const NUM_KEYS: usize = 2; + const NUM_PROPOSALS: usize = 100; + + let db = testdb().await; + + // create NUM_KEYS * NUM_PROPOSALS keys and values + let (keys, vals): (Vec<_>, Vec<_>) = (0..NUM_KEYS * NUM_PROPOSALS) + .map(|i| { + ( + format!("key{i}").into_bytes(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + // create batches of NUM_KEYS keys and values + let batches: Vec<_> = keys + .chunks(NUM_KEYS) + .zip(vals.chunks(NUM_KEYS)) + .map(|(k, v)| { + k.iter() + .zip(v.iter()) + .map(|(k, v)| BatchOp::Put { key: k, value: v }) + .collect() + }) + .collect(); + + // better be correct + assert_eq!(batches.len(), NUM_PROPOSALS); + + // create proposals from the batches. The first one is created from the db, the others are + // children + let mut batches_iter = batches.into_iter(); + let mut proposals = vec![db.propose(batches_iter.next().unwrap()).await.unwrap()]; + + for batch in batches_iter { + let proposal = proposals + .last() + .unwrap() + .clone() + .propose(batch) + .await + .unwrap(); + proposals.push(proposal); + } + + // check that each value is present in the final proposal + for (k, v) in keys.iter().zip(vals.iter()) { + assert_eq!(&proposals.last().unwrap().val(k).await.unwrap().unwrap(), v); + } + + // save the last proposal root hash for comparison with the final database root hash + let last_proposal_root_hash = proposals + .last() + .unwrap() + .root_hash() + .await + .unwrap() + .unwrap(); + + // commit the proposals + for proposal in proposals { + proposal.commit().await.unwrap(); + } + + // get the last committed revision + let last_root_hash = db.root_hash().await.unwrap().unwrap(); + let committed = db.revision(last_root_hash.clone()).await.unwrap(); + + // the last root hash should be the same as the last proposal root hash + assert_eq!(last_root_hash, last_proposal_root_hash); + + // check that all the keys and values are still present + for (k, v) in keys.iter().zip(vals.iter()) { + assert_eq!(&committed.val(k).await.unwrap().unwrap(), v); + } + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, From 18f3b2bd56003be8792afae571374d2b30e7a54c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 30 Jul 2025 08:36:52 -0700 Subject: [PATCH 0862/1053] chore: move all merkle tests into a subdirectory (#1150) The tests likely could be split up into even smaller files. For now, they are split into working with no feature flags (`mod.rs`), needing ethhash (`ethhash.rs`), needing without ethhash (`triehash.rs`), and the uncommented and unvalidated tests (`unvalidated.rs`). Will gladly accept recommendations for further splitting and categorization. --- firewood/src/merkle.rs | 2047 +--------------------- firewood/src/merkle/tests/ethhash.rs | 187 ++ firewood/src/merkle/tests/mod.rs | 817 +++++++++ firewood/src/merkle/tests/triehash.rs | 28 + firewood/src/merkle/tests/unvalidated.rs | 1192 +++++++++++++ 5 files changed, 2228 insertions(+), 2043 deletions(-) create mode 100644 firewood/src/merkle/tests/ethhash.rs create mode 100644 firewood/src/merkle/tests/mod.rs create mode 100644 firewood/src/merkle/tests/triehash.rs create mode 100644 firewood/src/merkle/tests/unvalidated.rs diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 1732cc9eb4f5..9038770de307 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -1,6 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +#[cfg(test)] +mod tests; + use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; #[cfg(test)] @@ -279,7 +282,7 @@ impl Merkle { /// data than requested, which is common for large ranges /// - Future optimization: Consider caching partial verification results for /// incremental range proof verification - pub fn verify_range_proof>( + pub fn verify_range_proof( &self, _first_key: Option, _last_key: Option, @@ -1236,2045 +1239,3 @@ impl<'a, T: PartialEq> PrefixOverlap<'a, T> { } } } - -#[cfg(test)] -mod tests { - #![expect(clippy::indexing_slicing, clippy::unwrap_used)] - #![expect( - clippy::items_after_statements, - reason = "Found 1 occurrences after enabling the lint." - )] - - use super::*; - use firewood_storage::{MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng, rng}; - use test_case::test_case; - - // Returns n random key-value pairs. - fn generate_random_kvs(seed: u64, n: usize) -> Vec<(Vec, Vec)> { - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - - let mut rng = StdRng::seed_from_u64(seed); - - let mut kvs: Vec<(Vec, Vec)> = Vec::new(); - for _ in 0..n { - let key_len = rng.random_range(1..=4096); - let key: Vec = (0..key_len).map(|_| rng.random()).collect(); - - let val_len = rng.random_range(1..=4096); - let val: Vec = (0..val_len).map(|_| rng.random()).collect(); - - kvs.push((key, val)); - } - - kvs - } - - #[test] - fn test_get_regression() { - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&[0], Box::new([0])).unwrap(); - assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); - - merkle.insert(&[1], Box::new([1])).unwrap(); - assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); - - merkle.insert(&[2], Box::new([2])).unwrap(); - assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); - - let merkle = merkle.hash(); - - assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); - assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); - assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); - - for result in merkle.path_iter(&[2]).unwrap() { - result.unwrap(); - } - } - - #[test] - fn insert_one() { - let mut merkle = create_in_memory_merkle(); - merkle.insert(b"abc", Box::new([])).unwrap(); - } - - fn create_in_memory_merkle() -> Merkle> { - let memstore = MemStore::new(vec![]); - - let nodestore = NodeStore::new_empty_proposal(memstore.into()); - - Merkle { nodestore } - } - - #[test] - fn test_insert_and_get() { - let mut merkle = create_in_memory_merkle(); - - // insert values - for key_val in u8::MIN..=u8::MAX { - let key = vec![key_val]; - let val = Box::new([key_val]); - - merkle.insert(&key, val.clone()).unwrap(); - - let fetched_val = merkle.get_value(&key).unwrap(); - - // make sure the value was inserted - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } - - // make sure none of the previous values were forgotten after initial insert - for key_val in u8::MIN..=u8::MAX { - let key = vec![key_val]; - let val = vec![key_val]; - - let fetched_val = merkle.get_value(&key).unwrap(); - - assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - } - } - - #[test] - fn remove_root() { - let key0 = vec![0]; - let val0 = [0]; - let key1 = vec![0, 1]; - let val1 = [0, 1]; - let key2 = vec![0, 1, 2]; - let val2 = [0, 1, 2]; - let key3 = vec![0, 1, 15]; - let val3 = [0, 1, 15]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key0, Box::from(val0)).unwrap(); - merkle.insert(&key1, Box::from(val1)).unwrap(); - merkle.insert(&key2, Box::from(val2)).unwrap(); - merkle.insert(&key3, Box::from(val3)).unwrap(); - // Trie is: - // key0 - // | - // key1 - // / \ - // key2 key3 - - // Test removal of root when it's a branch with 1 branch child - let removed_val = merkle.remove(&key0).unwrap(); - assert_eq!(removed_val, Some(Box::from(val0))); - assert!(merkle.get_value(&key0).unwrap().is_none()); - // Removing an already removed key is a no-op - assert!(merkle.remove(&key0).unwrap().is_none()); - - // Trie is: - // key1 - // / \ - // key2 key3 - // Test removal of root when it's a branch with multiple children - assert_eq!(merkle.remove(&key1).unwrap(), Some(Box::from(val1))); - assert!(merkle.get_value(&key1).unwrap().is_none()); - assert!(merkle.remove(&key1).unwrap().is_none()); - - // Trie is: - // key1 (now has no value) - // / \ - // key2 key3 - let removed_val = merkle.remove(&key2).unwrap(); - assert_eq!(removed_val, Some(Box::from(val2))); - assert!(merkle.get_value(&key2).unwrap().is_none()); - assert!(merkle.remove(&key2).unwrap().is_none()); - - // Trie is: - // key3 - let removed_val = merkle.remove(&key3).unwrap(); - assert_eq!(removed_val, Some(Box::from(val3))); - assert!(merkle.get_value(&key3).unwrap().is_none()); - assert!(merkle.remove(&key3).unwrap().is_none()); - - assert!(merkle.nodestore.root_node().is_none()); - } - - #[test] - fn remove_prefix_exact() { - let mut merkle = two_byte_all_keys(); - for key_val in u8::MIN..=u8::MAX { - let key = [key_val]; - let got = merkle.remove_prefix(&key).unwrap(); - assert_eq!(got, 1); - let got = merkle.get_value(&key).unwrap(); - assert!(got.is_none()); - } - } - - fn two_byte_all_keys() -> Merkle> { - let mut merkle = create_in_memory_merkle(); - for key_val in u8::MIN..=u8::MAX { - let key = [key_val, key_val]; - let val = [key_val]; - - merkle.insert(&key, Box::new(val)).unwrap(); - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(&*got, val); - } - merkle - } - - #[test] - fn remove_prefix_all() { - let mut merkle = two_byte_all_keys(); - let got = merkle.remove_prefix(&[]).unwrap(); - assert_eq!(got, 256); - } - - #[test] - fn remove_prefix_partial() { - let mut merkle = create_in_memory_merkle(); - merkle - .insert(b"abc", Box::from(b"value".as_slice())) - .unwrap(); - merkle - .insert(b"abd", Box::from(b"value".as_slice())) - .unwrap(); - let got = merkle.remove_prefix(b"ab").unwrap(); - assert_eq!(got, 2); - } - - #[test] - fn remove_many() { - let mut merkle = create_in_memory_merkle(); - - // insert key-value pairs - for key_val in u8::MIN..=u8::MAX { - let key = [key_val]; - let val = [key_val]; - - merkle.insert(&key, Box::new(val)).unwrap(); - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(&*got, val); - } - - // remove key-value pairs - for key_val in u8::MIN..=u8::MAX { - let key = [key_val]; - let val = [key_val]; - - let got = merkle.remove(&key).unwrap().unwrap(); - assert_eq!(&*got, val); - - // Removing an already removed key is a no-op - assert!(merkle.remove(&key).unwrap().is_none()); - - let got = merkle.get_value(&key).unwrap(); - assert!(got.is_none()); - } - assert!(merkle.nodestore.root_node().is_none()); - } - - #[test] - fn remove_prefix() { - let mut merkle = create_in_memory_merkle(); - - // insert key-value pairs - for key_val in u8::MIN..=u8::MAX { - let key = [key_val, key_val]; - let val = [key_val]; - - merkle.insert(&key, Box::new(val)).unwrap(); - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(&*got, val); - } - - // remove key-value pairs with prefix [0] - let prefix = [0]; - assert_eq!(merkle.remove_prefix(&[0]).unwrap(), 1); - - // make sure all keys with prefix [0] were removed - for key_val in u8::MIN..=u8::MAX { - let key = [key_val, key_val]; - let got = merkle.get_value(&key).unwrap(); - if key[0] == prefix[0] { - assert!(got.is_none()); - } else { - assert!(got.is_some()); - } - } - } - - #[test] - fn get_empty_proof() { - let merkle = create_in_memory_merkle().hash(); - let proof = merkle.prove(b"any-key"); - assert!(matches!(proof.unwrap_err(), ProofError::Empty)); - } - - #[test] - fn single_key_proof() { - let mut merkle = create_in_memory_merkle(); - - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - const TEST_SIZE: usize = 1; - - let kvs = generate_random_kvs(seed, TEST_SIZE); - - for (key, val) in &kvs { - merkle.insert(key, val.clone().into_boxed_slice()).unwrap(); - } - - let merkle = merkle.hash(); - - let root_hash = merkle.nodestore.root_hash().unwrap(); - - for (key, value) in kvs { - let proof = merkle.prove(&key).unwrap(); - - proof - .verify(key.clone(), Some(value.clone()), &root_hash) - .unwrap(); - - { - // Test that the proof is invalid when the value is different - let mut value = value.clone(); - value[0] = value[0].wrapping_add(1); - assert!(proof.verify(key.clone(), Some(value), &root_hash).is_err()); - } - - { - // Test that the proof is invalid when the hash is different - assert!( - proof - .verify(key, Some(value), &TrieHash::default()) - .is_err() - ); - } - } - } - - #[tokio::test] - async fn empty_range_proof() { - let merkle = create_in_memory_merkle(); - - assert!(matches!( - merkle.range_proof(None, None, None).await.unwrap_err(), - api::Error::RangeProofOnEmptyTrie - )); - } - - // #[tokio::test] - // async fn range_proof_invalid_bounds() { - // let merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - // let start_key = &[0x01]; - // let end_key = &[0x00]; - - // match merkle - // .range_proof::<&[u8]>(root_addr, Some(start_key), Some(end_key), Some(1)) - // .await - // { - // Err(api::Error::InvalidRange { - // first_key, - // last_key, - // }) if first_key == start_key && last_key == end_key => (), - // Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), - // _ => panic!("expected InvalidRange error"), - // } - // } - - // #[tokio::test] - // async fn full_range_proof() { - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - // // insert values - // for key_val in u8::MIN..=u8::MAX { - // let key = &[key_val]; - // let val = &[key_val]; - - // merkle.insert(key, val.to_vec(), root_addr).unwrap(); - // } - // merkle.flush_dirty(); - - // let rangeproof = merkle - // .range_proof::<&[u8]>(root_addr, None, None, None) - // .await - // .unwrap() - // .unwrap(); - // assert_eq!(rangeproof.middle.len(), u8::MAX as usize + 1); - // assert_ne!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - // let left_proof = merkle.prove([u8::MIN], root_addr).unwrap(); - // let right_proof = merkle.prove([u8::MAX], root_addr).unwrap(); - // assert_eq!(rangeproof.first_key_proof.0, left_proof.0); - // assert_eq!(rangeproof.last_key_proof.0, right_proof.0); - // } - - // #[tokio::test] - // async fn single_value_range_proof() { - // const RANDOM_KEY: u8 = 42; - - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - // // insert values - // for key_val in u8::MIN..=u8::MAX { - // let key = &[key_val]; - // let val = &[key_val]; - - // merkle.insert(key, val.to_vec(), root_addr).unwrap(); - // } - // merkle.flush_dirty(); - - // let rangeproof = merkle - // .range_proof(root_addr, Some([RANDOM_KEY]), None, Some(1)) - // .await - // .unwrap() - // .unwrap(); - // assert_eq!(rangeproof.first_key_proof.0, rangeproof.last_key_proof.0); - // assert_eq!(rangeproof.middle.len(), 1); - // } - - // #[test] - // fn shared_path_proof() { - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - - // let key1 = b"key1"; - // let value1 = b"1"; - // merkle.insert(key1, value1.to_vec(), root_addr).unwrap(); - - // let key2 = b"key2"; - // let value2 = b"2"; - // merkle.insert(key2, value2.to_vec(), root_addr).unwrap(); - - // let root_hash = merkle.root_hash(root_addr).unwrap(); - - // let verified = { - // let key = key1; - // let proof = merkle.prove(key, root_addr).unwrap(); - // proof.verify(key, root_hash.0).unwrap() - // }; - - // assert_eq!(verified, Some(value1.to_vec())); - - // let verified = { - // let key = key2; - // let proof = merkle.prove(key, root_addr).unwrap(); - // proof.verify(key, root_hash.0).unwrap() - // }; - - // assert_eq!(verified, Some(value2.to_vec())); - // } - - // // this was a specific failing case - // #[test] - // fn shared_path_on_insert() { - // type Bytes = &'static [u8]; - // let pairs: Vec<(Bytes, Bytes)> = vec![ - // ( - // &[1, 1, 46, 82, 67, 218], - // &[23, 252, 128, 144, 235, 202, 124, 243], - // ), - // ( - // &[1, 0, 0, 1, 1, 0, 63, 80], - // &[99, 82, 31, 213, 180, 196, 49, 242], - // ), - // ( - // &[0, 0, 0, 169, 176, 15], - // &[105, 211, 176, 51, 231, 182, 74, 207], - // ), - // ( - // &[1, 0, 0, 0, 53, 57, 93], - // &[234, 139, 214, 220, 172, 38, 168, 164], - // ), - // ]; - - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - - // for (key, val) in &pairs { - // let val = val.to_vec(); - // merkle.insert(key, val.clone(), root_addr).unwrap(); - - // let fetched_val = merkle.get(key, root_addr).unwrap(); - - // // make sure the value was inserted - // assert_eq!(fetched_val.as_deref(), val.as_slice().into()); - // } - - // for (key, val) in pairs { - // let fetched_val = merkle.get(key, root_addr).unwrap(); - - // // make sure the value was inserted - // assert_eq!(fetched_val.as_deref(), val.into()); - // } - // } - - // #[test] - // fn overwrite_leaf() { - // let key = vec![0x00]; - // let val = vec![1]; - // let overwrite = vec![2]; - - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - - // merkle.insert(&key, val.clone(), root_addr).unwrap(); - - // assert_eq!( - // merkle.get(&key, root_addr).unwrap().as_deref(), - // Some(val.as_slice()) - // ); - - // merkle - // .insert(&key, overwrite.clone(), root_addr) - // .unwrap(); - - // assert_eq!( - // merkle.get(&key, root_addr).unwrap().as_deref(), - // Some(overwrite.as_slice()) - // ); - // } - - #[test] - fn test_insert_leaf_suffix() { - // key_2 is a suffix of key, which is a leaf - let key = vec![0xff]; - let val = [1]; - let key_2 = vec![0xff, 0x00]; - let val_2 = [2]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key, Box::new(val)).unwrap(); - merkle.insert(&key_2, Box::new(val_2)).unwrap(); - - let got = merkle.get_value(&key).unwrap().unwrap(); - - assert_eq!(*got, val); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(*got, val_2); - } - - #[test] - fn test_insert_leaf_prefix() { - // key_2 is a prefix of key, which is a leaf - let key = vec![0xff, 0x00]; - let val = [1]; - let key_2 = vec![0xff]; - let val_2 = [2]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key, Box::new(val)).unwrap(); - merkle.insert(&key_2, Box::new(val_2)).unwrap(); - - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(*got, val); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(*got, val_2); - } - - #[test] - fn test_insert_sibling_leaf() { - // The node at key is a branch node with children key_2 and key_3. - // TODO assert in this test that key is the parent of key_2 and key_3. - // i.e. the node types are branch, leaf, leaf respectively. - let key = vec![0xff]; - let val = [1]; - let key_2 = vec![0xff, 0x00]; - let val_2 = [2]; - let key_3 = vec![0xff, 0x0f]; - let val_3 = [3]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key, Box::new(val)).unwrap(); - merkle.insert(&key_2, Box::new(val_2)).unwrap(); - merkle.insert(&key_3, Box::new(val_3)).unwrap(); - - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(*got, val); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(*got, val_2); - - let got = merkle.get_value(&key_3).unwrap().unwrap(); - assert_eq!(*got, val_3); - } - - #[test] - fn test_insert_branch_as_branch_parent() { - let key = vec![0xff, 0xf0]; - let val = [1]; - let key_2 = vec![0xff, 0xf0, 0x00]; - let val_2 = [2]; - let key_3 = vec![0xff]; - let val_3 = [3]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key, Box::new(val)).unwrap(); - // key is a leaf - - merkle.insert(&key_2, Box::new(val_2)).unwrap(); - // key is branch with child key_2 - - merkle.insert(&key_3, Box::new(val_3)).unwrap(); - // key_3 is a branch with child key - // key is a branch with child key_3 - - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(&*got, val); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(&*got, val_2); - - let got = merkle.get_value(&key_3).unwrap().unwrap(); - assert_eq!(&*got, val_3); - } - - #[test] - fn test_insert_overwrite_branch_value() { - let key = vec![0xff]; - let val = [1]; - let key_2 = vec![0xff, 0x00]; - let val_2 = [2]; - let overwrite = [3]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(&key, Box::new(val)).unwrap(); - merkle.insert(&key_2, Box::new(val_2)).unwrap(); - - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(*got, val); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(*got, val_2); - - merkle.insert(&key, Box::new(overwrite)).unwrap(); - - let got = merkle.get_value(&key).unwrap().unwrap(); - assert_eq!(*got, overwrite); - - let got = merkle.get_value(&key_2).unwrap().unwrap(); - assert_eq!(*got, val_2); - } - - #[test] - fn test_delete_one_child_with_branch_value() { - let mut merkle = create_in_memory_merkle(); - // insert a parent with a value - merkle.insert(&[0], Box::new([42u8])).unwrap(); - // insert child1 with a value - merkle.insert(&[0, 1], Box::new([43u8])).unwrap(); - // insert child2 with a value - merkle.insert(&[0, 2], Box::new([44u8])).unwrap(); - - // now delete one of the children - let deleted = merkle.remove(&[0, 1]).unwrap(); - assert_eq!(deleted, Some([43u8].to_vec().into_boxed_slice())); - - // make sure the parent still has the correct value - let got = merkle.get_value(&[0]).unwrap().unwrap(); - assert_eq!(*got, [42u8]); - - // and check the remaining child - let other_child = merkle.get_value(&[0, 2]).unwrap().unwrap(); - assert_eq!(*other_child, [44u8]); - } - - // #[test] - // fn single_key_proof_with_one_node() { - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - // let key = b"key"; - // let value = b"value"; - - // merkle.insert(key, value.to_vec(), root_addr).unwrap(); - // let root_hash = merkle.root_hash(root_addr).unwrap(); - - // let proof = merkle.prove(key, root_addr).unwrap(); - - // let verified = proof.verify(key, root_hash.0).unwrap(); - - // assert_eq!(verified, Some(value.to_vec())); - // } - - // #[test] - // fn two_key_proof_without_shared_path() { - // let mut merkle = create_in_memory_merkle(); - // let root_addr = merkle.init_sentinel().unwrap(); - - // let key1 = &[0x00]; - // let key2 = &[0xff]; - - // merkle.insert(key1, key1.to_vec(), root_addr).unwrap(); - // merkle.insert(key2, key2.to_vec(), root_addr).unwrap(); - - // let root_hash = merkle.root_hash(root_addr).unwrap(); - - // let verified = { - // let proof = merkle.prove(key1, root_addr).unwrap(); - // proof.verify(key1, root_hash.0).unwrap() - // }; - - // assert_eq!(verified.as_deref(), Some(key1.as_slice())); - // } - - fn merkle_build_test, V: AsRef<[u8]>>( - items: Vec<(K, V)>, - ) -> Result>, FileIoError> { - let nodestore = NodeStore::new_empty_proposal(MemStore::new(vec![]).into()); - let mut merkle = Merkle::from(nodestore); - for (k, v) in items { - merkle.insert(k.as_ref(), Box::from(v.as_ref()))?; - } - - Ok(merkle) - } - - #[test] - fn test_root_hash_simple_insertions() -> Result<(), Error> { - let items = vec![ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]; - let merkle = merkle_build_test(items).unwrap().hash(); - - merkle.dump().unwrap(); - Ok(()) - } - - #[cfg(not(feature = "ethhash"))] - #[test_case(vec![], None; "empty trie")] - #[test_case(vec![(&[0],&[0])], Some("073615413d814b23383fc2c8d8af13abfffcb371b654b98dbf47dd74b1e4d1b9"); "root")] - #[test_case(vec![(&[0,1],&[0,1])], Some("28e67ae4054c8cdf3506567aa43f122224fe65ef1ab3e7b7899f75448a69a6fd"); "root with partial path")] - #[test_case(vec![(&[0],&[1;32])], Some("ba0283637f46fa807280b7d08013710af08dfdc236b9b22f9d66e60592d6c8a3"); "leaf value >= 32 bytes")] - #[test_case(vec![(&[0],&[0]),(&[0,1],&[1;32])], Some("3edbf1fdd345db01e47655bcd0a9a456857c4093188cf35c5c89b8b0fb3de17e"); "branch value >= 32 bytes")] - #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")] - #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")] - #[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")] - fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) { - // TODO: get the hashes from merkledb and verify compatibility with branch factor 256 - #[cfg(not(feature = "branch_factor_256"))] - { - let merkle = merkle_build_test(kvs).unwrap().hash(); - let Some(got_hash) = merkle.nodestore.root_hash() else { - assert!(expected_hash.is_none()); - return; - }; - - let expected_hash = expected_hash.unwrap(); - - // This hash is from merkledb - let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap(); - - assert_eq!(got_hash, TrieHash::from(expected_hash)); - } - } - - #[cfg(feature = "ethhash")] - mod ethhasher { - use ethereum_types::H256; - use hash_db::Hasher; - use plain_hasher::PlainHasher; - use sha3::{Digest, Keccak256}; - - #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] - pub struct KeccakHasher; - - impl Hasher for KeccakHasher { - type Out = H256; - type StdHasher = PlainHasher; - const LENGTH: usize = 32; - - #[inline] - fn hash(x: &[u8]) -> Self::Out { - let mut hasher = Keccak256::new(); - hasher.update(x); - let result = hasher.finalize(); - H256::from_slice(result.as_slice()) - } - } - } - - #[cfg(feature = "ethhash")] - #[test_case(&[("doe", "reindeer")])] - #[test_case(&[("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cat")])] - #[test_case(&[("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] - #[test_case(&[("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] - fn test_root_hash_eth_compatible + Clone + Ord>(kvs: &[(T, T)]) { - use ethereum_types::H256; - use ethhasher::KeccakHasher; - use firewood_triehash::trie_root; - - let merkle = merkle_build_test(kvs.to_vec()).unwrap().hash(); - let firewood_hash = merkle.nodestore.root_hash().unwrap_or_default(); - let eth_hash = trie_root::(kvs.to_vec()); - let firewood_hash = H256::from_slice(firewood_hash.as_ref()); - - assert_eq!(firewood_hash, eth_hash); - } - - #[cfg(feature = "ethhash")] - #[test_case( - "0000000000000000000000000000000000000002", - "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - &[], - "c00ca9b8e6a74b03f6b1ae2db4a65ead348e61b74b339fe4b117e860d79c7821" - )] - #[test_case( - "0000000000000000000000000000000000000002", - "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - &[ - ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000") - ], - "91336bf4e6756f68e1af0ad092f4a551c52b4a66860dc31adbd736f0acbadaf6" - )] - #[test_case( - "0000000000000000000000000000000000000002", - "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - &[ - ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000"), - ("0e81f83a84964b811dd1b8328262a9f57e6bc3e5e7eb53627d10437c73c4b8da", "a02800000000000000000000000000000000000000000000000000000000000000"), - ], - "c267104830880c966c2cc8c669659e4bfaf3126558dbbd6216123b457944001b" - )] - fn test_eth_compatible_accounts( - account: &str, - account_value: &str, - key_suffixes_and_values: &[(&str, &str)], - expected_root: &str, - ) { - use sha2::Digest as _; - use sha3::Keccak256; - - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")) - .is_test(true) - .try_init() - .ok(); - - let account = make_box(account); - let expected_key_hash = Keccak256::digest(&account); - - let items = once(( - Box::from(expected_key_hash.as_slice()), - make_box(account_value), - )) - .chain(key_suffixes_and_values.iter().map(|(key_suffix, value)| { - let key = expected_key_hash - .iter() - .copied() - .chain(make_box(key_suffix).iter().copied()) - .collect(); - let value = make_box(value); - (key, value) - })) - .collect::, Box<_>)>>(); - - let merkle = merkle_build_test(items).unwrap().hash(); - let firewood_hash = merkle.nodestore.root_hash(); - - assert_eq!( - firewood_hash, - TrieHash::try_from(&*make_box(expected_root)).ok() - ); - } - - // helper method to convert a string into a boxed slice - #[cfg(feature = "ethhash")] - fn make_box(hex_str: &str) -> Box<[u8]> { - hex::decode(hex_str).unwrap().into_boxed_slice() - } - - #[test] - fn test_root_hash_fuzz_insertions() -> Result<(), FileIoError> { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.random_range(1..=max_len0), - rng.random_range(1..=max_len1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().random_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().random())) - .collect(); - key - }; - - // TODO: figure out why this fails if we use more than 27 iterations with branch_factor_256 - for _ in 0..27 { - let mut items = Vec::new(); - - for _ in 0..100 { - let val: Vec = (0..256).map(|_| rng.borrow_mut().random()).collect(); - items.push((keygen(), val)); - } - - // #[cfg(feature = "ethhash")] - // test_root_hash_eth_compatible(&items); - merkle_build_test(items)?; - } - - Ok(()) - } - - #[test] - fn test_delete_child() { - let items = vec![("do", "verb")]; - let mut merkle = merkle_build_test(items).unwrap(); - - assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); - assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); - } - - #[test] - fn test_delete_some() { - let items = (0..100) - .map(|n| { - let key = format!("key{n}"); - let val = format!("value{n}"); - (key.as_bytes().to_vec(), val.as_bytes().to_vec()) - }) - .collect::, Vec)>>(); - let mut merkle = merkle_build_test(items.clone()).unwrap(); - merkle.remove_prefix(b"key1").unwrap(); - for item in items { - let (key, val) = item; - if key.starts_with(b"key1") { - assert!(merkle.get_value(&key).unwrap().is_none()); - } else { - assert_eq!(&*merkle.get_value(&key).unwrap().unwrap(), val.as_slice()); - } - } - } - - #[test] - fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - - let _ = env_logger::Builder::new().is_test(true).try_init(); - - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); - - let max_len0 = 8; - let max_len1 = 4; - let keygen = || { - let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); - ( - rng.random_range(1..=max_len0), - rng.random_range(1..=max_len1), - ) - }; - let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().random_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().random())) - .collect(); - key - }; - - for _ in 0..10 { - let mut items: Vec<_> = (0..10) - .map(|_| keygen()) - .map(|key| { - let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); - (key, val) - }) - .collect(); - - items.sort(); - items.dedup_by_key(|(k, _)| k.clone()); - - let init_merkle: Merkle> = - merkle_build_test::, Box<[u8]>>(vec![])?; - let init_immutable_merkle: Merkle, _>> = - init_merkle.try_into().unwrap(); - - let (hashes, complete_immutable_merkle) = items.iter().fold( - (vec![], init_immutable_merkle), - |(mut hashes, immutable_merkle), (k, v)| { - let root_hash = immutable_merkle.nodestore.root_hash(); - hashes.push(root_hash); - let mut merkle = Merkle::from( - NodeStore::new(&Arc::new(immutable_merkle.nodestore)).unwrap(), - ); - merkle.insert(k, v.clone()).unwrap(); - (hashes, merkle.try_into().unwrap()) - }, - ); - - let (new_hashes, _) = items.iter().rev().fold( - (vec![], complete_immutable_merkle), - |(mut new_hashes, immutable_merkle_before_removal), (k, _)| { - let before = immutable_merkle_before_removal.dump().unwrap(); - let mut merkle = Merkle::from( - NodeStore::new(&Arc::new(immutable_merkle_before_removal.nodestore)) - .unwrap(), - ); - merkle.remove(k).unwrap(); - let immutable_merkle_after_removal: Merkle< - NodeStore, _>, - > = merkle.try_into().unwrap(); - new_hashes.push(( - immutable_merkle_after_removal.nodestore.root_hash(), - k, - before, - immutable_merkle_after_removal.dump().unwrap(), - )); - (new_hashes, immutable_merkle_after_removal) - }, - ); - - for (expected_hash, (actual_hash, key, before_removal, after_removal)) in - hashes.into_iter().rev().zip(new_hashes) - { - let key = key.iter().fold(String::new(), |mut s, b| { - let _ = write!(s, "{b:02x}"); - s - }); - assert_eq!( - actual_hash, expected_hash, - "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_hash:?}\nactual:\n{actual_hash:?}\n", - ); - } - } - - Ok(()) - } - - // #[test] - // fn test_root_hash_random_deletions() -> Result<(), Error> { - // use rand::rngs::StdRng; - // use rand::seq::SliceRandom; - // use rand::{Rng, SeedableRng}; - // let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); - // let max_len0 = 8; - // let max_len1 = 4; - // let keygen = || { - // let (len0, len1): (usize, usize) = { - // let mut rng = rng.borrow_mut(); - // ( - // rng.random_range(1..max_len0 + 1), - // rng.random_range(1..max_len1 + 1), - // ) - // }; - // let key: Vec = (0..len0) - // .map(|_| rng.borrow_mut().random_range(0..2)) - // .chain((0..len1).map(|_| rng.borrow_mut().random())) - // .collect(); - // key - // }; - - // for i in 0..10 { - // let mut items = std::collections::HashMap::new(); - - // for _ in 0..10 { - // let val: Box<[u8]> = (0..8).map(|_| rng.borrow_mut().random()).collect(); - // items.insert(keygen(), val); - // } - - // let mut items_ordered: Vec<_> = - // items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); - // items_ordered.sort(); - // items_ordered.shuffle(&mut *rng.borrow_mut()); - // let mut merkle = - // Merkle::new(HashedNodeStore::initialize(MemStore::new(vec![])).unwrap()); - - // for (k, v) in items.iter() { - // merkle.insert(k, v.clone())?; - // } - - // for (k, v) in items.iter() { - // assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); - // } - - // for (k, _) in items_ordered.into_iter() { - // assert!(merkle.get(&k)?.is_some()); - - // merkle.remove(&k)?; - - // assert!(merkle.get(&k)?.is_none()); - - // items.remove(&k); - - // for (k, v) in items.iter() { - // assert_eq!(&*merkle.get(k)?.unwrap(), &v[..]); - // } - - // let h = triehash::trie_root::, _, _>( - // items.iter().collect(), - // ); - - // let h0 = merkle.root_hash()?; - - // if TrieHash::from(h) != h0 { - // println!("{} != {}", hex::encode(h), hex::encode(*h0)); - // } - // } - - // println!("i = {i}"); - // } - // Ok(()) - // } - - // #[test] - // fn test_proof() -> Result<(), Error> { - // let set = fixed_and_pseudorandom_data(500); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - // let (keys, vals): (Vec<[u8; 32]>, Vec<[u8; 20]>) = items.into_iter().unzip(); - - // for (i, key) in keys.iter().enumerate() { - // let proof = merkle.prove(key)?; - // assert!(!proof.0.is_empty()); - // let val = merkle.verify_proof(key, &proof)?; - // assert!(val.is_some()); - // assert_eq!(val.unwrap(), vals[i]); - // } - - // Ok(()) - // } - - // #[test] - // /// Verify the proofs that end with leaf node with the given key. - // fn test_proof_end_with_leaf() -> Result<(), Error> { - // let items = vec![ - // ("do", "verb"), - // ("doe", "reindeer"), - // ("dog", "puppy"), - // ("doge", "coin"), - // ("horse", "stallion"), - // ("ddd", "ok"), - // ]; - // let merkle = merkle_build_test(items)?; - // let key = "doe".as_ref(); - - // let proof = merkle.prove(key)?; - // assert!(!proof.0.is_empty()); - - // let verify_proof = merkle.verify_proof(key, &proof)?; - // assert!(verify_proof.is_some()); - - // Ok(()) - // } - - // #[test] - // /// Verify the proofs that end with branch node with the given key. - // fn test_proof_end_with_branch() -> Result<(), Error> { - // let items = vec![ - // ("d", "verb"), - // ("do", "verb"), - // ("doe", "reindeer"), - // ("e", "coin"), - // ]; - // let merkle = merkle_build_test(items)?; - // let key = "d".as_ref(); - - // let proof = merkle.prove(key)?; - // assert!(!proof.0.is_empty()); - - // let verify_proof = merkle.verify_proof(key, &proof)?; - // assert!(verify_proof.is_some()); - - // Ok(()) - // } - - // #[test] - // fn test_bad_proof() -> Result<(), Error> { - // let set = fixed_and_pseudorandom_data(800); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - // let (keys, _): (Vec<[u8; 32]>, Vec<[u8; 20]>) = items.into_iter().unzip(); - - // for key in keys.iter() { - // let mut proof = merkle.prove(key)?; - // assert!(!proof.0.is_empty()); - - // // Delete an entry from the generated proofs. - // let len = proof.0.len(); - // let new_proof = Proof(proof.0.drain().take(len - 1).collect()); - // assert!(merkle.verify_proof(key, &new_proof).is_err()); - // } - - // Ok(()) - // } - - // #[test] - // // Tests that missing keys can also be proven. The test explicitly uses a single - // // entry trie and checks for missing keys both before and after the single entry. - // fn test_missing_key_proof() -> Result<(), Error> { - // let items = vec![("k", "v")]; - // let merkle = merkle_build_test(items)?; - // for key in ["a", "j", "l", "z"] { - // let proof = merkle.prove(key.as_ref())?; - // assert!(!proof.0.is_empty()); - // assert!(proof.0.len() == 1); - - // let val = merkle.verify_proof(key.as_ref(), &proof)?; - // assert!(val.is_none()); - // } - - // Ok(()) - // } - - // #[test] - // fn test_empty_tree_proof() -> Result<(), Error> { - // let items: Vec<(&str, &str)> = Vec::new(); - // let merkle = merkle_build_test(items)?; - // let key = "x".as_ref(); - - // let proof = merkle.prove(key)?; - // assert!(proof.0.is_empty()); - - // Ok(()) - // } - - // #[test] - // // Tests normal range proof with both edge proofs as the existent proof. - // // The test cases are generated randomly. - // fn test_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // for _ in 0..10 { - // let start = rand::rng().random_range(0..items.len()); - // let end = rand::rng().random_range(0..items.len() - start) + start - 1; - - // if end <= start { - // continue; - // } - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end - 1].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let mut keys = Vec::new(); - // let mut vals = Vec::new(); - // for item in items[start..end].iter() { - // keys.push(item.0.as_ref()); - // vals.push(&item.1); - // } - - // merkle.verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals)?; - // } - // Ok(()) - // } - - // #[test] - // // Tests a few cases which the proof is wrong. - // // The prover is expected to detect the error. - // fn test_bad_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // for _ in 0..10 { - // let start = rand::rng().random_range(0..items.len()); - // let end = rand::rng().random_range(0..items.len() - start) + start - 1; - - // if end <= start { - // continue; - // } - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end - 1].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let mut keys: Vec<[u8; 32]> = Vec::new(); - // let mut vals: Vec<[u8; 20]> = Vec::new(); - // for item in items[start..end].iter() { - // keys.push(*item.0); - // vals.push(*item.1); - // } - - // let test_case: u32 = rand::rng().random_range(0..6); - // let index = rand::rng().random_range(0..end - start); - // match test_case { - // 0 => { - // // Modified key - // keys[index] = rand::rng().random::<[u8; 32]>(); // In theory it can't be same - // } - // 1 => { - // // Modified val - // vals[index] = rand::rng().random::<[u8; 20]>(); // In theory it can't be same - // } - // 2 => { - // // Gapped entry slice - // if index == 0 || index == end - start - 1 { - // continue; - // } - // keys.remove(index); - // vals.remove(index); - // } - // 3 => { - // // Out of order - // let index_1 = rand::rng().random_range(0..end - start); - // let index_2 = rand::rng().random_range(0..end - start); - // if index_1 == index_2 { - // continue; - // } - // keys.swap(index_1, index_2); - // vals.swap(index_1, index_2); - // } - // 4 => { - // // Set random key to empty, do nothing - // keys[index] = [0; 32]; - // } - // 5 => { - // // Set random value to nil - // vals[index] = [0; 20]; - // } - // _ => unreachable!(), - // } - - // let keys_slice = keys.iter().map(|k| k.as_ref()).collect::>(); - // assert!(merkle - // .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys_slice, vals) - // .is_err()); - // } - - // Ok(()) - // } - - // #[test] - // // Tests normal range proof with two non-existent proofs. - // // The test cases are generated randomly. - // fn test_range_proof_with_non_existent_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // for _ in 0..10 { - // let start = rand::rng().random_range(0..items.len()); - // let end = rand::rng().random_range(0..items.len() - start) + start - 1; - - // if end <= start { - // continue; - // } - - // // Short circuit if the decreased key is same with the previous key - // let first = decrease_key(items[start].0); - // if start != 0 && first.as_ref() == items[start - 1].0.as_ref() { - // continue; - // } - // // Short circuit if the decreased key is underflow - // if &first > items[start].0 { - // continue; - // } - // // Short circuit if the increased key is same with the next key - // let last = increase_key(items[end - 1].0); - // if end != items.len() && last.as_ref() == items[end].0.as_ref() { - // continue; - // } - // // Short circuit if the increased key is overflow - // if &last < items[end - 1].0 { - // continue; - // } - - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let mut keys: Vec<&[u8]> = Vec::new(); - // let mut vals: Vec<[u8; 20]> = Vec::new(); - // for item in items[start..end].iter() { - // keys.push(item.0); - // vals.push(*item.1); - // } - - // merkle.verify_range_proof(&proof, &first, &last, keys, vals)?; - // } - - // // Special case, two edge proofs for two edge key. - // let first = &[0; 32]; - // let last = &[255; 32]; - // let mut proof = merkle.prove(first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - // let keys = keys.iter().map(|k| k.as_ref()).collect::>(); - // merkle.verify_range_proof(&proof, first, last, keys, vals)?; - - // Ok(()) - // } - - // #[test] - // // Tests such scenarios: - // // - There exists a gap between the first element and the left edge proof - // // - There exists a gap between the last element and the right edge proof - // fn test_range_proof_with_invalid_non_existent_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // // Case 1 - // let mut start = 100; - // let mut end = 200; - // let first = decrease_key(items[start].0); - - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end - 1].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // start = 105; // Gap created - // let mut keys: Vec<&[u8]> = Vec::new(); - // let mut vals: Vec<[u8; 20]> = Vec::new(); - // // Create gap - // for item in items[start..end].iter() { - // keys.push(item.0); - // vals.push(*item.1); - // } - // assert!(merkle - // .verify_range_proof(&proof, &first, items[end - 1].0, keys, vals) - // .is_err()); - - // // Case 2 - // start = 100; - // end = 200; - // let last = increase_key(items[end - 1].0); - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // end = 195; // Capped slice - // let mut keys: Vec<&[u8]> = Vec::new(); - // let mut vals: Vec<[u8; 20]> = Vec::new(); - // // Create gap - // for item in items[start..end].iter() { - // keys.push(item.0); - // vals.push(*item.1); - // } - // assert!(merkle - // .verify_range_proof(&proof, items[start].0, &last, keys, vals) - // .is_err()); - - // Ok(()) - // } - - // #[test] - // // Tests the proof with only one element. The first edge proof can be existent one or - // // non-existent one. - // fn test_one_element_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // // One element with existent edge proof, both edge proofs - // // point to the SAME key. - // let start = 1000; - // let start_proof = merkle.prove(items[start].0)?; - // assert!(!start_proof.0.is_empty()); - - // merkle.verify_range_proof( - // &start_proof, - // items[start].0, - // items[start].0, - // vec![items[start].0], - // vec![&items[start].1], - // )?; - - // // One element with left non-existent edge proof - // let first = decrease_key(items[start].0); - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[start].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof( - // &proof, - // &first, - // items[start].0, - // vec![items[start].0], - // vec![*items[start].1], - // )?; - - // // One element with right non-existent edge proof - // let last = increase_key(items[start].0); - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof( - // &proof, - // items[start].0, - // &last, - // vec![items[start].0], - // vec![*items[start].1], - // )?; - - // // One element with two non-existent edge proofs - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof( - // &proof, - // &first, - // &last, - // vec![items[start].0], - // vec![*items[start].1], - // )?; - - // // Test the mini trie with only a single element. - // let key = rand::rng().random::<[u8; 32]>(); - // let val = rand::rng().random::<[u8; 20]>(); - // let merkle = merkle_build_test(vec![(key, val)])?; - - // let first = &[0; 32]; - // let mut proof = merkle.prove(first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&key)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof(&proof, first, &key, vec![&key], vec![val])?; - - // Ok(()) - // } - - // #[test] - // // Tests the range proof with all elements. - // // The edge proofs can be nil. - // fn test_all_elements_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let item_iter = items.clone().into_iter(); - // let keys: Vec<&[u8]> = item_iter.clone().map(|item| item.0.as_ref()).collect(); - // let vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); - - // let empty_proof = Proof(HashMap::<[u8; 32], Vec>::new()); - // let empty_key: [u8; 32] = [0; 32]; - // merkle.verify_range_proof( - // &empty_proof, - // &empty_key, - // &empty_key, - // keys.clone(), - // vals.clone(), - // )?; - - // // With edge proofs, it should still work. - // let start = 0; - // let end = &items.len() - 1; - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof( - // &proof, - // items[start].0, - // items[end].0, - // keys.clone(), - // vals.clone(), - // )?; - - // // Even with non-existent edge proofs, it should still work. - // let first = &[0; 32]; - // let last = &[255; 32]; - // let mut proof = merkle.prove(first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // merkle.verify_range_proof(&proof, first, last, keys, vals)?; - - // Ok(()) - // } - - // #[test] - // // Tests the range proof with "no" element. The first edge proof must - // // be a non-existent proof. - // fn test_empty_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let cases = [(items.len() - 1, false)]; - // for c in cases.iter() { - // let first = increase_key(items[c.0].0); - // let proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - - // // key and value vectors are intentionally empty. - // let keys: Vec<&[u8]> = Vec::new(); - // let vals: Vec<[u8; 20]> = Vec::new(); - - // if c.1 { - // assert!(merkle - // .verify_range_proof(&proof, &first, &first, keys, vals) - // .is_err()); - // } else { - // merkle.verify_range_proof(&proof, &first, &first, keys, vals)?; - // } - // } - - // Ok(()) - // } - - // #[test] - // // Focuses on the small trie with embedded nodes. If the gapped - // // node is embedded in the trie, it should be detected too. - // fn test_gapped_range_proof() -> Result<(), ProofError> { - // let mut items = Vec::new(); - // // Sorted entries - // for i in 0..10_u32 { - // let mut key = [0; 32]; - // for (index, d) in i.to_be_bytes().iter().enumerate() { - // key[index] = *d; - // } - // items.push((key, i.to_be_bytes())); - // } - // let merkle = merkle_build_test(items.clone())?; - - // let first = 2; - // let last = 8; - - // let mut proof = merkle.prove(&items[first].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&items[last - 1].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let middle = (first + last) / 2 - first; - // let (keys, vals): (Vec<&[u8]>, Vec<&[u8; 4]>) = items[first..last] - // .iter() - // .enumerate() - // .filter(|(pos, _)| *pos != middle) - // .map(|(_, item)| (item.0.as_ref(), &item.1)) - // .unzip(); - - // assert!(merkle - // .verify_range_proof(&proof, &items[0].0, &items[items.len() - 1].0, keys, vals) - // .is_err()); - - // Ok(()) - // } - - // #[test] - // // Tests the element is not in the range covered by proofs. - // fn test_same_side_proof() -> Result<(), Error> { - // let set = fixed_and_pseudorandom_data(4096); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let pos = 1000; - // let mut last = decrease_key(items[pos].0); - // let mut first = last; - // first = decrease_key(&first); - - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // assert!(merkle - // .verify_range_proof( - // &proof, - // &first, - // &last, - // vec![items[pos].0], - // vec![items[pos].1] - // ) - // .is_err()); - - // first = increase_key(items[pos].0); - // last = first; - // last = increase_key(&last); - - // let mut proof = merkle.prove(&first)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&last)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // assert!(merkle - // .verify_range_proof( - // &proof, - // &first, - // &last, - // vec![items[pos].0], - // vec![items[pos].1] - // ) - // .is_err()); - - // Ok(()) - // } - - // #[test] - // // Tests the range starts from zero. - // fn test_single_side_range_proof() -> Result<(), ProofError> { - // for _ in 0..10 { - // let mut set = HashMap::new(); - // for _ in 0..4096_u32 { - // let key = rand::rng().random::<[u8; 32]>(); - // let val = rand::rng().random::<[u8; 20]>(); - // set.insert(key, val); - // } - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let cases = vec![0, 1, 100, 1000, items.len() - 1]; - // for case in cases { - // let start = &[0; 32]; - // let mut proof = merkle.prove(start)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[case].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let item_iter = items.clone().into_iter().take(case + 1); - // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); - // let vals = item_iter.map(|item| item.1).collect(); - - // merkle.verify_range_proof(&proof, start, items[case].0, keys, vals)?; - // } - // } - // Ok(()) - // } - - // #[test] - // // Tests the range ends with 0xffff...fff. - // fn test_reverse_single_side_range_proof() -> Result<(), ProofError> { - // for _ in 0..10 { - // let mut set = HashMap::new(); - // for _ in 0..1024_u32 { - // let key = rand::rng().random::<[u8; 32]>(); - // let val = rand::rng().random::<[u8; 20]>(); - // set.insert(key, val); - // } - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let cases = vec![0, 1, 100, 1000, items.len() - 1]; - // for case in cases { - // let end = &[255; 32]; - // let mut proof = merkle.prove(items[case].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(end)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let item_iter = items.clone().into_iter().skip(case); - // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); - // let vals = item_iter.map(|item| item.1).collect(); - - // merkle.verify_range_proof(&proof, items[case].0, end, keys, vals)?; - // } - // } - // Ok(()) - // } - - // #[test] - // // Tests the range starts with zero and ends with 0xffff...fff. - // fn test_both_sides_range_proof() -> Result<(), ProofError> { - // for _ in 0..10 { - // let mut set = HashMap::new(); - // for _ in 0..4096_u32 { - // let key = rand::rng().random::<[u8; 32]>(); - // let val = rand::rng().random::<[u8; 20]>(); - // set.insert(key, val); - // } - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // let start = &[0; 32]; - // let end = &[255; 32]; - - // let mut proof = merkle.prove(start)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(end)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let (keys, vals): (Vec<&[u8; 32]>, Vec<&[u8; 20]>) = items.into_iter().unzip(); - // let keys = keys.iter().map(|k| k.as_ref()).collect::>(); - // merkle.verify_range_proof(&proof, start, end, keys, vals)?; - // } - // Ok(()) - // } - - // #[test] - // // Tests normal range proof with both edge proofs - // // as the existent proof, but with an extra empty value included, which is a - // // noop technically, but practically should be rejected. - // fn test_empty_value_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(512); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // // Create a new entry with a slightly modified key - // let mid_index = items.len() / 2; - // let key = increase_key(items[mid_index - 1].0); - // let empty_value: [u8; 20] = [0; 20]; - // items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); - - // let start = 1; - // let end = items.len() - 1; - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end - 1].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let item_iter = items.clone().into_iter().skip(start).take(end - start); - // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); - // let vals = item_iter.map(|item| item.1).collect(); - // assert!(merkle - // .verify_range_proof(&proof, items[start].0, items[end - 1].0, keys, vals) - // .is_err()); - - // Ok(()) - // } - - // #[test] - // // Tests the range proof with all elements, - // // but with an extra empty value included, which is a noop technically, but - // // practically should be rejected. - // fn test_all_elements_empty_value_range_proof() -> Result<(), ProofError> { - // let set = fixed_and_pseudorandom_data(512); - // let mut items = Vec::from_iter(set.iter()); - // items.sort(); - // let merkle = merkle_build_test(items.clone())?; - - // // Create a new entry with a slightly modified key - // let mid_index = items.len() / 2; - // let key = increase_key(items[mid_index - 1].0); - // let empty_value: [u8; 20] = [0; 20]; - // items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().cloned()); - - // let start = 0; - // let end = items.len() - 1; - - // let mut proof = merkle.prove(items[start].0)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(items[end].0)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let item_iter = items.clone().into_iter(); - // let keys = item_iter.clone().map(|item| item.0.as_ref()).collect(); - // let vals = item_iter.map(|item| item.1).collect(); - // assert!(merkle - // .verify_range_proof(&proof, items[start].0, items[end].0, keys, vals) - // .is_err()); - - // Ok(()) - // } - - // #[test] - // fn test_range_proof_keys_with_shared_prefix() -> Result<(), ProofError> { - // let items = vec![ - // ( - // hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") - // .expect("Decoding failed"), - // hex::decode("02").expect("Decoding failed"), - // ), - // ( - // hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") - // .expect("Decoding failed"), - // hex::decode("03").expect("Decoding failed"), - // ), - // ]; - // let merkle = merkle_build_test(items.clone())?; - - // let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") - // .expect("Decoding failed"); - // let end = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - // .expect("Decoding failed"); - - // let mut proof = merkle.prove(&start)?; - // assert!(!proof.0.is_empty()); - // let end_proof = merkle.prove(&end)?; - // assert!(!end_proof.0.is_empty()); - // proof.extend(end_proof); - - // let keys = items.iter().map(|item| item.0.as_ref()).collect(); - // let vals = items.iter().map(|item| item.1.clone()).collect(); - - // merkle.verify_range_proof(&proof, &start, &end, keys, vals)?; - - // Ok(()) - // } - - // #[test] - // // Tests a malicious proof, where the proof is more or less the - // // whole trie. This is to match corresponding test in geth. - // fn test_bloadted_range_proof() -> Result<(), ProofError> { - // // Use a small trie - // let mut items = Vec::new(); - // for i in 0..100_u32 { - // let mut key: [u8; 32] = [0; 32]; - // let mut value: [u8; 20] = [0; 20]; - // for (index, d) in i.to_be_bytes().iter().enumerate() { - // key[index] = *d; - // value[index] = *d; - // } - // items.push((key, value)); - // } - // let merkle = merkle_build_test(items.clone())?; - - // // In the 'malicious' case, we add proofs for every single item - // // (but only one key/value pair used as leaf) - // let mut proof = Proof(HashMap::new()); - // let mut keys = Vec::new(); - // let mut vals = Vec::new(); - // for (i, item) in items.iter().enumerate() { - // let cur_proof = merkle.prove(&item.0)?; - // assert!(!cur_proof.0.is_empty()); - // proof.extend(cur_proof); - // if i == 50 { - // keys.push(item.0.as_ref()); - // vals.push(item.1); - // } - // } - - // merkle.verify_range_proof(&proof, keys[0], keys[keys.len() - 1], keys.clone(), vals)?; - - // Ok(()) - // } - - // generate pseudorandom data, but prefix it with some known data - // The number of fixed data points is 100; you specify how much random data you want - // fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { - // let mut items: HashMap<[u8; 32], [u8; 20]> = HashMap::new(); - // for i in 0..100_u32 { - // let mut key: [u8; 32] = [0; 32]; - // let mut value: [u8; 20] = [0; 20]; - // for (index, d) in i.to_be_bytes().iter().enumerate() { - // key[index] = *d; - // value[index] = *d; - // } - // items.insert(key, value); - - // let mut more_key: [u8; 32] = [0; 32]; - // for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { - // more_key[index] = *d; - // } - // items.insert(more_key, value); - // } - - // // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. - // let seed = std::env::var("FIREWOOD_TEST_SEED") - // .ok() - // .map_or_else( - // || None, - // |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - // ) - // .unwrap_or_else(|| rng().random()); - - // // the test framework will only render this in verbose mode or if the test fails - // // to re-run the test when it fails, just specify the seed instead of randomly - // // selecting one - // eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - // let mut r = StdRng::seed_from_u64(seed); - // for _ in 0..random_count { - // let key = r.random::<[u8; 32]>(); - // let val = r.random::<[u8; 20]>(); - // items.insert(key, val); - // } - // items - // } - - // fn increase_key(key: &[u8; 32]) -> [u8; 32] { - // let mut new_key = *key; - // for ch in new_key.iter_mut().rev() { - // let overflow; - // (*ch, overflow) = ch.overflowing_add(1); - // if !overflow { - // break; - // } - // } - // new_key - // } - - // fn decrease_key(key: &[u8; 32]) -> [u8; 32] { - // let mut new_key = *key; - // for ch in new_key.iter_mut().rev() { - // let overflow; - // (*ch, overflow) = ch.overflowing_sub(1); - // if !overflow { - // break; - // } - // } - // new_key - // } - #[test] - fn remove_nonexistent_with_one() { - let items = vec![("do", "verb")]; - let mut merkle = merkle_build_test(items).unwrap(); - - assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); - assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); - } -} diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs new file mode 100644 index 000000000000..5f5f7276519b --- /dev/null +++ b/firewood/src/merkle/tests/ethhash.rs @@ -0,0 +1,187 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::*; +use ethereum_types::H256; +use hash_db::Hasher; +use plain_hasher::PlainHasher; +use sha3::{Digest, Keccak256}; +use test_case::test_case; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct KeccakHasher; + +impl KeccakHasher { + fn trie_root(items: I) -> H256 + where + I: IntoIterator, + K: AsRef<[u8]> + Ord, + V: AsRef<[u8]>, + { + firewood_triehash::trie_root::(items) + } +} + +impl Hasher for KeccakHasher { + type Out = H256; + type StdHasher = PlainHasher; + const LENGTH: usize = 32; + + #[inline] + fn hash(x: &[u8]) -> Self::Out { + let mut hasher = Keccak256::new(); + hasher.update(x); + let result = hasher.finalize(); + H256::from_slice(result.as_slice()) + } +} + +#[test_case([("doe", "reindeer")])] +#[test_case([("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cat")])] +#[test_case([("doe", "reindeer"),("dog", "puppy"),("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] +#[test_case([("dogglesworth", "cacatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatt")])] +fn test_root_hash_eth_compatible(kvs: I) +where + I: Clone + IntoIterator, + K: AsRef<[u8]> + Ord, + V: AsRef<[u8]>, +{ + let merkle = init_merkle(kvs.clone()); + let firewood_hash = merkle.nodestore.root_hash().unwrap_or_default(); + let eth_hash = KeccakHasher::trie_root(kvs).to_fixed_bytes().into(); + assert_eq!(firewood_hash, eth_hash); +} + +#[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[], + "c00ca9b8e6a74b03f6b1ae2db4a65ead348e61b74b339fe4b117e860d79c7821" + )] +#[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[ + ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000") + ], + "91336bf4e6756f68e1af0ad092f4a551c52b4a66860dc31adbd736f0acbadaf6" + )] +#[test_case( + "0000000000000000000000000000000000000002", + "f844802ca00000000000000000000000000000000000000000000000000000000000000000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + &[ + ("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5", "a00200000000000000000000000000000000000000000000000000000000000000"), + ("0e81f83a84964b811dd1b8328262a9f57e6bc3e5e7eb53627d10437c73c4b8da", "a02800000000000000000000000000000000000000000000000000000000000000"), + ], + "c267104830880c966c2cc8c669659e4bfaf3126558dbbd6216123b457944001b" + )] +fn test_eth_compatible_accounts( + account: &str, + account_value: &str, + key_suffixes_and_values: &[(&str, &str)], + expected_root: &str, +) { + use sha2::Digest as _; + use sha3::Keccak256; + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")) + .is_test(true) + .try_init() + .ok(); + + let account = make_key(account); + let expected_key_hash = Keccak256::digest(&account); + + let items = once(( + Box::from(expected_key_hash.as_slice()), + make_key(account_value), + )) + .chain(key_suffixes_and_values.iter().map(|(key_suffix, value)| { + let key = expected_key_hash + .iter() + .copied() + .chain(make_key(key_suffix).iter().copied()) + .collect(); + let value = make_key(value); + (key, value) + })) + .collect::, Box<_>)>>(); + + let merkle = init_merkle(items); + let firewood_hash = merkle.nodestore.root_hash(); + + assert_eq!( + firewood_hash, + TrieHash::try_from(&*make_key(expected_root)).ok() + ); +} + +/// helper method to convert a hex encoded string into a boxed slice +fn make_key(hex_str: &str) -> Key { + hex::decode(hex_str).unwrap().into_boxed_slice() +} + +#[test] +#[ignore = "broken tests not yet validated"] +fn test_root_hash_random_deletions() { + use rand::rngs::StdRng; + use rand::seq::SliceRandom; + use rand::{Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + ( + rng.random_range(1..=max_len0), + rng.random_range(1..=max_len1), + ) + }; + (0..len0) + .map(|_| rng.borrow_mut().random_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().random())) + .collect() + }; + + for i in 0..10 { + let mut items = std::collections::HashMap::::new(); + + for _ in 0..10 { + let val = (0..8).map(|_| rng.borrow_mut().random()).collect(); + items.insert(keygen(), val); + } + + let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + items_ordered.sort_unstable(); + items_ordered.shuffle(&mut *rng.borrow_mut()); + + let mut committed_merkle = init_merkle(&items); + + for (k, v) in items_ordered { + let mut merkle = committed_merkle.fork().unwrap(); + assert_eq!(merkle.get_value(&k).unwrap().as_deref(), Some(v.as_ref())); + + merkle.remove(&k).unwrap(); + + // assert_eq(None) and not assert(is_none) for better error messages + assert_eq!(merkle.get_value(&k).unwrap().as_deref(), None); + + items.remove(&k); + + for (k, v) in &items { + assert_eq!(merkle.get_value(k).unwrap().as_deref(), Some(v.as_ref())); + } + + committed_merkle = into_committed(merkle.hash(), committed_merkle.nodestore()); + + let h: TrieHash = KeccakHasher::trie_root(&items).to_fixed_bytes().into(); + + let h0 = committed_merkle.nodestore().root_hash().unwrap(); + + assert_eq!(h, h0); + } + + println!("i = {i}"); + } +} diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs new file mode 100644 index 000000000000..e483b06655bd --- /dev/null +++ b/firewood/src/merkle/tests/mod.rs @@ -0,0 +1,817 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![expect(clippy::indexing_slicing, clippy::unwrap_used)] + +#[cfg(feature = "ethhash")] +mod ethhash; +// TODO: get the hashes from merkledb and verify compatibility with branch factor 256 +#[cfg(not(any(feature = "ethhash", feature = "branch_factor_256")))] +mod triehash; +mod unvalidated; + +use std::collections::HashMap; + +use super::*; +use firewood_storage::{Committed, MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng, rng}; + +// Returns n random key-value pairs. +fn generate_random_kvs(seed: u64, n: usize) -> Vec<(Vec, Vec)> { + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + + let mut rng = StdRng::seed_from_u64(seed); + + let mut kvs: Vec<(Vec, Vec)> = Vec::new(); + for _ in 0..n { + let key_len = rng.random_range(1..=4096); + let key: Vec = (0..key_len).map(|_| rng.random()).collect(); + + let val_len = rng.random_range(1..=4096); + let val: Vec = (0..val_len).map(|_| rng.random()).collect(); + + kvs.push((key, val)); + } + + kvs +} + +fn into_committed( + merkle: Merkle, MemStore>>, + parent: &NodeStore, +) -> Merkle> { + let ns = merkle.into_inner(); + ns.flush_freelist().unwrap(); + ns.flush_header().unwrap(); + let mut ns = ns.as_committed(parent); + ns.flush_nodes().unwrap(); + ns.into() +} + +fn init_merkle(iter: I) -> Merkle> +where + I: Clone + IntoIterator, + K: AsRef<[u8]>, + V: AsRef<[u8]>, +{ + let memstore = Arc::new(MemStore::new(Vec::with_capacity(64 * 1024))); + let base = Merkle::from(NodeStore::new_empty_committed(memstore.clone()).unwrap()); + let mut merkle = base.fork().unwrap(); + + for (k, v) in iter.clone() { + let key = k.as_ref(); + let value = v.as_ref(); + + merkle.insert(key, value.into()).unwrap(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(value), + "Failed to insert key: {key:?}", + ); + } + + for (k, v) in iter.clone() { + let key = k.as_ref(); + let value = v.as_ref(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(value), + "Failed to get key after insert: {key:?}", + ); + } + + let merkle = merkle.hash(); + + for (k, v) in iter.clone() { + let key = k.as_ref(); + let value = v.as_ref(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(value), + "Failed to get key after hashing: {key:?}" + ); + } + + let merkle = into_committed(merkle, base.nodestore()); + + for (k, v) in iter { + let key = k.as_ref(); + let value = v.as_ref(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(value), + "Failed to get key after committing: {key:?}" + ); + } + + merkle +} + +// generate pseudorandom data, but prefix it with some known data +// The number of fixed data points is 100; you specify how much random data you want +#[expect(clippy::arithmetic_side_effects)] +fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { + let mut items = HashMap::new(); + for i in 0..100_u32 { + let mut key: [u8; 32] = [0; 32]; + let mut value: [u8; 20] = [0; 20]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + value[index] = *d; + } + items.insert(key, value); + + let mut more_key: [u8; 32] = [0; 32]; + for (index, d) in (i + 10).to_be_bytes().iter().enumerate() { + more_key[index] = *d; + } + items.insert(more_key, value); + } + + // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + // the test framework will only render this in verbose mode or if the test fails + // to re-run the test when it fails, just specify the seed instead of randomly + // selecting one + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let mut r = StdRng::seed_from_u64(seed); + for _ in 0..random_count { + let key = r.random::<[u8; 32]>(); + let val = r.random::<[u8; 20]>(); + items.insert(key, val); + } + items +} + +fn increase_key(key: &[u8; 32]) -> [u8; 32] { + let mut new_key = *key; + for ch in new_key.iter_mut().rev() { + let overflow; + (*ch, overflow) = ch.overflowing_add(1); + if !overflow { + break; + } + } + new_key +} + +fn decrease_key(key: &[u8; 32]) -> [u8; 32] { + let mut new_key = *key; + for ch in new_key.iter_mut().rev() { + let overflow; + (*ch, overflow) = ch.overflowing_sub(1); + if !overflow { + break; + } + } + new_key +} + +#[test] +fn test_get_regression() { + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&[0], Box::new([0])).unwrap(); + assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); + + merkle.insert(&[1], Box::new([1])).unwrap(); + assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); + + merkle.insert(&[2], Box::new([2])).unwrap(); + assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); + + let merkle = merkle.hash(); + + assert_eq!(merkle.get_value(&[0]).unwrap(), Some(Box::from([0]))); + assert_eq!(merkle.get_value(&[1]).unwrap(), Some(Box::from([1]))); + assert_eq!(merkle.get_value(&[2]).unwrap(), Some(Box::from([2]))); + + for result in merkle.path_iter(&[2]).unwrap() { + result.unwrap(); + } +} + +#[test] +fn insert_one() { + let mut merkle = create_in_memory_merkle(); + merkle.insert(b"abc", Box::new([])).unwrap(); +} + +fn create_in_memory_merkle() -> Merkle> { + let memstore = MemStore::new(vec![]); + + let nodestore = NodeStore::new_empty_proposal(memstore.into()); + + Merkle { nodestore } +} + +#[test] +fn test_insert_and_get() { + let mut merkle = create_in_memory_merkle(); + + // insert values + for key_val in u8::MIN..=u8::MAX { + let key = vec![key_val]; + let val = Box::new([key_val]); + + merkle.insert(&key, val.clone()).unwrap(); + + let fetched_val = merkle.get_value(&key).unwrap(); + + // make sure the value was inserted + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } + + // make sure none of the previous values were forgotten after initial insert + for key_val in u8::MIN..=u8::MAX { + let key = vec![key_val]; + let val = vec![key_val]; + + let fetched_val = merkle.get_value(&key).unwrap(); + + assert_eq!(fetched_val.as_deref(), val.as_slice().into()); + } +} + +#[test] +fn remove_root() { + let key0 = vec![0]; + let val0 = [0]; + let key1 = vec![0, 1]; + let val1 = [0, 1]; + let key2 = vec![0, 1, 2]; + let val2 = [0, 1, 2]; + let key3 = vec![0, 1, 15]; + let val3 = [0, 1, 15]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key0, Box::from(val0)).unwrap(); + merkle.insert(&key1, Box::from(val1)).unwrap(); + merkle.insert(&key2, Box::from(val2)).unwrap(); + merkle.insert(&key3, Box::from(val3)).unwrap(); + // Trie is: + // key0 + // | + // key1 + // / \ + // key2 key3 + + // Test removal of root when it's a branch with 1 branch child + let removed_val = merkle.remove(&key0).unwrap(); + assert_eq!(removed_val, Some(Box::from(val0))); + assert!(merkle.get_value(&key0).unwrap().is_none()); + // Removing an already removed key is a no-op + assert!(merkle.remove(&key0).unwrap().is_none()); + + // Trie is: + // key1 + // / \ + // key2 key3 + // Test removal of root when it's a branch with multiple children + assert_eq!(merkle.remove(&key1).unwrap(), Some(Box::from(val1))); + assert!(merkle.get_value(&key1).unwrap().is_none()); + assert!(merkle.remove(&key1).unwrap().is_none()); + + // Trie is: + // key1 (now has no value) + // / \ + // key2 key3 + let removed_val = merkle.remove(&key2).unwrap(); + assert_eq!(removed_val, Some(Box::from(val2))); + assert!(merkle.get_value(&key2).unwrap().is_none()); + assert!(merkle.remove(&key2).unwrap().is_none()); + + // Trie is: + // key3 + let removed_val = merkle.remove(&key3).unwrap(); + assert_eq!(removed_val, Some(Box::from(val3))); + assert!(merkle.get_value(&key3).unwrap().is_none()); + assert!(merkle.remove(&key3).unwrap().is_none()); + + assert!(merkle.nodestore.root_node().is_none()); +} + +#[test] +fn remove_prefix_exact() { + let mut merkle = two_byte_all_keys(); + for key_val in u8::MIN..=u8::MAX { + let key = [key_val]; + let got = merkle.remove_prefix(&key).unwrap(); + assert_eq!(got, 1); + let got = merkle.get_value(&key).unwrap(); + assert!(got.is_none()); + } +} + +fn two_byte_all_keys() -> Merkle> { + let mut merkle = create_in_memory_merkle(); + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let val = [key_val]; + + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + } + merkle +} + +#[test] +fn remove_prefix_all() { + let mut merkle = two_byte_all_keys(); + let got = merkle.remove_prefix(&[]).unwrap(); + assert_eq!(got, 256); +} + +#[test] +fn remove_prefix_partial() { + let mut merkle = create_in_memory_merkle(); + merkle + .insert(b"abc", Box::from(b"value".as_slice())) + .unwrap(); + merkle + .insert(b"abd", Box::from(b"value".as_slice())) + .unwrap(); + let got = merkle.remove_prefix(b"ab").unwrap(); + assert_eq!(got, 2); +} + +#[test] +fn remove_many() { + let mut merkle = create_in_memory_merkle(); + + // insert key-value pairs + for key_val in u8::MIN..=u8::MAX { + let key = [key_val]; + let val = [key_val]; + + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + } + + // remove key-value pairs + for key_val in u8::MIN..=u8::MAX { + let key = [key_val]; + let val = [key_val]; + + let got = merkle.remove(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + + // Removing an already removed key is a no-op + assert!(merkle.remove(&key).unwrap().is_none()); + + let got = merkle.get_value(&key).unwrap(); + assert!(got.is_none()); + } + assert!(merkle.nodestore.root_node().is_none()); +} + +#[test] +fn remove_prefix() { + let mut merkle = create_in_memory_merkle(); + + // insert key-value pairs + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let val = [key_val]; + + merkle.insert(&key, Box::new(val)).unwrap(); + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + } + + // remove key-value pairs with prefix [0] + let prefix = [0]; + assert_eq!(merkle.remove_prefix(&[0]).unwrap(), 1); + + // make sure all keys with prefix [0] were removed + for key_val in u8::MIN..=u8::MAX { + let key = [key_val, key_val]; + let got = merkle.get_value(&key).unwrap(); + if key[0] == prefix[0] { + assert!(got.is_none()); + } else { + assert!(got.is_some()); + } + } +} + +#[test] +fn get_empty_proof() { + let merkle = create_in_memory_merkle().hash(); + let proof = merkle.prove(b"any-key"); + assert!(matches!(proof.unwrap_err(), ProofError::Empty)); +} + +#[test] +fn single_key_proof() { + const TEST_SIZE: usize = 1; + + let mut merkle = create_in_memory_merkle(); + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + let kvs = generate_random_kvs(seed, TEST_SIZE); + + for (key, val) in &kvs { + merkle.insert(key, val.clone().into_boxed_slice()).unwrap(); + } + + let merkle = merkle.hash(); + + let root_hash = merkle.nodestore.root_hash().unwrap(); + + for (key, value) in kvs { + let proof = merkle.prove(&key).unwrap(); + + proof + .verify(key.clone(), Some(value.clone()), &root_hash) + .unwrap(); + + { + // Test that the proof is invalid when the value is different + let mut value = value.clone(); + value[0] = value[0].wrapping_add(1); + assert!(proof.verify(key.clone(), Some(value), &root_hash).is_err()); + } + + { + // Test that the proof is invalid when the hash is different + assert!( + proof + .verify(key, Some(value), &TrieHash::default()) + .is_err() + ); + } + } +} + +#[tokio::test] +async fn empty_range_proof() { + let merkle = create_in_memory_merkle(); + + assert!(matches!( + merkle.range_proof(None, None, None).await.unwrap_err(), + api::Error::RangeProofOnEmptyTrie + )); +} + +#[test] +fn test_insert_leaf_suffix() { + // key_2 is a suffix of key, which is a leaf + let key = vec![0xff]; + let val = [1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = [2]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + + let got = merkle.get_value(&key).unwrap().unwrap(); + + assert_eq!(*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); +} + +#[test] +fn test_insert_leaf_prefix() { + // key_2 is a prefix of key, which is a leaf + let key = vec![0xff, 0x00]; + let val = [1]; + let key_2 = vec![0xff]; + let val_2 = [2]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); +} + +#[test] +fn test_insert_sibling_leaf() { + // The node at key is a branch node with children key_2 and key_3. + // TODO assert in this test that key is the parent of key_2 and key_3. + // i.e. the node types are branch, leaf, leaf respectively. + let key = vec![0xff]; + let val = [1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = [2]; + let key_3 = vec![0xff, 0x0f]; + let val_3 = [3]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + merkle.insert(&key_3, Box::new(val_3)).unwrap(); + + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); + + let got = merkle.get_value(&key_3).unwrap().unwrap(); + assert_eq!(*got, val_3); +} + +#[test] +fn test_insert_branch_as_branch_parent() { + let key = vec![0xff, 0xf0]; + let val = [1]; + let key_2 = vec![0xff, 0xf0, 0x00]; + let val_2 = [2]; + let key_3 = vec![0xff]; + let val_3 = [3]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key, Box::new(val)).unwrap(); + // key is a leaf + + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + // key is branch with child key_2 + + merkle.insert(&key_3, Box::new(val_3)).unwrap(); + // key_3 is a branch with child key + // key is a branch with child key_3 + + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(&*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(&*got, val_2); + + let got = merkle.get_value(&key_3).unwrap().unwrap(); + assert_eq!(&*got, val_3); +} + +#[test] +fn test_insert_overwrite_branch_value() { + let key = vec![0xff]; + let val = [1]; + let key_2 = vec![0xff, 0x00]; + let val_2 = [2]; + let overwrite = [3]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(&key, Box::new(val)).unwrap(); + merkle.insert(&key_2, Box::new(val_2)).unwrap(); + + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, val); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); + + merkle.insert(&key, Box::new(overwrite)).unwrap(); + + let got = merkle.get_value(&key).unwrap().unwrap(); + assert_eq!(*got, overwrite); + + let got = merkle.get_value(&key_2).unwrap().unwrap(); + assert_eq!(*got, val_2); +} + +#[test] +fn test_delete_one_child_with_branch_value() { + let mut merkle = create_in_memory_merkle(); + // insert a parent with a value + merkle.insert(&[0], Box::new([42u8])).unwrap(); + // insert child1 with a value + merkle.insert(&[0, 1], Box::new([43u8])).unwrap(); + // insert child2 with a value + merkle.insert(&[0, 2], Box::new([44u8])).unwrap(); + + // now delete one of the children + let deleted = merkle.remove(&[0, 1]).unwrap(); + assert_eq!(deleted, Some([43u8].to_vec().into_boxed_slice())); + + // make sure the parent still has the correct value + let got = merkle.get_value(&[0]).unwrap().unwrap(); + assert_eq!(*got, [42u8]); + + // and check the remaining child + let other_child = merkle.get_value(&[0, 2]).unwrap().unwrap(); + assert_eq!(*other_child, [44u8]); +} + +#[test] +fn test_root_hash_simple_insertions() -> Result<(), Error> { + init_merkle([ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]) + .dump() + .unwrap(); + Ok(()) +} + +#[test] +fn test_root_hash_fuzz_insertions() -> Result<(), FileIoError> { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + ( + rng.random_range(1..=max_len0), + rng.random_range(1..=max_len1), + ) + }; + let key: Vec = (0..len0) + .map(|_| rng.borrow_mut().random_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().random())) + .collect(); + key + }; + + // TODO: figure out why this fails if we use more than 27 iterations with branch_factor_256 + for _ in 0..27 { + let mut items = Vec::new(); + + for _ in 0..100 { + let val: Vec = (0..256).map(|_| rng.borrow_mut().random()).collect(); + items.push((keygen(), val)); + } + + init_merkle(items); + } + + Ok(()) +} + +#[test] +fn test_delete_child() { + let items = vec![("do", "verb")]; + let merkle = init_merkle(items); + let mut merkle = merkle.fork().unwrap(); + + assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); + assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); +} + +#[test] +fn test_delete_some() { + let items = (0..100) + .map(|n| { + let key = format!("key{n}"); + let val = format!("value{n}"); + (key.as_bytes().to_vec(), val.as_bytes().to_vec()) + }) + .collect::, Vec)>>(); + let mut merkle = init_merkle(items.clone()).fork().unwrap(); + merkle.remove_prefix(b"key1").unwrap(); + for item in items { + let (key, val) = item; + if key.starts_with(b"key1") { + assert!(merkle.get_value(&key).unwrap().is_none()); + } else { + assert_eq!(&*merkle.get_value(&key).unwrap().unwrap(), val.as_slice()); + } + } +} + +#[test] +fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + + let _ = env_logger::Builder::new().is_test(true).try_init(); + + let seed = std::env::var("FIREWOOD_TEST_SEED") + .ok() + .map_or_else( + || None, + |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), + ) + .unwrap_or_else(|| rng().random()); + + eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); + let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); + + let max_len0 = 8; + let max_len1 = 4; + let keygen = || { + let (len0, len1): (usize, usize) = { + let mut rng = rng.borrow_mut(); + ( + rng.random_range(1..=max_len0), + rng.random_range(1..=max_len1), + ) + }; + (0..len0) + .map(|_| rng.borrow_mut().random_range(0..2)) + .chain((0..len1).map(|_| rng.borrow_mut().random())) + .collect() + }; + + for _ in 0..10 { + let mut items: Vec<(Key, Value)> = (0..10) + .map(|_| keygen()) + .map(|key| { + let val = (0..8).map(|_| rng.borrow_mut().random()).collect(); + (key, val) + }) + .collect(); + + items.sort_unstable(); + items.dedup_by_key(|(k, _)| k.clone()); + + let init_merkle = create_in_memory_merkle(); + let init_immutable_merkle = init_merkle.hash(); + + let (hashes, complete_immutable_merkle) = items.iter().fold( + (vec![], init_immutable_merkle), + |(mut hashes, immutable_merkle), (k, v)| { + let root_hash = immutable_merkle.nodestore.root_hash(); + hashes.push(root_hash); + let mut merkle = immutable_merkle.fork().unwrap(); + merkle.insert(k, v.clone()).unwrap(); + (hashes, merkle.hash()) + }, + ); + + let (new_hashes, _) = items.iter().rev().fold( + (vec![], complete_immutable_merkle), + |(mut new_hashes, immutable_merkle_before_removal), (k, _)| { + let before = immutable_merkle_before_removal.dump().unwrap(); + let mut merkle = Merkle::from( + NodeStore::new(immutable_merkle_before_removal.nodestore()).unwrap(), + ); + merkle.remove(k).unwrap(); + let immutable_merkle_after_removal: Merkle, _>> = + merkle.try_into().unwrap(); + new_hashes.push(( + immutable_merkle_after_removal.nodestore.root_hash(), + k, + before, + immutable_merkle_after_removal.dump().unwrap(), + )); + (new_hashes, immutable_merkle_after_removal) + }, + ); + + for (expected_hash, (actual_hash, key, before_removal, after_removal)) in + hashes.into_iter().rev().zip(new_hashes) + { + let key = key.iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "{b:02x}"); + s + }); + assert_eq!( + actual_hash, expected_hash, + "\n\nkey: {key}\nbefore:\n{before_removal}\nafter:\n{after_removal}\n\nexpected:\n{expected_hash:?}\nactual:\n{actual_hash:?}\n", + ); + } + } + + Ok(()) +} + +#[test] +fn remove_nonexistent_with_one() { + let items = [("do", "verb")]; + let mut merkle = init_merkle(items).fork().unwrap(); + + assert_eq!(merkle.remove(b"does_not_exist").unwrap(), None); + assert_eq!(&*merkle.get_value(b"do").unwrap().unwrap(), b"verb"); +} diff --git a/firewood/src/merkle/tests/triehash.rs b/firewood/src/merkle/tests/triehash.rs new file mode 100644 index 000000000000..3e2a2a1e623b --- /dev/null +++ b/firewood/src/merkle/tests/triehash.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::*; +use test_case::test_case; + +#[test_case(vec![], None; "empty trie")] +#[test_case(vec![(&[0],&[0])], Some("073615413d814b23383fc2c8d8af13abfffcb371b654b98dbf47dd74b1e4d1b9"); "root")] +#[test_case(vec![(&[0,1],&[0,1])], Some("28e67ae4054c8cdf3506567aa43f122224fe65ef1ab3e7b7899f75448a69a6fd"); "root with partial path")] +#[test_case(vec![(&[0],&[1;32])], Some("ba0283637f46fa807280b7d08013710af08dfdc236b9b22f9d66e60592d6c8a3"); "leaf value >= 32 bytes")] +#[test_case(vec![(&[0],&[0]),(&[0,1],&[1;32])], Some("3edbf1fdd345db01e47655bcd0a9a456857c4093188cf35c5c89b8b0fb3de17e"); "branch value >= 32 bytes")] +#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")] +#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")] +#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")] +fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) { + let merkle = init_merkle(kvs); + let Some(got_hash) = merkle.nodestore.root_hash() else { + assert!(expected_hash.is_none()); + return; + }; + + let expected_hash = expected_hash.unwrap(); + + // This hash is from merkledb + let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap(); + + assert_eq!(got_hash, TrieHash::from(expected_hash)); +} diff --git a/firewood/src/merkle/tests/unvalidated.rs b/firewood/src/merkle/tests/unvalidated.rs new file mode 100644 index 000000000000..fcb2ad46aa8a --- /dev/null +++ b/firewood/src/merkle/tests/unvalidated.rs @@ -0,0 +1,1192 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::*; +use crate::range_proof::RangeProof; + +type KeyValuePairs = Vec<(Box<[u8]>, Box<[u8]>)>; + +#[tokio::test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +async fn range_proof_invalid_bounds() { + let merkle = create_in_memory_merkle().hash(); + + let start_key = &[0x01]; + let end_key = &[0x00]; + + match merkle + .range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(1)) + .await + { + Err(api::Error::InvalidRange { + start_key: first_key, + end_key: last_key, + }) if *first_key == *start_key && *last_key == *end_key => (), + Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), + _ => panic!("expected InvalidRange error"), + } +} + +#[tokio::test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +async fn full_range_proof() { + let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); + + let rangeproof = merkle.range_proof(None, None, None).await.unwrap(); + assert_eq!(rangeproof.key_values().len(), u8::MAX as usize + 1); + assert_ne!(rangeproof.start_proof(), rangeproof.end_proof()); + let left_proof = merkle.prove(&[u8::MIN]).unwrap(); + let right_proof = merkle.prove(&[u8::MAX]).unwrap(); + assert_eq!(rangeproof.start_proof(), &left_proof); + assert_eq!(rangeproof.end_proof(), &right_proof); +} + +#[tokio::test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +async fn single_value_range_proof() { + const RANDOM_KEY: u8 = 42; + + let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); + + let rangeproof = merkle + .range_proof(Some(&[RANDOM_KEY]), None, NonZeroUsize::new(1)) + .await + .unwrap(); + assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); + assert_eq!(rangeproof.key_values().len(), 1); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn shared_path_proof() { + let key1 = b"key1"; + let value1 = b"1"; + + let key2 = b"key2"; + let value2 = b"2"; + + let merkle = init_merkle([(key1, value1), (key2, value2)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = key1; + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value1), &root_hash).unwrap(); + + let key = key2; + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value2), &root_hash).unwrap(); +} + +// this was a specific failing case +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn shared_path_on_insert() { + init_merkle([ + ( + &[1, 1, 46, 82, 67, 218][..], + &[23, 252, 128, 144, 235, 202, 124, 243][..], + ), + ( + &[1, 0, 0, 1, 1, 0, 63, 80], + &[99, 82, 31, 213, 180, 196, 49, 242], + ), + ( + &[0, 0, 0, 169, 176, 15], + &[105, 211, 176, 51, 231, 182, 74, 207], + ), + ( + &[1, 0, 0, 0, 53, 57, 93], + &[234, 139, 214, 220, 172, 38, 168, 164], + ), + ]); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn overwrite_leaf() { + let key = &[0x00]; + let val = &[1]; + let overwrite = &[2]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(key, val[..].into()).unwrap(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(val.as_slice()) + ); + + merkle.insert(key, overwrite[..].into()).unwrap(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(overwrite.as_slice()) + ); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn single_key_proof_with_one_node() { + let key = b"key"; + let value = b"value"; + + let merkle = init_merkle([(key, value)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value), &root_hash).unwrap(); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn two_key_proof_without_shared_path() { + let key1 = &[0x00]; + let key2 = &[0xff]; + + let merkle = init_merkle([(key1, key1), (key2, key2)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let proof = merkle.prove(key1).unwrap(); + proof.verify(key1, Some(key1), &root_hash).unwrap(); + + let proof = merkle.prove(key2).unwrap(); + proof.verify(key2, Some(key2), &root_hash).unwrap(); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_proof() { + let set = fixed_and_pseudorandom_data(500); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + for (key, val) in items { + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + proof.verify(key, Some(val), &root_hash).unwrap(); + } +} + +#[test] +/// Verify the proofs that end with leaf node with the given key. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_proof_end_with_leaf() { + let merkle = init_merkle([ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = b"doe"; + + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + proof.verify(key, Some(b"reindeer"), &root_hash).unwrap(); +} + +#[test] +/// Verify the proofs that end with branch node with the given key. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_proof_end_with_branch() { + let items = [ + ("d", "verb"), + ("do", "verb"), + ("doe", "reindeer"), + ("e", "coin"), + ]; + let merkle = init_merkle(items); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = b"d"; + + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + proof.verify(key, Some(b"verb"), &root_hash).unwrap(); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_bad_proof() { + let set = fixed_and_pseudorandom_data(800); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + for (key, value) in items { + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + // Delete an entry from the generated proofs. + let mut new_proof = proof.into_mutable(); + new_proof.pop(); + + // TODO: verify error result matches expected error + assert!(new_proof.verify(key, Some(value), &root_hash).is_err()); + } +} + +#[test] +// Tests that missing keys can also be proven. The test explicitly uses a single +// entry trie and checks for missing keys both before and after the single entry. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_missing_key_proof() { + let items = [("k", "v")]; + let merkle = init_merkle(items); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + for key in ["a", "j", "l", "z"] { + let proof = merkle.prove(key.as_ref()).unwrap(); + assert!(!proof.is_empty()); + assert!(proof.len() == 1); + + proof.verify(key, None::<&[u8]>, &root_hash).unwrap(); + } +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_empty_tree_proof() { + let items: Vec<(&str, &str)> = Vec::new(); + let merkle = init_merkle(items); + let key = "x".as_ref(); + + let proof_err = merkle.prove(key).unwrap_err(); + assert!(matches!(proof_err, ProofError::Empty), "{proof_err:?}"); +} + +#[test] +// Tests normal range proof with both edge proofs as the existent proof. +// The test cases are generated randomly. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_range_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + for _ in 0..10 { + let start = rand::rng().random_range(0..items.len()); + let end = rand::rng().random_range(0..items.len() - start) + start - 1; + + if end <= start { + continue; + } + + let start_proof = merkle.prove(items[start].0).unwrap(); + let end_proof = merkle.prove(items[end - 1].0).unwrap(); + + let key_values: KeyValuePairs = items[start..end] + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[end - 1].0), + &root_hash, + &range_proof, + ) + .unwrap(); + } +} + +#[test] +// Tests a few cases which the proof is wrong. +// The prover is expected to detect the error. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_bad_range_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + for _ in 0..10 { + let start = rand::rng().random_range(0..items.len()); + let end = rand::rng().random_range(0..items.len() - start) + start - 1; + + if end <= start { + continue; + } + + let _proof = merkle + .prove(items[start].0) + .unwrap() + .join(merkle.prove(items[end - 1].0).unwrap()); + + let mut keys: Vec<[u8; 32]> = Vec::new(); + let mut vals: Vec<[u8; 20]> = Vec::new(); + for item in &items[start..end] { + keys.push(*item.0); + vals.push(*item.1); + } + + let test_case: u32 = rand::rng().random_range(0..6); + let index = rand::rng().random_range(0..end - start); + match test_case { + 0 => { + // Modified key + keys[index] = rand::rng().random::<[u8; 32]>(); // In theory it can't be same + } + 1 => { + // Modified val + vals[index] = rand::rng().random::<[u8; 20]>(); // In theory it can't be same + } + 2 => { + // Gapped entry slice + if index == 0 || index == end - start - 1 { + continue; + } + keys.remove(index); + vals.remove(index); + } + 3 => { + // Out of order + let index_1 = rand::rng().random_range(0..end - start); + let index_2 = rand::rng().random_range(0..end - start); + if index_1 == index_2 { + continue; + } + keys.swap(index_1, index_2); + vals.swap(index_1, index_2); + } + 4 => { + // Set random key to empty, do nothing + keys[index] = [0; 32]; + } + 5 => { + // Set random value to nil + vals[index] = [0; 20]; + } + _ => unreachable!(), + } + + let key_values: KeyValuePairs = keys + .iter() + .zip(vals.iter()) + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let start_proof = merkle.prove(items[start].0).unwrap(); + let end_proof = merkle.prove(items[end - 1].0).unwrap(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[end - 1].0), + &root_hash, + &range_proof, + ) + .is_err() + ); + } +} + +#[test] +// Tests normal range proof with two non-existent proofs. +// The test cases are generated randomly. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_range_proof_with_non_existent_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + for _ in 0..10 { + let start = rand::rng().random_range(0..items.len()); + let end = rand::rng().random_range(0..items.len() - start) + start - 1; + + if end <= start { + continue; + } + + // Short circuit if the decreased key is same with the previous key + let first = decrease_key(items[start].0); + if start != 0 && first.as_ref() == items[start - 1].0.as_ref() { + continue; + } + // Short circuit if the decreased key is underflow + if &first > items[start].0 { + continue; + } + // Short circuit if the increased key is same with the next key + let last = increase_key(items[end - 1].0); + if end != items.len() && last.as_ref() == items[end].0.as_ref() { + continue; + } + // Short circuit if the increased key is overflow + if &last < items[end - 1].0 { + continue; + } + + let start_proof = merkle.prove(&first).unwrap(); + let end_proof = merkle.prove(&last).unwrap(); + + let key_values: KeyValuePairs = items[start..end] + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(&first), Some(&last), &root_hash, &range_proof) + .unwrap(); + } + + // Special case, two edge proofs for two edge key. + let first = &[0; 32]; + let last = &[255; 32]; + + let start_proof = merkle.prove(first).unwrap(); + let end_proof = merkle.prove(last).unwrap(); + + let key_values: KeyValuePairs = items + .into_iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(first), Some(last), &root_hash, &range_proof) + .unwrap(); +} + +#[test] +// Tests such scenarios: +// - There exists a gap between the first element and the left edge proof +// - There exists a gap between the last element and the right edge proof +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_range_proof_with_invalid_non_existent_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + // Case 1 + let mut start = 100; + let mut end = 200; + let first = decrease_key(items[start].0); + + let start_proof = merkle.prove(&first).unwrap(); + let end_proof = merkle.prove(items[end - 1].0).unwrap(); + + start = 105; // Gap created + let key_values: KeyValuePairs = items[start..end] + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(&first), + Some(items[end - 1].0), + &root_hash, + &range_proof, + ) + .is_err() + ); + + // Case 2 + start = 100; + end = 200; + let last = increase_key(items[end - 1].0); + + let start_proof_2 = merkle.prove(items[start].0).unwrap(); + let end_proof_2 = merkle.prove(&last).unwrap(); + + end = 195; // Capped slice + let key_values_2: KeyValuePairs = items[start..end] + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof_2 = + RangeProof::new(start_proof_2, end_proof_2, key_values_2.into_boxed_slice()); + + let root_hash_2 = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(items[start].0), + Some(&last), + &root_hash_2, + &range_proof_2, + ) + .is_err() + ); +} + +#[test] +// Tests the proof with only one element. The first edge proof can be existent one or +// non-existent one. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_one_element_range_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + // One element with existent edge proof, both edge proofs + // point to the SAME key. + let start = 1000; + let proof = merkle.prove(items[start].0).unwrap(); + assert!(!proof.is_empty()); + + let key_values = vec![( + items[start].0.to_vec().into_boxed_slice(), + items[start].1.to_vec().into_boxed_slice(), + )]; + + let range_proof = RangeProof::new( + proof.clone(), // Same proof for start and end + proof, + key_values.into_boxed_slice(), + ); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[start].0), + &root_hash, + &range_proof, + ) + .unwrap(); + + // One element with left non-existent edge proof + let first = decrease_key(items[start].0); + let start_proof_2 = merkle.prove(&first).unwrap(); + let end_proof_2 = merkle.prove(items[start].0).unwrap(); + + let key_values_2 = vec![( + items[start].0.to_vec().into_boxed_slice(), + items[start].1.to_vec().into_boxed_slice(), + )]; + + let range_proof_2 = + RangeProof::new(start_proof_2, end_proof_2, key_values_2.into_boxed_slice()); + + merkle + .verify_range_proof( + Some(&first), + Some(items[start].0), + &root_hash, + &range_proof_2, + ) + .unwrap(); + + // One element with right non-existent edge proof + let last = increase_key(items[start].0); + let start_proof_3 = merkle.prove(items[start].0).unwrap(); + let end_proof_3 = merkle.prove(&last).unwrap(); + + let key_values_3 = vec![( + items[start].0.to_vec().into_boxed_slice(), + items[start].1.to_vec().into_boxed_slice(), + )]; + + let range_proof_3 = + RangeProof::new(start_proof_3, end_proof_3, key_values_3.into_boxed_slice()); + + merkle + .verify_range_proof( + Some(items[start].0), + Some(&last), + &root_hash, + &range_proof_3, + ) + .unwrap(); + + // One element with two non-existent edge proofs + let start_proof_4 = merkle.prove(&first).unwrap(); + let end_proof_4 = merkle.prove(&last).unwrap(); + + let key_values_4 = vec![( + items[start].0.to_vec().into_boxed_slice(), + items[start].1.to_vec().into_boxed_slice(), + )]; + + let range_proof_4 = + RangeProof::new(start_proof_4, end_proof_4, key_values_4.into_boxed_slice()); + + merkle + .verify_range_proof(Some(&first), Some(&last), &root_hash, &range_proof_4) + .unwrap(); + + // Test the mini trie with only a single element. + let key = rand::rng().random::<[u8; 32]>(); + let val = rand::rng().random::<[u8; 20]>(); + let merkle_mini = init_merkle(vec![(key, val)]); + + let first = &[0; 32]; + let start_proof_5 = merkle_mini.prove(first).unwrap(); + let end_proof_5 = merkle_mini.prove(&key).unwrap(); + + let key_values_5 = vec![( + key.to_vec().into_boxed_slice(), + val.to_vec().into_boxed_slice(), + )]; + + let range_proof_5 = + RangeProof::new(start_proof_5, end_proof_5, key_values_5.into_boxed_slice()); + + let root_hash_mini = merkle_mini.nodestore().root_hash().unwrap(); + + merkle_mini + .verify_range_proof(Some(first), Some(&key), &root_hash_mini, &range_proof_5) + .unwrap(); +} + +#[test] +// Tests the range proof with all elements. +// The edge proofs can be nil. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_all_elements_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let item_iter = items.clone().into_iter(); + let _keys: Vec<&[u8]> = item_iter.clone().map(|item| item.0.as_ref()).collect(); + let _vals: Vec<&[u8; 20]> = item_iter.map(|item| item.1).collect(); + + let empty_proof = Proof::empty(); + let empty_key: [u8; 32] = [0; 32]; + + let key_values: KeyValuePairs = items + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new( + empty_proof.clone(), + empty_proof, + key_values.into_boxed_slice(), + ); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(&empty_key), Some(&empty_key), &root_hash, &range_proof) + .unwrap(); + + // With edge proofs, it should still work. + let start = 0; + let end = &items.len() - 1; + let start_proof_2 = merkle.prove(items[start].0).unwrap(); + let end_proof_2 = merkle.prove(items[end].0).unwrap(); + + let key_values_2: KeyValuePairs = items + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof_2 = + RangeProof::new(start_proof_2, end_proof_2, key_values_2.into_boxed_slice()); + + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[end].0), + &root_hash, + &range_proof_2, + ) + .unwrap(); + + // Even with non-existent edge proofs, it should still work. + let first = &[0; 32]; + let last = &[255; 32]; + let start_proof_3 = merkle.prove(first).unwrap(); + let end_proof_3 = merkle.prove(last).unwrap(); + + let key_values_3: KeyValuePairs = items + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof_3 = + RangeProof::new(start_proof_3, end_proof_3, key_values_3.into_boxed_slice()); + + merkle + .verify_range_proof(Some(first), Some(last), &root_hash, &range_proof_3) + .unwrap(); +} + +#[test] +// Tests the range proof with "no" element. The first edge proof must +// be a non-existent proof. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_empty_range_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let cases = [(items.len() - 1, false)]; + for c in &cases { + let first = increase_key(items[c.0].0); + let proof = merkle.prove(&first).unwrap(); + assert!(!proof.is_empty()); + + // key and value vectors are intentionally empty. + let key_values: KeyValuePairs = Vec::new(); + + let range_proof = RangeProof::new(proof.clone(), proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + if c.1 { + assert!( + merkle + .verify_range_proof(Some(&first), Some(&first), &root_hash, &range_proof,) + .is_err() + ); + } else { + merkle + .verify_range_proof(Some(&first), Some(&first), &root_hash, &range_proof) + .unwrap(); + } + } +} + +#[test] +// Focuses on the small trie with embedded nodes. If the gapped +// node is embedded in the trie, it should be detected too. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_gapped_range_proof() { + let mut items = Vec::new(); + // Sorted entries + for i in 0..10_u32 { + let mut key = [0; 32]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + } + items.push((key, i.to_be_bytes())); + } + let merkle = init_merkle(items.clone()); + + let first = 2; + let last = 8; + let start_proof = merkle.prove(&items[first].0).unwrap(); + let end_proof = merkle.prove(&items[last - 1].0).unwrap(); + + let middle = usize::midpoint(first, last) - first; + let key_values: KeyValuePairs = items[first..last] + .iter() + .enumerate() + .filter(|(pos, _)| *pos != middle) + .map(|(_, item)| { + ( + item.0.to_vec().into_boxed_slice(), + item.1.to_vec().into_boxed_slice(), + ) + }) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(&items[0].0), + Some(&items[items.len() - 1].0), + &root_hash, + &range_proof, + ) + .is_err() + ); +} + +#[test] +// Tests the element is not in the range covered by proofs. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_same_side_proof() { + let set = fixed_and_pseudorandom_data(4096); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let pos = 1000; + let mut last = decrease_key(items[pos].0); + let mut first = last; + first = decrease_key(&first); + + let start_proof = merkle.prove(&first).unwrap(); + let end_proof = merkle.prove(&last).unwrap(); + + let key_values = vec![( + items[pos].0.to_vec().into_boxed_slice(), + items[pos].1.to_vec().into_boxed_slice(), + )]; + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof(Some(&first), Some(&last), &root_hash, &range_proof,) + .is_err() + ); + + first = increase_key(items[pos].0); + last = first; + last = increase_key(&last); + + let start_proof_2 = merkle.prove(&first).unwrap(); + let end_proof_2 = merkle.prove(&last).unwrap(); + + let key_values_2 = vec![( + items[pos].0.to_vec().into_boxed_slice(), + items[pos].1.to_vec().into_boxed_slice(), + )]; + + let range_proof_2 = + RangeProof::new(start_proof_2, end_proof_2, key_values_2.into_boxed_slice()); + + assert!( + merkle + .verify_range_proof(Some(&first), Some(&last), &root_hash, &range_proof_2,) + .is_err() + ); +} + +#[test] +// Tests the range starts from zero. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_single_side_range_proof() { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..4096_u32 { + let key = rand::rng().random::<[u8; 32]>(); + let val = rand::rng().random::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let cases = vec![0, 1, 100, 1000, items.len() - 1]; + for case in cases { + let start = &[0; 32]; + let start_proof = merkle.prove(start).unwrap(); + let end_proof = merkle.prove(items[case].0).unwrap(); + + let key_values: KeyValuePairs = items + .iter() + .take(case + 1) + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = + RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(start), Some(items[case].0), &root_hash, &range_proof) + .unwrap(); + } + } +} + +#[test] +// Tests the range ends with 0xffff...fff. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_reverse_single_side_range_proof() { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..1024_u32 { + let key = rand::rng().random::<[u8; 32]>(); + let val = rand::rng().random::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let cases = vec![0, 1, 100, 1000, items.len() - 1]; + for case in cases { + let end = &[255; 32]; + + let start_proof = merkle.prove(items[case].0).unwrap(); + let end_proof = merkle.prove(end).unwrap(); + + let key_values: KeyValuePairs = items + .iter() + .skip(case) + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = + RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(items[case].0), Some(end), &root_hash, &range_proof) + .unwrap(); + } + } +} + +#[test] +// Tests the range starts with zero and ends with 0xffff...fff. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_both_sides_range_proof() { + for _ in 0..10 { + let mut set = HashMap::new(); + for _ in 0..4096_u32 { + let key = rand::rng().random::<[u8; 32]>(); + let val = rand::rng().random::<[u8; 20]>(); + set.insert(key, val); + } + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let start = &[0; 32]; + let end = &[255; 32]; + let start_proof = merkle.prove(start).unwrap(); + let end_proof = merkle.prove(end).unwrap(); + + let key_values: KeyValuePairs = items + .into_iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(start), Some(end), &root_hash, &range_proof) + .unwrap(); + } +} + +#[test] +// Tests normal range proof with both edge proofs +// as the existent proof, but with an extra empty value included, which is a +// noop technically, but practically should be rejected. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_empty_value_range_proof() { + let set = fixed_and_pseudorandom_data(512); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + // Create a new entry with a slightly modified key + let mid_index = items.len() / 2; + let key = increase_key(items[mid_index - 1].0); + let empty_value: [u8; 20] = [0; 20]; + items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().copied()); + + let start = 1; + let end = items.len() - 1; + + let start_proof = merkle.prove(items[start].0).unwrap(); + let end_proof = merkle.prove(items[end - 1].0).unwrap(); + + let key_values: KeyValuePairs = items + .iter() + .skip(start) + .take(end - start) + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[end - 1].0), + &root_hash, + &range_proof, + ) + .is_err() + ); +} + +#[test] +// Tests the range proof with all elements, +// but with an extra empty value included, which is a noop technically, but +// practically should be rejected. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_all_elements_empty_value_range_proof() { + let set = fixed_and_pseudorandom_data(512); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + // Create a new entry with a slightly modified key + let mid_index = items.len() / 2; + let key = increase_key(items[mid_index - 1].0); + let empty_value: [u8; 20] = [0; 20]; + items.splice(mid_index..mid_index, [(&key, &empty_value)].iter().copied()); + + let start = 0; + let end = items.len() - 1; + + let start_proof = merkle.prove(items[start].0).unwrap(); + let end_proof = merkle.prove(items[end].0).unwrap(); + + let key_values: KeyValuePairs = items + .iter() + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + assert!( + merkle + .verify_range_proof( + Some(items[start].0), + Some(items[end].0), + &root_hash, + &range_proof, + ) + .is_err() + ); +} + +#[test] +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_range_proof_keys_with_shared_prefix() { + let items = vec![ + ( + hex::decode("aa10000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("02").expect("Decoding failed"), + ), + ( + hex::decode("aa20000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"), + hex::decode("03").expect("Decoding failed"), + ), + ]; + let merkle = init_merkle(items.clone()); + + let start = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + .expect("Decoding failed"); + let end = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .expect("Decoding failed"); + + let start_proof = merkle.prove(&start).unwrap(); + let end_proof = merkle.prove(&end).unwrap(); + + let key_values: KeyValuePairs = items + .iter() + .map(|(k, v)| (k.clone().into_boxed_slice(), v.clone().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof(Some(&start), Some(&end), &root_hash, &range_proof) + .unwrap(); +} + +#[test] +// Tests a malicious proof, where the proof is more or less the +// whole trie. This is to match corresponding test in geth. +#[ignore = "https://github.com/ava-labs/firewood/issues/738"] +fn test_bloadted_range_proof() { + // Use a small trie + let mut items = Vec::new(); + for i in 0..100_u32 { + let mut key: [u8; 32] = [0; 32]; + let mut value: [u8; 20] = [0; 20]; + for (index, d) in i.to_be_bytes().iter().enumerate() { + key[index] = *d; + value[index] = *d; + } + items.push((key, value)); + } + let merkle = init_merkle(items.clone()); + + // In the 'malicious' case, we add proofs for every single item + // (but only one key/value pair used as leaf) + let mut proof = Proof::empty().into_mutable(); + let mut keys = Vec::new(); + let mut vals = Vec::new(); + for (i, item) in items.iter().enumerate() { + let cur_proof = merkle.prove(&item.0).unwrap(); + assert!(!cur_proof.is_empty()); + proof.extend(cur_proof); + if i == 50 { + keys.push(item.0.as_ref()); + vals.push(item.1); + } + } + + // Create start and end proofs (same key in this case since only one key-value pair) + let start_proof = merkle.prove(keys[0]).unwrap(); + let end_proof = merkle.prove(keys[keys.len() - 1]).unwrap(); + + // Convert to the format expected by RangeProof + let key_values: KeyValuePairs = keys + .iter() + .zip(vals.iter()) + .map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) + .collect(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values.into_boxed_slice()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + merkle + .verify_range_proof( + Some(keys[0]), + Some(keys[keys.len() - 1]), + &root_hash, + &range_proof, + ) + .unwrap(); +} From 642209cbae447a2b2157baab0b1dafa967472157 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 30 Jul 2025 08:43:15 -0700 Subject: [PATCH 0863/1053] feat: update range_proof signature (#1151) - To require a NonZeroUsize limit - To no longer be test-only - To be used by some API implementations (the easy ones) Also fixed a minor potential recursive issue in `Proposal::val`. Recursive boxed futures cause a problem during polling where the async engine needs to traverse several layers of boxed futures to get to the one that is actually working. Depends On: #1150 --- firewood/src/db.rs | 33 ++++++++++++++++++++---------- firewood/src/merkle.rs | 14 ++----------- firewood/src/v2/api.rs | 7 ++++--- firewood/src/v2/emptydb.rs | 10 ++++++---- firewood/src/v2/propose.rs | 41 +++++++++++++++++++++----------------- 5 files changed, 58 insertions(+), 47 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index ede8d78be4a4..fcb0e6d739ec 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -19,6 +19,7 @@ use firewood_storage::{ }; use metrics::{counter, describe_counter}; use std::io::Write; +use std::num::NonZeroUsize; use std::path::Path; use std::sync::Arc; use std::sync::atomic::AtomicBool; @@ -111,13 +112,19 @@ impl api::DbView for HistoricalRev { merkle.prove(key.as_ref()).map_err(api::Error::from) } - async fn range_proof( + async fn range_proof( &self, - _first_key: Option, - _last_key: Option, - _limit: Option, + first_key: Option, + last_key: Option, + limit: Option, ) -> Result { - todo!() + Merkle::from(self) + .range_proof( + first_key.as_ref().map(AsRef::as_ref), + last_key.as_ref().map(AsRef::as_ref), + limit, + ) + .await } fn iter_option( @@ -389,13 +396,19 @@ impl api::DbView for Proposal<'_> { merkle.prove(key.as_ref()).map_err(api::Error::from) } - async fn range_proof( + async fn range_proof( &self, - _first_key: Option, - _last_key: Option, - _limit: Option, + first_key: Option, + last_key: Option, + limit: Option, ) -> Result { - todo!() + Merkle::from(&self.nodestore) + .range_proof( + first_key.as_ref().map(AsRef::as_ref), + last_key.as_ref().map(AsRef::as_ref), + limit, + ) + .await } fn iter_option( diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 9038770de307..263f252b0a3f 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -6,27 +6,20 @@ mod tests; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; -#[cfg(test)] -use crate::stream::MerkleKeyValueStream; -use crate::stream::PathIterator; -#[cfg(test)] -use crate::v2::api::FrozenRangeProof; -use crate::v2::api::{self, FrozenProof, KeyType, ValueType}; +use crate::stream::{MerkleKeyValueStream, PathIterator}; +use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, HashedNodeReader, ImmutableProposal, IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, Parentable, Path, ReadableStorage, SharedNode, TrieHash, TrieReader, ValueDigest, }; -#[cfg(test)] use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; use std::fmt::{Debug, Write}; -#[cfg(test)] use std::future::ready; use std::io::Error; use std::iter::once; -#[cfg(test)] use std::num::NonZeroUsize; use std::sync::Arc; @@ -299,12 +292,10 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } - #[cfg(test)] pub(super) fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { MerkleKeyValueStream::from(&self.nodestore) } - #[cfg(test)] pub(super) fn key_value_iter_from_key>( &self, key: K, @@ -381,7 +372,6 @@ impl Merkle { /// None /// ).await?; /// ``` - #[cfg(test)] pub(super) async fn range_proof( &self, start_key: Option<&[u8]>, diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 7a00945a26f9..86861880a9fb 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -18,6 +18,7 @@ use async_trait::async_trait; use firewood_storage::{FileIoError, TrieHash}; use futures::Stream; use std::fmt::Debug; +use std::num::NonZeroUsize; use std::sync::Arc; /// A `KeyType` is something that can be xcast to a u8 reference, @@ -29,7 +30,7 @@ impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// A `ValueType` is the same as a `KeyType`. However, these could /// be a different type from the `KeyType` on a given API call. -/// For example, you might insert {key: "key", value: vec!\[0u8\]} +/// For example, you might insert `{key: "key", value: [0u8]}` /// This also means that the type of all the keys for a single /// API call must be the same, as well as the type of all values /// must be the same. @@ -261,11 +262,11 @@ pub trait DbView { /// * `last_key` - If None, continue to the end of the database /// * `limit` - The maximum number of keys in the range proof /// - async fn range_proof( + async fn range_proof( &self, first_key: Option, last_key: Option, - limit: Option, + limit: Option, ) -> Result; /// Obtain a stream over the keys/values of this view, using an optional starting point diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs index 09a98d269bcb..7451c3d96f55 100644 --- a/firewood/src/v2/emptydb.rs +++ b/firewood/src/v2/emptydb.rs @@ -1,12 +1,14 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use super::api::{Batch, Db, DbView, Error, HashKey, KeyType, ValueType}; +use super::api::{ + Batch, Db, DbView, Error, FrozenProof, FrozenRangeProof, HashKey, KeyType, ValueType, +}; use super::propose::{Proposal, ProposalBase}; use crate::merkle::{Key, Value}; -use crate::v2::api::{FrozenProof, FrozenRangeProof}; use async_trait::async_trait; use futures::Stream; +use std::num::NonZeroUsize; use std::sync::Arc; /// An `EmptyDb` is a simple implementation of `api::Db` @@ -69,11 +71,11 @@ impl DbView for HistoricalImpl { Err(Error::RangeProofOnEmptyTrie) } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, - _limit: Option, + _limit: Option, ) -> Result { Err(Error::RangeProofOnEmptyTrie) } diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs index 2714910ce1e5..76a12a02b5a2 100644 --- a/firewood/src/v2/propose.rs +++ b/firewood/src/v2/propose.rs @@ -3,16 +3,14 @@ use std::collections::BTreeMap; use std::fmt::Debug; +use std::num::NonZeroUsize; use std::sync::Arc; use async_trait::async_trait; use futures::stream::Empty; -use super::api::{KeyType, ValueType}; -use crate::{ - merkle::{Key, Value}, - v2::api::{self, FrozenProof, FrozenRangeProof}, -}; +use super::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; +use crate::merkle::{Key, Value}; #[derive(Clone, Debug)] pub(crate) enum KeyOp { @@ -124,17 +122,24 @@ impl api::DbView for Proposal { async fn val(&self, key: K) -> Result, api::Error> { // see if this key is in this proposal - match self.delta.get(key.as_ref()) { - Some(change) => match change { - // key in proposal, check for Put or Delete - KeyOp::Put(val) => Ok(Some(val.clone())), - KeyOp::Delete => Ok(None), // key was deleted in this proposal - }, - None => match &self.base { - // key not in this proposal, so delegate to base - ProposalBase::Proposal(p) => p.val(key).await, - ProposalBase::View(view) => view.val(key).await, - }, + let key = key.as_ref(); + let mut this = self; + // avoid recursion problems by looping over the proposal chain + loop { + match this.delta.get(key) { + Some(change) => { + // key is in `this` proposal, check for Put or Delete + break match change { + KeyOp::Put(val) => Ok(Some(val.clone())), + KeyOp::Delete => Ok(None), // key was deleted in this proposal + }; + } + None => match &this.base { + // key not in this proposal, so delegate to base + ProposalBase::Proposal(p) => this = p, + ProposalBase::View(view) => break view.val(key).await, + }, + } } } @@ -142,11 +147,11 @@ impl api::DbView for Proposal { todo!(); } - async fn range_proof( + async fn range_proof( &self, _first_key: Option, _last_key: Option, - _limit: Option, + _limit: Option, ) -> Result { todo!(); } From a3750f2d8b0ef6dd9ba2f0fc5b66c8b8a04f9c72 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 1 Aug 2025 09:44:23 -0700 Subject: [PATCH 0864/1053] chore: require license header for ffi code (#1159) --- .github/check-license-headers.yaml | 8 +++++-- ffi/build.rs | 3 +++ ffi/cbindgen.toml | 23 ++++++++----------- ffi/firewood.go | 3 +++ ffi/firewood.h | 6 +++++ ffi/firewood_test.go | 3 +++ ffi/generate_cgo.go | 3 +++ ffi/kvbackend.go | 3 +++ ffi/memory.go | 3 +++ ffi/metrics.go | 3 +++ ffi/metrics_test.go | 3 +++ ffi/proposal.go | 3 +++ ffi/revision.go | 3 +++ ffi/src/lib.rs | 2 +- ffi/src/metrics_setup.rs | 3 +++ ffi/tests/eth/eth_compatibility_test.go | 3 +++ .../firewood/merkle_compatibility_test.go | 3 +++ 17 files changed, 61 insertions(+), 17 deletions(-) diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index ae9d210db3aa..33f9d83918a0 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -1,7 +1,9 @@ [ { "include": [ - "**/**/*.rs" + "**/**/*.rs", + "**/**/*.go", + "**/**/*.h", ], "exclude": [ "target/**", @@ -17,11 +19,13 @@ "CODEOWNERS", "CONTRIBUTING.md", "benchmark/**", - "ffi/**", "triehash/**", "CHANGELOG.md", "cliff.toml", "**/tests/compile_*/**", + "**/go.mod", + "**/go.mod", + "**/cbindgen.toml", ], "license": "./.github/license-header.txt" }, diff --git a/ffi/build.rs b/ffi/build.rs index f49a0f706f65..4a37d783294a 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::env; extern crate cbindgen; diff --git a/ffi/cbindgen.toml b/ffi/cbindgen.toml index a07b7f83a94d..0369f02b1c21 100644 --- a/ffi/cbindgen.toml +++ b/ffi/cbindgen.toml @@ -5,14 +5,18 @@ # for detailed documentation of every option here. - language = "C" - ############## Options for Wrapping the Contents of the Header ################# # header = "/* Text to put at the beginning of the generated file. Probably a license. */" +header = """// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// This file was @generated by cbindgen. Do not edit this file manually. +// Run `cargo build --target firewood-ffi` to regenerate this file.""" + # trailer = "/* Text to put at the end of the generated file */" # include_guard = "my_bindings_h" # pragma_once = true @@ -36,13 +40,13 @@ tab_width = 2 documentation = true documentation_style = "auto" documentation_length = "full" -line_endings = "LF" # also "CR", "CRLF", "Native" +line_endings = "LF" # also "CR", "CRLF", "Native" ############################# Codegen Options ################################## style = "both" -sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` usize_is_size_t = true [defines] @@ -93,7 +97,7 @@ rename_variants = "None" # deprecated = "DEPRECATED_ENUM" # deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE" add_sentinel = false -prefix_with_name = false +prefix_with_name = true derive_helper_methods = false derive_const_casts = false derive_mut_casts = false @@ -104,24 +108,16 @@ enum_class = true private_default_tagged_enum_constructor = false - - [const] allow_static_const = true allow_constexpr = false sort_by = "Name" - - [macro_expansion] bitflags = false - - - - ############## Options for How Your Rust library Should Be Parsed ############## [parse] @@ -132,7 +128,6 @@ clean = false extra_bindings = [] - [parse.expand] crates = [] all_features = false diff --git a/ffi/firewood.go b/ffi/firewood.go index 355081992dbd..e46e40c3deea 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood diff --git a/ffi/firewood.h b/ffi/firewood.h index 79a4bb946f2e..5ce787d49521 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -1,3 +1,9 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +// This file was @generated by cbindgen. Do not edit this file manually. +// Run `cargo build --target firewood-ffi` to regenerate this file. + #include #include #include diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 5da486d48739..7cbef24ef914 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package ffi import ( diff --git a/ffi/generate_cgo.go b/ffi/generate_cgo.go index 20a2a0ae9a30..a314724f22b2 100644 --- a/ffi/generate_cgo.go +++ b/ffi/generate_cgo.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + //go:build ignore // go generate script diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index b17bd3f167d1..869cbf21f859 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package ffi // implement a specific interface for firewood diff --git a/ffi/memory.go b/ffi/memory.go index 85342df55a17..9a0b9d21fdcc 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood diff --git a/ffi/metrics.go b/ffi/metrics.go index f6b51d54f481..837a79c8f0e1 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package ffi //go:generate go run generate_cgo.go diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go index cdac87a74596..dc054b5efc23 100644 --- a/ffi/metrics_test.go +++ b/ffi/metrics_test.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package ffi import ( diff --git a/ffi/proposal.go b/ffi/proposal.go index f92f7337053f..1c1c62d1a8ba 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood diff --git a/ffi/revision.go b/ffi/revision.go index 1f24a0c59598..35635418249b 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + // Package ffi provides a Go wrapper around the [Firewood] database. // // [Firewood]: https://github.com/ava-labs/firewood diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index b4d66b6fc149..b4eac8011fc9 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. #![doc = include_str!("../README.md")] diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 80d87df9d9a8..2dc79101f551 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::error::Error; diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index cffa57595b2b..e66f10e7404d 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package eth import ( diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index 61f913f32c5c..fbbc7e76dfb9 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + package firewood import ( From 976798387579cf3077223b97b57b9dbb783e1fc1 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:00:40 -0500 Subject: [PATCH 0865/1053] feat(checker): add InvalidKey error With ethhash, all values should correspond to a key with 32 or 64 bytes (64 or 128 nibbles). Without ethhash, all keys should at least contain full bytes (even number of nibbles). --- fwdctl/tests/cli.rs | 8 +++++--- storage/src/checker/mod.rs | 27 +++++++++++++++++++++++---- storage/src/lib.rs | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index e4b45b3629cd..dfbac18dda9e 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -541,6 +541,7 @@ fn fwdctl_check_empty_db() -> Result<()> { #[test] #[serial] fn fwdctl_check_db_with_data() -> Result<()> { + use rand::distr::Alphanumeric; use rand::rngs::StdRng; use rand::{Rng, SeedableRng, rng}; @@ -553,7 +554,8 @@ fn fwdctl_check_db_with_data() -> Result<()> { .unwrap_or_else(|| rng().random()); eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let mut rng = StdRng::seed_from_u64(seed); + let rng = StdRng::seed_from_u64(seed); + let mut sample_iter = rng.sample_iter(Alphanumeric).map(char::from); Command::cargo_bin(PRG)? .arg("create") @@ -563,8 +565,8 @@ fn fwdctl_check_db_with_data() -> Result<()> { // TODO: bulk loading data instead of inserting one by one for _ in 0..4 { - let key = format!("key_{}", rng.random::()); - let value = format!("value_{}", rng.random::()); + let key = sample_iter.by_ref().take(64).collect::(); + let value = sample_iter.by_ref().take(10).collect::(); Command::cargo_bin(PRG)? .arg("insert") .args([key]) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 7704928dbaab..f95e9cb948c4 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -19,6 +19,17 @@ use std::ops::Range; use indicatif::ProgressBar; +#[cfg(feature = "ethhash")] +fn is_valid_key(key: &Path) -> bool { + const VALID_ETH_KEY_SIZES: [usize; 2] = [64, 128]; // in number of nibbles - two nibbles make a byte + VALID_ETH_KEY_SIZES.contains(&key.0.len()) +} + +#[cfg(not(feature = "ethhash"))] +fn is_valid_key(key: &Path) -> bool { + key.0.len() % 2 == 0 +} + /// Options for the checker #[derive(Debug)] pub struct CheckOpt { @@ -150,14 +161,22 @@ impl NodeStore { // read the node and iterate over the children if branch node let node = self.read_node(subtrie_root_address)?; + let mut current_node_path = path_prefix.clone(); + current_node_path.0.extend_from_slice(node.partial_path()); + if node.value().is_some() && !is_valid_key(¤t_node_path) { + return Err(CheckerError::InvalidKey { + key: current_node_path, + address: subtrie_root_address, + parent, + }); + } if let Node::Branch(branch) = node.as_ref() { // this is an internal node, traverse the children #[cfg(feature = "ethhash")] let num_children = branch.children_iter().count(); for (nibble, (address, hash)) in branch.children_iter() { let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); - let mut child_path_prefix = path_prefix.clone(); - child_path_prefix.0.extend_from_slice(node.partial_path()); + let mut child_path_prefix = current_node_path.clone(); child_path_prefix.0.push(nibble as u8); let child_subtrie = SubTrieMetadata { root_address: address, @@ -366,7 +385,7 @@ mod test { /// graph TD /// Root["Root Node
    partial_path: [2]
    children: [0] -> Branch"] /// Branch["Branch Node
    partial_path: [3]
    path: 0x203
    children: [1] -> Leaf"] - /// Leaf["Leaf Node
    partial_path: [4, 5]
    path: 0x203145
    value: [6, 7, 8]"] + /// Leaf["Leaf Node
    partial_path: [4, 5]
    path: 0x2031454545...45 (32 bytes)
    value: [6, 7, 8]"] /// /// Root -->|"nibble 0"| Branch /// Branch -->|"nibble 1"| Leaf @@ -375,7 +394,7 @@ mod test { fn gen_test_trie(nodestore: &mut NodeStore) -> TestTrie { let mut high_watermark = NodeStoreHeader::SIZE; let leaf = Node::Leaf(LeafNode { - partial_path: Path::from([4, 5]), + partial_path: Path::from_nibbles_iterator(std::iter::repeat_n([4, 5], 30).flatten()), value: Box::new([6, 7, 8]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 26b60b09fb86..5291e69f25e8 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -261,6 +261,21 @@ pub enum CheckerError { #[error("The checker can only check persisted nodestores")] UnpersistedRoot, + #[error( + "The node {key:#x} at {address:#x} (parent: {parent:#x}) has a value but its path is not 32 or 64 bytes long" + )] + /// A value is found corresponding to an invalid key. + /// With ethhash, keys must be 32 or 64 bytes long. + /// Without ethhash, keys cannot contain half-bytes (i.e., odd number of nibbles). + InvalidKey { + /// The key found, or equivalently the path of the node that stores the value + key: Path, + /// Address of the node + address: LinearAddress, + /// Parent of the node + parent: TrieNodeParent, + }, + /// IO error #[error("IO error")] #[derive_where(skip_inner)] From 31cc5d7e40674742cc7a772f79eb6e1fc1bbc5e9 Mon Sep 17 00:00:00 2001 From: Kushneryk Pavel Date: Fri, 1 Aug 2025 23:24:39 +0200 Subject: [PATCH 0866/1053] fix: create metrics registration macros (#980) resolve https://github.com/ava-labs/firewood/issues/940 --- storage/src/lib.rs | 3 +++ storage/src/macros.rs | 34 +++++++++++++++++++++++ storage/src/nodestore/alloc.rs | 46 +++++++++++++++++++++++--------- storage/src/nodestore/persist.rs | 10 +++---- 4 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 storage/src/macros.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 5291e69f25e8..859ab181ab05 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -36,6 +36,9 @@ use crate::nodestore::AreaIndex; /// Logger module for handling logging functionality pub mod logger; +#[macro_use] +/// Macros module for defining macros used in the storage module +pub mod macros; // re-export these so callers don't need to know where they are pub use checker::CheckOpt; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; diff --git a/storage/src/macros.rs b/storage/src/macros.rs new file mode 100644 index 000000000000..3d8cc11ba28a --- /dev/null +++ b/storage/src/macros.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. +#[macro_export] +/// Macro to register and use a metric with description and labels. +/// This macro is a wrapper around the `metrics` crate's `counter!` and `describe_counter!` +/// macros. It ensures that the description is registered just once. +/// +/// Usage: +/// `firewood_counter!("metric_name", "description")` +/// `firewood_counter!("metric_name", "description", "label" => "value")` +/// +/// Call `.increment(val)` or `.absolute(val)` on the result as appropriate. +macro_rules! firewood_counter { + // With labels + ($name:expr, $desc:expr, $($labels:tt)+) => { + { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + metrics::describe_counter!($name, $desc); + }); + metrics::counter!($name, $($labels)+) + } + }; + // No labels + ($name:expr, $desc:expr) => { + { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + metrics::describe_counter!($name, $desc); + }); + metrics::counter!($name) + } + }; +} diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 8f109e5c6762..d5665659ae7b 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -25,7 +25,6 @@ use crate::logger::trace; use crate::node::branch::{ReadSerializable, Serializable}; use crate::nodestore::NodeStoreHeader; use integer_encoding::VarIntReader; -use metrics::counter; use sha2::{Digest, Sha256}; use std::fmt; @@ -34,7 +33,10 @@ use std::iter::FusedIterator; use std::num::NonZeroU64; use crate::node::ExtendableBytes; -use crate::{FreeListParent, MaybePersistedNode, ReadableStorage, TrieHash, WritableStorage}; +use crate::{ + FreeListParent, MaybePersistedNode, ReadableStorage, TrieHash, WritableStorage, + firewood_counter, +}; /// Returns the maximum size needed to encode a `VarInt`. const fn var_int_max_size() -> usize { @@ -513,10 +515,18 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { *free_stored_area_addr = free_head.next_free_block; } - counter!("firewood.space.reused", "index" => index_name(index)) - .increment(AREA_SIZES[index]); - counter!("firewood.space.wasted", "index" => index_name(index)) - .increment(AREA_SIZES[index].saturating_sub(n)); + firewood_counter!( + "firewood.space.reused", + "Bytes reused from free list by index", + "index" => index_name(index) + ) + .increment(AREA_SIZES[index]); + firewood_counter!( + "firewood.space.wasted", + "Bytes wasted from free list by index", + "index" => index_name(index) + ) + .increment(AREA_SIZES[index].saturating_sub(n)); // Return the address of the newly allocated block. trace!("Allocating from free list: addr: {address:?}, size: {index}"); @@ -524,8 +534,12 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { } trace!("No free blocks of sufficient size {index_wanted} found"); - counter!("firewood.space.from_end", "index" => index_name(index_wanted as usize)) - .increment(AREA_SIZES[index_wanted as usize]); + firewood_counter!( + "firewood.space.from_end", + "Space allocated from end of nodestore", + "index" => index_name(index_wanted as usize) + ) + .increment(AREA_SIZES[index_wanted as usize]); Ok(None) } @@ -585,10 +599,18 @@ impl NodeAllocator<'_, S> { let (area_size_index, _) = self.area_index_and_size(addr)?; trace!("Deleting node at {addr:?} of size {area_size_index}"); - counter!("firewood.delete_node", "index" => index_name(area_size_index as usize)) - .increment(1); - counter!("firewood.space.freed", "index" => index_name(area_size_index as usize)) - .increment(AREA_SIZES[area_size_index as usize]); + firewood_counter!( + "firewood.delete_node", + "Nodes deleted", + "index" => index_name(area_size_index as usize) + ) + .increment(1); + firewood_counter!( + "firewood.space.freed", + "Bytes freed in nodestore", + "index" => index_name(area_size_index as usize) + ) + .increment(AREA_SIZES[area_size_index as usize]); // The area that contained the node is now free. let mut stored_area_bytes = Vec::new(); diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 80c4b55929d9..42600394542d 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -29,9 +29,9 @@ use std::iter::FusedIterator; +use crate::firewood_counter; use crate::linear::FileIoError; use coarsetime::Instant; -use metrics::counter; #[cfg(feature = "io-uring")] use crate::logger::trace; @@ -270,7 +270,7 @@ impl NodeStore { self.storage.write_cached_nodes(cached_nodes)?; let flush_time = flush_start.elapsed().as_millis(); - counter!("firewood.flush_nodes").increment(flush_time); + firewood_counter!("firewood.flush_nodes", "flushed node amount").increment(flush_time); Ok(header) } @@ -424,13 +424,13 @@ impl NodeStore { ) })?; trace!("submission queue is full"); - counter!("ring.full").increment(1); + firewood_counter!("ring.full", "amount of full ring").increment(1); } break; } // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation // to complete, then handle the completion queue - counter!("ring.full").increment(1); + firewood_counter!("ring.full", "amount of full ring").increment(1); ring.submit_and_wait(1).map_err(|e| { self.storage .file_io_error(e, 0, Some("io-uring submit_and_wait".to_string())) @@ -469,7 +469,7 @@ impl NodeStore { debug_assert!(ring.completion().is_empty()); let flush_time = flush_start.elapsed().as_millis(); - counter!("firewood.flush_nodes").increment(flush_time); + firewood_counter!("firewood.flush_nodes", "amount flushed nodes").increment(flush_time); Ok(header) } From 11c9c95303de44b9353f08e0dd278224923783ab Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 1 Aug 2025 14:40:02 -0700 Subject: [PATCH 0867/1053] feat(deferred-persist): Part 1: unpersisted gauge (#1116) Co-authored-by: Brandon LeBlanc --- benchmark/Grafana-dashboard.json | 109 ++++++++++++++++++++++++++++++- storage/src/macros.rs | 36 +++++++++- storage/src/nodestore/hash.rs | 12 ++-- storage/src/nodestore/mod.rs | 80 ++++++++++++++++++++--- storage/src/nodestore/persist.rs | 48 ++++++++++++-- 5 files changed, 264 insertions(+), 21 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index b9d579af5e44..718216cf7553 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -845,6 +845,113 @@ ], "title": "Insert Merkle Ops by Type", "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1000 + }, + { + "color": "red", + "value": 5000 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "min", + "mean", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "editorMode": "code", + "expr": "firewood_nodes_unwritten", + "legendFormat": "Unwritten Nodes", + "range": true, + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + } + } + ], + "title": "Unwritten Nodes (In Memory)", + "type": "timeseries" } ], "refresh": "10s", @@ -863,4 +970,4 @@ "uid": "adxfhfmwx5ypsc", "version": 12, "weekStart": "" -} \ No newline at end of file +} diff --git a/storage/src/macros.rs b/storage/src/macros.rs index 3d8cc11ba28a..3aa48642d6e4 100644 --- a/storage/src/macros.rs +++ b/storage/src/macros.rs @@ -1,7 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. + #[macro_export] -/// Macro to register and use a metric with description and labels. +/// Macro to register and use a counter metric with description and labels. /// This macro is a wrapper around the `metrics` crate's `counter!` and `describe_counter!` /// macros. It ensures that the description is registered just once. /// @@ -32,3 +33,36 @@ macro_rules! firewood_counter { } }; } + +#[macro_export] +/// Macro to register and use a gauge metric with description and labels. +/// This macro is a wrapper around the `metrics` crate's `gauge!` and `describe_gauge!` +/// macros. It ensures that the description is registered just once. +/// +/// Usage: +/// `firewood_gauge!("metric_name", "description")` +/// `firewood_gauge!("metric_name", "description", "label" => "value")` +/// +/// Call `.increment(val)` or `.decrement(val)` on the result as appropriate. +macro_rules! firewood_gauge { + // With labels + ($name:expr, $desc:expr, $($labels:tt)+) => { + { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + metrics::describe_counter!($name, $desc); + }); + metrics::gauge!($name, $($labels)+) + } + }; + // No labels + ($name:expr, $desc:expr) => { + { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + metrics::describe_counter!($name, $desc); + }); + metrics::gauge!($name) + } + }; +} diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index 21dd9fbbd055..f1c07e9b3345 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -78,9 +78,10 @@ where mut node: Node, path_prefix: &mut Path, #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, - ) -> Result<(MaybePersistedNode, HashType), FileIoError> { + ) -> Result<(MaybePersistedNode, HashType, usize), FileIoError> { // If this is a branch, find all unhashed children and recursively hash them. trace!("hashing {node:?} at {path_prefix:?}"); + let mut nodes_processed = 1usize; // Count this node if let Node::Branch(ref mut b) = node { // special case code for ethereum hashes at the account level #[cfg(feature = "ethhash")] @@ -160,10 +161,13 @@ where path_prefix.0.push(nibble as u8); #[cfg(feature = "ethhash")] - let (child_node, child_hash) = + let (child_node, child_hash, child_count) = self.hash_helper(child_node, path_prefix, make_fake_root)?; #[cfg(not(feature = "ethhash"))] - let (child_node, child_hash) = Self::hash_helper(child_node, path_prefix)?; + let (child_node, child_hash, child_count) = + Self::hash_helper(child_node, path_prefix)?; + + nodes_processed = nodes_processed.saturating_add(child_count); *child = Some(Child::MaybePersisted(child_node, child_hash)); trace!("child now {child:?}"); @@ -191,7 +195,7 @@ where #[cfg(not(feature = "ethhash"))] let hash = hash_node(&node, path_prefix); - Ok((SharedNode::new(node).into(), hash)) + Ok((SharedNode::new(node).into(), hash, nodes_processed)) } #[cfg(feature = "ethhash")] diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index dda3652e03d1..039bdf77a7d3 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -43,6 +43,7 @@ pub(crate) mod hash; pub(crate) mod header; pub(crate) mod persist; +use crate::firewood_gauge; use crate::logger::trace; use crate::node::branch::ReadSerializable as _; use arc_swap::ArcSwap; @@ -50,6 +51,7 @@ use arc_swap::access::DynAccess; use smallvec::SmallVec; use std::fmt::Debug; use std::io::{Error, ErrorKind}; +use std::sync::atomic::AtomicUsize; // Re-export types from alloc module pub use alloc::{AreaIndex, LinearAddress, NodeAllocator}; @@ -121,6 +123,7 @@ impl NodeStore { deleted: Box::default(), root_hash: None, root: header.root_address().map(Into::into), + unwritten_nodes: AtomicUsize::new(0), }, storage, }; @@ -150,6 +153,7 @@ impl NodeStore { deleted: Box::default(), root_hash: None, root: None, + unwritten_nodes: AtomicUsize::new(0), }, }) } @@ -350,11 +354,27 @@ pub trait RootReader { } /// A committed revision of a merkle trie. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Committed { deleted: Box<[MaybePersistedNode]>, root_hash: Option, root: Option, + /// TODO: No readers of this variable yet - will be used for tracking unwritten nodes in committed revisions + unwritten_nodes: AtomicUsize, +} + +impl Clone for Committed { + fn clone(&self) -> Self { + Self { + deleted: self.deleted.clone(), + root_hash: self.root_hash.clone(), + root: self.root.clone(), + unwritten_nodes: AtomicUsize::new( + self.unwritten_nodes + .load(std::sync::atomic::Ordering::Relaxed), + ), + } + } } #[derive(Clone, Debug)] @@ -386,6 +406,8 @@ pub struct ImmutableProposal { root_hash: Option, /// The root node, either in memory or on disk root: Option, + /// The number of unwritten nodes in this proposal + unwritten_nodes: usize, } impl ImmutableProposal { @@ -403,6 +425,21 @@ impl ImmutableProposal { } } +impl Drop for ImmutableProposal { + fn drop(&mut self) { + // When an immutable proposal is dropped without being committed, + // decrement the gauge to reflect that these nodes will never be written + if self.unwritten_nodes > 0 { + #[allow(clippy::cast_precision_loss)] + firewood_gauge!( + "firewood.nodes.unwritten", + "current number of unwritten nodes" + ) + .decrement(self.unwritten_nodes as f64); + } + } +} + /// Contains the state of a revision of a merkle trie. /// /// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. @@ -461,14 +498,23 @@ impl, S: ReadableStorage> From> /// Commit a proposal to a new revision of the trie impl From> for NodeStore { fn from(val: NodeStore) -> Self { + let NodeStore { + header, + kind, + storage, + } = val; + // Use ManuallyDrop to prevent the Drop impl from running since we're committing + let kind = std::mem::ManuallyDrop::new(kind); + NodeStore { - header: val.header, + header, kind: Committed { - deleted: val.kind.deleted, - root_hash: val.kind.root_hash, - root: val.kind.root, + deleted: kind.deleted.clone(), + root_hash: kind.root_hash.clone(), + root: kind.root.clone(), + unwritten_nodes: AtomicUsize::new(kind.unwritten_nodes), }, - storage: val.storage, + storage, } } } @@ -495,6 +541,7 @@ impl NodeStore, S> { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), root: self.kind.root.clone(), + unwritten_nodes: AtomicUsize::new(self.kind.unwritten_nodes), }, storage: self.storage.clone(), } @@ -520,6 +567,7 @@ impl TryFrom> parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), root_hash: None, root: None, + unwritten_nodes: 0, }), storage, }; @@ -532,20 +580,32 @@ impl TryFrom> // Hashes the trie and returns the address of the new root. #[cfg(feature = "ethhash")] - let (root, root_hash) = nodestore.hash_helper(root, &mut Path::new(), None)?; + let (root, root_hash, unwritten_count) = + nodestore.hash_helper(root, &mut Path::new(), None)?; #[cfg(not(feature = "ethhash"))] - let (root, root_hash) = + let (root, root_hash, unwritten_count) = NodeStore::::hash_helper(root, &mut Path::new())?; let immutable_proposal = Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); + // Use ManuallyDrop to prevent Drop from running since we're replacing the proposal + let immutable_proposal = std::mem::ManuallyDrop::new(immutable_proposal); nodestore.kind = Arc::new(ImmutableProposal { - deleted: immutable_proposal.deleted, - parent: immutable_proposal.parent, + deleted: immutable_proposal.deleted.clone(), + parent: immutable_proposal.parent.clone(), root_hash: Some(root_hash.into_triehash()), root: Some(root), + unwritten_nodes: unwritten_count, }); + // Track unwritten nodes in metrics + #[allow(clippy::cast_precision_loss)] + firewood_gauge!( + "firewood.nodes.unwritten", + "current number of unwritten nodes" + ) + .increment(unwritten_count as f64); + Ok(nodestore) } } diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 42600394542d..5002af6723f1 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -26,11 +26,13 @@ //! - Metrics are collected for flush operation timing //! - Memory-efficient serialization with pre-allocated buffers //! - Ring buffer management for io-uring operations +//! +//! use std::iter::FusedIterator; -use crate::firewood_counter; use crate::linear::FileIoError; +use crate::{firewood_counter, firewood_gauge}; use coarsetime::Instant; #[cfg(feature = "io-uring")] @@ -260,6 +262,13 @@ impl NodeStore { .write(persisted_address.get(), serialized.as_slice())?; node.persist_at(persisted_address); + // Decrement gauge immediately after node is written to storage + firewood_gauge!( + "firewood.nodes.unwritten", + "current number of unwritten nodes" + ) + .decrement(1.0); + // Move the arc to a vector of persisted nodes for caching // we save them so we don't have to lock the cache while we write them // If we ever persist out of band, we might have a race condition, so @@ -303,6 +312,11 @@ impl NodeStore { // Finally persist the header self.flush_header()?; + // Reset unwritten nodes counter to zero since all nodes are now persisted + self.kind + .unwritten_nodes + .store(0, std::sync::atomic::Ordering::Relaxed); + Ok(()) } } @@ -325,11 +339,13 @@ impl NodeStore { } /// Helper function to handle completion queue entries and check for errors + /// Returns the number of completed operations fn handle_completion_queue( storage: &FileBacked, completion_queue: io_uring::cqueue::CompletionQueue<'_>, saved_pinned_buffers: &mut [PinnedBufferEntry], - ) -> Result<(), FileIoError> { + ) -> Result { + let mut completed_count = 0usize; for entry in completion_queue { let item = entry.user_data() as usize; let pbe = saved_pinned_buffers @@ -355,8 +371,9 @@ impl NodeStore { )); } pbe.offset = None; + completed_count = completed_count.wrapping_add(1); } - Ok(()) + Ok(completed_count) } const RINGSIZE: usize = FileBacked::RINGSIZE as usize; @@ -437,11 +454,21 @@ impl NodeStore { })?; let completion_queue = ring.completion(); trace!("competion queue length: {}", completion_queue.len()); - handle_completion_queue( + let completed_writes = handle_completion_queue( &self.storage, completion_queue, &mut saved_pinned_buffers, )?; + + // Decrement gauge for writes that have actually completed + if completed_writes > 0 { + #[expect(clippy::cast_precision_loss)] + firewood_gauge!( + "firewood.nodes.unwritten", + "current number of unwritten nodes" + ) + .decrement(completed_writes as f64); + } } // Mark node as persisted and collect for cache @@ -457,7 +484,18 @@ impl NodeStore { .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) })?; - handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; + let final_completed_writes = + handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; + + // Decrement gauge for final batch of writes that completed + if final_completed_writes > 0 { + #[expect(clippy::cast_precision_loss)] + firewood_gauge!( + "firewood.nodes.unwritten", + "current number of unwritten nodes" + ) + .decrement(final_completed_writes as f64); + } debug_assert!( !saved_pinned_buffers.iter().any(|pbe| pbe.offset.is_some()), From db19077efa208cbddeb3c8b6476ede70864a8c46 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:56:06 -0500 Subject: [PATCH 0868/1053] feat(checker): collect basic statistics while checking the db image (#1149) --- firewood/src/db.rs | 9 +- fwdctl/src/check.rs | 34 +++- storage/benches/serializer.rs | 2 +- storage/src/checker/mod.rs | 282 ++++++++++++++++++++++++++----- storage/src/lib.rs | 2 +- storage/src/linear/filebacked.rs | 10 +- storage/src/linear/memory.rs | 6 +- storage/src/linear/mod.rs | 17 +- storage/src/node/mod.rs | 4 +- storage/src/nodestore/alloc.rs | 7 +- storage/src/nodestore/mod.rs | 63 +++++-- 11 files changed, 361 insertions(+), 75 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index fcb0e6d739ec..e4d14ab46eec 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,8 +14,8 @@ pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; use firewood_storage::{ - CheckOpt, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, - TrieHash, + CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, + ImmutableProposal, NodeStore, TrieHash, }; use metrics::{counter, describe_counter}; use std::io::Write; @@ -336,7 +336,10 @@ impl Db { } /// Check the database for consistency - pub async fn check(&self, opt: CheckOpt) -> Result<(), firewood_storage::CheckerError> { + pub async fn check( + &self, + opt: CheckOpt, + ) -> Result { let latest_rev_nodestore = self.manager.current_revision(); latest_rev_nodestore.check(opt) } diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index d8379ad817a5..803e497ac253 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use clap::Args; use firewood::v2::api; -use firewood_storage::{CacheReadStrategy, CheckOpt, FileBacked, NodeStore}; +use firewood_storage::{CacheReadStrategy, CheckOpt, CheckerReport, FileBacked, NodeStore}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use nonzero_ext::nonzero; @@ -55,10 +55,38 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { ) .with_finish(ProgressFinish::WithMessage("Check Completed!".into())); - NodeStore::open(storage)? + let report = NodeStore::open(storage)? .check(CheckOpt { hash_check: opts.hash_check, progress_bar: Some(progress_bar), }) - .map_err(|e| api::Error::InternalError(Box::new(e))) + .map_err(|e| api::Error::InternalError(Box::new(e)))?; + + print_checker_report(report); + + Ok(()) +} + +#[expect(clippy::cast_precision_loss)] +fn print_checker_report(report: CheckerReport) { + println!("Raw Report: {report:?}\n"); + + println!("Advanced Data: "); + let total_trie_area_bytes = report + .trie_stats + .area_counts + .iter() + .map(|(area_size, count)| area_size.saturating_mul(*count)) + .sum::(); + println!( + "\tStorage Space Utilization: {} / {} = {:.2}%", + report.trie_stats.trie_bytes, + report.physical_bytes, + (report.trie_stats.trie_bytes as f64 / report.physical_bytes as f64) * 100.0 + ); + println!( + "\tInternal Fragmentation: {} / {total_trie_area_bytes} = {:.2}%", + report.trie_stats.trie_bytes, + (report.trie_stats.trie_bytes as f64 / total_trie_area_bytes as f64) * 100.0 + ); } diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 6c2e9cde31d0..7a0a881b49a3 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -71,7 +71,7 @@ fn manual_deserializer(b: &mut Bencher, input: &Vec) { .as_slice() .split_first() .expect("always has at least one byte"); - b.iter(|| Node::from_reader(std::io::Cursor::new(input)).expect("to deserialize node")); + b.iter(|| Node::from_reader(&mut std::io::Cursor::new(input)).expect("to deserialize node")); } fn to_bytes(input: &Node) -> Vec { diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index f95e9cb948c4..a99d40f87fab 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -5,20 +5,31 @@ mod range_set; pub(crate) use range_set::LinearAddressRangeSet; use crate::logger::warn; -use crate::nodestore::alloc::{AREA_SIZES, AreaIndex, FreeAreaWithMetadata, size_from_area_index}; +use crate::nodestore::alloc::{ + AREA_SIZES, AreaIndex, FreeAreaWithMetadata, area_size_to_index, size_from_area_index, +}; use crate::{ CheckerError, Committed, HashType, HashedNodeReader, IntoHashType, LinearAddress, Node, - NodeReader, NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, + NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, }; #[cfg(not(feature = "ethhash"))] use crate::hashednode::hash_node; use std::cmp::Ordering; +use std::collections::HashMap; use std::ops::Range; use indicatif::ProgressBar; +const OS_PAGE_SIZE: u64 = 4096; + +#[inline] +// return u64 since the start address may be 0 +const fn page_start(addr: LinearAddress) -> u64 { + addr.get() & !(OS_PAGE_SIZE - 1) +} + #[cfg(feature = "ethhash")] fn is_valid_key(key: &Path) -> bool { const VALID_ETH_KEY_SIZES: [usize; 2] = [64, 128]; // in number of nibbles - two nibbles make a byte @@ -39,10 +50,54 @@ pub struct CheckOpt { pub progress_bar: Option, } +#[derive(Debug)] +/// Report of the checker results. +pub struct CheckerReport { + /// The high watermark of the database + pub high_watermark: u64, + /// The physical number of bytes in the database returned through `stat` + pub physical_bytes: u64, + /// Statistics about the trie + pub trie_stats: TrieStats, + /// Statistics about the free list + pub free_list_stats: FreeListsStats, +} + +#[derive(Debug, Default, PartialEq)] +/// Statistics about the trie +pub struct TrieStats { + /// The total number of bytes of compressed trie nodes + pub trie_bytes: u64, + /// The number of key-value pairs stored in the trie + pub kv_count: u64, + /// The total number of bytes of for key-value pairs stored in the trie + pub kv_bytes: u64, + /// Branching factor distribution of each branch node + pub branching_factors: HashMap, + /// Depth distribution of each leaf node + pub depths: HashMap, + /// The distribution of area sizes in the trie + pub area_counts: HashMap, + /// The stored areas whose content can fit into a smaller area + pub low_occupancy_area_count: u64, + /// The number of stored areas that span multiple pages + pub multi_page_area_count: u64, +} + +#[derive(Debug, Default, PartialEq)] +/// Statistics about the free list +pub struct FreeListsStats { + /// The distribution of area sizes in the free lists + pub area_counts: HashMap, + /// The number of stored areas that span multiple pages + pub multi_page_area_count: u64, +} + struct SubTrieMetadata { root_address: LinearAddress, root_hash: HashType, parent: TrieNodeParent, + depth: usize, path_prefix: Path, #[cfg(feature = "ethhash")] has_peers: bool, @@ -60,9 +115,10 @@ impl NodeStore { /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. // TODO: report all errors, not just the first one - pub fn check(&self, opt: CheckOpt) -> Result<(), CheckerError> { + pub fn check(&self, opt: CheckOpt) -> Result { // 1. Check the header let db_size = self.size(); + let physical_bytes = self.physical_size()?; let mut visited = LinearAddressRangeSet::new(db_size)?; @@ -71,7 +127,7 @@ impl NodeStore { progress_bar.set_length(db_size); progress_bar.set_message("Traversing the trie..."); } - if let (Some(root), Some(root_hash)) = + let trie_stats = if let (Some(root), Some(root_hash)) = (self.root_as_maybe_persisted_node(), self.root_hash()) { // the database is not empty, and has a physical address, so traverse the trie @@ -86,17 +142,19 @@ impl NodeStore { &mut visited, opt.progress_bar.as_ref(), opt.hash_check, - )?; + )? } else { return Err(CheckerError::UnpersistedRoot); } - } + } else { + TrieStats::default() + }; // 3. check the free list - this can happen in parallel with the trie traversal if let Some(progress_bar) = &opt.progress_bar { progress_bar.set_message("Traversing free lists..."); } - self.visit_freelist(&mut visited, opt.progress_bar.as_ref())?; + let free_list_stats = self.visit_freelist(&mut visited, opt.progress_bar.as_ref())?; // 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? if let Some(progress_bar) = &opt.progress_bar { @@ -109,7 +167,12 @@ impl NodeStore { let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges, opt.progress_bar.as_ref()); // TODO: add leaked areas to the free list - Ok(()) + Ok(CheckerReport { + high_watermark: db_size, + physical_bytes, + trie_stats, + free_list_stats, + }) } fn visit_trie( @@ -119,23 +182,28 @@ impl NodeStore { visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, hash_check: bool, - ) -> Result<(), CheckerError> { + ) -> Result { let trie = SubTrieMetadata { root_address, root_hash, parent: TrieNodeParent::Root, + depth: 0, path_prefix: Path::new(), #[cfg(feature = "ethhash")] has_peers: false, }; - self.visit_trie_helper(trie, visited, progress_bar, hash_check) + let mut trie_stats = TrieStats::default(); + self.visit_trie_helper(trie, visited, &mut trie_stats, progress_bar, hash_check)?; + Ok(trie_stats) } /// Recursively traverse the trie from the given root node. + #[expect(clippy::too_many_lines)] fn visit_trie_helper( &self, subtrie: SubTrieMetadata, visited: &mut LinearAddressRangeSet, + trie_stats: &mut TrieStats, progress_bar: Option<&ProgressBar>, hash_check: bool, ) -> Result<(), CheckerError> { @@ -143,6 +211,7 @@ impl NodeStore { root_address: subtrie_root_address, root_hash: subtrie_root_hash, parent, + depth, path_prefix, #[cfg(feature = "ethhash")] has_peers, @@ -152,41 +221,105 @@ impl NodeStore { self.check_area_aligned(subtrie_root_address, StoredAreaParent::TrieNode(parent))?; // check that the area is within bounds and does not intersect with other areas - let (_, area_size) = self.area_index_and_size(subtrie_root_address)?; + let (area_index, area_size) = self.area_index_and_size(subtrie_root_address)?; visited.insert_area( subtrie_root_address, area_size, StoredAreaParent::TrieNode(parent), )?; - // read the node and iterate over the children if branch node - let node = self.read_node(subtrie_root_address)?; - let mut current_node_path = path_prefix.clone(); - current_node_path.0.extend_from_slice(node.partial_path()); - if node.value().is_some() && !is_valid_key(¤t_node_path) { + // update the area count + let area_count = trie_stats.area_counts.entry(area_size).or_insert(0); + *area_count = area_count.saturating_add(1); + + // read the node from the disk - we avoid cache since we will never visit the same node twice + let (node, node_bytes) = self.read_node_with_num_bytes_from_disk(subtrie_root_address)?; + { + // collect the trie bytes + trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); + // collect low occupancy area count + let smallest_area_index = area_size_to_index(node_bytes).map_err(|e| { + self.file_io_error( + e, + subtrie_root_address.get(), + Some("area_size_to_index".to_string()), + ) + })?; + if smallest_area_index < area_index { + trie_stats.low_occupancy_area_count = + trie_stats.low_occupancy_area_count.saturating_add(1); + } + // collect the multi-page area count + if page_start(subtrie_root_address) + != page_start( + subtrie_root_address + .advance(area_size) + .expect("impossible since we checked in visited.insert_area()"), + ) + { + trie_stats.multi_page_area_count = + trie_stats.multi_page_area_count.saturating_add(1); + } + } + + let mut current_path_prefix = path_prefix.clone(); + current_path_prefix.0.extend_from_slice(node.partial_path()); + if node.value().is_some() && !is_valid_key(¤t_path_prefix) { return Err(CheckerError::InvalidKey { - key: current_node_path, + key: current_path_prefix, address: subtrie_root_address, parent, }); } - if let Node::Branch(branch) = node.as_ref() { - // this is an internal node, traverse the children - #[cfg(feature = "ethhash")] - let num_children = branch.children_iter().count(); - for (nibble, (address, hash)) in branch.children_iter() { - let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); - let mut child_path_prefix = current_node_path.clone(); - child_path_prefix.0.push(nibble as u8); - let child_subtrie = SubTrieMetadata { - root_address: address, - root_hash: hash.clone(), - parent, - path_prefix: child_path_prefix, - #[cfg(feature = "ethhash")] - has_peers: num_children != 1, - }; - self.visit_trie_helper(child_subtrie, visited, progress_bar, hash_check)?; + + match node.as_ref() { + Node::Branch(branch) => { + let num_children = branch.children_iter().count(); + { + // collect the branching factor distribution + let branching_factor_count = trie_stats + .branching_factors + .entry(num_children) + .or_insert(0); + *branching_factor_count = branching_factor_count.saturating_add(1); + } + + // this is an internal node, traverse the children + for (nibble, (address, hash)) in branch.children_iter() { + let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); + let mut child_path_prefix = current_path_prefix.clone(); + child_path_prefix.0.push(nibble as u8); + let child_subtrie = SubTrieMetadata { + root_address: address, + root_hash: hash.clone(), + parent, + depth: depth.saturating_add(1), + path_prefix: child_path_prefix, + #[cfg(feature = "ethhash")] + has_peers: num_children != 1, + }; + self.visit_trie_helper( + child_subtrie, + visited, + trie_stats, + progress_bar, + hash_check, + )?; + } + } + Node::Leaf(leaf) => { + // collect the depth distribution + let depth_count = trie_stats.depths.entry(depth).or_insert(0); + *depth_count = depth_count.saturating_add(1); + // collect kv count + trie_stats.kv_count = trie_stats.kv_count.saturating_add(1); + // collect kv pair bytes - this is the minimum number of bytes needed to store the data + let key_bytes = current_path_prefix.0.len().div_ceil(2); + let value_bytes = leaf.value.len(); + trie_stats.kv_bytes = trie_stats + .kv_bytes + .saturating_add(key_bytes as u64) + .saturating_add(value_bytes as u64); } } @@ -219,7 +352,10 @@ impl NodeStore { &self, visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, - ) -> Result<(), CheckerError> { + ) -> Result { + let mut area_counts: HashMap = HashMap::new(); + let mut multi_page_area_count = 0u64; + let mut free_list_iter = self.free_list_iter(0); while let Some(free_area) = free_list_iter.next_with_metadata() { let FreeAreaWithMetadata { @@ -240,9 +376,26 @@ impl NodeStore { }); } visited.insert_area(addr, area_size, StoredAreaParent::FreeList(parent))?; + { + // collect the free lists area distribution + let area_count = area_counts.entry(area_size).or_insert(0); + *area_count = area_count.saturating_add(1); + // collect the multi-page area count + if page_start(addr) + != page_start( + addr.advance(area_size) + .expect("impossible since we checked in visited.insert_area()"), + ) + { + multi_page_area_count = multi_page_area_count.saturating_add(1); + } + } update_progress_bar(progress_bar, visited); } - Ok(()) + Ok(FreeListsStats { + area_counts, + multi_page_area_count, + }) } const fn check_area_aligned( @@ -376,6 +529,7 @@ mod test { high_watermark: u64, root_address: LinearAddress, root_hash: HashType, + stats: TrieStats, } /// Generate a test trie with the following structure: @@ -393,13 +547,20 @@ mod test { #[expect(clippy::arithmetic_side_effects)] fn gen_test_trie(nodestore: &mut NodeStore) -> TestTrie { let mut high_watermark = NodeStoreHeader::SIZE; + let mut total_bytes_written = 0; + let mut area_counts: HashMap = HashMap::new(); let leaf = Node::Leaf(LeafNode { partial_path: Path::from_nibbles_iterator(std::iter::repeat_n([4, 5], 30).flatten()), value: Box::new([6, 7, 8]), }); let leaf_addr = LinearAddress::new(high_watermark).unwrap(); let leaf_hash = hash_node(&leaf, &Path::from([2, 0, 3, 1])); - high_watermark += test_write_new_node(nodestore, &leaf, high_watermark); + let (bytes_written, stored_area_size) = + test_write_new_node(nodestore, &leaf, high_watermark); + high_watermark += stored_area_size; + total_bytes_written += bytes_written; + let area_count = area_counts.entry(stored_area_size).or_insert(0); + *area_count = area_count.saturating_add(1); let mut branch_children = BranchNode::empty_children(); branch_children[1] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); @@ -410,7 +571,12 @@ mod test { })); let branch_addr = LinearAddress::new(high_watermark).unwrap(); let branch_hash = hash_node(&branch, &Path::from([2, 0])); - high_watermark += test_write_new_node(nodestore, &branch, high_watermark); + let (bytes_written, stored_area_size) = + test_write_new_node(nodestore, &branch, high_watermark); + high_watermark += stored_area_size; + total_bytes_written += bytes_written; + let area_count = area_counts.entry(stored_area_size).or_insert(0); + *area_count = area_count.saturating_add(1); let mut root_children = BranchNode::empty_children(); root_children[0] = Some(Child::AddressWithHash(branch_addr, branch_hash)); @@ -421,7 +587,12 @@ mod test { })); let root_addr = LinearAddress::new(high_watermark).unwrap(); let root_hash = hash_node(&root, &Path::new()); - high_watermark += test_write_new_node(nodestore, &root, high_watermark); + let (bytes_written, stored_area_size) = + test_write_new_node(nodestore, &root, high_watermark); + high_watermark += stored_area_size; + total_bytes_written += bytes_written; + let area_count = area_counts.entry(stored_area_size).or_insert(0); + *area_count = area_count.saturating_add(1); // write the header test_write_header( @@ -431,11 +602,23 @@ mod test { FreeLists::default(), ); + let trie_stats = TrieStats { + trie_bytes: total_bytes_written, + kv_count: 1, + kv_bytes: 32 + 3, // 32 bytes for the key, 3 bytes for the value + area_counts, + branching_factors: HashMap::from([(1, 2)]), + depths: HashMap::from([(2, 1)]), + low_occupancy_area_count: 0, + multi_page_area_count: 0, + }; + TestTrie { nodes: vec![(leaf, leaf_addr), (branch, branch_addr), (root, root_addr)], high_watermark, root_address: root_addr, root_hash, + stats: trie_stats, } } @@ -453,7 +636,7 @@ mod test { // verify that all of the space is accounted for - since there is no free area let mut visited = LinearAddressRangeSet::new(test_trie.high_watermark).unwrap(); - nodestore + let stats = nodestore .visit_trie( test_trie.root_address, test_trie.root_hash, @@ -464,6 +647,7 @@ mod test { .unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); + assert_eq!(stats, test_trie.stats); } #[test] @@ -532,6 +716,8 @@ mod test { // write free areas let mut high_watermark = NodeStoreHeader::SIZE; + let mut free_area_counts: HashMap = HashMap::new(); + let mut multi_page_area_count = 0u64; let mut freelist = FreeLists::default(); for (area_index, area_size) in AREA_SIZES.iter().enumerate() { let mut next_free_block = None; @@ -544,20 +730,32 @@ mod test { high_watermark, ); next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); + let start_addr = LinearAddress::new(high_watermark).unwrap(); + let end_addr = start_addr.advance(*area_size).unwrap(); + if page_start(start_addr) != page_start(end_addr) { + multi_page_area_count = multi_page_area_count.saturating_add(1); + } high_watermark += area_size; } - freelist[area_index] = next_free_block; + if num_free_areas > 0 { + free_area_counts.insert(*area_size, num_free_areas); + } } + let expected_free_lists_stats = FreeListsStats { + area_counts: free_area_counts, + multi_page_area_count, + }; // write header test_write_header(&mut nodestore, high_watermark, None, freelist); // test that the we traversed all the free areas let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - nodestore.visit_freelist(&mut visited, None).unwrap(); + let actual_free_lists_stats = nodestore.visit_freelist(&mut visited, None).unwrap(); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); + assert_eq!(actual_free_lists_stats, expected_free_lists_stats); } #[test] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 859ab181ab05..300c47a1a734 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -40,7 +40,7 @@ pub mod logger; /// Macros module for defining macros used in the storage module pub mod macros; // re-export these so callers don't need to know where they are -pub use checker::CheckOpt; +pub use checker::{CheckOpt, CheckerReport, FreeListsStats, TrieStats}; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index c0f8d98575a0..ade4c7025af0 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -38,7 +38,7 @@ use metrics::counter; use crate::{CacheReadStrategy, LinearAddress, MaybePersistedNode, SharedNode}; -use super::{FileIoError, ReadableStorage, WritableStorage}; +use super::{FileIoError, OffsetReader, ReadableStorage, WritableStorage}; /// A [`ReadableStorage`] and [`WritableStorage`] backed by a file pub struct FileBacked { @@ -141,7 +141,7 @@ impl FileBacked { } impl ReadableStorage for FileBacked { - fn stream_from(&self, addr: u64) -> Result, FileIoError> { + fn stream_from(&self, addr: u64) -> Result, FileIoError> { counter!("firewood.read_node", "from" => "file").increment(1); Ok(Box::new(PredictiveReader::new(self, addr))) } @@ -295,6 +295,12 @@ impl Read for PredictiveReader<'_> { } } +impl OffsetReader for PredictiveReader<'_> { + fn offset(&self) -> u64 { + self.offset + } +} + #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index ddbeee1d2253..81a7e823d2db 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -10,9 +10,9 @@ reason = "Found 1 occurrences after enabling the lint." )] -use super::{FileIoError, ReadableStorage, WritableStorage}; +use super::{FileIoError, OffsetReader, ReadableStorage, WritableStorage}; use metrics::counter; -use std::io::{Cursor, Read}; +use std::io::Cursor; use std::sync::Mutex; #[derive(Debug, Default)] @@ -44,7 +44,7 @@ impl WritableStorage for MemStore { } impl ReadableStorage for MemStore { - fn stream_from(&self, addr: u64) -> Result, FileIoError> { + fn stream_from(&self, addr: u64) -> Result, FileIoError> { counter!("firewood.read_node", "from" => "memory").increment(1); let bytes = self .bytes diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index a0bea787b00b..042647ab9be9 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -20,7 +20,7 @@ )] use std::fmt::Debug; -use std::io::Read; +use std::io::{Cursor, Read}; use std::ops::Deref; use std::path::PathBuf; @@ -122,7 +122,7 @@ pub trait ReadableStorage: Debug + Sync + Send { /// # Returns /// /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. - fn stream_from(&self, addr: u64) -> Result, FileIoError>; + fn stream_from(&self, addr: u64) -> Result, FileIoError>; /// Return the size of the underlying storage, in bytes fn size(&self) -> Result; @@ -198,3 +198,16 @@ pub trait WritableStorage: ReadableStorage { /// Add a new entry to the freelist cache fn add_to_free_list_cache(&self, _addr: LinearAddress, _next: Option) {} } + +pub trait OffsetReader: Read { + fn offset(&self) -> u64; +} + +impl OffsetReader for Cursor +where + Cursor: Read, +{ + fn offset(&self) -> u64 { + self.position() + } +} diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 5e7abbe3f7b0..3fdca7f4c252 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -314,7 +314,7 @@ impl Node { } /// Given a reader, return a [Node] from those bytes - pub fn from_reader(mut serialized: impl Read) -> Result { + pub fn from_reader(mut serialized: &mut impl Read) -> Result { match serialized.read_byte()? { 255 => { // this is a freed area @@ -517,7 +517,7 @@ than 126 bytes as the length would be encoded in multiple bytes. assert_eq!(serialized.len(), expected_length); let mut cursor = Cursor::new(&serialized); cursor.set_position(1); - let deserialized = Node::from_reader(cursor).unwrap(); + let deserialized = Node::from_reader(&mut cursor).unwrap(); assert_eq!(node, deserialized); } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index d5665659ae7b..b4ffcc8f5b7b 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -839,7 +839,7 @@ pub mod test_utils { nodestore: &NodeStore, node: &Node, offset: u64, - ) -> u64 { + ) -> (u64, u64) { #![expect(clippy::indexing_slicing)] let mut encoded_node = Vec::new(); node.as_bytes(0, &mut encoded_node); @@ -847,11 +847,14 @@ pub mod test_utils { let area_size_index = area_size_to_index(encoded_node_len).unwrap(); let mut stored_area_bytes = Vec::new(); node.as_bytes(area_size_index, &mut stored_area_bytes); + let bytes_written = (stored_area_bytes.len() as u64) + .checked_sub(1) + .expect("serialized node should be at least 1 byte"); // -1 for area size index nodestore .storage .write(offset, stored_area_bytes.as_slice()) .unwrap(); - AREA_SIZES[area_size_index as usize] + (bytes_written, AREA_SIZES[area_size_index as usize]) } // Helper function to write a free area to the given offset. diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 039bdf77a7d3..df8e3494bbf7 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -685,21 +685,8 @@ impl NodeStore { return Ok(node); } - debug_assert!(addr.is_aligned()); - - // saturating because there is no way we can be reading at u64::MAX - // and this will fail very soon afterwards - let actual_addr = addr.get().saturating_add(1); // skip the length byte + let (node, _) = self.read_node_with_num_bytes_from_disk(addr)?; - let _span = fastrace::local::LocalSpan::enter_with_local_parent("read_and_deserialize"); - - let area_stream = self.storage.stream_from(actual_addr)?; - let node: SharedNode = Node::from_reader(area_stream) - .map_err(|e| { - self.storage - .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) - })? - .into(); match self.storage.cache_read_strategy() { CacheReadStrategy::All => { self.storage.cache_node(addr, node.clone()); @@ -711,9 +698,43 @@ impl NodeStore { } CacheReadStrategy::WritesOnly => {} } + Ok(node) } + pub(crate) fn read_node_with_num_bytes_from_disk( + &self, + addr: LinearAddress, + ) -> Result<(SharedNode, u64), FileIoError> { + debug_assert!(addr.is_aligned()); + + // saturating because there is no way we can be reading at u64::MAX + // and this will fail very soon afterwards + let actual_addr = addr.get().saturating_add(1); // skip the length byte + + let _span = fastrace::local::LocalSpan::enter_with_local_parent("read_and_deserialize"); + + let mut area_stream = self.storage.stream_from(actual_addr)?; + let offset_before = area_stream.offset(); + let node: SharedNode = Node::from_reader(&mut area_stream) + .map_err(|e| { + self.storage + .file_io_error(e, actual_addr, Some("read_node_from_disk".to_string())) + })? + .into(); + let length = area_stream + .offset() + .checked_sub(offset_before) + .ok_or_else(|| { + self.file_io_error( + Error::other("Reader offset went backwards"), + actual_addr, + Some("read_node_with_num_bytes_from_disk".to_string()), + ) + })?; + Ok((node, length)) + } + /// Returns (index, `area_size`) for the stored area at `addr`. /// `index` is the index of `area_size` in the array of valid block sizes. /// @@ -744,6 +765,20 @@ impl NodeStore { Ok((index, size)) } + + pub(crate) fn physical_size(&self) -> Result { + self.storage.size() + } + + #[cold] + pub(crate) fn file_io_error( + &self, + error: Error, + addr: u64, + context: Option, + ) -> FileIoError { + self.storage.file_io_error(error, addr, context) + } } impl HashedNodeReader for Arc From 84dfb5812959be93557b50593f2bf2d2fec6ba2a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 1 Aug 2025 15:42:22 -0700 Subject: [PATCH 0869/1053] feat(fwdctl): Add support for dump formats (#1161) Found myself needing to dump dot graphs during debugging. This was too hard, since the API for generating these wasn't exposed to fwdctl. Added it to fwdctl, as well as refactoring the dump methods to use writers instead of returning a string. --- firewood/src/db.rs | 4 +- firewood/src/manager.rs | 4 +- firewood/src/merkle.rs | 84 ++++++++++++++------------------ firewood/src/merkle/tests/mod.rs | 7 +-- firewood/src/stream.rs | 2 +- fwdctl/src/dump.rs | 57 ++++++++++++++++++---- 6 files changed, 93 insertions(+), 65 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e4d14ab46eec..d1437240e1ae 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -325,9 +325,7 @@ impl Db { pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); let merkle = Merkle::from(latest_rev_nodestore); - // TODO: This should be a stream - let output = merkle.dump()?; - write!(w, "{output}") + merkle.dump(w).map_err(std::io::Error::other) } /// Get a copy of the database metrics diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 485d9e7e8d66..7f50bcb73e5d 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -234,7 +234,9 @@ impl RevisionManager { if crate::logger::trace_enabled() { let merkle = Merkle::from(committed); - trace!("{}", merkle.dump().expect("failed to dump merkle")); + if let Ok(s) = merkle.dump_to_string() { + trace!("{s}"); + } } Ok(()) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 263f252b0a3f..0c5b29cbaec8 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -16,7 +16,7 @@ use firewood_storage::{ use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; -use std::fmt::{Debug, Write}; +use std::fmt::Debug; use std::future::ready; use std::io::Error; use std::iter::once; @@ -29,49 +29,29 @@ pub type Key = Box<[u8]>; /// Values are boxed u8 slices pub type Value = Box<[u8]>; -// convert a set of nibbles into a printable string -// panics if there is a non-nibble byte in the set -#[cfg(not(feature = "branch_factor_256"))] -fn nibbles_formatter>(nib: X) -> String { - nib.into_iter() - .map(|c| { - *b"0123456789abcdef" - .get(c as usize) - .expect("requires nibbles") as char - }) - .collect::() -} - -#[cfg(feature = "branch_factor_256")] -fn nibbles_formatter>(nib: X) -> String { - let collected: Box<[u8]> = nib.into_iter().collect(); - hex::encode(&collected) -} - macro_rules! write_attributes { ($writer:ident, $node:expr, $value:expr) => { if !$node.partial_path.0.is_empty() { - write!( - $writer, - " pp={}", - nibbles_formatter($node.partial_path.0.clone()) - ) - .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; + write!($writer, " pp={:x}", $node.partial_path) + .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; } if !$value.is_empty() { match std::str::from_utf8($value) { Ok(string) if string.chars().all(char::is_alphanumeric) => { - write!($writer, " val={}", string) + write!($writer, " val={:.6}", string) .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; + if string.len() > 6 { + $writer.write_all(b"...").map_err(|e| { + FileIoError::from_generic_no_file(e, "write attributes") + })?; + } } _ => { let hex = hex::encode($value); + write!($writer, " val={:.6}", hex) + .map_err(|e| FileIoError::from_generic_no_file(e, "write attributes"))?; if hex.len() > 6 { - write!($writer, " val={:.6}...", hex).map_err(|e| { - FileIoError::from_generic_no_file(e, "write attributes") - })?; - } else { - write!($writer, " val={}", hex).map_err(|e| { + $writer.write_all(b"...").map_err(|e| { FileIoError::from_generic_no_file(e, "write attributes") })?; } @@ -478,12 +458,12 @@ impl Merkle { impl Merkle { /// Dump a node, recursively, to a dot file - pub(crate) fn dump_node( + pub(crate) fn dump_node( &self, node: &MaybePersistedNode, hash: Option<&HashType>, seen: &mut HashSet, - writer: &mut dyn Write, + writer: &mut W, ) -> Result<(), FileIoError> { writeln!(writer, " {node}[label=\"{node}") .map_err(Error::other) @@ -536,31 +516,41 @@ impl Merkle { /// /// Dot files can be rendered using `dot -Tpng -o output.png input.dot` /// or online at - pub(crate) fn dump(&self) -> Result { + /// + /// # Errors + /// + /// Returns an error if writing to the output writer fails. + pub(crate) fn dump(&self, writer: &mut W) -> Result<(), Error> { let root = self.nodestore.root_as_maybe_persisted_node(); - let mut result = String::new(); - writeln!(result, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; + writeln!(writer, "digraph Merkle {{\n rankdir=LR;").map_err(Error::other)?; if let (Some(root), Some(root_hash)) = (root, self.nodestore.root_hash()) { - writeln!(result, " root -> {root}") + writeln!(writer, " root -> {root}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None)) .map_err(Error::other)?; let mut seen = HashSet::new(); - self.dump_node( - &root, - Some(&root_hash.into_hash_type()), - &mut seen, - &mut result, - ) - .map_err(Error::other)?; + self.dump_node(&root, Some(&root_hash.into_hash_type()), &mut seen, writer) + .map_err(Error::other)?; } - write!(result, "}}") + writeln!(writer, "}}") .map_err(Error::other) .map_err(|e| FileIoError::new(e, None, 0, None)) .map_err(Error::other)?; - Ok(result) + Ok(()) + } + /// Dump the trie to a string (for testing or logging). + /// + /// This is a convenience function for tests that need the dot output as a string. + /// + /// # Errors + /// + /// Returns an error if writing to the string fails. + pub(crate) fn dump_to_string(&self) -> Result { + let mut buffer = Vec::new(); + self.dump(&mut buffer)?; + String::from_utf8(buffer).map_err(Error::other) } } diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index e483b06655bd..72fa0ac40a12 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -11,6 +11,7 @@ mod triehash; mod unvalidated; use std::collections::HashMap; +use std::fmt::Write; use super::*; use firewood_storage::{Committed, MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; @@ -637,7 +638,7 @@ fn test_root_hash_simple_insertions() -> Result<(), Error> { ("horse", "stallion"), ("ddd", "ok"), ]) - .dump() + .dump_to_string() .unwrap(); Ok(()) } @@ -773,7 +774,7 @@ fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { let (new_hashes, _) = items.iter().rev().fold( (vec![], complete_immutable_merkle), |(mut new_hashes, immutable_merkle_before_removal), (k, _)| { - let before = immutable_merkle_before_removal.dump().unwrap(); + let before = immutable_merkle_before_removal.dump_to_string().unwrap(); let mut merkle = Merkle::from( NodeStore::new(immutable_merkle_before_removal.nodestore()).unwrap(), ); @@ -784,7 +785,7 @@ fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { immutable_merkle_after_removal.nodestore.root_hash(), k, before, - immutable_merkle_after_removal.dump().unwrap(), + immutable_merkle_after_removal.dump_to_string().unwrap(), )); (new_hashes, immutable_merkle_after_removal) }, diff --git a/firewood/src/stream.rs b/firewood/src/stream.rs index f67bc203584c..bbe224c10089 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/stream.rs @@ -1185,7 +1185,7 @@ mod tests { let immutable_merkle: Merkle, _>> = merkle.try_into().unwrap(); - println!("{}", immutable_merkle.dump().unwrap()); + println!("{}", immutable_merkle.dump_to_string().unwrap()); merkle = immutable_merkle.fork().unwrap(); let mut stream = merkle.key_value_iter(); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 0116cdc23c23..e498f578f245 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -91,16 +91,16 @@ pub struct Options { pub max_key_count: Option, /// The output format of database dump. - /// Possible Values: ["csv", "json", "stdout"]. + /// Possible Values: ["csv", "json", "stdout", "dot"]. /// Defaults to "stdout" #[arg( short = 'o', long, required = false, value_name = "OUTPUT_FORMAT", - value_parser = ["csv", "json", "stdout"], + value_parser = ["csv", "json", "stdout", "dot"], default_value = "stdout", - help = "Output format of database dump, default to stdout. CSV and JSON formats are available." + help = "Output format of database dump, default to stdout. CSV, JSON, and DOT formats are available." )] pub output_format: String, @@ -123,6 +123,28 @@ pub struct Options { pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {opts:?}"); + // Check if dot format is used with unsupported options + if opts.output_format == "dot" { + if opts.start_key.is_some() || opts.start_key_hex.is_some() { + return Err(api::Error::InternalError(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Dot format does not support --start-key or --start-key-hex options", + )))); + } + if opts.stop_key.is_some() || opts.stop_key_hex.is_some() { + return Err(api::Error::InternalError(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Dot format does not support --stop-key or --stop-key-hex options", + )))); + } + if opts.max_key_count.is_some() { + return Err(api::Error::InternalError(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Dot format does not support --max-key-count option", + )))); + } + } + let cfg = DbConfig::builder().truncate(false); let db = Db::new(opts.db.clone(), cfg.build()).await?; let latest_hash = db.root_hash().await?; @@ -132,6 +154,13 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { }; let latest_rev = db.revision(latest_hash).await?; + let Some(mut output_handler) = + create_output_handler(opts, &db).expect("Error creating output handler") + else { + // dot format is generated in the handler + return Ok(()); + }; + let start_key = opts .start_key .clone() @@ -141,7 +170,6 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let mut key_count: u32 = 0; let mut stream = MerkleKeyValueStream::from_key(&latest_rev, start_key); - let mut output_handler = create_output_handler(opts).expect("Error creating output handler"); while let Some(item) = stream.next().await { match item { @@ -283,7 +311,8 @@ impl OutputHandler for StdoutOutputHandler { fn create_output_handler( opts: &Options, -) -> Result, Box> { + db: &Db, +) -> Result>, Box> { let hex = opts.hex; let mut file_name = opts.output_file_name.clone(); file_name.set_extension(opts.output_format.as_str()); @@ -291,21 +320,29 @@ fn create_output_handler( "csv" => { println!("Dumping to {}", file_name.display()); let file = File::create(file_name)?; - Ok(Box::new(CsvOutputHandler { + Ok(Some(Box::new(CsvOutputHandler { writer: csv::Writer::from_writer(file), hex, - })) + }))) } "json" => { println!("Dumping to {}", file_name.display()); let file = File::create(file_name)?; - Ok(Box::new(JsonOutputHandler { + Ok(Some(Box::new(JsonOutputHandler { writer: BufWriter::new(file), hex, is_first: true, - })) + }))) + } + "stdout" => Ok(Some(Box::new(StdoutOutputHandler { hex }))), + "dot" => { + println!("Dumping to {}", file_name.display()); + let file = File::create(file_name)?; + let mut writer = BufWriter::new(file); + // For dot format, we generate the output immediately since it doesn't use streaming + db.dump_sync(&mut writer)?; + Ok(None) } - "stdout" => Ok(Box::new(StdoutOutputHandler { hex })), _ => unreachable!(), } } From 9fac19aa56c16888959c712c56f7dfc522366ecd Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 1 Aug 2025 15:55:26 -0700 Subject: [PATCH 0870/1053] feat(ffi): Remove the Arc wrapper around Proposal (#1160) Ron mentioned that having the proposals be cloneable posed a problem where we wanted to assert that a proposal was no longer in scope once committed. After some digging, I realized that this was unnecessary. There was the `ProposalBase` enum that we used to build a layerable in-memory database off an existing view (like the EmptyDb). However, neither are in use and created confusion with the other Proposal object. So, they were removed as part of unwrapping the Arc around proposals. A nit change also was to give names to all of the lifetimes in the proposal and db traits. Naming the lifetimes makes it easier to understand their purpose (though, I avoided the FFI package to save myself some rebase pains). --- Cargo.toml | 1 + ffi/src/lib.rs | 4 +- firewood/Cargo.toml | 1 - firewood/src/db.rs | 147 +++++++++---------------- firewood/src/v2/api.rs | 37 +++---- firewood/src/v2/emptydb.rs | 179 ------------------------------ firewood/src/v2/mod.rs | 6 - firewood/src/v2/propose.rs | 219 ------------------------------------- storage/Cargo.toml | 7 +- storage/src/checker/mod.rs | 2 +- 10 files changed, 77 insertions(+), 526 deletions(-) delete mode 100644 firewood/src/v2/emptydb.rs delete mode 100644 firewood/src/v2/propose.rs diff --git a/Cargo.toml b/Cargo.toml index 3e2389b14d2c..c08eb0901399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ firewood-ffi = { path = "ffi", version = "0.0.9" } firewood-triehash = { path = "triehash", version = "0.0.9" } # common dependencies +aquamarine = "0.6.0" clap = { version = "4.5.41", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index b4eac8011fc9..0b33f903e926 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -22,7 +22,7 @@ use std::ops::Deref; use std::os::unix::ffi::OsStrExt as _; use std::path::PathBuf; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Mutex, RwLock}; use firewood::db::{ BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal, @@ -60,7 +60,7 @@ pub struct DatabaseHandle<'p> { /// List of oustanding proposals, by ID // Keep proposals first, as they must be dropped before the database handle is dropped due to lifetime // issues. - proposals: RwLock>>>, + proposals: RwLock>>, /// A single cached view to improve performance of reads while committing cached_view: Mutex)>>, diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 67cc3138ae66..5a0b3f32fddb 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,7 +34,6 @@ test-case.workspace = true thiserror.workspace = true tokio.workspace = true # Regular dependencies -aquamarine = "0.6.0" async-trait = "0.1.88" futures = "0.3.31" typed-builder = "0.21.0" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index d1437240e1ae..7a1b185b045e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -22,7 +22,6 @@ use std::io::Write; use std::num::NonZeroUsize; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::AtomicBool; use thiserror::Error; use typed_builder::TypedBuilder; @@ -93,10 +92,10 @@ impl DbViewSyncBytes for Arc, FileBacked>> { #[async_trait] impl api::DbView for HistoricalRev { - type Stream<'a> - = MerkleKeyValueStream<'a, Self> + type Stream<'view> + = MerkleKeyValueStream<'view, Self> where - Self: 'a; + Self: 'view; async fn root_hash(&self) -> Result, api::Error> { Ok(HashedNodeReader::root_hash(self)) @@ -158,16 +157,13 @@ pub struct Db { } #[async_trait] -impl api::Db for Db -where - for<'p> Proposal<'p>: api::Proposal, -{ +impl api::Db for Db { type Historical = NodeStore; - type Proposal<'p> - = Proposal<'p> + type Proposal<'db> + = Proposal<'db> where - Self: 'p; + Self: 'db; async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { let nodestore = self.manager.revision(root_hash)?; @@ -183,13 +179,10 @@ where } #[fastrace::trace(short_name = true)] - async fn propose<'p, K: KeyType, V: ValueType>( - &'p self, + async fn propose<'db, K: KeyType, V: ValueType>( + &'db self, batch: api::Batch, - ) -> Result>, api::Error> - where - Self: 'p, - { + ) -> Result, api::Error> { let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); @@ -223,9 +216,7 @@ where Ok(Self::Proposal { nodestore: immutable, db: self, - committed: AtomicBool::new(false), - } - .into()) + }) } } @@ -261,12 +252,19 @@ impl Db { } /// Synchronously get the root hash of the latest revision. + #[cfg(not(feature = "ethhash"))] pub fn root_hash_sync(&self) -> Result, api::Error> { - let hash = self.manager.root_hash()?; - #[cfg(not(feature = "ethhash"))] - return Ok(hash); - #[cfg(feature = "ethhash")] - return Ok(Some(hash.unwrap_or_else(firewood_storage::empty_trie_hash))); + self.manager.root_hash().map_err(api::Error::from) + } + + /// Synchronously get the root hash of the latest revision. + #[cfg(feature = "ethhash")] + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(Some( + self.manager + .root_hash()? + .unwrap_or_else(firewood_storage::empty_trie_hash), + )) } /// Synchronously get a revision from a root hash @@ -283,9 +281,9 @@ impl Db { /// propose a new batch synchronously pub fn propose_sync( - &'_ self, + &self, batch: Batch, - ) -> Result>, api::Error> { + ) -> Result, api::Error> { let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); @@ -309,11 +307,10 @@ impl Db { self.metrics.proposals.increment(1); - Ok(Arc::new(Proposal { + Ok(Proposal { nodestore: immutable, db: self, - committed: AtomicBool::new(false), - })) + }) } /// Dump the Trie of the latest revision. @@ -345,46 +342,38 @@ impl Db { #[derive(Debug)] /// A user-visible database proposal -pub struct Proposal<'p> { +pub struct Proposal<'db> { nodestore: Arc, FileBacked>>, - db: &'p Db, - committed: AtomicBool, + db: &'db Db, } impl Proposal<'_> { /// Get the root hash of the proposal synchronously - pub fn start_commit(&self) -> Result<(), api::Error> { - if self - .committed - .swap(true, std::sync::atomic::Ordering::Relaxed) - { - return Err(api::Error::AlreadyCommitted); - } - Ok(()) + #[cfg(not(feature = "ethhash"))] + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(self.nodestore.root_hash()) } /// Get the root hash of the proposal synchronously + #[cfg(feature = "ethhash")] pub fn root_hash_sync(&self) -> Result, api::Error> { - #[cfg(not(feature = "ethhash"))] - return Ok(self.nodestore.root_hash()); - #[cfg(feature = "ethhash")] - return Ok(Some( + Ok(Some( self.nodestore .root_hash() .unwrap_or_else(firewood_storage::empty_trie_hash), - )); + )) } } #[async_trait] impl api::DbView for Proposal<'_> { - type Stream<'b> - = MerkleKeyValueStream<'b, NodeStore, FileBacked>> + type Stream<'view> + = MerkleKeyValueStream<'view, NodeStore, FileBacked>> where - Self: 'b; + Self: 'view; async fn root_hash(&self) -> Result, api::Error> { - Ok(self.nodestore.root_hash()) + self.root_hash_sync() } async fn val(&self, key: K) -> Result, api::Error> { @@ -421,27 +410,25 @@ impl api::DbView for Proposal<'_> { } #[async_trait] -impl<'a> api::Proposal for Proposal<'a> { - type Proposal = Proposal<'a>; +impl<'db> api::Proposal for Proposal<'db> { + type Proposal = Proposal<'db>; #[fastrace::trace(short_name = true)] async fn propose( - self: Arc, + &self, batch: api::Batch, - ) -> Result, api::Error> { - Ok(self.create_proposal(batch)?.into()) + ) -> Result { + self.create_proposal(batch) } - async fn commit(self: Arc) -> Result<(), api::Error> { - self.start_commit()?; + async fn commit(self) -> Result<(), api::Error> { Ok(self.db.manager.commit(self.nodestore.clone())?) } } impl Proposal<'_> { /// Commit a proposal synchronously - pub fn commit_sync(self: Arc) -> Result<(), api::Error> { - self.start_commit()?; + pub fn commit_sync(self) -> Result<(), api::Error> { Ok(self.db.manager.commit(self.nodestore.clone())?) } @@ -449,8 +436,8 @@ impl Proposal<'_> { pub fn propose_sync( &self, batch: api::Batch, - ) -> Result, api::Error> { - Ok(self.create_proposal(batch)?.into()) + ) -> Result { + self.create_proposal(batch) } #[crate::metrics("firewood.proposal.create", "database proposal creation")] @@ -482,7 +469,6 @@ impl Proposal<'_> { Ok(Self { nodestore: immutable, db: self.db, - committed: AtomicBool::new(false), }) } } @@ -490,10 +476,6 @@ impl Proposal<'_> { #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] - #![expect( - clippy::default_trait_access, - reason = "Found 1 occurrences after enabling the lint." - )] use std::ops::{Deref, DerefMut}; use std::path::PathBuf; @@ -502,27 +484,10 @@ mod test { use rand::rng; use crate::db::Db; - use crate::v2::api::{Db as _, DbView as _, Error, Proposal as _}; + use crate::v2::api::{Db as _, DbView as _, Proposal as _}; use super::{BatchOp, DbConfig}; - #[tokio::test] - async fn test_cloned_proposal_error() { - let db = testdb().await; - let proposal = db - .propose::, Vec>(Default::default()) - .await - .unwrap(); - let cloned = proposal.clone(); - - // attempt to commit the clone; this should fail - let result = cloned.commit().await; - assert!(result.is_ok()); - - let result = proposal.commit().await; - assert!(matches!(result, Err(Error::AlreadyCommitted)), "{result:?}"); - } - #[tokio::test] async fn test_proposal_reads() { let db = testdb().await; @@ -647,14 +612,14 @@ mod test { key: b"k2", value: b"v2", }]; - let proposal2 = proposal1.clone().propose(batch2).await.unwrap(); + let proposal2 = proposal1.propose(batch2).await.unwrap(); assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); let batch3 = vec![BatchOp::Put { key: b"k3", value: b"v3", }]; - let proposal3 = proposal2.clone().propose(batch3).await.unwrap(); + let proposal3 = proposal2.propose(batch3).await.unwrap(); assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); // the proposal is dropped here, but the underlying @@ -753,7 +718,7 @@ mod test { // create two proposals, second one has a base of the first one let proposal1 = db.propose(batch1).await.unwrap(); - let proposal2 = proposal1.clone().propose(batch2).await.unwrap(); + let proposal2 = proposal1.propose(batch2).await.unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal let mut kviter = keys.iter().zip(vals.iter()); @@ -885,13 +850,7 @@ mod test { let mut proposals = vec![db.propose(batches_iter.next().unwrap()).await.unwrap()]; for batch in batches_iter { - let proposal = proposals - .last() - .unwrap() - .clone() - .propose(batch) - .await - .unwrap(); + let proposal = proposals.last().unwrap().propose(batch).await.unwrap(); proposals.push(proposal); } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 86861880a9fb..3242f06fbc6e 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -1,15 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::iter_not_returning_iterator, - reason = "Found 1 occurrences after enabling the lint." -)] -#![expect( - clippy::missing_errors_doc, - reason = "Found 3 occurrences after enabling the lint." -)] - use crate::manager::RevisionManagerError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; @@ -185,9 +176,9 @@ pub trait Db { type Historical: DbView; /// The type of a proposal - type Proposal<'p>: DbView + Proposal + type Proposal<'db>: DbView + Proposal where - Self: 'p; + Self: 'db; /// Get a reference to a specific view based on a hash /// @@ -217,12 +208,12 @@ pub trait Db { /// * `data` - A batch consisting of [BatchOp::Put] and /// [BatchOp::Delete] operations to apply /// - async fn propose<'p, K: KeyType, V: ValueType>( - &'p self, + async fn propose<'db, K: KeyType, V: ValueType>( + &'db self, data: Batch, - ) -> Result>, Error> + ) -> Result, Error> where - Self: 'p; + Self: 'db; } /// A view of the database at a specific time. @@ -236,9 +227,9 @@ pub trait Db { #[async_trait] pub trait DbView { /// The type of a stream of key/value pairs - type Stream<'a>: Stream> + type Stream<'view>: Stream> where - Self: 'a; + Self: 'view; /// Get the root hash for the current DbView /// @@ -280,14 +271,18 @@ pub trait DbView { /// If you always want to start at the beginning, [DbView::iter] is easier to use /// If you always provide a key, [DbView::iter_from] is easier to use /// + #[expect(clippy::missing_errors_doc)] fn iter_option(&self, first_key: Option) -> Result, Error>; /// Obtain a stream over the keys/values of this view, starting from the beginning + #[expect(clippy::missing_errors_doc)] + #[expect(clippy::iter_not_returning_iterator)] fn iter(&self) -> Result, Error> { self.iter_option(Option::::None) } /// Obtain a stream over the key/values, starting at a specific key + #[expect(clippy::missing_errors_doc)] fn iter_from(&self, first_key: K) -> Result, Error> { self.iter_option(Some(first_key)) } @@ -310,7 +305,7 @@ pub trait Proposal: DbView + Send + Sync { type Proposal: DbView + Proposal; /// Commit this revision - async fn commit(self: Arc) -> Result<(), Error>; + async fn commit(self) -> Result<(), Error>; /// Propose a new revision on top of an existing proposal /// @@ -320,10 +315,10 @@ pub trait Proposal: DbView + Send + Sync { /// /// # Return value /// - /// A reference to a new proposal + /// A new proposal /// async fn propose( - self: Arc, + &self, data: Batch, - ) -> Result, Error>; + ) -> Result; } diff --git a/firewood/src/v2/emptydb.rs b/firewood/src/v2/emptydb.rs deleted file mode 100644 index 7451c3d96f55..000000000000 --- a/firewood/src/v2/emptydb.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::api::{ - Batch, Db, DbView, Error, FrozenProof, FrozenRangeProof, HashKey, KeyType, ValueType, -}; -use super::propose::{Proposal, ProposalBase}; -use crate::merkle::{Key, Value}; -use async_trait::async_trait; -use futures::Stream; -use std::num::NonZeroUsize; -use std::sync::Arc; - -/// An `EmptyDb` is a simple implementation of `api::Db` -/// that doesn't store any data. It contains a single -/// `HistoricalImpl` that has no keys or values -#[derive(Debug)] -pub struct EmptyDb; - -/// `HistoricalImpl` is always empty, and there is only one, -/// since nothing can be committed to an `EmptyDb`. -#[derive(Debug)] -pub struct HistoricalImpl; - -#[async_trait] -impl Db for EmptyDb { - type Historical = HistoricalImpl; - - type Proposal<'p> = Proposal; - - async fn revision(&self, hash_key: HashKey) -> Result, Error> { - Err(Error::HashNotFound { provided: hash_key }) - } - - async fn root_hash(&self) -> Result, Error> { - Ok(None) - } - - async fn propose<'p, K, V>( - &'p self, - data: Batch, - ) -> Result>, Error> - where - K: KeyType, - V: ValueType, - { - Ok(Proposal::new( - ProposalBase::View(HistoricalImpl.into()), - data, - )) - } - - async fn all_hashes(&self) -> Result, Error> { - Ok(vec![]) - } -} - -#[async_trait] -impl DbView for HistoricalImpl { - type Stream<'a> = EmptyStreamer; - - async fn root_hash(&self) -> Result, Error> { - Ok(None) - } - - async fn val(&self, _key: K) -> Result, Error> { - Ok(None) - } - - async fn single_key_proof(&self, _key: K) -> Result { - Err(Error::RangeProofOnEmptyTrie) - } - - async fn range_proof( - &self, - _first_key: Option, - _last_key: Option, - _limit: Option, - ) -> Result { - Err(Error::RangeProofOnEmptyTrie) - } - - fn iter_option(&self, _first_key: Option) -> Result { - Ok(EmptyStreamer {}) - } -} - -#[derive(Debug)] -/// An empty streamer that doesn't stream any data -pub struct EmptyStreamer; - -impl Stream for EmptyStreamer { - type Item = Result<(Key, Value), Error>; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(None) - } -} - -#[cfg(test)] -#[expect(clippy::unwrap_used)] -mod tests { - use super::*; - use crate::v2::api::{BatchOp, Proposal}; - - #[tokio::test] - async fn basic_proposal() -> Result<(), Error> { - let db = EmptyDb; - - let batch = vec![ - BatchOp::Put { - key: b"k", - value: b"v", - }, - BatchOp::Delete { key: b"z" }, - ]; - - let proposal = db.propose(batch).await?; - - assert_eq!( - proposal.val(b"k").await.unwrap().unwrap(), - Box::from(b"v".as_slice()) - ); - - assert!(proposal.val(b"z").await.unwrap().is_none()); - - Ok(()) - } - - #[tokio::test] - async fn nested_proposal() -> Result<(), Error> { - let db = EmptyDb; - // create proposal1 which adds key "k" with value "v" and deletes "z" - let batch = vec![ - BatchOp::Put { - key: b"k", - value: b"v", - }, - BatchOp::Delete { key: b"z" }, - ]; - - let proposal1 = db.propose(batch).await?; - - // create proposal2 which adds key "z" with value "undo" - let proposal2 = proposal1 - .clone() - .propose(vec![BatchOp::Put { - key: b"z", - value: "undo", - }]) - .await?; - // both proposals still have (k,v) - assert_eq!(proposal1.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); - assert_eq!(proposal2.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); - // only proposal1 doesn't have z - assert!(proposal1.val(b"z").await.unwrap().is_none()); - // proposal2 has z with value "undo" - assert_eq!( - proposal2.val(b"z").await.unwrap().unwrap().to_vec(), - b"undo" - ); - - // create a proposal3 by adding the two proposals together, keeping the originals - let proposal3 = proposal1.as_ref() + proposal2.as_ref(); - assert_eq!(proposal3.val(b"k").await.unwrap().unwrap().to_vec(), b"v"); - assert_eq!( - proposal3.val(b"z").await.unwrap().unwrap().to_vec(), - b"undo" - ); - - // now consume proposal1 and proposal2 - proposal2.commit().await?; - - Ok(()) - } -} diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 39cf64e4217f..31453fa78a14 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -3,9 +3,3 @@ /// The public API pub mod api; - -/// The proposal -pub mod propose; - -/// An empty database implementation for testing -pub mod emptydb; diff --git a/firewood/src/v2/propose.rs b/firewood/src/v2/propose.rs deleted file mode 100644 index 76a12a02b5a2..000000000000 --- a/firewood/src/v2/propose.rs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::collections::BTreeMap; -use std::fmt::Debug; -use std::num::NonZeroUsize; -use std::sync::Arc; - -use async_trait::async_trait; -use futures::stream::Empty; - -use super::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; -use crate::merkle::{Key, Value}; - -#[derive(Clone, Debug)] -pub(crate) enum KeyOp { - Put(V), - Delete, -} - -#[derive(Debug)] -pub(crate) enum ProposalBase { - Proposal(Arc>), - View(Arc), -} - -// Implement Clone because T doesn't need to be Clone -// so an automatically derived Clone won't work -impl Clone for ProposalBase { - fn clone(&self) -> Self { - match self { - Self::Proposal(arg0) => Self::Proposal(arg0.clone()), - Self::View(arg0) => Self::View(arg0.clone()), - } - } -} - -/// A proposal is created either from the [[`crate::v2::api::Db`]] object -/// or from another proposal. Proposals are owned by the -/// caller. A proposal can only be committed if it has a -/// base of the current revision of the [[`crate::v2::api::Db`]]. -#[cfg_attr(doc, aquamarine::aquamarine)] -/// ```mermaid -/// graph LR -/// subgraph historical -/// direction BT -/// PH1 --> R1((R1)) -/// PH2 --> R1 -/// PH3 --> PH2 -/// end -/// R1 ~~~|"proposals on R1
    may not be committed"| R1 -/// subgraph committed_head -/// direction BT -/// R2 ~~~|"proposals on R2
    may be committed"| R2 -/// PC4 --> R2((R2)) -/// PC6 --> PC5 -/// PC5 --> R2 -/// PC6 ~~~|"Committing PC6
    creates two revisions"| PC6 -/// end -/// subgraph new_committing -/// direction BT -/// PN --> R3((R3)) -/// R3 ~~~|"R3 does not yet exist"| R3 -/// PN ~~~|"this proposal
    is committing"
    --
    could be
    PC4 or PC5| PN -/// end -/// historical ==> committed_head -/// committed_head ==> new_committing -/// ``` -#[derive(Debug)] -pub struct Proposal { - pub(crate) base: ProposalBase, - pub(crate) delta: BTreeMap>, -} - -// Implement Clone because T doesn't need to be Clone -// so an automatically derived Clone won't work -impl Clone for Proposal { - fn clone(&self) -> Self { - Self { - base: self.base.clone(), - delta: self.delta.clone(), - } - } -} - -impl Proposal { - pub(crate) fn new( - base: ProposalBase, - batch: api::Batch, - ) -> Arc { - let delta = batch - .into_iter() - .map(|op| match op { - api::BatchOp::Put { key, value } => ( - key.as_ref().to_vec().into_boxed_slice(), - KeyOp::Put(value.as_ref().to_vec().into_boxed_slice()), - ), - api::BatchOp::Delete { key } => { - (key.as_ref().to_vec().into_boxed_slice(), KeyOp::Delete) - } - api::BatchOp::DeleteRange { prefix } => { - (prefix.as_ref().to_vec().into_boxed_slice(), KeyOp::Delete) - } - }) - .collect::>(); - - Arc::new(Self { base, delta }) - } -} - -#[async_trait] -impl api::DbView for Proposal { - // TODO: Replace with the correct stream type for an in-memory proposal implementation - type Stream<'a> - = Empty> - where - T: 'a; - - async fn root_hash(&self) -> Result, api::Error> { - todo!(); - } - - async fn val(&self, key: K) -> Result, api::Error> { - // see if this key is in this proposal - let key = key.as_ref(); - let mut this = self; - // avoid recursion problems by looping over the proposal chain - loop { - match this.delta.get(key) { - Some(change) => { - // key is in `this` proposal, check for Put or Delete - break match change { - KeyOp::Put(val) => Ok(Some(val.clone())), - KeyOp::Delete => Ok(None), // key was deleted in this proposal - }; - } - None => match &this.base { - // key not in this proposal, so delegate to base - ProposalBase::Proposal(p) => this = p, - ProposalBase::View(view) => break view.val(key).await, - }, - } - } - } - - async fn single_key_proof(&self, _key: K) -> Result { - todo!(); - } - - async fn range_proof( - &self, - _first_key: Option, - _last_key: Option, - _limit: Option, - ) -> Result { - todo!(); - } - - fn iter_option( - &self, - _first_key: Option, - ) -> Result, api::Error> { - todo!(); - } -} - -#[async_trait] -impl api::Proposal for Proposal { - type Proposal = Proposal; - - async fn propose( - self: Arc, - data: api::Batch, - ) -> Result, api::Error> { - // find the Arc for this base proposal from the parent - Ok(Proposal::new(ProposalBase::Proposal(self), data)) - } - - async fn commit(self: Arc) -> Result<(), api::Error> { - match &self.base { - ProposalBase::Proposal(base) => base.clone().commit().await, - ProposalBase::View(_) => Ok(()), - } - } -} - -impl std::ops::Add for Proposal { - type Output = Arc>; - - fn add(self, rhs: Self) -> Self::Output { - let mut delta = self.delta.clone(); - - delta.extend(rhs.delta); - - let proposal = Proposal { - base: self.base, - delta, - }; - - Arc::new(proposal) - } -} - -impl std::ops::Add for &Proposal { - type Output = Arc>; - - fn add(self, rhs: Self) -> Self::Output { - let mut delta = self.delta.clone(); - - delta.extend(rhs.delta.clone()); - - let proposal = Proposal { - base: self.base.clone(), - delta, - }; - - Arc::new(proposal) - } -} diff --git a/storage/Cargo.toml b/storage/Cargo.toml index ebec4cd025cf..cd26a620b51e 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -18,6 +18,7 @@ rust-version.workspace = true [dependencies] # Workspace dependencies +aquamarine.workspace = true coarsetime.workspace = true fastrace.workspace = true hex.workspace = true @@ -32,13 +33,13 @@ bitfield = "0.19.1" bitflags = "2.9.1" bytemuck = "1.23.1" bytemuck_derive = "1.10.0" +derive-where = "1.5.0" enum-as-inner = "0.6.1" +indicatif = "0.18.0" integer-encoding = "4.0.2" lru = "0.16.0" semver = "1.0.26" triomphe = "0.1.14" -indicatif = "0.18.0" -derive-where = "1.5.0" # Optional dependencies bytes = { version = "1.10.1", optional = true } io-uring = { version = "0.7.8", optional = true } @@ -58,7 +59,7 @@ test-case.workspace = true logger = ["log"] branch_factor_256 = [] io-uring = ["dep:io-uring"] -ethhash = [ "dep:rlp", "dep:sha3", "dep:bytes" ] +ethhash = ["dep:rlp", "dep:sha3", "dep:bytes"] [[bench]] name = "serializer" diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index a99d40f87fab..72f2a105f692 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -534,7 +534,7 @@ mod test { /// Generate a test trie with the following structure: /// - #[cfg_attr(doc, aquamarine)] + #[cfg_attr(doc, aquamarine::aquamarine)] /// ```mermaid /// graph TD /// Root["Root Node
    partial_path: [2]
    children: [0] -> Branch"] From b0bc90f15d5d3e348745a8876a023ff4d49d1b1d Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 1 Aug 2025 16:07:58 -0700 Subject: [PATCH 0871/1053] chore: Bump version to v0.0.10 (#1165) --- CHANGELOG.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 ++++++-------- RELEASE.md | 20 ++++++++--------- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 576d5b157aab..6f4673c61e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ All notable changes to this project will be documented in this file. +## [0.0.10] - 2025-08-01 + +### 🚀 Features + +- *(async-iterator)* Implement (#1096) +- Export logs (#1070) +- Render the commit sha in fwdctl (#1109) +- Update proof types to be generic over mutable or immutable collections (#1121) +- Refactor value types to use the type alias (#1122) +- *(dumper)* Child links in hex (easy) (#1124) +- *(deferred-allocate)* Part 3: Defer allocate (#1061) +- *(checker)* Disable buggy ethhash checker (#1127) +- Add `Children` type alias (#1123) +- Make NodeStore more generic (#1134) +- *(checker)* Add progress bar (#1105) +- *(checker)* Checker errors include reference to parent (#1085) +- Update RangeProof structure (#1136) +- Update range\_proof signature (#1151) +- *(checker)* Add InvalidKey error +- *(deferred-persist)* Part 1: unpersisted gauge (#1116) +- *(checker)* Collect basic statistics while checking the db image (#1149) +- *(fwdctl)* Add support for dump formats (#1161) +- *(ffi)* Remove the Arc wrapper around Proposal (#1160) + +### 🐛 Bug Fixes + +- *(fwdctl)* Fix fwdctl with ethhash (#1091) +- *(checker)* Fix checker with ethhash (#1130) +- Fix broken deserialization of old FreeArea format (#1147) +- Create metrics registration macros (#980) + +### 💼 Other + +- Cargo.toml upgrades and fixes (#1099) +- *(deps)* Update criterion requirement from 0.6.0 to 0.7.0 (#1140) + +### 📚 Documentation + +- Update ffi/README.md to include configs, metrics, and logs (#1111) + +### 🎨 Styling + +- Remove unnecessary string in error (#1104) + +### 🧪 Testing + +- Add fuzz testing for checker, with fixes (#1118) +- Port TestDeepPropose from go->rust (#1115) + +### ⚙️ Miscellaneous Tasks + +- Add propose-on-propose test (#1097) +- Implement newtype for LInearAddress (#1086) +- Refactor verifying value digests (#1119) +- Checker test cleanups (#1131) +- Minor cleanups and nits (#1133) +- Add a golang install script (#1141) +- Move all merkle tests into a subdirectory (#1150) +- Require license header for ffi code (#1159) +- Bump version to v0.0.10 + ## [0.0.9] - 2025-07-17 ### 🚀 Features diff --git a/Cargo.toml b/Cargo.toml index c08eb0901399..e22697803673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,11 @@ members = [ "storage", "triehash", ] -exclude = [ - "grpc-testtool", -] +exclude = ["grpc-testtool"] resolver = "2" [workspace.package] -version = "0.0.9" +version = "0.0.10" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -56,11 +54,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.9" } -firewood-macros = { path = "firewood-macros", version = "0.0.9" } -firewood-storage = { path = "storage", version = "0.0.9" } -firewood-ffi = { path = "ffi", version = "0.0.9" } -firewood-triehash = { path = "triehash", version = "0.0.9" } +firewood = { path = "firewood", version = "0.0.10" } +firewood-macros = { path = "firewood-macros", version = "0.0.10" } +firewood-storage = { path = "storage", version = "0.0.10" } +firewood-ffi = { path = "ffi", version = "0.0.10" } +firewood-triehash = { path = "triehash", version = "0.0.10" } # common dependencies aquamarine = "0.6.0" diff --git a/RELEASE.md b/RELEASE.md index d19ca7eefb14..6362ab95a5a8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,9 +12,9 @@ Start off by crating a new branch: ```console $ git fetch -$ git switch -c release/v0.0.10 origin/main -branch 'release/v0.0.10' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.10' +$ git switch -c release/v0.0.11 origin/main +branch 'release/v0.0.11' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.11' ``` ## Package Version @@ -26,7 +26,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.7" +version = "0.0.11" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -49,7 +49,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.10" } +firewood = { path = "firewood", version = "0.0.11" } ``` This allows packages within the workspace to inherit the dependency, @@ -77,8 +77,8 @@ the dependency versions to match. To build the changelog, see git-cliff.org. Short version: ```sh -cargo install git-cliff -git cliff --tag v0.0.10 | sed -e 's/_/\\_/g' > CHANGELOG.md +cargo install --locked git-cliff +git cliff --tag v0.0.11 | sed -e 's/_/\\_/g' > CHANGELOG.md ``` ## Review @@ -92,11 +92,11 @@ git cliff --tag v0.0.10 | sed -e 's/_/\\_/g' > CHANGELOG.md To trigger a release, push a tag to the main branch matching the new version, ```sh -git tag -S v0.0.10 -git push origin v0.0.10 +git tag -S v0.0.11 +git push origin v0.0.11 ``` -for `v0.0.10` for the merged version change. The CI will automatically publish a +for `v0.0.11` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). From 2f2bb126458669da17d50c105688258eb744b52d Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 1 Aug 2025 17:36:50 -0700 Subject: [PATCH 0872/1053] ci: metric change detection comments only on 1st-party PRs (#1167) The GitHub Actions runner does not have permissions to use OIDC when the PR is from a fork. This causes it to show up as a failure for external contributions. Fixes #1162 --- .github/workflows/metrics-check.yaml | 30 +++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/metrics-check.yaml b/.github/workflows/metrics-check.yaml index 20d3f279f899..4858f4b176d9 100644 --- a/.github/workflows/metrics-check.yaml +++ b/.github/workflows/metrics-check.yaml @@ -2,9 +2,7 @@ name: Metrics Change Check on: pull_request: - branches: [ main, master ] - push: - branches: [ main, master ] + types: [opened, synchronize, reopened] jobs: check-metrics-changes: @@ -13,7 +11,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 # Fetch full history to compare changes + fetch-depth: 0 # Fetch full history to compare changes - name: Check for metric-related changes id: check-metrics @@ -24,47 +22,47 @@ jobs: else BASE_COMMIT="${{ github.event.before }}" fi - + # Check if Grafana dashboard was modified - exit early if so if git diff --name-only $BASE_COMMIT..HEAD | grep -q "benchmark/Grafana-dashboard.json"; then echo "benchmark/Grafana-dashboard.json was modified - skipping metrics check" exit 0 fi - + # Regex pattern to match metric-related code changes METRIC_REGEX_PATTERN="(counter!|gauge!|histogram!|#\\[metrics\\])" - + # Check for metric-related changes METRIC_CHANGES=$(git diff $BASE_COMMIT..HEAD --unified=0 | grep -E "$METRIC_REGEX_PATTERN" || true) - + if [ -n "$METRIC_CHANGES" ]; then echo "⚠️ WARNING: Found metric-related changes, but no dashboard modification:" echo "$METRIC_CHANGES" else echo "✅ No metric-related changes found" fi - + # Set output variables for the comment step echo "metric_changes_found=$([ -n "$METRIC_CHANGES" ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT echo "metric_pattern=$METRIC_REGEX_PATTERN" >> $GITHUB_OUTPUT - name: Comment on PR (if applicable) - if: github.event_name == 'pull_request' && steps.check-metrics.outputs.metric_changes_found == 'true' + if: github.repository == github.event.pull_request.head.repo.full_name && steps.check-metrics.outputs.metric_changes_found == 'true' uses: actions/github-script@v7 with: script: | const { execSync } = require('child_process'); - + try { // Get the base commit for comparison const baseCommit = context.payload.pull_request.base.sha; - + // Get the metric pattern from the previous step const metricPattern = "${{ steps.check-metrics.outputs.metric_pattern }}"; - + // Check for metric changes const metricChanges = execSync(`git diff ${baseCommit}..HEAD --unified=0 | grep -E "${metricPattern}" || true`, { encoding: 'utf8' }); - + if (metricChanges.trim()) { const comment = '## Metrics Change Detection ⚠️\n\n' + 'This PR contains changes related to metrics:\n\n' + @@ -75,7 +73,7 @@ jobs: 'You may need to update `benchmark/Grafana-dashboard.json` accordingly.\n\n' + '---\n' + '*This check is automated to help maintain the dashboard.*'; - + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, @@ -85,4 +83,4 @@ jobs: } } catch (error) { console.log('No metric changes found or error occurred:', error.message); - } + } From 715c814de32835737fbd4e07e017e72c1cd1c916 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 1 Aug 2025 17:44:41 -0700 Subject: [PATCH 0873/1053] ci: run CI on macOS (#1168) As noted in #1129 and #1135, there are some inconsistencies with builds for macOS and Linux. This commit adds a macOS runner to the CI workflow to ensure that the lints and tests pass on macOS as well. Instead of splitting the matrix to hoist the os separately, I simply added a new element to the include block. This way the GitHub blocks on merging Pull Requests won't be confused by the change in matrix values (it matches on all of them literally). The cache key does not need a token for `macos` because the full cache key already includes the kernel type as a differentiator. E.g., ```log Cache hit for: v0-rust-debug-no-features-Linux-x64-6dc3ef10-b549d219 ``` versus ```log Cache hit for: v0-rust-debug-no-features-macos-Darwin-arm64-c5c5f0bd-b549d219 ``` The extra `macos` suffix is redundant and unnecessary. Fixes #1129 --- .github/workflows/ci.yaml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 92d9dc421bd9..e721145b1b4e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,11 +27,14 @@ jobs: profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" + - profile-key: debug-no-features + profile-args: "" + os: macos-latest - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" - profile-key: maxperf-ethhash-logger profile-args: "--profile maxperf --features ethhash,logger" - runs-on: ubuntu-latest + runs-on: ${{ matrix.os || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -65,7 +68,6 @@ jobs: # not originating from a fork. if: github.event_name == 'pull_request' && github.repository == github.event.pull_request.head.repo.full_name needs: build - runs-on: ubuntu-latest strategy: matrix: include: @@ -73,8 +75,12 @@ jobs: profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" + - profile-key: debug-no-features + profile-args: "" + os: macos-latest - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" + runs-on: ${{ matrix.os || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -93,7 +99,6 @@ jobs: rust-lint: needs: build - runs-on: ubuntu-latest strategy: matrix: include: @@ -101,8 +106,12 @@ jobs: profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" + - profile-key: debug-no-features + profile-args: "" + os: macos-latest - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" + runs-on: ${{ matrix.os || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -118,7 +127,6 @@ jobs: test: needs: build - runs-on: ubuntu-latest strategy: matrix: include: @@ -126,10 +134,14 @@ jobs: profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" + - profile-key: debug-no-features + profile-args: "" + os: macos-latest - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" - profile-key: maxperf-ethhash-logger profile-args: "--profile maxperf --features ethhash,logger" + runs-on: ${{ matrix.os || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -144,7 +156,7 @@ jobs: run: cargo test --verbose ${{ matrix.profile-args }} examples: - runs-on: ubuntu-latest + needs: build strategy: matrix: include: @@ -152,10 +164,14 @@ jobs: profile-args: "--no-default-features" - profile-key: debug-no-features profile-args: "" + - profile-key: debug-no-features + profile-args: "" + os: macos-latest - profile-key: debug-ethhash-logger profile-args: "--features ethhash,logger" - profile-key: maxperf-ethhash-logger profile-args: "--profile maxperf --features ethhash,logger" + runs-on: ${{ matrix.os || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable From 66fe16d53ecf03886fe0e07993a7b29ff8e3bcde Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Sat, 2 Aug 2025 16:26:01 -0700 Subject: [PATCH 0874/1053] ci: update .golangci.yaml (#1166) I also got bored and updated the script. This allows us to get more useful output when there is a change; e.g.,: ```console $ .github/scripts/verify_golangci_yaml_changes.sh 'ffi/.golangci.yaml' has unexpected changes from AvalancheGo. View the upstream changes at: https://github.com/ava-labs/avalanchego/commits/master/.golangci.yml and apply them if necessary. Current changes: --- .github/.golangci.yaml 2025-08-02 10:04:10 +++ ffi/.golangci.yaml 2025-08-02 10:01:55 @@ -12,7 +12,7 @@ # # Allowed values: readonly|vendor|mod # By default, it isn't set. - modules-download-mode: mod + modules-download-mode: readonly # Include non-test files tagged as test-only. # Context: https://github.com/ava-labs/avalanchego/pull/3173 ``` Additionally, we can run `apply` to apply our patch against the upstream file and refresh the patch file: ```console $ .github/scripts/verify_golangci_yaml_changes.sh apply patching file '.github/.golangci.yaml' Successfully applied the patch from .github/.golangci.yaml.patch to ffi/.golangci.yaml Updated expected changes in .github/.golangci.yaml.patch ``` fixes #1164 --- .github/.gitignore | 1 + .github/.golangci.yaml.patch | 95 ++++++++++++ .github/.golangci_yaml_expected_changes.txt | 62 -------- .../scripts/verify_golangci_yaml_changes.sh | 138 ++++++++++++++++-- .../expected-golangci-yaml-diff.yaml | 15 +- ffi/.golangci.yaml | 1 - 6 files changed, 236 insertions(+), 76 deletions(-) create mode 100644 .github/.gitignore create mode 100644 .github/.golangci.yaml.patch delete mode 100644 .github/.golangci_yaml_expected_changes.txt diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 000000000000..713fdda5ac5c --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +.golangci.yaml diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch new file mode 100644 index 000000000000..5b6f88091cd1 --- /dev/null +++ b/.github/.golangci.yaml.patch @@ -0,0 +1,95 @@ +--- .github/.golangci.yaml 2025-08-02 10:10:13 ++++ ffi/.golangci.yaml 2025-08-02 10:10:13 +@@ -69,8 +69,6 @@ + rules: + packages: + deny: +- - pkg: container/list +- desc: github.com/ava-labs/avalanchego/utils/linked should be used instead. + - pkg: github.com/golang/mock/gomock + desc: go.uber.org/mock/gomock should be used instead. + - pkg: github.com/stretchr/testify/assert +@@ -85,29 +83,10 @@ + forbidigo: + # Forbid the following identifiers (list of regexp). + forbid: +- - pattern: require\.Error$(# ErrorIs should be used instead)? +- - pattern: require\.ErrorContains$(# ErrorIs should be used instead)? +- - pattern: require\.EqualValues$(# Equal should be used instead)? +- - pattern: require\.NotEqualValues$(# NotEqual should be used instead)? + - pattern: ^(t|b|tb|f)\.(Fatal|Fatalf|Error|Errorf)$(# the require library should be used instead)? + - pattern: ^sort\.(Slice|Strings)$(# the slices package should be used instead)? + # Exclude godoc examples from forbidigo checks. + exclude-godoc-examples: false +- gosec: +- excludes: +- - G107 # Url provided to HTTP request as taint input https://securego.io/docs/rules/g107 +- - G115 # TODO(marun) Enable this ruleset in a follow-up PR +- importas: +- # Do not allow unaliased imports of aliased packages. +- no-unaliased: false +- # Do not allow non-required aliases. +- no-extra-aliases: false +- # List of aliases +- alias: +- - pkg: github.com/ava-labs/avalanchego/utils/math +- alias: safemath +- - pkg: github.com/ava-labs/avalanchego/utils/json +- alias: avajson + revive: + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr +@@ -168,17 +147,6 @@ + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + disabled: false +- spancheck: +- # https://github.com/jjti/go-spancheck#checks +- checks: +- - end +- staticcheck: +- # https://staticcheck.io/docs/options#checks +- checks: +- - all +- - -SA6002A # Storing non-pointer values in sync.Pool allocates memory +- - -SA1019 # Using a deprecated function, variable, constant or field +- - -QF1008 # Unnecessary embedded expressions + tagalign: + align: true + sort: true +@@ -186,16 +154,16 @@ + - serialize + strict: true + testifylint: +- # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). +- # Default: false +- enable-all: true +- # Disable checkers by name +- # (in addition to default +- # suite-thelper +- # ). +- disable: +- - go-require +- - float-compare ++ # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). ++ # Default: false ++ enable-all: true ++ # Disable checkers by name ++ # (in addition to default ++ # suite-thelper ++ # ). ++ disable: ++ - go-require ++ - float-compare + unused: + # Mark all struct fields that have been written to as used. + # Default: true +@@ -224,7 +192,7 @@ + - standard + - default + - blank +- - prefix(github.com/ava-labs/avalanchego) ++ - prefix(github.com/ava-labs/firewood/ffi) + - alias + - dot + custom-order: true diff --git a/.github/.golangci_yaml_expected_changes.txt b/.github/.golangci_yaml_expected_changes.txt deleted file mode 100644 index 9c7042622939..000000000000 --- a/.github/.golangci_yaml_expected_changes.txt +++ /dev/null @@ -1,62 +0,0 @@ -73,74d72 -< - pkg: container/list -< desc: github.com/ava-labs/avalanchego/utils/linked should be used instead. -89,92d86 -< - pattern: require\.Error$(# ErrorIs should be used instead)? -< - pattern: require\.ErrorContains$(# ErrorIs should be used instead)? -< - pattern: require\.EqualValues$(# Equal should be used instead)? -< - pattern: require\.NotEqualValues$(# NotEqual should be used instead)? -97,111d90 -< gosec: -< excludes: -< - G107 # Url provided to HTTP request as taint input https://securego.io/docs/rules/g107 -< - G115 # TODO(marun) Enable this ruleset in a follow-up PR -< importas: -< # Do not allow unaliased imports of aliased packages. -< no-unaliased: false -< # Do not allow non-required aliases. -< no-extra-aliases: false -< # List of aliases -< alias: -< - pkg: github.com/ava-labs/avalanchego/utils/math -< alias: safemath -< - pkg: github.com/ava-labs/avalanchego/utils/json -< alias: avajson -172,182d150 -< spancheck: -< # https://github.com/jjti/go-spancheck#checks -< checks: -< - end -< staticcheck: -< # https://staticcheck.io/docs/options#checks -< checks: -< - all -< - -SA6002A # Storing non-pointer values in sync.Pool allocates memory -< - -SA1019 # Using a deprecated function, variable, constant or field -< - -QF1008 # Unnecessary embedded expressions -190,199c158,167 -< # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). -< # Default: false -< enable-all: true -< # Disable checkers by name -< # (in addition to default -< # suite-thelper -< # ). -< disable: -< - go-require -< - float-compare ---- -> # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). -> # Default: false -> enable-all: true -> # Disable checkers by name -> # (in addition to default -> # suite-thelper -> # ). -> disable: -> - go-require -> - float-compare -228c196 -< - prefix(github.com/ava-labs/avalanchego) ---- -> - prefix(github.com/ava-labs/firewood/ffi) diff --git a/.github/scripts/verify_golangci_yaml_changes.sh b/.github/scripts/verify_golangci_yaml_changes.sh index 3902babfc61c..9773f61039b1 100755 --- a/.github/scripts/verify_golangci_yaml_changes.sh +++ b/.github/scripts/verify_golangci_yaml_changes.sh @@ -1,19 +1,135 @@ #!/usr/bin/env bash +set -euo pipefail + # ==== Generating Expected Diff ==== -# 1. Ensure working directory is .github -# 2. Store latest upstream version of .golangci.yaml to X -# 3. Run diff X ../ffi/.golangci.yaml > .golangci_yaml_expected_changes.txt +# 1. Review the changes from AvalancheGo and apply them if necessary to our local file. +# https://github.com/ava-labs/avalanchego/commits/master/.golangci.yml +# 2. Update the diff file by running: +# .github/scripts/verify_golangci_yaml_changes.sh update + +declare -r golangci_yaml="ffi/.golangci.yaml" +declare -r upstream_yaml=".github/.golangci.yaml" +declare -r expected_patch=".github/.golangci.yaml.patch" +declare -r upstream_url="https://raw.githubusercontent.com/ava-labs/avalanchego/refs/heads/master/.golangci.yml" +declare -r history_url="https://github.com/ava-labs/avalanchego/commits/master/.golangci.yml" + +function @die() { + local -r code="$1" + local -r message="$2" + + echo "Error: $message" >&2 + exit "$code" +} + +function @usage() { + cat < /tmp/diff.txt || true + 0: Command executed successfully (if 'check' command, no unexpected changes). + 1: Usage error. + 2: Failed to download upstream .golangci.yaml. + 3: Failed to generate patch; '$golangci_yaml' has no differences from '$upstream_yaml'. + 4: (on apply) Failed to apply the patch from '$expected_patch'. + 5: (on check) '$golangci_yaml' has unexpected changes from AvalancheGo. +EOF -# Compare with expected diff -if ! diff /tmp/diff.txt .golangci_yaml_expected_changes.txt; then - echo "ffi/.golangci.yaml has unexpected changes from AvalancheGo" exit 1 -fi +} + +function @download-upstream() { + echo "Downloading upstream .golangci.yaml from '$upstream_url'..." + if ! curl -fsSL -o "$upstream_yaml" "$upstream_url"; then + @die 2 "Failed to download upstream .golangci.yaml from '$upstream_url'" + fi +} + +function @generate-patch() { + local -r dest="$1" + if diff -Nau "$upstream_yaml" "$golangci_yaml" >"$dest" 2>/dev/null; then + @die 3 "'$golangci_yaml' has no differences '$upstream_yaml'; this is unexpected! At least package name must be different." + fi +} + +function @apply-patch-to-upstream() { + if ! patch -t "$upstream_yaml" "$expected_patch"; then + @die 4 "Failed to apply the patch from $expected_patch. Please review the changes manually." + fi +} + +function @apply() { + local backup + backup=$(mktemp) + trap 'rm -f "$backup"' EXIT + + # make a copy of the upstream yaml before applying the patch so we can refresh the patch file later + cp -f "$upstream_yaml" "$backup" + @apply-patch-to-upstream + + # We cleanly applied the patch, so we can now replace the local file with the patched version + cp -f "$upstream_yaml" "$golangci_yaml" + cp -f "$backup" "$upstream_yaml" + echo "Successfully applied the patch from $expected_patch to $golangci_yaml" + + # refresh the patch so `check` apply later can use it + @update +} + +function @check() { + @apply-patch-to-upstream + + local patch + patch=$(mktemp) + trap 'rm -f "$patch"' EXIT + + if diff -Nau "$upstream_yaml" "$golangci_yaml" >"$patch" 2>&1; then + echo "'$golangci_yaml' is up to date with AvalancheGo's .golangci.yaml." + exit 0 + fi + + { + echo "'$golangci_yaml' has unexpected changes from AvalancheGo." + echo "View the upstream changes at: $history_url and apply them if necessary." + echo "" + echo "Current changes:" + cat "$patch" + } >&2 + + exit 5 +} + +function @update() { + @generate-patch "$expected_patch" + echo "Updated expected changes in $expected_patch" + exit 0 +} + +case "${1:-check}" in +apply | check | update) + # make sure we are in the root of the repository + cd "$(git rev-parse --show-toplevel)" + + @download-upstream + + case "${1:-check}" in + apply) @apply ;; + check) @check ;; + update) @update ;; + esac + + ;; +*) @usage ;; +esac diff --git a/.github/workflows/expected-golangci-yaml-diff.yaml b/.github/workflows/expected-golangci-yaml-diff.yaml index 8daa5aae8bab..3eab5f9f436a 100644 --- a/.github/workflows/expected-golangci-yaml-diff.yaml +++ b/.github/workflows/expected-golangci-yaml-diff.yaml @@ -1,6 +1,18 @@ name: expected golangci.yaml diff on: + pull_request: + paths: + - .github/scripts/verify_golangci_yaml_changes.sh + - .github/workflows/expected-golangci-yaml-diff.yaml + - .github/.golangci.yaml.patch + - ffi/.golangci.yaml + # if updating the general CI workflow, go ahead and update the golangci.yaml + - .github/workflows/ci.yaml + # for good measure, if someone is editing the go.mod or go.sum files then + # they should make sure the golangci.yaml is up to date + - ffi/go.mod + - ffi/go.sum push: branches: - "main" @@ -11,5 +23,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Validate expected golangci-lint changes - working-directory: .github - run: ./scripts/verify_golangci_yaml_changes.sh + run: .github/scripts/verify_golangci_yaml_changes.sh diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index c4c4ffb5ae8c..6f205440cce9 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -36,7 +36,6 @@ linters: - bodyclose - copyloopvar - depguard - - dupword - errcheck - errorlint - forbidigo From 0f9e676e41111c07ae3e2a7c987c72e766aee369 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:50:38 -0500 Subject: [PATCH 0875/1053] feat(checker): checker returns all errors found in the report (#1176) Beforehand, checker returns an error whenever it encounters a problem. With this PR, it will return all errors it can find: 1. If a node in the trie is problematic, it will not traverse any deeper from this node, but checker will return multiple errors with nodes that do not have ancestry relationships. 2. If a free area is problematic, it will continue traversing and the iterator controls when to stop. 3. No error will ever be reported when visiting leaked areas. --- firewood/src/db.rs | 13 +- fwdctl/src/check.rs | 10 +- storage/src/checker/mod.rs | 307 +++++++++++++++++++++++-------------- storage/src/lib.rs | 12 ++ 4 files changed, 212 insertions(+), 130 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 7a1b185b045e..c90ade294817 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -331,10 +331,7 @@ impl Db { } /// Check the database for consistency - pub async fn check( - &self, - opt: CheckOpt, - ) -> Result { + pub async fn check(&self, opt: CheckOpt) -> CheckerReport { let latest_rev_nodestore = self.manager.current_revision(); latest_rev_nodestore.check(opt) } @@ -799,15 +796,15 @@ mod test { // check the database for consistency, sometimes checking the hashes let hash_check = rng.borrow_mut().random(); - if let Err(e) = db + let report = db .check(CheckOpt { hash_check, progress_bar: None, }) - .await - { + .await; + if !report.errors.is_empty() { db.dump(&mut std::io::stdout()).await.unwrap(); - panic!("error: {e}"); + panic!("error: {:?}", report.errors); } } } diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 803e497ac253..72c4c526dc6f 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -55,12 +55,10 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { ) .with_finish(ProgressFinish::WithMessage("Check Completed!".into())); - let report = NodeStore::open(storage)? - .check(CheckOpt { - hash_check: opts.hash_check, - progress_bar: Some(progress_bar), - }) - .map_err(|e| api::Error::InternalError(Box::new(e)))?; + let report = NodeStore::open(storage)?.check(CheckOpt { + hash_check: opts.hash_check, + progress_bar: Some(progress_bar), + }); print_checker_report(report); diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 72f2a105f692..c5e31cb0e789 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -53,6 +53,8 @@ pub struct CheckOpt { #[derive(Debug)] /// Report of the checker results. pub struct CheckerReport { + /// Errors encountered during the check + pub errors: Vec, /// The high watermark of the database pub high_watermark: u64, /// The physical number of bytes in the database returned through `stat` @@ -115,46 +117,57 @@ impl NodeStore { /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. // TODO: report all errors, not just the first one - pub fn check(&self, opt: CheckOpt) -> Result { + pub fn check(&self, opt: CheckOpt) -> CheckerReport { // 1. Check the header let db_size = self.size(); - let physical_bytes = self.physical_size()?; + let mut visited = match LinearAddressRangeSet::new(db_size) { + Ok(visited) => visited, + Err(e) => { + return CheckerReport { + errors: vec![e], + high_watermark: db_size, + physical_bytes: 0, + trie_stats: TrieStats::default(), + free_list_stats: FreeListsStats::default(), + }; + } + }; - let mut visited = LinearAddressRangeSet::new(db_size)?; + let mut errors = Vec::new(); // 2. traverse the trie and check the nodes if let Some(progress_bar) = &opt.progress_bar { progress_bar.set_length(db_size); progress_bar.set_message("Traversing the trie..."); } - let trie_stats = if let (Some(root), Some(root_hash)) = - (self.root_as_maybe_persisted_node(), self.root_hash()) - { - // the database is not empty, and has a physical address, so traverse the trie - if let Some(root_address) = root.as_linear_address() { - self.check_area_aligned( - root_address, - StoredAreaParent::TrieNode(TrieNodeParent::Root), - )?; - self.visit_trie( - root_address, - root_hash.into_hash_type(), - &mut visited, - opt.progress_bar.as_ref(), - opt.hash_check, - )? - } else { - return Err(CheckerError::UnpersistedRoot); - } - } else { - TrieStats::default() - }; + let trie_stats = self + .root_as_maybe_persisted_node() + .and_then(|node| self.root_hash().map(|root_hash| (node, root_hash))) + .and_then(|(root, root_hash)| { + if let Some(root_address) = root.as_linear_address() { + let (trie_stats, trie_errors) = self.visit_trie( + root_address, + root_hash.into_hash_type(), + &mut visited, + opt.progress_bar.as_ref(), + opt.hash_check, + ); + errors.extend(trie_errors); + Some(trie_stats) + } else { + errors.push(CheckerError::UnpersistedRoot); + None + } + }) + .unwrap_or_default(); // 3. check the free list - this can happen in parallel with the trie traversal if let Some(progress_bar) = &opt.progress_bar { progress_bar.set_message("Traversing free lists..."); } - let free_list_stats = self.visit_freelist(&mut visited, opt.progress_bar.as_ref())?; + let (free_list_stats, free_list_traverse_errors) = + self.visit_freelist(&mut visited, opt.progress_bar.as_ref()); + errors.extend(free_list_traverse_errors); // 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? if let Some(progress_bar) = &opt.progress_bar { @@ -167,12 +180,21 @@ impl NodeStore { let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges, opt.progress_bar.as_ref()); // TODO: add leaked areas to the free list - Ok(CheckerReport { + let physical_bytes = match self.physical_size() { + Ok(physical_bytes) => physical_bytes, + Err(e) => { + errors.push(e.into()); + 0 + } + }; + + CheckerReport { + errors, high_watermark: db_size, physical_bytes, trie_stats, free_list_stats, - }) + } } fn visit_trie( @@ -182,7 +204,7 @@ impl NodeStore { visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, hash_check: bool, - ) -> Result { + ) -> (TrieStats, Vec) { let trie = SubTrieMetadata { root_address, root_hash, @@ -193,8 +215,11 @@ impl NodeStore { has_peers: false, }; let mut trie_stats = TrieStats::default(); - self.visit_trie_helper(trie, visited, &mut trie_stats, progress_bar, hash_check)?; - Ok(trie_stats) + let errors = self + .visit_trie_helper(trie, visited, &mut trie_stats, progress_bar, hash_check) + .err() + .unwrap_or_default(); + (trie_stats, errors) } /// Recursively traverse the trie from the given root node. @@ -206,7 +231,7 @@ impl NodeStore { trie_stats: &mut TrieStats, progress_bar: Option<&ProgressBar>, hash_check: bool, - ) -> Result<(), CheckerError> { + ) -> Result<(), Vec> { let SubTrieMetadata { root_address: subtrie_root_address, root_hash: subtrie_root_hash, @@ -220,21 +245,53 @@ impl NodeStore { // check that address is aligned self.check_area_aligned(subtrie_root_address, StoredAreaParent::TrieNode(parent))?; - // check that the area is within bounds and does not intersect with other areas + // read the node from the disk - we avoid cache since we will never visit the same node twice let (area_index, area_size) = self.area_index_and_size(subtrie_root_address)?; + let (node, node_bytes) = self.read_node_with_num_bytes_from_disk(subtrie_root_address)?; + + // if the node has a value, check that the key is valid + let mut current_path_prefix = path_prefix.clone(); + current_path_prefix.0.extend_from_slice(node.partial_path()); + if node.value().is_some() && !is_valid_key(¤t_path_prefix) { + return Err(vec![CheckerError::InvalidKey { + key: current_path_prefix, + address: subtrie_root_address, + parent, + }]); + } + + // compute the hash of the node and check it against the stored hash + if hash_check { + #[cfg(feature = "ethhash")] + let hash = Self::compute_node_ethhash(&node, &path_prefix, has_peers); + #[cfg(not(feature = "ethhash"))] + let hash = hash_node(&node, &path_prefix); + if hash != subtrie_root_hash { + return Err(vec![CheckerError::HashMismatch { + path: current_path_prefix, + address: subtrie_root_address, + parent, + parent_stored_hash: subtrie_root_hash, + computed_hash: hash, + }]); + } + } + + // check that the area is within bounds and does not intersect with other areas and mark it as visiteds visited.insert_area( subtrie_root_address, area_size, StoredAreaParent::TrieNode(parent), )?; - // update the area count - let area_count = trie_stats.area_counts.entry(area_size).or_insert(0); - *area_count = area_count.saturating_add(1); + // at this point we have checked this area is correct - update progress bar, collect stats, and recursively traverse the children + update_progress_bar(progress_bar, visited); - // read the node from the disk - we avoid cache since we will never visit the same node twice - let (node, node_bytes) = self.read_node_with_num_bytes_from_disk(subtrie_root_address)?; + // collect trie stats { + // update the area count + let area_count = trie_stats.area_counts.entry(area_size).or_insert(0); + *area_count = area_count.saturating_add(1); // collect the trie bytes trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); // collect low occupancy area count @@ -262,16 +319,8 @@ impl NodeStore { } } - let mut current_path_prefix = path_prefix.clone(); - current_path_prefix.0.extend_from_slice(node.partial_path()); - if node.value().is_some() && !is_valid_key(¤t_path_prefix) { - return Err(CheckerError::InvalidKey { - key: current_path_prefix, - address: subtrie_root_address, - parent, - }); - } - + // collect more stats and recursively traverse the children + let mut errors = Vec::new(); match node.as_ref() { Node::Branch(branch) => { let num_children = branch.children_iter().count(); @@ -298,13 +347,15 @@ impl NodeStore { #[cfg(feature = "ethhash")] has_peers: num_children != 1, }; - self.visit_trie_helper( + if let Err(e) = self.visit_trie_helper( child_subtrie, visited, trie_stats, progress_bar, hash_check, - )?; + ) { + errors.extend(e); + } } } Node::Leaf(leaf) => { @@ -323,28 +374,11 @@ impl NodeStore { } } - // hash check - at this point all children hashes have been verified - if hash_check { - #[cfg(feature = "ethhash")] - let hash = Self::compute_node_ethhash(&node, &path_prefix, has_peers); - #[cfg(not(feature = "ethhash"))] - let hash = hash_node(&node, &path_prefix); - if hash != subtrie_root_hash { - let mut path = path_prefix.clone(); - path.0.extend_from_slice(node.partial_path()); - return Err(CheckerError::HashMismatch { - path, - address: subtrie_root_address, - parent, - parent_stored_hash: subtrie_root_hash, - computed_hash: hash, - }); - } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) } - - update_progress_bar(progress_bar, visited); - - Ok(()) } /// Traverse all the free areas in the freelist @@ -352,9 +386,10 @@ impl NodeStore { &self, visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, - ) -> Result { + ) -> (FreeListsStats, Vec) { let mut area_counts: HashMap = HashMap::new(); let mut multi_page_area_count = 0u64; + let mut errors = Vec::new(); let mut free_list_iter = self.free_list_iter(0); while let Some(free_area) = free_list_iter.next_with_metadata() { @@ -363,19 +398,41 @@ impl NodeStore { area_index, free_list_id, parent, - } = free_area?; - self.check_area_aligned(addr, StoredAreaParent::FreeList(parent))?; + } = match free_area { + Ok(free_area) => free_area, + Err(e) => { + errors.push(CheckerError::IO(e)); + continue; + } + }; + + // check that the area is aligned + if let Err(e) = self.check_area_aligned(addr, StoredAreaParent::FreeList(parent)) { + errors.push(e); + continue; + } + + // check that the area size matches the free list id (if it is in the correct free list) let area_size = size_from_area_index(area_index); if free_list_id != area_index { - return Err(CheckerError::FreelistAreaSizeMismatch { + errors.push(CheckerError::FreelistAreaSizeMismatch { address: addr, size: area_size, actual_free_list: free_list_id, expected_free_list: area_index, parent, }); + continue; } - visited.insert_area(addr, area_size, StoredAreaParent::FreeList(parent))?; + + // check the free area is within bounds and does not intersect with other areas + if let Err(e) = visited.insert_area(addr, area_size, StoredAreaParent::FreeList(parent)) + { + errors.push(e); + continue; + } + + // collect the free list stats { // collect the free lists area distribution let area_count = area_counts.entry(area_size).or_insert(0); @@ -392,10 +449,14 @@ impl NodeStore { } update_progress_bar(progress_bar, visited); } - Ok(FreeListsStats { - area_counts, - multi_page_area_count, - }) + + ( + FreeListsStats { + area_counts, + multi_page_area_count, + }, + errors, + ) } const fn check_area_aligned( @@ -636,18 +697,17 @@ mod test { // verify that all of the space is accounted for - since there is no free area let mut visited = LinearAddressRangeSet::new(test_trie.high_watermark).unwrap(); - let stats = nodestore - .visit_trie( - test_trie.root_address, - test_trie.root_hash, - &mut visited, - None, - true, - ) - .unwrap(); + let (stats, errors) = nodestore.visit_trie( + test_trie.root_address, + test_trie.root_hash, + &mut visited, + None, + true, + ); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); assert_eq!(stats, test_trie.stats); + assert_eq!(errors, vec![]); } #[test] @@ -657,52 +717,65 @@ mod test { let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); let mut test_trie = gen_test_trie(&mut nodestore); + let root_addr = test_trie.root_address; // find the root node and replace the branch hash with an incorrect (default) hash - let (root_node, root_addr) = test_trie + let (branch_node, branch_addr) = test_trie .nodes .iter_mut() - .find(|(node, _)| matches!(node, Node::Branch(b) if *b.partial_path.0 == [2])) + .find(|(node, _)| matches!(node, Node::Branch(b) if *b.partial_path.0 == [3])) .unwrap(); - let root_branch = root_node.as_branch_mut().unwrap(); + let branch = branch_node.as_branch_mut().unwrap(); + let branch_addr = *branch_addr; - // Get the branch address and original hash from the root's first child - let (branch_addr, computed_hash) = root_branch.children[0] + // Replace branch hash with a wrong hash + let (leaf_addr, _) = branch.children[1].as_ref().unwrap().persist_info().unwrap(); + branch.children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::default())); + test_write_new_node(&nodestore, branch_node, branch_addr.get()); + + // Compute the current branch hash + #[cfg(feature = "ethhash")] + let computed_hash = NodeStore::::compute_node_ethhash( + branch_node, + &Path::from([2, 0]), + false, + ); + #[cfg(not(feature = "ethhash"))] + let computed_hash = hash_node(branch_node, &Path::from([2, 0])); + + // Get parent stored hash + let (root_node, _) = test_trie + .nodes + .iter() + .find(|(node, _)| matches!(node, Node::Branch(b) if *b.partial_path.0 == [2])) + .unwrap(); + let root_branch = root_node.as_branch().unwrap(); + let (_, parent_stored_hash) = root_branch.children[0] .as_ref() .unwrap() .persist_info() .unwrap(); - let computed_hash = computed_hash.clone(); - root_branch.children[0] = Some(Child::AddressWithHash(branch_addr, HashType::default())); - - // Replace the branch hash in the root node with a wrong hash - if let Node::Branch(root_branch) = root_node { - root_branch.children[0] = - Some(Child::AddressWithHash(branch_addr, HashType::default())); - } - test_write_new_node(&nodestore, root_node, root_addr.get()); + let parent_stored_hash = parent_stored_hash.clone(); // run the checker and verify that it returns the HashMismatch error let mut visited = LinearAddressRangeSet::new(test_trie.high_watermark).unwrap(); - let err = nodestore - .visit_trie( - test_trie.root_address, - test_trie.root_hash, - &mut visited, - None, - true, - ) - .unwrap_err(); + let (_, errors) = nodestore.visit_trie( + test_trie.root_address, + test_trie.root_hash, + &mut visited, + None, + true, + ); let expected_error = CheckerError::HashMismatch { address: branch_addr, path: Path::from([2, 0, 3]), - parent: TrieNodeParent::Parent(*root_addr, 0), - parent_stored_hash: HashType::default(), + parent: TrieNodeParent::Parent(root_addr, 0), + parent_stored_hash, computed_hash, }; - assert_eq!(err, expected_error); + assert_eq!(errors, vec![expected_error]); } #[test] @@ -752,10 +825,12 @@ mod test { // test that the we traversed all the free areas let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - let actual_free_lists_stats = nodestore.visit_freelist(&mut visited, None).unwrap(); + let (actual_free_lists_stats, free_list_errors) = + nodestore.visit_freelist(&mut visited, None); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); assert_eq!(actual_free_lists_stats, expected_free_lists_stats); + assert_eq!(free_list_errors, vec![]); } #[test] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 300c47a1a734..9655a8ef4591 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -285,6 +285,18 @@ pub enum CheckerError { IO(#[from] FileIoError), } +impl From for Vec { + fn from(error: CheckerError) -> Self { + vec![error] + } +} + +impl From for Vec { + fn from(error: FileIoError) -> Self { + vec![CheckerError::IO(error)] + } +} + #[cfg(test)] mod test_utils { use rand::rngs::StdRng; From 3e148e38493c6e232e104486a8cc36d6c11d05e0 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:02:33 -0500 Subject: [PATCH 0876/1053] chore: allow FreeListIterator to skip to next free list (#1177) For the checker: if the free area is invalid, its next pointer may also contain garbage data, in which case we should stop iterating through the current free list and skip to the next one. --- storage/src/nodestore/alloc.rs | 108 ++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index b4ffcc8f5b7b..b49328e12593 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -771,6 +771,21 @@ impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { } } } + + // TODO: this function will be used by the checker to ignore free areas pointed by an invalid parent free area + #[allow(dead_code)] + fn move_to_next_free_list(&mut self) { + let (current_free_list_id, next_free_list_head) = match self.free_lists_iter.next() { + Some((id, head)) => (id as AreaIndex, *head), + None => (NUM_AREA_SIZES as AreaIndex, None), // skip unvisited free areas in the current iterator + }; + self.current_free_list_id = current_free_list_id; + self.free_list_iter = FreeListIterator::new( + self.storage, + next_free_list_head, + FreeListParent::FreeListHead(self.current_free_list_id), + ); + } } impl Iterator for FreeListsIterator<'_, S> { @@ -973,7 +988,7 @@ mod tests { // Create two free lists and check that `free_list_iter_with_metadata` correctly returns the free areas and their parents #[test] - fn free_list_iter_with_metadata() { + fn free_lists_iter_with_metadata() { let mut rng = seeded_rng(); let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); @@ -1073,6 +1088,97 @@ mod tests { } } + #[test] + fn free_lists_iter_skip_to_next_free_list() { + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let mut free_lists = FreeLists::default(); + let mut offset = NodeStoreHeader::SIZE; + + // first free list + let area_index1 = 3; + let area_size1 = AREA_SIZES[area_index1 as usize]; + let mut next_free_block1 = None; + + test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + let free_list1_area2 = LinearAddress::new(offset).unwrap(); + next_free_block1 = Some(free_list1_area2); + offset += area_size1; + + test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + let free_list1_area1 = LinearAddress::new(offset).unwrap(); + next_free_block1 = Some(free_list1_area1); + offset += area_size1; + + free_lists[area_index1 as usize] = next_free_block1; + + // second free list + let area_index2 = 5; + assert_ne!(area_index1, area_index2); + let area_size2 = AREA_SIZES[area_index2 as usize]; + let mut next_free_block2 = None; + + test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + let free_list2_area2 = LinearAddress::new(offset).unwrap(); + next_free_block2 = Some(free_list2_area2); + offset += area_size2; + + test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + let free_list2_area1 = LinearAddress::new(offset).unwrap(); + next_free_block2 = Some(free_list2_area1); + offset += area_size2; + + free_lists[area_index2 as usize] = next_free_block2; + + // write header + test_utils::test_write_header(&mut nodestore, offset, None, free_lists); + + // test iterator + let mut free_list_iter = nodestore.free_list_iter(0); + + // start at the first free list + assert_eq!(free_list_iter.current_free_list_id, 0); + assert_eq!( + free_list_iter.next_with_metadata().unwrap().unwrap(), + FreeAreaWithMetadata { + addr: free_list1_area1, + area_index: area_index1, + free_list_id: area_index1, + parent: FreeListParent::FreeListHead(area_index1), + }, + ); + // `next_with_metadata` moves the iterator to the first free list that is not empty + assert_eq!(free_list_iter.current_free_list_id, area_index1); + free_list_iter.move_to_next_free_list(); + // `move_to_next_free_list` moves the iterator to the next free list + assert_eq!(free_list_iter.current_free_list_id, area_index1 + 1); + assert_eq!( + free_list_iter.next_with_metadata().unwrap().unwrap(), + FreeAreaWithMetadata { + addr: free_list2_area1, + area_index: area_index2, + free_list_id: area_index2, + parent: FreeListParent::FreeListHead(area_index2), + }, + ); + // `next_with_metadata` moves the iterator to the first free list that is not empty + assert_eq!(free_list_iter.current_free_list_id, area_index2); + free_list_iter.move_to_next_free_list(); + // `move_to_next_free_list` moves the iterator to the next free list + assert_eq!(free_list_iter.current_free_list_id, area_index2 + 1); + assert!(free_list_iter.next_with_metadata().is_none()); + // since no more non-empty free lists, `move_to_next_free_list` moves the iterator to the last free list + assert_eq!( + free_list_iter.current_free_list_id, + (NUM_AREA_SIZES - 1) as u8 + ); + free_list_iter.move_to_next_free_list(); + // `move_to_next_free_list` will move the iterator beyond the last free list + assert_eq!(free_list_iter.current_free_list_id, NUM_AREA_SIZES as u8); + assert!(free_list_iter.next_with_metadata().is_none()); + } + #[test] fn const_expr_tests() { // these are const expr From 7e021b687a3d7e2696695285029b3ef5ca1ace53 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:28:22 -0500 Subject: [PATCH 0877/1053] fix(checker): skip freelist after first encountering an invalid free area (#1178) After encountering an error, move to the next free list instead of continuing with the current one since the free list may be corrupted. The free areas left out will be recollected as leaked areas at the final stage of checker. --- storage/src/checker/mod.rs | 97 ++++++++++++++++++++++++++++++++-- storage/src/nodestore/alloc.rs | 30 +++-------- 2 files changed, 100 insertions(+), 27 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index c5e31cb0e789..7ed7fe657497 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -402,6 +402,7 @@ impl NodeStore { Ok(free_area) => free_area, Err(e) => { errors.push(CheckerError::IO(e)); + free_list_iter.move_to_next_free_list(); continue; } }; @@ -409,6 +410,7 @@ impl NodeStore { // check that the area is aligned if let Err(e) = self.check_area_aligned(addr, StoredAreaParent::FreeList(parent)) { errors.push(e); + free_list_iter.move_to_next_free_list(); continue; } @@ -422,6 +424,7 @@ impl NodeStore { expected_free_list: area_index, parent, }); + free_list_iter.move_to_next_free_list(); continue; } @@ -429,8 +432,10 @@ impl NodeStore { if let Err(e) = visited.insert_area(addr, area_size, StoredAreaParent::FreeList(parent)) { errors.push(e); + free_list_iter.move_to_next_free_list(); continue; } + update_progress_bar(progress_bar, visited); // collect the free list stats { @@ -440,14 +445,13 @@ impl NodeStore { // collect the multi-page area count if page_start(addr) != page_start( - addr.advance(area_size) + addr.advance(area_size.saturating_sub(1)) // subtract 1 to get the last byte of the area .expect("impossible since we checked in visited.insert_area()"), ) { multi_page_area_count = multi_page_area_count.saturating_add(1); } } - update_progress_bar(progress_bar, visited); } ( @@ -582,7 +586,7 @@ mod test { test_write_free_area, test_write_header, test_write_new_node, test_write_zeroed_area, }; use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; - use crate::{BranchNode, Child, LeafNode, NodeStore, Path, hash_node}; + use crate::{BranchNode, Child, FreeListParent, LeafNode, NodeStore, Path, hash_node}; #[derive(Debug)] struct TestTrie { @@ -804,7 +808,7 @@ mod test { ); next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); let start_addr = LinearAddress::new(high_watermark).unwrap(); - let end_addr = start_addr.advance(*area_size).unwrap(); + let end_addr = start_addr.advance(*area_size - 1).unwrap(); if page_start(start_addr) != page_start(end_addr) { multi_page_area_count = multi_page_area_count.saturating_add(1); } @@ -829,8 +833,91 @@ mod test { nodestore.visit_freelist(&mut visited, None); let complement = visited.complement(); assert_eq!(complement.into_iter().collect::>(), vec![]); - assert_eq!(actual_free_lists_stats, expected_free_lists_stats); assert_eq!(free_list_errors, vec![]); + assert_eq!(actual_free_lists_stats, expected_free_lists_stats); + } + + #[test] + // Free list 1: 2048 bytes + // Free list 2: 4096 bytes + // --------------------------------------------------------------------------------------------------------------------------- + // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | + // --------------------------------------------------------------------------------------------------------------------------- + // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes + // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned + fn traverse_freelist_should_skip_offspring_of_incorrect_areas() { + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + + let mut free_lists = FreeLists::default(); + let mut high_watermark = NodeStoreHeader::SIZE + 16; // + 16 to create overlap + + // first free list + let area_index1 = 9; // 2048 + let area_size1 = AREA_SIZES[area_index1 as usize]; + let mut next_free_block1 = None; + + test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + let free_list1_area3 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area3); + high_watermark += area_size1; + + test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + let free_list1_area2 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area2); + high_watermark += area_size1; + + let intersection_end = LinearAddress::new(high_watermark).unwrap(); + high_watermark -= 16; // create an overlap with free_list1_area2 and restore to page-aligned address + let intersection_start = LinearAddress::new(high_watermark).unwrap(); + test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + let free_list1_area1 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area1); + high_watermark += area_size1; + + free_lists[area_index1 as usize] = next_free_block1; + + // second free list + let area_index2 = 10; // 4096 + let area_size2 = AREA_SIZES[area_index2 as usize]; + let mut next_free_block2 = None; + + test_write_free_area(&nodestore, next_free_block2, area_index2, high_watermark); + let free_list2_area2 = LinearAddress::new(high_watermark).unwrap(); + next_free_block2 = Some(free_list2_area2); + high_watermark += area_size2; + + test_write_free_area(&nodestore, next_free_block2, area_index2, high_watermark); + let free_list2_area1 = LinearAddress::new(high_watermark).unwrap(); + next_free_block2 = Some(free_list2_area1); + high_watermark += area_size2; + + free_lists[area_index2 as usize] = next_free_block2; + + // write header + test_write_header(&mut nodestore, high_watermark, None, free_lists); + + let expected_start_addr = free_lists[area_index1 as usize].unwrap(); + let expected_end_addr = LinearAddress::new(high_watermark).unwrap(); + let expected_free_areas = vec![expected_start_addr..expected_end_addr]; + let expected_freelist_errors = vec![CheckerError::AreaIntersects { + start: free_list1_area2, + size: area_size1, + intersection: vec![intersection_start..intersection_end], + parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea(free_list1_area1)), + }]; + let expected_free_lists_stats = FreeListsStats { + area_counts: HashMap::from([(area_size1, 1), (area_size2, 2)]), + multi_page_area_count: 0, + }; + + // test that the we traversed all the free areas + let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + let (actual_free_lists_stats, free_list_errors) = + nodestore.visit_freelist(&mut visited, None); + assert_eq!(visited.into_iter().collect::>(), expected_free_areas); + assert_eq!(actual_free_lists_stats, expected_free_lists_stats); + assert_eq!(free_list_errors, expected_freelist_errors); } #[test] diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index b49328e12593..3f62c5693342 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -755,26 +755,15 @@ impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { return Some(next); } - match self.free_lists_iter.next() { - Some((current_free_list_id, next_free_list_head)) => { - self.current_free_list_id = current_free_list_id as AreaIndex; - self.free_list_iter = FreeListIterator::new( - self.storage, - *next_free_list_head, - FreeListParent::FreeListHead(self.current_free_list_id), - ); - } - None => { - // no more free lists to iterate over - return None; - } + self.move_to_next_free_list(); + if self.current_free_list_id == NUM_AREA_SIZES as AreaIndex { + // no more free lists to iterate over + return None; } } } - // TODO: this function will be used by the checker to ignore free areas pointed by an invalid parent free area - #[allow(dead_code)] - fn move_to_next_free_list(&mut self) { + pub(crate) fn move_to_next_free_list(&mut self) { let (current_free_list_id, next_free_list_head) = match self.free_lists_iter.next() { Some((id, head)) => (id as AreaIndex, *head), None => (NUM_AREA_SIZES as AreaIndex, None), // skip unvisited free areas in the current iterator @@ -1168,13 +1157,10 @@ mod tests { // `move_to_next_free_list` moves the iterator to the next free list assert_eq!(free_list_iter.current_free_list_id, area_index2 + 1); assert!(free_list_iter.next_with_metadata().is_none()); - // since no more non-empty free lists, `move_to_next_free_list` moves the iterator to the last free list - assert_eq!( - free_list_iter.current_free_list_id, - (NUM_AREA_SIZES - 1) as u8 - ); + // since no more non-empty free lists, `move_to_next_free_list` moves the iterator to the end + assert_eq!(free_list_iter.current_free_list_id, NUM_AREA_SIZES as u8); free_list_iter.move_to_next_free_list(); - // `move_to_next_free_list` will move the iterator beyond the last free list + // `move_to_next_free_list` will do nothing since we are already at the end assert_eq!(free_list_iter.current_free_list_id, NUM_AREA_SIZES as u8); assert!(free_list_iter.next_with_metadata().is_none()); } From 2a5b7071fb35be4400d8a2573f00e38ea8aa8d81 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 6 Aug 2025 09:22:31 -0700 Subject: [PATCH 0878/1053] feat: remove Default impl on HashType (#1169) We currently have two "default" values for the TrieHash. In some cases I was using `unwrap_or_default` when I should have been using the empty RLP hash. This commit removes the `Default` impl on `HashType` and replaces those instances with `HashType::empty()`. Additionally, I have pre-calculated the empty hash value so it doesn't need to be done at runtime. --- ffi/src/lib.rs | 4 +- firewood/Cargo.toml | 1 + firewood/src/db.rs | 64 ++++++++----------------- firewood/src/manager.rs | 36 +++----------- firewood/src/merkle/tests/ethhash.rs | 2 +- firewood/src/merkle/tests/mod.rs | 6 +-- firewood/src/v2/api.rs | 72 ++++++++++++++++++++++++++++ storage/src/checker/mod.rs | 2 +- storage/src/hashednode.rs | 2 +- storage/src/lib.rs | 23 +-------- storage/src/node/branch.rs | 27 ++++++++--- storage/src/nodestore/persist.rs | 10 ++-- storage/src/trie_hash.rs | 58 ++++++++++++++++------ 13 files changed, 177 insertions(+), 130 deletions(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 0b33f903e926..6604faf68a31 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -238,7 +238,7 @@ fn get_from_root( key: &Value, ) -> Result { let db = db.ok_or("db should be non-null")?; - let requested_root = root.as_slice().try_into()?; + let requested_root = HashKey::try_from(root.as_slice()).map_err(|e| e.to_string())?; let mut cached_view = db.cached_view.lock().expect("cached_view lock is poisoned"); let value = match cached_view.as_ref() { // found the cached view, use it @@ -266,7 +266,7 @@ fn view_sync_from_root( root: &Value, ) -> Result, String> { let rev = db - .view_sync(root.as_slice().try_into()?) + .view_sync(HashKey::try_from(root.as_slice()).map_err(|e| e.to_string())?) .map_err(|e| e.to_string())?; Ok(rev) } diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 5a0b3f32fddb..8297439c86b8 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -62,6 +62,7 @@ tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread # Regular dependencies hash-db = "0.16.0" plain_hasher = "0.2.3" +rlp = "0.6.1" sha3 = "0.10.8" [[bench]] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c90ade294817..df89cdb5dfb5 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,14 +8,16 @@ use crate::merkle::{Merkle, Value}; use crate::stream::MerkleKeyValueStream; -use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; +use crate::v2::api::{ + self, FrozenProof, FrozenRangeProof, HashKey, KeyType, OptionalHashKeyExt, ValueType, +}; pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; use firewood_storage::{ CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, - ImmutableProposal, NodeStore, TrieHash, + ImmutableProposal, NodeStore, }; use metrics::{counter, describe_counter}; use std::io::Write; @@ -97,7 +99,7 @@ impl api::DbView for HistoricalRev { where Self: 'view; - async fn root_hash(&self) -> Result, api::Error> { + async fn root_hash(&self) -> Result, api::Error> { Ok(HashedNodeReader::root_hash(self)) } @@ -165,16 +167,16 @@ impl api::Db for Db { where Self: 'db; - async fn revision(&self, root_hash: TrieHash) -> Result, api::Error> { + async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { let nodestore = self.manager.revision(root_hash)?; Ok(nodestore) } - async fn root_hash(&self) -> Result, api::Error> { + async fn root_hash(&self) -> Result, api::Error> { self.root_hash_sync() } - async fn all_hashes(&self) -> Result, api::Error> { + async fn all_hashes(&self) -> Result, api::Error> { Ok(self.manager.all_hashes()) } @@ -227,11 +229,8 @@ impl Db { proposals: counter!("firewood.proposals"), }); describe_counter!("firewood.proposals", "Number of proposals created"); - let manager = RevisionManager::new( - db_path.as_ref().to_path_buf(), - cfg.truncate, - cfg.manager.clone(), - )?; + let manager = + RevisionManager::new(db_path.as_ref().to_path_buf(), cfg.truncate, cfg.manager)?; let db = Self { metrics, manager }; Ok(db) } @@ -242,39 +241,25 @@ impl Db { proposals: counter!("firewood.proposals"), }); describe_counter!("firewood.proposals", "Number of proposals created"); - let manager = RevisionManager::new( - db_path.as_ref().to_path_buf(), - cfg.truncate, - cfg.manager.clone(), - )?; + let manager = + RevisionManager::new(db_path.as_ref().to_path_buf(), cfg.truncate, cfg.manager)?; let db = Self { metrics, manager }; Ok(db) } /// Synchronously get the root hash of the latest revision. - #[cfg(not(feature = "ethhash"))] - pub fn root_hash_sync(&self) -> Result, api::Error> { - self.manager.root_hash().map_err(api::Error::from) - } - - /// Synchronously get the root hash of the latest revision. - #[cfg(feature = "ethhash")] - pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(Some( - self.manager - .root_hash()? - .unwrap_or_else(firewood_storage::empty_trie_hash), - )) + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(self.manager.root_hash()?.or_default_root_hash()) } /// Synchronously get a revision from a root hash - pub fn revision_sync(&self, root_hash: TrieHash) -> Result, api::Error> { + pub fn revision_sync(&self, root_hash: HashKey) -> Result, api::Error> { let nodestore = self.manager.revision(root_hash)?; Ok(nodestore) } /// Synchronously get a view, either committed or proposed - pub fn view_sync(&self, root_hash: TrieHash) -> Result, api::Error> { + pub fn view_sync(&self, root_hash: HashKey) -> Result, api::Error> { let nodestore = self.manager.view(root_hash)?; Ok(nodestore) } @@ -345,20 +330,9 @@ pub struct Proposal<'db> { } impl Proposal<'_> { - /// Get the root hash of the proposal synchronously - #[cfg(not(feature = "ethhash"))] - pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(self.nodestore.root_hash()) - } - - /// Get the root hash of the proposal synchronously - #[cfg(feature = "ethhash")] - pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(Some( - self.nodestore - .root_hash() - .unwrap_or_else(firewood_storage::empty_trie_hash), - )) + /// Synchronously get the root hash of the latest revision. + pub fn root_hash_sync(&self) -> Result, api::Error> { + Ok(self.nodestore.root_hash().or_default_root_hash()) } } diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 7f50bcb73e5d..4f23b71c586c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -13,8 +13,6 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -#[cfg(feature = "ethhash")] -use std::sync::OnceLock; use std::sync::{Arc, Mutex, RwLock}; use firewood_storage::logger::{trace, warn}; @@ -22,14 +20,14 @@ use metrics::gauge; use typed_builder::TypedBuilder; use crate::merkle::Merkle; -use crate::v2::api::HashKey; +use crate::v2::api::{HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, }; -#[derive(Clone, Debug, TypedBuilder)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TypedBuilder)] /// Revision manager configuratoin pub struct RevisionManagerConfig { /// The number of historical revisions to keep in memory. @@ -61,9 +59,6 @@ pub(crate) struct RevisionManager { proposals: Mutex>, // committing_proposals: VecDeque>, by_hash: RwLock>, - - #[cfg(feature = "ethhash")] - empty_hash: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -99,11 +94,9 @@ impl RevisionManager { by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), - #[cfg(feature = "ethhash")] - empty_hash: OnceLock::new(), }; - if let Some(hash) = nodestore.root_hash().or_else(|| manager.empty_trie_hash()) { + if let Some(hash) = nodestore.root_hash().or_default_root_hash() { manager .by_hash .write() @@ -123,13 +116,13 @@ impl RevisionManager { .read() .expect("poisoned lock") .iter() - .filter_map(|r| r.root_hash().or_else(|| self.empty_trie_hash())) + .filter_map(|r| r.root_hash().or_default_root_hash()) .chain( self.proposals .lock() .expect("poisoned lock") .iter() - .filter_map(|p| p.root_hash().or_else(|| self.empty_trie_hash())), + .filter_map(|p| p.root_hash().or_default_root_hash()), ) .collect() } @@ -173,7 +166,7 @@ impl RevisionManager { .expect("poisoned lock") .pop_front() .expect("must be present"); - if let Some(oldest_hash) = oldest.root_hash().or_else(|| self.empty_trie_hash()) { + if let Some(oldest_hash) = oldest.root_hash().or_default_root_hash() { self.by_hash .write() .expect("poisoned lock") @@ -212,7 +205,7 @@ impl RevisionManager { .write() .expect("poisoned lock") .push_back(committed.clone()); - if let Some(hash) = committed.root_hash().or_else(|| self.empty_trie_hash()) { + if let Some(hash) = committed.root_hash().or_default_root_hash() { self.by_hash .write() .expect("poisoned lock") @@ -291,21 +284,6 @@ impl RevisionManager { .expect("there is always one revision") .clone() } - #[cfg(not(feature = "ethhash"))] - #[inline] - pub const fn empty_trie_hash(&self) -> Option { - None - } - - #[cfg(feature = "ethhash")] - #[inline] - pub fn empty_trie_hash(&self) -> Option { - Some( - self.empty_hash - .get_or_init(firewood_storage::empty_trie_hash) - .clone(), - ) - } } #[cfg(test)] diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index 5f5f7276519b..e5c8a07fff7b 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -47,7 +47,7 @@ where V: AsRef<[u8]>, { let merkle = init_merkle(kvs.clone()); - let firewood_hash = merkle.nodestore.root_hash().unwrap_or_default(); + let firewood_hash = merkle.nodestore.root_hash().unwrap_or_else(TrieHash::empty); let eth_hash = KeccakHasher::trie_root(kvs).to_fixed_bytes().into(); assert_eq!(firewood_hash, eth_hash); } diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index 72fa0ac40a12..21fe7ce30631 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -458,11 +458,7 @@ fn single_key_proof() { { // Test that the proof is invalid when the hash is different - assert!( - proof - .verify(key, Some(value), &TrieHash::default()) - .is_err() - ); + assert!(proof.verify(key, Some(value), &TrieHash::empty()).is_err()); } } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 3242f06fbc6e..45e83d3c45d1 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -37,6 +37,49 @@ impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// proof pub type HashKey = firewood_storage::TrieHash; +/// An extension trait for the [`HashKey`] type to provide additional methods. +pub trait HashKeyExt: Sized { + /// Default root hash for an empty database. + fn default_root_hash() -> Option; +} + +/// An extension trait for an optional `HashKey` type to provide additional methods. +pub trait OptionalHashKeyExt: Sized { + /// Returns the default root hash if the current value is [`None`]. + fn or_default_root_hash(self) -> Option; +} + +#[cfg(not(feature = "ethhash"))] +impl HashKeyExt for HashKey { + /// Creates a new `HashKey` representing the empty root hash. + #[inline] + fn default_root_hash() -> Option { + None + } +} + +#[cfg(feature = "ethhash")] +impl HashKeyExt for HashKey { + #[inline] + fn default_root_hash() -> Option { + const EMPTY_RLP_HASH: [u8; size_of::()] = [ + // "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, + 0xf8, 0x6e, 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, + 0xe3, 0x63, 0xb4, 0x21, + ]; + + Some(EMPTY_RLP_HASH.into()) + } +} + +impl OptionalHashKeyExt for Option { + #[inline] + fn or_default_root_hash(self) -> Option { + self.or_else(HashKey::default_root_hash) + } +} + /// A frozen proof is a proof that is stored in immutable memory. pub type FrozenRangeProof = RangeProof>; @@ -154,6 +197,10 @@ pub enum Error { /// Revision not found #[error("revision not found")] RevisionNotFound, + + /// An invalid root hash was provided + #[error(transparent)] + InvalidRootHash(#[from] firewood_storage::InvalidTrieHashLength), } impl From for Error { @@ -322,3 +369,28 @@ pub trait Proposal: DbView + Send + Sync { data: Batch, ) -> Result; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "ethhash")] + fn test_ethhash_compat_default_root_hash_equals_empty_rlp_hash() { + use sha3::Digest as _; + + assert_eq!( + TrieHash::default_root_hash(), + sha3::Keccak256::digest(rlp::NULL_RLP) + .as_slice() + .try_into() + .ok(), + ); + } + + #[test] + #[cfg(not(feature = "ethhash"))] + fn test_firewood_default_root_hash_equals_none() { + assert_eq!(TrieHash::default_root_hash(), None); + } +} diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 7ed7fe657497..5bcc020f084f 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -735,7 +735,7 @@ mod test { // Replace branch hash with a wrong hash let (leaf_addr, _) = branch.children[1].as_ref().unwrap().persist_info().unwrap(); - branch.children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::default())); + branch.children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::empty())); test_write_new_node(&nodestore, branch_node, branch_addr.get()); // Compute the current branch hash diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 9ce50a7975f8..13279d72b0f8 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -45,7 +45,7 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { #[must_use] pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { // Key, 3 options, value digest - let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::default().len(); + let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::empty().len(); let mut buf = Vec::with_capacity(est_len); match node { Node::Branch(node) => { diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 9655a8ef4591..45f7fbcfc672 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -56,8 +56,7 @@ pub use nodestore::{ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; pub use node::persist::MaybePersistedNode; - -pub use trie_hash::TrieHash; +pub use trie_hash::{InvalidTrieHashLength, TrieHash}; /// A shared node, which is just a triophe Arc of a node pub type SharedNode = triomphe::Arc; @@ -66,7 +65,7 @@ pub type SharedNode = triomphe::Arc; /// from the storage layer. Generally, we only want to /// cache write operations, but for some read-heavy workloads /// you can enable caching of branch reads or all reads. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum CacheReadStrategy { /// Only cache writes (no reads will be cached) WritesOnly, @@ -84,24 +83,6 @@ impl Display for CacheReadStrategy { } } -/// Returns the hash of an empty trie, which is the Keccak256 hash of the RLP encoding of an empty byte array. -/// -/// This function is slow, so callers should cache the result -#[cfg(feature = "ethhash")] -#[must_use] -#[expect( - clippy::missing_panics_doc, - reason = "Found 1 occurrences after enabling the lint." -)] -pub fn empty_trie_hash() -> TrieHash { - use sha3::Digest as _; - - sha3::Keccak256::digest(rlp::NULL_RLP) - .as_slice() - .try_into() - .expect("empty trie hash is 32 bytes") -} - /// This enum encapsulates what points to the stored area. #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum StoredAreaParent { diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index c3286e51945b..5c764f90773c 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -206,6 +206,19 @@ mod ethhash { } impl HashOrRlp { + /// Creates a new `TrieHash` from the default value, which is the all zeros. + /// + /// ``` + /// assert_eq!( + /// firewood_storage::HashType::empty(), + /// firewood_storage::HashType::from([0; 32]), + /// ) + /// ``` + #[must_use] + pub fn empty() -> Self { + TrieHash::empty().into() + } + pub fn as_slice(&self) -> &[u8] { self } @@ -275,6 +288,14 @@ mod ethhash { } } + impl TryFrom<&[u8]> for HashOrRlp { + type Error = crate::InvalidTrieHashLength; + + fn try_from(value: &[u8]) -> Result { + value.try_into().map(HashOrRlp::Hash) + } + } + impl AsRef<[u8]> for HashOrRlp { fn as_ref(&self) -> &[u8] { match self { @@ -305,12 +326,6 @@ mod ethhash { } } } - - impl Default for HashOrRlp { - fn default() -> Self { - HashOrRlp::Hash(TrieHash::default()) - } - } } /// Type alias for a collection of children in a branch node. diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 5002af6723f1..f54cedb351bd 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -563,7 +563,7 @@ mod tests { for (index, child) in children { let shared_child = SharedNode::new(child); let maybe_persisted = MaybePersistedNode::from(shared_child); - let hash = HashType::default(); + let hash = HashType::empty(); branch.children[index as usize] = Some(Child::MaybePersisted(maybe_persisted, hash)); } @@ -670,21 +670,21 @@ mod tests { // unpersisted leaves Child::MaybePersisted( MaybePersistedNode::from(SharedNode::new(leaves[0].clone())), - HashType::default(), + HashType::empty(), ), Child::MaybePersisted( MaybePersistedNode::from(SharedNode::new(leaves[1].clone())), - HashType::default(), + HashType::empty(), ), // unpersisted branch Child::MaybePersisted( MaybePersistedNode::from(SharedNode::new(inner_branch.clone())), - HashType::default(), + HashType::empty(), ), // persisted branch Child::MaybePersisted( MaybePersistedNode::from(LinearAddress::new(42).unwrap()), - HashType::default(), + HashType::empty(), ), ] .into_iter() diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index bd4ee988ce27..6e2807e1efdb 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -7,11 +7,37 @@ use sha2::digest::generic_array::GenericArray; use sha2::digest::typenum; use std::fmt::{self, Debug, Display, Formatter}; +/// An error that occurs when trying to convert a slice to a `TrieHash` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)] +#[error("could not convert a slice of {0} bytes to TrieHash (an array of 32 bytes)")] +#[non_exhaustive] +pub struct InvalidTrieHashLength(pub usize); + /// A hash value inside a merkle trie /// We use the same type as returned by sha2 here to avoid copies -#[derive(PartialEq, Eq, Clone, Default, Hash)] +#[derive(PartialEq, Eq, Clone, Hash)] pub struct TrieHash(GenericArray); +/// Intentionally, there is no [`Default`] implementation for [`TrieHash`] to force +/// the user to explicitly decide between an empty RLP hash or a hash of all zeros. +/// +/// These unfortunately cannot be `const` because the [`GenericArray`] type does +/// provide a const constructor. +impl TrieHash { + /// Creates a new `TrieHash` from the default value, which is the all zeros. + /// + /// ``` + /// assert_eq!( + /// firewood_storage::TrieHash::empty(), + /// firewood_storage::TrieHash::from([0; 32]), + /// ) + /// ``` + #[must_use] + pub fn empty() -> Self { + TrieHash([0; TRIE_HASH_LEN].into()) + } +} + impl std::ops::Deref for TrieHash { type Target = GenericArray; fn deref(&self) -> &Self::Target { @@ -40,22 +66,25 @@ impl Display for TrieHash { const TRIE_HASH_LEN: usize = std::mem::size_of::(); -impl From<[u8; 32]> for TrieHash { +impl From<[u8; TRIE_HASH_LEN]> for TrieHash { fn from(value: [u8; TRIE_HASH_LEN]) -> Self { TrieHash(value.into()) } } +impl From for [u8; TRIE_HASH_LEN] { + fn from(value: TrieHash) -> Self { + value.0.into() + } +} + impl TryFrom<&[u8]> for TrieHash { - type Error = &'static str; + type Error = InvalidTrieHashLength; fn try_from(value: &[u8]) -> Result { - if value.len() == Self::len() { - let mut hash = TrieHash::default(); - hash.0.copy_from_slice(value); - Ok(hash) - } else { - Err("Invalid length") + match value.try_into() { + Ok(array) => Ok(Self::from_bytes(array)), + Err(_) => Err(InvalidTrieHashLength(value.len())), } } } @@ -67,17 +96,18 @@ impl From> for TrieHash { } impl TrieHash { - /// Return the length of a `TrieHash` - pub(crate) const fn len() -> usize { - std::mem::size_of::() - } - /// Some code needs a `TrieHash` even though it only has a `HashType`. /// This function is a no-op, as `HashType` is a `TrieHash` in this context. #[must_use] pub const fn into_triehash(self) -> Self { self } + + /// Creates a new `TrieHash` from an array of bytes. + #[must_use] + pub fn from_bytes(bytes: [u8; TRIE_HASH_LEN]) -> Self { + bytes.into() + } } impl Serializable for TrieHash { From 9a25347622e522d901fb8552e08512bbae305fdd Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 6 Aug 2025 09:31:22 -0700 Subject: [PATCH 0879/1053] feat: update revision manager error (#1170) Previously, the `api::Error` type had duplicate variants that also covered the errors from `RevisionManagerError`. This commit removes the duplicates and makes updates `RevisionManagerError` to include the fields necessary to translate the error to the `api::Error` type using the existing variants. Additionally, the message `"commit the parents of this proposal first"` is inaccurate. We also will be unable to commit a proposal when the parent is already committed if the parent is again not the latest revision. In this case, the message to commit the parents first doesn't make sense because they are already committed. We actually need to re-parent the proposal but currently do not have a mechanism for this via FFI. --- ffi/firewood.go | 1 - ffi/firewood_test.go | 5 +++-- firewood/src/manager.rs | 24 ++++++++++++++++------- firewood/src/merkle.rs | 2 +- firewood/src/v2/api.rs | 43 +++++++++++++++++++++++------------------ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index e46e40c3deea..fe54cc9391cb 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -38,7 +38,6 @@ import ( const ( RootLength = 32 rootHashNotFound = "IO error: Root hash not found" - keyNotFound = "key not found" ) var ( diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 7cbef24ef914..6197e04dabf0 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -23,6 +23,7 @@ const ( insert100Key = "100" emptyEthhashRoot = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" emptyFirewoodRoot = "0000000000000000000000000000000000000000000000000000000000000000" + errWrongParent = "The proposal cannot be committed since it is not a direct child of the most recent commit. " ) // expectedRoots contains the expected root hashes for different use cases across both default @@ -433,7 +434,7 @@ func TestConflictingProposals(t *testing.T) { // Now we ensure we cannot commit the other proposals. for i := 1; i < numProposals; i++ { err := proposals[i].Commit() - r.Contains(err.Error(), "commit the parents of this proposal first", "Commit(%d)", i) + r.Contains(err.Error(), errWrongParent, "Commit(%d)", i) } // After attempting to commit the other proposals, they should be completely invalid. @@ -760,7 +761,7 @@ func TestProposeSameRoot(t *testing.T) { // Attempt to commit P5. Since this isn't in the canonical chain, it should // fail. err = proposal5.Commit() - r.Contains(err.Error(), "commit the parents of this proposal first") // this error is internal to firewood + r.Contains(err.Error(), errWrongParent) // this error is internal to firewood // We should be able to commit P4, since it is in the canonical chain. err = proposal4.Commit() diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 4f23b71c586c..0e702f80d915 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -63,12 +63,15 @@ pub(crate) struct RevisionManager { #[derive(Debug, thiserror::Error)] pub(crate) enum RevisionManagerError { + #[error("Revision for {provided:?} not found")] + RevisionNotFound { provided: HashKey }, #[error( - "The proposal cannot be committed since it is not a direct child of the most recent commit" + "The proposal cannot be committed since it is not a direct child of the most recent commit. Proposal parent: {provided:?}, current root: {expected:?}" )] - NotLatest, - #[error("Revision not found")] - RevisionNotFound, + NotLatest { + provided: Option, + expected: Option, + }, #[error("An IO error occurred during the commit")] FileIoError(#[from] FileIoError), } @@ -149,7 +152,10 @@ impl RevisionManager { // 1. Commit check let current_revision = self.current_revision(); if !proposal.parent_hash_is(current_revision.root_hash()) { - return Err(RevisionManagerError::NotLatest); + return Err(RevisionManagerError::NotLatest { + provided: proposal.root_hash(), + expected: current_revision.root_hash(), + }); } let mut committed = proposal.as_committed(¤t_revision); @@ -258,7 +264,9 @@ impl RevisionManager { .iter() .find(|p| p.root_hash().as_ref() == Some(&root_hash)) .cloned() - .ok_or(RevisionManagerError::RevisionNotFound)?; + .ok_or(RevisionManagerError::RevisionNotFound { + provided: root_hash, + })?; Ok(Box::new(proposal)) } @@ -269,7 +277,9 @@ impl RevisionManager { .expect("poisoned lock") .get(&root_hash) .cloned() - .ok_or(RevisionManagerError::RevisionNotFound) + .ok_or(RevisionManagerError::RevisionNotFound { + provided: root_hash, + }) } pub fn root_hash(&self) -> Result, RevisionManagerError> { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 0c5b29cbaec8..2733bd5ede20 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -232,7 +232,7 @@ impl Merkle { /// /// * [`api::Error::ProofError`] - The proof structure is malformed or inconsistent /// * [`api::Error::InvalidRange`] - The proof boundaries don't match the requested range - /// * [`api::Error::IncorrectRootHash`] - The computed root hash doesn't match the expected hash + /// * [`api::Error::ParentNotLatest`] - The computed root hash doesn't match the expected hash /// * [`api::Error`] - Other errors during proposal construction or verification /// /// # Examples diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 45e83d3c45d1..132ccf1a1b65 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -130,19 +130,21 @@ pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch, }, /// Incorrect root hash for commit - #[error("Incorrect root hash for commit: {provided:?} != {current:?}")] - IncorrectRootHash { + #[error( + "The proposal cannot be committed since it is not a direct child of the most recent commit. Proposal parent: {provided:?}, current root: {expected:?}" + )] + ParentNotLatest { /// the provided root hash - provided: HashKey, - /// the current root hash - current: HashKey, + provided: Option, + /// the expected root hash + expected: Option, }, /// Invalid range @@ -182,10 +184,6 @@ pub enum Error { #[error("the latest revision is empty and has no root hash")] LatestIsEmpty, - /// This is not the latest proposal - #[error("commit the parents of this proposal first")] - NotLatest, - /// Sibling already committed #[error("sibling already committed")] SiblingCommitted, @@ -194,10 +192,6 @@ pub enum Error { #[error("proof error")] ProofError(#[from] ProofError), - /// Revision not found - #[error("revision not found")] - RevisionNotFound, - /// An invalid root hash was provided #[error(transparent)] InvalidRootHash(#[from] firewood_storage::InvalidTrieHashLength), @@ -205,10 +199,21 @@ pub enum Error { impl From for Error { fn from(err: RevisionManagerError) -> Self { + use RevisionManagerError::{FileIoError, NotLatest, RevisionNotFound}; match err { - RevisionManagerError::FileIoError(io_err) => Error::FileIO(io_err), - RevisionManagerError::NotLatest => Error::NotLatest, - RevisionManagerError::RevisionNotFound => Error::RevisionNotFound, + NotLatest { provided, expected } => Self::ParentNotLatest { provided, expected }, + RevisionNotFound { provided } => Self::RevisionNotFound { + provided: Some(provided), + }, + FileIoError(io_err) => Self::FileIO(io_err), + } + } +} + +impl From for Error { + fn from(value: crate::db::DbError) -> Self { + match value { + crate::db::DbError::FileIo(err) => Error::FileIO(err), } } } From 6d72d5347bdad2f31198f533ea9bf6a68791e4f3 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:50:29 -0500 Subject: [PATCH 0880/1053] feat(checker): return the leaked areas in the checker report (#1179) --- firewood/src/db.rs | 10 +++++++-- storage/src/checker/mod.rs | 35 ++++++++++++++++++++------------ storage/src/checker/range_set.rs | 23 ++++++++++++++++++++- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index df89cdb5dfb5..6a6b2425f4e2 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -451,7 +451,7 @@ mod test { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; - use firewood_storage::CheckOpt; + use firewood_storage::{CheckOpt, CheckerError}; use rand::rng; use crate::db::Db; @@ -776,7 +776,13 @@ mod test { progress_bar: None, }) .await; - if !report.errors.is_empty() { + if report + .errors + .iter() + .filter(|e| !matches!(e, CheckerError::AreaLeaks(_))) + .count() + != 0 + { db.dump(&mut std::io::stdout()).await.unwrap(); panic!("error: {:?}", report.errors); } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 5bcc020f084f..784009f8a3e6 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -176,9 +176,13 @@ impl NodeStore { let leaked_ranges = visited.complement(); if !leaked_ranges.is_empty() { warn!("Found leaked ranges: {leaked_ranges}"); + { + // TODO: add leaked areas to the free list + let _leaked_areas = + self.split_all_leaked_ranges(&leaked_ranges, opt.progress_bar.as_ref()); + } + errors.push(CheckerError::AreaLeaks(leaked_ranges)); } - let _leaked_areas = self.split_all_leaked_ranges(leaked_ranges, opt.progress_bar.as_ref()); - // TODO: add leaked areas to the free list let physical_bytes = match self.physical_size() { Ok(physical_bytes) => physical_bytes, @@ -475,9 +479,9 @@ impl NodeStore { } /// Wrapper around `split_into_leaked_areas` that iterates over a collection of ranges. - fn split_all_leaked_ranges( + fn split_all_leaked_ranges<'a>( &self, - leaked_ranges: impl IntoIterator>, + leaked_ranges: impl IntoIterator>, progress_bar: Option<&ProgressBar>, ) -> impl Iterator { leaked_ranges @@ -490,11 +494,11 @@ impl NodeStore { /// Returns error if the last leaked area extends beyond the end of the range. fn split_range_into_leaked_areas( &self, - leaked_range: Range, + leaked_range: Range<&LinearAddress>, progress_bar: Option<&ProgressBar>, ) -> Vec<(LinearAddress, AreaIndex)> { let mut leaked = Vec::new(); - let mut current_addr = leaked_range.start; + let mut current_addr = *leaked_range.start; // First attempt to read the valid stored areas from the leaked range loop { @@ -509,7 +513,7 @@ impl NodeStore { let next_addr = current_addr .advance(area_size) .expect("address overflow is impossible"); - match next_addr.cmp(&leaked_range.end) { + match next_addr.cmp(leaked_range.end) { Ordering::Equal => { // we have reached the end of the leaked area, done leaked.push((current_addr, area_index)); @@ -543,7 +547,7 @@ impl NodeStore { let next_addr = current_addr .advance(*area_size) .expect("address overflow is impossible"); - if next_addr <= leaked_range.end { + if next_addr <= *leaked_range.end { leaked.push((current_addr, area_index as AreaIndex)); if let Some(progress_bar) = progress_bar { progress_bar.inc(*area_size); @@ -556,7 +560,7 @@ impl NodeStore { } // we assume that all areas are aligned to `MIN_AREA_SIZE`, in which case leaked ranges can always be split into free areas perfectly - debug_assert!(current_addr == leaked_range.end); + debug_assert!(current_addr == *leaked_range.end); leaked } } @@ -976,7 +980,12 @@ mod test { } // check the leaked areas - let leaked_areas: HashMap<_, _> = nodestore.split_all_leaked_ranges(leaked, None).collect(); + let leaked_ranges = leaked + .iter() + .map(|Range { start, end }| Range { start, end }); + let leaked_areas: HashMap<_, _> = nodestore + .split_all_leaked_ranges(leaked_ranges, None) + .collect(); // assert that all leaked areas end up on the free list assert_eq!(leaked_areas, expected_free_areas); @@ -1009,8 +1018,8 @@ mod test { test_write_zeroed_area(&nodestore, leaked_range_size, NodeStoreHeader::SIZE); // check the leaked areas - let leaked_range = nonzero!(NodeStoreHeader::SIZE).into() - ..LinearAddress::new( + let leaked_range = &nonzero!(NodeStoreHeader::SIZE).into() + ..&LinearAddress::new( NodeStoreHeader::SIZE .checked_add(leaked_range_size) .unwrap(), @@ -1062,7 +1071,7 @@ mod test { // check the leaked areas let leaked_range = - nonzero!(NodeStoreHeader::SIZE).into()..LinearAddress::new(high_watermark).unwrap(); + &nonzero!(NodeStoreHeader::SIZE).into()..&LinearAddress::new(high_watermark).unwrap(); let (leaked_areas_offsets, leaked_area_size_indices): (Vec, Vec) = nodestore .split_range_into_leaked_areas(leaked_range, None) diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index 2278276fc216..b8ad3bf65e07 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -196,7 +196,7 @@ impl RangeSet { } pub fn iter(&self) -> impl Iterator> { - self.0.iter().map(|(end, start)| Range { start, end }) + self.into_iter() } pub fn is_empty(&self) -> bool { @@ -214,6 +214,18 @@ impl IntoIterator for RangeSet { } } +impl<'a, T: Debug> IntoIterator for &'a RangeSet { + type Item = Range<&'a T>; + type IntoIter = std::iter::Map< + std::collections::btree_map::Iter<'a, T, T>, + fn((&'a T, &'a T)) -> Self::Item, + >; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().map(|(end, start)| Range { start, end }) + } +} + /// A set of disjoint ranges of linear addresses in ascending order. #[derive(Debug)] pub struct LinearAddressRangeSet { @@ -321,6 +333,15 @@ impl IntoIterator for LinearAddressRangeSet { } } +impl<'a> IntoIterator for &'a LinearAddressRangeSet { + type Item = Range<&'a LinearAddress>; + type IntoIter = <&'a RangeSet as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&self.range_set).into_iter() + } +} + impl Display for LinearAddressRangeSet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { struct DisplayRange<'a>(Range<&'a LinearAddress>); From b065228974a2c6e989d07b29cb6eb18f0a0e09b8 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 6 Aug 2025 13:30:45 -0500 Subject: [PATCH 0881/1053] feat(checker): update unaligned page count (#1181) Some page areas are larger than 4KB so they cannot fit in one page anyways. Therefore, when collecting statistics, instead of collecting the number of areas that span multiple pages, we collect the number of areas that incurs an extra page read due to unalignment. --- storage/src/checker/mod.rs | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 784009f8a3e6..d24b84f33de9 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -26,8 +26,16 @@ const OS_PAGE_SIZE: u64 = 4096; #[inline] // return u64 since the start address may be 0 -const fn page_start(addr: LinearAddress) -> u64 { - addr.get() & !(OS_PAGE_SIZE - 1) +const fn page_number(addr: LinearAddress) -> u64 { + addr.get() / OS_PAGE_SIZE +} + +fn extra_read_pages(addr: LinearAddress, page_size: u64) -> Option { + let start_page = page_number(addr); + let end_page = page_number(addr.advance(page_size.saturating_sub(1))?); + let pages_read = end_page.saturating_sub(start_page); + let min_pages = page_size / OS_PAGE_SIZE; + Some(pages_read.saturating_sub(min_pages)) } #[cfg(feature = "ethhash")] @@ -92,7 +100,7 @@ pub struct FreeListsStats { /// The distribution of area sizes in the free lists pub area_counts: HashMap, /// The number of stored areas that span multiple pages - pub multi_page_area_count: u64, + pub extra_unaligned_page_read: u64, } struct SubTrieMetadata { @@ -311,12 +319,9 @@ impl NodeStore { trie_stats.low_occupancy_area_count.saturating_add(1); } // collect the multi-page area count - if page_start(subtrie_root_address) - != page_start( - subtrie_root_address - .advance(area_size) - .expect("impossible since we checked in visited.insert_area()"), - ) + if extra_read_pages(subtrie_root_address, area_size) + .expect("impossible since we checked in visited.insert_area()") + > 0 { trie_stats.multi_page_area_count = trie_stats.multi_page_area_count.saturating_add(1); @@ -447,11 +452,9 @@ impl NodeStore { let area_count = area_counts.entry(area_size).or_insert(0); *area_count = area_count.saturating_add(1); // collect the multi-page area count - if page_start(addr) - != page_start( - addr.advance(area_size.saturating_sub(1)) // subtract 1 to get the last byte of the area - .expect("impossible since we checked in visited.insert_area()"), - ) + if extra_read_pages(addr, area_size) + .expect("impossible since we checked in visited.insert_area()") + > 0 { multi_page_area_count = multi_page_area_count.saturating_add(1); } @@ -461,7 +464,7 @@ impl NodeStore { ( FreeListsStats { area_counts, - multi_page_area_count, + extra_unaligned_page_read: multi_page_area_count, }, errors, ) @@ -812,8 +815,7 @@ mod test { ); next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); let start_addr = LinearAddress::new(high_watermark).unwrap(); - let end_addr = start_addr.advance(*area_size - 1).unwrap(); - if page_start(start_addr) != page_start(end_addr) { + if extra_read_pages(start_addr, *area_size).unwrap() > 0 { multi_page_area_count = multi_page_area_count.saturating_add(1); } high_watermark += area_size; @@ -825,7 +827,7 @@ mod test { } let expected_free_lists_stats = FreeListsStats { area_counts: free_area_counts, - multi_page_area_count, + extra_unaligned_page_read: multi_page_area_count, }; // write header @@ -843,7 +845,7 @@ mod test { #[test] // Free list 1: 2048 bytes - // Free list 2: 4096 bytes + // Free list 2: 8192 bytes // --------------------------------------------------------------------------------------------------------------------------- // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | // --------------------------------------------------------------------------------------------------------------------------- @@ -882,7 +884,7 @@ mod test { free_lists[area_index1 as usize] = next_free_block1; // second free list - let area_index2 = 10; // 4096 + let area_index2 = 12; // 16384 let area_size2 = AREA_SIZES[area_index2 as usize]; let mut next_free_block2 = None; @@ -912,7 +914,7 @@ mod test { }]; let expected_free_lists_stats = FreeListsStats { area_counts: HashMap::from([(area_size1, 1), (area_size2, 2)]), - multi_page_area_count: 0, + extra_unaligned_page_read: 0, }; // test that the we traversed all the free areas From c4588ad983f5ba7ac3b9f3699497787deda3263f Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:11:14 -0500 Subject: [PATCH 0882/1053] feat(checker): add error when node data is bigger than area size (#1183) When reading a node from an area, we simply read from the address of the area without checking if the read will span beyond the given area. This PR fills in the gap. --- storage/src/checker/mod.rs | 25 ++++++++++++++----------- storage/src/lib.rs | 15 +++++++++++++++ storage/src/linear/filebacked.rs | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index d24b84f33de9..ee224a7f0b76 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -114,7 +114,6 @@ struct SubTrieMetadata { } /// [`NodeStore`] checker -// TODO: S needs to be writeable if we ask checker to fix the issues #[expect(clippy::result_large_err)] impl NodeStore { /// Go through the filebacked storage and check for any inconsistencies. It proceeds in the following steps: @@ -124,7 +123,6 @@ impl NodeStore { /// 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. - // TODO: report all errors, not just the first one pub fn check(&self, opt: CheckOpt) -> CheckerReport { // 1. Check the header let db_size = self.size(); @@ -261,6 +259,16 @@ impl NodeStore { let (area_index, area_size) = self.area_index_and_size(subtrie_root_address)?; let (node, node_bytes) = self.read_node_with_num_bytes_from_disk(subtrie_root_address)?; + // check if the node fits in the area, equal is not allowed due to 1-byte area size index + if node_bytes >= area_size { + return Err(vec![CheckerError::NodeLargerThanArea { + area_start: subtrie_root_address, + area_size, + node_bytes, + parent, + }]); + } + // if the node has a value, check that the key is valid let mut current_path_prefix = path_prefix.clone(); current_path_prefix.0.extend_from_slice(node.partial_path()); @@ -306,19 +314,14 @@ impl NodeStore { *area_count = area_count.saturating_add(1); // collect the trie bytes trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); - // collect low occupancy area count - let smallest_area_index = area_size_to_index(node_bytes).map_err(|e| { - self.file_io_error( - e, - subtrie_root_address.get(), - Some("area_size_to_index".to_string()), - ) - })?; + // collect low occupancy area count, add 1 for the area size index byte + let smallest_area_index = area_size_to_index(node_bytes.saturating_add(1)) + .expect("impossible since we checked that node_bytes < area_size"); if smallest_area_index < area_index { trie_stats.low_occupancy_area_count = trie_stats.low_occupancy_area_count.saturating_add(1); } - // collect the multi-page area count + // collect the number of areas that requires reading an extra page due to not being aligned if extra_read_pages(subtrie_root_address, area_size) .expect("impossible since we checked in visited.insert_area()") > 0 diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 45f7fbcfc672..17fa51f544b6 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -207,6 +207,21 @@ pub enum CheckerError { parent: StoredAreaParent, }, + /// Node is larger than the area it is stored in + #[error( + "stored area at {area_start:#x} with size {area_size} (parent: {parent:#x}) stores a node of size {node_bytes}" + )] + NodeLargerThanArea { + /// Address of the area + area_start: LinearAddress, + /// Size of the area + area_size: u64, + /// Size of the node + node_bytes: u64, + /// The parent of the area + parent: TrieNodeParent, + }, + /// Freelist area size does not match #[error( "Free area {address:#x} of size {size} (parent: {parent:#x}) is found in free list {actual_free_list} but it should be in freelist {expected_free_list}" diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index ade4c7025af0..802623789331 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -297,7 +297,7 @@ impl Read for PredictiveReader<'_> { impl OffsetReader for PredictiveReader<'_> { fn offset(&self) -> u64 { - self.offset + self.offset - self.len as u64 + self.pos as u64 } } From f0b8fdb512c54ebefb3c5ea1d110be9bc6e8edf0 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:34:41 -0500 Subject: [PATCH 0883/1053] perf: remove unnecessary Box on `OffsetReader` (#1185) --- storage/src/linear/filebacked.rs | 4 ++-- storage/src/linear/memory.rs | 5 +++-- storage/src/linear/mod.rs | 2 +- storage/src/nodestore/mod.rs | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 802623789331..150742510658 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -141,9 +141,9 @@ impl FileBacked { } impl ReadableStorage for FileBacked { - fn stream_from(&self, addr: u64) -> Result, FileIoError> { + fn stream_from(&self, addr: u64) -> Result { counter!("firewood.read_node", "from" => "file").increment(1); - Ok(Box::new(PredictiveReader::new(self, addr))) + Ok(PredictiveReader::new(self, addr)) } fn size(&self) -> Result { diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index 81a7e823d2db..45cdc91c9d00 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -44,7 +44,7 @@ impl WritableStorage for MemStore { } impl ReadableStorage for MemStore { - fn stream_from(&self, addr: u64) -> Result, FileIoError> { + fn stream_from(&self, addr: u64) -> Result { counter!("firewood.read_node", "from" => "memory").increment(1); let bytes = self .bytes @@ -54,7 +54,7 @@ impl ReadableStorage for MemStore { .unwrap_or_default() .to_owned(); - Ok(Box::new(Cursor::new(bytes))) + Ok(Cursor::new(bytes)) } fn size(&self) -> Result { @@ -66,6 +66,7 @@ impl ReadableStorage for MemStore { #[cfg(test)] mod test { use super::*; + use std::io::Read; use test_case::test_case; #[test_case(&[(0,&[1, 2, 3])],(0,&[1, 2, 3]); "write to empty store")] diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 042647ab9be9..eae88d1b665f 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -122,7 +122,7 @@ pub trait ReadableStorage: Debug + Sync + Send { /// # Returns /// /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails. - fn stream_from(&self, addr: u64) -> Result, FileIoError>; + fn stream_from(&self, addr: u64) -> Result; /// Return the size of the underlying storage, in bytes fn size(&self) -> Result; diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index df8e3494bbf7..156fe99f1ebb 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -44,13 +44,14 @@ pub(crate) mod header; pub(crate) mod persist; use crate::firewood_gauge; +use crate::linear::OffsetReader; use crate::logger::trace; use crate::node::branch::ReadSerializable as _; use arc_swap::ArcSwap; use arc_swap::access::DynAccess; use smallvec::SmallVec; use std::fmt::Debug; -use std::io::{Error, ErrorKind}; +use std::io::{Error, ErrorKind, Read}; use std::sync::atomic::AtomicUsize; // Re-export types from alloc module From 72528d881701c8918d71a4c7371b5a504488371c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 6 Aug 2025 18:52:56 -0700 Subject: [PATCH 0884/1053] chore: address lints triggered with rust 1.89 (#1182) 1.89 releases tomorrow (Aug 7). Clippy under this version detects the `StoredArea` is unused and complains. This will break CI once stable updates. Also included additional lints about the formatting of doc comments. To verify: ```bash { tmpdir=$(mktemp -d) RUSTUP_TOOLCHAIN=stable cargo clippy --target-dir="${tmpdir}" --workspace --all-targets -- -D warnings rm -rf "${tmpdir}" } & { tmpdir=$(mktemp -d) RUSTUP_TOOLCHAIN=1.89-beta cargo clippy --target-dir="${tmpdir}" --workspace --all-targets -- -D warnings rm -rf "${tmpdir}" } & wait ``` --- firewood/src/v2/api.rs | 28 ++++++++++++++-------------- storage/src/nodestore/alloc.rs | 16 +--------------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 132ccf1a1b65..9b5ae5f914a4 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -220,7 +220,7 @@ impl From for Error { /// The database interface. The methods here operate on the most /// recently committed revision, and allow the creation of a new -/// [Proposal] or a new [DbView] based on a specific historical +/// [`Proposal`] or a new [`DbView`] based on a specific historical /// revision. #[async_trait] pub trait Db { @@ -257,8 +257,8 @@ pub trait Db { /// /// # Arguments /// - /// * `data` - A batch consisting of [BatchOp::Put] and - /// [BatchOp::Delete] operations to apply + /// * `data` - A batch consisting of [`BatchOp::Put`] and + /// [`BatchOp::Delete`] operations to apply /// async fn propose<'db, K: KeyType, V: ValueType>( &'db self, @@ -270,12 +270,12 @@ pub trait Db { /// A view of the database at a specific time. /// -/// There are a few ways to create a [DbView]: -/// 1. From [Db::revision] which gives you a view for a specific +/// There are a few ways to create a [`DbView`]: +/// 1. From [`Db::revision`] which gives you a view for a specific /// historical revision -/// 2. From [Db::propose] which is a view on top of the most recently +/// 2. From [`Db::propose`] which is a view on top of the most recently /// committed revision with changes applied; or -/// 3. From [Proposal::propose] which is a view on top of another proposal. +/// 3. From [`Proposal::propose`] which is a view on top of another proposal. #[async_trait] pub trait DbView { /// The type of a stream of key/value pairs @@ -283,7 +283,7 @@ pub trait DbView { where Self: 'view; - /// Get the root hash for the current DbView + /// Get the root hash for the current [`DbView`] /// /// # Note /// @@ -320,8 +320,8 @@ pub trait DbView { /// /// # Note /// - /// If you always want to start at the beginning, [DbView::iter] is easier to use - /// If you always provide a key, [DbView::iter_from] is easier to use + /// If you always want to start at the beginning, [`DbView::iter`] is easier to use + /// If you always provide a key, [`DbView::iter_from`] is easier to use /// #[expect(clippy::missing_errors_doc)] fn iter_option(&self, first_key: Option) -> Result, Error>; @@ -343,13 +343,13 @@ pub trait DbView { /// A proposal for a new revision of the database. /// /// A proposal may be committed, which consumes the -/// [Proposal] and return the generic type T, which -/// is the same thing you get if you call [Db::root_hash] +/// [`Proposal`] and return the generic type `T`, which +/// is the same thing you get if you call [`Db::root_hash`] /// immediately after committing, and then call -/// [Db::revision] with the returned revision. +/// [`Db::revision`] with the returned revision. /// /// A proposal type must also implement everything in a -/// [DbView], which means you can fetch values from it or +/// [`DbView`], which means you can fetch values from it or /// obtain proofs. #[async_trait] pub trait Proposal: DbView + Send + Sync { diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 3f62c5693342..c250d088fcb1 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -272,20 +272,6 @@ impl From for LinearAddress { } } -/// Every item stored in the [`NodeStore`]'s `ReadableStorage` after the -/// `NodeStoreHeader` is a [`StoredArea`]. -/// -/// As an overview of what this looks like stored, we get something like this: -/// - Byte 0: The index of the area size -/// - Byte 1: 0x255 if free, otherwise the low-order bit indicates Branch or Leaf -/// - Bytes 2..n: The actual data -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct StoredArea { - /// Index in [`AREA_SIZES`] of this area's size - area_size_index: AreaIndex, - area: T, -} - /// `FreeLists` is an array of `Option` for each area size. pub type FreeLists = [Option; AREA_SIZES.len()]; @@ -1166,7 +1152,7 @@ mod tests { } #[test] - fn const_expr_tests() { + const fn const_expr_tests() { // these are const expr let _ = const { LinearAddress::new(0) }; let _ = const { LinearAddress::new(1).unwrap().advance(1u64) }; From a13f5f26803f31b2abc73a6066b0b06129a7b5da Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 6 Aug 2025 19:21:54 -0700 Subject: [PATCH 0885/1053] fix: Fix race around reading nodes during commit (#1180) We called persist_at before the node was actually persisted to disk in the io-uring case. What we really want is to keep all the nodes around in memory while they are being flushed, and then once they are added to the general cache, we can mark them as fully persisted. This is a lot better since now nodes will be used directly while they are being flushed instead of having a disk address that either hasn't been written to disk or at least isn't yet in the cache. --------- Co-authored-by: Austin Larson --- ffi/firewood_test.go | 73 ++++++++++++++++++++++++++++ storage/src/linear/filebacked.rs | 13 +++-- storage/src/linear/mod.rs | 4 +- storage/src/node/persist.rs | 83 ++++++++++++++++++++++++++++++-- storage/src/nodestore/persist.rs | 55 +++++++++------------ 5 files changed, 186 insertions(+), 42 deletions(-) diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 6197e04dabf0..99c5b2608160 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -9,9 +9,12 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strconv" "strings" + "sync" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -985,3 +988,73 @@ func TestGetFromRoot(t *testing.T) { _, err = db.GetFromRoot(nonExistentRoot, []byte("key")) r.Error(err, "GetFromRoot with non-existent root should return error") } + +func TestGetFromRootParallel(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + key, val := []byte("key"), []byte("value") + allKeys, allVals := kvForTest(10000) // large number to encourage contention + allKeys = append(allKeys, key) + allVals = append(allVals, val) + + // Create a proposal + p, err := db.Propose(allKeys, allVals) + r.NoError(err, "Propose key-value pairs") + + root, err := p.Root() + r.NoError(err, "Root of proposal") + + // Use multiple goroutines to stress test concurrent access + const numReaders = 10 + results := make(chan error, numReaders) + finish := make(chan struct{}) + wg := sync.WaitGroup{} + + readLots := func(readerID int) error { + for j := 0; ; j++ { + got, err := db.GetFromRoot(root, key) + if err != nil { + return fmt.Errorf("reader %d, iteration %d: GetFromRoot error: %w", readerID, j, err) + } + if !bytes.Equal(got, val) { + return fmt.Errorf("reader %d, iteration %d: expected %q, got %q", readerID, j, val, got) + } + + // Add small delays and yield to increase race chances + if j%100 == 0 { + runtime.Gosched() + } + select { + case <-finish: + return nil // Exit if finish signal received + default: + } + } + } + + // Start multiple reader goroutines + for i := 0; i < numReaders; i++ { + wg.Add(1) + go func(readerID int) { + results <- readLots(readerID) + wg.Done() + }(i) + } + + // Add small delay to let readers start + time.Sleep(time.Microsecond * 500) + t.Log("Committing proposal to allow readers to access data") + require.NoError(t, p.Commit(), "Commit proposal") + t.Log("Proposal committed, readers should now access data") + close(finish) // Signal readers to finish + + // Wait for all readers to finish + wg.Wait() + + // Collect all results + for i := 0; i < numReaders; i++ { + err := <-results + r.NoError(err, "Parallel operation failed") + } +} diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 150742510658..cca18235db8f 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -214,11 +214,18 @@ impl WritableStorage for FileBacked { fn write_cached_nodes( &self, - nodes: impl IntoIterator, + nodes: impl IntoIterator, ) -> Result<(), FileIoError> { let mut guard = self.cache.lock().expect("poisoned lock"); - for (addr, node) in nodes { - guard.put(addr, node); + for maybe_persisted_node in nodes { + // Since we know the node is in Allocated state, we can get both address and shared node + let (addr, shared_node) = maybe_persisted_node + .allocated_info() + .expect("node should be allocated"); + + guard.put(addr, shared_node); + // The node can now be read from the general cache, so we can delete the local copy + maybe_persisted_node.persist_at(addr); } Ok(()) } diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index eae88d1b665f..23d9cf885226 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -180,10 +180,10 @@ pub trait WritableStorage: ReadableStorage { /// The number of bytes written, or an error if the write operation fails. fn write(&self, offset: u64, object: &[u8]) -> Result; - /// Write all nodes to the cache (if any) + /// Write all nodes to the cache (if any) and persist them fn write_cached_nodes( &self, - _nodes: impl IntoIterator, + _nodes: impl IntoIterator, ) -> Result<(), FileIoError> { Ok(()) } diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 21a7ca76bf81..d335d2379d73 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -68,7 +68,9 @@ impl From<&MaybePersistedNode> for Option { fn from(node: &MaybePersistedNode) -> Option { match node.0.load().as_ref() { MaybePersisted::Unpersisted(_) => None, - MaybePersisted::Persisted(address) => Some(*address), + MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { + Some(*address) + } } } } @@ -90,7 +92,9 @@ impl MaybePersistedNode { /// - `Err(FileIoError)` if there was an error reading from storage pub fn as_shared_node(&self, storage: &S) -> Result { match self.0.load().as_ref() { - MaybePersisted::Unpersisted(node) => Ok(node.clone()), + MaybePersisted::Allocated(_, node) | MaybePersisted::Unpersisted(node) => { + Ok(node.clone()) + } MaybePersisted::Persisted(address) => storage.read_node(*address), } } @@ -104,7 +108,9 @@ impl MaybePersistedNode { pub fn as_linear_address(&self) -> Option { match self.0.load().as_ref() { MaybePersisted::Unpersisted(_) => None, - MaybePersisted::Persisted(address) => Some(*address), + MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { + Some(*address) + } } } @@ -116,7 +122,7 @@ impl MaybePersistedNode { #[must_use] pub fn unpersisted(&self) -> Option<&Self> { match self.0.load().as_ref() { - MaybePersisted::Unpersisted(_) => Some(self), + MaybePersisted::Allocated(_, _) | MaybePersisted::Unpersisted(_) => Some(self), MaybePersisted::Persisted(_) => None, } } @@ -134,6 +140,42 @@ impl MaybePersistedNode { pub fn persist_at(&self, addr: LinearAddress) { self.0.store(Arc::new(MaybePersisted::Persisted(addr))); } + + /// Updates the internal state to indicate this node is allocated at the specified disk address. + /// + /// This method changes the internal state of the `MaybePersistedNode` to `Allocated`, + /// indicating that the node has been allocated on disk but is still in memory. + /// + /// This is done atomically using the `ArcSwap` mechanism. + /// + /// # Arguments + /// + /// * `addr` - The `LinearAddress` where the node has been allocated on disk + pub fn allocate_at(&self, addr: LinearAddress) { + match self.0.load().as_ref() { + MaybePersisted::Unpersisted(node) | MaybePersisted::Allocated(_, node) => { + self.0 + .store(Arc::new(MaybePersisted::Allocated(addr, node.clone()))); + } + MaybePersisted::Persisted(_) => { + unreachable!("Cannot allocate a node that is already persisted on disk"); + } + } + } + + /// Returns the address and shared node if this node is in the Allocated state. + /// + /// # Returns + /// + /// Returns `Some((LinearAddress, SharedNode))` if the node is in the Allocated state, + /// otherwise `None`. + #[must_use] + pub fn allocated_info(&self) -> Option<(LinearAddress, SharedNode)> { + match self.0.load().as_ref() { + MaybePersisted::Allocated(addr, node) => Some((*addr, node.clone())), + _ => None, + } + } } /// Display the `MaybePersistedNode` as a string. @@ -149,6 +191,7 @@ impl Display for MaybePersistedNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0.load().as_ref() { MaybePersisted::Unpersisted(node) => write!(f, "M{:p}", (*node).as_ptr()), + MaybePersisted::Allocated(addr, node) => write!(f, "A{:p}@{addr}", (*node).as_ptr()), MaybePersisted::Persisted(addr) => write!(f, "{addr}"), } } @@ -156,12 +199,14 @@ impl Display for MaybePersistedNode { /// The internal state of a `MaybePersistedNode`. /// -/// This enum represents the two possible states of a `MaybePersisted`: +/// This enum represents the three possible states of a `MaybePersisted`: /// - `Unpersisted(SharedNode)`: The node is currently in memory +/// - `Allocated(LinearAddress, SharedNode)`: The node is allocated on disk but being flushed to disk /// - `Persisted(LinearAddress)`: The node is currently on disk at the specified address #[derive(Debug, PartialEq, Eq)] enum MaybePersisted { Unpersisted(SharedNode), + Allocated(LinearAddress, SharedNode), Persisted(LinearAddress), } @@ -242,4 +287,32 @@ mod test { Ok(()) } + + #[test] + fn test_allocated_info() { + let node = SharedNode::new(Node::Leaf(LeafNode { + partial_path: Path::new(), + value: vec![123].into(), + })); + + let maybe_persisted = MaybePersistedNode::from(node.clone()); + + // Initially unpersisted, so allocated_info should return None + assert!(maybe_persisted.allocated_info().is_none()); + + // Allocate the node + let addr = LinearAddress::new(2048).unwrap(); + maybe_persisted.allocate_at(addr); + + // Now allocated_info should return Some + let (retrieved_addr, retrieved_node) = maybe_persisted.allocated_info().unwrap(); + assert_eq!(retrieved_addr, addr); + assert_eq!(retrieved_node, node); + + // Persist the node + maybe_persisted.persist_at(addr); + + // After persisting, allocated_info should return None again + assert!(maybe_persisted.allocated_info().is_none()); + } } diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index f54cedb351bd..854b8315f364 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -242,15 +242,12 @@ impl NodeStore { fn flush_nodes_generic(&self) -> Result { let flush_start = Instant::now(); - // keep arcs to the allocated nodes to add them to cache + // keep MaybePersistedNodes to add them to cache and persist them let mut cached_nodes = Vec::new(); - // find all the unpersisted nodes - let unpersisted_iter = UnPersistedNodeIterator::new(self); - let mut header = self.header; let mut allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); - for node in unpersisted_iter { + for node in UnPersistedNodeIterator::new(self) { let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); let mut serialized = Vec::new(); shared_node.as_bytes(0, &mut serialized); @@ -260,7 +257,6 @@ impl NodeStore { *serialized.get_mut(0).expect("byte was reserved") = area_size_index; self.storage .write(persisted_address.get(), serialized.as_slice())?; - node.persist_at(persisted_address); // Decrement gauge immediately after node is written to storage firewood_gauge!( @@ -269,11 +265,9 @@ impl NodeStore { ) .decrement(1.0); - // Move the arc to a vector of persisted nodes for caching - // we save them so we don't have to lock the cache while we write them - // If we ever persist out of band, we might have a race condition, so - // consider adding each node to the cache as we persist them - cached_nodes.push((persisted_address, shared_node)); + // Allocate the node to store the address, then collect for caching and persistence + node.allocate_at(persisted_address); + cached_nodes.push(node); } self.storage.write_cached_nodes(cached_nodes)?; @@ -330,12 +324,13 @@ impl NodeStore { #[fastrace::trace(short_name = true)] #[cfg(feature = "io-uring")] fn flush_nodes_io_uring(&mut self) -> Result { + use crate::LinearAddress; use std::pin::Pin; #[derive(Clone, Debug)] struct PinnedBufferEntry { pinned_buffer: Pin>, - offset: Option, + node: Option<(LinearAddress, MaybePersistedNode)>, } /// Helper function to handle completion queue entries and check for errors @@ -364,13 +359,15 @@ impl NodeStore { } else { std::io::Error::from_raw_os_error(0 - entry.result()) }; + let (addr, _) = pbe.node.as_ref().expect("node should be Some"); return Err(storage.file_io_error( error, - pbe.offset.expect("offset should be Some"), + addr.get(), Some("write failure".to_string()), )); } - pbe.offset = None; + // I/O completed successfully + pbe.node = None; completed_count = completed_count.wrapping_add(1); } Ok(completed_count) @@ -383,12 +380,6 @@ impl NodeStore { let mut header = self.header; let mut node_allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); - // Collect all unpersisted nodes first to avoid mutating self while iterating - let unpersisted_nodes: Vec = { - let unpersisted_iter = UnPersistedNodeIterator::new(self); - unpersisted_iter.collect() - }; - // Collect addresses and nodes for caching let mut cached_nodes = Vec::new(); @@ -396,13 +387,13 @@ impl NodeStore { let mut saved_pinned_buffers = vec![ PinnedBufferEntry { pinned_buffer: Pin::new(Box::new([0; 0])), - offset: None, + node: None, }; RINGSIZE ]; - // Process each unpersisted node - for node in unpersisted_nodes { + // Process each unpersisted node directly from the iterator + for node in UnPersistedNodeIterator::new(self) { let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger shared_node.as_bytes(0, &mut serialized); @@ -416,10 +407,10 @@ impl NodeStore { if let Some((pos, pbe)) = saved_pinned_buffers .iter_mut() .enumerate() - .find(|(_, pbe)| pbe.offset.is_none()) + .find(|(_, pbe)| pbe.node.is_none()) { pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); - pbe.offset = Some(persisted_address.get()); + pbe.node = Some((persisted_address, node.clone())); let submission_queue_entry = self .storage @@ -471,13 +462,13 @@ impl NodeStore { } } - // Mark node as persisted and collect for cache - node.persist_at(persisted_address); - cached_nodes.push((persisted_address, shared_node)); + // Allocate the node to store the address, then collect for caching and persistence + node.allocate_at(persisted_address); + cached_nodes.push(node); } let pending = saved_pinned_buffers .iter() - .filter(|pbe| pbe.offset.is_some()) + .filter(|pbe| pbe.node.is_some()) .count(); ring.submit_and_wait(pending).map_err(|e| { self.storage @@ -498,9 +489,9 @@ impl NodeStore { } debug_assert!( - !saved_pinned_buffers.iter().any(|pbe| pbe.offset.is_some()), - "Found entry with offset still set: {:?}", - saved_pinned_buffers.iter().find(|pbe| pbe.offset.is_some()) + !saved_pinned_buffers.iter().any(|pbe| pbe.node.is_some()), + "Found entry with node still set: {:?}", + saved_pinned_buffers.iter().find(|pbe| pbe.node.is_some()) ); self.storage.write_cached_nodes(cached_nodes)?; From a4a5401008b883fe5233b35d14f08d1c2221027f Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 7 Aug 2025 11:05:21 -0700 Subject: [PATCH 0886/1053] feat: remove `Batch` type alias (#1171) The `Batch` type alias has been removed in favor of using `IntoIterator>` in its place. The functions were already generic over `K` and `V`. Therefore, making them generic over the iterator type makes sense as well. This also prevents unecessary allocations as we are no longer forced to collect into a vector unecessarily. Being able to iteratively build batch operations allow other range related improvements to be embedded in the iterator passed into `propose...`. `BatchOp` is constructed from `KeyValuePair` implementations and a default implementation is provided for `(K, V)` tuples. The behavior of using `DeleteRange` when the value is empty is preserved from FFI. Also included is a refactor of the benchmarks to use the preferred batch iterator style. This is also used later on to benchmark future changes in range operations. --- benchmark/src/create.rs | 2 +- benchmark/src/single.rs | 11 +- ffi/src/lib.rs | 22 +--- firewood/examples/insert.rs | 23 ++-- firewood/src/db.rs | 155 ++++++++++++++------------ firewood/src/v2/api.rs | 53 ++------- firewood/src/v2/batch_op.rs | 211 ++++++++++++++++++++++++++++++++++++ firewood/src/v2/mod.rs | 3 + 8 files changed, 334 insertions(+), 146 deletions(-) create mode 100644 firewood/src/v2/batch_op.rs diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index cef1b4af8903..c51ff87a5841 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -31,7 +31,7 @@ impl TestRunner for Create { let root = Span::root(func_path!(), SpanContext::random()); let _guard = root.set_local_parent(); - let batch = Self::generate_inserts(key * keys, args.global_opts.batch_size).collect(); + let batch = Self::generate_inserts(key * keys, args.global_opts.batch_size); let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index 5f17017102e2..f982756b2778 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -31,13 +31,10 @@ impl TestRunner for Single { let mut batch_id = 0; while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { - let batch = inner_keys - .iter() - .map(|key| BatchOp::Put { - key, - value: vec![batch_id as u8], - }) - .collect(); + let batch = inner_keys.iter().map(|key| BatchOp::Put { + key, + value: vec![batch_id as u8], + }); let proposal = db.propose(batch).await.expect("proposal should succeed"); proposal.commit().await?; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 6604faf68a31..a36f9931ef1c 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -29,7 +29,7 @@ use firewood::db::{ }; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -use firewood::v2::api::HashKey; +use firewood::v2::api::{HashKey, KeyValuePairIter}; use metrics::counter; #[doc(hidden)] @@ -322,21 +322,11 @@ pub unsafe extern "C" fn fwd_batch( /// * `values` - A slice of `KeyValue` structs /// /// # Returns -fn convert_to_batch(values: &[KeyValue]) -> Vec> { - let mut batch = Vec::with_capacity(values.len()); - for kv in values { - if kv.value.len == 0 { - batch.push(DbBatchOp::DeleteRange { - prefix: kv.key.as_slice(), - }); - } else { - batch.push(DbBatchOp::Put { - key: kv.key.as_slice(), - value: kv.value.as_slice(), - }); - } - } - batch +fn convert_to_batch(values: &[KeyValue]) -> impl IntoIterator> { + values + .iter() + .map(|kv| (kv.key.as_slice(), kv.value.as_slice())) + .map_into_batch() } /// Internal call for `fwd_batch` to remove error handling from the C API diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 60eb6b61d83e..e76f1bffd636 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -12,9 +12,9 @@ use std::num::NonZeroUsize; use std::ops::RangeInclusive; use std::time::Instant; -use firewood::db::{Batch, BatchOp, Db, DbConfig}; +use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; -use firewood::v2::api::{Db as _, DbView, Proposal as _}; +use firewood::v2::api::{Db as _, DbView, KeyType, Proposal as _, ValueType}; use rand::{Rng, SeedableRng as _}; use rand_distr::Alphanumeric; @@ -81,7 +81,7 @@ async fn main() -> Result<(), Box> { for _ in 0..args.number_of_batches { let keylen = rng.random_range(args.keylen.clone()); let valuelen = rng.random_range(args.valuelen.clone()); - let batch: Batch, Vec> = (0..keys) + let batch = (0..keys) .map(|_| { ( rng.borrow_mut() @@ -95,12 +95,12 @@ async fn main() -> Result<(), Box> { ) }) .map(|(key, value)| BatchOp::Put { key, value }) - .collect(); + .collect::>(); let verify = get_keys_to_verify(&batch, args.read_verify_percent); #[expect(clippy::unwrap_used)] - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch.clone()).await.unwrap(); proposal.commit().await?; verify_keys(&db, verify).await?; } @@ -114,16 +114,19 @@ async fn main() -> Result<(), Box> { Ok(()) } -fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>> { +fn get_keys_to_verify<'a, K: KeyType + 'a, V: ValueType + 'a>( + batch: impl IntoIterator>, + pct: u16, +) -> HashMap<&'a [u8], &'a [u8]> { if pct == 0 { HashMap::new() } else { batch - .iter() + .into_iter() .filter(|_last_key| rand::rng().random_range(0..=100u16.saturating_sub(pct)) == 0) .map(|op| { if let BatchOp::Put { key, value } = op { - (key.clone(), value.clone().into_boxed_slice()) + (key.as_ref(), value.as_ref()) } else { unreachable!() } @@ -134,13 +137,13 @@ fn get_keys_to_verify(batch: &Batch, Vec>, pct: u16) -> HashMap, Box<[u8]>>, + verify: HashMap<&[u8], &[u8]>, ) -> Result<(), firewood::v2::api::Error> { if !verify.is_empty() { let hash = db.root_hash().await?.expect("root hash should exist"); let revision = db.revision(hash).await?; for (key, value) in verify { - assert_eq!(Some(value), revision.val(key).await?); + assert_eq!(Some(value), revision.val(key).await?.as_deref()); } } Ok(()) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6a6b2425f4e2..a5fef0dd4137 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,10 +8,11 @@ use crate::merkle::{Merkle, Value}; use crate::stream::MerkleKeyValueStream; +pub use crate::v2::api::BatchOp; use crate::v2::api::{ - self, FrozenProof, FrozenRangeProof, HashKey, KeyType, OptionalHashKeyExt, ValueType, + self, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair, KeyValuePairIter, + OptionalHashKeyExt, }; -pub use crate::v2::api::{Batch, BatchOp}; use crate::manager::{RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; @@ -181,15 +182,15 @@ impl api::Db for Db { } #[fastrace::trace(short_name = true)] - async fn propose<'db, K: KeyType, V: ValueType>( + async fn propose<'db>( &'db self, - batch: api::Batch, + batch: (impl IntoIterator + Send), ) -> Result, api::Error> { let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); let span = fastrace::Span::enter_with_local_parent("merkleops"); - for op in batch { + for op in batch.into_iter().map_into_batch() { match op { BatchOp::Put { key, value } => { merkle.insert(key.as_ref(), value.as_ref().into())?; @@ -265,15 +266,15 @@ impl Db { } /// propose a new batch synchronously - pub fn propose_sync( + pub fn propose_sync( &self, - batch: Batch, + batch: impl IntoIterator, ) -> Result, api::Error> { let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); for op in batch { - match op { + match op.into_batch() { BatchOp::Put { key, value } => { merkle.insert(key.as_ref(), value.as_ref().into())?; } @@ -385,9 +386,9 @@ impl<'db> api::Proposal for Proposal<'db> { type Proposal = Proposal<'db>; #[fastrace::trace(short_name = true)] - async fn propose( + async fn propose( &self, - batch: api::Batch, + batch: (impl IntoIterator + Send), ) -> Result { self.create_proposal(batch) } @@ -404,23 +405,23 @@ impl Proposal<'_> { } /// Create a new proposal from the current one synchronously - pub fn propose_sync( + pub fn propose_sync( &self, - batch: api::Batch, + batch: impl IntoIterator, ) -> Result { self.create_proposal(batch) } #[crate::metrics("firewood.proposal.create", "database proposal creation")] - fn create_proposal( + fn create_proposal( &self, - batch: api::Batch, + batch: impl IntoIterator, ) -> Result { let parent = self.nodestore.clone(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); for op in batch { - match op { + match op.into_batch() { BatchOp::Put { key, value } => { merkle.insert(key.as_ref(), value.as_ref().into())?; } @@ -448,17 +449,56 @@ impl Proposal<'_> { mod test { #![expect(clippy::unwrap_used)] + use core::iter::Take; + use std::iter::Peekable; + use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use firewood_storage::{CheckOpt, CheckerError}; use rand::rng; - use crate::db::Db; - use crate::v2::api::{Db as _, DbView as _, Proposal as _}; + use crate::db::{Db, Proposal}; + use crate::v2::api::{Db as _, DbView as _, KeyValuePairIter, Proposal as _}; use super::{BatchOp, DbConfig}; + /// A chunk of an iterator, provided by [`IterExt::async_chunk_fold`] to the folding + /// function. + type Chunk<'chunk, 'base, T> = &'chunk mut Take<&'base mut Peekable>; + + trait IterExt: Iterator { + /// Asynchronously folds the iterator with chunks of a specified size. The last + /// chunk may be smaller than the specified size. + /// + /// The folding function is an async closure that takes an accumulator and a + /// chunk of the underlying iterator, and returns a new accumulator. + /// + /// # Panics + /// + /// If the folding function does not consume the entire chunk, it will panic. + /// + /// If the folding function panics, the iterator will be dropped (because this + /// method consumes `self`). + async fn async_chunk_fold(self, chunk_size: NonZeroUsize, init: B, mut f: F) -> B + where + Self: Sized, + F: for<'a, 'b> AsyncFnMut(B, Chunk<'a, 'b, Self>) -> B, + { + let chunk_size = chunk_size.get(); + let mut iter = self.peekable(); + let mut acc = init; + while iter.peek().is_some() { + let mut chunk = iter.by_ref().take(chunk_size); + acc = f(acc, chunk.by_ref()).await; + assert!(chunk.next().is_none(), "entire chunk was not consumed"); + } + acc + } + } + + impl IterExt for T {} + #[tokio::test] async fn test_proposal_reads() { let db = testdb().await; @@ -680,16 +720,11 @@ mod test { .unzip(); // create two batches, one with the first half of keys and values, and one with the last half keys and values - let mut kviter = keys - .iter() - .zip(vals.iter()) - .map(|(k, v)| BatchOp::Put { key: k, value: v }); - let batch1 = kviter.by_ref().take(N / 2).collect(); - let batch2 = kviter.collect(); + let mut kviter = keys.iter().zip(vals.iter()).map_into_batch(); // create two proposals, second one has a base of the first one - let proposal1 = db.propose(batch1).await.unwrap(); - let proposal2 = proposal1.propose(batch2).await.unwrap(); + let proposal1 = db.propose(kviter.by_ref().take(N / 2)).await.unwrap(); + let proposal2 = proposal1.propose(kviter).await.unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal let mut kviter = keys.iter().zip(vals.iter()); @@ -791,52 +826,33 @@ mod test { #[tokio::test] async fn test_deep_propose() { - const NUM_KEYS: usize = 2; + const NUM_KEYS: NonZeroUsize = const { NonZeroUsize::new(2).unwrap() }; const NUM_PROPOSALS: usize = 100; let db = testdb().await; - // create NUM_KEYS * NUM_PROPOSALS keys and values - let (keys, vals): (Vec<_>, Vec<_>) = (0..NUM_KEYS * NUM_PROPOSALS) - .map(|i| { - ( - format!("key{i}").into_bytes(), - Box::from(format!("value{i}").as_bytes()), - ) - }) - .unzip(); - - // create batches of NUM_KEYS keys and values - let batches: Vec<_> = keys - .chunks(NUM_KEYS) - .zip(vals.chunks(NUM_KEYS)) - .map(|(k, v)| { - k.iter() - .zip(v.iter()) - .map(|(k, v)| BatchOp::Put { key: k, value: v }) - .collect() - }) - .collect(); - - // better be correct - assert_eq!(batches.len(), NUM_PROPOSALS); - - // create proposals from the batches. The first one is created from the db, the others are - // children - let mut batches_iter = batches.into_iter(); - let mut proposals = vec![db.propose(batches_iter.next().unwrap()).await.unwrap()]; + let ops = (0..(NUM_KEYS.get() * NUM_PROPOSALS)) + .map(|i| (format!("key{i}"), format!("value{i}"))) + .collect::>(); - for batch in batches_iter { - let proposal = proposals.last().unwrap().propose(batch).await.unwrap(); - proposals.push(proposal); - } - - // check that each value is present in the final proposal - for (k, v) in keys.iter().zip(vals.iter()) { - assert_eq!(&proposals.last().unwrap().val(k).await.unwrap().unwrap(), v); - } + let proposals = ops + .iter() + .async_chunk_fold( + NUM_KEYS, + Vec::>::with_capacity(NUM_PROPOSALS), + async |mut proposals, ops| { + let proposal = if let Some(parent) = proposals.last() { + parent.propose(ops).await.unwrap() + } else { + db.propose(ops).await.unwrap() + }; + + proposals.push(proposal); + proposals + }, + ) + .await; - // save the last proposal root hash for comparison with the final database root hash let last_proposal_root_hash = proposals .last() .unwrap() @@ -858,8 +874,13 @@ mod test { assert_eq!(last_root_hash, last_proposal_root_hash); // check that all the keys and values are still present - for (k, v) in keys.iter().zip(vals.iter()) { - assert_eq!(&committed.val(k).await.unwrap().unwrap(), v); + for (k, v) in &ops { + let found = committed.val(k).await.unwrap(); + assert_eq!( + found.as_deref(), + Some(v.as_bytes()), + "Value for key {k:?} should be {v:?} but was {found:?}", + ); } } diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 9b5ae5f914a4..607493c1ce24 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -4,7 +4,6 @@ use crate::manager::RevisionManagerError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; -pub use crate::range_proof::RangeProof; use async_trait::async_trait; use firewood_storage::{FileIoError, TrieHash}; use futures::Stream; @@ -12,6 +11,9 @@ use std::fmt::Debug; use std::num::NonZeroUsize; use std::sync::Arc; +pub use crate::range_proof::RangeProof; +pub use crate::v2::batch_op::{BatchOp, KeyValuePair, KeyValuePairIter, MapIntoBatch}; + /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with /// lifetimes are not allowed (hence 'static) @@ -86,45 +88,6 @@ pub type FrozenRangeProof = RangeProof>; /// A frozen proof uses an immutable collection of proof nodes. pub type FrozenProof = Proof>; -/// A key/value pair operation. Only put (upsert) and delete are -/// supported -#[derive(Debug)] -pub enum BatchOp { - /// Upsert a key/value pair - Put { - /// the key - key: K, - /// the value - value: V, - }, - - /// Delete a key - Delete { - /// The key - key: K, - }, - - /// Delete a range of keys by prefix - DeleteRange { - /// The prefix of the keys to delete - prefix: K, - }, -} - -/// A list of operations to consist of a batch that -/// can be proposed -pub type Batch = Vec>; - -/// A convenience implementation to convert a vector of key/value -/// pairs into a batch of insert operations -#[must_use] -pub fn vec_into_batch(value: Vec<(K, V)>) -> Batch { - value - .into_iter() - .map(|(key, value)| BatchOp::Put { key, value }) - .collect() -} - /// Errors returned through the API #[derive(thiserror::Error, Debug)] #[non_exhaustive] @@ -170,7 +133,7 @@ pub enum Error { /// Internal error #[error("Internal error")] - InternalError(Box), + InternalError(Box), /// Range too small #[error("Range too small")] @@ -260,9 +223,9 @@ pub trait Db { /// * `data` - A batch consisting of [`BatchOp::Put`] and /// [`BatchOp::Delete`] operations to apply /// - async fn propose<'db, K: KeyType, V: ValueType>( + async fn propose<'db>( &'db self, - data: Batch, + data: (impl IntoIterator + Send), ) -> Result, Error> where Self: 'db; @@ -369,9 +332,9 @@ pub trait Proposal: DbView + Send + Sync { /// /// A new proposal /// - async fn propose( + async fn propose( &self, - data: Batch, + data: (impl IntoIterator + Send), ) -> Result; } diff --git a/firewood/src/v2/batch_op.rs b/firewood/src/v2/batch_op.rs new file mode 100644 index 000000000000..6b4189ae67d0 --- /dev/null +++ b/firewood/src/v2/batch_op.rs @@ -0,0 +1,211 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::v2::api::{KeyType, ValueType}; + +/// A key/value pair operation. Only put (upsert) and delete are +/// supported +#[derive(Debug, Clone, Copy)] +pub enum BatchOp { + /// Upsert a key/value pair + Put { + /// the key + key: K, + /// the value + value: V, + }, + + /// Delete a key + Delete { + /// The key + key: K, + }, + + /// Delete a range of keys by prefix + DeleteRange { + /// The prefix of the keys to delete + prefix: K, + }, +} + +impl BatchOp { + /// Get the key of this operation + #[must_use] + pub const fn key(&self) -> &K { + match self { + BatchOp::Put { key, .. } + | BatchOp::Delete { key } + | BatchOp::DeleteRange { prefix: key } => key, + } + } + + /// Get the value of this operation + #[must_use] + pub const fn value(&self) -> Option<&V> { + match self { + BatchOp::Put { value, .. } => Some(value), + _ => None, + } + } + + /// Convert this operation into a borrowed version, where the key and value + /// are references to the original data. + #[must_use] + pub const fn borrowed(&self) -> BatchOp<&K, &V> { + match self { + BatchOp::Put { key, value } => BatchOp::Put { key, value }, + BatchOp::Delete { key } => BatchOp::Delete { key }, + BatchOp::DeleteRange { prefix } => BatchOp::DeleteRange { prefix }, + } + } + + /// Erases the key and value types, returning a [`BatchOp`] with the key + /// and value dereferenced to `&[u8]`. + #[inline] + #[must_use] + pub fn as_ref(&self) -> BatchOp<&[u8], &[u8]> { + match self { + BatchOp::Put { key, value } => BatchOp::Put { + key: key.as_ref(), + value: value.as_ref(), + }, + BatchOp::Delete { key } => BatchOp::Delete { key: key.as_ref() }, + BatchOp::DeleteRange { prefix } => BatchOp::DeleteRange { + prefix: prefix.as_ref(), + }, + } + } +} + +impl BatchOp<&[u8], &[u8]> { + fn eq_impl(self, other: Self) -> bool { + std::mem::discriminant(&self) == std::mem::discriminant(&other) + && self.key() == other.key() + && self.value() == other.value() + } + + fn hash_impl(self, state: &mut H) { + use std::hash::Hash; + std::mem::discriminant(&self).hash(state); + self.key().hash(state); + if let Some(value) = self.value() { + value.hash(state); + } + } +} + +impl PartialEq> for BatchOp +where + K1: KeyType, + K2: KeyType, + V1: ValueType, + V2: ValueType, +{ + fn eq(&self, other: &BatchOp) -> bool { + BatchOp::eq_impl(self.as_ref(), other.as_ref()) + } +} + +impl Eq for BatchOp {} + +impl std::hash::Hash for BatchOp { + fn hash(&self, state: &mut H) { + BatchOp::hash_impl(self.as_ref(), state); + } +} + +/// A key/value pair that can be used in a batch. +pub trait KeyValuePair { + /// The key type + type Key: KeyType; + + /// The value type + type Value: ValueType; + + /// Convert this key-value pair into a [`BatchOp`]. + #[must_use] + fn into_batch(self) -> BatchOp; +} + +impl<'a, K: KeyType, V: ValueType> KeyValuePair for &'a (K, V) { + type Key = &'a K; + type Value = &'a V; + + #[inline] + fn into_batch(self) -> BatchOp { + // this converting `&'a (K, V)` into `(&'a K, &'a V)` + let (key, value) = self; + (key, value).into_batch() + } +} + +impl KeyValuePair for (K, V) { + type Key = K; + type Value = V; + + #[inline] + fn into_batch(self) -> BatchOp { + let (key, value) = self; + if value.as_ref().is_empty() { + BatchOp::DeleteRange { prefix: key } + } else { + BatchOp::Put { key, value } + } + } +} + +impl KeyValuePair for BatchOp { + type Key = K; + type Value = V; + + fn into_batch(self) -> BatchOp { + self + } +} + +impl<'a, K: KeyType, V: ValueType> KeyValuePair for &'a BatchOp { + type Key = &'a K; + type Value = &'a V; + + fn into_batch(self) -> BatchOp { + self.borrowed() + } +} + +/// An extension trait for iterators that yield [`KeyValuePair`]s. +pub trait KeyValuePairIter: + Iterator> +{ + /// An associated type for the iterator item's key type. This is a convenience + /// requirement to avoid needing to build up nested generic associated types. + /// E.g., `<::Item as KeyValuePair>::Key` + type Key: KeyType; + + /// An associated type for the iterator item's value type. This is a convenience + /// requirement to avoid needing to build up nested generic associated types. + /// E.g., `<::Item as KeyValuePair>::Value` + type Value: ValueType; + + /// Maps the items of this iterator into [`BatchOp`]s. + #[inline] + fn map_into_batch(self) -> MapIntoBatch + where + Self: Sized, + Self::Item: KeyValuePair, + { + self.map(KeyValuePair::into_batch) + } +} + +impl> KeyValuePairIter for I { + type Key = ::Key; + type Value = ::Value; +} + +/// An iterator that maps a [`KeyValuePair`] into a [`BatchOp`] on yielded items. +pub type MapIntoBatch = std::iter::Map< + I, + fn( + ::Item, + ) -> BatchOp<::Key, ::Value>, +>; diff --git a/firewood/src/v2/mod.rs b/firewood/src/v2/mod.rs index 31453fa78a14..f8c1ad60f7f8 100644 --- a/firewood/src/v2/mod.rs +++ b/firewood/src/v2/mod.rs @@ -3,3 +3,6 @@ /// The public API pub mod api; + +/// A batch operation and associated types +mod batch_op; From cfd49702edddac501d1fc84cbbc9e8f34a262758 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Aug 2025 11:11:49 -0700 Subject: [PATCH 0887/1053] test: Add read-during-commit test (#1186) --- firewood/Cargo.toml | 1 + firewood/src/db.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 8297439c86b8..02a3767f00c2 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -64,6 +64,7 @@ hash-db = "0.16.0" plain_hasher = "0.2.3" rlp = "0.6.1" sha3 = "0.10.8" +tokio-scoped = "0.2.0" [[bench]] name = "hashops" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index a5fef0dd4137..61178c463f90 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -57,7 +57,7 @@ pub trait DbViewSync { } /// A synchronous view of the database with raw byte keys (object-safe version). -pub trait DbViewSyncBytes: std::fmt::Debug { +pub trait DbViewSyncBytes: std::fmt::Debug + Send + Sync { /// find a value synchronously using raw bytes fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError>; } @@ -457,6 +457,7 @@ mod test { use firewood_storage::{CheckOpt, CheckerError}; use rand::rng; + use tokio::sync::mpsc::{Receiver, Sender}; use crate::db::{Db, Proposal}; use crate::v2::api::{Db as _, DbView as _, KeyValuePairIter, Proposal as _}; @@ -884,6 +885,56 @@ mod test { } } + /// Test that reading from a proposal during commit works as expected + #[tokio::test(flavor = "multi_thread")] + async fn test_read_during_commit() { + use crate::db::Proposal; + + const CHANNEL_CAPACITY: usize = 8; + + let testdb = testdb().await; + let db = &testdb.db; + + let (tx, mut rx): (Sender>, Receiver>) = + tokio::sync::mpsc::channel(CHANNEL_CAPACITY); + let (result_tx, mut result_rx) = tokio::sync::mpsc::channel(CHANNEL_CAPACITY); + + tokio_scoped::scope(|scope| { + // Commit task + scope.spawn(async move { + while let Some(proposal) = rx.recv().await { + let result = proposal.commit().await; + // send result back to the main thread, both for synchronization and stopping the + // test on error + result_tx.send(result).await.unwrap(); + } + }); + scope.spawn(async move { + // Proposal creation + for id in 0u32..5000 { + // insert a key of length 32 and a value of length 8, + // rotating between all zeroes through all 255 + let batch = vec![BatchOp::Put { + key: [id as u8; 32], + value: [id as u8; 8], + }]; + let proposal = db.propose(batch).await.unwrap(); + let last_hash = proposal.root_hash().await.unwrap().unwrap(); + let view = db.view_sync(last_hash).unwrap(); + + tx.send(proposal).await.unwrap(); + + let key = [id as u8; 32]; + let value = view.val_sync_bytes(&key).unwrap().unwrap(); + assert_eq!(&*value, &[id as u8; 8]); + result_rx.recv().await.unwrap().unwrap(); + } + // close the channel, which will cause the commit task to exit + drop(tx); + }); + }); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, From 15ba61cf6534d7882e4145e73c96f8714813a916 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 7 Aug 2025 11:29:05 -0700 Subject: [PATCH 0888/1053] chore: deny `undocumented-unsafe-blocks` (#1172) Except `ffi`, temporarily. --- Cargo.toml | 1 + ffi/src/lib.rs | 4 ++++ storage/src/nodestore/alloc.rs | 2 ++ storage/src/nodestore/header.rs | 11 +++-------- storage/src/nodestore/persist.rs | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e22697803673..f1201cb15e9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ indexing_slicing = "warn" explicit_deref_methods = "warn" missing_const_for_fn = "warn" arithmetic_side_effects = "warn" +undocumented_unsafe_blocks = "deny" # lower the priority of pedantic to allow overriding the lints it includes pedantic = { level = "warn", priority = -1 } # These lints are from pedantic but allowed. They are a bit too pedantic and diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index a36f9931ef1c..8b5092398c09 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -6,6 +6,10 @@ unsafe_code, reason = "This is an FFI library, so unsafe code is expected." )] +#![expect( + clippy::undocumented_unsafe_blocks, + reason = "https://github.com/ava-labs/firewood/pull/1158 will remove" +)] #![cfg_attr( not(target_pointer_width = "64"), forbid( diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index c250d088fcb1..659af091da8f 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -166,8 +166,10 @@ pub fn area_size_to_index(n: u64) -> Result { pub struct LinearAddress(NonZeroU64); #[expect(unsafe_code)] +// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `ZeroableInOption`. unsafe impl bytemuck::ZeroableInOption for LinearAddress {} #[expect(unsafe_code)] +// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `PodInOption`. unsafe impl bytemuck::PodInOption for LinearAddress {} impl LinearAddress { diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index 0c04b92d8ef6..c17f82e4e728 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -24,7 +24,7 @@ //! - Uses C-compatible representation for cross-language access //! -use bytemuck_derive::{AnyBitPattern, NoUninit}; +use bytemuck_derive::{Pod, Zeroable}; use std::io::{Error, ErrorKind}; use super::alloc::{FreeLists, LinearAddress, area_size_hash}; @@ -32,7 +32,7 @@ use crate::logger::{debug, trace}; /// Can be used by filesystem tooling such as "file" to identify /// the version of firewood used to create this `NodeStore` file. -#[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit, AnyBitPattern)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)] #[repr(transparent)] pub struct Version { bytes: [u8; 16], @@ -148,7 +148,7 @@ impl Version { /// Persisted metadata for a `NodeStore`. /// The [`NodeStoreHeader`] is at the start of the `ReadableStorage`. -#[derive(Copy, Debug, PartialEq, Eq, Clone)] +#[derive(Copy, Debug, PartialEq, Eq, Clone, Zeroable, Pod)] #[repr(C)] pub struct NodeStoreHeader { /// Identifies the version of firewood used to create this `NodeStore`. @@ -303,11 +303,6 @@ impl NodeStoreHeader { } } -#[expect(unsafe_code)] -unsafe impl bytemuck::Zeroable for NodeStoreHeader {} -#[expect(unsafe_code)] -unsafe impl bytemuck::Pod for NodeStoreHeader {} - #[cfg(test)] #[expect(clippy::unwrap_used)] mod tests { diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 854b8315f364..50502a0a1dd4 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -419,10 +419,10 @@ impl NodeStore { .build() .user_data(pos as u64); + #[expect(unsafe_code)] // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope // until the operation has been completed. This is ensured by having a Some(offset) // and not marking it None until the kernel has said it's done below. - #[expect(unsafe_code)] while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { ring.submitter().squeue_wait().map_err(|e| { self.storage.file_io_error( From a1838f164b71ff01240a1fdb3fa7cb5a21588979 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 7 Aug 2025 11:34:38 -0700 Subject: [PATCH 0889/1053] test: fix merkle compatibility test (#1173) The `createRandomBatch` utility method would sometimes generate more values than keys. This caused a future change to trigger the test to break. It has been fixed to ensure that an equal number of keys and values are generated. --- ffi/tests/firewood/merkle_compatibility_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index fbbc7e76dfb9..d93fc82bbcdf 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -153,7 +153,7 @@ func (tr *tree) dbUpdate() { func (tr *tree) createRandomBatch(numKeys int) ([][]byte, [][]byte) { keys := tr.selectRandomKeys(numKeys) - vals := createRandomByteSlices(numKeys, valSize, tr.rand) + vals := createRandomByteSlices(len(keys), valSize, tr.rand) return keys, vals } From 8ba74f4354ab80499d737f0949b020a551d4970c Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:33:06 -0500 Subject: [PATCH 0890/1053] feat(checker): annotate IO error with parent pointer in checker errors (#1188) This is the initial diff to fix freelist. Some IO errors are fixable by truncating the free list, so annotate IO error with enough information to fix it at the end of traversal. --- storage/src/checker/mod.rs | 31 ++++++++-- storage/src/lib.rs | 13 ++--- storage/src/nodestore/alloc.rs | 100 +++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 56 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index ee224a7f0b76..b45fb807d5a3 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -193,7 +193,10 @@ impl NodeStore { let physical_bytes = match self.physical_size() { Ok(physical_bytes) => physical_bytes, Err(e) => { - errors.push(e.into()); + errors.push(CheckerError::IO { + error: e, + parent: None, + }); 0 } }; @@ -256,8 +259,22 @@ impl NodeStore { self.check_area_aligned(subtrie_root_address, StoredAreaParent::TrieNode(parent))?; // read the node from the disk - we avoid cache since we will never visit the same node twice - let (area_index, area_size) = self.area_index_and_size(subtrie_root_address)?; - let (node, node_bytes) = self.read_node_with_num_bytes_from_disk(subtrie_root_address)?; + let (area_index, area_size) = + self.area_index_and_size(subtrie_root_address) + .map_err(|e| { + vec![CheckerError::IO { + error: e, + parent: Some(StoredAreaParent::TrieNode(parent)), + }] + })?; + let (node, node_bytes) = self + .read_node_with_num_bytes_from_disk(subtrie_root_address) + .map_err(|e| { + vec![CheckerError::IO { + error: e, + parent: Some(StoredAreaParent::TrieNode(parent)), + }] + })?; // check if the node fits in the area, equal is not allowed due to 1-byte area size index if node_bytes >= area_size { @@ -404,16 +421,18 @@ impl NodeStore { let mut errors = Vec::new(); let mut free_list_iter = self.free_list_iter(0); - while let Some(free_area) = free_list_iter.next_with_metadata() { + while let Some((free_area, parent)) = free_list_iter.next_with_metadata() { let FreeAreaWithMetadata { addr, area_index, free_list_id, - parent, } = match free_area { Ok(free_area) => free_area, Err(e) => { - errors.push(CheckerError::IO(e)); + errors.push(CheckerError::IO { + error: e, + parent: Some(StoredAreaParent::FreeList(parent)), + }); free_list_iter.move_to_next_free_list(); continue; } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 17fa51f544b6..0072d735161f 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -278,7 +278,12 @@ pub enum CheckerError { /// IO error #[error("IO error")] #[derive_where(skip_inner)] - IO(#[from] FileIoError), + IO { + /// The error + error: FileIoError, + /// parent of the area + parent: Option, + }, } impl From for Vec { @@ -287,12 +292,6 @@ impl From for Vec { } } -impl From for Vec { - fn from(error: FileIoError) -> Self { - vec![CheckerError::IO(error)] - } -} - #[cfg(test)] mod test_utils { use rand::rngs::StdRng; diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 659af091da8f..2ec4dbca19be 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -643,10 +643,13 @@ impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { )] fn next_with_parent( &mut self, - ) -> Option> { + ) -> Option<( + Result<(LinearAddress, AreaIndex), FileIoError>, + FreeListParent, + )> { let parent = self.parent; let next_addr = self.next()?; - Some(next_addr.map(|free_area| (free_area, parent))) + Some((next_addr, parent)) } } @@ -680,7 +683,6 @@ pub(crate) struct FreeAreaWithMetadata { pub addr: LinearAddress, pub area_index: AreaIndex, pub free_list_id: AreaIndex, - pub parent: FreeListParent, } pub(crate) struct FreeListsIterator<'a, S: ReadableStorage> { @@ -721,15 +723,17 @@ impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { pub(crate) fn next_with_metadata( &mut self, - ) -> Option> { + ) -> Option<(Result, FreeListParent)> { self.next_inner(FreeListIterator::next_with_parent) - .map(|next_with_parent| { - next_with_parent.map(|((addr, area_index), parent)| FreeAreaWithMetadata { - addr, - area_index, - free_list_id: self.current_free_list_id, + .map(|(next, parent)| { + ( + next.map(|(addr, area_index)| FreeAreaWithMetadata { + addr, + area_index, + free_list_id: self.current_free_list_id, + }), parent, - }) + ) }) } @@ -1017,33 +1021,41 @@ mod tests { // expected let expected_free_list1 = vec![ - FreeAreaWithMetadata { - addr: free_list1_area1, - area_index: area_index1, - free_list_id: area_index1, - parent: FreeListParent::FreeListHead(area_index1), - }, - FreeAreaWithMetadata { - addr: free_list1_area2, - area_index: area_index1, - free_list_id: area_index1, - parent: FreeListParent::PrevFreeArea(free_list1_area1), - }, + ( + FreeAreaWithMetadata { + addr: free_list1_area1, + area_index: area_index1, + free_list_id: area_index1, + }, + FreeListParent::FreeListHead(area_index1), + ), + ( + FreeAreaWithMetadata { + addr: free_list1_area2, + area_index: area_index1, + free_list_id: area_index1, + }, + FreeListParent::PrevFreeArea(free_list1_area1), + ), ]; let expected_free_list2 = vec![ - FreeAreaWithMetadata { - addr: free_list2_area1, - area_index: area_index2, - free_list_id: area_index2, - parent: FreeListParent::FreeListHead(area_index2), - }, - FreeAreaWithMetadata { - addr: free_list2_area2, - area_index: area_index2, - free_list_id: area_index2, - parent: FreeListParent::PrevFreeArea(free_list2_area1), - }, + ( + FreeAreaWithMetadata { + addr: free_list2_area1, + area_index: area_index2, + free_list_id: area_index2, + }, + FreeListParent::FreeListHead(area_index2), + ), + ( + FreeAreaWithMetadata { + addr: free_list2_area2, + area_index: area_index2, + free_list_id: area_index2, + }, + FreeListParent::PrevFreeArea(free_list2_area1), + ), ]; let mut expected_iterator = if area_index1 < area_index2 { @@ -1054,14 +1066,14 @@ mod tests { loop { let next = free_list_iter.next_with_metadata(); - let expected = expected_iterator.next(); - - if expected.is_none() { + let Some((expected, expected_parent)) = expected_iterator.next() else { assert!(next.is_none()); break; - } + }; - assert_eq!(next.unwrap().unwrap(), expected.unwrap()); + let (next, next_parent) = next.unwrap(); + assert_eq!(next.unwrap(), expected); + assert_eq!(next_parent, expected_parent); } } @@ -1116,29 +1128,31 @@ mod tests { // start at the first free list assert_eq!(free_list_iter.current_free_list_id, 0); + let (next, next_parent) = free_list_iter.next_with_metadata().unwrap(); assert_eq!( - free_list_iter.next_with_metadata().unwrap().unwrap(), + next.unwrap(), FreeAreaWithMetadata { addr: free_list1_area1, area_index: area_index1, free_list_id: area_index1, - parent: FreeListParent::FreeListHead(area_index1), }, ); + assert_eq!(next_parent, FreeListParent::FreeListHead(area_index1)); // `next_with_metadata` moves the iterator to the first free list that is not empty assert_eq!(free_list_iter.current_free_list_id, area_index1); free_list_iter.move_to_next_free_list(); // `move_to_next_free_list` moves the iterator to the next free list assert_eq!(free_list_iter.current_free_list_id, area_index1 + 1); + let (next, next_parent) = free_list_iter.next_with_metadata().unwrap(); assert_eq!( - free_list_iter.next_with_metadata().unwrap().unwrap(), + next.unwrap(), FreeAreaWithMetadata { addr: free_list2_area1, area_index: area_index2, free_list_id: area_index2, - parent: FreeListParent::FreeListHead(area_index2), }, ); + assert_eq!(next_parent, FreeListParent::FreeListHead(area_index2)); // `next_with_metadata` moves the iterator to the first free list that is not empty assert_eq!(free_list_iter.current_free_list_id, area_index2); free_list_iter.move_to_next_free_list(); From 91cc5735669bfa23af4bf9e07a4d05c83152fe1d Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 7 Aug 2025 15:16:32 -0700 Subject: [PATCH 0891/1053] fix(fwdctl)!: db path consistency + no auto-create (#1189) I found that a small typo in the database creates a new one. This isn't the expected behavior, and is a regression from the ffi changes that always create a database. In addition, cleaned up the database path option so it's exactly the same across all subcommands. - Use consistent `--db` flag across all commands instead of positional arguments - Set `create_if_missing=false` for most commands (except `create`) - Refactor tests to use cleaner `.arg()` syntax for single arguments - Compute the test path one time for all tests - Remove unnecessary string conversions --- firewood/src/db.rs | 21 ++++- firewood/src/manager.rs | 34 ++++--- fwdctl/Cargo.toml | 2 +- fwdctl/README.md | 6 +- fwdctl/src/check.rs | 16 ++-- fwdctl/src/create.rs | 18 ++-- fwdctl/src/delete.rs | 19 ++-- fwdctl/src/dump.rs | 16 ++-- fwdctl/src/get.rs | 20 ++-- fwdctl/src/graph.rs | 15 ++- fwdctl/src/insert.rs | 19 ++-- fwdctl/src/main.rs | 20 ++++ fwdctl/src/root.rs | 18 ++-- fwdctl/tests/cli.rs | 152 +++++++++++++++++++------------ storage/src/linear/filebacked.rs | 5 +- storage/src/nodestore/persist.rs | 1 + 16 files changed, 213 insertions(+), 169 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 61178c463f90..42b990e632a6 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -14,7 +14,7 @@ use crate::v2::api::{ OptionalHashKeyExt, }; -use crate::manager::{RevisionManager, RevisionManagerConfig}; +use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; use async_trait::async_trait; use firewood_storage::{ CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, @@ -143,6 +143,9 @@ impl api::DbView for HistoricalRev { /// Database configuration. #[derive(Clone, TypedBuilder, Debug)] pub struct DbConfig { + /// Whether to create the DB if it doesn't exist. + #[builder(default = true)] + pub create_if_missing: bool, /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its /// existing contents will be lost. #[builder(default = false)] @@ -230,8 +233,12 @@ impl Db { proposals: counter!("firewood.proposals"), }); describe_counter!("firewood.proposals", "Number of proposals created"); - let manager = - RevisionManager::new(db_path.as_ref().to_path_buf(), cfg.truncate, cfg.manager)?; + let config_manager = ConfigManager::builder() + .create(cfg.create_if_missing) + .truncate(cfg.truncate) + .manager(cfg.manager) + .build(); + let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager)?; let db = Self { metrics, manager }; Ok(db) } @@ -242,8 +249,12 @@ impl Db { proposals: counter!("firewood.proposals"), }); describe_counter!("firewood.proposals", "Number of proposals created"); - let manager = - RevisionManager::new(db_path.as_ref().to_path_buf(), cfg.truncate, cfg.manager)?; + let config_manager = ConfigManager::builder() + .create(cfg.create_if_missing) + .truncate(cfg.truncate) + .manager(cfg.manager) + .build(); + let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager)?; let db = Self { metrics, manager }; Ok(db) } diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 0e702f80d915..19108714cdc7 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -45,6 +45,21 @@ pub struct RevisionManagerConfig { cache_read_strategy: CacheReadStrategy, } +#[derive(Clone, Debug, TypedBuilder)] +/// Configuration manager that contains both truncate and revision manager config +pub struct ConfigManager { + /// Whether to create the DB if it doesn't exist. + #[builder(default = true)] + pub create: bool, + /// Whether to truncate the DB when opening it. If set, the DB will be reset and all its + /// existing contents will be lost. + #[builder(default = false)] + pub truncate: bool, + /// Revision manager configuration. + #[builder(default = RevisionManagerConfig::builder().build())] + pub manager: RevisionManagerConfig, +} + type CommittedRevision = Arc>; type ProposedRevision = Arc, FileBacked>>; @@ -77,22 +92,19 @@ pub(crate) enum RevisionManagerError { } impl RevisionManager { - pub fn new( - filename: PathBuf, - truncate: bool, - config: RevisionManagerConfig, - ) -> Result { + pub fn new(filename: PathBuf, config: ConfigManager) -> Result { let fb = FileBacked::new( filename, - config.node_cache_size, - config.free_list_cache_size, - truncate, - config.cache_read_strategy, + config.manager.node_cache_size, + config.manager.free_list_cache_size, + config.truncate, + config.create, + config.manager.cache_read_strategy, )?; let storage = Arc::new(fb); let nodestore = Arc::new(NodeStore::open(storage.clone())?); let manager = Self { - max_revisions: config.max_revisions, + max_revisions: config.manager.max_revisions, historical: RwLock::new(VecDeque::from([nodestore.clone()])), by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), @@ -107,7 +119,7 @@ impl RevisionManager { .insert(hash, nodestore.clone()); } - if truncate { + if config.truncate { nodestore.flush_header_with_padding()?; } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 620f98572c84..295448592cd8 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -24,7 +24,7 @@ path = "src/main.rs" [dependencies] # Workspace dependencies -clap = { workspace = true, features = ["cargo"] } +clap = { workspace = true, features = ["cargo", "string"] } env_logger.workspace = true firewood.workspace = true firewood-storage.workspace = true diff --git a/fwdctl/README.md b/fwdctl/README.md index 174090bdeaa1..b5afe1b00e20 100644 --- a/fwdctl/README.md +++ b/fwdctl/README.md @@ -30,10 +30,8 @@ To use ```sh # Check available options when creating a database, including the defaults. $ fwdctl create -h -# Create a new, blank instance of firewood using the default name "firewood". -$ fwdctl create firewood -# Look inside, there are several folders representing different components of firewood, including the WAL. -$ ls firewood +# Create a new, blank instance of firewood using the default name "firewood.db". +$ fwdctl create firewood.db ``` * fwdctl get KEY diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 72c4c526dc6f..e0909cc92fdb 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -10,18 +10,13 @@ use firewood_storage::{CacheReadStrategy, CheckOpt, CheckerReport, FileBacked, N use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use nonzero_ext::nonzero; +use crate::DatabasePath; + // TODO: (optionally) add a fix option #[derive(Args)] pub struct Options { - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - long, - required = false, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, + #[command(flatten)] + pub database: DatabasePath, /// Whether to perform hash check #[arg( @@ -34,7 +29,7 @@ pub struct Options { } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - let db_path = PathBuf::from(&opts.db); + let db_path = PathBuf::from(&opts.database.dbpath); let node_cache_size = nonzero!(1usize); let free_list_cache_size = nonzero!(1usize); @@ -43,6 +38,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { node_cache_size, free_list_cache_size, false, + false, // don't create if missing CacheReadStrategy::WritesOnly, // we scan the database once - no need to cache anything )?; let storage = Arc::new(fb); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 698116674060..e6bf4121b31b 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -5,15 +5,12 @@ use clap::{Args, value_parser}; use firewood::db::{Db, DbConfig}; use firewood::v2::api; +use crate::DatabasePath; + #[derive(Args)] pub struct Options { - /// DB Options - #[arg( - required = true, - value_name = "NAME", - help = "A name for the database. A good default name is firewood." - )] - pub name: String, + #[command(flatten)] + pub database: DatabasePath, #[arg( long, @@ -56,7 +53,10 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db_config = new(opts); log::debug!("database configuration parameters: \n{db_config:?}\n"); - Db::new(opts.name.clone(), db_config).await?; - println!("created firewood database in {:?}", opts.name); + Db::new(opts.database.dbpath.clone(), db_config).await?; + println!( + "created firewood database in {}", + opts.database.dbpath.display() + ); Ok(()) } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 6c9319b768e7..33e8d9070fc2 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -5,28 +5,23 @@ use clap::Args; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::v2::api::{self, Db as _, Proposal as _}; +use crate::DatabasePath; + #[derive(Debug, Args)] pub struct Options { + #[command(flatten)] + pub database: DatabasePath, + /// The key to delete #[arg(required = true, value_name = "KEY", help = "Key to delete")] pub key: String, - - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - long, - required = false, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("deleting key {opts:?}"); - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; let batch: Vec> = vec![BatchOp::Delete { key: opts.key.clone(), diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index e498f578f245..478d20df6925 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -18,18 +18,14 @@ use std::fs::File; use std::io::{BufWriter, Write}; use std::path::PathBuf; +use crate::DatabasePath; + type KeyFromStream = Option>; #[derive(Debug, Args)] pub struct Options { - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - required = true, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, + #[command(flatten)] + pub database: DatabasePath, /// The key to start dumping from (if no key is provided, start from the beginning). /// Defaults to None. @@ -145,8 +141,8 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { } } - let cfg = DbConfig::builder().truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; let latest_hash = db.root_hash().await?; let Some(latest_hash) = latest_hash else { println!("Database is empty"); diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 03a9ac564089..e89df9351672 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -2,33 +2,27 @@ // See the file LICENSE.md for licensing terms. use clap::Args; -use std::str; use firewood::db::{Db, DbConfig}; use firewood::v2::api::{self, Db as _, DbView as _}; +use crate::DatabasePath; + #[derive(Debug, Args)] pub struct Options { + #[command(flatten)] + pub database: DatabasePath, + /// The key to get the value for #[arg(required = true, value_name = "KEY", help = "Key to get")] pub key: String, - - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - long, - required = false, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("get key value pair {opts:?}"); - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; let hash = db.root_hash().await?; diff --git a/fwdctl/src/graph.rs b/fwdctl/src/graph.rs index 780fb31ba198..880b9ccfa5ec 100644 --- a/fwdctl/src/graph.rs +++ b/fwdctl/src/graph.rs @@ -6,22 +6,19 @@ use firewood::db::{Db, DbConfig}; use firewood::v2::api; use std::io::stdout; +use crate::DatabasePath; + #[derive(Debug, Args)] pub struct Options { - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, + #[command(flatten)] + pub database: DatabasePath, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {opts:?}"); - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; db.dump(&mut stdout()).await?; Ok(()) } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 9d302ddcfae9..3af26c9d6629 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -5,8 +5,13 @@ use clap::Args; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::v2::api::{self, Db as _, Proposal as _}; +use crate::DatabasePath; + #[derive(Debug, Args)] pub struct Options { + #[command(flatten)] + pub database: DatabasePath, + /// The key to insert #[arg(required = true, value_name = "KEY", help = "Key to insert")] pub key: String, @@ -14,23 +19,13 @@ pub struct Options { /// The value to insert #[arg(required = true, value_name = "VALUE", help = "Value to insert")] pub value: String, - - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - long, - required = false, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("inserting key value pair {opts:?}"); - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; let batch: Vec, Vec>> = vec![BatchOp::Put { key: opts.key.clone().into(), diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index ec52a3c0a801..2c3d3c611dfc 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -3,6 +3,8 @@ #![doc = include_str!("../README.md")] +use std::path::PathBuf; + use clap::{Parser, Subcommand}; use firewood::v2::api; @@ -15,6 +17,20 @@ pub mod graph; pub mod insert; pub mod root; +#[derive(Clone, Debug, Parser)] +pub struct DatabasePath { + /// The database path. Defaults to firewood.db + #[arg( + long = "db", + short = 'd', + required = false, + value_name = "DB_NAME", + default_value_os_t = default_db_path(), + help = "Name of the database" + )] + pub dbpath: PathBuf, +} + #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] @@ -75,3 +91,7 @@ async fn main() -> Result<(), api::Error> { Commands::Check(opts) => check::run(opts).await, } } + +fn default_db_path() -> PathBuf { + PathBuf::from("firewood.db") +} diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index afa6306c5235..8a0445fc6955 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -2,28 +2,22 @@ // See the file LICENSE.md for licensing terms. use clap::Args; -use std::str; use firewood::db::{Db, DbConfig}; use firewood::v2::api::{self, Db as _}; +use crate::DatabasePath; + #[derive(Debug, Args)] pub struct Options { - /// The database path (if no path is provided, return an error). Defaults to firewood. - #[arg( - long, - required = false, - value_name = "DB_NAME", - default_value_t = String::from("firewood"), - help = "Name of the database" - )] - pub db: String, + #[command(flatten)] + pub database: DatabasePath, } pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { - let cfg = DbConfig::builder().truncate(false); + let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.db.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; let hash = db.root_hash().await?; diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index dfbac18dda9e..5843c8acb320 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -6,7 +6,7 @@ use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; use std::fs::{self, remove_file}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -41,6 +41,7 @@ fn fwdctl_prints_version() -> Result<()> { fn fwdctl_creates_database() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -54,6 +55,7 @@ fn fwdctl_insert_successful() -> Result<()> { // Create db Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -63,8 +65,8 @@ fn fwdctl_insert_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("year")); @@ -78,7 +80,8 @@ fn fwdctl_get_successful() -> Result<()> { // Create db and insert data Command::cargo_bin(PRG)? .arg("create") - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success(); @@ -86,8 +89,8 @@ fn fwdctl_get_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("year")); @@ -96,8 +99,8 @@ fn fwdctl_get_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("get") .args(["year"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("2023")); @@ -110,6 +113,7 @@ fn fwdctl_get_successful() -> Result<()> { fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -118,8 +122,8 @@ fn fwdctl_delete_successful() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("year")); @@ -128,8 +132,8 @@ fn fwdctl_delete_successful() -> Result<()> { Command::cargo_bin(PRG)? .arg("delete") .args(["year"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("key year deleted successfully")); @@ -142,6 +146,7 @@ fn fwdctl_delete_successful() -> Result<()> { fn fwdctl_root_hash() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -150,8 +155,8 @@ fn fwdctl_root_hash() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("year")); @@ -159,8 +164,8 @@ fn fwdctl_root_hash() -> Result<()> { // Get root Command::cargo_bin(PRG)? .arg("root") - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::is_empty().not()); @@ -173,6 +178,7 @@ fn fwdctl_root_hash() -> Result<()> { fn fwdctl_dump() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -181,8 +187,8 @@ fn fwdctl_dump() -> Result<()> { .arg("insert") .args(["year"]) .args(["2023"]) - .args(["--db"]) - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("year")); @@ -190,7 +196,8 @@ fn fwdctl_dump() -> Result<()> { // Get root Command::cargo_bin(PRG)? .arg("dump") - .args([tmpdb::path()]) + .arg("--db") + .arg(tmpdb::path()) .assert() .success() .stdout(predicate::str::contains("2023")); @@ -203,36 +210,37 @@ fn fwdctl_dump() -> Result<()> { fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["a"]) .args(["1"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("a")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["b"]) .args(["2"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("b")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["c"]) .args(["3"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("c")); @@ -240,9 +248,10 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { // Test stop in the middle Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--stop-key"]) .arg("b") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains( @@ -252,9 +261,10 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { // Test stop in the end Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--stop-key"]) .arg("c") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains( @@ -264,9 +274,10 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { // Test start in the middle Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--start-key"]) .arg("b") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::starts_with("\'b")); @@ -274,11 +285,12 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { // Test start and stop Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--start-key"]) .arg("b") .args(["--stop-key"]) .arg("b") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::starts_with("\'b")) @@ -289,11 +301,12 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { // Test start and stop Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--start-key"]) .arg("b") .args(["--max-key-count"]) .arg("1") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::starts_with("\'b")) @@ -309,36 +322,37 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { fn fwdctl_dump_with_csv_and_json() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["a"]) .args(["1"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("a")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["b"]) .args(["2"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("b")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["c"]) .args(["3"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("c")); @@ -346,9 +360,10 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { // Test output csv Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--output-format"]) .arg("csv") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("Dumping to dump.csv")); @@ -360,9 +375,10 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { // Test output json Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--output-format"]) .arg("json") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("Dumping to dump.json")); @@ -382,16 +398,17 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { fn fwdctl_dump_with_file_name() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["a"]) .args(["1"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("a")); @@ -399,9 +416,10 @@ fn fwdctl_dump_with_file_name() -> Result<()> { // Test without output format Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--output-file-name"]) .arg("test") - .args([tmpdb::path()]) .assert() .failure() .stderr(predicate::str::contains("--output-format")); @@ -409,11 +427,12 @@ fn fwdctl_dump_with_file_name() -> Result<()> { // Test output csv Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--output-format"]) .arg("csv") .args(["--output-file-name"]) .arg("test") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("Dumping to test.csv")); @@ -425,11 +444,12 @@ fn fwdctl_dump_with_file_name() -> Result<()> { // Test output json Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--output-format"]) .arg("json") .args(["--output-file-name"]) .arg("test") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("Dumping to test.json")); @@ -446,36 +466,37 @@ fn fwdctl_dump_with_file_name() -> Result<()> { fn fwdctl_dump_with_hex() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["a"]) .args(["1"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("a")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["b"]) .args(["2"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("b")); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args(["c"]) .args(["3"]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::contains("c")); @@ -483,11 +504,12 @@ fn fwdctl_dump_with_hex() -> Result<()> { // Test without output format Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--start-key"]) .arg("a") .args(["--start-key-hex"]) .arg("61") - .args([tmpdb::path()]) .assert() .failure() .stderr(predicate::str::contains("--start-key")) @@ -496,9 +518,10 @@ fn fwdctl_dump_with_hex() -> Result<()> { // Test start with hex value Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--start-key-hex"]) .arg("62") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::starts_with("\'b")); @@ -506,9 +529,10 @@ fn fwdctl_dump_with_hex() -> Result<()> { // Test stop with hex value Command::cargo_bin(PRG)? .arg("dump") + .arg("--db") + .arg(tmpdb::path()) .args(["--stop-key-hex"]) .arg("62") - .args([tmpdb::path()]) .assert() .success() .stdout(predicate::str::starts_with("\'a")) @@ -524,6 +548,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { fn fwdctl_check_empty_db() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -559,6 +584,7 @@ fn fwdctl_check_db_with_data() -> Result<()> { Command::cargo_bin(PRG)? .arg("create") + .arg("--db") .arg(tmpdb::path()) .assert() .success(); @@ -569,10 +595,10 @@ fn fwdctl_check_db_with_data() -> Result<()> { let value = sample_iter.by_ref().take(10).collect::(); Command::cargo_bin(PRG)? .arg("insert") + .arg("--db") + .arg(tmpdb::path()) .args([key]) .args([value]) - .args(["--db"]) - .args([tmpdb::path()]) .assert() .success(); } @@ -597,16 +623,22 @@ fn fwdctl_check_db_with_data() -> Result<()> { // of this in different directories will have different databases mod tmpdb { + use std::sync::OnceLock; + use super::*; const FIREWOOD_TEST_DB_NAME: &str = "test_firewood"; const TARGET_TMP_DIR: Option<&str> = option_env!("CARGO_TARGET_TMPDIR"); - pub fn path() -> PathBuf { - TARGET_TMP_DIR - .map(PathBuf::from) - .or_else(|| std::env::var("TMPDIR").ok().map(PathBuf::from)) - .unwrap_or(std::env::temp_dir()) - .join(FIREWOOD_TEST_DB_NAME) + pub fn path() -> &'static Path { + static PATH: OnceLock = OnceLock::new(); + PATH.get_or_init(|| { + TARGET_TMP_DIR + .map(PathBuf::from) + .or_else(|| std::env::var("TMPDIR").ok().map(PathBuf::from)) + .unwrap_or(std::env::temp_dir()) + .join(FIREWOOD_TEST_DB_NAME) + }) + .as_path() } } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index cca18235db8f..bdbe46e9e634 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -91,13 +91,14 @@ impl FileBacked { node_cache_size: NonZero, free_list_cache_size: NonZero, truncate: bool, + create: bool, cache_read_strategy: CacheReadStrategy, ) -> Result { let fd = OpenOptions::new() .read(true) .write(true) .truncate(truncate) - .create(true) + .create(create) .open(&path) .map_err(|e| FileIoError { inner: e, @@ -331,6 +332,7 @@ mod test { nonzero!(10usize), nonzero!(10usize), false, + true, CacheReadStrategy::WritesOnly, ) .unwrap(); @@ -372,6 +374,7 @@ mod test { nonzero!(10usize), nonzero!(10usize), false, + true, CacheReadStrategy::WritesOnly, ) .unwrap(); diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 50502a0a1dd4..ba4de339633c 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -788,6 +788,7 @@ mod tests { nonzero!(10usize), nonzero!(10usize), false, + true, CacheReadStrategy::WritesOnly, ) .unwrap(), From 4ee6d4f282d0c702c39ee79010385c335c3dc43d Mon Sep 17 00:00:00 2001 From: Kushneryk Pavlo Date: Fri, 8 Aug 2025 21:51:45 +0200 Subject: [PATCH 0892/1053] chore: fwdctl cleanups (#1190) resolve https://github.com/ava-labs/firewood/issues/1163 --- fwdctl/src/dump.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 478d20df6925..f0eb047630e6 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -1,11 +1,5 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. - -#![expect( - clippy::doc_link_with_quotes, - reason = "Found 1 occurrences after enabling the lint." -)] - use clap::Args; use firewood::db::{Db, DbConfig}; use firewood::merkle::{Key, Value}; @@ -22,6 +16,14 @@ use crate::DatabasePath; type KeyFromStream = Option>; +#[derive(Debug, clap::ValueEnum, Clone, PartialEq)] +pub enum OutputFormat { + Csv, + Json, + Stdout, + Dot, +} + #[derive(Debug, Args)] pub struct Options { #[command(flatten)] @@ -87,18 +89,17 @@ pub struct Options { pub max_key_count: Option, /// The output format of database dump. - /// Possible Values: ["csv", "json", "stdout", "dot"]. /// Defaults to "stdout" #[arg( short = 'o', long, required = false, value_name = "OUTPUT_FORMAT", - value_parser = ["csv", "json", "stdout", "dot"], - default_value = "stdout", + value_enum, + default_value_t = OutputFormat::Stdout, help = "Output format of database dump, default to stdout. CSV, JSON, and DOT formats are available." )] - pub output_format: String, + pub output_format: OutputFormat, /// The output file name of database dump. /// Output format must be set when the file name is set. @@ -120,7 +121,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {opts:?}"); // Check if dot format is used with unsupported options - if opts.output_format == "dot" { + if opts.output_format == OutputFormat::Dot { if opts.start_key.is_some() || opts.start_key_hex.is_some() { return Err(api::Error::InternalError(Box::new(std::io::Error::new( std::io::ErrorKind::InvalidInput, @@ -311,9 +312,15 @@ fn create_output_handler( ) -> Result>, Box> { let hex = opts.hex; let mut file_name = opts.output_file_name.clone(); - file_name.set_extension(opts.output_format.as_str()); - match opts.output_format.as_str() { - "csv" => { + let extension = match opts.output_format { + OutputFormat::Csv => "csv", + OutputFormat::Json => "json", + OutputFormat::Stdout => "txt", + OutputFormat::Dot => "dot", + }; + file_name.set_extension(extension); + match opts.output_format { + OutputFormat::Csv => { println!("Dumping to {}", file_name.display()); let file = File::create(file_name)?; Ok(Some(Box::new(CsvOutputHandler { @@ -321,7 +328,7 @@ fn create_output_handler( hex, }))) } - "json" => { + OutputFormat::Json => { println!("Dumping to {}", file_name.display()); let file = File::create(file_name)?; Ok(Some(Box::new(JsonOutputHandler { @@ -330,8 +337,8 @@ fn create_output_handler( is_first: true, }))) } - "stdout" => Ok(Some(Box::new(StdoutOutputHandler { hex }))), - "dot" => { + OutputFormat::Stdout => Ok(Some(Box::new(StdoutOutputHandler { hex }))), + OutputFormat::Dot => { println!("Dumping to {}", file_name.display()); let file = File::create(file_name)?; let mut writer = BufWriter::new(file); @@ -339,6 +346,5 @@ fn create_output_handler( db.dump_sync(&mut writer)?; Ok(None) } - _ => unreachable!(), } } From 37e125c2937c6f4d5d2a4e0f6db85bb64c784c05 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 8 Aug 2025 13:47:02 -0700 Subject: [PATCH 0893/1053] test: ban `rand::rng()` and provide an env seeded alternative (#1192) This change standardizes and centralizes the use of a custom `SeededRng` random number generator throughout the tests, replacing direct usage of `rand`'s RNGs. It also introduces a `clippy.toml` configuration to enforce this usage. These changes collectively enforce best practices for deterministic testing and make debugging easier. --- .github/check-license-headers.yaml | 2 + benchmark/Cargo.toml | 1 + benchmark/src/zipf.rs | 6 +- clippy.toml | 11 ++ firewood/Cargo.toml | 8 +- firewood/benches/hashops.rs | 40 ++---- firewood/examples/insert.rs | 21 +-- firewood/src/db.rs | 27 +--- firewood/src/merkle/tests/ethhash.rs | 13 +- firewood/src/merkle/tests/mod.rs | 73 +++------- firewood/src/merkle/tests/unvalidated.rs | 92 ++++++++----- fwdctl/Cargo.toml | 1 + fwdctl/tests/cli.rs | 16 +-- storage/Cargo.toml | 2 + storage/src/checker/mod.rs | 7 +- storage/src/lib.rs | 25 +--- storage/src/nodestore/alloc.rs | 8 +- storage/src/test_utils.rs | 168 +++++++++++++++++++++++ 18 files changed, 315 insertions(+), 206 deletions(-) create mode 100644 clippy.toml create mode 100644 storage/src/test_utils.rs diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 33f9d83918a0..89ffccbe300e 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -22,6 +22,7 @@ "triehash/**", "CHANGELOG.md", "cliff.toml", + "clippy.toml", "**/tests/compile_*/**", "**/go.mod", "**/go.mod", @@ -48,6 +49,7 @@ "triehash/**", "CHANGELOG.md", "cliff.toml", + "clippy.toml", "**/tests/compile_*/**", ], } diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index a7c10efd6e6f..fb08eaf72a26 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -38,6 +38,7 @@ opentelemetry = "0.30.0" opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry_sdk = "0.30.0" pretty-duration = "0.1.1" +firewood-storage = { workspace = true, features = ["test_utils"] } [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 8dbe215ca4ec..6f3d9b5589f5 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -50,9 +50,11 @@ impl TestRunner for Zipf { let start = Instant::now(); let mut batch_id = 0; + let rng = firewood_storage::SeededRng::from_env_or_random(); while start.elapsed().as_secs() / 60 < args.global_opts.duration_minutes { let batch: Vec> = - generate_updates(batch_id, args.global_opts.batch_size as usize, zipf).collect(); + generate_updates(&rng, batch_id, args.global_opts.batch_size as usize, zipf) + .collect(); if log::log_enabled!(log::Level::Debug) { let mut distinct = HashSet::new(); for op in &batch { @@ -85,12 +87,12 @@ impl TestRunner for Zipf { } } fn generate_updates( + rng: &firewood_storage::SeededRng, batch_id: u32, batch_size: usize, zipf: rand_distr::Zipf, ) -> impl Iterator, Vec>> { let hash_of_batch_id = Sha256::digest(batch_id.to_ne_bytes()).to_vec(); - let rng = rand::rng(); zipf.sample_iter(rng) .take(batch_size) .map(|inner_key| { diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000000..b0467800ea92 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,11 @@ +# See https://doc.rust-lang.org/clippy/lint_configuration.html +# for full configuration options. + +disallowed-methods = [ + { path = "rand::rng", replacement = "firewood_storage::StdRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, +] + +disallowed-types = [ + { path = "rand::SeedableRng", replacement = "firewood_storage::StdRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, + { path = "rand::rngs::StdRng", replacement = "firewood_storage::StdRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, +] diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 02a3767f00c2..433bb4a33578 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -30,7 +30,6 @@ firewood-macros.workspace = true hex.workspace = true metrics.workspace = true sha2.workspace = true -test-case.workspace = true thiserror.workspace = true tokio.workspace = true # Regular dependencies @@ -43,8 +42,8 @@ default = [] nightly = [] io-uring = ["firewood-storage/io-uring"] logger = ["firewood-storage/logger"] -branch_factor_256 = [ "firewood-storage/branch_factor_256" ] -ethhash = [ "firewood-storage/ethhash" ] +branch_factor_256 = ["firewood-storage/branch_factor_256"] +ethhash = ["firewood-storage/ethhash"] [dev-dependencies] # Workspace dependencies @@ -52,12 +51,13 @@ clap = { workspace = true, features = ['derive'] } criterion = { workspace = true, features = ["async_tokio"] } env_logger.workspace = true ethereum-types.workspace = true +firewood-storage = { workspace = true, features = ["test_utils"] } firewood-triehash.workspace = true hex-literal.workspace = true pprof = { workspace = true, features = ["flamegraph"] } rand.workspace = true -rand_distr.workspace = true tempfile.workspace = true +test-case.workspace = true tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } # Regular dependencies hash-db = "0.16.0" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index e835615d5859..0cebfff7b8d2 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -10,9 +10,7 @@ use firewood::merkle::Merkle; use firewood::v2::api::{Db as _, Proposal as _}; use firewood_storage::{MemStore, NodeStore}; use pprof::ProfilerGuard; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; -use rand_distr::Alphanumeric; +use rand::{Rng, distr::Alphanumeric}; use std::fs::File; use std::iter::repeat_with; use std::os::raw::c_int; @@ -62,7 +60,7 @@ impl Profiler for FlamegraphProfiler { // This benchmark peeks into the merkle layer and times how long it takes // to insert NKEYS with a key length of KEYSIZE fn bench_merkle(criterion: &mut Criterion) { - let mut rng = StdRng::seed_from_u64(1234); + let rng = &firewood_storage::SeededRng::from_option(Some(1234)); criterion .benchmark_group("Merkle") @@ -74,14 +72,10 @@ fn bench_merkle(criterion: &mut Criter let nodestore = NodeStore::new_empty_proposal(store); let merkle = Merkle::from(nodestore); - let keys: Vec> = repeat_with(|| { - (&mut rng) - .sample_iter(&Alphanumeric) - .take(KEYSIZE) - .collect() - }) - .take(NKEYS) - .collect(); + let keys: Vec> = + repeat_with(|| rng.sample_iter(&Alphanumeric).take(KEYSIZE).collect()) + .take(NKEYS) + .collect(); (merkle, keys) }, @@ -100,7 +94,7 @@ fn bench_merkle(criterion: &mut Criter #[expect(clippy::unwrap_used)] fn bench_db(criterion: &mut Criterion) { const KEY_LEN: usize = 4; - let mut rng = StdRng::seed_from_u64(1234); + let rng = &firewood_storage::SeededRng::from_option(Some(1234)); criterion .benchmark_group("Db") @@ -109,18 +103,14 @@ fn bench_db(criterion: &mut Criterion) { b.to_async(tokio::runtime::Runtime::new().unwrap()) .iter_batched( || { - let batch_ops: Vec<_> = repeat_with(|| { - (&mut rng) - .sample_iter(&Alphanumeric) - .take(KEY_LEN) - .collect() - }) - .map(|key: Vec<_>| BatchOp::Put { - key, - value: vec![b'v'], - }) - .take(N) - .collect(); + let batch_ops: Vec<_> = + repeat_with(|| rng.sample_iter(&Alphanumeric).take(KEY_LEN).collect()) + .map(|key: Vec<_>| BatchOp::Put { + key, + value: vec![b'v'], + }) + .take(N) + .collect(); batch_ops }, |batch_ops| async { diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index e76f1bffd636..a780f6bceae8 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -5,7 +5,6 @@ // insert some random keys using the front-end API. use clap::Parser; -use std::borrow::BorrowMut as _; use std::collections::HashMap; use std::error::Error; use std::num::NonZeroUsize; @@ -15,8 +14,7 @@ use std::time::Instant; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::RevisionManagerConfig; use firewood::v2::api::{Db as _, DbView, KeyType, Proposal as _, ValueType}; -use rand::{Rng, SeedableRng as _}; -use rand_distr::Alphanumeric; +use rand::{Rng, distr::Alphanumeric}; #[derive(Parser, Debug)] struct Args { @@ -72,11 +70,7 @@ async fn main() -> Result<(), Box> { let keys = args.batch_size; let start = Instant::now(); - let mut rng = if let Some(seed) = args.seed { - rand::rngs::StdRng::seed_from_u64(seed) - } else { - rand::rngs::StdRng::from_os_rng() - }; + let rng = &firewood_storage::SeededRng::from_option(args.seed); for _ in 0..args.number_of_batches { let keylen = rng.random_range(args.keylen.clone()); @@ -84,12 +78,10 @@ async fn main() -> Result<(), Box> { let batch = (0..keys) .map(|_| { ( - rng.borrow_mut() - .sample_iter(&Alphanumeric) + rng.sample_iter(&Alphanumeric) .take(keylen) .collect::>(), - rng.borrow_mut() - .sample_iter(&Alphanumeric) + rng.sample_iter(&Alphanumeric) .take(valuelen) .collect::>(), ) @@ -97,7 +89,7 @@ async fn main() -> Result<(), Box> { .map(|(key, value)| BatchOp::Put { key, value }) .collect::>(); - let verify = get_keys_to_verify(&batch, args.read_verify_percent); + let verify = get_keys_to_verify(rng, &batch, args.read_verify_percent); #[expect(clippy::unwrap_used)] let proposal = db.propose(batch.clone()).await.unwrap(); @@ -115,6 +107,7 @@ async fn main() -> Result<(), Box> { } fn get_keys_to_verify<'a, K: KeyType + 'a, V: ValueType + 'a>( + rng: &firewood_storage::SeededRng, batch: impl IntoIterator>, pct: u16, ) -> HashMap<&'a [u8], &'a [u8]> { @@ -123,7 +116,7 @@ fn get_keys_to_verify<'a, K: KeyType + 'a, V: ValueType + 'a>( } else { batch .into_iter() - .filter(|_last_key| rand::rng().random_range(0..=100u16.saturating_sub(pct)) == 0) + .filter(|_last_key| rng.random_range(0..=100u16.saturating_sub(pct)) == 0) .map(|op| { if let BatchOp::Put { key, value } = op { (key.as_ref(), value.as_ref()) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 42b990e632a6..fabb7c7cf8ae 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -467,7 +467,6 @@ mod test { use std::path::PathBuf; use firewood_storage::{CheckOpt, CheckerError}; - use rand::rng; use tokio::sync::mpsc::{Receiver, Sender}; use crate::db::{Db, Proposal}; @@ -776,21 +775,9 @@ mod test { #[tokio::test] async fn fuzz_checker() { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - let _ = env_logger::Builder::new().is_test(true).try_init(); - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); + let rng = firewood_storage::SeededRng::from_env_or_random(); let db = testdb().await; @@ -798,16 +785,16 @@ mod test { for _ in 0..50 { // create a batch of 10 random key-value pairs let batch = (0..10).fold(vec![], |mut batch, _| { - let key: [u8; 32] = rng.borrow_mut().random(); - let value: [u8; 8] = rng.borrow_mut().random(); + let key: [u8; 32] = rng.random(); + let value: [u8; 8] = rng.random(); batch.push(BatchOp::Put { key: key.to_vec(), value, }); - if rng.borrow_mut().random_range(0..5) == 0 { - let addon: [u8; 32] = rng.borrow_mut().random(); + if rng.random_range(0..5) == 0 { + let addon: [u8; 32] = rng.random(); let key = [key, addon].concat(); - let value: [u8; 8] = rng.borrow_mut().random(); + let value: [u8; 8] = rng.random(); batch.push(BatchOp::Put { key, value }); } batch @@ -816,7 +803,7 @@ mod test { proposal.commit().await.unwrap(); // check the database for consistency, sometimes checking the hashes - let hash_check = rng.borrow_mut().random(); + let hash_check = rng.random(); let report = db .check(CheckOpt { hash_check, diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index e5c8a07fff7b..e8bdd401b270 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -124,23 +124,20 @@ fn make_key(hex_str: &str) -> Key { #[test] #[ignore = "broken tests not yet validated"] fn test_root_hash_random_deletions() { - use rand::rngs::StdRng; use rand::seq::SliceRandom; - use rand::{Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let rng = firewood_storage::SeededRng::from_option(Some(42)); let max_len0 = 8; let max_len1 = 4; let keygen = || { let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); ( rng.random_range(1..=max_len0), rng.random_range(1..=max_len1), ) }; (0..len0) - .map(|_| rng.borrow_mut().random_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().random())) + .map(|_| rng.random_range(0..2)) + .chain((0..len1).map(|_| rng.random())) .collect() }; @@ -148,13 +145,13 @@ fn test_root_hash_random_deletions() { let mut items = std::collections::HashMap::::new(); for _ in 0..10 { - let val = (0..8).map(|_| rng.borrow_mut().random()).collect(); + let val = (0..8).map(|_| rng.random()).collect(); items.insert(keygen(), val); } let mut items_ordered: Vec<_> = items.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); items_ordered.sort_unstable(); - items_ordered.shuffle(&mut *rng.borrow_mut()); + items_ordered.shuffle(&mut &rng); let mut committed_merkle = init_merkle(&items); diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index 21fe7ce30631..20825231d884 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -15,15 +15,9 @@ use std::fmt::Write; use super::*; use firewood_storage::{Committed, MemStore, MutableProposal, NodeStore, RootReader, TrieHash}; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng, rng}; // Returns n random key-value pairs. -fn generate_random_kvs(seed: u64, n: usize) -> Vec<(Vec, Vec)> { - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - - let mut rng = StdRng::seed_from_u64(seed); - +fn generate_random_kvs(rng: &firewood_storage::SeededRng, n: usize) -> Vec<(Vec, Vec)> { let mut kvs: Vec<(Vec, Vec)> = Vec::new(); for _ in 0..n { let key_len = rng.random_range(1..=4096); @@ -116,7 +110,10 @@ where // generate pseudorandom data, but prefix it with some known data // The number of fixed data points is 100; you specify how much random data you want #[expect(clippy::arithmetic_side_effects)] -fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> { +fn fixed_and_pseudorandom_data( + rng: &firewood_storage::SeededRng, + random_count: u32, +) -> HashMap<[u8; 32], [u8; 20]> { let mut items = HashMap::new(); for i in 0..100_u32 { let mut key: [u8; 32] = [0; 32]; @@ -134,23 +131,9 @@ fn fixed_and_pseudorandom_data(random_count: u32) -> HashMap<[u8; 32], [u8; 20]> items.insert(more_key, value); } - // read FIREWOOD_TEST_SEED from the environment. If it's there, parse it into a u64. - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - // the test framework will only render this in verbose mode or if the test fails - // to re-run the test when it fails, just specify the seed instead of randomly - // selecting one - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let mut r = StdRng::seed_from_u64(seed); for _ in 0..random_count { - let key = r.random::<[u8; 32]>(); - let val = r.random::<[u8; 20]>(); + let key = rng.random::<[u8; 32]>(); + let val = rng.random::<[u8; 20]>(); items.insert(key, val); } items @@ -424,15 +407,9 @@ fn single_key_proof() { let mut merkle = create_in_memory_merkle(); - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); + let rng = firewood_storage::SeededRng::from_env_or_random(); - let kvs = generate_random_kvs(seed, TEST_SIZE); + let kvs = generate_random_kvs(&rng, TEST_SIZE); for (key, val) in &kvs { merkle.insert(key, val.clone().into_boxed_slice()).unwrap(); @@ -641,22 +618,19 @@ fn test_root_hash_simple_insertions() -> Result<(), Error> { #[test] fn test_root_hash_fuzz_insertions() -> Result<(), FileIoError> { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(42)); + let rng = firewood_storage::SeededRng::from_option(Some(42)); let max_len0 = 8; let max_len1 = 4; let keygen = || { let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); ( rng.random_range(1..=max_len0), rng.random_range(1..=max_len1), ) }; let key: Vec = (0..len0) - .map(|_| rng.borrow_mut().random_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().random())) + .map(|_| rng.random_range(0..2)) + .chain((0..len1).map(|_| rng.random())) .collect(); key }; @@ -666,7 +640,7 @@ fn test_root_hash_fuzz_insertions() -> Result<(), FileIoError> { let mut items = Vec::new(); for _ in 0..100 { - let val: Vec = (0..256).map(|_| rng.borrow_mut().random()).collect(); + let val: Vec = (0..256).map(|_| rng.random()).collect(); items.push((keygen(), val)); } @@ -709,35 +683,22 @@ fn test_delete_some() { #[test] fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - let _ = env_logger::Builder::new().is_test(true).try_init(); - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let rng = std::cell::RefCell::new(StdRng::seed_from_u64(seed)); + let rng = firewood_storage::SeededRng::from_env_or_random(); let max_len0 = 8; let max_len1 = 4; let keygen = || { let (len0, len1): (usize, usize) = { - let mut rng = rng.borrow_mut(); ( rng.random_range(1..=max_len0), rng.random_range(1..=max_len1), ) }; (0..len0) - .map(|_| rng.borrow_mut().random_range(0..2)) - .chain((0..len1).map(|_| rng.borrow_mut().random())) + .map(|_| rng.random_range(0..2)) + .chain((0..len1).map(|_| rng.random())) .collect() }; @@ -745,7 +706,7 @@ fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { let mut items: Vec<(Key, Value)> = (0..10) .map(|_| keygen()) .map(|key| { - let val = (0..8).map(|_| rng.borrow_mut().random()).collect(); + let val = (0..8).map(|_| rng.random()).collect(); (key, val) }) .collect(); diff --git a/firewood/src/merkle/tests/unvalidated.rs b/firewood/src/merkle/tests/unvalidated.rs index fcb2ad46aa8a..9b3c1111be5d 100644 --- a/firewood/src/merkle/tests/unvalidated.rs +++ b/firewood/src/merkle/tests/unvalidated.rs @@ -160,7 +160,8 @@ fn two_key_proof_without_shared_path() { #[test] #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_proof() { - let set = fixed_and_pseudorandom_data(500); + let rng = firewood_storage::SeededRng::from_env_or_random(); + let set = fixed_and_pseudorandom_data(&rng, 500); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -220,7 +221,8 @@ fn test_proof_end_with_branch() { #[test] #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_bad_proof() { - let set = fixed_and_pseudorandom_data(800); + let rng = firewood_storage::SeededRng::from_env_or_random(); + let set = fixed_and_pseudorandom_data(&rng, 800); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -273,14 +275,16 @@ fn test_empty_tree_proof() { // The test cases are generated randomly. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_range_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); for _ in 0..10 { - let start = rand::rng().random_range(0..items.len()); - let end = rand::rng().random_range(0..items.len() - start) + start - 1; + let start = rng.random_range(0..items.len()); + let end = rng.random_range(0..items.len() - start) + start - 1; if end <= start { continue; @@ -314,14 +318,16 @@ fn test_range_proof() { // The prover is expected to detect the error. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_bad_range_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); for _ in 0..10 { - let start = rand::rng().random_range(0..items.len()); - let end = rand::rng().random_range(0..items.len() - start) + start - 1; + let start = rng.random_range(0..items.len()); + let end = rng.random_range(0..items.len() - start) + start - 1; if end <= start { continue; @@ -339,16 +345,16 @@ fn test_bad_range_proof() { vals.push(*item.1); } - let test_case: u32 = rand::rng().random_range(0..6); - let index = rand::rng().random_range(0..end - start); + let test_case: u32 = rng.random_range(0..6); + let index = rng.random_range(0..end - start); match test_case { 0 => { // Modified key - keys[index] = rand::rng().random::<[u8; 32]>(); // In theory it can't be same + keys[index] = rng.random::<[u8; 32]>(); // In theory it can't be same } 1 => { // Modified val - vals[index] = rand::rng().random::<[u8; 20]>(); // In theory it can't be same + vals[index] = rng.random::<[u8; 20]>(); // In theory it can't be same } 2 => { // Gapped entry slice @@ -360,8 +366,8 @@ fn test_bad_range_proof() { } 3 => { // Out of order - let index_1 = rand::rng().random_range(0..end - start); - let index_2 = rand::rng().random_range(0..end - start); + let index_1 = rng.random_range(0..end - start); + let index_2 = rng.random_range(0..end - start); if index_1 == index_2 { continue; } @@ -410,14 +416,16 @@ fn test_bad_range_proof() { // The test cases are generated randomly. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_range_proof_with_non_existent_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); for _ in 0..10 { - let start = rand::rng().random_range(0..items.len()); - let end = rand::rng().random_range(0..items.len() - start) + start - 1; + let start = rng.random_range(0..items.len()); + let end = rng.random_range(0..items.len() - start) + start - 1; if end <= start { continue; @@ -486,7 +494,9 @@ fn test_range_proof_with_non_existent_proof() { // - There exists a gap between the last element and the right edge proof #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_range_proof_with_invalid_non_existent_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -556,7 +566,9 @@ fn test_range_proof_with_invalid_non_existent_proof() { // non-existent one. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_one_element_range_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -650,8 +662,8 @@ fn test_one_element_range_proof() { .unwrap(); // Test the mini trie with only a single element. - let key = rand::rng().random::<[u8; 32]>(); - let val = rand::rng().random::<[u8; 20]>(); + let key = rng.random::<[u8; 32]>(); + let val = rng.random::<[u8; 20]>(); let merkle_mini = init_merkle(vec![(key, val)]); let first = &[0; 32]; @@ -678,7 +690,9 @@ fn test_one_element_range_proof() { // The edge proofs can be nil. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_all_elements_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -754,7 +768,9 @@ fn test_all_elements_proof() { // be a non-existent proof. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_empty_range_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -840,7 +856,9 @@ fn test_gapped_range_proof() { // Tests the element is not in the range covered by proofs. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_same_side_proof() { - let set = fixed_and_pseudorandom_data(4096); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 4096); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -894,11 +912,13 @@ fn test_same_side_proof() { // Tests the range starts from zero. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_single_side_range_proof() { + let rng = firewood_storage::SeededRng::from_env_or_random(); + for _ in 0..10 { let mut set = HashMap::new(); for _ in 0..4096_u32 { - let key = rand::rng().random::<[u8; 32]>(); - let val = rand::rng().random::<[u8; 20]>(); + let key = rng.random::<[u8; 32]>(); + let val = rng.random::<[u8; 20]>(); set.insert(key, val); } let mut items = set.iter().collect::>(); @@ -933,11 +953,13 @@ fn test_single_side_range_proof() { // Tests the range ends with 0xffff...fff. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_reverse_single_side_range_proof() { + let rng = firewood_storage::SeededRng::from_env_or_random(); + for _ in 0..10 { let mut set = HashMap::new(); for _ in 0..1024_u32 { - let key = rand::rng().random::<[u8; 32]>(); - let val = rand::rng().random::<[u8; 20]>(); + let key = rng.random::<[u8; 32]>(); + let val = rng.random::<[u8; 20]>(); set.insert(key, val); } let mut items = set.iter().collect::>(); @@ -973,11 +995,13 @@ fn test_reverse_single_side_range_proof() { // Tests the range starts with zero and ends with 0xffff...fff. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_both_sides_range_proof() { + let rng = firewood_storage::SeededRng::from_env_or_random(); + for _ in 0..10 { let mut set = HashMap::new(); for _ in 0..4096_u32 { - let key = rand::rng().random::<[u8; 32]>(); - let val = rand::rng().random::<[u8; 20]>(); + let key = rng.random::<[u8; 32]>(); + let val = rng.random::<[u8; 20]>(); set.insert(key, val); } let mut items = set.iter().collect::>(); @@ -1010,7 +1034,9 @@ fn test_both_sides_range_proof() { // noop technically, but practically should be rejected. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_empty_value_range_proof() { - let set = fixed_and_pseudorandom_data(512); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 512); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); @@ -1056,7 +1082,9 @@ fn test_empty_value_range_proof() { // practically should be rejected. #[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_all_elements_empty_value_range_proof() { - let set = fixed_and_pseudorandom_data(512); + let rng = firewood_storage::SeededRng::from_env_or_random(); + + let set = fixed_and_pseudorandom_data(&rng, 512); let mut items = set.iter().collect::>(); items.sort_unstable(); let merkle = init_merkle(items.clone()); diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 295448592cd8..70ff91ae682b 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -43,6 +43,7 @@ logger = ["firewood/logger"] [dev-dependencies] # Workspace dependencies +firewood-storage = { workspace = true, features = ["test_utils"] } rand.workspace = true # Regular dependencies anyhow = "1.0.98" diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index 5843c8acb320..f02f55a93d39 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -566,20 +566,8 @@ fn fwdctl_check_empty_db() -> Result<()> { #[test] #[serial] fn fwdctl_check_db_with_data() -> Result<()> { - use rand::distr::Alphanumeric; - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng, rng}; - - let seed = std::env::var("FIREWOOD_TEST_SEED") - .ok() - .map_or_else( - || None, - |s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")), - ) - .unwrap_or_else(|| rng().random()); - - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}"); - let rng = StdRng::seed_from_u64(seed); + use rand::{Rng, distr::Alphanumeric}; + let rng = firewood_storage::SeededRng::from_env_or_random(); let mut sample_iter = rng.sample_iter(Alphanumeric).map(char::from); Command::cargo_bin(PRG)? diff --git a/storage/Cargo.toml b/storage/Cargo.toml index cd26a620b51e..2260ce11e0cd 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -26,6 +26,7 @@ metrics.workspace = true nonzero_ext.workspace = true sha2.workspace = true smallvec = { workspace = true, features = ["write", "union"] } +rand = { workspace = true, optional = true } thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" @@ -60,6 +61,7 @@ logger = ["log"] branch_factor_256 = [] io-uring = ["dep:io-uring"] ethhash = ["dep:rlp", "dep:sha3", "dep:bytes"] +test_utils = ["dep:rand"] [[bench]] name = "serializer" diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index b45fb807d5a3..bbdb7909cf2d 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -813,9 +813,7 @@ mod test { #[test] fn traverse_correct_freelist() { - use rand::Rng; - - let mut rng = crate::test_utils::seeded_rng(); + let rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); @@ -952,10 +950,9 @@ mod test { // This test creates a linear set of free areas and free them. // When traversing it should break consecutive areas. fn split_correct_range_into_leaked_areas() { - use rand::Rng; use rand::seq::IteratorRandom; - let mut rng = crate::test_utils::seeded_rng(); + let mut rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0072d735161f..7764fe6971d8 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -29,6 +29,8 @@ mod iter; mod linear; mod node; mod nodestore; +#[cfg(any(test, feature = "test_utils"))] +mod test_utils; mod trie_hash; use crate::nodestore::AreaIndex; @@ -56,6 +58,8 @@ pub use nodestore::{ pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; pub use node::persist::MaybePersistedNode; +#[cfg(any(test, feature = "test_utils"))] +pub use test_utils::SeededRng; pub use trie_hash::{InvalidTrieHashLength, TrieHash}; /// A shared node, which is just a triophe Arc of a node @@ -291,24 +295,3 @@ impl From for Vec { vec![error] } } - -#[cfg(test)] -mod test_utils { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng, rng}; - - pub fn seeded_rng() -> StdRng { - let seed = std::env::var("FIREWOOD_STORAGE_TEST_SEED") - .ok() - .map_or_else( - || rng().random(), - |s| { - str::parse(&s) - .expect("couldn't parse FIREWOOD_STORAGE_TEST_SEED; must be a u64") - }, - ); - - eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_STORAGE_TEST_SEED={seed}"); - StdRng::seed_from_u64(seed) - } -} diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 2ec4dbca19be..0016ce7ad71d 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -897,8 +897,6 @@ pub mod test_utils { mod tests { use super::*; use crate::linear::memory::MemStore; - use crate::test_utils::seeded_rng; - use rand::Rng; use rand::seq::IteratorRandom; use test_case::test_case; @@ -922,7 +920,7 @@ mod tests { #[test] // Create a random free list and test that `FreeListIterator` is able to traverse all the free areas fn free_list_iterator() { - let mut rng = seeded_rng(); + let mut rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); @@ -969,8 +967,8 @@ mod tests { // Create two free lists and check that `free_list_iter_with_metadata` correctly returns the free areas and their parents #[test] - fn free_lists_iter_with_metadata() { - let mut rng = seeded_rng(); + fn free_list_iter_with_metadata() { + let rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); diff --git a/storage/src/test_utils.rs b/storage/src/test_utils.rs new file mode 100644 index 000000000000..c9baefb050dd --- /dev/null +++ b/storage/src/test_utils.rs @@ -0,0 +1,168 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::cell::RefCell; +use std::rc::Rc; + +#[expect( + clippy::disallowed_types, + reason = "we are implementing the alternative" +)] +use rand::rngs::StdRng; +#[expect( + clippy::disallowed_types, + reason = "we are implementing the alternative" +)] +use rand::{RngCore, SeedableRng, TryRngCore}; + +#[derive(Debug, Clone)] +#[must_use] +/// A seeded random number generator for testing purposes. +#[expect( + clippy::disallowed_types, + reason = "we are implementing the alternative" +)] +pub struct SeededRng(Rc>); + +const ENV: &str = "FIREWOOD_TEST_SEED"; +// `concat!` does not work with constants, only literals and certain special expressions, +const EXPECT: &str = "FIREWOOD_TEST_SEED, when present, must contain a valid u64 value"; + +impl SeededRng { + /// Creates a new `SeededRng` with the given seed. + pub fn new(seed: u64) -> Self { + #![expect( + clippy::disallowed_types, + reason = "we are implementing the alternative" + )] + // we do not log here so that we don't log if `from_option` was used with + // a `Some` value (the seed did not come from the environment) + Self(Rc::new(RefCell::new(StdRng::seed_from_u64(seed)))) + } + + /// Creates a new `SeededRng` from an `Option`. + /// + /// If the provided value is `None`, [`SeededRng::from_env_or_random`] will + /// be used to check the environment variable or otherwise generate a random seed. + pub fn from_option(seed: Option) -> Self { + seed.map_or_else(Self::from_env_or_random, Self::new) + } + + /// Creates a new `SeededRng` from an environment variable set seed. + /// + /// # Returns + /// + /// None if the environment variable is not set, otherwise a [`SeededRng`] + /// with the seed initialized. + /// + /// # Panics + /// + /// Panics if the environment variable is present but is not a valid `u64`. + #[track_caller] + #[must_use] + pub fn from_env() -> Option { + let seed = std::env::var_os(ENV)? + .into_string() + .ok() + .and_then(|s| s.parse().ok()) + .expect(EXPECT); + eprintln!("Seed {seed}: to rerun with this data, export {ENV}={seed}"); + Some(Self::new(seed)) + } + + /// Creates a new `SeededRng` with a random seed generated by the OS. + #[track_caller] + pub fn from_random() -> Self { + let seed = rand::rngs::OsRng.unwrap_err().next_u64(); + eprintln!("Seed {seed}: to rerun with this data, export {ENV}={seed}"); + Self::new(seed) + } + + /// Creates a new `SeededRng` from an environment variable (if set), otherwise + /// a random seed. + #[track_caller] + pub fn from_env_or_random() -> Self { + Self::from_env().unwrap_or_else(Self::from_random) + } + + /// Creates a new `SeededRng` seeded by this Rng. + pub fn seeded_fork(&self) -> Self { + Self::new(self.next_u64()) + } + + /// Convenience method to generate a new u32 from the Rng with a reference + /// and not a mutable reference. + #[must_use] + pub fn next_u32(&self) -> u32 { + self.0.borrow_mut().next_u32() + } + + /// Convenience method to generate a new u64 from the Rng with a reference + /// and not a mutable reference. + #[must_use] + pub fn next_u64(&self) -> u64 { + self.0.borrow_mut().next_u64() + } + + /// Convenience method to fill a byte slice with random bytes from the Rng + /// with a reference and not a mutable reference. + pub fn fill_bytes(&self, dst: &mut [u8]) { + self.0.borrow_mut().fill_bytes(dst); + } + + /// Convenience method for [`rand::Rng::random`] but with a reference and + /// instead of a mutable reference. + #[must_use] + #[inline] + pub fn random(&self) -> T + where + rand::distr::StandardUniform: rand::distr::Distribution, + { + rand::Rng::random(&mut &*self) + } + + /// Convenience method for [`rand::Rng::random_range`] but with a reference + /// and instead of a mutable reference. + #[track_caller] + pub fn random_range(&self, range: R) -> T + where + T: rand::distr::uniform::SampleUniform, + R: rand::distr::uniform::SampleRange, + { + rand::Rng::random_range(&mut &*self, range) + } +} + +impl rand::RngCore for SeededRng { + #[inline] + fn next_u32(&mut self) -> u32 { + SeededRng::next_u32(self) + } + + #[inline] + fn next_u64(&mut self) -> u64 { + SeededRng::next_u64(self) + } + + #[inline] + fn fill_bytes(&mut self, dst: &mut [u8]) { + SeededRng::fill_bytes(self, dst); + } +} + +impl rand::RngCore for &SeededRng { + #[inline] + fn next_u32(&mut self) -> u32 { + SeededRng::next_u32(self) + } + + #[inline] + fn next_u64(&mut self) -> u64 { + SeededRng::next_u64(self) + } + + #[inline] + fn fill_bytes(&mut self, dst: &mut [u8]) { + SeededRng::fill_bytes(self, dst); + } +} From eb5ff818b36f0479d96e2b632d73660ca513d5f5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 8 Aug 2025 15:19:06 -0700 Subject: [PATCH 0894/1053] chore: AreaIndex newtype (#1193) Co-authored-by: qusuyan --- storage/benches/serializer.rs | 2 +- storage/src/checker/mod.rs | 116 +++--- storage/src/checker/range_set.rs | 4 +- storage/src/lib.rs | 8 +- storage/src/macros.rs | 21 ++ storage/src/node/mod.rs | 10 +- storage/src/nodestore/alloc.rs | 537 ++++++++-------------------- storage/src/nodestore/header.rs | 3 +- storage/src/nodestore/mod.rs | 80 +++-- storage/src/nodestore/persist.rs | 9 +- storage/src/nodestore/primitives.rs | 355 ++++++++++++++++++ 11 files changed, 660 insertions(+), 485 deletions(-) create mode 100644 storage/src/nodestore/primitives.rs diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 7a0a881b49a3..77eb6372fc18 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -76,7 +76,7 @@ fn manual_deserializer(b: &mut Bencher, input: &Vec) { fn to_bytes(input: &Node) -> Vec { let mut bytes = Vec::new(); - input.as_bytes(0, &mut bytes); + input.as_bytes(firewood_storage::AreaIndex::MIN, &mut bytes); bytes } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index bbdb7909cf2d..55ddfc9fa93e 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -5,9 +5,8 @@ mod range_set; pub(crate) use range_set::LinearAddressRangeSet; use crate::logger::warn; -use crate::nodestore::alloc::{ - AREA_SIZES, AreaIndex, FreeAreaWithMetadata, area_size_to_index, size_from_area_index, -}; +use crate::nodestore::alloc::FreeAreaWithMetadata; +use crate::nodestore::primitives::{AreaIndex, area_size_iter}; use crate::{ CheckerError, Committed, HashType, HashedNodeReader, IntoHashType, LinearAddress, Node, NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, @@ -332,7 +331,7 @@ impl NodeStore { // collect the trie bytes trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); // collect low occupancy area count, add 1 for the area size index byte - let smallest_area_index = area_size_to_index(node_bytes.saturating_add(1)) + let smallest_area_index = AreaIndex::from_size(node_bytes.saturating_add(1)) .expect("impossible since we checked that node_bytes < area_size"); if smallest_area_index < area_index { trie_stats.low_occupancy_area_count = @@ -420,7 +419,7 @@ impl NodeStore { let mut multi_page_area_count = 0u64; let mut errors = Vec::new(); - let mut free_list_iter = self.free_list_iter(0); + let mut free_list_iter = self.free_list_iter(AreaIndex::MIN); while let Some((free_area, parent)) = free_list_iter.next_with_metadata() { let FreeAreaWithMetadata { addr, @@ -446,7 +445,7 @@ impl NodeStore { } // check that the area size matches the free list id (if it is in the correct free list) - let area_size = size_from_area_index(area_index); + let area_size = area_index.size(); if free_list_id != area_index { errors.push(CheckerError::FreelistAreaSizeMismatch { address: addr, @@ -567,15 +566,15 @@ impl NodeStore { // We encountered an error, split the rest of the leaked range into areas using heuristics // The heuristic is to split the leaked range into areas of the largest size possible - we assume `AREA_SIZE` is in ascending order - for (area_index, area_size) in AREA_SIZES.iter().enumerate().rev() { + for (area_index, area_size) in area_size_iter().rev() { loop { let next_addr = current_addr - .advance(*area_size) + .advance(area_size) .expect("address overflow is impossible"); if next_addr <= *leaked_range.end { - leaked.push((current_addr, area_index as AreaIndex)); + leaked.push((current_addr, area_index)); if let Some(progress_bar) = progress_bar { - progress_bar.inc(*area_size); + progress_bar.inc(area_size); } current_addr = next_addr; } else { @@ -611,11 +610,14 @@ mod test { use super::*; use crate::linear::memory::MemStore; use crate::nodestore::NodeStoreHeader; + use crate::nodestore::alloc::FreeLists; use crate::nodestore::alloc::test_utils::{ test_write_free_area, test_write_header, test_write_new_node, test_write_zeroed_area, }; - use crate::nodestore::alloc::{AREA_SIZES, FreeLists}; - use crate::{BranchNode, Child, FreeListParent, LeafNode, NodeStore, Path, hash_node}; + use crate::nodestore::primitives::area_size_iter; + use crate::{ + BranchNode, Child, FreeListParent, LeafNode, NodeStore, Path, area_index, hash_node, + }; #[derive(Debug)] struct TestTrie { @@ -823,26 +825,21 @@ mod test { let mut free_area_counts: HashMap = HashMap::new(); let mut multi_page_area_count = 0u64; let mut freelist = FreeLists::default(); - for (area_index, area_size) in AREA_SIZES.iter().enumerate() { + for (area_index, area_size) in area_size_iter() { let mut next_free_block = None; let num_free_areas = rng.random_range(0..4); for _ in 0..num_free_areas { - test_write_free_area( - &nodestore, - next_free_block, - area_index as u8, - high_watermark, - ); + test_write_free_area(&nodestore, next_free_block, area_index, high_watermark); next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); let start_addr = LinearAddress::new(high_watermark).unwrap(); - if extra_read_pages(start_addr, *area_size).unwrap() > 0 { + if extra_read_pages(start_addr, area_size).unwrap() > 0 { multi_page_area_count = multi_page_area_count.saturating_add(1); } high_watermark += area_size; } - freelist[area_index] = next_free_block; + freelist[area_index.as_usize()] = next_free_block; if num_free_areas > 0 { - free_area_counts.insert(*area_size, num_free_areas); + free_area_counts.insert(area_size, num_free_areas); } } let expected_free_lists_stats = FreeListsStats { @@ -871,7 +868,11 @@ mod test { // --------------------------------------------------------------------------------------------------------------------------- // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned + #[expect(clippy::arithmetic_side_effects)] fn traverse_freelist_should_skip_offspring_of_incorrect_areas() { + const AREA_INDEX1: AreaIndex = area_index!(9); // 2048 + const AREA_INDEX2: AreaIndex = area_index!(12); // 16384 + let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); @@ -879,16 +880,15 @@ mod test { let mut high_watermark = NodeStoreHeader::SIZE + 16; // + 16 to create overlap // first free list - let area_index1 = 9; // 2048 - let area_size1 = AREA_SIZES[area_index1 as usize]; + let area_size1 = AREA_INDEX1.size(); let mut next_free_block1 = None; - test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); let free_list1_area3 = LinearAddress::new(high_watermark).unwrap(); next_free_block1 = Some(free_list1_area3); high_watermark += area_size1; - test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); let free_list1_area2 = LinearAddress::new(high_watermark).unwrap(); next_free_block1 = Some(free_list1_area2); high_watermark += area_size1; @@ -896,34 +896,33 @@ mod test { let intersection_end = LinearAddress::new(high_watermark).unwrap(); high_watermark -= 16; // create an overlap with free_list1_area2 and restore to page-aligned address let intersection_start = LinearAddress::new(high_watermark).unwrap(); - test_write_free_area(&nodestore, next_free_block1, area_index1, high_watermark); + test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); let free_list1_area1 = LinearAddress::new(high_watermark).unwrap(); next_free_block1 = Some(free_list1_area1); high_watermark += area_size1; - free_lists[area_index1 as usize] = next_free_block1; + free_lists[AREA_INDEX1.as_usize()] = next_free_block1; // second free list - let area_index2 = 12; // 16384 - let area_size2 = AREA_SIZES[area_index2 as usize]; + let area_size2 = AREA_INDEX2.size(); let mut next_free_block2 = None; - test_write_free_area(&nodestore, next_free_block2, area_index2, high_watermark); + test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, high_watermark); let free_list2_area2 = LinearAddress::new(high_watermark).unwrap(); next_free_block2 = Some(free_list2_area2); high_watermark += area_size2; - test_write_free_area(&nodestore, next_free_block2, area_index2, high_watermark); + test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, high_watermark); let free_list2_area1 = LinearAddress::new(high_watermark).unwrap(); next_free_block2 = Some(free_list2_area1); high_watermark += area_size2; - free_lists[area_index2 as usize] = next_free_block2; + free_lists[AREA_INDEX2.as_usize()] = next_free_block2; // write header test_write_header(&mut nodestore, high_watermark, None, free_lists); - let expected_start_addr = free_lists[area_index1 as usize].unwrap(); + let expected_start_addr = free_lists[AREA_INDEX1.as_usize()].unwrap(); let expected_end_addr = LinearAddress::new(high_watermark).unwrap(); let expected_free_areas = vec![expected_start_addr..expected_end_addr]; let expected_freelist_errors = vec![CheckerError::AreaIntersects { @@ -963,17 +962,11 @@ mod test { let mut high_watermark = NodeStoreHeader::SIZE; let mut stored_areas = Vec::new(); for _ in 0..num_areas { - let (area_size_index, area_size) = - AREA_SIZES.iter().enumerate().choose(&mut rng).unwrap(); + let (area_size_index, area_size) = area_size_iter().choose(&mut rng).unwrap(); let area_addr = LinearAddress::new(high_watermark).unwrap(); - test_write_free_area( - &nodestore, - None, - area_size_index as AreaIndex, - high_watermark, - ); - stored_areas.push((area_addr, area_size_index as AreaIndex, *area_size)); - high_watermark += *area_size; + test_write_free_area(&nodestore, None, area_size_index, high_watermark); + stored_areas.push((area_addr, area_size_index, area_size)); + high_watermark += area_size; } test_write_header(&mut nodestore, high_watermark, None, FreeLists::default()); @@ -1015,14 +1008,21 @@ mod test { #[test] // This test creates a linear set of free areas and free them. // When traversing it should break consecutive areas. + #[expect(clippy::arithmetic_side_effects)] fn split_range_of_zeros_into_leaked_areas() { let memstore = MemStore::new(vec![]); let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - let expected_leaked_area_indices = vec![8u8, 7, 4, 2, 0]; + let expected_leaked_area_indices = vec![ + area_index!(8), + area_index!(7), + area_index!(4), + area_index!(2), + AreaIndex::MIN, + ]; let expected_leaked_area_sizes = expected_leaked_area_indices .iter() - .map(|i| AREA_SIZES[*i as usize]) + .map(|i| i.size()) .collect::>(); assert_eq!(expected_leaked_area_sizes, vec![1024, 768, 128, 64, 16]); let expected_offsets = expected_leaked_area_sizes @@ -1059,27 +1059,33 @@ mod test { #[test] // With both valid and invalid areas in the range, return the valid areas until reaching one invalid area, then use heuristics to split the rest of the range. + #[expect(clippy::arithmetic_side_effects)] fn split_range_into_leaked_areas_test() { let memstore = MemStore::new(vec![]); let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); // write two free areas let mut high_watermark = NodeStoreHeader::SIZE; - test_write_free_area(&nodestore, None, 8, high_watermark); // 1024 - high_watermark += AREA_SIZES[8]; - test_write_free_area(&nodestore, None, 7, high_watermark); // 768 - high_watermark += AREA_SIZES[7]; + test_write_free_area(&nodestore, None, area_index!(8), high_watermark); // 1024 + high_watermark += area_index!(8).size(); + test_write_free_area(&nodestore, None, area_index!(7), high_watermark); // 768 + high_watermark += area_index!(7).size(); // write an zeroed area test_write_zeroed_area(&nodestore, 768, high_watermark); high_watermark += 768; // write another free area - test_write_free_area(&nodestore, None, 8, high_watermark); // 1024 - high_watermark += AREA_SIZES[8]; - - let expected_indices = vec![8, 7, 8, 7]; + test_write_free_area(&nodestore, None, area_index!(8), high_watermark); // 1024 + high_watermark += area_index!(8).size(); + + let expected_indices = vec![ + area_index!(8), + area_index!(7), + area_index!(8), + area_index!(7), + ]; let expected_sizes = expected_indices .iter() - .map(|i| AREA_SIZES[*i as usize]) + .map(|i| i.size()) .collect::>(); let expected_offsets = expected_sizes .iter() diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index b8ad3bf65e07..03608569f36e 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -591,7 +591,7 @@ mod test_range_set { #[expect(clippy::unwrap_used)] mod test_linear_address_range_set { - use crate::{FreeListParent, TrieNodeParent}; + use crate::{FreeListParent, TrieNodeParent, area_index}; use super::*; use test_case::test_case; @@ -667,7 +667,7 @@ mod test_linear_address_range_set { let start2_addr = LinearAddress::new(start2).unwrap(); let parent1 = StoredAreaParent::TrieNode(TrieNodeParent::Parent(start1_addr, 5)); - let parent2 = StoredAreaParent::FreeList(FreeListParent::FreeListHead(3)); + let parent2 = StoredAreaParent::FreeList(FreeListParent::FreeListHead(area_index!(3))); let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); visited diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 7764fe6971d8..921dea16998b 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -33,8 +33,6 @@ mod nodestore; mod test_utils; mod trie_hash; -use crate::nodestore::AreaIndex; - /// Logger module for handling logging functionality pub mod logger; @@ -51,8 +49,8 @@ pub use node::{ branch::{HashType, IntoHashType}, }; pub use nodestore::{ - Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, - NodeStore, Parentable, RootReader, TrieReader, + AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, + NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; pub use linear::filebacked::FileBacked; @@ -246,7 +244,7 @@ pub enum CheckerError { /// The start address of a stored area is not a multiple of 16 #[error( "The start address of a stored area (parent: {parent:#x}) is not a multiple of {}: {address:#x}", - nodestore::alloc::LinearAddress::MIN_AREA_SIZE + nodestore::primitives::AreaIndex::MIN_AREA_SIZE )] AreaMisaligned { /// The start address of the stored area diff --git a/storage/src/macros.rs b/storage/src/macros.rs index 3aa48642d6e4..1188fb70fa3d 100644 --- a/storage/src/macros.rs +++ b/storage/src/macros.rs @@ -66,3 +66,24 @@ macro_rules! firewood_gauge { } }; } + +#[macro_export] +#[cfg(test)] +/// Macro to create an `AreaIndex` from a literal value at compile time. +/// This macro performs bounds checking at compile time and panics if the value is out of bounds. +/// +/// Usage: +/// `area_index!(0)` - creates an `AreaIndex` with value 0 +/// `area_index!(23)` - creates an `AreaIndex` with value 23 +/// +/// The macro will panic at compile time if the value is negative or >= `NUM_AREA_SIZES`. +macro_rules! area_index { + ($v:expr) => { + const { + match $crate::nodestore::primitives::AreaIndex::new($v as u8) { + Some(v) => v, + None => panic!("Constant area index out of bounds"), + } + } + }; +} diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 3fdca7f4c252..72cb9a95de5f 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -19,6 +19,7 @@ )] use crate::node::branch::ReadSerializable; +use crate::nodestore::AreaIndex; use crate::{HashType, LinearAddress, Path, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; @@ -220,7 +221,7 @@ impl Node { /// we always have one of those, we include it as a parameter for serialization. /// /// TODO: We could pack two bytes of the partial path into one and handle the odd byte length - pub fn as_bytes(&self, prefix: u8, encoded: &mut T) { + pub fn as_bytes(&self, prefix: AreaIndex, encoded: &mut T) { match self { Node::Branch(b) => { let child_iter = b @@ -250,7 +251,7 @@ impl Node { // create an output stack item, which can overflow to memory for very large branch nodes const OPTIMIZE_BRANCHES_FOR_SIZE: usize = 1024; encoded.reserve(OPTIMIZE_BRANCHES_FOR_SIZE); - encoded.push(prefix); + encoded.push(prefix.get()); encoded.push(first_byte.0); #[cfg(feature = "branch_factor_256")] encoded.push((childcount % BranchNode::MAX_CHILDREN) as u8); @@ -297,7 +298,7 @@ impl Node { const OPTIMIZE_LEAVES_FOR_SIZE: usize = 128; encoded.reserve(OPTIMIZE_LEAVES_FOR_SIZE); - encoded.push(prefix); + encoded.push(prefix.get()); encoded.push(first_byte.0); // encode the partial path, including the length if it didn't fit above @@ -449,6 +450,7 @@ mod test { #![expect(clippy::unwrap_used)] use crate::node::{BranchNode, LeafNode, Node}; + use crate::nodestore::AreaIndex; use crate::{Child, LinearAddress, NibblesIterator, Path}; use test_case::test_case; @@ -512,7 +514,7 @@ than 126 bytes as the length would be encoded in multiple bytes. use std::io::Cursor; let mut serialized = Vec::new(); - node.as_bytes(0, &mut serialized); + node.as_bytes(AreaIndex::MIN, &mut serialized); #[cfg(not(any(feature = "branch_factor_256", feature = "ethhash")))] assert_eq!(serialized.len(), expected_length); let mut cursor = Cursor::new(&serialized); diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 0016ce7ad71d..3df2cbe5dc80 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -20,22 +20,20 @@ //! - **`AreaType`** - 0xFF for free areas, otherwise node type data (1 byte) //! - **`NodeData`** - Serialized node content +use super::area_index_and_size; +use super::primitives::{AreaIndex, LinearAddress, index_name}; use crate::linear::FileIoError; use crate::logger::trace; use crate::node::branch::{ReadSerializable, Serializable}; use crate::nodestore::NodeStoreHeader; use integer_encoding::VarIntReader; -use sha2::{Digest, Sha256}; -use std::fmt; use std::io::{Error, ErrorKind, Read}; use std::iter::FusedIterator; -use std::num::NonZeroU64; use crate::node::ExtendableBytes; use crate::{ - FreeListParent, MaybePersistedNode, ReadableStorage, TrieHash, WritableStorage, - firewood_counter, + FreeListParent, MaybePersistedNode, ReadableStorage, WritableStorage, firewood_counter, }; /// Returns the maximum size needed to encode a `VarInt`. @@ -43,239 +41,8 @@ const fn var_int_max_size() -> usize { const { (size_of::() * 8 + 7) / 7 } } -/// [`NodeStore`] divides the linear store into blocks of different sizes. -/// [`AREA_SIZES`] is every valid block size. -pub const AREA_SIZES: [u64; 23] = [ - 16, // Min block size - 32, - 64, - 96, - 128, - 256, - 512, - 768, - 1024, - 1024 << 1, - 1024 << 2, - 1024 << 3, - 1024 << 4, - 1024 << 5, - 1024 << 6, - 1024 << 7, - 1024 << 8, - 1024 << 9, - 1024 << 10, - 1024 << 11, - 1024 << 12, - 1024 << 13, - 1024 << 14, -]; - -pub fn area_size_hash() -> TrieHash { - let mut hasher = Sha256::new(); - for size in AREA_SIZES { - hasher.update(size.to_ne_bytes()); - } - hasher.finalize().into() -} - -/// Get the size of an area index (used by the checker) -/// -/// # Panics -/// -/// Panics if `index` is out of bounds for the `AREA_SIZES` array. -#[must_use] -pub const fn size_from_area_index(index: AreaIndex) -> u64 { - #[expect(clippy::indexing_slicing)] - AREA_SIZES[index as usize] -} - -// TODO: automate this, must stay in sync with above -pub const fn index_name(index: usize) -> &'static str { - match index { - 0 => "16", - 1 => "32", - 2 => "64", - 3 => "96", - 4 => "128", - 5 => "256", - 6 => "512", - 7 => "768", - 8 => "1024", - 9 => "2048", - 10 => "4096", - 11 => "8192", - 12 => "16384", - 13 => "32768", - 14 => "65536", - 15 => "131072", - 16 => "262144", - 17 => "524288", - 18 => "1048576", - 19 => "2097152", - 20 => "4194304", - 21 => "8388608", - 22 => "16777216", - _ => "unknown", - } -} - -/// The type of an index into the [`AREA_SIZES`] array -/// This is not usize because we can store this as a single byte -pub type AreaIndex = u8; - -const NUM_AREA_SIZES: usize = AREA_SIZES.len(); -const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; -const MAX_AREA_SIZE: u64 = AREA_SIZES[NUM_AREA_SIZES - 1]; - -#[inline] -pub fn new_area_index(n: usize) -> AreaIndex { - n.try_into().expect("Area index out of bounds") -} - -/// Returns the index in `BLOCK_SIZES` of the smallest block size >= `n`. -pub fn area_size_to_index(n: u64) -> Result { - if n > MAX_AREA_SIZE { - return Err(Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - )); - } - - if n <= MIN_AREA_SIZE { - return Ok(0); - } - - AREA_SIZES - .iter() - .position(|&size| size >= n) - .map(new_area_index) - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - ) - }) -} - -/// A linear address in the nodestore storage. -/// -/// This represents a non-zero address in the linear storage space. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct LinearAddress(NonZeroU64); - -#[expect(unsafe_code)] -// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `ZeroableInOption`. -unsafe impl bytemuck::ZeroableInOption for LinearAddress {} -#[expect(unsafe_code)] -// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `PodInOption`. -unsafe impl bytemuck::PodInOption for LinearAddress {} - -impl LinearAddress { - /// Create a new `LinearAddress`, returns None if value is zero. - #[inline] - #[must_use] - pub const fn new(addr: u64) -> Option { - match NonZeroU64::new(addr) { - Some(addr) => Some(LinearAddress(addr)), - None => None, - } - } - - /// Get the underlying address as u64. - #[inline] - #[must_use] - pub const fn get(self) -> u64 { - self.0.get() - } - - /// Check if the address is 8-byte aligned. - #[inline] - #[must_use] - pub const fn is_aligned(self) -> bool { - self.0.get() % (Self::MIN_AREA_SIZE) == 0 - } - - /// The maximum area size available for allocation. - pub const MAX_AREA_SIZE: u64 = *AREA_SIZES.last().unwrap(); - - /// The minimum area size available for allocation. - pub const MIN_AREA_SIZE: u64 = *AREA_SIZES.first().unwrap(); - - /// Returns the number of different area sizes available. - #[inline] - #[must_use] - pub const fn num_area_sizes() -> usize { - const { AREA_SIZES.len() } - } - - /// Returns the inner `NonZeroU64` - #[inline] - #[must_use] - pub const fn into_nonzero(self) -> NonZeroU64 { - self.0 - } - - /// Advances a `LinearAddress` by `n` bytes. - /// - /// Returns `None` if the result overflows a u64 - /// Some(LinearAddress) otherwise - /// - #[inline] - #[must_use] - pub const fn advance(self, n: u64) -> Option { - match self.0.checked_add(n) { - // overflowed - None => None, - - // It is impossible to add a non-zero positive number to a u64 and get 0 without - // overflowing, so we don't check for that here, and panic instead. - Some(sum) => Some(LinearAddress(sum)), - } - } - - /// Returns the number of bytes between `other` and `self` if `other` is less than or equal to `self`. - /// Otherwise, returns `None`. - #[inline] - #[must_use] - pub const fn distance_from(self, other: Self) -> Option { - self.0.get().checked_sub(other.0.get()) - } -} - -impl std::ops::Deref for LinearAddress { - type Target = NonZeroU64; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl fmt::Display for LinearAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - std::fmt::Display::fmt(&self.get(), f) - } -} - -impl fmt::LowerHex for LinearAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - std::fmt::LowerHex::fmt(&self.get(), f) - } -} -impl From for u64 { - fn from(addr: LinearAddress) -> Self { - addr.get() - } -} - -impl From for LinearAddress { - fn from(addr: NonZeroU64) -> Self { - LinearAddress(addr) - } -} - /// `FreeLists` is an array of `Option` for each area size. -pub type FreeLists = [Option; AREA_SIZES.len()]; +pub type FreeLists = [Option; AreaIndex::NUM_AREA_SIZES]; /// A [`FreeArea`] is stored at the start of the area that contained a node that /// has been freed. @@ -407,12 +174,12 @@ impl FreeArea { const RESERVE_SIZE: usize = size_of::() + var_int_max_size::(); encoded.reserve(RESERVE_SIZE); - encoded.push(area_index); + encoded.push(area_index.get()); self.write_to(encoded); } fn from_storage_reader(mut reader: impl Read) -> std::io::Result<(Self, AreaIndex)> { - let area_index = reader.read_byte()?; + let area_index = AreaIndex::try_from(reader.read_byte()?)?; let free_area = reader.next_value()?; Ok((free_area, area_index)) } @@ -443,25 +210,7 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { &self, addr: LinearAddress, ) -> Result<(AreaIndex, u64), FileIoError> { - let mut area_stream = self.storage.stream_from(addr.get())?; - - let index: AreaIndex = area_stream.read_byte().map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("deserialize".to_string()), - ) - })?; - - let size = *AREA_SIZES - .get(index as usize) - .ok_or(self.storage.file_io_error( - Error::other(format!("Invalid area size index {index}")), - addr.get(), - None, - ))?; - - Ok((index, size)) + area_index_and_size(self.storage, addr) } /// Attempts to allocate `n` bytes from the free lists. @@ -469,13 +218,12 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { /// and the index of the free list that was used. /// If there are no free areas big enough for `n` bytes, returns None. /// TODO Consider splitting the area if we return a larger area than requested. - #[expect(clippy::indexing_slicing)] fn allocate_from_freed( &mut self, n: u64, ) -> Result, FileIoError> { // Find the smallest free list that can fit this size. - let index_wanted = area_size_to_index(n).map_err(|e| { + let index_wanted = AreaIndex::from_size(n).map_err(|e| { self.storage .file_io_error(e, 0, Some("allocate_from_freed".to_string())) })?; @@ -485,9 +233,11 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { .free_lists_mut() .iter_mut() .enumerate() - .skip(index_wanted as usize) + .skip(index_wanted.as_usize()) .find(|item| item.1.is_some()) { + let index = + AreaIndex::try_from(index).expect("index is less than AreaIndex::NUM_AREA_SIZES"); let address = free_stored_area_addr .take() .expect("impossible due to find earlier"); @@ -497,7 +247,7 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { *free_stored_area_addr = free_head; } else { let (free_head, read_index) = FreeArea::from_storage(self.storage, address)?; - debug_assert_eq!(read_index as usize, index); + debug_assert_eq!(read_index, index); // Update the free list to point to the next free block. *free_stored_area_addr = free_head.next_free_block; @@ -508,36 +258,35 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { "Bytes reused from free list by index", "index" => index_name(index) ) - .increment(AREA_SIZES[index]); + .increment(index.size()); firewood_counter!( "firewood.space.wasted", "Bytes wasted from free list by index", "index" => index_name(index) ) - .increment(AREA_SIZES[index].saturating_sub(n)); + .increment(index.size().saturating_sub(n)); // Return the address of the newly allocated block. trace!("Allocating from free list: addr: {address:?}, size: {index}"); - return Ok(Some((address, index as AreaIndex))); + return Ok(Some((address, index))); } trace!("No free blocks of sufficient size {index_wanted} found"); firewood_counter!( "firewood.space.from_end", "Space allocated from end of nodestore", - "index" => index_name(index_wanted as usize) + "index" => index_name(index_wanted) ) - .increment(AREA_SIZES[index_wanted as usize]); + .increment(index_wanted.size()); Ok(None) } - #[expect(clippy::indexing_slicing)] fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { - let index = area_size_to_index(n).map_err(|e| { + let index = AreaIndex::from_size(n).map_err(|e| { self.storage .file_io_error(e, 0, Some("allocate_from_end".to_string())) })?; - let area_size = AREA_SIZES[index as usize]; + let area_size = index.size(); let addr = LinearAddress::new(self.header.size()).expect("node store size can't be 0"); self.header .set_size(self.header.size().saturating_add(area_size)); @@ -590,28 +339,28 @@ impl NodeAllocator<'_, S> { firewood_counter!( "firewood.delete_node", "Nodes deleted", - "index" => index_name(area_size_index as usize) + "index" => index_name(area_size_index) ) .increment(1); firewood_counter!( "firewood.space.freed", "Bytes freed in nodestore", - "index" => index_name(area_size_index as usize) + "index" => index_name(area_size_index) ) - .increment(AREA_SIZES[area_size_index as usize]); + .increment(area_size_index.size()); // The area that contained the node is now free. let mut stored_area_bytes = Vec::new(); - FreeArea::new(self.header.free_lists()[area_size_index as usize]) + FreeArea::new(self.header.free_lists()[area_size_index.as_usize()]) .as_bytes(area_size_index, &mut stored_area_bytes); self.storage.write(addr.into(), &stored_area_bytes)?; self.storage - .add_to_free_list_cache(addr, self.header.free_lists()[area_size_index as usize]); + .add_to_free_list_cache(addr, self.header.free_lists()[area_size_index.as_usize()]); // The newly freed block is now the head of the free list. - self.header.free_lists_mut()[area_size_index as usize] = Some(addr); + self.header.free_lists_mut()[area_size_index.as_usize()] = Some(addr); Ok(()) } @@ -620,6 +369,7 @@ impl NodeAllocator<'_, S> { /// Iterator over free lists in the nodestore struct FreeListIterator<'a, S: ReadableStorage> { storage: &'a S, + id: AreaIndex, next_addr: Option, parent: FreeListParent, } @@ -627,29 +377,29 @@ struct FreeListIterator<'a, S: ReadableStorage> { impl<'a, S: ReadableStorage> FreeListIterator<'a, S> { const fn new( storage: &'a S, + free_list_id: AreaIndex, next_addr: Option, src_ptr: FreeListParent, ) -> Self { Self { storage, + id: free_list_id, next_addr, parent: src_ptr, } } - #[expect( - clippy::type_complexity, - reason = "TODO: we need some additional newtypes here" - )] - fn next_with_parent( + fn next_with_metadata( &mut self, - ) -> Option<( - Result<(LinearAddress, AreaIndex), FileIoError>, - FreeListParent, - )> { + ) -> Option<(Result, FreeListParent)> { let parent = self.parent; let next_addr = self.next()?; - Some((next_addr, parent)) + let next_with_metadata = next_addr.map(|(addr, area_index)| FreeAreaWithMetadata { + addr, + area_index, + free_list_id: self.id, + }); + Some((next_with_metadata, parent)) } } @@ -690,8 +440,7 @@ pub(crate) struct FreeListsIterator<'a, S: ReadableStorage> { free_lists_iter: std::iter::Skip< std::iter::Enumerate>>, >, - current_free_list_id: AreaIndex, - free_list_iter: FreeListIterator<'a, S>, + current_free_list: Option<(AreaIndex, FreeListIterator<'a, S>)>, } impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { @@ -703,38 +452,29 @@ impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { let mut free_lists_iter = free_lists .iter() .enumerate() - .skip(start_area_index as usize); - let (current_free_list_id, free_list_head) = match free_lists_iter.next() { - Some((id, head)) => (id as AreaIndex, *head), - None => (NUM_AREA_SIZES as AreaIndex, None), - }; - let start_iterator = FreeListIterator::new( - storage, - free_list_head, - FreeListParent::FreeListHead(current_free_list_id), - ); + .skip(start_area_index.as_usize()); + let current_free_list = free_lists_iter.next().map(|(id, head)| { + let free_list_id = + AreaIndex::try_from(id).expect("id is less than AreaIndex::NUM_AREA_SIZES"); + let free_list_iter = FreeListIterator::new( + storage, + free_list_id, + *head, + FreeListParent::FreeListHead(free_list_id), + ); + (free_list_id, free_list_iter) + }); Self { storage, free_lists_iter, - current_free_list_id, - free_list_iter: start_iterator, + current_free_list, } } pub(crate) fn next_with_metadata( &mut self, ) -> Option<(Result, FreeListParent)> { - self.next_inner(FreeListIterator::next_with_parent) - .map(|(next, parent)| { - ( - next.map(|(addr, area_index)| FreeAreaWithMetadata { - addr, - area_index, - free_list_id: self.current_free_list_id, - }), - parent, - ) - }) + self.next_inner(FreeListIterator::next_with_metadata) } fn next_inner) -> Option>( @@ -742,30 +482,32 @@ impl<'a, S: ReadableStorage> FreeListsIterator<'a, S> { mut next_fn: F, ) -> Option { loop { - if let Some(next) = next_fn(&mut self.free_list_iter) { + let Some((_, free_list_iter)) = &mut self.current_free_list else { + return None; + }; + if let Some(next) = next_fn(free_list_iter) { // the current free list is not exhausted, return the next free area return Some(next); } self.move_to_next_free_list(); - if self.current_free_list_id == NUM_AREA_SIZES as AreaIndex { - // no more free lists to iterate over - return None; - } } } pub(crate) fn move_to_next_free_list(&mut self) { - let (current_free_list_id, next_free_list_head) = match self.free_lists_iter.next() { - Some((id, head)) => (id as AreaIndex, *head), - None => (NUM_AREA_SIZES as AreaIndex, None), // skip unvisited free areas in the current iterator + let Some((next_free_list_id, next_free_list_head)) = self.free_lists_iter.next() else { + self.current_free_list = None; + return; }; - self.current_free_list_id = current_free_list_id; - self.free_list_iter = FreeListIterator::new( + let next_free_list_id = AreaIndex::try_from(next_free_list_id) + .expect("next_free_list_id is less than AreaIndex::NUM_AREA_SIZES"); + let next_free_list_iter = FreeListIterator::new( self.storage, - next_free_list_head, - FreeListParent::FreeListHead(self.current_free_list_id), + next_free_list_id, + *next_free_list_head, + FreeListParent::FreeListHead(next_free_list_id), ); + self.current_free_list = Some((next_free_list_id, next_free_list_iter)); } } @@ -836,11 +578,10 @@ pub mod test_utils { node: &Node, offset: u64, ) -> (u64, u64) { - #![expect(clippy::indexing_slicing)] let mut encoded_node = Vec::new(); - node.as_bytes(0, &mut encoded_node); + node.as_bytes(AreaIndex::MIN, &mut encoded_node); let encoded_node_len = encoded_node.len() as u64; - let area_size_index = area_size_to_index(encoded_node_len).unwrap(); + let area_size_index = AreaIndex::from_size(encoded_node_len).unwrap(); let mut stored_area_bytes = Vec::new(); node.as_bytes(area_size_index, &mut stored_area_bytes); let bytes_written = (stored_area_bytes.len() as u64) @@ -850,7 +591,7 @@ pub mod test_utils { .storage .write(offset, stored_area_bytes.as_slice()) .unwrap(); - (bytes_written, AREA_SIZES[area_size_index as usize]) + (bytes_written, area_size_index.size()) } // Helper function to write a free area to the given offset. @@ -896,18 +637,20 @@ pub mod test_utils { #[expect(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { use super::*; + use crate::area_index; use crate::linear::memory::MemStore; use rand::seq::IteratorRandom; use test_case::test_case; + use test_utils::{test_write_free_area, test_write_header}; - #[test_case(&[0x01, 0x01, 0x01, 0x2a], Some((1, 42)); "old format")] + #[test_case(&[0x01, 0x01, 0x01, 0x2a], Some((area_index!(1), 42)); "old format")] // StoredArea::new(12, Area::::Free(FreeArea::new(None))); - #[test_case(&[0x02, 0x01, 0x00], Some((2, 0)); "none")] - #[test_case(&[0x03, 0xff, 0x2b], Some((3, 43)); "new format")] + #[test_case(&[0x02, 0x01, 0x00], Some((area_index!(2), 0)); "none")] + #[test_case(&[0x03, 0xff, 0x2b], Some((area_index!(3), 43)); "new format")] #[test_case(&[0x03, 0x44, 0x55], None; "garbage")] #[test_case( &[0x03, 0x01, 0x01, 0xfd, 0xe0, 0xa2, 0x6d, 0x27, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x09, 0x03, 0x00], - Some((3, 0x6e_276d_a2e0)); + Some((area_index!(3), 0x6e_276d_a2e0)); "old format with u64 address (issue #1146)" )] fn test_free_list_format(reader: &[u8], expected: Option<(AreaIndex, u64)>) { @@ -924,8 +667,9 @@ mod tests { let memstore = MemStore::new(vec![]); let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - let area_index = rng.random_range(0..NUM_AREA_SIZES as u8); - let area_size = AREA_SIZES[area_index as usize]; + let area_index = rng.random_range(0..AreaIndex::NUM_AREA_SIZES as u8); + let area_index_type = AreaIndex::try_from(area_index).unwrap(); + let area_size = area_index_type.size(); // create a random free list scattered across the storage let offsets = (1..100u64) @@ -935,11 +679,16 @@ mod tests { test_utils::test_write_free_area( &nodestore, Some(LinearAddress::new(*next).unwrap()), - area_index, + area_index_type, *cur, ); } - test_utils::test_write_free_area(&nodestore, None, area_index, *offsets.last().unwrap()); + test_utils::test_write_free_area( + &nodestore, + None, + area_index_type, + *offsets.last().unwrap(), + ); // test iterator from a random starting point let skip = rng.random_range(0..offsets.len()); @@ -947,18 +696,19 @@ mod tests { let start = iterator.next().unwrap(); let mut free_list_iter = FreeListIterator::new( nodestore.storage.as_ref(), + area_index_type, LinearAddress::new(start), - FreeListParent::FreeListHead(area_index), + FreeListParent::FreeListHead(area_index_type), ); assert_eq!( free_list_iter.next().unwrap().unwrap(), - (LinearAddress::new(start).unwrap(), area_index) + (LinearAddress::new(start).unwrap(), area_index_type) ); for offset in iterator { assert_eq!( free_list_iter.next().unwrap().unwrap(), - (LinearAddress::new(offset).unwrap(), area_index) + (LinearAddress::new(offset).unwrap(), area_index_type) ); } @@ -976,46 +726,50 @@ mod tests { let mut offset = NodeStoreHeader::SIZE; // first free list - let area_index1 = rng.random_range(0..NUM_AREA_SIZES as u8); - let area_size1 = AREA_SIZES[area_index1 as usize]; + let area_index1 = + AreaIndex::try_from(rng.random_range(0..AreaIndex::NUM_AREA_SIZES as u8)).unwrap(); + let area_size1 = area_index1.size(); let mut next_free_block1 = None; - test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + test_write_free_area(&nodestore, next_free_block1, area_index1, offset); let free_list1_area2 = LinearAddress::new(offset).unwrap(); next_free_block1 = Some(free_list1_area2); offset += area_size1; - test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + test_write_free_area(&nodestore, next_free_block1, area_index1, offset); let free_list1_area1 = LinearAddress::new(offset).unwrap(); next_free_block1 = Some(free_list1_area1); offset += area_size1; - free_lists[area_index1 as usize] = next_free_block1; + free_lists[area_index1.as_usize()] = next_free_block1; // second free list - let area_index2 = - (area_index1 + rng.random_range(1..NUM_AREA_SIZES as u8)) % NUM_AREA_SIZES as u8; // make sure the second free list is different from the first + let area_index2 = AreaIndex::new( + (area_index1.get() + rng.random_range(1..AreaIndex::NUM_AREA_SIZES as u8)) + % AreaIndex::NUM_AREA_SIZES as u8, + ) + .unwrap(); // make sure the second free list is different from the first assert_ne!(area_index1, area_index2); - let area_size2 = AREA_SIZES[area_index2 as usize]; + let area_size2 = area_index2.size(); let mut next_free_block2 = None; - test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + test_write_free_area(&nodestore, next_free_block2, area_index2, offset); let free_list2_area2 = LinearAddress::new(offset).unwrap(); next_free_block2 = Some(free_list2_area2); offset += area_size2; - test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + test_write_free_area(&nodestore, next_free_block2, area_index2, offset); let free_list2_area1 = LinearAddress::new(offset).unwrap(); next_free_block2 = Some(free_list2_area1); offset += area_size2; - free_lists[area_index2 as usize] = next_free_block2; + free_lists[area_index2.as_usize()] = next_free_block2; // write header - test_utils::test_write_header(&mut nodestore, offset, None, free_lists); + test_write_header(&mut nodestore, offset, None, free_lists); // test iterator - let mut free_list_iter = nodestore.free_list_iter(0); + let mut free_list_iter = nodestore.free_list_iter(AreaIndex::MIN); // expected let expected_free_list1 = vec![ @@ -1076,7 +830,15 @@ mod tests { } #[test] + #[expect(clippy::arithmetic_side_effects)] fn free_lists_iter_skip_to_next_free_list() { + use test_utils::{test_write_free_area, test_write_header}; + + const AREA_INDEX1: AreaIndex = area_index!(3); + const AREA_INDEX1_PLUS_1: AreaIndex = area_index!(4); + const AREA_INDEX2: AreaIndex = area_index!(5); + const AREA_INDEX2_PLUS_1: AreaIndex = area_index!(6); + let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); @@ -1084,91 +846,110 @@ mod tests { let mut offset = NodeStoreHeader::SIZE; // first free list - let area_index1 = 3; - let area_size1 = AREA_SIZES[area_index1 as usize]; + let area_size1 = AREA_INDEX1.size(); let mut next_free_block1 = None; - test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, offset); let free_list1_area2 = LinearAddress::new(offset).unwrap(); next_free_block1 = Some(free_list1_area2); offset += area_size1; - test_utils::test_write_free_area(&nodestore, next_free_block1, area_index1, offset); + test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, offset); let free_list1_area1 = LinearAddress::new(offset).unwrap(); next_free_block1 = Some(free_list1_area1); offset += area_size1; - free_lists[area_index1 as usize] = next_free_block1; + free_lists[AREA_INDEX1.as_usize()] = next_free_block1; // second free list - let area_index2 = 5; - assert_ne!(area_index1, area_index2); - let area_size2 = AREA_SIZES[area_index2 as usize]; + assert_ne!(AREA_INDEX1, AREA_INDEX2); + let area_size2 = AREA_INDEX2.size(); let mut next_free_block2 = None; - test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, offset); let free_list2_area2 = LinearAddress::new(offset).unwrap(); next_free_block2 = Some(free_list2_area2); offset += area_size2; - test_utils::test_write_free_area(&nodestore, next_free_block2, area_index2, offset); + test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, offset); let free_list2_area1 = LinearAddress::new(offset).unwrap(); next_free_block2 = Some(free_list2_area1); offset += area_size2; - free_lists[area_index2 as usize] = next_free_block2; + free_lists[AREA_INDEX2.as_usize()] = next_free_block2; // write header - test_utils::test_write_header(&mut nodestore, offset, None, free_lists); + test_write_header(&mut nodestore, offset, None, free_lists); // test iterator - let mut free_list_iter = nodestore.free_list_iter(0); + let mut free_list_iter = nodestore.free_list_iter(AreaIndex::MIN); // start at the first free list - assert_eq!(free_list_iter.current_free_list_id, 0); + assert_eq!( + free_list_iter.current_free_list.as_ref().unwrap().0, + AreaIndex::MIN + ); let (next, next_parent) = free_list_iter.next_with_metadata().unwrap(); assert_eq!( next.unwrap(), FreeAreaWithMetadata { addr: free_list1_area1, - area_index: area_index1, - free_list_id: area_index1, + area_index: AREA_INDEX1, + free_list_id: AREA_INDEX1, }, ); - assert_eq!(next_parent, FreeListParent::FreeListHead(area_index1)); + assert_eq!(next_parent, FreeListParent::FreeListHead(AREA_INDEX1)); // `next_with_metadata` moves the iterator to the first free list that is not empty - assert_eq!(free_list_iter.current_free_list_id, area_index1); + assert_eq!( + free_list_iter.current_free_list.as_ref().unwrap().0, + AREA_INDEX1 + ); free_list_iter.move_to_next_free_list(); // `move_to_next_free_list` moves the iterator to the next free list - assert_eq!(free_list_iter.current_free_list_id, area_index1 + 1); + assert_eq!( + free_list_iter.current_free_list.as_ref().unwrap().0, + AREA_INDEX1_PLUS_1 + ); let (next, next_parent) = free_list_iter.next_with_metadata().unwrap(); assert_eq!( next.unwrap(), FreeAreaWithMetadata { addr: free_list2_area1, - area_index: area_index2, - free_list_id: area_index2, + area_index: AREA_INDEX2, + free_list_id: AREA_INDEX2, }, ); - assert_eq!(next_parent, FreeListParent::FreeListHead(area_index2)); + assert_eq!(next_parent, FreeListParent::FreeListHead(AREA_INDEX2)); // `next_with_metadata` moves the iterator to the first free list that is not empty - assert_eq!(free_list_iter.current_free_list_id, area_index2); + assert_eq!( + free_list_iter.current_free_list.as_ref().unwrap().0, + AREA_INDEX2 + ); free_list_iter.move_to_next_free_list(); // `move_to_next_free_list` moves the iterator to the next free list - assert_eq!(free_list_iter.current_free_list_id, area_index2 + 1); + assert_eq!( + free_list_iter.current_free_list.as_ref().unwrap().0, + AREA_INDEX2_PLUS_1 + ); assert!(free_list_iter.next_with_metadata().is_none()); // since no more non-empty free lists, `move_to_next_free_list` moves the iterator to the end - assert_eq!(free_list_iter.current_free_list_id, NUM_AREA_SIZES as u8); + assert!(free_list_iter.current_free_list.is_none()); free_list_iter.move_to_next_free_list(); // `move_to_next_free_list` will do nothing since we are already at the end - assert_eq!(free_list_iter.current_free_list_id, NUM_AREA_SIZES as u8); + assert!(free_list_iter.current_free_list.is_none()); assert!(free_list_iter.next_with_metadata().is_none()); } #[test] - const fn const_expr_tests() { + const fn la_const_expr_tests() { // these are const expr let _ = const { LinearAddress::new(0) }; let _ = const { LinearAddress::new(1).unwrap().advance(1u64) }; } + + #[test] + const fn ai_const_expr_tests() { + let _ = const { AreaIndex::new(1) }; + let _ = const { area_index!(1) }; + } } diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index c17f82e4e728..70ba78bff308 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -27,7 +27,8 @@ use bytemuck_derive::{Pod, Zeroable}; use std::io::{Error, ErrorKind}; -use super::alloc::{FreeLists, LinearAddress, area_size_hash}; +use super::alloc::FreeLists; +use super::primitives::{LinearAddress, area_size_hash}; use crate::logger::{debug, trace}; /// Can be used by filesystem tooling such as "file" to identify diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 156fe99f1ebb..624bd87e1528 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -42,6 +42,7 @@ pub(crate) mod alloc; pub(crate) mod hash; pub(crate) mod header; pub(crate) mod persist; +pub(crate) mod primitives; use crate::firewood_gauge; use crate::linear::OffsetReader; @@ -55,7 +56,8 @@ use std::io::{Error, ErrorKind, Read}; use std::sync::atomic::AtomicUsize; // Re-export types from alloc module -pub use alloc::{AreaIndex, LinearAddress, NodeAllocator}; +pub use alloc::NodeAllocator; +pub use primitives::{AreaIndex, LinearAddress}; // Re-export types from header module pub use header::NodeStoreHeader; @@ -89,7 +91,6 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::Node; use crate::node::persist::MaybePersistedNode; -use crate::nodestore::alloc::AREA_SIZES; use crate::{CacheReadStrategy, FileIoError, Path, ReadableStorage, SharedNode, TrieHash}; use super::linear::WritableStorage; @@ -670,6 +671,33 @@ where } } +// TODO: return only the index since we can easily get the size from the index +fn area_index_and_size( + storage: &S, + addr: LinearAddress, +) -> Result<(AreaIndex, u64), FileIoError> { + let mut area_stream = storage.stream_from(addr.get())?; + + let index: AreaIndex = AreaIndex::new(area_stream.read_byte().map_err(|e| { + storage.file_io_error( + Error::new(ErrorKind::InvalidData, e), + addr.get(), + Some("area_index_and_size".to_string()), + ) + })?) + .ok_or_else(|| { + storage.file_io_error( + Error::new(ErrorKind::InvalidData, "invalid area index"), + addr.get(), + Some("area_index_and_size".to_string()), + ) + })?; + + let size = index.size(); + + Ok((index, size)) +} + impl NodeStore { /// Read a [Node] from the provided [`LinearAddress`]. /// `addr` is the address of a `StoredArea` in the `ReadableStorage`. @@ -746,25 +774,7 @@ impl NodeStore { &self, addr: LinearAddress, ) -> Result<(AreaIndex, u64), FileIoError> { - let mut area_stream = self.storage.stream_from(addr.get())?; - - let index: AreaIndex = area_stream.read_byte().map_err(|e| { - self.storage.file_io_error( - Error::new(ErrorKind::InvalidData, e), - addr.get(), - Some("area_index_and_size".to_string()), - ) - })?; - - let size = *AREA_SIZES - .get(index as usize) - .ok_or(self.storage.file_io_error( - Error::other(format!("Invalid area size index {index}")), - addr.get(), - Some("area_index_and_size".to_string()), - ))?; - - Ok((index, size)) + area_index_and_size(self.storage.as_ref(), addr) } pub(crate) fn physical_size(&self) -> Result { @@ -847,41 +857,41 @@ mod tests { use arc_swap::access::DynGuard; use super::*; - use alloc::{AREA_SIZES, area_size_to_index}; + use primitives::area_size_iter; #[test] fn area_sizes_aligned() { - for area_size in &AREA_SIZES { - assert_eq!(area_size % LinearAddress::MIN_AREA_SIZE, 0); + for (_, area_size) in area_size_iter() { + assert_eq!(area_size % AreaIndex::MIN_AREA_SIZE, 0); } } #[test] fn test_area_size_to_index() { // TODO: rustify using: for size in AREA_SIZES - for (i, &area_size) in AREA_SIZES.iter().enumerate() { + for (i, area_size) in area_size_iter() { // area size is at top of range - assert_eq!(area_size_to_index(area_size).unwrap(), i as AreaIndex); + assert_eq!(AreaIndex::from_size(area_size).unwrap(), i); - if i > 0 { + if i > AreaIndex::MIN { // 1 less than top of range stays in range - assert_eq!(area_size_to_index(area_size - 1).unwrap(), i as AreaIndex); + assert_eq!(AreaIndex::from_size(area_size - 1).unwrap(), i); } - if i < LinearAddress::num_area_sizes() - 1 { + if i < AreaIndex::MAX { // 1 more than top of range goes to next range assert_eq!( - area_size_to_index(area_size + 1).unwrap(), - (i + 1) as AreaIndex + AreaIndex::from_size(area_size + 1).unwrap(), + AreaIndex::try_from(i.as_usize() + 1).unwrap() ); } } - for i in 0..=LinearAddress::MIN_AREA_SIZE { - assert_eq!(area_size_to_index(i).unwrap(), 0); + for i in 0..=AreaIndex::MIN_AREA_SIZE { + assert_eq!(AreaIndex::from_size(i).unwrap(), AreaIndex::MIN); } - assert!(area_size_to_index(LinearAddress::MAX_AREA_SIZE + 1).is_err()); + assert!(AreaIndex::from_size(AreaIndex::MAX_AREA_SIZE + 1).is_err()); } #[test] @@ -922,7 +932,7 @@ mod tests { let memstore = MemStore::new(vec![]); let mut node_store = NodeStore::new_empty_proposal(memstore.into()); - let huge_value = vec![0u8; *AREA_SIZES.last().unwrap() as usize]; + let huge_value = vec![0u8; AreaIndex::MAX_AREA_SIZE as usize]; let giant_leaf = Node::Leaf(LeafNode { partial_path: Path::from([0, 1, 2]), diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index ba4de339633c..464ab1072e2b 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -32,6 +32,7 @@ use std::iter::FusedIterator; use crate::linear::FileIoError; +use crate::nodestore::AreaIndex; use crate::{firewood_counter, firewood_gauge}; use coarsetime::Instant; @@ -250,11 +251,11 @@ impl NodeStore { for node in UnPersistedNodeIterator::new(self) { let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); let mut serialized = Vec::new(); - shared_node.as_bytes(0, &mut serialized); + shared_node.as_bytes(AreaIndex::MIN, &mut serialized); let (persisted_address, area_size_index) = allocator.allocate_node(serialized.as_slice())?; - *serialized.get_mut(0).expect("byte was reserved") = area_size_index; + *serialized.get_mut(0).expect("byte was reserved") = area_size_index.get(); self.storage .write(persisted_address.get(), serialized.as_slice())?; @@ -396,10 +397,10 @@ impl NodeStore { for node in UnPersistedNodeIterator::new(self) { let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger - shared_node.as_bytes(0, &mut serialized); + shared_node.as_bytes(AreaIndex::MIN, &mut serialized); let (persisted_address, area_size_index) = node_allocator.allocate_node(serialized.as_slice())?; - *serialized.get_mut(0).expect("byte was reserved") = area_size_index; + *serialized.get_mut(0).expect("byte was reserved") = area_size_index.get(); let mut serialized = serialized.into_boxed_slice(); loop { diff --git a/storage/src/nodestore/primitives.rs b/storage/src/nodestore/primitives.rs new file mode 100644 index 000000000000..f2b3cbd7c6fd --- /dev/null +++ b/storage/src/nodestore/primitives.rs @@ -0,0 +1,355 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # Primitives Module +//! +//! This module contains the primitives for the nodestore, including a list of valid +//! area sizes, `AreaIndex` that uniquely identifies a valid area size, and +//! `LinearAddress` that points to a specific location in the linear storage space. + +use crate::TrieHash; + +use sha2::{Digest, Sha256}; +use std::fmt; +use std::io::{Error, ErrorKind}; +use std::num::NonZeroU64; + +/// [`super::NodeStore`] divides the linear store into blocks of different sizes. +/// [`AREA_SIZES`] is every valid block size. +const AREA_SIZES: [u64; 23] = [ + 16, // Min block size + 32, + 64, + 96, + 128, + 256, + 512, + 768, + 1024, + 1024 << 1, + 1024 << 2, + 1024 << 3, + 1024 << 4, + 1024 << 5, + 1024 << 6, + 1024 << 7, + 1024 << 8, + 1024 << 9, + 1024 << 10, + 1024 << 11, + 1024 << 12, + 1024 << 13, + 1024 << 14, +]; + +/// Returns an iterator over all valid area sizes. +// TODO: return a named iterator +pub fn area_size_iter() -> impl DoubleEndedIterator { + AREA_SIZES + .iter() + .enumerate() + .map(|(i, &size)| (AreaIndex(i as u8), size)) +} + +pub fn area_size_hash() -> TrieHash { + let mut hasher = Sha256::new(); + for size in AREA_SIZES { + hasher.update(size.to_ne_bytes()); + } + hasher.finalize().into() +} + +// TODO: automate this, must stay in sync with above +pub const fn index_name(index: AreaIndex) -> &'static str { + match index.get() { + 0 => "16", + 1 => "32", + 2 => "64", + 3 => "96", + 4 => "128", + 5 => "256", + 6 => "512", + 7 => "768", + 8 => "1024", + 9 => "2048", + 10 => "4096", + 11 => "8192", + 12 => "16384", + 13 => "32768", + 14 => "65536", + 15 => "131072", + 16 => "262144", + 17 => "524288", + 18 => "1048576", + 19 => "2097152", + 20 => "4194304", + 21 => "8388608", + 22 => "16777216", + _ => "unknown", + } +} + +/// The type that uniquely identifies a valid area size. +/// This is not usize because we store this as a single byte +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct AreaIndex(u8); + +impl AreaIndex { + /// The number of different area sizes available. + pub const NUM_AREA_SIZES: usize = AREA_SIZES.len(); + + /// The minimum area index (0). + pub const MIN: AreaIndex = AreaIndex(0); + + /// The maximum area index (22). + pub const MAX: AreaIndex = AreaIndex(Self::NUM_AREA_SIZES as u8 - 1); + + /// The minimum area size available for allocation. + pub const MIN_AREA_SIZE: u64 = AREA_SIZES[0]; + + /// The maximum area size available for allocation. + pub const MAX_AREA_SIZE: u64 = AREA_SIZES[Self::NUM_AREA_SIZES - 1]; + + /// Create a new `AreaIndex` from a u8 value, returns None if value is out of range. + #[inline] + #[must_use] + pub const fn new(index: u8) -> Option { + if index < Self::NUM_AREA_SIZES as u8 { + Some(AreaIndex(index)) + } else { + None + } + } + + /// Create an `AreaIndex` from a size in bytes. + /// Returns the index of the smallest area size >= `n`. + /// + /// # Errors + /// + /// Returns an error if the size is too large. + pub fn from_size(n: u64) -> Result { + if n > Self::MAX_AREA_SIZE { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Node size {n} is too large"), + )); + } + + if n <= Self::MIN_AREA_SIZE { + return Ok(AreaIndex(0)); + } + + AREA_SIZES + .iter() + .position(|&size| size >= n) + .map(|index| AreaIndex(index as u8)) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!("Node size {n} is too large"), + ) + }) + } + + /// Get the underlying index as u8. + #[inline] + #[must_use] + pub const fn get(self) -> u8 { + self.0 + } + + /// Get the underlying index as usize. + #[inline] + #[must_use] + pub const fn as_usize(self) -> usize { + self.0 as usize + } + + /// Returns the number of different area sizes available. + #[inline] + #[must_use] + pub const fn num_area_sizes() -> usize { + Self::NUM_AREA_SIZES + } + + /// Create an `AreaIndex` from a u8 value without bounds checking. + #[inline] + #[must_use] + #[cfg(test)] + pub const fn from_u8_unchecked(index: u8) -> Self { + AreaIndex(index) + } + + /// Get the size of an area index (used by the checker) + /// + /// # Panics + /// + /// Panics if `index` is out of bounds for the `AREA_SIZES` array. + #[must_use] + pub const fn size(self) -> u64 { + #[expect(clippy::indexing_slicing)] + AREA_SIZES[self.as_usize()] + } +} + +impl TryFrom for AreaIndex { + type Error = Error; + + fn try_from(index: u8) -> Result { + AreaIndex::new(index).ok_or_else(|| { + Error::new( + ErrorKind::InvalidData, + format!("Area index out of bounds: {index}"), + ) + }) + } +} + +impl From for u8 { + fn from(area_index: AreaIndex) -> Self { + area_index.get() + } +} + +impl TryFrom for AreaIndex { + type Error = Error; + + fn try_from(index: usize) -> Result { + let index_u8: Result = index.try_into(); + index_u8.map(AreaIndex).map_err(|_| { + Error::new( + ErrorKind::InvalidData, + format!("Area index out of bounds: {index}"), + ) + }) + } +} + +impl From for usize { + fn from(area_index: AreaIndex) -> Self { + area_index.as_usize() + } +} + +impl fmt::Display for AreaIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(&self.get(), f) + } +} + +/// A linear address in the nodestore storage. +/// +/// This represents a non-zero address in the linear storage space. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct LinearAddress(NonZeroU64); + +#[expect(unsafe_code)] +// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `ZeroableInOption`. +unsafe impl bytemuck::ZeroableInOption for LinearAddress {} +#[expect(unsafe_code)] +// SAFETY: `LinearAddress` is a wrapper around `NonZeroU64` which is also `PodInOption`. +unsafe impl bytemuck::PodInOption for LinearAddress {} + +impl LinearAddress { + /// Create a new `LinearAddress`, returns None if value is zero. + #[inline] + #[must_use] + pub const fn new(addr: u64) -> Option { + match NonZeroU64::new(addr) { + Some(addr) => Some(LinearAddress(addr)), + None => None, + } + } + + /// Get the underlying address as u64. + #[inline] + #[must_use] + pub const fn get(self) -> u64 { + self.0.get() + } + + /// Check if the address is 8-byte aligned. + #[inline] + #[must_use] + pub const fn is_aligned(self) -> bool { + self.0.get() % (Self::MIN_AREA_SIZE) == 0 + } + + /// The maximum area size available for allocation. + pub const MAX_AREA_SIZE: u64 = *AREA_SIZES.last().unwrap(); + + /// The minimum area size available for allocation. + pub const MIN_AREA_SIZE: u64 = *AREA_SIZES.first().unwrap(); + + /// Returns the number of different area sizes available. + #[inline] + #[must_use] + pub const fn num_area_sizes() -> usize { + const { AREA_SIZES.len() } + } + + /// Returns the inner `NonZeroU64` + #[inline] + #[must_use] + pub const fn into_nonzero(self) -> NonZeroU64 { + self.0 + } + + /// Advances a `LinearAddress` by `n` bytes. + /// + /// Returns `None` if the result overflows a u64 + /// Some(LinearAddress) otherwise + /// + #[inline] + #[must_use] + pub const fn advance(self, n: u64) -> Option { + match self.0.checked_add(n) { + // overflowed + None => None, + + // It is impossible to add a non-zero positive number to a u64 and get 0 without + // overflowing, so we don't check for that here, and panic instead. + Some(sum) => Some(LinearAddress(sum)), + } + } + + /// Returns the number of bytes between `other` and `self` if `other` is less than or equal to `self`. + /// Otherwise, returns `None`. + #[inline] + #[must_use] + pub const fn distance_from(self, other: Self) -> Option { + self.0.get().checked_sub(other.0.get()) + } +} + +impl std::ops::Deref for LinearAddress { + type Target = NonZeroU64; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for LinearAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(&self.get(), f) + } +} + +impl fmt::LowerHex for LinearAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::LowerHex::fmt(&self.get(), f) + } +} +impl From for u64 { + fn from(addr: LinearAddress) -> Self { + addr.get() + } +} + +impl From for LinearAddress { + fn from(addr: NonZeroU64) -> Self { + LinearAddress(addr) + } +} From 53c58206041a9fb33a6889e0a85a3416ebb4d130 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Mon, 11 Aug 2025 23:28:16 -0500 Subject: [PATCH 0895/1053] feat(checker): do not return physical size to accomodate raw disks (#1200) Currently checker also obtains the actual file size from the filesystem for statistics, which is not reliable on raw disks. Using high watermark instead. --- fwdctl/src/check.rs | 4 ++-- storage/src/checker/mod.rs | 21 +++------------------ storage/src/lib.rs | 2 +- storage/src/nodestore/mod.rs | 16 +--------------- 4 files changed, 7 insertions(+), 36 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index e0909cc92fdb..320bbf917713 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -75,8 +75,8 @@ fn print_checker_report(report: CheckerReport) { println!( "\tStorage Space Utilization: {} / {} = {:.2}%", report.trie_stats.trie_bytes, - report.physical_bytes, - (report.trie_stats.trie_bytes as f64 / report.physical_bytes as f64) * 100.0 + report.high_watermark, + (report.trie_stats.trie_bytes as f64 / report.high_watermark as f64) * 100.0 ); println!( "\tInternal Fragmentation: {} / {total_trie_area_bytes} = {:.2}%", diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 55ddfc9fa93e..f1ef655be872 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -64,8 +64,6 @@ pub struct CheckerReport { pub errors: Vec, /// The high watermark of the database pub high_watermark: u64, - /// The physical number of bytes in the database returned through `stat` - pub physical_bytes: u64, /// Statistics about the trie pub trie_stats: TrieStats, /// Statistics about the free list @@ -131,7 +129,6 @@ impl NodeStore { return CheckerReport { errors: vec![e], high_watermark: db_size, - physical_bytes: 0, trie_stats: TrieStats::default(), free_list_stats: FreeListsStats::default(), }; @@ -189,21 +186,9 @@ impl NodeStore { errors.push(CheckerError::AreaLeaks(leaked_ranges)); } - let physical_bytes = match self.physical_size() { - Ok(physical_bytes) => physical_bytes, - Err(e) => { - errors.push(CheckerError::IO { - error: e, - parent: None, - }); - 0 - } - }; - CheckerReport { errors, high_watermark: db_size, - physical_bytes, trie_stats, free_list_stats, } @@ -263,7 +248,7 @@ impl NodeStore { .map_err(|e| { vec![CheckerError::IO { error: e, - parent: Some(StoredAreaParent::TrieNode(parent)), + parent: StoredAreaParent::TrieNode(parent), }] })?; let (node, node_bytes) = self @@ -271,7 +256,7 @@ impl NodeStore { .map_err(|e| { vec![CheckerError::IO { error: e, - parent: Some(StoredAreaParent::TrieNode(parent)), + parent: StoredAreaParent::TrieNode(parent), }] })?; @@ -430,7 +415,7 @@ impl NodeStore { Err(e) => { errors.push(CheckerError::IO { error: e, - parent: Some(StoredAreaParent::FreeList(parent)), + parent: StoredAreaParent::FreeList(parent), }); free_list_iter.move_to_next_free_list(); continue; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 921dea16998b..c1e08f429383 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -284,7 +284,7 @@ pub enum CheckerError { /// The error error: FileIoError, /// parent of the area - parent: Option, + parent: StoredAreaParent, }, } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 624bd87e1528..e0ebe3fd5763 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -755,7 +755,7 @@ impl NodeStore { .offset() .checked_sub(offset_before) .ok_or_else(|| { - self.file_io_error( + self.storage.file_io_error( Error::other("Reader offset went backwards"), actual_addr, Some("read_node_with_num_bytes_from_disk".to_string()), @@ -776,20 +776,6 @@ impl NodeStore { ) -> Result<(AreaIndex, u64), FileIoError> { area_index_and_size(self.storage.as_ref(), addr) } - - pub(crate) fn physical_size(&self) -> Result { - self.storage.size() - } - - #[cold] - pub(crate) fn file_io_error( - &self, - error: Error, - addr: u64, - context: Option, - ) -> FileIoError { - self.storage.file_io_error(error, addr, context) - } } impl HashedNodeReader for Arc From 07c45a6599f8cb0860cf91bb648d635a521e6c6c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 12 Aug 2025 09:39:51 -0700 Subject: [PATCH 0896/1053] ci: remove setup-protoc (#1203) CI for this step keeps failing and we no longer need it in our CI. So, the solution is to remove it. The CI failures appear to be rate-limit related. If we need to add protoc back, we should hard code the specific version to avoid this problem. --- .github/workflows/attach-static-libs.yaml | 9 +++---- .github/workflows/ci.yaml | 30 --------------------- .github/workflows/default-branch-cache.yaml | 3 --- .github/workflows/gh-pages.yaml | 3 --- 4 files changed, 3 insertions(+), 42 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index f0d1632a713c..01e3c8e98986 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -29,7 +29,7 @@ concurrency: # - x86_64-apple-darwin # - aarch64-apple-darwin jobs: - # Build the static libraries for each target architecture and upload + # Build the static libraries for each target architecture and upload # them as artifacts to collect and attach in the next job. build-firewood-ffi-libs: runs-on: ${{ matrix.os }} @@ -51,9 +51,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 - name: Run pre-build command @@ -62,7 +59,7 @@ jobs: - name: Build for ${{ matrix.target }} run: cargo build --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi - + - name: Upload binary uses: actions/upload-artifact@v4 with: @@ -156,7 +153,7 @@ jobs: git add . git commit -m "firewood ci ${{ github.sha }}: attach firewood static libs" git push -u origin ${{ steps.determine_branch.outputs.target_branch }} --force - + if [[ "${{ github.ref_type }}" == "tag" ]]; then # If the tag is a semantic version, prefix it with "ffi/" to ensure go get correctly # fetches the submodule. Otherwise, use the tag name as is. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e721145b1b4e..5c16521c63d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,9 +38,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: shared-key: ${{ matrix.profile-key }} @@ -84,9 +81,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -115,9 +109,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -145,9 +136,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -175,9 +163,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -193,9 +178,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -220,9 +202,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -251,9 +230,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -286,9 +262,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -317,9 +290,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 99de27d76e06..9d5cede1c1a7 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -17,9 +17,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 with: save-if: "false" diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index ad5e073c67da..b767da48d177 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -15,9 +15,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} # caution: this is the same restore as in ci.yaml - uses: Swatinem/rust-cache@v2 with: From 7e08a95b648e8792e84372cc2e49720669842a29 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 12 Aug 2025 10:00:25 -0700 Subject: [PATCH 0897/1053] ci: automatically label PRs from external contributors (#1195) This checks if the pull request is from a fork and not whether or not the PR is from a member of the org; there is no easy way for us to do that via the API. [`pull_request_target`](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target) allows the workflow to run on forks and also access the github API. But, caveat is that it does not run on _this_ pull request. example run: https://github.com/ava-labs/firewood/actions/runs/16842254097/job/47715616335?pr=1195 --- .github/workflows/label-pull-requests.yaml | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/label-pull-requests.yaml diff --git a/.github/workflows/label-pull-requests.yaml b/.github/workflows/label-pull-requests.yaml new file mode 100644 index 000000000000..900fa9f280e9 --- /dev/null +++ b/.github/workflows/label-pull-requests.yaml @@ -0,0 +1,52 @@ +name: Label pull requests + +on: pull_request_target + +jobs: + add_label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Check if pull request is from a fork + id: check_is_form + run: | + if [[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + echo "is_fork=true" >> "$GITHUB_OUTPUT" + else + echo "is_fork=false" >> "$GITHUB_OUTPUT" + fi + + - name: Add label to third-party pull request + uses: actions/github-script@v7 + with: + script: | + const label = '3rd party contributor'; + const isFork = ${{ steps.check_is_form.outputs.is_fork }}; + const { data: issue } = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const isLabeled = issue.labels.some(l => l.name === label); + // add the label if a fork (and not already labeled), + // remove if not a fork and is labeled + if (isFork && !isLabeled) { + console.log(`Adding label: ${label}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [label], + }); + } else if (!isFork && isLabeled) { + console.log(`Removing label: ${label}`); + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: label, + }); + } + console.log(`Label ${isFork ? 'added' : 'removed'}: ${label}`); From 8114f7e09370ddcf4c3659550cd6ab2e1b1bbf34 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 12 Aug 2025 10:10:14 -0700 Subject: [PATCH 0898/1053] ci: don't fail fast on certain jobs (#1198) It makes it easier to see all the failures in one go. --- .github/workflows/ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5c16521c63d5..feef2585b7f5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,8 @@ jobs: # the matrix here and later jobs must re-specify the full matrix, a subset, or declare a single # shared-key to read the cache. strategy: + # do not cancel concurrent jobs if one fails for exhaustive failure reasons + fail-fast: false matrix: include: - profile-key: debug-no-default-features @@ -94,6 +96,8 @@ jobs: rust-lint: needs: build strategy: + # do not cancel concurrent jobs if one fails for exhaustive failure reasons + fail-fast: false matrix: include: - profile-key: debug-no-default-features @@ -119,6 +123,8 @@ jobs: test: needs: build strategy: + # do not cancel concurrent jobs if one fails for exhaustive failure reasons + fail-fast: false matrix: include: - profile-key: debug-no-default-features From e9d3d7b0dcee00700da1b2e2d18cc290865d6b59 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 12 Aug 2025 10:21:20 -0700 Subject: [PATCH 0899/1053] feat(ffi): add BorrowedBytes type (#1174) Add a `BorrowedSlice<_>` abstraction to provide a more ergonomic method of providing sliced data to Rust from Go. Go and Rust are similar in that they use ptr/len slices for all slice types, not just byte slices. Additionaly, Go and Rust represent strings as byte slices, meaning the conversion to and from CStrings is unnecessary. A future change will include a `OwnedSlice<_>` type that Rust will provide back to Go callers for the same purpose, allowing Go to take ownership of the data that Rust returns. This will complete an abstraction over borrowed and owned slices that can be used for future FFI work. --- ffi/Cargo.toml | 5 + ffi/firewood.go | 58 +++++----- ffi/firewood.h | 126 +++++++++++++++++----- ffi/memory.go | 128 ++++++++++++++-------- ffi/metrics.go | 19 ++-- ffi/proposal.go | 23 ++-- ffi/revision.go | 23 ++-- ffi/src/lib.rs | 202 +++++++++++++++-------------------- ffi/src/value.rs | 10 ++ ffi/src/value/borrowed.rs | 161 ++++++++++++++++++++++++++++ ffi/src/value/display_hex.rs | 78 ++++++++++++++ ffi/src/value/kvp.rs | 54 ++++++++++ 12 files changed, 642 insertions(+), 245 deletions(-) create mode 100644 ffi/src/value.rs create mode 100644 ffi/src/value/borrowed.rs create mode 100644 ffi/src/value/display_hex.rs create mode 100644 ffi/src/value/kvp.rs diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index bf8431ef09f9..73963a581342 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -35,6 +35,11 @@ env_logger = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" +[dev-dependencies] +# Workspace dependencies +firewood-storage.workspace = true +test-case.workspace = true + [features] logger = ["dep:env_logger", "firewood/logger"] ethhash = ["firewood/ethhash"] diff --git a/ffi/firewood.go b/ffi/firewood.go index fe54cc9391cb..b45e7baecf5d 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -29,8 +29,8 @@ import ( "bytes" "errors" "fmt" + "runtime" "strings" - "unsafe" ) // These constants are used to identify errors returned by the Firewood Rust FFI. @@ -105,17 +105,17 @@ func New(filePath string, conf *Config) (*Database, error) { return nil, fmt.Errorf("%T.FreeListCacheEntries must be >= 1", conf) } + var pinner runtime.Pinner + defer pinner.Unpin() + args := C.struct_CreateOrOpenArgs{ - path: C.CString(filePath), + path: newBorrowedBytes([]byte(filePath), &pinner), cache_size: C.size_t(conf.NodeCacheEntries), free_list_cache_size: C.size_t(conf.FreeListCacheEntries), revisions: C.size_t(conf.Revisions), strategy: C.uint8_t(conf.ReadCacheStrategy), truncate: C.bool(conf.Truncate), } - // Defer freeing the C string allocated to the heap on the other side - // of the FFI boundary. - defer C.free(unsafe.Pointer(args.path)) dbResult := C.fwd_open_db(args) db, err := databaseFromResult(&dbResult) @@ -136,14 +136,15 @@ func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { return nil, errDBClosed } - ffiOps, cleanup := createOps(keys, vals) - defer cleanup() + var pinner runtime.Pinner + defer pinner.Unpin() - hash := C.fwd_batch( - db.handle, - C.size_t(len(ffiOps)), - unsafe.SliceData(ffiOps), // implicitly pinned - ) + kvp, err := newKeyValuePairs(keys, vals, &pinner) + if err != nil { + return nil, err + } + + hash := C.fwd_batch(db.handle, kvp) return bytesFromValue(&hash) } @@ -152,14 +153,15 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, errDBClosed } - ffiOps, cleanup := createOps(keys, vals) - defer cleanup() + var pinner runtime.Pinner + defer pinner.Unpin() - val := C.fwd_propose_on_db( - db.handle, - C.size_t(len(ffiOps)), - unsafe.SliceData(ffiOps), // implicitly pinned - ) + kvp, err := newKeyValuePairs(keys, vals, &pinner) + if err != nil { + return nil, err + } + + val := C.fwd_propose_on_db(db.handle, kvp) return newProposal(db.handle, &val) } @@ -170,9 +172,10 @@ func (db *Database) Get(key []byte) ([]byte, error) { return nil, errDBClosed } - values, cleanup := newValueFactory() - defer cleanup() - val := C.fwd_get_latest(db.handle, values.from(key)) + var pinner runtime.Pinner + defer pinner.Unpin() + + val := C.fwd_get_latest(db.handle, newBorrowedBytes(key, &pinner)) bytes, err := bytesFromValue(&val) // If the root hash is not found, return nil. @@ -196,9 +199,14 @@ func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { return nil, nil } - values, cleanup := newValueFactory() - defer cleanup() - val := C.fwd_get_from_root(db.handle, values.from(root), values.from(key)) + var pinner runtime.Pinner + defer pinner.Unpin() + + val := C.fwd_get_from_root( + db.handle, + newBorrowedBytes(root, &pinner), + newBorrowedBytes(key, &pinner), + ) return bytesFromValue(&val) } diff --git a/ffi/firewood.h b/ffi/firewood.h index 5ce787d49521..458aab3271ec 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -39,13 +39,89 @@ typedef struct Value { uint8_t *data; } Value; +/** + * A borrowed byte slice. Used to represent data that was passed in from C + * callers and will not be freed or retained by Rust code. + */ +typedef struct BorrowedSlice_u8 { + /** + * A pointer to the slice of bytes. This can be null if the slice is empty. + * + * If the pointer is not null, it must point to a valid slice of `len` + * elements sized and aligned for `T`. + * + * As a note, [`NonNull`] is not appropriate here because [`NonNull`] pointer + * provenance requires mutable access to the pointer, which is not an invariant + * we want to enforce here. We want (and require) the pointer to be immutable. + * + * [`NonNull`]: std::ptr::NonNull + */ + const uint8_t *ptr; + /** + * The length of the slice. It is ignored if the pointer is null; however, + * if the pointer is not null, it must be equal to the number of elements + * pointed to by `ptr`. + */ + size_t len; +} BorrowedSlice_u8; + +/** + * A type alias for a borrowed byte slice. + * + * C callers can use this to pass in a byte slice that will not be freed by Rust + * code. + * + * C callers must ensure that the pointer, if not null, points to a valid slice + * of bytes of length `len`. C callers must also ensure that the slice is valid + * for the duration of the C function call that was passed this slice. + */ +typedef struct BorrowedSlice_u8 BorrowedBytes; + /** * A `KeyValue` represents a key-value pair, passed to the FFI. */ -typedef struct KeyValue { - struct Value key; - struct Value value; -} KeyValue; +typedef struct KeyValuePair { + BorrowedBytes key; + BorrowedBytes value; +} KeyValuePair; + +/** + * A borrowed byte slice. Used to represent data that was passed in from C + * callers and will not be freed or retained by Rust code. + */ +typedef struct BorrowedSlice_KeyValuePair { + /** + * A pointer to the slice of bytes. This can be null if the slice is empty. + * + * If the pointer is not null, it must point to a valid slice of `len` + * elements sized and aligned for `T`. + * + * As a note, [`NonNull`] is not appropriate here because [`NonNull`] pointer + * provenance requires mutable access to the pointer, which is not an invariant + * we want to enforce here. We want (and require) the pointer to be immutable. + * + * [`NonNull`]: std::ptr::NonNull + */ + const struct KeyValuePair *ptr; + /** + * The length of the slice. It is ignored if the pointer is null; however, + * if the pointer is not null, it must be equal to the number of elements + * pointed to by `ptr`. + */ + size_t len; +} BorrowedSlice_KeyValuePair; + +/** + * A type alias for a borrowed slice of [`KeyValuePair`]s. + * + * C callers can use this to pass in a slice of key-value pairs that will not + * be freed by Rust code. + * + * C callers must ensure that the pointer, if not null, points to a valid slice + * of key-value pairs of length `len`. C callers must also ensure that the slice + * is valid for the duration of the C function call that was passed this slice. + */ +typedef struct BorrowedSlice_KeyValuePair BorrowedKeyValuePairs; /** * Struct returned by `fwd_create_db` and `fwd_open_db` @@ -71,7 +147,7 @@ typedef uint32_t ProposalId; * Returns an error if the value is not 0, 1, or 2. */ typedef struct CreateOrOpenArgs { - const char *path; + BorrowedBytes path; size_t cache_size; size_t free_list_cache_size; size_t revisions; @@ -87,8 +163,8 @@ typedef struct CreateOrOpenArgs { * * `filter_level` - The filter level for logs. By default, this is set to info. */ typedef struct LogArgs { - const char *path; - const char *filter_level; + BorrowedBytes path; + BorrowedBytes filter_level; } LogArgs; /** @@ -97,8 +173,7 @@ typedef struct LogArgs { * # Arguments * * * `db` - The database handle returned by `open_db` - * * `nkeys` - The number of key-value pairs to put - * * `values` - A pointer to an array of `KeyValue` structs + * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. * * # Returns * @@ -122,8 +197,7 @@ typedef struct LogArgs { * */ struct Value fwd_batch(const struct DatabaseHandle *db, - size_t nkeys, - const struct KeyValue *values); + BorrowedKeyValuePairs values); /** * Close and free the memory for a database handle @@ -240,7 +314,7 @@ struct Value fwd_gather(void); * * * `db` - The database handle returned by `open_db` * * `id` - The ID of the proposal to get the value from - * * `key` - The key to look up, in `Value` form + * * `key` - The key to look up, in `BorrowedBytes` form * * # Returns * @@ -257,7 +331,7 @@ struct Value fwd_gather(void); */ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, ProposalId id, - struct Value key); + BorrowedBytes key); /** * Gets a value assoicated with the given root hash and key. @@ -267,8 +341,8 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * # Arguments * * * `db` - The database handle returned by `open_db` - * * `root` - The root hash to look up, in `Value` form - * * `key` - The key to look up, in `Value` form + * * `root` - The root hash to look up, in `BorrowedBytes` form + * * `key` - The key to look up, in `BorrowedBytes` form * * # Returns * @@ -285,8 +359,8 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * */ struct Value fwd_get_from_root(const struct DatabaseHandle *db, - struct Value root, - struct Value key); + BorrowedBytes root, + BorrowedBytes key); /** * Gets the value associated with the given key from the database. @@ -294,7 +368,7 @@ struct Value fwd_get_from_root(const struct DatabaseHandle *db, * # Arguments * * * `db` - The database handle returned by `open_db` - * * `key` - The key to look up, in `Value` form + * * `key` - The key to look up, in `BorrowedBytes` form * * # Returns * @@ -312,7 +386,7 @@ struct Value fwd_get_from_root(const struct DatabaseHandle *db, * * call `free_value` to free the memory associated with the returned `Value` * */ -struct Value fwd_get_latest(const struct DatabaseHandle *db, struct Value key); +struct Value fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); /** * Open a database with the given cache size and maximum number of revisions @@ -341,8 +415,7 @@ struct DatabaseCreationResult fwd_open_db(struct CreateOrOpenArgs args); * # Arguments * * * `db` - The database handle returned by `open_db` - * * `nkeys` - The number of key-value pairs to put - * * `values` - A pointer to an array of `KeyValue` structs + * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. * * # Returns * @@ -360,8 +433,7 @@ struct DatabaseCreationResult fwd_open_db(struct CreateOrOpenArgs args); * */ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, - size_t nkeys, - const struct KeyValue *values); + BorrowedKeyValuePairs values); /** * Proposes a batch of operations to the database on top of an existing proposal. @@ -370,8 +442,7 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, * * * `db` - The database handle returned by `open_db` * * `proposal_id` - The ID of the proposal to propose on - * * `nkeys` - The number of key-value pairs to put - * * `values` - A pointer to an array of `KeyValue` structs + * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. * * # Returns * @@ -390,8 +461,7 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db, */ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, ProposalId proposal_id, - size_t nkeys, - const struct KeyValue *values); + BorrowedKeyValuePairs values); /** * Get the root hash of the latest version of the database @@ -427,7 +497,7 @@ struct Value fwd_root_hash(const struct DatabaseHandle *db); * A `Value` containing {0, null} if the global logger was initialized. * A `Value` containing {0, "error message"} if an error occurs. */ -struct Value fwd_start_logs(const struct LogArgs *args); +struct Value fwd_start_logs(struct LogArgs args); /** * Start metrics recorder for this process. diff --git a/ffi/memory.go b/ffi/memory.go index 9a0b9d21fdcc..13c40a2a0c68 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -13,19 +13,94 @@ import "C" import ( "errors" + "fmt" "runtime" "unsafe" ) var ( - errNilStruct = errors.New("nil struct pointer cannot be freed") - errBadValue = errors.New("value from cgo formatted incorrectly") + errNilStruct = errors.New("nil struct pointer cannot be freed") + errBadValue = errors.New("value from cgo formatted incorrectly") + errKeysAndValues = errors.New("keys and values must have the same length") ) -// KeyValue is a key-value pair. -type KeyValue struct { - Key []byte - Value []byte +type Pinner interface { + Pin(ptr any) + Unpin() +} + +// newBorrowedBytes creates a new BorrowedBytes from a Go byte slice. +// +// Provide a Pinner to ensure the memory is pinned while the BorrowedBytes is in use. +func newBorrowedBytes(slice []byte, pinner Pinner) C.BorrowedBytes { + sliceLen := len(slice) + if sliceLen == 0 { + return C.BorrowedBytes{ptr: nil, len: 0} + } + + ptr := unsafe.SliceData(slice) + if ptr == nil { + return C.BorrowedBytes{ptr: nil, len: 0} + } + + pinner.Pin(ptr) + + return C.BorrowedBytes{ + ptr: (*C.uint8_t)(ptr), + len: C.size_t(sliceLen), + } +} + +// newKeyValuePair creates a new KeyValuePair from Go byte slices for key and value. +// +// Provide a Pinner to ensure the memory is pinned while the KeyValuePair is in use. +func newKeyValuePair(key, value []byte, pinner Pinner) C.KeyValuePair { + return C.KeyValuePair{ + key: newBorrowedBytes(key, pinner), + value: newBorrowedBytes(value, pinner), + } +} + +// newBorrowedKeyValuePairs creates a new BorrowedKeyValuePairs from a slice of KeyValuePair. +// +// Provide a Pinner to ensure the memory is pinned while the BorrowedKeyValuePairs is +// in use. +func newBorrowedKeyValuePairs(pairs []C.KeyValuePair, pinner Pinner) C.BorrowedKeyValuePairs { + sliceLen := len(pairs) + if sliceLen == 0 { + return C.BorrowedKeyValuePairs{ptr: nil, len: 0} + } + + ptr := unsafe.SliceData(pairs) + if ptr == nil { + return C.BorrowedKeyValuePairs{ptr: nil, len: 0} + } + + pinner.Pin(ptr) + + return C.BorrowedKeyValuePairs{ + ptr: ptr, + len: C.size_t(sliceLen), + } +} + +// newKeyValuePairs creates a new BorrowedKeyValuePairs from slices of keys and values. +// +// The keys and values must have the same length. +// +// Provide a Pinner to ensure the memory is pinned while the BorrowedKeyValuePairs is +// in use. +func newKeyValuePairs(keys, vals [][]byte, pinner Pinner) (C.BorrowedKeyValuePairs, error) { + if len(keys) != len(vals) { + return C.BorrowedKeyValuePairs{}, fmt.Errorf("%w: %d != %d", errKeysAndValues, len(keys), len(vals)) + } + + pairs := make([]C.KeyValuePair, len(keys)) + for i := range keys { + pairs[i] = newKeyValuePair(keys[i], vals[i], pinner) + } + + return newBorrowedKeyValuePairs(pairs, pinner), nil } // hashAndIDFromValue converts the cgo `Value` payload into: @@ -160,44 +235,3 @@ func databaseFromResult(result *C.struct_DatabaseCreationResult) (*C.DatabaseHan } return result.db, nil } - -// newValueFactory returns a factory for converting byte slices into cgo `Value` -// structs that can be passed as arguments to cgo functions. The returned -// cleanup function MUST be called when the constructed values are no longer -// required, after which they can no longer be used as cgo arguments. -func newValueFactory() (*valueFactory, func()) { - f := new(valueFactory) - return f, func() { f.pin.Unpin() } -} - -type valueFactory struct { - pin runtime.Pinner -} - -func (f *valueFactory) from(data []byte) C.struct_Value { - if len(data) == 0 { - return C.struct_Value{0, nil} - } - ptr := (*C.uchar)(unsafe.SliceData(data)) - f.pin.Pin(ptr) - return C.struct_Value{C.size_t(len(data)), ptr} -} - -// createOps creates a slice of cgo `KeyValue` structs from the given keys and -// values and pins the memory of the underlying byte slices to prevent -// garbage collection while the cgo function is using them. The returned cleanup -// function MUST be called when the constructed values are no longer required, -// after which they can no longer be used as cgo arguments. -func createOps(keys, vals [][]byte) ([]C.struct_KeyValue, func()) { - values, cleanup := newValueFactory() - - ffiOps := make([]C.struct_KeyValue, len(keys)) - for i := range keys { - ffiOps[i] = C.struct_KeyValue{ - key: values.from(keys[i]), - value: values.from(vals[i]), - } - } - - return ffiOps, cleanup -} diff --git a/ffi/metrics.go b/ffi/metrics.go index 837a79c8f0e1..d8ace1d19e0c 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -10,8 +10,8 @@ package ffi import "C" import ( + "runtime" "strings" - "unsafe" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" @@ -85,15 +85,12 @@ type LogConfig struct { // This function only needs to be called once. // An error is returned if this method is called a second time. func StartLogs(config *LogConfig) error { - args := &C.struct_LogArgs{} - if config.Path != "" { - args.path = C.CString(config.Path) - defer C.free(unsafe.Pointer(args.path)) - } - if config.FilterLevel != "" { - args.filter_level = C.CString(config.FilterLevel) - defer C.free(unsafe.Pointer(args.filter_level)) - } - result := C.fwd_start_logs(args) + var pinner runtime.Pinner + defer pinner.Unpin() + + result := C.fwd_start_logs(C.struct_LogArgs{ + path: newBorrowedBytes([]byte(config.Path), &pinner), + filter_level: newBorrowedBytes([]byte(config.FilterLevel), &pinner), + }) return errorFromValue(&result) } diff --git a/ffi/proposal.go b/ffi/proposal.go index 1c1c62d1a8ba..431229710f78 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -12,7 +12,7 @@ import "C" import ( "errors" - "unsafe" + "runtime" ) var errDroppedProposal = errors.New("proposal already dropped") @@ -83,11 +83,12 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { if p.id == 0 { return nil, errDroppedProposal } - values, cleanup := newValueFactory() - defer cleanup() + + var pinner runtime.Pinner + defer pinner.Unpin() // Get the value for the given key. - val := C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), values.from(key)) + val := C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), newBorrowedBytes(key, &pinner)) return bytesFromValue(&val) } @@ -102,14 +103,16 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, errDroppedProposal } - ffiOps, cleanup := createOps(keys, vals) - defer cleanup() + var pinner runtime.Pinner + defer pinner.Unpin() + + kvp, err := newKeyValuePairs(keys, vals, &pinner) + if err != nil { + return nil, err + } // Propose the keys and values. - val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), - C.size_t(len(ffiOps)), - unsafe.SliceData(ffiOps), - ) + val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), kvp) return newProposal(p.handle, &val) } diff --git a/ffi/revision.go b/ffi/revision.go index 35635418249b..3c363ece1210 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -13,6 +13,7 @@ import "C" import ( "errors" "fmt" + "runtime" ) var ( @@ -39,12 +40,18 @@ func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { return nil, errInvalidRootLength } + var pinner runtime.Pinner + defer pinner.Unpin() + // Attempt to get any value from the root. // This will verify that the root is valid and accessible. // If the root is not valid, this will return an error. - values, cleanup := newValueFactory() - defer cleanup() - val := C.fwd_get_from_root(handle, values.from(root), values.from([]byte{})) + + val := C.fwd_get_from_root( + handle, + newBorrowedBytes(root, &pinner), + newBorrowedBytes(nil, &pinner), + ) _, err := bytesFromValue(&val) if err != nil { // Any error from this function indicates that the root is inaccessible. @@ -66,10 +73,14 @@ func (r *Revision) Get(key []byte) ([]byte, error) { return nil, errRevisionNotFound } - values, cleanup := newValueFactory() - defer cleanup() + var pinner runtime.Pinner + defer pinner.Unpin() - val := C.fwd_get_from_root(r.handle, values.from(r.root), values.from(key)) + val := C.fwd_get_from_root( + r.handle, + newBorrowedBytes(r.root, &pinner), + newBorrowedBytes(key, &pinner), + ) value, err := bytesFromValue(&val) if err != nil { // Any error from this function indicates that the revision is inaccessible. diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8b5092398c09..e467aa7b46ed 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,6 +1,14 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +// HINT WHEN REFERENCING TYPES OUTSIDE THIS LIBRARY: +// - Anything that is outside the crate must be included as a `type` alias (not just +// a `use`) in order for cbindgen to generate an opaque forward declaration. The type +// alias can have a doc comment which will be included in the generated header file. +// - The value must be boxed, or otherwise used via a pointer. This is because only +// a forward declaration is generated and callers will be unable to instantiate the +// type without a complete definition. + #![doc = include_str!("../README.md")] #![expect( unsafe_code, @@ -18,26 +26,23 @@ ) )] +mod metrics_setup; +mod value; + use std::collections::HashMap; -use std::ffi::{CStr, CString, OsStr, c_char}; +use std::ffi::{CStr, CString, c_char}; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt as _; -use std::path::PathBuf; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Mutex, RwLock}; -use firewood::db::{ - BatchOp as DbBatchOp, Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal, -}; +use firewood::db::{Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; use firewood::v2::api::{HashKey, KeyValuePairIter}; use metrics::counter; -#[doc(hidden)] -mod metrics_setup; +pub use crate::value::*; #[cfg(unix)] #[global_allocator] @@ -105,7 +110,7 @@ impl Deref for DatabaseHandle<'_> { /// # Arguments /// /// * `db` - The database handle returned by `open_db` -/// * `key` - The key to look up, in `Value` form +/// * `key` - The key to look up, in `BorrowedBytes` form /// /// # Returns /// @@ -123,14 +128,17 @@ impl Deref for DatabaseHandle<'_> { /// * call `free_value` to free the memory associated with the returned `Value` /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_get_latest(db: Option<&DatabaseHandle<'_>>, key: Value) -> Value { +pub unsafe extern "C" fn fwd_get_latest( + db: Option<&DatabaseHandle<'_>>, + key: BorrowedBytes<'_>, +) -> Value { get_latest(db, &key).unwrap_or_else(Into::into) } /// This function is not exposed to the C API. /// Internal call for `fwd_get_latest` to remove error handling from the C API #[doc(hidden)] -fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &Value) -> Result { +fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &[u8]) -> Result { let db = db.ok_or("db should be non-null")?; // Find root hash. // Matches `hash` function but we use the TrieHash type here @@ -143,7 +151,7 @@ fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &Value) -> Result>, key: &Value) -> Result>, key: &Value) -> Result>, id: ProposalId, - key: Value, + key: BorrowedBytes<'_>, ) -> Value { get_from_proposal(db, id, &key).unwrap_or_else(Into::into) } @@ -184,7 +192,7 @@ pub unsafe extern "C" fn fwd_get_from_proposal( fn get_from_proposal( db: Option<&DatabaseHandle<'_>>, id: ProposalId, - key: &Value, + key: &[u8], ) -> Result { let db = db.ok_or("db should be non-null")?; // Get proposal from ID. @@ -196,7 +204,7 @@ fn get_from_proposal( // Get value associated with key. let value = proposal - .val_sync(key.as_slice()) + .val_sync(key) .map_err(|e| e.to_string())? .ok_or("")?; Ok(value.into()) @@ -209,8 +217,8 @@ fn get_from_proposal( /// # Arguments /// /// * `db` - The database handle returned by `open_db` -/// * `root` - The root hash to look up, in `Value` form -/// * `key` - The key to look up, in `Value` form +/// * `root` - The root hash to look up, in `BorrowedBytes` form +/// * `key` - The key to look up, in `BorrowedBytes` form /// /// # Returns /// @@ -228,8 +236,8 @@ fn get_from_proposal( #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_root( db: Option<&DatabaseHandle<'_>>, - root: Value, - key: Value, + root: BorrowedBytes<'_>, + key: BorrowedBytes<'_>, ) -> Value { get_from_root(db, &root, &key).unwrap_or_else(Into::into) } @@ -238,24 +246,24 @@ pub unsafe extern "C" fn fwd_get_from_root( #[doc(hidden)] fn get_from_root( db: Option<&DatabaseHandle<'_>>, - root: &Value, - key: &Value, + root: &[u8], + key: &[u8], ) -> Result { let db = db.ok_or("db should be non-null")?; - let requested_root = HashKey::try_from(root.as_slice()).map_err(|e| e.to_string())?; + let requested_root = HashKey::try_from(root).map_err(|e| e.to_string())?; let mut cached_view = db.cached_view.lock().expect("cached_view lock is poisoned"); let value = match cached_view.as_ref() { // found the cached view, use it Some((root_hash, view)) if root_hash == &requested_root => { counter!("firewood.ffi.cached_view.hit").increment(1); - view.val_sync_bytes(key.as_slice()) + view.val_sync_bytes(key) } // If what was there didn't match the requested root, we need a new view, so we // update the cache _ => { counter!("firewood.ffi.cached_view.miss").increment(1); let rev = view_sync_from_root(db, root)?; - let result = rev.val_sync_bytes(key.as_slice()); + let result = rev.val_sync_bytes(key); *cached_view = Some((requested_root.clone(), rev)); result } @@ -267,28 +275,20 @@ fn get_from_root( } fn view_sync_from_root( db: &DatabaseHandle<'_>, - root: &Value, + root: &[u8], ) -> Result, String> { let rev = db - .view_sync(HashKey::try_from(root.as_slice()).map_err(|e| e.to_string())?) + .view_sync(HashKey::try_from(root).map_err(|e| e.to_string())?) .map_err(|e| e.to_string())?; Ok(rev) } -/// A `KeyValue` represents a key-value pair, passed to the FFI. -#[repr(C)] -pub struct KeyValue { - key: Value, - value: Value, -} - /// Puts the given key-value pairs into the database. /// /// # Arguments /// /// * `db` - The database handle returned by `open_db` -/// * `nkeys` - The number of key-value pairs to put -/// * `values` - A pointer to an array of `KeyValue` structs +/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. /// /// # Returns /// @@ -313,40 +313,19 @@ pub struct KeyValue { #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_batch( db: Option<&DatabaseHandle<'_>>, - nkeys: usize, - values: Option<&KeyValue>, + values: BorrowedKeyValuePairs, ) -> Value { - batch(db, nkeys, values).unwrap_or_else(Into::into) -} - -/// Converts a slice of `KeyValue` structs to a vector of `DbBatchOp` structs. -/// -/// # Arguments -/// -/// * `values` - A slice of `KeyValue` structs -/// -/// # Returns -fn convert_to_batch(values: &[KeyValue]) -> impl IntoIterator> { - values - .iter() - .map(|kv| (kv.key.as_slice(), kv.value.as_slice())) - .map_into_batch() + batch(db, &values).unwrap_or_else(Into::into) } /// Internal call for `fwd_batch` to remove error handling from the C API #[doc(hidden)] -fn batch( - db: Option<&DatabaseHandle<'_>>, - nkeys: usize, - values: Option<&KeyValue>, -) -> Result { +fn batch(db: Option<&DatabaseHandle<'_>>, values: &[KeyValuePair<'_>]) -> Result { let db = db.ok_or("db should be non-null")?; - let values = values.ok_or("key-value slice is null")?; let start = coarsetime::Instant::now(); // Create a batch of operations to perform. - let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; - let batch = convert_to_batch(key_value_ref); + let batch = values.iter().map_into_batch(); // Propose the batch of operations. let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; @@ -377,8 +356,7 @@ fn batch( /// # Arguments /// /// * `db` - The database handle returned by `open_db` -/// * `nkeys` - The number of key-value pairs to put -/// * `values` - A pointer to an array of `KeyValue` structs +/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. /// /// # Returns /// @@ -397,26 +375,22 @@ fn batch( #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_propose_on_db<'p>( db: Option<&'p DatabaseHandle<'p>>, - nkeys: usize, - values: Option<&KeyValue>, + values: BorrowedKeyValuePairs<'_>, ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_db(db, nkeys, values).unwrap_or_else(Into::into) + propose_on_db(db, &values).unwrap_or_else(Into::into) } /// Internal call for `fwd_propose_on_db` to remove error handling from the C API #[doc(hidden)] fn propose_on_db<'p>( db: Option<&'p DatabaseHandle<'p>>, - nkeys: usize, - values: Option<&KeyValue>, + values: &[KeyValuePair<'_>], ) -> Result { let db = db.ok_or("db should be non-null")?; - let values = values.ok_or("key-value slice is null")?; // Create a batch of operations to perform. - let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; - let batch = convert_to_batch(key_value_ref); + let batch = values.iter().map_into_batch(); // Propose the batch of operations. let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; @@ -443,8 +417,7 @@ fn propose_on_db<'p>( /// /// * `db` - The database handle returned by `open_db` /// * `proposal_id` - The ID of the proposal to propose on -/// * `nkeys` - The number of key-value pairs to put -/// * `values` - A pointer to an array of `KeyValue` structs +/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. /// /// # Returns /// @@ -464,12 +437,11 @@ fn propose_on_db<'p>( pub unsafe extern "C" fn fwd_propose_on_proposal( db: Option<&DatabaseHandle<'_>>, proposal_id: ProposalId, - nkeys: usize, - values: Option<&KeyValue>, + values: BorrowedKeyValuePairs<'_>, ) -> Value { // Note: the id is guaranteed to be non-zero // because we use an atomic counter that starts at 1. - propose_on_proposal(db, proposal_id, nkeys, values).unwrap_or_else(Into::into) + propose_on_proposal(db, proposal_id, &values).unwrap_or_else(Into::into) } /// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API @@ -477,14 +449,11 @@ pub unsafe extern "C" fn fwd_propose_on_proposal( fn propose_on_proposal( db: Option<&DatabaseHandle<'_>>, proposal_id: ProposalId, - nkeys: usize, - values: Option<&KeyValue>, + values: &[KeyValuePair<'_>], ) -> Result { let db = db.ok_or("db should be non-null")?; - let values = values.ok_or("key-value slice is null")?; // Create a batch of operations to perform. - let key_value_ref = unsafe { std::slice::from_raw_parts(values, nkeys) }; - let batch = convert_to_batch(key_value_ref); + let batch = values.iter().map_into_batch(); // Get proposal from ID. // We need write access to add the proposal after we create it. @@ -670,7 +639,8 @@ impl Value { #[must_use] pub const fn as_slice(&self) -> &[u8] { if let Some(data) = self.data { - // SAFETY: We assume that the data is valid and the length is correct. + // SAFETY: We must assume that if non-null, the C caller provided valid pointer + // and length, otherwise caller assumes responsibility for undefined behavior. unsafe { std::slice::from_raw_parts(data.as_ptr(), self.len) } } else { &[] @@ -868,8 +838,8 @@ pub extern "C" fn fwd_gather() -> Value { /// * `truncate` - Whether to truncate the database file if it exists. /// Returns an error if the value is not 0, 1, or 2. #[repr(C)] -pub struct CreateOrOpenArgs { - path: *const std::ffi::c_char, +pub struct CreateOrOpenArgs<'a> { + path: BorrowedBytes<'a>, cache_size: usize, free_list_cache_size: usize, revisions: usize, @@ -912,7 +882,13 @@ unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { )?) .build(); - let path = to_path(args.path).ok_or("database path is empty")?; + if args.path.is_empty() { + return Err("path should not be empty".to_string()); + } + let path = args + .path + .as_str() + .map_err(|e| format!("Invalid database path: {e}"))?; Db::new_sync(path, cfg).map_err(|e| e.to_string()) } @@ -922,9 +898,9 @@ unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { /// default, this is set to /tmp/firewood-log.txt /// * `filter_level` - The filter level for logs. By default, this is set to info. #[repr(C)] -pub struct LogArgs { - path: *const std::ffi::c_char, - filter_level: *const std::ffi::c_char, +pub struct LogArgs<'a> { + path: BorrowedBytes<'a>, + filter_level: BorrowedBytes<'a>, } /// Start logs for this process. @@ -938,11 +914,8 @@ pub struct LogArgs { /// A `Value` containing {0, null} if the global logger was initialized. /// A `Value` containing {0, "error message"} if an error occurs. #[unsafe(no_mangle)] -pub extern "C" fn fwd_start_logs(args: Option<&LogArgs>) -> Value { - match args { - Some(log_args) => start_logs(log_args).map_or_else(Into::into, Into::into), - None => String::from("failed to provide args").into(), - } +pub extern "C" fn fwd_start_logs(args: LogArgs<'_>) -> Value { + start_logs(&args).map_or_else(Into::into, Into::into) } #[cfg(feature = "logger")] @@ -952,22 +925,29 @@ fn start_logs(log_args: &LogArgs) -> Result<(), String> { use std::fs::OpenOptions; use std::path::Path; - let log_path = - to_path(log_args.path).unwrap_or(Path::join(&std::env::temp_dir(), "firewood-log.txt")); + let log_path = log_args + .path + .as_str() + .map_err(|e| format!("Invalid log path: {e}"))?; + let log_path = if log_path.is_empty() { + std::borrow::Cow::Owned(std::env::temp_dir().join("firewood-log.txt")) + } else { + std::borrow::Cow::Borrowed(std::path::Path::new(log_path)) + }; let log_dir = log_path.parent().unwrap_or_else(|| Path::new(".")); std::fs::create_dir_all(log_dir).map_err(|e| e.to_string())?; - let level_str = if log_args.filter_level.is_null() { + let level = if log_args.filter_level.is_empty() { "info" } else { - unsafe { CStr::from_ptr(log_args.filter_level) } - .to_str() - .map_err(|e| e.to_string())? - }; - let level = level_str - .parse::() - .map_err(|e| e.to_string())?; + log_args + .filter_level + .as_str() + .map_err(|e| format!("Invalid log level: {e}"))? + } + .parse::() + .map_err(|e| format!("failed to parse log level: {e}"))?; let file = OpenOptions::new() .create(true) @@ -991,20 +971,6 @@ fn start_logs(_log_args: &LogArgs) -> Result<(), String> { Err(String::from("logger feature is disabled")) } -/// Helper function to convert C String pointers to a path -/// Returns None if the pointer is null or if the string is empty -#[doc(hidden)] -fn to_path(cstr: *const std::ffi::c_char) -> Option { - if cstr.is_null() { - return None; - } - - let cstr = unsafe { CStr::from_ptr(cstr) }; - let osstr = OsStr::from_bytes(cstr.to_bytes()); - - (!osstr.is_empty()).then(|| PathBuf::from(osstr)) -} - #[doc(hidden)] fn manager_config( cache_size: usize, diff --git a/ffi/src/value.rs b/ffi/src/value.rs new file mode 100644 index 000000000000..89477c579070 --- /dev/null +++ b/ffi/src/value.rs @@ -0,0 +1,10 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +mod borrowed; +mod display_hex; +mod kvp; + +pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; +use self::display_hex::DisplayHex; +pub use self::kvp::KeyValuePair; diff --git a/ffi/src/value/borrowed.rs b/ffi/src/value/borrowed.rs new file mode 100644 index 000000000000..2050483669e7 --- /dev/null +++ b/ffi/src/value/borrowed.rs @@ -0,0 +1,161 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt; + +use crate::value::KeyValuePair; + +/// A type alias for a borrowed byte slice. +/// +/// C callers can use this to pass in a byte slice that will not be freed by Rust +/// code. +/// +/// C callers must ensure that the pointer, if not null, points to a valid slice +/// of bytes of length `len`. C callers must also ensure that the slice is valid +/// for the duration of the C function call that was passed this slice. +pub type BorrowedBytes<'a> = BorrowedSlice<'a, u8>; + +/// A type alias for a borrowed slice of [`KeyValuePair`]s. +/// +/// C callers can use this to pass in a slice of key-value pairs that will not +/// be freed by Rust code. +/// +/// C callers must ensure that the pointer, if not null, points to a valid slice +/// of key-value pairs of length `len`. C callers must also ensure that the slice +/// is valid for the duration of the C function call that was passed this slice. +pub type BorrowedKeyValuePairs<'a> = BorrowedSlice<'a, KeyValuePair<'a>>; + +/// A borrowed byte slice. Used to represent data that was passed in from C +/// callers and will not be freed or retained by Rust code. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct BorrowedSlice<'a, T> { + /// A pointer to the slice of bytes. This can be null if the slice is empty. + /// + /// If the pointer is not null, it must point to a valid slice of `len` + /// elements sized and aligned for `T`. + /// + /// As a note, [`NonNull`] is not appropriate here because [`NonNull`] pointer + /// provenance requires mutable access to the pointer, which is not an invariant + /// we want to enforce here. We want (and require) the pointer to be immutable. + /// + /// [`NonNull`]: std::ptr::NonNull + ptr: *const T, + /// The length of the slice. It is ignored if the pointer is null; however, + /// if the pointer is not null, it must be equal to the number of elements + /// pointed to by `ptr`. + len: usize, + /// This is not exposed to C callers, but is used by Rust to track the + /// lifetime of the slice passed in to C functions. + marker: std::marker::PhantomData<&'a [T]>, +} + +impl<'a, T> BorrowedSlice<'a, T> { + /// Creates a slice from the given pointer and length. + #[must_use] + pub const fn as_slice(&self) -> &'a [T] { + if self.ptr.is_null() { + &[] + } else { + // SAFETY: if the pointer is not null, we are assuming the caller has + // upheld the invariant that the pointer is valid for the length `len` + // `T` aligned elements. The phantom marker ensures that the lifetime + // of the returned slice is the same as the lifetime of the `BorrowedSlice`. + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } + } + } + + /// Creates a new `BorrowedSlice` from the given rust slice. + #[must_use] + pub const fn from_slice(slice: &'a [T]) -> Self { + let len = slice.len(); + Self { + ptr: std::ptr::from_ref(slice).cast(), + len, + marker: std::marker::PhantomData, + } + } +} + +impl std::ops::Deref for BorrowedSlice<'_, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl AsRef<[T]> for BorrowedSlice<'_, T> { + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl std::borrow::Borrow<[T]> for BorrowedSlice<'_, T> { + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl std::hash::Hash for BorrowedSlice<'_, T> { + fn hash(&self, state: &mut H) { + self.as_slice().hash(state); + } +} + +impl PartialEq for BorrowedSlice<'_, T> { + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Eq for BorrowedSlice<'_, T> {} + +impl PartialOrd for BorrowedSlice<'_, T> { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl Ord for BorrowedSlice<'_, T> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl fmt::Display for BorrowedBytes<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let precision = f.precision().unwrap_or(64); + write!(f, "{:.precision$}", super::DisplayHex(self.as_slice())) + } +} + +impl fmt::Pointer for BorrowedBytes<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.ptr.fmt(f) + } +} + +impl<'a> BorrowedBytes<'a> { + /// Creates a new [`str`] from this borrowed byte slice. + /// + /// # Errors + /// + /// If the slice is not valid UTF-8, an error is returned. + pub const fn as_str(&self) -> Result<&'a str, std::str::Utf8Error> { + // C callers are expected to pass a valid UTF-8 string for the path, even + // on Windows. Go does not handle UTF-16 paths on Windows, like Rust does, + // so we do not need to handle that here as well. + std::str::from_utf8(self.as_slice()) + } +} + +// send/sync rules for references apply here: the pointer is send and sync if and +// only if the value is sync. the value does not need to be send for the pointer +// to be send because this pointer is not moving the value across threads, only +// the reference to the value. + +// SAFETY: described above +unsafe impl Send for BorrowedSlice<'_, T> {} +// SAFETY: described above +unsafe impl Sync for BorrowedSlice<'_, T> {} diff --git a/ffi/src/value/display_hex.rs b/ffi/src/value/display_hex.rs new file mode 100644 index 000000000000..b0bb46a6cd08 --- /dev/null +++ b/ffi/src/value/display_hex.rs @@ -0,0 +1,78 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt; + +/// Implementation of `Display` for a slice that displays the bytes in hexadecimal format. +/// +/// If the `precision` is set, it will display only that many bytes in hex, +/// followed by an ellipsis and the number of remaining bytes. +pub struct DisplayHex<'a>(pub(super) &'a [u8]); + +impl fmt::Display for DisplayHex<'_> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #![expect(clippy::indexing_slicing, clippy::arithmetic_side_effects)] + + match f.precision() { + Some(p) if p < self.0.len() => { + display_hex_bytes(&self.0[..p], f)?; + f.write_fmt(format_args!("... ({} remaining bytes)", self.0.len() - p))?; + } + _ => display_hex_bytes(self.0, f)?, + } + + Ok(()) + } +} + +#[inline] +fn display_hex_bytes(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result { + const WIDTH: usize = size_of::() * 2; + + // SAFETY: it is trivially safe to transmute integer types, as long as the + // offset is aligned, which `align_to` guarantees. + let (before, aligned, after) = unsafe { bytes.align_to::() }; + + for &byte in before { + write!(f, "{byte:02x}")?; + } + + for &word in aligned { + let word = usize::from_be(word); + write!(f, "{word:0WIDTH$x}")?; + } + + for &byte in after { + write!(f, "{byte:02x}")?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "ethhash")] + use firewood::v2::api::HashKeyExt; + use test_case::test_case; + + #[test_case(&[], "", None; "empty slice")] + #[test_case(&[], "", Some(42); "empty slice with precision")] + #[test_case(b"abc", "616263", None; "short slice")] + #[test_case(b"abc", "61... (2 remaining bytes)", Some(1); "short slice with precision")] + #[test_case(b"abc", "616263", Some(16); "short slice with long precision")] + #[test_case(firewood_storage::TrieHash::empty().as_ref(), "0000000000000000000000000000000000000000000000000000000000000000", None; "empty trie hash")] + #[test_case(firewood_storage::TrieHash::empty().as_ref(), "00000000000000000000000000000000... (16 remaining bytes)", Some(16); "empty trie hash with precision")] + #[cfg_attr(feature = "ethhash", test_case(firewood_storage::TrieHash::default_root_hash().as_deref().expect("feature = \"ethhash\""), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", None; "empty rlp hash"))] + #[cfg_attr(feature = "ethhash", test_case(firewood_storage::TrieHash::default_root_hash().as_ref().expect("feature = \"ethhash\""), "56e81f171bcc55a6ff8345e692c0f86e... (16 remaining bytes)", Some(16); "empty rlp hash with precision"))] + fn test_display_hex(input: &[u8], expected: &str, precision: Option) { + let input = DisplayHex(input); + if let Some(p) = precision { + assert_eq!(format!("{input:.p$}"), expected); + } else { + assert_eq!(format!("{input}"), expected); + } + } +} diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs new file mode 100644 index 000000000000..f02d3878245e --- /dev/null +++ b/ffi/src/value/kvp.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt; + +use firewood::v2::api; + +use crate::value::BorrowedBytes; + +/// A `KeyValue` represents a key-value pair, passed to the FFI. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct KeyValuePair<'a> { + pub key: BorrowedBytes<'a>, + pub value: BorrowedBytes<'a>, +} + +impl fmt::Display for KeyValuePair<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let precision = f.precision().unwrap_or(64); + write!( + f, + "Key: {:.precision$}, Value: {:.precision$}", + self.key, self.value + ) + } +} + +impl<'a> api::KeyValuePair for KeyValuePair<'a> { + type Key = BorrowedBytes<'a>; + type Value = BorrowedBytes<'a>; + + #[inline] + fn into_batch(self) -> api::BatchOp { + if self.value.is_empty() { + api::BatchOp::DeleteRange { prefix: self.key } + } else { + api::BatchOp::Put { + key: self.key, + value: self.value, + } + } + } +} + +impl<'a> api::KeyValuePair for &KeyValuePair<'a> { + type Key = BorrowedBytes<'a>; + type Value = BorrowedBytes<'a>; + + #[inline] + fn into_batch(self) -> api::BatchOp { + (*self).into_batch() + } +} From 7f56f1816df049f908f9a0333b659f7b1353d9e8 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:10:48 -0500 Subject: [PATCH 0900/1053] feat(checker): more clear print formats for checker report (#1201) Sample output (after executing 1M blocks): ``` Errors (1): Found leaked areas: Linear Address Range Set: Address Range: [0x800, 0x900) Address Range: [0xa00, 0xb00) Address Range: [0xb60, 0xc60) Address Range: [0xdc0, 0xec0) Address Range: [0x1120, 0x12a0) Address Range: [0x13a0, 0x1620) Address Range: [0x1720, 0x17a0) Address Range: [0x1aa0, 0x20a0) Address Range: [0x27a0, 0x28a0) Address Range: [0x29a0, 0x2ea0) ... (76480 more hidden) Advanced Data: Storage Overhead: 394533632 / 46677089 = 8.45x Internal Fragmentation: 1 - (81634062 / 334746592) = 75.61% Trie Area Size Distribution: {16: 0, 32: 0, 64: 6422, 96: 236837, 128: 55637, 256: 105510, 512: 104601, 768: 247814, 1024: 32803, 2048: 0, 4096: 0, 8192: 0, 16384: 0, 32768: 0, 65536: 0, 131072: 0, 262144: 0, 524288: 0, 1048576: 0, 2097152: 0, 4194304: 0, 8388608: 0, 16777216: 0} Free List Area Size Distribution: {16: 0, 32: 0, 64: 0, 96: 0, 128: 0, 256: 0, 512: 0, 768: 58, 1024: 416, 2048: 0, 4096: 0, 8192: 0, 16384: 0, 32768: 0, 65536: 0, 131072: 0, 262144: 0, 524288: 0, 1048576: 0, 2097152: 0, 4194304: 0, 8388608: 0, 16777216: 0} Branching Factor Distribution: {1: 98, 2: 114082, 3: 33897, 4: 12560, 5: 6810, 6: 4994, 7: 4381, 8: 4359, 9: 4311, 10: 3917, 11: 3779, 12: 3162, 13: 2448, 14: 1516, 15: 941, 16: 3452} Depth Distribution: {4: 23285, 5: 36333, 6: 16921, 7: 42667, 8: 119550, 9: 197709, 10: 139367, 11: 9045, 12: 40} Low Occupancy Rate: 487040 / 789624 = 61.68% Trie Extra Unaligned Page Read Rate: 75388 / 789624 = 9.55% Free List Extra Unaligned Page Read Rate: 101 / 474 = 21.31% ``` --- fwdctl/src/check.rs | 48 ++++++++++++++-- storage/src/checker/mod.rs | 101 ++++++++++++++++++++++----------- storage/src/nodestore/alloc.rs | 4 +- storage/src/nodestore/mod.rs | 2 +- 4 files changed, 114 insertions(+), 41 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 320bbf917713..60adbc2c7d94 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -65,6 +65,12 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { fn print_checker_report(report: CheckerReport) { println!("Raw Report: {report:?}\n"); + println!("Errors ({}): ", report.errors.len()); + for error in report.errors { + println!("\t{error}"); + } + println!(); + println!("Advanced Data: "); let total_trie_area_bytes = report .trie_stats @@ -73,14 +79,46 @@ fn print_checker_report(report: CheckerReport) { .map(|(area_size, count)| area_size.saturating_mul(*count)) .sum::(); println!( - "\tStorage Space Utilization: {} / {} = {:.2}%", - report.trie_stats.trie_bytes, + "\tStorage Overhead: {} / {} = {:.2}x", report.high_watermark, - (report.trie_stats.trie_bytes as f64 / report.high_watermark as f64) * 100.0 + report.trie_stats.kv_bytes, + (report.high_watermark as f64 / report.trie_stats.kv_bytes as f64) ); println!( - "\tInternal Fragmentation: {} / {total_trie_area_bytes} = {:.2}%", + "\tInternal Fragmentation: 1 - ({} / {total_trie_area_bytes}) = {:.2}%", report.trie_stats.trie_bytes, - (report.trie_stats.trie_bytes as f64 / total_trie_area_bytes as f64) * 100.0 + (1f64 - (report.trie_stats.trie_bytes as f64 / total_trie_area_bytes as f64)) * 100.0 + ); + println!( + "\tTrie Area Size Distribution: {:?}", + report.trie_stats.area_counts + ); + println!( + "\tFree List Area Size Distribution: {:?}", + report.free_list_stats.area_counts + ); + println!( + "\tBranching Factor Distribution: {:?}", + report.trie_stats.branching_factors + ); + println!("\tDepth Distribution: {:?}", report.trie_stats.depths); + let total_trie_area_count = report.trie_stats.area_counts.values().sum::(); + println!( + "\tLow Occupancy Rate: {} / {total_trie_area_count} = {:.2}%", + report.trie_stats.low_occupancy_area_count, + (report.trie_stats.low_occupancy_area_count as f64 / total_trie_area_count as f64) * 100.0 + ); + println!( + "\tTrie Extra Unaligned Page Read Rate: {} / {total_trie_area_count} = {:.2}%", + report.trie_stats.extra_unaligned_page_read, + (report.trie_stats.extra_unaligned_page_read as f64 / total_trie_area_count as f64) * 100.0 + ); + let total_free_list_area_count = report.free_list_stats.area_counts.values().sum::(); + println!( + "\tFree List Extra Unaligned Page Read Rate: {} / {total_free_list_area_count} = {:.2}%", + report.free_list_stats.extra_unaligned_page_read, + (report.free_list_stats.extra_unaligned_page_read as f64 + / total_free_list_area_count as f64) + * 100.0 ); } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index f1ef655be872..b7fe574f1b06 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -16,7 +16,7 @@ use crate::{ use crate::hashednode::hash_node; use std::cmp::Ordering; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::ops::Range; use indicatif::ProgressBar; @@ -32,8 +32,8 @@ const fn page_number(addr: LinearAddress) -> u64 { fn extra_read_pages(addr: LinearAddress, page_size: u64) -> Option { let start_page = page_number(addr); let end_page = page_number(addr.advance(page_size.saturating_sub(1))?); - let pages_read = end_page.saturating_sub(start_page); - let min_pages = page_size / OS_PAGE_SIZE; + let pages_read = end_page.saturating_sub(start_page).saturating_add(1); // Include the first page + let min_pages = page_size.saturating_add(OS_PAGE_SIZE - 1) / OS_PAGE_SIZE; // Round up to the nearest page Some(pages_read.saturating_sub(min_pages)) } @@ -70,7 +70,7 @@ pub struct CheckerReport { pub free_list_stats: FreeListsStats, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, PartialEq)] /// Statistics about the trie pub struct TrieStats { /// The total number of bytes of compressed trie nodes @@ -80,26 +80,50 @@ pub struct TrieStats { /// The total number of bytes of for key-value pairs stored in the trie pub kv_bytes: u64, /// Branching factor distribution of each branch node - pub branching_factors: HashMap, + pub branching_factors: BTreeMap, /// Depth distribution of each leaf node - pub depths: HashMap, + pub depths: BTreeMap, /// The distribution of area sizes in the trie - pub area_counts: HashMap, + pub area_counts: BTreeMap, /// The stored areas whose content can fit into a smaller area pub low_occupancy_area_count: u64, - /// The number of stored areas that span multiple pages - pub multi_page_area_count: u64, + /// The number of areas that require an extra page read due to not being aligned + pub extra_unaligned_page_read: u64, +} + +impl Default for TrieStats { + fn default() -> Self { + Self { + trie_bytes: 0, + kv_count: 0, + kv_bytes: 0, + branching_factors: BTreeMap::new(), + depths: BTreeMap::new(), + area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), + low_occupancy_area_count: 0, + extra_unaligned_page_read: 0, + } + } } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, PartialEq)] /// Statistics about the free list pub struct FreeListsStats { /// The distribution of area sizes in the free lists - pub area_counts: HashMap, - /// The number of stored areas that span multiple pages + pub area_counts: BTreeMap, + /// The number of areas that require an extra page read due to not being aligned pub extra_unaligned_page_read: u64, } +impl Default for FreeListsStats { + fn default() -> Self { + Self { + area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), + extra_unaligned_page_read: 0, + } + } +} + struct SubTrieMetadata { root_address: LinearAddress, root_hash: HashType, @@ -261,7 +285,7 @@ impl NodeStore { })?; // check if the node fits in the area, equal is not allowed due to 1-byte area size index - if node_bytes >= area_size { + if node_bytes > area_size { return Err(vec![CheckerError::NodeLargerThanArea { area_start: subtrie_root_address, area_size, @@ -311,13 +335,16 @@ impl NodeStore { // collect trie stats { // update the area count - let area_count = trie_stats.area_counts.entry(area_size).or_insert(0); + let area_count = trie_stats + .area_counts + .get_mut(&area_size) + .expect("area size is initialized when trie_stats is created"); *area_count = area_count.saturating_add(1); // collect the trie bytes trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); // collect low occupancy area count, add 1 for the area size index byte - let smallest_area_index = AreaIndex::from_size(node_bytes.saturating_add(1)) - .expect("impossible since we checked that node_bytes < area_size"); + let smallest_area_index = AreaIndex::from_size(node_bytes) + .expect("impossible since we checked that node_bytes <= area_size"); if smallest_area_index < area_index { trie_stats.low_occupancy_area_count = trie_stats.low_occupancy_area_count.saturating_add(1); @@ -327,8 +354,8 @@ impl NodeStore { .expect("impossible since we checked in visited.insert_area()") > 0 { - trie_stats.multi_page_area_count = - trie_stats.multi_page_area_count.saturating_add(1); + trie_stats.extra_unaligned_page_read = + trie_stats.extra_unaligned_page_read.saturating_add(1); } } @@ -400,7 +427,8 @@ impl NodeStore { visited: &mut LinearAddressRangeSet, progress_bar: Option<&ProgressBar>, ) -> (FreeListsStats, Vec) { - let mut area_counts: HashMap = HashMap::new(); + let mut area_counts: BTreeMap = + area_size_iter().map(|(_, size)| (size, 0)).collect(); let mut multi_page_area_count = 0u64; let mut errors = Vec::new(); @@ -455,7 +483,9 @@ impl NodeStore { // collect the free list stats { // collect the free lists area distribution - let area_count = area_counts.entry(area_size).or_insert(0); + let area_count = area_counts + .get_mut(&area_size) + .expect("area size is initialized when free_list_stats is created"); *area_count = area_count.saturating_add(1); // collect the multi-page area count if extra_read_pages(addr, area_size) @@ -629,7 +659,8 @@ mod test { fn gen_test_trie(nodestore: &mut NodeStore) -> TestTrie { let mut high_watermark = NodeStoreHeader::SIZE; let mut total_bytes_written = 0; - let mut area_counts: HashMap = HashMap::new(); + let mut area_counts: BTreeMap = + area_size_iter().map(|(_, size)| (size, 0)).collect(); let leaf = Node::Leaf(LeafNode { partial_path: Path::from_nibbles_iterator(std::iter::repeat_n([4, 5], 30).flatten()), value: Box::new([6, 7, 8]), @@ -640,7 +671,7 @@ mod test { test_write_new_node(nodestore, &leaf, high_watermark); high_watermark += stored_area_size; total_bytes_written += bytes_written; - let area_count = area_counts.entry(stored_area_size).or_insert(0); + let area_count = area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); let mut branch_children = BranchNode::empty_children(); @@ -656,7 +687,7 @@ mod test { test_write_new_node(nodestore, &branch, high_watermark); high_watermark += stored_area_size; total_bytes_written += bytes_written; - let area_count = area_counts.entry(stored_area_size).or_insert(0); + let area_count = area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); let mut root_children = BranchNode::empty_children(); @@ -672,7 +703,7 @@ mod test { test_write_new_node(nodestore, &root, high_watermark); high_watermark += stored_area_size; total_bytes_written += bytes_written; - let area_count = area_counts.entry(stored_area_size).or_insert(0); + let area_count = area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); // write the header @@ -688,10 +719,10 @@ mod test { kv_count: 1, kv_bytes: 32 + 3, // 32 bytes for the key, 3 bytes for the value area_counts, - branching_factors: HashMap::from([(1, 2)]), - depths: HashMap::from([(2, 1)]), + branching_factors: BTreeMap::from([(1, 2)]), + depths: BTreeMap::from([(2, 1)]), low_occupancy_area_count: 0, - multi_page_area_count: 0, + extra_unaligned_page_read: 0, }; TestTrie { @@ -807,8 +838,9 @@ mod test { // write free areas let mut high_watermark = NodeStoreHeader::SIZE; - let mut free_area_counts: HashMap = HashMap::new(); - let mut multi_page_area_count = 0u64; + let mut free_area_counts: BTreeMap = + area_size_iter().map(|(_, size)| (size, 0)).collect(); + let mut extra_unaligned_page_read = 0u64; let mut freelist = FreeLists::default(); for (area_index, area_size) in area_size_iter() { let mut next_free_block = None; @@ -818,7 +850,7 @@ mod test { next_free_block = Some(LinearAddress::new(high_watermark).unwrap()); let start_addr = LinearAddress::new(high_watermark).unwrap(); if extra_read_pages(start_addr, area_size).unwrap() > 0 { - multi_page_area_count = multi_page_area_count.saturating_add(1); + extra_unaligned_page_read = extra_unaligned_page_read.saturating_add(1); } high_watermark += area_size; } @@ -829,7 +861,7 @@ mod test { } let expected_free_lists_stats = FreeListsStats { area_counts: free_area_counts, - extra_unaligned_page_read: multi_page_area_count, + extra_unaligned_page_read, }; // write header @@ -916,8 +948,13 @@ mod test { intersection: vec![intersection_start..intersection_end], parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea(free_list1_area1)), }]; + let mut area_counts = area_size_iter() + .map(|(_, size)| (size, 0)) + .collect::>(); + area_counts.insert(area_size1, 1); + area_counts.insert(area_size2, 2); let expected_free_lists_stats = FreeListsStats { - area_counts: HashMap::from([(area_size1, 1), (area_size2, 2)]), + area_counts, extra_unaligned_page_read: 0, }; diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 3df2cbe5dc80..c0e5a6fa4c5d 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -584,9 +584,7 @@ pub mod test_utils { let area_size_index = AreaIndex::from_size(encoded_node_len).unwrap(); let mut stored_area_bytes = Vec::new(); node.as_bytes(area_size_index, &mut stored_area_bytes); - let bytes_written = (stored_area_bytes.len() as u64) - .checked_sub(1) - .expect("serialized node should be at least 1 byte"); // -1 for area size index + let bytes_written = stored_area_bytes.len() as u64; nodestore .storage .write(offset, stored_area_bytes.as_slice()) diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index e0ebe3fd5763..d903e19ce574 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -761,7 +761,7 @@ impl NodeStore { Some("read_node_with_num_bytes_from_disk".to_string()), ) })?; - Ok((node, length)) + Ok((node, length.saturating_add(1))) // add 1 for the area size index byte } /// Returns (index, `area_size`) for the stored area at `addr`. From 65f305cff846407e500362f997941a091b155558 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:18:49 -0500 Subject: [PATCH 0901/1053] chore: add PathGuard type when computing hashes (#1202) This is a subset of changes in #1137. --- storage/src/nodestore/hash.rs | 148 +++++++++++++++++++++++----------- storage/src/nodestore/mod.rs | 5 +- 2 files changed, 105 insertions(+), 48 deletions(-) diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index f1c07e9b3345..7c9890a63bb5 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -15,8 +15,51 @@ use crate::node::Node; use crate::{Child, HashType, MaybePersistedNode, NodeStore, Path, ReadableStorage, SharedNode}; use super::NodeReader; -#[cfg(feature = "ethhash")] -use std::ops::Deref; + +use std::ops::{Deref, DerefMut}; + +/// Wrapper around a path that makes sure we truncate what gets extended to the path after it goes out of scope +/// This allows the same memory space to be reused for different path prefixes +#[derive(Debug)] +struct PathGuard<'a> { + path: &'a mut Path, + original_length: usize, +} + +impl<'a> PathGuard<'a> { + fn new(path: &'a mut PathGuard<'_>) -> Self { + Self { + original_length: path.0.len(), + path: &mut path.path, + } + } + + fn from_path(path: &'a mut Path) -> Self { + Self { + original_length: path.0.len(), + path, + } + } +} + +impl Drop for PathGuard<'_> { + fn drop(&mut self) { + self.path.0.truncate(self.original_length); + } +} + +impl Deref for PathGuard<'_> { + type Target = Path; + fn deref(&self) -> &Self::Target { + self.path + } +} + +impl DerefMut for PathGuard<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.path + } +} /// Classified children for ethereum hash processing #[cfg(feature = "ethhash")] @@ -70,13 +113,28 @@ where ) } - /// Hashes `node`, which is at the given `path_prefix`, and its children recursively. - /// The function appends to `path_prefix` and then truncate it back to the original length - we only reuse the memory space to avoid allocations + /// Hashes the given `node` and the subtree rooted at it. /// Returns the hashed node and its hash. pub(super) fn hash_helper( + #[cfg(feature = "ethhash")] &self, + node: Node, + ) -> Result<(MaybePersistedNode, HashType, usize), FileIoError> { + let mut root_path = Path::new(); + #[cfg(not(feature = "ethhash"))] + let res = Self::hash_helper_inner(node, PathGuard::from_path(&mut root_path))?; + #[cfg(feature = "ethhash")] + let res = self.hash_helper_inner(node, PathGuard::from_path(&mut root_path), None)?; + Ok(res) + } + + /// Recursive helper that hashes the given `node` and the subtree rooted at it. + /// This function takes a mut `node` to update the hash in place. + /// The `path_prefix` is also mut because we will extend it to the path of the child we are hashing in recursive calls - it will be restored after the recursive call returns. + /// The `num_siblings` is the number of children of the parent node, which includes this node. + fn hash_helper_inner( #[cfg(feature = "ethhash")] &self, mut node: Node, - path_prefix: &mut Path, + mut path_prefix: PathGuard<'_>, #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, ) -> Result<(MaybePersistedNode, HashType, usize), FileIoError> { // If this is a branch, find all unhashed children and recursively hash them. @@ -94,28 +152,27 @@ where mut hashed, } = self.ethhash_classify_children(&mut b.children); trace!("hashed {hashed:?} unhashed {unhashed:?}"); - if hashed.len() == 1 { - // we were left with one hashed node that must be rehashed - let (invalidated_node_idx, (invalidated_node, invalidated_hash)) = - hashed.first_mut().expect("hashed is not empty"); + // we were left with one hashed node that must be rehashed + if let [(child_idx, (child_node, child_hash))] = &mut hashed[..] { // Extract the address from the MaybePersistedNode - let addr: crate::LinearAddress = invalidated_node + let addr: crate::LinearAddress = child_node .as_linear_address() .expect("hashed node should be persisted"); let mut hashable_node = self.read_node(addr)?.deref().clone(); - let original_length = path_prefix.len(); - path_prefix.0.extend(b.partial_path.0.iter().copied()); - if unhashed.is_empty() { - hashable_node.update_partial_path(Path::from_nibbles_iterator( - std::iter::once(*invalidated_node_idx as u8) - .chain(hashable_node.partial_path().0.iter().copied()), - )); - } else { - path_prefix.0.push(*invalidated_node_idx as u8); - } - let hash = hash_node(&hashable_node, path_prefix); - path_prefix.0.truncate(original_length); - **invalidated_hash = hash; + let hash = { + let mut path_guard = PathGuard::new(&mut path_prefix); + path_guard.0.extend(b.partial_path.0.iter().copied()); + if unhashed.is_empty() { + hashable_node.update_partial_path(Path::from_nibbles_iterator( + std::iter::once(*child_idx as u8) + .chain(hashable_node.partial_path().0.iter().copied()), + )); + } else { + path_guard.0.push(*child_idx as u8); + } + hash_node(&hashable_node, &path_guard) + }; + **child_hash = hash; } // handle the single-child case for an account special below if hashed.is_empty() && unhashed.len() == 1 { @@ -148,30 +205,31 @@ where let child_node = std::mem::take(child_node); // Hash this child and update - // we extend and truncate path_prefix to reduce memory allocations - let original_length = path_prefix.len(); - path_prefix.0.extend(b.partial_path.0.iter().copied()); - #[cfg(feature = "ethhash")] - if make_fake_root.is_none() { - // we don't push the nibble there is only one unhashed child and - // we're on an account - path_prefix.0.push(nibble as u8); - } - #[cfg(not(feature = "ethhash"))] - path_prefix.0.push(nibble as u8); + let (child_node, child_hash, child_count) = { + // we extend and truncate path_prefix to reduce memory allocations] + let mut child_path_prefix = PathGuard::new(&mut path_prefix); + child_path_prefix.0.extend(b.partial_path.0.iter().copied()); + #[cfg(feature = "ethhash")] + if make_fake_root.is_none() { + // we don't push the nibble there is only one unhashed child and + // we're on an account + child_path_prefix.0.push(nibble as u8); + } + #[cfg(not(feature = "ethhash"))] + child_path_prefix.0.push(nibble as u8); + #[cfg(feature = "ethhash")] + let (child_node, child_hash, child_count) = + self.hash_helper_inner(child_node, child_path_prefix, make_fake_root)?; + #[cfg(not(feature = "ethhash"))] + let (child_node, child_hash, child_count) = + Self::hash_helper_inner(child_node, child_path_prefix)?; - #[cfg(feature = "ethhash")] - let (child_node, child_hash, child_count) = - self.hash_helper(child_node, path_prefix, make_fake_root)?; - #[cfg(not(feature = "ethhash"))] - let (child_node, child_hash, child_count) = - Self::hash_helper(child_node, path_prefix)?; + (child_node, child_hash, child_count) + }; nodes_processed = nodes_processed.saturating_add(child_count); - *child = Some(Child::MaybePersisted(child_node, child_hash)); trace!("child now {child:?}"); - path_prefix.0.truncate(original_length); } } // At this point, we either have a leaf or a branch with all children hashed. @@ -187,13 +245,13 @@ where std::iter::once(nibble).chain(fake_root.partial_path().0.iter().copied()), )); trace!("new node: {fake_root:?}"); - hash_node(&fake_root, path_prefix) + hash_node(&fake_root, &path_prefix) } else { - hash_node(&node, path_prefix) + hash_node(&node, &path_prefix) }; #[cfg(not(feature = "ethhash"))] - let hash = hash_node(&node, path_prefix); + let hash = hash_node(&node, &path_prefix); Ok((SharedNode::new(node).into(), hash, nodes_processed)) } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index d903e19ce574..4f540d384ead 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -582,11 +582,10 @@ impl TryFrom> // Hashes the trie and returns the address of the new root. #[cfg(feature = "ethhash")] - let (root, root_hash, unwritten_count) = - nodestore.hash_helper(root, &mut Path::new(), None)?; + let (root, root_hash, unwritten_count) = nodestore.hash_helper(root)?; #[cfg(not(feature = "ethhash"))] let (root, root_hash, unwritten_count) = - NodeStore::::hash_helper(root, &mut Path::new())?; + NodeStore::::hash_helper(root)?; let immutable_proposal = Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); From 6616455042486fca090cc4357a70c378c43e6246 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 14 Aug 2025 09:59:41 -0700 Subject: [PATCH 0902/1053] feat(async-removal): Phase 1 - lint on `clippy::unused_async` (#1211) Phase 1 of async removal is to add the lint for `clippy::unused_async` back into the mix to stop unnecessarily making functions async. --- Cargo.toml | 1 - benchmark/src/main.rs | 4 +- ffi/src/lib.rs | 2 +- firewood/benches/hashops.rs | 1 - firewood/examples/insert.rs | 4 +- firewood/src/db.rs | 73 +++++++++++++------------------------ fwdctl/src/check.rs | 2 +- fwdctl/src/create.rs | 4 +- fwdctl/src/delete.rs | 2 +- fwdctl/src/dump.rs | 8 ++-- fwdctl/src/get.rs | 2 +- fwdctl/src/graph.rs | 6 +-- fwdctl/src/insert.rs | 2 +- fwdctl/src/main.rs | 6 +-- fwdctl/src/root.rs | 2 +- 15 files changed, 45 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f1201cb15e9c..cd4ef86eab63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ pedantic = { level = "warn", priority = -1 } # These lints are from pedantic but allowed. They are a bit too pedantic and # encourage making backwards incompatible changes. needless_pass_by_value = "allow" -unused_async = "allow" unnecessary_wraps = "allow" unused_self = "allow" # Ignore interger casts. This is to avoid unnecessary `try_into` calls for usize diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 34894a68d21d..23d9014167e6 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -257,9 +257,7 @@ async fn main() -> Result<(), Box> { .manager(mgrcfg) .build(); - let db = Db::new(args.global_opts.dbname.clone(), cfg) - .await - .expect("db initiation should succeed"); + let db = Db::new(args.global_opts.dbname.clone(), cfg).expect("db initiation should succeed"); match args.test_name { TestName::Create => { diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e467aa7b46ed..394bc4325aef 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -889,7 +889,7 @@ unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { .path .as_str() .map_err(|e| format!("Invalid database path: {e}"))?; - Db::new_sync(path, cfg).map_err(|e| e.to_string()) + Db::new(path, cfg).map_err(|e| e.to_string()) } /// Arguments for logging diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 0cebfff7b8d2..f960da9ad985 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -119,7 +119,6 @@ fn bench_db(criterion: &mut Criterion) { let cfg = DbConfig::builder(); let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) - .await .unwrap(); db.propose(batch_ops).await.unwrap().commit().await.unwrap(); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index a780f6bceae8..370c18df55d1 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -63,9 +63,7 @@ async fn main() -> Result<(), Box> { .manager(mgrcfg) .build(); - let db = Db::new("rev_db", cfg) - .await - .expect("db initiation should succeed"); + let db = Db::new("rev_db", cfg).expect("db initiation should succeed"); let keys = args.batch_size; let start = Instant::now(); diff --git a/firewood/src/db.rs b/firewood/src/db.rs index fabb7c7cf8ae..84e48247b48a 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -228,23 +228,7 @@ impl api::Db for Db { impl Db { /// Create a new database instance. - pub async fn new>(db_path: P, cfg: DbConfig) -> Result { - let metrics = Arc::new(DbMetrics { - proposals: counter!("firewood.proposals"), - }); - describe_counter!("firewood.proposals", "Number of proposals created"); - let config_manager = ConfigManager::builder() - .create(cfg.create_if_missing) - .truncate(cfg.truncate) - .manager(cfg.manager) - .build(); - let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager)?; - let db = Self { metrics, manager }; - Ok(db) - } - - /// Create a new database instance with synchronous I/O. - pub fn new_sync>(db_path: P, cfg: DbConfig) -> Result { + pub fn new>(db_path: P, cfg: DbConfig) -> Result { let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals"), }); @@ -311,12 +295,7 @@ impl Db { } /// Dump the Trie of the latest revision. - pub async fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { - self.dump_sync(w) - } - - /// Dump the Trie of the latest revision, synchronously. - pub fn dump_sync(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { + pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); let merkle = Merkle::from(latest_rev_nodestore); merkle.dump(w).map_err(std::io::Error::other) @@ -328,7 +307,7 @@ impl Db { } /// Check the database for consistency - pub async fn check(&self, opt: CheckOpt) -> CheckerReport { + pub fn check(&self, opt: CheckOpt) -> CheckerReport { let latest_rev_nodestore = self.manager.current_revision(); latest_rev_nodestore.check(opt) } @@ -512,7 +491,7 @@ mod test { #[tokio::test] async fn test_proposal_reads() { - let db = testdb().await; + let db = testdb(); let batch = vec![BatchOp::Put { key: b"k", value: b"v", @@ -537,7 +516,7 @@ mod test { #[tokio::test] async fn reopen_test() { - let db = testdb().await; + let db = testdb(); let initial_root = db.root_hash().await.unwrap(); let batch = vec![ BatchOp::Put { @@ -553,13 +532,13 @@ mod test { proposal.commit().await.unwrap(); println!("{:?}", db.root_hash().await.unwrap().unwrap()); - let db = db.reopen().await; + let db = db.reopen(); println!("{:?}", db.root_hash().await.unwrap().unwrap()); let committed = db.root_hash().await.unwrap().unwrap(); let historical = db.revision(committed).await.unwrap(); assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); - let db = db.replace().await; + let db = db.replace(); println!("{:?}", db.root_hash().await.unwrap()); assert!(db.root_hash().await.unwrap() == initial_root); } @@ -570,7 +549,7 @@ mod test { // R1 --> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known async fn test_proposal_scope_historic() { - let db = testdb().await; + let db = testdb(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", @@ -622,7 +601,7 @@ mod test { // \-> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known async fn test_proposal_scope_orphan() { - let db = testdb().await; + let db = testdb(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", @@ -673,7 +652,7 @@ mod test { #[tokio::test] async fn test_view_sync() { - let db = testdb().await; + let db = testdb(); // Create and commit some data to get a historical revision let batch = vec![BatchOp::Put { @@ -718,7 +697,7 @@ mod test { // number of keys and values to create for this test const N: usize = 20; - let db = testdb().await; + let db = testdb(); // create N keys and values like (key0, value0)..(keyN, valueN) let (keys, vals): (Vec<_>, Vec<_>) = (0..N) @@ -779,7 +758,7 @@ mod test { let rng = firewood_storage::SeededRng::from_env_or_random(); - let db = testdb().await; + let db = testdb(); // takes about 0.3s on a mac to run 50 times for _ in 0..50 { @@ -804,12 +783,10 @@ mod test { // check the database for consistency, sometimes checking the hashes let hash_check = rng.random(); - let report = db - .check(CheckOpt { - hash_check, - progress_bar: None, - }) - .await; + let report = db.check(CheckOpt { + hash_check, + progress_bar: None, + }); if report .errors .iter() @@ -817,7 +794,7 @@ mod test { .count() != 0 { - db.dump(&mut std::io::stdout()).await.unwrap(); + db.dump(&mut std::io::stdout()).unwrap(); panic!("error: {:?}", report.errors); } } @@ -828,7 +805,7 @@ mod test { const NUM_KEYS: NonZeroUsize = const { NonZeroUsize::new(2).unwrap() }; const NUM_PROPOSALS: usize = 100; - let db = testdb().await; + let db = testdb(); let ops = (0..(NUM_KEYS.get() * NUM_PROPOSALS)) .map(|i| (format!("key{i}"), format!("value{i}"))) @@ -890,7 +867,7 @@ mod test { const CHANNEL_CAPACITY: usize = 8; - let testdb = testdb().await; + let testdb = testdb(); let db = &testdb.db; let (tx, mut rx): (Sender>, Receiver>) = @@ -950,13 +927,13 @@ mod test { } } - async fn testdb() -> TestDb { + fn testdb() -> TestDb { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); let dbconfig = DbConfig::builder().build(); - let db = Db::new(dbpath, dbconfig).await.unwrap(); + let db = Db::new(dbpath, dbconfig).unwrap(); TestDb { db, tmpdir } } @@ -966,23 +943,23 @@ mod test { .iter() .collect() } - async fn reopen(self) -> Self { + fn reopen(self) -> Self { let path = self.path(); drop(self.db); let dbconfig = DbConfig::builder().truncate(false).build(); - let db = Db::new(path, dbconfig).await.unwrap(); + let db = Db::new(path, dbconfig).unwrap(); TestDb { db, tmpdir: self.tmpdir, } } - async fn replace(self) -> Self { + fn replace(self) -> Self { let path = self.path(); drop(self.db); let dbconfig = DbConfig::builder().truncate(true).build(); - let db = Db::new(path, dbconfig).await.unwrap(); + let db = Db::new(path, dbconfig).unwrap(); TestDb { db, tmpdir: self.tmpdir, diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 60adbc2c7d94..0dc8a1ff5785 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -28,7 +28,7 @@ pub struct Options { pub hash_check: bool, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { let db_path = PathBuf::from(&opts.database.dbpath); let node_cache_size = nonzero!(1usize); let free_list_cache_size = nonzero!(1usize); diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index e6bf4121b31b..0d8c8da260ab 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -49,11 +49,11 @@ pub(super) fn new(opts: &Options) -> DbConfig { DbConfig::builder().truncate(opts.truncate).build() } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { let db_config = new(opts); log::debug!("database configuration parameters: \n{db_config:?}\n"); - Db::new(opts.database.dbpath.clone(), db_config).await?; + Db::new(opts.database.dbpath.clone(), db_config)?; println!( "created firewood database in {}", opts.database.dbpath.display() diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 33e8d9070fc2..4a8fde414b4e 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -21,7 +21,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("deleting key {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; let batch: Vec> = vec![BatchOp::Delete { key: opts.key.clone(), diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index f0eb047630e6..8da258ca3629 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -143,7 +143,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { } let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; let latest_hash = db.root_hash().await?; let Some(latest_hash) = latest_hash else { println!("Database is empty"); @@ -178,7 +178,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { if (stop_key.as_ref().is_some_and(|stop_key| key >= *stop_key)) || key_count_exceeded(opts.max_key_count, key_count) { - handle_next_key(stream.next().await).await; + handle_next_key(stream.next().await); break; } } @@ -223,7 +223,7 @@ fn key_value_to_string(key: &[u8], value: &[u8], hex: bool) -> (String, String) (key_str, value_str) } -async fn handle_next_key(next_key: KeyFromStream) { +fn handle_next_key(next_key: KeyFromStream) { match next_key { Some(Ok((key, _))) => { println!( @@ -343,7 +343,7 @@ fn create_output_handler( let file = File::create(file_name)?; let mut writer = BufWriter::new(file); // For dot format, we generate the output immediately since it doesn't use streaming - db.dump_sync(&mut writer)?; + db.dump(&mut writer)?; Ok(None) } } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index e89df9351672..9650f8cef9df 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -22,7 +22,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("get key value pair {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; let hash = db.root_hash().await?; diff --git a/fwdctl/src/graph.rs b/fwdctl/src/graph.rs index 880b9ccfa5ec..fbc14477db20 100644 --- a/fwdctl/src/graph.rs +++ b/fwdctl/src/graph.rs @@ -14,11 +14,11 @@ pub struct Options { pub database: DatabasePath, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; - db.dump(&mut stdout()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; + db.dump(&mut stdout())?; Ok(()) } diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 3af26c9d6629..c8444dd8cb94 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -25,7 +25,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("inserting key value pair {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; let batch: Vec, Vec>> = vec![BatchOp::Put { key: opts.key.clone().into(), diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index 2c3d3c611dfc..bf559294d5d2 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -81,14 +81,14 @@ async fn main() -> Result<(), api::Error> { ); match &cli.command { - Commands::Create(opts) => create::run(opts).await, + Commands::Create(opts) => create::run(opts), Commands::Insert(opts) => insert::run(opts).await, Commands::Get(opts) => get::run(opts).await, Commands::Delete(opts) => delete::run(opts).await, Commands::Root(opts) => root::run(opts).await, Commands::Dump(opts) => dump::run(opts).await, - Commands::Graph(opts) => graph::run(opts).await, - Commands::Check(opts) => check::run(opts).await, + Commands::Graph(opts) => graph::run(opts), + Commands::Check(opts) => check::run(opts), } } diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 8a0445fc6955..949b8f693ab8 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -17,7 +17,7 @@ pub struct Options { pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let cfg = DbConfig::builder().create_if_missing(false).truncate(false); - let db = Db::new(opts.database.dbpath.clone(), cfg.build()).await?; + let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; let hash = db.root_hash().await?; From 65d7e8c8584a506308dff71e2864499de1ed0062 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:35:27 -0500 Subject: [PATCH 0903/1053] test: reenable eth merkle compatibility test (#1214) --- firewood/src/merkle/tests/ethhash.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index e8bdd401b270..235a43eeea75 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -1,6 +1,8 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::v2::api::OptionalHashKeyExt; + use super::*; use ethereum_types::H256; use hash_db::Hasher; @@ -122,7 +124,6 @@ fn make_key(hex_str: &str) -> Key { } #[test] -#[ignore = "broken tests not yet validated"] fn test_root_hash_random_deletions() { use rand::seq::SliceRandom; let rng = firewood_storage::SeededRng::from_option(Some(42)); @@ -174,7 +175,11 @@ fn test_root_hash_random_deletions() { let h: TrieHash = KeccakHasher::trie_root(&items).to_fixed_bytes().into(); - let h0 = committed_merkle.nodestore().root_hash().unwrap(); + let h0 = committed_merkle + .nodestore() + .root_hash() + .or_default_root_hash() + .unwrap(); assert_eq!(h, h0); } From 85c313775b9bfea7652970a9c69b3f433ae5a00d Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:51:40 -0500 Subject: [PATCH 0904/1053] feat(checker): collect statistics for branches and leaves separately (#1206) Running checker with state at 1M blocks ``` Errors (1): Found leaked areas: Linear Address Range Set: Address Range: [0x800, 0x900) Address Range: [0xa00, 0xb00) Address Range: [0xb60, 0xc60) Address Range: [0xdc0, 0xec0) Address Range: [0x1120, 0x12a0) Address Range: [0x13a0, 0x1620) Address Range: [0x1720, 0x17a0) Address Range: [0x1aa0, 0x20a0) Address Range: [0x27a0, 0x28a0) Address Range: [0x29a0, 0x2ea0) ... (76480 more hidden) Basic Stats: Firewood Image Size / High Watermark (high_watermark): 394533632 Total Key-Value Count (kv_count): 586687 Total Key-Value Bytes (kv_bytes): 46859598 Trie Stats: Branching Factor Distribution: {1: 98, 2: 114082, 3: 33897, 4: 12560, 5: 6810, 6: 4994, 7: 4381, 8: 4359, 9: 4311, 10: 3917, 11: 3779, 12: 3162, 13: 2448, 14: 1516, 15: 941, 16: 3452} Depth Distribution: {4: 23285, 5: 36333, 6: 16921, 7: 42667, 8: 119550, 9: 197709, 10: 139367, 11: 9045, 12: 40} Branch Area Stats: Total Branch Data Bytes (branch_bytes): 33759976 Total Branch Area Count (branch_area_count): 204707 Total Branch Area Bytes (branch_area_bytes): 97750016 Branch Area Distribution: {16: 0, 32: 0, 64: 0, 96: 43100, 128: 16525, 256: 24453, 512: 37967, 768: 73624, 1024: 9038, 2048: 0, 4096: 0, 8192: 0, 16384: 0, 32768: 0, 65536: 0, 131072: 0, 262144: 0, 524288: 0, 1048576: 0, 2097152: 0, 4194304: 0, 8388608: 0, 16777216: 0} Branches that Can Fit Into Smaller Area (low_occupancy_branch_area): 118365 (57.82%) Leaf Area Stats: Total Leaf Data Bytes (leaf_bytes): 47874086 Total Leaf Area Count (leaf_area_count): 584917 Total Leaf Area Bytes (leaf_area_bytes): 236996576 Leaf Area Distribution: {16: 0, 32: 0, 64: 6422, 96: 193737, 128: 39112, 256: 81057, 512: 66634, 768: 174190, 1024: 23765, 2048: 0, 4096: 0, 8192: 0, 16384: 0, 32768: 0, 65536: 0, 131072: 0, 262144: 0, 524288: 0, 1048576: 0, 2097152: 0, 4194304: 0, 8388608: 0, 16777216: 0} Leaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): 368675 (63.03%) Free List Area Stats: Free List Area Counts (free_list_area_counts): 474 Total Free List Area Bytes (free_list_area_bytes): 470528 Free List Area Distribution: {16: 0, 32: 0, 64: 0, 96: 0, 128: 0, 256: 0, 512: 0, 768: 58, 1024: 416, 2048: 0, 4096: 0, 8192: 0, 16384: 0, 32768: 0, 65536: 0, 131072: 0, 262144: 0, 524288: 0, 1048576: 0, 2097152: 0, 4194304: 0, 8388608: 0, 16777216: 0} Alignment Stats: Trie Areas Spanning Extra Page Due to Unalignment: 75388 (9.55%) Free List Areas Spanning Extra Page Due to Unalignment: 101 (21.31%) Trie Nodes Spanning Extra Page Due to Unalignment: 16811 (2.13%) Advanced Stats: Storage Overhead: high_watermark / kv_bytes = 8.42x Internal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = 75.61% Areas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = 487040 (61.68%) ``` --- fwdctl/src/check.rs | 150 ++++++++++++++++++++++++++------- storage/src/checker/mod.rs | 166 +++++++++++++++++++++++-------------- 2 files changed, 223 insertions(+), 93 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 0dc8a1ff5785..bba3cb8c3d2a 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -62,63 +62,149 @@ pub(super) fn run(opts: &Options) -> Result<(), api::Error> { } #[expect(clippy::cast_precision_loss)] +#[expect(clippy::too_many_lines)] fn print_checker_report(report: CheckerReport) { + let total_branch_area_count = report.trie_stats.branch_area_counts.values().sum::(); + let total_leaf_area_count = report.trie_stats.leaf_area_counts.values().sum::(); + let total_trie_area_count = total_branch_area_count.saturating_add(total_leaf_area_count); + let total_branch_area_bytes = report + .trie_stats + .branch_area_counts + .iter() + .map(|(area_size, count)| area_size.saturating_mul(*count)) + .sum::(); + let total_leaf_area_bytes = report + .trie_stats + .leaf_area_counts + .iter() + .map(|(area_size, count)| area_size.saturating_mul(*count)) + .sum::(); + let total_trie_area_bytes = total_branch_area_bytes.saturating_add(total_leaf_area_bytes); + let total_free_list_area_count = report.free_list_stats.area_counts.values().sum::(); + let total_free_list_area_bytes = report + .free_list_stats + .area_counts + .iter() + .map(|(area_size, count)| area_size.saturating_mul(*count)) + .sum::(); + println!("Raw Report: {report:?}\n"); println!("Errors ({}): ", report.errors.len()); for error in report.errors { println!("\t{error}"); } - println!(); - println!("Advanced Data: "); - let total_trie_area_bytes = report - .trie_stats - .area_counts - .iter() - .map(|(area_size, count)| area_size.saturating_mul(*count)) - .sum::(); - println!( - "\tStorage Overhead: {} / {} = {:.2}x", - report.high_watermark, - report.trie_stats.kv_bytes, - (report.high_watermark as f64 / report.trie_stats.kv_bytes as f64) - ); + // Basic stats + println!("\nBasic Stats: "); println!( - "\tInternal Fragmentation: 1 - ({} / {total_trie_area_bytes}) = {:.2}%", - report.trie_stats.trie_bytes, - (1f64 - (report.trie_stats.trie_bytes as f64 / total_trie_area_bytes as f64)) * 100.0 + "\tFirewood Image Size / High Watermark (high_watermark): {}", + report.high_watermark ); println!( - "\tTrie Area Size Distribution: {:?}", - report.trie_stats.area_counts + "\tTotal Key-Value Count (kv_count): {}", + report.trie_stats.kv_count ); println!( - "\tFree List Area Size Distribution: {:?}", - report.free_list_stats.area_counts + "\tTotal Key-Value Bytes (kv_bytes): {}", + report.trie_stats.kv_bytes ); + + // Trie statistics + println!("\nTrie Stats: "); println!( "\tBranching Factor Distribution: {:?}", report.trie_stats.branching_factors ); println!("\tDepth Distribution: {:?}", report.trie_stats.depths); - let total_trie_area_count = report.trie_stats.area_counts.values().sum::(); + + // Branch area distribution + println!("\nBranch Area Stats: "); println!( - "\tLow Occupancy Rate: {} / {total_trie_area_count} = {:.2}%", - report.trie_stats.low_occupancy_area_count, - (report.trie_stats.low_occupancy_area_count as f64 / total_trie_area_count as f64) * 100.0 + "\tTotal Branch Data Bytes (branch_bytes): {}", + report.trie_stats.branch_bytes ); + println!("\tTotal Branch Area Count (branch_area_count): {total_branch_area_count}"); + println!("\tTotal Branch Area Bytes (branch_area_bytes): {total_branch_area_bytes}"); println!( - "\tTrie Extra Unaligned Page Read Rate: {} / {total_trie_area_count} = {:.2}%", - report.trie_stats.extra_unaligned_page_read, - (report.trie_stats.extra_unaligned_page_read as f64 / total_trie_area_count as f64) * 100.0 + "\tBranch Area Distribution: {:?}", + report.trie_stats.branch_area_counts ); - let total_free_list_area_count = report.free_list_stats.area_counts.values().sum::(); println!( - "\tFree List Extra Unaligned Page Read Rate: {} / {total_free_list_area_count} = {:.2}%", - report.free_list_stats.extra_unaligned_page_read, - (report.free_list_stats.extra_unaligned_page_read as f64 + "\tBranches that Can Fit Into Smaller Area (low_occupancy_branch_area): {} ({:.2}%)", + report.trie_stats.low_occupancy_branch_area_count, + (report.trie_stats.low_occupancy_branch_area_count as f64 / total_branch_area_count as f64) + * 100.0 + ); + + // Leaf area distribution + println!("\nLeaf Area Stats: "); + println!( + "\tTotal Leaf Data Bytes (leaf_bytes): {}", + report.trie_stats.leaf_bytes, + ); + println!("\tTotal Leaf Area Count (leaf_area_count): {total_leaf_area_count}"); + println!("\tTotal Leaf Area Bytes (leaf_area_bytes): {total_leaf_area_bytes}"); + println!( + "\tLeaf Area Distribution: {:?}", + report.trie_stats.leaf_area_counts + ); + println!( + "\tLeaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): {} ({:.2}%)", + report.trie_stats.low_occupancy_leaf_area_count, + (report.trie_stats.low_occupancy_leaf_area_count as f64 / total_leaf_area_count as f64) + * 100.0 + ); + + // Free list area distribution + println!("\nFree List Area Stats: "); + println!("\tFree List Area Counts (free_list_area_counts): {total_free_list_area_count}"); + println!("\tTotal Free List Area Bytes (free_list_area_bytes): {total_free_list_area_bytes}"); + println!( + "\tFree List Area Distribution: {:?}", + report.free_list_stats.area_counts + ); + + // alignment stats + println!("\nAlignment Stats: "); + println!( + "\tTrie Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", + report.trie_stats.area_extra_unaligned_page, + (report.trie_stats.area_extra_unaligned_page as f64 / total_trie_area_count as f64) * 100.0 + ); + println!( + "\tFree List Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", + report.free_list_stats.area_extra_unaligned_page, + (report.free_list_stats.area_extra_unaligned_page as f64 / total_free_list_area_count as f64) * 100.0 ); + println!( + "\tTrie Nodes Spanning Extra Page Due to Unalignment: {} ({:.2}%)", + report.trie_stats.node_extra_unaligned_page, + (report.trie_stats.node_extra_unaligned_page as f64 / total_trie_area_count as f64) * 100.0 + ); + + println!("\nAdvanced Stats: "); + println!( + "\tStorage Overhead: high_watermark / kv_bytes = {:.2}x", + (report.high_watermark as f64 / report.trie_stats.kv_bytes as f64) + ); + let total_trie_bytes = report + .trie_stats + .branch_bytes + .saturating_add(report.trie_stats.leaf_bytes); + println!( + "\tInternal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = {:.2}%", + (1f64 - (total_trie_bytes as f64 / total_trie_area_bytes as f64)) * 100.0 + ); + let low_occupancy_area_count = report + .trie_stats + .low_occupancy_branch_area_count + .saturating_add(report.trie_stats.low_occupancy_leaf_area_count); + println!( + "\tAreas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = {} ({:.2}%)", + low_occupancy_area_count, + (low_occupancy_area_count as f64 / total_trie_area_count as f64) * 100.0 + ); } diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index b7fe574f1b06..0fe298fedc73 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -29,11 +29,11 @@ const fn page_number(addr: LinearAddress) -> u64 { addr.get() / OS_PAGE_SIZE } -fn extra_read_pages(addr: LinearAddress, page_size: u64) -> Option { +fn extra_read_pages(addr: LinearAddress, bytes: u64) -> Option { let start_page = page_number(addr); - let end_page = page_number(addr.advance(page_size.saturating_sub(1))?); + let end_page = page_number(addr.advance(bytes.saturating_sub(1))?); let pages_read = end_page.saturating_sub(start_page).saturating_add(1); // Include the first page - let min_pages = page_size.saturating_add(OS_PAGE_SIZE - 1) / OS_PAGE_SIZE; // Round up to the nearest page + let min_pages = bytes.saturating_add(OS_PAGE_SIZE - 1) / OS_PAGE_SIZE; // Round up to the nearest page Some(pages_read.saturating_sub(min_pages)) } @@ -73,8 +73,10 @@ pub struct CheckerReport { #[derive(Debug, PartialEq)] /// Statistics about the trie pub struct TrieStats { - /// The total number of bytes of compressed trie nodes - pub trie_bytes: u64, + /// The total number of bytes of compressed branch nodes + pub branch_bytes: u64, + /// The total number of bytes of compressed leaf nodes + pub leaf_bytes: u64, /// The number of key-value pairs stored in the trie pub kv_count: u64, /// The total number of bytes of for key-value pairs stored in the trie @@ -83,25 +85,35 @@ pub struct TrieStats { pub branching_factors: BTreeMap, /// Depth distribution of each leaf node pub depths: BTreeMap, - /// The distribution of area sizes in the trie - pub area_counts: BTreeMap, - /// The stored areas whose content can fit into a smaller area - pub low_occupancy_area_count: u64, - /// The number of areas that require an extra page read due to not being aligned - pub extra_unaligned_page_read: u64, + /// The distribution of area sizes for branch nodes + pub branch_area_counts: BTreeMap, + /// The distribution of area sizes for leaf nodes + pub leaf_area_counts: BTreeMap, + /// Branches that can fit into a smaller area + pub low_occupancy_branch_area_count: u64, + /// Leaves that can fit into a smaller area + pub low_occupancy_leaf_area_count: u64, + /// The number of areas that span an extra page due to not being aligned + pub area_extra_unaligned_page: u64, + /// The number of nodes that span an extra page due to not being aligned + pub node_extra_unaligned_page: u64, } impl Default for TrieStats { fn default() -> Self { Self { - trie_bytes: 0, + branch_bytes: 0, + leaf_bytes: 0, kv_count: 0, kv_bytes: 0, branching_factors: BTreeMap::new(), depths: BTreeMap::new(), - area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), - low_occupancy_area_count: 0, - extra_unaligned_page_read: 0, + branch_area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), + leaf_area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), + low_occupancy_branch_area_count: 0, + low_occupancy_leaf_area_count: 0, + area_extra_unaligned_page: 0, + node_extra_unaligned_page: 0, } } } @@ -111,15 +123,15 @@ impl Default for TrieStats { pub struct FreeListsStats { /// The distribution of area sizes in the free lists pub area_counts: BTreeMap, - /// The number of areas that require an extra page read due to not being aligned - pub extra_unaligned_page_read: u64, + /// The number of free areas that span an extra page due to not being aligned + pub area_extra_unaligned_page: u64, } impl Default for FreeListsStats { fn default() -> Self { Self { area_counts: area_size_iter().map(|(_, size)| (size, 0)).collect(), - extra_unaligned_page_read: 0, + area_extra_unaligned_page: 0, } } } @@ -334,28 +346,32 @@ impl NodeStore { // collect trie stats { - // update the area count - let area_count = trie_stats - .area_counts - .get_mut(&area_size) - .expect("area size is initialized when trie_stats is created"); - *area_count = area_count.saturating_add(1); - // collect the trie bytes - trie_stats.trie_bytes = trie_stats.trie_bytes.saturating_add(node_bytes); - // collect low occupancy area count, add 1 for the area size index byte - let smallest_area_index = AreaIndex::from_size(node_bytes) - .expect("impossible since we checked that node_bytes <= area_size"); - if smallest_area_index < area_index { - trie_stats.low_occupancy_area_count = - trie_stats.low_occupancy_area_count.saturating_add(1); + if let Some(value) = node.value() { + // collect kv count + trie_stats.kv_count = trie_stats.kv_count.saturating_add(1); + // collect kv pair bytes - this is the minimum number of bytes needed to store the data + let key_bytes = current_path_prefix.0.len().div_ceil(2); + let value_bytes = value.len(); + trie_stats.kv_bytes = trie_stats + .kv_bytes + .saturating_add(key_bytes as u64) + .saturating_add(value_bytes as u64); } // collect the number of areas that requires reading an extra page due to not being aligned if extra_read_pages(subtrie_root_address, area_size) .expect("impossible since we checked in visited.insert_area()") > 0 { - trie_stats.extra_unaligned_page_read = - trie_stats.extra_unaligned_page_read.saturating_add(1); + trie_stats.area_extra_unaligned_page = + trie_stats.area_extra_unaligned_page.saturating_add(1); + } + // collect the number of nodes that requires reading an extra page due to not being aligned + if extra_read_pages(subtrie_root_address, node_bytes) + .expect("impossible since we checked in visited.insert_area()") + > 0 + { + trie_stats.node_extra_unaligned_page = + trie_stats.node_extra_unaligned_page.saturating_add(1); } } @@ -365,6 +381,21 @@ impl NodeStore { Node::Branch(branch) => { let num_children = branch.children_iter().count(); { + // update the branch area count + let branch_area_count = trie_stats + .branch_area_counts + .get_mut(&area_size) + .expect("area size is initialized when trie_stats is created and we checked that node_bytes <= area_size"); + *branch_area_count = branch_area_count.saturating_add(1); + // collect the branch bytes + trie_stats.branch_bytes = trie_stats.branch_bytes.saturating_add(node_bytes); + // collect low occupancy area count, add 1 for the area size index byte + let smallest_area_index = AreaIndex::from_size(node_bytes) + .expect("impossible since we checked that node_bytes <= area_size"); + if smallest_area_index < area_index { + trie_stats.low_occupancy_branch_area_count = + trie_stats.low_occupancy_branch_area_count.saturating_add(1); + } // collect the branching factor distribution let branching_factor_count = trie_stats .branching_factors @@ -398,19 +429,25 @@ impl NodeStore { } } } - Node::Leaf(leaf) => { + Node::Leaf(_) => { + // update the leaf area count + let leaf_area_count = trie_stats + .leaf_area_counts + .get_mut(&area_size) + .expect("area size is initialized when trie_stats is created and we checked that node_bytes <= area_size"); + *leaf_area_count = leaf_area_count.saturating_add(1); + // collect the leaf bytes + trie_stats.leaf_bytes = trie_stats.leaf_bytes.saturating_add(node_bytes); + // collect low occupancy area count, add 1 for the area size index byte + let smallest_area_index = AreaIndex::from_size(node_bytes) + .expect("impossible since we checked that node_bytes <= area_size"); + if smallest_area_index < area_index { + trie_stats.low_occupancy_leaf_area_count = + trie_stats.low_occupancy_leaf_area_count.saturating_add(1); + } // collect the depth distribution let depth_count = trie_stats.depths.entry(depth).or_insert(0); *depth_count = depth_count.saturating_add(1); - // collect kv count - trie_stats.kv_count = trie_stats.kv_count.saturating_add(1); - // collect kv pair bytes - this is the minimum number of bytes needed to store the data - let key_bytes = current_path_prefix.0.len().div_ceil(2); - let value_bytes = leaf.value.len(); - trie_stats.kv_bytes = trie_stats - .kv_bytes - .saturating_add(key_bytes as u64) - .saturating_add(value_bytes as u64); } } @@ -429,7 +466,7 @@ impl NodeStore { ) -> (FreeListsStats, Vec) { let mut area_counts: BTreeMap = area_size_iter().map(|(_, size)| (size, 0)).collect(); - let mut multi_page_area_count = 0u64; + let mut area_extra_unaligned_page = 0u64; let mut errors = Vec::new(); let mut free_list_iter = self.free_list_iter(AreaIndex::MIN); @@ -492,7 +529,7 @@ impl NodeStore { .expect("impossible since we checked in visited.insert_area()") > 0 { - multi_page_area_count = multi_page_area_count.saturating_add(1); + area_extra_unaligned_page = area_extra_unaligned_page.saturating_add(1); } } } @@ -500,7 +537,7 @@ impl NodeStore { ( FreeListsStats { area_counts, - extra_unaligned_page_read: multi_page_area_count, + area_extra_unaligned_page, }, errors, ) @@ -658,8 +695,11 @@ mod test { #[expect(clippy::arithmetic_side_effects)] fn gen_test_trie(nodestore: &mut NodeStore) -> TestTrie { let mut high_watermark = NodeStoreHeader::SIZE; - let mut total_bytes_written = 0; - let mut area_counts: BTreeMap = + let mut total_branch_bytes_written = 0; + let mut total_leaf_bytes_written = 0; + let mut branch_area_counts: BTreeMap = + area_size_iter().map(|(_, size)| (size, 0)).collect(); + let mut leaf_area_counts: BTreeMap = area_size_iter().map(|(_, size)| (size, 0)).collect(); let leaf = Node::Leaf(LeafNode { partial_path: Path::from_nibbles_iterator(std::iter::repeat_n([4, 5], 30).flatten()), @@ -670,8 +710,8 @@ mod test { let (bytes_written, stored_area_size) = test_write_new_node(nodestore, &leaf, high_watermark); high_watermark += stored_area_size; - total_bytes_written += bytes_written; - let area_count = area_counts.get_mut(&stored_area_size).unwrap(); + total_leaf_bytes_written += bytes_written; + let area_count = leaf_area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); let mut branch_children = BranchNode::empty_children(); @@ -686,8 +726,8 @@ mod test { let (bytes_written, stored_area_size) = test_write_new_node(nodestore, &branch, high_watermark); high_watermark += stored_area_size; - total_bytes_written += bytes_written; - let area_count = area_counts.get_mut(&stored_area_size).unwrap(); + total_branch_bytes_written += bytes_written; + let area_count = branch_area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); let mut root_children = BranchNode::empty_children(); @@ -702,8 +742,8 @@ mod test { let (bytes_written, stored_area_size) = test_write_new_node(nodestore, &root, high_watermark); high_watermark += stored_area_size; - total_bytes_written += bytes_written; - let area_count = area_counts.get_mut(&stored_area_size).unwrap(); + total_branch_bytes_written += bytes_written; + let area_count = branch_area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); // write the header @@ -715,14 +755,18 @@ mod test { ); let trie_stats = TrieStats { - trie_bytes: total_bytes_written, + branch_bytes: total_branch_bytes_written, + leaf_bytes: total_leaf_bytes_written, kv_count: 1, kv_bytes: 32 + 3, // 32 bytes for the key, 3 bytes for the value - area_counts, + branch_area_counts, + leaf_area_counts, branching_factors: BTreeMap::from([(1, 2)]), depths: BTreeMap::from([(2, 1)]), - low_occupancy_area_count: 0, - extra_unaligned_page_read: 0, + low_occupancy_branch_area_count: 0, + low_occupancy_leaf_area_count: 0, + area_extra_unaligned_page: 0, + node_extra_unaligned_page: 0, }; TestTrie { @@ -861,7 +905,7 @@ mod test { } let expected_free_lists_stats = FreeListsStats { area_counts: free_area_counts, - extra_unaligned_page_read, + area_extra_unaligned_page: extra_unaligned_page_read, }; // write header @@ -955,7 +999,7 @@ mod test { area_counts.insert(area_size2, 2); let expected_free_lists_stats = FreeListsStats { area_counts, - extra_unaligned_page_read: 0, + area_extra_unaligned_page: 0, }; // test that the we traversed all the free areas From b259619f1f64843bee2016e135a7c88af9ba2289 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 19 Aug 2025 12:06:47 -0700 Subject: [PATCH 0905/1053] feat(async-removal): Phase 2 - make `Proposal` trait sync (#1212) This pull request refactors the `Proposal` API in the Firewood database codebase by making the `propose` and `commit` methods synchronous instead of asynchronous. The change affects both the implementation and usage of these methods across benchmarks, examples, tests, and trait definitions. The update simplifies the API and eliminates unnecessary `.await` calls, improving code clarity and potentially reducing overhead. --- benchmark/src/create.rs | 2 +- benchmark/src/single.rs | 2 +- benchmark/src/tenkrandom.rs | 2 +- benchmark/src/zipf.rs | 2 +- firewood/benches/hashops.rs | 2 +- firewood/examples/insert.rs | 2 +- firewood/src/db.rs | 35 +++++++++++++++++------------------ firewood/src/v2/api.rs | 10 +++++----- fwdctl/src/delete.rs | 2 +- fwdctl/src/insert.rs | 2 +- 10 files changed, 30 insertions(+), 31 deletions(-) diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index c51ff87a5841..8ca522682000 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -34,7 +34,7 @@ impl TestRunner for Create { let batch = Self::generate_inserts(key * keys, args.global_opts.batch_size); let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; + proposal.commit()?; } let duration = start.elapsed(); info!( diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index f982756b2778..90679a9b1295 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -36,7 +36,7 @@ impl TestRunner for Single { value: vec![batch_id as u8], }); let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; + proposal.commit()?; if log::log_enabled!(log::Level::Debug) && batch_id % 1000 == 999 { debug!( diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index d58fd01fc104..7613a3f7228f 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -33,7 +33,7 @@ impl TestRunner for TenKRandom { .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) .collect(); let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; + proposal.commit()?; low += twenty_five_pct; high += twenty_five_pct; } diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 6f3d9b5589f5..3afbea66b06e 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -72,7 +72,7 @@ impl TestRunner for Zipf { ); } let proposal = db.propose(batch).await.expect("proposal should succeed"); - proposal.commit().await?; + proposal.commit()?; if log::log_enabled!(log::Level::Debug) { debug!( diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index f960da9ad985..8c5d8f19bf03 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -121,7 +121,7 @@ fn bench_db(criterion: &mut Criterion) { let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) .unwrap(); - db.propose(batch_ops).await.unwrap().commit().await.unwrap(); + db.propose(batch_ops).await.unwrap().commit().unwrap(); }, BatchSize::SmallInput, ); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 370c18df55d1..47ac2771f933 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -91,7 +91,7 @@ async fn main() -> Result<(), Box> { #[expect(clippy::unwrap_used)] let proposal = db.propose(batch.clone()).await.unwrap(); - proposal.commit().await?; + proposal.commit()?; verify_keys(&db, verify).await?; } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 84e48247b48a..bee3dc80a00c 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -371,19 +371,18 @@ impl api::DbView for Proposal<'_> { } } -#[async_trait] impl<'db> api::Proposal for Proposal<'db> { type Proposal = Proposal<'db>; #[fastrace::trace(short_name = true)] - async fn propose( + fn propose( &self, - batch: (impl IntoIterator + Send), + batch: impl IntoIterator, ) -> Result { self.create_proposal(batch) } - async fn commit(self) -> Result<(), api::Error> { + fn commit(self) -> Result<(), api::Error> { Ok(self.db.manager.commit(self.nodestore.clone())?) } } @@ -500,7 +499,7 @@ mod test { assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v"); assert_eq!(proposal.val(b"notfound").await.unwrap(), None); - proposal.commit().await.unwrap(); + proposal.commit().unwrap(); let batch = vec![BatchOp::Put { key: b"k", @@ -529,7 +528,7 @@ mod test { }, ]; let proposal = db.propose(batch).await.unwrap(); - proposal.commit().await.unwrap(); + proposal.commit().unwrap(); println!("{:?}", db.root_hash().await.unwrap().unwrap()); let db = db.reopen(); @@ -579,7 +578,7 @@ mod test { drop(proposal2); // commit the first proposal - proposal1.commit().await.unwrap(); + proposal1.commit().unwrap(); // Ensure we committed the first proposal's data let committed = db.root_hash().await.unwrap().unwrap(); let historical = db.revision(committed).await.unwrap(); @@ -613,14 +612,14 @@ mod test { key: b"k2", value: b"v2", }]; - let proposal2 = proposal1.propose(batch2).await.unwrap(); + let proposal2 = proposal1.propose(batch2).unwrap(); assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); let batch3 = vec![BatchOp::Put { key: b"k3", value: b"v3", }]; - let proposal3 = proposal2.propose(batch3).await.unwrap(); + let proposal3 = proposal2.propose(batch3).unwrap(); assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); // the proposal is dropped here, but the underlying @@ -631,7 +630,7 @@ mod test { drop(proposal2); // commit the first proposal - proposal1.commit().await.unwrap(); + proposal1.commit().unwrap(); // Ensure we committed the first proposal's data let committed = db.root_hash().await.unwrap().unwrap(); let historical = db.revision(committed).await.unwrap(); @@ -661,7 +660,7 @@ mod test { }]; let proposal = db.propose(batch).await.unwrap(); let historical_hash = proposal.root_hash().await.unwrap().unwrap(); - proposal.commit().await.unwrap(); + proposal.commit().unwrap(); // Create a new proposal (uncommitted) let batch = vec![BatchOp::Put { @@ -714,7 +713,7 @@ mod test { // create two proposals, second one has a base of the first one let proposal1 = db.propose(kviter.by_ref().take(N / 2)).await.unwrap(); - let proposal2 = proposal1.propose(kviter).await.unwrap(); + let proposal2 = proposal1.propose(kviter).unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal let mut kviter = keys.iter().zip(vals.iter()); @@ -733,7 +732,7 @@ mod test { assert_eq!(proposal1.val(k).await.unwrap(), None); } - proposal1.commit().await.unwrap(); + proposal1.commit().unwrap(); // all keys are still in the second proposal (first is no longer accessible) for (k, v) in keys.iter().zip(vals.iter()) { @@ -741,7 +740,7 @@ mod test { } // commit the second proposal - proposal2.commit().await.unwrap(); + proposal2.commit().unwrap(); // all keys are in the database let committed = db.root_hash().await.unwrap().unwrap(); @@ -779,7 +778,7 @@ mod test { batch }); let proposal = db.propose(batch).await.unwrap(); - proposal.commit().await.unwrap(); + proposal.commit().unwrap(); // check the database for consistency, sometimes checking the hashes let hash_check = rng.random(); @@ -818,7 +817,7 @@ mod test { Vec::>::with_capacity(NUM_PROPOSALS), async |mut proposals, ops| { let proposal = if let Some(parent) = proposals.last() { - parent.propose(ops).await.unwrap() + parent.propose(ops).unwrap() } else { db.propose(ops).await.unwrap() }; @@ -839,7 +838,7 @@ mod test { // commit the proposals for proposal in proposals { - proposal.commit().await.unwrap(); + proposal.commit().unwrap(); } // get the last committed revision @@ -878,7 +877,7 @@ mod test { // Commit task scope.spawn(async move { while let Some(proposal) = rx.recv().await { - let result = proposal.commit().await; + let result = proposal.commit(); // send result back to the main thread, both for synchronization and stopping the // test on error result_tx.send(result).await.unwrap(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 607493c1ce24..b0db0c169695 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -314,13 +314,13 @@ pub trait DbView { /// A proposal type must also implement everything in a /// [`DbView`], which means you can fetch values from it or /// obtain proofs. -#[async_trait] pub trait Proposal: DbView + Send + Sync { /// The type of a proposal type Proposal: DbView + Proposal; /// Commit this revision - async fn commit(self) -> Result<(), Error>; + #[expect(clippy::missing_errors_doc)] + fn commit(self) -> Result<(), Error>; /// Propose a new revision on top of an existing proposal /// @@ -331,10 +331,10 @@ pub trait Proposal: DbView + Send + Sync { /// # Return value /// /// A new proposal - /// - async fn propose( + #[expect(clippy::missing_errors_doc)] + fn propose( &self, - data: (impl IntoIterator + Send), + data: impl IntoIterator, ) -> Result; } diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 4a8fde414b4e..1d5d14a36c80 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -27,7 +27,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { key: opts.key.clone(), }]; let proposal = db.propose(batch).await?; - proposal.commit().await?; + proposal.commit()?; println!("key {} deleted successfully", opts.key); Ok(()) diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index c8444dd8cb94..7325a555bfc9 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -32,7 +32,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { value: opts.value.bytes().collect(), }]; let proposal = db.propose(batch).await?; - proposal.commit().await?; + proposal.commit()?; println!("{}", opts.key); Ok(()) From 4322690132cbc3047c61f9a2f10bba805c0ab061 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:53:21 -0500 Subject: [PATCH 0906/1053] feat(checker): add checker fix template (#1199) --- fwdctl/src/check.rs | 119 +++++++++++++++++++++++-------------- storage/src/checker/mod.rs | 102 ++++++++++++++++++++++++------- storage/src/lib.rs | 22 ++++++- 3 files changed, 176 insertions(+), 67 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index bba3cb8c3d2a..82d0fc3973da 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use clap::Args; use firewood::v2::api; -use firewood_storage::{CacheReadStrategy, CheckOpt, CheckerReport, FileBacked, NodeStore}; +use firewood_storage::{CacheReadStrategy, CheckOpt, DBStats, FileBacked, NodeStore}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use nonzero_ext::nonzero; @@ -26,6 +26,15 @@ pub struct Options { help = "Should perform hash check" )] pub hash_check: bool, + + /// Whether to fix observed inconsistencies + #[arg( + long, + required = false, + default_value_t = false, + help = "Should fix observed inconsistencies" + )] + pub fix: bool, } pub(super) fn run(opts: &Options) -> Result<(), api::Error> { @@ -51,89 +60,109 @@ pub(super) fn run(opts: &Options) -> Result<(), api::Error> { ) .with_finish(ProgressFinish::WithMessage("Check Completed!".into())); - let report = NodeStore::open(storage)?.check(CheckOpt { + let check_ops = CheckOpt { hash_check: opts.hash_check, progress_bar: Some(progress_bar), - }); - - print_checker_report(report); + }; + + let nodestore = NodeStore::open(storage)?; + let db_stats = if opts.fix { + let report = nodestore.check_and_fix(check_ops); + println!("Fixed Errors ({}):", report.fixed.len()); + for error in report.fixed { + println!("\t{error}"); + } + println!(); + println!("Unfixable Errors ({}):", report.unfixable.len(),); + for (error, io_error) in report.unfixable { + println!("\t{error}"); + if let Some(io_error) = io_error { + println!("\t\tError encountered while fixing: {io_error}"); + } + } + report.db_stats + } else { + let report = nodestore.check(check_ops); + println!("Errors ({}):", report.errors.len()); + for error in report.errors { + println!("\t{error}"); + } + report.db_stats + }; + println!(); + + print_stats_report(db_stats); Ok(()) } #[expect(clippy::cast_precision_loss)] #[expect(clippy::too_many_lines)] -fn print_checker_report(report: CheckerReport) { - let total_branch_area_count = report.trie_stats.branch_area_counts.values().sum::(); - let total_leaf_area_count = report.trie_stats.leaf_area_counts.values().sum::(); +fn print_stats_report(db_stats: DBStats) { + let total_branch_area_count = db_stats.trie_stats.branch_area_counts.values().sum::(); + let total_leaf_area_count = db_stats.trie_stats.leaf_area_counts.values().sum::(); let total_trie_area_count = total_branch_area_count.saturating_add(total_leaf_area_count); - let total_branch_area_bytes = report + let total_branch_area_bytes = db_stats .trie_stats .branch_area_counts .iter() .map(|(area_size, count)| area_size.saturating_mul(*count)) .sum::(); - let total_leaf_area_bytes = report + let total_leaf_area_bytes = db_stats .trie_stats .leaf_area_counts .iter() .map(|(area_size, count)| area_size.saturating_mul(*count)) .sum::(); let total_trie_area_bytes = total_branch_area_bytes.saturating_add(total_leaf_area_bytes); - let total_free_list_area_count = report.free_list_stats.area_counts.values().sum::(); - let total_free_list_area_bytes = report + let total_free_list_area_count = db_stats.free_list_stats.area_counts.values().sum::(); + let total_free_list_area_bytes = db_stats .free_list_stats .area_counts .iter() .map(|(area_size, count)| area_size.saturating_mul(*count)) .sum::(); - println!("Raw Report: {report:?}\n"); - - println!("Errors ({}): ", report.errors.len()); - for error in report.errors { - println!("\t{error}"); - } - // Basic stats println!("\nBasic Stats: "); println!( "\tFirewood Image Size / High Watermark (high_watermark): {}", - report.high_watermark + db_stats.high_watermark ); println!( "\tTotal Key-Value Count (kv_count): {}", - report.trie_stats.kv_count + db_stats.trie_stats.kv_count ); println!( "\tTotal Key-Value Bytes (kv_bytes): {}", - report.trie_stats.kv_bytes + db_stats.trie_stats.kv_bytes ); // Trie statistics println!("\nTrie Stats: "); println!( "\tBranching Factor Distribution: {:?}", - report.trie_stats.branching_factors + db_stats.trie_stats.branching_factors ); - println!("\tDepth Distribution: {:?}", report.trie_stats.depths); + println!("\tDepth Distribution: {:?}", db_stats.trie_stats.depths); // Branch area distribution println!("\nBranch Area Stats: "); println!( "\tTotal Branch Data Bytes (branch_bytes): {}", - report.trie_stats.branch_bytes + db_stats.trie_stats.branch_bytes ); println!("\tTotal Branch Area Count (branch_area_count): {total_branch_area_count}"); println!("\tTotal Branch Area Bytes (branch_area_bytes): {total_branch_area_bytes}"); println!( "\tBranch Area Distribution: {:?}", - report.trie_stats.branch_area_counts + db_stats.trie_stats.branch_area_counts ); println!( "\tBranches that Can Fit Into Smaller Area (low_occupancy_branch_area): {} ({:.2}%)", - report.trie_stats.low_occupancy_branch_area_count, - (report.trie_stats.low_occupancy_branch_area_count as f64 / total_branch_area_count as f64) + db_stats.trie_stats.low_occupancy_branch_area_count, + (db_stats.trie_stats.low_occupancy_branch_area_count as f64 + / total_branch_area_count as f64) * 100.0 ); @@ -141,18 +170,18 @@ fn print_checker_report(report: CheckerReport) { println!("\nLeaf Area Stats: "); println!( "\tTotal Leaf Data Bytes (leaf_bytes): {}", - report.trie_stats.leaf_bytes, + db_stats.trie_stats.leaf_bytes, ); println!("\tTotal Leaf Area Count (leaf_area_count): {total_leaf_area_count}"); println!("\tTotal Leaf Area Bytes (leaf_area_bytes): {total_leaf_area_bytes}"); println!( "\tLeaf Area Distribution: {:?}", - report.trie_stats.leaf_area_counts + db_stats.trie_stats.leaf_area_counts ); println!( "\tLeaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): {} ({:.2}%)", - report.trie_stats.low_occupancy_leaf_area_count, - (report.trie_stats.low_occupancy_leaf_area_count as f64 / total_leaf_area_count as f64) + db_stats.trie_stats.low_occupancy_leaf_area_count, + (db_stats.trie_stats.low_occupancy_leaf_area_count as f64 / total_leaf_area_count as f64) * 100.0 ); @@ -162,46 +191,48 @@ fn print_checker_report(report: CheckerReport) { println!("\tTotal Free List Area Bytes (free_list_area_bytes): {total_free_list_area_bytes}"); println!( "\tFree List Area Distribution: {:?}", - report.free_list_stats.area_counts + db_stats.free_list_stats.area_counts ); // alignment stats println!("\nAlignment Stats: "); println!( "\tTrie Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - report.trie_stats.area_extra_unaligned_page, - (report.trie_stats.area_extra_unaligned_page as f64 / total_trie_area_count as f64) * 100.0 + db_stats.trie_stats.area_extra_unaligned_page, + (db_stats.trie_stats.area_extra_unaligned_page as f64 / total_trie_area_count as f64) + * 100.0 ); println!( "\tFree List Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - report.free_list_stats.area_extra_unaligned_page, - (report.free_list_stats.area_extra_unaligned_page as f64 + db_stats.free_list_stats.area_extra_unaligned_page, + (db_stats.free_list_stats.area_extra_unaligned_page as f64 / total_free_list_area_count as f64) * 100.0 ); println!( "\tTrie Nodes Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - report.trie_stats.node_extra_unaligned_page, - (report.trie_stats.node_extra_unaligned_page as f64 / total_trie_area_count as f64) * 100.0 + db_stats.trie_stats.node_extra_unaligned_page, + (db_stats.trie_stats.node_extra_unaligned_page as f64 / total_trie_area_count as f64) + * 100.0 ); println!("\nAdvanced Stats: "); println!( "\tStorage Overhead: high_watermark / kv_bytes = {:.2}x", - (report.high_watermark as f64 / report.trie_stats.kv_bytes as f64) + (db_stats.high_watermark as f64 / db_stats.trie_stats.kv_bytes as f64) ); - let total_trie_bytes = report + let total_trie_bytes = db_stats .trie_stats .branch_bytes - .saturating_add(report.trie_stats.leaf_bytes); + .saturating_add(db_stats.trie_stats.leaf_bytes); println!( "\tInternal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = {:.2}%", (1f64 - (total_trie_bytes as f64 / total_trie_area_bytes as f64)) * 100.0 ); - let low_occupancy_area_count = report + let low_occupancy_area_count = db_stats .trie_stats .low_occupancy_branch_area_count - .saturating_add(report.trie_stats.low_occupancy_leaf_area_count); + .saturating_add(db_stats.trie_stats.low_occupancy_leaf_area_count); println!( "\tAreas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = {} ({:.2}%)", low_occupancy_area_count, diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 0fe298fedc73..625a6c131950 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -8,8 +8,9 @@ use crate::logger::warn; use crate::nodestore::alloc::FreeAreaWithMetadata; use crate::nodestore::primitives::{AreaIndex, area_size_iter}; use crate::{ - CheckerError, Committed, HashType, HashedNodeReader, IntoHashType, LinearAddress, Node, - NodeStore, Path, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, + CheckerError, Committed, FileIoError, HashType, HashedNodeReader, IntoHashType, LinearAddress, + Node, NodeStore, Path, ReadableStorage, RootReader, StoredAreaParent, TrieNodeParent, + WritableStorage, }; #[cfg(not(feature = "ethhash"))] @@ -57,11 +58,18 @@ pub struct CheckOpt { pub progress_bar: Option, } -#[derive(Debug)] /// Report of the checker results. +#[derive(Debug)] pub struct CheckerReport { /// Errors encountered during the check pub errors: Vec, + /// Statistics about the database + pub db_stats: DBStats, +} + +/// All statistics about the given database image +#[derive(Debug, Default)] +pub struct DBStats { /// The high watermark of the database pub high_watermark: u64, /// Statistics about the trie @@ -70,8 +78,8 @@ pub struct CheckerReport { pub free_list_stats: FreeListsStats, } -#[derive(Debug, PartialEq)] /// Statistics about the trie +#[derive(Debug, PartialEq)] pub struct TrieStats { /// The total number of bytes of compressed branch nodes pub branch_bytes: u64, @@ -118,8 +126,8 @@ impl Default for TrieStats { } } -#[derive(Debug, PartialEq)] /// Statistics about the free list +#[derive(Debug, PartialEq)] pub struct FreeListsStats { /// The distribution of area sizes in the free lists pub area_counts: BTreeMap, @@ -148,25 +156,23 @@ struct SubTrieMetadata { /// [`NodeStore`] checker #[expect(clippy::result_large_err)] -impl NodeStore { +impl NodeStore { /// Go through the filebacked storage and check for any inconsistencies. It proceeds in the following steps: /// 1. Check the header /// 2. traverse the trie and check the nodes /// 3. check the free list - /// 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? + /// 4. report leaked areas (areas that have not yet been visited) /// # Errors /// Returns a [`CheckerError`] if the database is inconsistent. pub fn check(&self, opt: CheckOpt) -> CheckerReport { // 1. Check the header - let db_size = self.size(); - let mut visited = match LinearAddressRangeSet::new(db_size) { + let high_watermark = self.size(); + let mut visited = match LinearAddressRangeSet::new(high_watermark) { Ok(visited) => visited, Err(e) => { return CheckerReport { errors: vec![e], - high_watermark: db_size, - trie_stats: TrieStats::default(), - free_list_stats: FreeListsStats::default(), + db_stats: DBStats::default(), }; } }; @@ -175,7 +181,7 @@ impl NodeStore { // 2. traverse the trie and check the nodes if let Some(progress_bar) = &opt.progress_bar { - progress_bar.set_length(db_size); + progress_bar.set_length(high_watermark); progress_bar.set_message("Traversing the trie..."); } let trie_stats = self @@ -207,26 +213,23 @@ impl NodeStore { self.visit_freelist(&mut visited, opt.progress_bar.as_ref()); errors.extend(free_list_traverse_errors); - // 4. check leaked areas - what are the spaces between trie nodes and free lists we have traversed? + // 4. report leaked areas (areas that have not yet been visited) if let Some(progress_bar) = &opt.progress_bar { progress_bar.set_message("Checking leaked areas..."); } let leaked_ranges = visited.complement(); if !leaked_ranges.is_empty() { warn!("Found leaked ranges: {leaked_ranges}"); - { - // TODO: add leaked areas to the free list - let _leaked_areas = - self.split_all_leaked_ranges(&leaked_ranges, opt.progress_bar.as_ref()); - } errors.push(CheckerError::AreaLeaks(leaked_ranges)); } CheckerReport { errors, - high_watermark: db_size, - trie_stats, - free_list_stats, + db_stats: DBStats { + high_watermark, + trie_stats, + free_list_stats, + }, } } @@ -553,6 +556,61 @@ impl NodeStore { } Ok(()) } +} + +/// Report of the fix operation +#[derive(Debug)] +pub struct FixReport { + /// Errors that were fixed + pub fixed: Vec, + /// Errors that were not fixed, either because they are unfixable or because an IO error occurred + pub unfixable: Vec<(CheckerError, Option)>, + /// Statistics about the database + pub db_stats: DBStats, +} + +impl NodeStore { + /// Check the node store and fix any errors found. + /// Returns a report of the fix operation. + pub fn check_and_fix(&self, opt: CheckOpt) -> FixReport { + let report = self.check(opt); + self.fix(report) + } + + fn fix(&self, check_report: CheckerReport) -> FixReport { + let fixed = Vec::new(); + let mut unfixable = Vec::new(); + + for error in check_report.errors { + match error.parent() { + Some(StoredAreaParent::TrieNode(_)) => { + warn!("Fix for trie node error not yet implemented"); + unfixable.push((error, None)); + } + Some(StoredAreaParent::FreeList(_)) => { + warn!("Fix for free list error not yet implemented"); + unfixable.push((error, None)); + } + None => { + if let CheckerError::AreaLeaks(ranges) = &error { + { + let _leaked_areas = self.split_all_leaked_ranges(ranges, None); + // TODO: add _leaked_areas to the free list + } + unfixable.push((error, None)); + } else { + unfixable.push((error, None)); + } + } + } + } + + FixReport { + fixed, + unfixable, + db_stats: check_report.db_stats, + } + } /// Wrapper around `split_into_leaked_areas` that iterates over a collection of ranges. fn split_all_leaked_ranges<'a>( diff --git a/storage/src/lib.rs b/storage/src/lib.rs index c1e08f429383..a9321a95214c 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -40,7 +40,7 @@ pub mod logger; /// Macros module for defining macros used in the storage module pub mod macros; // re-export these so callers don't need to know where they are -pub use checker::{CheckOpt, CheckerReport, FreeListsStats, TrieStats}; +pub use checker::{CheckOpt, CheckerReport, DBStats, FreeListsStats, TrieStats}; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; @@ -288,6 +288,26 @@ pub enum CheckerError { }, } +impl CheckerError { + const fn parent(&self) -> Option { + match self { + CheckerError::InvalidDBSize { .. } + | CheckerError::AreaLeaks(_) + | CheckerError::UnpersistedRoot => None, + CheckerError::AreaOutOfBounds { parent, .. } + | CheckerError::AreaIntersects { parent, .. } + | CheckerError::AreaMisaligned { parent, .. } + | CheckerError::IO { parent, .. } => Some(*parent), + CheckerError::HashMismatch { parent, .. } + | CheckerError::NodeLargerThanArea { parent, .. } + | CheckerError::InvalidKey { parent, .. } => Some(StoredAreaParent::TrieNode(*parent)), + CheckerError::FreelistAreaSizeMismatch { parent, .. } => { + Some(StoredAreaParent::FreeList(*parent)) + } + } + } +} + impl From for Vec { fn from(error: CheckerError) -> Self { vec![error] From e72fa1733c3d17f43c379dc524da47f9ffb5be04 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:21:02 -0500 Subject: [PATCH 0907/1053] chore(checker): add function to compute area counts and bytes (#1218) --- fwdctl/src/check.rs | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 82d0fc3973da..76fd4583e265 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -1,8 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::path::PathBuf; use std::sync::Arc; +use std::{collections::BTreeMap, path::PathBuf}; use clap::Args; use firewood::v2::api; @@ -96,32 +96,27 @@ pub(super) fn run(opts: &Options) -> Result<(), api::Error> { Ok(()) } +fn calculate_area_totals(area_counts: &BTreeMap) -> (u64, u64) { + let total_area_count = area_counts.values().sum::(); + let total_area_bytes = area_counts + .iter() + .map(|(area_size, count)| area_size.saturating_mul(*count)) + .sum::(); + (total_area_count, total_area_bytes) +} + #[expect(clippy::cast_precision_loss)] #[expect(clippy::too_many_lines)] fn print_stats_report(db_stats: DBStats) { - let total_branch_area_count = db_stats.trie_stats.branch_area_counts.values().sum::(); - let total_leaf_area_count = db_stats.trie_stats.leaf_area_counts.values().sum::(); + let (total_branch_area_count, total_branch_area_bytes) = + calculate_area_totals(&db_stats.trie_stats.branch_area_counts); + let (total_leaf_area_count, total_leaf_area_bytes) = + calculate_area_totals(&db_stats.trie_stats.leaf_area_counts); let total_trie_area_count = total_branch_area_count.saturating_add(total_leaf_area_count); - let total_branch_area_bytes = db_stats - .trie_stats - .branch_area_counts - .iter() - .map(|(area_size, count)| area_size.saturating_mul(*count)) - .sum::(); - let total_leaf_area_bytes = db_stats - .trie_stats - .leaf_area_counts - .iter() - .map(|(area_size, count)| area_size.saturating_mul(*count)) - .sum::(); let total_trie_area_bytes = total_branch_area_bytes.saturating_add(total_leaf_area_bytes); - let total_free_list_area_count = db_stats.free_list_stats.area_counts.values().sum::(); - let total_free_list_area_bytes = db_stats - .free_list_stats - .area_counts - .iter() - .map(|(area_size, count)| area_size.saturating_mul(*count)) - .sum::(); + + let (total_free_list_area_count, total_free_list_area_bytes) = + calculate_area_totals(&db_stats.free_list_stats.area_counts); // Basic stats println!("\nBasic Stats: "); From 23b05d56edc2697c703f7e26774d124c57a9a737 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 20 Aug 2025 07:49:50 -0700 Subject: [PATCH 0908/1053] chore(release): prepare for v0.0.11 (#1216) This PR bumps the crate versions to `0.0.11` and updates the CHANGELOG generation to also include links to the pull request. We could use the `github` integration in git-cliff to ensure we get the actual pull request link but that adds a significant amount of time to the generation time because of all of the github API requests. It also requires the user have `gh` installed and be authenticated. --- CHANGELOG.md | 567 ++++++++++++++++++++++++++++----------------------- Cargo.toml | 12 +- RELEASE.md | 18 +- cliff.toml | 5 +- clippy.toml | 2 + 5 files changed, 330 insertions(+), 274 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f4673c61e99..ec1bfc993184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,406 +2,459 @@ All notable changes to this project will be documented in this file. +## [0.0.11] - 2025-08-20 + +### 🚀 Features + +- *(checker)* Checker returns all errors found in the report ([#1176](https://github.com/ava-labs/firewood/pull/1176)) +- Remove Default impl on HashType ([#1169](https://github.com/ava-labs/firewood/pull/1169)) +- Update revision manager error ([#1170](https://github.com/ava-labs/firewood/pull/1170)) +- *(checker)* Return the leaked areas in the checker report ([#1179](https://github.com/ava-labs/firewood/pull/1179)) +- *(checker)* Update unaligned page count ([#1181](https://github.com/ava-labs/firewood/pull/1181)) +- *(checker)* Add error when node data is bigger than area size ([#1183](https://github.com/ava-labs/firewood/pull/1183)) +- Remove `Batch` type alias ([#1171](https://github.com/ava-labs/firewood/pull/1171)) +- *(checker)* Annotate IO error with parent pointer in checker errors ([#1188](https://github.com/ava-labs/firewood/pull/1188)) +- *(checker)* Do not return physical size to accomodate raw disks ([#1200](https://github.com/ava-labs/firewood/pull/1200)) +- *(ffi)* Add BorrowedBytes type ([#1174](https://github.com/ava-labs/firewood/pull/1174)) +- *(checker)* More clear print formats for checker report ([#1201](https://github.com/ava-labs/firewood/pull/1201)) +- *(async-removal)* Phase 1 - lint on `clippy::unused_async` ([#1211](https://github.com/ava-labs/firewood/pull/1211)) +- *(checker)* Collect statistics for branches and leaves separately ([#1206](https://github.com/ava-labs/firewood/pull/1206)) +- *(async-removal)* Phase 2 - make `Proposal` trait sync ([#1212](https://github.com/ava-labs/firewood/pull/1212)) +- *(checker)* Add checker fix template ([#1199](https://github.com/ava-labs/firewood/pull/1199)) + +### 🐛 Bug Fixes + +- *(checker)* Skip freelist after first encountering an invalid free area ([#1178](https://github.com/ava-labs/firewood/pull/1178)) +- Fix race around reading nodes during commit ([#1180](https://github.com/ava-labs/firewood/pull/1180)) +- *(fwdctl)* [**breaking**] Db path consistency + no auto-create ([#1189](https://github.com/ava-labs/firewood/pull/1189)) + +### ⚡ Performance + +- Remove unnecessary Box on `OffsetReader` ([#1185](https://github.com/ava-labs/firewood/pull/1185)) + +### 🧪 Testing + +- Add read-during-commit test ([#1186](https://github.com/ava-labs/firewood/pull/1186)) +- Fix merkle compatibility test ([#1173](https://github.com/ava-labs/firewood/pull/1173)) +- Ban `rand::rng()` and provide an env seeded alternative ([#1192](https://github.com/ava-labs/firewood/pull/1192)) +- Reenable eth merkle compatibility test ([#1214](https://github.com/ava-labs/firewood/pull/1214)) + +### ⚙️ Miscellaneous Tasks + +- Metric change detection comments only on 1st-party PRs ([#1167](https://github.com/ava-labs/firewood/pull/1167)) +- Run CI on macOS ([#1168](https://github.com/ava-labs/firewood/pull/1168)) +- Update .golangci.yaml ([#1166](https://github.com/ava-labs/firewood/pull/1166)) +- Allow FreeListIterator to skip to next free list ([#1177](https://github.com/ava-labs/firewood/pull/1177)) +- Address lints triggered with rust 1.89 ([#1182](https://github.com/ava-labs/firewood/pull/1182)) +- Deny `undocumented-unsafe-blocks` ([#1172](https://github.com/ava-labs/firewood/pull/1172)) +- Fwdctl cleanups ([#1190](https://github.com/ava-labs/firewood/pull/1190)) +- AreaIndex newtype ([#1193](https://github.com/ava-labs/firewood/pull/1193)) +- Remove setup-protoc ([#1203](https://github.com/ava-labs/firewood/pull/1203)) +- Automatically label PRs from external contributors ([#1195](https://github.com/ava-labs/firewood/pull/1195)) +- Don't fail fast on certain jobs ([#1198](https://github.com/ava-labs/firewood/pull/1198)) +- Add PathGuard type when computing hashes ([#1202](https://github.com/ava-labs/firewood/pull/1202)) +- *(checker)* Add function to compute area counts and bytes ([#1218](https://github.com/ava-labs/firewood/pull/1218)) + ## [0.0.10] - 2025-08-01 ### 🚀 Features -- *(async-iterator)* Implement (#1096) -- Export logs (#1070) -- Render the commit sha in fwdctl (#1109) -- Update proof types to be generic over mutable or immutable collections (#1121) -- Refactor value types to use the type alias (#1122) -- *(dumper)* Child links in hex (easy) (#1124) -- *(deferred-allocate)* Part 3: Defer allocate (#1061) -- *(checker)* Disable buggy ethhash checker (#1127) -- Add `Children` type alias (#1123) -- Make NodeStore more generic (#1134) -- *(checker)* Add progress bar (#1105) -- *(checker)* Checker errors include reference to parent (#1085) -- Update RangeProof structure (#1136) -- Update range\_proof signature (#1151) +- *(async-iterator)* Implement ([#1096](https://github.com/ava-labs/firewood/pull/1096)) +- Export logs ([#1070](https://github.com/ava-labs/firewood/pull/1070)) +- Render the commit sha in fwdctl ([#1109](https://github.com/ava-labs/firewood/pull/1109)) +- Update proof types to be generic over mutable or immutable collections ([#1121](https://github.com/ava-labs/firewood/pull/1121)) +- Refactor value types to use the type alias ([#1122](https://github.com/ava-labs/firewood/pull/1122)) +- *(dumper)* Child links in hex (easy) ([#1124](https://github.com/ava-labs/firewood/pull/1124)) +- *(deferred-allocate)* Part 3: Defer allocate ([#1061](https://github.com/ava-labs/firewood/pull/1061)) +- *(checker)* Disable buggy ethhash checker ([#1127](https://github.com/ava-labs/firewood/pull/1127)) +- Add `Children` type alias ([#1123](https://github.com/ava-labs/firewood/pull/1123)) +- Make NodeStore more generic ([#1134](https://github.com/ava-labs/firewood/pull/1134)) +- *(checker)* Add progress bar ([#1105](https://github.com/ava-labs/firewood/pull/1105)) +- *(checker)* Checker errors include reference to parent ([#1085](https://github.com/ava-labs/firewood/pull/1085)) +- Update RangeProof structure ([#1136](https://github.com/ava-labs/firewood/pull/1136)) +- Update range_proof signature ([#1151](https://github.com/ava-labs/firewood/pull/1151)) - *(checker)* Add InvalidKey error -- *(deferred-persist)* Part 1: unpersisted gauge (#1116) -- *(checker)* Collect basic statistics while checking the db image (#1149) -- *(fwdctl)* Add support for dump formats (#1161) -- *(ffi)* Remove the Arc wrapper around Proposal (#1160) +- *(deferred-persist)* Part 1: unpersisted gauge ([#1116](https://github.com/ava-labs/firewood/pull/1116)) +- *(checker)* Collect basic statistics while checking the db image ([#1149](https://github.com/ava-labs/firewood/pull/1149)) +- *(fwdctl)* Add support for dump formats ([#1161](https://github.com/ava-labs/firewood/pull/1161)) +- *(ffi)* Remove the Arc wrapper around Proposal ([#1160](https://github.com/ava-labs/firewood/pull/1160)) ### 🐛 Bug Fixes -- *(fwdctl)* Fix fwdctl with ethhash (#1091) -- *(checker)* Fix checker with ethhash (#1130) -- Fix broken deserialization of old FreeArea format (#1147) -- Create metrics registration macros (#980) +- *(fwdctl)* Fix fwdctl with ethhash ([#1091](https://github.com/ava-labs/firewood/pull/1091)) +- *(checker)* Fix checker with ethhash ([#1130](https://github.com/ava-labs/firewood/pull/1130)) +- Fix broken deserialization of old FreeArea format ([#1147](https://github.com/ava-labs/firewood/pull/1147)) +- Create metrics registration macros ([#980](https://github.com/ava-labs/firewood/pull/980)) ### 💼 Other -- Cargo.toml upgrades and fixes (#1099) -- *(deps)* Update criterion requirement from 0.6.0 to 0.7.0 (#1140) +- Cargo.toml upgrades and fixes ([#1099](https://github.com/ava-labs/firewood/pull/1099)) +- *(deps)* Update criterion requirement from 0.6.0 to 0.7.0 ([#1140](https://github.com/ava-labs/firewood/pull/1140)) ### 📚 Documentation -- Update ffi/README.md to include configs, metrics, and logs (#1111) +- Update ffi/README.md to include configs, metrics, and logs ([#1111](https://github.com/ava-labs/firewood/pull/1111)) ### 🎨 Styling -- Remove unnecessary string in error (#1104) +- Remove unnecessary string in error ([#1104](https://github.com/ava-labs/firewood/pull/1104)) ### 🧪 Testing -- Add fuzz testing for checker, with fixes (#1118) -- Port TestDeepPropose from go->rust (#1115) +- Add fuzz testing for checker, with fixes ([#1118](https://github.com/ava-labs/firewood/pull/1118)) +- Port TestDeepPropose from go->rust ([#1115](https://github.com/ava-labs/firewood/pull/1115)) ### ⚙️ Miscellaneous Tasks -- Add propose-on-propose test (#1097) -- Implement newtype for LInearAddress (#1086) -- Refactor verifying value digests (#1119) -- Checker test cleanups (#1131) -- Minor cleanups and nits (#1133) -- Add a golang install script (#1141) -- Move all merkle tests into a subdirectory (#1150) -- Require license header for ffi code (#1159) -- Bump version to v0.0.10 +- Add propose-on-propose test ([#1097](https://github.com/ava-labs/firewood/pull/1097)) +- Implement newtype for LInearAddress ([#1086](https://github.com/ava-labs/firewood/pull/1086)) +- Refactor verifying value digests ([#1119](https://github.com/ava-labs/firewood/pull/1119)) +- Checker test cleanups ([#1131](https://github.com/ava-labs/firewood/pull/1131)) +- Minor cleanups and nits ([#1133](https://github.com/ava-labs/firewood/pull/1133)) +- Add a golang install script ([#1141](https://github.com/ava-labs/firewood/pull/1141)) +- Move all merkle tests into a subdirectory ([#1150](https://github.com/ava-labs/firewood/pull/1150)) +- Require license header for ffi code ([#1159](https://github.com/ava-labs/firewood/pull/1159)) +- Bump version to v0.0.10 ([#1165](https://github.com/ava-labs/firewood/pull/1165)) ## [0.0.9] - 2025-07-17 ### 🚀 Features -- *(ffi)* Add gauges to metrics reporter (#1035) -- *(delayed-persist)* Part 1: Roots may be in mem (#1041) -- *(delayed-persist)* 2.1: Unpersisted deletions (#1045) -- *(delayed-persist)* Part 2.2: Branch Children (#1047) -- [**breaking**] Export firewood metrics (#1044) -- *(checker)* Add error to report finding leaked areas (#1052) -- *(delayed-persist)* Dump unpersisted nodestore (#1055) -- *(checker)* Split leaked ranges into valid areas (#1059) -- *(checker)* Check for misaligned stored areas (#1046) -- [**breaking**] Auto open or create with truncate (#1064) -- *(deferred-allocate)* UnpersistedIterator (#1060) -- *(checker)* Add hash checks (#1063) +- *(ffi)* Add gauges to metrics reporter ([#1035](https://github.com/ava-labs/firewood/pull/1035)) +- *(delayed-persist)* Part 1: Roots may be in mem ([#1041](https://github.com/ava-labs/firewood/pull/1041)) +- *(delayed-persist)* 2.1: Unpersisted deletions ([#1045](https://github.com/ava-labs/firewood/pull/1045)) +- *(delayed-persist)* Part 2.2: Branch Children ([#1047](https://github.com/ava-labs/firewood/pull/1047)) +- [**breaking**] Export firewood metrics ([#1044](https://github.com/ava-labs/firewood/pull/1044)) +- *(checker)* Add error to report finding leaked areas ([#1052](https://github.com/ava-labs/firewood/pull/1052)) +- *(delayed-persist)* Dump unpersisted nodestore ([#1055](https://github.com/ava-labs/firewood/pull/1055)) +- *(checker)* Split leaked ranges into valid areas ([#1059](https://github.com/ava-labs/firewood/pull/1059)) +- *(checker)* Check for misaligned stored areas ([#1046](https://github.com/ava-labs/firewood/pull/1046)) +- [**breaking**] Auto open or create with truncate ([#1064](https://github.com/ava-labs/firewood/pull/1064)) +- *(deferred-allocate)* UnpersistedIterator ([#1060](https://github.com/ava-labs/firewood/pull/1060)) +- *(checker)* Add hash checks ([#1063](https://github.com/ava-labs/firewood/pull/1063)) ### 🐛 Bug Fixes -- Avoid reference to LinearAddress (#1042) -- Remove dependency on serde (#1066) -- Encoding partial paths for leaf nodes (#1067) -- Root\_hash\_reversed\_deletions duplicate keys (#1076) -- *(checker)* Avoid checking physical file size for compatibility (#1079) +- Avoid reference to LinearAddress ([#1042](https://github.com/ava-labs/firewood/pull/1042)) +- Remove dependency on serde ([#1066](https://github.com/ava-labs/firewood/pull/1066)) +- Encoding partial paths for leaf nodes ([#1067](https://github.com/ava-labs/firewood/pull/1067)) +- Root_hash_reversed_deletions duplicate keys ([#1076](https://github.com/ava-labs/firewood/pull/1076)) +- *(checker)* Avoid checking physical file size for compatibility ([#1079](https://github.com/ava-labs/firewood/pull/1079)) ### 🎨 Styling -- Remove unnecessary error descriptor (#1049) +- Remove unnecessary error descriptor ([#1049](https://github.com/ava-labs/firewood/pull/1049)) ### ⚙️ Miscellaneous Tasks -- *(build)* Remove unused dependencies (#1037) -- Update firewood in grpc-testtool (#1040) -- Aaron is requested only for .github (#1043) -- Remove `#[allow]`s no longer needed (#1022) -- Split nodestore into functional areas (#1048) -- Update `golangci-lint` (#1053) -- Update CODEOWNERS (#1080) -- Run CI with --no-default-features (#1081) -- Release 0.0.9 (#1084) +- *(build)* Remove unused dependencies ([#1037](https://github.com/ava-labs/firewood/pull/1037)) +- Update firewood in grpc-testtool ([#1040](https://github.com/ava-labs/firewood/pull/1040)) +- Aaron is requested only for .github ([#1043](https://github.com/ava-labs/firewood/pull/1043)) +- Remove `#[allow]`s no longer needed ([#1022](https://github.com/ava-labs/firewood/pull/1022)) +- Split nodestore into functional areas ([#1048](https://github.com/ava-labs/firewood/pull/1048)) +- Update `golangci-lint` ([#1053](https://github.com/ava-labs/firewood/pull/1053)) +- Update CODEOWNERS ([#1080](https://github.com/ava-labs/firewood/pull/1080)) +- Run CI with --no-default-features ([#1081](https://github.com/ava-labs/firewood/pull/1081)) +- Release 0.0.9 ([#1084](https://github.com/ava-labs/firewood/pull/1084)) ## [0.0.8] - 2025-07-07 ### 🚀 Features -- *(checker)* Firewood checker framework (#936) -- Enable a configurable free list cache in the FFI (#1017) -- *(nodestore)* Add functionalities to iterate the free list (#1015) -- *(checker)* Traverse free lists (#1026) +- *(checker)* Firewood checker framework ([#936](https://github.com/ava-labs/firewood/pull/936)) +- Enable a configurable free list cache in the FFI ([#1017](https://github.com/ava-labs/firewood/pull/1017)) +- *(nodestore)* Add functionalities to iterate the free list ([#1015](https://github.com/ava-labs/firewood/pull/1015)) +- *(checker)* Traverse free lists ([#1026](https://github.com/ava-labs/firewood/pull/1026)) ### 🐛 Bug Fixes -- Unnecessary quotes in publish action (#996) -- Report IO errors (#1005) -- Publish firewood-macros (#1019) -- Logger macros causing linting warnings (#1021) +- Unnecessary quotes in publish action ([#996](https://github.com/ava-labs/firewood/pull/996)) +- Report IO errors ([#1005](https://github.com/ava-labs/firewood/pull/1005)) +- Publish firewood-macros ([#1019](https://github.com/ava-labs/firewood/pull/1019)) +- Logger macros causing linting warnings ([#1021](https://github.com/ava-labs/firewood/pull/1021)) ### 💼 Other -- *(deps)* Update lru requirement from 0.14.0 to 0.15.0 (#1001) -- *(deps)* Update lru requirement from 0.15.0 to 0.16.0 (#1023) -- *(deps)* Upgrade sha2, tokio, clap, fastrace, serde... (#1025) +- *(deps)* Update lru requirement from 0.14.0 to 0.15.0 ([#1001](https://github.com/ava-labs/firewood/pull/1001)) +- *(deps)* Update lru requirement from 0.15.0 to 0.16.0 ([#1023](https://github.com/ava-labs/firewood/pull/1023)) +- *(deps)* Upgrade sha2, tokio, clap, fastrace, serde... ([#1025](https://github.com/ava-labs/firewood/pull/1025)) ### 🚜 Refactor -- *(deps)* Move duplicates to workspace (#1002) -- *(ffi)* [**breaking**] Split starting metrics exporter from db startup (#1016) +- *(deps)* Move duplicates to workspace ([#1002](https://github.com/ava-labs/firewood/pull/1002)) +- *(ffi)* [**breaking**] Split starting metrics exporter from db startup ([#1016](https://github.com/ava-labs/firewood/pull/1016)) ### 📚 Documentation -- README cleanup (#1024) +- README cleanup ([#1024](https://github.com/ava-labs/firewood/pull/1024)) ### ⚡ Performance -- Cache the latest view (#1004) -- Allow cloned proposals (#1010) -- Break up the RevisionManager lock (#1027) +- Cache the latest view ([#1004](https://github.com/ava-labs/firewood/pull/1004)) +- Allow cloned proposals ([#1010](https://github.com/ava-labs/firewood/pull/1010)) +- Break up the RevisionManager lock ([#1027](https://github.com/ava-labs/firewood/pull/1027)) ### ⚙️ Miscellaneous Tasks -- Suppress clippy::cast\_possible\_truncation across the workspace (#1012) -- Clippy pushdown (#1011) -- Allow some extra pedantic warnings (#1014) -- Check for metrics changes (#1013) -- Share workspace metadata and packages (#1020) -- Add concurrency group to attach static libs workflow (#1038) -- Bump version to v0.0.8 (#1018) +- Suppress clippy::cast_possible_truncation across the workspace ([#1012](https://github.com/ava-labs/firewood/pull/1012)) +- Clippy pushdown ([#1011](https://github.com/ava-labs/firewood/pull/1011)) +- Allow some extra pedantic warnings ([#1014](https://github.com/ava-labs/firewood/pull/1014)) +- Check for metrics changes ([#1013](https://github.com/ava-labs/firewood/pull/1013)) +- Share workspace metadata and packages ([#1020](https://github.com/ava-labs/firewood/pull/1020)) +- Add concurrency group to attach static libs workflow ([#1038](https://github.com/ava-labs/firewood/pull/1038)) +- Bump version to v0.0.8 ([#1018](https://github.com/ava-labs/firewood/pull/1018)) ## [0.0.7] - 2025-06-26 ### 🚀 Features -- Add methods to fetch views from any hash (#993) +- Add methods to fetch views from any hash ([#993](https://github.com/ava-labs/firewood/pull/993)) ### 🐛 Bug Fixes -- *(ci)* Include submodule name in ffi tag (#991) +- *(ci)* Include submodule name in ffi tag ([#991](https://github.com/ava-labs/firewood/pull/991)) ### ⚡ Performance -- *(metrics)* Add some metrics around propose and commit times (#989) +- *(metrics)* Add some metrics around propose and commit times ([#989](https://github.com/ava-labs/firewood/pull/989)) ### 🎨 Styling -- Use cbindgen to convert to pointers (#969) +- Use cbindgen to convert to pointers ([#969](https://github.com/ava-labs/firewood/pull/969)) ### 🧪 Testing -- Check support for empty proposals (#988) +- Check support for empty proposals ([#988](https://github.com/ava-labs/firewood/pull/988)) ### ⚙️ Miscellaneous Tasks -- Simplify + cleanup generate\_cgo script (#979) -- Update Cargo.toml add repository field (#987) -- *(fuzz)* Add step to upload fuzz testdata on failure (#990) -- Add special case for non semver tags to attach static libs (#992) -- Remove requirement for conventional commits (#994) -- Release v0.0.7 (#997) +- Simplify + cleanup generate_cgo script ([#979](https://github.com/ava-labs/firewood/pull/979)) +- Update Cargo.toml add repository field ([#987](https://github.com/ava-labs/firewood/pull/987)) +- *(fuzz)* Add step to upload fuzz testdata on failure ([#990](https://github.com/ava-labs/firewood/pull/990)) +- Add special case for non semver tags to attach static libs ([#992](https://github.com/ava-labs/firewood/pull/992)) +- Remove requirement for conventional commits ([#994](https://github.com/ava-labs/firewood/pull/994)) +- Release v0.0.7 ([#997](https://github.com/ava-labs/firewood/pull/997)) ## [0.0.6] - 2025-06-21 ### 🚀 Features -- Improve error handling and add sync iterator (#941) -- *(metrics)* Add read\_node counters (#947) -- Return database creation errors through FFI (#945) -- *(ffi)* Add go generate switch between enabled cgo blocks (#978) +- Improve error handling and add sync iterator ([#941](https://github.com/ava-labs/firewood/pull/941)) +- *(metrics)* Add read_node counters ([#947](https://github.com/ava-labs/firewood/pull/947)) +- Return database creation errors through FFI ([#945](https://github.com/ava-labs/firewood/pull/945)) +- *(ffi)* Add go generate switch between enabled cgo blocks ([#978](https://github.com/ava-labs/firewood/pull/978)) ### 🐛 Bug Fixes -- Use saturating subtraction for metrics counter (#937) -- *(attach-static-libs)* Push commit/branch to remote on tag events (#944) -- Add add\_arithmetic\_side\_effects clippy (#949) -- Improve ethhash warning message (#961) -- *(storage)* Parse and validate database versions (#964) +- Use saturating subtraction for metrics counter ([#937](https://github.com/ava-labs/firewood/pull/937)) +- *(attach-static-libs)* Push commit/branch to remote on tag events ([#944](https://github.com/ava-labs/firewood/pull/944)) +- Add add_arithmetic_side_effects clippy ([#949](https://github.com/ava-labs/firewood/pull/949)) +- Improve ethhash warning message ([#961](https://github.com/ava-labs/firewood/pull/961)) +- *(storage)* Parse and validate database versions ([#964](https://github.com/ava-labs/firewood/pull/964)) ### 💼 Other -- *(deps)* Update fastrace-opentelemetry requirement from 0.11.0 to 0.12.0 (#943) -- Move lints to the workspace (#957) +- *(deps)* Update fastrace-opentelemetry requirement from 0.11.0 to 0.12.0 ([#943](https://github.com/ava-labs/firewood/pull/943)) +- Move lints to the workspace ([#957](https://github.com/ava-labs/firewood/pull/957)) ### ⚡ Performance -- Remove some unecessary allocs during serialization (#965) +- Remove some unecessary allocs during serialization ([#965](https://github.com/ava-labs/firewood/pull/965)) ### 🎨 Styling -- *(attach-static-libs)* Use go mod edit instead of sed to update mod path (#946) +- *(attach-static-libs)* Use go mod edit instead of sed to update mod path ([#946](https://github.com/ava-labs/firewood/pull/946)) ### 🧪 Testing -- *(ethhash)* Convert ethhash test to fuzz test for ethhash compatibility (#956) +- *(ethhash)* Convert ethhash test to fuzz test for ethhash compatibility ([#956](https://github.com/ava-labs/firewood/pull/956)) ### ⚙️ Miscellaneous Tasks -- Upgrade actions/checkout (#939) -- Add push to main to attach static libs triggers (#952) -- Check the PR title for conventional commits (#953) -- Add Brandon to CODEOWNERS (#954) -- Set up for publishing to crates.io (#962) -- Remove remnants of no-std (#968) -- *(ffi)* Rename ffi package to match dir (#971) -- *(attach-static-libs)* Add pre build command to set MACOSX\_DEPLOYMENT\_TARGET for static libs build (#973) -- Use new firewood-go-* FFI repo naming (#975) -- Upgrade metrics packages (#982) -- Release v0.0.6 (#985) +- Upgrade actions/checkout ([#939](https://github.com/ava-labs/firewood/pull/939)) +- Add push to main to attach static libs triggers ([#952](https://github.com/ava-labs/firewood/pull/952)) +- Check the PR title for conventional commits ([#953](https://github.com/ava-labs/firewood/pull/953)) +- Add Brandon to CODEOWNERS ([#954](https://github.com/ava-labs/firewood/pull/954)) +- Set up for publishing to crates.io ([#962](https://github.com/ava-labs/firewood/pull/962)) +- Remove remnants of no-std ([#968](https://github.com/ava-labs/firewood/pull/968)) +- *(ffi)* Rename ffi package to match dir ([#971](https://github.com/ava-labs/firewood/pull/971)) +- *(attach-static-libs)* Add pre build command to set MACOSX_DEPLOYMENT_TARGET for static libs build ([#973](https://github.com/ava-labs/firewood/pull/973)) +- Use new firewood-go-* FFI repo naming ([#975](https://github.com/ava-labs/firewood/pull/975)) +- Upgrade metrics packages ([#982](https://github.com/ava-labs/firewood/pull/982)) +- Release v0.0.6 ([#985](https://github.com/ava-labs/firewood/pull/985)) ## [0.0.5] - 2025-06-05 ### 🚀 Features -- *(ffi)* Ffi error messages (#860) -- *(ffi)* Proposal creation isolated from committing (#867) -- *(ffi)* Get values from proposals (#877) -- *(ffi)* Full proposal support (#878) -- *(ffi)* Support `Get` for historical revisions (#881) -- *(ffi)* Add proposal root retrieval (#910) +- *(ffi)* Ffi error messages ([#860](https://github.com/ava-labs/firewood/pull/860)) +- *(ffi)* Proposal creation isolated from committing ([#867](https://github.com/ava-labs/firewood/pull/867)) +- *(ffi)* Get values from proposals ([#877](https://github.com/ava-labs/firewood/pull/877)) +- *(ffi)* Full proposal support ([#878](https://github.com/ava-labs/firewood/pull/878)) +- *(ffi)* Support `Get` for historical revisions ([#881](https://github.com/ava-labs/firewood/pull/881)) +- *(ffi)* Add proposal root retrieval ([#910](https://github.com/ava-labs/firewood/pull/910)) ### 🐛 Bug Fixes -- *(ffi)* Prevent memory leak and tips for finding leaks (#862) -- *(src)* Drop unused revisions (#866) -- *(ffi)* Clarify roles of `Value` extractors (#875) -- *(ffi)* Check revision is available (#890) -- *(ffi)* Prevent undefined behavior on empty slices (#894) -- Fix empty hash values (#925) +- *(ffi)* Prevent memory leak and tips for finding leaks ([#862](https://github.com/ava-labs/firewood/pull/862)) +- *(src)* Drop unused revisions ([#866](https://github.com/ava-labs/firewood/pull/866)) +- *(ffi)* Clarify roles of `Value` extractors ([#875](https://github.com/ava-labs/firewood/pull/875)) +- *(ffi)* Check revision is available ([#890](https://github.com/ava-labs/firewood/pull/890)) +- *(ffi)* Prevent undefined behavior on empty slices ([#894](https://github.com/ava-labs/firewood/pull/894)) +- Fix empty hash values ([#925](https://github.com/ava-labs/firewood/pull/925)) ### 💼 Other -- *(deps)* Update pprof requirement from 0.12.1 to 0.13.0 (#283) -- *(deps)* Update lru requirement from 0.11.0 to 0.12.0 (#306) -- *(deps)* Update typed-builder requirement from 0.16.0 to 0.17.0 (#320) -- *(deps)* Update typed-builder requirement from 0.17.0 to 0.18.0 (#324) -- Remove dead code (#333) -- Kv\_dump should be done with the iterator (#347) -- Add remaining lint checks (#397) -- Finish error handler mapper (#421) -- Switch from EmptyDB to Db (#422) -- *(deps)* Update aquamarine requirement from 0.3.1 to 0.4.0 (#434) -- *(deps)* Update serial\_test requirement from 2.0.0 to 3.0.0 (#477) -- *(deps)* Update aquamarine requirement from 0.4.0 to 0.5.0 (#496) -- *(deps)* Update env\_logger requirement from 0.10.1 to 0.11.0 (#502) -- *(deps)* Update tonic-build requirement from 0.10.2 to 0.11.0 (#522) -- *(deps)* Update tonic requirement from 0.10.2 to 0.11.0 (#523) -- *(deps)* Update nix requirement from 0.27.1 to 0.28.0 (#563) -- Move clippy pragma closer to usage (#578) -- *(deps)* Update typed-builder requirement from 0.18.1 to 0.19.1 (#684) -- *(deps)* Update lru requirement from 0.8.0 to 0.12.4 (#708) -- *(deps)* Update typed-builder requirement from 0.19.1 to 0.20.0 (#711) -- *(deps)* Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#715) +- *(deps)* Update pprof requirement from 0.12.1 to 0.13.0 ([#283](https://github.com/ava-labs/firewood/pull/283)) +- *(deps)* Update lru requirement from 0.11.0 to 0.12.0 ([#306](https://github.com/ava-labs/firewood/pull/306)) +- *(deps)* Update typed-builder requirement from 0.16.0 to 0.17.0 ([#320](https://github.com/ava-labs/firewood/pull/320)) +- *(deps)* Update typed-builder requirement from 0.17.0 to 0.18.0 ([#324](https://github.com/ava-labs/firewood/pull/324)) +- Remove dead code ([#333](https://github.com/ava-labs/firewood/pull/333)) +- Kv_dump should be done with the iterator ([#347](https://github.com/ava-labs/firewood/pull/347)) +- Add remaining lint checks ([#397](https://github.com/ava-labs/firewood/pull/397)) +- Finish error handler mapper ([#421](https://github.com/ava-labs/firewood/pull/421)) +- Switch from EmptyDB to Db ([#422](https://github.com/ava-labs/firewood/pull/422)) +- *(deps)* Update aquamarine requirement from 0.3.1 to 0.4.0 ([#434](https://github.com/ava-labs/firewood/pull/434)) +- *(deps)* Update serial_test requirement from 2.0.0 to 3.0.0 ([#477](https://github.com/ava-labs/firewood/pull/477)) +- *(deps)* Update aquamarine requirement from 0.4.0 to 0.5.0 ([#496](https://github.com/ava-labs/firewood/pull/496)) +- *(deps)* Update env_logger requirement from 0.10.1 to 0.11.0 ([#502](https://github.com/ava-labs/firewood/pull/502)) +- *(deps)* Update tonic-build requirement from 0.10.2 to 0.11.0 ([#522](https://github.com/ava-labs/firewood/pull/522)) +- *(deps)* Update tonic requirement from 0.10.2 to 0.11.0 ([#523](https://github.com/ava-labs/firewood/pull/523)) +- *(deps)* Update nix requirement from 0.27.1 to 0.28.0 ([#563](https://github.com/ava-labs/firewood/pull/563)) +- Move clippy pragma closer to usage ([#578](https://github.com/ava-labs/firewood/pull/578)) +- *(deps)* Update typed-builder requirement from 0.18.1 to 0.19.1 ([#684](https://github.com/ava-labs/firewood/pull/684)) +- *(deps)* Update lru requirement from 0.8.0 to 0.12.4 ([#708](https://github.com/ava-labs/firewood/pull/708)) +- *(deps)* Update typed-builder requirement from 0.19.1 to 0.20.0 ([#711](https://github.com/ava-labs/firewood/pull/711)) +- *(deps)* Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows ([#715](https://github.com/ava-labs/firewood/pull/715)) - Insert truncated trie - Allow for trace and no logging -- Add read\_for\_update +- Add read_for_update - Revision history should never grow - Use a more random hash - Use smallvec to optimize for 16 byte values -- *(deps)* Update aquamarine requirement from 0.5.0 to 0.6.0 (#727) -- *(deps)* Update thiserror requirement from 1.0.57 to 2.0.3 (#751) -- *(deps)* Update pprof requirement from 0.13.0 to 0.14.0 (#750) -- *(deps)* Update metrics-util requirement from 0.18.0 to 0.19.0 (#765) -- *(deps)* Update cbindgen requirement from 0.27.0 to 0.28.0 (#767) -- *(deps)* Update bitfield requirement from 0.17.0 to 0.18.1 (#772) -- *(deps)* Update lru requirement from 0.12.4 to 0.13.0 (#771) -- *(deps)* Update bitfield requirement from 0.18.1 to 0.19.0 (#801) -- *(deps)* Update typed-builder requirement from 0.20.0 to 0.21.0 (#815) -- *(deps)* Update tonic requirement from 0.12.1 to 0.13.0 (#826) -- *(deps)* Update opentelemetry requirement from 0.28.0 to 0.29.0 (#816) -- *(deps)* Update lru requirement from 0.13.0 to 0.14.0 (#840) -- *(deps)* Update metrics-exporter-prometheus requirement from 0.16.1 to 0.17.0 (#853) -- *(deps)* Update rand requirement from 0.8.5 to 0.9.1 (#850) -- *(deps)* Update pprof requirement from 0.14.0 to 0.15.0 (#906) -- *(deps)* Update cbindgen requirement from 0.28.0 to 0.29.0 (#899) -- *(deps)* Update criterion requirement from 0.5.1 to 0.6.0 (#898) -- *(deps)* Bump golang.org/x/crypto from 0.17.0 to 0.35.0 in /ffi/tests (#907) -- *(deps)* Bump google.golang.org/protobuf from 1.27.1 to 1.33.0 /ffi/tests (#923) -- *(deps)* Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#924) +- *(deps)* Update aquamarine requirement from 0.5.0 to 0.6.0 ([#727](https://github.com/ava-labs/firewood/pull/727)) +- *(deps)* Update thiserror requirement from 1.0.57 to 2.0.3 ([#751](https://github.com/ava-labs/firewood/pull/751)) +- *(deps)* Update pprof requirement from 0.13.0 to 0.14.0 ([#750](https://github.com/ava-labs/firewood/pull/750)) +- *(deps)* Update metrics-util requirement from 0.18.0 to 0.19.0 ([#765](https://github.com/ava-labs/firewood/pull/765)) +- *(deps)* Update cbindgen requirement from 0.27.0 to 0.28.0 ([#767](https://github.com/ava-labs/firewood/pull/767)) +- *(deps)* Update bitfield requirement from 0.17.0 to 0.18.1 ([#772](https://github.com/ava-labs/firewood/pull/772)) +- *(deps)* Update lru requirement from 0.12.4 to 0.13.0 ([#771](https://github.com/ava-labs/firewood/pull/771)) +- *(deps)* Update bitfield requirement from 0.18.1 to 0.19.0 ([#801](https://github.com/ava-labs/firewood/pull/801)) +- *(deps)* Update typed-builder requirement from 0.20.0 to 0.21.0 ([#815](https://github.com/ava-labs/firewood/pull/815)) +- *(deps)* Update tonic requirement from 0.12.1 to 0.13.0 ([#826](https://github.com/ava-labs/firewood/pull/826)) +- *(deps)* Update opentelemetry requirement from 0.28.0 to 0.29.0 ([#816](https://github.com/ava-labs/firewood/pull/816)) +- *(deps)* Update lru requirement from 0.13.0 to 0.14.0 ([#840](https://github.com/ava-labs/firewood/pull/840)) +- *(deps)* Update metrics-exporter-prometheus requirement from 0.16.1 to 0.17.0 ([#853](https://github.com/ava-labs/firewood/pull/853)) +- *(deps)* Update rand requirement from 0.8.5 to 0.9.1 ([#850](https://github.com/ava-labs/firewood/pull/850)) +- *(deps)* Update pprof requirement from 0.14.0 to 0.15.0 ([#906](https://github.com/ava-labs/firewood/pull/906)) +- *(deps)* Update cbindgen requirement from 0.28.0 to 0.29.0 ([#899](https://github.com/ava-labs/firewood/pull/899)) +- *(deps)* Update criterion requirement from 0.5.1 to 0.6.0 ([#898](https://github.com/ava-labs/firewood/pull/898)) +- *(deps)* Bump golang.org/x/crypto from 0.17.0 to 0.35.0 in /ffi/tests ([#907](https://github.com/ava-labs/firewood/pull/907)) +- *(deps)* Bump google.golang.org/protobuf from 1.27.1 to 1.33.0 /ffi/tests ([#923](https://github.com/ava-labs/firewood/pull/923)) +- *(deps)* Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 ([#924](https://github.com/ava-labs/firewood/pull/924)) ### 🚜 Refactor -- *(ffi)* Cleanup unused and duplicate code (#926) +- *(ffi)* Cleanup unused and duplicate code ([#926](https://github.com/ava-labs/firewood/pull/926)) ### 📚 Documentation -- *(ffi)* Remove private declarations from public docs (#874) +- *(ffi)* Remove private declarations from public docs ([#874](https://github.com/ava-labs/firewood/pull/874)) ### 🧪 Testing -- *(ffi/tests)* Basic eth compatibility (#825) -- *(ethhash)* Use libevm (#900) +- *(ffi/tests)* Basic eth compatibility ([#825](https://github.com/ava-labs/firewood/pull/825)) +- *(ethhash)* Use libevm ([#900](https://github.com/ava-labs/firewood/pull/900)) ### ⚙️ Miscellaneous Tasks -- Use `decode` in single key proof verification (#295) -- Use `decode` in range proof verification (#303) -- Naming the elements of `ExtNode` (#305) -- Remove the getter pattern over `ExtNode` (#310) -- Proof cleanup (#316) -- *(ffi/tests)* Update go-ethereum v1.15.7 (#838) -- *(ffi)* Fix typo fwd\_close\_db comment (#843) -- *(ffi)* Add linter (#893) -- Require conventional commit format (#933) -- Bump to v0.5.0 (#934) +- Use `decode` in single key proof verification ([#295](https://github.com/ava-labs/firewood/pull/295)) +- Use `decode` in range proof verification ([#303](https://github.com/ava-labs/firewood/pull/303)) +- Naming the elements of `ExtNode` ([#305](https://github.com/ava-labs/firewood/pull/305)) +- Remove the getter pattern over `ExtNode` ([#310](https://github.com/ava-labs/firewood/pull/310)) +- Proof cleanup ([#316](https://github.com/ava-labs/firewood/pull/316)) +- *(ffi/tests)* Update go-ethereum v1.15.7 ([#838](https://github.com/ava-labs/firewood/pull/838)) +- *(ffi)* Fix typo fwd_close_db comment ([#843](https://github.com/ava-labs/firewood/pull/843)) +- *(ffi)* Add linter ([#893](https://github.com/ava-labs/firewood/pull/893)) +- Require conventional commit format ([#933](https://github.com/ava-labs/firewood/pull/933)) +- Bump to v0.5.0 ([#934](https://github.com/ava-labs/firewood/pull/934)) ## [0.0.4] - 2023-09-27 ### 🚀 Features -- Identify a revision with root hash (#126) -- Supports chains of `StoreRevMut` (#175) -- Add proposal (#181) +- Identify a revision with root hash ([#126](https://github.com/ava-labs/firewood/pull/126)) +- Supports chains of `StoreRevMut` ([#175](https://github.com/ava-labs/firewood/pull/175)) +- Add proposal ([#181](https://github.com/ava-labs/firewood/pull/181)) ### 🐛 Bug Fixes -- Update release to cargo-workspace-version (#75) +- Update release to cargo-workspace-version ([#75](https://github.com/ava-labs/firewood/pull/75)) ### 💼 Other -- *(deps)* Update criterion requirement from 0.4.0 to 0.5.1 (#96) -- *(deps)* Update enum-as-inner requirement from 0.5.1 to 0.6.0 (#107) -- :position FTW? (#140) -- *(deps)* Update indexmap requirement from 1.9.1 to 2.0.0 (#147) -- *(deps)* Update pprof requirement from 0.11.1 to 0.12.0 (#152) -- *(deps)* Update typed-builder requirement from 0.14.0 to 0.15.0 (#153) -- *(deps)* Update lru requirement from 0.10.0 to 0.11.0 (#155) -- Update hash fn to root\_hash (#170) -- Remove generics on Db (#196) -- Remove generics for Proposal (#197) -- Use quotes around all (#200) -- :get: use Nibbles (#210) -- Variable renames (#211) -- Use thiserror (#221) -- *(deps)* Update typed-builder requirement from 0.15.0 to 0.16.0 (#222) -- *(deps)* Update tonic-build requirement from 0.9.2 to 0.10.0 (#247) -- *(deps)* Update prost requirement from 0.11.9 to 0.12.0 (#246) +- *(deps)* Update criterion requirement from 0.4.0 to 0.5.1 ([#96](https://github.com/ava-labs/firewood/pull/96)) +- *(deps)* Update enum-as-inner requirement from 0.5.1 to 0.6.0 ([#107](https://github.com/ava-labs/firewood/pull/107)) +- :position FTW? ([#140](https://github.com/ava-labs/firewood/pull/140)) +- *(deps)* Update indexmap requirement from 1.9.1 to 2.0.0 ([#147](https://github.com/ava-labs/firewood/pull/147)) +- *(deps)* Update pprof requirement from 0.11.1 to 0.12.0 ([#152](https://github.com/ava-labs/firewood/pull/152)) +- *(deps)* Update typed-builder requirement from 0.14.0 to 0.15.0 ([#153](https://github.com/ava-labs/firewood/pull/153)) +- *(deps)* Update lru requirement from 0.10.0 to 0.11.0 ([#155](https://github.com/ava-labs/firewood/pull/155)) +- Update hash fn to root_hash ([#170](https://github.com/ava-labs/firewood/pull/170)) +- Remove generics on Db ([#196](https://github.com/ava-labs/firewood/pull/196)) +- Remove generics for Proposal ([#197](https://github.com/ava-labs/firewood/pull/197)) +- Use quotes around all ([#200](https://github.com/ava-labs/firewood/pull/200)) +- :get: use Nibbles ([#210](https://github.com/ava-labs/firewood/pull/210)) +- Variable renames ([#211](https://github.com/ava-labs/firewood/pull/211)) +- Use thiserror ([#221](https://github.com/ava-labs/firewood/pull/221)) +- *(deps)* Update typed-builder requirement from 0.15.0 to 0.16.0 ([#222](https://github.com/ava-labs/firewood/pull/222)) +- *(deps)* Update tonic-build requirement from 0.9.2 to 0.10.0 ([#247](https://github.com/ava-labs/firewood/pull/247)) +- *(deps)* Update prost requirement from 0.11.9 to 0.12.0 ([#246](https://github.com/ava-labs/firewood/pull/246)) ### ⚙️ Miscellaneous Tasks -- Refactor `rev.rs` (#74) -- Disable `test\_buffer\_with\_redo` (#128) -- Verify concurrent committing write batches (#172) -- Remove redundant code (#174) -- Remove unused clone for `StoreRevMutDelta` (#178) -- Abstract out mutable store creation (#176) -- Proposal test cleanup (#184) -- Add comments for `Proposal` (#186) -- Deprecate `WriteBatch` and use `Proposal` instead (#188) -- Inline doc clean up (#240) -- Remove unused blob in db (#245) -- Add license header to firewood files (#262) -- Revert back `test\_proof` changes accidentally changed (#279) +- Refactor `rev.rs` ([#74](https://github.com/ava-labs/firewood/pull/74)) +- Disable `test_buffer_with_redo` ([#128](https://github.com/ava-labs/firewood/pull/128)) +- Verify concurrent committing write batches ([#172](https://github.com/ava-labs/firewood/pull/172)) +- Remove redundant code ([#174](https://github.com/ava-labs/firewood/pull/174)) +- Remove unused clone for `StoreRevMutDelta` ([#178](https://github.com/ava-labs/firewood/pull/178)) +- Abstract out mutable store creation ([#176](https://github.com/ava-labs/firewood/pull/176)) +- Proposal test cleanup ([#184](https://github.com/ava-labs/firewood/pull/184)) +- Add comments for `Proposal` ([#186](https://github.com/ava-labs/firewood/pull/186)) +- Deprecate `WriteBatch` and use `Proposal` instead ([#188](https://github.com/ava-labs/firewood/pull/188)) +- Inline doc clean up ([#240](https://github.com/ava-labs/firewood/pull/240)) +- Remove unused blob in db ([#245](https://github.com/ava-labs/firewood/pull/245)) +- Add license header to firewood files ([#262](https://github.com/ava-labs/firewood/pull/262)) +- Revert back `test_proof` changes accidentally changed ([#279](https://github.com/ava-labs/firewood/pull/279)) ## [0.0.3] - 2023-04-28 ### 💼 Other -- Move benching to criterion (#61) -- Refactor file operations to use a Path (#26) -- Fix panic get\_item on a dirty write (#66) -- Improve error handling (#70) +- Move benching to criterion ([#61](https://github.com/ava-labs/firewood/pull/61)) +- Refactor file operations to use a Path ([#26](https://github.com/ava-labs/firewood/pull/26)) +- Fix panic get_item on a dirty write ([#66](https://github.com/ava-labs/firewood/pull/66)) +- Improve error handling ([#70](https://github.com/ava-labs/firewood/pull/70)) ### 🧪 Testing -- Speed up slow unit tests (#58) +- Speed up slow unit tests ([#58](https://github.com/ava-labs/firewood/pull/58)) ### ⚙️ Miscellaneous Tasks -- Add backtrace to e2e tests (#59) +- Add backtrace to e2e tests ([#59](https://github.com/ava-labs/firewood/pull/59)) ## [0.0.2] - 2023-04-21 ### 💼 Other -- Fix test flake (#44) +- Fix test flake ([#44](https://github.com/ava-labs/firewood/pull/44)) ### 📚 Documentation -- Add release notes (#27) -- Update CODEOWNERS (#28) -- Add badges to README (#33) +- Add release notes ([#27](https://github.com/ava-labs/firewood/pull/27)) +- Update CODEOWNERS ([#28](https://github.com/ava-labs/firewood/pull/28)) +- Add badges to README ([#33](https://github.com/ava-labs/firewood/pull/33)) ## [0.0.1] - 2023-04-14 @@ -412,12 +465,12 @@ All notable changes to this project will be documented in this file. - Unset the pre calculated RLP values of interval nodes - Run cargo clippy --fix - Handle empty key value proof arguments as an error -- Tweak repo organization (#130) -- Run clippy --fix across all workspaces (#149) -- Update StoreError to use thiserror (#156) -- Update db::new() to accept a Path (#187) -- Use bytemuck instead of unsafe in growth-ring (#185) -- Update firewood sub-projects (#16) +- Tweak repo organization ([#130](https://github.com/ava-labs/firewood/pull/130)) +- Run clippy --fix across all workspaces ([#149](https://github.com/ava-labs/firewood/pull/149)) +- Update StoreError to use thiserror ([#156](https://github.com/ava-labs/firewood/pull/156)) +- Update db::new() to accept a Path ([#187](https://github.com/ava-labs/firewood/pull/187)) +- Use bytemuck instead of unsafe in growth-ring ([#185](https://github.com/ava-labs/firewood/pull/185)) +- Update firewood sub-projects ([#16](https://github.com/ava-labs/firewood/pull/16)) ### 💼 Other @@ -438,7 +491,7 @@ All notable changes to this project will be documented in this file. - Add get command - Add delete command - Move cli tests under tests/ -- Only use kv\_ functions in fwdctl +- Only use kv_ functions in fwdctl - Fix implementation and add tests - Add exit codes and stderr error logging - Add tests @@ -448,7 +501,7 @@ All notable changes to this project will be documented in this file. - Fixup root tests to be serial - *(deps)* Update typed-builder requirement from 0.11.0 to 0.12.0 - Add VSCode -- Update merkle\_utils to return Results +- Update merkle_utils to return Results - Fixup command UX to be positional - Update firewood to match needed functionality - Update DB and Merkle errors to implement the Error trait @@ -457,23 +510,23 @@ All notable changes to this project will be documented in this file. - *(deps)* Update nix requirement from 0.25.0 to 0.26.2 - *(deps)* Update lru requirement from 0.8.0 to 0.10.0 - *(deps)* Update typed-builder requirement from 0.12.0 to 0.13.0 -- *(deps)* Update typed-builder requirement from 0.13.0 to 0.14.0 (#144) -- Update create\_file to return a Result (#150) -- *(deps)* Update predicates requirement from 2.1.1 to 3.0.1 (#154) -- Add new library crate (#158) -- *(deps)* Update serial\_test requirement from 1.0.0 to 2.0.0 (#173) -- Refactor kv\_remove to be more ergonomic (#168) -- Add e2e test (#167) -- Use eth and proof feature gates across all API surfaces. (#181) -- Add license header to firewood source code (#189) +- *(deps)* Update typed-builder requirement from 0.13.0 to 0.14.0 ([#144](https://github.com/ava-labs/firewood/pull/144)) +- Update create_file to return a Result ([#150](https://github.com/ava-labs/firewood/pull/150)) +- *(deps)* Update predicates requirement from 2.1.1 to 3.0.1 ([#154](https://github.com/ava-labs/firewood/pull/154)) +- Add new library crate ([#158](https://github.com/ava-labs/firewood/pull/158)) +- *(deps)* Update serial_test requirement from 1.0.0 to 2.0.0 ([#173](https://github.com/ava-labs/firewood/pull/173)) +- Refactor kv_remove to be more ergonomic ([#168](https://github.com/ava-labs/firewood/pull/168)) +- Add e2e test ([#167](https://github.com/ava-labs/firewood/pull/167)) +- Use eth and proof feature gates across all API surfaces. ([#181](https://github.com/ava-labs/firewood/pull/181)) +- Add license header to firewood source code ([#189](https://github.com/ava-labs/firewood/pull/189)) ### 📚 Documentation - Add link to fwdctl README in main README - Update fwdctl README with storage information - Update fwdctl README with more examples -- Document get\_revisions function with additional information. (#177) -- Add alpha warning to firewood README (#191) +- Document get_revisions function with additional information. ([#177](https://github.com/ava-labs/firewood/pull/177)) +- Add alpha warning to firewood README ([#191](https://github.com/ava-labs/firewood/pull/191)) ### 🧪 Testing @@ -486,7 +539,7 @@ All notable changes to this project will be documented in this file. - Add release and publish GH Actions - Update batch sizes in ci e2e job - Add docs linter to strengthen firewood documentation -- Clippy should fail in case of warnings (#151) -- Fail in case of error publishing firewood crate (#21) +- Clippy should fail in case of warnings ([#151](https://github.com/ava-labs/firewood/pull/151)) +- Fail in case of error publishing firewood crate ([#21](https://github.com/ava-labs/firewood/pull/21)) diff --git a/Cargo.toml b/Cargo.toml index cd4ef86eab63..46d99de1a7c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ exclude = ["grpc-testtool"] resolver = "2" [workspace.package] -version = "0.0.10" +version = "0.0.11" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -54,11 +54,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.10" } -firewood-macros = { path = "firewood-macros", version = "0.0.10" } -firewood-storage = { path = "storage", version = "0.0.10" } -firewood-ffi = { path = "ffi", version = "0.0.10" } -firewood-triehash = { path = "triehash", version = "0.0.10" } +firewood = { path = "firewood", version = "0.0.11" } +firewood-macros = { path = "firewood-macros", version = "0.0.11" } +firewood-storage = { path = "storage", version = "0.0.11" } +firewood-ffi = { path = "ffi", version = "0.0.11" } +firewood-triehash = { path = "triehash", version = "0.0.11" } # common dependencies aquamarine = "0.6.0" diff --git a/RELEASE.md b/RELEASE.md index 6362ab95a5a8..c542045505ee 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,9 +12,9 @@ Start off by crating a new branch: ```console $ git fetch -$ git switch -c release/v0.0.11 origin/main -branch 'release/v0.0.11' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.11' +$ git switch -c release/v0.0.12 origin/main +branch 'release/v0.0.12' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.12' ``` ## Package Version @@ -26,7 +26,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.11" +version = "0.0.12" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -49,7 +49,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.11" } +firewood = { path = "firewood", version = "0.0.12" } ``` This allows packages within the workspace to inherit the dependency, @@ -78,7 +78,7 @@ To build the changelog, see git-cliff.org. Short version: ```sh cargo install --locked git-cliff -git cliff --tag v0.0.11 | sed -e 's/_/\\_/g' > CHANGELOG.md +git cliff --tag v0.0.12 > CHANGELOG.md ``` ## Review @@ -92,11 +92,11 @@ git cliff --tag v0.0.11 | sed -e 's/_/\\_/g' > CHANGELOG.md To trigger a release, push a tag to the main branch matching the new version, ```sh -git tag -S v0.0.11 -git push origin v0.0.11 +git tag -S v0.0.12 +git push origin v0.0.12 ``` -for `v0.0.11` for the merged version change. The CI will automatically publish a +for `v0.0.12` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). diff --git a/cliff.toml b/cliff.toml index 236198f1b8dc..c7f5d34743bb 100644 --- a/cliff.toml +++ b/cliff.toml @@ -38,7 +38,7 @@ render_always = true # An array of regex based postprocessors to modify the changelog. postprocessors = [ # Replace the placeholder with a URL. - #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, + { pattern = '', replace = "https://github.com/ava-labs/firewood" }, ] # render body even when there are no releases to process # render_always = true @@ -63,6 +63,8 @@ commit_preprocessors = [ # Check spelling of the commit message using https://github.com/crate-ci/typos. # If the spelling is incorrect, it will be fixed automatically. #{ pattern = '.*', replace_command = 'typos --write-changes -' }, + # Squash merges end with the pull request number (e.g., `(#12345)`). Replace those with a link to the pull request. + { pattern = '\(#([0-9]+)\)', replace = '([#${1}](/pull/${1}))' }, ] # Prevent commits that are breaking from being excluded by commit parsers. protect_breaking_commits = false @@ -103,4 +105,3 @@ sort_commits = "oldest" recurse_submodules = false # Only process tags in this pattern tag_pattern = "v[0-9].*" - diff --git a/clippy.toml b/clippy.toml index b0467800ea92..42602fcff662 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,6 +1,8 @@ # See https://doc.rust-lang.org/clippy/lint_configuration.html # for full configuration options. +msrv = "1.85" + disallowed-methods = [ { path = "rand::rng", replacement = "firewood_storage::StdRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, ] From 2c6066ba9b4ff92dfeeb3140d22553a812fc9705 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 20 Aug 2025 12:04:33 -0700 Subject: [PATCH 0909/1053] feat(async-removal): Phase 3 - make `Db` trait sync (#1213) This pull request refactors the Firewood database API to remove all asynchronous methods from the `Db` trait and its implementation, making all database operations synchronous. As a result, all usages of async methods in the codebase, benchmarks, examples, FFI, and tests have been updated to use the new synchronous API. This change simplifies the code and improves performance by eliminating unnecessary async overhead for operations that are inherently synchronous. --- benchmark/src/create.rs | 2 +- benchmark/src/single.rs | 2 +- benchmark/src/tenkrandom.rs | 2 +- benchmark/src/zipf.rs | 2 +- ffi/src/lib.rs | 12 ++-- firewood/benches/hashops.rs | 2 +- firewood/examples/insert.rs | 6 +- firewood/src/db.rs | 130 ++++++++++++------------------------ firewood/src/v2/api.rs | 26 ++++---- fwdctl/src/delete.rs | 4 +- fwdctl/src/dump.rs | 4 +- fwdctl/src/get.rs | 4 +- fwdctl/src/insert.rs | 4 +- fwdctl/src/main.rs | 6 +- fwdctl/src/root.rs | 4 +- 15 files changed, 82 insertions(+), 128 deletions(-) diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index 8ca522682000..df8ab5d902f3 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -33,7 +33,7 @@ impl TestRunner for Create { let batch = Self::generate_inserts(key * keys, args.global_opts.batch_size); - let proposal = db.propose(batch).await.expect("proposal should succeed"); + let proposal = db.propose(batch).expect("proposal should succeed"); proposal.commit()?; } let duration = start.elapsed(); diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index 90679a9b1295..e64efbca7c6f 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -35,7 +35,7 @@ impl TestRunner for Single { key, value: vec![batch_id as u8], }); - let proposal = db.propose(batch).await.expect("proposal should succeed"); + let proposal = db.propose(batch).expect("proposal should succeed"); proposal.commit()?; if log::log_enabled!(log::Level::Debug) && batch_id % 1000 == 999 { diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index 7613a3f7228f..bc762206b6db 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -32,7 +32,7 @@ impl TestRunner for TenKRandom { .chain(generate_deletes(low, twenty_five_pct)) .chain(generate_updates(low + high / 2, twenty_five_pct * 2, low)) .collect(); - let proposal = db.propose(batch).await.expect("proposal should succeed"); + let proposal = db.propose(batch).expect("proposal should succeed"); proposal.commit()?; low += twenty_five_pct; high += twenty_five_pct; diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 3afbea66b06e..806e5c7ca963 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -71,7 +71,7 @@ impl TestRunner for Zipf { distinct.len() ); } - let proposal = db.propose(batch).await.expect("proposal should succeed"); + let proposal = db.propose(batch).expect("proposal should succeed"); proposal.commit()?; if log::log_enabled!(log::Level::Debug) { diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 394bc4325aef..5f05e4ea1b80 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -39,7 +39,7 @@ use std::sync::{Mutex, RwLock}; use firewood::db::{Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -use firewood::v2::api::{HashKey, KeyValuePairIter}; +use firewood::v2::api::{Db as _, HashKey, KeyValuePairIter}; use metrics::counter; pub use crate::value::*; @@ -142,12 +142,12 @@ fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &[u8]) -> Result>, values: &[KeyValuePair<'_>]) -> Result let batch = values.iter().map_into_batch(); // Propose the batch of operations. - let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; + let proposal = db.propose(batch).map_err(|e| e.to_string())?; let propose_time = start.elapsed().as_millis(); counter!("firewood.ffi.propose_ms").increment(propose_time); @@ -393,7 +393,7 @@ fn propose_on_db<'p>( let batch = values.iter().map_into_batch(); // Propose the batch of operations. - let proposal = db.propose_sync(batch).map_err(|e| e.to_string())?; + let proposal = db.propose(batch).map_err(|e| e.to_string())?; // Get the root hash of the new proposal. let mut root_hash: Value = match proposal.root_hash_sync().map_err(|e| e.to_string())? { @@ -596,7 +596,7 @@ pub unsafe extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle<'_>>) -> Value #[doc(hidden)] fn root_hash(db: Option<&DatabaseHandle<'_>>) -> Result { let db = db.ok_or("db should be non-null")?; - db.root_hash_sync() + db.root_hash() .map_err(|e| e.to_string())? .map(|root| Value::from(root.as_slice())) .map_or_else(|| Ok(Value::default()), Ok) diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 8c5d8f19bf03..8afff94e952c 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -121,7 +121,7 @@ fn bench_db(criterion: &mut Criterion) { let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) .unwrap(); - db.propose(batch_ops).await.unwrap().commit().unwrap(); + db.propose(batch_ops).unwrap().commit().unwrap(); }, BatchSize::SmallInput, ); diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 47ac2771f933..823e236cefc6 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -90,7 +90,7 @@ async fn main() -> Result<(), Box> { let verify = get_keys_to_verify(rng, &batch, args.read_verify_percent); #[expect(clippy::unwrap_used)] - let proposal = db.propose(batch.clone()).await.unwrap(); + let proposal = db.propose(batch.clone()).unwrap(); proposal.commit()?; verify_keys(&db, verify).await?; } @@ -131,8 +131,8 @@ async fn verify_keys( verify: HashMap<&[u8], &[u8]>, ) -> Result<(), firewood::v2::api::Error> { if !verify.is_empty() { - let hash = db.root_hash().await?.expect("root hash should exist"); - let revision = db.revision(hash).await?; + let hash = db.root_hash()?.expect("root hash should exist"); + let revision = db.revision(hash)?; for (key, value) in verify { assert_eq!(Some(value), revision.val(key).await?.as_deref()); } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index bee3dc80a00c..14a17e5cfc50 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -162,7 +162,6 @@ pub struct Db { manager: RevisionManager, } -#[async_trait] impl api::Db for Db { type Historical = NodeStore; @@ -171,24 +170,24 @@ impl api::Db for Db { where Self: 'db; - async fn revision(&self, root_hash: HashKey) -> Result, api::Error> { + fn revision(&self, root_hash: HashKey) -> Result, api::Error> { let nodestore = self.manager.revision(root_hash)?; Ok(nodestore) } - async fn root_hash(&self) -> Result, api::Error> { - self.root_hash_sync() + fn root_hash(&self) -> Result, api::Error> { + Ok(self.manager.root_hash()?.or_default_root_hash()) } - async fn all_hashes(&self) -> Result, api::Error> { + fn all_hashes(&self) -> Result, api::Error> { Ok(self.manager.all_hashes()) } #[fastrace::trace(short_name = true)] - async fn propose<'db>( - &'db self, - batch: (impl IntoIterator + Send), - ) -> Result, api::Error> { + fn propose( + &self, + batch: impl IntoIterator, + ) -> Result, api::Error> { let parent = self.manager.current_revision(); let proposal = NodeStore::new(&parent)?; let mut merkle = Merkle::from(proposal); @@ -243,57 +242,12 @@ impl Db { Ok(db) } - /// Synchronously get the root hash of the latest revision. - pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(self.manager.root_hash()?.or_default_root_hash()) - } - - /// Synchronously get a revision from a root hash - pub fn revision_sync(&self, root_hash: HashKey) -> Result, api::Error> { - let nodestore = self.manager.revision(root_hash)?; - Ok(nodestore) - } - /// Synchronously get a view, either committed or proposed pub fn view_sync(&self, root_hash: HashKey) -> Result, api::Error> { let nodestore = self.manager.view(root_hash)?; Ok(nodestore) } - /// propose a new batch synchronously - pub fn propose_sync( - &self, - batch: impl IntoIterator, - ) -> Result, api::Error> { - let parent = self.manager.current_revision(); - let proposal = NodeStore::new(&parent)?; - let mut merkle = Merkle::from(proposal); - for op in batch { - match op.into_batch() { - BatchOp::Put { key, value } => { - merkle.insert(key.as_ref(), value.as_ref().into())?; - } - BatchOp::Delete { key } => { - merkle.remove(key.as_ref())?; - } - BatchOp::DeleteRange { prefix } => { - merkle.remove_prefix(prefix.as_ref())?; - } - } - } - let nodestore = merkle.into_inner(); - let immutable: Arc, FileBacked>> = - Arc::new(nodestore.try_into()?); - self.manager.add_proposal(immutable.clone()); - - self.metrics.proposals.increment(1); - - Ok(Proposal { - nodestore: immutable, - db: self, - }) - } - /// Dump the Trie of the latest revision. pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); @@ -495,7 +449,7 @@ mod test { key: b"k", value: b"v", }]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v"); assert_eq!(proposal.val(b"notfound").await.unwrap(), None); @@ -505,18 +459,18 @@ mod test { key: b"k", value: b"v2", }]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v2"); - let committed = db.root_hash().await.unwrap().unwrap(); - let historical = db.revision(committed).await.unwrap(); + let committed = db.root_hash().unwrap().unwrap(); + let historical = db.revision(committed).unwrap(); assert_eq!(&*historical.val(b"k").await.unwrap().unwrap(), b"v"); } #[tokio::test] async fn reopen_test() { let db = testdb(); - let initial_root = db.root_hash().await.unwrap(); + let initial_root = db.root_hash().unwrap(); let batch = vec![ BatchOp::Put { key: b"a", @@ -527,19 +481,19 @@ mod test { value: b"2", }, ]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); proposal.commit().unwrap(); - println!("{:?}", db.root_hash().await.unwrap().unwrap()); + println!("{:?}", db.root_hash().unwrap().unwrap()); let db = db.reopen(); - println!("{:?}", db.root_hash().await.unwrap().unwrap()); - let committed = db.root_hash().await.unwrap().unwrap(); - let historical = db.revision(committed).await.unwrap(); + println!("{:?}", db.root_hash().unwrap().unwrap()); + let committed = db.root_hash().unwrap().unwrap(); + let historical = db.revision(committed).unwrap(); assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); let db = db.replace(); - println!("{:?}", db.root_hash().await.unwrap()); - assert!(db.root_hash().await.unwrap() == initial_root); + println!("{:?}", db.root_hash().unwrap()); + assert!(db.root_hash().unwrap() == initial_root); } #[tokio::test] @@ -553,39 +507,39 @@ mod test { key: b"k1", value: b"v1", }]; - let proposal1 = db.propose(batch1).await.unwrap(); + let proposal1 = db.propose(batch1).unwrap(); assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); let batch2 = vec![BatchOp::Put { key: b"k2", value: b"v2", }]; - let proposal2 = db.propose(batch2).await.unwrap(); + let proposal2 = db.propose(batch2).unwrap(); assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); let batch3 = vec![BatchOp::Put { key: b"k3", value: b"v3", }]; - let proposal3 = db.propose(batch3).await.unwrap(); + let proposal3 = db.propose(batch3).unwrap(); assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); // the proposal is dropped here, but the underlying // nodestore is still accessible because it's referenced by the revision manager // The third proposal remains referenced let p2hash = proposal2.root_hash().await.unwrap().unwrap(); - assert!(db.all_hashes().await.unwrap().contains(&p2hash)); + assert!(db.all_hashes().unwrap().contains(&p2hash)); drop(proposal2); // commit the first proposal proposal1.commit().unwrap(); // Ensure we committed the first proposal's data - let committed = db.root_hash().await.unwrap().unwrap(); - let historical = db.revision(committed).await.unwrap(); + let committed = db.root_hash().unwrap().unwrap(); + let historical = db.revision(committed).unwrap(); assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); // the second proposal shouldn't be available to commit anymore - assert!(!db.all_hashes().await.unwrap().contains(&p2hash)); + assert!(!db.all_hashes().unwrap().contains(&p2hash)); // the third proposal should still be contained within the all_hashes list // would be deleted if another proposal was committed and proposal3 was dropped here @@ -605,7 +559,7 @@ mod test { key: b"k1", value: b"v1", }]; - let proposal1 = db.propose(batch1).await.unwrap(); + let proposal1 = db.propose(batch1).unwrap(); assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); let batch2 = vec![BatchOp::Put { @@ -626,18 +580,18 @@ mod test { // nodestore is still accessible because it's referenced by the revision manager // The third proposal remains referenced let p2hash = proposal2.root_hash().await.unwrap().unwrap(); - assert!(db.all_hashes().await.unwrap().contains(&p2hash)); + assert!(db.all_hashes().unwrap().contains(&p2hash)); drop(proposal2); // commit the first proposal proposal1.commit().unwrap(); // Ensure we committed the first proposal's data - let committed = db.root_hash().await.unwrap().unwrap(); - let historical = db.revision(committed).await.unwrap(); + let committed = db.root_hash().unwrap().unwrap(); + let historical = db.revision(committed).unwrap(); assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); // the second proposal shouldn't be available to commit anymore - assert!(!db.all_hashes().await.unwrap().contains(&p2hash)); + assert!(!db.all_hashes().unwrap().contains(&p2hash)); // the third proposal should still be contained within the all_hashes list let hash3 = proposal3.root_hash().await.unwrap().unwrap(); @@ -658,7 +612,7 @@ mod test { key: b"historical_key", value: b"historical_value", }]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); let historical_hash = proposal.root_hash().await.unwrap().unwrap(); proposal.commit().unwrap(); @@ -667,7 +621,7 @@ mod test { key: b"proposal_key", value: b"proposal_value", }]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); let proposal_hash = proposal.root_hash().await.unwrap().unwrap(); // Test that view_sync can find the historical revision @@ -712,7 +666,7 @@ mod test { let mut kviter = keys.iter().zip(vals.iter()).map_into_batch(); // create two proposals, second one has a base of the first one - let proposal1 = db.propose(kviter.by_ref().take(N / 2)).await.unwrap(); + let proposal1 = db.propose(kviter.by_ref().take(N / 2)).unwrap(); let proposal2 = proposal1.propose(kviter).unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal @@ -743,8 +697,8 @@ mod test { proposal2.commit().unwrap(); // all keys are in the database - let committed = db.root_hash().await.unwrap().unwrap(); - let revision = db.revision(committed).await.unwrap(); + let committed = db.root_hash().unwrap().unwrap(); + let revision = db.revision(committed).unwrap(); for (k, v) in keys.into_iter().zip(vals.into_iter()) { assert_eq!(revision.val(k).await.unwrap().unwrap(), v); @@ -777,7 +731,7 @@ mod test { } batch }); - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); proposal.commit().unwrap(); // check the database for consistency, sometimes checking the hashes @@ -819,7 +773,7 @@ mod test { let proposal = if let Some(parent) = proposals.last() { parent.propose(ops).unwrap() } else { - db.propose(ops).await.unwrap() + db.propose(ops).unwrap() }; proposals.push(proposal); @@ -842,8 +796,8 @@ mod test { } // get the last committed revision - let last_root_hash = db.root_hash().await.unwrap().unwrap(); - let committed = db.revision(last_root_hash.clone()).await.unwrap(); + let last_root_hash = db.root_hash().unwrap().unwrap(); + let committed = db.revision(last_root_hash.clone()).unwrap(); // the last root hash should be the same as the last proposal root hash assert_eq!(last_root_hash, last_proposal_root_hash); @@ -892,7 +846,7 @@ mod test { key: [id as u8; 32], value: [id as u8; 8], }]; - let proposal = db.propose(batch).await.unwrap(); + let proposal = db.propose(batch).unwrap(); let last_hash = proposal.root_hash().await.unwrap().unwrap(); let view = db.view_sync(last_hash).unwrap(); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index b0db0c169695..2ab5de5155a7 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -185,7 +185,6 @@ impl From for Error { /// recently committed revision, and allow the creation of a new /// [`Proposal`] or a new [`DbView`] based on a specific historical /// revision. -#[async_trait] pub trait Db { /// The type of a historical revision type Historical: DbView; @@ -200,7 +199,8 @@ pub trait Db { /// # Arguments /// /// - `hash` - Identifies the revision for the view - async fn revision(&self, hash: TrieHash) -> Result, Error>; + #[expect(clippy::missing_errors_doc)] + fn revision(&self, hash: TrieHash) -> Result, Error>; /// Get the hash of the most recently committed version /// @@ -208,10 +208,12 @@ pub trait Db { /// /// If the database is empty, this will return None, unless the ethhash feature is enabled. /// In that case, we return the special ethhash compatible empty trie hash. - async fn root_hash(&self) -> Result, Error>; + #[expect(clippy::missing_errors_doc)] + fn root_hash(&self) -> Result, Error>; /// Get all the hashes available - async fn all_hashes(&self) -> Result, Error>; + #[expect(clippy::missing_errors_doc)] + fn all_hashes(&self) -> Result, Error>; /// Propose a change to the database via a batch /// @@ -220,15 +222,13 @@ pub trait Db { /// /// # Arguments /// - /// * `data` - A batch consisting of [`BatchOp::Put`] and - /// [`BatchOp::Delete`] operations to apply - /// - async fn propose<'db>( - &'db self, - data: (impl IntoIterator + Send), - ) -> Result, Error> - where - Self: 'db; + /// * `data` - A batch consisting of [`BatchOp::Put`] and [`BatchOp::Delete`] + /// operations to apply + #[expect(clippy::missing_errors_doc)] + fn propose( + &self, + data: impl IntoIterator, + ) -> Result, Error>; } /// A view of the database at a specific time. diff --git a/fwdctl/src/delete.rs b/fwdctl/src/delete.rs index 1d5d14a36c80..f59e71166f8b 100644 --- a/fwdctl/src/delete.rs +++ b/fwdctl/src/delete.rs @@ -17,7 +17,7 @@ pub struct Options { pub key: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("deleting key {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); @@ -26,7 +26,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let batch: Vec> = vec![BatchOp::Delete { key: opts.key.clone(), }]; - let proposal = db.propose(batch).await?; + let proposal = db.propose(batch)?; proposal.commit()?; println!("key {} deleted successfully", opts.key); diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 8da258ca3629..fe380a1d12fa 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -144,12 +144,12 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let cfg = DbConfig::builder().create_if_missing(false).truncate(false); let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; - let latest_hash = db.root_hash().await?; + let latest_hash = db.root_hash()?; let Some(latest_hash) = latest_hash else { println!("Database is empty"); return Ok(()); }; - let latest_rev = db.revision(latest_hash).await?; + let latest_rev = db.revision(latest_hash)?; let Some(mut output_handler) = create_output_handler(opts, &db).expect("Error creating output handler") diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 9650f8cef9df..522d8110a947 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -24,14 +24,14 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; - let hash = db.root_hash().await?; + let hash = db.root_hash()?; let Some(hash) = hash else { println!("Database is empty"); return Ok(()); }; - let rev = db.revision(hash).await?; + let rev = db.revision(hash)?; match rev.val(opts.key.as_bytes()).await { Ok(Some(val)) => { diff --git a/fwdctl/src/insert.rs b/fwdctl/src/insert.rs index 7325a555bfc9..bce652195cf2 100644 --- a/fwdctl/src/insert.rs +++ b/fwdctl/src/insert.rs @@ -21,7 +21,7 @@ pub struct Options { pub value: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("inserting key value pair {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); @@ -31,7 +31,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { key: opts.key.clone().into(), value: opts.value.bytes().collect(), }]; - let proposal = db.propose(batch).await?; + let proposal = db.propose(batch)?; proposal.commit()?; println!("{}", opts.key); diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index bf559294d5d2..e35d747ce87c 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -82,10 +82,10 @@ async fn main() -> Result<(), api::Error> { match &cli.command { Commands::Create(opts) => create::run(opts), - Commands::Insert(opts) => insert::run(opts).await, + Commands::Insert(opts) => insert::run(opts), Commands::Get(opts) => get::run(opts).await, - Commands::Delete(opts) => delete::run(opts).await, - Commands::Root(opts) => root::run(opts).await, + Commands::Delete(opts) => delete::run(opts), + Commands::Root(opts) => root::run(opts), Commands::Dump(opts) => dump::run(opts).await, Commands::Graph(opts) => graph::run(opts), Commands::Check(opts) => check::run(opts), diff --git a/fwdctl/src/root.rs b/fwdctl/src/root.rs index 949b8f693ab8..522253808e27 100644 --- a/fwdctl/src/root.rs +++ b/fwdctl/src/root.rs @@ -14,12 +14,12 @@ pub struct Options { pub database: DatabasePath, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { let cfg = DbConfig::builder().create_if_missing(false).truncate(false); let db = Db::new(opts.database.dbpath.clone(), cfg.build())?; - let hash = db.root_hash().await?; + let hash = db.root_hash()?; println!("{hash:?}"); Ok(()) From 1b72d7d5aa615d222238bf0f2500d07ba63aff8d Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:41:22 -0500 Subject: [PATCH 0910/1053] chore: only allocate the area needed (#1217) As shown from statistics, Firewood currently has a lot of internal fragmentations because it will allocate any large enough free area from the free list, often times in which the allocated area is several times bigger than the area needed. This PR mitigates this issue by only allocate the area needed to store the node but not larger areas. It reduces storage overhead to 1/4. --- storage/src/nodestore/alloc.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index c0e5a6fa4c5d..06b4a9b2c9ff 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -223,24 +223,18 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { n: u64, ) -> Result, FileIoError> { // Find the smallest free list that can fit this size. - let index_wanted = AreaIndex::from_size(n).map_err(|e| { + let index = AreaIndex::from_size(n).map_err(|e| { self.storage .file_io_error(e, 0, Some("allocate_from_freed".to_string())) })?; - if let Some((index, free_stored_area_addr)) = self + let free_stored_area_addr = self .header .free_lists_mut() - .iter_mut() - .enumerate() - .skip(index_wanted.as_usize()) - .find(|item| item.1.is_some()) - { - let index = - AreaIndex::try_from(index).expect("index is less than AreaIndex::NUM_AREA_SIZES"); - let address = free_stored_area_addr - .take() - .expect("impossible due to find earlier"); + .get_mut(index.as_usize()) + .expect("index is less than AreaIndex::NUM_AREA_SIZES"); + if let Some(address) = free_stored_area_addr { + let address = *address; // Get the first free block of sufficient size. if let Some(free_head) = self.storage.free_list_cache(address) { trace!("free_head@{address}(cached): {free_head:?} size:{index}"); @@ -271,13 +265,13 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { return Ok(Some((address, index))); } - trace!("No free blocks of sufficient size {index_wanted} found"); + trace!("No free blocks of sufficient size {index} found"); firewood_counter!( "firewood.space.from_end", "Space allocated from end of nodestore", - "index" => index_name(index_wanted) + "index" => index_name(index) ) - .increment(index_wanted.size()); + .increment(index.size()); Ok(None) } From 35c8ec6e35819d57504de4848f3cba4a7bcd1161 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:37:57 -0500 Subject: [PATCH 0911/1053] feat(checker): fix error with free area that is not head of a free list (#1231) --- storage/src/checker/mod.rs | 243 +++++++++++++++++++++------------ storage/src/lib.rs | 16 ++- storage/src/nodestore/alloc.rs | 30 +++- 3 files changed, 195 insertions(+), 94 deletions(-) diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 625a6c131950..0d08de9a75cd 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -8,9 +8,9 @@ use crate::logger::warn; use crate::nodestore::alloc::FreeAreaWithMetadata; use crate::nodestore::primitives::{AreaIndex, area_size_iter}; use crate::{ - CheckerError, Committed, FileIoError, HashType, HashedNodeReader, IntoHashType, LinearAddress, - Node, NodeStore, Path, ReadableStorage, RootReader, StoredAreaParent, TrieNodeParent, - WritableStorage, + CheckerError, Committed, FileIoError, FreeListParent, HashType, HashedNodeReader, IntoHashType, + LinearAddress, Node, NodeStore, Path, ReadableStorage, RootReader, StoredAreaParent, + TrieNodeParent, WritableStorage, }; #[cfg(not(feature = "ethhash"))] @@ -578,7 +578,7 @@ impl NodeStore { } fn fix(&self, check_report: CheckerReport) -> FixReport { - let fixed = Vec::new(); + let mut fixed = Vec::new(); let mut unfixable = Vec::new(); for error in check_report.errors { @@ -587,10 +587,22 @@ impl NodeStore { warn!("Fix for trie node error not yet implemented"); unfixable.push((error, None)); } - Some(StoredAreaParent::FreeList(_)) => { - warn!("Fix for free list error not yet implemented"); - unfixable.push((error, None)); - } + Some(StoredAreaParent::FreeList(free_list_parent)) => match free_list_parent { + FreeListParent::FreeListHead(_) => { + warn!("Fix for free list head error not yet implemented"); + unfixable.push((error, None)); + } + FreeListParent::PrevFreeArea { + area_size_idx, + parent_addr, + } => { + if let Err(e) = self.truncate_free_list(area_size_idx, parent_addr) { + unfixable.push((error, Some(e))); + } else { + fixed.push(error); + } + } + }, None => { if let CheckerError::AreaLeaks(ranges) = &error { { @@ -836,6 +848,104 @@ mod test { } } + #[derive(Debug)] + struct TestFreelist { + high_watermark: u64, + free_ranges: Vec>, + errors: Vec, + stats: FreeListsStats, + } + + // Free list 1: 2048 bytes + // Free list 2: 8192 bytes + // --------------------------------------------------------------------------------------------------------------------------- + // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | + // --------------------------------------------------------------------------------------------------------------------------- + // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes + // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned + #[expect(clippy::arithmetic_side_effects)] + fn gen_test_freelist_with_overlap( + nodestore: &mut NodeStore, + ) -> TestFreelist { + const AREA_INDEX1: AreaIndex = area_index!(9); // 2048 + const AREA_INDEX2: AreaIndex = area_index!(12); // 16384 + + let mut free_lists = FreeLists::default(); + let mut high_watermark = NodeStoreHeader::SIZE + 16; // + 16 to create overlap + + // first free list + let area_size1 = AREA_INDEX1.size(); + let mut next_free_block1 = None; + + test_write_free_area(nodestore, next_free_block1, AREA_INDEX1, high_watermark); + let free_list1_area3 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area3); + high_watermark += area_size1; + + test_write_free_area(nodestore, next_free_block1, AREA_INDEX1, high_watermark); + let free_list1_area2 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area2); + high_watermark += area_size1; + + let intersection_end = LinearAddress::new(high_watermark).unwrap(); + high_watermark -= 16; // create an overlap with free_list1_area2 and restore to page-aligned address + let intersection_start = LinearAddress::new(high_watermark).unwrap(); + test_write_free_area(nodestore, next_free_block1, AREA_INDEX1, high_watermark); + let free_list1_area1 = LinearAddress::new(high_watermark).unwrap(); + next_free_block1 = Some(free_list1_area1); + high_watermark += area_size1; + + free_lists[AREA_INDEX1.as_usize()] = next_free_block1; + + // second free list + let area_size2 = AREA_INDEX2.size(); + let mut next_free_block2 = None; + + test_write_free_area(nodestore, next_free_block2, AREA_INDEX2, high_watermark); + let free_list2_area2 = LinearAddress::new(high_watermark).unwrap(); + next_free_block2 = Some(free_list2_area2); + high_watermark += area_size2; + + test_write_free_area(nodestore, next_free_block2, AREA_INDEX2, high_watermark); + let free_list2_area1 = LinearAddress::new(high_watermark).unwrap(); + next_free_block2 = Some(free_list2_area1); + high_watermark += area_size2; + + free_lists[AREA_INDEX2.as_usize()] = next_free_block2; + + // write header + test_write_header(nodestore, high_watermark, None, free_lists); + + let expected_start_addr = free_lists[AREA_INDEX1.as_usize()].unwrap(); + let expected_end_addr = LinearAddress::new(high_watermark).unwrap(); + let expected_free_areas = vec![expected_start_addr..expected_end_addr]; + let expected_freelist_errors = vec![CheckerError::AreaIntersects { + start: free_list1_area2, + size: area_size1, + intersection: vec![intersection_start..intersection_end], + parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea { + area_size_idx: AREA_INDEX1, + parent_addr: free_list1_area1, + }), + }]; + let mut area_counts = area_size_iter() + .map(|(_, size)| (size, 0)) + .collect::>(); + area_counts.insert(area_size1, 1); + area_counts.insert(area_size2, 2); + let expected_free_lists_stats = FreeListsStats { + area_counts, + area_extra_unaligned_page: 0, + }; + + TestFreelist { + high_watermark, + free_ranges: expected_free_areas, + errors: expected_freelist_errors, + stats: expected_free_lists_stats, + } + } + use std::collections::HashMap; #[test] @@ -980,93 +1090,52 @@ mod test { } #[test] - // Free list 1: 2048 bytes - // Free list 2: 8192 bytes - // --------------------------------------------------------------------------------------------------------------------------- - // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | - // --------------------------------------------------------------------------------------------------------------------------- - // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes - // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned - #[expect(clippy::arithmetic_side_effects)] fn traverse_freelist_should_skip_offspring_of_incorrect_areas() { - const AREA_INDEX1: AreaIndex = area_index!(9); // 2048 - const AREA_INDEX2: AreaIndex = area_index!(12); // 16384 - let memstore = MemStore::new(vec![]); let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); - - let mut free_lists = FreeLists::default(); - let mut high_watermark = NodeStoreHeader::SIZE + 16; // + 16 to create overlap - - // first free list - let area_size1 = AREA_INDEX1.size(); - let mut next_free_block1 = None; - - test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); - let free_list1_area3 = LinearAddress::new(high_watermark).unwrap(); - next_free_block1 = Some(free_list1_area3); - high_watermark += area_size1; - - test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); - let free_list1_area2 = LinearAddress::new(high_watermark).unwrap(); - next_free_block1 = Some(free_list1_area2); - high_watermark += area_size1; - - let intersection_end = LinearAddress::new(high_watermark).unwrap(); - high_watermark -= 16; // create an overlap with free_list1_area2 and restore to page-aligned address - let intersection_start = LinearAddress::new(high_watermark).unwrap(); - test_write_free_area(&nodestore, next_free_block1, AREA_INDEX1, high_watermark); - let free_list1_area1 = LinearAddress::new(high_watermark).unwrap(); - next_free_block1 = Some(free_list1_area1); - high_watermark += area_size1; - - free_lists[AREA_INDEX1.as_usize()] = next_free_block1; - - // second free list - let area_size2 = AREA_INDEX2.size(); - let mut next_free_block2 = None; - - test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, high_watermark); - let free_list2_area2 = LinearAddress::new(high_watermark).unwrap(); - next_free_block2 = Some(free_list2_area2); - high_watermark += area_size2; - - test_write_free_area(&nodestore, next_free_block2, AREA_INDEX2, high_watermark); - let free_list2_area1 = LinearAddress::new(high_watermark).unwrap(); - next_free_block2 = Some(free_list2_area1); - high_watermark += area_size2; - - free_lists[AREA_INDEX2.as_usize()] = next_free_block2; - - // write header - test_write_header(&mut nodestore, high_watermark, None, free_lists); - - let expected_start_addr = free_lists[AREA_INDEX1.as_usize()].unwrap(); - let expected_end_addr = LinearAddress::new(high_watermark).unwrap(); - let expected_free_areas = vec![expected_start_addr..expected_end_addr]; - let expected_freelist_errors = vec![CheckerError::AreaIntersects { - start: free_list1_area2, - size: area_size1, - intersection: vec![intersection_start..intersection_end], - parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea(free_list1_area1)), - }]; - let mut area_counts = area_size_iter() - .map(|(_, size)| (size, 0)) - .collect::>(); - area_counts.insert(area_size1, 1); - area_counts.insert(area_size2, 2); - let expected_free_lists_stats = FreeListsStats { - area_counts, - area_extra_unaligned_page: 0, - }; + let TestFreelist { + high_watermark, + free_ranges, + errors, + stats, + } = gen_test_freelist_with_overlap(&mut nodestore); // test that the we traversed all the free areas let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); let (actual_free_lists_stats, free_list_errors) = nodestore.visit_freelist(&mut visited, None); - assert_eq!(visited.into_iter().collect::>(), expected_free_areas); - assert_eq!(actual_free_lists_stats, expected_free_lists_stats); - assert_eq!(free_list_errors, expected_freelist_errors); + assert_eq!(visited.into_iter().collect::>(), free_ranges); + assert_eq!(actual_free_lists_stats, stats); + assert_eq!(free_list_errors, errors); + } + + #[test] + fn fix_freelist_with_overlap() { + let memstore = MemStore::new(vec![]); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let TestFreelist { + high_watermark, + free_ranges: _, + errors, + stats, + } = gen_test_freelist_with_overlap(&mut nodestore); + let expected_error_num = errors.len(); + + // fix the freelist + let fix_report = nodestore.fix(CheckerReport { + errors, + db_stats: DBStats { + high_watermark, + trie_stats: TrieStats::default(), + free_list_stats: stats, + }, + }); + assert_eq!(fix_report.fixed.len(), expected_error_num); + assert_eq!(fix_report.unfixable.len(), 0); + + let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); + let (_, free_list_errors) = nodestore.visit_freelist(&mut visited, None); + assert_eq!(free_list_errors, vec![]); } #[test] diff --git a/storage/src/lib.rs b/storage/src/lib.rs index a9321a95214c..bb61440ee847 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -109,7 +109,12 @@ pub enum FreeListParent { /// The stored area is the head of the free list, so the header points to it FreeListHead(AreaIndex), /// The stored area is not the head of the free list, so a previous free area points to it - PrevFreeArea(LinearAddress), + PrevFreeArea { + /// The size index of the area, helps with debugging and fixing + area_size_idx: AreaIndex, + /// The address of the previous free area + parent_addr: LinearAddress, + }, } impl LowerHex for StoredAreaParent { @@ -138,9 +143,12 @@ impl LowerHex for FreeListParent { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { FreeListParent::FreeListHead(index) => f.write_fmt(format_args!("FreeLists[{index}]")), - FreeListParent::PrevFreeArea(addr) => { - f.write_str("FreeArea@")?; - LowerHex::fmt(addr, f) + FreeListParent::PrevFreeArea { + area_size_idx, + parent_addr, + } => { + f.write_fmt(format_args!("FreeArea[{area_size_idx}]@"))?; + LowerHex::fmt(parent_addr, f) } } } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 06b4a9b2c9ff..55bb8d63abef 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -414,7 +414,10 @@ impl Iterator for FreeListIterator<'_, S> { }; // update the next address to the next free block - self.parent = FreeListParent::PrevFreeArea(next_addr); + self.parent = FreeListParent::PrevFreeArea { + area_size_idx: stored_area_index, + parent_addr: next_addr, + }; self.next_addr = free_area.next_free_block(); Some(Ok((next_addr, stored_area_index))) } @@ -523,6 +526,21 @@ impl NodeStore { } } +// Functionalities use by the checker +impl NodeStore { + pub(crate) fn truncate_free_list( + &self, + area_size_index: AreaIndex, + addr: LinearAddress, + ) -> Result<(), FileIoError> { + let free_area = FreeArea::new(None); + let mut stored_area_bytes = Vec::new(); + free_area.as_bytes(area_size_index, &mut stored_area_bytes); + self.storage.write(addr.into(), &stored_area_bytes)?; + Ok(()) + } +} + fn read_bincode_varint_u64_le(reader: &mut impl Read) -> std::io::Result { // See https://github.com/ava-labs/firewood/issues/1146 for full details. // emulate this behavior: https://github.com/bincode-org/bincode/blob/c44b5e364e7084cdbabf9f94b63a3c7f32b8fb68/src/config/int.rs#L241-L258 @@ -779,7 +797,10 @@ mod tests { area_index: area_index1, free_list_id: area_index1, }, - FreeListParent::PrevFreeArea(free_list1_area1), + FreeListParent::PrevFreeArea { + area_size_idx: area_index1, + parent_addr: free_list1_area1, + }, ), ]; @@ -798,7 +819,10 @@ mod tests { area_index: area_index2, free_list_id: area_index2, }, - FreeListParent::PrevFreeArea(free_list2_area1), + FreeListParent::PrevFreeArea { + area_size_idx: area_index2, + parent_addr: free_list2_area1, + }, ), ]; From 72d0ee9b76de375aef158ee5c6cb79425a9b6688 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 10:07:59 -0700 Subject: [PATCH 0912/1053] chore: synchronize .golangci.yaml (#1234) https://github.com/ava-labs/firewood/actions/runs/17160824491/job/48689403352 ran ```console $ .github/scripts/verify_golangci_yaml_changes.sh apply Downloading upstream .golangci.yaml from 'https://raw.githubusercontent.com/ava-labs/avalanchego/refs/heads/master/.golangci.yml'... patching file '.github/.golangci.yaml' Successfully applied the patch from .github/.golangci.yaml.patch to ffi/.golangci.yaml Updated expected changes in .github/.golangci.yaml.patch $ .github/scripts/verify_golangci_yaml_changes.sh update Downloading upstream .golangci.yaml from 'https://raw.githubusercontent.com/ava-labs/avalanchego/refs/heads/master/.golangci.yml'... Updated expected changes in .github/.golangci.yaml.patch ``` and then also updated the gitignore because patch generated the orig file. --- .github/.gitignore | 1 + .github/.golangci.yaml.patch | 10 +++++----- ffi/.golangci.yaml | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/.gitignore b/.github/.gitignore index 713fdda5ac5c..29c5df23e83f 100644 --- a/.github/.gitignore +++ b/.github/.gitignore @@ -1 +1,2 @@ .golangci.yaml +.golangci.yaml.orig diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index 5b6f88091cd1..b9898cfe4203 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,5 +1,5 @@ ---- .github/.golangci.yaml 2025-08-02 10:10:13 -+++ ffi/.golangci.yaml 2025-08-02 10:10:13 +--- .github/.golangci.yaml 2025-08-22 10:01:30 ++++ ffi/.golangci.yaml 2025-08-22 10:01:26 @@ -69,8 +69,6 @@ rules: packages: @@ -39,7 +39,7 @@ revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr -@@ -168,17 +147,6 @@ +@@ -171,17 +150,6 @@ # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - name: useless-break disabled: false @@ -57,7 +57,7 @@ tagalign: align: true sort: true -@@ -186,16 +154,16 @@ +@@ -189,16 +157,16 @@ - serialize strict: true testifylint: @@ -84,7 +84,7 @@ unused: # Mark all struct fields that have been written to as used. # Default: true -@@ -224,7 +192,7 @@ +@@ -227,7 +195,7 @@ - standard - default - blank diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index 6f205440cce9..035839e55a76 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -98,6 +98,9 @@ linters: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines - name: empty-lines disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redundant-import-alias + - name: redundant-import-alias + disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format - name: string-format disabled: false From d3eafeeb5a69dbcd1425d65e8ecdb65a4ec9088c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 10:51:01 -0700 Subject: [PATCH 0913/1053] ci(metrics-check): Re-use previous comment instead of spamming new ones (#1232) --- .github/workflows/metrics-check.yaml | 74 ++++++++++++++-------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/metrics-check.yaml b/.github/workflows/metrics-check.yaml index 4858f4b176d9..9f997bc0ff6a 100644 --- a/.github/workflows/metrics-check.yaml +++ b/.github/workflows/metrics-check.yaml @@ -4,6 +4,10 @@ on: pull_request: types: [opened, synchronize, reopened] +concurrency: + group: metrics-check-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: check-metrics-changes: runs-on: ubuntu-latest @@ -30,7 +34,7 @@ jobs: fi # Regex pattern to match metric-related code changes - METRIC_REGEX_PATTERN="(counter!|gauge!|histogram!|#\\[metrics\\])" + METRIC_REGEX_PATTERN="(counter!|gauge!|histogram!|#\\[metrics\\])" # ci trigger # Check for metric-related changes METRIC_CHANGES=$(git diff $BASE_COMMIT..HEAD --unified=0 | grep -E "$METRIC_REGEX_PATTERN" || true) @@ -44,43 +48,39 @@ jobs: # Set output variables for the comment step echo "metric_changes_found=$([ -n "$METRIC_CHANGES" ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT - echo "metric_pattern=$METRIC_REGEX_PATTERN" >> $GITHUB_OUTPUT + echo "metric_changes<> $GITHUB_OUTPUT + echo "$METRIC_CHANGES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Look for previous comment + if: github.repository == github.event.pull_request.head.repo.full_name && steps.check-metrics.outputs.metric_changes_found == 'true' + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: github-actions[bot] + body-includes: "## Metrics Change Detection ⚠️" - name: Comment on PR (if applicable) if: github.repository == github.event.pull_request.head.repo.full_name && steps.check-metrics.outputs.metric_changes_found == 'true' - uses: actions/github-script@v7 + uses: peter-evans/create-or-update-comment@v4 with: - script: | - const { execSync } = require('child_process'); - - try { - // Get the base commit for comparison - const baseCommit = context.payload.pull_request.base.sha; - - // Get the metric pattern from the previous step - const metricPattern = "${{ steps.check-metrics.outputs.metric_pattern }}"; - - // Check for metric changes - const metricChanges = execSync(`git diff ${baseCommit}..HEAD --unified=0 | grep -E "${metricPattern}" || true`, { encoding: 'utf8' }); - - if (metricChanges.trim()) { - const comment = '## Metrics Change Detection ⚠️\n\n' + - 'This PR contains changes related to metrics:\n\n' + - '```\n' + - metricChanges + - '\n```\n\n' + - 'However, the dashboard was not modified.\n\n' + - 'You may need to update `benchmark/Grafana-dashboard.json` accordingly.\n\n' + - '---\n' + - '*This check is automated to help maintain the dashboard.*'; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); - } - } catch (error) { - console.log('No metric changes found or error occurred:', error.message); - } + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Metrics Change Detection ⚠️ + + This PR contains changes related to metrics: + + ``` + ${{ steps.check-metrics.outputs.metric_changes }} + ``` + + However, the dashboard was not modified. + + You may need to update `benchmark/Grafana-dashboard.json` accordingly. + + --- + + *This check is automated to help maintain the dashboard.* + edit-mode: replace + comment-id: ${{ steps.find-comment.outputs.comment-id }} From 88a793396effe1470fbb9cf10aabeff27ae57c8f Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 12:27:02 -0700 Subject: [PATCH 0914/1053] feat(async-removal): Phase 4 - Make `DbView` synchronous (#1219) This also removes all final `async` and `stream` usages in the code, with the exception of `firewood-benchmark` which must use tokio for handling the prometheus metrics listener. This finalizes the `async` removal process. # Why `ArcDynDbView`? You may wonder why I replaced `Box` with `Arc`. This is because in both cases where it was used, we already had an Arc object that the compiler could coerce into our `Arc` variant without adding an additional box into the mix. This removes one layer of pointer indirection. Fixes: #798 --- Cargo.toml | 1 - benchmark/Cargo.toml | 18 +- benchmark/src/create.rs | 2 +- benchmark/src/main.rs | 77 ++- benchmark/src/single.rs | 2 +- benchmark/src/tenkrandom.rs | 2 +- benchmark/src/zipf.rs | 2 +- ffi/src/lib.rs | 43 +- firewood/Cargo.toml | 7 +- firewood/benches/hashops.rs | 49 +- firewood/examples/insert.rs | 9 +- firewood/src/db.rs | 340 +++++------- firewood/src/{stream.rs => iter.rs} | 656 +++++++++-------------- firewood/src/iter/try_extend.rs | 75 +++ firewood/src/lib.rs | 6 +- firewood/src/manager.rs | 11 +- firewood/src/merkle.rs | 50 +- firewood/src/merkle/tests/mod.rs | 6 +- firewood/src/merkle/tests/unvalidated.rs | 20 +- firewood/src/v2/api.rs | 137 ++++- fwdctl/Cargo.toml | 2 - fwdctl/src/dump.rs | 11 +- fwdctl/src/get.rs | 4 +- fwdctl/src/main.rs | 7 +- storage/Cargo.toml | 2 +- 25 files changed, 740 insertions(+), 799 deletions(-) rename firewood/src/{stream.rs => iter.rs} (67%) create mode 100644 firewood/src/iter/try_extend.rs diff --git a/Cargo.toml b/Cargo.toml index 46d99de1a7c8..982652c01953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,6 @@ sha2 = "0.10.9" smallvec = "1.15.1" test-case = "3.3.1" thiserror = "2.0.12" -tokio = "1.46.1" # common dev dependencies criterion = "0.7.0" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index fb08eaf72a26..f874cd52cc11 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -23,28 +23,38 @@ clap = { workspace = true, features = ['string'] } env_logger.workspace = true fastrace = { workspace = true, features = ["enable"] } firewood.workspace = true +firewood-storage = { workspace = true, features = ["test_utils"] } hex.workspace = true log.workspace = true metrics.workspace = true -metrics-util.workspace = true +metrics-util = { workspace = true, optional = true } rand.workspace = true rand_distr.workspace = true sha2.workspace = true -tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } # Regular dependencies fastrace-opentelemetry = { version = "0.13.0" } -metrics-exporter-prometheus = "0.17.2" +metrics-exporter-prometheus = { version = "0.17.2", optional = true } opentelemetry = "0.30.0" opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } opentelemetry_sdk = "0.30.0" pretty-duration = "0.1.1" -firewood-storage = { workspace = true, features = ["test_utils"] } + +[dependencies.tokio] +optional = true +version = "1.47.1" +features = ["mio", "net", "parking_lot", "rt", "time"] [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" [features] +default = ["prometheus"] logger = ["firewood/logger"] +prometheus = [ + "dep:metrics-exporter-prometheus", + "dep:metrics-util", + "dep:tokio", +] [lints] workspace = true diff --git a/benchmark/src/create.rs b/benchmark/src/create.rs index df8ab5d902f3..dde8b096ce3d 100644 --- a/benchmark/src/create.rs +++ b/benchmark/src/create.rs @@ -23,7 +23,7 @@ use crate::{Args, TestRunner}; pub struct Create; impl TestRunner for Create { - async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { let keys = args.global_opts.batch_size; let start = Instant::now(); diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index 23d9014167e6..9af45df8e28f 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -16,16 +16,12 @@ use clap::{Parser, Subcommand, ValueEnum}; use fastrace_opentelemetry::OpenTelemetryReporter; use firewood::logger::trace; use log::LevelFilter; -use metrics_exporter_prometheus::PrometheusBuilder; -use metrics_util::MetricKindMask; use sha2::{Digest, Sha256}; use std::borrow::Cow; use std::error::Error; use std::fmt::Display; -use std::net::{Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; use std::path::PathBuf; -use std::time::Duration; use firewood::db::{BatchOp, Db, DbConfig}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; @@ -62,6 +58,7 @@ struct GlobalOpts { cache_size: NonZeroUsize, #[arg(short, long, default_value_t = 128)] revisions: usize, + #[cfg(feature = "prometheus")] #[arg( short = 'p', long, @@ -69,6 +66,7 @@ struct GlobalOpts { help = "Port to listen for prometheus" )] prometheus_port: u16, + #[cfg(feature = "prometheus")] #[arg( short = 's', long, @@ -159,7 +157,7 @@ enum TestName { } trait TestRunner { - async fn run(&self, db: &Db, args: &Args) -> Result<(), Box>; + fn run(&self, db: &Db, args: &Args) -> Result<(), Box>; fn generate_inserts( start: u64, @@ -185,8 +183,7 @@ trait TestRunner { #[cfg(unix)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -#[tokio::main(flavor = "multi_thread")] -async fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { let args = Args::parse(); if args.global_opts.telemetry_server { @@ -226,23 +223,9 @@ async fn main() -> Result<(), Box> { .init(); // Manually set up prometheus - let builder = PrometheusBuilder::new(); - let (prometheus_recorder, listener_future) = builder - .with_http_listener(SocketAddr::new( - Ipv6Addr::UNSPECIFIED.into(), - args.global_opts.prometheus_port, - )) - .idle_timeout( - MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, - Some(Duration::from_secs(10)), - ) - .build() - .expect("unable in run prometheusbuilder"); - - // Clone the handle so we can dump the stats at the end - let prometheus_handle = prometheus_recorder.handle(); - metrics::set_global_recorder(prometheus_recorder)?; - tokio::spawn(listener_future); + #[cfg(feature = "prometheus")] + let prometheus_handle = spawn_prometheus_listener(args.global_opts.prometheus_port) + .expect("failed to spawn prometheus listener"); let mgrcfg = RevisionManagerConfig::builder() .node_cache_size(args.global_opts.cache_size) @@ -262,22 +245,23 @@ async fn main() -> Result<(), Box> { match args.test_name { TestName::Create => { let runner = create::Create; - runner.run(&db, &args).await?; + runner.run(&db, &args)?; } TestName::TenKRandom => { let runner = tenkrandom::TenKRandom; - runner.run(&db, &args).await?; + runner.run(&db, &args)?; } TestName::Zipf(_) => { let runner = zipf::Zipf; - runner.run(&db, &args).await?; + runner.run(&db, &args)?; } TestName::Single => { let runner = single::Single; - runner.run(&db, &args).await?; + runner.run(&db, &args)?; } } + #[cfg(feature = "prometheus")] if args.global_opts.stats_dump { println!("{}", prometheus_handle.render()); } @@ -286,3 +270,40 @@ async fn main() -> Result<(), Box> { Ok(()) } + +#[cfg(feature = "prometheus")] +fn spawn_prometheus_listener( + port: u16, +) -> Result> { + use metrics_exporter_prometheus::PrometheusBuilder; + use metrics_util::MetricKindMask; + use std::net::{Ipv6Addr, SocketAddr}; + use std::time::Duration; + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + + let (recorder, exporter) = { + // PrometheusBuilder::build requires that we be within the tokio runtime context + // but we don't need to actually invoke the runtime until we spawn the thread + let _guard = rt.enter(); + PrometheusBuilder::new() + .with_http_listener(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port)) + .idle_timeout( + MetricKindMask::COUNTER | MetricKindMask::HISTOGRAM, + Some(Duration::from_secs(10)), + ) + .build()? + }; + + std::thread::Builder::new() + .name("metrics-exporter-prometheus".to_owned()) + .spawn(move || rt.block_on(exporter))?; + + let handle = recorder.handle(); + + metrics::set_global_recorder(recorder)?; + + Ok(handle) +} diff --git a/benchmark/src/single.rs b/benchmark/src/single.rs index e64efbca7c6f..04642d24a2dd 100644 --- a/benchmark/src/single.rs +++ b/benchmark/src/single.rs @@ -23,7 +23,7 @@ use std::time::Instant; pub struct Single; impl TestRunner for Single { - async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { + fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { let start = Instant::now(); let inner_keys: Vec<_> = (0..args.global_opts.batch_size) .map(|i| Sha256::digest(i.to_ne_bytes())) diff --git a/benchmark/src/tenkrandom.rs b/benchmark/src/tenkrandom.rs index bc762206b6db..ec47b8b8379b 100644 --- a/benchmark/src/tenkrandom.rs +++ b/benchmark/src/tenkrandom.rs @@ -20,7 +20,7 @@ use sha2::{Digest, Sha256}; pub struct TenKRandom; impl TestRunner for TenKRandom { - async fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { + fn run(&self, db: &Db, args: &Args) -> Result<(), Box> { let mut low = 0; let mut high = args.global_opts.number_of_batches * args.global_opts.batch_size; let twenty_five_pct = args.global_opts.batch_size / 4; diff --git a/benchmark/src/zipf.rs b/benchmark/src/zipf.rs index 806e5c7ca963..d1b76bbc7187 100644 --- a/benchmark/src/zipf.rs +++ b/benchmark/src/zipf.rs @@ -39,7 +39,7 @@ pub struct Args { pub struct Zipf; impl TestRunner for Zipf { - async fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { + fn run(&self, db: &Db, args: &crate::Args) -> Result<(), Box> { let exponent = if let crate::TestName::Zipf(args) = &args.test_name { args.exponent } else { diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 5f05e4ea1b80..f12b010ae878 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -36,10 +36,10 @@ use std::ops::Deref; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Mutex, RwLock}; -use firewood::db::{Db, DbConfig, DbViewSync as _, DbViewSyncBytes, Proposal}; +use firewood::db::{Db, DbConfig, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -use firewood::v2::api::{Db as _, HashKey, KeyValuePairIter}; +use firewood::v2::api::{ArcDynDbView, Db as _, DbView, HashKey, KeyValuePairIter, Proposal as _}; use metrics::counter; pub use crate::value::*; @@ -72,7 +72,7 @@ pub struct DatabaseHandle<'p> { proposals: RwLock>>, /// A single cached view to improve performance of reads while committing - cached_view: Mutex)>>, + cached_view: Mutex>, /// The database db: Db, @@ -150,10 +150,7 @@ fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &[u8]) -> Result { counter!("firewood.ffi.cached_view.hit").increment(1); - view.val_sync_bytes(key) + view.val(key) } // If what was there didn't match the requested root, we need a new view, so we // update the cache _ => { counter!("firewood.ffi.cached_view.miss").increment(1); let rev = view_sync_from_root(db, root)?; - let result = rev.val_sync_bytes(key); + let result = rev.val(key); *cached_view = Some((requested_root.clone(), rev)); result } @@ -273,12 +267,9 @@ fn get_from_root( Ok(value.into()) } -fn view_sync_from_root( - db: &DatabaseHandle<'_>, - root: &[u8], -) -> Result, String> { +fn view_sync_from_root(db: &DatabaseHandle<'_>, root: &[u8]) -> Result { let rev = db - .view_sync(HashKey::try_from(root).map_err(|e| e.to_string())?) + .view(HashKey::try_from(root).map_err(|e| e.to_string())?) .map_err(|e| e.to_string())?; Ok(rev) } @@ -333,14 +324,14 @@ fn batch(db: Option<&DatabaseHandle<'_>>, values: &[KeyValuePair<'_>]) -> Result counter!("firewood.ffi.propose_ms").increment(propose_time); let hash_val = proposal - .root_hash_sync() + .root_hash() .map_err(|e| e.to_string())? .ok_or("Proposed revision is empty")? .as_slice() .into(); // Commit the proposal. - proposal.commit_sync().map_err(|e| e.to_string())?; + proposal.commit().map_err(|e| e.to_string())?; // Get the root hash of the database post-commit. let propose_plus_commit_time = start.elapsed().as_millis(); @@ -396,7 +387,7 @@ fn propose_on_db<'p>( let proposal = db.propose(batch).map_err(|e| e.to_string())?; // Get the root hash of the new proposal. - let mut root_hash: Value = match proposal.root_hash_sync().map_err(|e| e.to_string())? { + let mut root_hash: Value = match proposal.root_hash().map_err(|e| e.to_string())? { Some(root) => Value::from(root.as_slice()), None => String::new().into(), }; @@ -462,11 +453,11 @@ fn propose_on_proposal( .write() .expect("failed to acquire write lock on proposals"); let proposal = guard.get(&proposal_id).ok_or("proposal not found")?; - let new_proposal = proposal.propose_sync(batch).map_err(|e| e.to_string())?; + let new_proposal = proposal.propose(batch).map_err(|e| e.to_string())?; drop(guard); // Drop the read lock before we get the write lock. // Get the root hash of the new proposal. - let mut root_hash: Value = match new_proposal.root_hash_sync().map_err(|e| e.to_string())? { + let mut root_hash: Value = match new_proposal.root_hash().map_err(|e| e.to_string())? { Some(root) => Value::from(root.as_slice()), None => String::new().into(), }; @@ -515,11 +506,11 @@ fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), Strin .ok_or("proposal not found")?; // Get the proposal hash and cache the view. We never cache an empty proposal. - let proposal_hash = proposal.root_hash_sync(); + let proposal_hash = proposal.root_hash(); if let Ok(Some(proposal_hash)) = proposal_hash { let mut guard = db.cached_view.lock().expect("cached_view lock is poisoned"); - match db.view_sync(proposal_hash.clone()) { + match db.view(proposal_hash.clone()) { Ok(view) => *guard = Some((proposal_hash, view)), Err(_) => *guard = None, // Clear cache on error } @@ -527,7 +518,7 @@ fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), Strin } // Commit the proposal - let result = proposal.commit_sync().map_err(|e| e.to_string()); + let result = proposal.commit().map_err(|e| e.to_string()); // Clear the cache, which will force readers after this point to find the committed root hash db.clear_cached_view(); diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 433bb4a33578..6977e822d285 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -31,10 +31,7 @@ hex.workspace = true metrics.workspace = true sha2.workspace = true thiserror.workspace = true -tokio.workspace = true # Regular dependencies -async-trait = "0.1.88" -futures = "0.3.31" typed-builder = "0.21.0" [features] @@ -48,7 +45,7 @@ ethhash = ["firewood-storage/ethhash"] [dev-dependencies] # Workspace dependencies clap = { workspace = true, features = ['derive'] } -criterion = { workspace = true, features = ["async_tokio"] } +criterion.workspace = true env_logger.workspace = true ethereum-types.workspace = true firewood-storage = { workspace = true, features = ["test_utils"] } @@ -58,13 +55,11 @@ pprof = { workspace = true, features = ["flamegraph"] } rand.workspace = true tempfile.workspace = true test-case.workspace = true -tokio = { workspace = true, features = ["rt", "sync", "macros", "rt-multi-thread"] } # Regular dependencies hash-db = "0.16.0" plain_hasher = "0.2.3" rlp = "0.6.1" sha3 = "0.10.8" -tokio-scoped = "0.2.0" [[bench]] name = "hashops" diff --git a/firewood/benches/hashops.rs b/firewood/benches/hashops.rs index 8afff94e952c..8e9e79a0266e 100644 --- a/firewood/benches/hashops.rs +++ b/firewood/benches/hashops.rs @@ -100,31 +100,30 @@ fn bench_db(criterion: &mut Criterion) { .benchmark_group("Db") .sample_size(30) .bench_function("commit", |b| { - b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter_batched( - || { - let batch_ops: Vec<_> = - repeat_with(|| rng.sample_iter(&Alphanumeric).take(KEY_LEN).collect()) - .map(|key: Vec<_>| BatchOp::Put { - key, - value: vec![b'v'], - }) - .take(N) - .collect(); - batch_ops - }, - |batch_ops| async { - let db_path = std::env::temp_dir(); - let db_path = db_path.join("benchmark_db"); - let cfg = DbConfig::builder(); - - let db = firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()) - .unwrap(); - - db.propose(batch_ops).unwrap().commit().unwrap(); - }, - BatchSize::SmallInput, - ); + b.iter_batched( + || { + let batch_ops: Vec<_> = + repeat_with(|| rng.sample_iter(&Alphanumeric).take(KEY_LEN).collect()) + .map(|key: Vec<_>| BatchOp::Put { + key, + value: vec![b'v'], + }) + .take(N) + .collect(); + batch_ops + }, + |batch_ops| { + let db_path = std::env::temp_dir(); + let db_path = db_path.join("benchmark_db"); + let cfg = DbConfig::builder(); + + let db = + firewood::db::Db::new(db_path, cfg.clone().truncate(true).build()).unwrap(); + + db.propose(batch_ops).unwrap().commit().unwrap(); + }, + BatchSize::SmallInput, + ); }); } diff --git a/firewood/examples/insert.rs b/firewood/examples/insert.rs index 823e236cefc6..b2f252bdd674 100644 --- a/firewood/examples/insert.rs +++ b/firewood/examples/insert.rs @@ -50,8 +50,7 @@ fn string_to_range(input: &str) -> Result, Box Result<(), Box> { +fn main() -> Result<(), Box> { let args = Args::parse(); let mgrcfg = RevisionManagerConfig::builder() @@ -92,7 +91,7 @@ async fn main() -> Result<(), Box> { #[expect(clippy::unwrap_used)] let proposal = db.propose(batch.clone()).unwrap(); proposal.commit()?; - verify_keys(&db, verify).await?; + verify_keys(&db, verify)?; } let duration = start.elapsed(); @@ -126,7 +125,7 @@ fn get_keys_to_verify<'a, K: KeyType + 'a, V: ValueType + 'a>( } } -async fn verify_keys( +fn verify_keys( db: &impl firewood::v2::api::Db, verify: HashMap<&[u8], &[u8]>, ) -> Result<(), firewood::v2::api::Error> { @@ -134,7 +133,7 @@ async fn verify_keys( let hash = db.root_hash()?.expect("root hash should exist"); let revision = db.revision(hash)?; for (key, value) in verify { - assert_eq!(Some(value), revision.val(key).await?.as_deref()); + assert_eq!(Some(value), revision.val(key)?.as_deref()); } } Ok(()) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 14a17e5cfc50..9bfa99e78b33 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -6,19 +6,18 @@ reason = "Found 12 occurrences after enabling the lint." )] +use crate::iter::MerkleKeyValueIter; use crate::merkle::{Merkle, Value}; -use crate::stream::MerkleKeyValueStream; pub use crate::v2::api::BatchOp; use crate::v2::api::{ - self, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair, KeyValuePairIter, - OptionalHashKeyExt, + self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair, + KeyValuePairIter, OptionalHashKeyExt, }; use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; -use async_trait::async_trait; use firewood_storage::{ CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, - ImmutableProposal, NodeStore, + ImmutableProposal, NodeStore, Parentable, ReadableStorage, TrieReader, }; use metrics::{counter, describe_counter}; use std::io::Write; @@ -36,8 +35,6 @@ pub enum DbError { FileIo(#[from] FileIoError), } -type HistoricalRev = NodeStore; - /// Metrics for the database. /// TODO: Add more metrics pub struct DbMetrics { @@ -50,92 +47,46 @@ impl std::fmt::Debug for DbMetrics { } } -/// A synchronous view of the database. -pub trait DbViewSync { - /// find a value synchronously - fn val_sync(&self, key: K) -> Result, DbError>; -} - -/// A synchronous view of the database with raw byte keys (object-safe version). -pub trait DbViewSyncBytes: std::fmt::Debug + Send + Sync { - /// find a value synchronously using raw bytes - fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError>; -} - -// Provide blanket implementation for DbViewSync using DbViewSyncBytes -impl DbViewSync for T { - fn val_sync(&self, key: K) -> Result, DbError> { - self.val_sync_bytes(key.as_ref()) - } -} - -impl DbViewSyncBytes for Arc { - fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { - let merkle = Merkle::from(self); - let value = merkle.get_value(key)?; - Ok(value) - } -} - -impl DbViewSyncBytes for Proposal<'_> { - fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { - let merkle = Merkle::from(self.nodestore.clone()); - let value = merkle.get_value(key)?; - Ok(value) - } -} - -impl DbViewSyncBytes for Arc, FileBacked>> { - fn val_sync_bytes(&self, key: &[u8]) -> Result, DbError> { - let merkle = Merkle::from(self.clone()); - let value = merkle.get_value(key)?; - Ok(value) - } -} - -#[async_trait] -impl api::DbView for HistoricalRev { - type Stream<'view> - = MerkleKeyValueStream<'view, Self> +impl api::DbView for NodeStore +where + NodeStore: TrieReader, +{ + type Iter<'view> + = MerkleKeyValueIter<'view, Self> where Self: 'view; - async fn root_hash(&self) -> Result, api::Error> { - Ok(HashedNodeReader::root_hash(self)) + fn root_hash(&self) -> Result, api::Error> { + Ok(HashedNodeReader::root_hash(self).or_default_root_hash()) } - async fn val(&self, key: K) -> Result, api::Error> { + fn val(&self, key: K) -> Result, api::Error> { let merkle = Merkle::from(self); Ok(merkle.get_value(key.as_ref())?) } - async fn single_key_proof(&self, key: K) -> Result { + fn single_key_proof(&self, key: K) -> Result { let merkle = Merkle::from(self); merkle.prove(key.as_ref()).map_err(api::Error::from) } - async fn range_proof( + fn range_proof( &self, first_key: Option, last_key: Option, limit: Option, ) -> Result { - Merkle::from(self) - .range_proof( - first_key.as_ref().map(AsRef::as_ref), - last_key.as_ref().map(AsRef::as_ref), - limit, - ) - .await + Merkle::from(self).range_proof( + first_key.as_ref().map(AsRef::as_ref), + last_key.as_ref().map(AsRef::as_ref), + limit, + ) } - fn iter_option( - &self, - first_key: Option, - ) -> Result, api::Error> { + fn iter_option(&self, first_key: Option) -> Result, api::Error> { match first_key { - Some(key) => Ok(MerkleKeyValueStream::from_key(self, key)), - None => Ok(MerkleKeyValueStream::from(self)), + Some(key) => Ok(MerkleKeyValueIter::from_key(self, key)), + None => Ok(MerkleKeyValueIter::from(self)), } } } @@ -243,9 +194,8 @@ impl Db { } /// Synchronously get a view, either committed or proposed - pub fn view_sync(&self, root_hash: HashKey) -> Result, api::Error> { - let nodestore = self.manager.view(root_hash)?; - Ok(nodestore) + pub fn view(&self, root_hash: HashKey) -> Result { + self.manager.view(root_hash).map_err(Into::into) } /// Dump the Trie of the latest revision. @@ -274,54 +224,35 @@ pub struct Proposal<'db> { db: &'db Db, } -impl Proposal<'_> { - /// Synchronously get the root hash of the latest revision. - pub fn root_hash_sync(&self) -> Result, api::Error> { - Ok(self.nodestore.root_hash().or_default_root_hash()) - } -} - -#[async_trait] impl api::DbView for Proposal<'_> { - type Stream<'view> - = MerkleKeyValueStream<'view, NodeStore, FileBacked>> + type Iter<'view> + = MerkleKeyValueIter<'view, NodeStore, FileBacked>> where Self: 'view; - async fn root_hash(&self) -> Result, api::Error> { - self.root_hash_sync() + fn root_hash(&self) -> Result, api::Error> { + api::DbView::root_hash(&*self.nodestore) } - async fn val(&self, key: K) -> Result, api::Error> { - let merkle = Merkle::from(self.nodestore.clone()); - merkle.get_value(key.as_ref()).map_err(api::Error::from) + fn val(&self, key: K) -> Result, api::Error> { + api::DbView::val(&*self.nodestore, key) } - async fn single_key_proof(&self, key: K) -> Result { - let merkle = Merkle::from(self.nodestore.clone()); - merkle.prove(key.as_ref()).map_err(api::Error::from) + fn single_key_proof(&self, key: K) -> Result { + api::DbView::single_key_proof(&*self.nodestore, key) } - async fn range_proof( + fn range_proof( &self, first_key: Option, last_key: Option, limit: Option, ) -> Result { - Merkle::from(&self.nodestore) - .range_proof( - first_key.as_ref().map(AsRef::as_ref), - last_key.as_ref().map(AsRef::as_ref), - limit, - ) - .await + api::DbView::range_proof(&*self.nodestore, first_key, last_key, limit) } - fn iter_option( - &self, - _first_key: Option, - ) -> Result, api::Error> { - todo!() + fn iter_option(&self, first_key: Option) -> Result, api::Error> { + api::DbView::iter_option(&*self.nodestore, first_key) } } @@ -342,19 +273,6 @@ impl<'db> api::Proposal for Proposal<'db> { } impl Proposal<'_> { - /// Commit a proposal synchronously - pub fn commit_sync(self) -> Result<(), api::Error> { - Ok(self.db.manager.commit(self.nodestore.clone())?) - } - - /// Create a new proposal from the current one synchronously - pub fn propose_sync( - &self, - batch: impl IntoIterator, - ) -> Result { - self.create_proposal(batch) - } - #[crate::metrics("firewood.proposal.create", "database proposal creation")] fn create_proposal( &self, @@ -399,14 +317,13 @@ mod test { use std::path::PathBuf; use firewood_storage::{CheckOpt, CheckerError}; - use tokio::sync::mpsc::{Receiver, Sender}; use crate::db::{Db, Proposal}; use crate::v2::api::{Db as _, DbView as _, KeyValuePairIter, Proposal as _}; use super::{BatchOp, DbConfig}; - /// A chunk of an iterator, provided by [`IterExt::async_chunk_fold`] to the folding + /// A chunk of an iterator, provided by [`IterExt::chunk_fold`] to the folding /// function. type Chunk<'chunk, 'base, T> = &'chunk mut Take<&'base mut Peekable>; @@ -414,7 +331,7 @@ mod test { /// Asynchronously folds the iterator with chunks of a specified size. The last /// chunk may be smaller than the specified size. /// - /// The folding function is an async closure that takes an accumulator and a + /// The folding function is a closure that takes an accumulator and a /// chunk of the underlying iterator, and returns a new accumulator. /// /// # Panics @@ -423,17 +340,17 @@ mod test { /// /// If the folding function panics, the iterator will be dropped (because this /// method consumes `self`). - async fn async_chunk_fold(self, chunk_size: NonZeroUsize, init: B, mut f: F) -> B + fn chunk_fold(self, chunk_size: NonZeroUsize, init: B, mut f: F) -> B where Self: Sized, - F: for<'a, 'b> AsyncFnMut(B, Chunk<'a, 'b, Self>) -> B, + F: for<'a, 'b> FnMut(B, Chunk<'a, 'b, Self>) -> B, { let chunk_size = chunk_size.get(); let mut iter = self.peekable(); let mut acc = init; while iter.peek().is_some() { let mut chunk = iter.by_ref().take(chunk_size); - acc = f(acc, chunk.by_ref()).await; + acc = f(acc, chunk.by_ref()); assert!(chunk.next().is_none(), "entire chunk was not consumed"); } acc @@ -442,17 +359,17 @@ mod test { impl IterExt for T {} - #[tokio::test] - async fn test_proposal_reads() { + #[test] + fn test_proposal_reads() { let db = testdb(); let batch = vec![BatchOp::Put { key: b"k", value: b"v", }]; let proposal = db.propose(batch).unwrap(); - assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(&*proposal.val(b"k").unwrap().unwrap(), b"v"); - assert_eq!(proposal.val(b"notfound").await.unwrap(), None); + assert_eq!(proposal.val(b"notfound").unwrap(), None); proposal.commit().unwrap(); let batch = vec![BatchOp::Put { @@ -460,15 +377,15 @@ mod test { value: b"v2", }]; let proposal = db.propose(batch).unwrap(); - assert_eq!(&*proposal.val(b"k").await.unwrap().unwrap(), b"v2"); + assert_eq!(&*proposal.val(b"k").unwrap().unwrap(), b"v2"); let committed = db.root_hash().unwrap().unwrap(); let historical = db.revision(committed).unwrap(); - assert_eq!(&*historical.val(b"k").await.unwrap().unwrap(), b"v"); + assert_eq!(&*historical.val(b"k").unwrap().unwrap(), b"v"); } - #[tokio::test] - async fn reopen_test() { + #[test] + fn reopen_test() { let db = testdb(); let initial_root = db.root_hash().unwrap(); let batch = vec![ @@ -489,45 +406,45 @@ mod test { println!("{:?}", db.root_hash().unwrap().unwrap()); let committed = db.root_hash().unwrap().unwrap(); let historical = db.revision(committed).unwrap(); - assert_eq!(&*historical.val(b"a").await.unwrap().unwrap(), b"1"); + assert_eq!(&*historical.val(b"a").unwrap().unwrap(), b"1"); let db = db.replace(); println!("{:?}", db.root_hash().unwrap()); assert!(db.root_hash().unwrap() == initial_root); } - #[tokio::test] + #[test] // test that dropping a proposal removes it from the list of known proposals // /-> P1 - will get committed // R1 --> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known - async fn test_proposal_scope_historic() { + fn test_proposal_scope_historic() { let db = testdb(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", }]; let proposal1 = db.propose(batch1).unwrap(); - assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); + assert_eq!(&*proposal1.val(b"k1").unwrap().unwrap(), b"v1"); let batch2 = vec![BatchOp::Put { key: b"k2", value: b"v2", }]; let proposal2 = db.propose(batch2).unwrap(); - assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); + assert_eq!(&*proposal2.val(b"k2").unwrap().unwrap(), b"v2"); let batch3 = vec![BatchOp::Put { key: b"k3", value: b"v3", }]; let proposal3 = db.propose(batch3).unwrap(); - assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + assert_eq!(&*proposal3.val(b"k3").unwrap().unwrap(), b"v3"); // the proposal is dropped here, but the underlying // nodestore is still accessible because it's referenced by the revision manager // The third proposal remains referenced - let p2hash = proposal2.root_hash().await.unwrap().unwrap(); + let p2hash = proposal2.root_hash().unwrap().unwrap(); assert!(db.all_hashes().unwrap().contains(&p2hash)); drop(proposal2); @@ -536,50 +453,50 @@ mod test { // Ensure we committed the first proposal's data let committed = db.root_hash().unwrap().unwrap(); let historical = db.revision(committed).unwrap(); - assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); + assert_eq!(&*historical.val(b"k1").unwrap().unwrap(), b"v1"); // the second proposal shouldn't be available to commit anymore assert!(!db.all_hashes().unwrap().contains(&p2hash)); // the third proposal should still be contained within the all_hashes list // would be deleted if another proposal was committed and proposal3 was dropped here - let hash3 = proposal3.root_hash().await.unwrap().unwrap(); + let hash3 = proposal3.root_hash().unwrap().unwrap(); assert!(db.manager.all_hashes().contains(&hash3)); } - #[tokio::test] + #[test] // test that dropping a proposal removes it from the list of known proposals // R1 - base revision // \-> P1 - will get committed // \-> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known - async fn test_proposal_scope_orphan() { + fn test_proposal_scope_orphan() { let db = testdb(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", }]; let proposal1 = db.propose(batch1).unwrap(); - assert_eq!(&*proposal1.val(b"k1").await.unwrap().unwrap(), b"v1"); + assert_eq!(&*proposal1.val(b"k1").unwrap().unwrap(), b"v1"); let batch2 = vec![BatchOp::Put { key: b"k2", value: b"v2", }]; let proposal2 = proposal1.propose(batch2).unwrap(); - assert_eq!(&*proposal2.val(b"k2").await.unwrap().unwrap(), b"v2"); + assert_eq!(&*proposal2.val(b"k2").unwrap().unwrap(), b"v2"); let batch3 = vec![BatchOp::Put { key: b"k3", value: b"v3", }]; let proposal3 = proposal2.propose(batch3).unwrap(); - assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + assert_eq!(&*proposal3.val(b"k3").unwrap().unwrap(), b"v3"); // the proposal is dropped here, but the underlying // nodestore is still accessible because it's referenced by the revision manager // The third proposal remains referenced - let p2hash = proposal2.root_hash().await.unwrap().unwrap(); + let p2hash = proposal2.root_hash().unwrap().unwrap(); assert!(db.all_hashes().unwrap().contains(&p2hash)); drop(proposal2); @@ -588,23 +505,23 @@ mod test { // Ensure we committed the first proposal's data let committed = db.root_hash().unwrap().unwrap(); let historical = db.revision(committed).unwrap(); - assert_eq!(&*historical.val(b"k1").await.unwrap().unwrap(), b"v1"); + assert_eq!(&*historical.val(b"k1").unwrap().unwrap(), b"v1"); // the second proposal shouldn't be available to commit anymore assert!(!db.all_hashes().unwrap().contains(&p2hash)); // the third proposal should still be contained within the all_hashes list - let hash3 = proposal3.root_hash().await.unwrap().unwrap(); + let hash3 = proposal3.root_hash().unwrap().unwrap(); assert!(db.manager.all_hashes().contains(&hash3)); // moreover, the data from the second and third proposals should still be available // through proposal3 - assert_eq!(&*proposal3.val(b"k2").await.unwrap().unwrap(), b"v2"); - assert_eq!(&*proposal3.val(b"k3").await.unwrap().unwrap(), b"v3"); + assert_eq!(&*proposal3.val(b"k2").unwrap().unwrap(), b"v2"); + assert_eq!(&*proposal3.val(b"k3").unwrap().unwrap(), b"v3"); } - #[tokio::test] - async fn test_view_sync() { + #[test] + fn test_view_sync() { let db = testdb(); // Create and commit some data to get a historical revision @@ -613,7 +530,7 @@ mod test { value: b"historical_value", }]; let proposal = db.propose(batch).unwrap(); - let historical_hash = proposal.root_hash().await.unwrap().unwrap(); + let historical_hash = proposal.root_hash().unwrap().unwrap(); proposal.commit().unwrap(); // Create a new proposal (uncommitted) @@ -622,22 +539,16 @@ mod test { value: b"proposal_value", }]; let proposal = db.propose(batch).unwrap(); - let proposal_hash = proposal.root_hash().await.unwrap().unwrap(); + let proposal_hash = proposal.root_hash().unwrap().unwrap(); // Test that view_sync can find the historical revision - let historical_view = db.view_sync(historical_hash).unwrap(); - let value = historical_view - .val_sync_bytes(b"historical_key") - .unwrap() - .unwrap(); + let historical_view = db.view(historical_hash).unwrap(); + let value = historical_view.val(b"historical_key").unwrap().unwrap(); assert_eq!(&*value, b"historical_value"); // Test that view_sync can find the proposal - let proposal_view = db.view_sync(proposal_hash).unwrap(); - let value = proposal_view - .val_sync_bytes(b"proposal_key") - .unwrap() - .unwrap(); + let proposal_view = db.view(proposal_hash).unwrap(); + let value = proposal_view.val(b"proposal_key").unwrap().unwrap(); assert_eq!(&*value, b"proposal_value"); } @@ -645,8 +556,8 @@ mod test { /// /// Test creates two batches and proposes them, and verifies that the values are in the correct proposal. /// It then commits them one by one, and verifies the latest committed version is correct. - #[tokio::test] - async fn test_propose_on_proposal() { + #[test] + fn test_propose_on_proposal() { // number of keys and values to create for this test const N: usize = 20; @@ -674,23 +585,23 @@ mod test { // first half of the keys should be in both proposals for (k, v) in kviter.by_ref().take(N / 2) { - assert_eq!(&proposal1.val(k).await.unwrap().unwrap(), v); - assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + assert_eq!(&proposal1.val(k).unwrap().unwrap(), v); + assert_eq!(&proposal2.val(k).unwrap().unwrap(), v); } // remaining keys should only be in the second proposal for (k, v) in kviter { // second half of keys are in the second proposal - assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + assert_eq!(&proposal2.val(k).unwrap().unwrap(), v); // but not in the first - assert_eq!(proposal1.val(k).await.unwrap(), None); + assert_eq!(proposal1.val(k).unwrap(), None); } proposal1.commit().unwrap(); // all keys are still in the second proposal (first is no longer accessible) for (k, v) in keys.iter().zip(vals.iter()) { - assert_eq!(&proposal2.val(k).await.unwrap().unwrap(), v); + assert_eq!(&proposal2.val(k).unwrap().unwrap(), v); } // commit the second proposal @@ -701,12 +612,12 @@ mod test { let revision = db.revision(committed).unwrap(); for (k, v) in keys.into_iter().zip(vals.into_iter()) { - assert_eq!(revision.val(k).await.unwrap().unwrap(), v); + assert_eq!(revision.val(k).unwrap().unwrap(), v); } } - #[tokio::test] - async fn fuzz_checker() { + #[test] + fn fuzz_checker() { let _ = env_logger::Builder::new().is_test(true).try_init(); let rng = firewood_storage::SeededRng::from_env_or_random(); @@ -753,8 +664,8 @@ mod test { } } - #[tokio::test] - async fn test_deep_propose() { + #[test] + fn test_deep_propose() { const NUM_KEYS: NonZeroUsize = const { NonZeroUsize::new(2).unwrap() }; const NUM_PROPOSALS: usize = 100; @@ -764,31 +675,22 @@ mod test { .map(|i| (format!("key{i}"), format!("value{i}"))) .collect::>(); - let proposals = ops - .iter() - .async_chunk_fold( - NUM_KEYS, - Vec::>::with_capacity(NUM_PROPOSALS), - async |mut proposals, ops| { - let proposal = if let Some(parent) = proposals.last() { - parent.propose(ops).unwrap() - } else { - db.propose(ops).unwrap() - }; - - proposals.push(proposal); - proposals - }, - ) - .await; - - let last_proposal_root_hash = proposals - .last() - .unwrap() - .root_hash() - .await - .unwrap() - .unwrap(); + let proposals = ops.iter().chunk_fold( + NUM_KEYS, + Vec::>::with_capacity(NUM_PROPOSALS), + |mut proposals, ops| { + let proposal = if let Some(parent) = proposals.last() { + parent.propose(ops).unwrap() + } else { + db.propose(ops).unwrap() + }; + + proposals.push(proposal); + proposals + }, + ); + + let last_proposal_root_hash = proposals.last().unwrap().root_hash().unwrap().unwrap(); // commit the proposals for proposal in proposals { @@ -804,7 +706,7 @@ mod test { // check that all the keys and values are still present for (k, v) in &ops { - let found = committed.val(k).await.unwrap(); + let found = committed.val(k).unwrap(); assert_eq!( found.as_deref(), Some(v.as_bytes()), @@ -814,8 +716,8 @@ mod test { } /// Test that reading from a proposal during commit works as expected - #[tokio::test(flavor = "multi_thread")] - async fn test_read_during_commit() { + #[test] + fn test_read_during_commit() { use crate::db::Proposal; const CHANNEL_CAPACITY: usize = 8; @@ -823,21 +725,21 @@ mod test { let testdb = testdb(); let db = &testdb.db; - let (tx, mut rx): (Sender>, Receiver>) = - tokio::sync::mpsc::channel(CHANNEL_CAPACITY); - let (result_tx, mut result_rx) = tokio::sync::mpsc::channel(CHANNEL_CAPACITY); + let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); + let (result_tx, result_rx) = std::sync::mpsc::sync_channel(CHANNEL_CAPACITY); - tokio_scoped::scope(|scope| { + // scope will block until all scope-spawned threads finish + std::thread::scope(|scope| { // Commit task - scope.spawn(async move { - while let Some(proposal) = rx.recv().await { + scope.spawn(move || { + while let Ok(proposal) = rx.recv() { let result = proposal.commit(); // send result back to the main thread, both for synchronization and stopping the // test on error - result_tx.send(result).await.unwrap(); + result_tx.send(result).unwrap(); } }); - scope.spawn(async move { + scope.spawn(move || { // Proposal creation for id in 0u32..5000 { // insert a key of length 32 and a value of length 8, @@ -847,15 +749,15 @@ mod test { value: [id as u8; 8], }]; let proposal = db.propose(batch).unwrap(); - let last_hash = proposal.root_hash().await.unwrap().unwrap(); - let view = db.view_sync(last_hash).unwrap(); + let last_hash = proposal.root_hash().unwrap().unwrap(); + let view = db.view(last_hash).unwrap(); - tx.send(proposal).await.unwrap(); + tx.send(proposal).unwrap(); let key = [id as u8; 32]; - let value = view.val_sync_bytes(&key).unwrap().unwrap(); + let value = view.val(&key).unwrap().unwrap(); assert_eq!(&*value, &[id as u8; 8]); - result_rx.recv().await.unwrap().unwrap(); + result_rx.recv().unwrap().unwrap(); } // close the channel, which will cause the commit task to exit drop(tx); diff --git a/firewood/src/stream.rs b/firewood/src/iter.rs similarity index 67% rename from firewood/src/stream.rs rename to firewood/src/iter.rs index bbe224c10089..2a9f82b77668 100644 --- a/firewood/src/stream.rs +++ b/firewood/src/iter.rs @@ -1,22 +1,17 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::used_underscore_binding, - reason = "Found 3 occurrences after enabling the lint." -)] +mod try_extend; +pub(crate) use self::try_extend::TryExtend; use crate::merkle::{Key, Value}; use crate::v2::api; use firewood_storage::{ BranchNode, Child, FileIoError, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader, }; -use futures::stream::FusedStream; -use futures::{Stream, StreamExt}; use std::cmp::Ordering; -use std::iter::once; -use std::task::Poll; +use std::iter::FusedIterator; /// Represents an ongoing iteration over a node and its children. enum IterationNode { @@ -53,7 +48,7 @@ impl std::fmt::Debug for IterationNode { } #[derive(Debug)] -enum NodeStreamState { +enum NodeIterState { /// The iterator state is lazily initialized when `poll_next` is called /// for the first time. The iteration start key is stored here. StartFromKey(Key), @@ -68,153 +63,130 @@ enum NodeStreamState { } #[derive(Debug)] -/// A stream of nodes in order starting from a specific point in the trie. -pub struct MerkleNodeStream<'a, T> { - state: NodeStreamState, +/// An iterator of nodes in order starting from a specific point in the trie. +pub struct MerkleNodeIter<'a, T> { + state: NodeIterState, merkle: &'a T, } -impl From for NodeStreamState { +impl From for NodeIterState { fn from(key: Key) -> Self { Self::StartFromKey(key) } } -impl FusedStream for MerkleNodeStream<'_, T> { - fn is_terminated(&self) -> bool { - // The top of `iter_stack` is the next node to return. - // If `iter_stack` is empty, there are no more nodes to visit. - matches!(&self.state, NodeStreamState::Iterating { iter_stack } if iter_stack.is_empty()) - } -} - -impl<'a, T: TrieReader> MerkleNodeStream<'a, T> { +impl<'a, T: TrieReader> MerkleNodeIter<'a, T> { /// Returns a new iterator that will iterate over all the nodes in `merkle` /// with keys greater than or equal to `key`. pub(super) fn new(merkle: &'a T, key: Key) -> Self { Self { - state: NodeStreamState::from(key), + state: NodeIterState::from(key), merkle, } } +} - /// Internal function that handles the core iteration logic shared between Iterator and Stream implementations. - /// Returns None when iteration is complete, or Some(Result) with either a node or an error. - fn next_internal(&mut self) -> Option> { - let Self { state, merkle } = &mut *self; +impl Iterator for MerkleNodeIter<'_, T> { + type Item = Result<(Key, SharedNode), FileIoError>; - match state { - NodeStreamState::StartFromKey(key) => { - match get_iterator_intial_state(*merkle, key) { - Ok(state) => self.state = state, - Err(e) => return Some(Err(e)), + fn next(&mut self) -> Option { + 'outer: loop { + match &mut self.state { + NodeIterState::StartFromKey(key) => { + match get_iterator_intial_state(self.merkle, key) { + Ok(state) => self.state = state, + Err(e) => return Some(Err(e)), + } } - self.next_internal() - } - NodeStreamState::Iterating { iter_stack } => { - while let Some(mut iter_node) = iter_stack.pop() { - match iter_node { - IterationNode::Unvisited { key, node } => { - match &*node { - Node::Leaf(_) => {} - Node::Branch(branch) => { - // `node` is a branch node. Visit its children next. - iter_stack.push(IterationNode::Visited { - key: key.clone(), - children_iter: Box::new(as_enumerated_children_iter( - branch, - )), - }); + NodeIterState::Iterating { iter_stack } => { + while let Some(mut iter_node) = iter_stack.pop() { + match iter_node { + IterationNode::Unvisited { key, node } => { + match &*node { + Node::Leaf(_) => {} + Node::Branch(branch) => { + // `node` is a branch node. Visit its children next. + iter_stack.push(IterationNode::Visited { + key: key.clone(), + children_iter: Box::new(as_enumerated_children_iter( + branch, + )), + }); + } } - } - let key = key_from_nibble_iter(key.iter().copied()); - return Some(Ok((key, node))); - } - IterationNode::Visited { - ref key, - ref mut children_iter, - } => { - // We returned `node` already. Visit its next child. - let Some((pos, child)) = children_iter.next() else { - // We visited all this node's descendants. Go back to its parent. - continue; - }; + let key = key_from_nibble_iter(key.iter().copied()); + return Some(Ok((key, node))); + } + IterationNode::Visited { + ref key, + ref mut children_iter, + } => { + // We returned `node` already. Visit its next child. + let Some((pos, child)) = children_iter.next() else { + // We visited all this node's descendants. Go back to its parent. + continue; + }; - let child = match child { - Child::AddressWithHash(addr, _) => match merkle.read_node(addr) { - Ok(node) => node, - Err(e) => return Some(Err(e)), - }, - Child::Node(node) => node.clone().into(), - Child::MaybePersisted(maybe_persisted, _) => { - // For MaybePersisted, we need to get the node - match maybe_persisted.as_shared_node(merkle) { - Ok(node) => node, - Err(e) => return Some(Err(e)), + let child = match child { + Child::AddressWithHash(addr, _) => { + match self.merkle.read_node(addr) { + Ok(node) => node, + Err(e) => return Some(Err(e)), + } } - } - }; + Child::Node(node) => node.clone().into(), + Child::MaybePersisted(maybe_persisted, _) => { + // For MaybePersisted, we need to get the node + match maybe_persisted.as_shared_node(self.merkle) { + Ok(node) => node, + Err(e) => return Some(Err(e)), + } + } + }; + + let child_partial_path = child.partial_path().iter().copied(); + + // The child's key is its parent's key, followed by the child's index, + // followed by the child's partial path (if any). + let child_key: Key = key + .iter() + .copied() + .chain(Some(pos)) + .chain(child_partial_path) + .collect(); - let child_partial_path = child.partial_path().iter().copied(); - - // The child's key is its parent's key, followed by the child's index, - // followed by the child's partial path (if any). - let child_key: Key = key - .iter() - .copied() - .chain(once(pos)) - .chain(child_partial_path) - .collect(); - - // There may be more children of this node to visit. - // Visit it again after visiting its `child`. - iter_stack.push(iter_node); - - iter_stack.push(IterationNode::Unvisited { - key: child_key, - node: child, - }); - return self.next_internal(); + // There may be more children of this node to visit. + // Visit it again after visiting its `child`. + iter_stack.push(iter_node); + + iter_stack.push(IterationNode::Unvisited { + key: child_key, + node: child, + }); + + continue 'outer; + } } } + + return None; } - None } } } } -impl Stream for MerkleNodeStream<'_, T> { - type Item = Result<(Key, SharedNode), FileIoError>; - - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll> { - match self.next_internal() { - Some(result) => Poll::Ready(Some(result)), - None => Poll::Ready(None), - } - } -} - -impl Iterator for MerkleNodeStream<'_, T> { - type Item = Result<(Key, SharedNode), FileIoError>; - - fn next(&mut self) -> Option { - self.next_internal() - } -} +impl FusedIterator for MerkleNodeIter<'_, T> {} /// Returns the initial state for an iterator over the given `merkle` which starts at `key`. fn get_iterator_intial_state( merkle: &T, key: &[u8], -) -> Result { +) -> Result { let Some(root) = merkle.root_node() else { // This merkle is empty. - return Ok(NodeStreamState::Iterating { iter_stack: vec![] }); + return Ok(NodeIterState::Iterating { iter_stack: vec![] }); }; let mut node = root; @@ -240,7 +212,7 @@ fn get_iterator_intial_state( Ordering::Less => { // `node` is before `key`. It shouldn't be visited // and neither should its descendants. - return Ok(NodeStreamState::Iterating { iter_stack }); + return Ok(NodeIterState::Iterating { iter_stack }); } Ordering::Greater => { // `node` is after `key`. Visit it first. @@ -248,7 +220,7 @@ fn get_iterator_intial_state( key: Box::from(matched_key_nibbles), node, }); - return Ok(NodeStreamState::Iterating { iter_stack }); + return Ok(NodeIterState::Iterating { iter_stack }); } Ordering::Equal => match &*node { Node::Leaf(_) => { @@ -256,7 +228,7 @@ fn get_iterator_intial_state( key: matched_key_nibbles.clone().into_boxed_slice(), node, }); - return Ok(NodeStreamState::Iterating { iter_stack }); + return Ok(NodeIterState::Iterating { iter_stack }); } Node::Branch(branch) => { let Some(next_unmatched_key_nibble) = unmatched_key_nibbles.next() else { @@ -266,7 +238,7 @@ fn get_iterator_intial_state( node, }); - return Ok(NodeStreamState::Iterating { iter_stack }); + return Ok(NodeIterState::Iterating { iter_stack }); }; // There is no child at `next_unmatched_key_nibble`. @@ -283,7 +255,7 @@ fn get_iterator_intial_state( #[expect(clippy::indexing_slicing)] let child = &branch.children[next_unmatched_key_nibble as usize]; node = match child { - None => return Ok(NodeStreamState::Iterating { iter_stack }), + None => return Ok(NodeIterState::Iterating { iter_stack }), Some(Child::AddressWithHash(addr, _)) => merkle.read_node(*addr)?, Some(Child::Node(node)) => (*node).clone().into(), // TODO can we avoid cloning this? Some(Child::MaybePersisted(maybe_persisted, _)) => { @@ -300,103 +272,56 @@ fn get_iterator_intial_state( } #[derive(Debug)] -enum MerkleKeyValueStreamState<'a, T> { - /// The iterator state is lazily initialized when `poll_next` is called - /// for the first time. The iteration start key is stored here. - _Uninitialized(Key), - /// The iterator works by iterating over the nodes in the merkle trie - /// and returning the key-value pairs for nodes that have values. - Initialized { node_iter: MerkleNodeStream<'a, T> }, -} - -impl> From for MerkleKeyValueStreamState<'_, T> { - fn from(key: K) -> Self { - Self::_Uninitialized(key.as_ref().into()) - } -} - -impl MerkleKeyValueStreamState<'_, T> { - /// Returns a new iterator that will iterate over all the key-value pairs in `merkle`. - fn _new() -> Self { - Self::_Uninitialized(Box::new([])) - } +/// An iterator of key-value pairs in order starting from a specific point in the trie. +pub struct MerkleKeyValueIter<'a, T> { + iter: MerkleNodeIter<'a, T>, } -#[derive(Debug)] -/// A stream of key-value pairs in order starting from a specific point in the trie. -pub struct MerkleKeyValueStream<'a, T> { - state: MerkleKeyValueStreamState<'a, T>, - merkle: &'a T, -} - -impl<'a, T: TrieReader> From<&'a T> for MerkleKeyValueStream<'a, T> { +impl<'a, T: TrieReader> From<&'a T> for MerkleKeyValueIter<'a, T> { fn from(merkle: &'a T) -> Self { Self { - state: MerkleKeyValueStreamState::_new(), - merkle, + iter: MerkleNodeIter::new(merkle, Box::new([])), } } } -impl FusedStream for MerkleKeyValueStream<'_, T> { - fn is_terminated(&self) -> bool { - matches!(&self.state, MerkleKeyValueStreamState::Initialized { node_iter } if node_iter.is_terminated()) - } -} - -impl<'a, T: TrieReader> MerkleKeyValueStream<'a, T> { - /// Construct a [`MerkleKeyValueStream`] that will iterate over all the key-value pairs in `merkle` +impl<'a, T: TrieReader> MerkleKeyValueIter<'a, T> { + /// Construct a [`MerkleKeyValueIter`] that will iterate over all the key-value pairs in `merkle` /// starting from a particular key pub fn from_key>(merkle: &'a T, key: K) -> Self { Self { - state: MerkleKeyValueStreamState::from(key.as_ref()), - merkle, + iter: MerkleNodeIter::new(merkle, key.as_ref().into()), } } } -impl Stream for MerkleKeyValueStream<'_, T> { +impl Iterator for MerkleKeyValueIter<'_, T> { type Item = Result<(Key, Value), api::Error>; - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll> { - // destructuring is necessary here because we need mutable access to `key_state` - // at the same time as immutable access to `merkle` - let Self { state, merkle } = &mut *self; - - match state { - MerkleKeyValueStreamState::_Uninitialized(key) => { - let iter = MerkleNodeStream::new(*merkle, key.clone()); - self.state = MerkleKeyValueStreamState::Initialized { node_iter: iter }; - self.poll_next(_cx) - } - MerkleKeyValueStreamState::Initialized { node_iter: iter } => { - match iter.poll_next_unpin(_cx) { - Poll::Ready(node) => match node { - Some(Ok((key, node))) => match &*node { - Node::Branch(branch) => { - let Some(value) = branch.value.as_ref() else { - // This node doesn't have a value to return. - // Continue to the next node. - return self.poll_next(_cx); - }; - - Poll::Ready(Some(Ok((key, value.clone())))) - } - Node::Leaf(leaf) => Poll::Ready(Some(Ok((key, leaf.value.clone())))), - }, - Some(Err(e)) => Poll::Ready(Some(Err(e.into()))), - None => Poll::Ready(None), - }, - Poll::Pending => Poll::Pending, - } - } - } + fn next(&mut self) -> Option { + self.iter.find_map(|result| { + result + .map(|(key, node)| { + match &*node { + Node::Branch(branch) => { + let Some(value) = branch.value.as_ref() else { + // This node doesn't have a value to return. + // Continue to the next node. + return None; + }; + Some((key, value.clone())) + } + Node::Leaf(leaf) => Some((key, leaf.value.clone())), + } + }) + .map_err(Into::into) + .transpose() + }) } } +impl FusedIterator for MerkleKeyValueIter<'_, T> {} + #[derive(Debug)] enum PathIteratorState<'a> { Iterating { @@ -637,13 +562,10 @@ fn key_from_nibble_iter>(mut nibbles: Iter) -> Key { #[cfg(test)] #[expect(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { - use std::sync::Arc; - - use firewood_storage::{ImmutableProposal, MemStore, MutableProposal, NodeStore}; - - use crate::merkle::Merkle; - use super::*; + use crate::merkle::Merkle; + use firewood_storage::{ImmutableProposal, MemStore, MutableProposal, NodeStore}; + use std::sync::Arc; use test_case::test_case; pub(super) fn create_test_merkle() -> Merkle> { @@ -655,11 +577,10 @@ mod tests { #[test_case(&[]; "empty key")] #[test_case(&[1]; "non-empty key")] - #[tokio::test] - async fn path_iterate_empty_merkle_empty_key(key: &[u8]) { + fn path_iterate_empty_merkle_empty_key(key: &[u8]) { let merkle = create_test_merkle(); - let mut stream = merkle.path_iter(key).unwrap(); - assert!(stream.next().is_none()); + let mut iter = merkle.path_iter(key).unwrap(); + assert!(iter.next().is_none()); } #[test_case(&[],false; "empty key")] @@ -667,14 +588,13 @@ mod tests { #[test_case(&[0xBE, 0xEF],true; "match singleton key")] #[test_case(&[0xBE, 0xEF,0x10],true; "suffix of singleton key")] #[test_case(&[0xF0],false; "no key nibbles match singleton key")] - #[tokio::test] - async fn path_iterate_singleton_merkle(key: &[u8], should_yield_elt: bool) { + fn path_iterate_singleton_merkle(key: &[u8], should_yield_elt: bool) { let mut merkle = create_test_merkle(); merkle.insert(&[0xBE, 0xEF], Box::new([0x42])).unwrap(); - let mut stream = merkle.path_iter(key).unwrap(); - let node = match stream.next() { + let mut iter = merkle.path_iter(key).unwrap(); + let node = match iter.next() { Some(Ok(item)) => item, Some(Err(e)) => panic!("{e:?}"), None => { @@ -694,18 +614,17 @@ mod tests { assert_eq!(node.node.as_leaf().unwrap().value, Box::from([0x42])); assert_eq!(node.next_nibble, None); - assert!(stream.next().is_none()); + assert!(iter.next().is_none()); } #[test_case(&[0x00, 0x00, 0x00, 0xFF]; "leaf key")] #[test_case(&[0x00, 0x00, 0x00, 0xFF, 0x01]; "leaf key suffix")] - #[tokio::test] - async fn path_iterate_non_singleton_merkle_seek_leaf(key: &[u8]) { + fn path_iterate_non_singleton_merkle_seek_leaf(key: &[u8]) { let merkle = created_populated_merkle(); - let mut stream = merkle.path_iter(key).unwrap(); + let mut iter = merkle.path_iter(key).unwrap(); - let node = match stream.next() { + let node = match iter.next() { Some(Ok(node)) => node, Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), @@ -717,7 +636,7 @@ mod tests { assert_eq!(node.next_nibble, Some(0)); assert!(node.node.as_branch().unwrap().value.is_none()); - let node = match stream.next() { + let node = match iter.next() { Some(Ok(node)) => node, Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), @@ -740,7 +659,7 @@ mod tests { Some(vec![0x00, 0x00, 0x00].into_boxed_slice()), ); - let node = match stream.next() { + let node = match iter.next() { Some(Ok(node)) => node, Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), @@ -756,18 +675,17 @@ mod tests { Box::from([0x00, 0x00, 0x00, 0x0FF]) ); - assert!(stream.next().is_none()); + assert!(iter.next().is_none()); } #[test_case(&[0x00, 0x00, 0x00]; "branch key")] #[test_case(&[0x00, 0x00, 0x00, 0x10]; "branch key suffix (but not a leaf key)")] - #[tokio::test] - async fn path_iterate_non_singleton_merkle_seek_branch(key: &[u8]) { + fn path_iterate_non_singleton_merkle_seek_branch(key: &[u8]) { let merkle = created_populated_merkle(); - let mut stream = merkle.path_iter(key).unwrap(); + let mut iter = merkle.path_iter(key).unwrap(); - let node = match stream.next() { + let node = match iter.next() { Some(Ok(node)) => node, Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), @@ -779,7 +697,7 @@ mod tests { assert!(node.node.as_branch().unwrap().value.is_none()); assert_eq!(node.next_nibble, Some(0)); - let node = match stream.next() { + let node = match iter.next() { Some(Ok(node)) => node, Some(Err(e)) => panic!("{e:?}"), None => panic!("unexpected end of iterator"), @@ -795,40 +713,37 @@ mod tests { ); assert_eq!(node.next_nibble, None); - assert!(stream.next().is_none()); + assert!(iter.next().is_none()); } - #[tokio::test] - async fn key_value_iterate_empty() { + #[test] + fn key_value_iterate_empty() { let merkle = create_test_merkle(); - let stream = merkle.key_value_iter_from_key(b"x".to_vec().into_boxed_slice()); - check_stream_is_done(stream).await; + let iter = merkle.key_value_iter_from_key(b"x".to_vec().into_boxed_slice()); + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn node_iterate_empty() { + #[test] + fn node_iterate_empty() { let merkle = create_test_merkle(); - let stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); - check_stream_is_done(stream).await; + let iter = MerkleNodeIter::new(merkle.nodestore(), Box::new([])); + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn node_iterate_root_only() { + #[test] + fn node_iterate_root_only() { let mut merkle = create_test_merkle(); merkle.insert(&[0x00], Box::new([0x00])).unwrap(); - let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); + let mut iter = MerkleNodeIter::new(merkle.nodestore(), Box::new([])); - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); assert_eq!(node.as_leaf().unwrap().value.to_vec(), vec![0x00]); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } /// Returns a new [Merkle] with the following key-value pairs: @@ -874,80 +789,59 @@ mod tests { merkle } - #[tokio::test] - async fn node_iterator_no_start_key() { + #[test] + fn node_iterator_no_start_key() { let merkle = created_populated_merkle(); - let mut stream = MerkleNodeStream::new(merkle.nodestore(), Box::new([])); + let mut iter = MerkleNodeIter::new(merkle.nodestore(), Box::new([])); // Covers case of branch with no value - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert!(node.value.is_none()); // Covers case of branch with value - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice()); let node = node.as_branch().unwrap(); assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]); // Covers case of leaf with partial path - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]); - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]); - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]); // Covers case of leaf with no partial path - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); let node = node.as_leaf().unwrap(); assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn node_iterator_start_key_between_nodes() { + #[test] + fn node_iterator_start_key_between_nodes() { let merkle = created_populated_merkle(); - let mut stream = MerkleNodeStream::new( + let mut iter = MerkleNodeIter::new( merkle.nodestore(), vec![0x00, 0x00, 0x01].into_boxed_slice(), ); - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -955,32 +849,26 @@ mod tests { ); // Covers case of leaf with no partial path - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn node_iterator_start_key_on_node() { + #[test] + fn node_iterator_start_key_on_node() { let merkle = created_populated_merkle(); - let mut stream = MerkleNodeStream::new( + let mut iter = MerkleNodeIter::new( merkle.nodestore(), vec![0x00, 0xD0, 0xD0].into_boxed_slice(), ); - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), @@ -988,34 +876,30 @@ mod tests { ); // Covers case of leaf with no partial path - let (key, node) = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let (key, node) = iter.next().unwrap().unwrap(); assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice()); assert_eq!( node.as_leaf().unwrap().clone().value.to_vec(), vec![0x00, 0xFF] ); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn node_iterator_start_key_after_last_key() { + #[test] + fn node_iterator_start_key_after_last_key() { let merkle = created_populated_merkle(); - let stream = MerkleNodeStream::new(merkle.nodestore(), vec![0xFF].into_boxed_slice()); + let iter = MerkleNodeIter::new(merkle.nodestore(), vec![0xFF].into_boxed_slice()); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } #[test_case(Some(&[u8::MIN]); "Starting at first key")] #[test_case(None; "No start specified")] #[test_case(Some(&[128u8]); "Starting in middle")] #[test_case(Some(&[u8::MAX]); "Starting at last key")] - #[tokio::test] - async fn key_value_iterate_many(start: Option<&[u8]>) { + fn key_value_iterate_many(start: Option<&[u8]>) { let mut merkle = create_test_merkle(); // insert all values from u8::MIN to u8::MAX, with the key and value the same @@ -1023,7 +907,7 @@ mod tests { merkle.insert(&[k], Box::new([k])).unwrap(); } - let mut stream = match start { + let mut iter = match start { Some(start) => merkle.key_value_iter_from_key(start.to_vec().into_boxed_slice()), None => merkle.key_value_iter(), }; @@ -1031,7 +915,7 @@ mod tests { // we iterate twice because we should get a None then start over #[expect(clippy::indexing_slicing)] for k in start.map(|r| r[0]).unwrap_or_default()..=u8::MAX { - let next = futures::StreamExt::next(&mut stream).await.map(|kv| { + let next = iter.next().map(|kv| { let (k, v) = kv.unwrap(); assert_eq!(&*k, &*v); k @@ -1040,17 +924,17 @@ mod tests { assert_eq!(next, Some(vec![k].into_boxed_slice())); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_fused_empty() { + #[test] + fn key_value_fused_empty() { let merkle = create_test_merkle(); - check_stream_is_done(merkle.key_value_iter()).await; + assert_iterator_is_exhausted(merkle.key_value_iter()); } - #[tokio::test] - async fn key_value_table_test() { + #[test] + fn key_value_table_test() { let mut merkle = create_test_merkle(); let max: u8 = 100; @@ -1066,17 +950,14 @@ mod tests { } // Test with no start key - let mut stream = merkle.key_value_iter(); + let mut iter = merkle.key_value_iter(); for i in 0..=max { for j in 0..=max { let expected_key = vec![i, j]; let expected_value = vec![i, j]; assert_eq!( - futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(), + iter.next().unwrap().unwrap(), ( expected_key.into_boxed_slice(), expected_value.into_boxed_slice(), @@ -1085,19 +966,16 @@ mod tests { ); } } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); // Test with start key for i in 0..=max { - let mut stream = merkle.key_value_iter_from_key(vec![i].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![i].into_boxed_slice()); for j in 0..=max { let expected_key = vec![i, j]; let expected_value = vec![i, j]; assert_eq!( - futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(), + iter.next().unwrap().unwrap(), ( expected_key.into_boxed_slice(), expected_value.into_boxed_slice(), @@ -1106,13 +984,10 @@ mod tests { ); } if i == max { - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } else { assert_eq!( - futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(), + iter.next().unwrap().unwrap(), ( vec![i + 1, 0].into_boxed_slice(), vec![i + 1, 0].into_boxed_slice(), @@ -1123,8 +998,8 @@ mod tests { } } - #[tokio::test] - async fn key_value_fused_full() { + #[test] + fn key_value_fused_full() { let mut merkle = create_test_merkle(); let last = vec![0x00, 0x00, 0x00]; @@ -1142,22 +1017,19 @@ mod tests { merkle.insert(kv, kv.clone().into_boxed_slice()).unwrap(); } - let mut stream = merkle.key_value_iter(); + let mut iter = merkle.key_value_iter(); for kv in &key_values { - let next = futures::StreamExt::next(&mut stream) - .await - .unwrap() - .unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.1, &**kv); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_root_with_empty_value() { + #[test] + fn key_value_root_with_empty_value() { let mut merkle = create_test_merkle(); let key = vec![].into_boxed_slice(); @@ -1165,13 +1037,13 @@ mod tests { merkle.insert(&key, value.into()).unwrap(); - let mut stream = merkle.key_value_iter(); + let mut iter = merkle.key_value_iter(); - assert_eq!(stream.next().await.unwrap().unwrap(), (key, value.into())); + assert_eq!(iter.next().unwrap().unwrap(), (key, value.into())); } - #[tokio::test] - async fn key_value_get_branch_and_leaf() { + #[test] + fn key_value_get_branch_and_leaf() { let mut merkle = create_test_merkle(); let first_leaf = [0x00, 0x00]; @@ -1188,26 +1060,26 @@ mod tests { println!("{}", immutable_merkle.dump_to_string().unwrap()); merkle = immutable_merkle.fork().unwrap(); - let mut stream = merkle.key_value_iter(); + let mut iter = merkle.key_value_iter(); assert_eq!( - stream.next().await.unwrap().unwrap(), + iter.next().unwrap().unwrap(), (branch.into(), branch.into()) ); assert_eq!( - stream.next().await.unwrap().unwrap(), + iter.next().unwrap().unwrap(), (first_leaf.into(), first_leaf.into()) ); assert_eq!( - stream.next().await.unwrap().unwrap(), + iter.next().unwrap().unwrap(), (second_leaf.into(), second_leaf.into()) ); } - #[tokio::test] - async fn key_value_start_at_key_not_in_trie() { + #[test] + fn key_value_start_at_key_not_in_trie() { let mut merkle = create_test_merkle(); let first_key = 0x00; @@ -1227,25 +1099,25 @@ mod tests { merkle.insert(key, key.clone().into_boxed_slice()).unwrap(); } - let mut stream = merkle.key_value_iter_from_key(vec![intermediate].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![intermediate].into_boxed_slice()); let first_expected = key_values[1].as_slice(); - let first = stream.next().await.unwrap().unwrap(); + let first = iter.next().unwrap().unwrap(); assert_eq!(&*first.0, &*first.1); assert_eq!(&*first.1, first_expected); let second_expected = key_values[2].as_slice(); - let second = stream.next().await.unwrap().unwrap(); + let second = iter.next().unwrap().unwrap(); assert_eq!(&*second.0, &*second.1); assert_eq!(&*second.1, second_expected); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_on_branch_with_no_value() { + #[test] + fn key_value_start_at_key_on_branch_with_no_value() { let sibling_path = 0x00; let branch_path = 0x0f; let children = 0..=0x0f; @@ -1273,20 +1145,20 @@ mod tests { let start = keys.iter().position(|key| key[0] == branch_path).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.key_value_iter_from_key(vec![branch_path].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![branch_path].into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_on_branch_with_value() { + #[test] + fn key_value_start_at_key_on_branch_with_value() { let sibling_path = 0x00; let branch_path = 0x0f; let branch_key = vec![branch_path]; @@ -1321,20 +1193,20 @@ mod tests { let start = keys.iter().position(|key| key == &branch_key).unwrap(); let keys = &keys[start..]; - let mut stream = merkle.key_value_iter_from_key(branch_key.into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(branch_key.into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_on_extension() { + #[test] + fn key_value_start_at_key_on_extension() { let missing = 0x0a; let children = (0..=0x0f).filter(|x| *x != missing); let mut merkle = create_test_merkle(); @@ -1351,20 +1223,20 @@ mod tests { let keys = &keys[(missing as usize)..]; - let mut stream = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_overlapping_with_extension_but_greater() { + #[test] + fn key_value_start_at_key_overlapping_with_extension_but_greater() { let start_key = 0x0a; let shared_path = 0x09; // 0x0900, 0x0901, ... 0x0a0f @@ -1377,13 +1249,13 @@ mod tests { merkle.insert(&key, key.clone().into()).unwrap(); }); - let stream = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); + let iter = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_overlapping_with_extension_but_smaller() { + #[test] + fn key_value_start_at_key_overlapping_with_extension_but_smaller() { let start_key = 0x00; let shared_path = 0x09; // 0x0900, 0x0901, ... 0x0a0f @@ -1398,20 +1270,20 @@ mod tests { }) .collect(); - let mut stream = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![start_key].into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_between_siblings() { + #[test] + fn key_value_start_at_key_between_siblings() { let missing = 0xaa; let children = (0..=0xf) .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff @@ -1430,32 +1302,32 @@ mod tests { let keys = &keys[((missing >> 4) as usize)..]; - let mut stream = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![missing].into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_greater_than_all_others_leaf() { + #[test] + fn key_value_start_at_key_greater_than_all_others_leaf() { let key = [0x00]; let greater_key = [0xff]; let mut merkle = create_test_merkle(); merkle.insert(&key, key.into()).unwrap(); - let stream = merkle.key_value_iter_from_key(greater_key); + let iter = merkle.key_value_iter_from_key(greater_key); - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - #[tokio::test] - async fn key_value_start_at_key_greater_than_all_others_branch() { + #[test] + fn key_value_start_at_key_greater_than_all_others_branch() { let greatest = 0xff; let children = (0..=0xf) .map(|val| (val << 4) + val) // 0x00, 0x11, ... 0xff @@ -1474,23 +1346,19 @@ mod tests { let keys = &keys[((greatest >> 4) as usize)..]; - let mut stream = merkle.key_value_iter_from_key(vec![greatest].into_boxed_slice()); + let mut iter = merkle.key_value_iter_from_key(vec![greatest].into_boxed_slice()); for key in keys { - let next = stream.next().await.unwrap().unwrap(); + let next = iter.next().unwrap().unwrap(); assert_eq!(&*next.0, &*next.1); assert_eq!(&*next.0, key); } - check_stream_is_done(stream).await; + assert_iterator_is_exhausted(iter); } - async fn check_stream_is_done(mut stream: S) - where - S: FusedStream + Unpin, - { - assert!(stream.next().await.is_none()); - assert!(stream.is_terminated()); + fn assert_iterator_is_exhausted(mut iter: I) { + assert!(iter.next().is_none()); } } diff --git a/firewood/src/iter/try_extend.rs b/firewood/src/iter/try_extend.rs new file mode 100644 index 000000000000..f6322d4b51dc --- /dev/null +++ b/firewood/src/iter/try_extend.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +/// An extension trait for extendable collections to handle iterators that yield +/// `Result`. +pub(crate) trait TryExtend: Extend { + /// Lazily collect an iterator over results into an extendable collection of the Ok value. + /// + /// Returns early if an error is encountered returning that error. + fn try_extend>, E>(&mut self, iter: I) -> Result<(), E> { + let mut error = None; + + self.extend(Shunt { + iter: iter.into_iter(), + error: &mut error, + }); + + if let Some(e) = error { Err(e) } else { Ok(()) } + } +} + +impl, T> TryExtend for C {} + +struct Shunt<'a, I, E> { + iter: I, + error: &'a mut Option, +} + +impl>, T, E> Iterator for Shunt<'_, I, E> { + type Item = T; + + fn next(&mut self) -> Option { + if self.error.is_some() { + return None; + } + + match self.iter.next() { + Some(Ok(item)) => Some(item), + Some(Err(e)) => { + *self.error = Some(e); + None + } + None => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + if self.error.is_some() { + (0, Some(0)) + } else { + let (_, upper) = self.iter.size_hint(); + (0, upper) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_try_extend() { + let input = [Ok(0), Ok(1), Err("error"), Ok(3)]; + let mut collection = Vec::new(); + + let mut iter = input.into_iter(); + let result = collection.try_extend(iter.by_ref()); + assert_eq!(result, Err("error")); + assert_eq!(iter.next(), Some(Ok(3))); + assert_eq!(iter.next(), None); + assert_eq!(*collection, [0, 1]); + // vec should allocate for 4 elements because of the size hint + assert_eq!(collection.capacity(), 4); + } +} diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index ba359008d24d..d34f65e77bb0 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -127,6 +127,9 @@ compile_error!( /// Database module for Firewood. pub mod db; +/// Iterator module, for both node and key-value streams +pub mod iter; + /// Database manager module pub mod manager; @@ -142,9 +145,6 @@ pub use firewood_macros::metrics; /// Range proof module pub mod range_proof; -/// Stream module, for both node and key-value streams -pub mod stream; - /// Version 2 API pub mod v2; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 19108714cdc7..11bf10a48bad 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -20,7 +20,7 @@ use metrics::gauge; use typed_builder::TypedBuilder; use crate::merkle::Merkle; -use crate::v2::api::{HashKey, OptionalHashKeyExt}; +use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ @@ -259,13 +259,10 @@ impl RevisionManager { self.proposals.lock().expect("poisoned lock").push(proposal); } - pub fn view( - &self, - root_hash: HashKey, - ) -> Result, RevisionManagerError> { + pub fn view(&self, root_hash: HashKey) -> Result { // First try to find it in committed revisions if let Ok(committed) = self.revision(root_hash.clone()) { - return Ok(Box::new(committed)); + return Ok(committed); } // If not found in committed revisions, try proposals @@ -280,7 +277,7 @@ impl RevisionManager { provided: root_hash, })?; - Ok(Box::new(proposal)) + Ok(proposal) } pub fn revision(&self, root_hash: HashKey) -> Result { diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 2733bd5ede20..0c499fbeacf7 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -4,20 +4,18 @@ #[cfg(test)] mod tests; +use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; -use crate::stream::{MerkleKeyValueStream, PathIterator}; use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; use firewood_storage::{ BranchNode, Child, FileIoError, HashType, HashedNodeReader, ImmutableProposal, IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, Parentable, Path, ReadableStorage, SharedNode, TrieHash, TrieReader, ValueDigest, }; -use futures::{StreamExt, TryStreamExt}; use metrics::counter; use std::collections::HashSet; use std::fmt::Debug; -use std::future::ready; use std::io::Error; use std::iter::once; use std::num::NonZeroUsize; @@ -272,16 +270,16 @@ impl Merkle { PathIterator::new(&self.nodestore, key) } - pub(super) fn key_value_iter(&self) -> MerkleKeyValueStream<'_, T> { - MerkleKeyValueStream::from(&self.nodestore) + pub(super) fn key_value_iter(&self) -> MerkleKeyValueIter<'_, T> { + MerkleKeyValueIter::from(&self.nodestore) } pub(super) fn key_value_iter_from_key>( &self, key: K, - ) -> MerkleKeyValueStream<'_, T> { + ) -> MerkleKeyValueIter<'_, T> { // TODO danlaine: change key to &[u8] - MerkleKeyValueStream::from_key(&self.nodestore, key.as_ref()) + MerkleKeyValueIter::from_key(&self.nodestore, key.as_ref()) } /// Generate a cryptographic proof for a range of key-value pairs in the Merkle trie. @@ -352,7 +350,7 @@ impl Merkle { /// None /// ).await?; /// ``` - pub(super) async fn range_proof( + pub(super) fn range_proof( &self, start_key: Option<&[u8]>, end_key: Option<&[u8]>, @@ -367,14 +365,14 @@ impl Merkle { } } - let mut stream = match start_key { + let mut iter = match start_key { // TODO: fix the call-site to force the caller to do the allocation Some(key) => self.key_value_iter_from_key(key.to_vec().into_boxed_slice()), None => self.key_value_iter(), }; // fetch the first key from the stream - let first_result = stream.next().await; + let first_result = iter.next(); // transpose the Option> to Result, E> // If this is an error, the ? operator will return it @@ -403,28 +401,22 @@ impl Merkle { let mut key_values = vec![(first_key, first_value)]; - // we stop streaming if either we hit the limit or the key returned was larger + // we stop iterating if either we hit the limit or the key returned was larger // than the largest key requested - key_values.extend( - stream - .take(limit.unwrap_or(usize::MAX)) - .take_while(|kv| { - // no last key asked for, so keep going - let Some(last_key) = end_key else { - return ready(true); - }; + key_values.try_extend(iter.take(limit.unwrap_or(usize::MAX)).take_while(|kv| { + // no last key asked for, so keep going + let Some(last_key) = end_key else { + return true; + }; - // return the error if there was one - let Ok(kv) = kv else { - return ready(true); - }; + // return the error if there was one + let Ok(kv) = kv else { + return true; + }; - // keep going if the key returned is less than the last key requested - ready(&*kv.0 <= last_key) - }) - .try_collect::>() - .await?, - ); + // keep going if the key returned is less than the last key requested + *kv.0 <= *last_key + }))?; let end_proof = key_values .last() diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index 20825231d884..7c62a9764b1e 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -440,12 +440,12 @@ fn single_key_proof() { } } -#[tokio::test] -async fn empty_range_proof() { +#[test] +fn empty_range_proof() { let merkle = create_in_memory_merkle(); assert!(matches!( - merkle.range_proof(None, None, None).await.unwrap_err(), + merkle.range_proof(None, None, None).unwrap_err(), api::Error::RangeProofOnEmptyTrie )); } diff --git a/firewood/src/merkle/tests/unvalidated.rs b/firewood/src/merkle/tests/unvalidated.rs index 9b3c1111be5d..7e2a815bf290 100644 --- a/firewood/src/merkle/tests/unvalidated.rs +++ b/firewood/src/merkle/tests/unvalidated.rs @@ -6,18 +6,15 @@ use crate::range_proof::RangeProof; type KeyValuePairs = Vec<(Box<[u8]>, Box<[u8]>)>; -#[tokio::test] +#[test] #[ignore = "https://github.com/ava-labs/firewood/issues/738"] -async fn range_proof_invalid_bounds() { +fn range_proof_invalid_bounds() { let merkle = create_in_memory_merkle().hash(); let start_key = &[0x01]; let end_key = &[0x00]; - match merkle - .range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(1)) - .await - { + match merkle.range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(1)) { Err(api::Error::InvalidRange { start_key: first_key, end_key: last_key, @@ -27,12 +24,12 @@ async fn range_proof_invalid_bounds() { } } -#[tokio::test] +#[test] #[ignore = "https://github.com/ava-labs/firewood/issues/738"] -async fn full_range_proof() { +fn full_range_proof() { let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); - let rangeproof = merkle.range_proof(None, None, None).await.unwrap(); + let rangeproof = merkle.range_proof(None, None, None).unwrap(); assert_eq!(rangeproof.key_values().len(), u8::MAX as usize + 1); assert_ne!(rangeproof.start_proof(), rangeproof.end_proof()); let left_proof = merkle.prove(&[u8::MIN]).unwrap(); @@ -41,16 +38,15 @@ async fn full_range_proof() { assert_eq!(rangeproof.end_proof(), &right_proof); } -#[tokio::test] +#[test] #[ignore = "https://github.com/ava-labs/firewood/issues/738"] -async fn single_value_range_proof() { +fn single_value_range_proof() { const RANDOM_KEY: u8 = 42; let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); let rangeproof = merkle .range_proof(Some(&[RANDOM_KEY]), None, NonZeroUsize::new(1)) - .await .unwrap(); assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); assert_eq!(rangeproof.key_values().len(), 1); diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 2ab5de5155a7..30217f227d2f 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -4,9 +4,7 @@ use crate::manager::RevisionManagerError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; -use async_trait::async_trait; use firewood_storage::{FileIoError, TrieHash}; -use futures::Stream; use std::fmt::Debug; use std::num::NonZeroUsize; use std::sync::Arc; @@ -17,9 +15,9 @@ pub use crate::v2::batch_op::{BatchOp, KeyValuePair, KeyValuePairIter, MapIntoBa /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with /// lifetimes are not allowed (hence 'static) -pub trait KeyType: AsRef<[u8]> + Send + Sync + Debug {} +pub trait KeyType: AsRef<[u8]> + Debug {} -impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug {} +impl KeyType for T where T: AsRef<[u8]> + Debug {} /// A `ValueType` is the same as a `KeyType`. However, these could /// be a different type from the `KeyType` on a given API call. @@ -27,9 +25,9 @@ impl KeyType for T where T: AsRef<[u8]> + Send + Sync + Debug {} /// This also means that the type of all the keys for a single /// API call must be the same, as well as the type of all values /// must be the same. -pub trait ValueType: AsRef<[u8]> + Send + Sync + Debug {} +pub trait ValueType: AsRef<[u8]> + Debug {} -impl ValueType for T where T: AsRef<[u8]> + Send + Sync + Debug {} +impl ValueType for T where T: AsRef<[u8]> + Debug {} /// The type and size of a single hash key /// These are 256-bit hashes that are used for a variety of reasons: @@ -239,10 +237,9 @@ pub trait Db { /// 2. From [`Db::propose`] which is a view on top of the most recently /// committed revision with changes applied; or /// 3. From [`Proposal::propose`] which is a view on top of another proposal. -#[async_trait] pub trait DbView { /// The type of a stream of key/value pairs - type Stream<'view>: Stream> + type Iter<'view>: Iterator> where Self: 'view; @@ -252,13 +249,16 @@ pub trait DbView { /// /// If the database is empty, this will return None, unless the ethhash feature is enabled. /// In that case, we return the special ethhash compatible empty trie hash. - async fn root_hash(&self) -> Result, Error>; + #[expect(clippy::missing_errors_doc)] + fn root_hash(&self) -> Result, Error>; /// Get the value of a specific key - async fn val(&self, key: K) -> Result, Error>; + #[expect(clippy::missing_errors_doc)] + fn val(&self, key: K) -> Result, Error>; /// Obtain a proof for a single key - async fn single_key_proof(&self, key: K) -> Result; + #[expect(clippy::missing_errors_doc)] + fn single_key_proof(&self, key: K) -> Result; /// Obtain a range proof over a set of keys /// @@ -267,8 +267,8 @@ pub trait DbView { /// * `first_key` - If None, start at the lowest key /// * `last_key` - If None, continue to the end of the database /// * `limit` - The maximum number of keys in the range proof - /// - async fn range_proof( + #[expect(clippy::missing_errors_doc)] + fn range_proof( &self, first_key: Option, last_key: Option, @@ -285,24 +285,125 @@ pub trait DbView { /// /// If you always want to start at the beginning, [`DbView::iter`] is easier to use /// If you always provide a key, [`DbView::iter_from`] is easier to use - /// #[expect(clippy::missing_errors_doc)] - fn iter_option(&self, first_key: Option) -> Result, Error>; + fn iter_option(&self, first_key: Option) -> Result, Error>; /// Obtain a stream over the keys/values of this view, starting from the beginning #[expect(clippy::missing_errors_doc)] #[expect(clippy::iter_not_returning_iterator)] - fn iter(&self) -> Result, Error> { + fn iter(&self) -> Result, Error> { self.iter_option(Option::::None) } /// Obtain a stream over the key/values, starting at a specific key #[expect(clippy::missing_errors_doc)] - fn iter_from(&self, first_key: K) -> Result, Error> { + fn iter_from(&self, first_key: K) -> Result, Error> { + self.iter_option(Some(first_key)) + } +} + +/// A boxed iterator over key/value pairs. +pub type BoxKeyValueIter<'view> = Box> + 'view>; + +/// A dynamic dyspatch version of [`DbView`] that can be shared. +pub type ArcDynDbView = Arc; + +/// A dyn-safe version of [`DbView`]. +pub trait DynDbView: Debug + Send + Sync + 'static { + /// Get the root hash for the current [`DynDbView`] + /// + /// # Note + /// + /// If the database is empty, this will return None, unless the ethhash feature is enabled. + /// In that case, we return the special ethhash compatible empty trie hash. + #[expect(clippy::missing_errors_doc)] + fn root_hash(&self) -> Result, Error>; + + /// Get the value of a specific key + #[expect(clippy::missing_errors_doc)] + fn val(&self, key: &[u8]) -> Result, Error>; + + /// Obtain a proof for a single key + #[expect(clippy::missing_errors_doc)] + fn single_key_proof(&self, key: &[u8]) -> Result; + + /// Obtain a range proof over a set of keys + /// + /// # Arguments + /// + /// * `first_key` - If None, start at the lowest key + /// * `last_key` - If None, continue to the end of the database + /// * `limit` - The maximum number of keys in the range proof + #[expect(clippy::missing_errors_doc)] + fn range_proof( + &self, + first_key: Option<&[u8]>, + last_key: Option<&[u8]>, + limit: Option, + ) -> Result; + + /// Obtain a stream over the keys/values of this view, using an optional starting point + /// + /// # Arguments + /// + /// * `first_key` - If None, start at the lowest key + /// + /// # Note + /// + /// If you always want to start at the beginning, [`DbView::iter`] is easier to use + /// If you always provide a key, [`DbView::iter_from`] is easier to use + #[expect(clippy::missing_errors_doc)] + fn iter_option(&self, first_key: Option<&[u8]>) -> Result, Error>; + + /// Obtain a stream over the keys/values of this view, starting from the beginning + #[expect(clippy::missing_errors_doc)] + #[expect(clippy::iter_not_returning_iterator)] + fn iter(&self) -> Result, Error> { + self.iter_option(None) + } + + /// Obtain a stream over the key/values, starting at a specific key + #[expect(clippy::missing_errors_doc)] + fn iter_from(&self, first_key: &[u8]) -> Result, Error> { self.iter_option(Some(first_key)) } } +impl DynDbView for T +where + for<'view> T::Iter<'view>: Sized, +{ + fn root_hash(&self) -> Result, Error> { + DbView::root_hash(self) + } + + fn val(&self, key: &[u8]) -> Result, Error> { + DbView::val(self, key) + } + + fn single_key_proof(&self, key: &[u8]) -> Result { + DbView::single_key_proof(self, key) + } + + fn range_proof( + &self, + first_key: Option<&[u8]>, + last_key: Option<&[u8]>, + limit: Option, + ) -> Result { + DbView::range_proof(self, first_key, last_key, limit) + } + + fn iter_option(&self, first_key: Option<&[u8]>) -> Result, Error> { + // NOTE: `Result::map` does not work here because the compiler cannot correctly + // infer the unsizing operation + match DbView::iter_option(self, first_key) { + Ok(iter) => Ok(Box::new(iter)), + Err(e) => Err(e), + } + } +} + /// A proposal for a new revision of the database. /// /// A proposal may be committed, which consumes the @@ -314,7 +415,7 @@ pub trait DbView { /// A proposal type must also implement everything in a /// [`DbView`], which means you can fetch values from it or /// obtain proofs. -pub trait Proposal: DbView + Send + Sync { +pub trait Proposal: DbView { /// The type of a proposal type Proposal: DbView + Proposal; diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 70ff91ae682b..c062a6d9ae9b 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -31,10 +31,8 @@ firewood-storage.workspace = true hex.workspace = true log.workspace = true nonzero_ext.workspace = true -tokio = { workspace = true, features = ["full"] } # Regular dependencies csv = "1.3.1" -futures-util = "0.3.31" indicatif = "0.18.0" [features] diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index fe380a1d12fa..801dde47d18f 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -2,10 +2,9 @@ // See the file LICENSE.md for licensing terms. use clap::Args; use firewood::db::{Db, DbConfig}; +use firewood::iter::MerkleKeyValueIter; use firewood::merkle::{Key, Value}; -use firewood::stream::MerkleKeyValueStream; use firewood::v2::api::{self, Db as _}; -use futures_util::StreamExt; use std::borrow::Cow; use std::error::Error; use std::fs::File; @@ -117,7 +116,7 @@ pub struct Options { pub hex: bool, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("dump database {opts:?}"); // Check if dot format is used with unsupported options @@ -166,9 +165,9 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let stop_key = opts.stop_key.clone().or(opts.stop_key_hex.clone()); let mut key_count: u32 = 0; - let mut stream = MerkleKeyValueStream::from_key(&latest_rev, start_key); + let mut iter = MerkleKeyValueIter::from_key(&latest_rev, start_key); - while let Some(item) = stream.next().await { + while let Some(item) = iter.next() { match item { Ok((key, value)) => { output_handler.handle_record(&key, &value)?; @@ -178,7 +177,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { if (stop_key.as_ref().is_some_and(|stop_key| key >= *stop_key)) || key_count_exceeded(opts.max_key_count, key_count) { - handle_next_key(stream.next().await); + handle_next_key(iter.next()); break; } } diff --git a/fwdctl/src/get.rs b/fwdctl/src/get.rs index 522d8110a947..7f357fbcc953 100644 --- a/fwdctl/src/get.rs +++ b/fwdctl/src/get.rs @@ -18,7 +18,7 @@ pub struct Options { pub key: String, } -pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { +pub(super) fn run(opts: &Options) -> Result<(), api::Error> { log::debug!("get key value pair {opts:?}"); let cfg = DbConfig::builder().create_if_missing(false).truncate(false); @@ -33,7 +33,7 @@ pub(super) async fn run(opts: &Options) -> Result<(), api::Error> { let rev = db.revision(hash)?; - match rev.val(opts.key.as_bytes()).await { + match rev.val(opts.key.as_bytes()) { Ok(Some(val)) => { let s = String::from_utf8_lossy(val.as_ref()); println!("{s:?}"); diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index e35d747ce87c..b571f9b68e95 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -71,8 +71,7 @@ enum Commands { Check(check::Options), } -#[tokio::main] -async fn main() -> Result<(), api::Error> { +fn main() -> Result<(), api::Error> { let cli = Cli::parse(); env_logger::init_from_env( @@ -83,10 +82,10 @@ async fn main() -> Result<(), api::Error> { match &cli.command { Commands::Create(opts) => create::run(opts), Commands::Insert(opts) => insert::run(opts), - Commands::Get(opts) => get::run(opts).await, + Commands::Get(opts) => get::run(opts), Commands::Delete(opts) => delete::run(opts), Commands::Root(opts) => root::run(opts), - Commands::Dump(opts) => dump::run(opts).await, + Commands::Dump(opts) => dump::run(opts), Commands::Graph(opts) => graph::run(opts), Commands::Check(opts) => check::run(opts), } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2260ce11e0cd..6724819769ca 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -50,7 +50,7 @@ sha3 = { version = "0.10.8", optional = true } [dev-dependencies] # Workspace dependencies -criterion = { workspace = true, features = ["async_tokio", "html_reports"] } +criterion = { workspace = true, features = ["html_reports"] } pprof = { workspace = true, features = ["flamegraph"] } rand.workspace = true tempfile.workspace = true From f33b8c6a20c25ce741b5cf947f08997e9083e7eb Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 12:35:03 -0700 Subject: [PATCH 0915/1053] chore: nuke grpc-testtool (#1220) Fixes: #1051 Depends On: #1219 --- Cargo.toml | 1 - grpc-testtool/Cargo.toml | 45 ---- grpc-testtool/README.md | 29 --- grpc-testtool/benches/insert.rs | 236 ------------------ grpc-testtool/build.rs | 24 -- grpc-testtool/proto/merkle/merkle.proto | 109 -------- .../proto/process-server/process-server.proto | 15 -- grpc-testtool/proto/rpcdb/rpcdb.proto | 127 ---------- grpc-testtool/proto/sync/sync.proto | 166 ------------ grpc-testtool/src/bin/client.rs | 6 - grpc-testtool/src/bin/process-server.rs | 111 -------- grpc-testtool/src/lib.rs | 23 -- grpc-testtool/src/service.rs | 103 -------- grpc-testtool/src/service/database.rs | 203 --------------- grpc-testtool/src/service/db.rs | 108 -------- grpc-testtool/src/service/process.rs | 14 -- 16 files changed, 1320 deletions(-) delete mode 100644 grpc-testtool/Cargo.toml delete mode 100644 grpc-testtool/README.md delete mode 100644 grpc-testtool/benches/insert.rs delete mode 100644 grpc-testtool/build.rs delete mode 100644 grpc-testtool/proto/merkle/merkle.proto delete mode 100644 grpc-testtool/proto/process-server/process-server.proto delete mode 100644 grpc-testtool/proto/rpcdb/rpcdb.proto delete mode 100644 grpc-testtool/proto/sync/sync.proto delete mode 100644 grpc-testtool/src/bin/client.rs delete mode 100644 grpc-testtool/src/bin/process-server.rs delete mode 100644 grpc-testtool/src/lib.rs delete mode 100644 grpc-testtool/src/service.rs delete mode 100644 grpc-testtool/src/service/database.rs delete mode 100644 grpc-testtool/src/service/db.rs delete mode 100644 grpc-testtool/src/service/process.rs diff --git a/Cargo.toml b/Cargo.toml index 982652c01953..45bcc42a640b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "storage", "triehash", ] -exclude = ["grpc-testtool"] resolver = "2" [workspace.package] diff --git a/grpc-testtool/Cargo.toml b/grpc-testtool/Cargo.toml deleted file mode 100644 index 415d5954f6ab..000000000000 --- a/grpc-testtool/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "rpc" -version = "0.0.4" -edition = "2024" -rust-version = "1.85.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "process-server" -test = false -bench = false - -[[bin]] -name = "client" -test = false -bench = false - -[dependencies] -firewood = { version = "*", path = "../firewood" } -prost = "0.13.1" -tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread"] } -tonic = { version = "0.13.0", features = ["tls-ring"] } -tracing = { version = "0.1.40" } -clap = { version = "4.5.0", features = ["derive"] } -log = "0.4.20" -env_logger = "0.11.2" -chrono = "0.4.34" -serde_json = "1.0.113" -serde = { version = "1.0.196", features = ["derive"] } - -[build-dependencies] -tonic-build = "0.13.0" - -[dev-dependencies] -criterion = { version = "0.6.0", features = ["async_tokio"] } -rand = "0.9.1" -rand_distr = "0.5.0" - -[[bench]] -name = "insert" -harness = false - -[package.metadata.cargo-machete] -ignored = ["prost", "tonic-build"] diff --git a/grpc-testtool/README.md b/grpc-testtool/README.md deleted file mode 100644 index 1cec486db8eb..000000000000 --- a/grpc-testtool/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Firewood process-server implementation - -This service is a plug-in for the test tool orchestrator (currently closed source). -The test tool is used for both performance and correctness testing, especially for the syncer. - -```mermaid -sequenceDiagram -Orchestrator->>ProcessServer: Startup (via command line) -ProcessServer->>Firewood: Open or create database -Orchestrator->>+ProcessServer: GRPC request -ProcessServer->>+Firewood: Native API Call -Firewood->>-ProcessServer: Response -ProcessServer->>-Orchestrator: Response -``` - -There are 3 RPC specs that must be implemented: - -1. The rpcdb proto, which supports simple operations like Has, Get, Put, Delete, and some iterators. -2. The sync proto, which supports retrieving range and change proofs -3. The process-server proto, which currently only retrieves metrics - -## Running - -To test the release version of firewood, just run `RUST_MIN_STACK=7000000 cargo bench`. If you make some changes and then -run it again, it will give you a report showing how much it sped up or slowed down. - -If you want to run this against merkledb, first build the process-server following the instructions in -the [merkledb-tester](https://github.com/ava-labs/merkledb-tester) directory, then modify your PATH so -that `process-server` from the merkledbexecutable is found first, then run `cargo bench`. diff --git a/grpc-testtool/benches/insert.rs b/grpc-testtool/benches/insert.rs deleted file mode 100644 index 39fad985fab5..000000000000 --- a/grpc-testtool/benches/insert.rs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; -use rand::{Rng, SeedableRng}; -use rand_distr::Alphanumeric; -use std::borrow::BorrowMut as _; -use std::cell::RefCell; -use std::env; -use std::fs::remove_dir_all; -use std::net::TcpStream; -use std::os::unix::process::CommandExt; -use std::path::PathBuf; -use std::thread::sleep; -use std::time::Duration; - -use rpc::rpcdb::{self, PutRequest, WriteBatchRequest}; -pub use rpc::service::Database as DatabaseService; - -use rpcdb::database_client::DatabaseClient; - -use std::process::Command; - -/// The directory where the database will be created -const TESTDIR: &str = "/tmp/benchdb"; -/// The port to use for testing -const TESTPORT: u16 = 5000; -/// The URI to connect to; this better match the TESTPORT -const TESTURI: &str = "http://127.0.0.1:5000"; -/// Retry timeouts (in seconds); we want this long for processes -/// to start and exit -const RETRY_TIMEOUT_SEC: u32 = 5; - -/// Merkledb configuration options; these are ignored by the rust side -/// These were chosen to be as close to the defaults for firewood as -/// possible. NodeCacheSize is a guess. -const MERKLEDB_OPTIONAL_CONFIGURATIONS: &str = r#" - { - "BranchFactor":16, - "ProcessName":"bench", - "HistoryLength":100, - "NodeCacheSize":1000 - } -"#; - -/// Clean up anything that might be left from prior runs -fn stop_everything() -> Result<(), std::io::Error> { - // kill all process servers - Command::new("killall").arg("process-server").output()?; - - // wait for them to die - retry("process-server wouldn't die", || { - process_server_pids().is_empty() - }); - - // remove test directory, ignoring any errors - let _ = remove_dir_all(TESTDIR); - - Ok(()) -} -fn reset_everything() -> Result<(), std::io::Error> { - stop_everything()?; - // find the process server - let process_server = process_server_path().expect("Can't find process-server on path"); - eprintln!("Using process-server {}", process_server.display()); - - // spawn a new one; use a separate thread to avoid zombies - std::thread::spawn(|| { - Command::new(process_server) - .arg("--grpc-port") - .arg(TESTPORT.to_string()) - .arg("--db-dir") - .arg(TESTDIR) - .arg("--config") - .arg(MERKLEDB_OPTIONAL_CONFIGURATIONS) - .process_group(0) - .spawn() - .expect("unable to start process-server") - .wait() - }); - - // wait for it to accept connections - retry("couldn't connect to process-server", || { - TcpStream::connect(format!("localhost:{TESTPORT}")).is_ok() - }); - - Ok(()) -} - -/// Poll a function until it returns true -/// -/// Pass in a message and a closure to execute. Panics if it times out. -/// -/// # Arguments -/// -/// * `msg` - The message to render if it panics -/// * `t` - The test closure -fn retry bool>(msg: &str, t: TEST) { - const TEST_INTERVAL_MS: u32 = 50; - for _ in 0..=(RETRY_TIMEOUT_SEC * 1000 / TEST_INTERVAL_MS) { - sleep(Duration::from_millis(TEST_INTERVAL_MS as u64)); - if t() { - return; - } - } - panic!("{msg} timed out after {RETRY_TIMEOUT_SEC} second(s)"); -} - -/// Return a list of process IDs for any running process-server processes -fn process_server_pids() -> Vec { - // Basically we do `ps -eo pid=,comm=` which removes the header from ps - // and gives us just the pid and the command, then we look for process-server - // TODO: we match "123456 process-server-something-else", which isn't ideal - let cmd = Command::new("ps") - .arg("-eo") - .arg("pid=,comm=") - .output() - .expect("Can't run ps"); - String::from_utf8_lossy(&cmd.stdout) - .lines() - .filter_map(|line| { - line.trim_start().find(" process-server").map(|pos| { - str::parse(line.trim_start().get(0..pos).unwrap_or_default()).unwrap_or_default() - }) - }) - .collect() -} - -/// Finds the first process-server on the path, or in some predefined locations -/// -/// If the process-server isn't on the path, look for it in a target directory -/// As a last resort, we check the parent in case you're running from the -/// grpc-testtool directory, or in the current directory -fn process_server_path() -> Option { - const OTHER_PLACES_TO_LOOK: &str = ":target/release:../target/release:."; - - env::var_os("PATH").and_then(|mut paths| { - paths.push(OTHER_PLACES_TO_LOOK); - env::split_paths(&paths) - .filter_map(|dir| { - let full_path = dir.join("process-server"); - if full_path.is_file() { - Some(full_path) - } else { - None - } - }) - .next() - }) -} - -/// The actual insert benchmark -fn insert( - criterion: &mut Criterion, -) { - // we save the tokio runtime because the client is only valid from within the - // same runtime - let runtime = tokio::runtime::Runtime::new().expect("tokio startup"); - - // clean up anything that was running before, and make sure we have an empty directory - // to run the tests in - reset_everything().expect("unable to reset everything"); - - // We want a consistent seed, but we need different values for each batch, so we - // reseed each time we compute more data from the next seed value upwards - let seed = RefCell::new(0); - - let client = runtime - .block_on(DatabaseClient::connect(TESTURI)) - .expect("connection succeeded"); - - criterion.bench_with_input( - BenchmarkId::new("insert", BATCHSIZE), - &BATCHSIZE, - |b, &s| { - b.to_async(&runtime).iter_batched( - || { - // seed a new random number generator to generate data - // each time we call this, we increase the seed by 1 - // this gives us different but consistently different random data - let seed = { - let mut inner = seed.borrow_mut(); - *inner += 1; - *inner - }; - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - // generate the put request, which is BATCHSIZE PutRequest objects with random keys/values - let put_requests: Vec = (0..s) - .map(|_| { - ( - rng.borrow_mut() - .sample_iter(&Alphanumeric) - .take(KEYLEN) - .collect::>(), - rng.borrow_mut() - .sample_iter(&Alphanumeric) - .take(DATALEN) - .collect::>(), - ) - }) - .map(|(key, value)| PutRequest { key, value }) - .collect(); - - // wrap it into a tonic::Request to pass to the execution routine - let req = tonic::Request::new(WriteBatchRequest { - puts: put_requests, - deletes: vec![], - }); - - // hand back the client and the request contents to the benchmark executor - (client.clone(), req) - }, - // this part is actually timed - |(mut client, req)| async move { - client - .write_batch(req) - .await - .expect("batch insert succeeds"); - }, - // I have no idea what this does, but the docs seem to say you almost always want this - BatchSize::SmallInput, - ) - }, - ); - - stop_everything().expect("unable to stop the process server"); -} - -criterion_group! { - name = benches; - config = Criterion::default().sample_size(20); - targets = insert::<1, 32, 32>, insert::<20, 32, 32>, insert::<10000, 32, 32> -} - -criterion_main!(benches); diff --git a/grpc-testtool/build.rs b/grpc-testtool/build.rs deleted file mode 100644 index f546f152e04e..000000000000 --- a/grpc-testtool/build.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use std::path::PathBuf; - -fn main() -> Result<(), Box> { - // we want to import these proto files - let import_protos = ["sync", "rpcdb", "process-server"]; - - let protos: Box<[PathBuf]> = import_protos - .into_iter() - .map(|proto| PathBuf::from(format!("proto/{proto}/{proto}.proto"))) - .collect(); - - // go through each proto and build it, also let cargo know we rerun this if the file changes - for proto in protos.iter() { - tonic_build::compile_protos(proto)?; - - // this improves recompile times; we only rerun tonic if any of these files change - println!("cargo:rerun-if-changed={}", proto.display()); - } - - Ok(()) -} diff --git a/grpc-testtool/proto/merkle/merkle.proto b/grpc-testtool/proto/merkle/merkle.proto deleted file mode 100644 index a894d5376284..000000000000 --- a/grpc-testtool/proto/merkle/merkle.proto +++ /dev/null @@ -1,109 +0,0 @@ -syntax = "proto3"; - -package merkle; - -import "google/protobuf/empty.proto"; - -// Methods on this service return status code NOT_FOUND if a requested -// view, iterator or root hash is not found. -service Merkle { - // --- Proposals --- - rpc NewProposal(NewProposalRequest) returns (NewProposalResponse); - rpc ProposalCommit(ProposalCommitRequest) returns (google.protobuf.Empty); - - // --- Views --- - rpc NewView(NewViewRequest) returns (NewViewResponse); - - // --- Reads --- - // The methods below may be called with an ID that corresponds to either a (committable) proposal - // or (non-committable) historical view. - rpc ViewHas(ViewHasRequest) returns (ViewHasResponse); - rpc ViewGet(ViewGetRequest) returns (ViewGetResponse); - - // --- Iterators --- - rpc ViewNewIteratorWithStartAndPrefix(ViewNewIteratorWithStartAndPrefixRequest) returns (ViewNewIteratorWithStartAndPrefixResponse); - // Returns status code OUT_OF_RANGE when the iterator is done - rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); - rpc IteratorError(IteratorErrorRequest) returns (google.protobuf.Empty); - // Iterator can't be used (even to check error) after release. - rpc IteratorRelease(IteratorReleaseRequest) returns (google.protobuf.Empty); - rpc ViewRelease(ViewReleaseRequest) returns (google.protobuf.Empty); -} - -message NewProposalRequest { - // If not given, the parent view is the current database revision. - optional uint32 parent_id = 1; - repeated PutRequest puts = 2; - // The keys being deleted. - repeated bytes deletes = 3; -} - -message NewProposalResponse { - uint32 id = 1; -} - -message ProposalCommitRequest { - uint32 id = 1; -} - -message NewViewRequest { - bytes root_hash = 1; -} - -message NewViewResponse { - uint32 id = 1; -} - -message ViewHasRequest { - uint32 id = 1; - bytes key = 2; -} - -message ViewHasResponse { - bool has = 1; -} - -message ViewGetRequest { - uint32 id = 1; - bytes key = 2; -} - -message ViewGetResponse { - bytes value = 1; -} - -message ViewNewIteratorWithStartAndPrefixRequest { - uint64 id = 1; - bytes start = 2; - bytes prefix = 3; -} - -message ViewNewIteratorWithStartAndPrefixResponse { - uint64 id = 1; -} - -message IteratorNextRequest { - uint64 id = 1; -} - -message IteratorNextResponse { - PutRequest data = 1; -} - -message IteratorErrorRequest { - uint64 id = 1; -} - -message IteratorReleaseRequest { - uint64 id = 1; -} - -message ViewReleaseRequest { - uint32 id = 1; -} - -// TODO import this from the rpcdb package. -message PutRequest { - bytes key = 1; - bytes value = 2; -} diff --git a/grpc-testtool/proto/process-server/process-server.proto b/grpc-testtool/proto/process-server/process-server.proto deleted file mode 100644 index 6aa8f55d41d4..000000000000 --- a/grpc-testtool/proto/process-server/process-server.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package process; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/ava-labs/merkledb-tester/proto/pb/process"; - -service ProcessServerService { - rpc Metrics(google.protobuf.Empty) returns (MetricsResponse); -} - -message MetricsResponse { - string metrics = 1; -} diff --git a/grpc-testtool/proto/rpcdb/rpcdb.proto b/grpc-testtool/proto/rpcdb/rpcdb.proto deleted file mode 100644 index 420d2b7a89fc..000000000000 --- a/grpc-testtool/proto/rpcdb/rpcdb.proto +++ /dev/null @@ -1,127 +0,0 @@ -syntax = "proto3"; - -package rpcdb; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/rpcdb"; - -service Database { - rpc Has(HasRequest) returns (HasResponse); - rpc Get(GetRequest) returns (GetResponse); - rpc Put(PutRequest) returns (PutResponse); - rpc Delete(DeleteRequest) returns (DeleteResponse); - rpc Compact(CompactRequest) returns (CompactResponse); - rpc Close(CloseRequest) returns (CloseResponse); - rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse); - rpc WriteBatch(WriteBatchRequest) returns (WriteBatchResponse); - rpc NewIteratorWithStartAndPrefix(NewIteratorWithStartAndPrefixRequest) returns (NewIteratorWithStartAndPrefixResponse); - rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); - rpc IteratorError(IteratorErrorRequest) returns (IteratorErrorResponse); - rpc IteratorRelease(IteratorReleaseRequest) returns (IteratorReleaseResponse); -} - -enum Error { - // ERROR_UNSPECIFIED is used to indicate that no error occurred. - ERROR_UNSPECIFIED = 0; - ERROR_CLOSED = 1; - ERROR_NOT_FOUND = 2; -} - -message HasRequest { - bytes key = 1; -} - -message HasResponse { - bool has = 1; - Error err = 2; -} - -message GetRequest { - bytes key = 1; -} - -message GetResponse { - bytes value = 1; - Error err = 2; -} - -message PutRequest { - bytes key = 1; - bytes value = 2; -} - -message PutResponse { - Error err = 1; -} - -message DeleteRequest { - bytes key = 1; -} - -message DeleteResponse { - Error err = 1; -} - -message CompactRequest { - bytes start = 1; - bytes limit = 2; -} - -message CompactResponse { - Error err = 1; -} - -message CloseRequest {} - -message CloseResponse { - Error err = 1; -} - -message WriteBatchRequest { - repeated PutRequest puts = 1; - repeated DeleteRequest deletes = 2; -} - -message WriteBatchResponse { - Error err = 1; -} - -message NewIteratorRequest {} - -message NewIteratorWithStartAndPrefixRequest { - bytes start = 1; - bytes prefix = 2; -} - -message NewIteratorWithStartAndPrefixResponse { - uint64 id = 1; -} - -message IteratorNextRequest { - uint64 id = 1; -} - -message IteratorNextResponse { - repeated PutRequest data = 1; -} - -message IteratorErrorRequest { - uint64 id = 1; -} - -message IteratorErrorResponse { - Error err = 1; -} - -message IteratorReleaseRequest { - uint64 id = 1; -} - -message IteratorReleaseResponse { - Error err = 1; -} - -message HealthCheckResponse { - bytes details = 1; -} diff --git a/grpc-testtool/proto/sync/sync.proto b/grpc-testtool/proto/sync/sync.proto deleted file mode 100644 index e1c1ccd22ec4..000000000000 --- a/grpc-testtool/proto/sync/sync.proto +++ /dev/null @@ -1,166 +0,0 @@ -syntax = "proto3"; - -package sync; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/sync"; - -// Request represents a request for information during syncing. -message Request { - oneof message { - SyncGetRangeProofRequest range_proof_request = 1; - SyncGetChangeProofRequest change_proof_request = 2; - } -} - -// The interface required by an x/sync/SyncManager for syncing. -// Note this service definition only exists for use in tests. -// A database shouldn't expose this over the internet, as it -// allows for reading/writing to the database. -service DB { - rpc GetMerkleRoot(google.protobuf.Empty) returns (GetMerkleRootResponse); - - rpc GetProof(GetProofRequest) returns (GetProofResponse); - - rpc GetChangeProof(GetChangeProofRequest) returns (GetChangeProofResponse); - rpc VerifyChangeProof(VerifyChangeProofRequest) returns (VerifyChangeProofResponse); - rpc CommitChangeProof(CommitChangeProofRequest) returns (google.protobuf.Empty); - - rpc GetRangeProof(GetRangeProofRequest) returns (GetRangeProofResponse); - rpc CommitRangeProof(CommitRangeProofRequest) returns (google.protobuf.Empty); -} - -message GetMerkleRootResponse { - bytes root_hash = 1; -} - -message GetProofRequest { - bytes key = 1; -} - -message GetProofResponse { - Proof proof = 1; -} - -message Proof { - bytes key = 1; - MaybeBytes value = 2; - repeated ProofNode proof = 3; -} - -// For use in sync client, which has a restriction on the size of -// the response. GetChangeProof in the DB service doesn't. -message SyncGetChangeProofRequest { - bytes start_root_hash = 1; - bytes end_root_hash = 2; - MaybeBytes start_key = 3; - MaybeBytes end_key = 4; - uint32 key_limit = 5; - uint32 bytes_limit = 6; -} - -message SyncGetChangeProofResponse { - oneof response { - ChangeProof change_proof = 1; - RangeProof range_proof = 2; - } -} - -message GetChangeProofRequest { - bytes start_root_hash = 1; - bytes end_root_hash = 2; - MaybeBytes start_key = 3; - MaybeBytes end_key = 4; - uint32 key_limit = 5; -} - -message GetChangeProofResponse { - oneof response { - ChangeProof change_proof = 1; - // True iff server errored with merkledb.ErrInsufficientHistory. - bool root_not_present = 2; - } -} - -message VerifyChangeProofRequest { - ChangeProof proof = 1; - MaybeBytes start_key = 2; - MaybeBytes end_key = 3; - bytes expected_root_hash = 4; -} - -message VerifyChangeProofResponse { - // If empty, there was no error. - string error = 1; -} - -message CommitChangeProofRequest { - ChangeProof proof = 1; -} - -// For use in sync client, which has a restriction on the size of -// the response. GetRangeProof in the DB service doesn't. -message SyncGetRangeProofRequest { - bytes root_hash = 1; - MaybeBytes start_key = 2; - MaybeBytes end_key = 3; - uint32 key_limit = 4; - uint32 bytes_limit = 5; -} - -message GetRangeProofRequest { - bytes root_hash = 1; - MaybeBytes start_key = 2; - MaybeBytes end_key = 3; - uint32 key_limit = 4; -} - -message GetRangeProofResponse { - RangeProof proof = 1; -} - -message CommitRangeProofRequest { - MaybeBytes start_key = 1; - RangeProof range_proof = 2; -} - -message ChangeProof { - repeated ProofNode start_proof = 1; - repeated ProofNode end_proof = 2; - repeated KeyChange key_changes = 3; -} - -message RangeProof { - repeated ProofNode start = 1; - repeated ProofNode end = 2; - repeated KeyValue key_values = 3; -} - -message ProofNode { - SerializedPath key = 1; - MaybeBytes value_or_hash = 2; - map children = 3; -} - -message KeyChange { - bytes key = 1; - MaybeBytes value = 2; -} - -message SerializedPath { - uint64 nibble_length = 1; - bytes value = 2; -} - -message MaybeBytes { - bytes value = 1; - // If false, this is None. - // Otherwise this is Some. - bool is_nothing = 2; -} - -message KeyValue { - bytes key = 1; - bytes value = 2; -} diff --git a/grpc-testtool/src/bin/client.rs b/grpc-testtool/src/bin/client.rs deleted file mode 100644 index 2fe2d056c828..000000000000 --- a/grpc-testtool/src/bin/client.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -fn main() { - println!("Hello from {}", file!()); -} diff --git a/grpc-testtool/src/bin/process-server.rs b/grpc-testtool/src/bin/process-server.rs deleted file mode 100644 index d5fe5cd7207d..000000000000 --- a/grpc-testtool/src/bin/process-server.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use chrono::Local; -use clap::Parser; -use env_logger::Builder; -use log::{LevelFilter, info}; -use rpc::DatabaseService; -use rpc::process_server::process_server_service_server::ProcessServerServiceServer; -use rpc::rpcdb::database_server::DatabaseServer as RpcServer; -use rpc::sync::db_server::DbServer as SyncServer; -use serde::Deserialize; -use std::error::Error; -use std::io::Write; -use std::net::IpAddr::V4; -use std::net::Ipv4Addr; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; -use tonic::transport::Server; - -#[derive(Clone, Debug, Deserialize)] -struct Options { - #[serde(default = "Options::history_length_default")] - history_length: u32, -} - -impl Options { - // used in two cases: - // serde deserializes Options and there was no history_length - // OR - // Options was not present - const fn history_length_default() -> u32 { - 100 - } -} - -impl FromStr for Options { - type Err = String; - - fn from_str(s: &str) -> Result { - serde_json::from_str(s).map_err(|e| format!("error parsing options: {}", e)) - } -} - -/// A GRPC server that can be plugged into the generic testing framework for merkledb - -#[derive(Debug, Parser)] -#[command(author, version, about, long_about = None)] -struct Opts { - #[arg(short, long)] - //// Port gRPC server listens on - grpc_port: u16, - - #[arg(short, long)] - db_dir: PathBuf, - - #[arg(short, long, default_value_t = LevelFilter::Info)] - log_level: LevelFilter, - - #[arg(short, long)] - config: Option, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // parse command line options - let args = Opts::parse(); - - // configure the logger - Builder::new() - .format(|buf, record| { - writeln!( - buf, - "{} [{}] - {}", - Local::now().format("%Y-%m-%dT%H:%M:%S"), - record.level(), - record.args() - ) - }) - .format_target(true) - .filter(None, args.log_level) - .init(); - - // tracing_subscriber::fmt::init(); - - // log to the file and to stderr - info!("Starting up: Listening on {}", args.grpc_port); - - let svc = Arc::new( - DatabaseService::new( - args.db_dir, - args.config - .map(|o| o.history_length) - .unwrap_or_else(Options::history_length_default), - ) - .await?, - ); - - // TODO: graceful shutdown - Ok(Server::builder() - .trace_fn(|_m| tracing::debug_span!("process-server")) - .add_service(RpcServer::from_arc(svc.clone())) - .add_service(SyncServer::from_arc(svc.clone())) - .add_service(ProcessServerServiceServer::from_arc(svc.clone())) - .serve(std::net::SocketAddr::new( - V4(Ipv4Addr::LOCALHOST), - args.grpc_port, - )) - .await?) -} diff --git a/grpc-testtool/src/lib.rs b/grpc-testtool/src/lib.rs deleted file mode 100644 index 4d331f2b921b..000000000000 --- a/grpc-testtool/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -#![doc = include_str!("../README.md")] - -pub mod sync { - #![expect(clippy::missing_const_for_fn)] - tonic::include_proto!("sync"); -} - -pub mod rpcdb { - #![expect(clippy::missing_const_for_fn)] - tonic::include_proto!("rpcdb"); -} - -pub mod process_server { - #![expect(clippy::missing_const_for_fn)] - tonic::include_proto!("process"); -} - -pub mod service; - -pub use service::Database as DatabaseService; diff --git a/grpc-testtool/src/service.rs b/grpc-testtool/src/service.rs deleted file mode 100644 index 86b392492b68..000000000000 --- a/grpc-testtool/src/service.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use firewood::db::{Db, DbConfig}; -use firewood::v2::api::{Db as _, Error}; - -use std::collections::HashMap; -use std::ops::Deref; -use std::path::Path; -use std::sync::Arc; -use std::sync::atomic::{AtomicU64, Ordering}; -use tokio::sync::Mutex; -use tonic::Status; - -pub mod database; -pub mod db; -pub mod process; - -trait IntoStatusResultExt { - fn into_status_result(self) -> Result>; -} - -impl IntoStatusResultExt for Result { - // We map errors from bad arguments into Status::invalid_argument; all other errors are Status::internal errors - fn into_status_result(self) -> Result> { - self.map_err(|err| match err { - Error::IncorrectRootHash { .. } | Error::HashNotFound { .. } | Error::RangeTooSmall => { - Box::new(Status::invalid_argument(err.to_string())) - } - Error::IO { .. } | Error::InternalError { .. } => { - Box::new(Status::internal(err.to_string())) - } - _ => Box::new(Status::internal(err.to_string())), - }) - } -} - -#[derive(Debug)] -pub struct Database { - db: Db, - iterators: Arc>, -} - -impl Database { - pub async fn new>(path: P, _history_length: u32) -> Result { - // try to create the parents for this directory, but it's okay if it fails; it will get caught in Db::new - std::fs::create_dir_all(&path).ok(); - // TODO: truncate should be false - // see https://github.com/ava-labs/firewood/issues/418 - let cfg = DbConfig::builder().truncate(true).build(); - - let db = Db::new(path, cfg).await?; - - Ok(Self { - db, - iterators: Default::default(), - }) - } -} - -impl Deref for Database { - type Target = Db; - - fn deref(&self) -> &Self::Target { - &self.db - } -} - -impl Database { - async fn latest(&self) -> Result::Historical>, Error> { - let root_hash = self.root_hash().await?.ok_or(Error::LatestIsEmpty)?; - self.revision(root_hash).await - } -} - -// TODO: implement Iterator -#[derive(Debug)] -struct Iter; - -#[derive(Default, Debug)] -struct Iterators { - map: HashMap, - next_id: AtomicU64, -} - -impl Iterators { - fn insert(&mut self, iter: Iter) -> u64 { - let id = self.next_id.fetch_add(1, Ordering::Relaxed); - self.map.insert(id, iter); - id - } - - fn _get(&self, id: u64) -> Option<&Iter> { - self.map.get(&id) - } - - fn remove(&mut self, id: u64) { - self.map.remove(&id); - } -} - -#[derive(Debug)] -pub struct ProcessServer; diff --git a/grpc-testtool/src/service/database.rs b/grpc-testtool/src/service/database.rs deleted file mode 100644 index 0218cd07a398..000000000000 --- a/grpc-testtool/src/service/database.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::{Database as DatabaseService, IntoStatusResultExt as _, Iter}; -use crate::rpcdb::database_server::Database; -use crate::rpcdb::{ - CloseRequest, CloseResponse, CompactRequest, CompactResponse, DeleteRequest, DeleteResponse, - GetRequest, GetResponse, HasRequest, HasResponse, HealthCheckResponse, IteratorErrorRequest, - IteratorErrorResponse, IteratorNextRequest, IteratorNextResponse, IteratorReleaseRequest, - IteratorReleaseResponse, NewIteratorWithStartAndPrefixRequest, - NewIteratorWithStartAndPrefixResponse, PutRequest, PutResponse, WriteBatchRequest, - WriteBatchResponse, -}; -use firewood::v2::api::{BatchOp, Db as _, DbView as _, Proposal as _}; - -use tonic::{Request, Response, Status, async_trait}; - -#[async_trait] -impl Database for DatabaseService { - async fn has(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.latest().await.into_status_result().map_err(|e| *e)?; - - let val = revision - .val(key) - .await - .into_status_result() - .map_err(|e| *e)?; - - let response = HasResponse { - has: val.is_some(), - ..Default::default() - }; - - Ok(Response::new(response)) - } - - async fn get(&self, request: Request) -> Result, Status> { - let key = request.into_inner().key; - let revision = self.latest().await.into_status_result().map_err(|e| *e)?; - - let value = revision - .val(key) - .await - .into_status_result() - .map_err(|e| *e)? - .map(|v| v.to_vec()); - - let Some(value) = value else { - return Err(Status::not_found("key not found")); - }; - - let response = GetResponse { - value, - ..Default::default() - }; - - Ok(Response::new(response)) - } - - async fn put(&self, request: Request) -> Result, Status> { - let PutRequest { key, value } = request.into_inner(); - let batch = BatchOp::Put { key, value }; - let proposal = self - .db - .propose(vec![batch]) - .await - .into_status_result() - .map_err(|e| *e)?; - let _ = proposal - .commit() - .await - .into_status_result() - .map_err(|e| *e)?; - - Ok(Response::new(PutResponse::default())) - } - - async fn delete( - &self, - request: Request, - ) -> Result, Status> { - let DeleteRequest { key } = request.into_inner(); - let batch = BatchOp::<_, Vec>::Delete { key }; - let proposal = self - .db - .propose(vec![batch]) - .await - .into_status_result() - .map_err(|e| *e)?; - let _ = proposal - .commit() - .await - .into_status_result() - .map_err(|e| *e)?; - - Ok(Response::new(DeleteResponse::default())) - } - - async fn compact( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("compact not implemented")) - } - - async fn close( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("close not implemented")) - } - - async fn health_check( - &self, - _request: Request<()>, - ) -> Result, Status> { - // TODO: why is the response a Vec? - Ok(Response::new(HealthCheckResponse::default())) - } - - async fn write_batch( - &self, - request: Request, - ) -> Result, Status> { - let WriteBatchRequest { puts, deletes } = request.into_inner(); - let batch = puts - .into_iter() - .map(from_put_request) - .chain(deletes.into_iter().map(from_delete_request)) - .collect(); - let proposal = self - .db - .propose(batch) - .await - .into_status_result() - .map_err(|e| *e)?; - let _ = proposal - .commit() - .await - .into_status_result() - .map_err(|e| *e)?; - - Ok(Response::new(WriteBatchResponse::default())) - } - - async fn new_iterator_with_start_and_prefix( - &self, - request: Request, - ) -> Result, Status> { - let NewIteratorWithStartAndPrefixRequest { - start: _, - prefix: _, - } = request.into_inner(); - - // TODO: create the actual iterator - let id = { - let mut iters = self.iterators.lock().await; - iters.insert(Iter) - }; - - Ok(Response::new(NewIteratorWithStartAndPrefixResponse { id })) - } - - async fn iterator_next( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("iterator_next not implemented")) - } - - async fn iterator_error( - &self, - _request: Request, - ) -> Result, Status> { - Err(Status::unimplemented("iterator_error not implemented")) - } - - async fn iterator_release( - &self, - request: Request, - ) -> Result, Status> { - let IteratorReleaseRequest { id } = request.into_inner(); - - { - let mut iters = self.iterators.lock().await; - iters.remove(id); - } - - Ok(Response::new(IteratorReleaseResponse::default())) - } -} - -fn from_put_request(request: PutRequest) -> BatchOp, Vec> { - BatchOp::Put { - key: request.key, - value: request.value, - } -} - -fn from_delete_request(request: DeleteRequest) -> BatchOp, Vec> { - BatchOp::Delete { key: request.key } -} diff --git a/grpc-testtool/src/service/db.rs b/grpc-testtool/src/service/db.rs deleted file mode 100644 index 2f0e28c88de5..000000000000 --- a/grpc-testtool/src/service/db.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use super::Database; -use crate::sync::db_server::Db as DbServerTrait; -use crate::sync::{ - CommitChangeProofRequest, CommitRangeProofRequest, GetChangeProofRequest, - GetChangeProofResponse, GetMerkleRootResponse, GetProofRequest, GetProofResponse, - GetRangeProofRequest, GetRangeProofResponse, VerifyChangeProofRequest, - VerifyChangeProofResponse, -}; -use tonic::{Request, Response, Status, async_trait}; - -#[async_trait] -impl DbServerTrait for Database { - #[tracing::instrument(level = "trace")] - async fn get_merkle_root( - &self, - _request: Request<()>, - ) -> Result, Status> { - todo!() - // let root_hash = self.db.root_hash().await.into_status_result().map_err(|e| *e)?.to_vec(); - // - // let response = GetMerkleRootResponse { root_hash }; - // - // Ok(Response::new(response)) - } - - #[tracing::instrument(level = "trace")] - async fn get_proof( - &self, - _request: Request, - ) -> Result, Status> { - todo!() - // let GetProofRequest { key: _ } = request.into_inner(); - // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; - } - - #[tracing::instrument(level = "trace")] - async fn get_change_proof( - &self, - _request: Request, - ) -> Result, Status> { - todo!() - // let GetChangeProofRequest { - // start_root_hash: _, - // end_root_hash: _, - // start_key: _, - // end_key: _, - // key_limit: _, - // } = request.into_inner(); - - // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; - } - - #[tracing::instrument(level = "trace")] - async fn verify_change_proof( - &self, - _request: Request, - ) -> Result, Status> { - todo!() - // let VerifyChangeProofRequest { - // proof: _, - // start_key: _, - // end_key: _, - // expected_root_hash: _, - // } = request.into_inner(); - - // let _revision = self.latest().await.into_status_result().map_err(|e| *e)?; - } - - #[tracing::instrument(level = "trace")] - async fn commit_change_proof( - &self, - _request: Request, - ) -> Result, Status> { - todo!() - // let CommitChangeProofRequest { proof: _ } = request.into_inner(); - } - - #[tracing::instrument(level = "trace")] - async fn get_range_proof( - &self, - request: Request, - ) -> Result, Status> { - let GetRangeProofRequest { - root_hash: _, - start_key: _, - end_key: _, - key_limit: _, - } = request.into_inner(); - - todo!() - } - - #[tracing::instrument(level = "trace")] - async fn commit_range_proof( - &self, - request: Request, - ) -> Result, Status> { - let CommitRangeProofRequest { - start_key: _, - range_proof: _, - } = request.into_inner(); - - todo!() - } -} diff --git a/grpc-testtool/src/service/process.rs b/grpc-testtool/src/service/process.rs deleted file mode 100644 index 226cc9afbc35..000000000000 --- a/grpc-testtool/src/service/process.rs +++ /dev/null @@ -1,14 +0,0 @@ -use tonic::{Request, Response, Status, async_trait}; - -use crate::process_server::process_server_service_server::ProcessServerService as ProcessTrait; - -use crate::process_server::MetricsResponse; - -#[async_trait] -impl ProcessTrait for super::Database { - async fn metrics(&self, _request: Request<()>) -> Result, Status> { - Err(Status::unimplemented("Metrics not yet supported")) - // TODO: collect the metrics here - // Ok(Response::new(MetricsResponse::default())) - } -} From 7c68d5cb1d00d14ea7428db7c420aa9ecb7c1225 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 12:40:51 -0700 Subject: [PATCH 0916/1053] feat(ffi-refactor): refactor cached view (1/8) (#1222) Because of earlier PRs, we can take advantage of the arc and eagerly drop the lock on the cache after loading it. Depends On: #1220 --- ffi/src/arc_cache.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ ffi/src/handle.rs | 29 ++++++++++++++ ffi/src/lib.rs | 57 ++++++---------------------- 3 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 ffi/src/arc_cache.rs create mode 100644 ffi/src/handle.rs diff --git a/ffi/src/arc_cache.rs b/ffi/src/arc_cache.rs new file mode 100644 index 000000000000..04a4d920a6cc --- /dev/null +++ b/ffi/src/arc_cache.rs @@ -0,0 +1,90 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! A simple single-item cache for database views. +//! +//! This module provides [`ArcCache`], a thread-safe cache that holds at most one +//! key-value pair. It's specifically designed to cache database views in the FFI +//! layer to improve performance by avoiding repeated view creation for the same +//! root hash during database operations. + +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; + +/// A thread-safe single-item cache that stores key-value pairs as `Arc`. +/// +/// This cache is optimized for scenarios where you frequently access the same +/// item and want to avoid expensive recomputation. It holds at most one cached +/// entry and replaces it when a different key is requested. +/// +/// The cache is thread-safe and uses a mutex to protect concurrent access. +/// Values are stored as `Arc` to allow cheap cloning and sharing across +/// threads. +#[derive(Debug)] +pub struct ArcCache { + cache: Mutex)>>, +} + +impl ArcCache { + pub const fn new() -> Self { + ArcCache { + cache: Mutex::new(None), + } + } + + /// Gets the cached value for the given key, or creates and caches a new value. + /// + /// If the cache contains an entry with a key equal to the provided key, + /// returns a clone of the cached `Arc`. Otherwise, calls the factory + /// function to create a new value, caches it, and returns it. + /// + /// # Cache Behavior + /// + /// - Cache hit: Returns the cached value immediately + /// - Cache miss: Clears any existing cache entry, calls factory, caches the result + /// - Factory error: Cache is cleared and the error is propagated + /// + /// # Arguments + /// + /// * `key` - The key to look up or cache + /// * `factory` - A function that creates the value if not cached. It receives + /// a reference to the key as an argument. + /// + /// # Errors + /// + /// Returns any error produced by the factory function. + pub fn get_or_try_insert_with( + &self, + key: K, + factory: impl FnOnce(&K) -> Result, E>, + ) -> Result, E> { + let mut cache = self.lock(); + if let Some((cached_key, value)) = cache.as_ref() { + if *cached_key == key { + return Ok(Arc::clone(value)); + } + } + + // clear the cache before running the factory in case it fails + *cache = None; + + let value = factory(&key)?; + *cache = Some((key, Arc::clone(&value))); + + Ok(value) + } + + /// Clears the cache, removing any stored key-value pair. + pub fn clear(&self) { + self.lock().take(); + } + + fn lock(&self) -> MutexGuard<'_, Option<(K, Arc)>> { + self.cache.lock().unwrap_or_else(PoisonError::into_inner) + } +} + +impl Default for ArcCache { + fn default() -> Self { + Self::new() + } +} diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs new file mode 100644 index 000000000000..83cb81a59705 --- /dev/null +++ b/ffi/src/handle.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood::v2::api::{self, ArcDynDbView, HashKey}; +use metrics::counter; + +use crate::DatabaseHandle; + +impl DatabaseHandle<'_> { + pub(crate) fn get_root(&self, root: HashKey) -> Result { + let mut cache_miss = false; + let view = self.cached_view.get_or_try_insert_with(root, |key| { + cache_miss = true; + self.db.view(HashKey::clone(key)) + })?; + + if cache_miss { + counter!("firewood.ffi.cached_view.miss").increment(1); + } else { + counter!("firewood.ffi.cached_view.hit").increment(1); + } + + Ok(view) + } + + pub(crate) fn clear_cached_view(&self) { + self.cached_view.clear(); + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index f12b010ae878..91ddf5f38e50 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -26,6 +26,8 @@ ) )] +mod arc_cache; +mod handle; mod metrics_setup; mod value; @@ -33,15 +35,16 @@ use std::collections::HashMap; use std::ffi::{CStr, CString, c_char}; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; +use std::sync::RwLock; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{Mutex, RwLock}; use firewood::db::{Db, DbConfig, Proposal}; use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; -use firewood::v2::api::{ArcDynDbView, Db as _, DbView, HashKey, KeyValuePairIter, Proposal as _}; +use firewood::v2::api::{self, Db as _, DbView, HashKey, KeyValuePairIter, Proposal as _}; use metrics::counter; +use crate::arc_cache::ArcCache; pub use crate::value::*; #[cfg(unix)] @@ -72,7 +75,7 @@ pub struct DatabaseHandle<'p> { proposals: RwLock>>, /// A single cached view to improve performance of reads while committing - cached_view: Mutex>, + cached_view: ArcCache, /// The database db: Db, @@ -83,20 +86,11 @@ impl From for DatabaseHandle<'_> { Self { db, proposals: RwLock::new(HashMap::new()), - cached_view: Mutex::new(None), + cached_view: ArcCache::new(), } } } -impl DatabaseHandle<'_> { - fn clear_cached_view(&self) { - self.cached_view - .lock() - .expect("cached_view lock is poisoned") - .take(); - } -} - impl Deref for DatabaseHandle<'_> { type Target = Db; @@ -245,34 +239,10 @@ fn get_from_root( ) -> Result { let db = db.ok_or("db should be non-null")?; let requested_root = HashKey::try_from(root).map_err(|e| e.to_string())?; - let mut cached_view = db.cached_view.lock().expect("cached_view lock is poisoned"); - let value = match cached_view.as_ref() { - // found the cached view, use it - Some((root_hash, view)) if root_hash == &requested_root => { - counter!("firewood.ffi.cached_view.hit").increment(1); - view.val(key) - } - // If what was there didn't match the requested root, we need a new view, so we - // update the cache - _ => { - counter!("firewood.ffi.cached_view.miss").increment(1); - let rev = view_sync_from_root(db, root)?; - let result = rev.val(key); - *cached_view = Some((requested_root.clone(), rev)); - result - } - } - .map_err(|e| e.to_string())? - .ok_or("")?; - + let cached_view = db.get_root(requested_root).map_err(|e| e.to_string())?; + let value = cached_view.val(key).map_err(|e| e.to_string())?.ok_or("")?; Ok(value.into()) } -fn view_sync_from_root(db: &DatabaseHandle<'_>, root: &[u8]) -> Result { - let rev = db - .view(HashKey::try_from(root).map_err(|e| e.to_string())?) - .map_err(|e| e.to_string())?; - Ok(rev) -} /// Puts the given key-value pairs into the database. /// @@ -508,13 +478,8 @@ fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), Strin // Get the proposal hash and cache the view. We never cache an empty proposal. let proposal_hash = proposal.root_hash(); - if let Ok(Some(proposal_hash)) = proposal_hash { - let mut guard = db.cached_view.lock().expect("cached_view lock is poisoned"); - match db.view(proposal_hash.clone()) { - Ok(view) => *guard = Some((proposal_hash, view)), - Err(_) => *guard = None, // Clear cache on error - } - drop(guard); + if let Ok(Some(ref hash_key)) = proposal_hash { + _ = db.get_root(hash_key.clone()); } // Commit the proposal From 1d6a74f0275407cb46d874969644b01c3673817d Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 12:46:42 -0700 Subject: [PATCH 0917/1053] feat(ffi-refactor): add OwnedSlice and OwnedBytes (2/8) (#1223) This is similar to the BorrowedBytes from #1174 but for bytes returns by Rust back to Go. This does not actually use the new type yet as there are too many places that need to be updated. However, this introduces the new type and it's free method so that we can start using it in the future. Depends On: #1222 --- ffi/firewood.h | 32 ++++++++ ffi/memory.go | 139 +++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 17 +++++ ffi/src/value.rs | 2 + ffi/src/value/owned.rs | 161 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+) create mode 100644 ffi/src/value/owned.rs diff --git a/ffi/firewood.h b/ffi/firewood.h index 458aab3271ec..8311e6ab7f25 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -131,6 +131,22 @@ typedef struct DatabaseCreationResult { uint8_t *error_str; } DatabaseCreationResult; +/** + * A Rust-owned vector of bytes that can be passed to C code. + * + * C callers must free this memory using the respective FFI function for the + * concrete type (but not using the `free` function from the C standard library). + */ +typedef struct OwnedSlice_u8 { + uint8_t *ptr; + size_t len; +} OwnedSlice_u8; + +/** + * A type alias for a rust-owned byte slice. + */ +typedef struct OwnedSlice_u8 OwnedBytes; + typedef uint32_t ProposalId; /** @@ -278,6 +294,22 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa */ void fwd_free_database_error_result(struct DatabaseCreationResult *result); +/** + * Consumes the [`OwnedBytes`] and frees the memory associated with it. + * + * # Arguments + * + * * `bytes` - The [`OwnedBytes`] struct to free, previously returned from any + * function from this library. + * + * # Safety + * + * The caller must ensure that the `bytes` struct is valid and that the memory + * it points to is uniquely owned by this object. However, if `bytes.ptr` is null, + * this function does nothing. + */ +void fwd_free_owned_bytes(OwnedBytes bytes); + /** * Frees the memory associated with a `Value`. * diff --git a/ffi/memory.go b/ffi/memory.go index 13c40a2a0c68..208bffafe3af 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -22,6 +22,7 @@ var ( errNilStruct = errors.New("nil struct pointer cannot be freed") errBadValue = errors.New("value from cgo formatted incorrectly") errKeysAndValues = errors.New("keys and values must have the same length") + errFreeingValue = errors.New("unexpected error while freeing value") ) type Pinner interface { @@ -29,6 +30,38 @@ type Pinner interface { Unpin() } +// Borrower is an interface for types that can borrow or copy bytes returned +// from FFI methods. +type Borrower interface { + // BorrowedBytes returns a slice of bytes that borrows the data from the + // Borrower's internal memory. + // + // The returned slice is valid only as long as the Borrower is valid. + // If the Borrower is freed, the slice will become invalid. + BorrowedBytes() []byte + + // CopiedBytes returns a slice of bytes that is a copy of the Borrower's + // internal memory. + // + // This is fully independent of the borrowed data and is valid even after + // the Borrower is freed. + CopiedBytes() []byte + + // Free releases the memory associated with the Borrower's data. + // + // It is safe to call this method multiple times. Subsequent calls will + // do nothing if the data has already been freed (or was never set). + // + // However, it is not safe to call this method concurrently from multiple + // goroutines. It is also not safe to call this method while there are + // outstanding references to the slice returned by BorrowedBytes. Any + // existing slices will become invalid and may cause undefined behavior + // if used after the Free call. + Free() error +} + +var _ Borrower = (*ownedBytes)(nil) + // newBorrowedBytes creates a new BorrowedBytes from a Go byte slice. // // Provide a Pinner to ensure the memory is pinned while the BorrowedBytes is in use. @@ -103,6 +136,112 @@ func newKeyValuePairs(keys, vals [][]byte, pinner Pinner) (C.BorrowedKeyValuePai return newBorrowedKeyValuePairs(pairs, pinner), nil } +// ownedBytes is a wrapper around C.OwnedBytes that provides a Go interface +// for Rust-owned byte slices. +// +// ownedBytes implements the [Borrower] interface allowing it to be shared +// outside of the FFI package without exposing the C types directly or any FFI +// implementation details. +type ownedBytes struct { + owned C.OwnedBytes +} + +// Free releases the memory associated with the Borrower's data. +// +// It is safe to call this method multiple times. Subsequent calls will +// do nothing if the data has already been freed (or was never set). +// +// However, it is not safe to call this method concurrently from multiple +// goroutines. It is also not safe to call this method while there are +// outstanding references to the slice returned by BorrowedBytes. Any +// existing slices will become invalid and may cause undefined behavior +// if used after the Free call. +func (b *ownedBytes) Free() error { + if b.owned.ptr == nil { + // Already freed (or never set), nothing to do. + return nil + } + + // TODO: a check for panic will be inserted here + C.fwd_free_owned_bytes(b.owned) + + // reset the pointer to nil to prevent double freeing + b.owned = C.OwnedBytes{} + + return nil +} + +// BorrowedBytes returns the underlying byte slice. It may return nil if the +// data has already been freed was never set. +// +// The returned slice is valid only as long as the ownedBytes is valid. +// +// It does not copy the data; however, the slice is valid only as long as the +// ownedBytes is valid. If the ownedBytes is freed, the slice will +// become invalid. +// +// It is safe to cast the returned slice as a string so long as the ownedBytes +// is not freed while the string is in use. +// +// BorrowedBytes is part of the [Borrower] interface. +func (b *ownedBytes) BorrowedBytes() []byte { + if b.owned.ptr == nil { + return nil + } + + return unsafe.Slice((*byte)(b.owned.ptr), b.owned.len) +} + +// CopiedBytes returns a copy of the underlying byte slice. It may return nil if the +// data has already been freed or was never set. +// +// The returned slice is a copy of the data and is valid independently of the +// ownedBytes. It is safe to use after the ownedBytes is freed and will +// be freed by the Go garbage collector. +// +// CopiedBytes is part of the [Borrower] interface. +func (b *ownedBytes) CopiedBytes() []byte { + if b.owned.ptr == nil { + return nil + } + + return C.GoBytes(unsafe.Pointer(b.owned.ptr), C.int(b.owned.len)) +} + +// intoError converts the ownedBytes into an error. This is used for methods +// that return a ownedBytes as an error type. +// +// If the ownedBytes is nil or has already been freed, it returns nil. +// Otherwise, the bytes will be copied into Go memory and converted into an +// error. +// +// The original ownedBytes will be freed after this operation and is no longer +// valid. +func (b *ownedBytes) intoError() error { + if b.owned.ptr == nil { + return nil + } + + err := errors.New(string(b.CopiedBytes())) + + if err2 := b.Free(); err2 != nil { + return fmt.Errorf("%w: %w (original error: %w)", errFreeingValue, err, err2) + } + + return err +} + +// newOwnedBytes creates a ownedBytes from a C.OwnedBytes. +// +// The caller is responsible for calling Free() on the returned ownedBytes +// when it is no longer needed otherwise memory will leak. +// +// It is not an error to provide an OwnedBytes with a nil pointer or zero length +// in which case the returned ownedBytes will be empty. +func newOwnedBytes(owned C.OwnedBytes) *ownedBytes { + return &ownedBytes{owned: owned} +} + // hashAndIDFromValue converts the cgo `Value` payload into: // // case | data | len | meaning diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 91ddf5f38e50..e2ca2a1de75d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -990,6 +990,23 @@ pub unsafe extern "C" fn fwd_close_db(db: Option<&mut DatabaseHandle>) { // The database handle will be dropped automatically when db_handle goes out of scope } +/// Consumes the [`OwnedBytes`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `bytes` - The [`OwnedBytes`] struct to free, previously returned from any +/// function from this library. +/// +/// # Safety +/// +/// The caller must ensure that the `bytes` struct is valid and that the memory +/// it points to is uniquely owned by this object. However, if `bytes.ptr` is null, +/// this function does nothing. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) { + drop(bytes); +} + #[cfg(test)] mod tests { #![expect(clippy::unwrap_used)] diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 89477c579070..0686c281ff0d 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -4,7 +4,9 @@ mod borrowed; mod display_hex; mod kvp; +mod owned; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::kvp::KeyValuePair; +pub use self::owned::{OwnedBytes, OwnedSlice}; diff --git a/ffi/src/value/owned.rs b/ffi/src/value/owned.rs new file mode 100644 index 000000000000..caa2c7cd7c44 --- /dev/null +++ b/ffi/src/value/owned.rs @@ -0,0 +1,161 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::{fmt, ptr::NonNull}; + +/// A type alias for a rust-owned byte slice. +pub type OwnedBytes = OwnedSlice; + +/// A Rust-owned vector of bytes that can be passed to C code. +/// +/// C callers must free this memory using the respective FFI function for the +/// concrete type (but not using the `free` function from the C standard library). +#[derive(Debug)] +#[repr(C)] +pub struct OwnedSlice { + ptr: Option>, + len: usize, +} + +impl OwnedSlice { + /// Dereferences the pointer to the owned slice, returning a slice of the contained type. + #[must_use] + pub const fn as_slice(&self) -> &[T] { + match self.ptr { + // SAFETY: if the pointer is not null, we are assuming the caller has not changed + // it from when we originally created the `OwnedSlice`. + Some(ptr) => unsafe { std::slice::from_raw_parts(ptr.as_ptr(), self.len) }, + None => &[], + } + } + + /// Returns a mutable slice of the owned slice, allowing modification of the contained type. + #[must_use] + pub const fn as_mut_slice(&mut self) -> &mut [T] { + match self.ptr { + // SAFETY: if the pointer is not null, we are assuming the caller has not changed + // it from when we originally created the `OwnedSlice`. + Some(ptr) => unsafe { std::slice::from_raw_parts_mut(ptr.as_ptr(), self.len) }, + None => &mut [], + } + } + + /// Transforms the owned slice into a boxed slice, consuming the original. + #[must_use] + pub fn into_boxed_slice(self) -> Box<[T]> { + self.into() + } + + fn take_box(&mut self) -> Box<[T]> { + match self.ptr.take() { + // SAFETY: if the pointer is not null, we are assuming the caller has not changed + // it from when we originally created the `OwnedSlice`. The owned slice was created + // from a `Box::leak`, so we can safely convert it back using `Box::from_raw`. + Some(ptr) => unsafe { + Box::from_raw(std::slice::from_raw_parts_mut(ptr.as_ptr(), self.len)) + }, + None => Box::new([]), + } + } +} + +impl Clone for OwnedSlice { + fn clone(&self) -> Self { + self.as_slice().to_vec().into() + } +} + +impl AsRef<[T]> for OwnedSlice { + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl AsMut<[T]> for OwnedSlice { + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl std::borrow::Borrow<[T]> for OwnedSlice { + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl std::borrow::BorrowMut<[T]> for OwnedSlice { + fn borrow_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl std::hash::Hash for OwnedSlice { + fn hash(&self, state: &mut H) { + self.as_slice().hash(state); + } +} + +impl PartialEq for OwnedSlice { + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Eq for OwnedSlice {} + +impl PartialOrd for OwnedSlice { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl Ord for OwnedSlice { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl From> for OwnedSlice { + fn from(data: Box<[T]>) -> Self { + let len = data.len(); + let ptr = NonNull::from(Box::leak(data)).cast::(); + Self { + ptr: Some(ptr), + len, + } + } +} + +impl From> for OwnedSlice { + fn from(data: Vec) -> Self { + data.into_boxed_slice().into() + } +} + +impl From> for Box<[T]> { + fn from(mut owned: OwnedSlice) -> Self { + owned.take_box() + } +} + +impl Drop for OwnedSlice { + fn drop(&mut self) { + drop(self.take_box()); + } +} + +impl fmt::Display for OwnedBytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + super::DisplayHex(self.as_slice()).fmt(f) + } +} + +// send/sync rules for owned values apply here: if the value is send, the pointer +// is send. if the value is sync, the pointer is sync. This is because we own the +// value and whether or not the pointer can traverse threads is determined by +// the value itself. + +// SAFETY: described above +unsafe impl Send for OwnedSlice {} +// SAFETY: described above +unsafe impl Sync for OwnedSlice {} From ce8c225a04279a436a1d76581ef06036b492136a Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 12:54:16 -0700 Subject: [PATCH 0918/1053] feat(ffi-refactor): Introduce VoidResult and panic handlers (3/8) (#1224) As part of the larger refactor, this adds a `VoidResult` type to the FFI library that will be returned on methods that previously returned `void` (or unit). Additionally added are two traits used by `invoke` and `invoke_with_handle` to automatically handle the common null pointer error and capture any panics from rust. These traits are extensively used in upcoming pull requests. Fixes: #1128 Depends On: #1223 --- ffi/Cargo.toml | 1 - ffi/firewood.go | 11 -- ffi/firewood.h | 139 ++++++++++++++++------ ffi/firewood_test.go | 3 +- ffi/memory.go | 69 +++++++++-- ffi/metrics.go | 13 +-- ffi/metrics_test.go | 2 +- ffi/src/lib.rs | 241 ++++++++++++++++++--------------------- ffi/src/logging.rs | 123 ++++++++++++++++++++ ffi/src/metrics_setup.rs | 16 +++ ffi/src/value.rs | 3 + ffi/src/value/results.rs | 129 +++++++++++++++++++++ 12 files changed, 550 insertions(+), 200 deletions(-) create mode 100644 ffi/src/logging.rs create mode 100644 ffi/src/value/results.rs diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 73963a581342..624fa636a3ca 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,7 +23,6 @@ crate-type = ["staticlib"] # Workspace dependencies coarsetime.workspace = true firewood.workspace = true -log.workspace = true metrics.workspace = true metrics-util.workspace = true # Regular dependencies diff --git a/ffi/firewood.go b/ffi/firewood.go index b45e7baecf5d..f81d93aab394 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -231,14 +231,3 @@ func (db *Database) Root() ([]byte, error) { func (db *Database) Revision(root []byte) (*Revision, error) { return newRevision(db.handle, root) } - -// Close closes the database and releases all held resources. -// Returns an error if already closed. -func (db *Database) Close() error { - if db.handle == nil { - return errDBClosed - } - C.fwd_close_db(db.handle) - db.handle = nil - return nil -} diff --git a/ffi/firewood.h b/ffi/firewood.h index 8311e6ab7f25..65d9306c2be4 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -123,14 +123,6 @@ typedef struct BorrowedSlice_KeyValuePair { */ typedef struct BorrowedSlice_KeyValuePair BorrowedKeyValuePairs; -/** - * Struct returned by `fwd_create_db` and `fwd_open_db` - */ -typedef struct DatabaseCreationResult { - struct DatabaseHandle *db; - uint8_t *error_str; -} DatabaseCreationResult; - /** * A Rust-owned vector of bytes that can be passed to C code. * @@ -147,6 +139,48 @@ typedef struct OwnedSlice_u8 { */ typedef struct OwnedSlice_u8 OwnedBytes; +/** + * The result type returned from an FFI function that returns no value but may + * return an error. + */ +typedef enum VoidResult_Tag { + /** + * The caller provided a null pointer to the input handle. + */ + VoidResult_NullHandlePointer, + /** + * The operation was successful and no error occurred. + */ + VoidResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. Its + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + VoidResult_Err, +} VoidResult_Tag; + +typedef struct VoidResult { + VoidResult_Tag tag; + union { + struct { + OwnedBytes err; + }; + }; +} VoidResult; + +/** + * Struct returned by `fwd_create_db` and `fwd_open_db` + */ +typedef struct DatabaseCreationResult { + struct DatabaseHandle *db; + uint8_t *error_str; +} DatabaseCreationResult; + typedef uint32_t ProposalId; /** @@ -172,14 +206,24 @@ typedef struct CreateOrOpenArgs { } CreateOrOpenArgs; /** - * Arguments for logging - * - * * `path` - The file path where logs for this process are stored. By - * default, this is set to /tmp/firewood-log.txt - * * `filter_level` - The filter level for logs. By default, this is set to info. + * Arguments for initializing logging for the Firewood FFI. */ typedef struct LogArgs { + /** + * The file path where logs for this process are stored. + * + * If empty, this is set to `${TMPDIR}/firewood-log.txt`. + * + * This is required to be a valid UTF-8 string. + */ BorrowedBytes path; + /** + * The filter level for logs. + * + * If empty, this is set to `info`. + * + * This is required to be a valid UTF-8 string. + */ BorrowedBytes filter_level; } LogArgs; @@ -218,23 +262,24 @@ struct Value fwd_batch(const struct DatabaseHandle *db, /** * Close and free the memory for a database handle * - * # Safety + * # Arguments * - * This function uses raw pointers so it is unsafe. - * It is the caller's responsibility to ensure that the database handle is valid. - * Using the db after calling this function is undefined behavior + * * `db` - The database handle to close, previously returned from a call to [`fwd_open_db`]. * - * # Arguments + * # Returns * - * * `db` - The database handle to close, previously returned from a call to `open_db()` + * - [`VoidResult::NullHandlePointer`] if the provided database handle is null. + * - [`VoidResult::Ok`] if the database handle was successfully closed and freed. + * - [`VoidResult::Err`] if the process panics while closing the database handle. * - * # Panics + * # Safety * - * This function panics if: - * * `db` is `None` (null pointer) - * * A lock is poisoned + * Callers must ensure that: + * + * - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. + * - The database handle is not used after this function is called. */ -void fwd_close_db(struct DatabaseHandle *db); +struct VoidResult fwd_close_db(struct DatabaseHandle *db); /** * Commits a proposal to the database. @@ -292,7 +337,7 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa * This function panics if `result` is `null`. * */ -void fwd_free_database_error_result(struct DatabaseCreationResult *result); +struct VoidResult fwd_free_database_error_result(struct DatabaseCreationResult *result); /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. @@ -302,13 +347,18 @@ void fwd_free_database_error_result(struct DatabaseCreationResult *result); * * `bytes` - The [`OwnedBytes`] struct to free, previously returned from any * function from this library. * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * * # Safety * * The caller must ensure that the `bytes` struct is valid and that the memory * it points to is uniquely owned by this object. However, if `bytes.ptr` is null, * this function does nothing. */ -void fwd_free_owned_bytes(OwnedBytes bytes); +struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); /** * Frees the memory associated with a `Value`. @@ -327,7 +377,7 @@ void fwd_free_owned_bytes(OwnedBytes bytes); * This function panics if `value` is `null`. * */ -void fwd_free_value(struct Value *value); +struct VoidResult fwd_free_value(struct Value *value); /** * Gather latest metrics for this process. @@ -522,34 +572,47 @@ struct Value fwd_root_hash(const struct DatabaseHandle *db); * * # Arguments * - * See `LogArgs`. + * See [`LogArgs`]. * * # Returns * - * A `Value` containing {0, null} if the global logger was initialized. - * A `Value` containing {0, "error message"} if an error occurs. + * - [`VoidResult::Ok`] if the recorder was initialized. + * - [`VoidResult::Err`] if an error occurs during initialization. + * + * # Safety + * + * The caller must: + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error (if any). */ -struct Value fwd_start_logs(struct LogArgs args); +struct VoidResult fwd_start_logs(struct LogArgs args); /** * Start metrics recorder for this process. * * # Returns * - * A `Value` containing {0, null} if the metrics recorder was initialized. - * A `Value` containing {0, "error message"} if an error occurs. + * - [`VoidResult::Ok`] if the recorder was initialized. + * - [`VoidResult::Err`] if an error occurs during initialization. */ -struct Value fwd_start_metrics(void); +struct VoidResult fwd_start_metrics(void); /** * Start metrics recorder and exporter for this process. * + * # Arguments + * * * `metrics_port` - the port where metrics will be exposed at * * # Returns * - * A `Value` containing {0, null} if the metrics recorder was initialized and - * the exporter was started. - * A `Value` containing {0, "error message"} if an error occurs. + * - [`VoidResult::Ok`] if the recorder was initialized. + * - [`VoidResult::Err`] if an error occurs during initialization. + * + * # Safety + * + * The caller must: + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error (if any). */ -struct Value fwd_start_metrics_with_exporter(uint16_t metrics_port); +struct VoidResult fwd_start_metrics_with_exporter(uint16_t metrics_port); diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 99c5b2608160..af2a8c8cda39 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -226,8 +226,7 @@ func TestClosedDatabase(t *testing.T) { r.Empty(root) r.ErrorIs(err, errDBClosed) - err = db.Close() - r.ErrorIs(err, errDBClosed) + r.NoError(db.Close()) } func keyForTest(i int) []byte { diff --git a/ffi/memory.go b/ffi/memory.go index 208bffafe3af..4ac074ec8b01 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -136,6 +136,25 @@ func newKeyValuePairs(keys, vals [][]byte, pinner Pinner) (C.BorrowedKeyValuePai return newBorrowedKeyValuePairs(pairs, pinner), nil } +// Close releases the memory associated with the Database. +// +// This is safe to call if the pointer is nil, in which case it does nothing. The +// pointer will be set to nil after freeing to prevent double free. However, it is +// not safe to call this method concurrently from multiple goroutines. +func (db *Database) Close() error { + if db.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { + return fmt.Errorf("unexpected error when closing database: %w", err) + } + + db.handle = nil // Prevent double free + + return nil +} + // ownedBytes is a wrapper around C.OwnedBytes that provides a Go interface // for Rust-owned byte slices. // @@ -162,10 +181,10 @@ func (b *ownedBytes) Free() error { return nil } - // TODO: a check for panic will be inserted here - C.fwd_free_owned_bytes(b.owned) + if err := getErrorFromVoidResult(C.fwd_free_owned_bytes(b.owned)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } - // reset the pointer to nil to prevent double freeing b.owned = C.OwnedBytes{} return nil @@ -242,6 +261,22 @@ func newOwnedBytes(owned C.OwnedBytes) *ownedBytes { return &ownedBytes{owned: owned} } +// getErrorgetErrorFromVoidResult converts a C.VoidResult to an error. +// +// It will return nil if the result is Ok, otherwise it returns an error. +func getErrorFromVoidResult(result C.VoidResult) error { + switch result.tag { + case C.VoidResult_NullHandlePointer: + return errDBClosed + case C.VoidResult_Ok: + return nil + case C.VoidResult_Err: + return newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + default: + return fmt.Errorf("unknown C.VoidResult tag: %d", result.tag) + } +} + // hashAndIDFromValue converts the cgo `Value` payload into: // // case | data | len | meaning @@ -273,7 +308,9 @@ func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { // Case 3 if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return nil, 0, fmt.Errorf("%w: %w", errFreeingValue, err) + } return nil, 0, errors.New(errStr) } @@ -281,7 +318,9 @@ func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { id := uint32(v.len) buf := C.GoBytes(unsafe.Pointer(v.data), RootLength) v.len = C.size_t(RootLength) // set the length to free - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return nil, 0, fmt.Errorf("%w: %w", errFreeingValue, err) + } return buf, id, nil } @@ -311,12 +350,16 @@ func errorFromValue(v *C.struct_Value) error { // Case 3 if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } return errors.New(errStr) } // Case 2 and 4 - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } return errBadValue } @@ -341,7 +384,9 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { // Case 4 if v.len != 0 && v.data != nil { buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } return buf, nil } @@ -353,7 +398,9 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { // Case 3 if v.len == 0 { errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - C.fwd_free_value(v) + if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { + return nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } return nil, errors.New(errStr) } @@ -368,7 +415,9 @@ func databaseFromResult(result *C.struct_DatabaseCreationResult) (*C.DatabaseHan if result.error_str != nil { errStr := C.GoString((*C.char)(unsafe.Pointer(result.error_str))) - C.fwd_free_database_error_result(result) + if err := getErrorFromVoidResult(C.fwd_free_database_error_result(result)); err != nil { + return nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } runtime.KeepAlive(result) return nil, errors.New(errStr) } diff --git a/ffi/metrics.go b/ffi/metrics.go index d8ace1d19e0c..064d45bef121 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -50,8 +50,7 @@ func (Gatherer) Gather() ([]*dto.MetricFamily, error) { // An error is returned if this method is called a second time, or if it is // called after StartMetricsWithExporter. func StartMetrics() error { - result := C.fwd_start_metrics() - return errorFromValue(&result) + return getErrorFromVoidResult(C.fwd_start_metrics()) } // Start global recorder for metrics along with an HTTP exporter. @@ -59,8 +58,7 @@ func StartMetrics() error { // An error is returned if this method is called a second time, if it is // called after StartMetrics, or if the exporter failed to start. func StartMetricsWithExporter(metricsPort uint16) error { - result := C.fwd_start_metrics_with_exporter(C.uint16_t(metricsPort)) - return errorFromValue(&result) + return getErrorFromVoidResult(C.fwd_start_metrics_with_exporter(C.uint16_t(metricsPort))) } // Collect metrics from global recorder @@ -88,9 +86,10 @@ func StartLogs(config *LogConfig) error { var pinner runtime.Pinner defer pinner.Unpin() - result := C.fwd_start_logs(C.struct_LogArgs{ + args := C.struct_LogArgs{ path: newBorrowedBytes([]byte(config.Path), &pinner), filter_level: newBorrowedBytes([]byte(config.FilterLevel), &pinner), - }) - return errorFromValue(&result) + } + + return getErrorFromVoidResult(C.fwd_start_logs(args)) } diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go index dc054b5efc23..de3df004234f 100644 --- a/ffi/metrics_test.go +++ b/ffi/metrics_test.go @@ -40,7 +40,7 @@ func TestMetrics(t *testing.T) { var logsDisabled bool if err := StartLogs(logConfig); err != nil { - r.Contains(err.Error(), "logger feature is disabled") + r.Contains(err.Error(), "Logging is not available") logsDisabled = true } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e2ca2a1de75d..5536b5e25ecb 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -28,6 +28,7 @@ mod arc_cache; mod handle; +mod logging; mod metrics_setup; mod value; @@ -45,6 +46,7 @@ use firewood::v2::api::{self, Db as _, DbView, HashKey, KeyValuePairIter, Propos use metrics::counter; use crate::arc_cache::ArcCache; +pub use crate::logging::*; pub use crate::value::*; #[cfg(unix)] @@ -63,6 +65,36 @@ fn next_id() -> ProposalId { ID_COUNTER.fetch_add(1, Ordering::Relaxed) } +/// Invokes a closure and returns the result as a [`CResult`]. +/// +/// If the closure panics, it will return [`CResult::from_panic`] with the panic +/// information. +#[inline] +fn invoke>(once: impl FnOnce() -> V) -> T { + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(once)) { + Ok(result) => result.into(), + Err(panic) => T::from_panic(panic), + } +} + +/// Invokes a closure that requires a handle and returns the result as a [`NullHandleResult`]. +/// +/// If the provided handle is [`None`], the function will return early with the +/// [`NullHandleResult::null_handle_pointer_error`] result. +/// +/// Otherwise, the closure is invoked with the handle. If the closure panics, +/// it will be caught and returned as a [`CResult::from_panic`]. +#[inline] +fn invoke_with_handle>( + handle: Option, + once: impl FnOnce(H) -> V, +) -> T { + match handle { + Some(handle) => invoke(move || once(handle)), + None => T::null_handle_pointer_error(), + } +} + /// A handle to the database, returned by `fwd_create_db` and `fwd_open_db`. /// /// These handles are passed to the other FFI functions. @@ -669,21 +701,22 @@ impl From<()> for Value { /// This function panics if `value` is `null`. /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_value(value: Option<&mut Value>) { - let value = value.expect("value should be non-null"); - if let Some(data) = value.data { - let data_ptr = data.as_ptr(); - // We assume that if the length is 0, then the data is a null-terminated string. - if value.len > 0 { - let recreated_box = - unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data_ptr, value.len)) }; - drop(recreated_box); - } else { - let raw_str = data_ptr.cast::(); - let cstr = unsafe { CString::from_raw(raw_str) }; - drop(cstr); +pub unsafe extern "C" fn fwd_free_value(value: Option<&mut Value>) -> VoidResult { + invoke_with_handle(value, |value| { + if let Some(data) = value.data { + let data_ptr = data.as_ptr(); + // We assume that if the length is 0, then the data is a null-terminated string. + if value.len > 0 { + let recreated_box = + unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data_ptr, value.len)) }; + drop(recreated_box); + } else { + let raw_str = data_ptr.cast::(); + let cstr = unsafe { CString::from_raw(raw_str) }; + drop(cstr); + } } - } + }) } /// Struct returned by `fwd_create_db` and `fwd_open_db` @@ -731,44 +764,48 @@ impl From> for DatabaseCreationResult { #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_free_database_error_result( result: Option<&mut DatabaseCreationResult>, -) { - let result = result.expect("result should be non-null"); - // Free the error string if it exists - if let Some(nonnull) = result.error_str { - let raw_str = nonnull.cast::().as_ptr(); - let cstr = unsafe { CString::from_raw(raw_str) }; - drop(cstr); - } - // Note: we don't free the db pointer as it's managed by the caller +) -> VoidResult { + invoke_with_handle(result, |result| { + // Free the error string if it exists + if let Some(nonnull) = result.error_str { + let raw_str = nonnull.cast::().as_ptr(); + let cstr = unsafe { CString::from_raw(raw_str) }; + drop(cstr); + } + // Note: we don't free the db pointer as it's managed by the caller + }) } /// Start metrics recorder for this process. /// /// # Returns /// -/// A `Value` containing {0, null} if the metrics recorder was initialized. -/// A `Value` containing {0, "error message"} if an error occurs. +/// - [`VoidResult::Ok`] if the recorder was initialized. +/// - [`VoidResult::Err`] if an error occurs during initialization. #[unsafe(no_mangle)] -pub extern "C" fn fwd_start_metrics() -> Value { - metrics_setup::setup_metrics() - .map_err(|e| e.to_string()) - .map_or_else(Into::into, Into::into) +pub extern "C" fn fwd_start_metrics() -> VoidResult { + invoke(metrics_setup::setup_metrics) } /// Start metrics recorder and exporter for this process. /// +/// # Arguments +/// /// * `metrics_port` - the port where metrics will be exposed at /// /// # Returns /// -/// A `Value` containing {0, null} if the metrics recorder was initialized and -/// the exporter was started. -/// A `Value` containing {0, "error message"} if an error occurs. +/// - [`VoidResult::Ok`] if the recorder was initialized. +/// - [`VoidResult::Err`] if an error occurs during initialization. +/// +/// # Safety +/// +/// The caller must: +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error (if any). #[unsafe(no_mangle)] -pub extern "C" fn fwd_start_metrics_with_exporter(metrics_port: u16) -> Value { - metrics_setup::setup_metrics_with_exporter(metrics_port) - .map_err(|e| e.to_string()) - .map_or_else(Into::into, Into::into) +pub extern "C" fn fwd_start_metrics_with_exporter(metrics_port: u16) -> VoidResult { + invoke(move || metrics_setup::setup_metrics_with_exporter(metrics_port)) } /// Gather latest metrics for this process. @@ -848,83 +885,25 @@ unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { Db::new(path, cfg).map_err(|e| e.to_string()) } -/// Arguments for logging -/// -/// * `path` - The file path where logs for this process are stored. By -/// default, this is set to /tmp/firewood-log.txt -/// * `filter_level` - The filter level for logs. By default, this is set to info. -#[repr(C)] -pub struct LogArgs<'a> { - path: BorrowedBytes<'a>, - filter_level: BorrowedBytes<'a>, -} - /// Start logs for this process. /// /// # Arguments /// -/// See `LogArgs`. +/// See [`LogArgs`]. /// /// # Returns /// -/// A `Value` containing {0, null} if the global logger was initialized. -/// A `Value` containing {0, "error message"} if an error occurs. +/// - [`VoidResult::Ok`] if the recorder was initialized. +/// - [`VoidResult::Err`] if an error occurs during initialization. +/// +/// # Safety +/// +/// The caller must: +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error (if any). #[unsafe(no_mangle)] -pub extern "C" fn fwd_start_logs(args: LogArgs<'_>) -> Value { - start_logs(&args).map_or_else(Into::into, Into::into) -} - -#[cfg(feature = "logger")] -#[doc(hidden)] -fn start_logs(log_args: &LogArgs) -> Result<(), String> { - use env_logger::Target::Pipe; - use std::fs::OpenOptions; - use std::path::Path; - - let log_path = log_args - .path - .as_str() - .map_err(|e| format!("Invalid log path: {e}"))?; - let log_path = if log_path.is_empty() { - std::borrow::Cow::Owned(std::env::temp_dir().join("firewood-log.txt")) - } else { - std::borrow::Cow::Borrowed(std::path::Path::new(log_path)) - }; - - let log_dir = log_path.parent().unwrap_or_else(|| Path::new(".")); - std::fs::create_dir_all(log_dir).map_err(|e| e.to_string())?; - - let level = if log_args.filter_level.is_empty() { - "info" - } else { - log_args - .filter_level - .as_str() - .map_err(|e| format!("Invalid log level: {e}"))? - } - .parse::() - .map_err(|e| format!("failed to parse log level: {e}"))?; - - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(false) - .open(log_path) - .map_err(|e| e.to_string())?; - - env_logger::Builder::new() - .filter_level(level) - .target(Pipe(Box::new(file))) - .try_init() - .map_err(|e| e.to_string())?; - - Ok(()) -} - -#[cfg(not(feature = "logger"))] -#[doc(hidden)] -fn start_logs(_log_args: &LogArgs) -> Result<(), String> { - Err(String::from("logger feature is disabled")) +pub extern "C" fn fwd_start_logs(args: LogArgs) -> VoidResult { + invoke(move || args.start_logging()) } #[doc(hidden)] @@ -959,35 +938,32 @@ fn manager_config( /// Close and free the memory for a database handle /// -/// # Safety +/// # Arguments /// -/// This function uses raw pointers so it is unsafe. -/// It is the caller's responsibility to ensure that the database handle is valid. -/// Using the db after calling this function is undefined behavior +/// * `db` - The database handle to close, previously returned from a call to [`fwd_open_db`]. /// -/// # Arguments +/// # Returns /// -/// * `db` - The database handle to close, previously returned from a call to `open_db()` +/// - [`VoidResult::NullHandlePointer`] if the provided database handle is null. +/// - [`VoidResult::Ok`] if the database handle was successfully closed and freed. +/// - [`VoidResult::Err`] if the process panics while closing the database handle. /// -/// # Panics +/// # Safety +/// +/// Callers must ensure that: /// -/// This function panics if: -/// * `db` is `None` (null pointer) -/// * A lock is poisoned +/// - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. +/// - The database handle is not used after this function is called. +#[expect(clippy::missing_panics_doc, reason = "panics are captured")] #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_close_db(db: Option<&mut DatabaseHandle>) { - let db_handle = db.expect("db should be non-null"); - - // Explicitly clear the downstream items. Drop will do these in order, so this - // code is defensive in case someone reorders the struct memebers of DatabaseHandle. - db_handle - .proposals - .write() - .expect("proposals lock is poisoned") - .clear(); - db_handle.clear_cached_view(); - - // The database handle will be dropped automatically when db_handle goes out of scope +pub unsafe extern "C" fn fwd_close_db(db: Option>>) -> VoidResult { + invoke_with_handle(db, |db| { + db.proposals + .write() + .expect("proposals lock is poisoned") + .clear(); + db.clear_cached_view(); + }) } /// Consumes the [`OwnedBytes`] and frees the memory associated with it. @@ -997,14 +973,19 @@ pub unsafe extern "C" fn fwd_close_db(db: Option<&mut DatabaseHandle>) { /// * `bytes` - The [`OwnedBytes`] struct to free, previously returned from any /// function from this library. /// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// /// # Safety /// /// The caller must ensure that the `bytes` struct is valid and that the memory /// it points to is uniquely owned by this object. However, if `bytes.ptr` is null, /// this function does nothing. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) { - drop(bytes); +pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { + invoke(move || drop(bytes)) } #[cfg(test)] diff --git a/ffi/src/logging.rs b/ffi/src/logging.rs new file mode 100644 index 000000000000..4b41da932eb8 --- /dev/null +++ b/ffi/src/logging.rs @@ -0,0 +1,123 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::BorrowedBytes; + +/// Arguments for initializing logging for the Firewood FFI. +#[repr(C)] +pub struct LogArgs<'a> { + /// The file path where logs for this process are stored. + /// + /// If empty, this is set to `${TMPDIR}/firewood-log.txt`. + /// + /// This is required to be a valid UTF-8 string. + pub path: BorrowedBytes<'a>, + + /// The filter level for logs. + /// + /// If empty, this is set to `info`. + /// + /// This is required to be a valid UTF-8 string. + pub filter_level: BorrowedBytes<'a>, +} + +#[cfg(feature = "logger")] +impl LogArgs<'_> { + fn path(&self) -> std::io::Result> { + let path = self.path.as_str().map_err(|err| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("log path contains invalid utf-8: {err}"), + ) + })?; + if path.is_empty() { + Ok(std::borrow::Cow::Owned( + std::env::temp_dir().join("firewood-log.txt"), + )) + } else { + Ok(std::borrow::Cow::Borrowed(std::path::Path::new(path))) + } + } + + fn log_level(&self) -> std::io::Result<&str> { + let level = self.filter_level.as_str().map_err(|err| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("log level contains invalid utf-8: {err}"), + ) + })?; + if level.is_empty() { + Ok("info") + } else { + Ok(level) + } + } + + /// Starts logging to the specified file path with the given filter level. + /// + /// # Errors + /// + /// If the log file cannot be created or opened, or if the log level is invalid, + /// this will return an error. + pub fn start_logging(&self) -> std::io::Result<()> { + use env_logger::Target::Pipe; + use std::fs::OpenOptions; + + let log_path = self.path()?; + + if let Some(log_dir) = log_path.parent() { + std::fs::create_dir_all(log_dir).map_err(|e| { + std::io::Error::new( + e.kind(), + format!( + "failed to create log directory `{}`: {e}", + log_dir.display() + ), + ) + })?; + } + + let level = self.log_level()?; + let level = level.parse().map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("invalid log level `{level}`: {e}"), + ) + })?; + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(false) + .open(&log_path) + .map_err(|e| { + std::io::Error::new( + e.kind(), + format!("failed to open log file `{}`: {e}", log_path.display()), + ) + })?; + + env_logger::Builder::new() + .filter_level(level) + .target(Pipe(Box::new(file))) + .try_init() + .map_err(|e| std::io::Error::other(format!("failed to initialize logger: {e}")))?; + + Ok(()) + } +} + +#[cfg(not(feature = "logger"))] +impl LogArgs<'_> { + /// Starts logging to the specified file path with the given filter level. + /// + /// # Errors + /// + /// This method will always return an error because the `logger` feature is not enabled. + pub fn start_logging(&self) -> std::io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "firewood-ffi was compiled without the `logger` feature. Logging is not available.", + )) + } +} diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 2dc79101f551..43cd29ad7c57 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -81,12 +81,28 @@ pub fn gather_metrics() -> Result { Ok(recorder.stats()) } +/// Internal data structure for the [`TextRecorder`] containing the metrics registry and help text. +/// +/// This structure holds: +/// - A metrics registry that stores all counter and gauge values with atomic storage +/// - A mutex-protected map of help text for each metric name +/// +/// The atomic storage ensures thread-safe updates to metric values, while the mutex +/// protects the help text map during concurrent access. #[derive(Debug)] struct TextRecorderInner { registry: Registry, help: Mutex>, } +/// A metrics recorder that outputs metrics in Prometheus text exposition format. +/// +/// This recorder implements the [`metrics::Recorder`] trait and formats all +/// collected metrics as text in the format expected by Prometheus. It supports +/// counters and gauges, but not histograms. +/// +/// The recorder is thread-safe and can be shared across multiple threads. +/// All metrics are stored in memory and can be retrieved via [`Self::stats`]. #[derive(Debug, Clone)] struct TextRecorder { inner: Arc, diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 0686c281ff0d..87eebd4fd92c 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -5,8 +5,11 @@ mod borrowed; mod display_hex; mod kvp; mod owned; +mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; +pub use self::results::VoidResult; +pub(crate) use self::results::{CResult, NullHandleResult}; diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs new file mode 100644 index 000000000000..a7e39d26f991 --- /dev/null +++ b/ffi/src/value/results.rs @@ -0,0 +1,129 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt; + +use crate::OwnedBytes; + +/// The result type returned from an FFI function that returns no value but may +/// return an error. +#[derive(Debug)] +#[repr(C)] +pub enum VoidResult { + /// The caller provided a null pointer to the input handle. + NullHandlePointer, + + /// The operation was successful and no error occurred. + Ok, + + /// An error occurred and the message is returned as an [`OwnedBytes`]. Its + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From<()> for VoidResult { + fn from((): ()) -> Self { + VoidResult::Ok + } +} + +impl From> for VoidResult { + fn from(value: Result<(), E>) -> Self { + match value { + Ok(()) => VoidResult::Ok, + Err(err) => VoidResult::Err(err.to_string().into_bytes().into()), + } + } +} + +/// Helper trait to handle the different result types returned from FFI functions. +/// +/// Once Try trait is stable, we can use that instead of this trait: +/// +/// ```ignore +/// impl std::ops::FromResidual> for VoidResult { +/// #[inline] +/// fn from_residual(residual: Option) -> Self { +/// match residual { +/// None => VoidResult::NullHandlePointer, +/// // no other branches are needed because `std::convert::Infallible` is uninhabited +/// // this compiles without error because the compiler knows that Some(_) is impossible +/// // see: https://github.com/rust-lang/rust/blob/3fb1b53a9dbfcdf37a4b67d35cde373316829930/library/core/src/option.rs#L2627-L2631 +/// // and: https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types +/// } +/// } +/// } +/// ``` +pub(crate) trait NullHandleResult: CResult { + fn null_handle_pointer_error() -> Self; +} + +pub(crate) trait CResult: Sized { + fn from_err(err: impl ToString) -> Self; + + fn from_panic(panic: Box) -> Self + where + Self: Sized, + { + Self::from_err(Panic::from(panic)) + } +} + +impl NullHandleResult for VoidResult { + fn null_handle_pointer_error() -> Self { + Self::NullHandlePointer + } +} + +impl CResult for VoidResult { + fn from_err(err: impl ToString) -> Self { + Self::Err(err.to_string().into_bytes().into()) + } +} + +enum Panic { + Static(&'static str), + Formatted(String), + SendSyncErr(Box), + SendErr(Box), + Unknown(#[expect(unused)] Box), + // TODO: add variant to capture backtrace with panic hook + // https://doc.rust-lang.org/stable/std/panic/fn.set_hook.html +} + +impl From> for Panic { + fn from(panic: Box) -> Self { + macro_rules! downcast { + ($Variant:ident($panic:ident)) => { + let $panic = match $panic.downcast() { + Ok(panic) => return Panic::$Variant(*panic), + Err(panic) => panic, + }; + }; + } + + downcast!(Static(panic)); + downcast!(Formatted(panic)); + downcast!(SendSyncErr(panic)); + downcast!(SendErr(panic)); + + Self::Unknown(panic) + } +} + +impl fmt::Display for Panic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Panic::Static(msg) => f.pad(msg), + Panic::Formatted(msg) => f.pad(msg), + Panic::SendSyncErr(err) => err.fmt(f), + Panic::SendErr(err) => err.fmt(f), + Panic::Unknown(_) => f.pad("unknown panic type recovered"), + } + } +} From ecbb6802432a360aa7202cc3a252919645f9fc9c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 13:03:59 -0700 Subject: [PATCH 0919/1053] feat(ffi-refactor): Refactor Db opening to use new Result structure (4/8) (#1225) Depends On: #1224 --- ffi/firewood.go | 10 +-- ffi/firewood.h | 134 +++++++++++++++++++++------------ ffi/memory.go | 33 ++++---- ffi/src/handle.rs | 102 ++++++++++++++++++++++++- ffi/src/lib.rs | 158 ++++----------------------------------- ffi/src/value.rs | 2 +- ffi/src/value/results.rs | 38 ++++++++++ 7 files changed, 256 insertions(+), 221 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index f81d93aab394..51b6b2fe046c 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -108,7 +108,7 @@ func New(filePath string, conf *Config) (*Database, error) { var pinner runtime.Pinner defer pinner.Unpin() - args := C.struct_CreateOrOpenArgs{ + args := C.struct_DatabaseHandleArgs{ path: newBorrowedBytes([]byte(filePath), &pinner), cache_size: C.size_t(conf.NodeCacheEntries), free_list_cache_size: C.size_t(conf.FreeListCacheEntries), @@ -117,13 +117,7 @@ func New(filePath string, conf *Config) (*Database, error) { truncate: C.bool(conf.Truncate), } - dbResult := C.fwd_open_db(args) - db, err := databaseFromResult(&dbResult) - if err != nil { - return nil, err - } - - return &Database{handle: db}, nil + return getDatabaseFromHandleResult(C.fwd_open_db(args)) } // Update applies a batch of updates to the database, returning the hash of the diff --git a/ffi/firewood.h b/ffi/firewood.h index 65d9306c2be4..b72584fe5241 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -12,7 +12,7 @@ /** - * A handle to the database, returned by `fwd_create_db` and `fwd_open_db`. + * A handle to the database, returned by `fwd_open_db`. * * These handles are passed to the other FFI functions. * @@ -173,37 +173,93 @@ typedef struct VoidResult { }; } VoidResult; +typedef uint32_t ProposalId; + /** - * Struct returned by `fwd_create_db` and `fwd_open_db` + * The result type returned from the open or create database functions. */ -typedef struct DatabaseCreationResult { - struct DatabaseHandle *db; - uint8_t *error_str; -} DatabaseCreationResult; +typedef enum HandleResult_Tag { + /** + * The database was opened or created successfully and the handle is + * returned as an opaque pointer. + * + * The caller must ensure that [`fwd_close_db`] is called to free resources + * associated with this handle when it is no longer needed. + * + * [`fwd_close_db`]: crate::fwd_close_db + */ + HandleResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + HandleResult_Err, +} HandleResult_Tag; -typedef uint32_t ProposalId; +typedef struct HandleResult { + HandleResult_Tag tag; + union { + struct { + struct DatabaseHandle *ok; + }; + struct { + OwnedBytes err; + }; + }; +} HandleResult; /** - * Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. - * - * * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` - * otherwise should exist if passed to `fwd_open_db()`. - * * `cache_size` - The size of the node cache, returns an error if <= 0 - * * `free_list_cache_size` - The size of the free list cache, returns an error if <= 0 - * * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. - * * `strategy` - The cache read strategy to use, 0 for writes only, - * 1 for branch reads, and 2 for all reads. - * * `truncate` - Whether to truncate the database file if it exists. - * Returns an error if the value is not 0, 1, or 2. + * Arguments for creating or opening a database. These are passed to [`fwd_open_db`] + * + * [`fwd_open_db`]: crate::fwd_open_db */ -typedef struct CreateOrOpenArgs { +typedef struct DatabaseHandleArgs { + /** + * The path to the database file. + * + * This must be a valid UTF-8 string, even on Windows. + * + * If this is empty, an error will be returned. + */ BorrowedBytes path; + /** + * The size of the node cache. + * + * Opening returns an error if this is zero. + */ size_t cache_size; + /** + * The size of the free list cache. + * + * Opening returns an error if this is zero. + */ size_t free_list_cache_size; + /** + * The maximum number of revisions to keep. + */ size_t revisions; + /** + * The cache read strategy to use. + * + * This must be one of the following: + * + * - `0`: No cache. + * - `1`: Cache only branch reads. + * - `2`: Cache all reads. + * + * Opening returns an error if this is not one of the above values. + */ uint8_t strategy; + /** + * Whether to truncate the database file if it exists. + */ bool truncate; -} CreateOrOpenArgs; +} DatabaseHandleArgs; /** * Arguments for initializing logging for the Firewood FFI. @@ -319,26 +375,6 @@ struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); */ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposal_id); -/** - * Frees the memory associated with a `DatabaseCreationResult`. - * This only needs to be called if the `error_str` field is non-null. - * - * # Arguments - * - * * `result` - The `DatabaseCreationResult` to free, previously returned from `fwd_create_db` or `fwd_open_db`. - * - * # Safety - * - * This function is unsafe because it dereferences raw pointers. - * The caller must ensure that `result` is a valid pointer. - * - * # Panics - * - * This function panics if `result` is `null`. - * - */ -struct VoidResult fwd_free_database_error_result(struct DatabaseCreationResult *result); - /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. * @@ -471,25 +507,25 @@ struct Value fwd_get_from_root(const struct DatabaseHandle *db, struct Value fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); /** - * Open a database with the given cache size and maximum number of revisions + * Open a database with the given arguments. * * # Arguments * - * See `CreateOrOpenArgs`. + * See [`DatabaseHandleArgs`]. * * # Returns * - * A database handle, or panics if it cannot be created + * - [`HandleResult::Ok`] with the database handle if successful. + * - [`HandleResult::Err`] if an error occurs while opening the database. * * # Safety * - * This function uses raw pointers so it is unsafe. - * It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. - * The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. - * The caller must call `close` to free the memory associated with the returned database handle. - * + * The caller must: + * - ensure that the database is freed with [`fwd_close_db`] when no longer needed. + * - ensure that the database handle is freed only after freeing or committing + * all proposals created on it. */ -struct DatabaseCreationResult fwd_open_db(struct CreateOrOpenArgs args); +struct HandleResult fwd_open_db(struct DatabaseHandleArgs args); /** * Proposes a batch of operations to the database. diff --git a/ffi/memory.go b/ffi/memory.go index 4ac074ec8b01..5b1bd11d355a 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -277,6 +277,23 @@ func getErrorFromVoidResult(result C.VoidResult) error { } } +// getDatabaseFromHandleResult converts a C.HandleResult to a Database or error. +// +// If the C.HandleResult is an error, it returns an error instead of a Database. +func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) { + switch result.tag { + case C.HandleResult_Ok: + ptr := *(**C.DatabaseHandle)(unsafe.Pointer(&result.anon0)) + db := &Database{handle: ptr} + return db, nil + case C.HandleResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.HandleResult tag: %d", result.tag) + } +} + // hashAndIDFromValue converts the cgo `Value` payload into: // // case | data | len | meaning @@ -407,19 +424,3 @@ func bytesFromValue(v *C.struct_Value) ([]byte, error) { // Case 2 return nil, errBadValue } - -func databaseFromResult(result *C.struct_DatabaseCreationResult) (*C.DatabaseHandle, error) { - if result == nil { - return nil, errNilStruct - } - - if result.error_str != nil { - errStr := C.GoString((*C.char)(unsafe.Pointer(result.error_str))) - if err := getErrorFromVoidResult(C.fwd_free_database_error_result(result)); err != nil { - return nil, fmt.Errorf("%w: %w", errFreeingValue, err) - } - runtime.KeepAlive(result) - return nil, errors.New(errStr) - } - return result.db, nil -} diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 83cb81a59705..83e5df2cd438 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -1,12 +1,106 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, ArcDynDbView, HashKey}; +use firewood::{ + db::{Db, DbConfig}, + manager::RevisionManagerConfig, + v2::api::{self, ArcDynDbView, HashKey}, +}; use metrics::counter; -use crate::DatabaseHandle; +use crate::{BorrowedBytes, DatabaseHandle}; + +/// Arguments for creating or opening a database. These are passed to [`fwd_open_db`] +/// +/// [`fwd_open_db`]: crate::fwd_open_db +#[repr(C)] +#[derive(Debug)] +pub struct DatabaseHandleArgs<'a> { + /// The path to the database file. + /// + /// This must be a valid UTF-8 string, even on Windows. + /// + /// If this is empty, an error will be returned. + pub path: BorrowedBytes<'a>, + + /// The size of the node cache. + /// + /// Opening returns an error if this is zero. + pub cache_size: usize, + + /// The size of the free list cache. + /// + /// Opening returns an error if this is zero. + pub free_list_cache_size: usize, + + /// The maximum number of revisions to keep. + pub revisions: usize, + + /// The cache read strategy to use. + /// + /// This must be one of the following: + /// + /// - `0`: No cache. + /// - `1`: Cache only branch reads. + /// - `2`: Cache all reads. + /// + /// Opening returns an error if this is not one of the above values. + pub strategy: u8, + + /// Whether to truncate the database file if it exists. + pub truncate: bool, +} + +impl DatabaseHandleArgs<'_> { + fn as_rev_manager_config(&self) -> Result { + let cache_read_strategy = match self.strategy { + 0 => firewood::manager::CacheReadStrategy::WritesOnly, + 1 => firewood::manager::CacheReadStrategy::BranchReads, + 2 => firewood::manager::CacheReadStrategy::All, + _ => return Err(invalid_data("invalid cache strategy")), + }; + let config = RevisionManagerConfig::builder() + .node_cache_size( + self.cache_size + .try_into() + .map_err(|_| invalid_data("cache size should be non-zero"))?, + ) + .max_revisions(self.revisions) + .cache_read_strategy(cache_read_strategy) + .free_list_cache_size( + self.free_list_cache_size + .try_into() + .map_err(|_| invalid_data("free list cache size should be non-zero"))?, + ) + .build(); + Ok(config) + } +} impl DatabaseHandle<'_> { + /// Creates a new database handle from the given arguments. + /// + /// # Errors + /// + /// If the path is empty, or if the configuration is invalid, this will return an error. + pub fn new(args: DatabaseHandleArgs<'_>) -> Result { + let cfg = DbConfig::builder() + .truncate(args.truncate) + .manager(args.as_rev_manager_config()?) + .build(); + + let path = args + .path + .as_str() + .map_err(|err| invalid_data(format!("database path contains invalid utf-8: {err}")))?; + + if path.is_empty() { + return Err(invalid_data("database path cannot be empty")); + } + + Db::new(path, cfg).map(Self::from) + } + pub(crate) fn get_root(&self, root: HashKey) -> Result { let mut cache_miss = false; let view = self.cached_view.get_or_try_insert_with(root, |key| { @@ -27,3 +121,7 @@ impl DatabaseHandle<'_> { self.cached_view.clear(); } } + +fn invalid_data(error: impl Into>) -> api::Error { + api::Error::IO(std::io::Error::new(std::io::ErrorKind::InvalidData, error)) +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 5536b5e25ecb..b36c7e352a7c 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -39,13 +39,12 @@ use std::ops::Deref; use std::sync::RwLock; use std::sync::atomic::{AtomicU32, Ordering}; -use firewood::db::{Db, DbConfig, Proposal}; -use firewood::manager::{CacheReadStrategy, RevisionManagerConfig}; - +use firewood::db::{Db, Proposal}; use firewood::v2::api::{self, Db as _, DbView, HashKey, KeyValuePairIter, Proposal as _}; use metrics::counter; use crate::arc_cache::ArcCache; +pub use crate::handle::*; pub use crate::logging::*; pub use crate::value::*; @@ -95,7 +94,7 @@ fn invoke_with_handle>( } } -/// A handle to the database, returned by `fwd_create_db` and `fwd_open_db`. +/// A handle to the database, returned by `fwd_open_db`. /// /// These handles are passed to the other FFI functions. /// @@ -719,63 +718,6 @@ pub unsafe extern "C" fn fwd_free_value(value: Option<&mut Value>) -> VoidResult }) } -/// Struct returned by `fwd_create_db` and `fwd_open_db` -#[derive(Debug)] -#[repr(C)] -pub struct DatabaseCreationResult { - pub db: Option>>, - pub error_str: Option>, -} - -impl From> for DatabaseCreationResult { - fn from(result: Result) -> Self { - match result { - Ok(db) => DatabaseCreationResult { - db: Some(Box::new(db.into())), - error_str: None, - }, - Err(error_msg) => { - let error_cstring = CString::new(error_msg).unwrap_or_default().into_raw(); - DatabaseCreationResult { - db: None, - error_str: std::ptr::NonNull::new(error_cstring.cast::()), - } - } - } - } -} - -/// Frees the memory associated with a `DatabaseCreationResult`. -/// This only needs to be called if the `error_str` field is non-null. -/// -/// # Arguments -/// -/// * `result` - The `DatabaseCreationResult` to free, previously returned from `fwd_create_db` or `fwd_open_db`. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointers. -/// The caller must ensure that `result` is a valid pointer. -/// -/// # Panics -/// -/// This function panics if `result` is `null`. -/// -#[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_database_error_result( - result: Option<&mut DatabaseCreationResult>, -) -> VoidResult { - invoke_with_handle(result, |result| { - // Free the error string if it exists - if let Some(nonnull) = result.error_str { - let raw_str = nonnull.cast::().as_ptr(); - let cstr = unsafe { CString::from_raw(raw_str) }; - drop(cstr); - } - // Note: we don't free the db pointer as it's managed by the caller - }) -} - /// Start metrics recorder for this process. /// /// # Returns @@ -819,70 +761,26 @@ pub extern "C" fn fwd_gather() -> Value { metrics_setup::gather_metrics().map_or_else(Into::into, |s| s.as_bytes().into()) } -/// Common arguments, accepted by both `fwd_create_db()` and `fwd_open_db()`. -/// -/// * `path` - The path to the database file, which will be truncated if passed to `fwd_create_db()` -/// otherwise should exist if passed to `fwd_open_db()`. -/// * `cache_size` - The size of the node cache, returns an error if <= 0 -/// * `free_list_cache_size` - The size of the free list cache, returns an error if <= 0 -/// * `revisions` - The maximum number of revisions to keep; firewood currently requires this to be at least 2. -/// * `strategy` - The cache read strategy to use, 0 for writes only, -/// 1 for branch reads, and 2 for all reads. -/// * `truncate` - Whether to truncate the database file if it exists. -/// Returns an error if the value is not 0, 1, or 2. -#[repr(C)] -pub struct CreateOrOpenArgs<'a> { - path: BorrowedBytes<'a>, - cache_size: usize, - free_list_cache_size: usize, - revisions: usize, - strategy: u8, - truncate: bool, -} - -/// Open a database with the given cache size and maximum number of revisions +/// Open a database with the given arguments. /// /// # Arguments /// -/// See `CreateOrOpenArgs`. +/// See [`DatabaseHandleArgs`]. /// /// # Returns /// -/// A database handle, or panics if it cannot be created +/// - [`HandleResult::Ok`] with the database handle if successful. +/// - [`HandleResult::Err`] if an error occurs while opening the database. /// /// # Safety /// -/// This function uses raw pointers so it is unsafe. -/// It is the caller's responsibility to ensure that path is a valid pointer to a null-terminated string. -/// The caller must also ensure that the cache size is greater than 0 and that the number of revisions is at least 2. -/// The caller must call `close` to free the memory associated with the returned database handle. -/// +/// The caller must: +/// - ensure that the database is freed with [`fwd_close_db`] when no longer needed. +/// - ensure that the database handle is freed only after freeing or committing +/// all proposals created on it. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_open_db(args: CreateOrOpenArgs) -> DatabaseCreationResult { - unsafe { open_db(&args) }.into() -} - -/// Internal call for `fwd_open_db` to remove error handling from the C API -#[doc(hidden)] -unsafe fn open_db(args: &CreateOrOpenArgs) -> Result { - let cfg = DbConfig::builder() - .truncate(args.truncate) - .manager(manager_config( - args.cache_size, - args.free_list_cache_size, - args.revisions, - args.strategy, - )?) - .build(); - - if args.path.is_empty() { - return Err("path should not be empty".to_string()); - } - let path = args - .path - .as_str() - .map_err(|e| format!("Invalid database path: {e}"))?; - Db::new(path, cfg).map_err(|e| e.to_string()) +pub unsafe extern "C" fn fwd_open_db(args: DatabaseHandleArgs) -> HandleResult { + invoke(move || DatabaseHandle::new(args)) } /// Start logs for this process. @@ -906,36 +804,6 @@ pub extern "C" fn fwd_start_logs(args: LogArgs) -> VoidResult { invoke(move || args.start_logging()) } -#[doc(hidden)] -fn manager_config( - cache_size: usize, - free_list_cache_size: usize, - revisions: usize, - strategy: u8, -) -> Result { - let cache_read_strategy = match strategy { - 0 => CacheReadStrategy::WritesOnly, - 1 => CacheReadStrategy::BranchReads, - 2 => CacheReadStrategy::All, - _ => return Err("invalid cache strategy".to_string()), - }; - let config = RevisionManagerConfig::builder() - .node_cache_size( - cache_size - .try_into() - .map_err(|_| "cache size should be non-zero")?, - ) - .max_revisions(revisions) - .cache_read_strategy(cache_read_strategy) - .free_list_cache_size( - free_list_cache_size - .try_into() - .map_err(|_| "free list cache size should be non-zero")?, - ) - .build(); - Ok(config) -} - /// Close and free the memory for a database handle /// /// # Arguments diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 87eebd4fd92c..12c64d5bdd14 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -11,5 +11,5 @@ pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; -pub use self::results::VoidResult; pub(crate) use self::results::{CResult, NullHandleResult}; +pub use self::results::{HandleResult, VoidResult}; diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index a7e39d26f991..6f800daf9719 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -41,6 +41,38 @@ impl From> for VoidResult { } } +/// The result type returned from the open or create database functions. +#[derive(Debug)] +#[repr(C)] +pub enum HandleResult { + /// The database was opened or created successfully and the handle is + /// returned as an opaque pointer. + /// + /// The caller must ensure that [`fwd_close_db`] is called to free resources + /// associated with this handle when it is no longer needed. + /// + /// [`fwd_close_db`]: crate::fwd_close_db + Ok(Box>), + + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From, E>> for HandleResult { + fn from(value: Result, E>) -> Self { + match value { + Ok(handle) => HandleResult::Ok(Box::new(handle)), + Err(err) => HandleResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// Helper trait to handle the different result types returned from FFI functions. /// /// Once Try trait is stable, we can use that instead of this trait: @@ -86,6 +118,12 @@ impl CResult for VoidResult { } } +impl CResult for HandleResult { + fn from_err(err: impl ToString) -> Self { + Self::Err(err.to_string().into_bytes().into()) + } +} + enum Panic { Static(&'static str), Formatted(String), From 364689ae1b95fd7feb6972ec468d533841db5af3 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 13:24:31 -0700 Subject: [PATCH 0920/1053] feat(ffi-refactor): refactor how hash values are returned (5/8) (#1226) This way we avoid allocating just to return the hash value. A 32-byte hash value is the same size as 4 words and the allocated buffer would be returning 2 words. If we skip the heap allocation, we can return all 4 words on the stack directly and avoid the heap allocation (on the rust side). The Go side later allocates for the slice, but this could be removed later if the interface was build around a fixed-size 32 byte array. Depends On: #1225 --- ffi/firewood.go | 9 +- ffi/firewood.h | 171 +++++++++++++++++++++++++------------- ffi/memory.go | 23 +++++ ffi/proposal.go | 4 +- ffi/src/handle.rs | 71 +++++++++++++++- ffi/src/lib.rs | 141 ++++++++----------------------- ffi/src/value.rs | 4 +- ffi/src/value/hash_key.rs | 38 +++++++++ ffi/src/value/results.rs | 50 ++++++++++- 9 files changed, 333 insertions(+), 178 deletions(-) create mode 100644 ffi/src/value/hash_key.rs diff --git a/ffi/firewood.go b/ffi/firewood.go index 51b6b2fe046c..743fe6bc31ef 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -36,7 +36,7 @@ import ( // These constants are used to identify errors returned by the Firewood Rust FFI. // These must be changed if the Rust FFI changes - should be reported by tests. const ( - RootLength = 32 + RootLength = C.sizeof_HashKey rootHashNotFound = "IO error: Root hash not found" ) @@ -138,8 +138,7 @@ func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { return nil, err } - hash := C.fwd_batch(db.handle, kvp) - return bytesFromValue(&hash) + return getHashKeyFromHashResult(C.fwd_batch(db.handle, kvp)) } func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { @@ -211,8 +210,8 @@ func (db *Database) Root() ([]byte, error) { if db.handle == nil { return nil, errDBClosed } - hash := C.fwd_root_hash(db.handle) - bytes, err := bytesFromValue(&hash) + + bytes, err := getHashKeyFromHashResult(C.fwd_root_hash(db.handle)) // If the root hash is not found, return a zeroed slice. if err == nil && bytes == nil { diff --git a/ffi/firewood.h b/ffi/firewood.h index b72584fe5241..bf927adfc0f5 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -20,24 +20,78 @@ typedef struct DatabaseHandle DatabaseHandle; /** - * A value returned by the FFI. - * - * This is used in several different ways, including: - * * An C-style string. - * * An ID for a proposal. - * * A byte slice containing data. - * - * For more details on how the data may be stored, refer to the function signature - * that returned it or the `From` implementations. - * - * The data stored in this struct (if `data` is not null) must be manually freed - * by the caller using `fwd_free_value`. + * A database hash key, used in FFI functions that require hashes. + * This type requires no allocation and can be copied freely and + * dropped without any additional overhead. + * + * This is useful because it is the same size as 4 words which is equivalent + * to 2 heap-allocated slices (pointer + length each), or 1.5 vectors (which + * uses an extra word for allocation capacity) and it can be passed around + * without needing to allocate or deallocate memory. + */ +typedef struct HashKey { + uint8_t _0[32]; +} HashKey; + +/** + * A Rust-owned vector of bytes that can be passed to C code. * + * C callers must free this memory using the respective FFI function for the + * concrete type (but not using the `free` function from the C standard library). */ -typedef struct Value { +typedef struct OwnedSlice_u8 { + uint8_t *ptr; size_t len; - uint8_t *data; -} Value; +} OwnedSlice_u8; + +/** + * A type alias for a rust-owned byte slice. + */ +typedef struct OwnedSlice_u8 OwnedBytes; + +/** + * A result type returned from FFI functions return the database root hash. This + * may or may not be after a mutation. + */ +typedef enum HashResult_Tag { + /** + * The caller provided a null pointer to a database handle. + */ + HashResult_NullHandlePointer, + /** + * The proposal resulted in an empty database or the database currently has + * no root hash. + */ + HashResult_None, + /** + * The mutation was successful and the root hash is returned, if this result + * was from a mutation. Otherwise, this is the current root hash of the + * database. + */ + HashResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + HashResult_Err, +} HashResult_Tag; + +typedef struct HashResult { + HashResult_Tag tag; + union { + struct { + struct HashKey some; + }; + struct { + OwnedBytes err; + }; + }; +} HashResult; /** * A borrowed byte slice. Used to represent data that was passed in from C @@ -123,22 +177,6 @@ typedef struct BorrowedSlice_KeyValuePair { */ typedef struct BorrowedSlice_KeyValuePair BorrowedKeyValuePairs; -/** - * A Rust-owned vector of bytes that can be passed to C code. - * - * C callers must free this memory using the respective FFI function for the - * concrete type (but not using the `free` function from the C standard library). - */ -typedef struct OwnedSlice_u8 { - uint8_t *ptr; - size_t len; -} OwnedSlice_u8; - -/** - * A type alias for a rust-owned byte slice. - */ -typedef struct OwnedSlice_u8 OwnedBytes; - /** * The result type returned from an FFI function that returns no value but may * return an error. @@ -173,6 +211,26 @@ typedef struct VoidResult { }; } VoidResult; +/** + * A value returned by the FFI. + * + * This is used in several different ways, including: + * * An C-style string. + * * An ID for a proposal. + * * A byte slice containing data. + * + * For more details on how the data may be stored, refer to the function signature + * that returned it or the `From` implementations. + * + * The data stored in this struct (if `data` is not null) must be manually freed + * by the caller using `fwd_free_value`. + * + */ +typedef struct Value { + size_t len; + uint8_t *data; +} Value; + typedef uint32_t ProposalId; /** @@ -288,32 +346,26 @@ typedef struct LogArgs { * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. + * * `db` - The database handle returned by [`fwd_open_db`] + * * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. * * # Returns * - * The new root hash of the database, in Value form. - * A `Value` containing {0, "error message"} if the commit failed. - * - * # Errors - * - * * `"key-value pair is null"` - A `KeyValue` struct is null - * * `"db should be non-null"` - The database handle is null - * * `"couldn't get key-value pair"` - A `KeyValue` struct is null - * * `"proposed revision is empty"` - The proposed revision is empty + * - [`HashResult::NullHandlePointer`] if the provided database handle is null. + * - [`HashResult::None`] if the commit resulted in an empty database. + * - [`HashResult::Some`] if the commit was successful, containing the new root hash. + * - [`HashResult::Err`] if an error occurred while committing the batch. * * # Safety * - * This function is unsafe because it dereferences raw pointers. * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. - * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. - * + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`] + * * ensure that `values` is valid for [`BorrowedKeyValuePairs`] + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error ([`HashKey`] does not need to be freed as it is returned by + * value). */ -struct Value fwd_batch(const struct DatabaseHandle *db, - BorrowedKeyValuePairs values); +struct HashResult fwd_batch(const struct DatabaseHandle *db, BorrowedKeyValuePairs values); /** * Close and free the memory for a database handle @@ -356,7 +408,7 @@ struct VoidResult fwd_close_db(struct DatabaseHandle *db); * The caller must ensure that `db` is a valid pointer returned by `open_db` * */ -struct Value fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); +struct HashResult fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); /** * Drops a proposal from the database. @@ -586,22 +638,23 @@ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, * * # Argument * - * * `db` - The database handle returned by `open_db` + * * `db` - The database handle returned by [`fwd_open_db`] * * # Returns * - * A `Value` containing the root hash of the database. - * A `Value` containing {0, "error message"} if the root hash could not be retrieved. - * One expected error is "IO error: Root hash not found" if the database is empty. - * This should be handled by the caller. + * - [`HashResult::NullHandlePointer`] if the provided database handle is null. + * - [`HashResult::None`] if the database is empty. + * - [`HashResult::Some`] with the root hash of the database. + * - [`HashResult::Err`] if an error occurred while looking up the root hash. * * # Safety * - * This function is unsafe because it dereferences raw pointers. - * The caller must ensure that `db` is a valid pointer returned by `open_db` - * + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`] + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error ([`HashKey`] does not need to be freed as it is returned + * by value). */ -struct Value fwd_root_hash(const struct DatabaseHandle *db); +struct HashResult fwd_root_hash(const struct DatabaseHandle *db); /** * Start logs for this process. diff --git a/ffi/memory.go b/ffi/memory.go index 5b1bd11d355a..bd6ac64f4175 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -261,6 +261,29 @@ func newOwnedBytes(owned C.OwnedBytes) *ownedBytes { return &ownedBytes{owned: owned} } +// getHashKeyFromHashResult creates a byte slice or error from a C.HashResult. +// +// It returns nil, nil if the result is None. +// It returns nil, err if the result is an error. +// It returns a byte slice, nil if the result is Some. +func getHashKeyFromHashResult(result C.HashResult) ([]byte, error) { + switch result.tag { + case C.HashResult_NullHandlePointer: + return nil, errDBClosed + case C.HashResult_None: + return nil, nil + case C.HashResult_Some: + cHashKey := (*C.HashKey)(unsafe.Pointer(&result.anon0)) + hashKey := *(*[32]byte)(unsafe.Pointer(&cHashKey._0)) + return hashKey[:], nil + case C.HashResult_Err: + ownedBytes := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))) + return nil, ownedBytes.intoError() + default: + return nil, fmt.Errorf("unknown C.HashResult tag: %d", result.tag) + } +} + // getErrorgetErrorFromVoidResult converts a C.VoidResult to an error. // // It will return nil if the result is Ok, otherwise it returns an error. diff --git a/ffi/proposal.go b/ffi/proposal.go index 431229710f78..f26bbdc6ff9a 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -128,9 +128,7 @@ func (p *Proposal) Commit() error { return errDroppedProposal } - // Commit the proposal and return the hash. - errVal := C.fwd_commit(p.handle, C.uint32_t(p.id)) - err := errorFromValue(&errVal) + _, err := getHashKeyFromHashResult(C.fwd_commit(p.handle, C.uint32_t(p.id))) if err != nil { // this is unrecoverable due to Rust's ownership model // The underlying proposal is no longer valid. diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 83e5df2cd438..820d3f2d4cf7 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,11 +4,11 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, - v2::api::{self, ArcDynDbView, HashKey}, + v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, Proposal as _}, }; use metrics::counter; -use crate::{BorrowedBytes, DatabaseHandle}; +use crate::{BorrowedBytes, DatabaseHandle, KeyValuePair}; /// Arguments for creating or opening a database. These are passed to [`fwd_open_db`] /// @@ -101,6 +101,73 @@ impl DatabaseHandle<'_> { Db::new(path, cfg).map(Self::from) } + /// Returns the current root hash of the database. + /// + /// # Errors + /// + /// An error is returned if there was an i/o error while reading the root hash. + pub fn current_root_hash(&self) -> Result, api::Error> { + self.db.root_hash() + } + + /// Creates a proposal with the given values and returns the proposal and the start time. + /// + /// # Errors + /// + /// An error is returned if the proposal could not be created. + pub fn create_batch<'kvp>( + &self, + values: (impl AsRef<[KeyValuePair<'kvp>]> + 'kvp), + ) -> Result, api::Error> { + let start = coarsetime::Instant::now(); + + let proposal = self.db.propose(values.as_ref())?; + + let propose_time = start.elapsed().as_millis(); + counter!("firewood.ffi.propose_ms").increment(propose_time); + + let hash_val = proposal.root_hash()?; + + proposal.commit()?; + + let propose_plus_commit_time = start.elapsed().as_millis(); + counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); + counter!("firewood.ffi.commit_ms") + .increment(propose_plus_commit_time.saturating_sub(propose_time)); + counter!("firewood.ffi.batch").increment(1); + + Ok(hash_val) + } + + /// Commits a proposal with the given ID. + /// + /// # Errors + /// + /// An error is returned if the proposal could not be committed, or if the proposal ID is invalid. + pub fn commit_proposal(&self, proposal_id: u32) -> Result, String> { + let proposal = self + .proposals + .write() + .map_err(|_| "proposal lock is poisoned")? + .remove(&proposal_id) + .ok_or("proposal not found")?; + + // Get the proposal hash and cache the view. We never cache an empty proposal. + let proposal_hash = proposal.root_hash().map_err(|e| e.to_string())?; + + if let Some(ref hash_key) = proposal_hash { + _ = self.get_root(hash_key.clone()); + } + + // Commit the proposal + let result = proposal.commit().map_err(|e| e.to_string()); + + // Clear the cache, which will force readers after this point to find the committed root hash + self.clear_cached_view(); + + result.map(|()| proposal_hash) + } + pub(crate) fn get_root(&self, root: HashKey) -> Result { let mut cache_miss = false; let view = self.cached_view.get_or_try_insert_with(root, |key| { diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index b36c7e352a7c..7b0d622935bb 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -40,8 +40,7 @@ use std::sync::RwLock; use std::sync::atomic::{AtomicU32, Ordering}; use firewood::db::{Db, Proposal}; -use firewood::v2::api::{self, Db as _, DbView, HashKey, KeyValuePairIter, Proposal as _}; -use metrics::counter; +use firewood::v2::api::{self, Db as _, DbView, KeyValuePairIter, Proposal as _}; use crate::arc_cache::ArcCache; pub use crate::handle::*; @@ -106,7 +105,7 @@ pub struct DatabaseHandle<'p> { proposals: RwLock>>, /// A single cached view to improve performance of reads while committing - cached_view: ArcCache, + cached_view: ArcCache, /// The database db: Db, @@ -269,7 +268,7 @@ fn get_from_root( key: &[u8], ) -> Result { let db = db.ok_or("db should be non-null")?; - let requested_root = HashKey::try_from(root).map_err(|e| e.to_string())?; + let requested_root = api::HashKey::try_from(root).map_err(|e| e.to_string())?; let cached_view = db.get_root(requested_root).map_err(|e| e.to_string())?; let value = cached_view.val(key).map_err(|e| e.to_string())?.ok_or("")?; Ok(value.into()) @@ -279,68 +278,30 @@ fn get_from_root( /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. +/// * `db` - The database handle returned by [`fwd_open_db`] +/// * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. /// /// # Returns /// -/// The new root hash of the database, in Value form. -/// A `Value` containing {0, "error message"} if the commit failed. -/// -/// # Errors -/// -/// * `"key-value pair is null"` - A `KeyValue` struct is null -/// * `"db should be non-null"` - The database handle is null -/// * `"couldn't get key-value pair"` - A `KeyValue` struct is null -/// * `"proposed revision is empty"` - The proposed revision is empty +/// - [`HashResult::NullHandlePointer`] if the provided database handle is null. +/// - [`HashResult::None`] if the commit resulted in an empty database. +/// - [`HashResult::Some`] if the commit was successful, containing the new root hash. +/// - [`HashResult::Err`] if an error occurred while committing the batch. /// /// # Safety /// -/// This function is unsafe because it dereferences raw pointers. /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. -/// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. -/// +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`] +/// * ensure that `values` is valid for [`BorrowedKeyValuePairs`] +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error ([`HashKey`] does not need to be freed as it is returned by +/// value). #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_batch( - db: Option<&DatabaseHandle<'_>>, - values: BorrowedKeyValuePairs, -) -> Value { - batch(db, &values).unwrap_or_else(Into::into) -} - -/// Internal call for `fwd_batch` to remove error handling from the C API -#[doc(hidden)] -fn batch(db: Option<&DatabaseHandle<'_>>, values: &[KeyValuePair<'_>]) -> Result { - let db = db.ok_or("db should be non-null")?; - let start = coarsetime::Instant::now(); - - // Create a batch of operations to perform. - let batch = values.iter().map_into_batch(); - - // Propose the batch of operations. - let proposal = db.propose(batch).map_err(|e| e.to_string())?; - let propose_time = start.elapsed().as_millis(); - counter!("firewood.ffi.propose_ms").increment(propose_time); - - let hash_val = proposal - .root_hash() - .map_err(|e| e.to_string())? - .ok_or("Proposed revision is empty")? - .as_slice() - .into(); - - // Commit the proposal. - proposal.commit().map_err(|e| e.to_string())?; - - // Get the root hash of the database post-commit. - let propose_plus_commit_time = start.elapsed().as_millis(); - counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); - counter!("firewood.ffi.commit_ms") - .increment(propose_plus_commit_time.saturating_sub(propose_time)); - counter!("firewood.ffi.batch").increment(1); - Ok(hash_val) + db: Option<&DatabaseHandle>, + values: BorrowedKeyValuePairs<'_>, +) -> HashResult { + invoke_with_handle(db, move |db| db.create_batch(values)) } /// Proposes a batch of operations to the database. @@ -491,35 +452,11 @@ fn propose_on_proposal( /// The caller must ensure that `db` is a valid pointer returned by `open_db` /// #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Value { - commit(db, proposal_id).map_or_else(Into::into, Into::into) -} - -/// Internal call for `fwd_commit` to remove error handling from the C API -#[doc(hidden)] -fn commit(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), String> { - let db = db.ok_or("db should be non-null")?; - let proposal = db - .proposals - .write() - .map_err(|_| "proposal lock is poisoned")? - .remove(&proposal_id) - .ok_or("proposal not found")?; - - // Get the proposal hash and cache the view. We never cache an empty proposal. - let proposal_hash = proposal.root_hash(); - - if let Ok(Some(ref hash_key)) = proposal_hash { - _ = db.get_root(hash_key.clone()); - } - - // Commit the proposal - let result = proposal.commit().map_err(|e| e.to_string()); - - // Clear the cache, which will force readers after this point to find the committed root hash - db.clear_cached_view(); - - result +pub unsafe extern "C" fn fwd_commit( + db: Option<&DatabaseHandle<'_>>, + proposal_id: u32, +) -> HashResult { + invoke_with_handle(db, move |db| db.commit_proposal(proposal_id)) } /// Drops a proposal from the database. @@ -559,34 +496,24 @@ fn drop_proposal(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<() /// /// # Argument /// -/// * `db` - The database handle returned by `open_db` +/// * `db` - The database handle returned by [`fwd_open_db`] /// /// # Returns /// -/// A `Value` containing the root hash of the database. -/// A `Value` containing {0, "error message"} if the root hash could not be retrieved. -/// One expected error is "IO error: Root hash not found" if the database is empty. -/// This should be handled by the caller. +/// - [`HashResult::NullHandlePointer`] if the provided database handle is null. +/// - [`HashResult::None`] if the database is empty. +/// - [`HashResult::Some`] with the root hash of the database. +/// - [`HashResult::Err`] if an error occurred while looking up the root hash. /// /// # Safety /// -/// This function is unsafe because it dereferences raw pointers. -/// The caller must ensure that `db` is a valid pointer returned by `open_db` -/// +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`] +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error ([`HashKey`] does not need to be freed as it is returned +/// by value). #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle<'_>>) -> Value { - root_hash(db).unwrap_or_else(Into::into) -} - -/// This function is not exposed to the C API. -/// Internal call for `fwd_root_hash` to remove error handling from the C API -#[doc(hidden)] -fn root_hash(db: Option<&DatabaseHandle<'_>>) -> Result { - let db = db.ok_or("db should be non-null")?; - db.root_hash() - .map_err(|e| e.to_string())? - .map(|root| Value::from(root.as_slice())) - .map_or_else(|| Ok(Value::default()), Ok) +pub unsafe extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle>) -> HashResult { + invoke_with_handle(db, DatabaseHandle::current_root_hash) } /// A value returned by the FFI. diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 12c64d5bdd14..17fe8ca4e370 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -3,13 +3,15 @@ mod borrowed; mod display_hex; +mod hash_key; mod kvp; mod owned; mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; +pub use self::hash_key::HashKey; pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; -pub use self::results::{HandleResult, VoidResult}; +pub use self::results::{HandleResult, HashResult, VoidResult}; diff --git a/ffi/src/value/hash_key.rs b/ffi/src/value/hash_key.rs new file mode 100644 index 000000000000..4527396c0a50 --- /dev/null +++ b/ffi/src/value/hash_key.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt; + +/// A database hash key, used in FFI functions that require hashes. +/// This type requires no allocation and can be copied freely and +/// dropped without any additional overhead. +/// +/// This is useful because it is the same size as 4 words which is equivalent +/// to 2 heap-allocated slices (pointer + length each), or 1.5 vectors (which +/// uses an extra word for allocation capacity) and it can be passed around +/// without needing to allocate or deallocate memory. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +// Must use `repr(C)` instead of `repr(transparent)` to ensure it is a struct +// with one field instead of a type alias of an array of 32 element, which is +// necessary for FFI compatibility so that `HashKey` can be passed by value; +// otherwise, it would look like a pointer to an array of 32 bytes. +#[repr(C)] +pub struct HashKey([u8; 32]); + +impl fmt::Display for HashKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + super::DisplayHex(&self.0).fmt(f) + } +} + +impl From for HashKey { + fn from(value: firewood::v2::api::HashKey) -> Self { + Self(value.into()) + } +} + +impl From for firewood::v2::api::HashKey { + fn from(value: HashKey) -> Self { + value.0.into() + } +} diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 6f800daf9719..57438c66204f 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -3,7 +3,9 @@ use std::fmt; -use crate::OwnedBytes; +use firewood::v2::api; + +use crate::{HashKey, OwnedBytes}; /// The result type returned from an FFI function that returns no value but may /// return an error. @@ -73,6 +75,40 @@ impl From, E>> for Handle } } +/// A result type returned from FFI functions return the database root hash. This +/// may or may not be after a mutation. +#[derive(Debug)] +#[repr(C)] +pub enum HashResult { + /// The caller provided a null pointer to a database handle. + NullHandlePointer, + /// The proposal resulted in an empty database or the database currently has + /// no root hash. + None, + /// The mutation was successful and the root hash is returned, if this result + /// was from a mutation. Otherwise, this is the current root hash of the + /// database. + Some(HashKey), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From, E>> for HashResult { + fn from(value: Result, E>) -> Self { + match value { + Ok(None) => HashResult::None, + Ok(Some(hash)) => HashResult::Some(HashKey::from(hash)), + Err(err) => HashResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// Helper trait to handle the different result types returned from FFI functions. /// /// Once Try trait is stable, we can use that instead of this trait: @@ -124,6 +160,18 @@ impl CResult for HandleResult { } } +impl NullHandleResult for HashResult { + fn null_handle_pointer_error() -> Self { + Self::NullHandlePointer + } +} + +impl CResult for HashResult { + fn from_err(err: impl ToString) -> Self { + Self::Err(err.to_string().into_bytes().into()) + } +} + enum Panic { Static(&'static str), Formatted(String), From 471c89f5304cb8341ae10c2de89dff16ab23c10f Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 13:34:51 -0700 Subject: [PATCH 0921/1053] feat(ffi-refactor): refactor revision to use database handle (6/8) (#1227) Depends On: #1226 --- ffi/firewood.go | 14 +++++++++- ffi/firewood_test.go | 2 +- ffi/revision.go | 63 ++------------------------------------------ 3 files changed, 16 insertions(+), 63 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 743fe6bc31ef..08b41427a7aa 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -222,5 +222,17 @@ func (db *Database) Root() ([]byte, error) { // Revision returns a historical revision of the database. func (db *Database) Revision(root []byte) (*Revision, error) { - return newRevision(db.handle, root) + if root == nil || len(root) != RootLength { + return nil, errInvalidRootLength + } + + // Attempt to get any value from the root. + // This will verify that the root is valid and accessible. + // If the root is not valid, this will return an error. + _, err := db.GetFromRoot(root, []byte{}) + if err != nil { + return nil, err + } + + return &Revision{database: db, root: root}, nil } diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index af2a8c8cda39..cdf1178d4d03 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -839,7 +839,7 @@ func TestInvalidRevision(t *testing.T) { validRoot := []byte("counting 32 bytes to make a hash") r.Len(validRoot, 32, "valid root") _, err = db.Revision(validRoot) - r.ErrorIs(err, errRevisionNotFound, "Revision(valid root)") + r.ErrorContains(err, "Revision for Some(636f756e74696e6720333220627974657320746f206d616b6520612068617368) not found", "Revision(valid root)") } // Tests that edge case `Get` calls are handled correctly. diff --git a/ffi/revision.go b/ffi/revision.go index 3c363ece1210..6e5dd2fb083c 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -13,7 +13,6 @@ import "C" import ( "errors" "fmt" - "runtime" ) var ( @@ -22,69 +21,11 @@ var ( ) type Revision struct { - // handle is returned and accepted by cgo functions. It MUST be treated as - // an opaque value without special meaning. - // https://en.wikipedia.org/wiki/Blinkenlights - handle *C.DatabaseHandle + database *Database // The revision root root []byte } -func newRevision(handle *C.DatabaseHandle, root []byte) (*Revision, error) { - if handle == nil { - return nil, errDBClosed - } - - // Check that the root is the correct length. - if root == nil || len(root) != RootLength { - return nil, errInvalidRootLength - } - - var pinner runtime.Pinner - defer pinner.Unpin() - - // Attempt to get any value from the root. - // This will verify that the root is valid and accessible. - // If the root is not valid, this will return an error. - - val := C.fwd_get_from_root( - handle, - newBorrowedBytes(root, &pinner), - newBorrowedBytes(nil, &pinner), - ) - _, err := bytesFromValue(&val) - if err != nil { - // Any error from this function indicates that the root is inaccessible. - return nil, errRevisionNotFound - } - - // All other verification of the root is done during use. - return &Revision{ - handle: handle, - root: root, - }, nil -} - func (r *Revision) Get(key []byte) ([]byte, error) { - if r.handle == nil { - return nil, errDBClosed - } - if r.root == nil { - return nil, errRevisionNotFound - } - - var pinner runtime.Pinner - defer pinner.Unpin() - - val := C.fwd_get_from_root( - r.handle, - newBorrowedBytes(r.root, &pinner), - newBorrowedBytes(key, &pinner), - ) - value, err := bytesFromValue(&val) - if err != nil { - // Any error from this function indicates that the revision is inaccessible. - r.root = nil - } - return value, err + return r.database.GetFromRoot(r.root, key) } From 2d319bc2e86729d95e34edb1160272a1417136f2 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 22 Aug 2025 14:10:37 -0700 Subject: [PATCH 0922/1053] feat(ffi-refactor): Add `ValueResult` type (7/8) (#1228) This is the penultimate pull-request in the series to refactor the Firewood FFI api. This adds a structures `ValueResult` type for functions that return a byte array back to the caller. Depends On: #1227 --- ffi/firewood.go | 21 +++--- ffi/firewood.h | 129 ++++++++++++++++++++++++++--------- ffi/firewood_test.go | 2 +- ffi/memory.go | 76 +++++++++------------ ffi/metrics.go | 6 +- ffi/proposal.go | 3 +- ffi/src/handle.rs | 50 +++++++++++++- ffi/src/lib.rs | 143 +++++++++++++++------------------------ ffi/src/value.rs | 2 +- ffi/src/value/results.rs | 67 ++++++++++++++++++ 10 files changed, 312 insertions(+), 187 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 08b41427a7aa..fcbf14442382 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -30,14 +30,12 @@ import ( "errors" "fmt" "runtime" - "strings" ) // These constants are used to identify errors returned by the Firewood Rust FFI. // These must be changed if the Rust FFI changes - should be reported by tests. const ( - RootLength = C.sizeof_HashKey - rootHashNotFound = "IO error: Root hash not found" + RootLength = C.sizeof_HashKey ) var ( @@ -168,15 +166,14 @@ func (db *Database) Get(key []byte) ([]byte, error) { var pinner runtime.Pinner defer pinner.Unpin() - val := C.fwd_get_latest(db.handle, newBorrowedBytes(key, &pinner)) - bytes, err := bytesFromValue(&val) - - // If the root hash is not found, return nil. - if err != nil && strings.Contains(err.Error(), rootHashNotFound) { + val, err := getValueFromValueResult(C.fwd_get_latest(db.handle, newBorrowedBytes(key, &pinner))) + // The revision won't be found if the database is empty. + // This is valid, but should be treated as a non-existent key + if errors.Is(err, errRevisionNotFound) { return nil, nil } - return bytes, err + return val, err } // GetFromRoot retrieves the value for the given key from a specific root hash. @@ -195,13 +192,11 @@ func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { var pinner runtime.Pinner defer pinner.Unpin() - val := C.fwd_get_from_root( + return getValueFromValueResult(C.fwd_get_from_root( db.handle, newBorrowedBytes(root, &pinner), newBorrowedBytes(key, &pinner), - ) - - return bytesFromValue(&val) + )) } // Root returns the current root hash of the trie. diff --git a/ffi/firewood.h b/ffi/firewood.h index bf927adfc0f5..e06da04637ec 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -231,6 +231,58 @@ typedef struct Value { uint8_t *data; } Value; +/** + * A result type returned from FFI functions that retrieve a single value. + */ +typedef enum ValueResult_Tag { + /** + * The caller provided a null pointer to a database handle. + */ + ValueResult_NullHandlePointer, + /** + * The provided root was not found in the database. + */ + ValueResult_RevisionNotFound, + /** + * The provided key was not found in the database or proposal. + */ + ValueResult_None, + /** + * A value was found and is returned. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this value. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + ValueResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + ValueResult_Err, +} ValueResult_Tag; + +typedef struct ValueResult { + ValueResult_Tag tag; + union { + struct { + struct HashKey revision_not_found; + }; + struct { + OwnedBytes some; + }; + struct { + OwnedBytes err; + }; + }; +} ValueResult; + typedef uint32_t ProposalId; /** @@ -472,10 +524,18 @@ struct VoidResult fwd_free_value(struct Value *value); * * # Returns * - * A `Value` containing {len, bytes} representing the latest metrics for this process. - * A `Value` containing {0, "error message"} if unable to get the latest metrics. + * - [`ValueResult::None`] if the gathered metrics resulted in an empty string. + * - [`ValueResult::Some`] the gathered metrics as an [`OwnedBytes`] (with + * guaranteed to be utf-8 data, not null terminated). + * - [`ValueResult::Err`] if an error occurred while retrieving the value. + * + * # Safety + * + * The caller must: + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error or value. */ -struct Value fwd_gather(void); +struct ValueResult fwd_gather(void); /** * Gets the value associated with the given key from the proposal provided. @@ -499,9 +559,9 @@ struct Value fwd_gather(void); * * call `free_value` to free the memory associated with the returned `Value` * */ -struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, - ProposalId id, - BorrowedBytes key); +struct ValueResult fwd_get_from_proposal(const struct DatabaseHandle *db, + ProposalId id, + BorrowedBytes key); /** * Gets a value assoicated with the given root hash and key. @@ -510,53 +570,60 @@ struct Value fwd_get_from_proposal(const struct DatabaseHandle *db, * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `root` - The root hash to look up, in `BorrowedBytes` form - * * `key` - The key to look up, in `BorrowedBytes` form + * * `db` - The database handle returned by [`fwd_open_db`] + * * `root` - The root hash to look up as a [`BorrowedBytes`] + * * `key` - The key to look up as a [`BorrowedBytes`] * * # Returns * - * A `Value` containing the requested value. - * A `Value` containing {0, "error message"} if the get failed. + * - [`ValueResult::NullHandlePointer`] if the provided database handle is null. + * - [`ValueResult::RevisionNotFound`] if no revision was found for the specified root. + * - [`ValueResult::None`] if the key was not found. + * - [`ValueResult::Some`] if the key was found with the associated value. + * - [`ValueResult::Err`] if an error occurred while retrieving the value. * * # Safety * * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `key` is a valid pointer to a `Value` struct - * * ensure that `root` is a valid pointer to a `Value` struct - * * call `free_value` to free the memory associated with the returned `Value` - * + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`] + * * ensure that `root` is a valid for [`BorrowedBytes`] + * * ensure that `key` is a valid for [`BorrowedBytes`] + * * call [`fwd_free_owned_bytes`] to free the memory associated [`OwnedBytes`] + * returned in the result. */ -struct Value fwd_get_from_root(const struct DatabaseHandle *db, - BorrowedBytes root, - BorrowedBytes key); +struct ValueResult fwd_get_from_root(const struct DatabaseHandle *db, + BorrowedBytes root, + BorrowedBytes key); /** - * Gets the value associated with the given key from the database. + * Gets the value associated with the given key from the database for the + * latest revision. * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `key` - The key to look up, in `BorrowedBytes` form + * * `db` - The database handle returned by [`fwd_open_db`] + * * `key` - The key to look up as a [`BorrowedBytes`] * * # Returns * - * A `Value` containing the requested value. - * A `Value` containing {0, "error message"} if the get failed. - * There is one error case that may be expected to be null by the caller, - * but should be handled externally: The database has no entries - "IO error: Root hash not found" - * This is expected behavior if the database is empty. + * - [`ValueResult::NullHandlePointer`] if the provided database handle is null. + * - [`ValueResult::RevisionNotFound`] if no revision was found for the root + * (i.e., there is no current root). + * - [`ValueResult::None`] if the key was not found. + * - [`ValueResult::Some`] if the key was found with the associated value. + * - [`ValueResult::Err`] if an error occurred while retrieving the value. * * # Safety * * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `key` is a valid pointer to a `Value` struct - * * call `free_value` to free the memory associated with the returned `Value` + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. + * * ensure that `key` is valid for [`BorrowedBytes`] + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error or value. * + * [`BorrowedBytes`]: crate::value::BorrowedBytes */ -struct Value fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); +struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); /** * Open a database with the given arguments. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index cdf1178d4d03..af2a8c8cda39 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -839,7 +839,7 @@ func TestInvalidRevision(t *testing.T) { validRoot := []byte("counting 32 bytes to make a hash") r.Len(validRoot, 32, "valid root") _, err = db.Revision(validRoot) - r.ErrorContains(err, "Revision for Some(636f756e74696e6720333220627974657320746f206d616b6520612068617368) not found", "Revision(valid root)") + r.ErrorIs(err, errRevisionNotFound, "Revision(valid root)") } // Tests that edge case `Get` calls are handled correctly. diff --git a/ffi/memory.go b/ffi/memory.go index bd6ac64f4175..a3b8061212d2 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -300,6 +300,37 @@ func getErrorFromVoidResult(result C.VoidResult) error { } } +// getValueFromValueResult converts a C.ValueResult to a byte slice or error. +// +// It returns nil, nil if the result is None. +// It returns nil, errRevisionNotFound if the result is RevisionNotFound. +// It returns a byte slice, nil if the result is Some. +// It returns an error if the result is an error. +func getValueFromValueResult(result C.ValueResult) ([]byte, error) { + switch result.tag { + case C.ValueResult_NullHandlePointer: + return nil, errDBClosed + case C.ValueResult_RevisionNotFound: + // NOTE: the result value contains the provided root hash, we could use + // it in the error message if needed. + return nil, errRevisionNotFound + case C.ValueResult_None: + return nil, nil + case C.ValueResult_Some: + ownedBytes := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))) + bytes := ownedBytes.CopiedBytes() + if err := ownedBytes.Free(); err != nil { + return nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } + return bytes, nil + case C.ValueResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.ValueResult tag: %d", result.tag) + } +} + // getDatabaseFromHandleResult converts a C.HandleResult to a Database or error. // // If the C.HandleResult is an error, it returns an error instead of a Database. @@ -402,48 +433,3 @@ func errorFromValue(v *C.struct_Value) error { } return errBadValue } - -// bytesFromValue converts the cgo `Value` payload to: -// -// case | data | len | meaning -// -// 1. | nil | 0 | empty -// 2. | nil | non-0 | invalid -// 3. | non-nil | 0 | error string -// 4. | non-nil | non-0 | bytes (most common) -// -// The value should never be nil. -func bytesFromValue(v *C.struct_Value) ([]byte, error) { - // Pin the returned value to prevent it from being garbage collected. - defer runtime.KeepAlive(v) - - if v == nil { - return nil, errNilStruct - } - - // Case 4 - if v.len != 0 && v.data != nil { - buf := C.GoBytes(unsafe.Pointer(v.data), C.int(v.len)) - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return nil, fmt.Errorf("%w: %w", errFreeingValue, err) - } - return buf, nil - } - - // Case 1 - if v.len == 0 && v.data == nil { - return nil, nil - } - - // Case 3 - if v.len == 0 { - errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return nil, fmt.Errorf("%w: %w", errFreeingValue, err) - } - return nil, errors.New(errStr) - } - - // Case 2 - return nil, errBadValue -} diff --git a/ffi/metrics.go b/ffi/metrics.go index 064d45bef121..50e4eaf9d8c8 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -65,12 +65,12 @@ func StartMetricsWithExporter(metricsPort uint16) error { // Returns an error if the global recorder is not initialized. // This method must be called after StartMetrics or StartMetricsWithExporter func GatherMetrics() (string, error) { - result := C.fwd_gather() - b, err := bytesFromValue(&result) + bytes, err := getValueFromValueResult(C.fwd_gather()) if err != nil { return "", err } - return string(b), nil + + return string(bytes), nil } // LogConfig configures logs for this process. diff --git a/ffi/proposal.go b/ffi/proposal.go index f26bbdc6ff9a..efd888fd6635 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -88,8 +88,7 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { defer pinner.Unpin() // Get the value for the given key. - val := C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), newBorrowedBytes(key, &pinner)) - return bytesFromValue(&val) + return getValueFromValueResult(C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), newBorrowedBytes(key, &pinner))) } // Propose creates a new proposal with the given keys and values. diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 820d3f2d4cf7..a9a0ea6e575a 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,7 +4,8 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, - v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, Proposal as _}, + merkle::Value, + v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType, Proposal as _}, }; use metrics::counter; @@ -110,6 +111,35 @@ impl DatabaseHandle<'_> { self.db.root_hash() } + /// Returns a value from the database for the given key from the latest root hash. + /// + /// # Errors + /// + /// An error is returned if there was an i/o error while reading the value. + pub fn get_latest(&self, key: impl KeyType) -> Result, api::Error> { + let Some(root) = self.current_root_hash()? else { + return Err(api::Error::RevisionNotFound { + provided: HashKey::default_root_hash(), + }); + }; + + self.db.revision(root)?.val(key) + } + + /// Returns a value from the database for the given key from the specified root hash. + /// + /// # Errors + /// + /// An error is returned if the root hash is invalid or if there was an i/o error + /// while reading the value. + pub fn get_from_root( + &self, + root: HashKey, + key: impl KeyType, + ) -> Result>, api::Error> { + self.get_root(root)?.val(key.as_ref()) + } + /// Creates a proposal with the given values and returns the proposal and the start time. /// /// # Errors @@ -139,6 +169,24 @@ impl DatabaseHandle<'_> { Ok(hash_val) } + /// Returns a value from the proposal with the given ID for the specified key. + /// + /// # Errors + /// + /// An error is returned if the proposal could not be found, or if the key is invalid. + pub fn get_from_proposal( + &self, + id: u32, + key: impl KeyType, + ) -> Result, api::Error> { + self.proposals + .read() + .map_err(|_| invalid_data("proposal lock is poisoned"))? + .get(&id) + .ok_or_else(|| invalid_data("proposal not found"))? + .val(key.as_ref()) + } + /// Commits a proposal with the given ID. /// /// # Errors diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 7b0d622935bb..ed57a120036e 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -129,53 +129,38 @@ impl Deref for DatabaseHandle<'_> { } } -/// Gets the value associated with the given key from the database. +/// Gets the value associated with the given key from the database for the +/// latest revision. /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `key` - The key to look up, in `BorrowedBytes` form +/// * `db` - The database handle returned by [`fwd_open_db`] +/// * `key` - The key to look up as a [`BorrowedBytes`] /// /// # Returns /// -/// A `Value` containing the requested value. -/// A `Value` containing {0, "error message"} if the get failed. -/// There is one error case that may be expected to be null by the caller, -/// but should be handled externally: The database has no entries - "IO error: Root hash not found" -/// This is expected behavior if the database is empty. +/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null. +/// - [`ValueResult::RevisionNotFound`] if no revision was found for the root +/// (i.e., there is no current root). +/// - [`ValueResult::None`] if the key was not found. +/// - [`ValueResult::Some`] if the key was found with the associated value. +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. /// /// # Safety /// /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `key` is a valid pointer to a `Value` struct -/// * call `free_value` to free the memory associated with the returned `Value` +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. +/// * ensure that `key` is valid for [`BorrowedBytes`] +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error or value. /// +/// [`BorrowedBytes`]: crate::value::BorrowedBytes #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_latest( - db: Option<&DatabaseHandle<'_>>, - key: BorrowedBytes<'_>, -) -> Value { - get_latest(db, &key).unwrap_or_else(Into::into) -} - -/// This function is not exposed to the C API. -/// Internal call for `fwd_get_latest` to remove error handling from the C API -#[doc(hidden)] -fn get_latest(db: Option<&DatabaseHandle<'_>>, key: &[u8]) -> Result { - let db = db.ok_or("db should be non-null")?; - // Find root hash. - // Matches `hash` function but we use the TrieHash type here - let Some(root) = db.root_hash().map_err(|e| e.to_string())? else { - return Ok(Value::default()); - }; - - // Find revision assoicated with root. - let rev = db.revision(root).map_err(|e| e.to_string())?; - - // Get value associated with key. - let value = rev.val(key).map_err(|e| e.to_string())?.ok_or("")?; - Ok(value.into()) + db: Option<&DatabaseHandle>, + key: BorrowedBytes, +) -> ValueResult { + invoke_with_handle(db, move |db| db.get_latest(key)) } /// Gets the value associated with the given key from the proposal provided. @@ -203,29 +188,8 @@ pub unsafe extern "C" fn fwd_get_from_proposal( db: Option<&DatabaseHandle<'_>>, id: ProposalId, key: BorrowedBytes<'_>, -) -> Value { - get_from_proposal(db, id, &key).unwrap_or_else(Into::into) -} - -/// This function is not exposed to the C API. -/// Internal call for `fwd_get_from_proposal` to remove error handling from the C API -#[doc(hidden)] -fn get_from_proposal( - db: Option<&DatabaseHandle<'_>>, - id: ProposalId, - key: &[u8], -) -> Result { - let db = db.ok_or("db should be non-null")?; - // Get proposal from ID. - let proposals = db - .proposals - .read() - .map_err(|_| "proposal lock is poisoned")?; - let proposal = proposals.get(&id).ok_or("proposal not found")?; - - // Get value associated with key. - let value = proposal.val(key).map_err(|e| e.to_string())?.ok_or("")?; - Ok(value.into()) +) -> ValueResult { + invoke_with_handle(db, move |db| db.get_from_proposal(id, key)) } /// Gets a value assoicated with the given root hash and key. @@ -234,44 +198,35 @@ fn get_from_proposal( /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `root` - The root hash to look up, in `BorrowedBytes` form -/// * `key` - The key to look up, in `BorrowedBytes` form +/// * `db` - The database handle returned by [`fwd_open_db`] +/// * `root` - The root hash to look up as a [`BorrowedBytes`] +/// * `key` - The key to look up as a [`BorrowedBytes`] /// /// # Returns /// -/// A `Value` containing the requested value. -/// A `Value` containing {0, "error message"} if the get failed. +/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null. +/// - [`ValueResult::RevisionNotFound`] if no revision was found for the specified root. +/// - [`ValueResult::None`] if the key was not found. +/// - [`ValueResult::Some`] if the key was found with the associated value. +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. /// /// # Safety /// /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `key` is a valid pointer to a `Value` struct -/// * ensure that `root` is a valid pointer to a `Value` struct -/// * call `free_value` to free the memory associated with the returned `Value` -/// +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`] +/// * ensure that `root` is a valid for [`BorrowedBytes`] +/// * ensure that `key` is a valid for [`BorrowedBytes`] +/// * call [`fwd_free_owned_bytes`] to free the memory associated [`OwnedBytes`] +/// returned in the result. #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_root( - db: Option<&DatabaseHandle<'_>>, - root: BorrowedBytes<'_>, - key: BorrowedBytes<'_>, -) -> Value { - get_from_root(db, &root, &key).unwrap_or_else(Into::into) -} - -/// Internal call for `fwd_get_from_root` to remove error handling from the C API -#[doc(hidden)] -fn get_from_root( - db: Option<&DatabaseHandle<'_>>, - root: &[u8], - key: &[u8], -) -> Result { - let db = db.ok_or("db should be non-null")?; - let requested_root = api::HashKey::try_from(root).map_err(|e| e.to_string())?; - let cached_view = db.get_root(requested_root).map_err(|e| e.to_string())?; - let value = cached_view.val(key).map_err(|e| e.to_string())?.ok_or("")?; - Ok(value.into()) + db: Option<&DatabaseHandle>, + root: BorrowedBytes, + key: BorrowedBytes, +) -> ValueResult { + invoke_with_handle(db, move |db| { + db.get_from_root(root.as_ref().try_into()?, key) + }) } /// Puts the given key-value pairs into the database. @@ -681,11 +636,19 @@ pub extern "C" fn fwd_start_metrics_with_exporter(metrics_port: u16) -> VoidResu /// /// # Returns /// -/// A `Value` containing {len, bytes} representing the latest metrics for this process. -/// A `Value` containing {0, "error message"} if unable to get the latest metrics. +/// - [`ValueResult::None`] if the gathered metrics resulted in an empty string. +/// - [`ValueResult::Some`] the gathered metrics as an [`OwnedBytes`] (with +/// guaranteed to be utf-8 data, not null terminated). +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. +/// +/// # Safety +/// +/// The caller must: +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error or value. #[unsafe(no_mangle)] -pub extern "C" fn fwd_gather() -> Value { - metrics_setup::gather_metrics().map_or_else(Into::into, |s| s.as_bytes().into()) +pub extern "C" fn fwd_gather() -> ValueResult { + invoke(metrics_setup::gather_metrics) } /// Open a database with the given arguments. diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 17fe8ca4e370..e5d1bbb8a1c2 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -14,4 +14,4 @@ pub use self::hash_key::HashKey; pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; -pub use self::results::{HandleResult, HashResult, VoidResult}; +pub use self::results::{HandleResult, HashResult, ValueResult, VoidResult}; diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 57438c66204f..2467223c36bc 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -75,6 +75,61 @@ impl From, E>> for Handle } } +/// A result type returned from FFI functions that retrieve a single value. +#[derive(Debug)] +#[repr(C)] +pub enum ValueResult { + /// The caller provided a null pointer to a database handle. + NullHandlePointer, + /// The provided root was not found in the database. + RevisionNotFound(HashKey), + /// The provided key was not found in the database or proposal. + None, + /// A value was found and is returned. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this value. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Some(OwnedBytes), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From> for ValueResult { + fn from(value: Result) -> Self { + match value { + Ok(data) => ValueResult::Some(data.into_bytes().into()), + Err(err) => ValueResult::Err(err.to_string().into_bytes().into()), + } + } +} + +impl From>, api::Error>> for ValueResult { + fn from(value: Result>, api::Error>) -> Self { + match value { + Ok(None) => ValueResult::None, + Err(api::Error::RevisionNotFound { provided }) => ValueResult::RevisionNotFound( + HashKey::from(provided.unwrap_or_else(api::HashKey::empty)), + ), + Ok(Some(data)) => ValueResult::Some(data.into()), + Err(err) => ValueResult::Err(err.to_string().into_bytes().into()), + } + } +} + +impl From>, firewood::db::DbError>> for ValueResult { + fn from(value: Result>, firewood::db::DbError>) -> Self { + value.map_err(api::Error::from).into() + } +} + /// A result type returned from FFI functions return the database root hash. This /// may or may not be after a mutation. #[derive(Debug)] @@ -160,6 +215,18 @@ impl CResult for HandleResult { } } +impl NullHandleResult for ValueResult { + fn null_handle_pointer_error() -> Self { + Self::NullHandlePointer + } +} + +impl CResult for ValueResult { + fn from_err(err: impl ToString) -> Self { + Self::Err(err.to_string().into_bytes().into()) + } +} + impl NullHandleResult for HashResult { fn null_handle_pointer_error() -> Self { Self::NullHandlePointer From daee56d95a7e27121149180e44d97d4b1d9c2665 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 26 Aug 2025 10:50:28 -0700 Subject: [PATCH 0923/1053] chore(release): prepare for v0.0.12 (#1238) This release is out of cycle to expedite publishing the fix for #1128 which was fixed as part of #1224. --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.toml | 12 ++++++------ RELEASE.md | 18 +++++++++--------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1bfc993184..3a04d160987d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to this project will be documented in this file. +## [0.0.12] - 2025-08-26 + +### 🚀 Features + +- *(async-removal)* Phase 3 - make `Db` trait sync ([#1213](https://github.com/ava-labs/firewood/pull/1213)) +- *(checker)* Fix error with free area that is not head of a free list ([#1231](https://github.com/ava-labs/firewood/pull/1231)) +- *(async-removal)* Phase 4 - Make `DbView` synchronous ([#1219](https://github.com/ava-labs/firewood/pull/1219)) +- *(ffi-refactor)* Refactor cached view (1/8) ([#1222](https://github.com/ava-labs/firewood/pull/1222)) +- *(ffi-refactor)* Add OwnedSlice and OwnedBytes (2/8) ([#1223](https://github.com/ava-labs/firewood/pull/1223)) +- *(ffi-refactor)* Introduce VoidResult and panic handlers (3/8) ([#1224](https://github.com/ava-labs/firewood/pull/1224)) +- *(ffi-refactor)* Refactor Db opening to use new Result structure (4/8) ([#1225](https://github.com/ava-labs/firewood/pull/1225)) +- *(ffi-refactor)* Refactor how hash values are returned (5/8) ([#1226](https://github.com/ava-labs/firewood/pull/1226)) +- *(ffi-refactor)* Refactor revision to use database handle (6/8) ([#1227](https://github.com/ava-labs/firewood/pull/1227)) +- *(ffi-refactor)* Add `ValueResult` type (7/8) ([#1228](https://github.com/ava-labs/firewood/pull/1228)) + +### ⚙️ Miscellaneous Tasks + +- Only allocate the area needed ([#1217](https://github.com/ava-labs/firewood/pull/1217)) +- Synchronize .golangci.yaml ([#1234](https://github.com/ava-labs/firewood/pull/1234)) +- *(metrics-check)* Re-use previous comment instead of spamming new ones ([#1232](https://github.com/ava-labs/firewood/pull/1232)) +- Nuke grpc-testtool ([#1220](https://github.com/ava-labs/firewood/pull/1220)) + ## [0.0.11] - 2025-08-20 ### 🚀 Features diff --git a/Cargo.toml b/Cargo.toml index 45bcc42a640b..c976007e4ca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.0.11" +version = "0.0.12" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -53,11 +53,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.11" } -firewood-macros = { path = "firewood-macros", version = "0.0.11" } -firewood-storage = { path = "storage", version = "0.0.11" } -firewood-ffi = { path = "ffi", version = "0.0.11" } -firewood-triehash = { path = "triehash", version = "0.0.11" } +firewood = { path = "firewood", version = "0.0.12" } +firewood-macros = { path = "firewood-macros", version = "0.0.12" } +firewood-storage = { path = "storage", version = "0.0.12" } +firewood-ffi = { path = "ffi", version = "0.0.12" } +firewood-triehash = { path = "triehash", version = "0.0.12" } # common dependencies aquamarine = "0.6.0" diff --git a/RELEASE.md b/RELEASE.md index c542045505ee..c2079c7e3b12 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,9 +12,9 @@ Start off by crating a new branch: ```console $ git fetch -$ git switch -c release/v0.0.12 origin/main -branch 'release/v0.0.12' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.12' +$ git switch -c release/v0.0.13 origin/main +branch 'release/v0.0.13' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.13' ``` ## Package Version @@ -26,7 +26,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.12" +version = "0.0.13" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -49,7 +49,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.12" } +firewood = { path = "firewood", version = "0.0.13" } ``` This allows packages within the workspace to inherit the dependency, @@ -78,7 +78,7 @@ To build the changelog, see git-cliff.org. Short version: ```sh cargo install --locked git-cliff -git cliff --tag v0.0.12 > CHANGELOG.md +git cliff --tag v0.0.13 > CHANGELOG.md ``` ## Review @@ -92,11 +92,11 @@ git cliff --tag v0.0.12 > CHANGELOG.md To trigger a release, push a tag to the main branch matching the new version, ```sh -git tag -S v0.0.12 -git push origin v0.0.12 +git tag -s -a v0.0.13 -m 'Release v0.0.13' +git push origin v0.0.13 ``` -for `v0.0.12` for the merged version change. The CI will automatically publish a +for `v0.0.13` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). From 12f88a914af48592ee8e0c7ab56aef5f64ffa2d6 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Aug 2025 10:59:15 -0700 Subject: [PATCH 0924/1053] chore: rename FuzzTree (#1239) Confusing since there is also one in coreth --- ffi/tests/eth/eth_compatibility_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index e66f10e7404d..d911345cface 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -295,7 +295,7 @@ func (tr *merkleTriePair) deleteStorage(accountIndex int, storageIndexInput uint tr.pendingFwdVals = append(tr.pendingFwdVals, []byte{}) } -func FuzzTree(f *testing.F) { +func FuzzFirewoodTree(f *testing.F) { for randSeed := range int64(5) { rand := rand.New(rand.NewSource(randSeed)) steps := make([]byte, 32) From 09e7ab73921f5f5f48b1715536819a437dc9664e Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 26 Aug 2025 16:52:15 -0700 Subject: [PATCH 0925/1053] chore: upgrade to rust 1.89 (#1242) Needed to get File::lock in an upcoming PR --- Cargo.toml | 2 +- clippy.toml | 2 +- ffi/src/arc_cache.rs | 8 ++++---- firewood/src/merkle.rs | 14 +++++++------- storage/src/node/path.rs | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c976007e4ca2..f9958a2337c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license-file = "LICENSE.md" homepage = "https://avalabs.org" repository = "https://github.com/ava-labs/firewood" readme = "README.md" -rust-version = "1.85.0" +rust-version = "1.89.0" [profile.release] debug = true diff --git a/clippy.toml b/clippy.toml index 42602fcff662..7b2993e3029a 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,7 +1,7 @@ # See https://doc.rust-lang.org/clippy/lint_configuration.html # for full configuration options. -msrv = "1.85" +msrv = "1.89" disallowed-methods = [ { path = "rand::rng", replacement = "firewood_storage::StdRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, diff --git a/ffi/src/arc_cache.rs b/ffi/src/arc_cache.rs index 04a4d920a6cc..472497d2972d 100644 --- a/ffi/src/arc_cache.rs +++ b/ffi/src/arc_cache.rs @@ -58,10 +58,10 @@ impl ArcCache { factory: impl FnOnce(&K) -> Result, E>, ) -> Result, E> { let mut cache = self.lock(); - if let Some((cached_key, value)) = cache.as_ref() { - if *cached_key == key { - return Ok(Arc::clone(value)); - } + if let Some((cached_key, value)) = cache.as_ref() + && *cached_key == key + { + return Ok(Arc::clone(value)); } // clear the cache before running the factory in case it fails diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 0c499fbeacf7..5e4ecd34e1a4 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -356,13 +356,13 @@ impl Merkle { end_key: Option<&[u8]>, limit: Option, ) -> Result { - if let (Some(k1), Some(k2)) = (&start_key, &end_key) { - if k1 > k2 { - return Err(api::Error::InvalidRange { - start_key: k1.to_vec().into(), - end_key: k2.to_vec().into(), - }); - } + if let (Some(k1), Some(k2)) = (&start_key, &end_key) + && k1 > k2 + { + return Err(api::Error::InvalidRange { + start_key: k1.to_vec().into(), + end_key: k2.to_vec().into(), + }); } let mut iter = match start_key { diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 9902bb4231a7..ed9cc1cd8d68 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -165,10 +165,10 @@ impl Add for Path { impl Iterator for BytesIterator<'_> { type Item = u8; fn next(&mut self) -> Option { - if let Some(&hi) = self.nibbles_iter.next() { - if let Some(&lo) = self.nibbles_iter.next() { - return Some(hi * 16 + lo); - } + if let Some(&hi) = self.nibbles_iter.next() + && let Some(&lo) = self.nibbles_iter.next() + { + return Some(hi * 16 + lo); } None } From 4d20e8309ec1674481f8c26982754d1feb689e80 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 27 Aug 2025 09:10:26 -0700 Subject: [PATCH 0926/1053] fix: Add an advisory lock (#1244) Use `File::try_lock` and produce an error if it's already opened by someone else. --------- Co-authored-by: Brandon LeBlanc --- firewood/src/db.rs | 3 +- firewood/src/manager.rs | 55 +++++++++++++++++++++++++++++++- storage/src/linear/filebacked.rs | 13 ++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 9bfa99e78b33..8482ac9fa45d 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -268,7 +268,7 @@ impl<'db> api::Proposal for Proposal<'db> { } fn commit(self) -> Result<(), api::Error> { - Ok(self.db.manager.commit(self.nodestore.clone())?) + Ok(self.db.manager.commit(self.nodestore)?) } } @@ -407,6 +407,7 @@ mod test { let committed = db.root_hash().unwrap().unwrap(); let historical = db.revision(committed).unwrap(); assert_eq!(&*historical.val(b"a").unwrap().unwrap(), b"1"); + drop(historical); let db = db.replace(); println!("{:?}", db.root_hash().unwrap()); diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 11bf10a48bad..86a65c5c7021 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -101,6 +101,11 @@ impl RevisionManager { config.create, config.manager.cache_read_strategy, )?; + + // Acquire an advisory lock on the database file to prevent multiple processes + // from opening the same database simultaneously + fb.lock()?; + let storage = Arc::new(fb); let nodestore = Arc::new(NodeStore::open(storage.clone())?); let manager = Self { @@ -306,6 +311,54 @@ impl RevisionManager { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { - // TODO + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_file_advisory_lock() { + // Create a temporary file for testing + let temp_file = NamedTempFile::new().unwrap(); + let db_path = temp_file.path().to_path_buf(); + + let config = ConfigManager::builder() + .create(true) + .truncate(false) + .build(); + + // First database instance should open successfully + let first_manager = RevisionManager::new(db_path.clone(), config.clone()); + assert!( + first_manager.is_ok(), + "First database should open successfully" + ); + + // Second database instance should fail to open due to file locking + let second_manager = RevisionManager::new(db_path.clone(), config.clone()); + assert!( + second_manager.is_err(), + "Second database should fail to open" + ); + + // Verify the error message contains the expected information + let error = second_manager.unwrap_err(); + let error_string = error.to_string(); + + assert!( + error_string.contains("database may be opened by another instance"), + "Error is missing 'database may be opened by another instance', got: {error_string}" + ); + + // The file lock is held by the FileBacked instance. When we drop the first_manager, + // the Arc should be dropped, releasing the file lock. + drop(first_manager.unwrap()); + + // Now the second database should open successfully + let third_manager = RevisionManager::new(db_path, config); + assert!( + third_manager.is_ok(), + "Database should open after first instance is dropped" + ); + } } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index bdbe46e9e634..71cdeb0bef4e 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -64,6 +64,19 @@ impl std::fmt::Debug for FileBacked { } impl FileBacked { + /// Acquire an advisory lock on the underlying file to prevent multiple processes + /// from accessing it simultaneously + pub fn lock(&self) -> Result<(), FileIoError> { + self.fd.try_lock().map_err(|e| { + let context = + "unable to obtain advisory lock: database may be opened by another instance" + .to_string(); + // Convert TryLockError to a generic IO error for our FileIoError + let io_error = std::io::Error::new(std::io::ErrorKind::WouldBlock, e); + self.file_io_error(io_error, 0, Some(context)) + }) + } + /// Make a write operation from a raw data buffer for this file #[cfg(feature = "io-uring")] pub(crate) fn make_op(&self, data: &[u8]) -> io_uring::opcode::Write { From b8db2d092696c7ccd520e3aeef8ee399f4dd7fbf Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 27 Aug 2025 11:29:23 -0700 Subject: [PATCH 0927/1053] chore: rename mut_root to root_mut (#1248) Closes #1065 --- firewood/src/merkle.rs | 12 ++++++------ storage/src/nodestore/mod.rs | 4 ++-- storage/src/nodestore/persist.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5e4ecd34e1a4..7cd255cc794c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -587,7 +587,7 @@ impl Merkle> { pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - let root = self.nodestore.mut_root(); + let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. Create a new leaf node with `value` and set @@ -601,7 +601,7 @@ impl Merkle> { }; let root_node = self.insert_helper(root_node, key.as_ref(), value)?; - *self.nodestore.mut_root() = root_node.into(); + *self.nodestore.root_mut() = root_node.into(); Ok(()) } @@ -751,7 +751,7 @@ impl Merkle> { pub fn remove(&mut self, key: &[u8]) -> Result, FileIoError> { let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - let root = self.nodestore.mut_root(); + let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. counter!("firewood.remove", "prefix" => "false", "result" => "nonexistent") @@ -760,7 +760,7 @@ impl Merkle> { }; let (root_node, removed_value) = self.remove_helper(root_node, &key)?; - *self.nodestore.mut_root() = root_node; + *self.nodestore.root_mut() = root_node; if removed_value.is_some() { counter!("firewood.remove", "prefix" => "false", "result" => "success").increment(1); } else { @@ -985,7 +985,7 @@ impl Merkle> { pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); - let root = self.nodestore.mut_root(); + let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. counter!("firewood.remove", "prefix" => "true", "result" => "nonexistent").increment(1); @@ -996,7 +996,7 @@ impl Merkle> { let root_node = self.remove_prefix_helper(root_node, &prefix, &mut deleted)?; counter!("firewood.remove", "prefix" => "true", "result" => "success") .increment(deleted as u64); - *self.nodestore.mut_root() = root_node; + *self.nodestore.root_mut() = root_node; Ok(deleted) } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 4f540d384ead..8ffd97c644e9 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -265,7 +265,7 @@ impl NodeStore { } /// Returns the root of this proposal. - pub const fn mut_root(&mut self) -> &mut Option { + pub const fn root_mut(&mut self) -> &mut Option { &mut self.kind.root } } @@ -924,7 +924,7 @@ mod tests { value: huge_value.into_boxed_slice(), }); - node_store.mut_root().replace(giant_leaf); + node_store.root_mut().replace(giant_leaf); let immutable = NodeStore::, _>::try_from(node_store).unwrap(); println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 464ab1072e2b..2cc73c78d227 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -532,7 +532,7 @@ mod tests { fn create_test_store_with_root(root: Node) -> NodeStore { let mem_store = MemStore::new(vec![]).into(); let mut store = NodeStore::new_empty_proposal(mem_store); - store.mut_root().replace(root); + store.root_mut().replace(root); store } @@ -734,7 +734,7 @@ mod tests { vec![(1, leaf1.clone()), (2, leaf2.clone())], ); - mutable_store.mut_root().replace(branch.clone()); + mutable_store.root_mut().replace(branch.clone()); // Convert to immutable proposal let immutable_store: NodeStore, _> = From 4f18459cd8b40fbcc35778f7cda06552f733ad8f Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 27 Aug 2025 11:34:36 -0700 Subject: [PATCH 0928/1053] test: mark new_empty_proposal as test only (#1249) While reviewing #1240, I noticed that `new_empty_proposal` is only used in tests and is unsafe to use in non-test code. This change tries to make that clearer by marking it as `#[cfg(test)]` or including it only if `features="test_utils"` is enabled (for use in integration tests). --- storage/src/nodestore/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 8ffd97c644e9..d8f990ba3170 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -277,6 +277,7 @@ impl NodeStore { /// # Panics /// /// Panics if the header cannot be written. + #[cfg(any(test, feature = "test_utils"))] pub fn new_empty_proposal(storage: Arc) -> Self { let header = NodeStoreHeader::new(); let header_bytes = bytemuck::bytes_of(&header); From ca6a382733abec1211ba67116ec855e026f03c5d Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:42:32 -0500 Subject: [PATCH 0929/1053] feat(checker): print report using template for better readability (#1237) ``` Errors (1): Found leaked areas: Linear Address Range Set: Address Range: [0x800, 0x900) Address Range: [0xa00, 0xb00) Address Range: [0xb60, 0xc60) Address Range: [0xdc0, 0xec0) Address Range: [0x1120, 0x12a0) Address Range: [0x13a0, 0x1620) Address Range: [0x1720, 0x17a0) Address Range: [0x1aa0, 0x20a0) Address Range: [0x27a0, 0x28a0) Address Range: [0x29a0, 0x2ea0) ... (76480 more hidden) Basic Stats: Firewood Image Size / High Watermark (high_watermark): 394,533,632 Total Key-Value Count (kv_count): 586,687 Total Key-Value Bytes (kv_bytes): 46,859,598 Trie Stats: Branching Factor Distribution: 1: 98, 2: 114,082, 3: 33,897, 4: 12,560, 5: 6,810, 6: 4,994, 7: 4,381, 8: 4,359, 9: 4,311, 10: 3,917, 11: 3,779, 12: 3,162, 13: 2,448, 14: 1,516, 15: 941, 16: 3,452 Depth Distribution: 4: 23,285, 5: 36,333, 6: 16,921, 7: 42,667, 8: 119,550, 9: 197,709, 10: 139,367, 11: 9,045, 12: 40 Branch Area Stats: Total Branch Data Bytes (branch_bytes): 33,759,976 Total Branch Area Count (branch_area_count): 204,707 Total Branch Area Bytes (branch_area_bytes): 97,750,016 Branch Area Distribution: 16: 0, 32: 0, 64: 0, 96: 43,100, 128: 16,525, 256: 24,453, 512: 37,967, 768: 73,624, 1,024: 9,038, 2,048: 0, 4,096: 0, 8,192: 0, 16,384: 0, 32,768: 0, 65,536: 0, 131,072: 0, 262,144: 0, 524,288: 0, 1,048,576: 0, 2,097,152: 0, 4,194,304: 0, 8,388,608: 0, 16,777,216: 0 Branches that Can Fit Into Smaller Area (low_occupancy_branch_area): 118,365 (57.82%) Leaf Area Stats: Total Leaf Data Bytes (leaf_bytes): 47,874,086 Total Leaf Area Count (leaf_area_count): 584,917 Total Leaf Area Bytes (leaf_area_bytes): 236,996,576 Leaf Area Distribution: 16: 0, 32: 0, 64: 6,422, 96: 193,737, 128: 39,112, 256: 81,057, 512: 66,634, 768: 174,190, 1,024: 23,765, 2,048: 0, 4,096: 0, 8,192: 0, 16,384: 0, 32,768: 0, 65,536: 0, 131,072: 0, 262,144: 0, 524,288: 0, 1,048,576: 0, 2,097,152: 0, 4,194,304: 0, 8,388,608: 0, 16,777,216: 0 Leaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): 368,675 (63.03%) Free List Area Stats: Total Free List Area Count (free_list_area_count): 474 Total Free List Area Bytes (free_list_area_bytes): 470,528 Free List Area Distribution: 16: 0, 32: 0, 64: 0, 96: 0, 128: 0, 256: 0, 512: 0, 768: 58, 1,024: 416, 2,048: 0, 4,096: 0, 8,192: 0, 16,384: 0, 32,768: 0, 65,536: 0, 131,072: 0, 262,144: 0, 524,288: 0, 1,048,576: 0, 2,097,152: 0, 4,194,304: 0, 8,388,608: 0, 16,777,216: 0 Alignment Stats: Trie Areas Spanning Extra Page Due to Unalignment: 75,388 (9.55%) Free List Areas Spanning Extra Page Due to Unalignment: 101 (21.31%) Trie Nodes Spanning Extra Page Due to Unalignment: 16,811 (2.13%%) Advanced Stats: Storage Overhead: high_watermark / kv_bytes = 8.42x Internal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = 75.61% Areas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = 487,040 (61.68%) ``` --- fwdctl/Cargo.toml | 2 + fwdctl/src/check.rs | 287 +++++++++++++++++++++++++++----------------- 2 files changed, 179 insertions(+), 110 deletions(-) diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index c062a6d9ae9b..0b979a48d93e 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -34,6 +34,8 @@ nonzero_ext.workspace = true # Regular dependencies csv = "1.3.1" indicatif = "0.18.0" +askama = "0.14.0" +num-format = "0.4.4" [features] ethhash = ["firewood/ethhash"] diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 76fd4583e265..513d3a924fe9 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -1,14 +1,17 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::collections::BTreeMap; +use std::path::PathBuf; use std::sync::Arc; -use std::{collections::BTreeMap, path::PathBuf}; +use askama::Template; use clap::Args; use firewood::v2::api; use firewood_storage::{CacheReadStrategy, CheckOpt, DBStats, FileBacked, NodeStore}; use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; use nonzero_ext::nonzero; +use num_format::{Locale, ToFormattedString}; use crate::DatabasePath; @@ -105,8 +108,117 @@ fn calculate_area_totals(area_counts: &BTreeMap) -> (u64, u64) { (total_area_count, total_area_bytes) } +#[derive(Template)] +#[template( + source = r" +Basic Stats: + Firewood Image Size / High Watermark (high_watermark): {{high_watermark}} + Total Key-Value Count (kv_count): {{kv_count}} + Total Key-Value Bytes (kv_bytes): {{kv_bytes}} + +Trie Stats: + Branching Factor Distribution: {{branching_factors}} + Depth Distribution: {{depths}} + +Branch Area Stats: + Total Branch Data Bytes (branch_bytes): {{branch_bytes}} + Total Branch Area Count (branch_area_count): {{total_branch_area_count}} + Total Branch Area Bytes (branch_area_bytes): {{total_branch_area_bytes}} + Branch Area Distribution: {{branch_area_counts}} + Branches that Can Fit Into Smaller Area (low_occupancy_branch_area): {{low_occupancy_branch_area_count}} ({{low_occupancy_branch_area_percent}}) + +Leaf Area Stats: + Total Leaf Data Bytes (leaf_bytes): {{leaf_bytes}} + Total Leaf Area Count (leaf_area_count): {{total_leaf_area_count}} + Total Leaf Area Bytes (leaf_area_bytes): {{total_leaf_area_bytes}} + Leaf Area Distribution: {{leaf_area_counts}} + Leaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): {{low_occupancy_leaf_area_count}} ({{low_occupancy_leaf_area_percent}}) + +Free List Area Stats: + Total Free List Area Count (free_list_area_count): {{total_free_list_area_count}} + Total Free List Area Bytes (free_list_area_bytes): {{total_free_list_area_bytes}} + Free List Area Distribution: {{free_list_area_counts}} + +Alignment Stats: + Trie Areas Spanning Extra Page Due to Unalignment: {{trie_area_extra_unaligned_page}} ({{trie_area_extra_unaligned_page_percent}}) + Free List Areas Spanning Extra Page Due to Unalignment: {{free_list_area_extra_unaligned_page}} ({{free_list_area_extra_unaligned_page_percent}}) + Trie Nodes Spanning Extra Page Due to Unalignment: {{trie_node_extra_unaligned_page}} ({{trie_node_extra_unaligned_page_percent}}%) + +Advanced Stats: + Storage Overhead: high_watermark / kv_bytes = {{storage_overhead}} + Internal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = {{internal_fragmentation}} + Areas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = {{low_occupancy_area_count}} ({{low_occupancy_area_percent}}) +", + ext = "txt" +)] +struct DBStatsReport { + // Basic stats + high_watermark: String, + kv_count: String, + kv_bytes: String, + // Trie stats + branching_factors: String, + depths: String, + // Branch area stats + branch_bytes: String, + total_branch_area_count: String, + total_branch_area_bytes: String, + branch_area_counts: String, + low_occupancy_branch_area_count: String, + low_occupancy_branch_area_percent: String, + // Leaf area stats + leaf_bytes: String, + total_leaf_area_count: String, + total_leaf_area_bytes: String, + leaf_area_counts: String, + low_occupancy_leaf_area_count: String, + low_occupancy_leaf_area_percent: String, + // Free list area stats + total_free_list_area_count: String, + total_free_list_area_bytes: String, + free_list_area_counts: String, + // Alignment stats + trie_area_extra_unaligned_page: String, + trie_area_extra_unaligned_page_percent: String, + free_list_area_extra_unaligned_page: String, + free_list_area_extra_unaligned_page_percent: String, + // Node stats + trie_node_extra_unaligned_page: String, + trie_node_extra_unaligned_page_percent: String, + // Advanced stats + storage_overhead: String, + internal_fragmentation: String, + low_occupancy_area_count: String, + low_occupancy_area_percent: String, +} + +fn format_u64(value: u64) -> String { + value.to_formatted_string(&Locale::en) +} + +fn format_map(map: &BTreeMap) -> String { + map.iter() + .map(|(key, value)| { + format!( + "{}: {}", + key.to_formatted_string(&Locale::en), + value.to_formatted_string(&Locale::en) + ) + }) + .collect::>() + .join(", ") +} + +#[expect(clippy::cast_precision_loss)] +fn format_percent(numerator: u64, denominator: u64) -> String { + format!("{:.2}%", (numerator as f64 / denominator as f64) * 100.0) +} + #[expect(clippy::cast_precision_loss)] -#[expect(clippy::too_many_lines)] +fn format_multiple(num: u64, base: u64) -> String { + format!("{:.2}x", num as f64 / base as f64) +} + fn print_stats_report(db_stats: DBStats) { let (total_branch_area_count, total_branch_area_bytes) = calculate_area_totals(&db_stats.trie_stats.branch_area_counts); @@ -118,119 +230,74 @@ fn print_stats_report(db_stats: DBStats) { let (total_free_list_area_count, total_free_list_area_bytes) = calculate_area_totals(&db_stats.free_list_stats.area_counts); - // Basic stats - println!("\nBasic Stats: "); - println!( - "\tFirewood Image Size / High Watermark (high_watermark): {}", - db_stats.high_watermark - ); - println!( - "\tTotal Key-Value Count (kv_count): {}", - db_stats.trie_stats.kv_count - ); - println!( - "\tTotal Key-Value Bytes (kv_bytes): {}", - db_stats.trie_stats.kv_bytes - ); - - // Trie statistics - println!("\nTrie Stats: "); - println!( - "\tBranching Factor Distribution: {:?}", - db_stats.trie_stats.branching_factors - ); - println!("\tDepth Distribution: {:?}", db_stats.trie_stats.depths); - - // Branch area distribution - println!("\nBranch Area Stats: "); - println!( - "\tTotal Branch Data Bytes (branch_bytes): {}", - db_stats.trie_stats.branch_bytes - ); - println!("\tTotal Branch Area Count (branch_area_count): {total_branch_area_count}"); - println!("\tTotal Branch Area Bytes (branch_area_bytes): {total_branch_area_bytes}"); - println!( - "\tBranch Area Distribution: {:?}", - db_stats.trie_stats.branch_area_counts - ); - println!( - "\tBranches that Can Fit Into Smaller Area (low_occupancy_branch_area): {} ({:.2}%)", - db_stats.trie_stats.low_occupancy_branch_area_count, - (db_stats.trie_stats.low_occupancy_branch_area_count as f64 - / total_branch_area_count as f64) - * 100.0 - ); - - // Leaf area distribution - println!("\nLeaf Area Stats: "); - println!( - "\tTotal Leaf Data Bytes (leaf_bytes): {}", - db_stats.trie_stats.leaf_bytes, - ); - println!("\tTotal Leaf Area Count (leaf_area_count): {total_leaf_area_count}"); - println!("\tTotal Leaf Area Bytes (leaf_area_bytes): {total_leaf_area_bytes}"); - println!( - "\tLeaf Area Distribution: {:?}", - db_stats.trie_stats.leaf_area_counts - ); - println!( - "\tLeaves that Can Fit Into Smaller Area (low_occupancy_leaf_area): {} ({:.2}%)", - db_stats.trie_stats.low_occupancy_leaf_area_count, - (db_stats.trie_stats.low_occupancy_leaf_area_count as f64 / total_leaf_area_count as f64) - * 100.0 - ); - - // Free list area distribution - println!("\nFree List Area Stats: "); - println!("\tFree List Area Counts (free_list_area_counts): {total_free_list_area_count}"); - println!("\tTotal Free List Area Bytes (free_list_area_bytes): {total_free_list_area_bytes}"); - println!( - "\tFree List Area Distribution: {:?}", - db_stats.free_list_stats.area_counts - ); - - // alignment stats - println!("\nAlignment Stats: "); - println!( - "\tTrie Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - db_stats.trie_stats.area_extra_unaligned_page, - (db_stats.trie_stats.area_extra_unaligned_page as f64 / total_trie_area_count as f64) - * 100.0 - ); - println!( - "\tFree List Areas Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - db_stats.free_list_stats.area_extra_unaligned_page, - (db_stats.free_list_stats.area_extra_unaligned_page as f64 - / total_free_list_area_count as f64) - * 100.0 - ); - println!( - "\tTrie Nodes Spanning Extra Page Due to Unalignment: {} ({:.2}%)", - db_stats.trie_stats.node_extra_unaligned_page, - (db_stats.trie_stats.node_extra_unaligned_page as f64 / total_trie_area_count as f64) - * 100.0 - ); - - println!("\nAdvanced Stats: "); - println!( - "\tStorage Overhead: high_watermark / kv_bytes = {:.2}x", - (db_stats.high_watermark as f64 / db_stats.trie_stats.kv_bytes as f64) - ); let total_trie_bytes = db_stats .trie_stats .branch_bytes .saturating_add(db_stats.trie_stats.leaf_bytes); - println!( - "\tInternal Fragmentation: 1 - (branch_bytes + leaf_bytes) / (branch_area_bytes + leaf_area_bytes) = {:.2}%", - (1f64 - (total_trie_bytes as f64 / total_trie_area_bytes as f64)) * 100.0 - ); - let low_occupancy_area_count = db_stats + let total_low_occupancy_area_count = db_stats .trie_stats .low_occupancy_branch_area_count .saturating_add(db_stats.trie_stats.low_occupancy_leaf_area_count); - println!( - "\tAreas that Can Fit Into Smaller Area: low_occupancy_branch_area + low_occupancy_leaf_area = {} ({:.2}%)", - low_occupancy_area_count, - (low_occupancy_area_count as f64 / total_trie_area_count as f64) * 100.0 - ); + + let report = DBStatsReport { + high_watermark: format_u64(db_stats.high_watermark), + kv_count: format_u64(db_stats.trie_stats.kv_count), + kv_bytes: format_u64(db_stats.trie_stats.kv_bytes), + branching_factors: format_map(&db_stats.trie_stats.branching_factors), + depths: format_map(&db_stats.trie_stats.depths), + branch_bytes: format_u64(db_stats.trie_stats.branch_bytes), + total_branch_area_count: format_u64(total_branch_area_count), + total_branch_area_bytes: format_u64(total_branch_area_bytes), + branch_area_counts: format_map(&db_stats.trie_stats.branch_area_counts), + low_occupancy_branch_area_count: format_u64( + db_stats.trie_stats.low_occupancy_branch_area_count, + ), + low_occupancy_branch_area_percent: format_percent( + db_stats.trie_stats.low_occupancy_branch_area_count, + total_branch_area_count, + ), + leaf_bytes: format_u64(db_stats.trie_stats.leaf_bytes), + total_leaf_area_count: format_u64(total_leaf_area_count), + total_leaf_area_bytes: format_u64(total_leaf_area_bytes), + leaf_area_counts: format_map(&db_stats.trie_stats.leaf_area_counts), + low_occupancy_leaf_area_count: format_u64( + db_stats.trie_stats.low_occupancy_leaf_area_count, + ), + low_occupancy_leaf_area_percent: format_percent( + db_stats.trie_stats.low_occupancy_leaf_area_count, + total_leaf_area_count, + ), + total_free_list_area_count: format_u64(total_free_list_area_count), + total_free_list_area_bytes: format_u64(total_free_list_area_bytes), + free_list_area_counts: format_map(&db_stats.free_list_stats.area_counts), + trie_area_extra_unaligned_page: format_u64(db_stats.trie_stats.area_extra_unaligned_page), + trie_area_extra_unaligned_page_percent: format_percent( + db_stats.trie_stats.area_extra_unaligned_page, + total_trie_area_count, + ), + free_list_area_extra_unaligned_page: format_u64( + db_stats.free_list_stats.area_extra_unaligned_page, + ), + free_list_area_extra_unaligned_page_percent: format_percent( + db_stats.free_list_stats.area_extra_unaligned_page, + total_free_list_area_count, + ), + trie_node_extra_unaligned_page: format_u64(db_stats.trie_stats.node_extra_unaligned_page), + trie_node_extra_unaligned_page_percent: format_percent( + db_stats.trie_stats.node_extra_unaligned_page, + total_trie_area_count, + ), + storage_overhead: format_multiple(db_stats.high_watermark, db_stats.trie_stats.kv_bytes), + internal_fragmentation: format_percent( + total_trie_area_bytes.saturating_sub(total_trie_bytes), + total_trie_area_bytes, + ), + low_occupancy_area_count: format_u64(total_low_occupancy_area_count), + low_occupancy_area_percent: format_percent( + total_low_occupancy_area_count, + total_trie_area_count, + ), + }; + + println!("{report}"); } From b91d20ad12eed19684a63d0d3b6467db929e08c9 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Fri, 29 Aug 2025 00:15:12 -0500 Subject: [PATCH 0930/1053] feat(checker): fix free lists when the erroneous area is the head (#1240) Here instead of fixing things in place, we create a new revision. This can also benefit future fixes for the trie. --- fwdctl/src/check.rs | 5 +- storage/src/checker/mod.rs | 131 ++++++++++++++++++++++----------- storage/src/nodestore/alloc.rs | 31 +++++--- storage/src/nodestore/mod.rs | 9 ++- 4 files changed, 122 insertions(+), 54 deletions(-) diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 513d3a924fe9..601747023e05 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -70,7 +70,10 @@ pub(super) fn run(opts: &Options) -> Result<(), api::Error> { let nodestore = NodeStore::open(storage)?; let db_stats = if opts.fix { - let report = nodestore.check_and_fix(check_ops); + let (nodestore, report) = nodestore.check_and_fix(check_ops); + if let Err(e) = nodestore { + println!("Error fixing database: {e}"); + } println!("Fixed Errors ({}):", report.fixed.len()); for error in report.fixed { println!("\t{error}"); diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 0d08de9a75cd..d746ff793421 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -8,9 +8,9 @@ use crate::logger::warn; use crate::nodestore::alloc::FreeAreaWithMetadata; use crate::nodestore::primitives::{AreaIndex, area_size_iter}; use crate::{ - CheckerError, Committed, FileIoError, FreeListParent, HashType, HashedNodeReader, IntoHashType, - LinearAddress, Node, NodeStore, Path, ReadableStorage, RootReader, StoredAreaParent, - TrieNodeParent, WritableStorage, + CheckerError, Committed, FileIoError, HashType, HashedNodeReader, ImmutableProposal, + IntoHashType, LinearAddress, MutableProposal, Node, NodeReader, NodeStore, Path, + ReadableStorage, RootReader, StoredAreaParent, TrieNodeParent, WritableStorage, }; #[cfg(not(feature = "ethhash"))] @@ -19,6 +19,7 @@ use crate::hashednode::hash_node; use std::cmp::Ordering; use std::collections::BTreeMap; use std::ops::Range; +use std::sync::Arc; use indicatif::ProgressBar; @@ -156,7 +157,10 @@ struct SubTrieMetadata { /// [`NodeStore`] checker #[expect(clippy::result_large_err)] -impl NodeStore { +impl NodeStore +where + NodeStore: HashedNodeReader, +{ /// Go through the filebacked storage and check for any inconsistencies. It proceeds in the following steps: /// 1. Check the header /// 2. traverse the trie and check the nodes @@ -572,12 +576,34 @@ pub struct FixReport { impl NodeStore { /// Check the node store and fix any errors found. /// Returns a report of the fix operation. - pub fn check_and_fix(&self, opt: CheckOpt) -> FixReport { - let report = self.check(opt); - self.fix(report) + // TODO: return a committed revision instead of an immutable proposal + pub fn check_and_fix( + &self, + opt: CheckOpt, + ) -> ( + Result, S>, FileIoError>, + FixReport, + ) { + let check_report = self.check(opt); + let mut proposal = match NodeStore::::new(self) { + Ok(proposal) => proposal, + Err(e) => { + let report = FixReport { + fixed: Vec::new(), + unfixable: check_report.errors.into_iter().map(|e| (e, None)).collect(), + db_stats: check_report.db_stats, + }; + return (Err(e), report); + } + }; + let fix_report = proposal.fix(check_report); + let immutable_proposal = NodeStore::, S>::try_from(proposal); + (immutable_proposal, fix_report) } +} - fn fix(&self, check_report: CheckerReport) -> FixReport { +impl NodeStore { + fn fix(&mut self, check_report: CheckerReport) -> FixReport { let mut fixed = Vec::new(); let mut unfixable = Vec::new(); @@ -587,22 +613,13 @@ impl NodeStore { warn!("Fix for trie node error not yet implemented"); unfixable.push((error, None)); } - Some(StoredAreaParent::FreeList(free_list_parent)) => match free_list_parent { - FreeListParent::FreeListHead(_) => { - warn!("Fix for free list head error not yet implemented"); - unfixable.push((error, None)); - } - FreeListParent::PrevFreeArea { - area_size_idx, - parent_addr, - } => { - if let Err(e) = self.truncate_free_list(area_size_idx, parent_addr) { - unfixable.push((error, Some(e))); - } else { - fixed.push(error); - } + Some(StoredAreaParent::FreeList(free_list_parent)) => { + if let Err(e) = self.truncate_free_list(free_list_parent) { + unfixable.push((error, Some(e))); + } else { + fixed.push(error); } - }, + } None => { if let CheckerError::AreaLeaks(ranges) = &error { { @@ -623,7 +640,12 @@ impl NodeStore { db_stats: check_report.db_stats, } } +} +impl NodeStore +where + NodeStore: NodeReader, +{ /// Wrapper around `split_into_leaked_areas` that iterates over a collection of ranges. fn split_all_leaked_ranges<'a>( &self, @@ -858,17 +880,19 @@ mod test { // Free list 1: 2048 bytes // Free list 2: 8192 bytes - // --------------------------------------------------------------------------------------------------------------------------- - // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | - // --------------------------------------------------------------------------------------------------------------------------- - // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes - // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned + // Free list 3: 96 bytes + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // | header | empty | free_list1_area3 | free_list1_area2 | overlap | free_list1_area1 | free_list2_area1 | free_list2_area2 | gap | free_list3_area1 | + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // ^ free_list1_area1 and free_list1_area2 overlap by 16 bytes ^ 1 byte + // ^ 16 empty bytes to ensure that free_list1_area1, free_list1_area2, and free_list2_area1 are page-aligned ^ missaligned #[expect(clippy::arithmetic_side_effects)] - fn gen_test_freelist_with_overlap( + fn gen_test_freelist_with_errors( nodestore: &mut NodeStore, ) -> TestFreelist { const AREA_INDEX1: AreaIndex = area_index!(9); // 2048 const AREA_INDEX2: AreaIndex = area_index!(12); // 16384 + const AREA_INDEX3: AreaIndex = area_index!(3); // 96 let mut free_lists = FreeLists::default(); let mut high_watermark = NodeStoreHeader::SIZE + 16; // + 16 to create overlap @@ -910,24 +934,42 @@ mod test { let free_list2_area1 = LinearAddress::new(high_watermark).unwrap(); next_free_block2 = Some(free_list2_area1); high_watermark += area_size2; + let expected_high_watermark = high_watermark; free_lists[AREA_INDEX2.as_usize()] = next_free_block2; + // third free list + high_watermark += 1; // + 1 to create gap + let area_size3 = AREA_INDEX3.size(); + let mut next_free_block3 = None; + test_write_free_area(nodestore, next_free_block3, AREA_INDEX3, high_watermark); + let free_list3_area1 = LinearAddress::new(high_watermark).unwrap(); + next_free_block3 = Some(free_list3_area1); + high_watermark += area_size3; + + free_lists[AREA_INDEX3.as_usize()] = next_free_block3; + // write header test_write_header(nodestore, high_watermark, None, free_lists); let expected_start_addr = free_lists[AREA_INDEX1.as_usize()].unwrap(); - let expected_end_addr = LinearAddress::new(high_watermark).unwrap(); + let expected_end_addr = LinearAddress::new(expected_high_watermark).unwrap(); let expected_free_areas = vec![expected_start_addr..expected_end_addr]; - let expected_freelist_errors = vec![CheckerError::AreaIntersects { - start: free_list1_area2, - size: area_size1, - intersection: vec![intersection_start..intersection_end], - parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea { - area_size_idx: AREA_INDEX1, - parent_addr: free_list1_area1, - }), - }]; + let expected_freelist_errors = vec![ + CheckerError::AreaMisaligned { + address: free_list3_area1, + parent: StoredAreaParent::FreeList(FreeListParent::FreeListHead(AREA_INDEX3)), + }, + CheckerError::AreaIntersects { + start: free_list1_area2, + size: area_size1, + intersection: vec![intersection_start..intersection_end], + parent: StoredAreaParent::FreeList(FreeListParent::PrevFreeArea { + area_size_idx: AREA_INDEX1, + parent_addr: free_list1_area1, + }), + }, + ]; let mut area_counts = area_size_iter() .map(|(_, size)| (size, 0)) .collect::>(); @@ -1098,7 +1140,7 @@ mod test { free_ranges, errors, stats, - } = gen_test_freelist_with_overlap(&mut nodestore); + } = gen_test_freelist_with_errors(&mut nodestore); // test that the we traversed all the free areas let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); @@ -1118,11 +1160,12 @@ mod test { free_ranges: _, errors, stats, - } = gen_test_freelist_with_overlap(&mut nodestore); + } = gen_test_freelist_with_errors(&mut nodestore); let expected_error_num = errors.len(); // fix the freelist - let fix_report = nodestore.fix(CheckerReport { + let mut proposal = NodeStore::::new(&nodestore).unwrap(); + let fix_report = proposal.fix(CheckerReport { errors, db_stats: DBStats { high_watermark, @@ -1133,8 +1176,10 @@ mod test { assert_eq!(fix_report.fixed.len(), expected_error_num); assert_eq!(fix_report.unfixable.len(), 0); + let immutable_proposal = + NodeStore::, _>::try_from(proposal).unwrap(); let mut visited = LinearAddressRangeSet::new(high_watermark).unwrap(); - let (_, free_list_errors) = nodestore.visit_freelist(&mut visited, None); + let (_, free_list_errors) = immutable_proposal.visit_freelist(&mut visited, None); assert_eq!(free_list_errors, vec![]); } diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 55bb8d63abef..e34097b5b642 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -527,17 +527,30 @@ impl NodeStore { } // Functionalities use by the checker -impl NodeStore { +impl NodeStore { pub(crate) fn truncate_free_list( - &self, - area_size_index: AreaIndex, - addr: LinearAddress, + &mut self, + free_list_parent: FreeListParent, ) -> Result<(), FileIoError> { - let free_area = FreeArea::new(None); - let mut stored_area_bytes = Vec::new(); - free_area.as_bytes(area_size_index, &mut stored_area_bytes); - self.storage.write(addr.into(), &stored_area_bytes)?; - Ok(()) + match free_list_parent { + FreeListParent::FreeListHead(area_size_index) => { + *self + .freelists_mut() + .get_mut(area_size_index.as_usize()) + .expect("area_size_index is less than AreaIndex::NUM_AREA_SIZES") = None; + Ok(()) + } + FreeListParent::PrevFreeArea { + area_size_idx, + parent_addr, + } => { + let free_area = FreeArea::new(None); + let mut stored_area_bytes = Vec::new(); + free_area.as_bytes(area_size_idx, &mut stored_area_bytes); + self.storage.write(parent_addr.into(), &stored_area_bytes)?; + Ok(()) + } + } } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index d8f990ba3170..65fc1f1d729e 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -470,6 +470,10 @@ impl NodeStore { pub(crate) const fn freelists(&self) -> &alloc::FreeLists { self.header.free_lists() } + + pub(crate) const fn freelists_mut(&mut self) -> &mut alloc::FreeLists { + self.header.free_lists_mut() + } } /// Contains the state of a proposal that is still being modified. @@ -813,7 +817,10 @@ impl NodeStore { } // Helper functions for the checker -impl NodeStore { +impl NodeStore +where + NodeStore: NodeReader, +{ pub(crate) const fn size(&self) -> u64 { self.header.size() } From b57f5b46312f7a962e3c2f6625078e4184db16b5 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 4 Sep 2025 11:58:52 -0700 Subject: [PATCH 0931/1053] chore: Add missing debug traits (#1254) Debugging range proofs is easier when things are printable. --- storage/src/hashednode.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 13279d72b0f8..e50d0c16112b 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -127,7 +127,7 @@ impl> ValueDigest { } /// A node in the trie that can be hashed. -pub trait Hashable { +pub trait Hashable: std::fmt::Debug { /// The key of the node where each byte is a nibble. fn key(&self) -> impl Iterator + Clone; /// The partial path of this node @@ -141,14 +141,14 @@ pub trait Hashable { } /// A preimage of a hash. -pub trait Preimage { +pub trait Preimage: std::fmt::Debug { /// Returns the hash of this preimage. fn to_hash(&self) -> HashType; /// Write this hash preimage to `buf`. fn write(&self, buf: &mut impl HasUpdate); } -trait HashableNode { +trait HashableNode: std::fmt::Debug { fn partial_path(&self) -> impl Iterator + Clone; fn value(&self) -> Option<&[u8]>; fn child_hashes(&self) -> Children; @@ -182,6 +182,7 @@ impl HashableNode for LeafNode { } } +#[derive(Debug)] struct NodeAndPrefix<'a, N: HashableNode> { node: &'a N, prefix: &'a Path, From 0098d123e103844e2d7dfc032e911be52da80489 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 5 Sep 2025 07:26:18 -0700 Subject: [PATCH 0932/1053] feat(ffi-proofs): Stub interfaces for FFI to interact with proofs. (#1253) This contains a stubbed api for the FFI library to build range and change proofs as well as verify and commit. No methods in the FFI library itself are implemented except for the free handlers. --- .gitattributes | 2 + ffi/Cargo.toml | 2 +- ffi/firewood.h | 777 +++++++++++++++++++++++++++++++++++++-- ffi/maybe.go | 54 +++ ffi/memory.go | 30 ++ ffi/proofs.go | 397 ++++++++++++++++++++ ffi/src/lib.rs | 2 + ffi/src/proofs.rs | 8 + ffi/src/proofs/change.rs | 261 +++++++++++++ ffi/src/proofs/range.rs | 280 ++++++++++++++ ffi/src/value.rs | 18 +- ffi/src/value/kvp.rs | 9 + ffi/src/value/results.rs | 158 ++++++-- 13 files changed, 1931 insertions(+), 67 deletions(-) create mode 100644 .gitattributes create mode 100644 ffi/maybe.go create mode 100644 ffi/proofs.go create mode 100644 ffi/src/proofs.rs create mode 100644 ffi/src/proofs/change.rs create mode 100644 ffi/src/proofs/range.rs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..e43ce2d10eaa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Hint that ffi/firewood.h is a generated file for GitHub Linguist +ffi/firewood.h linguist-generated=true diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 624fa636a3ca..881d3b3faf80 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,6 +23,7 @@ crate-type = ["staticlib"] # Workspace dependencies coarsetime.workspace = true firewood.workspace = true +firewood-storage.workspace = true metrics.workspace = true metrics-util.workspace = true # Regular dependencies @@ -36,7 +37,6 @@ tikv-jemallocator = "0.6.0" [dev-dependencies] # Workspace dependencies -firewood-storage.workspace = true test-case.workspace = true [features] diff --git a/ffi/firewood.h b/ffi/firewood.h index e06da04637ec..bb5803cb1488 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -11,6 +11,11 @@ #include +/** + * FFI context for a parsed or generated change proof. + */ +typedef struct ChangeProofContext ChangeProofContext; + /** * A handle to the database, returned by `fwd_open_db`. * @@ -19,6 +24,11 @@ */ typedef struct DatabaseHandle DatabaseHandle; +/** + * FFI context for for a parsed or generated range proof. + */ +typedef struct RangeProofContext RangeProofContext; + /** * A database hash key, used in FFI functions that require hashes. * This type requires no allocation and can be copied freely and @@ -178,20 +188,68 @@ typedef struct BorrowedSlice_KeyValuePair { typedef struct BorrowedSlice_KeyValuePair BorrowedKeyValuePairs; /** - * The result type returned from an FFI function that returns no value but may - * return an error. + * Maybe is a C-compatible optional type using a tagged union pattern. + * + * FFI methods and types can use this to represent optional values where `Optional` + * does not work due to it not having C-compatible layout. */ -typedef enum VoidResult_Tag { +typedef enum Maybe_OwnedBytes_Tag { + /** + * No value present. + */ + Maybe_OwnedBytes_None_OwnedBytes, + /** + * A value is present. + */ + Maybe_OwnedBytes_Some_OwnedBytes, +} Maybe_OwnedBytes_Tag; + +typedef struct Maybe_OwnedBytes { + Maybe_OwnedBytes_Tag tag; + union { + struct { + OwnedBytes some; + }; + }; +} Maybe_OwnedBytes; + +/** + * A key range that should be fetched to continue iterating through a range + * or change proof that was truncated. Represents a half-open range + * `[start_key, end_key)`. If `end_key` is `None`, the range is unbounded + * and continues to the end of the keyspace. + */ +typedef struct NextKeyRange { + /** + * The start key of the next range to fetch. + */ + OwnedBytes start_key; + /** + * If set, a non-inclusive upper bound for the next range to fetch. If not + * set, the range is unbounded (this is the final range). + */ + struct Maybe_OwnedBytes end_key; +} NextKeyRange; + +typedef enum NextKeyRangeResult_Tag { /** * The caller provided a null pointer to the input handle. */ - VoidResult_NullHandlePointer, + NextKeyRangeResult_NullHandlePointer, /** - * The operation was successful and no error occurred. + * The proof has not prepared into a proposal nor committed to the database. */ - VoidResult_Ok, + NextKeyRangeResult_NotPrepared, /** - * An error occurred and the message is returned as an [`OwnedBytes`]. Its + * There are no more keys to fetch. + */ + NextKeyRangeResult_None, + /** + * The next key range to fetch is returned. + */ + NextKeyRangeResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If * value is guaranteed to contain only valid UTF-8. * * The caller must call [`fwd_free_owned_bytes`] to free the memory @@ -199,37 +257,73 @@ typedef enum VoidResult_Tag { * * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes */ - VoidResult_Err, -} VoidResult_Tag; + NextKeyRangeResult_Err, +} NextKeyRangeResult_Tag; -typedef struct VoidResult { - VoidResult_Tag tag; +typedef struct NextKeyRangeResult { + NextKeyRangeResult_Tag tag; union { + struct { + struct NextKeyRange some; + }; struct { OwnedBytes err; }; }; -} VoidResult; +} NextKeyRangeResult; /** - * A value returned by the FFI. - * - * This is used in several different ways, including: - * * An C-style string. - * * An ID for a proposal. - * * A byte slice containing data. - * - * For more details on how the data may be stored, refer to the function signature - * that returned it or the `From` implementations. + * A result type returned from FFI functions that create or parse change proofs. * - * The data stored in this struct (if `data` is not null) must be manually freed - * by the caller using `fwd_free_value`. + * The caller must ensure that [`fwd_free_change_proof`] is called to + * free the memory associated with the returned context when it is no longer + * needed. * + * [`fwd_free_change_proof`]: crate::fwd_free_change_proof */ -typedef struct Value { - size_t len; - uint8_t *data; -} Value; +typedef enum ChangeProofResult_Tag { + /** + * The caller provided a null pointer to the input handle. + */ + ChangeProofResult_NullHandlePointer, + /** + * The provided root was not found in the database. + */ + ChangeProofResult_RevisionNotFound, + /** + * The proof was successfully created or parsed. + * + * If the value was parsed from a serialized proof, this does not imply that + * the proof is valid, only that it is well-formed. The verify method must + * be called to ensure the proof is cryptographically valid. + */ + ChangeProofResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + ChangeProofResult_Err, +} ChangeProofResult_Tag; + +typedef struct ChangeProofResult { + ChangeProofResult_Tag tag; + union { + struct { + struct HashKey revision_not_found; + }; + struct { + struct ChangeProofContext *ok; + }; + struct { + OwnedBytes err; + }; + }; +} ChangeProofResult; /** * A result type returned from FFI functions that retrieve a single value. @@ -283,6 +377,284 @@ typedef struct ValueResult { }; } ValueResult; +/** + * The result type returned from an FFI function that returns no value but may + * return an error. + */ +typedef enum VoidResult_Tag { + /** + * The caller provided a null pointer to the input handle. + */ + VoidResult_NullHandlePointer, + /** + * The operation was successful and no error occurred. + */ + VoidResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. Its + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + VoidResult_Err, +} VoidResult_Tag; + +typedef struct VoidResult { + VoidResult_Tag tag; + union { + struct { + OwnedBytes err; + }; + }; +} VoidResult; + +/** + * Maybe is a C-compatible optional type using a tagged union pattern. + * + * FFI methods and types can use this to represent optional values where `Optional` + * does not work due to it not having C-compatible layout. + */ +typedef enum Maybe_BorrowedBytes_Tag { + /** + * No value present. + */ + Maybe_BorrowedBytes_None_BorrowedBytes, + /** + * A value is present. + */ + Maybe_BorrowedBytes_Some_BorrowedBytes, +} Maybe_BorrowedBytes_Tag; + +typedef struct Maybe_BorrowedBytes { + Maybe_BorrowedBytes_Tag tag; + union { + struct { + BorrowedBytes some; + }; + }; +} Maybe_BorrowedBytes; + +/** + * Arguments for creating a change proof. + */ +typedef struct CreateChangeProofArgs { + /** + * The root hash of the starting revision. This must be provided. + * If the root is not found in the database, the function will return + * [`ChangeProofResult::RevisionNotFound`]. + */ + BorrowedBytes start_root; + /** + * The root hash of the ending revision. This must be provided. + * If the root is not found in the database, the function will return + * [`ChangeProofResult::RevisionNotFound`]. + */ + BorrowedBytes end_root; + /** + * The start key of the range to create the proof for. If `None`, the range + * starts from the beginning of the keyspace. + */ + struct Maybe_BorrowedBytes start_key; + /** + * The end key of the range to create the proof for. If `None`, the range + * ends at the end of the keyspace or until `max_length` items have been + * included in the proof. + */ + struct Maybe_BorrowedBytes end_key; + /** + * The maximum number of key/value pairs to include in the proof. If the + * range contains more items than this, the proof will be truncated. If + * `0`, there is no limit. + */ + uint32_t max_length; +} CreateChangeProofArgs; + +/** + * A result type returned from FFI functions that create or parse range proofs. + * + * The caller must ensure that [`fwd_free_range_proof`] is called to + * free the memory associated with the returned context when it is no longer + * needed. + * + * [`fwd_free_range_proof`]: crate::fwd_free_range_proof + */ +typedef enum RangeProofResult_Tag { + /** + * The caller provided a null pointer to the input handle. + */ + RangeProofResult_NullHandlePointer, + /** + * The provided root was not found in the database. + */ + RangeProofResult_RevisionNotFound, + /** + * The proof was successfully created or parsed. + * + * If the value was parsed from a serialized proof, this does not imply that + * the proof is valid, only that it is well-formed. The verify method must + * be called to ensure the proof is cryptographically valid. + */ + RangeProofResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + RangeProofResult_Err, +} RangeProofResult_Tag; + +typedef struct RangeProofResult { + RangeProofResult_Tag tag; + union { + struct { + struct HashKey revision_not_found; + }; + struct { + struct RangeProofContext *ok; + }; + struct { + OwnedBytes err; + }; + }; +} RangeProofResult; + +/** + * Arguments for creating a range proof. + */ +typedef struct CreateRangeProofArgs { + /** + * The root hash of the revision to prove. If `None`, the latest revision + * is used. + */ + struct Maybe_BorrowedBytes root; + /** + * The start key of the range to prove. If `None`, the range starts from the + * beginning of the keyspace. + * + * The start key must be less than the end key if both are provided. + */ + struct Maybe_BorrowedBytes start_key; + /** + * The end key of the range to prove. If `None`, the range ends at the end + * of the keyspace or until `max_length` items have been been included in + * the proof. + * + * If provided, end key is inclusive if not truncated. Otherwise, the end + * key will be the final key in the returned key-value pairs. + */ + struct Maybe_BorrowedBytes end_key; + /** + * The maximum number of key/value pairs to include in the proof. If the + * range contains more items than this, the proof will be truncated. If + * `0`, there is no limit. + */ + uint32_t max_length; +} CreateRangeProofArgs; + +/** + * Arguments for verifying a change proof. + */ +typedef struct VerifyChangeProofArgs { + /** + * The change proof to verify. If null, the function will return + * [`VoidResult::NullHandlePointer`]. We need a mutable reference to + * update the validation context. + */ + struct ChangeProofContext *proof; + /** + * The root hash of the starting revision. This must match the starting + * root of the proof. + */ + BorrowedBytes start_root; + /** + * The root hash of the ending revision. This must match the ending root of + * the proof. + */ + BorrowedBytes end_root; + /** + * The lower bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover from the start of the keyspace. + */ + struct Maybe_BorrowedBytes start_key; + /** + * The upper bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover to the end of the keyspace. + */ + struct Maybe_BorrowedBytes end_key; + /** + * The maximum number of key/value pairs that the proof is expected to cover. + * If the proof contains more items than this, it is considered invalid. If + * `0`, there is no limit. + */ + uint32_t max_length; +} VerifyChangeProofArgs; + +/** + * Arguments for verifying a range proof. + */ +typedef struct VerifyRangeProofArgs { + /** + * The range proof to verify. If null, the function will return + * [`VoidResult::NullHandlePointer`]. We need a mutable reference to + * update the validation context. + */ + struct RangeProofContext *proof; + /** + * The root hash to verify the proof against. This must match the calculated + * hash of the root of the proof. + */ + BorrowedBytes root; + /** + * The lower bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover from the start of the keyspace. + * + * Must be present if the range proof contains a lower bound proof and must + * be absent if the range proof does not contain a lower bound proof. + */ + struct Maybe_BorrowedBytes start_key; + /** + * The upper bound of the key range that the proof is expected to cover. If + * `None`, the proof is expected to cover to the end of the keyspace. + * + * This is ignored if the proof is truncated and does not cover the full, + * in which case the upper bound key is the final key in the key-value pairs. + */ + struct Maybe_BorrowedBytes end_key; + /** + * The maximum number of key/value pairs that the proof is expected to cover. + * If the proof contains more items than this, it is considered invalid. If + * `0`, there is no limit. + */ + uint32_t max_length; +} VerifyRangeProofArgs; + +/** + * A value returned by the FFI. + * + * This is used in several different ways, including: + * * An C-style string. + * * An ID for a proposal. + * * A byte slice containing data. + * + * For more details on how the data may be stored, refer to the function signature + * that returned it or the `From` implementations. + * + * The data stored in this struct (if `data` is not null) must be manually freed + * by the caller using `fwd_free_value`. + * + */ +typedef struct Value { + size_t len; + uint8_t *data; +} Value; + typedef uint32_t ProposalId; /** @@ -419,6 +791,74 @@ typedef struct LogArgs { */ struct HashResult fwd_batch(const struct DatabaseHandle *db, BorrowedKeyValuePairs values); +/** + * Returns the next key range that should be fetched after processing the + * current set of operations in a change proof that was truncated. + * + * Can be called multiple times to get subsequent disjoint key ranges until + * it returns [`NextKeyRangeResult::None`], indicating there are no more keys to + * fetch and the proof is complete. + * + * # Arguments + * + * - `proof` - A [`ChangeProofContext`] previously returned from the create + * methods and has been prepared into a proposal or already committed. + * + * # Returns + * + * - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into + * a proposal nor committed to the database. + * - [`NextKeyRangeResult::None`] if there are no more keys to fetch. + * - [`NextKeyRangeResult::Some`] containing the next key range to fetch. + * - [`NextKeyRangeResult::Err`] containing an error message if the next key range + * could not be determined. + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct NextKeyRangeResult fwd_change_proof_find_next_key(struct ChangeProofContext *_proof); + +/** + * Deserialize a `ChangeProof` from bytes. + * + * # Arguments + * + * * `bytes` - The bytes to deserialize the proof from. + * + * # Returns + * + * - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null or zero-length slice. + * - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof + * was successfully parsed. This does not imply that the proof is valid, only that it is + * well-formed. The verify method must be called to ensure the proof is cryptographically valid. + * - [`ChangeProofResult::Err`] containing an error message if the proof could not be parsed. + */ +struct ChangeProofResult fwd_change_proof_from_bytes(BorrowedBytes _bytes); + +/** + * Serialize a `ChangeProof` to bytes. + * + * # Arguments + * + * - `proof` - A [`ChangeProofContext`] previously returned from the create + * method. If from a parsed proof, the proof will not be verified before + * serialization. + * + * # Returns + * + * - [`ValueResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`ValueResult::Some`] containing the serialized bytes if successful. + * - [`ValueResult::Err`] if the caller provided a null pointer. + * + * The other [`ValueResult`] variants are not used. + */ +struct ValueResult fwd_change_proof_to_bytes(const struct ChangeProofContext *_proof); + /** * Close and free the memory for a database handle * @@ -462,6 +902,171 @@ struct VoidResult fwd_close_db(struct DatabaseHandle *db); */ struct HashResult fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); +/** + * Create a change proof for the given range of keys between two roots. + * + * # Arguments + * + * - `db` - The database to create the proof from. + * - `args` - The arguments for creating the change proof. + * + * # Returns + * + * - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`ChangeProofResult::RevisionNotFound`] if the caller provided a start or end root + * that was not found in the database. The missing root hash is included in the result. + * The start root is checked first, and if both are missing, only the start root is + * reported. + * - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof + * was successfully created. + * - [`ChangeProofResult::Err`] containing an error message if the proof could not be created. + */ +struct ChangeProofResult fwd_db_change_proof(const struct DatabaseHandle *_db, + struct CreateChangeProofArgs _args); + +/** + * Generate a range proof for the given range of keys for the latest revision. + * + * # Arguments + * + * - `db` - The database to create the proof from. + * - `args` - The arguments for creating the range proof. + * + * # Returns + * + * - [`RangeProofResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`RangeProofResult::RevisionNotFound`] if the caller provided a root that was + * not found in the database. The missing root hash is included in the result. + * - [`RangeProofResult::Ok`] containing a pointer to the `RangeProofContext` if the proof + * was successfully created. + * - [`RangeProofResult::Err`] containing an error message if the proof could not be created. + */ +struct RangeProofResult fwd_db_range_proof(const struct DatabaseHandle *_db, + struct CreateRangeProofArgs _args); + +/** + * Verify and commit a change proof to the database. + * + * If the proof has already been verified, the previously prepared proposal will be + * committed instead of re-verifying. If the proof has not been verified, it will be + * verified now. If the prepared proposal is no longer valid (e.g., the database has + * changed since it was prepared), a new proposal will be created and committed. + * + * The proof context will be updated with additional information about the committed + * proof to allow for optimized introspection of the committed changes. + * + * # Arguments + * + * - `db` - The database to commit the changes to. + * - `args` - The arguments for verifying the change proof. + * + * # Returns + * + * - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either + * the database or the proof. + * - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). + * - [`HashResult::Some`] containing the new root hash if the proof was successfully verified + * - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. + * + * [`fwd_commit`]: crate::fwd_commit + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct HashResult fwd_db_verify_and_commit_change_proof(const struct DatabaseHandle *_db, + struct VerifyChangeProofArgs _args); + +/** + * Verify and commit a range proof to the database. + * + * If a proposal was previously prepared by a call to [`fwd_db_verify_range_proof`], + * it will be committed instead of re-verifying the proof. If the proof has not yet + * been verified, it will be verified now. If the prepared proposal is no longer + * valid (e.g., the database has changed since it was prepared), a new proposal + * will be created and committed. + * + * The proof context will be updated with additional information about the committed + * proof to allow for optimized introspection of the committed changes. + * + * # Arguments + * + * - `db` - The database to commit the changes to. + * - `args` - The arguments for verifying the range proof. + * + * # Returns + * + * - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either + * the database or the proof. + * - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). + * - [`HashResult::Some`] containing the new root hash if the proof was successfully verified + * - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct HashResult fwd_db_verify_and_commit_range_proof(const struct DatabaseHandle *_db, + struct VerifyRangeProofArgs _args); + +/** + * Verify a change proof and prepare a proposal to later commit or drop. + * + * # Arguments + * + * - `db` - The database to verify the proof against. + * - `args` - The arguments for verifying the change proof. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to either + * the database or the proof. + * - [`VoidResult::Ok`] if the proof was successfully verified. + * - [`VoidResult::Err`] containing an error message if the proof could not be verified + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct VoidResult fwd_db_verify_change_proof(const struct DatabaseHandle *_db, + struct VerifyChangeProofArgs _args); + +/** + * Verify a range proof and prepare a proposal to later commit or drop. If the + * proof has already been verified, the cached validation context will be used + * to avoid re-verifying the proof. + * + * # Arguments + * + * - `db` - The database to verify the proof against. + * - `args` - The arguments for verifying the range proof. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to either + * the database or the proof. + * - [`VoidResult::Ok`] if the proof was successfully verified. + * - [`VoidResult::Err`] containing an error message if the proof could not be verified + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, + struct VerifyRangeProofArgs _args); + /** * Drops a proposal from the database. * The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. @@ -479,6 +1084,20 @@ struct HashResult fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_ */ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposal_id); +/** + * Frees the memory associated with a `ChangeProofContext`. + * + * # Arguments + * + * * `proof` - The `ChangeProofContext` to free, previously returned from any Rust function. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + */ +struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); + /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. * @@ -500,6 +1119,20 @@ struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposa */ struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); +/** + * Frees the memory associated with a `RangeProofContext`. + * + * # Arguments + * + * * `proof` - The `RangeProofContext` to free, previously returned from any Rust function. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + */ +struct VoidResult fwd_free_range_proof(struct RangeProofContext *proof); + /** * Frees the memory associated with a `Value`. * @@ -700,6 +1333,96 @@ struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, ProposalId proposal_id, BorrowedKeyValuePairs values); +/** + * Returns the next key range that should be fetched after processing the + * current set of key-value pairs in a range proof that was truncated. + * + * Can be called multiple times to get subsequent disjoint key ranges until + * it returns [`NextKeyRangeResult::None`], indicating there are no more keys to + * fetch and the proof is complete. + * + * # Arguments + * + * - `proof` - A [`RangeProofContext`] previously returned from the create + * methods and has been prepared into a proposal or already committed. + * + * # Returns + * + * - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into + * a proposal nor committed to the database. + * - [`NextKeyRangeResult::None`] if there are no more keys to fetch. + * - [`NextKeyRangeResult::Some`] containing the next key range to fetch. + * - [`NextKeyRangeResult::Err`] containing an error message if the next key range + * could not be determined. + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct NextKeyRangeResult fwd_range_proof_find_next_key(struct RangeProofContext *_proof); + +/** + * Deserialize a `RangeProof` from bytes. + * + * # Arguments + * + * - `bytes` - The bytes to deserialize the proof from. + * + * # Returns + * + * - [`RangeProofResult::NullHandlePointer`] if the caller provided a null or zero-length slice. + * - [`RangeProofResult::Ok`] containing a pointer to the `RangeProofContext` if the proof + * was successfully parsed. This does not imply that the proof is valid, only that it is + * well-formed. The verify method must be called to ensure the proof is cryptographically valid. + * - [`RangeProofResult::Err`] containing an error message if the proof could not be parsed. + */ +struct RangeProofResult fwd_range_proof_from_bytes(BorrowedBytes _bytes); + +/** + * Serialize a `RangeProof` to bytes. + * + * # Arguments + * + * - `proof` - A [`RangeProofContext`] previously returned from the create + * method. If from a parsed proof, the proof will not be verified before + * serialization. + * + * # Returns + * + * - [`ValueResult::NullHandlePointer`] if the caller provided a null pointer. + * - [`ValueResult::Some`] containing the serialized bytes if successful. + * - [`ValueResult::Err`] if the caller provided a null pointer. + */ +struct ValueResult fwd_range_proof_to_bytes(const struct RangeProofContext *_proof); + +/** + * Verify a range proof against the given start and end keys and root hash. The + * proof will be updated with the validation context if the proof is valid to + * avoid re-verifying it during commit. + * + * # Arguments + * + * - `args` - The arguments for verifying the range proof. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to the proof. + * - [`VoidResult::Ok`] if the proof was successfully verified. + * - [`VoidResult::Err`] containing an error message if the proof could not be verified. + * + * # Thread Safety + * + * It is not safe to call this function concurrently with the same proof context + * nor is it safe to call any other function that accesses the same proof context + * concurrently. The caller must ensure exclusive access to the proof context + * for the duration of the call. + */ +struct VoidResult fwd_range_proof_verify(struct VerifyRangeProofArgs _args); + /** * Get the root hash of the latest version of the database * diff --git a/ffi/maybe.go b/ffi/maybe.go new file mode 100644 index 000000000000..baa2a7eca7fe --- /dev/null +++ b/ffi/maybe.go @@ -0,0 +1,54 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +// #include +// #include "firewood.h" +import "C" + +import ( + "unsafe" +) + +// Maybe is an interface that represents an optional value of type T. +// +// Maybe is a drop-in replacement for the Maybe type defined in avalanchego/utils/maybe. +// This interface is used to avoid importing avalanchego packages into the ffi +// package, which would create a circular dependency. +// +// +// +// The avalanchego implementation of Maybe implements this interface. +type Maybe[T any] interface { + // HasValue returns true if the Maybe contains a value. + HasValue() bool + // Value returns the value contained in the Maybe. + // + // Implementations may panic if the Maybe contains no value but can also + // return the zero value of T. + Value() T +} + +func newMaybeBorrowedBytes(maybe Maybe[[]byte], pinner Pinner) C.Maybe_BorrowedBytes { + var cMaybe C.Maybe_BorrowedBytes + + if maybe != nil && maybe.HasValue() { + cMaybeBorrowedBytesPtr := (*C.BorrowedBytes)(unsafe.Pointer(&cMaybe.anon0)) + *cMaybeBorrowedBytesPtr = newBorrowedBytes(maybe.Value(), pinner) + + cMaybe.tag = C.Maybe_BorrowedBytes_Some_BorrowedBytes + } else { + cMaybe.tag = C.Maybe_BorrowedBytes_None_BorrowedBytes + } + + return cMaybe +} + +func (b *ownedBytes) HasValue() bool { + return b != nil +} + +func (b *ownedBytes) Value() *ownedBytes { + return b +} diff --git a/ffi/memory.go b/ffi/memory.go index a3b8061212d2..c6908e105db7 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -348,6 +348,36 @@ func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) { } } +func getRangeProofFromRangeProofResult(result C.RangeProofResult) (*RangeProof, error) { + switch result.tag { + case C.RangeProofResult_NullHandlePointer: + return nil, errDBClosed + case C.RangeProofResult_Ok: + ptr := *(**C.RangeProofContext)(unsafe.Pointer(&result.anon0)) + return &RangeProof{handle: ptr}, nil + case C.RangeProofResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.RangeProofResult tag: %d", result.tag) + } +} + +func getChangeProofFromChangeProofResult(result C.ChangeProofResult) (*ChangeProof, error) { + switch result.tag { + case C.ChangeProofResult_NullHandlePointer: + return nil, errDBClosed + case C.ChangeProofResult_Ok: + ptr := *(**C.ChangeProofContext)(unsafe.Pointer(&result.anon0)) + return &ChangeProof{handle: ptr}, nil + case C.ChangeProofResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.ChangeProofResult tag: %d", result.tag) + } +} + // hashAndIDFromValue converts the cgo `Value` payload into: // // case | data | len | meaning diff --git a/ffi/proofs.go b/ffi/proofs.go new file mode 100644 index 000000000000..e8a0d18090c1 --- /dev/null +++ b/ffi/proofs.go @@ -0,0 +1,397 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +// #include +// #include "firewood.h" +import "C" + +import ( + "errors" + "fmt" + "runtime" + "unsafe" +) + +var errNotPrepared = errors.New("proof not prepared into a proposal or committed") + +// RangeProof represents a proof that a range of keys and their values are +// included in a trie with a given root hash. +type RangeProof struct { + handle *C.RangeProofContext +} + +// ChangeProof represents a proof of changes between two roots for a range of keys. +type ChangeProof struct { + handle *C.ChangeProofContext +} + +// NextKeyRange represents a range of keys to fetch from the database. The start +// key is inclusive while the end key is exclusive. If the end key is Nothing, +// the range is unbounded in that direction. +type NextKeyRange struct { + startKey *ownedBytes + endKey Maybe[*ownedBytes] +} + +// RangeProof returns a proof that the values in the range [startKey, endKey] are +// included in the tree with the current root. The proof may be truncated to at +// most [maxLength] entries, if non-zero. If either [startKey] or [endKey] is +// Nothing, the range is unbounded in that direction. If [rootHash] is Nothing, the +// current root of the database is used. +func (db *Database) RangeProof( + rootHash, startKey, endKey Maybe[[]byte], + maxLength uint32, +) (*RangeProof, error) { + if db.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.CreateRangeProofArgs{ + root: newMaybeBorrowedBytes(rootHash, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getRangeProofFromRangeProofResult(C.fwd_db_range_proof(db.handle, args)) +} + +// Verify verifies the provided range [proof] proves the values in the range +// [startKey, endKey] are included in the tree with the given [rootHash]. If the +// proof is valid, nil is returned; otherwise an error describing why the proof is +// invalid is returned. +func (p *RangeProof) Verify( + rootHash []byte, + startKey, endKey Maybe[[]byte], + maxLength uint32, +) error { + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.VerifyRangeProofArgs{ + proof: p.handle, + root: newBorrowedBytes(rootHash, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getErrorFromVoidResult(C.fwd_range_proof_verify(args)) +} + +// VerifyChangeProof verifies the provided change [proof] proves the changes +// between [startRoot] and [endRoot] for keys in the range [startKey, endKey]. If +// the proof is valid, a proposal containing the changes is prepared. The +// call to [*Database.VerifyAndCommitRangeProof] will skip verification and commit the +// prepared proposal. +func (db *Database) VerifyRangeProof( + proof *RangeProof, + startKey, endKey Maybe[[]byte], + rootHash []byte, + maxLength uint32, +) error { + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.VerifyRangeProofArgs{ + proof: proof.handle, + root: newBorrowedBytes(rootHash, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getErrorFromVoidResult(C.fwd_db_verify_range_proof(db.handle, args)) +} + +// VerifyAndCommitRangeProof verifies the provided range [proof] proves the values +// in the range [startKey, endKey] are included in the tree with the given +// [rootHash]. If the proof is valid, it is committed to the database and the +// new root hash is returned. The resulting root hash may not equal the +// provided root hash if the proof was truncated due to [maxLength]. +func (db *Database) VerifyAndCommitRangeProof( + proof *RangeProof, + startKey, endKey Maybe[[]byte], + rootHash []byte, + maxLength uint32, +) ([]byte, error) { + if db.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.VerifyRangeProofArgs{ + proof: proof.handle, + root: newBorrowedBytes(rootHash, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getHashKeyFromHashResult(C.fwd_db_verify_and_commit_range_proof(db.handle, args)) +} + +// FindNextKey returns the next key range to fetch for this proof, if any. If the +// proof has been fully processed, nil is returned. If an error occurs while +// determining the next key range, that error is returned. +// +// FindNextKey can only be called after a successful call to [*Database.VerifyRangeProof] or +// [*Database.VerifyAndCommitRangeProof]. +func (p *RangeProof) FindNextKey() (*NextKeyRange, error) { + return getNextKeyRangeFromNextKeyRangeResult(C.fwd_range_proof_find_next_key(p.handle)) +} + +// MarshalBinary returns a serialized representation of this RangeProof. +// +// The format is unspecified and opaque to firewood. +func (p *RangeProof) MarshalBinary() ([]byte, error) { + return getValueFromValueResult(C.fwd_range_proof_to_bytes(p.handle)) +} + +// UnmarshalBinary sets the contents of this RangeProof to be the deserialized +// form of [data] overwriting any existing contents. +func (p *RangeProof) UnmarshalBinary(data []byte) error { + if err := p.Free(); err != nil { + return err + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + handle, err := getRangeProofFromRangeProofResult( + C.fwd_range_proof_from_bytes(newBorrowedBytes(data, &pinner))) + + if err == nil { + p.handle = handle.handle + handle.handle = nil + } + + return err +} + +// Free releases the resources associated with this RangeProof. +// +// It is safe to call Free more than once; subsequent calls after the first +// will be no-ops. +func (p *RangeProof) Free() error { + if p.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_range_proof(p.handle)); err != nil { + return err + } + + p.handle = nil + + return nil +} + +// ChangeProof returns a proof that the changes between [startRoot] and +// [endRoot] for keys in the range [startKey, endKey]. The proof may be +// truncated to at most [maxLength] entries, if non-zero. If either [startKey] or +// [endKey] is Nothing, the range is unbounded in that direction. +func (db *Database) ChangeProof( + startRoot, endRoot []byte, + startKey, endKey Maybe[[]byte], + maxLength uint32, +) (*ChangeProof, error) { + if db.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.CreateChangeProofArgs{ + start_root: newBorrowedBytes(startRoot, &pinner), + end_root: newBorrowedBytes(endRoot, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getChangeProofFromChangeProofResult(C.fwd_db_change_proof(db.handle, args)) +} + +// VerifyChangeProof verifies the provided change [proof] proves the changes +// between [startRoot] and [endRoot] for keys in the range [startKey, endKey]. If +// the proof is valid, a proposal containing the changes is prepared. The call +// to [*Database.VerifyAndCommitChangeProof] will skip verification and commit the +// prepared proposal. +func (db *Database) VerifyChangeProof( + proof *ChangeProof, + startRoot, endRoot []byte, + startKey, endKey Maybe[[]byte], + maxLength uint32, +) error { + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.VerifyChangeProofArgs{ + proof: proof.handle, + start_root: newBorrowedBytes(startRoot, &pinner), + end_root: newBorrowedBytes(endRoot, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getErrorFromVoidResult(C.fwd_db_verify_change_proof(db.handle, args)) +} + +// VerifyAndCommitChangeProof verifies the provided change [proof] proves the changes +// between [startRoot] and [endRoot] for keys in the range [startKey, endKey]. If +// the proof is valid, it is committed to the database and the new root hash is +// returned. The resulting root hash may not equal the end root if the proof was +// truncated due to [maxLength]. +func (db *Database) VerifyAndCommitChangeProof( + proof *ChangeProof, + startRoot, endRoot []byte, + startKey, endKey Maybe[[]byte], + maxLength uint32, +) ([]byte, error) { + if db.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + args := C.VerifyChangeProofArgs{ + proof: proof.handle, + start_root: newBorrowedBytes(startRoot, &pinner), + end_root: newBorrowedBytes(endRoot, &pinner), + start_key: newMaybeBorrowedBytes(startKey, &pinner), + end_key: newMaybeBorrowedBytes(endKey, &pinner), + max_length: C.uint32_t(maxLength), + } + + return getHashKeyFromHashResult(C.fwd_db_verify_and_commit_change_proof(db.handle, args)) +} + +// FindNextKey returns the next key range to fetch for this proof, if any. If the +// proof has been fully processed, nil is returned. If an error occurs while +// determining the next key range, that error is returned. +// +// FindNextKey can only be called after a successful call to [*Database.VerifyChangeProof] or +// [*Database.VerifyAndCommitChangeProof]. +func (p *ChangeProof) FindNextKey() (*NextKeyRange, error) { + return getNextKeyRangeFromNextKeyRangeResult(C.fwd_change_proof_find_next_key(p.handle)) +} + +// MarshalBinary returns a serialized representation of this ChangeProof. +// +// The format is unspecified and opaque to firewood. +func (p *ChangeProof) MarshalBinary() ([]byte, error) { + return getValueFromValueResult(C.fwd_change_proof_to_bytes(p.handle)) +} + +// UnmarshalBinary sets the contents of this ChangeProof to be the deserialized +// form of [data] overwriting any existing contents. +func (p *ChangeProof) UnmarshalBinary(data []byte) error { + if err := p.Free(); err != nil { + return err + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + handle, err := getChangeProofFromChangeProofResult( + C.fwd_change_proof_from_bytes(newBorrowedBytes(data, &pinner))) + + if err == nil { + p.handle = handle.handle + handle.handle = nil + } + + return err +} + +// Free releases the resources associated with this ChangeProof. +// +// It is safe to call Free more than once; subsequent calls after the first +// will be no-ops. +func (p *ChangeProof) Free() error { + if p.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_change_proof(p.handle)); err != nil { + return err + } + + p.handle = nil + + return nil +} + +// StartKey returns the inclusive start key of this key range. +func (r *NextKeyRange) StartKey() []byte { + return r.startKey.BorrowedBytes() +} + +// HasEndKey returns true if this key range has an exclusive end key. +func (r *NextKeyRange) HasEndKey() bool { + return r.endKey.HasValue() +} + +// EndKey returns the exclusive end key of this key range if it exists or nil if +// it does not. +func (r *NextKeyRange) EndKey() []byte { + if r.endKey.HasValue() { + return r.endKey.Value().BorrowedBytes() + } + return nil +} + +// Free releases the resources associated with this NextKeyRange. +// +// It is safe to call Free more than once; subsequent calls after the first +// will be no-ops. +func (r *NextKeyRange) Free() error { + var err1, err2 error + + err1 = r.startKey.Free() + if r.endKey.HasValue() { + err2 = r.endKey.Value().Free() + } + + return errors.Join(err1, err2) +} + +func newNextKeyRange(cRange C.NextKeyRange) *NextKeyRange { + var nextKeyRange NextKeyRange + + nextKeyRange.startKey = newOwnedBytes(cRange.start_key) + + if cRange.end_key.tag == C.Maybe_OwnedBytes_Some_OwnedBytes { + nextKeyRange.endKey = newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&cRange.end_key.anon0))) + } + + return &nextKeyRange +} + +func getNextKeyRangeFromNextKeyRangeResult(result C.NextKeyRangeResult) (*NextKeyRange, error) { + switch result.tag { + case C.NextKeyRangeResult_NullHandlePointer: + return nil, errDBClosed + case C.NextKeyRangeResult_NotPrepared: + return nil, errNotPrepared + case C.NextKeyRangeResult_None: + return nil, nil + case C.NextKeyRangeResult_Some: + return newNextKeyRange(*(*C.NextKeyRange)(unsafe.Pointer(&result.anon0))), nil + case C.NextKeyRangeResult_Err: + return nil, newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + default: + return nil, fmt.Errorf("unknown C.NextKeyRangeResult tag: %d", result.tag) + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ed57a120036e..1f34115d4f00 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -30,6 +30,7 @@ mod arc_cache; mod handle; mod logging; mod metrics_setup; +mod proofs; mod value; use std::collections::HashMap; @@ -45,6 +46,7 @@ use firewood::v2::api::{self, Db as _, DbView, KeyValuePairIter, Proposal as _}; use crate::arc_cache::ArcCache; pub use crate::handle::*; pub use crate::logging::*; +pub use crate::proofs::*; pub use crate::value::*; #[cfg(unix)] diff --git a/ffi/src/proofs.rs b/ffi/src/proofs.rs new file mode 100644 index 000000000000..8368f035fd5b --- /dev/null +++ b/ffi/src/proofs.rs @@ -0,0 +1,8 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +mod change; +mod range; + +pub use self::change::*; +pub use self::range::*; diff --git a/ffi/src/proofs/change.rs b/ffi/src/proofs/change.rs new file mode 100644 index 000000000000..e7cc88968fd7 --- /dev/null +++ b/ffi/src/proofs/change.rs @@ -0,0 +1,261 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::{ + BorrowedBytes, CResult, ChangeProofResult, DatabaseHandle, HashResult, Maybe, + NextKeyRangeResult, OwnedBytes, ValueResult, VoidResult, +}; + +/// Arguments for creating a change proof. +#[derive(Debug)] +#[repr(C)] +pub struct CreateChangeProofArgs<'a> { + /// The root hash of the starting revision. This must be provided. + /// If the root is not found in the database, the function will return + /// [`ChangeProofResult::RevisionNotFound`]. + pub start_root: BorrowedBytes<'a>, + /// The root hash of the ending revision. This must be provided. + /// If the root is not found in the database, the function will return + /// [`ChangeProofResult::RevisionNotFound`]. + pub end_root: BorrowedBytes<'a>, + /// The start key of the range to create the proof for. If `None`, the range + /// starts from the beginning of the keyspace. + pub start_key: Maybe>, + /// The end key of the range to create the proof for. If `None`, the range + /// ends at the end of the keyspace or until `max_length` items have been + /// included in the proof. + pub end_key: Maybe>, + /// The maximum number of key/value pairs to include in the proof. If the + /// range contains more items than this, the proof will be truncated. If + /// `0`, there is no limit. + pub max_length: u32, +} + +/// Arguments for verifying a change proof. +#[derive(Debug)] +#[repr(C)] +pub struct VerifyChangeProofArgs<'a> { + /// The change proof to verify. If null, the function will return + /// [`VoidResult::NullHandlePointer`]. We need a mutable reference to + /// update the validation context. + pub proof: Option<&'a mut ChangeProofContext>, + /// The root hash of the starting revision. This must match the starting + /// root of the proof. + pub start_root: BorrowedBytes<'a>, + /// The root hash of the ending revision. This must match the ending root of + /// the proof. + pub end_root: BorrowedBytes<'a>, + /// The lower bound of the key range that the proof is expected to cover. If + /// `None`, the proof is expected to cover from the start of the keyspace. + pub start_key: Maybe>, + /// The upper bound of the key range that the proof is expected to cover. If + /// `None`, the proof is expected to cover to the end of the keyspace. + pub end_key: Maybe>, + /// The maximum number of key/value pairs that the proof is expected to cover. + /// If the proof contains more items than this, it is considered invalid. If + /// `0`, there is no limit. + pub max_length: u32, +} + +/// FFI context for a parsed or generated change proof. +#[derive(Debug)] +pub struct ChangeProofContext { + _proof: (), // currently not implemented + _validation_context: (), // placeholder for future use + _commit_context: (), // placeholder for future use +} + +/// A key range that should be fetched to continue iterating through a range +/// or change proof that was truncated. Represents a half-open range +/// `[start_key, end_key)`. If `end_key` is `None`, the range is unbounded +/// and continues to the end of the keyspace. +#[derive(Debug)] +#[repr(C)] +pub struct NextKeyRange { + /// The start key of the next range to fetch. + pub start_key: OwnedBytes, + + /// If set, a non-inclusive upper bound for the next range to fetch. If not + /// set, the range is unbounded (this is the final range). + pub end_key: Maybe, +} + +/// Create a change proof for the given range of keys between two roots. +/// +/// # Arguments +/// +/// - `db` - The database to create the proof from. +/// - `args` - The arguments for creating the change proof. +/// +/// # Returns +/// +/// - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`ChangeProofResult::RevisionNotFound`] if the caller provided a start or end root +/// that was not found in the database. The missing root hash is included in the result. +/// The start root is checked first, and if both are missing, only the start root is +/// reported. +/// - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof +/// was successfully created. +/// - [`ChangeProofResult::Err`] containing an error message if the proof could not be created. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_change_proof( + _db: Option<&DatabaseHandle>, + _args: CreateChangeProofArgs, +) -> ChangeProofResult { + CResult::from_err("not yet implemented") +} + +/// Verify a change proof and prepare a proposal to later commit or drop. +/// +/// # Arguments +/// +/// - `db` - The database to verify the proof against. +/// - `args` - The arguments for verifying the change proof. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to either +/// the database or the proof. +/// - [`VoidResult::Ok`] if the proof was successfully verified. +/// - [`VoidResult::Err`] containing an error message if the proof could not be verified +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_verify_change_proof( + _db: Option<&DatabaseHandle>, + _args: VerifyChangeProofArgs, +) -> VoidResult { + CResult::from_err("not yet implemented") +} + +/// Verify and commit a change proof to the database. +/// +/// If the proof has already been verified, the previously prepared proposal will be +/// committed instead of re-verifying. If the proof has not been verified, it will be +/// verified now. If the prepared proposal is no longer valid (e.g., the database has +/// changed since it was prepared), a new proposal will be created and committed. +/// +/// The proof context will be updated with additional information about the committed +/// proof to allow for optimized introspection of the committed changes. +/// +/// # Arguments +/// +/// - `db` - The database to commit the changes to. +/// - `args` - The arguments for verifying the change proof. +/// +/// # Returns +/// +/// - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either +/// the database or the proof. +/// - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). +/// - [`HashResult::Some`] containing the new root hash if the proof was successfully verified +/// - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. +/// +/// [`fwd_commit`]: crate::fwd_commit +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_verify_and_commit_change_proof( + _db: Option<&DatabaseHandle>, + _args: VerifyChangeProofArgs, +) -> HashResult { + CResult::from_err("not yet implemented") +} + +/// Returns the next key range that should be fetched after processing the +/// current set of operations in a change proof that was truncated. +/// +/// Can be called multiple times to get subsequent disjoint key ranges until +/// it returns [`NextKeyRangeResult::None`], indicating there are no more keys to +/// fetch and the proof is complete. +/// +/// # Arguments +/// +/// - `proof` - A [`ChangeProofContext`] previously returned from the create +/// methods and has been prepared into a proposal or already committed. +/// +/// # Returns +/// +/// - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into +/// a proposal nor committed to the database. +/// - [`NextKeyRangeResult::None`] if there are no more keys to fetch. +/// - [`NextKeyRangeResult::Some`] containing the next key range to fetch. +/// - [`NextKeyRangeResult::Err`] containing an error message if the next key range +/// could not be determined. +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_change_proof_find_next_key( + _proof: Option<&mut ChangeProofContext>, +) -> NextKeyRangeResult { + CResult::from_err("not yet implemented") +} + +/// Serialize a `ChangeProof` to bytes. +/// +/// # Arguments +/// +/// - `proof` - A [`ChangeProofContext`] previously returned from the create +/// method. If from a parsed proof, the proof will not be verified before +/// serialization. +/// +/// # Returns +/// +/// - [`ValueResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`ValueResult::Some`] containing the serialized bytes if successful. +/// - [`ValueResult::Err`] if the caller provided a null pointer. +/// +/// The other [`ValueResult`] variants are not used. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_change_proof_to_bytes(_proof: Option<&ChangeProofContext>) -> ValueResult { + CResult::from_err("not yet implemented") +} + +/// Deserialize a `ChangeProof` from bytes. +/// +/// # Arguments +/// +/// * `bytes` - The bytes to deserialize the proof from. +/// +/// # Returns +/// +/// - [`ChangeProofResult::NullHandlePointer`] if the caller provided a null or zero-length slice. +/// - [`ChangeProofResult::Ok`] containing a pointer to the `ChangeProofContext` if the proof +/// was successfully parsed. This does not imply that the proof is valid, only that it is +/// well-formed. The verify method must be called to ensure the proof is cryptographically valid. +/// - [`ChangeProofResult::Err`] containing an error message if the proof could not be parsed. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_change_proof_from_bytes(_bytes: BorrowedBytes) -> ChangeProofResult { + CResult::from_err("not yet implemented") +} + +/// Frees the memory associated with a `ChangeProofContext`. +/// +/// # Arguments +/// +/// * `proof` - The `ChangeProofContext` to free, previously returned from any Rust function. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_free_change_proof(proof: Option>) -> VoidResult { + crate::invoke_with_handle(proof, drop) +} diff --git a/ffi/src/proofs/range.rs b/ffi/src/proofs/range.rs new file mode 100644 index 000000000000..af0ea023e23b --- /dev/null +++ b/ffi/src/proofs/range.rs @@ -0,0 +1,280 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood::v2::api::FrozenRangeProof; + +use crate::{ + BorrowedBytes, CResult, DatabaseHandle, HashResult, Maybe, NextKeyRangeResult, + RangeProofResult, ValueResult, VoidResult, +}; + +/// Arguments for creating a range proof. +#[derive(Debug)] +#[repr(C)] +pub struct CreateRangeProofArgs<'a> { + /// The root hash of the revision to prove. If `None`, the latest revision + /// is used. + pub root: Maybe>, + /// The start key of the range to prove. If `None`, the range starts from the + /// beginning of the keyspace. + /// + /// The start key must be less than the end key if both are provided. + pub start_key: Maybe>, + /// The end key of the range to prove. If `None`, the range ends at the end + /// of the keyspace or until `max_length` items have been been included in + /// the proof. + /// + /// If provided, end key is inclusive if not truncated. Otherwise, the end + /// key will be the final key in the returned key-value pairs. + pub end_key: Maybe>, + /// The maximum number of key/value pairs to include in the proof. If the + /// range contains more items than this, the proof will be truncated. If + /// `0`, there is no limit. + pub max_length: u32, +} + +/// Arguments for verifying a range proof. +#[derive(Debug)] +#[repr(C)] +pub struct VerifyRangeProofArgs<'a> { + /// The range proof to verify. If null, the function will return + /// [`VoidResult::NullHandlePointer`]. We need a mutable reference to + /// update the validation context. + pub proof: Option<&'a mut RangeProofContext>, + /// The root hash to verify the proof against. This must match the calculated + /// hash of the root of the proof. + pub root: BorrowedBytes<'a>, + /// The lower bound of the key range that the proof is expected to cover. If + /// `None`, the proof is expected to cover from the start of the keyspace. + /// + /// Must be present if the range proof contains a lower bound proof and must + /// be absent if the range proof does not contain a lower bound proof. + pub start_key: Maybe>, + /// The upper bound of the key range that the proof is expected to cover. If + /// `None`, the proof is expected to cover to the end of the keyspace. + /// + /// This is ignored if the proof is truncated and does not cover the full, + /// in which case the upper bound key is the final key in the key-value pairs. + pub end_key: Maybe>, + /// The maximum number of key/value pairs that the proof is expected to cover. + /// If the proof contains more items than this, it is considered invalid. If + /// `0`, there is no limit. + pub max_length: u32, +} + +/// FFI context for for a parsed or generated range proof. +#[derive(Debug)] +pub struct RangeProofContext { + _proof: FrozenRangeProof, + /// Information about the proof discovered during verification that does not + /// need to be recomputed. Also serves as a token that ensured we have + /// validated the proof and can skip it during commit. + _validation_context: (), // placeholder for future use + /// Information about the proof after it has been committed to the DB. This + /// allows for easy introspection of the specific revision that was committed + /// and is needed to optimize discovery of the next key/range as well as + /// other introspective optimizations. + _commit_context: (), // placeholder for future use +} + +/// Generate a range proof for the given range of keys for the latest revision. +/// +/// # Arguments +/// +/// - `db` - The database to create the proof from. +/// - `args` - The arguments for creating the range proof. +/// +/// # Returns +/// +/// - [`RangeProofResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`RangeProofResult::RevisionNotFound`] if the caller provided a root that was +/// not found in the database. The missing root hash is included in the result. +/// - [`RangeProofResult::Ok`] containing a pointer to the `RangeProofContext` if the proof +/// was successfully created. +/// - [`RangeProofResult::Err`] containing an error message if the proof could not be created. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_range_proof( + _db: Option<&DatabaseHandle>, + _args: CreateRangeProofArgs, +) -> RangeProofResult { + CResult::from_err("not yet implemented") +} + +/// Verify a range proof against the given start and end keys and root hash. The +/// proof will be updated with the validation context if the proof is valid to +/// avoid re-verifying it during commit. +/// +/// # Arguments +/// +/// - `args` - The arguments for verifying the range proof. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to the proof. +/// - [`VoidResult::Ok`] if the proof was successfully verified. +/// - [`VoidResult::Err`] containing an error message if the proof could not be verified. +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_range_proof_verify(_args: VerifyRangeProofArgs) -> VoidResult { + CResult::from_err("not yet implemented") +} + +/// Verify a range proof and prepare a proposal to later commit or drop. If the +/// proof has already been verified, the cached validation context will be used +/// to avoid re-verifying the proof. +/// +/// # Arguments +/// +/// - `db` - The database to verify the proof against. +/// - `args` - The arguments for verifying the range proof. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the caller provided a null pointer to either +/// the database or the proof. +/// - [`VoidResult::Ok`] if the proof was successfully verified. +/// - [`VoidResult::Err`] containing an error message if the proof could not be verified +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_verify_range_proof( + _db: Option<&DatabaseHandle>, + _args: VerifyRangeProofArgs, +) -> VoidResult { + CResult::from_err("not yet implemented") +} + +/// Verify and commit a range proof to the database. +/// +/// If a proposal was previously prepared by a call to [`fwd_db_verify_range_proof`], +/// it will be committed instead of re-verifying the proof. If the proof has not yet +/// been verified, it will be verified now. If the prepared proposal is no longer +/// valid (e.g., the database has changed since it was prepared), a new proposal +/// will be created and committed. +/// +/// The proof context will be updated with additional information about the committed +/// proof to allow for optimized introspection of the committed changes. +/// +/// # Arguments +/// +/// - `db` - The database to commit the changes to. +/// - `args` - The arguments for verifying the range proof. +/// +/// # Returns +/// +/// - [`HashResult::NullHandlePointer`] if the caller provided a null pointer to either +/// the database or the proof. +/// - [`HashResult::None`] if the proof resulted in an empty database (i.e., all keys were deleted). +/// - [`HashResult::Some`] containing the new root hash if the proof was successfully verified +/// - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_db_verify_and_commit_range_proof( + _db: Option<&DatabaseHandle>, + _args: VerifyRangeProofArgs, +) -> HashResult { + CResult::from_err("not yet implemented") +} + +/// Returns the next key range that should be fetched after processing the +/// current set of key-value pairs in a range proof that was truncated. +/// +/// Can be called multiple times to get subsequent disjoint key ranges until +/// it returns [`NextKeyRangeResult::None`], indicating there are no more keys to +/// fetch and the proof is complete. +/// +/// # Arguments +/// +/// - `proof` - A [`RangeProofContext`] previously returned from the create +/// methods and has been prepared into a proposal or already committed. +/// +/// # Returns +/// +/// - [`NextKeyRangeResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`NextKeyRangeResult::NotPrepared`] if the proof has not been prepared into +/// a proposal nor committed to the database. +/// - [`NextKeyRangeResult::None`] if there are no more keys to fetch. +/// - [`NextKeyRangeResult::Some`] containing the next key range to fetch. +/// - [`NextKeyRangeResult::Err`] containing an error message if the next key range +/// could not be determined. +/// +/// # Thread Safety +/// +/// It is not safe to call this function concurrently with the same proof context +/// nor is it safe to call any other function that accesses the same proof context +/// concurrently. The caller must ensure exclusive access to the proof context +/// for the duration of the call. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_range_proof_find_next_key( + _proof: Option<&mut RangeProofContext>, +) -> NextKeyRangeResult { + CResult::from_err("not yet implemented") +} + +/// Serialize a `RangeProof` to bytes. +/// +/// # Arguments +/// +/// - `proof` - A [`RangeProofContext`] previously returned from the create +/// method. If from a parsed proof, the proof will not be verified before +/// serialization. +/// +/// # Returns +/// +/// - [`ValueResult::NullHandlePointer`] if the caller provided a null pointer. +/// - [`ValueResult::Some`] containing the serialized bytes if successful. +/// - [`ValueResult::Err`] if the caller provided a null pointer. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_range_proof_to_bytes(_proof: Option<&RangeProofContext>) -> ValueResult { + CResult::from_err("not yet implemented") +} + +/// Deserialize a `RangeProof` from bytes. +/// +/// # Arguments +/// +/// - `bytes` - The bytes to deserialize the proof from. +/// +/// # Returns +/// +/// - [`RangeProofResult::NullHandlePointer`] if the caller provided a null or zero-length slice. +/// - [`RangeProofResult::Ok`] containing a pointer to the `RangeProofContext` if the proof +/// was successfully parsed. This does not imply that the proof is valid, only that it is +/// well-formed. The verify method must be called to ensure the proof is cryptographically valid. +/// - [`RangeProofResult::Err`] containing an error message if the proof could not be parsed. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_range_proof_from_bytes(_bytes: BorrowedBytes) -> RangeProofResult { + CResult::from_err("not yet implemented") +} + +/// Frees the memory associated with a `RangeProofContext`. +/// +/// # Arguments +/// +/// * `proof` - The `RangeProofContext` to free, previously returned from any Rust function. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +#[unsafe(no_mangle)] +pub extern "C" fn fwd_free_range_proof(proof: Option>) -> VoidResult { + crate::invoke_with_handle(proof, drop) +} diff --git a/ffi/src/value.rs b/ffi/src/value.rs index e5d1bbb8a1c2..ea443e19f8bd 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -14,4 +14,20 @@ pub use self::hash_key::HashKey; pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; -pub use self::results::{HandleResult, HashResult, ValueResult, VoidResult}; +pub use self::results::{ + ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, RangeProofResult, ValueResult, + VoidResult, +}; + +/// Maybe is a C-compatible optional type using a tagged union pattern. +/// +/// FFI methods and types can use this to represent optional values where `Optional` +/// does not work due to it not having C-compatible layout. +#[derive(Debug)] +#[repr(C)] +pub enum Maybe { + /// No value present. + None, + /// A value is present. + Some(T), +} diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index f02d3878245e..98471147ae35 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -15,6 +15,15 @@ pub struct KeyValuePair<'a> { pub value: BorrowedBytes<'a>, } +impl<'a> KeyValuePair<'a> { + pub fn new((key, value): &'a (impl AsRef<[u8]>, impl AsRef<[u8]>)) -> Self { + Self { + key: BorrowedBytes::from_slice(key.as_ref()), + value: BorrowedBytes::from_slice(value.as_ref()), + } + } +} + impl fmt::Display for KeyValuePair<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let precision = f.precision().unwrap_or(64); diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 2467223c36bc..853eef7f000f 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -5,7 +5,7 @@ use std::fmt; use firewood::v2::api; -use crate::{HashKey, OwnedBytes}; +use crate::{ChangeProofContext, HashKey, NextKeyRange, OwnedBytes, RangeProofContext}; /// The result type returned from an FFI function that returns no value but may /// return an error. @@ -164,6 +164,87 @@ impl From, E>> for HashResult { } } +/// A result type returned from FFI functions that create or parse range proofs. +/// +/// The caller must ensure that [`fwd_free_range_proof`] is called to +/// free the memory associated with the returned context when it is no longer +/// needed. +/// +/// [`fwd_free_range_proof`]: crate::fwd_free_range_proof +#[derive(Debug)] +#[repr(C)] +pub enum RangeProofResult { + /// The caller provided a null pointer to the input handle. + NullHandlePointer, + /// The provided root was not found in the database. + RevisionNotFound(HashKey), + /// The proof was successfully created or parsed. + /// + /// If the value was parsed from a serialized proof, this does not imply that + /// the proof is valid, only that it is well-formed. The verify method must + /// be called to ensure the proof is cryptographically valid. + Ok(Box), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +/// A result type returned from FFI functions that create or parse change proofs. +/// +/// The caller must ensure that [`fwd_free_change_proof`] is called to +/// free the memory associated with the returned context when it is no longer +/// needed. +/// +/// [`fwd_free_change_proof`]: crate::fwd_free_change_proof +#[derive(Debug)] +#[repr(C)] +pub enum ChangeProofResult { + /// The caller provided a null pointer to the input handle. + NullHandlePointer, + /// The provided root was not found in the database. + RevisionNotFound(HashKey), + /// The proof was successfully created or parsed. + /// + /// If the value was parsed from a serialized proof, this does not imply that + /// the proof is valid, only that it is well-formed. The verify method must + /// be called to ensure the proof is cryptographically valid. + Ok(Box), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +#[derive(Debug)] +#[repr(C)] +pub enum NextKeyRangeResult { + /// The caller provided a null pointer to the input handle. + NullHandlePointer, + /// The proof has not prepared into a proposal nor committed to the database. + NotPrepared, + /// There are no more keys to fetch. + None, + /// The next key range to fetch is returned. + Some(NextKeyRange), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + /// Helper trait to handle the different result types returned from FFI functions. /// /// Once Try trait is stable, we can use that instead of this trait: @@ -197,47 +278,48 @@ pub(crate) trait CResult: Sized { } } -impl NullHandleResult for VoidResult { - fn null_handle_pointer_error() -> Self { - Self::NullHandlePointer - } -} - -impl CResult for VoidResult { - fn from_err(err: impl ToString) -> Self { - Self::Err(err.to_string().into_bytes().into()) - } +macro_rules! impl_null_handle_result { + ($($Enum:ty),* $(,)?) => { + $( + impl NullHandleResult for $Enum { + fn null_handle_pointer_error() -> Self { + Self::NullHandlePointer + } + } + )* + }; } -impl CResult for HandleResult { - fn from_err(err: impl ToString) -> Self { - Self::Err(err.to_string().into_bytes().into()) - } -} - -impl NullHandleResult for ValueResult { - fn null_handle_pointer_error() -> Self { - Self::NullHandlePointer - } +macro_rules! impl_cresult { + ($($Enum:ty),* $(,)?) => { + $( + impl CResult for $Enum { + fn from_err(err: impl ToString) -> Self { + Self::Err(err.to_string().into_bytes().into()) + } + } + )* + }; } -impl CResult for ValueResult { - fn from_err(err: impl ToString) -> Self { - Self::Err(err.to_string().into_bytes().into()) - } -} - -impl NullHandleResult for HashResult { - fn null_handle_pointer_error() -> Self { - Self::NullHandlePointer - } -} - -impl CResult for HashResult { - fn from_err(err: impl ToString) -> Self { - Self::Err(err.to_string().into_bytes().into()) - } -} +impl_null_handle_result!( + VoidResult, + ValueResult, + HashResult, + RangeProofResult, + ChangeProofResult, + NextKeyRangeResult, +); + +impl_cresult!( + VoidResult, + ValueResult, + HashResult, + HandleResult, + RangeProofResult, + ChangeProofResult, + NextKeyRangeResult, +); enum Panic { Static(&'static str), From 28b0498a23f2e790e9662b394d1f894b4991643c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 5 Sep 2025 08:25:21 -0700 Subject: [PATCH 0933/1053] feat: explicit impl of PartialEq/Eq on HashOrRlp (#1260) Addresses my comments on #1259. --- firewood/src/merkle/tests/ethhash.rs | 2 +- storage/src/node/branch.rs | 35 +++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index 235a43eeea75..e577f0daa3be 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -50,7 +50,7 @@ where { let merkle = init_merkle(kvs.clone()); let firewood_hash = merkle.nodestore.root_hash().unwrap_or_else(TrieHash::empty); - let eth_hash = KeccakHasher::trie_root(kvs).to_fixed_bytes().into(); + let eth_hash: TrieHash = KeccakHasher::trie_root(kvs).to_fixed_bytes().into(); assert_eq!(firewood_hash, eth_hash); } diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 5c764f90773c..dc88fdcec7e2 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -197,7 +197,7 @@ mod ethhash { use super::Serializable; - #[derive(Clone, Debug, PartialEq, Eq)] + #[derive(Clone, Debug)] pub enum HashOrRlp { Hash(TrieHash), // TODO: this slice is never larger than 32 bytes so smallvec is probably not our best container @@ -228,6 +228,39 @@ mod ethhash { } } + impl PartialEq for HashOrRlp { + fn eq(&self, other: &TrieHash) -> bool { + match self { + HashOrRlp::Hash(h) => h == other, + HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == other.as_ref(), + } + } + } + + impl PartialEq for TrieHash { + fn eq(&self, other: &HashOrRlp) -> bool { + match other { + HashOrRlp::Hash(h) => h == self, + HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == self.as_ref(), + } + } + } + + impl PartialEq for HashOrRlp { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (HashOrRlp::Hash(h1), HashOrRlp::Hash(h2)) => h1 == h2, + (HashOrRlp::Rlp(r1), HashOrRlp::Rlp(r2)) => r1 == r2, + (HashOrRlp::Hash(h), HashOrRlp::Rlp(r)) + | (HashOrRlp::Rlp(r), HashOrRlp::Hash(h)) => { + Keccak256::digest(r.as_ref()).as_slice() == h.as_ref() + } + } + } + } + + impl Eq for HashOrRlp {} + impl Serializable for HashOrRlp { fn write_to(&self, vec: &mut W) { match self { From 9b2e4aa2f77cf2feede120ee08659b231441c0dc Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 5 Sep 2025 13:21:19 -0700 Subject: [PATCH 0934/1053] fix: Path iterator returned wrong node (#1259) Enabled all proof tests, which discovered an introduced bug when MaybePersisted was introduced. The node being returned was the wrong one, which caused some proof errors when constructing proofs, notably the wrong node was being returned if the node wasn't yet persisted. Since the tests run with unpersisted but hashed nodes, this caused some failures when the tests were re-enabled. Some tests still do not work with ethhash enabled. Created #1261 for tracking. Also restructured the tests. Proof tests are separated now from range proof tests. Range proof tests were moved but not enabled in this PR (will be done next). --- firewood/src/iter.rs | 16 +- firewood/src/merkle/tests/mod.rs | 26 +- firewood/src/merkle/tests/proof.rs | 287 ++++++++++++++++++ .../merkle/tests/{unvalidated.rs => range.rs} | 242 --------------- firewood/src/proof.rs | 29 +- 5 files changed, 347 insertions(+), 253 deletions(-) create mode 100644 firewood/src/merkle/tests/proof.rs rename firewood/src/merkle/tests/{unvalidated.rs => range.rs} (81%) diff --git a/firewood/src/iter.rs b/firewood/src/iter.rs index 2a9f82b77668..cec86fb472a3 100644 --- a/firewood/src/iter.rs +++ b/firewood/src/iter.rs @@ -415,13 +415,13 @@ impl Iterator for PathIterator<'_, '_, T> { Node::Branch(branch) => { // We're at a branch whose key is a prefix of `key`. // Find its child (if any) that matches the next nibble in the key. + let saved_node = node.clone(); let Some(next_unmatched_key_nibble) = unmatched_key.next() else { // We're at the node at `key` so we're done. - let node = node.clone(); self.state = PathIteratorState::Exhausted; return Some(Ok(PathIterItem { key_nibbles: node_key.clone(), - node, + node: saved_node, next_nibble: None, })); }; @@ -432,11 +432,10 @@ impl Iterator for PathIterator<'_, '_, T> { None => { // There's no child at the index of the next nibble in the key. // There's no node at `key` in this trie so we're done. - let node = node.clone(); self.state = PathIteratorState::Exhausted; Some(Ok(PathIterItem { key_nibbles: node_key.clone(), - node, + node: saved_node, next_nibble: None, })) } @@ -449,12 +448,11 @@ impl Iterator for PathIterator<'_, '_, T> { let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble); - let ret = node.clone(); *node = child; Some(Ok(PathIterItem { key_nibbles: node_key, - node: ret, + node: saved_node, next_nibble: Some(next_unmatched_key_nibble), })) } @@ -462,12 +460,11 @@ impl Iterator for PathIterator<'_, '_, T> { let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble); - let ret = node.clone(); *node = child.clone().into(); Some(Ok(PathIterItem { key_nibbles: node_key, - node: ret, + node: saved_node, next_nibble: Some(next_unmatched_key_nibble), })) } @@ -479,10 +476,11 @@ impl Iterator for PathIterator<'_, '_, T> { let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble); + *node = child; Some(Ok(PathIterItem { key_nibbles: node_key, - node: child, + node: saved_node, next_nibble: Some(next_unmatched_key_nibble), })) } diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index 7c62a9764b1e..00b9a2f2db8a 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -6,9 +6,10 @@ #[cfg(feature = "ethhash")] mod ethhash; // TODO: get the hashes from merkledb and verify compatibility with branch factor 256 +mod proof; +mod range; #[cfg(not(any(feature = "ethhash", feature = "branch_factor_256")))] mod triehash; -mod unvalidated; use std::collections::HashMap; use std::fmt::Write; @@ -229,6 +230,29 @@ fn test_insert_and_get() { } } +#[test] +fn overwrite_leaf() { + let key = &[0x00]; + let val = &[1]; + let overwrite = &[2]; + + let mut merkle = create_in_memory_merkle(); + + merkle.insert(key, val[..].into()).unwrap(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(val.as_slice()) + ); + + merkle.insert(key, overwrite[..].into()).unwrap(); + + assert_eq!( + merkle.get_value(key).unwrap().as_deref(), + Some(overwrite.as_slice()) + ); +} + #[test] fn remove_root() { let key0 = vec![0]; diff --git a/firewood/src/merkle/tests/proof.rs b/firewood/src/merkle/tests/proof.rs new file mode 100644 index 000000000000..765ea396325b --- /dev/null +++ b/firewood/src/merkle/tests/proof.rs @@ -0,0 +1,287 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood_storage::Preimage; +use firewood_storage::logger::debug; + +use super::*; + +#[test] +fn range_proof_invalid_bounds() { + let merkle = create_in_memory_merkle().hash(); + + let start_key = &[0x01]; + let end_key = &[0x00]; + + match merkle.range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(1)) { + Err(api::Error::InvalidRange { + start_key: first_key, + end_key: last_key, + }) if *first_key == *start_key && *last_key == *end_key => (), + Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), + _ => panic!("expected InvalidRange error"), + } +} + +#[test] +fn full_range_proof() { + let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); + + let rangeproof = merkle.range_proof(None, None, None).unwrap(); + assert_eq!(rangeproof.key_values().len(), u8::MAX as usize + 1); + assert_ne!(rangeproof.start_proof(), rangeproof.end_proof()); + let left_proof = merkle.prove(&[u8::MIN]).unwrap(); + let right_proof = merkle.prove(&[u8::MAX]).unwrap(); + assert_eq!(rangeproof.start_proof(), &left_proof); + assert_eq!(rangeproof.end_proof(), &right_proof); +} + +#[test] +fn single_value_range_proof() { + const RANDOM_KEY: u8 = 42; + + let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); + + let rangeproof = merkle + .range_proof(Some(&[RANDOM_KEY]), None, NonZeroUsize::new(1)) + .unwrap(); + assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); + assert_eq!(rangeproof.key_values().len(), 1); +} + +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1261" +)] +#[test] +fn shared_path_proof() { + let key1 = b"key1"; + let value1 = b"1"; + + let key2 = b"key2"; + let value2 = b"2"; + + let merkle = init_merkle([(key1, value1), (key2, value2)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = key1; + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value1), &root_hash).unwrap(); + + let key = key2; + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value2), &root_hash).unwrap(); +} + +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1261" +)] +#[test] +fn single_key_proof_with_one_node() { + let key = b"key"; + let value = b"value"; + + let merkle = init_merkle([(key, value)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let proof = merkle.prove(key).unwrap(); + proof.verify(key, Some(value), &root_hash).unwrap(); +} + +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1261" +)] +#[test] +fn two_key_proof_without_shared_path() { + let key1 = &[0x00]; + let key2 = &[0xff]; + + let merkle = init_merkle([(key1, key1), (key2, key2)]); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let proof = merkle.prove(key1).unwrap(); + proof.verify(key1, Some(key1), &root_hash).unwrap(); + + let proof = merkle.prove(key2).unwrap(); + proof.verify(key2, Some(key2), &root_hash).unwrap(); +} + +#[test] +fn test_empty_tree_proof() { + let items: Vec<(&str, &str)> = Vec::new(); + let merkle = init_merkle(items); + let key = "x".as_ref(); + + let proof_err = merkle.prove(key).unwrap_err(); + assert!(matches!(proof_err, ProofError::Empty), "{proof_err:?}"); +} + +#[test] +fn test_proof() { + let rng = firewood_storage::SeededRng::from_env_or_random(); + let set = fixed_and_pseudorandom_data(&rng, 500); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + + let root_hash = merkle.nodestore().root_hash().unwrap(); + + for (key, val) in items { + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + proof.verify(key, Some(val), &root_hash).unwrap(); + } +} + +#[test] +fn test_proof_end_with_leaf() { + let merkle = init_merkle([ + ("do", "verb"), + ("doe", "reindeer"), + ("dog", "puppy"), + ("doge", "coin"), + ("horse", "stallion"), + ("ddd", "ok"), + ]); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = b"doe"; + + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + proof.verify(key, Some(b"reindeer"), &root_hash).unwrap(); +} + +#[test] +fn test_proof_end_with_branch() { + let items = [ + ("d", "verb"), + ("do", "verb"), + ("doe", "reindeer"), + ("e", "coin"), + ]; + let merkle = init_merkle(items); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + let key = b"d"; + + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + proof.verify(key, Some(b"verb"), &root_hash).unwrap(); +} + +#[test] +fn test_bad_proof() { + let rng = firewood_storage::SeededRng::from_env_or_random(); + let set = fixed_and_pseudorandom_data(&rng, 800); + let mut items = set.iter().collect::>(); + items.sort_unstable(); + let merkle = init_merkle(items.clone()); + let root_hash = merkle.nodestore().root_hash().unwrap(); + + for (key, value) in items { + let proof = merkle.prove(key).unwrap(); + assert!(!proof.is_empty()); + + // Delete an entry from the generated proofs. + let mut new_proof = proof.into_mutable(); + new_proof.pop(); + + // TODO: verify error result matches expected error + assert!(new_proof.verify(key, Some(value), &root_hash).is_err()); + } +} + +#[cfg_attr( + feature = "ethhash", + ignore = "https://github.com/ava-labs/firewood/issues/1261" +)] +#[test] +fn exclusion_with_proof_value_present() { + // Build a trie where an ancestor on the path has a value + let mut merkle = crate::merkle::tests::create_in_memory_merkle(); + // Parent has a value + merkle.insert(&[0u8], Box::from([0u8])).unwrap(); + // Child under the same branch + merkle.insert(&[0u8, 1u8], Box::from([1u8])).unwrap(); + + let merkle = merkle.hash(); + let root_hash = merkle.nodestore.root_hash().unwrap(); + debug!("{}", merkle.dump_to_string().unwrap()); + debug!("root_hash: {root_hash:?}"); + + // Non-existent key under the same parent branch + let missing = [0u8, 2u8]; + let proof = merkle.prove(&missing).unwrap(); + + debug!("{proof:#?}"); + assert_eq!( + proof.as_ref().first().unwrap().to_hash(), + root_hash.clone().into_hash_type() + ); + + // Ensure at least one node in the proof carries a value (proof value present) + assert!(proof.as_ref().iter().any(|n| n.value_digest.is_some())); + + // Exclusion should verify with expected None even if proof includes node values + proof + .verify(missing, Option::<&[u8]>::None, &root_hash) + .unwrap(); +} + +#[test] +fn proof_path_construction_and_corruption() { + use crate::proof::{Proof, ProofNode}; + + // Build a trie with several entries + let mut merkle = crate::merkle::tests::create_in_memory_merkle(); + merkle.insert(b"a", Box::from(b"1".as_slice())).unwrap(); + merkle.insert(b"ab", Box::from(b"2".as_slice())).unwrap(); + merkle.insert(b"abc", Box::from(b"3".as_slice())).unwrap(); + merkle.insert(b"abd", Box::from(b"4".as_slice())).unwrap(); + + let merkle = merkle.hash(); + let root_hash = merkle.nodestore.root_hash().unwrap(); + + // Inclusion proof for an existing key + let key = b"abc"; + let val = b"3"; + let proof = merkle.prove(key).unwrap(); + debug!("proof: {proof:#?}"); + debug!("root_hash: {root_hash:?}"); + debug!("{}", merkle.dump_to_string().unwrap()); + + // Positive: check path monotonicity (each node key is a prefix of the next) + let nodes = proof.as_ref(); + for w in nodes.windows(2) { + let cur = w[0].key.as_ref(); + let nxt = w[1].key.as_ref(); + assert!(nxt.starts_with(cur), "proof path not prefix-ordered"); + } + + // Sanity: proof verifies + proof.verify(key, Some(val.as_slice()), &root_hash).unwrap(); + + // Negative: corrupt the path by clearing children of the first node + let mut corrupt: Proof> = proof.clone().into_mutable(); + if let Some(first) = (*corrupt).first_mut() { + // Set all child hashes to empty so traversal fails + first.child_hashes = firewood_storage::BranchNode::empty_children(); + } + let corrupt = corrupt.into_immutable(); + let err = corrupt + .verify(key, Some(val.as_slice()), &root_hash) + .unwrap_err(); + // Node traversal should fail + assert!(matches!( + err, + crate::proof::ProofError::NodeNotInTrie | crate::proof::ProofError::UnexpectedHash + )); +} diff --git a/firewood/src/merkle/tests/unvalidated.rs b/firewood/src/merkle/tests/range.rs similarity index 81% rename from firewood/src/merkle/tests/unvalidated.rs rename to firewood/src/merkle/tests/range.rs index 7e2a815bf290..e83e6b8f61ad 100644 --- a/firewood/src/merkle/tests/unvalidated.rs +++ b/firewood/src/merkle/tests/range.rs @@ -6,237 +6,6 @@ use crate::range_proof::RangeProof; type KeyValuePairs = Vec<(Box<[u8]>, Box<[u8]>)>; -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn range_proof_invalid_bounds() { - let merkle = create_in_memory_merkle().hash(); - - let start_key = &[0x01]; - let end_key = &[0x00]; - - match merkle.range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(1)) { - Err(api::Error::InvalidRange { - start_key: first_key, - end_key: last_key, - }) if *first_key == *start_key && *last_key == *end_key => (), - Err(api::Error::InvalidRange { .. }) => panic!("wrong bounds on InvalidRange error"), - _ => panic!("expected InvalidRange error"), - } -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn full_range_proof() { - let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); - - let rangeproof = merkle.range_proof(None, None, None).unwrap(); - assert_eq!(rangeproof.key_values().len(), u8::MAX as usize + 1); - assert_ne!(rangeproof.start_proof(), rangeproof.end_proof()); - let left_proof = merkle.prove(&[u8::MIN]).unwrap(); - let right_proof = merkle.prove(&[u8::MAX]).unwrap(); - assert_eq!(rangeproof.start_proof(), &left_proof); - assert_eq!(rangeproof.end_proof(), &right_proof); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn single_value_range_proof() { - const RANDOM_KEY: u8 = 42; - - let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); - - let rangeproof = merkle - .range_proof(Some(&[RANDOM_KEY]), None, NonZeroUsize::new(1)) - .unwrap(); - assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); - assert_eq!(rangeproof.key_values().len(), 1); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn shared_path_proof() { - let key1 = b"key1"; - let value1 = b"1"; - - let key2 = b"key2"; - let value2 = b"2"; - - let merkle = init_merkle([(key1, value1), (key2, value2)]); - - let root_hash = merkle.nodestore().root_hash().unwrap(); - - let key = key1; - let proof = merkle.prove(key).unwrap(); - proof.verify(key, Some(value1), &root_hash).unwrap(); - - let key = key2; - let proof = merkle.prove(key).unwrap(); - proof.verify(key, Some(value2), &root_hash).unwrap(); -} - -// this was a specific failing case -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn shared_path_on_insert() { - init_merkle([ - ( - &[1, 1, 46, 82, 67, 218][..], - &[23, 252, 128, 144, 235, 202, 124, 243][..], - ), - ( - &[1, 0, 0, 1, 1, 0, 63, 80], - &[99, 82, 31, 213, 180, 196, 49, 242], - ), - ( - &[0, 0, 0, 169, 176, 15], - &[105, 211, 176, 51, 231, 182, 74, 207], - ), - ( - &[1, 0, 0, 0, 53, 57, 93], - &[234, 139, 214, 220, 172, 38, 168, 164], - ), - ]); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn overwrite_leaf() { - let key = &[0x00]; - let val = &[1]; - let overwrite = &[2]; - - let mut merkle = create_in_memory_merkle(); - - merkle.insert(key, val[..].into()).unwrap(); - - assert_eq!( - merkle.get_value(key).unwrap().as_deref(), - Some(val.as_slice()) - ); - - merkle.insert(key, overwrite[..].into()).unwrap(); - - assert_eq!( - merkle.get_value(key).unwrap().as_deref(), - Some(overwrite.as_slice()) - ); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn single_key_proof_with_one_node() { - let key = b"key"; - let value = b"value"; - - let merkle = init_merkle([(key, value)]); - - let root_hash = merkle.nodestore().root_hash().unwrap(); - - let proof = merkle.prove(key).unwrap(); - proof.verify(key, Some(value), &root_hash).unwrap(); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn two_key_proof_without_shared_path() { - let key1 = &[0x00]; - let key2 = &[0xff]; - - let merkle = init_merkle([(key1, key1), (key2, key2)]); - - let root_hash = merkle.nodestore().root_hash().unwrap(); - - let proof = merkle.prove(key1).unwrap(); - proof.verify(key1, Some(key1), &root_hash).unwrap(); - - let proof = merkle.prove(key2).unwrap(); - proof.verify(key2, Some(key2), &root_hash).unwrap(); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn test_proof() { - let rng = firewood_storage::SeededRng::from_env_or_random(); - let set = fixed_and_pseudorandom_data(&rng, 500); - let mut items = set.iter().collect::>(); - items.sort_unstable(); - let merkle = init_merkle(items.clone()); - - let root_hash = merkle.nodestore().root_hash().unwrap(); - - for (key, val) in items { - let proof = merkle.prove(key).unwrap(); - assert!(!proof.is_empty()); - proof.verify(key, Some(val), &root_hash).unwrap(); - } -} - -#[test] -/// Verify the proofs that end with leaf node with the given key. -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn test_proof_end_with_leaf() { - let merkle = init_merkle([ - ("do", "verb"), - ("doe", "reindeer"), - ("dog", "puppy"), - ("doge", "coin"), - ("horse", "stallion"), - ("ddd", "ok"), - ]); - let root_hash = merkle.nodestore().root_hash().unwrap(); - - let key = b"doe"; - - let proof = merkle.prove(key).unwrap(); - assert!(!proof.is_empty()); - - proof.verify(key, Some(b"reindeer"), &root_hash).unwrap(); -} - -#[test] -/// Verify the proofs that end with branch node with the given key. -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn test_proof_end_with_branch() { - let items = [ - ("d", "verb"), - ("do", "verb"), - ("doe", "reindeer"), - ("e", "coin"), - ]; - let merkle = init_merkle(items); - let root_hash = merkle.nodestore().root_hash().unwrap(); - - let key = b"d"; - - let proof = merkle.prove(key).unwrap(); - assert!(!proof.is_empty()); - - proof.verify(key, Some(b"verb"), &root_hash).unwrap(); -} - -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn test_bad_proof() { - let rng = firewood_storage::SeededRng::from_env_or_random(); - let set = fixed_and_pseudorandom_data(&rng, 800); - let mut items = set.iter().collect::>(); - items.sort_unstable(); - let merkle = init_merkle(items.clone()); - let root_hash = merkle.nodestore().root_hash().unwrap(); - - for (key, value) in items { - let proof = merkle.prove(key).unwrap(); - assert!(!proof.is_empty()); - - // Delete an entry from the generated proofs. - let mut new_proof = proof.into_mutable(); - new_proof.pop(); - - // TODO: verify error result matches expected error - assert!(new_proof.verify(key, Some(value), &root_hash).is_err()); - } -} - #[test] // Tests that missing keys can also be proven. The test explicitly uses a single // entry trie and checks for missing keys both before and after the single entry. @@ -255,17 +24,6 @@ fn test_missing_key_proof() { } } -#[test] -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] -fn test_empty_tree_proof() { - let items: Vec<(&str, &str)> = Vec::new(); - let merkle = init_merkle(items); - let key = "x".as_ref(); - - let proof_err = merkle.prove(key).unwrap_err(); - assert!(matches!(proof_err, ProofError::Empty), "{proof_err:?}"); -} - #[test] // Tests normal range proof with both edge proofs as the existent proof. // The test cases are generated randomly. diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 379c0e8361fb..ea0278140b3d 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -74,7 +74,7 @@ pub enum ProofError { EmptyRange, } -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq, Eq, Default)] /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. @@ -89,6 +89,33 @@ pub struct ProofNode { pub child_hashes: Children, } +impl std::fmt::Debug for ProofNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let key: Vec = self.key().collect(); + let children: Vec<(usize, &HashType)> = self + .child_hashes + .iter() + .enumerate() + .filter_map(|(i, c)| c.as_ref().map(|h| (i, h))) + .collect(); + let value_digest = self.value_digest(); + + let mut ds = f.debug_struct("ProofNode"); + ds.field("key", &key) + .field("value_digest", &value_digest) + .field("child_hashes", &children); + + #[cfg(feature = "ethhash")] + ds.field("partial_len", &self.partial_len); + + // Compute the hash and render it as well + let hash = firewood_storage::Preimage::to_hash(self); + ds.field("hash", &hash); + + ds.finish() + } +} + impl Hashable for ProofNode { fn key(&self) -> impl Iterator + Clone { self.key.as_ref().iter().copied() From b6efd85cc4bc29f23737be5c3dc2ac5cfe6f6be9 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 5 Sep 2025 17:28:44 -0700 Subject: [PATCH 0935/1053] chore: these tests actually work now (#1262) --- firewood/src/merkle/tests/proof.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/firewood/src/merkle/tests/proof.rs b/firewood/src/merkle/tests/proof.rs index 765ea396325b..edfa07dbf6eb 100644 --- a/firewood/src/merkle/tests/proof.rs +++ b/firewood/src/merkle/tests/proof.rs @@ -49,10 +49,6 @@ fn single_value_range_proof() { assert_eq!(rangeproof.key_values().len(), 1); } -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1261" -)] #[test] fn shared_path_proof() { let key1 = b"key1"; @@ -74,10 +70,6 @@ fn shared_path_proof() { proof.verify(key, Some(value2), &root_hash).unwrap(); } -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1261" -)] #[test] fn single_key_proof_with_one_node() { let key = b"key"; @@ -91,10 +83,6 @@ fn single_key_proof_with_one_node() { proof.verify(key, Some(value), &root_hash).unwrap(); } -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1261" -)] #[test] fn two_key_proof_without_shared_path() { let key1 = &[0x00]; @@ -199,10 +187,6 @@ fn test_bad_proof() { } } -#[cfg_attr( - feature = "ethhash", - ignore = "https://github.com/ava-labs/firewood/issues/1261" -)] #[test] fn exclusion_with_proof_value_present() { // Build a trie where an ancestor on the path has a value From e707b731614ea6552809bc214e36bf18f746c438 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 8 Sep 2025 07:09:27 -0700 Subject: [PATCH 0936/1053] chore: cargo +nightly clippy --fix (#1265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ➜ firewood git:(main) cargo +nightly clippy Checking firewood-storage v0.0.12 (/Users/rkuris/open-source/firewood/storage) Compiling firewood-macros v0.0.12 (/Users/rkuris/open-source/firewood/firewood-macros) Compiling firewood-ffi v0.0.12 (/Users/rkuris/open-source/firewood/ffi) Compiling firewood-fwdctl v0.0.12 (/Users/rkuris/open-source/firewood/fwdctl) Checking firewood-triehash v0.0.12 (/Users/rkuris/open-source/firewood/triehash) warning: manual implementation of `.is_multiple_of()` --> storage/src/checker/mod.rs:50:5 | 50 | key.0.len() % 2 == 0 | ^^^^^^^^^^^^^^^^^^^^ help: replace with: `key.0.len().is_multiple_of(2)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of = note: `#[warn(clippy::manual_is_multiple_of)]` on by default warning: manual implementation of `.is_multiple_of()` --> storage/src/node/path.rs:214:25 | 214 | let result = if self.head % 2 == 0 { | ^^^^^^^^^^^^^^^^^^ help: replace with: `self.head.is_multiple_of(2)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of warning: manual implementation of `.is_multiple_of()` --> storage/src/node/path.rs:265:25 | 265 | let result = if self.tail % 2 == 0 { | ^^^^^^^^^^^^^^^^^^ help: replace with: `self.tail.is_multiple_of(2)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of warning: manual implementation of `.is_multiple_of()` --> storage/src/nodestore/primitives.rs:277:9 | 277 | self.0.get() % (Self::MIN_AREA_SIZE) == 0 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `self.0.get().is_multiple_of((Self::MIN_AREA_SIZE))` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of warning: `firewood-storage` (lib) generated 4 warnings (run `cargo clippy --fix --lib -p firewood-storage` to apply 4 suggestions) Checking firewood v0.0.12 (/Users/rkuris/open-source/firewood/firewood) Checking firewood-benchmark v0.0.12 (/Users/rkuris/open-source/firewood/benchmark) warning: implicitly cloning a `String` by calling `to_string` on its dereferenced type --> fwdctl/src/main.rs:79:56 | 79 | .filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.to_string()), | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `cli.log_level.clone()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone = note: `-W clippy::implicit-clone` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::implicit_clone)]` warning: `firewood-fwdctl` (bin "fwdctl") generated 1 warning (run `cargo clippy --fix --bin "fwdctl"` to apply 1 suggestion) warning: unnecessary parentheses around type --> ffi/src/handle.rs:150:17 | 150 | values: (impl AsRef<[KeyValuePair<'kvp>]> + 'kvp), | ^ ^ | = note: `#[warn(unused_parens)]` on by default help: remove these parentheses | 150 - values: (impl AsRef<[KeyValuePair<'kvp>]> + 'kvp), 150 + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, | warning: `firewood-ffi` (lib) generated 1 warning (run `cargo clippy --fix --lib -p firewood-ffi` to apply 1 suggestion) Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.37s ``` --- ffi/src/handle.rs | 2 +- fwdctl/src/main.rs | 3 +-- storage/src/checker/mod.rs | 2 +- storage/src/node/path.rs | 4 ++-- storage/src/nodestore/primitives.rs | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index a9a0ea6e575a..6e0e2aabc3a7 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -147,7 +147,7 @@ impl DatabaseHandle<'_> { /// An error is returned if the proposal could not be created. pub fn create_batch<'kvp>( &self, - values: (impl AsRef<[KeyValuePair<'kvp>]> + 'kvp), + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, ) -> Result, api::Error> { let start = coarsetime::Instant::now(); diff --git a/fwdctl/src/main.rs b/fwdctl/src/main.rs index b571f9b68e95..3dfd7021af36 100644 --- a/fwdctl/src/main.rs +++ b/fwdctl/src/main.rs @@ -75,8 +75,7 @@ fn main() -> Result<(), api::Error> { let cli = Cli::parse(); env_logger::init_from_env( - env_logger::Env::default() - .filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.to_string()), + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, cli.log_level.clone()), ); match &cli.command { diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index d746ff793421..d530808c2b91 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -47,7 +47,7 @@ fn is_valid_key(key: &Path) -> bool { #[cfg(not(feature = "ethhash"))] fn is_valid_key(key: &Path) -> bool { - key.0.len() % 2 == 0 + key.0.len().is_multiple_of(2) } /// Options for the checker diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index ed9cc1cd8d68..478e41e91f8f 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -211,7 +211,7 @@ impl Iterator for NibblesIterator<'_> { if self.is_empty() { return None; } - let result = if self.head % 2 == 0 { + let result = if self.head.is_multiple_of(2) { #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.head / 2] >> 4) as usize] } else { @@ -262,7 +262,7 @@ impl DoubleEndedIterator for NibblesIterator<'_> { return None; } - let result = if self.tail % 2 == 0 { + let result = if self.tail.is_multiple_of(2) { #[expect(clippy::indexing_slicing)] NIBBLES[(self.data[self.tail / 2 - 1] & 0xf) as usize] } else { diff --git a/storage/src/nodestore/primitives.rs b/storage/src/nodestore/primitives.rs index f2b3cbd7c6fd..131eb85e4932 100644 --- a/storage/src/nodestore/primitives.rs +++ b/storage/src/nodestore/primitives.rs @@ -274,7 +274,7 @@ impl LinearAddress { #[inline] #[must_use] pub const fn is_aligned(self) -> bool { - self.0.get() % (Self::MIN_AREA_SIZE) == 0 + self.0.get().is_multiple_of(Self::MIN_AREA_SIZE) } /// The maximum area size available for allocation. From e3b53da973321ad8e675995f4cfdf1884af3534b Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 8 Sep 2025 13:42:29 -0700 Subject: [PATCH 0937/1053] feat(proofs)!: disable `ValueDigest::Hash` for ethhash (#1269) As mentioned in #1267, the `ValueDigest::Hash` variant is not compatible with the ethereum hash function. This disables it when the `ethhash` feature is enabled to clear up some confusion until a more in-depth refactor is done. There is a potential advantage to simply leaving it this way. With the conditional inclusion of the second variant, `ValueDigest` becomes a thin new-type wrapper when `ethhash` is enabled because it contains only one variant. I.e., ```rust let ValueDigest::Value(value) = value_digest; ``` is correct when `ethhash` is enabled because the let binding is irrefutable. --- firewood/src/proof.rs | 1 + storage/src/hashednode.rs | 5 ++++- storage/src/hashers/ethhash.rs | 4 ---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index ea0278140b3d..334db26dc216 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -129,6 +129,7 @@ impl Hashable for ProofNode { fn value_digest(&self) -> Option> { self.value_digest.as_ref().map(|vd| match vd { ValueDigest::Value(v) => ValueDigest::Value(v.as_ref()), + #[cfg(not(feature = "ethhash"))] ValueDigest::Hash(h) => ValueDigest::Hash(h.as_ref()), }) } diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index e50d0c16112b..2023fc40489d 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -7,7 +7,6 @@ )] use crate::{BranchNode, Children, HashType, LeafNode, Node, Path}; -use sha2::{Digest, Sha256}; use smallvec::SmallVec; use std::ops::Deref; @@ -92,6 +91,7 @@ impl HasUpdate for SmallVec<[u8; 32]> { pub enum ValueDigest { /// The node's value. Value(T), + #[cfg(not(feature = "ethhash"))] /// TODO this variant will be used when we deserialize a proof node /// from a remote Firewood instance. The serialized proof node they /// send us may the hash of the value, not the value itself. @@ -104,6 +104,7 @@ impl Deref for ValueDigest { fn deref(&self) -> &Self::Target { match self { ValueDigest::Value(value) => value, + #[cfg(not(feature = "ethhash"))] ValueDigest::Hash(hash) => hash, } } @@ -117,7 +118,9 @@ impl> ValueDigest { // This proof proves that `key` maps to `got_value`. got_value.as_ref() == expected.as_ref() } + #[cfg(not(feature = "ethhash"))] Self::Hash(got_hash) => { + use sha2::{Digest, Sha256}; // This proof proves that `key` maps to a value // whose hash is `got_hash`. got_hash.as_ref() == Sha256::digest(expected.as_ref()).as_slice() diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 399f98d466ac..8ccf1f202f9a 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -146,9 +146,6 @@ impl Preimage for T { rlp.append(&bytes); } } - Some(ValueDigest::Hash(hash)) => { - rlp.append(&hash); - } None => { rlp.append_empty_data(); } @@ -156,7 +153,6 @@ impl Preimage for T { } else { match self.value_digest() { Some(ValueDigest::Value(bytes)) => rlp.append(&bytes), - Some(ValueDigest::Hash(hash)) => rlp.append(&hash), None => rlp.append_empty_data(), }; } From 00eb049590e953ad821258a1e7220cdac0c97ad9 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 9 Sep 2025 10:57:38 -0700 Subject: [PATCH 0938/1053] fix: use `count` instead of `size_hint` (#1268) `size_hint` is not guaranteed to be accurate and in some cases where we use it, it may not be. This can potentially lead to silent bugs due to incorrect assumptions about the number of elements in an iterator. The `count` method on iterators _may_ be suboptimal and try enumerating each item in the iterator; however, most iterator implementations are smarter than that and will shortcut evaluating `count` and return a more optimal response. E.g., [`core::slice::Iter`](https://doc.rust-lang.org/stable/src/core/slice/iter.rs.html#1376-1379) or [`alloc::vec::IntoIter`](https://doc.rust-lang.org/stable/src/alloc/vec/into_iter.rs.html#256-259) and most iterator adaptors take advantage of this as well such as [`core::iter::Chain`](https://doc.rust-lang.org/stable/src/core/iter/adapters/chain.rs.html#89-99) and [`core::iter::Copied`](https://doc.rust-lang.org/stable/src/core/iter/adapters/copied.rs.html#86-88). I also refactored nibbles_to_eth_compact to be clearer and take advantage of newer rust features to remove some redundant code. --- storage/src/hashers/ethhash.rs | 64 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 8ccf1f202f9a..53ee1d2f9972 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -3,13 +3,6 @@ // Ethereum compatible hashing algorithm. -#![cfg_attr( - feature = "ethhash", - expect( - clippy::indexing_slicing, - reason = "Found 4 occurrences after enabling the lint." - ) -)] #![cfg_attr( feature = "ethhash", expect( @@ -60,30 +53,31 @@ fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec } let nibbles = nibbles.as_ref(); + if cfg!(debug_assertions) { + for &nibble in nibbles { + assert!( + nibble < 16, + "nibbles contains byte out of range: {nibbles:?}" + ); + } + } - // nibble_pairs points to the first nibble that will be combined with the next nibble - // so we skip the first byte if there's an odd length and set the odd_nibbles bit to true - let (nibble_pairs, first_byte) = if nibbles.len() & 1 == 1 { - let low_nibble = nibbles[0]; - debug_assert!(low_nibble < 16); - ( - &nibbles[1..], - CompactFirstByte::new(is_leaf, true, low_nibble), - ) - } else { - (nibbles, CompactFirstByte::new(is_leaf, false, 0)) - }; + let mut first_byte = CompactFirstByte(0); + first_byte.set_is_leaf(is_leaf); - // at this point, we can be sure that nibble_pairs has an even length - debug_assert!(nibble_pairs.len() % 2 == 0); + let (maybe_low_nibble, nibble_pairs) = nibbles.as_rchunks::<2>(); + if let &[low_nibble] = maybe_low_nibble { + // we have an odd number of nibbles + first_byte.set_odd_nibbles(true); + first_byte.set_low_nibble(low_nibble); + } else { + // as_rchunks can only return 0 or 1 element in the first slice if N is 2 + debug_assert!(maybe_low_nibble.is_empty()); + } // now assemble everything: the first byte, and the nibble pairs compacted back together once(first_byte.0) - .chain( - nibble_pairs - .chunks(2) - .map(|chunk| (chunk[0] << 4) | chunk[1]), - ) + .chain(nibble_pairs.iter().map(|&[hi, lo]| (hi << 4) | lo)) .collect() } @@ -94,17 +88,11 @@ impl Preimage for T { let mut collector = SmallVec::with_capacity(32); self.write(&mut collector); - if crate::logger::trace_enabled() { - if self.key().size_hint().0 == 64 { - trace!("SIZE WAS 64 {}", hex::encode(&collector)); - } else { - trace!( - "SIZE WAS {1} {0}", - hex::encode(&collector), - self.key().size_hint().0 - ); - } - } + trace!( + "SIZE WAS {} {}", + self.key().count(), + hex::encode(&collector), + ); if collector.len() >= 32 { HashType::Hash(Keccak256::digest(collector).into()) @@ -114,7 +102,7 @@ impl Preimage for T { } fn write(&self, buf: &mut impl HasUpdate) { - let is_account = self.key().size_hint().0 == 64; + let is_account = self.key().count() == 64; trace!("is_account: {is_account}"); let child_hashes = self.children(); From f21ab6b8314020d388cf9bfb3690e94df95b2d27 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 10 Sep 2025 11:04:16 -0700 Subject: [PATCH 0939/1053] ci: update .golangci.yaml (#1274) After , a big chunk was no longer needed. --- .github/.gitignore | 1 + .github/.golangci.yaml.patch | 31 ++----------------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/.github/.gitignore b/.github/.gitignore index 29c5df23e83f..2815f8fa3fdf 100644 --- a/.github/.gitignore +++ b/.github/.gitignore @@ -1,2 +1,3 @@ .golangci.yaml .golangci.yaml.orig +.golangci.yaml.rej diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index b9898cfe4203..28e92b877d0e 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,5 +1,5 @@ ---- .github/.golangci.yaml 2025-08-22 10:01:30 -+++ ffi/.golangci.yaml 2025-08-22 10:01:26 +--- .github/.golangci.yaml 2025-09-10 10:51:41 ++++ ffi/.golangci.yaml 2025-08-22 11:42:25 @@ -69,8 +69,6 @@ rules: packages: @@ -57,33 +57,6 @@ tagalign: align: true sort: true -@@ -189,16 +157,16 @@ - - serialize - strict: true - testifylint: -- # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). -- # Default: false -- enable-all: true -- # Disable checkers by name -- # (in addition to default -- # suite-thelper -- # ). -- disable: -- - go-require -- - float-compare -+ # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). -+ # Default: false -+ enable-all: true -+ # Disable checkers by name -+ # (in addition to default -+ # suite-thelper -+ # ). -+ disable: -+ - go-require -+ - float-compare - unused: - # Mark all struct fields that have been written to as used. - # Default: true @@ -227,7 +195,7 @@ - standard - default From 24a918d8e3a9a16db1107f2e1265a81fe89ef3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Kj=C3=A6rstad?= Date: Mon, 15 Sep 2025 20:08:46 +0200 Subject: [PATCH 0940/1053] fix: Correct typo in README.md (#1276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct typo in README.md Signed-off-by: Marius Kjærstad --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 011ceb397565..67984e1d4b25 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ to [keccak256](https://docs.rs/sha3/latest/sha3/type.Keccak256.html), understands that an "account" is actually just a node in the storage tree at a specific depth with a specific RLP-encoded value, and computes the hash of the account trie as if it were an actual root. -It is worth nothing that the hash stored as a value inside the account root RLP is not used. +It is worth noting that the hash stored as a value inside the account root RLP is not used. During hash calculations, we know the hash of the children, and use that directly to modify the value in-place when hashing the node. From fe53dd444b2d84b84024d32d812bbaee4823cc77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:25:38 +0000 Subject: [PATCH 0941/1053] build(deps): update typed-builder requirement from 0.21.0 to 0.22.0 (#1275) --- firewood/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 6977e822d285..6067b7a1f16b 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -32,7 +32,7 @@ metrics.workspace = true sha2.workspace = true thiserror.workspace = true # Regular dependencies -typed-builder = "0.21.0" +typed-builder = "0.22.0" [features] default = [] From eddabe59ffdd1c37fe77aad8baaa3f6bbafc8d35 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 15 Sep 2025 15:47:17 -0400 Subject: [PATCH 0942/1053] feat!: rename `Hashable::key` (#1270) This renames `Hashable::key` to `Hashable::full_path`. I also included `parent_prefix_path` and `partial_path` unconditionally as they are useful to me during serialization. I had already done this in prep for serialization but comments on #1268 prompted me to pull it out and publish separately. --- firewood/src/merkle.rs | 1 - firewood/src/proof.rs | 51 +++++++++++++++------------------ storage/src/hashednode.rs | 21 +++++++------- storage/src/hashers/ethhash.rs | 6 ++-- storage/src/hashers/merkledb.rs | 2 +- 5 files changed, 37 insertions(+), 44 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 7cd255cc794c..dc134f5c0b08 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -167,7 +167,6 @@ impl Merkle { proof.push(ProofNode { key: root.partial_path().bytes(), - #[cfg(feature = "ethhash")] partial_len: root.partial_path().0.len(), value_digest: root .value() diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 334db26dc216..d3446ad349b1 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -80,7 +80,6 @@ pub struct ProofNode { /// The key this node is at. Each byte is a nibble. pub key: Key, /// The length of the key prefix that is shared with the previous node. - #[cfg(feature = "ethhash")] pub partial_len: usize, /// None if the node does not have a value. /// Otherwise, the node's value or the hash of its value. @@ -91,39 +90,37 @@ pub struct ProofNode { impl std::fmt::Debug for ProofNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let key: Vec = self.key().collect(); - let children: Vec<(usize, &HashType)> = self + // Filter the missing children and only show the present ones with their indices + let child_hashes = self .child_hashes .iter() .enumerate() - .filter_map(|(i, c)| c.as_ref().map(|h| (i, h))) - .collect(); - let value_digest = self.value_digest(); - - let mut ds = f.debug_struct("ProofNode"); - ds.field("key", &key) - .field("value_digest", &value_digest) - .field("child_hashes", &children); - - #[cfg(feature = "ethhash")] - ds.field("partial_len", &self.partial_len); - + .filter_map(|(i, h)| h.as_ref().map(|h| (i, h))) + .collect::>(); // Compute the hash and render it as well let hash = firewood_storage::Preimage::to_hash(self); - ds.field("hash", &hash); - ds.finish() + f.debug_struct("ProofNode") + .field("key", &self.key) + .field("partial_len", &self.partial_len) + .field("value_digest", &self.value_digest) + .field("child_hashes", &child_hashes) + .field("hash", &hash) + .finish() } } impl Hashable for ProofNode { - fn key(&self) -> impl Iterator + Clone { - self.key.as_ref().iter().copied() + fn parent_prefix_path(&self) -> impl Iterator + Clone { + self.full_path().take(self.partial_len) } - #[cfg(feature = "ethhash")] fn partial_path(&self) -> impl Iterator + Clone { - self.key.as_ref().iter().skip(self.partial_len).copied() + self.full_path().skip(self.partial_len) + } + + fn full_path(&self) -> impl Iterator + Clone { + self.key.as_ref().iter().copied() } fn value_digest(&self) -> Option> { @@ -147,15 +144,13 @@ impl From for ProofNode { BranchNode::empty_children() }; - #[cfg(feature = "ethhash")] let partial_len = item .key_nibbles .len() - .saturating_sub(item.node.partial_path().0.len()); + .saturating_sub(item.node.partial_path().len()); Self { key: item.key_nibbles, - #[cfg(feature = "ethhash")] partial_len, value_digest: item .node @@ -207,19 +202,19 @@ impl Proof { // Assert that only nodes whose keys are an even number of nibbles // have a `value_digest`. #[cfg(not(feature = "branch_factor_256"))] - if node.key().count() % 2 != 0 && node.value_digest().is_some() { + if node.full_path().count() % 2 != 0 && node.value_digest().is_some() { return Err(ProofError::ValueAtOddNibbleLength); } if let Some(next_node) = iter.peek() { // Assert that every node's key is a prefix of `key`, except for the last node, // whose key can be equal to or a suffix of `key` in an exclusion proof. - if next_nibble(node.key(), key.iter().copied()).is_none() { + if next_nibble(node.full_path(), key.iter().copied()).is_none() { return Err(ProofError::ShouldBePrefixOfProvenKey); } // Assert that every node's key is a prefix of the next node's key. - let next_node_index = next_nibble(node.key(), next_node.key()); + let next_node_index = next_nibble(node.full_path(), next_node.full_path()); let Some(next_nibble) = next_node_index else { return Err(ProofError::ShouldBePrefixOfNextKey); @@ -235,7 +230,7 @@ impl Proof { } } - if last_node.key().count() == key.len() { + if last_node.full_path().count() == key.len() { return Ok(last_node.value_digest()); } diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 2023fc40489d..0d6ae391028f 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -131,16 +131,20 @@ impl> ValueDigest { /// A node in the trie that can be hashed. pub trait Hashable: std::fmt::Debug { - /// The key of the node where each byte is a nibble. - fn key(&self) -> impl Iterator + Clone; - /// The partial path of this node - #[cfg(feature = "ethhash")] + /// The full path of this node's parent where each byte is a nibble. + fn parent_prefix_path(&self) -> impl Iterator + Clone; + /// The partial path of this node where each byte is a nibble. fn partial_path(&self) -> impl Iterator + Clone; /// The node's value or hash. fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. fn children(&self) -> Children; + + /// The full path of this node including the parent's prefix where each byte is a nibble. + fn full_path(&self) -> impl Iterator + Clone { + self.parent_prefix_path().chain(self.partial_path()) + } } /// A preimage of a hash. @@ -198,15 +202,10 @@ impl<'a, N: HashableNode> From> for HashType { } impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { - fn key(&self) -> impl Iterator + Clone { - self.prefix - .0 - .iter() - .copied() - .chain(self.node.partial_path()) + fn parent_prefix_path(&self) -> impl Iterator + Clone { + self.prefix.0.iter().copied() } - #[cfg(feature = "ethhash")] fn partial_path(&self) -> impl Iterator + Clone { self.node.partial_path() } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 53ee1d2f9972..ba89260fbcd4 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -90,7 +90,7 @@ impl Preimage for T { trace!( "SIZE WAS {} {}", - self.key().count(), + self.full_path().count(), hex::encode(&collector), ); @@ -102,7 +102,7 @@ impl Preimage for T { } fn write(&self, buf: &mut impl HasUpdate) { - let is_account = self.key().count() == 64; + let is_account = self.full_path().count() == 64; trace!("is_account: {is_account}"); let child_hashes = self.children(); @@ -224,7 +224,7 @@ impl Preimage for T { // treat like non-account since it didn't have a value warn!( "Account node {:x?} without value", - self.key().collect::>() + self.full_path().collect::>() ); bytes.as_ref().into() } diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index 98666ad23abc..3960dcf4c336 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -50,7 +50,7 @@ impl Preimage for T { add_value_digest_to_buf(buf, self.value_digest()); // Add key length (in bits) to hash pre-image - let mut key = self.key(); + let mut key = self.full_path(); // let mut key = key.as_ref().iter(); let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; add_varint_to_buf(buf, key_bit_len); From fee6bb6c237944bf194955cdd047064e396d2abf Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 15 Sep 2025 15:55:20 -0400 Subject: [PATCH 0943/1053] test(firewood): use ctor section to init logger for all tests (#1277) This uses the `ctor` library to install a function in the binary `ctor` section that initializes the logger for all tests. This makes it so we don't need to manually pepper in logger initializers for all tests. This isn't really safe for production level code and should only be enabled for testing. --- firewood/Cargo.toml | 1 + firewood/src/db.rs | 2 -- firewood/src/lib.rs | 19 +++++++++++++++++++ firewood/src/merkle/tests/ethhash.rs | 5 ----- firewood/src/merkle/tests/mod.rs | 2 -- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 6067b7a1f16b..aa8efcc6c51d 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -56,6 +56,7 @@ rand.workspace = true tempfile.workspace = true test-case.workspace = true # Regular dependencies +ctor = "0.5.0" hash-db = "0.16.0" plain_hasher = "0.2.3" rlp = "0.6.1" diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8482ac9fa45d..f4fec80d00a6 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -619,8 +619,6 @@ mod test { #[test] fn fuzz_checker() { - let _ = env_logger::Builder::new().is_test(true).try_init(); - let rng = firewood_storage::SeededRng::from_env_or_random(); let db = testdb(); diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index d34f65e77bb0..186d48b81749 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -150,3 +150,22 @@ pub mod v2; /// Expose the storage logger pub use firewood_storage::logger; + +#[cfg(all(test, feature = "logger"))] +#[ctor::ctor] +/// `ctor` will ensure this function is invoked before any tests are run so we +/// can initialize the logger consistently across all tests without having to +/// manually call it in each test. +/// +/// This is technically black magic as it runs before `main` is invoked, which +/// violates some of the Rust guarantees. But, it is convenient to ensure the +/// logger is initialized for all tests. +/// +/// In the event of unexpected behavior in testing, disable this first before +/// looking elsewhere. +fn init_logger() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")) + .is_test(true) + .try_init() + .ok(); +} diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index e577f0daa3be..5be7f763f085 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -86,11 +86,6 @@ fn test_eth_compatible_accounts( use sha2::Digest as _; use sha3::Keccak256; - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")) - .is_test(true) - .try_init() - .ok(); - let account = make_key(account); let expected_key_hash = Keccak256::digest(&account); diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index 00b9a2f2db8a..b55e27fba1ae 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -707,8 +707,6 @@ fn test_delete_some() { #[test] fn test_root_hash_reversed_deletions() -> Result<(), FileIoError> { - let _ = env_logger::Builder::new().is_test(true).try_init(); - let rng = firewood_storage::SeededRng::from_env_or_random(); let max_len0 = 8; From 9270be7b371935ef546ce8e7c4b0828a4dcfe6ce Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 16 Sep 2025 15:01:20 -0400 Subject: [PATCH 0944/1053] fix: resolve build failures by pinning opentelemetry to 0.30 (#1281) OpenTelemetry updated to 0.30.1 today, but not all packages were updated causing our builds to fail. This pins them to 0.30.0 until the issue is resolved. --- benchmark/Cargo.toml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index f874cd52cc11..e02f70a1f20c 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -13,6 +13,9 @@ repository.workspace = true readme.workspace = true rust-version.workspace = true +[package.metadata.cargo-machete] +ignored = ["opentelemetry-proto"] + [[bin]] name = "benchmark" path = "src/main.rs" @@ -34,9 +37,10 @@ sha2.workspace = true # Regular dependencies fastrace-opentelemetry = { version = "0.13.0" } metrics-exporter-prometheus = { version = "0.17.2", optional = true } -opentelemetry = "0.30.0" -opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic"] } -opentelemetry_sdk = "0.30.0" +opentelemetry = "=0.30.0" +opentelemetry-otlp = { version = "=0.30.0", features = ["grpc-tonic"] } +opentelemetry-proto = "=0.30.0" +opentelemetry_sdk = "=0.30.0" pretty-duration = "0.1.1" [dependencies.tokio] From adf1e02b5d1e30bed2c62f7e1cff649496aea1ee Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 16 Sep 2025 15:09:12 -0400 Subject: [PATCH 0945/1053] feat(range-proofs): KeyValuePairIter (1/2) (#1282) Part 1: Implement KeyValuePairIter for RangeProof Needed so that propose can consume a RangeProof directly. --- firewood/src/range_proof.rs | 155 ++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index 6c70ebafcf10..c253d52acca7 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -93,4 +93,159 @@ where pub fn is_empty(&self) -> bool { self.start_proof.is_empty() && self.end_proof.is_empty() && self.key_values.is_empty() } + + /// Returns an iterator over the key-value pairs in this range proof. + /// + /// The iterator yields references to the key-value pairs in the order they + /// appear in the proof (which should be lexicographic order as they appear + /// in the trie). + #[must_use] + pub fn iter(&self) -> RangeProofIter<'_, K, V> { + RangeProofIter(self.key_values.iter()) + } +} + +/// An iterator over the key-value pairs in a [`RangeProof`]. +/// +/// This iterator yields references to the key-value pairs contained within +/// the range proof in the order they appear (lexicographic order). +#[derive(Debug)] +pub struct RangeProofIter<'a, K, V>(std::slice::Iter<'a, (K, V)>); + +impl<'a, K, V> Iterator for RangeProofIter<'a, K, V> { + type Item = &'a (K, V); + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for RangeProofIter<'_, K, V> {} + +impl std::iter::FusedIterator for RangeProofIter<'_, K, V> {} + +impl<'a, K, V, H> IntoIterator for &'a RangeProof +where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + H: ProofCollection, +{ + type Item = &'a (K, V); + type IntoIter = RangeProofIter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[cfg(test)] +mod tests { + #![expect(clippy::unwrap_used, reason = "Tests can use unwrap")] + #![expect(clippy::indexing_slicing, reason = "Tests can use indexing")] + + use super::*; + use crate::v2::api::KeyValuePairIter; + + #[test] + fn test_range_proof_iterator() { + // Create test data + let key_values: Box<[(Vec, Vec)]> = Box::new([ + (b"key1".to_vec(), b"value1".to_vec()), + (b"key2".to_vec(), b"value2".to_vec()), + (b"key3".to_vec(), b"value3".to_vec()), + ]); + + // Create empty proofs for testing + let start_proof = Proof::empty(); + let end_proof = Proof::empty(); + + let range_proof = RangeProof::new(start_proof, end_proof, key_values); + + // Test basic iterator functionality + let mut iter = range_proof.iter(); + assert_eq!(iter.len(), 3); + + let first = iter.next().unwrap(); + assert_eq!(first.0, b"key1"); + assert_eq!(first.1, b"value1"); + + let second = iter.next().unwrap(); + assert_eq!(second.0, b"key2"); + assert_eq!(second.1, b"value2"); + + let third = iter.next().unwrap(); + assert_eq!(third.0, b"key3"); + assert_eq!(third.1, b"value3"); + + assert!(iter.next().is_none()); + } + + #[test] + fn test_range_proof_into_iterator() { + let key_values: Box<[(Vec, Vec)]> = Box::new([ + (b"a".to_vec(), b"alpha".to_vec()), + (b"b".to_vec(), b"beta".to_vec()), + ]); + + let start_proof = Proof::empty(); + let end_proof = Proof::empty(); + let range_proof = RangeProof::new(start_proof, end_proof, key_values); + + // Test that we can use for-loop syntax + let mut items = Vec::new(); + for item in &range_proof { + items.push(item); + } + + assert_eq!(items.len(), 2); + assert_eq!(items[0].0, b"a"); + assert_eq!(items[0].1, b"alpha"); + assert_eq!(items[1].0, b"b"); + assert_eq!(items[1].1, b"beta"); + } + + #[test] + fn test_keyvaluepair_iter_trait() { + let key_values: Box<[(Vec, Vec)]> = + Box::new([(b"test".to_vec(), b"data".to_vec())]); + + let start_proof = Proof::empty(); + let end_proof = Proof::empty(); + let range_proof = RangeProof::new(start_proof, end_proof, key_values); + + // Test that our iterator implements KeyValuePairIter + let iter = range_proof.iter(); + + // Verify we can call methods from KeyValuePairIter + let batch_iter = iter.map_into_batch(); + let batches: Vec<_> = batch_iter.collect(); + + assert_eq!(batches.len(), 1); + // The batch should be a Put operation since value is non-empty + if let crate::v2::api::BatchOp::Put { key, value } = &batches[0] { + assert_eq!(key.as_ref() as &[u8], b"test"); + assert_eq!(value.as_ref() as &[u8], b"data"); + } else { + panic!("Expected Put operation"); + } + } + + #[test] + fn test_empty_range_proof_iterator() { + let key_values: Box<[(Vec, Vec)]> = Box::new([]); + let start_proof = Proof::empty(); + let end_proof = Proof::empty(); + let range_proof = RangeProof::new(start_proof, end_proof, key_values); + + let mut iter = range_proof.iter(); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); + + let items: Vec<_> = range_proof.into_iter().collect(); + assert!(items.is_empty()); + } } From 2a119c89a700abcf280fe330f16ddab9acfae5e8 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 16 Sep 2025 15:16:41 -0400 Subject: [PATCH 0946/1053] feat(proofs)!: add v0 serialization for RangeProofs (#1271) --- Cargo.toml | 3 + clippy.toml | 6 + ffi/firewood.h | 16 +- ffi/memory.go | 30 ---- ffi/proofs.go | 41 ++++- ffi/proofs_test.go | 167 ++++++++++++++++++ ffi/src/proofs/range.rs | 58 ++++++- ffi/src/value.rs | 57 ++++++- ffi/src/value/results.rs | 27 +++ firewood/Cargo.toml | 4 +- firewood/src/lib.rs | 4 + firewood/src/merkle.rs | 2 +- firewood/src/merkle/tests/ethhash.rs | 2 +- firewood/src/merkle/tests/mod.rs | 2 +- firewood/src/merkle/tests/proof.rs | 34 ++++ firewood/src/proof.rs | 12 +- firewood/src/proofs.rs | 45 +++++ firewood/src/proofs/bitmap.rs | 126 ++++++++++++++ firewood/src/proofs/de.rs | 245 +++++++++++++++++++++++++++ firewood/src/proofs/header.rs | 161 ++++++++++++++++++ firewood/src/proofs/proof_type.rs | 50 ++++++ firewood/src/proofs/reader.rs | 171 +++++++++++++++++++ firewood/src/proofs/ser.rs | 194 +++++++++++++++++++++ firewood/src/proofs/tests.rs | 226 ++++++++++++++++++++++++ firewood/src/range_proof.rs | 2 +- storage/Cargo.toml | 8 +- storage/src/hashednode.rs | 68 +++++--- storage/src/hashers/ethhash.rs | 4 +- storage/src/hashers/merkledb.rs | 14 +- 29 files changed, 1678 insertions(+), 101 deletions(-) create mode 100644 ffi/proofs_test.go create mode 100644 firewood/src/proofs.rs create mode 100644 firewood/src/proofs/bitmap.rs create mode 100644 firewood/src/proofs/de.rs create mode 100644 firewood/src/proofs/header.rs create mode 100644 firewood/src/proofs/proof_type.rs create mode 100644 firewood/src/proofs/reader.rs create mode 100644 firewood/src/proofs/ser.rs create mode 100644 firewood/src/proofs/tests.rs diff --git a/Cargo.toml b/Cargo.toml index f9958a2337c5..d9e577062c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,11 +61,14 @@ firewood-triehash = { path = "triehash", version = "0.0.12" } # common dependencies aquamarine = "0.6.0" +bytemuck = "1.23.1" +bytemuck_derive = "1.10.0" clap = { version = "4.5.41", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" hex = "0.4.3" +integer-encoding = "4.0.2" log = "0.4.27" metrics = "0.24.2" metrics-util = "0.20.0" diff --git a/clippy.toml b/clippy.toml index 7b2993e3029a..1afe1ddf5b69 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,6 +3,12 @@ msrv = "1.89" +doc-valid-idents = [ + "MerkleDB", + # this list must end with ".." so that it does not truncate the default list + "..", +] + disallowed-methods = [ { path = "rand::rng", replacement = "firewood_storage::StdRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, ] diff --git a/ffi/firewood.h b/ffi/firewood.h index bb5803cb1488..9fb52e7e5568 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -191,7 +191,7 @@ typedef struct BorrowedSlice_KeyValuePair BorrowedKeyValuePairs; * Maybe is a C-compatible optional type using a tagged union pattern. * * FFI methods and types can use this to represent optional values where `Optional` - * does not work due to it not having C-compatible layout. + * does not work due to it not having a C-compatible layout. */ typedef enum Maybe_OwnedBytes_Tag { /** @@ -415,7 +415,7 @@ typedef struct VoidResult { * Maybe is a C-compatible optional type using a tagged union pattern. * * FFI methods and types can use this to represent optional values where `Optional` - * does not work due to it not having C-compatible layout. + * does not work due to it not having a C-compatible layout. */ typedef enum Maybe_BorrowedBytes_Tag { /** @@ -490,6 +490,10 @@ typedef enum RangeProofResult_Tag { * The provided root was not found in the database. */ RangeProofResult_RevisionNotFound, + /** + * A range proof was requested on an empty trie. + */ + RangeProofResult_EmptyTrie, /** * The proof was successfully created or parsed. * @@ -941,8 +945,8 @@ struct ChangeProofResult fwd_db_change_proof(const struct DatabaseHandle *_db, * was successfully created. * - [`RangeProofResult::Err`] containing an error message if the proof could not be created. */ -struct RangeProofResult fwd_db_range_proof(const struct DatabaseHandle *_db, - struct CreateRangeProofArgs _args); +struct RangeProofResult fwd_db_range_proof(const struct DatabaseHandle *db, + struct CreateRangeProofArgs args); /** * Verify and commit a change proof to the database. @@ -1380,7 +1384,7 @@ struct NextKeyRangeResult fwd_range_proof_find_next_key(struct RangeProofContext * well-formed. The verify method must be called to ensure the proof is cryptographically valid. * - [`RangeProofResult::Err`] containing an error message if the proof could not be parsed. */ -struct RangeProofResult fwd_range_proof_from_bytes(BorrowedBytes _bytes); +struct RangeProofResult fwd_range_proof_from_bytes(BorrowedBytes bytes); /** * Serialize a `RangeProof` to bytes. @@ -1397,7 +1401,7 @@ struct RangeProofResult fwd_range_proof_from_bytes(BorrowedBytes _bytes); * - [`ValueResult::Some`] containing the serialized bytes if successful. * - [`ValueResult::Err`] if the caller provided a null pointer. */ -struct ValueResult fwd_range_proof_to_bytes(const struct RangeProofContext *_proof); +struct ValueResult fwd_range_proof_to_bytes(const struct RangeProofContext *proof); /** * Verify a range proof against the given start and end keys and root hash. The diff --git a/ffi/memory.go b/ffi/memory.go index c6908e105db7..a3b8061212d2 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -348,36 +348,6 @@ func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) { } } -func getRangeProofFromRangeProofResult(result C.RangeProofResult) (*RangeProof, error) { - switch result.tag { - case C.RangeProofResult_NullHandlePointer: - return nil, errDBClosed - case C.RangeProofResult_Ok: - ptr := *(**C.RangeProofContext)(unsafe.Pointer(&result.anon0)) - return &RangeProof{handle: ptr}, nil - case C.RangeProofResult_Err: - err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() - return nil, err - default: - return nil, fmt.Errorf("unknown C.RangeProofResult tag: %d", result.tag) - } -} - -func getChangeProofFromChangeProofResult(result C.ChangeProofResult) (*ChangeProof, error) { - switch result.tag { - case C.ChangeProofResult_NullHandlePointer: - return nil, errDBClosed - case C.ChangeProofResult_Ok: - ptr := *(**C.ChangeProofContext)(unsafe.Pointer(&result.anon0)) - return &ChangeProof{handle: ptr}, nil - case C.ChangeProofResult_Err: - err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() - return nil, err - default: - return nil, fmt.Errorf("unknown C.ChangeProofResult tag: %d", result.tag) - } -} - // hashAndIDFromValue converts the cgo `Value` payload into: // // case | data | len | meaning diff --git a/ffi/proofs.go b/ffi/proofs.go index e8a0d18090c1..6373619f2ddf 100644 --- a/ffi/proofs.go +++ b/ffi/proofs.go @@ -14,7 +14,10 @@ import ( "unsafe" ) -var errNotPrepared = errors.New("proof not prepared into a proposal or committed") +var ( + errNotPrepared = errors.New("proof not prepared into a proposal or committed") + errEmptyTrie = errors.New("a range proof was requested on an empty trie") +) // RangeProof represents a proof that a range of keys and their values are // included in a trie with a given root hash. @@ -395,3 +398,39 @@ func getNextKeyRangeFromNextKeyRangeResult(result C.NextKeyRangeResult) (*NextKe return nil, fmt.Errorf("unknown C.NextKeyRangeResult tag: %d", result.tag) } } + +func getRangeProofFromRangeProofResult(result C.RangeProofResult) (*RangeProof, error) { + switch result.tag { + case C.RangeProofResult_NullHandlePointer: + return nil, errDBClosed + case C.RangeProofResult_RevisionNotFound: + // NOTE: the result value contains the provided root hash, we could use + // it in the error message if needed. + return nil, errRevisionNotFound + case C.RangeProofResult_EmptyTrie: + return nil, errEmptyTrie + case C.RangeProofResult_Ok: + ptr := *(**C.RangeProofContext)(unsafe.Pointer(&result.anon0)) + return &RangeProof{handle: ptr}, nil + case C.RangeProofResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.RangeProofResult tag: %d", result.tag) + } +} + +func getChangeProofFromChangeProofResult(result C.ChangeProofResult) (*ChangeProof, error) { + switch result.tag { + case C.ChangeProofResult_NullHandlePointer: + return nil, errDBClosed + case C.ChangeProofResult_Ok: + ptr := *(**C.ChangeProofContext)(unsafe.Pointer(&result.anon0)) + return &ChangeProof{handle: ptr}, nil + case C.ChangeProofResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.ChangeProofResult tag: %d", result.tag) + } +} diff --git a/ffi/proofs_test.go b/ffi/proofs_test.go new file mode 100644 index 000000000000..5b58bea253f1 --- /dev/null +++ b/ffi/proofs_test.go @@ -0,0 +1,167 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +const maxProofLen = 10 + +type maybe struct { + value []byte + hasValue bool +} + +func (m maybe) HasValue() bool { + return m.hasValue +} + +func (m maybe) Value() []byte { + return m.value +} + +func something(b []byte) maybe { + return maybe{ + hasValue: true, + value: b, + } +} + +func nothing() maybe { + return maybe{ + hasValue: false, + } +} + +func TestRangeProofEmptyDB(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + proof, err := db.RangeProof(nothing(), nothing(), nothing(), 0) + r.ErrorIs(err, errEmptyTrie) + r.Nil(proof) +} + +func TestRangeProofNonExistentRoot(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // insert some data + keys, vals := kvForTest(100) + root, err := db.Update(keys, vals) + r.NoError(err) + r.NotNil(root) + + // create a bogus root + bogusRoot := make([]byte, len(root)) + copy(bogusRoot, root) + bogusRoot[0] ^= 0xFF + + proof, err := db.RangeProof(something(bogusRoot), nothing(), nothing(), 0) + r.ErrorIs(err, errRevisionNotFound) + r.Nil(proof) +} + +func TestRangeProofPartialRange(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Insert a lot of data. + keys, vals := kvForTest(10000) + root, err := db.Update(keys, vals) + r.NoError(err) + + // get a proof over some partial range + proof1 := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing()) + + // get a proof over a different range + proof2 := rangeProofWithAndWithoutRoot(t, db, root, something([]byte("key2")), something([]byte("key3"))) + + // ensure the proofs are different + r.NotEqual(proof1, proof2) + + // TODO(https://github.com/ava-labs/firewood/issues/738): verify the proofs +} + +func TestRangeProofDiffersAfterUpdate(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Insert some data. + keys, vals := kvForTest(100) + root1, err := db.Update(keys[:50], vals[:50]) + r.NoError(err) + + // get a proof + proof := rangeProofWithAndWithoutRoot(t, db, root1, nothing(), nothing()) + + // insert more data + root2, err := db.Update(keys[50:], vals[50:]) + r.NoError(err) + r.NotEqual(root1, root2) + + // get a proof again + proof2 := rangeProofWithAndWithoutRoot(t, db, root2, nothing(), nothing()) + + // ensure the proofs are different + r.NotEqual(proof, proof2) +} + +func TestRoundTripSerialization(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Insert some data. + keys, vals := kvForTest(10) + root, err := db.Update(keys, vals) + r.NoError(err) + + // get a proof + proofBytes := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing()) + + // Deserialize the proof. + proof := new(RangeProof) + err = proof.UnmarshalBinary(proofBytes) + r.NoError(err) + + // serialize the proof again + serialized, err := proof.MarshalBinary() + r.NoError(err) + r.Equal(proofBytes, serialized) + + r.NoError(proof.Free()) +} + +// rangeProofWithAndWithoutRoot checks that requesting a range proof with and +// without the root, when the default root is the same as the provided root, +// yields the same proof and returns the proof bytes. +func rangeProofWithAndWithoutRoot( + t *testing.T, + db *Database, + root []byte, + startKey, endKey maybe, +) []byte { + r := require.New(t) + + proof1, err := db.RangeProof(maybe{hasValue: false}, startKey, endKey, maxProofLen) + r.NoError(err) + r.NotNil(proof1) + proof1Bytes, err := proof1.MarshalBinary() + r.NoError(err) + r.NoError(proof1.Free()) + + proof2, err := db.RangeProof(maybe{hasValue: true, value: root}, startKey, endKey, maxProofLen) + r.NoError(err) + r.NotNil(proof2) + proof2Bytes, err := proof2.MarshalBinary() + r.NoError(err) + r.NoError(proof2.Free()) + + r.Equal(proof1Bytes, proof2Bytes) + + return proof1Bytes +} diff --git a/ffi/src/proofs/range.rs b/ffi/src/proofs/range.rs index af0ea023e23b..34dd32231063 100644 --- a/ffi/src/proofs/range.rs +++ b/ffi/src/proofs/range.rs @@ -1,7 +1,9 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::FrozenRangeProof; +use std::num::NonZeroUsize; + +use firewood::v2::api::{self, FrozenRangeProof}; use crate::{ BorrowedBytes, CResult, DatabaseHandle, HashResult, Maybe, NextKeyRangeResult, @@ -65,7 +67,7 @@ pub struct VerifyRangeProofArgs<'a> { /// FFI context for for a parsed or generated range proof. #[derive(Debug)] pub struct RangeProofContext { - _proof: FrozenRangeProof, + proof: FrozenRangeProof, /// Information about the proof discovered during verification that does not /// need to be recomputed. Also serves as a token that ensured we have /// validated the proof and can skip it during commit. @@ -77,6 +79,16 @@ pub struct RangeProofContext { _commit_context: (), // placeholder for future use } +impl From for RangeProofContext { + fn from(proof: FrozenRangeProof) -> Self { + Self { + proof, + _validation_context: (), + _commit_context: (), + } + } +} + /// Generate a range proof for the given range of keys for the latest revision. /// /// # Arguments @@ -94,10 +106,30 @@ pub struct RangeProofContext { /// - [`RangeProofResult::Err`] containing an error message if the proof could not be created. #[unsafe(no_mangle)] pub extern "C" fn fwd_db_range_proof( - _db: Option<&DatabaseHandle>, - _args: CreateRangeProofArgs, + db: Option<&DatabaseHandle>, + args: CreateRangeProofArgs, ) -> RangeProofResult { - CResult::from_err("not yet implemented") + crate::invoke_with_handle(db, |db| { + let root_hash = match args.root { + Maybe::Some(root) => root.as_ref().try_into()?, + Maybe::None => db + .current_root_hash()? + .ok_or(api::Error::RangeProofOnEmptyTrie)?, + }; + + let view = db.get_root(root_hash)?; + view.range_proof( + args.start_key + .as_ref() + .map(BorrowedBytes::as_slice) + .into_option(), + args.end_key + .as_ref() + .map(BorrowedBytes::as_slice) + .into_option(), + NonZeroUsize::new(args.max_length as usize), + ) + }) } /// Verify a range proof against the given start and end keys and root hash. The @@ -242,8 +274,12 @@ pub extern "C" fn fwd_range_proof_find_next_key( /// - [`ValueResult::Some`] containing the serialized bytes if successful. /// - [`ValueResult::Err`] if the caller provided a null pointer. #[unsafe(no_mangle)] -pub extern "C" fn fwd_range_proof_to_bytes(_proof: Option<&RangeProofContext>) -> ValueResult { - CResult::from_err("not yet implemented") +pub extern "C" fn fwd_range_proof_to_bytes(proof: Option<&RangeProofContext>) -> ValueResult { + crate::invoke_with_handle(proof, |ctx| { + let mut vec = Vec::new(); + ctx.proof.write_to_vec(&mut vec); + vec + }) } /// Deserialize a `RangeProof` from bytes. @@ -260,8 +296,12 @@ pub extern "C" fn fwd_range_proof_to_bytes(_proof: Option<&RangeProofContext>) - /// well-formed. The verify method must be called to ensure the proof is cryptographically valid. /// - [`RangeProofResult::Err`] containing an error message if the proof could not be parsed. #[unsafe(no_mangle)] -pub extern "C" fn fwd_range_proof_from_bytes(_bytes: BorrowedBytes) -> RangeProofResult { - CResult::from_err("not yet implemented") +pub extern "C" fn fwd_range_proof_from_bytes(bytes: BorrowedBytes) -> RangeProofResult { + crate::invoke(move || { + FrozenRangeProof::from_slice(&bytes).map_err(|err| { + api::Error::ProofError(firewood::proof::ProofError::Deserialization(err)) + }) + }) } /// Frees the memory associated with a `RangeProofContext`. diff --git a/ffi/src/value.rs b/ffi/src/value.rs index ea443e19f8bd..b9baa4d5c2c7 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -22,8 +22,8 @@ pub use self::results::{ /// Maybe is a C-compatible optional type using a tagged union pattern. /// /// FFI methods and types can use this to represent optional values where `Optional` -/// does not work due to it not having C-compatible layout. -#[derive(Debug)] +/// does not work due to it not having a C-compatible layout. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] pub enum Maybe { /// No value present. @@ -31,3 +31,56 @@ pub enum Maybe { /// A value is present. Some(T), } + +impl Maybe { + /// Returns true if the `Maybe` contains a value. + pub const fn is_some(&self) -> bool { + matches!(self, Maybe::Some(_)) + } + + /// Returns true if the `Maybe` does not contain a value. + pub const fn is_none(&self) -> bool { + matches!(self, Maybe::None) + } + + /// Converts from `&Maybe` to `Maybe<&T>`. + pub const fn as_ref(&self) -> Maybe<&T> { + match self { + Maybe::None => Maybe::None, + Maybe::Some(v) => Maybe::Some(v), + } + } + + /// Converts from `&mut Maybe` to `Maybe<&mut T>`. + pub const fn as_mut(&mut self) -> Maybe<&mut T> { + match self { + Maybe::None => Maybe::None, + Maybe::Some(v) => Maybe::Some(v), + } + } + + /// Maps a `Maybe` to `Maybe` by applying a function to a contained value. + pub fn map U>(self, f: F) -> Maybe { + match self { + Maybe::None => Maybe::None, + Maybe::Some(v) => Maybe::Some(f(v)), + } + } + + /// Converts from `Maybe` to `Option`. + pub fn into_option(self) -> Option { + match self { + Maybe::None => None, + Maybe::Some(v) => Some(v), + } + } +} + +impl From> for Maybe { + fn from(opt: Option) -> Self { + match opt { + None => Maybe::None, + Some(v) => Maybe::Some(v), + } + } +} diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 853eef7f000f..4ef89350a06d 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -130,6 +130,18 @@ impl From>, firewood::db::DbError>> for ValueResult { } } +impl From> for ValueResult { + fn from(value: Vec) -> Self { + value.into_boxed_slice().into() + } +} + +impl From> for ValueResult { + fn from(value: Box<[u8]>) -> Self { + ValueResult::Some(value.into()) + } +} + /// A result type returned from FFI functions return the database root hash. This /// may or may not be after a mutation. #[derive(Debug)] @@ -178,6 +190,8 @@ pub enum RangeProofResult { NullHandlePointer, /// The provided root was not found in the database. RevisionNotFound(HashKey), + /// A range proof was requested on an empty trie. + EmptyTrie, /// The proof was successfully created or parsed. /// /// If the value was parsed from a serialized proof, this does not imply that @@ -194,6 +208,19 @@ pub enum RangeProofResult { Err(OwnedBytes), } +impl From> for RangeProofResult { + fn from(value: Result) -> Self { + match value { + Ok(proof) => RangeProofResult::Ok(Box::new(proof.into())), + Err(api::Error::RevisionNotFound { provided }) => RangeProofResult::RevisionNotFound( + HashKey::from(provided.unwrap_or_else(api::HashKey::empty)), + ), + Err(api::Error::RangeProofOnEmptyTrie) => RangeProofResult::EmptyTrie, + Err(err) => RangeProofResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// A result type returned from FFI functions that create or parse change proofs. /// /// The caller must ensure that [`fwd_free_change_proof`] is called to diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index aa8efcc6c51d..852ee2c30b70 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -24,12 +24,14 @@ rust-version.workspace = true [dependencies] # Workspace dependencies +bytemuck_derive.workspace = true +bytemuck.workspace = true coarsetime.workspace = true fastrace.workspace = true firewood-macros.workspace = true hex.workspace = true +integer-encoding.workspace = true metrics.workspace = true -sha2.workspace = true thiserror.workspace = true # Regular dependencies typed-builder = "0.22.0" diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 186d48b81749..0fed6bd3a1b5 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -139,6 +139,10 @@ pub mod merkle; /// Proof module pub mod proof; +/// Change, Range, and Key proofs for the Merkle Trie +// TODO: push `proof` and `range_proof` modules into this module +pub mod proofs; + // Re-export the proc macro from firewood-macros pub use firewood_macros::metrics; diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index dc134f5c0b08..664a3e2bf92e 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. #[cfg(test)] -mod tests; +pub(crate) mod tests; use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index 5be7f763f085..22e90118fa17 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -83,7 +83,7 @@ fn test_eth_compatible_accounts( key_suffixes_and_values: &[(&str, &str)], expected_root: &str, ) { - use sha2::Digest as _; + use sha3::Digest as _; use sha3::Keccak256; let account = make_key(account); diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index b55e27fba1ae..ce856a1cd10a 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -45,7 +45,7 @@ fn into_committed( ns.into() } -fn init_merkle(iter: I) -> Merkle> +pub(crate) fn init_merkle(iter: I) -> Merkle> where I: Clone + IntoIterator, K: AsRef<[u8]>, diff --git a/firewood/src/merkle/tests/proof.rs b/firewood/src/merkle/tests/proof.rs index edfa07dbf6eb..f8423d07ef33 100644 --- a/firewood/src/merkle/tests/proof.rs +++ b/firewood/src/merkle/tests/proof.rs @@ -34,6 +34,14 @@ fn full_range_proof() { let right_proof = merkle.prove(&[u8::MAX]).unwrap(); assert_eq!(rangeproof.start_proof(), &left_proof); assert_eq!(rangeproof.end_proof(), &right_proof); + + let rangeproof = roundtrip_range_proof(&rangeproof); + assert_eq!(rangeproof.key_values().len(), u8::MAX as usize + 1); + assert_ne!(rangeproof.start_proof(), rangeproof.end_proof()); + let left_proof = merkle.prove(&[u8::MIN]).unwrap(); + let right_proof = merkle.prove(&[u8::MAX]).unwrap(); + assert_eq!(rangeproof.start_proof(), &left_proof); + assert_eq!(rangeproof.end_proof(), &right_proof); } #[test] @@ -47,6 +55,10 @@ fn single_value_range_proof() { .unwrap(); assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); assert_eq!(rangeproof.key_values().len(), 1); + + let rangeproof = roundtrip_range_proof(&rangeproof); + assert_eq!(rangeproof.start_proof(), rangeproof.end_proof()); + assert_eq!(rangeproof.key_values().len(), 1); } #[test] @@ -269,3 +281,25 @@ fn proof_path_construction_and_corruption() { crate::proof::ProofError::NodeNotInTrie | crate::proof::ProofError::UnexpectedHash )); } + +#[test] +fn range_proof_serialization_roundtrip() { + let merkle = init_merkle((u8::MIN..=u8::MAX).map(|k| ([k], [k]))); + + let start_key = &[42u8]; + let end_key = &[84u8]; + + let rangeproof = merkle + .range_proof(Some(start_key), Some(end_key), NonZeroUsize::new(10)) + .unwrap(); + + drop(roundtrip_range_proof(&rangeproof)); +} + +fn roundtrip_range_proof(proof: &FrozenRangeProof) -> FrozenRangeProof { + let mut serialized = Vec::new(); + proof.write_to_vec(&mut serialized); + let deserialized = FrozenRangeProof::from_slice(&serialized).unwrap(); + assert_eq!(proof, &deserialized); + deserialized +} diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index d3446ad349b1..e53992ddd6de 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -69,12 +69,16 @@ pub enum ProofError { #[error("{0:?}")] IO(#[from] FileIoError), + /// Error deserializing a proof + #[error("error deserializing a proof: {0}")] + Deserialization(crate::proofs::ReadError), + /// Empty range #[error("empty range")] EmptyRange, } -#[derive(Clone, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq, Eq)] /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. @@ -124,11 +128,7 @@ impl Hashable for ProofNode { } fn value_digest(&self) -> Option> { - self.value_digest.as_ref().map(|vd| match vd { - ValueDigest::Value(v) => ValueDigest::Value(v.as_ref()), - #[cfg(not(feature = "ethhash"))] - ValueDigest::Hash(h) => ValueDigest::Hash(h.as_ref()), - }) + self.value_digest.as_ref().map(ValueDigest::as_ref) } fn children(&self) -> Children { diff --git a/firewood/src/proofs.rs b/firewood/src/proofs.rs new file mode 100644 index 000000000000..6ab58f28f9e3 --- /dev/null +++ b/firewood/src/proofs.rs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +mod bitmap; +mod de; +mod header; +mod proof_type; +mod reader; +mod ser; +#[cfg(test)] +mod tests; + +pub use self::header::InvalidHeader; +pub use self::reader::ReadError; + +mod magic { + pub const PROOF_HEADER: &[u8; 8] = b"fwdproof"; + + pub const PROOF_VERSION: u8 = 0; + + #[cfg(not(feature = "ethhash"))] + pub const HASH_MODE: u8 = 0; + #[cfg(feature = "ethhash")] + pub const HASH_MODE: u8 = 1; + + pub const fn hash_mode_name(v: u8) -> &'static str { + match v { + 0 => "sha256", + 1 => "keccak256", + _ => "unknown", + } + } + + #[cfg(not(feature = "branch_factor_256"))] + pub const BRANCH_FACTOR: u8 = 16; + #[cfg(feature = "branch_factor_256")] + pub const BRANCH_FACTOR: u8 = 0; // 256 wrapped to 0 + + pub const fn widen_branch_factor(v: u8) -> u16 { + match v { + 0 => 256, + _ => v as u16, + } + } +} diff --git a/firewood/src/proofs/bitmap.rs b/firewood/src/proofs/bitmap.rs new file mode 100644 index 000000000000..9b02f70a4055 --- /dev/null +++ b/firewood/src/proofs/bitmap.rs @@ -0,0 +1,126 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood_storage::Children; + +#[derive(Clone, Copy, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] +#[repr(C)] +/// A bitmap indicating which children are present in a node. +pub(super) struct ChildrenMap([u8; ChildrenMap::SIZE]); + +impl ChildrenMap { + const SIZE: usize = firewood_storage::BranchNode::MAX_CHILDREN / 8; + + /// Create a new `ChildrenMap` from the given children array. + pub fn new(children: &Children) -> Self { + let mut map = [0_u8; Self::SIZE]; + + for (i, child) in children.iter().enumerate() { + if child.is_some() { + let (idx, bit) = (i / 8, i % 8); + #[expect(clippy::indexing_slicing)] + { + map[idx] |= 1 << bit; + } + } + } + + Self(map) + } + + #[cfg(test)] + pub fn len(self) -> usize { + self.0.iter().map(|b| b.count_ones() as usize).sum() + } + + pub fn iter_indices(self) -> impl Iterator { + (0..firewood_storage::BranchNode::MAX_CHILDREN).filter( + #[expect(clippy::indexing_slicing)] + move |i| self.0[i / 8] & (1 << (i % 8)) != 0, + ) + } +} + +impl std::fmt::Display for ChildrenMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + f.debug_list().entries(self.iter_indices()).finish() + } else { + write!(f, "{self:b}") + } + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl std::fmt::Binary for ChildrenMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:016b}", u16::from_le_bytes(self.0)) + } +} + +#[cfg(feature = "branch_factor_256")] +impl std::fmt::Binary for ChildrenMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let [a, b] = bytemuck::cast::<_, [[u8; 16]; 2]>(self.0); + let a = u128::from_le_bytes(a); + let b = u128::from_le_bytes(b); + write!(f, "{a:0128b}{b:0128b}") + } +} + +impl std::fmt::Debug for ChildrenMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use firewood_storage::BranchNode; + use test_case::test_case; + + #[test_case(BranchNode::empty_children(), &[]; "empty")] + #[test_case({ + let mut children = BranchNode::empty_children(); + children[0] = Some(()); + children + }, &[0]; "first")] + #[test_case({ + let mut children = BranchNode::empty_children(); + children[1] = Some(()); + children + }, &[1]; "second")] + #[test_case({ + let mut children = BranchNode::empty_children(); + children[BranchNode::MAX_CHILDREN - 1] = Some(()); + children + }, &[BranchNode::MAX_CHILDREN - 1]; "last")] + #[test_case({ + let mut children = BranchNode::empty_children(); + for slot in children.iter_mut().step_by(2) { + *slot = Some(()); + } + children + }, &(0..BranchNode::MAX_CHILDREN).step_by(2).collect::>(); "evens")] + #[test_case({ + let mut children = BranchNode::empty_children(); + for slot in children.iter_mut().skip(1).step_by(2) { + *slot = Some(()); + } + children + }, &(1..BranchNode::MAX_CHILDREN).step_by(2).collect::>(); "odds")] + #[test_case([Some(()); BranchNode::MAX_CHILDREN], &(0..BranchNode::MAX_CHILDREN).collect::>(); "all")] + fn test_children_map(children: Children<()>, indicies: &[usize]) { + let map = ChildrenMap::new(&children); + assert_eq!(map.len(), indicies.len()); + + assert!( + indicies.iter().copied().eq(map.iter_indices()), + "expected {:?}, got {:?}", + indicies, + map.iter_indices().collect::>() + ); + } +} diff --git a/firewood/src/proofs/de.rs b/firewood/src/proofs/de.rs new file mode 100644 index 000000000000..fdc6de474878 --- /dev/null +++ b/firewood/src/proofs/de.rs @@ -0,0 +1,245 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#[cfg(feature = "ethhash")] +use firewood_storage::HashType; +use firewood_storage::{BranchNode, TrieHash, ValueDigest}; +use integer_encoding::VarInt; + +use crate::{ + proof::{Proof, ProofNode}, + proofs::{ + bitmap::ChildrenMap, + header::{Header, InvalidHeader}, + proof_type::ProofType, + reader::{ProofReader, ReadError, ReadItem, V0Reader, Version0}, + }, + v2::api::FrozenRangeProof, +}; + +impl FrozenRangeProof { + /// Parses a `FrozenRangeProof` from the given byte slice. + /// + /// Currently only V0 proofs are supported. See [`FrozenRangeProof::write_to_vec`] + /// for the serialization format. + /// + /// # Errors + /// + /// Returns a [`ReadError`] if the data is invalid. See the enum variants for + /// the possible reasons. + pub fn from_slice(data: &[u8]) -> Result { + let mut reader = ProofReader::new(data); + + let header = reader.read_item::
    ()?; + header + .validate(Some(ProofType::Range)) + .map_err(ReadError::InvalidHeader)?; + + match header.version { + 0 => { + let mut reader = V0Reader::new(reader, header); + let this = reader.read_v0_item()?; + if reader.remainder().is_empty() { + Ok(this) + } else { + Err(reader.invalid_item( + "trailing bytes", + "no data after the proof", + format!("{} bytes", reader.remainder().len()), + )) + } + } + found => Err(ReadError::InvalidHeader( + InvalidHeader::UnsupportedVersion { found }, + )), + } + } +} + +impl Version0 for Box<[T]> { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { + let num_items = reader + .read_item::() + .map_err(|err| err.set_item("array length"))?; + + // FIXME: we must somehow validate `num_items` matches what is expected + // An incorrect, or unexpectedly large value could lead to DoS via OOM + // or panicing + (0..num_items).map(|_| reader.read_v0_item()).collect() + } +} + +impl Version0 for FrozenRangeProof { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { + let start_proof = reader.read_v0_item()?; + let end_proof = reader.read_v0_item()?; + let key_values = reader.read_v0_item()?; + + Ok(Self::new( + Proof::new(start_proof), + Proof::new(end_proof), + key_values, + )) + } +} + +impl Version0 for ProofNode { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { + let key = reader.read_item()?; + let partial_len = reader.read_item()?; + let value_digest = reader.read_item()?; + + let children_map = reader.read_item::()?; + + let mut child_hashes = BranchNode::empty_children(); + for idx in children_map.iter_indices() { + #[expect(clippy::indexing_slicing)] + { + child_hashes[idx] = Some(reader.read_item()?); + } + } + + Ok(ProofNode { + key, + partial_len, + value_digest, + child_hashes, + }) + } +} + +impl Version0 for (Box<[u8]>, Box<[u8]>) { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { + Ok((reader.read_item()?, reader.read_item()?)) + } +} + +impl<'a> ReadItem<'a> for Header { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader + .read_chunk::<{ size_of::
    () }>() + .map_err(|err| err.set_item("header")) + .copied() + .map(bytemuck::cast) + } +} + +impl<'a> ReadItem<'a> for usize { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + match u64::decode_var(reader.remainder()) { + Some((n, size)) => { + reader.advance(size); + Ok(n as usize) + } + None if reader.remainder().is_empty() => Err(reader.incomplete_item("varint", 1)), + #[expect(clippy::indexing_slicing)] + None => Err(reader.invalid_item( + "varint", + "byte with no MSB within 9 bytes", + format!( + "{:?}", + &reader.remainder()[..reader.remainder().len().min(10)] + ), + )), + } + } +} + +impl<'a> ReadItem<'a> for &'a [u8] { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + let len = reader.read_item::()?; + reader.read_slice(len) + } +} + +impl<'a> ReadItem<'a> for Box<[u8]> { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader.read_item::<&[u8]>().map(Box::from) + } +} + +impl<'a> ReadItem<'a> for u8 { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader + .read_chunk::<1>() + .map(|&[b]| b) + .map_err(|err| err.set_item("u8")) + } +} + +impl<'a, T: ReadItem<'a>> ReadItem<'a> for Option { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + match reader + .read_item::() + .map_err(|err| err.set_item("option discriminant"))? + { + 0 => Ok(None), + 1 => Ok(Some(reader.read_item::()?)), + found => Err(reader.invalid_item("option discriminant", "0 or 1", found)), + } + } +} + +impl<'a> ReadItem<'a> for ValueDigest<&'a [u8]> { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + match reader + .read_item::() + .map_err(|err| err.set_item("value digest discriminant"))? + { + 0 => Ok(ValueDigest::Value(reader.read_item()?)), + #[cfg(not(feature = "ethhash"))] + 1 => Ok(ValueDigest::Hash(reader.read_item()?)), + found => Err(reader.invalid_item( + "value digest discriminant", + "0 (value) or 1 (hash)", + found, + )), + } + } +} + +impl<'a> ReadItem<'a> for ValueDigest> { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader.read_item::>().map(|vd| match vd { + ValueDigest::Value(v) => ValueDigest::Value(v.into()), + #[cfg(not(feature = "ethhash"))] + ValueDigest::Hash(h) => ValueDigest::Hash(h), + }) + } +} + +impl<'a> ReadItem<'a> for TrieHash { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader + .read_chunk::<{ size_of::() }>() + .map_err(|err| err.set_item("trie hash")) + .copied() + .map(TrieHash::from) + } +} + +impl<'a> ReadItem<'a> for ChildrenMap { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + reader + .read_chunk::<{ size_of::() }>() + .map_err(|err| err.set_item("children map")) + .copied() + .map(bytemuck::cast) + } +} + +#[cfg(feature = "ethhash")] +impl<'a> ReadItem<'a> for HashType { + fn read_item(reader: &mut ProofReader<'a>) -> Result { + match reader + .read_item::() + .map_err(|err| err.set_item("hash type discriminant"))? + { + 0 => Ok(HashType::Hash(reader.read_item()?)), + 1 => Ok(HashType::Rlp(reader.read_item::<&[u8]>()?.into())), + found => { + Err(reader.invalid_item("hash type discriminant", "0 (hash) or 1 (rlp)", found)) + } + } + } +} diff --git a/firewood/src/proofs/header.rs b/firewood/src/proofs/header.rs new file mode 100644 index 000000000000..3fbdbcba4467 --- /dev/null +++ b/firewood/src/proofs/header.rs @@ -0,0 +1,161 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::proofs::{magic, proof_type::ProofType}; + +/// A fixed-size header at the beginning of every serialized proof. +/// +/// # Format +/// +/// - 8 bytes: A magic value to identify the file type. This is `b"fwdproof"`. +/// - 1 byte: The version of the proof format. Currently `0`. +/// - 1 byte: The hash mode used in the proof. Currently `0` for sha256, `1` for +/// keccak256. +/// - 1 byte: The branching factor of the trie. Currently `16` or `0` for `256`. +/// - 1 byte: The type of proof. See [`ProofType`]. +/// - 20 bytes: Reserved for future use and to pad the header to 32 bytes. Ignored +/// when reading, and set to zero when writing. +#[derive(Debug, Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] +#[repr(C)] +pub struct Header { + pub(super) magic: [u8; 8], + pub(super) version: u8, + pub(super) hash_mode: u8, + pub(super) branch_factor: u8, + pub(super) proof_type: u8, + pub(super) _reserved: [u8; 20], +} + +const _: () = { + assert!(size_of::
    () == 32); +}; + +impl From for Header { + fn from(proof_type: ProofType) -> Self { + Self { + magic: *magic::PROOF_HEADER, + version: magic::PROOF_VERSION, + hash_mode: magic::HASH_MODE, + branch_factor: magic::BRANCH_FACTOR, + proof_type: proof_type as u8, + _reserved: [0; 20], + } + } +} + +impl Header { + /// Validates the header, returning the discovered proof type if valid. + /// + /// If `expected_type` is `Some`, the proof type must match (in which case the return + /// value can be ignored). + /// + /// # Errors + /// + /// Returns an [`InvalidHeader`] if the header is invalid. See the enum variants for + /// possible reasons. + pub(super) fn validate( + &self, + expected_type: Option, + ) -> Result { + if self.magic != *magic::PROOF_HEADER { + return Err(InvalidHeader::InvalidMagic { found: self.magic }); + } + + if self.version != magic::PROOF_VERSION { + return Err(InvalidHeader::UnsupportedVersion { + found: self.version, + }); + } + + if self.hash_mode != magic::HASH_MODE { + return Err(InvalidHeader::UnsupportedHashMode { + found: self.hash_mode, + }); + } + + if self.branch_factor != magic::BRANCH_FACTOR { + return Err(InvalidHeader::UnsupportedBranchFactor { + found: self.branch_factor, + }); + } + + match (ProofType::new(self.proof_type), expected_type) { + (None, expected) => Err(InvalidHeader::InvalidProofType { + found: self.proof_type, + expected, + }), + (Some(found), Some(expected)) if found != expected => { + Err(InvalidHeader::InvalidProofType { + found: self.proof_type, + expected: Some(expected), + }) + } + (Some(found), _) => Ok(found), + } + } +} + +/// Error when validating the header. +#[derive(Debug, thiserror::Error)] +pub enum InvalidHeader { + /// Expected a static byte string to prefix the input. + #[error("invalid magic: found {:016x}; expected {:016x}", u64::from_be_bytes(*found), u64::from_be_bytes(*magic::PROOF_HEADER))] + InvalidMagic { + /// The actual bytes found in place where the magic header was expected. + found: [u8; 8], + }, + /// The proof was encoded with an unrecognized version. + #[error( + "unsupported proof version: found {found:02x}; expected {:02x}", + magic::PROOF_VERSION + )] + UnsupportedVersion { + /// The version byte found instead of a supported version. + found: u8, + }, + /// The proof was encoded for an unsupported hash mode. + #[error( + "unsupported hash mode: found {found:02x} ({}); expected {:02x} ({})", + magic::hash_mode_name(*found), + magic::HASH_MODE, + magic::hash_mode_name(magic::HASH_MODE) + )] + UnsupportedHashMode { + /// The flag indicating which hash mode created this proof. + found: u8, + }, + /// The proof was encoded for an unsupported branching factor. + #[error( + "unsupported branch factor: found {}; expected {}", + magic::widen_branch_factor(*found), + magic::widen_branch_factor(magic::BRANCH_FACTOR) + )] + UnsupportedBranchFactor { + /// The actual branch factor encoded in the header. + found: u8, + }, + /// The header indicated an unexpected or invalid proof type. + #[error( + "invalid proof type: found {found:02x} ({}); expected {}", + ProofType::new(*found).map_or("unknown", ProofType::name), + DisplayProofType(*expected), + )] + InvalidProofType { + /// The flag from the header. + found: u8, + /// The expected type, if any. Otherwise any type was expected and we + /// found an unknown value. + expected: Option, + }, +} + +struct DisplayProofType(Option); + +impl std::fmt::Display for DisplayProofType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Some(pt) => write!(f, "{:02x} ({})", pt as u8, pt.name()), + None => write!(f, "one of 0x00 (single), 0x01 (range), 0x02 (change)"), + } + } +} diff --git a/firewood/src/proofs/proof_type.rs b/firewood/src/proofs/proof_type.rs new file mode 100644 index 000000000000..fe8b019ac4ff --- /dev/null +++ b/firewood/src/proofs/proof_type.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +/// The type of serialized proof. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ProofType { + /// A proof for a single key/value pair. + /// + /// A proof is a sequence of nodes from the root to a specific node. + /// Each node in the path includes the hash of its child nodes, allowing + /// for verification of the integrity of the path. + /// + /// A single proof includes the full key and value (if present) of the target + /// node. + Single = 0, + /// A range proof for all key/value pairs over a specific key range. + /// + /// A range proof includes a key proof for the beginning and end of the + /// range, as well as all key/value pairs in the range. + Range = 1, + /// A change proof for all key/value pairs that changed between two + /// versions of the tree. + /// + /// A change proof includes a key proof for the beginning and end of the + /// changed range, as well as all key/value pairs that changed. + Change = 2, +} + +impl ProofType { + /// Parse a byte into a [`ProofType`]. + #[must_use] + pub const fn new(v: u8) -> Option { + match v { + 0 => Some(ProofType::Single), + 1 => Some(ProofType::Range), + 2 => Some(ProofType::Change), + _ => None, + } + } + + /// Human readable name for the [`ProofType`] + #[must_use] + pub const fn name(self) -> &'static str { + match self { + ProofType::Single => "single", + ProofType::Range => "range", + ProofType::Change => "change", + } + } +} diff --git a/firewood/src/proofs/reader.rs b/firewood/src/proofs/reader.rs new file mode 100644 index 000000000000..0d417acbbad4 --- /dev/null +++ b/firewood/src/proofs/reader.rs @@ -0,0 +1,171 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::proofs::header::{Header, InvalidHeader}; + +pub(super) trait ReadItem<'a>: Sized { + /// Reads an item from the given reader, or terrminates with an error. + fn read_item(data: &mut ProofReader<'a>) -> Result; +} + +pub(super) trait Version0: Sized { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result; +} + +pub(super) struct ProofReader<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> ProofReader<'a> { + #[must_use] + pub const fn new(data: &'a [u8]) -> Self { + Self { data, offset: 0 } + } + + pub fn read_chunk(&mut self) -> Result<&'a [u8; N], ReadError> { + if let Some((chunk, _)) = self.remainder().split_first_chunk::() { + #[expect(clippy::arithmetic_side_effects)] + { + self.offset += N; + } + Ok(chunk) + } else { + Err(self.incomplete_item(std::any::type_name::<[u8; N]>(), N)) + } + } + + pub fn read_slice(&mut self, n: usize) -> Result<&'a [u8], ReadError> { + if self.remainder().len() >= n { + let (slice, _) = self.remainder().split_at(n); + #[expect(clippy::arithmetic_side_effects)] + { + self.offset += n; + } + Ok(slice) + } else { + Err(self.incomplete_item("byte slice", n)) + } + } + + pub(super) fn read_item>(&mut self) -> Result { + T::read_item(self) + } + + pub fn remainder(&self) -> &'a [u8] { + #![expect(clippy::indexing_slicing)] + &self.data[self.offset..] + } + + pub fn advance(&mut self, n: usize) { + #![expect(clippy::arithmetic_side_effects)] + debug_assert!(self.offset + n <= self.data.len()); + self.offset += n; + } + + #[must_use] + pub const fn incomplete_item(&self, item: &'static str, expected: usize) -> ReadError { + ReadError::IncompleteItem { + item, + offset: self.offset, + expected, + #[expect(clippy::arithmetic_side_effects)] + found: self.data.len() - self.offset, + } + } + + #[must_use] + pub fn invalid_item( + &self, + item: &'static str, + expected: &'static str, + found: impl ToString, + ) -> ReadError { + ReadError::InvalidItem { + item, + offset: self.offset, + expected, + found: found.to_string(), + } + } +} + +pub(super) struct V0Reader<'a> { + inner: ProofReader<'a>, + header: Header, +} + +impl<'a> V0Reader<'a> { + #[must_use] + pub fn new(inner: ProofReader<'a>, header: Header) -> Self { + let this = Self { inner, header }; + debug_assert_eq!(this.header().version, 0); + this + } + + pub fn read_v0_item(&mut self) -> Result { + T::read_v0_item(self) + } + + pub const fn header(&self) -> &Header { + &self.header + } +} + +impl<'a> std::ops::Deref for V0Reader<'a> { + type Target = ProofReader<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for V0Reader<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// Error that ocurred while reading an item from the byte stream. +#[derive(Debug, thiserror::Error)] +pub enum ReadError { + /// Insufficient data in the byte stream. + #[error("incomplete {item} at offset {offset}: expected {expected} bytes, but found {found}")] + IncompleteItem { + /// The specific item that was trying to parse. + item: &'static str, + /// The offset in the byte stream where the error ocurred. + offset: usize, + /// The expected length of the input (for this item). + expected: usize, + /// The number of bytes found in the byte stream. + found: usize, + }, + /// An item was invalid after parsing. + #[error("invalid {item} at offset {offset}: expected {expected}, but found {found}")] + InvalidItem { + /// The item that was trying to parse. + item: &'static str, + /// The offset in the byte stream where the error ocurred. + offset: usize, + /// A hint at what was expected. + expected: &'static str, + /// Message indicating what was actually found. + found: String, + }, + /// Failed to validate the header. + #[error("invalid header: {0}")] + InvalidHeader(InvalidHeader), +} + +impl ReadError { + pub(super) const fn set_item(mut self, item: &'static str) -> Self { + match &mut self { + Self::IncompleteItem { item: e_item, .. } | Self::InvalidItem { item: e_item, .. } => { + *e_item = item; + } + Self::InvalidHeader(_) => {} + } + self + } +} diff --git a/firewood/src/proofs/ser.rs b/firewood/src/proofs/ser.rs new file mode 100644 index 000000000000..87665a71c3d4 --- /dev/null +++ b/firewood/src/proofs/ser.rs @@ -0,0 +1,194 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood_storage::ValueDigest; +use integer_encoding::VarInt; + +use crate::{ + proof::ProofNode, + proofs::{bitmap::ChildrenMap, header::Header, proof_type::ProofType}, + v2::api::FrozenRangeProof, +}; + +impl FrozenRangeProof { + /// Serializes this proof into the provided byte vector. + /// + /// # Format + /// + /// The V0 serialization format for a range proof is: + /// + #[expect( + rustdoc::private_intra_doc_links, + reason = "Header and ProofType are not exported" + )] + /// - A 32-byte [`Header`] with the proof type set to [`ProofType::Range`]. + /// - The start proof, serialized as a _sequence_ of [`ProofNode`]s + /// - The end proof, serialized as a _sequence_ of [`ProofNode`]s + /// - The key-value pairs, serialized as a _sequence_ of `(key, value)` tuples. + /// + /// Each [`ProofNode`] is serialized as: + /// - The key, serialized as a _sequence_ of bytes where each byte is a nibble + /// of the appropriate branching factor. E.g., if the trie has a branching + /// factor of 16, each byte is only the lower 4 bits of the byte and the + /// upper 4 bits are all zero. + /// - A variable-length integer indicating the length of the parent's key of + /// the `key`. I.e., `key[partial_len..]` is the partial path of this node. + /// - The value digest: + /// - If there is no value for the node, a single byte with the value `0`. + /// - If there is a value for the node, a single byte with the value `1` + /// followed by: + /// - If the value digest is of a full value, a single byte with the value + /// `0` followed by the value serialized as a _sequence_ of bytes. + /// - If the value digest is of a hash, a single byte with the value `1` + /// followed by the hash serialized as exactly 32 bytes. + /// - See [`ValueDigest::make_hash`] as to when a value digest is a hash. + /// - The children bitmap, which is a fixed-size bit field where each bit + /// indicates whether the corresponding child is present. The size of the + /// bitmap is `branching_factor / 8` bytes. E.g., if the branching factor is + /// 16, the bitmap is 2 bytes (16 bits) and if the branching factor is 256, + /// the bitmap is 32 bytes (256 bits). All zero bits indicate that the node + /// is a leaf node and no children follow. + /// - For each child that is present, as indicated by the bitmap, the child's + /// node ID is serialized. + /// - If the trie is using MerkleDB hashing, the ID is a fixed 32-byte + /// sha256 hash. + /// - If the trie is using Ethereum hashing, the ID is serialized as: + /// - A single byte with the value `0` if the ID is a keccak256 hash; + /// followed by the fixed 32-byte hash. + /// - A single byte with the value `1` if the ID is an RLP encoded node; + /// followed by the RLP encoded bytes serialized as a _sequence_ of + /// bytes. This occurs when the hash input (RLP encoded node) is smaller + /// than 32 bytes; in which case, the hash result is the input value + /// unhashed. + /// + /// Each _sequence_ mentioned above is prefixed with a variable-length integer + /// indicating the number of items in the sequence. + /// + /// Variable-length integers are encoded using unsigned LEB128. + pub fn write_to_vec(&self, out: &mut Vec) { + Header::from(ProofType::Range).write_item(out); + self.write_item(out); + } +} + +trait PushVarInt { + fn push_var_int(&mut self, v: VI); +} + +impl PushVarInt for Vec { + fn push_var_int(&mut self, v: VI) { + let mut buf = [0u8; 10]; + let n = v.encode_var(&mut buf); + #[expect(clippy::indexing_slicing)] + self.extend_from_slice(&buf[..n]); + } +} + +trait WriteItem { + fn write_item(&self, out: &mut Vec); +} + +impl WriteItem for FrozenRangeProof { + fn write_item(&self, out: &mut Vec) { + self.start_proof().write_item(out); + self.end_proof().write_item(out); + self.key_values().write_item(out); + } +} + +impl WriteItem for ProofNode { + fn write_item(&self, out: &mut Vec) { + self.key.write_item(out); + out.push_var_int(self.partial_len); + self.value_digest.write_item(out); + ChildrenMap::new(&self.child_hashes).write_item(out); + for child in self.child_hashes.iter().flatten() { + child.write_item(out); + } + } +} + +impl WriteItem for Option { + fn write_item(&self, out: &mut Vec) { + if let Some(v) = self { + out.push(1); + v.write_item(out); + } else { + out.push(0); + } + } +} + +impl WriteItem for [T] { + fn write_item(&self, out: &mut Vec) { + out.push_var_int(self.len()); + for item in self { + item.write_item(out); + } + } +} + +impl WriteItem for [u8] { + fn write_item(&self, out: &mut Vec) { + out.push_var_int(self.len()); + out.extend_from_slice(self); + } +} + +impl> WriteItem for ValueDigest { + fn write_item(&self, out: &mut Vec) { + match self.make_hash() { + ValueDigest::Value(v) => { + out.push(0); + v.write_item(out); + } + #[cfg(not(feature = "ethhash"))] + ValueDigest::Hash(h) => { + out.push(1); + h.write_item(out); + } + } + } +} + +impl WriteItem for Header { + fn write_item(&self, out: &mut Vec) { + out.extend_from_slice(bytemuck::bytes_of(self)); + } +} + +impl WriteItem for firewood_storage::TrieHash { + fn write_item(&self, out: &mut Vec) { + out.extend_from_slice(self.as_ref()); + } +} + +#[cfg(feature = "ethhash")] +impl WriteItem for firewood_storage::HashType { + fn write_item(&self, out: &mut Vec) { + match self { + firewood_storage::HashType::Hash(h) => { + out.push(0); + h.write_item(out); + } + firewood_storage::HashType::Rlp(h) => { + out.push(1); + h.write_item(out); + } + } + } +} + +impl WriteItem for ChildrenMap { + fn write_item(&self, out: &mut Vec) { + out.extend_from_slice(bytemuck::bytes_of(self)); + } +} + +impl, V: AsRef<[u8]>> WriteItem for (K, V) { + fn write_item(&self, out: &mut Vec) { + let (key, value) = self; + key.as_ref().write_item(out); + value.as_ref().write_item(out); + } +} diff --git a/firewood/src/proofs/tests.rs b/firewood/src/proofs/tests.rs new file mode 100644 index 000000000000..408935f748c2 --- /dev/null +++ b/firewood/src/proofs/tests.rs @@ -0,0 +1,226 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![expect(clippy::unwrap_used, clippy::indexing_slicing)] + +use integer_encoding::VarInt; +use test_case::test_case; + +use crate::{ + proofs::{header::InvalidHeader, magic, proof_type::ProofType, reader::ReadError}, + v2::api::FrozenRangeProof, +}; + +fn create_valid_range_proof() -> (FrozenRangeProof, Vec) { + let merkle = crate::merkle::tests::init_merkle((0u8..=10).map(|k| ([k], [k]))); + let proof = merkle + .range_proof(Some(&[2u8]), Some(&[8u8]), std::num::NonZeroUsize::new(5)) + .unwrap(); + let mut serialized = Vec::new(); + proof.write_to_vec(&mut serialized); + (proof, serialized) +} + +#[test_case( + |data| data[0..8].copy_from_slice(b"badmagic"), + |err| matches!(err, InvalidHeader::InvalidMagic { found } if found == b"badmagic"); + "invalid magic" +)] +#[test_case( + |data| data[8] = 99, + |err| matches!(err, InvalidHeader::UnsupportedVersion { found: 99 }); + "unsupported version" +)] +#[test_case( + |data| data[9] = 99, + |err| matches!(err, InvalidHeader::UnsupportedHashMode { found: 99 }); + "unsupported hash mode" +)] +#[test_case( + |data| data[10] = 99, + |err| matches!(err, InvalidHeader::UnsupportedBranchFactor { found: 99 }); + "unsupported branch factor" +)] +#[test_case( + |data| data[11] = 99, + |err| matches!(err, InvalidHeader::InvalidProofType { found: 99, expected: Some(ProofType::Range) }); + "invalid proof type" +)] +#[test_case( + |data| data[11] = ProofType::Change as u8, + |err| matches!(err, InvalidHeader::InvalidProofType { found: 2, expected: Some(ProofType::Range) }); + "wrong proof type" +)] +fn test_invalid_header( + mutator: impl FnOnce(&mut Vec), + expected: impl FnOnce(&InvalidHeader) -> bool, +) { + let (_, mut data) = create_valid_range_proof(); + + mutator(&mut data); + + match FrozenRangeProof::from_slice(&data) { + Err(ReadError::InvalidHeader(err)) => assert!(expected(&err), "unexpected error: {err}"), + other => panic!("Expected ReadError::InvalidHeader, got: {other:?}"), + } +} + +#[test_case( + |_, data| data.truncate(20), + "header", + 32, // expected len + 20; // found len + "incomplete header" +)] +#[test_case( + |_, data| data.truncate(31), + "header", + 32, // expected len + 31; // found len + "header one byte short" +)] +#[test_case( + |_, data| data.truncate(32), + "array length", + 1, // expected len + 0; // found len + "no varint after header" +)] +#[cfg_attr(not(feature = "branch_factor_256"), test_case( + |proof, data| { + #[expect(clippy::arithmetic_side_effects)] + data.truncate( + 32 + + proof.start_proof().len().required_space() + + proof.start_proof()[0].key.len().required_space() + // truncate after the key length varint but before the key bytes + ); + }, + "byte slice", + 1, // expected len + 0; // found len + "truncated node key" +))] +fn test_incomplete_item( + mutator: impl FnOnce(&FrozenRangeProof, &mut Vec), + item: &'static str, + expected_len: usize, + found_len: usize, +) { + let (proof, mut data) = create_valid_range_proof(); + + eprintln!("data len: {}", data.len()); + eprintln!("proof: {proof:#?}"); + eprintln!("data: {}", hex::encode(&data)); + + mutator(&proof, &mut data); + + match FrozenRangeProof::from_slice(&data) { + Err(ReadError::IncompleteItem { + item: found_item, + offset: _, + expected, + found, + }) => { + assert_eq!( + found_item, item, + "unexpected `item` value, got: {found_item}, wanted: {item}; {data:?}" + ); + assert_eq!( + expected, expected_len, + "unexpected `expected` value, got: {expected}, wanted: {expected_len}; {data:?}" + ); + assert_eq!( + found, found_len, + "unexpected `found` value, got: {found}, wanted: {found_len}; {data:?}" + ); + } + other => panic!("Expected ReadError::IncompleteItem, got: {other:?}"), + } +} + +#[test_case( + |proof, data| data[32 + + proof.start_proof().len().required_space() + + proof.start_proof()[0].key.len().required_space() + + proof.start_proof()[0].key.len() + + proof.start_proof()[0].partial_len.required_space() + // Corrupt the option discriminant for the value digest (should be 0 or 1) + ] = 3, // invalid option discriminant + "option discriminant", + "0 or 1", + "3"; + "invalid option discriminant" +)] +#[test_case( + |_, data| data[32..42].copy_from_slice(&[0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89]), + "array length", + "byte with no MSB within 9 bytes", + "[128, 129, 130, 131, 132, 133, 134, 135, 136, 137]"; + "invalid varint" +)] +#[test_case( + |_, data| data.extend_from_slice(&[0xFF; 100]), // extend data with invalid trailing bytes + "trailing bytes", + "no data after the proof", + "100 bytes"; + "extra trailing bytes" +)] +fn test_invalid_item( + mutator: impl FnOnce(&FrozenRangeProof, &mut Vec), + item: &'static str, + expected: &'static str, + found: &'static str, +) { + let (proof, mut data) = create_valid_range_proof(); + + mutator(&proof, &mut data); + + match FrozenRangeProof::from_slice(&data) { + Err(ReadError::InvalidItem { + item: found_item, + offset: _, + expected: found_expected, + found: found_found, + }) => { + assert_eq!( + found_item, item, + "unexpected `item` value, got: {found_item}, wanted: {item}" + ); + assert_eq!( + found_expected, expected, + "unexpected `expected` value, got: {found_expected}, wanted: {expected}" + ); + assert_eq!( + found_found, found, + "unexpected `found` value, got: {found_found}, wanted: {found}" + ); + } + other => panic!("Expected ReadError::InvalidItem, got: {other:?}"), + } +} + +#[test] +fn test_empty_proof() { + #[rustfmt::skip] + let bytes = [ + b'f', b'w', b'd', b'p', b'r', b'o', b'o', b'f', // magic + 0, // version + magic::HASH_MODE, + magic::BRANCH_FACTOR, + ProofType::Range as u8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // reserved + 0, // start proof length = 0 + 0, // end proof length = 0 + 0, // key-value pairs length = 0 + ]; + + match FrozenRangeProof::from_slice(&bytes) { + Ok(proof) => { + assert!(proof.start_proof().is_empty()); + assert!(proof.end_proof().is_empty()); + assert!(proof.key_values().is_empty()); + } + Err(err) => panic!("Expected valid empty proof, got error: {err}"), + } +} diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index c253d52acca7..e309ddb25e9a 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -21,7 +21,7 @@ use crate::proof::{Proof, ProofCollection}; /// - State synchronization between nodes /// - Light client verification /// - Efficient auditing of specific key ranges -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct RangeProof { start_proof: Proof, end_proof: Proof, diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 6724819769ca..659836279461 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -19,25 +19,25 @@ rust-version.workspace = true [dependencies] # Workspace dependencies aquamarine.workspace = true +bytemuck.workspace = true +bytemuck_derive.workspace = true coarsetime.workspace = true fastrace.workspace = true hex.workspace = true +integer-encoding.workspace = true metrics.workspace = true nonzero_ext.workspace = true +rand = { workspace = true, optional = true } sha2.workspace = true smallvec = { workspace = true, features = ["write", "union"] } -rand = { workspace = true, optional = true } thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" bitfield = "0.19.1" bitflags = "2.9.1" -bytemuck = "1.23.1" -bytemuck_derive = "1.10.0" derive-where = "1.5.0" enum-as-inner = "0.6.1" indicatif = "0.18.0" -integer-encoding = "4.0.2" lru = "0.16.0" semver = "1.0.26" triomphe = "0.1.14" diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 0d6ae391028f..35d130ec9fe1 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -1,14 +1,8 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::arithmetic_side_effects, - reason = "Found 1 occurrences after enabling the lint." -)] - use crate::{BranchNode, Children, HashType, LeafNode, Node, Path}; use smallvec::SmallVec; -use std::ops::Deref; /// Returns the hash of `node`, which is at the given `path_prefix`. #[must_use] @@ -44,6 +38,7 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { #[must_use] pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { // Key, 3 options, value digest + #[expect(clippy::arithmetic_side_effects)] let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::empty().len(); let mut buf = Vec::with_capacity(est_len); match node { @@ -92,22 +87,9 @@ pub enum ValueDigest { /// The node's value. Value(T), #[cfg(not(feature = "ethhash"))] - /// TODO this variant will be used when we deserialize a proof node - /// from a remote Firewood instance. The serialized proof node they - /// send us may the hash of the value, not the value itself. - Hash(T), -} - -impl Deref for ValueDigest { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - ValueDigest::Value(value) => value, - #[cfg(not(feature = "ethhash"))] - ValueDigest::Hash(hash) => hash, - } - } + /// For MerkleDB hashing, the digest is the hash of the value if it is 32 + /// bytes or longer. + Hash(HashType), } impl> ValueDigest { @@ -123,8 +105,48 @@ impl> ValueDigest { use sha2::{Digest, Sha256}; // This proof proves that `key` maps to a value // whose hash is `got_hash`. - got_hash.as_ref() == Sha256::digest(expected.as_ref()).as_slice() + *got_hash == HashType::from(Sha256::digest(expected.as_ref())) + } + } + } + + /// Returns a `ValueDigest` that borrows from this one. + pub fn as_ref(&self) -> ValueDigest<&[u8]> { + match self { + Self::Value(v) => ValueDigest::Value(v.as_ref()), + #[cfg(not(feature = "ethhash"))] + Self::Hash(h) => ValueDigest::Hash(h.clone()), + } + } + + /// Convert the value to a hash if it is not already a hash. + /// + /// If the value is less than 32 bytes, it will be passed through as is + /// instead of hashing. + /// + /// If etherum hashing is enabled, this will always return the value as is. + pub fn make_hash(&self) -> ValueDigest<&[u8]> { + match self.as_ref() { + #[cfg(not(feature = "ethhash"))] + ValueDigest::Value(v) if v.len() >= 32 => { + use sha2::{Digest, Sha256}; + ValueDigest::Hash(HashType::from(Sha256::digest(v))) } + + ValueDigest::Value(v) => ValueDigest::Value(v), + + #[cfg(not(feature = "ethhash"))] + ValueDigest::Hash(v) => ValueDigest::Hash(v), + } + } +} + +impl> AsRef<[u8]> for ValueDigest { + fn as_ref(&self) -> &[u8] { + match self { + Self::Value(v) => v.as_ref(), + #[cfg(not(feature = "ethhash"))] + Self::Hash(h) => h.as_ref(), } } } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index ba89260fbcd4..db60911dd8a9 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -170,8 +170,8 @@ impl Preimage for T { // This is because account nodes have special handling where the storage root hash // gets replaced in the account data structure during serialization. let digest = (!is_account).then(|| self.value_digest()).flatten(); - if let Some(digest) = digest { - rlp.append(&*digest); + if let Some(ValueDigest::Value(digest)) = digest { + rlp.append(&digest); } else { rlp.append_empty_data(); } diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index 3960dcf4c336..fd19d6a38f83 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -51,7 +51,6 @@ impl Preimage for T { // Add key length (in bits) to hash pre-image let mut key = self.full_path(); - // let mut key = key.as_ref().iter(); let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; add_varint_to_buf(buf, key_bit_len); @@ -77,18 +76,7 @@ fn add_value_digest_to_buf>( let value_exists: u8 = 1; buf.update([value_exists]); - match value_digest { - ValueDigest::Value(value) if value.as_ref().len() >= 32 => { - let hash = Sha256::digest(value); - add_len_and_value_to_buf(buf, hash); - } - ValueDigest::Value(value) => { - add_len_and_value_to_buf(buf, value); - } - ValueDigest::Hash(hash) => { - add_len_and_value_to_buf(buf, hash); - } - } + add_len_and_value_to_buf(buf, value_digest.make_hash()); } #[inline] From 6e2b45bb290057bb201d69eafa3d0ef23d04277c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 16 Sep 2025 15:25:37 -0400 Subject: [PATCH 0947/1053] fix(range-proofs): serialize range proof key consistently (#1278) Every other range proof is created by collecting the nibbles The field is even documented to only be nibbles. However, when the exclusion proof is before the left-most key, we are serializing the proof node incorrectly. --- firewood/src/merkle.rs | 7 +++++-- storage/src/node/path.rs | 6 ------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 664a3e2bf92e..b198cdbc0350 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -166,8 +166,11 @@ impl Merkle { }; proof.push(ProofNode { - key: root.partial_path().bytes(), - partial_len: root.partial_path().0.len(), + // key is expected to be in nibbles + key: root.partial_path().iter().copied().collect(), + // partial len is the number of nibbles in the path leading to this node, + // which is always zero for the root node. + partial_len: 0, value_digest: root .value() .map(|value| ValueDigest::Value(value.to_vec().into_boxed_slice())), diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 478e41e91f8f..7f864e84ea6f 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -138,12 +138,6 @@ impl Path { nibbles_iter: self.iter(), } } - - /// Create a boxed set of bytes from the Path - #[must_use] - pub fn bytes(&self) -> Box<[u8]> { - self.bytes_iter().collect() - } } /// Returns the nibbles in `nibbles_iter` as compressed bytes. From 783f70cde70273847b4bd399b07747a9560cf1e7 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 16 Sep 2025 15:31:27 -0400 Subject: [PATCH 0948/1053] fix(range-proofs): fix verify of exclusion proofs (#1279) We were incorrectly checking the length of the last node's full path against the length of the key being proven. This meant that if the last node's full path was the same _length_ as the key being proven, but the key was different, we would incorrectly verify the proof by expecting a value when there should be none. --- firewood/src/merkle/tests/range.rs | 1 - firewood/src/proof.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/firewood/src/merkle/tests/range.rs b/firewood/src/merkle/tests/range.rs index e83e6b8f61ad..393bd3bee006 100644 --- a/firewood/src/merkle/tests/range.rs +++ b/firewood/src/merkle/tests/range.rs @@ -9,7 +9,6 @@ type KeyValuePairs = Vec<(Box<[u8]>, Box<[u8]>)>; #[test] // Tests that missing keys can also be proven. The test explicitly uses a single // entry trie and checks for missing keys both before and after the single entry. -#[ignore = "https://github.com/ava-labs/firewood/issues/738"] fn test_missing_key_proof() { let items = [("k", "v")]; let merkle = init_merkle(items); diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index e53992ddd6de..fca3a6070858 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -230,7 +230,7 @@ impl Proof { } } - if last_node.full_path().count() == key.len() { + if last_node.full_path().eq(key.iter().copied()) { return Ok(last_node.value_digest()); } From 47a37bb8db37591a48c149d3556d733e11d7b459 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Tue, 16 Sep 2025 19:45:54 -0400 Subject: [PATCH 0949/1053] docs: README implies commit == persist (#1283) Cleaned up the generated comments in README.md Also moved the missing_debug warning to the crate level, as it was only in the firewood crate. Warnings for missing_docs and rust_2018_idioms are not currently enabled anywhere now. --- Cargo.toml | 1 + ffi/src/logging.rs | 1 + firewood/src/lib.rs | 12 ++++-------- fwdctl/src/check.rs | 2 +- fwdctl/src/create.rs | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d9e577062c78..bd06a0981ba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ inherits = "release" [workspace.lints.rust] unsafe_code = "deny" +missing_debug_implementations = "warn" [workspace.lints.clippy] unwrap_used = "warn" diff --git a/ffi/src/logging.rs b/ffi/src/logging.rs index 4b41da932eb8..83923574258f 100644 --- a/ffi/src/logging.rs +++ b/ffi/src/logging.rs @@ -5,6 +5,7 @@ use crate::BorrowedBytes; /// Arguments for initializing logging for the Firewood FFI. #[repr(C)] +#[derive(Debug)] pub struct LogArgs<'a> { /// The file path where logs for this process are stored. /// diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 0fed6bd3a1b5..458e5f52dcae 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -15,7 +15,7 @@ //! a very fast storage layer for the EVM but could be used on any blockchain that //! requires authenticated state. //! -//! Firewood only attempts to store recent revisions on-disk and will actively clean up +//! Firewood only attempts to support queries from recent revisions and will actively clean up //! unused older revisions when state diffs are committed. The number of revisions is //! configured when the database is opened. //! @@ -67,7 +67,7 @@ //! the state of the nodestore, and a storage type. //! //! There are three states for a nodestore: -//! - [`firewood_storage::Committed`] for revisions that are on disk +//! - [`firewood_storage::Committed`] for revisions that are committed //! - [`firewood_storage::ImmutableProposal`] for revisions that are proposals against committed versions //! - [`firewood_storage::MutableProposal`] for revisions where nodes are still being added. //! @@ -96,16 +96,12 @@ //! - If you delete a node, mark it as deleted in the proposal and remove the child reference to it. //! //! - After making all mutations, convert the [`firewood_storage::MutableProposal`] to an [`firewood_storage::ImmutableProposal`]. This -//! involves walking the in-memory trie and looking for nodes without disk addresses, then assigning -//! them from the freelist of the parent. This gives the node an address, but it is stil in -//! memory. +//! involves walking the in-memory trie and converting them to a [`firewood_storage::SharedNode`]. //! //! - Since the root is guaranteed to be new, the new root will reference all of the new revision. //! -//! A commit involves simply writing the nodes and the freelist to disk. If the proposal is -//! abandoned, nothing has actually been written to disk. +//! A commit involves allocating space for the nmodes and writing them as well as the freelist to disk. //! -#![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] // Instead of using a `compile_error!`, cause clippy to hard fail if the target is not 64-bit. This // is a workaround for the fact that the `clippy::cast_possible_truncation` lint does not delineate // between 64-bit and non-64-bit targets with respect to `usize -> u64` casts and vice versa which diff --git a/fwdctl/src/check.rs b/fwdctl/src/check.rs index 601747023e05..9da7b621781f 100644 --- a/fwdctl/src/check.rs +++ b/fwdctl/src/check.rs @@ -16,7 +16,7 @@ use num_format::{Locale, ToFormattedString}; use crate::DatabasePath; // TODO: (optionally) add a fix option -#[derive(Args)] +#[derive(Args, Debug)] pub struct Options { #[command(flatten)] pub database: DatabasePath, diff --git a/fwdctl/src/create.rs b/fwdctl/src/create.rs index 0d8c8da260ab..71490b75b571 100644 --- a/fwdctl/src/create.rs +++ b/fwdctl/src/create.rs @@ -7,7 +7,7 @@ use firewood::v2::api; use crate::DatabasePath; -#[derive(Args)] +#[derive(Args, Debug)] pub struct Options { #[command(flatten)] pub database: DatabasePath, From 2caed710f1e47fdec5a8728fca64e2bd2589118f Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 18 Sep 2025 08:45:56 -0400 Subject: [PATCH 0950/1053] test(bootstrap): Bootstrap testing scripts (#1287) Still early, but we should be improving and maintaining this so branches can be tested for loading bootstrap performance using firewood. By default, we use the cheapest and smallest instance everything works on, but some other valid instance sizes are listed along with estimated cost per hour. TODO: enable local metrics. This might already be working, but hasn't been fully tested. It's tricky to get the dashboards loaded automatically. @bernard-avalabs @AminR443 please consider adding your ssh-keys and username preferences so we can log into each other's instances. --- benchmark/bootstrap/README.md | 4 + benchmark/bootstrap/aws-launch.sh | 393 ++++++++++++++++++++++++++++++ benchmark/cloud-config.txt | 88 ------- 3 files changed, 397 insertions(+), 88 deletions(-) create mode 100644 benchmark/bootstrap/README.md create mode 100755 benchmark/bootstrap/aws-launch.sh delete mode 100644 benchmark/cloud-config.txt diff --git a/benchmark/bootstrap/README.md b/benchmark/bootstrap/README.md new file mode 100644 index 000000000000..1f597d413455 --- /dev/null +++ b/benchmark/bootstrap/README.md @@ -0,0 +1,4 @@ +# Bootstrap testing script + +This script creates instances and sets up users for bootstrap testing. You'll need ec2 installed on your machine +and you'll need to `aws sso login` before running this script. diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh new file mode 100755 index 000000000000..fefca0ba3f80 --- /dev/null +++ b/benchmark/bootstrap/aws-launch.sh @@ -0,0 +1,393 @@ +#!/usr/bin/env bash +set +e + +# Default values +INSTANCE_TYPE=i4g.large +FIREWOOD_BRANCH="" +AVALANCHEGO_BRANCH="" +CORETH_BRANCH="" +LIBEVM_BRANCH="" +NBLOCKS="1m" +REGION="us-west-2" +DRY_RUN=false + +# Valid instance types and their architectures +declare -A VALID_INSTANCES=( + ["i4g.large"]="arm64" + ["i4i.large"]="amd64" + ["m6id.xlarge"]="arm64" + ["c6gd.2xlarge"]="arm64" + ["r6gd.2xlarge"]="arm64" + ["x2gd.xlarge"]="arm64" + ["x2gd.2xlarge"]="arm64" + ["m5ad.2xlarge"]="arm64" + ["z1d.2xlarge"]="amd64" +) + +# Valid nblocks values +VALID_NBLOCKS=("1m" "10m" "20m" "30m" "40m" "50m") + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --instance-type TYPE EC2 instance type (default: i4g.large)" + echo " --firewood-branch BRANCH Firewood git branch to checkout" + echo " --avalanchego-branch BRANCH AvalancheGo git branch to checkout" + echo " --coreth-branch BRANCH Coreth git branch to checkout" + echo " --libevm-branch BRANCH LibEVM git branch to checkout" + echo " --nblocks BLOCKS Number of blocks to download (default: 1m)" + echo " --region REGION AWS region (default: us-west-2)" + echo " --dry-run Show the aws command that would be run without executing it" + echo " --help Show this help message" + echo "" + echo "Valid instance types:" + echo " # name Type disk vcpu memory $/hr notes" + echo " i4g.large arm64 468 2 16 GiB \$0.1544 Graviton2-powered" + echo " i4i.large amd64 468 2 16 GiB \$0.1720 Intel Xeon Scalable" + echo " m6id.xlarge arm64 237 4 16 GiB \$0.2373 Intel Xeon Scalable" + echo " c6gd.2xlarge arm64 474 8 16 GiB \$0.3072 Graviton2 compute-optimized" + echo " r6gd.2xlarge arm64 474 8 64 GiB \$0.4608 Graviton2 memory-optimized" + echo " x2gd.xlarge arm64 237 4 64 GiB \$0.3340 Graviton2 memory-optimized" + echo " x2gd.2xlarge arm64 475 8 128 GiB \$0.6680 Graviton2 memory-optimized" + echo " m5ad.2xlarge arm64 300 8 32 GiB \$0.4120 AMD EPYC processors" + echo " z1d.2xlarge amd64 300 8 64 GiB \$0.7440 High-frequency Intel Xeon CPUs" + echo "" + echo "Valid nblocks values: ${VALID_NBLOCKS[*]}" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --instance-type) + INSTANCE_TYPE="$2" + if [[ ! ${VALID_INSTANCES[$INSTANCE_TYPE]+_} ]]; then + echo "Error: Invalid instance type '$INSTANCE_TYPE'" + echo "Valid types: ${!VALID_INSTANCES[*]}" + exit 1 + fi + shift 2 + ;; + --firewood-branch) + FIREWOOD_BRANCH="$2" + shift 2 + ;; + --avalanchego-branch) + AVALANCHEGO_BRANCH="$2" + shift 2 + ;; + --coreth-branch) + CORETH_BRANCH="$2" + shift 2 + ;; + --libevm-branch) + LIBEVM_BRANCH="$2" + shift 2 + ;; + --nblocks) + NBLOCKS="$2" + # Validate nblocks value + if [[ ! " ${VALID_NBLOCKS[*]} " =~ [[:space:]]${NBLOCKS}[[:space:]] ]]; then + echo "Error: Invalid nblocks value '$NBLOCKS'" + echo "Valid values: ${VALID_NBLOCKS[*]}" + exit 1 + fi + shift 2 + ;; + --region) + REGION="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --help) + show_usage + exit 0 + ;; + *) + echo "Error: Unknown option $1" + show_usage + exit 1 + ;; + esac +done + +# Set architecture type based on instance type +TYPE=${VALID_INSTANCES[$INSTANCE_TYPE]} + +echo "Configuration:" +echo " Instance Type: $INSTANCE_TYPE ($TYPE)" +echo " Firewood Branch: ${FIREWOOD_BRANCH:-default}" +echo " AvalancheGo Branch: ${AVALANCHEGO_BRANCH:-default}" +echo " Coreth Branch: ${CORETH_BRANCH:-default}" +echo " LibEVM Branch: ${LIBEVM_BRANCH:-default}" +echo " Number of Blocks: $NBLOCKS" +echo " Region: $REGION" +if [ "$DRY_RUN" = true ]; then + echo " Mode: DRY RUN (will not launch instance)" +fi +echo "" + +if [ "$DRY_RUN" = true ]; then + # For dry run, use placeholder values + AMI_ID="ami-placeholder" + USERDATA="base64-encoded-userdata-placeholder" +else + # find the latest ubuntu-noble base image ID (only works for intel processors) + AMI_ID=$(aws ec2 describe-images \ + --region "$REGION" \ + --owners 099720109477 \ + --filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-$TYPE-server-*" \ + "Name=state,Values=available" \ + --query "Images | sort_by(@, &CreationDate)[-1].ImageId" \ + --output text) + export AMI_ID +fi + +if [ "$DRY_RUN" = false ]; then + # Prepare branch arguments for cloud-init +FIREWOOD_BRANCH_ARG="" +AVALANCHEGO_BRANCH_ARG="" +CORETH_BRANCH_ARG="" +LIBEVM_BRANCH_ARG="" + +if [ -n "$FIREWOOD_BRANCH" ]; then + FIREWOOD_BRANCH_ARG="--branch $FIREWOOD_BRANCH" +fi +if [ -n "$AVALANCHEGO_BRANCH" ]; then + AVALANCHEGO_BRANCH_ARG="--branch $AVALANCHEGO_BRANCH" +fi +if [ -n "$CORETH_BRANCH" ]; then + CORETH_BRANCH_ARG="--branch $CORETH_BRANCH" +fi +if [ -n "$LIBEVM_BRANCH" ]; then + LIBEVM_BRANCH_ARG="--branch $LIBEVM_BRANCH" +fi + +# set up this script to run at startup, installing a few packages, creating user accounts, +# and downloading the 50m blockstore for the C-chain +USERDATA_TEMPLATE=$(cat <<'END_HEREDOC' +#cloud-config +package_update: true +package_upgrade: true +packages: + - git + - build-essential + - curl + - protobuf-compiler + - make + - apt-transport-https + - net-tools + - unzip +users: + - default + - name: rkuris + lock_passwd: true + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2RVmfpoKYi0tJd2DhQEp8tB3m2PSuaYxIfnLwqt03u cardno:23_537_110 ron + - name: austin + lock_passwd: true + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICoGgX8nCin3FPc1V3YYN1M9g039wMbzZSAXZJCqzBt3 cardno:31_786_961 austin + - name: aaron + lock_passwd: true + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMj2j6ySwsFx7Y6FW2UXlkjCZfFDQKHWh0GTBjkK9ruV cardno:19_236_959 aaron + +swap: + filename: /swapfile + size: 16G + maxsize: 16G + +# anyone can use the -D option +write_files: +- content: | + Defaults runcwd=* + path: "/etc/sudoers.d/91-cloud-init-enable-D-option" + permissions: '0440' +- content: | + export PATH="$PATH:/usr/local/go/bin" + permissions: "0644" + path: "/etc/profile.d/go_path.sh" +- content: | + export RUSTUP_HOME=/usr/local/rust + export PATH="$PATH:/usr/local/rust/bin" + permissions: "0644" + path: "/etc/profile.d/rust_path.sh" + +runcmd: + # install rust + - echo 'PATH=/usr/local/go/bin:$HOME/.cargo/bin:$PATH' >> ~ubuntu/.profile + - > + curl https://sh.rustup.rs -sSf + | RUSTUP_HOME=/usr/local/rust CARGO_HOME=/usr/local/rust + sh -s -- -y --no-modify-path + - sudo -u ubuntu --login rustup default stable + # install firewood + - git clone --depth 1 __FIREWOOD_BRANCH_ARG__ https://github.com/ava-labs/firewood.git /tmp/firewood + - bash /tmp/firewood/benchmark/setup-scripts/build-environment.sh + - bash -c 'mkdir ~ubuntu/firewood; mv /tmp/firewood/{.[!.],}* ~ubuntu/firewood/' + # fix up the directories so that anyone is group 'users' has r/w access + - chown -R ubuntu:users /mnt/nvme/ubuntu + - chmod -R g=u /mnt/nvme/ubuntu + - find /mnt/nvme/ubuntu -type d -print0 | xargs -0 chmod g+s + # helpful symbolic links from home directories + - sudo -u ubuntu ln -s /mnt/nvme/ubuntu/data /home/ubuntu/data + - sudo -u ubuntu ln -s /mnt/nvme/ubuntu/avalanchego /home/ubuntu/avalanchego + # install go and grafana + - bash /mnt/nvme/ubuntu/firewood/benchmark/setup-scripts/install-golang.sh + - bash /mnt/nvme/ubuntu/firewood/benchmark/setup-scripts/install-grafana.sh + # install task, avalanchego, coreth + - snap install task --classic + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu + git clone --depth 100 __AVALANCHEGO_BRANCH_ARG__ https://github.com/ava-labs/avalanchego.git + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu + git clone --depth 100 __CORETH_BRANCH_ARG__ https://github.com/ava-labs/coreth.git + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu + git clone --depth 100 __LIBEVM_BRANCH_ARG__ https://github.com/ava-labs/libevm.git + # force avalanchego to use the checked-out versions of coreth, libevm, and firewood + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego + /usr/local/go/bin/go mod edit -replace + github.com/ava-labs/firewood-go-ethhash/ffi=../firewood/ffi + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego + /usr/local/go/bin/go mod edit -replace + github.com/ava-labs/coreth=../coreth + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego + /usr/local/go/bin/go mod edit -replace + github.com/ava-labs/libevm=../libevm + # build firewood in maxperf mode + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/firewood/ffi --login time cargo build + --profile maxperf + --features ethhash,logger + > /mnt/nvme/ubuntu/firewood/build.log 2>&1 + # build avalanchego + - sudo -u ubuntu --login -D /mnt/nvme/ubuntu/avalanchego go mod tidy + - > + sudo -u ubuntu --login -D /mnt/nvme/ubuntu/avalanchego time scripts/build.sh + > /mnt/nvme/ubuntu/avalanchego/build.log 2>&1 & + # install s5cmd + - curl -L -o /tmp/s5cmd.deb $(curl -s https://api.github.com/repos/peak/s5cmd/releases/latest | grep "browser_download_url" | grep "linux_$(dpkg --print-architecture).deb" | cut -d '"' -f 4) && dpkg -i /tmp/s5cmd.deb + # download and extract mainnet blocks + - echo 'downloading mainnet blocks' + - sudo -u ubuntu mkdir -p /mnt/nvme/ubuntu/exec-data/blocks + - s5cmd cp s3://avalanchego-bootstrap-testing/cchain-mainnet-blocks-__NBLOCKS__-ldb/\* /mnt/nvme/ubuntu/exec-data/blocks/ >/dev/null + - chown -R ubuntu /mnt/nvme/ubuntu/exec-data + - chmod -R g=u /mnt/nvme/ubuntu/exec-data + # execute bootstrapping + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego --login + time task reexecute-cchain-range CURRENT_STATE_DIR=/mnt/nvme/ubuntu/exec-data/current-state BLOCK_DIR=/mnt/nvme/ubuntu/exec-data/blocks START_BLOCK=1 END_BLOCK=__END_BLOCK__ CONFIG=firewood METRICS_ENABLED=false + > bootstrap.log 2>&1 +END_HEREDOC +) + +# Convert nblocks to actual end block number +case $NBLOCKS in + "1m") END_BLOCK="1000000" ;; + "10m") END_BLOCK="10000000" ;; + "20m") END_BLOCK="20000000" ;; + "30m") END_BLOCK="30000000" ;; + "40m") END_BLOCK="40000000" ;; + "50m") END_BLOCK="50000000" ;; + *) END_BLOCK="1000000" ;; # Default fallback +esac + +# Substitute branch arguments and block values in the userdata template +USERDATA=$(echo "$USERDATA_TEMPLATE" | \ + sed "s|__FIREWOOD_BRANCH_ARG__|$FIREWOOD_BRANCH_ARG|g" | \ + sed "s|__AVALANCHEGO_BRANCH_ARG__|$AVALANCHEGO_BRANCH_ARG|g" | \ + sed "s|__CORETH_BRANCH_ARG__|$CORETH_BRANCH_ARG|g" | \ + sed "s|__LIBEVM_BRANCH_ARG__|$LIBEVM_BRANCH_ARG|g" | \ + sed "s|__NBLOCKS__|$NBLOCKS|g" | \ + sed "s|__END_BLOCK__|$END_BLOCK|g" | \ + base64) +export USERDATA + +fi # End of DRY_RUN=false conditional + + +SUFFIX=$(hexdump -vn4 -e'4/4 "%08X" 1 "\n"' /dev/urandom) + +# Build instance name with branch info +INSTANCE_NAME="$USER-fw-$SUFFIX" +if [ -n "$FIREWOOD_BRANCH" ]; then + INSTANCE_NAME="$INSTANCE_NAME-fw-$FIREWOOD_BRANCH" +fi +if [ -n "$AVALANCHEGO_BRANCH" ]; then + INSTANCE_NAME="$INSTANCE_NAME-ag-$AVALANCHEGO_BRANCH" +fi +if [ -n "$CORETH_BRANCH" ]; then + INSTANCE_NAME="$INSTANCE_NAME-ce-$CORETH_BRANCH" +fi +if [ -n "$LIBEVM_BRANCH" ]; then + INSTANCE_NAME="$INSTANCE_NAME-le-$LIBEVM_BRANCH" +fi + +# --instance-market-options '{"MarketType":"spot", "SpotOptions": {"MaxPrice":"0.6048"}}' \ + +if [ "$DRY_RUN" = true ]; then + echo "DRY RUN - Would execute the following command:" + echo "" + echo "aws ec2 run-instances \\" + echo " --region \"$REGION\" \\" + echo " --image-id \"$AMI_ID\" \\" + echo " --count 1 \\" + echo " --instance-type $INSTANCE_TYPE \\" + echo " --key-name rkuris \\" + echo " --security-groups rkuris-starlink-only \\" + echo " --iam-instance-profile \"Name=s3-readonly\" \\" + echo " --user-data \"$USERDATA\" \\" + echo " --tag-specifications \"ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]\" \\" + echo " --block-device-mappings \"DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}\" \\" + echo " --query 'Instances[0].InstanceId' \\" + echo " --output text" + echo "" + echo "Instance would be named: $INSTANCE_NAME" +else + set -e + INSTANCE_ID=$(aws ec2 run-instances \ + --region "$REGION" \ + --image-id "$AMI_ID" \ + --count 1 \ + --instance-type "$INSTANCE_TYPE" \ + --key-name rkuris \ + --security-groups rkuris-starlink-only \ + --iam-instance-profile "Name=s3-readonly" \ + --user-data "$USERDATA" \ + --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]" \ + --block-device-mappings "DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}" \ + --query 'Instances[0].InstanceId' \ + --output text \ + ) + echo "instance id $INSTANCE_ID started" +fi + +if [ "$DRY_RUN" = false ]; then + # IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[].Instances[].PublicIpAddress' --output text) + set +e + + #IP=$(echo "$JSON" | jq -r '.PublicIpAddress') + #echo $IP + #while nc -zv $IP 22; do + #sleep 1 + #done +fi diff --git a/benchmark/cloud-config.txt b/benchmark/cloud-config.txt deleted file mode 100644 index 658f8e3b5f72..000000000000 --- a/benchmark/cloud-config.txt +++ /dev/null @@ -1,88 +0,0 @@ -#cloud-config -write_files: -- path: /etc/systemd/system/grafana-server.service.d/override.conf - owner: root:root - permissions: '0644' - content: | - [Service] - CapabilityBoundingSet=CAP_NET_BIND_SERVICE - AmbientCapabilities=CAP_NET_BIND_SERVICE - PrivateUsers=false -- path: /run/firewood/build-firewood.sh - permissions: '0755' - content: | - #!/bin/bash - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - . "$HOME/.cargo/env" - git clone https://github.com/ava-labs/firewood.git - cd firewood - cargo build --release -- path: /etc/prometheus/prometheus.yml.addon - content: |2 - - job_name: firewood - static_configs: - - targets: ['localhost:3000'] -apt: - sources: - grafana: - source: deb https://apt.grafana.com stable main - key: | - -----BEGIN PGP PUBLIC KEY BLOCK----- - - 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 - =J9CI - -----END PGP PUBLIC KEY BLOCK----- - -package_update: true -package_upgrade: true -packages: -- git -- protobuf-compiler -- build-essential -- apt-transport-https -- grafana -- prometheus -- net-tools -runcmd: - - [ perl, -pi, -e, "s/^;?http_port = .*/http_port = 80/", /etc/grafana/grafana.ini ] - - [ dd, if=/etc/prometheus/prometheus.yml.addon, of=/etc/prometheus/prometheus.yml, conv=notrunc, oflag=append ] - - [ systemctl, daemon-reload ] - - [ systemctl, enable, grafana-server ] - - [ systemctl, start, grafana-server ] - - [ sudo, -l, -u, ubuntu, /run/firewood/build-firewood.sh ] From e135deeebfd46da0cf976ccfcb4fc3a303c5b71b Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 18 Sep 2025 09:42:50 -0400 Subject: [PATCH 0951/1053] chore: Various script improvements (#1288) - Added r6id.2xlarge instance type - Sorted instance types by cost - Corrected comment -- doesn't always use 50m blocks now --- benchmark/bootstrap/aws-launch.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index fefca0ba3f80..29774e5a2d28 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -17,10 +17,11 @@ declare -A VALID_INSTANCES=( ["i4i.large"]="amd64" ["m6id.xlarge"]="arm64" ["c6gd.2xlarge"]="arm64" - ["r6gd.2xlarge"]="arm64" ["x2gd.xlarge"]="arm64" - ["x2gd.2xlarge"]="arm64" ["m5ad.2xlarge"]="arm64" + ["r6gd.2xlarge"]="arm64" + ["r6id.2xlarge"]="amd64" + ["x2gd.2xlarge"]="arm64" ["z1d.2xlarge"]="amd64" ) @@ -48,10 +49,11 @@ show_usage() { echo " i4i.large amd64 468 2 16 GiB \$0.1720 Intel Xeon Scalable" echo " m6id.xlarge arm64 237 4 16 GiB \$0.2373 Intel Xeon Scalable" echo " c6gd.2xlarge arm64 474 8 16 GiB \$0.3072 Graviton2 compute-optimized" - echo " r6gd.2xlarge arm64 474 8 64 GiB \$0.4608 Graviton2 memory-optimized" echo " x2gd.xlarge arm64 237 4 64 GiB \$0.3340 Graviton2 memory-optimized" - echo " x2gd.2xlarge arm64 475 8 128 GiB \$0.6680 Graviton2 memory-optimized" echo " m5ad.2xlarge arm64 300 8 32 GiB \$0.4120 AMD EPYC processors" + echo " r6gd.2xlarge arm64 474 8 64 GiB \$0.4608 Graviton2 memory-optimized" + echo " r6id.2xlarge amd64 474 8 64 GiB \$0.6048 Intel Xeon Scalable" + echo " x2gd.2xlarge arm64 475 8 128 GiB \$0.6680 Graviton2 memory-optimized" echo " z1d.2xlarge amd64 300 8 64 GiB \$0.7440 High-frequency Intel Xeon CPUs" echo "" echo "Valid nblocks values: ${VALID_NBLOCKS[*]}" @@ -168,7 +170,7 @@ if [ -n "$LIBEVM_BRANCH" ]; then fi # set up this script to run at startup, installing a few packages, creating user accounts, -# and downloading the 50m blockstore for the C-chain +# and downloading the blocks for the C-chain USERDATA_TEMPLATE=$(cat <<'END_HEREDOC' #cloud-config package_update: true From dd1a4292043c361ae8b076a41449d2c1a03b5ca3 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Thu, 18 Sep 2025 18:51:18 -0400 Subject: [PATCH 0952/1053] chore(bootstrap): add keys for brandon (#1289) --- benchmark/bootstrap/aws-launch.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 29774e5a2d28..340f36d27205 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -207,6 +207,12 @@ users: sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMj2j6ySwsFx7Y6FW2UXlkjCZfFDQKHWh0GTBjkK9ruV cardno:19_236_959 aaron + - name: brandon + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuwpEMnsBLdfr7V9SFRTm9XWHEFX3yQQP7nmsFHetBo cardno:26_763_547 brandon swap: filename: /swapfile From 15e9a99fb5fc0648aaca523c4cdbf6c8de314294 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Thu, 18 Sep 2025 18:59:26 -0400 Subject: [PATCH 0953/1053] chore(bootstrap): add keys for Bernard and Amin (#1291) Signed-off-by: Ron Kuris Co-authored-by: Brandon LeBlanc Co-authored-by: Ron Kuris --- benchmark/bootstrap/aws-launch.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 340f36d27205..54d12a4143ab 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -213,6 +213,18 @@ users: sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuwpEMnsBLdfr7V9SFRTm9XWHEFX3yQQP7nmsFHetBo cardno:26_763_547 brandon + - name: amin + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE8iR1X8/ELrzjczZvCkrTGCEoN6/dtlP01QFGuUpYxV cardno:33_317_839 amin + - name: bernard + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/1C8JVL0g6qqMw1p0TwJMqJqERxYTX+7PnP+gXP4km cardno:19_155_748 bernard swap: filename: /swapfile From d9a74b1d9a30ea9b42a5e5d5ba426230a6319901 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 19 Sep 2025 10:38:39 -0400 Subject: [PATCH 0954/1053] chore!: decorate enums and structs with `#[non_exhaustive]` (#1292) This allows us to add new variants to the enums and fields to the relevant structs without it being a breaking change in the future. --- firewood/src/db.rs | 2 ++ firewood/src/manager.rs | 1 + firewood/src/proof.rs | 2 ++ firewood/src/v2/batch_op.rs | 7 +++++-- storage/src/lib.rs | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f4fec80d00a6..6aa6fda503ca 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -28,6 +28,7 @@ use thiserror::Error; use typed_builder::TypedBuilder; #[derive(Error, Debug)] +#[non_exhaustive] /// Represents the different types of errors that can occur in the database. pub enum DbError { /// I/O error @@ -93,6 +94,7 @@ where /// Database configuration. #[derive(Clone, TypedBuilder, Debug)] +#[non_exhaustive] pub struct DbConfig { /// Whether to create the DB if it doesn't exist. #[builder(default = true)] diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 86a65c5c7021..5d2dc9bd616f 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -46,6 +46,7 @@ pub struct RevisionManagerConfig { } #[derive(Clone, Debug, TypedBuilder)] +#[non_exhaustive] /// Configuration manager that contains both truncate and revision manager config pub struct ConfigManager { /// Whether to create the DB if it doesn't exist. diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index fca3a6070858..5bffb7ea2f15 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -19,6 +19,7 @@ use thiserror::Error; use crate::merkle::{Key, Value}; #[derive(Debug, Error)] +#[non_exhaustive] /// Reasons why a proof is invalid pub enum ProofError { /// Non-monotonic range decrease @@ -79,6 +80,7 @@ pub enum ProofError { } #[derive(Clone, PartialEq, Eq)] +#[non_exhaustive] /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. diff --git a/firewood/src/v2/batch_op.rs b/firewood/src/v2/batch_op.rs index 6b4189ae67d0..35aca576057f 100644 --- a/firewood/src/v2/batch_op.rs +++ b/firewood/src/v2/batch_op.rs @@ -3,14 +3,17 @@ use crate::v2::api::{KeyType, ValueType}; -/// A key/value pair operation. Only put (upsert) and delete are -/// supported +/// A key/value pair operation. +/// +/// Put (upsert), Delete (single key), or Prefix Delete (range) are supported. #[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum BatchOp { /// Upsert a key/value pair Put { /// the key key: K, + /// the value value: V, }, diff --git a/storage/src/lib.rs b/storage/src/lib.rs index bb61440ee847..cd5d07da96ad 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -68,6 +68,7 @@ pub type SharedNode = triomphe::Arc; /// cache write operations, but for some read-heavy workloads /// you can enable caching of branch reads or all reads. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] pub enum CacheReadStrategy { /// Only cache writes (no reads will be cached) WritesOnly, From 1d7a9eb9349c532e90838c0d2fc5f9d8e658c1d9 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 19 Sep 2025 12:00:22 -0400 Subject: [PATCH 0955/1053] chore: add spot instance support (#1294) For cases where cost matters, use spot instances. Also added support for i4g.xlarge and i4i.xlarge, which are still pretty cheap and have a lot of SSD space --- benchmark/bootstrap/aws-launch.sh | 78 +++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 54d12a4143ab..4701327a6abc 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -10,11 +10,14 @@ LIBEVM_BRANCH="" NBLOCKS="1m" REGION="us-west-2" DRY_RUN=false +SPOT_INSTANCE=false # Valid instance types and their architectures declare -A VALID_INSTANCES=( ["i4g.large"]="arm64" + ["i4g.xlarge"]="arm64" ["i4i.large"]="amd64" + ["i4i.xlarge"]="amd64" ["m6id.xlarge"]="arm64" ["c6gd.2xlarge"]="arm64" ["x2gd.xlarge"]="arm64" @@ -25,6 +28,22 @@ declare -A VALID_INSTANCES=( ["z1d.2xlarge"]="amd64" ) +# Maximum spot prices for each instance type (from the pricing table) +declare -A MAX_SPOT_PRICES=( + ["i4g.large"]="0.1544" + ["i4g.xlarge"]="0.3088" + ["i4i.large"]="0.1720" + ["i4i.xlarge"]="0.3440" + ["m6id.xlarge"]="0.2373" + ["c6gd.2xlarge"]="0.3072" + ["x2gd.xlarge"]="0.3340" + ["m5ad.2xlarge"]="0.4120" + ["r6gd.2xlarge"]="0.4608" + ["r6id.2xlarge"]="0.6048" + ["x2gd.2xlarge"]="0.6680" + ["z1d.2xlarge"]="0.7440" +) + # Valid nblocks values VALID_NBLOCKS=("1m" "10m" "20m" "30m" "40m" "50m") @@ -40,6 +59,7 @@ show_usage() { echo " --libevm-branch BRANCH LibEVM git branch to checkout" echo " --nblocks BLOCKS Number of blocks to download (default: 1m)" echo " --region REGION AWS region (default: us-west-2)" + echo " --spot Use spot instance pricing (default depends on instance type)" echo " --dry-run Show the aws command that would be run without executing it" echo " --help Show this help message" echo "" @@ -49,6 +69,8 @@ show_usage() { echo " i4i.large amd64 468 2 16 GiB \$0.1720 Intel Xeon Scalable" echo " m6id.xlarge arm64 237 4 16 GiB \$0.2373 Intel Xeon Scalable" echo " c6gd.2xlarge arm64 474 8 16 GiB \$0.3072 Graviton2 compute-optimized" + echo " i4g.xlarge arm64 937 4 32 GiB \$0.3088 Graviton2-powered" + echo " i4i.xlarge amd64 937 4 32 GiB \$0.3440 Intel Xeon Scalable" echo " x2gd.xlarge arm64 237 4 64 GiB \$0.3340 Graviton2 memory-optimized" echo " m5ad.2xlarge arm64 300 8 32 GiB \$0.4120 AMD EPYC processors" echo " r6gd.2xlarge arm64 474 8 64 GiB \$0.4608 Graviton2 memory-optimized" @@ -101,6 +123,10 @@ while [[ $# -gt 0 ]]; do REGION="$2" shift 2 ;; + --spot) + SPOT_INSTANCE=true + shift + ;; --dry-run) DRY_RUN=true shift @@ -128,6 +154,11 @@ echo " Coreth Branch: ${CORETH_BRANCH:-default}" echo " LibEVM Branch: ${LIBEVM_BRANCH:-default}" echo " Number of Blocks: $NBLOCKS" echo " Region: $REGION" +if [ "$SPOT_INSTANCE" = true ]; then + echo " Spot Instance: Yes (max price: \$${MAX_SPOT_PRICES[$INSTANCE_TYPE]})" +else + echo " Spot Instance: No" +fi if [ "$DRY_RUN" = true ]; then echo " Mode: DRY RUN (will not launch instance)" fi @@ -208,23 +239,26 @@ users: ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMj2j6ySwsFx7Y6FW2UXlkjCZfFDQKHWh0GTBjkK9ruV cardno:19_236_959 aaron - name: brandon + lock_passwd: true groups: users, adm, sudo shell: /usr/bin/bash sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuwpEMnsBLdfr7V9SFRTm9XWHEFX3yQQP7nmsFHetBo cardno:26_763_547 brandon + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuwpEMnsBLdfr7V9SFRTm9XWHEFX3yQQP7nmsFHetBo cardno:26_763_547 brandon - name: amin + lock_passwd: true groups: users, adm, sudo shell: /usr/bin/bash sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE8iR1X8/ELrzjczZvCkrTGCEoN6/dtlP01QFGuUpYxV cardno:33_317_839 amin + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE8iR1X8/ELrzjczZvCkrTGCEoN6/dtlP01QFGuUpYxV cardno:33_317_839 amin - name: bernard + lock_passwd: true groups: users, adm, sudo shell: /usr/bin/bash sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/1C8JVL0g6qqMw1p0TwJMqJqERxYTX+7PnP+gXP4km cardno:19_155_748 bernard + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/1C8JVL0g6qqMw1p0TwJMqJqERxYTX+7PnP+gXP4km cardno:19_155_748 bernard swap: filename: /swapfile @@ -362,7 +396,12 @@ if [ -n "$LIBEVM_BRANCH" ]; then INSTANCE_NAME="$INSTANCE_NAME-le-$LIBEVM_BRANCH" fi -# --instance-market-options '{"MarketType":"spot", "SpotOptions": {"MaxPrice":"0.6048"}}' \ +# Build spot instance market options if requested +SPOT_OPTIONS="" +if [ "$SPOT_INSTANCE" = true ]; then + MAX_PRICE=${MAX_SPOT_PRICES[$INSTANCE_TYPE]} + SPOT_OPTIONS="--instance-market-options '{\"MarketType\":\"spot\", \"SpotOptions\": {\"MaxPrice\":\"$MAX_PRICE\"}}'" +fi if [ "$DRY_RUN" = true ]; then echo "DRY RUN - Would execute the following command:" @@ -375,6 +414,9 @@ if [ "$DRY_RUN" = true ]; then echo " --key-name rkuris \\" echo " --security-groups rkuris-starlink-only \\" echo " --iam-instance-profile \"Name=s3-readonly\" \\" + if [ "$SPOT_INSTANCE" = true ]; then + echo " $SPOT_OPTIONS \\" + fi echo " --user-data \"$USERDATA\" \\" echo " --tag-specifications \"ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]\" \\" echo " --block-device-mappings \"DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}\" \\" @@ -384,20 +426,28 @@ if [ "$DRY_RUN" = true ]; then echo "Instance would be named: $INSTANCE_NAME" else set -e - INSTANCE_ID=$(aws ec2 run-instances \ - --region "$REGION" \ - --image-id "$AMI_ID" \ + # Build the AWS command with conditional spot options + AWS_CMD="aws ec2 run-instances \ + --region \"$REGION\" \ + --image-id \"$AMI_ID\" \ --count 1 \ - --instance-type "$INSTANCE_TYPE" \ + --instance-type \"$INSTANCE_TYPE\" \ --key-name rkuris \ --security-groups rkuris-starlink-only \ - --iam-instance-profile "Name=s3-readonly" \ - --user-data "$USERDATA" \ - --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]" \ - --block-device-mappings "DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}" \ + --iam-instance-profile \"Name=s3-readonly\"" + + if [ "$SPOT_INSTANCE" = true ]; then + AWS_CMD="$AWS_CMD $SPOT_OPTIONS" + fi + + AWS_CMD="$AWS_CMD \ + --user-data \"$USERDATA\" \ + --tag-specifications \"ResourceType=instance,Tags=[{Key=Name,Value=$INSTANCE_NAME}]\" \ + --block-device-mappings \"DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}\" \ --query 'Instances[0].InstanceId' \ - --output text \ - ) + --output text" + + INSTANCE_ID=$(eval "$AWS_CMD") echo "instance id $INSTANCE_ID started" fi From 61a80a28838e3d41d45bf1e428b211e1982d2328 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:34:33 -0400 Subject: [PATCH 0956/1053] fix(ffi): GetFromRoot typo (#1298) Fixes a typo in the documentation for `GetFromRoot()`. --- ffi/firewood.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index fcbf14442382..606d74098613 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -177,7 +177,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { } // GetFromRoot retrieves the value for the given key from a specific root hash. -// If the root is not found, it returnas an error. +// If the root is not found, it returns an error. // If key is not found, it returns (nil, nil). func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { if db.handle == nil { From 361ea64b55e4ef251c29529c968a44abbbce3bdb Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:48:36 -0400 Subject: [PATCH 0957/1053] chore: upgrade go (#1296) Exciting features in CGO! https://tip.golang.org/doc/go1.24 Also, worth exploring how `runtime.AddCleanup` could resolve some woes around forgetting to drop proposals... --- ffi/go.mod | 4 ++-- ffi/tests/eth/go.mod | 4 ++-- ffi/tests/firewood/go.mod | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ffi/go.mod b/ffi/go.mod index 7aebabae623b..651601d7e2f7 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -1,8 +1,8 @@ module github.com/ava-labs/firewood/ffi -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.7 require ( github.com/prometheus/client_golang v1.22.0 diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index 8b65d0d219a1..976f6f7ab911 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -1,8 +1,8 @@ module github.com/ava-labs/firewood/ffi/tests -go 1.23.9 +go 1.24 -toolchain go1.24.2 +toolchain go1.24.7 require ( github.com/ava-labs/firewood-go-ethhash/ffi v0.0.0 // this is replaced to use the parent folder diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index 9add5391b1ad..577783c798cf 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -1,8 +1,8 @@ module github.com/ava-labs/firewood/ffi/tests -go 1.23.9 +go 1.24 -toolchain go1.24.2 +toolchain go1.24.7 require ( github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder From fed7c93bb9e1feee29a162449a67db825ca54bd8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Wed, 24 Sep 2025 08:38:22 -0700 Subject: [PATCH 0958/1053] fix: incorrect gauge metrics (#1300) The metrics package is very strange in that gauges are stored as f64 but in a u64 container. Clueless as to why, but you must use from_bits to get the actual float value. The metrics are reporting 4629700416936869888 as the max_revision count, which is actually 32: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=80c05d7b7f47eb7f459382e2f9950c10 --- ffi/src/metrics_setup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 43cd29ad7c57..28d54742c418 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -160,8 +160,8 @@ impl TextRecorder { write!(output, "{sanitized_key_name}").expect("write error"); self.write_labels(&mut output, key.labels()); // Load gauge value: Uses gauge.load(Ordering::Relaxed) to get the current gauge value - writeln!(output, " {} {}", gauge.load(Ordering::Relaxed), epoch_ms) - .expect("write error"); + let value = gauge.load(Ordering::Relaxed); + writeln!(output, " {} {}", f64::from_bits(value), epoch_ms).expect("write error"); } // Prometheus does not support multiple TYPE declarations for the same metric, From 93ec2446105c1f0ab7afc02d196f7bead85abd44 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 25 Sep 2025 14:12:42 -0700 Subject: [PATCH 0959/1053] ci: ask for clippy and rustfmt (#1306) Not sure how this ever worked. --- .github/workflows/ci.yaml | 8 ++++++++ .github/workflows/default-branch-cache.yaml | 2 ++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index feef2585b7f5..8c9eb20649b6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,6 +40,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: clippy - uses: Swatinem/rust-cache@v2 with: shared-key: ${{ matrix.profile-key }} @@ -53,6 +55,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - name: Check license headers uses: viperproject/check-license-header@v2 with: @@ -83,6 +87,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: clippy - uses: Swatinem/rust-cache@v2 with: save-if: "false" @@ -113,6 +119,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: clippy - uses: Swatinem/rust-cache@v2 with: save-if: "false" diff --git a/.github/workflows/default-branch-cache.yaml b/.github/workflows/default-branch-cache.yaml index 9d5cede1c1a7..c41b43dc3d7a 100644 --- a/.github/workflows/default-branch-cache.yaml +++ b/.github/workflows/default-branch-cache.yaml @@ -17,6 +17,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + components: clippy,rustfmt - uses: Swatinem/rust-cache@v2 with: save-if: "false" From d6be8f1283b31d6ef942b39446de967750445879 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 25 Sep 2025 14:19:07 -0700 Subject: [PATCH 0960/1053] fix: m6id is a amd64 machine (#1305) --- benchmark/bootstrap/aws-launch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 4701327a6abc..8cbeba9b87f8 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -18,7 +18,7 @@ declare -A VALID_INSTANCES=( ["i4g.xlarge"]="arm64" ["i4i.large"]="amd64" ["i4i.xlarge"]="amd64" - ["m6id.xlarge"]="arm64" + ["m6id.xlarge"]="amd64" ["c6gd.2xlarge"]="arm64" ["x2gd.xlarge"]="arm64" ["m5ad.2xlarge"]="arm64" From 3b644fae75e0c5799e2139949e9061b129937597 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Thu, 25 Sep 2025 18:04:47 -0700 Subject: [PATCH 0961/1053] chore: Add support for enormous disk (#1308) I need to test firewood boostrapping when we don't delete anything so I added support for an instance with ~13T of disk space. --- benchmark/bootstrap/aws-launch.sh | 10 ++-- benchmark/setup-scripts/build-environment.sh | 53 +++++++++++++++++--- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 8cbeba9b87f8..d7ea714c9614 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -26,6 +26,7 @@ declare -A VALID_INSTANCES=( ["r6id.2xlarge"]="amd64" ["x2gd.2xlarge"]="arm64" ["z1d.2xlarge"]="amd64" + ["i8ge.12xlarge"]="arm64" ) # Maximum spot prices for each instance type (from the pricing table) @@ -42,10 +43,11 @@ declare -A MAX_SPOT_PRICES=( ["r6id.2xlarge"]="0.6048" ["x2gd.2xlarge"]="0.6680" ["z1d.2xlarge"]="0.7440" + ["i8ge.12xlarge"]="5.6952" ) # Valid nblocks values -VALID_NBLOCKS=("1m" "10m" "20m" "30m" "40m" "50m") +VALID_NBLOCKS=("1m" "10m" "50m") # Function to show usage show_usage() { @@ -77,6 +79,7 @@ show_usage() { echo " r6id.2xlarge amd64 474 8 64 GiB \$0.6048 Intel Xeon Scalable" echo " x2gd.2xlarge arm64 475 8 128 GiB \$0.6680 Graviton2 memory-optimized" echo " z1d.2xlarge amd64 300 8 64 GiB \$0.7440 High-frequency Intel Xeon CPUs" + echo " i8ge.12xlarge arm64 11250 48 384 GiB \$5.5952 Careful, very expensive" echo "" echo "Valid nblocks values: ${VALID_NBLOCKS[*]}" } @@ -350,7 +353,7 @@ runcmd: - > sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego --login time task reexecute-cchain-range CURRENT_STATE_DIR=/mnt/nvme/ubuntu/exec-data/current-state BLOCK_DIR=/mnt/nvme/ubuntu/exec-data/blocks START_BLOCK=1 END_BLOCK=__END_BLOCK__ CONFIG=firewood METRICS_ENABLED=false - > bootstrap.log 2>&1 + > /var/log/bootstrap.log 2>&1 END_HEREDOC ) @@ -358,9 +361,6 @@ END_HEREDOC case $NBLOCKS in "1m") END_BLOCK="1000000" ;; "10m") END_BLOCK="10000000" ;; - "20m") END_BLOCK="20000000" ;; - "30m") END_BLOCK="30000000" ;; - "40m") END_BLOCK="40000000" ;; "50m") END_BLOCK="50000000" ;; *) END_BLOCK="1000000" ;; # Default fallback esac diff --git a/benchmark/setup-scripts/build-environment.sh b/benchmark/setup-scripts/build-environment.sh index 06a5442d141d..7161acb174fd 100644 --- a/benchmark/setup-scripts/build-environment.sh +++ b/benchmark/setup-scripts/build-environment.sh @@ -10,7 +10,7 @@ fi apt upgrade -y # install the build dependency packages -pkgs=(git protobuf-compiler build-essential apt-transport-https net-tools zfsutils-linux) +pkgs=(git protobuf-compiler build-essential apt-transport-https net-tools zfsutils-linux mdadm) install_pkgs=() for pkg in "${pkgs[@]}"; do if ! dpkg -s "$pkg" > /dev/null 2>&1; then @@ -21,15 +21,52 @@ if [ "${#install_pkgs[@]}" -gt 0 ]; then apt-get install -y "${install_pkgs[@]}" fi -# If there is an NVMe device, format it and mount it to /mnt/nvme/ubuntu/firewood -# this happens on amazon ec2 instances -NVME_DEV="$(realpath /dev/disk/by-id/nvme-Amazon_EC2_NVMe_Instance_Storage_* | uniq)" -if [ -n "$NVME_DEV" ]; then - mkfs.ext4 -E nodiscard -i 6291456 "$NVME_DEV" +# If there are NVMe devices, set up RAID if multiple, or use single device +mapfile -t NVME_DEVS < <(realpath /dev/disk/by-id/nvme-Amazon_EC2_NVMe_Instance_Storage_* 2>/dev/null | sort | uniq) +if [ "${#NVME_DEVS[@]}" -gt 0 ]; then + DEVICE_TO_USE="" + + if [ "${#NVME_DEVS[@]}" -eq 1 ]; then + # Single device, use it directly + DEVICE_TO_USE="${NVME_DEVS[0]}" + echo "Using single NVMe device: $DEVICE_TO_USE" + elif [ "${#NVME_DEVS[@]}" -eq 2 ]; then + # Two devices, create RAID1 + echo "Creating RAID1 array with 2 devices: ${NVME_DEVS[*]}" + mdadm --create /dev/md0 --level=1 --raid-devices=2 "${NVME_DEVS[@]}" + DEVICE_TO_USE="/dev/md0" + elif [ "${#NVME_DEVS[@]}" -eq 3 ]; then + # Three devices, create RAID5 + echo "Creating RAID5 array with 3 devices: ${NVME_DEVS[*]}" + mdadm --create /dev/md0 --level=5 --raid-devices=3 "${NVME_DEVS[@]}" + DEVICE_TO_USE="/dev/md0" + elif [ "${#NVME_DEVS[@]}" -eq 4 ]; then + # Four devices, create RAID10 + echo "Creating RAID10 array with 4 devices: ${NVME_DEVS[*]}" + mdadm --create /dev/md0 --level=10 --raid-devices=4 "${NVME_DEVS[@]}" + DEVICE_TO_USE="/dev/md0" + else + echo "Unsupported number of NVMe devices: ${#NVME_DEVS[@]}. Using first device only." + DEVICE_TO_USE="${NVME_DEVS[0]}" + fi + + # Wait for RAID array to be ready (if created) + if [[ "$DEVICE_TO_USE" == "/dev/md0" ]]; then + echo "Waiting for RAID array to be ready..." + while [ ! -e "$DEVICE_TO_USE" ]; do + sleep 1 + done + # Save RAID configuration + mdadm --detail --scan >> /etc/mdadm/mdadm.conf + update-initramfs -u + fi + + # Format and mount the device + mkfs.ext4 -E nodiscard -i 6291456 "$DEVICE_TO_USE" NVME_MOUNT=/mnt/nvme mkdir -p "$NVME_MOUNT" - mount -o noatime "$NVME_DEV" "$NVME_MOUNT" - echo "$NVME_DEV $NVME_MOUNT ext4 noatime 0 0" >> /etc/fstab + mount -o noatime "$DEVICE_TO_USE" "$NVME_MOUNT" + echo "$DEVICE_TO_USE $NVME_MOUNT ext4 noatime 0 0" >> /etc/fstab mkdir -p "$NVME_MOUNT/ubuntu/firewood" chown ubuntu:ubuntu "$NVME_MOUNT/ubuntu" "$NVME_MOUNT/ubuntu/firewood" ln -s "$NVME_MOUNT/ubuntu/firewood" /home/ubuntu/firewood From 06430bb7b8ea2ccaaf00e7febc49708abbfedfe8 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 26 Sep 2025 09:08:50 -0700 Subject: [PATCH 0962/1053] fix: Revert #1116 (#1313) The use of ManuallyDrop seems to cause a leak. Let's fix this correctly later. --- benchmark/Grafana-dashboard.json | 109 +------------------------------ storage/src/nodestore/hash.rs | 16 ++--- storage/src/nodestore/mod.rs | 64 ++---------------- storage/src/nodestore/persist.rs | 48 ++------------ 4 files changed, 20 insertions(+), 217 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index 718216cf7553..b9d579af5e44 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -845,113 +845,6 @@ ], "title": "Insert Merkle Ops by Type", "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 1000 - }, - { - "color": "red", - "value": 5000 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 31 - }, - "id": 13, - "options": { - "legend": { - "calcs": [ - "min", - "mean", - "max", - "last" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.5.1", - "targets": [ - { - "editorMode": "code", - "expr": "firewood_nodes_unwritten", - "legendFormat": "Unwritten Nodes", - "range": true, - "refId": "A", - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - } - } - ], - "title": "Unwritten Nodes (In Memory)", - "type": "timeseries" } ], "refresh": "10s", @@ -970,4 +863,4 @@ "uid": "adxfhfmwx5ypsc", "version": 12, "weekStart": "" -} +} \ No newline at end of file diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index 7c9890a63bb5..f9ed6b60ad7e 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -118,7 +118,7 @@ where pub(super) fn hash_helper( #[cfg(feature = "ethhash")] &self, node: Node, - ) -> Result<(MaybePersistedNode, HashType, usize), FileIoError> { + ) -> Result<(MaybePersistedNode, HashType), FileIoError> { let mut root_path = Path::new(); #[cfg(not(feature = "ethhash"))] let res = Self::hash_helper_inner(node, PathGuard::from_path(&mut root_path))?; @@ -136,10 +136,9 @@ where mut node: Node, mut path_prefix: PathGuard<'_>, #[cfg(feature = "ethhash")] fake_root_extra_nibble: Option, - ) -> Result<(MaybePersistedNode, HashType, usize), FileIoError> { + ) -> Result<(MaybePersistedNode, HashType), FileIoError> { // If this is a branch, find all unhashed children and recursively hash them. trace!("hashing {node:?} at {path_prefix:?}"); - let mut nodes_processed = 1usize; // Count this node if let Node::Branch(ref mut b) = node { // special case code for ethereum hashes at the account level #[cfg(feature = "ethhash")] @@ -205,7 +204,7 @@ where let child_node = std::mem::take(child_node); // Hash this child and update - let (child_node, child_hash, child_count) = { + let (child_node, child_hash) = { // we extend and truncate path_prefix to reduce memory allocations] let mut child_path_prefix = PathGuard::new(&mut path_prefix); child_path_prefix.0.extend(b.partial_path.0.iter().copied()); @@ -218,16 +217,15 @@ where #[cfg(not(feature = "ethhash"))] child_path_prefix.0.push(nibble as u8); #[cfg(feature = "ethhash")] - let (child_node, child_hash, child_count) = + let (child_node, child_hash) = self.hash_helper_inner(child_node, child_path_prefix, make_fake_root)?; #[cfg(not(feature = "ethhash"))] - let (child_node, child_hash, child_count) = + let (child_node, child_hash) = Self::hash_helper_inner(child_node, child_path_prefix)?; - (child_node, child_hash, child_count) + (child_node, child_hash) }; - nodes_processed = nodes_processed.saturating_add(child_count); *child = Some(Child::MaybePersisted(child_node, child_hash)); trace!("child now {child:?}"); } @@ -253,7 +251,7 @@ where #[cfg(not(feature = "ethhash"))] let hash = hash_node(&node, &path_prefix); - Ok((SharedNode::new(node).into(), hash, nodes_processed)) + Ok((SharedNode::new(node).into(), hash)) } #[cfg(feature = "ethhash")] diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 65fc1f1d729e..bd98d87ce475 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -44,7 +44,6 @@ pub(crate) mod header; pub(crate) mod persist; pub(crate) mod primitives; -use crate::firewood_gauge; use crate::linear::OffsetReader; use crate::logger::trace; use crate::node::branch::ReadSerializable as _; @@ -53,7 +52,6 @@ use arc_swap::access::DynAccess; use smallvec::SmallVec; use std::fmt::Debug; use std::io::{Error, ErrorKind, Read}; -use std::sync::atomic::AtomicUsize; // Re-export types from alloc module pub use alloc::NodeAllocator; @@ -125,7 +123,6 @@ impl NodeStore { deleted: Box::default(), root_hash: None, root: header.root_address().map(Into::into), - unwritten_nodes: AtomicUsize::new(0), }, storage, }; @@ -155,7 +152,6 @@ impl NodeStore { deleted: Box::default(), root_hash: None, root: None, - unwritten_nodes: AtomicUsize::new(0), }, }) } @@ -362,8 +358,6 @@ pub struct Committed { deleted: Box<[MaybePersistedNode]>, root_hash: Option, root: Option, - /// TODO: No readers of this variable yet - will be used for tracking unwritten nodes in committed revisions - unwritten_nodes: AtomicUsize, } impl Clone for Committed { @@ -372,10 +366,6 @@ impl Clone for Committed { deleted: self.deleted.clone(), root_hash: self.root_hash.clone(), root: self.root.clone(), - unwritten_nodes: AtomicUsize::new( - self.unwritten_nodes - .load(std::sync::atomic::Ordering::Relaxed), - ), } } } @@ -409,8 +399,6 @@ pub struct ImmutableProposal { root_hash: Option, /// The root node, either in memory or on disk root: Option, - /// The number of unwritten nodes in this proposal - unwritten_nodes: usize, } impl ImmutableProposal { @@ -428,21 +416,6 @@ impl ImmutableProposal { } } -impl Drop for ImmutableProposal { - fn drop(&mut self) { - // When an immutable proposal is dropped without being committed, - // decrement the gauge to reflect that these nodes will never be written - if self.unwritten_nodes > 0 { - #[allow(clippy::cast_precision_loss)] - firewood_gauge!( - "firewood.nodes.unwritten", - "current number of unwritten nodes" - ) - .decrement(self.unwritten_nodes as f64); - } - } -} - /// Contains the state of a revision of a merkle trie. /// /// The first generic parameter is the type of the revision, which supports reading nodes from parent proposals. @@ -505,23 +478,14 @@ impl, S: ReadableStorage> From> /// Commit a proposal to a new revision of the trie impl From> for NodeStore { fn from(val: NodeStore) -> Self { - let NodeStore { - header, - kind, - storage, - } = val; - // Use ManuallyDrop to prevent the Drop impl from running since we're committing - let kind = std::mem::ManuallyDrop::new(kind); - NodeStore { - header, + header: val.header, kind: Committed { - deleted: kind.deleted.clone(), - root_hash: kind.root_hash.clone(), - root: kind.root.clone(), - unwritten_nodes: AtomicUsize::new(kind.unwritten_nodes), + deleted: val.kind.deleted.clone(), + root_hash: val.kind.root_hash.clone(), + root: val.kind.root.clone(), }, - storage, + storage: val.storage, } } } @@ -548,7 +512,6 @@ impl NodeStore, S> { deleted: self.kind.deleted.clone(), root_hash: self.kind.root_hash.clone(), root: self.kind.root.clone(), - unwritten_nodes: AtomicUsize::new(self.kind.unwritten_nodes), }, storage: self.storage.clone(), } @@ -574,7 +537,6 @@ impl TryFrom> parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), root_hash: None, root: None, - unwritten_nodes: 0, }), storage, }; @@ -587,31 +549,19 @@ impl TryFrom> // Hashes the trie and returns the address of the new root. #[cfg(feature = "ethhash")] - let (root, root_hash, unwritten_count) = nodestore.hash_helper(root)?; + let (root, root_hash) = nodestore.hash_helper(root)?; #[cfg(not(feature = "ethhash"))] - let (root, root_hash, unwritten_count) = - NodeStore::::hash_helper(root)?; + let (root, root_hash) = NodeStore::::hash_helper(root)?; let immutable_proposal = Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); - // Use ManuallyDrop to prevent Drop from running since we're replacing the proposal - let immutable_proposal = std::mem::ManuallyDrop::new(immutable_proposal); nodestore.kind = Arc::new(ImmutableProposal { deleted: immutable_proposal.deleted.clone(), parent: immutable_proposal.parent.clone(), root_hash: Some(root_hash.into_triehash()), root: Some(root), - unwritten_nodes: unwritten_count, }); - // Track unwritten nodes in metrics - #[allow(clippy::cast_precision_loss)] - firewood_gauge!( - "firewood.nodes.unwritten", - "current number of unwritten nodes" - ) - .increment(unwritten_count as f64); - Ok(nodestore) } } diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 2cc73c78d227..ac12e44da524 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -26,14 +26,12 @@ //! - Metrics are collected for flush operation timing //! - Memory-efficient serialization with pre-allocated buffers //! - Ring buffer management for io-uring operations -//! -//! use std::iter::FusedIterator; +use crate::firewood_counter; use crate::linear::FileIoError; use crate::nodestore::AreaIndex; -use crate::{firewood_counter, firewood_gauge}; use coarsetime::Instant; #[cfg(feature = "io-uring")] @@ -259,13 +257,6 @@ impl NodeStore { self.storage .write(persisted_address.get(), serialized.as_slice())?; - // Decrement gauge immediately after node is written to storage - firewood_gauge!( - "firewood.nodes.unwritten", - "current number of unwritten nodes" - ) - .decrement(1.0); - // Allocate the node to store the address, then collect for caching and persistence node.allocate_at(persisted_address); cached_nodes.push(node); @@ -307,11 +298,6 @@ impl NodeStore { // Finally persist the header self.flush_header()?; - // Reset unwritten nodes counter to zero since all nodes are now persisted - self.kind - .unwritten_nodes - .store(0, std::sync::atomic::Ordering::Relaxed); - Ok(()) } } @@ -335,13 +321,11 @@ impl NodeStore { } /// Helper function to handle completion queue entries and check for errors - /// Returns the number of completed operations fn handle_completion_queue( storage: &FileBacked, completion_queue: io_uring::cqueue::CompletionQueue<'_>, saved_pinned_buffers: &mut [PinnedBufferEntry], - ) -> Result { - let mut completed_count = 0usize; + ) -> Result<(), FileIoError> { for entry in completion_queue { let item = entry.user_data() as usize; let pbe = saved_pinned_buffers @@ -369,9 +353,8 @@ impl NodeStore { } // I/O completed successfully pbe.node = None; - completed_count = completed_count.wrapping_add(1); } - Ok(completed_count) + Ok(()) } const RINGSIZE: usize = FileBacked::RINGSIZE as usize; @@ -446,21 +429,11 @@ impl NodeStore { })?; let completion_queue = ring.completion(); trace!("competion queue length: {}", completion_queue.len()); - let completed_writes = handle_completion_queue( + handle_completion_queue( &self.storage, completion_queue, &mut saved_pinned_buffers, )?; - - // Decrement gauge for writes that have actually completed - if completed_writes > 0 { - #[expect(clippy::cast_precision_loss)] - firewood_gauge!( - "firewood.nodes.unwritten", - "current number of unwritten nodes" - ) - .decrement(completed_writes as f64); - } } // Allocate the node to store the address, then collect for caching and persistence @@ -476,18 +449,7 @@ impl NodeStore { .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) })?; - let final_completed_writes = - handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; - - // Decrement gauge for final batch of writes that completed - if final_completed_writes > 0 { - #[expect(clippy::cast_precision_loss)] - firewood_gauge!( - "firewood.nodes.unwritten", - "current number of unwritten nodes" - ) - .decrement(final_completed_writes as f64); - } + handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; debug_assert!( !saved_pinned_buffers.iter().any(|pbe| pbe.node.is_some()), From 4e612d45a40db5f30c7da878847af256ee999458 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Fri, 26 Sep 2025 12:33:43 -0400 Subject: [PATCH 0963/1053] chore: disable non-security dependabot version bumps (#1315) This setting is counter-intuitive to read, but is the correct way to go about this. --- .github/dependabot.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index df3af69a5c14..61c0bfc20600 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,4 @@ updates: interval: "daily" time: "05:00" timezone: "America/Los_Angeles" - open-pull-requests-limit: 10 - allow: - - dependency-type: "all" + open-pull-requests-limit: 0 # Disable non-security version updates From 14ed1852ce3d4ec63fbe94e1a233fda921268c9a Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 26 Sep 2025 09:54:11 -0700 Subject: [PATCH 0964/1053] chore: upgrade dependencies (#1314) Upgraded dependencies automatically. Ignored pinned versions, which cause some issues. List of pinned versions near the bottom of this commit comment. $ cargo +nightly upgrade Checking virtual workspace's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= bytemuck 1.23.1 1.23.2 1.23.2 1.23.2 bytemuck_derive 1.10.0 1.10.1 1.10.1 1.10.1 clap 4.5.41 4.5.48 4.5.48 4.5.48 log 0.4.27 0.4.28 0.4.28 0.4.28 thiserror 2.0.12 2.0.16 2.0.16 2.0.16 tempfile 3.20.0 3.23.0 3.23.0 3.23.0 Checking firewood's dependencies Checking firewood-benchmark's dependencies Checking firewood-ffi's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= chrono 0.4.41 0.4.42 0.4.42 0.4.42 Checking firewood-fwdctl's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= anyhow 1.0.98 1.0.100 1.0.100 1.0.100 Checking firewood-macros's dependencies Checking firewood-storage's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= bitfield 0.19.1 0.19.2 0.19.2 0.19.2 bitflags 2.9.1 2.9.4 2.9.4 2.9.4 derive-where 1.5.0 1.6.0 1.6.0 1.6.0 lru 0.16.0 0.16.1 0.16.1 0.16.1 semver 1.0.26 1.0.27 1.0.27 1.0.27 io-uring 0.7.8 0.7.10 0.7.10 0.7.10 log 0.4.27 0.4.28 0.4.28 0.4.28 Checking firewood-triehash's dependencies Upgrading recursive dependencies Locking 1 package to latest compatible version Adding hashbrown v0.16.0 note: pass `--verbose` to see 5 unchanged dependencies behind latest note: Re-run with `--pinned` to upgrade pinned version requirements note: Re-run with `--verbose` to show more dependencies latest: 54 packages pinned: 4 packages $ cargo +nightly upgrade --dry-run --pinned Checking virtual workspace's dependencies Checking firewood's dependencies Checking firewood-benchmark's dependencies name old req compatible latest new req ==== ======= ========== ====== ======= opentelemetry =0.30.0 0.30.0 0.31.0 =0.31.0 opentelemetry-otlp =0.30.0 0.30.0 0.31.0 =0.31.0 opentelemetry-proto =0.30.0 0.30.0 0.31.0 =0.31.0 opentelemetry_sdk =0.30.0 0.30.0 0.31.0 =0.31.0 Checking firewood-ffi's dependencies Checking firewood-fwdctl's dependencies Checking firewood-macros's dependencies Checking firewood-storage's dependencies Checking firewood-triehash's dependencies note: Re-run with `--verbose` to show more dependencies latest: 68 packages warning: aborting upgrade due to dry run --- Cargo.toml | 12 ++++++------ ffi/Cargo.toml | 2 +- fwdctl/Cargo.toml | 2 +- storage/Cargo.toml | 14 +++++++------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd06a0981ba9..3a4effe59780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,15 +62,15 @@ firewood-triehash = { path = "triehash", version = "0.0.12" } # common dependencies aquamarine = "0.6.0" -bytemuck = "1.23.1" -bytemuck_derive = "1.10.0" -clap = { version = "4.5.41", features = ["derive"] } +bytemuck = "1.23.2" +bytemuck_derive = "1.10.1" +clap = { version = "4.5.48", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" hex = "0.4.3" integer-encoding = "4.0.2" -log = "0.4.27" +log = "0.4.28" metrics = "0.24.2" metrics-util = "0.20.0" nonzero_ext = "0.3.0" @@ -78,7 +78,7 @@ rand_distr = "0.5.1" sha2 = "0.10.9" smallvec = "1.15.1" test-case = "3.3.1" -thiserror = "2.0.12" +thiserror = "2.0.16" # common dev dependencies criterion = "0.7.0" @@ -86,4 +86,4 @@ ethereum-types = "0.15.1" hex-literal = "1.0.0" pprof = "0.15.0" rand = "0.9.2" -tempfile = "3.20.0" +tempfile = "3.23.0" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 881d3b3faf80..af176c996603 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -27,7 +27,7 @@ firewood-storage.workspace = true metrics.workspace = true metrics-util.workspace = true # Regular dependencies -chrono = "0.4.41" +chrono = "0.4.42" oxhttp = "0.3.1" # Optional dependencies env_logger = { workspace = true, optional = true } diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 0b979a48d93e..94bdc66a7a9f 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -46,7 +46,7 @@ logger = ["firewood/logger"] firewood-storage = { workspace = true, features = ["test_utils"] } rand.workspace = true # Regular dependencies -anyhow = "1.0.98" +anyhow = "1.0.100" assert_cmd = "2.0.17" predicates = "3.1.3" serial_test = "3.2.0" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 659836279461..53fa5ffa7178 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -33,18 +33,18 @@ smallvec = { workspace = true, features = ["write", "union"] } thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" -bitfield = "0.19.1" -bitflags = "2.9.1" -derive-where = "1.5.0" +bitfield = "0.19.2" +bitflags = "2.9.4" +derive-where = "1.6.0" enum-as-inner = "0.6.1" indicatif = "0.18.0" -lru = "0.16.0" -semver = "1.0.26" +lru = "0.16.1" +semver = "1.0.27" triomphe = "0.1.14" # Optional dependencies bytes = { version = "1.10.1", optional = true } -io-uring = { version = "0.7.8", optional = true } -log = { version = "0.4.27", optional = true } +io-uring = { version = "0.7.10", optional = true } +log = { version = "0.4.28", optional = true } rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } From 8df1ccc1464f58839c732b57c6cd703e88137abc Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 26 Sep 2025 10:21:17 -0700 Subject: [PATCH 0965/1053] feat(ffi-refactor): replace sequence id with pointer to proposals (8/8) (#1221) This is the final PR in the series to refactor the FFI layer to be more rust and also a bit more Gooey. This replaces the serial atomic id for proposals with a pointer to the actual structures. The wrapper that includes the db handle is only necessary in order to do the cached view shenanigans. I will come back to that later. --- ffi/firewood.go | 25 +- ffi/firewood.h | 239 ++++++++++--------- ffi/firewood_test.go | 19 +- ffi/memory.go | 108 --------- ffi/proposal.go | 134 +++++------ ffi/src/handle.rs | 116 ++++------ ffi/src/lib.rs | 480 ++++++++------------------------------- ffi/src/proofs/change.rs | 2 - ffi/src/proposal.rs | 184 +++++++++++++++ ffi/src/value.rs | 4 +- ffi/src/value/results.rs | 56 ++++- 11 files changed, 603 insertions(+), 764 deletions(-) create mode 100644 ffi/src/proposal.rs diff --git a/ffi/firewood.go b/ffi/firewood.go index 606d74098613..66b7240d0f6f 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -152,8 +152,7 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, err } - val := C.fwd_propose_on_db(db.handle, kvp) - return newProposal(db.handle, &val) + return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), db) } // Get retrieves the value for the given key. It always returns a nil error. @@ -231,3 +230,25 @@ func (db *Database) Revision(root []byte) (*Revision, error) { return &Revision{database: db, root: root}, nil } + +// Close releases the memory associated with the Database. +// +// This is not safe to call while there are any outstanding Proposals. All proposals +// must be freed or committed before calling this. +// +// This is safe to call if the pointer is nil, in which case it does nothing. The +// pointer will be set to nil after freeing to prevent double free. However, it is +// not safe to call this method concurrently from multiple goroutines. +func (db *Database) Close() error { + if db.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { + return fmt.Errorf("unexpected error when closing database: %w", err) + } + + db.handle = nil // Prevent double free + + return nil +} diff --git a/ffi/firewood.h b/ffi/firewood.h index 9fb52e7e5568..cb13778d1932 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -24,6 +24,12 @@ typedef struct ChangeProofContext ChangeProofContext; */ typedef struct DatabaseHandle DatabaseHandle; +/** + * An opaque wrapper around a Proposal that also retains a reference to the + * database handle it was created from. + */ +typedef struct ProposalHandle ProposalHandle; + /** * FFI context for for a parsed or generated range proof. */ @@ -639,28 +645,6 @@ typedef struct VerifyRangeProofArgs { uint32_t max_length; } VerifyRangeProofArgs; -/** - * A value returned by the FFI. - * - * This is used in several different ways, including: - * * An C-style string. - * * An ID for a proposal. - * * A byte slice containing data. - * - * For more details on how the data may be stored, refer to the function signature - * that returned it or the `From` implementations. - * - * The data stored in this struct (if `data` is not null) must be manually freed - * by the caller using `fwd_free_value`. - * - */ -typedef struct Value { - size_t len; - uint8_t *data; -} Value; - -typedef uint32_t ProposalId; - /** * The result type returned from the open or create database functions. */ @@ -747,6 +731,58 @@ typedef struct DatabaseHandleArgs { bool truncate; } DatabaseHandleArgs; +/** + * A result type returned from FFI functions that create a proposal but do not + * commit it to the database. + */ +typedef enum ProposalResult_Tag { + /** + * The caller provided a null pointer to a database handle. + */ + ProposalResult_NullHandlePointer, + /** + * Buulding the proposal was successful and the proposal ID and root hash + * are returned. + */ + ProposalResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + ProposalResult_Err, +} ProposalResult_Tag; + +typedef struct ProposalResult_Ok_Body { + /** + * An opaque pointer to the [`ProposalHandle`] that can be use to create + * an additional proposal or later commit. The caller must ensure that this + * pointer is freed with [`fwd_free_proposal`] if it is not committed. + * + * [`fwd_free_proposal`]: crate::fwd_free_proposal + */ + struct ProposalHandle *handle; + /** + * The root hash of the proposal. Zeroed if the proposal resulted in an + * empty database. + */ + struct HashKey root_hash; +} ProposalResult_Ok_Body; + +typedef struct ProposalResult { + ProposalResult_Tag tag; + union { + ProposalResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} ProposalResult; + /** * Arguments for initializing logging for the Firewood FFI. */ @@ -881,6 +917,8 @@ struct ValueResult fwd_change_proof_to_bytes(const struct ChangeProofContext *_p * Callers must ensure that: * * - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. + * - There are no handles to any open proposals. If so, they must be freed first + * using [`fwd_free_proposal`]. * - The database handle is not used after this function is called. */ struct VoidResult fwd_close_db(struct DatabaseHandle *db); @@ -888,23 +926,33 @@ struct VoidResult fwd_close_db(struct DatabaseHandle *db); /** * Commits a proposal to the database. * + * This function will consume the proposal regardless of whether the commit + * is successful. + * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `proposal_id` - The ID of the proposal to commit + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. * * # Returns * - * A `Value` containing {0, null} if the commit was successful. - * A `Value` containing {0, "error message"} if the commit failed. + * # Returns * - * # Safety + * - [`HashResult::NullHandlePointer`] if the provided database handle is null. + * - [`HashResult::None`] if the commit resulted in an empty database. + * - [`HashResult::Some`] if the commit was successful, containing the new root hash. + * - [`HashResult::Err`] if an error occurred while committing the batch. * - * This function is unsafe because it dereferences raw pointers. - * The caller must ensure that `db` is a valid pointer returned by `open_db` + * # Safety * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `handle` is not used again after this function is called. + * * call [`fwd_free_owned_bytes`] to free the memory associated with the + * returned error ([`HashKey`] does not need to be freed as it is returned + * by value). */ -struct HashResult fwd_commit(const struct DatabaseHandle *db, uint32_t proposal_id); +struct HashResult fwd_commit_proposal(struct ProposalHandle *proposal); /** * Create a change proof for the given range of keys between two roots. @@ -972,8 +1020,6 @@ struct RangeProofResult fwd_db_range_proof(const struct DatabaseHandle *db, * - [`HashResult::Some`] containing the new root hash if the proof was successfully verified * - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. * - * [`fwd_commit`]: crate::fwd_commit - * * # Thread Safety * * It is not safe to call this function concurrently with the same proof context @@ -1071,23 +1117,6 @@ struct VoidResult fwd_db_verify_change_proof(const struct DatabaseHandle *_db, struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, struct VerifyRangeProofArgs _args); -/** - * Drops a proposal from the database. - * The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. - * - * # Arguments - * - * * `db` - The database handle returned by `open_db` - * * `proposal_id` - The ID of the proposal to drop - * - * # Safety - * - * This function is unsafe because it dereferences raw pointers. - * The caller must ensure that `db` is a valid pointer returned by `open_db` - * - */ -struct Value fwd_drop_proposal(const struct DatabaseHandle *db, uint32_t proposal_id); - /** * Frees the memory associated with a `ChangeProofContext`. * @@ -1124,37 +1153,42 @@ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); /** - * Frees the memory associated with a `RangeProofContext`. + * Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory. * * # Arguments * - * * `proof` - The `RangeProofContext` to free, previously returned from any Rust function. + * * `proposal` - A pointer to a [`ProposalHandle`] previously returned from a + * function from this library. * * # Returns * - * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::NullHandlePointer`] if the provided proposal handle is null. + * - [`VoidResult::Ok`] if the proposal was successfully cancelled and freed. * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `proposal` is not null and that it points to + * a valid [`ProposalHandle`] previously returned by a function from this library. + * + * The caller must ensure that the proposal was not committed. [`fwd_commit_proposal`] + * will consume the proposal automatically. */ -struct VoidResult fwd_free_range_proof(struct RangeProofContext *proof); +struct VoidResult fwd_free_proposal(struct ProposalHandle *proposal); /** - * Frees the memory associated with a `Value`. + * Frees the memory associated with a `RangeProofContext`. * * # Arguments * - * * `value` - The `Value` to free, previously returned from any Rust function. - * - * # Safety - * - * This function is unsafe because it dereferences raw pointers. - * The caller must ensure that `value` is a valid pointer. - * - * # Panics + * * `proof` - The `RangeProofContext` to free, previously returned from any Rust function. * - * This function panics if `value` is `null`. + * # Returns * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. */ -struct VoidResult fwd_free_value(struct Value *value); +struct VoidResult fwd_free_range_proof(struct RangeProofContext *proof); /** * Gather latest metrics for this process. @@ -1179,26 +1213,26 @@ struct ValueResult fwd_gather(void); * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `id` - The ID of the proposal to get the value from - * * `key` - The key to look up, in `BorrowedBytes` form + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. + * * `key` - The key to look up, as a [`BorrowedBytes`]. * * # Returns * - * A `Value` containing the requested value. - * A `Value` containing {0, "error message"} if the get failed. + * - [`ValueResult::NullHandlePointer`] if the provided database handle is null. + * - [`ValueResult::None`] if the key was not found. + * - [`ValueResult::Some`] if the key was found with the associated value. + * - [`ValueResult::Err`] if an error occurred while retrieving the value. * * # Safety * * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `key` is a valid pointer to a `Value` struct - * * call `free_value` to free the memory associated with the returned `Value` - * + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `key` is valid for [`BorrowedBytes`] + * * call [`fwd_free_owned_bytes`] to free the memory associated [`OwnedBytes`] + * returned in the result. */ -struct ValueResult fwd_get_from_proposal(const struct DatabaseHandle *db, - ProposalId id, - BorrowedBytes key); +struct ValueResult fwd_get_from_proposal(const struct ProposalHandle *handle, BorrowedBytes key); /** * Gets a value assoicated with the given root hash and key. @@ -1288,54 +1322,55 @@ struct HandleResult fwd_open_db(struct DatabaseHandleArgs args); * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. + * * `db` - The database handle returned by [`fwd_open_db`] + * * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. * * # Returns * - * On success, a `Value` containing {len=id, data=hash}. In this case, the - * hash will always be 32 bytes, and the id will be non-zero. - * On failure, a `Value` containing {0, "error message"}. + * - [`ProposalResult::NullHandlePointer`] if the provided database handle is null. + * - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle + * and calculated root hash. + * - [`ProposalResult::Err`] if an error occurred while creating the proposal. * * # Safety * - * This function is unsafe because it dereferences raw pointers. * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. - * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. - * + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`] + * * ensure that `values` is valid for [`BorrowedKeyValuePairs`] + * * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory + * associated with the proposal. And, the caller must ensure this is done + * before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior. */ -struct Value fwd_propose_on_db(const struct DatabaseHandle *db, - BorrowedKeyValuePairs values); +struct ProposalResult fwd_propose_on_db(const struct DatabaseHandle *db, + BorrowedKeyValuePairs values); /** * Proposes a batch of operations to the database on top of an existing proposal. * * # Arguments * - * * `db` - The database handle returned by `open_db` - * * `proposal_id` - The ID of the proposal to propose on - * * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. + * * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. * * # Returns * - * On success, a `Value` containing {len=id, data=hash}. In this case, the - * hash will always be 32 bytes, and the id will be non-zero. - * On failure, a `Value` containing {0, "error message"}. + * - [`ProposalResult::NullHandlePointer`] if the provided database handle is null. + * - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle + * and calculated root hash. + * - [`ProposalResult::Err`] if an error occurred while creating the proposal. * * # Safety * - * This function is unsafe because it dereferences raw pointers. * The caller must: - * * ensure that `db` is a valid pointer returned by `open_db` - * * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. - * * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. - * + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `values` is valid for [`BorrowedKeyValuePairs`] + * * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory + * associated with the proposal. And, the caller must ensure this is done + * before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior. */ -struct Value fwd_propose_on_proposal(const struct DatabaseHandle *db, - ProposalId proposal_id, - BorrowedKeyValuePairs values); +struct ProposalResult fwd_propose_on_proposal(const struct ProposalHandle *handle, + BorrowedKeyValuePairs values); /** * Returns the next key range that should be fetched after processing the diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index af2a8c8cda39..61816bfb9d39 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -513,21 +513,14 @@ func TestDropProposal(t *testing.T) { _, err = proposal.Get([]byte("non-existent")) r.ErrorIs(err, errDroppedProposal) _, err = proposal.Root() - r.ErrorIs(err, errDroppedProposal) + r.NoError(err, "Root of dropped proposal should still be accessible") - // Attempt to "emulate" the proposal to ensure it isn't internally available still. - proposal = &Proposal{ - handle: db.handle, - id: 1, + // Check that the keys are not in the database. + for i := range keys { + got, err := db.Get(keys[i]) + r.NoError(err, "Get(%d)", i) + r.Empty(got, "Get(%d)", i) } - - // Check all operations on the fake proposal. - _, err = proposal.Get([]byte("non-existent")) - r.Contains(err.Error(), "proposal not found", "Get(fake proposal)") - _, err = proposal.Propose([][]byte{[]byte("key")}, [][]byte{[]byte("value")}) - r.Contains(err.Error(), "proposal not found", "Propose(fake proposal)") - err = proposal.Commit() - r.Contains(err.Error(), "proposal not found", "Commit(fake proposal)") } // Create a proposal with 10 key-value pairs. diff --git a/ffi/memory.go b/ffi/memory.go index a3b8061212d2..441964ca9fde 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -14,13 +14,10 @@ import "C" import ( "errors" "fmt" - "runtime" "unsafe" ) var ( - errNilStruct = errors.New("nil struct pointer cannot be freed") - errBadValue = errors.New("value from cgo formatted incorrectly") errKeysAndValues = errors.New("keys and values must have the same length") errFreeingValue = errors.New("unexpected error while freeing value") ) @@ -136,25 +133,6 @@ func newKeyValuePairs(keys, vals [][]byte, pinner Pinner) (C.BorrowedKeyValuePai return newBorrowedKeyValuePairs(pairs, pinner), nil } -// Close releases the memory associated with the Database. -// -// This is safe to call if the pointer is nil, in which case it does nothing. The -// pointer will be set to nil after freeing to prevent double free. However, it is -// not safe to call this method concurrently from multiple goroutines. -func (db *Database) Close() error { - if db.handle == nil { - return nil - } - - if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { - return fmt.Errorf("unexpected error when closing database: %w", err) - } - - db.handle = nil // Prevent double free - - return nil -} - // ownedBytes is a wrapper around C.OwnedBytes that provides a Go interface // for Rust-owned byte slices. // @@ -347,89 +325,3 @@ func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) { return nil, fmt.Errorf("unknown C.HandleResult tag: %d", result.tag) } } - -// hashAndIDFromValue converts the cgo `Value` payload into: -// -// case | data | len | meaning -// -// 1. | nil | 0 | invalid -// 2. | nil | non-0 | proposal deleted everything -// 3. | non-nil | 0 | error string -// 4. | non-nil | non-0 | hash and id -// -// The value should never be nil. -func hashAndIDFromValue(v *C.struct_Value) ([]byte, uint32, error) { - // Pin the returned value to prevent it from being garbage collected. - defer runtime.KeepAlive(v) - - if v == nil { - return nil, 0, errNilStruct - } - - if v.data == nil { - // Case 2 - if v.len != 0 { - return nil, uint32(v.len), nil - } - - // Case 1 - return nil, 0, errBadValue - } - - // Case 3 - if v.len == 0 { - errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return nil, 0, fmt.Errorf("%w: %w", errFreeingValue, err) - } - return nil, 0, errors.New(errStr) - } - - // Case 4 - id := uint32(v.len) - buf := C.GoBytes(unsafe.Pointer(v.data), RootLength) - v.len = C.size_t(RootLength) // set the length to free - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return nil, 0, fmt.Errorf("%w: %w", errFreeingValue, err) - } - return buf, id, nil -} - -// errorFromValue converts the cgo `Value` payload into: -// -// case | data | len | meaning -// -// 1. | nil | 0 | empty -// 2. | nil | non-0 | invalid -// 3. | non-nil | 0 | error string -// 4. | non-nil | non-0 | invalid -// -// The value should never be nil. -func errorFromValue(v *C.struct_Value) error { - // Pin the returned value to prevent it from being garbage collected. - defer runtime.KeepAlive(v) - - if v == nil { - return errNilStruct - } - - // Case 1 - if v.data == nil && v.len == 0 { - return nil - } - - // Case 3 - if v.len == 0 { - errStr := C.GoString((*C.char)(unsafe.Pointer(v.data))) - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return fmt.Errorf("%w: %w", errFreeingValue, err) - } - return errors.New(errStr) - } - - // Case 2 and 4 - if err := getErrorFromVoidResult(C.fwd_free_value(v)); err != nil { - return fmt.Errorf("%w: %w", errFreeingValue, err) - } - return errBadValue -} diff --git a/ffi/proposal.go b/ffi/proposal.go index efd888fd6635..285c14f5ecc8 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -12,64 +12,36 @@ import "C" import ( "errors" + "fmt" "runtime" + "unsafe" ) var errDroppedProposal = errors.New("proposal already dropped") type Proposal struct { - // handle is returned and accepted by cgo functions. It MUST be treated as - // an opaque value without special meaning. - // https://en.wikipedia.org/wiki/Blinkenlights - handle *C.DatabaseHandle - - // The proposal ID. - // id = 0 is reserved for a dropped proposal. - id uint32 + // The database this proposal is associated with. We hold onto this to ensure + // the database handle outlives the proposal handle, which is required for + // the proposal to be valid. + db *Database + + // handle is an opaque pointer to the proposal within Firewood. It should be + // passed to the C FFI functions that operate on proposals + // + // It is not safe to call these methods with a nil handle. + // + // Calls to `C.fwd_commit_proposal` and `C.fwd_free_proposal` will invalidate + // this handle, so it should not be used after those calls. + handle *C.ProposalHandle // The proposal root hash. root []byte } -// newProposal creates a new Proposal from the given DatabaseHandle and Value. -// The Value must be returned from a Firewood FFI function. -// An error can only occur from parsing the Value. -func newProposal(handle *C.DatabaseHandle, val *C.struct_Value) (*Proposal, error) { - bytes, id, err := hashAndIDFromValue(val) - if err != nil { - return nil, err - } - - // If the proposal root is nil, it means the proposal is empty. - if bytes == nil { - bytes = make([]byte, RootLength) - } - - return &Proposal{ - handle: handle, - id: id, - root: bytes, - }, nil -} - // Root retrieves the root hash of the proposal. // If the proposal is empty (i.e. no keys in database), // it returns nil, nil. func (p *Proposal) Root() ([]byte, error) { - if p.handle == nil { - return nil, errDBClosed - } - - if p.id == 0 { - return nil, errDroppedProposal - } - - // If the hash is empty, return the empty root hash. - if p.root == nil { - return make([]byte, RootLength), nil - } - - // Get the root hash of the proposal. return p.root, nil } @@ -77,28 +49,19 @@ func (p *Proposal) Root() ([]byte, error) { // If the key does not exist, it returns (nil, nil). func (p *Proposal) Get(key []byte) ([]byte, error) { if p.handle == nil { - return nil, errDBClosed - } - - if p.id == 0 { return nil, errDroppedProposal } var pinner runtime.Pinner defer pinner.Unpin() - // Get the value for the given key. - return getValueFromValueResult(C.fwd_get_from_proposal(p.handle, C.uint32_t(p.id), newBorrowedBytes(key, &pinner))) + return getValueFromValueResult(C.fwd_get_from_proposal(p.handle, newBorrowedBytes(key, &pinner))) } // Propose creates a new proposal with the given keys and values. // The proposal is not committed until Commit is called. func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if p.handle == nil { - return nil, errDBClosed - } - - if p.id == 0 { return nil, errDroppedProposal } @@ -110,46 +73,61 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { return nil, err } - // Propose the keys and values. - val := C.fwd_propose_on_proposal(p.handle, C.uint32_t(p.id), kvp) - - return newProposal(p.handle, &val) + return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.db) } // Commit commits the proposal and returns any errors. -// If an error occurs, the proposal is dropped and no longer valid. +// +// The proposal handle is no longer valid after this call, but the root +// hash can still be retrieved using Root(). func (p *Proposal) Commit() error { if p.handle == nil { - return errDBClosed - } - - if p.id == 0 { return errDroppedProposal } - _, err := getHashKeyFromHashResult(C.fwd_commit(p.handle, C.uint32_t(p.id))) - if err != nil { - // this is unrecoverable due to Rust's ownership model - // The underlying proposal is no longer valid. - p.id = 0 - } + _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(p.handle)) + p.handle = nil // we no longer own the proposal handle + return err } -// Drop removes the proposal from memory in Firewood. -// In the case of an error, the proposal can assumed to be dropped. -// An error is returned if the proposal was already dropped. +// Drop releases the memory associated with the Proposal. +// +// This is safe to call if the pointer is nil, in which case it does nothing. +// +// The pointer will be set to nil after freeing to prevent double free. func (p *Proposal) Drop() error { if p.handle == nil { - return errDBClosed + return nil } - if p.id == 0 { - return errDroppedProposal + if err := getErrorFromVoidResult(C.fwd_free_proposal(p.handle)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) } - // Drop the proposal. - val := C.fwd_drop_proposal(p.handle, C.uint32_t(p.id)) - p.id = 0 - return errorFromValue(&val) + p.handle = nil // Prevent double free + + return nil +} + +// getProposalFromProposalResult converts a C.ProposalResult to a Proposal or error. +func getProposalFromProposalResult(result C.ProposalResult, db *Database) (*Proposal, error) { + switch result.tag { + case C.ProposalResult_NullHandlePointer: + return nil, errDBClosed + case C.ProposalResult_Ok: + body := (*C.ProposalResult_Ok_Body)(unsafe.Pointer(&result.anon0)) + hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) + proposal := &Proposal{ + db: db, + handle: body.handle, + root: hashKey[:], + } + return proposal, nil + case C.ProposalResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.ProposalResult tag: %d", result.tag) + } } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 6e0e2aabc3a7..f03fa26217e5 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,12 +4,12 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, - merkle::Value, - v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType, Proposal as _}, + v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType}, }; -use metrics::counter; -use crate::{BorrowedBytes, DatabaseHandle, KeyValuePair}; +use crate::{BorrowedBytes, CView, CreateProposalResult, KeyValuePair, arc_cache::ArcCache}; + +use metrics::counter; /// Arguments for creating or opening a database. These are passed to [`fwd_open_db`] /// @@ -78,7 +78,21 @@ impl DatabaseHandleArgs<'_> { } } -impl DatabaseHandle<'_> { +/// A handle to the database, returned by `fwd_open_db`. +/// +/// These handles are passed to the other FFI functions. +/// +#[derive(Debug)] +#[repr(C)] +pub struct DatabaseHandle { + /// A single cached view to improve performance of reads while committing + cached_view: ArcCache, + + /// The database + db: Db, +} + +impl DatabaseHandle { /// Creates a new database handle from the given arguments. /// /// # Errors @@ -116,7 +130,7 @@ impl DatabaseHandle<'_> { /// # Errors /// /// An error is returned if there was an i/o error while reading the value. - pub fn get_latest(&self, key: impl KeyType) -> Result, api::Error> { + pub fn get_latest(&self, key: impl KeyType) -> Result>, api::Error> { let Some(root) = self.current_root_hash()? else { return Err(api::Error::RevisionNotFound { provided: HashKey::default_root_hash(), @@ -149,71 +163,17 @@ impl DatabaseHandle<'_> { &self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, ) -> Result, api::Error> { - let start = coarsetime::Instant::now(); - - let proposal = self.db.propose(values.as_ref())?; + let CreateProposalResult { handle, start_time } = + self.create_proposal_handle(values.as_ref())?; - let propose_time = start.elapsed().as_millis(); - counter!("firewood.ffi.propose_ms").increment(propose_time); - - let hash_val = proposal.root_hash()?; - - proposal.commit()?; + let root_hash = handle.commit_proposal(|commit_time| { + counter!("firewood.ffi.commit_ms").increment(commit_time.as_millis()); + })?; - let propose_plus_commit_time = start.elapsed().as_millis(); - counter!("firewood.ffi.batch_ms").increment(propose_plus_commit_time); - counter!("firewood.ffi.commit_ms") - .increment(propose_plus_commit_time.saturating_sub(propose_time)); + counter!("firewood.ffi.batch_ms").increment(start_time.elapsed().as_millis()); counter!("firewood.ffi.batch").increment(1); - Ok(hash_val) - } - - /// Returns a value from the proposal with the given ID for the specified key. - /// - /// # Errors - /// - /// An error is returned if the proposal could not be found, or if the key is invalid. - pub fn get_from_proposal( - &self, - id: u32, - key: impl KeyType, - ) -> Result, api::Error> { - self.proposals - .read() - .map_err(|_| invalid_data("proposal lock is poisoned"))? - .get(&id) - .ok_or_else(|| invalid_data("proposal not found"))? - .val(key.as_ref()) - } - - /// Commits a proposal with the given ID. - /// - /// # Errors - /// - /// An error is returned if the proposal could not be committed, or if the proposal ID is invalid. - pub fn commit_proposal(&self, proposal_id: u32) -> Result, String> { - let proposal = self - .proposals - .write() - .map_err(|_| "proposal lock is poisoned")? - .remove(&proposal_id) - .ok_or("proposal not found")?; - - // Get the proposal hash and cache the view. We never cache an empty proposal. - let proposal_hash = proposal.root_hash().map_err(|e| e.to_string())?; - - if let Some(ref hash_key) = proposal_hash { - _ = self.get_root(hash_key.clone()); - } - - // Commit the proposal - let result = proposal.commit().map_err(|e| e.to_string()); - - // Clear the cache, which will force readers after this point to find the committed root hash - self.clear_cached_view(); - - result.map(|()| proposal_hash) + Ok(root_hash) } pub(crate) fn get_root(&self, root: HashKey) -> Result { @@ -237,6 +197,28 @@ impl DatabaseHandle<'_> { } } +impl From for DatabaseHandle { + fn from(db: Db) -> Self { + Self { + db, + cached_view: ArcCache::new(), + } + } +} + +impl<'db> CView<'db> for &'db crate::DatabaseHandle { + fn handle(&self) -> &'db crate::DatabaseHandle { + self + } + + fn create_proposal<'kvp>( + self, + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + ) -> Result, api::Error> { + self.db.propose(values.as_ref().iter()) + } +} + fn invalid_data(error: impl Into>) -> api::Error { api::Error::IO(std::io::Error::new(std::io::ErrorKind::InvalidData, error)) } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 1f34115d4f00..cb63d037b1cc 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -14,10 +14,6 @@ unsafe_code, reason = "This is an FFI library, so unsafe code is expected." )] -#![expect( - clippy::undocumented_unsafe_blocks, - reason = "https://github.com/ava-labs/firewood/pull/1158 will remove" -)] #![cfg_attr( not(target_pointer_width = "64"), forbid( @@ -31,22 +27,15 @@ mod handle; mod logging; mod metrics_setup; mod proofs; +mod proposal; mod value; -use std::collections::HashMap; -use std::ffi::{CStr, CString, c_char}; -use std::fmt::{self, Display, Formatter}; -use std::ops::Deref; -use std::sync::RwLock; -use std::sync::atomic::{AtomicU32, Ordering}; +use firewood::v2::api::DbView; -use firewood::db::{Db, Proposal}; -use firewood::v2::api::{self, Db as _, DbView, KeyValuePairIter, Proposal as _}; - -use crate::arc_cache::ArcCache; pub use crate::handle::*; pub use crate::logging::*; pub use crate::proofs::*; +pub use crate::proposal::*; pub use crate::value::*; #[cfg(unix)] @@ -54,17 +43,6 @@ pub use crate::value::*; #[doc(hidden)] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -type ProposalId = u32; - -#[doc(hidden)] -static ID_COUNTER: AtomicU32 = AtomicU32::new(1); - -/// Atomically retrieves the next proposal ID. -#[doc(hidden)] -fn next_id() -> ProposalId { - ID_COUNTER.fetch_add(1, Ordering::Relaxed) -} - /// Invokes a closure and returns the result as a [`CResult`]. /// /// If the closure panics, it will return [`CResult::from_panic`] with the panic @@ -95,42 +73,6 @@ fn invoke_with_handle>( } } -/// A handle to the database, returned by `fwd_open_db`. -/// -/// These handles are passed to the other FFI functions. -/// -#[derive(Debug)] -pub struct DatabaseHandle<'p> { - /// List of oustanding proposals, by ID - // Keep proposals first, as they must be dropped before the database handle is dropped due to lifetime - // issues. - proposals: RwLock>>, - - /// A single cached view to improve performance of reads while committing - cached_view: ArcCache, - - /// The database - db: Db, -} - -impl From for DatabaseHandle<'_> { - fn from(db: Db) -> Self { - Self { - db, - proposals: RwLock::new(HashMap::new()), - cached_view: ArcCache::new(), - } - } -} - -impl Deref for DatabaseHandle<'_> { - type Target = Db; - - fn deref(&self) -> &Self::Target { - &self.db - } -} - /// Gets the value associated with the given key from the database for the /// latest revision. /// @@ -169,29 +111,30 @@ pub unsafe extern "C" fn fwd_get_latest( /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `id` - The ID of the proposal to get the value from -/// * `key` - The key to look up, in `BorrowedBytes` form +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. +/// * `key` - The key to look up, as a [`BorrowedBytes`]. /// /// # Returns /// -/// A `Value` containing the requested value. -/// A `Value` containing {0, "error message"} if the get failed. +/// - [`ValueResult::NullHandlePointer`] if the provided database handle is null. +/// - [`ValueResult::None`] if the key was not found. +/// - [`ValueResult::Some`] if the key was found with the associated value. +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. /// /// # Safety /// /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `key` is a valid pointer to a `Value` struct -/// * call `free_value` to free the memory associated with the returned `Value` -/// +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `key` is valid for [`BorrowedBytes`] +/// * call [`fwd_free_owned_bytes`] to free the memory associated [`OwnedBytes`] +/// returned in the result. #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_proposal( - db: Option<&DatabaseHandle<'_>>, - id: ProposalId, - key: BorrowedBytes<'_>, + handle: Option<&ProposalHandle<'_>>, + key: BorrowedBytes, ) -> ValueResult { - invoke_with_handle(db, move |db| db.get_from_proposal(id, key)) + invoke_with_handle(handle, move |handle| handle.val(key)) } /// Gets a value assoicated with the given root hash and key. @@ -265,188 +208,127 @@ pub unsafe extern "C" fn fwd_batch( /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. +/// * `db` - The database handle returned by [`fwd_open_db`] +/// * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. /// /// # Returns /// -/// On success, a `Value` containing {len=id, data=hash}. In this case, the -/// hash will always be 32 bytes, and the id will be non-zero. -/// On failure, a `Value` containing {0, "error message"}. +/// - [`ProposalResult::NullHandlePointer`] if the provided database handle is null. +/// - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle +/// and calculated root hash. +/// - [`ProposalResult::Err`] if an error occurred while creating the proposal. /// /// # Safety /// -/// This function is unsafe because it dereferences raw pointers. /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. -/// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. -/// +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`] +/// * ensure that `values` is valid for [`BorrowedKeyValuePairs`] +/// * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory +/// associated with the proposal. And, the caller must ensure this is done +/// before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_propose_on_db<'p>( - db: Option<&'p DatabaseHandle<'p>>, +pub unsafe extern "C" fn fwd_propose_on_db<'db>( + db: Option<&'db DatabaseHandle>, values: BorrowedKeyValuePairs<'_>, -) -> Value { - // Note: the id is guaranteed to be non-zero - // because we use an atomic counter that starts at 1. - propose_on_db(db, &values).unwrap_or_else(Into::into) -} - -/// Internal call for `fwd_propose_on_db` to remove error handling from the C API -#[doc(hidden)] -fn propose_on_db<'p>( - db: Option<&'p DatabaseHandle<'p>>, - values: &[KeyValuePair<'_>], -) -> Result { - let db = db.ok_or("db should be non-null")?; - // Create a batch of operations to perform. - let batch = values.iter().map_into_batch(); - - // Propose the batch of operations. - let proposal = db.propose(batch).map_err(|e| e.to_string())?; - - // Get the root hash of the new proposal. - let mut root_hash: Value = match proposal.root_hash().map_err(|e| e.to_string())? { - Some(root) => Value::from(root.as_slice()), - None => String::new().into(), - }; - - // Store the proposal in the map. We need the write lock instead. - let new_id = next_id(); // Guaranteed to be non-zero - db.proposals - .write() - .map_err(|_| "proposal lock is poisoned")? - .insert(new_id, proposal); - root_hash.len = new_id as usize; // Set the length to the proposal ID - Ok(root_hash) +) -> ProposalResult<'db> { + invoke_with_handle(db, move |db| db.create_proposal_handle(values)) } /// Proposes a batch of operations to the database on top of an existing proposal. /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `proposal_id` - The ID of the proposal to propose on -/// * `values` - A `BorrowedKeyValuePairs` struct containing the key-value pairs to put. +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. +/// * `values` - A [`BorrowedKeyValuePairs`] containing the key-value pairs to put. /// /// # Returns /// -/// On success, a `Value` containing {len=id, data=hash}. In this case, the -/// hash will always be 32 bytes, and the id will be non-zero. -/// On failure, a `Value` containing {0, "error message"}. +/// - [`ProposalResult::NullHandlePointer`] if the provided database handle is null. +/// - [`ProposalResult::Ok`] if the proposal was created, with the proposal handle +/// and calculated root hash. +/// - [`ProposalResult::Err`] if an error occurred while creating the proposal. /// /// # Safety /// -/// This function is unsafe because it dereferences raw pointers. /// The caller must: -/// * ensure that `db` is a valid pointer returned by `open_db` -/// * ensure that `values` is a valid pointer and that it points to an array of `KeyValue` structs of length `nkeys`. -/// * ensure that the `Value` fields of the `KeyValue` structs are valid pointers. -/// +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `values` is valid for [`BorrowedKeyValuePairs`] +/// * call [`fwd_commit_proposal`] or [`fwd_free_proposal`] to free the memory +/// associated with the proposal. And, the caller must ensure this is done +/// before calling [`fwd_close_db`] to avoid memory leaks or undefined behavior. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_propose_on_proposal( - db: Option<&DatabaseHandle<'_>>, - proposal_id: ProposalId, +pub unsafe extern "C" fn fwd_propose_on_proposal<'db>( + handle: Option<&ProposalHandle<'db>>, values: BorrowedKeyValuePairs<'_>, -) -> Value { - // Note: the id is guaranteed to be non-zero - // because we use an atomic counter that starts at 1. - propose_on_proposal(db, proposal_id, &values).unwrap_or_else(Into::into) -} - -/// Internal call for `fwd_propose_on_proposal` to remove error handling from the C API -#[doc(hidden)] -fn propose_on_proposal( - db: Option<&DatabaseHandle<'_>>, - proposal_id: ProposalId, - values: &[KeyValuePair<'_>], -) -> Result { - let db = db.ok_or("db should be non-null")?; - // Create a batch of operations to perform. - let batch = values.iter().map_into_batch(); - - // Get proposal from ID. - // We need write access to add the proposal after we create it. - let guard = db - .proposals - .write() - .expect("failed to acquire write lock on proposals"); - let proposal = guard.get(&proposal_id).ok_or("proposal not found")?; - let new_proposal = proposal.propose(batch).map_err(|e| e.to_string())?; - drop(guard); // Drop the read lock before we get the write lock. - - // Get the root hash of the new proposal. - let mut root_hash: Value = match new_proposal.root_hash().map_err(|e| e.to_string())? { - Some(root) => Value::from(root.as_slice()), - None => String::new().into(), - }; - - // Store the proposal in the map. We need the write lock instead. - let new_id = next_id(); // Guaranteed to be non-zero - db.proposals - .write() - .map_err(|_| "proposal lock is poisoned")? - .insert(new_id, new_proposal); - root_hash.len = new_id as usize; // Set the length to the proposal ID - Ok(root_hash) +) -> ProposalResult<'db> { + invoke_with_handle(handle, move |p| p.create_proposal_handle(values)) } /// Commits a proposal to the database. /// +/// This function will consume the proposal regardless of whether the commit +/// is successful. +/// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `proposal_id` - The ID of the proposal to commit +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. /// /// # Returns /// -/// A `Value` containing {0, null} if the commit was successful. -/// A `Value` containing {0, "error message"} if the commit failed. +/// # Returns /// -/// # Safety +/// - [`HashResult::NullHandlePointer`] if the provided database handle is null. +/// - [`HashResult::None`] if the commit resulted in an empty database. +/// - [`HashResult::Some`] if the commit was successful, containing the new root hash. +/// - [`HashResult::Err`] if an error occurred while committing the batch. /// -/// This function is unsafe because it dereferences raw pointers. -/// The caller must ensure that `db` is a valid pointer returned by `open_db` +/// # Safety /// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `handle` is not used again after this function is called. +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the +/// returned error ([`HashKey`] does not need to be freed as it is returned +/// by value). #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_commit( - db: Option<&DatabaseHandle<'_>>, - proposal_id: u32, +pub unsafe extern "C" fn fwd_commit_proposal( + proposal: Option>>, ) -> HashResult { - invoke_with_handle(db, move |db| db.commit_proposal(proposal_id)) + invoke_with_handle(proposal, move |proposal| { + proposal.commit_proposal(|commit_time| { + metrics::counter!("firewood.ffi.commit_ms").increment(commit_time.as_millis()); + metrics::counter!("firewood.ffi.commit").increment(1); + }) + }) } -/// Drops a proposal from the database. -/// The propopsal's data is now inaccessible, and can be freed by the `RevisionManager`. +/// Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory. /// /// # Arguments /// -/// * `db` - The database handle returned by `open_db` -/// * `proposal_id` - The ID of the proposal to drop +/// * `proposal` - A pointer to a [`ProposalHandle`] previously returned from a +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided proposal handle is null. +/// - [`VoidResult::Ok`] if the proposal was successfully cancelled and freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. /// /// # Safety /// -/// This function is unsafe because it dereferences raw pointers. -/// The caller must ensure that `db` is a valid pointer returned by `open_db` +/// The caller must ensure that the `proposal` is not null and that it points to +/// a valid [`ProposalHandle`] previously returned by a function from this library. /// +/// The caller must ensure that the proposal was not committed. [`fwd_commit_proposal`] +/// will consume the proposal automatically. #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_drop_proposal( - db: Option<&DatabaseHandle<'_>>, - proposal_id: u32, -) -> Value { - drop_proposal(db, proposal_id).map_or_else(Into::into, Into::into) -} - -/// Internal call for `fwd_drop_proposal` to remove error handling from the C API -#[doc(hidden)] -fn drop_proposal(db: Option<&DatabaseHandle<'_>>, proposal_id: u32) -> Result<(), String> { - let db = db.ok_or("db should be non-null")?; - let mut proposals = db - .proposals - .write() - .map_err(|_| "proposal lock is poisoned")?; - proposals.remove(&proposal_id).ok_or("proposal not found")?; - Ok(()) +pub unsafe extern "C" fn fwd_free_proposal( + proposal: Option>>, +) -> VoidResult { + invoke_with_handle(proposal, drop) } /// Get the root hash of the latest version of the database @@ -473,135 +355,6 @@ pub unsafe extern "C" fn fwd_root_hash(db: Option<&DatabaseHandle>) -> HashResul invoke_with_handle(db, DatabaseHandle::current_root_hash) } -/// A value returned by the FFI. -/// -/// This is used in several different ways, including: -/// * An C-style string. -/// * An ID for a proposal. -/// * A byte slice containing data. -/// -/// For more details on how the data may be stored, refer to the function signature -/// that returned it or the `From` implementations. -/// -/// The data stored in this struct (if `data` is not null) must be manually freed -/// by the caller using `fwd_free_value`. -/// -#[derive(Debug, Default)] -#[repr(C)] -pub struct Value { - pub len: usize, - pub data: Option>, -} - -impl Display for Value { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.len, self.data) { - (0, None) => write!(f, "[not found]"), - (0, Some(data)) => write!(f, "[error] {}", unsafe { - CStr::from_ptr(data.as_ptr() as *const c_char).to_string_lossy() - }), - (len, None) => write!(f, "[id] {len}"), - (_, Some(_)) => write!(f, "[data] {:?}", self.as_slice()), - } - } -} - -impl Value { - #[must_use] - pub const fn as_slice(&self) -> &[u8] { - if let Some(data) = self.data { - // SAFETY: We must assume that if non-null, the C caller provided valid pointer - // and length, otherwise caller assumes responsibility for undefined behavior. - unsafe { std::slice::from_raw_parts(data.as_ptr(), self.len) } - } else { - &[] - } - } -} - -impl From<&[u8]> for Value { - fn from(data: &[u8]) -> Self { - let boxed: Box<[u8]> = data.into(); - boxed.into() - } -} - -impl From> for Value { - fn from(data: Box<[u8]>) -> Self { - let len = data.len(); - let leaked_ptr = Box::leak(data).as_mut_ptr(); - let data = std::ptr::NonNull::new(leaked_ptr); - Value { len, data } - } -} - -impl From for Value { - fn from(s: String) -> Self { - if s.is_empty() { - Self::default() - } else { - let cstr = CString::new(s).unwrap_or_default().into_raw(); - Value { - len: 0, - data: std::ptr::NonNull::new(cstr.cast::()), - } - } - } -} - -impl From for Value { - fn from(v: u32) -> Self { - // WARNING: This should only be called with values >= 1. - // In much of the Go code, v.len == 0 is used to indicate a null-terminated string. - // This may cause a panic or memory corruption if used incorrectly. - assert_ne!(v, 0); - Self { - len: v as usize, - data: None, - } - } -} - -impl From<()> for Value { - fn from((): ()) -> Self { - Self::default() - } -} - -/// Frees the memory associated with a `Value`. -/// -/// # Arguments -/// -/// * `value` - The `Value` to free, previously returned from any Rust function. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointers. -/// The caller must ensure that `value` is a valid pointer. -/// -/// # Panics -/// -/// This function panics if `value` is `null`. -/// -#[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_free_value(value: Option<&mut Value>) -> VoidResult { - invoke_with_handle(value, |value| { - if let Some(data) = value.data { - let data_ptr = data.as_ptr(); - // We assume that if the length is 0, then the data is a null-terminated string. - if value.len > 0 { - let recreated_box = - unsafe { Box::from_raw(std::slice::from_raw_parts_mut(data_ptr, value.len)) }; - drop(recreated_box); - } else { - let raw_str = data_ptr.cast::(); - let cstr = unsafe { CString::from_raw(raw_str) }; - drop(cstr); - } - } - }) -} - /// Start metrics recorder for this process. /// /// # Returns @@ -713,17 +466,12 @@ pub extern "C" fn fwd_start_logs(args: LogArgs) -> VoidResult { /// Callers must ensure that: /// /// - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. +/// - There are no handles to any open proposals. If so, they must be freed first +/// using [`fwd_free_proposal`]. /// - The database handle is not used after this function is called. -#[expect(clippy::missing_panics_doc, reason = "panics are captured")] #[unsafe(no_mangle)] -pub unsafe extern "C" fn fwd_close_db(db: Option>>) -> VoidResult { - invoke_with_handle(db, |db| { - db.proposals - .write() - .expect("proposals lock is poisoned") - .clear(); - db.clear_cached_view(); - }) +pub unsafe extern "C" fn fwd_close_db(db: Option>) -> VoidResult { + invoke_with_handle(db, drop) } /// Consumes the [`OwnedBytes`] and frees the memory associated with it. @@ -747,43 +495,3 @@ pub unsafe extern "C" fn fwd_close_db(db: Option>>) -> Vo pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { invoke(move || drop(bytes)) } - -#[cfg(test)] -mod tests { - #![expect(clippy::unwrap_used)] - - use super::*; - - #[test] - fn test_invalid_value_display() { - let value = Value::default(); - assert_eq!(format!("{value}"), "[not found]"); - } - - #[test] - fn test_value_display_with_error_string() { - let cstr = CString::new("test").unwrap(); - let value = Value { - len: 0, - data: std::ptr::NonNull::new(cstr.as_ptr().cast::().cast_mut()), - }; - assert_eq!(format!("{value}"), "[error] test"); - } - - #[test] - fn test_value_display_with_data() { - let value = Value { - len: 4, - data: std::ptr::NonNull::new( - Box::leak(b"test".to_vec().into_boxed_slice()).as_mut_ptr(), - ), - }; - assert_eq!(format!("{value}"), "[data] [116, 101, 115, 116]"); - } - - #[test] - fn test_value_display_with_id() { - let value = Value { len: 4, data: None }; - assert_eq!(format!("{value}"), "[id] 4"); - } -} diff --git a/ffi/src/proofs/change.rs b/ffi/src/proofs/change.rs index e7cc88968fd7..3db1903392c4 100644 --- a/ffi/src/proofs/change.rs +++ b/ffi/src/proofs/change.rs @@ -156,8 +156,6 @@ pub extern "C" fn fwd_db_verify_change_proof( /// - [`HashResult::Some`] containing the new root hash if the proof was successfully verified /// - [`HashResult::Err`] containing an error message if the proof could not be verified or committed. /// -/// [`fwd_commit`]: crate::fwd_commit -/// /// # Thread Safety /// /// It is not safe to call this function concurrently with the same proof context diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs new file mode 100644 index 000000000000..2c7dcdad651d --- /dev/null +++ b/ffi/src/proposal.rs @@ -0,0 +1,184 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood::v2::api::{self, DbView, HashKey, Proposal as _}; + +use crate::value::KeyValuePair; + +use metrics::counter; + +/// An opaque wrapper around a Proposal that also retains a reference to the +/// database handle it was created from. +#[derive(Debug)] +pub struct ProposalHandle<'db> { + hash_key: Option, + proposal: firewood::db::Proposal<'db>, + handle: &'db crate::DatabaseHandle, +} + +impl<'db> DbView for ProposalHandle<'db> { + type Iter<'view> + = as DbView>::Iter<'view> + where + Self: 'view; + + fn root_hash(&self) -> Result, api::Error> { + self.proposal.root_hash() + } + + fn val(&self, key: K) -> Result, api::Error> { + self.proposal.val(key) + } + + fn single_key_proof(&self, key: K) -> Result { + self.proposal.single_key_proof(key) + } + + fn range_proof( + &self, + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result { + self.proposal.range_proof(first_key, last_key, limit) + } + + fn iter_option( + &self, + first_key: Option, + ) -> Result, api::Error> { + self.proposal.iter_option(first_key) + } +} + +impl ProposalHandle<'_> { + /// Returns the root hash of the proposal. + #[must_use] + pub fn hash_key(&self) -> Option { + self.hash_key.clone().map(Into::into) + } + + /// Consume and commit a proposal. + /// + /// # Arguments + /// + /// - `token`: An callback function that will be called with the duration + /// of the commit operation. This will be dropped without being called if + /// the commit fails. + /// + /// # Errors + /// + /// This function will return an error if committing the proposal fails or if the + /// proposal is empty. + pub fn commit_proposal( + self, + token: impl FnOnce(coarsetime::Duration), + ) -> Result, api::Error> { + let ProposalHandle { + hash_key, + proposal, + handle, + } = self; + + // promote the proposal to the handle's cached view so that it can be used + // for future reads while the proposal is being committed + if let Some(ref hash_key) = hash_key { + _ = handle.get_root(hash_key.clone()); + } + + let start_time = coarsetime::Instant::now(); + proposal.commit()?; + let commit_time = start_time.elapsed(); + + // clear the cached view so that it does not hold onto the proposal view + handle.clear_cached_view(); + + token(commit_time); + + Ok(hash_key) + } +} + +#[derive(Debug)] +pub struct CreateProposalResult<'db> { + pub handle: ProposalHandle<'db>, + pub start_time: coarsetime::Instant, +} + +/// A trait that abstracts over database handles and proposal handles for creating proposals. +/// +/// This trait allows functions to work with both [`DatabaseHandle`] and [`ProposalHandle`] +/// uniformly when creating new proposals. It provides a common interface for: +/// - Getting the underlying database handle +/// - Creating proposals from key-value pairs +/// - Creating proposal handles with timing information +/// +/// This abstraction enables proposal chaining (creating proposals on top of other proposals) +/// while maintaining a consistent API. +/// +/// [`DatabaseHandle`]: crate::DatabaseHandle +pub trait CView<'db> { + /// Returns a reference to the database handle that is ultimately used to + /// create the proposal. For the database handle, this returns itself. For, + /// a proposal handle, this returns the handle that was used to create the + /// proposal. + fn handle(&self) -> &'db crate::DatabaseHandle; + + /// Create a [`firewood::db::Proposal`] with the provided key-value pairs. + /// + /// # Errors + /// + /// This function will return a database error if the proposal could not be + /// created. + fn create_proposal<'kvp>( + self, + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + ) -> Result, api::Error>; + + /// Create a [`ProposalHandle`] from the values and return it with timing + /// information. + /// + /// # Errors + /// + /// This function will return a database error if the proposal could not be + /// created or if the proposal is empty. + fn create_proposal_handle<'kvp>( + self, + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + ) -> Result, api::Error> + where + Self: Sized, + { + let handle = self.handle(); + + let start_time = coarsetime::Instant::now(); + let proposal = self.create_proposal(values)?; + let propose_time = start_time.elapsed(); + counter!("firewood.ffi.propose_ms").increment(propose_time.as_millis()); + counter!("firewood.ffi.propose").increment(1); + + let hash_key = proposal.root_hash()?; + + Ok(CreateProposalResult { + handle: ProposalHandle { + hash_key, + proposal, + handle, + }, + start_time, + }) + } +} + +impl<'db> CView<'db> for &ProposalHandle<'db> { + fn handle(&self) -> &'db crate::DatabaseHandle { + self.handle + } + + fn create_proposal<'kvp>( + self, + values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + ) -> Result, api::Error> { + self.proposal.propose(values.as_ref()) + } +} diff --git a/ffi/src/value.rs b/ffi/src/value.rs index b9baa4d5c2c7..f2cb160e0b72 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -15,8 +15,8 @@ pub use self::kvp::KeyValuePair; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ - ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, RangeProofResult, ValueResult, - VoidResult, + ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, ProposalResult, + RangeProofResult, ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 4ef89350a06d..edc52a1801eb 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -5,7 +5,10 @@ use std::fmt; use firewood::v2::api; -use crate::{ChangeProofContext, HashKey, NextKeyRange, OwnedBytes, RangeProofContext}; +use crate::{ + ChangeProofContext, CreateProposalResult, HashKey, NextKeyRange, OwnedBytes, ProposalHandle, + RangeProofContext, +}; /// The result type returned from an FFI function that returns no value but may /// return an error. @@ -54,7 +57,7 @@ pub enum HandleResult { /// associated with this handle when it is no longer needed. /// /// [`fwd_close_db`]: crate::fwd_close_db - Ok(Box>), + Ok(Box), /// An error occurred and the message is returned as an [`OwnedBytes`]. If /// value is guaranteed to contain only valid UTF-8. @@ -66,8 +69,8 @@ pub enum HandleResult { Err(OwnedBytes), } -impl From, E>> for HandleResult { - fn from(value: Result, E>) -> Self { +impl From> for HandleResult { + fn from(value: Result) -> Self { match value { Ok(handle) => HandleResult::Ok(Box::new(handle)), Err(err) => HandleResult::Err(err.to_string().into_bytes().into()), @@ -272,6 +275,49 @@ pub enum NextKeyRangeResult { Err(OwnedBytes), } +/// A result type returned from FFI functions that create a proposal but do not +/// commit it to the database. +#[derive(Debug)] +#[repr(C)] +pub enum ProposalResult<'db> { + /// The caller provided a null pointer to a database handle. + NullHandlePointer, + /// Buulding the proposal was successful and the proposal ID and root hash + /// are returned. + Ok { + /// An opaque pointer to the [`ProposalHandle`] that can be use to create + /// an additional proposal or later commit. The caller must ensure that this + /// pointer is freed with [`fwd_free_proposal`] if it is not committed. + /// + /// [`fwd_free_proposal`]: crate::fwd_free_proposal + // note: opaque pointers mut be boxed because the FFI does not the structure definition. + handle: Box>, + /// The root hash of the proposal. Zeroed if the proposal resulted in an + /// empty database. + root_hash: HashKey, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl<'db, E: fmt::Display> From, E>> for ProposalResult<'db> { + fn from(value: Result, E>) -> Self { + match value { + Ok(CreateProposalResult { handle, .. }) => ProposalResult::Ok { + root_hash: handle.hash_key().unwrap_or_default(), + handle: Box::new(handle), + }, + Err(err) => ProposalResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// Helper trait to handle the different result types returned from FFI functions. /// /// Once Try trait is stable, we can use that instead of this trait: @@ -336,6 +382,7 @@ impl_null_handle_result!( RangeProofResult, ChangeProofResult, NextKeyRangeResult, + ProposalResult<'_>, ); impl_cresult!( @@ -346,6 +393,7 @@ impl_cresult!( RangeProofResult, ChangeProofResult, NextKeyRangeResult, + ProposalResult<'_>, ); enum Panic { From 3a80f61d658254a87e7d8d2e285038a858ffe1bb Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:46:03 -0400 Subject: [PATCH 0966/1053] chore(benchmark): add ssh key (#1316) As requested, this PR adds my ssh key so that I can access any AWS instances that we launch. --- benchmark/bootstrap/aws-launch.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index d7ea714c9614..b0d812f8a5cc 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -262,6 +262,13 @@ users: sudo: "ALL=(ALL) NOPASSWD:ALL" ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/1C8JVL0g6qqMw1p0TwJMqJqERxYTX+7PnP+gXP4km cardno:19_155_748 bernard + - name: rodrigo + lock_passwd: true + groups: users, adm, sudo + shell: /usr/bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDT0/IE2kLNpvaELug1zppQGY03z3fe2zOTjyS655Sgq cardno:28_650_437 rodrigo swap: filename: /swapfile From 80dbeee04a06154967a31deaafb8fce15aa7afff Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 26 Sep 2025 11:11:59 -0700 Subject: [PATCH 0967/1053] chore(release): prepare for v0.0.13 (#1317) The changelog is a little messed up because when I tagged `v0.0.12`, I accidentally did it from the branch and not the commit that was squashed and merged into main. When git-cliff walks the history, it fails to find the tag thus 0.0.12 technically didn't happen. Correcting this in order to correct the changelog would require re-tagging and that would be very disruptive. It's unfortunately better to pretend 0.0.12 didn't happen. This is also why I amended the release doc. --- CHANGELOG.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 12 +++++------ RELEASE.md | 28 +++++++++++++++----------- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a04d160987d..dadac773b03f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [0.0.12] - 2025-08-26 +## [0.0.13] - 2025-09-26 ### 🚀 Features @@ -16,6 +16,43 @@ All notable changes to this project will be documented in this file. - *(ffi-refactor)* Refactor how hash values are returned (5/8) ([#1226](https://github.com/ava-labs/firewood/pull/1226)) - *(ffi-refactor)* Refactor revision to use database handle (6/8) ([#1227](https://github.com/ava-labs/firewood/pull/1227)) - *(ffi-refactor)* Add `ValueResult` type (7/8) ([#1228](https://github.com/ava-labs/firewood/pull/1228)) +- *(checker)* Print report using template for better readability ([#1237](https://github.com/ava-labs/firewood/pull/1237)) +- *(checker)* Fix free lists when the erroneous area is the head ([#1240](https://github.com/ava-labs/firewood/pull/1240)) +- *(ffi-proofs)* Stub interfaces for FFI to interact with proofs. ([#1253](https://github.com/ava-labs/firewood/pull/1253)) +- Explicit impl of PartialEq/Eq on HashOrRlp ([#1260](https://github.com/ava-labs/firewood/pull/1260)) +- *(proofs)* [**breaking**] Disable `ValueDigest::Hash` for ethhash ([#1269](https://github.com/ava-labs/firewood/pull/1269)) +- [**breaking**] Rename `Hashable::key` ([#1270](https://github.com/ava-labs/firewood/pull/1270)) +- *(range-proofs)* KeyValuePairIter (1/2) ([#1282](https://github.com/ava-labs/firewood/pull/1282)) +- *(proofs)* [**breaking**] Add v0 serialization for RangeProofs ([#1271](https://github.com/ava-labs/firewood/pull/1271)) +- *(ffi-refactor)* Replace sequence id with pointer to proposals (8/8) ([#1221](https://github.com/ava-labs/firewood/pull/1221)) + +### 🐛 Bug Fixes + +- Add an advisory lock ([#1244](https://github.com/ava-labs/firewood/pull/1244)) +- Path iterator returned wrong node ([#1259](https://github.com/ava-labs/firewood/pull/1259)) +- Use `count` instead of `size_hint` ([#1268](https://github.com/ava-labs/firewood/pull/1268)) +- Correct typo in README.md ([#1276](https://github.com/ava-labs/firewood/pull/1276)) +- Resolve build failures by pinning opentelemetry to 0.30 ([#1281](https://github.com/ava-labs/firewood/pull/1281)) +- *(range-proofs)* Serialize range proof key consistently ([#1278](https://github.com/ava-labs/firewood/pull/1278)) +- *(range-proofs)* Fix verify of exclusion proofs ([#1279](https://github.com/ava-labs/firewood/pull/1279)) +- *(ffi)* GetFromRoot typo ([#1298](https://github.com/ava-labs/firewood/pull/1298)) +- Incorrect gauge metrics ([#1300](https://github.com/ava-labs/firewood/pull/1300)) +- M6id is a amd64 machine ([#1305](https://github.com/ava-labs/firewood/pull/1305)) +- Revert #1116 ([#1313](https://github.com/ava-labs/firewood/pull/1313)) + +### 💼 Other + +- *(deps)* Update typed-builder requirement from 0.21.0 to 0.22.0 ([#1275](https://github.com/ava-labs/firewood/pull/1275)) + +### 📚 Documentation + +- README implies commit == persist ([#1283](https://github.com/ava-labs/firewood/pull/1283)) + +### 🧪 Testing + +- Mark new_empty_proposal as test only ([#1249](https://github.com/ava-labs/firewood/pull/1249)) +- *(firewood)* Use ctor section to init logger for all tests ([#1277](https://github.com/ava-labs/firewood/pull/1277)) +- *(bootstrap)* Bootstrap testing scripts ([#1287](https://github.com/ava-labs/firewood/pull/1287)) ### ⚙️ Miscellaneous Tasks @@ -23,6 +60,24 @@ All notable changes to this project will be documented in this file. - Synchronize .golangci.yaml ([#1234](https://github.com/ava-labs/firewood/pull/1234)) - *(metrics-check)* Re-use previous comment instead of spamming new ones ([#1232](https://github.com/ava-labs/firewood/pull/1232)) - Nuke grpc-testtool ([#1220](https://github.com/ava-labs/firewood/pull/1220)) +- Rename FuzzTree ([#1239](https://github.com/ava-labs/firewood/pull/1239)) +- Upgrade to rust 1.89 ([#1242](https://github.com/ava-labs/firewood/pull/1242)) +- Rename mut_root to root_mut ([#1248](https://github.com/ava-labs/firewood/pull/1248)) +- Add missing debug traits ([#1254](https://github.com/ava-labs/firewood/pull/1254)) +- These tests actually work now ([#1262](https://github.com/ava-labs/firewood/pull/1262)) +- Cargo +nightly clippy --fix ([#1265](https://github.com/ava-labs/firewood/pull/1265)) +- Update .golangci.yaml ([#1274](https://github.com/ava-labs/firewood/pull/1274)) +- Various script improvements ([#1288](https://github.com/ava-labs/firewood/pull/1288)) +- *(bootstrap)* Add keys for brandon ([#1289](https://github.com/ava-labs/firewood/pull/1289)) +- *(bootstrap)* Add keys for Bernard and Amin ([#1291](https://github.com/ava-labs/firewood/pull/1291)) +- [**breaking**] Decorate enums and structs with `#[non_exhaustive]` ([#1292](https://github.com/ava-labs/firewood/pull/1292)) +- Add spot instance support ([#1294](https://github.com/ava-labs/firewood/pull/1294)) +- Upgrade go ([#1296](https://github.com/ava-labs/firewood/pull/1296)) +- Ask for clippy and rustfmt ([#1306](https://github.com/ava-labs/firewood/pull/1306)) +- Add support for enormous disk ([#1308](https://github.com/ava-labs/firewood/pull/1308)) +- Disable non-security dependabot version bumps ([#1315](https://github.com/ava-labs/firewood/pull/1315)) +- Upgrade dependencies ([#1314](https://github.com/ava-labs/firewood/pull/1314)) +- *(benchmark)* Add ssh key ([#1316](https://github.com/ava-labs/firewood/pull/1316)) ## [0.0.11] - 2025-08-20 diff --git a/Cargo.toml b/Cargo.toml index 3a4effe59780..5f175f1b909f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.0.12" +version = "0.0.13" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -54,11 +54,11 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.12" } -firewood-macros = { path = "firewood-macros", version = "0.0.12" } -firewood-storage = { path = "storage", version = "0.0.12" } -firewood-ffi = { path = "ffi", version = "0.0.12" } -firewood-triehash = { path = "triehash", version = "0.0.12" } +firewood = { path = "firewood", version = "0.0.13" } +firewood-macros = { path = "firewood-macros", version = "0.0.13" } +firewood-storage = { path = "storage", version = "0.0.13" } +firewood-ffi = { path = "ffi", version = "0.0.13" } +firewood-triehash = { path = "triehash", version = "0.0.13" } # common dependencies aquamarine = "0.6.0" diff --git a/RELEASE.md b/RELEASE.md index c2079c7e3b12..a956c60c6184 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,9 +12,9 @@ Start off by crating a new branch: ```console $ git fetch -$ git switch -c release/v0.0.13 origin/main -branch 'release/v0.0.13' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.13' +$ git switch -c release/v0.0.14 origin/main +branch 'release/v0.0.14' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.14' ``` ## Package Version @@ -26,7 +26,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.13" +version = "0.0.14" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -41,15 +41,16 @@ Therefore, updating only the version defined in the root config is needed. ## Dependency Version -The next step is to bump the dependency declarations to the new version. Packages -within the workspace that are used as libraries are also defined within the -[`[workspace.dependencies]`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table) +`cargo publish` for each package requires that each dependency specify _a_ version; +therefore, the next step is to bump the dependency declarations to the new version. +Packages within the workspace that are used as libraries are also defined within +the [`[workspace.dependencies]`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table) table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.13" } +firewood = { path = "firewood", version = "0.0.14" } ``` This allows packages within the workspace to inherit the dependency, @@ -78,7 +79,7 @@ To build the changelog, see git-cliff.org. Short version: ```sh cargo install --locked git-cliff -git cliff --tag v0.0.13 > CHANGELOG.md +git cliff --tag v0.0.14 -o CHANGELOG.md ``` ## Review @@ -92,11 +93,14 @@ git cliff --tag v0.0.13 > CHANGELOG.md To trigger a release, push a tag to the main branch matching the new version, ```sh -git tag -s -a v0.0.13 -m 'Release v0.0.13' -git push origin v0.0.13 +# be sure to switch back to the main branch before tagging +git checkout main +git pull --prune +git tag -s -a v0.0.14 -m 'Release v0.0.14' +git push origin v0.0.14 ``` -for `v0.0.13` for the merged version change. The CI will automatically publish a +for `v0.0.14` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). From 0b5a6ca6c27d1d2de279d4f7a4565d793c80da59 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 29 Sep 2025 08:20:38 -0700 Subject: [PATCH 0968/1053] ci: include dependency lockfile (#1321) For reproducible builds and in order to write a nix flake, we need to include the lockfile. This is primarily for the ffi library. But, because we ship a binary artifact, we need to include it for all dependencies of the ffi library (including firewood itself). # `--frozen` vs `--locked` `--frozen` implies both `--locked` and `--offline`, which is what we want in most cases. However, `--locked` is what we want in all cases. `--offline` (and `--frozen`) do not work for most of the CI actions because the cache utility we use does not include `~/.cargo/registry/src` because cargo can theoretically reconstruct it from all of the `~/.cargo/registry/cache/*.crate` archives. And, it can. But, cargo refuses to do that if you specify `--frozen` or `--offline`. # Updates The lock file is updated any time someone: - Adds a new crate to the workspace - Adds a new dependency to a crate in the workspace - Runs `cargo update` which will resolve any semver compatible upgrades --- .github/check-license-headers.yaml | 2 + .github/workflows/attach-static-libs.yaml | 7 +- .github/workflows/ci.yaml | 24 +- .gitignore | 4 - Cargo.lock | 4380 +++++++++++++++++++++ 5 files changed, 4403 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 89ffccbe300e..bd1af237e927 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -14,6 +14,7 @@ "README*", "**/README*", "Cargo.toml", + "Cargo.lock", "*/Cargo.toml", "docs/**", "CODEOWNERS", @@ -40,6 +41,7 @@ "README*", "**/README*", "Cargo.toml", + "Cargo.lock", "*/Cargo.toml", "docs/**", "benchmark/**", diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 01e3c8e98986..f09983f95345 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -15,6 +15,7 @@ on: env: CARGO_TERM_COLOR: always + RUST_BACKTRACE: full concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -57,8 +58,12 @@ jobs: if: matrix.pre-build-cmd != '' run: ${{ matrix.pre-build-cmd }} + # Require Cargo.lock to be up to date and ensure the registry cache is populated + - name: Cargo Fetch (run `cargo generate-lockfile` if this fails) + run: cargo fetch --locked --verbose + - name: Build for ${{ matrix.target }} - run: cargo build --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi + run: cargo build --frozen --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi - name: Upload binary uses: actions/upload-artifact@v4 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8c9eb20649b6..85fb9f5add7f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,6 +7,7 @@ on: env: CARGO_TERM_COLOR: always + RUST_BACKTRACE: full jobs: build: @@ -45,10 +46,15 @@ jobs: - uses: Swatinem/rust-cache@v2 with: shared-key: ${{ matrix.profile-key }} + cache-all-crates: true + cache-workspace-crates: true + # Require Cargo.lock to be up to date and ensure the registry cache is populated + - name: Cargo Fetch (run `cargo generate-lockfile` if this fails) + run: cargo fetch --locked --verbose - name: Check - run: cargo check ${{ matrix.profile-args }} --workspace --all-targets + run: cargo check --frozen ${{ matrix.profile-args }} --workspace --all-targets - name: Build - run: cargo build ${{ matrix.profile-args }} --workspace --all-targets + run: cargo build --frozen ${{ matrix.profile-args }} --workspace --all-targets no-rust-cache-lint: runs-on: ubuntu-latest @@ -97,7 +103,7 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} name: ${{ matrix.profile-key }} - args: ${{ matrix.profile-args }} --workspace --all-targets + args: --locked ${{ matrix.profile-args }} --workspace --all-targets rust-lint: needs: build @@ -126,7 +132,7 @@ jobs: save-if: "false" shared-key: ${{ matrix.profile-key }} - name: Clippy - run: cargo clippy ${{ matrix.profile-args }} --workspace --all-targets -- -D warnings + run: cargo clippy --locked ${{ matrix.profile-args }} --workspace --all-targets -- -D warnings test: needs: build @@ -155,7 +161,7 @@ jobs: save-if: "false" shared-key: ${{ matrix.profile-key }} - name: Run tests with features enabled - run: cargo test --verbose ${{ matrix.profile-args }} + run: cargo test --locked --verbose ${{ matrix.profile-args }} examples: needs: build @@ -182,9 +188,9 @@ jobs: save-if: "false" shared-key: ${{ matrix.profile-key }} - name: Run benchmark example - run: RUST_BACKTRACE=1 cargo run ${{ matrix.profile-args }} --bin benchmark -- --number-of-batches 100 --batch-size 1000 create + run: cargo run --locked ${{ matrix.profile-args }} --bin benchmark -- --number-of-batches 100 --batch-size 1000 create - name: Run insert example - run: RUST_BACKTRACE=1 cargo run ${{ matrix.profile-args }} --example insert + run: cargo run --locked ${{ matrix.profile-args }} --example insert docs: needs: build @@ -205,7 +211,7 @@ jobs: !CHANGELOG.md !target/** - name: doc generation - run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps + run: RUSTDOCFLAGS="-D warnings" cargo doc --locked --document-private-items --no-deps ffi: needs: build @@ -222,7 +228,7 @@ jobs: shared-key: "debug-no-features" - name: Build Firewood FFI working-directory: ffi - run: cargo build --release + run: cargo build --locked --release - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/.gitignore b/.gitignore index 161625f6ab46..3ac280ce209a 100644 --- a/.gitignore +++ b/.gitignore @@ -51,10 +51,6 @@ Temporary Items debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000000..3c3797bc9fdc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4380 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libloading", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitfield" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62a3a774b2fcac1b726922b921ebba5e9fe36ad37659c822cf8ff2c1e0819892" +dependencies = [ + "bitfield-macros", +] + +[[package]] +name = "bitfield-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52511b09931f7d5fe3a14f23adefbc23e5725b184013e96c8419febb61f14734" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" + +[[package]] +name = "bytemuck_derive" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cbindgen" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "coarsetime" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "console" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.1", +] + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[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", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.1", +] + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "fastant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bf7fa928ce0c4a43bd6e7d1235318fc32ac3a3dea06a2208c44e729449471a" +dependencies = [ + "small_ctor", + "web-time", +] + +[[package]] +name = "fastrace" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318783b9fefe06130ab664ff1779215657586b004c0c7f3d6ece16d658936d06" +dependencies = [ + "fastant", + "fastrace-macro", + "parking_lot", + "pin-project", + "rand 0.9.2", + "rtrb", + "serde", +] + +[[package]] +name = "fastrace-macro" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7079009cf129d63c850dee732b58d7639d278a47ad99c607954ac94cfd57ef4" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fastrace-opentelemetry" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cf2a7ad300d531f04495e15720852fbf0f01f28e884ac29e22ddbb7228074e" +dependencies = [ + "fastrace", + "log", + "opentelemetry", + "opentelemetry_sdk", + "pollster", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "firewood" +version = "0.0.13" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "clap", + "coarsetime", + "criterion", + "ctor", + "env_logger", + "ethereum-types", + "fastrace", + "firewood-macros", + "firewood-storage", + "firewood-triehash", + "hash-db", + "hex", + "hex-literal", + "integer-encoding", + "metrics", + "plain_hasher", + "pprof", + "rand 0.9.2", + "rlp", + "sha3", + "tempfile", + "test-case", + "thiserror", + "typed-builder", +] + +[[package]] +name = "firewood-benchmark" +version = "0.0.13" +dependencies = [ + "clap", + "env_logger", + "fastrace", + "fastrace-opentelemetry", + "firewood", + "firewood-storage", + "hex", + "log", + "metrics", + "metrics-exporter-prometheus", + "metrics-util", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-proto", + "opentelemetry_sdk", + "pretty-duration", + "rand 0.9.2", + "rand_distr", + "sha2", + "tikv-jemallocator", + "tokio", +] + +[[package]] +name = "firewood-ffi" +version = "0.0.13" +dependencies = [ + "cbindgen", + "chrono", + "coarsetime", + "env_logger", + "firewood", + "firewood-storage", + "metrics", + "metrics-util", + "oxhttp", + "test-case", + "tikv-jemallocator", +] + +[[package]] +name = "firewood-fwdctl" +version = "0.0.13" +dependencies = [ + "anyhow", + "askama", + "assert_cmd", + "clap", + "csv", + "env_logger", + "firewood", + "firewood-storage", + "hex", + "indicatif", + "log", + "nonzero_ext", + "num-format", + "predicates", + "rand 0.9.2", + "serial_test", +] + +[[package]] +name = "firewood-macros" +version = "0.0.13" +dependencies = [ + "coarsetime", + "metrics", + "proc-macro2", + "quote", + "syn", + "trybuild", +] + +[[package]] +name = "firewood-storage" +version = "0.0.13" +dependencies = [ + "aquamarine", + "arc-swap", + "bitfield", + "bitflags 2.9.4", + "bytemuck", + "bytemuck_derive", + "bytes", + "coarsetime", + "criterion", + "derive-where", + "enum-as-inner", + "fastrace", + "hex", + "indicatif", + "integer-encoding", + "io-uring", + "log", + "lru", + "metrics", + "nonzero_ext", + "pprof", + "rand 0.9.2", + "rlp", + "semver", + "sha2", + "sha3", + "smallvec", + "tempfile", + "test-case", + "thiserror", + "triomphe", +] + +[[package]] +name = "firewood-triehash" +version = "0.0.13" +dependencies = [ + "criterion", + "ethereum-types", + "hash-db", + "hex-literal", + "keccak-hasher", + "rlp", + "tiny-keccak", + "trie-standardmap", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "indicatif" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + +[[package]] +name = "inferno" +version = "0.11.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +dependencies = [ + "ahash", + "indexmap", + "is-terminal", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml", + "rgb", + "str_stack", +] + +[[package]] +name = "integer-encoding" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d762194228a2f1c11063e46e32e5acb96e66e906382b9eb5441f2e0504bbd5a" + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-hasher" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ea4653859ca2266a86419d3f592d3f22e7a854b482f99180d2498507902048" +dependencies = [ + "hash-db", + "hash256-std-hasher", + "tiny-keccak", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.4", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + +[[package]] +name = "metrics" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +dependencies = [ + "base64", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "indexmap", + "metrics", + "ordered-float", + "quanta", + "radix_trie", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc" +dependencies = [ + "base64", + "hex", + "opentelemetry", + "opentelemetry_sdk", + "prost", + "serde", + "tonic", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "serde_json", + "thiserror", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "oxhttp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "971c03797806d47915950e62816a8f365e3ddc8800310e3200ec666934d0f7c9" +dependencies = [ + "http", + "httparse", + "url", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plain_hasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e19e6491bdde87c2c43d70f4c194bc8a758f2eb732df00f61e43f7362e3b4cc" +dependencies = [ + "crunchy", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "pprof" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a01da47675efa7673b032bf8efd8214f1917d89685e07e395ab125ea42b187" +dependencies = [ + "aligned-vec", + "backtrace", + "cfg-if", + "findshlibs", + "inferno", + "libc", + "log", + "nix", + "once_cell", + "smallvec", + "spin", + "symbolic-demangle", + "tempfile", + "thiserror", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty-duration" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8868e7264af614b3634ff0abbe37b178e61000611b8a75221aea40221924aba" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rtrb" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "security-framework" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "small_ctor" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88414a5ca1f85d82cc34471e975f0f74f6aa54c40f062efa42c0080e7f763f81" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symbolic-common" +version = "12.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.1", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "slab", + "socket2", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "trie-standardmap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684aafb332fae6f83d7fe10b3fbfdbe39a1b3234c4e2a618f030815838519516" +dependencies = [ + "hash-db", + "keccak-hasher", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "trybuild" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.7", +] + +[[package]] +name = "typed-builder" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.4", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From 7fa46ed71967f3dd6e9bfa3ff661f83f463d42f4 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:10:25 -0400 Subject: [PATCH 0969/1053] ci: Add race detection in Go (#1323) We probably should be testing for races in the Go tests (and potentially add more parallelization tests). --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 85fb9f5add7f..d9887cbf9b6d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -242,7 +242,7 @@ jobs: - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks - run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test ./... + run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test -race ./... firewood-ethhash-differential-fuzz: needs: build @@ -264,7 +264,7 @@ jobs: - name: Test Go FFI bindings working-directory: ffi # cgocheck2 is expensive but provides complete pointer checks - run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test ./... + run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test -race ./... - name: Test Firewood <> Ethereum Differential Fuzz working-directory: ffi/tests/eth run: go test -fuzz=. -fuzztime=1m From 8d69d198052b01a67283c6dd4f93728d7c4619f8 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 1 Oct 2025 11:25:16 -0700 Subject: [PATCH 0970/1053] fix(#1324): correct replacement hints in clippy.toml (#1325) As was pointed out, `firewood_storage::StdRng` doesn't exist. It is instead named `SeededRng`. Closes: #1324 --- clippy.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy.toml b/clippy.toml index 1afe1ddf5b69..bfe28d4a181d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,10 +10,10 @@ doc-valid-idents = [ ] disallowed-methods = [ - { path = "rand::rng", replacement = "firewood_storage::StdRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, + { path = "rand::rng", replacement = "firewood_storage::SeededRng::from_env_or_random", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, ] disallowed-types = [ - { path = "rand::SeedableRng", replacement = "firewood_storage::StdRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, - { path = "rand::rngs::StdRng", replacement = "firewood_storage::StdRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, + { path = "rand::SeedableRng", replacement = "firewood_storage::SeededRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, + { path = "rand::rngs::StdRng", replacement = "firewood_storage::SeededRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, ] From 18a95bdfcebf86f7843bfd9d88bcd42ae8cc4985 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 3 Oct 2025 06:27:42 -0700 Subject: [PATCH 0971/1053] refactor: unify root storage using Child enum (#1330) There were two fields representing a root, the address and the hash. They were both options, which means that it is possible for one to be None and the other to be Some, which would be a logic error. This PR makes that impossible. Thought hard about whether or not to use Child, as it's possible to use something simpler than that, but based on the fact that we're going to add deferred persistence, this is probably the right structure. The owned Node variant of Child will never be used, but this problem is systemic and perhaps worthy of another PR and is much more difficult. Summary of changes: - Replace separate root_hash and root fields with single Child-based root - Update all root access methods to use Child::as_maybe_persisted_node() - Fix test compilation errors by properly chaining method calls - Simplify root persistence logic using Child::persisted_address() --- storage/src/node/branch.rs | 2 +- storage/src/nodestore/mod.rs | 73 ++++++++++++++++---------------- storage/src/nodestore/persist.rs | 21 +++++---- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index dc88fdcec7e2..0813d5168f16 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -223,7 +223,7 @@ mod ethhash { self } - pub(crate) fn into_triehash(self) -> TrieHash { + pub fn into_triehash(self) -> TrieHash { self.into() } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index bd98d87ce475..0afffb636b02 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -89,7 +89,9 @@ use std::sync::Arc; use crate::hashednode::hash_node; use crate::node::Node; use crate::node::persist::MaybePersistedNode; -use crate::{CacheReadStrategy, FileIoError, Path, ReadableStorage, SharedNode, TrieHash}; +use crate::{ + CacheReadStrategy, Child, FileIoError, HashType, Path, ReadableStorage, SharedNode, TrieHash, +}; use super::linear::WritableStorage; @@ -121,16 +123,15 @@ impl NodeStore { header, kind: Committed { deleted: Box::default(), - root_hash: None, - root: header.root_address().map(Into::into), + root: None, }, storage, }; - if let Some(root_address) = nodestore.header.root_address() { + if let Some(root_address) = header.root_address() { let node = nodestore.read_node_from_disk(root_address, "open"); let root_hash = node.map(|n| hash_node(&n, &Path(SmallVec::default())))?; - nodestore.kind.root_hash = Some(root_hash.into_triehash()); + nodestore.kind.root = Some(Child::AddressWithHash(root_address, root_hash)); } Ok(nodestore) @@ -150,7 +151,6 @@ impl NodeStore { storage, kind: Committed { deleted: Box::default(), - root_hash: None, root: None, }, }) @@ -177,10 +177,12 @@ impl Parentable for Arc { NodeStoreParent::Proposed(Arc::clone(self)) } fn root_hash(&self) -> Option { - self.root_hash.clone() + self.root + .as_ref() + .and_then(|root| root.hash().cloned().map(HashType::into_triehash)) } fn root(&self) -> Option { - self.root.clone() + self.root.as_ref().map(Child::as_maybe_persisted_node) } } @@ -204,13 +206,19 @@ impl NodeStore, S> { impl Parentable for Committed { fn as_nodestore_parent(&self) -> NodeStoreParent { - NodeStoreParent::Committed(self.root_hash.clone()) + NodeStoreParent::Committed( + self.root + .as_ref() + .and_then(|root| root.hash().cloned().map(HashType::into_triehash)), + ) } fn root_hash(&self) -> Option { - self.root_hash.clone() + self.root + .as_ref() + .and_then(|root| root.hash().cloned().map(HashType::into_triehash)) } fn root(&self) -> Option { - self.root.clone() + self.root.as_ref().map(Child::as_maybe_persisted_node) } } @@ -353,21 +361,10 @@ pub trait RootReader { } /// A committed revision of a merkle trie. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Committed { deleted: Box<[MaybePersistedNode]>, - root_hash: Option, - root: Option, -} - -impl Clone for Committed { - fn clone(&self) -> Self { - Self { - deleted: self.deleted.clone(), - root_hash: self.root_hash.clone(), - root: self.root.clone(), - } - } + root: Option, } #[derive(Clone, Debug)] @@ -395,10 +392,8 @@ pub struct ImmutableProposal { deleted: Box<[MaybePersistedNode]>, /// The parent of this proposal. parent: Arc>, - /// The hash of the root node for this proposal - root_hash: Option, - /// The root node, either in memory or on disk - root: Option, + /// The root of the trie in this proposal. + root: Option, } impl ImmutableProposal { @@ -482,7 +477,6 @@ impl From> for NodeStore NodeStore, S> { header: current_revision.header, kind: Committed { deleted: self.kind.deleted.clone(), - root_hash: self.kind.root_hash.clone(), root: self.kind.root.clone(), }, storage: self.storage.clone(), @@ -535,7 +528,6 @@ impl TryFrom> kind: Arc::new(ImmutableProposal { deleted: kind.deleted.into(), parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), - root_hash: None, root: None, }), storage, @@ -558,8 +550,7 @@ impl TryFrom> nodestore.kind = Arc::new(ImmutableProposal { deleted: immutable_proposal.deleted.clone(), parent: immutable_proposal.parent.clone(), - root_hash: Some(root_hash.into_triehash()), - root: Some(root), + root: Some(Child::MaybePersisted(root, root_hash)), }); Ok(nodestore) @@ -593,20 +584,28 @@ impl RootReader for NodeStore { impl RootReader for NodeStore { fn root_node(&self) -> Option { // TODO: If the read_node fails, we just say there is no root; this is incorrect - self.kind.root.as_ref()?.as_shared_node(self).ok() + self.kind + .root + .as_ref() + .map(Child::as_maybe_persisted_node) + .and_then(|node| node.as_shared_node(self).ok()) } fn root_as_maybe_persisted_node(&self) -> Option { - self.kind.root.clone() + self.kind.root.as_ref().map(Child::as_maybe_persisted_node) } } impl RootReader for NodeStore, S> { fn root_node(&self) -> Option { // Use the MaybePersistedNode's as_shared_node method to get the root - self.kind.root.as_ref()?.as_shared_node(self).ok() + self.kind + .root + .as_ref() + .map(Child::as_maybe_persisted_node) + .and_then(|node| node.as_shared_node(self).ok()) } fn root_as_maybe_persisted_node(&self) -> Option { - self.kind.root.clone() + self.kind.root.as_ref().map(Child::as_maybe_persisted_node) } } diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index ac12e44da524..489ff7bbd212 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -29,9 +29,9 @@ use std::iter::FusedIterator; -use crate::firewood_counter; use crate::linear::FileIoError; use crate::nodestore::AreaIndex; +use crate::{Child, firewood_counter}; use coarsetime::Instant; #[cfg(feature = "io-uring")] @@ -288,11 +288,7 @@ impl NodeStore { self.header = self.flush_nodes()?; // Set the root address in the header based on the persisted root - let root_address = self - .kind - .root - .as_ref() - .and_then(crate::MaybePersistedNode::as_linear_address); + let root_address = self.kind.root.as_ref().and_then(Child::persisted_address); self.header.set_root_address(root_address); // Finally persist the header @@ -707,7 +703,10 @@ mod tests { // Verify the committed store has the expected values let root = committed_store.kind.root.as_ref().unwrap(); - let root_node = root.as_shared_node(&committed_store).unwrap(); + let root_maybe_persisted = root.as_maybe_persisted_node(); + let root_node = root_maybe_persisted + .as_shared_node(&committed_store) + .unwrap(); assert_eq!(*root_node.partial_path(), Path::from(&[0])); assert_eq!(root_node.value(), Some(&b"branch_value"[..])); assert!(root_node.is_branch()); @@ -718,16 +717,16 @@ mod tests { ); let child1 = root_branch.children[1].as_ref().unwrap(); - let child1_node = child1 - .as_maybe_persisted_node() + let child1_maybe_persisted = child1.as_maybe_persisted_node(); + let child1_node = child1_maybe_persisted .as_shared_node(&committed_store) .unwrap(); assert_eq!(*child1_node.partial_path(), Path::from(&[1, 2, 3])); assert_eq!(child1_node.value(), Some(&b"value1"[..])); let child2 = root_branch.children[2].as_ref().unwrap(); - let child2_node = child2 - .as_maybe_persisted_node() + let child2_maybe_persisted = child2.as_maybe_persisted_node(); + let child2_node = child2_maybe_persisted .as_shared_node(&committed_store) .unwrap(); assert_eq!(*child2_node.partial_path(), Path::from(&[4, 5, 6])); From fe7c60f2a2470642fc92f65975fd39b6ebf7aebc Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:14:49 -0400 Subject: [PATCH 0972/1053] refactor(db): `TestDb` constructor (#1351) I recently learned from @demosdemon that in Rust, the default constructor of a type is generally called `new()` while additional constructors are called `with_X()`. This PR refactors the `TestDb` code to have a `new()` constructor with this function being called through the database tests instead of `testdb()`. This helps break up #1346 as we'll just then need to add a `with_mockstore()` method to `TestDb` to allow for injecting instances of `MockStore` to `TestDb`. --- firewood/src/db.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 6aa6fda503ca..503551024ceb 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -363,7 +363,7 @@ mod test { #[test] fn test_proposal_reads() { - let db = testdb(); + let db = TestDb::new(); let batch = vec![BatchOp::Put { key: b"k", value: b"v", @@ -388,7 +388,7 @@ mod test { #[test] fn reopen_test() { - let db = testdb(); + let db = TestDb::new(); let initial_root = db.root_hash().unwrap(); let batch = vec![ BatchOp::Put { @@ -422,7 +422,7 @@ mod test { // R1 --> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known fn test_proposal_scope_historic() { - let db = testdb(); + let db = TestDb::new(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", @@ -474,7 +474,7 @@ mod test { // \-> P2 - will get dropped // \-> P3 - will get orphaned, but it's still known fn test_proposal_scope_orphan() { - let db = testdb(); + let db = TestDb::new(); let batch1 = vec![BatchOp::Put { key: b"k1", value: b"v1", @@ -525,7 +525,7 @@ mod test { #[test] fn test_view_sync() { - let db = testdb(); + let db = TestDb::new(); // Create and commit some data to get a historical revision let batch = vec![BatchOp::Put { @@ -564,7 +564,7 @@ mod test { // number of keys and values to create for this test const N: usize = 20; - let db = testdb(); + let db = TestDb::new(); // create N keys and values like (key0, value0)..(keyN, valueN) let (keys, vals): (Vec<_>, Vec<_>) = (0..N) @@ -623,7 +623,7 @@ mod test { fn fuzz_checker() { let rng = firewood_storage::SeededRng::from_env_or_random(); - let db = testdb(); + let db = TestDb::new(); // takes about 0.3s on a mac to run 50 times for _ in 0..50 { @@ -670,7 +670,7 @@ mod test { const NUM_KEYS: NonZeroUsize = const { NonZeroUsize::new(2).unwrap() }; const NUM_PROPOSALS: usize = 100; - let db = testdb(); + let db = TestDb::new(); let ops = (0..(NUM_KEYS.get() * NUM_PROPOSALS)) .map(|i| (format!("key{i}"), format!("value{i}"))) @@ -723,7 +723,7 @@ mod test { const CHANNEL_CAPACITY: usize = 8; - let testdb = testdb(); + let testdb = TestDb::new(); let db = &testdb.db; let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); @@ -783,17 +783,17 @@ mod test { } } - fn testdb() -> TestDb { - let tmpdir = tempfile::tempdir().unwrap(); - let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] - .iter() - .collect(); - let dbconfig = DbConfig::builder().build(); - let db = Db::new(dbpath, dbconfig).unwrap(); - TestDb { db, tmpdir } - } - impl TestDb { + fn new() -> Self { + let tmpdir = tempfile::tempdir().unwrap(); + let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect(); + let dbconfig = DbConfig::builder().build(); + let db = Db::new(dbpath, dbconfig).unwrap(); + TestDb { db, tmpdir } + } + fn path(&self) -> PathBuf { [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() From 476c72c839d60e54143cd07b15bdedeb4d240e09 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Fri, 10 Oct 2025 13:17:24 -0400 Subject: [PATCH 0973/1053] feat(monitoring): Grafana automatic dashboard and auth provisioning (#1307) This PR modifies the Grafana installation script to automatically provision the datasource and a dashboard directory. It also modifies the dashboard file with a configurable datasource so it automatically picks up the available prometheus one. --- benchmark/Grafana-dashboard.json | 83 +++++++++------------- benchmark/README.md | 19 ++--- benchmark/setup-scripts/install-grafana.sh | 44 +++++++++++- 3 files changed, 83 insertions(+), 63 deletions(-) diff --git a/benchmark/Grafana-dashboard.json b/benchmark/Grafana-dashboard.json index b9d579af5e44..e29741a71941 100644 --- a/benchmark/Grafana-dashboard.json +++ b/benchmark/Grafana-dashboard.json @@ -1,35 +1,4 @@ { - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.5.1" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { @@ -68,7 +37,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -154,7 +123,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "disableTextWrap": false, "editorMode": "builder", @@ -174,7 +143,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -261,7 +230,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "disableTextWrap": false, "editorMode": "code", @@ -277,7 +246,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "editorMode": "code", "expr": "100 * sum(increase(firewood_cache_freelist{type=\"hit\"}[$__rate_interval])) by (name) / sum(increase(firewood_cache_freelist[$__rate_interval])) by (name)", @@ -307,7 +276,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -394,7 +363,7 @@ "refId": "A", "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" } } ], @@ -404,7 +373,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -491,7 +460,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "editorMode": "code", "expr": "irate(firewood_remove[$__rate_interval])", @@ -504,7 +473,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "editorMode": "code", "expr": "sum(irate(firewood_insert{merkle!=\"update\"}[$__rate_interval]))", @@ -517,7 +486,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "editorMode": "code", "expr": "irate(firewood_insert{merkle=\"update\"}[$__rate_interval])", @@ -547,7 +516,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -629,7 +598,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "disableTextWrap": false, "editorMode": "code", @@ -649,7 +618,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -732,7 +701,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "editorMode": "code", "expr": "node_memory_Dirty_bytes", @@ -748,7 +717,7 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" }, "fieldConfig": { "defaults": { @@ -839,7 +808,7 @@ "refId": "A", "datasource": { "type": "prometheus", - "uid": "${DS_PROMETHEUS}" + "uid": "${datasource}" } } ], @@ -851,7 +820,23 @@ "schemaVersion": 40, "tags": [], "templating": { - "list": [] + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus" + }, + "includeAll": false, + "label": "Data Source", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + } + ] }, "time": { "from": "now-1h", diff --git a/benchmark/README.md b/benchmark/README.md index 75d7ec80022a..453775c2530c 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -128,18 +128,13 @@ If you want to install grafana and prometheus on an AWS host (using Ubuntu as a 8. Launch the instance 3. ssh ubuntu@AWS-IP 4. Run the scripts as described above, including the grafana installation. -5. Log in to grafana on - a. username: admin, password: admin -6. When prompted, change the password (`firewood_is_fast`) -7. On the left panel, click "Data Sources" - a. Select "Prometheus" - b. For the URL, use - c. click "Save and test" -8. On the left panel, click Dashboards - a. On the right top pulldown, click New->Import - b. Import the dashboard from the Grafana-dashboard.json file - c. Set the data source to Prometheus -9. \[optional] Install a stock dashboard from [grafana](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) +5. Log in to Grafana at + a. Username: `admin`, password: `firewood_is_fast` +6. A Prometheus data source, the Firewood dashboard, and a [system statistics dashboard](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) are preconfigured and ready to use. + +### Updating the dashboard + +If you want to update the dashboard and commit it, do not enable "Export for sharing externally" when exporting. These dashboards are provisioned and reference the default Prometheus data source, with the option to pick from available data sources. ## Usage diff --git a/benchmark/setup-scripts/install-grafana.sh b/benchmark/setup-scripts/install-grafana.sh index 36e05c81e067..f3c5157aad4d 100644 --- a/benchmark/setup-scripts/install-grafana.sh +++ b/benchmark/setup-scripts/install-grafana.sh @@ -6,8 +6,8 @@ if ! [ -d /etc/apt/keyrings ]; then mkdir -p /etc/apt/keyrings/ fi if ! [ -f /etc/apt/keyrings/grafana.gpg ]; then - wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null - echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list + wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor > /etc/apt/keyrings/grafana.gpg + echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" >> /etc/apt/sources.list.d/grafana.list fi apt-get update @@ -46,6 +46,46 @@ if ! grep -q '^http_port = 80$' /etc/grafana/grafana.ini; then perl -pi -e 's/^;?http_port = .*/http_port = 80/' /etc/grafana/grafana.ini fi +# configure username and password +# TODO(amin): auto-generate some password for more security +# TODO(amin): another possible option here is enabling google oauth, and this could give access +# to anyone within our org emails +sed -i -E "s|^;?\s*admin_user\s*=.*|admin_user = admin|" /etc/grafana/grafana.ini +sed -i -E "s|^;?\s*admin_password\s*=.*|admin_password = firewood_is_fast|" /etc/grafana/grafana.ini + +# provision data source and dashboards +cat > /etc/grafana/provisioning/datasources/prometheus.yml < /etc/grafana/provisioning/dashboards/dashboards.yaml <> /etc/prometheus/prometheus.yml < Date: Fri, 10 Oct 2025 11:55:53 -0700 Subject: [PATCH 0974/1053] fix: explicitly release advisory lock on drop (#1352) This change ensures that the advisory lock is explicitly released when the `FileBacked` struct is dropped, preventing an issue where we would race on reopening the file and acquiring the lock again. Stress testing over 20 minutes has confirmed that this resolves the issue where previous attempts would trigger the bug within 2 minutes. Fixes: #1250 --- storage/src/linear/filebacked.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 71cdeb0bef4e..3474d21021aa 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -25,6 +25,8 @@ use std::fs::{File, OpenOptions}; use std::io::Read; +#[cfg(feature = "io-uring")] +use std::mem::ManuallyDrop; use std::num::NonZero; #[cfg(unix)] use std::os::unix::fs::FileExt; @@ -48,7 +50,32 @@ pub struct FileBacked { free_list_cache: Mutex>>, cache_read_strategy: CacheReadStrategy, #[cfg(feature = "io-uring")] - pub(crate) ring: Mutex, + pub(crate) ring: Mutex>, +} + +impl Drop for FileBacked { + fn drop(&mut self) { + #[cfg(feature = "io-uring")] + { + // non-blocking because we have mutable access to self + let ring = self + .ring + .get_mut() + .unwrap_or_else(std::sync::PoisonError::into_inner); + + // We are manually dropping the ring here to ensure that it is + // flushed and dropped before we unlock the file descriptor. + #[expect(unsafe_code)] + // SAFETY: this is the only time `ring` is dropped. Furthermore, + // `Drop` ensures that this is only ever called once in a context + // where we are guaranteed to never use `ring` again. + unsafe { + ManuallyDrop::drop(ring); + } + } + + _ = self.fd.unlock().ok(); + } } // Manual implementation since ring doesn't implement Debug :( @@ -149,7 +176,7 @@ impl FileBacked { cache_read_strategy, filename: path, #[cfg(feature = "io-uring")] - ring: ring.into(), + ring: Mutex::new(ManuallyDrop::new(ring)), }) } } From 16edd88e72a10ee91d5afc79739044df9ab180c3 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:33:38 -0400 Subject: [PATCH 0975/1053] chore(nodestore): remove empty committed err (#1353) While factoring out the `NodeStore` changes of `RootStore` into its own PR, I found that `new_empty_committed()` returns a `Result` type but always returns `Ok`. This PR simplifies `new_empty_committed()` by removing the `Result` return type. --- firewood/src/merkle/tests/mod.rs | 2 +- storage/src/checker/mod.rs | 16 ++++++++-------- storage/src/node/persist.rs | 4 ++-- storage/src/nodestore/alloc.rs | 6 +++--- storage/src/nodestore/mod.rs | 14 +++++--------- storage/src/nodestore/persist.rs | 6 +++--- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/firewood/src/merkle/tests/mod.rs b/firewood/src/merkle/tests/mod.rs index ce856a1cd10a..3863163ecfb1 100644 --- a/firewood/src/merkle/tests/mod.rs +++ b/firewood/src/merkle/tests/mod.rs @@ -52,7 +52,7 @@ where V: AsRef<[u8]>, { let memstore = Arc::new(MemStore::new(Vec::with_capacity(64 * 1024))); - let base = Merkle::from(NodeStore::new_empty_committed(memstore.clone()).unwrap()); + let base = Merkle::from(NodeStore::new_empty_committed(memstore.clone())); let mut merkle = base.fork().unwrap(); for (k, v) in iter.clone() { diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index d530808c2b91..4e6f19fb8e7d 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -995,7 +995,7 @@ mod test { // We use primitive calls here to do a low-level check. fn checker_traverse_correct_trie() { let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let test_trie = gen_test_trie(&mut nodestore); // let (_, high_watermark, (root_addr, root_hash)) = gen_test_trie(&mut nodestore); @@ -1019,7 +1019,7 @@ mod test { // This test permutes the simple trie with a wrong hash and checks that the checker detects it. fn checker_traverse_trie_with_wrong_hash() { let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let mut test_trie = gen_test_trie(&mut nodestore); let root_addr = test_trie.root_address; @@ -1088,7 +1088,7 @@ mod test { let rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); // write free areas let mut high_watermark = NodeStoreHeader::SIZE; @@ -1134,7 +1134,7 @@ mod test { #[test] fn traverse_freelist_should_skip_offspring_of_incorrect_areas() { let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let TestFreelist { high_watermark, free_ranges, @@ -1154,7 +1154,7 @@ mod test { #[test] fn fix_freelist_with_overlap() { let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let TestFreelist { high_watermark, free_ranges: _, @@ -1192,7 +1192,7 @@ mod test { let mut rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let num_areas = 10; @@ -1249,7 +1249,7 @@ mod test { #[expect(clippy::arithmetic_side_effects)] fn split_range_of_zeros_into_leaked_areas() { let memstore = MemStore::new(vec![]); - let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let nodestore = NodeStore::new_empty_committed(memstore.into()); let expected_leaked_area_indices = vec![ area_index!(8), @@ -1300,7 +1300,7 @@ mod test { #[expect(clippy::arithmetic_side_effects)] fn split_range_into_leaked_areas_test() { let memstore = MemStore::new(vec![]); - let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let nodestore = NodeStore::new_empty_committed(memstore.into()); // write two free areas let mut high_watermark = NodeStoreHeader::SIZE; diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index d335d2379d73..6e42e827c4c9 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -222,7 +222,7 @@ mod test { #[test] fn test_maybe_persisted_node() -> Result<(), FileIoError> { let mem_store = MemStore::new(vec![]).into(); - let store = NodeStore::new_empty_committed(mem_store)?; + let store = NodeStore::new_empty_committed(mem_store); let node = SharedNode::new(Node::Leaf(LeafNode { partial_path: Path::new(), value: vec![0].into(), @@ -253,7 +253,7 @@ mod test { #[test] fn test_clone_shares_underlying_shared_node() -> Result<(), FileIoError> { let mem_store = MemStore::new(vec![]).into(); - let store = NodeStore::new_empty_committed(mem_store)?; + let store = NodeStore::new_empty_committed(mem_store); let node = SharedNode::new(Node::Leaf(LeafNode { partial_path: Path::new(), value: vec![42].into(), diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index e34097b5b642..886b064d5303 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -688,7 +688,7 @@ mod tests { fn free_list_iterator() { let mut rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); - let nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let nodestore = NodeStore::new_empty_committed(memstore.into()); let area_index = rng.random_range(0..AreaIndex::NUM_AREA_SIZES as u8); let area_index_type = AreaIndex::try_from(area_index).unwrap(); @@ -743,7 +743,7 @@ mod tests { fn free_list_iter_with_metadata() { let rng = crate::SeededRng::from_env_or_random(); let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let mut free_lists = FreeLists::default(); let mut offset = NodeStoreHeader::SIZE; @@ -869,7 +869,7 @@ mod tests { const AREA_INDEX2_PLUS_1: AreaIndex = area_index!(6); let memstore = MemStore::new(vec![]); - let mut nodestore = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let mut nodestore = NodeStore::new_empty_committed(memstore.into()); let mut free_lists = FreeLists::default(); let mut offset = NodeStoreHeader::SIZE; diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 0afffb636b02..f29fcf408fc0 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -107,7 +107,7 @@ impl NodeStore { let mut header_bytes = vec![0u8; std::mem::size_of::()]; if let Err(e) = stream.read_exact(&mut header_bytes) { if e.kind() == std::io::ErrorKind::UnexpectedEof { - return Self::new_empty_committed(storage.clone()); + return Ok(Self::new_empty_committed(storage.clone())); } return Err(storage.file_io_error(e, 0, Some("header read".to_string()))); } @@ -139,21 +139,17 @@ impl NodeStore { /// Create a new, empty, Committed [`NodeStore`] and clobber /// the underlying store with an empty freelist and no root node - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if the storage cannot be accessed. - pub fn new_empty_committed(storage: Arc) -> Result { + pub fn new_empty_committed(storage: Arc) -> Self { let header = NodeStoreHeader::new(); - Ok(Self { + Self { header, storage, kind: Committed { deleted: Box::default(), root: None, }, - }) + } } } @@ -840,7 +836,7 @@ mod tests { fn test_reparent() { // create an empty base revision let memstore = MemStore::new(vec![]); - let base = NodeStore::new_empty_committed(memstore.into()).unwrap(); + let base = NodeStore::new_empty_committed(memstore.into()); // create an empty r1, check that it's parent is the empty committed version let r1 = NodeStore::new(&base).unwrap(); diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 489ff7bbd212..0961d1e1d2f6 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -678,7 +678,7 @@ mod tests { fn test_into_committed_with_generic_storage() { // Create a base committed store with MemStore let mem_store = MemStore::new(vec![]); - let base_committed = NodeStore::new_empty_committed(mem_store.into()).unwrap(); + let base_committed = NodeStore::new_empty_committed(mem_store.into()); // Create a mutable proposal from the base let mut mutable_store = NodeStore::new(&base_committed).unwrap(); @@ -756,14 +756,14 @@ mod tests { .unwrap(), ); - let mut ns = NodeStore::new_empty_committed(fb.clone()).unwrap(); + let mut ns = NodeStore::new_empty_committed(fb.clone()); assert!(ns.downcast_to_file_backed().is_some()); } { let ms = Arc::new(MemStore::new(vec![])); - let mut ns = NodeStore::new_empty_committed(ms.clone()).unwrap(); + let mut ns = NodeStore::new_empty_committed(ms.clone()); assert!(ns.downcast_to_file_backed().is_none()); } } From 5515b0cc658e0649308eb08a60931a676680d78a Mon Sep 17 00:00:00 2001 From: AminR443 Date: Fri, 10 Oct 2025 16:20:47 -0400 Subject: [PATCH 0976/1053] feat(ffi): implement revision handles and expose to FFI layer (#1326) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces revision handles that hold a revision across the FFI while you use it, so it won’t be removed mid-operation. This should improve concurrency and avoids errors as mentioned in #1008 . It also makes iterator's design simpler, we can create them from a held RevisionHandle with no tricky lifetimes. This updates the existing Go bindings so `Database.Revision` returns a Revision with `Get` and `Drop` methods. Necessary changes to tests are applied. Closes #1008. --- ffi/firewood.go | 13 ++-- ffi/firewood.h | 133 +++++++++++++++++++++++++++++++++++++++ ffi/firewood_test.go | 92 ++++++++++++++++++++++++++- ffi/revision.go | 72 ++++++++++++++++++++- ffi/src/handle.rs | 16 +++++ ffi/src/lib.rs | 86 +++++++++++++++++++++++++ ffi/src/revision.rs | 62 ++++++++++++++++++ ffi/src/value.rs | 2 +- ffi/src/value/results.rs | 53 ++++++++++++++++ 9 files changed, 518 insertions(+), 11 deletions(-) create mode 100644 ffi/src/revision.rs diff --git a/ffi/firewood.go b/ffi/firewood.go index 66b7240d0f6f..06f3188e2863 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -220,15 +220,18 @@ func (db *Database) Revision(root []byte) (*Revision, error) { return nil, errInvalidRootLength } - // Attempt to get any value from the root. - // This will verify that the root is valid and accessible. - // If the root is not valid, this will return an error. - _, err := db.GetFromRoot(root, []byte{}) + var pinner runtime.Pinner + defer pinner.Unpin() + + rev, err := getRevisionFromResult(C.fwd_get_revision( + db.handle, + newBorrowedBytes(root, &pinner), + ), db) if err != nil { return nil, err } - return &Revision{database: db, root: root}, nil + return rev, nil } // Close releases the memory associated with the Database. diff --git a/ffi/firewood.h b/ffi/firewood.h index cb13778d1932..5f5c285eb019 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -35,6 +35,8 @@ typedef struct ProposalHandle ProposalHandle; */ typedef struct RangeProofContext RangeProofContext; +typedef struct RevisionHandle RevisionHandle; + /** * A database hash key, used in FFI functions that require hashes. * This type requires no allocation and can be copied freely and @@ -645,6 +647,62 @@ typedef struct VerifyRangeProofArgs { uint32_t max_length; } VerifyRangeProofArgs; +/** + * A result type returned from FFI functions that get a revision + */ +typedef enum RevisionResult_Tag { + /** + * The caller provided a null pointer to a database handle. + */ + RevisionResult_NullHandlePointer, + /** + * The provided root was not found in the database. + */ + RevisionResult_RevisionNotFound, + /** + * Getting the revision was successful and the revision handle and root + * hash are returned. + */ + RevisionResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. The + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + RevisionResult_Err, +} RevisionResult_Tag; + +typedef struct RevisionResult_Ok_Body { + /** + * An opaque pointer to the [`RevisionHandle`]. + * The value should be freed with [`fwd_free_revision`] + * + * [`fwd_free_revision`]: crate::fwd_free_revision + */ + struct RevisionHandle *handle; + /** + * The root hash of the revision. + */ + struct HashKey root_hash; +} RevisionResult_Ok_Body; + +typedef struct RevisionResult { + RevisionResult_Tag tag; + union { + struct { + struct HashKey revision_not_found; + }; + RevisionResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} RevisionResult; + /** * The result type returned from the open or create database functions. */ @@ -919,6 +977,9 @@ struct ValueResult fwd_change_proof_to_bytes(const struct ChangeProofContext *_p * - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. * - There are no handles to any open proposals. If so, they must be freed first * using [`fwd_free_proposal`]. + * - Freeing the database handle does not free outstanding [`RevisionHandle`]s + * returned by [`fwd_get_revision`]. To prevent leaks, free them separately + * with [`fwd_free_revision`]. * - The database handle is not used after this function is called. */ struct VoidResult fwd_close_db(struct DatabaseHandle *db); @@ -1190,6 +1251,27 @@ struct VoidResult fwd_free_proposal(struct ProposalHandle *proposal); */ struct VoidResult fwd_free_range_proof(struct RangeProofContext *proof); +/** + * Consumes the [`RevisionHandle`] and frees the memory associated with it. + * + * # Arguments + * + * * `revision` - A pointer to a [`RevisionHandle`] previously returned by + * [`fwd_get_revision`]. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the provided revision handle is null. + * - [`VoidResult::Ok`] if the revision handle was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the revision handle is valid and is not used again after + * this function is called. + */ +struct VoidResult fwd_free_revision(struct RevisionHandle *revision); + /** * Gather latest metrics for this process. * @@ -1234,6 +1316,31 @@ struct ValueResult fwd_gather(void); */ struct ValueResult fwd_get_from_proposal(const struct ProposalHandle *handle, BorrowedBytes key); +/** + * Gets the value associated with the given key from the provided revision handle. + * + * # Arguments + * + * * `revision` - The revision handle returned by [`fwd_get_revision`]. + * * `key` - The key to look up as a [`BorrowedBytes`]. + * + * # Returns + * + * - [`ValueResult::NullHandlePointer`] if the provided revision handle is null. + * - [`ValueResult::None`] if the key was not found in the revision. + * - [`ValueResult::Some`] if the key was found with the associated value. + * - [`ValueResult::Err`] if an error occurred while retrieving the value. + * + * # Safety + * + * The caller must: + * * ensure that `revision` is a valid pointer to a [`RevisionHandle`]. + * * ensure that `key` is valid for [`BorrowedBytes`]. + * * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`] + * returned in the result. + */ +struct ValueResult fwd_get_from_revision(const struct RevisionHandle *revision, BorrowedBytes key); + /** * Gets a value assoicated with the given root hash and key. * @@ -1296,6 +1403,32 @@ struct ValueResult fwd_get_from_root(const struct DatabaseHandle *db, */ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes key); +/** + * Gets a handle to the revision identified by the provided root hash. + * + * # Arguments + * + * * `db` - The database handle returned by [`fwd_open_db`]. + * * `root` - The hash of the revision as a [`BorrowedBytes`]. + * + * # Returns + * + * - [`RevisionResult::NullHandlePointer`] if the provided database handle is null. + * - [`RevisionResult::Ok`] containing a [`RevisionHandle`] and root hash if the revision exists. + * - [`RevisionResult::Err`] if the revision cannot be fetched or the root hash is invalid. + * + * # Safety + * + * The caller must: + * * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. + * * ensure that `root` is valid for [`BorrowedBytes`]. + * * call [`fwd_free_revision`] to free the returned handle when it is no longer needed. + * + * [`BorrowedBytes`]: crate::value::BorrowedBytes + * [`RevisionHandle`]: crate::revision::RevisionHandle + */ +struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); + /** * Open a database with the given arguments. * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 61816bfb9d39..76356952a9bb 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -126,12 +126,12 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func newTestDatabase(t *testing.T) *Database { +func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database { t.Helper() r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") - db, closeDB, err := newDatabase(dbFile) + db, closeDB, err := newDatabase(dbFile, configureFns...) r.NoError(err) t.Cleanup(func() { r.NoError(closeDB()) @@ -139,9 +139,12 @@ func newTestDatabase(t *testing.T) *Database { return db } -func newDatabase(dbFile string) (*Database, func() error, error) { +func newDatabase(dbFile string, configureFns ...func(*Config)) (*Database, func() error, error) { conf := DefaultConfig() conf.Truncate = true // in tests, we use filepath.Join, which creates an empty file + for _, fn := range configureFns { + fn(conf) + } f, err := New(dbFile, conf) if err != nil { @@ -783,6 +786,9 @@ func TestRevision(t *testing.T) { // Create a revision from this root. revision, err := db.Revision(root) r.NoError(err) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Check that all keys can be retrieved from the revision. for i := range keys { @@ -807,6 +813,10 @@ func TestRevision(t *testing.T) { // Create a "new" revision from the first old root. revision, err = db.Revision(root) r.NoError(err) + r.Equal(revision.Root(), root) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Check that all keys can be retrieved from the revision. for i := range keys { got, err := revision.Get(keys[i]) @@ -815,6 +825,79 @@ func TestRevision(t *testing.T) { } } +// Tests that even if a proposal is committed, the corresponding revision will not go away +// as we're holding on to it +func TestRevisionOutlivesProposal(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(20) + _, err := db.Update(keys[:10], vals[:10]) + r.NoError(err) + + // Create a proposal with 10 key-value pairs. + nKeys, nVals := keys[10:], vals[10:] + proposal, err := db.Propose(nKeys, nVals) + r.NoError(err) + root, err := proposal.Root() + r.NoError(err) + + rev, err := db.Revision(root) + r.NoError(err) + + // we drop the proposal + r.NoError(proposal.Drop()) + + // revision should outlive the proposal, as we're still referencing its node store + for i, key := range nKeys { + val, err := rev.Get(key) + r.NoError(err) + r.Equal(val, nVals[i]) + } + + r.NoError(rev.Drop()) +} + +// Tests that holding a reference to revision will prevent from it being reaped +func TestRevisionOutlivesReaping(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t, func(config *Config) { + config.Revisions = 2 + }) + + keys, vals := kvForTest(40) + firstRoot, err := db.Update(keys[:10], vals[:10]) + r.NoError(err) + // let's get a revision at root + rev, err := db.Revision(firstRoot) + r.NoError(err) + + // commit two times, this would normally reap the first revision + secondRoot, err := db.Update(keys[10:20], vals[10:20]) + r.NoError(err) + _, err = db.Update(keys[20:30], vals[20:30]) + r.NoError(err) + + // revision should be still accessible, as we're hanging on to it, prevent reaping + nKeys, nVals := keys[:10], vals[:10] + for i, key := range nKeys { + val, err := rev.Get(key) + r.NoError(err) + r.Equal(val, nVals[i]) + } + r.NoError(rev.Drop()) + + // since we dropped the revision, if we commit, reaping will happen (cleaning first two revisions) + _, err = db.Update(keys[30:], vals[30:]) + r.NoError(err) + + _, err = db.Revision(firstRoot) + r.Error(err) + + _, err = db.Revision(secondRoot) + r.Error(err) +} + func TestInvalidRevision(t *testing.T) { r := require.New(t) db := newTestDatabase(t) @@ -850,6 +933,9 @@ func TestGetNilCases(t *testing.T) { r.NoError(err) revision, err := db.Revision(root) r.NoError(err) + t.Cleanup(func() { + r.NoError(revision.Drop()) + }) // Create edge case keys. specialKeys := [][]byte{ diff --git a/ffi/revision.go b/ffi/revision.go index 6e5dd2fb083c..e884a43a883f 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -13,19 +13,87 @@ import "C" import ( "errors" "fmt" + "runtime" + "unsafe" ) var ( errRevisionNotFound = errors.New("revision not found") errInvalidRootLength = fmt.Errorf("root hash must be %d bytes", RootLength) + errDroppedRevision = errors.New("revision already dropped") ) +// Revision is an immutable view over the database at a specific root hash. +// Instances are created via Database.Revision, provide read-only access to the revision, +// and must be released with Drop when no longer needed. type Revision struct { - database *Database + // The database this revision is associated with. Holding this ensures + // the DB outlives the revision for cleanup ordering. + db *Database + handle *C.RevisionHandle // The revision root root []byte } +// Get reads the value stored at the provided key within the revision. +// +// It returns errDroppedRevision if Drop has already been called and the underlying +// handle is no longer available. func (r *Revision) Get(key []byte) ([]byte, error) { - return r.database.GetFromRoot(r.root, key) + if r.handle == nil { + return nil, errDroppedRevision + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + return getValueFromValueResult(C.fwd_get_from_revision( + r.handle, + newBorrowedBytes(key, &pinner), + )) +} + +// Drop releases the resources backed by the revision handle. +// +// It is safe to call Drop multiple times; subsequent calls after the first are no-ops. +func (r *Revision) Drop() error { + if r.handle == nil { + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_revision(r.handle)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + + r.handle = nil // Prevent double free + + return nil +} + +func (r *Revision) Root() []byte { + return r.root +} + +// getRevisionFromResult converts a C.RevisionResult to a Revision or error. +func getRevisionFromResult(result C.RevisionResult, db *Database) (*Revision, error) { + switch result.tag { + case C.RevisionResult_NullHandlePointer: + return nil, errDBClosed + case C.RevisionResult_RevisionNotFound: + return nil, errRevisionNotFound + case C.RevisionResult_Ok: + body := (*C.RevisionResult_Ok_Body)(unsafe.Pointer(&result.anon0)) + hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) + rev := &Revision{ + db: db, + handle: body.handle, + root: hashKey[:], + } + return rev, nil + case C.RevisionResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.RevisionResult tag: %d", result.tag) + } } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index f03fa26217e5..b4061978b9ae 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -9,6 +9,7 @@ use firewood::{ use crate::{BorrowedBytes, CView, CreateProposalResult, KeyValuePair, arc_cache::ArcCache}; +use crate::revision::{GetRevisionResult, RevisionHandle}; use metrics::counter; /// Arguments for creating or opening a database. These are passed to [`fwd_open_db`] @@ -176,6 +177,21 @@ impl DatabaseHandle { Ok(root_hash) } + /// Returns an owned handle to the revision corresponding to the provided root hash. + /// + /// # Errors + /// + /// Returns an error if could not get the view from underlying database for the specified + /// root hash, for example when the revision does not exist or an I/O error occurs while + /// accessing the database. + pub fn get_revision(&self, root: HashKey) -> Result { + let view = self.db.view(root.clone())?; + Ok(GetRevisionResult { + handle: RevisionHandle::new(view), + root_hash: root, + }) + } + pub(crate) fn get_root(&self, root: HashKey) -> Result { let mut cache_miss = false; let view = self.cached_view.get_or_try_insert_with(root, |key| { diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index cb63d037b1cc..8bd2e23aae2d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -28,6 +28,7 @@ mod logging; mod metrics_setup; mod proofs; mod proposal; +mod revision; mod value; use firewood::v2::api::DbView; @@ -36,6 +37,7 @@ pub use crate::handle::*; pub use crate::logging::*; pub use crate::proofs::*; pub use crate::proposal::*; +pub use crate::revision::*; pub use crate::value::*; #[cfg(unix)] @@ -107,6 +109,87 @@ pub unsafe extern "C" fn fwd_get_latest( invoke_with_handle(db, move |db| db.get_latest(key)) } +/// Gets a handle to the revision identified by the provided root hash. +/// +/// # Arguments +/// +/// * `db` - The database handle returned by [`fwd_open_db`]. +/// * `root` - The hash of the revision as a [`BorrowedBytes`]. +/// +/// # Returns +/// +/// - [`RevisionResult::NullHandlePointer`] if the provided database handle is null. +/// - [`RevisionResult::Ok`] containing a [`RevisionHandle`] and root hash if the revision exists. +/// - [`RevisionResult::Err`] if the revision cannot be fetched or the root hash is invalid. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `db` is a valid pointer to a [`DatabaseHandle`]. +/// * ensure that `root` is valid for [`BorrowedBytes`]. +/// * call [`fwd_free_revision`] to free the returned handle when it is no longer needed. +/// +/// [`BorrowedBytes`]: crate::value::BorrowedBytes +/// [`RevisionHandle`]: crate::revision::RevisionHandle +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_revision( + db: Option<&DatabaseHandle>, + root: BorrowedBytes, +) -> RevisionResult { + invoke_with_handle(db, move |db| db.get_revision(root.as_ref().try_into()?)) +} + +/// Gets the value associated with the given key from the provided revision handle. +/// +/// # Arguments +/// +/// * `revision` - The revision handle returned by [`fwd_get_revision`]. +/// * `key` - The key to look up as a [`BorrowedBytes`]. +/// +/// # Returns +/// +/// - [`ValueResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`ValueResult::None`] if the key was not found in the revision. +/// - [`ValueResult::Some`] if the key was found with the associated value. +/// - [`ValueResult::Err`] if an error occurred while retrieving the value. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`]. +/// * ensure that `key` is valid for [`BorrowedBytes`]. +/// * call [`fwd_free_owned_bytes`] to free the memory associated with the [`OwnedBytes`] +/// returned in the result. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_get_from_revision( + revision: Option<&RevisionHandle>, + key: BorrowedBytes, +) -> ValueResult { + invoke_with_handle(revision, move |rev| rev.val(key)) +} + +/// Consumes the [`RevisionHandle`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `revision` - A pointer to a [`RevisionHandle`] previously returned by +/// [`fwd_get_revision`]. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`VoidResult::Ok`] if the revision handle was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the revision handle is valid and is not used again after +/// this function is called. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_revision(revision: Option>) -> VoidResult { + invoke_with_handle(revision, drop) +} + /// Gets the value associated with the given key from the proposal provided. /// /// # Arguments @@ -468,6 +551,9 @@ pub extern "C" fn fwd_start_logs(args: LogArgs) -> VoidResult { /// - `db` is a valid pointer to a [`DatabaseHandle`] returned by [`fwd_open_db`]. /// - There are no handles to any open proposals. If so, they must be freed first /// using [`fwd_free_proposal`]. +/// - Freeing the database handle does not free outstanding [`RevisionHandle`]s +/// returned by [`fwd_get_revision`]. To prevent leaks, free them separately +/// with [`fwd_free_revision`]. /// - The database handle is not used after this function is called. #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_close_db(db: Option>) -> VoidResult { diff --git a/ffi/src/revision.rs b/ffi/src/revision.rs new file mode 100644 index 000000000000..ee23014f63d3 --- /dev/null +++ b/ffi/src/revision.rs @@ -0,0 +1,62 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood::v2::api; +use firewood::v2::api::{ArcDynDbView, BoxKeyValueIter, DbView, HashKey}; + +#[derive(Debug)] +pub struct RevisionHandle { + view: ArcDynDbView, +} + +impl RevisionHandle { + /// Creates a new revision handle for the provided database view. + pub(crate) fn new(view: ArcDynDbView) -> RevisionHandle { + RevisionHandle { view } + } +} + +impl DbView for RevisionHandle { + type Iter<'view> + = BoxKeyValueIter<'view> + where + Self: 'view; + + fn root_hash(&self) -> Result, api::Error> { + self.view.root_hash() + } + + fn val(&self, key: K) -> Result, api::Error> { + self.view.val(key.as_ref()) + } + + fn single_key_proof(&self, key: K) -> Result { + self.view.single_key_proof(key.as_ref()) + } + + fn range_proof( + &self, + first_key: Option, + last_key: Option, + limit: Option, + ) -> Result { + self.view.range_proof( + first_key.as_ref().map(AsRef::as_ref), + last_key.as_ref().map(AsRef::as_ref), + limit, + ) + } + + fn iter_option( + &self, + first_key: Option, + ) -> Result, api::Error> { + self.view.iter_option(first_key.as_ref().map(AsRef::as_ref)) + } +} + +#[derive(Debug)] +pub struct GetRevisionResult { + pub handle: RevisionHandle, + pub root_hash: HashKey, +} diff --git a/ffi/src/value.rs b/ffi/src/value.rs index f2cb160e0b72..39894b9a4864 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -16,7 +16,7 @@ pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, ProposalResult, - RangeProofResult, ValueResult, VoidResult, + RangeProofResult, RevisionResult, ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index edc52a1801eb..139ad9ae441e 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -5,6 +5,7 @@ use std::fmt; use firewood::v2::api; +use crate::revision::{GetRevisionResult, RevisionHandle}; use crate::{ ChangeProofContext, CreateProposalResult, HashKey, NextKeyRange, OwnedBytes, ProposalHandle, RangeProofContext, @@ -306,6 +307,56 @@ pub enum ProposalResult<'db> { Err(OwnedBytes), } +/// A result type returned from FFI functions that get a revision +#[derive(Debug)] +#[repr(C)] +pub enum RevisionResult { + /// The caller provided a null pointer to a database handle. + NullHandlePointer, + /// The provided root was not found in the database. + RevisionNotFound(HashKey), + /// Getting the revision was successful and the revision handle and root + /// hash are returned. + Ok { + /// An opaque pointer to the [`RevisionHandle`]. + /// The value should be freed with [`fwd_free_revision`] + /// + /// [`fwd_free_revision`]: crate::fwd_free_revision + handle: Box, + /// The root hash of the revision. + root_hash: HashKey, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. The + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From for RevisionResult { + fn from(value: GetRevisionResult) -> Self { + RevisionResult::Ok { + handle: Box::new(value.handle), + root_hash: HashKey::from(value.root_hash), + } + } +} + +impl From> for RevisionResult { + fn from(value: Result) -> Self { + match value { + Ok(res) => res.into(), + Err(api::Error::RevisionNotFound { provided }) => RevisionResult::RevisionNotFound( + HashKey::from(provided.unwrap_or_else(api::HashKey::empty)), + ), + Err(err) => RevisionResult::Err(err.to_string().into_bytes().into()), + } + } +} + impl<'db, E: fmt::Display> From, E>> for ProposalResult<'db> { fn from(value: Result, E>) -> Self { match value { @@ -383,6 +434,7 @@ impl_null_handle_result!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + RevisionResult, ); impl_cresult!( @@ -394,6 +446,7 @@ impl_cresult!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + RevisionResult, ); enum Panic { From e44391cb2d4e9b63571e04d6518f00b2c3f90d96 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Fri, 10 Oct 2025 17:01:15 -0400 Subject: [PATCH 0977/1053] feat(ffi-iterator): Implementation of Iterator in Rust (1/4) (#1255) This PR is a step towards implementing the FFI iterator mentioned in #1009. This implement FFI functions (`fwd_iter_on_root`, `fwd_iter_on_proposal`, `fwd_iter_next`) in Rust, and also necessary changes in `firewood` crate for `MerkleKeyValueStream` to work with `Arc>` as well. --------- Co-authored-by: Brandon LeBlanc --- Cargo.lock | 1 + ffi/Cargo.toml | 1 + ffi/firewood.h | 221 +++++++++++++++++++++++++++++++++++++++ ffi/src/iterator.rs | 35 +++++++ ffi/src/lib.rs | 131 +++++++++++++++++++++++ ffi/src/proposal.rs | 14 ++- ffi/src/revision.rs | 12 +++ ffi/src/value.rs | 6 +- ffi/src/value/kvp.rs | 24 ++++- ffi/src/value/results.rs | 89 +++++++++++++++- 10 files changed, 523 insertions(+), 11 deletions(-) create mode 100644 ffi/src/iterator.rs diff --git a/Cargo.lock b/Cargo.lock index 3c3797bc9fdc..ca36a178bb1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1088,6 +1088,7 @@ dependencies = [ "cbindgen", "chrono", "coarsetime", + "derive-where", "env_logger", "firewood", "firewood-storage", diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index af176c996603..6e6db493ae61 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -31,6 +31,7 @@ chrono = "0.4.42" oxhttp = "0.3.1" # Optional dependencies env_logger = { workspace = true, optional = true } +derive-where = "1.6.0" [target.'cfg(unix)'.dependencies] tikv-jemallocator = "0.6.0" diff --git a/ffi/firewood.h b/ffi/firewood.h index 5f5c285eb019..88c8ae1ebf84 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -24,6 +24,11 @@ typedef struct ChangeProofContext ChangeProofContext; */ typedef struct DatabaseHandle DatabaseHandle; +/** + * An opaque wrapper around a [`BoxKeyValueIter`]. + */ +typedef struct IteratorHandle IteratorHandle; + /** * An opaque wrapper around a Proposal that also retains a reference to the * database handle it was created from. @@ -647,6 +652,17 @@ typedef struct VerifyRangeProofArgs { uint32_t max_length; } VerifyRangeProofArgs; +/** + * Owned version of `KeyValuePair`, returned to ffi callers. + * + * C callers must free this using [`crate::fwd_free_owned_kv_pair`], + * not the C standard library's `free` function. + */ +typedef struct OwnedKeyValuePair { + OwnedBytes key; + OwnedBytes value; +} OwnedKeyValuePair; + /** * A result type returned from FFI functions that get a revision */ @@ -703,6 +719,94 @@ typedef struct RevisionResult { }; } RevisionResult; +/** + * A result type returned from iterator FFI functions + */ +typedef enum KeyValueResult_Tag { + /** + * The caller provided a null pointer to an iterator handle. + */ + KeyValueResult_NullHandlePointer, + /** + * The iterator is exhausted + */ + KeyValueResult_None, + /** + * The next item is returned. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with the key and the value of this pair. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. The + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueResult_Err, +} KeyValueResult_Tag; + +typedef struct KeyValueResult { + KeyValueResult_Tag tag; + union { + struct { + struct OwnedKeyValuePair some; + }; + struct { + OwnedBytes err; + }; + }; +} KeyValueResult; + +/** + * A result type returned from FFI functions that create an iterator + */ +typedef enum IteratorResult_Tag { + /** + * The caller provided a null pointer to a revision/proposal handle. + */ + IteratorResult_NullHandlePointer, + /** + * Building the iterator was successful and the iterator handle is returned + */ + IteratorResult_Ok, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + IteratorResult_Err, +} IteratorResult_Tag; + +typedef struct IteratorResult_Ok_Body { + /** + * An opaque pointer to the [`IteratorHandle`]. + * The value should be freed with [`fwd_free_iterator`] + * + * [`fwd_free_iterator`]: crate::fwd_free_iterator + */ + struct IteratorHandle *handle; +} IteratorResult_Ok_Body; + +typedef struct IteratorResult { + IteratorResult_Tag tag; + union { + IteratorResult_Ok_Body ok; + struct { + OwnedBytes err; + }; + }; +} IteratorResult; + /** * The result type returned from the open or create database functions. */ @@ -1192,6 +1296,28 @@ struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, */ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); +/** + * Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. + * + * # Arguments + * + * * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a + * function from this library. + * + * # Returns + * + * - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`VoidResult::Ok`] if the iterator was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `iterator` is not null and that it points to + * a valid [`IteratorHandle`] previously returned by a function from this library. + * + */ +struct VoidResult fwd_free_iterator(struct IteratorHandle *iterator); + /** * Consumes the [`OwnedBytes`] and frees the memory associated with it. * @@ -1213,6 +1339,25 @@ struct VoidResult fwd_free_change_proof(struct ChangeProofContext *proof); */ struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); +/** + * Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. + * + * # Arguments + * + * * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any + * function from this library. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `kv` struct is valid. + */ +struct VoidResult fwd_free_owned_kv_pair(struct OwnedKeyValuePair kv); + /** * Consumes the [`ProposalHandle`], cancels the proposal, and frees the memory. * @@ -1429,6 +1574,82 @@ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes */ struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); +/** + * Retrieves the next item from the iterator + * + * # Arguments + * + * * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or + * [`fwd_iter_on_proposal`]. + * + * # Returns + * + * - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. + * - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated + * key value pair. + * - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. + * * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] + * to free the memory associated with the returned error or value. + * + */ +struct KeyValueResult fwd_iter_next(struct IteratorHandle *handle); + +/** + * Returns an iterator on the provided proposal optionally starting from a key + * + * # Arguments + * + * * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or + * [`fwd_propose_on_proposal`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`ProposalHandle`] + * * ensure that `key` is a valid for [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_proposal(const struct ProposalHandle *handle, BorrowedBytes key); + +/** + * Returns an iterator optionally starting from a key in the provided revision. + * + * # Arguments + * + * * `revision` - The revision handle returned by [`fwd_get_revision`]. + * * `key` - The key to look up as a [`BorrowedBytes`] + * + * # Returns + * + * - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. + * - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. + * - [`IteratorResult::Err`] if an error occurred while creating the iterator. + * + * # Safety + * + * The caller must: + * * ensure that `revision` is a valid pointer to a [`RevisionHandle`] + * * ensure that `key` is a valid [`BorrowedBytes`] + * * call [`fwd_free_iterator`] to free the memory associated with the iterator. + * + */ +struct IteratorResult fwd_iter_on_revision(const struct RevisionHandle *revision, + BorrowedBytes key); + /** * Open a database with the given arguments. * diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs new file mode 100644 index 000000000000..487caf1cb6a4 --- /dev/null +++ b/ffi/src/iterator.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::ops::{Deref, DerefMut}; + +use derive_where::derive_where; +use firewood::v2::api::BoxKeyValueIter; + +/// An opaque wrapper around a [`BoxKeyValueIter`]. +#[derive_where(Debug)] +#[derive_where(skip_inner)] +pub struct IteratorHandle<'view>(BoxKeyValueIter<'view>); + +impl<'view> From> for IteratorHandle<'view> { + fn from(value: BoxKeyValueIter<'view>) -> Self { + IteratorHandle(value) + } +} + +impl<'view> Deref for IteratorHandle<'view> { + type Target = BoxKeyValueIter<'view>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for IteratorHandle<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Debug)] +pub struct CreateIteratorResult<'db>(pub IteratorHandle<'db>); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8bd2e23aae2d..4d54bdb7e9f0 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -24,6 +24,7 @@ mod arc_cache; mod handle; +mod iterator; mod logging; mod metrics_setup; mod proofs; @@ -34,6 +35,7 @@ mod value; use firewood::v2::api::DbView; pub use crate::handle::*; +pub use crate::iterator::*; pub use crate::logging::*; pub use crate::proofs::*; pub use crate::proposal::*; @@ -109,6 +111,115 @@ pub unsafe extern "C" fn fwd_get_latest( invoke_with_handle(db, move |db| db.get_latest(key)) } +/// Returns an iterator optionally starting from a key in the provided revision. +/// +/// # Arguments +/// +/// * `revision` - The revision handle returned by [`fwd_get_revision`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided revision handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `revision` is a valid pointer to a [`RevisionHandle`] +/// * ensure that `key` is a valid [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_revision<'view>( + revision: Option<&'view RevisionHandle>, + key: BorrowedBytes, +) -> IteratorResult<'view> { + invoke_with_handle(revision, move |rev| rev.iter_from(Some(key.as_slice()))) +} + +/// Returns an iterator on the provided proposal optionally starting from a key +/// +/// # Arguments +/// +/// * `handle` - The proposal handle returned by [`fwd_propose_on_db`] or +/// [`fwd_propose_on_proposal`]. +/// * `key` - The key to look up as a [`BorrowedBytes`] +/// +/// # Returns +/// +/// - [`IteratorResult::NullHandlePointer`] if the provided proposal handle is null. +/// - [`IteratorResult::Ok`] if the iterator was created, with the iterator handle. +/// - [`IteratorResult::Err`] if an error occurred while creating the iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`ProposalHandle`] +/// * ensure that `key` is a valid for [`BorrowedBytes`] +/// * call [`fwd_free_iterator`] to free the memory associated with the iterator. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( + handle: Option<&'p ProposalHandle<'_>>, + key: BorrowedBytes, +) -> IteratorResult<'p> { + invoke_with_handle(handle, move |p| p.iter_from(Some(key.as_slice()))) +} + +/// Retrieves the next item from the iterator +/// +/// # Arguments +/// +/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or +/// [`fwd_iter_on_proposal`]. +/// +/// # Returns +/// +/// - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. +/// - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated +/// key value pair. +/// - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. +/// * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] +/// to free the memory associated with the returned error or value. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult { + invoke_with_handle(handle, |it| it.next()) +} + +/// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. +/// +/// # Arguments +/// +/// * `iterator` - A pointer to a [`IteratorHandle`] previously returned from a +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`VoidResult::Ok`] if the iterator was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `iterator` is not null and that it points to +/// a valid [`IteratorHandle`] previously returned by a function from this library. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_iterator( + iterator: Option>>, +) -> VoidResult { + invoke_with_handle(iterator, drop) +} + /// Gets a handle to the revision identified by the provided root hash. /// /// # Arguments @@ -581,3 +692,23 @@ pub unsafe extern "C" fn fwd_close_db(db: Option>) -> VoidRe pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { invoke(move || drop(bytes)) } + +/// Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `kv` - The [`OwnedKeyValuePair`] struct to free, previously returned from any +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `kv` struct is valid. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_kv_pair(kv: OwnedKeyValuePair) -> VoidResult { + invoke(move || drop(kv)) +} diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 2c7dcdad651d..1281a9993539 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,10 +1,11 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, DbView, HashKey, Proposal as _}; +use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; use crate::value::KeyValuePair; +use crate::iterator::CreateIteratorResult; use metrics::counter; /// An opaque wrapper around a Proposal that also retains a reference to the @@ -97,8 +98,17 @@ impl ProposalHandle<'_> { Ok(hash_key) } -} + /// Creates an iterator on the proposal starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult((Box::new(it) as BoxKeyValueIter<'_>).into()) + } +} #[derive(Debug)] pub struct CreateProposalResult<'db> { pub handle: ProposalHandle<'db>, diff --git a/ffi/src/revision.rs b/ffi/src/revision.rs index ee23014f63d3..eb73f15e12cb 100644 --- a/ffi/src/revision.rs +++ b/ffi/src/revision.rs @@ -1,6 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use crate::CreateIteratorResult; use firewood::v2::api; use firewood::v2::api::{ArcDynDbView, BoxKeyValueIter, DbView, HashKey}; @@ -14,6 +15,17 @@ impl RevisionHandle { pub(crate) fn new(view: ArcDynDbView) -> RevisionHandle { RevisionHandle { view } } + + /// Creates an iterator on the revision starting from the given key. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> { + let it = self + .view + .iter_option(first_key) + .expect("infallible; see issue #1329"); + CreateIteratorResult(it.into()) + } } impl DbView for RevisionHandle { diff --git a/ffi/src/value.rs b/ffi/src/value.rs index 39894b9a4864..f9f5ee7f3e6f 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -11,12 +11,12 @@ mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::hash_key::HashKey; -pub use self::kvp::KeyValuePair; +pub use self::kvp::{KeyValuePair, OwnedKeyValuePair}; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ - ChangeProofResult, HandleResult, HashResult, NextKeyRangeResult, ProposalResult, - RangeProofResult, RevisionResult, ValueResult, VoidResult, + ChangeProofResult, HandleResult, HashResult, IteratorResult, KeyValueResult, + NextKeyRangeResult, ProposalResult, RangeProofResult, RevisionResult, ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index 98471147ae35..c5255cc404d2 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -3,9 +3,9 @@ use std::fmt; -use firewood::v2::api; - +use crate::OwnedBytes; use crate::value::BorrowedBytes; +use firewood::v2::api; /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] @@ -61,3 +61,23 @@ impl<'a> api::KeyValuePair for &KeyValuePair<'a> { (*self).into_batch() } } + +/// Owned version of `KeyValuePair`, returned to ffi callers. +/// +/// C callers must free this using [`crate::fwd_free_owned_kv_pair`], +/// not the C standard library's `free` function. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OwnedKeyValuePair { + pub key: OwnedBytes, + pub value: OwnedBytes, +} + +impl From<(Box<[u8]>, Box<[u8]>)> for OwnedKeyValuePair { + fn from(value: (Box<[u8]>, Box<[u8]>)) -> Self { + OwnedKeyValuePair { + key: value.0.into(), + value: value.1.into(), + } + } +} diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 139ad9ae441e..a351f79d8761 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -1,14 +1,14 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt; - +use firewood::merkle; use firewood::v2::api; +use std::fmt; use crate::revision::{GetRevisionResult, RevisionHandle}; use crate::{ - ChangeProofContext, CreateProposalResult, HashKey, NextKeyRange, OwnedBytes, ProposalHandle, - RangeProofContext, + ChangeProofContext, CreateIteratorResult, CreateProposalResult, HashKey, IteratorHandle, + NextKeyRange, OwnedBytes, OwnedKeyValuePair, ProposalHandle, RangeProofContext, }; /// The result type returned from an FFI function that returns no value but may @@ -307,6 +307,83 @@ pub enum ProposalResult<'db> { Err(OwnedBytes), } +/// A result type returned from FFI functions that create an iterator +#[derive(Debug)] +#[repr(C)] +pub enum IteratorResult<'db> { + /// The caller provided a null pointer to a revision/proposal handle. + NullHandlePointer, + /// Building the iterator was successful and the iterator handle is returned + Ok { + /// An opaque pointer to the [`IteratorHandle`]. + /// The value should be freed with [`fwd_free_iterator`] + /// + /// [`fwd_free_iterator`]: crate::fwd_free_iterator + handle: Box>, + }, + /// An error occurred and the message is returned as an [`OwnedBytes`]. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +/// A result type returned from iterator FFI functions +#[derive(Debug)] +#[repr(C)] +pub enum KeyValueResult { + /// The caller provided a null pointer to an iterator handle. + NullHandlePointer, + /// The iterator is exhausted + None, + /// The next item is returned. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with the key and the value of this pair. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Some(OwnedKeyValuePair), + /// An error occurred and the message is returned as an [`OwnedBytes`]. The + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From>> for KeyValueResult { + fn from(value: Option>) -> Self { + match value { + Some(value) => match value { + Ok(value) => KeyValueResult::Some(value.into()), + Err(err) => KeyValueResult::Err(err.to_string().into_bytes().into()), + }, + None => KeyValueResult::None, + } + } +} + +impl<'db> From> for IteratorResult<'db> { + fn from(value: CreateIteratorResult<'db>) -> Self { + IteratorResult::Ok { + handle: Box::new(value.0), + } + } +} + +impl<'db, E: fmt::Display> From, E>> for IteratorResult<'db> { + fn from(value: Result, E>) -> Self { + match value { + Ok(res) => res.into(), + Err(err) => IteratorResult::Err(err.to_string().into_bytes().into()), + } + } +} + /// A result type returned from FFI functions that get a revision #[derive(Debug)] #[repr(C)] @@ -434,7 +511,9 @@ impl_null_handle_result!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, RevisionResult, + KeyValueResult, ); impl_cresult!( @@ -446,7 +525,9 @@ impl_cresult!( ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, + IteratorResult<'_>, RevisionResult, + KeyValueResult, ); enum Panic { From 9dd02f140a645e23412904cdb3c1e63bb06d9000 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Fri, 10 Oct 2025 17:26:08 -0400 Subject: [PATCH 0978/1053] feat(ffi-iterator): Implementation of Iterator in Go (2/4) (#1256) This implement a simple Iterator interface in Go (`Next`, `Key`, `Value`) using the introduced FFI functions, without batching. Depends On: #1255 --------- Co-authored-by: Brandon LeBlanc --- ffi/firewood.go | 11 +++ ffi/firewood_test.go | 141 +++++++++++++++++++++++++++++++++++++++ ffi/iterator.go | 92 +++++++++++++++++++++++++ ffi/memory.go | 50 ++++++++++++++ ffi/proposal.go | 15 +++++ ffi/revision.go | 15 +++++ ffi/src/iterator.rs | 30 ++++----- ffi/src/lib.rs | 2 +- ffi/src/value/results.rs | 4 +- 9 files changed, 342 insertions(+), 18 deletions(-) create mode 100644 ffi/iterator.go diff --git a/ffi/firewood.go b/ffi/firewood.go index 06f3188e2863..6e2233ec79a9 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -214,6 +214,17 @@ func (db *Database) Root() ([]byte, error) { return bytes, err } +func (db *Database) LatestRevision() (*Revision, error) { + root, err := db.Root() + if err != nil { + return nil, err + } + if bytes.Equal(root, EmptyRoot) { + return nil, errRevisionNotFound + } + return db.Revision(root) +} + // Revision returns a historical revision of the database. func (db *Database) Revision(root []byte) (*Revision, error) { if root == nil || len(root) != RootLength { diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 76356952a9bb..88523b10fe87 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1136,3 +1136,144 @@ func TestGetFromRootParallel(t *testing.T) { r.NoError(err, "Parallel operation failed") } } + +func assertIteratorYields(r *require.Assertions, it *Iterator, keys [][]byte, vals [][]byte) { + i := 0 + for ; it.Next(); i += 1 { + r.Equal(keys[i], it.Key()) + r.Equal(vals[i], it.Value()) + } + r.NoError(it.Err()) + r.Equal(len(keys), i) +} + +// Tests that basic iterator functionality works +func TestIter(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) + + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + + assertIteratorYields(r, it, keys, vals) +} + +// Tests that iterators on different roots work fine +func TestIterOnRoot(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Commit 10 key-value pairs. + keys, vals := kvForTest(20) + keys = keys[:10] + vals1, vals2 := vals[:10], vals[10:] + + firstRoot, err := db.Update(keys, vals1) + r.NoError(err) + + // we use the same keys, but update the values + secondRoot, err := db.Update(keys, vals2) + r.NoError(err) + + r1, err := db.Revision(firstRoot) + r.NoError(err) + h1, err := r1.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h1.Drop()) + r.NoError(r1.Drop()) + }) + + r2, err := db.Revision(secondRoot) + r.NoError(err) + h2, err := r2.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h2.Drop()) + r.NoError(r2.Drop()) + }) + + assertIteratorYields(r, h1, keys, vals1) + assertIteratorYields(r, h2, keys, vals2) +} + +// Tests that basic iterator functionality works for proposal +func TestIterOnProposal(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + p, err := db.Propose(keys, vals) + r.NoError(err) + + it, err := p.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + }) + + assertIteratorYields(r, it, keys, vals) +} + +// Tests that the iterator still works after proposal is committed +func TestIterAfterProposalCommit(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + p, err := db.Propose(keys, vals) + r.NoError(err) + + it, err := p.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + }) + + err = p.Commit() + r.NoError(err) + + // iterate after commit + // because iterator hangs on the nodestore reference of proposal + // the nodestore won't be dropped until we drop the iterator + assertIteratorYields(r, it, keys, vals) +} + +// Tests that the iterator on latest revision works properly after a proposal commit +func TestIterUpdate(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(10) + _, err := db.Update(keys, vals) + r.NoError(err) + + // get an iterator on latest revision + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + + // update the database + keys2, vals2 := kvForTest(10) + _, err = db.Update(keys2, vals2) + r.NoError(err) + + // iterate after commit + // because iterator is fixed on the revision hash, it should return the initial values + assertIteratorYields(r, it, keys, vals) +} diff --git a/ffi/iterator.go b/ffi/iterator.go new file mode 100644 index 000000000000..9bf5fa190ecb --- /dev/null +++ b/ffi/iterator.go @@ -0,0 +1,92 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +// #include +// #include "firewood.h" +import "C" + +import ( + "fmt" + "unsafe" +) + +type Iterator struct { + // handle is an opaque pointer to the iterator within Firewood. It should be + // passed to the C FFI functions that operate on iterators + // + // It is not safe to call these methods with a nil handle. + handle *C.IteratorHandle + + // currentKey is the current key retrieved from the iterator + currentKey []byte + // currentVal is the current value retrieved from the iterator + currentVal []byte + // err is the error from the iterator, if any + err error +} + +// Next proceeds to the next item on the iterator, and returns true +// if succeeded and there is a pair available. +// The new pair could be retrieved with Key and Value methods. +func (it *Iterator) Next() bool { + kv, e := getKeyValueFromKeyValueResult(C.fwd_iter_next(it.handle)) + it.err = e + if kv == nil || e != nil { + return false + } + k, v, e := kv.Consume() + it.currentKey = k + it.currentVal = v + it.err = e + return e == nil +} + +// Key returns the key of the current pair +func (it *Iterator) Key() []byte { + if (it.currentKey == nil && it.currentVal == nil) || it.err != nil { + return nil + } + return it.currentKey +} + +// Value returns the value of the current pair +func (it *Iterator) Value() []byte { + if (it.currentKey == nil && it.currentVal == nil) || it.err != nil { + return nil + } + return it.currentVal +} + +// Err returns the error if Next failed +func (it *Iterator) Err() error { + return it.err +} + +// Drop drops the iterator and releases the resources +func (it *Iterator) Drop() error { + if it.handle != nil { + return getErrorFromVoidResult(C.fwd_free_iterator(it.handle)) + } + return nil +} + +// getIteratorFromIteratorResult converts a C.IteratorResult to an Iterator or error. +func getIteratorFromIteratorResult(result C.IteratorResult) (*Iterator, error) { + switch result.tag { + case C.IteratorResult_NullHandlePointer: + return nil, errDBClosed + case C.IteratorResult_Ok: + body := (*C.IteratorResult_Ok_Body)(unsafe.Pointer(&result.anon0)) + proposal := &Iterator{ + handle: body.handle, + } + return proposal, nil + case C.IteratorResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.IteratorResult tag: %d", result.tag) + } +} diff --git a/ffi/memory.go b/ffi/memory.go index 441964ca9fde..0e2c632895fc 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -309,6 +309,56 @@ func getValueFromValueResult(result C.ValueResult) ([]byte, error) { } } +type ownedKeyValue struct { + key *ownedBytes + value *ownedBytes +} + +func (kv *ownedKeyValue) Consume() ([]byte, []byte, error) { + key := kv.key.CopiedBytes() + if err := kv.key.Free(); err != nil { + return nil, nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } + value := kv.value.CopiedBytes() + if err := kv.value.Free(); err != nil { + return nil, nil, fmt.Errorf("%w: %w", errFreeingValue, err) + } + return key, value, nil +} + +// newOwnedKeyValue creates a ownedKeyValue from a C.OwnedKeyValuePair. +// +// The caller is responsible for calling Free() on the returned ownedKeyValue +// when it is no longer needed otherwise memory will leak. +func newOwnedKeyValue(owned C.OwnedKeyValuePair) *ownedKeyValue { + return &ownedKeyValue{ + key: newOwnedBytes(owned.key), + value: newOwnedBytes(owned.value), + } +} + +// getKeyValueFromKeyValueResult converts a C.KeyValueResult to a key value pair or error. +// +// It returns nil, nil if the result is None. +// It returns a *ownedKeyValue, nil if the result is Some. +// It returns an error if the result is an error. +func getKeyValueFromKeyValueResult(result C.KeyValueResult) (*ownedKeyValue, error) { + switch result.tag { + case C.KeyValueResult_NullHandlePointer: + return nil, errDBClosed + case C.KeyValueResult_None: + return nil, nil + case C.KeyValueResult_Some: + ownedKvp := newOwnedKeyValue(*(*C.OwnedKeyValuePair)(unsafe.Pointer(&result.anon0))) + return ownedKvp, nil + case C.ValueResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.KeyValueResult tag: %d", result.tag) + } +} + // getDatabaseFromHandleResult converts a C.HandleResult to a Database or error. // // If the C.HandleResult is an error, it returns an error instead of a Database. diff --git a/ffi/proposal.go b/ffi/proposal.go index 285c14f5ecc8..56ed0ad1a4a8 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -58,6 +58,21 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { return getValueFromValueResult(C.fwd_get_from_proposal(p.handle, newBorrowedBytes(key, &pinner))) } +// Iter creates and iterator starting from the provided key on proposal. +// pass empty slice to start from beginning +func (p *Proposal) Iter(key []byte) (*Iterator, error) { + if p.handle == nil { + return nil, errDBClosed + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + itResult := C.fwd_iter_on_proposal(p.handle, newBorrowedBytes(key, &pinner)) + + return getIteratorFromIteratorResult(itResult) +} + // Propose creates a new proposal with the given keys and values. // The proposal is not committed until Commit is called. func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { diff --git a/ffi/revision.go b/ffi/revision.go index e884a43a883f..0fc600aa42fd 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -53,6 +53,21 @@ func (r *Revision) Get(key []byte) ([]byte, error) { )) } +// Iter creates an iterator starting from the provided key on revision. +// pass empty slice to start from beginning +func (r *Revision) Iter(key []byte) (*Iterator, error) { + if r.handle == nil { + return nil, errDroppedRevision + } + + var pinner runtime.Pinner + defer pinner.Unpin() + + itResult := C.fwd_iter_on_revision(r.handle, newBorrowedBytes(key, &pinner)) + + return getIteratorFromIteratorResult(itResult) +} + // Drop releases the resources backed by the revision handle. // // It is safe to call Drop multiple times; subsequent calls after the first are no-ops. diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs index 487caf1cb6a4..94e6a5d644f2 100644 --- a/ffi/src/iterator.rs +++ b/ffi/src/iterator.rs @@ -1,35 +1,35 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::ops::{Deref, DerefMut}; - use derive_where::derive_where; +use firewood::merkle; +use firewood::v2::api; use firewood::v2::api::BoxKeyValueIter; /// An opaque wrapper around a [`BoxKeyValueIter`]. +#[derive(Default)] #[derive_where(Debug)] #[derive_where(skip_inner)] -pub struct IteratorHandle<'view>(BoxKeyValueIter<'view>); +pub struct IteratorHandle<'view>(Option>); impl<'view> From> for IteratorHandle<'view> { fn from(value: BoxKeyValueIter<'view>) -> Self { - IteratorHandle(value) + IteratorHandle(Some(value)) } } -impl<'view> Deref for IteratorHandle<'view> { - type Target = BoxKeyValueIter<'view>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +impl Iterator for IteratorHandle<'_> { + type Item = Result<(merkle::Key, merkle::Value), api::Error>; -impl DerefMut for IteratorHandle<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + fn next(&mut self) -> Option { + let out = self.0.as_mut()?.next(); + if out.is_none() { + // iterator exhausted; drop it so the NodeStore can be released + self.0 = None; + } + out } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct CreateIteratorResult<'db>(pub IteratorHandle<'db>); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 4d54bdb7e9f0..77515bae3976 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -192,7 +192,7 @@ pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult { - invoke_with_handle(handle, |it| it.next()) + invoke_with_handle(handle, Iterator::next) } /// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index a351f79d8761..ee94c97e3705 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -355,8 +355,8 @@ pub enum KeyValueResult { Err(OwnedBytes), } -impl From>> for KeyValueResult { - fn from(value: Option>) -> Self { +impl From>> for KeyValueResult { + fn from(value: Option>) -> Self { match value { Some(value) => match value { Ok(value) => KeyValueResult::Some(value.into()), From 78dfc29d34038700f608545dfc1665986d950664 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Mon, 13 Oct 2025 08:17:33 -0700 Subject: [PATCH 0979/1053] fix: EINTR during iouring calls (#1354) golang does not guarantee signals are using SA_RESTART, so any system call on a "slow device" might cause the EINTR error. We saw this in the error in issue 1347. This doesn't resolve that issue completely because IO errors can occur during these writes. That will be fixed under a separate issue. My research indicated that re-calling the system call is the right thing to do here. --- Cargo.lock | 4 +-- storage/src/nodestore/persist.rs | 42 ++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca36a178bb1a..e49800c59672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1964,9 +1964,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index 0961d1e1d2f6..e87845e74709 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -308,6 +308,7 @@ impl NodeStore { #[cfg(feature = "io-uring")] fn flush_nodes_io_uring(&mut self) -> Result { use crate::LinearAddress; + use std::io::ErrorKind::Interrupted; use std::pin::Pin; #[derive(Clone, Debug)] @@ -316,6 +317,32 @@ impl NodeStore { node: Option<(LinearAddress, MaybePersistedNode)>, } + /// Helper function to retry `submit_and_wait` on EINTR + fn submit_and_wait_with_retry( + ring: &mut io_uring::IoUring, + wait_nr: u32, + storage: &FileBacked, + operation_name: &str, + ) -> Result<(), FileIoError> { + loop { + match ring.submit_and_wait(wait_nr as usize) { + Ok(_) => return Ok(()), + Err(e) => { + // Retry if the error is an interrupted system call + if e.kind() == Interrupted { + continue; + } + // For other errors, return the error + return Err(storage.file_io_error( + e, + 0, + Some(format!("io-uring {operation_name}")), + )); + } + } + } + } + /// Helper function to handle completion queue entries and check for errors fn handle_completion_queue( storage: &FileBacked, @@ -419,10 +446,7 @@ impl NodeStore { // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation // to complete, then handle the completion queue firewood_counter!("ring.full", "amount of full ring").increment(1); - ring.submit_and_wait(1).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("io-uring submit_and_wait".to_string())) - })?; + submit_and_wait_with_retry(&mut ring, 1, &self.storage, "submit_and_wait")?; let completion_queue = ring.completion(); trace!("competion queue length: {}", completion_queue.len()); handle_completion_queue( @@ -440,10 +464,12 @@ impl NodeStore { .iter() .filter(|pbe| pbe.node.is_some()) .count(); - ring.submit_and_wait(pending).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("io-uring final submit_and_wait".to_string())) - })?; + submit_and_wait_with_retry( + &mut ring, + pending as u32, + &self.storage, + "final submit_and_wait", + )?; handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; From 735817c365d6cfb0f346649a5f7b5b63df495e95 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 13 Oct 2025 09:04:47 -0700 Subject: [PATCH 0980/1053] feat(1/7): add U4 type to be used as a path component (#1336) This change introduces a `U4` type that uses an underlying enum with only 16 states in order to enable niche optimizations. This change does not yet use the type within code as that will cause a large amount of changes that will make reviewing this difficult. The focus of this change is the `U4` type itself and nothing outside of that. This type also is not a `PathComponent`, which will appear in the next Pull Request. ## Memory Layout The niche optimizations apply to memory layout allowing for `Option` to have the same size and alignment of `U4`, meaning there is no additional storage to keep the Option discriminant. ## Array Bounds Checking The niche optimizations also apply to bounds checking when indexing into a fixed size array, like we do for the `Children<_>` type alias. See This change contributes to the resolution of #1230. --- storage/src/lib.rs | 2 + storage/src/u4.rs | 343 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 storage/src/u4.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index cd5d07da96ad..3c7c70f5639c 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -32,6 +32,7 @@ mod nodestore; #[cfg(any(test, feature = "test_utils"))] mod test_utils; mod trie_hash; +mod u4; /// Logger module for handling logging functionality pub mod logger; @@ -52,6 +53,7 @@ pub use nodestore::{ AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; +pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; pub use linear::memory::MemStore; diff --git a/storage/src/u4.rs b/storage/src/u4.rs new file mode 100644 index 000000000000..0220e54d1259 --- /dev/null +++ b/storage/src/u4.rs @@ -0,0 +1,343 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +/// An error similar to [`std::num::TryFromIntError`] but able to be created +/// within our crate. +/// +/// The std error does not have a public constructor, and neither does ours. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +#[non_exhaustive] +#[error("out of range integral type conversion attempted")] +pub struct TryFromIntError; + +/// A 4-bit unsigned integer representing a hexary digit (0-15, inclusive) used +/// as a path component in hexary tries. +/// +/// The internal representation is a `u8` where only the lower 4 bits are used. +/// +/// Niche optimizations are enabled through the inner representation to enable +/// memory efficiency when used in data structures like [`Option`] as well as +/// allowing for faster indexing into [`Children`](crate::node::Children) arrays +/// by enabling the compiler to optimize away bounds checks. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct U4(Repr); + +impl U4 { + /// The number of bits required to represent a single u4. + pub const BITS: u32 = 4; + + /// The minimum value of a [`U4`], representing zero and the leftmost child + /// in a node's children array. + pub const MIN: Self = Self(Repr::Ox0); + + /// The maximum value of a [`U4`], representing fifteen and the rightmost child + /// in a node's children array. + pub const MAX: Self = Self(Repr::OxF); + + /// Fallibly converts a `u8` to a [`U4`], returning [`None`] if the + /// value is out of range. + #[inline] + #[must_use] + pub const fn try_new(v: u8) -> Option { + // FIXME(rust-lang/rust#143874): Option::map is not yet const + match Repr::try_new(v) { + Some(repr) => Some(Self(repr)), + None => None, + } + } + + /// Creates a new [`U4`] without checking that the provided value + /// is valid. + /// + /// # Safety + /// + /// The caller must ensure that the value is between 0 and 15 (inclusive). + /// Providing a value outside of this range results in immediate undefined + /// behavior. + #[inline] + #[must_use] + pub const unsafe fn new_unchecked(v: u8) -> Self { + #![expect(unsafe_code)] + + debug_assert!(v <= 0xF); + // SAFETY: the caller must ensure that `v` is a valid value (0 to 15). + unsafe { Self(Repr::new_unchecked(v)) } + } + + /// Creates a new [`U4`] using only the lower 4 bits of the input value and + /// ignoring the upper 4 bits. + #[inline] + #[must_use] + pub const fn new_masked(v: u8) -> Self { + #[expect(unsafe_code)] + // SAFETY: the value is masked to be between 0 and 15. + unsafe { + Self::new_unchecked(v & 0xF) + } + } + + /// Creates a new [`U4`] using only the upper 4 bits of the input value and + /// ignoring the lower 4 bits. + #[inline] + #[must_use] + pub const fn new_shifted(v: u8) -> Self { + #[expect(unsafe_code)] + // SAFETY: the value is shifted to be between 0 and 15. The extra mask is + // redundant but added for extra clarity. + unsafe { + Self::new_unchecked((v >> 4) & 0xF) + } + } + + /// Creates a pair of [`U4`]s from a single `u8`, where the first element + /// is created from the upper 4 bits and the second element is created from + /// the lower 4 bits. + #[inline] + #[must_use] + pub const fn new_pair(v: u8) -> (Self, Self) { + (Self::new_shifted(v), Self::new_masked(v)) + } + + /// Casts the [`U4`] to a `u8`. + #[inline] + #[must_use] + pub const fn as_u8(self) -> u8 { + self.0 as u8 + } + + /// Casts the [`U4`] to a `usize`. + #[inline] + #[must_use] + pub const fn as_usize(self) -> usize { + self.0 as usize + } + + /// Joins this [`U4`] with another [`U4`] to create a single `u8` where + /// this component is the upper 4 bits and the provided component is the + /// lower 4 bits. + #[inline] + #[must_use] + pub const fn join(self, lower: U4) -> u8 { + (self.as_u8() << 4) | lower.as_u8() + } +} + +impl TryFrom for U4 { + type Error = TryFromIntError; + + fn try_from(value: u8) -> Result { + Self::try_new(value).ok_or(TryFromIntError) + } +} + +/// The internal representation of a [`U4`]. +/// +/// This enum explicitly represents each of the 16 possible values (0b0000 to 0b1111) +/// of a 4-bit unsigned integer. It is used to optimize memory usage and performance +/// in scenarios where many instances of `U4` are stored, such as in a hexary trie, +/// +/// For example, when using [`Option`], the resulting size is still 1 byte +/// due to Rust's niche optimization, where [`Option::None`] can be represented by +/// any of the unused bit patterns of the inner [`Repr`] enum. +/// +/// Additionally, when using [`U4`] as an index into a fixed-size array of 16 elements, +/// like the [`Children`](crate::Children) array in the hexary trie, the compiler can +/// optimize away bounds checks and remove possible panic points. This is because the +/// compiler knows that the value of [`U4`] can only be one of the 16 valid indices. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Repr { + Ox0, + Ox1, + Ox2, + Ox3, + Ox4, + Ox5, + Ox6, + Ox7, + Ox8, + Ox9, + OxA, + OxB, + OxC, + OxD, + OxE, + OxF, +} + +impl Repr { + #[inline] + #[must_use] + const unsafe fn new_unchecked(v: u8) -> Self { + #![expect(unsafe_code)] + // SAFETY: the caller must ensure that `v` is a valid value (0 to 15). + unsafe { Self::try_new(v).unwrap_unchecked() } + } + + #[inline] + #[must_use] + const fn try_new(v: u8) -> Option { + // this could have been a transmute but then dead code detection would + // annoyingly complain about each variant going unused. the final code + // is the same regardless: https://rust.godbolt.org/z/6v6sddf6d + match v { + 0x0 => Some(Self::Ox0), + 0x1 => Some(Self::Ox1), + 0x2 => Some(Self::Ox2), + 0x3 => Some(Self::Ox3), + 0x4 => Some(Self::Ox4), + 0x5 => Some(Self::Ox5), + 0x6 => Some(Self::Ox6), + 0x7 => Some(Self::Ox7), + 0x8 => Some(Self::Ox8), + 0x9 => Some(Self::Ox9), + 0xA => Some(Self::OxA), + 0xB => Some(Self::OxB), + 0xC => Some(Self::OxC), + 0xD => Some(Self::OxD), + 0xE => Some(Self::OxE), + 0xF => Some(Self::OxF), + _ => None, + } + } +} + +impl std::fmt::Debug for U4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.as_u8(), f) + } +} + +impl std::fmt::Display for U4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.as_u8(), f) + } +} + +impl std::fmt::LowerHex for U4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.as_u8(), f) + } +} + +impl std::fmt::UpperHex for U4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.as_u8(), f) + } +} + +impl std::fmt::Binary for U4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.as_u8(), f) + } +} + +#[cfg(test)] +mod tests { + use test_case::test_case; + + use super::*; + + #[test_case(0x00, Some(U4(Repr::Ox0)); "0x00 -> 0")] + #[test_case(0x01, Some(U4(Repr::Ox1)); "0x01 -> 1")] + #[test_case(0x02, Some(U4(Repr::Ox2)); "0x02 -> 2")] + #[test_case(0x03, Some(U4(Repr::Ox3)); "0x03 -> 3")] + #[test_case(0x04, Some(U4(Repr::Ox4)); "0x04 -> 4")] + #[test_case(0x05, Some(U4(Repr::Ox5)); "0x05 -> 5")] + #[test_case(0x06, Some(U4(Repr::Ox6)); "0x06 -> 6")] + #[test_case(0x07, Some(U4(Repr::Ox7)); "0x07 -> 7")] + #[test_case(0x08, Some(U4(Repr::Ox8)); "0x08 -> 8")] + #[test_case(0x09, Some(U4(Repr::Ox9)); "0x09 -> 9")] + #[test_case(0x0A, Some(U4(Repr::OxA)); "0x0A -> 10")] + #[test_case(0x0B, Some(U4(Repr::OxB)); "0x0B -> 11")] + #[test_case(0x0C, Some(U4(Repr::OxC)); "0x0C -> 12")] + #[test_case(0x0D, Some(U4(Repr::OxD)); "0x0D -> 13")] + #[test_case(0x0E, Some(U4(Repr::OxE)); "0x0E -> 14")] + #[test_case(0x0F, Some(U4(Repr::OxF)); "0x0F -> 15")] + #[test_case(0x10, None; "0x10 -> None")] + #[test_case(0xFF, None; "0xFF -> None")] + fn test_try_new(input: u8, expected: Option) { + assert_eq!(U4::try_new(input), expected); + } + + #[test_case(0x00, U4(Repr::Ox0); "0x00 -> 0")] + #[test_case(0x01, U4(Repr::Ox1); "0x01 -> 1")] + #[test_case(0x02, U4(Repr::Ox2); "0x02 -> 2")] + #[test_case(0x03, U4(Repr::Ox3); "0x03 -> 3")] + #[test_case(0x04, U4(Repr::Ox4); "0x04 -> 4")] + #[test_case(0x05, U4(Repr::Ox5); "0x05 -> 5")] + #[test_case(0x06, U4(Repr::Ox6); "0x06 -> 6")] + #[test_case(0x07, U4(Repr::Ox7); "0x07 -> 7")] + #[test_case(0x08, U4(Repr::Ox8); "0x08 -> 8")] + #[test_case(0x09, U4(Repr::Ox9); "0x09 -> 9")] + #[test_case(0x0A, U4(Repr::OxA); "0x0A -> 10")] + #[test_case(0x0B, U4(Repr::OxB); "0x0B -> 11")] + #[test_case(0x0C, U4(Repr::OxC); "0x0C -> 12")] + #[test_case(0x0D, U4(Repr::OxD); "0x0D -> 13")] + #[test_case(0x0E, U4(Repr::OxE); "0x0E -> 14")] + #[test_case(0x0F, U4(Repr::OxF); "0x0F -> 15")] + #[test_case(0x10, U4(Repr::Ox0); "0x10 -> 0")] + #[test_case(0x20, U4(Repr::Ox0); "0x20 -> 0")] + #[test_case(0x30, U4(Repr::Ox0); "0x30 -> 0")] + #[test_case(0x40, U4(Repr::Ox0); "0x40 -> 0")] + #[test_case(0x50, U4(Repr::Ox0); "0x50 -> 0")] + #[test_case(0x60, U4(Repr::Ox0); "0x60 -> 0")] + #[test_case(0x70, U4(Repr::Ox0); "0x70 -> 0")] + #[test_case(0x80, U4(Repr::Ox0); "0x80 -> 0")] + #[test_case(0x90, U4(Repr::Ox0); "0x90 -> 0")] + #[test_case(0xA0, U4(Repr::Ox0); "0xA0 -> 0")] + #[test_case(0xB0, U4(Repr::Ox0); "0xB0 -> 0")] + #[test_case(0xC0, U4(Repr::Ox0); "0xC0 -> 0")] + #[test_case(0xD0, U4(Repr::Ox0); "0xD0 -> 0")] + #[test_case(0xE0, U4(Repr::Ox0); "0xE0 -> 0")] + #[test_case(0xF0, U4(Repr::Ox0); "0xF0 -> 0")] + #[test_case(0xFF, U4(Repr::OxF); "0xFF -> 15")] + fn test_new_masked(input: u8, expected: U4) { + assert_eq!(U4::new_masked(input), expected); + } + + #[test_case(0x00, U4(Repr::Ox0); "0x00 -> 0")] + #[test_case(0x01, U4(Repr::Ox0); "0x01 -> 0")] + #[test_case(0x02, U4(Repr::Ox0); "0x02 -> 0")] + #[test_case(0x03, U4(Repr::Ox0); "0x03 -> 0")] + #[test_case(0x04, U4(Repr::Ox0); "0x04 -> 0")] + #[test_case(0x05, U4(Repr::Ox0); "0x05 -> 0")] + #[test_case(0x06, U4(Repr::Ox0); "0x06 -> 0")] + #[test_case(0x07, U4(Repr::Ox0); "0x07 -> 0")] + #[test_case(0x08, U4(Repr::Ox0); "0x08 -> 0")] + #[test_case(0x09, U4(Repr::Ox0); "0x09 -> 0")] + #[test_case(0x0A, U4(Repr::Ox0); "0x0A -> 0")] + #[test_case(0x0B, U4(Repr::Ox0); "0x0B -> 0")] + #[test_case(0x0C, U4(Repr::Ox0); "0x0C -> 0")] + #[test_case(0x0D, U4(Repr::Ox0); "0x0D -> 0")] + #[test_case(0x0E, U4(Repr::Ox0); "0x0E -> 0")] + #[test_case(0x0F, U4(Repr::Ox0); "0x0F -> 0")] + #[test_case(0x10, U4(Repr::Ox1); "0x10 -> 1")] + #[test_case(0x20, U4(Repr::Ox2); "0x20 -> 2")] + #[test_case(0x30, U4(Repr::Ox3); "0x30 -> 3")] + #[test_case(0x40, U4(Repr::Ox4); "0x40 -> 4")] + #[test_case(0x50, U4(Repr::Ox5); "0x50 -> 5")] + #[test_case(0x60, U4(Repr::Ox6); "0x60 -> 6")] + #[test_case(0x70, U4(Repr::Ox7); "0x70 -> 7")] + #[test_case(0x80, U4(Repr::Ox8); "0x80 -> 8")] + #[test_case(0x90, U4(Repr::Ox9); "0x90 -> 9")] + #[test_case(0xA0, U4(Repr::OxA); "0xA0 -> 10")] + #[test_case(0xB0, U4(Repr::OxB); "0xB0 -> 11")] + #[test_case(0xC0, U4(Repr::OxC); "0xC0 -> 12")] + #[test_case(0xD0, U4(Repr::OxD); "0xD0 -> 13")] + #[test_case(0xE0, U4(Repr::OxE); "0xE0 -> 14")] + #[test_case(0xF0, U4(Repr::OxF); "0xF0 -> 15")] + #[test_case(0xFF, U4(Repr::OxF); "0xFF -> 15")] + fn test_new_shifted(input: u8, expected: U4) { + assert_eq!(U4::new_shifted(input), expected); + } + + #[test_case(0x00, (U4(Repr::Ox0), U4(Repr::Ox0)); "0x00 -> (0, 0)")] + #[test_case(0x0F, (U4(Repr::Ox0), U4(Repr::OxF)); "0x0F -> (0, 15)")] + #[test_case(0xF0, (U4(Repr::OxF), U4(Repr::Ox0)); "0xF0 -> (15, 0)")] + #[test_case(0xFF, (U4(Repr::OxF), U4(Repr::OxF)); "0xFF -> (15, 15)")] + fn test_new_pair_then_join(input: u8, expected: (U4, U4)) { + let (upper, lower) = U4::new_pair(input); + assert_eq!((upper, lower), expected); + assert_eq!(upper.join(lower), input); + } +} From 69a1541613e5243185c3ff2ea5870e37f3c9a57e Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 13 Oct 2025 09:12:50 -0700 Subject: [PATCH 0981/1053] feat(2/7): add newtype for PathComponent (#1337) The new `PathComponent` type is introduced to encapsulate the possible values of a single component in the Trie path. If `branch_factor_256` is not enabled, the default, `PathComponent` wraps `U4`, which was added in the previous pull request. This type is only value for values 0-15, inclusive. If `branch_factor_256` is enabled, `PathComponent` wraps a `u8`, which can represent values 0-255, inclusive. This change contributes to the resolution of #1230, but does not fully resolve it until all usages of `Path` are replaced with future changes. --- storage/src/lib.rs | 2 + storage/src/path/component.rs | 153 ++++++++++++++++++++++++++++++++++ storage/src/path/mod.rs | 6 ++ 3 files changed, 161 insertions(+) create mode 100644 storage/src/path/component.rs create mode 100644 storage/src/path/mod.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 3c7c70f5639c..0a0c7ab05cc8 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -29,6 +29,7 @@ mod iter; mod linear; mod node; mod nodestore; +mod path; #[cfg(any(test, feature = "test_utils"))] mod test_utils; mod trie_hash; @@ -53,6 +54,7 @@ pub use nodestore::{ AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; +pub use path::PathComponent; pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; diff --git a/storage/src/path/component.rs b/storage/src/path/component.rs new file mode 100644 index 000000000000..cc0b540206b8 --- /dev/null +++ b/storage/src/path/component.rs @@ -0,0 +1,153 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#[cfg(not(feature = "branch_factor_256"))] +/// A path component in a hexary trie; which is only 4 bits (aka a nibble). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PathComponent(pub crate::u4::U4); + +#[cfg(feature = "branch_factor_256")] +/// A path component in a 256-ary trie; which is 8 bits (aka a byte). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PathComponent(pub u8); + +impl PathComponent { + /// All possible path components. + /// + /// This makes it easy to iterate over all possible children of a branch + /// in a type-safe way. It is preferrable to do: + /// + /// ```ignore + /// for (idx, slot) in PathComponent::ALL.into_iter().zip(branch.children.each_ref()) { + /// /// use idx and slot + /// } + /// ``` + /// + /// instead of using a raw range like (`0..16`) or [`Iterator::enumerate`], + /// which does not give a type-safe path component. + #[cfg(not(feature = "branch_factor_256"))] + pub const ALL: [Self; 16] = [ + Self(crate::u4::U4::new_masked(0x0)), + Self(crate::u4::U4::new_masked(0x1)), + Self(crate::u4::U4::new_masked(0x2)), + Self(crate::u4::U4::new_masked(0x3)), + Self(crate::u4::U4::new_masked(0x4)), + Self(crate::u4::U4::new_masked(0x5)), + Self(crate::u4::U4::new_masked(0x6)), + Self(crate::u4::U4::new_masked(0x7)), + Self(crate::u4::U4::new_masked(0x8)), + Self(crate::u4::U4::new_masked(0x9)), + Self(crate::u4::U4::new_masked(0xA)), + Self(crate::u4::U4::new_masked(0xB)), + Self(crate::u4::U4::new_masked(0xC)), + Self(crate::u4::U4::new_masked(0xD)), + Self(crate::u4::U4::new_masked(0xE)), + Self(crate::u4::U4::new_masked(0xF)), + ]; + + /// All possible path components. + /// + /// This makes it easy to iterate over all possible children of a branch + /// in a type-safe way. It is preferrable to do: + /// + /// ```ignore + /// for (idx, slot) in PathComponent::ALL.into_iter().zip(branch.children.each_ref()) { + /// /// use idx and slot + /// } + /// ``` + /// + /// instead of using a raw range like (`0..256`) or [`Iterator::enumerate`], + /// which does not give a type-safe path component. + #[cfg(feature = "branch_factor_256")] + pub const ALL: [Self; 256] = { + let mut all = [Self(0); 256]; + let mut i = 0; + #[expect(clippy::indexing_slicing)] + while i < 256 { + all[i] = Self(i as u8); + i += 1; + } + all + }; +} + +impl PathComponent { + /// Returns the path component as a [`u8`]. + #[must_use] + pub const fn as_u8(self) -> u8 { + #[cfg(not(feature = "branch_factor_256"))] + { + self.0.as_u8() + } + #[cfg(feature = "branch_factor_256")] + { + self.0 + } + } + + /// Returns the path component as a [`usize`]. + #[must_use] + pub const fn as_usize(self) -> usize { + self.as_u8() as usize + } + + /// Tries to create a path component from the given [`u8`]. + /// + /// For hexary tries, the input must be in the range 0x00 to 0x0F inclusive. + /// Any value outside this range will result in [`None`]. + /// + /// For 256-ary tries, any value is valid. + #[must_use] + pub const fn try_new(value: u8) -> Option { + #[cfg(not(feature = "branch_factor_256"))] + { + match crate::u4::U4::try_new(value) { + Some(u4) => Some(Self(u4)), + None => None, + } + } + #[cfg(feature = "branch_factor_256")] + { + Some(Self(value)) + } + } + + /// Creates a pair of path components from a single byte. + #[cfg(not(feature = "branch_factor_256"))] + #[must_use] + pub const fn new_pair(v: u8) -> (Self, Self) { + let (upper, lower) = crate::u4::U4::new_pair(v); + (Self(upper), Self(lower)) + } +} + +impl std::fmt::Display for PathComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[cfg(not(feature = "branch_factor_256"))] + { + write!(f, "{self:X}") + } + #[cfg(feature = "branch_factor_256")] + { + write!(f, "{self:02X}") + } + } +} + +impl std::fmt::Binary for PathComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.0, f) + } +} + +impl std::fmt::LowerHex for PathComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.0, f) + } +} + +impl std::fmt::UpperHex for PathComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.0, f) + } +} diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs new file mode 100644 index 000000000000..49f8f1286ab0 --- /dev/null +++ b/storage/src/path/mod.rs @@ -0,0 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +mod component; + +pub use self::component::PathComponent; From da715a271b42f8845a2b3dca5284f9b2e5274c9f Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 13 Oct 2025 09:20:58 -0700 Subject: [PATCH 0982/1053] feat(3/7): add TriePath trait and path abstraction (#1338) The `TriePath` trait provides a unified interface for handling paths in the trie structure. Somtimes paths are backed by a sequence of bytes, and sometimes by a sequence of components. For branch_factor_256, they are the same. This abstraction allows us to work with both without running into type issues and inconsistencies with the two representations. --- storage/src/lib.rs | 2 +- storage/src/path/component.rs | 290 ++++++++++++++++++++++++++++++++++ storage/src/path/joined.rs | 48 ++++++ storage/src/path/mod.rs | 204 ++++++++++++++++++++++++ 4 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 storage/src/path/joined.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0a0c7ab05cc8..108bc68cce74 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -54,7 +54,7 @@ pub use nodestore::{ AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; -pub use path::PathComponent; +pub use path::{JoinedPath, PathComponent, TriePath, TriePathFromUnpackedBytes}; pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; diff --git a/storage/src/path/component.rs b/storage/src/path/component.rs index cc0b540206b8..3cd44610d67b 100644 --- a/storage/src/path/component.rs +++ b/storage/src/path/component.rs @@ -1,6 +1,10 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use smallvec::SmallVec; + +use super::{TriePath, TriePathFromUnpackedBytes}; + #[cfg(not(feature = "branch_factor_256"))] /// A path component in a hexary trie; which is only 4 bits (aka a nibble). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -151,3 +155,289 @@ impl std::fmt::UpperHex for PathComponent { std::fmt::UpperHex::fmt(&self.0, f) } } + +impl TriePath for PathComponent { + type Components<'a> + = std::option::IntoIter + where + Self: 'a; + + fn len(&self) -> usize { + 1 + } + + fn components(&self) -> Self::Components<'_> { + Some(*self).into_iter() + } +} + +impl TriePath for Option { + type Components<'a> + = std::option::IntoIter + where + Self: 'a; + + fn len(&self) -> usize { + usize::from(self.is_some()) + } + + fn components(&self) -> Self::Components<'_> { + (*self).into_iter() + } +} + +impl TriePath for [PathComponent] { + type Components<'a> + = std::iter::Copied> + where + Self: 'a; + + fn len(&self) -> usize { + self.len() + } + + fn components(&self) -> Self::Components<'_> { + self.iter().copied() + } +} + +impl TriePath for [PathComponent; N] { + type Components<'a> + = std::iter::Copied> + where + Self: 'a; + + fn len(&self) -> usize { + N + } + + fn components(&self) -> Self::Components<'_> { + self.iter().copied() + } +} + +impl TriePath for Vec { + type Components<'a> + = std::iter::Copied> + where + Self: 'a; + + fn len(&self) -> usize { + self.len() + } + + fn components(&self) -> Self::Components<'_> { + self.iter().copied() + } +} + +impl> TriePath for SmallVec { + type Components<'a> + = std::iter::Copied> + where + Self: 'a; + + fn len(&self) -> usize { + self.len() + } + + fn components(&self) -> Self::Components<'_> { + self.iter().copied() + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl<'input> TriePathFromUnpackedBytes<'input> for &'input [PathComponent] { + type Error = crate::u4::TryFromIntError; + + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result { + if bytes.iter().all(|&b| b <= 0x0F) { + #[expect(unsafe_code)] + // SAFETY: we have verified that all bytes are in the valid range for + // `U4` (0x00 to 0x0F inclusive); therefore, it is now safe for us + // to reinterpret a &[u8] as a &[PathComponent]. + Ok(unsafe { byte_slice_as_path_components_unchecked(bytes) }) + } else { + Err(crate::u4::TryFromIntError) + } + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl TriePathFromUnpackedBytes<'_> for Vec { + type Error = crate::u4::TryFromIntError; + + fn path_from_unpacked_bytes(bytes: &[u8]) -> Result { + try_from_maybe_u4(bytes.iter().copied()) + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl> TriePathFromUnpackedBytes<'_> for SmallVec { + type Error = crate::u4::TryFromIntError; + + fn path_from_unpacked_bytes(bytes: &[u8]) -> Result { + try_from_maybe_u4(bytes.iter().copied()) + } +} + +#[cfg(feature = "branch_factor_256")] +impl<'input> TriePathFromUnpackedBytes<'input> for &'input [PathComponent] { + type Error = std::convert::Infallible; + + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result { + #[expect(unsafe_code)] + // SAFETY: u8 is always valid for PathComponent in 256-ary tries. + Ok(unsafe { byte_slice_as_path_components_unchecked(bytes) }) + } +} + +#[cfg(feature = "branch_factor_256")] +impl TriePathFromUnpackedBytes<'_> for Vec { + type Error = std::convert::Infallible; + + fn path_from_unpacked_bytes(bytes: &[u8]) -> Result { + Ok(bytes.iter().copied().map(PathComponent).collect()) + } +} + +#[cfg(feature = "branch_factor_256")] +impl> TriePathFromUnpackedBytes<'_> for SmallVec { + type Error = std::convert::Infallible; + + fn path_from_unpacked_bytes(bytes: &[u8]) -> Result { + Ok(bytes.iter().copied().map(PathComponent).collect()) + } +} + +impl<'input> TriePathFromUnpackedBytes<'input> for Box<[PathComponent]> { + type Error = as TriePathFromUnpackedBytes<'input>>::Error; + + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result { + Vec::::path_from_unpacked_bytes(bytes).map(Into::into) + } +} + +impl<'input> TriePathFromUnpackedBytes<'input> for std::rc::Rc<[PathComponent]> { + type Error = as TriePathFromUnpackedBytes<'input>>::Error; + + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result { + Vec::::path_from_unpacked_bytes(bytes).map(Into::into) + } +} + +impl<'input> TriePathFromUnpackedBytes<'input> for std::sync::Arc<[PathComponent]> { + type Error = as TriePathFromUnpackedBytes<'input>>::Error; + + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result { + Vec::::path_from_unpacked_bytes(bytes).map(Into::into) + } +} + +#[inline] +const unsafe fn byte_slice_as_path_components_unchecked(bytes: &[u8]) -> &[PathComponent] { + #![expect(unsafe_code)] + + // SAFETY: The caller must ensure that all bytes are valid for `PathComponent`, + // which is trivially true for 256-ary tries. For hexary tries, the caller must + // ensure that each byte is in the range 0x00 to 0x0F inclusive. + // + // We also rely on the fact that `PathComponent` is a single element type + // over `u8` (or `u4` which looks like a `u8` for this purpose). + // + // borrow rules ensure that the pointer for `bytes` is not null and + // `bytes.len()` is always valid. The returned reference will have the same + // lifetime as `bytes` so it cannot outlive the original slice. + unsafe { + &*(std::ptr::slice_from_raw_parts(bytes.as_ptr().cast::(), bytes.len())) + } +} + +#[inline] +#[cfg(not(feature = "branch_factor_256"))] +fn try_from_maybe_u4>( + bytes: impl IntoIterator, +) -> Result { + bytes + .into_iter() + .map(PathComponent::try_new) + .collect::>() + .ok_or(crate::u4::TryFromIntError) +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use test_case::test_case; + + use super::*; + + #[cfg_attr(not(feature = "branch_factor_256"), test_case(PhantomData::<&[PathComponent]>; "slice"))] + #[cfg_attr(not(feature = "branch_factor_256"), test_case(PhantomData::>; "boxed slice"))] + #[cfg_attr(not(feature = "branch_factor_256"), test_case(PhantomData::>; "vec"))] + #[cfg_attr(not(feature = "branch_factor_256"), test_case(PhantomData::>; "smallvec"))] + #[cfg_attr(feature = "branch_factor_256", test_case(PhantomData::<&[PathComponent]>; "slice"))] + #[cfg_attr(feature = "branch_factor_256", test_case(PhantomData::>; "boxed slice"))] + #[cfg_attr(feature = "branch_factor_256", test_case(PhantomData::>; "vec"))] + #[cfg_attr(feature = "branch_factor_256", test_case(PhantomData::>; "smallvec"))] + fn test_path_from_unpacked_bytes_hexary(_: PhantomData) + where + T: TriePathFromUnpackedBytes<'static, Error: std::fmt::Debug>, + { + let input: &[u8; _] = &[0x00, 0x01, 0x0A, 0x0F]; + let output = ::path_from_unpacked_bytes(input).expect("valid input"); + + assert_eq!(output.len(), input.len()); + assert_eq!( + output + .components() + .map(PathComponent::as_u8) + .zip(input.iter().copied()) + .take_while(|&(pc, b)| pc == b) + .count(), + input.len(), + ); + } + + #[cfg(not(feature = "branch_factor_256"))] + #[test_case(PhantomData::<&[PathComponent]>; "slice")] + #[test_case(PhantomData::>; "boxed slice")] + #[test_case(PhantomData::>; "vec")] + #[test_case(PhantomData::>; "smallvec")] + fn test_path_from_unpacked_bytes_hexary_invalid(_: PhantomData) + where + T: TriePathFromUnpackedBytes<'static> + std::fmt::Debug, + { + let input: &[u8; _] = &[0x00, 0x10, 0x0A, 0x0F]; + let _ = ::path_from_unpacked_bytes(input).expect_err("invalid input"); + } + + #[test] + fn test_joined_path() { + let path = <&[PathComponent] as TriePathFromUnpackedBytes>::path_from_unpacked_bytes(&[ + 0x0A, 0x0B, 0x0C, + ]) + .expect("valid input"); + + let with_suffix = path.append(PathComponent::try_new(0x0D).expect("valid")); + assert_eq!(with_suffix.len(), 4); + assert_eq!( + with_suffix + .components() + .map(PathComponent::as_u8) + .collect::>(), + vec![0x0A, 0x0B, 0x0C, 0x0D], + ); + + let with_prefix = with_suffix.prepend(PathComponent::try_new(0x09).expect("valid")); + assert_eq!(with_prefix.len(), 5); + assert_eq!( + with_prefix + .components() + .map(PathComponent::as_u8) + .collect::>(), + vec![0x09, 0x0A, 0x0B, 0x0C, 0x0D], + ); + } +} diff --git a/storage/src/path/joined.rs b/storage/src/path/joined.rs new file mode 100644 index 000000000000..87cffb94becd --- /dev/null +++ b/storage/src/path/joined.rs @@ -0,0 +1,48 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::iter::Chain; + +use super::TriePath; + +/// Joins two path segments into a single path, retaining the original segments +/// without needing to allocate a new contiguous array. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct JoinedPath { + /// The prefix segment of the path. + pub prefix: P, + + /// The suffix segment of the path. + pub suffix: S, +} + +impl JoinedPath { + /// Creates a new joined path from the given prefix and suffix. + /// + /// This does not allocate and takes ownership of the input segments. + pub const fn new(prefix: P, suffix: S) -> Self { + Self { prefix, suffix } + } +} + +impl TriePath for JoinedPath { + type Components<'a> + = Chain, S::Components<'a>> + where + Self: 'a; + + fn len(&self) -> usize { + self.prefix + .len() + .checked_add(self.suffix.len()) + .expect("joined path length overflowed usize") + } + + fn is_empty(&self) -> bool { + self.prefix.is_empty() && self.suffix.is_empty() + } + + fn components(&self) -> Self::Components<'_> { + self.prefix.components().chain(self.suffix.components()) + } +} diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 49f8f1286ab0..1de32e7414e1 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -2,5 +2,209 @@ // See the file LICENSE.md for licensing terms. mod component; +mod joined; pub use self::component::PathComponent; +pub use self::joined::JoinedPath; + +/// A trie path of components with different underlying representations. +/// +/// The underlying representation does not need to be a contiguous array of +/// [`PathComponent`], but it must be possible to iterate over them in order +/// as well as have a known length. +pub trait TriePath { + /// The iterator returned by [`TriePath::components`]. + type Components<'a>: Iterator + Clone + 'a + where + Self: 'a; + + /// The length, in path components, of this path. + fn len(&self) -> usize; + + /// Returns true if this path is empty (i.e. has length 0). + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns an iterator over the components of this path. + fn components(&self) -> Self::Components<'_>; + + /// Appends the provided path segment to this path, returning a new joined + /// path that represents the concatenation of the two paths. + /// + /// The returned path is a view over the two input paths and does not + /// allocate. The input paths are consumed and ownership is taken. + fn append(self, suffix: S) -> JoinedPath + where + Self: Sized, + S: TriePath, + { + JoinedPath::new(self, suffix) + } + + /// Prepends the provided path segment to this path, returning a new joined + /// path that represents the concatenation of the two paths. + /// + /// The inverse of [`TriePath::append`]. + /// + /// The returned path is a view over the two input paths and does not + /// allocate. The input paths are consumed and ownership is taken. + fn prepend

    (self, prefix: P) -> JoinedPath + where + Self: Sized, + P: TriePath, + { + prefix.append(self) + } + + /// Compares this path against another path for equality using path component + /// equality. + /// + /// This is analogous to [`Iterator::eq`] and is different than [`PartialEq`] + /// which may have different semantics depending on the underlying type and + /// representation as well as may not be implemented for the cross-type + /// comparisons. + fn path_eq(&self, other: &T) -> bool { + self.len() == other.len() && self.components().eq(other.components()) + } + + /// Compares this path against another path using path-component lexicographic + /// ordering. Strict prefixes are less than their longer counterparts. + /// + /// This is analogous to [`Iterator::cmp`] and is different than [`Ord`] + /// which may have different semantics depending on the underlying type and + /// representation as well as may not be implemented for the cross-type + /// comparisons. + fn path_cmp(&self, other: &T) -> std::cmp::Ordering { + self.components().cmp(other.components()) + } + + /// Returns a wrapper type that implements [`std::fmt::Display`] and + /// [`std::fmt::Debug`] for this path. + fn display(&self) -> DisplayPath<'_, Self> { + DisplayPath { path: self } + } +} + +/// Constructor for a trie path from a set of unpacked bytes; where each byte +/// is a whole path component regardless of the normal width of a path component. +/// +/// For 256-ary tries, this is the bytes as-is. +/// +/// For hexary tries, each byte must occupy only the lower 4 bits. Any byte with +/// a bit set in the upper 4 bits will result in an error. +pub trait TriePathFromUnpackedBytes<'input>: TriePath + Sized { + /// The error type returned if the bytes are invalid. + type Error; + + /// Constructs a path from the given unpacked bytes. + /// + /// For hexary tries, each byte must be in the range 0x00 to 0x0F inclusive. + /// Any byte outside this range will result in an error. + /// + /// # Errors + /// + /// - The input is invalid. + fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result; +} + +#[inline] +fn display_path( + f: &mut std::fmt::Formatter<'_>, + mut comp: impl Iterator, +) -> std::fmt::Result { + comp.try_for_each(|c| write!(f, "{c}")) +} + +/// A wrapper type that implements [`Display`](std::fmt::Display) and +/// [`Debug`](std::fmt::Debug) for any type that implements [`TriePath`]. +pub struct DisplayPath<'a, P: TriePath + ?Sized> { + path: &'a P, +} + +impl std::fmt::Debug for DisplayPath<'_, P> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + display_path(f, self.path.components()) + } +} + +impl std::fmt::Display for DisplayPath<'_, P> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + display_path(f, self.path.components()) + } +} + +impl TriePath for &T { + type Components<'a> + = T::Components<'a> + where + Self: 'a; + + fn len(&self) -> usize { + (**self).len() + } + + fn components(&self) -> Self::Components<'_> { + (**self).components() + } +} + +impl TriePath for &mut T { + type Components<'a> + = T::Components<'a> + where + Self: 'a; + + fn len(&self) -> usize { + (**self).len() + } + + fn components(&self) -> Self::Components<'_> { + (**self).components() + } +} + +impl TriePath for Box { + type Components<'a> + = T::Components<'a> + where + Self: 'a; + + fn len(&self) -> usize { + (**self).len() + } + + fn components(&self) -> Self::Components<'_> { + (**self).components() + } +} + +impl TriePath for std::rc::Rc { + type Components<'a> + = T::Components<'a> + where + Self: 'a; + + fn len(&self) -> usize { + (**self).len() + } + + fn components(&self) -> Self::Components<'_> { + (**self).components() + } +} + +impl TriePath for std::sync::Arc { + type Components<'a> + = T::Components<'a> + where + Self: 'a; + + fn len(&self) -> usize { + (**self).len() + } + + fn components(&self) -> Self::Components<'_> { + (**self).components() + } +} From 477bf6b555b23772f233d1f19e6839c83d028dbd Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 13 Oct 2025 09:27:14 -0700 Subject: [PATCH 0983/1053] feat(4/7): add SplitPath trait (#1339) One of the most common operations on paths is finding the longest common prefix between two paths. This is useful for a variety of reasons, such as determining if one path is a subpath of another, or finding the relative path between two paths. A normal `TriePath` implementation does not provide a way to do this because that trait should remain `?Sized`. Whereas, splitting the path requires a sized type in order to produce a new instance of the path when splitting. This change adds a `SplitPath` trait that provides the ability to split a path. Also added is `IntoSplitPath` which is a conversion trait to yield a splittable path. This also acts as the borrower for owned paths to yield shared references to the path. --- storage/src/lib.rs | 5 +- storage/src/path/joined.rs | 52 +++++++- storage/src/path/mod.rs | 2 + storage/src/path/split.rs | 250 +++++++++++++++++++++++++++++++++++++ 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 storage/src/path/split.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 108bc68cce74..f262dbb315d1 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -54,7 +54,10 @@ pub use nodestore::{ AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal, NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; -pub use path::{JoinedPath, PathComponent, TriePath, TriePathFromUnpackedBytes}; +pub use path::{ + IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, SplitPath, TriePath, + TriePathFromUnpackedBytes, +}; pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; diff --git a/storage/src/path/joined.rs b/storage/src/path/joined.rs index 87cffb94becd..2e4b9afd7496 100644 --- a/storage/src/path/joined.rs +++ b/storage/src/path/joined.rs @@ -3,7 +3,7 @@ use std::iter::Chain; -use super::TriePath; +use super::{SplitPath, TriePath}; /// Joins two path segments into a single path, retaining the original segments /// without needing to allocate a new contiguous array. @@ -46,3 +46,53 @@ impl TriePath for JoinedPath { self.prefix.components().chain(self.suffix.components()) } } + +impl SplitPath for JoinedPath { + fn split_at(self, mid: usize) -> (Self, Self) { + if let Some(mid) = mid.checked_sub(self.prefix.len()) { + let (a_suffix, b_suffix) = self.suffix.split_at(mid); + let prefix: Self = Self { + prefix: self.prefix, + suffix: a_suffix, + }; + let suffix = Self { + prefix: P::default(), + suffix: b_suffix, + }; + (prefix, suffix) + } else { + let (a_prefix, b_prefix) = self.prefix.split_at(mid); + let prefix = Self { + prefix: a_prefix, + suffix: S::default(), + }; + let suffix: Self = Self { + prefix: b_prefix, + suffix: self.suffix, + }; + (prefix, suffix) + } + } + + fn split_first(self) -> Option<(super::PathComponent, Self)> { + if let Some((first, prefix)) = self.prefix.split_first() { + Some(( + first, + Self { + prefix, + suffix: self.suffix, + }, + )) + } else if let Some((first, suffix)) = self.suffix.split_first() { + Some(( + first, + Self { + prefix: P::default(), + suffix, + }, + )) + } else { + None + } + } +} diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 1de32e7414e1..99fb06855efa 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -3,9 +3,11 @@ mod component; mod joined; +mod split; pub use self::component::PathComponent; pub use self::joined::JoinedPath; +pub use self::split::{IntoSplitPath, PathCommonPrefix, SplitPath}; /// A trie path of components with different underlying representations. /// diff --git a/storage/src/path/split.rs b/storage/src/path/split.rs new file mode 100644 index 000000000000..dc6d3eb5856a --- /dev/null +++ b/storage/src/path/split.rs @@ -0,0 +1,250 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use smallvec::SmallVec; + +use super::{PathComponent, TriePath}; + +/// A trie path that can be (cheaply) split into two sub-paths. +/// +/// Implementations are expected to be cheap to split (i.e. no allocations). +pub trait SplitPath: TriePath + Default + Copy { + /// Splits the path at the given index within the path. + /// + /// The returned tuple contains the two sub-paths `(prefix, suffix)`. + /// + /// # Panics + /// + /// - If `mid > self.len()`. + fn split_at(self, mid: usize) -> (Self, Self); + + /// Splits the first path component off of this path, returning it along with + /// the remaining path. + /// + /// Returns [`None`] if the path is empty. + fn split_first(self) -> Option<(PathComponent, Self)>; + + /// Computes the longest common prefix of this path and another path, along + /// with their respective suffixes. + fn longest_common_prefix(self, other: T) -> PathCommonPrefix { + PathCommonPrefix::new(self, other) + } +} + +/// A type that can be converted into a splittable path. +/// +/// This trait is analogous to [`IntoIterator`] for [`Iterator`] but for trie +/// paths instead of iterators. +/// +/// Like `IntoIterator`, a blanket implementation is provided for all types that +/// already implement [`SplitPath`]. +pub trait IntoSplitPath { + /// The splittable path type derived from this type. + type Path: SplitPath; + + /// Converts this type into a splittable path. + #[must_use] + fn into_split_path(self) -> Self::Path; +} + +impl IntoSplitPath for T { + type Path = T; + + #[inline] + fn into_split_path(self) -> Self::Path { + self + } +} + +/// The common prefix of two paths, along with their respective suffixes. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PathCommonPrefix { + /// The common prefix of the two paths. + pub common: C, + /// The suffix of the first path after the common prefix. + pub a_suffix: A, + /// The suffix of the second path after the common prefix. + pub b_suffix: B, +} + +impl PathCommonPrefix { + /// Computes the common prefix of the two given paths, along with their suffixes. + pub fn new(a: A, b: B) -> Self { + let mid = a + .components() + .zip(b.components()) + .take_while(|&(a, b)| a == b) + .count(); + let (common, a_suffix) = a.split_at(mid); + let (_, b_suffix) = b.split_at(mid); + Self { + common, + a_suffix, + b_suffix, + } + } +} + +impl SplitPath for &[PathComponent] { + fn split_at(self, mid: usize) -> (Self, Self) { + self.split_at(mid) + } + + fn split_first(self) -> Option<(PathComponent, Self)> { + match self.split_first() { + Some((&first, rest)) => Some((first, rest)), + None => None, + } + } +} + +impl<'a, const N: usize> IntoSplitPath for &'a [PathComponent; N] { + type Path = &'a [PathComponent]; + + fn into_split_path(self) -> Self::Path { + self + } +} + +impl<'a> IntoSplitPath for &'a Vec { + type Path = &'a [PathComponent]; + + fn into_split_path(self) -> Self::Path { + self + } +} + +impl<'a, A: smallvec::Array> IntoSplitPath for &'a SmallVec { + type Path = &'a [PathComponent]; + + fn into_split_path(self) -> Self::Path { + self + } +} + +#[cfg(test)] +mod tests { + use test_case::test_case; + + use super::*; + + /// Yields a [`PathComponent`] failing to compile if the given expression is + /// not a valid path component. + macro_rules! pc { + ($elem:expr) => { + const { PathComponent::ALL[$elem] } + }; + } + + /// Yields an array of [`PathComponent`]s failing to compile if any of the + /// given expressions are not valid path components. + /// + /// The expression yields an array, not a slice, and a reference must be taken + /// to convert it to a slice. + macro_rules! path { + ($($elem:expr),* $(,)?) => { + [ $( pc!($elem), )* ] + }; + } + + struct LcpTest<'a> { + a: &'a [PathComponent], + b: &'a [PathComponent], + expected_common: &'a [PathComponent], + expected_a_suffix: &'a [PathComponent], + expected_b_suffix: &'a [PathComponent], + } + + #[test_case( + LcpTest { + a: &path![1, 2, 3], + b: &path![1, 2, 3], + expected_common: &path![1, 2, 3], + expected_a_suffix: &path![], + expected_b_suffix: &path![], + }; + "identical paths" + )] + #[test_case( + LcpTest { + a: &path![1, 2, 3], + b: &path![1, 2, 4], + expected_common: &path![1, 2], + expected_a_suffix: &path![3], + expected_b_suffix: &path![4], + }; + "diverging paths" + )] + #[test_case( + LcpTest { + a: &path![1, 2, 3], + b: &path![1, 2, 3, 4, 5], + expected_common: &path![1, 2, 3], + expected_a_suffix: &path![], + expected_b_suffix: &path![4, 5], + }; + "a is a strict prefix of b" + )] + #[test_case( + LcpTest { + a: &path![1, 2, 3, 4, 5], + b: &path![1, 2, 3], + expected_common: &path![1, 2, 3], + expected_a_suffix: &path![4, 5], + expected_b_suffix: &path![], + }; + "b is a strict prefix of a" + )] + #[test_case( + LcpTest { + a: &path![1, 2, 3], + b: &path![4, 5, 6], + expected_common: &path![], + expected_a_suffix: &path![1, 2, 3], + expected_b_suffix: &path![4, 5, 6], + }; + "no common prefix" + )] + fn test_longest_common_prefix(case: LcpTest<'_>) { + let PathCommonPrefix { + common, + a_suffix, + b_suffix, + } = SplitPath::longest_common_prefix(case.a, case.b); + + assert_eq!(common, case.expected_common); + assert_eq!(a_suffix, case.expected_a_suffix); + assert_eq!(b_suffix, case.expected_b_suffix); + } + + struct SplitFirstTest<'a> { + path: &'a [PathComponent], + expected: Option<(PathComponent, &'a [PathComponent])>, + } + + #[test_case( + SplitFirstTest { + path: &path![], + expected: None, + }; + "empty path" + )] + #[test_case( + SplitFirstTest{ + path: &path![1], + expected: Some((pc!(1), &path![])), + }; + "single element path" + )] + #[test_case( + SplitFirstTest{ + path: &path![1, 2, 3], + expected: Some((pc!(1), &path![2, 3])), + }; + "path with multiple elements" + )] + fn test_split_first(case: SplitFirstTest<'_>) { + let result = SplitPath::split_first(case.path); + assert_eq!(result, case.expected); + } +} From 481125d8989273105a4b3dad583de87a59bf4bfa Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 13 Oct 2025 13:37:29 -0700 Subject: [PATCH 0984/1053] chore: upgrade dependencies (#1360) The transitive upgrade on `generic-array` caused a large number of deprecation warnings and is beginning to interfere with CI on pull requests. To resolve this, I replaced the generic-array with a plain array in TrieHash and annotated the remaining instances with the lint expectation. --- Cargo.lock | 405 +++++++++++++++------------ Cargo.toml | 10 +- benchmark/Cargo.toml | 10 +- ffi/src/value/display_hex.rs | 2 +- firewood/Cargo.toml | 2 +- firewood/src/merkle/tests/ethhash.rs | 2 + firewood/src/v2/api.rs | 1 + storage/Cargo.toml | 4 +- storage/src/hashers/ethhash.rs | 2 + storage/src/node/branch.rs | 5 +- storage/src/trie_hash.rs | 16 +- 11 files changed, 262 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e49800c59672..b3229e98ad1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,9 +71,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" dependencies = [ "bindgen", "cc", @@ -308,18 +308,18 @@ dependencies = [ [[package]] name = "bitfield" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a3a774b2fcac1b726922b921ebba5e9fe36ad37659c822cf8ff2c1e0819892" +checksum = "6bf79f42d21f18b5926a959280215903e659760da994835d27c3a0c5ff4f898f" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52511b09931f7d5fe3a14f23adefbc23e5725b184013e96c8419febb61f14734" +checksum = "6115af052c7914c0cbb97195e5c72cb61c511527250074f5c041d1048b0d8b16" dependencies = [ "proc-macro2", "quote", @@ -384,15 +384,15 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.39" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", "clap_derive", @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -538,9 +538,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -550,9 +550,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" @@ -590,14 +590,26 @@ dependencies = [ "libc", "once_cell", "unicode-width", - "windows-sys 0.61.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-hex" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", ] [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -744,9 +756,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +checksum = "59c9b8bdf64ee849747c1b12eb861d21aa47fa161564f48332f1afe2373bf899" dependencies = [ "ctor-proc-macro", "dtor", @@ -754,9 +766,9 @@ dependencies = [ [[package]] name = "ctor-proc-macro" -version = "0.0.6" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] name = "debugid" @@ -864,9 +876,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -918,7 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -936,9 +948,9 @@ dependencies = [ [[package]] name = "ethereum-types" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +checksum = "f7326303c6c18bb03c7a3c4c22d4032ae60dfe673ca9109602aa4d8154b2637e" dependencies = [ "ethbloom", "fixed-hash", @@ -987,9 +999,9 @@ dependencies = [ [[package]] name = "fastrace-opentelemetry" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65cf2a7ad300d531f04495e15720852fbf0f01f28e884ac29e22ddbb7228074e" +checksum = "b7e8ec7cff0ea398352764b6ee15c0902ccabf23d823525254b52d7f878fcf60" dependencies = [ "fastrace", "log", @@ -1006,9 +1018,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "findshlibs" @@ -1329,9 +1341,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1393,12 +1405,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1975,7 +1988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] @@ -1998,11 +2011,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -2210,9 +2222,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "opentelemetry" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" dependencies = [ "futures-core", "futures-sink", @@ -2224,9 +2236,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", @@ -2237,9 +2249,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ "http", "opentelemetry", @@ -2256,24 +2268,26 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ "base64", - "hex", + "const-hex", "opentelemetry", "opentelemetry_sdk", "prost", "serde", + "serde_json", "tonic", + "tonic-prost", ] [[package]] name = "opentelemetry_sdk" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" dependencies = [ "futures-channel", "futures-executor", @@ -2281,7 +2295,6 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.9.2", - "serde_json", "thiserror", ] @@ -2335,9 +2348,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2345,15 +2358,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2540,9 +2553,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +checksum = "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794" dependencies = [ "fixed-hash", "impl-codec", @@ -2557,7 +2570,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.6", + "toml_edit 0.23.7", ] [[package]] @@ -2591,11 +2604,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +dependencies = [ + "bitflags 2.9.4", + "lazy_static", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", +] + [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", @@ -2603,9 +2632,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools 0.14.0", @@ -2640,9 +2669,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -2738,6 +2767,15 @@ dependencies = [ "rand 0.9.2", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "rand_xoshiro" version = "0.7.0" @@ -2778,18 +2816,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -2799,9 +2837,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -2810,15 +2848,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -2915,7 +2953,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2955,9 +2993,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "aws-lc-rs", "ring", @@ -3001,7 +3039,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3018,9 +3056,9 @@ checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "security-framework" -version = "3.5.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.9.4", "core-foundation", @@ -3047,9 +3085,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -3057,18 +3095,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3099,9 +3137,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -3196,12 +3234,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3215,9 +3253,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -3319,7 +3357,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -3372,18 +3410,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3505,14 +3543,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", "toml_parser", "toml_writer", "winnow", @@ -3529,9 +3567,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] @@ -3552,21 +3590,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime 0.7.2", + "toml_datetime 0.7.3", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -3579,15 +3617,15 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tonic" -version = "0.13.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", "base64", @@ -3600,7 +3638,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", + "sync_wrapper", "tokio", "tokio-stream", "tower", @@ -3609,6 +3647,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.2" @@ -3701,9 +3750,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" dependencies = [ "serde", "stable_deref_trait", @@ -3727,7 +3776,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 0.9.7", + "toml 0.9.8", ] [[package]] @@ -3752,9 +3801,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uint" @@ -3768,6 +3817,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.19" @@ -3776,9 +3831,9 @@ checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -4013,7 +4068,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -4024,9 +4079,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -4037,9 +4092,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -4048,9 +4103,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -4059,24 +4114,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -4105,14 +4160,14 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -4135,19 +4190,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -4158,9 +4213,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -4170,9 +4225,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -4182,9 +4237,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -4194,9 +4249,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -4206,9 +4261,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -4218,9 +4273,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -4230,9 +4285,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -4242,9 +4297,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -4343,9 +4398,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 5f175f1b909f..ddb2490824bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,9 +62,9 @@ firewood-triehash = { path = "triehash", version = "0.0.13" } # common dependencies aquamarine = "0.6.0" -bytemuck = "1.23.2" -bytemuck_derive = "1.10.1" -clap = { version = "4.5.48", features = ["derive"] } +bytemuck = "1.24.0" +bytemuck_derive = "1.10.2" +clap = { version = "4.5.49", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" @@ -78,11 +78,11 @@ rand_distr = "0.5.1" sha2 = "0.10.9" smallvec = "1.15.1" test-case = "3.3.1" -thiserror = "2.0.16" +thiserror = "2.0.17" # common dev dependencies criterion = "0.7.0" -ethereum-types = "0.15.1" +ethereum-types = "0.16.0" hex-literal = "1.0.0" pprof = "0.15.0" rand = "0.9.2" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index e02f70a1f20c..e836d188d5d8 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -35,12 +35,12 @@ rand.workspace = true rand_distr.workspace = true sha2.workspace = true # Regular dependencies -fastrace-opentelemetry = { version = "0.13.0" } +fastrace-opentelemetry = { version = "=0.14.0" } metrics-exporter-prometheus = { version = "0.17.2", optional = true } -opentelemetry = "=0.30.0" -opentelemetry-otlp = { version = "=0.30.0", features = ["grpc-tonic"] } -opentelemetry-proto = "=0.30.0" -opentelemetry_sdk = "=0.30.0" +opentelemetry = "=0.31.0" +opentelemetry-otlp = { version = "=0.31.0", features = ["grpc-tonic"] } +opentelemetry-proto = "=0.31.0" +opentelemetry_sdk = "=0.31.0" pretty-duration = "0.1.1" [dependencies.tokio] diff --git a/ffi/src/value/display_hex.rs b/ffi/src/value/display_hex.rs index b0bb46a6cd08..10a6941c39a4 100644 --- a/ffi/src/value/display_hex.rs +++ b/ffi/src/value/display_hex.rs @@ -66,7 +66,7 @@ mod tests { #[test_case(firewood_storage::TrieHash::empty().as_ref(), "0000000000000000000000000000000000000000000000000000000000000000", None; "empty trie hash")] #[test_case(firewood_storage::TrieHash::empty().as_ref(), "00000000000000000000000000000000... (16 remaining bytes)", Some(16); "empty trie hash with precision")] #[cfg_attr(feature = "ethhash", test_case(firewood_storage::TrieHash::default_root_hash().as_deref().expect("feature = \"ethhash\""), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", None; "empty rlp hash"))] - #[cfg_attr(feature = "ethhash", test_case(firewood_storage::TrieHash::default_root_hash().as_ref().expect("feature = \"ethhash\""), "56e81f171bcc55a6ff8345e692c0f86e... (16 remaining bytes)", Some(16); "empty rlp hash with precision"))] + #[cfg_attr(feature = "ethhash", test_case(firewood_storage::TrieHash::default_root_hash().as_deref().expect("feature = \"ethhash\""), "56e81f171bcc55a6ff8345e692c0f86e... (16 remaining bytes)", Some(16); "empty rlp hash with precision"))] fn test_display_hex(input: &[u8], expected: &str, precision: Option) { let input = DisplayHex(input); if let Some(p) = precision { diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 852ee2c30b70..ff8a61651fe6 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -58,7 +58,7 @@ rand.workspace = true tempfile.workspace = true test-case.workspace = true # Regular dependencies -ctor = "0.5.0" +ctor = "0.6.0" hash-db = "0.16.0" plain_hasher = "0.2.3" rlp = "0.6.1" diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index 22e90118fa17..0d4c8c8615df 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -34,6 +34,7 @@ impl Hasher for KeccakHasher { let mut hasher = Keccak256::new(); hasher.update(x); let result = hasher.finalize(); + #[expect(deprecated, reason = "transitive dependency on generic-array")] H256::from_slice(result.as_slice()) } } @@ -90,6 +91,7 @@ fn test_eth_compatible_accounts( let expected_key_hash = Keccak256::digest(&account); let items = once(( + #[expect(deprecated, reason = "transitive dependency on generic-array")] Box::from(expected_key_hash.as_slice()), make_key(account_value), )) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 30217f227d2f..bc2f883e613b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -445,6 +445,7 @@ mod tests { #[test] #[cfg(feature = "ethhash")] + #[expect(deprecated, reason = "transitive dependency on generic-array")] fn test_ethhash_compat_default_root_hash_equals_empty_rlp_hash() { use sha3::Digest as _; diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 53fa5ffa7178..cf0c841a8a42 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -33,14 +33,14 @@ smallvec = { workspace = true, features = ["write", "union"] } thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" -bitfield = "0.19.2" +bitfield = "0.19.3" bitflags = "2.9.4" derive-where = "1.6.0" enum-as-inner = "0.6.1" indicatif = "0.18.0" lru = "0.16.1" semver = "1.0.27" -triomphe = "0.1.14" +triomphe = "0.1.15" # Optional dependencies bytes = { version = "1.10.1", optional = true } io-uring = { version = "0.7.10", optional = true } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index db60911dd8a9..66363c754fb4 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -125,6 +125,7 @@ impl Preimage for T { if is_account { // we are a leaf that is at depth 32 match self.value_digest() { + #[expect(deprecated, reason = "transitive dependency on generic-array")] Some(ValueDigest::Value(bytes)) => { let new_hash = Keccak256::digest(rlp::NULL_RLP).as_slice().to_vec(); let bytes_mut = BytesMut::from(bytes); @@ -241,6 +242,7 @@ impl Preimage for T { final_bytes.append(&&*nibbles_to_eth_compact(partial_path, is_account)); // if the RLP is short enough, we can use it as-is, otherwise we hash it // to make the maximum length 32 bytes + #[expect(deprecated, reason = "transitive dependency on generic-array")] if updated_bytes.len() > 31 && !is_account { let hashed_bytes = Keccak256::digest(updated_bytes); final_bytes.append(&hashed_bytes.as_slice()); diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 0813d5168f16..2b7473316b75 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -232,6 +232,7 @@ mod ethhash { fn eq(&self, other: &TrieHash) -> bool { match self { HashOrRlp::Hash(h) => h == other, + #[expect(deprecated, reason = "transitive dependency on generic-array")] HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == other.as_ref(), } } @@ -241,6 +242,7 @@ mod ethhash { fn eq(&self, other: &HashOrRlp) -> bool { match other { HashOrRlp::Hash(h) => h == self, + #[expect(deprecated, reason = "transitive dependency on generic-array")] HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == self.as_ref(), } } @@ -251,6 +253,7 @@ mod ethhash { match (self, other) { (HashOrRlp::Hash(h1), HashOrRlp::Hash(h2)) => h1 == h2, (HashOrRlp::Rlp(r1), HashOrRlp::Rlp(r2)) => r1 == r2, + #[expect(deprecated, reason = "transitive dependency on generic-array")] (HashOrRlp::Hash(h), HashOrRlp::Rlp(r)) | (HashOrRlp::Rlp(r), HashOrRlp::Hash(h)) => { Keccak256::digest(r.as_ref()).as_slice() == h.as_ref() @@ -342,7 +345,7 @@ mod ethhash { type Target = [u8]; fn deref(&self) -> &Self::Target { match self { - HashOrRlp::Hash(h) => h, + HashOrRlp::Hash(h) => h.as_slice(), HashOrRlp::Rlp(r) => r, } } diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index 6e2807e1efdb..b114a09eb551 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -3,6 +3,7 @@ use crate::node::ExtendableBytes; use crate::node::branch::Serializable; +#[expect(deprecated, reason = "transitive dependency on generic-array")] use sha2::digest::generic_array::GenericArray; use sha2::digest::typenum; use std::fmt::{self, Debug, Display, Formatter}; @@ -16,7 +17,7 @@ pub struct InvalidTrieHashLength(pub usize); /// A hash value inside a merkle trie /// We use the same type as returned by sha2 here to avoid copies #[derive(PartialEq, Eq, Clone, Hash)] -pub struct TrieHash(GenericArray); +pub struct TrieHash([u8; 32]); /// Intentionally, there is no [`Default`] implementation for [`TrieHash`] to force /// the user to explicitly decide between an empty RLP hash or a hash of all zeros. @@ -33,13 +34,13 @@ impl TrieHash { /// ) /// ``` #[must_use] - pub fn empty() -> Self { - TrieHash([0; TRIE_HASH_LEN].into()) + pub const fn empty() -> Self { + TrieHash([0; TRIE_HASH_LEN]) } } impl std::ops::Deref for TrieHash { - type Target = GenericArray; + type Target = [u8; 32]; fn deref(&self) -> &Self::Target { &self.0 } @@ -68,13 +69,13 @@ const TRIE_HASH_LEN: usize = std::mem::size_of::(); impl From<[u8; TRIE_HASH_LEN]> for TrieHash { fn from(value: [u8; TRIE_HASH_LEN]) -> Self { - TrieHash(value.into()) + TrieHash(value) } } impl From for [u8; TRIE_HASH_LEN] { fn from(value: TrieHash) -> Self { - value.0.into() + value.0 } } @@ -89,9 +90,10 @@ impl TryFrom<&[u8]> for TrieHash { } } +#[expect(deprecated, reason = "transitive dependency on generic-array")] impl From> for TrieHash { fn from(value: GenericArray) -> Self { - TrieHash(value) + TrieHash(value.into()) } } From db2c8568c7fe1bb95a764fe8d4866cc64c62de8c Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:25:02 -0400 Subject: [PATCH 0985/1053] chore(nodestore): access persisted nodestores (#1355) This PR breaks up #1346 by factoring out the `NodeStore` changes. This PR allows for persisted nodestores which are no longer in memory to be retrieved again. This is useful for `RootStore` as this is what it'll use to construct prior revisions in the db `view()` method. --- firewood/src/db.rs | 49 +++++++++++++++++++++++++++++++-- storage/src/nodestore/header.rs | 6 +++- storage/src/nodestore/mod.rs | 44 +++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 503551024ceb..c1c6f57ad778 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -318,10 +318,10 @@ mod test { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; - use firewood_storage::{CheckOpt, CheckerError}; + use firewood_storage::{CheckOpt, CheckerError, HashedNodeReader, IntoHashType, NodeStore}; use crate::db::{Db, Proposal}; - use crate::v2::api::{Db as _, DbView as _, KeyValuePairIter, Proposal as _}; + use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -766,6 +766,51 @@ mod test { }); } + #[test] + fn test_resurrect_unpersisted_root() { + let db = TestDb::new(); + + // First, create a revision to retrieve + let key = b"key"; + let value = b"value"; + let batch = vec![BatchOp::Put { key, value }]; + + let proposal = db.propose(batch).unwrap(); + let root_hash = proposal.root_hash().unwrap().unwrap(); + proposal.commit().unwrap(); + + let root_address = db + .revision(root_hash.clone()) + .unwrap() + .root_address() + .unwrap(); + + // Next, overwrite the kv-pair with a new revision + let new_value = b"new_value"; + let batch = vec![BatchOp::Put { + key, + value: new_value, + }]; + + let proposal = db.propose(batch).unwrap(); + proposal.commit().unwrap(); + + // Finally, reopen the database and make sure that we can retrieve the first revision + let db = db.reopen(); + + let latest_root_hash = db.root_hash().unwrap().unwrap(); + let latest_revision = db.revision(latest_root_hash).unwrap(); + + let latest_value = latest_revision.val(key).unwrap().unwrap(); + assert_eq!(new_value, latest_value.as_ref()); + + let node_store = + NodeStore::with_root(root_hash.into_hash_type(), root_address, latest_revision); + + let retrieved_value = node_store.val(key).unwrap().unwrap(); + assert_eq!(value, retrieved_value.as_ref()); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear struct TestDb { db: Db, diff --git a/storage/src/nodestore/header.rs b/storage/src/nodestore/header.rs index 70ba78bff308..8364ca9e573a 100644 --- a/storage/src/nodestore/header.rs +++ b/storage/src/nodestore/header.rs @@ -188,11 +188,15 @@ impl NodeStoreHeader { } pub fn new() -> Self { + Self::with_root(None) + } + + pub fn with_root(root_address: Option) -> Self { Self { // The store just contains the header at this point size: Self::SIZE, endian_test: 1, - root_address: None, + root_address, version: Version::new(), free_lists: Default::default(), area_size_hash: area_size_hash() diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index f29fcf408fc0..880c421b2941 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -151,6 +151,50 @@ impl NodeStore { }, } } + + /// Create a new Committed [`NodeStore`] with a specified root node. + /// + /// This constructor is used when you have an existing root node at a known + /// address and hash, typically when reconstructing a [`NodeStore`] from + /// a committed state. The `latest_nodestore` provides access to the underlying + /// storage backend containing the persisted trie data. + /// + /// ## Panics + /// + /// Panics in debug builds if the hash of the node at `root_address` does + /// not equal `root_hash`. + #[must_use] + pub fn with_root( + root_hash: HashType, + root_address: LinearAddress, + latest_nodestore: Arc>, + ) -> Self { + let header = NodeStoreHeader::with_root(Some(root_address)); + let storage = latest_nodestore.storage.clone(); + + let nodestore = NodeStore { + header, + kind: Committed { + deleted: Box::default(), + root: Some(Child::AddressWithHash(root_address, root_hash)), + }, + storage, + }; + + debug_assert_eq!( + nodestore + .root_hash() + .expect("Nodestore should have root hash"), + hash_node( + &nodestore + .read_node(root_address) + .expect("Root node read should succeed"), + &Path(SmallVec::default()) + ) + ); + + nodestore + } } /// Some nodestore kinds implement Parentable. From bf31fe48706ae8954a97d7936dd2624adcd78a03 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:40:59 -0400 Subject: [PATCH 0986/1053] chore: update Go to 1.24.8 (#1361) Go 1.24.7 had a bunch of vulnerable packages in it (nothing we use directly). However, let's use a stable version of Go for consistency. --- ffi/go.mod | 2 +- ffi/tests/eth/go.mod | 2 +- ffi/tests/firewood/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ffi/go.mod b/ffi/go.mod index 651601d7e2f7..ebb119023b78 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi go 1.24 -toolchain go1.24.7 +toolchain go1.24.8 require ( github.com/prometheus/client_golang v1.22.0 diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index 976f6f7ab911..b680bc0f8e04 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi/tests go 1.24 -toolchain go1.24.7 +toolchain go1.24.8 require ( github.com/ava-labs/firewood-go-ethhash/ffi v0.0.0 // this is replaced to use the parent folder diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index 577783c798cf..dab19db72f49 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi/tests go 1.24 -toolchain go1.24.7 +toolchain go1.24.8 require ( github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder From 48ef23d40028b7237f87ef8979e4bf92d9e8a7cc Mon Sep 17 00:00:00 2001 From: AminR443 Date: Tue, 14 Oct 2025 21:07:37 -0400 Subject: [PATCH 0987/1053] feat(ffi-iterator): Batching support for Iterator (3/4) (#1257) This PR introduces batched retrieval from iterator, to reduce overheads introduced from too many calls to FFI. Depends On: #1256 --------- Co-authored-by: Brandon LeBlanc --- ffi/firewood.h | 117 +++++++++++++++++- ffi/firewood_test.go | 250 +++++++++++++++++++++++++++++++-------- ffi/iterator.go | 117 +++++++++++++++--- ffi/memory.go | 103 ++++++++++++++-- ffi/src/iterator.rs | 17 ++- ffi/src/lib.rs | 70 ++++++++++- ffi/src/value.rs | 7 +- ffi/src/value/kvp.rs | 5 +- ffi/src/value/results.rs | 35 +++++- 9 files changed, 626 insertions(+), 95 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 88c8ae1ebf84..46291149cbe3 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -663,6 +663,22 @@ typedef struct OwnedKeyValuePair { OwnedBytes value; } OwnedKeyValuePair; +/** + * A Rust-owned vector of bytes that can be passed to C code. + * + * C callers must free this memory using the respective FFI function for the + * concrete type (but not using the `free` function from the C standard library). + */ +typedef struct OwnedSlice_OwnedKeyValuePair { + struct OwnedKeyValuePair *ptr; + size_t len; +} OwnedSlice_OwnedKeyValuePair; + +/** + * A type alias for a rust-owned byte slice. + */ +typedef struct OwnedSlice_OwnedKeyValuePair OwnedKeyValueBatch; + /** * A result type returned from FFI functions that get a revision */ @@ -764,6 +780,42 @@ typedef struct KeyValueResult { }; } KeyValueResult; +/** + * A result type returned from iterator FFI functions + */ +typedef enum KeyValueBatchResult_Tag { + /** + * The caller provided a null pointer to an iterator handle. + */ + KeyValueBatchResult_NullHandlePointer, + /** + * The next batch of items on iterator are returned. + */ + KeyValueBatchResult_Some, + /** + * An error occurred and the message is returned as an [`OwnedBytes`]. If + * value is guaranteed to contain only valid UTF-8. + * + * The caller must call [`fwd_free_owned_bytes`] to free the memory + * associated with this error. + * + * [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + */ + KeyValueBatchResult_Err, +} KeyValueBatchResult_Tag; + +typedef struct KeyValueBatchResult { + KeyValueBatchResult_Tag tag; + union { + struct { + OwnedKeyValueBatch some; + }; + struct { + OwnedBytes err; + }; + }; +} KeyValueBatchResult; + /** * A result type returned from FFI functions that create an iterator */ @@ -1339,6 +1391,27 @@ struct VoidResult fwd_free_iterator(struct IteratorHandle *iterator); */ struct VoidResult fwd_free_owned_bytes(OwnedBytes bytes); +/** + * Consumes the [`OwnedKeyValueBatch`] and frees the memory associated with it. + * + * # Arguments + * + * * `batch` - The [`OwnedKeyValueBatch`] struct to free, previously returned from any + * function from this library. + * + * # Returns + * + * - [`VoidResult::Ok`] if the memory was successfully freed. + * - [`VoidResult::Err`] if the process panics while freeing the memory. + * + * # Safety + * + * The caller must ensure that the `batch` struct is valid and that the memory + * it points to is uniquely owned by this object. However, if `batch.ptr` is null, + * this function does nothing. + */ +struct VoidResult fwd_free_owned_key_value_batch(OwnedKeyValueBatch batch); + /** * Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. * @@ -1575,7 +1648,7 @@ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); /** - * Retrieves the next item from the iterator + * Retrieves the next item from the iterator. * * # Arguments * @@ -1585,21 +1658,55 @@ struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, Borrowed * # Returns * * - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. - * - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. + * - [`KeyValueResult::None`] if the iterator is exhausted (no remaining values). Once returned, + * subsequent calls will continue returning [`KeyValueResult::None`]. You may still call this + * safely, but freeing the iterator with [`fwd_free_iterator`] is recommended. * - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated * key value pair. - * - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. + * - [`KeyValueResult::Err`] if an I/O error occurred while retrieving the next item. Most + * iterator errors are non-reentrant. Once returned, the iterator should be considered + * invalid and must be freed with [`fwd_free_iterator`]. * * # Safety * * The caller must: * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. - * * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] - * to free the memory associated with the returned error or value. + * * call [`fwd_free_owned_kv_pair`] on returned [`OwnedKeyValuePair`] + * to free the memory associated with the returned value. * */ struct KeyValueResult fwd_iter_next(struct IteratorHandle *handle); +/** + * Retrieves the next batch of items from the iterator. + * + * # Arguments + * + * * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or + * [`fwd_iter_on_proposal`]. + * + * # Returns + * + * - [`KeyValueBatchResult::NullHandlePointer`] if the provided iterator handle is null. + * - [`KeyValueBatchResult::Some`] with up to `n` key/value pairs. If the iterator is + * exhausted, this may be fewer than `n`, including zero items. + * - [`KeyValueBatchResult::Err`] if an I/O error occurred while retrieving items. Most + * iterator errors are non-reentrant. Once returned, the iterator should be considered + * invalid and must be freed with [`fwd_free_iterator`]. + * + * Once an empty batch or items fewer than `n` is returned (iterator exhausted), subsequent calls + * will continue returning empty batches. You may still call this safely, but freeing the + * iterator with [`fwd_free_iterator`] is recommended. + * + * # Safety + * + * The caller must: + * * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. + * * call [`fwd_free_owned_key_value_batch`] on the returned batch to free any allocated memory. + * + */ +struct KeyValueBatchResult fwd_iter_next_n(struct IteratorHandle *handle, size_t n); + /** * Returns an iterator on the provided proposal optionally starting from a key * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 88523b10fe87..2f642b6e81cf 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -6,10 +6,12 @@ package ffi import ( "bytes" "encoding/hex" + "errors" "fmt" "os" "path/filepath" "runtime" + "slices" "strconv" "strings" "sync" @@ -247,9 +249,41 @@ func kvForTest(num int) ([][]byte, [][]byte) { keys[i] = keyForTest(i) vals[i] = valForTest(i) } + _ = sortKV(keys, vals) return keys, vals } +// sortKV sorts keys lexicographically and keeps vals paired. +func sortKV(keys, vals [][]byte) error { + if len(keys) != len(vals) { + return errors.New("keys/vals length mismatch") + } + n := len(keys) + if n <= 1 { + return nil + } + ord := make([]int, n) + for i := range ord { + ord[i] = i + } + slices.SortFunc(ord, func(i, j int) int { + return bytes.Compare(keys[i], keys[j]) + }) + perm := make([]int, n) + for dest, orig := range ord { + perm[orig] = dest + } + for i := 0; i < n; i++ { + for perm[i] != i { + j := perm[i] + keys[i], keys[j] = keys[j], keys[i] + vals[i], vals[j] = vals[j], vals[i] + perm[i], perm[j] = perm[j], j + } + } + return nil +} + // Tests that 100 key-value pairs can be inserted and retrieved. // This happens in three ways: // 1. By calling [Database.Propose] and then [Proposal.Commit]. @@ -1137,7 +1171,24 @@ func TestGetFromRootParallel(t *testing.T) { } } -func assertIteratorYields(r *require.Assertions, it *Iterator, keys [][]byte, vals [][]byte) { +type kvIter interface { + SetBatchSize(int) + Next() bool + Key() []byte + Value() []byte + Err() error + Drop() error +} +type borrowIter struct{ it *Iterator } + +func (b *borrowIter) SetBatchSize(batchSize int) { b.it.SetBatchSize(batchSize) } +func (b *borrowIter) Next() bool { return b.it.NextBorrowed() } +func (b *borrowIter) Key() []byte { return b.it.Key() } +func (b *borrowIter) Value() []byte { return b.it.Value() } +func (b *borrowIter) Err() error { return b.it.Err() } +func (b *borrowIter) Drop() error { return b.it.Drop() } + +func assertIteratorYields(r *require.Assertions, it kvIter, keys [][]byte, vals [][]byte) { i := 0 for ; it.Next(); i += 1 { r.Equal(keys[i], it.Key()) @@ -1147,82 +1198,143 @@ func assertIteratorYields(r *require.Assertions, it *Iterator, keys [][]byte, va r.Equal(len(keys), i) } +type iteratorConfigFn = func(it kvIter) kvIter + +var iterConfigs = map[string]iteratorConfigFn{ + "Owned": func(it kvIter) kvIter { return it }, + "Borrowed": func(it kvIter) kvIter { return &borrowIter{it: it.(*Iterator)} }, + "Single": func(it kvIter) kvIter { + it.SetBatchSize(1) + return it + }, + "Batched": func(it kvIter) kvIter { + it.SetBatchSize(100) + return it + }, +} + +func runIteratorTestForModes(t *testing.T, fn func(*testing.T, iteratorConfigFn), modes ...string) { + testName := strings.Join(modes, "/") + t.Run(testName, func(t *testing.T) { + r := require.New(t) + fn(t, func(it kvIter) kvIter { + for _, m := range modes { + config, ok := iterConfigs[m] + r.Truef(ok, "specified config mode %s does not exist", m) + it = config(it) + } + return it + }) + }) +} + +func runIteratorTestForAllModes(parentT *testing.T, fn func(*testing.T, iteratorConfigFn)) { + for _, dataMode := range []string{"Owned", "Borrowed"} { + for _, batchMode := range []string{"Single", "Batched"} { + runIteratorTestForModes(parentT, fn, batchMode, dataMode) + } + } +} + // Tests that basic iterator functionality works func TestIter(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - - keys, vals := kvForTest(10) + keys, vals := kvForTest(100) _, err := db.Update(keys, vals) r.NoError(err) - rev, err := db.LatestRevision() - r.NoError(err) - it, err := rev.Iter(nil) - r.NoError(err) - t.Cleanup(func() { - r.NoError(it.Drop()) - r.NoError(rev.Drop()) - }) + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) - assertIteratorYields(r, it, keys, vals) + assertIteratorYields(r, cfn(it), keys, vals) + }) } -// Tests that iterators on different roots work fine func TestIterOnRoot(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - - // Commit 10 key-value pairs. - keys, vals := kvForTest(20) - keys = keys[:10] - vals1, vals2 := vals[:10], vals[10:] - - firstRoot, err := db.Update(keys, vals1) - r.NoError(err) - - // we use the same keys, but update the values - secondRoot, err := db.Update(keys, vals2) + keys, vals := kvForTest(240) + firstRoot, err := db.Update(keys[:80], vals[:80]) r.NoError(err) - - r1, err := db.Revision(firstRoot) + secondRoot, err := db.Update(keys[80:160], vals[80:160]) r.NoError(err) - h1, err := r1.Iter(nil) + thirdRoot, err := db.Update(keys[160:], vals[160:]) r.NoError(err) - t.Cleanup(func() { - r.NoError(h1.Drop()) - r.NoError(r1.Drop()) - }) - r2, err := db.Revision(secondRoot) - r.NoError(err) - h2, err := r2.Iter(nil) - r.NoError(err) - t.Cleanup(func() { - r.NoError(h2.Drop()) - r.NoError(r2.Drop()) - }) + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + r1, err := db.Revision(firstRoot) + r.NoError(err) + h1, err := r1.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h1.Drop()) + r.NoError(r1.Drop()) + }) - assertIteratorYields(r, h1, keys, vals1) - assertIteratorYields(r, h2, keys, vals2) + r2, err := db.Revision(secondRoot) + r.NoError(err) + h2, err := r2.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h2.Drop()) + r.NoError(r2.Drop()) + }) + + r3, err := db.Revision(thirdRoot) + r.NoError(err) + h3, err := r3.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(h3.Drop()) + r.NoError(r3.Drop()) + }) + + assertIteratorYields(r, cfn(h1), keys[:80], vals[:80]) + assertIteratorYields(r, cfn(h2), keys[:160], vals[:160]) + assertIteratorYields(r, cfn(h3), keys, vals) + }) } -// Tests that basic iterator functionality works for proposal func TestIterOnProposal(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - - keys, vals := kvForTest(10) - p, err := db.Propose(keys, vals) + keys, vals := kvForTest(240) + _, err := db.Update(keys, vals) r.NoError(err) - it, err := p.Iter(nil) - r.NoError(err) - t.Cleanup(func() { - r.NoError(it.Drop()) - }) + runIteratorTestForAllModes(t, func(t *testing.T, cfn iteratorConfigFn) { + r := require.New(t) + updatedValues := make([][]byte, len(vals)) + copy(updatedValues, vals) - assertIteratorYields(r, it, keys, vals) + changedKeys := make([][]byte, 0) + changedVals := make([][]byte, 0) + for i := 0; i < len(vals); i += 4 { + changedKeys = append(changedKeys, keys[i]) + newVal := []byte{byte(i)} + changedVals = append(changedVals, newVal) + updatedValues[i] = newVal + } + p, err := db.Propose(changedKeys, changedVals) + r.NoError(err) + it, err := p.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + }) + + assertIteratorYields(r, cfn(it), keys, updatedValues) + }) } // Tests that the iterator still works after proposal is committed @@ -1277,3 +1389,39 @@ func TestIterUpdate(t *testing.T) { // because iterator is fixed on the revision hash, it should return the initial values assertIteratorYields(r, it, keys, vals) } + +// Tests the iterator's behavior after exhaustion, should safely return empty item/batch, indicating done +func TestIterDone(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(18) + _, err := db.Update(keys, vals) + r.NoError(err) + + // get an iterator on latest revision + rev, err := db.LatestRevision() + r.NoError(err) + it, err := rev.Iter(nil) + r.NoError(err) + t.Cleanup(func() { + r.NoError(it.Drop()) + r.NoError(rev.Drop()) + }) + // consume the iterator + assertIteratorYields(r, it, keys, vals) + // calling next again should be safe and return false + r.False(it.Next()) + r.NoError(it.Err()) + + // get a new iterator + it2, err := rev.Iter(nil) + r.NoError(err) + // set batch size to 5 + it2.SetBatchSize(5) + // consume the iterator + assertIteratorYields(r, it2, keys, vals) + // calling next again should be safe and return false + r.False(it.Next()) + r.NoError(it.Err()) +} diff --git a/ffi/iterator.go b/ffi/iterator.go index 9bf5fa190ecb..08a7cf7796d2 100644 --- a/ffi/iterator.go +++ b/ffi/iterator.go @@ -8,6 +8,7 @@ package ffi import "C" import ( + "errors" "fmt" "unsafe" ) @@ -19,33 +20,112 @@ type Iterator struct { // It is not safe to call these methods with a nil handle. handle *C.IteratorHandle - // currentKey is the current key retrieved from the iterator - currentKey []byte - // currentVal is the current value retrieved from the iterator - currentVal []byte + // batchSize is the number of items that are loaded at once + // to reduce ffi call overheads + batchSize int + + // loadedPairs is the latest loaded key value pairs retrieved + // from the iterator, not yet consumed by user + loadedPairs []*ownedKeyValue + + // current* fields correspond to the current cursor state + // nil/empty if not started or exhausted; refreshed on each Next(). + currentPair *ownedKeyValue + currentKey []byte + currentValue []byte + // FFI resource for current pair or batch to free on advance or drop + currentResource interface{ free() error } + // err is the error from the iterator, if any err error } +func (it *Iterator) freeCurrentAllocation() error { + if it.currentResource == nil { + return nil + } + e := it.currentResource.free() + it.currentResource = nil + return e +} + +func (it *Iterator) nextInternal() error { + if len(it.loadedPairs) > 0 { + it.currentPair, it.loadedPairs = it.loadedPairs[0], it.loadedPairs[1:] + return nil + } + + // current resources should **only** be freed, on the next call to the FFI + // this is to make sure we don't invalidate a batch in between iteration + if e := it.freeCurrentAllocation(); e != nil { + return e + } + if it.batchSize <= 1 { + kv, e := getKeyValueFromResult(C.fwd_iter_next(it.handle)) + if e != nil { + return e + } + it.currentPair = kv + it.currentResource = kv + } else { + batch, e := getKeyValueBatchFromResult(C.fwd_iter_next_n(it.handle, C.size_t(it.batchSize))) + if e != nil { + return e + } + pairs := batch.copy() + if len(pairs) > 0 { + it.currentPair, it.loadedPairs = pairs[0], pairs[1:] + } else { + it.currentPair = nil + } + it.currentResource = batch + } + + return nil +} + +// SetBatchSize sets the max number of pairs to be retrieved in one ffi call. +func (it *Iterator) SetBatchSize(batchSize int) { + it.batchSize = batchSize +} + // Next proceeds to the next item on the iterator, and returns true // if succeeded and there is a pair available. // The new pair could be retrieved with Key and Value methods. func (it *Iterator) Next() bool { - kv, e := getKeyValueFromKeyValueResult(C.fwd_iter_next(it.handle)) - it.err = e - if kv == nil || e != nil { + it.err = it.nextInternal() + if it.currentPair == nil || it.err != nil { return false } - k, v, e := kv.Consume() + k, v := it.currentPair.copy() it.currentKey = k - it.currentVal = v - it.err = e - return e == nil + it.currentValue = v + return true +} + +// NextBorrowed is like Next, but Key and Value **borrow** rust-owned buffers. +// +// Lifetime: the returned slices are valid **only until** the next call to +// Next, NextBorrowed, Close, or any operation that advances/invalidates the iterator. +// They alias FFI-owned memory that will be **freed or reused** on the next advance. +// +// Do **not** retain, store, or modify these slices. +// **Copy** or use Next if you need to keep them. +// Misuse can read freed memory and cause undefined behavior. +func (it *Iterator) NextBorrowed() bool { + it.err = it.nextInternal() + if it.currentPair == nil || it.err != nil { + return false + } + it.currentKey = it.currentPair.key.BorrowedBytes() + it.currentValue = it.currentPair.value.BorrowedBytes() + it.err = nil + return true } // Key returns the key of the current pair func (it *Iterator) Key() []byte { - if (it.currentKey == nil && it.currentVal == nil) || it.err != nil { + if it.currentPair == nil || it.err != nil { return nil } return it.currentKey @@ -53,10 +133,10 @@ func (it *Iterator) Key() []byte { // Value returns the value of the current pair func (it *Iterator) Value() []byte { - if (it.currentKey == nil && it.currentVal == nil) || it.err != nil { + if it.currentPair == nil || it.err != nil { return nil } - return it.currentVal + return it.currentValue } // Err returns the error if Next failed @@ -66,10 +146,15 @@ func (it *Iterator) Err() error { // Drop drops the iterator and releases the resources func (it *Iterator) Drop() error { + err := it.freeCurrentAllocation() if it.handle != nil { - return getErrorFromVoidResult(C.fwd_free_iterator(it.handle)) + // Always free the iterator even if releasing the current KV/batch failed. + // The iterator holds a NodeStore ref that must be dropped. + return errors.Join( + err, + getErrorFromVoidResult(C.fwd_free_iterator(it.handle))) } - return nil + return err } // getIteratorFromIteratorResult converts a C.IteratorResult to an Iterator or error. diff --git a/ffi/memory.go b/ffi/memory.go index 0e2c632895fc..8819459cc658 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -309,21 +309,83 @@ func getValueFromValueResult(result C.ValueResult) ([]byte, error) { } } +type ownedKeyValueBatch struct { + owned C.OwnedKeyValueBatch +} + +func (b *ownedKeyValueBatch) copy() []*ownedKeyValue { + if b.owned.ptr == nil { + return nil + } + borrowed := b.borrow() + copied := make([]*ownedKeyValue, len(borrowed)) + for i, borrow := range borrowed { + copied[i] = newOwnedKeyValue(borrow) + } + return copied +} + +func (b *ownedKeyValueBatch) borrow() []C.OwnedKeyValuePair { + if b.owned.ptr == nil { + return nil + } + + return unsafe.Slice((*C.OwnedKeyValuePair)(unsafe.Pointer(b.owned.ptr)), b.owned.len) +} + +func (b *ownedKeyValueBatch) free() error { + if b == nil || b.owned.ptr == nil { + // we want ownedKeyValueBatch to be typed-nil safe + return nil + } + + if err := getErrorFromVoidResult(C.fwd_free_owned_key_value_batch(b.owned)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + + b.owned = C.OwnedKeyValueBatch{} + + return nil +} + +// newOwnedKeyValueBatch creates a ownedKeyValueBatch from a C.OwnedKeyValueBatch. +// +// The caller is responsible for calling Free() on the returned ownedKeyValue +// when it is no longer needed otherwise memory will leak. +func newOwnedKeyValueBatch(owned C.OwnedKeyValueBatch) *ownedKeyValueBatch { + return &ownedKeyValueBatch{ + owned: owned, + } +} + type ownedKeyValue struct { + // owned holds the original C-provided pair so we can free it + // with fwd_free_owned_kv_pair instead of freeing key/value separately. + owned C.OwnedKeyValuePair + // key and value wrappers provide Borrowed/Copied accessors key *ownedBytes value *ownedBytes } -func (kv *ownedKeyValue) Consume() ([]byte, []byte, error) { +func (kv *ownedKeyValue) copy() ([]byte, []byte) { key := kv.key.CopiedBytes() - if err := kv.key.Free(); err != nil { - return nil, nil, fmt.Errorf("%w: %w", errFreeingValue, err) - } value := kv.value.CopiedBytes() - if err := kv.value.Free(); err != nil { - return nil, nil, fmt.Errorf("%w: %w", errFreeingValue, err) + return key, value +} + +func (kv *ownedKeyValue) free() error { + if kv == nil { + // we want ownedKeyValue to be typed-nil safe + return nil } - return key, value, nil + if err := getErrorFromVoidResult(C.fwd_free_owned_kv_pair(kv.owned)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + // zero out fields to avoid accidental reuse/double free + kv.owned = C.OwnedKeyValuePair{} + kv.key = nil + kv.value = nil + return nil } // newOwnedKeyValue creates a ownedKeyValue from a C.OwnedKeyValuePair. @@ -332,17 +394,18 @@ func (kv *ownedKeyValue) Consume() ([]byte, []byte, error) { // when it is no longer needed otherwise memory will leak. func newOwnedKeyValue(owned C.OwnedKeyValuePair) *ownedKeyValue { return &ownedKeyValue{ + owned: owned, key: newOwnedBytes(owned.key), value: newOwnedBytes(owned.value), } } -// getKeyValueFromKeyValueResult converts a C.KeyValueResult to a key value pair or error. +// getKeyValueFromResult converts a C.KeyValueResult to a key value pair or error. // // It returns nil, nil if the result is None. // It returns a *ownedKeyValue, nil if the result is Some. // It returns an error if the result is an error. -func getKeyValueFromKeyValueResult(result C.KeyValueResult) (*ownedKeyValue, error) { +func getKeyValueFromResult(result C.KeyValueResult) (*ownedKeyValue, error) { switch result.tag { case C.KeyValueResult_NullHandlePointer: return nil, errDBClosed @@ -351,7 +414,7 @@ func getKeyValueFromKeyValueResult(result C.KeyValueResult) (*ownedKeyValue, err case C.KeyValueResult_Some: ownedKvp := newOwnedKeyValue(*(*C.OwnedKeyValuePair)(unsafe.Pointer(&result.anon0))) return ownedKvp, nil - case C.ValueResult_Err: + case C.KeyValueResult_Err: err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() return nil, err default: @@ -359,6 +422,26 @@ func getKeyValueFromKeyValueResult(result C.KeyValueResult) (*ownedKeyValue, err } } +// getKeyValueBatchFromResult converts a C.KeyValueBatchResult to a key value batch or error. +// +// It returns nil, nil if the result is None. +// It returns a *ownedKeyValueBatch, nil if the result is Some. +// It returns an error if the result is an error. +func getKeyValueBatchFromResult(result C.KeyValueBatchResult) (*ownedKeyValueBatch, error) { + switch result.tag { + case C.KeyValueBatchResult_NullHandlePointer: + return nil, errDBClosed + case C.KeyValueBatchResult_Some: + ownedBatch := newOwnedKeyValueBatch(*(*C.OwnedKeyValueBatch)(unsafe.Pointer(&result.anon0))) + return ownedBatch, nil + case C.KeyValueBatchResult_Err: + err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() + return nil, err + default: + return nil, fmt.Errorf("unknown C.KeyValueBatchResult tag: %d", result.tag) + } +} + // getDatabaseFromHandleResult converts a C.HandleResult to a Database or error. // // If the C.HandleResult is an error, it returns an error instead of a Database. diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs index 94e6a5d644f2..2389cb234f8e 100644 --- a/ffi/src/iterator.rs +++ b/ffi/src/iterator.rs @@ -3,8 +3,10 @@ use derive_where::derive_where; use firewood::merkle; -use firewood::v2::api; -use firewood::v2::api::BoxKeyValueIter; +use firewood::v2::api::{self, BoxKeyValueIter}; +use std::iter::FusedIterator; + +type KeyValueItem = (merkle::Key, merkle::Value); /// An opaque wrapper around a [`BoxKeyValueIter`]. #[derive(Default)] @@ -19,7 +21,7 @@ impl<'view> From> for IteratorHandle<'view> { } impl Iterator for IteratorHandle<'_> { - type Item = Result<(merkle::Key, merkle::Value), api::Error>; + type Item = Result; fn next(&mut self) -> Option { let out = self.0.as_mut()?.next(); @@ -31,5 +33,14 @@ impl Iterator for IteratorHandle<'_> { } } +impl FusedIterator for IteratorHandle<'_> {} + +#[expect(clippy::missing_errors_doc)] +impl IteratorHandle<'_> { + pub fn iter_next_n(&mut self, n: usize) -> Result, api::Error> { + self.by_ref().take(n).collect() + } +} + #[derive(Debug, Default)] pub struct CreateIteratorResult<'db>(pub IteratorHandle<'db>); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 77515bae3976..79daac92a9ec 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -168,7 +168,7 @@ pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( invoke_with_handle(handle, move |p| p.iter_from(Some(key.as_slice()))) } -/// Retrieves the next item from the iterator +/// Retrieves the next item from the iterator. /// /// # Arguments /// @@ -178,23 +178,61 @@ pub unsafe extern "C" fn fwd_iter_on_proposal<'p>( /// # Returns /// /// - [`KeyValueResult::NullHandlePointer`] if the provided iterator handle is null. -/// - [`KeyValueResult::None`] if the iterator doesn't have any remaining values/exhausted. +/// - [`KeyValueResult::None`] if the iterator is exhausted (no remaining values). Once returned, +/// subsequent calls will continue returning [`KeyValueResult::None`]. You may still call this +/// safely, but freeing the iterator with [`fwd_free_iterator`] is recommended. /// - [`KeyValueResult::Some`] if the next item on iterator was retrieved, with the associated /// key value pair. -/// - [`KeyValueResult::Err`] if an error occurred while retrieving the next item on iterator. +/// - [`KeyValueResult::Err`] if an I/O error occurred while retrieving the next item. Most +/// iterator errors are non-reentrant. Once returned, the iterator should be considered +/// invalid and must be freed with [`fwd_free_iterator`]. /// /// # Safety /// /// The caller must: /// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. -/// * call [`fwd_free_owned_bytes`] on [`OwnedKeyValuePair::key`] and [`OwnedKeyValuePair::value`] -/// to free the memory associated with the returned error or value. +/// * call [`fwd_free_owned_kv_pair`] on returned [`OwnedKeyValuePair`] +/// to free the memory associated with the returned value. /// #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_iter_next(handle: Option<&mut IteratorHandle<'_>>) -> KeyValueResult { invoke_with_handle(handle, Iterator::next) } +/// Retrieves the next batch of items from the iterator. +/// +/// # Arguments +/// +/// * `handle` - The iterator handle returned by [`fwd_iter_on_revision`] or +/// [`fwd_iter_on_proposal`]. +/// +/// # Returns +/// +/// - [`KeyValueBatchResult::NullHandlePointer`] if the provided iterator handle is null. +/// - [`KeyValueBatchResult::Some`] with up to `n` key/value pairs. If the iterator is +/// exhausted, this may be fewer than `n`, including zero items. +/// - [`KeyValueBatchResult::Err`] if an I/O error occurred while retrieving items. Most +/// iterator errors are non-reentrant. Once returned, the iterator should be considered +/// invalid and must be freed with [`fwd_free_iterator`]. +/// +/// Once an empty batch or items fewer than `n` is returned (iterator exhausted), subsequent calls +/// will continue returning empty batches. You may still call this safely, but freeing the +/// iterator with [`fwd_free_iterator`] is recommended. +/// +/// # Safety +/// +/// The caller must: +/// * ensure that `handle` is a valid pointer to a [`IteratorHandle`]. +/// * call [`fwd_free_owned_key_value_batch`] on the returned batch to free any allocated memory. +/// +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_iter_next_n( + handle: Option<&mut IteratorHandle<'_>>, + n: usize, +) -> KeyValueBatchResult { + invoke_with_handle(handle, |it| it.iter_next_n(n)) +} + /// Consumes the [`IteratorHandle`], destroys the iterator, and frees the memory. /// /// # Arguments @@ -693,6 +731,28 @@ pub unsafe extern "C" fn fwd_free_owned_bytes(bytes: OwnedBytes) -> VoidResult { invoke(move || drop(bytes)) } +/// Consumes the [`OwnedKeyValueBatch`] and frees the memory associated with it. +/// +/// # Arguments +/// +/// * `batch` - The [`OwnedKeyValueBatch`] struct to free, previously returned from any +/// function from this library. +/// +/// # Returns +/// +/// - [`VoidResult::Ok`] if the memory was successfully freed. +/// - [`VoidResult::Err`] if the process panics while freeing the memory. +/// +/// # Safety +/// +/// The caller must ensure that the `batch` struct is valid and that the memory +/// it points to is uniquely owned by this object. However, if `batch.ptr` is null, +/// this function does nothing. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwd_free_owned_key_value_batch(batch: OwnedKeyValueBatch) -> VoidResult { + invoke(move || drop(batch)) +} + /// Consumes the [`OwnedKeyValuePair`] and frees the memory associated with it. /// /// # Arguments diff --git a/ffi/src/value.rs b/ffi/src/value.rs index f9f5ee7f3e6f..c80f2a610380 100644 --- a/ffi/src/value.rs +++ b/ffi/src/value.rs @@ -11,12 +11,13 @@ mod results; pub use self::borrowed::{BorrowedBytes, BorrowedKeyValuePairs, BorrowedSlice}; use self::display_hex::DisplayHex; pub use self::hash_key::HashKey; -pub use self::kvp::{KeyValuePair, OwnedKeyValuePair}; +pub use self::kvp::{KeyValuePair, OwnedKeyValueBatch, OwnedKeyValuePair}; pub use self::owned::{OwnedBytes, OwnedSlice}; pub(crate) use self::results::{CResult, NullHandleResult}; pub use self::results::{ - ChangeProofResult, HandleResult, HashResult, IteratorResult, KeyValueResult, - NextKeyRangeResult, ProposalResult, RangeProofResult, RevisionResult, ValueResult, VoidResult, + ChangeProofResult, HandleResult, HashResult, IteratorResult, KeyValueBatchResult, + KeyValueResult, NextKeyRangeResult, ProposalResult, RangeProofResult, RevisionResult, + ValueResult, VoidResult, }; /// Maybe is a C-compatible optional type using a tagged union pattern. diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index c5255cc404d2..b693d5db492a 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -3,10 +3,13 @@ use std::fmt; -use crate::OwnedBytes; use crate::value::BorrowedBytes; +use crate::{OwnedBytes, OwnedSlice}; use firewood::v2::api; +/// A type alias for a rust-owned byte slice. +pub type OwnedKeyValueBatch = OwnedSlice; + /// A `KeyValue` represents a key-value pair, passed to the FFI. #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index ee94c97e3705..20b2d206a590 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -8,7 +8,8 @@ use std::fmt; use crate::revision::{GetRevisionResult, RevisionHandle}; use crate::{ ChangeProofContext, CreateIteratorResult, CreateProposalResult, HashKey, IteratorHandle, - NextKeyRange, OwnedBytes, OwnedKeyValuePair, ProposalHandle, RangeProofContext, + NextKeyRange, OwnedBytes, OwnedKeyValueBatch, OwnedKeyValuePair, ProposalHandle, + RangeProofContext, }; /// The result type returned from an FFI function that returns no value but may @@ -367,6 +368,36 @@ impl From>> for KeyValue } } +/// A result type returned from iterator FFI functions +#[derive(Debug)] +#[repr(C)] +pub enum KeyValueBatchResult { + /// The caller provided a null pointer to an iterator handle. + NullHandlePointer, + /// The next batch of items on iterator are returned. + Some(OwnedKeyValueBatch), + /// An error occurred and the message is returned as an [`OwnedBytes`]. If + /// value is guaranteed to contain only valid UTF-8. + /// + /// The caller must call [`fwd_free_owned_bytes`] to free the memory + /// associated with this error. + /// + /// [`fwd_free_owned_bytes`]: crate::fwd_free_owned_bytes + Err(OwnedBytes), +} + +impl From, api::Error>> for KeyValueBatchResult { + fn from(value: Result, api::Error>) -> Self { + match value { + Ok(pairs) => { + let values: Vec<_> = pairs.into_iter().map(Into::into).collect(); + KeyValueBatchResult::Some(values.into()) + } + Err(err) => KeyValueBatchResult::Err(err.to_string().into_bytes().into()), + } + } +} + impl<'db> From> for IteratorResult<'db> { fn from(value: CreateIteratorResult<'db>) -> Self { IteratorResult::Ok { @@ -513,6 +544,7 @@ impl_null_handle_result!( ProposalResult<'_>, IteratorResult<'_>, RevisionResult, + KeyValueBatchResult, KeyValueResult, ); @@ -527,6 +559,7 @@ impl_cresult!( ProposalResult<'_>, IteratorResult<'_>, RevisionResult, + KeyValueBatchResult, KeyValueResult, ); From 94971c6c6d98540cdb9db09c784f4f9a2078aa67 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 15 Oct 2025 07:46:52 -0700 Subject: [PATCH 0988/1053] feat(5/7): add PackedPathRef type (#1341) This adds a view type for paths that store components in a packed format, which is the default for keys on the storage layer. This should improve conversion between keys and paths and reduce bugs from mismatched types. --- storage/src/lib.rs | 6 +- storage/src/path/component.rs | 99 +++++++- storage/src/path/mod.rs | 63 ++++- storage/src/path/packed.rs | 441 ++++++++++++++++++++++++++++++++++ 4 files changed, 605 insertions(+), 4 deletions(-) create mode 100644 storage/src/path/packed.rs diff --git a/storage/src/lib.rs b/storage/src/lib.rs index f262dbb315d1..27c802f9b3f6 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -55,9 +55,11 @@ pub use nodestore::{ NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; pub use path::{ - IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, SplitPath, TriePath, - TriePathFromUnpackedBytes, + IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, PathComponentSliceExt, SplitPath, + TriePath, TriePathAsPackedBytes, TriePathFromPackedBytes, TriePathFromUnpackedBytes, }; +#[cfg(not(feature = "branch_factor_256"))] +pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; diff --git a/storage/src/path/component.rs b/storage/src/path/component.rs index 3cd44610d67b..f6196907753e 100644 --- a/storage/src/path/component.rs +++ b/storage/src/path/component.rs @@ -15,6 +15,12 @@ pub struct PathComponent(pub crate::u4::U4); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PathComponent(pub u8); +/// Extension methods for slices of path components. +pub trait PathComponentSliceExt { + /// Casts this slice of path components to a byte slice. + fn as_byte_slice(&self) -> &[u8]; +} + impl PathComponent { /// All possible path components. /// @@ -116,13 +122,24 @@ impl PathComponent { } } - /// Creates a pair of path components from a single byte. + /// Creates a pair of path components from a single byte where the + /// upper 4 bits are the first component and the lower 4 bits are the + /// second component. #[cfg(not(feature = "branch_factor_256"))] #[must_use] pub const fn new_pair(v: u8) -> (Self, Self) { let (upper, lower) = crate::u4::U4::new_pair(v); (Self(upper), Self(lower)) } + + /// Joins this [`PathComponent`] with another to create a single [`u8`] where + /// this component is the upper 4 bits and the provided component is the + /// lower 4 bits. + #[cfg(not(feature = "branch_factor_256"))] + #[must_use] + pub const fn join(self, other: Self) -> u8 { + self.0.join(other.0) + } } impl std::fmt::Display for PathComponent { @@ -281,6 +298,34 @@ impl> TriePathFromUnpackedBytes<'_> for } } +#[cfg(not(feature = "branch_factor_256"))] +impl super::TriePathFromPackedBytes<'_> for Vec { + fn path_from_packed_bytes(bytes: &'_ [u8]) -> Self { + let path = super::PackedPathRef::path_from_packed_bytes(bytes); + let mut this = Vec::new(); + // reserve_exact is used because we trust that `TriePath::len` returns the exact + // length but `Vec::extend` won't trust `Iterator::size_hint` and may + // over/under-allocate. + this.reserve_exact(path.len()); + this.extend(path.components()); + this + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl> super::TriePathFromPackedBytes<'_> for SmallVec { + fn path_from_packed_bytes(bytes: &'_ [u8]) -> Self { + let path = super::PackedPathRef::path_from_packed_bytes(bytes); + let mut this = SmallVec::::new(); + // reserve_exact is used because we trust that `TriePath::len` returns the exact + // length but `SmallVec::extend` won't trust `Iterator::size_hint` and may + // over/under-allocate. + this.reserve_exact(path.len()); + this.extend(path.components()); + this + } +} + #[cfg(feature = "branch_factor_256")] impl<'input> TriePathFromUnpackedBytes<'input> for &'input [PathComponent] { type Error = std::convert::Infallible; @@ -334,6 +379,43 @@ impl<'input> TriePathFromUnpackedBytes<'input> for std::sync::Arc<[PathComponent } } +// these must also only be included when `branch_factor_256` is not enabled otherwise +// they would conflict with the blanket impl of TriePathFromPackedBytes over +// TriePathFromUnpackedBytes + +#[cfg(not(feature = "branch_factor_256"))] +impl super::TriePathFromPackedBytes<'_> for Box<[PathComponent]> { + fn path_from_packed_bytes(bytes: &[u8]) -> Self { + Vec::::path_from_packed_bytes(bytes).into() + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl super::TriePathFromPackedBytes<'_> for std::rc::Rc<[PathComponent]> { + fn path_from_packed_bytes(bytes: &[u8]) -> Self { + Vec::::path_from_packed_bytes(bytes).into() + } +} + +#[cfg(not(feature = "branch_factor_256"))] +impl super::TriePathFromPackedBytes<'_> for std::sync::Arc<[PathComponent]> { + fn path_from_packed_bytes(bytes: &[u8]) -> Self { + Vec::::path_from_packed_bytes(bytes).into() + } +} + +impl PathComponentSliceExt for [PathComponent] { + fn as_byte_slice(&self) -> &[u8] { + path_components_as_byte_slice(self) + } +} + +impl> PathComponentSliceExt for T { + fn as_byte_slice(&self) -> &[u8] { + path_components_as_byte_slice(self) + } +} + #[inline] const unsafe fn byte_slice_as_path_components_unchecked(bytes: &[u8]) -> &[PathComponent] { #![expect(unsafe_code)] @@ -353,6 +435,21 @@ const unsafe fn byte_slice_as_path_components_unchecked(bytes: &[u8]) -> &[PathC } } +#[inline] +const fn path_components_as_byte_slice(components: &[PathComponent]) -> &[u8] { + #![expect(unsafe_code)] + + // SAFETY: We rely on the fact that `PathComponent` is a single element type + // over `u8` (or `u4` which looks like a `u8` for this purpose). + // + // borrow rules ensure that the pointer for `components` is not null and + // `components.len()` is always valid. The returned reference will have the same + // lifetime as `components` so it cannot outlive the original slice. + unsafe { + &*(std::ptr::slice_from_raw_parts(components.as_ptr().cast::(), components.len())) + } +} + #[inline] #[cfg(not(feature = "branch_factor_256"))] fn try_from_maybe_u4>( diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 99fb06855efa..7d0844bb4386 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -3,10 +3,14 @@ mod component; mod joined; +#[cfg(not(feature = "branch_factor_256"))] +mod packed; mod split; -pub use self::component::PathComponent; +pub use self::component::{PathComponent, PathComponentSliceExt}; pub use self::joined::JoinedPath; +#[cfg(not(feature = "branch_factor_256"))] +pub use self::packed::{PackedBytes, PackedPathComponents, PackedPathRef}; pub use self::split::{IntoSplitPath, PathCommonPrefix, SplitPath}; /// A trie path of components with different underlying representations. @@ -110,6 +114,63 @@ pub trait TriePathFromUnpackedBytes<'input>: TriePath + Sized { fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result; } +/// Constructor for a trie path from a set of packed bytes; where each byte contains +/// as many path components as possible. +/// +/// For 256-ary tries, this is just the bytes as-is. +/// +/// For hexary tries, each byte contains two path components; one in the upper 4 +/// bits and one in the lower 4 bits, in big-endian order. The resulting path +/// will always have an even length (`bytes.len() * 2`). +/// +/// For future compatibility, this trait only supports paths where the width of +/// a path component is a factor of 8 (i.e. 1, 2, 4, or 8 bits). +pub trait TriePathFromPackedBytes<'input>: Sized { + /// Constructs a path from the given packed bytes. + fn path_from_packed_bytes(bytes: &'input [u8]) -> Self; +} + +/// Converts this path to an iterator over its packed bytes. +pub trait TriePathAsPackedBytes { + /// The iterator type returned by [`TriePathAsPackedBytes::as_packed_bytes`]. + type PackedBytesIter<'a>: Iterator + where + Self: 'a; + + /// Returns an iterator over the packed bytes of this path. + /// + /// If the final path component does not fill a whole byte, it is padded with zero. + fn as_packed_bytes(&self) -> Self::PackedBytesIter<'_>; +} + +/// Blanket implementation of [`TriePathFromPackedBytes`] for 256-ary tries +/// because packed bytes and unpacked bytes are identical. +#[cfg(feature = "branch_factor_256")] +impl<'input, T> TriePathFromPackedBytes<'input> for T +where + T: TriePathFromUnpackedBytes<'input, Error = std::convert::Infallible>, +{ + fn path_from_packed_bytes(bytes: &'input [u8]) -> Self { + match Self::path_from_unpacked_bytes(bytes) { + Ok(p) => p, + // no Err(_) branch because Infallible is an uninhabited type and + // cannot be represented, therefore a match on is impossible + } + } +} + +#[cfg(feature = "branch_factor_256")] +impl TriePathAsPackedBytes for T { + type PackedBytesIter<'a> + = std::iter::Map, fn(PathComponent) -> u8> + where + Self: 'a; + + fn as_packed_bytes(&self) -> Self::PackedBytesIter<'_> { + self.components().map(PathComponent::as_u8) + } +} + #[inline] fn display_path( f: &mut std::fmt::Formatter<'_>, diff --git a/storage/src/path/packed.rs b/storage/src/path/packed.rs new file mode 100644 index 000000000000..5cb47ee59b8a --- /dev/null +++ b/storage/src/path/packed.rs @@ -0,0 +1,441 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::TriePathAsPackedBytes; + +use super::{PathComponent, SplitPath, TriePath, TriePathFromPackedBytes}; + +/// A packed representation of a trie path where each byte encodes two path components. +/// +/// This is borrowed over the underlying byte slice and does not allocate. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct PackedPathRef<'a> { + prefix: Option, + middle: &'a [u8], + suffix: Option, +} + +/// An iterator over the components of a [`PackedPathRef`]. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct PackedPathComponents<'a> { + path: PackedPathRef<'a>, +} + +/// An iterator over the components of a path that yields them packed into bytes. +/// +/// If there is a trailing component without a pair, it is padded with zero. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct PackedBytes { + iter: I, +} + +impl> PackedBytes { + /// Creates a new iterator over the packed bytes of the given component iterator. + pub const fn new(iter: I) -> Self { + Self { iter } + } +} + +impl TriePath for PackedPathRef<'_> { + type Components<'a> + = PackedPathComponents<'a> + where + Self: 'a; + + fn len(&self) -> usize { + self.middle + .len() + .wrapping_mul(2) + .wrapping_add(usize::from(self.prefix.is_some())) + .wrapping_add(usize::from(self.suffix.is_some())) + } + + fn components(&self) -> Self::Components<'_> { + PackedPathComponents { path: *self } + } +} + +impl SplitPath for PackedPathRef<'_> { + fn split_at(self, mid: usize) -> (Self, Self) { + assert!(mid <= self.len(), "mid > self.len()"); + + if let Some(mid) = mid.checked_sub(usize::from(self.prefix.is_some())) { + // the mid consumed the prefix, if it existed + if mid.is_multiple_of(2) { + // middle is split cleanly + let (a_middle, b_middle) = self.middle.split_at(mid / 2); + let prefix = Self { + prefix: self.prefix, + middle: a_middle, + suffix: None, + }; + let suffix = Self { + prefix: None, + middle: b_middle, + suffix: self.suffix, + }; + (prefix, suffix) + } else { + // mid splits a middle component into the suffix of `prefix` and the + // prefix of `suffix` + let (a_middle, b_middle) = self.middle.split_at(mid / 2); + let Some((&middle_byte, b_middle)) = b_middle.split_first() else { + // `mid` is oob of `b_middle`, which happens if self.suffix is Some, + // and `mid` is self.len() + return (self, Self::default()); + }; + + let (upper, lower) = PathComponent::new_pair(middle_byte); + let prefix = Self { + prefix: self.prefix, + middle: a_middle, + suffix: Some(upper), + }; + let suffix = Self { + prefix: Some(lower), + middle: b_middle, + suffix: self.suffix, + }; + (prefix, suffix) + } + } else { + // `prefix` is some and `mid` is zero + (Self::default(), self) + } + } + + fn split_first(self) -> Option<(PathComponent, Self)> { + let mut iter = self.into_iter(); + let first = iter.next()?; + Some((first, iter.path)) + } +} + +impl<'input> TriePathFromPackedBytes<'input> for PackedPathRef<'input> { + fn path_from_packed_bytes(bytes: &'input [u8]) -> Self { + Self { + prefix: None, + middle: bytes, + suffix: None, + } + } +} + +impl<'a> IntoIterator for PackedPathRef<'a> { + type Item = PathComponent; + + type IntoIter = PackedPathComponents<'a>; + + fn into_iter(self) -> Self::IntoIter { + PackedPathComponents { path: self } + } +} + +impl Iterator for PackedPathComponents<'_> { + type Item = PathComponent; + + fn next(&mut self) -> Option { + if let Some(next) = self.path.prefix.take() { + return Some(next); + } + + if let Some((&next, rest)) = self.path.middle.split_first() { + let (upper, lower) = PathComponent::new_pair(next); + self.path.prefix = Some(lower); + self.path.middle = rest; + return Some(upper); + } + + self.path.suffix.take() + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.path.len(); + (len, Some(len)) + } +} + +impl DoubleEndedIterator for PackedPathComponents<'_> { + fn next_back(&mut self) -> Option { + if let Some(next) = self.path.suffix.take() { + return Some(next); + } + + if let Some((&last, rest)) = self.path.middle.split_last() { + let (upper, lower) = PathComponent::new_pair(last); + self.path.suffix = Some(upper); + self.path.middle = rest; + return Some(lower); + } + + self.path.prefix.take() + } +} + +impl ExactSizeIterator for PackedPathComponents<'_> {} + +impl std::iter::FusedIterator for PackedPathComponents<'_> {} + +impl> Iterator for PackedBytes { + type Item = u8; + + fn next(&mut self) -> Option { + let hi = self.iter.next()?; + let lo = self.iter.next().unwrap_or(const { PathComponent::ALL[0] }); + Some(hi.join(lo)) + } + + fn size_hint(&self) -> (usize, Option) { + let (lower, upper) = self.iter.size_hint(); + let lower = lower.div_ceil(2); + let upper = upper.map(|u| u.div_ceil(2)); + (lower, upper) + } +} + +impl> DoubleEndedIterator for PackedBytes { + fn next_back(&mut self) -> Option { + let lo = self.iter.next_back()?; + let hi = self + .iter + .next_back() + .unwrap_or(const { PathComponent::ALL[0] }); + Some(hi.join(lo)) + } +} + +impl> ExactSizeIterator for PackedBytes {} + +impl> std::iter::FusedIterator + for PackedBytes +{ +} + +impl TriePathAsPackedBytes for T { + type PackedBytesIter<'a> + = PackedBytes> + where + Self: 'a; + + fn as_packed_bytes(&self) -> Self::PackedBytesIter<'_> { + PackedBytes::new(self.components()) + } +} + +#[cfg(test)] +mod tests { + #![expect(clippy::unwrap_used)] + + // NB: tests do not need to worry about 256-ary tries because this module is + // only compiled when the "branch_factor_256" feature is not enabled. + + use test_case::test_case; + + use super::*; + + /// Yields a [`PathComponent`] failing to compile if the given expression is + /// not a valid path component. + macro_rules! pc { + ($elem:expr) => { + const { PathComponent::ALL[$elem] } + }; + } + + /// Yields an array of [`PathComponent`]s failing to compile if any of the + /// given expressions are not valid path components. + /// + /// The expression yields an array, not a slice, and a reference must be taken + /// to convert it to a slice. + macro_rules! path { + ($($elem:expr),* $(,)?) => { + [ $( pc!($elem), )* ] + }; + } + + struct FromPackedBytesTest<'a> { + bytes: &'a [u8], + components: &'a [PathComponent], + } + + #[test_case( + FromPackedBytesTest{ + bytes: &[], + components: &path![], + }; + "empty path" + )] + #[test_case( + FromPackedBytesTest{ + bytes: b"a", + components: &path![6, 1], + }; + "single byte path" + )] + #[test_case( + FromPackedBytesTest{ + bytes: b"abc", + components: &path![6, 1, 6, 2, 6, 3], + }; + "multi-byte path" + )] + fn test_from_packed_bytes(case: FromPackedBytesTest<'_>) { + let path = PackedPathRef::path_from_packed_bytes(case.bytes); + assert_eq!(path.len(), case.components.len()); + + assert!(path.components().eq(case.components.iter().copied())); + assert!( + path.components() + .rev() + .eq(case.components.iter().copied().rev()) + ); + + assert!(path.as_packed_bytes().eq(case.bytes.iter().copied())); + assert!( + path.as_packed_bytes() + .rev() + .eq(case.bytes.iter().copied().rev()) + ); + + assert!(TriePath::path_eq(&path, &case.components)); + assert!(TriePath::path_cmp(&path, &case.components).is_eq()); + } + + struct SplitAtTest<'a> { + path: PackedPathRef<'a>, + mid: usize, + a: &'a [PathComponent], + b: &'a [PathComponent], + } + + #[test_case( + SplitAtTest{ + path: PackedPathRef::path_from_packed_bytes(b""), + mid: 0, + a: &path![], + b: &path![], + }; + "empty path" + )] + #[test_case( + SplitAtTest{ + path: PackedPathRef::path_from_packed_bytes(b"a"), + mid: 0, + a: &path![], + b: &path![6, 1], + }; + "single byte path, split at 0" + )] + #[test_case( + SplitAtTest{ + path: PackedPathRef::path_from_packed_bytes(b"a"), + mid: 1, + a: &path![6], + b: &path![1], + }; + "single byte path, split at 1" + )] + #[test_case( + SplitAtTest{ + path: PackedPathRef::path_from_packed_bytes(b"a"), + mid: 2, + a: &path![6, 1], + b: &path![], + }; + "single byte path, split at len" + )] + #[test_case( + SplitAtTest{ + path: PackedPathRef::path_from_packed_bytes(b"abc"), + mid: 2, + a: &path![6, 1], + b: &path![6, 2, 6, 3], + }; + "multi byte path, split at 2" + )] + fn test_split_at(case: SplitAtTest<'_>) { + let (a, b) = case.path.split_at(case.mid); + assert_eq!(a.len(), case.mid); + assert_eq!(a.len().wrapping_add(b.len()), case.path.len()); + assert!(a.path_eq(&case.a)); + assert!(b.path_eq(&case.b)); + assert!(a.append(b).path_eq(&case.path)); + if let Some(mid) = case.mid.checked_sub(1) { + let (_, path) = dbg!(case.path.split_first()).unwrap(); + let (_, b) = dbg!(path.split_at(mid)); + assert!( + b.path_eq(&case.b), + "{} != {} ({}) (mid = {mid})", + b.display(), + case.b.display(), + path.display(), + ); + } + if let Some(len) = case.path.len().checked_sub(1) + && len >= case.mid + { + let (path, _) = dbg!(case.path.split_at(len)); + let (a, _) = dbg!(path.split_at(case.mid)); + assert!( + a.path_eq(&case.a), + "{} != {} ({}) (len = {len})", + a.display(), + case.a.display(), + path.display(), + ); + } + } + + struct AsPackedBytesTest<'a, T> { + path: T, + expected: &'a [u8], + } + + #[test_case( + AsPackedBytesTest { + path: PackedPathRef::path_from_packed_bytes(&[]), + expected: &[], + }; + "empty packed path" + )] + #[test_case( + AsPackedBytesTest::<&[PathComponent]> { + path: &[], + expected: &[], + }; + "empty unpacked path" + )] + #[test_case( + AsPackedBytesTest { + path: PackedPathRef::path_from_packed_bytes(b"abc"), + expected: b"abc", + }; + "multi-byte packed path" + )] + #[test_case( + AsPackedBytesTest::<&[PathComponent]> { + path: &[PathComponent::ALL[6]], + expected: &[0x60], + }; + "odd-lengthed unpacked path" + )] + #[test_case( + AsPackedBytesTest::<&[PathComponent]> { + path: &[PathComponent::ALL[6], PathComponent::ALL[1]], + expected: &[0x61], + }; + "even-lengthed unpacked path" + )] + #[test_case( + AsPackedBytesTest::<&[PathComponent]> { + path: &[PathComponent::ALL[6], PathComponent::ALL[1], PathComponent::ALL[2]], + expected: &[0x61, 0x20], + }; + "three-length unpacked path" + )] + fn test_as_packed_bytes(case: AsPackedBytesTest<'_, T>) { + let as_packed = case.path.as_packed_bytes().collect::>(); + assert_eq!(*as_packed, *case.expected); + } +} From 2d1556a96bb4a60764de01ad481be06ca5c5f2ef Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 15 Oct 2025 07:59:20 -0700 Subject: [PATCH 0989/1053] feat(6/7): Children newtype (#1344) This change upgrades the `Children` type from an alias to a formal new-type. This makes it possible to implement iterators and other functions that operate with the `PathComponent` type, making indexing and manipulation of the array safer and more ergonomic. --- firewood/src/iter.rs | 42 +-- firewood/src/merkle.rs | 426 +++++++++-------------------- firewood/src/merkle/tests/proof.rs | 2 +- firewood/src/proof.rs | 22 +- firewood/src/proofs/bitmap.rs | 79 +++--- firewood/src/proofs/de.rs | 9 +- firewood/src/proofs/ser.rs | 2 +- storage/benches/serializer.rs | 13 +- storage/src/checker/mod.rs | 35 ++- storage/src/checker/range_set.rs | 5 +- storage/src/hashednode.rs | 15 +- storage/src/hashers/ethhash.rs | 6 +- storage/src/hashers/merkledb.rs | 6 +- storage/src/lib.rs | 9 +- storage/src/node/branch.rs | 147 +++------- storage/src/node/children.rs | 236 ++++++++++++++++ storage/src/node/mod.rs | 44 +-- storage/src/node/path.rs | 1 + storage/src/nodestore/hash.rs | 24 +- storage/src/nodestore/persist.rs | 69 +++-- storage/src/path/component.rs | 36 ++- storage/src/path/mod.rs | 2 +- 22 files changed, 634 insertions(+), 596 deletions(-) create mode 100644 storage/src/node/children.rs diff --git a/firewood/src/iter.rs b/firewood/src/iter.rs index cec86fb472a3..657e95f457d9 100644 --- a/firewood/src/iter.rs +++ b/firewood/src/iter.rs @@ -8,7 +8,8 @@ use crate::merkle::{Key, Value}; use crate::v2::api; use firewood_storage::{ - BranchNode, Child, FileIoError, NibblesIterator, Node, PathIterItem, SharedNode, TrieReader, + BranchNode, Child, FileIoError, NibblesIterator, Node, PathComponent, PathIterItem, SharedNode, + TrieReader, }; use std::cmp::Ordering; use std::iter::FusedIterator; @@ -27,7 +28,7 @@ enum IterationNode { key: Key, /// Returns the non-empty children of this node and their positions /// in the node's children array. - children_iter: Box + Send>, + children_iter: Box + Send>, }, } @@ -152,7 +153,7 @@ impl Iterator for MerkleNodeIter<'_, T> { let child_key: Key = key .iter() .copied() - .chain(Some(pos)) + .chain(Some(pos.as_u8())) .chain(child_partial_path) .collect(); @@ -240,6 +241,8 @@ fn get_iterator_intial_state( return Ok(NodeIterState::Iterating { iter_stack }); }; + let next_unmatched_key_nibble = + PathComponent::try_new(next_unmatched_key_nibble).expect("valid nibble"); // There is no child at `next_unmatched_key_nibble`. // We'll visit `node`'s first child at index > `next_unmatched_key_nibble` @@ -252,8 +255,7 @@ fn get_iterator_intial_state( ), }); - #[expect(clippy::indexing_slicing)] - let child = &branch.children[next_unmatched_key_nibble as usize]; + let child = &branch.children[next_unmatched_key_nibble]; node = match child { None => return Ok(NodeIterState::Iterating { iter_stack }), Some(Child::AddressWithHash(addr, _)) => merkle.read_node(*addr)?, @@ -264,7 +266,7 @@ fn get_iterator_intial_state( } }; - matched_key_nibbles.push(next_unmatched_key_nibble); + matched_key_nibbles.push(next_unmatched_key_nibble.as_u8()); } }, } @@ -425,9 +427,11 @@ impl Iterator for PathIterator<'_, '_, T> { next_nibble: None, })); }; + let next_unmatched_key_nibble = + PathComponent::try_new(next_unmatched_key_nibble) + .expect("valid nibble"); - #[expect(clippy::indexing_slicing)] - let child = &branch.children[next_unmatched_key_nibble as usize]; + let child = &branch.children[next_unmatched_key_nibble]; match child { None => { // There's no child at the index of the next nibble in the key. @@ -446,7 +450,7 @@ impl Iterator for PathIterator<'_, '_, T> { }; let node_key = matched_key.clone().into_boxed_slice(); - matched_key.push(next_unmatched_key_nibble); + matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child; @@ -458,7 +462,7 @@ impl Iterator for PathIterator<'_, '_, T> { } Some(Child::Node(child)) => { let node_key = matched_key.clone().into_boxed_slice(); - matched_key.push(next_unmatched_key_nibble); + matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child.clone().into(); @@ -475,7 +479,7 @@ impl Iterator for PathIterator<'_, '_, T> { }; let node_key = matched_key.clone().into_boxed_slice(); - matched_key.push(next_unmatched_key_nibble); + matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child; Some(Ok(PathIterItem { @@ -528,13 +532,14 @@ where /// Returns an iterator that returns (`pos`,`child`) for each non-empty child of `branch`, /// where `pos` is the position of the child in `branch`'s children array. -fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator + use<> { +fn as_enumerated_children_iter( + branch: &BranchNode, +) -> impl Iterator + use<> { branch .children .clone() .into_iter() - .enumerate() - .filter_map(|(pos, child)| child.map(|child| (pos as u8, child))) + .filter_map(|(pos, child)| child.map(|child| (pos, child))) } #[cfg(feature = "branch_factor_256")] @@ -631,7 +636,7 @@ mod tests { assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); #[cfg(feature = "branch_factor_256")] assert_eq!(node.key_nibbles, vec![0].into_boxed_slice()); - assert_eq!(node.next_nibble, Some(0)); + assert_eq!(node.next_nibble, Some(PathComponent::ALL[0])); assert!(node.node.as_branch().unwrap().value.is_none()); let node = match iter.next() { @@ -647,10 +652,7 @@ mod tests { #[cfg(feature = "branch_factor_256")] assert_eq!(node.key_nibbles, vec![0, 0, 0].into_boxed_slice()); - #[cfg(not(feature = "branch_factor_256"))] - assert_eq!(node.next_nibble, Some(0x0F)); - #[cfg(feature = "branch_factor_256")] - assert_eq!(node.next_nibble, Some(0xFF)); + assert_eq!(node.next_nibble, PathComponent::ALL.last().copied()); assert_eq!( node.node.as_branch().unwrap().value, @@ -693,7 +695,7 @@ mod tests { assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); assert!(node.node.as_branch().unwrap().value.is_none()); - assert_eq!(node.next_nibble, Some(0)); + assert_eq!(node.next_nibble, Some(PathComponent::ALL[0])); let node = match iter.next() { Some(Ok(node)) => node, diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index b198cdbc0350..fe55cc9ffb41 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -9,9 +9,10 @@ use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; use firewood_storage::{ - BranchNode, Child, FileIoError, HashType, HashedNodeReader, ImmutableProposal, IntoHashType, - LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, Parentable, - Path, ReadableStorage, SharedNode, TrieHash, TrieReader, ValueDigest, + BranchNode, Child, Children, FileIoError, HashType, HashedNodeReader, ImmutableProposal, + IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, + Parentable, Path, PathComponent, ReadableStorage, SharedNode, TrieHash, TrieReader, + ValueDigest, }; use metrics::counter; use std::collections::HashSet; @@ -84,14 +85,11 @@ fn get_helper( } (None, None) => Ok(Some(node.clone().into())), // 1. The node is at `key` (Some((child_index, remaining_key)), None) => { + let child_index = PathComponent::try_new(child_index).expect("index is in bounds"); // 3. The key is below the node (i.e. its descendant) match node { Node::Leaf(_) => Ok(None), - Node::Branch(node) => match node - .children - .get(child_index as usize) - .expect("index is in bounds") - { + Node::Branch(node) => match node.children[child_index].as_ref() { None => Ok(None), Some(Child::Node(child)) => get_helper(nodestore, child, remaining_key), Some(Child::AddressWithHash(addr, _)) => { @@ -162,7 +160,7 @@ impl Merkle { let child_hashes = if let Some(branch) = root.as_branch() { branch.children_hashes() } else { - BranchNode::empty_children() + Children::new() }; proof.push(ProofNode { @@ -473,7 +471,7 @@ impl Merkle { write_attributes!(writer, b, &b.value.clone().unwrap_or(Box::from([]))); writeln!(writer, "\"]") .map_err(|e| FileIoError::from_generic_no_file(e, "write branch"))?; - for (childidx, child) in b.children.iter().enumerate() { + for (childidx, child) in &b.children { let (child, child_hash) = match child { None => continue, Some(node) => (node.as_maybe_persisted_node(), node.hash()), @@ -584,6 +582,14 @@ impl Merkle> { self.try_into().expect("failed to convert") } + fn read_for_update(&mut self, child: Child) -> Result { + match child { + Child::Node(node) => Ok(node), + Child::AddressWithHash(addr, _) => self.nodestore.read_for_update(addr.into()), + Child::MaybePersisted(node, _) => self.nodestore.read_for_update(node), + } + } + /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { @@ -610,6 +616,7 @@ impl Merkle> { /// Map `key` to `value` into the subtrie rooted at `node`. /// Each element of `key` is 1 nibble. /// Returns the new root of the subtrie. + #[expect(clippy::missing_panics_doc)] pub fn insert_helper( &mut self, mut node: Node, @@ -641,6 +648,7 @@ impl Merkle> { Ok(node) } (None, Some((child_index, partial_path))) => { + let child_index = PathComponent::try_new(child_index).expect("valid component"); // 2. The key is above the node (i.e. its ancestor) // Make a new branch node and insert the current node as a child. // ... ... @@ -651,17 +659,18 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: Some(value), - children: BranchNode::empty_children(), + children: Children::new(), }; // Shorten the node's partial path since it has a new parent. node.update_partial_path(partial_path); - branch.update_child(child_index, Some(Child::Node(node))); + branch.children[child_index] = Some(Child::Node(node)); counter!("firewood.insert", "merkle"=>"above").increment(1); Ok(Node::Branch(Box::new(branch))) } (Some((child_index, partial_path)), None) => { + let child_index = PathComponent::try_new(child_index).expect("valid component"); // 3. The key is below the node (i.e. its descendant) // ... ... // | | @@ -670,39 +679,28 @@ impl Merkle> { // ... (key may be below) ... (key is below) match node { Node::Branch(ref mut branch) => { - #[expect(clippy::indexing_slicing)] - let child = match std::mem::take(&mut branch.children[child_index as usize]) - { - None => { - // There is no child at this index. - // Create a new leaf and put it here. - let new_leaf = Node::Leaf(LeafNode { - value, - partial_path, - }); - branch.update_child(child_index, Some(Child::Node(new_leaf))); - counter!("firewood.insert", "merkle"=>"below").increment(1); - return Ok(node); - } - Some(Child::Node(child)) => child, - Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr.into())? - } - Some(Child::MaybePersisted(maybe_persisted, _)) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } + let Some(child) = branch.children.take(child_index) else { + // There is no child at this index. + // Create a new leaf and put it here. + let new_leaf = Node::Leaf(LeafNode { + value, + partial_path, + }); + branch.children[child_index] = Some(Child::Node(new_leaf)); + counter!("firewood.insert", "merkle"=>"below").increment(1); + return Ok(node); }; - + let child = self.read_for_update(child)?; let child = self.insert_helper(child, partial_path.as_ref(), value)?; - branch.update_child(child_index, Some(Child::Node(child))); + branch.children[child_index] = Some(Child::Node(child)); Ok(node) } - Node::Leaf(ref mut leaf) => { + Node::Leaf(leaf) => { // Turn this node into a branch node and put a new leaf as a child. let mut branch = BranchNode { - partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()), - value: Some(std::mem::take(&mut leaf.value)), - children: BranchNode::empty_children(), + partial_path: leaf.partial_path, + value: Some(leaf.value), + children: Children::new(), }; let new_leaf = Node::Leaf(LeafNode { @@ -710,7 +708,7 @@ impl Merkle> { partial_path, }); - branch.update_child(child_index, Some(Child::Node(new_leaf))); + branch.children[child_index] = Some(Child::Node(new_leaf)); counter!("firewood.insert", "merkle"=>"split").increment(1); Ok(Node::Branch(Box::new(branch))) @@ -718,6 +716,8 @@ impl Merkle> { } } (Some((key_index, key_partial_path)), Some((node_index, node_partial_path))) => { + let key_index = PathComponent::try_new(key_index).expect("valid component"); + let node_index = PathComponent::try_new(node_index).expect("valid component"); // 4. Neither is an ancestor of the other // ... ... // | | @@ -728,17 +728,17 @@ impl Merkle> { let mut branch = BranchNode { partial_path: path_overlap.shared.into(), value: None, - children: BranchNode::empty_children(), + children: Children::new(), }; node.update_partial_path(node_partial_path); - branch.update_child(node_index, Some(Child::Node(node))); + branch.children[node_index] = Some(Child::Node(node)); let new_leaf = Node::Leaf(LeafNode { value, partial_path: key_partial_path, }); - branch.update_child(key_index, Some(Child::Node(new_leaf))); + branch.children[key_index] = Some(Child::Node(new_leaf)); counter!("firewood.insert", "merkle" => "split").increment(1); Ok(Node::Branch(Box::new(branch))) @@ -775,10 +775,9 @@ impl Merkle> { /// Removes the value associated with the given `key` from the subtrie rooted at `node`. /// Returns the new root of the subtrie and the value that was removed, if any. /// Each element of `key` is 1 nibble. - #[expect(clippy::too_many_lines)] fn remove_helper( &mut self, - mut node: Node, + node: Node, key: &[u8], ) -> Result<(Option, Option), FileIoError> { // 4 possibilities for the position of the `key` relative to `node`: @@ -803,179 +802,37 @@ impl Merkle> { } (None, None) => { // 1. The node is at `key` - match &mut node { - Node::Branch(branch) => { + match node { + Node::Branch(mut branch) => { let Some(removed_value) = branch.value.take() else { // The branch has no value. Return the node as is. - return Ok((Some(node), None)); + return Ok((Some(Node::Branch(branch)), None)); }; - // This branch node has a value. - // If it has multiple children, return the node as is. - // Otherwise, its only child becomes the root of this subtrie. - let mut children_iter = - branch - .children - .iter_mut() - .enumerate() - .filter_map(|(index, child)| { - child.as_mut().map(|child| (index, child)) - }); - - let (child_index, child) = children_iter - .next() - .expect("branch node must have children"); - - if children_iter.next().is_some() { - // The branch has more than 1 child so it can't be removed. - Ok((Some(node), Some(removed_value))) - } else { - // The branch's only child becomes the root of this subtrie. - let mut child = match child { - Child::Node(child_node) => std::mem::take(child_node), - Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update((*addr).into())? - } - Child::MaybePersisted(maybe_persisted, _) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } - }; - - // The child's partial path is the concatenation of its (now removed) parent, - // its (former) child index, and its partial path. - match child { - Node::Branch(ref mut child_branch) => { - let partial_path = Path::from_nibbles_iterator( - branch - .partial_path - .iter() - .copied() - .chain(once(child_index as u8)) - .chain(child_branch.partial_path.iter().copied()), - ); - child_branch.partial_path = partial_path; - } - Node::Leaf(ref mut leaf) => { - let partial_path = Path::from_nibbles_iterator( - branch - .partial_path - .iter() - .copied() - .chain(once(child_index as u8)) - .chain(leaf.partial_path.iter().copied()), - ); - leaf.partial_path = partial_path; - } - } - - let node_partial_path = - std::mem::replace(&mut branch.partial_path, Path::new()); - - let partial_path = Path::from_nibbles_iterator( - branch - .partial_path - .iter() - .chain(once(&(child_index as u8))) - .chain(node_partial_path.iter()) - .copied(), - ); - - node.update_partial_path(partial_path); - - Ok((Some(child), Some(removed_value))) - } - } - Node::Leaf(leaf) => { - let removed_value = std::mem::take(&mut leaf.value); - Ok((None, Some(removed_value))) + Ok((self.flatten_branch(branch)?, Some(removed_value))) } + Node::Leaf(leaf) => Ok((None, Some(leaf.value))), } } (Some((child_index, child_partial_path)), None) => { + let child_index = PathComponent::try_new(child_index).expect("valid component"); // 3. The key is below the node (i.e. its descendant) match node { // we found a non-matching leaf node, so the value does not exist Node::Leaf(_) => Ok((Some(node), None)), - Node::Branch(ref mut branch) => { - #[expect(clippy::indexing_slicing)] - let child = match std::mem::take(&mut branch.children[child_index as usize]) - { - None => { - return Ok((Some(node), None)); - } - Some(Child::Node(node)) => node, - Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr.into())? - } - Some(Child::MaybePersisted(maybe_persisted, _)) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } + Node::Branch(mut branch) => { + let Some(child) = branch.children.take(child_index) else { + // child does not exist, so the value does not exist + return Ok((Some(Node::Branch(branch)), None)); }; + let child = self.read_for_update(child)?; let (child, removed_value) = self.remove_helper(child, child_partial_path.as_ref())?; - if let Some(child) = child { - branch.update_child(child_index, Some(Child::Node(child))); - } else { - branch.update_child(child_index, None); - } - - let mut children_iter = - branch - .children - .iter_mut() - .enumerate() - .filter_map(|(index, child)| { - child.as_mut().map(|child| (index, child)) - }); - - let Some((child_index, child)) = children_iter.next() else { - // The branch has no children. Turn it into a leaf. - let leaf = Node::Leaf(LeafNode { - value: branch.value.take().expect( - "branch node must have a value if it previously had only 1 child", - ), - partial_path: branch.partial_path.clone(), // TODO remove clone - }); - return Ok((Some(leaf), removed_value)); - }; - - // if there is more than one child or the branch has a value, return it - if branch.value.is_some() || children_iter.next().is_some() { - return Ok((Some(node), removed_value)); - } - - // The branch has only 1 child. Remove the branch and return the child. - let mut child = match child { - Child::Node(child_node) => std::mem::replace( - child_node, - Node::Leaf(LeafNode { - value: Box::default(), - partial_path: Path::new(), - }), - ), - Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update((*addr).into())? - } - Child::MaybePersisted(maybe_persisted, _) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } - }; + branch.children[child_index] = child.map(Child::Node); - // The child's partial path is the concatenation of its (now removed) parent, - // its (former) child index, and its partial path. - let child_partial_path = Path::from_nibbles_iterator( - branch - .partial_path - .iter() - .chain(once(&(child_index as u8))) - .chain(child.partial_path().iter()) - .copied(), - ); - child.update_partial_path(child_partial_path); - - Ok((Some(child), removed_value)) + Ok((self.flatten_branch(branch)?, removed_value)) } } } @@ -1004,7 +861,7 @@ impl Merkle> { fn remove_prefix_helper( &mut self, - mut node: Node, + node: Node, key: &[u8], deleted: &mut usize, ) -> Result, FileIoError> { @@ -1027,7 +884,7 @@ impl Merkle> { (None, _) => { // 1. The node is at `key`, or we're just above it // so we can start deleting below here - match &mut node { + match node { Node::Branch(branch) => { if branch.value.is_some() { // a KV pair was in the branch itself @@ -1047,89 +904,22 @@ impl Merkle> { Ok(Some(node)) } (Some((child_index, child_partial_path)), None) => { + let child_index = PathComponent::try_new(child_index).expect("valid component"); // 3. The key is below the node (i.e. its descendant) match node { Node::Leaf(_) => Ok(Some(node)), - Node::Branch(ref mut branch) => { - #[expect(clippy::indexing_slicing)] - let child = match std::mem::take(&mut branch.children[child_index as usize]) - { - None => { - return Ok(Some(node)); - } - Some(Child::Node(node)) => node, - Some(Child::AddressWithHash(addr, _)) => { - self.nodestore.read_for_update(addr.into())? - } - Some(Child::MaybePersisted(maybe_persisted, _)) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } + Node::Branch(mut branch) => { + let Some(child) = branch.children.take(child_index) else { + return Ok(Some(Node::Branch(branch))); }; + let child = self.read_for_update(child)?; let child = self.remove_prefix_helper(child, child_partial_path.as_ref(), deleted)?; - if let Some(child) = child { - branch.update_child(child_index, Some(Child::Node(child))); - } else { - branch.update_child(child_index, None); - } - - let mut children_iter = - branch - .children - .iter_mut() - .enumerate() - .filter_map(|(index, child)| { - child.as_mut().map(|child| (index, child)) - }); - - let Some((child_index, child)) = children_iter.next() else { - // The branch has no children. Turn it into a leaf. - let leaf = Node::Leaf(LeafNode { - value: branch.value.take().expect( - "branch node must have a value if it previously had only 1 child", - ), - partial_path: branch.partial_path.clone(), // TODO remove clone - }); - return Ok(Some(leaf)); - }; - - // if there is more than one child or the branch has a value, return it - if branch.value.is_some() || children_iter.next().is_some() { - return Ok(Some(node)); - } + branch.children[child_index] = child.map(Child::Node); - // The branch has only 1 child. Remove the branch and return the child. - let mut child = match child { - Child::Node(child_node) => std::mem::replace( - child_node, - Node::Leaf(LeafNode { - value: Box::default(), - partial_path: Path::new(), - }), - ), - Child::AddressWithHash(addr, _) => { - self.nodestore.read_for_update((*addr).into())? - } - Child::MaybePersisted(maybe_persisted, _) => { - self.nodestore.read_for_update(maybe_persisted.clone())? - } - }; - - // The child's partial path is the concatenation of its (now removed) parent, - // its (former) child index, and its partial path. - let child_partial_path = Path::from_nibbles_iterator( - branch - .partial_path - .iter() - .chain(once(&(child_index as u8))) - .chain(child.partial_path().iter()) - .copied(), - ); - child.update_partial_path(child_partial_path); - - Ok(Some(child)) + self.flatten_branch(branch) } } } @@ -1139,29 +929,18 @@ impl Merkle> { /// Recursively deletes all children of a branch node. fn delete_children( &mut self, - branch: &mut BranchNode, + mut branch: Box, deleted: &mut usize, ) -> Result<(), FileIoError> { if branch.value.is_some() { // a KV pair was in the branch itself *deleted = deleted.saturating_add(1); } - for children in &mut branch.children { - // read the child node - let child = match children { - Some(Child::Node(node)) => node, - Some(Child::AddressWithHash(addr, _)) => { - &mut self.nodestore.read_for_update((*addr).into())? - } - Some(Child::MaybePersisted(maybe_persisted, _)) => { - // For MaybePersisted, we need to get the node to update it - // We can't get a mutable reference from SharedNode, so we need to handle this differently - // For now, we'll skip this child since we can't modify it - let _shared_node = maybe_persisted.as_shared_node(&self.nodestore)?; - continue; - } - None => continue, + for (_, child) in &mut branch.children { + let Some(child) = child.take() else { + continue; }; + let child = self.read_for_update(child)?; match child { Node::Branch(child_branch) => { self.delete_children(child_branch, deleted)?; @@ -1173,6 +952,69 @@ impl Merkle> { } Ok(()) } + + /// Flattens a branch node into a new node. + /// + /// - If the branch has no value and no children, returns `None`. + /// - If the branch has a value and no children, it becomes a leaf node. + /// - If the branch has no value and exactly one child, it is replaced by that child + /// with an updated partial path. + /// - If the branch has a value and any children, it is returned as-is. + /// - If the branch has no value and multiple children, it is returned as-is. + fn flatten_branch( + &mut self, + mut branch_node: Box, + ) -> Result, FileIoError> { + let mut children_iter = branch_node.children.each_mut().into_iter(); + + let (child_index, child) = loop { + let Some((child_index, child_slot)) = children_iter.next() else { + // The branch has no children. Turn it into a leaf. + return match branch_node.value { + Some(value) => Ok(Some(Node::Leaf(LeafNode { + value, + partial_path: branch_node.partial_path, + }))), + None => Ok(None), + }; + }; + + let Some(child) = child_slot.take() else { + continue; + }; + + if branch_node.value.is_some() || children_iter.any(|(_, slot)| slot.is_some()) { + // put the child back in its slot since we removed it + child_slot.replace(child); + + // explicitly drop the iterator to release the mutable borrow on branch_node + drop(children_iter); + + // The branch has a value or more than 1 child, so it can't be flattened. + return Ok(Some(Node::Branch(branch_node))); + } + + // we have found the only child + break (child_index, child); + }; + + // The branch has only 1 child. Remove the branch and return the child. + let mut child = self.read_for_update(child)?; + + // The child's partial path is the concatenation of its (now removed) parent, + // its (former) child index, and its partial path. + let child_partial_path = Path::from_nibbles_iterator( + branch_node + .partial_path + .iter() + .chain(once(&child_index.as_u8())) + .chain(child.partial_path().iter()) + .copied(), + ); + child.update_partial_path(child_partial_path); + + Ok(Some(child)) + } } /// Returns an iterator where each element is the result of combining diff --git a/firewood/src/merkle/tests/proof.rs b/firewood/src/merkle/tests/proof.rs index f8423d07ef33..82ed412fbeb0 100644 --- a/firewood/src/merkle/tests/proof.rs +++ b/firewood/src/merkle/tests/proof.rs @@ -269,7 +269,7 @@ fn proof_path_construction_and_corruption() { let mut corrupt: Proof> = proof.clone().into_mutable(); if let Some(first) = (*corrupt).first_mut() { // Set all child hashes to empty so traversal fails - first.child_hashes = firewood_storage::BranchNode::empty_children(); + first.child_hashes = Children::new(); } let corrupt = corrupt.into_immutable(); let err = corrupt diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 5bffb7ea2f15..8d27f19549a8 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -11,7 +11,7 @@ )] use firewood_storage::{ - BranchNode, Children, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, + Children, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, PathComponent, PathIterItem, Preimage, TrieHash, ValueDigest, }; use thiserror::Error; @@ -91,18 +91,13 @@ pub struct ProofNode { /// Otherwise, the node's value or the hash of its value. pub value_digest: Option>, /// The hash of each child, or None if the child does not exist. - pub child_hashes: Children, + pub child_hashes: Children>, } impl std::fmt::Debug for ProofNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Filter the missing children and only show the present ones with their indices - let child_hashes = self - .child_hashes - .iter() - .enumerate() - .filter_map(|(i, h)| h.as_ref().map(|h| (i, h))) - .collect::>(); + let child_hashes = self.child_hashes.iter_present().collect::>(); // Compute the hash and render it as well let hash = firewood_storage::Preimage::to_hash(self); @@ -133,7 +128,7 @@ impl Hashable for ProofNode { self.value_digest.as_ref().map(ValueDigest::as_ref) } - fn children(&self) -> Children { + fn children(&self) -> Children> { self.child_hashes.clone() } } @@ -143,7 +138,7 @@ impl From for ProofNode { let child_hashes = if let Some(branch) = item.node.as_branch() { branch.children_hashes() } else { - BranchNode::empty_children() + Children::new() }; let partial_len = item @@ -222,10 +217,9 @@ impl Proof { return Err(ProofError::ShouldBePrefixOfNextKey); }; - expected_hash = node - .children() - .get(usize::from(next_nibble)) - .ok_or(ProofError::ChildIndexOutOfBounds)? + let next_nibble = + PathComponent::try_new(next_nibble).ok_or(ProofError::ChildIndexOutOfBounds)?; + expected_hash = node.children()[next_nibble] .as_ref() .ok_or(ProofError::NodeNotInTrie)? .clone(); diff --git a/firewood/src/proofs/bitmap.rs b/firewood/src/proofs/bitmap.rs index 9b02f70a4055..2ae53df87ec0 100644 --- a/firewood/src/proofs/bitmap.rs +++ b/firewood/src/proofs/bitmap.rs @@ -1,7 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood_storage::Children; +use firewood_storage::{Children, PathComponent}; #[derive(Clone, Copy, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] @@ -12,20 +12,14 @@ impl ChildrenMap { const SIZE: usize = firewood_storage::BranchNode::MAX_CHILDREN / 8; /// Create a new `ChildrenMap` from the given children array. - pub fn new(children: &Children) -> Self { - let mut map = [0_u8; Self::SIZE]; - - for (i, child) in children.iter().enumerate() { - if child.is_some() { - let (idx, bit) = (i / 8, i % 8); - #[expect(clippy::indexing_slicing)] - { - map[idx] |= 1 << bit; - } - } + pub fn new(children: &Children>) -> Self { + let mut map = Self([0_u8; Self::SIZE]); + + for (i, _) in children.iter_present() { + map.set(i); } - Self(map) + map } #[cfg(test)] @@ -33,11 +27,20 @@ impl ChildrenMap { self.0.iter().map(|b| b.count_ones() as usize).sum() } - pub fn iter_indices(self) -> impl Iterator { - (0..firewood_storage::BranchNode::MAX_CHILDREN).filter( - #[expect(clippy::indexing_slicing)] - move |i| self.0[i / 8] & (1 << (i % 8)) != 0, - ) + pub const fn get(self, index: PathComponent) -> bool { + #![expect(clippy::indexing_slicing)] + let i = index.as_usize(); + self.0[i / 8] & (1 << (i % 8)) != 0 + } + + pub const fn set(&mut self, index: PathComponent) { + #![expect(clippy::indexing_slicing)] + let i = index.as_usize(); + self.0[i / 8] |= 1 << (i % 8); + } + + pub fn iter_indices(self) -> impl Iterator { + PathComponent::ALL.into_iter().filter(move |&i| self.get(i)) } } @@ -76,43 +79,45 @@ impl std::fmt::Debug for ChildrenMap { #[cfg(test)] mod tests { + #![expect(clippy::unwrap_used)] + use super::*; - use firewood_storage::BranchNode; + use firewood_storage::{Children, PathComponent}; use test_case::test_case; - #[test_case(BranchNode::empty_children(), &[]; "empty")] + #[test_case(Children::new(), &[]; "empty")] #[test_case({ - let mut children = BranchNode::empty_children(); - children[0] = Some(()); + let mut children = Children::new(); + children[PathComponent::ALL[0]] = Some(()); children - }, &[0]; "first")] + }, &[PathComponent::ALL[0]]; "first")] #[test_case({ - let mut children = BranchNode::empty_children(); - children[1] = Some(()); + let mut children = Children::new(); + children[PathComponent::ALL[1]] = Some(()); children - }, &[1]; "second")] + }, &[PathComponent::ALL[1]]; "second")] #[test_case({ - let mut children = BranchNode::empty_children(); - children[BranchNode::MAX_CHILDREN - 1] = Some(()); + let mut children = Children::new(); + children[PathComponent::ALL.last().copied().unwrap()] = Some(()); children - }, &[BranchNode::MAX_CHILDREN - 1]; "last")] + }, &[PathComponent::ALL.last().copied().unwrap()]; "last")] #[test_case({ - let mut children = BranchNode::empty_children(); - for slot in children.iter_mut().step_by(2) { + let mut children = Children::new(); + for (_, slot) in children.iter_mut().step_by(2) { *slot = Some(()); } children - }, &(0..BranchNode::MAX_CHILDREN).step_by(2).collect::>(); "evens")] + }, &PathComponent::ALL.into_iter().step_by(2).collect::>(); "evens")] #[test_case({ - let mut children = BranchNode::empty_children(); - for slot in children.iter_mut().skip(1).step_by(2) { + let mut children = Children::new(); + for (_, slot) in children.iter_mut().skip(1).step_by(2) { *slot = Some(()); } children - }, &(1..BranchNode::MAX_CHILDREN).step_by(2).collect::>(); "odds")] - #[test_case([Some(()); BranchNode::MAX_CHILDREN], &(0..BranchNode::MAX_CHILDREN).collect::>(); "all")] - fn test_children_map(children: Children<()>, indicies: &[usize]) { + }, &PathComponent::ALL.into_iter().skip(1).step_by(2).collect::>(); "odds")] + #[test_case(Children::from_fn(|_| Some(())), &PathComponent::ALL; "all")] + fn test_children_map(children: Children>, indicies: &[PathComponent]) { let map = ChildrenMap::new(&children); assert_eq!(map.len(), indicies.len()); diff --git a/firewood/src/proofs/de.rs b/firewood/src/proofs/de.rs index fdc6de474878..b00c84de19c7 100644 --- a/firewood/src/proofs/de.rs +++ b/firewood/src/proofs/de.rs @@ -3,7 +3,7 @@ #[cfg(feature = "ethhash")] use firewood_storage::HashType; -use firewood_storage::{BranchNode, TrieHash, ValueDigest}; +use firewood_storage::{Children, TrieHash, ValueDigest}; use integer_encoding::VarInt; use crate::{ @@ -91,12 +91,9 @@ impl Version0 for ProofNode { let children_map = reader.read_item::()?; - let mut child_hashes = BranchNode::empty_children(); + let mut child_hashes = Children::new(); for idx in children_map.iter_indices() { - #[expect(clippy::indexing_slicing)] - { - child_hashes[idx] = Some(reader.read_item()?); - } + child_hashes[idx] = Some(reader.read_item()?); } Ok(ProofNode { diff --git a/firewood/src/proofs/ser.rs b/firewood/src/proofs/ser.rs index 87665a71c3d4..d6e9dc1ad7d7 100644 --- a/firewood/src/proofs/ser.rs +++ b/firewood/src/proofs/ser.rs @@ -102,7 +102,7 @@ impl WriteItem for ProofNode { out.push_var_int(self.partial_len); self.value_digest.write_item(out); ChildrenMap::new(&self.child_hashes).write_item(out); - for child in self.child_hashes.iter().flatten() { + for (_, child) in self.child_hashes.iter_present() { child.write_item(out); } } diff --git a/storage/benches/serializer.rs b/storage/benches/serializer.rs index 77eb6372fc18..6005d7c49f5c 100644 --- a/storage/benches/serializer.rs +++ b/storage/benches/serializer.rs @@ -10,13 +10,12 @@ reason = "Found 7 occurrences after enabling the lint." )] -use std::array::from_fn; use std::fs::File; use std::os::raw::c_int; use criterion::profiler::Profiler; use criterion::{Bencher, Criterion, criterion_group, criterion_main}; -use firewood_storage::{LeafNode, Node, Path}; +use firewood_storage::{Children, LeafNode, Node, Path, PathComponent}; use pprof::ProfilerGuard; use smallvec::SmallVec; @@ -97,8 +96,8 @@ fn branch(c: &mut Criterion) { let mut input = Node::Branch(Box::new(firewood_storage::BranchNode { partial_path: Path(SmallVec::from_slice(&[0, 1])), value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()), - children: from_fn(|i| { - if i == 0 { + children: Children::from_fn(|i| { + if i.as_u8() == 0 { Some(firewood_storage::Child::AddressWithHash( firewood_storage::LinearAddress::new(1).unwrap(), firewood_storage::HashType::from([0; 32]), @@ -119,15 +118,15 @@ fn branch(c: &mut Criterion) { group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); - let child = input.as_branch().unwrap().children[0].clone(); + let child = input.as_branch().unwrap().children[PathComponent::ALL[0]].clone(); let mut group = c.benchmark_group("2_child"); - input.as_branch_mut().unwrap().children[1] = child.clone(); + input.as_branch_mut().unwrap().children[PathComponent::ALL[1]] = child.clone(); group.bench_with_input("manual", &input, manual_serializer); group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); let mut group = c.benchmark_group("16_child"); - input.as_branch_mut().unwrap().children = std::array::from_fn(|_| child.clone()); + input.as_branch_mut().unwrap().children = Children::from_fn(|_| child.clone()); group.bench_with_input("manual", &input, manual_serializer); group.bench_with_input("from_reader", &to_bytes(&input), manual_deserializer); group.finish(); diff --git a/storage/src/checker/mod.rs b/storage/src/checker/mod.rs index 4e6f19fb8e7d..d4dbdf841893 100644 --- a/storage/src/checker/mod.rs +++ b/storage/src/checker/mod.rs @@ -386,7 +386,8 @@ where let mut errors = Vec::new(); match node.as_ref() { Node::Branch(branch) => { - let num_children = branch.children_iter().count(); + let persist_info = branch.persist_info(); + let num_children = persist_info.count(); { // update the branch area count let branch_area_count = trie_stats @@ -412,10 +413,13 @@ where } // this is an internal node, traverse the children - for (nibble, (address, hash)) in branch.children_iter() { + for (nibble, (address, hash)) in persist_info + .into_iter() + .filter_map(|(idx, slot)| slot.map(|child| (idx, child))) + { let parent = TrieNodeParent::Parent(subtrie_root_address, nibble); let mut child_path_prefix = current_path_prefix.clone(); - child_path_prefix.0.push(nibble as u8); + child_path_prefix.0.push(nibble.as_u8()); let child_subtrie = SubTrieMetadata { root_address: address, root_hash: hash.clone(), @@ -760,7 +764,8 @@ mod test { }; use crate::nodestore::primitives::area_size_iter; use crate::{ - BranchNode, Child, FreeListParent, LeafNode, NodeStore, Path, area_index, hash_node, + BranchNode, Child, Children, FreeListParent, LeafNode, NodeStore, Path, PathComponent, + area_index, hash_node, }; #[derive(Debug)] @@ -806,8 +811,8 @@ mod test { let area_count = leaf_area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); - let mut branch_children = BranchNode::empty_children(); - branch_children[1] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); + let mut branch_children = Children::new(); + branch_children[PathComponent::ALL[1]] = Some(Child::AddressWithHash(leaf_addr, leaf_hash)); let branch = Node::Branch(Box::new(BranchNode { partial_path: Path::from([3]), value: None, @@ -822,8 +827,9 @@ mod test { let area_count = branch_area_counts.get_mut(&stored_area_size).unwrap(); *area_count = area_count.saturating_add(1); - let mut root_children = BranchNode::empty_children(); - root_children[0] = Some(Child::AddressWithHash(branch_addr, branch_hash)); + let mut root_children = Children::new(); + root_children[PathComponent::ALL[0]] = + Some(Child::AddressWithHash(branch_addr, branch_hash)); let root = Node::Branch(Box::new(BranchNode { partial_path: Path::from([2]), value: None, @@ -1035,8 +1041,13 @@ mod test { let branch_addr = *branch_addr; // Replace branch hash with a wrong hash - let (leaf_addr, _) = branch.children[1].as_ref().unwrap().persist_info().unwrap(); - branch.children[1] = Some(Child::AddressWithHash(leaf_addr, HashType::empty())); + let (leaf_addr, _) = branch.children[PathComponent::ALL[1]] + .as_ref() + .unwrap() + .persist_info() + .unwrap(); + branch.children[PathComponent::ALL[1]] = + Some(Child::AddressWithHash(leaf_addr, HashType::empty())); test_write_new_node(&nodestore, branch_node, branch_addr.get()); // Compute the current branch hash @@ -1056,7 +1067,7 @@ mod test { .find(|(node, _)| matches!(node, Node::Branch(b) if *b.partial_path.0 == [2])) .unwrap(); let root_branch = root_node.as_branch().unwrap(); - let (_, parent_stored_hash) = root_branch.children[0] + let (_, parent_stored_hash) = root_branch.children[PathComponent::ALL[0]] .as_ref() .unwrap() .persist_info() @@ -1076,7 +1087,7 @@ mod test { let expected_error = CheckerError::HashMismatch { address: branch_addr, path: Path::from([2, 0, 3]), - parent: TrieNodeParent::Parent(root_addr, 0), + parent: TrieNodeParent::Parent(root_addr, PathComponent::ALL[0]), parent_stored_hash, computed_hash, }; diff --git a/storage/src/checker/range_set.rs b/storage/src/checker/range_set.rs index 03608569f36e..52bf271b4d70 100644 --- a/storage/src/checker/range_set.rs +++ b/storage/src/checker/range_set.rs @@ -591,7 +591,7 @@ mod test_range_set { #[expect(clippy::unwrap_used)] mod test_linear_address_range_set { - use crate::{FreeListParent, TrieNodeParent, area_index}; + use crate::{FreeListParent, PathComponent, TrieNodeParent, area_index}; use super::*; use test_case::test_case; @@ -666,7 +666,8 @@ mod test_linear_address_range_set { let end1_addr = LinearAddress::new(start1 + size1).unwrap(); let start2_addr = LinearAddress::new(start2).unwrap(); - let parent1 = StoredAreaParent::TrieNode(TrieNodeParent::Parent(start1_addr, 5)); + let parent1 = + StoredAreaParent::TrieNode(TrieNodeParent::Parent(start1_addr, PathComponent::ALL[5])); let parent2 = StoredAreaParent::FreeList(FreeListParent::FreeListHead(area_index!(3))); let mut visited = LinearAddressRangeSet::new(0x1000).unwrap(); diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 35d130ec9fe1..43c714936e10 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -11,11 +11,10 @@ pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { Node::Branch(node) => { // All child hashes should be filled in. // TODO danlaine: Enforce this with the type system. - #[cfg(debug_assertions)] debug_assert!( node.children .iter() - .all(|c| !matches!(c, Some(crate::Child::Node(_)))), + .all(|(_, c)| !matches!(c, Some(crate::Child::Node(_)))), "branch children: {:?}", node.children ); @@ -161,7 +160,7 @@ pub trait Hashable: std::fmt::Debug { fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. - fn children(&self) -> Children; + fn children(&self) -> Children>; /// The full path of this node including the parent's prefix where each byte is a nibble. fn full_path(&self) -> impl Iterator + Clone { @@ -180,7 +179,7 @@ pub trait Preimage: std::fmt::Debug { trait HashableNode: std::fmt::Debug { fn partial_path(&self) -> impl Iterator + Clone; fn value(&self) -> Option<&[u8]>; - fn child_hashes(&self) -> Children; + fn child_hashes(&self) -> Children>; } impl HashableNode for BranchNode { @@ -192,7 +191,7 @@ impl HashableNode for BranchNode { self.value.as_deref() } - fn child_hashes(&self) -> Children { + fn child_hashes(&self) -> Children> { self.children_hashes() } } @@ -206,8 +205,8 @@ impl HashableNode for LeafNode { Some(&self.value) } - fn child_hashes(&self) -> Children { - BranchNode::empty_children() + fn child_hashes(&self) -> Children> { + Children::new() } } @@ -236,7 +235,7 @@ impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { self.node.value().map(ValueDigest::Value) } - fn children(&self) -> Children { + fn children(&self) -> Children> { self.node.child_hashes() } } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 66363c754fb4..06d32d7a4954 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -107,7 +107,7 @@ impl Preimage for T { let child_hashes = self.children(); - let children = child_hashes.iter().filter(|c| c.is_some()).count(); + let children = child_hashes.count(); if children == 0 { // since there are no children, this must be a leaf @@ -157,7 +157,7 @@ impl Preimage for T { // for a branch, there are always 16 children and a value // Child::None we encode as RLP empty_data (0x80) let mut rlp = RlpStream::new_list(const { BranchNode::MAX_CHILDREN + 1 }); - for child in &child_hashes { + for (_, child) in &child_hashes { match child { Some(HashType::Hash(hash)) => rlp.append(&hash.as_slice()), Some(HashType::Rlp(rlp_bytes)) => rlp.append_raw(rlp_bytes, 1), @@ -197,7 +197,7 @@ impl Preimage for T { // actually one longer than it is reported match child_hashes .iter() - .find_map(|c| c.as_ref()) + .find_map(|(_, c)| c.as_ref()) .expect("we know there is exactly one child") { HashType::Hash(hash) => hash.clone(), diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index fd19d6a38f83..d13da0a9496f 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -35,13 +35,13 @@ impl Preimage for T { fn write(&self, buf: &mut impl HasUpdate) { let children = self.children(); - let num_children = children.iter().filter(|c| c.is_some()).count() as u64; + let num_children = children.count() as u64; add_varint_to_buf(buf, num_children); - for (index, hash) in children.iter().enumerate() { + for (index, hash) in &children { if let Some(hash) = hash { - add_varint_to_buf(buf, index as u64); + add_varint_to_buf(buf, u64::from(index.as_u8())); buf.update(hash); } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 27c802f9b3f6..de8443c950ed 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -47,7 +47,7 @@ pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; pub use node::{ - BranchNode, Child, Children, LeafNode, Node, PathIterItem, + BranchNode, Child, Children, ChildrenSlots, LeafNode, Node, PathIterItem, branch::{HashType, IntoHashType}, }; pub use nodestore::{ @@ -55,8 +55,9 @@ pub use nodestore::{ NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; pub use path::{ - IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, PathComponentSliceExt, SplitPath, - TriePath, TriePathAsPackedBytes, TriePathFromPackedBytes, TriePathFromUnpackedBytes, + ComponentIter, IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, + PathComponentSliceExt, SplitPath, TriePath, TriePathAsPackedBytes, TriePathFromPackedBytes, + TriePathFromUnpackedBytes, }; #[cfg(not(feature = "branch_factor_256"))] pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; @@ -110,7 +111,7 @@ pub enum TrieNodeParent { /// The stored area is the root of the trie, so the header points to it Root, /// The stored area is not the root of the trie, so a parent trie node points to it - Parent(LinearAddress, usize), + Parent(LinearAddress, PathComponent), } /// This enum encapsulates what points to the stored area allocated for a free list. diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 2b7473316b75..cea3321d2cd6 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -5,13 +5,10 @@ clippy::match_same_arms, reason = "Found 1 occurrences after enabling the lint." )] -#![expect( - clippy::missing_panics_doc, - reason = "Found 2 occurrences after enabling the lint." -)] use crate::node::ExtendableBytes; -use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path, SharedNode}; +use crate::node::children::Children; +use crate::{LeafNode, LinearAddress, MaybePersistedNode, Node, Path, PathComponent, SharedNode}; use std::fmt::{Debug, Formatter}; use std::io::Read; @@ -92,7 +89,6 @@ pub(crate) trait ReadSerializable: Read { impl ReadSerializable for T {} #[derive(PartialEq, Eq, Clone, Debug)] -#[repr(C)] /// A child of a branch node. pub enum Child { /// There is a child at this index, but we haven't hashed it @@ -289,7 +285,10 @@ mod ethhash { Ok(HashOrRlp::Hash(TrieHash::from(bytes))) } len if len < 32 => { - reader.read_exact(&mut bytes[0..len as usize])?; + #[expect(clippy::indexing_slicing)] + { + reader.read_exact(&mut bytes[0..len as usize])?; + } Ok(HashOrRlp::Rlp(SmallVec::from_buf_and_len( bytes, len as usize, @@ -364,9 +363,6 @@ mod ethhash { } } -/// Type alias for a collection of children in a branch node. -pub type Children = [Option; BranchNode::MAX_CHILDREN]; - #[derive(PartialEq, Eq, Clone)] /// A branch node pub struct BranchNode { @@ -380,7 +376,7 @@ pub struct BranchNode { /// Element i is the child at index i, or None if there is no child at that index. /// Each element is (`child_hash`, `child_address`). /// `child_address` is None if we don't know the child's hash. - pub children: Children, + pub children: Children>, } impl Debug for BranchNode { @@ -388,7 +384,7 @@ impl Debug for BranchNode { write!(f, "[BranchNode")?; write!(f, r#" path="{:?}""#, self.partial_path)?; - for (i, c) in self.children.iter().enumerate() { + for (i, c) in &self.children { match c { None => {} Some(Child::Node(_)) => {} //TODO @@ -417,68 +413,20 @@ impl Debug for BranchNode { } impl BranchNode { - /// The maximum number of children in a [`BranchNode`] - #[cfg(feature = "branch_factor_256")] - pub const MAX_CHILDREN: usize = 256; - - /// The maximum number of children in a [`BranchNode`] - #[cfg(not(feature = "branch_factor_256"))] - pub const MAX_CHILDREN: usize = 16; + /// The maximum number of children a branch node can have. + pub const MAX_CHILDREN: usize = PathComponent::LEN; - /// Convenience function to create a new array of empty children. - #[must_use] - pub const fn empty_children() -> Children { - [const { None }; Self::MAX_CHILDREN] - } - - /// Returns the address of the child at the given index. - /// Panics if `child_index` >= [`BranchNode::MAX_CHILDREN`]. - #[must_use] - pub fn child(&self, child_index: u8) -> &Option { - self.children - .get(child_index as usize) - .expect("child_index is in bounds") - } - - /// Update the child at `child_index` to be `new_child_addr`. - /// If `new_child_addr` is None, the child is removed. - pub fn update_child(&mut self, child_index: u8, new_child: Option) { - let child = self - .children - .get_mut(child_index as usize) - .expect("child_index is in bounds"); - - *child = new_child; - } - - /// Helper to iterate over only valid children + /// Returns a set of persistence information (address and hash) for each child that + /// is persisted. /// - /// ## Panics - /// - /// Note: This function will panic if any child is a [`Child::Node`] variant - /// as it is still mutable and has not been hashed yet. Unlike - /// [`BranchNode::children_addresses`], this will _not_ panic if the child - /// is an unpersisted [`Child::MaybePersisted`]. - #[track_caller] - pub(crate) fn children_iter( - &self, - ) -> impl Iterator + Clone { + /// This will skip any child that is a [`Child::Node`] variant (not yet hashed) + /// or a [`Child::MaybePersisted`] variant that does not have an address (not + /// yet persisted). + #[must_use] + pub fn persist_info(&self) -> Children> { self.children - .iter() - .enumerate() - .filter_map(|(i, child)| match child { - None => None, - Some(Child::Node(_)) => { - panic!("attempted to iterate over an in-memory mutable node") - } - Some(Child::AddressWithHash(address, hash)) => Some((i, (*address, hash))), - Some(Child::MaybePersisted(maybe_persisted, hash)) => { - // For MaybePersisted, we need the address if it's persisted - maybe_persisted - .as_linear_address() - .map(|addr| (i, (addr, hash))) - } - }) + .each_ref() + .map(|_, c| c.as_ref().and_then(Child::persist_info)) } /// Returns a set of hashes for each child that has a hash set. @@ -486,28 +434,16 @@ impl BranchNode { /// The index of the hash in the returned array corresponds to the index of the child /// in the branch node. /// - /// ## Panics - /// - /// Note: This function will panic if any child is a [`Child::Node`] variant + /// Note: This function will skip any child is a [`Child::Node`] variant /// as it is still mutable and has not been hashed yet. /// /// This is an unintentional side effect of the current implementation. Future - /// changes will have this check implemented structurally to prevent such panics. + /// changes will have this check implemented structurally to prevent such cases. #[must_use] - #[track_caller] - pub fn children_hashes(&self) -> Children { - let mut hashes = Self::empty_children(); - for (child, slot) in self.children.iter().zip(hashes.iter_mut()) { - match child { - None => {} - Some(Child::Node(_)) => { - panic!("attempted to get the hash of an in-memory mutable node") - } - Some(Child::AddressWithHash(_, hash)) => _ = slot.replace(hash.clone()), - Some(Child::MaybePersisted(_, hash)) => _ = slot.replace(hash.clone()), - } - } - hashes + pub fn children_hashes(&self) -> Children> { + self.children + .each_ref() + .map(|_, c| c.as_ref().and_then(Child::hash).cloned()) } /// Returns a set of addresses for each child that has an address set. @@ -515,37 +451,18 @@ impl BranchNode { /// The index of the address in the returned array corresponds to the index of the child /// in the branch node. /// - /// ## Panics - /// - /// Note: This function will panic if: + /// Note: This function will skip: /// - Any child is a [`Child::Node`] variant as it does not have an address. /// - Any child is a [`Child::MaybePersisted`] variant that is not yet /// persisted, as we do not yet know its address. /// /// This is an unintentional side effect of the current implementation. Future - /// changes will have this check implemented structurally to prevent such panics. + /// changes will have this check implemented structurally to prevent such cases. #[must_use] - #[track_caller] - pub fn children_addresses(&self) -> Children { - let mut addrs = Self::empty_children(); - for (child, slot) in self.children.iter().zip(addrs.iter_mut()) { - match child { - None => {} - Some(Child::Node(_)) => { - panic!("attempted to get the address of an in-memory mutable node") - } - Some(Child::AddressWithHash(address, _)) => _ = slot.replace(*address), - Some(Child::MaybePersisted(maybe_persisted, _)) => { - // For MaybePersisted, we need the address if it's persisted - if let Some(addr) = maybe_persisted.as_linear_address() { - slot.replace(addr); - } else { - panic!("attempted to get the address of an unpersisted MaybePersistedNode") - } - } - } - } - addrs + pub fn children_addresses(&self) -> Children> { + self.children + .each_ref() + .map(|_, c| c.as_ref().and_then(Child::persisted_address)) } } @@ -554,7 +471,7 @@ impl From<&LeafNode> for BranchNode { BranchNode { partial_path: leaf.partial_path.clone(), value: Some(Box::from(&leaf.value[..])), - children: BranchNode::empty_children(), + children: Children::new(), } } } diff --git a/storage/src/node/children.rs b/storage/src/node/children.rs new file mode 100644 index 000000000000..7c0c96d55ac1 --- /dev/null +++ b/storage/src/node/children.rs @@ -0,0 +1,236 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::PathComponent; + +const MAX_CHILDREN: usize = PathComponent::LEN; + +/// Type alias for an iterator over the slots of a branch node's children +/// with its corresponding [`PathComponent`]. +pub type ChildrenSlots = std::iter::Zip< + std::array::IntoIter, + std::array::IntoIter, +>; + +/// The type of iterator returned by [`Children::iter_present`]. +pub type IterPresentRef<'a, T> = std::iter::FilterMap< + ChildrenSlots<&'a Option>, + fn((PathComponent, &'a Option)) -> Option<(PathComponent, &'a T)>, +>; + +/// The type of iterator returned by [`Children::iter_present`]. +pub type IterPresentMut<'a, T> = std::iter::FilterMap< + ChildrenSlots<&'a mut Option>, + fn((PathComponent, &'a mut Option)) -> Option<(PathComponent, &'a mut T)>, +>; + +/// Type alias for a collection of children in a branch node. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Children([T; MAX_CHILDREN]); + +impl std::fmt::Debug for Children { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_map() + .entries(self.iter().map(|(pc, child)| (pc.as_u8(), child))) + .finish() + } +} + +impl Children { + /// Creates a new [`Children`] by calling `f` for each possible + /// [`PathComponent`]. + #[must_use] + pub fn from_fn(f: impl FnMut(PathComponent) -> T) -> Self { + Self(PathComponent::ALL.map(f)) + } + + /// Borrows each element and returns a [`Children`] wrapping the references. + #[must_use] + pub fn each_ref(&self) -> Children<&T> { + Children(self.0.each_ref()) + } + + /// Borrows each element mutably and returns a [`Children`] wrapping the + /// mutable references. + #[must_use] + pub fn each_mut(&mut self) -> Children<&mut T> { + Children(self.0.each_mut()) + } + + /// Returns a reference to the element at `index`. + /// + /// This is infallible because `index` is guaranteed to be in-bounds. + #[must_use] + pub const fn get(&self, index: PathComponent) -> &T { + #![expect(clippy::indexing_slicing)] + &self.0[index.as_usize()] + } + + /// Returns a mutable reference to the element at `index`. + /// + /// This is infallible because `index` is guaranteed to be in-bounds. + #[must_use] + pub const fn get_mut(&mut self, index: PathComponent) -> &mut T { + #![expect(clippy::indexing_slicing)] + &mut self.0[index.as_usize()] + } + + /// Replaces the element at `index` with `value`, returning the previous + /// value. + /// + /// This is infallible because `index` is guaranteed to be in-bounds. + pub const fn replace(&mut self, index: PathComponent, value: T) -> T { + #![expect(clippy::indexing_slicing)] + std::mem::replace(&mut self.0[index.as_usize()], value) + } + + /// Maps each element to another value using `f`, returning a new + /// [`Children`] containing the results. + #[must_use] + pub fn map(self, mut f: impl FnMut(PathComponent, T) -> O) -> Children { + let mut pc = const { PathComponent::ALL[0] }; + Children(self.0.map(|child| { + let out = f(pc, child); + pc = pc.wrapping_next(); + out + })) + } + + /// Returns an iterator over each element with its corresponding + /// [`PathComponent`]. + pub fn iter(&self) -> ChildrenSlots<&T> { + self.into_iter() + } + + /// Returns a mutable iterator over each element with its corresponding + /// [`PathComponent`]. + pub fn iter_mut(&mut self) -> ChildrenSlots<&mut T> { + self.into_iter() + } + + /// Merges this collection of children with another collection of children + /// using the given function. + /// + /// If the function returns an error, the merge is aborted and the error is + /// returned. Because this method takes `self` and `other` by value, they + /// will be dropped if the merge fails. + pub fn merge( + self, + other: Children, + mut merge: impl FnMut(PathComponent, T, U) -> Result, E>, + ) -> Result>, E> { + let iter = self.0.into_iter().zip(other.0); + let mut output = [const { None }; MAX_CHILDREN]; + for (slot, (pc, (a, b))) in output + .iter_mut() + .zip(PathComponent::ALL.into_iter().zip(iter)) + { + *slot = merge(pc, a, b)?; + } + Ok(Children(output)) + } +} + +impl Children> { + /// Creates a new [`Children`] with all elements set to `None`. + #[must_use] + pub const fn new() -> Self { + Self([const { None }; MAX_CHILDREN]) + } + + /// Returns the number of [`Some`] elements in this collection. + #[must_use] + pub fn count(&self) -> usize { + self.0.iter().filter(|c| c.is_some()).count() + } + + /// Sets the element at `index` to `None`, returning the previous value. + #[must_use] + pub const fn take(&mut self, index: PathComponent) -> Option { + self.replace(index, None) + } + + /// Returns an iterator over each [`Some`] element with its corresponding + /// [`PathComponent`]. + pub fn iter_present(&self) -> IterPresentRef<'_, T> { + self.into_iter() + .filter_map(|(pc, opt)| opt.as_ref().map(|v| (pc, v))) + } +} + +impl Default for Children> { + fn default() -> Self { + Self::new() + } +} + +impl<'a, T> Children<&'a Option> { + /// Returns the number of [`Some`] elements in this collection. + #[must_use] + pub fn count(&self) -> usize { + self.0.iter().filter(|c| c.is_some()).count() + } + + /// Returns an iterator over each [`Some`] element with its corresponding + /// [`PathComponent`]. + pub fn iter_present(self) -> IterPresentRef<'a, T> { + self.into_iter() + .filter_map(|(pc, opt)| opt.as_ref().map(|v| (pc, v))) + } +} + +impl<'a, T> Children<&'a mut Option> { + /// Returns the number of [`Some`] elements in this collection. + #[must_use] + pub fn count(&self) -> usize { + self.0.iter().filter(|c| c.is_some()).count() + } + + /// Returns an iterator over each [`Some`] element with its corresponding + /// [`PathComponent`]. + pub fn iter_present(self) -> IterPresentMut<'a, T> { + self.into_iter() + .filter_map(|(pc, opt)| opt.as_mut().map(|v| (pc, v))) + } +} + +impl std::ops::Index for Children { + type Output = T; + + fn index(&self, index: PathComponent) -> &Self::Output { + self.get(index) + } +} + +impl std::ops::IndexMut for Children { + fn index_mut(&mut self, index: PathComponent) -> &mut Self::Output { + self.get_mut(index) + } +} + +impl IntoIterator for Children { + type Item = (PathComponent, T); + type IntoIter = ChildrenSlots; + + fn into_iter(self) -> Self::IntoIter { + PathComponent::ALL.into_iter().zip(self.0) + } +} + +impl<'a, T: 'a> IntoIterator for &'a Children { + type Item = (PathComponent, &'a T); + type IntoIter = ChildrenSlots<&'a T>; + + fn into_iter(self) -> Self::IntoIter { + self.each_ref().into_iter() + } +} + +impl<'a, T: 'a> IntoIterator for &'a mut Children { + type Item = (PathComponent, &'a mut T); + type IntoIter = ChildrenSlots<&'a mut T>; + + fn into_iter(self) -> Self::IntoIter { + self.each_mut().into_iter() + } +} diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 72cb9a95de5f..3c7d36cbddaf 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -1,10 +1,6 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#![expect( - clippy::indexing_slicing, - reason = "Found 1 occurrences after enabling the lint." -)] #![expect( clippy::items_after_statements, reason = "Found 2 occurrences after enabling the lint." @@ -20,10 +16,11 @@ use crate::node::branch::ReadSerializable; use crate::nodestore::AreaIndex; -use crate::{HashType, LinearAddress, Path, SharedNode}; +use crate::{HashType, LinearAddress, Path, PathComponent, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; -pub use branch::{BranchNode, Child, Children}; +pub use branch::{BranchNode, Child}; +pub use children::{Children, ChildrenSlots}; use enum_as_inner::EnumAsInner; use integer_encoding::{VarInt, VarIntReader as _}; pub use leaf::LeafNode; @@ -31,6 +28,7 @@ use std::fmt::Debug; use std::io::{Error, Read, Write}; pub mod branch; +pub mod children; mod leaf; pub mod path; pub mod persist; @@ -224,11 +222,7 @@ impl Node { pub fn as_bytes(&self, prefix: AreaIndex, encoded: &mut T) { match self { Node::Branch(b) => { - let child_iter = b - .children - .iter() - .enumerate() - .filter_map(|(offset, child)| child.as_ref().map(|c| (offset, c))); + let child_iter = b.children.iter_present(); let childcount = child_iter.clone().count(); // encode the first byte @@ -279,7 +273,7 @@ impl Node { } } else { for (position, child) in child_iter { - encoded.extend_var_int(position); + encoded.extend_var_int(position.as_u8()); let (address, hash) = child .persist_info() .expect("child must be hashed when serializing"); @@ -357,10 +351,10 @@ impl Node { None }; - let mut children = BranchNode::empty_children(); + let mut children = Children::new(); if childcount == 0 { // branch is full of all children - for child in &mut children { + for (_, child) in &mut children { // TODO: we can read them all at once let mut address_buf = [0u8; 8]; serialized.read_exact(&mut address_buf)?; @@ -378,7 +372,13 @@ impl Node { for _ in 0..childcount { let mut position_buf = [0u8; 1]; serialized.read_exact(&mut position_buf)?; - let position = position_buf[0] as usize; + let position = + PathComponent::try_new(position_buf[0]).ok_or_else(|| { + Error::other(format!( + "invalid child position {:02x}", + position_buf[0] + )) + })?; let mut address_buf = [0u8; 8]; serialized.read_exact(&mut address_buf)?; @@ -417,7 +417,7 @@ pub struct PathIterItem { /// Specifically, it's the child at index `next_nibble` in `node`'s /// children array. /// None if `node` is the last node in the path. - pub next_nibble: Option, + pub next_nibble: Option, } fn read_path_with_overflow_length( @@ -451,7 +451,7 @@ mod test { use crate::node::{BranchNode, LeafNode, Node}; use crate::nodestore::AreaIndex; - use crate::{Child, LinearAddress, NibblesIterator, Path}; + use crate::{Child, Children, LinearAddress, NibblesIterator, Path}; use test_case::test_case; #[test_case( @@ -467,8 +467,8 @@ mod test { #[test_case(Node::Branch(Box::new(BranchNode { partial_path: Path::from(vec![0, 1]), value: None, - children: std::array::from_fn(|i| { - if i == 15 { + children: Children::from_fn(|i| { + if i.as_u8() == 15 { Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) } else { None @@ -478,14 +478,14 @@ mod test { #[test_case(Node::Branch(Box::new(BranchNode { partial_path: Path::from(vec![0, 1, 2, 3]), value: Some(vec![4, 5, 6, 7].into()), - children: std::array::from_fn(|_| + children: Children::from_fn(|_| Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 652; "full branch node with long partial path and value" )] #[test_case(Node::Branch(Box::new(BranchNode { partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"this is a really long partial path, like so long it's more than 63 nibbles long which triggers #1056.")), value: Some(vec![4, 5, 6, 7].into()), - children: std::array::from_fn(|_| + children: Children::from_fn(|_| Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 851; "full branch node with obnoxiously long partial path" )] @@ -497,7 +497,7 @@ verify that we decode the entire value every time. previously, we would only rea the first byte for the value length, which is incorrect if the length is greater than 126 bytes as the length would be encoded in multiple bytes. ").into()), - children: std::array::from_fn(|_| + children: Children::from_fn(|_| Some(Child::AddressWithHash(LinearAddress::new(1).unwrap(), std::array::from_fn::(|i| i as u8).into())) )})), 1165; "full branch node with obnoxiously long partial path and long value" )] diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 7f864e84ea6f..61bd31596ceb 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -192,6 +192,7 @@ impl Iterator for NibblesIterator<'_> { #[cfg(feature = "branch_factor_256")] fn next(&mut self) -> Option { + #![expect(clippy::indexing_slicing)] if self.is_empty() { return None; } diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index f9ed6b60ad7e..8b1abeb51dc3 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -6,13 +6,13 @@ //! This module contains all node hashing functionality for the nodestore, including //! specialized support for Ethereum-compatible hash processing. -#[cfg(feature = "ethhash")] -use crate::Children; use crate::hashednode::hash_node; use crate::linear::FileIoError; use crate::logger::trace; use crate::node::Node; use crate::{Child, HashType, MaybePersistedNode, NodeStore, Path, ReadableStorage, SharedNode}; +#[cfg(feature = "ethhash")] +use crate::{Children, PathComponent}; use super::NodeReader; @@ -64,8 +64,8 @@ impl DerefMut for PathGuard<'_> { /// Classified children for ethereum hash processing #[cfg(feature = "ethhash")] pub(super) struct ClassifiedChildren<'a> { - pub(super) unhashed: Vec<(usize, Node)>, - pub(super) hashed: Vec<(usize, (MaybePersistedNode, &'a mut HashType))>, + pub(super) unhashed: Vec<(PathComponent, Node)>, + pub(super) hashed: Vec<(PathComponent, (MaybePersistedNode, &'a mut HashType))>, } impl NodeStore @@ -78,9 +78,9 @@ where #[cfg(feature = "ethhash")] pub(super) fn ethhash_classify_children<'a>( &self, - children: &'a mut Children, + children: &'a mut Children>, ) -> ClassifiedChildren<'a> { - children.iter_mut().enumerate().fold( + children.into_iter().fold( ClassifiedChildren { unhashed: Vec::new(), hashed: Vec::new(), @@ -163,11 +163,11 @@ where path_guard.0.extend(b.partial_path.0.iter().copied()); if unhashed.is_empty() { hashable_node.update_partial_path(Path::from_nibbles_iterator( - std::iter::once(*child_idx as u8) + std::iter::once(child_idx.as_u8()) .chain(hashable_node.partial_path().0.iter().copied()), )); } else { - path_guard.0.push(*child_idx as u8); + path_guard.0.push(child_idx.as_u8()); } hash_node(&hashable_node, &path_guard) }; @@ -175,7 +175,7 @@ where } // handle the single-child case for an account special below if hashed.is_empty() && unhashed.len() == 1 { - Some(unhashed.last().expect("only one").0 as u8) + Some(unhashed.last().expect("only one").0.as_u8()) } else { None } @@ -192,7 +192,7 @@ where // 5. 1 hashed, >0 unhashed <-- rehash case // 6. everything already hashed - for (nibble, child) in b.children.iter_mut().enumerate() { + for (nibble, child) in &mut b.children { // If this is empty or already hashed, we're done // Empty matches None, and non-Node types match Some(None) here, so we want // Some(Some(node)) @@ -212,10 +212,10 @@ where if make_fake_root.is_none() { // we don't push the nibble there is only one unhashed child and // we're on an account - child_path_prefix.0.push(nibble as u8); + child_path_prefix.0.push(nibble.as_u8()); } #[cfg(not(feature = "ethhash"))] - child_path_prefix.0.push(nibble as u8); + child_path_prefix.0.push(nibble.as_u8()); #[cfg(feature = "ethhash")] let (child_node, child_hash) = self.hash_helper_inner(child_node, child_path_prefix, make_fake_root)?; diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index e87845e74709..f16342ba58b4 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -125,12 +125,8 @@ impl<'a, N: NodeReader + RootReader> UnPersistedNodeIterator<'a, N> { // Create an iterator over unpersisted children let unpersisted_children: Vec = branch .children - .iter() - .filter_map(|child_opt| { - child_opt - .as_ref() - .and_then(|child| child.unpersisted().cloned()) - }) + .iter_present() + .filter_map(|(_, child)| child.unpersisted().cloned()) .collect(); ( @@ -170,12 +166,8 @@ impl Iterator for UnPersistedNodeIterator<'_, N> { // Create an iterator over unpersisted children let unpersisted_children: Vec = branch .children - .iter() - .filter_map(|child_opt| { - child_opt - .as_ref() - .and_then(|child| child.unpersisted().cloned()) - }) + .iter_present() + .filter_map(|(_, child)| child.unpersisted().cloned()) .collect(); // Push new child iterator to the stack @@ -494,7 +486,8 @@ impl NodeStore { mod tests { use super::*; use crate::{ - Child, HashType, ImmutableProposal, LinearAddress, NodeStore, Path, SharedNode, + Child, Children, HashType, ImmutableProposal, LinearAddress, NodeStore, Path, + PathComponent, SharedNode, linear::memory::MemStore, node::{BranchNode, LeafNode, Node}, nodestore::MutableProposal, @@ -529,18 +522,22 @@ mod tests { } /// Helper to create a branch node with children - fn create_branch(path: &[u8], value: Option<&[u8]>, children: Vec<(u8, Node)>) -> Node { + fn create_branch( + path: &[u8], + value: Option<&[u8]>, + children: Vec<(PathComponent, Node)>, + ) -> Node { let mut branch = BranchNode { partial_path: Path::from(path), value: value.map(|v| v.to_vec().into_boxed_slice()), - children: std::array::from_fn(|_| None), + children: Children::new(), }; for (index, child) in children { let shared_child = SharedNode::new(child); let maybe_persisted = MaybePersistedNode::from(shared_child); let hash = HashType::empty(); - branch.children[index as usize] = Some(Child::MaybePersisted(maybe_persisted, hash)); + branch.children[index] = Some(Child::MaybePersisted(maybe_persisted, hash)); } Node::Branch(Box::new(branch)) @@ -573,7 +570,11 @@ mod tests { #[test] fn test_branch_with_single_child() { let leaf = create_leaf(&[7, 8], &[9, 10]); - let branch = create_branch(&[1, 2], Some(&[3, 4]), vec![(5, leaf.clone())]); + let branch = create_branch( + &[1, 2], + Some(&[3, 4]), + vec![(PathComponent::ALL[5], leaf.clone())], + ); let store = create_test_store_with_root(branch.clone()); let mut iter = UnPersistedNodeIterator::new(&store).map(|node| node.as_shared_node(&store).unwrap()); @@ -603,9 +604,9 @@ mod tests { &[0], None, vec![ - (1, leaves[0].clone()), - (5, leaves[1].clone()), - (10, leaves[2].clone()), + (PathComponent::ALL[1], leaves[0].clone()), + (PathComponent::ALL[5], leaves[1].clone()), + (PathComponent::ALL[10], leaves[2].clone()), ], ); let store = create_test_store_with_root(branch.clone()); @@ -639,10 +640,14 @@ mod tests { // Create a nested structure: root -> branch1 -> leaf[0] // -> leaf[1] // -> branch2 -> leaf[2] - let inner_branch = create_branch(&[10], Some(&[50]), vec![(0, leaves[2].clone())]); + let inner_branch = create_branch( + &[10], + Some(&[50]), + vec![(PathComponent::ALL[0], leaves[2].clone())], + ); - let mut children = BranchNode::empty_children(); - for (value, slot) in [ + let mut children = Children::new(); + for (value, (_, slot)) in [ // unpersisted leaves Child::MaybePersisted( MaybePersistedNode::from(SharedNode::new(leaves[0].clone())), @@ -715,7 +720,10 @@ mod tests { let branch = create_branch( &[0], Some(b"branch_value"), - vec![(1, leaf1.clone()), (2, leaf2.clone())], + vec![ + (PathComponent::ALL[1], leaf1.clone()), + (PathComponent::ALL[2], leaf2.clone()), + ], ); mutable_store.root_mut().replace(branch.clone()); @@ -737,12 +745,11 @@ mod tests { assert_eq!(root_node.value(), Some(&b"branch_value"[..])); assert!(root_node.is_branch()); let root_branch = root_node.as_branch().unwrap(); - assert_eq!( - root_branch.children.iter().filter(|c| c.is_some()).count(), - 2 - ); + assert_eq!(root_branch.children.count(), 2); - let child1 = root_branch.children[1].as_ref().unwrap(); + let child1 = root_branch.children[PathComponent::ALL[1]] + .as_ref() + .unwrap(); let child1_maybe_persisted = child1.as_maybe_persisted_node(); let child1_node = child1_maybe_persisted .as_shared_node(&committed_store) @@ -750,7 +757,9 @@ mod tests { assert_eq!(*child1_node.partial_path(), Path::from(&[1, 2, 3])); assert_eq!(child1_node.value(), Some(&b"value1"[..])); - let child2 = root_branch.children[2].as_ref().unwrap(); + let child2 = root_branch.children[PathComponent::ALL[2]] + .as_ref() + .unwrap(); let child2_maybe_persisted = child2.as_maybe_persisted_node(); let child2_node = child2_maybe_persisted .as_shared_node(&committed_store) diff --git a/storage/src/path/component.rs b/storage/src/path/component.rs index f6196907753e..da308f4ca234 100644 --- a/storage/src/path/component.rs +++ b/storage/src/path/component.rs @@ -15,6 +15,9 @@ pub struct PathComponent(pub crate::u4::U4); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PathComponent(pub u8); +/// An iterator over path components. +pub type ComponentIter<'a> = std::iter::Copied>; + /// Extension methods for slices of path components. pub trait PathComponentSliceExt { /// Casts this slice of path components to a byte slice. @@ -36,7 +39,7 @@ impl PathComponent { /// instead of using a raw range like (`0..16`) or [`Iterator::enumerate`], /// which does not give a type-safe path component. #[cfg(not(feature = "branch_factor_256"))] - pub const ALL: [Self; 16] = [ + pub const ALL: [Self; Self::LEN] = [ Self(crate::u4::U4::new_masked(0x0)), Self(crate::u4::U4::new_masked(0x1)), Self(crate::u4::U4::new_masked(0x2)), @@ -69,7 +72,7 @@ impl PathComponent { /// instead of using a raw range like (`0..256`) or [`Iterator::enumerate`], /// which does not give a type-safe path component. #[cfg(feature = "branch_factor_256")] - pub const ALL: [Self; 256] = { + pub const ALL: [Self; Self::LEN] = { let mut all = [Self(0); 256]; let mut i = 0; #[expect(clippy::indexing_slicing)] @@ -79,6 +82,13 @@ impl PathComponent { } all }; + + /// The number of possible path components. + pub const LEN: usize = if cfg!(feature = "branch_factor_256") { + 256 + } else { + 16 + }; } impl PathComponent { @@ -140,6 +150,20 @@ impl PathComponent { pub const fn join(self, other: Self) -> u8 { self.0.join(other.0) } + + pub(crate) const fn wrapping_next(self) -> Self { + #[cfg(not(feature = "branch_factor_256"))] + { + match crate::u4::U4::try_new(self.0.as_u8().wrapping_add(1)) { + Some(next) => Self(next), + None => Self(crate::u4::U4::MIN), + } + } + #[cfg(feature = "branch_factor_256")] + { + Self(self.0.wrapping_add(1)) + } + } } impl std::fmt::Display for PathComponent { @@ -205,7 +229,7 @@ impl TriePath for Option { impl TriePath for [PathComponent] { type Components<'a> - = std::iter::Copied> + = ComponentIter<'a> where Self: 'a; @@ -220,7 +244,7 @@ impl TriePath for [PathComponent] { impl TriePath for [PathComponent; N] { type Components<'a> - = std::iter::Copied> + = ComponentIter<'a> where Self: 'a; @@ -235,7 +259,7 @@ impl TriePath for [PathComponent; N] { impl TriePath for Vec { type Components<'a> - = std::iter::Copied> + = ComponentIter<'a> where Self: 'a; @@ -250,7 +274,7 @@ impl TriePath for Vec { impl> TriePath for SmallVec { type Components<'a> - = std::iter::Copied> + = ComponentIter<'a> where Self: 'a; diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 7d0844bb4386..2bf7e5ef69fb 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -7,7 +7,7 @@ mod joined; mod packed; mod split; -pub use self::component::{PathComponent, PathComponentSliceExt}; +pub use self::component::{ComponentIter, PathComponent, PathComponentSliceExt}; pub use self::joined::JoinedPath; #[cfg(not(feature = "branch_factor_256"))] pub use self::packed::{PackedBytes, PackedPathComponents, PackedPathRef}; From a9ba1e1163783c037b860fdd761de3de3ad6abf4 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 15 Oct 2025 08:07:45 -0700 Subject: [PATCH 0990/1053] feat: Use PathComponent in proofs (#1359) This change primarily replaces the soon to be removed `Path` struct with `PathBuf` (which is a type alias for the smallvec of `PathComponent`) within the proof structure and related hashing traits. Changing only the proof and related components was less invasive changing `LeafNode`, `BranchNode`, or the iterator states. Those changes will come in future updates. --- firewood/src/iter.rs | 55 +++++++------- firewood/src/merkle.rs | 2 +- firewood/src/proof.rs | 56 ++++++-------- firewood/src/proofs/de.rs | 17 ++++- firewood/src/proofs/ser.rs | 9 ++- storage/src/hashednode.rs | 28 +++---- storage/src/hashers/ethhash.rs | 48 +++++------- storage/src/hashers/merkledb.rs | 13 +--- storage/src/lib.rs | 6 +- storage/src/node/mod.rs | 4 +- storage/src/node/path.rs | 9 +++ storage/src/path/buf.rs | 130 ++++++++++++++++++++++++++++++++ storage/src/path/component.rs | 26 ++++++- storage/src/path/joined.rs | 13 ++++ storage/src/path/mod.rs | 29 +++++++ storage/src/path/packed.rs | 20 +++++ storage/src/path/split.rs | 15 +++- 17 files changed, 353 insertions(+), 127 deletions(-) create mode 100644 storage/src/path/buf.rs diff --git a/firewood/src/iter.rs b/firewood/src/iter.rs index 657e95f457d9..72e9d61fdd7c 100644 --- a/firewood/src/iter.rs +++ b/firewood/src/iter.rs @@ -8,8 +8,8 @@ use crate::merkle::{Key, Value}; use crate::v2::api; use firewood_storage::{ - BranchNode, Child, FileIoError, NibblesIterator, Node, PathComponent, PathIterItem, SharedNode, - TrieReader, + BranchNode, Child, FileIoError, NibblesIterator, Node, PathBuf, PathComponent, PathIterItem, + SharedNode, TriePathFromUnpackedBytes, TrieReader, }; use std::cmp::Ordering; use std::iter::FusedIterator; @@ -401,7 +401,8 @@ impl Iterator for PathIterator<'_, '_, T> { } Ordering::Equal => { matched_key.extend(partial_path.iter()); - let node_key = matched_key.clone().into_boxed_slice(); + let node_key = PathBuf::path_from_unpacked_bytes(matched_key) + .expect("valid components"); match &**node { Node::Leaf(_) => { @@ -409,7 +410,7 @@ impl Iterator for PathIterator<'_, '_, T> { let node = node.clone(); self.state = PathIteratorState::Exhausted; Some(Ok(PathIterItem { - key_nibbles: node_key.clone(), + key_nibbles: node_key, node, next_nibble: None, })) @@ -422,7 +423,7 @@ impl Iterator for PathIterator<'_, '_, T> { // We're at the node at `key` so we're done. self.state = PathIteratorState::Exhausted; return Some(Ok(PathIterItem { - key_nibbles: node_key.clone(), + key_nibbles: node_key, node: saved_node, next_nibble: None, })); @@ -438,7 +439,7 @@ impl Iterator for PathIterator<'_, '_, T> { // There's no node at `key` in this trie so we're done. self.state = PathIteratorState::Exhausted; Some(Ok(PathIterItem { - key_nibbles: node_key.clone(), + key_nibbles: node_key, node: saved_node, next_nibble: None, })) @@ -449,7 +450,6 @@ impl Iterator for PathIterator<'_, '_, T> { Err(e) => return Some(Err(e)), }; - let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child; @@ -461,7 +461,6 @@ impl Iterator for PathIterator<'_, '_, T> { })) } Some(Child::Node(child)) => { - let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child.clone().into(); @@ -478,7 +477,6 @@ impl Iterator for PathIterator<'_, '_, T> { Err(e) => return Some(Err(e)), }; - let node_key = matched_key.clone().into_boxed_slice(); matched_key.push(next_unmatched_key_nibble.as_u8()); *node = child; @@ -571,6 +569,16 @@ mod tests { use std::sync::Arc; use test_case::test_case; + macro_rules! path { + ($($elem:expr),* $(,)?)=>{ + [ + $( + PathComponent::ALL[$elem], + )* + ] + }; + } + pub(super) fn create_test_merkle() -> Merkle> { let memstore = MemStore::new(vec![]); let memstore = Arc::new(memstore); @@ -608,12 +616,9 @@ mod tests { assert!(should_yield_elt); #[cfg(not(feature = "branch_factor_256"))] - assert_eq!( - node.key_nibbles, - vec![0x0B, 0x0E, 0x0E, 0x0F].into_boxed_slice() - ); + assert_eq!(*node.key_nibbles, path![0x0B, 0x0E, 0x0E, 0x0F]); #[cfg(feature = "branch_factor_256")] - assert_eq!(node.key_nibbles, vec![0xBE, 0xEF].into_boxed_slice()); + assert_eq!(*node.key_nibbles, path![0xBE, 0xEF]); assert_eq!(node.node.as_leaf().unwrap().value, Box::from([0x42])); assert_eq!(node.next_nibble, None); @@ -633,9 +638,9 @@ mod tests { None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] - assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + assert_eq!(*node.key_nibbles, path![0x00, 0x00]); #[cfg(feature = "branch_factor_256")] - assert_eq!(node.key_nibbles, vec![0].into_boxed_slice()); + assert_eq!(*node.key_nibbles, path![0]); assert_eq!(node.next_nibble, Some(PathComponent::ALL[0])); assert!(node.node.as_branch().unwrap().value.is_none()); @@ -645,12 +650,9 @@ mod tests { None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] - assert_eq!( - node.key_nibbles, - vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() - ); + assert_eq!(*node.key_nibbles, path![0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); #[cfg(feature = "branch_factor_256")] - assert_eq!(node.key_nibbles, vec![0, 0, 0].into_boxed_slice()); + assert_eq!(*node.key_nibbles, path![0, 0, 0]); assert_eq!(node.next_nibble, PathComponent::ALL.last().copied()); @@ -666,8 +668,8 @@ mod tests { }; #[cfg(not(feature = "branch_factor_256"))] assert_eq!( - node.key_nibbles, - vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice() + *node.key_nibbles, + path![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F] ); assert_eq!(node.next_nibble, None); assert_eq!( @@ -692,7 +694,7 @@ mod tests { }; // TODO: make this branch factor 16 compatible #[cfg(not(feature = "branch_factor_256"))] - assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice()); + assert_eq!(*node.key_nibbles, path![0x00, 0x00]); assert!(node.node.as_branch().unwrap().value.is_none()); assert_eq!(node.next_nibble, Some(PathComponent::ALL[0])); @@ -703,10 +705,7 @@ mod tests { None => panic!("unexpected end of iterator"), }; #[cfg(not(feature = "branch_factor_256"))] - assert_eq!( - node.key_nibbles, - vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice() - ); + assert_eq!(*node.key_nibbles, path![0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); assert_eq!( node.node.as_branch().unwrap().value, Some(vec![0x00, 0x00, 0x00].into_boxed_slice()), diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index fe55cc9ffb41..a27d298c389c 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -165,7 +165,7 @@ impl Merkle { proof.push(ProofNode { // key is expected to be in nibbles - key: root.partial_path().iter().copied().collect(), + key: root.partial_path().as_components().into(), // partial len is the number of nibbles in the path leading to this node, // which is always zero for the root node. partial_len: 0, diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 8d27f19549a8..4340e33f1efa 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -5,18 +5,14 @@ clippy::missing_errors_doc, reason = "Found 1 occurrences after enabling the lint." )] -#![expect( - clippy::needless_continue, - reason = "Found 1 occurrences after enabling the lint." -)] use firewood_storage::{ - Children, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, PathComponent, - PathIterItem, Preimage, TrieHash, ValueDigest, + Children, FileIoError, HashType, Hashable, IntoHashType, IntoSplitPath, NibblesIterator, Path, + PathBuf, PathComponent, PathIterItem, Preimage, SplitPath, TrieHash, TriePath, ValueDigest, }; use thiserror::Error; -use crate::merkle::{Key, Value}; +use crate::merkle::Value; #[derive(Debug, Error)] #[non_exhaustive] @@ -84,7 +80,7 @@ pub enum ProofError { /// A node in a proof. pub struct ProofNode { /// The key this node is at. Each byte is a nibble. - pub key: Key, + pub key: PathBuf, /// The length of the key prefix that is shared with the previous node. pub partial_len: usize, /// None if the node does not have a value. @@ -112,16 +108,18 @@ impl std::fmt::Debug for ProofNode { } impl Hashable for ProofNode { - fn parent_prefix_path(&self) -> impl Iterator + Clone { - self.full_path().take(self.partial_len) + fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + let (prefix, _) = self.key.split_at(self.partial_len); + prefix } - fn partial_path(&self) -> impl Iterator + Clone { - self.full_path().skip(self.partial_len) + fn partial_path(&self) -> impl IntoSplitPath + '_ { + let (_, suffix) = self.key.split_at(self.partial_len); + suffix } - fn full_path(&self) -> impl Iterator + Clone { - self.key.as_ref().iter().copied() + fn full_path(&self) -> impl IntoSplitPath + '_ { + &self.key } fn value_digest(&self) -> Option> { @@ -199,14 +197,14 @@ impl Proof { // Assert that only nodes whose keys are an even number of nibbles // have a `value_digest`. #[cfg(not(feature = "branch_factor_256"))] - if node.full_path().count() % 2 != 0 && node.value_digest().is_some() { + if !node.full_path().len().is_multiple_of(2) && node.value_digest().is_some() { return Err(ProofError::ValueAtOddNibbleLength); } if let Some(next_node) = iter.peek() { // Assert that every node's key is a prefix of `key`, except for the last node, // whose key can be equal to or a suffix of `key` in an exclusion proof. - if next_nibble(node.full_path(), key.iter().copied()).is_none() { + if next_nibble(node.full_path(), key.as_components()).is_none() { return Err(ProofError::ShouldBePrefixOfProvenKey); } @@ -217,8 +215,6 @@ impl Proof { return Err(ProofError::ShouldBePrefixOfNextKey); }; - let next_nibble = - PathComponent::try_new(next_nibble).ok_or(ProofError::ChildIndexOutOfBounds)?; expected_hash = node.children()[next_nibble] .as_ref() .ok_or(ProofError::NodeNotInTrie)? @@ -226,7 +222,7 @@ impl Proof { } } - if last_node.full_path().eq(key.iter().copied()) { + if last_node.full_path().path_eq(key.as_components()) { return Ok(last_node.value_digest()); } @@ -384,23 +380,13 @@ impl ProofCollection for EmptyProofCollection { /// Returns the next nibble in `c` after `b`. /// Returns None if `b` is not a strict prefix of `c`. -fn next_nibble(b: B, c: C) -> Option -where - B: IntoIterator, - C: IntoIterator, -{ - let b = b.into_iter(); - let mut c = c.into_iter(); - - // Check if b is a prefix of c - for b_item in b { - match c.next() { - Some(c_item) if b_item == c_item => continue, - _ => return None, - } +fn next_nibble(b: impl IntoSplitPath, c: impl IntoSplitPath) -> Option { + let b = b.into_split_path(); + let c = c.into_split_path(); + match b.longest_common_prefix(c).split_first_parts() { + (None, Some((c, _)), _) => Some(c), + _ => None, } - - c.next() } fn verify_opt_value_digest( diff --git a/firewood/src/proofs/de.rs b/firewood/src/proofs/de.rs index b00c84de19c7..95e9e0fdcbc8 100644 --- a/firewood/src/proofs/de.rs +++ b/firewood/src/proofs/de.rs @@ -3,7 +3,7 @@ #[cfg(feature = "ethhash")] use firewood_storage::HashType; -use firewood_storage::{Children, TrieHash, ValueDigest}; +use firewood_storage::{Children, PathBuf, TrieHash, TriePathFromUnpackedBytes, ValueDigest}; use integer_encoding::VarInt; use crate::{ @@ -85,7 +85,7 @@ impl Version0 for FrozenRangeProof { impl Version0 for ProofNode { fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { - let key = reader.read_item()?; + let key = reader.read_v0_item()?; let partial_len = reader.read_item()?; let value_digest = reader.read_item()?; @@ -105,6 +105,19 @@ impl Version0 for ProofNode { } } +impl Version0 for PathBuf { + fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { + let bytes = reader.read_item::<&[u8]>()?; + TriePathFromUnpackedBytes::path_from_unpacked_bytes(bytes).map_err(|_| { + reader.invalid_item( + "path", + "valid nibbles", + format!("invalid nibbles: {}", hex::encode(bytes)), + ) + }) + } +} + impl Version0 for (Box<[u8]>, Box<[u8]>) { fn read_v0_item(reader: &mut V0Reader<'_>) -> Result { Ok((reader.read_item()?, reader.read_item()?)) diff --git a/firewood/src/proofs/ser.rs b/firewood/src/proofs/ser.rs index d6e9dc1ad7d7..18d6a7cbb2b6 100644 --- a/firewood/src/proofs/ser.rs +++ b/firewood/src/proofs/ser.rs @@ -1,7 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood_storage::ValueDigest; +use firewood_storage::{PathBuf, PathComponentSliceExt, ValueDigest}; use integer_encoding::VarInt; use crate::{ @@ -108,6 +108,13 @@ impl WriteItem for ProofNode { } } +impl WriteItem for PathBuf { + fn write_item(&self, out: &mut Vec) { + out.push_var_int(self.len()); + out.extend_from_slice(self.as_byte_slice()); + } +} + impl WriteItem for Option { fn write_item(&self, out: &mut Vec) { if let Some(v) = self { diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 43c714936e10..02055d658bf2 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{BranchNode, Children, HashType, LeafNode, Node, Path}; +use crate::{BranchNode, Children, HashType, IntoSplitPath, LeafNode, Node, Path, TriePath}; use smallvec::SmallVec; /// Returns the hash of `node`, which is at the given `path_prefix`. @@ -153,9 +153,9 @@ impl> AsRef<[u8]> for ValueDigest { /// A node in the trie that can be hashed. pub trait Hashable: std::fmt::Debug { /// The full path of this node's parent where each byte is a nibble. - fn parent_prefix_path(&self) -> impl Iterator + Clone; + fn parent_prefix_path(&self) -> impl IntoSplitPath + '_; /// The partial path of this node where each byte is a nibble. - fn partial_path(&self) -> impl Iterator + Clone; + fn partial_path(&self) -> impl IntoSplitPath + '_; /// The node's value or hash. fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. @@ -163,8 +163,10 @@ pub trait Hashable: std::fmt::Debug { fn children(&self) -> Children>; /// The full path of this node including the parent's prefix where each byte is a nibble. - fn full_path(&self) -> impl Iterator + Clone { - self.parent_prefix_path().chain(self.partial_path()) + fn full_path(&self) -> impl IntoSplitPath + '_ { + self.parent_prefix_path() + .into_split_path() + .append(self.partial_path().into_split_path()) } } @@ -177,14 +179,14 @@ pub trait Preimage: std::fmt::Debug { } trait HashableNode: std::fmt::Debug { - fn partial_path(&self) -> impl Iterator + Clone; + fn partial_path(&self) -> impl IntoSplitPath + '_; fn value(&self) -> Option<&[u8]>; fn child_hashes(&self) -> Children>; } impl HashableNode for BranchNode { - fn partial_path(&self) -> impl Iterator + Clone { - self.partial_path.0.iter().copied() + fn partial_path(&self) -> impl IntoSplitPath + '_ { + self.partial_path.as_components() } fn value(&self) -> Option<&[u8]> { @@ -197,8 +199,8 @@ impl HashableNode for BranchNode { } impl HashableNode for LeafNode { - fn partial_path(&self) -> impl Iterator + Clone { - self.partial_path.0.iter().copied() + fn partial_path(&self) -> impl IntoSplitPath + '_ { + self.partial_path.as_components() } fn value(&self) -> Option<&[u8]> { @@ -223,11 +225,11 @@ impl<'a, N: HashableNode> From> for HashType { } impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { - fn parent_prefix_path(&self) -> impl Iterator + Clone { - self.prefix.0.iter().copied() + fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + self.prefix.as_components() } - fn partial_path(&self) -> impl Iterator + Clone { + fn partial_path(&self) -> impl IntoSplitPath + '_ { self.node.partial_path() } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index 06d32d7a4954..a7cbd0eb6e93 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -13,8 +13,8 @@ use crate::logger::warn; use crate::{ - BranchNode, HashType, Hashable, Preimage, TrieHash, ValueDigest, hashednode::HasUpdate, - logger::trace, + BranchNode, HashType, Hashable, Preimage, TrieHash, TriePath, ValueDigest, + hashednode::HasUpdate, logger::trace, }; use bitfield::bitfield; use bytes::BytesMut; @@ -40,7 +40,7 @@ impl HasUpdate for Keccak256 { // B is 1 if the input had an odd number of nibbles // CCCC is the first nibble if B is 1, otherwise it is all 0s -fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec<[u8; 32]> { +fn nibbles_to_eth_compact(nibbles: T, is_leaf: bool) -> SmallVec<[u8; 32]> { // This is a bitfield that represents the first byte of the output, documented above bitfield! { struct CompactFirstByte(u8); @@ -52,15 +52,7 @@ fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec low_nibble, set_low_nibble: 3, 0; } - let nibbles = nibbles.as_ref(); - if cfg!(debug_assertions) { - for &nibble in nibbles { - assert!( - nibble < 16, - "nibbles contains byte out of range: {nibbles:?}" - ); - } - } + let nibbles = nibbles.as_component_slice(); let mut first_byte = CompactFirstByte(0); first_byte.set_is_leaf(is_leaf); @@ -69,7 +61,7 @@ fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec if let &[low_nibble] = maybe_low_nibble { // we have an odd number of nibbles first_byte.set_odd_nibbles(true); - first_byte.set_low_nibble(low_nibble); + first_byte.set_low_nibble(low_nibble.as_u8()); } else { // as_rchunks can only return 0 or 1 element in the first slice if N is 2 debug_assert!(maybe_low_nibble.is_empty()); @@ -77,7 +69,7 @@ fn nibbles_to_eth_compact>(nibbles: T, is_leaf: bool) -> SmallVec // now assemble everything: the first byte, and the nibble pairs compacted back together once(first_byte.0) - .chain(nibble_pairs.iter().map(|&[hi, lo]| (hi << 4) | lo)) + .chain(nibble_pairs.iter().map(|&[hi, lo]| hi.join(lo))) .collect() } @@ -90,7 +82,7 @@ impl Preimage for T { trace!( "SIZE WAS {} {}", - self.full_path().count(), + self.full_path().len(), hex::encode(&collector), ); @@ -102,7 +94,7 @@ impl Preimage for T { } fn write(&self, buf: &mut impl HasUpdate) { - let is_account = self.full_path().count() == 64; + let is_account = self.full_path().len() == 64; trace!("is_account: {is_account}"); let child_hashes = self.children(); @@ -117,10 +109,7 @@ impl Preimage for T { let mut rlp = RlpStream::new_list(2); - rlp.append(&&*nibbles_to_eth_compact( - self.partial_path().collect::>(), - true, - )); + rlp.append(&&*nibbles_to_eth_compact(self.partial_path(), true)); if is_account { // we are a leaf that is at depth 32 @@ -147,10 +136,7 @@ impl Preimage for T { } let bytes = rlp.out(); - trace!( - "partial path {:?}", - hex::encode(self.partial_path().collect::>()) - ); + trace!("partial path {:?}", self.partial_path().display()); trace!("serialized leaf-rlp: {:?}", hex::encode(&bytes)); buf.update(&bytes); } else { @@ -203,10 +189,7 @@ impl Preimage for T { HashType::Hash(hash) => hash.clone(), HashType::Rlp(rlp_bytes) => { let mut rlp = RlpStream::new_list(2); - rlp.append(&&*nibbles_to_eth_compact( - self.partial_path().collect::>(), - true, - )); + rlp.append(&&*nibbles_to_eth_compact(self.partial_path(), true)); rlp.append_raw(rlp_bytes, 1); let bytes = rlp.out(); TrieHash::from(Keccak256::digest(bytes)) @@ -225,7 +208,7 @@ impl Preimage for T { // treat like non-account since it didn't have a value warn!( "Account node {:x?} without value", - self.full_path().collect::>() + self.full_path().display(), ); bytes.as_ref().into() } @@ -233,7 +216,7 @@ impl Preimage for T { bytes.as_ref().into() }; - let partial_path = self.partial_path().collect::>(); + let partial_path = self.partial_path(); if partial_path.is_empty() { trace!("pass 2=bytes {:02X?}", hex::encode(&updated_bytes)); buf.update(updated_bytes); @@ -282,6 +265,8 @@ fn replace_hash, U: AsRef<[u8]>>(bytes: T, new_hash: U) -> Option mod test { use test_case::test_case; + use crate::{PathComponent, TriePathFromUnpackedBytes}; + #[test_case(&[], false, &[0x00])] #[test_case(&[], true, &[0x20])] #[test_case(&[1, 2, 3, 4, 5], false, &[0x11, 0x23, 0x45])] @@ -289,8 +274,9 @@ mod test { #[test_case(&[15, 1, 12, 11, 8], true, &[0x3f, 0x1c, 0xb8])] #[test_case(&[0, 15, 1, 12, 11, 8], true, &[0x20, 0x0f, 0x1c, 0xb8])] fn test_hex_to_compact(hex: &[u8], has_value: bool, expected_compact: &[u8]) { + let path = <&[PathComponent]>::path_from_unpacked_bytes(hex).expect("valid path"); assert_eq!( - &*super::nibbles_to_eth_compact(hex, has_value), + &*super::nibbles_to_eth_compact(path, has_value), expected_compact ); } diff --git a/storage/src/hashers/merkledb.rs b/storage/src/hashers/merkledb.rs index d13da0a9496f..c82472da0ecf 100644 --- a/storage/src/hashers/merkledb.rs +++ b/storage/src/hashers/merkledb.rs @@ -10,7 +10,7 @@ )] use crate::hashednode::{HasUpdate, Hashable, Preimage}; -use crate::{TrieHash, ValueDigest}; +use crate::{TrieHash, TriePath, TriePathAsPackedBytes, ValueDigest}; /// Merkledb compatible hashing algorithm. use integer_encoding::VarInt; use sha2::{Digest, Sha256}; @@ -50,16 +50,11 @@ impl Preimage for T { add_value_digest_to_buf(buf, self.value_digest()); // Add key length (in bits) to hash pre-image - let mut key = self.full_path(); - let key_bit_len = BITS_PER_NIBBLE * key.clone().count() as u64; + let key = self.full_path(); + let key_bit_len = BITS_PER_NIBBLE * key.len() as u64; add_varint_to_buf(buf, key_bit_len); - // Add key to hash pre-image - while let Some(high_nibble) = key.next() { - let low_nibble = key.next().unwrap_or(0); - let byte = (high_nibble << 4) | low_nibble; - buf.update([byte]); - } + key.as_packed_bytes().for_each(|byte| buf.update([byte])); } } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index de8443c950ed..2fc09e43ae40 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -55,9 +55,9 @@ pub use nodestore::{ NodeReader, NodeStore, Parentable, RootReader, TrieReader, }; pub use path::{ - ComponentIter, IntoSplitPath, JoinedPath, PathCommonPrefix, PathComponent, - PathComponentSliceExt, SplitPath, TriePath, TriePathAsPackedBytes, TriePathFromPackedBytes, - TriePathFromUnpackedBytes, + ComponentIter, IntoSplitPath, JoinedPath, PartialPath, PathBuf, PathCommonPrefix, + PathComponent, PathComponentSliceExt, SplitPath, TriePath, TriePathAsPackedBytes, + TriePathFromPackedBytes, TriePathFromUnpackedBytes, }; #[cfg(not(feature = "branch_factor_256"))] pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 3c7d36cbddaf..29c6df2cb804 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -16,7 +16,7 @@ use crate::node::branch::ReadSerializable; use crate::nodestore::AreaIndex; -use crate::{HashType, LinearAddress, Path, PathComponent, SharedNode}; +use crate::{HashType, LinearAddress, Path, PathBuf, PathComponent, SharedNode}; use bitfield::bitfield; use branch::Serializable as _; pub use branch::{BranchNode, Child}; @@ -410,7 +410,7 @@ impl Node { #[derive(Debug)] pub struct PathIterItem { /// The key of the node at `address` as nibbles. - pub key_nibbles: Box<[u8]>, + pub key_nibbles: PathBuf, /// A reference to the node pub node: SharedNode, /// The next item returned by the iterator is a child of `node`. diff --git a/storage/src/node/path.rs b/storage/src/node/path.rs index 61bd31596ceb..7fcfc44ef141 100644 --- a/storage/src/node/path.rs +++ b/storage/src/node/path.rs @@ -21,6 +21,8 @@ use std::fmt::{self, Debug, LowerHex}; use std::iter::{FusedIterator, once}; use std::ops::Add; +use crate::{PathComponent, TriePathFromUnpackedBytes}; + static NIBBLES: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; /// Path is part or all of a node's path in the trie. @@ -138,6 +140,13 @@ impl Path { nibbles_iter: self.iter(), } } + + /// Casts the path to a slice of its components. + #[must_use] + pub fn as_components(&self) -> &[PathComponent] { + TriePathFromUnpackedBytes::path_from_unpacked_bytes(&self.0) + .expect("path should contain only nibbles") + } } /// Returns the nibbles in `nibbles_iter` as compressed bytes. diff --git a/storage/src/path/buf.rs b/storage/src/path/buf.rs new file mode 100644 index 000000000000..9af84cbb962c --- /dev/null +++ b/storage/src/path/buf.rs @@ -0,0 +1,130 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::{ComponentIter, IntoSplitPath, PathComponent, TriePath}; + +/// An owned buffer of path components. +pub type PathBuf = smallvec::SmallVec<[PathComponent; 32]>; + +/// A trie path represented as a slice of path components, either borrowed or owned. +pub enum PartialPath<'a> { + /// A borrowed slice of path components. + Borrowed(&'a [PathComponent]), + + /// An owned buffer of path components. + Owned(PathBuf), +} + +impl std::fmt::Debug for PartialPath<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.display().fmt(f) + } +} + +impl std::fmt::Display for PartialPath<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.display().fmt(f) + } +} + +impl PartialPath<'_> { + /// Returns the path as a slice of path components. + #[inline] + #[must_use] + pub fn as_slice(&self) -> &[PathComponent] { + match self { + PartialPath::Borrowed(slice) => slice, + PartialPath::Owned(buf) => buf.as_slice(), + } + } + + /// Converts this partial path into an owned path buffer. + /// + /// If the path is already owned, this is a no-op. + /// If the path is borrowed, this allocates a new buffer and copies the + /// components into it. + #[inline] + #[must_use] + pub fn into_owned(self) -> PathBuf { + match self { + PartialPath::Borrowed(slice) => slice.into(), + PartialPath::Owned(buf) => buf, + } + } + + /// Returns true if this partial path is a borrowed slice. + #[inline] + #[must_use] + pub const fn is_borrowed(&self) -> bool { + matches!(self, PartialPath::Borrowed(_)) + } + + /// Returns true if this partial path is an owned buffer. + #[inline] + #[must_use] + pub const fn is_owned(&self) -> bool { + matches!(self, PartialPath::Owned(_)) + } + + /// Acquires a mutable reference to the owned path buffer, converting + /// the path to an owned buffer if it is currently a borrowed slice. + pub fn to_mut(&mut self) -> &mut PathBuf { + if let Self::Borrowed(buf) = self { + *self = Self::Owned((*buf).into()); + } + + if let Self::Owned(buf) = self { + buf + } else { + unreachable!() + } + } +} + +impl std::ops::Deref for PartialPath<'_> { + type Target = [PathComponent]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl std::borrow::Borrow<[PathComponent]> for PartialPath<'_> { + fn borrow(&self) -> &[PathComponent] { + self.as_slice() + } +} + +impl AsRef<[PathComponent]> for PartialPath<'_> { + fn as_ref(&self) -> &[PathComponent] { + self.as_slice() + } +} + +impl TriePath for PartialPath<'_> { + type Components<'a> + = ComponentIter<'a> + where + Self: 'a; + + fn len(&self) -> usize { + self.as_slice().len() + } + + fn components(&self) -> Self::Components<'_> { + self.as_slice().iter().copied() + } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self.as_slice()) + } +} + +impl<'a> IntoSplitPath for &'a PartialPath<'_> { + type Path = &'a [PathComponent]; + + #[inline] + fn into_split_path(self) -> Self::Path { + self + } +} diff --git a/storage/src/path/component.rs b/storage/src/path/component.rs index da308f4ca234..13e7e17a24d7 100644 --- a/storage/src/path/component.rs +++ b/storage/src/path/component.rs @@ -3,7 +3,7 @@ use smallvec::SmallVec; -use super::{TriePath, TriePathFromUnpackedBytes}; +use super::{PartialPath, TriePath, TriePathFromUnpackedBytes}; #[cfg(not(feature = "branch_factor_256"))] /// A path component in a hexary trie; which is only 4 bits (aka a nibble). @@ -210,6 +210,10 @@ impl TriePath for PathComponent { fn components(&self) -> Self::Components<'_> { Some(*self).into_iter() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(std::slice::from_ref(self)) + } } impl TriePath for Option { @@ -225,6 +229,10 @@ impl TriePath for Option { fn components(&self) -> Self::Components<'_> { (*self).into_iter() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self.as_slice()) + } } impl TriePath for [PathComponent] { @@ -240,6 +248,10 @@ impl TriePath for [PathComponent] { fn components(&self) -> Self::Components<'_> { self.iter().copied() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self) + } } impl TriePath for [PathComponent; N] { @@ -255,6 +267,10 @@ impl TriePath for [PathComponent; N] { fn components(&self) -> Self::Components<'_> { self.iter().copied() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self) + } } impl TriePath for Vec { @@ -270,6 +286,10 @@ impl TriePath for Vec { fn components(&self) -> Self::Components<'_> { self.iter().copied() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self.as_slice()) + } } impl> TriePath for SmallVec { @@ -285,6 +305,10 @@ impl> TriePath for SmallVec { fn components(&self) -> Self::Components<'_> { self.iter().copied() } + + fn as_component_slice(&self) -> PartialPath<'_> { + PartialPath::Borrowed(self.as_slice()) + } } #[cfg(not(feature = "branch_factor_256"))] diff --git a/storage/src/path/joined.rs b/storage/src/path/joined.rs index 2e4b9afd7496..13369454dece 100644 --- a/storage/src/path/joined.rs +++ b/storage/src/path/joined.rs @@ -45,6 +45,19 @@ impl TriePath for JoinedPath { fn components(&self) -> Self::Components<'_> { self.prefix.components().chain(self.suffix.components()) } + + fn as_component_slice(&self) -> super::PartialPath<'_> { + if self.prefix.is_empty() { + self.suffix.as_component_slice() + } else if self.suffix.is_empty() { + self.prefix.as_component_slice() + } else { + let mut buf = super::PathBuf::with_capacity(self.len()); + buf.extend(self.prefix.components()); + buf.extend(self.suffix.components()); + super::PartialPath::Owned(buf) + } + } } impl SplitPath for JoinedPath { diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 2bf7e5ef69fb..145756c9f318 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -1,12 +1,14 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +mod buf; mod component; mod joined; #[cfg(not(feature = "branch_factor_256"))] mod packed; mod split; +pub use self::buf::{PartialPath, PathBuf}; pub use self::component::{ComponentIter, PathComponent, PathComponentSliceExt}; pub use self::joined::JoinedPath; #[cfg(not(feature = "branch_factor_256"))] @@ -35,6 +37,13 @@ pub trait TriePath { /// Returns an iterator over the components of this path. fn components(&self) -> Self::Components<'_>; + /// Returns a contiguous view of this path's components. + /// + /// If the underlying representation is already contiguous, this should be a + /// cheap operation (i.e. no allocations or copies). If not, this may allocate + /// and copy the components into a contiguous buffer. + fn as_component_slice(&self) -> PartialPath<'_>; + /// Appends the provided path segment to this path, returning a new joined /// path that represents the concatenation of the two paths. /// @@ -210,6 +219,10 @@ impl TriePath for &T { fn components(&self) -> Self::Components<'_> { (**self).components() } + + fn as_component_slice(&self) -> PartialPath<'_> { + (**self).as_component_slice() + } } impl TriePath for &mut T { @@ -225,6 +238,10 @@ impl TriePath for &mut T { fn components(&self) -> Self::Components<'_> { (**self).components() } + + fn as_component_slice(&self) -> PartialPath<'_> { + (**self).as_component_slice() + } } impl TriePath for Box { @@ -240,6 +257,10 @@ impl TriePath for Box { fn components(&self) -> Self::Components<'_> { (**self).components() } + + fn as_component_slice(&self) -> PartialPath<'_> { + (**self).as_component_slice() + } } impl TriePath for std::rc::Rc { @@ -255,6 +276,10 @@ impl TriePath for std::rc::Rc { fn components(&self) -> Self::Components<'_> { (**self).components() } + + fn as_component_slice(&self) -> PartialPath<'_> { + (**self).as_component_slice() + } } impl TriePath for std::sync::Arc { @@ -270,4 +295,8 @@ impl TriePath for std::sync::Arc { fn components(&self) -> Self::Components<'_> { (**self).components() } + + fn as_component_slice(&self) -> PartialPath<'_> { + (**self).as_component_slice() + } } diff --git a/storage/src/path/packed.rs b/storage/src/path/packed.rs index 5cb47ee59b8a..d53e7f5d3eb2 100644 --- a/storage/src/path/packed.rs +++ b/storage/src/path/packed.rs @@ -55,6 +55,26 @@ impl TriePath for PackedPathRef<'_> { fn components(&self) -> Self::Components<'_> { PackedPathComponents { path: *self } } + + fn as_component_slice(&self) -> super::PartialPath<'_> { + if self.is_empty() { + super::PartialPath::Borrowed(&[]) + } else { + let mut buf = super::PathBuf::with_capacity(self.len()); + if let Some(prefix) = self.prefix { + buf.push(prefix); + } + for &byte in self.middle { + let (upper, lower) = PathComponent::new_pair(byte); + buf.push(upper); + buf.push(lower); + } + if let Some(suffix) = self.suffix { + buf.push(suffix); + } + super::PartialPath::Owned(buf) + } + } } impl SplitPath for PackedPathRef<'_> { diff --git a/storage/src/path/split.rs b/storage/src/path/split.rs index dc6d3eb5856a..b37590f93fec 100644 --- a/storage/src/path/split.rs +++ b/storage/src/path/split.rs @@ -38,7 +38,7 @@ pub trait SplitPath: TriePath + Default + Copy { /// /// Like `IntoIterator`, a blanket implementation is provided for all types that /// already implement [`SplitPath`]. -pub trait IntoSplitPath { +pub trait IntoSplitPath: TriePath { /// The splittable path type derived from this type. type Path: SplitPath; @@ -85,6 +85,19 @@ impl PathCommonPrefix { } } +impl PathCommonPrefix { + /// Converts this into its constituent parts, where the suffixes are + /// optionally empty. + #[expect(clippy::type_complexity)] + pub fn split_first_parts(self) -> (Option<(PathComponent, A)>, Option<(PathComponent, B)>, C) { + ( + self.a_suffix.split_first(), + self.b_suffix.split_first(), + self.common, + ) + } +} + impl SplitPath for &[PathComponent] { fn split_at(self, mid: usize) -> (Self, Self) { self.split_at(mid) From aa34da3eec358b09125d9eecc3c1797f37e3252c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 15 Oct 2025 08:14:57 -0700 Subject: [PATCH 0991/1053] feat: replace NodeAndPrefix with HashableShunt (#1362) The `NodeAndPrefix` struct was too limiting in that it required a full node from the storage layer. In order to verify proofs, we only need the individual components of the hash preimage and not the full node. This change introduces `HashableShunt` which is a more flexible abstraction that can be constructed from either a full node or the individual components. This allows us to create shunts for hashing directly from proof data without needing to reconstruct full nodes. --- storage/src/hashednode.rs | 162 ++++++++++++------------------------- storage/src/hashedshunt.rs | 69 ++++++++++++++++ storage/src/lib.rs | 2 + 3 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 storage/src/hashedshunt.rs diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 02055d658bf2..58501298ac85 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -1,37 +1,49 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{BranchNode, Children, HashType, IntoSplitPath, LeafNode, Node, Path, TriePath}; +use crate::{ + Children, HashType, HashableShunt, IntoSplitPath, Node, Path, PathComponent, SplitPath, + TriePath, +}; use smallvec::SmallVec; -/// Returns the hash of `node`, which is at the given `path_prefix`. -#[must_use] -pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { - match node { - Node::Branch(node) => { - // All child hashes should be filled in. - // TODO danlaine: Enforce this with the type system. - debug_assert!( - node.children - .iter() - .all(|(_, c)| !matches!(c, Some(crate::Child::Node(_)))), - "branch children: {:?}", - node.children - ); - NodeAndPrefix { - node: node.as_ref(), - prefix: path_prefix, +impl<'a, P: SplitPath> HashableShunt<'a, P, &'a [PathComponent]> { + /// Creates a new [`HashableShunt`] from the given `node` at the given `prefix`. + pub fn from_node(prefix: P, node: &'a Node) -> Self { + match node { + Node::Branch(node) => { + // All child hashes should be filled in. + // TODO danlaine: Enforce this with the type system. + debug_assert!( + node.children + .iter() + .all(|(_, c)| !matches!(c, Some(crate::Child::Node(_)))), + "branch children: {:?}", + node.children + ); + Self::new( + prefix, + node.partial_path.as_components(), + node.value.as_deref().map(ValueDigest::Value), + node.children_hashes(), + ) } - .into() - } - Node::Leaf(node) => NodeAndPrefix { - node, - prefix: path_prefix, + Node::Leaf(node) => Self::new( + prefix, + node.partial_path.as_components(), + Some(ValueDigest::Value(&node.value)), + Children::new(), + ), } - .into(), } } +/// Returns the hash of `node`, which is at the given `path_prefix`. +#[must_use] +pub fn hash_node(node: &Node, path_prefix: &Path) -> HashType { + HashableShunt::from_node(path_prefix.as_components(), node).to_hash() +} + /// Returns the serialized representation of `node` used as the pre-image /// when hashing the node. The node is at the given `path_prefix`. #[must_use] @@ -40,20 +52,7 @@ pub fn hash_preimage(node: &Node, path_prefix: &Path) -> Box<[u8]> { #[expect(clippy::arithmetic_side_effects)] let est_len = node.partial_path().len() + path_prefix.len() + 3 + HashType::empty().len(); let mut buf = Vec::with_capacity(est_len); - match node { - Node::Branch(node) => { - NodeAndPrefix { - node: node.as_ref(), - prefix: path_prefix, - } - .write(&mut buf); - } - Node::Leaf(node) => NodeAndPrefix { - node, - prefix: path_prefix, - } - .write(&mut buf), - } + HashableShunt::from_node(path_prefix.as_components(), node).write(&mut buf); buf.into_boxed_slice() } @@ -63,20 +62,13 @@ pub trait HasUpdate { impl HasUpdate for Vec { fn update>(&mut self, data: T) { - self.extend(data.as_ref().iter().copied()); + self.extend_from_slice(data.as_ref()); } } -// TODO: make it work with any size SmallVec -// impl + smallvec::Array> HasUpdate for SmallVec { -// fn update>(&mut self, data: U) { -// self.extend(data.as_ref()); -// } -// } - -impl HasUpdate for SmallVec<[u8; 32]> { +impl> HasUpdate for SmallVec { fn update>(&mut self, data: T) { - self.extend(data.as_ref().iter().copied()); + self.extend_from_slice(data.as_ref()); } } @@ -138,6 +130,15 @@ impl> ValueDigest { ValueDigest::Hash(v) => ValueDigest::Hash(v), } } + + /// Maps the value inside this `ValueDigest` to another value. + pub fn map(self, f: impl FnOnce(T) -> O) -> ValueDigest { + match self { + Self::Value(v) => ValueDigest::Value(f(v)), + #[cfg(not(feature = "ethhash"))] + Self::Hash(h) => ValueDigest::Hash(h), + } + } } impl> AsRef<[u8]> for ValueDigest { @@ -174,70 +175,7 @@ pub trait Hashable: std::fmt::Debug { pub trait Preimage: std::fmt::Debug { /// Returns the hash of this preimage. fn to_hash(&self) -> HashType; + /// Write this hash preimage to `buf`. fn write(&self, buf: &mut impl HasUpdate); } - -trait HashableNode: std::fmt::Debug { - fn partial_path(&self) -> impl IntoSplitPath + '_; - fn value(&self) -> Option<&[u8]>; - fn child_hashes(&self) -> Children>; -} - -impl HashableNode for BranchNode { - fn partial_path(&self) -> impl IntoSplitPath + '_ { - self.partial_path.as_components() - } - - fn value(&self) -> Option<&[u8]> { - self.value.as_deref() - } - - fn child_hashes(&self) -> Children> { - self.children_hashes() - } -} - -impl HashableNode for LeafNode { - fn partial_path(&self) -> impl IntoSplitPath + '_ { - self.partial_path.as_components() - } - - fn value(&self) -> Option<&[u8]> { - Some(&self.value) - } - - fn child_hashes(&self) -> Children> { - Children::new() - } -} - -#[derive(Debug)] -struct NodeAndPrefix<'a, N: HashableNode> { - node: &'a N, - prefix: &'a Path, -} - -impl<'a, N: HashableNode> From> for HashType { - fn from(node: NodeAndPrefix<'a, N>) -> Self { - node.to_hash() - } -} - -impl<'a, N: HashableNode> Hashable for NodeAndPrefix<'a, N> { - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { - self.prefix.as_components() - } - - fn partial_path(&self) -> impl IntoSplitPath + '_ { - self.node.partial_path() - } - - fn value_digest(&self) -> Option> { - self.node.value().map(ValueDigest::Value) - } - - fn children(&self) -> Children> { - self.node.child_hashes() - } -} diff --git a/storage/src/hashedshunt.rs b/storage/src/hashedshunt.rs new file mode 100644 index 000000000000..ab7306a7eb52 --- /dev/null +++ b/storage/src/hashedshunt.rs @@ -0,0 +1,69 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::{Children, HashType, Hashable, IntoSplitPath, SplitPath, ValueDigest}; + +/// A shunt for a hasheable trie that we can use to compute the hash of a node +/// using component parts. +pub struct HashableShunt<'a, P1, P2> { + parent_prefix: P1, + partial_path: P2, + value: Option>, + child_hashes: Children>, +} + +impl<'a, P1: SplitPath, P2: SplitPath> HashableShunt<'a, P1, P2> { + /// Creates a new [`HashableShunt`]. + #[must_use] + pub const fn new( + parent_prefix: P1, + partial_path: P2, + value: Option>, + child_hashes: Children>, + ) -> Self { + Self { + parent_prefix, + partial_path, + value, + child_hashes, + } + } + + /// Calculates the hash of this shunt. + pub fn to_hash(&self) -> HashType { + crate::Preimage::to_hash(self) + } +} + +impl std::fmt::Debug for HashableShunt<'_, P1, P2> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HashableShunt") + .field("parent_prefix", &self.parent_prefix.display()) + .field("partial_path", &self.partial_path.display()) + .field( + "value", + &self.value.as_ref().map(|v| v.as_ref().map(hex::encode)), + ) + .field("child_hashes", &self.child_hashes) + .field("hash", &self.to_hash()) + .finish() + } +} + +impl Hashable for HashableShunt<'_, P1, P2> { + fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + self.parent_prefix + } + + fn partial_path(&self) -> impl IntoSplitPath + '_ { + self.partial_path + } + + fn value_digest(&self) -> Option> { + self.value.clone() + } + + fn children(&self) -> Children> { + self.child_hashes.clone() + } +} diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 2fc09e43ae40..5414340cae86 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -24,6 +24,7 @@ use std::ops::Range; mod checker; mod hashednode; +mod hashedshunt; mod hashers; mod iter; mod linear; @@ -44,6 +45,7 @@ pub mod macros; // re-export these so callers don't need to know where they are pub use checker::{CheckOpt, CheckerReport, DBStats, FreeListsStats, TrieStats}; pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage}; +pub use hashedshunt::HashableShunt; pub use linear::{FileIoError, ReadableStorage, WritableStorage}; pub use node::path::{NibblesIterator, Path}; pub use node::{ From 49ab032c4112f65637f0d8f9dadbe98cd1bca6e7 Mon Sep 17 00:00:00 2001 From: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:17:22 -0400 Subject: [PATCH 0992/1053] feat: Parallel updates to Merkle trie (v2) (#1258) Second implementation of adding parallel updates to a Merkle trie. In this approach, each child of the root node is a separate trie that can be modified independently by a separate worker thread. Four steps are needed to create a parallel proposal: 1. Prepare: The trie is modified such that the root is always a branch with an empty partial path. This prevents any update from replacing the root node with one of its children, which is necessary to allow the sub-tries to be operated on independently. 2. Split: The updates from a batch are split and sent to different workers based on the first nibble of their corresponding keys. 3. Merge: After completing the batch, each worker returns the root of its sub-trie to the main thread. The main thread attaches the sub-trie roots to the children array of the root node. 4. Post-process: The Merkle trie is returned to its canonical form. This eliminates cases where a trie has a root node with no value and only one single child, or a trie has a root branch node with no value and no children. The main changes are in parallel.rs. A test case (test_propose_parallel) has been added to test parallel insertion/deletion. --------- Signed-off-by: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> --- Cargo.lock | 1 + firewood/Cargo.toml | 1 + firewood/src/db.rs | 180 ++++++++ firewood/src/manager.rs | 25 +- firewood/src/{merkle.rs => merkle/mod.rs} | 36 +- firewood/src/merkle/parallel.rs | 473 ++++++++++++++++++++++ firewood/src/v2/api.rs | 21 + storage/src/node/mod.rs | 47 +++ storage/src/nodestore/mod.rs | 32 ++ 9 files changed, 810 insertions(+), 6 deletions(-) rename firewood/src/{merkle.rs => merkle/mod.rs} (97%) create mode 100644 firewood/src/merkle/parallel.rs diff --git a/Cargo.lock b/Cargo.lock index b3229e98ad1c..1174a5421fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,6 +1058,7 @@ dependencies = [ "plain_hasher", "pprof", "rand 0.9.2", + "rayon", "rlp", "sha3", "tempfile", diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index ff8a61651fe6..d44d06d1be18 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,6 +35,7 @@ metrics.workspace = true thiserror.workspace = true # Regular dependencies typed-builder = "0.22.0" +rayon = "1.11.0" [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c1c6f57ad778..29f4586ce3d0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -27,6 +27,8 @@ use std::sync::Arc; use thiserror::Error; use typed_builder::TypedBuilder; +use crate::merkle::parallel::ParallelMerkle; + #[derive(Error, Debug)] #[non_exhaustive] /// Represents the different types of errors that can occur in the database. @@ -200,6 +202,26 @@ impl Db { self.manager.view(root_hash).map_err(Into::into) } + /// Propose a new batch that is processed in parallel. + /// + /// # Panics + /// + /// Panics if the revision manager cannot create a thread pool. + pub fn propose_parallel( + &self, + batch: impl IntoIterator, + ) -> Result, api::Error> { + let parent = self.manager.current_revision(); + let mut parallel_merkle = ParallelMerkle::default(); + let immutable = + parallel_merkle.create_proposal(&parent, batch, self.manager.threadpool())?; + self.manager.add_proposal(immutable.clone()); + Ok(Proposal { + nodestore: immutable, + db: self, + }) + } + /// Dump the Trie of the latest revision. pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); @@ -555,6 +577,164 @@ mod test { assert_eq!(&*value, b"proposal_value"); } + #[test] + fn test_propose_parallel_reopen() { + fn insert_commit(db: &TestDb, kv: u8) { + let keys: Vec<[u8; 1]> = vec![[kv; 1]]; + let vals: Vec> = vec![Box::new([kv; 1])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + } + + // Create, insert, close, open, insert + let db = TestDb::new(); + insert_commit(&db, 1); + let db = db.reopen(); + insert_commit(&db, 2); + // Check that the keys are still there after the commits + let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); + let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; + let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&committed.val(k).unwrap().unwrap(), v); + } + drop(db); + + // Open-db1, insert, open-db2, insert + let db1 = TestDb::new(); + insert_commit(&db1, 1); + let db2 = TestDb::new(); + insert_commit(&db2, 2); + let committed1 = db1.revision(db1.root_hash().unwrap().unwrap()).unwrap(); + let committed2 = db2.revision(db2.root_hash().unwrap().unwrap()).unwrap(); + let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; + let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; + let mut kviter = keys.iter().zip(vals.iter()); + let (k, v) = kviter.next().unwrap(); + assert_eq!(&committed1.val(k).unwrap().unwrap(), v); + let (k, v) = kviter.next().unwrap(); + assert_eq!(&committed2.val(k).unwrap().unwrap(), v); + } + + #[test] + fn test_propose_parallel() { + const N: usize = 100; + let db = TestDb::new(); + + // Test an empty proposal + let keys: Vec<[u8; 0]> = Vec::new(); + let vals: Vec> = Vec::new(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + + // Create a proposal consisting of a single entry and an empty key. + let keys: Vec<[u8; 0]> = vec![[0; 0]]; + + // Note that if the value is [], then it is interpreted as a DeleteRange. + // Instead, set value to [0] + let vals: Vec> = vec![Box::new([0; 1])]; + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + + // Check that the key is still there after the commit + let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&committed.val(k).unwrap().unwrap(), v); + } + + // Create a proposal that deletes the previous entry + let vals: Vec> = vec![Box::new([0; 0])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + let kviter = keys.iter().zip(vals.iter()); + for (k, _v) in kviter { + assert_eq!(proposal.val(k).unwrap(), None); + } + proposal.commit().unwrap(); + + // Create a proposal that inserts 0 to 999 + let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) + .map(|i| { + ( + format!("key{i}").into_bytes(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + + // Create a proposal that deletes all of the even entries + let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) + .filter_map(|i| { + if i % 2 != 0 { + Some::<(Vec, Box<[u8]>)>((format!("key{i}").into_bytes(), Box::new([]))) + } else { + None + } + }) + .unzip(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, _v) in kviter { + assert_eq!(proposal.val(k).unwrap(), None); + } + proposal.commit().unwrap(); + + // Create a proposal that deletes using empty prefix + let keys: Vec<[u8; 0]> = vec![[0; 0]]; + let vals: Vec> = vec![Box::new([0; 0])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + + // Create N keys and values like (key0, value0)..(keyN, valueN) + let rng = firewood_storage::SeededRng::from_env_or_random(); + let (keys, vals): (Vec<_>, Vec<_>) = (0..N) + .map(|i| { + ( + rng.random::<[u8; 32]>(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + // Looping twice to test that we are reusing the thread pool. + for _ in 0..2 { + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + // iterate over the keys and values again, checking that the values are in the correct proposal + let kviter = keys.iter().zip(vals.iter()); + + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + } + } + /// Test that proposing on a proposal works as expected /// /// Test creates two batches and proposes them, and verifies that the values are in the correct proposal. diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 5d2dc9bd616f..6fa9e214a400 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -13,10 +13,11 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex, OnceLock, RwLock}; use firewood_storage::logger::{trace, warn}; use metrics::gauge; +use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; @@ -24,7 +25,8 @@ use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ - Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, + BranchNode, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, + TrieHash, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TypedBuilder)] @@ -75,6 +77,7 @@ pub(crate) struct RevisionManager { proposals: Mutex>, // committing_proposals: VecDeque>, by_hash: RwLock>, + threadpool: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -115,6 +118,7 @@ impl RevisionManager { by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), + threadpool: OnceLock::new(), }; if let Some(hash) = nodestore.root_hash().or_default_root_hash() { @@ -309,6 +313,23 @@ impl RevisionManager { .expect("there is always one revision") .clone() } + + /// Gets or creates a threadpool associated with the revision manager. + /// + /// # Panics + /// + /// Panics if the it cannot create a thread pool. + pub fn threadpool(&self) -> &ThreadPool { + // Note that OnceLock currently doesn't support get_or_try_init (it is available in a + // nightly release). The get_or_init should be replaced with get_or_try_init once it + // is available to allow the error to be passed back to the caller. + self.threadpool.get_or_init(|| { + ThreadPoolBuilder::new() + .num_threads(BranchNode::MAX_CHILDREN) + .build() + .expect("Error in creating threadpool") + }) + } } #[cfg(test)] diff --git a/firewood/src/merkle.rs b/firewood/src/merkle/mod.rs similarity index 97% rename from firewood/src/merkle.rs rename to firewood/src/merkle/mod.rs index a27d298c389c..366cb0390a03 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle/mod.rs @@ -4,6 +4,9 @@ #[cfg(test)] pub(crate) mod tests; +/// Parallel merkle +pub mod parallel; + use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; @@ -593,10 +596,17 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { - let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); + self.insert_from_iter(NibblesIterator::new(key), value) + } + /// Map `key` to `value` in the trie when `key` is a `NibblesIterator` + pub fn insert_from_iter( + &mut self, + key: NibblesIterator<'_>, + value: Value, + ) -> Result<(), FileIoError> { + let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); - let Some(root_node) = std::mem::take(root) else { // The trie is empty. Create a new leaf node with `value` and set // it as the root. @@ -751,8 +761,18 @@ impl Merkle> { /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. pub fn remove(&mut self, key: &[u8]) -> Result, FileIoError> { - let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); + self.remove_from_iter(NibblesIterator::new(key)) + } + /// Removes the value associated with the given `key` where `key` is a `NibblesIterator` + /// Returns the value that was removed, if any. + /// Otherwise returns `None`. + /// Each element of `key` is 2 nibbles. + pub fn remove_from_iter( + &mut self, + key: NibblesIterator<'_>, + ) -> Result, FileIoError> { + let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. @@ -842,8 +862,16 @@ impl Merkle> { /// Removes any key-value pairs with keys that have the given `prefix`. /// Returns the number of key-value pairs removed. pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { - let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); + self.remove_prefix_from_iter(NibblesIterator::new(prefix)) + } + /// Removes any key-value pairs with keys that have the given `prefix` where `prefix` is a `NibblesIterator` + /// Returns the number of key-value pairs removed. + pub fn remove_prefix_from_iter( + &mut self, + prefix: NibblesIterator<'_>, + ) -> Result { + let prefix = Path::from_nibbles_iterator(prefix); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. diff --git a/firewood/src/merkle/parallel.rs b/firewood/src/merkle/parallel.rs new file mode 100644 index 000000000000..7514a3894ff5 --- /dev/null +++ b/firewood/src/merkle/parallel.rs @@ -0,0 +1,473 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::db::BatchOp; +use crate::merkle::{Key, Merkle, Value}; +use crate::v2::api::KeyValuePairIter; +use firewood_storage::logger::error; +use firewood_storage::{ + BranchNode, Child, Children, FileBacked, FileIoError, ImmutableProposal, LeafNode, + MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeReader, NodeStore, Parentable, + Path, PathComponent, +}; +use rayon::ThreadPool; +use std::iter::once; +use std::ops::Deref; +use std::sync::mpsc::{Receiver, SendError, Sender}; +use std::sync::{Arc, mpsc}; + +#[derive(Debug)] +struct WorkerSender(mpsc::Sender>); + +impl std::ops::Deref for WorkerSender { + type Target = mpsc::Sender>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Response returned from a worker to the main thread. Includes the new root of the subtrie +/// at the given first path component and the deleted nodes. +#[derive(Debug)] +struct Response { + first_path_component: PathComponent, + root: Option, + deleted_nodes: Vec, +} + +#[derive(Debug)] +pub enum CreateProposalError { + FileIoError(FileIoError), + SendError, + InvalidConversionToPathComponent, +} + +impl From for CreateProposalError { + fn from(err: FileIoError) -> Self { + CreateProposalError::FileIoError(err) + } +} + +impl From>> for CreateProposalError { + fn from(_err: SendError>) -> Self { + CreateProposalError::SendError + } +} + +/// `ParallelMerkle` safely performs parallel modifications to a Merkle trie. It does this +/// by creating a worker for each subtrie from the root, and allowing the the workers to +/// perform inserts and removes to their subtries. +#[derive(Debug, Default)] +pub struct ParallelMerkle { + workers: Children>, +} + +impl ParallelMerkle { + /// Force the root (if necessary) into a branch with no partial path to allow the clean + /// separation of the trie into an array of subtries that can be operated on independently + /// by the worker threads. + fn force_root( + &self, + proposal: &mut NodeStore, + ) -> Result, CreateProposalError> { + // There are 3 different cases to handle depending on the value of the root node. + // + // 1. If root is None, create a branch node with an empty partial path and a None for + // value. Create Nones for all of its children. + // 2. If the existing root has a partial path, then create a new root with an empty + // partial path and a None for a value. Push down the previous root as a child. Note + // that this modified Merkle trie is not currently valid and may need to be updated + // during the post-processing step. + // 3. If the existing root does not have a partial path, then there is nothing we need + // to do if it is a branch. If it is a leaf, then convert it into a branch. + // + // Cases 2 and 3 are handled by `force_branch_for_insert`. This function returns a branch + // node with an empty partial path. + proposal.root_mut().take().map_or_else( + || { + // Empty trie. Create a branch node with an empty partial path and a None for a value. + Ok(BranchNode { + partial_path: Path::default(), + value: None, + children: Children::default(), + } + .into()) + }, + |node| { + // Returns an error if it cannot convert a child index into a path component. + node.force_branch_for_insert() + .map_err(|_| CreateProposalError::InvalidConversionToPathComponent) + }, + ) + } + + /// After performing parallel modifications, it may be necessary to perform post processing to + /// return the Merkle trie to the correct canonical form. This involves checking if the Merkle + /// trie has an extra root node. If it does, apply a transform to return the trie to a valid + /// state by following the steps below: + /// + /// If the root node has: + /// 0 children and no value, the trie is empty. Just delete the root. + /// 0 children and a value (from an empty key), the root should be a leaf + /// 1 child and no value, the child should be the root (need to update partial path) + /// In all other cases, the root is already correct. + fn postprocess_trie( + &self, + nodestore: &mut NodeStore, + mut branch: Box, + ) -> Result, FileIoError> { + let mut children_iter = branch + .children + .iter_mut() + .filter_map(|(index, child)| child.as_mut().map(|child| (index, child))); + + let first_child = children_iter.next(); + match first_child { + None => { + Ok(branch.value.map(|value| { + // There is a value for the empty key. Create a leaf with the value and return. + Node::Leaf(LeafNode { + value, + partial_path: Path::default(), // Partial path should be empty + }) + })) + } + Some((child_index, child)) => { + // Check if the root has a value or if there is more than one child. If yes, then + // just return the root unmodified + if branch.value.is_some() || children_iter.next().is_some() { + drop(children_iter); + return Ok(Some(Node::Branch(branch))); + } + + // Return the child as the new root. Update its partial path to include the index value. + let mut child = match child { + Child::Node(child_node) => std::mem::take(child_node), + Child::AddressWithHash(addr, _) => nodestore.read_for_update((*addr).into())?, + Child::MaybePersisted(maybe_persisted, _) => { + nodestore.read_for_update(maybe_persisted.clone())? + } + }; + + // The child's partial path is the concatenation of its (now removed) parent, which + // should always be empty because of our prepare step, its (former) child index, and + // its partial path. Because the parent's partial path should always be empty, we + // can omit it and start with the `child_index`. + let partial_path = Path::from_nibbles_iterator( + once(child_index.as_u8()).chain(child.partial_path().iter().copied()), + ); + child.update_partial_path(partial_path); + Ok(Some(child)) + } + } + } + + /// Call by a worker to processes requests from `child_receiver` and send back a response on + /// `response_sender` once the main thread closes the child sender. + fn worker_event_loop( + mut merkle: Merkle>, + first_path_component: PathComponent, + child_receiver: Receiver>, + response_sender: Sender>, + ) -> Result<(), Box>>> { + // Wait for a message on the receiver child channel. Break out of loop when the sender has + // closed the child sender. + while let Ok(request) = child_receiver.recv() { + if let Err(err) = match request { + // insert a key-value pair into the subtrie + BatchOp::Put { key, value } => { + let mut nibbles_iter = NibblesIterator::new(&key); + nibbles_iter.next(); // Skip the first nibble + merkle.insert_from_iter(nibbles_iter, value) + } + BatchOp::Delete { key } => { + let mut nibbles_iter = NibblesIterator::new(&key); + nibbles_iter.next(); // Skip the first nibble + merkle.remove_from_iter(nibbles_iter).map(|_| ()) + } + BatchOp::DeleteRange { prefix } => { + let mut nibbles_iter = NibblesIterator::new(&prefix); + nibbles_iter.next(); // Skip the first nibble + merkle.remove_prefix_from_iter(nibbles_iter).map(|_| ()) + } + } { + response_sender.send(Err(err))?; + break; // Stop handling additional requests + } + } + // The main thread has closed the channel. Send back the worker's response. + let mut nodestore = merkle.into_inner(); + let response = Response { + first_path_component, + root: nodestore.root_mut().take().map(Child::Node), + deleted_nodes: nodestore.take_deleted_nodes(), + }; + response_sender.send(Ok(response))?; + Ok(()) + } + + /// Creates a worker for performing operations on a subtrie, with the subtrie being determined + /// by the value of the `first_path_component`. + fn create_worker( + pool: &ThreadPool, + proposal: &NodeStore, + root_branch: &mut BranchNode, + first_path_component: PathComponent, + response_sender: Sender>, + ) -> Result { + // Create a channel for the coordinator (main thread) to send messages to this worker. + let (child_sender, child_receiver) = mpsc::channel(); + + // The root's child becomes the root node of the worker + let child_root = root_branch + .children + .get_mut(first_path_component) + .take() + .map(|child| match child { + Child::Node(node) => Ok(node), + Child::AddressWithHash(address, _) => { + Ok(proposal.read_node(address)?.deref().clone()) + } + Child::MaybePersisted(maybe_persisted, _) => { + Ok(maybe_persisted.as_shared_node(proposal)?.deref().clone()) + } + }) + .transpose()?; + + // Build a nodestore from the child node + let worker_nodestore = NodeStore::from_root(proposal, child_root); + + // Spawn a worker from the threadpool for this nibble. The worker will send messages to the coordinator + // using `worker_sender`. + pool.spawn(move || { + if let Err(err) = ParallelMerkle::worker_event_loop( + Merkle::from(worker_nodestore), + first_path_component, + child_receiver, + response_sender, + ) { + error!("Worker cannot send to main thread using response channel: {err:?}"); + } + }); + Ok(WorkerSender(child_sender)) + } + + // Collect responses from the workers, each representing the root of a subtrie and merge them into the + // root node of the main trie. + fn merge_children( + &mut self, + response_channel: Receiver>, + proposal: &mut NodeStore, + root_branch: &mut BranchNode, + ) -> Result<(), FileIoError> { + while let Ok(response) = response_channel.recv() { + match response { + Ok(response) => { + // Adding deleted nodes (from calling read_for_update) from the child's nodestore. + proposal.delete_nodes(response.deleted_nodes.as_slice()); + + // Set the child at index to response.root which is the root of the child's subtrie. + *root_branch.children.get_mut(response.first_path_component) = response.root; + } + Err(err) => { + return Err(err); // Early termination. + } + } + } + Ok(()) + } + + /// Get a worker from the worker pool based on the `first_path_component` value. Create a worker if + /// it doesn't exist already. + fn worker( + &mut self, + pool: &ThreadPool, + proposal: &NodeStore, + root_branch: &mut BranchNode, + first_path_component: PathComponent, + response_sender: Sender>, + ) -> Result<&mut WorkerSender, FileIoError> { + // Find the worker's state corresponding to the first nibble which is stored in an array. + let worker_option = self.workers.get_mut(first_path_component); + + // Create a new worker if it doesn't exist. Not using `get_or_insert_with` with worker_option + // because it is possible to generate a FileIoError within the closure. + match worker_option { + Some(worker) => Ok(worker), + None => Ok(worker_option.insert(ParallelMerkle::create_worker( + pool, + proposal, + root_branch, + first_path_component, + response_sender, + )?)), + } + } + + /// Removes all of the entries in the trie. For the root entry, the value is removed but the + /// root itself will remain. An empty root will only be removed during post processing. + fn remove_all_entries( + &self, + root_branch: &mut BranchNode, + ) -> Result<(), SendError>> { + for worker in self + .workers + .iter() + .filter_map(|(_, worker)| worker.as_ref()) + { + worker.send(BatchOp::DeleteRange { + prefix: Box::default(), // Empty prefix + })?; + } + // Also set the root value to None but does not delete the root. + root_branch.value = None; + Ok(()) + } + + /// The parent thread may receive a `SendError` if the worker that it is sending to has + /// returned to the threadpool after encountering a `FileIoError`. This function should + /// be called after receiving a `SendError` to find and propagate the `FileIoError`. + fn find_fileio_error( + response_receiver: &Receiver>, + ) -> Result<(), FileIoError> { + // Go through the messages in the response channel without blocking to see if we can + // find the FileIoError that caused the worker to close the channel, resulting in a + // send error. If we can find it, then we propagate the FileIoError. Note that + // successful responses can be in the response channel ahead of the FileIoError. + // These are sent from workers that completed their requests without encountering + // a FileIoError. + for result in response_receiver.try_iter() { + let _ = result?; // explicitly ignore the successful Response + } + Ok(()) + } + + /// Creates a parallel proposal in 4 steps: Prepare, Split, Merge, and Post-process. In the + /// Prepare step, the trie is modified to ensure that the root is a branch node with no + /// partial path. In the split step, entries from the batch are sent to workers that + /// independently modify their sub-tries. In the merge step, the sub-tries are merged back + /// to the main trie. Finally, in the post-processing step, the trie is returned to its + /// canonical form. + /// + /// # Errors + /// + /// Returns a `CreateProposalError::FileIoError` if it encounters an error fetching nodes + /// from storage, a `CreateProposalError::SendError` if it is unable to send messages to + /// the workers, and a `CreateProposalError::InvalidConversionToPathComponent` if it is + /// unable to convert a u8 index into a path component. + pub fn create_proposal( + &mut self, + parent: &NodeStore, + batch: impl IntoIterator, + pool: &ThreadPool, + ) -> Result, FileBacked>>, CreateProposalError> { + // Create a mutable nodestore from the parent + let mut mutable_nodestore = NodeStore::new(parent)?; + + // Prepare step: Force the root into a branch with no partial path in preparation for + // performing parallel modifications to the trie. + let mut root_branch = self.force_root(&mut mutable_nodestore)?; + + // Create a response channel the workers use to send messages back to the coordinator (us) + let (response_sender, response_receiver) = mpsc::channel(); + + // Split step: for each operation in the batch, send a request to the worker that is + // responsible for the sub-trie corresponding to the operation's first nibble. + for op in batch.into_iter().map_into_batch() { + // Get the first nibble of the key to determine which worker to send the request to. + // + // Need to handle an empty key. Since the partial_path of the root must be empty, an + // empty key should always be for the root node. There are 3 cases to consider. + // + // Insert: The main thread modifies the value of the root. + // + // Remove: The main thread removes any value at the root. However, it should not delete + // the root node, which, if necessary later, will be done in post processing. + // + // Remove Prefix: + // For a remove prefix, we would need to remove everything. We do this by sending + // a remove prefix with an empty prefix to all of the children, then removing the + // value of the root node. + let mut key_nibbles = NibblesIterator::new(op.key().as_ref()); + let Some(first_path_component) = key_nibbles.next() else { + match &op { + BatchOp::Put { key: _, value } => { + root_branch.value = Some(value.as_ref().into()); + } + BatchOp::Delete { key: _ } => { + root_branch.value = None; + } + BatchOp::DeleteRange { prefix: _ } => { + // Calling remove prefix with an empty prefix is equivalent to a remove all. + if let Err(err) = self.remove_all_entries(&mut root_branch) { + // A send error is most likely due to a worker returning to the thread pool + // after it encountered a FileIoError. Try to find the FileIoError in the + // response channel and return that instead. + ParallelMerkle::find_fileio_error(&response_receiver)?; + return Err(err.into()); + } + } + } + continue; // Done with this empty key operation. + }; + + // Verify that the worker index taken from the first nibble is valid. + let first_path_component = PathComponent::try_new(first_path_component) + .ok_or(CreateProposalError::InvalidConversionToPathComponent)?; + + // Get the worker that is responsible for this nibble. The worker will be created if it + // doesn't already exist. + let worker = self.worker( + pool, + &mutable_nodestore, + &mut root_branch, + first_path_component, + response_sender.clone(), + )?; + + // Send the current operation to the worker. + // TODO: Currently the key from the BatchOp is copied to a Box<[u8]> before it is sent + // to the worker. It may be possible to send a nibble iterator instead of a + // Box<[u8]> to the worker if we use rayon scoped threads. This change would + // eliminate a memory copy but may require some code refactoring. + if let Err(err) = match &op { + BatchOp::Put { key: _, value } => worker.send(BatchOp::Put { + key: op.key().as_ref().into(), + value: value.as_ref().into(), + }), + BatchOp::Delete { key: _ } => worker.send(BatchOp::Delete { + key: op.key().as_ref().into(), + }), + BatchOp::DeleteRange { prefix: _ } => worker.send(BatchOp::DeleteRange { + prefix: op.key().as_ref().into(), + }), + } { + // A send error is most likely due to a worker returning to the thread pool + // after it encountered a FileIoError. Try to find the FileIoError in the + // response channel and return that instead. + ParallelMerkle::find_fileio_error(&response_receiver)?; + return Err(err.into()); + } + } + + // Drop the sender response channel from the parent thread. + drop(response_sender); + + // Setting the workers to default will close the senders to the workers. This will cause the + // workers to send back their responses. + self.workers = Children::default(); + + // Merge step: Collect the results from the workers and merge them as children to the root. + self.merge_children(response_receiver, &mut mutable_nodestore, &mut root_branch)?; + + // Post-process step: return the trie to its canonical form. + *mutable_nodestore.root_mut() = + self.postprocess_trie(&mut mutable_nodestore, root_branch)?; + + let immutable: Arc, FileBacked>> = + Arc::new(mutable_nodestore.try_into()?); + + Ok(immutable) + } +} diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index bc2f883e613b..7a0678bb611b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::manager::RevisionManagerError; +use crate::merkle::parallel::CreateProposalError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; use firewood_storage::{FileIoError, TrieHash}; @@ -156,6 +157,14 @@ pub enum Error { /// An invalid root hash was provided #[error(transparent)] InvalidRootHash(#[from] firewood_storage::InvalidTrieHashLength), + + // Error sending to worker + #[error("send error to worker")] + SendErrorToWorker, + + // Error converting a u8 index into a path component + #[error("error converting a u8 index into a path component")] + InvalidConversionToPathComponent, } impl From for Error { @@ -179,6 +188,18 @@ impl From for Error { } } +impl From for Error { + fn from(value: CreateProposalError) -> Self { + match value { + CreateProposalError::FileIoError(err) => Error::FileIO(err), + CreateProposalError::SendError => Error::SendErrorToWorker, + CreateProposalError::InvalidConversionToPathComponent => { + Error::InvalidConversionToPathComponent + } + } + } +} + /// The database interface. The methods here operate on the most /// recently committed revision, and allow the creation of a new /// [`Proposal`] or a new [`DbView`] based on a specific historical diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 29c6df2cb804..5f032b7713d5 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -402,6 +402,53 @@ impl Node { } } } + + /// Force the node (which should be a root of a trie) into a branch with no partial path + /// in preparation for a parallel insert. There are two cases to handle depending on whether + /// the node has a partial path. + /// + /// 1. If the node has a partial path, then create a new node with an empty partial path + /// and a None for a value. Push down the previous node as a child and return the + /// branch. + /// 2. If the existing node does not have a partial path, then there is nothing we need + /// to do if it is a branch. If it is a leaf, then convert it into a branch. + /// + /// # Errors + /// + /// Returns an `Error` if it cannot create a `PathComponent` from a u8 index. + pub fn force_branch_for_insert(mut self) -> Result, Error> { + // If the `partial_path` is non-empty, then create a branch that will be the new + // root with the previous root as the child at the index returned from split_first. + if let Some((child_index, child_path)) = self + .partial_path() + .split_first() + .map(|(index, path)| (*index, path.into())) + { + let mut branch = BranchNode { + partial_path: Path::new(), + value: None, + children: Children::default(), + }; + self.update_partial_path(child_path); + let child_path_component = PathComponent::try_new(child_index) + .ok_or_else(|| Error::other("invalid child index"))?; + *branch.children.get_mut(child_path_component) = Some(Child::Node(self)); + Ok(branch.into()) + } else { + Ok(match self { + Node::Leaf(leaf) => { + // Root is a leaf with an empty partial path. Replace it with a branch. + BranchNode { + partial_path: Path::new(), + value: Some(leaf.value), + children: Children::default(), + } + .into() + } + Node::Branch(branch) => branch, + }) + } + } } /// A path iterator item, which has the key nibbles up to this point, diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 880c421b2941..5f70a27edbba 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -295,6 +295,16 @@ impl NodeStore { self.kind.deleted.push(node); } + /// Take the nodes that have been marked as deleted in this proposal. + pub fn take_deleted_nodes(&mut self) -> Vec { + take(&mut self.kind.deleted) + } + + /// Adds to the nodes deleted in this proposal. + pub fn delete_nodes(&mut self, nodes: &[MaybePersistedNode]) { + self.kind.deleted.extend_from_slice(nodes); + } + /// Reads a node for update, marking it as deleted in this proposal. /// We get an arc from cache (reading it from disk if necessary) then /// copy/clone the node and return it. @@ -314,6 +324,28 @@ impl NodeStore { } } +impl NodeStore { + /// Creates a new [`NodeStore`] from a root node. + #[must_use] + pub fn from_root(parent: &NodeStore, root: Option) -> Self { + NodeStore { + header: parent.header, + kind: MutableProposal { + root, + deleted: Vec::default(), + parent: parent.kind.parent.clone(), + }, + storage: parent.storage.clone(), + } + } + + /// Consumes the `NodeStore` and returns the root of the trie + #[must_use] + pub fn into_root(self) -> Option { + self.kind.root + } +} + impl NodeStore { /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. /// This is used during testing and during the creation of an in-memory merkle for proofs From 5d9f5020a6112c6737cd9b0d431813a25c35b61e Mon Sep 17 00:00:00 2001 From: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:03:12 -0400 Subject: [PATCH 0993/1053] fix: Revert "feat: Parallel updates to Merkle trie (v2)" (#1372) Reverts ava-labs/firewood#1258 Runs out of memory in firewood-merkle-differential-fuzz test. --- Cargo.lock | 1 - firewood/Cargo.toml | 1 - firewood/src/db.rs | 180 -------- firewood/src/manager.rs | 25 +- firewood/src/{merkle/mod.rs => merkle.rs} | 36 +- firewood/src/merkle/parallel.rs | 473 ---------------------- firewood/src/v2/api.rs | 21 - storage/src/node/mod.rs | 47 --- storage/src/nodestore/mod.rs | 32 -- 9 files changed, 6 insertions(+), 810 deletions(-) rename firewood/src/{merkle/mod.rs => merkle.rs} (97%) delete mode 100644 firewood/src/merkle/parallel.rs diff --git a/Cargo.lock b/Cargo.lock index 1174a5421fd0..b3229e98ad1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,7 +1058,6 @@ dependencies = [ "plain_hasher", "pprof", "rand 0.9.2", - "rayon", "rlp", "sha3", "tempfile", diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d44d06d1be18..ff8a61651fe6 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,7 +35,6 @@ metrics.workspace = true thiserror.workspace = true # Regular dependencies typed-builder = "0.22.0" -rayon = "1.11.0" [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 29f4586ce3d0..c1c6f57ad778 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -27,8 +27,6 @@ use std::sync::Arc; use thiserror::Error; use typed_builder::TypedBuilder; -use crate::merkle::parallel::ParallelMerkle; - #[derive(Error, Debug)] #[non_exhaustive] /// Represents the different types of errors that can occur in the database. @@ -202,26 +200,6 @@ impl Db { self.manager.view(root_hash).map_err(Into::into) } - /// Propose a new batch that is processed in parallel. - /// - /// # Panics - /// - /// Panics if the revision manager cannot create a thread pool. - pub fn propose_parallel( - &self, - batch: impl IntoIterator, - ) -> Result, api::Error> { - let parent = self.manager.current_revision(); - let mut parallel_merkle = ParallelMerkle::default(); - let immutable = - parallel_merkle.create_proposal(&parent, batch, self.manager.threadpool())?; - self.manager.add_proposal(immutable.clone()); - Ok(Proposal { - nodestore: immutable, - db: self, - }) - } - /// Dump the Trie of the latest revision. pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); @@ -577,164 +555,6 @@ mod test { assert_eq!(&*value, b"proposal_value"); } - #[test] - fn test_propose_parallel_reopen() { - fn insert_commit(db: &TestDb, kv: u8) { - let keys: Vec<[u8; 1]> = vec![[kv; 1]]; - let vals: Vec> = vec![Box::new([kv; 1])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - proposal.commit().unwrap(); - } - - // Create, insert, close, open, insert - let db = TestDb::new(); - insert_commit(&db, 1); - let db = db.reopen(); - insert_commit(&db, 2); - // Check that the keys are still there after the commits - let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); - let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; - let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; - let kviter = keys.iter().zip(vals.iter()); - for (k, v) in kviter { - assert_eq!(&committed.val(k).unwrap().unwrap(), v); - } - drop(db); - - // Open-db1, insert, open-db2, insert - let db1 = TestDb::new(); - insert_commit(&db1, 1); - let db2 = TestDb::new(); - insert_commit(&db2, 2); - let committed1 = db1.revision(db1.root_hash().unwrap().unwrap()).unwrap(); - let committed2 = db2.revision(db2.root_hash().unwrap().unwrap()).unwrap(); - let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; - let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; - let mut kviter = keys.iter().zip(vals.iter()); - let (k, v) = kviter.next().unwrap(); - assert_eq!(&committed1.val(k).unwrap().unwrap(), v); - let (k, v) = kviter.next().unwrap(); - assert_eq!(&committed2.val(k).unwrap().unwrap(), v); - } - - #[test] - fn test_propose_parallel() { - const N: usize = 100; - let db = TestDb::new(); - - // Test an empty proposal - let keys: Vec<[u8; 0]> = Vec::new(); - let vals: Vec> = Vec::new(); - - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - proposal.commit().unwrap(); - - // Create a proposal consisting of a single entry and an empty key. - let keys: Vec<[u8; 0]> = vec![[0; 0]]; - - // Note that if the value is [], then it is interpreted as a DeleteRange. - // Instead, set value to [0] - let vals: Vec> = vec![Box::new([0; 1])]; - - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - - let kviter = keys.iter().zip(vals.iter()); - for (k, v) in kviter { - assert_eq!(&proposal.val(k).unwrap().unwrap(), v); - } - proposal.commit().unwrap(); - - // Check that the key is still there after the commit - let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); - let kviter = keys.iter().zip(vals.iter()); - for (k, v) in kviter { - assert_eq!(&committed.val(k).unwrap().unwrap(), v); - } - - // Create a proposal that deletes the previous entry - let vals: Vec> = vec![Box::new([0; 0])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - - let kviter = keys.iter().zip(vals.iter()); - for (k, _v) in kviter { - assert_eq!(proposal.val(k).unwrap(), None); - } - proposal.commit().unwrap(); - - // Create a proposal that inserts 0 to 999 - let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) - .map(|i| { - ( - format!("key{i}").into_bytes(), - Box::from(format!("value{i}").as_bytes()), - ) - }) - .unzip(); - - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - let kviter = keys.iter().zip(vals.iter()); - for (k, v) in kviter { - assert_eq!(&proposal.val(k).unwrap().unwrap(), v); - } - proposal.commit().unwrap(); - - // Create a proposal that deletes all of the even entries - let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) - .filter_map(|i| { - if i % 2 != 0 { - Some::<(Vec, Box<[u8]>)>((format!("key{i}").into_bytes(), Box::new([]))) - } else { - None - } - }) - .unzip(); - - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - let kviter = keys.iter().zip(vals.iter()); - for (k, _v) in kviter { - assert_eq!(proposal.val(k).unwrap(), None); - } - proposal.commit().unwrap(); - - // Create a proposal that deletes using empty prefix - let keys: Vec<[u8; 0]> = vec![[0; 0]]; - let vals: Vec> = vec![Box::new([0; 0])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - proposal.commit().unwrap(); - - // Create N keys and values like (key0, value0)..(keyN, valueN) - let rng = firewood_storage::SeededRng::from_env_or_random(); - let (keys, vals): (Vec<_>, Vec<_>) = (0..N) - .map(|i| { - ( - rng.random::<[u8; 32]>(), - Box::from(format!("value{i}").as_bytes()), - ) - }) - .unzip(); - - // Looping twice to test that we are reusing the thread pool. - for _ in 0..2 { - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); - - // iterate over the keys and values again, checking that the values are in the correct proposal - let kviter = keys.iter().zip(vals.iter()); - - for (k, v) in kviter { - assert_eq!(&proposal.val(k).unwrap().unwrap(), v); - } - proposal.commit().unwrap(); - } - } - /// Test that proposing on a proposal works as expected /// /// Test creates two batches and proposes them, and verifies that the values are in the correct proposal. diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 6fa9e214a400..5d2dc9bd616f 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -13,11 +13,10 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::{Arc, Mutex, OnceLock, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use firewood_storage::logger::{trace, warn}; use metrics::gauge; -use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; @@ -25,8 +24,7 @@ use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ - BranchNode, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, - TrieHash, + Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TypedBuilder)] @@ -77,7 +75,6 @@ pub(crate) struct RevisionManager { proposals: Mutex>, // committing_proposals: VecDeque>, by_hash: RwLock>, - threadpool: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -118,7 +115,6 @@ impl RevisionManager { by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), - threadpool: OnceLock::new(), }; if let Some(hash) = nodestore.root_hash().or_default_root_hash() { @@ -313,23 +309,6 @@ impl RevisionManager { .expect("there is always one revision") .clone() } - - /// Gets or creates a threadpool associated with the revision manager. - /// - /// # Panics - /// - /// Panics if the it cannot create a thread pool. - pub fn threadpool(&self) -> &ThreadPool { - // Note that OnceLock currently doesn't support get_or_try_init (it is available in a - // nightly release). The get_or_init should be replaced with get_or_try_init once it - // is available to allow the error to be passed back to the caller. - self.threadpool.get_or_init(|| { - ThreadPoolBuilder::new() - .num_threads(BranchNode::MAX_CHILDREN) - .build() - .expect("Error in creating threadpool") - }) - } } #[cfg(test)] diff --git a/firewood/src/merkle/mod.rs b/firewood/src/merkle.rs similarity index 97% rename from firewood/src/merkle/mod.rs rename to firewood/src/merkle.rs index 366cb0390a03..a27d298c389c 100644 --- a/firewood/src/merkle/mod.rs +++ b/firewood/src/merkle.rs @@ -4,9 +4,6 @@ #[cfg(test)] pub(crate) mod tests; -/// Parallel merkle -pub mod parallel; - use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; @@ -596,17 +593,10 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { - self.insert_from_iter(NibblesIterator::new(key), value) - } + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - /// Map `key` to `value` in the trie when `key` is a `NibblesIterator` - pub fn insert_from_iter( - &mut self, - key: NibblesIterator<'_>, - value: Value, - ) -> Result<(), FileIoError> { - let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); + let Some(root_node) = std::mem::take(root) else { // The trie is empty. Create a new leaf node with `value` and set // it as the root. @@ -761,18 +751,8 @@ impl Merkle> { /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. pub fn remove(&mut self, key: &[u8]) -> Result, FileIoError> { - self.remove_from_iter(NibblesIterator::new(key)) - } + let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); - /// Removes the value associated with the given `key` where `key` is a `NibblesIterator` - /// Returns the value that was removed, if any. - /// Otherwise returns `None`. - /// Each element of `key` is 2 nibbles. - pub fn remove_from_iter( - &mut self, - key: NibblesIterator<'_>, - ) -> Result, FileIoError> { - let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. @@ -862,16 +842,8 @@ impl Merkle> { /// Removes any key-value pairs with keys that have the given `prefix`. /// Returns the number of key-value pairs removed. pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { - self.remove_prefix_from_iter(NibblesIterator::new(prefix)) - } + let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); - /// Removes any key-value pairs with keys that have the given `prefix` where `prefix` is a `NibblesIterator` - /// Returns the number of key-value pairs removed. - pub fn remove_prefix_from_iter( - &mut self, - prefix: NibblesIterator<'_>, - ) -> Result { - let prefix = Path::from_nibbles_iterator(prefix); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. diff --git a/firewood/src/merkle/parallel.rs b/firewood/src/merkle/parallel.rs deleted file mode 100644 index 7514a3894ff5..000000000000 --- a/firewood/src/merkle/parallel.rs +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::db::BatchOp; -use crate::merkle::{Key, Merkle, Value}; -use crate::v2::api::KeyValuePairIter; -use firewood_storage::logger::error; -use firewood_storage::{ - BranchNode, Child, Children, FileBacked, FileIoError, ImmutableProposal, LeafNode, - MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeReader, NodeStore, Parentable, - Path, PathComponent, -}; -use rayon::ThreadPool; -use std::iter::once; -use std::ops::Deref; -use std::sync::mpsc::{Receiver, SendError, Sender}; -use std::sync::{Arc, mpsc}; - -#[derive(Debug)] -struct WorkerSender(mpsc::Sender>); - -impl std::ops::Deref for WorkerSender { - type Target = mpsc::Sender>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Response returned from a worker to the main thread. Includes the new root of the subtrie -/// at the given first path component and the deleted nodes. -#[derive(Debug)] -struct Response { - first_path_component: PathComponent, - root: Option, - deleted_nodes: Vec, -} - -#[derive(Debug)] -pub enum CreateProposalError { - FileIoError(FileIoError), - SendError, - InvalidConversionToPathComponent, -} - -impl From for CreateProposalError { - fn from(err: FileIoError) -> Self { - CreateProposalError::FileIoError(err) - } -} - -impl From>> for CreateProposalError { - fn from(_err: SendError>) -> Self { - CreateProposalError::SendError - } -} - -/// `ParallelMerkle` safely performs parallel modifications to a Merkle trie. It does this -/// by creating a worker for each subtrie from the root, and allowing the the workers to -/// perform inserts and removes to their subtries. -#[derive(Debug, Default)] -pub struct ParallelMerkle { - workers: Children>, -} - -impl ParallelMerkle { - /// Force the root (if necessary) into a branch with no partial path to allow the clean - /// separation of the trie into an array of subtries that can be operated on independently - /// by the worker threads. - fn force_root( - &self, - proposal: &mut NodeStore, - ) -> Result, CreateProposalError> { - // There are 3 different cases to handle depending on the value of the root node. - // - // 1. If root is None, create a branch node with an empty partial path and a None for - // value. Create Nones for all of its children. - // 2. If the existing root has a partial path, then create a new root with an empty - // partial path and a None for a value. Push down the previous root as a child. Note - // that this modified Merkle trie is not currently valid and may need to be updated - // during the post-processing step. - // 3. If the existing root does not have a partial path, then there is nothing we need - // to do if it is a branch. If it is a leaf, then convert it into a branch. - // - // Cases 2 and 3 are handled by `force_branch_for_insert`. This function returns a branch - // node with an empty partial path. - proposal.root_mut().take().map_or_else( - || { - // Empty trie. Create a branch node with an empty partial path and a None for a value. - Ok(BranchNode { - partial_path: Path::default(), - value: None, - children: Children::default(), - } - .into()) - }, - |node| { - // Returns an error if it cannot convert a child index into a path component. - node.force_branch_for_insert() - .map_err(|_| CreateProposalError::InvalidConversionToPathComponent) - }, - ) - } - - /// After performing parallel modifications, it may be necessary to perform post processing to - /// return the Merkle trie to the correct canonical form. This involves checking if the Merkle - /// trie has an extra root node. If it does, apply a transform to return the trie to a valid - /// state by following the steps below: - /// - /// If the root node has: - /// 0 children and no value, the trie is empty. Just delete the root. - /// 0 children and a value (from an empty key), the root should be a leaf - /// 1 child and no value, the child should be the root (need to update partial path) - /// In all other cases, the root is already correct. - fn postprocess_trie( - &self, - nodestore: &mut NodeStore, - mut branch: Box, - ) -> Result, FileIoError> { - let mut children_iter = branch - .children - .iter_mut() - .filter_map(|(index, child)| child.as_mut().map(|child| (index, child))); - - let first_child = children_iter.next(); - match first_child { - None => { - Ok(branch.value.map(|value| { - // There is a value for the empty key. Create a leaf with the value and return. - Node::Leaf(LeafNode { - value, - partial_path: Path::default(), // Partial path should be empty - }) - })) - } - Some((child_index, child)) => { - // Check if the root has a value or if there is more than one child. If yes, then - // just return the root unmodified - if branch.value.is_some() || children_iter.next().is_some() { - drop(children_iter); - return Ok(Some(Node::Branch(branch))); - } - - // Return the child as the new root. Update its partial path to include the index value. - let mut child = match child { - Child::Node(child_node) => std::mem::take(child_node), - Child::AddressWithHash(addr, _) => nodestore.read_for_update((*addr).into())?, - Child::MaybePersisted(maybe_persisted, _) => { - nodestore.read_for_update(maybe_persisted.clone())? - } - }; - - // The child's partial path is the concatenation of its (now removed) parent, which - // should always be empty because of our prepare step, its (former) child index, and - // its partial path. Because the parent's partial path should always be empty, we - // can omit it and start with the `child_index`. - let partial_path = Path::from_nibbles_iterator( - once(child_index.as_u8()).chain(child.partial_path().iter().copied()), - ); - child.update_partial_path(partial_path); - Ok(Some(child)) - } - } - } - - /// Call by a worker to processes requests from `child_receiver` and send back a response on - /// `response_sender` once the main thread closes the child sender. - fn worker_event_loop( - mut merkle: Merkle>, - first_path_component: PathComponent, - child_receiver: Receiver>, - response_sender: Sender>, - ) -> Result<(), Box>>> { - // Wait for a message on the receiver child channel. Break out of loop when the sender has - // closed the child sender. - while let Ok(request) = child_receiver.recv() { - if let Err(err) = match request { - // insert a key-value pair into the subtrie - BatchOp::Put { key, value } => { - let mut nibbles_iter = NibblesIterator::new(&key); - nibbles_iter.next(); // Skip the first nibble - merkle.insert_from_iter(nibbles_iter, value) - } - BatchOp::Delete { key } => { - let mut nibbles_iter = NibblesIterator::new(&key); - nibbles_iter.next(); // Skip the first nibble - merkle.remove_from_iter(nibbles_iter).map(|_| ()) - } - BatchOp::DeleteRange { prefix } => { - let mut nibbles_iter = NibblesIterator::new(&prefix); - nibbles_iter.next(); // Skip the first nibble - merkle.remove_prefix_from_iter(nibbles_iter).map(|_| ()) - } - } { - response_sender.send(Err(err))?; - break; // Stop handling additional requests - } - } - // The main thread has closed the channel. Send back the worker's response. - let mut nodestore = merkle.into_inner(); - let response = Response { - first_path_component, - root: nodestore.root_mut().take().map(Child::Node), - deleted_nodes: nodestore.take_deleted_nodes(), - }; - response_sender.send(Ok(response))?; - Ok(()) - } - - /// Creates a worker for performing operations on a subtrie, with the subtrie being determined - /// by the value of the `first_path_component`. - fn create_worker( - pool: &ThreadPool, - proposal: &NodeStore, - root_branch: &mut BranchNode, - first_path_component: PathComponent, - response_sender: Sender>, - ) -> Result { - // Create a channel for the coordinator (main thread) to send messages to this worker. - let (child_sender, child_receiver) = mpsc::channel(); - - // The root's child becomes the root node of the worker - let child_root = root_branch - .children - .get_mut(first_path_component) - .take() - .map(|child| match child { - Child::Node(node) => Ok(node), - Child::AddressWithHash(address, _) => { - Ok(proposal.read_node(address)?.deref().clone()) - } - Child::MaybePersisted(maybe_persisted, _) => { - Ok(maybe_persisted.as_shared_node(proposal)?.deref().clone()) - } - }) - .transpose()?; - - // Build a nodestore from the child node - let worker_nodestore = NodeStore::from_root(proposal, child_root); - - // Spawn a worker from the threadpool for this nibble. The worker will send messages to the coordinator - // using `worker_sender`. - pool.spawn(move || { - if let Err(err) = ParallelMerkle::worker_event_loop( - Merkle::from(worker_nodestore), - first_path_component, - child_receiver, - response_sender, - ) { - error!("Worker cannot send to main thread using response channel: {err:?}"); - } - }); - Ok(WorkerSender(child_sender)) - } - - // Collect responses from the workers, each representing the root of a subtrie and merge them into the - // root node of the main trie. - fn merge_children( - &mut self, - response_channel: Receiver>, - proposal: &mut NodeStore, - root_branch: &mut BranchNode, - ) -> Result<(), FileIoError> { - while let Ok(response) = response_channel.recv() { - match response { - Ok(response) => { - // Adding deleted nodes (from calling read_for_update) from the child's nodestore. - proposal.delete_nodes(response.deleted_nodes.as_slice()); - - // Set the child at index to response.root which is the root of the child's subtrie. - *root_branch.children.get_mut(response.first_path_component) = response.root; - } - Err(err) => { - return Err(err); // Early termination. - } - } - } - Ok(()) - } - - /// Get a worker from the worker pool based on the `first_path_component` value. Create a worker if - /// it doesn't exist already. - fn worker( - &mut self, - pool: &ThreadPool, - proposal: &NodeStore, - root_branch: &mut BranchNode, - first_path_component: PathComponent, - response_sender: Sender>, - ) -> Result<&mut WorkerSender, FileIoError> { - // Find the worker's state corresponding to the first nibble which is stored in an array. - let worker_option = self.workers.get_mut(first_path_component); - - // Create a new worker if it doesn't exist. Not using `get_or_insert_with` with worker_option - // because it is possible to generate a FileIoError within the closure. - match worker_option { - Some(worker) => Ok(worker), - None => Ok(worker_option.insert(ParallelMerkle::create_worker( - pool, - proposal, - root_branch, - first_path_component, - response_sender, - )?)), - } - } - - /// Removes all of the entries in the trie. For the root entry, the value is removed but the - /// root itself will remain. An empty root will only be removed during post processing. - fn remove_all_entries( - &self, - root_branch: &mut BranchNode, - ) -> Result<(), SendError>> { - for worker in self - .workers - .iter() - .filter_map(|(_, worker)| worker.as_ref()) - { - worker.send(BatchOp::DeleteRange { - prefix: Box::default(), // Empty prefix - })?; - } - // Also set the root value to None but does not delete the root. - root_branch.value = None; - Ok(()) - } - - /// The parent thread may receive a `SendError` if the worker that it is sending to has - /// returned to the threadpool after encountering a `FileIoError`. This function should - /// be called after receiving a `SendError` to find and propagate the `FileIoError`. - fn find_fileio_error( - response_receiver: &Receiver>, - ) -> Result<(), FileIoError> { - // Go through the messages in the response channel without blocking to see if we can - // find the FileIoError that caused the worker to close the channel, resulting in a - // send error. If we can find it, then we propagate the FileIoError. Note that - // successful responses can be in the response channel ahead of the FileIoError. - // These are sent from workers that completed their requests without encountering - // a FileIoError. - for result in response_receiver.try_iter() { - let _ = result?; // explicitly ignore the successful Response - } - Ok(()) - } - - /// Creates a parallel proposal in 4 steps: Prepare, Split, Merge, and Post-process. In the - /// Prepare step, the trie is modified to ensure that the root is a branch node with no - /// partial path. In the split step, entries from the batch are sent to workers that - /// independently modify their sub-tries. In the merge step, the sub-tries are merged back - /// to the main trie. Finally, in the post-processing step, the trie is returned to its - /// canonical form. - /// - /// # Errors - /// - /// Returns a `CreateProposalError::FileIoError` if it encounters an error fetching nodes - /// from storage, a `CreateProposalError::SendError` if it is unable to send messages to - /// the workers, and a `CreateProposalError::InvalidConversionToPathComponent` if it is - /// unable to convert a u8 index into a path component. - pub fn create_proposal( - &mut self, - parent: &NodeStore, - batch: impl IntoIterator, - pool: &ThreadPool, - ) -> Result, FileBacked>>, CreateProposalError> { - // Create a mutable nodestore from the parent - let mut mutable_nodestore = NodeStore::new(parent)?; - - // Prepare step: Force the root into a branch with no partial path in preparation for - // performing parallel modifications to the trie. - let mut root_branch = self.force_root(&mut mutable_nodestore)?; - - // Create a response channel the workers use to send messages back to the coordinator (us) - let (response_sender, response_receiver) = mpsc::channel(); - - // Split step: for each operation in the batch, send a request to the worker that is - // responsible for the sub-trie corresponding to the operation's first nibble. - for op in batch.into_iter().map_into_batch() { - // Get the first nibble of the key to determine which worker to send the request to. - // - // Need to handle an empty key. Since the partial_path of the root must be empty, an - // empty key should always be for the root node. There are 3 cases to consider. - // - // Insert: The main thread modifies the value of the root. - // - // Remove: The main thread removes any value at the root. However, it should not delete - // the root node, which, if necessary later, will be done in post processing. - // - // Remove Prefix: - // For a remove prefix, we would need to remove everything. We do this by sending - // a remove prefix with an empty prefix to all of the children, then removing the - // value of the root node. - let mut key_nibbles = NibblesIterator::new(op.key().as_ref()); - let Some(first_path_component) = key_nibbles.next() else { - match &op { - BatchOp::Put { key: _, value } => { - root_branch.value = Some(value.as_ref().into()); - } - BatchOp::Delete { key: _ } => { - root_branch.value = None; - } - BatchOp::DeleteRange { prefix: _ } => { - // Calling remove prefix with an empty prefix is equivalent to a remove all. - if let Err(err) = self.remove_all_entries(&mut root_branch) { - // A send error is most likely due to a worker returning to the thread pool - // after it encountered a FileIoError. Try to find the FileIoError in the - // response channel and return that instead. - ParallelMerkle::find_fileio_error(&response_receiver)?; - return Err(err.into()); - } - } - } - continue; // Done with this empty key operation. - }; - - // Verify that the worker index taken from the first nibble is valid. - let first_path_component = PathComponent::try_new(first_path_component) - .ok_or(CreateProposalError::InvalidConversionToPathComponent)?; - - // Get the worker that is responsible for this nibble. The worker will be created if it - // doesn't already exist. - let worker = self.worker( - pool, - &mutable_nodestore, - &mut root_branch, - first_path_component, - response_sender.clone(), - )?; - - // Send the current operation to the worker. - // TODO: Currently the key from the BatchOp is copied to a Box<[u8]> before it is sent - // to the worker. It may be possible to send a nibble iterator instead of a - // Box<[u8]> to the worker if we use rayon scoped threads. This change would - // eliminate a memory copy but may require some code refactoring. - if let Err(err) = match &op { - BatchOp::Put { key: _, value } => worker.send(BatchOp::Put { - key: op.key().as_ref().into(), - value: value.as_ref().into(), - }), - BatchOp::Delete { key: _ } => worker.send(BatchOp::Delete { - key: op.key().as_ref().into(), - }), - BatchOp::DeleteRange { prefix: _ } => worker.send(BatchOp::DeleteRange { - prefix: op.key().as_ref().into(), - }), - } { - // A send error is most likely due to a worker returning to the thread pool - // after it encountered a FileIoError. Try to find the FileIoError in the - // response channel and return that instead. - ParallelMerkle::find_fileio_error(&response_receiver)?; - return Err(err.into()); - } - } - - // Drop the sender response channel from the parent thread. - drop(response_sender); - - // Setting the workers to default will close the senders to the workers. This will cause the - // workers to send back their responses. - self.workers = Children::default(); - - // Merge step: Collect the results from the workers and merge them as children to the root. - self.merge_children(response_receiver, &mut mutable_nodestore, &mut root_branch)?; - - // Post-process step: return the trie to its canonical form. - *mutable_nodestore.root_mut() = - self.postprocess_trie(&mut mutable_nodestore, root_branch)?; - - let immutable: Arc, FileBacked>> = - Arc::new(mutable_nodestore.try_into()?); - - Ok(immutable) - } -} diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 7a0678bb611b..bc2f883e613b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,7 +2,6 @@ // See the file LICENSE.md for licensing terms. use crate::manager::RevisionManagerError; -use crate::merkle::parallel::CreateProposalError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; use firewood_storage::{FileIoError, TrieHash}; @@ -157,14 +156,6 @@ pub enum Error { /// An invalid root hash was provided #[error(transparent)] InvalidRootHash(#[from] firewood_storage::InvalidTrieHashLength), - - // Error sending to worker - #[error("send error to worker")] - SendErrorToWorker, - - // Error converting a u8 index into a path component - #[error("error converting a u8 index into a path component")] - InvalidConversionToPathComponent, } impl From for Error { @@ -188,18 +179,6 @@ impl From for Error { } } -impl From for Error { - fn from(value: CreateProposalError) -> Self { - match value { - CreateProposalError::FileIoError(err) => Error::FileIO(err), - CreateProposalError::SendError => Error::SendErrorToWorker, - CreateProposalError::InvalidConversionToPathComponent => { - Error::InvalidConversionToPathComponent - } - } - } -} - /// The database interface. The methods here operate on the most /// recently committed revision, and allow the creation of a new /// [`Proposal`] or a new [`DbView`] based on a specific historical diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 5f032b7713d5..29c6df2cb804 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -402,53 +402,6 @@ impl Node { } } } - - /// Force the node (which should be a root of a trie) into a branch with no partial path - /// in preparation for a parallel insert. There are two cases to handle depending on whether - /// the node has a partial path. - /// - /// 1. If the node has a partial path, then create a new node with an empty partial path - /// and a None for a value. Push down the previous node as a child and return the - /// branch. - /// 2. If the existing node does not have a partial path, then there is nothing we need - /// to do if it is a branch. If it is a leaf, then convert it into a branch. - /// - /// # Errors - /// - /// Returns an `Error` if it cannot create a `PathComponent` from a u8 index. - pub fn force_branch_for_insert(mut self) -> Result, Error> { - // If the `partial_path` is non-empty, then create a branch that will be the new - // root with the previous root as the child at the index returned from split_first. - if let Some((child_index, child_path)) = self - .partial_path() - .split_first() - .map(|(index, path)| (*index, path.into())) - { - let mut branch = BranchNode { - partial_path: Path::new(), - value: None, - children: Children::default(), - }; - self.update_partial_path(child_path); - let child_path_component = PathComponent::try_new(child_index) - .ok_or_else(|| Error::other("invalid child index"))?; - *branch.children.get_mut(child_path_component) = Some(Child::Node(self)); - Ok(branch.into()) - } else { - Ok(match self { - Node::Leaf(leaf) => { - // Root is a leaf with an empty partial path. Replace it with a branch. - BranchNode { - partial_path: Path::new(), - value: Some(leaf.value), - children: Children::default(), - } - .into() - } - Node::Branch(branch) => branch, - }) - } - } } /// A path iterator item, which has the key nibbles up to this point, diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 5f70a27edbba..880c421b2941 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -295,16 +295,6 @@ impl NodeStore { self.kind.deleted.push(node); } - /// Take the nodes that have been marked as deleted in this proposal. - pub fn take_deleted_nodes(&mut self) -> Vec { - take(&mut self.kind.deleted) - } - - /// Adds to the nodes deleted in this proposal. - pub fn delete_nodes(&mut self, nodes: &[MaybePersistedNode]) { - self.kind.deleted.extend_from_slice(nodes); - } - /// Reads a node for update, marking it as deleted in this proposal. /// We get an arc from cache (reading it from disk if necessary) then /// copy/clone the node and return it. @@ -324,28 +314,6 @@ impl NodeStore { } } -impl NodeStore { - /// Creates a new [`NodeStore`] from a root node. - #[must_use] - pub fn from_root(parent: &NodeStore, root: Option) -> Self { - NodeStore { - header: parent.header, - kind: MutableProposal { - root, - deleted: Vec::default(), - parent: parent.kind.parent.clone(), - }, - storage: parent.storage.clone(), - } - } - - /// Consumes the `NodeStore` and returns the root of the trie - #[must_use] - pub fn into_root(self) -> Option { - self.kind.root - } -} - impl NodeStore { /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. /// This is used during testing and during the creation of an in-memory merkle for proofs From cab8f5560be926b9de682ba498a5444a90574f40 Mon Sep 17 00:00:00 2001 From: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:41:15 -0400 Subject: [PATCH 0994/1053] fix: Revert "fix: Revert "feat: Parallel updates to Merkle trie (v2)"" (#1374) Reverts ava-labs/firewood#1372 At around 8:30 pm Eastern on Oct 16, 2025 my merged to main PR was failing `firewood-merkle-differential-fuzz`. My PR passed this test prior to merging but it failed consistently with an out of memory error after merging. A revert PR was merged to see if that would fix this issue, but the test continued to fail after merging the revert PR. Around an hour after merging the revert PR, the `firewood-merkle-differential-fuzz` began passing consistently. I've also reran the test at both head and before the revert (commit hash 49ab032c4112f65637f0d8f9dadbe98cd1bca6e7) this morning (Oct 17) and it is passing consistently. I suspect it was a CI runner/infrastructure issue that caused the test to fail. This PR is to revert the revert PR from last night. --- Cargo.lock | 1 + firewood/Cargo.toml | 1 + firewood/src/db.rs | 180 ++++++++ firewood/src/manager.rs | 25 +- firewood/src/{merkle.rs => merkle/mod.rs} | 36 +- firewood/src/merkle/parallel.rs | 473 ++++++++++++++++++++++ firewood/src/v2/api.rs | 21 + storage/src/node/mod.rs | 47 +++ storage/src/nodestore/mod.rs | 32 ++ 9 files changed, 810 insertions(+), 6 deletions(-) rename firewood/src/{merkle.rs => merkle/mod.rs} (97%) create mode 100644 firewood/src/merkle/parallel.rs diff --git a/Cargo.lock b/Cargo.lock index b3229e98ad1c..1174a5421fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,6 +1058,7 @@ dependencies = [ "plain_hasher", "pprof", "rand 0.9.2", + "rayon", "rlp", "sha3", "tempfile", diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index ff8a61651fe6..d44d06d1be18 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -35,6 +35,7 @@ metrics.workspace = true thiserror.workspace = true # Regular dependencies typed-builder = "0.22.0" +rayon = "1.11.0" [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index c1c6f57ad778..29f4586ce3d0 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -27,6 +27,8 @@ use std::sync::Arc; use thiserror::Error; use typed_builder::TypedBuilder; +use crate::merkle::parallel::ParallelMerkle; + #[derive(Error, Debug)] #[non_exhaustive] /// Represents the different types of errors that can occur in the database. @@ -200,6 +202,26 @@ impl Db { self.manager.view(root_hash).map_err(Into::into) } + /// Propose a new batch that is processed in parallel. + /// + /// # Panics + /// + /// Panics if the revision manager cannot create a thread pool. + pub fn propose_parallel( + &self, + batch: impl IntoIterator, + ) -> Result, api::Error> { + let parent = self.manager.current_revision(); + let mut parallel_merkle = ParallelMerkle::default(); + let immutable = + parallel_merkle.create_proposal(&parent, batch, self.manager.threadpool())?; + self.manager.add_proposal(immutable.clone()); + Ok(Proposal { + nodestore: immutable, + db: self, + }) + } + /// Dump the Trie of the latest revision. pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); @@ -555,6 +577,164 @@ mod test { assert_eq!(&*value, b"proposal_value"); } + #[test] + fn test_propose_parallel_reopen() { + fn insert_commit(db: &TestDb, kv: u8) { + let keys: Vec<[u8; 1]> = vec![[kv; 1]]; + let vals: Vec> = vec![Box::new([kv; 1])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + } + + // Create, insert, close, open, insert + let db = TestDb::new(); + insert_commit(&db, 1); + let db = db.reopen(); + insert_commit(&db, 2); + // Check that the keys are still there after the commits + let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); + let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; + let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&committed.val(k).unwrap().unwrap(), v); + } + drop(db); + + // Open-db1, insert, open-db2, insert + let db1 = TestDb::new(); + insert_commit(&db1, 1); + let db2 = TestDb::new(); + insert_commit(&db2, 2); + let committed1 = db1.revision(db1.root_hash().unwrap().unwrap()).unwrap(); + let committed2 = db2.revision(db2.root_hash().unwrap().unwrap()).unwrap(); + let keys: Vec<[u8; 1]> = vec![[1; 1], [2; 1]]; + let vals: Vec> = vec![Box::new([1; 1]), Box::new([2; 1])]; + let mut kviter = keys.iter().zip(vals.iter()); + let (k, v) = kviter.next().unwrap(); + assert_eq!(&committed1.val(k).unwrap().unwrap(), v); + let (k, v) = kviter.next().unwrap(); + assert_eq!(&committed2.val(k).unwrap().unwrap(), v); + } + + #[test] + fn test_propose_parallel() { + const N: usize = 100; + let db = TestDb::new(); + + // Test an empty proposal + let keys: Vec<[u8; 0]> = Vec::new(); + let vals: Vec> = Vec::new(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + + // Create a proposal consisting of a single entry and an empty key. + let keys: Vec<[u8; 0]> = vec![[0; 0]]; + + // Note that if the value is [], then it is interpreted as a DeleteRange. + // Instead, set value to [0] + let vals: Vec> = vec![Box::new([0; 1])]; + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + + // Check that the key is still there after the commit + let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&committed.val(k).unwrap().unwrap(), v); + } + + // Create a proposal that deletes the previous entry + let vals: Vec> = vec![Box::new([0; 0])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + let kviter = keys.iter().zip(vals.iter()); + for (k, _v) in kviter { + assert_eq!(proposal.val(k).unwrap(), None); + } + proposal.commit().unwrap(); + + // Create a proposal that inserts 0 to 999 + let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) + .map(|i| { + ( + format!("key{i}").into_bytes(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + + // Create a proposal that deletes all of the even entries + let (keys, vals): (Vec<_>, Vec<_>) = (0..1000) + .filter_map(|i| { + if i % 2 != 0 { + Some::<(Vec, Box<[u8]>)>((format!("key{i}").into_bytes(), Box::new([]))) + } else { + None + } + }) + .unzip(); + + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + let kviter = keys.iter().zip(vals.iter()); + for (k, _v) in kviter { + assert_eq!(proposal.val(k).unwrap(), None); + } + proposal.commit().unwrap(); + + // Create a proposal that deletes using empty prefix + let keys: Vec<[u8; 0]> = vec![[0; 0]]; + let vals: Vec> = vec![Box::new([0; 0])]; + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + proposal.commit().unwrap(); + + // Create N keys and values like (key0, value0)..(keyN, valueN) + let rng = firewood_storage::SeededRng::from_env_or_random(); + let (keys, vals): (Vec<_>, Vec<_>) = (0..N) + .map(|i| { + ( + rng.random::<[u8; 32]>(), + Box::from(format!("value{i}").as_bytes()), + ) + }) + .unzip(); + + // Looping twice to test that we are reusing the thread pool. + for _ in 0..2 { + let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let proposal = db.propose_parallel(kviter).unwrap(); + + // iterate over the keys and values again, checking that the values are in the correct proposal + let kviter = keys.iter().zip(vals.iter()); + + for (k, v) in kviter { + assert_eq!(&proposal.val(k).unwrap().unwrap(), v); + } + proposal.commit().unwrap(); + } + } + /// Test that proposing on a proposal works as expected /// /// Test creates two batches and proposes them, and verifies that the values are in the correct proposal. diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 5d2dc9bd616f..6fa9e214a400 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -13,10 +13,11 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex, OnceLock, RwLock}; use firewood_storage::logger::{trace, warn}; use metrics::gauge; +use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; @@ -24,7 +25,8 @@ use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ - Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, TrieHash, + BranchNode, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, + TrieHash, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TypedBuilder)] @@ -75,6 +77,7 @@ pub(crate) struct RevisionManager { proposals: Mutex>, // committing_proposals: VecDeque>, by_hash: RwLock>, + threadpool: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -115,6 +118,7 @@ impl RevisionManager { by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), + threadpool: OnceLock::new(), }; if let Some(hash) = nodestore.root_hash().or_default_root_hash() { @@ -309,6 +313,23 @@ impl RevisionManager { .expect("there is always one revision") .clone() } + + /// Gets or creates a threadpool associated with the revision manager. + /// + /// # Panics + /// + /// Panics if the it cannot create a thread pool. + pub fn threadpool(&self) -> &ThreadPool { + // Note that OnceLock currently doesn't support get_or_try_init (it is available in a + // nightly release). The get_or_init should be replaced with get_or_try_init once it + // is available to allow the error to be passed back to the caller. + self.threadpool.get_or_init(|| { + ThreadPoolBuilder::new() + .num_threads(BranchNode::MAX_CHILDREN) + .build() + .expect("Error in creating threadpool") + }) + } } #[cfg(test)] diff --git a/firewood/src/merkle.rs b/firewood/src/merkle/mod.rs similarity index 97% rename from firewood/src/merkle.rs rename to firewood/src/merkle/mod.rs index a27d298c389c..366cb0390a03 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle/mod.rs @@ -4,6 +4,9 @@ #[cfg(test)] pub(crate) mod tests; +/// Parallel merkle +pub mod parallel; + use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; @@ -593,10 +596,17 @@ impl Merkle> { /// Map `key` to `value` in the trie. /// Each element of key is 2 nibbles. pub fn insert(&mut self, key: &[u8], value: Value) -> Result<(), FileIoError> { - let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); + self.insert_from_iter(NibblesIterator::new(key), value) + } + /// Map `key` to `value` in the trie when `key` is a `NibblesIterator` + pub fn insert_from_iter( + &mut self, + key: NibblesIterator<'_>, + value: Value, + ) -> Result<(), FileIoError> { + let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); - let Some(root_node) = std::mem::take(root) else { // The trie is empty. Create a new leaf node with `value` and set // it as the root. @@ -751,8 +761,18 @@ impl Merkle> { /// Otherwise returns `None`. /// Each element of `key` is 2 nibbles. pub fn remove(&mut self, key: &[u8]) -> Result, FileIoError> { - let key = Path::from_nibbles_iterator(NibblesIterator::new(key)); + self.remove_from_iter(NibblesIterator::new(key)) + } + /// Removes the value associated with the given `key` where `key` is a `NibblesIterator` + /// Returns the value that was removed, if any. + /// Otherwise returns `None`. + /// Each element of `key` is 2 nibbles. + pub fn remove_from_iter( + &mut self, + key: NibblesIterator<'_>, + ) -> Result, FileIoError> { + let key = Path::from_nibbles_iterator(key); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. @@ -842,8 +862,16 @@ impl Merkle> { /// Removes any key-value pairs with keys that have the given `prefix`. /// Returns the number of key-value pairs removed. pub fn remove_prefix(&mut self, prefix: &[u8]) -> Result { - let prefix = Path::from_nibbles_iterator(NibblesIterator::new(prefix)); + self.remove_prefix_from_iter(NibblesIterator::new(prefix)) + } + /// Removes any key-value pairs with keys that have the given `prefix` where `prefix` is a `NibblesIterator` + /// Returns the number of key-value pairs removed. + pub fn remove_prefix_from_iter( + &mut self, + prefix: NibblesIterator<'_>, + ) -> Result { + let prefix = Path::from_nibbles_iterator(prefix); let root = self.nodestore.root_mut(); let Some(root_node) = std::mem::take(root) else { // The trie is empty. There is nothing to remove. diff --git a/firewood/src/merkle/parallel.rs b/firewood/src/merkle/parallel.rs new file mode 100644 index 000000000000..7514a3894ff5 --- /dev/null +++ b/firewood/src/merkle/parallel.rs @@ -0,0 +1,473 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::db::BatchOp; +use crate::merkle::{Key, Merkle, Value}; +use crate::v2::api::KeyValuePairIter; +use firewood_storage::logger::error; +use firewood_storage::{ + BranchNode, Child, Children, FileBacked, FileIoError, ImmutableProposal, LeafNode, + MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeReader, NodeStore, Parentable, + Path, PathComponent, +}; +use rayon::ThreadPool; +use std::iter::once; +use std::ops::Deref; +use std::sync::mpsc::{Receiver, SendError, Sender}; +use std::sync::{Arc, mpsc}; + +#[derive(Debug)] +struct WorkerSender(mpsc::Sender>); + +impl std::ops::Deref for WorkerSender { + type Target = mpsc::Sender>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Response returned from a worker to the main thread. Includes the new root of the subtrie +/// at the given first path component and the deleted nodes. +#[derive(Debug)] +struct Response { + first_path_component: PathComponent, + root: Option, + deleted_nodes: Vec, +} + +#[derive(Debug)] +pub enum CreateProposalError { + FileIoError(FileIoError), + SendError, + InvalidConversionToPathComponent, +} + +impl From for CreateProposalError { + fn from(err: FileIoError) -> Self { + CreateProposalError::FileIoError(err) + } +} + +impl From>> for CreateProposalError { + fn from(_err: SendError>) -> Self { + CreateProposalError::SendError + } +} + +/// `ParallelMerkle` safely performs parallel modifications to a Merkle trie. It does this +/// by creating a worker for each subtrie from the root, and allowing the the workers to +/// perform inserts and removes to their subtries. +#[derive(Debug, Default)] +pub struct ParallelMerkle { + workers: Children>, +} + +impl ParallelMerkle { + /// Force the root (if necessary) into a branch with no partial path to allow the clean + /// separation of the trie into an array of subtries that can be operated on independently + /// by the worker threads. + fn force_root( + &self, + proposal: &mut NodeStore, + ) -> Result, CreateProposalError> { + // There are 3 different cases to handle depending on the value of the root node. + // + // 1. If root is None, create a branch node with an empty partial path and a None for + // value. Create Nones for all of its children. + // 2. If the existing root has a partial path, then create a new root with an empty + // partial path and a None for a value. Push down the previous root as a child. Note + // that this modified Merkle trie is not currently valid and may need to be updated + // during the post-processing step. + // 3. If the existing root does not have a partial path, then there is nothing we need + // to do if it is a branch. If it is a leaf, then convert it into a branch. + // + // Cases 2 and 3 are handled by `force_branch_for_insert`. This function returns a branch + // node with an empty partial path. + proposal.root_mut().take().map_or_else( + || { + // Empty trie. Create a branch node with an empty partial path and a None for a value. + Ok(BranchNode { + partial_path: Path::default(), + value: None, + children: Children::default(), + } + .into()) + }, + |node| { + // Returns an error if it cannot convert a child index into a path component. + node.force_branch_for_insert() + .map_err(|_| CreateProposalError::InvalidConversionToPathComponent) + }, + ) + } + + /// After performing parallel modifications, it may be necessary to perform post processing to + /// return the Merkle trie to the correct canonical form. This involves checking if the Merkle + /// trie has an extra root node. If it does, apply a transform to return the trie to a valid + /// state by following the steps below: + /// + /// If the root node has: + /// 0 children and no value, the trie is empty. Just delete the root. + /// 0 children and a value (from an empty key), the root should be a leaf + /// 1 child and no value, the child should be the root (need to update partial path) + /// In all other cases, the root is already correct. + fn postprocess_trie( + &self, + nodestore: &mut NodeStore, + mut branch: Box, + ) -> Result, FileIoError> { + let mut children_iter = branch + .children + .iter_mut() + .filter_map(|(index, child)| child.as_mut().map(|child| (index, child))); + + let first_child = children_iter.next(); + match first_child { + None => { + Ok(branch.value.map(|value| { + // There is a value for the empty key. Create a leaf with the value and return. + Node::Leaf(LeafNode { + value, + partial_path: Path::default(), // Partial path should be empty + }) + })) + } + Some((child_index, child)) => { + // Check if the root has a value or if there is more than one child. If yes, then + // just return the root unmodified + if branch.value.is_some() || children_iter.next().is_some() { + drop(children_iter); + return Ok(Some(Node::Branch(branch))); + } + + // Return the child as the new root. Update its partial path to include the index value. + let mut child = match child { + Child::Node(child_node) => std::mem::take(child_node), + Child::AddressWithHash(addr, _) => nodestore.read_for_update((*addr).into())?, + Child::MaybePersisted(maybe_persisted, _) => { + nodestore.read_for_update(maybe_persisted.clone())? + } + }; + + // The child's partial path is the concatenation of its (now removed) parent, which + // should always be empty because of our prepare step, its (former) child index, and + // its partial path. Because the parent's partial path should always be empty, we + // can omit it and start with the `child_index`. + let partial_path = Path::from_nibbles_iterator( + once(child_index.as_u8()).chain(child.partial_path().iter().copied()), + ); + child.update_partial_path(partial_path); + Ok(Some(child)) + } + } + } + + /// Call by a worker to processes requests from `child_receiver` and send back a response on + /// `response_sender` once the main thread closes the child sender. + fn worker_event_loop( + mut merkle: Merkle>, + first_path_component: PathComponent, + child_receiver: Receiver>, + response_sender: Sender>, + ) -> Result<(), Box>>> { + // Wait for a message on the receiver child channel. Break out of loop when the sender has + // closed the child sender. + while let Ok(request) = child_receiver.recv() { + if let Err(err) = match request { + // insert a key-value pair into the subtrie + BatchOp::Put { key, value } => { + let mut nibbles_iter = NibblesIterator::new(&key); + nibbles_iter.next(); // Skip the first nibble + merkle.insert_from_iter(nibbles_iter, value) + } + BatchOp::Delete { key } => { + let mut nibbles_iter = NibblesIterator::new(&key); + nibbles_iter.next(); // Skip the first nibble + merkle.remove_from_iter(nibbles_iter).map(|_| ()) + } + BatchOp::DeleteRange { prefix } => { + let mut nibbles_iter = NibblesIterator::new(&prefix); + nibbles_iter.next(); // Skip the first nibble + merkle.remove_prefix_from_iter(nibbles_iter).map(|_| ()) + } + } { + response_sender.send(Err(err))?; + break; // Stop handling additional requests + } + } + // The main thread has closed the channel. Send back the worker's response. + let mut nodestore = merkle.into_inner(); + let response = Response { + first_path_component, + root: nodestore.root_mut().take().map(Child::Node), + deleted_nodes: nodestore.take_deleted_nodes(), + }; + response_sender.send(Ok(response))?; + Ok(()) + } + + /// Creates a worker for performing operations on a subtrie, with the subtrie being determined + /// by the value of the `first_path_component`. + fn create_worker( + pool: &ThreadPool, + proposal: &NodeStore, + root_branch: &mut BranchNode, + first_path_component: PathComponent, + response_sender: Sender>, + ) -> Result { + // Create a channel for the coordinator (main thread) to send messages to this worker. + let (child_sender, child_receiver) = mpsc::channel(); + + // The root's child becomes the root node of the worker + let child_root = root_branch + .children + .get_mut(first_path_component) + .take() + .map(|child| match child { + Child::Node(node) => Ok(node), + Child::AddressWithHash(address, _) => { + Ok(proposal.read_node(address)?.deref().clone()) + } + Child::MaybePersisted(maybe_persisted, _) => { + Ok(maybe_persisted.as_shared_node(proposal)?.deref().clone()) + } + }) + .transpose()?; + + // Build a nodestore from the child node + let worker_nodestore = NodeStore::from_root(proposal, child_root); + + // Spawn a worker from the threadpool for this nibble. The worker will send messages to the coordinator + // using `worker_sender`. + pool.spawn(move || { + if let Err(err) = ParallelMerkle::worker_event_loop( + Merkle::from(worker_nodestore), + first_path_component, + child_receiver, + response_sender, + ) { + error!("Worker cannot send to main thread using response channel: {err:?}"); + } + }); + Ok(WorkerSender(child_sender)) + } + + // Collect responses from the workers, each representing the root of a subtrie and merge them into the + // root node of the main trie. + fn merge_children( + &mut self, + response_channel: Receiver>, + proposal: &mut NodeStore, + root_branch: &mut BranchNode, + ) -> Result<(), FileIoError> { + while let Ok(response) = response_channel.recv() { + match response { + Ok(response) => { + // Adding deleted nodes (from calling read_for_update) from the child's nodestore. + proposal.delete_nodes(response.deleted_nodes.as_slice()); + + // Set the child at index to response.root which is the root of the child's subtrie. + *root_branch.children.get_mut(response.first_path_component) = response.root; + } + Err(err) => { + return Err(err); // Early termination. + } + } + } + Ok(()) + } + + /// Get a worker from the worker pool based on the `first_path_component` value. Create a worker if + /// it doesn't exist already. + fn worker( + &mut self, + pool: &ThreadPool, + proposal: &NodeStore, + root_branch: &mut BranchNode, + first_path_component: PathComponent, + response_sender: Sender>, + ) -> Result<&mut WorkerSender, FileIoError> { + // Find the worker's state corresponding to the first nibble which is stored in an array. + let worker_option = self.workers.get_mut(first_path_component); + + // Create a new worker if it doesn't exist. Not using `get_or_insert_with` with worker_option + // because it is possible to generate a FileIoError within the closure. + match worker_option { + Some(worker) => Ok(worker), + None => Ok(worker_option.insert(ParallelMerkle::create_worker( + pool, + proposal, + root_branch, + first_path_component, + response_sender, + )?)), + } + } + + /// Removes all of the entries in the trie. For the root entry, the value is removed but the + /// root itself will remain. An empty root will only be removed during post processing. + fn remove_all_entries( + &self, + root_branch: &mut BranchNode, + ) -> Result<(), SendError>> { + for worker in self + .workers + .iter() + .filter_map(|(_, worker)| worker.as_ref()) + { + worker.send(BatchOp::DeleteRange { + prefix: Box::default(), // Empty prefix + })?; + } + // Also set the root value to None but does not delete the root. + root_branch.value = None; + Ok(()) + } + + /// The parent thread may receive a `SendError` if the worker that it is sending to has + /// returned to the threadpool after encountering a `FileIoError`. This function should + /// be called after receiving a `SendError` to find and propagate the `FileIoError`. + fn find_fileio_error( + response_receiver: &Receiver>, + ) -> Result<(), FileIoError> { + // Go through the messages in the response channel without blocking to see if we can + // find the FileIoError that caused the worker to close the channel, resulting in a + // send error. If we can find it, then we propagate the FileIoError. Note that + // successful responses can be in the response channel ahead of the FileIoError. + // These are sent from workers that completed their requests without encountering + // a FileIoError. + for result in response_receiver.try_iter() { + let _ = result?; // explicitly ignore the successful Response + } + Ok(()) + } + + /// Creates a parallel proposal in 4 steps: Prepare, Split, Merge, and Post-process. In the + /// Prepare step, the trie is modified to ensure that the root is a branch node with no + /// partial path. In the split step, entries from the batch are sent to workers that + /// independently modify their sub-tries. In the merge step, the sub-tries are merged back + /// to the main trie. Finally, in the post-processing step, the trie is returned to its + /// canonical form. + /// + /// # Errors + /// + /// Returns a `CreateProposalError::FileIoError` if it encounters an error fetching nodes + /// from storage, a `CreateProposalError::SendError` if it is unable to send messages to + /// the workers, and a `CreateProposalError::InvalidConversionToPathComponent` if it is + /// unable to convert a u8 index into a path component. + pub fn create_proposal( + &mut self, + parent: &NodeStore, + batch: impl IntoIterator, + pool: &ThreadPool, + ) -> Result, FileBacked>>, CreateProposalError> { + // Create a mutable nodestore from the parent + let mut mutable_nodestore = NodeStore::new(parent)?; + + // Prepare step: Force the root into a branch with no partial path in preparation for + // performing parallel modifications to the trie. + let mut root_branch = self.force_root(&mut mutable_nodestore)?; + + // Create a response channel the workers use to send messages back to the coordinator (us) + let (response_sender, response_receiver) = mpsc::channel(); + + // Split step: for each operation in the batch, send a request to the worker that is + // responsible for the sub-trie corresponding to the operation's first nibble. + for op in batch.into_iter().map_into_batch() { + // Get the first nibble of the key to determine which worker to send the request to. + // + // Need to handle an empty key. Since the partial_path of the root must be empty, an + // empty key should always be for the root node. There are 3 cases to consider. + // + // Insert: The main thread modifies the value of the root. + // + // Remove: The main thread removes any value at the root. However, it should not delete + // the root node, which, if necessary later, will be done in post processing. + // + // Remove Prefix: + // For a remove prefix, we would need to remove everything. We do this by sending + // a remove prefix with an empty prefix to all of the children, then removing the + // value of the root node. + let mut key_nibbles = NibblesIterator::new(op.key().as_ref()); + let Some(first_path_component) = key_nibbles.next() else { + match &op { + BatchOp::Put { key: _, value } => { + root_branch.value = Some(value.as_ref().into()); + } + BatchOp::Delete { key: _ } => { + root_branch.value = None; + } + BatchOp::DeleteRange { prefix: _ } => { + // Calling remove prefix with an empty prefix is equivalent to a remove all. + if let Err(err) = self.remove_all_entries(&mut root_branch) { + // A send error is most likely due to a worker returning to the thread pool + // after it encountered a FileIoError. Try to find the FileIoError in the + // response channel and return that instead. + ParallelMerkle::find_fileio_error(&response_receiver)?; + return Err(err.into()); + } + } + } + continue; // Done with this empty key operation. + }; + + // Verify that the worker index taken from the first nibble is valid. + let first_path_component = PathComponent::try_new(first_path_component) + .ok_or(CreateProposalError::InvalidConversionToPathComponent)?; + + // Get the worker that is responsible for this nibble. The worker will be created if it + // doesn't already exist. + let worker = self.worker( + pool, + &mutable_nodestore, + &mut root_branch, + first_path_component, + response_sender.clone(), + )?; + + // Send the current operation to the worker. + // TODO: Currently the key from the BatchOp is copied to a Box<[u8]> before it is sent + // to the worker. It may be possible to send a nibble iterator instead of a + // Box<[u8]> to the worker if we use rayon scoped threads. This change would + // eliminate a memory copy but may require some code refactoring. + if let Err(err) = match &op { + BatchOp::Put { key: _, value } => worker.send(BatchOp::Put { + key: op.key().as_ref().into(), + value: value.as_ref().into(), + }), + BatchOp::Delete { key: _ } => worker.send(BatchOp::Delete { + key: op.key().as_ref().into(), + }), + BatchOp::DeleteRange { prefix: _ } => worker.send(BatchOp::DeleteRange { + prefix: op.key().as_ref().into(), + }), + } { + // A send error is most likely due to a worker returning to the thread pool + // after it encountered a FileIoError. Try to find the FileIoError in the + // response channel and return that instead. + ParallelMerkle::find_fileio_error(&response_receiver)?; + return Err(err.into()); + } + } + + // Drop the sender response channel from the parent thread. + drop(response_sender); + + // Setting the workers to default will close the senders to the workers. This will cause the + // workers to send back their responses. + self.workers = Children::default(); + + // Merge step: Collect the results from the workers and merge them as children to the root. + self.merge_children(response_receiver, &mut mutable_nodestore, &mut root_branch)?; + + // Post-process step: return the trie to its canonical form. + *mutable_nodestore.root_mut() = + self.postprocess_trie(&mut mutable_nodestore, root_branch)?; + + let immutable: Arc, FileBacked>> = + Arc::new(mutable_nodestore.try_into()?); + + Ok(immutable) + } +} diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index bc2f883e613b..7a0678bb611b 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -2,6 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::manager::RevisionManagerError; +use crate::merkle::parallel::CreateProposalError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; use firewood_storage::{FileIoError, TrieHash}; @@ -156,6 +157,14 @@ pub enum Error { /// An invalid root hash was provided #[error(transparent)] InvalidRootHash(#[from] firewood_storage::InvalidTrieHashLength), + + // Error sending to worker + #[error("send error to worker")] + SendErrorToWorker, + + // Error converting a u8 index into a path component + #[error("error converting a u8 index into a path component")] + InvalidConversionToPathComponent, } impl From for Error { @@ -179,6 +188,18 @@ impl From for Error { } } +impl From for Error { + fn from(value: CreateProposalError) -> Self { + match value { + CreateProposalError::FileIoError(err) => Error::FileIO(err), + CreateProposalError::SendError => Error::SendErrorToWorker, + CreateProposalError::InvalidConversionToPathComponent => { + Error::InvalidConversionToPathComponent + } + } + } +} + /// The database interface. The methods here operate on the most /// recently committed revision, and allow the creation of a new /// [`Proposal`] or a new [`DbView`] based on a specific historical diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 29c6df2cb804..5f032b7713d5 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -402,6 +402,53 @@ impl Node { } } } + + /// Force the node (which should be a root of a trie) into a branch with no partial path + /// in preparation for a parallel insert. There are two cases to handle depending on whether + /// the node has a partial path. + /// + /// 1. If the node has a partial path, then create a new node with an empty partial path + /// and a None for a value. Push down the previous node as a child and return the + /// branch. + /// 2. If the existing node does not have a partial path, then there is nothing we need + /// to do if it is a branch. If it is a leaf, then convert it into a branch. + /// + /// # Errors + /// + /// Returns an `Error` if it cannot create a `PathComponent` from a u8 index. + pub fn force_branch_for_insert(mut self) -> Result, Error> { + // If the `partial_path` is non-empty, then create a branch that will be the new + // root with the previous root as the child at the index returned from split_first. + if let Some((child_index, child_path)) = self + .partial_path() + .split_first() + .map(|(index, path)| (*index, path.into())) + { + let mut branch = BranchNode { + partial_path: Path::new(), + value: None, + children: Children::default(), + }; + self.update_partial_path(child_path); + let child_path_component = PathComponent::try_new(child_index) + .ok_or_else(|| Error::other("invalid child index"))?; + *branch.children.get_mut(child_path_component) = Some(Child::Node(self)); + Ok(branch.into()) + } else { + Ok(match self { + Node::Leaf(leaf) => { + // Root is a leaf with an empty partial path. Replace it with a branch. + BranchNode { + partial_path: Path::new(), + value: Some(leaf.value), + children: Children::default(), + } + .into() + } + Node::Branch(branch) => branch, + }) + } + } } /// A path iterator item, which has the key nibbles up to this point, diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 880c421b2941..5f70a27edbba 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -295,6 +295,16 @@ impl NodeStore { self.kind.deleted.push(node); } + /// Take the nodes that have been marked as deleted in this proposal. + pub fn take_deleted_nodes(&mut self) -> Vec { + take(&mut self.kind.deleted) + } + + /// Adds to the nodes deleted in this proposal. + pub fn delete_nodes(&mut self, nodes: &[MaybePersistedNode]) { + self.kind.deleted.extend_from_slice(nodes); + } + /// Reads a node for update, marking it as deleted in this proposal. /// We get an arc from cache (reading it from disk if necessary) then /// copy/clone the node and return it. @@ -314,6 +324,28 @@ impl NodeStore { } } +impl NodeStore { + /// Creates a new [`NodeStore`] from a root node. + #[must_use] + pub fn from_root(parent: &NodeStore, root: Option) -> Self { + NodeStore { + header: parent.header, + kind: MutableProposal { + root, + deleted: Vec::default(), + parent: parent.kind.parent.clone(), + }, + storage: parent.storage.clone(), + } + } + + /// Consumes the `NodeStore` and returns the root of the trie + #[must_use] + pub fn into_root(self) -> Option { + self.kind.root + } +} + impl NodeStore { /// Creates a new, empty, [`NodeStore`] and clobbers the underlying `storage` with an empty header. /// This is used during testing and during the creation of an in-memory merkle for proofs From 54b9828b69e02932e216e55c73ec89b2b5238cc2 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 17 Oct 2025 16:39:44 -0700 Subject: [PATCH 0995/1053] ci: print ulimits and set memlock to unlimited before fuzz tests (#1375) --- .github/workflows/ci.yaml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d9887cbf9b6d..3274a7a81f49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -267,7 +267,14 @@ jobs: run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash go test -race ./... - name: Test Firewood <> Ethereum Differential Fuzz working-directory: ffi/tests/eth - run: go test -fuzz=. -fuzztime=1m + run: | + # Set memlock to unlimited for the shell invoking the fuzz tests + ulimit -Sa + ulimit -Ha + sudo prlimit --pid $$ --memlock=-1:-1 || echo "prlimit not available, locking kernel memory may fail when setting up io-uring" + ulimit -Sa + ulimit -Ha + go test -fuzz=. -fuzztime=1m - name: Upload Fuzz testdata if: failure() uses: actions/upload-artifact@v4 @@ -295,7 +302,14 @@ jobs: cache-dependency-path: "ffi/tests/firewood/go.sum" - name: Test Firewood <> MerkleDB Differential fuzz working-directory: ffi/tests/firewood - run: go test -fuzz=. -fuzztime=1m + run: | + # Set memlock to unlimited for the shell invoking the fuzz tests + ulimit -Sa + ulimit -Ha + sudo prlimit --pid $$ --memlock=-1:-1 || echo "prlimit not available, locking kernel memory may fail when setting up io-uring" + ulimit -Sa + ulimit -Ha + go test -fuzz=. -fuzztime=1m - name: Upload Fuzz testdata if: failure() uses: actions/upload-artifact@v4 From a4089bd64371c660e261e6462bc4d4d3b5312d86 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:15:50 -0400 Subject: [PATCH 0996/1053] chore(db/manager): add `MockStore` (#1346) As part of #1299, this PR introduces the `RootStore` trait and makes both the revision manager and database generic over `RootStore`. The revision manager, in particular, is modified to make calls to its `RootStore` as described in the `RootStore` design doc. It's expected that the archival state datastore will implement this `RootStore` trait in production. This PR also introduces two implementations of `RootStore`: - `NoOpStore`: a no-op implementation of `RootStore` that is used in all contexts outside of testing - `MockStore`: a mock implementation of `RootStore` that is used for testing the `RootStore` API. This PR also introduces two database tests to test the `RootStore` API. --------- Co-authored-by: Brandon LeBlanc --- ffi/src/handle.rs | 3 +- ffi/src/proposal.rs | 13 ++- firewood/src/db.rs | 182 ++++++++++++++++++++++++++++--------- firewood/src/lib.rs | 3 + firewood/src/manager.rs | 140 +++++++++++++++++++--------- firewood/src/root_store.rs | 105 +++++++++++++++++++++ firewood/src/v2/api.rs | 15 ++- 7 files changed, 368 insertions(+), 93 deletions(-) create mode 100644 firewood/src/root_store.rs diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index b4061978b9ae..73c7f9f8805c 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,6 +4,7 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, + root_store::NoOpStore, v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType}, }; @@ -230,7 +231,7 @@ impl<'db> CView<'db> for &'db crate::DatabaseHandle { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error> { + ) -> Result, api::Error> { self.db.propose(values.as_ref().iter()) } } diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 1281a9993539..0ed971df297c 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,7 +1,10 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; +use firewood::{ + root_store::NoOpStore, + v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}, +}; use crate::value::KeyValuePair; @@ -13,13 +16,13 @@ use metrics::counter; #[derive(Debug)] pub struct ProposalHandle<'db> { hash_key: Option, - proposal: firewood::db::Proposal<'db>, + proposal: firewood::db::Proposal<'db, NoOpStore>, handle: &'db crate::DatabaseHandle, } impl<'db> DbView for ProposalHandle<'db> { type Iter<'view> - = as DbView>::Iter<'view> + = as DbView>::Iter<'view> where Self: 'view; @@ -143,7 +146,7 @@ pub trait CView<'db> { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error>; + ) -> Result, api::Error>; /// Create a [`ProposalHandle`] from the values and return it with timing /// information. @@ -188,7 +191,7 @@ impl<'db> CView<'db> for &ProposalHandle<'db> { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error> { + ) -> Result, api::Error> { self.proposal.propose(values.as_ref()) } } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 29f4586ce3d0..59f778df56a7 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -8,6 +8,7 @@ use crate::iter::MerkleKeyValueIter; use crate::merkle::{Merkle, Value}; +use crate::root_store::{NoOpStore, RootStore}; pub use crate::v2::api::BatchOp; use crate::v2::api::{ self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair, @@ -112,16 +113,16 @@ pub struct DbConfig { #[derive(Debug)] /// A database instance. -pub struct Db { +pub struct Db { metrics: Arc, - manager: RevisionManager, + manager: RevisionManager, } -impl api::Db for Db { +impl api::Db for Db { type Historical = NodeStore; type Proposal<'db> - = Proposal<'db> + = Proposal<'db, RS> where Self: 'db; @@ -180,9 +181,19 @@ impl api::Db for Db { } } -impl Db { +impl Db { /// Create a new database instance. pub fn new>(db_path: P, cfg: DbConfig) -> Result { + Self::with_root_store(db_path, cfg, NoOpStore {}) + } +} + +impl Db { + fn with_root_store>( + db_path: P, + cfg: DbConfig, + root_store: RS, + ) -> Result { let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals"), }); @@ -192,7 +203,9 @@ impl Db { .truncate(cfg.truncate) .manager(cfg.manager) .build(); - let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager)?; + + let manager = + RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager, root_store)?; let db = Self { metrics, manager }; Ok(db) } @@ -201,6 +214,26 @@ impl Db { pub fn view(&self, root_hash: HashKey) -> Result { self.manager.view(root_hash).map_err(Into::into) } +} + +impl Db { + /// Dump the Trie of the latest revision. + pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { + let latest_rev_nodestore = self.manager.current_revision(); + let merkle = Merkle::from(latest_rev_nodestore); + merkle.dump(w).map_err(std::io::Error::other) + } + + /// Get a copy of the database metrics + pub fn metrics(&self) -> Arc { + self.metrics.clone() + } + + /// Check the database for consistency + pub fn check(&self, opt: CheckOpt) -> CheckerReport { + let latest_rev_nodestore = self.manager.current_revision(); + latest_rev_nodestore.check(opt) + } /// Propose a new batch that is processed in parallel. /// @@ -210,7 +243,7 @@ impl Db { pub fn propose_parallel( &self, batch: impl IntoIterator, - ) -> Result, api::Error> { + ) -> Result, api::Error> { let parent = self.manager.current_revision(); let mut parallel_merkle = ParallelMerkle::default(); let immutable = @@ -221,34 +254,16 @@ impl Db { db: self, }) } - - /// Dump the Trie of the latest revision. - pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { - let latest_rev_nodestore = self.manager.current_revision(); - let merkle = Merkle::from(latest_rev_nodestore); - merkle.dump(w).map_err(std::io::Error::other) - } - - /// Get a copy of the database metrics - pub fn metrics(&self) -> Arc { - self.metrics.clone() - } - - /// Check the database for consistency - pub fn check(&self, opt: CheckOpt) -> CheckerReport { - let latest_rev_nodestore = self.manager.current_revision(); - latest_rev_nodestore.check(opt) - } } #[derive(Debug)] /// A user-visible database proposal -pub struct Proposal<'db> { +pub struct Proposal<'db, RS> { nodestore: Arc, FileBacked>>, - db: &'db Db, + db: &'db Db, } -impl api::DbView for Proposal<'_> { +impl api::DbView for Proposal<'_, RS> { type Iter<'view> = MerkleKeyValueIter<'view, NodeStore, FileBacked>> where @@ -280,8 +295,8 @@ impl api::DbView for Proposal<'_> { } } -impl<'db> api::Proposal for Proposal<'db> { - type Proposal = Proposal<'db>; +impl<'db, RS: RootStore> api::Proposal for Proposal<'db, RS> { + type Proposal = Proposal<'db, RS>; #[fastrace::trace(short_name = true)] fn propose( @@ -296,7 +311,7 @@ impl<'db> api::Proposal for Proposal<'db> { } } -impl Proposal<'_> { +impl Proposal<'_, RS> { #[crate::metrics("firewood.proposal.create", "database proposal creation")] fn create_proposal( &self, @@ -340,9 +355,12 @@ mod test { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; - use firewood_storage::{CheckOpt, CheckerError, HashedNodeReader, IntoHashType, NodeStore}; + use firewood_storage::{ + CheckOpt, CheckerError, HashedNodeReader, IntoHashType, NodeStore, TrieHash, + }; use crate::db::{Db, Proposal}; + use crate::root_store::{MockStore, NoOpStore, RootStore}; use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -858,7 +876,7 @@ mod test { let proposals = ops.iter().chunk_fold( NUM_KEYS, - Vec::>::with_capacity(NUM_PROPOSALS), + Vec::>::with_capacity(NUM_PROPOSALS), |mut proposals, ops| { let proposal = if let Some(parent) = proposals.last() { parent.propose(ops).unwrap() @@ -906,7 +924,7 @@ mod test { let testdb = TestDb::new(); let db = &testdb.db; - let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); + let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); let (result_tx, result_rx) = std::sync::mpsc::sync_channel(CHANNEL_CAPACITY); // scope will block until all scope-spawned threads finish @@ -991,18 +1009,75 @@ mod test { assert_eq!(value, retrieved_value.as_ref()); } + #[test] + fn test_root_store() { + let mock_store = MockStore::default(); + let db = TestDb::with_mockstore(mock_store); + + // First, create a revision to retrieve + let key = b"key"; + let value = b"value"; + let batch = vec![BatchOp::Put { key, value }]; + + let proposal = db.propose(batch).unwrap(); + let root_hash = proposal.root_hash().unwrap().unwrap(); + proposal.commit().unwrap(); + + // Next, overwrite the kv-pair with a new revision + let new_value = b"new_value"; + let batch = vec![BatchOp::Put { + key, + value: new_value, + }]; + + let proposal = db.propose(batch).unwrap(); + proposal.commit().unwrap(); + + // Reopen the database and verify that the database can access a persisted revision + let db = db.reopen(); + + let view = db.view(root_hash).unwrap(); + let retrieved_value = view.val(key).unwrap().unwrap(); + assert_eq!(value, retrieved_value.as_ref()); + } + + #[test] + fn test_root_store_errs() { + let mock_store = MockStore::with_failures(); + let db = TestDb::with_mockstore(mock_store); + + let view = db.view(TrieHash::empty()); + assert!(view.is_err()); + + let batch = vec![BatchOp::Put { + key: b"k", + value: b"v", + }]; + + let proposal = db.propose(batch).unwrap(); + assert!(proposal.commit().is_err()); + } + + #[test] + fn test_rootstore_empty_db_reopen() { + let mock_store = MockStore::default(); + let db = TestDb::with_mockstore(mock_store); + + db.reopen(); + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear - struct TestDb { - db: Db, + struct TestDb { + db: Db, tmpdir: tempfile::TempDir, } - impl Deref for TestDb { - type Target = Db; + impl Deref for TestDb { + type Target = Db; fn deref(&self) -> &Self::Target { &self.db } } - impl DerefMut for TestDb { + impl DerefMut for TestDb { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.db } @@ -1018,18 +1093,28 @@ mod test { let db = Db::new(dbpath, dbconfig).unwrap(); TestDb { db, tmpdir } } + } - fn path(&self) -> PathBuf { - [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + impl TestDb { + fn with_mockstore(mock_store: MockStore) -> Self { + let tmpdir = tempfile::tempdir().unwrap(); + let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() - .collect() + .collect(); + let dbconfig = DbConfig::builder().build(); + let db = Db::with_root_store(dbpath, dbconfig, mock_store).unwrap(); + TestDb { db, tmpdir } } + } + + impl TestDb { fn reopen(self) -> Self { let path = self.path(); + let root_store = self.manager.root_store(); drop(self.db); let dbconfig = DbConfig::builder().truncate(false).build(); - let db = Db::new(path, dbconfig).unwrap(); + let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); TestDb { db, tmpdir: self.tmpdir, @@ -1037,14 +1122,23 @@ mod test { } fn replace(self) -> Self { let path = self.path(); + let root_store = self.manager.root_store(); drop(self.db); let dbconfig = DbConfig::builder().truncate(true).build(); - let db = Db::new(path, dbconfig).unwrap(); + let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); TestDb { db, tmpdir: self.tmpdir, } } } + + impl TestDb { + fn path(&self) -> PathBuf { + [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect() + } + } } diff --git a/firewood/src/lib.rs b/firewood/src/lib.rs index 458e5f52dcae..24768dc3b316 100644 --- a/firewood/src/lib.rs +++ b/firewood/src/lib.rs @@ -145,6 +145,9 @@ pub use firewood_macros::metrics; /// Range proof module pub mod range_proof; +/// Root store module +pub mod root_store; + /// Version 2 API pub mod v2; diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 6fa9e214a400..a6ec4cab9446 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -21,12 +21,13 @@ use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; +use crate::root_store::{RootStore, RootStoreError}; use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; use firewood_storage::{ - BranchNode, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, - TrieHash, + BranchNode, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, + IntoHashType, NodeStore, TrieHash, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TypedBuilder)] @@ -67,7 +68,7 @@ type CommittedRevision = Arc>; type ProposedRevision = Arc, FileBacked>>; #[derive(Debug)] -pub(crate) struct RevisionManager { +pub(crate) struct RevisionManager { /// Maximum number of revisions to keep on disk max_revisions: usize, @@ -78,12 +79,15 @@ pub(crate) struct RevisionManager { // committing_proposals: VecDeque>, by_hash: RwLock>, threadpool: OnceLock, + root_store: RS, } #[derive(Debug, thiserror::Error)] pub(crate) enum RevisionManagerError { #[error("Revision for {provided:?} not found")] RevisionNotFound { provided: HashKey }, + #[error("Revision for {provided:?} has no address")] + RevisionWithoutAddress { provided: HashKey }, #[error( "The proposal cannot be committed since it is not a direct child of the most recent commit. Proposal parent: {provided:?}, current root: {expected:?}" )] @@ -91,12 +95,18 @@ pub(crate) enum RevisionManagerError { provided: Option, expected: Option, }, - #[error("An IO error occurred during the commit")] + #[error("An IO error occurred during the commit: {0}")] FileIoError(#[from] FileIoError), + #[error("A RootStore error occurred")] + RootStoreError(#[from] RootStoreError), } -impl RevisionManager { - pub fn new(filename: PathBuf, config: ConfigManager) -> Result { +impl RevisionManager { + pub fn new( + filename: PathBuf, + config: ConfigManager, + root_store: RS, + ) -> Result { let fb = FileBacked::new( filename, config.manager.node_cache_size, @@ -119,6 +129,7 @@ impl RevisionManager { proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), threadpool: OnceLock::new(), + root_store, }; if let Some(hash) = nodestore.root_hash().or_default_root_hash() { @@ -133,23 +144,18 @@ impl RevisionManager { nodestore.flush_header_with_padding()?; } - Ok(manager) - } + // On startup, we always write the latest revision to RootStore + if let Some(root_hash) = manager.current_revision().root_hash() { + let root_address = manager.current_revision().root_address().ok_or( + RevisionManagerError::RevisionWithoutAddress { + provided: root_hash.clone(), + }, + )?; - pub fn all_hashes(&self) -> Vec { - self.historical - .read() - .expect("poisoned lock") - .iter() - .filter_map(|r| r.root_hash().or_default_root_hash()) - .chain( - self.proposals - .lock() - .expect("poisoned lock") - .iter() - .filter_map(|p| p.root_hash().or_default_root_hash()), - ) - .collect() + manager.root_store.add_root(&root_hash, &root_address)?; + } + + Ok(manager) } /// Commit a proposal @@ -164,9 +170,10 @@ impl RevisionManager { /// 3. Revision reaping. If more than the maximum number of revisions are kept in memory, the /// oldest revision is reaped. /// 4. Persist to disk. This includes flushing everything to disk. - /// 5. Set last committed revision. + /// 5. Persist the revision to `RootStore`. + /// 6. Set last committed revision. /// Set last committed revision in memory. - /// 6. Proposal Cleanup. + /// 7. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. #[fastrace::trace(short_name = true)] #[crate::metrics("firewood.proposal.commit", "proposal commit to storage")] @@ -227,7 +234,12 @@ impl RevisionManager { // we move the header out of NodeStore, which is in a future PR. committed.persist()?; - // 5. Set last committed revision + // 5. Persist revision to root store + if let (Some(hash), Some(address)) = (committed.root_hash(), committed.root_address()) { + self.root_store.add_root(&hash, &address)?; + } + + // 6. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical .write() @@ -240,7 +252,7 @@ impl RevisionManager { .insert(hash, committed.clone()); } - // 6. Proposal Cleanup + // 7. Proposal Cleanup // Free proposal that is being committed as well as any proposals no longer // referenced by anyone else. self.proposals @@ -262,32 +274,69 @@ impl RevisionManager { Ok(()) } -} - -impl RevisionManager { - pub fn add_proposal(&self, proposal: ProposedRevision) { - self.proposals.lock().expect("poisoned lock").push(proposal); - } + /// View the database at a specific hash. + /// To view the database at a specific hash involves a few steps: + /// 1. Try to find it in committed revisions. + /// 2. Try to find it in proposals. + /// 3. Try to find it in `RootStore`. pub fn view(&self, root_hash: HashKey) -> Result { - // First try to find it in committed revisions + // 1. Try to find it in committed revisions. if let Ok(committed) = self.revision(root_hash.clone()) { return Ok(committed); } - // If not found in committed revisions, try proposals + // 2. Try to find it in proposals. let proposal = self .proposals .lock() .expect("poisoned lock") .iter() .find(|p| p.root_hash().as_ref() == Some(&root_hash)) - .cloned() - .ok_or(RevisionManagerError::RevisionNotFound { - provided: root_hash, - })?; + .cloned(); - Ok(proposal) + if let Some(proposal) = proposal { + return Ok(proposal); + } + + // 3. Try to find it in `RootStore`. + let revision_addr = + self.root_store + .get(&root_hash)? + .ok_or(RevisionManagerError::RevisionNotFound { + provided: root_hash.clone(), + })?; + + let node_store = NodeStore::with_root( + root_hash.into_hash_type(), + revision_addr, + self.current_revision(), + ); + + Ok(Arc::new(node_store)) + } +} + +impl RevisionManager { + pub fn add_proposal(&self, proposal: ProposedRevision) { + self.proposals.lock().expect("poisoned lock").push(proposal); + } + + /// TODO: should we support fetching all hashes from `RootStore`? + pub fn all_hashes(&self) -> Vec { + self.historical + .read() + .expect("poisoned lock") + .iter() + .filter_map(|r| r.root_hash().or_default_root_hash()) + .chain( + self.proposals + .lock() + .expect("poisoned lock") + .iter() + .filter_map(|p| p.root_hash().or_default_root_hash()), + ) + .collect() } pub fn revision(&self, root_hash: HashKey) -> Result { @@ -336,8 +385,15 @@ impl RevisionManager { #[allow(clippy::unwrap_used)] mod tests { use super::*; + use crate::root_store::NoOpStore; use tempfile::NamedTempFile; + impl RevisionManager { + pub fn root_store(&self) -> RS { + self.root_store.clone() + } + } + #[test] fn test_file_advisory_lock() { // Create a temporary file for testing @@ -350,14 +406,14 @@ mod tests { .build(); // First database instance should open successfully - let first_manager = RevisionManager::new(db_path.clone(), config.clone()); + let first_manager = RevisionManager::new(db_path.clone(), config.clone(), NoOpStore {}); assert!( first_manager.is_ok(), "First database should open successfully" ); // Second database instance should fail to open due to file locking - let second_manager = RevisionManager::new(db_path.clone(), config.clone()); + let second_manager = RevisionManager::new(db_path.clone(), config.clone(), NoOpStore {}); assert!( second_manager.is_err(), "Second database should fail to open" @@ -377,7 +433,7 @@ mod tests { drop(first_manager.unwrap()); // Now the second database should open successfully - let third_manager = RevisionManager::new(db_path, config); + let third_manager = RevisionManager::new(db_path, config, NoOpStore {}); assert!( third_manager.is_ok(), "Database should open after first instance is dropped" diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs new file mode 100644 index 000000000000..901690612023 --- /dev/null +++ b/firewood/src/root_store.rs @@ -0,0 +1,105 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#[cfg(test)] +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use firewood_storage::{LinearAddress, TrieHash}; + +#[derive(Debug)] +pub enum RootStoreMethod { + Add, + Get, +} + +#[derive(Debug, thiserror::Error)] +#[error("A RootStore error occurred.")] +pub struct RootStoreError { + pub method: RootStoreMethod, + #[source] + pub source: Box, +} + +pub trait RootStore { + /// `add_root` persists a revision's address to `RootStore`. + /// + /// Args: + /// - hash: the hash of the revision + /// - address: the address of the revision + /// + /// # Errors + /// + /// Will return an error if unable to persist the revision address to the + /// underlying datastore + fn add_root(&self, hash: &TrieHash, address: &LinearAddress) -> Result<(), RootStoreError>; + + /// `get` returns the address of a revision. + /// + /// Args: + /// - hash: the hash of the revision + /// + /// # Errors + /// + /// Will return an error if unable to query the underlying datastore. + fn get(&self, hash: &TrieHash) -> Result, RootStoreError>; +} + +#[cfg_attr(test, derive(Clone))] +#[derive(Debug)] +pub struct NoOpStore {} + +impl RootStore for NoOpStore { + fn add_root(&self, _hash: &TrieHash, _address: &LinearAddress) -> Result<(), RootStoreError> { + Ok(()) + } + + fn get(&self, _hash: &TrieHash) -> Result, RootStoreError> { + Ok(None) + } +} + +#[cfg(test)] +#[cfg_attr(test, derive(Clone))] +#[derive(Debug, Default)] +pub struct MockStore { + roots: Rc>>, + should_fail: bool, +} + +#[cfg(test)] +impl MockStore { + /// Returns an instance of `MockStore` that fails for all `add_root` and `get` calls. + #[must_use] + pub fn with_failures() -> Self { + Self { + should_fail: true, + ..Default::default() + } + } +} + +#[cfg(test)] +impl RootStore for MockStore { + fn add_root(&self, hash: &TrieHash, address: &LinearAddress) -> Result<(), RootStoreError> { + if self.should_fail { + return Err(RootStoreError { + method: RootStoreMethod::Add, + source: "Adding roots should fail".into(), + }); + } + + self.roots.borrow_mut().insert(hash.clone(), *address); + Ok(()) + } + + fn get(&self, hash: &TrieHash) -> Result, RootStoreError> { + if self.should_fail { + return Err(RootStoreError { + method: RootStoreMethod::Get, + source: "Getting roots should fail".into(), + }); + } + + Ok(self.roots.borrow().get(hash).copied()) + } +} diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 7a0678bb611b..74a14f6927e1 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -5,6 +5,7 @@ use crate::manager::RevisionManagerError; use crate::merkle::parallel::CreateProposalError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; +use crate::root_store::RootStoreError; use firewood_storage::{FileIoError, TrieHash}; use std::fmt::Debug; use std::num::NonZeroUsize; @@ -98,6 +99,10 @@ pub enum Error { provided: Option, }, + /// A committed revision does not have an address. + #[error("Revision for {provided:?} has no address")] + RevisionWithoutAddress { provided: HashKey }, + /// Incorrect root hash for commit #[error( "The proposal cannot be committed since it is not a direct child of the most recent commit. Proposal parent: {provided:?}, current root: {expected:?}" @@ -126,6 +131,10 @@ pub enum Error { /// A file I/O error occurred FileIO(#[from] FileIoError), + #[error("RootStore error: {0}")] + /// A `RootStore` error occurred + RootStoreError(#[from] RootStoreError), + /// Cannot commit a committed proposal #[error("Cannot commit a committed proposal")] AlreadyCommitted, @@ -169,13 +178,17 @@ pub enum Error { impl From for Error { fn from(err: RevisionManagerError) -> Self { - use RevisionManagerError::{FileIoError, NotLatest, RevisionNotFound}; + use RevisionManagerError::{ + FileIoError, NotLatest, RevisionNotFound, RevisionWithoutAddress, RootStoreError, + }; match err { NotLatest { provided, expected } => Self::ParentNotLatest { provided, expected }, RevisionNotFound { provided } => Self::RevisionNotFound { provided: Some(provided), }, + RevisionWithoutAddress { provided } => Self::RevisionWithoutAddress { provided }, FileIoError(io_err) => Self::FileIO(io_err), + RootStoreError(err) => Self::RootStoreError(err), } } } From 3db72fe361230a7d73ed0333d11328c53f6c269f Mon Sep 17 00:00:00 2001 From: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:20:29 -0400 Subject: [PATCH 0997/1053] feat: Parallel hashing of Merkle trie (#1303) This PR builds on https://github.com/ava-labs/firewood/pull/1258 to support parallel hashing. The workers performing parallel inserts will now perform hashing on their subtrie when they receive the Done message from the main thread. This is safe even if the trie is modified after post-processing because there is only one case in which any of the subtries is modified during post-processing (the root only has one child and no value). For this case, the updated root will be rehashed. The function hash_help has modified to take a path to allow the workers to correctly hash their subtrie by accounting for their child index. --------- Signed-off-by: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> --- firewood/src/merkle/parallel.rs | 30 +++++++++++++++++++++++------- storage/src/nodestore/hash.rs | 13 +++++++++---- storage/src/nodestore/mod.rs | 6 +++--- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/firewood/src/merkle/parallel.rs b/firewood/src/merkle/parallel.rs index 7514a3894ff5..ca3b187bc09c 100644 --- a/firewood/src/merkle/parallel.rs +++ b/firewood/src/merkle/parallel.rs @@ -196,14 +196,30 @@ impl ParallelMerkle { break; // Stop handling additional requests } } - // The main thread has closed the channel. Send back the worker's response. + // The main thread has closed the channel. Hash this subtrie and send back the worker's + // response where the root is a Child::MaybePersisted. let mut nodestore = merkle.into_inner(); - let response = Response { - first_path_component, - root: nodestore.root_mut().take().map(Child::Node), - deleted_nodes: nodestore.take_deleted_nodes(), - }; - response_sender.send(Ok(response))?; + let response = nodestore + .root_mut() + .take() + .map(|root| { + #[cfg(not(feature = "ethhash"))] + let (root_node, root_hash) = NodeStore::::hash_helper( + root, + Path::from(&[first_path_component.as_u8()]), + )?; + #[cfg(feature = "ethhash")] + let (root_node, root_hash) = + nodestore.hash_helper(root, Path::from(&[first_path_component.as_u8()]))?; + Ok(Child::MaybePersisted(root_node, root_hash)) + }) + .transpose() + .map(|hashed_root| Response { + first_path_component, + root: hashed_root, + deleted_nodes: nodestore.take_deleted_nodes(), + }); + response_sender.send(response)?; Ok(()) } diff --git a/storage/src/nodestore/hash.rs b/storage/src/nodestore/hash.rs index 8b1abeb51dc3..c7187168be74 100644 --- a/storage/src/nodestore/hash.rs +++ b/storage/src/nodestore/hash.rs @@ -113,13 +113,18 @@ where ) } - /// Hashes the given `node` and the subtree rooted at it. - /// Returns the hashed node and its hash. - pub(super) fn hash_helper( + /// Hashes the given `node` and the subtree rooted at it. The `root_path` should be empty + /// if this is called from the root, or it should include the partial path if this is called + /// on a subtrie. Returns the hashed node and its hash. + /// + /// # Errors + /// + /// Can return a `FileIoError` if it is unable to read a node that it is hashing. + pub fn hash_helper( #[cfg(feature = "ethhash")] &self, node: Node, + mut root_path: Path, ) -> Result<(MaybePersistedNode, HashType), FileIoError> { - let mut root_path = Path::new(); #[cfg(not(feature = "ethhash"))] let res = Self::hash_helper_inner(node, PathGuard::from_path(&mut root_path))?; #[cfg(feature = "ethhash")] diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 5f70a27edbba..c3003bc0939f 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -611,11 +611,11 @@ impl TryFrom> return Ok(nodestore); }; - // Hashes the trie and returns the address of the new root. + // Hashes the trie with an empty path and returns the address of the new root. #[cfg(feature = "ethhash")] - let (root, root_hash) = nodestore.hash_helper(root)?; + let (root, root_hash) = nodestore.hash_helper(root, Path::new())?; #[cfg(not(feature = "ethhash"))] - let (root, root_hash) = NodeStore::::hash_helper(root)?; + let (root, root_hash) = NodeStore::::hash_helper(root, Path::new())?; let immutable_proposal = Arc::into_inner(nodestore.kind).expect("no other references to the proposal"); From a911b6b9b0175f63d0c18badee75c602fa999fbe Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:25:58 -0400 Subject: [PATCH 0998/1053] chore: update Go to 1.24.9 (#1380) AvalancheGo currently uses go1.24.9 while Coreth is also has a PR open to use go.1.24.9 [[ref](https://github.com/ava-labs/coreth/pull/1352)]. This PR updates our go version to 1.24.9. --- ffi/go.mod | 2 +- ffi/tests/eth/go.mod | 2 +- ffi/tests/firewood/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ffi/go.mod b/ffi/go.mod index ebb119023b78..e3cfa3fc93dc 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi go 1.24 -toolchain go1.24.8 +toolchain go1.24.9 require ( github.com/prometheus/client_golang v1.22.0 diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index b680bc0f8e04..67dac0002d0d 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi/tests go 1.24 -toolchain go1.24.8 +toolchain go1.24.9 require ( github.com/ava-labs/firewood-go-ethhash/ffi v0.0.0 // this is replaced to use the parent folder diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index dab19db72f49..59fea55099a6 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -2,7 +2,7 @@ module github.com/ava-labs/firewood/ffi/tests go 1.24 -toolchain go1.24.8 +toolchain go1.24.9 require ( github.com/ava-labs/firewood-go/ffi v0.0.0 // this is replaced to use the parent folder From bdf58f91b8efda6b6778a1da548d589f152f677d Mon Sep 17 00:00:00 2001 From: maru Date: Wed, 22 Oct 2025 12:38:54 -0400 Subject: [PATCH 0999/1053] feat: Add nix flake for ffi (#1319) ## Why this should be merged A nix flake for the firewood ffi library enables deterministic builds both for [firewood-go-ethhash](http://github.com/ava-labs/firewood-go-ethhash) and a future nix build of avalanchego. ## How this works - Adds flake for ffi crate - Updates attach-static-libs workflow to build static libs with the flake ## How was this tested - CI (new ffi-nix job) - Locally - Install nix: - `curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install` - `cd ffi` - Build: - `nix build .#firewood-ffi` - Test (assuming build step perfomed) outside of a nix dev shell: - `GOLANG="nix run $PWD#go"` - Runs the version of golang referenced by the ffi flake - Need to capture this before changing directories to `result/ffi` because `result` is a nix store symlink so `../../` won't resolve to the `ffi` path containing the flake - Running golang this way instead of with `nix develop` validates usage of the build output won't require `nix develop`'s shell magic - `cd result/ffi` - `GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash ${GOLANG} test ./... -v` --------- Co-authored-by: Claude --- .cargo/config.toml | 2 + .github/dependabot.yml | 5 + .github/workflows/attach-static-libs.yaml | 2 +- .github/workflows/ci.yaml | 19 +++ ffi/.gitignore | 3 + ffi/flake.lock | 146 ++++++++++++++++++++++ ffi/flake.nix | 140 +++++++++++++++++++++ ffi/go.mod | 5 + ffi/test-build-equivalency.sh | 92 ++++++++++++++ 9 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 .cargo/config.toml create mode 100644 ffi/flake.lock create mode 100644 ffi/flake.nix create mode 100755 ffi/test-build-equivalency.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000000..cc8300c56f92 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +build-static-ffi = "build --frozen --profile maxperf --package firewood-ffi --features ethhash,logger" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 61c0bfc20600..0f613b81fe35 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,8 @@ updates: time: "05:00" timezone: "America/Los_Angeles" open-pull-requests-limit: 0 # Disable non-security version updates + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 0 # Disable non-security version updates diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index f09983f95345..0e514e532cda 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -63,7 +63,7 @@ jobs: run: cargo fetch --locked --verbose - name: Build for ${{ matrix.target }} - run: cargo build --frozen --profile maxperf --features ethhash,logger --target ${{ matrix.target }} -p firewood-ffi + run: cargo build-static-ffi --target ${{ matrix.target }} - name: Upload binary uses: actions/upload-artifact@v4 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3274a7a81f49..f7c74b6bdf0b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -244,6 +244,25 @@ jobs: # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test -race ./... + ffi-nix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 #v20 + - uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 #v13 + - name: Test build equivalency between Nix and Cargo + run: bash -x ./ffi/test-build-equivalency.sh + - name: Test Go FFI bindings + working-directory: ffi + # - cgocheck2 is expensive but provides complete pointer checks + # - use hash mode ethhash since the flake builds with `--features ethhash,logger` + # - run golang outside a nix shell to validate viability without the env setup performed by a nix shell + run: | + GOLANG="nix run $PWD#go" + cd result/ffi + GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash ${GOLANG} test ./... + shell: bash + firewood-ethhash-differential-fuzz: needs: build runs-on: ubuntu-latest diff --git a/ffi/.gitignore b/ffi/.gitignore index 4e24ab4c7504..ecc17f0fc9a1 100644 --- a/ffi/.gitignore +++ b/ffi/.gitignore @@ -1,2 +1,5 @@ dbtest _obj + +# Nix output +result diff --git a/ffi/flake.lock b/ffi/flake.lock new file mode 100644 index 000000000000..e95d44ffe963 --- /dev/null +++ b/ffi/flake.lock @@ -0,0 +1,146 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1758758545, + "narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=", + "owner": "ipetkov", + "repo": "crane", + "rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "golang": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "dir": "nix/go", + "lastModified": 1760457933, + "narHash": "sha256-/OztRdmXd3cL6ycrzRYAgFkPTv9v5WWlcdtVlERaRKI=", + "owner": "ava-labs", + "repo": "avalanchego", + "rev": "edf67505fee7c95837c2220467de86fea0efc860", + "type": "github" + }, + "original": { + "dir": "nix/go", + "owner": "ava-labs", + "ref": "edf67505fee7c95837c2220467de86fea0efc860", + "repo": "avalanchego", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1759735786, + "narHash": "sha256-a0+h02lyP2KwSNrZz4wLJTu9ikujNsTWIC874Bv7IJ0=", + "rev": "20c4598c84a671783f741e02bf05cbfaf4907cff", + "revCount": 810859, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.810859%2Brev-20c4598c84a671783f741e02bf05cbfaf4907cff/0199bc43-02e2-7036-8e2c-e43f6d6b4ede/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.2505.%2A.tar.gz" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1758589230, + "narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=", + "rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0", + "revCount": 810308, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.810308%2Brev-d1d883129b193f0b495d75c148c2c3a7d95789a0/01997816-a6f6-7040-8535-2ae74ed9bd44/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.2505.%2A.tar.gz" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils", + "golang": "golang", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1758854041, + "narHash": "sha256-kZ+24pbf4FiHlYlcvts64BhpxpHkPKIQXBmx1OmBAIo=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "02227ca8c229c968dbb5de95584cfb12b4313104", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/ffi/flake.nix b/ffi/flake.nix new file mode 100644 index 000000000000..e2853cc0c94d --- /dev/null +++ b/ffi/flake.nix @@ -0,0 +1,140 @@ +{ + # To test with arbitrary firewood versions (alternative to firewood-go-ethhash): + # - Install nix: https://github.com/DeterminateSystems/nix-installer?tab=readme-ov-file#install-nix + # - Clone firewood locally at desired version/commit + # - Build: `cd ffi && nix build` + # - In your Go project: `go mod edit -replace github.com/ava-labs/firewood-go-ethhash/ffi=/path/to/firewood/ffi/result/ffi` + + description = "Firewood FFI library and development environment"; + + inputs = { + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2505.*.tar.gz"; + rust-overlay.url = "github:oxalica/rust-overlay"; + crane.url = "github:ipetkov/crane"; + flake-utils.url = "github:numtide/flake-utils"; + golang.url = "github:ava-labs/avalanchego?dir=nix/go&ref=f10757d594eedf0f016bc1400739788c542f005f"; + }; + + outputs = { self, nixpkgs, rust-overlay, crane, flake-utils, golang }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + inherit (pkgs) lib; + + go = golang.packages.${system}.default; + + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rustfmt" "clippy" ]; + }; + + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; + + # Extract crate info from Cargo.toml files + ffiCargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); + workspaceCargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml); + + src = lib.cleanSourceWith { + src = craneLib.path ./..; + filter = path: type: + (lib.hasSuffix "\.md" path) || + (lib.hasSuffix "\.go" path) || + (lib.hasSuffix "go.mod" path) || + (lib.hasSuffix "go.sum" path) || + (lib.hasSuffix "firewood.h" path) || + (craneLib.filterCargoSources path type); + }; + + commonArgs = { + inherit src; + strictDeps = true; + dontStrip = true; + + # Build only the firewood-ffi crate + pname = ffiCargoToml.package.name; + version = workspaceCargoToml.workspace.package.version; + + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + + # Force sequential build of vendored jemalloc to avoid race conditions + # that cause non-deterministic symbol generation on x86_64 + # MAKEFLAGS only affects make invocations (jemalloc), not cargo parallelism + # See: https://github.com/NixOS/nixpkgs/issues/380852 + MAKEFLAGS = "-j1"; + } // lib.optionalAttrs pkgs.stdenv.isDarwin { + # Set macOS deployment target for Darwin builds + MACOSX_DEPLOYMENT_TARGET = "13.0"; + }; + + cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { + # Use cargo alias defined in .cargo/config.toml + cargoBuildCommand = "cargo build-static-ffi"; + }); + + firewood-ffi = craneLib.buildPackage (commonArgs // { + inherit cargoArtifacts; + # Use cargo alias defined in .cargo/config.toml + cargoBuildCommand = "cargo build-static-ffi"; + + # Disable tests - we only need to build the static library + doCheck = false; + + # Install the static library and header + postInstall = '' + # Create a package structure compatible with FIREWOOD_LD_MODE=STATIC_LIBS + mkdir -p $out/ffi + cp -R ./ffi/* $out/ffi/ + mkdir -p $out/ffi/libs/${pkgs.stdenv.hostPlatform.config} + cp target/maxperf/libfirewood_ffi.a $out/ffi/libs/${pkgs.stdenv.hostPlatform.config}/ + + # Run go generate to switch CGO directives to STATIC_LIBS mode + cd $out/ffi + HOME=$TMPDIR GOTOOLCHAIN=local FIREWOOD_LD_MODE=STATIC_LIBS ${go}/bin/go generate + ''; + + meta = with lib; { + description = "C FFI bindings for Firewood, an embedded key-value store"; + homepage = "https://github.com/ava-labs/firewood"; + license = { + fullName = "Ava Labs Ecosystem License 1.1"; + url = "https://github.com/ava-labs/firewood/blob/main/LICENSE.md"; + }; + platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + }; + }); + in + { + packages = { + inherit firewood-ffi; + default = firewood-ffi; + }; + + apps.go = { + type = "app"; + program = "${go}/bin/go"; + }; + + devShells.default = craneLib.devShell { + inputsFrom = [ firewood-ffi ]; + + packages = with pkgs; [ + firewood-ffi + rustToolchain + go + ]; + + shellHook = '' + # Ensure golang bin is in the path + GOBIN="$(go env GOPATH)/bin" + if [[ ":$PATH:" != *":$GOBIN:"* ]]; then + export PATH="$GOBIN:$PATH" + fi + + # Force sequential build of vendored jemalloc for reproducibility + export MAKEFLAGS="-j1" + ''; + }; + }); +} diff --git a/ffi/go.mod b/ffi/go.mod index e3cfa3fc93dc..be46486ab6b4 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -2,6 +2,11 @@ module github.com/ava-labs/firewood/ffi go 1.24 +// Changes to the toolchain version should be replicated in: +// - ffi/go.mod (here) +// - ffi/flake.nix (update golang.url to a version of avalanchego's nix/go/flake.nix that uses the desired version) +// - ffi/tests/eth/go.mod +// - ffi/tests/firewood/go.mod toolchain go1.24.9 require ( diff --git a/ffi/test-build-equivalency.sh b/ffi/test-build-equivalency.sh new file mode 100755 index 000000000000..5fc4cc57b8e5 --- /dev/null +++ b/ffi/test-build-equivalency.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Always work from the repo root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$REPO_ROOT" + +# Define paths to libraries (relative to repo root) +NIX_LIB="ffi/result/lib/libfirewood_ffi.a" # Default path for the nix build +CARGO_LIB="target/maxperf/libfirewood_ffi.a" + +# Create temporary directory and ensure cleanup on exit +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT + +echo "Building with cargo (using nix dev shell)..." +nix develop ./ffi#default --command bash -c "cargo fetch --locked --verbose && cargo build-static-ffi" + +echo "Building with nix..." +cd ffi && nix build .#firewood-ffi && cd .. + +echo "" +echo "=== File Size Comparison ===" +ls -lh "$CARGO_LIB" "$NIX_LIB" + +echo "" +echo "=== Symbol Count Comparison ===" +NIX_SYMBOLS=$(nm "$NIX_LIB" | wc -l) +CARGO_SYMBOLS=$(nm "$CARGO_LIB" | wc -l) +echo "Nix build: $NIX_SYMBOLS symbols" +echo "Cargo build: $CARGO_SYMBOLS symbols" +if [ "$NIX_SYMBOLS" -eq "$CARGO_SYMBOLS" ]; then + echo "✅ Symbol counts match" +else + echo "❌ Symbol counts differ" +fi + +echo "" +echo "=== Relocation Count Comparison ===" + +# Determine os-specific reloc config +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + RELOC_CMD="otool -rv" + RELOC_PATTERN='[A-Z_]+_RELOC_[A-Z0-9_]+' +else + # Linux + RELOC_CMD="readelf -r" + RELOC_PATTERN='R_[A-Z0-9_]+' +fi + +$RELOC_CMD "$NIX_LIB" > "$TMPDIR/nix-relocs.txt" +$RELOC_CMD "$CARGO_LIB" > "$TMPDIR/cargo-relocs.txt" + +NIX_RELOCS=$(wc -l < "$TMPDIR/nix-relocs.txt") +CARGO_RELOCS=$(wc -l < "$TMPDIR/cargo-relocs.txt") +echo "Nix build: $NIX_RELOCS relocation entries" +echo "Cargo build: $CARGO_RELOCS relocation entries" +if [ "$NIX_RELOCS" -eq "$CARGO_RELOCS" ]; then + echo "✅ Relocation counts match" +else + echo "❌ Relocation counts differ" +fi + +echo "" +echo "=== Relocation Type Comparison ===" + +# Use grep with -E for better portability (avoid -P which isn't available on macOS) +grep -Eo "$RELOC_PATTERN" "$TMPDIR/nix-relocs.txt" | sort | uniq -c > "$TMPDIR/nix-reloc-types.txt" +grep -Eo "$RELOC_PATTERN" "$TMPDIR/cargo-relocs.txt" | sort | uniq -c > "$TMPDIR/cargo-reloc-types.txt" + +if diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" > /dev/null; then + echo "✅ Relocation types match" +else + echo "❌ Relocation types differ" + diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" +fi + +echo "" +echo "=== Relocation Type Distribution ===" +cat "$TMPDIR/nix-reloc-types.txt" + +echo "" +echo "=== Summary ===" +if [ "$NIX_SYMBOLS" -eq "$CARGO_SYMBOLS" ] && [ "$NIX_RELOCS" -eq "$CARGO_RELOCS" ] && diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" > /dev/null; then + echo "✅ Builds are equivalent - both using maxperf profile" +else + echo "❌ Builds differ" + exit 1 +fi From 443c2a5eada888250ac0580193f82fb8340a5a89 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:38:59 -0400 Subject: [PATCH 1000/1053] chore(ffi/firewood): remove `RootStore` generics (#1388) While going through a first pass of adding `ProductionStore`, I found that adding a `RootStore` generic to `Db` resulted in the generic bubbling up to the `ffi` layer, which caused a myriad of issues from the Golang side (and a massive refactoring). This PR removes the `RootStore` generic and instead sets the `root_store` field of the `RevisionManager` to be a `Box` pointer. --- ffi/src/handle.rs | 3 +- ffi/src/proposal.rs | 13 +++--- firewood/src/db.rs | 89 ++++++++++++++++++-------------------- firewood/src/manager.rs | 26 ++++++----- firewood/src/root_store.rs | 19 +++++--- 5 files changed, 74 insertions(+), 76 deletions(-) diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 73c7f9f8805c..b4061978b9ae 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,7 +4,6 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, - root_store::NoOpStore, v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType}, }; @@ -231,7 +230,7 @@ impl<'db> CView<'db> for &'db crate::DatabaseHandle { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error> { + ) -> Result, api::Error> { self.db.propose(values.as_ref().iter()) } } diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 0ed971df297c..1281a9993539 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,10 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::{ - root_store::NoOpStore, - v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}, -}; +use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; use crate::value::KeyValuePair; @@ -16,13 +13,13 @@ use metrics::counter; #[derive(Debug)] pub struct ProposalHandle<'db> { hash_key: Option, - proposal: firewood::db::Proposal<'db, NoOpStore>, + proposal: firewood::db::Proposal<'db>, handle: &'db crate::DatabaseHandle, } impl<'db> DbView for ProposalHandle<'db> { type Iter<'view> - = as DbView>::Iter<'view> + = as DbView>::Iter<'view> where Self: 'view; @@ -146,7 +143,7 @@ pub trait CView<'db> { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error>; + ) -> Result, api::Error>; /// Create a [`ProposalHandle`] from the values and return it with timing /// information. @@ -191,7 +188,7 @@ impl<'db> CView<'db> for &ProposalHandle<'db> { fn create_proposal<'kvp>( self, values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, - ) -> Result, api::Error> { + ) -> Result, api::Error> { self.proposal.propose(values.as_ref()) } } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 59f778df56a7..e7d0c61415ab 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -113,16 +113,16 @@ pub struct DbConfig { #[derive(Debug)] /// A database instance. -pub struct Db { +pub struct Db { metrics: Arc, - manager: RevisionManager, + manager: RevisionManager, } -impl api::Db for Db { +impl api::Db for Db { type Historical = NodeStore; type Proposal<'db> - = Proposal<'db, RS> + = Proposal<'db> where Self: 'db; @@ -181,18 +181,16 @@ impl api::Db for Db { } } -impl Db { +impl Db { /// Create a new database instance. pub fn new>(db_path: P, cfg: DbConfig) -> Result { - Self::with_root_store(db_path, cfg, NoOpStore {}) + Self::with_root_store(db_path, cfg, Box::new(NoOpStore {})) } -} -impl Db { fn with_root_store>( db_path: P, cfg: DbConfig, - root_store: RS, + root_store: Box, ) -> Result { let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals"), @@ -214,9 +212,7 @@ impl Db { pub fn view(&self, root_hash: HashKey) -> Result { self.manager.view(root_hash).map_err(Into::into) } -} -impl Db { /// Dump the Trie of the latest revision. pub fn dump(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { let latest_rev_nodestore = self.manager.current_revision(); @@ -243,7 +239,7 @@ impl Db { pub fn propose_parallel( &self, batch: impl IntoIterator, - ) -> Result, api::Error> { + ) -> Result, api::Error> { let parent = self.manager.current_revision(); let mut parallel_merkle = ParallelMerkle::default(); let immutable = @@ -258,12 +254,12 @@ impl Db { #[derive(Debug)] /// A user-visible database proposal -pub struct Proposal<'db, RS> { +pub struct Proposal<'db> { nodestore: Arc, FileBacked>>, - db: &'db Db, + db: &'db Db, } -impl api::DbView for Proposal<'_, RS> { +impl api::DbView for Proposal<'_> { type Iter<'view> = MerkleKeyValueIter<'view, NodeStore, FileBacked>> where @@ -295,8 +291,8 @@ impl api::DbView for Proposal<'_, RS> { } } -impl<'db, RS: RootStore> api::Proposal for Proposal<'db, RS> { - type Proposal = Proposal<'db, RS>; +impl<'db> api::Proposal for Proposal<'db> { + type Proposal = Proposal<'db>; #[fastrace::trace(short_name = true)] fn propose( @@ -311,7 +307,7 @@ impl<'db, RS: RootStore> api::Proposal for Proposal<'db, RS> { } } -impl Proposal<'_, RS> { +impl Proposal<'_> { #[crate::metrics("firewood.proposal.create", "database proposal creation")] fn create_proposal( &self, @@ -360,7 +356,7 @@ mod test { }; use crate::db::{Db, Proposal}; - use crate::root_store::{MockStore, NoOpStore, RootStore}; + use crate::root_store::{MockStore, RootStore}; use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -401,6 +397,15 @@ mod test { impl IterExt for T {} + #[cfg(test)] + impl Db { + /// Extract the root store by consuming the database instance. + /// This is primarily used for reopening or replacing the database with the same root store. + pub fn into_root_store(self) -> Box { + self.manager.into_root_store() + } + } + #[test] fn test_proposal_reads() { let db = TestDb::new(); @@ -876,7 +881,7 @@ mod test { let proposals = ops.iter().chunk_fold( NUM_KEYS, - Vec::>::with_capacity(NUM_PROPOSALS), + Vec::>::with_capacity(NUM_PROPOSALS), |mut proposals, ops| { let proposal = if let Some(parent) = proposals.last() { parent.propose(ops).unwrap() @@ -924,7 +929,7 @@ mod test { let testdb = TestDb::new(); let db = &testdb.db; - let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); + let (tx, rx) = std::sync::mpsc::sync_channel::>(CHANNEL_CAPACITY); let (result_tx, result_rx) = std::sync::mpsc::sync_channel(CHANNEL_CAPACITY); // scope will block until all scope-spawned threads finish @@ -1067,17 +1072,17 @@ mod test { } // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear - struct TestDb { - db: Db, + struct TestDb { + db: Db, tmpdir: tempfile::TempDir, } - impl Deref for TestDb { - type Target = Db; + impl Deref for TestDb { + type Target = Db; fn deref(&self) -> &Self::Target { &self.db } } - impl DerefMut for TestDb { + impl DerefMut for TestDb { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.db } @@ -1093,48 +1098,38 @@ mod test { let db = Db::new(dbpath, dbconfig).unwrap(); TestDb { db, tmpdir } } - } - impl TestDb { fn with_mockstore(mock_store: MockStore) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); let dbconfig = DbConfig::builder().build(); - let db = Db::with_root_store(dbpath, dbconfig, mock_store).unwrap(); + let db = Db::with_root_store(dbpath, dbconfig, Box::new(mock_store)).unwrap(); TestDb { db, tmpdir } } - } - impl TestDb { fn reopen(self) -> Self { let path = self.path(); - let root_store = self.manager.root_store(); - drop(self.db); - let dbconfig = DbConfig::builder().truncate(false).build(); + let TestDb { db, tmpdir } = self; + + let root_store = db.into_root_store(); + let dbconfig = DbConfig::builder().truncate(false).build(); let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); - TestDb { - db, - tmpdir: self.tmpdir, - } + TestDb { db, tmpdir } } fn replace(self) -> Self { let path = self.path(); - let root_store = self.manager.root_store(); - drop(self.db); - let dbconfig = DbConfig::builder().truncate(true).build(); + let TestDb { db, tmpdir } = self; + + let root_store = db.into_root_store(); + let dbconfig = DbConfig::builder().truncate(true).build(); let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); - TestDb { - db, - tmpdir: self.tmpdir, - } + TestDb { db, tmpdir } } - } - impl TestDb { fn path(&self) -> PathBuf { [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index a6ec4cab9446..63c39a52e68c 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -68,7 +68,7 @@ type CommittedRevision = Arc>; type ProposedRevision = Arc, FileBacked>>; #[derive(Debug)] -pub(crate) struct RevisionManager { +pub(crate) struct RevisionManager { /// Maximum number of revisions to keep on disk max_revisions: usize, @@ -79,7 +79,7 @@ pub(crate) struct RevisionManager { // committing_proposals: VecDeque>, by_hash: RwLock>, threadpool: OnceLock, - root_store: RS, + root_store: Box, } #[derive(Debug, thiserror::Error)] @@ -101,11 +101,11 @@ pub(crate) enum RevisionManagerError { RootStoreError(#[from] RootStoreError), } -impl RevisionManager { +impl RevisionManager { pub fn new( filename: PathBuf, config: ConfigManager, - root_store: RS, + root_store: Box, ) -> Result { let fb = FileBacked::new( filename, @@ -315,9 +315,7 @@ impl RevisionManager { Ok(Arc::new(node_store)) } -} -impl RevisionManager { pub fn add_proposal(&self, proposal: ProposedRevision) { self.proposals.lock().expect("poisoned lock").push(proposal); } @@ -388,9 +386,11 @@ mod tests { use crate::root_store::NoOpStore; use tempfile::NamedTempFile; - impl RevisionManager { - pub fn root_store(&self) -> RS { - self.root_store.clone() + #[cfg(test)] + impl RevisionManager { + /// Extract the root store by consuming the revision manager instance. + pub fn into_root_store(self) -> Box { + self.root_store } } @@ -406,14 +406,16 @@ mod tests { .build(); // First database instance should open successfully - let first_manager = RevisionManager::new(db_path.clone(), config.clone(), NoOpStore {}); + let first_manager = + RevisionManager::new(db_path.clone(), config.clone(), Box::new(NoOpStore {})); assert!( first_manager.is_ok(), "First database should open successfully" ); // Second database instance should fail to open due to file locking - let second_manager = RevisionManager::new(db_path.clone(), config.clone(), NoOpStore {}); + let second_manager = + RevisionManager::new(db_path.clone(), config.clone(), Box::new(NoOpStore {})); assert!( second_manager.is_err(), "Second database should fail to open" @@ -433,7 +435,7 @@ mod tests { drop(first_manager.unwrap()); // Now the second database should open successfully - let third_manager = RevisionManager::new(db_path, config, NoOpStore {}); + let third_manager = RevisionManager::new(db_path, config, Box::new(NoOpStore {})); assert!( third_manager.is_ok(), "Database should open after first instance is dropped" diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index 901690612023..a112ffc9c010 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -1,8 +1,12 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::fmt::Debug; #[cfg(test)] -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use firewood_storage::{LinearAddress, TrieHash}; @@ -20,7 +24,7 @@ pub struct RootStoreError { pub source: Box, } -pub trait RootStore { +pub trait RootStore: Debug { /// `add_root` persists a revision's address to `RootStore`. /// /// Args: @@ -44,7 +48,6 @@ pub trait RootStore { fn get(&self, hash: &TrieHash) -> Result, RootStoreError>; } -#[cfg_attr(test, derive(Clone))] #[derive(Debug)] pub struct NoOpStore {} @@ -59,10 +62,9 @@ impl RootStore for NoOpStore { } #[cfg(test)] -#[cfg_attr(test, derive(Clone))] #[derive(Debug, Default)] pub struct MockStore { - roots: Rc>>, + roots: Arc>>, should_fail: bool, } @@ -88,7 +90,10 @@ impl RootStore for MockStore { }); } - self.roots.borrow_mut().insert(hash.clone(), *address); + self.roots + .lock() + .expect("poisoned lock") + .insert(hash.clone(), *address); Ok(()) } @@ -100,6 +105,6 @@ impl RootStore for MockStore { }); } - Ok(self.roots.borrow().get(hash).copied()) + Ok(self.roots.lock().expect("poisoned lock").get(hash).copied()) } } From 1a541c4bd87d6d58eb16b49b1a66842140aabe97 Mon Sep 17 00:00:00 2001 From: Suyan Qu <36519575+qusuyan@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:26:38 -0500 Subject: [PATCH 1001/1053] feat: add more info for IO error message (#1378) --- firewood/src/db.rs | 1 + storage/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index e7d0c61415ab..8ac60d275ecc 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1119,6 +1119,7 @@ mod test { let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); TestDb { db, tmpdir } } + fn replace(self) -> Self { let path = self.path(); let TestDb { db, tmpdir } = self; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 5414340cae86..869a16e5e1ec 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -299,7 +299,7 @@ pub enum CheckerError { }, /// IO error - #[error("IO error")] + #[error("IO error reading pointer stored at {parent:#x}: {error}")] #[derive_where(skip_inner)] IO { /// The error From 2ff7915dfc59ee19a3d6437970438d1179a0c2e2 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 29 Oct 2025 09:29:02 -0700 Subject: [PATCH 1002/1053] chore: update .golangci.yaml (#1394) - Ran `.github/scripts/verify_golangci_yaml_changes.sh apply` and committed the changes. - Corrected new lint error that surfaced from the config change. --- .github/.golangci.yaml.patch | 12 ++++++------ ffi/.golangci.yaml | 18 ++++++++++++++++++ ffi/metrics_test.go | 3 +-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index 28e92b877d0e..9580ebf13898 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,6 +1,6 @@ ---- .github/.golangci.yaml 2025-09-10 10:51:41 -+++ ffi/.golangci.yaml 2025-08-22 11:42:25 -@@ -69,8 +69,6 @@ +--- .github/.golangci.yaml 2025-10-29 08:30:25 ++++ ffi/.golangci.yaml 2025-10-29 08:30:25 +@@ -70,8 +70,6 @@ rules: packages: deny: @@ -9,7 +9,7 @@ - pkg: github.com/golang/mock/gomock desc: go.uber.org/mock/gomock should be used instead. - pkg: github.com/stretchr/testify/assert -@@ -85,29 +83,10 @@ +@@ -86,29 +84,10 @@ forbidigo: # Forbid the following identifiers (list of regexp). forbid: @@ -39,7 +39,7 @@ revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr -@@ -171,17 +150,6 @@ +@@ -172,17 +151,6 @@ # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - name: useless-break disabled: false @@ -57,7 +57,7 @@ tagalign: align: true sort: true -@@ -227,7 +195,7 @@ +@@ -245,7 +213,7 @@ - standard - default - blank diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index 035839e55a76..d56130539eb2 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -60,6 +60,7 @@ linters: - tagalign - testifylint - unconvert + - usetesting - unparam - unused - usestdlibvars @@ -167,6 +168,14 @@ linters: disable: - go-require - float-compare + usetesting: + os-create-temp: true # Disallow `os.CreateTemp("", ...)` + os-mkdir-temp: true # Disallow `os.MkdirTemp()` + os-setenv: true # Disallow `os.Setenv()` + os-temp-dir: true # Disallow `os.TempDir()` + os-chdir: true # Disallow `os.Chdir()` + context-background: true # Disallow `context.Background()` + context-todo: true # Disallow `context.TODO()` unused: # Mark all struct fields that have been written to as used. # Default: true @@ -184,6 +193,15 @@ linters: - common-false-positives - legacy - std-error-handling + rules: + # Exclude some linters from running on test files. + # 1. Exclude the top level tests/ directory. + # 2. Exclude any file prefixed with test_ in any directory. + # 3. Exclude any directory suffixed with test. + # 4. Exclude any file suffixed with _test.go. + - path: "(^tests/)|(^(.*/)*test_[^/]*\\.go$)|(.*test/.*)|(.*_test\\.go$)" + linters: + - prealloc formatters: enable: - gci diff --git a/ffi/metrics_test.go b/ffi/metrics_test.go index de3df004234f..b588ee2d4adc 100644 --- a/ffi/metrics_test.go +++ b/ffi/metrics_test.go @@ -4,7 +4,6 @@ package ffi import ( - "context" "fmt" "io" "net/http" @@ -22,7 +21,7 @@ import ( // This lives under one test as we can only instantiate the global recorder once func TestMetrics(t *testing.T) { r := require.New(t) - ctx := context.Background() + ctx := t.Context() // test params var ( From c8d35c6c490fddff468ce15a440f8f3b85aeac82 Mon Sep 17 00:00:00 2001 From: maru Date: Wed, 29 Oct 2025 11:00:39 -0700 Subject: [PATCH 1003/1053] ci: Update ffi build check to configure cargo with same MAKEFLAGS as nix (#1392) As per a [recent flake](https://github.com/ava-labs/firewood/actions/runs/18783578425/job/53595874673?pr=1388) it's not enough to configure the nix build with `MAKEFLAGS='-j1'` to avoid jmalloc non-determinism. The cargo build invoked in the nix shell also needs to be configured this way or CI runs on x86 runners run the risk of non-deterministic builds. --- ffi/test-build-equivalency.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ffi/test-build-equivalency.sh b/ffi/test-build-equivalency.sh index 5fc4cc57b8e5..1f2a23d14eac 100755 --- a/ffi/test-build-equivalency.sh +++ b/ffi/test-build-equivalency.sh @@ -15,8 +15,9 @@ CARGO_LIB="target/maxperf/libfirewood_ffi.a" TMPDIR=$(mktemp -d) trap "rm -rf $TMPDIR" EXIT +# Build serially with MAKEFLAGS='-j1' for consistency with the flake build echo "Building with cargo (using nix dev shell)..." -nix develop ./ffi#default --command bash -c "cargo fetch --locked --verbose && cargo build-static-ffi" +nix develop ./ffi#default --command bash -c "export MAKEFLAGS='-j1' && cargo fetch --locked --verbose && cargo build-static-ffi" echo "Building with nix..." cd ffi && nix build .#firewood-ffi && cd .. From 216426207c735aa77ff74032ecae55ff6152b557 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 29 Oct 2025 11:16:14 -0700 Subject: [PATCH 1004/1053] feat: add TrieNode trait and related functionality (#1363) `TrieNode` is a generic trait that abstracts over the behavior of nodes in a fixed-arity radix trie. This trait allows for different structures to represent tries while enabling similar operations on them. `TrieNode` is merkle-aware, meaning it can represent nodes that are a mix of locally described children or children referenced by only their hash. This is accomplished through `TrieEdgeState` and is used later in proof verification. The `HashedTrieNode` trait is for post-hashed nodes and is distinctly different from the hash described on `TrieNode`. This trait is intended to represent the computed hash of the node itself; whereas, the hash accessed via `TrieNode` is the hash of a child node as seen from its parent. While they are usually the same, they can differ during intermediate states of a trie or during proof verification when some information is incomplete. `KeyValueTrieRoot` is an implementation of `TrieNode` that only represents a key-value store and is not merkleized. This is really a separate change and is a component of proof verification but is also needed here as it is used to test and verify the iterator. --- Cargo.toml | 2 +- storage/Cargo.toml | 2 +- storage/src/lib.rs | 7 +- storage/src/node/branch.rs | 33 +++- storage/src/path/buf.rs | 76 ++++++++ storage/src/path/mod.rs | 2 +- storage/src/tries/iter.rs | 267 +++++++++++++++++++++++++ storage/src/tries/kvp.rs | 387 +++++++++++++++++++++++++++++++++++++ storage/src/tries/mod.rs | 191 ++++++++++++++++++ 9 files changed, 958 insertions(+), 9 deletions(-) create mode 100644 storage/src/tries/iter.rs create mode 100644 storage/src/tries/kvp.rs create mode 100644 storage/src/tries/mod.rs diff --git a/Cargo.toml b/Cargo.toml index ddb2490824bb..618c1ec3cfee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ metrics-util = "0.20.0" nonzero_ext = "0.3.0" rand_distr = "0.5.1" sha2 = "0.10.9" -smallvec = "1.15.1" +smallvec = { version = "1.15.1", features = ["write", "union", "const_new"] } test-case = "3.3.1" thiserror = "2.0.17" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index cf0c841a8a42..f426c6ee4286 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -29,7 +29,7 @@ metrics.workspace = true nonzero_ext.workspace = true rand = { workspace = true, optional = true } sha2.workspace = true -smallvec = { workspace = true, features = ["write", "union"] } +smallvec.workspace = true thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 869a16e5e1ec..983018b004cd 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -34,6 +34,7 @@ mod path; #[cfg(any(test, feature = "test_utils"))] mod test_utils; mod trie_hash; +mod tries; mod u4; /// Logger module for handling logging functionality @@ -58,11 +59,15 @@ pub use nodestore::{ }; pub use path::{ ComponentIter, IntoSplitPath, JoinedPath, PartialPath, PathBuf, PathCommonPrefix, - PathComponent, PathComponentSliceExt, SplitPath, TriePath, TriePathAsPackedBytes, + PathComponent, PathComponentSliceExt, PathGuard, SplitPath, TriePath, TriePathAsPackedBytes, TriePathFromPackedBytes, TriePathFromUnpackedBytes, }; #[cfg(not(feature = "branch_factor_256"))] pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; +pub use tries::{ + DuplicateKeyError, HashedTrieNode, IterAscending, IterDescending, KeyValueTrieRoot, + TrieEdgeIter, TrieEdgeState, TrieNode, TrieValueIter, +}; pub use u4::{TryFromIntError, U4}; pub use linear::filebacked::FileBacked; diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index cea3321d2cd6..95e5702b4a48 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -222,6 +222,12 @@ mod ethhash { pub fn into_triehash(self) -> TrieHash { self.into() } + + // used in PartialEq and Hash impls and is to trick clippy into not caring + // about creating an owned instance for comparison + fn as_triehash(&self) -> TrieHash { + self.into() + } } impl PartialEq for HashOrRlp { @@ -247,19 +253,27 @@ mod ethhash { impl PartialEq for HashOrRlp { fn eq(&self, other: &Self) -> bool { match (self, other) { + // if both are hash or rlp, we can skip hashing (HashOrRlp::Hash(h1), HashOrRlp::Hash(h2)) => h1 == h2, (HashOrRlp::Rlp(r1), HashOrRlp::Rlp(r2)) => r1 == r2, - #[expect(deprecated, reason = "transitive dependency on generic-array")] - (HashOrRlp::Hash(h), HashOrRlp::Rlp(r)) - | (HashOrRlp::Rlp(r), HashOrRlp::Hash(h)) => { - Keccak256::digest(r.as_ref()).as_slice() == h.as_ref() - } + // otherwise, one is a hash and the other isn't, so convert both + // to hash and compare + _ => self.as_triehash() == other.as_triehash(), } } } impl Eq for HashOrRlp {} + impl std::hash::Hash for HashOrRlp { + fn hash(&self, state: &mut H) { + // contract on `Hash` and `PartialEq` requires that if `a == b` then `hash(a) == hash(b)` + // and since `PartialEq` may require hashing, we must always convert to `TrieHash` here + // and use it's hash implementation + self.as_triehash().hash(state); + } + } + impl Serializable for HashOrRlp { fn write_to(&self, vec: &mut W) { match self { @@ -311,6 +325,15 @@ mod ethhash { } } + impl From<&HashOrRlp> for TrieHash { + fn from(val: &HashOrRlp) -> Self { + match val { + HashOrRlp::Hash(h) => h.clone(), + HashOrRlp::Rlp(r) => Keccak256::digest(r).into(), + } + } + } + impl From for HashOrRlp { fn from(val: TrieHash) -> Self { HashOrRlp::Hash(val) diff --git a/storage/src/path/buf.rs b/storage/src/path/buf.rs index 9af84cbb962c..64293fc004d1 100644 --- a/storage/src/path/buf.rs +++ b/storage/src/path/buf.rs @@ -128,3 +128,79 @@ impl<'a> IntoSplitPath for &'a PartialPath<'_> { self } } + +/// A RAII guard that resets a path buffer to its original length when dropped. +/// +/// The guard exposes append-only methods to add components to the path buffer +/// but not remove them without dropping the guard. This ensures that the guard +/// will always restore the path buffer to its original state when dropped. +#[must_use] +pub struct PathGuard<'a> { + buf: &'a mut PathBuf, + len: usize, +} + +impl std::fmt::Debug for PathGuard<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.buf.display().fmt(f) + } +} + +impl Drop for PathGuard<'_> { + fn drop(&mut self) { + self.buf.truncate(self.len); + } +} + +impl<'a> PathGuard<'a> { + /// Creates a new guard that will reset the provided buffer to its current + /// length when dropped. + pub fn new(buf: &'a mut PathBuf) -> Self { + Self { + len: buf.len(), + buf, + } + } + + /// Creates a new guard that will reset this guard's buffer to its current + /// length when dropped. + /// + /// This allows for nested guards that can be used in recursive algorithms. + pub fn fork(&mut self) -> PathGuard<'_> { + PathGuard::new(self.buf) + } + + /// Fork this guard and append the given segment to the path buffer. + /// + /// This is a convenience method that combines `fork` then `extend` in a + /// single operation for ergonomic one-liners. + /// + /// The returned guard will reset the path buffer to its original length, + /// before appending the given segment, when dropped. + pub fn fork_append(&mut self, path: impl TriePath) -> PathGuard<'_> { + let mut fork = self.fork(); + fork.extend(path.components()); + fork + } + + /// Appends the given component to the path buffer. + /// + /// This component will be removed when the guard is dropped. + pub fn push(&mut self, component: PathComponent) { + self.buf.push(component); + } +} + +impl std::ops::Deref for PathGuard<'_> { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + self.buf + } +} + +impl Extend for PathGuard<'_> { + fn extend>(&mut self, iter: T) { + self.buf.extend(iter); + } +} diff --git a/storage/src/path/mod.rs b/storage/src/path/mod.rs index 145756c9f318..54aad8145a7b 100644 --- a/storage/src/path/mod.rs +++ b/storage/src/path/mod.rs @@ -8,7 +8,7 @@ mod joined; mod packed; mod split; -pub use self::buf::{PartialPath, PathBuf}; +pub use self::buf::{PartialPath, PathBuf, PathGuard}; pub use self::component::{ComponentIter, PathComponent, PathComponentSliceExt}; pub use self::joined::JoinedPath; #[cfg(not(feature = "branch_factor_256"))] diff --git a/storage/src/tries/iter.rs b/storage/src/tries/iter.rs new file mode 100644 index 000000000000..a874d9edcacb --- /dev/null +++ b/storage/src/tries/iter.rs @@ -0,0 +1,267 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::{HashType, PathBuf, PathComponent, TrieNode, TriePath, tries::TrieEdgeState}; + +/// A marker type for [`TrieEdgeIter`] that indicates that the iterator traverses +/// the trie in ascending order. +#[derive(Debug)] +pub struct IterAscending; + +/// A marker type for [`TrieEdgeIter`] that indicates that the iterator traverses +/// the trie in descending order. +#[derive(Debug)] +pub struct IterDescending; + +/// An iterator over the edges in a key-value trie in a specified order. +/// +/// Use [`TrieNode::iter_edges`] or [`TrieNode::iter_edges_desc`] to +/// create an instance of this iterator in ascending or descending order, +/// respectively. +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct TrieEdgeIter<'a, N: ?Sized, V: ?Sized, D> { + leading_path: PathBuf, + stack: Vec>, + marker: std::marker::PhantomData, +} + +/// An iterator over the key-value pairs in a key-value trie. +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct TrieValueIter<'a, N: ?Sized, V: ?Sized, D> { + edges: TrieEdgeIter<'a, N, V, D>, +} + +#[derive(Debug)] +struct Frame<'a, N: ?Sized, V: ?Sized> { + node: &'a N, + hash: Option<&'a HashType>, + leading_path_len: usize, + children: Option>, + marker: std::marker::PhantomData, +} + +impl<'a, N, V, D> TrieEdgeIter<'a, N, V, D> +where + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized, +{ + /// Creates a new iterator over the given key-value trie. + pub fn new(root: &'a N, root_hash: Option<&'a HashType>) -> Self { + let mut this = Self { + leading_path: PathBuf::new_const(), + stack: Vec::new(), + marker: std::marker::PhantomData, + }; + this.push_frame(None, root, root_hash); + this + } + + /// Transforms this iterator into an iterator over the key-value pairs in + /// the trie. + pub const fn node_values(self) -> TrieValueIter<'a, N, V, D> { + TrieValueIter { edges: self } + } + + fn push_frame( + &mut self, + leading_component: Option, + node: &'a N, + hash: Option<&'a HashType>, + ) { + let frame = Frame { + node, + hash, + leading_path_len: self.leading_path.len(), + children: None, + marker: std::marker::PhantomData, + }; + self.stack.push(frame); + self.leading_path.extend(leading_component); + self.leading_path.extend(node.partial_path().components()); + } +} + +/// Both iterators share this logic to descend into a node's children. +/// +/// The passed in `children_iter` should be an iterator over the indices into +/// the children array in the desired order (e.g. ascending or descending). +macro_rules! descend { + ( + $self:expr, + $node:expr, + $children_iter:expr + ) => { + if let Some((pc, state)) = + $children_iter.find_map(|pc| $node.child_state(pc).map(|state| (pc, state))) + { + match state { + TrieEdgeState::LocalChild { node, hash } => { + $self.push_frame(Some(pc), node, Some(hash)); + } + TrieEdgeState::RemoteChild { hash } => { + let mut path = $self.leading_path.clone(); + path.push(pc); + return Some((path, TrieEdgeState::RemoteChild { hash })); + } + TrieEdgeState::UnhashedChild { node } => { + $self.push_frame(Some(pc), node, None); + } + } + + continue; + } + }; +} + +impl<'a, N, V> Iterator for TrieEdgeIter<'a, N, V, IterAscending> +where + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized, +{ + type Item = (PathBuf, TrieEdgeState<'a, N>); + + fn next(&mut self) -> Option { + while let Some(&mut Frame { + node, + hash, + leading_path_len, + ref mut children, + marker: _, + }) = self.stack.last_mut() + { + // ascending iterator yields the node before iterating its children + let mut do_yield = false; + + let children = children.get_or_insert_with(|| { + do_yield = true; + PathComponent::ALL.into_iter() + }); + + if do_yield { + return Some(( + self.leading_path.clone(), + TrieEdgeState::from_node(node, hash), + )); + } + + descend!(self, node, children); + + // we've exhausted this node's children, so pop its frame + self.stack.pop(); + self.leading_path.truncate(leading_path_len); + } + + None + } +} + +impl<'a, N, V> Iterator for TrieEdgeIter<'a, N, V, IterDescending> +where + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized, +{ + type Item = (PathBuf, TrieEdgeState<'a, N>); + + fn next(&mut self) -> Option { + while let Some(&mut Frame { + node, + hash, + leading_path_len, + ref mut children, + marker: _, + }) = self.stack.last_mut() + { + // descending iterator yields the node after iterating its children + let children = children.get_or_insert_with(|| PathComponent::ALL.into_iter()); + + descend!(self, node, children.rev()); + + // clone the path before we pop the frame + let leading_path = self.leading_path.clone(); + + // we've exhausted this node's children, so pop its frame and yield the node + self.stack.pop(); + self.leading_path.truncate(leading_path_len); + + return Some((leading_path, TrieEdgeState::from_node(node, hash))); + } + + None + } +} + +impl<'a, N, V> Iterator for TrieValueIter<'a, N, V, IterAscending> +where + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized + 'a, +{ + type Item = (PathBuf, &'a V); + + fn next(&mut self) -> Option { + self.edges + .find_map(|(path, node)| node.value().map(|v| (path, v))) + } +} + +impl<'a, N, V> Iterator for TrieValueIter<'a, N, V, IterDescending> +where + N: TrieNode + ?Sized, + V: AsRef<[u8]> + ?Sized + 'a, +{ + type Item = (PathBuf, &'a V); + + fn next(&mut self) -> Option { + self.edges + .find_map(|(path, node)| node.value().map(|v| (path, v))) + } +} + +// auto-derived implementations would require N: Clone, V: Clone which is too much + +impl Clone for TrieEdgeIter<'_, N, V, D> { + fn clone(&self) -> Self { + Self { + leading_path: self.leading_path.clone(), + stack: self.stack.clone(), + marker: std::marker::PhantomData, + } + } + + fn clone_from(&mut self, source: &Self) { + self.leading_path.clone_from(&source.leading_path); + self.stack.clone_from(&source.stack); + } +} + +impl Clone for TrieValueIter<'_, N, V, D> { + fn clone(&self) -> Self { + Self { + edges: self.edges.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.edges.clone_from(&source.edges); + } +} + +impl Clone for Frame<'_, N, V> { + fn clone(&self) -> Self { + Self { + node: self.node, + hash: self.hash, + leading_path_len: self.leading_path_len, + children: self.children.clone(), + marker: std::marker::PhantomData, + } + } + + fn clone_from(&mut self, source: &Self) { + self.node = source.node; + self.hash = source.hash; + self.leading_path_len = source.leading_path_len; + self.children.clone_from(&source.children); + } +} diff --git a/storage/src/tries/kvp.rs b/storage/src/tries/kvp.rs new file mode 100644 index 000000000000..f9a2a3b2121d --- /dev/null +++ b/storage/src/tries/kvp.rs @@ -0,0 +1,387 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#[cfg(not(feature = "branch_factor_256"))] +use crate::PackedPathRef; +use crate::{ + Children, HashType, PathBuf, PathComponent, PathGuard, SplitPath, TrieNode, TriePath, + TriePathFromPackedBytes, +}; + +#[cfg(feature = "branch_factor_256")] +type PackedPathRef<'a> = &'a [PathComponent]; + +/// A duplicate key error when merging two key-value tries. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] +#[error("duplicate keys found at path {}", path.display())] +pub struct DuplicateKeyError { + /// The path at which the duplicate keys were found. + pub path: PathBuf, +} + +/// The root of a key-value trie. +/// +/// The trie maps byte string keys to values of type `T`. +/// +/// The trie is a compressed prefix tree, where each node contains: +/// - A partial path (a sequence of path components) representing the path from +/// its parent to itself. +/// - An optional value of type `T` if the path to this node corresponds to a +/// key in the trie. +/// - A set of children nodes, each corresponding to a different path component +/// extending the current node's path. +pub struct KeyValueTrieRoot<'a, T: ?Sized> { + /// The partial path from this node's parent to itself. + pub partial_path: PackedPathRef<'a>, + /// The value associated with the path to this node, if any. + /// + /// If [`None`], this node does not correspond to a key in the trie and is + /// expected to have two or more children. If [`Some`], this node may have + /// zero or more children. + pub value: Option<&'a T>, + /// The children of this node, indexed by their leading path component. + pub children: Children>>, +} + +impl + ?Sized> std::fmt::Debug for KeyValueTrieRoot<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KeyValueTrieRoot") + .field("partial_path", &self.partial_path.display()) + .field("value", &DebugValue::new(self.value)) + .field("children", &DebugChildren::new(&self.children)) + .finish() + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> KeyValueTrieRoot<'a, T> { + /// Constructs a new leaf node with the given path and value. + #[must_use] + pub fn new_leaf(path: &'a [u8], value: &'a T) -> Box { + Box::new(Self { + partial_path: PackedPathRef::path_from_packed_bytes(path), + value: Some(value), + children: Children::new(), + }) + } + + /// Constructs a new key-value trie from the given slice of key-value pairs. + /// + /// For efficiency, the slice should be sorted by key; however, this is not + /// explicitly required. + /// + /// # Errors + /// + /// If duplicate keys are found, a [`DuplicateKeyError`] is returned. + pub fn from_slice(slice: &'a [(K, V)]) -> Result>, DuplicateKeyError> + where + K: AsRef<[u8]>, + V: AsRef, + { + match slice { + [] => Ok(None), + [(k, v)] => Ok(Some(Self::new_leaf(k.as_ref(), v.as_ref()))), + _ => { + let mid = slice.len() / 2; + let (lhs, rhs) = slice.split_at(mid); + let lhs = Self::from_slice(lhs)?; + let rhs = Self::from_slice(rhs)?; + Self::merge_root(PathGuard::new(&mut PathBuf::new_const()), lhs, rhs) + } + } + } + + /// Constructs a new internal node with the given two children. + /// + /// The two children must have different leading path components. + #[must_use] + fn new_siblings( + path: PackedPathRef<'a>, + lhs_path: PathComponent, + lhs: Box, + rhs_path: PathComponent, + rhs: Box, + ) -> Box { + debug_assert_ne!(lhs_path, rhs_path); + let mut children = Children::new(); + children[lhs_path] = Some(lhs); + children[rhs_path] = Some(rhs); + Box::new(Self { + partial_path: path, + value: None, + children, + }) + } + + /// Returns a new node with the same contents as `self` but with its path + /// replaced by the given path. + #[must_use] + fn with_path(mut self: Box, path: PackedPathRef<'a>) -> Box { + self.partial_path = path; + self + } + + /// Deeply merges two key-value tries, returning the merged trie. + fn merge_root( + leading_path: PathGuard<'_>, + lhs: Option>, + rhs: Option>, + ) -> Result>, DuplicateKeyError> { + match (lhs, rhs) { + (Some(l), Some(r)) => Self::merge(leading_path, l, r).map(Some), + (Some(node), None) | (None, Some(node)) => Ok(Some(node)), + (None, None) => Ok(None), + } + } + + /// Merges two disjoint tries, returning the merged trie. + fn merge( + leading_path: PathGuard<'_>, + lhs: Box, + rhs: Box, + ) -> Result, DuplicateKeyError> { + match lhs + .partial_path + .longest_common_prefix(rhs.partial_path) + .split_first_parts() + { + (None, None, _) => { + // both paths are identical, perform a deep merge of each child slot + Self::deep_merge(leading_path, lhs, rhs) + } + (Some((l, l_rest)), Some((r, r_rest)), path) => { + // the paths diverge at this component, create a new parent node + // with the two nodes as children + Ok(Self::new_siblings( + path, + l, + lhs.with_path(l_rest), + r, + rhs.with_path(r_rest), + )) + } + (Some((l, l_rest)), None, _) => { + // rhs is a prefix of lhs, so rhs becomes the parent of lhs + rhs.merge_child(leading_path, l, lhs.with_path(l_rest)) + } + (None, Some((r, r_rest)), _) => { + // lhs is a prefix of rhs, so lhs becomes the parent of rhs + lhs.merge_child(leading_path, r, rhs.with_path(r_rest)) + } + } + } + + /// Deeply merges two kvp nodes that have identical paths. + fn deep_merge( + mut leading_path: PathGuard<'_>, + mut lhs: Box, + #[expect(clippy::boxed_local)] mut rhs: Box, + ) -> Result, DuplicateKeyError> { + leading_path.extend(lhs.partial_path.components()); + + lhs.value = match (lhs.value.take(), rhs.value.take()) { + (Some(v), None) | (None, Some(v)) => Some(v), + (Some(_), Some(_)) => { + return Err(DuplicateKeyError { + path: leading_path.as_slice().into(), + }); + } + (None, None) => None, + }; + + lhs.children = lhs.children.merge(rhs.children, |pc, lhs, rhs| { + Self::merge_root(leading_path.fork_append(pc), lhs, rhs) + })?; + + Ok(lhs) + } + + /// Merges the given child into this node at the given prefix. + /// + /// If there is already a child at the given prefix, the two children + /// are recursively merged. + fn merge_child( + mut self: Box, + mut leading_path: PathGuard<'_>, + prefix: PathComponent, + child: Box, + ) -> Result, DuplicateKeyError> { + leading_path.extend(self.partial_path.components()); + leading_path.push(prefix); + + self.children[prefix] = Some(match self.children.take(prefix) { + Some(existing) => Self::merge(leading_path, existing, child)?, + None => child, + }); + + Ok(self) + } +} + +impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { + fn partial_path(&self) -> impl SplitPath + '_ { + self.partial_path + } + + fn value(&self) -> Option<&T> { + self.value + } + + fn child_hash(&self, pc: PathComponent) -> Option<&HashType> { + let _ = pc; + None + } + + fn child_node(&self, pc: PathComponent) -> Option<&Self> { + self.children[pc].as_deref() + } +} + +struct DebugValue<'a, T: ?Sized> { + value: Option<&'a T>, +} + +impl<'a, T: AsRef<[u8]> + ?Sized> DebugValue<'a, T> { + const fn new(value: Option<&'a T>) -> Self { + Self { value } + } +} + +impl + ?Sized> std::fmt::Debug for DebugValue<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #![expect(clippy::indexing_slicing)] + + const MAX_BYTES: usize = 32; + + let Some(value) = self.value else { + return write!(f, "None"); + }; + + let value = value.as_ref(); + let truncated = &value[..value.len().min(MAX_BYTES)]; + + let mut hex_buf = [0u8; MAX_BYTES * 2]; + let hex_buf = &mut hex_buf[..truncated.len().wrapping_mul(2)]; + hex::encode_to_slice(truncated, hex_buf).expect("exact fit"); + let s = str::from_utf8(hex_buf).expect("valid hex"); + + if truncated.len() < value.len() { + write!(f, "0x{s}... (len {})", value.len()) + } else { + write!(f, "0x{s}") + } + } +} + +struct DebugChildren<'a, T> { + children: &'a Children>, +} + +impl<'a, T: std::fmt::Debug> DebugChildren<'a, T> { + const fn new(children: &'a Children>) -> Self { + Self { children } + } +} + +impl std::fmt::Debug for DebugChildren<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + // if alternate, debug children as-is (which is pretty and recursive) + self.children.fmt(f) + } else { + // otherwise, replace each child with a continuation marker + self.children + .each_ref() + .map( + |_, child| { + if child.is_some() { "Some(...)" } else { "None" } + }, + ) + .fmt(f) + } + } +} + +#[cfg(test)] +mod tests { + #![expect(clippy::unwrap_used)] + + use test_case::test_case; + + use super::*; + + #[test_case(&[])] + #[test_case(&[("a", "1")])] + #[test_case(&[("a", "1"), ("b", "2")])] + #[test_case(&[("a", "1"), ("ab", "2")])] + #[test_case(&[("a", "1"), ("b", "2"), ("c", "3")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3")])] + #[test_case(&[("a", "1"), ("b", "2"), ("ba", "3")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("abc", "3")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5"), ("bb", "6")])] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5"), ("bb", "6"), ("c", "7")])] + fn test_trie_from_slice(slice: &[(&str, &str)]) { + let root = KeyValueTrieRoot::::from_slice(slice).unwrap(); + if slice.is_empty() { + if let Some(root) = root { + panic!("expected None, got {root:#?}"); + } + } else { + let root = root.unwrap(); + eprintln!("trie: {root:#?}"); + for ((kvp_path, kvp_value), &(slice_key, slice_value)) in root + .iter_values() + .zip(slice) + .chain(root.iter_values_desc().zip(slice.iter().rev())) + { + let slice_key = PackedPathRef::path_from_packed_bytes(slice_key.as_bytes()); + assert!( + kvp_path.path_eq(&slice_key), + "expected path {} got {}", + slice_key.display(), + kvp_path.display(), + ); + assert_eq!(kvp_value, slice_value); + } + } + } + + #[test] + fn test_trie_from_slice_duplicate_keys() { + let slice = [("a", "1"), ("ab", "2"), ("a", "3")]; + let err = KeyValueTrieRoot::::from_slice(&slice).unwrap_err(); + assert_eq!( + err, + DuplicateKeyError { + path: PathBuf::path_from_packed_bytes(b"a") + } + ); + } + + #[test] + fn test_trie_from_unsorted_slice() { + let slice = [("b", "2"), ("a", "1"), ("ab", "3")]; + let expected = [("a", "1"), ("ab", "3"), ("b", "2")]; + let root = KeyValueTrieRoot::::from_slice(&slice) + .unwrap() + .unwrap(); + eprintln!("trie: {root:#?}"); + + for ((kvp_path, kvp_value), &(slice_key, slice_value)) in root + .iter_values() + .zip(&expected) + .chain(root.iter_values_desc().zip(expected.iter().rev())) + { + let slice_key = PackedPathRef::path_from_packed_bytes(slice_key.as_bytes()); + assert!( + kvp_path.path_eq(&slice_key), + "expected path {} got {}", + slice_key.display(), + kvp_path.display(), + ); + assert_eq!(kvp_value, slice_value); + } + } +} diff --git a/storage/src/tries/mod.rs b/storage/src/tries/mod.rs new file mode 100644 index 000000000000..c309a6aa343b --- /dev/null +++ b/storage/src/tries/mod.rs @@ -0,0 +1,191 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +mod iter; +mod kvp; + +use crate::{HashType, PathComponent, SplitPath}; + +pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TrieValueIter}; +pub use self::kvp::{DuplicateKeyError, KeyValueTrieRoot}; + +/// The state of an edge from a parent node to a child node in a trie. +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum TrieEdgeState<'a, N: ?Sized> { + /// A child node that is fully known locally, along with its hash. + LocalChild { + /// The child node at this edge. + node: &'a N, + /// The hash of the child at this edge, as known to the parent. A locally + /// hashed child implements [`HashedTrieNode`]. It is possible for the + /// child's computed hash to differ from this hash if the local node has + /// incomplete information. + hash: &'a HashType, + }, + /// A child node that is not known locally, but whose hash is known to the + /// parent. + RemoteChild { + /// The hash of the remote child at this edge, as known to the parent. + hash: &'a HashType, + }, + /// A child node that is known locally, but whose hash is not known to the + /// parent. + UnhashedChild { + /// The child node at this edge. + node: &'a N, + }, +} + +/// A node in a fixed-arity radix trie. +pub trait TrieNode + ?Sized> { + /// The path from this node's parent to this node. + fn partial_path(&self) -> impl SplitPath + '_; + + /// The value stored at this node, if any. + fn value(&self) -> Option<&V>; + + /// The node-local hash of the child at the given path component, if any. + /// + /// This *may* be different from the child's computed hash the child has + /// missing information. + /// + /// A trie node may also have a child node without knowing its hash, in which + /// case this returns [`None`], but [`child_node`] will return [`Some`]. + /// + /// A trie node may also know the hash of a child without having a reference + /// to the child's node. In this case, this will return [`Some`], but + /// [`child_node`] return [`None`]. For example, this occurs in the proof + /// trie where a proof follows a linear path down the trie and only includes + /// the hashes of sibling nodes that branch off the path. + /// + /// [`child_node`]: TrieNode::child_node + fn child_hash(&self, pc: PathComponent) -> Option<&HashType>; + + /// The child node at the given path component, if any. + /// + /// See the documentation for [`child_hash`] for more details on the + /// relationship between these two methods. + /// + /// [`child_hash`]: TrieNode::child_hash + fn child_node(&self, pc: PathComponent) -> Option<&Self>; + + /// A combined view of the child node and its hash at the given path + /// component, if any. + /// + /// This is a combination of [`child_node`] and [`child_hash`], returning + /// a [`TrieEdgeState`] that describes which of the child node and + /// hash are known. + /// + /// [`child_node`]: TrieNode::child_node + /// [`child_hash`]: TrieNode::child_hash + fn child_state(&self, pc: PathComponent) -> Option> { + match (self.child_node(pc), self.child_hash(pc)) { + (Some(node), Some(hash)) => Some(TrieEdgeState::LocalChild { node, hash }), + (Some(node), None) => Some(TrieEdgeState::UnhashedChild { node }), + (None, Some(hash)) => Some(TrieEdgeState::RemoteChild { hash }), + (None, None) => None, + } + } + + /// Returns a breadth-first iterator over the edges in this trie in ascending + /// order. + /// + /// The returned iterator performs a pre-order traversal of the trie, yielding + /// each edge from parent to child before descending into the child node. The + /// children of each node are yielded in ascending order by path component. + fn iter_edges(&self) -> TrieEdgeIter<'_, Self, V, IterAscending> { + TrieEdgeIter::new(self, None) + } + + /// Returns a depth-first iterator over the edges in this trie in descending + /// order. + /// + /// The returned iterator performs a post-order traversal of the trie, yielding + /// each edge from parent to child after ascending back from the child node. + /// The children of each node are yielded in descending order by path component. + fn iter_edges_desc(&self) -> TrieEdgeIter<'_, Self, V, IterDescending> { + TrieEdgeIter::new(self, None) + } + + /// Returns an iterator over each key-value pair in this trie in ascending order. + fn iter_values(&self) -> TrieValueIter<'_, Self, V, IterAscending> { + self.iter_edges().node_values() + } + + /// Returns an iterator over each key-value pair in this trie in descending order. + fn iter_values_desc(&self) -> TrieValueIter<'_, Self, V, IterDescending> { + self.iter_edges_desc().node_values() + } +} + +/// A merkleized node in a fixed-arity radix trie. +pub trait HashedTrieNode + ?Sized>: TrieNode { + /// The computed hash of this node. + fn computed(&self) -> &HashType; +} + +impl<'a, N: ?Sized> TrieEdgeState<'a, N> { + const fn from_node(node: &'a N, hash: Option<&'a HashType>) -> Self { + match hash { + Some(hash) => TrieEdgeState::LocalChild { node, hash }, + None => TrieEdgeState::UnhashedChild { node }, + } + } + + fn value + ?Sized>(self) -> Option<&'a V> + where + N: TrieNode, + { + self.node().and_then(|n| n.value()) + } + + /// Returns `true` if this edge state represents a local child node with a known hash. + #[must_use] + pub const fn is_local(self) -> bool { + matches!(self, TrieEdgeState::LocalChild { .. }) + } + + /// Returns `true` if this edge state represents a remote child node with only a known hash. + #[must_use] + pub const fn is_remote(self) -> bool { + matches!(self, TrieEdgeState::RemoteChild { .. }) + } + + /// Returns `true` if this edge state represents a local child node without a known hash. + #[must_use] + pub const fn is_unhashed(self) -> bool { + matches!(self, TrieEdgeState::UnhashedChild { .. }) + } + + /// Returns the child node if it is known locally. + #[must_use] + pub const fn node(self) -> Option<&'a N> { + match self { + TrieEdgeState::LocalChild { node, .. } | TrieEdgeState::UnhashedChild { node } => { + Some(node) + } + TrieEdgeState::RemoteChild { .. } => None, + } + } + + /// Returns the hash of the child node if it is known. + #[must_use] + pub const fn hash(self) -> Option<&'a HashType> { + match self { + TrieEdgeState::LocalChild { hash, .. } | TrieEdgeState::RemoteChild { hash } => { + Some(hash) + } + TrieEdgeState::UnhashedChild { .. } => None, + } + } +} + +// auto-derived implementations would require N: Clone + Copy which is too much + +impl Clone for TrieEdgeState<'_, N> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for TrieEdgeState<'_, N> {} From 9ed71b121dae9de79465acda4254e935d68d193a Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 29 Oct 2025 11:51:47 -0700 Subject: [PATCH 1005/1053] feat: add hashed key-value trie implementation (#1365) This expands on the previously added key-value trie by adding a hashed variant and mechanism to convert from unhashed to hashed. --- storage/src/lib.rs | 4 +- storage/src/node/branch.rs | 14 +- storage/src/tries/kvp.rs | 294 ++++++++++++++++++++++++++++++++++++- storage/src/tries/mod.rs | 2 +- 4 files changed, 308 insertions(+), 6 deletions(-) diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 983018b004cd..0c290bb66dd8 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -65,8 +65,8 @@ pub use path::{ #[cfg(not(feature = "branch_factor_256"))] pub use path::{PackedBytes, PackedPathComponents, PackedPathRef}; pub use tries::{ - DuplicateKeyError, HashedTrieNode, IterAscending, IterDescending, KeyValueTrieRoot, - TrieEdgeIter, TrieEdgeState, TrieNode, TrieValueIter, + DuplicateKeyError, HashedKeyValueTrieRoot, HashedTrieNode, IterAscending, IterDescending, + KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode, TrieValueIter, }; pub use u4::{TryFromIntError, U4}; diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 95e5702b4a48..4d9222d0f4f3 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -193,7 +193,7 @@ mod ethhash { use super::Serializable; - #[derive(Clone, Debug)] + #[derive(Clone)] pub enum HashOrRlp { Hash(TrieHash), // TODO: this slice is never larger than 32 bytes so smallvec is probably not our best container @@ -201,6 +201,18 @@ mod ethhash { Rlp(SmallVec<[u8; 32]>), } + /// Manual implementation of [`Debug`](std::fmt::Debug) so that the RLP bytes + /// are displayed as hex rather than raw bytes, which is more useful for + /// debugging purposes. + impl std::fmt::Debug for HashOrRlp { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + HashOrRlp::Hash(h) => write!(f, "Hash({h})"), + HashOrRlp::Rlp(r) => write!(f, "Rlp({})", hex::encode(r)), + } + } + } + impl HashOrRlp { /// Creates a new `TrieHash` from the default value, which is the all zeros. /// diff --git a/storage/src/tries/kvp.rs b/storage/src/tries/kvp.rs index f9a2a3b2121d..1af0e65b5eb2 100644 --- a/storage/src/tries/kvp.rs +++ b/storage/src/tries/kvp.rs @@ -4,8 +4,8 @@ #[cfg(not(feature = "branch_factor_256"))] use crate::PackedPathRef; use crate::{ - Children, HashType, PathBuf, PathComponent, PathGuard, SplitPath, TrieNode, TriePath, - TriePathFromPackedBytes, + Children, HashType, Hashable, HashableShunt, HashedTrieNode, PathBuf, PathComponent, PathGuard, + SplitPath, TrieNode, TriePath, TriePathFromPackedBytes, ValueDigest, }; #[cfg(feature = "branch_factor_256")] @@ -44,6 +44,19 @@ pub struct KeyValueTrieRoot<'a, T: ?Sized> { pub children: Children>>, } +/// The root of a hashed key-value trie. +/// +/// This is similar to [`KeyValueTrieRoot`], but includes the computed hash of +/// the node as well as its leading path components. Consequently, the hashed +/// trie is formed by hashing the un-hashed trie. +pub struct HashedKeyValueTrieRoot<'a, T: ?Sized> { + computed: HashType, + leading_path: PathBuf, + partial_path: PackedPathRef<'a>, + value: Option<&'a T>, + children: Children>>, +} + impl + ?Sized> std::fmt::Debug for KeyValueTrieRoot<'_, T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("KeyValueTrieRoot") @@ -54,6 +67,18 @@ impl + ?Sized> std::fmt::Debug for KeyValueTrieRoot<'_, T> { } } +impl + ?Sized> std::fmt::Debug for HashedKeyValueTrieRoot<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HashedKeyValueTrieRoot") + .field("computed", &self.computed) + .field("leading_path", &self.leading_path.display()) + .field("partial_path", &self.partial_path.display()) + .field("value", &DebugValue::new(self.value)) + .field("children", &DebugChildren::new(&self.children)) + .finish() + } +} + impl<'a, T: AsRef<[u8]> + ?Sized> KeyValueTrieRoot<'a, T> { /// Constructs a new leaf node with the given path and value. #[must_use] @@ -216,6 +241,42 @@ impl<'a, T: AsRef<[u8]> + ?Sized> KeyValueTrieRoot<'a, T> { Ok(self) } + + /// Hashes this trie, returning a hashed trie. + #[must_use] + pub fn into_hashed_trie(self: Box) -> Box> { + HashedKeyValueTrieRoot::new(PathGuard::new(&mut PathBuf::new_const()), self) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> HashedKeyValueTrieRoot<'a, T> { + /// Constructs a new hashed key-value trie node from the given un-hashed + /// node. + #[must_use] + pub fn new( + mut leading_path: PathGuard<'_>, + #[expect(clippy::boxed_local)] node: Box>, + ) -> Box { + let children = node + .children + .map(|pc, child| child.map(|child| Self::new(leading_path.fork_append(pc), child))); + + Box::new(Self { + computed: HashableShunt::new( + leading_path.as_slice(), + node.partial_path, + node.value.map(|v| ValueDigest::Value(v.as_ref())), + children + .each_ref() + .map(|_, c| c.as_deref().map(|c| c.computed.clone())), + ) + .to_hash(), + leading_path: leading_path.as_slice().into(), + partial_path: node.partial_path, + value: node.value, + children, + }) + } } impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { @@ -235,6 +296,62 @@ impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { fn child_node(&self, pc: PathComponent) -> Option<&Self> { self.children[pc].as_deref() } + + fn child_state(&self, pc: PathComponent) -> Option> { + self.children[pc] + .as_deref() + .map(|node| super::TrieEdgeState::UnhashedChild { node }) + } +} + +impl + ?Sized> TrieNode for HashedKeyValueTrieRoot<'_, T> { + fn partial_path(&self) -> impl SplitPath + '_ { + self.partial_path + } + + fn value(&self) -> Option<&T> { + self.value + } + + fn child_hash(&self, pc: PathComponent) -> Option<&HashType> { + self.children[pc].as_deref().map(|c| &c.computed) + } + + fn child_node(&self, pc: PathComponent) -> Option<&Self> { + self.children[pc].as_deref() + } + + fn child_state(&self, pc: PathComponent) -> Option> { + self.children[pc] + .as_deref() + .map(|node| super::TrieEdgeState::from_node(node, Some(&node.computed))) + } +} + +impl + ?Sized> HashedTrieNode for HashedKeyValueTrieRoot<'_, T> { + fn computed(&self) -> &HashType { + &self.computed + } +} + +impl + ?Sized> Hashable for HashedKeyValueTrieRoot<'_, T> { + fn parent_prefix_path(&self) -> impl crate::IntoSplitPath + '_ { + self.leading_path.as_slice() + } + + fn partial_path(&self) -> impl crate::IntoSplitPath + '_ { + self.partial_path + } + + fn value_digest(&self) -> Option> { + self.value.map(|v| ValueDigest::Value(v.as_ref())) + } + + fn children(&self) -> Children> { + self.children + .each_ref() + .map(|_, c| c.as_deref().map(|c| c.computed.clone())) + } } struct DebugValue<'a, T: ?Sized> { @@ -310,6 +427,114 @@ mod tests { use super::*; + /// In constant context, convert an ASCII hex string to a byte array. + /// + /// `FROM` must be exactly twice `TO`. This is workaround because generic + /// parameters cannot be used in constant expressions yet (see [upstream]). + /// + /// The [`expected_hash`] macro is able to work around this limitation by + /// evaluating the length at macro expansion time which provides a constant + /// value for both generic parameters at compile time. + /// + /// # Panics + /// + /// Panics if the input is not valid hex. Because this panic occurs in constant + /// context, this will result in an "erroneous constant error" during compilation. + /// + /// [upstream]: https://github.com/rust-lang/rust/issues/76560 + const fn from_ascii(hex: &[u8; FROM]) -> [u8; TO] { + #![expect(clippy::arithmetic_side_effects, clippy::indexing_slicing)] + + const fn from_hex_char(c: u8) -> u8 { + match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + b'A'..=b'F' => c - b'A' + 10, + _ => panic!("invalid hex character"), + } + } + + const { + assert!(FROM == TO.wrapping_mul(2)); + } + + let mut bytes = [0u8; TO]; + let mut i = 0_usize; + while i < TO { + let off = i.wrapping_mul(2); + let hi = hex[off]; + let off = off.wrapping_add(1); + let lo = hex[off]; + bytes[i] = (from_hex_char(hi) << 4) | from_hex_char(lo); + i += 1; + } + + bytes + } + + /// A macro to select the expected hash based on the enabled cargo features. + /// + /// For both merkledb hash types, only a 64-character hex string (32 bytes) + /// is expected. For the ethereum hash type, either a 64-character hex string + /// or an RLP-encoded byte string of arbitrary length can be provided, if + /// wrapped in `rlp(...)`. + /// + /// # Example + /// + /// ```ignore + /// expected_hash!{ + /// merkledb16: b"749390713e51d3e4e50ba492a669c1644a6d9cb7e48b2a14d556e7f953da92fc", + /// merkledb256: b"30dbf15b59c97d2997f4fbed1ae86d1eab8e7aa2dd84337029fe898f47aeb8e6", + /// ethereum: b"2e636399fae96dc07abaf21167a34b8a5514d6594e777635987e319c76f28a75", + /// } + /// ``` + /// + /// or with RLP: + /// + /// ```ignore + /// expected_hash!{ + /// merkledb16: b"1ffe11ce995a9c07021d6f8a8c5b1817e6375dd0ea27296b91a8d48db2858bc9", + /// merkledb256: b"831a115e52af616bd2df8cd7a0993e21e544d7d201e151a7f61dcdd1a6bd557c", + /// ethereum: rlp(b"c482206131"), + /// } + /// ``` + macro_rules! expected_hash { + ( + merkledb16: $hex16:expr, + merkledb256: $hex256:expr, + ethereum: rlp($hexeth:expr), + ) => { + match () { + #[cfg(all(not(feature = "branch_factor_256"), not(feature = "ethhash")))] + () => $crate::HashType::from(from_ascii($hex16)), + #[cfg(all(feature = "branch_factor_256", not(feature = "ethhash")))] + () => $crate::HashType::from(from_ascii($hex256)), + #[cfg(all(not(feature = "branch_factor_256"), feature = "ethhash"))] + () => $crate::HashType::Rlp(smallvec::SmallVec::from( + &from_ascii::<{ $hexeth.len() }, { $hexeth.len() / 2 }>($hexeth)[..], + )), + #[cfg(all(feature = "branch_factor_256", feature = "ethhash"))] + () => compile_error!("branch_factor_256 and ethhash cannot both be enabled"), + } + }; + ( + merkledb16: $hex16:expr, + merkledb256: $hex256:expr, + ethereum: $hexeth:expr, + ) => { + $crate::HashType::from(from_ascii(match () { + #[cfg(all(not(feature = "branch_factor_256"), not(feature = "ethhash")))] + () => $hex16, + #[cfg(all(feature = "branch_factor_256", not(feature = "ethhash")))] + () => $hex256, + #[cfg(all(not(feature = "branch_factor_256"), feature = "ethhash"))] + () => $hexeth, + #[cfg(all(feature = "branch_factor_256", feature = "ethhash"))] + () => compile_error!("branch_factor_256 and ethhash cannot both be enabled"), + })) + }; + } + #[test_case(&[])] #[test_case(&[("a", "1")])] #[test_case(&[("a", "1"), ("b", "2")])] @@ -384,4 +609,69 @@ mod tests { assert_eq!(kvp_value, slice_value); } } + + #[test_case(&[("a", "1")], expected_hash!{ + merkledb16: b"1ffe11ce995a9c07021d6f8a8c5b1817e6375dd0ea27296b91a8d48db2858bc9", + merkledb256: b"831a115e52af616bd2df8cd7a0993e21e544d7d201e151a7f61dcdd1a6bd557c", + ethereum: rlp(b"c482206131"), + }; "single key")] + #[test_case(&[("a", "1"), ("b", "2")], expected_hash!{ + merkledb16: b"ff783ce73f7a5fa641991d76d626eefd7840a839590db4269e1e92359ae60593", + merkledb256: b"301e9035ef0fe1b50788f9b5bca3a2c19bce9c798bdb1dda09fc71dd22564ce4", + ethereum: rlp(b"d81696d580c22031c220328080808080808080808080808080"), + }; "two disjoint keys")] + #[test_case(&[("a", "1"), ("ab", "2")], expected_hash!{ + merkledb16: b"c5def8c64a2f3b8647283251732b68a2fb185f8bf92c0103f31d5ec69bb9a90c", + merkledb256: b"2453f6f0b38fd36bcb66b145aff0f7ae3a6b96121fa1187d13afcffa7641b156", + ethereum: rlp(b"d882006194d3808080808080c2323280808080808080808031"), + }; "two nested keys")] + #[test_case(&[("a", "1"), ("b", "2"), ("c", "3")], expected_hash!{ + merkledb16: b"95618fd79a0ca2d7612bf9fd60663b81f632c9a65e76bb5bc3ed5f3045cf1404", + merkledb256: b"f5c185a96ed86da8da052a52f6c2e7368c90d342c272dd0e6c9e72c0071cdb0c", + ethereum: rlp(b"da1698d780c22031c22032c2203380808080808080808080808080"), + }; "three disjoint keys")] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3")], expected_hash!{ + merkledb16: b"ee8a7a1409935f58ab6ce40a1e05ee2a587bdc06c201dbec7006ee1192e71f70", + merkledb256: b"40c9cee60ac59e7926109137fbaa5d68642d4770863b150f98bd8ac00aedbff3", + ethereum: b"6ffab67bf7096a9608b312b9b2459c17ec9429286b283a3b3cdaa64860182699", + }; "two children of same parent")] + #[test_case(&[("a", "1"), ("b", "2"), ("ba", "3")], expected_hash!{ + merkledb16: b"d3efab83a1a4dd193c8ae51dfe638bba3494d8b1917e7a9185d20301ff1c528b", + merkledb256: b"e6f711e762064ffcc7276e9c6149fc8f1050e009a21e436e7b78a4a60079e3ba", + ethereum: b"21a118e1765c556e505a8752a0fd5bbb4ea78fb21077f8488d42862ebabf0130", + }; "nested sibling")] + #[test_case(&[("a", "1"), ("ab", "2"), ("abc", "3")], expected_hash!{ + merkledb16: b"af11454e2f920fb49041c9890c318455952d651b7d835f5731218dbc4bde4805", + merkledb256: b"5dc43e88b3019050e741be52ed4afff621e1ac93cd2c68d37f82947d1d16cff5", + ethereum: b"eabecb5e4efb9b5824cd926fac6350bdcb4a599508b16538afde303d72571169", + }; "linear nested keys")] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4")], expected_hash!{ + merkledb16: b"749390713e51d3e4e50ba492a669c1644a6d9cb7e48b2a14d556e7f953da92fc", + merkledb256: b"30dbf15b59c97d2997f4fbed1ae86d1eab8e7aa2dd84337029fe898f47aeb8e6", + ethereum: b"2e636399fae96dc07abaf21167a34b8a5514d6594e777635987e319c76f28a75", + }; "four keys")] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5")], expected_hash!{ + merkledb16: b"1c043978de0cd65fe2e75a74eaa98878b753f4ec20f6fbbb7232a39f02e88c6f", + merkledb256: b"02bb75b5d5b81ba4c64464a5e39547de4e0d858c04da4a4aae9e63fc8385279d", + ethereum: b"df930bafb34edb6d758eb5f4dd9461fc259c8c13abf38da8a0f63f289e107ecd", + }; "five keys")] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5"), ("bb", "6")], expected_hash!{ + merkledb16: b"c2c13c095f7f07ce9ef92401f73951b4846a19e2b092b8a527fe96fa82f55cfd", + merkledb256: b"56d69386ad494d6be42bbdd78b3ad00c07c12e631338767efa1539d6720ce7a6", + ethereum: b"8ca7c3b09aa0a8877122d67fd795051bd1e6ff169932e3b7a1158ed3d66fbedf", + }; "six keys")] + #[test_case(&[("a", "1"), ("ab", "2"), ("ac", "3"), ("b", "4"), ("ba", "5"), ("bb", "6"), ("c", "7")], expected_hash!{ + merkledb16: b"697e767d6f4af8236090bc95131220c1c94cadba3e66e0a8011c9beef7b255a5", + merkledb256: b"2f083246b86da1e6e135f771ae712f271c1162c23ebfaa16178ea57f0317bf06", + ethereum: b"3fa832b90f7f1a053a48a4528d1e446cc679fbcf376d0ef8703748d64030e19d", + }; "seven keys")] + fn test_hashed_trie(slice: &[(&str, &str)], root_hash: crate::HashType) { + let root = KeyValueTrieRoot::::from_slice(slice) + .unwrap() + .unwrap() + .into_hashed_trie(); + + assert_eq!(*root.computed(), root_hash); + assert_eq!(*root.computed(), crate::Preimage::to_hash(&*root)); + } } diff --git a/storage/src/tries/mod.rs b/storage/src/tries/mod.rs index c309a6aa343b..f39ca0d792c1 100644 --- a/storage/src/tries/mod.rs +++ b/storage/src/tries/mod.rs @@ -7,7 +7,7 @@ mod kvp; use crate::{HashType, PathComponent, SplitPath}; pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TrieValueIter}; -pub use self::kvp::{DuplicateKeyError, KeyValueTrieRoot}; +pub use self::kvp::{DuplicateKeyError, HashedKeyValueTrieRoot, KeyValueTrieRoot}; /// The state of an edge from a parent node to a child node in a trie. #[derive(Debug, PartialEq, Eq, Hash)] From a35f4a7688d1d98641b122ed0b4b593c2300ed1b Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 31 Oct 2025 10:35:07 -0700 Subject: [PATCH 1006/1053] chore: fix new lint warning from 1.91 update (#1417) 1.91 was released and introduced a new set of lint warnings that are interfering with CI. New lint warning triggers because we were using std::slice::from_raw_parts_mut to construct a slice and immediately turn back into a (fat) pointer. --- ffi/src/value/owned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/src/value/owned.rs b/ffi/src/value/owned.rs index caa2c7cd7c44..ca268cc2e950 100644 --- a/ffi/src/value/owned.rs +++ b/ffi/src/value/owned.rs @@ -52,7 +52,7 @@ impl OwnedSlice { // it from when we originally created the `OwnedSlice`. The owned slice was created // from a `Box::leak`, so we can safely convert it back using `Box::from_raw`. Some(ptr) => unsafe { - Box::from_raw(std::slice::from_raw_parts_mut(ptr.as_ptr(), self.len)) + Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr.as_ptr(), self.len)) }, None => Box::new([]), } From db78de9b2f83158d46b398e2f9fe4cf91b3cfd45 Mon Sep 17 00:00:00 2001 From: Ron Kuris Date: Fri, 31 Oct 2025 14:08:32 -0700 Subject: [PATCH 1007/1053] fix(storage): flush freelist early to prevent corruption (#1389) Fix a critical bug where nodes were written to storage before the freelist was updated, leaving the database in an inconsistent state if an I/O error occurred during node persistence. Problem: When persisting nodes, if the freelist flush happened after node writes, a crash or I/O error between node writes and freelist flush would leave allocated storage space untracked in the freelist. On recovery, these areas could be reallocated, leading to data corruption or loss. Solution: - Flush freelist immediately after allocating node addresses but before writing any node data - Add NodeAllocator::flush_freelist() to enable explicit freelist updates - Add flush_freelist_from() to persist a specific header's freelist state Additional improvements: - Refactor to use bumpalo bump allocator with bounded memory for node serialization (prevents unbounded memory growth) - Extract common serialization and batching logic into shared helper functions (serialize_node_to_bump, process_unpersisted_nodes) - Eliminate ~90 lines of code duplication between io-uring and non-io-uring paths while ensuring both use identical logic --- Cargo.lock | 1 + storage/Cargo.toml | 1 + storage/src/node/mod.rs | 15 + storage/src/nodestore/alloc.rs | 7 + storage/src/nodestore/mod.rs | 6 + storage/src/nodestore/persist.rs | 419 ++++++++-------------- storage/src/nodestore/persist_io_uring.rs | 215 +++++++++++ 7 files changed, 387 insertions(+), 277 deletions(-) create mode 100644 storage/src/nodestore/persist_io_uring.rs diff --git a/Cargo.lock b/Cargo.lock index 1174a5421fd0..ddf0cd03181a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1154,6 +1154,7 @@ dependencies = [ "arc-swap", "bitfield", "bitflags 2.9.4", + "bumpalo", "bytemuck", "bytemuck_derive", "bytes", diff --git a/storage/Cargo.toml b/storage/Cargo.toml index f426c6ee4286..3519e033eefa 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -47,6 +47,7 @@ io-uring = { version = "0.7.10", optional = true } log = { version = "0.4.28", optional = true } rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } +bumpalo = { version = "3.19.0", features = ["collections", "std"] } [dev-dependencies] # Workspace dependencies diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index 5f032b7713d5..acb28cfa5313 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -149,6 +149,21 @@ impl ExtendableBytes for Vec { } } +impl ExtendableBytes for bumpalo::collections::Vec<'_, u8> { + fn extend>(&mut self, other: T) { + std::iter::Extend::extend(self, other); + } + fn reserve(&mut self, reserve: usize) { + bumpalo::collections::Vec::reserve(self, reserve); + } + fn push(&mut self, value: u8) { + bumpalo::collections::Vec::push(self, value); + } + fn extend_from_slice(&mut self, other: &[u8]) { + bumpalo::collections::Vec::extend_from_slice(self, other); + } +} + impl Node { /// Returns the partial path of the node. #[must_use] diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index 886b064d5303..fc43c2dce624 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -358,6 +358,13 @@ impl NodeAllocator<'_, S> { Ok(()) } + + pub fn flush_freelist(&mut self) -> Result<(), FileIoError> { + let free_list_bytes = bytemuck::bytes_of(self.header.free_lists()); + let free_list_offset = NodeStoreHeader::free_lists_offset(); + self.storage.write(free_list_offset, free_list_bytes)?; + Ok(()) + } } /// Iterator over free lists in the nodestore diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index c3003bc0939f..ec76989c498e 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -42,6 +42,8 @@ pub(crate) mod alloc; pub(crate) mod hash; pub(crate) mod header; pub(crate) mod persist; +#[cfg(feature = "io-uring")] +pub(crate) mod persist_io_uring; pub(crate) mod primitives; use crate::linear::OffsetReader; @@ -95,6 +97,10 @@ use crate::{ use super::linear::WritableStorage; +/// Initial size for the bump allocator used in node serialization batches. +/// Set to the maximum area size to minimize allocations for large nodes. +const INITIAL_BUMP_SIZE: usize = AreaIndex::MAX_AREA_SIZE as usize; + impl NodeStore { /// Open an existing [`NodeStore`] /// Assumes the header is written in the [`ReadableStorage`]. diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index f16342ba58b4..a4d70ceddd3d 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -34,17 +34,11 @@ use crate::nodestore::AreaIndex; use crate::{Child, firewood_counter}; use coarsetime::Instant; -#[cfg(feature = "io-uring")] -use crate::logger::trace; - -use crate::{FileBacked, MaybePersistedNode, NodeReader, WritableStorage}; +use crate::{MaybePersistedNode, NodeReader, WritableStorage}; #[cfg(test)] use crate::RootReader; -#[cfg(feature = "io-uring")] -use crate::ReadableStorage; - use super::alloc::NodeAllocator; use super::header::NodeStoreHeader; use super::{Committed, NodeStore}; @@ -84,10 +78,22 @@ impl NodeStore { /// # Errors /// /// Returns a [`FileIoError`] if the free list cannot be written to storage. - #[fastrace::trace(short_name = true)] pub fn flush_freelist(&self) -> Result<(), FileIoError> { - // Write the free lists to storage - let free_list_bytes = bytemuck::bytes_of(self.header.free_lists()); + self.flush_freelist_from(&self.header) + } + + /// Persist the freelist from the given header to storage + /// + /// This function is used to ensure that the freelist is advanced after allocating + /// nodes for writing. This allows the database to be recovered from an I/O error while + /// persisting a revision to disk. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if the free list cannot be written to storage. + #[fastrace::trace(name = "firewood.flush_freelist")] + pub(crate) fn flush_freelist_from(&self, header: &NodeStoreHeader) -> Result<(), FileIoError> { + let free_list_bytes = bytemuck::bytes_of(header.free_lists()); let free_list_offset = NodeStoreHeader::free_lists_offset(); self.storage.write(free_list_offset, free_list_bytes)?; Ok(()) @@ -191,6 +197,80 @@ impl Iterator for UnPersistedNodeIterator<'_, N> { } } +/// Helper function to serialize a node into a bump allocator and allocate storage for it +/// +/// # Errors +/// +/// Returns a [`FileIoError`] if the node cannot be allocated in storage. +fn serialize_node_to_bump<'a>( + bump: &'a bumpalo::Bump, + shared_node: &crate::SharedNode, + node_allocator: &mut NodeAllocator<'_, impl WritableStorage>, +) -> Result<(&'a [u8], crate::LinearAddress, usize), FileIoError> { + let mut bytes = bumpalo::collections::Vec::new_in(bump); + shared_node.as_bytes(AreaIndex::MIN, &mut bytes); + let (persisted_address, area_size_index) = node_allocator.allocate_node(bytes.as_slice())?; + *bytes.get_mut(0).expect("byte was reserved") = area_size_index.get(); + bytes.shrink_to_fit(); + let slice = bytes.into_bump_slice(); + Ok((slice, persisted_address, area_size_index.size() as usize)) +} + +/// Helper function to process unpersisted nodes with batching and overflow detection +/// +/// This function iterates through all unpersisted nodes, serializes them into a bump allocator, +/// and flushes them in batches when the bump allocator is about to overflow. +/// +/// # Errors +/// +/// Returns a [`FileIoError`] if any node cannot be serialized, allocated, or written to storage. +pub(super) fn process_unpersisted_nodes( + bump: &mut bumpalo::Bump, + node_allocator: &mut NodeAllocator<'_, S>, + node_store: &N, + bump_size_limit: usize, + mut write_fn: F, +) -> Result<(), FileIoError> +where + N: NodeReader + RootReader, + S: WritableStorage, + F: FnMut(Vec<(&[u8], crate::LinearAddress, MaybePersistedNode)>) -> Result<(), FileIoError>, +{ + let mut allocated_objects = Vec::new(); + + // Process each unpersisted node directly from the iterator + for node in UnPersistedNodeIterator::new(node_store) { + let shared_node = node + .as_shared_node(node_store) + .expect("in memory, so no IO"); + + // Serialize the node into the bump allocator + let (slice, persisted_address, idx_size) = + serialize_node_to_bump(bump, &shared_node, node_allocator)?; + + allocated_objects.push((slice, persisted_address, node)); + + // we pause if we can't allocate another node of the same size as the last one + // This isn't a guarantee that we won't exceed bump_size_limit + // but it's a good enough approximation + let might_overflow = bump.allocated_bytes() > bump_size_limit.saturating_sub(idx_size); + if might_overflow { + // must persist freelist before writing anything + node_allocator.flush_freelist()?; + write_fn(allocated_objects)?; + allocated_objects = Vec::new(); + bump.reset(); + } + } + if !allocated_objects.is_empty() { + // Flush the freelist using the node_allocator before the final write + node_allocator.flush_freelist()?; + write_fn(allocated_objects)?; + } + + Ok(()) +} + impl NodeStore { /// Persist all the nodes of a proposal to storage. /// @@ -199,30 +279,30 @@ impl NodeStore { /// Returns a [`FileIoError`] if any node cannot be written to storage. #[fastrace::trace(short_name = true)] pub fn flush_nodes(&mut self) -> Result { - #[cfg(feature = "io-uring")] - if let Some(this) = self.downcast_to_file_backed() { - return this.flush_nodes_io_uring(); - } - self.flush_nodes_generic() - } + let flush_start = Instant::now(); + + let header = { + #[cfg(feature = "io-uring")] + { + use crate::FileBacked; + // Try to use io-uring if available and storage type supports it + let this = self as &mut dyn std::any::Any; + if let Some(file_backed) = this.downcast_mut::>() { + file_backed.flush_nodes_io_uring()? + } else { + self.flush_nodes_generic()? + } + } + #[cfg(not(feature = "io-uring"))] + { + self.flush_nodes_generic()? + } + }; - #[cfg(feature = "io-uring")] - #[inline] - fn downcast_to_file_backed(&mut self) -> Option<&mut NodeStore> { - /* - * FIXME(rust-lang/rfcs#1210, rust-lang/rust#31844): - * - * This is a slight hack that exists because rust trait specialization - * is not yet stable. If the `io-uring` feature is enabled, we attempt to - * downcast `self` into a `NodeStore`, and if successful, - * we call the specialized `flush_nodes_io_uring` method. - * - * During monomorphization, this will be completely optimized out as the - * type id comparison is done with constants that the compiler can resolve - * and use to detect dead branches. - */ - let this = self as &mut dyn std::any::Any; - this.downcast_mut::>() + let flush_time = flush_start.elapsed().as_millis(); + firewood_counter!("firewood.flush_nodes", "amount flushed nodes").increment(flush_time); + + Ok(header) } /// Persist all the nodes of a proposal to storage. @@ -231,23 +311,37 @@ impl NodeStore { /// /// Returns a [`FileIoError`] if any node cannot be written to storage. fn flush_nodes_generic(&self) -> Result { - let flush_start = Instant::now(); + use bumpalo::Bump; + + let mut header = self.header; + let mut node_allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); + let mut bump = Bump::with_capacity(super::INITIAL_BUMP_SIZE); + + process_unpersisted_nodes( + &mut bump, + &mut node_allocator, + self, + super::INITIAL_BUMP_SIZE, + |allocated_objects| self.write_nodes_generic(allocated_objects), + )?; + + Ok(header) + } - // keep MaybePersistedNodes to add them to cache and persist them + /// Write a batch of serialized nodes to storage + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if any node cannot be written to storage. + fn write_nodes_generic( + &self, + allocated_objects: Vec<(&[u8], crate::LinearAddress, MaybePersistedNode)>, + ) -> Result<(), FileIoError> { + // Collect addresses and nodes for caching let mut cached_nodes = Vec::new(); - let mut header = self.header; - let mut allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); - for node in UnPersistedNodeIterator::new(self) { - let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); - let mut serialized = Vec::new(); - shared_node.as_bytes(AreaIndex::MIN, &mut serialized); - - let (persisted_address, area_size_index) = - allocator.allocate_node(serialized.as_slice())?; - *serialized.get_mut(0).expect("byte was reserved") = area_size_index.get(); - self.storage - .write(persisted_address.get(), serialized.as_slice())?; + for (serialized, persisted_address, node) in allocated_objects { + self.storage.write(persisted_address.get(), serialized)?; // Allocate the node to store the address, then collect for caching and persistence node.allocate_at(persisted_address); @@ -256,10 +350,7 @@ impl NodeStore { self.storage.write_cached_nodes(cached_nodes)?; - let flush_time = flush_start.elapsed().as_millis(); - firewood_counter!("firewood.flush_nodes", "flushed node amount").increment(flush_time); - - Ok(header) + Ok(()) } } @@ -290,197 +381,6 @@ impl NodeStore { } } -impl NodeStore { - /// Persist all the nodes of a proposal to storage. - /// - /// # Errors - /// - /// Returns a [`FileIoError`] if any node cannot be written to storage. - #[fastrace::trace(short_name = true)] - #[cfg(feature = "io-uring")] - fn flush_nodes_io_uring(&mut self) -> Result { - use crate::LinearAddress; - use std::io::ErrorKind::Interrupted; - use std::pin::Pin; - - #[derive(Clone, Debug)] - struct PinnedBufferEntry { - pinned_buffer: Pin>, - node: Option<(LinearAddress, MaybePersistedNode)>, - } - - /// Helper function to retry `submit_and_wait` on EINTR - fn submit_and_wait_with_retry( - ring: &mut io_uring::IoUring, - wait_nr: u32, - storage: &FileBacked, - operation_name: &str, - ) -> Result<(), FileIoError> { - loop { - match ring.submit_and_wait(wait_nr as usize) { - Ok(_) => return Ok(()), - Err(e) => { - // Retry if the error is an interrupted system call - if e.kind() == Interrupted { - continue; - } - // For other errors, return the error - return Err(storage.file_io_error( - e, - 0, - Some(format!("io-uring {operation_name}")), - )); - } - } - } - } - - /// Helper function to handle completion queue entries and check for errors - fn handle_completion_queue( - storage: &FileBacked, - completion_queue: io_uring::cqueue::CompletionQueue<'_>, - saved_pinned_buffers: &mut [PinnedBufferEntry], - ) -> Result<(), FileIoError> { - for entry in completion_queue { - let item = entry.user_data() as usize; - let pbe = saved_pinned_buffers - .get_mut(item) - .expect("should be an index into the array"); - - if entry.result() - != pbe - .pinned_buffer - .len() - .try_into() - .expect("buffer should be small enough") - { - let error = if entry.result() >= 0 { - std::io::Error::other("Partial write") - } else { - std::io::Error::from_raw_os_error(0 - entry.result()) - }; - let (addr, _) = pbe.node.as_ref().expect("node should be Some"); - return Err(storage.file_io_error( - error, - addr.get(), - Some("write failure".to_string()), - )); - } - // I/O completed successfully - pbe.node = None; - } - Ok(()) - } - - const RINGSIZE: usize = FileBacked::RINGSIZE as usize; - - let flush_start = Instant::now(); - - let mut header = self.header; - let mut node_allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); - - // Collect addresses and nodes for caching - let mut cached_nodes = Vec::new(); - - let mut ring = self.storage.ring.lock().expect("poisoned lock"); - let mut saved_pinned_buffers = vec![ - PinnedBufferEntry { - pinned_buffer: Pin::new(Box::new([0; 0])), - node: None, - }; - RINGSIZE - ]; - - // Process each unpersisted node directly from the iterator - for node in UnPersistedNodeIterator::new(self) { - let shared_node = node.as_shared_node(self).expect("in memory, so no IO"); - let mut serialized = Vec::with_capacity(100); // TODO: better size? we can guess branches are larger - shared_node.as_bytes(AreaIndex::MIN, &mut serialized); - let (persisted_address, area_size_index) = - node_allocator.allocate_node(serialized.as_slice())?; - *serialized.get_mut(0).expect("byte was reserved") = area_size_index.get(); - let mut serialized = serialized.into_boxed_slice(); - - loop { - // Find the first available write buffer, enumerate to get the position for marking it completed - if let Some((pos, pbe)) = saved_pinned_buffers - .iter_mut() - .enumerate() - .find(|(_, pbe)| pbe.node.is_none()) - { - pbe.pinned_buffer = std::pin::Pin::new(std::mem::take(&mut serialized)); - pbe.node = Some((persisted_address, node.clone())); - - let submission_queue_entry = self - .storage - .make_op(&pbe.pinned_buffer) - .offset(persisted_address.get()) - .build() - .user_data(pos as u64); - - #[expect(unsafe_code)] - // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope - // until the operation has been completed. This is ensured by having a Some(offset) - // and not marking it None until the kernel has said it's done below. - while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { - ring.submitter().squeue_wait().map_err(|e| { - self.storage.file_io_error( - e, - persisted_address.get(), - Some("io-uring squeue_wait".to_string()), - ) - })?; - trace!("submission queue is full"); - firewood_counter!("ring.full", "amount of full ring").increment(1); - } - break; - } - // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation - // to complete, then handle the completion queue - firewood_counter!("ring.full", "amount of full ring").increment(1); - submit_and_wait_with_retry(&mut ring, 1, &self.storage, "submit_and_wait")?; - let completion_queue = ring.completion(); - trace!("competion queue length: {}", completion_queue.len()); - handle_completion_queue( - &self.storage, - completion_queue, - &mut saved_pinned_buffers, - )?; - } - - // Allocate the node to store the address, then collect for caching and persistence - node.allocate_at(persisted_address); - cached_nodes.push(node); - } - let pending = saved_pinned_buffers - .iter() - .filter(|pbe| pbe.node.is_some()) - .count(); - submit_and_wait_with_retry( - &mut ring, - pending as u32, - &self.storage, - "final submit_and_wait", - )?; - - handle_completion_queue(&self.storage, ring.completion(), &mut saved_pinned_buffers)?; - - debug_assert!( - !saved_pinned_buffers.iter().any(|pbe| pbe.node.is_some()), - "Found entry with node still set: {:?}", - saved_pinned_buffers.iter().find(|pbe| pbe.node.is_some()) - ); - - self.storage.write_cached_nodes(cached_nodes)?; - debug_assert!(ring.completion().is_empty()); - - let flush_time = flush_start.elapsed().as_millis(); - firewood_counter!("firewood.flush_nodes", "amount flushed nodes").increment(flush_time); - - Ok(header) - } -} - #[cfg(test)] #[expect(clippy::unwrap_used, clippy::indexing_slicing)] mod tests { @@ -767,39 +667,4 @@ mod tests { assert_eq!(*child2_node.partial_path(), Path::from(&[4, 5, 6])); assert_eq!(child2_node.value(), Some(&b"value2"[..])); } - - #[cfg(feature = "io-uring")] - #[test] - fn test_downcast_to_file_backed() { - use nonzero_ext::nonzero; - - use crate::CacheReadStrategy; - - { - let tf = tempfile::NamedTempFile::new().unwrap(); - let path = tf.path().to_owned(); - - let fb = Arc::new( - FileBacked::new( - path, - nonzero!(10usize), - nonzero!(10usize), - false, - true, - CacheReadStrategy::WritesOnly, - ) - .unwrap(), - ); - - let mut ns = NodeStore::new_empty_committed(fb.clone()); - - assert!(ns.downcast_to_file_backed().is_some()); - } - - { - let ms = Arc::new(MemStore::new(vec![])); - let mut ns = NodeStore::new_empty_committed(ms.clone()); - assert!(ns.downcast_to_file_backed().is_none()); - } - } } diff --git a/storage/src/nodestore/persist_io_uring.rs b/storage/src/nodestore/persist_io_uring.rs new file mode 100644 index 000000000000..ca1bc993d0c4 --- /dev/null +++ b/storage/src/nodestore/persist_io_uring.rs @@ -0,0 +1,215 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +//! # io-uring Persistence Module +//! +//! This module contains io-uring-specific implementations for batch node persistence. +//! It is only compiled when the `io-uring` feature is enabled. + +use super::alloc::NodeAllocator; +use super::header::NodeStoreHeader; +use super::persist::process_unpersisted_nodes; +use super::{Committed, NodeStore}; +use crate::LinearAddress; +use crate::linear::{FileIoError, ReadableStorage, WritableStorage}; +use crate::logger::trace; +use crate::{FileBacked, MaybePersistedNode, firewood_counter}; + +/// Entry in the pinned buffer array tracking in-flight io-uring operations +#[derive(Clone, Debug)] +struct BufferEntry<'a> { + buffer: &'a [u8], + address: LinearAddress, + node: MaybePersistedNode, +} + +/// Helper function to retry `submit_and_wait` on EINTR +fn submit_and_wait_with_retry( + ring: &mut io_uring::IoUring, + wait_nr: u32, + storage: &FileBacked, + operation_name: &str, +) -> Result<(), FileIoError> { + use std::io::ErrorKind::Interrupted; + + loop { + match ring.submit_and_wait(wait_nr as usize) { + Ok(_) => return Ok(()), + Err(e) => { + // Retry if the error is an interrupted system call + if e.kind() == Interrupted { + continue; + } + // For other errors, return the error + return Err(storage.file_io_error( + e, + 0, + Some(format!("io-uring {operation_name}")), + )); + } + } + } +} + +/// Helper function to handle completion queue entries and check for errors +fn handle_completion_queue( + storage: &FileBacked, + completion_queue: io_uring::cqueue::CompletionQueue<'_>, + saved_pinned_buffers: &mut [Option>], + cached_nodes: &mut Vec, +) -> Result<(), FileIoError> { + for entry in completion_queue { + // user data contains the index of the entry in the saved_pinned_buffers array + let item = entry.user_data() as usize; + let pbe_entry = saved_pinned_buffers + .get_mut(item) + .expect("completed item user_data should point to an entry") + .take() + .expect("completed items are always in use"); + + let expected_len: i32 = pbe_entry + .buffer + .len() + .try_into() + .expect("buffer length will fit into an i32"); + if entry.result() != expected_len { + let error = if entry.result() >= 0 { + std::io::Error::other("Partial write") + } else { + std::io::Error::from_raw_os_error(0 - entry.result()) + }; + return Err(storage.file_io_error( + error, + pbe_entry.address.get(), + Some("write failure".to_string()), + )); + } + // I/O completed successfully - mark node as persisted and cache it + pbe_entry.node.allocate_at(pbe_entry.address); + cached_nodes.push(pbe_entry.node); + } + Ok(()) +} + +impl NodeStore { + /// Persist all the nodes of a proposal to storage using io-uring. + /// + /// # Errors + /// + /// Returns a [`FileIoError`] if any node cannot be written to storage. + #[fastrace::trace(short_name = true)] + pub(super) fn flush_nodes_io_uring(&mut self) -> Result { + use bumpalo::Bump; + + let mut header = self.header; + let mut node_allocator = NodeAllocator::new(self.storage.as_ref(), &mut header); + let mut bump = Bump::with_capacity(super::INITIAL_BUMP_SIZE); + + process_unpersisted_nodes( + &mut bump, + &mut node_allocator, + self, + super::INITIAL_BUMP_SIZE, + |allocated_objects| self.ring_writes(allocated_objects), + )?; + + Ok(header) + } + + fn ring_writes( + &self, + allocated_objects: Vec<(&[u8], LinearAddress, MaybePersistedNode)>, + ) -> Result<(), FileIoError> { + let mut ring = self.storage.ring.lock().expect("poisoned lock"); + + let mut saved_pinned_buffers = + vec![Option::>::None; FileBacked::RINGSIZE as usize]; + + // Collect addresses and nodes for caching + let mut cached_nodes = Vec::new(); + + for (serialized, persisted_address, node) in allocated_objects { + loop { + // Find the first available write buffer, enumerate to get the position for marking it completed + if let Some((pos, pbe)) = saved_pinned_buffers + .iter_mut() + .enumerate() + .find(|(_, pbe)| pbe.is_none()) + { + *pbe = Some(BufferEntry { + buffer: serialized, + address: persisted_address, + node: node.clone(), + }); + + let submission_queue_entry = self + .storage + .make_op(serialized) + .offset(persisted_address.get()) + .build() + .user_data(pos as u64); + + #[expect(unsafe_code)] + // SAFETY: the submission_queue_entry's found buffer must not move or go out of scope + // until the operation has been completed. This is ensured by having a Some(offset) + // and not marking it None until the kernel has said it's done below. + while unsafe { ring.submission().push(&submission_queue_entry) }.is_err() { + ring.submitter().squeue_wait().map_err(|e| { + self.storage.file_io_error( + e, + persisted_address.get(), + Some("io-uring squeue_wait".to_string()), + ) + })?; + trace!("submission queue is full"); + firewood_counter!("ring.full", "amount of full ring").increment(1); + } + break; + } + // if we get here, that means we couldn't find a place to queue the request, so wait for at least one operation + // to complete, then handle the completion queue + firewood_counter!("ring.full", "amount of full ring").increment(1); + submit_and_wait_with_retry(&mut ring, 1, &self.storage, "submit_and_wait")?; + let completion_queue = ring.completion(); + trace!("competion queue length: {}", completion_queue.len()); + handle_completion_queue( + &self.storage, + completion_queue, + &mut saved_pinned_buffers, + &mut cached_nodes, + )?; + } + } + let pending = saved_pinned_buffers + .iter() + .filter(|pbe| pbe.is_some()) + .count(); + submit_and_wait_with_retry( + &mut ring, + pending as u32, + &self.storage, + "final submit_and_wait", + )?; + + handle_completion_queue( + &self.storage, + ring.completion(), + &mut saved_pinned_buffers, + &mut cached_nodes, + )?; + + debug_assert!( + !saved_pinned_buffers + .iter() + .any(std::option::Option::is_some), + "Found entry with node still set: {:?}", + saved_pinned_buffers.iter().find(|pbe| pbe.is_some()) + ); + + self.storage.write_cached_nodes(cached_nodes)?; + debug_assert!(ring.completion().is_empty()); + + // All references to batch.bump are now dropped, caller can reset it + Ok(()) + } +} From c1215ec751068b64314ba82576066b5e20c96b94 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 31 Oct 2025 14:23:55 -0700 Subject: [PATCH 1008/1053] chore: update .golangci.yaml (#1419) exec: `.github/scripts/verify_golangci_yaml_changes.sh apply` updated by ava-labs/avalanchego#4444 --- .github/.golangci.yaml.patch | 12 ++++++------ ffi/.golangci.yaml | 5 +++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index 9580ebf13898..07c093fc0dac 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,6 +1,6 @@ ---- .github/.golangci.yaml 2025-10-29 08:30:25 -+++ ffi/.golangci.yaml 2025-10-29 08:30:25 -@@ -70,8 +70,6 @@ +--- .github/.golangci.yaml 2025-10-31 18:45:07.201781593 +0000 ++++ ffi/.golangci.yaml 2025-10-31 18:45:07.200643422 +0000 +@@ -75,8 +75,6 @@ rules: packages: deny: @@ -9,7 +9,7 @@ - pkg: github.com/golang/mock/gomock desc: go.uber.org/mock/gomock should be used instead. - pkg: github.com/stretchr/testify/assert -@@ -86,29 +84,10 @@ +@@ -91,29 +89,10 @@ forbidigo: # Forbid the following identifiers (list of regexp). forbid: @@ -39,7 +39,7 @@ revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr -@@ -172,17 +151,6 @@ +@@ -177,17 +156,6 @@ # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - name: useless-break disabled: false @@ -57,7 +57,7 @@ tagalign: align: true sort: true -@@ -245,7 +213,7 @@ +@@ -250,7 +218,7 @@ - standard - default - blank diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index d56130539eb2..0dc1e6a6c00a 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -33,19 +33,23 @@ linters: default: none enable: - asciicheck + - bidichk - bodyclose - copyloopvar - depguard + - durationcheck - errcheck - errorlint - forbidigo - goconst + - gocheckcompilerdirectives - gocritic - goprintffuncname - gosec - govet - importas - ineffassign + - mirror - misspell - nakedret - nilerr @@ -54,6 +58,7 @@ linters: - perfsprint - prealloc - predeclared + - reassign - revive - spancheck - staticcheck From 49d0f16543b0c2cb3b74d2976b507625ec339e6c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 31 Oct 2025 15:54:05 -0700 Subject: [PATCH 1009/1053] feat: define explicit associated types on Hashable (#1366) In my next pull request, I have a need for these to be defined as explicit associated types. This was pulled out into a separate change for simplicity. --- firewood/src/proof.rs | 21 +++++++++++++++--- storage/src/hashednode.rs | 29 +++++++++++++++--------- storage/src/hashedshunt.rs | 25 ++++++++++++++++++--- storage/src/tries/kvp.rs | 45 +++++++++++++++++++++++++++++++------- storage/src/tries/mod.rs | 9 ++++++-- 5 files changed, 103 insertions(+), 26 deletions(-) diff --git a/firewood/src/proof.rs b/firewood/src/proof.rs index 4340e33f1efa..251f47fc4ca5 100644 --- a/firewood/src/proof.rs +++ b/firewood/src/proof.rs @@ -108,17 +108,32 @@ impl std::fmt::Debug for ProofNode { } impl Hashable for ProofNode { - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + type LeadingPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + type PartialPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + type FullPath<'a> + = &'a [PathComponent] + where + Self: 'a; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { let (prefix, _) = self.key.split_at(self.partial_len); prefix } - fn partial_path(&self) -> impl IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { let (_, suffix) = self.key.split_at(self.partial_len); suffix } - fn full_path(&self) -> impl IntoSplitPath + '_ { + fn full_path(&self) -> Self::FullPath<'_> { &self.key } diff --git a/storage/src/hashednode.rs b/storage/src/hashednode.rs index 58501298ac85..4eb0c681000e 100644 --- a/storage/src/hashednode.rs +++ b/storage/src/hashednode.rs @@ -3,7 +3,6 @@ use crate::{ Children, HashType, HashableShunt, IntoSplitPath, Node, Path, PathComponent, SplitPath, - TriePath, }; use smallvec::SmallVec; @@ -153,22 +152,32 @@ impl> AsRef<[u8]> for ValueDigest { /// A node in the trie that can be hashed. pub trait Hashable: std::fmt::Debug { + /// The type of the leading path. + type LeadingPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + + /// The type of the partial path. + type PartialPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + + /// The type of the full path. + type FullPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + /// The full path of this node's parent where each byte is a nibble. - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_; + fn parent_prefix_path(&self) -> Self::LeadingPath<'_>; /// The partial path of this node where each byte is a nibble. - fn partial_path(&self) -> impl IntoSplitPath + '_; + fn partial_path(&self) -> Self::PartialPath<'_>; + /// The full path of this node including the parent's prefix where each byte is a nibble. + fn full_path(&self) -> Self::FullPath<'_>; /// The node's value or hash. fn value_digest(&self) -> Option>; /// Each element is a child's index and hash. /// Yields 0 elements if the node is a leaf. fn children(&self) -> Children>; - - /// The full path of this node including the parent's prefix where each byte is a nibble. - fn full_path(&self) -> impl IntoSplitPath + '_ { - self.parent_prefix_path() - .into_split_path() - .append(self.partial_path().into_split_path()) - } } /// A preimage of a hash. diff --git a/storage/src/hashedshunt.rs b/storage/src/hashedshunt.rs index ab7306a7eb52..fe5bb1d230bf 100644 --- a/storage/src/hashedshunt.rs +++ b/storage/src/hashedshunt.rs @@ -1,7 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use crate::{Children, HashType, Hashable, IntoSplitPath, SplitPath, ValueDigest}; +use crate::{Children, HashType, Hashable, JoinedPath, SplitPath, ValueDigest}; /// A shunt for a hasheable trie that we can use to compute the hash of a node /// using component parts. @@ -51,14 +51,33 @@ impl std::fmt::Debug for HashableShunt<'_, P1, P2> } impl Hashable for HashableShunt<'_, P1, P2> { - fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ { + type LeadingPath<'a> + = P1 + where + Self: 'a; + + type PartialPath<'a> + = P2 + where + Self: 'a; + + type FullPath<'a> + = JoinedPath + where + Self: 'a; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { self.parent_prefix } - fn partial_path(&self) -> impl IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } + fn full_path(&self) -> Self::FullPath<'_> { + self.parent_prefix_path().append(self.partial_path()) + } + fn value_digest(&self) -> Option> { self.value.clone() } diff --git a/storage/src/tries/kvp.rs b/storage/src/tries/kvp.rs index 1af0e65b5eb2..192b316faf94 100644 --- a/storage/src/tries/kvp.rs +++ b/storage/src/tries/kvp.rs @@ -4,8 +4,8 @@ #[cfg(not(feature = "branch_factor_256"))] use crate::PackedPathRef; use crate::{ - Children, HashType, Hashable, HashableShunt, HashedTrieNode, PathBuf, PathComponent, PathGuard, - SplitPath, TrieNode, TriePath, TriePathFromPackedBytes, ValueDigest, + Children, HashType, Hashable, HashableShunt, HashedTrieNode, JoinedPath, PathBuf, + PathComponent, PathGuard, SplitPath, TrieNode, TriePath, TriePathFromPackedBytes, ValueDigest, }; #[cfg(feature = "branch_factor_256")] @@ -280,7 +280,12 @@ impl<'a, T: AsRef<[u8]> + ?Sized> HashedKeyValueTrieRoot<'a, T> { } impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { - fn partial_path(&self) -> impl SplitPath + '_ { + type PartialPath<'a> + = PackedPathRef<'a> + where + Self: 'a; + + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } @@ -305,7 +310,12 @@ impl + ?Sized> TrieNode for KeyValueTrieRoot<'_, T> { } impl + ?Sized> TrieNode for HashedKeyValueTrieRoot<'_, T> { - fn partial_path(&self) -> impl SplitPath + '_ { + type PartialPath<'a> + = PackedPathRef<'a> + where + Self: 'a; + + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } @@ -334,15 +344,34 @@ impl + ?Sized> HashedTrieNode for HashedKeyValueTrieRoot<'_, T } } -impl + ?Sized> Hashable for HashedKeyValueTrieRoot<'_, T> { - fn parent_prefix_path(&self) -> impl crate::IntoSplitPath + '_ { - self.leading_path.as_slice() +impl<'a, T: AsRef<[u8]> + ?Sized> Hashable for HashedKeyValueTrieRoot<'a, T> { + type LeadingPath<'b> + = &'b [PathComponent] + where + Self: 'b; + + type PartialPath<'b> + = PackedPathRef<'a> + where + Self: 'b; + + type FullPath<'b> + = JoinedPath<&'b [PathComponent], PackedPathRef<'a>> + where + Self: 'b; + + fn parent_prefix_path(&self) -> Self::LeadingPath<'_> { + &self.leading_path } - fn partial_path(&self) -> impl crate::IntoSplitPath + '_ { + fn partial_path(&self) -> Self::PartialPath<'_> { self.partial_path } + fn full_path(&self) -> Self::FullPath<'_> { + self.parent_prefix_path().append(self.partial_path) + } + fn value_digest(&self) -> Option> { self.value.map(|v| ValueDigest::Value(v.as_ref())) } diff --git a/storage/src/tries/mod.rs b/storage/src/tries/mod.rs index f39ca0d792c1..ce19101964ae 100644 --- a/storage/src/tries/mod.rs +++ b/storage/src/tries/mod.rs @@ -4,7 +4,7 @@ mod iter; mod kvp; -use crate::{HashType, PathComponent, SplitPath}; +use crate::{HashType, IntoSplitPath, PathComponent}; pub use self::iter::{IterAscending, IterDescending, TrieEdgeIter, TrieValueIter}; pub use self::kvp::{DuplicateKeyError, HashedKeyValueTrieRoot, KeyValueTrieRoot}; @@ -38,8 +38,13 @@ pub enum TrieEdgeState<'a, N: ?Sized> { /// A node in a fixed-arity radix trie. pub trait TrieNode + ?Sized> { + /// The type of path from this node's parent to this node. + type PartialPath<'a>: IntoSplitPath + 'a + where + Self: 'a; + /// The path from this node's parent to this node. - fn partial_path(&self) -> impl SplitPath + '_; + fn partial_path(&self) -> Self::PartialPath<'_>; /// The value stored at this node, if any. fn value(&self) -> Option<&V>; From f2b9cd37057a0c703a5ddbfbe43cb2b7dbdad217 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:04:28 +0000 Subject: [PATCH 1010/1053] feat(ffi): `Database.Close()` guarantees proposals committed or freed (#1349) Explicit freeing of proposals propagated through `libevm` (i.e. `geth`) plumbing has proven difficult when not being committed as they are simply dropped for the GC to collect. Furthermore, strict ordering of calls to `Proposal.Drop()` (or `Commit()`) before `Database.Close()` is required to avoid segfaults. This PR implements a fix for both issues: 1. All new `Proposal`s have a GC finalizer attached, which calls `Drop()`. This is safe because it is a no-op if called twice or after a call to `Commit()`. 2. The `Database` has a `sync.WaitGroup` introduced, which tracks all outstanding proposals. Calls to `Commit()` / `Drop()` decrement the group counter (only once per `Proposal`). 3. `Database.Close()` waits on the `WaitGroup` before freeing its own handle, avoiding segfaults. Assuming that all calls to `Database.Propose()` and `Proposal.Propose()` occur _before_ the call to `Database.Close()` then this is a correct usage of `sync.WaitGroup`'s documented requirement for ordering of calls to `Add()` and `Wait()`. An integration test demonstrates blocking and eventual return of `Database.Close()`, specifically due to the unreachability of un-dropped, un-committed `Proposal`s, resulting in their finalizers decrementing the `WaitGroup`. --------- Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> --- ffi/firewood.go | 48 +++++++++-- ffi/firewood_test.go | 86 ++++++++++++++++--- ffi/kvbackend.go | 3 - ffi/proposal.go | 65 ++++++++------ ffi/tests/eth/eth_compatibility_test.go | 3 +- .../firewood/merkle_compatibility_test.go | 10 +-- 6 files changed, 159 insertions(+), 56 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 6e2233ec79a9..a7c6f51b2edb 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -27,9 +27,12 @@ import "C" import ( "bytes" + "context" "errors" "fmt" "runtime" + "sync" + "time" ) // These constants are used to identify errors returned by the Firewood Rust FFI. @@ -49,7 +52,8 @@ type Database struct { // handle is returned and accepted by cgo functions. It MUST be treated as // an opaque value without special meaning. // https://en.wikipedia.org/wiki/Blinkenlights - handle *C.DatabaseHandle + handle *C.DatabaseHandle + proposals sync.WaitGroup } // Config configures the opening of a [Database]. @@ -139,6 +143,9 @@ func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { return getHashKeyFromHashResult(C.fwd_batch(db.handle, kvp)) } +// Propose creates a new proposal with the given keys and values. The proposal +// is not committed until [Proposal.Commit] is called. See [Database.Close] re +// freeing proposals. func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if db.handle == nil { return nil, errDBClosed @@ -151,8 +158,7 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if err != nil { return nil, err } - - return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), db) + return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), &db.proposals) } // Get retrieves the value for the given key. It always returns a nil error. @@ -245,19 +251,43 @@ func (db *Database) Revision(root []byte) (*Revision, error) { return rev, nil } +// defaultCloseTimeout is the duration by which the [context.Context] passed to +// [Database.Close] is limited. A minute is arbitrary but well above what is +// reasonably required, and is chosen simply to avoid permanently blocking. +var defaultCloseTimeout = time.Minute + // Close releases the memory associated with the Database. // -// This is not safe to call while there are any outstanding Proposals. All proposals -// must be freed or committed before calling this. +// This blocks until all outstanding Proposals are either unreachable or one of +// [Proposal.Commit] or [Proposal.Drop] has been called on them. Unreachable +// proposals will be automatically dropped before Close returns, unless an +// alternate GC finalizer is set on them. // -// This is safe to call if the pointer is nil, in which case it does nothing. The -// pointer will be set to nil after freeing to prevent double free. However, it is -// not safe to call this method concurrently from multiple goroutines. -func (db *Database) Close() error { +// This is safe to call if the handle pointer is nil, in which case it does +// nothing. The pointer will be set to nil after freeing to prevent double free. +// However, it is not safe to call this method concurrently from multiple +// goroutines. +func (db *Database) Close(ctx context.Context) error { if db.handle == nil { return nil } + go runtime.GC() + + done := make(chan struct{}) + go func() { + db.proposals.Wait() + close(done) + }() + + ctx, cancel := context.WithTimeout(ctx, defaultCloseTimeout) + defer cancel() + select { + case <-done: + case <-ctx.Done(): + return fmt.Errorf("at least one reachable %T neither dropped nor committed", &Proposal{}) + } + if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { return fmt.Errorf("unexpected error when closing database: %w", err) } diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 2f642b6e81cf..4b05d261ba87 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -5,6 +5,7 @@ package ffi import ( "bytes" + "context" "encoding/hex" "errors" "fmt" @@ -59,14 +60,14 @@ var ( expectedRoots map[string]string ) -func inferHashingMode() (string, error) { +func inferHashingMode(ctx context.Context) (string, error) { dbFile := filepath.Join(os.TempDir(), "test.db") - db, closeDB, err := newDatabase(dbFile) + db, err := newDatabase(dbFile) if err != nil { return "", err } defer func() { - _ = closeDB() + _ = db.Close(ctx) _ = os.Remove(dbFile) }() @@ -111,7 +112,7 @@ func TestMain(m *testing.M) { // Otherwise, infer the hash mode from an empty database. hashMode := os.Getenv("TEST_FIREWOOD_HASH_MODE") if hashMode == "" { - inferredHashMode, err := inferHashingMode() + inferredHashMode, err := inferHashingMode(context.Background()) if err != nil { fmt.Fprintf(os.Stderr, "failed to infer hash mode %v\n", err) os.Exit(1) @@ -133,15 +134,15 @@ func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database { r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") - db, closeDB, err := newDatabase(dbFile, configureFns...) + db, err := newDatabase(dbFile, configureFns...) r.NoError(err) t.Cleanup(func() { - r.NoError(closeDB()) + r.NoError(db.Close(context.Background())) //nolint:usetesting // t.Context() will already be cancelled }) return db } -func newDatabase(dbFile string, configureFns ...func(*Config)) (*Database, func() error, error) { +func newDatabase(dbFile string, configureFns ...func(*Config)) (*Database, error) { conf := DefaultConfig() conf.Truncate = true // in tests, we use filepath.Join, which creates an empty file for _, fn := range configureFns { @@ -150,9 +151,9 @@ func newDatabase(dbFile string, configureFns ...func(*Config)) (*Database, func( f, err := New(dbFile, conf) if err != nil { - return nil, nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) + return nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) } - return f, f.Close, nil + return f, nil } func TestUpdateSingleKV(t *testing.T) { @@ -196,7 +197,7 @@ func TestTruncateDatabase(t *testing.T) { r.NoError(err) // Close the database. - r.NoError(db.Close()) + r.NoError(db.Close(t.Context())) // Reopen the database with truncate enabled. db, err = New(dbFile, config) @@ -210,16 +211,16 @@ func TestTruncateDatabase(t *testing.T) { r.NoError(err) r.Equal(expectedHash, hash, "Root hash mismatch after truncation") - r.NoError(db.Close()) + r.NoError(db.Close(t.Context())) } func TestClosedDatabase(t *testing.T) { r := require.New(t) dbFile := filepath.Join(t.TempDir(), "test.db") - db, _, err := newDatabase(dbFile) + db, err := newDatabase(dbFile) r.NoError(err) - r.NoError(db.Close()) + r.NoError(db.Close(t.Context())) _, err = db.Root() r.ErrorIs(err, errDBClosed) @@ -231,7 +232,7 @@ func TestClosedDatabase(t *testing.T) { r.Empty(root) r.ErrorIs(err, errDBClosed) - r.NoError(db.Close()) + r.NoError(db.Close(t.Context())) } func keyForTest(i int) []byte { @@ -1171,6 +1172,63 @@ func TestGetFromRootParallel(t *testing.T) { } } +func TestProposalHandlesFreed(t *testing.T) { + t.Parallel() + + db, err := newDatabase(filepath.Join(t.TempDir(), "test_GC_drops_proposal.db")) + require.NoError(t, err) + + // These MUST NOT be committed nor dropped as they demonstrate that the GC + // finalizer does it for us. + p0, err := db.Propose(kvForTest(1)) + require.NoErrorf(t, err, "%T.Propose(...)", db) + p1, err := p0.Propose(kvForTest(1)) + require.NoErrorf(t, err, "%T.Propose(...)", p0) + + // Demonstrates that explicit [Proposal.Commit] and [Proposal.Drop] calls + // are sufficient to unblock [Database.Close]. + var keep []*Proposal + for name, free := range map[string](func(*Proposal) error){ + "Commit": (*Proposal).Commit, + "Drop": (*Proposal).Drop, + } { + p, err := db.Propose(kvForTest(1)) + require.NoErrorf(t, err, "%T.Propose(...)", db) + require.NoErrorf(t, free(p), "%T.%s()", p, name) + keep = append(keep, p) + } + + done := make(chan struct{}) + go func() { + require.NoErrorf(t, db.Close(t.Context()), "%T.Close()", db) + close(done) + }() + + select { + case <-done: + t.Errorf("%T.Close() returned with undropped %T", db, p0) //nolint:forbidigo // Use of require is impossible without a hack like require.False(true) + case <-time.After(300 * time.Millisecond): + // TODO(arr4n) use `synctest` package when at Go 1.25 + } + + runtime.KeepAlive(p0) + runtime.KeepAlive(p1) + p0 = nil + p1 = nil //nolint:ineffassign // Makes the value unreachable, allowing the finalizer to call Drop() + + // In practice there's no need to call [runtime.GC] if [Database.Close] is + // called after all proposals are unreachable, as it does it itself. + runtime.GC() + // Note that [Database.Close] waits for outstanding proposals, so this would + // block permanently if the unreachability of `p0` and `p1` didn't result in + // their [Proposal.Drop] methods being called. + <-done + + for _, p := range keep { + runtime.KeepAlive(p) + } +} + type kvIter interface { SetBatchSize(int) Next() bool diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go index 869cbf21f859..2c7e4d3f3a9f 100644 --- a/ffi/kvbackend.go +++ b/ffi/kvbackend.go @@ -38,9 +38,6 @@ type kVBackend interface { // commits happen on a rolling basis. // Length of the root slice is guaranteed to be common.HashLength. Commit(root []byte) error - - // Close closes the backend and releases all held resources. - Close() error } // Prefetch is a no-op since we don't need to prefetch for Firewood. diff --git a/ffi/proposal.go b/ffi/proposal.go index 56ed0ad1a4a8..0c2879d32408 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -14,17 +14,13 @@ import ( "errors" "fmt" "runtime" + "sync" "unsafe" ) var errDroppedProposal = errors.New("proposal already dropped") type Proposal struct { - // The database this proposal is associated with. We hold onto this to ensure - // the database handle outlives the proposal handle, which is required for - // the proposal to be valid. - db *Database - // handle is an opaque pointer to the proposal within Firewood. It should be // passed to the C FFI functions that operate on proposals // @@ -33,6 +29,11 @@ type Proposal struct { // Calls to `C.fwd_commit_proposal` and `C.fwd_free_proposal` will invalidate // this handle, so it should not be used after those calls. handle *C.ProposalHandle + disown sync.Mutex + // [Database.Close] blocks on this WaitGroup, which is incremented by + // [getProposalFromProposalResult], and decremented by either + // [Proposal.Commit] or [Proposal.Drop] (when the handle is disowned). + openProposals *sync.WaitGroup // The proposal root hash. root []byte @@ -73,8 +74,8 @@ func (p *Proposal) Iter(key []byte) (*Iterator, error) { return getIteratorFromIteratorResult(itResult) } -// Propose creates a new proposal with the given keys and values. -// The proposal is not committed until Commit is called. +// Propose is equivalent to [Database.Propose] except that the new proposal is +// based on `p`. func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if p.handle == nil { return nil, errDroppedProposal @@ -87,8 +88,24 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if err != nil { return nil, err } + return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.openProposals) +} + +// disownHandle is the common path of [Proposal.Commit] and [Proposal.Drop], the +// `fn` argument defining the method-specific behaviour. +func (p *Proposal) disownHandle(fn func(*C.ProposalHandle) error, disownEvenOnErr bool) error { + p.disown.Lock() + defer p.disown.Unlock() - return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.db) + if p.handle == nil { + return errDroppedProposal + } + err := fn(p.handle) + if disownEvenOnErr || err == nil { + p.handle = nil + p.openProposals.Done() + } + return err } // Commit commits the proposal and returns any errors. @@ -96,13 +113,11 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { // The proposal handle is no longer valid after this call, but the root // hash can still be retrieved using Root(). func (p *Proposal) Commit() error { - if p.handle == nil { - return errDroppedProposal - } - - _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(p.handle)) - p.handle = nil // we no longer own the proposal handle + return p.disownHandle(commitProposal, true) +} +func commitProposal(h *C.ProposalHandle) error { + _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(h)) return err } @@ -112,21 +127,21 @@ func (p *Proposal) Commit() error { // // The pointer will be set to nil after freeing to prevent double free. func (p *Proposal) Drop() error { - if p.handle == nil { - return nil + if err := p.disownHandle(dropProposal, false); err != nil && err != errDroppedProposal { + return err } + return nil +} - if err := getErrorFromVoidResult(C.fwd_free_proposal(p.handle)); err != nil { +func dropProposal(h *C.ProposalHandle) error { + if err := getErrorFromVoidResult(C.fwd_free_proposal(h)); err != nil { return fmt.Errorf("%w: %w", errFreeingValue, err) } - - p.handle = nil // Prevent double free - return nil } // getProposalFromProposalResult converts a C.ProposalResult to a Proposal or error. -func getProposalFromProposalResult(result C.ProposalResult, db *Database) (*Proposal, error) { +func getProposalFromProposalResult(result C.ProposalResult, openProposals *sync.WaitGroup) (*Proposal, error) { switch result.tag { case C.ProposalResult_NullHandlePointer: return nil, errDBClosed @@ -134,10 +149,12 @@ func getProposalFromProposalResult(result C.ProposalResult, db *Database) (*Prop body := (*C.ProposalResult_Ok_Body)(unsafe.Pointer(&result.anon0)) hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) proposal := &Proposal{ - db: db, - handle: body.handle, - root: hashKey[:], + handle: body.handle, + root: hashKey[:], + openProposals: openProposals, } + openProposals.Add(1) + runtime.SetFinalizer(proposal, (*Proposal).Drop) return proposal, nil case C.ProposalResult_Err: err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index d911345cface..54dd62396c91 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -4,6 +4,7 @@ package eth import ( + "context" "encoding/binary" "math/rand" "path" @@ -78,7 +79,7 @@ func newMerkleTriePair(t *testing.T) *merkleTriePair { tr, err := tdb.OpenTrie(ethRoot) r.NoError(err) t.Cleanup(func() { - r.NoError(db.Close()) + r.NoError(db.Close(context.Background())) //nolint:usetesting // t.Context() will already be cancelled }) return &merkleTriePair{ diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index d93fc82bbcdf..76a45c6a8f79 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -53,21 +53,21 @@ func newTestFirewoodDatabase(t *testing.T) *firewood.Database { t.Helper() dbFile := filepath.Join(t.TempDir(), "test.db") - db, closeDB, err := newFirewoodDatabase(dbFile) + db, err := newFirewoodDatabase(dbFile) require.NoError(t, err) t.Cleanup(func() { - require.NoError(t, closeDB()) + require.NoError(t, db.Close(context.Background())) //nolint:usetesting // t.Context() will already be cancelled }) return db } -func newFirewoodDatabase(dbFile string) (*firewood.Database, func() error, error) { +func newFirewoodDatabase(dbFile string) (*firewood.Database, error) { conf := firewood.DefaultConfig() f, err := firewood.New(dbFile, conf) if err != nil { - return nil, nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) + return nil, fmt.Errorf("failed to create new database at filepath %q: %w", dbFile, err) } - return f, f.Close, nil + return f, nil } type tree struct { From 908ede9e19fe866e43e7e822f98ba86f4cccc946 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:29:14 -0500 Subject: [PATCH 1011/1053] docs(benchmark/bootstrap): expand README.md (#1421) Currently, anyone wanting to run `aws-launch.sh` will need to peek into the implementation of the script to understand what it does. For first time users (like myself), this introduces an extremely large learning curve. This PR removes the need to read the implementation of `aws-launch.sh` by expanding the README.md to cover how to use the script and how to view the reexecution benchmark results. Using this new PR, I was able to get an EC2 instance up and running. --- benchmark/bootstrap/README.md | 83 +++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/benchmark/bootstrap/README.md b/benchmark/bootstrap/README.md index 1f597d413455..ac35a7c08fb1 100644 --- a/benchmark/bootstrap/README.md +++ b/benchmark/bootstrap/README.md @@ -1,4 +1,81 @@ -# Bootstrap testing script +# Bootstrap Testing Script -This script creates instances and sets up users for bootstrap testing. You'll need ec2 installed on your machine -and you'll need to `aws sso login` before running this script. +This directory contains tools for automated Firewood blockchain database benchmarking on AWS. The `aws-launch.sh` script creates EC2 instances, sets up the complete testing environment, and executes C-chain (Avalanche) block bootstrapping tests. + +## Prerequisites + +Before running the script, you'll need: + +- AWS CLI installed and configured on your machine +- Authenticated AWS session: `aws sso login` + - Your session should be configured to use the `Experimental` account. + +## What It Does + +The `aws-launch.sh` script automatically: + +1. Launches an EC2 instance with the specified instance type and configuration +2. Sets up the environment with all necessary dependencies (Git, Rust, Go, build tools, Grafana) +3. Creates user accounts with SSH access for the team +4. Clones and builds: + - Firewood (from specified branch or default) + - AvalancheGo (from specified branch or default) + - Coreth (from specified branch or default) + - LibEVM (from specified branch or default) +5. Downloads pre-existing blockchain data from S3 (1M, 10M, or 50M blocks) +6. Executes the bootstrapping benchmark to test Firewood's performance + +## Usage + +```bash +./aws-launch.sh [OPTIONS] +``` + +For a complete list of options, run: + +```bash +./aws-launch.sh --help +``` + +## Examples + +### Run a large benchmark with spot pricing + +```bash +./aws-launch.sh --instance-type i4i.xlarge --nblocks 10m --spot +``` + +### Test multiple component branches together + +```bash +./aws-launch.sh --firewood-branch my-firewood-branch --avalanchego-branch develop --coreth-branch foo --libevm-branch bar +``` + +### Preview a configuration without launching + +```bash +./aws-launch.sh --dry-run --firewood-branch my-branch --nblocks 1m +``` + +## Monitoring Results + +After launching, the script outputs an instance ID. You can: + +1. **SSH to the instance** - Only authorized team members (rkuris, austin, aaron, brandon, amin, bernard, rodrigo) can SSH using their configured GPG hardware keys. Note: Your GPG agent must be properly configured for SSH support on your local machine. + + ```bash + ssh @ + ``` + +2. **Monitor benchmark progress**: + + ```bash + tail -f /var/log/bootstrap.log + ``` + +3. **Check build logs**: + + ```bash + tail -f /mnt/nvme/ubuntu/firewood/build.log + tail -f /mnt/nvme/ubuntu/avalanchego/build.log + ``` From fe278561f14f068442a82df90a078c06ae2d29d3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:43:18 +0000 Subject: [PATCH 1012/1053] feat(ffi)!: Support empty values in Update operations (#1420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(ffi)!: Support empty values in Update operations The FFI currently treats both `nil` and empty `[]byte{}` values identically, converting both to `DeleteRange` operations. This prevents transaction stores from inserting keys with empty values. ## Changes **Go FFI (`memory.go`)** - Modified `newBorrowedBytes` to preserve nil vs empty slice distinction by checking pointer nullity before length **Rust FFI (`borrowed.rs`, `kvp.rs`)** - Added `is_null()` method to `BorrowedSlice` - Updated `into_batch` to use `is_null()` instead of `is_empty()` for operation selection **Documentation (`firewood.go`, `proposal.go`)** - Added clear "Value Semantics" documentation to `Update` and `Propose` methods - Documented the distinction between nil values (DeleteRange), empty slices (Put with empty value), and non-empty values ## Behavior ```go // Delete key (DeleteRange operation) db.Update([][]byte{key}, [][]byte{nil}) // Insert key with empty value (Put operation) db.Update([][]byte{key}, [][]byte{{}}) ``` The Rust side now correctly distinguishes: - `ptr == null` → `BatchOp::DeleteRange` - `ptr != null, len == 0` → `BatchOp::Put` with empty value Added `TestNilVsEmptyValue` to validate the behavior. ## Testing - [x] All existing tests pass - [x] New test validates the fix - [x] golangci-lint v2 passes with no issues - [x] Documentation added for godocs - [x] Merged with main branch using `-X ours` strategy Fixes #1414

    Original prompt > > ---- > > *This section details on the original issue you should resolve* > > ffi: expose BatchOp (or otherwise enable inserting keys with empty values) > ## Problem > > The FFI api exposes an `Update(keys, values [][]byte)` method. When a value is empty or nil, firewood converts this into a [`DeleteRange`](https://github.com/ava-labs/firewood/blob/9ed71b121dae9de79465acda4254e935d68d193a/firewood/src/v2/batch_op.rs#L150-L158) operation. > > This is insufficient for transaction store as key entries will have empty values. > > ## Potential Solution > > In order to prevent breaking the existing API, we can add an additional `Batch` method and the associated [`BatchOp`](https://github.com/ava-labs/firewood/blob/9ed71b121dae9de79465acda4254e935d68d193a/firewood/src/v2/batch_op.rs#L11-L32) enum to allow callers to specify Insert with an empty value. This also allows us to expose `Delete` versus `DeleteRange` which are distinctly different operations. > > > Change the go code to look at the passed in value. When the value is actually nil, use the DELETE_RANGE operator. If it's an empty slice, use the PUT logic and set the value to an empty value. > > ## Comments on the Issue (you are @copilot in this section) > > > @rkuris > IMO the right "fix" here is to differentiate between a nil slice and an empty slice. We kind of need both to correctly support this. > >
    - Fixes ava-labs/firewood#1414 --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> Co-authored-by: Ron Kuris Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> --- ffi/firewood.go | 14 ++++- ffi/firewood_test.go | 68 +++++++++++++++++++++++++ ffi/memory.go | 13 +++-- ffi/proposal.go | 5 ++ ffi/src/value/borrowed.rs | 7 +++ ffi/src/value/kvp.rs | 4 +- ffi/tests/eth/eth_compatibility_test.go | 4 +- 7 files changed, 105 insertions(+), 10 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index a7c6f51b2edb..7ba670fd3452 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -125,8 +125,13 @@ func New(filePath string, conf *Config) (*Database, error) { // Update applies a batch of updates to the database, returning the hash of the // root node after the batch is applied. // -// WARNING: a consequence of prefix deletion is that calling Update with an empty -// key and value will delete the entire database. +// Value Semantics: +// - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix +// - empty slice (vals[i] != nil && len(vals[i]) == 0): Inserts/updates the key with an empty value +// - non-empty value: Inserts/updates the key with the provided value +// +// WARNING: Calling Update with an empty key and nil value will delete the entire database +// due to prefix deletion semantics. func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { if db.handle == nil { return nil, errDBClosed @@ -146,6 +151,11 @@ func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { // Propose creates a new proposal with the given keys and values. The proposal // is not committed until [Proposal.Commit] is called. See [Database.Close] re // freeing proposals. +// +// Value Semantics: +// - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix +// - empty slice (vals[i] != nil && len(vals[i]) == 0): Inserts/updates the key with an empty value +// - non-empty value: Inserts/updates the key with the provided value func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if db.handle == nil { return nil, errDBClosed diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 4b05d261ba87..9dbfd82f92a3 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1483,3 +1483,71 @@ func TestIterDone(t *testing.T) { r.False(it.Next()) r.NoError(it.Err()) } + +// TestNilVsEmptyValue tests that nil values cause delete operations while +// empty []byte{} values result in inserts with empty values. +func TestNilVsEmptyValue(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + // Insert a key with a non-empty value + key1 := []byte("key1") + value1 := []byte("value1") + _, err := db.Update([][]byte{key1}, [][]byte{value1}) + r.NoError(err, "Insert key1 with value1") + + // Verify the key exists + got, err := db.Get(key1) + r.NoError(err, "Get key1") + r.Equal(value1, got, "key1 should have value1") + + // Insert another key with an empty value (not nil) + key2 := []byte("key2") + emptyValue := []byte{} // empty slice, not nil + _, err = db.Update([][]byte{key2}, [][]byte{emptyValue}) + r.NoError(err, "Insert key2 with empty value") + + // Verify key2 exists with empty value + got, err = db.Get(key2) + r.NoError(err, "Get key2") + r.NotNil(got, "key2 should exist (not be nil)") + r.Empty(got, "key2 should have empty value") + + // Now use nil value to delete key1 (DeleteRange operation) + var nilValue []byte = nil + _, err = db.Update([][]byte{key1}, [][]byte{nilValue}) + r.NoError(err, "Delete key1 with nil value") + + // Verify key1 is deleted + got, err = db.Get(key1) + r.NoError(err, "Get key1 after delete") + r.Nil(got, "key1 should be deleted") + + // Verify key2 still exists with empty value + got, err = db.Get(key2) + r.NoError(err, "Get key2 after key1 delete") + r.NotNil(got, "key2 should still exist") + r.Empty(got, "key2 should still have empty value") + + // Test with batch operations + key3 := []byte("key3") + value3 := []byte("value3") + key4 := []byte("key4") + emptyValue4 := []byte{} + + _, err = db.Update( + [][]byte{key3, key4}, + [][]byte{value3, emptyValue4}, + ) + r.NoError(err, "Batch insert key3 and key4") + + // Verify both keys exist + got, err = db.Get(key3) + r.NoError(err, "Get key3") + r.Equal(value3, got, "key3 should have value3") + + got, err = db.Get(key4) + r.NoError(err, "Get key4") + r.NotNil(got, "key4 should exist") + r.Empty(got, "key4 should have empty value") +} diff --git a/ffi/memory.go b/ffi/memory.go index 8819459cc658..d97a8bf2f341 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -63,17 +63,20 @@ var _ Borrower = (*ownedBytes)(nil) // // Provide a Pinner to ensure the memory is pinned while the BorrowedBytes is in use. func newBorrowedBytes(slice []byte, pinner Pinner) C.BorrowedBytes { + // Get the pointer first to distinguish between nil slice and empty slice + ptr := unsafe.SliceData(slice) sliceLen := len(slice) - if sliceLen == 0 { - return C.BorrowedBytes{ptr: nil, len: 0} - } - ptr := unsafe.SliceData(slice) + // If ptr is nil (which means the slice itself is nil), return nil pointer if ptr == nil { return C.BorrowedBytes{ptr: nil, len: 0} } - pinner.Pin(ptr) + // For non-nil slices (including empty slices like []byte{}), + // pin the pointer if the slice has data + if sliceLen > 0 { + pinner.Pin(ptr) + } return C.BorrowedBytes{ ptr: (*C.uint8_t)(ptr), diff --git a/ffi/proposal.go b/ffi/proposal.go index 0c2879d32408..fd76063aac76 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -76,6 +76,11 @@ func (p *Proposal) Iter(key []byte) (*Iterator, error) { // Propose is equivalent to [Database.Propose] except that the new proposal is // based on `p`. +// +// Value Semantics: +// - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix +// - empty slice (vals[i] != nil && len(vals[i]) == 0): Inserts/updates the key with an empty value +// - non-empty value: Inserts/updates the key with the provided value func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if p.handle == nil { return nil, errDroppedProposal diff --git a/ffi/src/value/borrowed.rs b/ffi/src/value/borrowed.rs index 2050483669e7..72881a9d48ab 100644 --- a/ffi/src/value/borrowed.rs +++ b/ffi/src/value/borrowed.rs @@ -75,6 +75,13 @@ impl<'a, T> BorrowedSlice<'a, T> { marker: std::marker::PhantomData, } } + + /// Returns true if the pointer is null. + /// This is used to differentiate between a nil slice and an empty slice. + #[must_use] + pub const fn is_null(&self) -> bool { + self.ptr.is_null() + } } impl std::ops::Deref for BorrowedSlice<'_, T> { diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index b693d5db492a..d6d1b340532e 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -44,7 +44,9 @@ impl<'a> api::KeyValuePair for KeyValuePair<'a> { #[inline] fn into_batch(self) -> api::BatchOp { - if self.value.is_empty() { + // Check if the value pointer is null (nil slice in Go) + // vs non-null but empty (empty slice []byte{} in Go) + if self.value.is_null() { api::BatchOp::DeleteRange { prefix: self.key } } else { api::BatchOp::Put { diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index 54dd62396c91..2fbf987c5883 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -204,7 +204,7 @@ func (tr *merkleTriePair) deleteAccount(accountIndex int) { }) tr.pendingFwdKeys = append(tr.pendingFwdKeys, accHash[:]) - tr.pendingFwdVals = append(tr.pendingFwdVals, []byte{}) + tr.pendingFwdVals = append(tr.pendingFwdVals, nil) } // openStorageTrie opens the storage trie for the provided account address. @@ -293,7 +293,7 @@ func (tr *merkleTriePair) deleteStorage(accountIndex int, storageIndexInput uint tr.require.NoError(str.DeleteStorage(addr, storageKey[:])) tr.pendingFwdKeys = append(tr.pendingFwdKeys, append(accHash[:], storageKeyHash[:]...)) - tr.pendingFwdVals = append(tr.pendingFwdVals, []byte{}) + tr.pendingFwdVals = append(tr.pendingFwdVals, nil) } func FuzzFirewoodTree(f *testing.F) { From 03f12df0ef79ab0881c0a4c776b1d2d76736b20b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:32:46 +0100 Subject: [PATCH 1013/1053] chore!: Drop binary support for macos 13/14 (#1425) ## Summary Successfully upgraded the GitHub Actions workflow from deprecated macos-13 (Intel) runners to macos-latest (ARM64). The binaries now require macOS 15.0 or later, dropping support for macOS 13 and 14. This is appropriate since GitHub no longer provides runners for testing on those versions. ## Breaking Changes - **MACOSX_DEPLOYMENT_TARGET**: Updated from 13.0 to 15.0 - Binaries built by this workflow will only run on macOS 15 (Sequoia) and later - macOS 13 (Ventura) and 14 (Sonoma) are no longer supported - Fixes ava-labs/firewood#1424 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> --- .github/workflows/attach-static-libs.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/attach-static-libs.yaml b/.github/workflows/attach-static-libs.yaml index 0e514e532cda..d216daadcc73 100644 --- a/.github/workflows/attach-static-libs.yaml +++ b/.github/workflows/attach-static-libs.yaml @@ -43,16 +43,21 @@ jobs: target: aarch64-unknown-linux-gnu - os: macos-latest target: aarch64-apple-darwin - pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> "$GITHUB_ENV" - - os: macos-13 + pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=15.0" >> "$GITHUB_ENV" + - os: macos-latest target: x86_64-apple-darwin - pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> "$GITHUB_ENV" + pre-build-cmd: echo "MACOSX_DEPLOYMENT_TARGET=15.0" >> "$GITHUB_ENV" outputs: has_secrets: ${{ steps.check_secrets.outputs.has_secrets }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + with: + shared-key: ${{ matrix.target }} + + - name: Add target for cross-compilation + run: rustup target add ${{ matrix.target }} - name: Run pre-build command if: matrix.pre-build-cmd != '' @@ -183,7 +188,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, ubuntu-22.04-arm, macos-latest, macos-13] + os: [ubuntu-latest, ubuntu-22.04-arm, macos-latest] needs: push-firewood-ffi-libs continue-on-error: true steps: From 8799a2402f9cf715ee005dc623f68ded2e10d146 Mon Sep 17 00:00:00 2001 From: maru Date: Tue, 4 Nov 2025 13:00:54 -0800 Subject: [PATCH 1014/1053] chore(ci): Disable ffi-nix job pending reliable build equivalency (#1426) --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f7c74b6bdf0b..1eb0c58625f8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -245,6 +245,7 @@ jobs: run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test -race ./... ffi-nix: + if: false # TODO(marun) Re-enable once non-determinism is rooted out runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From cbb1c5a46b43fbed340930f50db73e96acca0d84 Mon Sep 17 00:00:00 2001 From: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:20:07 -0500 Subject: [PATCH 1015/1053] chore: Added helper to reduce code duplication between Db.propose and Proposal.create_proposal (#1343) Addresses https://github.com/ava-labs/firewood/issues/1342. Created a `propose_with_parent` to allow code reuse between Db.propose and Proposal.create_proposal. `propose_with_parent` will also choose between using parallel or serial proposal creation based on the batch size. Currently the batch size for using parallel creation is 8, but there is a TODO to update the constant once we have some experimental results. We use `size_hint()` on the batch iterator to estimate the batch size. --------- Signed-off-by: bernard-avalabs <53795885+bernard-avalabs@users.noreply.github.com> --- firewood/src/db.rs | 194 +++++++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 85 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8ac60d275ecc..3703b07cc21e 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -11,8 +11,8 @@ use crate::merkle::{Merkle, Value}; use crate::root_store::{NoOpStore, RootStore}; pub use crate::v2::api::BatchOp; use crate::v2::api::{ - self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair, - KeyValuePairIter, OptionalHashKeyExt, + self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePairIter, + OptionalHashKeyExt, }; use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; @@ -95,6 +95,14 @@ where } } +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub enum UseParallel { + Never, + BatchSize(usize), + Always, +} + /// Database configuration. #[derive(Clone, TypedBuilder, Debug)] #[non_exhaustive] @@ -109,6 +117,11 @@ pub struct DbConfig { /// Revision manager configuration. #[builder(default = RevisionManagerConfig::builder().build())] pub manager: RevisionManagerConfig, + // Whether to perform parallel proposal creation. If set to BatchSize, then firewood + // performs parallel proposal creation if the batch is >= to the BatchSize value. + // TODO: Experimentally determine the right value for BatchSize. + #[builder(default = UseParallel::BatchSize(8))] + pub use_parallel: UseParallel, } #[derive(Debug)] @@ -116,6 +129,7 @@ pub struct DbConfig { pub struct Db { metrics: Arc, manager: RevisionManager, + use_parallel: UseParallel, } impl api::Db for Db { @@ -139,45 +153,11 @@ impl api::Db for Db { Ok(self.manager.all_hashes()) } - #[fastrace::trace(short_name = true)] fn propose( &self, batch: impl IntoIterator, ) -> Result, api::Error> { - let parent = self.manager.current_revision(); - let proposal = NodeStore::new(&parent)?; - let mut merkle = Merkle::from(proposal); - let span = fastrace::Span::enter_with_local_parent("merkleops"); - for op in batch.into_iter().map_into_batch() { - match op { - BatchOp::Put { key, value } => { - merkle.insert(key.as_ref(), value.as_ref().into())?; - } - BatchOp::Delete { key } => { - merkle.remove(key.as_ref())?; - } - BatchOp::DeleteRange { prefix } => { - merkle.remove_prefix(prefix.as_ref())?; - } - } - } - - drop(span); - let span = fastrace::Span::enter_with_local_parent("freeze"); - - let nodestore = merkle.into_inner(); - let immutable: Arc, FileBacked>> = - Arc::new(nodestore.try_into()?); - - drop(span); - self.manager.add_proposal(immutable.clone()); - - self.metrics.proposals.increment(1); - - Ok(Self::Proposal { - nodestore: immutable, - db: self, - }) + self.propose_with_parent(batch, &self.manager.current_revision()) } } @@ -204,7 +184,11 @@ impl Db { let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager, root_store)?; - let db = Self { metrics, manager }; + let db = Self { + metrics, + manager, + use_parallel: cfg.use_parallel, + }; Ok(db) } @@ -231,20 +215,57 @@ impl Db { latest_rev_nodestore.check(opt) } - /// Propose a new batch that is processed in parallel. + /// Create a proposal with a specified parent. A proposal is created in parallel if `use_parallel` + /// is `Always` or if `use_parallel` is `BatchSize` and the batch is >= to the `BatchSize` value. /// /// # Panics /// /// Panics if the revision manager cannot create a thread pool. - pub fn propose_parallel( + #[fastrace::trace(name = "propose")] + fn propose_with_parent( &self, batch: impl IntoIterator, + parent: &NodeStore, ) -> Result, api::Error> { - let parent = self.manager.current_revision(); - let mut parallel_merkle = ParallelMerkle::default(); - let immutable = - parallel_merkle.create_proposal(&parent, batch, self.manager.threadpool())?; + // If use_parallel is BatchSize, then perform parallel proposal creation if the batch + // size is >= BatchSize. + let batch = batch.into_iter(); + let use_parallel = match self.use_parallel { + UseParallel::Never => false, + UseParallel::Always => true, + UseParallel::BatchSize(required_size) => batch.size_hint().0 >= required_size, + }; + let immutable = if use_parallel { + let mut parallel_merkle = ParallelMerkle::default(); + let _span = fastrace::Span::enter_with_local_parent("parallel_merkle"); + parallel_merkle.create_proposal(parent, batch, self.manager.threadpool())? + } else { + let proposal = NodeStore::new(parent)?; + let mut merkle = Merkle::from(proposal); + let span = fastrace::Span::enter_with_local_parent("merkleops"); + for op in batch.into_iter().map_into_batch() { + match op { + BatchOp::Put { key, value } => { + merkle.insert(key.as_ref(), value.as_ref().into())?; + } + BatchOp::Delete { key } => { + merkle.remove(key.as_ref())?; + } + BatchOp::DeleteRange { prefix } => { + merkle.remove_prefix(prefix.as_ref())?; + } + } + } + + drop(span); + let _span = fastrace::Span::enter_with_local_parent("freeze"); + let nodestore = merkle.into_inner(); + Arc::new(nodestore.try_into()?) + }; self.manager.add_proposal(immutable.clone()); + + self.metrics.proposals.increment(1); + Ok(Proposal { nodestore: immutable, db: self, @@ -313,31 +334,7 @@ impl Proposal<'_> { &self, batch: impl IntoIterator, ) -> Result { - let parent = self.nodestore.clone(); - let proposal = NodeStore::new(&parent)?; - let mut merkle = Merkle::from(proposal); - for op in batch { - match op.into_batch() { - BatchOp::Put { key, value } => { - merkle.insert(key.as_ref(), value.as_ref().into())?; - } - BatchOp::Delete { key } => { - merkle.remove(key.as_ref())?; - } - BatchOp::DeleteRange { prefix } => { - merkle.remove_prefix(prefix.as_ref())?; - } - } - } - let nodestore = merkle.into_inner(); - let immutable: Arc, FileBacked>> = - Arc::new(nodestore.try_into()?); - self.db.manager.add_proposal(immutable.clone()); - - Ok(Self { - nodestore: immutable, - db: self.db, - }) + self.db.propose_with_parent(batch, &self.nodestore) } } @@ -355,7 +352,7 @@ mod test { CheckOpt, CheckerError, HashedNodeReader, IntoHashType, NodeStore, TrieHash, }; - use crate::db::{Db, Proposal}; + use crate::db::{Db, Proposal, UseParallel}; use crate::root_store::{MockStore, RootStore}; use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _}; @@ -606,14 +603,23 @@ mod test { let keys: Vec<[u8; 1]> = vec![[kv; 1]]; let vals: Vec> = vec![Box::new([kv; 1])]; let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); } // Create, insert, close, open, insert - let db = TestDb::new(); + let db = TestDb::new_with_config( + DbConfig::builder() + .use_parallel(UseParallel::Always) + .build(), + ); insert_commit(&db, 1); - let db = db.reopen(); + let db = db.reopen_with_config( + DbConfig::builder() + .truncate(false) + .use_parallel(UseParallel::Always) + .build(), + ); insert_commit(&db, 2); // Check that the keys are still there after the commits let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); @@ -626,9 +632,17 @@ mod test { drop(db); // Open-db1, insert, open-db2, insert - let db1 = TestDb::new(); + let db1 = TestDb::new_with_config( + DbConfig::builder() + .use_parallel(UseParallel::Always) + .build(), + ); insert_commit(&db1, 1); - let db2 = TestDb::new(); + let db2 = TestDb::new_with_config( + DbConfig::builder() + .use_parallel(UseParallel::Always) + .build(), + ); insert_commit(&db2, 2); let committed1 = db1.revision(db1.root_hash().unwrap().unwrap()).unwrap(); let committed2 = db2.revision(db2.root_hash().unwrap().unwrap()).unwrap(); @@ -644,14 +658,18 @@ mod test { #[test] fn test_propose_parallel() { const N: usize = 100; - let db = TestDb::new(); + let db = TestDb::new_with_config( + DbConfig::builder() + .use_parallel(UseParallel::Always) + .build(), + ); // Test an empty proposal let keys: Vec<[u8; 0]> = Vec::new(); let vals: Vec> = Vec::new(); let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); // Create a proposal consisting of a single entry and an empty key. @@ -662,7 +680,7 @@ mod test { let vals: Vec> = vec![Box::new([0; 1])]; let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, v) in kviter { @@ -680,7 +698,7 @@ mod test { // Create a proposal that deletes the previous entry let vals: Vec> = vec![Box::new([0; 0])]; let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, _v) in kviter { @@ -699,7 +717,7 @@ mod test { .unzip(); let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, v) in kviter { assert_eq!(&proposal.val(k).unwrap().unwrap(), v); @@ -718,7 +736,7 @@ mod test { .unzip(); let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, _v) in kviter { assert_eq!(proposal.val(k).unwrap(), None); @@ -729,7 +747,7 @@ mod test { let keys: Vec<[u8; 0]> = vec![[0; 0]]; let vals: Vec> = vec![Box::new([0; 0])]; let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); // Create N keys and values like (key0, value0)..(keyN, valueN) @@ -746,7 +764,7 @@ mod test { // Looping twice to test that we are reusing the thread pool. for _ in 0..2 { let kviter = keys.iter().zip(vals.iter()).map_into_batch(); - let proposal = db.propose_parallel(kviter).unwrap(); + let proposal = db.propose(kviter).unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal let kviter = keys.iter().zip(vals.iter()); @@ -1090,11 +1108,14 @@ mod test { impl TestDb { fn new() -> Self { + TestDb::new_with_config(DbConfig::builder().build()) + } + + fn new_with_config(dbconfig: DbConfig) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let dbconfig = DbConfig::builder().build(); let db = Db::new(dbpath, dbconfig).unwrap(); TestDb { db, tmpdir } } @@ -1110,12 +1131,15 @@ mod test { } fn reopen(self) -> Self { + self.reopen_with_config(DbConfig::builder().truncate(false).build()) + } + + fn reopen_with_config(self, dbconfig: DbConfig) -> Self { let path = self.path(); let TestDb { db, tmpdir } = self; let root_store = db.into_root_store(); - let dbconfig = DbConfig::builder().truncate(false).build(); let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); TestDb { db, tmpdir } } From fc48a6dc091f71b1b5d365a54f33dec43c42dca5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:29:33 +0000 Subject: [PATCH 1016/1053] test: show symbol diff when ffi-nix build equivalency test fails (#1423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ffi-nix test fails intermittently with differing symbol counts (e.g., 20534 vs 20535) but doesn't indicate which symbol differs, making root cause analysis difficult. ## Changes - Extract symbols to temp files and use `diff` to show which symbols are unique to each build when counts mismatch - Preserves existing behavior—only adds diagnostic output on failure - Added clarifying comments explaining the diff format options **Before:** ```bash Nix build: 20534 symbols Cargo build: 20535 symbols ❌ Symbol counts differ ``` **After:** ```bash Nix build: 20534 symbols Cargo build: 20535 symbols ❌ Symbol counts differ === Symbol Differences === Symbols only in Nix build: [symbol list] Symbols only in Cargo build: [symbol list] ``` ## Testing - ✅ Verified bash syntax is valid - ✅ Tested diff logic with various scenarios - ✅ No new shellcheck warnings introduced - ✅ Maintains backward compatibility - ✅ CI validated the changes and successfully identified the symbol causing the flakiness (`rtree_read.constprop.0`) - Fixes #1422
    Original prompt > > ---- > > *This section details on the original issue you should resolve* > > ffi-nix test is flaky > See https://github.com/ava-labs/firewood/actions/runs/19077399006/job/54496653989 for an example. Earliest known failure is Oct 24. > > ## Comments on the Issue (you are @copilot in this section) > > > @rkuris > @maru-ava reports it is not reproducible outside of CI. It may be x86 only. > @rkuris > The biggest clue is that the number of symbols is different: > ``` > + echo 'Nix build: 20534 symbols' > Nix build: 20534 symbols > + echo 'Cargo build: 20535 symbols' > Cargo build: 20535 symbols > + '[' 20534 -eq 20535 ']' > ``` > > @copilot can you change the failing script so that it outputs the symbol differences? > >
    - Fixes ava-labs/firewood#1422 --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> Co-authored-by: maru --- ffi/test-build-equivalency.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ffi/test-build-equivalency.sh b/ffi/test-build-equivalency.sh index 1f2a23d14eac..155a32d98e88 100755 --- a/ffi/test-build-equivalency.sh +++ b/ffi/test-build-equivalency.sh @@ -28,14 +28,27 @@ ls -lh "$CARGO_LIB" "$NIX_LIB" echo "" echo "=== Symbol Count Comparison ===" -NIX_SYMBOLS=$(nm "$NIX_LIB" | wc -l) -CARGO_SYMBOLS=$(nm "$CARGO_LIB" | wc -l) +# Extract symbols to temporary files for comparison +nm "$NIX_LIB" | sort > "$TMPDIR/nix-symbols.txt" +nm "$CARGO_LIB" | sort > "$TMPDIR/cargo-symbols.txt" + +NIX_SYMBOLS=$(wc -l < "$TMPDIR/nix-symbols.txt") +CARGO_SYMBOLS=$(wc -l < "$TMPDIR/cargo-symbols.txt") echo "Nix build: $NIX_SYMBOLS symbols" echo "Cargo build: $CARGO_SYMBOLS symbols" if [ "$NIX_SYMBOLS" -eq "$CARGO_SYMBOLS" ]; then echo "✅ Symbol counts match" else echo "❌ Symbol counts differ" + echo "" + echo "=== Symbol Differences ===" + echo "Symbols only in Nix build:" + # Show lines that exist in the old file (nix) but not in the new file (cargo) + diff --unchanged-line-format="" --old-line-format="%L" --new-line-format="" "$TMPDIR/nix-symbols.txt" "$TMPDIR/cargo-symbols.txt" || true + echo "" + echo "Symbols only in Cargo build:" + # Show lines that exist in the new file (cargo) but not in the old file (nix) + diff --unchanged-line-format="" --old-line-format="" --new-line-format="%L" "$TMPDIR/nix-symbols.txt" "$TMPDIR/cargo-symbols.txt" || true fi echo "" From df8f1c18faa4ab0189a362b1a20de1dd4bcbbd20 Mon Sep 17 00:00:00 2001 From: maru Date: Wed, 5 Nov 2025 08:35:55 -0800 Subject: [PATCH 1017/1053] feat: Add just task runner to ensure reproducibility of CI (#1345) As a follow-on to #1319, this PR introduces the [just](https://github.com/casey/just) command runner to ensure the new CI ffi-nix CI job can be trivially reproduced locally. --------- Co-authored-by: Ron Kuris --- .github/check-license-headers.yaml | 2 + .github/workflows/ci.yaml | 16 +--- ffi/flake.lock | 8 +- ffi/flake.nix | 14 ++- ffi/go.mod | 3 +- justfile | 131 +++++++++++++++++++++++++++++ scripts/run-just.sh | 21 +++++ 7 files changed, 177 insertions(+), 18 deletions(-) create mode 100644 justfile create mode 100755 scripts/run-just.sh diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index bd1af237e927..2ca80dbfdd4a 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -53,6 +53,8 @@ "cliff.toml", "clippy.toml", "**/tests/compile_*/**", + "justfile", + "scripts/run-just.sh", ], } ] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1eb0c58625f8..e48e774c02d4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -251,18 +251,10 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 #v20 - uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 #v13 - - name: Test build equivalency between Nix and Cargo - run: bash -x ./ffi/test-build-equivalency.sh - - name: Test Go FFI bindings - working-directory: ffi - # - cgocheck2 is expensive but provides complete pointer checks - # - use hash mode ethhash since the flake builds with `--features ethhash,logger` - # - run golang outside a nix shell to validate viability without the env setup performed by a nix shell - run: | - GOLANG="nix run $PWD#go" - cd result/ffi - GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash ${GOLANG} test ./... - shell: bash + - name: Check that FFI flake is up-to-date + run: ./scripts/run-just.sh check-ffi-flake + - name: Test nix build of Golang FFI bindings + run: ./scripts/run-just.sh test-ffi-nix firewood-ethhash-differential-fuzz: needs: build diff --git a/ffi/flake.lock b/ffi/flake.lock index e95d44ffe963..2eda9b50caf9 100644 --- a/ffi/flake.lock +++ b/ffi/flake.lock @@ -39,17 +39,17 @@ }, "locked": { "dir": "nix/go", - "lastModified": 1760457933, - "narHash": "sha256-/OztRdmXd3cL6ycrzRYAgFkPTv9v5WWlcdtVlERaRKI=", + "lastModified": 1760973838, + "narHash": "sha256-UnngvRB45lUeWwot7cvB0MaedaQEQmcw+q8Y6WbeGtE=", "owner": "ava-labs", "repo": "avalanchego", - "rev": "edf67505fee7c95837c2220467de86fea0efc860", + "rev": "f10757d594eedf0f016bc1400739788c542f005f", "type": "github" }, "original": { "dir": "nix/go", "owner": "ava-labs", - "ref": "edf67505fee7c95837c2220467de86fea0efc860", + "ref": "f10757d594eedf0f016bc1400739788c542f005f", "repo": "avalanchego", "type": "github" } diff --git a/ffi/flake.nix b/ffi/flake.nix index e2853cc0c94d..1b25a796cb80 100644 --- a/ffi/flake.nix +++ b/ffi/flake.nix @@ -116,13 +116,25 @@ program = "${go}/bin/go"; }; + apps.jq = { + type = "app"; + program = "${pkgs.jq}/bin/jq"; + }; + + apps.just = { + type = "app"; + program = "${pkgs.just}/bin/just"; + }; + devShells.default = craneLib.devShell { inputsFrom = [ firewood-ffi ]; packages = with pkgs; [ firewood-ffi - rustToolchain go + jq + just + rustToolchain ]; shellHook = '' diff --git a/ffi/go.mod b/ffi/go.mod index be46486ab6b4..30ef658c3cd8 100644 --- a/ffi/go.mod +++ b/ffi/go.mod @@ -4,9 +4,10 @@ go 1.24 // Changes to the toolchain version should be replicated in: // - ffi/go.mod (here) -// - ffi/flake.nix (update golang.url to a version of avalanchego's nix/go/flake.nix that uses the desired version) +// - ffi/flake.nix (update golang.url to a version of avalanchego's nix/go/flake.nix that uses the desired version and run `just update-ffi-flake`) // - ffi/tests/eth/go.mod // - ffi/tests/firewood/go.mod +// `just check-golang-version` validates that these versions are in sync and will run in CI as part of the ffi-nix job. toolchain go1.24.9 require ( diff --git a/justfile b/justfile new file mode 100644 index 000000000000..61b95cb48dec --- /dev/null +++ b/justfile @@ -0,0 +1,131 @@ +# List available recipes +default: + ./scripts/run-just.sh --list + +# Build ffi with nix +build-ffi-nix: check-nix + cd ffi && nix build + +# Check if the git branch is clean +check-clean-branch: + #!/usr/bin/env bash + set -euo pipefail + + git add --all + git update-index --really-refresh >> /dev/null + + # Show the status of the working tree. + git status --short + + # Exits if any uncommitted changes are found. + git diff-index --quiet HEAD + +# Check if the FFI flake is up-to-date (requires clean git tree) +check-ffi-flake: check-nix + #!/usr/bin/env bash + set -euo pipefail + ./scripts/run-just.sh update-ffi-flake + ./scripts/run-just.sh check-clean-branch + +# Check if the golang version is set consistently (requires clean git tree) +check-golang-version: check-nix + #!/usr/bin/env bash + set -euo pipefail + + # Exit only at the end if any of the checks set FAILED=1 + FAILED= + + cd ffi + + TOOLCHAIN_VERSION=$(nix develop --command bash -c "go mod edit -json | jq -r '.Toolchain'") + echo "toolchain version in ffi/go.mod is ${TOOLCHAIN_VERSION}" + + ETH_TESTS_VERSION=$(nix develop --command bash -c "cd tests/eth && go mod edit -json | jq -r '.Toolchain'") + echo "toolchain version in ffi/tests/eth/go.mod is ${ETH_TESTS_VERSION}" + + if [[ "${TOOLCHAIN_VERSION}" != "${ETH_TESTS_VERSION}" ]]; then + echo "❌ toolchain version in ffi/tests/eth/go.mod should be ${TOOLCHAIN_VERSION}" + FAILED=1 + fi + + FIREWOOD_TESTS_VERSION=$(nix develop --command bash -c "cd tests/firewood && go mod edit -json | jq -r '.Toolchain'") + echo "toolchain version in ffi/tests/firewood/go.mod is ${FIREWOOD_TESTS_VERSION}" + + if [[ "${TOOLCHAIN_VERSION}" != "${FIREWOOD_TESTS_VERSION}" ]]; then + echo "❌ toolchain version in ffi/tests/firewood/go.mod should be ${TOOLCHAIN_VERSION}" + FAILED=1 + fi + + NIX_VERSION=$(nix run .#go -- version | awk '{print $3}') + echo "golang provided by ffi/flake.nix is ${NIX_VERSION}" + + if [[ "${TOOLCHAIN_VERSION}" != "${NIX_VERSION}" ]]; then + echo "❌ golang provided by ffi/flake/nix should be ${TOOLCHAIN_VERSION}" + echo "It will be necessary to update the golang.url in ffi/flake.nix to point to a SHA of"\ + "AvalancheGo whose nix/go/flake.nix provides ${TOOLCHAIN_VERSION}." + fi + + if [[ -n "${FAILED}" ]]; then + exit 1 + fi + +# Check if nix is installed +check-nix: + #!/usr/bin/env bash + set -euo pipefail + if ! command -v nix &> /dev/null; then + echo "Error: 'nix' is not installed." >&2 + echo "" >&2 + echo "To install nix:" >&2 + echo " - Visit: https://github.com/DeterminateSystems/nix-installer" >&2 + echo " - Or run: curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" >&2 + exit 1 + fi + +# Run all checks of ffi built with nix +test-ffi-nix: test-ffi-nix-build-equivalency test-ffi-nix-go-bindings + +# Test ffi build equivalency between nix and cargo +test-ffi-nix-build-equivalency: check-nix + #!/usr/bin/env bash + set -euo pipefail + + echo "Testing ffi build equivalency between nix and cargo" + + bash -x ./ffi/test-build-equivalency.sh + +# Test golang ffi bindings using the nix-built artifacts +test-ffi-nix-go-bindings: build-ffi-nix + #!/usr/bin/env bash + set -euo pipefail + + echo "running ffi tests against bindings built by nix..." + + cd ffi + + # Need to capture the flake path before changing directories to + # result/ffi because `result` is a nix store symlink so ../../ + # won't resolve to the ffi path containing the flake. + FLAKE_PATH="$PWD" + + # This runs golang outside a nix shell to validate viability + # without the env setup performed by a nix shell + GO="nix run $FLAKE_PATH#go" + + cd result/ffi + + # - cgocheck2 is expensive but provides complete pointer checks + # - use hash mode ethhash since the flake builds with `--features ethhash,logger` + GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=ethhash ${GO} test ./... + +# Ensure the FFI flake is up-to-date +update-ffi-flake: check-nix + #!/usr/bin/env bash + set -euo pipefail + cd ffi + + echo "ensuring flake lock file is current for golang" + nix flake update golang + + echo "checking for a consistent golang verion" + ../scripts/run-just.sh check-golang-version diff --git a/scripts/run-just.sh b/scripts/run-just.sh new file mode 100755 index 000000000000..377bffdcc24e --- /dev/null +++ b/scripts/run-just.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v just &> /dev/null; then + exec just "$@" +elif command -v nix &> /dev/null; then + exec nix run nixpkgs#just -- "$@" +else + echo "Error: Neither 'just' nor 'nix' is installed." >&2 + echo "" >&2 + echo "Please install one of the following:" >&2 + echo "" >&2 + echo "Option 1 - Install just:" >&2 + echo " - Visit: https://github.com/casey/just#installation" >&2 + echo " - Or use cargo: cargo install just" >&2 + echo "" >&2 + echo "Option 2 - Install nix:" >&2 + echo " - Visit: https://github.com/DeterminateSystems/nix-installer" >&2 + echo " - Or run: curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install" >&2 + exit 1 +fi From 801890d061943ec59e99886b67a725a1f365963e Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:49:11 -0500 Subject: [PATCH 1018/1053] feat(ffi)!: Add finalization logic for Revisions (#1435) When integrating code from the previous auto-drop method for Proposals in #1349, it's only reasonable to support this for Revisions as well. The logic here is exactly the same as for proposals, and they even share the same WaitGroup. --- ffi/firewood.go | 10 ++++---- ffi/firewood_test.go | 61 ++++++++++++++++++++++++++++++++++---------- ffi/proposal.go | 16 ++++++------ ffi/revision.go | 20 +++++++++++---- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 7ba670fd3452..5fc00ad0595e 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -52,8 +52,8 @@ type Database struct { // handle is returned and accepted by cgo functions. It MUST be treated as // an opaque value without special meaning. // https://en.wikipedia.org/wiki/Blinkenlights - handle *C.DatabaseHandle - proposals sync.WaitGroup + handle *C.DatabaseHandle + outstandingHandles sync.WaitGroup } // Config configures the opening of a [Database]. @@ -168,7 +168,7 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if err != nil { return nil, err } - return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), &db.proposals) + return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), &db.outstandingHandles) } // Get retrieves the value for the given key. It always returns a nil error. @@ -253,7 +253,7 @@ func (db *Database) Revision(root []byte) (*Revision, error) { rev, err := getRevisionFromResult(C.fwd_get_revision( db.handle, newBorrowedBytes(root, &pinner), - ), db) + ), &db.outstandingHandles) if err != nil { return nil, err } @@ -286,7 +286,7 @@ func (db *Database) Close(ctx context.Context) error { done := make(chan struct{}) go func() { - db.proposals.Wait() + db.outstandingHandles.Wait() close(done) }() diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 9dbfd82f92a3..6b2c3c391703 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1172,30 +1172,64 @@ func TestGetFromRootParallel(t *testing.T) { } } -func TestProposalHandlesFreed(t *testing.T) { +func TestHandlesFreeImplicitly(t *testing.T) { t.Parallel() - db, err := newDatabase(filepath.Join(t.TempDir(), "test_GC_drops_proposal.db")) + db, err := newDatabase(filepath.Join(t.TempDir(), "test_GC_drops_implicitly.db")) require.NoError(t, err) + // make the db non-empty + _, err = db.Update([][]byte{keyForTest(1)}, [][]byte{valForTest(1)}) + require.NoError(t, err) + + var ( + explicitlyDropped []any + implicitlyDropped []any + ) + + // Grab one revision initially. All proposals can also be revisions, so + // this is the only case in which we don't add both a proposal and a revision. + historicExplicit, err := db.LatestRevision() + require.NoErrorf(t, err, "%T.LatestRevision()", db) + require.NoError(t, historicExplicit.Drop()) + explicitlyDropped = append(explicitlyDropped, historicExplicit) + historicImplicit, err := db.LatestRevision() + require.NoErrorf(t, err, "%T.LatestRevision()", db) + implicitlyDropped = append(implicitlyDropped, historicImplicit) + // These MUST NOT be committed nor dropped as they demonstrate that the GC // finalizer does it for us. p0, err := db.Propose(kvForTest(1)) require.NoErrorf(t, err, "%T.Propose(...)", db) + root0, err := p0.Root() + require.NoErrorf(t, err, "%T.Root()", p0) + rev0, err := db.Revision(root0) + require.NoErrorf(t, err, "%T.Revision(...)", db) + implicitlyDropped = append(implicitlyDropped, p0, rev0) p1, err := p0.Propose(kvForTest(1)) require.NoErrorf(t, err, "%T.Propose(...)", p0) + root1, err := p1.Root() + require.NoErrorf(t, err, "%T.Root()", p1) + rev1, err := db.Revision(root1) + require.NoErrorf(t, err, "%T.Revision(...)", db) + implicitlyDropped = append(implicitlyDropped, p1, rev1) // Demonstrates that explicit [Proposal.Commit] and [Proposal.Drop] calls // are sufficient to unblock [Database.Close]. - var keep []*Proposal + // Each proposal has a corresponding revision to drop as well. for name, free := range map[string](func(*Proposal) error){ "Commit": (*Proposal).Commit, "Drop": (*Proposal).Drop, } { p, err := db.Propose(kvForTest(1)) require.NoErrorf(t, err, "%T.Propose(...)", db) + root, err := p.Root() + require.NoErrorf(t, err, "%T.Root()", p) + rev, err := db.Revision(root) + require.NoErrorf(t, err, "%T.Revision(...)", db) require.NoErrorf(t, free(p), "%T.%s()", p, name) - keep = append(keep, p) + require.NoErrorf(t, rev.Drop(), "%T.Drop()", rev) + explicitlyDropped = append(explicitlyDropped, p, rev) } done := make(chan struct{}) @@ -1206,25 +1240,26 @@ func TestProposalHandlesFreed(t *testing.T) { select { case <-done: - t.Errorf("%T.Close() returned with undropped %T", db, p0) //nolint:forbidigo // Use of require is impossible without a hack like require.False(true) + require.Failf(t, "Unexpected return", "%T.Close() returned with undropped %T", db, p0) case <-time.After(300 * time.Millisecond): // TODO(arr4n) use `synctest` package when at Go 1.25 } - runtime.KeepAlive(p0) - runtime.KeepAlive(p1) - p0 = nil - p1 = nil //nolint:ineffassign // Makes the value unreachable, allowing the finalizer to call Drop() + // This is the last time we must guarantee that these handles are reachable. + // After this, they must be unreachable so that the GC can collect them. + for _, h := range implicitlyDropped { + runtime.KeepAlive(h) + } // In practice there's no need to call [runtime.GC] if [Database.Close] is // called after all proposals are unreachable, as it does it itself. runtime.GC() - // Note that [Database.Close] waits for outstanding proposals, so this would - // block permanently if the unreachability of `p0` and `p1` didn't result in - // their [Proposal.Drop] methods being called. + // Note that [Database.Close] waits for outstanding handles, so this would + // block permanently if the unreachability of implicitly dropped handles didn't + // result in their Drop methods being called. <-done - for _, p := range keep { + for _, p := range explicitlyDropped { runtime.KeepAlive(p) } } diff --git a/ffi/proposal.go b/ffi/proposal.go index fd76063aac76..51ac6369b2cf 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -33,7 +33,7 @@ type Proposal struct { // [Database.Close] blocks on this WaitGroup, which is incremented by // [getProposalFromProposalResult], and decremented by either // [Proposal.Commit] or [Proposal.Drop] (when the handle is disowned). - openProposals *sync.WaitGroup + outstandingHandles *sync.WaitGroup // The proposal root hash. root []byte @@ -93,7 +93,7 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if err != nil { return nil, err } - return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.openProposals) + return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.outstandingHandles) } // disownHandle is the common path of [Proposal.Commit] and [Proposal.Drop], the @@ -108,7 +108,7 @@ func (p *Proposal) disownHandle(fn func(*C.ProposalHandle) error, disownEvenOnEr err := fn(p.handle) if disownEvenOnErr || err == nil { p.handle = nil - p.openProposals.Done() + p.outstandingHandles.Done() } return err } @@ -146,7 +146,7 @@ func dropProposal(h *C.ProposalHandle) error { } // getProposalFromProposalResult converts a C.ProposalResult to a Proposal or error. -func getProposalFromProposalResult(result C.ProposalResult, openProposals *sync.WaitGroup) (*Proposal, error) { +func getProposalFromProposalResult(result C.ProposalResult, outstandingHandles *sync.WaitGroup) (*Proposal, error) { switch result.tag { case C.ProposalResult_NullHandlePointer: return nil, errDBClosed @@ -154,11 +154,11 @@ func getProposalFromProposalResult(result C.ProposalResult, openProposals *sync. body := (*C.ProposalResult_Ok_Body)(unsafe.Pointer(&result.anon0)) hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) proposal := &Proposal{ - handle: body.handle, - root: hashKey[:], - openProposals: openProposals, + handle: body.handle, + root: hashKey[:], + outstandingHandles: outstandingHandles, } - openProposals.Add(1) + outstandingHandles.Add(1) runtime.SetFinalizer(proposal, (*Proposal).Drop) return proposal, nil case C.ProposalResult_Err: diff --git a/ffi/revision.go b/ffi/revision.go index 0fc600aa42fd..96a5a911727f 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" "runtime" + "sync" "unsafe" ) @@ -29,10 +30,13 @@ var ( type Revision struct { // The database this revision is associated with. Holding this ensures // the DB outlives the revision for cleanup ordering. - db *Database handle *C.RevisionHandle + disown sync.Mutex // The revision root root []byte + // outstandingHandles is the WaitGroup in Database that tracks open Rust handles. + // This is incremented when the revision is created, and decremented when Drop is called. + outstandingHandles *sync.WaitGroup } // Get reads the value stored at the provided key within the revision. @@ -72,6 +76,9 @@ func (r *Revision) Iter(key []byte) (*Iterator, error) { // // It is safe to call Drop multiple times; subsequent calls after the first are no-ops. func (r *Revision) Drop() error { + r.disown.Lock() + defer r.disown.Unlock() + if r.handle == nil { return nil } @@ -80,6 +87,7 @@ func (r *Revision) Drop() error { return fmt.Errorf("%w: %w", errFreeingValue, err) } + r.outstandingHandles.Done() r.handle = nil // Prevent double free return nil @@ -90,7 +98,7 @@ func (r *Revision) Root() []byte { } // getRevisionFromResult converts a C.RevisionResult to a Revision or error. -func getRevisionFromResult(result C.RevisionResult, db *Database) (*Revision, error) { +func getRevisionFromResult(result C.RevisionResult, openProposals *sync.WaitGroup) (*Revision, error) { switch result.tag { case C.RevisionResult_NullHandlePointer: return nil, errDBClosed @@ -100,10 +108,12 @@ func getRevisionFromResult(result C.RevisionResult, db *Database) (*Revision, er body := (*C.RevisionResult_Ok_Body)(unsafe.Pointer(&result.anon0)) hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) rev := &Revision{ - db: db, - handle: body.handle, - root: hashKey[:], + handle: body.handle, + root: hashKey[:], + outstandingHandles: openProposals, } + openProposals.Add(1) + runtime.SetFinalizer(rev, (*Revision).Drop) return rev, nil case C.RevisionResult_Err: err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError() From a921e81f6fb1e47f6bf45a5dfe761ea74ea6489e Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:06:28 -0500 Subject: [PATCH 1019/1053] chore: Add guidance on Go workspaces (#1434) Many editors do not work well with multiple Go modules combined with CGO, and this is just one solution to ease the developer experience slightly. An alternative to #1430 --- .gitignore | 4 ++++ ffi/README.md | 4 +++- ffi/tests/eth/go.mod | 2 +- ffi/tests/firewood/go.mod | 2 +- justfile | 9 +++++++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3ac280ce209a..bc93dbf81862 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,10 @@ Temporary Items debug/ target/ +### Golang ### +go.work +go.work.sum + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/ffi/README.md b/ffi/README.md index da24e79c148c..70773c4de162 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -142,9 +142,11 @@ As you edit any Rust code and save the file in VS Code, the `firewood.h` file is Because the C header file is autogenerated from the Rust code, the naming matches exactly (due to the `no_mangle` macro). However, the C definitions imported in Go do not match exactly, and are prefixed with `struct_`. Function naming is the same as the header file. These names are generated by the `go tool cgo` command above. +It is possible that your editor does not properly recognize the C bindings, due to the nature of multi-module workspaces. If the Go code still compiles, this is likely the issue. To fix this, you can just `./scripts/run_just.sh setup-go-workspace` from the repository root and refresh your editor. Changes to the go workspace should not be checked in. + ### Testing -Although the VS Code testing feature does work, there are some quirks in ensuring proper building. The Rust code must be compiled separated, and sometimes the `go test` command continues to use a cached result. Whenever testing after making changes to the Rust/C builds, the cache should be cleared if results don't seem correct. Do not compile with `--features ethhash`, as some tests will fail. +Although the VS Code testing feature does work, there are some quirks in ensuring proper building. The Rust code must be compiled separated, and sometimes the `go test` command continues to use a cached result. Whenever testing after making changes to the Rust/C builds, the cache should be cleared if results don't seem correct. The Go testing suite can determine dynamically whether ethhash is enabled or not, so it can be run with either configuration. For each individual testing module, ensure the hashing method you compiled with matches the test. To ensure there are no memory leaks, the easiest way is to use your preferred CLI tool (e.g. `valgrind` for Linux, `leaks` for macOS) and compile the tests into a binary. You must not compile a release binary to ensure all memory can be managed. An example flow is given below. diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index 67dac0002d0d..b4bbd7f68f6c 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -1,4 +1,4 @@ -module github.com/ava-labs/firewood/ffi/tests +module github.com/ava-labs/firewood/ffi/tests/eth go 1.24 diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index 59fea55099a6..56179bdcde07 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -1,4 +1,4 @@ -module github.com/ava-labs/firewood/ffi/tests +module github.com/ava-labs/firewood/ffi/tests/firewood go 1.24 diff --git a/justfile b/justfile index 61b95cb48dec..d0885778f5f0 100644 --- a/justfile +++ b/justfile @@ -82,6 +82,15 @@ check-nix: exit 1 fi +# Adds go workspace for user experience consistency +setup-go-workspace: + #!/usr/bin/env bash + set -euo pipefail + if [ -f "go.work" ]; then + rm go.work go.work.sum + fi + go work init ./ffi ./ffi/tests/eth ./ffi/tests/firewood + # Run all checks of ffi built with nix test-ffi-nix: test-ffi-nix-build-equivalency test-ffi-nix-go-bindings From c2ec8a32c1b37e39e6f7b0cfb7ce8680c163c23b Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:12:44 -0500 Subject: [PATCH 1020/1053] fix(benchmark/bootstrap): consistent go deps (#1436) Currently, there are two bugs when it comes to utilizing the bootstrap script: - Currently, we force AvalancheGo to utilize the local versions of Coreth, Libevm, and Firewood. However, we don't apply this same process to Coreth. This is problematic because if we specify different versions of Libevm and Firewood in the bootstrap script than what's in the Coreth `go.mod`, then AvalancheGo and Coreth will diverge on what versions of Libevm and Firewood they use. - Utilizing the same version of libevm that's in the AvalancheGo/Coreth `go.mod` (`v1.13.15`) results in the error below. To my understanding, this is because checking out a tag breaks Go module resolutions even if you run `go mod tidy` inside of Libevm. ``` ../libevm/common/bytes.go:24:2: missing go.sum entry for module providing package github.com/ethereum/go-ethereum/common/hexutil (imported by github.com/ava-labs/libevm/core); to add: go get github.com/ava-labs/libevm/core@v1.13.15-0.20251016142715-1bccf4f2ddb2 FAIL github.com/ava-labs/avalanchego/tests/reexecute/c [setup failed] FAIL task: Failed to run task "reexecute-cchain-range": exit status 1 ``` This PR fixes these issues as follows: - Adds an additional step to force Coreth to use the local versions of Libevm and Firewood - Replaces passing in a branch with passing in a commit for Libevm --- benchmark/bootstrap/aws-launch.sh | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index b0d812f8a5cc..514cd9c3d1e6 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -6,7 +6,7 @@ INSTANCE_TYPE=i4g.large FIREWOOD_BRANCH="" AVALANCHEGO_BRANCH="" CORETH_BRANCH="" -LIBEVM_BRANCH="" +LIBEVM_COMMIT="" NBLOCKS="1m" REGION="us-west-2" DRY_RUN=false @@ -58,7 +58,7 @@ show_usage() { echo " --firewood-branch BRANCH Firewood git branch to checkout" echo " --avalanchego-branch BRANCH AvalancheGo git branch to checkout" echo " --coreth-branch BRANCH Coreth git branch to checkout" - echo " --libevm-branch BRANCH LibEVM git branch to checkout" + echo " --libevm-commit COMMIT LibEVM git commit to checkout" echo " --nblocks BLOCKS Number of blocks to download (default: 1m)" echo " --region REGION AWS region (default: us-west-2)" echo " --spot Use spot instance pricing (default depends on instance type)" @@ -108,8 +108,8 @@ while [[ $# -gt 0 ]]; do CORETH_BRANCH="$2" shift 2 ;; - --libevm-branch) - LIBEVM_BRANCH="$2" + --libevm-commit) + LIBEVM_COMMIT="$2" shift 2 ;; --nblocks) @@ -154,7 +154,7 @@ echo " Instance Type: $INSTANCE_TYPE ($TYPE)" echo " Firewood Branch: ${FIREWOOD_BRANCH:-default}" echo " AvalancheGo Branch: ${AVALANCHEGO_BRANCH:-default}" echo " Coreth Branch: ${CORETH_BRANCH:-default}" -echo " LibEVM Branch: ${LIBEVM_BRANCH:-default}" +echo " LibEVM Commit: ${LIBEVM_COMMIT:-default}" echo " Number of Blocks: $NBLOCKS" echo " Region: $REGION" if [ "$SPOT_INSTANCE" = true ]; then @@ -188,7 +188,6 @@ if [ "$DRY_RUN" = false ]; then FIREWOOD_BRANCH_ARG="" AVALANCHEGO_BRANCH_ARG="" CORETH_BRANCH_ARG="" -LIBEVM_BRANCH_ARG="" if [ -n "$FIREWOOD_BRANCH" ]; then FIREWOOD_BRANCH_ARG="--branch $FIREWOOD_BRANCH" @@ -199,9 +198,9 @@ fi if [ -n "$CORETH_BRANCH" ]; then CORETH_BRANCH_ARG="--branch $CORETH_BRANCH" fi -if [ -n "$LIBEVM_BRANCH" ]; then - LIBEVM_BRANCH_ARG="--branch $LIBEVM_BRANCH" -fi + +# For libevm, default to main branch if no commit specified +LIBEVM_COMMIT_CHECKOUT="${LIBEVM_COMMIT:-main}" # set up this script to run at startup, installing a few packages, creating user accounts, # and downloading the blocks for the C-chain @@ -323,7 +322,19 @@ runcmd: git clone --depth 100 __CORETH_BRANCH_ARG__ https://github.com/ava-labs/coreth.git - > sudo -u ubuntu -D /mnt/nvme/ubuntu - git clone --depth 100 __LIBEVM_BRANCH_ARG__ https://github.com/ava-labs/libevm.git + git clone https://github.com/ava-labs/libevm.git + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/libevm + git checkout __LIBEVM_COMMIT__ + # force coreth to use the checked-out versions of libevm and firewood + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/coreth + /usr/local/go/bin/go mod edit -replace + github.com/ava-labs/firewood-go-ethhash/ffi=../firewood/ffi + - > + sudo -u ubuntu -D /mnt/nvme/ubuntu/coreth + /usr/local/go/bin/go mod edit -replace + github.com/ava-labs/libevm=../libevm # force avalanchego to use the checked-out versions of coreth, libevm, and firewood - > sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego @@ -343,8 +354,10 @@ runcmd: --profile maxperf --features ethhash,logger > /mnt/nvme/ubuntu/firewood/build.log 2>&1 - # build avalanchego + # run go mod tidy for coreth and avalanchego + - sudo -u ubuntu --login -D /mnt/nvme/ubuntu/coreth go mod tidy - sudo -u ubuntu --login -D /mnt/nvme/ubuntu/avalanchego go mod tidy + # build avalanchego - > sudo -u ubuntu --login -D /mnt/nvme/ubuntu/avalanchego time scripts/build.sh > /mnt/nvme/ubuntu/avalanchego/build.log 2>&1 & @@ -377,7 +390,7 @@ USERDATA=$(echo "$USERDATA_TEMPLATE" | \ sed "s|__FIREWOOD_BRANCH_ARG__|$FIREWOOD_BRANCH_ARG|g" | \ sed "s|__AVALANCHEGO_BRANCH_ARG__|$AVALANCHEGO_BRANCH_ARG|g" | \ sed "s|__CORETH_BRANCH_ARG__|$CORETH_BRANCH_ARG|g" | \ - sed "s|__LIBEVM_BRANCH_ARG__|$LIBEVM_BRANCH_ARG|g" | \ + sed "s|__LIBEVM_COMMIT__|$LIBEVM_COMMIT_CHECKOUT|g" | \ sed "s|__NBLOCKS__|$NBLOCKS|g" | \ sed "s|__END_BLOCK__|$END_BLOCK|g" | \ base64) @@ -399,8 +412,8 @@ fi if [ -n "$CORETH_BRANCH" ]; then INSTANCE_NAME="$INSTANCE_NAME-ce-$CORETH_BRANCH" fi -if [ -n "$LIBEVM_BRANCH" ]; then - INSTANCE_NAME="$INSTANCE_NAME-le-$LIBEVM_BRANCH" +if [ -n "$LIBEVM_COMMIT" ]; then + INSTANCE_NAME="$INSTANCE_NAME-le-$LIBEVM_COMMIT" fi # Build spot instance market options if requested @@ -456,6 +469,7 @@ else INSTANCE_ID=$(eval "$AWS_CMD") echo "instance id $INSTANCE_ID started" + echo -e "\033[33mREMINDER: Please manually shut down your EC2 instance after running the benchmark to avoid unexpected costs!\033[0m" fi if [ "$DRY_RUN" = false ]; then From e775eeb178b36fdfb808540b59a4d91f6cc2dac6 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:10:19 -0500 Subject: [PATCH 1021/1053] feat(benchmark/bootstrap): add config param (#1438) This PR adds a `--config` option to the benchmark script. This is useful as it facilitates benchmarking between different configurations of Coreth (e.g. using vanilla Coreth vs Coreth with Firewood). The current supported Coreth config options in the reexecution tests can be found here: https://github.com/ava-labs/avalanchego/blob/29075934327672a3cdc091c604264be5ca6fa596/tests/reexecute/c/vm_reexecute_test.go#L80-L91 --- benchmark/bootstrap/aws-launch.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/benchmark/bootstrap/aws-launch.sh b/benchmark/bootstrap/aws-launch.sh index 514cd9c3d1e6..b4645ac2561a 100755 --- a/benchmark/bootstrap/aws-launch.sh +++ b/benchmark/bootstrap/aws-launch.sh @@ -8,6 +8,7 @@ AVALANCHEGO_BRANCH="" CORETH_BRANCH="" LIBEVM_COMMIT="" NBLOCKS="1m" +CONFIG="firewood" REGION="us-west-2" DRY_RUN=false SPOT_INSTANCE=false @@ -60,6 +61,7 @@ show_usage() { echo " --coreth-branch BRANCH Coreth git branch to checkout" echo " --libevm-commit COMMIT LibEVM git commit to checkout" echo " --nblocks BLOCKS Number of blocks to download (default: 1m)" + echo " --config CONFIG The VM reexecution config to use (default: firewood)" echo " --region REGION AWS region (default: us-west-2)" echo " --spot Use spot instance pricing (default depends on instance type)" echo " --dry-run Show the aws command that would be run without executing it" @@ -122,6 +124,10 @@ while [[ $# -gt 0 ]]; do fi shift 2 ;; + --config) + CONFIG="$2" + shift 2 + ;; --region) REGION="$2" shift 2 @@ -156,6 +162,7 @@ echo " AvalancheGo Branch: ${AVALANCHEGO_BRANCH:-default}" echo " Coreth Branch: ${CORETH_BRANCH:-default}" echo " LibEVM Commit: ${LIBEVM_COMMIT:-default}" echo " Number of Blocks: $NBLOCKS" +echo " Config: $CONFIG" echo " Region: $REGION" if [ "$SPOT_INSTANCE" = true ]; then echo " Spot Instance: Yes (max price: \$${MAX_SPOT_PRICES[$INSTANCE_TYPE]})" @@ -372,7 +379,7 @@ runcmd: # execute bootstrapping - > sudo -u ubuntu -D /mnt/nvme/ubuntu/avalanchego --login - time task reexecute-cchain-range CURRENT_STATE_DIR=/mnt/nvme/ubuntu/exec-data/current-state BLOCK_DIR=/mnt/nvme/ubuntu/exec-data/blocks START_BLOCK=1 END_BLOCK=__END_BLOCK__ CONFIG=firewood METRICS_ENABLED=false + time task reexecute-cchain-range CURRENT_STATE_DIR=/mnt/nvme/ubuntu/exec-data/current-state BLOCK_DIR=/mnt/nvme/ubuntu/exec-data/blocks START_BLOCK=1 END_BLOCK=__END_BLOCK__ CONFIG=__CONFIG__ METRICS_ENABLED=false > /var/log/bootstrap.log 2>&1 END_HEREDOC ) @@ -393,6 +400,7 @@ USERDATA=$(echo "$USERDATA_TEMPLATE" | \ sed "s|__LIBEVM_COMMIT__|$LIBEVM_COMMIT_CHECKOUT|g" | \ sed "s|__NBLOCKS__|$NBLOCKS|g" | \ sed "s|__END_BLOCK__|$END_BLOCK|g" | \ + sed "s|__CONFIG__|$CONFIG|g" | \ base64) export USERDATA From cae9899e7730a185fa0a3777b2c8b549cdc26835 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 7 Nov 2025 12:56:44 -0800 Subject: [PATCH 1022/1053] chore(release): prepare for v0.0.14 (#1442) - Updated `.golangci.yaml` - Updated cargo dependencies - Resolved deprecated warning in `fwdctl` - Resolved new 1.91 lint warnings - Updated release documentation --- .github/.golangci.yaml.patch | 6 +- CHANGELOG.md | 73 ++++++ Cargo.lock | 481 ++++++++++++++--------------------- Cargo.toml | 22 +- RELEASE.md | 86 ++++++- benchmark/Cargo.toml | 4 +- clippy.toml | 2 +- ffi/.golangci.yaml | 1 + ffi/Cargo.toml | 4 +- firewood/Cargo.toml | 4 +- fwdctl/Cargo.toml | 6 +- fwdctl/tests/cli.rs | 103 ++++---- storage/Cargo.toml | 8 +- storage/src/node/children.rs | 4 +- 14 files changed, 429 insertions(+), 375 deletions(-) diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index 07c093fc0dac..47f67e25de00 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,5 +1,5 @@ ---- .github/.golangci.yaml 2025-10-31 18:45:07.201781593 +0000 -+++ ffi/.golangci.yaml 2025-10-31 18:45:07.200643422 +0000 +--- .github/.golangci.yaml 2025-11-07 11:52:50 ++++ ffi/.golangci.yaml 2025-11-07 11:52:50 @@ -75,8 +75,6 @@ rules: packages: @@ -57,7 +57,7 @@ tagalign: align: true sort: true -@@ -250,7 +218,7 @@ +@@ -251,7 +219,7 @@ - standard - default - blank diff --git a/CHANGELOG.md b/CHANGELOG.md index dadac773b03f..7fc44f2dc6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,79 @@ All notable changes to this project will be documented in this file. +## [0.0.14] - 2025-11-07 + +### 🚀 Features + +- *(monitoring)* Grafana automatic dashboard and auth provisioning ([#1307](https://github.com/ava-labs/firewood/pull/1307)) +- *(ffi)* Implement revision handles and expose to FFI layer ([#1326](https://github.com/ava-labs/firewood/pull/1326)) +- *(ffi-iterator)* Implementation of Iterator in Rust (1/4) ([#1255](https://github.com/ava-labs/firewood/pull/1255)) +- *(ffi-iterator)* Implementation of Iterator in Go (2/4) ([#1256](https://github.com/ava-labs/firewood/pull/1256)) +- *(1/7)* Add U4 type to be used as a path component ([#1336](https://github.com/ava-labs/firewood/pull/1336)) +- *(2/7)* Add newtype for PathComponent ([#1337](https://github.com/ava-labs/firewood/pull/1337)) +- *(3/7)* Add TriePath trait and path abstraction ([#1338](https://github.com/ava-labs/firewood/pull/1338)) +- *(4/7)* Add SplitPath trait ([#1339](https://github.com/ava-labs/firewood/pull/1339)) +- *(ffi-iterator)* Batching support for Iterator (3/4) ([#1257](https://github.com/ava-labs/firewood/pull/1257)) +- *(5/7)* Add PackedPathRef type ([#1341](https://github.com/ava-labs/firewood/pull/1341)) +- *(6/7)* Children newtype ([#1344](https://github.com/ava-labs/firewood/pull/1344)) +- Use PathComponent in proofs ([#1359](https://github.com/ava-labs/firewood/pull/1359)) +- Replace NodeAndPrefix with HashableShunt ([#1362](https://github.com/ava-labs/firewood/pull/1362)) +- Parallel updates to Merkle trie (v2) ([#1258](https://github.com/ava-labs/firewood/pull/1258)) +- Parallel hashing of Merkle trie ([#1303](https://github.com/ava-labs/firewood/pull/1303)) +- Add nix flake for ffi ([#1319](https://github.com/ava-labs/firewood/pull/1319)) +- Add more info for IO error message ([#1378](https://github.com/ava-labs/firewood/pull/1378)) +- Add TrieNode trait and related functionality ([#1363](https://github.com/ava-labs/firewood/pull/1363)) +- Add hashed key-value trie implementation ([#1365](https://github.com/ava-labs/firewood/pull/1365)) +- Define explicit associated types on Hashable ([#1366](https://github.com/ava-labs/firewood/pull/1366)) +- *(ffi)* `Database.Close()` guarantees proposals committed or freed ([#1349](https://github.com/ava-labs/firewood/pull/1349)) +- *(ffi)* [**breaking**] Support empty values in Update operations ([#1420](https://github.com/ava-labs/firewood/pull/1420)) +- Add just task runner to ensure reproducibility of CI ([#1345](https://github.com/ava-labs/firewood/pull/1345)) +- *(ffi)* [**breaking**] Add finalization logic for Revisions ([#1435](https://github.com/ava-labs/firewood/pull/1435)) +- *(benchmark/bootstrap)* Add config param ([#1438](https://github.com/ava-labs/firewood/pull/1438)) + +### 🐛 Bug Fixes + +- Explicitly release advisory lock on drop ([#1352](https://github.com/ava-labs/firewood/pull/1352)) +- EINTR during iouring calls ([#1354](https://github.com/ava-labs/firewood/pull/1354)) +- Revert "feat: Parallel updates to Merkle trie (v2)" ([#1372](https://github.com/ava-labs/firewood/pull/1372)) +- Revert "fix: Revert "feat: Parallel updates to Merkle trie (v2)"" ([#1374](https://github.com/ava-labs/firewood/pull/1374)) +- *(storage)* Flush freelist early to prevent corruption ([#1389](https://github.com/ava-labs/firewood/pull/1389)) +- *(benchmark/bootstrap)* Consistent go deps ([#1436](https://github.com/ava-labs/firewood/pull/1436)) + +### 🚜 Refactor + +- Unify root storage using Child enum ([#1330](https://github.com/ava-labs/firewood/pull/1330)) +- *(db)* `TestDb` constructor ([#1351](https://github.com/ava-labs/firewood/pull/1351)) + +### 📚 Documentation + +- *(benchmark/bootstrap)* Expand README.md ([#1421](https://github.com/ava-labs/firewood/pull/1421)) + +### 🧪 Testing + +- Show symbol diff when ffi-nix build equivalency test fails ([#1423](https://github.com/ava-labs/firewood/pull/1423)) + +### ⚙️ Miscellaneous Tasks + +- Include dependency lockfile ([#1321](https://github.com/ava-labs/firewood/pull/1321)) +- Add race detection in Go ([#1323](https://github.com/ava-labs/firewood/pull/1323)) +- *(nodestore)* Remove empty committed err ([#1353](https://github.com/ava-labs/firewood/pull/1353)) +- Upgrade dependencies ([#1360](https://github.com/ava-labs/firewood/pull/1360)) +- *(nodestore)* Access persisted nodestores ([#1355](https://github.com/ava-labs/firewood/pull/1355)) +- Update Go to 1.24.8 ([#1361](https://github.com/ava-labs/firewood/pull/1361)) +- Print ulimits and set memlock to unlimited before fuzz tests ([#1375](https://github.com/ava-labs/firewood/pull/1375)) +- *(db/manager)* Add `MockStore` ([#1346](https://github.com/ava-labs/firewood/pull/1346)) +- Update Go to 1.24.9 ([#1380](https://github.com/ava-labs/firewood/pull/1380)) +- *(ffi/firewood)* Remove `RootStore` generics ([#1388](https://github.com/ava-labs/firewood/pull/1388)) +- Update .golangci.yaml ([#1394](https://github.com/ava-labs/firewood/pull/1394)) +- Update ffi build check to configure cargo with same MAKEFLAGS as nix ([#1392](https://github.com/ava-labs/firewood/pull/1392)) +- Fix new lint warning from 1.91 update ([#1417](https://github.com/ava-labs/firewood/pull/1417)) +- Update .golangci.yaml ([#1419](https://github.com/ava-labs/firewood/pull/1419)) +- [**breaking**] Drop binary support for macos 13/14 ([#1425](https://github.com/ava-labs/firewood/pull/1425)) +- *(ci)* Disable ffi-nix job pending reliable build equivalency ([#1426](https://github.com/ava-labs/firewood/pull/1426)) +- Added helper to reduce code duplication between Db.propose and Proposal.create_proposal ([#1343](https://github.com/ava-labs/firewood/pull/1343)) +- Add guidance on Go workspaces ([#1434](https://github.com/ava-labs/firewood/pull/1434)) + ## [0.0.13] - 2025-09-26 ### 🚀 Features diff --git a/Cargo.lock b/Cargo.lock index ddf0cd03181a..1f705973b445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -32,9 +32,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -195,13 +195,12 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.17" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" dependencies = [ "anstyle", "bstr", - "doc-comment", "libc", "predicates", "predicates-core", @@ -244,16 +243,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libloading", ] [[package]] @@ -292,7 +290,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -334,9 +332,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -361,9 +359,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -419,9 +417,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cbindgen" -version = "0.29.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" dependencies = [ "clap", "heck", @@ -433,14 +431,14 @@ dependencies = [ "serde_json", "syn", "tempfile", - "toml 0.8.23", + "toml", ] [[package]] name = "cc" -version = "1.2.41" +version = "1.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" dependencies = [ "find-msvc-tools", "jobserver", @@ -459,9 +457,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" @@ -516,9 +514,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -526,9 +524,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -595,9 +593,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", @@ -735,30 +733,30 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] [[package]] name = "ctor" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c9b8bdf64ee849747c1b12eb861d21aa47fa161564f48332f1afe2373bf899" +checksum = "3ffc71fcdcdb40d6f087edddf7f8f1f8f79e6cf922f555a9ee8779752d4819bd" dependencies = [ "ctor-proc-macro", "dtor", @@ -817,17 +815,11 @@ dependencies = [ "syn", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dtor" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" dependencies = [ "dtor-proc-macro", ] @@ -1036,7 +1028,7 @@ dependencies = [ [[package]] name = "firewood" -version = "0.0.13" +version = "0.0.14" dependencies = [ "bytemuck", "bytemuck_derive", @@ -1069,7 +1061,7 @@ dependencies = [ [[package]] name = "firewood-benchmark" -version = "0.0.13" +version = "0.0.14" dependencies = [ "clap", "env_logger", @@ -1096,7 +1088,7 @@ dependencies = [ [[package]] name = "firewood-ffi" -version = "0.0.13" +version = "0.0.14" dependencies = [ "cbindgen", "chrono", @@ -1114,7 +1106,7 @@ dependencies = [ [[package]] name = "firewood-fwdctl" -version = "0.0.13" +version = "0.0.14" dependencies = [ "anyhow", "askama", @@ -1136,7 +1128,7 @@ dependencies = [ [[package]] name = "firewood-macros" -version = "0.0.13" +version = "0.0.14" dependencies = [ "coarsetime", "metrics", @@ -1148,12 +1140,12 @@ dependencies = [ [[package]] name = "firewood-storage" -version = "0.0.13" +version = "0.0.14" dependencies = [ "aquamarine", "arc-swap", "bitfield", - "bitflags 2.9.4", + "bitflags 2.10.0", "bumpalo", "bytemuck", "bytemuck_derive", @@ -1186,7 +1178,7 @@ dependencies = [ [[package]] name = "firewood-triehash" -version = "0.0.13" +version = "0.0.14" dependencies = [ "criterion", "ethereum-types", @@ -1231,6 +1223,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1359,19 +1357,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] @@ -1407,9 +1405,9 @@ dependencies = [ [[package]] name = "half" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", @@ -1437,9 +1435,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1447,6 +1443,11 @@ name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -1468,9 +1469,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "http" @@ -1621,9 +1622,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1634,9 +1635,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1647,11 +1648,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1662,42 +1662,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1785,9 +1781,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1795,9 +1791,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" dependencies = [ "console", "portable-atomic", @@ -1826,17 +1822,17 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "4.0.2" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d762194228a2f1c11063e46e32e5acb96e66e906382b9eb5441f2e0504bbd5a" +checksum = "14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699" [[package]] name = "io-uring" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "libc", ] @@ -1849,9 +1845,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -1859,20 +1855,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1909,22 +1905,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -1937,15 +1933,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1985,12 +1981,12 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.5", + "windows-link", ] [[package]] @@ -2007,9 +2003,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" @@ -2028,11 +2024,11 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -2043,9 +2039,9 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -2118,13 +2114,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -2206,9 +2202,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -2469,9 +2465,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2572,7 +2568,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit", ] [[package]] @@ -2599,21 +2595,20 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ - "bitflags 2.9.4", - "lazy_static", + "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -2655,7 +2650,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] @@ -2671,9 +2666,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2756,7 +2751,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -2793,7 +2788,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2822,7 +2817,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2951,7 +2946,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -2960,9 +2955,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "aws-lc-rs", "once_cell", @@ -2974,9 +2969,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2986,18 +2981,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -3062,7 +3057,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -3128,15 +3123,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.3" @@ -3308,9 +3294,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", @@ -3345,9 +3331,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-triple" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" +checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" [[package]] name = "tempfile" @@ -3356,7 +3342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3432,9 +3418,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" dependencies = [ "cc", "libc", @@ -3442,9 +3428,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -3461,9 +3447,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -3481,20 +3467,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", - "slab", "socket2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3520,9 +3503,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3531,18 +3514,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - [[package]] name = "toml" version = "0.9.8" @@ -3551,22 +3522,13 @@ checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.7.3" @@ -3576,20 +3538,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow", -] - [[package]] name = "toml_edit" version = "0.23.7" @@ -3597,7 +3545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime 0.7.3", + "toml_datetime", "toml_parser", "winnow", ] @@ -3611,12 +3559,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "toml_writer" version = "1.0.4" @@ -3685,7 +3627,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -3768,9 +3710,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +checksum = "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335" dependencies = [ "glob", "serde", @@ -3778,23 +3720,23 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 0.9.8", + "toml", ] [[package]] name = "typed-builder" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" +checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" +checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" dependencies = [ "proc-macro2", "quote", @@ -3827,9 +3769,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -3929,15 +3871,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -3953,14 +3886,14 @@ version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" dependencies = [ - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -3969,25 +3902,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -3998,9 +3917,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4008,31 +3927,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -4147,15 +4066,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -4320,9 +4230,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wyz" @@ -4335,11 +4245,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4347,9 +4256,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -4406,9 +4315,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4417,9 +4326,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4428,9 +4337,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 618c1ec3cfee..6e97a40cc1d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,15 @@ members = [ resolver = "2" [workspace.package] -version = "0.0.13" +# NOTE: when bumping to 0.1.0, this will be removed and each crate will have its +# version set independently. +version = "0.0.14" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" repository = "https://github.com/ava-labs/firewood" readme = "README.md" -rust-version = "1.89.0" +rust-version = "1.91.0" [profile.release] debug = true @@ -54,22 +56,22 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.13" } -firewood-macros = { path = "firewood-macros", version = "0.0.13" } -firewood-storage = { path = "storage", version = "0.0.13" } -firewood-ffi = { path = "ffi", version = "0.0.13" } -firewood-triehash = { path = "triehash", version = "0.0.13" } +firewood = { path = "firewood", version = "0.0.14" } +firewood-macros = { path = "firewood-macros", version = "0.0.14" } +firewood-storage = { path = "storage", version = "0.0.14" } +firewood-ffi = { path = "ffi", version = "0.0.14" } +firewood-triehash = { path = "triehash", version = "0.0.14" } # common dependencies aquamarine = "0.6.0" bytemuck = "1.24.0" bytemuck_derive = "1.10.2" -clap = { version = "4.5.49", features = ["derive"] } +clap = { version = "4.5.51", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" hex = "0.4.3" -integer-encoding = "4.0.2" +integer-encoding = "4.1.0" log = "0.4.28" metrics = "0.24.2" metrics-util = "0.20.0" @@ -83,7 +85,7 @@ thiserror = "2.0.17" # common dev dependencies criterion = "0.7.0" ethereum-types = "0.16.0" -hex-literal = "1.0.0" +hex-literal = "1.1.0" pprof = "0.15.0" rand = "0.9.2" tempfile = "3.23.0" diff --git a/RELEASE.md b/RELEASE.md index a956c60c6184..4ddd74d2a6fa 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -6,17 +6,66 @@ Cargo.toml file is currently manual. Firewood is made up of several sub-projects in a workspace. Each project is in its own crate and has an independent version. +## Workspace Dependencies + +Ensure the following tools are installed before beginning: + +```shell +cargo install --locked git-cliff cargo-edit +``` + ## Git Branch -Start off by crating a new branch: +Before making changes, create a new branch (if not already on one): ```console $ git fetch -$ git switch -c release/v0.0.14 origin/main -branch 'release/v0.0.14' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.14' +$ git switch -c release/v0.0.15 origin/main +branch 'release/v0.0.15' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.15' ``` +If already on a new branch, ensure `HEAD` is the same as the remote's `main`. +As the upstream repo changes, rebase the branch onto `main` so that the changes +from `git cliff` follow the repository history in the correct linear order. + +## Dependency upgrades + +### Rust MSRV + +Optionally, the minimum supported rust version can be bumped. See + for the latest version. Best +effort 2 releases behind the current latest stable [^1]. However, Rust evolves +fairly quickly and we occasionally want to take advantage of a new improvement +before the stable release matures. + +Updating the Rust MSRV requires edits in two places: + +- [clippy.toml] + - Update the root `msrv` setting to indicate the set MSRV. This configures + clippy to include newer lints that would otherwise be incorrect with a lower + MSRV; such as recommending newly stablized features that were unstable in + the older release. +- [Cargo.toml] + - Update `workspace.package.rust-version` which will propagate through the + other cargo packages that have `package.rust-version.workspace = true` set. + +[^1]: e.g., with 1.91 stable, 1.89 is the best effort MSRV + +### Cargo Dependencies + +```shell +cargo upgrade # installed through cargo-edit above +cargo upgrade --incompatible # may require fixes; if too many fixes are needed, split into a separate PR +cargo update --verbose # ensure lockfile is up-to-date with all changes +cargo test --workspace --all-targets +cargo test --workspace --all-targets -F ethhash +``` + +### Go Dependencies + +Go dependencies are updated on an as-needed basis, usually for security. + ## Package Version Next, update the workspace version and ensure all crates within the firewood @@ -26,7 +75,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.14" +version = "0.0.15" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -50,7 +99,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.14" } +firewood = { path = "firewood", version = "0.0.15" } ``` This allows packages within the workspace to inherit the dependency, @@ -73,15 +122,30 @@ firewood-storage.workspace = true Thefefore, after updating the `workspace.package.version` value, we must update the dependency versions to match. +Run `cargo update` after editing the `Cargo.toml` files to ensure the lockfile +is correct and reflects the new package versions. + ## Changelog To build the changelog, see git-cliff.org. Short version: ```sh -cargo install --locked git-cliff -git cliff --tag v0.0.14 -o CHANGELOG.md +git cliff --tag v0.0.15 -o CHANGELOG.md ``` +## Commit + +Commit the version bump and change log updates. Using the summary prefix: + +> chore(release): prepare for + +will cause `git-cliff` to omit the commit from the changelog. This is why +substantive changes when upgrading dependencies should be in their own commit. +If you do not use the prefix, there will be an egg-and-chicken problem as the +commit message generated by GitHub includes the pull request as a suffix. +Manually resolve this in the CHANGELOG if needed; otherwise, the CHANGELOG will +have irrelevant changes on future releases. + ## Review > ❗ Be sure to update the versions of all sub-projects before creating a new @@ -96,11 +160,11 @@ To trigger a release, push a tag to the main branch matching the new version, # be sure to switch back to the main branch before tagging git checkout main git pull --prune -git tag -s -a v0.0.14 -m 'Release v0.0.14' -git push origin v0.0.14 +git tag -s -a v0.0.15 -m 'Release v0.0.15' +git push origin v0.0.15 ``` -for `v0.0.14` for the merged version change. The CI will automatically publish a +for `v0.0.15` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index e836d188d5d8..962868bc5605 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -45,11 +45,11 @@ pretty-duration = "0.1.1" [dependencies.tokio] optional = true -version = "1.47.1" +version = "1.48.0" features = ["mio", "net", "parking_lot", "rt", "time"] [target.'cfg(unix)'.dependencies] -tikv-jemallocator = "0.6.0" +tikv-jemallocator = "0.6.1" [features] default = ["prometheus"] diff --git a/clippy.toml b/clippy.toml index bfe28d4a181d..af263e3308da 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,7 +1,7 @@ # See https://doc.rust-lang.org/clippy/lint_configuration.html # for full configuration options. -msrv = "1.89" +msrv = "1.91" doc-valid-idents = [ "MerkleDB", diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index 0dc1e6a6c00a..6dcbc24bb2ca 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -206,6 +206,7 @@ linters: # 4. Exclude any file suffixed with _test.go. - path: "(^tests/)|(^(.*/)*test_[^/]*\\.go$)|(.*test/.*)|(.*_test\\.go$)" linters: + - gosec - prealloc formatters: enable: diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 6e6db493ae61..9e891ebf6afe 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -34,7 +34,7 @@ env_logger = { workspace = true, optional = true } derive-where = "1.6.0" [target.'cfg(unix)'.dependencies] -tikv-jemallocator = "0.6.0" +tikv-jemallocator = "0.6.1" [dev-dependencies] # Workspace dependencies @@ -45,7 +45,7 @@ logger = ["dep:env_logger", "firewood/logger"] ethhash = ["firewood/ethhash"] [build-dependencies] -cbindgen = "0.29.0" +cbindgen = "0.29.2" [lints] workspace = true diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index d44d06d1be18..12d14e778c68 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,7 +34,7 @@ integer-encoding.workspace = true metrics.workspace = true thiserror.workspace = true # Regular dependencies -typed-builder = "0.22.0" +typed-builder = "0.23.0" rayon = "1.11.0" [features] @@ -59,7 +59,7 @@ rand.workspace = true tempfile.workspace = true test-case.workspace = true # Regular dependencies -ctor = "0.6.0" +ctor = "0.6.1" hash-db = "0.16.0" plain_hasher = "0.2.3" rlp = "0.6.1" diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 94bdc66a7a9f..03f651d282e6 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -32,8 +32,8 @@ hex.workspace = true log.workspace = true nonzero_ext.workspace = true # Regular dependencies -csv = "1.3.1" -indicatif = "0.18.0" +csv = "1.4.0" +indicatif = "0.18.2" askama = "0.14.0" num-format = "0.4.4" @@ -47,7 +47,7 @@ firewood-storage = { workspace = true, features = ["test_utils"] } rand.workspace = true # Regular dependencies anyhow = "1.0.100" -assert_cmd = "2.0.17" +assert_cmd = "2.1.1" predicates = "3.1.3" serial_test = "3.2.0" diff --git a/fwdctl/tests/cli.rs b/fwdctl/tests/cli.rs index f02f55a93d39..aa072c7b51c0 100644 --- a/fwdctl/tests/cli.rs +++ b/fwdctl/tests/cli.rs @@ -2,7 +2,6 @@ // See the file LICENSE.md for licensing terms. use anyhow::{Result, anyhow}; -use assert_cmd::Command; use predicates::prelude::*; use serial_test::serial; use std::fs::{self, remove_file}; @@ -11,6 +10,12 @@ use std::path::{Path, PathBuf}; const PRG: &str = "fwdctl"; const VERSION: &str = env!("CARGO_PKG_VERSION"); +macro_rules! cargo_bin_cmd { + () => { + ::assert_cmd::cargo::cargo_bin_cmd!("fwdctl") + }; +} + // Removes the firewood database on disk fn fwdctl_delete_db() -> Result<()> { if let Err(e) = remove_file(tmpdb::path()) { @@ -27,7 +32,7 @@ fn fwdctl_prints_version() -> Result<()> { let expected_version_output: String = format!("{PRG} {VERSION}"); // version is defined and succeeds with the desired output - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .args(["-V"]) .assert() .success() @@ -39,7 +44,7 @@ fn fwdctl_prints_version() -> Result<()> { #[test] #[serial] fn fwdctl_creates_database() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) @@ -53,7 +58,7 @@ fn fwdctl_creates_database() -> Result<()> { #[serial] fn fwdctl_insert_successful() -> Result<()> { // Create db - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) @@ -61,7 +66,7 @@ fn fwdctl_insert_successful() -> Result<()> { .success(); // Insert data - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .args(["year"]) .args(["2023"]) @@ -78,14 +83,14 @@ fn fwdctl_insert_successful() -> Result<()> { #[serial] fn fwdctl_get_successful() -> Result<()> { // Create db and insert data - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .args(["year"]) .args(["2023"]) @@ -96,7 +101,7 @@ fn fwdctl_get_successful() -> Result<()> { .stdout(predicate::str::contains("year")); // Get value back out - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("get") .args(["year"]) .arg("--db") @@ -111,14 +116,14 @@ fn fwdctl_get_successful() -> Result<()> { #[test] #[serial] fn fwdctl_delete_successful() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .args(["year"]) .args(["2023"]) @@ -129,7 +134,7 @@ fn fwdctl_delete_successful() -> Result<()> { .stdout(predicate::str::contains("year")); // Delete key -- prints raw data of deleted value - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("delete") .args(["year"]) .arg("--db") @@ -144,14 +149,14 @@ fn fwdctl_delete_successful() -> Result<()> { #[test] #[serial] fn fwdctl_root_hash() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .args(["year"]) .args(["2023"]) @@ -162,7 +167,7 @@ fn fwdctl_root_hash() -> Result<()> { .stdout(predicate::str::contains("year")); // Get root - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("root") .arg("--db") .arg(tmpdb::path()) @@ -176,14 +181,14 @@ fn fwdctl_root_hash() -> Result<()> { #[test] #[serial] fn fwdctl_dump() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .args(["year"]) .args(["2023"]) @@ -194,7 +199,7 @@ fn fwdctl_dump() -> Result<()> { .stdout(predicate::str::contains("year")); // Get root - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -208,14 +213,14 @@ fn fwdctl_dump() -> Result<()> { #[test] #[serial] fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -225,7 +230,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { .success() .stdout(predicate::str::contains("a")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -235,7 +240,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { .success() .stdout(predicate::str::contains("b")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -246,7 +251,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { .stdout(predicate::str::contains("c")); // Test stop in the middle - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -259,7 +264,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { )); // Test stop in the end - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -272,7 +277,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { )); // Test start in the middle - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -283,7 +288,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { .stdout(predicate::str::starts_with("\'b")); // Test start and stop - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -299,7 +304,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { )); // Test start and stop - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -320,14 +325,14 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> { #[test] #[serial] fn fwdctl_dump_with_csv_and_json() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -337,7 +342,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { .success() .stdout(predicate::str::contains("a")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -347,7 +352,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { .success() .stdout(predicate::str::contains("b")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -358,7 +363,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { .stdout(predicate::str::contains("c")); // Test output csv - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -373,7 +378,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { fs::remove_file("dump.csv").expect("Should remove dump.csv file"); // Test output json - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -396,14 +401,14 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> { #[test] #[serial] fn fwdctl_dump_with_file_name() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -414,7 +419,7 @@ fn fwdctl_dump_with_file_name() -> Result<()> { .stdout(predicate::str::contains("a")); // Test without output format - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -425,7 +430,7 @@ fn fwdctl_dump_with_file_name() -> Result<()> { .stderr(predicate::str::contains("--output-format")); // Test output csv - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -442,7 +447,7 @@ fn fwdctl_dump_with_file_name() -> Result<()> { fs::remove_file("test.csv").expect("Should remove test.csv file"); // Test output json - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -464,14 +469,14 @@ fn fwdctl_dump_with_file_name() -> Result<()> { #[test] #[serial] fn fwdctl_dump_with_hex() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -481,7 +486,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { .success() .stdout(predicate::str::contains("a")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -491,7 +496,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { .success() .stdout(predicate::str::contains("b")); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -502,7 +507,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { .stdout(predicate::str::contains("c")); // Test without output format - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -516,7 +521,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { .stderr(predicate::str::contains("--start-key-hex")); // Test start with hex value - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -527,7 +532,7 @@ fn fwdctl_dump_with_hex() -> Result<()> { .stdout(predicate::str::starts_with("\'b")); // Test stop with hex value - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("dump") .arg("--db") .arg(tmpdb::path()) @@ -546,14 +551,14 @@ fn fwdctl_dump_with_hex() -> Result<()> { #[test] #[serial] fn fwdctl_check_empty_db() -> Result<()> { - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) .assert() .success(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("check") .arg("--db") .arg(tmpdb::path()) @@ -570,7 +575,7 @@ fn fwdctl_check_db_with_data() -> Result<()> { let rng = firewood_storage::SeededRng::from_env_or_random(); let mut sample_iter = rng.sample_iter(Alphanumeric).map(char::from); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("create") .arg("--db") .arg(tmpdb::path()) @@ -581,7 +586,7 @@ fn fwdctl_check_db_with_data() -> Result<()> { for _ in 0..4 { let key = sample_iter.by_ref().take(64).collect::(); let value = sample_iter.by_ref().take(10).collect::(); - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("insert") .arg("--db") .arg(tmpdb::path()) @@ -591,7 +596,7 @@ fn fwdctl_check_db_with_data() -> Result<()> { .success(); } - Command::cargo_bin(PRG)? + cargo_bin_cmd!() .arg("check") .arg("--db") .arg(tmpdb::path()) diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 3519e033eefa..d478fe920108 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -34,16 +34,16 @@ thiserror.workspace = true # Regular dependencies arc-swap = "1.7.1" bitfield = "0.19.3" -bitflags = "2.9.4" +bitflags = "2.10.0" derive-where = "1.6.0" enum-as-inner = "0.6.1" -indicatif = "0.18.0" -lru = "0.16.1" +indicatif = "0.18.2" +lru = "0.16.2" semver = "1.0.27" triomphe = "0.1.15" # Optional dependencies bytes = { version = "1.10.1", optional = true } -io-uring = { version = "0.7.10", optional = true } +io-uring = { version = "0.7.11", optional = true } log = { version = "0.4.28", optional = true } rlp = { version = "0.6.1", optional = true } sha3 = { version = "0.10.8", optional = true } diff --git a/storage/src/node/children.rs b/storage/src/node/children.rs index 7c0c96d55ac1..5e3f27dc00db 100644 --- a/storage/src/node/children.rs +++ b/storage/src/node/children.rs @@ -46,14 +46,14 @@ impl Children { /// Borrows each element and returns a [`Children`] wrapping the references. #[must_use] - pub fn each_ref(&self) -> Children<&T> { + pub const fn each_ref(&self) -> Children<&T> { Children(self.0.each_ref()) } /// Borrows each element mutably and returns a [`Children`] wrapping the /// mutable references. #[must_use] - pub fn each_mut(&mut self) -> Children<&mut T> { + pub const fn each_mut(&mut self) -> Children<&mut T> { Children(self.0.each_mut()) } From 1af640f665eca6d87bc4d92049b65557d88fe162 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 10 Nov 2025 09:04:42 -0800 Subject: [PATCH 1023/1053] feat: merge key-value range into trie (#1427) This change adds a new mechanism for merging key-value pairs with a merkle trie, refactors iterator and batch operation traits, and updates error handling to allow for batch iterators that may produce errors. ### Merkle Trie Merge Functionality * Added the `merge_key_value_range` method to the `Merkle` struct, which merges a sorted sequence of key-value pairs with the base trie and yields a sequence of `BatchOp`s describing the changes. This is implemented via the new `MergeKeyValueIter` iterator, supporting efficient diffing and batch operation generation. ### Iterator and Batch Trait Refactoring * Refactored batch operation traits: replaced `KeyValuePairIter` and related traits with `TryIntoBatch`, `BatchIter`, and `IntoBatchIter`, providing a more flexible and type-safe interface for batch operations. ### Error Handling and Iterator Return Types * Changed iterator return types throughout the codebase to use `FileIoError` instead of the generic API `Error`, ensuring consistent error propagation for IO-related failures. Iterating over a trie can fail, but only because of IO-reasons (including data inconsistencies). This was also the driving change for changing the batch api to accept errors. Updated trait bounds and type aliases accordingly. * Implemented `From` for the API `Error` type to allow for seamless error conversion in infallible cases. --- ffi/src/iterator.rs | 2 +- ffi/src/value/kvp.rs | 30 +++- firewood/src/db.rs | 113 ++++++++----- firewood/src/db/tests.rs | 8 + firewood/src/db/tests/merge.rs | 252 +++++++++++++++++++++++++++++ firewood/src/iter.rs | 4 +- firewood/src/merkle/merge.rs | 278 ++++++++++++++++++++++++++++++++ firewood/src/merkle/mod.rs | 36 ++++- firewood/src/merkle/parallel.rs | 23 +-- firewood/src/range_proof.rs | 7 +- firewood/src/v2/api.rs | 23 +-- firewood/src/v2/batch_op.rs | 165 +++++++++++++++---- fwdctl/src/dump.rs | 5 +- storage/src/linear/mod.rs | 6 + 14 files changed, 842 insertions(+), 110 deletions(-) create mode 100644 firewood/src/db/tests.rs create mode 100644 firewood/src/db/tests/merge.rs create mode 100644 firewood/src/merkle/merge.rs diff --git a/ffi/src/iterator.rs b/ffi/src/iterator.rs index 2389cb234f8e..420749a50e9b 100644 --- a/ffi/src/iterator.rs +++ b/ffi/src/iterator.rs @@ -29,7 +29,7 @@ impl Iterator for IteratorHandle<'_> { // iterator exhausted; drop it so the NodeStore can be released self.0 = None; } - out + out.map(|res| res.map_err(api::Error::from)) } } diff --git a/ffi/src/value/kvp.rs b/ffi/src/value/kvp.rs index d6d1b340532e..6dda75b866e4 100644 --- a/ffi/src/value/kvp.rs +++ b/ffi/src/value/kvp.rs @@ -38,32 +38,48 @@ impl fmt::Display for KeyValuePair<'_> { } } -impl<'a> api::KeyValuePair for KeyValuePair<'a> { +impl<'a> api::TryIntoBatch for KeyValuePair<'a> { type Key = BorrowedBytes<'a>; type Value = BorrowedBytes<'a>; + type Error = std::convert::Infallible; #[inline] - fn into_batch(self) -> api::BatchOp { + fn try_into_batch(self) -> Result, Self::Error> { // Check if the value pointer is null (nil slice in Go) // vs non-null but empty (empty slice []byte{} in Go) - if self.value.is_null() { + Ok(if self.value.is_null() { api::BatchOp::DeleteRange { prefix: self.key } } else { api::BatchOp::Put { key: self.key, value: self.value, } - } + }) + } +} + +impl api::KeyValuePair for KeyValuePair<'_> { + #[inline] + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error> { + Ok((self.key, self.value)) } } -impl<'a> api::KeyValuePair for &KeyValuePair<'a> { +impl<'a> api::TryIntoBatch for &KeyValuePair<'a> { type Key = BorrowedBytes<'a>; type Value = BorrowedBytes<'a>; + type Error = std::convert::Infallible; + + #[inline] + fn try_into_batch(self) -> Result, Self::Error> { + (*self).try_into_batch() + } +} +impl api::KeyValuePair for &KeyValuePair<'_> { #[inline] - fn into_batch(self) -> api::BatchOp { - (*self).into_batch() + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error> { + (*self).try_into_tuple() } } diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 3703b07cc21e..1c8729ba8040 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -6,13 +6,16 @@ reason = "Found 12 occurrences after enabling the lint." )] +#[cfg(test)] +mod tests; + use crate::iter::MerkleKeyValueIter; use crate::merkle::{Merkle, Value}; use crate::root_store::{NoOpStore, RootStore}; pub use crate::v2::api::BatchOp; use crate::v2::api::{ - self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePairIter, - OptionalHashKeyExt, + self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, IntoBatchIter, KeyType, + KeyValuePair, OptionalHashKeyExt, }; use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; @@ -153,10 +156,7 @@ impl api::Db for Db { Ok(self.manager.all_hashes()) } - fn propose( - &self, - batch: impl IntoIterator, - ) -> Result, api::Error> { + fn propose(&self, batch: impl IntoBatchIter) -> Result, api::Error> { self.propose_with_parent(batch, &self.manager.current_revision()) } } @@ -224,7 +224,7 @@ impl Db { #[fastrace::trace(name = "propose")] fn propose_with_parent( &self, - batch: impl IntoIterator, + batch: impl IntoBatchIter, parent: &NodeStore, ) -> Result, api::Error> { // If use_parallel is BatchSize, then perform parallel proposal creation if the batch @@ -243,8 +243,8 @@ impl Db { let proposal = NodeStore::new(parent)?; let mut merkle = Merkle::from(proposal); let span = fastrace::Span::enter_with_local_parent("merkleops"); - for op in batch.into_iter().map_into_batch() { - match op { + for res in batch.into_batch_iter::() { + match res? { BatchOp::Put { key, value } => { merkle.insert(key.as_ref(), value.as_ref().into())?; } @@ -271,6 +271,54 @@ impl Db { db: self, }) } + + /// Merge a range of key-values into a new proposal on top of the current + /// root revision. + /// + /// All items within the range `(first_key..=last_key)` will be replaced with + /// the provided key-values from the iterator. I.e., any existing keys within + /// the range that are not present in the provided key-values will be deleted, + /// any duplicate keys will be overwritten, and any new keys will be inserted. + /// + /// Invariant: `key_values` must be sorted by key in ascending order; however, + /// because debug assertions are disabled, this is not checked. + pub fn merge_key_value_range( + &self, + first_key: Option, + last_key: Option, + key_values: impl IntoIterator, + ) -> Result, api::Error> { + self.merge_key_value_range_with_parent( + first_key, + last_key, + key_values, + &self.manager.current_revision(), + ) + } + + /// Merge a range of key-values into a new proposal on top of a specified parent. + /// + /// All items within the range `(first_key..=last_key)` will be replaced with + /// the provided key-values from the iterator. I.e., any existing keys within + /// the range that are not present in the provided key-values will be deleted, + /// any duplicate keys will be overwritten, and any new keys will be inserted. + /// + /// Invariant: `key_values` must be sorted by key in ascending order; however, + /// because debug assertions are disabled, this is not checked. + pub fn merge_key_value_range_with_parent( + &self, + first_key: Option, + last_key: Option, + key_values: impl IntoIterator, + parent: &NodeStore, + ) -> Result, api::Error> + where + NodeStore: TrieReader, + { + let merkle = Merkle::from(parent); + let merge_ops = merkle.merge_key_value_range(first_key, last_key, key_values); + self.propose_with_parent(merge_ops, merkle.nodestore()) + } } #[derive(Debug)] @@ -315,11 +363,7 @@ impl api::DbView for Proposal<'_> { impl<'db> api::Proposal for Proposal<'db> { type Proposal = Proposal<'db>; - #[fastrace::trace(short_name = true)] - fn propose( - &self, - batch: impl IntoIterator, - ) -> Result { + fn propose(&self, batch: impl IntoBatchIter) -> Result { self.create_proposal(batch) } @@ -330,10 +374,7 @@ impl<'db> api::Proposal for Proposal<'db> { impl Proposal<'_> { #[crate::metrics("firewood.proposal.create", "database proposal creation")] - fn create_proposal( - &self, - batch: impl IntoIterator, - ) -> Result { + fn create_proposal(&self, batch: impl IntoBatchIter) -> Result { self.db.propose_with_parent(batch, &self.nodestore) } } @@ -354,7 +395,7 @@ mod test { use crate::db::{Db, Proposal, UseParallel}; use crate::root_store::{MockStore, RootStore}; - use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _}; + use crate::v2::api::{Db as _, DbView, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -602,7 +643,7 @@ mod test { fn insert_commit(db: &TestDb, kv: u8) { let keys: Vec<[u8; 1]> = vec![[kv; 1]]; let vals: Vec> = vec![Box::new([kv; 1])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); } @@ -668,7 +709,7 @@ mod test { let keys: Vec<[u8; 0]> = Vec::new(); let vals: Vec> = Vec::new(); - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); @@ -679,7 +720,7 @@ mod test { // Instead, set value to [0] let vals: Vec> = vec![Box::new([0; 1])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); @@ -697,7 +738,7 @@ mod test { // Create a proposal that deletes the previous entry let vals: Vec> = vec![Box::new([0; 0])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); @@ -716,7 +757,7 @@ mod test { }) .unzip(); - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, v) in kviter { @@ -735,7 +776,7 @@ mod test { }) .unzip(); - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); let kviter = keys.iter().zip(vals.iter()); for (k, _v) in kviter { @@ -746,7 +787,7 @@ mod test { // Create a proposal that deletes using empty prefix let keys: Vec<[u8; 0]> = vec![[0; 0]]; let vals: Vec> = vec![Box::new([0; 0])]; - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); proposal.commit().unwrap(); @@ -763,7 +804,7 @@ mod test { // Looping twice to test that we are reusing the thread pool. for _ in 0..2 { - let kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let kviter = keys.iter().zip(vals.iter()); let proposal = db.propose(kviter).unwrap(); // iterate over the keys and values again, checking that the values are in the correct proposal @@ -798,7 +839,7 @@ mod test { .unzip(); // create two batches, one with the first half of keys and values, and one with the last half keys and values - let mut kviter = keys.iter().zip(vals.iter()).map_into_batch(); + let mut kviter = keys.iter().zip(vals.iter()); // create two proposals, second one has a base of the first one let proposal1 = db.propose(kviter.by_ref().take(N / 2)).unwrap(); @@ -1090,7 +1131,7 @@ mod test { } // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear - struct TestDb { + pub(super) struct TestDb { db: Db, tmpdir: tempfile::TempDir, } @@ -1107,11 +1148,11 @@ mod test { } impl TestDb { - fn new() -> Self { + pub fn new() -> Self { TestDb::new_with_config(DbConfig::builder().build()) } - fn new_with_config(dbconfig: DbConfig) -> Self { + pub fn new_with_config(dbconfig: DbConfig) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() @@ -1120,7 +1161,7 @@ mod test { TestDb { db, tmpdir } } - fn with_mockstore(mock_store: MockStore) -> Self { + pub fn with_mockstore(mock_store: MockStore) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() @@ -1130,11 +1171,11 @@ mod test { TestDb { db, tmpdir } } - fn reopen(self) -> Self { + pub fn reopen(self) -> Self { self.reopen_with_config(DbConfig::builder().truncate(false).build()) } - fn reopen_with_config(self, dbconfig: DbConfig) -> Self { + pub fn reopen_with_config(self, dbconfig: DbConfig) -> Self { let path = self.path(); let TestDb { db, tmpdir } = self; @@ -1144,7 +1185,7 @@ mod test { TestDb { db, tmpdir } } - fn replace(self) -> Self { + pub fn replace(self) -> Self { let path = self.path(); let TestDb { db, tmpdir } = self; @@ -1155,7 +1196,7 @@ mod test { TestDb { db, tmpdir } } - fn path(&self) -> PathBuf { + pub fn path(&self) -> PathBuf { [self.tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect() diff --git a/firewood/src/db/tests.rs b/firewood/src/db/tests.rs new file mode 100644 index 000000000000..db77235fa532 --- /dev/null +++ b/firewood/src/db/tests.rs @@ -0,0 +1,8 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +#![expect(clippy::unwrap_used)] + +mod merge; + +use super::test::TestDb; diff --git a/firewood/src/db/tests/merge.rs b/firewood/src/db/tests/merge.rs new file mode 100644 index 000000000000..cdb701d55e07 --- /dev/null +++ b/firewood/src/db/tests/merge.rs @@ -0,0 +1,252 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use crate::{ + db::BatchOp, + v2::api::{Db, DbView, Proposal}, +}; + +use super::*; +use test_case::test_case; + +#[test_case( + &[], + None, + None, + &[], + &[]; + "empty everything - no initial data, no merge data" + )] +#[test_case( + &[(b"key1", b"val1"), (b"key2", b"val2"), (b"key3", b"val3")], + Some(b"key1".as_slice()), + Some(b"key3".as_slice()), + &[(b"key1", b"new1"), (b"key2", b"new2"), (b"key3", b"new3")], + &[(b"key1", Some(b"new1")), (b"key2", Some(b"new2")), (b"key3", Some(b"new3"))]; + "basic happy path - update all values in range" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4")], + Some(b"b".as_slice()), + Some(b"c".as_slice()), + &[], + &[(b"a", Some(b"1")), (b"b", None), (b"c", None), (b"d", Some(b"4"))]; + "empty input iterator - deletes all keys in range" + )] +#[test_case( + &[], + None, + None, + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + &[(b"a", Some(b"1")), (b"b", Some(b"2")), (b"c", Some(b"3"))]; + "empty base trie - insert all keys" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + None, + None, + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + &[(b"a", Some(b"1")), (b"b", Some(b"2")), (b"c", Some(b"3"))]; + "no changes - identical key-values" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + Some(b"a".as_slice()), + Some(b"c".as_slice()), + &[(b"x", b"10"), (b"y", b"20"), (b"z", b"30")], + &[(b"a", None), (b"b", None), (b"c", None), (b"x", Some(b"10")), (b"y", Some(b"20")), (b"z", Some(b"30"))]; + "full replacement - completely different keys" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4")], + Some(b"b".as_slice()), + Some(b"c".as_slice()), + &[(b"b", b"new2"), (b"c2", b"inserted")], + &[(b"a", Some(b"1")), (b"b", Some(b"new2")), (b"c", None), (b"c2", Some(b"inserted")), (b"d", Some(b"4"))]; + "partial overlap - mix of updates, inserts, deletes" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + Some(b"b".as_slice()), + Some(b"b".as_slice()), + &[(b"b", b"updated")], + &[(b"a", Some(b"1")), (b"b", Some(b"updated")), (b"c", Some(b"3"))]; + "single key range - first_key equals last_key" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + None, + None, + &[(b"x", b"10"), (b"y", b"20")], + &[(b"a", None), (b"b", None), (b"c", None), (b"x", Some(b"10")), (b"y", Some(b"20"))]; + "unbounded range - replace entire trie" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4")], + Some(b"b".as_slice()), + None, + &[(b"b", b"new2"), (b"c", b"new3")], + &[(b"a", Some(b"1")), (b"b", Some(b"new2")), (b"c", Some(b"new3")), (b"d", None)]; + "left-bounded only - from key b to end" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4")], + None, + Some(b"c".as_slice()), + &[(b"a", b"new1"), (b"b", b"new2")], + &[(b"a", Some(b"new1")), (b"b", Some(b"new2")), (b"c", None), (b"d", Some(b"4"))]; + "right-bounded only - from start to key c" + )] +#[test_case( + &[(b"a", b"1"), (b"e", b"5"), (b"f", b"6")], + Some(b"b".as_slice()), + Some(b"d".as_slice()), + &[(b"b", b"2"), (b"c", b"3"), (b"d", b"4")], + &[(b"a", Some(b"1")), (b"b", Some(b"2")), (b"c", Some(b"3")), (b"d", Some(b"4")), (b"e", Some(b"5")), (b"f", Some(b"6"))]; + "gap range - merge into range with no existing keys" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")], + Some(b"b".as_slice()), + Some(b"c".as_slice()), + &[(b"b", b"updated"), (b"c", b"3")], + &[(b"a", Some(b"1")), (b"b", Some(b"updated")), (b"c", Some(b"3"))]; + "value updates only - same keys, one different value" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4"), (b"e", b"5")], + Some(b"b".as_slice()), + Some(b"d".as_slice()), + &[], + &[(b"a", Some(b"1")), (b"b", None), (b"c", None), (b"d", None), (b"e", Some(b"5"))]; + "delete middle range - empty iterator deletes b, c, d" + )] +#[test_case( + &[(b"app", b"1"), (b"apple", b"2"), (b"application", b"3")], + Some(b"app".as_slice()), + Some(b"application".as_slice()), + &[(b"app", b"new1"), (b"apply", b"4")], + &[(b"app", Some(b"new1")), (b"apple", None), (b"application", None), (b"apply", Some(b"4"))]; + "common prefix keys - test branch navigation" + )] +#[test_case( + &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3"), (b"d", b"4"), (b"e", b"5")], + Some(b"b".as_slice()), + Some(b"d".as_slice()), + &[(b"b2", b"new"), (b"c", b"updated")], + &[(b"a", Some(b"1")), (b"b", None), (b"b2", Some(b"new")), (b"c", Some(b"updated")), (b"d", None), (b"e", Some(b"5"))]; + "boundary precision - a and e should remain unchanged" + )] +fn test_merge_key_value_range( + initial_kvs: &[(&[u8], &[u8])], + first_key: Option<&[u8]>, + last_key: Option<&[u8]>, + merge_kvs: &[(&[u8], &[u8])], + expected_kvs: &[(&[u8], Option<&[u8]>)], +) { + let db = TestDb::new(); + + if !initial_kvs.is_empty() { + db.propose(initial_kvs).unwrap().commit().unwrap(); + } + + let proposal = db + .merge_key_value_range(first_key, last_key, merge_kvs) + .unwrap(); + + for (key, expected_value) in expected_kvs { + let actual_value = proposal.val(key).unwrap(); + match expected_value { + Some(v) => { + assert_eq!( + actual_value.as_deref(), + Some(*v), + "Key {key:?} should have value {v:?}, but got {actual_value:?}" + ); + } + None => { + assert!( + actual_value.is_none(), + "Key {key:?} should be deleted, but got {actual_value:?}" + ); + } + } + } + + let merge_root_hash = proposal.root_hash().unwrap(); + + // Create a fresh database with the same initial state + let db2 = TestDb::new(); + if !initial_kvs.is_empty() { + db2.propose(initial_kvs).unwrap().commit().unwrap(); + } + + // Apply the expected operations manually + let mut batch = Vec::new(); + if let Some(root) = db2.root_hash().unwrap() { + batch.extend( + db2.revision(root) + .unwrap() + .iter_option(first_key) + .unwrap() + .take_while(|res| { + if let Some(last_key) = last_key { + match res { + Ok((key, _)) => **key <= *last_key, + Err(_) => true, + } + } else { + true + } + }) + .map(|res| res.map(|(key, _)| BatchOp::Delete { key })), + ); + } + batch.extend(merge_kvs.iter().map(|(key, value)| { + Ok(BatchOp::Put { + key: (*key).into(), + value, + }) + })); + + let manual_proposal = db2.propose(batch).unwrap(); + let manual_root_hash = manual_proposal.root_hash().unwrap(); + + assert_eq!( + merge_root_hash, manual_root_hash, + "Root hash from merge should match root hash from manual operations" + ); + + // Commit the proposal and verify persistence + proposal.commit().unwrap(); + + // Verify the committed state + if let Some(root) = db.root_hash().unwrap() { + let committed = db.revision(root).unwrap(); + for (key, expected_value) in expected_kvs { + let actual_value = committed.val(key).unwrap(); + match expected_value { + Some(v) => { + assert_eq!( + actual_value.as_deref(), + Some(*v), + "After commit, key {key:?} should have value {v:?}, but got {actual_value:?}" + ); + } + None => { + assert!( + actual_value.is_none(), + "After commit, key {key:?} should be deleted, but got {actual_value:?}" + ); + } + } + } + } else { + for (key, expected_value) in expected_kvs { + assert!( + expected_value.is_none(), + "Database is empty, but expected key {key:?} to have value {expected_value:?}" + ); + } + } +} diff --git a/firewood/src/iter.rs b/firewood/src/iter.rs index 72e9d61fdd7c..8314afee66a3 100644 --- a/firewood/src/iter.rs +++ b/firewood/src/iter.rs @@ -5,7 +5,6 @@ mod try_extend; pub(crate) use self::try_extend::TryExtend; use crate::merkle::{Key, Value}; -use crate::v2::api; use firewood_storage::{ BranchNode, Child, FileIoError, NibblesIterator, Node, PathBuf, PathComponent, PathIterItem, @@ -298,7 +297,7 @@ impl<'a, T: TrieReader> MerkleKeyValueIter<'a, T> { } impl Iterator for MerkleKeyValueIter<'_, T> { - type Item = Result<(Key, Value), api::Error>; + type Item = Result<(Key, Value), FileIoError>; fn next(&mut self) -> Option { self.iter.find_map(|result| { @@ -316,7 +315,6 @@ impl Iterator for MerkleKeyValueIter<'_, T> { Node::Leaf(leaf) => Some((key, leaf.value.clone())), } }) - .map_err(Into::into) .transpose() }) } diff --git a/firewood/src/merkle/merge.rs b/firewood/src/merkle/merge.rs new file mode 100644 index 000000000000..7de746266599 --- /dev/null +++ b/firewood/src/merkle/merge.rs @@ -0,0 +1,278 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use firewood_storage::{FileIoError, TrieReader}; + +use crate::{ + db::BatchOp, + iter::MerkleKeyValueIter, + merkle::Key, + v2::api::{BatchIter, KeyType, KeyValuePair}, +}; + +/// Serializes a sequence of key-value pairs merged with the base merkle trie +/// as a sequence of [`BatchOp`]s. +/// +/// The key-value range is considered total, meaning keys within the specified +/// bounds that are present within the base trie but not in the key-value iterator +/// will be yielded as [`BatchOp::Delete`] or [`BatchOp::DeleteRange`] operations. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub(super) struct MergeKeyValueIter<'a, T, I, K> +where + T: TrieReader, + I: Iterator, + K: KeyType, +{ + trie: ReturnableIterator, K>>, + kvp: ReturnableIterator>, +} + +impl<'a, T, I, K> MergeKeyValueIter<'a, T, I, K> +where + T: TrieReader, + I: Iterator, + K: KeyType, +{ + pub fn new( + merkle: &'a crate::merkle::Merkle, + first_key: Option, + last_key: Option, + kvp_iter: impl IntoIterator, + ) -> Self { + let base_iter = match first_key { + Some(k) => merkle.key_value_iter_from_key(k), + None => merkle.key_value_iter(), + }; + + Self { + trie: ReturnableIterator::new(KeyRangeIter::new(base_iter, last_key)), + kvp: ReturnableIterator::new(KeyRangeIter::new(kvp_iter.into_iter(), None)), + } + } +} + +impl Iterator for MergeKeyValueIter<'_, T, I, K> +where + T: TrieReader, + I: Iterator, + K: KeyType, +{ + type Item = Result< + BatchOp::Key>, ::Value>, + FileIoError, + >; + + fn next(&mut self) -> Option { + loop { + break match (self.trie.next(), self.kvp.next()) { + // we've exhausted both iterators + (None, None) => None, + + (Some(Err(err)), kvp) => { + if let Some(kvp) = kvp { + self.kvp.set_next(kvp); + } + + Some(Err(err)) + } + (trie, Some(Err(err))) => { + if let Some(trie) = trie { + self.trie.set_next(trie); + } + + Some(Err(err.into())) + } + + // only the trie iterator has remaining items. + (Some(Ok((key, _))), None) => Some(Ok(BatchOp::Delete { + key: EitherKey::Left(key), + })), + + // only kvp iterator has remaining items. + (None, Some(Ok((key, value)))) => Some(Ok(BatchOp::Put { + key: EitherKey::Right(key), + value, + })), + + (Some(Ok((base_key, node_value))), Some(Ok((kvp_key, kvp_value)))) => { + match <[u8] as Ord>::cmp(&base_key, kvp_key.as_ref()) { + std::cmp::Ordering::Less => { + // retain the kvp iterator's current item. + self.kvp.set_next(Ok((kvp_key, kvp_value))); + + // trie key is less than next kvp key, so it must be deleted. + Some(Ok(BatchOp::Delete { + key: EitherKey::Left(base_key), + })) + } + std::cmp::Ordering::Equal => { + // retain neither iterator's current item. + + if *node_value == *kvp_value.as_ref() { + // skip since value is unchanged + continue; + } + + // values differ, so we need to insert the new value + Some(Ok(BatchOp::Put { + key: EitherKey::Right(kvp_key), + value: kvp_value, + })) + } + std::cmp::Ordering::Greater => { + // retain the trie iterator's current item. + self.trie.set_next(Ok((base_key, node_value))); + // trie key is greater than next kvp key, so we need to insert it. + Some(Ok(BatchOp::Put { + key: EitherKey::Right(kvp_key), + value: kvp_value, + })) + } + } + } + }; + } + } +} + +/// Similar to a peekable iterator. Instead of peeking at the next item, it allows +/// you to put it back to be returned on the next call to `next()`. +struct ReturnableIterator { + iter: I, + next: Option, +} + +impl ReturnableIterator { + const fn new(iter: I) -> Self { + Self { iter, next: None } + } + + const fn set_next(&mut self, head: I::Item) -> Option { + self.next.replace(head) + } +} + +impl Iterator for ReturnableIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.next.take().or_else(|| self.iter.next()) + } + + fn size_hint(&self) -> (usize, Option) { + let (lower, upper) = self.iter.size_hint(); + let head_count = usize::from(self.next.is_some()); + ( + lower.saturating_add(head_count), + upper.and_then(|u| u.checked_add(head_count)), + ) + } +} + +enum KeyRangeIter { + Unfiltered { iter: I }, + Filtered { iter: I, last_key: K }, + Exhausted, +} + +impl KeyRangeIter { + fn new(iter: I, last_key: Option) -> Self { + match last_key { + Some(k) => KeyRangeIter::Filtered { iter, last_key: k }, + None => KeyRangeIter::Unfiltered { iter }, + } + } +} + +impl, T: KeyValuePair, K: KeyType> Iterator for KeyRangeIter { + type Item = Result<(T::Key, T::Value), T::Error>; + + fn next(&mut self) -> Option { + match self { + KeyRangeIter::Unfiltered { iter } => iter.next().map(T::try_into_tuple), + KeyRangeIter::Filtered { iter, last_key } => match iter.next().map(T::try_into_tuple) { + Some(Ok((key, value))) if key.as_ref() <= last_key.as_ref() => { + Some(Ok((key, value))) + } + Some(Err(e)) => Some(Err(e)), + _ => { + *self = KeyRangeIter::Exhausted; + None + } + }, + KeyRangeIter::Exhausted => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + KeyRangeIter::Unfiltered { iter } => iter.size_hint(), + KeyRangeIter::Filtered { iter, .. } => { + let (_, upper) = iter.size_hint(); + (0, upper) + } + KeyRangeIter::Exhausted => (0, Some(0)), + } + } +} + +#[derive(Debug)] +pub(super) enum EitherKey { + Left(L), + Right(R), +} + +impl EitherKey { + fn as_key(&self) -> &[u8] { + match self { + EitherKey::Left(key) => key.as_ref(), + EitherKey::Right(key) => key.as_ref(), + } + } +} + +impl AsRef<[u8]> for EitherKey { + fn as_ref(&self) -> &[u8] { + self.as_key() + } +} + +impl std::borrow::Borrow<[u8]> for EitherKey { + fn borrow(&self) -> &[u8] { + self.as_key() + } +} + +impl std::ops::Deref for EitherKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_key() + } +} + +impl PartialEq for EitherKey { + fn eq(&self, other: &V) -> bool { + self.as_key() == other.as_ref() + } +} + +impl Eq for EitherKey {} + +impl PartialOrd for EitherKey { + fn partial_cmp(&self, other: &V) -> Option { + Some(self.as_key().cmp(other.as_ref())) + } +} + +impl Ord for EitherKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_key().cmp(other.as_key()) + } +} + +impl std::hash::Hash for EitherKey { + fn hash(&self, state: &mut H) { + self.as_key().hash(state); + } +} diff --git a/firewood/src/merkle/mod.rs b/firewood/src/merkle/mod.rs index 366cb0390a03..41c8aaeed958 100644 --- a/firewood/src/merkle/mod.rs +++ b/firewood/src/merkle/mod.rs @@ -4,13 +4,16 @@ #[cfg(test)] pub(crate) mod tests; +mod merge; /// Parallel merkle pub mod parallel; use crate::iter::{MerkleKeyValueIter, PathIterator, TryExtend}; use crate::proof::{Proof, ProofCollection, ProofError, ProofNode}; use crate::range_proof::RangeProof; -use crate::v2::api::{self, FrozenProof, FrozenRangeProof, KeyType, ValueType}; +use crate::v2::api::{ + self, BatchIter, FrozenProof, FrozenRangeProof, KeyType, KeyValuePair, ValueType, +}; use firewood_storage::{ BranchNode, Child, Children, FileIoError, HashType, HashedNodeReader, ImmutableProposal, IntoHashType, LeafNode, MaybePersistedNode, MutableProposal, NibblesIterator, Node, NodeStore, @@ -132,7 +135,6 @@ impl Merkle { self.nodestore.root_node() } - #[cfg(test)] pub(crate) const fn nodestore(&self) -> &T { &self.nodestore } @@ -266,6 +268,36 @@ impl Merkle { todo!() } + /// Merges a sequence of key-value pairs with the base merkle trie, yielding + /// a sequence of [`BatchOp`]s that describe the changes. + /// + /// The key-value range is considered total, meaning keys within the inclusive + /// bounds that are present within the base trie but not in the key-value iterator + /// will be yielded as [`BatchOp::Delete`] or [`BatchOp::DeleteRange`] operations. + /// + /// # Invariant + /// + /// The key-value pairs provided by the `key_values` iterator must be sorted in + /// ascending order, not contain duplicates, and must lie within the specified + /// `first_key` and `last_key` bounds (if provided). Behavior is unspecified if + /// this invariant is violated and no verification is performed. + /// + /// # Errors + /// + /// Returns an error if there is an issue reading from the underlying trie storage. + /// + /// [`BatchOp`]: crate::db::BatchOp + /// [`BatchOp::Delete`]: crate::db::BatchOp::Delete + /// [`BatchOp::DeleteRange`]: crate::db::BatchOp::DeleteRange + pub fn merge_key_value_range( + &self, + first_key: Option, + last_key: Option, + key_values: impl IntoIterator>, + ) -> impl BatchIter { + merge::MergeKeyValueIter::new(self, first_key, last_key, key_values) + } + pub(crate) fn path_iter<'a>( &self, key: &'a [u8], diff --git a/firewood/src/merkle/parallel.rs b/firewood/src/merkle/parallel.rs index ca3b187bc09c..7694cc159ca7 100644 --- a/firewood/src/merkle/parallel.rs +++ b/firewood/src/merkle/parallel.rs @@ -3,7 +3,7 @@ use crate::db::BatchOp; use crate::merkle::{Key, Merkle, Value}; -use crate::v2::api::KeyValuePairIter; +use crate::v2::api::IntoBatchIter; use firewood_storage::logger::error; use firewood_storage::{ BranchNode, Child, Children, FileBacked, FileIoError, ImmutableProposal, LeafNode, @@ -240,13 +240,15 @@ impl ParallelMerkle { .children .get_mut(first_path_component) .take() - .map(|child| match child { - Child::Node(node) => Ok(node), - Child::AddressWithHash(address, _) => { - Ok(proposal.read_node(address)?.deref().clone()) - } - Child::MaybePersisted(maybe_persisted, _) => { - Ok(maybe_persisted.as_shared_node(proposal)?.deref().clone()) + .map(|child| -> Result<_, FileIoError> { + match child { + Child::Node(node) => Ok(node), + Child::AddressWithHash(address, _) => { + Ok(proposal.read_node(address)?.deref().clone()) + } + Child::MaybePersisted(maybe_persisted, _) => { + Ok(maybe_persisted.as_shared_node(proposal)?.deref().clone()) + } } }) .transpose()?; @@ -375,7 +377,7 @@ impl ParallelMerkle { pub fn create_proposal( &mut self, parent: &NodeStore, - batch: impl IntoIterator, + batch: impl IntoBatchIter, pool: &ThreadPool, ) -> Result, FileBacked>>, CreateProposalError> { // Create a mutable nodestore from the parent @@ -390,7 +392,8 @@ impl ParallelMerkle { // Split step: for each operation in the batch, send a request to the worker that is // responsible for the sub-trie corresponding to the operation's first nibble. - for op in batch.into_iter().map_into_batch() { + for res in batch.into_batch_iter::() { + let op = res?; // Get the first nibble of the key to determine which worker to send the request to. // // Need to handle an empty key. Since the partial_path of the root must be empty, an diff --git a/firewood/src/range_proof.rs b/firewood/src/range_proof.rs index e309ddb25e9a..1cd9969ab5d9 100644 --- a/firewood/src/range_proof.rs +++ b/firewood/src/range_proof.rs @@ -147,8 +147,9 @@ mod tests { #![expect(clippy::unwrap_used, reason = "Tests can use unwrap")] #![expect(clippy::indexing_slicing, reason = "Tests can use indexing")] + use crate::v2::api::TryIntoBatch; + use super::*; - use crate::v2::api::KeyValuePairIter; #[test] fn test_range_proof_iterator() { @@ -221,8 +222,8 @@ mod tests { let iter = range_proof.iter(); // Verify we can call methods from KeyValuePairIter - let batch_iter = iter.map_into_batch(); - let batches: Vec<_> = batch_iter.collect(); + let batch_iter = iter.map(TryIntoBatch::try_into_batch); + let batches: Vec<_> = batch_iter.collect::>().unwrap(); assert_eq!(batches.len(), 1); // The batch should be a Put operation since value is non-empty diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 74a14f6927e1..de961d542bfd 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -12,7 +12,7 @@ use std::num::NonZeroUsize; use std::sync::Arc; pub use crate::range_proof::RangeProof; -pub use crate::v2::batch_op::{BatchOp, KeyValuePair, KeyValuePairIter, MapIntoBatch}; +pub use crate::v2::batch_op::{BatchIter, BatchOp, IntoBatchIter, KeyValuePair, TryIntoBatch}; /// A `KeyType` is something that can be xcast to a u8 reference, /// and can be sent and shared across threads. References with @@ -176,6 +176,12 @@ pub enum Error { InvalidConversionToPathComponent, } +impl From for Error { + fn from(value: std::convert::Infallible) -> Self { + match value {} + } +} + impl From for Error { fn from(err: RevisionManagerError) -> Self { use RevisionManagerError::{ @@ -257,10 +263,7 @@ pub trait Db { /// * `data` - A batch consisting of [`BatchOp::Put`] and [`BatchOp::Delete`] /// operations to apply #[expect(clippy::missing_errors_doc)] - fn propose( - &self, - data: impl IntoIterator, - ) -> Result, Error>; + fn propose(&self, data: impl IntoBatchIter) -> Result, Error>; } /// A view of the database at a specific time. @@ -273,7 +276,7 @@ pub trait Db { /// 3. From [`Proposal::propose`] which is a view on top of another proposal. pub trait DbView { /// The type of a stream of key/value pairs - type Iter<'view>: Iterator> + type Iter<'view>: Iterator> where Self: 'view; @@ -337,7 +340,8 @@ pub trait DbView { } /// A boxed iterator over key/value pairs. -pub type BoxKeyValueIter<'view> = Box> + 'view>; +pub type BoxKeyValueIter<'view> = + Box> + 'view>; /// A dynamic dyspatch version of [`DbView`] that can be shared. pub type ArcDynDbView = Arc; @@ -467,10 +471,7 @@ pub trait Proposal: DbView { /// /// A new proposal #[expect(clippy::missing_errors_doc)] - fn propose( - &self, - data: impl IntoIterator, - ) -> Result; + fn propose(&self, data: impl IntoBatchIter) -> Result; } #[cfg(test)] diff --git a/firewood/src/v2/batch_op.rs b/firewood/src/v2/batch_op.rs index 35aca576057f..23f2315f18f1 100644 --- a/firewood/src/v2/batch_op.rs +++ b/firewood/src/v2/batch_op.rs @@ -1,6 +1,8 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use firewood_storage::FileIoError; + use crate::v2::api::{KeyType, ValueType}; /// A key/value pair operation. @@ -117,67 +119,140 @@ impl std::hash::Hash for BatchOp { } } +/// A key/value pair that can be converted into a batch operation. +/// +/// The difference between this trait and [`TryIntoBatch`] is that this trait +/// is only used in places where the operations are expected to all be `Put` +/// operations. Empty values are not treated as `DeleteRange` operations here. +pub trait KeyValuePair: TryIntoBatch { + /// Convert this into a tuple of key and value. + /// + /// # Errors + /// + /// Returns an error if the conversion fails. E.g., if a read from storage + /// is required to obtain the key or value. + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error>; +} + +impl KeyValuePair for (K, V) { + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error> { + Ok(self) + } +} + +impl KeyValuePair for &(K, V) { + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error> { + let (key, value) = self; + Ok((key, value)) + } +} + +impl, E: Into> KeyValuePair + for Result +{ + fn try_into_tuple(self) -> Result<(Self::Key, Self::Value), Self::Error> { + match self { + Ok(t) => t.try_into_tuple().map_err(|e| match e {}), + Err(e) => Err(e), + } + } +} + /// A key/value pair that can be used in a batch. -pub trait KeyValuePair { +pub trait TryIntoBatch { /// The key type type Key: KeyType; /// The value type type Value: ValueType; + /// The error type. It is preferable to use an error that is convertible into + /// [`FileIoError`] instead of the type directly to allow for better compiler + /// optimizations. + /// + /// E.g., [`std::convert::Infallible`] is preferred over [`FileIoError`] when + /// there is no possibility of error so that the compiler can optimize away + /// error handling. + type Error: Into; + /// Convert this key-value pair into a [`BatchOp`]. - #[must_use] - fn into_batch(self) -> BatchOp; + /// + /// # Errors + /// + /// Returns an error if the conversion fails. E.g., if a read from storage + /// is required to obtain the key or value. + fn try_into_batch(self) -> Result, Self::Error>; } -impl<'a, K: KeyType, V: ValueType> KeyValuePair for &'a (K, V) { +impl<'a, K: KeyType, V: ValueType> TryIntoBatch for &'a (K, V) { type Key = &'a K; type Value = &'a V; + type Error = std::convert::Infallible; #[inline] - fn into_batch(self) -> BatchOp { + fn try_into_batch(self) -> Result, Self::Error> { // this converting `&'a (K, V)` into `(&'a K, &'a V)` let (key, value) = self; - (key, value).into_batch() + (key, value).try_into_batch() } } -impl KeyValuePair for (K, V) { +impl TryIntoBatch for (K, V) { type Key = K; type Value = V; + type Error = std::convert::Infallible; #[inline] - fn into_batch(self) -> BatchOp { + fn try_into_batch(self) -> Result, Self::Error> { let (key, value) = self; if value.as_ref().is_empty() { - BatchOp::DeleteRange { prefix: key } + Ok(BatchOp::DeleteRange { prefix: key }) } else { - BatchOp::Put { key, value } + Ok(BatchOp::Put { key, value }) } } } -impl KeyValuePair for BatchOp { +impl TryIntoBatch for BatchOp { type Key = K; type Value = V; + type Error = std::convert::Infallible; - fn into_batch(self) -> BatchOp { - self + fn try_into_batch(self) -> Result, Self::Error> { + Ok(self) } } -impl<'a, K: KeyType, V: ValueType> KeyValuePair for &'a BatchOp { +impl<'a, K: KeyType, V: ValueType> TryIntoBatch for &'a BatchOp { type Key = &'a K; type Value = &'a V; + type Error = std::convert::Infallible; + + fn try_into_batch(self) -> Result, Self::Error> { + Ok(self.borrowed()) + } +} + +impl TryIntoBatch for Result +where + T: TryIntoBatch, + E: Into, +{ + type Key = T::Key; + type Value = T::Value; + type Error = E; - fn into_batch(self) -> BatchOp { - self.borrowed() + fn try_into_batch(self) -> Result, Self::Error> { + match self { + Ok(t) => t.try_into_batch().map_err(|e| match e {}), + Err(e) => Err(e), + } } } -/// An extension trait for iterators that yield [`KeyValuePair`]s. -pub trait KeyValuePairIter: - Iterator> +/// An extension trait for iterators that yield [`TryIntoBatch`]s. +pub trait BatchIter: + Iterator> { /// An associated type for the iterator item's key type. This is a convenience /// requirement to avoid needing to build up nested generic associated types. @@ -189,26 +264,46 @@ pub trait KeyValuePairIter: /// E.g., `<::Item as KeyValuePair>::Value` type Value: ValueType; - /// Maps the items of this iterator into [`BatchOp`]s. - #[inline] - fn map_into_batch(self) -> MapIntoBatch + /// An associated type for the iterator item's error type. This is a convenience + /// requirement to avoid needing to build up nested generic associated types. + /// E.g., `<::Item as KeyValuePair>::Error` + type Error: Into; +} + +impl> BatchIter for I { + type Key = ::Key; + type Value = ::Value; + type Error = ::Error; +} + +/// An extension trait for types that can be converted into an iterator of batch +/// operations. +pub trait IntoBatchIter: IntoIterator { + /// Convert this type into an iterator of batch operations. + /// + /// This is a convenience method that maps over the iterator returned by + /// [`IntoIterator::into_iter`] and calls [`TryIntoBatch::try_into_batch`] + /// on each item. Additionally, the error type is converted into the specified + /// error type `E` using the `Into` trait. + /// + /// The pass-through of the error type `E` allows for better compiler optimizations + /// by allowing error handling to be omitted during monomorphization when the error + /// type is `Infallible` or another type that can be optimized away. + fn into_batch_iter(self) -> std::iter::Map> where Self: Sized, - Self::Item: KeyValuePair, + FileIoError: Into, { - self.map(KeyValuePair::into_batch) + self.into_iter().map(|item| { + item.try_into_batch() + .map_err(Into::::into) + .map_err(Into::::into) + }) } } -impl> KeyValuePairIter for I { - type Key = ::Key; - type Value = ::Value; -} +impl> IntoBatchIter for T {} -/// An iterator that maps a [`KeyValuePair`] into a [`BatchOp`] on yielded items. -pub type MapIntoBatch = std::iter::Map< - I, - fn( - ::Item, - ) -> BatchOp<::Key, ::Value>, ->; +/// Type alias for `fn(T) -> Result, E>` where `T: TryIntoBatch`. +pub type MapIntoBatchFn = + fn(T) -> Result::Key, ::Value>, E>; diff --git a/fwdctl/src/dump.rs b/fwdctl/src/dump.rs index 801dde47d18f..f1c3b776d944 100644 --- a/fwdctl/src/dump.rs +++ b/fwdctl/src/dump.rs @@ -5,6 +5,7 @@ use firewood::db::{Db, DbConfig}; use firewood::iter::MerkleKeyValueIter; use firewood::merkle::{Key, Value}; use firewood::v2::api::{self, Db as _}; +use firewood_storage::FileIoError; use std::borrow::Cow; use std::error::Error; use std::fs::File; @@ -13,7 +14,7 @@ use std::path::PathBuf; use crate::DatabasePath; -type KeyFromStream = Option>; +type KeyFromStream = Option>; #[derive(Debug, clap::ValueEnum, Clone, PartialEq)] pub enum OutputFormat { @@ -181,7 +182,7 @@ pub(super) fn run(opts: &Options) -> Result<(), api::Error> { break; } } - Err(e) => return Err(e), + Err(e) => return Err(e.into()), } } output_handler.flush()?; diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 23d9cf885226..64fb08dc010d 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -40,6 +40,12 @@ pub struct FileIoError { context: Option, } +impl From for FileIoError { + fn from(error: std::convert::Infallible) -> Self { + match error {} + } +} + impl FileIoError { /// Create a new [`FileIoError`] from a generic error /// From 439ddcceb4b77f52eaad3ef641ee7de32bc25037 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:09:44 -0500 Subject: [PATCH 1024/1053] docs: add agent instructions (#1445) This PR adds an `AGENTS.md` file catered towards `Firewood` and which can be used by agents such as Copilot. This PR also adds a `CLAUDE.md` file which is symlinked to `AGENTS.md` for use with Claude Code. --- .github/check-license-headers.yaml | 2 + AGENTS.md | 188 +++++++++++++++++++++++++++++ CLAUDE.md | 1 + 3 files changed, 191 insertions(+) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 2ca80dbfdd4a..4bd878d01b4e 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -50,6 +50,8 @@ "CONTRIBUTING.md", "triehash/**", "CHANGELOG.md", + "AGENTS.md", + "CLAUDE.md", "cliff.toml", "clippy.toml", "**/tests/compile_*/**", diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..7751e53ad8dc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,188 @@ +# Firewood - AI Assistant Guide + +This document provides context and guidance for AI assistants working with the Firewood codebase. + +## Project Overview + +Firewood is an embedded key-value store optimized for storing recent Merkleized +blockchain state with minimal overhead. It's designed for the Avalanche C-Chain +and EVM-compatible blockchains that store state in Merkle tries. + +**Key Characteristics:** + +- Written in Rust (edition 2024, MSRV 1.91.0) +- Beta-level software with evolving API +- Compaction-less database that directly stores trie nodes on-disk +- Not built on generic KV stores (LevelDB/RocksDB) +- Uses trie structure directly as the index on-disk +- Maintains configurable number of recent revisions in memory and on disk + +## Architecture Principles + +1. **Direct Trie Storage**: Unlike most state management approaches, Firewood directly uses the trie structure as the index on-disk rather than emulating it on top of a generic database. + +2. **Revision Management**: Creates new roots for each revision, tracks deleted nodes in a future-delete log (FDL), and returns space to free lists when revisions expire. + +3. **Disk Addressing**: Root address of a node is simply the disk offset within the database file, not based on hashes. + +4. **Recoverability**: Guarantees recoverability by not referencing new nodes before they're flushed to disk and carefully managing free lists. + +## Workspace Structure + +This is a Cargo workspace with the following members: + +```text +firewood/ # Core library and main database implementation +├── src/ # Core source code +│ ├── db.rs # Main database API +│ └── manager.rs # RevisionManager for managing historical revisions +├── examples/ # Example usage (e.g., insert example) +└── benches/ # Benchmarks + +firewood-macros/ # Procedural macros for the project +storage/ # Storage layer implementation +triehash/ # Trie hashing functionality +ffi/ # Foreign Function Interface (FFI) binding for Golang +├── src/ # Rust FFI bindings (C-compatible API) +├── firewood.go # Go wrapper around the Firewood `Db` type +├── proposal.go # Go wrapper around the Firewood `Proposal` type +└── revision.go # Go wrapper around the Firewood `DbView` type +fwdctl/ # CLI tool for interacting with Firewood databases +benchmark/ # Performance benchmarking suite +├── bootstrap/ # Script for running C-Chain reexecution benchmark on an EC2 instance. +└── setup-scripts/ # Scripts for setting up benchmark environments +``` + +## Important Terminology + +- **Revision**: Historical point-in-time state of the trie +- **View**: Interface to read from a Revision or Proposal +- **Node**: Portion of a trie that can point to other nodes and/or contain Key/Value pairs +- **Hash/Root Hash**: Merkle hash for a node/root node +- **Proposal**: Consists of base Root Hash and Batch, not yet committed +- **Commit**: Operation of applying Proposals to the most recent Revision +- **Batch**: Ordered set of Put/Delete operations + +## Feature Flags + +### `ethhash` + +By default, Firewood uses SHA-256 hashing compatible with merkledb. Enable this feature for Ethereum compatibility: + +- Changes hashing from SHA-256 to Keccak-256 +- Understands "account" nodes at specific depths with RLP-encoded values +- Computes account trie hash as actual root +- See `firewood/storage/src/hashers/ethhash.rs` for implementation details + +### `logging` + +Enable for runtime logging. Set `RUST_LOG` environment variable accordingly (uses `env_logger`). + +## Common Development Tasks + +### FFI + +Building and using the FFI library is a multi-step process. To generate the +Firewood Rust FFI bindings: + +```bash +cd ffi/src # Go to Rust binding directory +cargo clean # Remove any existing bindings +cargo build --profile maxperf --features ethhash,logger # Generate bindings +``` + +To then have Golang utilize these new bindings: + +```bash +cd .. # Go to ffi directory +go tool cgo firewood.go # Generate cgo wrappers +``` + +### Using the CLI + +The `fwdctl` tool provides command-line operations on databases. See `fwdctl/README.md`. + +## Coding Conventions and Constraints + +For more information on coding conventions and constraints, please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) + +## PR Strategy + +Before submitting/updating a PR, run the following + +```bash +cargo fmt # Format code +cargo test --workspace --features ethhash,logger --all-targets # Run tests +cargo clippy --workspace --features ethhash,logger --all-targets # Linter +cargo doc --no-deps # Ensure docs build +``` + +All tests must pass, and there should be no clippy warnings. + +### Markdown Linter + +If your PR touches any Markdown file, run the following: + +```bash +markdownlint-cli2 . +``` + +If the linter fails, run the following to fix any lint errors: + +```bash +markdownlint-cli2 . --fix +``` + +If you don't have `markdownlint-cli2` available on your system, run the +following to install the linter: + +```bash +brew install markdownlint-cli2 +``` + +## Performance Profiles + +- **release**: Standard release with debug symbols +- **maxperf**: Panic abort, single codegen unit, fat LTO, no debug symbols + +## Dependencies Management + +Key dependencies are centrally managed in workspace `Cargo.toml`: + +- `firewood`, `firewood-macros`, `firewood-storage`, `firewood-ffi`, `firewood-triehash` (workspace members) +- Common deps: `clap`, `thiserror`, `smallvec`, `sha2`, `log`, etc. +- Test deps: `criterion`, `tempfile`, `rand`, etc. + +## Key Files to Know + +- `README.md` - Main documentation +- `CONTRIBUTING.md` - Contribution guidelines +- `RELEASE.md` - Release process +- `README.docker.md` - Docker setup +- `CHANGELOG.md` - Version history +- `clippy.toml` - Linting configuration +- `justfile` - Just command runner recipes +- `cliff.toml` - Changelog generation config + +## Notes for AI Assistants + +1. **Safety First**: This codebase denies unsafe code. Never suggest unsafe + blocks without documentation and strong justification. Unsafe code could be + utilized in the `ffi` crate. + +2. **Testing**: Any changes should include appropriate tests. Run `cargo test --release` to verify. + +3. **Performance Context**: This is a database designed for blockchain state. Performance matters. Consider allocation patterns and hot paths. + +4. **Beta Status**: The API may change. Don't assume stability guarantees. + +5. **Feature Flags**: Be aware of `ethhash` feature flag when discussing Ethereum compatibility vs. default merkledb compatibility. + +6. **Documentation**: Public APIs should be well-documented. Run `cargo doc --no-deps` to check. + +7. **Workspace Awareness**: This is a multi-crate workspace. Changes may affect multiple crates. Check `Cargo.toml` for workspace structure. + +## Additional Resources + +- [Auto-generated docs](https://ava-labs.github.io/firewood/firewood/) +- [Issue tracker](https://github.com/ava-labs/firewood/issues) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000000..47dc3e3d863c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 7d28349bc781429a56b1fbaf6ecb9e2520fcf0ec Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:23:47 -0500 Subject: [PATCH 1025/1053] refactor(rootstore): replace RootStoreError with boxed error (#1446) As mentioned in https://github.com/ava-labs/firewood/pull/1395#discussion_r2504821379, having to create an instance of `RootStoreError` everytime we want to return an error is cumbersome. This PR reduces the amount of duplicate code by removing the `RootStoreError` type as the `method` field isn't really useful (we can figure out which method returned the error during debugging) and the `source` field doesn't need to be wrapped. --- firewood/src/manager.rs | 28 +++++++++++-------- firewood/src/root_store.rs | 57 +++++++++++++++++++------------------- firewood/src/v2/api.rs | 3 +- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 63c39a52e68c..c69c5e48946e 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -21,7 +21,7 @@ use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; use crate::merkle::Merkle; -use crate::root_store::{RootStore, RootStoreError}; +use crate::root_store::RootStore; use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; @@ -97,8 +97,8 @@ pub(crate) enum RevisionManagerError { }, #[error("An IO error occurred during the commit: {0}")] FileIoError(#[from] FileIoError), - #[error("A RootStore error occurred")] - RootStoreError(#[from] RootStoreError), + #[error("A RootStore error occurred: {0}")] + RootStoreError(#[source] Box), } impl RevisionManager { @@ -152,7 +152,10 @@ impl RevisionManager { }, )?; - manager.root_store.add_root(&root_hash, &root_address)?; + manager + .root_store + .add_root(&root_hash, &root_address) + .map_err(RevisionManagerError::RootStoreError)?; } Ok(manager) @@ -236,7 +239,9 @@ impl RevisionManager { // 5. Persist revision to root store if let (Some(hash), Some(address)) = (committed.root_hash(), committed.root_address()) { - self.root_store.add_root(&hash, &address)?; + self.root_store + .add_root(&hash, &address) + .map_err(RevisionManagerError::RootStoreError)?; } // 6. Set last committed revision @@ -300,12 +305,13 @@ impl RevisionManager { } // 3. Try to find it in `RootStore`. - let revision_addr = - self.root_store - .get(&root_hash)? - .ok_or(RevisionManagerError::RevisionNotFound { - provided: root_hash.clone(), - })?; + let revision_addr = self + .root_store + .get(&root_hash) + .map_err(RevisionManagerError::RootStoreError)? + .ok_or(RevisionManagerError::RevisionNotFound { + provided: root_hash.clone(), + })?; let node_store = NodeStore::with_root( root_hash.into_hash_type(), diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index a112ffc9c010..a55e9d9d75f8 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -10,20 +10,6 @@ use std::{ use firewood_storage::{LinearAddress, TrieHash}; -#[derive(Debug)] -pub enum RootStoreMethod { - Add, - Get, -} - -#[derive(Debug, thiserror::Error)] -#[error("A RootStore error occurred.")] -pub struct RootStoreError { - pub method: RootStoreMethod, - #[source] - pub source: Box, -} - pub trait RootStore: Debug { /// `add_root` persists a revision's address to `RootStore`. /// @@ -35,7 +21,11 @@ pub trait RootStore: Debug { /// /// Will return an error if unable to persist the revision address to the /// underlying datastore - fn add_root(&self, hash: &TrieHash, address: &LinearAddress) -> Result<(), RootStoreError>; + fn add_root( + &self, + hash: &TrieHash, + address: &LinearAddress, + ) -> Result<(), Box>; /// `get` returns the address of a revision. /// @@ -45,18 +35,28 @@ pub trait RootStore: Debug { /// # Errors /// /// Will return an error if unable to query the underlying datastore. - fn get(&self, hash: &TrieHash) -> Result, RootStoreError>; + fn get( + &self, + hash: &TrieHash, + ) -> Result, Box>; } #[derive(Debug)] pub struct NoOpStore {} impl RootStore for NoOpStore { - fn add_root(&self, _hash: &TrieHash, _address: &LinearAddress) -> Result<(), RootStoreError> { + fn add_root( + &self, + _hash: &TrieHash, + _address: &LinearAddress, + ) -> Result<(), Box> { Ok(()) } - fn get(&self, _hash: &TrieHash) -> Result, RootStoreError> { + fn get( + &self, + _hash: &TrieHash, + ) -> Result, Box> { Ok(None) } } @@ -82,12 +82,13 @@ impl MockStore { #[cfg(test)] impl RootStore for MockStore { - fn add_root(&self, hash: &TrieHash, address: &LinearAddress) -> Result<(), RootStoreError> { + fn add_root( + &self, + hash: &TrieHash, + address: &LinearAddress, + ) -> Result<(), Box> { if self.should_fail { - return Err(RootStoreError { - method: RootStoreMethod::Add, - source: "Adding roots should fail".into(), - }); + return Err("Adding roots should fail".into()); } self.roots @@ -97,12 +98,12 @@ impl RootStore for MockStore { Ok(()) } - fn get(&self, hash: &TrieHash) -> Result, RootStoreError> { + fn get( + &self, + hash: &TrieHash, + ) -> Result, Box> { if self.should_fail { - return Err(RootStoreError { - method: RootStoreMethod::Get, - source: "Getting roots should fail".into(), - }); + return Err("Getting roots should fail".into()); } Ok(self.roots.lock().expect("poisoned lock").get(hash).copied()) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index de961d542bfd..45ca1bc44178 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -5,7 +5,6 @@ use crate::manager::RevisionManagerError; use crate::merkle::parallel::CreateProposalError; use crate::merkle::{Key, Value}; use crate::proof::{Proof, ProofError, ProofNode}; -use crate::root_store::RootStoreError; use firewood_storage::{FileIoError, TrieHash}; use std::fmt::Debug; use std::num::NonZeroUsize; @@ -133,7 +132,7 @@ pub enum Error { #[error("RootStore error: {0}")] /// A `RootStore` error occurred - RootStoreError(#[from] RootStoreError), + RootStoreError(#[source] Box), /// Cannot commit a committed proposal #[error("Cannot commit a committed proposal")] From efdb77163d8615af87340eb67bf682afd4613a58 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Thu, 13 Nov 2025 01:00:53 -0500 Subject: [PATCH 1026/1053] feat(storage): replace `ArcSwap` with `Mutex` for better performance (#1447) Per benchmarks done for discovering source of commit time regressions in parallel propose (#1383), we identified that the reason was `ArcSwap` usage in `MaybePersistedNode`. The lock-free mechanism of `ArcSwap` introduces write overhead when multiple threads read the data, growing linearly by the number of threads. More details are provided in #1383. This PR replaces the `ArcSwap` usage with `Mutex>`. Re-execution tests for blocks 50-60m show 20% improvement (MGas/s) with parallel propose using `Mutex`, while the `ArcSwap` version provided no benefit over normal propose and slightly degraded performance. --- storage/src/node/persist.rs | 90 ++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 6e42e827c4c9..2e68553e7b65 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -1,10 +1,9 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::sync::{Mutex, TryLockError}; use std::{fmt::Display, sync::Arc}; -use arc_swap::ArcSwap; - use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; /// A node that is either in memory or on disk. @@ -12,9 +11,8 @@ use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; /// In-memory nodes that can be moved to disk. This structure allows that to happen /// atomically. /// -/// `MaybePersistedNode` owns a reference counted pointer to an atomically swapable -/// pointer. The atomically swapable pointer points to a reference counted pointer -/// to the enum of either an un-persisted but committed (or committing) node or the +/// `MaybePersistedNode` owns a reference-counted pointer to a mutex-protected +/// enum representing either an un-persisted (or allocating) node or the /// linear address of a persisted node. /// /// This type is complicated, so here is a breakdown of the types involved: @@ -22,9 +20,8 @@ use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; /// | Item | Description | /// |------------------------|--------------------------------------------------------| /// | [`MaybePersistedNode`] | Newtype wrapper around the remaining items. | -/// | [Arc] | reference counted pointer to an updatable pointer. | -/// | [`ArcSwap`] | Swappable [Arc]. Actually an `ArcSwapAny`<`Arc`<_>> | -/// | [Arc] | reference-counted pointer to the enum (required by `ArcSwap`) | +/// | [Arc] | Reference counted pointer to a mutexed enum | +/// | `Mutex` | Protects the inner enum during updates | /// | `MaybePersisted` | Enum of either `Unpersisted` or `Persisted` | /// | variant `Unpersisted` | The shared node, in memory, for unpersisted nodes | /// | -> [`SharedNode`] | A `triomphe::Arc` of a [Node](`crate::Node`) | @@ -34,15 +31,18 @@ use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; /// Traversing these pointers does incur a runtime penalty. /// /// When an `Unpersisted` node is `Persisted` using [`MaybePersistedNode::persist_at`], -/// a new `Arc` is created to the new `MaybePersisted::Persisted` variant and the `ArcSwap` -/// is updated atomically. Subsequent accesses to any instance of it, including any clones, -/// will see the `Persisted` node address. +/// the enum value inside the mutex is replaced under the lock. Subsequent accesses +/// to any instance of it, including any clones, will see the `Persisted` node address. #[derive(Debug, Clone)] -pub struct MaybePersistedNode(Arc>); +pub struct MaybePersistedNode(Arc>); impl PartialEq for MaybePersistedNode { fn eq(&self, other: &MaybePersistedNode) -> bool { - self.0.load().as_ref() == other.0.load().as_ref() + // if underlying mutex is same, this is necessary to avoid deadlock + if Arc::ptr_eq(&self.0, &other.0) { + return true; + } + *self.0.lock().expect("poisoned lock") == *other.0.lock().expect("poisoned lock") } } @@ -50,23 +50,19 @@ impl Eq for MaybePersistedNode {} impl From for MaybePersistedNode { fn from(node: SharedNode) -> Self { - MaybePersistedNode(Arc::new(ArcSwap::new(Arc::new( - MaybePersisted::Unpersisted(node), - )))) + MaybePersistedNode(Arc::new(Mutex::new(MaybePersisted::Unpersisted(node)))) } } impl From for MaybePersistedNode { fn from(address: LinearAddress) -> Self { - MaybePersistedNode(Arc::new(ArcSwap::new(Arc::new(MaybePersisted::Persisted( - address, - ))))) + MaybePersistedNode(Arc::new(Mutex::new(MaybePersisted::Persisted(address)))) } } impl From<&MaybePersistedNode> for Option { fn from(node: &MaybePersistedNode) -> Option { - match node.0.load().as_ref() { + match &*node.0.lock().expect("poisoned lock") { MaybePersisted::Unpersisted(_) => None, MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { Some(*address) @@ -91,7 +87,7 @@ impl MaybePersistedNode { /// - `Ok(SharedNode)` contains the node if successfully retrieved /// - `Err(FileIoError)` if there was an error reading from storage pub fn as_shared_node(&self, storage: &S) -> Result { - match self.0.load().as_ref() { + match &*self.0.lock().expect("poisoned lock") { MaybePersisted::Allocated(_, node) | MaybePersisted::Unpersisted(node) => { Ok(node.clone()) } @@ -106,7 +102,7 @@ impl MaybePersistedNode { /// Returns `Some(LinearAddress)` if the node is persisted on disk, otherwise `None`. #[must_use] pub fn as_linear_address(&self) -> Option { - match self.0.load().as_ref() { + match &*self.0.lock().expect("poisoned lock") { MaybePersisted::Unpersisted(_) => None, MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { Some(*address) @@ -121,7 +117,7 @@ impl MaybePersistedNode { /// Returns `Some(&Self)` if the node is unpersisted, otherwise `None`. #[must_use] pub fn unpersisted(&self) -> Option<&Self> { - match self.0.load().as_ref() { + match &*self.0.lock().expect("poisoned lock") { MaybePersisted::Allocated(_, _) | MaybePersisted::Unpersisted(_) => Some(self), MaybePersisted::Persisted(_) => None, } @@ -132,13 +128,13 @@ impl MaybePersistedNode { /// This method changes the internal state of the `MaybePersistedNode` from `Mem` to `Disk`, /// indicating that the node has been written to the specified disk location. /// - /// This is done atomically using the `ArcSwap` mechanism. + /// This is done under a `Mutex` lock. /// /// # Arguments /// /// * `addr` - The `LinearAddress` where the node has been persisted on disk pub fn persist_at(&self, addr: LinearAddress) { - self.0.store(Arc::new(MaybePersisted::Persisted(addr))); + *self.0.lock().expect("poisoned lock") = MaybePersisted::Persisted(addr); } /// Updates the internal state to indicate this node is allocated at the specified disk address. @@ -146,21 +142,24 @@ impl MaybePersistedNode { /// This method changes the internal state of the `MaybePersistedNode` to `Allocated`, /// indicating that the node has been allocated on disk but is still in memory. /// - /// This is done atomically using the `ArcSwap` mechanism. + /// This is done under a `Mutex` lock. /// /// # Arguments /// /// * `addr` - The `LinearAddress` where the node has been allocated on disk pub fn allocate_at(&self, addr: LinearAddress) { - match self.0.load().as_ref() { - MaybePersisted::Unpersisted(node) | MaybePersisted::Allocated(_, node) => { - self.0 - .store(Arc::new(MaybePersisted::Allocated(addr, node.clone()))); - } - MaybePersisted::Persisted(_) => { - unreachable!("Cannot allocate a node that is already persisted on disk"); + let mut guard = self.0.lock().expect("poisoned lock"); + let node = { + match &*guard { + MaybePersisted::Unpersisted(node) | MaybePersisted::Allocated(_, node) => { + node.clone() + } + MaybePersisted::Persisted(_) => { + unreachable!("Cannot allocate a node that is already persisted on disk"); + } } - } + }; + *guard = MaybePersisted::Allocated(addr, node); } /// Returns the address and shared node if this node is in the Allocated state. @@ -171,7 +170,7 @@ impl MaybePersistedNode { /// otherwise `None`. #[must_use] pub fn allocated_info(&self) -> Option<(LinearAddress, SharedNode)> { - match self.0.load().as_ref() { + match &*self.0.lock().expect("poisoned lock") { MaybePersisted::Allocated(addr, node) => Some((*addr, node.clone())), _ => None, } @@ -189,10 +188,20 @@ impl MaybePersistedNode { /// If instead you want the node itself, use [`MaybePersistedNode::as_shared_node`] first. impl Display for MaybePersistedNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.load().as_ref() { - MaybePersisted::Unpersisted(node) => write!(f, "M{:p}", (*node).as_ptr()), - MaybePersisted::Allocated(addr, node) => write!(f, "A{:p}@{addr}", (*node).as_ptr()), - MaybePersisted::Persisted(addr) => write!(f, "{addr}"), + match self.0.try_lock() { + Ok(guard) => match &*guard { + MaybePersisted::Unpersisted(node) => write!(f, "M{:p}", (*node).as_ptr()), + MaybePersisted::Allocated(addr, node) => { + write!(f, "A{:p}@{addr}", (*node).as_ptr()) + } + MaybePersisted::Persisted(addr) => write!(f, "{addr}"), + }, + Err(TryLockError::WouldBlock) => { + write!(f, "") + } + Err(TryLockError::Poisoned(_)) => { + panic!("poisoned lock") + } } } } @@ -275,7 +284,8 @@ mod test { let addr = nonzero!(1024u64).into(); original.persist_at(addr); - // Both original and clone should now be persisted since they share the same ArcSwap + // Both original and clone should now be persisted since they share the same + // mutex-protected pointer assert!(original.as_shared_node(&store).is_err()); assert!(cloned.as_shared_node(&store).is_err()); assert_eq!(Some(addr), Option::from(&original)); From 6e4fb9b512de143f35a2497332da3b19f4af068b Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:39:43 -0500 Subject: [PATCH 1027/1053] feat(ffi)!: Remove unused kvBackend interface (#1448) This is straight techdebt. This interface isn't even exported, and the two functions are no-ops --- ffi/kvbackend.go | 59 ------------------------------------------------ 1 file changed, 59 deletions(-) delete mode 100644 ffi/kvbackend.go diff --git a/ffi/kvbackend.go b/ffi/kvbackend.go deleted file mode 100644 index 2c7e4d3f3a9f..000000000000 --- a/ffi/kvbackend.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -package ffi - -// implement a specific interface for firewood -// this is used for some of the firewood performance tests - -// Validate that Firewood implements the KVBackend interface -var _ kVBackend = (*Database)(nil) - -type kVBackend interface { - // Returns the current root hash of the trie. - // Empty trie must return common.Hash{}. - // Length of the returned slice must be common.HashLength. - Root() ([]byte, error) - - // Get retrieves the value for the given key. - // If the key does not exist, it must return (nil, nil). - Get(key []byte) ([]byte, error) - - // Prefetch loads the intermediary nodes of the given key into memory. - // The first return value is ignored. - Prefetch(key []byte) ([]byte, error) - - // After this call, Root() should return the same hash as returned by this call. - // Note when length of a particular value is zero, it means the corresponding - // key should be deleted. - // There may be duplicate keys in the batch provided, and the last one should - // take effect. - // Note after this call, the next call to Update must build on the returned root, - // regardless of whether Commit is called. - // Length of the returned root must be common.HashLength. - Update(keys, vals [][]byte) ([]byte, error) - - // After this call, changes related to [root] should be persisted to disk. - // This may be implemented as no-op if Update already persists changes, or - // commits happen on a rolling basis. - // Length of the root slice is guaranteed to be common.HashLength. - Commit(root []byte) error -} - -// Prefetch is a no-op since we don't need to prefetch for Firewood. -func (db *Database) Prefetch(_ []byte) ([]byte, error) { - if db.handle == nil { - return nil, errDBClosed - } - - return nil, nil -} - -// Commit is a no-op, since [Database.Update] already persists changes. -func (db *Database) Commit(_ []byte) error { - if db.handle == nil { - return errDBClosed - } - - return nil -} From f4a2cf07028543015facf2a8d839e64757c13808 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:02:46 -0500 Subject: [PATCH 1028/1053] docs(firewood): clean up commit steps (#1453) Reading through the documentation for `commit()`, it seems that step 2 refers to a process that no longer exists/is handled by (what was) step 3. This PR cleans up the documentation for `commit()` by removing step 2 and reordering as necessary. --- firewood/src/manager.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index c69c5e48946e..b80dbcfcc3ef 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -165,18 +165,14 @@ impl RevisionManager { /// To commit a proposal involves a few steps: /// 1. Commit check. /// The proposal's parent must be the last committed revision, otherwise the commit fails. - /// 2. Persist delete list. - /// The list of all nodes that were to be deleted for this proposal must be fully flushed to disk. - /// The address of the root node and the root hash is also persisted. - /// Note that this is *not* a write ahead log. /// It only contains the address of the nodes that are deleted, which should be very small. - /// 3. Revision reaping. If more than the maximum number of revisions are kept in memory, the + /// 2. Revision reaping. If more than the maximum number of revisions are kept in memory, the /// oldest revision is reaped. - /// 4. Persist to disk. This includes flushing everything to disk. - /// 5. Persist the revision to `RootStore`. - /// 6. Set last committed revision. + /// 3. Persist to disk. This includes flushing everything to disk. + /// 4. Persist the revision to `RootStore`. + /// 5. Set last committed revision. /// Set last committed revision in memory. - /// 7. Proposal Cleanup. + /// 6. Proposal Cleanup. /// Any other proposals that have this proposal as a parent should be reparented to the committed version. #[fastrace::trace(short_name = true)] #[crate::metrics("firewood.proposal.commit", "proposal commit to storage")] @@ -192,9 +188,8 @@ impl RevisionManager { let mut committed = proposal.as_committed(¤t_revision); - // 2. Persist delete list for this committed revision to disk for recovery - - // 3 Take the deleted entries from the oldest revision and mark them as free for this revision + // 2. Revision reaping + // Take the deleted entries from the oldest revision and mark them as free for this revision // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free while self.historical.read().expect("poisoned lock").len() >= self.max_revisions { @@ -232,19 +227,19 @@ impl RevisionManager { gauge!("firewood.max_revisions").set(self.max_revisions as f64); } - // 4. Persist to disk. + // 3. Persist to disk. // TODO: We can probably do this in another thread, but it requires that // we move the header out of NodeStore, which is in a future PR. committed.persist()?; - // 5. Persist revision to root store + // 4. Persist revision to root store if let (Some(hash), Some(address)) = (committed.root_hash(), committed.root_address()) { self.root_store .add_root(&hash, &address) .map_err(RevisionManagerError::RootStoreError)?; } - // 6. Set last committed revision + // 5. Set last committed revision let committed: CommittedRevision = committed.into(); self.historical .write() @@ -257,7 +252,7 @@ impl RevisionManager { .insert(hash, committed.clone()); } - // 7. Proposal Cleanup + // 6. Proposal Cleanup // Free proposal that is being committed as well as any proposals no longer // referenced by anyone else. self.proposals From 8165c36e4cc18e4e6d53668947a54ef50fbbe655 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:30:03 -0500 Subject: [PATCH 1029/1053] perf(ffi)!: Use fixed size Hash (#1449) There's no sense casting between byte slices and fixed sizes all the time, and it only encourages bad usage. I expect a teeny tiny performance win out of this, but more this is a point of style. The root is still passed to Rust as a slice, but everything else is now stored in Go as a fixed length array. One potentially unnecessary side effect is a change to the interface for Range Proofs, since I don't know why you would possibly want a "most recent range proof" anyway. Potential future work may include optimizing the FFI layer to actually use these fixed length arrays, rather than typecasting to slices. --------- Signed-off-by: Austin Larson <78000745+alarso16@users.noreply.github.com> Co-authored-by: Joachim Brandon LeBlanc Co-authored-by: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> --- ffi/firewood.go | 50 ++++++----------- ffi/firewood.h | 19 ++++--- ffi/firewood_test.go | 54 ++++++------------- ffi/memory.go | 18 ++++--- ffi/proofs.go | 43 +++++++-------- ffi/proofs_test.go | 48 ++++++----------- ffi/proposal.go | 8 +-- ffi/revision.go | 13 +++-- ffi/src/lib.rs | 10 ++-- ffi/src/proofs/change.rs | 10 ++-- ffi/src/proofs/range.rs | 18 ++----- ffi/tests/eth/eth_compatibility_test.go | 2 +- .../firewood/merkle_compatibility_test.go | 7 +-- 13 files changed, 122 insertions(+), 178 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index 5fc00ad0595e..0b0877d69248 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -26,7 +26,6 @@ package ffi import "C" import ( - "bytes" "context" "errors" "fmt" @@ -35,15 +34,13 @@ import ( "time" ) -// These constants are used to identify errors returned by the Firewood Rust FFI. -// These must be changed if the Rust FFI changes - should be reported by tests. -const ( - RootLength = C.sizeof_HashKey -) +const RootLength = C.sizeof_HashKey + +type Hash [RootLength]byte var ( + EmptyRoot Hash errDBClosed = errors.New("firewood database already closed") - EmptyRoot = make([]byte, RootLength) ) // A Database is a handle to a Firewood database. @@ -132,9 +129,9 @@ func New(filePath string, conf *Config) (*Database, error) { // // WARNING: Calling Update with an empty key and nil value will delete the entire database // due to prefix deletion semantics. -func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { +func (db *Database) Update(keys, vals [][]byte) (Hash, error) { if db.handle == nil { - return nil, errDBClosed + return EmptyRoot, errDBClosed } var pinner runtime.Pinner @@ -142,7 +139,7 @@ func (db *Database) Update(keys, vals [][]byte) ([]byte, error) { kvp, err := newKeyValuePairs(keys, vals, &pinner) if err != nil { - return nil, err + return EmptyRoot, err } return getHashKeyFromHashResult(C.fwd_batch(db.handle, kvp)) @@ -194,13 +191,13 @@ func (db *Database) Get(key []byte) ([]byte, error) { // GetFromRoot retrieves the value for the given key from a specific root hash. // If the root is not found, it returns an error. // If key is not found, it returns (nil, nil). -func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { +func (db *Database) GetFromRoot(root Hash, key []byte) ([]byte, error) { if db.handle == nil { return nil, errDBClosed } // If the root is empty, the database is empty. - if len(root) == 0 || bytes.Equal(root, EmptyRoot) { + if root == EmptyRoot { return nil, nil } @@ -209,25 +206,19 @@ func (db *Database) GetFromRoot(root, key []byte) ([]byte, error) { return getValueFromValueResult(C.fwd_get_from_root( db.handle, - newBorrowedBytes(root, &pinner), + newCHashKey(root), newBorrowedBytes(key, &pinner), )) } // Root returns the current root hash of the trie. -// Empty trie must return common.Hash{}. -func (db *Database) Root() ([]byte, error) { +// Empty trie must return common.EmptyRoot. +func (db *Database) Root() (Hash, error) { if db.handle == nil { - return nil, errDBClosed + return EmptyRoot, errDBClosed } - bytes, err := getHashKeyFromHashResult(C.fwd_root_hash(db.handle)) - - // If the root hash is not found, return a zeroed slice. - if err == nil && bytes == nil { - bytes = EmptyRoot - } - return bytes, err + return getHashKeyFromHashResult(C.fwd_root_hash(db.handle)) } func (db *Database) LatestRevision() (*Revision, error) { @@ -235,24 +226,17 @@ func (db *Database) LatestRevision() (*Revision, error) { if err != nil { return nil, err } - if bytes.Equal(root, EmptyRoot) { + if root == EmptyRoot { return nil, errRevisionNotFound } return db.Revision(root) } // Revision returns a historical revision of the database. -func (db *Database) Revision(root []byte) (*Revision, error) { - if root == nil || len(root) != RootLength { - return nil, errInvalidRootLength - } - - var pinner runtime.Pinner - defer pinner.Unpin() - +func (db *Database) Revision(root Hash) (*Revision, error) { rev, err := getRevisionFromResult(C.fwd_get_revision( db.handle, - newBorrowedBytes(root, &pinner), + newCHashKey(root), ), &db.outstandingHandles) if err != nil { return nil, err diff --git a/ffi/firewood.h b/ffi/firewood.h index 46291149cbe3..242ee7779105 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -459,13 +459,13 @@ typedef struct CreateChangeProofArgs { * If the root is not found in the database, the function will return * [`ChangeProofResult::RevisionNotFound`]. */ - BorrowedBytes start_root; + struct HashKey start_root; /** * The root hash of the ending revision. This must be provided. * If the root is not found in the database, the function will return * [`ChangeProofResult::RevisionNotFound`]. */ - BorrowedBytes end_root; + struct HashKey end_root; /** * The start key of the range to create the proof for. If `None`, the range * starts from the beginning of the keyspace. @@ -547,10 +547,9 @@ typedef struct RangeProofResult { */ typedef struct CreateRangeProofArgs { /** - * The root hash of the revision to prove. If `None`, the latest revision - * is used. + * The root hash of the revision to prove. */ - struct Maybe_BorrowedBytes root; + struct HashKey root; /** * The start key of the range to prove. If `None`, the range starts from the * beginning of the keyspace. @@ -589,12 +588,12 @@ typedef struct VerifyChangeProofArgs { * The root hash of the starting revision. This must match the starting * root of the proof. */ - BorrowedBytes start_root; + struct HashKey start_root; /** * The root hash of the ending revision. This must match the ending root of * the proof. */ - BorrowedBytes end_root; + struct HashKey end_root; /** * The lower bound of the key range that the proof is expected to cover. If * `None`, the proof is expected to cover from the start of the keyspace. @@ -627,7 +626,7 @@ typedef struct VerifyRangeProofArgs { * The root hash to verify the proof against. This must match the calculated * hash of the root of the proof. */ - BorrowedBytes root; + struct HashKey root; /** * The lower bound of the key range that the proof is expected to cover. If * `None`, the proof is expected to cover from the start of the keyspace. @@ -1588,7 +1587,7 @@ struct ValueResult fwd_get_from_revision(const struct RevisionHandle *revision, * returned in the result. */ struct ValueResult fwd_get_from_root(const struct DatabaseHandle *db, - BorrowedBytes root, + struct HashKey root, BorrowedBytes key); /** @@ -1645,7 +1644,7 @@ struct ValueResult fwd_get_latest(const struct DatabaseHandle *db, BorrowedBytes * [`BorrowedBytes`]: crate::value::BorrowedBytes * [`RevisionHandle`]: crate::revision::RevisionHandle */ -struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, BorrowedBytes root); +struct RevisionResult fwd_get_revision(const struct DatabaseHandle *db, struct HashKey root); /** * Retrieves the next item from the iterator. diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 6b2c3c391703..f4131a114dab 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -60,6 +60,14 @@ var ( expectedRoots map[string]string ) +func stringToHash(t *testing.T, s string) Hash { + t.Helper() + b, err := hex.DecodeString(s) + require.NoError(t, err) + require.Len(t, b, RootLength) + return Hash(b) +} + func inferHashingMode(ctx context.Context) (string, error) { dbFile := filepath.Join(os.TempDir(), "test.db") db, err := newDatabase(dbFile) @@ -75,7 +83,7 @@ func inferHashingMode(ctx context.Context) (string, error) { if err != nil { return "", fmt.Errorf("failed to get root of empty database: %w", err) } - actualEmptyRootHex := hex.EncodeToString(actualEmptyRoot) + actualEmptyRootHex := hex.EncodeToString(actualEmptyRoot[:]) actualFwMode, ok := expectedEmptyRootToMode[actualEmptyRootHex] if !ok { @@ -206,9 +214,7 @@ func TestTruncateDatabase(t *testing.T) { // Check that the database is empty after truncation. hash, err := db.Root() r.NoError(err) - emptyRootStr := expectedRoots[emptyKey] - expectedHash, err := hex.DecodeString(emptyRootStr) - r.NoError(err) + expectedHash := stringToHash(t, expectedRoots[emptyKey]) r.Equal(expectedHash, hash, "Root hash mismatch after truncation") r.NoError(db.Close(t.Context())) @@ -294,7 +300,7 @@ func TestInsert100(t *testing.T) { type dbView interface { Get(key []byte) ([]byte, error) Propose(keys, vals [][]byte) (*Proposal, error) - Root() ([]byte, error) + Root() (Hash, error) } tests := []struct { @@ -358,18 +364,12 @@ func TestInsert100(t *testing.T) { hash, err := newDB.Root() r.NoError(err) - rootFromInsert, err := newDB.Root() - r.NoError(err) - // Assert the hash is exactly as expected. Test failure indicates a // non-hash compatible change has been made since the string was set. // If that's expected, update the string at the top of the file to // fix this test. - expectedHashHex := expectedRoots[insert100Key] - expectedHash, err := hex.DecodeString(expectedHashHex) - r.NoError(err) + expectedHash := stringToHash(t, expectedRoots[insert100Key]) r.Equal(expectedHash, hash, "Root hash mismatch.\nExpected (hex): %x\nActual (hex): %x", expectedHash, hash) - r.Equal(rootFromInsert, hash) }) } } @@ -405,9 +405,7 @@ func TestInvariants(t *testing.T) { hash, err := db.Root() r.NoError(err) - emptyRootStr := expectedRoots[emptyKey] - expectedHash, err := hex.DecodeString(emptyRootStr) - r.NoError(err) + expectedHash := stringToHash(t, expectedRoots[emptyKey]) r.Equalf(expectedHash, hash, "expected %x, got %x", expectedHash, hash) got, err := db.Get([]byte("non-existent")) @@ -514,10 +512,7 @@ func TestDeleteAll(t *testing.T) { r.Empty(got, "Get(%d)", i) } - emptyRootStr := expectedRoots[emptyKey] - expectedHash, err := hex.DecodeString(emptyRootStr) - r.NoError(err, "Decode expected empty root hash") - + expectedHash := stringToHash(t, expectedRoots[emptyKey]) hash, err := proposal.Root() r.NoError(err, "%T.Root() after commit", proposal) r.Equalf(expectedHash, hash, "%T.Root() of empty trie", db) @@ -937,19 +932,9 @@ func TestInvalidRevision(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - // Create a nil revision. - _, err := db.Revision(nil) - r.ErrorIs(err, errInvalidRootLength) - - // Create a fake revision with an invalid root. - invalidRoot := []byte("not a valid root") - _, err = db.Revision(invalidRoot) - r.ErrorIs(err, errInvalidRootLength) - // Create a fake revision with an valid root. - validRoot := []byte("counting 32 bytes to make a hash") - r.Len(validRoot, 32, "valid root") - _, err = db.Revision(validRoot) + validRoot := Hash([]byte("counting 32 bytes to make a hash")) + _, err := db.Revision(validRoot) r.ErrorIs(err, errRevisionNotFound, "Revision(valid root)") } @@ -1088,13 +1073,8 @@ func TestGetFromRoot(t *testing.T) { r.Equal(vals[i+5], got, "GetFromRoot root1 newer key %d", i) } - // Test with invalid root hash - invalidRoot := []byte("this is not a valid 32-byte hash") - _, err = db.GetFromRoot(invalidRoot, []byte("key")) - r.Error(err, "GetFromRoot with invalid root should return error") - // Test with valid-length but non-existent root - nonExistentRoot := make([]byte, RootLength) + var nonExistentRoot Hash for i := range nonExistentRoot { nonExistentRoot[i] = 0xFF // All 1's, very unlikely to exist } diff --git a/ffi/memory.go b/ffi/memory.go index d97a8bf2f341..7e117e6cb9ad 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -247,21 +247,21 @@ func newOwnedBytes(owned C.OwnedBytes) *ownedBytes { // It returns nil, nil if the result is None. // It returns nil, err if the result is an error. // It returns a byte slice, nil if the result is Some. -func getHashKeyFromHashResult(result C.HashResult) ([]byte, error) { +func getHashKeyFromHashResult(result C.HashResult) (Hash, error) { switch result.tag { case C.HashResult_NullHandlePointer: - return nil, errDBClosed + return EmptyRoot, errDBClosed case C.HashResult_None: - return nil, nil + return EmptyRoot, nil case C.HashResult_Some: cHashKey := (*C.HashKey)(unsafe.Pointer(&result.anon0)) - hashKey := *(*[32]byte)(unsafe.Pointer(&cHashKey._0)) - return hashKey[:], nil + hashKey := *(*Hash)(unsafe.Pointer(&cHashKey._0)) + return hashKey, nil case C.HashResult_Err: ownedBytes := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))) - return nil, ownedBytes.intoError() + return EmptyRoot, ownedBytes.intoError() default: - return nil, fmt.Errorf("unknown C.HashResult tag: %d", result.tag) + return EmptyRoot, fmt.Errorf("unknown C.HashResult tag: %d", result.tag) } } @@ -461,3 +461,7 @@ func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) { return nil, fmt.Errorf("unknown C.HandleResult tag: %d", result.tag) } } + +func newCHashKey(hash Hash) C.HashKey { + return *(*C.HashKey)(unsafe.Pointer(&hash)) +} diff --git a/ffi/proofs.go b/ffi/proofs.go index 6373619f2ddf..94bc7d7a80cd 100644 --- a/ffi/proofs.go +++ b/ffi/proofs.go @@ -44,7 +44,8 @@ type NextKeyRange struct { // Nothing, the range is unbounded in that direction. If [rootHash] is Nothing, the // current root of the database is used. func (db *Database) RangeProof( - rootHash, startKey, endKey Maybe[[]byte], + rootHash Hash, + startKey, endKey Maybe[[]byte], maxLength uint32, ) (*RangeProof, error) { if db.handle == nil { @@ -55,7 +56,7 @@ func (db *Database) RangeProof( defer pinner.Unpin() args := C.CreateRangeProofArgs{ - root: newMaybeBorrowedBytes(rootHash, &pinner), + root: newCHashKey(rootHash), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -69,7 +70,7 @@ func (db *Database) RangeProof( // proof is valid, nil is returned; otherwise an error describing why the proof is // invalid is returned. func (p *RangeProof) Verify( - rootHash []byte, + rootHash Hash, startKey, endKey Maybe[[]byte], maxLength uint32, ) error { @@ -78,7 +79,7 @@ func (p *RangeProof) Verify( args := C.VerifyRangeProofArgs{ proof: p.handle, - root: newBorrowedBytes(rootHash, &pinner), + root: newCHashKey(rootHash), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -95,7 +96,7 @@ func (p *RangeProof) Verify( func (db *Database) VerifyRangeProof( proof *RangeProof, startKey, endKey Maybe[[]byte], - rootHash []byte, + rootHash Hash, maxLength uint32, ) error { var pinner runtime.Pinner @@ -103,7 +104,7 @@ func (db *Database) VerifyRangeProof( args := C.VerifyRangeProofArgs{ proof: proof.handle, - root: newBorrowedBytes(rootHash, &pinner), + root: newCHashKey(rootHash), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -120,11 +121,11 @@ func (db *Database) VerifyRangeProof( func (db *Database) VerifyAndCommitRangeProof( proof *RangeProof, startKey, endKey Maybe[[]byte], - rootHash []byte, + rootHash Hash, maxLength uint32, -) ([]byte, error) { +) (Hash, error) { if db.handle == nil { - return nil, errDBClosed + return EmptyRoot, errDBClosed } var pinner runtime.Pinner @@ -132,7 +133,7 @@ func (db *Database) VerifyAndCommitRangeProof( args := C.VerifyRangeProofArgs{ proof: proof.handle, - root: newBorrowedBytes(rootHash, &pinner), + root: newCHashKey(rootHash), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -202,7 +203,7 @@ func (p *RangeProof) Free() error { // truncated to at most [maxLength] entries, if non-zero. If either [startKey] or // [endKey] is Nothing, the range is unbounded in that direction. func (db *Database) ChangeProof( - startRoot, endRoot []byte, + startRoot, endRoot Hash, startKey, endKey Maybe[[]byte], maxLength uint32, ) (*ChangeProof, error) { @@ -214,8 +215,8 @@ func (db *Database) ChangeProof( defer pinner.Unpin() args := C.CreateChangeProofArgs{ - start_root: newBorrowedBytes(startRoot, &pinner), - end_root: newBorrowedBytes(endRoot, &pinner), + start_root: newCHashKey(startRoot), + end_root: newCHashKey(endRoot), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -231,7 +232,7 @@ func (db *Database) ChangeProof( // prepared proposal. func (db *Database) VerifyChangeProof( proof *ChangeProof, - startRoot, endRoot []byte, + startRoot, endRoot Hash, startKey, endKey Maybe[[]byte], maxLength uint32, ) error { @@ -240,8 +241,8 @@ func (db *Database) VerifyChangeProof( args := C.VerifyChangeProofArgs{ proof: proof.handle, - start_root: newBorrowedBytes(startRoot, &pinner), - end_root: newBorrowedBytes(endRoot, &pinner), + start_root: newCHashKey(startRoot), + end_root: newCHashKey(endRoot), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), @@ -257,12 +258,12 @@ func (db *Database) VerifyChangeProof( // truncated due to [maxLength]. func (db *Database) VerifyAndCommitChangeProof( proof *ChangeProof, - startRoot, endRoot []byte, + startRoot, endRoot Hash, startKey, endKey Maybe[[]byte], maxLength uint32, -) ([]byte, error) { +) (Hash, error) { if db.handle == nil { - return nil, errDBClosed + return EmptyRoot, errDBClosed } var pinner runtime.Pinner @@ -270,8 +271,8 @@ func (db *Database) VerifyAndCommitChangeProof( args := C.VerifyChangeProofArgs{ proof: proof.handle, - start_root: newBorrowedBytes(startRoot, &pinner), - end_root: newBorrowedBytes(endRoot, &pinner), + start_root: newCHashKey(startRoot), + end_root: newCHashKey(endRoot), start_key: newMaybeBorrowedBytes(startKey, &pinner), end_key: newMaybeBorrowedBytes(endKey, &pinner), max_length: C.uint32_t(maxLength), diff --git a/ffi/proofs_test.go b/ffi/proofs_test.go index 5b58bea253f1..1f19c8f74531 100644 --- a/ffi/proofs_test.go +++ b/ffi/proofs_test.go @@ -41,8 +41,8 @@ func TestRangeProofEmptyDB(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - proof, err := db.RangeProof(nothing(), nothing(), nothing(), 0) - r.ErrorIs(err, errEmptyTrie) + proof, err := db.RangeProof(EmptyRoot, nothing(), nothing(), 0) + r.ErrorIs(err, errRevisionNotFound) r.Nil(proof) } @@ -54,14 +54,11 @@ func TestRangeProofNonExistentRoot(t *testing.T) { keys, vals := kvForTest(100) root, err := db.Update(keys, vals) r.NoError(err) - r.NotNil(root) // create a bogus root - bogusRoot := make([]byte, len(root)) - copy(bogusRoot, root) - bogusRoot[0] ^= 0xFF + root[0] ^= 0xFF - proof, err := db.RangeProof(something(bogusRoot), nothing(), nothing(), 0) + proof, err := db.RangeProof(root, nothing(), nothing(), 0) r.ErrorIs(err, errRevisionNotFound) r.Nil(proof) } @@ -76,10 +73,10 @@ func TestRangeProofPartialRange(t *testing.T) { r.NoError(err) // get a proof over some partial range - proof1 := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing()) + proof1 := rangeProof(t, db, root, nothing(), nothing()) // get a proof over a different range - proof2 := rangeProofWithAndWithoutRoot(t, db, root, something([]byte("key2")), something([]byte("key3"))) + proof2 := rangeProof(t, db, root, something([]byte("key2")), something([]byte("key3"))) // ensure the proofs are different r.NotEqual(proof1, proof2) @@ -97,7 +94,7 @@ func TestRangeProofDiffersAfterUpdate(t *testing.T) { r.NoError(err) // get a proof - proof := rangeProofWithAndWithoutRoot(t, db, root1, nothing(), nothing()) + proof := rangeProof(t, db, root1, nothing(), nothing()) // insert more data root2, err := db.Update(keys[50:], vals[50:]) @@ -105,7 +102,7 @@ func TestRangeProofDiffersAfterUpdate(t *testing.T) { r.NotEqual(root1, root2) // get a proof again - proof2 := rangeProofWithAndWithoutRoot(t, db, root2, nothing(), nothing()) + proof2 := rangeProof(t, db, root2, nothing(), nothing()) // ensure the proofs are different r.NotEqual(proof, proof2) @@ -121,7 +118,7 @@ func TestRoundTripSerialization(t *testing.T) { r.NoError(err) // get a proof - proofBytes := rangeProofWithAndWithoutRoot(t, db, root, nothing(), nothing()) + proofBytes := rangeProof(t, db, root, nothing(), nothing()) // Deserialize the proof. proof := new(RangeProof) @@ -136,32 +133,21 @@ func TestRoundTripSerialization(t *testing.T) { r.NoError(proof.Free()) } -// rangeProofWithAndWithoutRoot checks that requesting a range proof with and -// without the root, when the default root is the same as the provided root, -// yields the same proof and returns the proof bytes. -func rangeProofWithAndWithoutRoot( +// rangeProof generates a range proof for the given parameters. +func rangeProof( t *testing.T, db *Database, - root []byte, + root Hash, startKey, endKey maybe, ) []byte { r := require.New(t) - proof1, err := db.RangeProof(maybe{hasValue: false}, startKey, endKey, maxProofLen) - r.NoError(err) - r.NotNil(proof1) - proof1Bytes, err := proof1.MarshalBinary() - r.NoError(err) - r.NoError(proof1.Free()) - - proof2, err := db.RangeProof(maybe{hasValue: true, value: root}, startKey, endKey, maxProofLen) + proof, err := db.RangeProof(root, startKey, endKey, maxProofLen) r.NoError(err) - r.NotNil(proof2) - proof2Bytes, err := proof2.MarshalBinary() + r.NotNil(proof) + proofBytes, err := proof.MarshalBinary() r.NoError(err) - r.NoError(proof2.Free()) - - r.Equal(proof1Bytes, proof2Bytes) + r.NoError(proof.Free()) - return proof1Bytes + return proofBytes } diff --git a/ffi/proposal.go b/ffi/proposal.go index 51ac6369b2cf..c04fa8b153dd 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -36,13 +36,13 @@ type Proposal struct { outstandingHandles *sync.WaitGroup // The proposal root hash. - root []byte + root Hash } // Root retrieves the root hash of the proposal. // If the proposal is empty (i.e. no keys in database), // it returns nil, nil. -func (p *Proposal) Root() ([]byte, error) { +func (p *Proposal) Root() (Hash, error) { return p.root, nil } @@ -152,10 +152,10 @@ func getProposalFromProposalResult(result C.ProposalResult, outstandingHandles * return nil, errDBClosed case C.ProposalResult_Ok: body := (*C.ProposalResult_Ok_Body)(unsafe.Pointer(&result.anon0)) - hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) + hashKey := *(*Hash)(unsafe.Pointer(&body.root_hash._0)) proposal := &Proposal{ handle: body.handle, - root: hashKey[:], + root: hashKey, outstandingHandles: outstandingHandles, } outstandingHandles.Add(1) diff --git a/ffi/revision.go b/ffi/revision.go index 96a5a911727f..7ffc270b1348 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -19,9 +19,8 @@ import ( ) var ( - errRevisionNotFound = errors.New("revision not found") - errInvalidRootLength = fmt.Errorf("root hash must be %d bytes", RootLength) - errDroppedRevision = errors.New("revision already dropped") + errRevisionNotFound = errors.New("revision not found") + errDroppedRevision = errors.New("revision already dropped") ) // Revision is an immutable view over the database at a specific root hash. @@ -33,7 +32,7 @@ type Revision struct { handle *C.RevisionHandle disown sync.Mutex // The revision root - root []byte + root Hash // outstandingHandles is the WaitGroup in Database that tracks open Rust handles. // This is incremented when the revision is created, and decremented when Drop is called. outstandingHandles *sync.WaitGroup @@ -93,7 +92,7 @@ func (r *Revision) Drop() error { return nil } -func (r *Revision) Root() []byte { +func (r *Revision) Root() Hash { return r.root } @@ -106,10 +105,10 @@ func getRevisionFromResult(result C.RevisionResult, openProposals *sync.WaitGrou return nil, errRevisionNotFound case C.RevisionResult_Ok: body := (*C.RevisionResult_Ok_Body)(unsafe.Pointer(&result.anon0)) - hashKey := *(*[32]byte)(unsafe.Pointer(&body.root_hash._0)) + hashKey := *(*Hash)(unsafe.Pointer(&body.root_hash._0)) rev := &Revision{ handle: body.handle, - root: hashKey[:], + root: hashKey, outstandingHandles: openProposals, } openProposals.Add(1) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 79daac92a9ec..ffa05ba4152f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -283,9 +283,9 @@ pub unsafe extern "C" fn fwd_free_iterator( #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_revision( db: Option<&DatabaseHandle>, - root: BorrowedBytes, + root: HashKey, ) -> RevisionResult { - invoke_with_handle(db, move |db| db.get_revision(root.as_ref().try_into()?)) + invoke_with_handle(db, move |db| db.get_revision(root.into())) } /// Gets the value associated with the given key from the provided revision handle. @@ -398,12 +398,10 @@ pub unsafe extern "C" fn fwd_get_from_proposal( #[unsafe(no_mangle)] pub unsafe extern "C" fn fwd_get_from_root( db: Option<&DatabaseHandle>, - root: BorrowedBytes, + root: HashKey, key: BorrowedBytes, ) -> ValueResult { - invoke_with_handle(db, move |db| { - db.get_from_root(root.as_ref().try_into()?, key) - }) + invoke_with_handle(db, move |db| db.get_from_root(root.into(), key)) } /// Puts the given key-value pairs into the database. diff --git a/ffi/src/proofs/change.rs b/ffi/src/proofs/change.rs index 3db1903392c4..ae38d9ad99af 100644 --- a/ffi/src/proofs/change.rs +++ b/ffi/src/proofs/change.rs @@ -2,7 +2,7 @@ // See the file LICENSE.md for licensing terms. use crate::{ - BorrowedBytes, CResult, ChangeProofResult, DatabaseHandle, HashResult, Maybe, + BorrowedBytes, CResult, ChangeProofResult, DatabaseHandle, HashKey, HashResult, Maybe, NextKeyRangeResult, OwnedBytes, ValueResult, VoidResult, }; @@ -13,11 +13,11 @@ pub struct CreateChangeProofArgs<'a> { /// The root hash of the starting revision. This must be provided. /// If the root is not found in the database, the function will return /// [`ChangeProofResult::RevisionNotFound`]. - pub start_root: BorrowedBytes<'a>, + pub start_root: HashKey, /// The root hash of the ending revision. This must be provided. /// If the root is not found in the database, the function will return /// [`ChangeProofResult::RevisionNotFound`]. - pub end_root: BorrowedBytes<'a>, + pub end_root: HashKey, /// The start key of the range to create the proof for. If `None`, the range /// starts from the beginning of the keyspace. pub start_key: Maybe>, @@ -41,10 +41,10 @@ pub struct VerifyChangeProofArgs<'a> { pub proof: Option<&'a mut ChangeProofContext>, /// The root hash of the starting revision. This must match the starting /// root of the proof. - pub start_root: BorrowedBytes<'a>, + pub start_root: HashKey, /// The root hash of the ending revision. This must match the ending root of /// the proof. - pub end_root: BorrowedBytes<'a>, + pub end_root: HashKey, /// The lower bound of the key range that the proof is expected to cover. If /// `None`, the proof is expected to cover from the start of the keyspace. pub start_key: Maybe>, diff --git a/ffi/src/proofs/range.rs b/ffi/src/proofs/range.rs index 34dd32231063..0fbd55b9cf53 100644 --- a/ffi/src/proofs/range.rs +++ b/ffi/src/proofs/range.rs @@ -6,7 +6,7 @@ use std::num::NonZeroUsize; use firewood::v2::api::{self, FrozenRangeProof}; use crate::{ - BorrowedBytes, CResult, DatabaseHandle, HashResult, Maybe, NextKeyRangeResult, + BorrowedBytes, CResult, DatabaseHandle, HashKey, HashResult, Maybe, NextKeyRangeResult, RangeProofResult, ValueResult, VoidResult, }; @@ -14,9 +14,8 @@ use crate::{ #[derive(Debug)] #[repr(C)] pub struct CreateRangeProofArgs<'a> { - /// The root hash of the revision to prove. If `None`, the latest revision - /// is used. - pub root: Maybe>, + /// The root hash of the revision to prove. + pub root: HashKey, /// The start key of the range to prove. If `None`, the range starts from the /// beginning of the keyspace. /// @@ -45,7 +44,7 @@ pub struct VerifyRangeProofArgs<'a> { pub proof: Option<&'a mut RangeProofContext>, /// The root hash to verify the proof against. This must match the calculated /// hash of the root of the proof. - pub root: BorrowedBytes<'a>, + pub root: HashKey, /// The lower bound of the key range that the proof is expected to cover. If /// `None`, the proof is expected to cover from the start of the keyspace. /// @@ -110,14 +109,7 @@ pub extern "C" fn fwd_db_range_proof( args: CreateRangeProofArgs, ) -> RangeProofResult { crate::invoke_with_handle(db, |db| { - let root_hash = match args.root { - Maybe::Some(root) => root.as_ref().try_into()?, - Maybe::None => db - .current_root_hash()? - .ok_or(api::Error::RangeProofOnEmptyTrie)?, - }; - - let view = db.get_root(root_hash)?; + let view = db.get_root(args.root.into())?; view.range_proof( args.start_key .as_ref() diff --git a/ffi/tests/eth/eth_compatibility_test.go b/ffi/tests/eth/eth_compatibility_test.go index 2fbf987c5883..9f4218068566 100644 --- a/ffi/tests/eth/eth_compatibility_test.go +++ b/ffi/tests/eth/eth_compatibility_test.go @@ -134,7 +134,7 @@ func (tr *merkleTriePair) commit() { fwdRoot, err := tr.fwdDB.Update(tr.pendingFwdKeys, tr.pendingFwdVals) tr.require.NoError(err) - tr.require.Equal(fwdRoot, updatedRoot[:]) + tr.require.Equal(updatedRoot, common.Hash(fwdRoot)) tr.pendingFwdKeys = nil tr.pendingFwdVals = nil diff --git a/ffi/tests/firewood/merkle_compatibility_test.go b/ffi/tests/firewood/merkle_compatibility_test.go index 76a45c6a8f79..a3df63d9f071 100644 --- a/ffi/tests/firewood/merkle_compatibility_test.go +++ b/ffi/tests/firewood/merkle_compatibility_test.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/x/merkledb" "github.com/stretchr/testify/require" ) @@ -235,7 +236,7 @@ func (tr *tree) checkDBHash() { tr.require.NoError(err) // Compare the root hashes. - tr.require.Equal(merkleRoot[:], fwdRoot) + tr.require.Equal(merkleRoot, ids.ID(fwdRoot)) } func (tr *tree) createProposalOnProposal() { @@ -268,7 +269,7 @@ func (tr *tree) createProposalOnProposal() { tr.require.NoError(err) merkleRoot, err := merkleChildView.GetMerkleRoot(context.Background()) tr.require.NoError(err) - tr.require.Equal(fwdRoot, merkleRoot[:]) + tr.require.Equal(merkleRoot, ids.ID(fwdRoot)) tr.nextID++ newProposal := &proposal{ @@ -301,7 +302,7 @@ func (tr *tree) createProposalOnDB() { tr.require.NoError(err) merkleRoot, err := merkleChildView.GetMerkleRoot(context.Background()) tr.require.NoError(err) - tr.require.Equal(fwdRoot, merkleRoot[:]) + tr.require.Equal(merkleRoot, ids.ID(fwdRoot)) tr.nextID++ newProposal := &proposal{ From bac68f5966cfd8bf755b530f9ef670e80c6864ee Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Fri, 14 Nov 2025 09:17:12 -0800 Subject: [PATCH 1030/1053] feat(ffi): add keepalive struct to own waitgroup logic (#1437) Expanding on Arran and Austin's work, I added the `databaseKeepAliveHandle` to manage the WaitGroup logic for keeping the database alive while various objects needed. In the future, the `RangeProof` object will also use this keep-alive handle, but only during certain stages of its lifecycle. Because of this, the disown method on the keep-alive handle is capable of being called on the nil receiver without causing a panic. --- ffi/firewood.go | 16 +++-- ffi/firewood_test.go | 160 +++++++++++++++++++++++++++++++++++++++++++ ffi/keepalive.go | 61 +++++++++++++++++ ffi/proposal.go | 88 ++++++++++++------------ ffi/revision.go | 62 +++++++++-------- 5 files changed, 308 insertions(+), 79 deletions(-) create mode 100644 ffi/keepalive.go diff --git a/ffi/firewood.go b/ffi/firewood.go index 0b0877d69248..b9c13c1ce2bc 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -39,8 +39,9 @@ const RootLength = C.sizeof_HashKey type Hash [RootLength]byte var ( - EmptyRoot Hash - errDBClosed = errors.New("firewood database already closed") + EmptyRoot Hash + errDBClosed = errors.New("firewood database already closed") + ErrActiveKeepAliveHandles = errors.New("cannot close database with active keep-alive handles") ) // A Database is a handle to a Firewood database. @@ -252,10 +253,11 @@ var defaultCloseTimeout = time.Minute // Close releases the memory associated with the Database. // -// This blocks until all outstanding Proposals are either unreachable or one of -// [Proposal.Commit] or [Proposal.Drop] has been called on them. Unreachable -// proposals will be automatically dropped before Close returns, unless an -// alternate GC finalizer is set on them. +// This blocks until all outstanding keep-alive handles are disowned. That is, +// until all Revisions and Proposals created from this Database are either +// unreachable or one of [Proposal.Commit], [Proposal.Drop], or [Revision.Drop] +// has been called on them. Unreachable objects will be automatically dropped +// before Close returns, unless an alternate GC finalizer is set on them. // // This is safe to call if the handle pointer is nil, in which case it does // nothing. The pointer will be set to nil after freeing to prevent double free. @@ -279,7 +281,7 @@ func (db *Database) Close(ctx context.Context) error { select { case <-done: case <-ctx.Done(): - return fmt.Errorf("at least one reachable %T neither dropped nor committed", &Proposal{}) + return ErrActiveKeepAliveHandles } if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index f4131a114dab..904a5a8fbf9f 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1566,3 +1566,163 @@ func TestNilVsEmptyValue(t *testing.T) { r.NotNil(got, "key4 should exist") r.Empty(got, "key4 should have empty value") } + +// TestCloseWithCancelledContext verifies that Database.Close returns +// ErrActiveKeepAliveHandles when the context is cancelled before handles are dropped. +func TestCloseWithCancelledContext(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + db, err := newDatabase(dbFile) + r.NoError(err) + + // Create a proposal to keep a handle active + keys, vals := kvForTest(1) + proposal, err := db.Propose(keys, vals) + r.NoError(err) + + ctx, cancel := context.WithCancel(t.Context()) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + err = db.Close(ctx) + }() + + cancel() + wg.Wait() + + r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles when context is cancelled") + + // Drop the proposal + r.NoError(proposal.Drop()) + + // Now Close should succeed + r.NoError(db.Close(t.Context())) +} + +// TestCloseWithShortTimeout verifies that Database.Close returns +// ErrActiveKeepAliveHandles when the context times out before handles are dropped. +func TestCloseWithShortTimeout(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + db, err := newDatabase(dbFile) + r.NoError(err) + + // Create a revision to keep a handle active + keys, vals := kvForTest(1) + root, err := db.Update(keys, vals) + r.NoError(err) + + revision, err := db.Revision(root) + r.NoError(err) + + // Create a context with a very short timeout (100ms) + ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) + defer cancel() + + // Record start time to verify timeout occurred quickly + start := time.Now() + + // Close should return ErrActiveKeepAliveHandles after timeout + err = db.Close(ctx) + elapsed := time.Since(start) + + r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles when context times out") + r.Less(elapsed, defaultCloseTimeout, "Close should timeout quickly, not wait for default timeout") + + // Drop the revision + r.NoError(revision.Drop()) + + // Now Close should succeed + r.NoError(db.Close(t.Context())) +} + +// TestCloseWithMultipleActiveHandles verifies that Database.Close returns +// ErrActiveKeepAliveHandles when multiple handles are active and context is cancelled. +func TestCloseWithMultipleActiveHandles(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + db, err := newDatabase(dbFile) + r.NoError(err) + + // Create multiple proposals + keys1, vals1 := kvForTest(3) + proposal1, err := db.Propose(keys1[:1], vals1[:1]) + r.NoError(err) + proposal2, err := db.Propose(keys1[1:2], vals1[1:2]) + r.NoError(err) + proposal3, err := db.Propose(keys1[2:3], vals1[2:3]) + r.NoError(err) + + // Create multiple revisions + root1, err := db.Update([][]byte{keyForTest(10)}, [][]byte{valForTest(10)}) + r.NoError(err) + root2, err := db.Update([][]byte{keyForTest(20)}, [][]byte{valForTest(20)}) + r.NoError(err) + + revision1, err := db.Revision(root1) + r.NoError(err) + revision2, err := db.Revision(root2) + r.NoError(err) + + // Create a cancelled context + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + // Close should return ErrActiveKeepAliveHandles + err = db.Close(ctx) + r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles with multiple active handles") + + // Drop all handles + r.NoError(proposal1.Drop()) + r.NoError(proposal2.Drop()) + r.NoError(proposal3.Drop()) + r.NoError(revision1.Drop()) + r.NoError(revision2.Drop()) + + // Now Close should succeed + r.NoError(db.Close(t.Context())) +} + +// TestCloseSucceedsWhenHandlesDroppedInTime verifies that Database.Close succeeds +// when all handles are dropped before the context timeout. +func TestCloseSucceedsWhenHandlesDroppedInTime(t *testing.T) { + r := require.New(t) + dbFile := filepath.Join(t.TempDir(), "test.db") + db, err := newDatabase(dbFile) + r.NoError(err) + + // Create two active proposals + keys, vals := kvForTest(2) + proposal1, err := db.Propose(keys[:1], vals[:1]) + r.NoError(err) + proposal2, err := db.Propose(keys[1:2], vals[1:2]) + r.NoError(err) + + // Create a context with a reasonable timeout + ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond) + defer cancel() + + // Channel to receive Close result + closeDone := make(chan error, 1) + + // Start Close in a goroutine + go func() { + closeDone <- db.Close(ctx) + }() + + // Drop handles after a short delay (before timeout) + time.Sleep(100 * time.Millisecond) + r.NoError(proposal1.Drop()) + r.NoError(proposal2.Drop()) + + // Close should succeed (not timeout) + select { + case err := <-closeDone: + r.NoError(err, "Close should succeed when handles are dropped before timeout") + case <-time.After(defaultCloseTimeout): + r.Fail("Close did not complete in time") + } +} diff --git a/ffi/keepalive.go b/ffi/keepalive.go new file mode 100644 index 000000000000..458b88b6fc60 --- /dev/null +++ b/ffi/keepalive.go @@ -0,0 +1,61 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +package ffi + +import "sync" + +// databaseKeepAliveHandle is added to types that hold a lease on the database +// to ensure it is not closed while those types are still in use. +// +// This is necessary to prevent use-after-free bugs where a type holding a +// reference to the database outlives the database itself. Even attempting to +// free those objects after the database has been closed will lead to undefined +// behavior, as a part of the underling Rust object will have already been freed. +type databaseKeepAliveHandle struct { + mu sync.Mutex + // [Database.Close] blocks on this WaitGroup, which is set and incremented + // by [newKeepAliveHandle], and decremented by + // [databaseKeepAliveHandle.disown]. + outstandingHandles *sync.WaitGroup +} + +// init initializes the keep-alive handle to track a new outstanding handle. +func (h *databaseKeepAliveHandle) init(wg *sync.WaitGroup) { + // lock not necessary today, but will be necessary in the future for types + // that initialize the handle at some point after construction (#1429). + h.mu.Lock() + defer h.mu.Unlock() + + if h.outstandingHandles != nil { + // setting the finalizer twice will also panic, so we're panicking + // early to provide better context + panic("keep-alive handle already initialized") + } + + h.outstandingHandles = wg + h.outstandingHandles.Add(1) +} + +// disown indicates that the object owning this handle is no longer keeping the +// database alive. If [attemptDisown] returns an error, disowning will only occur +// if [disownEvenOnErr] is true. +// +// This method is safe to call multiple times; subsequent calls after the first +// will continue to invoke [attemptDisown] but will not decrement the wait group +// unless [databaseKeepAliveHandle.init] was called again in the meantime. +func (h *databaseKeepAliveHandle) disown(disownEvenOnErr bool, attemptDisown func() error) error { + h.mu.Lock() + defer h.mu.Unlock() + + err := attemptDisown() + + if (err == nil || disownEvenOnErr) && h.outstandingHandles != nil { + h.outstandingHandles.Done() + // prevent calling `Done` multiple times if disown is called again, which + // may happen when the finalizer runs after an explicit call to Drop or Commit. + h.outstandingHandles = nil + } + + return err +} diff --git a/ffi/proposal.go b/ffi/proposal.go index c04fa8b153dd..315de8ef6191 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -20,6 +20,15 @@ import ( var errDroppedProposal = errors.New("proposal already dropped") +// Proposal represents a set of proposed changes to be committed to the database. +// Proposals are created via [Database.Propose] or [Proposal.Propose], and must be +// either committed with [Proposal.Commit] or released with [Proposal.Drop]. +// +// Proposals must be committed or dropped before the associated database is +// closed. A finalizer is set on each Proposal to ensure that Drop is called +// when the Proposal is garbage collected, but relying on finalizers is not +// recommended. Failing to commit or drop a proposal before the database is +// closed will cause it to block or fail. type Proposal struct { // handle is an opaque pointer to the proposal within Firewood. It should be // passed to the C FFI functions that operate on proposals @@ -29,14 +38,14 @@ type Proposal struct { // Calls to `C.fwd_commit_proposal` and `C.fwd_free_proposal` will invalidate // this handle, so it should not be used after those calls. handle *C.ProposalHandle - disown sync.Mutex - // [Database.Close] blocks on this WaitGroup, which is incremented by - // [getProposalFromProposalResult], and decremented by either - // [Proposal.Commit] or [Proposal.Drop] (when the handle is disowned). - outstandingHandles *sync.WaitGroup - // The proposal root hash. + // root is the root hash of the proposal and the expected root hash after commit. root Hash + + // keepAliveHandle is used to keep the database alive while this proposal is + // in use. It is initialized when the proposal is created and disowned after + // [Proposal.Commit] or [Proposal.Drop] is called. + keepAliveHandle databaseKeepAliveHandle } // Root retrieves the root hash of the proposal. @@ -93,37 +102,25 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { if err != nil { return nil, err } - return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.outstandingHandles) -} - -// disownHandle is the common path of [Proposal.Commit] and [Proposal.Drop], the -// `fn` argument defining the method-specific behaviour. -func (p *Proposal) disownHandle(fn func(*C.ProposalHandle) error, disownEvenOnErr bool) error { - p.disown.Lock() - defer p.disown.Unlock() - - if p.handle == nil { - return errDroppedProposal - } - err := fn(p.handle) - if disownEvenOnErr || err == nil { - p.handle = nil - p.outstandingHandles.Done() - } - return err + return getProposalFromProposalResult(C.fwd_propose_on_proposal(p.handle, kvp), p.keepAliveHandle.outstandingHandles) } // Commit commits the proposal and returns any errors. // // The proposal handle is no longer valid after this call, but the root -// hash can still be retrieved using Root(). +// hash can still be retrieved using [Proposal.Root]. func (p *Proposal) Commit() error { - return p.disownHandle(commitProposal, true) -} + return p.keepAliveHandle.disown(true /* evenOnError */, func() error { + if p.handle == nil { + return errDroppedProposal + } + + _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(p.handle)) -func commitProposal(h *C.ProposalHandle) error { - _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(h)) - return err + p.handle = nil + + return err + }) } // Drop releases the memory associated with the Proposal. @@ -132,21 +129,23 @@ func commitProposal(h *C.ProposalHandle) error { // // The pointer will be set to nil after freeing to prevent double free. func (p *Proposal) Drop() error { - if err := p.disownHandle(dropProposal, false); err != nil && err != errDroppedProposal { - return err - } - return nil -} + return p.keepAliveHandle.disown(false /* evenOnError */, func() error { + if p.handle == nil { + return nil + } -func dropProposal(h *C.ProposalHandle) error { - if err := getErrorFromVoidResult(C.fwd_free_proposal(h)); err != nil { - return fmt.Errorf("%w: %w", errFreeingValue, err) - } - return nil + if err := getErrorFromVoidResult(C.fwd_free_proposal(p.handle)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } + + p.handle = nil + + return nil + }) } // getProposalFromProposalResult converts a C.ProposalResult to a Proposal or error. -func getProposalFromProposalResult(result C.ProposalResult, outstandingHandles *sync.WaitGroup) (*Proposal, error) { +func getProposalFromProposalResult(result C.ProposalResult, wg *sync.WaitGroup) (*Proposal, error) { switch result.tag { case C.ProposalResult_NullHandlePointer: return nil, errDBClosed @@ -154,11 +153,10 @@ func getProposalFromProposalResult(result C.ProposalResult, outstandingHandles * body := (*C.ProposalResult_Ok_Body)(unsafe.Pointer(&result.anon0)) hashKey := *(*Hash)(unsafe.Pointer(&body.root_hash._0)) proposal := &Proposal{ - handle: body.handle, - root: hashKey, - outstandingHandles: outstandingHandles, + handle: body.handle, + root: hashKey, } - outstandingHandles.Add(1) + proposal.keepAliveHandle.init(wg) runtime.SetFinalizer(proposal, (*Proposal).Drop) return proposal, nil case C.ProposalResult_Err: diff --git a/ffi/revision.go b/ffi/revision.go index 7ffc270b1348..83792880ddc9 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -24,18 +24,30 @@ var ( ) // Revision is an immutable view over the database at a specific root hash. -// Instances are created via Database.Revision, provide read-only access to the revision, -// and must be released with Drop when no longer needed. +// Instances are created via [Database.Revision], provide read-only access to +// the revision, and must be released with [Revision.Drop] when no longer needed. +// +// Revisions must be dropped before the associated database is closed. A finalizer +// is set on each Revision to ensure that Drop is called when the Revision is +// garbage collected, but relying on finalizers is not recommended. Failing to +// drop a revision before the database is closed will cause it to block or fail. type Revision struct { - // The database this revision is associated with. Holding this ensures - // the DB outlives the revision for cleanup ordering. + // handle is an opaque pointer to the revision within Firewood. It should be + // passed to the C FFI functions that operate on revisions + // + // It is not safe to call these methods with a nil handle. + // + // Calls to `C.fwd_free_revision` will invalidate this handle, so it should + // not be used after that call. handle *C.RevisionHandle - disown sync.Mutex - // The revision root + + // root is the root hash of the revision. root Hash - // outstandingHandles is the WaitGroup in Database that tracks open Rust handles. - // This is incremented when the revision is created, and decremented when Drop is called. - outstandingHandles *sync.WaitGroup + + // keepAliveHandle is used to keep the database alive while this revision is + // in use. It is initialized when the revision is created and disowned after + // [Revision.Drop] is called. + keepAliveHandle databaseKeepAliveHandle } // Get reads the value stored at the provided key within the revision. @@ -75,21 +87,18 @@ func (r *Revision) Iter(key []byte) (*Iterator, error) { // // It is safe to call Drop multiple times; subsequent calls after the first are no-ops. func (r *Revision) Drop() error { - r.disown.Lock() - defer r.disown.Unlock() - - if r.handle == nil { - return nil - } - - if err := getErrorFromVoidResult(C.fwd_free_revision(r.handle)); err != nil { - return fmt.Errorf("%w: %w", errFreeingValue, err) - } + return r.keepAliveHandle.disown(false /* evenOnError */, func() error { + if r.handle == nil { + return nil + } - r.outstandingHandles.Done() - r.handle = nil // Prevent double free + if err := getErrorFromVoidResult(C.fwd_free_revision(r.handle)); err != nil { + return fmt.Errorf("%w: %w", errFreeingValue, err) + } - return nil + r.handle = nil + return nil + }) } func (r *Revision) Root() Hash { @@ -97,7 +106,7 @@ func (r *Revision) Root() Hash { } // getRevisionFromResult converts a C.RevisionResult to a Revision or error. -func getRevisionFromResult(result C.RevisionResult, openProposals *sync.WaitGroup) (*Revision, error) { +func getRevisionFromResult(result C.RevisionResult, wg *sync.WaitGroup) (*Revision, error) { switch result.tag { case C.RevisionResult_NullHandlePointer: return nil, errDBClosed @@ -107,11 +116,10 @@ func getRevisionFromResult(result C.RevisionResult, openProposals *sync.WaitGrou body := (*C.RevisionResult_Ok_Body)(unsafe.Pointer(&result.anon0)) hashKey := *(*Hash)(unsafe.Pointer(&body.root_hash._0)) rev := &Revision{ - handle: body.handle, - root: hashKey, - outstandingHandles: openProposals, + handle: body.handle, + root: hashKey, } - openProposals.Add(1) + rev.keepAliveHandle.init(wg) runtime.SetFinalizer(rev, (*Revision).Drop) return rev, nil case C.RevisionResult_Err: From 9c33e6d1abcb181fbb340a88d562c4e87f154898 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:13:57 -0500 Subject: [PATCH 1031/1053] Fix inode exhaustion in benchmark bootstrap by reducing default bytes-per-inode (#1455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LevelDB benchmarks fail with "no space left on device" due to inode exhaustion, not disk space exhaustion. The bootstrap script creates ext4 filesystems with 6MB per inode (6291456 bytes), which severely limits inode count on large NVMe devices. LevelDB's many small files quickly exhaust available inodes. ## Changes - **build-environment.sh**: Make bytes-per-inode configurable via `--bytes-per-inode` command-line option, default to 2MB (2097152 bytes) instead of hardcoded 6MB - **build-environment.sh**: Add `--help` option to display usage information - **README.md**: Document the new command-line option with guidance on when to use different values: - Default 2MB works well for LevelDB workloads with many small files (such as LevelDB with AvalancheGo) - Larger values like 6MB can be used for Firewood-only workloads to provide more database space ## Usage ```bash # Uses default 2MB per inode (good for LevelDB + AvalancheGo) sudo bash build-environment.sh # Use larger value for Firewood-only workloads (more database space) sudo bash build-environment.sh --bytes-per-inode 6291456 # Display help sudo bash build-environment.sh --help ``` The 2MB default provides 3x more inodes while maintaining sufficient space for Firewood's single-file database and LevelDB's numerous small files. For Firewood-only workloads without LevelDB, users can increase the value to 6MB for more database space. - Fixes ava-labs/firewood#1444
    Original prompt > > ---- > > *This section details on the original issue you should resolve* > > bug(benchmark/bootstrap): benchmark fails due to inode exhaustion > While trying to gauge the performance of Firewood + RootStore against LevelDB + HashDB, I ran the following: > > ```bash > ./aws-launch.sh --firewood-branch rodrigo/add-root-store-alpha --avalanchego-branch rodrigo/add-firewood-archive-config-in-reexecution --coreth-branch rodrigo/firewood-v0.0.14-with-rootstore --libevm-commit 1bccf4f2ddb2 --instance-type i4i.xlarge --config archive --nblocks 10m > ``` > > I was able to get the reexecution test running on an EC2 instance, but the test eventually failed and I saw this in the error logs: > > ``` > [11-08|08:00:13.388] ERROR <2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5 Chain> triedb/hashdb/database.go:420 Failed to write flush list to disk err="open > /mnt/nvme/ubuntu/exec-data/current-state/db/6725573.ldb: no space left on device" > [11-08|08:00:13.388] CRIT <2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5 Chain> core/blockchain.go:620 unable to flatten snapshot from acceptor blockHash=e7a18c..c04315 err="open > /mnt/nvme/ubuntu/exec-data/current-state/db/6725573.ldb: no space left on device" > ``` > > At first glance, I thought that the LevelDB instance had grown larger than the SSD size, but running `df -h --total` revealed the opposite: > > ``` > Filesystem Size Used Avail Use% Mounted on > /dev/root 48G 26G 23G 53% / > tmpfs 16G 0 16G 0% /dev/shm > tmpfs 6.2G 948K 6.2G 1% /run > tmpfs 5.0M 0 5.0M 0% /run/lock > efivarfs 128K 3.9K 120K 4% /sys/firmware/efi/efivars > /dev/nvme1n1p16 881M 155M 665M 19% /boot > /dev/nvme1n1p15 105M 6.2M 99M 6% /boot/efi > /dev/nvme0n1 872G 407G 422G 50% /mnt/nvme > tmpfs 3.1G 12K 3.1G 1% /run/user/1007 > total 945G 432G 469G 48% - > ``` > > Consulting with Claude, it seems that the test failed due to inode exhaustion - running `df -i` seems to confirm this: > > ``` > /dev/nvme0n1 223424 223424 0 100% /mnt/nvme > ``` > > I'm somewhat hesitant to consider this a bug since `aws-launch.sh` is tailored towards running Firewood benchmarks but if we want to have consistent benchmarks of Firewood _and_ LevelDB + HashDB, the script should also be able to setup a EC2 instance that tests LevelDB + HashDB. > > Change the script to support configuring the bytes per inode, and set the default to 2097152 > > ## Comments on the Issue (you are @copilot in this section) > > > @AminR443 > This happens because of the way we create the filesystem in [firewood/benchmark/setup-scripts/build-environment.sh](https://github.com/ava-labs/firewood/blob/cae9899e7730a185fa0a3777b2c8b549cdc26835/benchmark/setup-scripts/build-environment.sh), and is not related to the `aws-launch.sh` script. > > I think `build-environment.sh` script was meant for the benchmark scripts that we had before, and we reused it in `aws-launch.sh`. We didn't run into any issues as firewood is a single file database, and other files (like the blockdb ones) didn't seem to exhaust the inodes in our experiments. > > I ran into this in my tests when I needed to compile `perf` from sources to do some profiling and cloning linux repo exhausted all of inodes. > > The reason is this line: > https://github.com/ava-labs/firewood/blob/cae9899e7730a185fa0a3777b2c8b549cdc26835/benchmark/setup-scripts/build-environment.sh#L65 > > It sets bytes-per-inode to 6MB. The main reason I think @rkuris did this was to speedup file system creation when using large devices. Reducing that should solve your problem, default value for ext4 is 16KB iirc. > > @rkuris I think we should actually change this for our experiments, because of the LevelDB stuff we get from S3, in some storage/block-range pairs we might run into this issue with firewood config as well. Let me know and I'll create a PR. > @RodrigoVillar > Ah thanks for the context @AminR443! Let's wait for @rkuris to respond but I would be in favor of parameterizing `bytes-per-inode` or lowering it altogether so that `build-environment.sh` can also be used for testing LevelDB + HashDB with archival mode enabled. > @rkuris > As discussed, our bytes per inode value is larger to allow for more space but doesn't work so well with leveldb. I think we can probably lower this by a factor of 3 to 2097152. > >
    - Fixes ava-labs/firewood#1444 --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> --- benchmark/setup-scripts/README.md | 8 +++++ benchmark/setup-scripts/build-environment.sh | 32 +++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/benchmark/setup-scripts/README.md b/benchmark/setup-scripts/README.md index b182c9b7d5a0..31b92807dcb0 100644 --- a/benchmark/setup-scripts/README.md +++ b/benchmark/setup-scripts/README.md @@ -8,6 +8,14 @@ sudo bash build-environment.sh This script sets up the build environment, including installing the firewood build dependencies. +By default, it sets the bytes-per-inode to 2097152 (2MB) when creating the ext4 filesystem. This default works well for workloads that create many small files (such as LevelDB with AvalancheGo). + +If you're not using LevelDB (for example, just using Firewood without AvalancheGo), you don't need as many inodes, which gives you more room for the database itself. In this case, you can and should use a larger value with the `--bytes-per-inode` option: + +```bash +sudo bash build-environment.sh --bytes-per-inode 6291456 +``` + ```bash sudo bash install-grafana.sh ``` diff --git a/benchmark/setup-scripts/build-environment.sh b/benchmark/setup-scripts/build-environment.sh index 7161acb174fd..82a1a42efaac 100644 --- a/benchmark/setup-scripts/build-environment.sh +++ b/benchmark/setup-scripts/build-environment.sh @@ -7,6 +7,36 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi +# Default bytes-per-inode for ext4 filesystem (2MB) +BYTES_PER_INODE=2097152 + +# Parse command line arguments +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --bytes-per-inode BYTES Set bytes-per-inode for ext4 filesystem (default: 2097152)" + echo " --help Show this help message" +} + +while [[ $# -gt 0 ]]; do + case $1 in + --bytes-per-inode) + BYTES_PER_INODE="$2" + shift 2 + ;; + --help) + show_usage + exit 0 + ;; + *) + echo "Error: Unknown option $1" >&2 + show_usage + exit 1 + ;; + esac +done + apt upgrade -y # install the build dependency packages @@ -62,7 +92,7 @@ if [ "${#NVME_DEVS[@]}" -gt 0 ]; then fi # Format and mount the device - mkfs.ext4 -E nodiscard -i 6291456 "$DEVICE_TO_USE" + mkfs.ext4 -E nodiscard -i "$BYTES_PER_INODE" "$DEVICE_TO_USE" NVME_MOUNT=/mnt/nvme mkdir -p "$NVME_MOUNT" mount -o noatime "$DEVICE_TO_USE" "$NVME_MOUNT" From 8ad5e19b56487388ea66e86d1726e3c8999a3543 Mon Sep 17 00:00:00 2001 From: maru Date: Mon, 17 Nov 2025 11:36:09 +0100 Subject: [PATCH 1032/1053] chore(ci): Re-enable ffi-nix job (#1450) After much [confusion](https://github.com/ava-labs/firewood/pull/1428) and many [blind alleys](https://github.com/ava-labs/firewood/pull/1431) over the build equivalency test flaking, it was observed that cargo builds were producing .a files with relocation tables on some github actions runners that differed with the relocation tables of a nix build. Since cargo reproducibility is orthogonal to nix reproducibility, and nix is by all measures producing consistent results, relocation count comparison has been removed from the equivalency test. The symbol count comparison is still considered relevant because it surfaced the undesired nix default of stripping debug symbols. - [x] Run `nix flake update` to ensure use of rust 1.91.0 - [x] Update minimum version of macos to 15.0 as per [a recent CI change](https://github.com/ava-labs/firewood/pull/1425) - [x] Remove relocation comparisons from build equivalency test - [x] Remove MAKEFLAGS configuration ([determined irrelevant to nix consistency](https://github.com/ava-labs/firewood/actions/runs/19305061394/job/55292940784?pr=1431)) - [x] Unskip ffi-nix job --- .github/workflows/ci.yaml | 1 - ffi/flake.lock | 22 ++++++------- ffi/flake.nix | 7 +---- ffi/test-build-equivalency.sh | 59 ++--------------------------------- 4 files changed, 14 insertions(+), 75 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e48e774c02d4..a632ae036657 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -245,7 +245,6 @@ jobs: run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test -race ./... ffi-nix: - if: false # TODO(marun) Re-enable once non-determinism is rooted out runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/ffi/flake.lock b/ffi/flake.lock index 2eda9b50caf9..1910fe44ebe1 100644 --- a/ffi/flake.lock +++ b/ffi/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1758758545, - "narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=", + "lastModified": 1762538466, + "narHash": "sha256-8zrIPl6J+wLm9MH5ksHcW7BUHo7jSNOu0/hA0ohOOaM=", "owner": "ipetkov", "repo": "crane", - "rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364", + "rev": "0cea393fffb39575c46b7a0318386467272182fe", "type": "github" }, "original": { @@ -70,12 +70,12 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1758589230, - "narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=", - "rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0", - "revCount": 810308, + "lastModified": 1762498405, + "narHash": "sha256-Zg/SCgCaAioc0/SVZQJxuECGPJy+OAeBcGeA5okdYDc=", + "rev": "6faeb062ee4cf4f105989d490831713cc5a43ee1", + "revCount": 812554, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.810308%2Brev-d1d883129b193f0b495d75c148c2c3a7d95789a0/01997816-a6f6-7040-8535-2ae74ed9bd44/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.812554%2Brev-6faeb062ee4cf4f105989d490831713cc5a43ee1/019a5f75-0159-79b8-b171-f9b6d2148da2/source.tar.gz" }, "original": { "type": "tarball", @@ -112,11 +112,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1758854041, - "narHash": "sha256-kZ+24pbf4FiHlYlcvts64BhpxpHkPKIQXBmx1OmBAIo=", + "lastModified": 1762742448, + "narHash": "sha256-XMxV0h13gg63s0sV6beihCIqdpcJhtbse6DHI743nvo=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "02227ca8c229c968dbb5de95584cfb12b4313104", + "rev": "7f3556887e3375dc26ff1601b57c93ee286f2c5e", "type": "github" }, "original": { diff --git a/ffi/flake.nix b/ffi/flake.nix index 1b25a796cb80..dab896af88b6 100644 --- a/ffi/flake.nix +++ b/ffi/flake.nix @@ -58,14 +58,9 @@ pkg-config ]; - # Force sequential build of vendored jemalloc to avoid race conditions - # that cause non-deterministic symbol generation on x86_64 - # MAKEFLAGS only affects make invocations (jemalloc), not cargo parallelism - # See: https://github.com/NixOS/nixpkgs/issues/380852 - MAKEFLAGS = "-j1"; } // lib.optionalAttrs pkgs.stdenv.isDarwin { # Set macOS deployment target for Darwin builds - MACOSX_DEPLOYMENT_TARGET = "13.0"; + MACOSX_DEPLOYMENT_TARGET = "15.0"; }; cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { diff --git a/ffi/test-build-equivalency.sh b/ffi/test-build-equivalency.sh index 155a32d98e88..a815795bb81c 100755 --- a/ffi/test-build-equivalency.sh +++ b/ffi/test-build-equivalency.sh @@ -15,9 +15,8 @@ CARGO_LIB="target/maxperf/libfirewood_ffi.a" TMPDIR=$(mktemp -d) trap "rm -rf $TMPDIR" EXIT -# Build serially with MAKEFLAGS='-j1' for consistency with the flake build echo "Building with cargo (using nix dev shell)..." -nix develop ./ffi#default --command bash -c "export MAKEFLAGS='-j1' && cargo fetch --locked --verbose && cargo build-static-ffi" +nix develop ./ffi#default --command bash -c "cargo fetch --locked --verbose && cargo build-static-ffi" echo "Building with nix..." cd ffi && nix build .#firewood-ffi && cd .. @@ -37,7 +36,7 @@ CARGO_SYMBOLS=$(wc -l < "$TMPDIR/cargo-symbols.txt") echo "Nix build: $NIX_SYMBOLS symbols" echo "Cargo build: $CARGO_SYMBOLS symbols" if [ "$NIX_SYMBOLS" -eq "$CARGO_SYMBOLS" ]; then - echo "✅ Symbol counts match" + echo "✅ Symbol counts are both $NIX_SYMBOLS" else echo "❌ Symbol counts differ" echo "" @@ -50,57 +49,3 @@ else # Show lines that exist in the new file (cargo) but not in the old file (nix) diff --unchanged-line-format="" --old-line-format="" --new-line-format="%L" "$TMPDIR/nix-symbols.txt" "$TMPDIR/cargo-symbols.txt" || true fi - -echo "" -echo "=== Relocation Count Comparison ===" - -# Determine os-specific reloc config -if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - RELOC_CMD="otool -rv" - RELOC_PATTERN='[A-Z_]+_RELOC_[A-Z0-9_]+' -else - # Linux - RELOC_CMD="readelf -r" - RELOC_PATTERN='R_[A-Z0-9_]+' -fi - -$RELOC_CMD "$NIX_LIB" > "$TMPDIR/nix-relocs.txt" -$RELOC_CMD "$CARGO_LIB" > "$TMPDIR/cargo-relocs.txt" - -NIX_RELOCS=$(wc -l < "$TMPDIR/nix-relocs.txt") -CARGO_RELOCS=$(wc -l < "$TMPDIR/cargo-relocs.txt") -echo "Nix build: $NIX_RELOCS relocation entries" -echo "Cargo build: $CARGO_RELOCS relocation entries" -if [ "$NIX_RELOCS" -eq "$CARGO_RELOCS" ]; then - echo "✅ Relocation counts match" -else - echo "❌ Relocation counts differ" -fi - -echo "" -echo "=== Relocation Type Comparison ===" - -# Use grep with -E for better portability (avoid -P which isn't available on macOS) -grep -Eo "$RELOC_PATTERN" "$TMPDIR/nix-relocs.txt" | sort | uniq -c > "$TMPDIR/nix-reloc-types.txt" -grep -Eo "$RELOC_PATTERN" "$TMPDIR/cargo-relocs.txt" | sort | uniq -c > "$TMPDIR/cargo-reloc-types.txt" - -if diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" > /dev/null; then - echo "✅ Relocation types match" -else - echo "❌ Relocation types differ" - diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" -fi - -echo "" -echo "=== Relocation Type Distribution ===" -cat "$TMPDIR/nix-reloc-types.txt" - -echo "" -echo "=== Summary ===" -if [ "$NIX_SYMBOLS" -eq "$CARGO_SYMBOLS" ] && [ "$NIX_RELOCS" -eq "$CARGO_RELOCS" ] && diff "$TMPDIR/nix-reloc-types.txt" "$TMPDIR/cargo-reloc-types.txt" > /dev/null; then - echo "✅ Builds are equivalent - both using maxperf profile" -else - echo "❌ Builds differ" - exit 1 -fi From 20786f972de1966ddb92b2d0a8fa3577d959c569 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:28:34 +0000 Subject: [PATCH 1033/1053] docs: Merge code review process into CONTRIBUTING.md (#1397) ## Summary Successfully merged the Firewood Code Review Guidelines into CONTRIBUTING.md. The review process documentation is now consolidated in one place for easier contributor access. Also updated the help section to direct questions to the issue tracker instead of Twitter. ## Changes Made 1. **Main Change**: Added comprehensive "Code Review Process" section 2. **Minor Fixes**: Fixed pre-existing typos and improved punctuation clarity 3. **Help Section Update**: Replaced Twitter contact with instructions to use the issue tracker for questions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> --- CONTRIBUTING.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4035357f6d5c..0ee1df69d288 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,6 +9,7 @@ guidelines for contributing to firewood. * [Quick Links](#Quick Links) * [Testing](#testing) * [How to submit changes](#How to submit changes) +* [Code Review Process](#Code Review Process) * [Where can I ask for help?](#Where can I ask for help) ## [Quick Links] @@ -30,9 +31,71 @@ Resolve any warnings or errors before making your PR. ## [How to submit changes] -To create a PR, fork firewood, and use github to create the PR. We typically prioritize reviews in the middle of our the next work day, +To create a PR, fork firewood, and use GitHub to create the PR. We typically prioritize reviews in the middle of the next work day, so you should expect a response during the week within 24 hours. +## [Code Review Process] + +Code review is a critical part of our development process. It ensures that our codebase remains maintainable, performant, and secure. This document outlines how we approach code reviews at Ava Labs, with responsibilities and expectations for both reviewers and authors. + +### For Reviewers + +Reviews should be completed or commented within one business day. We have a daily reminder for reviews that have not been reviewed that is posted in slack's #firewood channel. + +When reviewing code, your goal is to help the author improve the quality of the change and confirm that it meets our architectural and operational standards. GitHub provides three primary review options: + +#### ✅ Accept (Approve) + +Use this when the code is an improvement over the current state of the codebase. + +* It's okay to request minor changes in comments and still approve the pull request. +* Perfection is not the goal — progress is. If the submitted code is better than what's in production, it's acceptable to approve even if small improvements remain. Consider adding a new issue or request adding a code TODO for larger changes. + +#### 💬 Comment (Comment Only) + +Use this when your review is incomplete, or you're not ready to approve or reject yet. You should use this if the code is too large to review in a limited amount of time (typically 30-60 minutes). You can also suggest how to break up this diff into a smaller diff. + +* This can be helpful for asking clarifying questions, suggesting optional improvements, or flagging issues you're unsure about. +* This state signals that your review is in progress or advisory, not final. + +#### ❌ Reject (Request Changes) + +Use this when there are significant concerns with the code's correctness, architecture, design, or maintainability. + +* A "Reject" signals that the pull request must not be merged until the raised issues are addressed. +* The author is expected to make substantial revisions and return the code for a second round of review by the same reviewer. + +#### Best Practices + +* Be respectful and constructive. Your comments should guide and empower the author, not discourage them. +* Justify your feedback with principles, not preferences. +* If you're unsure, ask questions rather than assume intent. +* If you're going to nitpick, preface the comment with "nit:". This means the author can choose to ignore the comment. + +### For Authors + +As the author of a pull request, your responsibility is to ensure the review process is smooth, transparent, and productive. + +#### Before Requesting a Review + +* Review your own code. Catch obvious issues and clean up unnecessary changes. +* Some code changes are too large to be reviewed quickly. This can happen when the number of lines of new code is more than a few hundred. Consider breaking up your code in this case. +* Write a clear PR description. Include context, reasoning, and anything reviewers should know up front. +* Add tests and verify they pass locally and in CI. + +#### During Review + +* Respond to each comment, even if just to acknowledge it. +* Use GitHub's "Resolve" feature when you've addressed feedback. In some cases, to get to the "Resolve" button requires you select "Hide" first, with a reason of "Resolved". +* Don't be afraid to explain your design decisions—but stay open to change. +* If you disagree with a reviewer's suggestion, provide reasoning. If you're sure your response fully resolves the reviewer's suggestion, mark it as resolved. + +#### After Review + +* When you've made requested changes, clearly indicate it in your comment or commit, and re-request the review. +* If the PR was rejected, wait for explicit re-approval before merging. +* Thank your reviewers—they're helping you ship better code. + ## [How to report a bug] Please use the [issue tracker](https://github.com/ava-labs/firewood/issues) for reporting issues. @@ -54,7 +117,7 @@ By default, we prohibit bare `unwrap` calls and index dereferencing, as there ar ## [Where can I ask for help]? -Please reach out on X (formerly twitter) @rkuris for help or questions! +If you have questions or need help, please post them as issues in the [issue tracker](https://github.com/ava-labs/firewood/issues). This allows the community to benefit from the discussion and helps us maintain a searchable knowledge base. ## Thank you From 2f71077f751d27aab226c0359885fc9d8cec3416 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:39:13 +0000 Subject: [PATCH 1034/1053] refactor: remove default 1-minute timeout on `Database.Close()` and set 1-sec in tests (#1458) Closes #1457 --------- Co-authored-by: Austin Larson --- ffi/firewood.go | 21 ++++-------- ffi/firewood_test.go | 82 +++++++++++++------------------------------- 2 files changed, 31 insertions(+), 72 deletions(-) diff --git a/ffi/firewood.go b/ffi/firewood.go index b9c13c1ce2bc..23da94e36ef3 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -31,7 +31,6 @@ import ( "fmt" "runtime" "sync" - "time" ) const RootLength = C.sizeof_HashKey @@ -246,18 +245,14 @@ func (db *Database) Revision(root Hash) (*Revision, error) { return rev, nil } -// defaultCloseTimeout is the duration by which the [context.Context] passed to -// [Database.Close] is limited. A minute is arbitrary but well above what is -// reasonably required, and is chosen simply to avoid permanently blocking. -var defaultCloseTimeout = time.Minute - // Close releases the memory associated with the Database. // -// This blocks until all outstanding keep-alive handles are disowned. That is, -// until all Revisions and Proposals created from this Database are either -// unreachable or one of [Proposal.Commit], [Proposal.Drop], or [Revision.Drop] -// has been called on them. Unreachable objects will be automatically dropped -// before Close returns, unless an alternate GC finalizer is set on them. +// This blocks until all outstanding keep-alive handles are disowned or the +// [context.Context] is cancelled. That is, until all Revisions and Proposals +// created from this Database are either unreachable or one of +// [Proposal.Commit], [Proposal.Drop], or [Revision.Drop] has been called on +// them. Unreachable objects will be automatically dropped before Close returns, +// unless an alternate GC finalizer is set on them. // // This is safe to call if the handle pointer is nil, in which case it does // nothing. The pointer will be set to nil after freeing to prevent double free. @@ -276,12 +271,10 @@ func (db *Database) Close(ctx context.Context) error { close(done) }() - ctx, cancel := context.WithTimeout(ctx, defaultCloseTimeout) - defer cancel() select { case <-done: case <-ctx.Done(): - return ErrActiveKeepAliveHandles + return fmt.Errorf("%w: %w", ctx.Err(), ErrActiveKeepAliveHandles) } if err := getErrorFromVoidResult(C.fwd_close_db(db.handle)); err != nil { diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 904a5a8fbf9f..32a243187ff2 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -75,6 +75,8 @@ func inferHashingMode(ctx context.Context) (string, error) { return "", err } defer func() { + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() _ = db.Close(ctx) _ = os.Remove(dbFile) }() @@ -137,6 +139,16 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +// oneSecCtx returns `tb.Context()` with a 1-second timeout added. Any existing +// cancellation on `tb.Context()` is removed, which allows this function to be +// used inside a `tb.Cleanup()` +func oneSecCtx(tb testing.TB) context.Context { + ctx := context.WithoutCancel(tb.Context()) + ctx, cancel := context.WithTimeout(ctx, time.Second) + tb.Cleanup(cancel) + return ctx +} + func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database { t.Helper() r := require.New(t) @@ -145,7 +157,7 @@ func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database { db, err := newDatabase(dbFile, configureFns...) r.NoError(err) t.Cleanup(func() { - r.NoError(db.Close(context.Background())) //nolint:usetesting // t.Context() will already be cancelled + r.NoError(db.Close(oneSecCtx(t))) }) return db } @@ -205,7 +217,7 @@ func TestTruncateDatabase(t *testing.T) { r.NoError(err) // Close the database. - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) // Reopen the database with truncate enabled. db, err = New(dbFile, config) @@ -217,7 +229,7 @@ func TestTruncateDatabase(t *testing.T) { expectedHash := stringToHash(t, expectedRoots[emptyKey]) r.Equal(expectedHash, hash, "Root hash mismatch after truncation") - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) } func TestClosedDatabase(t *testing.T) { @@ -226,7 +238,7 @@ func TestClosedDatabase(t *testing.T) { db, err := newDatabase(dbFile) r.NoError(err) - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) _, err = db.Root() r.ErrorIs(err, errDBClosed) @@ -238,7 +250,7 @@ func TestClosedDatabase(t *testing.T) { r.Empty(root) r.ErrorIs(err, errDBClosed) - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) } func keyForTest(i int) []byte { @@ -1214,7 +1226,7 @@ func TestHandlesFreeImplicitly(t *testing.T) { done := make(chan struct{}) go func() { - require.NoErrorf(t, db.Close(t.Context()), "%T.Close()", db) + require.NoErrorf(t, db.Close(oneSecCtx(t)), "%T.Close()", db) close(done) }() @@ -1594,49 +1606,13 @@ func TestCloseWithCancelledContext(t *testing.T) { wg.Wait() r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles when context is cancelled") + r.ErrorIs(err, context.Canceled, "Close error should wrap context.Canceled") // Drop the proposal r.NoError(proposal.Drop()) // Now Close should succeed - r.NoError(db.Close(t.Context())) -} - -// TestCloseWithShortTimeout verifies that Database.Close returns -// ErrActiveKeepAliveHandles when the context times out before handles are dropped. -func TestCloseWithShortTimeout(t *testing.T) { - r := require.New(t) - dbFile := filepath.Join(t.TempDir(), "test.db") - db, err := newDatabase(dbFile) - r.NoError(err) - - // Create a revision to keep a handle active - keys, vals := kvForTest(1) - root, err := db.Update(keys, vals) - r.NoError(err) - - revision, err := db.Revision(root) - r.NoError(err) - - // Create a context with a very short timeout (100ms) - ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) - defer cancel() - - // Record start time to verify timeout occurred quickly - start := time.Now() - - // Close should return ErrActiveKeepAliveHandles after timeout - err = db.Close(ctx) - elapsed := time.Since(start) - - r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles when context times out") - r.Less(elapsed, defaultCloseTimeout, "Close should timeout quickly, not wait for default timeout") - - // Drop the revision - r.NoError(revision.Drop()) - - // Now Close should succeed - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) } // TestCloseWithMultipleActiveHandles verifies that Database.Close returns @@ -1674,6 +1650,7 @@ func TestCloseWithMultipleActiveHandles(t *testing.T) { // Close should return ErrActiveKeepAliveHandles err = db.Close(ctx) r.ErrorIs(err, ErrActiveKeepAliveHandles, "Close should return ErrActiveKeepAliveHandles with multiple active handles") + r.ErrorIs(err, context.Canceled, "Close error should wrap context.Canceled") // Drop all handles r.NoError(proposal1.Drop()) @@ -1683,7 +1660,7 @@ func TestCloseWithMultipleActiveHandles(t *testing.T) { r.NoError(revision2.Drop()) // Now Close should succeed - r.NoError(db.Close(t.Context())) + r.NoError(db.Close(oneSecCtx(t))) } // TestCloseSucceedsWhenHandlesDroppedInTime verifies that Database.Close succeeds @@ -1701,28 +1678,17 @@ func TestCloseSucceedsWhenHandlesDroppedInTime(t *testing.T) { proposal2, err := db.Propose(keys[1:2], vals[1:2]) r.NoError(err) - // Create a context with a reasonable timeout - ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond) - defer cancel() - // Channel to receive Close result closeDone := make(chan error, 1) // Start Close in a goroutine go func() { - closeDone <- db.Close(ctx) + closeDone <- db.Close(oneSecCtx(t)) }() // Drop handles after a short delay (before timeout) time.Sleep(100 * time.Millisecond) r.NoError(proposal1.Drop()) r.NoError(proposal2.Drop()) - - // Close should succeed (not timeout) - select { - case err := <-closeDone: - r.NoError(err, "Close should succeed when handles are dropped before timeout") - case <-time.After(defaultCloseTimeout): - r.Fail("Close did not complete in time") - } + r.NoError(<-closeDone, "Close should succeed when handles are dropped before timeout") } From 1945efa9b373b47a9b60bbb6500c972ce5d34059 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:33:36 +0000 Subject: [PATCH 1035/1053] docs: Add comprehensive metrics documentation in METRICS.md (#1402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [x] Successfully rebased on main (commit 03f12df) - [x] Created METRICS.md with comprehensive documentation of all 29 metrics - [x] Added note about one metrics instance per process - [x] Updated README.md to reference METRICS.md - [x] Added METRICS.md to license header exclusion list in .github/check-license-headers.yaml - [x] Clean single commit with only documentation changes - [x] No unrelated PR changes visible in diff Add comprehensive metrics documentation to README > ### Problem > There is no comprehensive document listing or describing the available metrics in Firewood. Users and developers cannot easily understand what metrics are available for monitoring or how to interpret them. > > ### Proposed Solution > Add a dedicated "Metrics" section to the top-level README.md that documents all available metrics. > > ### Content to Include > - List of all available metrics with their names > - Description of what each metric measures > - Usage examples for enabling and gathering metrics > - Information about metric labels and values > - Examples of how to interpret metrics for monitoring and debugging > > ### Current State > - Metrics are mentioned briefly in `ffi/README.md` > - The `firewood-macros/README.md` shows how metrics are instrumented > - No comprehensive listing exists of what metrics are actually available > > ### References > - See `storage/src/macros.rs` for metric macro implementations > - See `ffi/README.md` for existing metrics documentation > - See `firewood-macros/README.md` for metrics macro usage - Fixes ava-labs/firewood#1398
    Original prompt > > ---- > > *This section details on the original issue you should resolve* > > Add comprehensive metrics documentation to README > ### Problem > There is no comprehensive document listing or describing the available metrics in Firewood. Users and developers cannot easily understand what metrics are available for monitoring or how to interpret them. > > ### Proposed Solution > Add a dedicated "Metrics" section to the top-level README.md that documents all available metrics. > > ### Content to Include > - List of all available metrics with their names > - Description of what each metric measures > - Usage examples for enabling and gathering metrics > - Information about metric labels and values > - Examples of how to interpret metrics for monitoring and debugging > > ### Current State > - Metrics are mentioned briefly in `ffi/README.md` > - The `firewood-macros/README.md` shows how metrics are instrumented > - No comprehensive listing exists of what metrics are actually available > > ### References > - See `storage/src/macros.rs` for metric macro implementations > - See `ffi/README.md` for existing metrics documentation > - See `firewood-macros/README.md` for metrics macro usage > > ## Comments on the Issue (you are @copilot in this section) > > > >
    - Fixes ava-labs/firewood#1398 --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rkuris <3193068+rkuris@users.noreply.github.com> Co-authored-by: demosdemon <310610+demosdemon@users.noreply.github.com> Co-authored-by: Brandon LeBlanc --- .github/check-license-headers.yaml | 2 + METRICS.md | 291 +++++++++++++++++++++++++++++ README.md | 4 + 3 files changed, 297 insertions(+) create mode 100644 METRICS.md diff --git a/.github/check-license-headers.yaml b/.github/check-license-headers.yaml index 4bd878d01b4e..3452bdf4c4f8 100644 --- a/.github/check-license-headers.yaml +++ b/.github/check-license-headers.yaml @@ -13,6 +13,7 @@ "grpc-testtool/**", "README*", "**/README*", + "METRICS.md", "Cargo.toml", "Cargo.lock", "*/Cargo.toml", @@ -40,6 +41,7 @@ "grpc-testtool/**", "README*", "**/README*", + "METRICS.md", "Cargo.toml", "Cargo.lock", "*/Cargo.toml", diff --git a/METRICS.md b/METRICS.md new file mode 100644 index 000000000000..464022c1f515 --- /dev/null +++ b/METRICS.md @@ -0,0 +1,291 @@ +# Firewood Metrics + +Firewood provides comprehensive metrics for monitoring database performance, resource utilization, and operational characteristics. These metrics are built using the [Prometheus](https://prometheus.io/) format and can be exposed for collection by monitoring systems. + +**Note**: Metric names in this documentation use dots (e.g., `firewood.proposal.commit`), but when exported to Prometheus, dots are automatically converted to underscores (e.g., `firewood_proposal_commit`) following Prometheus naming conventions. + +## Enabling Metrics + +Metrics are available when Firewood is built with the `metrics` feature. By default, metrics collection is enabled in the library but needs to be explicitly started in applications. + +**Important**: Only one metrics instance can be created per process. Attempting to initialize metrics multiple times will result in an error. + +### For Rust Applications + +Metrics are automatically registered when the instrumented code paths are executed. To expose metrics via HTTP: + +```rust +use metrics_exporter_prometheus::PrometheusBuilder; + +// Set up Prometheus exporter on port 9000 +PrometheusBuilder::new() + .install() + .expect("failed to install Prometheus recorder"); +``` + +### For FFI/Go Applications + +In the Go FFI layer, metrics must be explicitly enabled: + +```go +import "github.com/ava-labs/firewood-go-ethhash/ffi" + +// Option 1: Start metrics with HTTP exporter on a specific port +ffi.StartMetricsWithExporter(9000) + +// Option 2: Start metrics without exporter (use Gatherer to access) +ffi.StartMetrics() + +// Retrieve metrics programmatically +gatherer := ffi.Gatherer{} +metrics, err := gatherer.Gather() +``` + +See the [FFI README](ffi/README.md) for more details on FFI metrics configuration. + +## Available Metrics + +### Database Operations + +#### Proposal Metrics + +- **`firewood.proposals`** (counter) + - Description: Total number of proposals created + - Use: Track proposal creation rate and throughput + +- **`firewood.proposal.create`** (counter with `success` label) + - Description: Count of proposal creation operations + - Labels: `success=true|false` + - Use: Monitor proposal creation success rate + +- **`firewood.proposal.create_ms`** (counter with `success` label) + - Description: Time spent creating proposals in milliseconds + - Labels: `success=true|false` + - Use: Track proposal creation latency + +- **`firewood.proposal.commit`** (counter with `success` label) + - Description: Count of proposal commit operations + - Labels: `success=true|false` + - Use: Monitor commit success rate + +- **`firewood.proposal.commit_ms`** (counter with `success` label) + - Description: Time spent committing proposals in milliseconds + - Labels: `success=true|false` + - Use: Track commit latency and identify slow commits + +#### Revision Management + +- **`firewood.active_revisions`** (gauge) + - Description: Current number of active revisions in memory + - Use: Monitor memory usage and revision retention + +- **`firewood.max_revisions`** (gauge) + - Description: Maximum number of revisions configured + - Use: Track configuration setting + +### Merkle Trie Operations + +#### Insert Operations + +- **`firewood.insert`** (counter with `merkle` label) + - Description: Count of insert operations by type + - Labels: `merkle=update|above|below|split` + - `update`: Value updated at existing key + - `above`: New node inserted above existing node + - `below`: New node inserted below existing node + - `split`: Node split during insertion + - Use: Understand insert patterns and trie structure evolution + +#### Remove Operations + +- **`firewood.remove`** (counter with `prefix` and `result` labels) + - Description: Count of remove operations + - Labels: + - `prefix=true|false`: Whether operation is prefix-based removal + - `result=success|nonexistent`: Whether key(s) were found + - Use: Track deletion patterns and key existence + +### Storage and I/O Metrics + +#### Node Reading + +- **`firewood.read_node`** (counter with `from` label) + - Description: Count of node reads by source + - Labels: `from=file|memory` + - Use: Monitor read patterns and storage layer usage + +#### Cache Performance + +- **`firewood.cache.node`** (counter with `mode` and `type` labels) + - Description: Node cache hit/miss statistics + - Labels: + - `mode`: Read operation mode + - `type=hit|miss`: Cache hit or miss + - Use: Evaluate cache effectiveness for nodes + +- **`firewood.cache.freelist`** (counter with `type` label) + - Description: Free list cache hit/miss statistics + - Labels: `type=hit|miss` + - Use: Monitor free list cache efficiency + +#### I/O Operations + +- **`firewood.io.read`** (counter) + - Description: Total number of I/O read operations + - Use: Track I/O operation count + +- **`firewood.io.read_ms`** (counter) + - Description: Total time spent in I/O reads in milliseconds + - Use: Identify I/O bottlenecks and disk performance issues + +#### Node Persistence + +- **`firewood.flush_nodes`** (counter) + - Description: Cumulative time spent flushing nodes to disk in milliseconds (counter incremented by flush duration) + - Use: Monitor flush performance and identify slow disk writes; calculate average flush time using rate() + +### Memory Management + +#### Space Allocation + +- **`firewood.space.reused`** (counter with `index` label) + - Description: Bytes reused from free list + - Labels: `index`: Size index of allocated area + - Use: Track memory reuse efficiency + +- **`firewood.space.wasted`** (counter with `index` label) + - Description: Bytes wasted when allocating from free list (allocated more than needed) + - Labels: `index`: Size index of allocated area + - Use: Monitor allocation efficiency and fragmentation + +- **`firewood.space.from_end`** (counter with `index` label) + - Description: Bytes allocated from end of nodestore when free list was insufficient + - Labels: `index`: Size index of allocated area + - Use: Track database growth and free list effectiveness + +- **`firewood.space.freed`** (counter with `index` label) + - Description: Bytes freed back to free list + - Labels: `index`: Size index of freed area + - Use: Monitor memory reclamation + +#### Node Management + +- **`firewood.delete_node`** (counter with `index` label) + - Description: Count of nodes deleted + - Labels: `index`: Size index of deleted node + - Use: Track node deletion patterns + +#### Ring Buffer + +- **`ring.full`** (counter) + - Description: Count of times the ring buffer became full during node flushing + - Use: Identify backpressure in node persistence pipeline + +### FFI Layer Metrics + +These metrics are specific to the Foreign Function Interface (Go) layer: + +#### Batch Operations + +- **`firewood.ffi.batch`** (counter) + - Description: Count of batch operations completed + - Use: Track FFI batch throughput + +- **`firewood.ffi.batch_ms`** (counter) + - Description: Time spent processing batches in milliseconds + - Use: Monitor FFI batch latency + +#### Proposal Operations + +- **`firewood.ffi.propose`** (counter) + - Description: Count of proposal operations via FFI + - Use: Track FFI proposal throughput + +- **`firewood.ffi.propose_ms`** (counter) + - Description: Time spent creating proposals via FFI in milliseconds + - Use: Monitor FFI proposal latency + +#### Commit Operations + +- **`firewood.ffi.commit`** (counter) + - Description: Count of commit operations via FFI + - Use: Track FFI commit throughput + +- **`firewood.ffi.commit_ms`** (counter) + - Description: Time spent committing via FFI in milliseconds + - Use: Monitor FFI commit latency + +#### View Caching + +- **`firewood.ffi.cached_view.hit`** (counter) + - Description: Count of cached view hits + - Use: Monitor view cache effectiveness + +- **`firewood.ffi.cached_view.miss`** (counter) + - Description: Count of cached view misses + - Use: Monitor view cache effectiveness + +## Interpreting Metrics + +### Performance Monitoring + +1. **Latency Tracking**: The `*_ms` metrics track operation durations. Monitor these for: + - Sudden increases indicating performance degradation + - Baseline establishment for SLA monitoring + - Correlation with system load + +2. **Throughput Monitoring**: Counter metrics without `_ms` suffix track operation counts: + - Rate of change indicates throughput + - Compare with expected load patterns + - Identify anomalies in operation rates + +### Resource Utilization + +1. **Cache Efficiency**: + - Calculate hit rate: `cache.hit / (cache.hit + cache.miss)` + - Target: >90% for node cache, >80% for free list cache + - Low hit rates may indicate insufficient cache size + +2. **Memory Management**: + - Monitor `space.reused` vs `space.from_end` ratio + - High `space.from_end` indicates database growth + - High `space.wasted` suggests fragmentation issues + +3. **Active Revisions**: + - `active_revisions` approaching `max_revisions` triggers cleanup + - Sustained high values may indicate memory pressure + +### Debugging + +1. **Failed Operations**: + - Check metrics with `success=false` label + - Correlate with error logs for root cause analysis + +2. **Ring Buffer Backpressure**: + - `ring.full` counter increasing indicates persistence bottleneck + - May require tuning of flush parameters or disk subsystem + +3. **Insert/Remove Patterns**: + - `firewood.insert` labels show trie structure evolution + - High `split` counts indicate complex key distributions + - Remove `nonexistent` suggests application-level issues + +## Example Monitoring Queries + +For Prometheus-based monitoring (note: metric names use underscores in queries): + +```promql +# Average commit latency over 5 minutes +rate(firewood_proposal_commit_ms[5m]) / rate(firewood_proposal_commit[5m]) + +# Cache hit rate +sum(rate(firewood_cache_node{type="hit"}[5m])) / +sum(rate(firewood_cache_node[5m])) + +# Database growth rate (bytes/sec) +rate(firewood_space_from_end[5m]) + +# Failed commit ratio +rate(firewood_proposal_commit{success="false"}[5m]) / +rate(firewood_proposal_commit[5m]) +``` diff --git a/README.md b/README.md index 67984e1d4b25..e06489714e34 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ as well as carefully managing the free list during the creation and expiration o - `Commit` - The operation of applying one or more `Proposal`s to the most recent `Revision`. +## Metrics + +Firewood provides comprehensive metrics for monitoring database performance, resource utilization, and operational characteristics. For detailed information about all available metrics, how to enable them, and how to interpret them, see [METRICS.md](METRICS.md). + ## Build In order to build firewood, the following dependencies must be installed: From b8f30ee7ba65af40f731fd15c5093ef2090562c0 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 17 Nov 2025 12:45:27 -0800 Subject: [PATCH 1036/1053] feat: remove the last bits of arc-swap (#1464) I noticed there was one remaining component using arc-swap. This change replaces it with a standard Mutex. And, removes the dependency on arc-swap. --- Cargo.lock | 7 ----- storage/Cargo.toml | 1 - storage/src/nodestore/mod.rs | 50 +++++++++++++++++------------------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f705973b445..54110b16c600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,12 +139,6 @@ dependencies = [ "syn", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - [[package]] name = "arrayvec" version = "0.7.6" @@ -1143,7 +1137,6 @@ name = "firewood-storage" version = "0.0.14" dependencies = [ "aquamarine", - "arc-swap", "bitfield", "bitflags 2.10.0", "bumpalo", diff --git a/storage/Cargo.toml b/storage/Cargo.toml index d478fe920108..3e32541adb38 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -32,7 +32,6 @@ sha2.workspace = true smallvec.workspace = true thiserror.workspace = true # Regular dependencies -arc-swap = "1.7.1" bitfield = "0.19.3" bitflags = "2.10.0" derive-where = "1.6.0" diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index ec76989c498e..d0b76fa2711a 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -49,8 +49,6 @@ pub(crate) mod primitives; use crate::linear::OffsetReader; use crate::logger::trace; use crate::node::branch::ReadSerializable as _; -use arc_swap::ArcSwap; -use arc_swap::access::DynAccess; use smallvec::SmallVec; use std::fmt::Debug; use std::io::{Error, ErrorKind, Read}; @@ -236,17 +234,7 @@ impl NodeStore, S> { /// When an immutable proposal commits, we need to reparent any proposal that /// has the committed proposal as it's parent pub fn commit_reparent(&self, other: &NodeStore, S>) { - match *other.kind.parent.load() { - NodeStoreParent::Proposed(ref parent) => { - if Arc::ptr_eq(&self.kind, parent) { - other - .kind - .parent - .store(NodeStoreParent::Committed(self.kind.root_hash()).into()); - } - } - NodeStoreParent::Committed(_) => {} - } + other.kind.commit_reparent(&self.kind); } } @@ -469,7 +457,7 @@ pub struct ImmutableProposal { /// Nodes that have been deleted in this proposal. deleted: Box<[MaybePersistedNode]>, /// The parent of this proposal. - parent: Arc>, + parent: Arc>, /// The root of the trie in this proposal. root: Option, } @@ -478,15 +466,20 @@ impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. #[must_use] fn parent_hash_is(&self, hash: Option) -> bool { - match > as arc_swap::access::DynAccess>>::load( - &self.parent, - ) - .as_ref() - { + match &*self.parent.lock().expect("poisoned lock") { NodeStoreParent::Committed(root_hash) => *root_hash == hash, NodeStoreParent::Proposed(_) => false, } } + + fn commit_reparent(self: &Arc, committing: &Arc) { + let mut guard = self.parent.lock().expect("poisoned lock"); + if let NodeStoreParent::Proposed(ref parent) = *guard + && Arc::ptr_eq(parent, committing) + { + *guard = NodeStoreParent::Committed(committing.root_hash()); + } + } } /// Contains the state of a revision of a merkle trie. @@ -605,7 +598,7 @@ impl TryFrom> header, kind: Arc::new(ImmutableProposal { deleted: kind.deleted.into(), - parent: Arc::new(ArcSwap::new(Arc::new(kind.parent))), + parent: Arc::new(std::sync::Mutex::new(kind.parent)), root: None, }), storage, @@ -874,7 +867,6 @@ mod tests { use crate::LeafNode; use crate::linear::memory::MemStore; - use arc_swap::access::DynGuard; use super::*; use primitives::area_size_iter; @@ -923,21 +915,25 @@ mod tests { // create an empty r1, check that it's parent is the empty committed version let r1 = NodeStore::new(&base).unwrap(); let r1: NodeStore, _> = r1.try_into().unwrap(); - let parent: DynGuard> = r1.kind.parent.load(); - assert!(matches!(**parent, NodeStoreParent::Committed(None))); + { + let parent = r1.kind.parent.lock().expect("poisoned lock"); + assert!(matches!(*parent, NodeStoreParent::Committed(None))); + } // create an empty r2, check that it's parent is the proposed version r1 let r2: NodeStore = NodeStore::new(&r1).unwrap(); let r2: NodeStore, _> = r2.try_into().unwrap(); - let parent: DynGuard> = r2.kind.parent.load(); - assert!(matches!(**parent, NodeStoreParent::Proposed(_))); + { + let parent = r2.kind.parent.lock().expect("poisoned lock"); + assert!(matches!(*parent, NodeStoreParent::Proposed(_))); + } // reparent r2 r1.commit_reparent(&r2); // now check r2's parent, should match the hash of r1 (which is still None) - let parent: DynGuard> = r2.kind.parent.load(); - if let NodeStoreParent::Committed(hash) = &**parent { + let parent = r2.kind.parent.lock().expect("poisoned lock"); + if let NodeStoreParent::Committed(hash) = &*parent { assert_eq!(*hash, r1.root_hash()); assert_eq!(*hash, None); } else { From 2ab4570f8f303e9b03ff0fb024916a28e748c682 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 18 Nov 2025 09:06:26 -0800 Subject: [PATCH 1037/1053] feat(ffi): fill in ffi methods for range proofs (#1429) --- ffi/firewood.h | 12 +- ffi/proofs.go | 76 ++++++++++-- ffi/proofs_test.go | 244 ++++++++++++++++++++++++++++++++---- ffi/src/handle.rs | 20 ++- ffi/src/proofs/range.rs | 252 +++++++++++++++++++++++++++++++++----- ffi/src/proposal.rs | 60 +++++---- ffi/src/value/borrowed.rs | 9 ++ ffi/src/value/results.rs | 10 +- 8 files changed, 574 insertions(+), 109 deletions(-) diff --git a/ffi/firewood.h b/ffi/firewood.h index 242ee7779105..5d9882508bc6 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -1278,8 +1278,8 @@ struct HashResult fwd_db_verify_and_commit_change_proof(const struct DatabaseHan * concurrently. The caller must ensure exclusive access to the proof context * for the duration of the call. */ -struct HashResult fwd_db_verify_and_commit_range_proof(const struct DatabaseHandle *_db, - struct VerifyRangeProofArgs _args); +struct HashResult fwd_db_verify_and_commit_range_proof(const struct DatabaseHandle *db, + struct VerifyRangeProofArgs args); /** * Verify a change proof and prepare a proposal to later commit or drop. @@ -1330,8 +1330,8 @@ struct VoidResult fwd_db_verify_change_proof(const struct DatabaseHandle *_db, * concurrently. The caller must ensure exclusive access to the proof context * for the duration of the call. */ -struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *_db, - struct VerifyRangeProofArgs _args); +struct VoidResult fwd_db_verify_range_proof(const struct DatabaseHandle *db, + struct VerifyRangeProofArgs args); /** * Frees the memory associated with a `ChangeProofContext`. @@ -1862,7 +1862,7 @@ struct ProposalResult fwd_propose_on_proposal(const struct ProposalHandle *handl * concurrently. The caller must ensure exclusive access to the proof context * for the duration of the call. */ -struct NextKeyRangeResult fwd_range_proof_find_next_key(struct RangeProofContext *_proof); +struct NextKeyRangeResult fwd_range_proof_find_next_key(struct RangeProofContext *proof); /** * Deserialize a `RangeProof` from bytes. @@ -1920,7 +1920,7 @@ struct ValueResult fwd_range_proof_to_bytes(const struct RangeProofContext *proo * concurrently. The caller must ensure exclusive access to the proof context * for the duration of the call. */ -struct VoidResult fwd_range_proof_verify(struct VerifyRangeProofArgs _args); +struct VoidResult fwd_range_proof_verify(struct VerifyRangeProofArgs args); /** * Get the root hash of the latest version of the database diff --git a/ffi/proofs.go b/ffi/proofs.go index 94bc7d7a80cd..56883928b17b 100644 --- a/ffi/proofs.go +++ b/ffi/proofs.go @@ -8,6 +8,7 @@ package ffi import "C" import ( + "encoding" "errors" "fmt" "runtime" @@ -19,10 +20,44 @@ var ( errEmptyTrie = errors.New("a range proof was requested on an empty trie") ) +var ( + _ encoding.BinaryMarshaler = (*RangeProof)(nil) + _ encoding.BinaryUnmarshaler = (*RangeProof)(nil) +) + // RangeProof represents a proof that a range of keys and their values are // included in a trie with a given root hash. +// +// RangeProofs can be created via [Database.RangeProof] and are marshallable via +// [encoding.BinaryMarshaler] and [encoding.BinaryUnmarshaler]. They can be +// verified independent of a database via [RangeProof.Verify] or with a database +// via [Database.VerifyRangeProof]. Verified range proofs can be committed to a +// database via [Database.VerifyAndCommitRangeProof] (where verification will +// be skipped if it was already verified). Verifying a range proof with a +// database will optimistically prepare a proposal that can be committed later. type RangeProof struct { + // handle is an opaque pointer to the range proof within Firewood. It should be + // passed to the C FFI functions that operate on range proofs + // + // It is not safe to call these methods with a nil handle. + // + // Calls to `C.fwd_free_range_proof` will invalidate this handle, so it + // should not be used after those calls. + // + // Calls to `C.fwd_db_verify_range_proof` will cause the range proof to + // build and retain ownership of an embedded proposal, which also retains + // a reference to the database. Therefore, while the range proof owns an + // embedded proposal, the database must be kept alive. The proposal and + // reference to the database are released after calling + // `C.fwd_db_verify_and_commit_range_proof` or `C.fwd_free_range_proof`. handle *C.RangeProofContext + + // keepAliveHandle keeps the database alive while this range proof + // owns an embedded proposal. It is initialized when the range proof is + // verified with a database handle ([Database.VerifyRangeProof]) and not by + // unmarshalling or when [RangeProof.Verify] is used. It is disowned after + // [Database.VerifyAndCommitRangeProof] or [RangeProof.Free]. + keepAliveHandle databaseKeepAliveHandle } // ChangeProof represents a proof of changes between two roots for a range of keys. @@ -93,6 +128,9 @@ func (p *RangeProof) Verify( // the proof is valid, a proposal containing the changes is prepared. The // call to [*Database.VerifyAndCommitRangeProof] will skip verification and commit the // prepared proposal. +// +// Because this method prepares a proposal, the database must be kept alive +// until the proof is committed or freed. func (db *Database) VerifyRangeProof( proof *RangeProof, startKey, endKey Maybe[[]byte], @@ -110,7 +148,14 @@ func (db *Database) VerifyRangeProof( max_length: C.uint32_t(maxLength), } - return getErrorFromVoidResult(C.fwd_db_verify_range_proof(db.handle, args)) + if err := getErrorFromVoidResult(C.fwd_db_verify_range_proof(db.handle, args)); err != nil { + return err + } + + // keep the database alive while the proof owns the embedded proposal + proof.keepAliveHandle.init(&db.outstandingHandles) + runtime.SetFinalizer(proof, (*RangeProof).Free) + return nil } // VerifyAndCommitRangeProof verifies the provided range [proof] proves the values @@ -139,7 +184,13 @@ func (db *Database) VerifyAndCommitRangeProof( max_length: C.uint32_t(maxLength), } - return getHashKeyFromHashResult(C.fwd_db_verify_and_commit_range_proof(db.handle, args)) + var hash Hash + err := proof.keepAliveHandle.disown(true /* evenOnError */, func() error { + var err error + hash, err = getHashKeyFromHashResult(C.fwd_db_verify_and_commit_range_proof(db.handle, args)) + return err + }) + return hash, err } // FindNextKey returns the next key range to fetch for this proof, if any. If the @@ -185,17 +236,18 @@ func (p *RangeProof) UnmarshalBinary(data []byte) error { // It is safe to call Free more than once; subsequent calls after the first // will be no-ops. func (p *RangeProof) Free() error { - if p.handle == nil { - return nil - } - - if err := getErrorFromVoidResult(C.fwd_free_range_proof(p.handle)); err != nil { - return err - } + return p.keepAliveHandle.disown(false /* evenOnError */, func() error { + if p.handle == nil { + return nil + } - p.handle = nil + if err := getErrorFromVoidResult(C.fwd_free_range_proof(p.handle)); err != nil { + return err + } - return nil + p.handle = nil + return nil + }) } // ChangeProof returns a proof that the changes between [startRoot] and @@ -364,7 +416,7 @@ func (r *NextKeyRange) Free() error { var err1, err2 error err1 = r.startKey.Free() - if r.endKey.HasValue() { + if r.endKey != nil && r.endKey.HasValue() { err2 = r.endKey.Value().Free() } diff --git a/ffi/proofs_test.go b/ffi/proofs_test.go index 1f19c8f74531..a0cb9d36d7da 100644 --- a/ffi/proofs_test.go +++ b/ffi/proofs_test.go @@ -4,12 +4,16 @@ package ffi import ( + "runtime" "testing" "github.com/stretchr/testify/require" ) -const maxProofLen = 10 +const ( + rangeProofLenUnbounded = 0 + rangeProofLenTruncated = 10 +) type maybe struct { value []byte @@ -37,11 +41,60 @@ func nothing() maybe { } } +// assertProofNotNil verifies that the given proof and its inner handle are not nil. +func assertProofNotNil(t *testing.T, proof *RangeProof) { + t.Helper() + r := require.New(t) + r.NotNil(proof) + r.NotNil(proof.handle) +} + +// newVerifiedRangeProof generates a range proof for the given parameters and +// verifies using [RangeProof.Verify] which does not prepare a proposal. A +// cleanup is registered to free the proof when the test ends. +func newVerifiedRangeProof( + t *testing.T, + db *Database, + root Hash, + startKey, endKey maybe, + proofLen uint32, +) *RangeProof { + r := require.New(t) + + proof, err := db.RangeProof(root, startKey, endKey, proofLen) + r.NoError(err) + assertProofNotNil(t, proof) + t.Cleanup(func() { r.NoError(proof.Free()) }) + + r.NoError(proof.Verify(root, startKey, endKey, proofLen)) + + return proof +} + +// newSerializedRangeProof generates a range proof for the given parameters and +// returns its serialized bytes. +func newSerializedRangeProof( + t *testing.T, + db *Database, + root Hash, + startKey, endKey maybe, + proofLen uint32, +) []byte { + r := require.New(t) + + proof := newVerifiedRangeProof(t, db, root, startKey, endKey, proofLen) + + proofBytes, err := proof.MarshalBinary() + r.NoError(err) + + return proofBytes +} + func TestRangeProofEmptyDB(t *testing.T) { r := require.New(t) db := newTestDatabase(t) - proof, err := db.RangeProof(EmptyRoot, nothing(), nothing(), 0) + proof, err := db.RangeProof(EmptyRoot, nothing(), nothing(), rangeProofLenUnbounded) r.ErrorIs(err, errRevisionNotFound) r.Nil(proof) } @@ -58,7 +111,7 @@ func TestRangeProofNonExistentRoot(t *testing.T) { // create a bogus root root[0] ^= 0xFF - proof, err := db.RangeProof(root, nothing(), nothing(), 0) + proof, err := db.RangeProof(root, nothing(), nothing(), rangeProofLenUnbounded) r.ErrorIs(err, errRevisionNotFound) r.Nil(proof) } @@ -73,15 +126,13 @@ func TestRangeProofPartialRange(t *testing.T) { r.NoError(err) // get a proof over some partial range - proof1 := rangeProof(t, db, root, nothing(), nothing()) + proof1 := newSerializedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenTruncated) // get a proof over a different range - proof2 := rangeProof(t, db, root, something([]byte("key2")), something([]byte("key3"))) + proof2 := newSerializedRangeProof(t, db, root, something([]byte("key2")), something([]byte("key3")), rangeProofLenTruncated) // ensure the proofs are different r.NotEqual(proof1, proof2) - - // TODO(https://github.com/ava-labs/firewood/issues/738): verify the proofs } func TestRangeProofDiffersAfterUpdate(t *testing.T) { @@ -94,7 +145,7 @@ func TestRangeProofDiffersAfterUpdate(t *testing.T) { r.NoError(err) // get a proof - proof := rangeProof(t, db, root1, nothing(), nothing()) + proof := newSerializedRangeProof(t, db, root1, nothing(), nothing(), rangeProofLenTruncated) // insert more data root2, err := db.Update(keys[50:], vals[50:]) @@ -102,7 +153,7 @@ func TestRangeProofDiffersAfterUpdate(t *testing.T) { r.NotEqual(root1, root2) // get a proof again - proof2 := rangeProof(t, db, root2, nothing(), nothing()) + proof2 := newSerializedRangeProof(t, db, root2, nothing(), nothing(), rangeProofLenTruncated) // ensure the proofs are different r.NotEqual(proof, proof2) @@ -118,36 +169,185 @@ func TestRoundTripSerialization(t *testing.T) { r.NoError(err) // get a proof - proofBytes := rangeProof(t, db, root, nothing(), nothing()) + proofBytes := newSerializedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenUnbounded) // Deserialize the proof. proof := new(RangeProof) err = proof.UnmarshalBinary(proofBytes) r.NoError(err) + t.Cleanup(func() { r.NoError(proof.Free()) }) // serialize the proof again serialized, err := proof.MarshalBinary() r.NoError(err) r.Equal(proofBytes, serialized) +} - r.NoError(proof.Free()) +func TestRangeProofVerify(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(100) + root, err := db.Update(keys, vals) + r.NoError(err) + + proof := newVerifiedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenTruncated) + + // Database should be immediately closeable (no keep-alive) + r.NoError(db.Close(oneSecCtx(t))) + + // Verify with wrong root should fail + root[0] ^= 0xFF + err = proof.Verify(root, nothing(), nothing(), rangeProofLenTruncated) + + // TODO(#738): re-enable after verification is implemented + // r.Error(err, "Verification with wrong root should fail") + r.NoError(err) } -// rangeProof generates a range proof for the given parameters. -func rangeProof( - t *testing.T, - db *Database, - root Hash, - startKey, endKey maybe, -) []byte { +func TestVerifyAndCommitRangeProof(t *testing.T) { r := require.New(t) - proof, err := db.RangeProof(root, startKey, endKey, maxProofLen) + // Create source and target databases + dbSource := newTestDatabase(t) + dbTarget := newTestDatabase(t) + + // Populate source + keys, vals := kvForTest(50) + sourceRoot, err := dbSource.Update(keys, vals) r.NoError(err) - r.NotNil(proof) - proofBytes, err := proof.MarshalBinary() + + proof := newVerifiedRangeProof(t, dbSource, sourceRoot, nothing(), nothing(), rangeProofLenUnbounded) + + // Verify and commit to target without previously calling db.VerifyRangeProof + committedRoot, err := dbTarget.VerifyAndCommitRangeProof(proof, nothing(), nothing(), sourceRoot, rangeProofLenUnbounded) + r.NoError(err) + r.Equal(sourceRoot, committedRoot) + + // Verify all keys are now in target database + for i, key := range keys { + got, err := dbTarget.Get(key) + r.NoError(err, "Get key %d", i) + r.Equal(vals[i], got, "Value mismatch for key %d", i) + } +} + +func TestRangeProofFindNextKey(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + + keys, vals := kvForTest(100) + root, err := db.Update(keys, vals) + r.NoError(err) + + proof := newVerifiedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenTruncated) + + // FindNextKey should fail before preparing a proposal or committing + _, err = proof.FindNextKey() + r.ErrorIs(err, errNotPrepared, "FindNextKey should fail on unverified proof") + + // Verify the proof + r.NoError(db.VerifyRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated)) + + // Now FindNextKey should work + nextRange, err := proof.FindNextKey() + r.NoError(err) + r.NotNil(nextRange) + startKey := nextRange.StartKey() + r.NotEmpty(startKey) + startKey = append([]byte{}, startKey...) // copy to new slice to avoid use-after-free + r.NoError(nextRange.Free()) + + _, err = db.VerifyAndCommitRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated) r.NoError(err) + + // FindNextKey should still work after commit + nextRange, err = proof.FindNextKey() + r.NoError(err) + r.NotNil(nextRange) + r.Equal(nextRange.StartKey(), startKey) + r.NoError(nextRange.Free()) +} + +func TestRangeProofFreeReleasesKeepAlive(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(50) + root, err := db.Update(keys, vals) + r.NoError(err) + + proof := newVerifiedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenTruncated) + r.NoError(err) + + // prepare proposal (acquires keep-alive) + r.NoError(db.VerifyRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated)) + + // Database should not be closeable while proof has keep-alive + r.ErrorIs(db.Close(oneSecCtx(t)), ErrActiveKeepAliveHandles) + + // Free the proof (releases keep-alive) r.NoError(proof.Free()) - return proofBytes + // Database should now be closeable + r.NoError(db.Close(oneSecCtx(t))) +} + +func TestRangeProofCommitReleasesKeepAlive(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(50) + root, err := db.Update(keys, vals) + r.NoError(err) + + proof := newVerifiedRangeProof(t, db, root, nothing(), nothing(), rangeProofLenTruncated) + marshalledBeforeCommit, err := proof.MarshalBinary() + r.NoError(err) + + // prepare proposal (acquires keep-alive) + r.NoError(db.VerifyRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated)) + + // Database should not be closeable while proof has keep-alive + r.ErrorIs(db.Close(oneSecCtx(t)), ErrActiveKeepAliveHandles) + + // Commit the proof (releases keep-alive) + _, err = db.VerifyAndCommitRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated) + r.NoError(err) + + // Database should now be closeable + r.NoError(db.Close(oneSecCtx(t))) + + marshalledAfterCommit, err := proof.MarshalBinary() + r.NoError(err) + + // methods like MarshalBinary should still work after commit and closing the database + r.Equal(marshalledBeforeCommit, marshalledAfterCommit) +} + +// TestRangeProofFinalizerCleanup verifies that the finalizer properly releases +// the keep-alive handle when the proof goes out of scope. +func TestRangeProofFinalizerCleanup(t *testing.T) { + r := require.New(t) + db := newTestDatabase(t) + keys, vals := kvForTest(50) + root, err := db.Update(keys, vals) + r.NoError(err) + + // note: this does not use newVerifiedRangeProof because it sets a cleanup + // which retains a handle to the proof blocking our ability to wait for the + // finalizer to run + proof, err := db.RangeProof(root, nothing(), nothing(), rangeProofLenTruncated) + r.NoError(err) + assertProofNotNil(t, proof) + + // prepare proposal (acquires keep-alive) + r.NoError(db.VerifyRangeProof(proof, nothing(), nothing(), root, rangeProofLenTruncated)) + + // Database should not be closeable while proof has keep-alive + r.ErrorIs(db.Close(oneSecCtx(t)), ErrActiveKeepAliveHandles) + + runtime.KeepAlive(proof) + proof = nil //nolint:ineffassign // necessary to drop the reference for GC + runtime.GC() + + r.NoError(db.Close(t.Context()), "Database should be closeable after proof is garbage collected") } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index b4061978b9ae..371b7c8e7e63 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -4,7 +4,7 @@ use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, - v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, KeyType}, + v2::api::{self, ArcDynDbView, Db as _, DbView, HashKey, HashKeyExt, IntoBatchIter, KeyType}, }; use crate::{BorrowedBytes, CView, CreateProposalResult, KeyValuePair, arc_cache::ArcCache}; @@ -211,6 +211,18 @@ impl DatabaseHandle { pub(crate) fn clear_cached_view(&self) { self.cached_view.clear(); } + + pub(crate) fn merge_key_value_range( + &self, + first_key: Option, + last_key: Option, + key_values: impl IntoIterator, + ) -> Result, api::Error> { + CreateProposalResult::new(self, || { + self.db + .merge_key_value_range(first_key, last_key, key_values) + }) + } } impl From for DatabaseHandle { @@ -227,11 +239,11 @@ impl<'db> CView<'db> for &'db crate::DatabaseHandle { self } - fn create_proposal<'kvp>( + fn create_proposal( self, - values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + values: impl IntoBatchIter, ) -> Result, api::Error> { - self.db.propose(values.as_ref().iter()) + self.db.propose(values) } } diff --git a/ffi/src/proofs/range.rs b/ffi/src/proofs/range.rs index 0fbd55b9cf53..1bd76b705871 100644 --- a/ffi/src/proofs/range.rs +++ b/ffi/src/proofs/range.rs @@ -3,10 +3,13 @@ use std::num::NonZeroUsize; -use firewood::v2::api::{self, FrozenRangeProof}; +use firewood::{ + logger::warn, + v2::api::{self, DbView, FrozenRangeProof, HashKey}, +}; use crate::{ - BorrowedBytes, CResult, DatabaseHandle, HashKey, HashResult, Maybe, NextKeyRangeResult, + BorrowedBytes, DatabaseHandle, HashResult, Maybe, NextKeyRange, NextKeyRangeResult, RangeProofResult, ValueResult, VoidResult, }; @@ -15,7 +18,7 @@ use crate::{ #[repr(C)] pub struct CreateRangeProofArgs<'a> { /// The root hash of the revision to prove. - pub root: HashKey, + pub root: crate::HashKey, /// The start key of the range to prove. If `None`, the range starts from the /// beginning of the keyspace. /// @@ -37,14 +40,14 @@ pub struct CreateRangeProofArgs<'a> { /// Arguments for verifying a range proof. #[derive(Debug)] #[repr(C)] -pub struct VerifyRangeProofArgs<'a> { +pub struct VerifyRangeProofArgs<'a, 'db> { /// The range proof to verify. If null, the function will return /// [`VoidResult::NullHandlePointer`]. We need a mutable reference to /// update the validation context. - pub proof: Option<&'a mut RangeProofContext>, + pub proof: Option<&'a mut RangeProofContext<'db>>, /// The root hash to verify the proof against. This must match the calculated /// hash of the root of the proof. - pub root: HashKey, + pub root: crate::HashKey, /// The lower bound of the key range that the proof is expected to cover. If /// `None`, the proof is expected to cover from the start of the keyspace. /// @@ -65,26 +68,126 @@ pub struct VerifyRangeProofArgs<'a> { /// FFI context for for a parsed or generated range proof. #[derive(Debug)] -pub struct RangeProofContext { +pub struct RangeProofContext<'db> { proof: FrozenRangeProof, - /// Information about the proof discovered during verification that does not - /// need to be recomputed. Also serves as a token that ensured we have - /// validated the proof and can skip it during commit. - _validation_context: (), // placeholder for future use - /// Information about the proof after it has been committed to the DB. This - /// allows for easy introspection of the specific revision that was committed - /// and is needed to optimize discovery of the next key/range as well as - /// other introspective optimizations. - _commit_context: (), // placeholder for future use + state: RangeProofState<'db>, +} + +#[derive(Debug)] +enum RangeProofState<'db> { + Unverified, + Verified, + Proposed(crate::ProposalHandle<'db>), + Committed(Option), } -impl From for RangeProofContext { +impl From for RangeProofContext<'_> { fn from(proof: FrozenRangeProof) -> Self { Self { proof, - _validation_context: (), - _commit_context: (), + state: RangeProofState::Unverified, + } + } +} + +impl<'db> RangeProofState<'db> { + const fn is_verified(&self) -> bool { + !matches!(self, Self::Unverified) + } + + const fn is_proposed_or_committed(&self) -> bool { + matches!(self, Self::Proposed(_) | Self::Committed(_)) + } + + const fn is_committed(&self) -> bool { + matches!(self, Self::Committed(_)) + } + + fn take_proposal(&mut self) -> Option> { + match std::mem::replace(self, Self::Unverified) { + Self::Proposed(proposal) => Some(proposal), + other => { + // put it back (so we don't need to write unsafe/panicking code) + *self = other; + None + } + } + } + + fn root_hash(&self) -> Result, api::Error> { + match self { + Self::Committed(root) => Ok(root.clone()), + Self::Proposed(proposal) => proposal.root_hash(), + _ => Ok(None), + } + } +} + +impl<'db> RangeProofContext<'db> { + fn verify( + &mut self, + _root: HashKey, + _start_key: Option<&[u8]>, + _end_key: Option<&[u8]>, + _max_length: Option, + ) -> Result<(), api::Error> { + if !self.state.is_verified() { + return Ok(()); + } + + warn!("range proof verification not yet implemented"); + self.state = RangeProofState::Verified; + Ok(()) + } + + fn verify_and_propose( + &mut self, + db: &'db crate::DatabaseHandle, + root: HashKey, + start_key: Option<&[u8]>, + end_key: Option<&[u8]>, + max_length: Option, + ) -> Result<(), api::Error> { + if self.state.is_proposed_or_committed() { + return Ok(()); } + + self.verify(root, start_key, end_key, max_length)?; + + let proposal = db.merge_key_value_range(start_key, end_key, self.proof.key_values())?; + self.state = RangeProofState::Proposed(proposal.handle); + + Ok(()) + } + + fn verify_and_commit( + &mut self, + db: &'db crate::DatabaseHandle, + root: HashKey, + start_key: Option<&[u8]>, + end_key: Option<&[u8]>, + max_length: Option, + ) -> Result, api::Error> { + if self.state.is_committed() { + return self.state.root_hash(); + } + + self.verify(root, start_key, end_key, max_length)?; + + let proposal_handle = if let Some(proposal) = self.state.take_proposal() { + proposal + } else { + db.merge_key_value_range(start_key, end_key, self.proof.key_values())? + .handle + }; + + let hash = proposal_handle.commit_proposal(|commit_time| { + metrics::counter!("firewood.ffi.commit_ms").increment(commit_time.as_millis()); + metrics::counter!("firewood.ffi.merge").increment(1); + })?; + + self.state = RangeProofState::Committed(hash.clone()); + Ok(hash) } } @@ -107,7 +210,10 @@ impl From for RangeProofContext { pub extern "C" fn fwd_db_range_proof( db: Option<&DatabaseHandle>, args: CreateRangeProofArgs, -) -> RangeProofResult { +) -> RangeProofResult<'static> { + // static lifetime is safe because the returned `RangeProofResult` does not + // retain a reference to the provided database handle. + crate::invoke_with_handle(db, |db| { let view = db.get_root(args.root.into())?; view.range_proof( @@ -145,8 +251,25 @@ pub extern "C" fn fwd_db_range_proof( /// concurrently. The caller must ensure exclusive access to the proof context /// for the duration of the call. #[unsafe(no_mangle)] -pub extern "C" fn fwd_range_proof_verify(_args: VerifyRangeProofArgs) -> VoidResult { - CResult::from_err("not yet implemented") +pub extern "C" fn fwd_range_proof_verify(args: VerifyRangeProofArgs) -> VoidResult { + let VerifyRangeProofArgs { + proof, + root, + start_key, + end_key, + max_length, + } = args; + + crate::invoke_with_handle(proof, |ctx| { + let start_key = start_key.into_option(); + let end_key = end_key.into_option(); + ctx.verify( + root.into(), + start_key.as_deref(), + end_key.as_deref(), + NonZeroUsize::new(max_length as usize), + ) + }) } /// Verify a range proof and prepare a proposal to later commit or drop. If the @@ -172,11 +295,31 @@ pub extern "C" fn fwd_range_proof_verify(_args: VerifyRangeProofArgs) -> VoidRes /// concurrently. The caller must ensure exclusive access to the proof context /// for the duration of the call. #[unsafe(no_mangle)] -pub extern "C" fn fwd_db_verify_range_proof( - _db: Option<&DatabaseHandle>, - _args: VerifyRangeProofArgs, +pub extern "C" fn fwd_db_verify_range_proof<'db>( + db: Option<&'db DatabaseHandle>, + args: VerifyRangeProofArgs<'_, 'db>, ) -> VoidResult { - CResult::from_err("not yet implemented") + let VerifyRangeProofArgs { + proof, + root, + start_key, + end_key, + max_length, + } = args; + + let handle = db.and_then(|db| proof.map(|p| (db, p))); + + crate::invoke_with_handle(handle, |(db, ctx)| { + let start_key = start_key.into_option(); + let end_key = end_key.into_option(); + ctx.verify_and_propose( + db, + root.into(), + start_key.as_deref(), + end_key.as_deref(), + NonZeroUsize::new(max_length as usize), + ) + }) } /// Verify and commit a range proof to the database. @@ -210,11 +353,31 @@ pub extern "C" fn fwd_db_verify_range_proof( /// concurrently. The caller must ensure exclusive access to the proof context /// for the duration of the call. #[unsafe(no_mangle)] -pub extern "C" fn fwd_db_verify_and_commit_range_proof( - _db: Option<&DatabaseHandle>, - _args: VerifyRangeProofArgs, +pub extern "C" fn fwd_db_verify_and_commit_range_proof<'db>( + db: Option<&'db DatabaseHandle>, + args: VerifyRangeProofArgs<'_, 'db>, ) -> HashResult { - CResult::from_err("not yet implemented") + let VerifyRangeProofArgs { + proof, + root, + start_key, + end_key, + max_length, + } = args; + + let handle = db.and_then(|db| proof.map(|p| (db, p))); + + crate::invoke_with_handle(handle, |(db, ctx)| { + let start_key = start_key.into_option(); + let end_key = end_key.into_option(); + ctx.verify_and_commit( + db, + root.into(), + start_key.as_deref(), + end_key.as_deref(), + NonZeroUsize::new(max_length as usize), + ) + }) } /// Returns the next key range that should be fetched after processing the @@ -247,9 +410,30 @@ pub extern "C" fn fwd_db_verify_and_commit_range_proof( /// for the duration of the call. #[unsafe(no_mangle)] pub extern "C" fn fwd_range_proof_find_next_key( - _proof: Option<&mut RangeProofContext>, + proof: Option<&mut RangeProofContext>, ) -> NextKeyRangeResult { - CResult::from_err("not yet implemented") + // TODO(#352): proper implementation, this naively retuns the last key in + // in the range, which is correct, but not sufficient. + crate::invoke_with_handle(proof, |ctx| match ctx.state { + RangeProofState::Unverified | RangeProofState::Verified => NextKeyRangeResult::NotPrepared, + RangeProofState::Proposed(_) | RangeProofState::Committed(_) => { + if ctx.proof.end_proof().is_empty() { + // unbounded, so we are done + NextKeyRangeResult::None + } else { + match ctx.proof.key_values().last() { + Some((key, _)) => NextKeyRangeResult::Some(NextKeyRange { + start_key: key.clone().into(), + end_key: None.into(), + }), + None => { + // no key-values in the proof, so we are done + NextKeyRangeResult::None + } + } + } + } + }) } /// Serialize a `RangeProof` to bytes. @@ -288,7 +472,9 @@ pub extern "C" fn fwd_range_proof_to_bytes(proof: Option<&RangeProofContext>) -> /// well-formed. The verify method must be called to ensure the proof is cryptographically valid. /// - [`RangeProofResult::Err`] containing an error message if the proof could not be parsed. #[unsafe(no_mangle)] -pub extern "C" fn fwd_range_proof_from_bytes(bytes: BorrowedBytes) -> RangeProofResult { +pub extern "C" fn fwd_range_proof_from_bytes( + bytes: BorrowedBytes<'_>, +) -> RangeProofResult<'static> { crate::invoke(move || { FrozenRangeProof::from_slice(&bytes).map_err(|err| { api::Error::ProofError(firewood::proof::ProofError::Deserialization(err)) diff --git a/ffi/src/proposal.rs b/ffi/src/proposal.rs index 1281a9993539..cbceefc3ed55 100644 --- a/ffi/src/proposal.rs +++ b/ffi/src/proposal.rs @@ -1,9 +1,7 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, Proposal as _}; - -use crate::value::KeyValuePair; +use firewood::v2::api::{self, BoxKeyValueIter, DbView, HashKey, IntoBatchIter, Proposal as _}; use crate::iterator::CreateIteratorResult; use metrics::counter; @@ -115,6 +113,30 @@ pub struct CreateProposalResult<'db> { pub start_time: coarsetime::Instant, } +impl<'db> CreateProposalResult<'db> { + pub(crate) fn new( + handle: &'db crate::DatabaseHandle, + f: impl FnOnce() -> Result, api::Error>, + ) -> Result { + let start_time = coarsetime::Instant::now(); + let proposal = f()?; + let propose_time = start_time.elapsed(); + counter!("firewood.ffi.propose_ms").increment(propose_time.as_millis()); + counter!("firewood.ffi.propose").increment(1); + + let hash_key = proposal.root_hash()?; + + Ok(CreateProposalResult { + handle: ProposalHandle { + hash_key, + proposal, + handle, + }, + start_time, + }) + } +} + /// A trait that abstracts over database handles and proposal handles for creating proposals. /// /// This trait allows functions to work with both [`DatabaseHandle`] and [`ProposalHandle`] @@ -140,9 +162,9 @@ pub trait CView<'db> { /// /// This function will return a database error if the proposal could not be /// created. - fn create_proposal<'kvp>( + fn create_proposal( self, - values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + values: impl IntoBatchIter, ) -> Result, api::Error>; /// Create a [`ProposalHandle`] from the values and return it with timing @@ -152,31 +174,15 @@ pub trait CView<'db> { /// /// This function will return a database error if the proposal could not be /// created or if the proposal is empty. - fn create_proposal_handle<'kvp>( + fn create_proposal_handle( self, - values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + values: impl IntoBatchIter, ) -> Result, api::Error> where Self: Sized, { let handle = self.handle(); - - let start_time = coarsetime::Instant::now(); - let proposal = self.create_proposal(values)?; - let propose_time = start_time.elapsed(); - counter!("firewood.ffi.propose_ms").increment(propose_time.as_millis()); - counter!("firewood.ffi.propose").increment(1); - - let hash_key = proposal.root_hash()?; - - Ok(CreateProposalResult { - handle: ProposalHandle { - hash_key, - proposal, - handle, - }, - start_time, - }) + CreateProposalResult::new(handle, || self.create_proposal(values)) } } @@ -185,10 +191,10 @@ impl<'db> CView<'db> for &ProposalHandle<'db> { self.handle } - fn create_proposal<'kvp>( + fn create_proposal( self, - values: impl AsRef<[KeyValuePair<'kvp>]> + 'kvp, + values: impl IntoBatchIter, ) -> Result, api::Error> { - self.proposal.propose(values.as_ref()) + self.proposal.propose(values) } } diff --git a/ffi/src/value/borrowed.rs b/ffi/src/value/borrowed.rs index 72881a9d48ab..39eb86a87a18 100644 --- a/ffi/src/value/borrowed.rs +++ b/ffi/src/value/borrowed.rs @@ -130,6 +130,15 @@ impl Ord for BorrowedSlice<'_, T> { } } +impl<'a, T> IntoIterator for BorrowedSlice<'a, T> { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.as_slice().iter() + } +} + impl fmt::Display for BorrowedBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let precision = f.precision().unwrap_or(64); diff --git a/ffi/src/value/results.rs b/ffi/src/value/results.rs index 20b2d206a590..ce25ac64b58d 100644 --- a/ffi/src/value/results.rs +++ b/ffi/src/value/results.rs @@ -190,7 +190,7 @@ impl From, E>> for HashResult { /// [`fwd_free_range_proof`]: crate::fwd_free_range_proof #[derive(Debug)] #[repr(C)] -pub enum RangeProofResult { +pub enum RangeProofResult<'db> { /// The caller provided a null pointer to the input handle. NullHandlePointer, /// The provided root was not found in the database. @@ -202,7 +202,7 @@ pub enum RangeProofResult { /// If the value was parsed from a serialized proof, this does not imply that /// the proof is valid, only that it is well-formed. The verify method must /// be called to ensure the proof is cryptographically valid. - Ok(Box), + Ok(Box>), /// An error occurred and the message is returned as an [`OwnedBytes`]. If /// value is guaranteed to contain only valid UTF-8. /// @@ -213,7 +213,7 @@ pub enum RangeProofResult { Err(OwnedBytes), } -impl From> for RangeProofResult { +impl From> for RangeProofResult<'_> { fn from(value: Result) -> Self { match value { Ok(proof) => RangeProofResult::Ok(Box::new(proof.into())), @@ -538,7 +538,7 @@ impl_null_handle_result!( VoidResult, ValueResult, HashResult, - RangeProofResult, + RangeProofResult<'_>, ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, @@ -553,7 +553,7 @@ impl_cresult!( ValueResult, HashResult, HandleResult, - RangeProofResult, + RangeProofResult<'_>, ChangeProofResult, NextKeyRangeResult, ProposalResult<'_>, From 40f93a4bc39a667939c9b40fdd5cdf856f91cd13 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:26:46 -0500 Subject: [PATCH 1038/1053] ci: relegate build equivalent check to post-merge job (#1469) As discussed offline and in #1460, the `ffi-nix` job takes around 15 minutes to complete and runs on every PR. This PR changes it so that the `ffi-nix` job runs only on commits to `main`. --- .github/workflows/ci.yaml | 11 ----------- .github/workflows/ffi-nix.yaml | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/ffi-nix.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a632ae036657..3274a7a81f49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -244,17 +244,6 @@ jobs: # cgocheck2 is expensive but provides complete pointer checks run: GOEXPERIMENT=cgocheck2 TEST_FIREWOOD_HASH_MODE=firewood go test -race ./... - ffi-nix: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 #v20 - - uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 #v13 - - name: Check that FFI flake is up-to-date - run: ./scripts/run-just.sh check-ffi-flake - - name: Test nix build of Golang FFI bindings - run: ./scripts/run-just.sh test-ffi-nix - firewood-ethhash-differential-fuzz: needs: build runs-on: ubuntu-latest diff --git a/.github/workflows/ffi-nix.yaml b/.github/workflows/ffi-nix.yaml new file mode 100644 index 000000000000..c0962eacd614 --- /dev/null +++ b/.github/workflows/ffi-nix.yaml @@ -0,0 +1,18 @@ +name: ffi-nix + +on: + push: + branches: [main] + +jobs: + ffi-nix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 #v20 + - uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 #v13 + - name: Check that FFI flake is up-to-date + run: ./scripts/run-just.sh check-ffi-flake + - name: Test nix build of Golang FFI bindings + run: ./scripts/run-just.sh test-ffi-nix + From 8b6e408976b0ae46394f8fc45e55f94505986080 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:43:47 -0500 Subject: [PATCH 1039/1053] feat(firewood/ffi): add `FjallStore` (#1395) This PR adds `FjallStore`, an implementation of `RootStore` that allows for persistent storage of revisions (in contrast to `MockStore`, which keeps everything in-memory). The main changes of this PR are as follows: - Adds `FjallStore` implementation - Extends the `DbConfig` to take in a path to where `FjallStore` will be stored - Extends the FFI database config to enable archival support - Adds `FjallStore` tests in both Rust and Golang --- Cargo.lock | 200 +++++++++++++++++++++++++++++++++++++ ffi/firewood.go | 2 + ffi/firewood.h | 12 +++ ffi/firewood_test.go | 56 +++++++++++ ffi/src/handle.rs | 23 +++++ firewood/Cargo.toml | 3 + firewood/src/db.rs | 90 ++++++++++++++++- firewood/src/manager.rs | 119 +++++++++++++--------- firewood/src/root_store.rs | 76 +++++++++++++- 9 files changed, 530 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54110b16c600..6ff6af1b7528 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "byteview" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5" + [[package]] name = "cast" version = "0.3.0" @@ -572,6 +578,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "compare" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7" + [[package]] name = "console" version = "0.16.1" @@ -703,6 +715,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-skiplist" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -762,6 +784,20 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "debugid" version = "0.8.0" @@ -809,6 +845,12 @@ dependencies = [ "syn", ] +[[package]] +name = "double-ended-peekable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57" + [[package]] name = "dtor" version = "0.1.1" @@ -860,6 +902,18 @@ dependencies = [ "syn", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_filter" version = "0.1.4" @@ -1030,12 +1084,14 @@ dependencies = [ "coarsetime", "criterion", "ctor", + "derive-where", "env_logger", "ethereum-types", "fastrace", "firewood-macros", "firewood-storage", "firewood-triehash", + "fjall", "hash-db", "hex", "hex-literal", @@ -1051,6 +1107,7 @@ dependencies = [ "test-case", "thiserror", "typed-builder", + "weak-table", ] [[package]] @@ -1195,6 +1252,23 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fjall" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25ad44cd4360a0448a9b5a0a6f1c7a621101cca4578706d43c9a821418aebc" +dependencies = [ + "byteorder", + "byteview", + "dashmap", + "log", + "lsm-tree", + "path-absolutize", + "std-semaphore", + "tempfile", + "xxhash-rust", +] + [[package]] name = "float-cmp" version = "0.10.0" @@ -1377,6 +1451,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + [[package]] name = "h2" version = "0.4.12" @@ -1422,6 +1502,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1819,6 +1905,15 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699" +[[package]] +name = "interval-heap" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6" +dependencies = [ + "compare", +] + [[package]] name = "io-uring" version = "0.7.11" @@ -2024,6 +2119,36 @@ dependencies = [ "hashbrown 0.16.0", ] +[[package]] +name = "lsm-tree" +version = "2.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15" +dependencies = [ + "byteorder", + "crossbeam-skiplist", + "double-ended-peekable", + "enum_dispatch", + "guardian", + "interval-heap", + "log", + "lz4_flex", + "path-absolutize", + "quick_cache", + "rustc-hash", + "self_cell", + "tempfile", + "value-log", + "varint-rs", + "xxhash-rust", +] + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + [[package]] name = "memchr" version = "2.7.6" @@ -2360,6 +2485,24 @@ dependencies = [ "windows-link", ] +[[package]] +name = "path-absolutize" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" +dependencies = [ + "path-dedot", +] + +[[package]] +name = "path-dedot" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" +dependencies = [ + "once_cell", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2657,6 +2800,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick_cache" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + [[package]] name = "quote" version = "1.0.42" @@ -3067,6 +3220,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + [[package]] name = "semver" version = "1.0.27" @@ -3244,6 +3403,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "std-semaphore" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e" + [[package]] name = "str_stack" version = "0.1.0" @@ -3824,6 +3989,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-log" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fc7c4ce161f049607ecea654dca3f2d727da5371ae85e2e4f14ce2b98ed67c" +dependencies = [ + "byteorder", + "byteview", + "interval-heap", + "log", + "path-absolutize", + "rustc-hash", + "tempfile", + "varint-rs", + "xxhash-rust", +] + +[[package]] +name = "varint-rs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" + [[package]] name = "version_check" version = "0.9.5" @@ -3940,6 +4128,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + [[package]] name = "web-sys" version = "0.3.82" @@ -4236,6 +4430,12 @@ dependencies = [ "tap", ] +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "yoke" version = "0.8.1" diff --git a/ffi/firewood.go b/ffi/firewood.go index 23da94e36ef3..9a84a0a439ae 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -60,6 +60,7 @@ type Config struct { FreeListCacheEntries uint Revisions uint ReadCacheStrategy CacheStrategy + RootStoreDir string } // DefaultConfig returns a sensible default Config. @@ -114,6 +115,7 @@ func New(filePath string, conf *Config) (*Database, error) { revisions: C.size_t(conf.Revisions), strategy: C.uint8_t(conf.ReadCacheStrategy), truncate: C.bool(conf.Truncate), + root_store_path: newBorrowedBytes([]byte(conf.RootStoreDir), &pinner), } return getDatabaseFromHandleResult(C.fwd_open_db(args)) diff --git a/ffi/firewood.h b/ffi/firewood.h index 5d9882508bc6..1955ac86d8b7 100644 --- a/ffi/firewood.h +++ b/ffi/firewood.h @@ -910,6 +910,18 @@ typedef struct DatabaseHandleArgs { * If this is empty, an error will be returned. */ BorrowedBytes path; + /** + * The path to the `RootStore` directory. + * + * This must be a valid UTF-8 string, even on Windows. + * + * If this is empty, then the archival feature is disabled. + * + * Note: Setting this directory will only track new revisions going forward + * and will not contain revisions from a prior database instance that didn't + * set a `root_store_path`. + */ + BorrowedBytes root_store_path; /** * The size of the node cache. * diff --git a/ffi/firewood_test.go b/ffi/firewood_test.go index 32a243187ff2..97ed3e9e4421 100644 --- a/ffi/firewood_test.go +++ b/ffi/firewood_test.go @@ -1256,6 +1256,62 @@ func TestHandlesFreeImplicitly(t *testing.T) { } } +func TestFjallStore(t *testing.T) { + r := require.New(t) + + var ( + tmpdir = t.TempDir() + dbFile = filepath.Join(tmpdir, "test.db") + rootStoreDir = filepath.Join(tmpdir, "root_store_dir") + ) + + // Create a new database with RootStore enabled + config := DefaultConfig() + config.RootStoreDir = rootStoreDir + // Setting the number of in-memory revisions to 5 tests that revision nodes + // are not reaped prior to closing the database. + config.Revisions = 5 + + db, err := New(dbFile, config) + r.NoError(err) + + // Create and commit 10 proposals + numRevisions := 10 + key := []byte("root_store") + _, vals := kvForTest(numRevisions) + revisionRoots := make([]Hash, numRevisions) + for i := range numRevisions { + proposal, err := db.Propose([][]byte{key}, [][]byte{vals[i]}) + r.NoError(err) + r.NoError(proposal.Commit()) + + revisionRoots[i], err = proposal.Root() + r.NoError(err) + } + + // Close and reopen the database + r.NoError(db.Close(t.Context())) + + db, err = New(dbFile, config) + r.NoError(err) + + // Verify that we can access all revisions + for i := range numRevisions { + revision, err := db.Revision(revisionRoots[i]) + r.NoError(err) + + defer func() { + r.NoError(revision.Drop()) + }() + + v, err := revision.Get(key) + r.NoError(err) + + r.Equal(vals[i], v) + r.NoError(revision.Drop()) + } +} + type kvIter interface { SetBatchSize(int) Next() bool diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 371b7c8e7e63..994e04cd198f 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -1,6 +1,8 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use std::path::PathBuf; + use firewood::{ db::{Db, DbConfig}, manager::RevisionManagerConfig, @@ -25,6 +27,17 @@ pub struct DatabaseHandleArgs<'a> { /// If this is empty, an error will be returned. pub path: BorrowedBytes<'a>, + /// The path to the `RootStore` directory. + /// + /// This must be a valid UTF-8 string, even on Windows. + /// + /// If this is empty, then the archival feature is disabled. + /// + /// Note: Setting this directory will only track new revisions going forward + /// and will not contain revisions from a prior database instance that didn't + /// set a `root_store_path`. + pub root_store_path: BorrowedBytes<'a>, + /// The size of the node cache. /// /// Opening returns an error if this is zero. @@ -100,9 +113,19 @@ impl DatabaseHandle { /// /// If the path is empty, or if the configuration is invalid, this will return an error. pub fn new(args: DatabaseHandleArgs<'_>) -> Result { + let root_store_path = args + .root_store_path + .as_str() + .map_err(|e| invalid_data(format!("root store path contains invalid utf-8: {e}")))?; + + let root_store_dir = Some(root_store_path) + .filter(|s| !s.is_empty()) + .map(PathBuf::from); + let cfg = DbConfig::builder() .truncate(args.truncate) .manager(args.as_rev_manager_config()?) + .root_store_dir(root_store_dir) .build(); let path = args diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 12d14e778c68..6e9ad67c7f3c 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -36,6 +36,9 @@ thiserror.workspace = true # Regular dependencies typed-builder = "0.23.0" rayon = "1.11.0" +fjall = "2.11.2" +derive-where = "1.6.0" +weak-table = "0.3.2" [features] default = [] diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 1c8729ba8040..864203e40350 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -11,14 +11,14 @@ mod tests; use crate::iter::MerkleKeyValueIter; use crate::merkle::{Merkle, Value}; -use crate::root_store::{NoOpStore, RootStore}; +use crate::root_store::{FjallStore, NoOpStore, RootStore}; pub use crate::v2::api::BatchOp; use crate::v2::api::{ self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, IntoBatchIter, KeyType, KeyValuePair, OptionalHashKeyExt, }; -use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; +use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig, RevisionManagerError}; use firewood_storage::{ CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, Parentable, ReadableStorage, TrieReader, @@ -26,7 +26,7 @@ use firewood_storage::{ use metrics::{counter, describe_counter}; use std::io::Write; use std::num::NonZeroUsize; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use thiserror::Error; use typed_builder::TypedBuilder; @@ -125,6 +125,9 @@ pub struct DbConfig { // TODO: Experimentally determine the right value for BatchSize. #[builder(default = UseParallel::BatchSize(8))] pub use_parallel: UseParallel, + /// `RootStore` directory path + #[builder(default = None)] + pub root_store_dir: Option, } #[derive(Debug)] @@ -164,7 +167,14 @@ impl api::Db for Db { impl Db { /// Create a new database instance. pub fn new>(db_path: P, cfg: DbConfig) -> Result { - Self::with_root_store(db_path, cfg, Box::new(NoOpStore {})) + let root_store: Box = match &cfg.root_store_dir { + Some(path) => { + Box::new(FjallStore::new(path).map_err(RevisionManagerError::RootStoreError)?) + } + None => Box::new(NoOpStore {}), + }; + + Self::with_root_store(db_path, cfg, root_store) } fn with_root_store>( @@ -384,6 +394,7 @@ mod test { #![expect(clippy::unwrap_used)] use core::iter::Take; + use std::collections::HashMap; use std::iter::Peekable; use std::num::NonZeroUsize; use std::ops::{Deref, DerefMut}; @@ -394,6 +405,7 @@ mod test { }; use crate::db::{Db, Proposal, UseParallel}; + use crate::manager::RevisionManagerConfig; use crate::root_store::{MockStore, RootStore}; use crate::v2::api::{Db as _, DbView, Proposal as _}; @@ -1078,6 +1090,27 @@ mod test { let mock_store = MockStore::default(); let db = TestDb::with_mockstore(mock_store); + test_root_store_helper(db); + } + + #[test] + fn test_fjall_store() { + let tmpdir = tempfile::tempdir().unwrap(); + let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect(); + let root_store_path = tmpdir.as_ref().join("fjall_store"); + let dbconfig = DbConfig::builder() + .root_store_dir(Some(root_store_path)) + .build(); + let db = Db::new(dbpath, dbconfig).unwrap(); + let db = TestDb { db, tmpdir }; + + test_root_store_helper(db); + } + + /// Verifies that persisted revisions are still accessible when reopening the database. + fn test_root_store_helper(db: TestDb) { // First, create a revision to retrieve let key = b"key"; let value = b"value"; @@ -1130,6 +1163,55 @@ mod test { db.reopen(); } + /// Verifies that revisions exceeding the in-memory limit can still be retrieved. + #[test] + fn test_fjall_store_with_capped_max_revisions() { + const NUM_REVISIONS: usize = 10; + + let tmpdir = tempfile::tempdir().unwrap(); + let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] + .iter() + .collect(); + let root_store_path = tmpdir.as_ref().join("fjall_store"); + let dbconfig = DbConfig::builder() + .root_store_dir(Some(root_store_path)) + .manager(RevisionManagerConfig::builder().max_revisions(5).build()) + .build(); + let db = Db::new(dbpath, dbconfig).unwrap(); + let db = TestDb { db, tmpdir }; + + // Create and commit 10 proposals + let key = b"root_store"; + let revisions: HashMap = (0..NUM_REVISIONS) + .map(|i| { + let value = i.to_be_bytes(); + let batch = vec![BatchOp::Put { key, value }]; + let proposal = db.propose(batch).unwrap(); + let root_hash = proposal.root_hash().unwrap().unwrap(); + proposal.commit().unwrap(); + + (root_hash, value) + }) + .collect(); + + // Verify that we can access all revisions with their correct values + for (root_hash, value) in &revisions { + let revision = db.revision(root_hash.clone()).unwrap(); + let retrieved_value = revision.val(key).unwrap().unwrap(); + assert_eq!(value.as_slice(), retrieved_value.as_ref()); + } + + let db = db.reopen(); + + // Verify that we can access all revisions with their correct values + // after reopening + for (root_hash, value) in &revisions { + let revision = db.revision(root_hash.clone()).unwrap(); + let retrieved_value = revision.val(key).unwrap().unwrap(); + assert_eq!(value.as_slice(), retrieved_value.as_ref()); + } + } + // Testdb is a helper struct for testing the Db. Once it's dropped, the directory and file disappear pub(super) struct TestDb { db: Db, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index b80dbcfcc3ef..6ad91f0f67a4 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -13,12 +13,13 @@ use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::{Arc, Mutex, OnceLock, RwLock}; +use std::sync::{Arc, Mutex, OnceLock, RwLock, Weak}; use firewood_storage::logger::{trace, warn}; use metrics::gauge; use rayon::{ThreadPool, ThreadPoolBuilder}; use typed_builder::TypedBuilder; +use weak_table::WeakValueHashMap; use crate::merkle::Merkle; use crate::root_store::RootStore; @@ -78,6 +79,7 @@ pub(crate) struct RevisionManager { proposals: Mutex>, // committing_proposals: VecDeque>, by_hash: RwLock>, + by_rootstore: Mutex>>>, threadpool: OnceLock, root_store: Box, } @@ -128,6 +130,7 @@ impl RevisionManager { by_hash: RwLock::new(Default::default()), proposals: Mutex::new(Default::default()), // committing_proposals: Default::default(), + by_rootstore: Mutex::new(WeakValueHashMap::new()), threadpool: OnceLock::new(), root_store, }; @@ -166,8 +169,12 @@ impl RevisionManager { /// 1. Commit check. /// The proposal's parent must be the last committed revision, otherwise the commit fails. /// It only contains the address of the nodes that are deleted, which should be very small. - /// 2. Revision reaping. If more than the maximum number of revisions are kept in memory, the - /// oldest revision is reaped. + /// 2. Revision reaping. + /// If more than the maximum number of revisions are kept in memory, the + /// oldest revision is removed from memory. If `RootStore` allows space + /// reuse, the oldest revision's nodes are added to the free list for space reuse. + /// Otherwise, the oldest revision's nodes are preserved on disk, which + /// is useful for historical queries. /// 3. Persist to disk. This includes flushing everything to disk. /// 4. Persist the revision to `RootStore`. /// 5. Set last committed revision. @@ -189,7 +196,8 @@ impl RevisionManager { let mut committed = proposal.as_committed(¤t_revision); // 2. Revision reaping - // Take the deleted entries from the oldest revision and mark them as free for this revision + // When we exceed max_revisions, remove the oldest revision from memory. + // If `RootStore` allows space reuse, add the oldest revision's nodes to the free list. // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free while self.historical.read().expect("poisoned lock").len() >= self.max_revisions { @@ -199,27 +207,28 @@ impl RevisionManager { .expect("poisoned lock") .pop_front() .expect("must be present"); - if let Some(oldest_hash) = oldest.root_hash().or_default_root_hash() { - self.by_hash - .write() - .expect("poisoned lock") - .remove(&oldest_hash); + let oldest_hash = oldest.root_hash().or_default_root_hash(); + if let Some(ref hash) = oldest_hash { + self.by_hash.write().expect("poisoned lock").remove(hash); } - // This `try_unwrap` is safe because nobody else will call `try_unwrap` on this Arc - // in a different thread, so we don't have to worry about the race condition where - // the Arc we get back is not usable as indicated in the docs for `try_unwrap`. - // This guarantee is there because we have a `&mut self` reference to the manager, so - // the compiler guarantees we are the only one using this manager. - match Arc::try_unwrap(oldest) { - Ok(oldest) => oldest.reap_deleted(&mut committed)?, - Err(original) => { - warn!("Oldest revision could not be reaped; still referenced"); - self.historical - .write() - .expect("poisoned lock") - .push_front(original); - break; + // We reap the revision's nodes only if `RootStore` allows space reuse. + if self.root_store.allow_space_reuse() { + // This `try_unwrap` is safe because nobody else will call `try_unwrap` on this Arc + // in a different thread, so we don't have to worry about the race condition where + // the Arc we get back is not usable as indicated in the docs for `try_unwrap`. + // This guarantee is there because we have a `&mut self` reference to the manager, so + // the compiler guarantees we are the only one using this manager. + match Arc::try_unwrap(oldest) { + Ok(oldest) => oldest.reap_deleted(&mut committed)?, + Err(original) => { + warn!("Oldest revision could not be reaped; still referenced"); + self.historical + .write() + .expect("poisoned lock") + .push_front(original); + break; + } } } gauge!("firewood.active_revisions") @@ -279,7 +288,6 @@ impl RevisionManager { /// To view the database at a specific hash involves a few steps: /// 1. Try to find it in committed revisions. /// 2. Try to find it in proposals. - /// 3. Try to find it in `RootStore`. pub fn view(&self, root_hash: HashKey) -> Result { // 1. Try to find it in committed revisions. if let Ok(committed) = self.revision(root_hash.clone()) { @@ -293,28 +301,12 @@ impl RevisionManager { .expect("poisoned lock") .iter() .find(|p| p.root_hash().as_ref() == Some(&root_hash)) - .cloned(); - - if let Some(proposal) = proposal { - return Ok(proposal); - } - - // 3. Try to find it in `RootStore`. - let revision_addr = self - .root_store - .get(&root_hash) - .map_err(RevisionManagerError::RootStoreError)? + .cloned() .ok_or(RevisionManagerError::RevisionNotFound { - provided: root_hash.clone(), + provided: root_hash, })?; - let node_store = NodeStore::with_root( - root_hash.into_hash_type(), - revision_addr, - self.current_revision(), - ); - - Ok(Arc::new(node_store)) + Ok(proposal) } pub fn add_proposal(&self, proposal: ProposedRevision) { @@ -338,15 +330,50 @@ impl RevisionManager { .collect() } + /// Retrieve a committed revision by its root hash. + /// To retrieve a revision involves a few steps: + /// 1. Check the in-memory revision manager. + /// 2. Check the in-memory `RootStore` cache. + /// 3. Check the persistent `RootStore`. pub fn revision(&self, root_hash: HashKey) -> Result { - self.by_hash + // 1. Check the in-memory revision manager. + if let Some(revision) = self + .by_hash .read() .expect("poisoned lock") .get(&root_hash) .cloned() + { + return Ok(revision); + } + + let mut cache_guard = self.by_rootstore.lock().expect("poisoned lock"); + + // 2. Check the in-memory `RootStore` cache. + if let Some(nodestore) = cache_guard.get(&root_hash) { + return Ok(nodestore); + } + + // 3. Check the persistent `RootStore`. + // If the revision exists, get its root address and construct a NodeStore for it. + let root_address = self + .root_store + .get(&root_hash) + .map_err(RevisionManagerError::RootStoreError)? .ok_or(RevisionManagerError::RevisionNotFound { - provided: root_hash, - }) + provided: root_hash.clone(), + })?; + + let nodestore = Arc::new(NodeStore::with_root( + root_hash.clone().into_hash_type(), + root_address, + self.current_revision(), + )); + + // Cache the nodestore (stored as a weak reference). + cache_guard.insert(root_hash, nodestore.clone()); + + Ok(nodestore) } pub fn root_hash(&self) -> Result, RevisionManagerError> { diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index a55e9d9d75f8..854a2459f42a 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -1,15 +1,18 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::fmt::Debug; #[cfg(test)] use std::{ collections::HashMap, sync::{Arc, Mutex}, }; +use std::{fmt::Debug, path::Path}; +use derive_where::derive_where; use firewood_storage::{LinearAddress, TrieHash}; +const FJALL_PARTITION_NAME: &str = "firewood"; + pub trait RootStore: Debug { /// `add_root` persists a revision's address to `RootStore`. /// @@ -39,6 +42,9 @@ pub trait RootStore: Debug { &self, hash: &TrieHash, ) -> Result, Box>; + + /// Returns whether revision nodes should be added to the free list. + fn allow_space_reuse(&self) -> bool; } #[derive(Debug)] @@ -59,6 +65,10 @@ impl RootStore for NoOpStore { ) -> Result, Box> { Ok(None) } + + fn allow_space_reuse(&self) -> bool { + true + } } #[cfg(test)] @@ -108,4 +118,68 @@ impl RootStore for MockStore { Ok(self.roots.lock().expect("poisoned lock").get(hash).copied()) } + + fn allow_space_reuse(&self) -> bool { + false + } +} + +use fjall::{Config, Keyspace, PartitionCreateOptions, PartitionHandle, PersistMode}; + +#[derive_where(Debug)] +#[derive_where(skip_inner)] +pub struct FjallStore { + keyspace: Keyspace, + items: PartitionHandle, +} + +impl FjallStore { + /// Creates or opens an instance of `FjallStore`. + /// + /// Args: + /// - path: the directory where `FjallStore` will write to. + /// + /// # Errors + /// + /// Will return an error if unable to create or open an instance of `FjallStore`. + pub fn new>( + path: P, + ) -> Result> { + let keyspace = Config::new(path).open()?; + let items = + keyspace.open_partition(FJALL_PARTITION_NAME, PartitionCreateOptions::default())?; + + Ok(Self { keyspace, items }) + } +} + +impl RootStore for FjallStore { + fn add_root( + &self, + hash: &TrieHash, + address: &LinearAddress, + ) -> Result<(), Box> { + self.items.insert(**hash, address.get().to_be_bytes())?; + + self.keyspace.persist(PersistMode::Buffer)?; + + Ok(()) + } + + fn get( + &self, + hash: &TrieHash, + ) -> Result, Box> { + let Some(v) = self.items.get(**hash)? else { + return Ok(None); + }; + + let array: [u8; 8] = v.as_ref().try_into()?; + + Ok(LinearAddress::new(u64::from_be_bytes(array))) + } + + fn allow_space_reuse(&self) -> bool { + false + } } From c1b1d7ce053b0b15bff655287b6494409a52e9ab Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 18 Nov 2025 10:01:12 -0800 Subject: [PATCH 1040/1053] test: fix `giant_node` test (#1465) The previous work to defer allocating nodes until commit time had the side effect of allowing nodes to exceed the maximum allowed size at hash time. This defers the error until commit time, which is not ideal. This change updates the test to confirm that the allocator still errors when trying to allocate a node that is too large. However, we would prefer to do this earlier but without serializing the node multiple times. As part of this change, I created a helper type that the test can use to verify the exact error was returned instead of comparing against the string representation of the error. As a part of that, I discovered that there was some vestiges of old code where the allocator would potentially return an area larger than necessary when allocating from the free list. However, that functionality was removed in #1217; thus, I have simplified the remaining code to reflect that which also simplified my `giant_node` test changes. Similarly, the removed wasted metric was no longer relevant because we no longer select free nodes larger than needed. I added `DisplayTruncatedHex` to the `Debug` representations of the leaf and branch nodes to truncate the node values when printing debug info. This was necessary before fixing the `giant_node` test because the debug output included the full contents of the node, which would flood the terminal when the test failed and cause it to hang. Closes: #1054 --- METRICS.md | 9 ++---- storage/src/linear/mod.rs | 5 +++ storage/src/node/branch.rs | 13 +++----- storage/src/node/leaf.rs | 2 +- storage/src/node/mod.rs | 14 +++++++++ storage/src/nodestore/alloc.rs | 48 ++++++++++------------------- storage/src/nodestore/mod.rs | 32 +++++++++++++++---- storage/src/nodestore/primitives.rs | 16 ++++------ 8 files changed, 76 insertions(+), 63 deletions(-) diff --git a/METRICS.md b/METRICS.md index 464022c1f515..fcac0c31a123 100644 --- a/METRICS.md +++ b/METRICS.md @@ -153,11 +153,6 @@ See the [FFI README](ffi/README.md) for more details on FFI metrics configuratio - Labels: `index`: Size index of allocated area - Use: Track memory reuse efficiency -- **`firewood.space.wasted`** (counter with `index` label) - - Description: Bytes wasted when allocating from free list (allocated more than needed) - - Labels: `index`: Size index of allocated area - - Use: Monitor allocation efficiency and fragmentation - - **`firewood.space.from_end`** (counter with `index` label) - Description: Bytes allocated from end of nodestore when free list was insufficient - Labels: `index`: Size index of allocated area @@ -279,13 +274,13 @@ For Prometheus-based monitoring (note: metric names use underscores in queries): rate(firewood_proposal_commit_ms[5m]) / rate(firewood_proposal_commit[5m]) # Cache hit rate -sum(rate(firewood_cache_node{type="hit"}[5m])) / +sum(rate(firewood_cache_node{type="hit"}[5m])) / sum(rate(firewood_cache_node[5m])) # Database growth rate (bytes/sec) rate(firewood_space_from_end[5m]) # Failed commit ratio -rate(firewood_proposal_commit{success="false"}[5m]) / +rate(firewood_proposal_commit{success="false"}[5m]) / rate(firewood_proposal_commit[5m]) ``` diff --git a/storage/src/linear/mod.rs b/storage/src/linear/mod.rs index 64fb08dc010d..f01ad468f920 100644 --- a/storage/src/linear/mod.rs +++ b/storage/src/linear/mod.rs @@ -85,6 +85,11 @@ impl FileIoError { context, } } + + #[cfg(test)] + pub(crate) fn context(&self) -> Option<&str> { + self.context.as_deref() + } } impl std::error::Error for FileIoError { diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index 4d9222d0f4f3..fa22a72235c2 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -436,14 +436,11 @@ impl Debug for BranchNode { } } - write!( - f, - " v={}]", - match &self.value { - Some(v) => hex::encode(&**v), - None => "nil".to_string(), - } - ) + let value: &dyn std::fmt::Display = match self.value.as_deref() { + Some(v) => &super::DisplayTruncatedHex(v), + None => &"nil", + }; + write!(f, "v={value}]") } } diff --git a/storage/src/node/leaf.rs b/storage/src/node/leaf.rs index 4d6a022c1dff..83170558d2ca 100644 --- a/storage/src/node/leaf.rs +++ b/storage/src/node/leaf.rs @@ -21,7 +21,7 @@ impl Debug for LeafNode { f, "[Leaf {:?} {}]", self.partial_path, - hex::encode(&*self.value) + super::DisplayTruncatedHex(&self.value) ) } } diff --git a/storage/src/node/mod.rs b/storage/src/node/mod.rs index acb28cfa5313..04d2b99a6629 100644 --- a/storage/src/node/mod.rs +++ b/storage/src/node/mod.rs @@ -507,6 +507,20 @@ fn read_path_with_provided_length(reader: &mut impl Read, len: usize) -> std::io reader.read_fixed_len(len).map(Path::from) } +struct DisplayTruncatedHex<'a>(&'a [u8]); + +impl std::fmt::Display for DisplayTruncatedHex<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(truncated) = self.0.get(0..256) + && truncated.len() < self.0.len() + { + write!(f, "0x{}... (len={})", hex::encode(truncated), self.0.len()) + } else { + write!(f, "0x{}", hex::encode(self.0)) + } + } +} + #[cfg(test)] mod test { #![expect(clippy::unwrap_used)] diff --git a/storage/src/nodestore/alloc.rs b/storage/src/nodestore/alloc.rs index fc43c2dce624..b506f496f4dc 100644 --- a/storage/src/nodestore/alloc.rs +++ b/storage/src/nodestore/alloc.rs @@ -213,21 +213,14 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { area_index_and_size(self.storage, addr) } - /// Attempts to allocate `n` bytes from the free lists. - /// If successful returns the address of the newly allocated area - /// and the index of the free list that was used. - /// If there are no free areas big enough for `n` bytes, returns None. - /// TODO Consider splitting the area if we return a larger area than requested. + /// Attempts to allocate from the free lists for `index`. + /// + /// If successful returns the address of the newly allocated area; + /// otherwise, returns None. fn allocate_from_freed( &mut self, - n: u64, - ) -> Result, FileIoError> { - // Find the smallest free list that can fit this size. - let index = AreaIndex::from_size(n).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("allocate_from_freed".to_string())) - })?; - + index: AreaIndex, + ) -> Result, FileIoError> { let free_stored_area_addr = self .header .free_lists_mut() @@ -235,7 +228,6 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { .expect("index is less than AreaIndex::NUM_AREA_SIZES"); if let Some(address) = free_stored_area_addr { let address = *address; - // Get the first free block of sufficient size. if let Some(free_head) = self.storage.free_list_cache(address) { trace!("free_head@{address}(cached): {free_head:?} size:{index}"); *free_stored_area_addr = free_head; @@ -253,16 +245,10 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { "index" => index_name(index) ) .increment(index.size()); - firewood_counter!( - "firewood.space.wasted", - "Bytes wasted from free list by index", - "index" => index_name(index) - ) - .increment(index.size().saturating_sub(n)); // Return the address of the newly allocated block. trace!("Allocating from free list: addr: {address:?}, size: {index}"); - return Ok(Some((address, index))); + return Ok(Some(address)); } trace!("No free blocks of sufficient size {index} found"); @@ -275,18 +261,14 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { Ok(None) } - fn allocate_from_end(&mut self, n: u64) -> Result<(LinearAddress, AreaIndex), FileIoError> { - let index = AreaIndex::from_size(n).map_err(|e| { - self.storage - .file_io_error(e, 0, Some("allocate_from_end".to_string())) - })?; + fn allocate_from_end(&mut self, index: AreaIndex) -> Result { let area_size = index.size(); let addr = LinearAddress::new(self.header.size()).expect("node store size can't be 0"); self.header .set_size(self.header.size().saturating_add(area_size)); debug_assert!(addr.is_aligned()); trace!("Allocating from end: addr: {addr:?}, size: {index}"); - Ok((addr, index)) + Ok(addr) } /// Returns an address that can be used to store the given `node` and updates @@ -301,16 +283,20 @@ impl<'a, S: ReadableStorage> NodeAllocator<'a, S> { node: &[u8], ) -> Result<(LinearAddress, AreaIndex), FileIoError> { let stored_area_size = node.len() as u64; + let area_index = AreaIndex::from_size(stored_area_size).map_err(|e| { + self.storage + .file_io_error(e, 0, Some("allocate_node".to_owned())) + })?; // Attempt to allocate from a free list. // If we can't allocate from a free list, allocate past the existing // of the ReadableStorage. - let (addr, index) = match self.allocate_from_freed(stored_area_size)? { - Some((addr, index)) => (addr, index), - None => self.allocate_from_end(stored_area_size)?, + let addr = match self.allocate_from_freed(area_index)? { + Some(addr) => addr, + None => self.allocate_from_end(area_index)?, }; - Ok((addr, index)) + Ok((addr, area_index)) } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index d0b76fa2711a..2cb9513335dd 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -870,6 +870,7 @@ mod tests { use super::*; use primitives::area_size_iter; + use std::error::Error; #[test] fn area_sizes_aligned() { @@ -942,11 +943,11 @@ mod tests { } #[test] - #[ignore = "https://github.com/ava-labs/firewood/issues/1054"] - #[should_panic(expected = "Node size 16777225 is too large")] fn giant_node() { - let memstore = MemStore::new(vec![]); - let mut node_store = NodeStore::new_empty_proposal(memstore.into()); + let memstore = Arc::new(MemStore::new(vec![])); + let empty_root = NodeStore::new_empty_committed(Arc::clone(&memstore)); + + let mut node_store = NodeStore::new(&empty_root).unwrap(); let huge_value = vec![0u8; AreaIndex::MAX_AREA_SIZE as usize]; @@ -957,7 +958,26 @@ mod tests { node_store.root_mut().replace(giant_leaf); - let immutable = NodeStore::, _>::try_from(node_store).unwrap(); - println!("{immutable:?}"); // should not be reached, but need to consume immutable to avoid optimization removal + let node_store = NodeStore::, _>::try_from(node_store).unwrap(); + + let mut node_store = node_store.as_committed(&empty_root); + + let err = node_store.persist().unwrap_err(); + let err_ctx = err.context(); + assert!(err_ctx == Some("allocate_node")); + + let io_err = err + .source() + .unwrap() + .downcast_ref::() + .unwrap(); + assert_eq!(io_err.kind(), std::io::ErrorKind::OutOfMemory); + + let io_err_source = io_err + .get_ref() + .unwrap() + .downcast_ref::() + .unwrap(); + assert_eq!(io_err_source.0, 16_777_225); } } diff --git a/storage/src/nodestore/primitives.rs b/storage/src/nodestore/primitives.rs index 131eb85e4932..8d0fcf994850 100644 --- a/storage/src/nodestore/primitives.rs +++ b/storage/src/nodestore/primitives.rs @@ -130,10 +130,7 @@ impl AreaIndex { /// Returns an error if the size is too large. pub fn from_size(n: u64) -> Result { if n > Self::MAX_AREA_SIZE { - return Err(Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - )); + return Err(Error::new(ErrorKind::OutOfMemory, AreaSizeError(n))); } if n <= Self::MIN_AREA_SIZE { @@ -144,12 +141,7 @@ impl AreaIndex { .iter() .position(|&size| size >= n) .map(|index| AreaIndex(index as u8)) - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidData, - format!("Node size {n} is too large"), - ) - }) + .ok_or_else(|| Error::new(ErrorKind::OutOfMemory, AreaSizeError(n))) } /// Get the underlying index as u8. @@ -238,6 +230,10 @@ impl fmt::Display for AreaIndex { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +#[error("Node size {_0} is too large")] +pub(super) struct AreaSizeError(pub(super) u64); + /// A linear address in the nodestore storage. /// /// This represents a non-zero address in the linear storage space. From 136a3965f8f0be3a69bc11c0d504e6f94a20e642 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Tue, 18 Nov 2025 10:15:40 -0800 Subject: [PATCH 1041/1053] chore(release): prepare for v0.0.15 (#1461) Upgrading dependencies included `crypto-common@0.1.7` which pins its dependency on `generic-array` to `0.14.7`, removing the deprecation warnings we were seeing that were added to `generic-array` in version `0.14.9` (where `0.14.8` was yanked). --- CHANGELOG.md | 37 ++++++++++ Cargo.lock | 106 +++++++++++++-------------- Cargo.toml | 14 ++-- RELEASE.md | 18 ++--- firewood/Cargo.toml | 2 +- firewood/src/merkle/tests/ethhash.rs | 2 - firewood/src/v2/api.rs | 1 - fwdctl/Cargo.toml | 2 +- storage/Cargo.toml | 6 +- storage/src/hashers/ethhash.rs | 2 - storage/src/node/branch.rs | 2 - storage/src/trie_hash.rs | 2 - 12 files changed, 111 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc44f2dc6c0..5eb0f1baf4b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ All notable changes to this project will be documented in this file. +## [0.0.15] - 2025-11-18 + +### 🚀 Features + +- Merge key-value range into trie ([#1427](https://github.com/ava-labs/firewood/pull/1427)) +- *(storage)* Replace `ArcSwap` with `Mutex` for better performance ([#1447](https://github.com/ava-labs/firewood/pull/1447)) +- *(ffi)* [**breaking**] Remove unused kvBackend interface ([#1448](https://github.com/ava-labs/firewood/pull/1448)) +- *(ffi)* Add keepalive struct to own waitgroup logic ([#1437](https://github.com/ava-labs/firewood/pull/1437)) +- Remove the last bits of arc-swap ([#1464](https://github.com/ava-labs/firewood/pull/1464)) +- *(ffi)* Fill in ffi methods for range proofs ([#1429](https://github.com/ava-labs/firewood/pull/1429)) +- *(firewood/ffi)* Add `FjallStore` ([#1395](https://github.com/ava-labs/firewood/pull/1395)) + +### 🚜 Refactor + +- *(rootstore)* Replace RootStoreError with boxed error ([#1446](https://github.com/ava-labs/firewood/pull/1446)) +- Remove default 1-minute timeout on `Database.Close()` and set 1-sec in tests ([#1458](https://github.com/ava-labs/firewood/pull/1458)) + +### 📚 Documentation + +- Add agent instructions ([#1445](https://github.com/ava-labs/firewood/pull/1445)) +- *(firewood)* Clean up commit steps ([#1453](https://github.com/ava-labs/firewood/pull/1453)) +- Merge code review process into CONTRIBUTING.md ([#1397](https://github.com/ava-labs/firewood/pull/1397)) +- Add comprehensive metrics documentation in METRICS.md ([#1402](https://github.com/ava-labs/firewood/pull/1402)) + +### ⚡ Performance + +- *(ffi)* [**breaking**] Use fixed size Hash ([#1449](https://github.com/ava-labs/firewood/pull/1449)) + +### 🧪 Testing + +- Fix `giant_node` test ([#1465](https://github.com/ava-labs/firewood/pull/1465)) + +### ⚙️ Miscellaneous Tasks + +- *(ci)* Re-enable ffi-nix job ([#1450](https://github.com/ava-labs/firewood/pull/1450)) +- Relegate build equivalent check to post-merge job ([#1469](https://github.com/ava-labs/firewood/pull/1469)) + ## [0.0.14] - 2025-11-07 ### 🚀 Features diff --git a/Cargo.lock b/Cargo.lock index 6ff6af1b7528..1f72bcf64935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,22 +101,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -227,9 +227,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "5932a7d9d28b0d2ea34c6b3779d35e3dd6f6345317c34e73438c4f1f29144151" dependencies = [ "aws-lc-sys", "zeroize", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.3" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +checksum = "1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc" dependencies = [ "bindgen", "cc", @@ -300,18 +300,18 @@ dependencies = [ [[package]] name = "bitfield" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf79f42d21f18b5926a959280215903e659760da994835d27c3a0c5ff4f898f" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6115af052c7914c0cbb97195e5c72cb61c511527250074f5c041d1048b0d8b16" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", @@ -399,9 +399,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "byteview" @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -514,9 +514,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", "clap_derive", @@ -524,9 +524,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstream", "anstyle", @@ -739,9 +739,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1058,9 +1058,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "findshlibs" @@ -1076,7 +1076,7 @@ dependencies = [ [[package]] name = "firewood" -version = "0.0.14" +version = "0.0.15" dependencies = [ "bytemuck", "bytemuck_derive", @@ -1112,7 +1112,7 @@ dependencies = [ [[package]] name = "firewood-benchmark" -version = "0.0.14" +version = "0.0.15" dependencies = [ "clap", "env_logger", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "firewood-ffi" -version = "0.0.14" +version = "0.0.15" dependencies = [ "cbindgen", "chrono", @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "firewood-fwdctl" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "askama", @@ -1179,7 +1179,7 @@ dependencies = [ [[package]] name = "firewood-macros" -version = "0.0.14" +version = "0.0.15" dependencies = [ "coarsetime", "metrics", @@ -1191,7 +1191,7 @@ dependencies = [ [[package]] name = "firewood-storage" -version = "0.0.14" +version = "0.0.15" dependencies = [ "aquamarine", "bitfield", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "firewood-triehash" -version = "0.0.14" +version = "0.0.15" dependencies = [ "criterion", "ethereum-types", @@ -1408,9 +1408,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1600,9 +1600,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1653,9 +1653,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64", "bytes", @@ -1870,9 +1870,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console", "portable-atomic", @@ -3429,9 +3429,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.16.3" +version = "12.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50" +checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60" dependencies = [ "debugid", "memmap2", @@ -3441,9 +3441,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.16.3" +version = "12.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8" +checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -3452,9 +3452,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -3883,18 +3883,18 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66" +checksum = "1cce8e9c8115897e896894868ad4ae6851eff0fb7fd33fa95610e0fa93211886" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" +checksum = "921d52b8b19b1a455f54fa76a925a1cf49c0d6a7c6b232fc58523400d1f91560" dependencies = [ "proc-macro2", "quote", @@ -3945,9 +3945,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "untrusted" diff --git a/Cargo.toml b/Cargo.toml index 6e97a40cc1d2..16b7580933c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ resolver = "2" [workspace.package] # NOTE: when bumping to 0.1.0, this will be removed and each crate will have its # version set independently. -version = "0.0.14" +version = "0.0.15" edition = "2024" license-file = "LICENSE.md" homepage = "https://avalabs.org" @@ -56,17 +56,17 @@ cast_possible_truncation = "allow" [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.14" } -firewood-macros = { path = "firewood-macros", version = "0.0.14" } -firewood-storage = { path = "storage", version = "0.0.14" } -firewood-ffi = { path = "ffi", version = "0.0.14" } -firewood-triehash = { path = "triehash", version = "0.0.14" } +firewood = { path = "firewood", version = "0.0.15" } +firewood-macros = { path = "firewood-macros", version = "0.0.15" } +firewood-storage = { path = "storage", version = "0.0.15" } +firewood-ffi = { path = "ffi", version = "0.0.15" } +firewood-triehash = { path = "triehash", version = "0.0.15" } # common dependencies aquamarine = "0.6.0" bytemuck = "1.24.0" bytemuck_derive = "1.10.2" -clap = { version = "4.5.51", features = ["derive"] } +clap = { version = "4.5.52", features = ["derive"] } coarsetime = "0.1.36" env_logger = "0.11.8" fastrace = "0.7.14" diff --git a/RELEASE.md b/RELEASE.md index 4ddd74d2a6fa..c037539d8a91 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -20,9 +20,9 @@ Before making changes, create a new branch (if not already on one): ```console $ git fetch -$ git switch -c release/v0.0.15 origin/main -branch 'release/v0.0.15' set up to track 'origin/main'. -Switched to a new branch 'release/v0.0.15' +$ git switch -c release/v0.0.16 origin/main +branch 'release/v0.0.16' set up to track 'origin/main'. +Switched to a new branch 'release/v0.0.16' ``` If already on a new branch, ensure `HEAD` is the same as the remote's `main`. @@ -75,7 +75,7 @@ table to define the version for all subpackages. ```toml [workspace.package] -version = "0.0.15" +version = "0.0.16" ``` Each package inherits this version by setting `package.version.workspace = true`. @@ -99,7 +99,7 @@ table. E.g.,: ```toml [workspace.dependencies] # workspace local packages -firewood = { path = "firewood", version = "0.0.15" } +firewood = { path = "firewood", version = "0.0.16" } ``` This allows packages within the workspace to inherit the dependency, @@ -130,7 +130,7 @@ is correct and reflects the new package versions. To build the changelog, see git-cliff.org. Short version: ```sh -git cliff --tag v0.0.15 -o CHANGELOG.md +git cliff --tag v0.0.16 -o CHANGELOG.md ``` ## Commit @@ -160,11 +160,11 @@ To trigger a release, push a tag to the main branch matching the new version, # be sure to switch back to the main branch before tagging git checkout main git pull --prune -git tag -s -a v0.0.15 -m 'Release v0.0.15' -git push origin v0.0.15 +git tag -s -a v0.0.16 -m 'Release v0.0.16' +git push origin v0.0.16 ``` -for `v0.0.15` for the merged version change. The CI will automatically publish a +for `v0.0.16` for the merged version change. The CI will automatically publish a draft release which consists of release notes and changes (see [.github/workflows/release.yaml](.github/workflows/release.yaml)). diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 6e9ad67c7f3c..445f26e67261 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,7 +34,7 @@ integer-encoding.workspace = true metrics.workspace = true thiserror.workspace = true # Regular dependencies -typed-builder = "0.23.0" +typed-builder = "0.23.1" rayon = "1.11.0" fjall = "2.11.2" derive-where = "1.6.0" diff --git a/firewood/src/merkle/tests/ethhash.rs b/firewood/src/merkle/tests/ethhash.rs index 0d4c8c8615df..22e90118fa17 100644 --- a/firewood/src/merkle/tests/ethhash.rs +++ b/firewood/src/merkle/tests/ethhash.rs @@ -34,7 +34,6 @@ impl Hasher for KeccakHasher { let mut hasher = Keccak256::new(); hasher.update(x); let result = hasher.finalize(); - #[expect(deprecated, reason = "transitive dependency on generic-array")] H256::from_slice(result.as_slice()) } } @@ -91,7 +90,6 @@ fn test_eth_compatible_accounts( let expected_key_hash = Keccak256::digest(&account); let items = once(( - #[expect(deprecated, reason = "transitive dependency on generic-array")] Box::from(expected_key_hash.as_slice()), make_key(account_value), )) diff --git a/firewood/src/v2/api.rs b/firewood/src/v2/api.rs index 45ca1bc44178..9d3ae9ab9962 100644 --- a/firewood/src/v2/api.rs +++ b/firewood/src/v2/api.rs @@ -479,7 +479,6 @@ mod tests { #[test] #[cfg(feature = "ethhash")] - #[expect(deprecated, reason = "transitive dependency on generic-array")] fn test_ethhash_compat_default_root_hash_equals_empty_rlp_hash() { use sha3::Digest as _; diff --git a/fwdctl/Cargo.toml b/fwdctl/Cargo.toml index 03f651d282e6..b82452d21fc3 100644 --- a/fwdctl/Cargo.toml +++ b/fwdctl/Cargo.toml @@ -33,7 +33,7 @@ log.workspace = true nonzero_ext.workspace = true # Regular dependencies csv = "1.4.0" -indicatif = "0.18.2" +indicatif = "0.18.3" askama = "0.14.0" num-format = "0.4.4" diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 3e32541adb38..6430e067ef7a 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -32,16 +32,16 @@ sha2.workspace = true smallvec.workspace = true thiserror.workspace = true # Regular dependencies -bitfield = "0.19.3" +bitfield = "0.19.4" bitflags = "2.10.0" derive-where = "1.6.0" enum-as-inner = "0.6.1" -indicatif = "0.18.2" +indicatif = "0.18.3" lru = "0.16.2" semver = "1.0.27" triomphe = "0.1.15" # Optional dependencies -bytes = { version = "1.10.1", optional = true } +bytes = { version = "1.11.0", optional = true } io-uring = { version = "0.7.11", optional = true } log = { version = "0.4.28", optional = true } rlp = { version = "0.6.1", optional = true } diff --git a/storage/src/hashers/ethhash.rs b/storage/src/hashers/ethhash.rs index a7cbd0eb6e93..a70966850d3c 100644 --- a/storage/src/hashers/ethhash.rs +++ b/storage/src/hashers/ethhash.rs @@ -114,7 +114,6 @@ impl Preimage for T { if is_account { // we are a leaf that is at depth 32 match self.value_digest() { - #[expect(deprecated, reason = "transitive dependency on generic-array")] Some(ValueDigest::Value(bytes)) => { let new_hash = Keccak256::digest(rlp::NULL_RLP).as_slice().to_vec(); let bytes_mut = BytesMut::from(bytes); @@ -225,7 +224,6 @@ impl Preimage for T { final_bytes.append(&&*nibbles_to_eth_compact(partial_path, is_account)); // if the RLP is short enough, we can use it as-is, otherwise we hash it // to make the maximum length 32 bytes - #[expect(deprecated, reason = "transitive dependency on generic-array")] if updated_bytes.len() > 31 && !is_account { let hashed_bytes = Keccak256::digest(updated_bytes); final_bytes.append(&hashed_bytes.as_slice()); diff --git a/storage/src/node/branch.rs b/storage/src/node/branch.rs index fa22a72235c2..25f96d1f2d18 100644 --- a/storage/src/node/branch.rs +++ b/storage/src/node/branch.rs @@ -246,7 +246,6 @@ mod ethhash { fn eq(&self, other: &TrieHash) -> bool { match self { HashOrRlp::Hash(h) => h == other, - #[expect(deprecated, reason = "transitive dependency on generic-array")] HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == other.as_ref(), } } @@ -256,7 +255,6 @@ mod ethhash { fn eq(&self, other: &HashOrRlp) -> bool { match other { HashOrRlp::Hash(h) => h == self, - #[expect(deprecated, reason = "transitive dependency on generic-array")] HashOrRlp::Rlp(r) => Keccak256::digest(r.as_ref()).as_slice() == self.as_ref(), } } diff --git a/storage/src/trie_hash.rs b/storage/src/trie_hash.rs index b114a09eb551..b5916edce2ce 100644 --- a/storage/src/trie_hash.rs +++ b/storage/src/trie_hash.rs @@ -3,7 +3,6 @@ use crate::node::ExtendableBytes; use crate::node::branch::Serializable; -#[expect(deprecated, reason = "transitive dependency on generic-array")] use sha2::digest::generic_array::GenericArray; use sha2::digest::typenum; use std::fmt::{self, Debug, Display, Formatter}; @@ -90,7 +89,6 @@ impl TryFrom<&[u8]> for TrieHash { } } -#[expect(deprecated, reason = "transitive dependency on generic-array")] impl From> for TrieHash { fn from(value: GenericArray) -> Self { TrieHash(value.into()) From 78f459fe1e6d1fd64d2a3a032a3a93755e6ac5f3 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:24:28 -0500 Subject: [PATCH 1042/1053] docs(ffi): Update godoc (#1466) This doesn't totally complete #1113 since I didn't touch the Proof or Iterator files. However, this does clarify a lot of the use cases, and what is valid for concurrent operations. The best way to review this is open the godocs and reading through it, commenting on what doesn't seem clear. To reproduce the godocs, run: ``` cd ./ffi godoc -http=: ``` Please leave nit-picky comments! It would be great if any reader of the docs would know how to use the code. Closes #1348, #1413 --- ffi/README.md | 72 ------------------------------- ffi/firewood.go | 112 ++++++++++++++++++++++++++++++++++++++---------- ffi/memory.go | 3 -- ffi/metrics.go | 1 + ffi/proposal.go | 27 ++++++------ ffi/revision.go | 24 +++++++---- 6 files changed, 121 insertions(+), 118 deletions(-) diff --git a/ffi/README.md b/ffi/README.md index 70773c4de162..7c6507e7fbf0 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -60,78 +60,6 @@ cargo build -p firewood-ffi --features ethhash To support development in [Coreth](https://github.com/ava-labs/coreth), Firewood pushes static libraries for Ethereum-compatible hashing to [firewood-go-ethhash](https://github.com/ava-labs/firewood-go-ethhash) with `ethhash` enabled by default. To use Firewood's native hashing structure, you must still build the static library separately. -## Configuration - -### Database Config - -A `Config` should be provided when creating the database. A default config is provided at `ffi.DefaultConfig()`: - -```go -&Config{ - NodeCacheEntries: 1000000, - FreeListCacheEntries: 40000, - Revisions: 100, - ReadCacheStrategy: OnlyCacheWrites, -} -``` - -If no config is provided (`config == nil`), the default config is used. A description of all available values, and the default if not set, is available below. - -#### `Truncate` - `bool` - -If set to `true`, an empty database will be created, overriding any existing file. Otherwise, if a file exists, that file will be loaded to create the database. In either case, if the file doesn't exist, it will be created. - -*Default*: `false` - -#### `Revisions` - `uint` - -Indicates the number of committed roots accessible before the diff layer is compressed. Must be explicitly set if the config is specified to at least 2. - -#### `ReadCacheStrategy` - `uint` - -Should be one of `OnlyCacheWrites`, `CacheBranchReads`, or `CacheAllReads`. In the latter two cases, writes are still cached. - -*Default*: `OnlyCacheWrites` - -#### `NodeCacheEntries`- `uint` - -The number of nodes in the database that are stored in cache. Must be explicitly set if the config is supplied. - -#### `FreeListCacheEntries` - `uint` - -The number of entries in the free list (see [Firewood Overview](../README.md)). Must be explicitly set if the config is supplied. - -### Metrics - -By default, metrics are not enabled in Firewood's FFI. However, if compiled with this option, they can be recorded by a call to `StartMetrics()` or `StartMetricsWithExporter(port)`. One of these may be called exactly once, since it starts the metrics globally on the process. - -To use these metrics, you can: - -- Listen on the port specified, if you started the metrics with the exporter. -- Call `GatherMetrics()`, which returns an easily parsable string containing all metrics. -- Create the Prometheus gatherer, and call `Gather`. This can easily be integrated into other applications which already use prometehus. Example usage is below: - -```go -gatherer := ffi.Gatherer{} -metrics, err := gatherer.Gather() -``` - -### Logs - -Logs are configured globally on the process, and not enabled by default. They can be enabled using the `StartLogs(config)` function. Firewood must be built with the `logger` feature for this function to work. This should be called before opening the database. - -#### `Path` - `string` - -The path to the file where the logs will be written. - -*Default*: `{TEMP}/firewood-log.txt`, where `{TEMP}` is the platform's temporary directory. For Unix-based OSes, this is typically `/tmp`. - -#### `FilterLevel` - `string` - -One of `trace`, `debug`, `info`, `warn` or `error`. - -*Default*: `info` - ## Development Iterative building is unintuitive for the ffi and some common sources of confusion are listed below. diff --git a/ffi/firewood.go b/ffi/firewood.go index 9a84a0a439ae..c6d4c17f4e47 100644 --- a/ffi/firewood.go +++ b/ffi/firewood.go @@ -33,18 +33,35 @@ import ( "sync" ) +// RootLength is the hash length for all Firewood hashes. const RootLength = C.sizeof_HashKey +// Hash is the type used for all firewood hashes. type Hash [RootLength]byte var ( - EmptyRoot Hash - errDBClosed = errors.New("firewood database already closed") + // EmptyRoot is the zero value for [Hash] + EmptyRoot Hash + // ErrActiveKeepAliveHandles is returned when attempting to close a database with unfreed memory. ErrActiveKeepAliveHandles = errors.New("cannot close database with active keep-alive handles") + + errDBClosed = errors.New("firewood database already closed") ) -// A Database is a handle to a Firewood database. -// It is not safe to call these methods with a nil handle. +// Database is an FFI wrapper for the Rust Firewood database. +// All functions rely on CGO to call into the underlying Rust implementation. +// Instances are created via [New] and must be closed with [Database.Close] +// when no longer needed. +// +// A Database can have outstanding [Revision] and [Proposal], which +// access the database's memory. These must be released before closing the +// database. See [Database.Close] for more details. +// +// Database supports two hashing modes: Firewood hashing and Ethereum-compatible +// hashing. Ethereum-compatible hashing is distributed, but you can use the more efficient +// Firewood hashing by compiling from source. See the Firewood repository for more details. +// +// For concurrent use cases, see each type and method's documentation for thread-safety. type Database struct { // handle is returned and accepted by cgo functions. It MUST be treated as // an opaque value without special meaning. @@ -53,17 +70,32 @@ type Database struct { outstandingHandles sync.WaitGroup } -// Config configures the opening of a [Database]. +// Config defines the configuration parameters used when opening a [Database]. type Config struct { - Truncate bool - NodeCacheEntries uint + // Truncate indicates whether to clear the database file if it already exists. + Truncate bool + // NodeCacheEntries is the number of entries in the cache. + // Must be non-zero. + NodeCacheEntries uint + // FreeListCacheEntries is the number of entries in the freelist cache. + // Must be non-zero. FreeListCacheEntries uint - Revisions uint - ReadCacheStrategy CacheStrategy - RootStoreDir string + // Revisions is the maximum number of historical revisions to keep in memory. + // If RootStoreDir is set, then any revisions removed from memory will still be kept on disk. + // Otherwise, any revisions removed from memory will no longer be kept on disk. + // Must be >= 2. + Revisions uint + // ReadCacheStrategy is the caching strategy used for the node cache. + ReadCacheStrategy CacheStrategy + // RootStoreDir defines a path to store all historical roots on disk. + RootStoreDir string } -// DefaultConfig returns a sensible default Config. +// DefaultConfig returns a [*Config] with sensible defaults: +// - NodeCacheEntries: 1_000_000 +// - FreeListCacheEntries: 40_000 +// - Revisions: 100 +// - ReadCacheStrategy: OnlyCacheWrites func DefaultConfig() *Config { return &Config{ NodeCacheEntries: 1_000_000, @@ -77,8 +109,11 @@ func DefaultConfig() *Config { type CacheStrategy uint8 const ( + // OnlyCacheWrites caches only writes. OnlyCacheWrites CacheStrategy = iota + // CacheBranchReads caches intermediate reads and writes. CacheBranchReads + // CacheAllReads caches all reads and writes. CacheAllReads // invalidCacheStrategy MUST be the final value in the iota block to make it @@ -87,7 +122,13 @@ const ( ) // New opens or creates a new Firewood database with the given configuration. If -// a nil `Config` is provided [DefaultConfig] will be used instead. +// a nil config is provided, [DefaultConfig] will be used instead. +// The database file will be created at the provided file path if it does not +// already exist. +// +// It is the caller's responsibility to call [Database.Close] when the database +// is no longer needed. No other [Database] in this process should be opened with +// the same file path until the database is closed. func New(filePath string, conf *Config) (*Database, error) { if conf == nil { conf = DefaultConfig() @@ -122,7 +163,8 @@ func New(filePath string, conf *Config) (*Database, error) { } // Update applies a batch of updates to the database, returning the hash of the -// root node after the batch is applied. +// root node after the batch is applied. This is equilalent to creating a proposal +// with [Database.Propose], then committing it with [Proposal.Commit]. // // Value Semantics: // - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix @@ -131,6 +173,9 @@ func New(filePath string, conf *Config) (*Database, error) { // // WARNING: Calling Update with an empty key and nil value will delete the entire database // due to prefix deletion semantics. +// +// This function is not thread-safe with respect to other calls that reference the latest +// state of the database, nor any calls that mutate the state of the database. func (db *Database) Update(keys, vals [][]byte) (Hash, error) { if db.handle == nil { return EmptyRoot, errDBClosed @@ -148,13 +193,16 @@ func (db *Database) Update(keys, vals [][]byte) (Hash, error) { } // Propose creates a new proposal with the given keys and values. The proposal -// is not committed until [Proposal.Commit] is called. See [Database.Close] re -// freeing proposals. +// is not committed until [Proposal.Commit] is called. See [Database.Close] regarding +// freeing proposals. All proposals should be freed before closing the database. // // Value Semantics: // - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix // - empty slice (vals[i] != nil && len(vals[i]) == 0): Inserts/updates the key with an empty value // - non-empty value: Inserts/updates the key with the provided value +// +// This function is not thread-safe with respect to other calls that reference the latest +// state of the database, nor any calls that mutate the latest state of the database. func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { if db.handle == nil { return nil, errDBClosed @@ -170,8 +218,11 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) { return getProposalFromProposalResult(C.fwd_propose_on_db(db.handle, kvp), &db.outstandingHandles) } -// Get retrieves the value for the given key. It always returns a nil error. -// If the key is not found, the return value will be (nil, nil). +// Get retrieves the value for the given key from the most recent revision. +// If the key is not found, the return value will be nil. +// +// This function is thread-safe with all other read operations, but is not thread-safe +// with respect to other calls that mutate the latest state of the database. func (db *Database) Get(key []byte) ([]byte, error) { if db.handle == nil { return nil, errDBClosed @@ -192,7 +243,12 @@ func (db *Database) Get(key []byte) ([]byte, error) { // GetFromRoot retrieves the value for the given key from a specific root hash. // If the root is not found, it returns an error. -// If key is not found, it returns (nil, nil). +// If key is not found, it returns nil. +// +// GetFromRoot caches a handle to the revision associated with the provided root hash, allowing +// subsequent calls with the same root to be more efficient. +// +// This function is thread-safe with all other operations. func (db *Database) GetFromRoot(root Hash, key []byte) ([]byte, error) { if db.handle == nil { return nil, errDBClosed @@ -214,7 +270,10 @@ func (db *Database) GetFromRoot(root Hash, key []byte) ([]byte, error) { } // Root returns the current root hash of the trie. -// Empty trie must return common.EmptyRoot. +// With Firewood hashing, the empty trie must return [EmptyRoot]. +// +// This function is thread-safe with all other operations, except those that mutate +// the latest state of the database. func (db *Database) Root() (Hash, error) { if db.handle == nil { return EmptyRoot, errDBClosed @@ -223,6 +282,12 @@ func (db *Database) Root() (Hash, error) { return getHashKeyFromHashResult(C.fwd_root_hash(db.handle)) } +// LatestRevision returns a [Revision] representing the latest state of the database. +// If the latest revision has root [EmptyRoot], it returns an error. The [Revision] must +// be dropped prior to closing the database. +// +// This function is thread-safe with all other operations, except those that mutate +// the latest state of the database. func (db *Database) LatestRevision() (*Revision, error) { root, err := db.Root() if err != nil { @@ -235,6 +300,10 @@ func (db *Database) LatestRevision() (*Revision, error) { } // Revision returns a historical revision of the database. +// If the provided root does not exist (or is the [EmptyRoot]), it returns an error. +// The [Revision] must be dropped prior to closing the database. +// +// This function is thread-safe with all other operations. func (db *Database) Revision(root Hash) (*Revision, error) { rev, err := getRevisionFromResult(C.fwd_get_revision( db.handle, @@ -256,9 +325,8 @@ func (db *Database) Revision(root Hash) (*Revision, error) { // them. Unreachable objects will be automatically dropped before Close returns, // unless an alternate GC finalizer is set on them. // -// This is safe to call if the handle pointer is nil, in which case it does -// nothing. The pointer will be set to nil after freeing to prevent double free. -// However, it is not safe to call this method concurrently from multiple +// This is safe to call multiple times; subsequent calls after the first will do +// nothing. However, it is not safe to call this method concurrently from multiple // goroutines. func (db *Database) Close(ctx context.Context) error { if db.handle == nil { diff --git a/ffi/memory.go b/ffi/memory.go index 7e117e6cb9ad..bfc6d2493002 100644 --- a/ffi/memory.go +++ b/ffi/memory.go @@ -1,9 +1,6 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -// Package ffi provides a Go wrapper around the [Firewood] database. -// -// [Firewood]: https://github.com/ava-labs/firewood package ffi // // Note that -lm is required on Linux but not on Mac. diff --git a/ffi/metrics.go b/ffi/metrics.go index 50e4eaf9d8c8..e50ae69b5833 100644 --- a/ffi/metrics.go +++ b/ffi/metrics.go @@ -49,6 +49,7 @@ func (Gatherer) Gather() ([]*dto.MetricFamily, error) { // This function only needs to be called once. // An error is returned if this method is called a second time, or if it is // called after StartMetricsWithExporter. +// This is best used in conjunction with the [Gatherer] type to collect metrics. func StartMetrics() error { return getErrorFromVoidResult(C.fwd_start_metrics()) } diff --git a/ffi/proposal.go b/ffi/proposal.go index 315de8ef6191..4d59f4e35791 100644 --- a/ffi/proposal.go +++ b/ffi/proposal.go @@ -1,9 +1,6 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -// Package ffi provides a Go wrapper around the [Firewood] database. -// -// [Firewood]: https://github.com/ava-labs/firewood package ffi // #include @@ -29,6 +26,10 @@ var errDroppedProposal = errors.New("proposal already dropped") // when the Proposal is garbage collected, but relying on finalizers is not // recommended. Failing to commit or drop a proposal before the database is // closed will cause it to block or fail. +// +// All operations on a Proposal are thread-safe with respect to each other, +// except for [Proposal.Commit] and [Proposal.Drop], which are not safe to +// call concurrently with any other operations. type Proposal struct { // handle is an opaque pointer to the proposal within Firewood. It should be // passed to the C FFI functions that operate on proposals @@ -49,14 +50,12 @@ type Proposal struct { } // Root retrieves the root hash of the proposal. -// If the proposal is empty (i.e. no keys in database), -// it returns nil, nil. func (p *Proposal) Root() (Hash, error) { return p.root, nil } // Get retrieves the value for the given key. -// If the key does not exist, it returns (nil, nil). +// If the key does not exist, it returns nil. func (p *Proposal) Get(key []byte) ([]byte, error) { if p.handle == nil { return nil, errDroppedProposal @@ -69,7 +68,7 @@ func (p *Proposal) Get(key []byte) ([]byte, error) { } // Iter creates and iterator starting from the provided key on proposal. -// pass empty slice to start from beginning +// pass empty slice to start from beginning. func (p *Proposal) Iter(key []byte) (*Iterator, error) { if p.handle == nil { return nil, errDBClosed @@ -85,6 +84,8 @@ func (p *Proposal) Iter(key []byte) (*Iterator, error) { // Propose is equivalent to [Database.Propose] except that the new proposal is // based on `p`. +// The returned proposal cannot be committed until the parent proposal `p` has been +// committed. Additionally, it must be committed or dropped before the [Database] is closed. // // Value Semantics: // - nil value (vals[i] == nil): Performs a DeleteRange operation using the key as a prefix @@ -107,7 +108,7 @@ func (p *Proposal) Propose(keys, vals [][]byte) (*Proposal, error) { // Commit commits the proposal and returns any errors. // -// The proposal handle is no longer valid after this call, but the root +// The underlying data is no longer available after this call, but the root // hash can still be retrieved using [Proposal.Root]. func (p *Proposal) Commit() error { return p.keepAliveHandle.disown(true /* evenOnError */, func() error { @@ -117,17 +118,18 @@ func (p *Proposal) Commit() error { _, err := getHashKeyFromHashResult(C.fwd_commit_proposal(p.handle)) + // Prevent double free p.handle = nil return err }) } -// Drop releases the memory associated with the Proposal. -// -// This is safe to call if the pointer is nil, in which case it does nothing. +// Drop releases the memory associated with the Proposal. All child proposals +// created from this proposal can no longer be committed. // -// The pointer will be set to nil after freeing to prevent double free. +// This is safe to call if the memory has already been released, in which case +// it does nothing. func (p *Proposal) Drop() error { return p.keepAliveHandle.disown(false /* evenOnError */, func() error { if p.handle == nil { @@ -138,6 +140,7 @@ func (p *Proposal) Drop() error { return fmt.Errorf("%w: %w", errFreeingValue, err) } + // Prevent double free p.handle = nil return nil diff --git a/ffi/revision.go b/ffi/revision.go index 83792880ddc9..743a9b5d7d51 100644 --- a/ffi/revision.go +++ b/ffi/revision.go @@ -1,9 +1,6 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -// Package ffi provides a Go wrapper around the [Firewood] database. -// -// [Firewood]: https://github.com/ava-labs/firewood package ffi // #include @@ -19,11 +16,11 @@ import ( ) var ( + ErrDroppedRevision = errors.New("revision already dropped") errRevisionNotFound = errors.New("revision not found") - errDroppedRevision = errors.New("revision already dropped") ) -// Revision is an immutable view over the database at a specific root hash. +// Revision is an immutable view over the state at a specific root hash. // Instances are created via [Database.Revision], provide read-only access to // the revision, and must be released with [Revision.Drop] when no longer needed. // @@ -31,6 +28,13 @@ var ( // is set on each Revision to ensure that Drop is called when the Revision is // garbage collected, but relying on finalizers is not recommended. Failing to // drop a revision before the database is closed will cause it to block or fail. +// +// Additionally, Revisions should be dropped when no longer needed to allow the +// database to free any associated resources. Firewood ensures that the state +// associated with a Revision is retained until all Revisions based on that state +// have been dropped. +// +// All operations on a Revision are thread-safe with respect to each other. type Revision struct { // handle is an opaque pointer to the revision within Firewood. It should be // passed to the C FFI functions that operate on revisions @@ -51,12 +55,12 @@ type Revision struct { } // Get reads the value stored at the provided key within the revision. +// If the key does not exist, it returns nil. // -// It returns errDroppedRevision if Drop has already been called and the underlying -// handle is no longer available. +// It returns ErrDroppedRevision if Drop has already been called. func (r *Revision) Get(key []byte) ([]byte, error) { if r.handle == nil { - return nil, errDroppedRevision + return nil, ErrDroppedRevision } var pinner runtime.Pinner @@ -70,9 +74,10 @@ func (r *Revision) Get(key []byte) ([]byte, error) { // Iter creates an iterator starting from the provided key on revision. // pass empty slice to start from beginning +// It returns ErrDroppedRevision if Drop has already been called. func (r *Revision) Iter(key []byte) (*Iterator, error) { if r.handle == nil { - return nil, errDroppedRevision + return nil, ErrDroppedRevision } var pinner runtime.Pinner @@ -101,6 +106,7 @@ func (r *Revision) Drop() error { }) } +// Root returns the root hash of the revision. func (r *Revision) Root() Hash { return r.root } From f22ddd3fe7de23e41e75a489130967bd045e4441 Mon Sep 17 00:00:00 2001 From: AminR443 Date: Wed, 19 Nov 2025 17:05:59 -0500 Subject: [PATCH 1043/1053] feat: use `parking_lot` to eliminate lock poisoning (#1476) This PR closes #1030 by migrating from the standard library's `Mutex` and `RwLock` implementations to `parking_lot` equivalents throughout the codebase. --- Cargo.lock | 3 ++ Cargo.toml | 1 + clippy.toml | 5 +++ ffi/Cargo.toml | 1 + ffi/src/arc_cache.rs | 5 ++- ffi/src/metrics_setup.rs | 7 ++-- firewood/Cargo.toml | 1 + firewood/src/manager.rs | 51 ++++++----------------- firewood/src/root_store.rs | 14 +++---- storage/Cargo.toml | 1 + storage/src/linear/filebacked.rs | 21 ++++------ storage/src/linear/memory.rs | 7 ++-- storage/src/node/persist.rs | 27 +++++------- storage/src/nodestore/mod.rs | 14 +++---- storage/src/nodestore/persist_io_uring.rs | 2 +- 15 files changed, 67 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f72bcf64935..f8739b401b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,6 +1097,7 @@ dependencies = [ "hex-literal", "integer-encoding", "metrics", + "parking_lot", "plain_hasher", "pprof", "rand 0.9.2", @@ -1151,6 +1152,7 @@ dependencies = [ "metrics", "metrics-util", "oxhttp", + "parking_lot", "test-case", "tikv-jemallocator", ] @@ -1213,6 +1215,7 @@ dependencies = [ "lru", "metrics", "nonzero_ext", + "parking_lot", "pprof", "rand 0.9.2", "rlp", diff --git a/Cargo.toml b/Cargo.toml index 16b7580933c1..cfdff00314a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ sha2 = "0.10.9" smallvec = { version = "1.15.1", features = ["write", "union", "const_new"] } test-case = "3.3.1" thiserror = "2.0.17" +parking_lot = "0.12.3" # common dev dependencies criterion = "0.7.0" diff --git a/clippy.toml b/clippy.toml index af263e3308da..f46b95050f76 100644 --- a/clippy.toml +++ b/clippy.toml @@ -16,4 +16,9 @@ disallowed-methods = [ disallowed-types = [ { path = "rand::SeedableRng", replacement = "firewood_storage::SeededRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, { path = "rand::rngs::StdRng", replacement = "firewood_storage::SeededRng", reason = "use a prng with a user-defined seed instead", allow-invalid = true }, + { path = "std::sync::Mutex", replacement = "parking_lot::Mutex", reason = "prefer parking_lot locking primitives for consistency and performance", allow-invalid = true }, + { path = "std::sync::MutexGuard", replacement = "parking_lot::MutexGuard", reason = "prefer parking_lot locking primitives for consistency and performance", allow-invalid = true }, + { path = "std::sync::RwLock", replacement = "parking_lot::RwLock", reason = "prefer parking_lot locking primitives for consistency and performance", allow-invalid = true }, + { path = "std::sync::RwLockReadGuard", replacement = "parking_lot::RwLockReadGuard", reason = "prefer parking_lot locking primitives for consistency and performance", allow-invalid = true }, + { path = "std::sync::RwLockWriteGuard", replacement = "parking_lot::RwLockWriteGuard", reason = "prefer parking_lot locking primitives for consistency and performance", allow-invalid = true }, ] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 9e891ebf6afe..9ba1b4953498 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -26,6 +26,7 @@ firewood.workspace = true firewood-storage.workspace = true metrics.workspace = true metrics-util.workspace = true +parking_lot.workspace = true # Regular dependencies chrono = "0.4.42" oxhttp = "0.3.1" diff --git a/ffi/src/arc_cache.rs b/ffi/src/arc_cache.rs index 472497d2972d..2d727bbce8a0 100644 --- a/ffi/src/arc_cache.rs +++ b/ffi/src/arc_cache.rs @@ -8,7 +8,8 @@ //! layer to improve performance by avoiding repeated view creation for the same //! root hash during database operations. -use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use parking_lot::{Mutex, MutexGuard}; +use std::sync::Arc; /// A thread-safe single-item cache that stores key-value pairs as `Arc`. /// @@ -79,7 +80,7 @@ impl ArcCache { } fn lock(&self) -> MutexGuard<'_, Option<(K, Arc)>> { - self.cache.lock().unwrap_or_else(PoisonError::into_inner) + self.cache.lock() } } diff --git a/ffi/src/metrics_setup.rs b/ffi/src/metrics_setup.rs index 28d54742c418..f37a61e02f38 100644 --- a/ffi/src/metrics_setup.rs +++ b/ffi/src/metrics_setup.rs @@ -1,15 +1,16 @@ // Copyright (C) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. +use parking_lot::Mutex; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::error::Error; use std::fmt::Write; use std::net::Ipv6Addr; use std::ops::Deref; +use std::sync::Arc; use std::sync::OnceLock; use std::sync::atomic::Ordering; -use std::sync::{Arc, Mutex}; use std::time::SystemTime; use oxhttp::Server; @@ -122,7 +123,7 @@ impl TextRecorder { .saturating_add(u64::from(epoch_duration.subsec_millis())); writeln!(output, "# {utc_now}").expect("write to string cannot fail"); - let help_guard = self.inner.help.lock().expect("poisoned lock"); + let help_guard = self.inner.help.lock(); let counters = self.registry.get_counter_handles(); let mut seen_counters = HashSet::new(); @@ -228,7 +229,6 @@ impl metrics::Recorder for TextRecorder { self.inner .help .lock() - .expect("poisoned lock") .insert(key.as_str().to_string(), description.to_string()); } @@ -241,7 +241,6 @@ impl metrics::Recorder for TextRecorder { self.inner .help .lock() - .expect("poisoned lock") .insert(key.as_str().to_string(), description.to_string()); } diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index 445f26e67261..ca6daa98ab66 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -36,6 +36,7 @@ thiserror.workspace = true # Regular dependencies typed-builder = "0.23.1" rayon = "1.11.0" +parking_lot.workspace = true fjall = "2.11.2" derive-where = "1.6.0" weak-table = "0.3.2" diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 6ad91f0f67a4..f5b5afa40f30 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -10,10 +10,11 @@ reason = "Found 3 occurrences after enabling the lint." )] +use parking_lot::{Mutex, RwLock}; use std::collections::{HashMap, VecDeque}; use std::num::NonZero; use std::path::PathBuf; -use std::sync::{Arc, Mutex, OnceLock, RwLock, Weak}; +use std::sync::{Arc, OnceLock, Weak}; use firewood_storage::logger::{trace, warn}; use metrics::gauge; @@ -136,11 +137,7 @@ impl RevisionManager { }; if let Some(hash) = nodestore.root_hash().or_default_root_hash() { - manager - .by_hash - .write() - .expect("poisoned lock") - .insert(hash, nodestore.clone()); + manager.by_hash.write().insert(hash, nodestore.clone()); } if config.truncate { @@ -200,16 +197,15 @@ impl RevisionManager { // If `RootStore` allows space reuse, add the oldest revision's nodes to the free list. // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free - while self.historical.read().expect("poisoned lock").len() >= self.max_revisions { + while self.historical.read().len() >= self.max_revisions { let oldest = self .historical .write() - .expect("poisoned lock") .pop_front() .expect("must be present"); let oldest_hash = oldest.root_hash().or_default_root_hash(); if let Some(ref hash) = oldest_hash { - self.by_hash.write().expect("poisoned lock").remove(hash); + self.by_hash.write().remove(hash); } // We reap the revision's nodes only if `RootStore` allows space reuse. @@ -223,16 +219,12 @@ impl RevisionManager { Ok(oldest) => oldest.reap_deleted(&mut committed)?, Err(original) => { warn!("Oldest revision could not be reaped; still referenced"); - self.historical - .write() - .expect("poisoned lock") - .push_front(original); + self.historical.write().push_front(original); break; } } } - gauge!("firewood.active_revisions") - .set(self.historical.read().expect("poisoned lock").len() as f64); + gauge!("firewood.active_revisions").set(self.historical.read().len() as f64); gauge!("firewood.max_revisions").set(self.max_revisions as f64); } @@ -250,15 +242,9 @@ impl RevisionManager { // 5. Set last committed revision let committed: CommittedRevision = committed.into(); - self.historical - .write() - .expect("poisoned lock") - .push_back(committed.clone()); + self.historical.write().push_back(committed.clone()); if let Some(hash) = committed.root_hash().or_default_root_hash() { - self.by_hash - .write() - .expect("poisoned lock") - .insert(hash, committed.clone()); + self.by_hash.write().insert(hash, committed.clone()); } // 6. Proposal Cleanup @@ -266,11 +252,10 @@ impl RevisionManager { // referenced by anyone else. self.proposals .lock() - .expect("poisoned lock") .retain(|p| !Arc::ptr_eq(&proposal, p) && Arc::strong_count(p) > 1); // then reparent any proposals that have this proposal as a parent - for p in &*self.proposals.lock().expect("poisoned lock") { + for p in &*self.proposals.lock() { proposal.commit_reparent(p); } @@ -298,7 +283,6 @@ impl RevisionManager { let proposal = self .proposals .lock() - .expect("poisoned lock") .iter() .find(|p| p.root_hash().as_ref() == Some(&root_hash)) .cloned() @@ -310,20 +294,18 @@ impl RevisionManager { } pub fn add_proposal(&self, proposal: ProposedRevision) { - self.proposals.lock().expect("poisoned lock").push(proposal); + self.proposals.lock().push(proposal); } /// TODO: should we support fetching all hashes from `RootStore`? pub fn all_hashes(&self) -> Vec { self.historical .read() - .expect("poisoned lock") .iter() .filter_map(|r| r.root_hash().or_default_root_hash()) .chain( self.proposals .lock() - .expect("poisoned lock") .iter() .filter_map(|p| p.root_hash().or_default_root_hash()), ) @@ -337,17 +319,11 @@ impl RevisionManager { /// 3. Check the persistent `RootStore`. pub fn revision(&self, root_hash: HashKey) -> Result { // 1. Check the in-memory revision manager. - if let Some(revision) = self - .by_hash - .read() - .expect("poisoned lock") - .get(&root_hash) - .cloned() - { + if let Some(revision) = self.by_hash.read().get(&root_hash).cloned() { return Ok(revision); } - let mut cache_guard = self.by_rootstore.lock().expect("poisoned lock"); + let mut cache_guard = self.by_rootstore.lock(); // 2. Check the in-memory `RootStore` cache. if let Some(nodestore) = cache_guard.get(&root_hash) { @@ -383,7 +359,6 @@ impl RevisionManager { pub fn current_revision(&self) -> CommittedRevision { self.historical .read() - .expect("poisoned lock") .back() .expect("there is always one revision") .clone() diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index 854a2459f42a..59e64598377d 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -2,10 +2,9 @@ // See the file LICENSE.md for licensing terms. #[cfg(test)] -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; +use parking_lot::Mutex; +#[cfg(test)] +use std::{collections::HashMap, sync::Arc}; use std::{fmt::Debug, path::Path}; use derive_where::derive_where; @@ -101,10 +100,7 @@ impl RootStore for MockStore { return Err("Adding roots should fail".into()); } - self.roots - .lock() - .expect("poisoned lock") - .insert(hash.clone(), *address); + self.roots.lock().insert(hash.clone(), *address); Ok(()) } @@ -116,7 +112,7 @@ impl RootStore for MockStore { return Err("Getting roots should fail".into()); } - Ok(self.roots.lock().expect("poisoned lock").get(hash).copied()) + Ok(self.roots.lock().get(hash).copied()) } fn allow_space_reuse(&self) -> bool { diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 6430e067ef7a..b3be6bfb0ad8 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -40,6 +40,7 @@ indicatif = "0.18.3" lru = "0.16.2" semver = "1.0.27" triomphe = "0.1.15" +parking_lot.workspace = true # Optional dependencies bytes = { version = "1.11.0", optional = true } io-uring = { version = "0.7.11", optional = true } diff --git a/storage/src/linear/filebacked.rs b/storage/src/linear/filebacked.rs index 3474d21021aa..3e4d553d1e75 100644 --- a/storage/src/linear/filebacked.rs +++ b/storage/src/linear/filebacked.rs @@ -23,6 +23,7 @@ reason = "Found 1 occurrences after enabling the lint." )] +use parking_lot::Mutex; use std::fs::{File, OpenOptions}; use std::io::Read; #[cfg(feature = "io-uring")] @@ -33,7 +34,6 @@ use std::os::unix::fs::FileExt; #[cfg(windows)] use std::os::windows::fs::FileExt; use std::path::PathBuf; -use std::sync::Mutex; use lru::LruCache; use metrics::counter; @@ -58,10 +58,7 @@ impl Drop for FileBacked { #[cfg(feature = "io-uring")] { // non-blocking because we have mutable access to self - let ring = self - .ring - .get_mut() - .unwrap_or_else(std::sync::PoisonError::into_inner); + let ring = self.ring.get_mut(); // We are manually dropping the ring here to ensure that it is // flushed and dropped before we unlock the file descriptor. @@ -196,7 +193,7 @@ impl ReadableStorage for FileBacked { } fn read_cached_node(&self, addr: LinearAddress, mode: &'static str) -> Option { - let mut guard = self.cache.lock().expect("poisoned lock"); + let mut guard = self.cache.lock(); let cached = guard.get(&addr).cloned(); counter!("firewood.cache.node", "mode" => mode, "type" => if cached.is_some() { "hit" } else { "miss" }) .increment(1); @@ -204,7 +201,7 @@ impl ReadableStorage for FileBacked { } fn free_list_cache(&self, addr: LinearAddress) -> Option> { - let mut guard = self.free_list_cache.lock().expect("poisoned lock"); + let mut guard = self.free_list_cache.lock(); let cached = guard.pop(&addr); counter!("firewood.cache.freelist", "type" => if cached.is_some() { "hit" } else { "miss" }).increment(1); cached @@ -220,12 +217,12 @@ impl ReadableStorage for FileBacked { // we don't cache reads } CacheReadStrategy::All => { - let mut guard = self.cache.lock().expect("poisoned lock"); + let mut guard = self.cache.lock(); guard.put(addr, node); } CacheReadStrategy::BranchReads => { if !node.is_leaf() { - let mut guard = self.cache.lock().expect("poisoned lock"); + let mut guard = self.cache.lock(); guard.put(addr, node); } } @@ -257,7 +254,7 @@ impl WritableStorage for FileBacked { &self, nodes: impl IntoIterator, ) -> Result<(), FileIoError> { - let mut guard = self.cache.lock().expect("poisoned lock"); + let mut guard = self.cache.lock(); for maybe_persisted_node in nodes { // Since we know the node is in Allocated state, we can get both address and shared node let (addr, shared_node) = maybe_persisted_node @@ -272,14 +269,14 @@ impl WritableStorage for FileBacked { } fn invalidate_cached_nodes<'a>(&self, nodes: impl Iterator) { - let mut guard = self.cache.lock().expect("poisoned lock"); + let mut guard = self.cache.lock(); for addr in nodes.filter_map(MaybePersistedNode::as_linear_address) { guard.pop(&addr); } } fn add_to_free_list_cache(&self, addr: LinearAddress, next: Option) { - let mut guard = self.free_list_cache.lock().expect("poisoned lock"); + let mut guard = self.free_list_cache.lock(); guard.put(addr, next); } } diff --git a/storage/src/linear/memory.rs b/storage/src/linear/memory.rs index 45cdc91c9d00..feaf7499db94 100644 --- a/storage/src/linear/memory.rs +++ b/storage/src/linear/memory.rs @@ -12,8 +12,8 @@ use super::{FileIoError, OffsetReader, ReadableStorage, WritableStorage}; use metrics::counter; +use parking_lot::Mutex; use std::io::Cursor; -use std::sync::Mutex; #[derive(Debug, Default)] /// An in-memory impelementation of [`WritableStorage`] and [`ReadableStorage`] @@ -34,7 +34,7 @@ impl MemStore { impl WritableStorage for MemStore { fn write(&self, offset: u64, object: &[u8]) -> Result { let offset = offset as usize; - let mut guard = self.bytes.lock().expect("poisoned lock"); + let mut guard = self.bytes.lock(); if offset + object.len() > guard.len() { guard.resize(offset + object.len(), 0); } @@ -49,7 +49,6 @@ impl ReadableStorage for MemStore { let bytes = self .bytes .lock() - .expect("poisoned lock") .get(addr as usize..) .unwrap_or_default() .to_owned(); @@ -58,7 +57,7 @@ impl ReadableStorage for MemStore { } fn size(&self) -> Result { - Ok(self.bytes.lock().expect("poisoned lock").len() as u64) + Ok(self.bytes.lock().len() as u64) } } diff --git a/storage/src/node/persist.rs b/storage/src/node/persist.rs index 2e68553e7b65..60f91344da38 100644 --- a/storage/src/node/persist.rs +++ b/storage/src/node/persist.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use std::sync::{Mutex, TryLockError}; +use parking_lot::Mutex; use std::{fmt::Display, sync::Arc}; use crate::{FileIoError, LinearAddress, NodeReader, SharedNode}; @@ -42,7 +42,7 @@ impl PartialEq for MaybePersistedNode { if Arc::ptr_eq(&self.0, &other.0) { return true; } - *self.0.lock().expect("poisoned lock") == *other.0.lock().expect("poisoned lock") + *self.0.lock() == *other.0.lock() } } @@ -62,7 +62,7 @@ impl From for MaybePersistedNode { impl From<&MaybePersistedNode> for Option { fn from(node: &MaybePersistedNode) -> Option { - match &*node.0.lock().expect("poisoned lock") { + match &*node.0.lock() { MaybePersisted::Unpersisted(_) => None, MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { Some(*address) @@ -87,7 +87,7 @@ impl MaybePersistedNode { /// - `Ok(SharedNode)` contains the node if successfully retrieved /// - `Err(FileIoError)` if there was an error reading from storage pub fn as_shared_node(&self, storage: &S) -> Result { - match &*self.0.lock().expect("poisoned lock") { + match &*self.0.lock() { MaybePersisted::Allocated(_, node) | MaybePersisted::Unpersisted(node) => { Ok(node.clone()) } @@ -102,7 +102,7 @@ impl MaybePersistedNode { /// Returns `Some(LinearAddress)` if the node is persisted on disk, otherwise `None`. #[must_use] pub fn as_linear_address(&self) -> Option { - match &*self.0.lock().expect("poisoned lock") { + match &*self.0.lock() { MaybePersisted::Unpersisted(_) => None, MaybePersisted::Allocated(address, _) | MaybePersisted::Persisted(address) => { Some(*address) @@ -117,7 +117,7 @@ impl MaybePersistedNode { /// Returns `Some(&Self)` if the node is unpersisted, otherwise `None`. #[must_use] pub fn unpersisted(&self) -> Option<&Self> { - match &*self.0.lock().expect("poisoned lock") { + match &*self.0.lock() { MaybePersisted::Allocated(_, _) | MaybePersisted::Unpersisted(_) => Some(self), MaybePersisted::Persisted(_) => None, } @@ -134,7 +134,7 @@ impl MaybePersistedNode { /// /// * `addr` - The `LinearAddress` where the node has been persisted on disk pub fn persist_at(&self, addr: LinearAddress) { - *self.0.lock().expect("poisoned lock") = MaybePersisted::Persisted(addr); + *self.0.lock() = MaybePersisted::Persisted(addr); } /// Updates the internal state to indicate this node is allocated at the specified disk address. @@ -148,7 +148,7 @@ impl MaybePersistedNode { /// /// * `addr` - The `LinearAddress` where the node has been allocated on disk pub fn allocate_at(&self, addr: LinearAddress) { - let mut guard = self.0.lock().expect("poisoned lock"); + let mut guard = self.0.lock(); let node = { match &*guard { MaybePersisted::Unpersisted(node) | MaybePersisted::Allocated(_, node) => { @@ -170,7 +170,7 @@ impl MaybePersistedNode { /// otherwise `None`. #[must_use] pub fn allocated_info(&self) -> Option<(LinearAddress, SharedNode)> { - match &*self.0.lock().expect("poisoned lock") { + match &*self.0.lock() { MaybePersisted::Allocated(addr, node) => Some((*addr, node.clone())), _ => None, } @@ -189,19 +189,14 @@ impl MaybePersistedNode { impl Display for MaybePersistedNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0.try_lock() { - Ok(guard) => match &*guard { + Some(guard) => match &*guard { MaybePersisted::Unpersisted(node) => write!(f, "M{:p}", (*node).as_ptr()), MaybePersisted::Allocated(addr, node) => { write!(f, "A{:p}@{addr}", (*node).as_ptr()) } MaybePersisted::Persisted(addr) => write!(f, "{addr}"), }, - Err(TryLockError::WouldBlock) => { - write!(f, "") - } - Err(TryLockError::Poisoned(_)) => { - panic!("poisoned lock") - } + None => write!(f, ""), } } } diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 2cb9513335dd..3575d6c9e7cb 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -457,7 +457,7 @@ pub struct ImmutableProposal { /// Nodes that have been deleted in this proposal. deleted: Box<[MaybePersistedNode]>, /// The parent of this proposal. - parent: Arc>, + parent: Arc>, /// The root of the trie in this proposal. root: Option, } @@ -466,14 +466,14 @@ impl ImmutableProposal { /// Returns true if the parent of this proposal is committed and has the given hash. #[must_use] fn parent_hash_is(&self, hash: Option) -> bool { - match &*self.parent.lock().expect("poisoned lock") { + match &*self.parent.lock() { NodeStoreParent::Committed(root_hash) => *root_hash == hash, NodeStoreParent::Proposed(_) => false, } } fn commit_reparent(self: &Arc, committing: &Arc) { - let mut guard = self.parent.lock().expect("poisoned lock"); + let mut guard = self.parent.lock(); if let NodeStoreParent::Proposed(ref parent) = *guard && Arc::ptr_eq(parent, committing) { @@ -598,7 +598,7 @@ impl TryFrom> header, kind: Arc::new(ImmutableProposal { deleted: kind.deleted.into(), - parent: Arc::new(std::sync::Mutex::new(kind.parent)), + parent: Arc::new(parking_lot::Mutex::new(kind.parent)), root: None, }), storage, @@ -917,7 +917,7 @@ mod tests { let r1 = NodeStore::new(&base).unwrap(); let r1: NodeStore, _> = r1.try_into().unwrap(); { - let parent = r1.kind.parent.lock().expect("poisoned lock"); + let parent = r1.kind.parent.lock(); assert!(matches!(*parent, NodeStoreParent::Committed(None))); } @@ -925,7 +925,7 @@ mod tests { let r2: NodeStore = NodeStore::new(&r1).unwrap(); let r2: NodeStore, _> = r2.try_into().unwrap(); { - let parent = r2.kind.parent.lock().expect("poisoned lock"); + let parent = r2.kind.parent.lock(); assert!(matches!(*parent, NodeStoreParent::Proposed(_))); } @@ -933,7 +933,7 @@ mod tests { r1.commit_reparent(&r2); // now check r2's parent, should match the hash of r1 (which is still None) - let parent = r2.kind.parent.lock().expect("poisoned lock"); + let parent = r2.kind.parent.lock(); if let NodeStoreParent::Committed(hash) = &*parent { assert_eq!(*hash, r1.root_hash()); assert_eq!(*hash, None); diff --git a/storage/src/nodestore/persist_io_uring.rs b/storage/src/nodestore/persist_io_uring.rs index ca1bc993d0c4..c3e82c28fecb 100644 --- a/storage/src/nodestore/persist_io_uring.rs +++ b/storage/src/nodestore/persist_io_uring.rs @@ -120,7 +120,7 @@ impl NodeStore { &self, allocated_objects: Vec<(&[u8], LinearAddress, MaybePersistedNode)>, ) -> Result<(), FileIoError> { - let mut ring = self.storage.ring.lock().expect("poisoned lock"); + let mut ring = self.storage.ring.lock(); let mut saved_pinned_buffers = vec![Option::>::None; FileBacked::RINGSIZE as usize]; From cddb5c12843a6c2af299327f212ff73ed331da89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:34:19 -0800 Subject: [PATCH 1044/1053] chore(deps): bump golang.org/x/crypto from 0.36.0 to 0.45.0 in /ffi/tests/firewood (#1479) --- ffi/tests/firewood/go.mod | 12 ++++++------ ffi/tests/firewood/go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ffi/tests/firewood/go.mod b/ffi/tests/firewood/go.mod index 56179bdcde07..f2eb01aba20c 100644 --- a/ffi/tests/firewood/go.mod +++ b/ffi/tests/firewood/go.mod @@ -1,6 +1,6 @@ module github.com/ava-labs/firewood/ffi/tests/firewood -go 1.24 +go 1.24.0 toolchain go1.24.9 @@ -39,12 +39,12 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/ffi/tests/firewood/go.sum b/ffi/tests/firewood/go.sum index cd8a9e419e92..321999ea8f96 100644 --- a/ffi/tests/firewood/go.sum +++ b/ffi/tests/firewood/go.sum @@ -75,20 +75,20 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= From 2cbc78055151d4f516d7503339d155b267b4272d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:43:20 +0000 Subject: [PATCH 1045/1053] chore(deps): bump golang.org/x/crypto from 0.35.0 to 0.45.0 in /ffi/tests/eth (#1478) --- ffi/tests/eth/go.mod | 10 +++++----- ffi/tests/eth/go.sum | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ffi/tests/eth/go.mod b/ffi/tests/eth/go.mod index b4bbd7f68f6c..4ea0559a2e39 100644 --- a/ffi/tests/eth/go.mod +++ b/ffi/tests/eth/go.mod @@ -1,6 +1,6 @@ module github.com/ava-labs/firewood/ffi/tests/eth -go 1.24 +go 1.24.0 toolchain go1.24.9 @@ -59,11 +59,11 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/crypto v0.35.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/ffi/tests/eth/go.sum b/ffi/tests/eth/go.sum index 37f9918afefb..a553aa139038 100644 --- a/ffi/tests/eth/go.sum +++ b/ffi/tests/eth/go.sum @@ -336,8 +336,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= @@ -349,8 +349,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -372,8 +372,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -382,8 +382,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -419,8 +419,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -429,8 +429,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -446,8 +446,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From b5e22b7c17aa7ce95d14c2bf72eaba874f8763b6 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:21:20 -0500 Subject: [PATCH 1046/1053] refactor(rootstore): remove `MockStore` (#1477) With `FjallStore` now merged in, I believe there's not a strong reason to keep around `MockStore`. This PR removes `MockStore` from Firewood. This PR also includes cleaning up the `FjallStore` tests by adding a helper for constructing instances of `Db` with `FjallStore`: 9b0d009. --- firewood/src/db.rs | 71 +++++++++----------------------------- firewood/src/root_store.rs | 57 +----------------------------- 2 files changed, 18 insertions(+), 110 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 864203e40350..f92338e37e3f 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -406,7 +406,7 @@ mod test { use crate::db::{Db, Proposal, UseParallel}; use crate::manager::RevisionManagerConfig; - use crate::root_store::{MockStore, RootStore}; + use crate::root_store::RootStore; use crate::v2::api::{Db as _, DbView, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -1085,32 +1085,11 @@ mod test { assert_eq!(value, retrieved_value.as_ref()); } - #[test] - fn test_root_store() { - let mock_store = MockStore::default(); - let db = TestDb::with_mockstore(mock_store); - - test_root_store_helper(db); - } - + /// Verifies that persisted revisions are still accessible when reopening the database. #[test] fn test_fjall_store() { - let tmpdir = tempfile::tempdir().unwrap(); - let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] - .iter() - .collect(); - let root_store_path = tmpdir.as_ref().join("fjall_store"); - let dbconfig = DbConfig::builder() - .root_store_dir(Some(root_store_path)) - .build(); - let db = Db::new(dbpath, dbconfig).unwrap(); - let db = TestDb { db, tmpdir }; - - test_root_store_helper(db); - } + let db = TestDb::new_with_fjall_store(DbConfig::builder().build()); - /// Verifies that persisted revisions are still accessible when reopening the database. - fn test_root_store_helper(db: TestDb) { // First, create a revision to retrieve let key = b"key"; let value = b"value"; @@ -1138,27 +1117,9 @@ mod test { assert_eq!(value, retrieved_value.as_ref()); } - #[test] - fn test_root_store_errs() { - let mock_store = MockStore::with_failures(); - let db = TestDb::with_mockstore(mock_store); - - let view = db.view(TrieHash::empty()); - assert!(view.is_err()); - - let batch = vec![BatchOp::Put { - key: b"k", - value: b"v", - }]; - - let proposal = db.propose(batch).unwrap(); - assert!(proposal.commit().is_err()); - } - #[test] fn test_rootstore_empty_db_reopen() { - let mock_store = MockStore::default(); - let db = TestDb::with_mockstore(mock_store); + let db = TestDb::new_with_fjall_store(DbConfig::builder().build()); db.reopen(); } @@ -1168,17 +1129,10 @@ mod test { fn test_fjall_store_with_capped_max_revisions() { const NUM_REVISIONS: usize = 10; - let tmpdir = tempfile::tempdir().unwrap(); - let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] - .iter() - .collect(); - let root_store_path = tmpdir.as_ref().join("fjall_store"); let dbconfig = DbConfig::builder() - .root_store_dir(Some(root_store_path)) .manager(RevisionManagerConfig::builder().max_revisions(5).build()) .build(); - let db = Db::new(dbpath, dbconfig).unwrap(); - let db = TestDb { db, tmpdir }; + let db = TestDb::new_with_fjall_store(dbconfig); // Create and commit 10 proposals let key = b"root_store"; @@ -1243,13 +1197,22 @@ mod test { TestDb { db, tmpdir } } - pub fn with_mockstore(mock_store: MockStore) -> Self { + /// Creates a new test database with `FjallStore` enabled. + /// + /// Overrides `root_store_dir` in dbconfig to provide a directory for `FjallStore`. + pub fn new_with_fjall_store(dbconfig: DbConfig) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let dbconfig = DbConfig::builder().build(); - let db = Db::with_root_store(dbpath, dbconfig, Box::new(mock_store)).unwrap(); + let root_store_path = tmpdir.as_ref().join("fjall_store"); + + let dbconfig = DbConfig { + root_store_dir: Some(root_store_path), + ..dbconfig + }; + + let db = Db::new(dbpath, dbconfig).unwrap(); TestDb { db, tmpdir } } diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index 59e64598377d..c23c77173a28 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -1,10 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -#[cfg(test)] -use parking_lot::Mutex; -#[cfg(test)] -use std::{collections::HashMap, sync::Arc}; +use fjall::{Config, Keyspace, PartitionCreateOptions, PartitionHandle, PersistMode}; use std::{fmt::Debug, path::Path}; use derive_where::derive_where; @@ -70,58 +67,6 @@ impl RootStore for NoOpStore { } } -#[cfg(test)] -#[derive(Debug, Default)] -pub struct MockStore { - roots: Arc>>, - should_fail: bool, -} - -#[cfg(test)] -impl MockStore { - /// Returns an instance of `MockStore` that fails for all `add_root` and `get` calls. - #[must_use] - pub fn with_failures() -> Self { - Self { - should_fail: true, - ..Default::default() - } - } -} - -#[cfg(test)] -impl RootStore for MockStore { - fn add_root( - &self, - hash: &TrieHash, - address: &LinearAddress, - ) -> Result<(), Box> { - if self.should_fail { - return Err("Adding roots should fail".into()); - } - - self.roots.lock().insert(hash.clone(), *address); - Ok(()) - } - - fn get( - &self, - hash: &TrieHash, - ) -> Result, Box> { - if self.should_fail { - return Err("Getting roots should fail".into()); - } - - Ok(self.roots.lock().get(hash).copied()) - } - - fn allow_space_reuse(&self) -> bool { - false - } -} - -use fjall::{Config, Keyspace, PartitionCreateOptions, PartitionHandle, PersistMode}; - #[derive_where(Debug)] #[derive_where(skip_inner)] pub struct FjallStore { From 5f94d4fa964f86c9d236809b811a8bc70c555837 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:55:59 -0500 Subject: [PATCH 1047/1053] refactor(firewood/db): unify reopen methods (#1481) IMO, the `reopen()` method of `TestDb` is a misnomer - while this method does not truncate the existing Firewood file, it uses the default configuration for the new `TestDb` instance, ignoring the prior instance's configuration. As a consumer, I would expect `reopen()` to maintain the existing configuration. This PR does the following: - Unifies `reopen()` and `reopen_with_config()` into a single method - Stores the `dbconfig` in `testDb` to allow the database configuration to persist across reopens - Adds documentation to both `reopen()` and `replace()` - Removes `into_root_store()` test helpers since `RootStore` reopens are now handled via the database configuration This PR is a precursor to #1481. --- firewood/src/db.rs | 91 +++++++++++++++++++++++++---------------- firewood/src/manager.rs | 8 ---- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index f92338e37e3f..8aedaa3c1e32 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -406,7 +406,6 @@ mod test { use crate::db::{Db, Proposal, UseParallel}; use crate::manager::RevisionManagerConfig; - use crate::root_store::RootStore; use crate::v2::api::{Db as _, DbView, Proposal as _}; use super::{BatchOp, DbConfig}; @@ -447,15 +446,6 @@ mod test { impl IterExt for T {} - #[cfg(test)] - impl Db { - /// Extract the root store by consuming the database instance. - /// This is primarily used for reopening or replacing the database with the same root store. - pub fn into_root_store(self) -> Box { - self.manager.into_root_store() - } - } - #[test] fn test_proposal_reads() { let db = TestDb::new(); @@ -667,12 +657,7 @@ mod test { .build(), ); insert_commit(&db, 1); - let db = db.reopen_with_config( - DbConfig::builder() - .truncate(false) - .use_parallel(UseParallel::Always) - .build(), - ); + let db = db.reopen(); insert_commit(&db, 2); // Check that the keys are still there after the commits let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap(); @@ -1170,6 +1155,7 @@ mod test { pub(super) struct TestDb { db: Db, tmpdir: tempfile::TempDir, + dbconfig: DbConfig, } impl Deref for TestDb { type Target = Db; @@ -1193,8 +1179,12 @@ mod test { let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let db = Db::new(dbpath, dbconfig).unwrap(); - TestDb { db, tmpdir } + let db = Db::new(dbpath, dbconfig.clone()).unwrap(); + TestDb { + db, + tmpdir, + dbconfig, + } } /// Creates a new test database with `FjallStore` enabled. @@ -1205,40 +1195,69 @@ mod test { let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let root_store_path = tmpdir.as_ref().join("fjall_store"); + let root_store_dir = tmpdir.as_ref().join("fjall_store"); let dbconfig = DbConfig { - root_store_dir: Some(root_store_path), + root_store_dir: Some(root_store_dir), ..dbconfig }; - let db = Db::new(dbpath, dbconfig).unwrap(); - TestDb { db, tmpdir } + let db = Db::new(dbpath, dbconfig.clone()).unwrap(); + TestDb { + db, + tmpdir, + dbconfig, + } } + /// Reopens the database at the same path, preserving existing data. + /// + /// This method closes the current database instance (releasing the advisory lock), + /// then opens it again at the same path while keeping the same configuration. pub fn reopen(self) -> Self { - self.reopen_with_config(DbConfig::builder().truncate(false).build()) - } - - pub fn reopen_with_config(self, dbconfig: DbConfig) -> Self { let path = self.path(); - let TestDb { db, tmpdir } = self; - - let root_store = db.into_root_store(); - - let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); - TestDb { db, tmpdir } + let TestDb { + db, + tmpdir, + dbconfig, + } = self; + + drop(db); + + let db = Db::new(path, dbconfig.clone()).unwrap(); + TestDb { + db, + tmpdir, + dbconfig, + } } + /// Replaces the database with a fresh instance at the same path. + /// + /// This method closes the current database instance (releasing the advisory lock), + /// and creates a new database. This completely resets the database, removing all + /// existing data and starting fresh. The new database instance will use the default + /// configuration with truncation enabled. + /// + /// This is useful for testing scenarios where you want to start with a clean slate + /// while maintaining the same temporary directory structure. pub fn replace(self) -> Self { let path = self.path(); - let TestDb { db, tmpdir } = self; + let TestDb { + db, + tmpdir, + dbconfig: _, + } = self; - let root_store = db.into_root_store(); + drop(db); let dbconfig = DbConfig::builder().truncate(true).build(); - let db = Db::with_root_store(path, dbconfig, root_store).unwrap(); - TestDb { db, tmpdir } + let db = Db::new(path, dbconfig.clone()).unwrap(); + TestDb { + db, + tmpdir, + dbconfig, + } } pub fn path(&self) -> PathBuf { diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index f5b5afa40f30..90ff8fbf5897 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -389,14 +389,6 @@ mod tests { use crate::root_store::NoOpStore; use tempfile::NamedTempFile; - #[cfg(test)] - impl RevisionManager { - /// Extract the root store by consuming the revision manager instance. - pub fn into_root_store(self) -> Box { - self.root_store - } - } - #[test] fn test_file_advisory_lock() { // Create a temporary file for testing From ec187ba1dc3e8ae8083a0a7b3d20d9b830ec8351 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:56:16 -0500 Subject: [PATCH 1048/1053] refactor(db/revisionmanager): move `RootStore` creation to `RevisionManager` (#1482) In preparation of moving in-memory revisions to their own data structure (#1470), this PR moves creation of `RootStore` into the `RevisionManager` - this move will allow us to inject the in-memory revisions data structure into `RootStore` during creation. This PR is a precursor to #1480. --- firewood/src/db.rs | 22 +++------------------- firewood/src/manager.rs | 27 +++++++++++++++------------ 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index 8aedaa3c1e32..a710898ca1ad 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -11,14 +11,13 @@ mod tests; use crate::iter::MerkleKeyValueIter; use crate::merkle::{Merkle, Value}; -use crate::root_store::{FjallStore, NoOpStore, RootStore}; pub use crate::v2::api::BatchOp; use crate::v2::api::{ self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, IntoBatchIter, KeyType, KeyValuePair, OptionalHashKeyExt, }; -use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig, RevisionManagerError}; +use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig}; use firewood_storage::{ CheckOpt, CheckerReport, Committed, FileBacked, FileIoError, HashedNodeReader, ImmutableProposal, NodeStore, Parentable, ReadableStorage, TrieReader, @@ -167,21 +166,6 @@ impl api::Db for Db { impl Db { /// Create a new database instance. pub fn new>(db_path: P, cfg: DbConfig) -> Result { - let root_store: Box = match &cfg.root_store_dir { - Some(path) => { - Box::new(FjallStore::new(path).map_err(RevisionManagerError::RootStoreError)?) - } - None => Box::new(NoOpStore {}), - }; - - Self::with_root_store(db_path, cfg, root_store) - } - - fn with_root_store>( - db_path: P, - cfg: DbConfig, - root_store: Box, - ) -> Result { let metrics = Arc::new(DbMetrics { proposals: counter!("firewood.proposals"), }); @@ -189,11 +173,11 @@ impl Db { let config_manager = ConfigManager::builder() .create(cfg.create_if_missing) .truncate(cfg.truncate) + .root_store_dir(cfg.root_store_dir) .manager(cfg.manager) .build(); - let manager = - RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager, root_store)?; + let manager = RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager)?; let db = Self { metrics, manager, diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index 90ff8fbf5897..a09d6e79c1dc 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -23,7 +23,7 @@ use typed_builder::TypedBuilder; use weak_table::WeakValueHashMap; use crate::merkle::Merkle; -use crate::root_store::RootStore; +use crate::root_store::{FjallStore, NoOpStore, RootStore}; use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; @@ -61,6 +61,9 @@ pub struct ConfigManager { /// existing contents will be lost. #[builder(default = false)] pub truncate: bool, + /// `RootStore` directory path + #[builder(default = None)] + pub root_store_dir: Option, /// Revision manager configuration. #[builder(default = RevisionManagerConfig::builder().build())] pub manager: RevisionManagerConfig, @@ -105,11 +108,7 @@ pub(crate) enum RevisionManagerError { } impl RevisionManager { - pub fn new( - filename: PathBuf, - config: ConfigManager, - root_store: Box, - ) -> Result { + pub fn new(filename: PathBuf, config: ConfigManager) -> Result { let fb = FileBacked::new( filename, config.manager.node_cache_size, @@ -123,6 +122,13 @@ impl RevisionManager { // from opening the same database simultaneously fb.lock()?; + let root_store: Box = match config.root_store_dir { + Some(path) => { + Box::new(FjallStore::new(path).map_err(RevisionManagerError::RootStoreError)?) + } + None => Box::new(NoOpStore {}), + }; + let storage = Arc::new(fb); let nodestore = Arc::new(NodeStore::open(storage.clone())?); let manager = Self { @@ -386,7 +392,6 @@ impl RevisionManager { #[allow(clippy::unwrap_used)] mod tests { use super::*; - use crate::root_store::NoOpStore; use tempfile::NamedTempFile; #[test] @@ -401,16 +406,14 @@ mod tests { .build(); // First database instance should open successfully - let first_manager = - RevisionManager::new(db_path.clone(), config.clone(), Box::new(NoOpStore {})); + let first_manager = RevisionManager::new(db_path.clone(), config.clone()); assert!( first_manager.is_ok(), "First database should open successfully" ); // Second database instance should fail to open due to file locking - let second_manager = - RevisionManager::new(db_path.clone(), config.clone(), Box::new(NoOpStore {})); + let second_manager = RevisionManager::new(db_path.clone(), config.clone()); assert!( second_manager.is_err(), "Second database should fail to open" @@ -430,7 +433,7 @@ mod tests { drop(first_manager.unwrap()); // Now the second database should open successfully - let third_manager = RevisionManager::new(db_path, config, Box::new(NoOpStore {})); + let third_manager = RevisionManager::new(db_path, config); assert!( third_manager.is_ok(), "Database should open after first instance is dropped" From bb9c4c764cba7158de82a6bf2bea72bc80832ce4 Mon Sep 17 00:00:00 2001 From: rodrigo <77309055+RodrigoVillar@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:13:16 -0500 Subject: [PATCH 1049/1053] refactor(rootstore): remove box pointer (#1484) While taking a stab at #1483, I noticed after removing `NoOpStore` in favor of `Option`, there's actually no need for the `Box` pointer since, if we have `Some`, it'll always be an instance of `FjallStore`. This PR does the following: - Removes the usage of `Box` pointers for `RootStore` - Removes the `RootStore` trait and renames `FjallStore => RootStore` - Updates code to use new `RootStore` naming and logic --- firewood/src/db.rs | 18 +++---- firewood/src/manager.rs | 57 +++++++++++---------- firewood/src/root_store.rs | 100 ++++++++++--------------------------- 3 files changed, 68 insertions(+), 107 deletions(-) diff --git a/firewood/src/db.rs b/firewood/src/db.rs index a710898ca1ad..c96d5d6b1b38 100644 --- a/firewood/src/db.rs +++ b/firewood/src/db.rs @@ -1056,8 +1056,8 @@ mod test { /// Verifies that persisted revisions are still accessible when reopening the database. #[test] - fn test_fjall_store() { - let db = TestDb::new_with_fjall_store(DbConfig::builder().build()); + fn test_root_store() { + let db = TestDb::new_with_root_store(DbConfig::builder().build()); // First, create a revision to retrieve let key = b"key"; @@ -1088,20 +1088,20 @@ mod test { #[test] fn test_rootstore_empty_db_reopen() { - let db = TestDb::new_with_fjall_store(DbConfig::builder().build()); + let db = TestDb::new_with_root_store(DbConfig::builder().build()); db.reopen(); } /// Verifies that revisions exceeding the in-memory limit can still be retrieved. #[test] - fn test_fjall_store_with_capped_max_revisions() { + fn test_root_store_with_capped_max_revisions() { const NUM_REVISIONS: usize = 10; let dbconfig = DbConfig::builder() .manager(RevisionManagerConfig::builder().max_revisions(5).build()) .build(); - let db = TestDb::new_with_fjall_store(dbconfig); + let db = TestDb::new_with_root_store(dbconfig); // Create and commit 10 proposals let key = b"root_store"; @@ -1171,15 +1171,15 @@ mod test { } } - /// Creates a new test database with `FjallStore` enabled. + /// Creates a new test database with `RootStore` enabled. /// - /// Overrides `root_store_dir` in dbconfig to provide a directory for `FjallStore`. - pub fn new_with_fjall_store(dbconfig: DbConfig) -> Self { + /// Overrides `root_store_dir` in dbconfig to provide a directory for `RootStore`. + pub fn new_with_root_store(dbconfig: DbConfig) -> Self { let tmpdir = tempfile::tempdir().unwrap(); let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")] .iter() .collect(); - let root_store_dir = tmpdir.as_ref().join("fjall_store"); + let root_store_dir = tmpdir.as_ref().join("root_store"); let dbconfig = DbConfig { root_store_dir: Some(root_store_dir), diff --git a/firewood/src/manager.rs b/firewood/src/manager.rs index a09d6e79c1dc..8b83e85c4c87 100644 --- a/firewood/src/manager.rs +++ b/firewood/src/manager.rs @@ -23,7 +23,7 @@ use typed_builder::TypedBuilder; use weak_table::WeakValueHashMap; use crate::merkle::Merkle; -use crate::root_store::{FjallStore, NoOpStore, RootStore}; +use crate::root_store::RootStore; use crate::v2::api::{ArcDynDbView, HashKey, OptionalHashKeyExt}; pub use firewood_storage::CacheReadStrategy; @@ -85,7 +85,7 @@ pub(crate) struct RevisionManager { by_hash: RwLock>, by_rootstore: Mutex>>>, threadpool: OnceLock, - root_store: Box, + root_store: Option, } #[derive(Debug, thiserror::Error)] @@ -122,11 +122,9 @@ impl RevisionManager { // from opening the same database simultaneously fb.lock()?; - let root_store: Box = match config.root_store_dir { - Some(path) => { - Box::new(FjallStore::new(path).map_err(RevisionManagerError::RootStoreError)?) - } - None => Box::new(NoOpStore {}), + let root_store: Option = match config.root_store_dir { + Some(path) => Some(RootStore::new(path).map_err(RevisionManagerError::RootStoreError)?), + None => None, }; let storage = Arc::new(fb); @@ -158,10 +156,11 @@ impl RevisionManager { }, )?; - manager - .root_store - .add_root(&root_hash, &root_address) - .map_err(RevisionManagerError::RootStoreError)?; + if let Some(store) = &manager.root_store { + store + .add_root(&root_hash, &root_address) + .map_err(RevisionManagerError::RootStoreError)?; + } } Ok(manager) @@ -174,8 +173,8 @@ impl RevisionManager { /// It only contains the address of the nodes that are deleted, which should be very small. /// 2. Revision reaping. /// If more than the maximum number of revisions are kept in memory, the - /// oldest revision is removed from memory. If `RootStore` allows space - /// reuse, the oldest revision's nodes are added to the free list for space reuse. + /// oldest revision is removed from memory. If `RootStore` does not exist, + /// the oldest revision's nodes are added to the free list for space reuse. /// Otherwise, the oldest revision's nodes are preserved on disk, which /// is useful for historical queries. /// 3. Persist to disk. This includes flushing everything to disk. @@ -200,7 +199,7 @@ impl RevisionManager { // 2. Revision reaping // When we exceed max_revisions, remove the oldest revision from memory. - // If `RootStore` allows space reuse, add the oldest revision's nodes to the free list. + // If `RootStore` does not exist, add the oldest revision's nodes to the free list. // If you crash after freeing some of these, then the free list will point to nodes that are not actually free. // TODO: Handle the case where we get something off the free list that is not free while self.historical.read().len() >= self.max_revisions { @@ -214,8 +213,8 @@ impl RevisionManager { self.by_hash.write().remove(hash); } - // We reap the revision's nodes only if `RootStore` allows space reuse. - if self.root_store.allow_space_reuse() { + // We reap the revision's nodes only if `RootStore` does not exist. + if self.root_store.is_none() { // This `try_unwrap` is safe because nobody else will call `try_unwrap` on this Arc // in a different thread, so we don't have to worry about the race condition where // the Arc we get back is not usable as indicated in the docs for `try_unwrap`. @@ -240,8 +239,10 @@ impl RevisionManager { committed.persist()?; // 4. Persist revision to root store - if let (Some(hash), Some(address)) = (committed.root_hash(), committed.root_address()) { - self.root_store + if let Some(store) = &self.root_store + && let (Some(hash), Some(address)) = (committed.root_hash(), committed.root_address()) + { + store .add_root(&hash, &address) .map_err(RevisionManagerError::RootStoreError)?; } @@ -338,13 +339,19 @@ impl RevisionManager { // 3. Check the persistent `RootStore`. // If the revision exists, get its root address and construct a NodeStore for it. - let root_address = self - .root_store - .get(&root_hash) - .map_err(RevisionManagerError::RootStoreError)? - .ok_or(RevisionManagerError::RevisionNotFound { - provided: root_hash.clone(), - })?; + let root_address = match &self.root_store { + Some(store) => store + .get(&root_hash) + .map_err(RevisionManagerError::RootStoreError)? + .ok_or(RevisionManagerError::RevisionNotFound { + provided: root_hash.clone(), + })?, + None => { + return Err(RevisionManagerError::RevisionNotFound { + provided: root_hash.clone(), + }); + } + }; let nodestore = Arc::new(NodeStore::with_root( root_hash.clone().into_hash_type(), diff --git a/firewood/src/root_store.rs b/firewood/src/root_store.rs index c23c77173a28..43641cdab6c0 100644 --- a/firewood/src/root_store.rs +++ b/firewood/src/root_store.rs @@ -2,100 +2,50 @@ // See the file LICENSE.md for licensing terms. use fjall::{Config, Keyspace, PartitionCreateOptions, PartitionHandle, PersistMode}; -use std::{fmt::Debug, path::Path}; +use std::path::Path; use derive_where::derive_where; use firewood_storage::{LinearAddress, TrieHash}; const FJALL_PARTITION_NAME: &str = "firewood"; -pub trait RootStore: Debug { - /// `add_root` persists a revision's address to `RootStore`. - /// - /// Args: - /// - hash: the hash of the revision - /// - address: the address of the revision - /// - /// # Errors - /// - /// Will return an error if unable to persist the revision address to the - /// underlying datastore - fn add_root( - &self, - hash: &TrieHash, - address: &LinearAddress, - ) -> Result<(), Box>; - - /// `get` returns the address of a revision. - /// - /// Args: - /// - hash: the hash of the revision - /// - /// # Errors - /// - /// Will return an error if unable to query the underlying datastore. - fn get( - &self, - hash: &TrieHash, - ) -> Result, Box>; - - /// Returns whether revision nodes should be added to the free list. - fn allow_space_reuse(&self) -> bool; -} - -#[derive(Debug)] -pub struct NoOpStore {} - -impl RootStore for NoOpStore { - fn add_root( - &self, - _hash: &TrieHash, - _address: &LinearAddress, - ) -> Result<(), Box> { - Ok(()) - } - - fn get( - &self, - _hash: &TrieHash, - ) -> Result, Box> { - Ok(None) - } - - fn allow_space_reuse(&self) -> bool { - true - } -} - #[derive_where(Debug)] #[derive_where(skip_inner)] -pub struct FjallStore { +pub struct RootStore { keyspace: Keyspace, items: PartitionHandle, } -impl FjallStore { - /// Creates or opens an instance of `FjallStore`. +impl RootStore { + /// Creates or opens an instance of `RootStore`. /// /// Args: - /// - path: the directory where `FjallStore` will write to. + /// - path: the directory where `RootStore` will write to. /// /// # Errors /// - /// Will return an error if unable to create or open an instance of `FjallStore`. + /// Will return an error if unable to create or open an instance of `RootStore`. pub fn new>( path: P, - ) -> Result> { + ) -> Result> { let keyspace = Config::new(path).open()?; let items = keyspace.open_partition(FJALL_PARTITION_NAME, PartitionCreateOptions::default())?; Ok(Self { keyspace, items }) } -} -impl RootStore for FjallStore { - fn add_root( + /// `add_root` persists a revision's address to `RootStore`. + /// + /// Args: + /// - hash: the hash of the revision + /// - address: the address of the revision + /// + /// # Errors + /// + /// Will return an error if unable to persist the revision address to the + /// underlying datastore + pub fn add_root( &self, hash: &TrieHash, address: &LinearAddress, @@ -107,7 +57,15 @@ impl RootStore for FjallStore { Ok(()) } - fn get( + /// `get` returns the address of a revision. + /// + /// Args: + /// - hash: the hash of the revision + /// + /// # Errors + /// + /// Will return an error if unable to query the underlying datastore. + pub fn get( &self, hash: &TrieHash, ) -> Result, Box> { @@ -119,8 +77,4 @@ impl RootStore for FjallStore { Ok(LinearAddress::new(u64::from_be_bytes(array))) } - - fn allow_space_reuse(&self) -> bool { - false - } } From e192ec3f027e656b6c2ba02a4e9f2199ccfd567c Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Wed, 26 Nov 2025 09:42:45 -0800 Subject: [PATCH 1050/1053] fix: mark node as allocated before serializing parent (#1489) #1488 found a panic during serialization of a node, indicating that a child node was either not hashed or not allocated and therefore could not serialize the parent node. After a walk through of the code, I noticed that after #1389, we began serializing nodes in batches in order to prevent corrupting the free-list after a failed write and introduced a case where this could occur. Previously, we would serialize a node, write it to storage, and then mark it as allocated in one step. Now we serialize and allocate the node and add the node to a batch before moving onto the next node. Like before, we only mark the node as allocated after writing it to disk. This means that if the batch contained child and parent, the parent node fails to serialize because it cannot reference the address. This change refactors the allocation logic to mark nodes as allocated during batching. This ensures that if its parent is also in the batch, it can successfully serialize. This linear address itself is not read from by viewers of the `Allocated` state because the node is not considered committed until the entire write has completed (i.e., after the root and header are written). This means this change does not introduce any early reads of uncommitted data. In the course of writing tests, I discovered that the arena allocator returns the number of bytes by the arena, not the number of bytes occupied by items within the arena. This meant that the batching logic should have always generated a batch of 1 element. To fix this, I track the allocated length of each area. This successfully triggered the bug I discovered; however, this also means the bug might not be the source of the original panic. --- storage/src/nodestore/mod.rs | 96 +++++++++++++++++++++++ storage/src/nodestore/persist.rs | 11 ++- storage/src/nodestore/persist_io_uring.rs | 3 +- 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/storage/src/nodestore/mod.rs b/storage/src/nodestore/mod.rs index 3575d6c9e7cb..c2972ca05422 100644 --- a/storage/src/nodestore/mod.rs +++ b/storage/src/nodestore/mod.rs @@ -865,10 +865,16 @@ where #[expect(clippy::cast_possible_truncation)] mod tests { + use crate::BranchNode; + use crate::Children; + use crate::FileBacked; use crate::LeafNode; + use crate::NibblesIterator; + use crate::PathComponent; use crate::linear::memory::MemStore; use super::*; + use nonzero_ext::nonzero; use primitives::area_size_iter; use std::error::Error; @@ -980,4 +986,94 @@ mod tests { .unwrap(); assert_eq!(io_err_source.0, 16_777_225); } + + /// Test that persisting a branch node with children succeeds. + /// + /// This test verifies the fix for issue #1488 where the panic + /// "child must be hashed when serializing" occurred during persist. + /// + /// The bug was caused by batching nodes before writing but only calling + /// `allocate_at` after writing. This meant when serializing a parent node, + /// its children didn't have addresses yet (children are serialized first + /// in depth-first order, but without `allocate_at()` being called before + /// batching, the parent couldn't get the child's address during serialization). + /// + /// The fix calls `allocate_at` immediately after allocating storage for a node + /// but before adding it to the batch, ensuring children have addresses when + /// their parents are serialized. + #[test] + fn persist_branch_with_children() -> Result<(), Box> { + let tmpdir = tempfile::tempdir()?; + let dbfile = tmpdir.path().join("nodestore_branch_persist_test.db"); + + let nodestore = NodeStore::open(Arc::new(FileBacked::new( + dbfile, + nonzero!(10usize), + nonzero!(10usize), + false, + true, + CacheReadStrategy::WritesOnly, + )?))?; + + let mut proposal = NodeStore::new(&nodestore)?; + + { + let root = proposal.root_mut(); + assert!(root.is_none()); + + let mut children = Children::new(); + children[PathComponent::ALL[0x0]] = Some(Child::Node(Node::Leaf(LeafNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"123")), + value: b"\x00123".to_vec().into_boxed_slice(), + }))); + children[PathComponent::ALL[0xF]] = Some(Child::Node(Node::Leaf(LeafNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"abc")), + value: b"\x0Fabc".to_vec().into_boxed_slice(), + }))); + let branch1 = Node::Branch(Box::new(BranchNode { + partial_path: Path::new(), + value: None, + children, + })); + + let mut children = Children::new(); + children[PathComponent::ALL[0x0]] = Some(Child::Node(Node::Leaf(LeafNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"123")), + value: b"\xF0123".to_vec().into_boxed_slice(), + }))); + children[PathComponent::ALL[0xF]] = Some(Child::Node(Node::Leaf(LeafNode { + partial_path: Path::from_nibbles_iterator(NibblesIterator::new(b"abc")), + value: b"\xFFabc".to_vec().into_boxed_slice(), + }))); + let branch2 = Node::Branch(Box::new(BranchNode { + partial_path: Path::new(), + value: None, + children, + })); + + let mut children = Children::new(); + children[PathComponent::ALL[0x0]] = Some(Child::Node(branch1)); + children[PathComponent::ALL[0xF]] = Some(Child::Node(branch2)); + + *root = Some(Node::Branch(Box::new(BranchNode { + partial_path: Path::new(), + value: None, + children, + }))); + } + + let proposal = NodeStore::, _>::try_from(proposal)?; + + let mut nodestore = proposal.as_committed(&nodestore); + nodestore.persist()?; + + let mut proposal = NodeStore::new(&nodestore)?; + + { + let root = proposal.root_mut(); + assert!(root.is_some()); + } + + Ok(()) + } } diff --git a/storage/src/nodestore/persist.rs b/storage/src/nodestore/persist.rs index a4d70ceddd3d..112bfc7b7d4a 100644 --- a/storage/src/nodestore/persist.rs +++ b/storage/src/nodestore/persist.rs @@ -237,6 +237,7 @@ where F: FnMut(Vec<(&[u8], crate::LinearAddress, MaybePersistedNode)>) -> Result<(), FileIoError>, { let mut allocated_objects = Vec::new(); + let mut allocated_len = 0_usize; // Process each unpersisted node directly from the iterator for node in UnPersistedNodeIterator::new(node_store) { @@ -248,18 +249,23 @@ where let (slice, persisted_address, idx_size) = serialize_node_to_bump(bump, &shared_node, node_allocator)?; + // NOTE(#1488): we need to set the address so that the parent node can + // reference it when they are serialized within the same batch. + node.allocate_at(persisted_address); + allocated_len = allocated_len.saturating_add(idx_size); allocated_objects.push((slice, persisted_address, node)); // we pause if we can't allocate another node of the same size as the last one // This isn't a guarantee that we won't exceed bump_size_limit // but it's a good enough approximation - let might_overflow = bump.allocated_bytes() > bump_size_limit.saturating_sub(idx_size); + let might_overflow = allocated_len > bump_size_limit.saturating_sub(idx_size); if might_overflow { // must persist freelist before writing anything node_allocator.flush_freelist()?; write_fn(allocated_objects)?; allocated_objects = Vec::new(); bump.reset(); + allocated_len = 0; } } if !allocated_objects.is_empty() { @@ -343,8 +349,7 @@ impl NodeStore { for (serialized, persisted_address, node) in allocated_objects { self.storage.write(persisted_address.get(), serialized)?; - // Allocate the node to store the address, then collect for caching and persistence - node.allocate_at(persisted_address); + // I/O completed successfully - enqueue node for caching at the end cached_nodes.push(node); } diff --git a/storage/src/nodestore/persist_io_uring.rs b/storage/src/nodestore/persist_io_uring.rs index c3e82c28fecb..b44361196536 100644 --- a/storage/src/nodestore/persist_io_uring.rs +++ b/storage/src/nodestore/persist_io_uring.rs @@ -84,8 +84,7 @@ fn handle_completion_queue( Some("write failure".to_string()), )); } - // I/O completed successfully - mark node as persisted and cache it - pbe_entry.node.allocate_at(pbe_entry.address); + // I/O completed successfully - enqueue node for caching at the end cached_nodes.push(pbe_entry.node); } Ok(()) From dd9cb9feec1c8ea4261a974ebcfcb8dd64885933 Mon Sep 17 00:00:00 2001 From: Joachim Brandon LeBlanc Date: Mon, 1 Dec 2025 10:05:52 -0800 Subject: [PATCH 1051/1053] chore: update golangci yaml files (#1492) The upstream `.golangci.yaml` was updated to relocate the linters block. This commit updates our `.golangci.yaml` to do the same and updates the patch file to reflect this change. Upstream: --- .github/.golangci.yaml.patch | 28 ++++++++++++++-------------- ffi/.golangci.yaml | 35 ++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.github/.golangci.yaml.patch b/.github/.golangci.yaml.patch index 47f67e25de00..f90b1b764702 100644 --- a/.github/.golangci.yaml.patch +++ b/.github/.golangci.yaml.patch @@ -1,6 +1,15 @@ ---- .github/.golangci.yaml 2025-11-07 11:52:50 -+++ ffi/.golangci.yaml 2025-11-07 11:52:50 -@@ -75,8 +75,6 @@ +--- .github/.golangci.yaml 2025-11-26 17:52:36.814736814 +0000 ++++ ffi/.golangci.yaml 2025-11-26 17:52:31.624079933 +0000 +@@ -40,7 +40,7 @@ + - standard + - default + - blank +- - prefix(github.com/ava-labs/avalanchego) ++ - prefix(github.com/ava-labs/firewood/ffi) + - alias + - dot + custom-order: true +@@ -93,8 +93,6 @@ rules: packages: deny: @@ -9,7 +18,7 @@ - pkg: github.com/golang/mock/gomock desc: go.uber.org/mock/gomock should be used instead. - pkg: github.com/stretchr/testify/assert -@@ -91,29 +89,10 @@ +@@ -109,29 +107,10 @@ forbidigo: # Forbid the following identifiers (list of regexp). forbid: @@ -39,7 +48,7 @@ revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr -@@ -177,17 +156,6 @@ +@@ -195,17 +174,6 @@ # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - name: useless-break disabled: false @@ -57,12 +66,3 @@ tagalign: align: true sort: true -@@ -251,7 +219,7 @@ - - standard - - default - - blank -- - prefix(github.com/ava-labs/avalanchego) -+ - prefix(github.com/ava-labs/firewood/ffi) - - alias - - dot - custom-order: true diff --git a/ffi/.golangci.yaml b/ffi/.golangci.yaml index 6dcbc24bb2ca..a833506d90df 100644 --- a/ffi/.golangci.yaml +++ b/ffi/.golangci.yaml @@ -29,6 +29,24 @@ issues: # Maximum count of issues with the same text. max-same-issues: 0 +formatters: + enable: + - gci + - gofmt + - gofumpt + settings: + gci: + sections: + - standard + - default + - blank + - prefix(github.com/ava-labs/firewood/ffi) + - alias + - dot + custom-order: true + exclusions: + generated: lax + linters: default: none enable: @@ -208,20 +226,3 @@ linters: linters: - gosec - prealloc -formatters: - enable: - - gci - - gofmt - - gofumpt - settings: - gci: - sections: - - standard - - default - - blank - - prefix(github.com/ava-labs/firewood/ffi) - - alias - - dot - custom-order: true - exclusions: - generated: lax From 136dba8679caa7d4bf9823a8115183723ecc6ed7 Mon Sep 17 00:00:00 2001 From: maru Date: Wed, 3 Dec 2025 14:06:32 +0000 Subject: [PATCH 1052/1053] [bazel] WIP Initial bazelification --- .bazelignore | 7 + .bazelrc | 30 + BUILD.bazel | 45 + MODULE.bazel | 179 ++ MODULE.bazel.lock | 2181 +++++++++++++++++ Taskfile.yml | 44 + api/BUILD.bazel | 19 + api/admin/BUILD.bazel | 55 + api/connectclient/BUILD.bazel | 13 + api/health/BUILD.bazel | 44 + api/info/BUILD.bazel | 52 + api/metrics/BUILD.bazel | 38 + api/server/BUILD.bazel | 44 + app/BUILD.bazel | 17 + cache/BUILD.bazel | 12 + cache/cachetest/BUILD.bazel | 13 + cache/lru/BUILD.bazel | 32 + cache/metercacher/BUILD.bazel | 29 + chains/BUILD.bazel | 84 + chains/atomic/BUILD.bazel | 44 + chains/atomic/atomicmock/BUILD.bazel | 14 + chains/atomic/atomictest/BUILD.bazel | 15 + chains/atomic/gsharedmemory/BUILD.bazel | 36 + codec/BUILD.bazel | 23 + codec/codecmock/BUILD.bazel | 12 + codec/codectest/BUILD.bazel | 13 + codec/hierarchycodec/BUILD.bazel | 24 + codec/linearcodec/BUILD.bazel | 24 + codec/reflectcodec/BUILD.bazel | 16 + combined.patch | 0 config/BUILD.bazel | 71 + config/node/BUILD.bazel | 35 + connectproto/pb/proposervm/BUILD.bazel | 12 + .../proposervm/proposervmconnect/BUILD.bazel | 12 + connectproto/pb/xsvm/BUILD.bazel | 12 + connectproto/pb/xsvm/xsvmconnect/BUILD.bazel | 12 + connectproto/proposervm/BUILD.bazel | 24 + connectproto/xsvm/BUILD.bazel | 24 + database/BUILD.bazel | 33 + database/corruptabledb/BUILD.bazel | 28 + database/databasemock/BUILD.bazel | 15 + database/dbtest/BUILD.bazel | 21 + database/factory/BUILD.bazel | 18 + database/heightindexdb/dbtest/BUILD.bazel | 12 + database/heightindexdb/memdb/BUILD.bazel | 19 + database/heightindexdb/meterdb/BUILD.bazel | 25 + database/leveldb/BUILD.bazel | 37 + database/linkeddb/BUILD.bazel | 33 + database/memdb/BUILD.bazel | 16 + database/meterdb/BUILD.bazel | 25 + database/pebbledb/BUILD.bazel | 36 + database/prefixdb/BUILD.bazel | 24 + database/rpcdb/BUILD.bazel | 35 + database/versiondb/BUILD.bazel | 24 + flake.nix | 4 + genesis/BUILD.bazel | 77 + genesis/generate/checkpoints/BUILD.bazel | 21 + genesis/generate/validators/BUILD.bazel | 22 + graft/BUILD.bazel | 2 + graft/coreth/BUILD.bazel | 7 + graft/coreth/accounts/abi/BUILD.bazel | 52 + graft/coreth/accounts/abi/bind/BUILD.bazel | 59 + .../accounts/abi/bind/backends/BUILD.bazel | 16 + graft/coreth/cmd/simulator/config/BUILD.bazel | 12 + graft/coreth/cmd/simulator/key/BUILD.bazel | 12 + graft/coreth/cmd/simulator/load/BUILD.bazel | 26 + graft/coreth/cmd/simulator/main/BUILD.bazel | 21 + .../coreth/cmd/simulator/metrics/BUILD.bazel | 14 + graft/coreth/cmd/simulator/txs/BUILD.bazel | 19 + graft/coreth/consensus/BUILD.bazel | 17 + graft/coreth/consensus/dummy/BUILD.bazel | 22 + graft/coreth/constants/BUILD.bazel | 9 + graft/coreth/core/BUILD.bazel | 161 ++ graft/coreth/core/coretest/BUILD.bazel | 13 + graft/coreth/core/extstate/BUILD.bazel | 54 + graft/coreth/core/state/pruner/BUILD.bazel | 24 + graft/coreth/core/state/snapshot/BUILD.bazel | 73 + graft/coreth/core/txpool/BUILD.bazel | 27 + graft/coreth/core/txpool/blobpool/BUILD.bazel | 67 + .../coreth/core/txpool/legacypool/BUILD.bazel | 55 + graft/coreth/core/vm/runtime/BUILD.bazel | 51 + graft/coreth/eth/BUILD.bazel | 77 + graft/coreth/eth/ethconfig/BUILD.bazel | 21 + graft/coreth/eth/filters/BUILD.bazel | 59 + graft/coreth/eth/gasestimator/BUILD.bazel | 18 + graft/coreth/eth/gasprice/BUILD.bazel | 53 + graft/coreth/eth/tracers/BUILD.bazel | 61 + graft/coreth/ethclient/BUILD.bazel | 23 + .../coreth/ethclient/corethclient/BUILD.bazel | 16 + graft/coreth/ethclient/simulated/BUILD.bazel | 51 + graft/coreth/interfaces/BUILD.bazel | 13 + graft/coreth/internal/blocktest/BUILD.bazel | 12 + graft/coreth/internal/debug/BUILD.bazel | 23 + graft/coreth/internal/ethapi/BUILD.bazel | 93 + graft/coreth/internal/flags/BUILD.bazel | 17 + .../coreth/internal/shutdowncheck/BUILD.bazel | 14 + graft/coreth/internal/version/BUILD.bazel | 12 + graft/coreth/log/BUILD.bazel | 16 + graft/coreth/miner/BUILD.bazel | 49 + graft/coreth/nativeasset/BUILD.bazel | 35 + graft/coreth/network/BUILD.bazel | 55 + graft/coreth/network/stats/BUILD.bazel | 9 + graft/coreth/node/BUILD.bazel | 24 + graft/coreth/params/BUILD.bazel | 53 + graft/coreth/params/extras/BUILD.bazel | 33 + .../params/extras/extrastest/BUILD.bazel | 16 + graft/coreth/params/paramstest/BUILD.bazel | 12 + graft/coreth/plugin/BUILD.bazel | 27 + graft/coreth/plugin/evm/BUILD.bazel | 196 ++ graft/coreth/plugin/evm/atomic/BUILD.bazel | 63 + .../plugin/evm/atomic/atomictest/BUILD.bazel | 28 + .../plugin/evm/atomic/state/BUILD.bazel | 69 + .../coreth/plugin/evm/atomic/sync/BUILD.bazel | 63 + .../plugin/evm/atomic/txpool/BUILD.bazel | 44 + graft/coreth/plugin/evm/atomic/vm/BUILD.bazel | 135 + graft/coreth/plugin/evm/client/BUILD.bazel | 23 + graft/coreth/plugin/evm/config/BUILD.bazel | 32 + .../plugin/evm/customheader/BUILD.bazel | 69 + .../coreth/plugin/evm/customrawdb/BUILD.bazel | 37 + .../coreth/plugin/evm/customtypes/BUILD.bazel | 59 + graft/coreth/plugin/evm/extension/BUILD.bazel | 30 + graft/coreth/plugin/evm/gossip/BUILD.bazel | 16 + graft/coreth/plugin/evm/log/BUILD.bazel | 20 + graft/coreth/plugin/evm/message/BUILD.bazel | 46 + .../plugin/evm/tempextrastest/BUILD.bazel | 16 + .../coreth/plugin/evm/upgrade/ap0/BUILD.bazel | 12 + .../coreth/plugin/evm/upgrade/ap1/BUILD.bazel | 9 + .../coreth/plugin/evm/upgrade/ap3/BUILD.bazel | 23 + .../coreth/plugin/evm/upgrade/ap4/BUILD.bazel | 19 + .../coreth/plugin/evm/upgrade/ap5/BUILD.bazel | 8 + .../plugin/evm/upgrade/cortina/BUILD.bazel | 8 + .../plugin/evm/upgrade/etna/BUILD.bazel | 9 + graft/coreth/plugin/evm/vmerrors/BUILD.bazel | 8 + graft/coreth/plugin/evm/vmsync/BUILD.bazel | 49 + graft/coreth/plugin/evm/vmtest/BUILD.bazel | 56 + graft/coreth/plugin/factory/BUILD.bazel | 16 + graft/coreth/precompile/contract/BUILD.bazel | 37 + .../precompile/contracts/warp/BUILD.bazel | 67 + graft/coreth/precompile/modules/BUILD.bazel | 28 + .../precompile/precompileconfig/BUILD.bazel | 27 + .../precompile/precompiletest/BUILD.bazel | 26 + graft/coreth/precompile/registry/BUILD.bazel | 9 + graft/coreth/rpc/BUILD.bazel | 60 + graft/coreth/sync/BUILD.bazel | 15 + graft/coreth/sync/blocksync/BUILD.bazel | 39 + graft/coreth/sync/client/BUILD.bazel | 58 + graft/coreth/sync/client/stats/BUILD.bazel | 12 + graft/coreth/sync/handlers/BUILD.bazel | 66 + graft/coreth/sync/handlers/stats/BUILD.bazel | 9 + .../sync/handlers/stats/statstest/BUILD.bazel | 9 + graft/coreth/sync/statesync/BUILD.bazel | 76 + .../sync/statesync/statesynctest/BUILD.bazel | 24 + graft/coreth/sync/syncutils/BUILD.bazel | 13 + graft/coreth/tests/BUILD.bazel | 32 + graft/coreth/tests/utils/BUILD.bazel | 30 + graft/coreth/tests/warp/BUILD.bazel | 34 + graft/coreth/triedb/firewood/BUILD.bazel | 29 + graft/coreth/triedb/hashdb/BUILD.bazel | 25 + graft/coreth/triedb/pathdb/BUILD.bazel | 62 + graft/coreth/utils/BUILD.bazel | 34 + graft/coreth/utils/rand/BUILD.bazel | 8 + graft/coreth/utils/rpc/BUILD.bazel | 12 + graft/coreth/utils/utilstest/BUILD.bazel | 24 + graft/coreth/warp/BUILD.bazel | 59 + graft/coreth/warp/warptest/BUILD.bazel | 15 + ids/BUILD.bazel | 41 + ids/galiasreader/BUILD.bazel | 28 + ids/idstest/BUILD.bazel | 12 + indexer/BUILD.bazel | 67 + indexer/examples/p-chain/BUILD.bazel | 21 + indexer/examples/x-chain-blocks/BUILD.bazel | 21 + main/BUILD.bazel | 25 + message/BUILD.bazel | 50 + message/messagemock/BUILD.bazel | 18 + nat/BUILD.bazel | 24 + network/BUILD.bazel | 102 + network/dialer/BUILD.bazel | 24 + network/p2p/BUILD.bazel | 63 + network/p2p/acp118/BUILD.bazel | 51 + network/p2p/gossip/BUILD.bazel | 56 + network/p2p/p2ptest/BUILD.bazel | 31 + network/peer/BUILD.bazel | 95 + network/throttling/BUILD.bazel | 68 + nix/go/BUILD.bazel | 5 + nix/go/bazel.nix | 7 + nix/go/default.nix | 2 + node/BUILD.bazel | 96 + patch1.txt | 0 patch2.txt | 0 patches/BUILD.bazel | 6 + patches/blst_build.patch | 111 + patches/firewood_ffi.patch | 77 + patches/libevm_secp256k1.patch | 32 + proto/aliasreader/BUILD.bazel | 24 + proto/appsender/BUILD.bazel | 25 + proto/http/BUILD.bazel | 24 + proto/http/responsewriter/BUILD.bazel | 25 + proto/io/reader/BUILD.bazel | 24 + proto/io/writer/BUILD.bazel | 24 + proto/net/conn/BUILD.bazel | 25 + proto/p2p/BUILD.bazel | 23 + proto/pb/aliasreader/BUILD.bazel | 18 + proto/pb/appsender/BUILD.bazel | 19 + proto/pb/http/BUILD.bazel | 18 + proto/pb/http/responsewriter/BUILD.bazel | 19 + proto/pb/io/reader/BUILD.bazel | 18 + proto/pb/io/writer/BUILD.bazel | 18 + proto/pb/net/conn/BUILD.bazel | 19 + proto/pb/p2p/BUILD.bazel | 12 + proto/pb/platformvm/BUILD.bazel | 12 + proto/pb/rpcdb/BUILD.bazel | 19 + proto/pb/sdk/BUILD.bazel | 12 + proto/pb/sharedmemory/BUILD.bazel | 18 + proto/pb/signer/BUILD.bazel | 18 + proto/pb/sync/BUILD.bazel | 12 + proto/pb/validatorstate/BUILD.bazel | 19 + proto/pb/vm/BUILD.bazel | 22 + proto/pb/vm/runtime/BUILD.bazel | 19 + proto/pb/warp/BUILD.bazel | 18 + proto/platformvm/BUILD.bazel | 23 + proto/rpcdb/BUILD.bazel | 25 + proto/sdk/BUILD.bazel | 23 + proto/sharedmemory/BUILD.bazel | 24 + proto/signer/BUILD.bazel | 24 + proto/sync/BUILD.bazel | 23 + proto/validatorstate/BUILD.bazel | 25 + proto/vm/BUILD.bazel | 31 + proto/vm/runtime/BUILD.bazel | 25 + proto/warp/BUILD.bazel | 24 + result | 1 + simplex/BUILD.bazel | 84 + snow/BUILD.bazel | 27 + snow/choices/BUILD.bazel | 23 + snow/consensus/avalanche/BUILD.bazel | 15 + snow/consensus/snowball/BUILD.bazel | 54 + snow/consensus/snowman/BUILD.bazel | 58 + .../snowman/bootstrapper/BUILD.bazel | 43 + snow/consensus/snowman/poll/BUILD.bazel | 42 + .../consensus/snowman/snowmanmock/BUILD.bazel | 12 + .../consensus/snowman/snowmantest/BUILD.bazel | 20 + snow/consensus/snowstorm/BUILD.bazel | 16 + snow/engine/avalanche/BUILD.bazel | 12 + snow/engine/avalanche/bootstrap/BUILD.bazel | 66 + .../avalanche/bootstrap/queue/BUILD.bazel | 50 + snow/engine/avalanche/getter/BUILD.bazel | 23 + snow/engine/avalanche/state/BUILD.bazel | 48 + snow/engine/avalanche/vertex/BUILD.bazel | 45 + .../avalanche/vertex/vertexmock/BUILD.bazel | 18 + .../avalanche/vertex/vertextest/BUILD.bazel | 22 + snow/engine/common/BUILD.bazel | 58 + snow/engine/common/appsender/BUILD.bazel | 18 + snow/engine/common/commonmock/BUILD.bazel | 14 + snow/engine/common/tracker/BUILD.bazel | 35 + snow/engine/enginetest/BUILD.bazel | 24 + snow/engine/snowman/BUILD.bazel | 75 + snow/engine/snowman/ancestor/BUILD.bazel | 23 + snow/engine/snowman/block/BUILD.bazel | 50 + .../snowman/block/blockmock/BUILD.bazel | 24 + .../snowman/block/blocktest/BUILD.bazel | 21 + snow/engine/snowman/bootstrap/BUILD.bazel | 69 + .../snowman/bootstrap/interval/BUILD.bazel | 32 + snow/engine/snowman/getter/BUILD.bazel | 39 + snow/engine/snowman/job/BUILD.bazel | 15 + snow/engine/snowman/syncer/BUILD.bazel | 54 + snow/networking/benchlist/BUILD.bazel | 40 + snow/networking/handler/BUILD.bazel | 78 + .../handler/handlermock/BUILD.bazel | 14 + snow/networking/router/BUILD.bazel | 76 + snow/networking/router/routermock/BUILD.bazel | 21 + snow/networking/sender/BUILD.bazel | 72 + snow/networking/sender/sendermock/BUILD.bazel | 16 + snow/networking/sender/sendertest/BUILD.bazel | 16 + snow/networking/timeout/BUILD.bazel | 37 + .../timeout/timeoutmock/BUILD.bazel | 14 + snow/networking/tracker/BUILD.bazel | 45 + .../tracker/trackermock/BUILD.bazel | 15 + snow/snowtest/BUILD.bazel | 28 + snow/uptime/BUILD.bazel | 39 + snow/uptime/uptimemock/BUILD.bazel | 12 + snow/validators/BUILD.bazel | 59 + snow/validators/gvalidators/BUILD.bazel | 36 + snow/validators/validatorsmock/BUILD.bazel | 13 + snow/validators/validatorstest/BUILD.bazel | 19 + staking/BUILD.bazel | 35 + subnets/BUILD.bazel | 33 + tests/BUILD.bazel | 26 + tests/antithesis/BUILD.bazel | 29 + tests/antithesis/avalanchego/BUILD.bazel | 44 + .../avalanchego/gencomposeconfig/BUILD.bazel | 20 + tests/antithesis/xsvm/BUILD.bazel | 34 + .../xsvm/gencomposeconfig/BUILD.bazel | 22 + tests/e2e/BUILD.bazel | 30 + tests/e2e/banff/BUILD.bazel | 21 + tests/e2e/c/BUILD.bazel | 42 + tests/e2e/faultinjection/BUILD.bazel | 19 + tests/e2e/p/BUILD.bazel | 61 + tests/e2e/vms/BUILD.bazel | 28 + tests/e2e/x/BUILD.bazel | 20 + tests/e2e/x/transfer/BUILD.bazel | 27 + tests/fixture/bootstrapmonitor/BUILD.bazel | 42 + .../fixture/bootstrapmonitor/cmd/BUILD.bazel | 20 + .../fixture/bootstrapmonitor/e2e/BUILD.bazel | 27 + tests/fixture/e2e/BUILD.bazel | 38 + tests/fixture/stacktrace/BUILD.bazel | 8 + tests/fixture/subnet/BUILD.bazel | 14 + tests/fixture/tmpnet/BUILD.bazel | 99 + tests/fixture/tmpnet/flags/BUILD.bazel | 22 + tests/fixture/tmpnet/tmpnetctl/BUILD.bazel | 24 + tests/load/BUILD.bazel | 28 + tests/load/contracts/BUILD.bazel | 21 + tests/load/main/BUILD.bazel | 28 + tests/reexecute/c/BUILD.bazel | 35 + tests/upgrade/BUILD.bazel | 13 + tools/bazel/workspace_status.sh | 10 + trace/BUILD.bazel | 31 + updated-plan.md | 594 +++++ upgrade/BUILD.bazel | 19 + upgrade/upgradetest/BUILD.bazel | 12 + utils/BUILD.bazel | 28 + utils/bag/BUILD.bazel | 30 + utils/beacon/BUILD.bazel | 22 + utils/bimap/BUILD.bazel | 19 + utils/bloom/BUILD.bazel | 31 + utils/buffer/BUILD.bazel | 24 + utils/cb58/BUILD.bazel | 19 + utils/compression/BUILD.bazel | 31 + utils/constants/BUILD.bazel | 29 + utils/crypto/bls/BUILD.bazel | 31 + utils/crypto/bls/blstest/BUILD.bazel | 8 + .../crypto/bls/signer/localsigner/BUILD.bazel | 30 + utils/crypto/bls/signer/rpcsigner/BUILD.bazel | 28 + utils/crypto/keychain/BUILD.bazel | 12 + utils/crypto/secp256k1/BUILD.bazel | 39 + utils/dynamicip/BUILD.bazel | 35 + utils/filesystem/BUILD.bazel | 22 + utils/filesystem/filesystemmock/BUILD.bazel | 9 + utils/formatting/BUILD.bazel | 27 + utils/formatting/address/BUILD.bazel | 15 + utils/hashing/BUILD.bazel | 18 + utils/hashing/consistent/BUILD.bazel | 26 + utils/hashing/hashingmock/BUILD.bazel | 9 + utils/heap/BUILD.bazel | 24 + utils/ips/BUILD.bazel | 25 + utils/iterator/BUILD.bazel | 38 + utils/json/BUILD.bazel | 27 + utils/linked/BUILD.bazel | 25 + utils/lock/BUILD.bazel | 15 + utils/logging/BUILD.bazel | 36 + utils/math/BUILD.bazel | 34 + utils/math/meter/BUILD.bazel | 22 + utils/maybe/BUILD.bazel | 15 + utils/metric/BUILD.bazel | 24 + utils/packages/BUILD.bazel | 12 + utils/password/BUILD.bazel | 25 + utils/perms/BUILD.bazel | 14 + utils/profiler/BUILD.bazel | 23 + utils/resource/BUILD.bazel | 30 + utils/resource/resourcemock/BUILD.bazel | 9 + utils/rpc/BUILD.bazel | 13 + utils/sampler/BUILD.bazel | 42 + utils/set/BUILD.bazel | 33 + utils/setmap/BUILD.bazel | 22 + utils/storage/BUILD.bazel | 12 + utils/timer/BUILD.bazel | 36 + utils/timer/mockable/BUILD.bazel | 15 + utils/tree/BUILD.bazel | 24 + utils/ulimit/BUILD.bazel | 43 + utils/units/BUILD.bazel | 11 + utils/window/BUILD.bazel | 23 + utils/wrappers/BUILD.bazel | 19 + version/BUILD.bazel | 27 + vms/BUILD.bazel | 20 + vms/avm/BUILD.bazel | 124 + vms/avm/block/BUILD.bazel | 44 + vms/avm/block/builder/BUILD.bazel | 57 + vms/avm/block/executor/BUILD.bazel | 57 + .../block/executor/executormock/BUILD.bazel | 17 + vms/avm/config/BUILD.bazel | 9 + vms/avm/fxs/BUILD.bazel | 17 + vms/avm/metrics/BUILD.bazel | 24 + vms/avm/metrics/metricsmock/BUILD.bazel | 14 + vms/avm/network/BUILD.bazel | 56 + vms/avm/state/BUILD.bazel | 48 + vms/avm/state/statemock/BUILD.bazel | 21 + vms/avm/txs/BUILD.bazel | 68 + vms/avm/txs/executor/BUILD.bazel | 66 + vms/avm/txs/mempool/BUILD.bazel | 13 + vms/avm/txs/txsmock/BUILD.bazel | 16 + vms/avm/txs/txstest/BUILD.bazel | 29 + vms/avm/utxo/BUILD.bazel | 19 + vms/components/avax/BUILD.bazel | 69 + vms/components/avax/avaxmock/BUILD.bazel | 16 + vms/components/chain/BUILD.bazel | 38 + vms/components/gas/BUILD.bazel | 31 + vms/components/verify/BUILD.bazel | 32 + vms/components/verify/verifymock/BUILD.bazel | 9 + vms/evm/acp176/BUILD.bazel | 25 + vms/evm/acp226/BUILD.bazel | 19 + vms/evm/database/BUILD.bazel | 24 + vms/evm/emulate/BUILD.bazel | 24 + vms/evm/metrics/metricstest/BUILD.bazel | 9 + vms/evm/metrics/prometheus/BUILD.bazel | 31 + vms/evm/predicate/BUILD.bazel | 35 + vms/evm/sync/customrawdb/BUILD.bazel | 40 + vms/evm/uptimetracker/BUILD.bazel | 36 + vms/example/xsvm/BUILD.bazel | 41 + vms/example/xsvm/api/BUILD.bazel | 31 + vms/example/xsvm/block/BUILD.bazel | 16 + vms/example/xsvm/builder/BUILD.bazel | 21 + vms/example/xsvm/chain/BUILD.bazel | 23 + vms/example/xsvm/cmd/account/BUILD.bazel | 19 + vms/example/xsvm/cmd/chain/BUILD.bazel | 13 + vms/example/xsvm/cmd/chain/create/BUILD.bazel | 23 + .../xsvm/cmd/chain/genesis/BUILD.bazel | 19 + vms/example/xsvm/cmd/issue/BUILD.bazel | 14 + vms/example/xsvm/cmd/issue/export/BUILD.bazel | 23 + .../xsvm/cmd/issue/importtx/BUILD.bazel | 26 + vms/example/xsvm/cmd/issue/status/BUILD.bazel | 12 + .../xsvm/cmd/issue/transfer/BUILD.bazel | 23 + vms/example/xsvm/cmd/run/BUILD.bazel | 13 + vms/example/xsvm/cmd/version/BUILD.bazel | 14 + vms/example/xsvm/cmd/versionjson/BUILD.bazel | 15 + vms/example/xsvm/cmd/xsvm/BUILD.bazel | 23 + vms/example/xsvm/execute/BUILD.bazel | 26 + vms/example/xsvm/genesis/BUILD.bazel | 26 + vms/example/xsvm/state/BUILD.bazel | 17 + vms/example/xsvm/tx/BUILD.bazel | 24 + vms/fx/BUILD.bazel | 8 + vms/metervm/BUILD.bazel | 32 + vms/nftfx/BUILD.bazel | 49 + vms/platformvm/BUILD.bazel | 167 ++ vms/platformvm/api/BUILD.bazel | 24 + vms/platformvm/block/BUILD.bazel | 56 + vms/platformvm/block/builder/BUILD.bazel | 91 + vms/platformvm/block/executor/BUILD.bazel | 124 + .../block/executor/executormock/BUILD.bazel | 18 + vms/platformvm/config/BUILD.bazel | 33 + vms/platformvm/fx/BUILD.bazel | 19 + vms/platformvm/fx/fxmock/BUILD.bazel | 16 + vms/platformvm/genesis/BUILD.bazel | 38 + .../genesis/genesistest/BUILD.bazel | 22 + vms/platformvm/metrics/BUILD.bazel | 23 + vms/platformvm/network/BUILD.bazel | 77 + vms/platformvm/reward/BUILD.bazel | 25 + vms/platformvm/signer/BUILD.bazel | 32 + vms/platformvm/signer/signermock/BUILD.bazel | 12 + vms/platformvm/stakeable/BUILD.bazel | 21 + vms/platformvm/state/BUILD.bazel | 139 ++ vms/platformvm/state/statetest/BUILD.bazel | 27 + vms/platformvm/status/BUILD.bazel | 22 + vms/platformvm/txs/BUILD.bazel | 122 + vms/platformvm/txs/executor/BUILD.bazel | 125 + vms/platformvm/txs/fee/BUILD.bazel | 56 + vms/platformvm/txs/mempool/BUILD.bazel | 45 + vms/platformvm/txs/txheap/BUILD.bazel | 28 + vms/platformvm/txs/txstest/BUILD.bazel | 29 + vms/platformvm/utxo/BUILD.bazel | 42 + vms/platformvm/utxo/utxomock/BUILD.bazel | 15 + vms/platformvm/validators/BUILD.bazel | 54 + vms/platformvm/validators/fee/BUILD.bazel | 23 + .../validators/validatorstest/BUILD.bazel | 13 + vms/platformvm/warp/BUILD.bazel | 53 + vms/platformvm/warp/gwarp/BUILD.bazel | 33 + vms/platformvm/warp/message/BUILD.bazel | 48 + vms/platformvm/warp/payload/BUILD.bazel | 34 + vms/platformvm/warp/signertest/BUILD.bazel | 15 + vms/propertyfx/BUILD.bazel | 47 + vms/proposervm/BUILD.bazel | 118 + vms/proposervm/acp181/BUILD.bazel | 24 + vms/proposervm/block/BUILD.bazel | 42 + vms/proposervm/proposer/BUILD.bazel | 37 + .../proposer/proposermock/BUILD.bazel | 12 + vms/proposervm/state/BUILD.bazel | 52 + vms/proposervm/summary/BUILD.bazel | 32 + vms/registry/BUILD.bazel | 43 + vms/registry/registrymock/BUILD.bazel | 16 + vms/rpcchainvm/BUILD.bazel | 104 + vms/rpcchainvm/ghttp/BUILD.bazel | 37 + vms/rpcchainvm/ghttp/gconn/BUILD.bazel | 30 + vms/rpcchainvm/ghttp/greader/BUILD.bazel | 24 + .../ghttp/gresponsewriter/BUILD.bazel | 23 + vms/rpcchainvm/ghttp/gwriter/BUILD.bazel | 12 + vms/rpcchainvm/grpcutils/BUILD.bazel | 41 + vms/rpcchainvm/gruntime/BUILD.bazel | 16 + vms/rpcchainvm/runtime/BUILD.bazel | 11 + vms/rpcchainvm/runtime/subprocess/BUILD.bazel | 31 + vms/secp256k1fx/BUILD.bazel | 68 + vms/tracedvm/BUILD.bazel | 30 + vms/txs/mempool/BUILD.bazel | 34 + vms/types/BUILD.bazel | 16 + vms/vmsmock/BUILD.bazel | 17 + wallet/chain/c/BUILD.bazel | 39 + wallet/chain/p/BUILD.bazel | 54 + wallet/chain/p/builder/BUILD.bazel | 46 + wallet/chain/p/signer/BUILD.bazel | 24 + wallet/chain/p/wallet/BUILD.bazel | 31 + wallet/chain/x/BUILD.bazel | 47 + wallet/chain/x/builder/BUILD.bazel | 31 + wallet/chain/x/signer/BUILD.bazel | 25 + wallet/subnet/primary/BUILD.bazel | 55 + wallet/subnet/primary/common/BUILD.bazel | 21 + .../primary/common/utxotest/BUILD.bazel | 15 + .../BUILD.bazel | 23 + .../add-primary-validator/BUILD.bazel | 24 + .../examples/c-chain-export/BUILD.bazel | 22 + .../examples/c-chain-import/BUILD.bazel | 23 + .../examples/convert-subnet-to-l1/BUILD.bazel | 24 + .../primary/examples/create-asset/BUILD.bazel | 22 + .../primary/examples/create-chain/BUILD.bazel | 22 + .../create-locked-stakeable/BUILD.bazel | 24 + .../examples/create-subnet/BUILD.bazel | 20 + .../examples/disable-l1-validator/BUILD.bazel | 20 + .../examples/get-p-chain-balance/BUILD.bazel | 23 + .../examples/get-x-chain-balance/BUILD.bazel | 22 + .../increase-l1-validator-balance/BUILD.bazel | 20 + .../register-l1-validator/BUILD.bazel | 28 + .../remove-subnet-validator/BUILD.bazel | 20 + .../set-l1-validator-weight/BUILD.bazel | 26 + .../BUILD.bazel | 31 + .../BUILD.bazel | 32 + .../BUILD.bazel | 31 + .../sign-l1-validator-weight/BUILD.bazel | 30 + .../sign-subnet-to-l1-conversion/BUILD.bazel | 31 + x/archivedb/BUILD.bazel | 34 + x/blockdb/BUILD.bazel | 47 + x/merkledb/BUILD.bazel | 102 + x/sync/BUILD.bazel | 46 + x/sync/protoutils/BUILD.bazel | 12 + 528 files changed, 19786 insertions(+) create mode 100644 .bazelignore create mode 100644 .bazelrc create mode 100644 BUILD.bazel create mode 100644 MODULE.bazel create mode 100644 MODULE.bazel.lock create mode 100644 api/BUILD.bazel create mode 100644 api/admin/BUILD.bazel create mode 100644 api/connectclient/BUILD.bazel create mode 100644 api/health/BUILD.bazel create mode 100644 api/info/BUILD.bazel create mode 100644 api/metrics/BUILD.bazel create mode 100644 api/server/BUILD.bazel create mode 100644 app/BUILD.bazel create mode 100644 cache/BUILD.bazel create mode 100644 cache/cachetest/BUILD.bazel create mode 100644 cache/lru/BUILD.bazel create mode 100644 cache/metercacher/BUILD.bazel create mode 100644 chains/BUILD.bazel create mode 100644 chains/atomic/BUILD.bazel create mode 100644 chains/atomic/atomicmock/BUILD.bazel create mode 100644 chains/atomic/atomictest/BUILD.bazel create mode 100644 chains/atomic/gsharedmemory/BUILD.bazel create mode 100644 codec/BUILD.bazel create mode 100644 codec/codecmock/BUILD.bazel create mode 100644 codec/codectest/BUILD.bazel create mode 100644 codec/hierarchycodec/BUILD.bazel create mode 100644 codec/linearcodec/BUILD.bazel create mode 100644 codec/reflectcodec/BUILD.bazel create mode 100644 combined.patch create mode 100644 config/BUILD.bazel create mode 100644 config/node/BUILD.bazel create mode 100644 connectproto/pb/proposervm/BUILD.bazel create mode 100644 connectproto/pb/proposervm/proposervmconnect/BUILD.bazel create mode 100644 connectproto/pb/xsvm/BUILD.bazel create mode 100644 connectproto/pb/xsvm/xsvmconnect/BUILD.bazel create mode 100644 connectproto/proposervm/BUILD.bazel create mode 100644 connectproto/xsvm/BUILD.bazel create mode 100644 database/BUILD.bazel create mode 100644 database/corruptabledb/BUILD.bazel create mode 100644 database/databasemock/BUILD.bazel create mode 100644 database/dbtest/BUILD.bazel create mode 100644 database/factory/BUILD.bazel create mode 100644 database/heightindexdb/dbtest/BUILD.bazel create mode 100644 database/heightindexdb/memdb/BUILD.bazel create mode 100644 database/heightindexdb/meterdb/BUILD.bazel create mode 100644 database/leveldb/BUILD.bazel create mode 100644 database/linkeddb/BUILD.bazel create mode 100644 database/memdb/BUILD.bazel create mode 100644 database/meterdb/BUILD.bazel create mode 100644 database/pebbledb/BUILD.bazel create mode 100644 database/prefixdb/BUILD.bazel create mode 100644 database/rpcdb/BUILD.bazel create mode 100644 database/versiondb/BUILD.bazel create mode 100644 genesis/BUILD.bazel create mode 100644 genesis/generate/checkpoints/BUILD.bazel create mode 100644 genesis/generate/validators/BUILD.bazel create mode 100644 graft/BUILD.bazel create mode 100644 graft/coreth/BUILD.bazel create mode 100644 graft/coreth/accounts/abi/BUILD.bazel create mode 100644 graft/coreth/accounts/abi/bind/BUILD.bazel create mode 100644 graft/coreth/accounts/abi/bind/backends/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/config/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/key/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/load/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/main/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/metrics/BUILD.bazel create mode 100644 graft/coreth/cmd/simulator/txs/BUILD.bazel create mode 100644 graft/coreth/consensus/BUILD.bazel create mode 100644 graft/coreth/consensus/dummy/BUILD.bazel create mode 100644 graft/coreth/constants/BUILD.bazel create mode 100644 graft/coreth/core/BUILD.bazel create mode 100644 graft/coreth/core/coretest/BUILD.bazel create mode 100644 graft/coreth/core/extstate/BUILD.bazel create mode 100644 graft/coreth/core/state/pruner/BUILD.bazel create mode 100644 graft/coreth/core/state/snapshot/BUILD.bazel create mode 100644 graft/coreth/core/txpool/BUILD.bazel create mode 100644 graft/coreth/core/txpool/blobpool/BUILD.bazel create mode 100644 graft/coreth/core/txpool/legacypool/BUILD.bazel create mode 100644 graft/coreth/core/vm/runtime/BUILD.bazel create mode 100644 graft/coreth/eth/BUILD.bazel create mode 100644 graft/coreth/eth/ethconfig/BUILD.bazel create mode 100644 graft/coreth/eth/filters/BUILD.bazel create mode 100644 graft/coreth/eth/gasestimator/BUILD.bazel create mode 100644 graft/coreth/eth/gasprice/BUILD.bazel create mode 100644 graft/coreth/eth/tracers/BUILD.bazel create mode 100644 graft/coreth/ethclient/BUILD.bazel create mode 100644 graft/coreth/ethclient/corethclient/BUILD.bazel create mode 100644 graft/coreth/ethclient/simulated/BUILD.bazel create mode 100644 graft/coreth/interfaces/BUILD.bazel create mode 100644 graft/coreth/internal/blocktest/BUILD.bazel create mode 100644 graft/coreth/internal/debug/BUILD.bazel create mode 100644 graft/coreth/internal/ethapi/BUILD.bazel create mode 100644 graft/coreth/internal/flags/BUILD.bazel create mode 100644 graft/coreth/internal/shutdowncheck/BUILD.bazel create mode 100644 graft/coreth/internal/version/BUILD.bazel create mode 100644 graft/coreth/log/BUILD.bazel create mode 100644 graft/coreth/miner/BUILD.bazel create mode 100644 graft/coreth/nativeasset/BUILD.bazel create mode 100644 graft/coreth/network/BUILD.bazel create mode 100644 graft/coreth/network/stats/BUILD.bazel create mode 100644 graft/coreth/node/BUILD.bazel create mode 100644 graft/coreth/params/BUILD.bazel create mode 100644 graft/coreth/params/extras/BUILD.bazel create mode 100644 graft/coreth/params/extras/extrastest/BUILD.bazel create mode 100644 graft/coreth/params/paramstest/BUILD.bazel create mode 100644 graft/coreth/plugin/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/atomictest/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/state/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/sync/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/txpool/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/atomic/vm/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/client/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/config/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/customheader/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/customrawdb/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/customtypes/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/extension/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/gossip/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/log/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/message/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/tempextrastest/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/ap0/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/ap1/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/ap3/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/ap4/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/ap5/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/cortina/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/upgrade/etna/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/vmerrors/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/vmsync/BUILD.bazel create mode 100644 graft/coreth/plugin/evm/vmtest/BUILD.bazel create mode 100644 graft/coreth/plugin/factory/BUILD.bazel create mode 100644 graft/coreth/precompile/contract/BUILD.bazel create mode 100644 graft/coreth/precompile/contracts/warp/BUILD.bazel create mode 100644 graft/coreth/precompile/modules/BUILD.bazel create mode 100644 graft/coreth/precompile/precompileconfig/BUILD.bazel create mode 100644 graft/coreth/precompile/precompiletest/BUILD.bazel create mode 100644 graft/coreth/precompile/registry/BUILD.bazel create mode 100644 graft/coreth/rpc/BUILD.bazel create mode 100644 graft/coreth/sync/BUILD.bazel create mode 100644 graft/coreth/sync/blocksync/BUILD.bazel create mode 100644 graft/coreth/sync/client/BUILD.bazel create mode 100644 graft/coreth/sync/client/stats/BUILD.bazel create mode 100644 graft/coreth/sync/handlers/BUILD.bazel create mode 100644 graft/coreth/sync/handlers/stats/BUILD.bazel create mode 100644 graft/coreth/sync/handlers/stats/statstest/BUILD.bazel create mode 100644 graft/coreth/sync/statesync/BUILD.bazel create mode 100644 graft/coreth/sync/statesync/statesynctest/BUILD.bazel create mode 100644 graft/coreth/sync/syncutils/BUILD.bazel create mode 100644 graft/coreth/tests/BUILD.bazel create mode 100644 graft/coreth/tests/utils/BUILD.bazel create mode 100644 graft/coreth/tests/warp/BUILD.bazel create mode 100644 graft/coreth/triedb/firewood/BUILD.bazel create mode 100644 graft/coreth/triedb/hashdb/BUILD.bazel create mode 100644 graft/coreth/triedb/pathdb/BUILD.bazel create mode 100644 graft/coreth/utils/BUILD.bazel create mode 100644 graft/coreth/utils/rand/BUILD.bazel create mode 100644 graft/coreth/utils/rpc/BUILD.bazel create mode 100644 graft/coreth/utils/utilstest/BUILD.bazel create mode 100644 graft/coreth/warp/BUILD.bazel create mode 100644 graft/coreth/warp/warptest/BUILD.bazel create mode 100644 ids/BUILD.bazel create mode 100644 ids/galiasreader/BUILD.bazel create mode 100644 ids/idstest/BUILD.bazel create mode 100644 indexer/BUILD.bazel create mode 100644 indexer/examples/p-chain/BUILD.bazel create mode 100644 indexer/examples/x-chain-blocks/BUILD.bazel create mode 100644 main/BUILD.bazel create mode 100644 message/BUILD.bazel create mode 100644 message/messagemock/BUILD.bazel create mode 100644 nat/BUILD.bazel create mode 100644 network/BUILD.bazel create mode 100644 network/dialer/BUILD.bazel create mode 100644 network/p2p/BUILD.bazel create mode 100644 network/p2p/acp118/BUILD.bazel create mode 100644 network/p2p/gossip/BUILD.bazel create mode 100644 network/p2p/p2ptest/BUILD.bazel create mode 100644 network/peer/BUILD.bazel create mode 100644 network/throttling/BUILD.bazel create mode 100644 nix/go/BUILD.bazel create mode 100644 nix/go/bazel.nix create mode 100644 node/BUILD.bazel create mode 100644 patch1.txt create mode 100644 patch2.txt create mode 100644 patches/BUILD.bazel create mode 100644 patches/blst_build.patch create mode 100644 patches/firewood_ffi.patch create mode 100644 patches/libevm_secp256k1.patch create mode 100644 proto/aliasreader/BUILD.bazel create mode 100644 proto/appsender/BUILD.bazel create mode 100644 proto/http/BUILD.bazel create mode 100644 proto/http/responsewriter/BUILD.bazel create mode 100644 proto/io/reader/BUILD.bazel create mode 100644 proto/io/writer/BUILD.bazel create mode 100644 proto/net/conn/BUILD.bazel create mode 100644 proto/p2p/BUILD.bazel create mode 100644 proto/pb/aliasreader/BUILD.bazel create mode 100644 proto/pb/appsender/BUILD.bazel create mode 100644 proto/pb/http/BUILD.bazel create mode 100644 proto/pb/http/responsewriter/BUILD.bazel create mode 100644 proto/pb/io/reader/BUILD.bazel create mode 100644 proto/pb/io/writer/BUILD.bazel create mode 100644 proto/pb/net/conn/BUILD.bazel create mode 100644 proto/pb/p2p/BUILD.bazel create mode 100644 proto/pb/platformvm/BUILD.bazel create mode 100644 proto/pb/rpcdb/BUILD.bazel create mode 100644 proto/pb/sdk/BUILD.bazel create mode 100644 proto/pb/sharedmemory/BUILD.bazel create mode 100644 proto/pb/signer/BUILD.bazel create mode 100644 proto/pb/sync/BUILD.bazel create mode 100644 proto/pb/validatorstate/BUILD.bazel create mode 100644 proto/pb/vm/BUILD.bazel create mode 100644 proto/pb/vm/runtime/BUILD.bazel create mode 100644 proto/pb/warp/BUILD.bazel create mode 100644 proto/platformvm/BUILD.bazel create mode 100644 proto/rpcdb/BUILD.bazel create mode 100644 proto/sdk/BUILD.bazel create mode 100644 proto/sharedmemory/BUILD.bazel create mode 100644 proto/signer/BUILD.bazel create mode 100644 proto/sync/BUILD.bazel create mode 100644 proto/validatorstate/BUILD.bazel create mode 100644 proto/vm/BUILD.bazel create mode 100644 proto/vm/runtime/BUILD.bazel create mode 100644 proto/warp/BUILD.bazel create mode 120000 result create mode 100644 simplex/BUILD.bazel create mode 100644 snow/BUILD.bazel create mode 100644 snow/choices/BUILD.bazel create mode 100644 snow/consensus/avalanche/BUILD.bazel create mode 100644 snow/consensus/snowball/BUILD.bazel create mode 100644 snow/consensus/snowman/BUILD.bazel create mode 100644 snow/consensus/snowman/bootstrapper/BUILD.bazel create mode 100644 snow/consensus/snowman/poll/BUILD.bazel create mode 100644 snow/consensus/snowman/snowmanmock/BUILD.bazel create mode 100644 snow/consensus/snowman/snowmantest/BUILD.bazel create mode 100644 snow/consensus/snowstorm/BUILD.bazel create mode 100644 snow/engine/avalanche/BUILD.bazel create mode 100644 snow/engine/avalanche/bootstrap/BUILD.bazel create mode 100644 snow/engine/avalanche/bootstrap/queue/BUILD.bazel create mode 100644 snow/engine/avalanche/getter/BUILD.bazel create mode 100644 snow/engine/avalanche/state/BUILD.bazel create mode 100644 snow/engine/avalanche/vertex/BUILD.bazel create mode 100644 snow/engine/avalanche/vertex/vertexmock/BUILD.bazel create mode 100644 snow/engine/avalanche/vertex/vertextest/BUILD.bazel create mode 100644 snow/engine/common/BUILD.bazel create mode 100644 snow/engine/common/appsender/BUILD.bazel create mode 100644 snow/engine/common/commonmock/BUILD.bazel create mode 100644 snow/engine/common/tracker/BUILD.bazel create mode 100644 snow/engine/enginetest/BUILD.bazel create mode 100644 snow/engine/snowman/BUILD.bazel create mode 100644 snow/engine/snowman/ancestor/BUILD.bazel create mode 100644 snow/engine/snowman/block/BUILD.bazel create mode 100644 snow/engine/snowman/block/blockmock/BUILD.bazel create mode 100644 snow/engine/snowman/block/blocktest/BUILD.bazel create mode 100644 snow/engine/snowman/bootstrap/BUILD.bazel create mode 100644 snow/engine/snowman/bootstrap/interval/BUILD.bazel create mode 100644 snow/engine/snowman/getter/BUILD.bazel create mode 100644 snow/engine/snowman/job/BUILD.bazel create mode 100644 snow/engine/snowman/syncer/BUILD.bazel create mode 100644 snow/networking/benchlist/BUILD.bazel create mode 100644 snow/networking/handler/BUILD.bazel create mode 100644 snow/networking/handler/handlermock/BUILD.bazel create mode 100644 snow/networking/router/BUILD.bazel create mode 100644 snow/networking/router/routermock/BUILD.bazel create mode 100644 snow/networking/sender/BUILD.bazel create mode 100644 snow/networking/sender/sendermock/BUILD.bazel create mode 100644 snow/networking/sender/sendertest/BUILD.bazel create mode 100644 snow/networking/timeout/BUILD.bazel create mode 100644 snow/networking/timeout/timeoutmock/BUILD.bazel create mode 100644 snow/networking/tracker/BUILD.bazel create mode 100644 snow/networking/tracker/trackermock/BUILD.bazel create mode 100644 snow/snowtest/BUILD.bazel create mode 100644 snow/uptime/BUILD.bazel create mode 100644 snow/uptime/uptimemock/BUILD.bazel create mode 100644 snow/validators/BUILD.bazel create mode 100644 snow/validators/gvalidators/BUILD.bazel create mode 100644 snow/validators/validatorsmock/BUILD.bazel create mode 100644 snow/validators/validatorstest/BUILD.bazel create mode 100644 staking/BUILD.bazel create mode 100644 subnets/BUILD.bazel create mode 100644 tests/BUILD.bazel create mode 100644 tests/antithesis/BUILD.bazel create mode 100644 tests/antithesis/avalanchego/BUILD.bazel create mode 100644 tests/antithesis/avalanchego/gencomposeconfig/BUILD.bazel create mode 100644 tests/antithesis/xsvm/BUILD.bazel create mode 100644 tests/antithesis/xsvm/gencomposeconfig/BUILD.bazel create mode 100644 tests/e2e/BUILD.bazel create mode 100644 tests/e2e/banff/BUILD.bazel create mode 100644 tests/e2e/c/BUILD.bazel create mode 100644 tests/e2e/faultinjection/BUILD.bazel create mode 100644 tests/e2e/p/BUILD.bazel create mode 100644 tests/e2e/vms/BUILD.bazel create mode 100644 tests/e2e/x/BUILD.bazel create mode 100644 tests/e2e/x/transfer/BUILD.bazel create mode 100644 tests/fixture/bootstrapmonitor/BUILD.bazel create mode 100644 tests/fixture/bootstrapmonitor/cmd/BUILD.bazel create mode 100644 tests/fixture/bootstrapmonitor/e2e/BUILD.bazel create mode 100644 tests/fixture/e2e/BUILD.bazel create mode 100644 tests/fixture/stacktrace/BUILD.bazel create mode 100644 tests/fixture/subnet/BUILD.bazel create mode 100644 tests/fixture/tmpnet/BUILD.bazel create mode 100644 tests/fixture/tmpnet/flags/BUILD.bazel create mode 100644 tests/fixture/tmpnet/tmpnetctl/BUILD.bazel create mode 100644 tests/load/BUILD.bazel create mode 100644 tests/load/contracts/BUILD.bazel create mode 100644 tests/load/main/BUILD.bazel create mode 100644 tests/reexecute/c/BUILD.bazel create mode 100644 tests/upgrade/BUILD.bazel create mode 100755 tools/bazel/workspace_status.sh create mode 100644 trace/BUILD.bazel create mode 100644 updated-plan.md create mode 100644 upgrade/BUILD.bazel create mode 100644 upgrade/upgradetest/BUILD.bazel create mode 100644 utils/BUILD.bazel create mode 100644 utils/bag/BUILD.bazel create mode 100644 utils/beacon/BUILD.bazel create mode 100644 utils/bimap/BUILD.bazel create mode 100644 utils/bloom/BUILD.bazel create mode 100644 utils/buffer/BUILD.bazel create mode 100644 utils/cb58/BUILD.bazel create mode 100644 utils/compression/BUILD.bazel create mode 100644 utils/constants/BUILD.bazel create mode 100644 utils/crypto/bls/BUILD.bazel create mode 100644 utils/crypto/bls/blstest/BUILD.bazel create mode 100644 utils/crypto/bls/signer/localsigner/BUILD.bazel create mode 100644 utils/crypto/bls/signer/rpcsigner/BUILD.bazel create mode 100644 utils/crypto/keychain/BUILD.bazel create mode 100644 utils/crypto/secp256k1/BUILD.bazel create mode 100644 utils/dynamicip/BUILD.bazel create mode 100644 utils/filesystem/BUILD.bazel create mode 100644 utils/filesystem/filesystemmock/BUILD.bazel create mode 100644 utils/formatting/BUILD.bazel create mode 100644 utils/formatting/address/BUILD.bazel create mode 100644 utils/hashing/BUILD.bazel create mode 100644 utils/hashing/consistent/BUILD.bazel create mode 100644 utils/hashing/hashingmock/BUILD.bazel create mode 100644 utils/heap/BUILD.bazel create mode 100644 utils/ips/BUILD.bazel create mode 100644 utils/iterator/BUILD.bazel create mode 100644 utils/json/BUILD.bazel create mode 100644 utils/linked/BUILD.bazel create mode 100644 utils/lock/BUILD.bazel create mode 100644 utils/logging/BUILD.bazel create mode 100644 utils/math/BUILD.bazel create mode 100644 utils/math/meter/BUILD.bazel create mode 100644 utils/maybe/BUILD.bazel create mode 100644 utils/metric/BUILD.bazel create mode 100644 utils/packages/BUILD.bazel create mode 100644 utils/password/BUILD.bazel create mode 100644 utils/perms/BUILD.bazel create mode 100644 utils/profiler/BUILD.bazel create mode 100644 utils/resource/BUILD.bazel create mode 100644 utils/resource/resourcemock/BUILD.bazel create mode 100644 utils/rpc/BUILD.bazel create mode 100644 utils/sampler/BUILD.bazel create mode 100644 utils/set/BUILD.bazel create mode 100644 utils/setmap/BUILD.bazel create mode 100644 utils/storage/BUILD.bazel create mode 100644 utils/timer/BUILD.bazel create mode 100644 utils/timer/mockable/BUILD.bazel create mode 100644 utils/tree/BUILD.bazel create mode 100644 utils/ulimit/BUILD.bazel create mode 100644 utils/units/BUILD.bazel create mode 100644 utils/window/BUILD.bazel create mode 100644 utils/wrappers/BUILD.bazel create mode 100644 version/BUILD.bazel create mode 100644 vms/BUILD.bazel create mode 100644 vms/avm/BUILD.bazel create mode 100644 vms/avm/block/BUILD.bazel create mode 100644 vms/avm/block/builder/BUILD.bazel create mode 100644 vms/avm/block/executor/BUILD.bazel create mode 100644 vms/avm/block/executor/executormock/BUILD.bazel create mode 100644 vms/avm/config/BUILD.bazel create mode 100644 vms/avm/fxs/BUILD.bazel create mode 100644 vms/avm/metrics/BUILD.bazel create mode 100644 vms/avm/metrics/metricsmock/BUILD.bazel create mode 100644 vms/avm/network/BUILD.bazel create mode 100644 vms/avm/state/BUILD.bazel create mode 100644 vms/avm/state/statemock/BUILD.bazel create mode 100644 vms/avm/txs/BUILD.bazel create mode 100644 vms/avm/txs/executor/BUILD.bazel create mode 100644 vms/avm/txs/mempool/BUILD.bazel create mode 100644 vms/avm/txs/txsmock/BUILD.bazel create mode 100644 vms/avm/txs/txstest/BUILD.bazel create mode 100644 vms/avm/utxo/BUILD.bazel create mode 100644 vms/components/avax/BUILD.bazel create mode 100644 vms/components/avax/avaxmock/BUILD.bazel create mode 100644 vms/components/chain/BUILD.bazel create mode 100644 vms/components/gas/BUILD.bazel create mode 100644 vms/components/verify/BUILD.bazel create mode 100644 vms/components/verify/verifymock/BUILD.bazel create mode 100644 vms/evm/acp176/BUILD.bazel create mode 100644 vms/evm/acp226/BUILD.bazel create mode 100644 vms/evm/database/BUILD.bazel create mode 100644 vms/evm/emulate/BUILD.bazel create mode 100644 vms/evm/metrics/metricstest/BUILD.bazel create mode 100644 vms/evm/metrics/prometheus/BUILD.bazel create mode 100644 vms/evm/predicate/BUILD.bazel create mode 100644 vms/evm/sync/customrawdb/BUILD.bazel create mode 100644 vms/evm/uptimetracker/BUILD.bazel create mode 100644 vms/example/xsvm/BUILD.bazel create mode 100644 vms/example/xsvm/api/BUILD.bazel create mode 100644 vms/example/xsvm/block/BUILD.bazel create mode 100644 vms/example/xsvm/builder/BUILD.bazel create mode 100644 vms/example/xsvm/chain/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/account/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/chain/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/chain/create/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/chain/genesis/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/issue/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/issue/export/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/issue/importtx/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/issue/status/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/issue/transfer/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/run/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/version/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/versionjson/BUILD.bazel create mode 100644 vms/example/xsvm/cmd/xsvm/BUILD.bazel create mode 100644 vms/example/xsvm/execute/BUILD.bazel create mode 100644 vms/example/xsvm/genesis/BUILD.bazel create mode 100644 vms/example/xsvm/state/BUILD.bazel create mode 100644 vms/example/xsvm/tx/BUILD.bazel create mode 100644 vms/fx/BUILD.bazel create mode 100644 vms/metervm/BUILD.bazel create mode 100644 vms/nftfx/BUILD.bazel create mode 100644 vms/platformvm/BUILD.bazel create mode 100644 vms/platformvm/api/BUILD.bazel create mode 100644 vms/platformvm/block/BUILD.bazel create mode 100644 vms/platformvm/block/builder/BUILD.bazel create mode 100644 vms/platformvm/block/executor/BUILD.bazel create mode 100644 vms/platformvm/block/executor/executormock/BUILD.bazel create mode 100644 vms/platformvm/config/BUILD.bazel create mode 100644 vms/platformvm/fx/BUILD.bazel create mode 100644 vms/platformvm/fx/fxmock/BUILD.bazel create mode 100644 vms/platformvm/genesis/BUILD.bazel create mode 100644 vms/platformvm/genesis/genesistest/BUILD.bazel create mode 100644 vms/platformvm/metrics/BUILD.bazel create mode 100644 vms/platformvm/network/BUILD.bazel create mode 100644 vms/platformvm/reward/BUILD.bazel create mode 100644 vms/platformvm/signer/BUILD.bazel create mode 100644 vms/platformvm/signer/signermock/BUILD.bazel create mode 100644 vms/platformvm/stakeable/BUILD.bazel create mode 100644 vms/platformvm/state/BUILD.bazel create mode 100644 vms/platformvm/state/statetest/BUILD.bazel create mode 100644 vms/platformvm/status/BUILD.bazel create mode 100644 vms/platformvm/txs/BUILD.bazel create mode 100644 vms/platformvm/txs/executor/BUILD.bazel create mode 100644 vms/platformvm/txs/fee/BUILD.bazel create mode 100644 vms/platformvm/txs/mempool/BUILD.bazel create mode 100644 vms/platformvm/txs/txheap/BUILD.bazel create mode 100644 vms/platformvm/txs/txstest/BUILD.bazel create mode 100644 vms/platformvm/utxo/BUILD.bazel create mode 100644 vms/platformvm/utxo/utxomock/BUILD.bazel create mode 100644 vms/platformvm/validators/BUILD.bazel create mode 100644 vms/platformvm/validators/fee/BUILD.bazel create mode 100644 vms/platformvm/validators/validatorstest/BUILD.bazel create mode 100644 vms/platformvm/warp/BUILD.bazel create mode 100644 vms/platformvm/warp/gwarp/BUILD.bazel create mode 100644 vms/platformvm/warp/message/BUILD.bazel create mode 100644 vms/platformvm/warp/payload/BUILD.bazel create mode 100644 vms/platformvm/warp/signertest/BUILD.bazel create mode 100644 vms/propertyfx/BUILD.bazel create mode 100644 vms/proposervm/BUILD.bazel create mode 100644 vms/proposervm/acp181/BUILD.bazel create mode 100644 vms/proposervm/block/BUILD.bazel create mode 100644 vms/proposervm/proposer/BUILD.bazel create mode 100644 vms/proposervm/proposer/proposermock/BUILD.bazel create mode 100644 vms/proposervm/state/BUILD.bazel create mode 100644 vms/proposervm/summary/BUILD.bazel create mode 100644 vms/registry/BUILD.bazel create mode 100644 vms/registry/registrymock/BUILD.bazel create mode 100644 vms/rpcchainvm/BUILD.bazel create mode 100644 vms/rpcchainvm/ghttp/BUILD.bazel create mode 100644 vms/rpcchainvm/ghttp/gconn/BUILD.bazel create mode 100644 vms/rpcchainvm/ghttp/greader/BUILD.bazel create mode 100644 vms/rpcchainvm/ghttp/gresponsewriter/BUILD.bazel create mode 100644 vms/rpcchainvm/ghttp/gwriter/BUILD.bazel create mode 100644 vms/rpcchainvm/grpcutils/BUILD.bazel create mode 100644 vms/rpcchainvm/gruntime/BUILD.bazel create mode 100644 vms/rpcchainvm/runtime/BUILD.bazel create mode 100644 vms/rpcchainvm/runtime/subprocess/BUILD.bazel create mode 100644 vms/secp256k1fx/BUILD.bazel create mode 100644 vms/tracedvm/BUILD.bazel create mode 100644 vms/txs/mempool/BUILD.bazel create mode 100644 vms/types/BUILD.bazel create mode 100644 vms/vmsmock/BUILD.bazel create mode 100644 wallet/chain/c/BUILD.bazel create mode 100644 wallet/chain/p/BUILD.bazel create mode 100644 wallet/chain/p/builder/BUILD.bazel create mode 100644 wallet/chain/p/signer/BUILD.bazel create mode 100644 wallet/chain/p/wallet/BUILD.bazel create mode 100644 wallet/chain/x/BUILD.bazel create mode 100644 wallet/chain/x/builder/BUILD.bazel create mode 100644 wallet/chain/x/signer/BUILD.bazel create mode 100644 wallet/subnet/primary/BUILD.bazel create mode 100644 wallet/subnet/primary/common/BUILD.bazel create mode 100644 wallet/subnet/primary/common/utxotest/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/add-permissioned-subnet-validator/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/add-primary-validator/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/c-chain-export/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/c-chain-import/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/convert-subnet-to-l1/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/create-asset/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/create-chain/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/create-locked-stakeable/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/create-subnet/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/disable-l1-validator/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/get-p-chain-balance/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/get-x-chain-balance/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/increase-l1-validator-balance/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/register-l1-validator/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/remove-subnet-validator/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/set-l1-validator-weight/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/sign-l1-validator-registration/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/sign-l1-validator-removal-genesis/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/sign-l1-validator-removal-registration/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/sign-l1-validator-weight/BUILD.bazel create mode 100644 wallet/subnet/primary/examples/sign-subnet-to-l1-conversion/BUILD.bazel create mode 100644 x/archivedb/BUILD.bazel create mode 100644 x/blockdb/BUILD.bazel create mode 100644 x/merkledb/BUILD.bazel create mode 100644 x/sync/BUILD.bazel create mode 100644 x/sync/protoutils/BUILD.bazel diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000000..7b7803117d38 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,7 @@ +# Directories to exclude from Bazel's file watching +.git +node_modules +.avalanchego +build +.direnv +result diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 000000000000..7dba04158631 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,30 @@ +# Bazel configuration for avalanchego + +# Enable bzlmod (default in Bazel 7+, explicit for clarity) +common --enable_bzlmod + +# Build settings +build --incompatible_enable_cc_toolchain_resolution + +# CGO flags for BLST cryptography library +# Must match scripts/build.sh: CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +build --action_env=CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +build --action_env=CGO_ENABLED=1 + +# Use purego build tag for gnark-crypto to avoid assembly include path issues +# gnark-crypto's assembly files use cross-package #include which Bazel doesn't support +build --define gotags=purego + +# Test settings +test --test_output=errors + +# Performance +build --jobs=auto + +# Nix integration - allow network for nix-build +# This may need adjustment based on your Bazel sandbox configuration +build --sandbox_add_mount_pair=/nix + +# Version injection - provides STABLE_GIT_COMMIT for x_defs +build --workspace_status_command=tools/bazel/workspace_status.sh +build --stamp diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000000..cced2664d656 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,45 @@ +load("@gazelle//:def.bzl", "gazelle") + +# Gazelle configuration +# gazelle:prefix github.com/ava-labs/avalanchego +# gazelle:exclude .git +# gazelle:exclude build +# gazelle:exclude .direnv + +# Resolve coreth imports to local packages (handle replace directive) +# gazelle:resolve go github.com/ava-labs/avalanchego/graft/coreth //graft/coreth +# gazelle:resolve go github.com/ava-labs/avalanchego/graft/coreth/plugin/evm //graft/coreth/plugin/evm + +# Resolve proto pb imports - prefer the pb versions +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/p2p //proto/pb/p2p +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/rpcdb //proto/pb/rpcdb +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/sharedmemory //proto/pb/sharedmemory +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/aliasreader //proto/pb/aliasreader +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/appsender //proto/pb/appsender +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/validatorstate //proto/pb/validatorstate +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/sdk //proto/pb/sdk +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/platformvm //proto/pb/platformvm +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/warp //proto/pb/warp +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/sync //proto/pb/sync +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/vm //proto/pb/vm +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/http //proto/pb/http +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/http/responsewriter //proto/pb/http/responsewriter +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/io/reader //proto/pb/io/reader +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/io/writer //proto/pb/io/writer +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/net/conn //proto/pb/net/conn +# gazelle:resolve go github.com/ava-labs/avalanchego/proto/pb/signer //proto/pb/signer +# gazelle:resolve go github.com/ava-labs/avalanchego/connectproto/pb/proposervm //connectproto/pb/proposervm +# gazelle:resolve go github.com/ava-labs/avalanchego/connectproto/pb/xsvm //connectproto/pb/xsvm + +gazelle(name = "gazelle") + +# Target to update external Go dependencies +gazelle( + name = "gazelle-update-repos", + args = [ + "-from_file=go.mod", + "-to_macro=deps.bzl%go_dependencies", + "-prune", + ], + command = "update-repos", +) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000000..37b4fb7d4eaf --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,179 @@ +"""Bazel module definition for avalanchego.""" + +module( + name = "avalanchego", + version = "0.0.0", +) + +# Core dependencies from Bazel Central Registry +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "rules_go", version = "0.59.0") +bazel_dep(name = "gazelle", version = "0.47.0") + +# Go SDK registration +# Uses same SHA256 checksums as nix/go/default.nix for identical binaries +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download( + name = "go_sdk", + sdks = { + # SHA256s from nix/go/default.nix lines 24-29 + "linux_amd64": ("go1.24.9.linux-amd64.tar.gz", "5b7899591c2dd6e9da1809fde4a2fad842c45d3f6b9deb235ba82216e31e34a6"), + "linux_arm64": ("go1.24.9.linux-arm64.tar.gz", "9aa1243d51d41e2f93e895c89c0a2daf7166768c4a4c3ac79db81029d295a540"), + "darwin_amd64": ("go1.24.9.darwin-amd64.tar.gz", "961aa2ae2b97e428d6d8991367e7c98cb403bac54276b8259aead42a0081591c"), + "darwin_arm64": ("go1.24.9.darwin-arm64.tar.gz", "af451b40651d7fb36db1bbbd9c66ddbed28b96d7da48abea50a19f82c6e9d1d6"), + }, + version = "1.24.9", +) +use_repo(go_sdk, "go_sdk") + +# Go dependencies from go.mod +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//:go.mod") + +# Override libevm's secp256k1 BUILD file to include C source files for CGO +# The gazelle-generated BUILD file doesn't include the libsecp256k1 C sources +go_deps.module_override( + patch_strip = 1, + patches = ["//patches:libevm_secp256k1.patch"], + path = "github.com/ava-labs/libevm", +) + +# Disable gazelle for blst and use custom BUILD file via patch +# blst has complex CGO with assembly that gazelle cannot handle automatically +# The patch creates BUILD.bazel from /dev/null (new file) based on Prysm's blst.BUILD +go_deps.gazelle_override( + build_file_generation = "off", + path = "github.com/supranational/blst", +) +go_deps.module_override( + patch_strip = 1, + patches = ["//patches:blst_build.patch"], + path = "github.com/supranational/blst", +) + +# Use purego build tag for gnark-crypto to avoid assembly include path issues +# gnark-crypto's assembly files use cross-package #include which Bazel doesn't support +# The purego implementation is slower but avoids the complex assembly setup +go_deps.gazelle_override( + directives = [ + "gazelle:build_tags purego", + ], + path = "github.com/consensys/gnark-crypto", +) + +# Fix firewood-go-ethhash FFI to use cc_import for pre-built static libraries +# The gazelle-generated BUILD uses -L paths that don't work in Bazel sandbox +go_deps.gazelle_override( + build_file_generation = "off", + path = "github.com/ava-labs/firewood-go-ethhash/ffi", +) +go_deps.module_override( + patch_strip = 1, + patches = ["//patches:firewood_ffi.patch"], + path = "github.com/ava-labs/firewood-go-ethhash/ffi", +) + +# Additional dependencies from graft/coreth/go.mod that aren't in the main go.mod +go_deps.module( + path = "github.com/ava-labs/firewood-go-ethhash/ffi", + sum = "h1:NAVjEu508HwdgbxH/xQxMQoBUgYUn9RQf0VeCrhtYMY=", + version = "v0.0.15", +) +go_deps.module( + path = "github.com/go-cmd/cmd", + sum = "h1:6y3G+3UqPerXvPcXvj+5QNPHT02BUw7p6PsqRxLNA7Y=", + version = "v1.4.3", +) +go_deps.module( + path = "github.com/google/go-cmp", + sum = "h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=", + version = "v0.7.0", +) +use_repo( + go_deps, + "com_connectrpc_connect", + "com_connectrpc_grpcreflect", + "com_github_antithesishq_antithesis_sdk_go", + "com_github_ava_labs_avalanchego_graft_coreth", + "com_github_ava_labs_firewood_go_ethhash_ffi", + "com_github_ava_labs_libevm", + "com_github_ava_labs_simplex", + "com_github_ava_labs_subnet_evm", + "com_github_btcsuite_btcd_btcutil", + "com_github_cespare_xxhash_v2", + "com_github_cockroachdb_pebble", + "com_github_compose_spec_compose_go", + "com_github_datadog_zstd", + "com_github_davecgh_go_spew", + "com_github_deckarep_golang_set_v2", + "com_github_decred_dcrd_dcrec_secp256k1_v4", + "com_github_go_cmd_cmd", + "com_github_google_btree", + "com_github_google_go_cmp", + "com_github_google_renameio_v2", + "com_github_google_uuid", + "com_github_gorilla_mux", + "com_github_gorilla_rpc", + "com_github_gorilla_websocket", + "com_github_grpc_ecosystem_go_grpc_prometheus", + "com_github_hashicorp_go_bexpr", + "com_github_hashicorp_golang_lru", + "com_github_holiman_billy", + "com_github_holiman_bloomfilter_v2", + "com_github_holiman_uint256", + "com_github_huin_goupnp", + "com_github_jackpal_gateway", + "com_github_jackpal_go_nat_pmp", + "com_github_leanovate_gopter", + "com_github_mattn_go_colorable", + "com_github_mattn_go_isatty", + "com_github_mitchellh_mapstructure", + "com_github_mr_tron_base58", + "com_github_nbutton23_zxcvbn_go", + "com_github_onsi_ginkgo_v2", + "com_github_pires_go_proxyproto", + "com_github_prometheus_client_golang", + "com_github_prometheus_client_model", + "com_github_prometheus_common", + "com_github_rs_cors", + "com_github_shirou_gopsutil", + "com_github_spf13_cast", + "com_github_spf13_cobra", + "com_github_spf13_pflag", + "com_github_spf13_viper", + "com_github_stephenbuttolph_canoto", + "com_github_stretchr_testify", + "com_github_supranational_blst", + "com_github_syndtr_goleveldb", + "com_github_thepudds_fzgen", + "com_github_tyler_smith_go_bip39", + "com_github_urfave_cli_v2", + "com_github_victoriametrics_fastcache", + "in_gopkg_natefinch_lumberjack_v2", + "in_gopkg_yaml_v3", + "io_k8s_api", + "io_k8s_apimachinery", + "io_k8s_client_go", + "io_k8s_utils", + "io_opentelemetry_go_otel", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracegrpc", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracehttp", + "io_opentelemetry_go_otel_sdk", + "io_opentelemetry_go_otel_trace", + "org_golang_google_genproto_googleapis_rpc", + "org_golang_google_grpc", + "org_golang_google_protobuf", + "org_golang_x_crypto", + "org_golang_x_exp", + "org_golang_x_net", + "org_golang_x_sync", + "org_golang_x_term", + "org_golang_x_time", + "org_golang_x_tools", + "org_gonum_v1_gonum", + "org_uber_go_goleak", + "org_uber_go_mock", + "org_uber_go_zap", +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000000..75e0b6edc636 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,2181 @@ +{ + "lockFileVersion": 13, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/source.json": "16a3fc5b4483cb307643791f5a4b7365fa98d2e70da7c378cdbde55f0c0b32cf", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel": "b61bb007c4efad134aa30ee7f4a8e2a39b22aa5685f005edaa022fbd1de43ebc", + "https://bcr.bazel.build/modules/gazelle/0.47.0/source.json": "aeb2e5df14b7fb298625d75d08b9c65bdb0b56014c5eb89da9e5dd0572280ae6", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/source.json": "cfbee3381201f20e35c304041b4abb3b793e66c9b1829b5478d033ad4a5e3aef", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/source.json": "4bb4fed7f5499775d495739f785a5494a1f854645fa1bac5de131264f5acdf01", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel": "a4ed760d3ac0dbc0d7b967631a9a3fd9100d28f7d9fcf214b4df87d4bfff5f9a", + "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", + "https://bcr.bazel.build/modules/rules_go/0.59.0/source.json": "1df17bb7865cfc029492c30163cee891d0dd8658ea0d5bfdf252c4b6db5c1ef6", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.12.2/source.json": "b0890f9cda8ff1b8e691a3ac6037b5c14b7fd4134765a3946b89f31ea02e5884", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.31.0/source.json": "a41c836d4065888eef4377f2f27b6eea0fedb9b5adb1bab1970437373fe90dc7", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", + "usagesDigest": "+hz7IHWN6A1oVJJWNDB6yZRG+RYhF76wAYItpAeIUIg=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@pybind11_bazel~//:python_configure.bzl%extension": { + "general": { + "bzlTransitiveDigest": "whINYge95GgPtysKDbNHQ0ZlWYdtKybHs5y2tLF+x7Q=", + "usagesDigest": "gNvOHVcAlwgDsNXD0amkv2CC96mnaCThPQoE44y8K+w=", + "recordedFileInputs": { + "@@pybind11_bazel~//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_python": { + "bzlFile": "@@pybind11_bazel~//:python_configure.bzl", + "ruleClassName": "python_configure", + "attributes": {} + }, + "pybind11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "build_file": "@@pybind11_bazel~//:pybind11.BUILD", + "strip_prefix": "pybind11-2.11.1", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.11.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "pybind11_bazel~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_fuzzing~//fuzzing/private:extensions.bzl%non_module_dependencies": { + "general": { + "bzlTransitiveDigest": "hVgJRQ3Er45/UUAgNn1Yp2Khcp/Y8WyafA2kXIYmQ5M=", + "usagesDigest": "YnIrdgwnf3iCLfChsltBdZ7yOJh706lpa2vww/i2pDI=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "platforms": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" + ], + "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" + } + }, + "rules_python": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", + "strip_prefix": "rules_python-0.28.0", + "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" + } + }, + "bazel_skylib": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" + ] + } + }, + "com_google_absl": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" + ], + "strip_prefix": "abseil-cpp-20240116.1", + "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" + } + }, + "rules_fuzzing_oss_fuzz": { + "bzlFile": "@@rules_fuzzing~//fuzzing/private/oss_fuzz:repository.bzl", + "ruleClassName": "oss_fuzz_repository", + "attributes": {} + }, + "honggfuzz": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "build_file": "@@rules_fuzzing~//:honggfuzz.BUILD", + "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", + "url": "https://github.com/google/honggfuzz/archive/2.5.zip", + "strip_prefix": "honggfuzz-2.5" + } + }, + "rules_fuzzing_jazzer": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_jar", + "attributes": { + "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" + } + }, + "rules_fuzzing_jazzer_api": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_jar", + "attributes": { + "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_fuzzing~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "fus14IFJ/1LGWWGKPH/U18VnJCoMjfDt1ckahqCnM0A=", + "usagesDigest": "aJF6fLy82rR95Ff5CZPAqxNoFgOMLMN5ImfBS0nhnkg=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", + "ruleClassName": "kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", + "ruleClassName": "kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl", + "ruleClassName": "ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_python~//python/extensions:pip.bzl%pip": { + "os:linux,arch:aarch64": { + "bzlTransitiveDigest": "KMo99TwNLOdfK/7ueUAPKftzh7swgLQOAvGS8weGD00=", + "usagesDigest": "KebG+ouEJqkTn3X39Yws2LuZ61wmGGuqEyVESsFSAEc=", + "recordedFileInputs": { + "@@rules_fuzzing~//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", + "@@protobuf~//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pip_deps_38__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "pip_deps_38_", + "groups": {} + } + }, + "pip_deps_38_numpy": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "numpy<=1.26.1", + "repo": "pip_deps_38", + "repo_prefix": "pip_deps_38_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_8_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_38_setuptools": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "setuptools<=70.3.0", + "repo": "pip_deps_38", + "repo_prefix": "pip_deps_38_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_8_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_39__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "pip_deps_39_", + "groups": {} + } + }, + "pip_deps_39_numpy": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "numpy<=1.26.1", + "repo": "pip_deps_39", + "repo_prefix": "pip_deps_39_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_9_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_39_setuptools": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "setuptools<=70.3.0", + "repo": "pip_deps_39", + "repo_prefix": "pip_deps_39_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_9_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_310__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "pip_deps_310_", + "groups": {} + } + }, + "pip_deps_310_numpy": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "numpy<=1.26.1", + "repo": "pip_deps_310", + "repo_prefix": "pip_deps_310_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_10_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_310_setuptools": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "setuptools<=70.3.0", + "repo": "pip_deps_310", + "repo_prefix": "pip_deps_310_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_10_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_311__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "pip_deps_311_", + "groups": {} + } + }, + "pip_deps_311_numpy": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "numpy<=1.26.1", + "repo": "pip_deps_311", + "repo_prefix": "pip_deps_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_311_setuptools": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "setuptools<=70.3.0", + "repo": "pip_deps_311", + "repo_prefix": "pip_deps_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_312__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "pip_deps_312_", + "groups": {} + } + }, + "pip_deps_312_numpy": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "numpy<=1.26.1", + "repo": "pip_deps_312", + "repo_prefix": "pip_deps_312_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_12_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps_312_setuptools": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "setuptools<=70.3.0", + "repo": "pip_deps_312", + "repo_prefix": "pip_deps_312_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_12_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_38__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "rules_fuzzing_py_deps_38_", + "groups": {} + } + }, + "rules_fuzzing_py_deps_38_absl_py": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3", + "repo": "rules_fuzzing_py_deps_38", + "repo_prefix": "rules_fuzzing_py_deps_38_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_8_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_38_six": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "repo": "rules_fuzzing_py_deps_38", + "repo_prefix": "rules_fuzzing_py_deps_38_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_8_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_39__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "rules_fuzzing_py_deps_39_", + "groups": {} + } + }, + "rules_fuzzing_py_deps_39_absl_py": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3", + "repo": "rules_fuzzing_py_deps_39", + "repo_prefix": "rules_fuzzing_py_deps_39_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_9_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_39_six": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "repo": "rules_fuzzing_py_deps_39", + "repo_prefix": "rules_fuzzing_py_deps_39_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_9_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_310__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "rules_fuzzing_py_deps_310_", + "groups": {} + } + }, + "rules_fuzzing_py_deps_310_absl_py": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3", + "repo": "rules_fuzzing_py_deps_310", + "repo_prefix": "rules_fuzzing_py_deps_310_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_10_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_310_six": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "repo": "rules_fuzzing_py_deps_310", + "repo_prefix": "rules_fuzzing_py_deps_310_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_10_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_311__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "rules_fuzzing_py_deps_311_", + "groups": {} + } + }, + "rules_fuzzing_py_deps_311_absl_py": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3", + "repo": "rules_fuzzing_py_deps_311", + "repo_prefix": "rules_fuzzing_py_deps_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_311_six": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "repo": "rules_fuzzing_py_deps_311", + "repo_prefix": "rules_fuzzing_py_deps_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_312__groups": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "group_library", + "attributes": { + "repo_prefix": "rules_fuzzing_py_deps_312_", + "groups": {} + } + }, + "rules_fuzzing_py_deps_312_absl_py": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3", + "repo": "rules_fuzzing_py_deps_312", + "repo_prefix": "rules_fuzzing_py_deps_312_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_12_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "rules_fuzzing_py_deps_312_six": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "repo": "rules_fuzzing_py_deps_312", + "repo_prefix": "rules_fuzzing_py_deps_312_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_12_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [ + "--require-hashes" + ], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_deps": { + "bzlFile": "@@rules_python~//python/private/bzlmod:pip_repository.bzl", + "ruleClassName": "pip_repository", + "attributes": { + "repo_name": "pip_deps", + "whl_map": { + "numpy": [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12" + ], + "setuptools": [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12" + ] + }, + "default_version": "3.11" + } + }, + "rules_fuzzing_py_deps": { + "bzlFile": "@@rules_python~//python/private/bzlmod:pip_repository.bzl", + "ruleClassName": "pip_repository", + "attributes": { + "repo_name": "rules_fuzzing_py_deps", + "whl_map": { + "absl_py": [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12" + ], + "six": [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12" + ] + }, + "default_version": "3.11" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features~", + "bazel_features_globals", + "bazel_features~~version_extension~bazel_features_globals" + ], + [ + "bazel_features~", + "bazel_features_version", + "bazel_features~~version_extension~bazel_features_version" + ], + [ + "rules_python~", + "bazel_features", + "bazel_features~" + ], + [ + "rules_python~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_python~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python~", + "pypi__build", + "rules_python~~internal_deps~pypi__build" + ], + [ + "rules_python~", + "pypi__click", + "rules_python~~internal_deps~pypi__click" + ], + [ + "rules_python~", + "pypi__colorama", + "rules_python~~internal_deps~pypi__colorama" + ], + [ + "rules_python~", + "pypi__importlib_metadata", + "rules_python~~internal_deps~pypi__importlib_metadata" + ], + [ + "rules_python~", + "pypi__installer", + "rules_python~~internal_deps~pypi__installer" + ], + [ + "rules_python~", + "pypi__more_itertools", + "rules_python~~internal_deps~pypi__more_itertools" + ], + [ + "rules_python~", + "pypi__packaging", + "rules_python~~internal_deps~pypi__packaging" + ], + [ + "rules_python~", + "pypi__pep517", + "rules_python~~internal_deps~pypi__pep517" + ], + [ + "rules_python~", + "pypi__pip", + "rules_python~~internal_deps~pypi__pip" + ], + [ + "rules_python~", + "pypi__pip_tools", + "rules_python~~internal_deps~pypi__pip_tools" + ], + [ + "rules_python~", + "pypi__pyproject_hooks", + "rules_python~~internal_deps~pypi__pyproject_hooks" + ], + [ + "rules_python~", + "pypi__setuptools", + "rules_python~~internal_deps~pypi__setuptools" + ], + [ + "rules_python~", + "pypi__tomli", + "rules_python~~internal_deps~pypi__tomli" + ], + [ + "rules_python~", + "pypi__wheel", + "rules_python~~internal_deps~pypi__wheel" + ], + [ + "rules_python~", + "pypi__zipp", + "rules_python~~internal_deps~pypi__zipp" + ], + [ + "rules_python~", + "pythons_hub", + "rules_python~~python~pythons_hub" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_10_aarch64-unknown-linux-gnu", + "rules_python~~python~python_3_10_aarch64-unknown-linux-gnu" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_10_host", + "rules_python~~python~python_3_10_host" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_11_aarch64-unknown-linux-gnu", + "rules_python~~python~python_3_11_aarch64-unknown-linux-gnu" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_11_host", + "rules_python~~python~python_3_11_host" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_12_aarch64-unknown-linux-gnu", + "rules_python~~python~python_3_12_aarch64-unknown-linux-gnu" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_12_host", + "rules_python~~python~python_3_12_host" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_8_aarch64-unknown-linux-gnu", + "rules_python~~python~python_3_8_aarch64-unknown-linux-gnu" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_8_host", + "rules_python~~python~python_3_8_host" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_9_aarch64-unknown-linux-gnu", + "rules_python~~python~python_3_9_aarch64-unknown-linux-gnu" + ], + [ + "rules_python~~python~pythons_hub", + "python_3_9_host", + "rules_python~~python~python_3_9_host" + ] + ] + } + }, + "@@rules_python~//python/extensions:python.bzl%python": { + "general": { + "bzlTransitiveDigest": "8vDKUdCc6qEk2/YsFiPsFO1Jqgl+XPFRklapOxMAbE8=", + "usagesDigest": "dhjp1hteIlxSdhYDivRRdp1JyPeEip+5RuKlqD4X27w=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": { + "RULES_PYTHON_BZLMOD_DEBUG": null + }, + "generatedRepoSpecs": { + "python_3_8_aarch64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "1825b1f7220bc93ff143f2e70b5c6a79c6469e0eeb40824e07a7277f59aabfda", + "patches": [], + "platform": "aarch64-apple-darwin", + "python_version": "3.8.18", + "release_filename": "20231002/cpython-3.8.18+20231002-aarch64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-aarch64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_8_aarch64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "236a300f386ead02ca98dbddbc026ff4ef4de6701a394106e291ff8b75445ee1", + "patches": [], + "platform": "aarch64-unknown-linux-gnu", + "python_version": "3.8.18", + "release_filename": "20231002/cpython-3.8.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_8_x86_64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "fcf04532e644644213977242cd724fe5e84c0a5ac92ae038e07f1b01b474fca3", + "patches": [], + "platform": "x86_64-apple-darwin", + "python_version": "3.8.18", + "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_8_x86_64-pc-windows-msvc": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "a9d203e78caed94de368d154e841610cef6f6b484738573f4ae9059d37e898a5", + "patches": [], + "platform": "x86_64-pc-windows-msvc", + "python_version": "3.8.18", + "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_8_x86_64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "1e8a3babd1500111359b0f5675d770984bcbcb2cc8890b117394f0ed342fb9ec", + "patches": [], + "platform": "x86_64-unknown-linux-gnu", + "python_version": "3.8.18", + "release_filename": "20231002/cpython-3.8.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.8.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_8_host": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "host_toolchain", + "attributes": { + "python_version": "3.8.18", + "user_repository_name": "python_3_8", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_8": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "toolchain_aliases", + "attributes": { + "python_version": "3.8.18", + "user_repository_name": "python_3_8", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_9_aarch64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "fdc4054837e37b69798c2ef796222a480bc1f80e8ad3a01a95d0168d8282a007", + "patches": [], + "platform": "aarch64-apple-darwin", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-aarch64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-aarch64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_aarch64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "1e0a3e8ce8e58901a259748c0ab640d2b8294713782d14229e882c6898b2fb36", + "patches": [], + "platform": "aarch64-unknown-linux-gnu", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_ppc64le-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "101c38b22fb2f5a0945156da4259c8e9efa0c08de9d7f59afa51e7ce6e22a1cc", + "patches": [], + "platform": "ppc64le-unknown-linux-gnu", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_s390x-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "eee31e55ffbc1f460d7b17f05dd89e45a2636f374a6f8dc29ea13d0497f7f586", + "patches": [], + "platform": "s390x-unknown-linux-gnu", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-s390x-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-s390x-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_x86_64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "82231cb77d4a5c8081a1a1d5b8ae440abe6993514eb77a926c826e9a69a94fb1", + "patches": [], + "platform": "x86_64-apple-darwin", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_x86_64-pc-windows-msvc": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "02ea7bb64524886bd2b05d6b6be4401035e4ba4319146f274f0bcd992822cd75", + "patches": [], + "platform": "x86_64-pc-windows-msvc", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_x86_64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "f3ff38b1ccae7dcebd8bbf2e533c9a984fac881de0ffd1636fbb61842bd924de", + "patches": [], + "platform": "x86_64-unknown-linux-gnu", + "python_version": "3.9.18", + "release_filename": "20231002/cpython-3.9.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.9.18+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_9_host": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "host_toolchain", + "attributes": { + "python_version": "3.9.18", + "user_repository_name": "python_3_9", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_9": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "toolchain_aliases", + "attributes": { + "python_version": "3.9.18", + "user_repository_name": "python_3_9", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_10_aarch64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "fd027b1dedf1ea034cdaa272e91771bdf75ddef4c8653b05d224a0645aa2ca3c", + "patches": [], + "platform": "aarch64-apple-darwin", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-aarch64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-aarch64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_aarch64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "8675915ff454ed2f1597e27794bc7df44f5933c26b94aa06af510fe91b58bb97", + "patches": [], + "platform": "aarch64-unknown-linux-gnu", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-aarch64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_ppc64le-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "f3f9c43eec1a0c3f72845d0b705da17a336d3906b7df212d2640b8f47e8ff375", + "patches": [], + "platform": "ppc64le-unknown-linux-gnu", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_s390x-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "859f6cfe9aedb6e8858892fdc124037e83ab05f28d42a7acd314c6a16d6bd66c", + "patches": [], + "platform": "s390x-unknown-linux-gnu", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-s390x-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-s390x-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_x86_64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "be0b19b6af1f7d8c667e5abef5505ad06cf72e5a11bb5844970c395a7e5b1275", + "patches": [], + "platform": "x86_64-apple-darwin", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_x86_64-pc-windows-msvc": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "b8d930ce0d04bda83037ad3653d7450f8907c88e24bb8255a29b8dab8930d6f1", + "patches": [], + "platform": "x86_64-pc-windows-msvc", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_x86_64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "5d0429c67c992da19ba3eb58b3acd0b35ec5e915b8cae9a4aa8ca565c423847a", + "patches": [], + "platform": "x86_64-unknown-linux-gnu", + "python_version": "3.10.13", + "release_filename": "20231002/cpython-3.10.13+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.10.13+20231002-x86_64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_10_host": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "host_toolchain", + "attributes": { + "python_version": "3.10.13", + "user_repository_name": "python_3_10", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_10": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "toolchain_aliases", + "attributes": { + "python_version": "3.10.13", + "user_repository_name": "python_3_10", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_11_aarch64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883", + "patches": [], + "platform": "aarch64-apple-darwin", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-aarch64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-aarch64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_aarch64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13", + "patches": [], + "platform": "aarch64-unknown-linux-gnu", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_ppc64le-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "b44e1b74afe75c7b19143413632c4386708ae229117f8f950c2094e9681d34c7", + "patches": [], + "platform": "ppc64le-unknown-linux-gnu", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_s390x-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "49520e3ff494708020f306e30b0964f079170be83e956be4504f850557378a22", + "patches": [], + "platform": "s390x-unknown-linux-gnu", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-s390x-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-s390x-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_x86_64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4", + "patches": [], + "platform": "x86_64-apple-darwin", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_x86_64-pc-windows-msvc": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e", + "patches": [], + "platform": "x86_64-pc-windows-msvc", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_x86_64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140", + "patches": [], + "platform": "x86_64-unknown-linux-gnu", + "python_version": "3.11.7", + "release_filename": "20240107/cpython-3.11.7+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_11_host": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "host_toolchain", + "attributes": { + "python_version": "3.11.7", + "user_repository_name": "python_3_11", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_11": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "toolchain_aliases", + "attributes": { + "python_version": "3.11.7", + "user_repository_name": "python_3_11", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_12_aarch64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af", + "patches": [], + "platform": "aarch64-apple-darwin", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-aarch64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-aarch64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_aarch64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b", + "patches": [], + "platform": "aarch64-unknown-linux-gnu", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-aarch64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_ppc64le-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "78051f0d1411ee62bc2af5edfccf6e8400ac4ef82887a2affc19a7ace6a05267", + "patches": [], + "platform": "ppc64le-unknown-linux-gnu", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_s390x-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "60631211c701f8d2c56e5dd7b154e68868128a019b9db1d53a264f56c0d4aee2", + "patches": [], + "platform": "s390x-unknown-linux-gnu", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-s390x-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-s390x-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_x86_64-apple-darwin": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8", + "patches": [], + "platform": "x86_64-apple-darwin", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-apple-darwin-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-apple-darwin-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_x86_64-pc-windows-msvc": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a", + "patches": [], + "platform": "x86_64-pc-windows-msvc", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_x86_64-unknown-linux-gnu": { + "bzlFile": "@@rules_python~//python:repositories.bzl", + "ruleClassName": "python_repository", + "attributes": { + "sha256": "74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472", + "patches": [], + "platform": "x86_64-unknown-linux-gnu", + "python_version": "3.12.1", + "release_filename": "20240107/cpython-3.12.1+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", + "urls": [ + "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz" + ], + "distutils_content": "", + "strip_prefix": "python", + "coverage_tool": "", + "ignore_root_user_error": false + } + }, + "python_3_12_host": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "host_toolchain", + "attributes": { + "python_version": "3.12.1", + "user_repository_name": "python_3_12", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "python_3_12": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "toolchain_aliases", + "attributes": { + "python_version": "3.12.1", + "user_repository_name": "python_3_12", + "platforms": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "ppc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] + } + }, + "pythons_hub": { + "bzlFile": "@@rules_python~//python/private/bzlmod:pythons_hub.bzl", + "ruleClassName": "hub_repo", + "attributes": { + "default_python_version": "3.11", + "toolchain_prefixes": [ + "_0000_python_3_8_", + "_0001_python_3_9_", + "_0002_python_3_10_", + "_0003_python_3_12_", + "_0004_python_3_11_" + ], + "toolchain_python_versions": [ + "3.8", + "3.9", + "3.10", + "3.12", + "3.11" + ], + "toolchain_set_python_version_constraints": [ + "True", + "True", + "True", + "True", + "False" + ], + "toolchain_user_repository_names": [ + "python_3_8", + "python_3_9", + "python_3_10", + "python_3_12", + "python_3_11" + ] + } + }, + "python_versions": { + "bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl", + "ruleClassName": "multi_toolchain_aliases", + "attributes": { + "python_versions": { + "3.8": "python_3_8", + "3.9": "python_3_9", + "3.10": "python_3_10", + "3.11": "python_3_11", + "3.12": "python_3_12" + } + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_python~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_python~//python/private/bzlmod:internal_deps.bzl%internal_deps": { + "general": { + "bzlTransitiveDigest": "7yogJIhmw7i9Wq/n9sQB8N0F84220dJbw64SjOwrmQk=", + "usagesDigest": "r7vtlnQfWxEwrL+QFXux06yzeWEkq/hrcwAssoCoSLY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "rules_python_internal": { + "bzlFile": "@@rules_python~//python/private:internal_config_repo.bzl", + "ruleClassName": "internal_config_repo", + "attributes": {} + }, + "pypi__build": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/58/91/17b00d5fac63d3dca605f1b8269ba3c65e98059e1fd99d00283e42a454f0/build-0.10.0-py3-none-any.whl", + "sha256": "af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl", + "sha256": "3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/5a/cb/6dce742ea14e47d6f565589e859ad225f2a5de576d7696e0623b784e226b/more_itertools-10.1.0-py3-none-any.whl", + "sha256": "64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", + "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ee/2f/ef63e64e9429111e73d3d6cbee80591672d16f2725e648ebc52096f3d323/pep517-0.13.0-py3-none-any.whl", + "sha256": "4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl", + "sha256": "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e8/df/47e6267c6b5cdae867adbdd84b437393e6202ce4322de0a5e0b92960e1d6/pip_tools-7.3.0-py3-none-any.whl", + "sha256": "8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl", + "sha256": "283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/4f/ab/0bcfebdfc3bfa8554b2b2c97a555569c4c1ebc74ea288741ea8326c51906/setuptools-68.1.2-py3-none-any.whl", + "sha256": "3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl", + "sha256": "75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl", + "sha256": "679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python~", + "bazel_tools", + "bazel_tools" + ] + ] + } + } + } +} diff --git a/Taskfile.yml b/Taskfile.yml index a4b4e782be70..0c4db9f716fd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -461,3 +461,47 @@ tasks: cmds: - task: build - cmd: bash -x ./scripts/tests.upgrade.sh {{.CLI_ARGS}} + + # ============================================================================ + # Bazel build tasks + # ============================================================================ + + bazel-build: + desc: Build avalanchego with Bazel + cmds: + - bazel build //main:avalanchego + + bazel-build-opt: + desc: Build avalanchego with Bazel (optimized) + cmds: + - bazel build --compilation_mode=opt //main:avalanchego + + bazel-test: + desc: Run all tests with Bazel + cmds: + - bazel test //... + + bazel-clean: + desc: Clean Bazel build outputs + cmds: + - bazel clean + + bazel-clean-all: + desc: Clean all Bazel caches (full clean) + cmds: + - bazel clean --expunge + + bazel-gazelle: + desc: Run gazelle to update BUILD files + cmds: + - bazel run //:gazelle + + bazel-fmt: + desc: Format BUILD files with buildifier + cmds: + - buildifier -r . + + bazel-mod-tidy: + desc: Tidy MODULE.bazel use_repo calls + cmds: + - bazel mod tidy diff --git a/api/BUILD.bazel b/api/BUILD.bazel new file mode 100644 index 000000000000..7b27d5dca165 --- /dev/null +++ b/api/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "api", + srcs = [ + "common_args_responses.go", + "traced_handler.go", + ], + importpath = "github.com/ava-labs/avalanchego/api", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//trace", + "//utils/formatting", + "//utils/json", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + ], +) diff --git a/api/admin/BUILD.bazel b/api/admin/BUILD.bazel new file mode 100644 index 000000000000..46e9cb79bbb9 --- /dev/null +++ b/api/admin/BUILD.bazel @@ -0,0 +1,55 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "admin", + srcs = [ + "client.go", + "key_value_reader.go", + "service.go", + ], + importpath = "github.com/ava-labs/avalanchego/api/admin", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//api/server", + "//chains", + "//database", + "//database/rpcdb", + "//ids", + "//proto/pb/rpcdb", + "//utils", + "//utils/constants", + "//utils/formatting", + "//utils/json", + "//utils/logging", + "//utils/perms", + "//utils/profiler", + "//utils/rpc", + "//vms", + "//vms/registry", + "@com_github_gorilla_rpc//v2:rpc", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "admin_test", + srcs = [ + "client_test.go", + "service_test.go", + ], + embed = [":admin"], + deps = [ + "//api", + "//database/memdb", + "//ids", + "//proto/pb/rpcdb", + "//utils/formatting", + "//utils/logging", + "//utils/rpc", + "//vms/registry/registrymock", + "//vms/vmsmock", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/api/connectclient/BUILD.bazel b/api/connectclient/BUILD.bazel new file mode 100644 index 000000000000..b9d3777614a0 --- /dev/null +++ b/api/connectclient/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "connectclient", + srcs = ["client.go"], + importpath = "github.com/ava-labs/avalanchego/api/connectclient", + visibility = ["//visibility:public"], + deps = [ + "//api/server", + "@com_connectrpc_connect//:connect", + "@org_golang_x_net//http2", + ], +) diff --git a/api/health/BUILD.bazel b/api/health/BUILD.bazel new file mode 100644 index 000000000000..5cbea4dc69fd --- /dev/null +++ b/api/health/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "health", + srcs = [ + "checker.go", + "client.go", + "handler.go", + "health.go", + "result.go", + "service.go", + "worker.go", + ], + importpath = "github.com/ava-labs/avalanchego/api/health", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/json", + "//utils/logging", + "//utils/rpc", + "//utils/set", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "health_test", + srcs = [ + "client_test.go", + "health_test.go", + "service_test.go", + ], + embed = [":health"], + deps = [ + "//ids", + "//utils", + "//utils/logging", + "//utils/rpc", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/api/info/BUILD.bazel b/api/info/BUILD.bazel new file mode 100644 index 000000000000..b7d2a0ba7c04 --- /dev/null +++ b/api/info/BUILD.bazel @@ -0,0 +1,52 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "info", + srcs = [ + "client.go", + "service.go", + ], + importpath = "github.com/ava-labs/avalanchego/api/info", + visibility = ["//visibility:public"], + deps = [ + "//chains", + "//ids", + "//network", + "//network/peer", + "//snow/networking/benchlist", + "//snow/validators", + "//upgrade", + "//utils", + "//utils/constants", + "//utils/json", + "//utils/logging", + "//utils/rpc", + "//utils/set", + "//utils/units", + "//version", + "//vms", + "//vms/nftfx", + "//vms/platformvm/signer", + "//vms/propertyfx", + "//vms/secp256k1fx", + "@com_github_gorilla_rpc//v2:rpc", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "info_test", + srcs = [ + "client_test.go", + "service_test.go", + ], + embed = [":info"], + deps = [ + "//ids", + "//utils/logging", + "//utils/rpc", + "//vms/vmsmock", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/api/metrics/BUILD.bazel b/api/metrics/BUILD.bazel new file mode 100644 index 000000000000..a7762e66d5e2 --- /dev/null +++ b/api/metrics/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "metrics", + srcs = [ + "client.go", + "label_gatherer.go", + "multi_gatherer.go", + "prefix_gatherer.go", + ], + importpath = "github.com/ava-labs/avalanchego/api/metrics", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/metric", + "//utils/rpc", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_model//go", + "@com_github_prometheus_common//expfmt", + "@org_golang_google_protobuf//proto", + ], +) + +go_test( + name = "metrics_test", + srcs = [ + "gatherer_test.go", + "label_gatherer_test.go", + "prefix_gatherer_test.go", + ], + embed = [":metrics"], + deps = [ + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_prometheus_client_model//go", + "@com_github_stretchr_testify//require", + ], +) diff --git a/api/server/BUILD.bazel b/api/server/BUILD.bazel new file mode 100644 index 000000000000..d3126beaeb46 --- /dev/null +++ b/api/server/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "server", + srcs = [ + "allowed_hosts.go", + "metrics.go", + "router.go", + "server.go", + ], + importpath = "github.com/ava-labs/avalanchego/api/server", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//ids", + "//snow", + "//snow/engine/common", + "//trace", + "//utils/constants", + "//utils/logging", + "//utils/set", + "@com_github_gorilla_mux//:mux", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_rs_cors//:cors", + "@org_golang_x_net//http2", + "@org_golang_x_net//http2/h2c", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "server_test", + srcs = [ + "allowed_hosts_test.go", + "router_test.go", + "server_test.go", + ], + embed = [":server"], + deps = [ + "//snow", + "//snow/snowtest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/app/BUILD.bazel b/app/BUILD.bazel new file mode 100644 index 000000000000..7e974c078828 --- /dev/null +++ b/app/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "app", + srcs = ["app.go"], + importpath = "github.com/ava-labs/avalanchego/app", + visibility = ["//visibility:public"], + deps = [ + "//config/node", + "//node", + "//utils", + "//utils/logging", + "//utils/perms", + "//utils/ulimit", + "@org_uber_go_zap//:zap", + ], +) diff --git a/cache/BUILD.bazel b/cache/BUILD.bazel new file mode 100644 index 000000000000..c317de38a015 --- /dev/null +++ b/cache/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "cache", + srcs = [ + "cache.go", + "empty.go", + ], + importpath = "github.com/ava-labs/avalanchego/cache", + visibility = ["//visibility:public"], + deps = ["//utils"], +) diff --git a/cache/cachetest/BUILD.bazel b/cache/cachetest/BUILD.bazel new file mode 100644 index 000000000000..5bd4162ad7a6 --- /dev/null +++ b/cache/cachetest/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "cachetest", + srcs = ["cacher.go"], + importpath = "github.com/ava-labs/avalanchego/cache/cachetest", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/cache/lru/BUILD.bazel b/cache/lru/BUILD.bazel new file mode 100644 index 000000000000..60129c60322e --- /dev/null +++ b/cache/lru/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "lru", + srcs = [ + "cache.go", + "deduplicator.go", + "sized_cache.go", + ], + importpath = "github.com/ava-labs/avalanchego/cache/lru", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//utils", + "//utils/linked", + ], +) + +go_test( + name = "lru_test", + srcs = [ + "cache_test.go", + "deduplicator_test.go", + "sized_cache_test.go", + ], + embed = [":lru"], + deps = [ + "//cache/cachetest", + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/cache/metercacher/BUILD.bazel b/cache/metercacher/BUILD.bazel new file mode 100644 index 000000000000..1769ad615126 --- /dev/null +++ b/cache/metercacher/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "metercacher", + srcs = [ + "cache.go", + "metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/cache/metercacher", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "metercacher_test", + srcs = ["cache_test.go"], + embed = [":metercacher"], + deps = [ + "//cache", + "//cache/cachetest", + "//cache/lru", + "//ids", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/chains/BUILD.bazel b/chains/BUILD.bazel new file mode 100644 index 000000000000..ea97ac3352c6 --- /dev/null +++ b/chains/BUILD.bazel @@ -0,0 +1,84 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "chains", + srcs = [ + "linearizable_vm.go", + "manager.go", + "registrant.go", + "subnets.go", + "test_manager.go", + ], + importpath = "github.com/ava-labs/avalanchego/chains", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//api/metrics", + "//api/server", + "//chains/atomic", + "//database", + "//database/meterdb", + "//database/prefixdb", + "//ids", + "//message", + "//network", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowball", + "//snow/consensus/snowman", + "//snow/engine/avalanche", + "//snow/engine/avalanche/bootstrap", + "//snow/engine/avalanche/bootstrap/queue", + "//snow/engine/avalanche/getter", + "//snow/engine/avalanche/state", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/snowman", + "//snow/engine/snowman/block", + "//snow/engine/snowman/bootstrap", + "//snow/engine/snowman/getter", + "//snow/engine/snowman/syncer", + "//snow/networking/handler", + "//snow/networking/router", + "//snow/networking/sender", + "//snow/networking/timeout", + "//snow/networking/tracker", + "//snow/validators", + "//staking", + "//subnets", + "//trace", + "//upgrade", + "//utils/buffer", + "//utils/constants", + "//utils/crypto/bls", + "//utils/logging", + "//utils/metric", + "//utils/perms", + "//utils/set", + "//vms", + "//vms/fx", + "//vms/metervm", + "//vms/nftfx", + "//vms/platformvm/warp", + "//vms/propertyfx", + "//vms/proposervm", + "//vms/secp256k1fx", + "//vms/tracedvm", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "chains_test", + srcs = ["subnets_test.go"], + embed = [":chains"], + deps = [ + "//ids", + "//subnets", + "//utils/constants", + "@com_github_stretchr_testify//require", + ], +) diff --git a/chains/atomic/BUILD.bazel b/chains/atomic/BUILD.bazel new file mode 100644 index 000000000000..629d76e3aa40 --- /dev/null +++ b/chains/atomic/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "atomic", + srcs = [ + "codec.go", + "memory.go", + "prefixes.go", + "shared_memory.go", + "state.go", + "writer.go", + ], + importpath = "github.com/ava-labs/avalanchego/chains/atomic", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//database", + "//database/linkeddb", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//utils", + "//utils/hashing", + "//utils/set", + ], +) + +go_test( + name = "atomic_test", + srcs = [ + "memory_test.go", + "mocks_generate_test.go", + "shared_memory_test.go", + ], + embed = [":atomic"], + deps = [ + "//chains/atomic/atomictest", + "//database/memdb", + "//database/prefixdb", + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/chains/atomic/atomicmock/BUILD.bazel b/chains/atomic/atomicmock/BUILD.bazel new file mode 100644 index 000000000000..7723a898ffde --- /dev/null +++ b/chains/atomic/atomicmock/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "atomicmock", + srcs = ["shared_memory.go"], + importpath = "github.com/ava-labs/avalanchego/chains/atomic/atomicmock", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//database", + "//ids", + "@org_uber_go_mock//gomock", + ], +) diff --git a/chains/atomic/atomictest/BUILD.bazel b/chains/atomic/atomictest/BUILD.bazel new file mode 100644 index 000000000000..17a4f7704175 --- /dev/null +++ b/chains/atomic/atomictest/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "atomictest", + srcs = ["shared_memory.go"], + importpath = "github.com/ava-labs/avalanchego/chains/atomic/atomictest", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//database", + "//ids", + "//utils/units", + "@com_github_stretchr_testify//require", + ], +) diff --git a/chains/atomic/gsharedmemory/BUILD.bazel b/chains/atomic/gsharedmemory/BUILD.bazel new file mode 100644 index 000000000000..de5c2b14109b --- /dev/null +++ b/chains/atomic/gsharedmemory/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gsharedmemory", + srcs = [ + "filtered_batch.go", + "shared_memory_client.go", + "shared_memory_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/chains/atomic/gsharedmemory", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//database", + "//ids", + "//proto/pb/sharedmemory", + "//utils/set", + ], +) + +go_test( + name = "gsharedmemory_test", + srcs = ["shared_memory_test.go"], + embed = [":gsharedmemory"], + deps = [ + "//chains/atomic", + "//chains/atomic/atomictest", + "//database", + "//database/memdb", + "//database/prefixdb", + "//ids", + "//proto/pb/sharedmemory", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/codec/BUILD.bazel b/codec/BUILD.bazel new file mode 100644 index 000000000000..1b3f434a5782 --- /dev/null +++ b/codec/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "codec", + srcs = [ + "codec.go", + "general_codec.go", + "manager.go", + "registry.go", + ], + importpath = "github.com/ava-labs/avalanchego/codec", + visibility = ["//visibility:public"], + deps = [ + "//utils/units", + "//utils/wrappers", + ], +) + +go_test( + name = "codec_test", + srcs = ["mocks_generate_test.go"], + embed = [":codec"], +) diff --git a/codec/codecmock/BUILD.bazel b/codec/codecmock/BUILD.bazel new file mode 100644 index 000000000000..506ab501345a --- /dev/null +++ b/codec/codecmock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "codecmock", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/codec/codecmock", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "@org_uber_go_mock//gomock", + ], +) diff --git a/codec/codectest/BUILD.bazel b/codec/codectest/BUILD.bazel new file mode 100644 index 000000000000..cc9a57037513 --- /dev/null +++ b/codec/codectest/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "codectest", + srcs = ["codectest.go"], + importpath = "github.com/ava-labs/avalanchego/codec/codectest", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//utils/wrappers", + "@com_github_stretchr_testify//require", + ], +) diff --git a/codec/hierarchycodec/BUILD.bazel b/codec/hierarchycodec/BUILD.bazel new file mode 100644 index 000000000000..46b10cf6f928 --- /dev/null +++ b/codec/hierarchycodec/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "hierarchycodec", + srcs = ["codec.go"], + importpath = "github.com/ava-labs/avalanchego/codec/hierarchycodec", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/reflectcodec", + "//utils/bimap", + "//utils/wrappers", + ], +) + +go_test( + name = "hierarchycodec_test", + srcs = ["codec_test.go"], + embed = [":hierarchycodec"], + deps = [ + "//codec", + "//codec/codectest", + ], +) diff --git a/codec/linearcodec/BUILD.bazel b/codec/linearcodec/BUILD.bazel new file mode 100644 index 000000000000..fb0824419a32 --- /dev/null +++ b/codec/linearcodec/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "linearcodec", + srcs = ["codec.go"], + importpath = "github.com/ava-labs/avalanchego/codec/linearcodec", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/reflectcodec", + "//utils/bimap", + "//utils/wrappers", + ], +) + +go_test( + name = "linearcodec_test", + srcs = ["codec_test.go"], + embed = [":linearcodec"], + deps = [ + "//codec", + "//codec/codectest", + ], +) diff --git a/codec/reflectcodec/BUILD.bazel b/codec/reflectcodec/BUILD.bazel new file mode 100644 index 000000000000..3b306f85a9d9 --- /dev/null +++ b/codec/reflectcodec/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "reflectcodec", + srcs = [ + "struct_fielder.go", + "type_codec.go", + ], + importpath = "github.com/ava-labs/avalanchego/codec/reflectcodec", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//utils/set", + "//utils/wrappers", + ], +) diff --git a/combined.patch b/combined.patch new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/config/BUILD.bazel b/config/BUILD.bazel new file mode 100644 index 000000000000..f5d27a1c7a73 --- /dev/null +++ b/config/BUILD.bazel @@ -0,0 +1,71 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "config", + srcs = [ + "config.go", + "flags.go", + "keys.go", + "viper.go", + ], + importpath = "github.com/ava-labs/avalanchego/config", + visibility = ["//visibility:public"], + deps = [ + "//api/server", + "//chains", + "//config/node", + "//database/leveldb", + "//database/memdb", + "//database/pebbledb", + "//genesis", + "//ids", + "//network", + "//network/dialer", + "//network/throttling", + "//snow/consensus/snowball", + "//snow/networking/benchlist", + "//snow/networking/router", + "//snow/networking/tracker", + "//staking", + "//subnets", + "//trace", + "//upgrade", + "//utils/bag", + "//utils/compression", + "//utils/constants", + "//utils/dynamicip", + "//utils/ips", + "//utils/logging", + "//utils/perms", + "//utils/profiler", + "//utils/set", + "//utils/storage", + "//utils/timer", + "//utils/ulimit", + "//utils/units", + "//version", + "//vms/components/gas", + "//vms/platformvm/reward", + "//vms/platformvm/validators/fee", + "//vms/proposervm", + "@com_github_spf13_pflag//:pflag", + "@com_github_spf13_viper//:viper", + ], +) + +go_test( + name = "config_test", + srcs = ["config_test.go"], + embed = [":config"], + deps = [ + "//chains", + "//config/node", + "//ids", + "//snow/consensus/snowball", + "//subnets", + "//utils/constants", + "@com_github_spf13_pflag//:pflag", + "@com_github_spf13_viper//:viper", + "@com_github_stretchr_testify//require", + ], +) diff --git a/config/node/BUILD.bazel b/config/node/BUILD.bazel new file mode 100644 index 000000000000..c55dea2c6f1e --- /dev/null +++ b/config/node/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "node", + srcs = [ + "config.go", + "process_context.go", + ], + importpath = "github.com/ava-labs/avalanchego/config/node", + visibility = ["//visibility:public"], + deps = [ + "//api/server", + "//chains", + "//genesis", + "//ids", + "//network", + "//snow/networking/benchlist", + "//snow/networking/router", + "//snow/networking/tracker", + "//subnets", + "//trace", + "//upgrade", + "//utils/logging", + "//utils/profiler", + "//utils/set", + "//utils/timer", + ], +) + +go_test( + name = "node_test", + srcs = ["process_context_test.go"], + embed = [":node"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/connectproto/pb/proposervm/BUILD.bazel b/connectproto/pb/proposervm/BUILD.bazel new file mode 100644 index 000000000000..3f23dee42e0f --- /dev/null +++ b/connectproto/pb/proposervm/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "proposervm", + srcs = ["service.pb.go"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/proposervm", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/connectproto/pb/proposervm/proposervmconnect/BUILD.bazel b/connectproto/pb/proposervm/proposervmconnect/BUILD.bazel new file mode 100644 index 000000000000..112d01214ca0 --- /dev/null +++ b/connectproto/pb/proposervm/proposervmconnect/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "proposervmconnect", + srcs = ["service.connect.go"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/proposervm/proposervmconnect", + visibility = ["//visibility:public"], + deps = [ + "//connectproto/pb/proposervm", + "@com_connectrpc_connect//:connect", + ], +) diff --git a/connectproto/pb/xsvm/BUILD.bazel b/connectproto/pb/xsvm/BUILD.bazel new file mode 100644 index 000000000000..b2ad1276cd78 --- /dev/null +++ b/connectproto/pb/xsvm/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "xsvm", + srcs = ["service.pb.go"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/xsvm", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/connectproto/pb/xsvm/xsvmconnect/BUILD.bazel b/connectproto/pb/xsvm/xsvmconnect/BUILD.bazel new file mode 100644 index 000000000000..bc9a7709b1c4 --- /dev/null +++ b/connectproto/pb/xsvm/xsvmconnect/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "xsvmconnect", + srcs = ["service.connect.go"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/xsvm/xsvmconnect", + visibility = ["//visibility:public"], + deps = [ + "//connectproto/pb/xsvm", + "@com_connectrpc_connect//:connect", + ], +) diff --git a/connectproto/proposervm/BUILD.bazel b/connectproto/proposervm/BUILD.bazel new file mode 100644 index 000000000000..6271dc0e065d --- /dev/null +++ b/connectproto/proposervm/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "proposervm_proto", + srcs = ["service.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "proposervm_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/proposervm", + proto = ":proposervm_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "proposervm", + embed = [":proposervm_go_proto"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/proposervm", + visibility = ["//visibility:public"], +) diff --git a/connectproto/xsvm/BUILD.bazel b/connectproto/xsvm/BUILD.bazel new file mode 100644 index 000000000000..16187ebb78cb --- /dev/null +++ b/connectproto/xsvm/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "xsvm_proto", + srcs = ["service.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "xsvm_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/xsvm", + proto = ":xsvm_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "xsvm", + embed = [":xsvm_go_proto"], + importpath = "github.com/ava-labs/avalanchego/connectproto/pb/xsvm", + visibility = ["//visibility:public"], +) diff --git a/database/BUILD.bazel b/database/BUILD.bazel new file mode 100644 index 000000000000..ac42bbf3c6de --- /dev/null +++ b/database/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "database", + srcs = [ + "batch.go", + "common.go", + "database.go", + "errors.go", + "helpers.go", + "iterator.go", + ], + importpath = "github.com/ava-labs/avalanchego/database", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//ids", + ], +) + +go_test( + name = "database_test", + srcs = [ + "helpers_test.go", + "mocks_generate_test.go", + ], + embed = [":database"], + deps = [ + "//database/memdb", + "//utils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/corruptabledb/BUILD.bazel b/database/corruptabledb/BUILD.bazel new file mode 100644 index 000000000000..a26acf7ac6a3 --- /dev/null +++ b/database/corruptabledb/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "corruptabledb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/corruptabledb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//utils/logging", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "corruptabledb_test", + srcs = ["db_test.go"], + embed = [":corruptabledb"], + deps = [ + "//database", + "//database/databasemock", + "//database/dbtest", + "//database/memdb", + "//utils/logging", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/database/databasemock/BUILD.bazel b/database/databasemock/BUILD.bazel new file mode 100644 index 000000000000..3ffa4953c6a1 --- /dev/null +++ b/database/databasemock/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "databasemock", + srcs = [ + "batch.go", + "iterator.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/databasemock", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@org_uber_go_mock//gomock", + ], +) diff --git a/database/dbtest/BUILD.bazel b/database/dbtest/BUILD.bazel new file mode 100644 index 000000000000..d0cfe496084d --- /dev/null +++ b/database/dbtest/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "dbtest", + srcs = [ + "benchmark.go", + "dbtest.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/dbtest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/databasemock", + "//utils", + "//utils/units", + "@com_github_stretchr_testify//require", + "@org_golang_x_exp//maps", + "@org_golang_x_sync//errgroup", + "@org_uber_go_mock//gomock", + ], +) diff --git a/database/factory/BUILD.bazel b/database/factory/BUILD.bazel new file mode 100644 index 000000000000..c8ace318ee42 --- /dev/null +++ b/database/factory/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "factory", + srcs = ["factory.go"], + importpath = "github.com/ava-labs/avalanchego/database/factory", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/corruptabledb", + "//database/leveldb", + "//database/memdb", + "//database/pebbledb", + "//database/versiondb", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/database/heightindexdb/dbtest/BUILD.bazel b/database/heightindexdb/dbtest/BUILD.bazel new file mode 100644 index 000000000000..09d88a4f69af --- /dev/null +++ b/database/heightindexdb/dbtest/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "dbtest", + srcs = ["dbtest.go"], + importpath = "github.com/ava-labs/avalanchego/database/heightindexdb/dbtest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/heightindexdb/memdb/BUILD.bazel b/database/heightindexdb/memdb/BUILD.bazel new file mode 100644 index 000000000000..1677eb4f32b8 --- /dev/null +++ b/database/heightindexdb/memdb/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "memdb", + srcs = ["database.go"], + importpath = "github.com/ava-labs/avalanchego/database/heightindexdb/memdb", + visibility = ["//visibility:public"], + deps = ["//database"], +) + +go_test( + name = "memdb_test", + srcs = ["database_test.go"], + embed = [":memdb"], + deps = [ + "//database", + "//database/heightindexdb/dbtest", + ], +) diff --git a/database/heightindexdb/meterdb/BUILD.bazel b/database/heightindexdb/meterdb/BUILD.bazel new file mode 100644 index 000000000000..9a4b2367a407 --- /dev/null +++ b/database/heightindexdb/meterdb/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "meterdb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/heightindexdb/meterdb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "meterdb_test", + srcs = ["db_test.go"], + embed = [":meterdb"], + deps = [ + "//database", + "//database/heightindexdb/memdb", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_model//go", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/leveldb/BUILD.bazel b/database/leveldb/BUILD.bazel new file mode 100644 index 000000000000..7932120bd8b8 --- /dev/null +++ b/database/leveldb/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "leveldb", + srcs = [ + "db.go", + "metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/leveldb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//utils", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_syndtr_goleveldb//leveldb", + "@com_github_syndtr_goleveldb//leveldb/errors", + "@com_github_syndtr_goleveldb//leveldb/filter", + "@com_github_syndtr_goleveldb//leveldb/iterator", + "@com_github_syndtr_goleveldb//leveldb/opt", + "@com_github_syndtr_goleveldb//leveldb/util", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "leveldb_test", + srcs = ["db_test.go"], + embed = [":leveldb"], + deps = [ + "//database", + "//database/dbtest", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/linkeddb/BUILD.bazel b/database/linkeddb/BUILD.bazel new file mode 100644 index 000000000000..daf86e60cebd --- /dev/null +++ b/database/linkeddb/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "linkeddb", + srcs = [ + "codec.go", + "linkeddb.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/linkeddb", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//codec", + "//codec/linearcodec", + "//database", + ], +) + +go_test( + name = "linkeddb_test", + srcs = [ + "db_test.go", + "linkeddb_test.go", + ], + embed = [":linkeddb"], + deps = [ + "//database", + "//database/dbtest", + "//database/memdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/memdb/BUILD.bazel b/database/memdb/BUILD.bazel new file mode 100644 index 000000000000..1ebf77af7e74 --- /dev/null +++ b/database/memdb/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "memdb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/memdb", + visibility = ["//visibility:public"], + deps = ["//database"], +) + +go_test( + name = "memdb_test", + srcs = ["db_test.go"], + embed = [":memdb"], + deps = ["//database/dbtest"], +) diff --git a/database/meterdb/BUILD.bazel b/database/meterdb/BUILD.bazel new file mode 100644 index 000000000000..83ae4cff922f --- /dev/null +++ b/database/meterdb/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "meterdb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/meterdb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "meterdb_test", + srcs = ["db_test.go"], + embed = [":meterdb"], + deps = [ + "//database", + "//database/dbtest", + "//database/memdb", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/pebbledb/BUILD.bazel b/database/pebbledb/BUILD.bazel new file mode 100644 index 000000000000..198af7b77f9f --- /dev/null +++ b/database/pebbledb/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "pebbledb", + srcs = [ + "batch.go", + "db.go", + "iterator.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/pebbledb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//utils/logging", + "//utils/set", + "//utils/units", + "@com_github_cockroachdb_pebble//:pebble", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "pebbledb_test", + srcs = [ + "batch_test.go", + "db_test.go", + ], + embed = [":pebbledb"], + deps = [ + "//database/dbtest", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/prefixdb/BUILD.bazel b/database/prefixdb/BUILD.bazel new file mode 100644 index 000000000000..8cdee5ee3f43 --- /dev/null +++ b/database/prefixdb/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "prefixdb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/prefixdb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//utils", + "//utils/hashing", + ], +) + +go_test( + name = "prefixdb_test", + srcs = ["db_test.go"], + embed = [":prefixdb"], + deps = [ + "//database/dbtest", + "//database/memdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/rpcdb/BUILD.bazel b/database/rpcdb/BUILD.bazel new file mode 100644 index 000000000000..8c781eaa7996 --- /dev/null +++ b/database/rpcdb/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "rpcdb", + srcs = [ + "db_client.go", + "db_server.go", + "errors.go", + ], + importpath = "github.com/ava-labs/avalanchego/database/rpcdb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//proto/pb/rpcdb", + "//utils", + "//utils/set", + "//utils/units", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) + +go_test( + name = "rpcdb_test", + srcs = ["db_test.go"], + embed = [":rpcdb"], + deps = [ + "//database/corruptabledb", + "//database/dbtest", + "//database/memdb", + "//proto/pb/rpcdb", + "//utils/logging", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/database/versiondb/BUILD.bazel b/database/versiondb/BUILD.bazel new file mode 100644 index 000000000000..1a0a7d952785 --- /dev/null +++ b/database/versiondb/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "versiondb", + srcs = ["db.go"], + importpath = "github.com/ava-labs/avalanchego/database/versiondb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/memdb", + ], +) + +go_test( + name = "versiondb_test", + srcs = ["db_test.go"], + embed = [":versiondb"], + deps = [ + "//database", + "//database/dbtest", + "//database/memdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/flake.nix b/flake.nix index 8c5443159c75..a561d7b150bc 100644 --- a/flake.nix +++ b/flake.nix @@ -41,6 +41,10 @@ # Task runner go-task + # Bazel build system + bazel_7 + buildifier + # Local Go package from nested flake go-flake.packages.${pkgs.system}.default diff --git a/genesis/BUILD.bazel b/genesis/BUILD.bazel new file mode 100644 index 000000000000..247282ab6e56 --- /dev/null +++ b/genesis/BUILD.bazel @@ -0,0 +1,77 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "genesis", + srcs = [ + "aliases.go", + "bootstrappers.go", + "checkpoints.go", + "config.go", + "genesis.go", + "genesis_fuji.go", + "genesis_local.go", + "genesis_mainnet.go", + "params.go", + "unparsed_config.go", + "validators.go", + ], + embedsrcs = [ + "bootstrappers.json", + "checkpoints.json", + "genesis_fuji.json", + "genesis_local.json", + "genesis_mainnet.json", + "validators.json", + ], + importpath = "github.com/ava-labs/avalanchego/genesis", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils", + "//utils/cb58", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/formatting/address", + "//utils/math", + "//utils/sampler", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "//vms/avm", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/components/gas", + "//vms/nftfx", + "//vms/platformvm/genesis", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/txs", + "//vms/platformvm/validators/fee", + "//vms/propertyfx", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "genesis_test", + srcs = [ + "bootstrappers_test.go", + "config_test.go", + "genesis_test.go", + ], + embed = [":genesis"], + embedsrcs = [ + "genesis_test.json", + "genesis_test_invalid_allocations.json", + ], + deps = [ + "//ids", + "//upgrade", + "//utils/constants", + "//utils/hashing", + "//utils/perms", + "//vms/platformvm/genesis", + "@com_github_ava_labs_libevm//core", + "@com_github_stretchr_testify//require", + ], +) diff --git a/genesis/generate/checkpoints/BUILD.bazel b/genesis/generate/checkpoints/BUILD.bazel new file mode 100644 index 000000000000..9d6ce1c24fcc --- /dev/null +++ b/genesis/generate/checkpoints/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "checkpoints_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/genesis/generate/checkpoints", + visibility = ["//visibility:private"], + deps = [ + "//ids", + "//indexer", + "//utils/constants", + "//utils/perms", + "//utils/set", + ], +) + +go_binary( + name = "checkpoints", + embed = [":checkpoints_lib"], + visibility = ["//visibility:public"], +) diff --git a/genesis/generate/validators/BUILD.bazel b/genesis/generate/validators/BUILD.bazel new file mode 100644 index 000000000000..578e3e6192c1 --- /dev/null +++ b/genesis/generate/validators/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "validators_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/genesis/generate/validators", + visibility = ["//visibility:private"], + deps = [ + "//ids", + "//utils/constants", + "//utils/perms", + "//utils/set", + "//vms/platformvm", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "validators", + embed = [":validators_lib"], + visibility = ["//visibility:public"], +) diff --git a/graft/BUILD.bazel b/graft/BUILD.bazel new file mode 100644 index 000000000000..a01e5d0050b4 --- /dev/null +++ b/graft/BUILD.bazel @@ -0,0 +1,2 @@ +# graft directory BUILD file +# Marker to make this directory a Bazel package diff --git a/graft/coreth/BUILD.bazel b/graft/coreth/BUILD.bazel new file mode 100644 index 000000000000..abd7b60fecd2 --- /dev/null +++ b/graft/coreth/BUILD.bazel @@ -0,0 +1,7 @@ +# Gazelle configuration for coreth submodule +# gazelle:prefix github.com/ava-labs/avalanchego/graft/coreth + +exports_files([ + "go.mod", + "go.sum", +]) diff --git a/graft/coreth/accounts/abi/BUILD.bazel b/graft/coreth/accounts/abi/BUILD.bazel new file mode 100644 index 000000000000..4e978c0d55f3 --- /dev/null +++ b/graft/coreth/accounts/abi/BUILD.bazel @@ -0,0 +1,52 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "abi", + srcs = [ + "abi.go", + "argument.go", + "doc.go", + "error.go", + "error_handling.go", + "event.go", + "method.go", + "pack.go", + "reflect.go", + "topics.go", + "type.go", + "unpack.go", + "utils.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/accounts/abi", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//crypto", + ], +) + +go_test( + name = "abi_test", + srcs = [ + "abi_extra_test.go", + "abi_test.go", + "event_test.go", + "method_test.go", + "pack_test.go", + "packing_test.go", + "reflect_test.go", + "topics_test.go", + "type_test.go", + "unpack_test.go", + ], + embed = [":abi"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//crypto", + "@com_github_davecgh_go_spew//spew", + "@com_github_stretchr_testify//assert", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/accounts/abi/bind/BUILD.bazel b/graft/coreth/accounts/abi/bind/BUILD.bazel new file mode 100644 index 000000000000..81a8c20a5a08 --- /dev/null +++ b/graft/coreth/accounts/abi/bind/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bind", + srcs = [ + "auth.go", + "backend.go", + "base.go", + "bind.go", + "template.go", + "util.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/accounts/abi/bind", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/nativeasset", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//accounts", + "@com_github_ava_labs_libevm//accounts/external", + "@com_github_ava_labs_libevm//accounts/keystore", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + ], +) + +go_test( + name = "bind_test", + srcs = [ + "base_test.go", + "bind_extra_test.go", + "bind_test.go", + "util_test.go", + ], + embed = [":bind"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/accounts/abi/bind/backends", + "//graft/coreth/core", + "//graft/coreth/eth/ethconfig", + "//graft/coreth/ethclient/simulated", + "//graft/coreth/nativeasset", + "//graft/coreth/node", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//rlp", + "@com_github_stretchr_testify//assert", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/accounts/abi/bind/backends/BUILD.bazel b/graft/coreth/accounts/abi/bind/backends/BUILD.bazel new file mode 100644 index 000000000000..b21ac9dba526 --- /dev/null +++ b/graft/coreth/accounts/abi/bind/backends/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "backends", + srcs = ["simulated.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/accounts/abi/bind/backends", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/accounts/abi/bind", + "//graft/coreth/ethclient/simulated", + "//graft/coreth/interfaces", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/cmd/simulator/config/BUILD.bazel b/graft/coreth/cmd/simulator/config/BUILD.bazel new file mode 100644 index 000000000000..2bbb0f2c4fcc --- /dev/null +++ b/graft/coreth/cmd/simulator/config/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = ["flags.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/config", + visibility = ["//visibility:public"], + deps = [ + "@com_github_spf13_pflag//:pflag", + "@com_github_spf13_viper//:viper", + ], +) diff --git a/graft/coreth/cmd/simulator/key/BUILD.bazel b/graft/coreth/cmd/simulator/key/BUILD.bazel new file mode 100644 index 000000000000..365224f8416c --- /dev/null +++ b/graft/coreth/cmd/simulator/key/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "key", + srcs = ["key.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/key", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//crypto", + ], +) diff --git a/graft/coreth/cmd/simulator/load/BUILD.bazel b/graft/coreth/cmd/simulator/load/BUILD.bazel new file mode 100644 index 000000000000..d9bd698b4573 --- /dev/null +++ b/graft/coreth/cmd/simulator/load/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "load", + srcs = [ + "funder.go", + "loader.go", + "worker.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/load", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/cmd/simulator/config", + "//graft/coreth/cmd/simulator/key", + "//graft/coreth/cmd/simulator/metrics", + "//graft/coreth/cmd/simulator/txs", + "//graft/coreth/ethclient", + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/graft/coreth/cmd/simulator/main/BUILD.bazel b/graft/coreth/cmd/simulator/main/BUILD.bazel new file mode 100644 index 000000000000..9a14e77cf359 --- /dev/null +++ b/graft/coreth/cmd/simulator/main/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "main_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/main", + visibility = ["//visibility:private"], + deps = [ + "//graft/coreth/cmd/simulator/config", + "//graft/coreth/cmd/simulator/load", + "//graft/coreth/log", + "@com_github_ava_labs_libevm//log", + "@com_github_spf13_pflag//:pflag", + ], +) + +go_binary( + name = "main", + embed = [":main_lib"], + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/cmd/simulator/metrics/BUILD.bazel b/graft/coreth/cmd/simulator/metrics/BUILD.bazel new file mode 100644 index 000000000000..2d14a7ee6b59 --- /dev/null +++ b/graft/coreth/cmd/simulator/metrics/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "metrics", + srcs = ["metrics.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/metrics", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//log", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/promhttp", + ], +) diff --git a/graft/coreth/cmd/simulator/txs/BUILD.bazel b/graft/coreth/cmd/simulator/txs/BUILD.bazel new file mode 100644 index 000000000000..0fb1fce64edb --- /dev/null +++ b/graft/coreth/cmd/simulator/txs/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "txs", + srcs = [ + "agent.go", + "tx_generator.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/cmd/simulator/txs", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/cmd/simulator/metrics", + "//graft/coreth/ethclient", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//log", + ], +) diff --git a/graft/coreth/consensus/BUILD.bazel b/graft/coreth/consensus/BUILD.bazel new file mode 100644 index 000000000000..2a94a348ad59 --- /dev/null +++ b/graft/coreth/consensus/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "consensus", + srcs = [ + "consensus.go", + "errors.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/consensus", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/consensus/dummy/BUILD.bazel b/graft/coreth/consensus/dummy/BUILD.bazel new file mode 100644 index 000000000000..d79301db55c2 --- /dev/null +++ b/graft/coreth/consensus/dummy/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "dummy", + srcs = ["consensus.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/consensus/dummy", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/utils", + "//vms/components/gas", + "//vms/evm/acp226", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//trie", + ], +) diff --git a/graft/coreth/constants/BUILD.bazel b/graft/coreth/constants/BUILD.bazel new file mode 100644 index 000000000000..7cdada1557ff --- /dev/null +++ b/graft/coreth/constants/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "constants", + srcs = ["constants.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/constants", + visibility = ["//visibility:public"], + deps = ["@com_github_ava_labs_libevm//common"], +) diff --git a/graft/coreth/core/BUILD.bazel b/graft/coreth/core/BUILD.bazel new file mode 100644 index 000000000000..99548dbd955a --- /dev/null +++ b/graft/coreth/core/BUILD.bazel @@ -0,0 +1,161 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "core", + srcs = [ + "block_validator.go", + "blockchain.go", + "blockchain_ext.go", + "blockchain_iterator.go", + "blockchain_reader.go", + "bloom_indexer.go", + "bounded_buffer.go", + "chain_indexer.go", + "chain_makers.go", + "error.go", + "events.go", + "evm.go", + "fifo_cache.go", + "gaspool.go", + "gen_genesis.go", + "genesis.go", + "headerchain.go", + "predicate_check.go", + "sender_cacher.go", + "state_manager.go", + "state_processor.go", + "state_processor_ext.go", + "state_transition.go", + "txindexer.go", + "types.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/core/extstate", + "//graft/coreth/core/state/snapshot", + "//graft/coreth/internal/version", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/modules", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/triedb/firewood", + "//graft/coreth/triedb/hashdb", + "//graft/coreth/triedb/pathdb", + "//utils/set", + "//vms/evm/acp176", + "//vms/evm/acp226", + "//vms/evm/predicate", + "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/bitutil", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//common/lru", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//crypto/kzg4844", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "core_test", + srcs = [ + "bench_test.go", + "block_validator_test.go", + "blockchain_ext_test.go", + "blockchain_log_test.go", + "blockchain_repair_test.go", + "blockchain_sethead_test.go", + "blockchain_snapshot_test.go", + "blockchain_test.go", + "chain_indexer_test.go", + "chain_makers_test.go", + "genesis_extra_test.go", + "genesis_test.go", + "headerchain_test.go", + "main_test.go", + "predicate_check_test.go", + "rlp_test.go", + "state_manager_test.go", + "state_processor_test.go", + "state_transition_test.go", + "txindexer_test.go", + ], + embed = [":core"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/consensus", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core/coretest", + "//graft/coreth/core/extstate", + "//graft/coreth/core/state/pruner", + "//graft/coreth/nativeasset", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/params/paramstest", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/plugin/evm/upgrade/ap4", + "//graft/coreth/plugin/evm/upgrade/cortina", + "//graft/coreth/precompile/contracts/warp", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/triedb/firewood", + "//graft/coreth/triedb/pathdb", + "//graft/coreth/utils", + "//snow/engine/snowman/block", + "//upgrade", + "//upgrade/upgradetest", + "//utils/set", + "//vms/evm/acp176", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//eth/tracers/logger", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_davecgh_go_spew//spew", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + "@org_golang_x_crypto//sha3", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/graft/coreth/core/coretest/BUILD.bazel b/graft/coreth/core/coretest/BUILD.bazel new file mode 100644 index 000000000000..d661f118f7b5 --- /dev/null +++ b/graft/coreth/core/coretest/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "coretest", + srcs = ["test_indices.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/coretest", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/core/extstate/BUILD.bazel b/graft/coreth/core/extstate/BUILD.bazel new file mode 100644 index 000000000000..f31a5414923c --- /dev/null +++ b/graft/coreth/core/extstate/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "extstate", + srcs = [ + "database.go", + "firewood_database.go", + "options.go", + "statedb.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/extstate", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/triedb/firewood", + "//graft/coreth/utils", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "extstate_test", + srcs = [ + "database_test.go", + "state_object_test.go", + "statedb_multicoin_test.go", + ], + embed = [":extstate"], + deps = [ + "//graft/coreth/core/state/snapshot", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/triedb/firewood", + "//graft/coreth/triedb/hashdb", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/core/state/pruner/BUILD.bazel b/graft/coreth/core/state/pruner/BUILD.bazel new file mode 100644 index 000000000000..ed27050999f6 --- /dev/null +++ b/graft/coreth/core/state/pruner/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "pruner", + srcs = [ + "bloom.go", + "pruner.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/state/pruner", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core/state/snapshot", + "//graft/coreth/plugin/evm/customrawdb", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_bloomfilter_v2//:bloomfilter", + ], +) diff --git a/graft/coreth/core/state/snapshot/BUILD.bazel b/graft/coreth/core/state/snapshot/BUILD.bazel new file mode 100644 index 000000000000..3ce480a674fe --- /dev/null +++ b/graft/coreth/core/state/snapshot/BUILD.bazel @@ -0,0 +1,73 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "snapshot", + srcs = [ + "context.go", + "conversion.go", + "difflayer.go", + "disklayer.go", + "generate.go", + "iterator.go", + "iterator_binary.go", + "iterator_fast.go", + "journal.go", + "snapshot.go", + "snapshot_ext.go", + "utils.go", + "wipe.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/state/snapshot", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state/snapshot", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_bloomfilter_v2//:bloomfilter", + "@org_golang_x_exp//slices", + "@org_golang_x_exp//slog", + ], +) + +go_test( + name = "snapshot_test", + srcs = [ + "difflayer_test.go", + "disklayer_test.go", + "generate_test.go", + "iterator_test.go", + "snapshot_test.go", + "wipe_test.go", + ], + embed = [":snapshot"], + deps = [ + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/triedb/hashdb", + "//graft/coreth/triedb/pathdb", + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//ethdb/memorydb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + "@org_golang_x_crypto//sha3", + ], +) diff --git a/graft/coreth/core/txpool/BUILD.bazel b/graft/coreth/core/txpool/BUILD.bazel new file mode 100644 index 000000000000..f25d23f659cc --- /dev/null +++ b/graft/coreth/core/txpool/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "txpool", + srcs = [ + "errors.go", + "subpool.go", + "txpool.go", + "validation.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/txpool", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto/kzg4844", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//params", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/core/txpool/blobpool/BUILD.bazel b/graft/coreth/core/txpool/blobpool/BUILD.bazel new file mode 100644 index 000000000000..226cc64c383a --- /dev/null +++ b/graft/coreth/core/txpool/blobpool/BUILD.bazel @@ -0,0 +1,67 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "blobpool", + srcs = [ + "blobpool.go", + "config.go", + "evictheap.go", + "interface.go", + "limbo.go", + "metrics.go", + "priority.go", + "slotter.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/txpool/blobpool", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/core/txpool", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customheader", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/txpool/blobpool", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_holiman_billy//:billy", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "blobpool_test", + srcs = [ + "blobpool_test.go", + "evictheap_test.go", + "priority_test.go", + "slotter_test.go", + ], + embed = [":blobpool"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/core/txpool", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap3", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//crypto/kzg4844", + "@com_github_ava_labs_libevm//ethdb/memorydb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_holiman_billy//:billy", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/core/txpool/legacypool/BUILD.bazel b/graft/coreth/core/txpool/legacypool/BUILD.bazel new file mode 100644 index 000000000000..95af342e261c --- /dev/null +++ b/graft/coreth/core/txpool/legacypool/BUILD.bazel @@ -0,0 +1,55 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "legacypool", + srcs = [ + "journal.go", + "legacypool.go", + "list.go", + "noncer.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/txpool/legacypool", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/core/txpool", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/prque", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/txpool/legacypool", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_holiman_uint256//:uint256", + "@org_golang_x_exp//slices", + ], +) + +go_test( + name = "legacypool_test", + srcs = [ + "legacypool2_test.go", + "legacypool_test.go", + "list_test.go", + ], + embed = [":legacypool"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/core/txpool", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//trie", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/core/vm/runtime/BUILD.bazel b/graft/coreth/core/vm/runtime/BUILD.bazel new file mode 100644 index 000000000000..64cd33ac152a --- /dev/null +++ b/graft/coreth/core/vm/runtime/BUILD.bazel @@ -0,0 +1,51 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "runtime", + srcs = [ + "doc.go", + "env.go", + "runtime.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/core/vm/runtime", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/upgrade/ap3", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//params", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "runtime_test", + srcs = [ + "runtime_example_test.go", + "runtime_test.go", + ], + embed = [":runtime"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/consensus", + "//graft/coreth/core", + "//graft/coreth/eth/tracers", + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/asm", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//eth/tracers/js", + "@com_github_ava_labs_libevm//eth/tracers/logger", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/eth/BUILD.bazel b/graft/coreth/eth/BUILD.bazel new file mode 100644 index 000000000000..5149b008a40d --- /dev/null +++ b/graft/coreth/eth/BUILD.bazel @@ -0,0 +1,77 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "eth", + srcs = [ + "api.go", + "api_admin.go", + "api_backend.go", + "api_debug.go", + "backend.go", + "bloombits.go", + "chain_with_final_block.go", + "state_accessor.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/core", + "//graft/coreth/core/extstate", + "//graft/coreth/core/state/pruner", + "//graft/coreth/core/txpool", + "//graft/coreth/core/txpool/legacypool", + "//graft/coreth/eth/ethconfig", + "//graft/coreth/eth/filters", + "//graft/coreth/eth/gasprice", + "//graft/coreth/eth/tracers", + "//graft/coreth/internal/ethapi", + "//graft/coreth/internal/shutdowncheck", + "//graft/coreth/miner", + "//graft/coreth/node", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/rpc", + "//utils/timer/mockable", + "@com_github_ava_labs_libevm//accounts", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/bitutil", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + ], +) + +go_test( + name = "eth_test", + srcs = [ + "api_backend_test.go", + "api_debug_test.go", + ], + embed = [":eth"], + deps = [ + "//graft/coreth/core/extstate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//triedb", + "@com_github_davecgh_go_spew//spew", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//assert", + "@com_github_stretchr_testify//require", + "@org_golang_x_exp//slices", + ], +) diff --git a/graft/coreth/eth/ethconfig/BUILD.bazel b/graft/coreth/eth/ethconfig/BUILD.bazel new file mode 100644 index 000000000000..5abbba08f7c9 --- /dev/null +++ b/graft/coreth/eth/ethconfig/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ethconfig", + srcs = [ + "config.go", + "gen_config.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth/ethconfig", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/core/txpool/blobpool", + "//graft/coreth/core/txpool/legacypool", + "//graft/coreth/eth/gasprice", + "//graft/coreth/internal/ethapi", + "//graft/coreth/miner", + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + ], +) diff --git a/graft/coreth/eth/filters/BUILD.bazel b/graft/coreth/eth/filters/BUILD.bazel new file mode 100644 index 000000000000..e3ee372afc67 --- /dev/null +++ b/graft/coreth/eth/filters/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "filters", + srcs = [ + "api.go", + "filter.go", + "filter_system.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth/filters", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/internal/ethapi", + "//graft/coreth/params", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + ], +) + +go_test( + name = "filters_test", + srcs = [ + "api_test.go", + "bench_test.go", + "filter_system_test.go", + "filter_test.go", + ], + embed = [":filters"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/internal/ethapi", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/bitutil", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//triedb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/eth/gasestimator/BUILD.bazel b/graft/coreth/eth/gasestimator/BUILD.bazel new file mode 100644 index 000000000000..df77be73dba9 --- /dev/null +++ b/graft/coreth/eth/gasestimator/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "gasestimator", + srcs = ["gasestimator.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth/gasestimator", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + ], +) diff --git a/graft/coreth/eth/gasprice/BUILD.bazel b/graft/coreth/eth/gasprice/BUILD.bazel new file mode 100644 index 000000000000..18ccbe1cce9e --- /dev/null +++ b/graft/coreth/eth/gasprice/BUILD.bazel @@ -0,0 +1,53 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gasprice", + srcs = [ + "fee_info_provider.go", + "feehistory.go", + "gasprice.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth/gasprice", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/rpc", + "//utils/timer/mockable", + "//vms/evm/acp176", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/lru", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_hashicorp_golang_lru//:golang-lru", + "@org_golang_x_exp//slices", + ], +) + +go_test( + name = "gasprice_test", + srcs = [ + "fee_info_provider_test.go", + "feehistory_test.go", + "gasprice_test.go", + ], + embed = [":gasprice"], + deps = [ + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap4", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/eth/tracers/BUILD.bazel b/graft/coreth/eth/tracers/BUILD.bazel new file mode 100644 index 000000000000..06d8ab9fb638 --- /dev/null +++ b/graft/coreth/eth/tracers/BUILD.bazel @@ -0,0 +1,61 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "tracers", + srcs = [ + "api.go", + "tracers.go", + "tracker.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/eth/tracers", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/core", + "//graft/coreth/internal/ethapi", + "//graft/coreth/params", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//eth/tracers", + "@com_github_ava_labs_libevm//eth/tracers/logger", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + ], +) + +go_test( + name = "tracers_test", + srcs = [ + "api_test.go", + "tracers_test.go", + "tracker_test.go", + ], + embed = [":tracers"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/internal/ethapi", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/rpc", + "//graft/coreth/tests", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//eth/tracers/logger", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//params", + "@org_golang_x_exp//slices", + ], +) diff --git a/graft/coreth/ethclient/BUILD.bazel b/graft/coreth/ethclient/BUILD.bazel new file mode 100644 index 000000000000..f14842da31ba --- /dev/null +++ b/graft/coreth/ethclient/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ethclient", + srcs = [ + "ethclient.go", + "ethclient_ext.go", + "signer.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/ethclient", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/accounts/abi/bind", + "//graft/coreth/interfaces", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/precompile/registry", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/ethclient/corethclient/BUILD.bazel b/graft/coreth/ethclient/corethclient/BUILD.bazel new file mode 100644 index 000000000000..a1e9345a6fc5 --- /dev/null +++ b/graft/coreth/ethclient/corethclient/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "corethclient", + srcs = ["corethclient.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/ethclient/corethclient", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/ethclient", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/ethclient/simulated/BUILD.bazel b/graft/coreth/ethclient/simulated/BUILD.bazel new file mode 100644 index 000000000000..4fcf40ddc3ac --- /dev/null +++ b/graft/coreth/ethclient/simulated/BUILD.bazel @@ -0,0 +1,51 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "simulated", + srcs = [ + "backend.go", + "options.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/ethclient/simulated", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus/dummy", + "//graft/coreth/constants", + "//graft/coreth/core", + "//graft/coreth/eth", + "//graft/coreth/eth/ethconfig", + "//graft/coreth/ethclient", + "//graft/coreth/interfaces", + "//graft/coreth/node", + "//graft/coreth/params", + "//graft/coreth/rpc", + "//utils/timer/mockable", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + ], +) + +go_test( + name = "simulated_test", + srcs = [ + "backend_test.go", + "options_test.go", + ], + embed = [":simulated"], + deps = [ + "//graft/coreth/accounts/abi/bind", + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/rpc", + "//vms/evm/acp176", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/interfaces/BUILD.bazel b/graft/coreth/interfaces/BUILD.bazel new file mode 100644 index 000000000000..db8e5d4b8222 --- /dev/null +++ b/graft/coreth/interfaces/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "interfaces", + srcs = ["interfaces.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/interfaces", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/internal/blocktest/BUILD.bazel b/graft/coreth/internal/blocktest/BUILD.bazel new file mode 100644 index 000000000000..ae09a57ab10d --- /dev/null +++ b/graft/coreth/internal/blocktest/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "blocktest", + srcs = ["test_hash.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/blocktest", + visibility = ["//graft/coreth:__subpackages__"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@org_golang_x_crypto//sha3", + ], +) diff --git a/graft/coreth/internal/debug/BUILD.bazel b/graft/coreth/internal/debug/BUILD.bazel new file mode 100644 index 000000000000..1daa99857c82 --- /dev/null +++ b/graft/coreth/internal/debug/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "debug", + srcs = [ + "api.go", + "flags.go", + "loudpanic.go", + "trace.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/debug", + visibility = ["//graft/coreth:__subpackages__"], + deps = [ + "//graft/coreth/internal/flags", + "@com_github_ava_labs_libevm//log", + "@com_github_hashicorp_go_bexpr//:go-bexpr", + "@com_github_mattn_go_colorable//:go-colorable", + "@com_github_mattn_go_isatty//:go-isatty", + "@com_github_urfave_cli_v2//:cli", + "@in_gopkg_natefinch_lumberjack_v2//:lumberjack_v2", + "@org_golang_x_exp//slog", + ], +) diff --git a/graft/coreth/internal/ethapi/BUILD.bazel b/graft/coreth/internal/ethapi/BUILD.bazel new file mode 100644 index 000000000000..8c171f255270 --- /dev/null +++ b/graft/coreth/internal/ethapi/BUILD.bazel @@ -0,0 +1,93 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ethapi", + srcs = [ + "addrlock.go", + "api.coreth.go", + "api.go", + "api_extra.go", + "backend.go", + "errors.go", + "transaction_args.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/ethapi", + visibility = ["//graft/coreth:__subpackages__"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/consensus", + "//graft/coreth/core", + "//graft/coreth/eth/gasestimator", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/rpc", + "//graft/coreth/triedb/firewood", + "@com_github_ava_labs_libevm//accounts", + "@com_github_ava_labs_libevm//accounts/keystore", + "@com_github_ava_labs_libevm//accounts/scwallet", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//crypto/kzg4844", + "@com_github_ava_labs_libevm//eth/tracers/logger", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_davecgh_go_spew//spew", + "@com_github_holiman_uint256//:uint256", + "@com_github_tyler_smith_go_bip39//:go-bip39", + ], +) + +go_test( + name = "ethapi_test", + srcs = [ + "api.coreth_test.go", + "api_extra_test.go", + "api_test.go", + "mocks_generate_test.go", + "mocks_test.go", + "transaction_args_test.go", + ], + data = glob(["testdata/**"]), + embed = [":ethapi"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/internal/blocktest", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/rpc", + "//graft/coreth/utils", + "//vms/evm/acp176", + "@com_github_ava_labs_libevm//accounts", + "@com_github_ava_labs_libevm//accounts/keystore", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/bloombits", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//crypto/kzg4844", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//params", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + "@org_golang_x_exp//slices", + "@org_uber_go_mock//gomock", + ], +) diff --git a/graft/coreth/internal/flags/BUILD.bazel b/graft/coreth/internal/flags/BUILD.bazel new file mode 100644 index 000000000000..36326a4a6435 --- /dev/null +++ b/graft/coreth/internal/flags/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "flags", + srcs = [ + "categories.go", + "helpers.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/flags", + visibility = ["//graft/coreth:__subpackages__"], + deps = [ + "//graft/coreth/internal/version", + "//graft/coreth/params", + "@com_github_mattn_go_isatty//:go-isatty", + "@com_github_urfave_cli_v2//:cli", + ], +) diff --git a/graft/coreth/internal/shutdowncheck/BUILD.bazel b/graft/coreth/internal/shutdowncheck/BUILD.bazel new file mode 100644 index 000000000000..287e5dd6791d --- /dev/null +++ b/graft/coreth/internal/shutdowncheck/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "shutdowncheck", + srcs = ["shutdown_tracker.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/shutdowncheck", + visibility = ["//graft/coreth:__subpackages__"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + ], +) diff --git a/graft/coreth/internal/version/BUILD.bazel b/graft/coreth/internal/version/BUILD.bazel new file mode 100644 index 000000000000..08c55c5b733d --- /dev/null +++ b/graft/coreth/internal/version/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "version", + srcs = [ + "vcs.go", + "version.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/internal/version", + visibility = ["//graft/coreth:__subpackages__"], + deps = ["//graft/coreth/params"], +) diff --git a/graft/coreth/log/BUILD.bazel b/graft/coreth/log/BUILD.bazel new file mode 100644 index 000000000000..2a792252d620 --- /dev/null +++ b/graft/coreth/log/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "log", + srcs = [ + "format.go", + "handler.go", + "logger.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/log", + visibility = ["//visibility:public"], + deps = [ + "@com_github_holiman_uint256//:uint256", + "@org_golang_x_exp//slog", + ], +) diff --git a/graft/coreth/miner/BUILD.bazel b/graft/coreth/miner/BUILD.bazel new file mode 100644 index 000000000000..a85829d20afc --- /dev/null +++ b/graft/coreth/miner/BUILD.bazel @@ -0,0 +1,49 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "miner", + srcs = [ + "miner.go", + "ordering.go", + "worker.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/miner", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/consensus", + "//graft/coreth/core", + "//graft/coreth/core/extstate", + "//graft/coreth/core/txpool", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/cortina", + "//graft/coreth/precompile/precompileconfig", + "//utils/timer/mockable", + "//utils/units", + "//vms/evm/acp176", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//consensus/misc/eip4844", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//event", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "miner_test", + srcs = ["ordering_test.go"], + embed = [":miner"], + deps = [ + "//graft/coreth/core/txpool", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/nativeasset/BUILD.bazel b/graft/coreth/nativeasset/BUILD.bazel new file mode 100644 index 000000000000..aeb60d47dbd9 --- /dev/null +++ b/graft/coreth/nativeasset/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "nativeasset", + srcs = ["contract.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/nativeasset", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/precompile/contract", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//log", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "nativeasset_test", + srcs = ["contract_test.go"], + deps = [ + ":nativeasset", + "//graft/coreth/core", + "//graft/coreth/core/extstate", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//params", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/network/BUILD.bazel b/graft/coreth/network/BUILD.bazel new file mode 100644 index 000000000000..17cba0780d9a --- /dev/null +++ b/graft/coreth/network/BUILD.bazel @@ -0,0 +1,55 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "network", + srcs = [ + "network.go", + "peer_tracker.go", + "waiting_handler.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/network", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//graft/coreth/network/stats", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/utils/rand", + "//ids", + "//network/p2p", + "//snow", + "//snow/engine/common", + "//snow/validators", + "//utils", + "//utils/math", + "//utils/set", + "//version", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_x_sync//semaphore", + ], +) + +go_test( + name = "network_test", + srcs = [ + "network_test.go", + "peer_tracker_test.go", + ], + embed = [":network"], + deps = [ + "//codec", + "//codec/linearcodec", + "//graft/coreth/plugin/evm/message", + "//ids", + "//network/p2p", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/snowtest", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/graft/coreth/network/stats/BUILD.bazel b/graft/coreth/network/stats/BUILD.bazel new file mode 100644 index 000000000000..aeac77f7b87c --- /dev/null +++ b/graft/coreth/network/stats/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "stats", + srcs = ["stats.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/network/stats", + visibility = ["//visibility:public"], + deps = ["@com_github_ava_labs_libevm//metrics"], +) diff --git a/graft/coreth/node/BUILD.bazel b/graft/coreth/node/BUILD.bazel new file mode 100644 index 000000000000..4bf0bfd42cc4 --- /dev/null +++ b/graft/coreth/node/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "node", + srcs = [ + "api.go", + "config.go", + "defaults.go", + "errors.go", + "node.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/node", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/internal/debug", + "//graft/coreth/rpc", + "@com_github_ava_labs_libevm//accounts", + "@com_github_ava_labs_libevm//accounts/external", + "@com_github_ava_labs_libevm//accounts/keystore", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//log", + ], +) diff --git a/graft/coreth/params/BUILD.bazel b/graft/coreth/params/BUILD.bazel new file mode 100644 index 000000000000..7fb670e9ee0c --- /dev/null +++ b/graft/coreth/params/BUILD.bazel @@ -0,0 +1,53 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "params", + srcs = [ + "config.go", + "config_extra.go", + "config_libevm.go", + "denomination.go", + "hooks_libevm.go", + "network_params.go", + "protocol_params_ext.go", + "version.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/params", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/nativeasset", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/modules", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/utils", + "//snow", + "//upgrade", + "//utils/set", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//libevm/legacy", + "@com_github_ava_labs_libevm//params", + ], +) + +go_test( + name = "params_test", + srcs = [ + "config_extra_test.go", + "config_test.go", + "protocol_params_test.go", + ], + embed = [":params"], + deps = [ + "//graft/coreth/params/extras", + "//graft/coreth/utils", + "//upgrade/upgradetest", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/params/extras/BUILD.bazel b/graft/coreth/params/extras/BUILD.bazel new file mode 100644 index 000000000000..8764358d4838 --- /dev/null +++ b/graft/coreth/params/extras/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "extras", + srcs = [ + "config.go", + "network_upgrades.go", + "precompile_upgrade.go", + "precompiles.go", + "rules.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/params/extras", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/precompile/modules", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/utils", + "//snow", + "//upgrade", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//params", + ], +) + +go_test( + name = "extras_test", + srcs = ["config_extra_test.go"], + embed = [":extras"], + deps = [ + "//graft/coreth/utils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/params/extras/extrastest/BUILD.bazel b/graft/coreth/params/extras/extrastest/BUILD.bazel new file mode 100644 index 000000000000..feec2906d567 --- /dev/null +++ b/graft/coreth/params/extras/extrastest/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "extrastest", + srcs = ["test_rules.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/params/extras/extrastest", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/params/paramstest", + "//upgrade", + "//upgrade/upgradetest", + "@com_github_ava_labs_libevm//common", + ], +) diff --git a/graft/coreth/params/paramstest/BUILD.bazel b/graft/coreth/params/paramstest/BUILD.bazel new file mode 100644 index 000000000000..c03f411e2b54 --- /dev/null +++ b/graft/coreth/params/paramstest/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "paramstest", + srcs = ["forks.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/params/paramstest", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params", + "//upgrade/upgradetest", + ], +) diff --git a/graft/coreth/plugin/BUILD.bazel b/graft/coreth/plugin/BUILD.bazel new file mode 100644 index 000000000000..a3d1efa7ef36 --- /dev/null +++ b/graft/coreth/plugin/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "plugin_lib", + srcs = [ + "keys.go", + "main.go", + "params.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin", + visibility = ["//visibility:private"], + deps = [ + "//graft/coreth/plugin/evm", + "//graft/coreth/plugin/factory", + "//utils/logging", + "//utils/ulimit", + "//vms/rpcchainvm", + "@com_github_spf13_pflag//:pflag", + "@com_github_spf13_viper//:viper", + ], +) + +go_binary( + name = "plugin", + embed = [":plugin_lib"], + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/plugin/evm/BUILD.bazel b/graft/coreth/plugin/evm/BUILD.bazel new file mode 100644 index 000000000000..b6c788d8f0b6 --- /dev/null +++ b/graft/coreth/plugin/evm/BUILD.bazel @@ -0,0 +1,196 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "evm", + srcs = [ + "admin.go", + "block_builder.go", + "eth_gossiper.go", + "health.go", + "libevm.go", + "network_handler.go", + "version.go", + "vm.go", + "vm_database.go", + "vm_extensible.go", + "wrapped_block.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//cache/lru", + "//cache/metercacher", + "//codec", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//graft/coreth/consensus/dummy", + "//graft/coreth/constants", + "//graft/coreth/core", + "//graft/coreth/core/extstate", + "//graft/coreth/core/txpool", + "//graft/coreth/eth", + "//graft/coreth/eth/ethconfig", + "//graft/coreth/miner", + "//graft/coreth/network", + "//graft/coreth/node", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/client", + "//graft/coreth/plugin/evm/config", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/extension", + "//graft/coreth/plugin/evm/gossip", + "//graft/coreth/plugin/evm/log", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/vmerrors", + "//graft/coreth/plugin/evm/vmsync", + "//graft/coreth/precompile/contracts/warp", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/precompile/registry", + "//graft/coreth/rpc", + "//graft/coreth/sync/client", + "//graft/coreth/sync/client/stats", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/triedb/hashdb", + "//graft/coreth/utils/rpc", + "//graft/coreth/warp", + "//ids", + "//network/p2p", + "//network/p2p/acp118", + "//network/p2p/gossip", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils", + "//utils/lock", + "//utils/math", + "//utils/perms", + "//utils/profiler", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/chain", + "//vms/components/gas", + "//vms/evm/acp176", + "//vms/evm/acp226", + "//vms/evm/database", + "//vms/evm/metrics/prometheus", + "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//eth/tracers/js", + "@com_github_ava_labs_libevm//eth/tracers/native", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "evm_test", + srcs = [ + "block_builder_test.go", + "gossip_test.go", + "imports_test.go", + "prestate_tracer_test.go", + "syncervm_test.go", + "tx_gossip_test.go", + "vm_test.go", + "vm_warp_test.go", + ], + data = glob(["testdata/**"]), + embed = [":evm"], + embedsrcs = [ + "ExampleWarp.abi", + "ExampleWarp.bin", + ], + deps = [ + "//database", + "//database/memdb", + "//graft/coreth/consensus/dummy", + "//graft/coreth/constants", + "//graft/coreth/core", + "//graft/coreth/core/txpool", + "//graft/coreth/core/txpool/legacypool", + "//graft/coreth/eth", + "//graft/coreth/eth/tracers", + "//graft/coreth/miner", + "//graft/coreth/node", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/params/paramstest", + "//graft/coreth/plugin/evm/config", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/extension", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/vmtest", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/contracts/warp", + "//graft/coreth/rpc", + "//graft/coreth/tests", + "//graft/coreth/utils", + "//graft/coreth/utils/utilstest", + "//graft/coreth/warp", + "//ids", + "//network/p2p", + "//network/p2p/acp118", + "//network/p2p/gossip", + "//proto/pb/sdk", + "//snow", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/snowtest", + "//snow/validators", + "//snow/validators/validatorstest", + "//upgrade", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/set", + "//utils/timer/mockable", + "//vms/components/chain", + "//vms/evm/acp176", + "//vms/evm/acp226", + "//vms/evm/predicate", + "//vms/platformvm/warp", + "//vms/platformvm/warp/payload", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//trie", + "@com_github_holiman_uint256//:uint256", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/BUILD.bazel b/graft/coreth/plugin/evm/atomic/BUILD.bazel new file mode 100644 index 000000000000..ea57eec11251 --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/BUILD.bazel @@ -0,0 +1,63 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "atomic", + srcs = [ + "codec.go", + "export_tx.go", + "gossip.go", + "import_tx.go", + "metadata.go", + "params.go", + "status.go", + "tx.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//codec", + "//codec/linearcodec", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap5", + "//ids", + "//network/p2p/gossip", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/math", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//log", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "atomic_test", + srcs = [ + "gossip_test.go", + "tx_test.go", + ], + embed = [":atomic"], + deps = [ + "//graft/coreth/plugin/evm/upgrade/ap5", + "//ids", + "//utils/crypto/secp256k1", + "//utils/math", + "//utils/units", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/atomictest/BUILD.bazel b/graft/coreth/plugin/evm/atomic/atomictest/BUILD.bazel new file mode 100644 index 000000000000..170cb50ec5b3 --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/atomictest/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "atomictest", + srcs = [ + "ops.go", + "shared_memories.go", + "tx.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic/atomictest", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//codec", + "//codec/linearcodec", + "//database", + "//database/memdb", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/atomic", + "//ids", + "//snow", + "//snow/snowtest", + "//utils", + "//utils/set", + "//utils/wrappers", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/state/BUILD.bazel b/graft/coreth/plugin/evm/atomic/state/BUILD.bazel new file mode 100644 index 000000000000..a29b8f09891d --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/state/BUILD.bazel @@ -0,0 +1,69 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "state", + srcs = [ + "atomic_backend.go", + "atomic_repository.go", + "atomic_state.go", + "atomic_trie.go", + "atomic_trie_iterator.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic/state", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//codec", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/triedb/hashdb", + "//ids", + "//utils", + "//utils/units", + "//utils/wrappers", + "//vms/evm/database", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//triedb", + ], +) + +go_test( + name = "state_test", + srcs = [ + "atomic_repository_test.go", + "atomic_trie_iterator_test.go", + "atomic_trie_test.go", + ], + embed = [":state"], + deps = [ + "//chains/atomic", + "//codec", + "//database", + "//database/leveldb", + "//database/memdb", + "//database/prefixdb", + "//database/versiondb", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/atomic/atomictest", + "//ids", + "//snow/snowtest", + "//utils", + "//utils/logging", + "//utils/set", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/sync/BUILD.bazel b/graft/coreth/plugin/evm/atomic/sync/BUILD.bazel new file mode 100644 index 000000000000..2fda215f39bf --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/sync/BUILD.bazel @@ -0,0 +1,63 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "sync", + srcs = [ + "extender.go", + "leaf_handler.go", + "summary.go", + "summary_parser.go", + "summary_provider.go", + "syncer.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic/sync", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//database/versiondb", + "//graft/coreth/plugin/evm/atomic/state", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync", + "//graft/coreth/sync/client", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "//ids", + "//snow/engine/snowman/block", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//libevm/options", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + ], +) + +go_test( + name = "sync_test", + srcs = [ + "summary_test.go", + "syncer_test.go", + ], + embed = [":sync"], + deps = [ + "//database", + "//database/memdb", + "//database/versiondb", + "//graft/coreth/plugin/evm/atomic/atomictest", + "//graft/coreth/plugin/evm/atomic/state", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/sync/statesync/statesynctest", + "//ids", + "//snow/engine/snowman/block", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/txpool/BUILD.bazel b/graft/coreth/plugin/evm/atomic/txpool/BUILD.bazel new file mode 100644 index 000000000000..2b67b8016381 --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/txpool/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "txpool", + srcs = [ + "mempool.go", + "metrics.go", + "tx_heap.go", + "txs.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic/txpool", + visibility = ["//visibility:public"], + deps = [ + "//cache/lru", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/config", + "//ids", + "//network/p2p/gossip", + "//snow", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_holiman_uint256//:uint256", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "txpool_test", + srcs = [ + "mempool_test.go", + "tx_heap_test.go", + ], + embed = [":txpool"], + deps = [ + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/atomic/atomictest", + "//graft/coreth/plugin/evm/config", + "//snow/snowtest", + "//utils/bloom", + "@com_github_holiman_uint256//:uint256", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/atomic/vm/BUILD.bazel b/graft/coreth/plugin/evm/atomic/vm/BUILD.bazel new file mode 100644 index 000000000000..8051f9a00823 --- /dev/null +++ b/graft/coreth/plugin/evm/atomic/vm/BUILD.bazel @@ -0,0 +1,135 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "vm", + srcs = [ + "api.go", + "block_extension.go", + "bonus_blocks.go", + "ext_data_hashes.go", + "formatting.go", + "tx_semantic_verifier.go", + "vm.go", + ], + embedsrcs = [ + "fuji_ext_data_hashes.json", + "mainnet_ext_data_hashes.json", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic/vm", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//codec", + "//codec/linearcodec", + "//database", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core/extstate", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/atomic/state", + "//graft/coreth/plugin/evm/atomic/sync", + "//graft/coreth/plugin/evm/atomic/txpool", + "//graft/coreth/plugin/evm/client", + "//graft/coreth/plugin/evm/config", + "//graft/coreth/plugin/evm/customheader", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/extension", + "//graft/coreth/plugin/evm/gossip", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap5", + "//graft/coreth/plugin/evm/vmerrors", + "//graft/coreth/utils", + "//graft/coreth/utils/rpc", + "//ids", + "//network/p2p", + "//network/p2p/gossip", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/logging", + "//utils/math", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/avax", + "//vms/secp256k1fx", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//log", + ], +) + +go_test( + name = "vm_test", + srcs = [ + "export_tx_test.go", + "import_tx_test.go", + "syncervm_test.go", + "tx_gossip_test.go", + "tx_test.go", + "vm_test.go", + ], + embed = [":vm"], + deps = [ + "//chains/atomic", + "//database/memdb", + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/core/extstate", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/params/extras/extrastest", + "//graft/coreth/plugin/evm", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/atomic/atomictest", + "//graft/coreth/plugin/evm/atomic/txpool", + "//graft/coreth/plugin/evm/config", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/extension", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/vmtest", + "//graft/coreth/utils", + "//graft/coreth/utils/utilstest", + "//ids", + "//network/p2p", + "//network/p2p/gossip", + "//proto/pb/sdk", + "//snow", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/snowtest", + "//snow/validators", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/logging", + "//utils/set", + "//utils/units", + "//vms/components/avax", + "//vms/components/chain", + "//vms/evm/predicate", + "//vms/secp256k1fx", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_holiman_uint256//:uint256", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + ], +) diff --git a/graft/coreth/plugin/evm/client/BUILD.bazel b/graft/coreth/plugin/evm/client/BUILD.bazel new file mode 100644 index 000000000000..3fe3cbce9903 --- /dev/null +++ b/graft/coreth/plugin/evm/client/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "client", + srcs = [ + "client.go", + "utils.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/client", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/config", + "//ids", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/rpc", + "@com_github_ava_labs_libevm//common", + "@org_golang_x_exp//slog", + ], +) diff --git a/graft/coreth/plugin/evm/config/BUILD.bazel b/graft/coreth/plugin/evm/config/BUILD.bazel new file mode 100644 index 000000000000..611362ed32f8 --- /dev/null +++ b/graft/coreth/plugin/evm/config/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "config", + srcs = [ + "config.go", + "constants.go", + "default_config.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/config", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "//utils/constants", + "//utils/units", + "//vms/components/gas", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_spf13_cast//:cast", + ], +) + +go_test( + name = "config_test", + srcs = ["config_test.go"], + embed = [":config"], + deps = [ + "//utils/constants", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/customheader/BUILD.bazel b/graft/coreth/plugin/evm/customheader/BUILD.bazel new file mode 100644 index 000000000000..2649e537d6ff --- /dev/null +++ b/graft/coreth/plugin/evm/customheader/BUILD.bazel @@ -0,0 +1,69 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "customheader", + srcs = [ + "base_fee.go", + "block_gas_cost.go", + "dynamic_fee_state.go", + "dynamic_fee_windower.go", + "extra.go", + "gas_limit.go", + "min_delay_excess.go", + "time.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customheader", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/plugin/evm/upgrade/ap4", + "//graft/coreth/plugin/evm/upgrade/ap5", + "//graft/coreth/plugin/evm/upgrade/cortina", + "//graft/coreth/plugin/evm/upgrade/etna", + "//utils/math", + "//vms/components/gas", + "//vms/evm/acp176", + "//vms/evm/acp226", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//core/types", + ], +) + +go_test( + name = "customheader_test", + srcs = [ + "base_fee_test.go", + "block_gas_cost_test.go", + "dynamic_fee_windower_test.go", + "extra_test.go", + "gas_limit_test.go", + "min_delay_excess_test.go", + "time_test.go", + ], + embed = [":customheader"], + deps = [ + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/upgrade/ap0", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/plugin/evm/upgrade/ap4", + "//graft/coreth/plugin/evm/upgrade/ap5", + "//graft/coreth/plugin/evm/upgrade/cortina", + "//graft/coreth/plugin/evm/upgrade/etna", + "//graft/coreth/utils", + "//graft/coreth/utils/utilstest", + "//utils/math", + "//vms/components/gas", + "//vms/evm/acp176", + "//vms/evm/acp226", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/customrawdb/BUILD.bazel b/graft/coreth/plugin/evm/customrawdb/BUILD.bazel new file mode 100644 index 000000000000..a88adb91534b --- /dev/null +++ b/graft/coreth/plugin/evm/customrawdb/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "customrawdb", + srcs = [ + "accessors_metadata_ext.go", + "accessors_snapshot_ext.go", + "accessors_state_sync.go", + "database_ext.go", + "schema_ext.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customrawdb", + visibility = ["//visibility:public"], + deps = [ + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + ], +) + +go_test( + name = "customrawdb_test", + srcs = [ + "accessors_state_sync_test.go", + "database_ext_test.go", + ], + embed = [":customrawdb"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/customtypes/BUILD.bazel b/graft/coreth/plugin/evm/customtypes/BUILD.bazel new file mode 100644 index 000000000000..9739fe6ab96e --- /dev/null +++ b/graft/coreth/plugin/evm/customtypes/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "customtypes", + srcs = [ + "block_ext.go", + "gen_header_serializable_json.go", + "gen_header_serializable_rlp.go", + "hashes_ext.go", + "header_ext.go", + "libevm.go", + "state_account_ext.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/customtypes", + visibility = ["//visibility:public"], + deps = [ + "//vms/evm/acp226", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//rlp", + ], +) + +go_test( + name = "customtypes_test", + srcs = [ + "aliases_test.go", + "block_ext_test.go", + "block_test.go", + "hashing_test.go", + "header_ext_test.go", + "rlp_fuzzer_test.go", + "types_test.go", + ], + embed = [":customtypes"], + deps = [ + "//graft/coreth/internal/blocktest", + "//graft/coreth/params", + "//graft/coreth/utils", + "//graft/coreth/utils/utilstest", + "//vms/evm/acp226", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_google_go_cmp//cmp", + "@com_github_google_go_cmp//cmp/cmpopts", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/extension/BUILD.bazel b/graft/coreth/plugin/evm/extension/BUILD.bazel new file mode 100644 index 000000000000..7bb34f84d452 --- /dev/null +++ b/graft/coreth/plugin/evm/extension/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "extension", + srcs = ["config.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/extension", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/versiondb", + "//graft/coreth/consensus/dummy", + "//graft/coreth/eth", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/config", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/plugin/evm/vmsync", + "//graft/coreth/sync", + "//graft/coreth/sync/handlers", + "//ids", + "//network/p2p", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/timer/mockable", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/graft/coreth/plugin/evm/gossip/BUILD.bazel b/graft/coreth/plugin/evm/gossip/BUILD.bazel new file mode 100644 index 000000000000..fc5da5e3d20c --- /dev/null +++ b/graft/coreth/plugin/evm/gossip/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "gossip", + srcs = ["handler.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/gossip", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//network/p2p", + "//network/p2p/gossip", + "//snow/engine/common", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/graft/coreth/plugin/evm/log/BUILD.bazel b/graft/coreth/plugin/evm/log/BUILD.bazel new file mode 100644 index 000000000000..94f37463fb16 --- /dev/null +++ b/graft/coreth/plugin/evm/log/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "log", + srcs = ["log.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/log", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/log", + "@com_github_ava_labs_libevm//log", + "@org_golang_x_exp//slog", + ], +) + +go_test( + name = "log_test", + srcs = ["log_test.go"], + embed = [":log"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/graft/coreth/plugin/evm/message/BUILD.bazel b/graft/coreth/plugin/evm/message/BUILD.bazel new file mode 100644 index 000000000000..7818426752ed --- /dev/null +++ b/graft/coreth/plugin/evm/message/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "message", + srcs = [ + "block_request.go", + "block_sync_summary.go", + "block_sync_summary_parser.go", + "block_sync_summary_provider.go", + "code_request.go", + "codec.go", + "handler.go", + "leafs_request.go", + "request.go", + "syncable.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/message", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//snow/engine/snowman/block", + "//utils/units", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + ], +) + +go_test( + name = "message_test", + srcs = [ + "block_request_test.go", + "block_sync_summary_test.go", + "code_request_test.go", + "leafs_request_test.go", + ], + embed = [":message"], + deps = [ + "//snow/engine/snowman/block", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/tempextrastest/BUILD.bazel b/graft/coreth/plugin/evm/tempextrastest/BUILD.bazel new file mode 100644 index 000000000000..6dea724d2095 --- /dev/null +++ b/graft/coreth/plugin/evm/tempextrastest/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "tempextrastest_test", + srcs = ["tempextras_test.go"], + deps = [ + "//graft/coreth/params", + "//graft/coreth/plugin/evm", + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/upgrade/ap0/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/ap0/BUILD.bazel new file mode 100644 index 000000000000..614bb82eb4f4 --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/ap0/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ap0", + srcs = ["params.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/ap0", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "//utils/units", + ], +) diff --git a/graft/coreth/plugin/evm/upgrade/ap1/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/ap1/BUILD.bazel new file mode 100644 index 000000000000..4b621393f51b --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/ap1/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ap1", + srcs = ["params.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/ap1", + visibility = ["//visibility:public"], + deps = ["//graft/coreth/utils"], +) diff --git a/graft/coreth/plugin/evm/upgrade/ap3/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/ap3/BUILD.bazel new file mode 100644 index 000000000000..d3873efec79c --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/ap3/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ap3", + srcs = ["window.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/ap3", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common/math", + ], +) + +go_test( + name = "ap3_test", + srcs = ["window_test.go"], + embed = [":ap3"], + deps = [ + "@com_github_ava_labs_libevm//common/math", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/upgrade/ap4/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/ap4/BUILD.bazel new file mode 100644 index 000000000000..7b8d1ead4ad7 --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/ap4/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ap4", + srcs = ["cost.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/ap4", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "//utils/math", + ], +) + +go_test( + name = "ap4_test", + srcs = ["cost_test.go"], + embed = [":ap4"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/graft/coreth/plugin/evm/upgrade/ap5/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/ap5/BUILD.bazel new file mode 100644 index 000000000000..094132208143 --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/ap5/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ap5", + srcs = ["params.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/ap5", + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/plugin/evm/upgrade/cortina/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/cortina/BUILD.bazel new file mode 100644 index 000000000000..b6afdb15abf4 --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/cortina/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "cortina", + srcs = ["params.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/cortina", + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/plugin/evm/upgrade/etna/BUILD.bazel b/graft/coreth/plugin/evm/upgrade/etna/BUILD.bazel new file mode 100644 index 000000000000..e66673cc4b6b --- /dev/null +++ b/graft/coreth/plugin/evm/upgrade/etna/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "etna", + srcs = ["params.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/upgrade/etna", + visibility = ["//visibility:public"], + deps = ["//graft/coreth/utils"], +) diff --git a/graft/coreth/plugin/evm/vmerrors/BUILD.bazel b/graft/coreth/plugin/evm/vmerrors/BUILD.bazel new file mode 100644 index 000000000000..02c934381a25 --- /dev/null +++ b/graft/coreth/plugin/evm/vmerrors/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vmerrors", + srcs = ["errors.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/vmerrors", + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/plugin/evm/vmsync/BUILD.bazel b/graft/coreth/plugin/evm/vmsync/BUILD.bazel new file mode 100644 index 000000000000..1ffc95ffd741 --- /dev/null +++ b/graft/coreth/plugin/evm/vmsync/BUILD.bazel @@ -0,0 +1,49 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "vmsync", + srcs = [ + "client.go", + "registry.go", + "server.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/vmsync", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/versiondb", + "//graft/coreth/core", + "//graft/coreth/core/state/snapshot", + "//graft/coreth/eth", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync", + "//graft/coreth/sync/blocksync", + "//graft/coreth/sync/client", + "//graft/coreth/sync/statesync", + "//ids", + "//snow/engine/snowman/block", + "//vms/components/chain", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@org_golang_x_sync//errgroup", + ], +) + +go_test( + name = "vmsync_test", + srcs = [ + "doubles_test.go", + "registry_test.go", + ], + embed = [":vmsync"], + deps = [ + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync", + "//graft/coreth/utils/utilstest", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/evm/vmtest/BUILD.bazel b/graft/coreth/plugin/evm/vmtest/BUILD.bazel new file mode 100644 index 000000000000..e871adf9a2d4 --- /dev/null +++ b/graft/coreth/plugin/evm/vmtest/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vmtest", + srcs = [ + "genesis.go", + "test_syncervm.go", + "test_vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/vmtest", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//chains/atomic", + "//database", + "//database/memdb", + "//database/prefixdb", + "//graft/coreth/consensus/dummy", + "//graft/coreth/constants", + "//graft/coreth/core", + "//graft/coreth/core/coretest", + "//graft/coreth/params", + "//graft/coreth/params/paramstest", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/extension", + "//graft/coreth/plugin/evm/upgrade/ap3", + "//graft/coreth/plugin/evm/vmsync", + "//graft/coreth/sync/client", + "//graft/coreth/sync/statesync/statesynctest", + "//graft/coreth/utils/utilstest", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/snowtest", + "//upgrade", + "//upgrade/upgradetest", + "//utils", + "//utils/crypto/secp256k1", + "//utils/set", + "//vms/components/chain", + "//vms/evm/database", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/plugin/factory/BUILD.bazel b/graft/coreth/plugin/factory/BUILD.bazel new file mode 100644 index 000000000000..06fc22440e83 --- /dev/null +++ b/graft/coreth/plugin/factory/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "factory", + srcs = ["factory.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/plugin/factory", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/plugin/evm", + "//graft/coreth/plugin/evm/atomic/vm", + "//ids", + "//snow/engine/snowman/block", + "//utils/logging", + "//vms", + ], +) diff --git a/graft/coreth/precompile/contract/BUILD.bazel b/graft/coreth/precompile/contract/BUILD.bazel new file mode 100644 index 000000000000..551244e60d92 --- /dev/null +++ b/graft/coreth/precompile/contract/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "contract", + srcs = [ + "contract.go", + "interfaces.go", + "mocks.go", + "utils.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/contract", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/precompile/precompileconfig", + "//snow", + "//utils/set", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_holiman_uint256//:uint256", + "@org_uber_go_mock//gomock", + ], +) + +go_test( + name = "contract_test", + srcs = [ + "mocks_generate_test.go", + "utils_test.go", + ], + embed = [":contract"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/graft/coreth/precompile/contracts/warp/BUILD.bazel b/graft/coreth/precompile/contracts/warp/BUILD.bazel new file mode 100644 index 000000000000..7ec07435baa2 --- /dev/null +++ b/graft/coreth/precompile/contracts/warp/BUILD.bazel @@ -0,0 +1,67 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "warp", + srcs = [ + "config.go", + "contract.go", + "contract_warp_handler.go", + "module.go", + ], + embedsrcs = ["contract.abi"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/contracts/warp", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/accounts/abi", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/modules", + "//graft/coreth/precompile/precompileconfig", + "//utils/constants", + "//vms/evm/predicate", + "//vms/platformvm/warp", + "//vms/platformvm/warp/payload", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_ava_labs_libevm//log", + ], +) + +go_test( + name = "warp_test", + srcs = [ + "config_test.go", + "contract_test.go", + "predicate_test.go", + ], + embed = [":warp"], + deps = [ + "//graft/coreth/params/extras", + "//graft/coreth/params/extras/extrastest", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/precompileconfig", + "//graft/coreth/precompile/precompiletest", + "//graft/coreth/utils", + "//ids", + "//snow", + "//snow/engine/snowman/block", + "//snow/snowtest", + "//snow/validators", + "//snow/validators/validatorstest", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/math", + "//utils/set", + "//vms/evm/predicate", + "//vms/platformvm/warp", + "//vms/platformvm/warp/payload", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/vm", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/graft/coreth/precompile/modules/BUILD.bazel b/graft/coreth/precompile/modules/BUILD.bazel new file mode 100644 index 000000000000..2ef8b08c1a1e --- /dev/null +++ b/graft/coreth/precompile/modules/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "modules", + srcs = [ + "module.go", + "registerer.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/modules", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/constants", + "//graft/coreth/precompile/contract", + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + ], +) + +go_test( + name = "modules_test", + srcs = ["registerer_test.go"], + embed = [":modules"], + deps = [ + "//graft/coreth/constants", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/precompile/precompileconfig/BUILD.bazel b/graft/coreth/precompile/precompileconfig/BUILD.bazel new file mode 100644 index 000000000000..b88d30293b95 --- /dev/null +++ b/graft/coreth/precompile/precompileconfig/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "precompileconfig", + srcs = [ + "config.go", + "mocks.go", + "upgradeable.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/precompileconfig", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "//snow", + "//snow/engine/snowman/block", + "//vms/evm/predicate", + "//vms/platformvm/warp", + "@com_github_ava_labs_libevm//common", + "@org_uber_go_mock//gomock", + ], +) + +go_test( + name = "precompileconfig_test", + srcs = ["mocks_generate_test.go"], + embed = [":precompileconfig"], +) diff --git a/graft/coreth/precompile/precompiletest/BUILD.bazel b/graft/coreth/precompile/precompiletest/BUILD.bazel new file mode 100644 index 000000000000..7e52fac0d51d --- /dev/null +++ b/graft/coreth/precompile/precompiletest/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "precompiletest", + srcs = [ + "test_config.go", + "test_precompile.go", + "test_predicate.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/precompiletest", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core/extstate", + "//graft/coreth/params/extras", + "//graft/coreth/precompile/contract", + "//graft/coreth/precompile/modules", + "//graft/coreth/precompile/precompileconfig", + "//snow/snowtest", + "//vms/evm/predicate", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/graft/coreth/precompile/registry/BUILD.bazel b/graft/coreth/precompile/registry/BUILD.bazel new file mode 100644 index 000000000000..cc7c439515ea --- /dev/null +++ b/graft/coreth/precompile/registry/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "registry", + srcs = ["registry.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/precompile/registry", + visibility = ["//visibility:public"], + deps = ["//graft/coreth/precompile/contracts/warp"], +) diff --git a/graft/coreth/rpc/BUILD.bazel b/graft/coreth/rpc/BUILD.bazel new file mode 100644 index 000000000000..e25032b8b4bf --- /dev/null +++ b/graft/coreth/rpc/BUILD.bazel @@ -0,0 +1,60 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "rpc", + srcs = [ + "client.go", + "client_opt.go", + "context_headers.go", + "doc.go", + "errors.go", + "handler.go", + "http.go", + "inproc.go", + "json.go", + "metrics.go", + "server.go", + "service.go", + "subscription.go", + "types.go", + "websocket.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/rpc", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rpc", + "@com_github_deckarep_golang_set_v2//:golang-set", + "@com_github_gorilla_websocket//:websocket", + "@org_golang_x_time//rate", + ], +) + +go_test( + name = "rpc_test", + srcs = [ + "client_opt_test.go", + "client_test.go", + "http_test.go", + "main_test.go", + "server_test.go", + "subscription_test.go", + "testservice_test.go", + "types_test.go", + "websocket_test.go", + ], + data = glob(["testdata/**"]), + embed = [":rpc"], + deps = [ + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/math", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//log", + "@com_github_davecgh_go_spew//spew", + "@com_github_gorilla_websocket//:websocket", + ], +) diff --git a/graft/coreth/sync/BUILD.bazel b/graft/coreth/sync/BUILD.bazel new file mode 100644 index 000000000000..ec546e277b41 --- /dev/null +++ b/graft/coreth/sync/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sync", + srcs = ["types.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync", + visibility = ["//visibility:public"], + deps = [ + "//database/versiondb", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client", + "//snow/engine/snowman/block", + "@com_github_ava_labs_libevm//core/types", + ], +) diff --git a/graft/coreth/sync/blocksync/BUILD.bazel b/graft/coreth/sync/blocksync/BUILD.bazel new file mode 100644 index 000000000000..a548ea51b451 --- /dev/null +++ b/graft/coreth/sync/blocksync/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "blocksync", + srcs = ["syncer.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/blocksync", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/sync", + "//graft/coreth/sync/client", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + ], +) + +go_test( + name = "blocksync_test", + srcs = ["syncer_test.go"], + embed = [":blocksync"], + deps = [ + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/sync/client/BUILD.bazel b/graft/coreth/sync/client/BUILD.bazel new file mode 100644 index 000000000000..c869f90cee71 --- /dev/null +++ b/graft/coreth/sync/client/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "client", + srcs = [ + "client.go", + "leaf_syncer.go", + "test_client.go", + "test_network.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/client", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//graft/coreth/network", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client/stats", + "//graft/coreth/sync/handlers", + "//graft/coreth/utils", + "//ids", + "//version", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@org_golang_x_sync//errgroup", + ], +) + +go_test( + name = "client_test", + srcs = ["client_test.go"], + embed = [":client"], + deps = [ + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client/stats", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/sync/statesync/statesynctest", + "//ids", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//triedb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/sync/client/stats/BUILD.bazel b/graft/coreth/sync/client/stats/BUILD.bazel new file mode 100644 index 000000000000..84d7bba70692 --- /dev/null +++ b/graft/coreth/sync/client/stats/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "stats", + srcs = ["stats.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/client/stats", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/plugin/evm/message", + "@com_github_ava_labs_libevm//metrics", + ], +) diff --git a/graft/coreth/sync/handlers/BUILD.bazel b/graft/coreth/sync/handlers/BUILD.bazel new file mode 100644 index 000000000000..8a1b5dcdba18 --- /dev/null +++ b/graft/coreth/sync/handlers/BUILD.bazel @@ -0,0 +1,66 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "handlers", + srcs = [ + "block_request.go", + "code_request.go", + "handler.go", + "leafs_request.go", + "test_providers.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/handlers", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//graft/coreth/core/state/snapshot", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/sync/syncutils", + "//graft/coreth/utils", + "//ids", + "//utils/units", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//ethdb/memorydb", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + ], +) + +go_test( + name = "handlers_test", + srcs = [ + "block_request_test.go", + "code_request_test.go", + "leafs_request_test.go", + ], + embed = [":handlers"], + deps = [ + "//graft/coreth/consensus/dummy", + "//graft/coreth/core", + "//graft/coreth/core/state/snapshot", + "//graft/coreth/params", + "//graft/coreth/plugin/evm/customtypes", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/sync/handlers/stats/statstest", + "//graft/coreth/sync/statesync/statesynctest", + "//ids", + "//utils/units", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//ethdb/memorydb", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/sync/handlers/stats/BUILD.bazel b/graft/coreth/sync/handlers/stats/BUILD.bazel new file mode 100644 index 000000000000..30617e0fd5aa --- /dev/null +++ b/graft/coreth/sync/handlers/stats/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "stats", + srcs = ["stats.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/handlers/stats", + visibility = ["//visibility:public"], + deps = ["@com_github_ava_labs_libevm//metrics"], +) diff --git a/graft/coreth/sync/handlers/stats/statstest/BUILD.bazel b/graft/coreth/sync/handlers/stats/statstest/BUILD.bazel new file mode 100644 index 000000000000..2144c77de828 --- /dev/null +++ b/graft/coreth/sync/handlers/stats/statstest/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "statstest", + srcs = ["test_stats.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/handlers/stats/statstest", + visibility = ["//visibility:public"], + deps = ["//graft/coreth/sync/handlers/stats"], +) diff --git a/graft/coreth/sync/statesync/BUILD.bazel b/graft/coreth/sync/statesync/BUILD.bazel new file mode 100644 index 000000000000..3e42e641fb4c --- /dev/null +++ b/graft/coreth/sync/statesync/BUILD.bazel @@ -0,0 +1,76 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "statesync", + srcs = [ + "code_queue.go", + "code_syncer.go", + "state_syncer.go", + "sync_helpers.go", + "trie_queue.go", + "trie_segments.go", + "trie_sync_stats.go", + "trie_sync_tasks.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/statesync", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core/state/snapshot", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync", + "//graft/coreth/sync/client", + "//graft/coreth/sync/syncutils", + "//graft/coreth/utils", + "//utils/math", + "//utils/timer", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm/options", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@org_golang_x_sync//errgroup", + ], +) + +go_test( + name = "statesync_test", + srcs = [ + "code_queue_test.go", + "code_syncer_test.go", + "sync_test.go", + "trie_sync_stats_test.go", + ], + embed = [":statesync"], + deps = [ + "//graft/coreth/core/state/snapshot", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/plugin/evm/message", + "//graft/coreth/sync/client", + "//graft/coreth/sync/handlers", + "//graft/coreth/sync/handlers/stats", + "//graft/coreth/sync/statesync/statesynctest", + "//utils", + "//utils/set", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//ethdb/memorydb", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//triedb", + "@com_github_google_go_cmp//cmp", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + "@org_uber_go_goleak//:goleak", + ], +) diff --git a/graft/coreth/sync/statesync/statesynctest/BUILD.bazel b/graft/coreth/sync/statesync/statesynctest/BUILD.bazel new file mode 100644 index 000000000000..0a3ce2dea341 --- /dev/null +++ b/graft/coreth/sync/statesync/statesynctest/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "statesynctest", + srcs = [ + "test_sync.go", + "test_trie.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/statesync/statesynctest", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils/utilstest", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/sync/syncutils/BUILD.bazel b/graft/coreth/sync/syncutils/BUILD.bazel new file mode 100644 index 000000000000..0435a53e709a --- /dev/null +++ b/graft/coreth/sync/syncutils/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "syncutils", + srcs = ["iterators.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/sync/syncutils", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core/state/snapshot", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + ], +) diff --git a/graft/coreth/tests/BUILD.bazel b/graft/coreth/tests/BUILD.bazel new file mode 100644 index 000000000000..dfe1bcf2c275 --- /dev/null +++ b/graft/coreth/tests/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "tests", + srcs = [ + "init.go", + "rlp_test_util.go", + "state_test_util.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/tests", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/core/extstate", + "//graft/coreth/core/state/snapshot", + "//graft/coreth/params", + "//graft/coreth/params/extras", + "//graft/coreth/plugin/evm/customrawdb", + "//graft/coreth/triedb/firewood", + "//graft/coreth/triedb/hashdb", + "//graft/coreth/triedb/pathdb", + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + ], +) diff --git a/graft/coreth/tests/utils/BUILD.bazel b/graft/coreth/tests/utils/BUILD.bazel new file mode 100644 index 000000000000..194bfa9f162b --- /dev/null +++ b/graft/coreth/tests/utils/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "utils", + srcs = [ + "command.go", + "constants.go", + "proposervm.go", + "subnet.go", + "tmpnet.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/tests/utils", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//config", + "//graft/coreth/accounts/abi/bind", + "//graft/coreth/ethclient", + "//graft/coreth/plugin/evm/upgrade/ap1", + "//tests/fixture/tmpnet", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//params", + "@com_github_go_cmd_cmd//:go_default_library", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/tests/warp/BUILD.bazel b/graft/coreth/tests/warp/BUILD.bazel new file mode 100644 index 000000000000..7c98b820e44e --- /dev/null +++ b/graft/coreth/tests/warp/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "warp_test", + srcs = ["warp_test.go"], + deps = [ + "//api/info", + "//graft/coreth/accounts/abi/bind", + "//graft/coreth/cmd/simulator/key", + "//graft/coreth/cmd/simulator/load", + "//graft/coreth/cmd/simulator/metrics", + "//graft/coreth/cmd/simulator/txs", + "//graft/coreth/ethclient", + "//graft/coreth/params", + "//graft/coreth/precompile/contracts/warp", + "//graft/coreth/tests/utils", + "//graft/coreth/warp", + "//ids", + "//snow/validators", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/constants", + "//vms/evm/predicate", + "//vms/platformvm", + "//vms/platformvm/api", + "//vms/platformvm/warp", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/triedb/firewood/BUILD.bazel b/graft/coreth/triedb/firewood/BUILD.bazel new file mode 100644 index 000000000000..064d76fdf2c9 --- /dev/null +++ b/graft/coreth/triedb/firewood/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "firewood", + srcs = [ + "account_trie.go", + "database.go", + "storage_trie.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/triedb/firewood", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//trie/triestate", + "@com_github_ava_labs_libevm//triedb", + "@com_github_ava_labs_libevm//triedb/database", + ], +) diff --git a/graft/coreth/triedb/hashdb/BUILD.bazel b/graft/coreth/triedb/hashdb/BUILD.bazel new file mode 100644 index 000000000000..d4e12bc7decf --- /dev/null +++ b/graft/coreth/triedb/hashdb/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "hashdb", + srcs = ["database.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/triedb/hashdb", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/utils", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//trie/triestate", + "@com_github_ava_labs_libevm//triedb", + "@com_github_ava_labs_libevm//triedb/database", + "@com_github_ava_labs_libevm//triedb/hashdb", + ], +) diff --git a/graft/coreth/triedb/pathdb/BUILD.bazel b/graft/coreth/triedb/pathdb/BUILD.bazel new file mode 100644 index 000000000000..88cdc751de9b --- /dev/null +++ b/graft/coreth/triedb/pathdb/BUILD.bazel @@ -0,0 +1,62 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "pathdb", + srcs = [ + "database.go", + "difflayer.go", + "disklayer.go", + "errors.go", + "history.go", + "journal.go", + "layertree.go", + "metrics.go", + "nodebuffer.go", + "testutils.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/triedb/pathdb", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/params", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//libevm/stateconf", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//trie/triestate", + "@com_github_ava_labs_libevm//triedb", + "@com_github_ava_labs_libevm//triedb/database", + "@com_github_ava_labs_libevm//triedb/pathdb", + "@com_github_victoriametrics_fastcache//:fastcache", + "@org_golang_x_crypto//sha3", + "@org_golang_x_exp//slices", + ], +) + +go_test( + name = "pathdb_test", + srcs = [ + "database_test.go", + "difflayer_test.go", + "history_test.go", + ], + embed = [":pathdb"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie/testutil", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//trie/triestate", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/utils/BUILD.bazel b/graft/coreth/utils/BUILD.bazel new file mode 100644 index 000000000000..47e0abccf497 --- /dev/null +++ b/graft/coreth/utils/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utils", + srcs = [ + "address_range.go", + "bounded_workers.go", + "bytes.go", + "denomination.go", + "metered_cache.go", + "numbers.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/utils", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//metrics", + "@com_github_victoriametrics_fastcache//:fastcache", + ], +) + +go_test( + name = "utils_test", + srcs = [ + "bytes_test.go", + "numbers_test.go", + ], + embed = [":utils"], + deps = [ + "//utils", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/utils/rand/BUILD.bazel b/graft/coreth/utils/rand/BUILD.bazel new file mode 100644 index 000000000000..96909cafc019 --- /dev/null +++ b/graft/coreth/utils/rand/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "rand", + srcs = ["rand.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/utils/rand", + visibility = ["//visibility:public"], +) diff --git a/graft/coreth/utils/rpc/BUILD.bazel b/graft/coreth/utils/rpc/BUILD.bazel new file mode 100644 index 000000000000..fe353dcbfb60 --- /dev/null +++ b/graft/coreth/utils/rpc/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "rpc", + srcs = ["handler.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/utils/rpc", + visibility = ["//visibility:public"], + deps = [ + "//utils/json", + "@com_github_gorilla_rpc//v2:rpc", + ], +) diff --git a/graft/coreth/utils/utilstest/BUILD.bazel b/graft/coreth/utils/utilstest/BUILD.bazel new file mode 100644 index 000000000000..18964272189d --- /dev/null +++ b/graft/coreth/utils/utilstest/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "utilstest", + srcs = [ + "context.go", + "key.go", + "pointer.go", + "snow.go", + "timeout.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/utils/utilstest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/snowtest", + "//snow/validators", + "//snow/validators/validatorstest", + "//utils/constants", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//crypto", + "@com_github_stretchr_testify//require", + ], +) diff --git a/graft/coreth/warp/BUILD.bazel b/graft/coreth/warp/BUILD.bazel new file mode 100644 index 000000000000..90dbc0eee1df --- /dev/null +++ b/graft/coreth/warp/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "warp", + srcs = [ + "backend.go", + "client.go", + "service.go", + "verifier_backend.go", + "verifier_stats.go", + ], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/warp", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//database", + "//graft/coreth/precompile/contracts/warp", + "//graft/coreth/rpc", + "//ids", + "//network/p2p/acp118", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//vms/platformvm/warp", + "//vms/platformvm/warp/payload", + "@com_github_ava_labs_libevm//common/hexutil", + "@com_github_ava_labs_libevm//log", + "@com_github_ava_labs_libevm//metrics", + ], +) + +go_test( + name = "warp_test", + srcs = [ + "backend_test.go", + "verifier_backend_test.go", + ], + embed = [":warp"], + deps = [ + "//cache", + "//cache/lru", + "//database", + "//database/memdb", + "//graft/coreth/warp/warptest", + "//ids", + "//network/p2p/acp118", + "//proto/pb/sdk", + "//snow/engine/common", + "//snow/snowtest", + "//utils", + "//utils/crypto/bls/signer/localsigner", + "//vms/evm/metrics/metricstest", + "//vms/platformvm/warp", + "//vms/platformvm/warp/payload", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + ], +) diff --git a/graft/coreth/warp/warptest/BUILD.bazel b/graft/coreth/warp/warptest/BUILD.bazel new file mode 100644 index 000000000000..e55f86ebc75a --- /dev/null +++ b/graft/coreth/warp/warptest/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "warptest", + srcs = ["block_client.go"], + importpath = "github.com/ava-labs/avalanchego/graft/coreth/warp/warptest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/snowtest", + ], +) diff --git a/ids/BUILD.bazel b/ids/BUILD.bazel new file mode 100644 index 000000000000..21836bbf5231 --- /dev/null +++ b/ids/BUILD.bazel @@ -0,0 +1,41 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ids", + srcs = [ + "aliases.go", + "bits.go", + "id.go", + "node_id.go", + "request_id.go", + "short.go", + "test_generator.go", + ], + importpath = "github.com/ava-labs/avalanchego/ids", + visibility = ["//visibility:public"], + deps = [ + "//staking", + "//utils", + "//utils/cb58", + "//utils/hashing", + "//utils/wrappers", + ], +) + +go_test( + name = "ids_test", + srcs = [ + "aliases_test.go", + "bits_test.go", + "id_test.go", + "node_id_test.go", + ], + embed = [":ids"], + deps = [ + "//ids/idstest", + "//utils", + "//utils/cb58", + "//utils/hashing", + "@com_github_stretchr_testify//require", + ], +) diff --git a/ids/galiasreader/BUILD.bazel b/ids/galiasreader/BUILD.bazel new file mode 100644 index 000000000000..f22a25281523 --- /dev/null +++ b/ids/galiasreader/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "galiasreader", + srcs = [ + "alias_reader_client.go", + "alias_reader_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/ids/galiasreader", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//proto/pb/aliasreader", + ], +) + +go_test( + name = "galiasreader_test", + srcs = ["alias_reader_test.go"], + embed = [":galiasreader"], + deps = [ + "//ids", + "//ids/idstest", + "//proto/pb/aliasreader", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/ids/idstest/BUILD.bazel b/ids/idstest/BUILD.bazel new file mode 100644 index 000000000000..dc6c2b97e1e0 --- /dev/null +++ b/ids/idstest/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "idstest", + srcs = ["aliases.go"], + importpath = "github.com/ava-labs/avalanchego/ids/idstest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/indexer/BUILD.bazel b/indexer/BUILD.bazel new file mode 100644 index 000000000000..874352d0df93 --- /dev/null +++ b/indexer/BUILD.bazel @@ -0,0 +1,67 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "indexer", + srcs = [ + "client.go", + "codec.go", + "container.go", + "index.go", + "indexer.go", + "service.go", + ], + importpath = "github.com/ava-labs/avalanchego/indexer", + visibility = ["//visibility:public"], + deps = [ + "//api/server", + "//chains", + "//codec", + "//codec/linearcodec", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/constants", + "//utils/formatting", + "//utils/json", + "//utils/logging", + "//utils/rpc", + "//utils/timer/mockable", + "//utils/wrappers", + "@com_github_gorilla_rpc//v2:rpc", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "indexer_test", + srcs = [ + "client_test.go", + "index_test.go", + "indexer_test.go", + ], + embed = [":indexer"], + deps = [ + "//api/server", + "//database/memdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/engine/avalanche/vertex/vertexmock", + "//snow/engine/snowman/block/blockmock", + "//snow/snowtest", + "//utils", + "//utils/formatting", + "//utils/json", + "//utils/logging", + "//utils/rpc", + "//utils/set", + "//utils/timer/mockable", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/indexer/examples/p-chain/BUILD.bazel b/indexer/examples/p-chain/BUILD.bazel new file mode 100644 index 000000000000..0e1bb1df0cbc --- /dev/null +++ b/indexer/examples/p-chain/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "p-chain_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/indexer/examples/p-chain", + visibility = ["//visibility:private"], + deps = [ + "//indexer", + "//utils/constants", + "//vms/platformvm/block", + "//vms/proposervm/block", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "p-chain", + embed = [":p-chain_lib"], + visibility = ["//visibility:public"], +) diff --git a/indexer/examples/x-chain-blocks/BUILD.bazel b/indexer/examples/x-chain-blocks/BUILD.bazel new file mode 100644 index 000000000000..4b6499e3be85 --- /dev/null +++ b/indexer/examples/x-chain-blocks/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "x-chain-blocks_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/indexer/examples/x-chain-blocks", + visibility = ["//visibility:private"], + deps = [ + "//ids", + "//indexer", + "//vms/proposervm/block", + "//wallet/chain/x/builder", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "x-chain-blocks", + embed = [":x-chain-blocks_lib"], + visibility = ["//visibility:public"], +) diff --git a/main/BUILD.bazel b/main/BUILD.bazel new file mode 100644 index 000000000000..ffab5909814b --- /dev/null +++ b/main/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "main_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/main", + visibility = ["//visibility:private"], + deps = [ + "//app", + "//config", + "//graft/coreth/plugin/evm", + "//version", + "@com_github_spf13_pflag//:pflag", + "@org_golang_x_term//:term", + ], +) + +go_binary( + name = "avalanchego", + embed = [":main_lib"], + visibility = ["//visibility:public"], + x_defs = { + "github.com/ava-labs/avalanchego/version.GitCommit": "{STABLE_GIT_COMMIT}", + }, +) diff --git a/message/BUILD.bazel b/message/BUILD.bazel new file mode 100644 index 000000000000..ee88f13b928d --- /dev/null +++ b/message/BUILD.bazel @@ -0,0 +1,50 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "message", + srcs = [ + "creator.go", + "fields.go", + "inbound_msg_builder.go", + "internal_msg_builder.go", + "messages.go", + "ops.go", + "outbound_msg_builder.go", + ], + importpath = "github.com/ava-labs/avalanchego/message", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//proto/pb/p2p", + "//utils/compression", + "//utils/constants", + "//utils/ips", + "//utils/set", + "//utils/timer/mockable", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_test( + name = "message_test", + srcs = [ + "inbound_msg_builder_test.go", + "messages_benchmark_test.go", + "messages_test.go", + "mocks_generate_test.go", + "outbound_msg_builder_test.go", + ], + embed = [":message"], + deps = [ + "//ids", + "//proto/pb/p2p", + "//staking", + "//utils/compression", + "//utils/timer/mockable", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + ], +) diff --git a/message/messagemock/BUILD.bazel b/message/messagemock/BUILD.bazel new file mode 100644 index 000000000000..5a688c9b3c26 --- /dev/null +++ b/message/messagemock/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "messagemock", + srcs = [ + "outbound_message.go", + "outbound_message_builder.go", + ], + importpath = "github.com/ava-labs/avalanchego/message/messagemock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//proto/pb/p2p", + "//utils/ips", + "@org_uber_go_mock//gomock", + ], +) diff --git a/nat/BUILD.bazel b/nat/BUILD.bazel new file mode 100644 index 000000000000..231b98f98319 --- /dev/null +++ b/nat/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "nat", + srcs = [ + "nat.go", + "no_router.go", + "pmp.go", + "upnp.go", + ], + importpath = "github.com/ava-labs/avalanchego/nat", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/ips", + "//utils/logging", + "@com_github_huin_goupnp//:goupnp", + "@com_github_huin_goupnp//dcps/internetgateway1", + "@com_github_huin_goupnp//dcps/internetgateway2", + "@com_github_jackpal_gateway//:gateway", + "@com_github_jackpal_go_nat_pmp//:go-nat-pmp", + "@org_uber_go_zap//:zap", + ], +) diff --git a/network/BUILD.bazel b/network/BUILD.bazel new file mode 100644 index 000000000000..1f08ac14f9ef --- /dev/null +++ b/network/BUILD.bazel @@ -0,0 +1,102 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "network", + srcs = [ + "config.go", + "ip_tracker.go", + "metrics.go", + "network.go", + "no_ingress_conn_alert.go", + "test_network.go", + "tracked_ip.go", + ], + importpath = "github.com/ava-labs/avalanchego/network", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//genesis", + "//ids", + "//message", + "//network/dialer", + "//network/peer", + "//network/throttling", + "//snow/engine/common", + "//snow/networking/router", + "//snow/networking/sender", + "//snow/networking/tracker", + "//snow/uptime", + "//snow/validators", + "//staking", + "//subnets", + "//upgrade", + "//utils", + "//utils/bloom", + "//utils/compression", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/ips", + "//utils/logging", + "//utils/math", + "//utils/math/meter", + "//utils/resource", + "//utils/sampler", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "//version", + "@com_github_pires_go_proxyproto//:go-proxyproto", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "network_test", + srcs = [ + "conn_test.go", + "dialer_test.go", + "example_test.go", + "handler_test.go", + "ip_tracker_test.go", + "listener_test.go", + "network_test.go", + "no_ingress_conn_alert_test.go", + "tracked_ip_test.go", + ], + embed = [":network"], + deps = [ + "//genesis", + "//ids", + "//message", + "//network/dialer", + "//network/peer", + "//network/throttling", + "//snow/engine/common", + "//snow/networking/router", + "//snow/networking/tracker", + "//snow/uptime", + "//snow/validators", + "//staking", + "//subnets", + "//upgrade", + "//utils", + "//utils/bloom", + "//utils/constants", + "//utils/crypto/bls/signer/localsigner", + "//utils/ips", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + "@org_uber_go_zap//:zap", + ], +) diff --git a/network/dialer/BUILD.bazel b/network/dialer/BUILD.bazel new file mode 100644 index 000000000000..4be935bd9bdd --- /dev/null +++ b/network/dialer/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "dialer", + srcs = ["dialer.go"], + importpath = "github.com/ava-labs/avalanchego/network/dialer", + visibility = ["//visibility:public"], + deps = [ + "//network/throttling", + "//utils/logging", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "dialer_test", + srcs = ["dialer_test.go"], + embed = [":dialer"], + deps = [ + "//utils/logging", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/network/p2p/BUILD.bazel b/network/p2p/BUILD.bazel new file mode 100644 index 000000000000..7d39bfe1cc06 --- /dev/null +++ b/network/p2p/BUILD.bazel @@ -0,0 +1,63 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "p2p", + srcs = [ + "client.go", + "error.go", + "handler.go", + "network.go", + "node_sampler.go", + "peer_tracker.go", + "router.go", + "throttler.go", + "throttler_handler.go", + "validators.go", + ], + importpath = "github.com/ava-labs/avalanchego/network/p2p", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow/engine/common", + "//snow/validators", + "//utils", + "//utils/heap", + "//utils/logging", + "//utils/math", + "//utils/sampler", + "//utils/set", + "//utils/timer/mockable", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "p2p_test", + srcs = [ + "handler_test.go", + "network_test.go", + "peer_tracker_test.go", + "throttler_handler_test.go", + "throttler_test.go", + "validators_test.go", + ], + embed = [":p2p"], + deps = [ + "//ids", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/validators", + "//snow/validators/validatorsmock", + "//snow/validators/validatorstest", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/network/p2p/acp118/BUILD.bazel b/network/p2p/acp118/BUILD.bazel new file mode 100644 index 000000000000..d25967d0d7d0 --- /dev/null +++ b/network/p2p/acp118/BUILD.bazel @@ -0,0 +1,51 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "acp118", + srcs = [ + "aggregator.go", + "handler.go", + ], + importpath = "github.com/ava-labs/avalanchego/network/p2p/acp118", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//ids", + "//network/p2p", + "//proto/pb/sdk", + "//snow/engine/common", + "//snow/validators", + "//utils/crypto/bls", + "//utils/logging", + "//utils/set", + "//vms/platformvm/warp", + "@org_golang_google_protobuf//proto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "acp118_test", + srcs = [ + "aggregator_test.go", + "handler_test.go", + ], + embed = [":acp118"], + deps = [ + "//cache", + "//cache/lru", + "//ids", + "//network/p2p", + "//network/p2p/p2ptest", + "//proto/pb/sdk", + "//snow/engine/common", + "//snow/validators", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//utils/set", + "//vms/platformvm/warp", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + ], +) diff --git a/network/p2p/gossip/BUILD.bazel b/network/p2p/gossip/BUILD.bazel new file mode 100644 index 000000000000..0945a2deb708 --- /dev/null +++ b/network/p2p/gossip/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gossip", + srcs = [ + "bloom.go", + "gossip.go", + "gossipable.go", + "handler.go", + "message.go", + "test_gossip.go", + ], + importpath = "github.com/ava-labs/avalanchego/network/p2p/gossip", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//ids", + "//network/p2p", + "//proto/pb/sdk", + "//snow/engine/common", + "//utils/bloom", + "//utils/buffer", + "//utils/logging", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "gossip_test", + srcs = [ + "bloom_test.go", + "gossip_test.go", + ], + embed = [":gossip"], + deps = [ + "//ids", + "//network/p2p", + "//proto/pb/sdk", + "//snow/engine/enginetest", + "//snow/validators", + "//snow/validators/validatorstest", + "//utils/constants", + "//utils/logging", + "//utils/set", + "//utils/units", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + "@org_golang_x_exp//maps", + ], +) diff --git a/network/p2p/p2ptest/BUILD.bazel b/network/p2p/p2ptest/BUILD.bazel new file mode 100644 index 000000000000..abcf4a81dd89 --- /dev/null +++ b/network/p2p/p2ptest/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "p2ptest", + srcs = ["client.go"], + importpath = "github.com/ava-labs/avalanchego/network/p2p/p2ptest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//network/p2p", + "//snow/engine/common", + "//snow/engine/enginetest", + "//utils/logging", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) + +go_test( + name = "p2ptest_test", + srcs = ["client_test.go"], + embed = [":p2ptest"], + deps = [ + "//ids", + "//network/p2p", + "//snow/engine/common", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/network/peer/BUILD.bazel b/network/peer/BUILD.bazel new file mode 100644 index 000000000000..3e51d36df798 --- /dev/null +++ b/network/peer/BUILD.bazel @@ -0,0 +1,95 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "peer", + srcs = [ + "config.go", + "info.go", + "ip.go", + "ip_signer.go", + "message_queue.go", + "metrics.go", + "msg_length.go", + "network.go", + "peer.go", + "set.go", + "test_network.go", + "test_peer.go", + "tls_config.go", + "upgrader.go", + ], + importpath = "github.com/ava-labs/avalanchego/network/peer", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//network/throttling", + "//proto/pb/p2p", + "//snow/networking/router", + "//snow/networking/tracker", + "//snow/uptime", + "//snow/validators", + "//staking", + "//upgrade", + "//utils", + "//utils/bloom", + "//utils/buffer", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/hashing", + "//utils/ips", + "//utils/json", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/sampler", + "//utils/set", + "//utils/timer/mockable", + "//utils/wrappers", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "peer_test", + srcs = [ + "example_test.go", + "ip_signer_test.go", + "ip_test.go", + "message_queue_test.go", + "msg_length_test.go", + "peer_test.go", + "set_test.go", + "tls_config_test.go", + "upgrader_test.go", + ], + embed = [":peer"], + embedsrcs = ["8192RSA_test.pem"], + deps = [ + "//ids", + "//message", + "//network/throttling", + "//snow/networking/router", + "//snow/networking/tracker", + "//snow/uptime", + "//snow/validators", + "//staking", + "//upgrade", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_crypto//ed25519", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/network/throttling/BUILD.bazel b/network/throttling/BUILD.bazel new file mode 100644 index 000000000000..8bd4fbc4eb33 --- /dev/null +++ b/network/throttling/BUILD.bazel @@ -0,0 +1,68 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "throttling", + srcs = [ + "bandwidth_throttler.go", + "common.go", + "dial_throttler.go", + "inbound_conn_throttler.go", + "inbound_conn_upgrade_throttler.go", + "inbound_msg_buffer_throttler.go", + "inbound_msg_byte_throttler.go", + "inbound_msg_throttler.go", + "inbound_resource_throttler.go", + "no_inbound_msg_throttler.go", + "outbound_msg_throttler.go", + "release_func.go", + ], + importpath = "github.com/ava-labs/avalanchego/network/throttling", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow/networking/tracker", + "//snow/validators", + "//utils/constants", + "//utils/linked", + "//utils/logging", + "//utils/metric", + "//utils/set", + "//utils/timer", + "//utils/timer/mockable", + "//utils/wrappers", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_x_time//rate", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "throttling_test", + srcs = [ + "bandwidth_throttler_test.go", + "inbound_conn_throttler_test.go", + "inbound_conn_upgrade_throttler_test.go", + "inbound_msg_buffer_throttler_test.go", + "inbound_msg_byte_throttler_test.go", + "inbound_resource_throttler_test.go", + "outbound_msg_throttler_test.go", + ], + embed = [":throttling"], + deps = [ + "//ids", + "//message", + "//message/messagemock", + "//snow/networking/tracker", + "//snow/networking/tracker/trackermock", + "//snow/validators", + "//utils/constants", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/timer/mockable", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/nix/go/BUILD.bazel b/nix/go/BUILD.bazel new file mode 100644 index 000000000000..7aee75908eee --- /dev/null +++ b/nix/go/BUILD.bazel @@ -0,0 +1,5 @@ +# Make nix files visible to Bazel +exports_files([ + "bazel.nix", + "default.nix", +]) diff --git a/nix/go/bazel.nix b/nix/go/bazel.nix new file mode 100644 index 000000000000..6b7d633227c9 --- /dev/null +++ b/nix/go/bazel.nix @@ -0,0 +1,7 @@ +# Bazel wrapper for Go SDK +# Called by rules_nixpkgs_go to build the Go toolchain +# +# This file re-uses the same Go derivation as nix develop, +# ensuring version consistency between Nix and Bazel environments. +{ pkgs ? import {} }: +import ./default.nix { inherit pkgs; } diff --git a/nix/go/default.nix b/nix/go/default.nix index aa510a23fcc0..e404a05a1341 100644 --- a/nix/go/default.nix +++ b/nix/go/default.nix @@ -53,5 +53,7 @@ pkgs.stdenv.mkDerivation { tar xzf $src -C $out --strip-components=1 --no-same-owner --no-same-permissions # Ensure go binary is executable chmod +x $out/bin/go + # ROOT marker required by rules_go/rules_nixpkgs for SDK identification + touch $out/ROOT ''; } diff --git a/node/BUILD.bazel b/node/BUILD.bazel new file mode 100644 index 000000000000..e032fec10d91 --- /dev/null +++ b/node/BUILD.bazel @@ -0,0 +1,96 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "node", + srcs = [ + "beacon_manager.go", + "insecure_validator_manager.go", + "node.go", + "overridden_manager.go", + ], + importpath = "github.com/ava-labs/avalanchego/node", + visibility = ["//visibility:public"], + deps = [ + "//api/admin", + "//api/health", + "//api/info", + "//api/metrics", + "//api/server", + "//chains", + "//chains/atomic", + "//config/node", + "//database", + "//database/factory", + "//database/leveldb", + "//database/meterdb", + "//database/pebbledb", + "//database/prefixdb", + "//genesis", + "//graft/coreth/plugin/factory", + "//ids", + "//indexer", + "//message", + "//nat", + "//network", + "//network/dialer", + "//network/peer", + "//network/throttling", + "//snow", + "//snow/networking/benchlist", + "//snow/networking/router", + "//snow/networking/timeout", + "//snow/networking/tracker", + "//snow/uptime", + "//snow/validators", + "//staking", + "//trace", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/bls/signer/rpcsigner", + "//utils/dynamicip", + "//utils/filesystem", + "//utils/hashing", + "//utils/ips", + "//utils/logging", + "//utils/math/meter", + "//utils/metric", + "//utils/perms", + "//utils/profiler", + "//utils/resource", + "//utils/set", + "//version", + "//vms", + "//vms/avm", + "//vms/avm/config", + "//vms/platformvm", + "//vms/platformvm/config", + "//vms/platformvm/signer", + "//vms/registry", + "//vms/rpcchainvm/runtime", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/collectors", + "@com_github_prometheus_client_golang//prometheus/promhttp", + "@org_golang_x_exp//maps", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "node_test", + srcs = [ + "beacon_manager_test.go", + "overridden_manager_test.go", + ], + embed = [":node"], + deps = [ + "//ids", + "//snow/networking/router/routermock", + "//snow/validators", + "//utils/constants", + "//version", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/patch1.txt b/patch1.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/patch2.txt b/patch2.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/patches/BUILD.bazel b/patches/BUILD.bazel new file mode 100644 index 000000000000..2f3cf1688bce --- /dev/null +++ b/patches/BUILD.bazel @@ -0,0 +1,6 @@ +# Patch files for external dependencies +exports_files([ + "libevm_secp256k1.patch", + "blst_build.patch", + "firewood_ffi.patch", +]) diff --git a/patches/blst_build.patch b/patches/blst_build.patch new file mode 100644 index 000000000000..e287031c78a4 --- /dev/null +++ b/patches/blst_build.patch @@ -0,0 +1,111 @@ +diff --git a/BUILD.bazel b/BUILD.bazel +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/BUILD.bazel +@@ -0,0 +1,59 @@ ++# Custom BUILD file for github.com/supranational/blst ++# Based on prysmaticlabs/prysm's third_party/blst/blst.BUILD ++ ++load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") ++ ++cc_library( ++ name = "blst", ++ srcs = [ ++ "bindings/blst.h", ++ "bindings/blst_aux.h", ++ ], ++ hdrs = [ ++ "bindings/blst.h", ++ "bindings/blst_aux.h", ++ ], ++ deps = [ ++ ":src", ++ ":asm", ++ ], ++ strip_include_prefix = "bindings", ++ visibility = ["//visibility:public"], ++) ++ ++cc_library( ++ name = "asm_hdrs", ++ hdrs = glob([ ++ "build/**/*.s", ++ "build/**/*.S", ++ ], exclude = ["build/assembly.s"]), ++) ++ ++cc_library( ++ name = "asm", ++ srcs = ["build/assembly.S"], ++ copts = [ ++ "-O2", ++ "-D__BLST_PORTABLE__", ++ ] + select({ ++ "@io_bazel_rules_go//go/platform:amd64": [ ++ "-mno-avx", ++ "-D__ADX__", ++ ], ++ "//conditions:default": [], ++ }), ++ deps = [":asm_hdrs"], ++ linkstatic = True, ++) ++ ++cc_library( ++ name = "hdrs", ++ hdrs = glob(["src/*.c", "src/*.h"], exclude = ["src/client_*.c"]), ++ strip_include_prefix = "src", ++) ++ ++cc_library( ++ name = "src", ++ srcs = ["src/server.c"], ++ deps = [":hdrs"], ++) +diff --git a/bindings/go/BUILD.bazel b/bindings/go/BUILD.bazel +new file mode 100644 +index 0000000..abcdef0 +--- /dev/null ++++ b/bindings/go/BUILD.bazel +@@ -0,0 +1,40 @@ ++# Go bindings for blst library ++ ++load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") ++ ++go_library( ++ name = "go", ++ srcs = [ ++ "blst.go", ++ "cgo_server.c", ++ ], ++ cgo = True, ++ copts = [ ++ "-D__BLST_CGO__", ++ "-I../..", ++ "-I../../bindings", ++ "-I../../src", ++ "-O2", ++ "-D__BLST_PORTABLE__", ++ ] + select({ ++ "@io_bazel_rules_go//go/platform:amd64": [ ++ "-mno-avx", ++ "-D__ADX__", ++ ], ++ "//conditions:default": [], ++ }), ++ cdeps = ["//:blst"], ++ importpath = "github.com/supranational/blst/bindings/go", ++ visibility = ["//visibility:public"], ++) ++ ++go_test( ++ name = "go_test", ++ srcs = [ ++ "blst_htoc_test.go", ++ "blst_minpk_test.go", ++ "blst_minsig_test.go", ++ ], ++ embed = [":go"], ++ data = glob(["hash_to_curve/*.json"]), ++) diff --git a/patches/firewood_ffi.patch b/patches/firewood_ffi.patch new file mode 100644 index 000000000000..789bc14bf6bc --- /dev/null +++ b/patches/firewood_ffi.patch @@ -0,0 +1,77 @@ +diff --git a/BUILD.bazel b/BUILD.bazel +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/BUILD.bazel +@@ -0,0 +1,71 @@ ++load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") ++ ++# Pre-built static library for firewood FFI ++cc_import( ++ name = "firewood_ffi_lib", ++ static_library = select({ ++ "@io_bazel_rules_go//go/platform:darwin_amd64": "libs/x86_64-apple-darwin/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:darwin_arm64": "libs/aarch64-apple-darwin/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:linux_amd64": "libs/x86_64-unknown-linux-gnu/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:linux_arm64": "libs/aarch64-unknown-linux-gnu/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:android_amd64": "libs/x86_64-unknown-linux-gnu/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:android_arm64": "libs/aarch64-unknown-linux-gnu/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:ios_amd64": "libs/x86_64-apple-darwin/libfirewood_ffi.a", ++ "@io_bazel_rules_go//go/platform:ios_arm64": "libs/aarch64-apple-darwin/libfirewood_ffi.a", ++ }), ++ visibility = ["//visibility:public"], ++) ++ ++# Wrapper cc_library to use with cdeps ++cc_library( ++ name = "firewood_ffi", ++ hdrs = ["firewood.h"], ++ deps = [":firewood_ffi_lib"], ++ linkopts = ["-lm"], ++ visibility = ["//visibility:public"], ++) ++ ++go_library( ++ name = "ffi", ++ srcs = [ ++ "firewood.go", ++ "firewood.h", ++ "iterator.go", ++ "keepalive.go", ++ "maybe.go", ++ "memory.go", ++ "metrics.go", ++ "proofs.go", ++ "proposal.go", ++ "revision.go", ++ ], ++ cgo = True, ++ cdeps = [":firewood_ffi"], ++ importpath = "github.com/ava-labs/firewood-go-ethhash/ffi", ++ visibility = ["//visibility:public"], ++ deps = [ ++ "@com_github_prometheus_client_golang//prometheus", ++ "@com_github_prometheus_client_model//go", ++ "@com_github_prometheus_common//expfmt", ++ ], ++) ++ ++alias( ++ name = "go_default_library", ++ actual = ":ffi", ++ visibility = ["//visibility:public"], ++) ++ ++go_test( ++ name = "ffi_test", ++ srcs = [ ++ "firewood_test.go", ++ "metrics_test.go", ++ "proofs_test.go", ++ ], ++ embed = [":ffi"], ++ deps = [ ++ "@com_github_prometheus_client_model//go", ++ "@com_github_stretchr_testify//require", ++ ], ++) diff --git a/patches/libevm_secp256k1.patch b/patches/libevm_secp256k1.patch new file mode 100644 index 000000000000..a57d377f4880 --- /dev/null +++ b/patches/libevm_secp256k1.patch @@ -0,0 +1,32 @@ +--- a/crypto/secp256k1/BUILD.bazel ++++ b/crypto/secp256k1/BUILD.bazel +@@ -1,5 +1,16 @@ + load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + ++# The secp256k1 Go package uses cgo and #includes C source files directly. ++# These files must be available as textual headers (not compiled separately). ++cc_library( ++ name = "libsecp256k1_hdrs", ++ textual_hdrs = glob([ ++ "libsecp256k1/**/*.c", ++ "libsecp256k1/**/*.h", ++ ]), ++ visibility = ["//visibility:private"], ++) ++ + go_library( + name = "secp256k1", + srcs = [ +@@ -10,11 +21,8 @@ + "scalar_mult_nocgo.go", + "secp256.go", + ], ++ cdeps = [":libsecp256k1_hdrs"], + cgo = True, +- copts = [ +- "-Icrypto/secp256k1/libsecp256k1", +- "-Icrypto/secp256k1/libsecp256k1/src", +- ], + importpath = "github.com/ava-labs/libevm/crypto/secp256k1", + visibility = ["//visibility:public"], + ) diff --git a/proto/aliasreader/BUILD.bazel b/proto/aliasreader/BUILD.bazel new file mode 100644 index 000000000000..fda4a4880a58 --- /dev/null +++ b/proto/aliasreader/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "aliasreader_proto", + srcs = ["aliasreader.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "aliasreader_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/aliasreader", + proto = ":aliasreader_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "aliasreader", + embed = [":aliasreader_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/aliasreader", + visibility = ["//visibility:public"], +) diff --git a/proto/appsender/BUILD.bazel b/proto/appsender/BUILD.bazel new file mode 100644 index 000000000000..55d2d45b13e5 --- /dev/null +++ b/proto/appsender/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "appsender_proto", + srcs = ["appsender.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "appsender_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/appsender", + proto = ":appsender_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "appsender", + embed = [":appsender_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/appsender", + visibility = ["//visibility:public"], +) diff --git a/proto/http/BUILD.bazel b/proto/http/BUILD.bazel new file mode 100644 index 000000000000..703f8b95b4f2 --- /dev/null +++ b/proto/http/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "http_proto", + srcs = ["http.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "http_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http", + proto = ":http_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "http", + embed = [":http_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http", + visibility = ["//visibility:public"], +) diff --git a/proto/http/responsewriter/BUILD.bazel b/proto/http/responsewriter/BUILD.bazel new file mode 100644 index 000000000000..5144f85ed219 --- /dev/null +++ b/proto/http/responsewriter/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "responsewriter_proto", + srcs = ["responsewriter.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "responsewriter_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http/responsewriter", + proto = ":responsewriter_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "responsewriter", + embed = [":responsewriter_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http/responsewriter", + visibility = ["//visibility:public"], +) diff --git a/proto/io/reader/BUILD.bazel b/proto/io/reader/BUILD.bazel new file mode 100644 index 000000000000..1be502e4d8e3 --- /dev/null +++ b/proto/io/reader/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "reader_proto", + srcs = ["reader.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "reader_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/reader", + proto = ":reader_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "reader", + embed = [":reader_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/reader", + visibility = ["//visibility:public"], +) diff --git a/proto/io/writer/BUILD.bazel b/proto/io/writer/BUILD.bazel new file mode 100644 index 000000000000..dfe55d240cfe --- /dev/null +++ b/proto/io/writer/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "writer_proto", + srcs = ["writer.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "writer_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/writer", + proto = ":writer_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "writer", + embed = [":writer_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/writer", + visibility = ["//visibility:public"], +) diff --git a/proto/net/conn/BUILD.bazel b/proto/net/conn/BUILD.bazel new file mode 100644 index 000000000000..575771d41bde --- /dev/null +++ b/proto/net/conn/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "conn_proto", + srcs = ["conn.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "conn_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/net/conn", + proto = ":conn_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "conn", + embed = [":conn_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/net/conn", + visibility = ["//visibility:public"], +) diff --git a/proto/p2p/BUILD.bazel b/proto/p2p/BUILD.bazel new file mode 100644 index 000000000000..d14fbbdba7cf --- /dev/null +++ b/proto/p2p/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "p2p_proto", + srcs = ["p2p.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "p2p_go_proto", + importpath = "github.com/ava-labs/avalanchego/proto/pb/p2p", + proto = ":p2p_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "p2p", + embed = [":p2p_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/p2p", + visibility = ["//visibility:public"], +) diff --git a/proto/pb/aliasreader/BUILD.bazel b/proto/pb/aliasreader/BUILD.bazel new file mode 100644 index 000000000000..22294b61bf75 --- /dev/null +++ b/proto/pb/aliasreader/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "aliasreader", + srcs = [ + "aliasreader.pb.go", + "aliasreader_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/aliasreader", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/appsender/BUILD.bazel b/proto/pb/appsender/BUILD.bazel new file mode 100644 index 000000000000..60fcd8cf196c --- /dev/null +++ b/proto/pb/appsender/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "appsender", + srcs = [ + "appsender.pb.go", + "appsender_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/appsender", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/http/BUILD.bazel b/proto/pb/http/BUILD.bazel new file mode 100644 index 000000000000..335c16647d38 --- /dev/null +++ b/proto/pb/http/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "http", + srcs = [ + "http.pb.go", + "http_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/http/responsewriter/BUILD.bazel b/proto/pb/http/responsewriter/BUILD.bazel new file mode 100644 index 000000000000..8863081ed578 --- /dev/null +++ b/proto/pb/http/responsewriter/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "responsewriter", + srcs = [ + "responsewriter.pb.go", + "responsewriter_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/http/responsewriter", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/io/reader/BUILD.bazel b/proto/pb/io/reader/BUILD.bazel new file mode 100644 index 000000000000..e74407b8e023 --- /dev/null +++ b/proto/pb/io/reader/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "reader", + srcs = [ + "reader.pb.go", + "reader_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/reader", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/io/writer/BUILD.bazel b/proto/pb/io/writer/BUILD.bazel new file mode 100644 index 000000000000..1fa595973262 --- /dev/null +++ b/proto/pb/io/writer/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "writer", + srcs = [ + "writer.pb.go", + "writer_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/io/writer", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/net/conn/BUILD.bazel b/proto/pb/net/conn/BUILD.bazel new file mode 100644 index 000000000000..c08b95680840 --- /dev/null +++ b/proto/pb/net/conn/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "conn", + srcs = [ + "conn.pb.go", + "conn_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/net/conn", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/p2p/BUILD.bazel b/proto/pb/p2p/BUILD.bazel new file mode 100644 index 000000000000..435c6663121c --- /dev/null +++ b/proto/pb/p2p/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "p2p", + srcs = ["p2p.pb.go"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/p2p", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/platformvm/BUILD.bazel b/proto/pb/platformvm/BUILD.bazel new file mode 100644 index 000000000000..44b0af4d4828 --- /dev/null +++ b/proto/pb/platformvm/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "platformvm", + srcs = ["platformvm.pb.go"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/platformvm", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/rpcdb/BUILD.bazel b/proto/pb/rpcdb/BUILD.bazel new file mode 100644 index 000000000000..b05c475c8ec4 --- /dev/null +++ b/proto/pb/rpcdb/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "rpcdb", + srcs = [ + "rpcdb.pb.go", + "rpcdb_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/rpcdb", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/sdk/BUILD.bazel b/proto/pb/sdk/BUILD.bazel new file mode 100644 index 000000000000..4a2011993817 --- /dev/null +++ b/proto/pb/sdk/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sdk", + srcs = ["sdk.pb.go"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sdk", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/sharedmemory/BUILD.bazel b/proto/pb/sharedmemory/BUILD.bazel new file mode 100644 index 000000000000..f4b7c4707fb1 --- /dev/null +++ b/proto/pb/sharedmemory/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sharedmemory", + srcs = [ + "sharedmemory.pb.go", + "sharedmemory_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sharedmemory", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/signer/BUILD.bazel b/proto/pb/signer/BUILD.bazel new file mode 100644 index 000000000000..65617f70e380 --- /dev/null +++ b/proto/pb/signer/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "signer", + srcs = [ + "signer.pb.go", + "signer_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/signer", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/sync/BUILD.bazel b/proto/pb/sync/BUILD.bazel new file mode 100644 index 000000000000..012fdb5e0ed6 --- /dev/null +++ b/proto/pb/sync/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sync", + srcs = ["sync.pb.go"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sync", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/pb/validatorstate/BUILD.bazel b/proto/pb/validatorstate/BUILD.bazel new file mode 100644 index 000000000000..c1c89e1a35df --- /dev/null +++ b/proto/pb/validatorstate/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "validatorstate", + srcs = [ + "validator_state.pb.go", + "validator_state_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/validatorstate", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/vm/BUILD.bazel b/proto/pb/vm/BUILD.bazel new file mode 100644 index 000000000000..b14227a52ced --- /dev/null +++ b/proto/pb/vm/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vm", + srcs = [ + "vm.pb.go", + "vm_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm", + visibility = ["//visibility:public"], + deps = [ + "@com_github_prometheus_client_model//go", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/durationpb", + "@org_golang_google_protobuf//types/known/emptypb", + "@org_golang_google_protobuf//types/known/timestamppb", + ], +) diff --git a/proto/pb/vm/runtime/BUILD.bazel b/proto/pb/vm/runtime/BUILD.bazel new file mode 100644 index 000000000000..eef8f460f00b --- /dev/null +++ b/proto/pb/vm/runtime/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "runtime", + srcs = [ + "runtime.pb.go", + "runtime_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm/runtime", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/proto/pb/warp/BUILD.bazel b/proto/pb/warp/BUILD.bazel new file mode 100644 index 000000000000..56c68c85e317 --- /dev/null +++ b/proto/pb/warp/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "warp", + srcs = [ + "message.pb.go", + "message_grpc.pb.go", + ], + importpath = "github.com/ava-labs/avalanchego/proto/pb/warp", + visibility = ["//visibility:public"], + deps = [ + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//runtime/protoimpl", + ], +) diff --git a/proto/platformvm/BUILD.bazel b/proto/platformvm/BUILD.bazel new file mode 100644 index 000000000000..835c36a23af6 --- /dev/null +++ b/proto/platformvm/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "platformvm_proto", + srcs = ["platformvm.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "platformvm_go_proto", + importpath = "github.com/ava-labs/avalanchego/proto/pb/platformvm", + proto = ":platformvm_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "platformvm", + embed = [":platformvm_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/platformvm", + visibility = ["//visibility:public"], +) diff --git a/proto/rpcdb/BUILD.bazel b/proto/rpcdb/BUILD.bazel new file mode 100644 index 000000000000..48c5bfb6e2f7 --- /dev/null +++ b/proto/rpcdb/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "rpcdb_proto", + srcs = ["rpcdb.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "rpcdb_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/rpcdb", + proto = ":rpcdb_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "rpcdb", + embed = [":rpcdb_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/rpcdb", + visibility = ["//visibility:public"], +) diff --git a/proto/sdk/BUILD.bazel b/proto/sdk/BUILD.bazel new file mode 100644 index 000000000000..9a2498566ee1 --- /dev/null +++ b/proto/sdk/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "sdk_proto", + srcs = ["sdk.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "sdk_go_proto", + importpath = "github.com/ava-labs/avalanchego/proto/pb/sdk", + proto = ":sdk_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "sdk", + embed = [":sdk_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sdk", + visibility = ["//visibility:public"], +) diff --git a/proto/sharedmemory/BUILD.bazel b/proto/sharedmemory/BUILD.bazel new file mode 100644 index 000000000000..7dd153f9af96 --- /dev/null +++ b/proto/sharedmemory/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "sharedmemory_proto", + srcs = ["sharedmemory.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "sharedmemory_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sharedmemory", + proto = ":sharedmemory_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "sharedmemory", + embed = [":sharedmemory_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sharedmemory", + visibility = ["//visibility:public"], +) diff --git a/proto/signer/BUILD.bazel b/proto/signer/BUILD.bazel new file mode 100644 index 000000000000..ef366fd24c60 --- /dev/null +++ b/proto/signer/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "signer_proto", + srcs = ["signer.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "signer_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/signer", + proto = ":signer_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "signer", + embed = [":signer_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/signer", + visibility = ["//visibility:public"], +) diff --git a/proto/sync/BUILD.bazel b/proto/sync/BUILD.bazel new file mode 100644 index 000000000000..023157b86689 --- /dev/null +++ b/proto/sync/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "sync_proto", + srcs = ["sync.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "sync_go_proto", + importpath = "github.com/ava-labs/avalanchego/proto/pb/sync", + proto = ":sync_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "sync", + embed = [":sync_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/sync", + visibility = ["//visibility:public"], +) diff --git a/proto/validatorstate/BUILD.bazel b/proto/validatorstate/BUILD.bazel new file mode 100644 index 000000000000..c8b0ed9d063b --- /dev/null +++ b/proto/validatorstate/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "validatorstate_proto", + srcs = ["validator_state.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "validatorstate_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/validatorstate", + proto = ":validatorstate_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "validatorstate", + embed = [":validatorstate_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/validatorstate", + visibility = ["//visibility:public"], +) diff --git a/proto/vm/BUILD.bazel b/proto/vm/BUILD.bazel new file mode 100644 index 000000000000..df641e7e9886 --- /dev/null +++ b/proto/vm/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "vm_proto", + srcs = ["vm.proto"], + visibility = ["//visibility:public"], + deps = [ + "//io/prometheus/client:client_proto", + "@com_google_protobuf//:duration_proto", + "@com_google_protobuf//:empty_proto", + "@com_google_protobuf//:timestamp_proto", + ], +) + +go_proto_library( + name = "vm_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm", + proto = ":vm_proto", + visibility = ["//visibility:public"], + deps = ["//io/prometheus/client:metrics_proto"], +) + +go_library( + name = "vm", + embed = [":vm_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm", + visibility = ["//visibility:public"], +) diff --git a/proto/vm/runtime/BUILD.bazel b/proto/vm/runtime/BUILD.bazel new file mode 100644 index 000000000000..522e387e8511 --- /dev/null +++ b/proto/vm/runtime/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "manager_proto", + srcs = ["runtime.proto"], + visibility = ["//visibility:public"], + deps = ["@com_google_protobuf//:empty_proto"], +) + +go_proto_library( + name = "manager_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm/manager", + proto = ":manager_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "manager", + embed = [":manager_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/vm/manager", + visibility = ["//visibility:public"], +) diff --git a/proto/warp/BUILD.bazel b/proto/warp/BUILD.bazel new file mode 100644 index 000000000000..ee8f28cba22b --- /dev/null +++ b/proto/warp/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") +load("@rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "warp_proto", + srcs = ["message.proto"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "warp_go_proto", + compilers = ["@io_bazel_rules_go//proto:go_grpc_v2"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/warp", + proto = ":warp_proto", + visibility = ["//visibility:public"], +) + +go_library( + name = "warp", + embed = [":warp_go_proto"], + importpath = "github.com/ava-labs/avalanchego/proto/pb/warp", + visibility = ["//visibility:public"], +) diff --git a/result b/result new file mode 120000 index 000000000000..defcc64bc372 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/2vjbfwp7hlys3bb54hs7zl8piipkcrry-go-1.24.9 \ No newline at end of file diff --git a/simplex/BUILD.bazel b/simplex/BUILD.bazel new file mode 100644 index 000000000000..6c8bfe93565d --- /dev/null +++ b/simplex/BUILD.bazel @@ -0,0 +1,84 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "simplex", + srcs = [ + "block.canoto.go", + "block.go", + "block_builder.go", + "bls.go", + "codec.go", + "comm.go", + "config.go", + "messages.go", + "qc.canoto.go", + "qc.go", + "storage.canoto.go", + "storage.go", + ], + importpath = "github.com/ava-labs/avalanchego/simplex", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//database", + "//ids", + "//message", + "//proto/pb/p2p", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//snow/networking/sender", + "//snow/validators", + "//subnets", + "//utils", + "//utils/crypto/bls", + "//utils/hashing", + "//utils/logging", + "//utils/set", + "//utils/tree", + "//vms/platformvm/warp", + "@com_github_ava_labs_simplex//:simplex", + "@com_github_stephenbuttolph_canoto//:canoto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "simplex_test", + srcs = [ + "block_builder_test.go", + "block_test.go", + "bls_test.go", + "comm_test.go", + "qc_test.go", + "storage_test.go", + "util_test.go", + ], + embed = [":simplex"], + deps = [ + "//database", + "//database/memdb", + "//ids", + "//message", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/engine/snowman/block/blocktest", + "//snow/networking/sender/sendermock", + "//snow/snowtest", + "//snow/validators", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//utils/set", + "@com_github_ava_labs_simplex//:simplex", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stephenbuttolph_canoto//:canoto", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/BUILD.bazel b/snow/BUILD.bazel new file mode 100644 index 000000000000..0f70888a2219 --- /dev/null +++ b/snow/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "snow", + srcs = [ + "acceptor.go", + "context.go", + "decidable.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//chains/atomic", + "//ids", + "//proto/pb/p2p", + "//snow/validators", + "//upgrade", + "//utils", + "//utils/crypto/bls", + "//utils/logging", + "//vms/platformvm/warp", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) diff --git a/snow/choices/BUILD.bazel b/snow/choices/BUILD.bazel new file mode 100644 index 000000000000..9066a7833b59 --- /dev/null +++ b/snow/choices/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "choices", + srcs = [ + "decidable.go", + "status.go", + "test_decidable.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/choices", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/wrappers", + ], +) + +go_test( + name = "choices_test", + srcs = ["status_test.go"], + embed = [":choices"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/snow/consensus/avalanche/BUILD.bazel b/snow/consensus/avalanche/BUILD.bazel new file mode 100644 index 000000000000..8806c3557a16 --- /dev/null +++ b/snow/consensus/avalanche/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "avalanche", + srcs = [ + "test_vertex.go", + "vertex.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/avalanche", + visibility = ["//visibility:public"], + deps = [ + "//snow/choices", + "//snow/consensus/snowstorm", + ], +) diff --git a/snow/consensus/snowball/BUILD.bazel b/snow/consensus/snowball/BUILD.bazel new file mode 100644 index 000000000000..5b41e29a1262 --- /dev/null +++ b/snow/consensus/snowball/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "snowball", + srcs = [ + "binary_slush.go", + "binary_snowball.go", + "binary_snowflake.go", + "consensus.go", + "factory.go", + "flat.go", + "nnary_slush.go", + "nnary_snowball.go", + "nnary_snowflake.go", + "parameters.go", + "tree.go", + "unary_snowball.go", + "unary_snowflake.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowball", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/bag", + ], +) + +go_test( + name = "snowball_test", + srcs = [ + "binary_snowball_test.go", + "binary_snowflake_test.go", + "consensus_performance_test.go", + "consensus_reversibility_test.go", + "consensus_test.go", + "flat_test.go", + "network_test.go", + "nnary_snowball_test.go", + "nnary_snowflake_test.go", + "parameters_test.go", + "snowflake_test.go", + "tree_test.go", + "unary_snowball_test.go", + "unary_snowflake_test.go", + ], + embed = [":snowball"], + deps = [ + "//ids", + "//utils/bag", + "//utils/sampler", + "@com_github_stretchr_testify//require", + "@org_gonum_v1_gonum//mathext/prng", + ], +) diff --git a/snow/consensus/snowman/BUILD.bazel b/snow/consensus/snowman/BUILD.bazel new file mode 100644 index 000000000000..384abecc8c1d --- /dev/null +++ b/snow/consensus/snowman/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "snowman", + srcs = [ + "block.go", + "consensus.go", + "factory.go", + "metrics.go", + "oracle_block.go", + "snowman_block.go", + "topological.go", + "traced_consensus.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowman", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//ids", + "//snow", + "//snow/consensus/snowball", + "//trace", + "//utils/bag", + "//utils/linked", + "//utils/logging", + "//utils/metric", + "//utils/set", + "//utils/wrappers", + "@com_github_prometheus_client_golang//prometheus", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "snowman_test", + srcs = [ + "consensus_test.go", + "mixed_test.go", + "mocks_generate_test.go", + "network_test.go", + "topological_test.go", + ], + embed = [":snowman"], + deps = [ + "//ids", + "//snow/consensus/snowball", + "//snow/consensus/snowman/snowmantest", + "//snow/snowtest", + "//utils", + "//utils/bag", + "//utils/sampler", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_gonum_v1_gonum//mathext/prng", + ], +) diff --git a/snow/consensus/snowman/bootstrapper/BUILD.bazel b/snow/consensus/snowman/bootstrapper/BUILD.bazel new file mode 100644 index 000000000000..ba14cbbc6693 --- /dev/null +++ b/snow/consensus/snowman/bootstrapper/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bootstrapper", + srcs = [ + "majority.go", + "minority.go", + "noop.go", + "poll.go", + "requests.go", + "sampler.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowman/bootstrapper", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/logging", + "//utils/math", + "//utils/sampler", + "//utils/set", + "@org_golang_x_exp//maps", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "bootstrapper_test", + srcs = [ + "majority_test.go", + "minority_test.go", + "noop_test.go", + "poll_test.go", + "sampler_test.go", + ], + embed = [":bootstrapper"], + deps = [ + "//ids", + "//utils/logging", + "//utils/math", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/consensus/snowman/poll/BUILD.bazel b/snow/consensus/snowman/poll/BUILD.bazel new file mode 100644 index 000000000000..595f170f4aea --- /dev/null +++ b/snow/consensus/snowman/poll/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "poll", + srcs = [ + "early_term_traversal.go", + "graph.go", + "interfaces.go", + "prefix.go", + "set.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowman/poll", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/bag", + "//utils/formatting", + "//utils/linked", + "//utils/logging", + "//utils/metric", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "poll_test", + srcs = [ + "early_term_traversal_test.go", + "graph_test.go", + "prefix_test.go", + "set_test.go", + ], + embed = [":poll"], + deps = [ + "//ids", + "//utils/bag", + "//utils/logging", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/consensus/snowman/snowmanmock/BUILD.bazel b/snow/consensus/snowman/snowmanmock/BUILD.bazel new file mode 100644 index 000000000000..1fdcdc00d6c4 --- /dev/null +++ b/snow/consensus/snowman/snowmanmock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "snowmanmock", + srcs = ["block.go"], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowman/snowmanmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/consensus/snowman/snowmantest/BUILD.bazel b/snow/consensus/snowman/snowmantest/BUILD.bazel new file mode 100644 index 000000000000..df2ffb36de3f --- /dev/null +++ b/snow/consensus/snowman/snowmantest/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "snowmantest", + srcs = [ + "block.go", + "engine.go", + "require.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowman/snowmantest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow/snowtest", + "//upgrade", + "//utils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/consensus/snowstorm/BUILD.bazel b/snow/consensus/snowstorm/BUILD.bazel new file mode 100644 index 000000000000..264e5b9ee67e --- /dev/null +++ b/snow/consensus/snowstorm/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "snowstorm", + srcs = [ + "test_tx.go", + "tx.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/consensus/snowstorm", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/choices", + "//utils/set", + ], +) diff --git a/snow/engine/avalanche/BUILD.bazel b/snow/engine/avalanche/BUILD.bazel new file mode 100644 index 000000000000..fceae5dcff15 --- /dev/null +++ b/snow/engine/avalanche/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "avalanche", + srcs = ["engine.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche", + visibility = ["//visibility:public"], + deps = [ + "//snow", + "//snow/engine/common", + ], +) diff --git a/snow/engine/avalanche/bootstrap/BUILD.bazel b/snow/engine/avalanche/bootstrap/BUILD.bazel new file mode 100644 index 000000000000..3d16c14fa41d --- /dev/null +++ b/snow/engine/avalanche/bootstrap/BUILD.bazel @@ -0,0 +1,66 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bootstrap", + srcs = [ + "bootstrapper.go", + "config.go", + "metrics.go", + "tx_job.go", + "vertex_job.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/bootstrap", + visibility = ["//visibility:public"], + deps = [ + "//cache/lru", + "//ids", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/choices", + "//snow/consensus/avalanche", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/bootstrap/queue", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//utils/bimap", + "//utils/heap", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "bootstrap_test", + srcs = ["bootstrapper_test.go"], + embed = [":bootstrap"], + deps = [ + "//database/memdb", + "//database/prefixdb", + "//ids", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/choices", + "//snow/consensus/avalanche", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/bootstrap/queue", + "//snow/engine/avalanche/getter", + "//snow/engine/avalanche/vertex/vertextest", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/snowtest", + "//snow/validators", + "//utils/constants", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/avalanche/bootstrap/queue/BUILD.bazel b/snow/engine/avalanche/bootstrap/queue/BUILD.bazel new file mode 100644 index 000000000000..d54571a0432c --- /dev/null +++ b/snow/engine/avalanche/bootstrap/queue/BUILD.bazel @@ -0,0 +1,50 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "queue", + srcs = [ + "job.go", + "jobs.go", + "parser.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/bootstrap/queue", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//database", + "//database/linkeddb", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/engine/common", + "//utils/metric", + "//utils/set", + "//utils/timer", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "queue_test", + srcs = [ + "job_test.go", + "jobs_test.go", + "parser_test.go", + ], + embed = [":queue"], + deps = [ + "//database", + "//database/memdb", + "//ids", + "//snow/engine/common", + "//snow/snowtest", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/avalanche/getter/BUILD.bazel b/snow/engine/avalanche/getter/BUILD.bazel new file mode 100644 index 000000000000..60a8c0ffd0b0 --- /dev/null +++ b/snow/engine/avalanche/getter/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "getter", + srcs = ["getter.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/getter", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow/choices", + "//snow/consensus/avalanche", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//utils/constants", + "//utils/logging", + "//utils/metric", + "//utils/set", + "//utils/wrappers", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) diff --git a/snow/engine/avalanche/state/BUILD.bazel b/snow/engine/avalanche/state/BUILD.bazel new file mode 100644 index 000000000000..467d58f56ee5 --- /dev/null +++ b/snow/engine/avalanche/state/BUILD.bazel @@ -0,0 +1,48 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "state", + srcs = [ + "prefixed_state.go", + "serializer.go", + "state.go", + "unique_vertex.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/state", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//database", + "//database/versiondb", + "//ids", + "//snow/choices", + "//snow/consensus/avalanche", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//utils/formatting", + "//utils/hashing", + "//utils/logging", + "//utils/math", + "//utils/set", + "//utils/wrappers", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "state_test", + srcs = ["unique_vertex_test.go"], + embed = [":state"], + deps = [ + "//database/memdb", + "//ids", + "//snow/choices", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//snow/engine/avalanche/vertex/vertextest", + "//utils/hashing", + "//utils/logging", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/avalanche/vertex/BUILD.bazel b/snow/engine/avalanche/vertex/BUILD.bazel new file mode 100644 index 000000000000..82520429e0b9 --- /dev/null +++ b/snow/engine/avalanche/vertex/BUILD.bazel @@ -0,0 +1,45 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "vertex", + srcs = [ + "builder.go", + "codec.go", + "manager.go", + "parser.go", + "stateless_vertex.go", + "storage.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//codec/reflectcodec", + "//ids", + "//snow/consensus/avalanche", + "//snow/consensus/snowstorm", + "//snow/engine/snowman/block", + "//utils", + "//utils/hashing", + "//utils/units", + "//vms/components/verify", + ], +) + +go_test( + name = "vertex_test", + srcs = [ + "builder_test.go", + "mocks_generate_test.go", + "parser_test.go", + "stateless_vertex_test.go", + ], + embed = [":vertex"], + deps = [ + "//codec", + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/avalanche/vertex/vertexmock/BUILD.bazel b/snow/engine/avalanche/vertex/vertexmock/BUILD.bazel new file mode 100644 index 000000000000..7bcb9058de4d --- /dev/null +++ b/snow/engine/avalanche/vertex/vertexmock/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vertexmock", + srcs = ["linearizable_vm.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex/vertexmock", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowstorm", + "//snow/engine/common", + "//version", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/engine/avalanche/vertex/vertextest/BUILD.bazel b/snow/engine/avalanche/vertex/vertextest/BUILD.bazel new file mode 100644 index 000000000000..acbccb0f1feb --- /dev/null +++ b/snow/engine/avalanche/vertex/vertextest/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vertextest", + srcs = [ + "builder.go", + "manager.go", + "parser.go", + "storage.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex/vertextest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/avalanche", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//snow/engine/snowman/block/blocktest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/common/BUILD.bazel b/snow/engine/common/BUILD.bazel new file mode 100644 index 000000000000..49189c314beb --- /dev/null +++ b/snow/engine/common/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "common", + srcs = [ + "bootstrap_tracker.go", + "bootstrapable.go", + "engine.go", + "error.go", + "fx.go", + "halter.go", + "message.go", + "no_ops_handlers.go", + "notifier.go", + "request.go", + "sender.go", + "state_syncer.go", + "timer.go", + "traced_bootstrapable_engine.go", + "traced_engine.go", + "traced_state_syncer.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/common", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//database", + "//ids", + "//message", + "//snow", + "//snow/validators", + "//trace", + "//utils/logging", + "//utils/set", + "//version", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "common_test", + srcs = [ + "error_test.go", + "mocks_generate_test.go", + "notifier_test.go", + "request_test.go", + "timer_test.go", + ], + embed = [":common"], + deps = [ + "//ids", + "//utils/logging", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/common/appsender/BUILD.bazel b/snow/engine/common/appsender/BUILD.bazel new file mode 100644 index 000000000000..e919d3752c5a --- /dev/null +++ b/snow/engine/common/appsender/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "appsender", + srcs = [ + "appsender_client.go", + "appsender_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/common/appsender", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//proto/pb/appsender", + "//snow/engine/common", + "//utils/set", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/snow/engine/common/commonmock/BUILD.bazel b/snow/engine/common/commonmock/BUILD.bazel new file mode 100644 index 000000000000..2acd91b2dd8e --- /dev/null +++ b/snow/engine/common/commonmock/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "commonmock", + srcs = ["sender.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/common/commonmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/engine/common", + "//utils/set", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/engine/common/tracker/BUILD.bazel b/snow/engine/common/tracker/BUILD.bazel new file mode 100644 index 000000000000..760fe9758795 --- /dev/null +++ b/snow/engine/common/tracker/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "tracker", + srcs = [ + "accepted.go", + "peers.go", + "startup.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/common/tracker", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "//utils/crypto/bls", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "tracker_test", + srcs = [ + "accepted_test.go", + "peers_test.go", + ], + embed = [":tracker"], + deps = [ + "//ids", + "//version", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/enginetest/BUILD.bazel b/snow/engine/enginetest/BUILD.bazel new file mode 100644 index 000000000000..2dc5150f9cb3 --- /dev/null +++ b/snow/engine/enginetest/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "enginetest", + srcs = [ + "bootstrap_tracker.go", + "bootstrapper.go", + "engine.go", + "sender.go", + "timer.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/enginetest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/engine/common", + "//utils/set", + "//version", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/BUILD.bazel b/snow/engine/snowman/BUILD.bazel new file mode 100644 index 000000000000..872caad24f03 --- /dev/null +++ b/snow/engine/snowman/BUILD.bazel @@ -0,0 +1,75 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "snowman", + srcs = [ + "config.go", + "engine.go", + "issuer.go", + "memory_block.go", + "metrics.go", + "voter.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//ids", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowball", + "//snow/consensus/snowman", + "//snow/consensus/snowman/poll", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/snowman/ancestor", + "//snow/engine/snowman/block", + "//snow/engine/snowman/job", + "//snow/validators", + "//utils/bag", + "//utils/bimap", + "//utils/constants", + "//utils/logging", + "//utils/math", + "//utils/metric", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "snowman_test", + srcs = [ + "config_test.go", + "engine_test.go", + ], + embed = [":snowman"], + deps = [ + "//cache", + "//cache/lru", + "//database", + "//ids", + "//snow", + "//snow/consensus/snowball", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/ancestor", + "//snow/engine/snowman/block/blocktest", + "//snow/engine/snowman/getter", + "//snow/snowtest", + "//snow/validators", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/ancestor/BUILD.bazel b/snow/engine/snowman/ancestor/BUILD.bazel new file mode 100644 index 000000000000..91c69a8a12aa --- /dev/null +++ b/snow/engine/snowman/ancestor/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ancestor", + srcs = ["tree.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/ancestor", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/set", + ], +) + +go_test( + name = "ancestor_test", + srcs = ["tree_test.go"], + embed = [":ancestor"], + deps = [ + "//ids", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/block/BUILD.bazel b/snow/engine/snowman/block/BUILD.bazel new file mode 100644 index 000000000000..84cf203e94d7 --- /dev/null +++ b/snow/engine/snowman/block/BUILD.bazel @@ -0,0 +1,50 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "block", + srcs = [ + "batched_vm.go", + "block_context_vm.canoto.go", + "block_context_vm.go", + "notifier.go", + "state_summary.go", + "state_sync_mode.go", + "state_syncable_vm.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/block", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//utils/logging", + "//utils/wrappers", + "@com_github_stephenbuttolph_canoto//:canoto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "block_test", + srcs = [ + "batched_vm_test.go", + "mocks_generate_test.go", + "notifier_test.go", + ], + embed = [":block"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/snowman/block/blockmock", + "//snow/engine/snowman/block/blocktest", + "//utils/logging", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/engine/snowman/block/blockmock/BUILD.bazel b/snow/engine/snowman/block/blockmock/BUILD.bazel new file mode 100644 index 000000000000..0443fc420a60 --- /dev/null +++ b/snow/engine/snowman/block/blockmock/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "blockmock", + srcs = [ + "build_block_with_context_chain_vm.go", + "chain_vm.go", + "full_vm.go", + "state_syncable_vm.go", + "with_verify_context.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/block/blockmock", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//version", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/engine/snowman/block/blocktest/BUILD.bazel b/snow/engine/snowman/block/blocktest/BUILD.bazel new file mode 100644 index 000000000000..1d69385d9cb0 --- /dev/null +++ b/snow/engine/snowman/block/blocktest/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "blocktest", + srcs = [ + "batched_vm.go", + "set_preference_vm.go", + "state_summary.go", + "state_syncable_vm.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/block/blocktest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/bootstrap/BUILD.bazel b/snow/engine/snowman/bootstrap/BUILD.bazel new file mode 100644 index 000000000000..328f58efd1ed --- /dev/null +++ b/snow/engine/snowman/bootstrap/BUILD.bazel @@ -0,0 +1,69 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bootstrap", + srcs = [ + "acceptor.go", + "bootstrapper.go", + "config.go", + "metrics.go", + "storage.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/bootstrap", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//genesis", + "//ids", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowman/bootstrapper", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/snowman/block", + "//snow/engine/snowman/bootstrap/interval", + "//snow/validators", + "//utils/bimap", + "//utils/logging", + "//utils/set", + "//utils/timer", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "bootstrap_test", + srcs = [ + "bootstrapper_test.go", + "storage_test.go", + ], + embed = [":bootstrap"], + deps = [ + "//database", + "//database/memdb", + "//ids", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/engine/snowman/block/blocktest", + "//snow/engine/snowman/bootstrap/interval", + "//snow/engine/snowman/getter", + "//snow/snowtest", + "//snow/validators", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/bootstrap/interval/BUILD.bazel b/snow/engine/snowman/bootstrap/interval/BUILD.bazel new file mode 100644 index 000000000000..dcc48d27f7aa --- /dev/null +++ b/snow/engine/snowman/bootstrap/interval/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "interval", + srcs = [ + "blocks.go", + "interval.go", + "state.go", + "tree.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/bootstrap/interval", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@com_github_google_btree//:btree", + ], +) + +go_test( + name = "interval_test", + srcs = [ + "blocks_test.go", + "interval_test.go", + "tree_test.go", + ], + embed = [":interval"], + deps = [ + "//database", + "//database/memdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/engine/snowman/getter/BUILD.bazel b/snow/engine/snowman/getter/BUILD.bazel new file mode 100644 index 000000000000..9af100dcb891 --- /dev/null +++ b/snow/engine/snowman/getter/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "getter", + srcs = ["getter.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/getter", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/constants", + "//utils/logging", + "//utils/metric", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "getter_test", + srcs = ["getter_test.go"], + embed = [":getter"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/engine/snowman/block/blockmock", + "//snow/engine/snowman/block/blocktest", + "//utils/logging", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/engine/snowman/job/BUILD.bazel b/snow/engine/snowman/job/BUILD.bazel new file mode 100644 index 000000000000..84d687c30f01 --- /dev/null +++ b/snow/engine/snowman/job/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "job", + srcs = ["scheduler.go"], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/job", + visibility = ["//visibility:public"], +) + +go_test( + name = "job_test", + srcs = ["scheduler_test.go"], + embed = [":job"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/snow/engine/snowman/syncer/BUILD.bazel b/snow/engine/snowman/syncer/BUILD.bazel new file mode 100644 index 000000000000..0cda727e4854 --- /dev/null +++ b/snow/engine/snowman/syncer/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "syncer", + srcs = [ + "config.go", + "state_syncer.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/engine/snowman/syncer", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//proto/pb/p2p", + "//snow", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/snowman/block", + "//snow/validators", + "//utils/logging", + "//utils/math", + "//utils/set", + "//version", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "syncer_test", + srcs = [ + "state_syncer_test.go", + "utils_test.go", + ], + embed = [":syncer"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/engine/snowman/block/blocktest", + "//snow/engine/snowman/getter", + "//snow/snowtest", + "//snow/validators", + "//utils/hashing", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/networking/benchlist/BUILD.bazel b/snow/networking/benchlist/BUILD.bazel new file mode 100644 index 000000000000..c925cc2a5b50 --- /dev/null +++ b/snow/networking/benchlist/BUILD.bazel @@ -0,0 +1,40 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "benchlist", + srcs = [ + "benchable.go", + "benchlist.go", + "manager.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/benchlist", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//ids", + "//snow", + "//snow/validators", + "//utils/heap", + "//utils/math", + "//utils/set", + "//utils/timer/mockable", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "benchlist_test", + srcs = [ + "benchable_test.go", + "benchlist_test.go", + ], + embed = [":benchlist"], + deps = [ + "//ids", + "//snow/snowtest", + "//snow/validators", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/networking/handler/BUILD.bazel b/snow/networking/handler/BUILD.bazel new file mode 100644 index 000000000000..71e4c3ad3043 --- /dev/null +++ b/snow/networking/handler/BUILD.bazel @@ -0,0 +1,78 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "handler", + srcs = [ + "engine.go", + "handler.go", + "health.go", + "message_queue.go", + "message_queue_metrics.go", + "metrics.go", + "parser.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/handler", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//ids", + "//message", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/snowman/block", + "//snow/networking/tracker", + "//snow/validators", + "//subnets", + "//utils/buffer", + "//utils/logging", + "//utils/metric", + "//utils/set", + "//utils/timer/mockable", + "@com_github_prometheus_client_golang//prometheus", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_golang_x_sync//errgroup", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "handler_test", + srcs = [ + "engine_test.go", + "handler_test.go", + "health_test.go", + "message_queue_test.go", + "mocks_generate_test.go", + ], + embed = [":handler"], + deps = [ + "//ids", + "//message", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowball", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/networking/tracker", + "//snow/networking/tracker/trackermock", + "//snow/snowtest", + "//snow/validators", + "//subnets", + "//utils/constants", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/handler/handlermock/BUILD.bazel b/snow/networking/handler/handlermock/BUILD.bazel new file mode 100644 index 000000000000..6a754f940b3c --- /dev/null +++ b/snow/networking/handler/handlermock/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "handlermock", + srcs = ["handler.go"], + importpath = "github.com/ava-labs/avalanchego/snow/networking/handler/handlermock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//snow/networking/handler", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/router/BUILD.bazel b/snow/networking/router/BUILD.bazel new file mode 100644 index 000000000000..3a86fe339506 --- /dev/null +++ b/snow/networking/router/BUILD.bazel @@ -0,0 +1,76 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "router", + srcs = [ + "chain_router.go", + "chain_router_metrics.go", + "health.go", + "inbound_handler.go", + "router.go", + "traced_router.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/router", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//ids", + "//message", + "//proto/pb/p2p", + "//snow/networking/benchlist", + "//snow/networking/handler", + "//snow/networking/timeout", + "//trace", + "//utils", + "//utils/constants", + "//utils/linked", + "//utils/logging", + "//utils/set", + "//utils/timer/mockable", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "router_test", + srcs = [ + "chain_router_test.go", + "main_test.go", + "mocks_generate_test.go", + ], + embed = [":router"], + deps = [ + "//ids", + "//message", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/networking/benchlist", + "//snow/networking/handler", + "//snow/networking/handler/handlermock", + "//snow/networking/timeout", + "//snow/networking/tracker", + "//snow/snowtest", + "//snow/validators", + "//subnets", + "//tests", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//utils/timer", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/router/routermock/BUILD.bazel b/snow/networking/router/routermock/BUILD.bazel new file mode 100644 index 000000000000..f1a5cf1f8146 --- /dev/null +++ b/snow/networking/router/routermock/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "routermock", + srcs = ["router.go"], + importpath = "github.com/ava-labs/avalanchego/snow/networking/router/routermock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//proto/pb/p2p", + "//snow/networking/handler", + "//snow/networking/router", + "//snow/networking/timeout", + "//utils/logging", + "//utils/set", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/sender/BUILD.bazel b/snow/networking/sender/BUILD.bazel new file mode 100644 index 000000000000..6514a12730cc --- /dev/null +++ b/snow/networking/sender/BUILD.bazel @@ -0,0 +1,72 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "sender", + srcs = [ + "external_sender.go", + "sender.go", + "traced_sender.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/sender", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//proto/pb/p2p", + "//snow", + "//snow/engine/common", + "//snow/networking/router", + "//snow/networking/timeout", + "//subnets", + "//trace", + "//utils/logging", + "//utils/set", + "@com_github_prometheus_client_golang//prometheus", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "sender_test", + srcs = [ + "mocks_generate_test.go", + "sender_test.go", + ], + embed = [":sender"], + deps = [ + "//ids", + "//message", + "//message/messagemock", + "//network/p2p", + "//proto/pb/p2p", + "//snow", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/networking/benchlist", + "//snow/networking/handler", + "//snow/networking/router", + "//snow/networking/router/routermock", + "//snow/networking/sender/sendermock", + "//snow/networking/sender/sendertest", + "//snow/networking/timeout", + "//snow/networking/timeout/timeoutmock", + "//snow/networking/tracker", + "//snow/snowtest", + "//snow/validators", + "//subnets", + "//utils/constants", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//utils/timer", + "//version", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/sender/sendermock/BUILD.bazel b/snow/networking/sender/sendermock/BUILD.bazel new file mode 100644 index 000000000000..6d6ac742ea21 --- /dev/null +++ b/snow/networking/sender/sendermock/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sendermock", + srcs = ["external_sender.go"], + importpath = "github.com/ava-labs/avalanchego/snow/networking/sender/sendermock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow/engine/common", + "//subnets", + "//utils/set", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/sender/sendertest/BUILD.bazel b/snow/networking/sender/sendertest/BUILD.bazel new file mode 100644 index 000000000000..9c0ceaf9206c --- /dev/null +++ b/snow/networking/sender/sendertest/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "sendertest", + srcs = ["external.go"], + importpath = "github.com/ava-labs/avalanchego/snow/networking/sender/sendertest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow/engine/common", + "//snow/networking/sender", + "//subnets", + "//utils/set", + ], +) diff --git a/snow/networking/timeout/BUILD.bazel b/snow/networking/timeout/BUILD.bazel new file mode 100644 index 000000000000..174da566397e --- /dev/null +++ b/snow/networking/timeout/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "timeout", + srcs = [ + "manager.go", + "metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/timeout", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow", + "//snow/networking/benchlist", + "//utils/timer", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "timeout_test", + srcs = [ + "main_test.go", + "manager_test.go", + "mocks_generate_test.go", + ], + embed = [":timeout"], + deps = [ + "//ids", + "//snow/networking/benchlist", + "//utils/timer", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + ], +) diff --git a/snow/networking/timeout/timeoutmock/BUILD.bazel b/snow/networking/timeout/timeoutmock/BUILD.bazel new file mode 100644 index 000000000000..5c8f86a5aad0 --- /dev/null +++ b/snow/networking/timeout/timeoutmock/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "timeoutmock", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/snow/networking/timeout/timeoutmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//message", + "//snow", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/tracker/BUILD.bazel b/snow/networking/tracker/BUILD.bazel new file mode 100644 index 000000000000..26f5a5e0eb5b --- /dev/null +++ b/snow/networking/tracker/BUILD.bazel @@ -0,0 +1,45 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "tracker", + srcs = [ + "resource_tracker.go", + "targeter.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/tracker", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "//utils/constants", + "//utils/linked", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "tracker_test", + srcs = [ + "mocks_generate_test.go", + "resource_tracker_test.go", + "targeter_test.go", + ], + embed = [":tracker"], + deps = [ + "//ids", + "//snow/networking/tracker/trackermock", + "//snow/validators", + "//utils/constants", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/resource/resourcemock", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/networking/tracker/trackermock/BUILD.bazel b/snow/networking/tracker/trackermock/BUILD.bazel new file mode 100644 index 000000000000..1563af67b4ec --- /dev/null +++ b/snow/networking/tracker/trackermock/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "trackermock", + srcs = [ + "targeter.go", + "tracker.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/networking/tracker/trackermock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/snowtest/BUILD.bazel b/snow/snowtest/BUILD.bazel new file mode 100644 index 000000000000..9e0b6b186a1f --- /dev/null +++ b/snow/snowtest/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "snowtest", + srcs = [ + "context.go", + "decidable.go", + "status.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/snowtest", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//chains/atomic", + "//database/memdb", + "//ids", + "//snow", + "//snow/validators", + "//snow/validators/validatorstest", + "//upgrade/upgradetest", + "//utils/constants", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//vms/platformvm/warp", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/uptime/BUILD.bazel b/snow/uptime/BUILD.bazel new file mode 100644 index 000000000000..be6c9c2ea8e9 --- /dev/null +++ b/snow/uptime/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "uptime", + srcs = [ + "locked_calculator.go", + "manager.go", + "no_op_calculator.go", + "state.go", + "test_state.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/uptime", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils", + "//utils/timer/mockable", + ], +) + +go_test( + name = "uptime_test", + srcs = [ + "locked_calculator_test.go", + "manager_test.go", + "mocks_generate_test.go", + ], + embed = [":uptime"], + deps = [ + "//database", + "//ids", + "//snow/uptime/uptimemock", + "//utils", + "//utils/timer/mockable", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/uptime/uptimemock/BUILD.bazel b/snow/uptime/uptimemock/BUILD.bazel new file mode 100644 index 000000000000..c5bd0e611f79 --- /dev/null +++ b/snow/uptime/uptimemock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "uptimemock", + srcs = ["calculator.go"], + importpath = "github.com/ava-labs/avalanchego/snow/uptime/uptimemock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/validators/BUILD.bazel b/snow/validators/BUILD.bazel new file mode 100644 index 000000000000..bb5169d7916d --- /dev/null +++ b/snow/validators/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "validators", + srcs = [ + "connector.go", + "logger.go", + "manager.go", + "set.go", + "state.go", + "traced_state.go", + "validator.go", + "warp.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/validators", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//ids", + "//trace", + "//utils", + "//utils/crypto/bls", + "//utils/formatting", + "//utils/json", + "//utils/logging", + "//utils/math", + "//utils/sampler", + "//utils/set", + "//version", + "//vms/types", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_golang_x_exp//maps", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "validators_test", + srcs = [ + "manager_test.go", + "mocks_generate_test.go", + "set_test.go", + "state_test.go", + "warp_test.go", + ], + embed = [":validators"], + deps = [ + "//ids", + "//snow/validators/validatorstest", + "//upgrade", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/math", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/snow/validators/gvalidators/BUILD.bazel b/snow/validators/gvalidators/BUILD.bazel new file mode 100644 index 000000000000..bef58258712a --- /dev/null +++ b/snow/validators/gvalidators/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gvalidators", + srcs = [ + "validator_state_client.go", + "validator_state_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/validators/gvalidators", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//proto/pb/validatorstate", + "//snow/validators", + "//utils/crypto/bls", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) + +go_test( + name = "gvalidators_test", + srcs = ["validator_state_test.go"], + embed = [":gvalidators"], + deps = [ + "//ids", + "//proto/pb/validatorstate", + "//snow/validators", + "//snow/validators/validatorsmock", + "//snow/validators/validatorstest", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/validators/validatorsmock/BUILD.bazel b/snow/validators/validatorsmock/BUILD.bazel new file mode 100644 index 000000000000..c983c4bb9131 --- /dev/null +++ b/snow/validators/validatorsmock/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "validatorsmock", + srcs = ["state.go"], + importpath = "github.com/ava-labs/avalanchego/snow/validators/validatorsmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "@org_uber_go_mock//gomock", + ], +) diff --git a/snow/validators/validatorstest/BUILD.bazel b/snow/validators/validatorstest/BUILD.bazel new file mode 100644 index 000000000000..c74d6b85ff65 --- /dev/null +++ b/snow/validators/validatorstest/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "validatorstest", + srcs = [ + "state.go", + "warp.go", + ], + importpath = "github.com/ava-labs/avalanchego/snow/validators/validatorstest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "//utils", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "@com_github_stretchr_testify//require", + ], +) diff --git a/staking/BUILD.bazel b/staking/BUILD.bazel new file mode 100644 index 000000000000..5431240f8b31 --- /dev/null +++ b/staking/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "staking", + srcs = [ + "asn1.go", + "certificate.go", + "parse.go", + "tls.go", + "verify.go", + ], + importpath = "github.com/ava-labs/avalanchego/staking", + visibility = ["//visibility:public"], + deps = [ + "//utils/perms", + "//utils/units", + "@org_golang_x_crypto//cryptobyte", + "@org_golang_x_crypto//cryptobyte/asn1", + ], +) + +go_test( + name = "staking_test", + srcs = [ + "parse_test.go", + "tls_test.go", + "verify_test.go", + ], + embed = [":staking"], + embedsrcs = ["large_rsa_key.cert"], + deps = [ + "//utils/hashing", + "@com_github_stretchr_testify//require", + ], +) diff --git a/subnets/BUILD.bazel b/subnets/BUILD.bazel new file mode 100644 index 000000000000..09429f3c0daa --- /dev/null +++ b/subnets/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "subnets", + srcs = [ + "config.go", + "no_op_allower.go", + "subnet.go", + ], + importpath = "github.com/ava-labs/avalanchego/subnets", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowball", + "//snow/engine/common", + "//utils/set", + ], +) + +go_test( + name = "subnets_test", + srcs = [ + "config_test.go", + "subnet_test.go", + ], + embed = [":subnets"], + deps = [ + "//ids", + "//snow/consensus/snowball", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 000000000000..b5fc8daec0d6 --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "tests", + srcs = [ + "context_helpers.go", + "log.go", + "metrics.go", + "notify_context.go", + "prometheus_server.go", + "simple_test_context.go", + "test_context.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//utils/logging", + "//wallet/subnet/primary/common", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/promhttp", + "@com_github_prometheus_client_model//go", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/antithesis/BUILD.bazel b/tests/antithesis/BUILD.bazel new file mode 100644 index 000000000000..7859abb5529d --- /dev/null +++ b/tests/antithesis/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "antithesis", + srcs = [ + "compose.go", + "config.go", + "context.go", + "init_db.go", + "node_health.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/antithesis", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//config", + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/constants", + "//utils/logging", + "//utils/perms", + "@com_github_antithesishq_antithesis_sdk_go//assert", + "@com_github_compose_spec_compose_go//types", + "@com_github_stretchr_testify//require", + "@in_gopkg_yaml_v3//:yaml_v3", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/antithesis/avalanchego/BUILD.bazel b/tests/antithesis/avalanchego/BUILD.bazel new file mode 100644 index 000000000000..70507b41575f --- /dev/null +++ b/tests/antithesis/avalanchego/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "avalanchego_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/antithesis/avalanchego", + visibility = ["//visibility:private"], + deps = [ + "//database", + "//genesis", + "//ids", + "//tests", + "//tests/antithesis", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/set", + "//utils/timer", + "//utils/units", + "//vms/avm", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm", + "//vms/platformvm/txs", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//wallet/chain/x/builder", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + "@com_github_antithesishq_antithesis_sdk_go//assert", + "@com_github_antithesishq_antithesis_sdk_go//lifecycle", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) + +go_binary( + name = "avalanchego", + embed = [":avalanchego_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/antithesis/avalanchego/gencomposeconfig/BUILD.bazel b/tests/antithesis/avalanchego/gencomposeconfig/BUILD.bazel new file mode 100644 index 000000000000..f13e2a08da22 --- /dev/null +++ b/tests/antithesis/avalanchego/gencomposeconfig/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "gencomposeconfig_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/antithesis/avalanchego/gencomposeconfig", + visibility = ["//visibility:private"], + deps = [ + "//tests", + "//tests/antithesis", + "//tests/fixture/tmpnet", + "@org_uber_go_zap//:zap", + ], +) + +go_binary( + name = "gencomposeconfig", + embed = [":gencomposeconfig_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/antithesis/xsvm/BUILD.bazel b/tests/antithesis/xsvm/BUILD.bazel new file mode 100644 index 000000000000..8114bf0a4e5c --- /dev/null +++ b/tests/antithesis/xsvm/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "xsvm_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/antithesis/xsvm", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//tests", + "//tests/antithesis", + "//tests/fixture/subnet", + "//tests/fixture/tmpnet", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/set", + "//utils/timer", + "//utils/units", + "//vms/example/xsvm/api", + "//vms/example/xsvm/cmd/issue/status", + "//vms/example/xsvm/cmd/issue/transfer", + "@com_github_antithesishq_antithesis_sdk_go//assert", + "@com_github_antithesishq_antithesis_sdk_go//lifecycle", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) + +go_binary( + name = "xsvm", + embed = [":xsvm_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/antithesis/xsvm/gencomposeconfig/BUILD.bazel b/tests/antithesis/xsvm/gencomposeconfig/BUILD.bazel new file mode 100644 index 000000000000..a74889ed5e30 --- /dev/null +++ b/tests/antithesis/xsvm/gencomposeconfig/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "gencomposeconfig_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/antithesis/xsvm/gencomposeconfig", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//tests", + "//tests/antithesis", + "//tests/fixture/subnet", + "//tests/fixture/tmpnet", + "@org_uber_go_zap//:zap", + ], +) + +go_binary( + name = "gencomposeconfig", + embed = [":gencomposeconfig_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/e2e/BUILD.bazel b/tests/e2e/BUILD.bazel new file mode 100644 index 000000000000..e846562cfb1d --- /dev/null +++ b/tests/e2e/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "e2e", + srcs = ["ignore.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e", + visibility = ["//visibility:public"], +) + +go_test( + name = "e2e_test", + srcs = ["e2e_test.go"], + deps = [ + "//config", + "//graft/coreth/plugin/evm", + "//tests/e2e/banff", + "//tests/e2e/c", + "//tests/e2e/faultinjection", + "//tests/e2e/p", + "//tests/e2e/vms", + "//tests/e2e/x", + "//tests/e2e/x/transfer", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//upgrade/upgradetest", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/e2e/banff/BUILD.bazel b/tests/e2e/banff/BUILD.bazel new file mode 100644 index 000000000000..aaebba669e06 --- /dev/null +++ b/tests/e2e/banff/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "banff", + srcs = ["suites.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/banff", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//tests", + "//tests/fixture/e2e", + "//utils/constants", + "//utils/units", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tests/e2e/c/BUILD.bazel b/tests/e2e/c/BUILD.bazel new file mode 100644 index 000000000000..55bd7cebecd6 --- /dev/null +++ b/tests/e2e/c/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "c", + srcs = [ + "api.go", + "consume_gas.go", + "dynamic_fees.go", + "interchain_workflow.go", + "proposervm_epoch.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/c", + visibility = ["//visibility:public"], + deps = [ + "//api/connectclient", + "//api/info", + "//connectproto/pb/proposervm", + "//connectproto/pb/proposervm/proposervmconnect", + "//graft/coreth/ethclient", + "//graft/coreth/plugin/evm/upgrade/cortina", + "//ids", + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/set", + "//utils/units", + "//vms/evm/acp176", + "//vms/proposervm", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + "@com_connectrpc_connect//:connect", + "@com_github_ava_labs_libevm//accounts/abi", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//params", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/e2e/faultinjection/BUILD.bazel b/tests/e2e/faultinjection/BUILD.bazel new file mode 100644 index 000000000000..d3d4fd90b51f --- /dev/null +++ b/tests/e2e/faultinjection/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "faultinjection", + srcs = ["duplicate_node_id.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/faultinjection", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//config", + "//ids", + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/set", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tests/e2e/p/BUILD.bazel b/tests/e2e/p/BUILD.bazel new file mode 100644 index 000000000000..0c4792c54d52 --- /dev/null +++ b/tests/e2e/p/BUILD.bazel @@ -0,0 +1,61 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "p", + srcs = [ + "interchain_workflow.go", + "l1.go", + "owner_retrieval.go", + "staking_rewards.go", + "validator_sets.go", + "workflow.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/p", + visibility = ["//visibility:public"], + deps = [ + "//api/admin", + "//api/info", + "//config", + "//genesis", + "//ids", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/p2p", + "//proto/pb/platformvm", + "//proto/pb/sdk", + "//snow/networking/router", + "//snow/validators", + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils", + "//utils/buffer", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/set", + "//utils/units", + "//vms/components/avax", + "//vms/example/xsvm/genesis", + "//vms/platformvm", + "//vms/platformvm/api", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/txs", + "//vms/platformvm/validators", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/proposervm", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + "@com_github_mitchellh_mapstructure//:mapstructure", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/e2e/vms/BUILD.bazel b/tests/e2e/vms/BUILD.bazel new file mode 100644 index 000000000000..ed5ce99c8469 --- /dev/null +++ b/tests/e2e/vms/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vms", + srcs = ["xsvm.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/vms", + visibility = ["//visibility:public"], + deps = [ + "//api/connectclient", + "//connectproto/pb/xsvm", + "//connectproto/pb/xsvm/xsvmconnect", + "//ids", + "//tests/fixture/e2e", + "//tests/fixture/subnet", + "//tests/fixture/tmpnet", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/example/xsvm/api", + "//vms/example/xsvm/cmd/issue/export", + "//vms/example/xsvm/cmd/issue/importtx", + "//vms/example/xsvm/cmd/issue/transfer", + "@com_connectrpc_connect//:connect", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/e2e/x/BUILD.bazel b/tests/e2e/x/BUILD.bazel new file mode 100644 index 000000000000..255a845ad0db --- /dev/null +++ b/tests/e2e/x/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "x", + srcs = ["interchain_workflow.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/x", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//tests/fixture/e2e", + "//utils/constants", + "//utils/set", + "//utils/units", + "//vms/components/avax", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tests/e2e/x/transfer/BUILD.bazel b/tests/e2e/x/transfer/BUILD.bazel new file mode 100644 index 000000000000..61423d62f143 --- /dev/null +++ b/tests/e2e/x/transfer/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "transfer", + srcs = ["virtuous.go"], + importpath = "github.com/ava-labs/avalanchego/tests/e2e/x/transfer", + visibility = ["//visibility:public"], + deps = [ + "//chains", + "//ids", + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//utils/crypto/secp256k1", + "//utils/set", + "//utils/units", + "//vms/avm", + "//vms/components/avax", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/fixture/bootstrapmonitor/BUILD.bazel b/tests/fixture/bootstrapmonitor/BUILD.bazel new file mode 100644 index 000000000000..cc84d53112dd --- /dev/null +++ b/tests/fixture/bootstrapmonitor/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bootstrapmonitor", + srcs = [ + "bootstrap_test_config.go", + "common.go", + "init.go", + "wait.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/bootstrapmonitor", + visibility = ["//visibility:public"], + deps = [ + "//chains", + "//config", + "//tests/fixture/tmpnet", + "//tests/fixture/tmpnet/flags", + "//utils/logging", + "//utils/perms", + "//version", + "@com_github_spf13_cast//:cast", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/types", + "@io_k8s_apimachinery//pkg/util/wait", + "@io_k8s_client_go//kubernetes", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "bootstrapmonitor_test", + srcs = ["bootstrap_test_config_test.go"], + embed = [":bootstrapmonitor"], + deps = [ + "//chains", + "//version", + "@com_github_stretchr_testify//require", + "@io_k8s_api//core/v1:core", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + ], +) diff --git a/tests/fixture/bootstrapmonitor/cmd/BUILD.bazel b/tests/fixture/bootstrapmonitor/cmd/BUILD.bazel new file mode 100644 index 000000000000..f939328319de --- /dev/null +++ b/tests/fixture/bootstrapmonitor/cmd/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "cmd_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/bootstrapmonitor/cmd", + visibility = ["//visibility:private"], + deps = [ + "//tests/fixture/bootstrapmonitor", + "//utils/logging", + "//version", + "@com_github_spf13_cobra//:cobra", + ], +) + +go_binary( + name = "cmd", + embed = [":cmd_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/fixture/bootstrapmonitor/e2e/BUILD.bazel b/tests/fixture/bootstrapmonitor/e2e/BUILD.bazel new file mode 100644 index 000000000000..3dd09b74d966 --- /dev/null +++ b/tests/fixture/bootstrapmonitor/e2e/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "e2e_test", + srcs = ["e2e_test.go"], + deps = [ + "//config", + "//ids", + "//tests", + "//tests/fixture/bootstrapmonitor", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//tests/fixture/tmpnet/flags", + "//utils/constants", + "//utils/logging", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + "@io_k8s_api//apps/v1:apps", + "@io_k8s_api//core/v1:core", + "@io_k8s_api//rbac/v1:rbac", + "@io_k8s_apimachinery//pkg/api/errors", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_client_go//kubernetes", + "@io_k8s_client_go//rest", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/fixture/e2e/BUILD.bazel b/tests/fixture/e2e/BUILD.bazel new file mode 100644 index 000000000000..57a94c266f3a --- /dev/null +++ b/tests/fixture/e2e/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "e2e", + srcs = [ + "apitest.go", + "describe.go", + "env.go", + "flags.go", + "ginkgo_test_context.go", + "helpers.go", + "metrics_link.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/e2e", + visibility = ["//visibility:public"], + deps = [ + "//config", + "//graft/coreth/ethclient", + "//ids", + "//tests", + "//tests/fixture/tmpnet", + "//tests/fixture/tmpnet/flags", + "//utils/crypto/secp256k1", + "//utils/logging", + "//vms/platformvm/txs/fee", + "//vms/secp256k1fx", + "//wallet/chain/p/builder", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//core/types", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_spf13_cast//:cast", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + "@org_uber_go_zap//zapcore", + ], +) diff --git a/tests/fixture/stacktrace/BUILD.bazel b/tests/fixture/stacktrace/BUILD.bazel new file mode 100644 index 000000000000..dfe90ae162b5 --- /dev/null +++ b/tests/fixture/stacktrace/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "stacktrace", + srcs = ["stacktrace.go"], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/stacktrace", + visibility = ["//visibility:public"], +) diff --git a/tests/fixture/subnet/BUILD.bazel b/tests/fixture/subnet/BUILD.bazel new file mode 100644 index 000000000000..47685e96904d --- /dev/null +++ b/tests/fixture/subnet/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "subnet", + srcs = ["xsvm.go"], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/subnet", + visibility = ["//visibility:public"], + deps = [ + "//tests/fixture/tmpnet", + "//utils/constants", + "//utils/crypto/secp256k1", + "//vms/example/xsvm/genesis", + ], +) diff --git a/tests/fixture/tmpnet/BUILD.bazel b/tests/fixture/tmpnet/BUILD.bazel new file mode 100644 index 000000000000..60aa72a85b64 --- /dev/null +++ b/tests/fixture/tmpnet/BUILD.bazel @@ -0,0 +1,99 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "tmpnet", + srcs = [ + "check_monitoring.go", + "defaults.go", + "detached_process_default.go", + "flagsmap.go", + "genesis.go", + "kube.go", + "kube_runtime.go", + "local_network.go", + "monitor_kube.go", + "monitor_processes.go", + "network.go", + "network_config.go", + "node.go", + "node_config.go", + "process_runtime.go", + "start_kind_cluster.go", + "subnet.go", + "utils.go", + ], + embedsrcs = [ + "yaml/prometheus-agent.yaml", + "yaml/promtail-daemonset.yaml", + "yaml/tmpnet-rbac.yaml", + ], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/tmpnet", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//api/info", + "//chains", + "//config", + "//config/node", + "//genesis", + "//ids", + "//staking", + "//tests/fixture/stacktrace", + "//upgrade", + "//utils/constants", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/formatting/address", + "//utils/logging", + "//utils/perms", + "//utils/rpc", + "//utils/set", + "//utils/units", + "//vms/platformvm", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + "@com_github_ava_labs_libevm//core", + "@com_github_ava_labs_libevm//params", + "@com_github_google_uuid//:uuid", + "@com_github_prometheus_client_golang//api", + "@com_github_prometheus_client_golang//api/prometheus/v1:prometheus", + "@com_github_prometheus_common//model", + "@io_k8s_api//apps/v1:apps", + "@io_k8s_api//authentication/v1:authentication", + "@io_k8s_api//core/v1:core", + "@io_k8s_api//networking/v1:networking", + "@io_k8s_apimachinery//pkg/api/errors", + "@io_k8s_apimachinery//pkg/api/resource", + "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured", + "@io_k8s_apimachinery//pkg/runtime/schema", + "@io_k8s_apimachinery//pkg/runtime/serializer/yaml", + "@io_k8s_apimachinery//pkg/types", + "@io_k8s_apimachinery//pkg/util/intstr", + "@io_k8s_apimachinery//pkg/util/wait", + "@io_k8s_client_go//dynamic", + "@io_k8s_client_go//kubernetes", + "@io_k8s_client_go//rest", + "@io_k8s_client_go//tools/clientcmd", + "@io_k8s_client_go//tools/clientcmd/api", + "@io_k8s_client_go//tools/portforward", + "@io_k8s_client_go//transport/spdy", + "@io_k8s_utils//ptr", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "tmpnet_test", + srcs = ["network_test.go"], + embed = [":tmpnet"], + deps = [ + "//utils/logging", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tests/fixture/tmpnet/flags/BUILD.bazel b/tests/fixture/tmpnet/flags/BUILD.bazel new file mode 100644 index 000000000000..28761be518f9 --- /dev/null +++ b/tests/fixture/tmpnet/flags/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "flags", + srcs = [ + "collector.go", + "common.go", + "kube_runtime.go", + "kubeconfig.go", + "process_runtime.go", + "runtime.go", + "start_network.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/flags", + visibility = ["//visibility:public"], + deps = [ + "//tests/fixture/stacktrace", + "//tests/fixture/tmpnet", + "@com_github_spf13_cast//:cast", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/tests/fixture/tmpnet/tmpnetctl/BUILD.bazel b/tests/fixture/tmpnet/tmpnetctl/BUILD.bazel new file mode 100644 index 000000000000..83797e526076 --- /dev/null +++ b/tests/fixture/tmpnet/tmpnetctl/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "tmpnetctl_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/tmpnetctl", + visibility = ["//visibility:private"], + deps = [ + "//tests", + "//tests/fixture/stacktrace", + "//tests/fixture/tmpnet", + "//tests/fixture/tmpnet/flags", + "//utils/logging", + "//version", + "@com_github_spf13_cobra//:cobra", + "@org_uber_go_zap//:zap", + ], +) + +go_binary( + name = "tmpnetctl", + embed = [":tmpnetctl_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/load/BUILD.bazel b/tests/load/BUILD.bazel new file mode 100644 index 000000000000..7219a70bd5d6 --- /dev/null +++ b/tests/load/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "load", + srcs = [ + "generator.go", + "metrics.go", + "tests.go", + "wallet.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/load", + visibility = ["//visibility:public"], + deps = [ + "//tests", + "//tests/load/contracts", + "//utils/logging", + "//utils/sampler", + "@com_github_ava_labs_libevm//accounts/abi/bind", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethclient", + "@com_github_ava_labs_libevm//params", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/tests/load/contracts/BUILD.bazel b/tests/load/contracts/BUILD.bazel new file mode 100644 index 000000000000..38e74c855081 --- /dev/null +++ b/tests/load/contracts/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "contracts", + srcs = [ + "ERC20.bindings.go", + "LoadSimulator.bindings.go", + "TrieStressTest.bindings.go", + "generate.go", + ], + importpath = "github.com/ava-labs/avalanchego/tests/load/contracts", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//:libevm", + "@com_github_ava_labs_libevm//accounts/abi", + "@com_github_ava_labs_libevm//accounts/abi/bind", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//event", + ], +) diff --git a/tests/load/main/BUILD.bazel b/tests/load/main/BUILD.bazel new file mode 100644 index 000000000000..0a5346665ec2 --- /dev/null +++ b/tests/load/main/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "main_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/tests/load/main", + visibility = ["//visibility:private"], + deps = [ + "//tests", + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//tests/load", + "//tests/load/contracts", + "@com_github_ava_labs_libevm//accounts/abi/bind", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//ethclient", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_exp//maps", + ], +) + +go_binary( + name = "main", + embed = [":main_lib"], + visibility = ["//visibility:public"], +) diff --git a/tests/reexecute/c/BUILD.bazel b/tests/reexecute/c/BUILD.bazel new file mode 100644 index 000000000000..1b2664491125 --- /dev/null +++ b/tests/reexecute/c/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "c_test", + srcs = ["vm_reexecute_test.go"], + deps = [ + "//api/metrics", + "//chains/atomic", + "//database", + "//database/leveldb", + "//database/prefixdb", + "//genesis", + "//graft/coreth/plugin/evm", + "//graft/coreth/plugin/factory", + "//ids", + "//snow", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/validators/validatorstest", + "//tests", + "//tests/fixture/tmpnet", + "//upgrade", + "//utils/constants", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//utils/timer", + "//utils/units", + "//vms/metervm", + "//vms/platformvm/warp", + "@com_github_google_uuid//:uuid", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + ], +) diff --git a/tests/upgrade/BUILD.bazel b/tests/upgrade/BUILD.bazel new file mode 100644 index 000000000000..93987f0a6e60 --- /dev/null +++ b/tests/upgrade/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "upgrade_test", + srcs = ["upgrade_test.go"], + deps = [ + "//tests/fixture/e2e", + "//tests/fixture/tmpnet", + "//tests/fixture/tmpnet/flags", + "@com_github_onsi_ginkgo_v2//:ginkgo", + "@com_github_stretchr_testify//require", + ], +) diff --git a/tools/bazel/workspace_status.sh b/tools/bazel/workspace_status.sh new file mode 100755 index 000000000000..c32816da11ef --- /dev/null +++ b/tools/bazel/workspace_status.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Workspace status script for Bazel +# Provides build metadata for version injection + +# Stable status (cached, won't trigger rebuilds on change) +echo "STABLE_GIT_COMMIT $(git rev-parse HEAD 2>/dev/null || echo unknown)" +echo "STABLE_GIT_BRANCH $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" + +# Volatile status (changes trigger rebuilds but only for rules that use them) +echo "BUILD_TIMESTAMP $(date -u +%Y-%m-%dT%H:%M:%SZ)" diff --git a/trace/BUILD.bazel b/trace/BUILD.bazel new file mode 100644 index 000000000000..01fc3e838280 --- /dev/null +++ b/trace/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "trace", + srcs = [ + "exporter.go", + "exporter_type.go", + "noop.go", + "tracer.go", + ], + importpath = "github.com/ava-labs/avalanchego/trace", + visibility = ["//visibility:public"], + deps = [ + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel//semconv/v1.4.0:v1_4_0", + "@io_opentelemetry_go_otel_exporters_otlp_otlptrace//:otlptrace", + "@io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracegrpc//:otlptracegrpc", + "@io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracehttp//:otlptracehttp", + "@io_opentelemetry_go_otel_sdk//resource", + "@io_opentelemetry_go_otel_sdk//trace", + "@io_opentelemetry_go_otel_trace//:trace", + "@io_opentelemetry_go_otel_trace//noop", + ], +) + +go_test( + name = "trace_test", + srcs = ["exporter_type_test.go"], + embed = [":trace"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/updated-plan.md b/updated-plan.md new file mode 100644 index 000000000000..05a57a1f694c --- /dev/null +++ b/updated-plan.md @@ -0,0 +1,594 @@ +# Bazel Integration Execution Plan for avalanchego + +## Background Context + +### Repository Overview +- **Repository**: avalanchego - Avalanche network node implementation +- **Branch**: maru/bazel +- **Main module**: `github.com/ava-labs/avalanchego` (BSD-3 license) at repository root +- **Coreth module**: `github.com/ava-labs/avalanchego/graft/coreth` (LGPL-3 license) at `graft/coreth/` +- **Go version**: 1.24.9 (security-critical, must be exact) + +### Why Bazel? +The repository is evolving into a multi-language monorepo: +1. Currently: Two Go modules (avalanchego + coreth) with circular dependencies +2. Coming: Another Go module +3. Future: Rust dependency (Firewood) + +Bazel enables: +- Targeted CI/builds (don't rebuild the world for every change) +- Multi-language support (Go now, Rust later) +- Shared toolchain management via rules_nixpkgs + +### Current Build System +- **Nix flake** (`flake.nix`) provides development environment +- **Task runner** (`Taskfile.yml`) orchestrates builds via shell scripts +- **Go SDK** defined in `nix/go/default.nix` with explicit version and SHA256s +- **No existing Bazel** - this plan creates it from scratch + +### Key Files to Reference +- `flake.nix` - Nix development environment (lines 37-77 have package list) +- `nix/go/default.nix` - Custom Go 1.24.9 derivation with SHA256 checksums +- `nix/go/flake.nix` - Flake wrapper for Go derivation +- `go.mod` - Main module definition with replace directive for coreth +- `graft/coreth/go.mod` - Coreth module with reverse replace directive +- `Taskfile.yml` - Task definitions +- `scripts/build.sh` - Current build script (CGO flags, ldflags) + +--- + +## Execution Strategy + +### Approach +- **Minimal first**: Build just avalanchego binary before expanding +- **Incremental**: Validate each step, commit on success +- **Single source of truth**: Go SDK from Nix via rules_nixpkgs (not duplicated) +- **Exclude coreth**: Defer circular dependency handling + +### Go SDK Strategy +Use `rules_nixpkgs_go` to source Go SDK from existing Nix derivation: +- Avoids duplicating Go version/checksums in Bazel config +- Future-proof for Rust (rules_nixpkgs_rust uses same pattern) +- Fallback to explicit SDK registration if rules_nixpkgs proves problematic + +--- + +## Step 1: Add Bazel to Nix Environment + +### Files to Modify +**`flake.nix`** - Add to the packages list (around line 37-77): + +```nix +# After "go-task" line, add: +bazel_7 # Bazel build system +buildifier # Bazel file formatter +``` + +### Full Context +Current packages section looks like: +```nix +packages = with pkgs; [ + git + go-task + # ... (add bazel_7 and buildifier here) +``` + +### Validation Commands +```bash +# Test Bazel is available +nix develop --command bazel version +# Should output: Build label: 7.x.x + +# Test buildifier is available +nix develop --command buildifier --version +# Should output version info +``` + +### Commit +Message: `Add Bazel and buildifier to nix development environment` + +--- + +## Step 2: Modify Nix Go Derivation for rules_go Compatibility + +### Why This Is Needed +`nixpkgs_go_configure` from rules_nixpkgs expects the Go SDK to have a `ROOT` marker file at the package root. This is how rules_go identifies the SDK root directory. + +### Files to Modify +**`nix/go/default.nix`** - Add ROOT marker in installPhase (around line 49-56): + +Current: +```nix +installPhase = '' + mkdir -p $out + tar xzf $src -C $out --strip-components=1 --no-same-owner --no-same-permissions + chmod +x $out/bin/go +''; +``` + +Change to: +```nix +installPhase = '' + mkdir -p $out + tar xzf $src -C $out --strip-components=1 --no-same-owner --no-same-permissions + chmod +x $out/bin/go + # ROOT marker required by rules_go/rules_nixpkgs for SDK identification + touch $out/ROOT +''; +``` + +### Validation Commands +```bash +# Build the Go derivation +nix build ./nix/go + +# Check ROOT file exists +ls $(nix build ./nix/go --print-out-paths 2>/dev/null)/ROOT +# Should show: /nix/store/.../ROOT + +# Verify Go still works +$(nix build ./nix/go --print-out-paths 2>/dev/null)/bin/go version +# Should show: go version go1.24.9 ... +``` + +### Commit +Message: `Add ROOT marker to Go derivation for Bazel compatibility` + +--- + +## Step 3: Initialize Bazel Workspace with bzlmod + rules_nixpkgs + +### Files to Create + +#### **`MODULE.bazel`** (new file at repository root) +```python +"""Bazel module definition for avalanchego.""" + +module( + name = "avalanchego", + version = "0.0.0", +) + +# Core dependencies from Bazel Central Registry +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.10") +bazel_dep(name = "rules_go", version = "0.50.1") +bazel_dep(name = "gazelle", version = "0.39.1") + +# rules_nixpkgs for Nix integration +# Not in BCR, so we use git_override to fetch from GitHub +bazel_dep(name = "rules_nixpkgs_core", version = "0.13.0") +bazel_dep(name = "rules_nixpkgs_go", version = "0.13.0") + +git_override( + module_name = "rules_nixpkgs_core", + remote = "https://github.com/tweag/rules_nixpkgs.git", + # TODO: Pin to specific commit after initial testing + # Find latest: git ls-remote https://github.com/tweag/rules_nixpkgs.git HEAD + commit = "REPLACE_WITH_ACTUAL_COMMIT_SHA", + strip_prefix = "core", +) + +git_override( + module_name = "rules_nixpkgs_go", + remote = "https://github.com/tweag/rules_nixpkgs.git", + commit = "REPLACE_WITH_ACTUAL_COMMIT_SHA", # Same commit as above + strip_prefix = "toolchains/go", +) + +# Configure Go SDK from Nix derivation +# This ensures Bazel uses the exact same Go version as nix develop +nixpkgs_go = use_extension( + "@rules_nixpkgs_go//:extensions.bzl", + "nixpkgs_go", +) +nixpkgs_go.toolchain( + nix_file = "//nix/go:bazel.nix", + nix_file_deps = ["//nix/go:default.nix"], +) +use_repo(nixpkgs_go, "go_toolchains") + +register_toolchains("@go_toolchains//:all") + +# Go dependencies from go.mod +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//:go.mod") +use_repo( + go_deps, + # Will be populated by gazelle +) +``` + +**Important**: Before creating this file, get the actual commit SHA: +```bash +git ls-remote https://github.com/tweag/rules_nixpkgs.git HEAD +# Use the commit SHA in both git_override blocks +``` + +#### **`nix/go/bazel.nix`** (new file) +```nix +# Bazel wrapper for Go SDK +# Called by rules_nixpkgs_go to build the Go toolchain +# +# This file re-uses the same Go derivation as nix develop, +# ensuring version consistency between Nix and Bazel environments. +{ pkgs ? import {} }: +import ./default.nix { inherit pkgs; } +``` + +#### **`nix/go/BUILD.bazel`** (new file) +```python +# Make nix files visible to Bazel +exports_files([ + "bazel.nix", + "default.nix", +]) +``` + +#### **`.bazelrc`** (new file at repository root) +``` +# Bazel configuration for avalanchego + +# Enable bzlmod (default in Bazel 7+, explicit for clarity) +common --enable_bzlmod + +# Build settings +build --incompatible_enable_cc_toolchain_resolution + +# CGO flags for BLST cryptography library +# Must match scripts/build.sh: CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +build --action_env=CGO_CFLAGS=-O2 -D__BLST_PORTABLE__ +build --action_env=CGO_ENABLED=1 + +# Test settings +test --test_output=errors + +# Performance +build --jobs=auto + +# Nix integration - allow network for nix-build +# This may need adjustment based on your Bazel sandbox configuration +build --sandbox_add_mount_pair=/nix + +# Version injection (added in Step 6) +# build --workspace_status_command=tools/bazel/workspace_status.sh +``` + +#### **`.bazelignore`** (new file at repository root) +``` +# Directories to exclude from Bazel's file watching +.git +node_modules +.avalanchego +build +.direnv +result +``` + +### Validation Commands +```bash +# Verify Bazel recognizes the workspace +nix develop --command bazel version +# Should show version without errors about missing workspace + +# Verify module resolution (may take time on first run) +nix develop --command bazel mod deps +# Should show dependency tree including rules_go, gazelle, rules_nixpkgs +``` + +### Commit +Message: `Initialize Bazel workspace with rules_nixpkgs Go integration` + +--- + +## Step 4: Configure Gazelle and Generate Root BUILD + +### Files to Create + +#### **`BUILD.bazel`** (new file at repository root) +```python +load("@gazelle//:def.bzl", "gazelle") + +# Gazelle configuration +# gazelle:prefix github.com/ava-labs/avalanchego +# gazelle:exclude graft/coreth +# gazelle:exclude .git +# gazelle:exclude build +# gazelle:exclude .direnv + +gazelle(name = "gazelle") + +# Target to update external Go dependencies +gazelle( + name = "gazelle-update-repos", + args = [ + "-from_file=go.mod", + "-to_macro=deps.bzl%go_dependencies", + "-prune", + ], + command = "update-repos", +) +``` + +### Why Exclude graft/coreth +The main module and coreth have circular `replace` directives: +- `go.mod`: `replace github.com/ava-labs/avalanchego/graft/coreth => ./graft/coreth` +- `graft/coreth/go.mod`: `replace github.com/ava-labs/avalanchego => ../../` + +Handling this in Bazel requires careful configuration. For the minimal-first approach, we exclude coreth and focus on building avalanchego without coreth dependencies. + +### Validation Commands +```bash +# Run gazelle to generate BUILD files +nix develop --command bazel run //:gazelle + +# Check that BUILD.bazel files were created +find . -name "BUILD.bazel" -not -path "./.git/*" | head -20 +# Should show BUILD.bazel files in various directories + +# Verify no BUILD files in graft/coreth +ls graft/coreth/BUILD.bazel 2>/dev/null +# Should not exist (excluded) +``` + +### Commit +Message: `Add root BUILD.bazel with Gazelle configuration` + +--- + +## Step 5: Generate BUILD Files and Fix Issues + +### Actions +1. Run Gazelle to generate BUILD files throughout the codebase +2. Attempt to build and fix issues iteratively + +### Common Issues and Fixes + +#### Issue: Missing CGO flags for specific packages +Some packages (especially crypto-related) may need explicit CGO configuration. + +Solution: Add to affected BUILD.bazel files: +```python +go_library( + name = "...", + # ... existing config ... + cgo = True, + copts = ["-O2", "-D__BLST_PORTABLE__"], +) +``` + +#### Issue: Replace directives not handled +Bazel doesn't automatically handle go.mod replace directives. + +Solution: For external dependencies, add to MODULE.bazel: +```python +go_deps.module( + path = "github.com/some/dependency", + sum = "h1:...", + version = "v1.2.3", + replace = "github.com/fork/dependency", +) +``` + +#### Issue: Test files with special requirements +Some test files may need to be excluded from the initial build. + +Solution: Add gazelle directives: +```python +# gazelle:exclude *_test.go +``` + +### Validation Commands +```bash +# Attempt build (will likely fail initially) +nix develop --command bazel build //cmd/avalanchego:all + +# If errors, fix and retry iteratively +# Check specific error messages and adjust BUILD files + +# Once individual fixes are applied, regenerate with gazelle +nix develop --command bazel run //:gazelle +``` + +### Commit +Message: `Generate initial BUILD files with Gazelle` + +--- + +## Step 6: Build avalanchego Binary with Version Injection + +### Files to Create/Modify + +#### **`tools/bazel/workspace_status.sh`** (new file) +```bash +#!/bin/bash +# Workspace status script for Bazel +# Provides variables that can be used in x_defs for version injection +# +# Usage: build --workspace_status_command=tools/bazel/workspace_status.sh +# Access in BUILD: x_defs = {"...version.GitCommit": "{STABLE_GIT_COMMIT}"} + +set -euo pipefail + +# Git commit hash - matches scripts/build.sh behavior +echo "STABLE_GIT_COMMIT $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" + +# Optional: Add more status variables as needed +# echo "STABLE_BUILD_TIME $(date -u +%Y-%m-%dT%H:%M:%SZ)" +``` + +Make executable: +```bash +chmod +x tools/bazel/workspace_status.sh +mkdir -p tools/bazel +``` + +#### **`.bazelrc`** - Add workspace status command +Uncomment/add this line: +``` +build --workspace_status_command=tools/bazel/workspace_status.sh +``` + +#### **`cmd/avalanchego/BUILD.bazel`** - Modify generated file +After Gazelle generates this file, modify the `go_binary` target: + +```python +go_binary( + name = "avalanchego", + embed = [":avalanchego_lib"], + visibility = ["//visibility:public"], + # Version injection - matches scripts/build.sh ldflags + x_defs = { + "github.com/ava-labs/avalanchego/version.GitCommit": "{STABLE_GIT_COMMIT}", + }, +) +``` + +### Validation Commands +```bash +# Build the binary +nix develop --command bazel build //cmd/avalanchego + +# Find and run the binary +BINARY=$(nix develop --command bazel cquery --output=files //cmd/avalanchego 2>/dev/null) +$BINARY --version + +# Verify git commit is embedded +$BINARY --version | grep -i commit +# Should show the current git commit hash +``` + +### Commit +Message: `Configure avalanchego binary build with version injection` + +--- + +## Step 7: Add Task Wrapper + +### Files to Modify + +#### **`Taskfile.yml`** - Add Bazel tasks +Add after existing build tasks: + +```yaml + build-bazel: + desc: Builds avalanchego using Bazel + cmds: + - nix develop --command bazel build //cmd/avalanchego + sources: + - "**/*.go" + - "**/*.bazel" + - MODULE.bazel + - .bazelrc + + build-bazel-all: + desc: Builds all Bazel targets + cmds: + - nix develop --command bazel build //... + + gazelle: + desc: Updates BUILD.bazel files using Gazelle + cmds: + - nix develop --command bazel run //:gazelle + + check-gazelle: + desc: Verifies BUILD.bazel files are up-to-date + cmds: + - nix develop --command bazel run //:gazelle + - task: check-clean-branch +``` + +### Validation Commands +```bash +# Test the new task +task build-bazel +# Should build successfully + +# Verify original build still works +task build +# Should build successfully (no regression) + +# Compare binary sizes (should be similar, ±10%) +ls -la build/avalanchego +ls -la $(bazel cquery --output=files //cmd/avalanchego 2>/dev/null) +``` + +### Commit +Message: `Add Bazel build tasks to Taskfile` + +--- + +## Troubleshooting Guide + +### If rules_nixpkgs_go fails + +**Symptom**: Errors about missing extensions or module not found + +**Solution**: Fall back to explicit SDK registration. Replace the nixpkgs_go section in MODULE.bazel with: + +```python +# Fallback: Explicit Go SDK registration +# Uses same SHA256 checksums as nix/go/default.nix for identical binaries +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download( + name = "go_sdk", + version = "1.24.9", + sdks = { + # SHA256s from nix/go/default.nix lines 24-29 + "linux_amd64": ("go1.24.9.linux-amd64.tar.gz", "5b7899591c2dd6e9da1809fde4a2fad842c45d3f6b9deb235ba82216e31e34a6"), + "linux_arm64": ("go1.24.9.linux-arm64.tar.gz", "9aa1243d51d41e2f93e895c89c0a2daf7166768c4a4c3ac79db81029d295a540"), + "darwin_amd64": ("go1.24.9.darwin-amd64.tar.gz", "961aa2ae2b97e428d6d8991367e7c98cb403bac54276b8259aead42a0081591c"), + "darwin_arm64": ("go1.24.9.darwin-arm64.tar.gz", "af451b40651d7fb36db1bbbd9c66ddbed28b96d7da48abea50a19f82c6e9d1d6"), + }, +) +use_repo(go_sdk, "go_sdk") +``` + +### If Nix sandbox conflicts with Bazel + +**Symptom**: Errors about /nix not being accessible + +**Solution**: Add to `.bazelrc`: +``` +build --sandbox_add_mount_pair=/nix +# Or disable sandbox for specific actions: +build --spawn_strategy=local +``` + +### If circular dependency errors appear + +**Symptom**: Import cycle errors involving graft/coreth + +**Solution**: Ensure `# gazelle:exclude graft/coreth` is in root BUILD.bazel and re-run gazelle. + +--- + +## Success Criteria + +After completing all steps: + +1. ✅ `nix develop --command bazel build //cmd/avalanchego` produces working binary +2. ✅ Binary shows correct git commit in `--version` output +3. ✅ `task build-bazel` succeeds +4. ✅ Original `task build` still works (no regression) +5. ✅ All changes committed to `maru/bazel` branch + +--- + +## What's Deferred (Future Work) + +- **Coreth integration**: Handle circular dependencies with proper Bazel configuration +- **Code generation**: Protobuf, mocks, canoto via Bazel rules +- **Test execution**: `bazel test //...` +- **CI integration**: Remote caching, build event streaming +- **Rust/Firewood**: Add rules_nixpkgs_rust when Firewood is integrated + +--- + +## References + +- [rules_nixpkgs GitHub](https://github.com/tweag/rules_nixpkgs) +- [rules_go bzlmod docs](https://github.com/bazel-contrib/rules_go/blob/master/docs/go/core/bzlmod.md) +- [nix-bazel.build](https://nix-bazel.build/) +- [Bazel Central Registry - rules_nixpkgs_core](https://registry.bazel.build/modules/rules_nixpkgs_core) +- [Bazel git_override documentation](https://bazel.build/rules/lib/globals/module#git_override) diff --git a/upgrade/BUILD.bazel b/upgrade/BUILD.bazel new file mode 100644 index 000000000000..1462edceb914 --- /dev/null +++ b/upgrade/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "upgrade", + srcs = ["upgrade.go"], + importpath = "github.com/ava-labs/avalanchego/upgrade", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/constants", + ], +) + +go_test( + name = "upgrade_test", + srcs = ["upgrade_test.go"], + embed = [":upgrade"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/upgrade/upgradetest/BUILD.bazel b/upgrade/upgradetest/BUILD.bazel new file mode 100644 index 000000000000..02993d86546b --- /dev/null +++ b/upgrade/upgradetest/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "upgradetest", + srcs = [ + "config.go", + "fork.go", + ], + importpath = "github.com/ava-labs/avalanchego/upgrade/upgradetest", + visibility = ["//visibility:public"], + deps = ["//upgrade"], +) diff --git a/utils/BUILD.bazel b/utils/BUILD.bazel new file mode 100644 index 000000000000..08f3f9da3a9c --- /dev/null +++ b/utils/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utils", + srcs = [ + "atomic.go", + "bytes.go", + "slice.go", + "sorting.go", + "stacktrace.go", + "zero.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils", + visibility = ["//visibility:public"], + deps = ["//utils/hashing"], +) + +go_test( + name = "utils_test", + srcs = [ + "atomic_test.go", + "bytes_test.go", + "slice_test.go", + "sorting_test.go", + ], + embed = [":utils"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/bag/BUILD.bazel b/utils/bag/BUILD.bazel new file mode 100644 index 000000000000..6e07b4522929 --- /dev/null +++ b/utils/bag/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bag", + srcs = [ + "bag.go", + "unique_bag.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/bag", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/set", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "bag_test", + srcs = [ + "bag_benchmark_test.go", + "bag_test.go", + "unique_bag_test.go", + ], + embed = [":bag"], + deps = [ + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/beacon/BUILD.bazel b/utils/beacon/BUILD.bazel new file mode 100644 index 000000000000..40f67abadfae --- /dev/null +++ b/utils/beacon/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "beacon", + srcs = [ + "beacon.go", + "set.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/beacon", + visibility = ["//visibility:public"], + deps = ["//ids"], +) + +go_test( + name = "beacon_test", + srcs = ["set_test.go"], + embed = [":beacon"], + deps = [ + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/bimap/BUILD.bazel b/utils/bimap/BUILD.bazel new file mode 100644 index 000000000000..c8c513848f31 --- /dev/null +++ b/utils/bimap/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bimap", + srcs = ["bimap.go"], + importpath = "github.com/ava-labs/avalanchego/utils/bimap", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "bimap_test", + srcs = ["bimap_test.go"], + embed = [":bimap"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/bloom/BUILD.bazel b/utils/bloom/BUILD.bazel new file mode 100644 index 000000000000..19cf8cc61f3d --- /dev/null +++ b/utils/bloom/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bloom", + srcs = [ + "filter.go", + "hasher.go", + "metrics.go", + "optimal.go", + "read_filter.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/bloom", + visibility = ["//visibility:public"], + deps = ["@com_github_prometheus_client_golang//prometheus"], +) + +go_test( + name = "bloom_test", + srcs = [ + "filter_test.go", + "hasher_test.go", + "optimal_test.go", + "read_filter_test.go", + ], + embed = [":bloom"], + deps = [ + "//ids", + "//utils/units", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/buffer/BUILD.bazel b/utils/buffer/BUILD.bazel new file mode 100644 index 000000000000..f5e675a01b0d --- /dev/null +++ b/utils/buffer/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "buffer", + srcs = [ + "bounded_nonblocking_queue.go", + "unbounded_blocking_deque.go", + "unbounded_deque.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/buffer", + visibility = ["//visibility:public"], + deps = ["//utils"], +) + +go_test( + name = "buffer_test", + srcs = [ + "bounded_nonblocking_queue_test.go", + "unbounded_blocking_deque_test.go", + "unbounded_deque_test.go", + ], + embed = [":buffer"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/cb58/BUILD.bazel b/utils/cb58/BUILD.bazel new file mode 100644 index 000000000000..3b210858f2f3 --- /dev/null +++ b/utils/cb58/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "cb58", + srcs = ["cb58.go"], + importpath = "github.com/ava-labs/avalanchego/utils/cb58", + visibility = ["//visibility:public"], + deps = [ + "//utils/hashing", + "@com_github_mr_tron_base58//base58", + ], +) + +go_test( + name = "cb58_test", + srcs = ["cb58_test.go"], + embed = [":cb58"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/compression/BUILD.bazel b/utils/compression/BUILD.bazel new file mode 100644 index 000000000000..03fbb8237e8e --- /dev/null +++ b/utils/compression/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "compression", + srcs = [ + "compressor.go", + "no_compressor.go", + "type.go", + "zstd_compressor.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/compression", + visibility = ["//visibility:public"], + deps = ["@com_github_datadog_zstd//:zstd"], +) + +go_test( + name = "compression_test", + srcs = [ + "compressor_test.go", + "no_compressor_test.go", + "type_test.go", + ], + embed = [":compression"], + embedsrcs = ["zstd_zip_bomb.bin"], + deps = [ + "//utils", + "//utils/units", + "@com_github_datadog_zstd//:zstd", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/constants/BUILD.bazel b/utils/constants/BUILD.bazel new file mode 100644 index 000000000000..87ad75737614 --- /dev/null +++ b/utils/constants/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "constants", + srcs = [ + "acps.go", + "aliases.go", + "application.go", + "memory.go", + "network_ids.go", + "networking.go", + "vm_ids.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/constants", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/compression", + "//utils/set", + "//utils/units", + ], +) + +go_test( + name = "constants_test", + srcs = ["network_ids_test.go"], + embed = [":constants"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/crypto/bls/BUILD.bazel b/utils/crypto/bls/BUILD.bazel new file mode 100644 index 000000000000..8fe7042f0a83 --- /dev/null +++ b/utils/crypto/bls/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "bls", + srcs = [ + "ciphersuite.go", + "public.go", + "signature.go", + "signer.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/bls", + visibility = ["//visibility:public"], + deps = ["@com_github_supranational_blst//bindings/go"], +) + +go_test( + name = "bls_test", + srcs = [ + "bls_benchmark_test.go", + "bls_test.go", + "public_test.go", + "signature_test.go", + ], + embed = [":bls"], + deps = [ + "//utils", + "//utils/crypto/bls/blstest", + "@com_github_stretchr_testify//require", + "@com_github_supranational_blst//bindings/go", + ], +) diff --git a/utils/crypto/bls/blstest/BUILD.bazel b/utils/crypto/bls/blstest/BUILD.bazel new file mode 100644 index 000000000000..15236a703e26 --- /dev/null +++ b/utils/crypto/bls/blstest/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "blstest", + srcs = ["benchmark.go"], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/bls/blstest", + visibility = ["//visibility:public"], +) diff --git a/utils/crypto/bls/signer/localsigner/BUILD.bazel b/utils/crypto/bls/signer/localsigner/BUILD.bazel new file mode 100644 index 000000000000..822c8535954f --- /dev/null +++ b/utils/crypto/bls/signer/localsigner/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "localsigner", + srcs = ["localsigner.go"], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner", + visibility = ["//visibility:public"], + deps = [ + "//utils/crypto/bls", + "//utils/perms", + "@com_github_supranational_blst//bindings/go", + ], +) + +go_test( + name = "localsigner_test", + srcs = [ + "benchmark_test.go", + "bls_test.go", + "serialization_test.go", + ], + embed = [":localsigner"], + deps = [ + "//utils", + "//utils/crypto/bls", + "//utils/crypto/bls/blstest", + "@com_github_stretchr_testify//require", + "@com_github_supranational_blst//bindings/go", + ], +) diff --git a/utils/crypto/bls/signer/rpcsigner/BUILD.bazel b/utils/crypto/bls/signer/rpcsigner/BUILD.bazel new file mode 100644 index 000000000000..eac8b96ec7cf --- /dev/null +++ b/utils/crypto/bls/signer/rpcsigner/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "rpcsigner", + srcs = ["client.go"], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/rpcsigner", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/signer", + "//utils/crypto/bls", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//backoff", + "@org_golang_google_grpc//credentials/insecure", + ], +) + +go_test( + name = "rpcsigner_test", + srcs = ["client_test.go"], + embed = [":rpcsigner"], + deps = [ + "//proto/pb/signer", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + ], +) diff --git a/utils/crypto/keychain/BUILD.bazel b/utils/crypto/keychain/BUILD.bazel new file mode 100644 index 000000000000..9b26a0d56977 --- /dev/null +++ b/utils/crypto/keychain/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "keychain", + srcs = ["keychain.go"], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/keychain", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/set", + ], +) diff --git a/utils/crypto/secp256k1/BUILD.bazel b/utils/crypto/secp256k1/BUILD.bazel new file mode 100644 index 000000000000..5c221b016982 --- /dev/null +++ b/utils/crypto/secp256k1/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "secp256k1", + srcs = [ + "secp256k1.go", + "test_keys.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/crypto/secp256k1", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//ids", + "//utils/cb58", + "//utils/hashing", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//crypto", + "@com_github_decred_dcrd_dcrec_secp256k1_v4//:secp256k1", + "@com_github_decred_dcrd_dcrec_secp256k1_v4//ecdsa", + ], +) + +go_test( + name = "secp256k1_test", + srcs = [ + "rfc6979_test.go", + "secp256k1_benchmark_test.go", + "secp256k1_test.go", + ], + embed = [":secp256k1"], + deps = [ + "//utils", + "//utils/cb58", + "//utils/hashing", + "@com_github_decred_dcrd_dcrec_secp256k1_v4//:secp256k1", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/dynamicip/BUILD.bazel b/utils/dynamicip/BUILD.bazel new file mode 100644 index 000000000000..00c31c8196e8 --- /dev/null +++ b/utils/dynamicip/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "dynamicip", + srcs = [ + "ifconfig_resolver.go", + "no_updater.go", + "opendns_resolver.go", + "resolver.go", + "updater.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/dynamicip", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/ips", + "//utils/logging", + "//utils/rpc", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "dynamicip_test", + srcs = [ + "resolver_test.go", + "updater_test.go", + ], + embed = [":dynamicip"], + deps = [ + "//utils", + "//utils/logging", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/filesystem/BUILD.bazel b/utils/filesystem/BUILD.bazel new file mode 100644 index 000000000000..1ca8cf13a2fc --- /dev/null +++ b/utils/filesystem/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "filesystem", + srcs = [ + "io.go", + "mock_file.go", + "rename.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/filesystem", + visibility = ["//visibility:public"], +) + +go_test( + name = "filesystem_test", + srcs = [ + "mocks_generate_test.go", + "rename_test.go", + ], + embed = [":filesystem"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/filesystem/filesystemmock/BUILD.bazel b/utils/filesystem/filesystemmock/BUILD.bazel new file mode 100644 index 000000000000..fb322d8747b5 --- /dev/null +++ b/utils/filesystem/filesystemmock/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "filesystemmock", + srcs = ["reader.go"], + importpath = "github.com/ava-labs/avalanchego/utils/filesystem/filesystemmock", + visibility = ["//visibility:public"], + deps = ["@org_uber_go_mock//gomock"], +) diff --git a/utils/formatting/BUILD.bazel b/utils/formatting/BUILD.bazel new file mode 100644 index 000000000000..0f06a6875f5d --- /dev/null +++ b/utils/formatting/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "formatting", + srcs = [ + "encoding.go", + "int_format.go", + "prefixed_stringer.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/formatting", + visibility = ["//visibility:public"], + deps = ["//utils/hashing"], +) + +go_test( + name = "formatting_test", + srcs = [ + "encoding_benchmark_test.go", + "encoding_test.go", + "int_format_test.go", + ], + embed = [":formatting"], + deps = [ + "//utils/units", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/formatting/address/BUILD.bazel b/utils/formatting/address/BUILD.bazel new file mode 100644 index 000000000000..8f4a1dab5807 --- /dev/null +++ b/utils/formatting/address/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "address", + srcs = [ + "address.go", + "converter.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/formatting/address", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@com_github_btcsuite_btcd_btcutil//bech32", + ], +) diff --git a/utils/hashing/BUILD.bazel b/utils/hashing/BUILD.bazel new file mode 100644 index 000000000000..f9695379d1a2 --- /dev/null +++ b/utils/hashing/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "hashing", + srcs = [ + "hasher.go", + "hashing.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/hashing", + visibility = ["//visibility:public"], + deps = ["@org_golang_x_crypto//ripemd160"], +) + +go_test( + name = "hashing_test", + srcs = ["mocks_generate_test.go"], + embed = [":hashing"], +) diff --git a/utils/hashing/consistent/BUILD.bazel b/utils/hashing/consistent/BUILD.bazel new file mode 100644 index 000000000000..12d83cf0408d --- /dev/null +++ b/utils/hashing/consistent/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "consistent", + srcs = [ + "hashable.go", + "ring.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/hashing/consistent", + visibility = ["//visibility:public"], + deps = [ + "//utils/hashing", + "@com_github_google_btree//:btree", + ], +) + +go_test( + name = "consistent_test", + srcs = ["ring_test.go"], + embed = [":consistent"], + deps = [ + "//utils/hashing/hashingmock", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/utils/hashing/hashingmock/BUILD.bazel b/utils/hashing/hashingmock/BUILD.bazel new file mode 100644 index 000000000000..c73007b2b853 --- /dev/null +++ b/utils/hashing/hashingmock/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "hashingmock", + srcs = ["hasher.go"], + importpath = "github.com/ava-labs/avalanchego/utils/hashing/hashingmock", + visibility = ["//visibility:public"], + deps = ["@org_uber_go_mock//gomock"], +) diff --git a/utils/heap/BUILD.bazel b/utils/heap/BUILD.bazel new file mode 100644 index 000000000000..f6cf1d720071 --- /dev/null +++ b/utils/heap/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "heap", + srcs = [ + "map.go", + "queue.go", + "set.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/heap", + visibility = ["//visibility:public"], + deps = ["//utils"], +) + +go_test( + name = "heap_test", + srcs = [ + "map_test.go", + "queue_test.go", + "set_test.go", + ], + embed = [":heap"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/ips/BUILD.bazel b/utils/ips/BUILD.bazel new file mode 100644 index 000000000000..98567ead043a --- /dev/null +++ b/utils/ips/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ips", + srcs = [ + "claimed_ip_port.go", + "ip.go", + "lookup.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/ips", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//staking", + "//utils/hashing", + "//utils/wrappers", + ], +) + +go_test( + name = "ips_test", + srcs = ["lookup_test.go"], + embed = [":ips"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/iterator/BUILD.bazel b/utils/iterator/BUILD.bazel new file mode 100644 index 000000000000..632578075a7f --- /dev/null +++ b/utils/iterator/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "iterator", + srcs = [ + "empty.go", + "filter.go", + "iterator.go", + "merge.go", + "slice.go", + "tree.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/iterator", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/heap", + "//utils/set", + "@com_github_google_btree//:btree", + ], +) + +go_test( + name = "iterator_test", + srcs = [ + "empty_test.go", + "filter_test.go", + "merge_test.go", + "tree_test.go", + ], + embed = [":iterator"], + deps = [ + "//ids", + "//vms/platformvm/state", + "@com_github_google_btree//:btree", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/json/BUILD.bazel b/utils/json/BUILD.bazel new file mode 100644 index 000000000000..d71077a87d8a --- /dev/null +++ b/utils/json/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "json", + srcs = [ + "codec.go", + "float32.go", + "float64.go", + "uint16.go", + "uint32.go", + "uint64.go", + "uint8.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/json", + visibility = ["//visibility:public"], + deps = [ + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_gorilla_rpc//v2/json2", + ], +) + +go_test( + name = "json_test", + srcs = ["float32_test.go"], + embed = [":json"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/linked/BUILD.bazel b/utils/linked/BUILD.bazel new file mode 100644 index 000000000000..9cd33674cf5e --- /dev/null +++ b/utils/linked/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "linked", + srcs = [ + "hashmap.go", + "list.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/linked", + visibility = ["//visibility:public"], + deps = ["//utils"], +) + +go_test( + name = "linked_test", + srcs = [ + "hashmap_test.go", + "list_test.go", + ], + embed = [":linked"], + deps = [ + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/lock/BUILD.bazel b/utils/lock/BUILD.bazel new file mode 100644 index 000000000000..a756399513fd --- /dev/null +++ b/utils/lock/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "lock", + srcs = ["cond.go"], + importpath = "github.com/ava-labs/avalanchego/utils/lock", + visibility = ["//visibility:public"], +) + +go_test( + name = "lock_test", + srcs = ["cond_test.go"], + embed = [":lock"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/logging/BUILD.bazel b/utils/logging/BUILD.bazel new file mode 100644 index 000000000000..6f688c3d3f8b --- /dev/null +++ b/utils/logging/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "logging", + srcs = [ + "color.go", + "config.go", + "factory.go", + "format.go", + "level.go", + "log.go", + "logger.go", + "sanitize.go", + "test_log.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/logging", + visibility = ["//visibility:public"], + deps = [ + "@in_gopkg_natefinch_lumberjack_v2//:lumberjack_v2", + "@org_golang_x_exp//maps", + "@org_golang_x_term//:term", + "@org_uber_go_zap//:zap", + "@org_uber_go_zap//zapcore", + ], +) + +go_test( + name = "logging_test", + srcs = ["log_test.go"], + embed = [":logging"], + deps = [ + "@com_github_stretchr_testify//require", + "@org_uber_go_zap//:zap", + "@org_uber_go_zap//zapcore", + ], +) diff --git a/utils/math/BUILD.bazel b/utils/math/BUILD.bazel new file mode 100644 index 000000000000..1a2c60900e28 --- /dev/null +++ b/utils/math/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "math", + srcs = [ + "averager.go", + "averager_heap.go", + "continuous_averager.go", + "safe_math.go", + "sync_averager.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/math", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/heap", + "@org_golang_x_exp//constraints", + ], +) + +go_test( + name = "math_test", + srcs = [ + "averager_heap_test.go", + "continuous_averager_benchmark_test.go", + "continuous_averager_test.go", + "safe_math_test.go", + ], + embed = [":math"], + deps = [ + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/math/meter/BUILD.bazel b/utils/math/meter/BUILD.bazel new file mode 100644 index 000000000000..185277a5a322 --- /dev/null +++ b/utils/math/meter/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "meter", + srcs = [ + "continuous_meter.go", + "factory.go", + "meter.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/math/meter", + visibility = ["//visibility:public"], +) + +go_test( + name = "meter_test", + srcs = [ + "meter_benchmark_test.go", + "meter_test.go", + ], + embed = [":meter"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/maybe/BUILD.bazel b/utils/maybe/BUILD.bazel new file mode 100644 index 000000000000..8fe2a901c026 --- /dev/null +++ b/utils/maybe/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "maybe", + srcs = ["maybe.go"], + importpath = "github.com/ava-labs/avalanchego/utils/maybe", + visibility = ["//visibility:public"], +) + +go_test( + name = "maybe_test", + srcs = ["maybe_test.go"], + embed = [":maybe"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/metric/BUILD.bazel b/utils/metric/BUILD.bazel new file mode 100644 index 000000000000..e2c9f39e8dee --- /dev/null +++ b/utils/metric/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "metric", + srcs = [ + "api_interceptor.go", + "averager.go", + "namespace.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/metric", + visibility = ["//visibility:public"], + deps = [ + "//utils/wrappers", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "metric_test", + srcs = ["namespace_test.go"], + embed = [":metric"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/packages/BUILD.bazel b/utils/packages/BUILD.bazel new file mode 100644 index 000000000000..12c504eabe90 --- /dev/null +++ b/utils/packages/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "packages", + srcs = ["dependencies.go"], + importpath = "github.com/ava-labs/avalanchego/utils/packages", + visibility = ["//visibility:public"], + deps = [ + "//utils/set", + "@org_golang_x_tools//go/packages", + ], +) diff --git a/utils/password/BUILD.bazel b/utils/password/BUILD.bazel new file mode 100644 index 000000000000..7e63e2f8f5bf --- /dev/null +++ b/utils/password/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "password", + srcs = [ + "hash.go", + "password.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/password", + visibility = ["//visibility:public"], + deps = [ + "@com_github_nbutton23_zxcvbn_go//:zxcvbn-go", + "@org_golang_x_crypto//argon2", + ], +) + +go_test( + name = "password_test", + srcs = [ + "hash_test.go", + "password_test.go", + ], + embed = [":password"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/perms/BUILD.bazel b/utils/perms/BUILD.bazel new file mode 100644 index 000000000000..d70ed10d83e4 --- /dev/null +++ b/utils/perms/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "perms", + srcs = [ + "chmod.go", + "create.go", + "perms.go", + "write_file.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/perms", + visibility = ["//visibility:public"], + deps = ["@com_github_google_renameio_v2//maybe"], +) diff --git a/utils/profiler/BUILD.bazel b/utils/profiler/BUILD.bazel new file mode 100644 index 000000000000..8b80bfcf140f --- /dev/null +++ b/utils/profiler/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "profiler", + srcs = [ + "continuous.go", + "profiler.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/profiler", + visibility = ["//visibility:public"], + deps = [ + "//utils/filesystem", + "//utils/perms", + "@org_golang_x_sync//errgroup", + ], +) + +go_test( + name = "profiler_test", + srcs = ["profiler_test.go"], + embed = [":profiler"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/resource/BUILD.bazel b/utils/resource/BUILD.bazel new file mode 100644 index 000000000000..aff50d83702c --- /dev/null +++ b/utils/resource/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "resource", + srcs = [ + "metrics.go", + "no_usage.go", + "usage.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/resource", + visibility = ["//visibility:public"], + deps = [ + "//utils/logging", + "//utils/storage", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_shirou_gopsutil//cpu", + "@com_github_shirou_gopsutil//process", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "resource_test", + srcs = [ + "mocks_generate_test.go", + "usage_test.go", + ], + embed = [":resource"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/resource/resourcemock/BUILD.bazel b/utils/resource/resourcemock/BUILD.bazel new file mode 100644 index 000000000000..3f8984c5c560 --- /dev/null +++ b/utils/resource/resourcemock/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "resourcemock", + srcs = ["user.go"], + importpath = "github.com/ava-labs/avalanchego/utils/resource/resourcemock", + visibility = ["//visibility:public"], + deps = ["@org_uber_go_mock//gomock"], +) diff --git a/utils/rpc/BUILD.bazel b/utils/rpc/BUILD.bazel new file mode 100644 index 000000000000..a387af3d9e05 --- /dev/null +++ b/utils/rpc/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "rpc", + srcs = [ + "json.go", + "options.go", + "requester.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/rpc", + visibility = ["//visibility:public"], + deps = ["@com_github_gorilla_rpc//v2/json2"], +) diff --git a/utils/sampler/BUILD.bazel b/utils/sampler/BUILD.bazel new file mode 100644 index 000000000000..97b5f7cf9c81 --- /dev/null +++ b/utils/sampler/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "sampler", + srcs = [ + "rand.go", + "uniform.go", + "uniform_replacer.go", + "weighted.go", + "weighted_heap.go", + "weighted_without_replacement.go", + "weighted_without_replacement_generic.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/sampler", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/math", + "@org_gonum_v1_gonum//mathext/prng", + ], +) + +go_test( + name = "sampler_test", + srcs = [ + "rand_test.go", + "uniform_benchmark_test.go", + "uniform_test.go", + "weighted_benchmark_test.go", + "weighted_heap_test.go", + "weighted_test.go", + "weighted_without_replacement_benchmark_test.go", + "weighted_without_replacement_test.go", + ], + embed = [":sampler"], + deps = [ + "//utils/math", + "@com_github_stretchr_testify//require", + "@com_github_thepudds_fzgen//fuzzer", + "@org_gonum_v1_gonum//mathext/prng", + ], +) diff --git a/utils/set/BUILD.bazel b/utils/set/BUILD.bazel new file mode 100644 index 000000000000..c6d777d5b709 --- /dev/null +++ b/utils/set/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "set", + srcs = [ + "bits.go", + "bits_64.go", + "sampleable_set.go", + "set.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/set", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/json", + "//utils/sampler", + "//utils/wrappers", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "set_test", + srcs = [ + "bits_64_test.go", + "bits_test.go", + "sampleable_set_test.go", + "set_benchmark_test.go", + "set_test.go", + ], + embed = [":set"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/setmap/BUILD.bazel b/utils/setmap/BUILD.bazel new file mode 100644 index 000000000000..9cd7718903e2 --- /dev/null +++ b/utils/setmap/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "setmap", + srcs = ["setmap.go"], + importpath = "github.com/ava-labs/avalanchego/utils/setmap", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/set", + ], +) + +go_test( + name = "setmap_test", + srcs = ["setmap_test.go"], + embed = [":setmap"], + deps = [ + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/storage/BUILD.bazel b/utils/storage/BUILD.bazel new file mode 100644 index 000000000000..697e1dd82fef --- /dev/null +++ b/utils/storage/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "storage", + srcs = [ + "storage_common.go", + "storage_openbsd.go", + "storage_unix.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/storage", + visibility = ["//visibility:public"], +) diff --git a/utils/timer/BUILD.bazel b/utils/timer/BUILD.bazel new file mode 100644 index 000000000000..817a9a2a8bc6 --- /dev/null +++ b/utils/timer/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "timer", + srcs = [ + "adaptive_timeout_manager.go", + "eta.go", + "meter.go", + "stopped_timer.go", + "timer.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/timer", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/heap", + "//utils/math", + "//utils/timer/mockable", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "timer_test", + srcs = [ + "adaptive_timeout_manager_test.go", + "eta_test.go", + "timer_test.go", + ], + embed = [":timer"], + deps = [ + "//ids", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/timer/mockable/BUILD.bazel b/utils/timer/mockable/BUILD.bazel new file mode 100644 index 000000000000..444d5956a88d --- /dev/null +++ b/utils/timer/mockable/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "mockable", + srcs = ["clock.go"], + importpath = "github.com/ava-labs/avalanchego/utils/timer/mockable", + visibility = ["//visibility:public"], +) + +go_test( + name = "mockable_test", + srcs = ["clock_test.go"], + embed = [":mockable"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/utils/tree/BUILD.bazel b/utils/tree/BUILD.bazel new file mode 100644 index 000000000000..61cd4770e1ae --- /dev/null +++ b/utils/tree/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "tree", + srcs = ["tree.go"], + importpath = "github.com/ava-labs/avalanchego/utils/tree", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "tree_test", + srcs = ["tree_test.go"], + embed = [":tree"], + deps = [ + "//snow/consensus/snowman/snowmantest", + "//snow/snowtest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/ulimit/BUILD.bazel b/utils/ulimit/BUILD.bazel new file mode 100644 index 000000000000..2bc5fa20ea2c --- /dev/null +++ b/utils/ulimit/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "ulimit", + srcs = [ + "ulimit_bsd.go", + "ulimit_darwin.go", + "ulimit_unix.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/ulimit", + visibility = ["//visibility:public"], + deps = select({ + "@rules_go//go/platform:android": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:darwin": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:freebsd": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:ios": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:linux": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:netbsd": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "@rules_go//go/platform:openbsd": [ + "//utils/logging", + "@org_uber_go_zap//:zap", + ], + "//conditions:default": [], + }), +) diff --git a/utils/units/BUILD.bazel b/utils/units/BUILD.bazel new file mode 100644 index 000000000000..baeb53849f2b --- /dev/null +++ b/utils/units/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "units", + srcs = [ + "avax.go", + "bytes.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/units", + visibility = ["//visibility:public"], +) diff --git a/utils/window/BUILD.bazel b/utils/window/BUILD.bazel new file mode 100644 index 000000000000..4b49790adc23 --- /dev/null +++ b/utils/window/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "window", + srcs = ["window.go"], + importpath = "github.com/ava-labs/avalanchego/utils/window", + visibility = ["//visibility:public"], + deps = [ + "//utils", + "//utils/buffer", + "//utils/timer/mockable", + ], +) + +go_test( + name = "window_test", + srcs = ["window_test.go"], + embed = [":window"], + deps = [ + "//utils/timer/mockable", + "@com_github_stretchr_testify//require", + ], +) diff --git a/utils/wrappers/BUILD.bazel b/utils/wrappers/BUILD.bazel new file mode 100644 index 000000000000..dc6ffd4abcd9 --- /dev/null +++ b/utils/wrappers/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "wrappers", + srcs = [ + "closers.go", + "errors.go", + "packing.go", + ], + importpath = "github.com/ava-labs/avalanchego/utils/wrappers", + visibility = ["//visibility:public"], +) + +go_test( + name = "wrappers_test", + srcs = ["packing_test.go"], + embed = [":wrappers"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/version/BUILD.bazel b/version/BUILD.bazel new file mode 100644 index 000000000000..3156a58b0e1b --- /dev/null +++ b/version/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "version", + srcs = [ + "application.go", + "compatibility.go", + "constants.go", + "string.go", + ], + embedsrcs = ["compatibility.json"], + importpath = "github.com/ava-labs/avalanchego/version", + visibility = ["//visibility:public"], + deps = ["//utils/timer/mockable"], +) + +go_test( + name = "version_test", + srcs = [ + "application_test.go", + "compatibility_test.go", + "constants_test.go", + "string_test.go", + ], + embed = [":version"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/BUILD.bazel b/vms/BUILD.bazel new file mode 100644 index 000000000000..d0235952c9b5 --- /dev/null +++ b/vms/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "vms", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/vms", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/engine/common", + "//utils/logging", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "vms_test", + srcs = ["mocks_generate_test.go"], + embed = [":vms"], +) diff --git a/vms/avm/BUILD.bazel b/vms/avm/BUILD.bazel new file mode 100644 index 000000000000..d46234018187 --- /dev/null +++ b/vms/avm/BUILD.bazel @@ -0,0 +1,124 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "avm", + srcs = [ + "client.go", + "config.go", + "factory.go", + "genesis.go", + "health.go", + "service.go", + "tx.go", + "tx_init.go", + "vm.go", + "wallet_client.go", + "wallet_service.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//api/metrics", + "//codec", + "//database", + "//database/versiondb", + "//ids", + "//snow", + "//snow/choices", + "//snow/consensus/snowman", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//utils", + "//utils/constants", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/linked", + "//utils/logging", + "//utils/math", + "//utils/rpc", + "//utils/set", + "//utils/timer/mockable", + "//version", + "//vms", + "//vms/avm/block", + "//vms/avm/block/builder", + "//vms/avm/block/executor", + "//vms/avm/config", + "//vms/avm/fxs", + "//vms/avm/metrics", + "//vms/avm/network", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/avm/txs/executor", + "//vms/avm/txs/mempool", + "//vms/avm/utxo", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//vms/txs/mempool", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "avm_test", + srcs = [ + "config_test.go", + "environment_test.go", + "fx_test.go", + "genesis_test.go", + "service_test.go", + "state_test.go", + "vm_benchmark_test.go", + "vm_regression_test.go", + "vm_test.go", + ], + embed = [":avm"], + deps = [ + "//api", + "//chains/atomic", + "//codec", + "//database", + "//database/memdb", + "//database/prefixdb", + "//ids", + "//snow", + "//snow/choices", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/snowtest", + "//upgrade/upgradetest", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/logging", + "//utils/set", + "//utils/units", + "//vms/avm/block", + "//vms/avm/block/executor", + "//vms/avm/block/executor/executormock", + "//vms/avm/config", + "//vms/avm/fxs", + "//vms/avm/network", + "//vms/avm/state/statemock", + "//vms/avm/txs", + "//vms/avm/txs/txstest", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "@com_github_btcsuite_btcd_btcutil//bech32", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/block/BUILD.bazel b/vms/avm/block/BUILD.bazel new file mode 100644 index 000000000000..693aa181ca45 --- /dev/null +++ b/vms/avm/block/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "block", + srcs = [ + "block.go", + "mock_block.go", + "parser.go", + "standard_block.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/block", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//ids", + "//snow", + "//utils/hashing", + "//utils/logging", + "//utils/timer/mockable", + "//vms/avm/fxs", + "//vms/avm/txs", + "@org_uber_go_mock//gomock", + ], +) + +go_test( + name = "block_test", + srcs = [ + "block_test.go", + "mocks_generate_test.go", + ], + embed = [":block"], + deps = [ + "//codec", + "//ids", + "//utils/constants", + "//utils/crypto/secp256k1", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/avm/block/builder/BUILD.bazel b/vms/avm/block/builder/BUILD.bazel new file mode 100644 index 000000000000..94dff743d93b --- /dev/null +++ b/vms/avm/block/builder/BUILD.bazel @@ -0,0 +1,57 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "builder", + srcs = ["builder.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/block/builder", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "//snow/engine/common", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//vms/avm/block", + "//vms/avm/block/executor", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/avm/txs/executor", + "//vms/txs/mempool", + ], +) + +go_test( + name = "builder_test", + srcs = ["builder_test.go"], + embed = [":builder"], + deps = [ + "//codec", + "//codec/codecmock", + "//database/memdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/timer/mockable", + "//vms/avm/block", + "//vms/avm/block/executor", + "//vms/avm/block/executor/executormock", + "//vms/avm/fxs", + "//vms/avm/metrics", + "//vms/avm/state", + "//vms/avm/state/statemock", + "//vms/avm/txs", + "//vms/avm/txs/executor", + "//vms/avm/txs/mempool", + "//vms/avm/txs/txsmock", + "//vms/components/avax", + "//vms/secp256k1fx", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/block/executor/BUILD.bazel b/vms/avm/block/executor/BUILD.bazel new file mode 100644 index 000000000000..88bc7d841e04 --- /dev/null +++ b/vms/avm/block/executor/BUILD.bazel @@ -0,0 +1,57 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "executor", + srcs = [ + "block.go", + "manager.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/block/executor", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//ids", + "//snow/consensus/snowman", + "//utils/set", + "//utils/timer/mockable", + "//vms/avm/block", + "//vms/avm/metrics", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/avm/txs/executor", + "//vms/txs/mempool", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "executor_test", + srcs = [ + "block_test.go", + "manager_test.go", + "mocks_generate_test.go", + ], + embed = [":executor"], + deps = [ + "//chains/atomic", + "//chains/atomic/atomicmock", + "//ids", + "//snow", + "//upgrade/upgradetest", + "//utils", + "//utils/logging", + "//utils/set", + "//utils/timer/mockable", + "//vms/avm/block", + "//vms/avm/config", + "//vms/avm/metrics/metricsmock", + "//vms/avm/state/statemock", + "//vms/avm/txs", + "//vms/avm/txs/executor", + "//vms/avm/txs/mempool", + "//vms/avm/txs/txsmock", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/block/executor/executormock/BUILD.bazel b/vms/avm/block/executor/executormock/BUILD.bazel new file mode 100644 index 000000000000..c11471711caf --- /dev/null +++ b/vms/avm/block/executor/executormock/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "executormock", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/block/executor/executormock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "//utils/set", + "//vms/avm/block", + "//vms/avm/state", + "//vms/avm/txs", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/config/BUILD.bazel b/vms/avm/config/BUILD.bazel new file mode 100644 index 000000000000..202be3fa693c --- /dev/null +++ b/vms/avm/config/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = ["config.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/config", + visibility = ["//visibility:public"], + deps = ["//upgrade"], +) diff --git a/vms/avm/fxs/BUILD.bazel b/vms/avm/fxs/BUILD.bazel new file mode 100644 index 000000000000..f8518dab59f4 --- /dev/null +++ b/vms/avm/fxs/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "fxs", + srcs = ["fx.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/fxs", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + ], +) diff --git a/vms/avm/metrics/BUILD.bazel b/vms/avm/metrics/BUILD.bazel new file mode 100644 index 000000000000..9a13c7896a9a --- /dev/null +++ b/vms/avm/metrics/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "metrics", + srcs = [ + "metrics.go", + "tx_metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/metrics", + visibility = ["//visibility:public"], + deps = [ + "//utils/metric", + "//utils/wrappers", + "//vms/avm/block", + "//vms/avm/txs", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "metrics_test", + srcs = ["mocks_generate_test.go"], + embed = [":metrics"], +) diff --git a/vms/avm/metrics/metricsmock/BUILD.bazel b/vms/avm/metrics/metricsmock/BUILD.bazel new file mode 100644 index 000000000000..e2820f27042f --- /dev/null +++ b/vms/avm/metrics/metricsmock/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "metricsmock", + srcs = ["metrics.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/metrics/metricsmock", + visibility = ["//visibility:public"], + deps = [ + "//vms/avm/block", + "//vms/avm/txs", + "@com_github_gorilla_rpc//v2:rpc", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/network/BUILD.bazel b/vms/avm/network/BUILD.bazel new file mode 100644 index 000000000000..d263c3d708d1 --- /dev/null +++ b/vms/avm/network/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "network", + srcs = [ + "atomic.go", + "config.go", + "gossip.go", + "network.go", + "tx_verifier.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/network", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//network/p2p", + "//network/p2p/gossip", + "//snow/engine/common", + "//snow/validators", + "//utils", + "//utils/logging", + "//utils/units", + "//vms/avm/txs", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "network_test", + srcs = [ + "gossip_test.go", + "network_test.go", + ], + embed = [":network"], + deps = [ + "//ids", + "//snow/engine/common", + "//snow/engine/common/commonmock", + "//snow/validators", + "//snow/validators/validatorstest", + "//utils/logging", + "//vms/avm/block/executor/executormock", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/avm/txs/mempool", + "//vms/components/avax", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/state/BUILD.bazel b/vms/avm/state/BUILD.bazel new file mode 100644 index 000000000000..0d83b3f26359 --- /dev/null +++ b/vms/avm/state/BUILD.bazel @@ -0,0 +1,48 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "state", + srcs = [ + "diff.go", + "state.go", + "versions.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/state", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//vms/avm/block", + "//vms/avm/txs", + "//vms/components/avax", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "state_test", + srcs = [ + "mocks_generate_test.go", + "state_test.go", + ], + embed = [":state"], + deps = [ + "//database", + "//database/memdb", + "//database/versiondb", + "//ids", + "//upgrade", + "//vms/avm/block", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/secp256k1fx", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/avm/state/statemock/BUILD.bazel b/vms/avm/state/statemock/BUILD.bazel new file mode 100644 index 000000000000..aa2939c146d7 --- /dev/null +++ b/vms/avm/state/statemock/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "statemock", + srcs = [ + "chain.go", + "diff.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/state/statemock", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//vms/avm/block", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/components/avax", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/txs/BUILD.bazel b/vms/avm/txs/BUILD.bazel new file mode 100644 index 000000000000..9492f1fe3058 --- /dev/null +++ b/vms/avm/txs/BUILD.bazel @@ -0,0 +1,68 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "txs", + srcs = [ + "base_tx.go", + "codec.go", + "create_asset_tx.go", + "export_tx.go", + "import_tx.go", + "initial_state.go", + "operation.go", + "operation_tx.go", + "parser.go", + "tx.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/txs", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//network/p2p/gossip", + "//snow", + "//utils", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/logging", + "//utils/set", + "//utils/timer/mockable", + "//utils/wrappers", + "//vms/avm/fxs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "txs_test", + srcs = [ + "base_tx_test.go", + "create_asset_tx_test.go", + "export_tx_test.go", + "import_tx_test.go", + "initial_state_test.go", + "mocks_generate_test.go", + "operation_test.go", + ], + embed = [":txs"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//snow", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/avm/fxs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/avm/txs/executor/BUILD.bazel b/vms/avm/txs/executor/BUILD.bazel new file mode 100644 index 000000000000..f18b362baac5 --- /dev/null +++ b/vms/avm/txs/executor/BUILD.bazel @@ -0,0 +1,66 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "executor", + srcs = [ + "backend.go", + "executor.go", + "semantic_verifier.go", + "syntactic_verifier.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/txs/executor", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//codec", + "//ids", + "//snow", + "//utils", + "//utils/set", + "//vms/avm/config", + "//vms/avm/fxs", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + ], +) + +go_test( + name = "executor_test", + srcs = [ + "executor_test.go", + "semantic_verifier_test.go", + "syntactic_verifier_test.go", + ], + embed = [":executor"], + deps = [ + "//chains/atomic", + "//database", + "//database/memdb", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow/snowtest", + "//snow/validators/validatorsmock", + "//upgrade/upgradetest", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/math", + "//utils/timer/mockable", + "//utils/units", + "//vms/avm/block", + "//vms/avm/config", + "//vms/avm/fxs", + "//vms/avm/state", + "//vms/avm/state/statemock", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/txs/mempool/BUILD.bazel b/vms/avm/txs/mempool/BUILD.bazel new file mode 100644 index 000000000000..3d54995d473f --- /dev/null +++ b/vms/avm/txs/mempool/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "mempool", + srcs = ["mempool.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/txs/mempool", + visibility = ["//visibility:public"], + deps = [ + "//vms/avm/txs", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/vms/avm/txs/txsmock/BUILD.bazel b/vms/avm/txs/txsmock/BUILD.bazel new file mode 100644 index 000000000000..a2384adcb138 --- /dev/null +++ b/vms/avm/txs/txsmock/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "txsmock", + srcs = ["tx.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/txs/txsmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils/set", + "//vms/avm/txs", + "//vms/components/avax", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/avm/txs/txstest/BUILD.bazel b/vms/avm/txs/txstest/BUILD.bazel new file mode 100644 index 000000000000..f4f2f351bce1 --- /dev/null +++ b/vms/avm/txs/txstest/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "txstest", + srcs = [ + "builder.go", + "context.go", + "utxos.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/avm/txs/txstest", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//codec", + "//database", + "//ids", + "//snow", + "//utils/set", + "//vms/avm/config", + "//vms/avm/state", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "//wallet/chain/x/builder", + "//wallet/chain/x/signer", + "//wallet/subnet/primary/common", + ], +) diff --git a/vms/avm/utxo/BUILD.bazel b/vms/avm/utxo/BUILD.bazel new file mode 100644 index 000000000000..8f4f6b901d5f --- /dev/null +++ b/vms/avm/utxo/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "utxo", + srcs = ["spender.go"], + importpath = "github.com/ava-labs/avalanchego/vms/avm/utxo", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//ids", + "//utils/crypto/secp256k1", + "//utils/math", + "//utils/timer/mockable", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/nftfx", + "//vms/secp256k1fx", + ], +) diff --git a/vms/components/avax/BUILD.bazel b/vms/components/avax/BUILD.bazel new file mode 100644 index 000000000000..d52ffa1372e8 --- /dev/null +++ b/vms/components/avax/BUILD.bazel @@ -0,0 +1,69 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "avax", + srcs = [ + "addresses.go", + "asset.go", + "atomic_utxos.go", + "base_tx.go", + "flow_checker.go", + "state.go", + "test_verifiable.go", + "transferables.go", + "utxo.go", + "utxo_fetching.go", + "utxo_handler.go", + "utxo_id.go", + "utxo_state.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/components/avax", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//chains/atomic", + "//codec", + "//database", + "//database/linkeddb", + "//database/prefixdb", + "//ids", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/formatting/address", + "//utils/math", + "//utils/set", + "//utils/wrappers", + "//vms/components/verify", + "//vms/types", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "avax_test", + srcs = [ + "asset_test.go", + "mocks_generate_test.go", + "transferables_test.go", + "utxo_fetching_test.go", + "utxo_id_test.go", + "utxo_state_test.go", + "utxo_test.go", + ], + embed = [":avax"], + deps = [ + "//codec", + "//codec/linearcodec", + "//database", + "//database/memdb", + "//ids", + "//utils", + "//utils/set", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/components/avax/avaxmock/BUILD.bazel b/vms/components/avax/avaxmock/BUILD.bazel new file mode 100644 index 000000000000..3968be6eb403 --- /dev/null +++ b/vms/components/avax/avaxmock/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "avaxmock", + srcs = [ + "transferable_in.go", + "transferable_out.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/components/avax/avaxmock", + visibility = ["//visibility:public"], + deps = [ + "//snow", + "//vms/components/verify", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/components/chain/BUILD.bazel b/vms/components/chain/BUILD.bazel new file mode 100644 index 000000000000..7eb63c82e910 --- /dev/null +++ b/vms/components/chain/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "chain", + srcs = [ + "block.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/components/chain", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//database", + "//ids", + "//snow/consensus/snowman", + "//snow/engine/snowman/block", + "//utils/constants", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "chain_test", + srcs = ["state_test.go"], + embed = [":chain"], + deps = [ + "//database", + "//ids", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmantest", + "//snow/snowtest", + "//utils/hashing", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/components/gas/BUILD.bazel b/vms/components/gas/BUILD.bazel new file mode 100644 index 000000000000..ba3f1acbe6c0 --- /dev/null +++ b/vms/components/gas/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gas", + srcs = [ + "config.go", + "dimensions.go", + "gas.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/components/gas", + visibility = ["//visibility:public"], + deps = [ + "//utils/math", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "gas_test", + srcs = [ + "dimensions_test.go", + "gas_test.go", + "state_test.go", + ], + embed = [":gas"], + deps = [ + "//utils/math", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/components/verify/BUILD.bazel b/vms/components/verify/BUILD.bazel new file mode 100644 index 000000000000..f3852e36de87 --- /dev/null +++ b/vms/components/verify/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "verify", + srcs = [ + "subnet.go", + "verification.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/components/verify", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + ], +) + +go_test( + name = "verify_test", + srcs = [ + "mocks_generate_test.go", + "subnet_test.go", + "verification_test.go", + ], + embed = [":verify"], + deps = [ + "//ids", + "//snow", + "//snow/validators/validatorsmock", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/components/verify/verifymock/BUILD.bazel b/vms/components/verify/verifymock/BUILD.bazel new file mode 100644 index 000000000000..093319e18c1e --- /dev/null +++ b/vms/components/verify/verifymock/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "verifymock", + srcs = ["verifiable.go"], + importpath = "github.com/ava-labs/avalanchego/vms/components/verify/verifymock", + visibility = ["//visibility:public"], + deps = ["@org_uber_go_mock//gomock"], +) diff --git a/vms/evm/acp176/BUILD.bazel b/vms/evm/acp176/BUILD.bazel new file mode 100644 index 000000000000..5b6c6b78efed --- /dev/null +++ b/vms/evm/acp176/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "acp176", + srcs = ["acp176.go"], + importpath = "github.com/ava-labs/avalanchego/vms/evm/acp176", + visibility = ["//visibility:public"], + deps = [ + "//utils/math", + "//utils/wrappers", + "//vms/components/gas", + "@com_github_holiman_uint256//:uint256", + ], +) + +go_test( + name = "acp176_test", + srcs = ["acp176_test.go"], + embed = [":acp176"], + deps = [ + "//vms/components/gas", + "@com_github_ava_labs_libevm//common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/acp226/BUILD.bazel b/vms/evm/acp226/BUILD.bazel new file mode 100644 index 000000000000..9222163ce964 --- /dev/null +++ b/vms/evm/acp226/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "acp226", + srcs = ["acp226.go"], + importpath = "github.com/ava-labs/avalanchego/vms/evm/acp226", + visibility = ["//visibility:public"], + deps = [ + "//utils/math", + "//vms/components/gas", + ], +) + +go_test( + name = "acp226_test", + srcs = ["acp226_test.go"], + embed = [":acp226"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/evm/database/BUILD.bazel b/vms/evm/database/BUILD.bazel new file mode 100644 index 000000000000..f475a84597ab --- /dev/null +++ b/vms/evm/database/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "database", + srcs = ["database.go"], + importpath = "github.com/ava-labs/avalanchego/vms/evm/database", + visibility = ["//visibility:public"], + deps = [ + "//database", + "@com_github_ava_labs_libevm//ethdb", + ], +) + +go_test( + name = "database_test", + srcs = ["database_test.go"], + embed = [":database"], + deps = [ + "//database/memdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//ethdb/dbtest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/emulate/BUILD.bazel b/vms/evm/emulate/BUILD.bazel new file mode 100644 index 000000000000..14d836e9e568 --- /dev/null +++ b/vms/evm/emulate/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "emulate", + srcs = ["emulate.go"], + importpath = "github.com/ava-labs/avalanchego/vms/evm/emulate", + visibility = ["//visibility:public"], + deps = [ + "//graft/coreth/plugin/evm", + "@com_github_ava_labs_subnet_evm//plugin/evm", + ], +) + +go_test( + name = "emulate_test", + srcs = ["emulate_test.go"], + embed = [":emulate"], + deps = [ + "//graft/coreth/plugin/evm/customtypes", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_subnet_evm//plugin/evm/customtypes", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/metrics/metricstest/BUILD.bazel b/vms/evm/metrics/metricstest/BUILD.bazel new file mode 100644 index 000000000000..feb6ed384ec1 --- /dev/null +++ b/vms/evm/metrics/metricstest/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "metricstest", + srcs = ["metrics.go"], + importpath = "github.com/ava-labs/avalanchego/vms/evm/metrics/metricstest", + visibility = ["//visibility:public"], + deps = ["@com_github_ava_labs_libevm//metrics"], +) diff --git a/vms/evm/metrics/prometheus/BUILD.bazel b/vms/evm/metrics/prometheus/BUILD.bazel new file mode 100644 index 000000000000..5fd62cc26e12 --- /dev/null +++ b/vms/evm/metrics/prometheus/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "prometheus", + srcs = [ + "interfaces.go", + "prometheus.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/evm/metrics/prometheus", + visibility = ["//visibility:public"], + deps = [ + "@com_github_ava_labs_libevm//metrics", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_model//go", + ], +) + +go_test( + name = "prometheus_test", + srcs = [ + "enabled_test.go", + "prometheus_test.go", + ], + embed = [":prometheus"], + deps = [ + "//vms/evm/metrics/metricstest", + "@com_github_ava_labs_libevm//metrics", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/predicate/BUILD.bazel b/vms/evm/predicate/BUILD.bazel new file mode 100644 index 000000000000..8d4cbc3c1445 --- /dev/null +++ b/vms/evm/predicate/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "predicate", + srcs = [ + "predicate.go", + "results.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/evm/predicate", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//utils/set", + "//utils/units", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + ], +) + +go_test( + name = "predicate_test", + srcs = [ + "predicate_test.go", + "results_test.go", + ], + embed = [":predicate"], + deps = [ + "//codec", + "//utils/set", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/types", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/sync/customrawdb/BUILD.bazel b/vms/evm/sync/customrawdb/BUILD.bazel new file mode 100644 index 000000000000..8859fdd4ca03 --- /dev/null +++ b/vms/evm/sync/customrawdb/BUILD.bazel @@ -0,0 +1,40 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "customrawdb", + srcs = [ + "db.go", + "markers.go", + "sync_progress.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/evm/sync/customrawdb", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//utils/wrappers", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//params", + "@com_github_ava_labs_libevm//rlp", + ], +) + +go_test( + name = "customrawdb_test", + srcs = [ + "db_test.go", + "markers_test.go", + "sync_progress_test.go", + ], + embed = [":customrawdb"], + deps = [ + "//database", + "//utils/set", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//ethdb", + "@com_github_ava_labs_libevm//params", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/evm/uptimetracker/BUILD.bazel b/vms/evm/uptimetracker/BUILD.bazel new file mode 100644 index 000000000000..95ba05c67543 --- /dev/null +++ b/vms/evm/uptimetracker/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "uptimetracker", + srcs = [ + "state.go", + "uptime_tracker.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/evm/uptimetracker", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//database", + "//ids", + "//snow/uptime", + "//snow/validators", + "//utils/set", + "//utils/timer/mockable", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "uptimetracker_test", + srcs = ["uptime_tracker_test.go"], + embed = [":uptimetracker"], + deps = [ + "//database/memdb", + "//ids", + "//snow/validators", + "//snow/validators/validatorstest", + "//utils/timer/mockable", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/example/xsvm/BUILD.bazel b/vms/example/xsvm/BUILD.bazel new file mode 100644 index 000000000000..2d9f15e9a6bc --- /dev/null +++ b/vms/example/xsvm/BUILD.bazel @@ -0,0 +1,41 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "xsvm", + srcs = [ + "constants.go", + "factory.go", + "vm.go", + "warp.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm", + visibility = ["//visibility:public"], + deps = [ + "//connectproto/pb/xsvm/xsvmconnect", + "//database", + "//database/versiondb", + "//ids", + "//network/p2p", + "//network/p2p/acp118", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/constants", + "//utils/json", + "//utils/logging", + "//vms", + "//vms/example/xsvm/api", + "//vms/example/xsvm/block", + "//vms/example/xsvm/builder", + "//vms/example/xsvm/chain", + "//vms/example/xsvm/execute", + "//vms/example/xsvm/genesis", + "//vms/example/xsvm/state", + "//vms/platformvm/warp", + "@com_connectrpc_grpcreflect//:grpcreflect", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) diff --git a/vms/example/xsvm/api/BUILD.bazel b/vms/example/xsvm/api/BUILD.bazel new file mode 100644 index 000000000000..fc0b00295f47 --- /dev/null +++ b/vms/example/xsvm/api/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "api", + srcs = [ + "client.go", + "ping.go", + "server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/api", + visibility = ["//visibility:public"], + deps = [ + "//connectproto/pb/xsvm", + "//connectproto/pb/xsvm/xsvmconnect", + "//database", + "//ids", + "//snow", + "//utils/constants", + "//utils/logging", + "//utils/rpc", + "//vms/example/xsvm/block", + "//vms/example/xsvm/builder", + "//vms/example/xsvm/chain", + "//vms/example/xsvm/genesis", + "//vms/example/xsvm/state", + "//vms/example/xsvm/tx", + "//vms/platformvm/warp", + "@com_connectrpc_connect//:connect", + "@org_uber_go_zap//:zap", + ], +) diff --git a/vms/example/xsvm/block/BUILD.bazel b/vms/example/xsvm/block/BUILD.bazel new file mode 100644 index 000000000000..d0150806ef19 --- /dev/null +++ b/vms/example/xsvm/block/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "block", + srcs = [ + "block.go", + "codec.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/block", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/hashing", + "//vms/example/xsvm/tx", + ], +) diff --git a/vms/example/xsvm/builder/BUILD.bazel b/vms/example/xsvm/builder/BUILD.bazel new file mode 100644 index 000000000000..67dad3aff5ca --- /dev/null +++ b/vms/example/xsvm/builder/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "builder", + srcs = ["builder.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/builder", + visibility = ["//visibility:public"], + deps = [ + "//database/versiondb", + "//ids", + "//snow", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/linked", + "//utils/lock", + "//vms/example/xsvm/block", + "//vms/example/xsvm/chain", + "//vms/example/xsvm/execute", + "//vms/example/xsvm/tx", + ], +) diff --git a/vms/example/xsvm/chain/BUILD.bazel b/vms/example/xsvm/chain/BUILD.bazel new file mode 100644 index 000000000000..c44e8e8b7b6a --- /dev/null +++ b/vms/example/xsvm/chain/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "chain", + srcs = [ + "block.go", + "chain.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/chain", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/versiondb", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/snowman/block", + "//utils/set", + "//vms/example/xsvm/block", + "//vms/example/xsvm/execute", + "//vms/example/xsvm/state", + ], +) diff --git a/vms/example/xsvm/cmd/account/BUILD.bazel b/vms/example/xsvm/cmd/account/BUILD.bazel new file mode 100644 index 000000000000..f306c03554a3 --- /dev/null +++ b/vms/example/xsvm/cmd/account/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "account", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/account", + visibility = ["//visibility:public"], + deps = [ + "//genesis", + "//ids", + "//vms/example/xsvm/api", + "//wallet/subnet/primary", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/chain/BUILD.bazel b/vms/example/xsvm/cmd/chain/BUILD.bazel new file mode 100644 index 000000000000..9f8ddafd541a --- /dev/null +++ b/vms/example/xsvm/cmd/chain/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "chain", + srcs = ["cmd.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/chain", + visibility = ["//visibility:public"], + deps = [ + "//vms/example/xsvm/cmd/chain/create", + "//vms/example/xsvm/cmd/chain/genesis", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/vms/example/xsvm/cmd/chain/create/BUILD.bazel b/vms/example/xsvm/cmd/chain/create/BUILD.bazel new file mode 100644 index 000000000000..ec5df8a6295c --- /dev/null +++ b/vms/example/xsvm/cmd/chain/create/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "create", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/chain/create", + visibility = ["//visibility:public"], + deps = [ + "//genesis", + "//ids", + "//utils/constants", + "//utils/crypto/secp256k1", + "//vms/example/xsvm/genesis", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/chain/genesis/BUILD.bazel b/vms/example/xsvm/cmd/chain/genesis/BUILD.bazel new file mode 100644 index 000000000000..03573ec119d9 --- /dev/null +++ b/vms/example/xsvm/cmd/chain/genesis/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "genesis", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/chain/genesis", + visibility = ["//visibility:public"], + deps = [ + "//genesis", + "//ids", + "//utils/formatting", + "//vms/example/xsvm/genesis", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/issue/BUILD.bazel b/vms/example/xsvm/cmd/issue/BUILD.bazel new file mode 100644 index 000000000000..5f7c57281e90 --- /dev/null +++ b/vms/example/xsvm/cmd/issue/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "issue", + srcs = ["cmd.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue", + visibility = ["//visibility:public"], + deps = [ + "//vms/example/xsvm/cmd/issue/export", + "//vms/example/xsvm/cmd/issue/importtx", + "//vms/example/xsvm/cmd/issue/transfer", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/vms/example/xsvm/cmd/issue/export/BUILD.bazel b/vms/example/xsvm/cmd/issue/export/BUILD.bazel new file mode 100644 index 000000000000..2aec8c9d44d0 --- /dev/null +++ b/vms/example/xsvm/cmd/issue/export/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "export", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/export", + visibility = ["//visibility:public"], + deps = [ + "//genesis", + "//ids", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/example/xsvm/api", + "//vms/example/xsvm/cmd/issue/status", + "//vms/example/xsvm/tx", + "//wallet/subnet/primary", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/issue/importtx/BUILD.bazel b/vms/example/xsvm/cmd/issue/importtx/BUILD.bazel new file mode 100644 index 000000000000..387dd269850d --- /dev/null +++ b/vms/example/xsvm/cmd/issue/importtx/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "importtx", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/importtx", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//genesis", + "//ids", + "//utils/crypto/bls", + "//utils/crypto/secp256k1", + "//utils/set", + "//vms/example/xsvm/api", + "//vms/example/xsvm/cmd/issue/status", + "//vms/example/xsvm/tx", + "//vms/platformvm/warp", + "//wallet/subnet/primary", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/issue/status/BUILD.bazel b/vms/example/xsvm/cmd/issue/status/BUILD.bazel new file mode 100644 index 000000000000..4dfe0d4377f1 --- /dev/null +++ b/vms/example/xsvm/cmd/issue/status/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "status", + srcs = ["status.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/status", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//vms/example/xsvm/tx", + ], +) diff --git a/vms/example/xsvm/cmd/issue/transfer/BUILD.bazel b/vms/example/xsvm/cmd/issue/transfer/BUILD.bazel new file mode 100644 index 000000000000..87ad14a54411 --- /dev/null +++ b/vms/example/xsvm/cmd/issue/transfer/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "transfer", + srcs = [ + "cmd.go", + "flags.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/transfer", + visibility = ["//visibility:public"], + deps = [ + "//genesis", + "//ids", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/example/xsvm/api", + "//vms/example/xsvm/cmd/issue/status", + "//vms/example/xsvm/tx", + "//wallet/subnet/primary", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_pflag//:pflag", + ], +) diff --git a/vms/example/xsvm/cmd/run/BUILD.bazel b/vms/example/xsvm/cmd/run/BUILD.bazel new file mode 100644 index 000000000000..a97eb30fc245 --- /dev/null +++ b/vms/example/xsvm/cmd/run/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "run", + srcs = ["cmd.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/run", + visibility = ["//visibility:public"], + deps = [ + "//vms/example/xsvm", + "//vms/rpcchainvm", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/vms/example/xsvm/cmd/version/BUILD.bazel b/vms/example/xsvm/cmd/version/BUILD.bazel new file mode 100644 index 000000000000..7a0d310e780d --- /dev/null +++ b/vms/example/xsvm/cmd/version/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "version", + srcs = ["cmd.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/version", + visibility = ["//visibility:public"], + deps = [ + "//utils/constants", + "//version", + "//vms/example/xsvm", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/vms/example/xsvm/cmd/versionjson/BUILD.bazel b/vms/example/xsvm/cmd/versionjson/BUILD.bazel new file mode 100644 index 000000000000..6828acf3638c --- /dev/null +++ b/vms/example/xsvm/cmd/versionjson/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "versionjson", + srcs = ["cmd.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/versionjson", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/constants", + "//version", + "//vms/example/xsvm", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/vms/example/xsvm/cmd/xsvm/BUILD.bazel b/vms/example/xsvm/cmd/xsvm/BUILD.bazel new file mode 100644 index 000000000000..5330be56bcd7 --- /dev/null +++ b/vms/example/xsvm/cmd/xsvm/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "xsvm_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/xsvm", + visibility = ["//visibility:private"], + deps = [ + "//vms/example/xsvm/cmd/account", + "//vms/example/xsvm/cmd/chain", + "//vms/example/xsvm/cmd/issue", + "//vms/example/xsvm/cmd/run", + "//vms/example/xsvm/cmd/version", + "//vms/example/xsvm/cmd/versionjson", + "@com_github_spf13_cobra//:cobra", + ], +) + +go_binary( + name = "xsvm", + embed = [":xsvm_lib"], + visibility = ["//visibility:public"], +) diff --git a/vms/example/xsvm/execute/BUILD.bazel b/vms/example/xsvm/execute/BUILD.bazel new file mode 100644 index 000000000000..ebeef577c899 --- /dev/null +++ b/vms/example/xsvm/execute/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "execute", + srcs = [ + "block.go", + "expects_context.go", + "genesis.go", + "tx.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/execute", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/engine/snowman/block", + "//utils/hashing", + "//utils/wrappers", + "//vms/example/xsvm/block", + "//vms/example/xsvm/genesis", + "//vms/example/xsvm/state", + "//vms/example/xsvm/tx", + "//vms/platformvm/warp", + ], +) diff --git a/vms/example/xsvm/genesis/BUILD.bazel b/vms/example/xsvm/genesis/BUILD.bazel new file mode 100644 index 000000000000..91db2c82ea3a --- /dev/null +++ b/vms/example/xsvm/genesis/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "genesis", + srcs = [ + "codec.go", + "genesis.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/hashing", + "//vms/example/xsvm/block", + ], +) + +go_test( + name = "genesis_test", + srcs = ["genesis_test.go"], + embed = [":genesis"], + deps = [ + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/example/xsvm/state/BUILD.bazel b/vms/example/xsvm/state/BUILD.bazel new file mode 100644 index 000000000000..87dbcc37fd8c --- /dev/null +++ b/vms/example/xsvm/state/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "state", + srcs = [ + "keys.go", + "storage.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/state", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils/math", + "//vms/platformvm/warp", + ], +) diff --git a/vms/example/xsvm/tx/BUILD.bazel b/vms/example/xsvm/tx/BUILD.bazel new file mode 100644 index 000000000000..3ae2a23bdce0 --- /dev/null +++ b/vms/example/xsvm/tx/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "tx", + srcs = [ + "codec.go", + "export.go", + "import.go", + "payload.go", + "transfer.go", + "tx.go", + "unsigned.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/example/xsvm/tx", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//utils/crypto/secp256k1", + "//utils/hashing", + ], +) diff --git a/vms/fx/BUILD.bazel b/vms/fx/BUILD.bazel new file mode 100644 index 000000000000..0dfae1973d26 --- /dev/null +++ b/vms/fx/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "fx", + srcs = ["factory.go"], + importpath = "github.com/ava-labs/avalanchego/vms/fx", + visibility = ["//visibility:public"], +) diff --git a/vms/metervm/BUILD.bazel b/vms/metervm/BUILD.bazel new file mode 100644 index 000000000000..1b686ac9d9a8 --- /dev/null +++ b/vms/metervm/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "metervm", + srcs = [ + "batched_vm.go", + "block.go", + "block_metrics.go", + "block_vm.go", + "build_block_with_context_vm.go", + "metrics.go", + "set_preference_with_context_vm.go", + "state_syncable_vm.go", + "vertex_metrics.go", + "vertex_vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/metervm", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/metric", + "//utils/wrappers", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/vms/nftfx/BUILD.bazel b/vms/nftfx/BUILD.bazel new file mode 100644 index 000000000000..e18c6a57f5af --- /dev/null +++ b/vms/nftfx/BUILD.bazel @@ -0,0 +1,49 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "nftfx", + srcs = [ + "credential.go", + "factory.go", + "fx.go", + "mint_operation.go", + "mint_output.go", + "transfer_operation.go", + "transfer_output.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/nftfx", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils/units", + "//vms/components/verify", + "//vms/fx", + "//vms/secp256k1fx", + "//vms/types", + ], +) + +go_test( + name = "nftfx_test", + srcs = [ + "credential_test.go", + "factory_test.go", + "fx_test.go", + "mint_operation_test.go", + "mint_output_test.go", + "transfer_operation_test.go", + "transfer_output_test.go", + ], + embed = [":nftfx"], + deps = [ + "//codec/linearcodec", + "//ids", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/logging", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/BUILD.bazel b/vms/platformvm/BUILD.bazel new file mode 100644 index 000000000000..e6d4d76c06c5 --- /dev/null +++ b/vms/platformvm/BUILD.bazel @@ -0,0 +1,167 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "platformvm", + srcs = [ + "client.go", + "client_permissionless_validator.go", + "factory.go", + "health.go", + "service.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//api/metrics", + "//cache/lru", + "//codec", + "//codec/linearcodec", + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//snow/uptime", + "//snow/validators", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/logging", + "//utils/math", + "//utils/rpc", + "//utils/set", + "//utils/timer/mockable", + "//version", + "//vms", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/api", + "//vms/platformvm/block", + "//vms/platformvm/block/builder", + "//vms/platformvm/block/executor", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/metrics", + "//vms/platformvm/network", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/state", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/utxo", + "//vms/platformvm/validators", + "//vms/platformvm/validators/fee", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//vms/txs/mempool", + "//vms/types", + "@com_github_gorilla_rpc//v2:rpc", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "platformvm_test", + srcs = [ + "main_test.go", + "service_test.go", + "validator_set_property_test.go", + "vm_regression_test.go", + "vm_test.go", + ], + embed = [":platformvm"], + deps = [ + "//api", + "//cache/lru", + "//chains", + "//chains/atomic", + "//database", + "//database/memdb", + "//database/prefixdb", + "//ids", + "//message", + "//network/p2p", + "//network/p2p/gossip", + "//proto/pb/p2p", + "//snow", + "//snow/consensus/snowball", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/common/tracker", + "//snow/engine/enginetest", + "//snow/engine/snowman", + "//snow/engine/snowman/block", + "//snow/engine/snowman/bootstrap", + "//snow/engine/snowman/getter", + "//snow/networking/benchlist", + "//snow/networking/handler", + "//snow/networking/router", + "//snow/networking/sender", + "//snow/networking/sender/sendertest", + "//snow/networking/timeout", + "//snow/networking/tracker", + "//snow/snowtest", + "//snow/uptime", + "//snow/validators", + "//subnets", + "//upgrade/upgradetest", + "//utils/bloom", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/formatting", + "//utils/formatting/address", + "//utils/json", + "//utils/logging", + "//utils/math/meter", + "//utils/resource", + "//utils/set", + "//utils/timer", + "//utils/timer/mockable", + "//utils/units", + "//version", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/api", + "//vms/platformvm/block", + "//vms/platformvm/block/builder", + "//vms/platformvm/block/executor", + "//vms/platformvm/block/executor/executormock", + "//vms/platformvm/config", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/txstest", + "//vms/platformvm/validators/fee", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//vms/types", + "//wallet/chain/p/builder", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + "@com_github_leanovate_gopter//:gopter", + "@com_github_leanovate_gopter//gen", + "@com_github_leanovate_gopter//prop", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_exp//maps", + "@org_golang_x_sync//errgroup", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/api/BUILD.bazel b/vms/platformvm/api/BUILD.bazel new file mode 100644 index 000000000000..0ec8df8f7e55 --- /dev/null +++ b/vms/platformvm/api/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "api", + srcs = [ + "height.go", + "validator.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/api", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/json", + "//vms/platformvm/signer", + "//vms/types", + ], +) + +go_test( + name = "api_test", + srcs = ["height_test.go"], + embed = [":api"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/platformvm/block/BUILD.bazel b/vms/platformvm/block/BUILD.bazel new file mode 100644 index 000000000000..41ebfc7ccc6c --- /dev/null +++ b/vms/platformvm/block/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "block", + srcs = [ + "abort_block.go", + "atomic_block.go", + "block.go", + "codec.go", + "commit_block.go", + "common_block.go", + "mock_block.go", + "parse.go", + "proposal_block.go", + "standard_block.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/block", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//snow", + "//utils/hashing", + "//utils/wrappers", + "//vms/platformvm/txs", + "@org_uber_go_mock//gomock", + ], +) + +go_test( + name = "block_test", + srcs = [ + "abort_block_test.go", + "atomic_block_test.go", + "commit_block_test.go", + "mocks_generate_test.go", + "parse_test.go", + "proposal_block_test.go", + "serialization_test.go", + "standard_block_test.go", + ], + embed = [":block"], + deps = [ + "//codec", + "//ids", + "//utils/constants", + "//utils/crypto/secp256k1", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/block/builder/BUILD.bazel b/vms/platformvm/block/builder/BUILD.bazel new file mode 100644 index 000000000000..a4beaf38a9dc --- /dev/null +++ b/vms/platformvm/block/builder/BUILD.bazel @@ -0,0 +1,91 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "builder", + srcs = ["builder.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/block/builder", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/block/executor", + "//vms/platformvm/state", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/fee", + "//vms/platformvm/txs/mempool", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "builder_test", + srcs = [ + "builder_test.go", + "helpers_test.go", + "main_test.go", + "standard_block_test.go", + ], + embed = [":builder"], + deps = [ + "//chains", + "//chains/atomic", + "//codec", + "//codec/linearcodec", + "//database/memdb", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/snowtest", + "//snow/uptime", + "//snow/validators", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/iterator", + "//utils/logging", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/block/executor", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/metrics", + "//vms/platformvm/network", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/txs/txstest", + "//vms/platformvm/utxo", + "//vms/platformvm/validators/validatorstest", + "//vms/secp256k1fx", + "//wallet/chain/p/wallet", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/block/executor/BUILD.bazel b/vms/platformvm/block/executor/BUILD.bazel new file mode 100644 index 000000000000..5b56fbfd2857 --- /dev/null +++ b/vms/platformvm/block/executor/BUILD.bazel @@ -0,0 +1,124 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "executor", + srcs = [ + "acceptor.go", + "backend.go", + "block.go", + "block_state.go", + "manager.go", + "options.go", + "rejector.go", + "verifier.go", + "warp_verifier.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/block/executor", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/snowman/block", + "//snow/uptime", + "//snow/validators", + "//utils/constants", + "//utils/logging", + "//utils/math", + "//utils/set", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/metrics", + "//vms/platformvm/reward", + "//vms/platformvm/state", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/fee", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/validators", + "//vms/platformvm/validators/fee", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "executor_test", + srcs = [ + "acceptor_test.go", + "backend_test.go", + "block_test.go", + "helpers_test.go", + "manager_test.go", + "mocks_generate_test.go", + "options_test.go", + "proposal_block_test.go", + "rejector_test.go", + "standard_block_test.go", + "verifier_test.go", + "warp_verifier_test.go", + ], + embed = [":executor"], + deps = [ + "//chains", + "//chains/atomic", + "//chains/atomic/atomicmock", + "//codec", + "//codec/linearcodec", + "//database", + "//database/databasemock", + "//database/memdb", + "//database/prefixdb", + "//database/versiondb", + "//genesis", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/snowman/block", + "//snow/snowtest", + "//snow/uptime", + "//snow/uptime/uptimemock", + "//snow/validators", + "//upgrade", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/iterator", + "//utils/logging", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/metrics", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/executor", + "//vms/platformvm/txs/fee", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/txs/txstest", + "//vms/platformvm/utxo", + "//vms/platformvm/validators/fee", + "//vms/platformvm/validators/validatorstest", + "//vms/secp256k1fx", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/block/executor/executormock/BUILD.bazel b/vms/platformvm/block/executor/executormock/BUILD.bazel new file mode 100644 index 000000000000..41be826ef9db --- /dev/null +++ b/vms/platformvm/block/executor/executormock/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "executormock", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/block/executor/executormock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/consensus/snowman", + "//snow/engine/snowman/block", + "//utils/set", + "//vms/platformvm/block", + "//vms/platformvm/state", + "//vms/platformvm/txs", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/config/BUILD.bazel b/vms/platformvm/config/BUILD.bazel new file mode 100644 index 000000000000..065d8915292e --- /dev/null +++ b/vms/platformvm/config/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "config", + srcs = [ + "config.go", + "internal.go", + "network.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/config", + visibility = ["//visibility:public"], + deps = [ + "//chains", + "//ids", + "//snow/uptime", + "//snow/validators", + "//upgrade", + "//utils/constants", + "//utils/set", + "//utils/units", + "//vms/components/gas", + "//vms/platformvm/reward", + "//vms/platformvm/txs", + "//vms/platformvm/validators/fee", + ], +) + +go_test( + name = "config_test", + srcs = ["config_test.go"], + embed = [":config"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/platformvm/fx/BUILD.bazel b/vms/platformvm/fx/BUILD.bazel new file mode 100644 index 000000000000..2fb2ecdd00fc --- /dev/null +++ b/vms/platformvm/fx/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "fx", + srcs = ["fx.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/fx", + visibility = ["//visibility:public"], + deps = [ + "//snow", + "//vms/components/verify", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "fx_test", + srcs = ["mocks_generate_test.go"], + embed = [":fx"], +) diff --git a/vms/platformvm/fx/fxmock/BUILD.bazel b/vms/platformvm/fx/fxmock/BUILD.bazel new file mode 100644 index 000000000000..02a7b6fb1ea7 --- /dev/null +++ b/vms/platformvm/fx/fxmock/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "fxmock", + srcs = [ + "fx.go", + "owner.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/fx/fxmock", + visibility = ["//visibility:public"], + deps = [ + "//snow", + "//vms/components/verify", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/genesis/BUILD.bazel b/vms/platformvm/genesis/BUILD.bazel new file mode 100644 index 000000000000..50b913ed1984 --- /dev/null +++ b/vms/platformvm/genesis/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "genesis", + srcs = [ + "codec.go", + "genesis.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/genesis", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils", + "//utils/formatting/address", + "//utils/math", + "//vms/components/avax", + "//vms/platformvm/block", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/platformvm/txs/txheap", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "genesis_test", + srcs = ["genesis_test.go"], + embed = [":genesis"], + deps = [ + "//ids", + "//utils/constants", + "//utils/formatting/address", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/genesis/genesistest/BUILD.bazel b/vms/platformvm/genesis/genesistest/BUILD.bazel new file mode 100644 index 000000000000..4657146f4474 --- /dev/null +++ b/vms/platformvm/genesis/genesistest/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "genesistest", + srcs = ["genesis.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/snowtest", + "//upgrade", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/components/avax", + "//vms/platformvm/genesis", + "//vms/platformvm/reward", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/metrics/BUILD.bazel b/vms/platformvm/metrics/BUILD.bazel new file mode 100644 index 000000000000..6157c6292d1c --- /dev/null +++ b/vms/platformvm/metrics/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "metrics", + srcs = [ + "block_metrics.go", + "metrics.go", + "no_op.go", + "tx_metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/metrics", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/metric", + "//utils/wrappers", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/txs", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + ], +) diff --git a/vms/platformvm/network/BUILD.bazel b/vms/platformvm/network/BUILD.bazel new file mode 100644 index 000000000000..05ede186043c --- /dev/null +++ b/vms/platformvm/network/BUILD.bazel @@ -0,0 +1,77 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "network", + srcs = [ + "gossip.go", + "network.go", + "tx_verifier.go", + "warp.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/network", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//network/p2p", + "//network/p2p/acp118", + "//network/p2p/gossip", + "//proto/pb/platformvm", + "//snow/engine/common", + "//snow/validators", + "//utils/logging", + "//vms/platformvm/config", + "//vms/platformvm/state", + "//vms/platformvm/txs", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "network_test", + srcs = [ + "gossip_test.go", + "main_test.go", + "network_test.go", + "warp_test.go", + ], + embed = [":network"], + deps = [ + "//ids", + "//proto/pb/platformvm", + "//snow/engine/common", + "//snow/engine/common/commonmock", + "//snow/snowtest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/config", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/txs", + "//vms/platformvm/txs/mempool", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + "@org_golang_x_exp//rand", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/reward/BUILD.bazel b/vms/platformvm/reward/BUILD.bazel new file mode 100644 index 000000000000..1148a01724f0 --- /dev/null +++ b/vms/platformvm/reward/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "reward", + srcs = [ + "calculator.go", + "config.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/reward", + visibility = ["//visibility:public"], + deps = ["//utils/math"], +) + +go_test( + name = "reward_test", + srcs = [ + "calculator_test.go", + "example_test.go", + ], + embed = [":reward"], + deps = [ + "//utils/units", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/signer/BUILD.bazel b/vms/platformvm/signer/BUILD.bazel new file mode 100644 index 000000000000..f1bdcea7d3a3 --- /dev/null +++ b/vms/platformvm/signer/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "signer", + srcs = [ + "empty.go", + "proof_of_possession.go", + "signer.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/signer", + visibility = ["//visibility:public"], + deps = [ + "//utils/crypto/bls", + "//utils/formatting", + "//vms/components/verify", + ], +) + +go_test( + name = "signer_test", + srcs = [ + "empty_test.go", + "mocks_generate_test.go", + "proof_of_possession_test.go", + ], + embed = [":signer"], + deps = [ + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/signer/signermock/BUILD.bazel b/vms/platformvm/signer/signermock/BUILD.bazel new file mode 100644 index 000000000000..84174de7ff34 --- /dev/null +++ b/vms/platformvm/signer/signermock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "signermock", + srcs = ["signer.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/signer/signermock", + visibility = ["//visibility:public"], + deps = [ + "//utils/crypto/bls", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/stakeable/BUILD.bazel b/vms/platformvm/stakeable/BUILD.bazel new file mode 100644 index 000000000000..abea6475e8be --- /dev/null +++ b/vms/platformvm/stakeable/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "stakeable", + srcs = ["stakeable_lock.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/stakeable", + visibility = ["//visibility:public"], + deps = ["//vms/components/avax"], +) + +go_test( + name = "stakeable_test", + srcs = ["stakeable_lock_test.go"], + embed = [":stakeable"], + deps = [ + "//vms/components/avax", + "//vms/components/avax/avaxmock", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/state/BUILD.bazel b/vms/platformvm/state/BUILD.bazel new file mode 100644 index 000000000000..5567d3d7861f --- /dev/null +++ b/vms/platformvm/state/BUILD.bazel @@ -0,0 +1,139 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "state", + srcs = [ + "chain_time_helpers.go", + "diff.go", + "disk_staker_diff_iterator.go", + "expiry.go", + "l1_validator.go", + "metadata_codec.go", + "metadata_delegator.go", + "metadata_validator.go", + "mock_chain.go", + "mock_diff.go", + "mock_state.go", + "staker.go", + "staker_diff_iterator.go", + "staker_status.go", + "stakers.go", + "state.go", + "subnet_id_node_id.go", + "versions.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/state", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//codec", + "//codec/linearcodec", + "//database", + "//database/linkeddb", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/choices", + "//snow/uptime", + "//snow/validators", + "//upgrade", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/hashing", + "//utils/heap", + "//utils/iterator", + "//utils/logging", + "//utils/math", + "//utils/maybe", + "//utils/set", + "//utils/timer", + "//utils/timer/mockable", + "//utils/wrappers", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/genesis", + "//vms/platformvm/metrics", + "//vms/platformvm/reward", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/validators/fee", + "@com_github_google_btree//:btree", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_x_exp//maps", + "@org_uber_go_mock//gomock", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "state_test", + srcs = [ + "chain_time_helpers_test.go", + "diff_test.go", + "disk_staker_diff_iterator_test.go", + "expiry_test.go", + "l1_validator_test.go", + "metadata_delegator_test.go", + "metadata_validator_test.go", + "mocks_generate_test.go", + "staker_diff_iterator_test.go", + "staker_test.go", + "stakers_test.go", + "state_test.go", + "subnet_id_node_id_test.go", + ], + embed = [":state"], + deps = [ + "//cache", + "//cache/lru", + "//codec", + "//database", + "//database/memdb", + "//genesis", + "//ids", + "//snow", + "//snow/choices", + "//snow/validators", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/iterator", + "//utils/logging", + "//utils/math", + "//utils/maybe", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//utils/wrappers", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/fx/fxmock", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/metrics", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/signer/signermock", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/validators/fee", + "//vms/secp256k1fx", + "//vms/types", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@com_github_thepudds_fzgen//fuzzer", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/state/statetest/BUILD.bazel b/vms/platformvm/state/statetest/BUILD.bazel new file mode 100644 index 000000000000..1e077bfded5e --- /dev/null +++ b/vms/platformvm/state/statetest/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "statetest", + srcs = ["state.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//database/memdb", + "//ids", + "//snow", + "//snow/validators", + "//upgrade", + "//upgrade/upgradetest", + "//utils/constants", + "//utils/logging", + "//utils/units", + "//vms/platformvm/config", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/metrics", + "//vms/platformvm/reward", + "//vms/platformvm/state", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/status/BUILD.bazel b/vms/platformvm/status/BUILD.bazel new file mode 100644 index 000000000000..e1baee7bd36f --- /dev/null +++ b/vms/platformvm/status/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "status", + srcs = [ + "blockchain_status.go", + "status.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/status", + visibility = ["//visibility:public"], + deps = ["//vms/components/verify"], +) + +go_test( + name = "status_test", + srcs = [ + "blockchain_status_test.go", + "status_test.go", + ], + embed = [":status"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/platformvm/txs/BUILD.bazel b/vms/platformvm/txs/BUILD.bazel new file mode 100644 index 000000000000..2d0a5ed63e33 --- /dev/null +++ b/vms/platformvm/txs/BUILD.bazel @@ -0,0 +1,122 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "txs", + srcs = [ + "add_delegator_tx.go", + "add_permissionless_delegator_tx.go", + "add_permissionless_validator_tx.go", + "add_subnet_validator_tx.go", + "add_validator_tx.go", + "advance_time_tx.go", + "base_tx.go", + "codec.go", + "convert_subnet_to_l1_tx.go", + "create_chain_tx.go", + "create_subnet_tx.go", + "disable_l1_validator_tx.go", + "export_tx.go", + "import_tx.go", + "increase_l1_validator_balance_tx.go", + "priorities.go", + "register_l1_validator_tx.go", + "remove_subnet_validator_tx.go", + "reward_validator_tx.go", + "set_l1_validator_weight_tx.go", + "staker_tx.go", + "subnet_validator.go", + "transfer_subnet_ownership_tx.go", + "transform_subnet_tx.go", + "tx.go", + "unsigned_tx.go", + "validator.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//network/p2p/gossip", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/math", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//vms/types", + ], +) + +go_test( + name = "txs_test", + srcs = [ + "add_delegator_test.go", + "add_permissionless_delegator_tx_test.go", + "add_permissionless_validator_tx_test.go", + "add_subnet_validator_test.go", + "add_validator_test.go", + "base_tx_test.go", + "convert_subnet_to_l1_tx_test.go", + "create_chain_test.go", + "disable_l1_validator_tx_test.go", + "increase_l1_validator_balance_tx_test.go", + "priorities_test.go", + "register_l1_validator_tx_test.go", + "remove_subnet_validator_tx_test.go", + "set_l1_validator_weight_tx_test.go", + "subnet_validator_test.go", + "transfer_subnet_ownership_tx_test.go", + "transform_subnet_tx_test.go", + "validator_test.go", + ], + embed = [":txs"], + embedsrcs = [ + "convert_subnet_to_l1_tx_test_complex.json", + "convert_subnet_to_l1_tx_test_simple.json", + "disable_l1_validator_tx_test.json", + "increase_l1_validator_balance_tx_test.json", + "register_l1_validator_tx_test.json", + "set_l1_validator_weight_tx_test.json", + ], + deps = [ + "//ids", + "//snow", + "//snow/snowtest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/math", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/avax", + "//vms/components/avax/avaxmock", + "//vms/components/verify", + "//vms/components/verify/verifymock", + "//vms/platformvm/fx/fxmock", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//vms/types", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/txs/executor/BUILD.bazel b/vms/platformvm/txs/executor/BUILD.bazel new file mode 100644 index 000000000000..5a442147a798 --- /dev/null +++ b/vms/platformvm/txs/executor/BUILD.bazel @@ -0,0 +1,125 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "executor", + srcs = [ + "atomic_tx_executor.go", + "backend.go", + "proposal_tx_executor.go", + "staker_tx_verification.go", + "staker_tx_verification_helpers.go", + "standard_tx_executor.go", + "state_changes.go", + "subnet_tx_verification.go", + "warp_verifier.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor", + visibility = ["//visibility:public"], + deps = [ + "//chains/atomic", + "//database", + "//ids", + "//snow", + "//snow/uptime", + "//snow/validators", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/math", + "//utils/set", + "//utils/timer/mockable", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/state", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/utxo", + "//vms/platformvm/validators/fee", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "executor_test", + srcs = [ + "advance_time_test.go", + "create_chain_test.go", + "export_test.go", + "helpers_test.go", + "import_test.go", + "proposal_tx_executor_test.go", + "reward_validator_test.go", + "staker_tx_verification_test.go", + "standard_tx_executor_test.go", + "state_changes_test.go", + "warp_verifier_test.go", + ], + embed = [":executor"], + deps = [ + "//chains", + "//chains/atomic", + "//codec", + "//codec/linearcodec", + "//database", + "//database/memdb", + "//database/prefixdb", + "//database/versiondb", + "//genesis", + "//ids", + "//snow", + "//snow/snowtest", + "//snow/uptime", + "//snow/validators", + "//snow/validators/validatorstest", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/iterator", + "//utils/logging", + "//utils/math", + "//utils/set", + "//utils/timer/mockable", + "//utils/units", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/fx/fxmock", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/status", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/txs/txstest", + "//vms/platformvm/utxo", + "//vms/platformvm/utxo/utxomock", + "//vms/platformvm/validators/fee", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//vms/types", + "//wallet/chain/p/builder", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/txs/fee/BUILD.bazel b/vms/platformvm/txs/fee/BUILD.bazel new file mode 100644 index 000000000000..8a27e181c268 --- /dev/null +++ b/vms/platformvm/txs/fee/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "fee", + srcs = [ + "calculator.go", + "complexity.go", + "dynamic_calculator.go", + "simple_calculator.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//ids", + "//utils/crypto/bls", + "//utils/crypto/secp256k1", + "//utils/math", + "//utils/wrappers", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/platformvm/warp", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "fee_test", + srcs = [ + "calculator_test.go", + "complexity_test.go", + "dynamic_calculator_test.go", + ], + embed = [":fee"], + deps = [ + "//codec", + "//ids", + "//utils/crypto/secp256k1", + "//utils/units", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/txs/mempool/BUILD.bazel b/vms/platformvm/txs/mempool/BUILD.bazel new file mode 100644 index 000000000000..4c977e5a1cc9 --- /dev/null +++ b/vms/platformvm/txs/mempool/BUILD.bazel @@ -0,0 +1,45 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "mempool", + srcs = ["mempool.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool", + visibility = ["//visibility:public"], + deps = [ + "//cache/lru", + "//ids", + "//snow/engine/common", + "//utils/lock", + "//utils/math", + "//utils/set", + "//utils/setmap", + "//vms/components/gas", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/utxo", + "//vms/txs/mempool", + "@com_github_google_btree//:btree", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "mempool_test", + srcs = ["mempool_test.go"], + embed = [":mempool"], + deps = [ + "//ids", + "//snow/engine/common", + "//snow/snowtest", + "//utils/set", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/txs", + "//vms/platformvm/utxo", + "//vms/secp256k1fx", + "//vms/txs/mempool", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/vms/platformvm/txs/txheap/BUILD.bazel b/vms/platformvm/txs/txheap/BUILD.bazel new file mode 100644 index 000000000000..6145abae1429 --- /dev/null +++ b/vms/platformvm/txs/txheap/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "txheap", + srcs = [ + "by_end_time.go", + "heap.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs/txheap", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/heap", + "//vms/platformvm/txs", + ], +) + +go_test( + name = "txheap_test", + srcs = ["by_end_time_test.go"], + embed = [":txheap"], + deps = [ + "//ids", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/txs/txstest/BUILD.bazel b/vms/platformvm/txs/txstest/BUILD.bazel new file mode 100644 index 000000000000..98344b47ad09 --- /dev/null +++ b/vms/platformvm/txs/txstest/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "txstest", + srcs = [ + "context.go", + "wallet.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils/constants", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/config", + "//vms/platformvm/fx", + "//vms/platformvm/state", + "//vms/platformvm/txs", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//wallet/chain/p/builder", + "//wallet/chain/p/signer", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/utxo/BUILD.bazel b/vms/platformvm/utxo/BUILD.bazel new file mode 100644 index 000000000000..d41d6f2cb300 --- /dev/null +++ b/vms/platformvm/utxo/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utxo", + srcs = ["verifier.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/utxo", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils/hashing", + "//utils/math", + "//utils/timer/mockable", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + ], +) + +go_test( + name = "utxo_test", + srcs = [ + "mocks_generate_test.go", + "verifier_test.go", + ], + embed = [":utxo"], + deps = [ + "//ids", + "//snow/snowtest", + "//utils/crypto/secp256k1", + "//utils/math", + "//utils/timer/mockable", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/utxo/utxomock/BUILD.bazel b/vms/platformvm/utxo/utxomock/BUILD.bazel new file mode 100644 index 000000000000..2ae5a68f0e61 --- /dev/null +++ b/vms/platformvm/utxo/utxomock/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "utxomock", + srcs = ["verifier.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/txs", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/validators/BUILD.bazel b/vms/platformvm/validators/BUILD.bazel new file mode 100644 index 000000000000..54086700d164 --- /dev/null +++ b/vms/platformvm/validators/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "validators", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/validators", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//ids", + "//snow/validators", + "//utils/constants", + "//utils/crypto/bls", + "//utils/timer/mockable", + "//utils/window", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/metrics", + "//vms/platformvm/state", + "//vms/platformvm/status", + "//vms/platformvm/txs", + ], +) + +go_test( + name = "validators_test", + srcs = [ + "manager_benchmark_test.go", + "manager_test.go", + ], + embed = [":validators"], + deps = [ + "//database/leveldb", + "//ids", + "//snow/validators", + "//upgrade/upgradetest", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/logging", + "//utils/timer/mockable", + "//utils/units", + "//vms/platformvm/block", + "//vms/platformvm/config", + "//vms/platformvm/genesis/genesistest", + "//vms/platformvm/metrics", + "//vms/platformvm/state", + "//vms/platformvm/state/statetest", + "//vms/platformvm/txs", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/validators/fee/BUILD.bazel b/vms/platformvm/validators/fee/BUILD.bazel new file mode 100644 index 000000000000..92caa0edffdd --- /dev/null +++ b/vms/platformvm/validators/fee/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "fee", + srcs = ["fee.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee", + visibility = ["//visibility:public"], + deps = [ + "//utils/math", + "//vms/components/gas", + ], +) + +go_test( + name = "fee_test", + srcs = ["fee_test.go"], + embed = [":fee"], + deps = [ + "//utils/math", + "//vms/components/gas", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/validators/validatorstest/BUILD.bazel b/vms/platformvm/validators/validatorstest/BUILD.bazel new file mode 100644 index 000000000000..a9c420b93ae6 --- /dev/null +++ b/vms/platformvm/validators/validatorstest/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "validatorstest", + srcs = ["manager.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/validators/validatorstest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "//vms/platformvm/validators", + ], +) diff --git a/vms/platformvm/warp/BUILD.bazel b/vms/platformvm/warp/BUILD.bazel new file mode 100644 index 000000000000..e13bd60496ae --- /dev/null +++ b/vms/platformvm/warp/BUILD.bazel @@ -0,0 +1,53 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "warp", + srcs = [ + "codec.go", + "constants.go", + "message.go", + "signature.go", + "signer.go", + "unsigned_message.go", + "validator.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/warp", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//snow/validators", + "//utils/crypto/bls", + "//utils/hashing", + "//utils/math", + "//utils/set", + ], +) + +go_test( + name = "warp_test", + srcs = [ + "message_test.go", + "signature_test.go", + "signer_test.go", + "unsigned_message_test.go", + "validator_test.go", + ], + embed = [":warp"], + deps = [ + "//codec", + "//ids", + "//snow/validators", + "//snow/validators/validatorsmock", + "//snow/validators/validatorstest", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/set", + "//vms/platformvm/warp/signertest", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/platformvm/warp/gwarp/BUILD.bazel b/vms/platformvm/warp/gwarp/BUILD.bazel new file mode 100644 index 000000000000..d4ce70949ebc --- /dev/null +++ b/vms/platformvm/warp/gwarp/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gwarp", + srcs = [ + "client.go", + "server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/warp/gwarp", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//proto/pb/warp", + "//vms/platformvm/warp", + ], +) + +go_test( + name = "gwarp_test", + srcs = ["signer_test.go"], + embed = [":gwarp"], + deps = [ + "//ids", + "//proto/pb/warp", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//vms/platformvm/warp", + "//vms/platformvm/warp/signertest", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/warp/message/BUILD.bazel b/vms/platformvm/warp/message/BUILD.bazel new file mode 100644 index 000000000000..7e46eedce87d --- /dev/null +++ b/vms/platformvm/warp/message/BUILD.bazel @@ -0,0 +1,48 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "message", + srcs = [ + "codec.go", + "l1_validator_registration.go", + "l1_validator_weight.go", + "payload.go", + "register_l1_validator.go", + "subnet_to_l1_conversion.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/warp/message", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//utils/constants", + "//utils/crypto/bls", + "//utils/hashing", + "//vms/components/verify", + "//vms/secp256k1fx", + "//vms/types", + ], +) + +go_test( + name = "message_test", + srcs = [ + "l1_validator_registration_test.go", + "l1_validator_weight_test.go", + "payload_test.go", + "register_l1_validator_test.go", + "subnet_to_l1_conversion_test.go", + ], + embed = [":message"], + deps = [ + "//codec", + "//ids", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/hashing", + "//vms/types", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/warp/payload/BUILD.bazel b/vms/platformvm/warp/payload/BUILD.bazel new file mode 100644 index 000000000000..97dbfc57ec5d --- /dev/null +++ b/vms/platformvm/warp/payload/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "payload", + srcs = [ + "addressed_call.go", + "codec.go", + "hash.go", + "payload.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//utils/units", + ], +) + +go_test( + name = "payload_test", + srcs = [ + "addressed_call_test.go", + "hash_test.go", + "payload_test.go", + ], + embed = [":payload"], + deps = [ + "//codec", + "//ids", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/platformvm/warp/signertest/BUILD.bazel b/vms/platformvm/warp/signertest/BUILD.bazel new file mode 100644 index 000000000000..5241f39e9455 --- /dev/null +++ b/vms/platformvm/warp/signertest/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "signertest", + srcs = ["signertest.go"], + importpath = "github.com/ava-labs/avalanchego/vms/platformvm/warp/signertest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/constants", + "//utils/crypto/bls", + "//vms/platformvm/warp", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/propertyfx/BUILD.bazel b/vms/propertyfx/BUILD.bazel new file mode 100644 index 000000000000..c99f7ab89c31 --- /dev/null +++ b/vms/propertyfx/BUILD.bazel @@ -0,0 +1,47 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "propertyfx", + srcs = [ + "burn_operation.go", + "credential.go", + "factory.go", + "fx.go", + "mint_operation.go", + "mint_output.go", + "owned_output.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/propertyfx", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//vms/components/verify", + "//vms/fx", + "//vms/secp256k1fx", + ], +) + +go_test( + name = "propertyfx_test", + srcs = [ + "burn_operation_test.go", + "credential_test.go", + "factory_test.go", + "fx_test.go", + "mint_operation_test.go", + "mint_output_test.go", + "owned_output_test.go", + ], + embed = [":propertyfx"], + deps = [ + "//codec/linearcodec", + "//ids", + "//utils/crypto/secp256k1", + "//utils/hashing", + "//utils/logging", + "//vms/components/verify", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/proposervm/BUILD.bazel b/vms/proposervm/BUILD.bazel new file mode 100644 index 000000000000..dec634dbcd4e --- /dev/null +++ b/vms/proposervm/BUILD.bazel @@ -0,0 +1,118 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "proposervm", + srcs = [ + "batched_vm.go", + "block.go", + "client_jsonrpc.go", + "config.go", + "height_indexed_vm.go", + "post_fork_block.go", + "post_fork_option.go", + "pre_fork_block.go", + "service.go", + "state_summary.go", + "state_syncable_vm.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm", + visibility = ["//visibility:public"], + deps = [ + "//api", + "//api/server", + "//cache", + "//cache/lru", + "//cache/metercacher", + "//connectproto/pb/proposervm", + "//connectproto/pb/proposervm/proposervmconnect", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//staking", + "//upgrade", + "//utils/constants", + "//utils/json", + "//utils/math", + "//utils/metric", + "//utils/rpc", + "//utils/timer/mockable", + "//utils/tree", + "//utils/units", + "//utils/wrappers", + "//vms/proposervm/acp181", + "//vms/proposervm/block", + "//vms/proposervm/proposer", + "//vms/proposervm/state", + "//vms/proposervm/summary", + "@com_connectrpc_connect//:connect", + "@com_connectrpc_grpcreflect//:grpcreflect", + "@com_github_gorilla_rpc//v2:rpc", + "@com_github_prometheus_client_golang//prometheus", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "proposervm_test", + srcs = [ + "batched_vm_test.go", + "block_test.go", + "main_test.go", + "mocks_generate_test.go", + "mocks_test.go", + "post_fork_block_test.go", + "post_fork_option_test.go", + "pre_fork_block_test.go", + "service_test.go", + "state_syncable_vm_test.go", + "vm_byzantine_test.go", + "vm_test.go", + ], + embed = [":proposervm"], + deps = [ + "//api", + "//api/connectclient", + "//connectproto/pb/proposervm", + "//connectproto/pb/proposervm/proposervmconnect", + "//database", + "//database/memdb", + "//database/prefixdb", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmanmock", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/common", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/engine/snowman/block/blockmock", + "//snow/engine/snowman/block/blocktest", + "//snow/snowtest", + "//snow/validators", + "//snow/validators/validatorsmock", + "//snow/validators/validatorstest", + "//staking", + "//upgrade", + "//upgrade/upgradetest", + "//utils", + "//utils/constants", + "//utils/logging", + "//vms/proposervm/acp181", + "//vms/proposervm/block", + "//vms/proposervm/proposer", + "//vms/proposervm/proposer/proposermock", + "//vms/proposervm/summary", + "@com_connectrpc_connect//:connect", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/testutil", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/proposervm/acp181/BUILD.bazel b/vms/proposervm/acp181/BUILD.bazel new file mode 100644 index 000000000000..cfe79601f02a --- /dev/null +++ b/vms/proposervm/acp181/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "acp181", + srcs = ["epoch.go"], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/acp181", + visibility = ["//visibility:public"], + deps = [ + "//upgrade", + "//vms/proposervm/block", + ], +) + +go_test( + name = "acp181_test", + srcs = ["epoch_test.go"], + embed = [":acp181"], + deps = [ + "//upgrade", + "//upgrade/upgradetest", + "//vms/proposervm/block", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/proposervm/block/BUILD.bazel b/vms/proposervm/block/BUILD.bazel new file mode 100644 index 000000000000..1a1ffe00d4e2 --- /dev/null +++ b/vms/proposervm/block/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "block", + srcs = [ + "block.go", + "build.go", + "codec.go", + "header.go", + "option.go", + "parse.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/block", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//staking", + "//utils/hashing", + "//utils/wrappers", + ], +) + +go_test( + name = "block_test", + srcs = [ + "block_test.go", + "build_test.go", + "header_test.go", + "parse_test.go", + ], + embed = [":block"], + deps = [ + "//codec", + "//ids", + "//staking", + "//utils/units", + "//utils/wrappers", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/proposervm/proposer/BUILD.bazel b/vms/proposervm/proposer/BUILD.bazel new file mode 100644 index 000000000000..21f4ac507940 --- /dev/null +++ b/vms/proposervm/proposer/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "proposer", + srcs = [ + "validators.go", + "windower.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/proposer", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow/validators", + "//utils", + "//utils/math", + "//utils/sampler", + "//utils/wrappers", + "@org_gonum_v1_gonum//mathext/prng", + ], +) + +go_test( + name = "proposer_test", + srcs = [ + "mocks_generate_test.go", + "validators_test.go", + "windower_test.go", + ], + embed = [":proposer"], + deps = [ + "//ids", + "//snow/validators", + "//snow/validators/validatorstest", + "//utils/math", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/proposervm/proposer/proposermock/BUILD.bazel b/vms/proposervm/proposer/proposermock/BUILD.bazel new file mode 100644 index 000000000000..22cf1e32ee1c --- /dev/null +++ b/vms/proposervm/proposer/proposermock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "proposermock", + srcs = ["windower.go"], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/proposer/proposermock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/proposervm/state/BUILD.bazel b/vms/proposervm/state/BUILD.bazel new file mode 100644 index 000000000000..e97ae869c872 --- /dev/null +++ b/vms/proposervm/state/BUILD.bazel @@ -0,0 +1,52 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "state", + srcs = [ + "block_height_index.go", + "block_state.go", + "chain_state.go", + "codec.go", + "state.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/state", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//cache/metercacher", + "//codec", + "//codec/linearcodec", + "//database", + "//database/prefixdb", + "//database/versiondb", + "//ids", + "//snow/choices", + "//utils/constants", + "//utils/metric", + "//utils/units", + "//utils/wrappers", + "//vms/proposervm/block", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "state_test", + srcs = [ + "block_state_test.go", + "chain_state_test.go", + "state_test.go", + ], + embed = [":state"], + deps = [ + "//database", + "//database/memdb", + "//database/versiondb", + "//ids", + "//staking", + "//vms/proposervm/block", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/proposervm/summary/BUILD.bazel b/vms/proposervm/summary/BUILD.bazel new file mode 100644 index 000000000000..9f3a70422236 --- /dev/null +++ b/vms/proposervm/summary/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "summary", + srcs = [ + "build.go", + "codec.go", + "parse.go", + "state_summary.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/proposervm/summary", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//utils/hashing", + ], +) + +go_test( + name = "summary_test", + srcs = [ + "build_test.go", + "parse_test.go", + ], + embed = [":summary"], + deps = [ + "//codec", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/registry/BUILD.bazel b/vms/registry/BUILD.bazel new file mode 100644 index 000000000000..67c784f68fc6 --- /dev/null +++ b/vms/registry/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "registry", + srcs = [ + "vm_getter.go", + "vm_registry.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/registry", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//ids", + "//utils/filesystem", + "//utils/resource", + "//vms", + "//vms/rpcchainvm", + "//vms/rpcchainvm/runtime", + ], +) + +go_test( + name = "registry_test", + srcs = [ + "mocks_generate_test.go", + "vm_getter_test.go", + "vm_registry_test.go", + ], + embed = [":registry"], + deps = [ + "//ids", + "//utils/filesystem", + "//utils/filesystem/filesystemmock", + "//utils/logging", + "//utils/resource", + "//vms", + "//vms/registry/registrymock", + "//vms/vmsmock", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/registry/registrymock/BUILD.bazel b/vms/registry/registrymock/BUILD.bazel new file mode 100644 index 000000000000..39e481d6d4d8 --- /dev/null +++ b/vms/registry/registrymock/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "registrymock", + srcs = [ + "vm_getter.go", + "vm_registry.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/registry/registrymock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//vms", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/rpcchainvm/BUILD.bazel b/vms/rpcchainvm/BUILD.bazel new file mode 100644 index 000000000000..86a7398fd371 --- /dev/null +++ b/vms/rpcchainvm/BUILD.bazel @@ -0,0 +1,104 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "rpcchainvm", + srcs = [ + "errors.go", + "factory.go", + "vm.go", + "vm_client.go", + "vm_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm", + visibility = ["//visibility:public"], + deps = [ + "//api/metrics", + "//chains/atomic/gsharedmemory", + "//database", + "//database/corruptabledb", + "//database/rpcdb", + "//ids", + "//ids/galiasreader", + "//proto/pb/aliasreader", + "//proto/pb/appsender", + "//proto/pb/http", + "//proto/pb/rpcdb", + "//proto/pb/sharedmemory", + "//proto/pb/validatorstate", + "//proto/pb/vm", + "//proto/pb/vm/runtime", + "//proto/pb/warp", + "//snow", + "//snow/consensus/snowman", + "//snow/engine/common", + "//snow/engine/common/appsender", + "//snow/engine/snowman/block", + "//snow/validators", + "//snow/validators/gvalidators", + "//upgrade", + "//utils", + "//utils/crypto/bls", + "//utils/logging", + "//utils/resource", + "//utils/units", + "//utils/wrappers", + "//version", + "//vms", + "//vms/components/chain", + "//vms/platformvm/warp/gwarp", + "//vms/rpcchainvm/ghttp", + "//vms/rpcchainvm/grpcutils", + "//vms/rpcchainvm/gruntime", + "//vms/rpcchainvm/runtime", + "//vms/rpcchainvm/runtime/subprocess", + "@com_github_grpc_ecosystem_go_grpc_prometheus//:go-grpc-prometheus", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_golang//prometheus/collectors", + "@com_github_prometheus_client_model//go", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//health", + "@org_golang_google_grpc//health/grpc_health_v1", + "@org_golang_google_protobuf//types/known/durationpb", + "@org_golang_google_protobuf//types/known/emptypb", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "rpcchainvm_test", + srcs = [ + "batched_vm_test.go", + "state_syncable_vm_test.go", + "vm_test.go", + "with_context_vm_test.go", + ], + embed = [":rpcchainvm"], + deps = [ + "//api/metrics", + "//database/memdb", + "//database/prefixdb", + "//ids", + "//proto/pb/vm", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowman/snowmanmock", + "//snow/consensus/snowman/snowmantest", + "//snow/engine/enginetest", + "//snow/engine/snowman/block", + "//snow/engine/snowman/block/blockmock", + "//snow/engine/snowman/block/blocktest", + "//snow/snowtest", + "//upgrade", + "//utils", + "//utils/constants", + "//utils/logging", + "//vms/components/chain", + "//vms/rpcchainvm/grpcutils", + "//vms/rpcchainvm/runtime", + "//vms/rpcchainvm/runtime/subprocess", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//test/bufconn", + "@org_uber_go_mock//gomock", + ], +) diff --git a/vms/rpcchainvm/ghttp/BUILD.bazel b/vms/rpcchainvm/ghttp/BUILD.bazel new file mode 100644 index 000000000000..8461ce0c3e1d --- /dev/null +++ b/vms/rpcchainvm/ghttp/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ghttp", + srcs = [ + "http_client.go", + "http_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/http", + "//proto/pb/http/responsewriter", + "//proto/pb/io/reader", + "//utils/logging", + "//vms/rpcchainvm/ghttp/greader", + "//vms/rpcchainvm/ghttp/gresponsewriter", + "//vms/rpcchainvm/grpcutils", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "ghttp_test", + srcs = ["http_test.go"], + embed = [":ghttp"], + deps = [ + "//proto/pb/http", + "//utils/logging", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//credentials/insecure", + "@org_golang_google_grpc//test/bufconn", + "@org_golang_x_exp//maps", + ], +) diff --git a/vms/rpcchainvm/ghttp/gconn/BUILD.bazel b/vms/rpcchainvm/ghttp/gconn/BUILD.bazel new file mode 100644 index 000000000000..72aa3522e8c9 --- /dev/null +++ b/vms/rpcchainvm/ghttp/gconn/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "gconn", + srcs = [ + "conn_client.go", + "conn_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/gconn", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/net/conn", + "//utils/wrappers", + "//vms/rpcchainvm/grpcutils", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) + +go_test( + name = "gconn_test", + srcs = ["gconn_test.go"], + embed = [":gconn"], + deps = [ + "//proto/pb/net/conn", + "//vms/rpcchainvm/grpcutils", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//test/bufconn", + ], +) diff --git a/vms/rpcchainvm/ghttp/greader/BUILD.bazel b/vms/rpcchainvm/ghttp/greader/BUILD.bazel new file mode 100644 index 000000000000..757fdc25e29d --- /dev/null +++ b/vms/rpcchainvm/ghttp/greader/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "greader", + srcs = [ + "reader_client.go", + "reader_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/greader", + visibility = ["//visibility:public"], + deps = ["//proto/pb/io/reader"], +) + +go_test( + name = "greader_test", + srcs = ["greader_test.go"], + embed = [":greader"], + deps = [ + "//proto/pb/io/reader", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//test/bufconn", + ], +) diff --git a/vms/rpcchainvm/ghttp/gresponsewriter/BUILD.bazel b/vms/rpcchainvm/ghttp/gresponsewriter/BUILD.bazel new file mode 100644 index 000000000000..553d031a1ece --- /dev/null +++ b/vms/rpcchainvm/ghttp/gresponsewriter/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "gresponsewriter", + srcs = [ + "locked_writer.go", + "writer_client.go", + "writer_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/gresponsewriter", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/http/responsewriter", + "//proto/pb/io/reader", + "//proto/pb/io/writer", + "//proto/pb/net/conn", + "//vms/rpcchainvm/ghttp/gconn", + "//vms/rpcchainvm/ghttp/greader", + "//vms/rpcchainvm/ghttp/gwriter", + "//vms/rpcchainvm/grpcutils", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/vms/rpcchainvm/ghttp/gwriter/BUILD.bazel b/vms/rpcchainvm/ghttp/gwriter/BUILD.bazel new file mode 100644 index 000000000000..e0261c58c92f --- /dev/null +++ b/vms/rpcchainvm/ghttp/gwriter/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "gwriter", + srcs = [ + "writer_client.go", + "writer_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/gwriter", + visibility = ["//visibility:public"], + deps = ["//proto/pb/io/writer"], +) diff --git a/vms/rpcchainvm/grpcutils/BUILD.bazel b/vms/rpcchainvm/grpcutils/BUILD.bazel new file mode 100644 index 000000000000..ffe1e012979d --- /dev/null +++ b/vms/rpcchainvm/grpcutils/BUILD.bazel @@ -0,0 +1,41 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "grpcutils", + srcs = [ + "client.go", + "server.go", + "server_closer.go", + "util.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/grpcutils", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/http", + "@org_golang_google_genproto_googleapis_rpc//status", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//credentials/insecure", + "@org_golang_google_grpc//keepalive", + "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//proto", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/timestamppb", + ], +) + +go_test( + name = "grpcutils_test", + srcs = ["client_test.go"], + embed = [":grpcutils"], + deps = [ + "//database/memdb", + "//database/rpcdb", + "//proto/pb/rpcdb", + "@com_github_grpc_ecosystem_go_grpc_prometheus//:go-grpc-prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_grpc//:grpc", + "@org_golang_google_grpc//codes", + "@org_golang_google_grpc//credentials/insecure", + "@org_golang_google_grpc//status", + ], +) diff --git a/vms/rpcchainvm/gruntime/BUILD.bazel b/vms/rpcchainvm/gruntime/BUILD.bazel new file mode 100644 index 000000000000..1cc4529eb08d --- /dev/null +++ b/vms/rpcchainvm/gruntime/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "gruntime", + srcs = [ + "runtime_client.go", + "runtime_server.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/gruntime", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/vm/runtime", + "//vms/rpcchainvm/runtime", + "@org_golang_google_protobuf//types/known/emptypb", + ], +) diff --git a/vms/rpcchainvm/runtime/BUILD.bazel b/vms/rpcchainvm/runtime/BUILD.bazel new file mode 100644 index 000000000000..4058c5322c52 --- /dev/null +++ b/vms/rpcchainvm/runtime/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "runtime", + srcs = [ + "manager.go", + "runtime.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime", + visibility = ["//visibility:public"], +) diff --git a/vms/rpcchainvm/runtime/subprocess/BUILD.bazel b/vms/rpcchainvm/runtime/subprocess/BUILD.bazel new file mode 100644 index 000000000000..51439993a989 --- /dev/null +++ b/vms/rpcchainvm/runtime/subprocess/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "subprocess", + srcs = [ + "initializer.go", + "linux_stopper.go", + "non_linux_stopper.go", + "runtime.go", + "stopper.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime/subprocess", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/vm/runtime", + "//utils/logging", + "//version", + "//vms/rpcchainvm/grpcutils", + "//vms/rpcchainvm/gruntime", + "//vms/rpcchainvm/runtime", + "@org_uber_go_zap//:zap", + ] + select({ + "@rules_go//go/platform:android": [ + "//utils/wrappers", + ], + "@rules_go//go/platform:linux": [ + "//utils/wrappers", + ], + "//conditions:default": [], + }), +) diff --git a/vms/secp256k1fx/BUILD.bazel b/vms/secp256k1fx/BUILD.bazel new file mode 100644 index 000000000000..75451c7bd784 --- /dev/null +++ b/vms/secp256k1fx/BUILD.bazel @@ -0,0 +1,68 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "secp256k1fx", + srcs = [ + "credential.go", + "factory.go", + "fx.go", + "input.go", + "keychain.go", + "mint_operation.go", + "mint_output.go", + "output_owners.go", + "transfer_input.go", + "transfer_output.go", + "tx.go", + "vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/secp256k1fx", + visibility = ["//visibility:public"], + deps = [ + "//codec", + "//ids", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/keychain", + "//utils/crypto/secp256k1", + "//utils/formatting", + "//utils/formatting/address", + "//utils/hashing", + "//utils/logging", + "//utils/math", + "//utils/set", + "//utils/timer/mockable", + "//vms/components/verify", + "//vms/fx", + "@com_github_ava_labs_libevm//common", + ], +) + +go_test( + name = "secp256k1fx_test", + srcs = [ + "credential_test.go", + "factory_test.go", + "fx_test.go", + "input_test.go", + "keychain_test.go", + "mint_operation_test.go", + "mint_output_test.go", + "output_owners_test.go", + "transfer_input_test.go", + "transfer_output_test.go", + ], + embed = [":secp256k1fx"], + deps = [ + "//codec", + "//codec/linearcodec", + "//ids", + "//utils/cb58", + "//utils/crypto/secp256k1", + "//utils/formatting", + "//utils/logging", + "//vms/components/verify", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/tracedvm/BUILD.bazel b/vms/tracedvm/BUILD.bazel new file mode 100644 index 000000000000..383ffbd29632 --- /dev/null +++ b/vms/tracedvm/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "tracedvm", + srcs = [ + "batched_vm.go", + "block.go", + "block_vm.go", + "build_block_with_context_vm.go", + "set_preference_with_context_vm.go", + "state_syncable_vm.go", + "tx.go", + "vertex_vm.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/tracedvm", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//snow", + "//snow/consensus/snowman", + "//snow/consensus/snowstorm", + "//snow/engine/avalanche/vertex", + "//snow/engine/common", + "//snow/engine/snowman/block", + "//trace", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + ], +) diff --git a/vms/txs/mempool/BUILD.bazel b/vms/txs/mempool/BUILD.bazel new file mode 100644 index 000000000000..86cb1c3217ef --- /dev/null +++ b/vms/txs/mempool/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "mempool", + srcs = [ + "mempool.go", + "metrics.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/txs/mempool", + visibility = ["//visibility:public"], + deps = [ + "//cache/lru", + "//ids", + "//snow/engine/common", + "//utils/linked", + "//utils/lock", + "//utils/set", + "//utils/setmap", + "//utils/units", + "@com_github_prometheus_client_golang//prometheus", + ], +) + +go_test( + name = "mempool_test", + srcs = ["mempool_test.go"], + embed = [":mempool"], + deps = [ + "//ids", + "//snow/engine/common", + "//utils/set", + "@com_github_stretchr_testify//require", + ], +) diff --git a/vms/types/BUILD.bazel b/vms/types/BUILD.bazel new file mode 100644 index 000000000000..a94629608dcd --- /dev/null +++ b/vms/types/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "types", + srcs = ["blob_data.go"], + importpath = "github.com/ava-labs/avalanchego/vms/types", + visibility = ["//visibility:public"], + deps = ["//utils/formatting"], +) + +go_test( + name = "types_test", + srcs = ["blob_data_test.go"], + embed = [":types"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/vms/vmsmock/BUILD.bazel b/vms/vmsmock/BUILD.bazel new file mode 100644 index 000000000000..e1d4b07e84fb --- /dev/null +++ b/vms/vmsmock/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "vmsmock", + srcs = [ + "factory.go", + "manager.go", + ], + importpath = "github.com/ava-labs/avalanchego/vms/vmsmock", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/logging", + "//vms", + "@org_uber_go_mock//gomock", + ], +) diff --git a/wallet/chain/c/BUILD.bazel b/wallet/chain/c/BUILD.bazel new file mode 100644 index 000000000000..13ac03586602 --- /dev/null +++ b/wallet/chain/c/BUILD.bazel @@ -0,0 +1,39 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "c", + srcs = [ + "backend.go", + "builder.go", + "builder_with_options.go", + "context.go", + "signer.go", + "wallet.go", + "wallet_with_options.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/c", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//database", + "//graft/coreth/ethclient", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/client", + "//ids", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/keychain", + "//utils/crypto/secp256k1", + "//utils/logging", + "//utils/math", + "//utils/rpc", + "//utils/set", + "//vms/avm", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + "@com_github_ava_labs_libevm//common", + ], +) diff --git a/wallet/chain/p/BUILD.bazel b/wallet/chain/p/BUILD.bazel new file mode 100644 index 000000000000..0b6165cc7224 --- /dev/null +++ b/wallet/chain/p/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "p", + srcs = [ + "client.go", + "context.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/p", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//utils/constants", + "//vms/platformvm", + "//vms/platformvm/txs", + "//wallet/chain/p/builder", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + ], +) + +go_test( + name = "p_test", + srcs = ["builder_test.go"], + embed = [":p"], + deps = [ + "//ids", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/crypto/secp256k1", + "//utils/set", + "//utils/units", + "//vms/components/avax", + "//vms/components/gas", + "//vms/platformvm/fx", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//vms/types", + "//wallet/chain/p/builder", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary/common", + "//wallet/subnet/primary/common/utxotest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/wallet/chain/p/builder/BUILD.bazel b/wallet/chain/p/builder/BUILD.bazel new file mode 100644 index 000000000000..0f91364b9878 --- /dev/null +++ b/wallet/chain/p/builder/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "builder", + srcs = [ + "builder.go", + "context.go", + "with_options.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/p/builder", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils", + "//utils/constants", + "//utils/crypto/bls", + "//utils/logging", + "//utils/math", + "//utils/set", + "//vms/components/avax", + "//vms/components/gas", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/signer", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/platformvm/txs/fee", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + ], +) + +go_test( + name = "builder_test", + srcs = ["builder_test.go"], + embed = [":builder"], + deps = [ + "//ids", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/stakeable", + "//vms/secp256k1fx", + "@com_github_stretchr_testify//require", + ], +) diff --git a/wallet/chain/p/signer/BUILD.bazel b/wallet/chain/p/signer/BUILD.bazel new file mode 100644 index 000000000000..4fa7fe9b9978 --- /dev/null +++ b/wallet/chain/p/signer/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "signer", + srcs = [ + "signer.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/p/signer", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils/constants", + "//utils/crypto/keychain", + "//utils/crypto/secp256k1", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/fx", + "//vms/platformvm/stakeable", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + ], +) diff --git a/wallet/chain/p/wallet/BUILD.bazel b/wallet/chain/p/wallet/BUILD.bazel new file mode 100644 index 000000000000..df0374852440 --- /dev/null +++ b/wallet/chain/p/wallet/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "wallet", + srcs = [ + "backend.go", + "backend_visitor.go", + "wallet.go", + "with_options.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/p/wallet", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils/constants", + "//utils/crypto/bls", + "//utils/set", + "//vms/components/avax", + "//vms/platformvm/fx", + "//vms/platformvm/signer", + "//vms/platformvm/txs", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//wallet/chain/p/builder", + "//wallet/chain/p/signer", + "//wallet/subnet/primary/common", + ], +) diff --git a/wallet/chain/x/BUILD.bazel b/wallet/chain/x/BUILD.bazel new file mode 100644 index 000000000000..a0095465ba37 --- /dev/null +++ b/wallet/chain/x/BUILD.bazel @@ -0,0 +1,47 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "x", + srcs = [ + "backend.go", + "backend_visitor.go", + "context.go", + "wallet.go", + "wallet_with_options.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/x", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//ids", + "//vms/avm", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/secp256k1fx", + "//wallet/chain/x/builder", + "//wallet/chain/x/signer", + "//wallet/subnet/primary/common", + ], +) + +go_test( + name = "x_test", + srcs = ["builder_test.go"], + embed = [":x"], + deps = [ + "//ids", + "//utils/constants", + "//utils/crypto/secp256k1", + "//utils/set", + "//utils/units", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//wallet/chain/x/builder", + "//wallet/subnet/primary/common/utxotest", + "@com_github_stretchr_testify//require", + ], +) diff --git a/wallet/chain/x/builder/BUILD.bazel b/wallet/chain/x/builder/BUILD.bazel new file mode 100644 index 000000000000..24ea7cba7314 --- /dev/null +++ b/wallet/chain/x/builder/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "builder", + srcs = [ + "builder.go", + "builder_with_options.go", + "constants.go", + "context.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/x/builder", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//snow", + "//utils", + "//utils/constants", + "//utils/logging", + "//utils/math", + "//utils/set", + "//vms/avm/block", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//wallet/subnet/primary/common", + ], +) diff --git a/wallet/chain/x/signer/BUILD.bazel b/wallet/chain/x/signer/BUILD.bazel new file mode 100644 index 000000000000..b1a9537e7ceb --- /dev/null +++ b/wallet/chain/x/signer/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "signer", + srcs = [ + "signer.go", + "visitor.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/chain/x/signer", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils/crypto/keychain", + "//utils/crypto/secp256k1", + "//vms/avm/fxs", + "//vms/avm/txs", + "//vms/components/avax", + "//vms/components/verify", + "//vms/nftfx", + "//vms/propertyfx", + "//vms/secp256k1fx", + "//wallet/chain/x/builder", + ], +) diff --git a/wallet/subnet/primary/BUILD.bazel b/wallet/subnet/primary/BUILD.bazel new file mode 100644 index 000000000000..b0498c0a06a9 --- /dev/null +++ b/wallet/subnet/primary/BUILD.bazel @@ -0,0 +1,55 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "primary", + srcs = [ + "api.go", + "wallet.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary", + visibility = ["//visibility:public"], + deps = [ + "//api/info", + "//codec", + "//graft/coreth/ethclient", + "//graft/coreth/plugin/evm/atomic", + "//graft/coreth/plugin/evm/client", + "//ids", + "//utils/constants", + "//utils/crypto/keychain", + "//utils/rpc", + "//utils/set", + "//vms/avm", + "//vms/components/avax", + "//vms/platformvm", + "//vms/platformvm/txs", + "//wallet/chain/c", + "//wallet/chain/p", + "//wallet/chain/p/builder", + "//wallet/chain/p/signer", + "//wallet/chain/p/wallet", + "//wallet/chain/x", + "//wallet/chain/x/builder", + "//wallet/chain/x/signer", + "//wallet/subnet/primary/common", + "@com_github_ava_labs_libevm//common", + ], +) + +go_test( + name = "primary_test", + srcs = ["example_test.go"], + embed = [":primary"], + deps = [ + "//genesis", + "//ids", + "//utils/constants", + "//utils/units", + "//vms/components/avax", + "//vms/components/verify", + "//vms/platformvm/reward", + "//vms/platformvm/signer", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + ], +) diff --git a/wallet/subnet/primary/common/BUILD.bazel b/wallet/subnet/primary/common/BUILD.bazel new file mode 100644 index 000000000000..d17d881f0025 --- /dev/null +++ b/wallet/subnet/primary/common/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "common", + srcs = [ + "options.go", + "spend.go", + "utxos.go", + ], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/common", + visibility = ["//visibility:public"], + deps = [ + "//database", + "//ids", + "//utils/set", + "//vms/components/avax", + "//vms/secp256k1fx", + "@com_github_ava_labs_libevm//common", + "@org_golang_x_exp//maps", + ], +) diff --git a/wallet/subnet/primary/common/utxotest/BUILD.bazel b/wallet/subnet/primary/common/utxotest/BUILD.bazel new file mode 100644 index 000000000000..5da3d3222d0a --- /dev/null +++ b/wallet/subnet/primary/common/utxotest/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "utxotest", + srcs = ["utxotest.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/common/utxotest", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//utils/constants", + "//vms/components/avax", + "//wallet/subnet/primary/common", + "@com_github_stretchr_testify//require", + ], +) diff --git a/wallet/subnet/primary/examples/add-permissioned-subnet-validator/BUILD.bazel b/wallet/subnet/primary/examples/add-permissioned-subnet-validator/BUILD.bazel new file mode 100644 index 000000000000..cb81ae27e0d9 --- /dev/null +++ b/wallet/subnet/primary/examples/add-permissioned-subnet-validator/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "add-permissioned-subnet-validator_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/add-permissioned-subnet-validator", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//genesis", + "//ids", + "//utils/units", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "add-permissioned-subnet-validator", + embed = [":add-permissioned-subnet-validator_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/add-primary-validator/BUILD.bazel b/wallet/subnet/primary/examples/add-primary-validator/BUILD.bazel new file mode 100644 index 000000000000..f85640f89f94 --- /dev/null +++ b/wallet/subnet/primary/examples/add-primary-validator/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "add-primary-validator_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/add-primary-validator", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//genesis", + "//ids", + "//utils/units", + "//vms/platformvm/reward", + "//vms/platformvm/txs", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "add-primary-validator", + embed = [":add-primary-validator_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/c-chain-export/BUILD.bazel b/wallet/subnet/primary/examples/c-chain-export/BUILD.bazel new file mode 100644 index 000000000000..96c33cd2a8d4 --- /dev/null +++ b/wallet/subnet/primary/examples/c-chain-export/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "c-chain-export_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/c-chain-export", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/constants", + "//utils/units", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "c-chain-export", + embed = [":c-chain-export_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/c-chain-import/BUILD.bazel b/wallet/subnet/primary/examples/c-chain-import/BUILD.bazel new file mode 100644 index 000000000000..aa6d8d4e4f84 --- /dev/null +++ b/wallet/subnet/primary/examples/c-chain-import/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "c-chain-import_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/c-chain-import", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/constants", + "//utils/units", + "//vms/components/avax", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "c-chain-import", + embed = [":c-chain-import_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/convert-subnet-to-l1/BUILD.bazel b/wallet/subnet/primary/examples/convert-subnet-to-l1/BUILD.bazel new file mode 100644 index 000000000000..ab29aea160dc --- /dev/null +++ b/wallet/subnet/primary/examples/convert-subnet-to-l1/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "convert-subnet-to-l1_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/convert-subnet-to-l1", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//genesis", + "//ids", + "//utils/units", + "//vms/platformvm/txs", + "//vms/platformvm/warp/message", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "convert-subnet-to-l1", + embed = [":convert-subnet-to-l1_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/create-asset/BUILD.bazel b/wallet/subnet/primary/examples/create-asset/BUILD.bazel new file mode 100644 index 000000000000..96d9fb79f836 --- /dev/null +++ b/wallet/subnet/primary/examples/create-asset/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "create-asset_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/create-asset", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/units", + "//vms/components/verify", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "create-asset", + embed = [":create-asset_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/create-chain/BUILD.bazel b/wallet/subnet/primary/examples/create-chain/BUILD.bazel new file mode 100644 index 000000000000..d06807965014 --- /dev/null +++ b/wallet/subnet/primary/examples/create-chain/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "create-chain_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/create-chain", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/constants", + "//vms/example/xsvm/genesis", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "create-chain", + embed = [":create-chain_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/create-locked-stakeable/BUILD.bazel b/wallet/subnet/primary/examples/create-locked-stakeable/BUILD.bazel new file mode 100644 index 000000000000..700410eb5d87 --- /dev/null +++ b/wallet/subnet/primary/examples/create-locked-stakeable/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "create-locked-stakeable_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/create-locked-stakeable", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/formatting/address", + "//utils/units", + "//vms/components/avax", + "//vms/platformvm/stakeable", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "create-locked-stakeable", + embed = [":create-locked-stakeable_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/create-subnet/BUILD.bazel b/wallet/subnet/primary/examples/create-subnet/BUILD.bazel new file mode 100644 index 000000000000..c05152b90d02 --- /dev/null +++ b/wallet/subnet/primary/examples/create-subnet/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "create-subnet_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/create-subnet", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "create-subnet", + embed = [":create-subnet_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/disable-l1-validator/BUILD.bazel b/wallet/subnet/primary/examples/disable-l1-validator/BUILD.bazel new file mode 100644 index 000000000000..516b978ebcad --- /dev/null +++ b/wallet/subnet/primary/examples/disable-l1-validator/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "disable-l1-validator_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/disable-l1-validator", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "disable-l1-validator", + embed = [":disable-l1-validator_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/get-p-chain-balance/BUILD.bazel b/wallet/subnet/primary/examples/get-p-chain-balance/BUILD.bazel new file mode 100644 index 000000000000..c82163134c92 --- /dev/null +++ b/wallet/subnet/primary/examples/get-p-chain-balance/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "get-p-chain-balance_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/get-p-chain-balance", + visibility = ["//visibility:private"], + deps = [ + "//utils/constants", + "//utils/formatting/address", + "//utils/set", + "//wallet/chain/p/builder", + "//wallet/chain/p/wallet", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + ], +) + +go_binary( + name = "get-p-chain-balance", + embed = [":get-p-chain-balance_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/get-x-chain-balance/BUILD.bazel b/wallet/subnet/primary/examples/get-x-chain-balance/BUILD.bazel new file mode 100644 index 000000000000..65e98861076a --- /dev/null +++ b/wallet/subnet/primary/examples/get-x-chain-balance/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "get-x-chain-balance_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/get-x-chain-balance", + visibility = ["//visibility:private"], + deps = [ + "//utils/formatting/address", + "//utils/set", + "//wallet/chain/x", + "//wallet/chain/x/builder", + "//wallet/subnet/primary", + "//wallet/subnet/primary/common", + ], +) + +go_binary( + name = "get-x-chain-balance", + embed = [":get-x-chain-balance_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/increase-l1-validator-balance/BUILD.bazel b/wallet/subnet/primary/examples/increase-l1-validator-balance/BUILD.bazel new file mode 100644 index 000000000000..cdc24a15f0b9 --- /dev/null +++ b/wallet/subnet/primary/examples/increase-l1-validator-balance/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "increase-l1-validator-balance_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/increase-l1-validator-balance", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "increase-l1-validator-balance", + embed = [":increase-l1-validator-balance_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/register-l1-validator/BUILD.bazel b/wallet/subnet/primary/examples/register-l1-validator/BUILD.bazel new file mode 100644 index 000000000000..6190312b945c --- /dev/null +++ b/wallet/subnet/primary/examples/register-l1-validator/BUILD.bazel @@ -0,0 +1,28 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "register-l1-validator_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/register-l1-validator", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//genesis", + "//ids", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/set", + "//utils/units", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "register-l1-validator", + embed = [":register-l1-validator_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/remove-subnet-validator/BUILD.bazel b/wallet/subnet/primary/examples/remove-subnet-validator/BUILD.bazel new file mode 100644 index 000000000000..a44a45b71010 --- /dev/null +++ b/wallet/subnet/primary/examples/remove-subnet-validator/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "remove-subnet-validator_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/remove-subnet-validator", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "remove-subnet-validator", + embed = [":remove-subnet-validator_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/set-l1-validator-weight/BUILD.bazel b/wallet/subnet/primary/examples/set-l1-validator-weight/BUILD.bazel new file mode 100644 index 000000000000..e92b1eef78ae --- /dev/null +++ b/wallet/subnet/primary/examples/set-l1-validator-weight/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "set-l1-validator-weight_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/set-l1-validator-weight", + visibility = ["//visibility:private"], + deps = [ + "//genesis", + "//ids", + "//utils/crypto/bls", + "//utils/crypto/bls/signer/localsigner", + "//utils/set", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//vms/secp256k1fx", + "//wallet/subnet/primary", + ], +) + +go_binary( + name = "set-l1-validator-weight", + embed = [":set-l1-validator-weight_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/sign-l1-validator-registration/BUILD.bazel b/wallet/subnet/primary/examples/sign-l1-validator-registration/BUILD.bazel new file mode 100644 index 000000000000..7cf6cb261b84 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-l1-validator-registration/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "sign-l1-validator-registration_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/sign-l1-validator-registration", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//ids", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/sdk", + "//snow/networking/router", + "//utils/compression", + "//utils/constants", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//wallet/subnet/primary", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_binary( + name = "sign-l1-validator-registration", + embed = [":sign-l1-validator-registration_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/sign-l1-validator-removal-genesis/BUILD.bazel b/wallet/subnet/primary/examples/sign-l1-validator-removal-genesis/BUILD.bazel new file mode 100644 index 000000000000..ad1042f9009b --- /dev/null +++ b/wallet/subnet/primary/examples/sign-l1-validator-removal-genesis/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "sign-l1-validator-removal-genesis_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/sign-l1-validator-removal-genesis", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//ids", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/platformvm", + "//proto/pb/sdk", + "//snow/networking/router", + "//utils/compression", + "//utils/constants", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//wallet/subnet/primary", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_binary( + name = "sign-l1-validator-removal-genesis", + embed = [":sign-l1-validator-removal-genesis_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/sign-l1-validator-removal-registration/BUILD.bazel b/wallet/subnet/primary/examples/sign-l1-validator-removal-registration/BUILD.bazel new file mode 100644 index 000000000000..c6776b7ddc4a --- /dev/null +++ b/wallet/subnet/primary/examples/sign-l1-validator-removal-registration/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "sign-l1-validator-removal-registration_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/sign-l1-validator-removal-registration", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/platformvm", + "//proto/pb/sdk", + "//snow/networking/router", + "//utils/compression", + "//utils/constants", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//wallet/subnet/primary", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_binary( + name = "sign-l1-validator-removal-registration", + embed = [":sign-l1-validator-removal-registration_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/sign-l1-validator-weight/BUILD.bazel b/wallet/subnet/primary/examples/sign-l1-validator-weight/BUILD.bazel new file mode 100644 index 000000000000..d8ffb8270695 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-l1-validator-weight/BUILD.bazel @@ -0,0 +1,30 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "sign-l1-validator-weight_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/sign-l1-validator-weight", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/sdk", + "//snow/networking/router", + "//utils/compression", + "//utils/constants", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//wallet/subnet/primary", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_binary( + name = "sign-l1-validator-weight", + embed = [":sign-l1-validator-weight_lib"], + visibility = ["//visibility:public"], +) diff --git a/wallet/subnet/primary/examples/sign-subnet-to-l1-conversion/BUILD.bazel b/wallet/subnet/primary/examples/sign-subnet-to-l1-conversion/BUILD.bazel new file mode 100644 index 000000000000..890411dee6ff --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-to-l1-conversion/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "sign-subnet-to-l1-conversion_lib", + srcs = ["main.go"], + importpath = "github.com/ava-labs/avalanchego/wallet/subnet/primary/examples/sign-subnet-to-l1-conversion", + visibility = ["//visibility:private"], + deps = [ + "//api/info", + "//ids", + "//message", + "//network/p2p", + "//network/peer", + "//proto/pb/sdk", + "//snow/networking/router", + "//utils/compression", + "//utils/constants", + "//vms/platformvm/warp", + "//vms/platformvm/warp/message", + "//vms/platformvm/warp/payload", + "//wallet/subnet/primary", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + ], +) + +go_binary( + name = "sign-subnet-to-l1-conversion", + embed = [":sign-subnet-to-l1-conversion_lib"], + visibility = ["//visibility:public"], +) diff --git a/x/archivedb/BUILD.bazel b/x/archivedb/BUILD.bazel new file mode 100644 index 000000000000..6dc998626134 --- /dev/null +++ b/x/archivedb/BUILD.bazel @@ -0,0 +1,34 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "archivedb", + srcs = [ + "batch.go", + "db.go", + "key.go", + "reader.go", + "value.go", + ], + importpath = "github.com/ava-labs/avalanchego/x/archivedb", + visibility = ["//visibility:public"], + deps = [ + "//api/health", + "//database", + "//utils/wrappers", + ], +) + +go_test( + name = "archivedb_test", + srcs = [ + "db_test.go", + "key_test.go", + "prefix_test.go", + ], + embed = [":archivedb"], + deps = [ + "//database", + "//database/memdb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/x/blockdb/BUILD.bazel b/x/blockdb/BUILD.bazel new file mode 100644 index 000000000000..02d8f4f1154f --- /dev/null +++ b/x/blockdb/BUILD.bazel @@ -0,0 +1,47 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "blockdb", + srcs = [ + "cache_db.go", + "config.go", + "database.go", + "errors.go", + ], + importpath = "github.com/ava-labs/avalanchego/x/blockdb", + visibility = ["//visibility:public"], + deps = [ + "//cache/lru", + "//database", + "//utils/compression", + "//utils/logging", + "//utils/math", + "@com_github_cespare_xxhash_v2//:xxhash", + "@com_github_datadog_zstd//:zstd", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "blockdb_test", + srcs = [ + "cache_db_test.go", + "database_test.go", + "datasplit_test.go", + "helpers_test.go", + "readblock_test.go", + "recovery_test.go", + "writeblock_test.go", + ], + embed = [":blockdb"], + deps = [ + "//cache/lru", + "//database", + "//database/heightindexdb/dbtest", + "//utils/compression", + "//utils/logging", + "//utils/math", + "@com_github_datadog_zstd//:zstd", + "@com_github_stretchr_testify//require", + ], +) diff --git a/x/merkledb/BUILD.bazel b/x/merkledb/BUILD.bazel new file mode 100644 index 000000000000..97715a4c9d78 --- /dev/null +++ b/x/merkledb/BUILD.bazel @@ -0,0 +1,102 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "merkledb", + srcs = [ + "batch.go", + "bytes_pool.go", + "cache.go", + "codec.go", + "db.go", + "hashing.go", + "history.go", + "intermediate_node_db.go", + "key.go", + "metrics.go", + "node.go", + "proof.go", + "tracer.go", + "trie.go", + "value_node_db.go", + "view.go", + "view_iterator.go", + "wait_group.go", + ], + importpath = "github.com/ava-labs/avalanchego/x/merkledb", + visibility = ["//visibility:public"], + deps = [ + "//cache", + "//cache/lru", + "//database", + "//database/memdb", + "//ids", + "//proto/pb/sync", + "//trace", + "//utils", + "//utils/buffer", + "//utils/heap", + "//utils/linked", + "//utils/maybe", + "//utils/metric", + "//utils/set", + "//utils/units", + "//utils/wrappers", + "//x/sync", + "//x/sync/protoutils", + "@com_github_prometheus_client_golang//prometheus", + "@io_opentelemetry_go_otel//attribute", + "@io_opentelemetry_go_otel_trace//:trace", + "@org_golang_google_protobuf//proto", + "@org_golang_x_exp//maps", + ], +) + +go_test( + name = "merkledb_test", + srcs = [ + "bytes_pool_test.go", + "cache_test.go", + "client_test.go", + "codec_test.go", + "db_test.go", + "hashing_test.go", + "helpers_test.go", + "history_test.go", + "intermediate_node_db_test.go", + "key_test.go", + "metrics_test.go", + "network_server_test.go", + "node_test.go", + "proof_test.go", + "sync_test.go", + "trie_test.go", + "value_node_db_test.go", + "view_iterator_test.go", + "view_test.go", + "wait_group_test.go", + ], + embed = [":merkledb"], + deps = [ + "//database", + "//database/dbtest", + "//database/memdb", + "//ids", + "//network/p2p", + "//network/p2p/p2ptest", + "//proto/pb/sync", + "//snow/engine/common", + "//trace", + "//utils", + "//utils/hashing", + "//utils/logging", + "//utils/maybe", + "//utils/set", + "//utils/units", + "//x/sync", + "@com_github_prometheus_client_golang//prometheus", + "@com_github_stretchr_testify//require", + "@org_golang_google_protobuf//proto", + "@org_golang_x_exp//maps", + "@org_golang_x_sync//errgroup", + ], +) diff --git a/x/sync/BUILD.bazel b/x/sync/BUILD.bazel new file mode 100644 index 000000000000..1ddf277f654b --- /dev/null +++ b/x/sync/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "sync", + srcs = [ + "db.go", + "manager.go", + "metrics.go", + "network_server.go", + "workheap.go", + ], + importpath = "github.com/ava-labs/avalanchego/x/sync", + visibility = ["//visibility:public"], + deps = [ + "//ids", + "//network/p2p", + "//proto/pb/sync", + "//snow/engine/common", + "//utils/constants", + "//utils/hashing", + "//utils/heap", + "//utils/logging", + "//utils/maybe", + "//utils/set", + "//utils/units", + "//x/sync/protoutils", + "@com_github_google_btree//:btree", + "@com_github_prometheus_client_golang//prometheus", + "@org_golang_google_protobuf//proto", + "@org_uber_go_zap//:zap", + ], +) + +go_test( + name = "sync_test", + srcs = [ + "sync_test.go", + "workheap_test.go", + ], + embed = [":sync"], + deps = [ + "//ids", + "//utils/maybe", + "@com_github_stretchr_testify//require", + ], +) diff --git a/x/sync/protoutils/BUILD.bazel b/x/sync/protoutils/BUILD.bazel new file mode 100644 index 000000000000..8446cf6111ef --- /dev/null +++ b/x/sync/protoutils/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "protoutils", + srcs = ["utils.go"], + importpath = "github.com/ava-labs/avalanchego/x/sync/protoutils", + visibility = ["//visibility:public"], + deps = [ + "//proto/pb/sync", + "//utils/maybe", + ], +) From 1377417aba65f9f57fdde0e56b1bb00603b6db17 Mon Sep 17 00:00:00 2001 From: maru Date: Wed, 3 Dec 2025 18:01:56 +0000 Subject: [PATCH 1053/1053] Enable firewood build with bazel --- .gitignore | 3 + BUILD.bazel | 4 + MODULE.bazel | 50 +- MODULE.bazel.lock | 8748 +++++++++++++++++++++- firewood/BUILD.bazel | 4 + firewood/Cargo.lock | 7 + firewood/ffi/BUILD.bazel | 94 + firewood/ffi/tests/eth/BUILD.bazel | 19 + firewood/ffi/tests/firewood/BUILD.bazel | 13 + firewood/firewood-macros/BUILD.bazel | 16 + firewood/firewood/BUILD.bazel | 35 + firewood/storage/BUILD.bazel | 45 + firewood/triehash/BUILD.bazel | 15 + graft/coreth/core/BUILD.bazel | 2 +- graft/coreth/go.mod | 2 + graft/coreth/plugin/evm/BUILD.bazel | 2 +- graft/coreth/tests/utils/BUILD.bazel | 2 +- graft/coreth/triedb/firewood/BUILD.bazel | 2 +- network/p2p/gossip/BUILD.bazel | 3 +- 19 files changed, 9009 insertions(+), 57 deletions(-) create mode 100644 firewood/BUILD.bazel create mode 100644 firewood/ffi/BUILD.bazel create mode 100644 firewood/ffi/tests/eth/BUILD.bazel create mode 100644 firewood/ffi/tests/firewood/BUILD.bazel create mode 100644 firewood/firewood-macros/BUILD.bazel create mode 100644 firewood/firewood/BUILD.bazel create mode 100644 firewood/storage/BUILD.bazel create mode 100644 firewood/triehash/BUILD.bazel diff --git a/.gitignore b/.gitignore index f2c2f521d92f..a166c1457938 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ vendor # debug files __debug_* + +# Bazel symlinks +/bazel-* diff --git a/BUILD.bazel b/BUILD.bazel index cced2664d656..275a4237c9a8 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -31,6 +31,10 @@ load("@gazelle//:def.bzl", "gazelle") # gazelle:resolve go github.com/ava-labs/avalanchego/connectproto/pb/proposervm //connectproto/pb/proposervm # gazelle:resolve go github.com/ava-labs/avalanchego/connectproto/pb/xsvm //connectproto/pb/xsvm +# Resolve firewood imports to local build (handle both module names) +# gazelle:resolve go github.com/ava-labs/firewood-go-ethhash/ffi //firewood/ffi:ffi +# gazelle:resolve go github.com/ava-labs/firewood/ffi //firewood/ffi:ffi + gazelle(name = "gazelle") # Target to update external Go dependencies diff --git a/MODULE.bazel b/MODULE.bazel index 37b4fb7d4eaf..389ecc253f38 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,6 +10,38 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_go", version = "0.59.0") bazel_dep(name = "gazelle", version = "0.47.0") +bazel_dep(name = "rules_rust", version = "0.67.0") + +# Rust toolchain for firewood +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2024", + versions = ["1.91.0"], +) +use_repo(rust, "rust_toolchains") +register_toolchains("@rust_toolchains//:all") + +# Crate universe for firewood Rust dependencies +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +crate.from_cargo( + name = "firewood_crates", + cargo_lockfile = "//firewood:Cargo.lock", + manifests = [ + "//firewood:Cargo.toml", + "//firewood/ffi:Cargo.toml", + "//firewood/firewood:Cargo.toml", + "//firewood/storage:Cargo.toml", + "//firewood/firewood-macros:Cargo.toml", + "//firewood/triehash:Cargo.toml", + ], +) + +# Add extra crates needed for ethhash feature that aren't auto-detected +crate.spec( + package = "bytes", + version = "1.11.0", +) +use_repo(crate, "firewood_crates") # Go SDK registration # Uses same SHA256 checksums as nix/go/default.nix for identical binaries @@ -62,24 +94,7 @@ go_deps.gazelle_override( path = "github.com/consensys/gnark-crypto", ) -# Fix firewood-go-ethhash FFI to use cc_import for pre-built static libraries -# The gazelle-generated BUILD uses -L paths that don't work in Bazel sandbox -go_deps.gazelle_override( - build_file_generation = "off", - path = "github.com/ava-labs/firewood-go-ethhash/ffi", -) -go_deps.module_override( - patch_strip = 1, - patches = ["//patches:firewood_ffi.patch"], - path = "github.com/ava-labs/firewood-go-ethhash/ffi", -) - # Additional dependencies from graft/coreth/go.mod that aren't in the main go.mod -go_deps.module( - path = "github.com/ava-labs/firewood-go-ethhash/ffi", - sum = "h1:NAVjEu508HwdgbxH/xQxMQoBUgYUn9RQf0VeCrhtYMY=", - version = "v0.0.15", -) go_deps.module( path = "github.com/go-cmd/cmd", sum = "h1:6y3G+3UqPerXvPcXvj+5QNPHT02BUw7p6PsqRxLNA7Y=", @@ -96,7 +111,6 @@ use_repo( "com_connectrpc_grpcreflect", "com_github_antithesishq_antithesis_sdk_go", "com_github_ava_labs_avalanchego_graft_coreth", - "com_github_ava_labs_firewood_go_ethhash_ffi", "com_github_ava_labs_libevm", "com_github_ava_labs_simplex", "com_github_ava_labs_subnet_evm", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 75e0b6edc636..63c375ed96f5 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -10,8 +10,9 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", + "https://bcr.bazel.build/modules/apple_support/1.24.1/source.json": "cf725267cbacc5f028ef13bb77e7f2c2e0066923a4dab1025e4a0511b1ed258a", "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", - "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", @@ -19,8 +20,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", - "https://bcr.bazel.build/modules/bazel_features/1.28.0/source.json": "16a3fc5b4483cb307643791f5a4b7365fa98d2e70da7c378cdbde55f0c0b32cf", + "https://bcr.bazel.build/modules/bazel_features/1.32.0/MODULE.bazel": "095d67022a58cb20f7e20e1aefecfa65257a222c18a938e2914fd257b5f1ccdc", + "https://bcr.bazel.build/modules/bazel_features/1.32.0/source.json": "2546c766986a6541f0bacd3e8542a1f621e2b14a80ea9e88c6f89f7eedf64ae1", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", @@ -34,7 +37,8 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", @@ -86,7 +90,9 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", - "https://bcr.bazel.build/modules/rules_cc/0.1.5/source.json": "4bb4fed7f5499775d495739f785a5494a1f854645fa1bac5de131264f5acdf01", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/source.json": "85087982aca15f31307bd52698316b28faa31bd2c3095a41f456afec0131344c", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", @@ -133,9 +139,12 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.31.0/source.json": "a41c836d4065888eef4377f2f27b6eea0fedb9b5adb1bab1970437373fe90dc7", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", + "https://bcr.bazel.build/modules/rules_rust/0.67.0/source.json": "a8ef4d3be30eb98e060cad9e5875a55b603195487f76e01b619b51a1df4641cc", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", @@ -148,34 +157,6 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { - "general": { - "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", - "usagesDigest": "+hz7IHWN6A1oVJJWNDB6yZRG+RYhF76wAYItpAeIUIg=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_config_apple_cc_toolchains": { - "bzlFile": "@@apple_support~//crosstool:setup.bzl", - "ruleClassName": "_apple_cc_autoconf_toolchains", - "attributes": {} - }, - "local_config_apple_cc": { - "bzlFile": "@@apple_support~//crosstool:setup.bzl", - "ruleClassName": "_apple_cc_autoconf", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "apple_support~", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@pybind11_bazel~//:python_configure.bzl%extension": { "general": { "bzlTransitiveDigest": "whINYge95GgPtysKDbNHQ0ZlWYdtKybHs5y2tLF+x7Q=", @@ -2176,6 +2157,8707 @@ ] ] } + }, + "@@rules_rust~//crate_universe:extensions.bzl%crate": { + "general": { + "bzlTransitiveDigest": "1azVd6gpXQg9x3bekeu+IGdkmemcfxLY9DaTmaHAxrA=", + "usagesDigest": "L8U+lw26jVlIaJrnJfy5CBKygS6HrWBBx3Awi/9FlgI=", + "recordedFileInputs": { + "@@//firewood/ffi/Cargo.toml": "a6c8893e3a1e7c63b454b563ee1071179758175bf99339d1861c638cf21186dc", + "@@//firewood/firewood/Cargo.toml": "85ee84082a924315691f3bdee5ff915f947587c2eafa44ed311390a1ea81aa9b", + "@@//firewood/firewood-macros/Cargo.toml": "9b3bb43ec604e10a1462e9a0a2abe062f4aabe4dd15f0e86815f57d50429ce16", + "@@//firewood/Cargo.lock": "77ddbda024f564ba0990c0ecc4ef03024d9c787b98188ce5b07531e51c5afc98", + "@@//firewood/triehash/Cargo.toml": "550839ddb3ca61414681843185b47464023ede9f1d575a7cf44580e810c6a686", + "@@//firewood/storage/Cargo.toml": "98cbb8044c967c335a2e0d212fa6735fdd5a7c1c473ebd88a13b7ca106fc87cd", + "@@//firewood/Cargo.toml": "1251caa48ba8b498a932cbead1eb4f8fb6f0852d1d41dbaa955b06189370ed40", + "@@//firewood/fwdctl/Cargo.toml": "0a34f265c3d6dc767f4146e7f99019548af8329d22492f931c6bd1e784e375da", + "@@//MODULE.bazel": "8f9763b0b150604df04c7dcf3939f88368874b0c6b1b73853fdea696f692fef7", + "@@rules_rust~~rust_host_tools~rust_host_tools//rust_host_tools": "ENOENT", + "@@//firewood/benchmark/Cargo.toml": "8f6665728370082c9968311e75c1ba92f457eb5398487eb8e4749e83fe6086d9" + }, + "recordedDirentsInputs": {}, + "envVariables": { + "CARGO_BAZEL_DEBUG": null, + "CARGO_BAZEL_GENERATOR_SHA256": null, + "CARGO_BAZEL_GENERATOR_URL": null, + "CARGO_BAZEL_ISOLATED": null, + "CARGO_BAZEL_REPIN": null, + "CARGO_BAZEL_REPIN_ONLY": null, + "CARGO_BAZEL_TIMEOUT": null, + "REPIN": null + }, + "generatedRepoSpecs": { + "firewood_crates": { + "bzlFile": "@@rules_rust~//crate_universe:extensions.bzl", + "ruleClassName": "_generate_repo", + "attributes": { + "contents": { + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"anyhow-1.0.100\",\n actual = \"@firewood_crates__anyhow-1.0.100//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anyhow\",\n actual = \"@firewood_crates__anyhow-1.0.100//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aquamarine-0.6.0\",\n actual = \"@firewood_crates__aquamarine-0.6.0//:aquamarine\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aquamarine\",\n actual = \"@firewood_crates__aquamarine-0.6.0//:aquamarine\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"askama-0.14.0\",\n actual = \"@firewood_crates__askama-0.14.0//:askama\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"askama\",\n actual = \"@firewood_crates__askama-0.14.0//:askama\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd-2.1.1\",\n actual = \"@firewood_crates__assert_cmd-2.1.1//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"assert_cmd\",\n actual = \"@firewood_crates__assert_cmd-2.1.1//:assert_cmd\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-0.19.4\",\n actual = \"@firewood_crates__bitfield-0.19.4//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield\",\n actual = \"@firewood_crates__bitfield-0.19.4//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags-2.10.0\",\n actual = \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags\",\n actual = \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bumpalo-3.19.0\",\n actual = \"@firewood_crates__bumpalo-3.19.0//:bumpalo\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bumpalo\",\n actual = \"@firewood_crates__bumpalo-3.19.0//:bumpalo\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytemuck-1.24.0\",\n actual = \"@firewood_crates__bytemuck-1.24.0//:bytemuck\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytemuck\",\n actual = \"@firewood_crates__bytemuck-1.24.0//:bytemuck\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytemuck_derive-1.10.2\",\n actual = \"@firewood_crates__bytemuck_derive-1.10.2//:bytemuck_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytemuck_derive\",\n actual = \"@firewood_crates__bytemuck_derive-1.10.2//:bytemuck_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytes-1.11.0\",\n actual = \"@firewood_crates__bytes-1.11.0//:bytes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytes\",\n actual = \"@firewood_crates__bytes-1.11.0//:bytes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cbindgen-0.29.2\",\n actual = \"@firewood_crates__cbindgen-0.29.2//:cbindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cbindgen\",\n actual = \"@firewood_crates__cbindgen-0.29.2//:cbindgen\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"chrono-0.4.42\",\n actual = \"@firewood_crates__chrono-0.4.42//:chrono\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"chrono\",\n actual = \"@firewood_crates__chrono-0.4.42//:chrono\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.5.52\",\n actual = \"@firewood_crates__clap-4.5.52//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@firewood_crates__clap-4.5.52//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"coarsetime-0.1.36\",\n actual = \"@firewood_crates__coarsetime-0.1.36//:coarsetime\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"coarsetime\",\n actual = \"@firewood_crates__coarsetime-0.1.36//:coarsetime\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion-0.7.0\",\n actual = \"@firewood_crates__criterion-0.7.0//:criterion\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion\",\n actual = \"@firewood_crates__criterion-0.7.0//:criterion\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"csv-1.4.0\",\n actual = \"@firewood_crates__csv-1.4.0//:csv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"csv\",\n actual = \"@firewood_crates__csv-1.4.0//:csv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctor-0.6.1\",\n actual = \"@firewood_crates__ctor-0.6.1//:ctor\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctor\",\n actual = \"@firewood_crates__ctor-0.6.1//:ctor\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"derive-where-1.6.0\",\n actual = \"@firewood_crates__derive-where-1.6.0//:derive_where\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"derive-where\",\n actual = \"@firewood_crates__derive-where-1.6.0//:derive_where\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum-as-inner-0.6.1\",\n actual = \"@firewood_crates__enum-as-inner-0.6.1//:enum_as_inner\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"enum-as-inner\",\n actual = \"@firewood_crates__enum-as-inner-0.6.1//:enum_as_inner\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"env_logger-0.11.8\",\n actual = \"@firewood_crates__env_logger-0.11.8//:env_logger\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"env_logger\",\n actual = \"@firewood_crates__env_logger-0.11.8//:env_logger\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ethereum-types-0.16.0\",\n actual = \"@firewood_crates__ethereum-types-0.16.0//:ethereum_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ethereum-types\",\n actual = \"@firewood_crates__ethereum-types-0.16.0//:ethereum_types\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrace-0.7.14\",\n actual = \"@firewood_crates__fastrace-0.7.14//:fastrace\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrace\",\n actual = \"@firewood_crates__fastrace-0.7.14//:fastrace\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrace-opentelemetry-0.14.0\",\n actual = \"@firewood_crates__fastrace-opentelemetry-0.14.0//:fastrace_opentelemetry\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrace-opentelemetry\",\n actual = \"@firewood_crates__fastrace-opentelemetry-0.14.0//:fastrace_opentelemetry\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fjall-2.11.2\",\n actual = \"@firewood_crates__fjall-2.11.2//:fjall\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fjall\",\n actual = \"@firewood_crates__fjall-2.11.2//:fjall\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hash-db-0.16.0\",\n actual = \"@firewood_crates__hash-db-0.16.0//:hash_db\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hash-db\",\n actual = \"@firewood_crates__hash-db-0.16.0//:hash_db\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex-0.4.3\",\n actual = \"@firewood_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex\",\n actual = \"@firewood_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex-literal-1.1.0\",\n actual = \"@firewood_crates__hex-literal-1.1.0//:hex_literal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex-literal\",\n actual = \"@firewood_crates__hex-literal-1.1.0//:hex_literal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indicatif-0.18.3\",\n actual = \"@firewood_crates__indicatif-0.18.3//:indicatif\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"indicatif\",\n actual = \"@firewood_crates__indicatif-0.18.3//:indicatif\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"integer-encoding-4.1.0\",\n actual = \"@firewood_crates__integer-encoding-4.1.0//:integer_encoding\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"integer-encoding\",\n actual = \"@firewood_crates__integer-encoding-4.1.0//:integer_encoding\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"io-uring-0.7.11\",\n actual = \"@firewood_crates__io-uring-0.7.11//:io_uring\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"io-uring\",\n actual = \"@firewood_crates__io-uring-0.7.11//:io_uring\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"keccak-hasher-0.16.0\",\n actual = \"@firewood_crates__keccak-hasher-0.16.0//:keccak_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"keccak-hasher\",\n actual = \"@firewood_crates__keccak-hasher-0.16.0//:keccak_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.28\",\n actual = \"@firewood_crates__log-0.4.28//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@firewood_crates__log-0.4.28//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lru-0.16.2\",\n actual = \"@firewood_crates__lru-0.16.2//:lru\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lru\",\n actual = \"@firewood_crates__lru-0.16.2//:lru\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics-0.24.2\",\n actual = \"@firewood_crates__metrics-0.24.2//:metrics\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics\",\n actual = \"@firewood_crates__metrics-0.24.2//:metrics\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics-exporter-prometheus-0.17.2\",\n actual = \"@firewood_crates__metrics-exporter-prometheus-0.17.2//:metrics_exporter_prometheus\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics-exporter-prometheus\",\n actual = \"@firewood_crates__metrics-exporter-prometheus-0.17.2//:metrics_exporter_prometheus\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics-util-0.20.0\",\n actual = \"@firewood_crates__metrics-util-0.20.0//:metrics_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"metrics-util\",\n actual = \"@firewood_crates__metrics-util-0.20.0//:metrics_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nonzero_ext-0.3.0\",\n actual = \"@firewood_crates__nonzero_ext-0.3.0//:nonzero_ext\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nonzero_ext\",\n actual = \"@firewood_crates__nonzero_ext-0.3.0//:nonzero_ext\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"num-format-0.4.4\",\n actual = \"@firewood_crates__num-format-0.4.4//:num_format\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"num-format\",\n actual = \"@firewood_crates__num-format-0.4.4//:num_format\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry-0.31.0\",\n actual = \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry\",\n actual = \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry-otlp-0.31.0\",\n actual = \"@firewood_crates__opentelemetry-otlp-0.31.0//:opentelemetry_otlp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry-otlp\",\n actual = \"@firewood_crates__opentelemetry-otlp-0.31.0//:opentelemetry_otlp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry-proto-0.31.0\",\n actual = \"@firewood_crates__opentelemetry-proto-0.31.0//:opentelemetry_proto\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry-proto\",\n actual = \"@firewood_crates__opentelemetry-proto-0.31.0//:opentelemetry_proto\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry_sdk-0.31.0\",\n actual = \"@firewood_crates__opentelemetry_sdk-0.31.0//:opentelemetry_sdk\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"opentelemetry_sdk\",\n actual = \"@firewood_crates__opentelemetry_sdk-0.31.0//:opentelemetry_sdk\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"oxhttp-0.3.1\",\n actual = \"@firewood_crates__oxhttp-0.3.1//:oxhttp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"oxhttp\",\n actual = \"@firewood_crates__oxhttp-0.3.1//:oxhttp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"parking_lot-0.12.5\",\n actual = \"@firewood_crates__parking_lot-0.12.5//:parking_lot\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"parking_lot\",\n actual = \"@firewood_crates__parking_lot-0.12.5//:parking_lot\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"plain_hasher-0.2.3\",\n actual = \"@firewood_crates__plain_hasher-0.2.3//:plain_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"plain_hasher\",\n actual = \"@firewood_crates__plain_hasher-0.2.3//:plain_hasher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pprof-0.15.0\",\n actual = \"@firewood_crates__pprof-0.15.0//:pprof\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pprof\",\n actual = \"@firewood_crates__pprof-0.15.0//:pprof\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"predicates-3.1.3\",\n actual = \"@firewood_crates__predicates-3.1.3//:predicates\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"predicates\",\n actual = \"@firewood_crates__predicates-3.1.3//:predicates\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty-duration-0.1.1\",\n actual = \"@firewood_crates__pretty-duration-0.1.1//:pretty_duration\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pretty-duration\",\n actual = \"@firewood_crates__pretty-duration-0.1.1//:pretty_duration\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2-1.0.103\",\n actual = \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2\",\n actual = \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote-1.0.42\",\n actual = \"@firewood_crates__quote-1.0.42//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote\",\n actual = \"@firewood_crates__quote-1.0.42//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand-0.9.2\",\n actual = \"@firewood_crates__rand-0.9.2//:rand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand\",\n actual = \"@firewood_crates__rand-0.9.2//:rand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_distr-0.5.1\",\n actual = \"@firewood_crates__rand_distr-0.5.1//:rand_distr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_distr\",\n actual = \"@firewood_crates__rand_distr-0.5.1//:rand_distr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon-1.11.0\",\n actual = \"@firewood_crates__rayon-1.11.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rayon\",\n actual = \"@firewood_crates__rayon-1.11.0//:rayon\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rlp-0.6.1\",\n actual = \"@firewood_crates__rlp-0.6.1//:rlp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rlp\",\n actual = \"@firewood_crates__rlp-0.6.1//:rlp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"semver-1.0.27\",\n actual = \"@firewood_crates__semver-1.0.27//:semver\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"semver\",\n actual = \"@firewood_crates__semver-1.0.27//:semver\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serial_test-3.2.0\",\n actual = \"@firewood_crates__serial_test-3.2.0//:serial_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serial_test\",\n actual = \"@firewood_crates__serial_test-3.2.0//:serial_test\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2-0.10.9\",\n actual = \"@firewood_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2\",\n actual = \"@firewood_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3-0.10.8\",\n actual = \"@firewood_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3\",\n actual = \"@firewood_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smallvec-1.15.1\",\n actual = \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smallvec\",\n actual = \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-2.0.110\",\n actual = \"@firewood_crates__syn-2.0.110//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn\",\n actual = \"@firewood_crates__syn-2.0.110//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile-3.23.0\",\n actual = \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tempfile\",\n actual = \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"test-case-3.3.1\",\n actual = \"@firewood_crates__test-case-3.3.1//:test_case\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"test-case\",\n actual = \"@firewood_crates__test-case-3.3.1//:test_case\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.17\",\n actual = \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tikv-jemallocator-0.6.1\",\n actual = \"@firewood_crates__tikv-jemallocator-0.6.1//:tikv_jemallocator\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tikv-jemallocator\",\n actual = \"@firewood_crates__tikv-jemallocator-0.6.1//:tikv_jemallocator\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tiny-keccak-2.0.2\",\n actual = \"@firewood_crates__tiny-keccak-2.0.2//:tiny_keccak\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tiny-keccak\",\n actual = \"@firewood_crates__tiny-keccak-2.0.2//:tiny_keccak\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-1.48.0\",\n actual = \"@firewood_crates__tokio-1.48.0//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio\",\n actual = \"@firewood_crates__tokio-1.48.0//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"trie-standardmap-0.16.0\",\n actual = \"@firewood_crates__trie-standardmap-0.16.0//:trie_standardmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"trie-standardmap\",\n actual = \"@firewood_crates__trie-standardmap-0.16.0//:trie_standardmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"triomphe-0.1.15\",\n actual = \"@firewood_crates__triomphe-0.1.15//:triomphe\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"triomphe\",\n actual = \"@firewood_crates__triomphe-0.1.15//:triomphe\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"trybuild-1.0.114\",\n actual = \"@firewood_crates__trybuild-1.0.114//:trybuild\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"trybuild\",\n actual = \"@firewood_crates__trybuild-1.0.114//:trybuild\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"typed-builder-0.23.1\",\n actual = \"@firewood_crates__typed-builder-0.23.1//:typed_builder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"typed-builder\",\n actual = \"@firewood_crates__typed-builder-0.23.1//:typed_builder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"weak-table-0.3.2\",\n actual = \"@firewood_crates__weak-table-0.3.2//:weak_table\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"weak-table\",\n actual = \"@firewood_crates__weak-table-0.3.2//:weak_table\",\n tags = [\"manual\"],\n)\n", + "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"firewood\": {\n _COMMON_CONDITION: {\n \"bytes\": Label(\"@firewood_crates//:bytes-1.11.0\"),\n },\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n \"bytemuck\": Label(\"@firewood_crates//:bytemuck-1.24.0\"),\n \"coarsetime\": Label(\"@firewood_crates//:coarsetime-0.1.36\"),\n \"fastrace\": Label(\"@firewood_crates//:fastrace-0.7.14\"),\n \"fjall\": Label(\"@firewood_crates//:fjall-2.11.2\"),\n \"hex\": Label(\"@firewood_crates//:hex-0.4.3\"),\n \"integer-encoding\": Label(\"@firewood_crates//:integer-encoding-4.1.0\"),\n \"metrics\": Label(\"@firewood_crates//:metrics-0.24.2\"),\n \"parking_lot\": Label(\"@firewood_crates//:parking_lot-0.12.5\"),\n \"rayon\": Label(\"@firewood_crates//:rayon-1.11.0\"),\n \"thiserror\": Label(\"@firewood_crates//:thiserror-2.0.17\"),\n \"typed-builder\": Label(\"@firewood_crates//:typed-builder-0.23.1\"),\n \"weak-table\": Label(\"@firewood_crates//:weak-table-0.3.2\"),\n },\n },\n \"firewood/benchmark\": {\n _COMMON_CONDITION: {\n \"clap\": Label(\"@firewood_crates//:clap-4.5.52\"),\n \"env_logger\": Label(\"@firewood_crates//:env_logger-0.11.8\"),\n \"fastrace\": Label(\"@firewood_crates//:fastrace-0.7.14\"),\n \"fastrace-opentelemetry\": Label(\"@firewood_crates//:fastrace-opentelemetry-0.14.0\"),\n \"hex\": Label(\"@firewood_crates//:hex-0.4.3\"),\n \"log\": Label(\"@firewood_crates//:log-0.4.28\"),\n \"metrics\": Label(\"@firewood_crates//:metrics-0.24.2\"),\n \"metrics-exporter-prometheus\": Label(\"@firewood_crates//:metrics-exporter-prometheus-0.17.2\"),\n \"metrics-util\": Label(\"@firewood_crates//:metrics-util-0.20.0\"),\n \"opentelemetry\": Label(\"@firewood_crates//:opentelemetry-0.31.0\"),\n \"opentelemetry-otlp\": Label(\"@firewood_crates//:opentelemetry-otlp-0.31.0\"),\n \"opentelemetry-proto\": Label(\"@firewood_crates//:opentelemetry-proto-0.31.0\"),\n \"opentelemetry_sdk\": Label(\"@firewood_crates//:opentelemetry_sdk-0.31.0\"),\n \"pretty-duration\": Label(\"@firewood_crates//:pretty-duration-0.1.1\"),\n \"rand\": Label(\"@firewood_crates//:rand-0.9.2\"),\n \"rand_distr\": Label(\"@firewood_crates//:rand_distr-0.5.1\"),\n \"sha2\": Label(\"@firewood_crates//:sha2-0.10.9\"),\n \"tokio\": Label(\"@firewood_crates//:tokio-1.48.0\"),\n },\n \"cfg(unix)\": {\n \"tikv-jemallocator\": Label(\"@firewood_crates//:tikv-jemallocator-0.6.1\"),\n },\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n \"chrono\": Label(\"@firewood_crates//:chrono-0.4.42\"),\n \"coarsetime\": Label(\"@firewood_crates//:coarsetime-0.1.36\"),\n \"metrics\": Label(\"@firewood_crates//:metrics-0.24.2\"),\n \"metrics-util\": Label(\"@firewood_crates//:metrics-util-0.20.0\"),\n \"oxhttp\": Label(\"@firewood_crates//:oxhttp-0.3.1\"),\n \"parking_lot\": Label(\"@firewood_crates//:parking_lot-0.12.5\"),\n },\n \"cfg(unix)\": {\n \"tikv-jemallocator\": Label(\"@firewood_crates//:tikv-jemallocator-0.6.1\"),\n },\n },\n \"firewood/fwdctl\": {\n _COMMON_CONDITION: {\n \"askama\": Label(\"@firewood_crates//:askama-0.14.0\"),\n \"clap\": Label(\"@firewood_crates//:clap-4.5.52\"),\n \"csv\": Label(\"@firewood_crates//:csv-1.4.0\"),\n \"env_logger\": Label(\"@firewood_crates//:env_logger-0.11.8\"),\n \"hex\": Label(\"@firewood_crates//:hex-0.4.3\"),\n \"indicatif\": Label(\"@firewood_crates//:indicatif-0.18.3\"),\n \"log\": Label(\"@firewood_crates//:log-0.4.28\"),\n \"nonzero_ext\": Label(\"@firewood_crates//:nonzero_ext-0.3.0\"),\n \"num-format\": Label(\"@firewood_crates//:num-format-0.4.4\"),\n },\n },\n \"firewood/firewood-macros\": {\n _COMMON_CONDITION: {\n \"proc-macro2\": Label(\"@firewood_crates//:proc-macro2-1.0.103\"),\n \"quote\": Label(\"@firewood_crates//:quote-1.0.42\"),\n \"syn\": Label(\"@firewood_crates//:syn-2.0.110\"),\n },\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n \"bitfield\": Label(\"@firewood_crates//:bitfield-0.19.4\"),\n \"bitflags\": Label(\"@firewood_crates//:bitflags-2.10.0\"),\n \"bumpalo\": Label(\"@firewood_crates//:bumpalo-3.19.0\"),\n \"bytemuck\": Label(\"@firewood_crates//:bytemuck-1.24.0\"),\n \"coarsetime\": Label(\"@firewood_crates//:coarsetime-0.1.36\"),\n \"fastrace\": Label(\"@firewood_crates//:fastrace-0.7.14\"),\n \"hex\": Label(\"@firewood_crates//:hex-0.4.3\"),\n \"indicatif\": Label(\"@firewood_crates//:indicatif-0.18.3\"),\n \"integer-encoding\": Label(\"@firewood_crates//:integer-encoding-4.1.0\"),\n \"lru\": Label(\"@firewood_crates//:lru-0.16.2\"),\n \"metrics\": Label(\"@firewood_crates//:metrics-0.24.2\"),\n \"nonzero_ext\": Label(\"@firewood_crates//:nonzero_ext-0.3.0\"),\n \"parking_lot\": Label(\"@firewood_crates//:parking_lot-0.12.5\"),\n \"rand\": Label(\"@firewood_crates//:rand-0.9.2\"),\n \"semver\": Label(\"@firewood_crates//:semver-1.0.27\"),\n \"sha2\": Label(\"@firewood_crates//:sha2-0.10.9\"),\n \"smallvec\": Label(\"@firewood_crates//:smallvec-1.15.1\"),\n \"thiserror\": Label(\"@firewood_crates//:thiserror-2.0.17\"),\n \"triomphe\": Label(\"@firewood_crates//:triomphe-0.1.15\"),\n },\n \"aarch64-unknown-linux-gnu\": {\n \"io-uring\": Label(\"@firewood_crates//:io-uring-0.7.11\"),\n },\n \"x86_64-unknown-linux-gnu\": {\n \"io-uring\": Label(\"@firewood_crates//:io-uring-0.7.11\"),\n },\n \"x86_64-unknown-nixos-gnu\": {\n \"io-uring\": Label(\"@firewood_crates//:io-uring-0.7.11\"),\n },\n },\n \"firewood/triehash\": {\n _COMMON_CONDITION: {\n \"hash-db\": Label(\"@firewood_crates//:hash-db-0.16.0\"),\n \"rlp\": Label(\"@firewood_crates//:rlp-0.6.1\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"firewood\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/benchmark\": {\n _COMMON_CONDITION: {\n },\n \"cfg(unix)\": {\n },\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n },\n \"cfg(unix)\": {\n },\n },\n \"firewood/fwdctl\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/firewood-macros\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n },\n \"aarch64-unknown-linux-gnu\": {\n },\n \"x86_64-unknown-linux-gnu\": {\n },\n \"x86_64-unknown-nixos-gnu\": {\n },\n },\n \"firewood/triehash\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n \"clap\": Label(\"@firewood_crates//:clap-4.5.52\"),\n \"criterion\": Label(\"@firewood_crates//:criterion-0.7.0\"),\n \"ctor\": Label(\"@firewood_crates//:ctor-0.6.1\"),\n \"env_logger\": Label(\"@firewood_crates//:env_logger-0.11.8\"),\n \"ethereum-types\": Label(\"@firewood_crates//:ethereum-types-0.16.0\"),\n \"hash-db\": Label(\"@firewood_crates//:hash-db-0.16.0\"),\n \"hex-literal\": Label(\"@firewood_crates//:hex-literal-1.1.0\"),\n \"plain_hasher\": Label(\"@firewood_crates//:plain_hasher-0.2.3\"),\n \"pprof\": Label(\"@firewood_crates//:pprof-0.15.0\"),\n \"rand\": Label(\"@firewood_crates//:rand-0.9.2\"),\n \"rlp\": Label(\"@firewood_crates//:rlp-0.6.1\"),\n \"sha3\": Label(\"@firewood_crates//:sha3-0.10.8\"),\n \"tempfile\": Label(\"@firewood_crates//:tempfile-3.23.0\"),\n \"test-case\": Label(\"@firewood_crates//:test-case-3.3.1\"),\n },\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n \"test-case\": Label(\"@firewood_crates//:test-case-3.3.1\"),\n },\n },\n \"firewood/fwdctl\": {\n _COMMON_CONDITION: {\n \"anyhow\": Label(\"@firewood_crates//:anyhow-1.0.100\"),\n \"assert_cmd\": Label(\"@firewood_crates//:assert_cmd-2.1.1\"),\n \"predicates\": Label(\"@firewood_crates//:predicates-3.1.3\"),\n \"rand\": Label(\"@firewood_crates//:rand-0.9.2\"),\n \"serial_test\": Label(\"@firewood_crates//:serial_test-3.2.0\"),\n },\n },\n \"firewood/firewood-macros\": {\n _COMMON_CONDITION: {\n \"coarsetime\": Label(\"@firewood_crates//:coarsetime-0.1.36\"),\n \"metrics\": Label(\"@firewood_crates//:metrics-0.24.2\"),\n \"trybuild\": Label(\"@firewood_crates//:trybuild-1.0.114\"),\n },\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n \"criterion\": Label(\"@firewood_crates//:criterion-0.7.0\"),\n \"pprof\": Label(\"@firewood_crates//:pprof-0.15.0\"),\n \"tempfile\": Label(\"@firewood_crates//:tempfile-3.23.0\"),\n \"test-case\": Label(\"@firewood_crates//:test-case-3.3.1\"),\n },\n },\n \"firewood/triehash\": {\n _COMMON_CONDITION: {\n \"criterion\": Label(\"@firewood_crates//:criterion-0.7.0\"),\n \"ethereum-types\": Label(\"@firewood_crates//:ethereum-types-0.16.0\"),\n \"hex-literal\": Label(\"@firewood_crates//:hex-literal-1.1.0\"),\n \"keccak-hasher\": Label(\"@firewood_crates//:keccak-hasher-0.16.0\"),\n \"tiny-keccak\": Label(\"@firewood_crates//:tiny-keccak-2.0.2\"),\n \"trie-standardmap\": Label(\"@firewood_crates//:trie-standardmap-0.16.0\"),\n },\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/fwdctl\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/firewood-macros\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/triehash\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n \"bytemuck_derive\": Label(\"@firewood_crates//:bytemuck_derive-1.10.2\"),\n \"derive-where\": Label(\"@firewood_crates//:derive-where-1.6.0\"),\n },\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n \"derive-where\": Label(\"@firewood_crates//:derive-where-1.6.0\"),\n },\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n \"aquamarine\": Label(\"@firewood_crates//:aquamarine-0.6.0\"),\n \"bytemuck_derive\": Label(\"@firewood_crates//:bytemuck_derive-1.10.2\"),\n \"derive-where\": Label(\"@firewood_crates//:derive-where-1.6.0\"),\n \"enum-as-inner\": Label(\"@firewood_crates//:enum-as-inner-0.6.1\"),\n },\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/fwdctl\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/firewood-macros\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/storage\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/triehash\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n \"cbindgen\": Label(\"@firewood_crates//:cbindgen-0.29.2\"),\n },\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n _COMMON_CONDITION: {\n },\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"firewood\": {\n },\n \"firewood/firewood\": {\n },\n \"firewood/benchmark\": {\n },\n \"firewood/ffi\": {\n },\n \"firewood/fwdctl\": {\n },\n \"firewood/firewood-macros\": {\n },\n \"firewood/storage\": {\n },\n \"firewood/triehash\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-linux-android\": [],\n \"aarch64-pc-windows-gnullvm\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_os = \\\"windows\\\"))\": [],\n \"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_vendor = \\\"apple\\\", any(target_os = \\\"ios\\\", target_os = \\\"macos\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(any(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), all(target_arch = \\\"arm\\\", target_endian = \\\"little\\\")), any(target_os = \\\"android\\\", target_os = \\\"linux\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(all(any(target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_os = \\\"linux\\\"))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"loongarch64\\\", target_os = \\\"linux\\\"))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\": [],\n \"cfg(all(unix, not(target_os = \\\"macos\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any())\": [],\n \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\": [],\n \"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\": [],\n \"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\": [],\n \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"ios\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(target_os = \\\"wasix\\\", target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(windows, target_os = \\\"cygwin\\\"))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(not(all(target_arch = \\\"arm\\\", target_os = \\\"none\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(any(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"aarch64\\\"), any(target_os = \\\"linux\\\", target_os = \\\"macos\\\", target_os = \\\"windows\\\"), any(target_env = \\\"gnu\\\", target_env = \\\"musl\\\", target_env = \\\"msvc\\\", target_env = \\\"\\\")), all(target_arch = \\\"x86\\\", target_os = \\\"windows\\\", target_env = \\\"msvc\\\"), all(target_arch = \\\"x86\\\", target_os = \\\"linux\\\", target_env = \\\"gnu\\\"))))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(not(any(target_os = \\\"wasix\\\", target_os = \\\"wasi\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(any(target_os = \\\"windows\\\", target_arch = \\\"wasm32\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_arch = \\\"wasm32\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_has_atomic = \\\"ptr\\\"))\": [],\n \"cfg(target_arch = \\\"aarch64\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(target_arch = \\\"spirv\\\")\": [],\n \"cfg(target_arch = \\\"wasm32\\\")\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(target_arch = \\\"x86\\\")\": [],\n \"cfg(target_arch = \\\"x86_64\\\")\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(target_feature = \\\"atomics\\\")\": [],\n \"cfg(target_os = \\\"android\\\")\": [],\n \"cfg(target_os = \\\"haiku\\\")\": [],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"macos\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(target_os = \\\"netbsd\\\")\": [],\n \"cfg(target_os = \\\"redox\\\")\": [],\n \"cfg(target_os = \\\"solaris\\\")\": [],\n \"cfg(target_os = \\\"vxworks\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(target_os = \\\"windows\\\")\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(target_pointer_width = \\\"32\\\")\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(target_vendor = \\\"apple\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(windows)\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(windows_raw_dylib)\": [],\n \"i686-pc-windows-gnu\": [],\n \"i686-pc-windows-gnullvm\": [],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-gnu\": [],\n \"x86_64-pc-windows-gnullvm\": [],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"firewood_crates__addr2line-0.25.1\",\n sha256 = \"1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/addr2line/0.25.1/download\"],\n strip_prefix = \"addr2line-0.25.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.addr2line-0.25.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__adler2-2.0.1\",\n sha256 = \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/adler2/2.0.1/download\"],\n strip_prefix = \"adler2-2.0.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.adler2-2.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ahash-0.8.12\",\n sha256 = \"5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ahash/0.8.12/download\"],\n strip_prefix = \"ahash-0.8.12\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ahash-0.8.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__aho-corasick-1.1.4\",\n sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n strip_prefix = \"aho-corasick-1.1.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.aho-corasick-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__aligned-vec-0.6.4\",\n sha256 = \"dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aligned-vec/0.6.4/download\"],\n strip_prefix = \"aligned-vec-0.6.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.aligned-vec-0.6.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__allocator-api2-0.2.21\",\n sha256 = \"683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/allocator-api2/0.2.21/download\"],\n strip_prefix = \"allocator-api2-0.2.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.allocator-api2-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__android_system_properties-0.1.5\",\n sha256 = \"819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/android_system_properties/0.1.5/download\"],\n strip_prefix = \"android_system_properties-0.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.android_system_properties-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anes-0.1.6\",\n sha256 = \"4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anes/0.1.6/download\"],\n strip_prefix = \"anes-0.1.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anes-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anstream-0.6.21\",\n sha256 = \"43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/0.6.21/download\"],\n strip_prefix = \"anstream-0.6.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anstream-0.6.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anstyle-1.0.13\",\n sha256 = \"5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.13/download\"],\n strip_prefix = \"anstyle-1.0.13\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anstyle-1.0.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anstyle-parse-0.2.7\",\n sha256 = \"4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/0.2.7/download\"],\n strip_prefix = \"anstyle-parse-0.2.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anstyle-parse-0.2.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__anyhow-1.0.100\",\n sha256 = \"a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.100/download\"],\n strip_prefix = \"anyhow-1.0.100\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.anyhow-1.0.100.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__aquamarine-0.6.0\",\n sha256 = \"0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aquamarine/0.6.0/download\"],\n strip_prefix = \"aquamarine-0.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.aquamarine-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__arrayvec-0.7.6\",\n sha256 = \"7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/arrayvec/0.7.6/download\"],\n strip_prefix = \"arrayvec-0.7.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.arrayvec-0.7.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__askama-0.14.0\",\n sha256 = \"f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/askama/0.14.0/download\"],\n strip_prefix = \"askama-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.askama-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__askama_derive-0.14.0\",\n sha256 = \"129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/askama_derive/0.14.0/download\"],\n strip_prefix = \"askama_derive-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.askama_derive-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__askama_parser-0.14.0\",\n sha256 = \"d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/askama_parser/0.14.0/download\"],\n strip_prefix = \"askama_parser-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.askama_parser-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__assert_cmd-2.1.1\",\n sha256 = \"bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/assert_cmd/2.1.1/download\"],\n strip_prefix = \"assert_cmd-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.assert_cmd-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__async-trait-0.1.89\",\n sha256 = \"9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/async-trait/0.1.89/download\"],\n strip_prefix = \"async-trait-0.1.89\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.async-trait-0.1.89.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__atomic-waker-1.1.2\",\n sha256 = \"1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/atomic-waker/1.1.2/download\"],\n strip_prefix = \"atomic-waker-1.1.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.atomic-waker-1.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__aws-lc-rs-1.15.0\",\n sha256 = \"5932a7d9d28b0d2ea34c6b3779d35e3dd6f6345317c34e73438c4f1f29144151\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aws-lc-rs/1.15.0/download\"],\n strip_prefix = \"aws-lc-rs-1.15.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.aws-lc-rs-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__aws-lc-sys-0.33.0\",\n sha256 = \"1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aws-lc-sys/0.33.0/download\"],\n strip_prefix = \"aws-lc-sys-0.33.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.aws-lc-sys-0.33.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__backtrace-0.3.76\",\n sha256 = \"bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/backtrace/0.3.76/download\"],\n strip_prefix = \"backtrace-0.3.76\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.backtrace-0.3.76.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__base64-0.22.1\",\n sha256 = \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64/0.22.1/download\"],\n strip_prefix = \"base64-0.22.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.base64-0.22.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__basic-toml-0.1.10\",\n sha256 = \"ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/basic-toml/0.1.10/download\"],\n strip_prefix = \"basic-toml-0.1.10\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.basic-toml-0.1.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bindgen-0.72.1\",\n sha256 = \"993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bindgen/0.72.1/download\"],\n strip_prefix = \"bindgen-0.72.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bindgen-0.72.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bitfield-0.19.4\",\n sha256 = \"21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield/0.19.4/download\"],\n strip_prefix = \"bitfield-0.19.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bitfield-0.19.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bitfield-macros-0.19.4\",\n sha256 = \"f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield-macros/0.19.4/download\"],\n strip_prefix = \"bitfield-macros-0.19.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bitfield-macros-0.19.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bitflags-1.3.2\",\n sha256 = \"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/1.3.2/download\"],\n strip_prefix = \"bitflags-1.3.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bitflags-1.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bitflags-2.10.0\",\n sha256 = \"812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.10.0/download\"],\n strip_prefix = \"bitflags-2.10.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bitflags-2.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bitvec-1.0.1\",\n sha256 = \"1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitvec/1.0.1/download\"],\n strip_prefix = \"bitvec-1.0.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bitvec-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__block-buffer-0.10.4\",\n sha256 = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/block-buffer/0.10.4/download\"],\n strip_prefix = \"block-buffer-0.10.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.block-buffer-0.10.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bstr-1.12.1\",\n sha256 = \"63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bstr/1.12.1/download\"],\n strip_prefix = \"bstr-1.12.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bstr-1.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bumpalo-3.19.0\",\n sha256 = \"46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bumpalo/3.19.0/download\"],\n strip_prefix = \"bumpalo-3.19.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bumpalo-3.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__byte-slice-cast-1.2.3\",\n sha256 = \"7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byte-slice-cast/1.2.3/download\"],\n strip_prefix = \"byte-slice-cast-1.2.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.byte-slice-cast-1.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bytemuck-1.24.0\",\n sha256 = \"1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytemuck/1.24.0/download\"],\n strip_prefix = \"bytemuck-1.24.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bytemuck-1.24.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bytemuck_derive-1.10.2\",\n sha256 = \"f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytemuck_derive/1.10.2/download\"],\n strip_prefix = \"bytemuck_derive-1.10.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bytemuck_derive-1.10.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__byteorder-1.5.0\",\n sha256 = \"1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byteorder/1.5.0/download\"],\n strip_prefix = \"byteorder-1.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.byteorder-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__bytes-1.11.0\",\n sha256 = \"b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.0/download\"],\n strip_prefix = \"bytes-1.11.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.bytes-1.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__byteview-0.6.1\",\n sha256 = \"6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byteview/0.6.1/download\"],\n strip_prefix = \"byteview-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.byteview-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cast-0.3.0\",\n sha256 = \"37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cast/0.3.0/download\"],\n strip_prefix = \"cast-0.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cast-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cbindgen-0.29.2\",\n sha256 = \"befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cbindgen/0.29.2/download\"],\n strip_prefix = \"cbindgen-0.29.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cbindgen-0.29.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cc-1.2.46\",\n sha256 = \"b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cc/1.2.46/download\"],\n strip_prefix = \"cc-1.2.46\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cc-1.2.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cexpr-0.6.0\",\n sha256 = \"6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cexpr/0.6.0/download\"],\n strip_prefix = \"cexpr-0.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cexpr-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__chrono-0.4.42\",\n sha256 = \"145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/chrono/0.4.42/download\"],\n strip_prefix = \"chrono-0.4.42\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.chrono-0.4.42.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ciborium-0.2.2\",\n sha256 = \"42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium/0.2.2/download\"],\n strip_prefix = \"ciborium-0.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ciborium-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ciborium-io-0.2.2\",\n sha256 = \"05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-io/0.2.2/download\"],\n strip_prefix = \"ciborium-io-0.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ciborium-io-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ciborium-ll-0.2.2\",\n sha256 = \"57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-ll/0.2.2/download\"],\n strip_prefix = \"ciborium-ll-0.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ciborium-ll-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__clang-sys-1.8.1\",\n sha256 = \"0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clang-sys/1.8.1/download\"],\n strip_prefix = \"clang-sys-1.8.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.clang-sys-1.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__clap-4.5.52\",\n sha256 = \"aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.5.52/download\"],\n strip_prefix = \"clap-4.5.52\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.clap-4.5.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__clap_builder-4.5.52\",\n sha256 = \"02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.5.52/download\"],\n strip_prefix = \"clap_builder-4.5.52\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.clap_builder-4.5.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__clap_derive-4.5.49\",\n sha256 = \"2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.5.49/download\"],\n strip_prefix = \"clap_derive-4.5.49\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.clap_derive-4.5.49.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__clap_lex-0.7.6\",\n sha256 = \"a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/0.7.6/download\"],\n strip_prefix = \"clap_lex-0.7.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.clap_lex-0.7.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cmake-0.1.54\",\n sha256 = \"e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cmake/0.1.54/download\"],\n strip_prefix = \"cmake-0.1.54\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cmake-0.1.54.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__coarsetime-0.1.36\",\n sha256 = \"91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/coarsetime/0.1.36/download\"],\n strip_prefix = \"coarsetime-0.1.36\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.coarsetime-0.1.36.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__colorchoice-1.0.4\",\n sha256 = \"b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.4/download\"],\n strip_prefix = \"colorchoice-1.0.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.colorchoice-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__compare-0.0.6\",\n sha256 = \"ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compare/0.0.6/download\"],\n strip_prefix = \"compare-0.0.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.compare-0.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__console-0.16.1\",\n sha256 = \"b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/console/0.16.1/download\"],\n strip_prefix = \"console-0.16.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.console-0.16.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__const-hex-1.17.0\",\n sha256 = \"3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const-hex/1.17.0/download\"],\n strip_prefix = \"const-hex-1.17.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.const-hex-1.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__const_format-0.2.35\",\n sha256 = \"7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const_format/0.2.35/download\"],\n strip_prefix = \"const_format-0.2.35\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.const_format-0.2.35.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__const_format_proc_macros-0.2.34\",\n sha256 = \"1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const_format_proc_macros/0.2.34/download\"],\n strip_prefix = \"const_format_proc_macros-0.2.34\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.const_format_proc_macros-0.2.34.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__core-foundation-0.10.1\",\n sha256 = \"b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/core-foundation/0.10.1/download\"],\n strip_prefix = \"core-foundation-0.10.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.core-foundation-0.10.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__core-foundation-sys-0.8.7\",\n sha256 = \"773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/core-foundation-sys/0.8.7/download\"],\n strip_prefix = \"core-foundation-sys-0.8.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.core-foundation-sys-0.8.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cpp_demangle-0.4.5\",\n sha256 = \"f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cpp_demangle/0.4.5/download\"],\n strip_prefix = \"cpp_demangle-0.4.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cpp_demangle-0.4.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__cpufeatures-0.2.17\",\n sha256 = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cpufeatures/0.2.17/download\"],\n strip_prefix = \"cpufeatures-0.2.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.cpufeatures-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__criterion-0.7.0\",\n sha256 = \"e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion/0.7.0/download\"],\n strip_prefix = \"criterion-0.7.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.criterion-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__criterion-plot-0.6.0\",\n sha256 = \"9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion-plot/0.6.0/download\"],\n strip_prefix = \"criterion-plot-0.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.criterion-plot-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crossbeam-deque-0.8.6\",\n sha256 = \"9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-deque/0.8.6/download\"],\n strip_prefix = \"crossbeam-deque-0.8.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crossbeam-deque-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crossbeam-epoch-0.9.18\",\n sha256 = \"5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-epoch/0.9.18/download\"],\n strip_prefix = \"crossbeam-epoch-0.9.18\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crossbeam-epoch-0.9.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crossbeam-skiplist-0.1.3\",\n sha256 = \"df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-skiplist/0.1.3/download\"],\n strip_prefix = \"crossbeam-skiplist-0.1.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crossbeam-skiplist-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crossbeam-utils-0.8.21\",\n sha256 = \"d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-utils/0.8.21/download\"],\n strip_prefix = \"crossbeam-utils-0.8.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crossbeam-utils-0.8.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crunchy-0.2.4\",\n sha256 = \"460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crunchy/0.2.4/download\"],\n strip_prefix = \"crunchy-0.2.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crunchy-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__crypto-common-0.1.7\",\n sha256 = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-common/0.1.7/download\"],\n strip_prefix = \"crypto-common-0.1.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.crypto-common-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__csv-1.4.0\",\n sha256 = \"52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/csv/1.4.0/download\"],\n strip_prefix = \"csv-1.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.csv-1.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__csv-core-0.1.13\",\n sha256 = \"704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/csv-core/0.1.13/download\"],\n strip_prefix = \"csv-core-0.1.13\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.csv-core-0.1.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ctor-0.6.1\",\n sha256 = \"3ffc71fcdcdb40d6f087edddf7f8f1f8f79e6cf922f555a9ee8779752d4819bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ctor/0.6.1/download\"],\n strip_prefix = \"ctor-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ctor-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ctor-proc-macro-0.0.7\",\n sha256 = \"52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ctor-proc-macro/0.0.7/download\"],\n strip_prefix = \"ctor-proc-macro-0.0.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ctor-proc-macro-0.0.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__dashmap-6.1.0\",\n sha256 = \"5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dashmap/6.1.0/download\"],\n strip_prefix = \"dashmap-6.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.dashmap-6.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__debugid-0.8.0\",\n sha256 = \"bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/debugid/0.8.0/download\"],\n strip_prefix = \"debugid-0.8.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.debugid-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__derive-where-1.6.0\",\n sha256 = \"ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/derive-where/1.6.0/download\"],\n strip_prefix = \"derive-where-1.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.derive-where-1.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__difflib-0.4.0\",\n sha256 = \"6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/difflib/0.4.0/download\"],\n strip_prefix = \"difflib-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.difflib-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__digest-0.10.7\",\n sha256 = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/digest/0.10.7/download\"],\n strip_prefix = \"digest-0.10.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.digest-0.10.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__displaydoc-0.2.5\",\n sha256 = \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/displaydoc/0.2.5/download\"],\n strip_prefix = \"displaydoc-0.2.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.displaydoc-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__double-ended-peekable-0.1.0\",\n sha256 = \"c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/double-ended-peekable/0.1.0/download\"],\n strip_prefix = \"double-ended-peekable-0.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.double-ended-peekable-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__dtor-0.1.1\",\n sha256 = \"404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dtor/0.1.1/download\"],\n strip_prefix = \"dtor-0.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.dtor-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__dtor-proc-macro-0.0.6\",\n sha256 = \"f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dtor-proc-macro/0.0.6/download\"],\n strip_prefix = \"dtor-proc-macro-0.0.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.dtor-proc-macro-0.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__dunce-1.0.5\",\n sha256 = \"92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dunce/1.0.5/download\"],\n strip_prefix = \"dunce-1.0.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.dunce-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__encode_unicode-1.0.0\",\n sha256 = \"34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/encode_unicode/1.0.0/download\"],\n strip_prefix = \"encode_unicode-1.0.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.encode_unicode-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__endian-type-0.1.2\",\n sha256 = \"c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/endian-type/0.1.2/download\"],\n strip_prefix = \"endian-type-0.1.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.endian-type-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__enum-as-inner-0.6.1\",\n sha256 = \"a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/enum-as-inner/0.6.1/download\"],\n strip_prefix = \"enum-as-inner-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.enum-as-inner-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__enum_dispatch-0.3.13\",\n sha256 = \"aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/enum_dispatch/0.3.13/download\"],\n strip_prefix = \"enum_dispatch-0.3.13\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.enum_dispatch-0.3.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__env_filter-0.1.4\",\n sha256 = \"1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/env_filter/0.1.4/download\"],\n strip_prefix = \"env_filter-0.1.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.env_filter-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__env_logger-0.11.8\",\n sha256 = \"13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/env_logger/0.11.8/download\"],\n strip_prefix = \"env_logger-0.11.8\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.env_logger-0.11.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__equator-0.4.2\",\n sha256 = \"4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equator/0.4.2/download\"],\n strip_prefix = \"equator-0.4.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.equator-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__equator-macro-0.4.2\",\n sha256 = \"44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equator-macro/0.4.2/download\"],\n strip_prefix = \"equator-macro-0.4.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.equator-macro-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ethbloom-0.14.1\",\n sha256 = \"8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ethbloom/0.14.1/download\"],\n strip_prefix = \"ethbloom-0.14.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ethbloom-0.14.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ethereum-types-0.16.0\",\n sha256 = \"f7326303c6c18bb03c7a3c4c22d4032ae60dfe673ca9109602aa4d8154b2637e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ethereum-types/0.16.0/download\"],\n strip_prefix = \"ethereum-types-0.16.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ethereum-types-0.16.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fastant-0.1.10\",\n sha256 = \"62bf7fa928ce0c4a43bd6e7d1235318fc32ac3a3dea06a2208c44e729449471a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastant/0.1.10/download\"],\n strip_prefix = \"fastant-0.1.10\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fastant-0.1.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fastrace-0.7.14\",\n sha256 = \"318783b9fefe06130ab664ff1779215657586b004c0c7f3d6ece16d658936d06\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrace/0.7.14/download\"],\n strip_prefix = \"fastrace-0.7.14\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fastrace-0.7.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fastrace-macro-0.7.14\",\n sha256 = \"c7079009cf129d63c850dee732b58d7639d278a47ad99c607954ac94cfd57ef4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrace-macro/0.7.14/download\"],\n strip_prefix = \"fastrace-macro-0.7.14\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fastrace-macro-0.7.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fastrace-opentelemetry-0.14.0\",\n sha256 = \"b7e8ec7cff0ea398352764b6ee15c0902ccabf23d823525254b52d7f878fcf60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrace-opentelemetry/0.14.0/download\"],\n strip_prefix = \"fastrace-opentelemetry-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fastrace-opentelemetry-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fastrand-2.3.0\",\n sha256 = \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrand/2.3.0/download\"],\n strip_prefix = \"fastrand-2.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fastrand-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__find-msvc-tools-0.1.5\",\n sha256 = \"3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/find-msvc-tools/0.1.5/download\"],\n strip_prefix = \"find-msvc-tools-0.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.find-msvc-tools-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__findshlibs-0.10.2\",\n sha256 = \"40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/findshlibs/0.10.2/download\"],\n strip_prefix = \"findshlibs-0.10.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.findshlibs-0.10.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fixed-hash-0.8.0\",\n sha256 = \"835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fixed-hash/0.8.0/download\"],\n strip_prefix = \"fixed-hash-0.8.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fixed-hash-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fjall-2.11.2\",\n sha256 = \"0b25ad44cd4360a0448a9b5a0a6f1c7a621101cca4578706d43c9a821418aebc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fjall/2.11.2/download\"],\n strip_prefix = \"fjall-2.11.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fjall-2.11.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__float-cmp-0.10.0\",\n sha256 = \"b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/float-cmp/0.10.0/download\"],\n strip_prefix = \"float-cmp-0.10.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.float-cmp-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fnv-1.0.7\",\n sha256 = \"3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fnv/1.0.7/download\"],\n strip_prefix = \"fnv-1.0.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fnv-1.0.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__foldhash-0.2.0\",\n sha256 = \"77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.2.0/download\"],\n strip_prefix = \"foldhash-0.2.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.foldhash-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__form_urlencoded-1.2.2\",\n sha256 = \"cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/form_urlencoded/1.2.2/download\"],\n strip_prefix = \"form_urlencoded-1.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.form_urlencoded-1.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__fs_extra-1.3.0\",\n sha256 = \"42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fs_extra/1.3.0/download\"],\n strip_prefix = \"fs_extra-1.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.fs_extra-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__funty-2.0.0\",\n sha256 = \"e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/funty/2.0.0/download\"],\n strip_prefix = \"funty-2.0.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.funty-2.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-0.3.31\",\n sha256 = \"65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures/0.3.31/download\"],\n strip_prefix = \"futures-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-channel-0.3.31\",\n sha256 = \"2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-channel/0.3.31/download\"],\n strip_prefix = \"futures-channel-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-channel-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-core-0.3.31\",\n sha256 = \"05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-core/0.3.31/download\"],\n strip_prefix = \"futures-core-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-core-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-executor-0.3.31\",\n sha256 = \"1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-executor/0.3.31/download\"],\n strip_prefix = \"futures-executor-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-executor-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-io-0.3.31\",\n sha256 = \"9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-io/0.3.31/download\"],\n strip_prefix = \"futures-io-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-io-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-macro-0.3.31\",\n sha256 = \"162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-macro/0.3.31/download\"],\n strip_prefix = \"futures-macro-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-macro-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-sink-0.3.31\",\n sha256 = \"e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-sink/0.3.31/download\"],\n strip_prefix = \"futures-sink-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-sink-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-task-0.3.31\",\n sha256 = \"f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-task/0.3.31/download\"],\n strip_prefix = \"futures-task-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-task-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__futures-util-0.3.31\",\n sha256 = \"9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-util/0.3.31/download\"],\n strip_prefix = \"futures-util-0.3.31\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.futures-util-0.3.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__generic-array-0.14.7\",\n sha256 = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/generic-array/0.14.7/download\"],\n strip_prefix = \"generic-array-0.14.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.generic-array-0.14.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__getrandom-0.2.16\",\n sha256 = \"335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.2.16/download\"],\n strip_prefix = \"getrandom-0.2.16\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.getrandom-0.2.16.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__getrandom-0.3.4\",\n sha256 = \"899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.3.4/download\"],\n strip_prefix = \"getrandom-0.3.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.getrandom-0.3.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__gimli-0.32.3\",\n sha256 = \"e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/gimli/0.32.3/download\"],\n strip_prefix = \"gimli-0.32.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.gimli-0.32.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__glob-0.3.3\",\n sha256 = \"0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/glob/0.3.3/download\"],\n strip_prefix = \"glob-0.3.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.glob-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__guardian-1.3.0\",\n sha256 = \"17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/guardian/1.3.0/download\"],\n strip_prefix = \"guardian-1.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.guardian-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__h2-0.4.12\",\n sha256 = \"f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/h2/0.4.12/download\"],\n strip_prefix = \"h2-0.4.12\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.h2-0.4.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__half-2.7.1\",\n sha256 = \"6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/half/2.7.1/download\"],\n strip_prefix = \"half-2.7.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.half-2.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hash-db-0.16.0\",\n sha256 = \"8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hash-db/0.16.0/download\"],\n strip_prefix = \"hash-db-0.16.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hash-db-0.16.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hash256-std-hasher-0.15.2\",\n sha256 = \"92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hash256-std-hasher/0.15.2/download\"],\n strip_prefix = \"hash256-std-hasher-0.15.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hash256-std-hasher-0.15.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hashbrown-0.14.5\",\n sha256 = \"e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.14.5/download\"],\n strip_prefix = \"hashbrown-0.14.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hashbrown-0.14.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hashbrown-0.16.0\",\n sha256 = \"5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.16.0/download\"],\n strip_prefix = \"hashbrown-0.16.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hashbrown-0.16.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hermit-abi-0.5.2\",\n sha256 = \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hermit-abi/0.5.2/download\"],\n strip_prefix = \"hermit-abi-0.5.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hermit-abi-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hex-literal-1.1.0\",\n sha256 = \"e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex-literal/1.1.0/download\"],\n strip_prefix = \"hex-literal-1.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hex-literal-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__http-1.3.1\",\n sha256 = \"f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http/1.3.1/download\"],\n strip_prefix = \"http-1.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.http-1.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__http-body-1.0.1\",\n sha256 = \"1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http-body/1.0.1/download\"],\n strip_prefix = \"http-body-1.0.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.http-body-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__http-body-util-0.1.3\",\n sha256 = \"b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http-body-util/0.1.3/download\"],\n strip_prefix = \"http-body-util-0.1.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.http-body-util-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__httparse-1.10.1\",\n sha256 = \"6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/httparse/1.10.1/download\"],\n strip_prefix = \"httparse-1.10.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.httparse-1.10.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__httpdate-1.0.3\",\n sha256 = \"df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/httpdate/1.0.3/download\"],\n strip_prefix = \"httpdate-1.0.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.httpdate-1.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hyper-1.8.1\",\n sha256 = \"2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper/1.8.1/download\"],\n strip_prefix = \"hyper-1.8.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hyper-1.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hyper-rustls-0.27.7\",\n sha256 = \"e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-rustls/0.27.7/download\"],\n strip_prefix = \"hyper-rustls-0.27.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hyper-rustls-0.27.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hyper-timeout-0.5.2\",\n sha256 = \"2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-timeout/0.5.2/download\"],\n strip_prefix = \"hyper-timeout-0.5.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hyper-timeout-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__hyper-util-0.1.18\",\n sha256 = \"52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-util/0.1.18/download\"],\n strip_prefix = \"hyper-util-0.1.18\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.hyper-util-0.1.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__iana-time-zone-0.1.64\",\n sha256 = \"33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/iana-time-zone/0.1.64/download\"],\n strip_prefix = \"iana-time-zone-0.1.64\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.iana-time-zone-0.1.64.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__iana-time-zone-haiku-0.1.2\",\n sha256 = \"f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/iana-time-zone-haiku/0.1.2/download\"],\n strip_prefix = \"iana-time-zone-haiku-0.1.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.iana-time-zone-haiku-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_collections-2.1.1\",\n sha256 = \"4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_collections/2.1.1/download\"],\n strip_prefix = \"icu_collections-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_collections-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_locale_core-2.1.1\",\n sha256 = \"edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_locale_core/2.1.1/download\"],\n strip_prefix = \"icu_locale_core-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_locale_core-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_normalizer-2.1.1\",\n sha256 = \"5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_normalizer/2.1.1/download\"],\n strip_prefix = \"icu_normalizer-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_normalizer-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_normalizer_data-2.1.1\",\n sha256 = \"7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_normalizer_data/2.1.1/download\"],\n strip_prefix = \"icu_normalizer_data-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_normalizer_data-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_properties-2.1.1\",\n sha256 = \"e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_properties/2.1.1/download\"],\n strip_prefix = \"icu_properties-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_properties-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_properties_data-2.1.1\",\n sha256 = \"02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_properties_data/2.1.1/download\"],\n strip_prefix = \"icu_properties_data-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_properties_data-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__icu_provider-2.1.1\",\n sha256 = \"85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_provider/2.1.1/download\"],\n strip_prefix = \"icu_provider-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.icu_provider-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__idna-1.1.0\",\n sha256 = \"3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/idna/1.1.0/download\"],\n strip_prefix = \"idna-1.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.idna-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__idna_adapter-1.2.1\",\n sha256 = \"3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/idna_adapter/1.2.1/download\"],\n strip_prefix = \"idna_adapter-1.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.idna_adapter-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__impl-codec-0.7.1\",\n sha256 = \"2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/impl-codec/0.7.1/download\"],\n strip_prefix = \"impl-codec-0.7.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.impl-codec-0.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__impl-rlp-0.4.0\",\n sha256 = \"54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/impl-rlp/0.4.0/download\"],\n strip_prefix = \"impl-rlp-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.impl-rlp-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__impl-serde-0.5.0\",\n sha256 = \"4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/impl-serde/0.5.0/download\"],\n strip_prefix = \"impl-serde-0.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.impl-serde-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__impl-trait-for-tuples-0.2.3\",\n sha256 = \"a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/impl-trait-for-tuples/0.2.3/download\"],\n strip_prefix = \"impl-trait-for-tuples-0.2.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.impl-trait-for-tuples-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__include_dir-0.7.4\",\n sha256 = \"923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/include_dir/0.7.4/download\"],\n strip_prefix = \"include_dir-0.7.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.include_dir-0.7.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__include_dir_macros-0.7.4\",\n sha256 = \"7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/include_dir_macros/0.7.4/download\"],\n strip_prefix = \"include_dir_macros-0.7.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.include_dir_macros-0.7.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__indexmap-2.12.0\",\n sha256 = \"6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.12.0/download\"],\n strip_prefix = \"indexmap-2.12.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.indexmap-2.12.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__indicatif-0.18.3\",\n sha256 = \"9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indicatif/0.18.3/download\"],\n strip_prefix = \"indicatif-0.18.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.indicatif-0.18.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__inferno-0.11.21\",\n sha256 = \"232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/inferno/0.11.21/download\"],\n strip_prefix = \"inferno-0.11.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.inferno-0.11.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__integer-encoding-4.1.0\",\n sha256 = \"14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/integer-encoding/4.1.0/download\"],\n strip_prefix = \"integer-encoding-4.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.integer-encoding-4.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__interval-heap-0.0.5\",\n sha256 = \"11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/interval-heap/0.0.5/download\"],\n strip_prefix = \"interval-heap-0.0.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.interval-heap-0.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__io-uring-0.7.11\",\n sha256 = \"fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/io-uring/0.7.11/download\"],\n strip_prefix = \"io-uring-0.7.11\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.io-uring-0.7.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ipnet-2.11.0\",\n sha256 = \"469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ipnet/2.11.0/download\"],\n strip_prefix = \"ipnet-2.11.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ipnet-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__iri-string-0.7.9\",\n sha256 = \"4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/iri-string/0.7.9/download\"],\n strip_prefix = \"iri-string-0.7.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.iri-string-0.7.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__is-terminal-0.4.17\",\n sha256 = \"3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is-terminal/0.4.17/download\"],\n strip_prefix = \"is-terminal-0.4.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.is-terminal-0.4.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__itertools-0.10.5\",\n sha256 = \"b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.10.5/download\"],\n strip_prefix = \"itertools-0.10.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.itertools-0.10.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__itertools-0.13.0\",\n sha256 = \"413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.13.0/download\"],\n strip_prefix = \"itertools-0.13.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.itertools-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__itertools-0.14.0\",\n sha256 = \"2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.14.0/download\"],\n strip_prefix = \"itertools-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.itertools-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__itoa-1.0.15\",\n sha256 = \"4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itoa/1.0.15/download\"],\n strip_prefix = \"itoa-1.0.15\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.itoa-1.0.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__jiff-0.2.16\",\n sha256 = \"49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jiff/0.2.16/download\"],\n strip_prefix = \"jiff-0.2.16\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.jiff-0.2.16.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__jiff-static-0.2.16\",\n sha256 = \"980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jiff-static/0.2.16/download\"],\n strip_prefix = \"jiff-static-0.2.16\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.jiff-static-0.2.16.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__jobserver-0.1.34\",\n sha256 = \"9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jobserver/0.1.34/download\"],\n strip_prefix = \"jobserver-0.1.34\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.jobserver-0.1.34.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__js-sys-0.3.82\",\n sha256 = \"b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/js-sys/0.3.82/download\"],\n strip_prefix = \"js-sys-0.3.82\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.js-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__keccak-0.1.5\",\n sha256 = \"ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/keccak/0.1.5/download\"],\n strip_prefix = \"keccak-0.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.keccak-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__keccak-hasher-0.16.0\",\n sha256 = \"19ea4653859ca2266a86419d3f592d3f22e7a854b482f99180d2498507902048\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/keccak-hasher/0.16.0/download\"],\n strip_prefix = \"keccak-hasher-0.16.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.keccak-hasher-0.16.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__lazy_static-1.5.0\",\n sha256 = \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy_static/1.5.0/download\"],\n strip_prefix = \"lazy_static-1.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.lazy_static-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__libc-0.2.177\",\n sha256 = \"2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.177/download\"],\n strip_prefix = \"libc-0.2.177\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.libc-0.2.177.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__libloading-0.8.9\",\n sha256 = \"d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libloading/0.8.9/download\"],\n strip_prefix = \"libloading-0.8.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.libloading-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__libm-0.2.15\",\n sha256 = \"f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libm/0.2.15/download\"],\n strip_prefix = \"libm-0.2.15\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.libm-0.2.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__linux-raw-sys-0.11.0\",\n sha256 = \"df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.11.0/download\"],\n strip_prefix = \"linux-raw-sys-0.11.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.linux-raw-sys-0.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__litemap-0.8.1\",\n sha256 = \"6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/litemap/0.8.1/download\"],\n strip_prefix = \"litemap-0.8.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.litemap-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__lock_api-0.4.14\",\n sha256 = \"224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lock_api/0.4.14/download\"],\n strip_prefix = \"lock_api-0.4.14\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.lock_api-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__log-0.4.28\",\n sha256 = \"34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.28/download\"],\n strip_prefix = \"log-0.4.28\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.log-0.4.28.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__lru-0.16.2\",\n sha256 = \"96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lru/0.16.2/download\"],\n strip_prefix = \"lru-0.16.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.lru-0.16.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__lsm-tree-2.10.4\",\n sha256 = \"799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lsm-tree/2.10.4/download\"],\n strip_prefix = \"lsm-tree-2.10.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.lsm-tree-2.10.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__lz4_flex-0.11.5\",\n sha256 = \"08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lz4_flex/0.11.5/download\"],\n strip_prefix = \"lz4_flex-0.11.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.lz4_flex-0.11.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__memchr-2.7.6\",\n sha256 = \"f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.7.6/download\"],\n strip_prefix = \"memchr-2.7.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.memchr-2.7.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__memmap2-0.9.9\",\n sha256 = \"744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memmap2/0.9.9/download\"],\n strip_prefix = \"memmap2-0.9.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.memmap2-0.9.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__metrics-0.24.2\",\n sha256 = \"25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/metrics/0.24.2/download\"],\n strip_prefix = \"metrics-0.24.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.metrics-0.24.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__metrics-exporter-prometheus-0.17.2\",\n sha256 = \"2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/metrics-exporter-prometheus/0.17.2/download\"],\n strip_prefix = \"metrics-exporter-prometheus-0.17.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.metrics-exporter-prometheus-0.17.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__metrics-util-0.20.0\",\n sha256 = \"fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/metrics-util/0.20.0/download\"],\n strip_prefix = \"metrics-util-0.20.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.metrics-util-0.20.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__minimal-lexical-0.2.1\",\n sha256 = \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minimal-lexical/0.2.1/download\"],\n strip_prefix = \"minimal-lexical-0.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.minimal-lexical-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__miniz_oxide-0.8.9\",\n sha256 = \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/miniz_oxide/0.8.9/download\"],\n strip_prefix = \"miniz_oxide-0.8.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.miniz_oxide-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__mio-1.1.0\",\n sha256 = \"69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mio/1.1.0/download\"],\n strip_prefix = \"mio-1.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.mio-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__nibble_vec-0.1.0\",\n sha256 = \"77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nibble_vec/0.1.0/download\"],\n strip_prefix = \"nibble_vec-0.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.nibble_vec-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__nix-0.26.4\",\n sha256 = \"598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nix/0.26.4/download\"],\n strip_prefix = \"nix-0.26.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.nix-0.26.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__nom-7.1.3\",\n sha256 = \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nom/7.1.3/download\"],\n strip_prefix = \"nom-7.1.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.nom-7.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__nonzero_ext-0.3.0\",\n sha256 = \"38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nonzero_ext/0.3.0/download\"],\n strip_prefix = \"nonzero_ext-0.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.nonzero_ext-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__normalize-line-endings-0.3.0\",\n sha256 = \"61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/normalize-line-endings/0.3.0/download\"],\n strip_prefix = \"normalize-line-endings-0.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.normalize-line-endings-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__num-format-0.4.4\",\n sha256 = \"a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-format/0.4.4/download\"],\n strip_prefix = \"num-format-0.4.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.num-format-0.4.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__num-traits-0.2.19\",\n sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n strip_prefix = \"num-traits-0.2.19\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.num-traits-0.2.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__object-0.37.3\",\n sha256 = \"ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/object/0.37.3/download\"],\n strip_prefix = \"object-0.37.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.object-0.37.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__once_cell-1.21.3\",\n sha256 = \"42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell/1.21.3/download\"],\n strip_prefix = \"once_cell-1.21.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.once_cell-1.21.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__oorandom-11.1.5\",\n sha256 = \"d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/oorandom/11.1.5/download\"],\n strip_prefix = \"oorandom-11.1.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.oorandom-11.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__openssl-probe-0.1.6\",\n sha256 = \"d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/openssl-probe/0.1.6/download\"],\n strip_prefix = \"openssl-probe-0.1.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.openssl-probe-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__opentelemetry-0.31.0\",\n sha256 = \"b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opentelemetry/0.31.0/download\"],\n strip_prefix = \"opentelemetry-0.31.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.opentelemetry-0.31.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__opentelemetry-http-0.31.0\",\n sha256 = \"d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opentelemetry-http/0.31.0/download\"],\n strip_prefix = \"opentelemetry-http-0.31.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.opentelemetry-http-0.31.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__opentelemetry-otlp-0.31.0\",\n sha256 = \"7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opentelemetry-otlp/0.31.0/download\"],\n strip_prefix = \"opentelemetry-otlp-0.31.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.opentelemetry-otlp-0.31.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__opentelemetry-proto-0.31.0\",\n sha256 = \"a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opentelemetry-proto/0.31.0/download\"],\n strip_prefix = \"opentelemetry-proto-0.31.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.opentelemetry-proto-0.31.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__opentelemetry_sdk-0.31.0\",\n sha256 = \"e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opentelemetry_sdk/0.31.0/download\"],\n strip_prefix = \"opentelemetry_sdk-0.31.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.opentelemetry_sdk-0.31.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ordered-float-4.6.0\",\n sha256 = \"7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ordered-float/4.6.0/download\"],\n strip_prefix = \"ordered-float-4.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ordered-float-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__oxhttp-0.3.1\",\n sha256 = \"971c03797806d47915950e62816a8f365e3ddc8800310e3200ec666934d0f7c9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/oxhttp/0.3.1/download\"],\n strip_prefix = \"oxhttp-0.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.oxhttp-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__parity-scale-codec-3.7.5\",\n sha256 = \"799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parity-scale-codec/3.7.5/download\"],\n strip_prefix = \"parity-scale-codec-3.7.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.parity-scale-codec-3.7.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__parity-scale-codec-derive-3.7.5\",\n sha256 = \"34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parity-scale-codec-derive/3.7.5/download\"],\n strip_prefix = \"parity-scale-codec-derive-3.7.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.parity-scale-codec-derive-3.7.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__parking_lot-0.12.5\",\n sha256 = \"93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot/0.12.5/download\"],\n strip_prefix = \"parking_lot-0.12.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.parking_lot-0.12.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__parking_lot_core-0.9.12\",\n sha256 = \"2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot_core/0.9.12/download\"],\n strip_prefix = \"parking_lot_core-0.9.12\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.parking_lot_core-0.9.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__path-absolutize-3.1.1\",\n sha256 = \"e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/path-absolutize/3.1.1/download\"],\n strip_prefix = \"path-absolutize-3.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.path-absolutize-3.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__path-dedot-3.1.1\",\n sha256 = \"07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/path-dedot/3.1.1/download\"],\n strip_prefix = \"path-dedot-3.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.path-dedot-3.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__percent-encoding-2.3.2\",\n sha256 = \"9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/percent-encoding/2.3.2/download\"],\n strip_prefix = \"percent-encoding-2.3.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.percent-encoding-2.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pin-project-1.1.10\",\n sha256 = \"677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project/1.1.10/download\"],\n strip_prefix = \"pin-project-1.1.10\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pin-project-1.1.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pin-project-internal-1.1.10\",\n sha256 = \"6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-internal/1.1.10/download\"],\n strip_prefix = \"pin-project-internal-1.1.10\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pin-project-internal-1.1.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pin-project-lite-0.2.16\",\n sha256 = \"3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-lite/0.2.16/download\"],\n strip_prefix = \"pin-project-lite-0.2.16\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pin-project-lite-0.2.16.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pin-utils-0.1.0\",\n sha256 = \"8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-utils/0.1.0/download\"],\n strip_prefix = \"pin-utils-0.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pin-utils-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__plain_hasher-0.2.3\",\n sha256 = \"1e19e6491bdde87c2c43d70f4c194bc8a758f2eb732df00f61e43f7362e3b4cc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plain_hasher/0.2.3/download\"],\n strip_prefix = \"plain_hasher-0.2.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.plain_hasher-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__plotters-0.3.7\",\n sha256 = \"5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters/0.3.7/download\"],\n strip_prefix = \"plotters-0.3.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.plotters-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__plotters-backend-0.3.7\",\n sha256 = \"df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-backend/0.3.7/download\"],\n strip_prefix = \"plotters-backend-0.3.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.plotters-backend-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__plotters-svg-0.3.7\",\n sha256 = \"51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-svg/0.3.7/download\"],\n strip_prefix = \"plotters-svg-0.3.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.plotters-svg-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pollster-0.4.0\",\n sha256 = \"2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pollster/0.4.0/download\"],\n strip_prefix = \"pollster-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pollster-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__portable-atomic-1.11.1\",\n sha256 = \"f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic/1.11.1/download\"],\n strip_prefix = \"portable-atomic-1.11.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.portable-atomic-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__portable-atomic-util-0.2.4\",\n sha256 = \"d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic-util/0.2.4/download\"],\n strip_prefix = \"portable-atomic-util-0.2.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.portable-atomic-util-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__potential_utf-0.1.4\",\n sha256 = \"b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/potential_utf/0.1.4/download\"],\n strip_prefix = \"potential_utf-0.1.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.potential_utf-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pprof-0.15.0\",\n sha256 = \"38a01da47675efa7673b032bf8efd8214f1917d89685e07e395ab125ea42b187\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pprof/0.15.0/download\"],\n strip_prefix = \"pprof-0.15.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pprof-0.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ppv-lite86-0.2.21\",\n sha256 = \"85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ppv-lite86/0.2.21/download\"],\n strip_prefix = \"ppv-lite86-0.2.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ppv-lite86-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__predicates-3.1.3\",\n sha256 = \"a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates/3.1.3/download\"],\n strip_prefix = \"predicates-3.1.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.predicates-3.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__predicates-core-1.0.9\",\n sha256 = \"727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-core/1.0.9/download\"],\n strip_prefix = \"predicates-core-1.0.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.predicates-core-1.0.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__predicates-tree-1.0.12\",\n sha256 = \"72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/predicates-tree/1.0.12/download\"],\n strip_prefix = \"predicates-tree-1.0.12\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.predicates-tree-1.0.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__pretty-duration-0.1.1\",\n sha256 = \"d8868e7264af614b3634ff0abbe37b178e61000611b8a75221aea40221924aba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pretty-duration/0.1.1/download\"],\n strip_prefix = \"pretty-duration-0.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.pretty-duration-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__prettyplease-0.2.37\",\n sha256 = \"479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prettyplease/0.2.37/download\"],\n strip_prefix = \"prettyplease-0.2.37\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.prettyplease-0.2.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__primitive-types-0.14.0\",\n sha256 = \"721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/primitive-types/0.14.0/download\"],\n strip_prefix = \"primitive-types-0.14.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.primitive-types-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__proc-macro-crate-3.4.0\",\n sha256 = \"219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro-crate/3.4.0/download\"],\n strip_prefix = \"proc-macro-crate-3.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.proc-macro-crate-3.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__proc-macro-error-attr2-2.0.0\",\n sha256 = \"96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro-error-attr2/2.0.0/download\"],\n strip_prefix = \"proc-macro-error-attr2-2.0.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.proc-macro-error-attr2-2.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__proc-macro-error2-2.0.1\",\n sha256 = \"11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro-error2/2.0.1/download\"],\n strip_prefix = \"proc-macro-error2-2.0.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.proc-macro-error2-2.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__proc-macro2-1.0.103\",\n sha256 = \"5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.103/download\"],\n strip_prefix = \"proc-macro2-1.0.103\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.proc-macro2-1.0.103.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__proptest-1.9.0\",\n sha256 = \"bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proptest/1.9.0/download\"],\n strip_prefix = \"proptest-1.9.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.proptest-1.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__prost-0.14.1\",\n sha256 = \"7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost/0.14.1/download\"],\n strip_prefix = \"prost-0.14.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.prost-0.14.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__prost-derive-0.14.1\",\n sha256 = \"9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost-derive/0.14.1/download\"],\n strip_prefix = \"prost-derive-0.14.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.prost-derive-0.14.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__quanta-0.12.6\",\n sha256 = \"f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quanta/0.12.6/download\"],\n strip_prefix = \"quanta-0.12.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.quanta-0.12.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__quick-xml-0.26.0\",\n sha256 = \"7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quick-xml/0.26.0/download\"],\n strip_prefix = \"quick-xml-0.26.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.quick-xml-0.26.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__quick_cache-0.6.18\",\n sha256 = \"7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quick_cache/0.6.18/download\"],\n strip_prefix = \"quick_cache-0.6.18\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.quick_cache-0.6.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__quote-1.0.42\",\n sha256 = \"a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.42/download\"],\n strip_prefix = \"quote-1.0.42\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.quote-1.0.42.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__r-efi-5.3.0\",\n sha256 = \"69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/r-efi/5.3.0/download\"],\n strip_prefix = \"r-efi-5.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.r-efi-5.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__radium-0.7.0\",\n sha256 = \"dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/radium/0.7.0/download\"],\n strip_prefix = \"radium-0.7.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.radium-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__radix_trie-0.2.1\",\n sha256 = \"c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/radix_trie/0.2.1/download\"],\n strip_prefix = \"radix_trie-0.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.radix_trie-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand-0.8.5\",\n sha256 = \"34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand/0.8.5/download\"],\n strip_prefix = \"rand-0.8.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand-0.8.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand-0.9.2\",\n sha256 = \"6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand/0.9.2/download\"],\n strip_prefix = \"rand-0.9.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_chacha-0.3.1\",\n sha256 = \"e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_chacha/0.3.1/download\"],\n strip_prefix = \"rand_chacha-0.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_chacha-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_chacha-0.9.0\",\n sha256 = \"d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_chacha/0.9.0/download\"],\n strip_prefix = \"rand_chacha-0.9.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_chacha-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_core-0.6.4\",\n sha256 = \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.6.4/download\"],\n strip_prefix = \"rand_core-0.6.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_core-0.6.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_core-0.9.3\",\n sha256 = \"99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.9.3/download\"],\n strip_prefix = \"rand_core-0.9.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_core-0.9.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_distr-0.5.1\",\n sha256 = \"6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_distr/0.5.1/download\"],\n strip_prefix = \"rand_distr-0.5.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_distr-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_xorshift-0.4.0\",\n sha256 = \"513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_xorshift/0.4.0/download\"],\n strip_prefix = \"rand_xorshift-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_xorshift-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rand_xoshiro-0.7.0\",\n sha256 = \"f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_xoshiro/0.7.0/download\"],\n strip_prefix = \"rand_xoshiro-0.7.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rand_xoshiro-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__raw-cpuid-11.6.0\",\n sha256 = \"498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/raw-cpuid/11.6.0/download\"],\n strip_prefix = \"raw-cpuid-11.6.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.raw-cpuid-11.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rayon-1.11.0\",\n sha256 = \"368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon/1.11.0/download\"],\n strip_prefix = \"rayon-1.11.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rayon-1.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rayon-core-1.13.0\",\n sha256 = \"22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon-core/1.13.0/download\"],\n strip_prefix = \"rayon-core-1.13.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rayon-core-1.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__redox_syscall-0.5.18\",\n sha256 = \"ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/redox_syscall/0.5.18/download\"],\n strip_prefix = \"redox_syscall-0.5.18\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.redox_syscall-0.5.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__regex-1.12.2\",\n sha256 = \"843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex/1.12.2/download\"],\n strip_prefix = \"regex-1.12.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.regex-1.12.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__regex-automata-0.4.13\",\n sha256 = \"5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-automata/0.4.13/download\"],\n strip_prefix = \"regex-automata-0.4.13\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.regex-automata-0.4.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__regex-syntax-0.8.8\",\n sha256 = \"7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-syntax/0.8.8/download\"],\n strip_prefix = \"regex-syntax-0.8.8\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.regex-syntax-0.8.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__reqwest-0.12.24\",\n sha256 = \"9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/reqwest/0.12.24/download\"],\n strip_prefix = \"reqwest-0.12.24\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.reqwest-0.12.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rgb-0.8.52\",\n sha256 = \"0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rgb/0.8.52/download\"],\n strip_prefix = \"rgb-0.8.52\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rgb-0.8.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ring-0.17.14\",\n sha256 = \"a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ring/0.17.14/download\"],\n strip_prefix = \"ring-0.17.14\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ring-0.17.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rlp-0.6.1\",\n sha256 = \"fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rlp/0.6.1/download\"],\n strip_prefix = \"rlp-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rlp-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rtrb-0.3.2\",\n sha256 = \"ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rtrb/0.3.2/download\"],\n strip_prefix = \"rtrb-0.3.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rtrb-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustc-demangle-0.1.26\",\n sha256 = \"56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-demangle/0.1.26/download\"],\n strip_prefix = \"rustc-demangle-0.1.26\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustc-demangle-0.1.26.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustc-hash-2.1.1\",\n sha256 = \"357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-hash/2.1.1/download\"],\n strip_prefix = \"rustc-hash-2.1.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustc-hash-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustc-hex-2.1.0\",\n sha256 = \"3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-hex/2.1.0/download\"],\n strip_prefix = \"rustc-hex-2.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustc-hex-2.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustix-1.1.2\",\n sha256 = \"cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.2/download\"],\n strip_prefix = \"rustix-1.1.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustix-1.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustls-0.23.35\",\n sha256 = \"533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls/0.23.35/download\"],\n strip_prefix = \"rustls-0.23.35\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustls-0.23.35.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustls-native-certs-0.8.2\",\n sha256 = \"9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-native-certs/0.8.2/download\"],\n strip_prefix = \"rustls-native-certs-0.8.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustls-native-certs-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustls-pki-types-1.13.0\",\n sha256 = \"94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-pki-types/1.13.0/download\"],\n strip_prefix = \"rustls-pki-types-1.13.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustls-pki-types-1.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustls-webpki-0.103.8\",\n sha256 = \"2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-webpki/0.103.8/download\"],\n strip_prefix = \"rustls-webpki-0.103.8\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustls-webpki-0.103.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__rustversion-1.0.22\",\n sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n strip_prefix = \"rustversion-1.0.22\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.rustversion-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__ryu-1.0.20\",\n sha256 = \"28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ryu/1.0.20/download\"],\n strip_prefix = \"ryu-1.0.20\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.ryu-1.0.20.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__same-file-1.0.6\",\n sha256 = \"93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/same-file/1.0.6/download\"],\n strip_prefix = \"same-file-1.0.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.same-file-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__scc-2.4.0\",\n sha256 = \"46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scc/2.4.0/download\"],\n strip_prefix = \"scc-2.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.scc-2.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__schannel-0.1.28\",\n sha256 = \"891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/schannel/0.1.28/download\"],\n strip_prefix = \"schannel-0.1.28\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.schannel-0.1.28.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__scopeguard-1.2.0\",\n sha256 = \"94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scopeguard/1.2.0/download\"],\n strip_prefix = \"scopeguard-1.2.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.scopeguard-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__sdd-3.0.10\",\n sha256 = \"490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sdd/3.0.10/download\"],\n strip_prefix = \"sdd-3.0.10\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.sdd-3.0.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__security-framework-3.5.1\",\n sha256 = \"b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/security-framework/3.5.1/download\"],\n strip_prefix = \"security-framework-3.5.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.security-framework-3.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__security-framework-sys-2.15.0\",\n sha256 = \"cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/security-framework-sys/2.15.0/download\"],\n strip_prefix = \"security-framework-sys-2.15.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.security-framework-sys-2.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__self_cell-1.2.1\",\n sha256 = \"16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/self_cell/1.2.1/download\"],\n strip_prefix = \"self_cell-1.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.self_cell-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__semver-1.0.27\",\n sha256 = \"d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/1.0.27/download\"],\n strip_prefix = \"semver-1.0.27\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.semver-1.0.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde_json-1.0.145\",\n sha256 = \"402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json/1.0.145/download\"],\n strip_prefix = \"serde_json-1.0.145\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde_json-1.0.145.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde_spanned-1.0.3\",\n sha256 = \"e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_spanned/1.0.3/download\"],\n strip_prefix = \"serde_spanned-1.0.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde_spanned-1.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serde_urlencoded-0.7.1\",\n sha256 = \"d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_urlencoded/0.7.1/download\"],\n strip_prefix = \"serde_urlencoded-0.7.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serde_urlencoded-0.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serial_test-3.2.0\",\n sha256 = \"1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serial_test/3.2.0/download\"],\n strip_prefix = \"serial_test-3.2.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serial_test-3.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__serial_test_derive-3.2.0\",\n sha256 = \"5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serial_test_derive/3.2.0/download\"],\n strip_prefix = \"serial_test_derive-3.2.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.serial_test_derive-3.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__sha2-0.10.9\",\n sha256 = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha2/0.10.9/download\"],\n strip_prefix = \"sha2-0.10.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.sha2-0.10.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__sha3-0.10.8\",\n sha256 = \"75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha3/0.10.8/download\"],\n strip_prefix = \"sha3-0.10.8\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.sha3-0.10.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__shlex-1.3.0\",\n sha256 = \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/shlex/1.3.0/download\"],\n strip_prefix = \"shlex-1.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.shlex-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__sketches-ddsketch-0.3.0\",\n sha256 = \"c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sketches-ddsketch/0.3.0/download\"],\n strip_prefix = \"sketches-ddsketch-0.3.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.sketches-ddsketch-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__slab-0.4.11\",\n sha256 = \"7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/slab/0.4.11/download\"],\n strip_prefix = \"slab-0.4.11\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.slab-0.4.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__small_ctor-0.1.2\",\n sha256 = \"88414a5ca1f85d82cc34471e975f0f74f6aa54c40f062efa42c0080e7f763f81\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/small_ctor/0.1.2/download\"],\n strip_prefix = \"small_ctor-0.1.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.small_ctor-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__smallvec-1.15.1\",\n sha256 = \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smallvec/1.15.1/download\"],\n strip_prefix = \"smallvec-1.15.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.smallvec-1.15.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__socket2-0.6.1\",\n sha256 = \"17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/socket2/0.6.1/download\"],\n strip_prefix = \"socket2-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.socket2-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__spin-0.10.0\",\n sha256 = \"d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spin/0.10.0/download\"],\n strip_prefix = \"spin-0.10.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.spin-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__stable_deref_trait-1.2.1\",\n sha256 = \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/stable_deref_trait/1.2.1/download\"],\n strip_prefix = \"stable_deref_trait-1.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.stable_deref_trait-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__static_assertions-1.1.0\",\n sha256 = \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/static_assertions/1.1.0/download\"],\n strip_prefix = \"static_assertions-1.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.static_assertions-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__std-semaphore-0.1.0\",\n sha256 = \"33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/std-semaphore/0.1.0/download\"],\n strip_prefix = \"std-semaphore-0.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.std-semaphore-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__str_stack-0.1.0\",\n sha256 = \"9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/str_stack/0.1.0/download\"],\n strip_prefix = \"str_stack-0.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.str_stack-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__subtle-2.6.1\",\n sha256 = \"13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/subtle/2.6.1/download\"],\n strip_prefix = \"subtle-2.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.subtle-2.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__symbolic-common-12.17.0\",\n sha256 = \"b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/symbolic-common/12.17.0/download\"],\n strip_prefix = \"symbolic-common-12.17.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.symbolic-common-12.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__symbolic-demangle-12.17.0\",\n sha256 = \"1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/symbolic-demangle/12.17.0/download\"],\n strip_prefix = \"symbolic-demangle-12.17.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.symbolic-demangle-12.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__syn-2.0.110\",\n sha256 = \"a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.110/download\"],\n strip_prefix = \"syn-2.0.110\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.syn-2.0.110.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__sync_wrapper-1.0.2\",\n sha256 = \"0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sync_wrapper/1.0.2/download\"],\n strip_prefix = \"sync_wrapper-1.0.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.sync_wrapper-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__synstructure-0.13.2\",\n sha256 = \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/synstructure/0.13.2/download\"],\n strip_prefix = \"synstructure-0.13.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.synstructure-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tap-1.0.1\",\n sha256 = \"55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tap/1.0.1/download\"],\n strip_prefix = \"tap-1.0.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tap-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__target-triple-1.0.0\",\n sha256 = \"591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/target-triple/1.0.0/download\"],\n strip_prefix = \"target-triple-1.0.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.target-triple-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tempfile-3.23.0\",\n sha256 = \"2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tempfile/3.23.0/download\"],\n strip_prefix = \"tempfile-3.23.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tempfile-3.23.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__termcolor-1.4.1\",\n sha256 = \"06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/termcolor/1.4.1/download\"],\n strip_prefix = \"termcolor-1.4.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.termcolor-1.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__termtree-0.5.1\",\n sha256 = \"8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/termtree/0.5.1/download\"],\n strip_prefix = \"termtree-0.5.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.termtree-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__test-case-3.3.1\",\n sha256 = \"eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/test-case/3.3.1/download\"],\n strip_prefix = \"test-case-3.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.test-case-3.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__test-case-core-3.3.1\",\n sha256 = \"adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/test-case-core/3.3.1/download\"],\n strip_prefix = \"test-case-core-3.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.test-case-core-3.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__test-case-macros-3.3.1\",\n sha256 = \"5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/test-case-macros/3.3.1/download\"],\n strip_prefix = \"test-case-macros-3.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.test-case-macros-3.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__thiserror-2.0.17\",\n sha256 = \"f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.17/download\"],\n strip_prefix = \"thiserror-2.0.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.thiserror-2.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__thiserror-impl-2.0.17\",\n sha256 = \"3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.17/download\"],\n strip_prefix = \"thiserror-impl-2.0.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.thiserror-impl-2.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tikv-jemalloc-sys-0.6.1-5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7\",\n sha256 = \"cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tikv-jemalloc-sys/0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/download\"],\n strip_prefix = \"tikv-jemalloc-sys-0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tikv-jemalloc-sys-0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tikv-jemallocator-0.6.1\",\n sha256 = \"0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tikv-jemallocator/0.6.1/download\"],\n strip_prefix = \"tikv-jemallocator-0.6.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tikv-jemallocator-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tiny-keccak-2.0.2\",\n sha256 = \"2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tiny-keccak/2.0.2/download\"],\n strip_prefix = \"tiny-keccak-2.0.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tiny-keccak-2.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tinystr-0.8.2\",\n sha256 = \"42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinystr/0.8.2/download\"],\n strip_prefix = \"tinystr-0.8.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tinystr-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tinytemplate-1.2.1\",\n sha256 = \"be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinytemplate/1.2.1/download\"],\n strip_prefix = \"tinytemplate-1.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tinytemplate-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tokio-1.48.0\",\n sha256 = \"ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio/1.48.0/download\"],\n strip_prefix = \"tokio-1.48.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tokio-1.48.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tokio-rustls-0.26.4\",\n sha256 = \"1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-rustls/0.26.4/download\"],\n strip_prefix = \"tokio-rustls-0.26.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tokio-rustls-0.26.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tokio-stream-0.1.17\",\n sha256 = \"eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-stream/0.1.17/download\"],\n strip_prefix = \"tokio-stream-0.1.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tokio-stream-0.1.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tokio-util-0.7.17\",\n sha256 = \"2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-util/0.7.17/download\"],\n strip_prefix = \"tokio-util-0.7.17\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tokio-util-0.7.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__toml-0.9.8\",\n sha256 = \"f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml/0.9.8/download\"],\n strip_prefix = \"toml-0.9.8\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.toml-0.9.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__toml_datetime-0.7.3\",\n sha256 = \"f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_datetime/0.7.3/download\"],\n strip_prefix = \"toml_datetime-0.7.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.toml_datetime-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__toml_edit-0.23.7\",\n sha256 = \"6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_edit/0.23.7/download\"],\n strip_prefix = \"toml_edit-0.23.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.toml_edit-0.23.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__toml_parser-1.0.4\",\n sha256 = \"c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_parser/1.0.4/download\"],\n strip_prefix = \"toml_parser-1.0.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.toml_parser-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__toml_writer-1.0.4\",\n sha256 = \"df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_writer/1.0.4/download\"],\n strip_prefix = \"toml_writer-1.0.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.toml_writer-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tonic-0.14.2\",\n sha256 = \"eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tonic/0.14.2/download\"],\n strip_prefix = \"tonic-0.14.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tonic-0.14.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tonic-prost-0.14.2\",\n sha256 = \"66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tonic-prost/0.14.2/download\"],\n strip_prefix = \"tonic-prost-0.14.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tonic-prost-0.14.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tower-0.5.2\",\n sha256 = \"d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower/0.5.2/download\"],\n strip_prefix = \"tower-0.5.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tower-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tower-http-0.6.6\",\n sha256 = \"adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-http/0.6.6/download\"],\n strip_prefix = \"tower-http-0.6.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tower-http-0.6.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tower-layer-0.3.3\",\n sha256 = \"121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-layer/0.3.3/download\"],\n strip_prefix = \"tower-layer-0.3.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tower-layer-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tower-service-0.3.3\",\n sha256 = \"8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-service/0.3.3/download\"],\n strip_prefix = \"tower-service-0.3.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tower-service-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tracing-0.1.41\",\n sha256 = \"784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing/0.1.41/download\"],\n strip_prefix = \"tracing-0.1.41\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tracing-0.1.41.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tracing-attributes-0.1.30\",\n sha256 = \"81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing-attributes/0.1.30/download\"],\n strip_prefix = \"tracing-attributes-0.1.30\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tracing-attributes-0.1.30.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__tracing-core-0.1.34\",\n sha256 = \"b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing-core/0.1.34/download\"],\n strip_prefix = \"tracing-core-0.1.34\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.tracing-core-0.1.34.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__trie-standardmap-0.16.0\",\n sha256 = \"684aafb332fae6f83d7fe10b3fbfdbe39a1b3234c4e2a618f030815838519516\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/trie-standardmap/0.16.0/download\"],\n strip_prefix = \"trie-standardmap-0.16.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.trie-standardmap-0.16.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__triomphe-0.1.15\",\n sha256 = \"dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/triomphe/0.1.15/download\"],\n strip_prefix = \"triomphe-0.1.15\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.triomphe-0.1.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__try-lock-0.2.5\",\n sha256 = \"e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/try-lock/0.2.5/download\"],\n strip_prefix = \"try-lock-0.2.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.try-lock-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__trybuild-1.0.114\",\n sha256 = \"3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/trybuild/1.0.114/download\"],\n strip_prefix = \"trybuild-1.0.114\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.trybuild-1.0.114.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__typed-builder-0.23.1\",\n sha256 = \"1cce8e9c8115897e896894868ad4ae6851eff0fb7fd33fa95610e0fa93211886\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typed-builder/0.23.1/download\"],\n strip_prefix = \"typed-builder-0.23.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.typed-builder-0.23.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__typed-builder-macro-0.23.1\",\n sha256 = \"921d52b8b19b1a455f54fa76a925a1cf49c0d6a7c6b232fc58523400d1f91560\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typed-builder-macro/0.23.1/download\"],\n strip_prefix = \"typed-builder-macro-0.23.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.typed-builder-macro-0.23.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__typenum-1.19.0\",\n sha256 = \"562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typenum/1.19.0/download\"],\n strip_prefix = \"typenum-1.19.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.typenum-1.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__uint-0.10.0\",\n sha256 = \"909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/uint/0.10.0/download\"],\n strip_prefix = \"uint-0.10.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.uint-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__unarray-0.1.4\",\n sha256 = \"eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unarray/0.1.4/download\"],\n strip_prefix = \"unarray-0.1.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.unarray-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__unicode-ident-1.0.22\",\n sha256 = \"9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.22/download\"],\n strip_prefix = \"unicode-ident-1.0.22\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.unicode-ident-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__unicode-width-0.2.2\",\n sha256 = \"b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-width/0.2.2/download\"],\n strip_prefix = \"unicode-width-0.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.unicode-width-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__unicode-xid-0.2.6\",\n sha256 = \"ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-xid/0.2.6/download\"],\n strip_prefix = \"unicode-xid-0.2.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.unicode-xid-0.2.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__unit-prefix-0.5.2\",\n sha256 = \"81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unit-prefix/0.5.2/download\"],\n strip_prefix = \"unit-prefix-0.5.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.unit-prefix-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__untrusted-0.9.0\",\n sha256 = \"8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/untrusted/0.9.0/download\"],\n strip_prefix = \"untrusted-0.9.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.untrusted-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__url-2.5.7\",\n sha256 = \"08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/url/2.5.7/download\"],\n strip_prefix = \"url-2.5.7\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.url-2.5.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__utf8_iter-1.0.4\",\n sha256 = \"b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8_iter/1.0.4/download\"],\n strip_prefix = \"utf8_iter-1.0.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.utf8_iter-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__uuid-1.18.1\",\n sha256 = \"2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/uuid/1.18.1/download\"],\n strip_prefix = \"uuid-1.18.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.uuid-1.18.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__value-log-1.9.0\",\n sha256 = \"62fc7c4ce161f049607ecea654dca3f2d727da5371ae85e2e4f14ce2b98ed67c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/value-log/1.9.0/download\"],\n strip_prefix = \"value-log-1.9.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.value-log-1.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__varint-rs-2.2.0\",\n sha256 = \"8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/varint-rs/2.2.0/download\"],\n strip_prefix = \"varint-rs-2.2.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.varint-rs-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__version_check-0.9.5\",\n sha256 = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/version_check/0.9.5/download\"],\n strip_prefix = \"version_check-0.9.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.version_check-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wait-timeout-0.2.1\",\n sha256 = \"09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wait-timeout/0.2.1/download\"],\n strip_prefix = \"wait-timeout-0.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wait-timeout-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__walkdir-2.5.0\",\n sha256 = \"29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/walkdir/2.5.0/download\"],\n strip_prefix = \"walkdir-2.5.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.walkdir-2.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__want-0.3.1\",\n sha256 = \"bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/want/0.3.1/download\"],\n strip_prefix = \"want-0.3.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.want-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasip2-1.0.1-wasi-0.2.4\",\n sha256 = \"0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip2/1.0.1+wasi-0.2.4/download\"],\n strip_prefix = \"wasip2-1.0.1+wasi-0.2.4\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasip2-1.0.1+wasi-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasix-0.12.21\",\n sha256 = \"c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasix/0.12.21/download\"],\n strip_prefix = \"wasix-0.12.21\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasix-0.12.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasm-bindgen-0.2.105\",\n sha256 = \"da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-0.2.105\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasm-bindgen-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasm-bindgen-futures-0.4.55\",\n sha256 = \"551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-futures/0.4.55/download\"],\n strip_prefix = \"wasm-bindgen-futures-0.4.55\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasm-bindgen-futures-0.4.55.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasm-bindgen-macro-0.2.105\",\n sha256 = \"04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-0.2.105\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasm-bindgen-macro-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasm-bindgen-macro-support-0.2.105\",\n sha256 = \"420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-macro-support-0.2.105\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasm-bindgen-macro-support-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wasm-bindgen-shared-0.2.105\",\n sha256 = \"76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-shared/0.2.105/download\"],\n strip_prefix = \"wasm-bindgen-shared-0.2.105\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wasm-bindgen-shared-0.2.105.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__weak-table-0.3.2\",\n sha256 = \"323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/weak-table/0.3.2/download\"],\n strip_prefix = \"weak-table-0.3.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.weak-table-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__web-sys-0.3.82\",\n sha256 = \"3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-sys/0.3.82/download\"],\n strip_prefix = \"web-sys-0.3.82\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.web-sys-0.3.82.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__web-time-1.1.0\",\n sha256 = \"5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-time/1.1.0/download\"],\n strip_prefix = \"web-time-1.1.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.web-time-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__winapi-0.3.9\",\n sha256 = \"5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi/0.3.9/download\"],\n strip_prefix = \"winapi-0.3.9\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.winapi-0.3.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__winapi-i686-pc-windows-gnu-0.4.0\",\n sha256 = \"ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-i686-pc-windows-gnu/0.4.0/download\"],\n strip_prefix = \"winapi-i686-pc-windows-gnu-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.winapi-i686-pc-windows-gnu-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__winapi-util-0.1.11\",\n sha256 = \"c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-util/0.1.11/download\"],\n strip_prefix = \"winapi-util-0.1.11\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.winapi-util-0.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__winapi-x86_64-pc-windows-gnu-0.4.0\",\n sha256 = \"712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-x86_64-pc-windows-gnu/0.4.0/download\"],\n strip_prefix = \"winapi-x86_64-pc-windows-gnu-0.4.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.winapi-x86_64-pc-windows-gnu-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-core-0.62.2\",\n sha256 = \"b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-core/0.62.2/download\"],\n strip_prefix = \"windows-core-0.62.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-core-0.62.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-implement-0.60.2\",\n sha256 = \"053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-implement/0.60.2/download\"],\n strip_prefix = \"windows-implement-0.60.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-implement-0.60.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-interface-0.59.3\",\n sha256 = \"3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-interface/0.59.3/download\"],\n strip_prefix = \"windows-interface-0.59.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-interface-0.59.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-result-0.4.1\",\n sha256 = \"7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-result/0.4.1/download\"],\n strip_prefix = \"windows-result-0.4.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-result-0.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-strings-0.5.1\",\n sha256 = \"7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-strings/0.5.1/download\"],\n strip_prefix = \"windows-strings-0.5.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-strings-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-sys-0.52.0\",\n sha256 = \"282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.52.0/download\"],\n strip_prefix = \"windows-sys-0.52.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-sys-0.52.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-sys-0.60.2\",\n sha256 = \"f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.60.2/download\"],\n strip_prefix = \"windows-sys-0.60.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-sys-0.60.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-targets-0.52.6\",\n sha256 = \"9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.52.6/download\"],\n strip_prefix = \"windows-targets-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-targets-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows-targets-0.53.5\",\n sha256 = \"4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.53.5/download\"],\n strip_prefix = \"windows-targets-0.53.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows-targets-0.53.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_aarch64_gnullvm-0.52.6\",\n sha256 = \"32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_aarch64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_aarch64_gnullvm-0.53.1\",\n sha256 = \"a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_aarch64_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_aarch64_msvc-0.52.6\",\n sha256 = \"09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_aarch64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_aarch64_msvc-0.53.1\",\n sha256 = \"b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.53.1/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_aarch64_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_gnu-0.52.6\",\n sha256 = \"8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnu-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_gnu-0.53.1\",\n sha256 = \"960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.53.1/download\"],\n strip_prefix = \"windows_i686_gnu-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_gnu-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_gnullvm-0.52.6\",\n sha256 = \"0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_gnullvm-0.53.1\",\n sha256 = \"fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_msvc-0.52.6\",\n sha256 = \"240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.52.6/download\"],\n strip_prefix = \"windows_i686_msvc-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_i686_msvc-0.53.1\",\n sha256 = \"1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.53.1/download\"],\n strip_prefix = \"windows_i686_msvc-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_i686_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_gnu-0.52.6\",\n sha256 = \"147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_gnu-0.53.1\",\n sha256 = \"9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_gnu-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_gnullvm-0.52.6\",\n sha256 = \"24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_gnullvm-0.53.1\",\n sha256 = \"0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_msvc-0.52.6\",\n sha256 = \"589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.52.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__windows_x86_64_msvc-0.53.1\",\n sha256 = \"d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.53.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.windows_x86_64_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__winnow-0.7.13\",\n sha256 = \"21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/0.7.13/download\"],\n strip_prefix = \"winnow-0.7.13\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.winnow-0.7.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wit-bindgen-0.46.0\",\n sha256 = \"f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen/0.46.0/download\"],\n strip_prefix = \"wit-bindgen-0.46.0\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wit-bindgen-0.46.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__writeable-0.6.2\",\n sha256 = \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/writeable/0.6.2/download\"],\n strip_prefix = \"writeable-0.6.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.writeable-0.6.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__wyz-0.5.1\",\n sha256 = \"05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wyz/0.5.1/download\"],\n strip_prefix = \"wyz-0.5.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.wyz-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__xxhash-rust-0.8.15\",\n sha256 = \"fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/xxhash-rust/0.8.15/download\"],\n strip_prefix = \"xxhash-rust-0.8.15\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.xxhash-rust-0.8.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__yoke-0.8.1\",\n sha256 = \"72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yoke/0.8.1/download\"],\n strip_prefix = \"yoke-0.8.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.yoke-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__yoke-derive-0.8.1\",\n sha256 = \"b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yoke-derive/0.8.1/download\"],\n strip_prefix = \"yoke-derive-0.8.1\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.yoke-derive-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerocopy-0.8.27\",\n sha256 = \"0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.27/download\"],\n strip_prefix = \"zerocopy-0.8.27\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerocopy-0.8.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerocopy-derive-0.8.27\",\n sha256 = \"88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.27/download\"],\n strip_prefix = \"zerocopy-derive-0.8.27\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerocopy-derive-0.8.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerofrom-0.1.6\",\n sha256 = \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerofrom/0.1.6/download\"],\n strip_prefix = \"zerofrom-0.1.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerofrom-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerofrom-derive-0.1.6\",\n sha256 = \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerofrom-derive/0.1.6/download\"],\n strip_prefix = \"zerofrom-derive-0.1.6\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerofrom-derive-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zeroize-1.8.2\",\n sha256 = \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize/1.8.2/download\"],\n strip_prefix = \"zeroize-1.8.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zeroize-1.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerotrie-0.2.3\",\n sha256 = \"2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerotrie/0.2.3/download\"],\n strip_prefix = \"zerotrie-0.2.3\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerotrie-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerovec-0.11.5\",\n sha256 = \"6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerovec/0.11.5/download\"],\n strip_prefix = \"zerovec-0.11.5\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerovec-0.11.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"firewood_crates__zerovec-derive-0.11.2\",\n sha256 = \"eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerovec-derive/0.11.2/download\"],\n strip_prefix = \"zerovec-derive-0.11.2\",\n build_file = Label(\"@firewood_crates//firewood_crates:BUILD.zerovec-derive-0.11.2.bazel\"),\n )\n\n return [\n struct(repo=\"firewood_crates__aquamarine-0.6.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__askama-0.14.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bitfield-0.19.4\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bitflags-2.10.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bumpalo-3.19.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bytemuck-1.24.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bytemuck_derive-1.10.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__bytes-1.11.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__cbindgen-0.29.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__chrono-0.4.42\", is_dev_dep = False),\n struct(repo=\"firewood_crates__clap-4.5.52\", is_dev_dep = False),\n struct(repo=\"firewood_crates__coarsetime-0.1.36\", is_dev_dep = False),\n struct(repo=\"firewood_crates__csv-1.4.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__derive-where-1.6.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__enum-as-inner-0.6.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__env_logger-0.11.8\", is_dev_dep = False),\n struct(repo=\"firewood_crates__fastrace-0.7.14\", is_dev_dep = False),\n struct(repo=\"firewood_crates__fastrace-opentelemetry-0.14.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__fjall-2.11.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__hash-db-0.16.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__hex-0.4.3\", is_dev_dep = False),\n struct(repo=\"firewood_crates__indicatif-0.18.3\", is_dev_dep = False),\n struct(repo=\"firewood_crates__integer-encoding-4.1.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__io-uring-0.7.11\", is_dev_dep = False),\n struct(repo=\"firewood_crates__log-0.4.28\", is_dev_dep = False),\n struct(repo=\"firewood_crates__lru-0.16.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__metrics-0.24.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__metrics-exporter-prometheus-0.17.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__metrics-util-0.20.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__nonzero_ext-0.3.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__num-format-0.4.4\", is_dev_dep = False),\n struct(repo=\"firewood_crates__opentelemetry-0.31.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__opentelemetry-otlp-0.31.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__opentelemetry-proto-0.31.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__opentelemetry_sdk-0.31.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__oxhttp-0.3.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__parking_lot-0.12.5\", is_dev_dep = False),\n struct(repo=\"firewood_crates__pretty-duration-0.1.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__proc-macro2-1.0.103\", is_dev_dep = False),\n struct(repo=\"firewood_crates__quote-1.0.42\", is_dev_dep = False),\n struct(repo=\"firewood_crates__rand-0.9.2\", is_dev_dep = False),\n struct(repo=\"firewood_crates__rand_distr-0.5.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__rayon-1.11.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__rlp-0.6.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__semver-1.0.27\", is_dev_dep = False),\n struct(repo=\"firewood_crates__sha2-0.10.9\", is_dev_dep = False),\n struct(repo=\"firewood_crates__smallvec-1.15.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__syn-2.0.110\", is_dev_dep = False),\n struct(repo=\"firewood_crates__thiserror-2.0.17\", is_dev_dep = False),\n struct(repo=\"firewood_crates__tikv-jemallocator-0.6.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__tokio-1.48.0\", is_dev_dep = False),\n struct(repo=\"firewood_crates__triomphe-0.1.15\", is_dev_dep = False),\n struct(repo=\"firewood_crates__typed-builder-0.23.1\", is_dev_dep = False),\n struct(repo=\"firewood_crates__weak-table-0.3.2\", is_dev_dep = False),\n struct(repo = \"firewood_crates__anyhow-1.0.100\", is_dev_dep = True),\n struct(repo = \"firewood_crates__assert_cmd-2.1.1\", is_dev_dep = True),\n struct(repo = \"firewood_crates__criterion-0.7.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__ctor-0.6.1\", is_dev_dep = True),\n struct(repo = \"firewood_crates__ethereum-types-0.16.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__hex-literal-1.1.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__keccak-hasher-0.16.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__plain_hasher-0.2.3\", is_dev_dep = True),\n struct(repo = \"firewood_crates__pprof-0.15.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__predicates-3.1.3\", is_dev_dep = True),\n struct(repo = \"firewood_crates__serial_test-3.2.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__sha3-0.10.8\", is_dev_dep = True),\n struct(repo = \"firewood_crates__tempfile-3.23.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__test-case-3.3.1\", is_dev_dep = True),\n struct(repo = \"firewood_crates__tiny-keccak-2.0.2\", is_dev_dep = True),\n struct(repo = \"firewood_crates__trie-standardmap-0.16.0\", is_dev_dep = True),\n struct(repo = \"firewood_crates__trybuild-1.0.114\", is_dev_dep = True),\n ]\n" + } + } + }, + "firewood_crates__addr2line-0.25.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/addr2line/0.25.1/download" + ], + "strip_prefix": "addr2line-0.25.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"addr2line\",\n deps = [\n \"@firewood_crates__gimli-0.32.3//:gimli\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=addr2line\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.25.1\",\n)\n" + } + }, + "firewood_crates__adler2-2.0.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/adler2/2.0.1/download" + ], + "strip_prefix": "adler2-2.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"adler2\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=adler2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.1\",\n)\n" + } + }, + "firewood_crates__ahash-0.8.12": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ahash/0.8.12/download" + ], + "strip_prefix": "ahash-0.8.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ahash\",\n deps = [\n \"@firewood_crates__ahash-0.8.12//:build_script_build\",\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__getrandom-0.3.4//:getrandom\",\n \"@firewood_crates__zerocopy-0.8.27//:zerocopy\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"getrandom\",\n \"runtime-rng\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ahash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.12\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"getrandom\",\n \"runtime-rng\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__version_check-0.9.5//:version_check\",\n ],\n edition = \"2018\",\n pkg_name = \"ahash\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ahash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.12\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__aho-corasick-1.1.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aho-corasick/1.1.4/download" + ], + "strip_prefix": "aho-corasick-1.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aho_corasick\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"perf-literal\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aho-corasick\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n" + } + }, + "firewood_crates__aligned-vec-0.6.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aligned-vec/0.6.4/download" + ], + "strip_prefix": "aligned-vec-0.6.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aligned_vec\",\n deps = [\n \"@firewood_crates__equator-0.4.2//:equator\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aligned-vec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.4\",\n)\n" + } + }, + "firewood_crates__allocator-api2-0.2.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/allocator-api2/0.2.21/download" + ], + "strip_prefix": "allocator-api2-0.2.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"allocator_api2\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=allocator-api2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.21\",\n)\n" + } + }, + "firewood_crates__android_system_properties-0.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/android_system_properties/0.1.5/download" + ], + "strip_prefix": "android_system_properties-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"android_system_properties\",\n deps = [\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=android_system_properties\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "firewood_crates__anes-0.1.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anes/0.1.6/download" + ], + "strip_prefix": "anes-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anes\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "firewood_crates__anstream-0.6.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstream/0.6.21/download" + ], + "strip_prefix": "anstream-0.6.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstream\",\n deps = [\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n \"@firewood_crates__anstyle-parse-0.2.7//:anstyle_parse\",\n \"@firewood_crates__anstyle-query-1.1.5//:anstyle_query\",\n \"@firewood_crates__colorchoice-1.0.4//:colorchoice\",\n \"@firewood_crates__is_terminal_polyfill-1.70.2//:is_terminal_polyfill\",\n \"@firewood_crates__utf8parse-0.2.2//:utf8parse\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__anstyle-wincon-3.0.11//:anstyle_wincon\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auto\",\n \"default\",\n \"wincon\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.21\",\n)\n" + } + }, + "firewood_crates__anstyle-1.0.13": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle/1.0.13/download" + ], + "strip_prefix": "anstyle-1.0.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.13\",\n)\n" + } + }, + "firewood_crates__anstyle-parse-0.2.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-parse/0.2.7/download" + ], + "strip_prefix": "anstyle-parse-0.2.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_parse\",\n deps = [\n \"@firewood_crates__utf8parse-0.2.2//:utf8parse\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"utf8\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-parse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.7\",\n)\n" + } + }, + "firewood_crates__anstyle-query-1.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-query/1.1.5/download" + ], + "strip_prefix": "anstyle-query-1.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_query\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-query\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.5\",\n)\n" + } + }, + "firewood_crates__anstyle-wincon-3.0.11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-wincon/3.0.11/download" + ], + "strip_prefix": "anstyle-wincon-3.0.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_wincon\",\n deps = [\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__once_cell_polyfill-1.70.2//:once_cell_polyfill\", # cfg(windows)\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-wincon\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.0.11\",\n)\n" + } + }, + "firewood_crates__anyhow-1.0.100": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anyhow/1.0.100/download" + ], + "strip_prefix": "anyhow-1.0.100", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anyhow\",\n deps = [\n \"@firewood_crates__anyhow-1.0.100//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anyhow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.100\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"anyhow\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anyhow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.100\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__aquamarine-0.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aquamarine/0.6.0/download" + ], + "strip_prefix": "aquamarine-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"aquamarine\",\n deps = [\n \"@firewood_crates__include_dir-0.7.4//:include_dir\",\n \"@firewood_crates__itertools-0.10.5//:itertools\",\n \"@firewood_crates__proc-macro-error2-2.0.1//:proc_macro_error2\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aquamarine\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n" + } + }, + "firewood_crates__arrayvec-0.7.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/arrayvec/0.7.6/download" + ], + "strip_prefix": "arrayvec-0.7.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"arrayvec\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=arrayvec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.6\",\n)\n" + } + }, + "firewood_crates__askama-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/askama/0.14.0/download" + ], + "strip_prefix": "askama-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"askama\",\n deps = [\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__askama_derive-0.14.0//:askama_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"config\",\n \"default\",\n \"derive\",\n \"std\",\n \"urlencode\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=askama\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__askama_derive-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/askama_derive/0.14.0/download" + ], + "strip_prefix": "askama_derive-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"askama_derive\",\n deps = [\n \"@firewood_crates__askama_parser-0.14.0//:askama_parser\",\n \"@firewood_crates__basic-toml-0.1.10//:basic_toml\",\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__rustc-hash-2.1.1//:rustc_hash\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__serde_derive-1.0.228//:serde_derive\",\n ],\n aliases = {\n \"@firewood_crates__askama_parser-0.14.0//:askama_parser\": \"parser\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"config\",\n \"default\",\n \"derive\",\n \"std\",\n \"urlencode\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=askama_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__askama_parser-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/askama_parser/0.14.0/download" + ], + "strip_prefix": "askama_parser-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"askama_parser\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__winnow-0.7.13//:winnow\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"config\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=askama_parser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__assert_cmd-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/assert_cmd/2.1.1/download" + ], + "strip_prefix": "assert_cmd-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"assert_cmd\",\n deps = [\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n \"@firewood_crates__assert_cmd-2.1.1//:build_script_build\",\n \"@firewood_crates__bstr-1.12.1//:bstr\",\n \"@firewood_crates__predicates-3.1.3//:predicates\",\n \"@firewood_crates__predicates-core-1.0.9//:predicates_core\",\n \"@firewood_crates__predicates-tree-1.0.12//:predicates_tree\",\n \"@firewood_crates__wait-timeout-0.2.1//:wait_timeout\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=assert_cmd\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"assert_cmd\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=assert_cmd\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__async-trait-0.1.89": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/async-trait/0.1.89/download" + ], + "strip_prefix": "async-trait-0.1.89", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"async_trait\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=async-trait\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.89\",\n)\n" + } + }, + "firewood_crates__atomic-waker-1.1.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/atomic-waker/1.1.2/download" + ], + "strip_prefix": "atomic-waker-1.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"atomic_waker\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=atomic-waker\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.2\",\n)\n" + } + }, + "firewood_crates__autocfg-1.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/autocfg/1.5.0/download" + ], + "strip_prefix": "autocfg-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"autocfg\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=autocfg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "firewood_crates__aws-lc-rs-1.15.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5932a7d9d28b0d2ea34c6b3779d35e3dd6f6345317c34e73438c4f1f29144151", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aws-lc-rs/1.15.0/download" + ], + "strip_prefix": "aws-lc-rs-1.15.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aws_lc_rs\",\n deps = [\n \"@firewood_crates__aws-lc-rs-1.15.0//:build_script_build\",\n \"@firewood_crates__aws-lc-sys-0.33.0//:aws_lc_sys\",\n \"@firewood_crates__zeroize-1.8.2//:zeroize\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-sys\",\n \"prebuilt-nasm\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.15.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-sys\",\n \"prebuilt-nasm\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@firewood_crates__aws-lc-sys-0.33.0//:aws_lc_sys\",\n ],\n edition = \"2021\",\n links = \"aws_lc_rs_1_15_0_sys\",\n pkg_name = \"aws-lc-rs\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.15.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__aws-lc-sys-0.33.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aws-lc-sys/0.33.0/download" + ], + "strip_prefix": "aws-lc-sys-0.33.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aws_lc_sys\",\n deps = [\n \"@firewood_crates__aws-lc-sys-0.33.0//:build_script_main\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"prebuilt-nasm\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.33.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"prebuilt-nasm\",\n ],\n crate_name = \"build_script_main\",\n crate_root = \"builder/main.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n \"@firewood_crates__cmake-0.1.54//:cmake\",\n \"@firewood_crates__dunce-1.0.5//:dunce\",\n \"@firewood_crates__fs_extra-1.3.0//:fs_extra\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__bindgen-0.72.1//:bindgen\", # cfg(not(any(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), any(target_os = \"linux\", target_os = \"macos\", target_os = \"windows\"), any(target_env = \"gnu\", target_env = \"musl\", target_env = \"msvc\", target_env = \"\")), all(target_arch = \"x86\", target_os = \"windows\", target_env = \"msvc\"), all(target_arch = \"x86\", target_os = \"linux\", target_env = \"gnu\"))))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__bindgen-0.72.1//:bindgen\", # cfg(not(any(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), any(target_os = \"linux\", target_os = \"macos\", target_os = \"windows\"), any(target_env = \"gnu\", target_env = \"musl\", target_env = \"msvc\", target_env = \"\")), all(target_arch = \"x86\", target_os = \"windows\", target_env = \"msvc\"), all(target_arch = \"x86\", target_os = \"linux\", target_env = \"gnu\"))))\n ],\n \"//conditions:default\": [],\n }),\n edition = \"2021\",\n links = \"aws_lc_0_33_0\",\n pkg_name = \"aws-lc-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.33.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_main\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__backtrace-0.3.76": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/backtrace/0.3.76/download" + ], + "strip_prefix": "backtrace-0.3.76", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"backtrace\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__rustc-demangle-0.1.26//:rustc_demangle\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\", # cfg(any(windows, target_os = \"cygwin\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__addr2line-0.25.1//:addr2line\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__miniz_oxide-0.8.9//:miniz_oxide\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n \"@firewood_crates__object-0.37.3//:object\", # cfg(not(all(windows, target_env = \"msvc\", not(target_vendor = \"uwp\"))))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=backtrace\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.76\",\n)\n" + } + }, + "firewood_crates__base64-0.22.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/base64/0.22.1/download" + ], + "strip_prefix": "base64-0.22.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"base64\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=base64\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.22.1\",\n)\n" + } + }, + "firewood_crates__basic-toml-0.1.10": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/basic-toml/0.1.10/download" + ], + "strip_prefix": "basic-toml-0.1.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"basic_toml\",\n deps = [\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=basic-toml\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.10\",\n)\n" + } + }, + "firewood_crates__bindgen-0.72.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bindgen/0.72.1/download" + ], + "strip_prefix": "bindgen-0.72.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bindgen\",\n deps = [\n \"@firewood_crates__bindgen-0.72.1//:build_script_build\",\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__cexpr-0.6.0//:cexpr\",\n \"@firewood_crates__clang-sys-1.8.1//:clang_sys\",\n \"@firewood_crates__itertools-0.13.0//:itertools\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__regex-1.12.2//:regex\",\n \"@firewood_crates__rustc-hash-2.1.1//:rustc_hash\",\n \"@firewood_crates__shlex-1.3.0//:shlex\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.72.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@firewood_crates__clang-sys-1.8.1//:clang_sys\",\n ],\n edition = \"2021\",\n pkg_name = \"bindgen\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.72.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__bitfield-0.19.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitfield/0.19.4/download" + ], + "strip_prefix": "bitfield-0.19.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitfield\",\n proc_macro_deps = [\n \"@firewood_crates__bitfield-macros-0.19.4//:bitfield_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitfield\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.19.4\",\n)\n" + } + }, + "firewood_crates__bitfield-macros-0.19.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitfield-macros/0.19.4/download" + ], + "strip_prefix": "bitfield-macros-0.19.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"bitfield_macros\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitfield-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.19.4\",\n)\n" + } + }, + "firewood_crates__bitflags-1.3.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitflags/1.3.2/download" + ], + "strip_prefix": "bitflags-1.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitflags\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitflags\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.2\",\n)\n" + } + }, + "firewood_crates__bitflags-2.10.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitflags/2.10.0/download" + ], + "strip_prefix": "bitflags-2.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitflags\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"std\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"std\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"std\", # wasm32-wasip1\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"std\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"std\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitflags\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.10.0\",\n)\n" + } + }, + "firewood_crates__bitvec-1.0.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitvec/1.0.1/download" + ], + "strip_prefix": "bitvec-1.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitvec\",\n deps = [\n \"@firewood_crates__funty-2.0.0//:funty\",\n \"@firewood_crates__radium-0.7.0//:radium\",\n \"@firewood_crates__tap-1.0.1//:tap\",\n \"@firewood_crates__wyz-0.5.1//:wyz\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitvec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1\",\n)\n" + } + }, + "firewood_crates__block-buffer-0.10.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/block-buffer/0.10.4/download" + ], + "strip_prefix": "block-buffer-0.10.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"block_buffer\",\n deps = [\n \"@firewood_crates__generic-array-0.14.7//:generic_array\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=block-buffer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.4\",\n)\n" + } + }, + "firewood_crates__bstr-1.12.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bstr/1.12.1/download" + ], + "strip_prefix": "bstr-1.12.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bstr\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__regex-automata-0.4.13//:regex_automata\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n \"unicode\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bstr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.12.1\",\n)\n" + } + }, + "firewood_crates__bumpalo-3.19.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bumpalo/3.19.0/download" + ], + "strip_prefix": "bumpalo-3.19.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bumpalo\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"collections\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bumpalo\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.19.0\",\n)\n" + } + }, + "firewood_crates__byte-slice-cast-1.2.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/byte-slice-cast/1.2.3/download" + ], + "strip_prefix": "byte-slice-cast-1.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"byte_slice_cast\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=byte-slice-cast\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.3\",\n)\n" + } + }, + "firewood_crates__bytemuck-1.24.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bytemuck/1.24.0/download" + ], + "strip_prefix": "bytemuck-1.24.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bytemuck\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bytemuck\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.24.0\",\n)\n" + } + }, + "firewood_crates__bytemuck_derive-1.10.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bytemuck_derive/1.10.2/download" + ], + "strip_prefix": "bytemuck_derive-1.10.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"bytemuck_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bytemuck_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.10.2\",\n)\n" + } + }, + "firewood_crates__byteorder-1.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/byteorder/1.5.0/download" + ], + "strip_prefix": "byteorder-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"byteorder\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=byteorder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "firewood_crates__bytes-1.11.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bytes/1.11.0/download" + ], + "strip_prefix": "bytes-1.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bytes\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bytes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.11.0\",\n)\n" + } + }, + "firewood_crates__byteview-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/byteview/0.6.1/download" + ], + "strip_prefix": "byteview-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"byteview\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=byteview\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__cast-0.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cast/0.3.0/download" + ], + "strip_prefix": "cast-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cast\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cast\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "firewood_crates__cbindgen-0.29.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cbindgen/0.29.2/download" + ], + "strip_prefix": "cbindgen-0.29.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cbindgen\",\n deps = [\n \"@firewood_crates__cbindgen-0.29.2//:build_script_build\",\n \"@firewood_crates__clap-4.5.52//:clap\",\n \"@firewood_crates__heck-0.5.0//:heck\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_json-1.0.145//:serde_json\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n \"@firewood_crates__toml-0.9.8//:toml\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clap\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cbindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.29.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clap\",\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"cbindgen\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cbindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.29.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__cc-1.2.46": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cc/1.2.46/download" + ], + "strip_prefix": "cc-1.2.46", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cc\",\n deps = [\n \"@firewood_crates__find-msvc-tools-0.1.5//:find_msvc_tools\",\n \"@firewood_crates__jobserver-0.1.34//:jobserver\",\n \"@firewood_crates__shlex-1.3.0//:shlex\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"parallel\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.46\",\n)\n" + } + }, + "firewood_crates__cexpr-0.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cexpr/0.6.0/download" + ], + "strip_prefix": "cexpr-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cexpr\",\n deps = [\n \"@firewood_crates__nom-7.1.3//:nom\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cexpr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n" + } + }, + "firewood_crates__cfg-if-1.0.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cfg-if/1.0.4/download" + ], + "strip_prefix": "cfg-if-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cfg_if\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cfg-if\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "firewood_crates__chrono-0.4.42": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/chrono/0.4.42/download" + ], + "strip_prefix": "chrono-0.4.42", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chrono\",\n deps = [\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__iana-time-zone-0.1.64//:iana_time_zone\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__iana-time-zone-0.1.64//:iana_time_zone\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # wasm32-unknown-unknown\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # wasm32-unknown-unknown\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__iana-time-zone-0.1.64//:iana_time_zone\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__iana-time-zone-0.1.64//:iana_time_zone\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"clock\",\n \"default\",\n \"iana-time-zone\",\n \"js-sys\",\n \"now\",\n \"oldtime\",\n \"std\",\n \"wasm-bindgen\",\n \"wasmbind\",\n \"winapi\",\n \"windows-link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=chrono\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.42\",\n)\n" + } + }, + "firewood_crates__ciborium-0.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium/0.2.2/download" + ], + "strip_prefix": "ciborium-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium\",\n deps = [\n \"@firewood_crates__ciborium-io-0.2.2//:ciborium_io\",\n \"@firewood_crates__ciborium-ll-0.2.2//:ciborium_ll\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "firewood_crates__ciborium-io-0.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium-io/0.2.2/download" + ], + "strip_prefix": "ciborium-io-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium_io\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium-io\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "firewood_crates__ciborium-ll-0.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium-ll/0.2.2/download" + ], + "strip_prefix": "ciborium-ll-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium_ll\",\n deps = [\n \"@firewood_crates__ciborium-io-0.2.2//:ciborium_io\",\n \"@firewood_crates__half-2.7.1//:half\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium-ll\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "firewood_crates__clang-sys-1.8.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clang-sys/1.8.1/download" + ], + "strip_prefix": "clang-sys-1.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clang_sys\",\n deps = [\n \"@firewood_crates__clang-sys-1.8.1//:build_script_build\",\n \"@firewood_crates__glob-0.3.3//:glob\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clang-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__glob-0.3.3//:glob\",\n ],\n edition = \"2021\",\n links = \"clang\",\n pkg_name = \"clang-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clang-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.8.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__clap-4.5.52": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap/4.5.52/download" + ], + "strip_prefix": "clap-4.5.52", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap\",\n deps = [\n \"@firewood_crates__clap_builder-4.5.52//:clap_builder\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__clap_derive-4.5.49//:clap_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cargo\",\n \"color\",\n \"default\",\n \"derive\",\n \"error-context\",\n \"help\",\n \"std\",\n \"string\",\n \"suggestions\",\n \"usage\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.5.52\",\n)\n" + } + }, + "firewood_crates__clap_builder-4.5.52": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_builder/4.5.52/download" + ], + "strip_prefix": "clap_builder-4.5.52", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap_builder\",\n deps = [\n \"@firewood_crates__anstream-0.6.21//:anstream\",\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n \"@firewood_crates__clap_lex-0.7.6//:clap_lex\",\n \"@firewood_crates__strsim-0.11.1//:strsim\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cargo\",\n \"color\",\n \"error-context\",\n \"help\",\n \"std\",\n \"string\",\n \"suggestions\",\n \"usage\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_builder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.5.52\",\n)\n" + } + }, + "firewood_crates__clap_derive-4.5.49": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_derive/4.5.49/download" + ], + "strip_prefix": "clap_derive-4.5.49", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"clap_derive\",\n deps = [\n \"@firewood_crates__heck-0.5.0//:heck\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.5.49\",\n)\n" + } + }, + "firewood_crates__clap_lex-0.7.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_lex/0.7.6/download" + ], + "strip_prefix": "clap_lex-0.7.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap_lex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_lex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.6\",\n)\n" + } + }, + "firewood_crates__cmake-0.1.54": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cmake/0.1.54/download" + ], + "strip_prefix": "cmake-0.1.54", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cmake\",\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cmake\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.54\",\n)\n" + } + }, + "firewood_crates__coarsetime-0.1.36": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/coarsetime/0.1.36/download" + ], + "strip_prefix": "coarsetime-0.1.36", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"coarsetime\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__wasix-0.12.21//:wasix\", # cfg(any(target_os = \"wasix\", target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"wasix\", target_os = \"wasi\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=coarsetime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.36\",\n)\n" + } + }, + "firewood_crates__colorchoice-1.0.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/colorchoice/1.0.4/download" + ], + "strip_prefix": "colorchoice-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"colorchoice\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=colorchoice\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "firewood_crates__compare-0.0.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/compare/0.0.6/download" + ], + "strip_prefix": "compare-0.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"compare\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=compare\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.6\",\n)\n" + } + }, + "firewood_crates__console-0.16.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/console/0.16.1/download" + ], + "strip_prefix": "console-0.16.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"console\",\n deps = [\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__unicode-width-0.2.2//:unicode_width\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__encode_unicode-1.0.0//:encode_unicode\", # cfg(windows)\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"ansi-parsing\",\n \"std\",\n \"unicode-width\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=console\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.1\",\n)\n" + } + }, + "firewood_crates__const-hex-1.17.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/const-hex/1.17.0/download" + ], + "strip_prefix": "const-hex-1.17.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"const_hex\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=const-hex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.17.0\",\n)\n" + } + }, + "firewood_crates__const_format-0.2.35": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/const_format/0.2.35/download" + ], + "strip_prefix": "const_format-0.2.35", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"const_format\",\n proc_macro_deps = [\n \"@firewood_crates__const_format_proc_macros-0.2.34//:const_format_proc_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=const_format\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.35\",\n)\n" + } + }, + "firewood_crates__const_format_proc_macros-0.2.34": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/const_format_proc_macros/0.2.34/download" + ], + "strip_prefix": "const_format_proc_macros-0.2.34", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"const_format_proc_macros\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__unicode-xid-0.2.6//:unicode_xid\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=const_format_proc_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.34\",\n)\n" + } + }, + "firewood_crates__core-foundation-0.10.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/core-foundation/0.10.1/download" + ], + "strip_prefix": "core-foundation-0.10.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"core_foundation\",\n deps = [\n \"@firewood_crates__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=core-foundation\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.1\",\n)\n" + } + }, + "firewood_crates__core-foundation-sys-0.8.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/core-foundation-sys/0.8.7/download" + ], + "strip_prefix": "core-foundation-sys-0.8.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"core_foundation_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=core-foundation-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.7\",\n)\n" + } + }, + "firewood_crates__cpp_demangle-0.4.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cpp_demangle/0.4.5/download" + ], + "strip_prefix": "cpp_demangle-0.4.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cpp_demangle\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__cpp_demangle-0.4.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cpp_demangle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"cpp_demangle\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cpp_demangle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__cpufeatures-0.2.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cpufeatures/0.2.17/download" + ], + "strip_prefix": "cpufeatures-0.2.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cpufeatures\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cpufeatures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.17\",\n)\n" + } + }, + "firewood_crates__criterion-0.7.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/criterion/0.7.0/download" + ], + "strip_prefix": "criterion-0.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"criterion\",\n deps = [\n \"@firewood_crates__anes-0.1.6//:anes\",\n \"@firewood_crates__cast-0.3.0//:cast\",\n \"@firewood_crates__ciborium-0.2.2//:ciborium\",\n \"@firewood_crates__clap-4.5.52//:clap\",\n \"@firewood_crates__criterion-plot-0.6.0//:criterion_plot\",\n \"@firewood_crates__itertools-0.13.0//:itertools\",\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n \"@firewood_crates__oorandom-11.1.5//:oorandom\",\n \"@firewood_crates__plotters-0.3.7//:plotters\",\n \"@firewood_crates__rayon-1.11.0//:rayon\",\n \"@firewood_crates__regex-1.12.2//:regex\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_json-1.0.145//:serde_json\",\n \"@firewood_crates__tinytemplate-1.2.1//:tinytemplate\",\n \"@firewood_crates__walkdir-2.5.0//:walkdir\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cargo_bench_support\",\n \"default\",\n \"html_reports\",\n \"plotters\",\n \"rayon\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=criterion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.0\",\n)\n" + } + }, + "firewood_crates__criterion-plot-0.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/criterion-plot/0.6.0/download" + ], + "strip_prefix": "criterion-plot-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"criterion_plot\",\n deps = [\n \"@firewood_crates__cast-0.3.0//:cast\",\n \"@firewood_crates__itertools-0.13.0//:itertools\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=criterion-plot\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n" + } + }, + "firewood_crates__crossbeam-deque-0.8.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-deque/0.8.6/download" + ], + "strip_prefix": "crossbeam-deque-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_deque\",\n deps = [\n \"@firewood_crates__crossbeam-epoch-0.9.18//:crossbeam_epoch\",\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-deque\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "firewood_crates__crossbeam-epoch-0.9.18": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download" + ], + "strip_prefix": "crossbeam-epoch-0.9.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_epoch\",\n deps = [\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-epoch\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.18\",\n)\n" + } + }, + "firewood_crates__crossbeam-skiplist-0.1.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-skiplist/0.1.3/download" + ], + "strip_prefix": "crossbeam-skiplist-0.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_skiplist\",\n deps = [\n \"@firewood_crates__crossbeam-epoch-0.9.18//:crossbeam_epoch\",\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-skiplist\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.3\",\n)\n" + } + }, + "firewood_crates__crossbeam-utils-0.8.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-utils/0.8.21/download" + ], + "strip_prefix": "crossbeam-utils-0.8.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_utils\",\n deps = [\n \"@firewood_crates__crossbeam-utils-0.8.21//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.21\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"crossbeam-utils\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.21\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__crunchy-0.2.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crunchy/0.2.4/download" + ], + "strip_prefix": "crunchy-0.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crunchy\",\n deps = [\n \"@firewood_crates__crunchy-0.2.4//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"limit_128\",\n \"limit_256\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crunchy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"limit_128\",\n \"limit_256\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"crunchy\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crunchy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__crypto-common-0.1.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crypto-common/0.1.7/download" + ], + "strip_prefix": "crypto-common-0.1.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crypto_common\",\n deps = [\n \"@firewood_crates__generic-array-0.14.7//:generic_array\",\n \"@firewood_crates__typenum-1.19.0//:typenum\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crypto-common\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.7\",\n)\n" + } + }, + "firewood_crates__csv-1.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/csv/1.4.0/download" + ], + "strip_prefix": "csv-1.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"csv\",\n deps = [\n \"@firewood_crates__csv-core-0.1.13//:csv_core\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__ryu-1.0.20//:ryu\",\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=csv\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.4.0\",\n)\n" + } + }, + "firewood_crates__csv-core-0.1.13": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/csv-core/0.1.13/download" + ], + "strip_prefix": "csv-core-0.1.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"csv_core\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=csv-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.13\",\n)\n" + } + }, + "firewood_crates__ctor-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3ffc71fcdcdb40d6f087edddf7f8f1f8f79e6cf922f555a9ee8779752d4819bd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ctor/0.6.1/download" + ], + "strip_prefix": "ctor-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ctor\",\n deps = [\n \"@firewood_crates__dtor-0.1.1//:dtor\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__ctor-proc-macro-0.0.7//:ctor_proc_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__no_warn_on_missing_unsafe\",\n \"default\",\n \"dtor\",\n \"proc_macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ctor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__ctor-proc-macro-0.0.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ctor-proc-macro/0.0.7/download" + ], + "strip_prefix": "ctor-proc-macro-0.0.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"ctor_proc_macro\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ctor-proc-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.7\",\n)\n" + } + }, + "firewood_crates__dashmap-6.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dashmap/6.1.0/download" + ], + "strip_prefix": "dashmap-6.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dashmap\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@firewood_crates__hashbrown-0.14.5//:hashbrown\",\n \"@firewood_crates__lock_api-0.4.14//:lock_api\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__parking_lot_core-0.9.12//:parking_lot_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dashmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"6.1.0\",\n)\n" + } + }, + "firewood_crates__debugid-0.8.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/debugid/0.8.0/download" + ], + "strip_prefix": "debugid-0.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"debugid\",\n deps = [\n \"@firewood_crates__uuid-1.18.1//:uuid\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=debugid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.0\",\n)\n" + } + }, + "firewood_crates__derive-where-1.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/derive-where/1.6.0/download" + ], + "strip_prefix": "derive-where-1.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"derive_where\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=derive-where\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.6.0\",\n)\n" + } + }, + "firewood_crates__difflib-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/difflib/0.4.0/download" + ], + "strip_prefix": "difflib-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"difflib\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=difflib\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n" + } + }, + "firewood_crates__digest-0.10.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/digest/0.10.7/download" + ], + "strip_prefix": "digest-0.10.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"digest\",\n deps = [\n \"@firewood_crates__block-buffer-0.10.4//:block_buffer\",\n \"@firewood_crates__crypto-common-0.1.7//:crypto_common\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"block-buffer\",\n \"core-api\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=digest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.7\",\n)\n" + } + }, + "firewood_crates__displaydoc-0.2.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/displaydoc/0.2.5/download" + ], + "strip_prefix": "displaydoc-0.2.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"displaydoc\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=displaydoc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.5\",\n)\n" + } + }, + "firewood_crates__double-ended-peekable-0.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/double-ended-peekable/0.1.0/download" + ], + "strip_prefix": "double-ended-peekable-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"double_ended_peekable\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=double-ended-peekable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "firewood_crates__dtor-0.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dtor/0.1.1/download" + ], + "strip_prefix": "dtor-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dtor\",\n proc_macro_deps = [\n \"@firewood_crates__dtor-proc-macro-0.0.6//:dtor_proc_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__no_warn_on_missing_unsafe\",\n \"proc_macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dtor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "firewood_crates__dtor-proc-macro-0.0.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dtor-proc-macro/0.0.6/download" + ], + "strip_prefix": "dtor-proc-macro-0.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"dtor_proc_macro\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dtor-proc-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.6\",\n)\n" + } + }, + "firewood_crates__dunce-1.0.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dunce/1.0.5/download" + ], + "strip_prefix": "dunce-1.0.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dunce\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dunce\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.5\",\n)\n" + } + }, + "firewood_crates__either-1.15.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/either/1.15.0/download" + ], + "strip_prefix": "either-1.15.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"either\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=either\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.15.0\",\n)\n" + } + }, + "firewood_crates__encode_unicode-1.0.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/encode_unicode/1.0.0/download" + ], + "strip_prefix": "encode_unicode-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"encode_unicode\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=encode_unicode\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n" + } + }, + "firewood_crates__endian-type-0.1.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/endian-type/0.1.2/download" + ], + "strip_prefix": "endian-type-0.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"endian_type\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=endian-type\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n" + } + }, + "firewood_crates__enum-as-inner-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/enum-as-inner/0.6.1/download" + ], + "strip_prefix": "enum-as-inner-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"enum_as_inner\",\n deps = [\n \"@firewood_crates__heck-0.5.0//:heck\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=enum-as-inner\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__enum_dispatch-0.3.13": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/enum_dispatch/0.3.13/download" + ], + "strip_prefix": "enum_dispatch-0.3.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"enum_dispatch\",\n deps = [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=enum_dispatch\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.13\",\n)\n" + } + }, + "firewood_crates__env_filter-0.1.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/env_filter/0.1.4/download" + ], + "strip_prefix": "env_filter-0.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"env_filter\",\n deps = [\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__regex-1.12.2//:regex\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"regex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=env_filter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.4\",\n)\n" + } + }, + "firewood_crates__env_logger-0.11.8": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/env_logger/0.11.8/download" + ], + "strip_prefix": "env_logger-0.11.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"env_logger\",\n deps = [\n \"@firewood_crates__anstream-0.6.21//:anstream\",\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n \"@firewood_crates__env_filter-0.1.4//:env_filter\",\n \"@firewood_crates__jiff-0.2.16//:jiff\",\n \"@firewood_crates__log-0.4.28//:log\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auto-color\",\n \"color\",\n \"default\",\n \"humantime\",\n \"regex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=env_logger\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.8\",\n)\n" + } + }, + "firewood_crates__equator-0.4.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/equator/0.4.2/download" + ], + "strip_prefix": "equator-0.4.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"equator\",\n proc_macro_deps = [\n \"@firewood_crates__equator-macro-0.4.2//:equator_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=equator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.2\",\n)\n" + } + }, + "firewood_crates__equator-macro-0.4.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/equator-macro/0.4.2/download" + ], + "strip_prefix": "equator-macro-0.4.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"equator_macro\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=equator-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.2\",\n)\n" + } + }, + "firewood_crates__equivalent-1.0.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/equivalent/1.0.2/download" + ], + "strip_prefix": "equivalent-1.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"equivalent\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=equivalent\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2\",\n)\n" + } + }, + "firewood_crates__errno-0.3.14": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/errno/0.3.14/download" + ], + "strip_prefix": "errno-0.3.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"errno\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=errno\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.14\",\n)\n" + } + }, + "firewood_crates__ethbloom-0.14.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ethbloom/0.14.1/download" + ], + "strip_prefix": "ethbloom-0.14.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ethbloom\",\n deps = [\n \"@firewood_crates__crunchy-0.2.4//:crunchy\",\n \"@firewood_crates__fixed-hash-0.8.0//:fixed_hash\",\n \"@firewood_crates__impl-rlp-0.4.0//:impl_rlp\",\n \"@firewood_crates__impl-serde-0.5.0//:impl_serde\",\n \"@firewood_crates__tiny-keccak-2.0.2//:tiny_keccak\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"impl-rlp\",\n \"impl-serde\",\n \"rlp\",\n \"rustc-hex\",\n \"serialize\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ethbloom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.1\",\n)\n" + } + }, + "firewood_crates__ethereum-types-0.16.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f7326303c6c18bb03c7a3c4c22d4032ae60dfe673ca9109602aa4d8154b2637e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ethereum-types/0.16.0/download" + ], + "strip_prefix": "ethereum-types-0.16.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ethereum_types\",\n deps = [\n \"@firewood_crates__ethbloom-0.14.1//:ethbloom\",\n \"@firewood_crates__fixed-hash-0.8.0//:fixed_hash\",\n \"@firewood_crates__impl-rlp-0.4.0//:impl_rlp\",\n \"@firewood_crates__impl-serde-0.5.0//:impl_serde\",\n \"@firewood_crates__primitive-types-0.14.0//:primitive_types\",\n \"@firewood_crates__uint-0.10.0//:uint\",\n ],\n aliases = {\n \"@firewood_crates__uint-0.10.0//:uint\": \"uint_crate\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"ethbloom\",\n \"impl-rlp\",\n \"impl-serde\",\n \"rlp\",\n \"serialize\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ethereum-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.0\",\n)\n" + } + }, + "firewood_crates__fastant-0.1.10": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "62bf7fa928ce0c4a43bd6e7d1235318fc32ac3a3dea06a2208c44e729449471a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastant/0.1.10/download" + ], + "strip_prefix": "fastant-0.1.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fastant\",\n deps = [\n \"@firewood_crates__web-time-1.1.0//:web_time\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__small_ctor-0.1.2//:small_ctor\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastant\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.10\",\n)\n" + } + }, + "firewood_crates__fastrace-0.7.14": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "318783b9fefe06130ab664ff1779215657586b004c0c7f3d6ece16d658936d06", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastrace/0.7.14/download" + ], + "strip_prefix": "fastrace-0.7.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fastrace\",\n deps = [\n \"@firewood_crates__fastant-0.1.10//:fastant\",\n \"@firewood_crates__parking_lot-0.12.5//:parking_lot\",\n \"@firewood_crates__pin-project-1.1.10//:pin_project\",\n \"@firewood_crates__rand-0.9.2//:rand\",\n \"@firewood_crates__rtrb-0.3.2//:rtrb\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__fastrace-macro-0.7.14//:fastrace_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"enable\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastrace\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.14\",\n)\n" + } + }, + "firewood_crates__fastrace-macro-0.7.14": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c7079009cf129d63c850dee732b58d7639d278a47ad99c607954ac94cfd57ef4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastrace-macro/0.7.14/download" + ], + "strip_prefix": "fastrace-macro-0.7.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"fastrace_macro\",\n deps = [\n \"@firewood_crates__proc-macro-error2-2.0.1//:proc_macro_error2\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"enable\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastrace-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.14\",\n)\n" + } + }, + "firewood_crates__fastrace-opentelemetry-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b7e8ec7cff0ea398352764b6ee15c0902ccabf23d823525254b52d7f878fcf60", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastrace-opentelemetry/0.14.0/download" + ], + "strip_prefix": "fastrace-opentelemetry-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fastrace_opentelemetry\",\n deps = [\n \"@firewood_crates__fastrace-0.7.14//:fastrace\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n \"@firewood_crates__opentelemetry_sdk-0.31.0//:opentelemetry_sdk\",\n \"@firewood_crates__pollster-0.4.0//:pollster\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastrace-opentelemetry\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__fastrand-2.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastrand/2.3.0/download" + ], + "strip_prefix": "fastrand-2.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fastrand\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastrand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.0\",\n)\n" + } + }, + "firewood_crates__find-msvc-tools-0.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/find-msvc-tools/0.1.5/download" + ], + "strip_prefix": "find-msvc-tools-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"find_msvc_tools\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=find-msvc-tools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "firewood_crates__findshlibs-0.10.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/findshlibs/0.10.2/download" + ], + "strip_prefix": "findshlibs-0.10.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"findshlibs\",\n deps = [\n \"@firewood_crates__findshlibs-0.10.2//:build_script_build\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__lazy_static-1.5.0//:lazy_static\", # cfg(any(target_os = \"macos\", target_os = \"ios\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__winapi-0.3.9//:winapi\", # cfg(target_os = \"windows\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=findshlibs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n ],\n edition = \"2018\",\n pkg_name = \"findshlibs\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=findshlibs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.10.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__fixed-hash-0.8.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fixed-hash/0.8.0/download" + ], + "strip_prefix": "fixed-hash-0.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fixed_hash\",\n deps = [\n \"@firewood_crates__byteorder-1.5.0//:byteorder\",\n \"@firewood_crates__rand-0.8.5//:rand\",\n \"@firewood_crates__rustc-hex-2.1.0//:rustc_hex\",\n \"@firewood_crates__static_assertions-1.1.0//:static_assertions\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"byteorder\",\n \"default\",\n \"rand\",\n \"rustc-hex\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fixed-hash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.0\",\n)\n" + } + }, + "firewood_crates__fjall-2.11.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0b25ad44cd4360a0448a9b5a0a6f1c7a621101cca4578706d43c9a821418aebc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fjall/2.11.2/download" + ], + "strip_prefix": "fjall-2.11.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fjall\",\n deps = [\n \"@firewood_crates__byteorder-1.5.0//:byteorder\",\n \"@firewood_crates__byteview-0.6.1//:byteview\",\n \"@firewood_crates__dashmap-6.1.0//:dashmap\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__lsm-tree-2.10.4//:lsm_tree\",\n \"@firewood_crates__path-absolutize-3.1.1//:path_absolutize\",\n \"@firewood_crates__std-semaphore-0.1.0//:std_semaphore\",\n \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n \"@firewood_crates__xxhash-rust-0.8.15//:xxhash_rust\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"lz4\",\n \"single_writer_tx\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fjall\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.11.2\",\n)\n" + } + }, + "firewood_crates__float-cmp-0.10.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/float-cmp/0.10.0/download" + ], + "strip_prefix": "float-cmp-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"float_cmp\",\n deps = [\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"num-traits\",\n \"ratio\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=float-cmp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "firewood_crates__fnv-1.0.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fnv/1.0.7/download" + ], + "strip_prefix": "fnv-1.0.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fnv\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fnv\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.7\",\n)\n" + } + }, + "firewood_crates__foldhash-0.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foldhash/0.1.5/download" + ], + "strip_prefix": "foldhash-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foldhash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foldhash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "firewood_crates__foldhash-0.2.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foldhash/0.2.0/download" + ], + "strip_prefix": "foldhash-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foldhash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foldhash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "firewood_crates__form_urlencoded-1.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/form_urlencoded/1.2.2/download" + ], + "strip_prefix": "form_urlencoded-1.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"form_urlencoded\",\n deps = [\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=form_urlencoded\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.2\",\n)\n" + } + }, + "firewood_crates__fs_extra-1.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fs_extra/1.3.0/download" + ], + "strip_prefix": "fs_extra-1.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fs_extra\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fs_extra\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.0\",\n)\n" + } + }, + "firewood_crates__funty-2.0.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/funty/2.0.0/download" + ], + "strip_prefix": "funty-2.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"funty\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=funty\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.0\",\n)\n" + } + }, + "firewood_crates__futures-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures/0.3.31/download" + ], + "strip_prefix": "futures-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures\",\n deps = [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-executor-0.3.31//:futures_executor\",\n \"@firewood_crates__futures-io-0.3.31//:futures_io\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n \"@firewood_crates__futures-task-0.3.31//:futures_task\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"executor\",\n \"futures-executor\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-channel-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-channel/0.3.31/download" + ], + "strip_prefix": "futures-channel-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_channel\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"futures-sink\",\n \"sink\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-channel\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-core-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-core/0.3.31/download" + ], + "strip_prefix": "futures-core-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_core\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-executor-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-executor/0.3.31/download" + ], + "strip_prefix": "futures-executor-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_executor\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-task-0.3.31//:futures_task\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-executor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-io-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-io/0.3.31/download" + ], + "strip_prefix": "futures-io-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_io\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-io\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-macro-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-macro/0.3.31/download" + ], + "strip_prefix": "futures-macro-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"futures_macro\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-sink-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-sink/0.3.31/download" + ], + "strip_prefix": "futures-sink-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_sink\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-sink\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-task-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-task/0.3.31/download" + ], + "strip_prefix": "futures-task-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_task\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-task\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__futures-util-0.3.31": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-util/0.3.31/download" + ], + "strip_prefix": "futures-util-0.3.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_util\",\n deps = [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-io-0.3.31//:futures_io\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n \"@firewood_crates__futures-task-0.3.31//:futures_task\",\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__pin-utils-0.1.0//:pin_utils\",\n \"@firewood_crates__slab-0.4.11//:slab\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__futures-macro-0.3.31//:futures_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"async-await\",\n \"async-await-macro\",\n \"channel\",\n \"futures-channel\",\n \"futures-io\",\n \"futures-macro\",\n \"futures-sink\",\n \"io\",\n \"memchr\",\n \"sink\",\n \"slab\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.31\",\n)\n" + } + }, + "firewood_crates__generic-array-0.14.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/generic-array/0.14.7/download" + ], + "strip_prefix": "generic-array-0.14.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"generic_array\",\n deps = [\n \"@firewood_crates__generic-array-0.14.7//:build_script_build\",\n \"@firewood_crates__typenum-1.19.0//:typenum\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"more_lengths\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=generic-array\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.7\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"more_lengths\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__version_check-0.9.5//:version_check\",\n ],\n edition = \"2015\",\n pkg_name = \"generic-array\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=generic-array\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.14.7\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__getrandom-0.2.16": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/getrandom/0.2.16/download" + ], + "strip_prefix": "getrandom-0.2.16", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"getrandom\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__wasi-0.11.1-wasi-snapshot-preview1//:wasi\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.16\",\n)\n" + } + }, + "firewood_crates__getrandom-0.3.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/getrandom/0.3.4/download" + ], + "strip_prefix": "getrandom-0.3.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"getrandom\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__getrandom-0.3.4//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"getrandom\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__gimli-0.32.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/gimli/0.32.3/download" + ], + "strip_prefix": "gimli-0.32.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"gimli\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"read\",\n \"read-core\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=gimli\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.32.3\",\n)\n" + } + }, + "firewood_crates__glob-0.3.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/glob/0.3.3/download" + ], + "strip_prefix": "glob-0.3.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"glob\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=glob\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.3\",\n)\n" + } + }, + "firewood_crates__guardian-1.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/guardian/1.3.0/download" + ], + "strip_prefix": "guardian-1.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"guardian\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=guardian\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.0\",\n)\n" + } + }, + "firewood_crates__h2-0.4.12": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/h2/0.4.12/download" + ], + "strip_prefix": "h2-0.4.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"h2\",\n deps = [\n \"@firewood_crates__atomic-waker-1.1.2//:atomic_waker\",\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__fnv-1.0.7//:fnv\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__slab-0.4.11//:slab\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tokio-util-0.7.17//:tokio_util\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=h2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.12\",\n)\n" + } + }, + "firewood_crates__half-2.7.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/half/2.7.1/download" + ], + "strip_prefix": "half-2.7.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"half\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__zerocopy-0.8.27//:zerocopy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=half\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.7.1\",\n)\n" + } + }, + "firewood_crates__hash-db-0.16.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hash-db/0.16.0/download" + ], + "strip_prefix": "hash-db-0.16.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hash_db\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hash-db\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.0\",\n)\n" + } + }, + "firewood_crates__hash256-std-hasher-0.15.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hash256-std-hasher/0.15.2/download" + ], + "strip_prefix": "hash256-std-hasher-0.15.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hash256_std_hasher\",\n deps = [\n \"@firewood_crates__crunchy-0.2.4//:crunchy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hash256-std-hasher\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.15.2\",\n)\n" + } + }, + "firewood_crates__hashbrown-0.14.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.14.5/download" + ], + "strip_prefix": "hashbrown-0.14.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"raw\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.5\",\n)\n" + } + }, + "firewood_crates__hashbrown-0.15.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.15.5/download" + ], + "strip_prefix": "hashbrown-0.15.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n deps = [\n \"@firewood_crates__foldhash-0.1.5//:foldhash\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default-hasher\",\n \"raw-entry\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.15.5\",\n)\n" + } + }, + "firewood_crates__hashbrown-0.16.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.16.0/download" + ], + "strip_prefix": "hashbrown-0.16.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n deps = [\n \"@firewood_crates__allocator-api2-0.2.21//:allocator_api2\",\n \"@firewood_crates__equivalent-1.0.2//:equivalent\",\n \"@firewood_crates__foldhash-0.2.0//:foldhash\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"allocator-api2\",\n \"default\",\n \"default-hasher\",\n \"equivalent\",\n \"inline-more\",\n \"raw-entry\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.0\",\n)\n" + } + }, + "firewood_crates__heck-0.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/heck/0.5.0/download" + ], + "strip_prefix": "heck-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"heck\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=heck\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "firewood_crates__hermit-abi-0.5.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hermit-abi/0.5.2/download" + ], + "strip_prefix": "hermit-abi-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hermit_abi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hermit-abi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "firewood_crates__hex-0.4.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hex/0.4.3/download" + ], + "strip_prefix": "hex-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n" + } + }, + "firewood_crates__hex-literal-1.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hex-literal/1.1.0/download" + ], + "strip_prefix": "hex-literal-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hex_literal\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hex-literal\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "firewood_crates__http-1.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http/1.3.1/download" + ], + "strip_prefix": "http-1.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__fnv-1.0.7//:fnv\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.1\",\n)\n" + } + }, + "firewood_crates__http-body-1.0.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http-body/1.0.1/download" + ], + "strip_prefix": "http-body-1.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http_body\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__http-1.3.1//:http\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http-body\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1\",\n)\n" + } + }, + "firewood_crates__http-body-util-0.1.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http-body-util/0.1.3/download" + ], + "strip_prefix": "http-body-util-0.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http_body_util\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__http-body-1.0.1//:http_body\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http-body-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.3\",\n)\n" + } + }, + "firewood_crates__httparse-1.10.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/httparse/1.10.1/download" + ], + "strip_prefix": "httparse-1.10.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"httparse\",\n deps = [\n \"@firewood_crates__httparse-1.10.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httparse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.10.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"httparse\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httparse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.10.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__httpdate-1.0.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/httpdate/1.0.3/download" + ], + "strip_prefix": "httpdate-1.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"httpdate\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httpdate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.3\",\n)\n" + } + }, + "firewood_crates__hyper-1.8.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper/1.8.1/download" + ], + "strip_prefix": "hyper-1.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper\",\n deps = [\n \"@firewood_crates__atomic-waker-1.1.2//:atomic_waker\",\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__h2-0.4.12//:h2\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__http-body-1.0.1//:http_body\",\n \"@firewood_crates__httparse-1.10.1//:httparse\",\n \"@firewood_crates__httpdate-1.0.3//:httpdate\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__pin-utils-0.1.0//:pin_utils\",\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__want-0.3.1//:want\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"default\",\n \"http1\",\n \"http2\",\n \"server\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.1\",\n)\n" + } + }, + "firewood_crates__hyper-rustls-0.27.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-rustls/0.27.7/download" + ], + "strip_prefix": "hyper-rustls-0.27.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_rustls\",\n deps = [\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__hyper-1.8.1//:hyper\",\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\",\n \"@firewood_crates__rustls-0.23.35//:rustls\",\n \"@firewood_crates__rustls-native-certs-0.8.2//:rustls_native_certs\",\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tokio-rustls-0.26.4//:tokio_rustls\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n ],\n aliases = {\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"http1\",\n \"rustls-native-certs\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.27.7\",\n)\n" + } + }, + "firewood_crates__hyper-timeout-0.5.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-timeout/0.5.2/download" + ], + "strip_prefix": "hyper-timeout-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_timeout\",\n deps = [\n \"@firewood_crates__hyper-1.8.1//:hyper\",\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-timeout\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "firewood_crates__hyper-util-0.1.18": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-util/0.1.18/download" + ], + "strip_prefix": "hyper-util-0.1.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_util\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__http-body-1.0.1//:http_body\",\n \"@firewood_crates__hyper-1.8.1//:hyper\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__socket2-0.6.1//:socket2\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__base64-0.22.1//:base64\", # aarch64-apple-darwin\n \"@firewood_crates__ipnet-2.11.0//:ipnet\", # aarch64-apple-darwin\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__base64-0.22.1//:base64\", # aarch64-unknown-linux-gnu\n \"@firewood_crates__ipnet-2.11.0//:ipnet\", # aarch64-unknown-linux-gnu\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__base64-0.22.1//:base64\", # x86_64-pc-windows-msvc\n \"@firewood_crates__ipnet-2.11.0//:ipnet\", # x86_64-pc-windows-msvc\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__base64-0.22.1//:base64\", # x86_64-unknown-linux-gnu\n \"@firewood_crates__ipnet-2.11.0//:ipnet\", # x86_64-unknown-linux-gnu\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__base64-0.22.1//:base64\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@firewood_crates__ipnet-2.11.0//:ipnet\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"client-legacy\",\n \"default\",\n \"http1\",\n \"http2\",\n \"server\",\n \"server-auto\",\n \"service\",\n \"tokio\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"client-proxy\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"client-proxy\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"client-proxy\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"client-proxy\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"client-proxy\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.18\",\n)\n" + } + }, + "firewood_crates__iana-time-zone-0.1.64": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/iana-time-zone/0.1.64/download" + ], + "strip_prefix": "iana-time-zone-0.1.64", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"iana_time_zone\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__core-foundation-sys-0.8.7//:core_foundation_sys\", # cfg(target_vendor = \"apple\")\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-core-0.62.2//:windows_core\", # cfg(target_os = \"windows\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"fallback\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=iana-time-zone\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.64\",\n)\n" + } + }, + "firewood_crates__iana-time-zone-haiku-0.1.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/iana-time-zone-haiku/0.1.2/download" + ], + "strip_prefix": "iana-time-zone-haiku-0.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"iana_time_zone_haiku\",\n deps = [\n \"@firewood_crates__iana-time-zone-haiku-0.1.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=iana-time-zone-haiku\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n ],\n edition = \"2018\",\n pkg_name = \"iana-time-zone-haiku\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=iana-time-zone-haiku\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.1.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__icu_collections-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_collections/2.1.1/download" + ], + "strip_prefix": "icu_collections-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_collections\",\n deps = [\n \"@firewood_crates__potential_utf-0.1.4//:potential_utf\",\n \"@firewood_crates__yoke-0.8.1//:yoke\",\n \"@firewood_crates__zerofrom-0.1.6//:zerofrom\",\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_collections\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__icu_locale_core-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_locale_core/2.1.1/download" + ], + "strip_prefix": "icu_locale_core-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_locale_core\",\n deps = [\n \"@firewood_crates__litemap-0.8.1//:litemap\",\n \"@firewood_crates__tinystr-0.8.2//:tinystr\",\n \"@firewood_crates__writeable-0.6.2//:writeable\",\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_locale_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__icu_normalizer-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_normalizer/2.1.1/download" + ], + "strip_prefix": "icu_normalizer-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_normalizer\",\n deps = [\n \"@firewood_crates__icu_collections-2.1.1//:icu_collections\",\n \"@firewood_crates__icu_normalizer_data-2.1.1//:icu_normalizer_data\",\n \"@firewood_crates__icu_provider-2.1.1//:icu_provider\",\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__icu_normalizer_data-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_normalizer_data/2.1.1/download" + ], + "strip_prefix": "icu_normalizer_data-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_normalizer_data\",\n deps = [\n \"@firewood_crates__icu_normalizer_data-2.1.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"icu_normalizer_data\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__icu_properties-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_properties/2.1.1/download" + ], + "strip_prefix": "icu_properties-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_properties\",\n deps = [\n \"@firewood_crates__icu_collections-2.1.1//:icu_collections\",\n \"@firewood_crates__icu_locale_core-2.1.1//:icu_locale_core\",\n \"@firewood_crates__icu_properties_data-2.1.1//:icu_properties_data\",\n \"@firewood_crates__icu_provider-2.1.1//:icu_provider\",\n \"@firewood_crates__zerotrie-0.2.3//:zerotrie\",\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__icu_properties_data-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_properties_data/2.1.1/download" + ], + "strip_prefix": "icu_properties_data-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_properties_data\",\n deps = [\n \"@firewood_crates__icu_properties_data-2.1.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"icu_properties_data\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__icu_provider-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_provider/2.1.1/download" + ], + "strip_prefix": "icu_provider-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_provider\",\n deps = [\n \"@firewood_crates__icu_locale_core-2.1.1//:icu_locale_core\",\n \"@firewood_crates__writeable-0.6.2//:writeable\",\n \"@firewood_crates__yoke-0.8.1//:yoke\",\n \"@firewood_crates__zerofrom-0.1.6//:zerofrom\",\n \"@firewood_crates__zerotrie-0.2.3//:zerotrie\",\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"baked\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_provider\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__idna-1.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/idna/1.1.0/download" + ], + "strip_prefix": "idna-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"idna\",\n deps = [\n \"@firewood_crates__idna_adapter-1.2.1//:idna_adapter\",\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n \"@firewood_crates__utf8_iter-1.0.4//:utf8_iter\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"compiled_data\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=idna\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "firewood_crates__idna_adapter-1.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/idna_adapter/1.2.1/download" + ], + "strip_prefix": "idna_adapter-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"idna_adapter\",\n deps = [\n \"@firewood_crates__icu_normalizer-2.1.1//:icu_normalizer\",\n \"@firewood_crates__icu_properties-2.1.1//:icu_properties\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=idna_adapter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "firewood_crates__impl-codec-0.7.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/impl-codec/0.7.1/download" + ], + "strip_prefix": "impl-codec-0.7.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"impl_codec\",\n deps = [\n \"@firewood_crates__parity-scale-codec-3.7.5//:parity_scale_codec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=impl-codec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.1\",\n)\n" + } + }, + "firewood_crates__impl-rlp-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/impl-rlp/0.4.0/download" + ], + "strip_prefix": "impl-rlp-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"impl_rlp\",\n deps = [\n \"@firewood_crates__rlp-0.6.1//:rlp\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=impl-rlp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n" + } + }, + "firewood_crates__impl-serde-0.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/impl-serde/0.5.0/download" + ], + "strip_prefix": "impl-serde-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"impl_serde\",\n deps = [\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=impl-serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "firewood_crates__impl-trait-for-tuples-0.2.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/impl-trait-for-tuples/0.2.3/download" + ], + "strip_prefix": "impl-trait-for-tuples-0.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"impl_trait_for_tuples\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=impl-trait-for-tuples\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.3\",\n)\n" + } + }, + "firewood_crates__include_dir-0.7.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/include_dir/0.7.4/download" + ], + "strip_prefix": "include_dir-0.7.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"include_dir\",\n proc_macro_deps = [\n \"@firewood_crates__include_dir_macros-0.7.4//:include_dir_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=include_dir\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.4\",\n)\n" + } + }, + "firewood_crates__include_dir_macros-0.7.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/include_dir_macros/0.7.4/download" + ], + "strip_prefix": "include_dir_macros-0.7.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"include_dir_macros\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=include_dir_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.4\",\n)\n" + } + }, + "firewood_crates__indexmap-2.12.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/indexmap/2.12.0/download" + ], + "strip_prefix": "indexmap-2.12.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"indexmap\",\n deps = [\n \"@firewood_crates__equivalent-1.0.2//:equivalent\",\n \"@firewood_crates__hashbrown-0.16.0//:hashbrown\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=indexmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.12.0\",\n)\n" + } + }, + "firewood_crates__indicatif-0.18.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/indicatif/0.18.3/download" + ], + "strip_prefix": "indicatif-0.18.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"indicatif\",\n deps = [\n \"@firewood_crates__console-0.16.1//:console\",\n \"@firewood_crates__portable-atomic-1.11.1//:portable_atomic\",\n \"@firewood_crates__unicode-width-0.2.2//:unicode_width\",\n \"@firewood_crates__unit-prefix-0.5.2//:unit_prefix\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__web-time-1.1.0//:web_time\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__web-time-1.1.0//:web_time\", # cfg(target_arch = \"wasm32\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"unicode-width\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=indicatif\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.18.3\",\n)\n" + } + }, + "firewood_crates__inferno-0.11.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/inferno/0.11.21/download" + ], + "strip_prefix": "inferno-0.11.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"inferno\",\n deps = [\n \"@firewood_crates__ahash-0.8.12//:ahash\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__is-terminal-0.4.17//:is_terminal\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__num-format-0.4.4//:num_format\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__quick-xml-0.26.0//:quick_xml\",\n \"@firewood_crates__rgb-0.8.52//:rgb\",\n \"@firewood_crates__str_stack-0.1.0//:str_stack\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"indexmap\",\n \"nameattr\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=inferno\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.21\",\n)\n" + } + }, + "firewood_crates__integer-encoding-4.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/integer-encoding/4.1.0/download" + ], + "strip_prefix": "integer-encoding-4.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"integer_encoding\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=integer-encoding\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.1.0\",\n)\n" + } + }, + "firewood_crates__interval-heap-0.0.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/interval-heap/0.0.5/download" + ], + "strip_prefix": "interval-heap-0.0.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"interval_heap\",\n deps = [\n \"@firewood_crates__compare-0.0.6//:compare\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=interval-heap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.5\",\n)\n" + } + }, + "firewood_crates__io-uring-0.7.11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/io-uring/0.7.11/download" + ], + "strip_prefix": "io-uring-0.7.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"io_uring\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__io-uring-0.7.11//:build_script_build\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=io-uring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.11\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"io-uring\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=io-uring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.7.11\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__ipnet-2.11.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ipnet/2.11.0/download" + ], + "strip_prefix": "ipnet-2.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ipnet\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ipnet\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.11.0\",\n)\n" + } + }, + "firewood_crates__iri-string-0.7.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/iri-string/0.7.9/download" + ], + "strip_prefix": "iri-string-0.7.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"iri_string\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=iri-string\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.9\",\n)\n" + } + }, + "firewood_crates__is-terminal-0.4.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/is-terminal/0.4.17/download" + ], + "strip_prefix": "is-terminal-0.4.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"is_terminal\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=is-terminal\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.17\",\n)\n" + } + }, + "firewood_crates__is_terminal_polyfill-1.70.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download" + ], + "strip_prefix": "is_terminal_polyfill-1.70.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"is_terminal_polyfill\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=is_terminal_polyfill\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.70.2\",\n)\n" + } + }, + "firewood_crates__itertools-0.10.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itertools/0.10.5/download" + ], + "strip_prefix": "itertools-0.10.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itertools\",\n deps = [\n \"@firewood_crates__either-1.15.0//:either\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"use_alloc\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itertools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.5\",\n)\n" + } + }, + "firewood_crates__itertools-0.13.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itertools/0.13.0/download" + ], + "strip_prefix": "itertools-0.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itertools\",\n deps = [\n \"@firewood_crates__either-1.15.0//:either\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"use_alloc\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itertools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.0\",\n)\n" + } + }, + "firewood_crates__itertools-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itertools/0.14.0/download" + ], + "strip_prefix": "itertools-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itertools\",\n deps = [\n \"@firewood_crates__either-1.15.0//:either\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"use_alloc\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itertools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__itoa-1.0.15": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itoa/1.0.15/download" + ], + "strip_prefix": "itoa-1.0.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itoa\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itoa\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.15\",\n)\n" + } + }, + "firewood_crates__jiff-0.2.16": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jiff/0.2.16/download" + ], + "strip_prefix": "jiff-0.2.16", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jiff\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jiff\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.16\",\n)\n" + } + }, + "firewood_crates__jiff-static-0.2.16": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jiff-static/0.2.16/download" + ], + "strip_prefix": "jiff-static-0.2.16", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"jiff_static\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jiff-static\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.16\",\n)\n" + } + }, + "firewood_crates__jobserver-0.1.34": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jobserver/0.1.34/download" + ], + "strip_prefix": "jobserver-0.1.34", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jobserver\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jobserver\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.34\",\n)\n" + } + }, + "firewood_crates__js-sys-0.3.82": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/js-sys/0.3.82/download" + ], + "strip_prefix": "js-sys-0.3.82", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"js_sys\",\n deps = [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=js-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.82\",\n)\n" + } + }, + "firewood_crates__keccak-0.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/keccak/0.1.5/download" + ], + "strip_prefix": "keccak-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"keccak\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(target_arch = \"aarch64\")\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(target_arch = \"aarch64\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=keccak\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "firewood_crates__keccak-hasher-0.16.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "19ea4653859ca2266a86419d3f592d3f22e7a854b482f99180d2498507902048", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/keccak-hasher/0.16.0/download" + ], + "strip_prefix": "keccak-hasher-0.16.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"keccak_hasher\",\n deps = [\n \"@firewood_crates__hash-db-0.16.0//:hash_db\",\n \"@firewood_crates__hash256-std-hasher-0.15.2//:hash256_std_hasher\",\n \"@firewood_crates__tiny-keccak-2.0.2//:tiny_keccak\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=keccak-hasher\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.0\",\n)\n" + } + }, + "firewood_crates__lazy_static-1.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lazy_static/1.5.0/download" + ], + "strip_prefix": "lazy_static-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lazy_static\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lazy_static\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "firewood_crates__libc-0.2.177": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libc/0.2.177/download" + ], + "strip_prefix": "libc-0.2.177", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libc\",\n deps = [\n \"@firewood_crates__libc-0.2.177//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"extra_traits\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.177\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"extra_traits\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"libc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.177\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__libloading-0.8.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libloading/0.8.9/download" + ], + "strip_prefix": "libloading-0.8.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libloading\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libloading\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.9\",\n)\n" + } + }, + "firewood_crates__libm-0.2.15": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libm/0.2.15/download" + ], + "strip_prefix": "libm-0.2.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libm\",\n deps = [\n \"@firewood_crates__libm-0.2.15//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"arch\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.15\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"arch\",\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"libm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.15\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__linux-raw-sys-0.11.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/linux-raw-sys/0.11.0/download" + ], + "strip_prefix": "linux-raw-sys-0.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"linux_raw_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auxvec\",\n \"elf\",\n \"errno\",\n \"general\",\n \"ioctl\",\n \"no_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linux-raw-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.0\",\n)\n" + } + }, + "firewood_crates__litemap-0.8.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/litemap/0.8.1/download" + ], + "strip_prefix": "litemap-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"litemap\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=litemap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "firewood_crates__lock_api-0.4.14": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lock_api/0.4.14/download" + ], + "strip_prefix": "lock_api-0.4.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lock_api\",\n deps = [\n \"@firewood_crates__scopeguard-1.2.0//:scopeguard\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"atomic_usize\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lock_api\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.14\",\n)\n" + } + }, + "firewood_crates__log-0.4.28": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/log/0.4.28/download" + ], + "strip_prefix": "log-0.4.28", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"log\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=log\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.28\",\n)\n" + } + }, + "firewood_crates__lru-0.16.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lru/0.16.2/download" + ], + "strip_prefix": "lru-0.16.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lru\",\n deps = [\n \"@firewood_crates__hashbrown-0.16.0//:hashbrown\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"hashbrown\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lru\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.2\",\n)\n" + } + }, + "firewood_crates__lsm-tree-2.10.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lsm-tree/2.10.4/download" + ], + "strip_prefix": "lsm-tree-2.10.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lsm_tree\",\n deps = [\n \"@firewood_crates__byteorder-1.5.0//:byteorder\",\n \"@firewood_crates__crossbeam-skiplist-0.1.3//:crossbeam_skiplist\",\n \"@firewood_crates__double-ended-peekable-0.1.0//:double_ended_peekable\",\n \"@firewood_crates__guardian-1.3.0//:guardian\",\n \"@firewood_crates__interval-heap-0.0.5//:interval_heap\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__lz4_flex-0.11.5//:lz4_flex\",\n \"@firewood_crates__path-absolutize-3.1.1//:path_absolutize\",\n \"@firewood_crates__quick_cache-0.6.18//:quick_cache\",\n \"@firewood_crates__rustc-hash-2.1.1//:rustc_hash\",\n \"@firewood_crates__self_cell-1.2.1//:self_cell\",\n \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n \"@firewood_crates__value-log-1.9.0//:value_log\",\n \"@firewood_crates__varint-rs-2.2.0//:varint_rs\",\n \"@firewood_crates__xxhash-rust-0.8.15//:xxhash_rust\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__enum_dispatch-0.3.13//:enum_dispatch\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"lz4\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lsm-tree\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.10.4\",\n)\n" + } + }, + "firewood_crates__lz4_flex-0.11.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lz4_flex/0.11.5/download" + ], + "strip_prefix": "lz4_flex-0.11.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lz4_flex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lz4_flex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.5\",\n)\n" + } + }, + "firewood_crates__memchr-2.7.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/memchr/2.7.6/download" + ], + "strip_prefix": "memchr-2.7.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.7.6\",\n)\n" + } + }, + "firewood_crates__memmap2-0.9.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/memmap2/0.9.9/download" + ], + "strip_prefix": "memmap2-0.9.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memmap2\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memmap2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.9\",\n)\n" + } + }, + "firewood_crates__metrics-0.24.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/metrics/0.24.2/download" + ], + "strip_prefix": "metrics-0.24.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"metrics\",\n deps = [\n \"@firewood_crates__ahash-0.8.12//:ahash\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__portable-atomic-1.11.1//:portable_atomic\", # cfg(target_pointer_width = \"32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__portable-atomic-1.11.1//:portable_atomic\", # cfg(target_pointer_width = \"32\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=metrics\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.24.2\",\n)\n" + } + }, + "firewood_crates__metrics-exporter-prometheus-0.17.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/metrics-exporter-prometheus/0.17.2/download" + ], + "strip_prefix": "metrics-exporter-prometheus-0.17.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"metrics_exporter_prometheus\",\n deps = [\n \"@firewood_crates__base64-0.22.1//:base64\",\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\",\n \"@firewood_crates__hyper-1.8.1//:hyper\",\n \"@firewood_crates__hyper-rustls-0.27.7//:hyper_rustls\",\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__ipnet-2.11.0//:ipnet\",\n \"@firewood_crates__metrics-0.24.2//:metrics\",\n \"@firewood_crates__metrics-util-0.20.0//:metrics_util\",\n \"@firewood_crates__quanta-0.12.6//:quanta\",\n \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"_hyper-client\",\n \"_hyper-server\",\n \"async-runtime\",\n \"default\",\n \"http-body-util\",\n \"http-listener\",\n \"hyper\",\n \"hyper-rustls\",\n \"hyper-util\",\n \"ipnet\",\n \"push-gateway\",\n \"tokio\",\n \"tracing\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=metrics-exporter-prometheus\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.17.2\",\n)\n" + } + }, + "firewood_crates__metrics-util-0.20.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/metrics-util/0.20.0/download" + ], + "strip_prefix": "metrics-util-0.20.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"metrics_util\",\n deps = [\n \"@firewood_crates__aho-corasick-1.1.4//:aho_corasick\",\n \"@firewood_crates__crossbeam-epoch-0.9.18//:crossbeam_epoch\",\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@firewood_crates__hashbrown-0.15.5//:hashbrown\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__metrics-0.24.2//:metrics\",\n \"@firewood_crates__ordered-float-4.6.0//:ordered_float\",\n \"@firewood_crates__quanta-0.12.6//:quanta\",\n \"@firewood_crates__radix_trie-0.2.1//:radix_trie\",\n \"@firewood_crates__rand-0.9.2//:rand\",\n \"@firewood_crates__rand_xoshiro-0.7.0//:rand_xoshiro\",\n \"@firewood_crates__sketches-ddsketch-0.3.0//:sketches_ddsketch\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aho-corasick\",\n \"crossbeam-epoch\",\n \"crossbeam-utils\",\n \"debugging\",\n \"default\",\n \"hashbrown\",\n \"indexmap\",\n \"layer-filter\",\n \"layer-router\",\n \"layers\",\n \"ordered-float\",\n \"quanta\",\n \"radix_trie\",\n \"rand\",\n \"rand_xoshiro\",\n \"recency\",\n \"registry\",\n \"sketches-ddsketch\",\n \"storage\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=metrics-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.20.0\",\n)\n" + } + }, + "firewood_crates__minimal-lexical-0.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/minimal-lexical/0.2.1/download" + ], + "strip_prefix": "minimal-lexical-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"minimal_lexical\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=minimal-lexical\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "firewood_crates__miniz_oxide-0.8.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/miniz_oxide/0.8.9/download" + ], + "strip_prefix": "miniz_oxide-0.8.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"miniz_oxide\",\n deps = [\n \"@firewood_crates__adler2-2.0.1//:adler2\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=miniz_oxide\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.9\",\n)\n" + } + }, + "firewood_crates__mio-1.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/mio/1.1.0/download" + ], + "strip_prefix": "mio-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"mio\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(target_os = \"wasi\")\n \"@firewood_crates__wasi-0.11.1-wasi-snapshot-preview1//:wasi\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"net\",\n \"os-ext\",\n \"os-poll\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mio\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "firewood_crates__nibble_vec-0.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nibble_vec/0.1.0/download" + ], + "strip_prefix": "nibble_vec-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nibble_vec\",\n deps = [\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nibble_vec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "firewood_crates__nix-0.26.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nix/0.26.4/download" + ], + "strip_prefix": "nix-0.26.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nix\",\n deps = [\n \"@firewood_crates__bitflags-1.3.2//:bitflags\",\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"fs\",\n \"process\",\n \"signal\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.4\",\n)\n" + } + }, + "firewood_crates__nom-7.1.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nom/7.1.3/download" + ], + "strip_prefix": "nom-7.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nom\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__minimal-lexical-0.2.1//:minimal_lexical\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"7.1.3\",\n)\n" + } + }, + "firewood_crates__nonzero_ext-0.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nonzero_ext/0.3.0/download" + ], + "strip_prefix": "nonzero_ext-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nonzero_ext\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nonzero_ext\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "firewood_crates__normalize-line-endings-0.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/normalize-line-endings/0.3.0/download" + ], + "strip_prefix": "normalize-line-endings-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"normalize_line_endings\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=normalize-line-endings\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "firewood_crates__num-format-0.4.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-format/0.4.4/download" + ], + "strip_prefix": "num-format-0.4.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_format\",\n deps = [\n \"@firewood_crates__arrayvec-0.7.6//:arrayvec\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-format\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.4\",\n)\n" + } + }, + "firewood_crates__num-traits-0.2.19": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-traits/0.2.19/download" + ], + "strip_prefix": "num-traits-0.2.19", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_traits\",\n deps = [\n \"@firewood_crates__libm-0.2.15//:libm\",\n \"@firewood_crates__num-traits-0.2.19//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"libm\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.19\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"libm\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__autocfg-1.5.0//:autocfg\",\n ],\n edition = \"2021\",\n pkg_name = \"num-traits\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.19\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__object-0.37.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/object/0.37.3/download" + ], + "strip_prefix": "object-0.37.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"object\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__object-0.37.3//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"archive\",\n \"coff\",\n \"elf\",\n \"macho\",\n \"pe\",\n \"read_core\",\n \"unaligned\",\n \"xcoff\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=object\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.37.3\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"archive\",\n \"coff\",\n \"elf\",\n \"macho\",\n \"pe\",\n \"read_core\",\n \"unaligned\",\n \"xcoff\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"object\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=object\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.37.3\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__once_cell-1.21.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/once_cell/1.21.3/download" + ], + "strip_prefix": "once_cell-1.21.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"once_cell\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"race\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=once_cell\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.21.3\",\n)\n" + } + }, + "firewood_crates__once_cell_polyfill-1.70.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/once_cell_polyfill/1.70.2/download" + ], + "strip_prefix": "once_cell_polyfill-1.70.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"once_cell_polyfill\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=once_cell_polyfill\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.70.2\",\n)\n" + } + }, + "firewood_crates__oorandom-11.1.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/oorandom/11.1.5/download" + ], + "strip_prefix": "oorandom-11.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"oorandom\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=oorandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"11.1.5\",\n)\n" + } + }, + "firewood_crates__openssl-probe-0.1.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/openssl-probe/0.1.6/download" + ], + "strip_prefix": "openssl-probe-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"openssl_probe\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl-probe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "firewood_crates__opentelemetry-0.31.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/opentelemetry/0.31.0/download" + ], + "strip_prefix": "opentelemetry-0.31.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"opentelemetry\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"futures\",\n \"futures-core\",\n \"futures-sink\",\n \"internal-logs\",\n \"logs\",\n \"metrics\",\n \"pin-project-lite\",\n \"thiserror\",\n \"trace\",\n \"tracing\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=opentelemetry\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.0\",\n)\n" + } + }, + "firewood_crates__opentelemetry-http-0.31.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/opentelemetry-http/0.31.0/download" + ], + "strip_prefix": "opentelemetry-http-0.31.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"opentelemetry_http\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n \"@firewood_crates__reqwest-0.12.24//:reqwest\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__async-trait-0.1.89//:async_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"internal-logs\",\n \"reqwest\",\n \"reqwest-blocking\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=opentelemetry-http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.0\",\n)\n" + } + }, + "firewood_crates__opentelemetry-otlp-0.31.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/opentelemetry-otlp/0.31.0/download" + ], + "strip_prefix": "opentelemetry-otlp-0.31.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"opentelemetry_otlp\",\n deps = [\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n \"@firewood_crates__opentelemetry-http-0.31.0//:opentelemetry_http\",\n \"@firewood_crates__opentelemetry-proto-0.31.0//:opentelemetry_proto\",\n \"@firewood_crates__opentelemetry_sdk-0.31.0//:opentelemetry_sdk\",\n \"@firewood_crates__prost-0.14.1//:prost\",\n \"@firewood_crates__reqwest-0.12.24//:reqwest\",\n \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tonic-0.14.2//:tonic\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"grpc-tonic\",\n \"http\",\n \"http-proto\",\n \"internal-logs\",\n \"logs\",\n \"metrics\",\n \"opentelemetry-http\",\n \"prost\",\n \"reqwest\",\n \"reqwest-blocking-client\",\n \"tokio\",\n \"tonic\",\n \"trace\",\n \"tracing\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=opentelemetry-otlp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.0\",\n)\n" + } + }, + "firewood_crates__opentelemetry-proto-0.31.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/opentelemetry-proto/0.31.0/download" + ], + "strip_prefix": "opentelemetry-proto-0.31.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"opentelemetry_proto\",\n deps = [\n \"@firewood_crates__base64-0.22.1//:base64\",\n \"@firewood_crates__const-hex-1.17.0//:const_hex\",\n \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n \"@firewood_crates__opentelemetry_sdk-0.31.0//:opentelemetry_sdk\",\n \"@firewood_crates__prost-0.14.1//:prost\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_json-1.0.145//:serde_json\",\n \"@firewood_crates__tonic-0.14.2//:tonic\",\n \"@firewood_crates__tonic-prost-0.14.2//:tonic_prost\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"base64\",\n \"const-hex\",\n \"default\",\n \"full\",\n \"gen-tonic\",\n \"gen-tonic-messages\",\n \"internal-logs\",\n \"logs\",\n \"metrics\",\n \"prost\",\n \"serde\",\n \"serde_json\",\n \"tonic\",\n \"tonic-prost\",\n \"trace\",\n \"with-serde\",\n \"zpages\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=opentelemetry-proto\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.0\",\n)\n" + } + }, + "firewood_crates__opentelemetry_sdk-0.31.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/opentelemetry_sdk/0.31.0/download" + ], + "strip_prefix": "opentelemetry_sdk-0.31.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"opentelemetry_sdk\",\n deps = [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\",\n \"@firewood_crates__futures-executor-0.3.31//:futures_executor\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n \"@firewood_crates__opentelemetry-0.31.0//:opentelemetry\",\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\",\n \"@firewood_crates__rand-0.9.2//:rand\",\n \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"internal-logs\",\n \"logs\",\n \"metrics\",\n \"percent-encoding\",\n \"rand\",\n \"trace\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=opentelemetry_sdk\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.0\",\n)\n" + } + }, + "firewood_crates__ordered-float-4.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ordered-float/4.6.0/download" + ], + "strip_prefix": "ordered-float-4.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ordered_float\",\n deps = [\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ordered-float\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.6.0\",\n)\n" + } + }, + "firewood_crates__oxhttp-0.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "971c03797806d47915950e62816a8f365e3ddc8800310e3200ec666934d0f7c9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/oxhttp/0.3.1/download" + ], + "strip_prefix": "oxhttp-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"oxhttp\",\n deps = [\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__httparse-1.10.1//:httparse\",\n \"@firewood_crates__url-2.5.7//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"default\",\n \"server\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=oxhttp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "firewood_crates__parity-scale-codec-3.7.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parity-scale-codec/3.7.5/download" + ], + "strip_prefix": "parity-scale-codec-3.7.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parity_scale_codec\",\n deps = [\n \"@firewood_crates__arrayvec-0.7.6//:arrayvec\",\n \"@firewood_crates__byte-slice-cast-1.2.3//:byte_slice_cast\",\n \"@firewood_crates__const_format-0.2.35//:const_format\",\n \"@firewood_crates__parity-scale-codec-3.7.5//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__impl-trait-for-tuples-0.2.3//:impl_trait_for_tuples\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parity-scale-codec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.7.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"parity-scale-codec\",\n proc_macro_deps = [\n \"@firewood_crates__rustversion-1.0.22//:rustversion\",\n ],\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parity-scale-codec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"3.7.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__parity-scale-codec-derive-3.7.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parity-scale-codec-derive/3.7.5/download" + ], + "strip_prefix": "parity-scale-codec-derive-3.7.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"parity_scale_codec_derive\",\n deps = [\n \"@firewood_crates__proc-macro-crate-3.4.0//:proc_macro_crate\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parity-scale-codec-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.7.5\",\n)\n" + } + }, + "firewood_crates__parking_lot-0.12.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parking_lot/0.12.5/download" + ], + "strip_prefix": "parking_lot-0.12.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parking_lot\",\n deps = [\n \"@firewood_crates__lock_api-0.4.14//:lock_api\",\n \"@firewood_crates__parking_lot_core-0.9.12//:parking_lot_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.5\",\n)\n" + } + }, + "firewood_crates__parking_lot_core-0.9.12": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parking_lot_core/0.9.12/download" + ], + "strip_prefix": "parking_lot_core-0.9.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parking_lot_core\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__parking_lot_core-0.9.12//:build_script_build\",\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.12\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"parking_lot_core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.9.12\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__path-absolutize-3.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/path-absolutize/3.1.1/download" + ], + "strip_prefix": "path-absolutize-3.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"path_absolutize\",\n deps = [\n \"@firewood_crates__path-dedot-3.1.1//:path_dedot\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=path-absolutize\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.1.1\",\n)\n" + } + }, + "firewood_crates__path-dedot-3.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/path-dedot/3.1.1/download" + ], + "strip_prefix": "path-dedot-3.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"path_dedot\",\n deps = [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=path-dedot\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.1.1\",\n)\n" + } + }, + "firewood_crates__percent-encoding-2.3.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/percent-encoding/2.3.2/download" + ], + "strip_prefix": "percent-encoding-2.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"percent_encoding\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=percent-encoding\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.2\",\n)\n" + } + }, + "firewood_crates__pin-project-1.1.10": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project/1.1.10/download" + ], + "strip_prefix": "pin-project-1.1.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_project\",\n proc_macro_deps = [\n \"@firewood_crates__pin-project-internal-1.1.10//:pin_project_internal\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.10\",\n)\n" + } + }, + "firewood_crates__pin-project-internal-1.1.10": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project-internal/1.1.10/download" + ], + "strip_prefix": "pin-project-internal-1.1.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"pin_project_internal\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project-internal\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.10\",\n)\n" + } + }, + "firewood_crates__pin-project-lite-0.2.16": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project-lite/0.2.16/download" + ], + "strip_prefix": "pin-project-lite-0.2.16", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_project_lite\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project-lite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.16\",\n)\n" + } + }, + "firewood_crates__pin-utils-0.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-utils/0.1.0/download" + ], + "strip_prefix": "pin-utils-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_utils\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "firewood_crates__plain_hasher-0.2.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1e19e6491bdde87c2c43d70f4c194bc8a758f2eb732df00f61e43f7362e3b4cc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plain_hasher/0.2.3/download" + ], + "strip_prefix": "plain_hasher-0.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plain_hasher\",\n deps = [\n \"@firewood_crates__crunchy-0.2.4//:crunchy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plain_hasher\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.3\",\n)\n" + } + }, + "firewood_crates__plotters-0.3.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters/0.3.7/download" + ], + "strip_prefix": "plotters-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters\",\n deps = [\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n \"@firewood_crates__plotters-backend-0.3.7//:plotters_backend\",\n \"@firewood_crates__plotters-svg-0.3.7//:plotters_svg\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))\n \"@firewood_crates__web-sys-0.3.82//:web_sys\", # cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"area_series\",\n \"line_series\",\n \"plotters-svg\",\n \"svg_backend\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "firewood_crates__plotters-backend-0.3.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters-backend/0.3.7/download" + ], + "strip_prefix": "plotters-backend-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters_backend\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters-backend\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "firewood_crates__plotters-svg-0.3.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters-svg/0.3.7/download" + ], + "strip_prefix": "plotters-svg-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters_svg\",\n deps = [\n \"@firewood_crates__plotters-backend-0.3.7//:plotters_backend\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters-svg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "firewood_crates__pollster-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pollster/0.4.0/download" + ], + "strip_prefix": "pollster-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pollster\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pollster\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n" + } + }, + "firewood_crates__portable-atomic-1.11.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/portable-atomic/1.11.1/download" + ], + "strip_prefix": "portable-atomic-1.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"portable_atomic\",\n deps = [\n \"@firewood_crates__portable-atomic-1.11.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"fallback\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.11.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"fallback\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"portable-atomic\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.11.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__portable-atomic-util-0.2.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/portable-atomic-util/0.2.4/download" + ], + "strip_prefix": "portable-atomic-util-0.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"portable_atomic_util\",\n deps = [\n \"@firewood_crates__portable-atomic-1.11.1//:portable_atomic\",\n \"@firewood_crates__portable-atomic-util-0.2.4//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"portable-atomic-util\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__potential_utf-0.1.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/potential_utf/0.1.4/download" + ], + "strip_prefix": "potential_utf-0.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"potential_utf\",\n deps = [\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=potential_utf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.4\",\n)\n" + } + }, + "firewood_crates__pprof-0.15.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "38a01da47675efa7673b032bf8efd8214f1917d89685e07e395ab125ea42b187", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pprof/0.15.0/download" + ], + "strip_prefix": "pprof-0.15.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pprof\",\n deps = [\n \"@firewood_crates__aligned-vec-0.6.4//:aligned_vec\",\n \"@firewood_crates__backtrace-0.3.76//:backtrace\",\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__findshlibs-0.10.2//:findshlibs\",\n \"@firewood_crates__inferno-0.11.21//:inferno\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__nix-0.26.4//:nix\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__pprof-0.15.0//:build_script_build\",\n \"@firewood_crates__smallvec-1.15.1//:smallvec\",\n \"@firewood_crates__spin-0.10.0//:spin\",\n \"@firewood_crates__symbolic-demangle-12.17.0//:symbolic_demangle\",\n \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n \"@firewood_crates__thiserror-2.0.17//:thiserror\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cpp\",\n \"default\",\n \"flamegraph\",\n \"inferno\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pprof\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.15.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cpp\",\n \"default\",\n \"flamegraph\",\n \"inferno\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"pprof\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pprof\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.15.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__ppv-lite86-0.2.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ppv-lite86/0.2.21/download" + ], + "strip_prefix": "ppv-lite86-0.2.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ppv_lite86\",\n deps = [\n \"@firewood_crates__zerocopy-0.8.27//:zerocopy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"simd\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ppv-lite86\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.21\",\n)\n" + } + }, + "firewood_crates__predicates-3.1.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/predicates/3.1.3/download" + ], + "strip_prefix": "predicates-3.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"predicates\",\n deps = [\n \"@firewood_crates__anstyle-1.0.13//:anstyle\",\n \"@firewood_crates__difflib-0.4.0//:difflib\",\n \"@firewood_crates__float-cmp-0.10.0//:float_cmp\",\n \"@firewood_crates__normalize-line-endings-0.3.0//:normalize_line_endings\",\n \"@firewood_crates__predicates-core-1.0.9//:predicates_core\",\n \"@firewood_crates__regex-1.12.2//:regex\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"color\",\n \"default\",\n \"diff\",\n \"float-cmp\",\n \"normalize-line-endings\",\n \"regex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=predicates\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.1.3\",\n)\n" + } + }, + "firewood_crates__predicates-core-1.0.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/predicates-core/1.0.9/download" + ], + "strip_prefix": "predicates-core-1.0.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"predicates_core\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=predicates-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.9\",\n)\n" + } + }, + "firewood_crates__predicates-tree-1.0.12": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/predicates-tree/1.0.12/download" + ], + "strip_prefix": "predicates-tree-1.0.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"predicates_tree\",\n deps = [\n \"@firewood_crates__predicates-core-1.0.9//:predicates_core\",\n \"@firewood_crates__termtree-0.5.1//:termtree\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=predicates-tree\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.12\",\n)\n" + } + }, + "firewood_crates__pretty-duration-0.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d8868e7264af614b3634ff0abbe37b178e61000611b8a75221aea40221924aba", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pretty-duration/0.1.1/download" + ], + "strip_prefix": "pretty-duration-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pretty_duration\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pretty-duration\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "firewood_crates__prettyplease-0.2.37": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prettyplease/0.2.37/download" + ], + "strip_prefix": "prettyplease-0.2.37", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"prettyplease\",\n deps = [\n \"@firewood_crates__prettyplease-0.2.37//:build_script_build\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prettyplease\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.37\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"prettyplease02\",\n pkg_name = \"prettyplease\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prettyplease\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.37\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__primitive-types-0.14.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/primitive-types/0.14.0/download" + ], + "strip_prefix": "primitive-types-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"primitive_types\",\n deps = [\n \"@firewood_crates__fixed-hash-0.8.0//:fixed_hash\",\n \"@firewood_crates__impl-rlp-0.4.0//:impl_rlp\",\n \"@firewood_crates__impl-serde-0.5.0//:impl_serde\",\n \"@firewood_crates__uint-0.10.0//:uint\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"impl-rlp\",\n \"impl-serde\",\n \"rand\",\n \"rlp\",\n \"rustc-hex\",\n \"serde_no_std\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=primitive-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "firewood_crates__proc-macro-crate-3.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro-crate/3.4.0/download" + ], + "strip_prefix": "proc-macro-crate-3.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro_crate\",\n deps = [\n \"@firewood_crates__toml_edit-0.23.7//:toml_edit\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro-crate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.4.0\",\n)\n" + } + }, + "firewood_crates__proc-macro-error-attr2-2.0.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro-error-attr2/2.0.0/download" + ], + "strip_prefix": "proc-macro-error-attr2-2.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"proc_macro_error_attr2\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro-error-attr2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.0\",\n)\n" + } + }, + "firewood_crates__proc-macro-error2-2.0.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro-error2/2.0.1/download" + ], + "strip_prefix": "proc-macro-error2-2.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro_error2\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__proc-macro-error-attr2-2.0.0//:proc_macro_error_attr2\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"syn-error\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro-error2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.1\",\n)\n" + } + }, + "firewood_crates__proc-macro2-1.0.103": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro2/1.0.103/download" + ], + "strip_prefix": "proc-macro2-1.0.103", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro2\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:build_script_build\",\n \"@firewood_crates__unicode-ident-1.0.22//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.103\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"proc-macro2\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.103\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__proptest-1.9.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proptest/1.9.0/download" + ], + "strip_prefix": "proptest-1.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proptest\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n \"@firewood_crates__rand-0.9.2//:rand\",\n \"@firewood_crates__rand_chacha-0.9.0//:rand_chacha\",\n \"@firewood_crates__rand_xorshift-0.4.0//:rand_xorshift\",\n \"@firewood_crates__unarray-0.1.4//:unarray\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proptest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.9.0\",\n)\n" + } + }, + "firewood_crates__prost-0.14.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prost/0.14.1/download" + ], + "strip_prefix": "prost-0.14.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"prost\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__prost-derive-0.14.1//:prost_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"derive\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prost\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.1\",\n)\n" + } + }, + "firewood_crates__prost-derive-0.14.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prost-derive/0.14.1/download" + ], + "strip_prefix": "prost-derive-0.14.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"prost_derive\",\n deps = [\n \"@firewood_crates__anyhow-1.0.100//:anyhow\",\n \"@firewood_crates__itertools-0.14.0//:itertools\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prost-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.1\",\n)\n" + } + }, + "firewood_crates__quanta-0.12.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quanta/0.12.6/download" + ], + "strip_prefix": "quanta-0.12.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quanta\",\n deps = [\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"windows\", target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"windows\", target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__web-sys-0.3.82//:web_sys\", # cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__wasi-0.11.1-wasi-snapshot-preview1//:wasi\", # cfg(all(target_arch = \"wasm32\", target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__raw-cpuid-11.6.0//:raw_cpuid\", # cfg(target_arch = \"x86_64\")\n \"@firewood_crates__winapi-0.3.9//:winapi\", # cfg(target_os = \"windows\")\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"windows\", target_arch = \"wasm32\")))\n \"@firewood_crates__raw-cpuid-11.6.0//:raw_cpuid\", # cfg(target_arch = \"x86_64\")\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(not(any(target_os = \"windows\", target_arch = \"wasm32\")))\n \"@firewood_crates__raw-cpuid-11.6.0//:raw_cpuid\", # cfg(target_arch = \"x86_64\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quanta\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.6\",\n)\n" + } + }, + "firewood_crates__quick-xml-0.26.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quick-xml/0.26.0/download" + ], + "strip_prefix": "quick-xml-0.26.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quick_xml\",\n deps = [\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quick-xml\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.0\",\n)\n" + } + }, + "firewood_crates__quick_cache-0.6.18": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quick_cache/0.6.18/download" + ], + "strip_prefix": "quick_cache-0.6.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quick_cache\",\n deps = [\n \"@firewood_crates__equivalent-1.0.2//:equivalent\",\n \"@firewood_crates__hashbrown-0.16.0//:hashbrown\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quick_cache\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.18\",\n)\n" + } + }, + "firewood_crates__quote-1.0.42": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quote/1.0.42/download" + ], + "strip_prefix": "quote-1.0.42", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quote\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.42\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"quote\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.42\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__r-efi-5.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/r-efi/5.3.0/download" + ], + "strip_prefix": "r-efi-5.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"r_efi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=r-efi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"5.3.0\",\n)\n" + } + }, + "firewood_crates__radium-0.7.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/radium/0.7.0/download" + ], + "strip_prefix": "radium-0.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"radium\",\n deps = [\n \"@firewood_crates__radium-0.7.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=radium\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"radium\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=radium\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.7.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__radix_trie-0.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/radix_trie/0.2.1/download" + ], + "strip_prefix": "radix_trie-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"radix_trie\",\n deps = [\n \"@firewood_crates__endian-type-0.1.2//:endian_type\",\n \"@firewood_crates__nibble_vec-0.1.0//:nibble_vec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=radix_trie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "firewood_crates__rand-0.8.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand/0.8.5/download" + ], + "strip_prefix": "rand-0.8.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand\",\n deps = [\n \"@firewood_crates__rand_chacha-0.3.1//:rand_chacha\",\n \"@firewood_crates__rand_core-0.6.4//:rand_core\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"getrandom\",\n \"libc\",\n \"rand_chacha\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.5\",\n)\n" + } + }, + "firewood_crates__rand-0.9.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand/0.9.2/download" + ], + "strip_prefix": "rand-0.9.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand\",\n deps = [\n \"@firewood_crates__rand_chacha-0.9.0//:rand_chacha\",\n \"@firewood_crates__rand_core-0.9.3//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"os_rng\",\n \"small_rng\",\n \"std\",\n \"std_rng\",\n \"thread_rng\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.2\",\n)\n" + } + }, + "firewood_crates__rand_chacha-0.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_chacha/0.3.1/download" + ], + "strip_prefix": "rand_chacha-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_chacha\",\n deps = [\n \"@firewood_crates__ppv-lite86-0.2.21//:ppv_lite86\",\n \"@firewood_crates__rand_core-0.6.4//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_chacha\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "firewood_crates__rand_chacha-0.9.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_chacha/0.9.0/download" + ], + "strip_prefix": "rand_chacha-0.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_chacha\",\n deps = [\n \"@firewood_crates__ppv-lite86-0.2.21//:ppv_lite86\",\n \"@firewood_crates__rand_core-0.9.3//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_chacha\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.0\",\n)\n" + } + }, + "firewood_crates__rand_core-0.6.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_core/0.6.4/download" + ], + "strip_prefix": "rand_core-0.6.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_core\",\n deps = [\n \"@firewood_crates__getrandom-0.2.16//:getrandom\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"getrandom\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.4\",\n)\n" + } + }, + "firewood_crates__rand_core-0.9.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_core/0.9.3/download" + ], + "strip_prefix": "rand_core-0.9.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_core\",\n deps = [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"os_rng\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.3\",\n)\n" + } + }, + "firewood_crates__rand_distr-0.5.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_distr/0.5.1/download" + ], + "strip_prefix": "rand_distr-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_distr\",\n deps = [\n \"@firewood_crates__num-traits-0.2.19//:num_traits\",\n \"@firewood_crates__rand-0.9.2//:rand\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_distr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "firewood_crates__rand_xorshift-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_xorshift/0.4.0/download" + ], + "strip_prefix": "rand_xorshift-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_xorshift\",\n deps = [\n \"@firewood_crates__rand_core-0.9.3//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_xorshift\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n" + } + }, + "firewood_crates__rand_xoshiro-0.7.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_xoshiro/0.7.0/download" + ], + "strip_prefix": "rand_xoshiro-0.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_xoshiro\",\n deps = [\n \"@firewood_crates__rand_core-0.9.3//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_xoshiro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.0\",\n)\n" + } + }, + "firewood_crates__raw-cpuid-11.6.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/raw-cpuid/11.6.0/download" + ], + "strip_prefix": "raw-cpuid-11.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"raw_cpuid\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=raw-cpuid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"11.6.0\",\n)\n" + } + }, + "firewood_crates__rayon-1.11.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rayon/1.11.0/download" + ], + "strip_prefix": "rayon-1.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rayon\",\n deps = [\n \"@firewood_crates__either-1.15.0//:either\",\n \"@firewood_crates__rayon-core-1.13.0//:rayon_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.11.0\",\n)\n" + } + }, + "firewood_crates__rayon-core-1.13.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rayon-core/1.13.0/download" + ], + "strip_prefix": "rayon-core-1.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rayon_core\",\n deps = [\n \"@firewood_crates__crossbeam-deque-0.8.6//:crossbeam_deque\",\n \"@firewood_crates__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@firewood_crates__rayon-core-1.13.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.13.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"rayon-core\",\n pkg_name = \"rayon-core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.13.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__redox_syscall-0.5.18": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/redox_syscall/0.5.18/download" + ], + "strip_prefix": "redox_syscall-0.5.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syscall\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=redox_syscall\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.18\",\n)\n" + } + }, + "firewood_crates__regex-1.12.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex/1.12.2/download" + ], + "strip_prefix": "regex-1.12.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex\",\n deps = [\n \"@firewood_crates__aho-corasick-1.1.4//:aho_corasick\",\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__regex-automata-0.4.13//:regex_automata\",\n \"@firewood_crates__regex-syntax-0.8.8//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"perf\",\n \"perf-backtrack\",\n \"perf-cache\",\n \"perf-dfa\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-onepass\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.12.2\",\n)\n" + } + }, + "firewood_crates__regex-automata-0.4.13": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-automata/0.4.13/download" + ], + "strip_prefix": "regex-automata-0.4.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_automata\",\n deps = [\n \"@firewood_crates__aho-corasick-1.1.4//:aho_corasick\",\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__regex-syntax-0.8.8//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"dfa-onepass\",\n \"dfa-search\",\n \"hybrid\",\n \"meta\",\n \"nfa-backtrack\",\n \"nfa-pikevm\",\n \"nfa-thompson\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-literal-multisubstring\",\n \"perf-literal-substring\",\n \"std\",\n \"syntax\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n \"unicode-word-boundary\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-automata\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.13\",\n)\n" + } + }, + "firewood_crates__regex-syntax-0.8.8": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-syntax/0.8.8/download" + ], + "strip_prefix": "regex-syntax-0.8.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_syntax\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-syntax\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.8\",\n)\n" + } + }, + "firewood_crates__reqwest-0.12.24": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/reqwest/0.12.24/download" + ], + "strip_prefix": "reqwest-0.12.24", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"reqwest\",\n deps = [\n \"@firewood_crates__base64-0.22.1//:base64\",\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_urlencoded-0.7.1//:serde_urlencoded\",\n \"@firewood_crates__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@firewood_crates__url-2.5.7//:url\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\", # aarch64-apple-darwin\n \"@firewood_crates__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tokio-1.48.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-0.5.2//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-http-0.6.6//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\", # aarch64-unknown-linux-gnu\n \"@firewood_crates__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tokio-1.48.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-0.5.2//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-http-0.6.6//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__serde_json-1.0.145//:serde_json\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__wasm-bindgen-futures-0.4.55//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__web-sys-0.3.82//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__serde_json-1.0.145//:serde_json\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__wasm-bindgen-futures-0.4.55//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@firewood_crates__web-sys-0.3.82//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\", # x86_64-pc-windows-msvc\n \"@firewood_crates__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tokio-1.48.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-0.5.2//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-http-0.6.6//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\", # x86_64-unknown-linux-gnu\n \"@firewood_crates__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tokio-1.48.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-0.5.2//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-http-0.6.6//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__futures-channel-0.3.31//:futures_channel\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@firewood_crates__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__log-0.4.28//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tokio-1.48.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-0.5.2//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-http-0.6.6//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@firewood_crates__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"blocking\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=reqwest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.24\",\n)\n" + } + }, + "firewood_crates__rgb-0.8.52": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rgb/0.8.52/download" + ], + "strip_prefix": "rgb-0.8.52", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rgb\",\n deps = [\n \"@firewood_crates__bytemuck-1.24.0//:bytemuck\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"argb\",\n \"as-bytes\",\n \"bytemuck\",\n \"default\",\n \"grb\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rgb\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.52\",\n)\n" + } + }, + "firewood_crates__ring-0.17.14": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ring/0.17.14/download" + ], + "strip_prefix": "ring-0.17.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ring\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__getrandom-0.2.16//:getrandom\",\n \"@firewood_crates__ring-0.17.14//:build_script_build\",\n \"@firewood_crates__untrusted-0.9.0//:untrusted\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(all(target_arch = \"aarch64\", target_endian = \"little\"), target_vendor = \"apple\", any(target_os = \"ios\", target_os = \"macos\", target_os = \"tvos\", target_os = \"visionos\", target_os = \"watchos\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(any(all(target_arch = \"aarch64\", target_endian = \"little\"), all(target_arch = \"arm\", target_endian = \"little\")), any(target_os = \"android\", target_os = \"linux\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.17.14\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n ],\n edition = \"2021\",\n links = \"ring_core_0_17_14_\",\n pkg_name = \"ring\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.17.14\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__rlp-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rlp/0.6.1/download" + ], + "strip_prefix": "rlp-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rlp\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__rustc-hex-2.1.0//:rustc_hex\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rlp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__rtrb-0.3.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rtrb/0.3.2/download" + ], + "strip_prefix": "rtrb-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rtrb\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rtrb\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "firewood_crates__rustc-demangle-0.1.26": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustc-demangle/0.1.26/download" + ], + "strip_prefix": "rustc-demangle-0.1.26", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustc_demangle\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustc-demangle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.26\",\n)\n" + } + }, + "firewood_crates__rustc-hash-2.1.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustc-hash/2.1.1/download" + ], + "strip_prefix": "rustc-hash-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustc_hash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustc-hash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "firewood_crates__rustc-hex-2.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustc-hex/2.1.0/download" + ], + "strip_prefix": "rustc-hex-2.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustc_hex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustc-hex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.0\",\n)\n" + } + }, + "firewood_crates__rustix-1.1.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustix/1.1.2/download" + ], + "strip_prefix": "rustix-1.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustix\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__rustix-1.1.2//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__errno-0.3.14//:errno\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__linux-raw-sys-0.11.0//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__errno-0.3.14//:errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__errno-0.3.14//:errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__errno-0.3.14//:errno\", # cfg(windows)\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__linux-raw-sys-0.11.0//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__linux-raw-sys-0.11.0//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"//conditions:default\": [],\n }),\n aliases = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": {\n \"@firewood_crates__errno-0.3.14//:errno\": \"libc_errno\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n },\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": {\n \"@firewood_crates__errno-0.3.14//:errno\": \"libc_errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n },\n \"@rules_rust//rust/platform:wasm32-wasip1\": {\n \"@firewood_crates__errno-0.3.14//:errno\": \"libc_errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n },\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": {\n \"@firewood_crates__errno-0.3.14//:errno\": \"libc_errno\", # cfg(windows)\n },\n \"//conditions:default\": {},\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"fs\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"fs\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"rustix\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.1.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__rustls-0.23.35": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls/0.23.35/download" + ], + "strip_prefix": "rustls-0.23.35", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls\",\n deps = [\n \"@firewood_crates__aws-lc-rs-1.15.0//:aws_lc_rs\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__rustls-0.23.35//:build_script_build\",\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\",\n \"@firewood_crates__rustls-webpki-0.103.8//:webpki\",\n \"@firewood_crates__subtle-2.6.1//:subtle\",\n \"@firewood_crates__zeroize-1.8.2//:zeroize\",\n ],\n aliases = {\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"aws_lc_rs\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.23.35\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"aws_lc_rs\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@firewood_crates__aws-lc-rs-1.15.0//:aws_lc_rs\",\n ],\n edition = \"2021\",\n pkg_name = \"rustls\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.23.35\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__rustls-native-certs-0.8.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-native-certs/0.8.2/download" + ], + "strip_prefix": "rustls-native-certs-0.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_native_certs\",\n deps = [\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__security-framework-3.5.1//:security_framework\", # cfg(target_os = \"macos\")\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__openssl-probe-0.1.6//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__schannel-0.1.28//:schannel\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__openssl-probe-0.1.6//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__openssl-probe-0.1.6//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"//conditions:default\": [],\n }),\n aliases = {\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-native-certs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.2\",\n)\n" + } + }, + "firewood_crates__rustls-pki-types-1.13.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-pki-types/1.13.0/download" + ], + "strip_prefix": "rustls-pki-types-1.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_pki_types\",\n deps = [\n \"@firewood_crates__zeroize-1.8.2//:zeroize\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-pki-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.13.0\",\n)\n" + } + }, + "firewood_crates__rustls-webpki-0.103.8": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-webpki/0.103.8/download" + ], + "strip_prefix": "rustls-webpki-0.103.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"webpki\",\n deps = [\n \"@firewood_crates__aws-lc-rs-1.15.0//:aws_lc_rs\",\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\",\n \"@firewood_crates__untrusted-0.9.0//:untrusted\",\n ],\n aliases = {\n \"@firewood_crates__rustls-pki-types-1.13.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"aws-lc-rs\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-webpki\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.103.8\",\n)\n" + } + }, + "firewood_crates__rustversion-1.0.22": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustversion/1.0.22/download" + ], + "strip_prefix": "rustversion-1.0.22", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"rustversion\",\n deps = [\n \"@firewood_crates__rustversion-1.0.22//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.22\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build/build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"rustversion\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.22\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__ryu-1.0.20": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ryu/1.0.20/download" + ], + "strip_prefix": "ryu-1.0.20", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ryu\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ryu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.20\",\n)\n" + } + }, + "firewood_crates__same-file-1.0.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/same-file/1.0.6/download" + ], + "strip_prefix": "same-file-1.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"same_file\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__winapi-util-0.1.11//:winapi_util\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=same-file\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6\",\n)\n" + } + }, + "firewood_crates__scc-2.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/scc/2.4.0/download" + ], + "strip_prefix": "scc-2.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"scc\",\n deps = [\n \"@firewood_crates__sdd-3.0.10//:sdd\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=scc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.4.0\",\n)\n" + } + }, + "firewood_crates__schannel-0.1.28": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/schannel/0.1.28/download" + ], + "strip_prefix": "schannel-0.1.28", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"schannel\",\n deps = [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=schannel\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.28\",\n)\n" + } + }, + "firewood_crates__scopeguard-1.2.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/scopeguard/1.2.0/download" + ], + "strip_prefix": "scopeguard-1.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"scopeguard\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=scopeguard\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.0\",\n)\n" + } + }, + "firewood_crates__sdd-3.0.10": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sdd/3.0.10/download" + ], + "strip_prefix": "sdd-3.0.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sdd\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sdd\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.0.10\",\n)\n" + } + }, + "firewood_crates__security-framework-3.5.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/security-framework/3.5.1/download" + ], + "strip_prefix": "security-framework-3.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"security_framework\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__core-foundation-0.10.1//:core_foundation\",\n \"@firewood_crates__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__security-framework-sys-2.15.0//:security_framework_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"OSX_10_12\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=security-framework\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.5.1\",\n)\n" + } + }, + "firewood_crates__security-framework-sys-2.15.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/security-framework-sys/2.15.0/download" + ], + "strip_prefix": "security-framework-sys-2.15.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"security_framework_sys\",\n deps = [\n \"@firewood_crates__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@firewood_crates__libc-0.2.177//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"OSX_10_10\",\n \"OSX_10_11\",\n \"OSX_10_12\",\n \"OSX_10_9\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=security-framework-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.15.0\",\n)\n" + } + }, + "firewood_crates__self_cell-1.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/self_cell/1.2.1/download" + ], + "strip_prefix": "self_cell-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"self_cell\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=self_cell\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "firewood_crates__semver-1.0.27": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/semver/1.0.27/download" + ], + "strip_prefix": "semver-1.0.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"semver\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=semver\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.27\",\n)\n" + } + }, + "firewood_crates__serde-1.0.228": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde/1.0.228/download" + ], + "strip_prefix": "serde-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde\",\n deps = [\n \"@firewood_crates__serde-1.0.228//:build_script_build\",\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"derive\",\n \"serde_derive\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"derive\",\n \"serde_derive\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__serde_core-1.0.228": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_core/1.0.228/download" + ], + "strip_prefix": "serde_core-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_core\",\n deps = [\n \"@firewood_crates__serde_core-1.0.228//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"result\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"result\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__serde_derive-1.0.228": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_derive/1.0.228/download" + ], + "strip_prefix": "serde_derive-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"serde_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n" + } + }, + "firewood_crates__serde_json-1.0.145": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_json/1.0.145/download" + ], + "strip_prefix": "serde_json-1.0.145", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_json\",\n deps = [\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__memchr-2.7.6//:memchr\",\n \"@firewood_crates__ryu-1.0.20//:ryu\",\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n \"@firewood_crates__serde_json-1.0.145//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.145\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_json\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.145\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__serde_spanned-1.0.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_spanned/1.0.3/download" + ], + "strip_prefix": "serde_spanned-1.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_spanned\",\n deps = [\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_spanned\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.3\",\n)\n" + } + }, + "firewood_crates__serde_urlencoded-0.7.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_urlencoded/0.7.1/download" + ], + "strip_prefix": "serde_urlencoded-0.7.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_urlencoded\",\n deps = [\n \"@firewood_crates__form_urlencoded-1.2.2//:form_urlencoded\",\n \"@firewood_crates__itoa-1.0.15//:itoa\",\n \"@firewood_crates__ryu-1.0.20//:ryu\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_urlencoded\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.1\",\n)\n" + } + }, + "firewood_crates__serial_test-3.2.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serial_test/3.2.0/download" + ], + "strip_prefix": "serial_test-3.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serial_test\",\n deps = [\n \"@firewood_crates__futures-0.3.31//:futures\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__parking_lot-0.12.5//:parking_lot\",\n \"@firewood_crates__scc-2.4.0//:scc\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__serial_test_derive-3.2.0//:serial_test_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async\",\n \"default\",\n \"logging\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serial_test\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.2.0\",\n)\n" + } + }, + "firewood_crates__serial_test_derive-3.2.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serial_test_derive/3.2.0/download" + ], + "strip_prefix": "serial_test_derive-3.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"serial_test_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serial_test_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.2.0\",\n)\n" + } + }, + "firewood_crates__sha2-0.10.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sha2/0.10.9/download" + ], + "strip_prefix": "sha2-0.10.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sha2\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__digest-0.10.7//:digest\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sha2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.9\",\n)\n" + } + }, + "firewood_crates__sha3-0.10.8": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sha3/0.10.8/download" + ], + "strip_prefix": "sha3-0.10.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sha3\",\n deps = [\n \"@firewood_crates__digest-0.10.7//:digest\",\n \"@firewood_crates__keccak-0.1.5//:keccak\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sha3\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.8\",\n)\n" + } + }, + "firewood_crates__shlex-1.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/shlex/1.3.0/download" + ], + "strip_prefix": "shlex-1.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"shlex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=shlex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.0\",\n)\n" + } + }, + "firewood_crates__sketches-ddsketch-0.3.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sketches-ddsketch/0.3.0/download" + ], + "strip_prefix": "sketches-ddsketch-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sketches_ddsketch\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sketches-ddsketch\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "firewood_crates__slab-0.4.11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/slab/0.4.11/download" + ], + "strip_prefix": "slab-0.4.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"slab\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=slab\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.11\",\n)\n" + } + }, + "firewood_crates__small_ctor-0.1.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "88414a5ca1f85d82cc34471e975f0f74f6aa54c40f062efa42c0080e7f763f81", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/small_ctor/0.1.2/download" + ], + "strip_prefix": "small_ctor-0.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"small_ctor\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=small_ctor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n" + } + }, + "firewood_crates__smallvec-1.15.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/smallvec/1.15.1/download" + ], + "strip_prefix": "smallvec-1.15.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"smallvec\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"const_generics\",\n \"const_new\",\n \"union\",\n \"write\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=smallvec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.15.1\",\n)\n" + } + }, + "firewood_crates__socket2-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/socket2/0.6.1/download" + ], + "strip_prefix": "socket2-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"socket2\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.60.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"all\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=socket2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__spin-0.10.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spin/0.10.0/download" + ], + "strip_prefix": "spin-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spin\",\n deps = [\n \"@firewood_crates__lock_api-0.4.14//:lock_api\",\n ],\n aliases = {\n \"@firewood_crates__lock_api-0.4.14//:lock_api\": \"lock_api_crate\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"barrier\",\n \"default\",\n \"lazy\",\n \"lock_api\",\n \"mutex\",\n \"once\",\n \"rwlock\",\n \"spin_mutex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spin\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "firewood_crates__stable_deref_trait-1.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/stable_deref_trait/1.2.1/download" + ], + "strip_prefix": "stable_deref_trait-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"stable_deref_trait\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=stable_deref_trait\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "firewood_crates__static_assertions-1.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/static_assertions/1.1.0/download" + ], + "strip_prefix": "static_assertions-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"static_assertions\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=static_assertions\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "firewood_crates__std-semaphore-0.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/std-semaphore/0.1.0/download" + ], + "strip_prefix": "std-semaphore-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"std_semaphore\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=std-semaphore\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "firewood_crates__str_stack-0.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/str_stack/0.1.0/download" + ], + "strip_prefix": "str_stack-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"str_stack\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=str_stack\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "firewood_crates__strsim-0.11.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/strsim/0.11.1/download" + ], + "strip_prefix": "strsim-0.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"strsim\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=strsim\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1\",\n)\n" + } + }, + "firewood_crates__subtle-2.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/subtle/2.6.1/download" + ], + "strip_prefix": "subtle-2.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"subtle\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=subtle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.6.1\",\n)\n" + } + }, + "firewood_crates__symbolic-common-12.17.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/symbolic-common/12.17.0/download" + ], + "strip_prefix": "symbolic-common-12.17.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"symbolic_common\",\n deps = [\n \"@firewood_crates__debugid-0.8.0//:debugid\",\n \"@firewood_crates__memmap2-0.9.9//:memmap2\",\n \"@firewood_crates__stable_deref_trait-1.2.1//:stable_deref_trait\",\n \"@firewood_crates__uuid-1.18.1//:uuid\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=symbolic-common\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"12.17.0\",\n)\n" + } + }, + "firewood_crates__symbolic-demangle-12.17.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/symbolic-demangle/12.17.0/download" + ], + "strip_prefix": "symbolic-demangle-12.17.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"symbolic_demangle\",\n deps = [\n \"@firewood_crates__cpp_demangle-0.4.5//:cpp_demangle\",\n \"@firewood_crates__rustc-demangle-0.1.26//:rustc_demangle\",\n \"@firewood_crates__symbolic-common-12.17.0//:symbolic_common\",\n \"@firewood_crates__symbolic-demangle-12.17.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cpp\",\n \"cpp_demangle\",\n \"rust\",\n \"rustc-demangle\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=symbolic-demangle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"12.17.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cpp\",\n \"cpp_demangle\",\n \"rust\",\n \"rustc-demangle\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"symbolic-demangle\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=symbolic-demangle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"12.17.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__syn-2.0.110": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/syn/2.0.110/download" + ], + "strip_prefix": "syn-2.0.110", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syn\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__unicode-ident-1.0.22//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"default\",\n \"derive\",\n \"extra-traits\",\n \"fold\",\n \"full\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n \"visit\",\n \"visit-mut\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.110\",\n)\n" + } + }, + "firewood_crates__sync_wrapper-1.0.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sync_wrapper/1.0.2/download" + ], + "strip_prefix": "sync_wrapper-1.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sync_wrapper\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"futures\",\n \"futures-core\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sync_wrapper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2\",\n)\n" + } + }, + "firewood_crates__synstructure-0.13.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/synstructure/0.13.2/download" + ], + "strip_prefix": "synstructure-0.13.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"synstructure\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=synstructure\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.2\",\n)\n" + } + }, + "firewood_crates__tap-1.0.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tap/1.0.1/download" + ], + "strip_prefix": "tap-1.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tap\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1\",\n)\n" + } + }, + "firewood_crates__target-triple-1.0.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/target-triple/1.0.0/download" + ], + "strip_prefix": "target-triple-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"target_triple\",\n deps = [\n \"@firewood_crates__target-triple-1.0.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=target-triple\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"target-triple\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=target-triple\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__tempfile-3.23.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tempfile/3.23.0/download" + ], + "strip_prefix": "tempfile-3.23.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tempfile\",\n deps = [\n \"@firewood_crates__fastrand-2.3.0//:fastrand\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # aarch64-apple-darwin\n \"@firewood_crates__rustix-1.1.2//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # aarch64-unknown-linux-gnu\n \"@firewood_crates__rustix-1.1.2//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # wasm32-wasip1\n \"@firewood_crates__rustix-1.1.2//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # x86_64-pc-windows-msvc\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # x86_64-unknown-linux-gnu\n \"@firewood_crates__rustix-1.1.2//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__getrandom-0.3.4//:getrandom\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@firewood_crates__rustix-1.1.2//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"getrandom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tempfile\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.23.0\",\n)\n" + } + }, + "firewood_crates__termcolor-1.4.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/termcolor/1.4.1/download" + ], + "strip_prefix": "termcolor-1.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"termcolor\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__winapi-util-0.1.11//:winapi_util\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=termcolor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.4.1\",\n)\n" + } + }, + "firewood_crates__termtree-0.5.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/termtree/0.5.1/download" + ], + "strip_prefix": "termtree-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"termtree\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=termtree\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "firewood_crates__test-case-3.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/test-case/3.3.1/download" + ], + "strip_prefix": "test-case-3.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"test_case\",\n proc_macro_deps = [\n \"@firewood_crates__test-case-macros-3.3.1//:test_case_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=test-case\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.3.1\",\n)\n" + } + }, + "firewood_crates__test-case-core-3.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/test-case-core/3.3.1/download" + ], + "strip_prefix": "test-case-core-3.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"test_case_core\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=test-case-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.3.1\",\n)\n" + } + }, + "firewood_crates__test-case-macros-3.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/test-case-macros/3.3.1/download" + ], + "strip_prefix": "test-case-macros-3.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"test_case_macros\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n \"@firewood_crates__test-case-core-3.3.1//:test_case_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=test-case-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.3.1\",\n)\n" + } + }, + "firewood_crates__thiserror-2.0.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror/2.0.17/download" + ], + "strip_prefix": "thiserror-2.0.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"thiserror\",\n deps = [\n \"@firewood_crates__thiserror-2.0.17//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__thiserror-impl-2.0.17//:thiserror_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.17\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"thiserror\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.0.17\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__thiserror-impl-2.0.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror-impl/2.0.17/download" + ], + "strip_prefix": "thiserror-impl-2.0.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"thiserror_impl\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.17\",\n)\n" + } + }, + "firewood_crates__tikv-jemalloc-sys-0.6.1-5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tikv-jemalloc-sys/0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/download" + ], + "strip_prefix": "tikv-jemalloc-sys-0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tikv_jemalloc_sys\",\n deps = [\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__tikv-jemalloc-sys-0.6.1-5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"background_threads_runtime_support\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tikv-jemalloc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"background_threads_runtime_support\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@firewood_crates__cc-1.2.46//:cc\",\n ],\n edition = \"2018\",\n links = \"jemalloc\",\n pkg_name = \"tikv-jemalloc-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tikv-jemalloc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__tikv-jemallocator-0.6.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tikv-jemallocator/0.6.1/download" + ], + "strip_prefix": "tikv-jemallocator-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tikv_jemallocator\",\n deps = [\n \"@firewood_crates__libc-0.2.177//:libc\",\n \"@firewood_crates__tikv-jemalloc-sys-0.6.1-5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7//:tikv_jemalloc_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"background_threads_runtime_support\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tikv-jemallocator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "firewood_crates__tiny-keccak-2.0.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tiny-keccak/2.0.2/download" + ], + "strip_prefix": "tiny-keccak-2.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tiny_keccak\",\n deps = [\n \"@firewood_crates__crunchy-0.2.4//:crunchy\",\n \"@firewood_crates__tiny-keccak-2.0.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"keccak\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tiny-keccak\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"keccak\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"tiny-keccak\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tiny-keccak\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.0.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__tinystr-0.8.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinystr/0.8.2/download" + ], + "strip_prefix": "tinystr-0.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinystr\",\n deps = [\n \"@firewood_crates__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinystr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.2\",\n)\n" + } + }, + "firewood_crates__tinytemplate-1.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinytemplate/1.2.1/download" + ], + "strip_prefix": "tinytemplate-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinytemplate\",\n deps = [\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_json-1.0.145//:serde_json\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinytemplate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "firewood_crates__tokio-1.48.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio/1.48.0/download" + ], + "strip_prefix": "tokio-1.48.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__mio-1.1.0//:mio\",\n \"@firewood_crates__parking_lot-0.12.5//:parking_lot\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-apple-darwin\n \"@firewood_crates__socket2-0.6.1//:socket2\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # aarch64-unknown-linux-gnu\n \"@firewood_crates__socket2-0.6.1//:socket2\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__socket2-0.6.1//:socket2\", # x86_64-pc-windows-msvc\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu\n \"@firewood_crates__socket2-0.6.1//:socket2\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@firewood_crates__socket2-0.6.1//:socket2\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bytes\",\n \"default\",\n \"io-std\",\n \"io-util\",\n \"libc\",\n \"mio\",\n \"net\",\n \"parking_lot\",\n \"rt\",\n \"rt-multi-thread\",\n \"socket2\",\n \"sync\",\n \"time\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"windows-sys\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.48.0\",\n)\n" + } + }, + "firewood_crates__tokio-rustls-0.26.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-rustls/0.26.4/download" + ], + "strip_prefix": "tokio-rustls-0.26.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_rustls\",\n deps = [\n \"@firewood_crates__rustls-0.23.35//:rustls\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.4\",\n)\n" + } + }, + "firewood_crates__tokio-stream-0.1.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-stream/0.1.17/download" + ], + "strip_prefix": "tokio-stream-0.1.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_stream\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-stream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.17\",\n)\n" + } + }, + "firewood_crates__tokio-util-0.7.17": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-util/0.7.17/download" + ], + "strip_prefix": "tokio-util-0.7.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_util\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-sink-0.3.31//:futures_sink\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"codec\",\n \"default\",\n \"io\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.17\",\n)\n" + } + }, + "firewood_crates__toml-0.9.8": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml/0.9.8/download" + ], + "strip_prefix": "toml-0.9.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml\",\n deps = [\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n \"@firewood_crates__serde_spanned-1.0.3//:serde_spanned\",\n \"@firewood_crates__toml_datetime-0.7.3//:toml_datetime\",\n \"@firewood_crates__toml_parser-1.0.4//:toml_parser\",\n \"@firewood_crates__toml_writer-1.0.4//:toml_writer\",\n \"@firewood_crates__winnow-0.7.13//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"display\",\n \"parse\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.8\",\n)\n" + } + }, + "firewood_crates__toml_datetime-0.7.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_datetime/0.7.3/download" + ], + "strip_prefix": "toml_datetime-0.7.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_datetime\",\n deps = [\n \"@firewood_crates__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_datetime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.3\",\n)\n" + } + }, + "firewood_crates__toml_edit-0.23.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_edit/0.23.7/download" + ], + "strip_prefix": "toml_edit-0.23.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_edit\",\n deps = [\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__toml_datetime-0.7.3//:toml_datetime\",\n \"@firewood_crates__toml_parser-1.0.4//:toml_parser\",\n \"@firewood_crates__winnow-0.7.13//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"parse\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_edit\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.23.7\",\n)\n" + } + }, + "firewood_crates__toml_parser-1.0.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_parser/1.0.4/download" + ], + "strip_prefix": "toml_parser-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_parser\",\n deps = [\n \"@firewood_crates__winnow-0.7.13//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_parser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "firewood_crates__toml_writer-1.0.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_writer/1.0.4/download" + ], + "strip_prefix": "toml_writer-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_writer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_writer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "firewood_crates__tonic-0.14.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tonic/0.14.2/download" + ], + "strip_prefix": "tonic-0.14.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tonic\",\n deps = [\n \"@firewood_crates__base64-0.22.1//:base64\",\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__http-body-1.0.1//:http_body\",\n \"@firewood_crates__http-body-util-0.1.3//:http_body_util\",\n \"@firewood_crates__hyper-1.8.1//:hyper\",\n \"@firewood_crates__hyper-timeout-0.5.2//:hyper_timeout\",\n \"@firewood_crates__hyper-util-0.1.18//:hyper_util\",\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\",\n \"@firewood_crates__pin-project-1.1.10//:pin_project\",\n \"@firewood_crates__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tokio-stream-0.1.17//:tokio_stream\",\n \"@firewood_crates__tower-0.5.2//:tower\",\n \"@firewood_crates__tower-layer-0.3.3//:tower_layer\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__async-trait-0.1.89//:async_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"channel\",\n \"codegen\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tonic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.2\",\n)\n" + } + }, + "firewood_crates__tonic-prost-0.14.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tonic-prost/0.14.2/download" + ], + "strip_prefix": "tonic-prost-0.14.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tonic_prost\",\n deps = [\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__prost-0.14.1//:prost\",\n \"@firewood_crates__tonic-0.14.2//:tonic\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tonic-prost\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.2\",\n)\n" + } + }, + "firewood_crates__tower-0.5.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower/0.5.2/download" + ], + "strip_prefix": "tower-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower\",\n deps = [\n \"@firewood_crates__futures-core-0.3.31//:futures_core\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n \"@firewood_crates__indexmap-2.12.0//:indexmap\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__slab-0.4.11//:slab\",\n \"@firewood_crates__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@firewood_crates__tokio-1.48.0//:tokio\",\n \"@firewood_crates__tokio-util-0.7.17//:tokio_util\",\n \"@firewood_crates__tower-layer-0.3.3//:tower_layer\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n \"@firewood_crates__tracing-0.1.41//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__common\",\n \"balance\",\n \"buffer\",\n \"discover\",\n \"futures-core\",\n \"futures-util\",\n \"indexmap\",\n \"limit\",\n \"load\",\n \"load-shed\",\n \"make\",\n \"pin-project-lite\",\n \"ready-cache\",\n \"slab\",\n \"sync_wrapper\",\n \"tokio\",\n \"tokio-util\",\n \"tracing\",\n \"util\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"retry\", # aarch64-apple-darwin\n \"timeout\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"retry\", # aarch64-unknown-linux-gnu\n \"timeout\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"retry\", # x86_64-pc-windows-msvc\n \"timeout\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"retry\", # x86_64-unknown-linux-gnu\n \"timeout\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"retry\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"timeout\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "firewood_crates__tower-http-0.6.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-http/0.6.6/download" + ], + "strip_prefix": "tower-http-0.6.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_http\",\n deps = [\n \"@firewood_crates__bitflags-2.10.0//:bitflags\",\n \"@firewood_crates__bytes-1.11.0//:bytes\",\n \"@firewood_crates__futures-util-0.3.31//:futures_util\",\n \"@firewood_crates__http-1.3.1//:http\",\n \"@firewood_crates__http-body-1.0.1//:http_body\",\n \"@firewood_crates__iri-string-0.7.9//:iri_string\",\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__tower-0.5.2//:tower\",\n \"@firewood_crates__tower-layer-0.3.3//:tower_layer\",\n \"@firewood_crates__tower-service-0.3.3//:tower_service\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"follow-redirect\",\n \"futures-util\",\n \"iri-string\",\n \"tower\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.6\",\n)\n" + } + }, + "firewood_crates__tower-layer-0.3.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-layer/0.3.3/download" + ], + "strip_prefix": "tower-layer-0.3.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_layer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-layer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.3\",\n)\n" + } + }, + "firewood_crates__tower-service-0.3.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-service/0.3.3/download" + ], + "strip_prefix": "tower-service-0.3.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_service\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-service\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.3\",\n)\n" + } + }, + "firewood_crates__tracing-0.1.41": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing/0.1.41/download" + ], + "strip_prefix": "tracing-0.1.41", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tracing\",\n deps = [\n \"@firewood_crates__pin-project-lite-0.2.16//:pin_project_lite\",\n \"@firewood_crates__tracing-core-0.1.34//:tracing_core\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__tracing-attributes-0.1.30//:tracing_attributes\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"attributes\",\n \"default\",\n \"std\",\n \"tracing-attributes\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.41\",\n)\n" + } + }, + "firewood_crates__tracing-attributes-0.1.30": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing-attributes/0.1.30/download" + ], + "strip_prefix": "tracing-attributes-0.1.30", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"tracing_attributes\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing-attributes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.30\",\n)\n" + } + }, + "firewood_crates__tracing-core-0.1.34": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing-core/0.1.34/download" + ], + "strip_prefix": "tracing-core-0.1.34", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tracing_core\",\n deps = [\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"once_cell\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.34\",\n)\n" + } + }, + "firewood_crates__trie-standardmap-0.16.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "684aafb332fae6f83d7fe10b3fbfdbe39a1b3234c4e2a618f030815838519516", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/trie-standardmap/0.16.0/download" + ], + "strip_prefix": "trie-standardmap-0.16.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"trie_standardmap\",\n deps = [\n \"@firewood_crates__hash-db-0.16.0//:hash_db\",\n \"@firewood_crates__keccak-hasher-0.16.0//:keccak_hasher\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=trie-standardmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.0\",\n)\n" + } + }, + "firewood_crates__triomphe-0.1.15": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/triomphe/0.1.15/download" + ], + "strip_prefix": "triomphe-0.1.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"triomphe\",\n deps = [\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__stable_deref_trait-1.2.1//:stable_deref_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"stable_deref_trait\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=triomphe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.15\",\n)\n" + } + }, + "firewood_crates__try-lock-0.2.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/try-lock/0.2.5/download" + ], + "strip_prefix": "try-lock-0.2.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"try_lock\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=try-lock\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.5\",\n)\n" + } + }, + "firewood_crates__trybuild-1.0.114": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/trybuild/1.0.114/download" + ], + "strip_prefix": "trybuild-1.0.114", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"trybuild\",\n deps = [\n \"@firewood_crates__glob-0.3.3//:glob\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n \"@firewood_crates__serde_json-1.0.145//:serde_json\",\n \"@firewood_crates__target-triple-1.0.0//:target_triple\",\n \"@firewood_crates__termcolor-1.4.1//:termcolor\",\n \"@firewood_crates__toml-0.9.8//:toml\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=trybuild\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.114\",\n)\n" + } + }, + "firewood_crates__typed-builder-0.23.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1cce8e9c8115897e896894868ad4ae6851eff0fb7fd33fa95610e0fa93211886", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/typed-builder/0.23.1/download" + ], + "strip_prefix": "typed-builder-0.23.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"typed_builder\",\n proc_macro_deps = [\n \"@firewood_crates__typed-builder-macro-0.23.1//:typed_builder_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typed-builder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.23.1\",\n)\n" + } + }, + "firewood_crates__typed-builder-macro-0.23.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "921d52b8b19b1a455f54fa76a925a1cf49c0d6a7c6b232fc58523400d1f91560", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/typed-builder-macro/0.23.1/download" + ], + "strip_prefix": "typed-builder-macro-0.23.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"typed_builder_macro\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typed-builder-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.23.1\",\n)\n" + } + }, + "firewood_crates__typenum-1.19.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/typenum/1.19.0/download" + ], + "strip_prefix": "typenum-1.19.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"typenum\",\n deps = [\n \"@firewood_crates__typenum-1.19.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typenum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.19.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"typenum\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typenum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.19.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__uint-0.10.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/uint/0.10.0/download" + ], + "strip_prefix": "uint-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"uint\",\n deps = [\n \"@firewood_crates__byteorder-1.5.0//:byteorder\",\n \"@firewood_crates__crunchy-0.2.4//:crunchy\",\n \"@firewood_crates__hex-0.4.3//:hex\",\n \"@firewood_crates__static_assertions-1.1.0//:static_assertions\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=uint\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "firewood_crates__unarray-0.1.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unarray/0.1.4/download" + ], + "strip_prefix": "unarray-0.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unarray\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unarray\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.4\",\n)\n" + } + }, + "firewood_crates__unicode-ident-1.0.22": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-ident/1.0.22/download" + ], + "strip_prefix": "unicode-ident-1.0.22", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_ident\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-ident\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.22\",\n)\n" + } + }, + "firewood_crates__unicode-width-0.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-width/0.2.2/download" + ], + "strip_prefix": "unicode-width-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_width\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cjk\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-width\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "firewood_crates__unicode-xid-0.2.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-xid/0.2.6/download" + ], + "strip_prefix": "unicode-xid-0.2.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_xid\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-xid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.6\",\n)\n" + } + }, + "firewood_crates__unit-prefix-0.5.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unit-prefix/0.5.2/download" + ], + "strip_prefix": "unit-prefix-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unit_prefix\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unit-prefix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "firewood_crates__untrusted-0.9.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/untrusted/0.9.0/download" + ], + "strip_prefix": "untrusted-0.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"untrusted\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=untrusted\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.0\",\n)\n" + } + }, + "firewood_crates__url-2.5.7": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/url/2.5.7/download" + ], + "strip_prefix": "url-2.5.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"url\",\n deps = [\n \"@firewood_crates__form_urlencoded-1.2.2//:form_urlencoded\",\n \"@firewood_crates__idna-1.1.0//:idna\",\n \"@firewood_crates__percent-encoding-2.3.2//:percent_encoding\",\n \"@firewood_crates__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=url\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.5.7\",\n)\n" + } + }, + "firewood_crates__utf8_iter-1.0.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/utf8_iter/1.0.4/download" + ], + "strip_prefix": "utf8_iter-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"utf8_iter\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=utf8_iter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "firewood_crates__utf8parse-0.2.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/utf8parse/0.2.2/download" + ], + "strip_prefix": "utf8parse-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"utf8parse\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=utf8parse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "firewood_crates__uuid-1.18.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/uuid/1.18.1/download" + ], + "strip_prefix": "uuid-1.18.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"uuid\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=uuid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.18.1\",\n)\n" + } + }, + "firewood_crates__value-log-1.9.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "62fc7c4ce161f049607ecea654dca3f2d727da5371ae85e2e4f14ce2b98ed67c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/value-log/1.9.0/download" + ], + "strip_prefix": "value-log-1.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"value_log\",\n deps = [\n \"@firewood_crates__byteorder-1.5.0//:byteorder\",\n \"@firewood_crates__byteview-0.6.1//:byteview\",\n \"@firewood_crates__interval-heap-0.0.5//:interval_heap\",\n \"@firewood_crates__log-0.4.28//:log\",\n \"@firewood_crates__path-absolutize-3.1.1//:path_absolutize\",\n \"@firewood_crates__rustc-hash-2.1.1//:rustc_hash\",\n \"@firewood_crates__tempfile-3.23.0//:tempfile\",\n \"@firewood_crates__varint-rs-2.2.0//:varint_rs\",\n \"@firewood_crates__xxhash-rust-0.8.15//:xxhash_rust\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=value-log\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.9.0\",\n)\n" + } + }, + "firewood_crates__varint-rs-2.2.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/varint-rs/2.2.0/download" + ], + "strip_prefix": "varint-rs-2.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"varint_rs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"signed\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=varint-rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.2.0\",\n)\n" + } + }, + "firewood_crates__version_check-0.9.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/version_check/0.9.5/download" + ], + "strip_prefix": "version_check-0.9.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"version_check\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=version_check\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.5\",\n)\n" + } + }, + "firewood_crates__wait-timeout-0.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wait-timeout/0.2.1/download" + ], + "strip_prefix": "wait-timeout-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wait_timeout\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__libc-0.2.177//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wait-timeout\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "firewood_crates__walkdir-2.5.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/walkdir/2.5.0/download" + ], + "strip_prefix": "walkdir-2.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"walkdir\",\n deps = [\n \"@firewood_crates__same-file-1.0.6//:same_file\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__winapi-util-0.1.11//:winapi_util\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=walkdir\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.5.0\",\n)\n" + } + }, + "firewood_crates__want-0.3.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/want/0.3.1/download" + ], + "strip_prefix": "want-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"want\",\n deps = [\n \"@firewood_crates__try-lock-0.2.5//:try_lock\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=want\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "firewood_crates__wasi-0.11.1-wasi-snapshot-preview1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download" + ], + "strip_prefix": "wasi-0.11.1+wasi-snapshot-preview1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1+wasi-snapshot-preview1\",\n)\n" + } + }, + "firewood_crates__wasip2-1.0.1-wasi-0.2.4": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasip2/1.0.1+wasi-0.2.4/download" + ], + "strip_prefix": "wasip2-1.0.1+wasi-0.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasip2\",\n deps = [\n \"@firewood_crates__wit-bindgen-0.46.0//:wit_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasip2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1+wasi-0.2.4\",\n)\n" + } + }, + "firewood_crates__wasix-0.12.21": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasix/0.12.21/download" + ], + "strip_prefix": "wasix-0.12.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasix\",\n deps = [\n \"@firewood_crates__wasi-0.11.1-wasi-snapshot-preview1//:wasi\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.21\",\n)\n" + } + }, + "firewood_crates__wasm-bindgen-0.2.105": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen/0.2.105/download" + ], + "strip_prefix": "wasm-bindgen-0.2.105", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__wasm-bindgen-0.2.105//:build_script_build\",\n \"@firewood_crates__wasm-bindgen-shared-0.2.105//:wasm_bindgen_shared\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__wasm-bindgen-macro-0.2.105//:wasm_bindgen_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.105\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n aliases = {\n \"@firewood_crates__rustversion-1.0.22//:rustversion\": \"rustversion_compat\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@firewood_crates__wasm-bindgen-shared-0.2.105//:wasm_bindgen_shared\",\n ],\n edition = \"2021\",\n pkg_name = \"wasm-bindgen\",\n proc_macro_deps = [\n \"@firewood_crates__rustversion-1.0.22//:rustversion\",\n ],\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.105\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__wasm-bindgen-futures-0.4.55": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-futures/0.4.55/download" + ], + "strip_prefix": "wasm-bindgen-futures-0.4.55", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_futures\",\n deps = [\n \"@firewood_crates__cfg-if-1.0.4//:cfg_if\",\n \"@firewood_crates__js-sys-0.3.82//:js_sys\",\n \"@firewood_crates__once_cell-1.21.3//:once_cell\",\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-futures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.55\",\n)\n" + } + }, + "firewood_crates__wasm-bindgen-macro-0.2.105": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-macro/0.2.105/download" + ], + "strip_prefix": "wasm-bindgen-macro-0.2.105", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"wasm_bindgen_macro\",\n deps = [\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__wasm-bindgen-macro-support-0.2.105//:wasm_bindgen_macro_support\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.105\",\n)\n" + } + }, + "firewood_crates__wasm-bindgen-macro-support-0.2.105": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.105/download" + ], + "strip_prefix": "wasm-bindgen-macro-support-0.2.105", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_macro_support\",\n deps = [\n \"@firewood_crates__bumpalo-3.19.0//:bumpalo\",\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n \"@firewood_crates__wasm-bindgen-shared-0.2.105//:wasm_bindgen_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-macro-support\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.105\",\n)\n" + } + }, + "firewood_crates__wasm-bindgen-shared-0.2.105": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-shared/0.2.105/download" + ], + "strip_prefix": "wasm-bindgen-shared-0.2.105", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_shared\",\n deps = [\n \"@firewood_crates__unicode-ident-1.0.22//:unicode_ident\",\n \"@firewood_crates__wasm-bindgen-shared-0.2.105//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.105\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"wasm_bindgen\",\n pkg_name = \"wasm-bindgen-shared\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.105\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__weak-table-0.3.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/weak-table/0.3.2/download" + ], + "strip_prefix": "weak-table-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"weak_table\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=weak-table\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "firewood_crates__web-sys-0.3.82": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/web-sys/0.3.82/download" + ], + "strip_prefix": "web-sys-0.3.82", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"web_sys\",\n deps = [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\",\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"AbortController\",\n \"AbortSignal\",\n \"Blob\",\n \"BlobPropertyBag\",\n \"EventTarget\",\n \"File\",\n \"FormData\",\n \"Headers\",\n \"ReadableStream\",\n \"Request\",\n \"RequestCache\",\n \"RequestCredentials\",\n \"RequestInit\",\n \"RequestMode\",\n \"Response\",\n \"ServiceWorkerGlobalScope\",\n \"Window\",\n \"WorkerGlobalScope\",\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"CanvasRenderingContext2d\", # wasm32-unknown-unknown\n \"Document\", # wasm32-unknown-unknown\n \"DomRect\", # wasm32-unknown-unknown\n \"DomRectReadOnly\", # wasm32-unknown-unknown\n \"Element\", # wasm32-unknown-unknown\n \"HtmlCanvasElement\", # wasm32-unknown-unknown\n \"HtmlElement\", # wasm32-unknown-unknown\n \"Node\", # wasm32-unknown-unknown\n \"Performance\", # wasm32-unknown-unknown\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=web-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.82\",\n)\n" + } + }, + "firewood_crates__web-time-1.1.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/web-time/1.1.0/download" + ], + "strip_prefix": "web-time-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"web_time\",\n deps = select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@firewood_crates__js-sys-0.3.82//:js_sys\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n \"@firewood_crates__wasm-bindgen-0.2.105//:wasm_bindgen\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=web-time\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "firewood_crates__winapi-0.3.9": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi/0.3.9/download" + ], + "strip_prefix": "winapi-0.3.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi\",\n deps = [\n \"@firewood_crates__winapi-0.3.9//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"libloaderapi\",\n \"memoryapi\",\n \"processthreadsapi\",\n \"profileapi\",\n \"psapi\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.9\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"libloaderapi\",\n \"memoryapi\",\n \"processthreadsapi\",\n \"profileapi\",\n \"psapi\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.9\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__winapi-i686-pc-windows-gnu-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-i686-pc-windows-gnu/0.4.0/download" + ], + "strip_prefix": "winapi-i686-pc-windows-gnu-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_i686_pc_windows_gnu\",\n deps = [\n \"@firewood_crates__winapi-i686-pc-windows-gnu-0.4.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-i686-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi-i686-pc-windows-gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-i686-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__winapi-util-0.1.11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-util/0.1.11/download" + ], + "strip_prefix": "winapi-util-0.1.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_util\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.11\",\n)\n" + } + }, + "firewood_crates__winapi-x86_64-pc-windows-gnu-0.4.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-x86_64-pc-windows-gnu/0.4.0/download" + ], + "strip_prefix": "winapi-x86_64-pc-windows-gnu-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_x86_64_pc_windows_gnu\",\n deps = [\n \"@firewood_crates__winapi-x86_64-pc-windows-gnu-0.4.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-x86_64-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi-x86_64-pc-windows-gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-x86_64-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows-core-0.62.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-core/0.62.2/download" + ], + "strip_prefix": "windows-core-0.62.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_core\",\n deps = [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\",\n \"@firewood_crates__windows-result-0.4.1//:windows_result\",\n \"@firewood_crates__windows-strings-0.5.1//:windows_strings\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__windows-implement-0.60.2//:windows_implement\",\n \"@firewood_crates__windows-interface-0.59.3//:windows_interface\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.62.2\",\n)\n" + } + }, + "firewood_crates__windows-implement-0.60.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-implement/0.60.2/download" + ], + "strip_prefix": "windows-implement-0.60.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"windows_implement\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-implement\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.60.2\",\n)\n" + } + }, + "firewood_crates__windows-interface-0.59.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-interface/0.59.3/download" + ], + "strip_prefix": "windows-interface-0.59.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"windows_interface\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-interface\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.59.3\",\n)\n" + } + }, + "firewood_crates__windows-link-0.2.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-link/0.2.1/download" + ], + "strip_prefix": "windows-link-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_link\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-link\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "firewood_crates__windows-result-0.4.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-result/0.4.1/download" + ], + "strip_prefix": "windows-result-0.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_result\",\n deps = [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-result\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.1\",\n)\n" + } + }, + "firewood_crates__windows-strings-0.5.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-strings/0.5.1/download" + ], + "strip_prefix": "windows-strings-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_strings\",\n deps = [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-strings\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "firewood_crates__windows-sys-0.52.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.52.0/download" + ], + "strip_prefix": "windows-sys-0.52.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@firewood_crates__windows-targets-0.52.6//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.0\",\n)\n" + } + }, + "firewood_crates__windows-sys-0.60.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.60.2/download" + ], + "strip_prefix": "windows-sys-0.60.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@firewood_crates__windows-targets-0.53.5//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Networking\",\n \"Win32_Networking_WinSock\",\n \"Win32_System\",\n \"Win32_System_IO\",\n \"Win32_System_Threading\",\n \"Win32_System_WindowsProgramming\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.60.2\",\n)\n" + } + }, + "firewood_crates__windows-sys-0.61.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.61.2/download" + ], + "strip_prefix": "windows-sys-0.61.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@firewood_crates__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Wdk\",\n \"Wdk_Foundation\",\n \"Wdk_Storage\",\n \"Wdk_Storage_FileSystem\",\n \"Wdk_System\",\n \"Wdk_System_IO\",\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Networking\",\n \"Win32_Networking_WinSock\",\n \"Win32_Security\",\n \"Win32_Security_Authentication\",\n \"Win32_Security_Authentication_Identity\",\n \"Win32_Security_Credentials\",\n \"Win32_Security_Cryptography\",\n \"Win32_Storage\",\n \"Win32_Storage_FileSystem\",\n \"Win32_System\",\n \"Win32_System_Console\",\n \"Win32_System_IO\",\n \"Win32_System_LibraryLoader\",\n \"Win32_System_Memory\",\n \"Win32_System_Pipes\",\n \"Win32_System_SystemInformation\",\n \"Win32_System_SystemServices\",\n \"Win32_System_WindowsProgramming\",\n \"Win32_UI\",\n \"Win32_UI_Input\",\n \"Win32_UI_Input_KeyboardAndMouse\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.61.2\",\n)\n" + } + }, + "firewood_crates__windows-targets-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.52.6/download" + ], + "strip_prefix": "windows-targets-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows_x86_64_msvc-0.52.6//:windows_x86_64_msvc\", # cfg(all(any(target_arch = \"x86_64\", target_arch = \"arm64ec\"), target_env = \"msvc\", not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__windows_x86_64_gnu-0.52.6//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__windows_x86_64_gnu-0.52.6//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n" + } + }, + "firewood_crates__windows-targets-0.53.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.53.5/download" + ], + "strip_prefix": "windows-targets-0.53.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@firewood_crates__windows_x86_64_msvc-0.53.1//:windows_x86_64_msvc\", # cfg(all(any(target_arch = \"x86_64\", target_arch = \"arm64ec\"), target_env = \"msvc\", not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@firewood_crates__windows_x86_64_gnu-0.53.1//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@firewood_crates__windows_x86_64_gnu-0.53.1//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.5\",\n)\n" + } + }, + "firewood_crates__windows_aarch64_gnullvm-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@firewood_crates__windows_aarch64_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_aarch64_gnullvm-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@firewood_crates__windows_aarch64_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_aarch64_msvc-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@firewood_crates__windows_aarch64_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_aarch64_msvc-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.53.1/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@firewood_crates__windows_aarch64_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_gnu-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.52.6/download" + ], + "strip_prefix": "windows_i686_gnu-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@firewood_crates__windows_i686_gnu-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_gnu-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.53.1/download" + ], + "strip_prefix": "windows_i686_gnu-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@firewood_crates__windows_i686_gnu-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_gnullvm-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_i686_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnullvm\",\n deps = [\n \"@firewood_crates__windows_i686_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_gnullvm-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_i686_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnullvm\",\n deps = [\n \"@firewood_crates__windows_i686_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_msvc-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.52.6/download" + ], + "strip_prefix": "windows_i686_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@firewood_crates__windows_i686_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_i686_msvc-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.53.1/download" + ], + "strip_prefix": "windows_i686_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@firewood_crates__windows_i686_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_gnu-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@firewood_crates__windows_x86_64_gnu-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_gnu-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@firewood_crates__windows_x86_64_gnu-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_gnullvm-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@firewood_crates__windows_x86_64_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_gnullvm-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@firewood_crates__windows_x86_64_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_msvc-0.52.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@firewood_crates__windows_x86_64_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__windows_x86_64_msvc-0.53.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@firewood_crates__windows_x86_64_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__winnow-0.7.13": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winnow/0.7.13/download" + ], + "strip_prefix": "winnow-0.7.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winnow\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winnow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.13\",\n)\n" + } + }, + "firewood_crates__wit-bindgen-0.46.0": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-bindgen/0.46.0/download" + ], + "strip_prefix": "wit-bindgen-0.46.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_bindgen\",\n deps = [\n \"@firewood_crates__wit-bindgen-0.46.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.46.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"wit-bindgen\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.46.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__writeable-0.6.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/writeable/0.6.2/download" + ], + "strip_prefix": "writeable-0.6.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"writeable\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=writeable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.2\",\n)\n" + } + }, + "firewood_crates__wyz-0.5.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wyz/0.5.1/download" + ], + "strip_prefix": "wyz-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wyz\",\n deps = [\n \"@firewood_crates__tap-1.0.1//:tap\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wyz\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "firewood_crates__xxhash-rust-0.8.15": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/xxhash-rust/0.8.15/download" + ], + "strip_prefix": "xxhash-rust-0.8.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"xxhash_rust\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"xxh3\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=xxhash-rust\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.15\",\n)\n" + } + }, + "firewood_crates__yoke-0.8.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/yoke/0.8.1/download" + ], + "strip_prefix": "yoke-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"yoke\",\n deps = [\n \"@firewood_crates__stable_deref_trait-1.2.1//:stable_deref_trait\",\n \"@firewood_crates__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__yoke-derive-0.8.1//:yoke_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"zerofrom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=yoke\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "firewood_crates__yoke-derive-0.8.1": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/yoke-derive/0.8.1/download" + ], + "strip_prefix": "yoke-derive-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"yoke_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n \"@firewood_crates__synstructure-0.13.2//:synstructure\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=yoke-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "firewood_crates__zerocopy-0.8.27": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerocopy/0.8.27/download" + ], + "strip_prefix": "zerocopy-0.8.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerocopy\",\n deps = [\n \"@firewood_crates__zerocopy-0.8.27//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__zerocopy-derive-0.8.27//:zerocopy_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"simd\",\n \"zerocopy-derive\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.27\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"simd\",\n \"zerocopy-derive\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"zerocopy\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.27\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "firewood_crates__zerocopy-derive-0.8.27": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerocopy-derive/0.8.27/download" + ], + "strip_prefix": "zerocopy-derive-0.8.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerocopy_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.27\",\n)\n" + } + }, + "firewood_crates__zerofrom-0.1.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerofrom/0.1.6/download" + ], + "strip_prefix": "zerofrom-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerofrom\",\n proc_macro_deps = [\n \"@firewood_crates__zerofrom-derive-0.1.6//:zerofrom_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerofrom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "firewood_crates__zerofrom-derive-0.1.6": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerofrom-derive/0.1.6/download" + ], + "strip_prefix": "zerofrom-derive-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerofrom_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n \"@firewood_crates__synstructure-0.13.2//:synstructure\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerofrom-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "firewood_crates__zeroize-1.8.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zeroize/1.8.2/download" + ], + "strip_prefix": "zeroize-1.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zeroize\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zeroize\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.2\",\n)\n" + } + }, + "firewood_crates__zerotrie-0.2.3": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerotrie/0.2.3/download" + ], + "strip_prefix": "zerotrie-0.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerotrie\",\n deps = [\n \"@firewood_crates__yoke-0.8.1//:yoke\",\n \"@firewood_crates__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"yoke\",\n \"zerofrom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerotrie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.3\",\n)\n" + } + }, + "firewood_crates__zerovec-0.11.5": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerovec/0.11.5/download" + ], + "strip_prefix": "zerovec-0.11.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerovec\",\n deps = [\n \"@firewood_crates__yoke-0.8.1//:yoke\",\n \"@firewood_crates__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@firewood_crates__zerovec-derive-0.11.2//:zerovec_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"yoke\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerovec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.5\",\n)\n" + } + }, + "firewood_crates__zerovec-derive-0.11.2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "patch_args": [ + "-p0" + ], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerovec-derive/0.11.2/download" + ], + "strip_prefix": "zerovec-derive-0.11.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'avalanchego'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerovec_derive\",\n deps = [\n \"@firewood_crates__proc-macro2-1.0.103//:proc_macro2\",\n \"@firewood_crates__quote-1.0.42//:quote\",\n \"@firewood_crates__syn-2.0.110//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerovec-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.2\",\n)\n" + } + } + }, + "moduleExtensionMetadata": { + "useAllRepos": "NO", + "reproducible": false + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features~", + "bazel_features_globals", + "bazel_features~~version_extension~bazel_features_globals" + ], + [ + "bazel_features~", + "bazel_features_version", + "bazel_features~~version_extension~bazel_features_version" + ], + [ + "rules_cc~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_cc~", + "rules_cc", + "rules_cc~" + ], + [ + "rules_rust~", + "bazel_features", + "bazel_features~" + ], + [ + "rules_rust~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_rust~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_rust~", + "rules_cc", + "rules_cc~" + ], + [ + "rules_rust~", + "rules_rust", + "rules_rust~" + ] + ] + } } } } diff --git a/firewood/BUILD.bazel b/firewood/BUILD.bazel new file mode 100644 index 000000000000..969e08488f94 --- /dev/null +++ b/firewood/BUILD.bazel @@ -0,0 +1,4 @@ +exports_files([ + "Cargo.toml", + "Cargo.lock", +]) diff --git a/firewood/Cargo.lock b/firewood/Cargo.lock index f8739b401b1d..9f3d87310afb 100644 --- a/firewood/Cargo.lock +++ b/firewood/Cargo.lock @@ -834,6 +834,13 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "direct-cargo-bazel-deps" +version = "0.0.1" +dependencies = [ + "bytes", +] + [[package]] name = "displaydoc" version = "0.2.5" diff --git a/firewood/ffi/BUILD.bazel b/firewood/ffi/BUILD.bazel new file mode 100644 index 000000000000..a76626f137fa --- /dev/null +++ b/firewood/ffi/BUILD.bazel @@ -0,0 +1,94 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") +load("@rules_rust//rust:defs.bzl", "rust_static_library") + +# Rust static library for firewood FFI +# Note: We use the committed firewood.h header rather than running cbindgen +# at build time. The header should be kept in sync via cargo build locally. +rust_static_library( + name = "firewood_ffi_rust", + srcs = glob(["src/**/*.rs"]), + compile_data = ["README.md"], + crate_features = [ + "ethhash", + "logger", + ], + crate_root = "src/lib.rs", + edition = "2024", + proc_macro_deps = [ + "@firewood_crates//:derive-where", + ], + visibility = ["//visibility:public"], + deps = [ + "//firewood/firewood", + "//firewood/storage:firewood-storage", + "@firewood_crates//:chrono", + "@firewood_crates//:coarsetime", + "@firewood_crates//:env_logger", + "@firewood_crates//:metrics", + "@firewood_crates//:metrics-util", + "@firewood_crates//:oxhttp", + "@firewood_crates//:parking_lot", + ] + select({ + "@platforms//os:linux": ["@firewood_crates//:tikv-jemallocator"], + "@platforms//os:macos": ["@firewood_crates//:tikv-jemallocator"], + "//conditions:default": [], + }), +) + +# C library wrapper for CGO +cc_library( + name = "firewood_ffi", + hdrs = ["firewood.h"], + linkopts = select({ + "@platforms//os:linux": [ + "-lm", + "-ldl", + "-lpthread", + ], + "@platforms//os:macos": ["-lm"], + "//conditions:default": ["-lm"], + }), + visibility = ["//visibility:public"], + deps = [":firewood_ffi_rust"], +) + +# Go library with CGO bindings +# gazelle:ignore +go_library( + name = "ffi", + srcs = [ + "firewood.go", + "firewood.h", + "iterator.go", + "keepalive.go", + "maybe.go", + "memory.go", + "metrics.go", + "proofs.go", + "proposal.go", + "revision.go", + ], + cdeps = [":firewood_ffi"], + cgo = True, + importpath = "github.com/ava-labs/firewood-go-ethhash/ffi", + visibility = ["//visibility:public"], + deps = [ + "@com_github_prometheus_client_golang//prometheus", + "@com_github_prometheus_client_model//go", + "@com_github_prometheus_common//expfmt", + ], +) + +go_test( + name = "ffi_test", + srcs = [ + "firewood_test.go", + "metrics_test.go", + "proofs_test.go", + ], + embed = [":ffi"], + deps = [ + "@com_github_prometheus_client_model//go", + "@com_github_stretchr_testify//require", + ], +) diff --git a/firewood/ffi/tests/eth/BUILD.bazel b/firewood/ffi/tests/eth/BUILD.bazel new file mode 100644 index 000000000000..a2b42548e4e8 --- /dev/null +++ b/firewood/ffi/tests/eth/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "eth_test", + srcs = ["eth_compatibility_test.go"], + deps = [ + "//firewood/ffi", + "@com_github_ava_labs_libevm//common", + "@com_github_ava_labs_libevm//core/rawdb", + "@com_github_ava_labs_libevm//core/state", + "@com_github_ava_labs_libevm//core/types", + "@com_github_ava_labs_libevm//crypto", + "@com_github_ava_labs_libevm//rlp", + "@com_github_ava_labs_libevm//trie/trienode", + "@com_github_ava_labs_libevm//triedb", + "@com_github_holiman_uint256//:uint256", + "@com_github_stretchr_testify//require", + ], +) diff --git a/firewood/ffi/tests/firewood/BUILD.bazel b/firewood/ffi/tests/firewood/BUILD.bazel new file mode 100644 index 000000000000..d6633815021e --- /dev/null +++ b/firewood/ffi/tests/firewood/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "firewood_test", + srcs = ["merkle_compatibility_test.go"], + deps = [ + "//database", + "//database/memdb", + "//ids", + "//x/merkledb", + "@com_github_stretchr_testify//require", + ], +) diff --git a/firewood/firewood-macros/BUILD.bazel b/firewood/firewood-macros/BUILD.bazel new file mode 100644 index 000000000000..b5bef6da30fa --- /dev/null +++ b/firewood/firewood-macros/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_rust//rust:defs.bzl", "rust_proc_macro") + +exports_files(["Cargo.toml"]) + +rust_proc_macro( + name = "firewood-macros", + srcs = glob(["src/**/*.rs"]), + crate_root = "src/lib.rs", + edition = "2024", + visibility = ["//firewood:__subpackages__"], + deps = [ + "@firewood_crates//:proc-macro2", + "@firewood_crates//:quote", + "@firewood_crates//:syn", + ], +) diff --git a/firewood/firewood/BUILD.bazel b/firewood/firewood/BUILD.bazel new file mode 100644 index 000000000000..2aa4d49a5a7b --- /dev/null +++ b/firewood/firewood/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +exports_files(["Cargo.toml"]) + +rust_library( + name = "firewood", + srcs = glob(["src/**/*.rs"]), + crate_features = [ + "ethhash", + "logger", + ], + crate_root = "src/lib.rs", + edition = "2024", + proc_macro_deps = [ + "//firewood/firewood-macros", + "@firewood_crates//:bytemuck_derive", + "@firewood_crates//:derive-where", + ], + visibility = ["//firewood:__subpackages__"], + deps = [ + "//firewood/storage:firewood-storage", + "@firewood_crates//:bytemuck", + "@firewood_crates//:coarsetime", + "@firewood_crates//:fastrace", + "@firewood_crates//:fjall", + "@firewood_crates//:hex", + "@firewood_crates//:integer-encoding", + "@firewood_crates//:metrics", + "@firewood_crates//:parking_lot", + "@firewood_crates//:rayon", + "@firewood_crates//:thiserror", + "@firewood_crates//:typed-builder", + "@firewood_crates//:weak-table", + ], +) diff --git a/firewood/storage/BUILD.bazel b/firewood/storage/BUILD.bazel new file mode 100644 index 000000000000..c9f9e487a021 --- /dev/null +++ b/firewood/storage/BUILD.bazel @@ -0,0 +1,45 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +exports_files(["Cargo.toml"]) + +rust_library( + name = "firewood-storage", + srcs = glob(["src/**/*.rs"]), + crate_features = [ + "ethhash", + "logger", + ], + crate_root = "src/lib.rs", + edition = "2024", + proc_macro_deps = [ + "@firewood_crates//:aquamarine", + "@firewood_crates//:bytemuck_derive", + "@firewood_crates//:derive-where", + "@firewood_crates//:enum-as-inner", + ], + visibility = ["//firewood:__subpackages__"], + deps = [ + "@firewood_crates//:bitfield", + "@firewood_crates//:bitflags", + "@firewood_crates//:bumpalo", + "@firewood_crates//:bytemuck", + "@firewood_crates//:bytes", + "@firewood_crates//:coarsetime", + "@firewood_crates//:fastrace", + "@firewood_crates//:hex", + "@firewood_crates//:indicatif", + "@firewood_crates//:integer-encoding", + "@firewood_crates//:log", + "@firewood_crates//:lru", + "@firewood_crates//:metrics", + "@firewood_crates//:nonzero_ext", + "@firewood_crates//:parking_lot", + "@firewood_crates//:rlp", + "@firewood_crates//:semver", + "@firewood_crates//:sha2", + "@firewood_crates//:sha3", + "@firewood_crates//:smallvec", + "@firewood_crates//:thiserror", + "@firewood_crates//:triomphe", + ], +) diff --git a/firewood/triehash/BUILD.bazel b/firewood/triehash/BUILD.bazel new file mode 100644 index 000000000000..14ef8e046b94 --- /dev/null +++ b/firewood/triehash/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +exports_files(["Cargo.toml"]) + +rust_library( + name = "firewood-triehash", + srcs = glob(["src/**/*.rs"]), + crate_root = "src/lib.rs", + edition = "2024", + visibility = ["//firewood:__subpackages__"], + deps = [ + "@firewood_crates//:hash-db", + "@firewood_crates//:rlp", + ], +) diff --git a/graft/coreth/core/BUILD.bazel b/graft/coreth/core/BUILD.bazel index 99548dbd955a..779b12baa387 100644 --- a/graft/coreth/core/BUILD.bazel +++ b/graft/coreth/core/BUILD.bazel @@ -32,6 +32,7 @@ go_library( importpath = "github.com/ava-labs/avalanchego/graft/coreth/core", visibility = ["//visibility:public"], deps = [ + "//firewood/ffi", "//graft/coreth/consensus", "//graft/coreth/core/extstate", "//graft/coreth/core/state/snapshot", @@ -53,7 +54,6 @@ go_library( "//vms/evm/acp176", "//vms/evm/acp226", "//vms/evm/predicate", - "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", "@com_github_ava_labs_libevm//common", "@com_github_ava_labs_libevm//common/bitutil", "@com_github_ava_labs_libevm//common/hexutil", diff --git a/graft/coreth/go.mod b/graft/coreth/go.mod index 8ed6774b6dc6..db89c72a6a40 100644 --- a/graft/coreth/go.mod +++ b/graft/coreth/go.mod @@ -190,3 +190,5 @@ tool ( ) replace github.com/ava-labs/avalanchego => ../../ + +replace github.com/ava-labs/firewood-go-ethhash/ffi => ../../firewood/ffi diff --git a/graft/coreth/plugin/evm/BUILD.bazel b/graft/coreth/plugin/evm/BUILD.bazel index b6c788d8f0b6..66034994ab4f 100644 --- a/graft/coreth/plugin/evm/BUILD.bazel +++ b/graft/coreth/plugin/evm/BUILD.bazel @@ -25,6 +25,7 @@ go_library( "//database", "//database/prefixdb", "//database/versiondb", + "//firewood/ffi", "//graft/coreth/consensus/dummy", "//graft/coreth/constants", "//graft/coreth/core", @@ -82,7 +83,6 @@ go_library( "//vms/evm/acp226", "//vms/evm/database", "//vms/evm/metrics/prometheus", - "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", "@com_github_ava_labs_libevm//common", "@com_github_ava_labs_libevm//core/rawdb", "@com_github_ava_labs_libevm//core/types", diff --git a/graft/coreth/tests/utils/BUILD.bazel b/graft/coreth/tests/utils/BUILD.bazel index 194bfa9f162b..745081923e43 100644 --- a/graft/coreth/tests/utils/BUILD.bazel +++ b/graft/coreth/tests/utils/BUILD.bazel @@ -23,7 +23,7 @@ go_library( "@com_github_ava_labs_libevm//crypto", "@com_github_ava_labs_libevm//log", "@com_github_ava_labs_libevm//params", - "@com_github_go_cmd_cmd//:go_default_library", + "@com_github_go_cmd_cmd//:cmd", "@com_github_onsi_ginkgo_v2//:ginkgo", "@com_github_stretchr_testify//require", ], diff --git a/graft/coreth/triedb/firewood/BUILD.bazel b/graft/coreth/triedb/firewood/BUILD.bazel index 064d76fdf2c9..e5ceab92474d 100644 --- a/graft/coreth/triedb/firewood/BUILD.bazel +++ b/graft/coreth/triedb/firewood/BUILD.bazel @@ -10,7 +10,7 @@ go_library( importpath = "github.com/ava-labs/avalanchego/graft/coreth/triedb/firewood", visibility = ["//visibility:public"], deps = [ - "@com_github_ava_labs_firewood_go_ethhash_ffi//:ffi", + "//firewood/ffi", "@com_github_ava_labs_libevm//common", "@com_github_ava_labs_libevm//core/rawdb", "@com_github_ava_labs_libevm//core/types", diff --git a/network/p2p/gossip/BUILD.bazel b/network/p2p/gossip/BUILD.bazel index 0945a2deb708..dac07c2beda6 100644 --- a/network/p2p/gossip/BUILD.bazel +++ b/network/p2p/gossip/BUILD.bazel @@ -8,7 +8,6 @@ go_library( "gossipable.go", "handler.go", "message.go", - "test_gossip.go", ], importpath = "github.com/ava-labs/avalanchego/network/p2p/gossip", visibility = ["//visibility:public"], @@ -43,6 +42,7 @@ go_test( "//snow/engine/enginetest", "//snow/validators", "//snow/validators/validatorstest", + "//utils/bloom", "//utils/constants", "//utils/logging", "//utils/set", @@ -51,6 +51,5 @@ go_test( "@com_github_prometheus_client_golang//prometheus/testutil", "@com_github_stretchr_testify//require", "@org_golang_google_protobuf//proto", - "@org_golang_x_exp//maps", ], )
    Changelog

    Sourced from lru's changelog.

    v0.15.0 - 2025-06-26

    • Return bool from promote and demote to indicate whether key was found.

    v0.14.0 - 2025-04-12

    • Use NonZeroUsize::MAX instead of unwrap(), and update MSRV to 1.70.0.

    v0.13.0 - 2025-01-27

    • Add peek_mru and pop_mru methods, upgrade dependency on hashbrown to 0.15.2, and update MSRV to 1.65.0.

    v0.12.5 - 2024-10-30

    • Upgrade hashbrown dependency to 0.15.

    v0.12.4 - 2024-07-30

    • Add methods that take a reference to the key that should be inserted.

    v0.12.3 - 2024-02-24

    • Add get_key_value_mut method.

    v0.12.2 - 2024-01-28

    • Add clone method.

    v0.12.1 - 2023-11-21

    • Add get_key_value method.

    v0.12.0 - 2023-10-03

    • Add lifetime specifier to try_get_or_insert_mut.
    • Add BuildHasher trait bound to Debug for LruCache.

    v0.11.1 - 2023-09-05

    • Add try_get_or_insert_mut method.

    v0.11.0 - 2023-07-11

    • Update dependency on hashbrown to 0.14 and update MSRV to 1.64.0.

    v0.10.1 - 2023-06-29

    • Add try_get_or_insert method.

    v0.10.0 - 2023-03-04

    ... (truncated)